1. Wprowadzenie
Last Updated: 2024-11-01
Jak zmodernizować starą aplikację PHP w Google Cloud?
(📽️ obejrzyj 7-minutowy film wprowadzający do tego ćwiczenia z programowania)
Często zdarza się, że w środowisku lokalnym działają starsze aplikacje, które wymagają modernizacji. Oznacza to, że muszą być skalowalne, bezpieczne i możliwe do wdrożenia w różnych środowiskach.
W tym warsztacie:
- Skonteneryzuj aplikację PHP.
- Przejdź na zarządzaną usługę bazy danych ( Cloud SQL).
- Wdrażanie w Cloud Run (alternatywa dla GKE/Kubernetes, która nie wymaga żadnych operacji).
- Zabezpiecz aplikację za pomocą usług Identity and Access Management (IAM) i Secret Manager.
- Zdefiniuj potok CI/CD za pomocą Cloud Build. Cloud Build można połączyć z repozytorium Git hostowanym u popularnych dostawców Git, takich jak GitHub czy GitLab, i uruchamiać przy każdym przesłaniu do głównego repozytorium.
- Przechowuj zdjęcia aplikacji w Cloud Storage. Można to osiągnąć przez podłączanie, a zmiana aplikacji nie wymaga kodu.
- Wprowadzenie funkcji generatywnej AI za pomocą Gemini, zarządzanych przez Cloud Functions (bezserwerowe).
- Zapoznaj się z SLO i obsługą nowej aplikacji.
Wykonując te czynności, możesz stopniowo modernizować aplikację PHP, zwiększając jej skalowalność, bezpieczeństwo i elastyczność wdrażania. Przeniesienie aplikacji do Google Cloud umożliwia też korzystanie z potężnej infrastruktury i usług, które zapewniają jej płynne działanie w środowisku natywnym dla chmury.
Uważamy, że wiedzę zdobytą dzięki tym prostym krokom możesz wykorzystać w przypadku własnej aplikacji i organizacji, które korzystają z innego języka lub stosu technologicznego i mają inne przypadki użycia.
Informacje o aplikacji
Aplikacja ( kod na licencji MIT), którą sklonujesz, to podstawowa aplikacja PHP 5.7 z uwierzytelnianiem MySQL. Głównym założeniem aplikacji jest udostępnienie platformy, na której użytkownicy mogą przesyłać zdjęcia, a administratorzy mogą oznaczać nieodpowiednie obrazy. Aplikacja ma 2 tabele:
- Użytkownicy Jest wstępnie skompilowany z administratorami. Nowi użytkownicy mogą się rejestrować.
- Grafika Zawiera kilka przykładowych obrazów. Zalogowani użytkownicy mogą przesyłać nowe zdjęcia. Dodamy tu trochę magii.
Twój cel
Chcemy zmodernizować starą aplikację, aby można było jej używać w Google Cloud. Będziemy korzystać z narzędzi i usług tej platformy, aby zwiększać skalowalność, poprawiać bezpieczeństwo, automatyzować zarządzanie infrastrukturą i integrować funkcje zaawansowane, takie jak przetwarzanie obrazów, monitorowanie i przechowywanie danych, za pomocą usług takich jak Cloud SQL, Cloud Run, Cloud Build i Secret Manager.

Co ważniejsze, chcemy to zrobić krok po kroku, abyś mógł nauczyć się, jakie jest uzasadnienie każdego z nich. Zwykle każdy krok otwiera nowe możliwości dla kolejnych (np. moduły 2–3 i 6–7).
Nadal się zastanawiasz? Obejrzyj ten 7-minutowy film w YouTube.
Czego potrzebujesz
- komputer z przeglądarką i połączeniem internetowym,
- niektóre środki GCP; Zobacz następny krok.
- Będziesz używać Cloud Shell. Zawiera wszystkie preinstalowane polecenia, których będziesz potrzebować, oraz zintegrowane środowisko programistyczne.
- Konto GitHub. Jest to potrzebne do rozwidlenia oryginalnego kodu 🧑🏻💻 gdgpescara/app-mod-workshop w Twoim repozytorium Git. Jest to potrzebne, aby mieć własny potok CI/CD (automatyczne zatwierdzanie –> kompilacja –> wdrażanie).
Przykładowe rozwiązania znajdziesz tutaj:
- Repozytorium autora: https://github.com/Friends-of-Ricc/app-mod-workshop
- Oryginalne repozytorium warsztatów w folderach
.solutions/, w podziale na rozdziały.
Te warsztaty są przeznaczone do przeprowadzenia w Cloud Shell (w przeglądarce).
Możesz też spróbować zrobić to na komputerze lokalnym.
2. Konfigurowanie kredytu i Fork

Wykorzystaj środki w GCP i skonfiguruj środowisko GCP [opcjonalnie]
Aby przeprowadzić te warsztaty, musisz mieć konto rozliczeniowe z pewną ilością środków. Jeśli masz już własną formę płatności, możesz pominąć ten krok.
Utwórz nowe konto Google Gmail (*), które połączysz ze środkami w GCP. Poproś nauczyciela o link do wykorzystania środków w GCP lub użyj środków tutaj: bit.ly/PHP-Amarcord-credits .
Zaloguj się na nowo utworzone konto i postępuj zgodnie z instrukcjami.

(
) Dlaczego potrzebuję nowego konta Gmail?*
Zdarza się, że użytkownicy nie mogą ukończyć laboratorium, ponieważ ich konto (zwłaszcza służbowe lub szkolne adresy e-mail) było wcześniej używane w GCP i zasady organizacji ograniczają ich możliwości. Zalecamy utworzenie nowego konta Gmail lub użycie istniejącego konta Gmail (gmail.com), które nie było wcześniej używane w GCP.
Kliknij przycisk, aby wykorzystać środki.

Wypełnij poniższy formularz, podając imię i nazwisko, oraz zaakceptuj Warunki.
Zanim konto rozliczeniowe pojawi się na stronie https://console.cloud.google.com/billing, może minąć kilka sekund.
Po zakończeniu otwórz konsolę Google Cloud i utwórz nowy projekt, klikając selektor projektu w lewym górnym menu, w którym wyświetla się „Brak organizacji”. Szczegółowe informacje znajdziesz poniżej

Jeśli nie masz projektu, utwórz nowy, jak pokazano na zrzucie ekranu poniżej. W prawym górnym rogu znajduje się opcja „NOWY PROJEKT”.

Pamiętaj, aby połączyć nowy projekt z kontem rozliczeniowym w ramach okresu próbnego GCP w ten sposób:

Możesz już korzystać z Google Cloud Platform. Jeśli jesteś początkującym użytkownikiem lub chcesz wszystko robić w środowisku chmurowym, możesz uzyskać dostęp do Cloud Shell i jego edytora, klikając przycisk w lewym górnym rogu, jak pokazano poniżej.

Sprawdź, czy w lewym górnym rogu wybrany jest nowy projekt:
Nie wybrano (źle):

Wybrano (dobrze):

Utwórz rozwidlenie aplikacji z GitHuba
- Otwórz aplikację w wersji demonstracyjnej: https://github.com/gdgpescara/app-mod-workshop
- Kliknij 🍴 fork.
- Jeśli nie masz konta GitHub, musisz utworzyć nowe.
- Edytuj elementy w dowolny sposób.

- Sklonuj kod aplikacji za pomocą
git clonehttps://github.com/YOUR-GITHUB-USER/YOUR-REPO-NAME
- Otwórz sklonowany folder projektu w ulubionym edytorze. Jeśli wybierzesz Cloud Shell, możesz to zrobić, klikając „Otwórz edytor”, jak pokazano poniżej.

Wszystko, czego potrzebujesz, znajdziesz w edytorze Google Cloud Shell, jak pokazano na ilustracji poniżej.

Możesz to zrobić wizualnie, klikając „Otwórz folder” i wybierając folder, prawdopodobnie app-mod-workshop w folderze domowym.
3. Moduł 1. Tworzenie instancji SQL
Utwórz instancję Google Cloud SQL
Nasza aplikacja PHP będzie łączyć się z bazą danych MySQL, dlatego musimy ją zreplikować w Google Cloud, aby przeprowadzić migrację bez żadnych problemów. Cloud SQL to idealne rozwiązanie, ponieważ umożliwia uruchamianie w pełni zarządzanej bazy danych MySQL w chmurze. Aby to zrobić:
- Otwórz stronę Cloud SQL: https://console.cloud.google.com/sql/instances
- Kliknij „Utwórz instancję”.
- Włącz API (w razie potrzeby). Może to potrwać kilka sekund.
- Wybierz MySQL.
- (Staramy się znaleźć dla Ciebie najtańszą wersję, aby dłużej Ci służyła):
- Wersja: Enterprise
- Wstępnie ustawione: development (próbowaliśmy piaskownicy, ale nie sprawdziła się w naszym przypadku)
- Wersja MySQL: 5.7 (wow, to już historia!)
- Identyfikator instancji: wybierz
appmod-phpapp(jeśli to zmienisz, pamiętaj, aby odpowiednio zmodyfikować też przyszłe skrypty i rozwiązania). - Hasło: dowolne, ale zapisz je jako CLOUDSQL_INSTANCE_PASSWORD.
- Region: pozostaw taki sam jak w przypadku reszty aplikacji (np. Mediolan =
europe-west8). - Dostępność strefowa: 1 strefa (oszczędzamy pieniądze na potrzeby wersji demonstracyjnej)
Kliknij przycisk Utwórz instancję, aby wdrożyć bazę danych Cloud SQL. ⌛ Zajmie to około 10 minut⌛. W międzyczasie czytaj dalej dokumentację. Możesz też zacząć rozwiązywać zadania z następnego modułu („Konteneryzacja aplikacji PHP”), ponieważ w pierwszej części (do momentu naprawienia połączenia z bazą danych) nie jest on zależny od tego modułu.
Uwaga. Ta instancja powinna kosztować około 7 PLN dziennie. Po warsztatach pamiętaj o jego wyłączeniu.
Tworzenie bazy danych image_catalog i użytkownika w Cloud SQL
Projekt aplikacji zawiera folder db/, w którym znajdują się 2 pliki SQL:
- 01_schema.sql : zawiera kod SQL do utworzenia 2 tabel z danymi o użytkownikach i obrazach.
- 02_seed.sql: zawiera kod SQL do wypełniania danych w utworzonych wcześniej tabelach.
Pliki te zostaną użyte później, po utworzeniu bazy danych image_catalog. Aby to zrobić, wykonaj te czynności:
- Otwórz instancję i kliknij kartę Bazy danych:
- kliknij „Utwórz bazę danych”.
- nadaj mu nazwę
image_catalog(jak w konfiguracji aplikacji PHP);

