1. Введение
Последнее обновление: 01.11.2024
Как модернизировать старое PHP-приложение для работы в Google Cloud?
(📽️ Посмотрите 7-минутное вводное видео к этому практическому занятию)
Нередко встречаются устаревшие приложения, работающие локально и нуждающиеся в модернизации. Это означает обеспечение их масштабируемости, безопасности и возможности развертывания в различных средах.
На этом мастер-классе вы:
- Контейнеризация PHP-приложения.
- Переход на управляемую службу баз данных ( Cloud SQL ).
- Развертывание в Cloud Run (это альтернатива GKE/Kubernetes, не требующая дополнительных операций).
- Защитите приложение с помощью управления идентификацией и доступом (IAM) и менеджера секретов.
- Настройте конвейер CI/CD с помощью Cloud Build . Cloud Build можно подключить к вашему репозиторию Git, размещенному на популярных платформах Git, таких как GitHub или GitLab, и запускать, например, при каждом изменении в основной репозиторий.
- Размещайте изображения приложения в облачном хранилище . Это достигается путем монтирования, и для изменения приложения не требуется писать какой-либо код.
- Внедрите функциональность Gen AI с помощью Gemini , управляемой через Cloud Functions (бессерверная архитектура).
- Ознакомьтесь с целями обслуживания клиентов (SLO) и принципами работы обновленного приложения.
Следуя этим шагам, вы сможете постепенно модернизировать свое PHP-приложение, улучшив его масштабируемость, безопасность и гибкость развертывания. Более того, переход в Google Cloud позволит вам использовать его мощную инфраструктуру и сервисы, чтобы обеспечить бесперебойную работу вашего приложения в облачной среде.
Мы уверены, что знания, полученные в ходе выполнения этих простых шагов, можно будет применить в ваших собственных приложениях и организациях, используя различные языки программирования/технологические стеки и различные сценарии использования.
О приложении
Приложение ( код , под лицензией MIT ), которое вы будете форкнуть, представляет собой базовое PHP-приложение 5.7 с аутентификацией MySQL. Основная идея приложения — предоставить платформу, где пользователи могут загружать фотографии, а администраторы — помечать изображения, не соответствующие их содержанию. Приложение содержит две таблицы:
- Пользователи . Включает в себя предустановленных администраторов. Новые пользователи могут зарегистрироваться.
- Изображения . В комплекте несколько примеров изображений. Зарегистрированные пользователи могут загружать новые фотографии. Мы добавим сюда немного волшебства.
Ваша цель
Мы хотим модернизировать старое приложение, чтобы разместить его в Google Cloud. Мы будем использовать его инструменты и сервисы для повышения масштабируемости, улучшения безопасности, автоматизации управления инфраструктурой и интеграции передовых функций, таких как обработка изображений, мониторинг и хранение данных, с помощью таких сервисов, как Cloud SQL, Cloud Run, Cloud Build, Secret Manager и других.

Что еще более важно, мы хотим сделать это шаг за шагом, чтобы вы могли понять ход мыслей, стоящий за каждым этапом, и, как правило, каждый шаг открывает новые возможности для последующих (например: модули 2 -> 3 и 6 -> 7).
Всё ещё не убеждены? Посмотрите это 7-минутное видео на YouTube .
Что вам понадобится
- Компьютер с браузером, подключенный к интернету.
- Получены некоторые кредиты GCP. Подробнее об этом смотрите в следующем шаге .
- Вы будете использовать Cloud Shell . В нём предустановлены все необходимые команды и интегрированная среда разработки (IDE) .
- Учетная запись GitHub . Она необходима для создания ветки исходного кода 🧑🏻💻 gdgpescara/app-mod-workshop с вашим собственным репозиторием Git. Это также необходимо для создания собственного конвейера CI/CD (автоматический коммит -> сборка -> развертывание).
Примеры решений можно найти здесь:
- Репозиторий автора: https://github.com/Friends-of-Ricc/app-mod-workshop
- Исходный репозиторий семинара находится в папках
.solutions/, по главам.
Данный семинар предназначен для прохождения в Cloud Shell (в браузере).
Однако это можно попробовать и с вашего локального компьютера.
2. Настройка кредита и создание форка.

Активируйте кредит GCP и настройте свою среду GCP [необязательно]
Для проведения этого мастер-класса вам потребуется платежный аккаунт с достаточным балансом. Если у вас уже есть свой платежный аккаунт, вы можете пропустить этот шаг.
Создайте совершенно новый аккаунт Google Gmail (*), чтобы связать его с вашими кредитами GCP. Запросите ссылку для активации кредитов GCP у своего преподавателя или используйте кредиты здесь: bit.ly/PHP-Amarcord-credits .
Войдите в систему, используя только что созданную учетную запись, и следуйте инструкциям.