Następnie tworzymy użytkownika bazy danych. Dzięki temu możemy uwierzytelnić się w bazie danych image_catalog.
- Teraz kliknij kartę Użytkownicy.
- Kliknij „Dodaj konto użytkownika”.
- Użytkownik: utwórzmy go:
- Nazwa użytkownika:
appmod-phpapp-user - Hasło: wybierz coś, co łatwo zapamiętać, lub kliknij „Wygeneruj”.
- Wybierz „Zezwalaj na dowolnego hosta (%)”.
- kliknij DODAJ.
Otwórz bazę danych znanych adresów IP.
Pamiętaj, że wszystkie bazy danych w Cloud SQL są „izolowane”. Musisz wyraźnie skonfigurować sieć, z której ma być dostępna.
- Kliknij instancję.
- Otwórz menu „Połączenia”.
- Kliknij kartę „Sieci”.
- Kliknij w sekcji „Autoryzowane sieci”. Teraz dodaj sieć (czyli podsieć).
- Na razie wybierzmy szybkie, ale NIEBEZPIECZNE ustawienia, aby aplikacja działała. Później możesz ograniczyć dostęp do zaufanych adresów IP:
- Nazwa: „Everyone in the world - INSECURE” (Wszyscy na świecie – NIEZABEZPIECZONE).
- Sieć: „
0.0.0.0/0"(uwaga: to jest NIEZABEZPIECZONA część!) - Kliknij GOTOWE.
- Kliknij Zapisz.
Powinien pojawić się ekran podobny do tego:

Uwaga. To rozwiązanie jest dobrym kompromisem, który pozwala ukończyć warsztaty w czasie O(godziny). Zapoznaj się jednak z dokumentem SECURITY, aby zabezpieczyć rozwiązanie na potrzeby wdrożenia w wersji produkcyjnej.
Czas przetestować połączenie z bazą danych!
Sprawdźmy, czy utworzony wcześniej image_catalog użytkownik działa.
Otwórz „Cloud SQL Studio” w instancji i wpisz nazwę bazy danych, nazwę użytkownika i hasło, aby się uwierzytelnić, jak pokazano poniżej:

Teraz możesz otworzyć edytor SQL i przejść do następnej sekcji.
Importowanie bazy danych z bazy kodu
Użyj edytora SQL, aby zaimportować tabele image_catalog wraz z danymi. Skopiuj kod SQL z plików w repozytorium ( 01_schema.sql, a potem 02_seed.sql) i wykonaj je kolejno.
Następnie w katalogu obrazów powinny pojawić się 2 tabele: users i images, jak pokazano poniżej:

Możesz to sprawdzić, uruchamiając w edytorze to polecenie: select * from images;
Zanotuj też publiczny adres IP instancji Cloud SQL, ponieważ będzie Ci potrzebny później. Aby uzyskać adres IP, otwórz stronę główną instancji Cloud SQL na stronie Przegląd. (Przegląd > Połącz z tą instancją > Publiczny adres IP).
4. Moduł 2. Konteneryzowanie aplikacji w PHP

Chcemy stworzyć tę aplikację z myślą o chmurze.
Oznacza to spakowanie kodu w pliku ZIP, który zawiera wszystkie informacje potrzebne do uruchomienia go w chmurze.
Możesz to zrobić na kilka sposobów:
- Docker Bardzo popularne, ale dość skomplikowane w prawidłowej konfiguracji.
- Buildpacki Mniej popularny, ale zwykle „automatycznie zgaduje”, co należy zbudować i uruchomić. Często po prostu działa.
Na potrzeby tych warsztatów założymy, że używasz Dockera.
Jeśli zdecydujesz się użyć Cloud Shell, otwórz go ponownie (kliknij w prawym górnym rogu konsoli Cloud).

W dolnej części strony powinien otworzyć się wygodny shell, w którym w kroku konfiguracji nastąpiło rozwidlenie kodu.

Docker
Jeśli chcesz mieć kontrolę, jest to odpowiednie rozwiązanie. Ma to sens, gdy musisz skonfigurować określone biblioteki i wstrzyknąć pewne nieoczywiste zachowania (chmod w przypadku przesyłania, niestandardowy plik wykonywalny w aplikacji itp.).
Ponieważ ostatecznie chcemy wdrożyć skonteneryzowaną aplikację w Cloud Run, zapoznaj się z tą dokumentacją. Jak przenieść go z PHP 8 do PHP 5.7? Możesz do tego użyć Gemini. Możesz też użyć tej gotowej wersji:
# Use the official PHP image: https://hub.docker.com/_/php
FROM php:5.6-apache
# Configure PHP for Cloud Run.
# Precompile PHP code with opcache.
# Install PHP's extension for MySQL
RUN docker-php-ext-install -j "$(nproc)" opcache mysqli pdo pdo_mysql && docker-php-ext-enable pdo_mysql
RUN set -ex; \
{ \
echo "; Cloud Run enforces memory & timeouts"; \
echo "memory_limit = -1"; \
echo "max_execution_time = 0"; \
echo "; File upload at Cloud Run network limit"; \
echo "upload_max_filesize = 32M"; \
echo "post_max_size = 32M"; \
echo "; Configure Opcache for Containers"; \
echo "opcache.enable = On"; \
echo "opcache.validate_timestamps = Off"; \
echo "; Configure Opcache Memory (Application-specific)"; \
echo "opcache.memory_consumption = 32"; \
} > "$PHP_INI_DIR/conf.d/cloud-run.ini"
# Copy in custom code from the host machine.
WORKDIR /var/www/html
COPY . .
# Setup the PORT environment variable in Apache configuration files: https://cloud.google.com/run/docs/reference/container-contract#port
ENV PORT=8080
# Tell Apache to use 8080 instead of 80.
RUN sed -i 's/80/${PORT}/g' /etc/apache2/sites-available/000-default.conf /etc/apache2/ports.conf
# Note: This is quite insecure and opens security breaches. See last chapter for hardening ideas.
# Uncomment at your own risk:
#RUN chmod 777 /var/www/html/uploads/
# Configure PHP for development.
# Switch to the production php.ini for production operations.
# RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
# https://github.com/docker-library/docs/blob/master/php/README.md#configuration
RUN mv "$PHP_INI_DIR/php.ini-development" "$PHP_INI_DIR/php.ini"
# Expose the port
EXPOSE 8080
Najnowsza wersja Dockerfile jest dostępna tutaj.
Aby przetestować aplikację lokalnie, musimy zmienić plik config.php w taki sposób, aby aplikacja PHP łączyła się z bazą danych MySQL dostępną w Google Cloud SQL. Na podstawie tego, co zostało wcześniej skonfigurowane, wypełnij puste pola:
<?php
// Database configuration
$db_host = '____________';
$db_name = '____________';
$db_user = '____________';
$db_pass = '____________';
try {
$pdo = new PDO("mysql:host=$db_host;dbname=$db_name", $db_user, $db_pass);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
die("Errore di connessione: " . $e->getMessage());
}
session_start();
?>
DB_HOSTto publiczny adres IP Cloud SQL. Znajdziesz go w konsoli SQL:

DB_NAMEpowinna pozostać bez zmian:image_catalog- Wartość
DB_USERpowinna wynosićappmod-phpapp-user DB_PASSto coś, co zostało przez Ciebie wybrane. Ustaw go w pojedynczych cudzysłowach i w razie potrzeby użyj znaku ucieczki.
Możesz też przetłumaczyć kilka 🇮🇹 włoskich fragmentów na język angielski za pomocą Gemini.
OK. Skoro masz już Dockerfile i skonfigurowałeś(-aś) aplikację PHP tak, aby łączyła się z bazą danych, spróbujmy to zrobić.
Zainstaluj Dockera, jeśli jeszcze go nie masz ( link). Nie jest to potrzebne, jeśli używasz Cloud Shell (jakie to fajne!).
Teraz spróbuj utworzyć i uruchomić skonteneryzowaną aplikację PHP za pomocą odpowiednich poleceń docker build i run.
# Build command - don't forget the final . This works if Dockerfile is inside the code folder:
$ docker build -t my-php-app-docker .
# Local Run command: most likely ports will be 8080:8080
$ docker run -it -p <CONTAINER_PORT>:<LOCAL_MACHINE_PORT> my-php-app-docker
Jeśli wszystko działa prawidłowo, po połączeniu z hostem lokalnym powinna wyświetlić się ta strona internetowa. Aplikacja działa teraz na porcie 8080. Kliknij ikonę „Podgląd w przeglądarce” (przeglądarka z okiem), a następnie Podejrzyj na porcie 8080 (lub „Zmień port”, aby użyć innego portu).

Testowanie wyniku w przeglądarce
Aplikacja powinna teraz wyglądać mniej więcej tak:

Jeśli zalogujesz się za pomocą danych Admin/admin123, zobaczysz coś takiego.

Świetnie! Poza włoskim tekstem wszystko działa! 🎉🎉🎉
Jeśli konteneryzacja jest prawidłowa, ale dane logowania do bazy danych są nieprawidłowe, możesz zobaczyć taki komunikat:

Spróbuj jeszcze raz. Prawie się udało.
Zapisywanie w Artifact Registry [opcjonalnie]
Powinna już być dostępna działająca skonteneryzowana aplikacja PHP gotowa do wdrożenia w chmurze. Następnie potrzebujemy miejsca w chmurze, w którym będziemy przechowywać obraz Dockera i udostępniać go na potrzeby wdrażania w usługach Google Cloud, takich jak Cloud Run. To rozwiązanie do przechowywania nazywa się Artifact Registry. Jest to w pełni zarządzana usługa Google Cloud przeznaczona do przechowywania artefaktów aplikacji, w tym obrazów kontenerów Dockera, pakietów Maven, modułów npm i innych.
Utwórzmy repozytorium w Google Cloud Artifact Registry za pomocą odpowiedniego przycisku.

Wybierz prawidłową nazwę, format i region odpowiedni do przechowywania artefaktów.

Wróć do lokalnego środowiska programistycznego, dodaj tag do obrazu kontenera aplikacji i prześlij go do utworzonego przed chwilą repozytorium Artifact Registry. Aby to zrobić, wykonaj te polecenia.
- docker tag OBRAZ_ŹRÓDŁOWY[:TAG] OBRAZ_DOCELOWY[:TAG]
- docker push TARGET_IMAGE[:TAG]
Wynik powinien wyglądać jak na zrzucie ekranu poniżej.

Hurra 🎉🎉🎉 możesz przejść na kolejny poziom. Wcześniej poświęć 2 minuty na wypróbowanie przesyłania, logowania i wylogowywania oraz zapoznanie się z punktami końcowymi aplikacji.Będą Ci potrzebne później.
Możliwe błędy
Jeśli pojawią się błędy związane z konteneryzacją, poproś Gemini o wyjaśnienie i naprawienie błędu, podając:
- Obecny plik Dockerfile
- otrzymany błąd,
- [w razie potrzeby] wykonywany kod PHP.
Uprawnienia do przesyłania. Wypróbuj też punkt końcowy /upload.php i prześlij zdjęcie. Może pojawić się błąd podany poniżej. W takim przypadku musisz chmod/chown naprawić Dockerfile.
Ostrzeżenie: move_uploaded_file(uploads/image (3).png): nie udało się otworzyć strumienia: odmowa uprawnień w /var/www/html/upload.php w wierszu 11
PDOException „could not find driver” (lub „Errore di connessione: could not find driver”). Upewnij się, że plik Dockerfile zawiera odpowiednie biblioteki PDO dla MySQL (pdo_mysql), aby można było połączyć się z bazą danych. Inspirację znajdziesz tutaj.
Nie udało się przekazać prośby do systemu backendowego. Nie udało się połączyć z serwerem na porcie 8080. Oznacza to, że prawdopodobnie udostępniasz nieprawidłowy port. Upewnij się, że udostępniasz port, z którego serwery Apache/Nginx faktycznie obsługują żądania. To nie jest proste. Jeśli to możliwe, ustaw port na 8080 (ułatwi to korzystanie z Cloud Run). Jeśli chcesz zachować port 80 (np. dlatego, że Apache tego wymaga), użyj innego polecenia:
$ docker run -it -p 8080:80 # force 80
# Use the PORT environment variable in Apache configuration files.
# https://cloud.google.com/run/docs/reference/container-contract#port
RUN sed -i 's/80/${PORT}/g' /etc/apache2/sites-available/000-default.conf /etc/apache2/ports.conf
5. Moduł 3. Wdrażanie aplikacji w Cloud Run

Dlaczego warto wybrać Cloud Run?
Słuszne pytanie. Jeszcze kilka lat temu z pewnością wybrałbyś Google App Engine.
Mówiąc wprost, Cloud Run ma nowszy stos technologiczny, jest łatwiejszy we wdrożeniu, tańszy i skaluje się do 0, gdy nie jest używana. Dzięki elastyczności, która pozwala na uruchamianie dowolnego kontenera bezstanowego, oraz integracji z różnymi usługami Google Cloud jest to idealne rozwiązanie do wdrażania mikroserwisów i nowoczesnych aplikacji przy minimalnym narzucie i maksymalnej wydajności.
Cloud Run to w pełni zarządzana platforma Google Cloud, która umożliwia uruchamianie bezstanowych aplikacji skonteneryzowanych w środowisku bezserwerowym. Automatycznie obsługuje całą infrastrukturę, skalując się od zera, aby sprostać przychodzącemu ruchowi, i zmniejszając skalę, gdy jest bezczynna, co czyni ją opłacalną i wydajną. Cloud Run obsługuje dowolny język lub bibliotekę, o ile są one spakowane w kontenerze, co zapewnia dużą elastyczność podczas programowania. Usługa dobrze integruje się z innymi usługami Google Cloud i nadaje się do tworzenia mikroserwisów, interfejsów API, witryn i aplikacji opartych na zdarzeniach bez konieczności zarządzania infrastrukturą serwerową.
Wymagania wstępne
Aby wykonać to zadanie, musisz mieć zainstalowane narzędzie gcloud na komputerze lokalnym. Jeśli nie, zapoznaj się z instrukcjami tutaj. Jeśli korzystasz z Google Cloud Shell, nie musisz nic robić.
Przed wdrożeniem...
Jeśli pracujesz w środowisku lokalnym, uwierzytelnij się w Google Cloud za pomocą tego polecenia:
$ gcloud auth login –update-adc # not needed in Cloud Shell
Powinno to uwierzytelnić Cię za pomocą loginu OAuth w przeglądarce. Zaloguj się w Chrome jako ten sam użytkownik (np. vattelapesca@gmail.com), który jest zalogowany w Google Cloud z włączonym rozliczeniem.
Włącz Cloud Run API za pomocą tego polecenia:
$ gcloud services enable run.googleapis.com cloudbuild.googleapis.com
Na tym etapie wszystko jest gotowe do wdrożenia w Cloud Run.
Wdrażanie aplikacji w Cloud Run za pomocą gcloud
Polecenie, które umożliwia wdrożenie aplikacji w usłudze Aplikacje w Cloud Run, to gcloud run deploy. Aby osiągnąć swój cel, możesz ustawić kilka opcji. Minimalny zestaw opcji (które możesz podać w wierszu poleceń lub o które narzędzie poprosi Cię w interaktywnym prompcie) obejmuje:
- Nazwa usługi Cloud Run, którą chcesz wdrożyć w aplikacji. Usługa Cloud Run zwróci adres URL, który będzie punktem końcowym aplikacji.
- Region Google Cloud, w którym będzie działać aplikacja. (
--regionREGION) - Obraz kontenera, który zawiera aplikację.
- Zmienne środowiskowe, których aplikacja potrzebuje podczas wykonywania.
- Flaga Allow-Unauthenticated, która umożliwia wszystkim dostęp do aplikacji bez dodatkowego uwierzytelniania.
Zapoznaj się z dokumentacją (lub przewiń w dół, aby znaleźć możliwe rozwiązanie), aby dowiedzieć się, jak zastosować tę opcję w wierszu poleceń.
Wdrażanie potrwa kilka minut. Jeśli wszystko jest w porządku, w konsoli Google Cloud zobaczysz coś takiego:


Kliknij adres URL podany przez Cloud Run i przetestuj aplikację. Po uwierzytelnieniu zobaczysz coś takiego.

„gcloud run deploy” bez argumentów
Możesz zauważyć, że gcloud run deploy zadaje odpowiednie pytania i wypełnia pozostawione przez Ciebie puste pola. Jest niesamowicie!
W kilku modułach dodamy to polecenie do aktywatora kompilacji Cloud Build, więc nie możemy sobie pozwolić na interaktywne pytania. Musimy wypełnić wszystkie opcje w poleceniu. Chcesz stworzyć złotą gcloud run deploy --option1 blah --foo bar --region your-fav-region. Jak to zrobisz?
- powtarzaj kroki 2–3–4, aż gcloud przestanie zadawać pytania:
- [LOOP]
gcloud run deployz dotychczas znalezionymi opcjami - [LOOP] systemy proszą o opcję X
- [LOOP] Wyszukaj w dokumentach publicznych informacje o tym, jak skonfigurować X z interfejsu wiersza poleceń, dodając opcję
--my-option [my-value]. - Wróć do kroku 2, chyba że gcloud zakończy działanie bez dalszych pytań.
- This gcloud run deploy BLAH BLAH BLAH rocks! Zapisz polecenie, ponieważ będzie Ci potrzebne później w kroku Cloud Build.
Możliwe rozwiązanie znajdziesz tutaj. Dokumenty znajdziesz tutaj.
Gratulacje 🎉🎉🎉 udało Ci się wdrożyć aplikację w Google Cloud, co jest pierwszym krokiem w procesie modernizacji.
6. Moduł 4. Czyszczenie hasła za pomocą usługi Secret Manager