(
Зачем мне нужен новый аккаунт Gmail?
Мы видели случаи, когда люди не проходили практическое задание, потому что их учетные записи (особенно рабочие или студенческие) ранее использовались в GCP, и корпоративные правила ограничивали их возможности. Мы рекомендуем либо создать новую учетную запись Gmail, либо использовать существующую учетную запись Gmail (gmail.com), которая ранее не использовалась в GCP.
Нажмите кнопку, чтобы использовать накопленные средства.

Заполните следующую форму, указав свое имя и фамилию, и согласитесь с условиями использования.
Возможно, вам придётся подождать несколько секунд, прежде чем здесь появится информация о платёжном аккаунте: https://console.cloud.google.com/billing
После этого откройте консоль Google Cloud и создайте новый проект, выбрав пункт «Выбор проекта» в выпадающем меню в левом верхнем углу, где отображается «Без организации». См. ниже.

Создайте новый проект, если у вас его еще нет, как показано на скриншоте ниже. В правом верхнем углу находится кнопка «НОВЫЙ ПРОЕКТ».

Обязательно свяжите новый проект с пробным аккаунтом GCP следующим образом.

Теперь вы готовы использовать платформу Google Cloud. Если вы новичок или просто хотите работать в облачной среде, вы можете получить доступ к Cloud Shell и его редактору, нажав на кнопку в верхнем левом углу, как показано ниже.

Убедитесь, что в левом верхнем углу выбран ваш новый проект:
Не выбрано (неверно):

Выбрано (хорошо):

Создайте форк приложения на GitHub.
- Перейдите по ссылке на демонстрационное приложение: https://github.com/gdgpescara/app-mod-workshop
- Нажмите на вилку 🍴.
- Если у вас нет учетной записи на GitHub, вам необходимо создать новую.
- Редактируйте по своему усмотрению.

- Клонируйте код приложения, используя
-
YOUR-GITHUB-USER/YOUR-REPO-NAMEgit clonehttps://github.com/
- Откройте клонированную папку проекта в вашем любимом редакторе. Если вы выберете Cloud Shell, вы можете сделать это, нажав кнопку « Открыть редактор », как показано ниже.

В редакторе Google Cloud Shell Editor есть все необходимое, как показано на следующем рисунке.

Это можно сделать визуально, нажав кнопку «Открыть папку» и выбрав папку, скорее всего, app-mod-workshop в вашей домашней папке.
3. Модуль 1: Создание экземпляра SQL
Создайте экземпляр Google Cloud SQL.
Наше PHP-приложение будет подключаться к базе данных MySQL, поэтому нам необходимо реплицировать её в Google Cloud для беспроблемной миграции. Cloud SQL идеально подходит, поскольку позволяет запускать полностью управляемую базу данных MySQL в облаке. Вот шаги, которые необходимо выполнить:
- Перейдите на страницу Cloud SQL: https://console.cloud.google.com/sql/instances
- Нажмите «Создать экземпляр».
- Включите API (при необходимости). Это может занять несколько секунд.
- Выберите MySQL.
- (Мы стараемся предложить вам самый дешевый вариант, чтобы он прослужил дольше):
- Издание: Enterprise
- Предварительные настройки: разработка (мы пробовали песочницу, но она нам не подошла).
- Версия MySQL: 5.7 (ух ты, привет из прошлого!)
- Идентификатор экземпляра: выберите
appmod-phpapp(если вы его измените, не забудьте также внести соответствующие изменения в будущие скрипты и решения). - Пароль: любой, но запишите его как CLOUDSQL_INSTANCE_PASSWORD
- Регион: оставьте тот же регион, который вы выбрали для остальной части приложения (например, Милан =
europe-west8). - Доступно по зонам: Одна зона (мы копим деньги на демонтаж).
Нажмите кнопку «Создать экземпляр», чтобы развернуть базу данных Cloud SQL; ⌛это займет около 10 минут⌛ . Тем временем продолжайте читать документацию; вы также можете начать решать следующий модуль («Контейнеризация вашего PHP-приложения»), поскольку он не зависит от этого модуля в первой части (пока вы не настроите подключение к базе данных).
Примечание . Этот экземпляр обойдется вам примерно в 7 долларов в день. Не забудьте отключить его после завершения мастер-класса.
Создайте базу данных image_catalog и пользователя в Cloud SQL.
В состав проекта приложения входит папка db/ , содержащая два SQL-файла:
- 01_schema.sql : Содержит SQL-код для создания двух таблиц, содержащих данные о пользователях и изображениях.
- 02_seed.sql : Содержит SQL-код для заполнения ранее созданных таблиц данными.
Эти файлы будут использованы позже, после создания базы данных image_catalog . Для этого выполните следующие действия:
- Откройте свой экземпляр и перейдите на вкладку «Базы данных»:
- Нажмите кнопку "Создать базу данных".
- Назовите его
image_catalog(как в конфигурации PHP-приложения).

Затем мы создаём пользователя базы данных. С его помощью мы можем пройти аутентификацию в базе данных image_catalog.
- Теперь нажмите на вкладку «Пользователи» .
- Нажмите «Добавить учетную запись пользователя».
- Пользователь: Давайте создадим один:
- Имя пользователя:
appmod-phpapp-user - Пароль: выберите пароль, который вы сможете запомнить, или нажмите «сгенерировать».
- Сохраните параметр " Разрешить любому хосту (%) ".
- Нажмите ДОБАВИТЬ.
Откройте базу данных для известных IP-адресов.
Обратите внимание, что все базы данных в Cloud SQL изначально "изолированы". Для обеспечения доступа к ним необходимо явно настроить сеть.
- Щелкните по своему экземпляру
- Откройте меню «Подключения».
- Перейдите на вкладку «Сеть».
- Нажмите в разделе «Авторизованные сети». Теперь добавьте сеть (т.е. подсеть).
- На данный момент давайте выберем быстрые, но НЕБЕЗОПАСНЫЕ настройки, чтобы приложение могло работать — позже вы, возможно, захотите ограничить его доступ только для тех IP-адресов, которым доверяете:
- Имя: «Все в мире — неуверенные в себе».
- Сеть: "
0.0.0.0/0"(Примечание: это НЕБЕЗОПАСНАЯ часть!) - Нажмите ГОТОВО
- Нажмите «Сохранить».
Вы должны увидеть что-то подобное:

Примечание . Это решение является хорошим компромиссом для завершения семинара за O (часов). Однако, ознакомьтесь с документом по безопасности , чтобы обеспечить безопасность вашего решения для использования в производственной среде!
Пора проверить подключение к базе данных!
Давайте проверим, работает ли созданный нами ранее пользователь image_catalog .
Откройте «Cloud SQL Studio» внутри экземпляра и введите базу данных, имя пользователя и пароль для аутентификации, как показано ниже:

Теперь, когда вы вошли, вы можете открыть редактор SQL и перейти к следующему разделу.
Импортируйте базу данных из кода.
Используйте редактор SQL для импорта таблиц image_catalog с их данными. Скопируйте SQL-код из файлов в репозитории ( 01_schema.sql , а затем 02_seed.sql ) и выполните их один за другим в последовательном порядке.
После этого в таблице image_catalog должны появиться две таблицы: users и images, как показано ниже:

Вы можете проверить это, выполнив в редакторе следующую команду: select * from images;
Также обязательно запишите публичный IP-адрес экземпляра Cloud SQL, он понадобится вам позже. Чтобы получить IP-адрес, перейдите на главную страницу экземпляра Cloud SQL в раздел «Обзор» (Обзор > Подключиться к этому экземпляру > Публичный IP-адрес).
4. Модуль 2: Контейнеризация вашего PHP-приложения

Мы хотим создать это приложение для облачной среды.
Это означает упаковку кода в своего рода ZIP-архив, содержащий всю информацию для его запуска в облаке.
Есть несколько способов его упаковать:
- Docker . Очень популярный инструмент, но его правильная настройка довольно сложна.
- Сборщики сборки (Buildpacks) . Менее популярны, но, как правило, автоматически угадывают, что собирать и что запускать. Часто это просто работает!
В рамках этого семинара мы будем исходить из того, что вы используете Docker.
Если вы выбрали использование Cloud Shell , сейчас самое время снова открыть его (щелкните в правом верхнем углу консоли Cloud Shell).

Это должно открыть удобную оболочку внизу страницы, где вы должны были скопировать код на этапе настройки .

Docker
Если вам нужен полный контроль, это именно то решение. Оно особенно полезно, когда необходимо настроить определенные библиотеки и внедрить некоторые неочевидные функции (например, изменить права доступа при загрузке файлов, добавить нестандартный исполняемый файл в ваше приложение и т. д.).
Поскольку в конечном итоге мы хотим развернуть наше контейнеризированное приложение в Cloud Run, ознакомьтесь со следующей документацией . Как бы вы перенесли его с PHP 8 на PHP 5.7? Возможно, для этого можно использовать Gemini. В качестве альтернативы вы можете использовать эту готовую версию:
# 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
Последняя версия Dockerfile доступна здесь .
Для локального тестирования нашего приложения нам необходимо изменить файл config.php таким образом, чтобы наше PHP-приложение подключалось к базе данных MySQL, доступной в Google CloudSQL. В зависимости от ваших предыдущих настроек, заполните пробелы :
<?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_HOST— это публичный IP-адрес Cloud SQL, его можно найти в консоли SQL:

-
DB_NAMEдолжен оставаться без изменений:image_catalog -
DB_USERдолжен бытьappmod-phpapp-user -
DB_PASS— это значение, которое вы выбираете. Заключите его в одинарные кавычки и экранируйте при необходимости.
Также, не стесняйтесь переводить несколько итальянских статей на английский язык с помощью Gemini !
Итак, теперь, когда у вас есть Dockerfile и вы настроили ваше PHP-приложение для подключения к базе данных, давайте попробуем это сделать!
Установите Docker, если у вас его ещё нет ( ссылка ). Если вы используете Cloud Shell, это не потребуется (как здорово, правда?).
Теперь попробуйте собрать и запустить ваше контейнеризированное PHP-приложение, используя соответствующие команды docker build и 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
Если всё работает, при подключении к локальному хосту вы должны увидеть следующую веб-страницу! Теперь ваше приложение работает на порту 8080 , нажмите значок «Предварительный просмотр веб-страницы» (браузер с глазом), а затем выберите «Предварительный просмотр на порту 8080 » (или «Изменить порт» для любого другого порта).

Проверьте результат в вашем браузере.
Теперь ваше приложение должно выглядеть примерно так:

А если вы войдете в систему под учетной записью Admin/admin123, вы должны увидеть что-то подобное.

Отлично!!! За исключением итальянского текста, всё работает! 🎉🎉🎉
Если ваша Docker-контейнеризация работает корректно, но учетные данные базы данных неверны, вы можете получить примерно следующее:

Попробуйте ещё раз, вы близки к цели!
Сохранение в реестр артефактов [необязательно]
К этому моменту у вас должно быть готовое к развертыванию в облаке контейнеризированное PHP-приложение. Далее нам понадобится место в облаке для хранения образа Docker и обеспечения доступа к нему для развертывания в сервисах Google Cloud, таких как Cloud Run. Это решение для хранения называется Artifact Registry — полностью управляемый сервис Google Cloud, предназначенный для хранения артефактов приложения, включая образы контейнеров Docker, пакеты Maven, модули npm и многое другое.
Давайте создадим репозиторий в Google Cloud Artifact Registry, используя соответствующую кнопку.

Выберите допустимое имя, формат и регион, подходящие для хранения артефактов.

Вернитесь к тегу вашей локальной среды разработки и загрузите образ контейнера приложения в только что созданный репозиторий реестра артефактов. Для этого выполните следующие команды.
- docker tag SOURCE_IMAGE[:TAG] TARGET_IMAGE[:TAG]
- docker push TARGET_IMAGE[:TAG]
В результате должно получиться примерно так, как на следующем скриншоте.

Ура! 🎉🎉🎉 Вы можете перейти на следующий уровень. А пока, может быть, потратьте 2 минуты на то, чтобы попробовать загрузить/войти/выйти из системы и ознакомиться с конечными точками приложения. Они вам понадобятся позже.
Возможные ошибки
Если возникают ошибки контейнеризации, попробуйте использовать Gemini для объяснения и исправления ошибки, предоставив следующую информацию:
- Ваш текущий Dockerfile
- Получена ошибка
- [при необходимости] выполняемый PHP-код.
Проверьте права доступа к файлу . Также попробуйте использовать конечную точку /upload.php и загрузить изображение. Возможно, вы получите ошибку, указанную ниже. В этом случае вам нужно внести изменения в Dockerfile , используя команды chmod/chown .
Предупреждение : move_uploaded_file(uploads/image (3).png): не удалось открыть поток: Отказано в доступе в /var/www/html/upload.php на строке 11
Ошибка PDOException "could not find driver" (или "Errore di connessione: could not find driver") . Убедитесь, что в вашем Dockerfile указаны необходимые библиотеки PDO для MySQL ( pdo_mysql ) для подключения к базе данных. Вдохновение можно почерпнуть из решений, представленных здесь .
Не удалось перенаправить ваш запрос на бэкэнд. Не удалось подключиться к серверу на порту 8080. Это означает, что вы, вероятно, используете неправильный порт. Убедитесь, что вы используете тот порт, с которого фактически работают Apache/Nginx. Это нетривиальная задача. Если возможно, попробуйте использовать порт 8080 (это упростит работу с Cloud Run). Если вы хотите оставить порт 80 (например, потому что Apache этого требует), используйте другую команду для его запуска:
$ 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. Модуль 3: Развертывание приложения в Cloud Run

Почему именно Cloud Run?
Вполне резонный вопрос! Несколько лет назад вы бы наверняка выбрали Google App Engine.
Проще говоря, сегодня Cloud Run использует более современный технологический стек, его проще развертывать, он дешевле и масштабируется до нуля, когда вы его не используете. Благодаря своей гибкости в запуске любых контейнеров без сохранения состояния и интеграции с различными сервисами Google Cloud, он идеально подходит для развертывания микросервисов и современных приложений с минимальными затратами и максимальной эффективностью.
В частности, Cloud Run — это полностью управляемая платформа от Google Cloud, позволяющая запускать контейнеризированные приложения без сохранения состояния в бессерверной среде. Она автоматически управляет всей инфраструктурой, масштабируясь с нуля для обработки входящего трафика и уменьшая нагрузку в режиме ожидания, что делает её экономически эффективной и производительной. Cloud Run поддерживает любой язык программирования или библиотеку, если они упакованы в контейнер, что обеспечивает большую гибкость в разработке. Она хорошо интегрируется с другими сервисами Google Cloud и подходит для создания микросервисов, API, веб-сайтов и событийно-ориентированных приложений без необходимости управления серверной инфраструктурой.
Предварительные требования
Для выполнения этой задачи вам необходимо установить gcloud на свой локальный компьютер. Если нет, см. инструкции здесь . Если же вы используете Google Cloud Shell, никаких действий предпринимать не нужно.
Перед развертыванием...
Если вы работаете в локальной среде, выполните аутентификацию в Google Cloud с помощью следующих команд.
-
$ gcloud auth login –update-adc # not needed in Cloud Shell
Это должно обеспечить вашу аутентификацию через OAuth в вашем браузере. Убедитесь, что вы входите через Chrome под тем же пользователем (например, vattelapesca@gmail.com), который авторизован в Google Cloud с включенной оплатой.
Включите API Cloud Run с помощью следующей команды:
-
$ gcloud services enable run.googleapis.com cloudbuild.googleapis.com
На данном этапе все готово для развертывания в Cloud Run.
Разверните свое приложение в Cloud Run с помощью gcloud.
Команда, позволяющая развернуть приложение в Cloud Run, — это gcloud run deploy . Для достижения желаемого результата необходимо установить несколько параметров. Минимальный набор параметров (которые можно указать через командную строку или инструмент запросит у вас в интерактивной подсказке) следующий:
- Укажите имя службы Cloud Run, которую вы хотите развернуть для своего приложения. Служба Cloud Run вернет вам URL-адрес, предоставляющий конечную точку для вашего приложения.
- Регион Google Cloud , в котором будет работать ваше приложение. (
--regionREGION) - Образ контейнера , который оборачивает ваше приложение.
- Переменные среды , которые ваше приложение должно использовать во время выполнения.
- Флаг Allow-Unauthenticated разрешает доступ к вашему приложению всем без дополнительной аутентификации.
Обратитесь к документации (или прокрутите вниз, чтобы найти возможное решение), чтобы узнать, как применить эту опцию к вашей командной строке.
Развертывание займет несколько минут. Если все в порядке, вы должны увидеть что-то подобное в консоли Google Cloud.


Перейдите по предоставленной Cloud Run ссылке и протестируйте свое приложение. После аутентификации вы должны увидеть что-то подобное.

Команда `gcloud run deploy` без аргументов
Возможно, вы заметили, что gcloud run deploy задаёт правильные вопросы и заполняет пропущенные вами поля. Это просто потрясающе!
Однако в нескольких модулях мы добавим эту команду в триггер Cloud Build, поэтому интерактивные вопросы нам не по карману. Нам нужно заполнить каждый параметр команды. Итак, вы хотите создать идеальный вариант: gcloud run deploy --option1 blah --foo bar --region your-fav-region . Как это сделать?
- Повторяйте шаги 2-3-4 до тех пор, пока gcloud не перестанет задавать вопросы:
- [ЦИКЛ]
gcloud run deployс найденными на данный момент параметрами - [LOOP] системы запрашивают вариант X
- [ЦИКЛ] Найдите в общедоступной документации, как настроить X из командной строки, добавив опцию
--my-option [my-value]. - Теперь вернёмся к шагу 2, если только gcloud не завершит работу без дальнейших вопросов.
- Эта команда `gcloud run deploy BLAH BLAH BLAH` просто потрясающая! Сохраните её где-нибудь, она понадобится вам позже на этапе Cloud Build!
Возможное решение находится здесь . Документация находится здесь .
Ура! 🎉🎉🎉 Вы успешно развернули свое приложение в Google Cloud, выполнив первый шаг модернизации.
6. Модуль 4: Очистка паролей с помощью менеджера секретов

На предыдущем этапе нам удалось успешно развернуть и запустить наше приложение в Cloud Run. Однако мы сделали это с нарушением правил безопасности: передали некоторые секретные данные в открытом виде .
Первая итерация: обновите файл config.php , чтобы использовать ENV.
Возможно, вы заметили, что мы вставляем пароль к базе данных непосредственно в код файла config.php. Это хорошо для целей тестирования и проверки работоспособности приложения. Но в производственной среде нельзя использовать такой код. Пароль (и другие параметры подключения к базе данных) должны считываться динамически и передаваться приложению во время выполнения. Измените файл config.php так, чтобы он считывал параметры базы данных из переменных окружения. Если это не удается, следует рассмотреть возможность установки значений по умолчанию . Это полезно в случае, если не удается загрузить переменные окружения, чтобы на странице отображалось, используются ли значения по умолчанию. Заполните пробелы и замените код в файле 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();
?>
Поскольку ваше приложение контейнеризировано, вам необходимо предоставить способ передачи переменных окружения в приложение. Это можно сделать несколькими способами:
- Во время сборки , в Dockerfile, добавьте к предыдущему Dockerfile 4 параметра, используя синтаксис ENV DB_VAR=ENV_VAR_VALUE. Это установит значения по умолчанию, которые можно будет переопределить во время выполнения. Например, 'DB_NAME' и 'DB_USER' можно установить здесь и нигде больше.
- Во время выполнения . Вы можете установить эти переменные для Cloud Run как из командной строки, так и из пользовательского интерфейса. Это подходящее место для размещения всех ваших 4 переменных (если вы не хотите оставить значения по умолчанию, заданные в Dockerfile).
На локальном компьютере вам может понадобиться поместить переменные окружения в файл .env (проверьте папку solutions ).
Также убедитесь, что файл .env добавлен в ваш .gitignore : вы же не хотите, чтобы ваши секреты попали на GitHub!
echo .env >> .gitignore
После этого вы можете протестировать экземпляр локально:
docker run -it -p 8080:8080 --env-file .env my-php-app-docker
Теперь вы достигли следующего:
- Ваше приложение будет динамически считывать переменные из вашей среды выполнения.
- Вы повысили уровень безопасности, удалив пароль к базе данных из своего кода.
Теперь вы можете развернуть новую версию в Cloud Run. Давайте перейдем к пользовательскому интерфейсу и вручную настроим переменные среды:
- Перейдите по ссылке https://console.cloud.google.com/run
- Нажмите на ваше приложение
- Нажмите «Редактировать и развернуть новую версию».
- На первой вкладке «Контейнер(ы)» щелкните нижнюю вкладку «Переменные и секреты».
- Нажмите кнопку "+ Добавить переменную" и добавьте все необходимые переменные. В результате у вас должно получиться что-то подобное:


Идеально ли это? Нет. Ваш пароль по-прежнему виден большинству операторов. Эту проблему можно решить с помощью Google Cloud Secret Manager.
Вторая версия: Менеджер секретов
Ваши пароли исчезли из вашего собственного кода: победа! Но подождите — мы уже в безопасности?
Ваши пароли по-прежнему видны любому, кто имеет доступ к консоли Google Cloud. Более того, если вы получите доступ к файлу развертывания Cloud Run в формате YAML, вы сможете его восстановить. Или, если вы попытаетесь отредактировать или развернуть новую версию Cloud Run, пароль будет виден в разделе «Переменные и секреты», как показано на скриншотах ниже.
Google Cloud Secret Manager — это безопасный централизованный сервис для управления конфиденциальной информацией, такой как ключи API, пароли, сертификаты и другие секреты.
Это позволяет хранить, управлять и получать доступ к секретам с детальной настройкой прав доступа и надежным шифрованием. Интегрированный с системой управления идентификацией и доступом (IAM) Google Cloud, Secret Manager позволяет контролировать доступ к конкретным секретам, обеспечивая безопасность данных и соответствие нормативным требованиям.
Он также поддерживает автоматическую ротацию и версионирование секретов, упрощая управление жизненным циклом секретов и повышая безопасность приложений в сервисах Google Cloud.
Чтобы получить доступ к Secret Manager, перейдите из меню «Гамбургер» в раздел «Службы безопасности » и найдите его в разделе «Защита данных» , как показано на скриншоте ниже.

После этого включите API Secret Manager, как показано на следующем изображении.

- Теперь нажмите « Создать секрет »: назовём его, как вам удобнее:
- Имя:
php-amarcord-db-pass - Секретное значение: ' ваш пароль к базе данных ' (игнорируйте часть "загрузить файл").
- Добавьте аннотацию к этой секретной ссылке, она должна выглядеть примерно так:
projects/123456789012/secrets/php-amarcord-db-pass. Это уникальный указатель на ваш секрет (для Terraform, Cloud Run и других). Число — это уникальный номер вашего проекта.
Совет : Старайтесь использовать единообразные правила именования для ваших секретов, специализируя их слева направо, например: cloud-devrel-phpamarcord-dbpass
- Организация (совместно с компанией)
- Команда (внутри организации)
- Применение (внутри команды)
- Название переменной (внутри приложения)
Это позволит вам легко создавать регулярные выражения для поиска всех секретных данных для одного приложения.
Создайте новую ревизию Cloud Run.
Теперь, когда у нас есть новый секрет, нам нужно удалить переменную окружения DB_PASS и заменить её новым секретом. Итак:
- Доступ к Cloud Run через консоль Google Cloud.
- Выберите приложение.
- Нажмите «Редактировать и развернуть новую версию».
- Найдите вкладку "Переменные и секреты".
- Используйте кнопку "+ Указать секрет", чтобы сбросить переменную окружения DB_PASS.
- Используйте тот же параметр "DB_PASS" для указанных секретов и используйте последнюю версию.

После завершения вы должны получить следующую ошибку.

Попробуйте разобраться, как это исправить. Для этого вам нужно зайти в раздел IAM и администрирование и изменить предоставленные права доступа. Удачной отладки!
Как только вы разберетесь, вернитесь в Cloud Run и разверните новую версию. Результат должен выглядеть примерно так, как показано на рисунке ниже:

Совет : консоль разработчика (пользовательский интерфейс) отлично подходит для выявления проблем с правами доступа. Уделите время изучению всех ссылок для ваших облачных сущностей!
7. Модуль 5: Настройка CI/CD с помощью Cloud Build

Зачем нужен конвейер CI/CD?
К этому моменту вы, вероятно, уже несколько раз набрали команду gcloud run deploy , возможно, неоднократно отвечая на один и тот же вопрос.
Устали вручную развертывать приложение с помощью команды `gcloud run deploy`? Было бы здорово, если бы ваше приложение автоматически развертывалось каждый раз, когда вы отправляете новые изменения в свой репозиторий Git!
Для использования конвейера CI/CD вам понадобятся две вещи:
- Персональный репозиторий Git : К счастью, на шаге 2 вы уже должны были создать форк репозитория workshop в своей учетной записи GitHub. Если нет, вернитесь и выполните этот шаг. Ваш форкнутый репозиторий должен выглядеть так:
https://github.com/<YOUR_GITHUB_USER>/app-mod-workshop - Cloud Build . Этот замечательный и недорогой сервис позволяет настраивать автоматизацию сборки практически для всего: Terraform, контейнеризированных приложений и т.д.
В этом разделе основное внимание будет уделено настройке Cloud Build.
Представляем Cloud Build!
Для этого мы воспользуемся Cloud Build:
- Создайте исходный код (с помощью Dockerfile). Представьте это как «большой .zip-файл», содержащий все необходимое для сборки и запуска (ваш «артефакт сборки»).
- Отправьте этот артефакт в Реестр артефактов (AR).
- Затем выполните развертывание приложения "php-amarcord" из AR в Cloud Run.
- Это создаст новую версию («ревизию») существующего приложения (представьте себе слой с новым кодом), и мы настроим его таким образом, чтобы в случае успешного обновления трафик перенаправлялся на новую версию.
Это пример нескольких сборок моего приложения php-amarcord :

Как нам всё это сделать?
- Создав один идеальный YAML-файл:
cloudbuild.yaml - Путем создания триггера Cloud Build.
- Подключившись к нашему репозиторию GitHub через пользовательский интерфейс Cloud Build .
Создайте триггер (и подключите репозиторий).
- Перейдите по ссылке https://console.cloud.google.com/cloud-build/triggers
- Нажмите кнопку «Создать триггер».
- Компиляция:
- Название: Что-то осмысленное, например,
on-git-commit-build-php-app - Событие: Отправка изменений в ветку
- Источник: "Подключить новый репозиторий"

- Справа откроется окно: "Подключить репозиторий"
- Источник: "Github" (первый)
- "Продолжать"
- Authenticate откроет окно на GitHub для кросс-аутентификации. Следуйте инструкциям и наберитесь терпения. Если у вас много репозиториев, это может занять некоторое время.
- "Выберите репозиторий". Выберите свою учетную запись/репозиторий и поставьте галочку в разделе "Я понимаю...".
- Если вы получили ошибку : «Приложение GitHub не установлено ни в одном из ваших репозиториев», нажмите «Установить Google Cloud Build» и следуйте инструкциям.
Нажмите «Подключиться». 
- Ура! Ваш репозиторий теперь подключен.
- Вернемся к части, касающейся спускового механизма...
- Конфигурация: Автоматическое определение (*)
- Дополнительные параметры: Выберите учетную запись службы "[НОМЕР_ПРОЕКТА]- compute@developer.gserviceaccount.com "
- xxxxx — это идентификатор вашего проекта.
- Учетная запись службы вычислений по умолчанию подходит для лабораторного использования — не используйте ее в производственной среде! ( Подробнее ).
- Оставьте все остальное как есть.
- Нажмите кнопку «Создать».
(*) Это самый простой способ, поскольку он проверяет наличие Dockerfile или cloudbuild.yaml. Однако cloudbuild.yaml дает вам реальную возможность решать, что делать на каком этапе.
У меня есть власть!
Теперь триггер не будет работать, если вы не укажете учетную запись службы Cloud Build (что такое учетная запись службы? Это адрес электронной почты «робота», который действует от вашего имени при выполнении задачи — в данном случае, при сборке чего-либо в облаке!).
Ваш системный администратор не сможет выполнить сборку и развертывание, если вы не предоставите ему для этого необходимые полномочия. К счастью, это несложно!
- Перейдите в раздел "Cloud Build" > " Настройки ".
- "[НОМЕР_ПРОЕКТА]- compute@developer.gserviceaccount.com " сервисный аккаунт
- Отметьте эти пункты:
- Cloud Run
- Секретный менеджер
- Служебные аккаунты
- Cloud Build
- Также поставьте галочку напротив пункта "Установить в качестве предпочтительной учетной записи службы".

Где находится YAML-файл Cloud Build?
Мы настоятельно рекомендуем вам уделить некоторое время созданию собственного облачного Build YAML-файла.
Однако, если у вас нет времени или вы не хотите его выделять, вы можете почерпнуть вдохновение из этой папки с решениями: .solutions
Теперь вы можете отправить изменения на GitHub и наблюдать за работой Cloud Build.
Настройка Cloud Build может быть непростой задачей. Ожидайте некоторой переписки с участием следующих сторон:
- Проверка логов в https://console.cloud.google.com/cloud-build/builds;region=global
- Поиск вашей ошибки.
- Исправление в коде и повторная отправка git commit / git push.
- Sometimes the error is not in code, but in some configuration. In that case, you can issue a new build from UI (cloud build > "Triggers" > Run)

Note that if you use this solution, there is still some work to do. For instance, you need to set the ENV variables for the newly created dev/prod endpoints:

Это можно сделать двумя способами:
- Via UI - by setting ENV variables again
- Via CLI by crafting the "perfect" script for you. An example can be found here: gcloud-run-deploy.sh . You need to tweak a few things, for instance the endpoint and project number; You can find your project number in the Cloud Overview .
How do i commit code to github?
It's beyond the scope of this workshop to teach you the best way to git push to github. However, in case your are stuck and you're in Cloud Shell, there are two ways:
- CLI . Add an ssh key locally and add a remote with git@github.com :YOUR_USER/app-mod-workshop.git (instead of http)
- VSCode . If you use the Cloud Shell editor, ou can use the Source control (ctrl-shift-G) tab, click on "sync changes" and follow instructions. You should be able to authenticate your github account to vscode and the pull/push from there become a breeze.

Remember to git add clodubuild.yaml among other files, or it won't work.
Deep vs Shallow "dev/prod parity" [optional]
If you copied the model version from here , you're going to have two identical DEV and PROD versions. This is cool, and in line with the rule 10 of The Twelve-Factor App .
However, we are using two different Web endpoints to have an app pointing to the same Database. This is good enough for a workshop; however, in real life, you want to spend some time to create a proper prod environment. This means having two databases (one for dev and one for prod) and also choosing where to have them for disaster recovery / high availability. This goes beyond the scope of this workshop, but it's some food for thought.
If you have time to do a "deep" version of production, please keep in mind all the resources you need to duplicate, like:
- Cloud SQL Database (and probably SQL instance).
- GCS bucket
- Cloud Function.
- You might use Gemini 1.5 Flash as a model in dev (cheaper, faster), and Gemini 1.5 Pro (more powerful).
In general, every time you do something about the app, think critically: should production has this same value or not? And if not, duplicate your effort. This of course is a lot easier with Terraform, where you can inject your environment (-dev, -prod) as a suffix to your resources.
8. Module 6: Move to Google Cloud Storage

Хранилище

Currently the app stored the state in a docker container. If the machine breaks, the app explodes, or simply if you push a new revision , a new revision will be scheduled, with a new, empty storage: 🙈
How do we fix it? there are a number of approaches.
- Store images in the DB. That's what i've ended up doing with my previous PHP app. It's the simplest solution as it doesn't add complexity to it. But it adds latency and load to your DB for sure!
- Migrate your Cloud Run app to a storage-friendly solution: GCE + Persistent disk ? Maybe GKE + Storage ? Note: what you gain in control, you lose in agility.
- Move to GCS . Google Cloud Storage offers best in class Storage for the whole of Google Cloud and it's the most Cloud idiomatic solution. However, it requires us with getting dirty with PHP libraries . Do we have PHP 5.7 libraries for GCS ? Does
PHP 5.7even supportComposer(seems like PHP 5.3.2 is the earliest version supported by Composer)? - Maybe use a docker sidecar ?
- Or maybe use GCS Cloud Run Volume Mounts . This sounds amazing.
🤔 Migrate storage (open ended)
[Open Ended] In this exercise, we want you to find a solution to move your images in a way which is persisted in some way.
Acceptance test
I don't want to tell you the solution, but I want this to happen:
- You upload
newpic.jpg. You see it in the app. - You upgrade the app to a new version.
-
newpic.jpgis still there, visible.
💡 Possible solution (GCS Cloud Run Volume Mounts)
This is a very elegant solution which allows us to achieve stateful file uploads while not touching the code AT ALL (apart from showing an image description, but that's trivial and just for eye satisfaction).
This should allow you to mount a folder from Cloud Run to GCS, so:
- All uploads to GCS will actually be visible in your app.
- All uploads to your app will actually be uploaded to GCS
- Magic will happen tyo objects uploaded in GCS (chapter 7).
Note . Please read the FUSE fine print. This is NOT ok if performance is an issue.
Create a GCS bucket
GCS is the omni-present storage service of Google Cloud. It's battle-tested, and is used by every GCP service needing storage.
Note that Cloud Shell export PROJECT_ID as 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/"
Configure Cloud Run to mount the bucket in the /uploads/ folder
Now let's come to the elegant part. We create a volume php_uploads and instruct Cloud Run to do a FUSE mount on MOUNT_PATH (something like /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"
Now, repeat this step for all the endpoints you want to point to Cloud Storage.
You can also achieve the same from UI
- Under "Volumes" tab, create a Volume Mounts pointing to your bucket, of type "Cloud Storage bucket", for example with name "php_uploads".
- Under Container(s) > Volume Mounts mount the volume you just created on the volume point requested by your app. It depends on the dockerfile, but it might look like
var/www/html/uploads/.
Either way, if it works, editing the new Cloud Run revision should show you something like this:

Now test the new application uploading one new image to the /upload.php endpoint.
The images should flow seamlessly on GCS without writing a single line of PHP:

Что только что произошло?
Something very magical has happened.
An old application with old code is still doing its job. A new, modernized stack allows us to have all the images/pictures in our app comfortably sitting in a stateful Cloud Bucket. Now the sky is the limit:
- Want to send an email every time an image with "dangerous" or "nude" comes in? You can do that without touching the PHP code.
- Want to use a Gemini Multimodal model every time an image comes in to describe it, and upload the DB with its description? You can do that without touching the PHP code. You don't believe me? Keep reading on in chapter 7.
We've just unlocked a big space of opportunity here.
9. Module 7: Empower your App with Google Gemini

Now you have an awesome modernized, shiny new PHP app (like a 2024 Fiat 126 ) with Cloudified storage.
What can you do with it?
Предварительные требования
In the previous chapter, a model solution allowed us to mount images /uploads/ on GCS, de facto separating the App logic from the image storage.
This exercise requires you to:
- Have successfully completed exercise in chapter 6 (storage).
- Have a GCS bucket with the image uploads, where people upload pictures on your app and pictures flow to your bucket.
Set up a Cloud function (in python)
Have you ever wondered how to implement an event-driven application ? Something like:
- when <event> happens => send an email
- when <event> happens => if <condition> is true, then update the Database.
Event can be anything, from new record available in BigQuery, a new object changed in a folder in GCS, or a new message is waiting in a queue in Pub/Sub.
Google Cloud supports multiple paradigms to achieve this. Most notably:
- EventArc . See how to receive GCS events . Great to create DAGs and orchestrate actions based on if-then-else in the CLoud.
- Cloud Scheduler . Great for a midnight cron job in the Cloud, for instance.
- Cloud Workflows . Similarly to Event Arc, allows you to
- Cloud Run Functions (familiarly known as
lambdas). - Cloud Composer . Basically Google version of Apache Airflow , also great for DAG s.
In this exercise, we'll delve into Cloud Function to achieve a quite spectacular result. And we will provide optional exercises for you.
Note that sample code is provided under .solutions/
Set up a Cloud function (🐍 python)
We are trying to create a very ambitious GCF.
- When a new image is created on GCS.. (probably as someone has uploaded it on the app - but not only)
- .. call Gemini to describe it and get a textual description of the image .. (would be nice to check the MIME and ensure its an image and not a PDF, MP3, or Text)
- .. and update the DB with this description. (this might require patching the DB to add a
descriptioncolumn to theimagestable).
Patch the DB to add description to images
- Open Cloud SQL Studio:

- Put your user and password for the Images DB
- Inject this SQL which adds a column for an image description:
ALTER TABLE images ADD COLUMN description TEXT;

And bingo! Try now to check if it worked:
SELECT * FROM images;
You should see the new description column:

Write the Gemini f(x)
Note . This function was actually created with Gemini Code assist help.
Note . Creating this function you might incur into IAM permission errors. Some are documented below under "Possible errors" paragraph.
- Включите API
- Go to https://console.cloud.google.com/functions/list
- Click "Create Function"
- Enable APIs from API wizard:

You can either create the GCF from UI or from command line. Here we will use the command line.
A possible code can be found under .solutions/
- Create a folder to host your code, eg "gcf/". Enter the folder.
- Create a
requirements.txtfile:
google-cloud-storage
google-cloud-aiplatform
pymysql
- Create a python function. Sample code here: 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
- Push the function. You can use a script similar to this: gcf/push-to-gcf.sh .
Note 1 . Make sure to source the ENVs with the right values, or just add them on top ( GS_BUCKET=blah , ..):
Note 2 . This will push all the local code ( . ) so make sure to surround your code in a specific folder and to use .gcloudignore like a pro to avoid pushing huge libraries. ( example ).
#!/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
Note : in this example, generate_caption will be the invoked method, and Cloud Function will pass the GCS event to it with all the relevant info (bucket name, object name, ..). Take some time to debug that event python dict.
Testing the function
Модульные тесты
The function has many moving parts. You might want to be able to test all the single ones.
An example is in gcf/test.py .
Cloud Functions UI
Also take some time to explore your function on the UI. Every tab is worth exploring, particularly the Source (my favourite), Variables , Trigger , and Logs ; You'll spend a lot of time in the Logs to troubleshoots errors (also see possible errors on the bottom of this page). also make sure to check Permissions .

E2E Test
Time to manually test the function!
- Go to your app, and login
- Upload a picture (not too big, we've seen issues with big images)
- check on UI the picture is uploaded.
- Check on Cloud SQL Studio that the description has been updated. Login and run this query:
SELECT * FROM images.

And it works! We might also want to update the frontend to show that description.
Update PHP to show [optional]
We have proven the app works. However, it would be nice that the users could also see that description.
We don't need to be PHP experts to add the description to the index.php . This code should do (yes, Gemini wrote it for me too!):
<?php if (!empty($image['description'])): ?>
<p class="font-bold">Gemini Caption:</p>
<p class="italic"><?php echo $image['description']; ?></p>
<?php endif; ?>
Position this code inside the foreach at your own taste.
In the next steps we also see a prettier UI version, thanks to Gemini Code Assist. A pretty version might look like this:

Выводы
You got a Cloud Function triggered on new objects landing on GCS which is able to annotate the content of the image like a human could do, and automatically update the DB. Wow!
What's next? You could follow the same reasoning to achieve two great functionalities.
[optional] Add further Cloud Functions [open ended]
A couple of additional features come to mind.
📩 Email Trigger
An email trigger which sends you an email every time someone sends a picture.
- Too often? Add a further constraint: A BIG picture, or a picture whose Gemini content contains the words "nude/nudity/violent".
- Consider checking
EventArcfor this.
🚫 Auto-moderate inappropriate pics
Currently a human admin is flagging images for "inappropriate". How about having Gemini doing the heavy lifting and moderating the space? Add a test to flag inappropriate trigger content and update the DB as we learnt in the previous function. This means basically taking the previous function, changing the prompt, and updating the DB based on the answer.
Caveat . Generative AI has unpredictable outputs. Make sure the "creative output" from Gemini is put "on rails". You might ask a deterministic answer like a confidence score from 0 to 1, a JSON, .. You can achieve this in many ways, for example: * Using python libraries pydantic , langchain , .. * Use Gemini Structured Output .
Tip . You could have MULTIPLE functions or have a single prompt which enforces a JSON answer (works greta with "Gemini Structured Output"as highlighted above) like:
What would the prompt be to generate this?
{
"description": "This is the picture of an arrosticino",
"suitable": TRUE
}
You could add in the prompt additional fields to get insights like: is there something good about it? Bad about it? Do you recognize the place? Is there some text (OCR has never been easier):
-
goods: "It looks like yummie food" -
bads: "It looks like unhealthy food" -
OCR: "Da consumare preferibilmente prima del 10 Novembre 2024" -
location: "Pescara, Lungomare"
While it's usually better to have N function for N outcomes, it's incredibly rewarding to do one which does 10 things. Check this article by Riccardo to see how.
Possible errors (mostly IAM / permissions)
The first I've developed this solution I came onto some IAM permission issues. I will add them here for empathy and to give some ideas on how to fix them.
Error: not enough permissions for Service Account
- Note that for deploying a GCF function which listens to a GCS bucket you need to set up proper permissions to the Service Account you are using for the job, as in figure:

You might also have to enable EventArc APIs - what a few minutes before they become fully available.
Error: Missing Cloud Run invoker
- Another comment from UI for GCF permissioning is this ( Cloud run Invoker role ):

This error can be fixed running the command in the image, which is similar to fix-permissions.sh
This issue is described here: https://cloud.google.com/functions/docs/securing/authenticating
Error: Memory limit exceeded
The first time I ran it, my logs could said: "'Memory limit of 244 MiB exceeded with 270 MiB used. Consider increasing the memory limit, see https://cloud.google.com/functions/docs/configuring/memory '". Again, add RAM to your GCF. This is super easy to do in the UI. Here's a possible bump:

Alternatively, you can also fix your Cloud run deployment script to bump MEM/CPU. This takes a bit longer.
Error: PubSub Published
Creatuing a trigger with GCF v1 gave once this error:

Again, this is easy to fix by going to IAM and giving your Service Account the "Pub/Sub Publisher" role.
Error: Vertex AI has not been used
If you receive this error:
Permission Denied: 403 Vertex AI API has not been used in project YOUR_PROJECT before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/aiplatform.googleapis.com/overview?project=YOR_PROJECT
You just need to enable Vertex AI APis. The easiest way to enable ALL needed APIs is this:
- https://console.cloud.google.com/vertex-ai
- Click the "enable all recommended APIS".

Error: EventArc Trigger not found.
If you get this, please redeploy the function.

Error: 400 Service agents are being provisioned
400 Service agents are being provisioned ( https://cloud.google.com/vertex-ai/docs/general/access-control#service-agents ). Service agents are needed to read the Cloud Storage file provided. So please try again in a few minutes.
If this happens, wait some time or ask a Googler.
10. Module 8: Create Availability SLOs
In the Chapter we try to achieve this:
- Creating SLIs
- Creating SLOs based on the SLIs
- Creating Alerts based on SLOs

This is a very dear topic to the author, since Riccardo works in the SRE / DevOps area of Google Cloud.
(open-ended) Create SLIs and SLOs for this app
How good is an app if you can't tell when it's down?
What is an SLO?
Oh my! Google invented SLOs! To read more about it I can suggest:
- SRE Book - chapter 2 - Implementing SLOs . ( 👉 more SREbooks )
- Art of SLOs ( awesome video ). It's a fantastic training to learn more about how to craft a perfect SLO for your service.
- SRE course on Coursera . I contributed to it!
Step 1: Create Availability SLI/SLO
Let's start with Availability SLO, as it's the easiest and possibly the most important thing you want to measure.
Luckily Cloud run comes with pre built SLO support, thanks to Istio .
Once your app is on Cloud run, this is super simple to achieve, it takes me 30 seconds.
- Go to your Cloud Run page.
- Click/select your app.
- Select the
SLOstab. - Click "+ Create SLO".
- Availability , Request-based
- Продолжать
- Calendar Month / 99%.
- click "Create SLO".

Step 2: set up Alerting on this SLO
I suggest to create 2 alerts:
- One with a low burnrate ("Slowburn") to alert you via email (simulates low pri ticket).
- One with a high burnrate ("Fastburn") to alert you via SMS (simulates high pri ticket / pager)
Go to your SLO tab from before.
Do this twice:

- Click "Create SLO Alert" (the 🔔 button with a plus inside, to the right)
- Lookback duration, Burn Rate threshold:
- [FAST]. First:
60min /10x - [SLOW]. Second:
720min /2x - Notification channel: click on Manage notification channels
- First, "Email" -> Add new -> ..
- Second, "SMS" -> Add new -> Verify on the phone.
- Tip: I like to use emoji in the names! It's fun for demos.
- when done, click the big X on top right.
- Select phone first (fast), email next (slow).
- Add some sample documentation like:
-
[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.
Бинго!
Окончательный результат
We can consider this exercise finished once you have 1 working SLO + 2x alerts for your availability, and it's alerting to your email and to your phone.
If you want you can add a Latency (and I strongly encourage you to do so) or even a more complex one. For latency, choose a latency you deem reasonable; when in doubt, choose 200ms .
11. Next steps
You've completed EVERYTHING, what's missing?
Some food for thought:
Play with Gemini
You can use Gemini in two flavours:
- Vertex AI. The "Enterprise way", intertwined with your GCP, which we've explored in chapter 7 (GCF+Gemini). All authentication magically works, and services beautifully interconnect.
- Google AI. The "Consumer way". You get a Gemini API Key from here and start building little scripts which can be tied onto any workload you already have (proprietary work, other clouds, localhost, ..). You just substitute your API key and the code starts magically to work.
We encourage you to try exploring the (2) with your own pet projects.
UI Lifting
I'm no good at UIs. But Gemini is! You can just take a single PHP page, and say something like this:
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!]
You can easily get this in less than 5 minutes, one Cloud Build away! :)
The response from Gemini was perfect (meaning, I didn't have to change a thing):

And here's the new layout in the author's personal app:

Note: the code is pasted as image as we don't want to encourage you to take the code, but to get Gemini to write the code for you, with your own creative UI/frontend constraints; trust me, you're left with very minor changes afterwards.
Безопасность
Properly securing this app is a non-goal for this 4-hour workshop, as it would increase the time to complete this workshop by 1-2 orders of magnitude.
However, this topic is super-important! We've collected some ideas in SECURITY .
12. Поздравляем!
Congratulations 🎉🎉🎉 , you've successfully modernized your legacy PHP application with Google Cloud.

In summary in this codelab you have learned:
- How to deploy a database in Google Cloud SQL and how to migrate your existing database into it.
- How to containerize your PHP application with Docker and Buildpacks and store its image to Google Cloud Artifact Registry
- How to deploy your containerized App to Cloud Run and make it run with Cloud SQL
- How to secretly store/use sensitive configuration parameters (such as DB password) using Google Secret Manager
- How to set up your CI/CD pipeline with Google Cloud Build to automatically build and deploy your PHP App at any code push to your GitHub repo.
- How to use Cloud Storage to "cloudify" your app resources
- How to leverage serverless technologies to build amazing workflows on top of Google Cloud without touching your app code.
- Use Gemini multimodal capabilities for a fitting use case.
- Implement SRE principles within Google Cloud
This is a great start for your journey into Application modernization with Google Cloud!
🔁 Feedback
If you want to tell us about your experience with this workshop, consider filing this feedback form .
We welcome your feedback as well as PR s for pieces of code you're particularly proud of.
🙏 Thanks
The author would like to thank Mirko Gilioli and Maurizio Ipsale from Datatonic for help on the writeup and testing the solution.