W poprzednim kroku udało nam się wdrożyć i uruchomić aplikację w Cloud Run. Zrobiliśmy to jednak w sposób niezgodny z zasadami bezpieczeństwa: podaliśmy niektóre tajne informacje w postaci zwykłego tekstu.
Pierwsza iteracja: zaktualizuj plik config.php, aby używać zmiennej ENV
Być może zauważyliście, że hasło do bazy danych umieściliśmy bezpośrednio w kodzie w pliku config.php. Jest to wystarczające do celów testowych i sprawdzenia, czy aplikacja działa. Nie możesz jednak zatwierdzać ani używać kodu w taki sposób w środowisku produkcyjnym. Hasło (i inne parametry połączenia z bazą danych) powinno być odczytywane dynamicznie i przekazywane do aplikacji w czasie działania. Zmodyfikuj plik config.php, aby odczytywał parametry bazy danych ze zmiennych środowiskowych. Jeśli się nie powiedzie, rozważ ustawienie wartości domyślnych. Jest to przydatne w przypadku niepowodzenia wczytywania zmiennych środowiskowych. Dane wyjściowe strony poinformują Cię, czy używane są wartości domyślne. Wypełnij puste pola i zastąp kod w pliku config.php.
<?php
// Database configuration with ENV variables. Set default values as well
$db_host = getenv('DB_HOST') ?: 'localhost';
$db_name = getenv('DB_NAME') ?: 'image_catalog';
$db_user = getenv('DB_USER') ?: 'appmod-phpapp-user';
$db_pass = getenv('DB_PASS') ?: 'wrong_password';
// Note getenv() is PHP 5.3 compatible
try {
$pdo = new PDO("mysql:host=$db_host;dbname=$db_name", $db_user, $db_pass);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
die("Errore di connessione: " . $e->getMessage());
}
session_start();
?>
Ponieważ aplikacja jest skonteneryzowana, musisz podać sposób dostarczania do niej zmiennych środowiskowych. Możesz to zrobić na kilka sposobów:
- W czasie kompilacji w pliku Dockerfile. Dodaj do poprzedniego pliku Dockerfile 4 parametry, używając składni ENV DB_VAR=ENV_VAR_VALUE. Spowoduje to skonfigurowanie wartości domyślnych, które można zastąpić w czasie działania programu. Na przykład wartości „DB_NAME” i „DB_USER” można ustawić tylko tutaj.
- w czasie wykonywania. Możesz skonfigurować te zmienne dla Cloud Run, zarówno w interfejsie wiersza poleceń, jak i w interfejsie. To odpowiednie miejsce na umieszczenie wszystkich 4 zmiennych (chyba że chcesz zachować wartości domyślne ustawione w pliku Dockerfile).
W środowisku localhost możesz umieścić zmienne środowiskowe w pliku .env (sprawdź folder solutions).
Upewnij się też, że plik .env jest dodany do .gitignore – nie chcesz przecież przesyłać swoich danych logowania do GitHub.
echo .env >> .gitignore
Następnie możesz przetestować instancję lokalnie:
docker run -it -p 8080:8080 --env-file .env my-php-app-docker
Udało Ci się:
- Aplikacja będzie dynamicznie odczytywać zmienną z ENV
- Zwiększono bezpieczeństwo, ponieważ hasło do bazy danych zostało usunięte z kodu.
Możesz teraz wdrożyć nową wersję w Cloud Run. Przejdźmy do interfejsu i ręcznie ustawmy zmienne środowiskowe:
- Wejdź na https://console.cloud.google.com/run.
- Kliknij aplikację.
- Kliknij „Edytuj i wdróż nową wersję”.
- Na pierwszej karcie „Kontenery” kliknij dolną kartę „Zmienne i obiekty tajne”.
- Kliknij „+ Dodaj zmienną” i dodaj wszystkie potrzebne zmienne. Powinno to wyglądać mniej więcej tak:


Czy to idealne rozwiązanie? Nie. Twój PASS jest nadal widoczny dla większości operatorów. Można temu zapobiec, korzystając z usługi Google Cloud Secret Manager.
Druga iteracja: Secret Manager
Hasła zniknęły z Twojego kodu: sukces! Ale zaraz – czy jesteśmy już bezpieczni?
Hasła są nadal widoczne dla każdego, kto ma dostęp do konsoli Google Cloud. W rzeczywistości możesz go pobrać, jeśli uzyskasz dostęp do pliku YAML wdrożenia Cloud Run. Jeśli spróbujesz edytować lub wdrożyć nową wersję Cloud Run, hasło będzie widoczne w sekcji Zmienne i tajne dane, jak pokazano na zrzutach ekranu poniżej.
Google Cloud Secret Manager to bezpieczna, scentralizowana usługa do zarządzania informacjami wrażliwymi, takimi jak klucze interfejsu API, hasła, certyfikaty i inne obiekty tajne.
Umożliwia przechowywanie obiektów tajnych, zarządzanie nimi i uzyskiwanie do nich dostępu za pomocą szczegółowych uprawnień i solidnego szyfrowania. Menedżer obiektów tajnych jest zintegrowany z usługą Google Cloud Identity and Access Management (IAM), dzięki czemu możesz kontrolować, kto ma dostęp do określonych obiektów tajnych, co zapewnia bezpieczeństwo danych i zgodność z przepisami.
Obsługuje też automatyczne rotowanie i obsługa wersji obiektów tajnych, co upraszcza zarządzanie cyklem życia obiektów tajnych i zwiększa bezpieczeństwo aplikacji w usługach Google Cloud.
Aby uzyskać dostęp do Secret Manager, w menu hamburgera wybierz usługi Bezpieczeństwo i znajdź go w sekcji Ochrona danych, jak pokazano na zrzucie ekranu poniżej.

Włącz interfejs Secret Manager API, jak pokazano na poniższym obrazie.

- Teraz kliknij Utwórz obiekt tajny. Nazwijmy go racjonalnie:
- Nazwa:
php-amarcord-db-pass - Wartość obiektu tajnego: „your DB password” (zignoruj część „upload file”).
- dodawać adnotacje do tego tajnego linku, powinien wyglądać tak:
projects/123456789012/secrets/php-amarcord-db-pass. Jest to unikalny wskaźnik do Twojego obiektu tajnego (w przypadku Terraform, Cloud Run i innych usług). Ten numer to Twój unikalny numer projektu.
Wskazówka: staraj się stosować spójne konwencje nazewnictwa w przypadku kluczy tajnych, specjalizując się od lewej do prawej, np. cloud-devrel-phpamarcord-dbpass
- Organizacja (z firmą)
- Zespół (w organizacji)
- Aplikacja (w zespole)
- Nazwa zmiennej (w aplikacji)
Dzięki temu możesz łatwo tworzyć wyrażenia regularne, aby znajdować wszystkie tajne informacje w jednej aplikacji.
Utwórz nową wersję Cloud Run
Teraz, gdy mamy już nowy klucz Secret, musimy pozbyć się zmiennej środowiskowej DB_PASS i zastąpić ją nowym kluczem Secret. Przykłady:
- Dostęp do Cloud Run za pomocą konsoli Google Cloud
- Wybierz aplikację.
- Kliknij „Edytuj i wdróż nową wersję”.
- znajdź kartę „Zmienne i obiekty tajne”;
- Aby zresetować zmienną środowiskową DB_PASS, użyj przycisku „+ Odwołaj się do obiektu tajnego”.
- Użyj tego samego hasła „DB_PASS” w przypadku odwoływanych się do niego obiektów tajnych i użyj najnowszej wersji.

Gdy to zrobisz, powinien pojawić się ten błąd:

Spróbuj dowiedzieć się, jak to naprawić. Aby rozwiązać ten problem, otwórz sekcję Administracja i zmień uprawnienia do przyznawania dostępu. Miłego debugowania!
Gdy to zrobisz, wróć do Cloud Run i wdróż nową wersję. Wynik powinien wyglądać jak na tym rysunku:

Wskazówka: Konsola programisty (interfejs) doskonale wykrywa problemy z uprawnieniami. Poświęć trochę czasu na przejrzenie wszystkich linków do swoich podmiotów w chmurze.
7. Moduł 5. Konfigurowanie CI/CD za pomocą Cloud Build

Dlaczego warto używać potoku CI/CD?
Do tej pory powinnaś(-eś) kilka razy wpisać gcloud run deploy, być może odpowiadając na to samo pytanie.
Masz dość ręcznego wdrażania aplikacji za pomocą gcloud run deploy? Czy nie byłoby wspaniale, gdyby Twoja aplikacja mogła wdrażać się automatycznie za każdym razem, gdy przenosisz nową zmianę do repozytorium Git?
Aby korzystać z potoku CI/CD, musisz mieć:
- Osobiste repozytorium Git: na szczęście w kroku 2 utworzono już rozwidlenie repozytorium warsztatowego na koncie GitHub. Jeśli nie, wróć i wykonaj ten krok. Rozwidlone repozytorium powinno wyglądać tak:
https://github.com/<YOUR_GITHUB_USER>/app-mod-workshop - Cloud Build. Ta niesamowita i tania usługa umożliwia skonfigurowanie automatyzacji kompilacji niemal wszystkiego: Terraform, aplikacji w kontenerach Dockera itp.
Ta sekcja dotyczy konfigurowania Cloud Build.
Włącz Cloud Build
W tym celu użyjemy Cloud Build:
- skompilować źródło (za pomocą pliku Dockerfile); Można go traktować jako „duży plik ZIP”, który zawiera wszystko, co jest potrzebne do jego utworzenia i uruchomienia (tzw. „artefakt kompilacji”).
- przesłać ten artefakt do Artifact Registry (AR).
- Następnie wdróż aplikację „php-amarcord” z AR w Cloud Run.
- Spowoduje to utworzenie nowej wersji („rewizji”) istniejącej aplikacji (pomyśl o warstwie z nowym kodem). Skonfigurujemy ją tak, aby w przypadku powodzenia wysyłania powiadomień kierować ruch do nowej wersji.
Oto przykład kilku wersji mojej aplikacji php-amarcord:

Jak to wszystko robimy?
- Tworząc jeden idealny plik YAML:
cloudbuild.yaml - Tworząc aktywator kompilacji w Cloud Build.
- Połączenie z repozytorium GitHub za pomocą interfejsu Cloud Build.
Utwórz wyzwalacz (i połącz repozytorium)
- Otwórz stronę https://console.cloud.google.com/cloud-build/triggers.
- Kliknij „Utwórz aktywator”.
- Kompilacja:
- Nazwa: coś znaczącego, np.
on-git-commit-build-php-app - Zdarzenie: wypchnięcie do gałęzi
- Źródło: „Połącz nowe repozytorium”

- Po prawej stronie otworzy się okno „Połącz repozytorium”.
- Dostawca źródłowy: „Github” (pierwszy)
- „Dalej”
- Uwierzytelnianie otworzy okno w GitHubie, w którym można przeprowadzić uwierzytelnianie krzyżowe. Postępuj zgodnie z instrukcjami i bądź cierpliwy. Jeśli masz wiele repozytoriów, może to zająć trochę czasu.
- „Wybierz repozytorium” Wybierz konto lub repozytorium i zaznacz pole „Rozumiem…”.
- Jeśli pojawi się błąd: Aplikacja na GitHubie nie jest zainstalowana w żadnym Twoim repozytorium, kliknij „Zainstaluj Google Cloud Build” i postępuj zgodnie z instrukcjami.
Kliknij Połącz.
- Bingo! Twoje repozytorium jest teraz połączone.
- Wróć do części Aktywator…
- Konfiguracja: wykryta automatycznie (*)
- Zaawansowane: wybierz konto usługi „[PROJECT_NUMBER]- compute@developer.gserviceaccount.com”
- xxxxx to identyfikator Twojego projektu.
- Domyślne konto usługi Compute jest odpowiednie w przypadku podejścia laboratoryjnego – nie używaj go w środowisku produkcyjnym. ( Więcej informacji).
- pozostawić wszystko inne bez zmian.
- Kliknij przycisk „Utwórz”.
(*) To najprostszy sposób, ponieważ sprawdza, czy w repozytorium znajduje się plik Dockerfile lub cloudbuild.yaml. Jednak cloudbuild.yaml daje Ci prawdziwą możliwość decydowania, co zrobić na danym etapie.
Mam moc!
Teraz aktywator nie będzie działać, dopóki nie przyznasz kontu usługi Cloud Build (co to jest konto usługi? Adres e-mail „robota”, który wykonuje zadanie w Twoim imieniu – w tym przypadku tworzy elementy w chmurze.
Jeśli nie przyznasz SA uprawnień do tworzenia i wdrażania, nie będzie on w stanie tego zrobić. Na szczęście to proste.
- kliknij „Cloud Build” > „Ustawienia”.
- Konto usługi „[PROJECT_NUMBER]- compute@developer.gserviceaccount.com”
- Zaznacz te pola:
- Cloud Run
- Secret Manager
- Konta usługi
- Cloud Build
- Zaznacz też pole „Ustaw jako preferowane konto usługi”.

Gdzie jest plik YAML Cloud Build?
Zachęcamy do poświęcenia czasu na utworzenie własnego pliku YAML usługi Cloud Build.
Jeśli jednak nie masz czasu lub nie chcesz go poświęcać, możesz poszukać inspiracji w tym folderze rozwiązań: .solutions
Teraz możesz przesłać zmianę do GitHub i obserwować, jak Cloud Build wykonuje swoją część zadania.
Konfigurowanie Cloud Build może być skomplikowane. Spodziewaj się wymiany informacji przez:
- Sprawdzanie logów na stronie https://console.cloud.google.com/cloud-build/builds;region=global
- Znajdowanie błędu.
- Poprawienie kodu i ponowne wydanie polecenia git commit / git push.
- Czasami błąd nie występuje w kodzie, ale w konfiguracji. W takim przypadku możesz wydać nową kompilację z poziomu interfejsu (Cloud Build > „Triggery” > Uruchom).

Pamiętaj, że jeśli użyjesz tego rozwiązania, nadal musisz wykonać pewne czynności. Na przykład musisz ustawić zmienne środowiskowe dla nowo utworzonych punktów końcowych środowiska deweloperskiego i produkcyjnego:

Można to zrobić na dwa sposoby:
- Za pomocą interfejsu – przez ponowne ustawienie zmiennych środowiskowych.
- Za pomocą CLI, tworząc dla Ciebie „idealny” skrypt. Przykład znajdziesz tutaj: gcloud-run-deploy.sh . Musisz wprowadzić kilka zmian, np. w punkcie końcowym i numerze projektu. Numer projektu znajdziesz w przeglądzie Cloud.
Jak przesłać kod do GitHub?
Wykracza to poza zakres tego warsztatu.git push Jeśli jednak utkniesz i korzystasz z Cloud Shell, możesz to zrobić na 2 sposoby:
- CLI. Dodaj lokalnie klucz SSH i dodaj zdalne repozytorium za pomocą adresu git@github.com:YOUR_USER/app-mod-workshop.git (zamiast http).
- VSCode. Jeśli używasz edytora Cloud Shell, możesz otworzyć kartę Kontrola źródła (Ctrl+Shift+G), kliknąć „Synchronizuj zmiany” i postępować zgodnie z instrukcjami. Powinno być możliwe uwierzytelnienie konta GitHub w VS Code, a pobieranie i wysyłanie zmian będzie odtąd bardzo proste.

Pamiętaj, aby git add clodubuild.yaml wśród innych plików, w przeciwnym razie nie będzie działać.
Głębokie a płytkie „równoległe środowiska deweloperskie i produkcyjne” [opcjonalnie]
Jeśli wersja modelu została skopiowana stąd, będziesz mieć 2 identyczne wersje DEV i PROD. To świetne rozwiązanie, które jest zgodne z regułą 10 aplikacji dwunastoczynnikowej.
Używamy jednak 2 różnych punktów końcowych sieci, aby aplikacja wskazywała tę samą bazę danych. W przypadku warsztatów to wystarczy, ale w rzeczywistości warto poświęcić trochę czasu na utworzenie odpowiedniego środowiska produkcyjnego. Oznacza to posiadanie 2 baz danych (jednej do testowania, a drugiej do produkcji) oraz wybranie miejsca, w którym będą one przechowywane na potrzeby odtwarzania awaryjnego lub zapewnienia wysokiej dostępności. To wykracza poza zakres tych warsztatów, ale warto o tym pomyśleć.
Jeśli masz czas na przygotowanie „zaawansowanej” wersji produkcji, pamiętaj o wszystkich zasobach, które musisz powielić, takich jak:
- baza danych Cloud SQL (i prawdopodobnie instancja SQL);
- Zasobnik GCS
- funkcji w Cloud Functions.
- Możesz używać Gemini 1.5 Flash jako modelu w środowisku deweloperskim (tańszy, szybszy) i Gemini 1.5 Pro (bardziej wydajny).
Zastanów się, czy wartość w środowisku produkcyjnym powinna być taka sama. Jeśli nie, powtórz te działania. Jest to oczywiście dużo łatwiejsze w przypadku Terraform, gdzie możesz wstawić środowisko (-dev, -prod) jako sufiks do zasobów.
8. Moduł 6. Przenoszenie do Google Cloud Storage

Miejsce na dane

Obecnie aplikacja przechowuje stan w kontenerze Dockera. Jeśli maszyna ulegnie awarii, aplikacja przestanie działać lub po prostu wdrożysz nową wersję, zostanie zaplanowana nowa wersja z nowym, pustym miejscem na dane: 🙈
Jak to naprawić? Istnieje kilka sposobów.
- przechowywanie obrazów w bazie danych, Tak zrobiłem w przypadku poprzedniej aplikacji PHP. To najprostsze rozwiązanie, ponieważ nie komplikuje aplikacji. Ale na pewno zwiększa to opóźnienie i obciążenie bazy danych.
- Przenieś aplikację Cloud Run do rozwiązania przyjaznego dla pamięci masowej: GCE + dysk stały? Może GKE + Storage? Uwaga: to, co zyskujesz w zakresie kontroli, tracisz w zakresie elastyczności.
- Przejdź do GCS. Google Cloud Storage to najlepsza w swojej klasie usługa przechowywania danych w całym Google Cloud, która jest najbardziej idiomatycznym rozwiązaniem w chmurze. Wymaga to jednak użycia bibliotek PHP. Czy mamy biblioteki PHP 5.7 dla GCS? Czy
PHP 5.7w ogóle obsługujeComposer(wygląda na to, że PHP 5.3.2 to najstarsza wersja obsługiwana przez Composer)? - Może użyć kontenera dodatkowego Dockera?
- Możesz też użyć montowania woluminów Cloud Run w GCS. Brzmi świetnie.
🤔 Migracja miejsca na dane (pytanie otwarte)
[Open Ended] W tym ćwiczeniu chcemy, abyś znalazł(-a) rozwiązanie, które pozwoli przenieść obrazy w sposób, który zostanie w jakiś sposób utrwalony.
Test akceptacyjny
Nie chcę Ci podawać rozwiązania, ale chcę, żeby stało się to:
- Przesyłasz
newpic.jpg. Zobaczysz je w aplikacji. - Uaktualniasz aplikację do nowej wersji.
newpic.jpgjest nadal widoczny.
💡 Możliwe rozwiązanie (montowanie woluminów Cloud Run w GCS)
Jest to bardzo eleganckie rozwiązanie, które pozwala nam osiągnąć stanowe przesyłanie plików bez dotykania kodu (z wyjątkiem wyświetlania opisu obrazu, ale to jest trywialne i służy tylko zadowoleniu oka).
Powinno to umożliwić zamontowanie folderu z Cloud Run w GCS, więc:
- Wszystkie przesłane pliki będą widoczne w aplikacji.
- Wszystkie pliki przesyłane do aplikacji będą w rzeczywistości przesyłane do GCS.
- Magia będzie działać na obiekty przesłane do GCS (rozdział 7).
Uwaga. Zapoznaj się z informacjami drobnym drukiem dotyczącymi FUSE. Jeśli wydajność jest problemem, NIE jest to w porządku.
Utwórz zasobnik GCS
GCS to wszechobecna usługa przechowywania danych w Google Cloud. Jest to sprawdzone rozwiązanie, z którego korzystają wszystkie usługi GCP wymagające miejsca na dane.
Pamiętaj, że Cloud Shell eksportuje PROJECT_ID jako GOOGLE_CLOUD_PROJECT:
$ export PROJECT_ID=$GOOGLE_CLOUD_PROJECT
#!/bin/bash
set -euo pipefail
# Your Cloud Run Service Name, eg php-amarcord-dev
SERVICE_NAME='php-amarcord-dev'
BUCKET="${PROJECT_ID}-public-images"
GS_BUCKET="gs://${BUCKET}"
# Create bucket
gsutil mb -l "$GCP_REGION" -p "$PROJECT_ID" "$GS_BUCKET/"
# Copy original pictures there - better if you add an image of YOURS before.
gsutil cp ./uploads/*.png "$GS_BUCKET/"
Skonfiguruj Cloud Run tak, aby zamontować zasobnik w folderze /uploads/
Teraz przejdźmy do eleganckiej części. Tworzymy wolumin php_uploads i instruujemy Cloud Run, aby wykonał podłączenie FUSE na MOUNT_PATH (np. /var/www/html/uploads/):
#!/bin/bash
set -euo pipefail
# .. keep variables from previous script..
# Uploads folder within your docker container.
# Tweak it for your app code.
MOUNT_PATH='/var/www/html/uploads/'
# Inject a volume mount to your GCS bucket in the right folder.
gcloud --project "$PROJECT_ID" beta run services update "$SERVICE_NAME" \
--region $GCP_REGION \
--execution-environment gen2 \
--add-volume=name=php_uploads,type=cloud-storage,bucket="$BUCKET" \
--add-volume-mount=volume=php_uploads,mount-path="$MOUNT_PATH"
Teraz powtórz ten krok w przypadku wszystkich punktów końcowych, które chcesz przekierować do Cloud Storage.
Możesz to też zrobić w interfejsie.
- Na karcie „Woluminy” utwórz punkty montowania woluminu wskazujące Twój zasobnik typu „Zasobnik Cloud Storage”, np. o nazwie „php_uploads”.
- W sekcji Kontenery > Podłączenia woluminów podłącz utworzony wolumin do punktu woluminu wymaganego przez aplikację. Zależy to od pliku Dockerfile, ale może wyglądać tak:
var/www/html/uploads/.
Jeśli wszystko działa, po edytowaniu nowej wersji Cloud Run powinna pojawić się mniej więcej taka informacja:

Teraz przetestuj nową aplikację, przesyłając 1 nowy obraz do punktu końcowego /upload.php.
Obrazy powinny być płynnie przesyłane w GCS bez konieczności pisania ani jednej linii kodu PHP:

Co się właśnie stało?
Stało się coś magicznego.
Stara aplikacja ze starym kodem nadal działa. Nowy, zmodernizowany stos umożliwia nam przechowywanie wszystkich obrazów i zdjęć w naszej aplikacji w zasobniku Cloud Bucket z zachowaniem stanu. Teraz nie ma już ograniczeń:
- Chcesz wysyłać e-maila za każdym razem, gdy otrzymasz obraz z treściami „niebezpiecznymi” lub „przedstawiającymi nagość”? Możesz to zrobić bez modyfikowania kodu PHP.
- Chcesz używać modelu multimodalnego Gemini za każdym razem, gdy pojawi się obraz, aby go opisać, i przesyłać bazę danych z opisem? Możesz to zrobić bez modyfikowania kodu PHP. Nie wierzysz mi? Czytaj dalej w rozdziale 7.
Otworzyliśmy sobie właśnie ogromne pole możliwości.
9. Moduł 7. Wzbogać swoją aplikację o Google Gemini

Masz teraz świetną, zmodernizowaną, nową aplikację PHP (jak Fiat 126 z 2024 r.) z pamięcią w chmurze.
Do czego służy
Wymagania wstępne
W poprzednim rozdziale rozwiązanie modelowe umożliwiło nam zamontowanie obrazów/uploads/ w GCS, co de facto oddzieliło logikę aplikacji od pamięci obrazów.
To ćwiczenie wymaga:
- Ukończ ćwiczenie w rozdziale 6 (pamięć).
- Musisz mieć zasobnik GCS z przesłanymi obrazami, do którego użytkownicy przesyłają zdjęcia w Twojej aplikacji.
Konfigurowanie funkcji w Cloud Functions (w Pythonie)
Zastanawiasz się, jak wdrożyć aplikację opartą na zdarzeniach? Na przykład:
- gdy nastąpi <zdarzenie> => wyślij e-maila
- gdy nastąpi <event> => jeśli <condition> ma wartość prawda, zaktualizuj bazę danych.
Wydarzenie może być dowolne, np. nowy rekord dostępny w BigQuery, nowy obiekt zmieniony w folderze w GCS lub nowa wiadomość oczekująca w kolejce w Pub/Sub.
Google Cloud obsługuje wiele paradygmatów, które umożliwiają osiągnięcie tego celu. W szczególności:
- EventArc Dowiedz się, jak otrzymywać zdarzenia GCS. Świetne narzędzie do tworzenia DAG-ów i orkiestrowania działań na podstawie instrukcji warunkowych w chmurze.
- Cloud Scheduler Świetnie sprawdza się na przykład w przypadku zadania cron uruchamianego o północy w chmurze.
- Cloud Workflows Podobnie jak Event Arc, umożliwia
- Funkcje Cloud Run (znane też jako
lambdas). - Cloud Composer. Jest to w zasadzie wersja Google Apache Airflow, która świetnie sprawdza się też w przypadku DAG.
W tym ćwiczeniu przyjrzymy się funkcji w Cloud Functions, aby osiągnąć spektakularny rezultat. Zaproponujemy Ci też opcjonalne ćwiczenia.
Pamiętaj, że przykładowy kod jest dostępny w katalogu .solutions/.
Konfigurowanie funkcji w Cloud Functions (🐍 Python)
Próbujemy stworzyć bardzo ambitny GCF.
- Gdy w GCS zostanie utworzony nowy obraz… (prawdopodobnie dlatego, że ktoś przesłał go w aplikacji – ale nie tylko)
- .. wywołaj Gemini, aby opisać obraz i uzyskać jego opis tekstowy .. (warto sprawdzić typ MIME i upewnić się, że jest to obraz, a nie plik PDF, MP3 lub tekstowy)
- .. i zaktualizować bazę danych tym opisem. (może to wymagać wprowadzenia poprawek w bazie danych, aby dodać kolumnę
descriptiondo tabeliimages).
Popraw bazę danych, aby dodać description do obrazów
- Otwórz Cloud SQL Studio:

- Wpisz nazwę użytkownika i hasło do bazy danych Grafiki.
- Wstaw ten kod SQL, który dodaje kolumnę z opisem obrazu:
ALTER TABLE images ADD COLUMN description TEXT;

I bingo! Sprawdź teraz, czy to zadziałało:
SELECT * FROM images;
Powinna się wyświetlić nowa kolumna z opisem:

Napisz Gemini f(x)
Uwaga. Ta funkcja została utworzona przy pomocy Gemini Code Assist.
Uwaga. Podczas tworzenia tej funkcji mogą wystąpić błędy uprawnień. Niektóre z nich zostały opisane poniżej w sekcji „Możliwe błędy”.
- Włączanie interfejsów API
- Otwórz stronę https://console.cloud.google.com/functions/list.
- Kliknij „Utwórz funkcję”.
- Włączanie interfejsów API za pomocą kreatora API:

Możesz utworzyć GCF w interfejsie lub w wierszu poleceń. W tym przypadku użyjemy wiersza poleceń.
Możliwy kod znajdziesz w sekcji .solutions/.
- Utwórz folder na swój kod, np. „gcf/”. Otwórz folder.
- Utwórz plik
requirements.txt:
google-cloud-storage
google-cloud-aiplatform
pymysql
- Utwórz funkcję w Pythonie. Przykładowy kod znajdziesz tutaj: gcf/main.py.
#!/usr/bin/env python
"""Complete this"""
from google.cloud import storage
from google.cloud import aiplatform
import vertexai
from vertexai.generative_models import GenerativeModel, Part
import os
import pymysql
import pymysql.cursors
# Replace with your project ID
PROJECT_ID = "your-project-id"
GEMINI_MODEL = "gemini-1.5-pro-002"
DEFAULT_PROMPT = "Generate a caption for this image: "
def gemini_describe_image_from_gcs(gcs_url, image_prompt=DEFAULT_PROMPT):
pass
def update_db_with_description(image_filename, caption, db_user, db_pass, db_host, db_name):
pass
def generate_caption(event, context):
"""
Cloud Function triggered by a GCS event.
Args:
event (dict): The dictionary with data specific to this type of event.
context (google.cloud.functions.Context): The context parameter contains
event metadata such as event ID
and timestamp.
"""
pass
- Wypchnij funkcję. Możesz użyć skryptu podobnego do tego: gcf/push-to-gcf.sh.
Uwaga 1. Upewnij się, że zmienne środowiskowe mają odpowiednie wartości, lub po prostu dodaj je na górze (GS_BUCKET=blah, ..):
Uwaga 2. Spowoduje to przesłanie całego kodu lokalnego (.), więc umieść kod w określonym folderze i używaj polecenia .gcloudignore jak profesjonalista, aby uniknąć przesyłania dużych bibliotek. ( przykład).
#!/bin/bash
set -euo pipefail
# add your logic here, for instance:
source .env || exit 2
echo "Pushing ☁️ f(x)☁ to 🪣 $GS_BUCKET, along with DB config.. (DB_PASS=$DB_PASS)"
gcloud --project "$PROJECT_ID" functions deploy php_amarcord_generate_caption \
--runtime python310 \
--region "$GCP_REGION" \
--trigger-event google.cloud.storage.object.v1.finalized \
--trigger-resource "$BUCKET" \
--set-env-vars "DB_HOST=$DB_HOST,DB_NAME=$DB_NAME,DB_PASS=$DB_PASS,DB_USER=$DB_USER" \
--source . \
--entry-point generate_caption \
--gen2
Uwaga: w tym przykładzie wywoływana będzie metoda generate_caption, a funkcja Cloud Function przekaże do niej zdarzenie GCS ze wszystkimi odpowiednimi informacjami (nazwą zasobnika, nazwą obiektu itp.). Poświęć trochę czasu na debugowanie tego słownika zdarzeń w Pythonie.
Testowanie funkcji
Testy jednostkowe
Funkcja ma wiele elementów. Możesz chcieć przetestować wszystkie pojedyncze elementy.
Przykład znajdziesz w pliku gcf/test.py.
Interfejs Cloud Functions
Poświęć też trochę czasu na zapoznanie się z funkcją w interfejsie. Warto przejrzeć wszystkie karty, zwłaszcza Source (moja ulubiona), Variables, Trigger i Logs. Na karcie Logs spędzisz dużo czasu, rozwiązując problemy (możliwe błędy znajdziesz też na dole tej strony). Sprawdź też Permissions.

Test E2E
Czas ręcznie przetestować funkcję.
- Otwórz aplikację i zaloguj się.
- Prześlij zdjęcie (nie za duże, bo duże obrazy mogą powodować problemy).
- sprawdź w interfejsie, czy zdjęcie zostało przesłane.
- Sprawdź w Cloud SQL Studio, czy opis został zaktualizowany. Zaloguj się i uruchom to zapytanie:
SELECT * FROM images.

To działa! Możemy też zaktualizować interfejs, aby wyświetlać ten opis.
Aktualizacja PHP, aby wyświetlać [opcjonalnie]
Udowodniliśmy, że aplikacja działa. Byłoby jednak dobrze, gdyby użytkownicy też mogli zobaczyć ten opis.
Nie musimy być ekspertami w zakresie PHP, aby dodać opis do index.php. Ten kod powinien (tak, Gemini też mi go napisał!):
<?php if (!empty($image['description'])): ?>
<p class="font-bold">Gemini Caption:</p>
<p class="italic"><?php echo $image['description']; ?></p>
<?php endif; ?>
Umieść ten kod w foreach w dowolnym miejscu.
W kolejnych krokach widzimy też ładniejszą wersję interfejsu dzięki Gemini Code Assist. Wersja z ładnym formatowaniem może wyglądać tak:

Wnioski
Masz funkcję w Cloud Functions, która jest wywoływana, gdy w GCS pojawiają się nowe obiekty. Potrafi ona dodawać adnotacje do zawartości obrazu tak jak człowiek i automatycznie aktualizować bazę danych. Niesamowite!
Co dalej? Możesz postępować w ten sam sposób, aby uzyskać 2 przydatne funkcje.
[opcjonalnie] Dodaj kolejne funkcje w Cloud Functions [otwarte]
Przychodzą mi na myśl jeszcze 2 dodatkowe funkcje.
📩 Aktywator e-mail
Wywoływacz e-mail, który wysyła e-maila za każdym razem, gdy ktoś wyśle zdjęcie.
- Za często? Dodaj kolejne ograniczenie: duże zdjęcie lub zdjęcie, którego treść w Gemini zawiera słowa „nude/nudity/violent” (nagość/brutalne).
- Sprawdź
EventArc.
🚫 Automatyczna moderacja nieodpowiednich zdjęć
Obecnie administratorzy oznaczają obrazy jako „nieodpowiednie”. A może powierzyć Gemini moderowanie pokoju? Dodaj test, który będzie oznaczać nieodpowiednie treści wywołujące, i zaktualizuj bazę danych, tak jak w przypadku poprzedniej funkcji. Oznacza to w zasadzie użycie poprzedniej funkcji, zmianę promptu i zaktualizowanie bazy danych na podstawie odpowiedzi.
Zastrzeżenie: Generatywna AI ma nieprzewidywalne dane wyjściowe. Upewnij się, że „wyniki kreatywne” Gemini są „pod kontrolą”. Możesz poprosić o odpowiedź deterministyczną, np. wynik ufności od 0 do 1, kod JSON itp. Możesz to zrobić na wiele sposobów, np.: * używając bibliotek Pythona pydantic, langchain itp.; * korzystając z ustrukturyzowanych danych wyjściowych Gemini.
Wskazówka: Możesz mieć WIELE funkcji lub jeden prompt, który wymusza odpowiedź w formacie JSON (świetnie sprawdza się w przypadku „Gemini Structured Output”, jak wspomnieliśmy powyżej), np.:
Jakiego prompta należy użyć, aby wygenerować ten obraz?
{
"description": "This is the picture of an arrosticino",
"suitable": TRUE
}
Możesz dodać do promptu dodatkowe pola, aby uzyskać informacje, np. czy jest w nim coś dobrego. Co w tym złego? Czy rozpoznajesz to miejsce? Czy na zdjęciu znajduje się tekst (OCR nigdy nie był tak prosty):
goods: „It looks like yummie food”bads: „Wygląda na niezdrowe jedzenie”OCR: "Da consumare preferibilmente prima del 10 Novembre 2024"location: "Pescara, Lungomare"
Zwykle lepiej jest mieć funkcję N dla N wyników, ale wykonanie funkcji, która robi 10 rzeczy, jest niezwykle satysfakcjonujące. Aby dowiedzieć się, jak to zrobić, przeczytaj ten artykuł Riccardo.
Możliwe błędy (głównie związane z IAM lub uprawnieniami)
Gdy po raz pierwszy opracowałem to rozwiązanie, napotkałem problemy z uprawnieniami IAM. Dodam je tutaj, aby okazać empatię i podsunąć pomysły na ich rozwiązanie.
Błąd: niewystarczające uprawnienia konta usługi
- Pamiętaj, że aby wdrożyć funkcję GCF, która nasłuchuje zasobnika GCS, musisz skonfigurować odpowiednie uprawnienia dla konta usługi używanego w tym zadaniu, jak na rysunku:

Może też być konieczne włączenie interfejsów API EventArc, co może potrwać kilka minut, zanim staną się w pełni dostępne.
Błąd: brak wywołującego Cloud Run
- Kolejny komentarz z interfejsu do zarządzania uprawnieniami GCF: Rola wywołującego Cloud Run.

Ten błąd można naprawić, uruchamiając w obrazie polecenie podobne do fix-permissions.sh.
Ten problem jest opisany tutaj: https://cloud.google.com/functions/docs/securing/authenticating
Błąd: przekroczono limit pamięci
Gdy uruchomiłem go po raz pierwszy, w logach pojawił się komunikat: „Przekroczono limit pamięci 244 MiB, wykorzystano 270 MiB”. Rozważ zwiększenie limitu pamięci. Więcej informacji znajdziesz na stronie https://cloud.google.com/functions/docs/configuring/memory'". Ponownie dodaj pamięć RAM do GCF. Możesz to łatwo zrobić w interfejsie. Oto możliwy wzrost:

Możesz też poprawić skrypt wdrażania Cloud Run, aby zwiększyć MEM/CPU. Zajmie to trochę więcej czasu.
Błąd: opublikowano w PubSub
Podczas tworzenia aktywatora za pomocą GCF w wersji 1 wystąpił ten błąd:

Możesz to łatwo naprawić, przechodząc do IAM i przypisując kontu usługi rolę „Publikujący Pub/Sub”.
Błąd: Vertex AI nie był używany
Jeśli pojawi się ten błąd:
Permission Denied: 403 Interfejs Vertex AI API nie był wcześniej używany w projekcie YOUR_PROJECT lub jest wyłączony. Aby go włączyć, wejdź na https://console.developers.google.com/apis/api/aiplatform.googleapis.com/overview?project=YOR_PROJECT
Wystarczy włączyć interfejsy Vertex AI API. Najprostszy sposób na włączenie WSZYSTKICH potrzebnych interfejsów API:
- https://console.cloud.google.com/vertex-ai
- Kliknij „Włącz wszystkie zalecane interfejsy API”.

Błąd: nie znaleziono aktywatora EventArc.
Jeśli pojawi się ten komunikat, ponownie wdróż funkcję.

Błąd: 400 – agenci usługi są udostępniani
400 – trwa udostępnianie agentów usługi ( https://cloud.google.com/vertex-ai/docs/general/access-control#service-agents ). Agenci usługi są potrzebni do odczytania podanego pliku Cloud Storage. Spróbuj ponownie za kilka minut.
W takim przypadku poczekaj lub poproś o pomoc pracownika Google.
10. Moduł 8. Tworzenie celów SLO dotyczących dostępności
W tym rozdziale staramy się to osiągnąć:
- Tworzenie wskaźników poziomu usług
- Tworzenie celów SLO na podstawie wskaźników SLI
- Tworzenie alertów na podstawie docelowych poziomów usług

Jest to bardzo ważny temat dla autora, ponieważ Riccardo pracuje w Google Cloud w obszarze SRE / DevOps.
(otwarte) Utwórz wskaźniki poziomu usług i docelowe poziomy usług dla tej aplikacji
Jak dobra jest aplikacja, jeśli nie wiesz, kiedy nie działa?
Co to jest SLO?
O rany! Google wymyśliło docelowe poziomy usług. Więcej informacji na ten temat znajdziesz w tych artykułach:
- Książka SRE – rozdział 2. Wdrażanie docelowych poziomów usług ( 👉 więcej książek o SRE)
- Art of SLOs ( świetny film). To świetne szkolenie, dzięki któremu dowiesz się, jak stworzyć idealny SLO dla swojej usługi.
- Kurs SRE w serwisie Coursera Przyłożyłem do tego rękę.
Krok 1. Utwórz wskaźnik SLI/SLO dostępności
Zacznijmy od SLO dostępności, ponieważ jest to najprostsza i prawdopodobnie najważniejsza rzecz, którą chcesz mierzyć.
Na szczęście Cloud Run ma wbudowaną obsługę SLO dzięki Istio.
Gdy aplikacja jest już w Cloud Run, jest to bardzo proste i zajmuje mi 30 sekund.
- Otwórz stronę Cloud Run.
- Kliknij/wybierz aplikację.
- Wybierz kartę
SLOs. - Kliknij „+ Utwórz SLO”.
- Dostępność, na żądanie
- Dalej
- Miesiąc kalendarzowy / 99%.
- kliknij „Utwórz SLO”;

Krok 2. Skonfiguruj alerty dotyczące tego docelowego poziomu usług
Proponuję utworzyć 2 alerty:
- Jeden z niskim tempem wykorzystania środków („Slowburn”), który będzie wysyłać Ci alerty e-mailem (symuluje zgłoszenie o niskim priorytecie).
- jeden z wysokim tempem wykorzystania środków („Fastburn”), który będzie Cię powiadamiać SMS-em (symuluje zgłoszenie o wysokim priorytecie / pager);
Wróć do SLO tab.
Zrób to 2 razy:

- Kliknij „Utwórz alert docelowego poziomu usług” (przycisk 🔔 z plusem w środku po prawej stronie).
- Okres wyszukiwania wstecz, próg tempa wykorzystania:
- [FAST]. Pierwszy:
60min /10x - [SLOW]. Druga:
720min /2x - Kanał powiadomień: kliknij Zarządzaj kanałami powiadomień.
- Najpierw „Email” -> Dodaj nowy -> ..
- Po drugie, „SMS” –> Dodaj nowy –> Zweryfikuj na telefonie.
- Wskazówka: lubię używać emotikonów w nazwach. Jest to przydatne w prezentacjach.
- Gdy skończysz, kliknij duży znak X w prawym górnym rogu.
- Najpierw wybierz telefon (szybko), a potem e-maila (wolno).
- Dodaj przykładową dokumentację, np.:
[PHP Amarcord] Riccardo told me to type sudo reboot or to check documentation in http://example.com/playbooks/1.php but I guess he was joking.
Bingo!
Wynik końcowy
To ćwiczenie możemy uznać za zakończone, gdy będziesz mieć 1 działający SLO i 2 alerty dotyczące dostępności, które będą wysyłane na Twój adres e-mail i telefon.
Możesz dodać opóźnienie (bardzo Cię do tego zachęcam) lub nawet bardziej złożone opóźnienie. W przypadku opóźnienia wybierz wartość, która Twoim zdaniem jest odpowiednia. Jeśli masz wątpliwości, wybierz 200 ms.
11. Dalsze kroki
Wszystko zostało ukończone. Czego brakuje?
Kilka kwestii do przemyślenia:
Wypróbuj Gemini
Gemini jest dostępny w 2 wersjach:
- Vertex AI „Sposób Enterprise” powiązany z GCP, który omówiliśmy w rozdziale 7 (GCF+Gemini). Całe uwierzytelnianie działa magicznie, a usługi są ze sobą doskonale połączone.
- AI od Google „Ścieżka konsumenta”. Klucz interfejsu Gemini API możesz uzyskać tutaj. Następnie możesz zacząć tworzyć małe skrypty, które można powiązać z dowolnym zadaniem, które już masz (prace zastrzeżone, inne chmury, localhost itp.). Wystarczy, że zastąpisz klucz interfejsu API, a kod zacznie magicznie działać.
Zachęcamy do wypróbowania (2) w ramach własnych projektów.
Ulepszenia interfejsu
Nie znam się na interfejsach. Ale Gemini tak! Możesz po prostu wziąć jedną stronę PHP i napisać coś takiego:
I have a VERY old PHP application. I want to touch it as little as possible. Can you help me:
1. add some nice CSS to it, a single static include for tailwind or similar, whatever you prefer
2. Transform the image print with description into cards, which fit 4 per line in the canvas?
Here's the code:
-----------------------------------
[Paste your PHP page, for instance index.php - mind the token limit!]
Możesz to łatwo zrobić w mniej niż 5 minut, korzystając z jednej usługi Cloud Build. :)
Odpowiedź Gemini była idealna (nie musiałem niczego zmieniać):

A oto nowy układ w osobistej aplikacji autora:

Uwaga: kod jest wklejony jako obraz, ponieważ nie chcemy zachęcać Cię do jego kopiowania. Chcemy, aby Gemini napisał go za Ciebie, uwzględniając Twoje własne kreatywne ograniczenia interfejsu użytkownika lub interfejsu. Zaufaj nam, po tym procesie pozostaną Ci bardzo drobne zmiany.
Bezpieczeństwo
Odpowiednie zabezpieczenie tej aplikacji nie jest celem tych 4-godzinnych warsztatów, ponieważ wydłużyłoby czas ich ukończenia o 1–2 rzędy wielkości.
Ten temat jest jednak bardzo ważny. Zebraliśmy kilka pomysłów w SECURITY.
12. Gratulacje!
Gratulacje 🎉🎉🎉. Udało Ci się zmodernizować starszą aplikację PHP za pomocą Google Cloud.

Podsumowując, z tego ćwiczenia dowiedzieliśmy się:
- Jak wdrożyć bazę danych w Google Cloud SQL i jak przenieść do niej istniejącą bazę danych.
- Jak skonteneryzować aplikację PHP za pomocą Dockera i Buildpacków oraz zapisać jej obraz w Google Cloud Artifact Registry
- Jak wdrożyć skonteneryzowaną aplikację w Cloud Run i uruchomić ją w Cloud SQL
- Jak bezpiecznie przechowywać i używać poufnych parametrów konfiguracji (np. hasła do bazy danych) za pomocą usługi Google Secret Manager
- Jak skonfigurować potok CI/CD za pomocą Google Cloud Build, aby automatycznie kompilować i wdrażać aplikację PHP przy każdym przeniesieniu kodu do repozytorium GitHub.
- Jak używać Cloud Storage do „przenoszenia” zasobów aplikacji do chmury
- Jak wykorzystać technologie bezserwerowe do tworzenia niesamowitych przepływów pracy w Google Cloud bez modyfikowania kodu aplikacji.
- Wykorzystaj multimodalne możliwości Gemini w odpowiednim przypadku użycia.
- Wdrażanie zasad SRE w Google Cloud
To świetny początek Twojej przygody z modernizacją aplikacji w Google Cloud.
🔁 Opinie
Jeśli chcesz podzielić się z nami opinią o tych warsztatach, wypełnij ten formularz.
Chętnie poznamy Twoją opinię, a także prośby o scalenie dotyczące fragmentów kodu, z których jesteś szczególnie dumny(-a).
🙏 Pozdrawiamy
Autor dziękuje Mirko Gilioli i Maurizio Ipsale z Datatonic za pomoc w opracowaniu i testowaniu rozwiązania.
