۱. مقدمه
آخرین بهروزرسانی: 2024-11-01
چگونه یک برنامه قدیمی PHP را به Google Cloud مدرن کنیم؟
(📽️ یک ویدیوی مقدماتی ۷ دقیقهای برای این آزمایشگاه کد تماشا کنید)
رایج است که برنامههای قدیمی در حال اجرا در محل باشند و نیاز به نوسازی داشته باشند. این به معنای مقیاسپذیر، ایمن و قابل استقرار کردن آنها در محیطهای مختلف است.
در این کارگاه، شما:
- برنامه PHP را کانتینرایز کنید .
- به یک سرویس پایگاه داده مدیریتشده ( Cloud SQL ) منتقل شوید.
- استقرار در Cloud Run (جایگزین بدون عملیات برای GKE/Kubernetes).
- برنامه را با مدیریت هویت و دسترسی (IAM) و مدیر مخفی ایمن کنید .
- یک خط لوله CI/CD را از طریق Cloud Build تعریف کنید. Cloud Build میتواند به مخزن Git شما که روی ارائهدهندگان محبوب Git مانند GitHub یا GitLab میزبانی میشود، متصل شود و مثلاً با هر فشاری به main فعال شود.
- تصاویر برنامه را در فضای ابری میزبانی کنید. این کار از طریق نصب (mount) انجام میشود و برای تغییر برنامه نیازی به کدنویسی نیست.
- معرفی قابلیتهای هوش مصنوعی نسل جدید از طریق Gemini ، هماهنگشده با Cloud Functions (بدون سرور).
- با SLOها و نحوهی کار با اپلیکیشن تازه بهروزرسانیشدهتان آشنا شوید.
با دنبال کردن این مراحل، میتوانید به تدریج برنامه PHP خود را مدرن کنید، مقیاسپذیری، امنیت و انعطافپذیری استقرار آن را بهبود بخشید. علاوه بر این، انتقال به Google Cloud به شما این امکان را میدهد که از زیرساختها و سرویسهای قدرتمند آن بهره ببرید تا از اجرای روان برنامه خود در یک محیط ابری مطمئن شوید.
ما معتقدیم آنچه که در این مراحل ساده یاد خواهید گرفت، میتواند برای برنامه و سازمان شما با زبان/پشته متفاوت و موارد استفاده متفاوت اعمال شود.
درباره برنامه
برنامهای ( کد ، تحت مجوز MIT ) که شما آن را منشعب خواهید کرد، یک برنامهی پایهی PHP 5.7 با احراز هویت MySQL است. ایدهی اصلی این برنامه، ارائهی بستری است که در آن کاربران بتوانند عکس آپلود کنند و مدیران بتوانند تصاویر نامناسب را برچسبگذاری کنند. این برنامه دارای دو جدول است:
- کاربران . به صورت از پیش کامپایل شده به همراه مدیران ارائه میشود. افراد جدید میتوانند ثبتنام کنند.
- تصاویر . همراه با چند تصویر نمونه ارائه میشود. کاربران وارد شده میتوانند تصاویر جدید آپلود کنند. ما اینجا کمی جادو اضافه خواهیم کرد.
هدف شما
ما میخواهیم برنامه قدیمی را مدرن کنیم تا آن را در Google Cloud داشته باشیم. ما از ابزارها و خدمات آن برای بهبود مقیاسپذیری، افزایش امنیت، خودکارسازی مدیریت زیرساخت و ادغام ویژگیهای پیشرفته مانند پردازش تصویر، نظارت و ذخیرهسازی دادهها با استفاده از خدماتی مانند Cloud SQL، Cloud Run، Cloud Build، Secret Manager و موارد دیگر استفاده خواهیم کرد.

مهمتر از همه، ما میخواهیم این کار را گام به گام انجام دهیم تا شما بتوانید فرآیند فکری پشت هر مرحله را بیاموزید و معمولاً هر مرحله امکانات جدیدی را برای مراحل بعدی باز میکند (مثال: ماژولهای ۲ -> ۳ و ۶ -> ۷).
هنوز قانع نشدهاید؟ این ویدیوی ۷ دقیقهای را در یوتیوب ببینید.
آنچه نیاز دارید
- یک کامپیوتر با مرورگر و متصل به اینترنت.
- برخی از امتیازات GCP. برای اطلاع از آن به مرحله بعدی مراجعه کنید.
- شما از cloud Shell استفاده خواهید کرد. این پوسته با تمام دستورات از پیش نصب شده مورد نیاز شما و یک IDE ارائه میشود.
- حساب گیتهاب . برای انشعاب کد اصلی 🧑🏻💻 gdgpescara/app-mod-workshop با مخزن گیت خودتان به این حساب نیاز دارید. این حساب برای داشتن خط لوله CI/CD خودتان (کامیت خودکار -> ساخت -> استقرار) مورد نیاز است.
نمونه راهحلها را میتوانید اینجا پیدا کنید:
- مخزن نویسنده: https://github.com/Friends-of-Ricc/app-mod-workshop
- مخزن اصلی کارگاه، در پوشههای
.solutions/، به ازای هر فصل.
این کارگاه آموزشی برای تکمیل در Cloud Shell (در مرورگر) طراحی شده است.
با این حال، میتوان آن را از رایانه محلی شما نیز امتحان کرد.
۲. تنظیم اعتبار و فورک

اعتبار GCP را بازخرید کنید و محیط GCP خود را راهاندازی کنید [اختیاری]
برای اجرای این کارگاه، به یک حساب صورتحساب با مقداری اعتبار نیاز دارید. اگر از قبل صورتحساب خود را دارید، میتوانید از این مرحله صرف نظر کنید.
یک حساب کاربری گوگل جیمیل کاملاً جدید (*) ایجاد کنید تا به اعتبار GCP شما متصل شود. از مربی خود بخواهید که لینک را برای استفاده از اعتبار GCP به شما بدهد یا از اعتبارات اینجا استفاده کنید: bit.ly/PHP-Amarcord-credits .
با حساب کاربری تازه ایجاد شده وارد شوید و دستورالعملها را دنبال کنید.

(
) چرا به یک حساب جیمیل کاملاً جدید نیاز دارم؟ *
ما افرادی را دیدهایم که در codelab شکست خوردهاند، زیرا حساب کاربری آنها (بهویژه ایمیلهای کاری یا دانشجویی) قبلاً با GCP مواجه شده و سیاستهای سازمانی، توانایی آنها را برای انجام این کار محدود کرده است. توصیه میکنیم یا یک حساب جیمیل جدید ایجاد کنید یا از یک حساب جیمیل موجود (gmail.com) که قبلاً با GCP مواجه نشده است، استفاده کنید.
برای دریافت اعتبار، روی دکمه کلیک کنید.

فرم زیر را با نام و نام خانوادگی خود پر کنید و با شرایط و ضوابط موافقت کنید.
ممکن است لازم باشد چند ثانیه صبر کنید تا حساب پرداخت در اینجا ظاهر شود: https://console.cloud.google.com/billing
پس از انجام این کار، کنسول گوگل کلود را باز کنید و با کلیک روی انتخابگر پروژه در منوی کشویی بالا سمت چپ، جایی که «بدون سازمان» نشان داده شده است، یک پروژه جدید ایجاد کنید. به تصویر زیر مراجعه کنید.

اگر پروژه جدیدی ندارید، همانطور که در تصویر زیر نشان داده شده است، یک پروژه جدید ایجاد کنید. گزینه "پروژه جدید" در گوشه بالا سمت راست وجود دارد.

مطمئن شوید که پروژه جدید را به حساب پرداخت آزمایشی GCP به شرح زیر پیوند دادهاید.

شما آماده استفاده از پلتفرم ابری گوگل هستید. اگر مبتدی هستید یا فقط میخواهید همه کارها را در یک محیط ابری انجام دهید، میتوانید از طریق دکمه زیر در گوشه بالا سمت چپ، همانطور که در شکل زیر نشان داده شده است، به Cloud Shell و ویرایشگر آن دسترسی پیدا کنید.

مطمئن شوید که پروژه جدید شما در بالا سمت چپ انتخاب شده است:
انتخاب نشده (بد):

انتخاب شده (خوب):

برنامه را از گیتهاب فورک کنید
- به برنامه آزمایشی بروید: https://github.com/gdgpescara/app-mod-workshop
- روی چنگال 🍴 کلیک کنید.
- اگر حساب کاربری github ندارید، باید یک حساب کاربری جدید ایجاد کنید.
- موارد را مطابق میل خود ویرایش کنید.

- کد برنامه را با استفاده از کلون کنید
-
git clonehttps://github.com/YOUR-GITHUB-USER/YOUR-REPO-NAME
- پوشه پروژه کلون شده را با ویرایشگر مورد علاقه خود باز کنید. اگر Cloud Shell را انتخاب میکنید، میتوانید با کلیک روی « باز کردن ویرایشگر » همانطور که در زیر نشان داده شده است، این کار را انجام دهید.

همانطور که شکل زیر نشان میدهد، شما هر آنچه را که نیاز دارید با ویرایشگر پوسته ابری گوگل (Google Cloud Shell Editor) در اختیار دارید.

این کار را میتوان به صورت بصری با کلیک روی «باز کردن پوشه» و انتخاب پوشه، احتمالاً app-mod-workshop در پوشه خانه خود، انجام داد.
۳. ماژول ۱: ایجاد یک نمونه SQL
نمونه SQL گوگل کلود را ایجاد کنید
برنامه PHP ما به یک پایگاه داده MySQL متصل خواهد شد و بنابراین باید آن را برای انتقال بدون دردسر به Google Cloud کپی کنیم. Cloud SQL گزینه مناسبی است زیرا به شما امکان میدهد یک پایگاه داده MySQL کاملاً مدیریت شده را در Cloud اجرا کنید. مراحل زیر را دنبال کنید:
- به صفحه Cloud SQL بروید: https://console.cloud.google.com/sql/instances
- روی «ایجاد نمونه» کلیک کنید
- فعال کردن API (در صورت نیاز). این ممکن است چند ثانیه طول بکشد.
- MySQL را انتخاب کنید.
- (ما سعی میکنیم ارزانترین نسخه را برای شما تهیه کنیم تا دوام بیشتری داشته باشد):
- نسخه: سازمانی
- پیشتنظیم: توسعه (ما Sandbox را امتحان کردیم و برای ما کار نکرد)
- نسخه Mysql: 5.7 (وای، یه تجربه بینظیر از گذشته!)
- شناسه نمونه:
appmod-phpappرا انتخاب کنید (اگر این را تغییر دهید، به یاد داشته باشید که اسکریپتها و راهحلهای آینده را نیز بر این اساس تغییر دهید). - رمز عبور: هر چه که میخواهید، اما آن را به صورت CLOUDSQL_INSTANCE_PASSWORD یادداشت کنید.
- منطقه: همان منطقه ای را که برای بقیه برنامه انتخاب کرده اید، حفظ کنید (مثلاً میلان =
europe-west8) - قابلیت استفاده منطقهای: تک منطقهای (ما برای نسخه آزمایشی در حال صرفهجویی در هزینه هستیم)
برای استقرار پایگاه داده Cloud SQL روی دکمه Create Instance کلیک کنید؛ ⌛ تکمیل آن حدود 10 دقیقه طول میکشد⌛ . در این فاصله، به خواندن مستندات ادامه دهید؛ همچنین میتوانید حل ماژول بعدی ("Containerize your PHP App") را شروع کنید زیرا هیچ وابستگی به این ماژول در بخش اول ندارد (تا زمانی که اتصال DB را برطرف کنید).
توجه . این نمونه باید حدود ۷ دلار در روز برای شما هزینه داشته باشد. حتماً بعد از کارگاه آن را واگذار کنید.
ایجاد پایگاه داده image_catalog و کاربر در Cloud SQL
پروژه App با یک پوشه db/ همراه است که شامل دو فایل sql است:
- 01_schema.sql : شامل کد SQL برای ایجاد دو جدول حاوی دادههای کاربران و تصاویر است.
- 02_seed.sql : شامل کد SQL برای قرار دادن دادهها در جداول ایجاد شده قبلی است.
این فایلها بعداً و پس از ایجاد پایگاه داده image_catalog مورد استفاده قرار خواهند گرفت. میتوانید این کار را با انجام مراحل زیر انجام دهید:
- نمونه خود را باز کنید و روی برگه پایگاه داده کلیک کنید:
- روی «ایجاد پایگاه داده» کلیک کنید
- آن را
image_catalogبنامید (مانند پیکربندی برنامه PHP).

سپس کاربر پایگاه داده را ایجاد میکنیم. با این کار میتوانیم در پایگاه داده image_catalog احراز هویت کنیم.
- حالا روی تب کاربران کلیک کنید
- روی «افزودن حساب کاربری» کلیک کنید.
- کاربر: بیایید یکی بسازیم:
- نام کاربری:
appmod-phpapp-user - رمز عبور: چیزی را انتخاب کنید که بتوانید به خاطر بسپارید، یا روی «ایجاد» کلیک کنید
- « به هر میزبان (%) اجازه دهید » را نگه دارید.
- روی افزودن کلیک کنید.
پایگاه داده را روی IP های شناخته شده باز کنید.
توجه داشته باشید که همه پایگاههای داده در Cloud SQL به صورت «ایزوله» متولد میشوند. شما باید صریحاً یک شبکه راهاندازی کنید تا از طریق آن قابل دسترسی باشید.
- روی نمونه خود کلیک کنید
- منوی «اتصالات» را باز کنید
- روی برگه «شبکه» کلیک کنید.
- زیر «شبکههای مجاز» کلیک کنید. حالا یک شبکه (یعنی یک زیرشبکه) اضافه کنید.
- فعلاً، بیایید یک تنظیمات سریع اما ناامن (INSECURE) را انتخاب کنیم تا برنامه بتواند کار کند - شاید بعداً بخواهید آن را به IPهایی که به آنها اعتماد دارید محدود کنید:
- نام: «همه در جهان - ناامن».
- شبکه: "
0.0.0.0/0"(توجه: این بخش ناامن است!) - روی انجام شد کلیک کنید
- روی ذخیره کلیک کنید.
شما باید چیزی شبیه به این را ببینید:

توجه . این راهحل، مصالحهی خوبی برای اتمام کارگاه در O(ساعت) است. با این حال، برای ایمنسازی راهحل خود برای محیط عملیاتی، سند امنیت (SECURY) را بررسی کنید!
وقت آن است که اتصال پایگاه داده را آزمایش کنیم!
بیایید ببینیم که آیا کاربر image_catalog که قبلاً ایجاد کردهایم کار میکند یا خیر.
به "Cloud SQL Studio" در داخل نمونه دسترسی پیدا کنید و پایگاه داده، نام کاربری و رمز عبور را برای تأیید اعتبار وارد کنید، همانطور که در زیر نشان داده شده است:

حالا که وارد شدید، میتوانید ویرایشگر SQL را باز کنید و به بخش بعدی بروید.
وارد کردن پایگاه داده از کدبیس
از ویرایشگر SQL برای وارد کردن جداول image_catalog به همراه دادههایشان استفاده کنید. کد SQL را از فایلهای موجود در مخزن ( 01_schema.sql و سپس 02_seed.sql ) کپی کنید و آنها را یکی پس از دیگری به ترتیب اجرا کنید.
بعد از این، شما باید دو جدول در image_catalog داشته باشید که مربوط به کاربران و تصاویر هستند، همانطور که در زیر نشان داده شده است:

میتوانید با اجرای دستور زیر در ویرایشگر، آن را آزمایش کنید: select * from images;
همچنین مطمئن شوید که آدرس IP عمومی نمونه Cloud SQL را یادداشت کردهاید، بعداً به آن نیاز خواهید داشت. برای دریافت IP، به صفحه اصلی نمونه CLoud SQL در زیر صفحه Overview بروید. (Overview > Connect to this Instance > Public IP Address).
۴. ماژول ۲: برنامه PHP خود را کانتینرایز کنید

ما میخواهیم این برنامه را برای فضای ابری بسازیم.
این به معنی بستهبندی کد در نوعی فایل زیپ است که شامل تمام اطلاعات لازم برای اجرای آن در فضای ابری است.
چند روش برای بسته بندی آن وجود دارد:
- داکر . بسیار محبوب، اما راهاندازی صحیح آن بسیار پیچیده است.
- بستههای ساخت . کمتر محبوب هستند، اما معمولاً «به طور خودکار حدس میزنند» چه چیزی را بسازند و چه چیزی را اجرا کنند. اغلب اوقات این روش جواب میدهد!
در این کارگاه، فرض میکنیم که شما از داکر استفاده میکنید.
اگر تصمیم به استفاده از Cloud Shell گرفتهاید، الان زمان باز کردن مجدد آن است (روی قسمت بالا سمت راست کنسول ابری کلیک کنید).

این باید یک پوستهی مناسب در پایین صفحه باز کند، جایی که باید کد را در مرحلهی راهاندازی در آن قرار میدادید.

داکر
اگر میخواهید کنترل داشته باشید، این راه حل مناسب شماست. این زمانی منطقی است که نیاز به پیکربندی کتابخانههای خاص و تزریق رفتارهای نامشخص خاصی (مانند chmod در آپلودها، یک فایل اجرایی غیر استاندارد در برنامه شما و ...) داشته باشید.
از آنجایی که میخواهیم در نهایت برنامه کانتینر شده خود را روی 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چیزی است که شما انتخاب کردهاید. آن را داخل تک کوتیشن قرار دهید و در صورت نیاز از escape استفاده کنید.
همچنین، در صورت تمایل میتوانید چند قطعه ایتالیایی 🇮🇹 را با کمک جمینی به انگلیسی ترجمه کنید !
خب، حالا که Dockerfile را دارید و برنامه PHP خود را برای اتصال به پایگاه داده پیکربندی کردهاید، بیایید این را امتحان کنیم!
اگر هنوز داکر را نصب ندارید، آن را نصب کنید ( لینک ). اگر از Cloud Shell استفاده میکنید، به آن نیازی ندارید (چقدر جالب است؟).
حالا سعی کنید برنامه PHP کانتینر شده خود را با دستورات ساخت و اجرای مناسب docker بسازید و اجرا کنید.
# 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
اگر همه چیز درست کار میکند، باید بتوانید هنگام اتصال به میزبان محلی، صفحه وب زیر را ببینید! اکنون برنامه شما روی پورت ۸۰۸۰ اجرا میشود، روی نماد «پیشنمایش وب» (یک مرورگر با چشم) کلیک کنید و سپس پیشنمایش روی پورت ۸۰۸۰ (یا «تغییر پورت» برای هر پورت دیگری) را انتخاب کنید.

آزمایش نتیجه در مرورگر شما
حالا برنامه شما باید چیزی شبیه به این باشد:

و اگر با Admin/admin123 وارد سیستم شوید، باید چیزی شبیه به این را ببینید.

عالیه!!! جدا از متن ایتالیایی، داره کار میکنه! 🎉🎉🎉
اگر داکرسازی شما خوب باشد اما اعتبارنامههای پایگاه داده اشتباه باشند، ممکن است چیزی شبیه به این دریافت کنید:

دوباره امتحان کن، نزدیک شدی!
ذخیره در رجیستری مصنوعات [اختیاری]
تا اینجا، شما باید یک برنامه PHP کانتینر شده آماده برای استقرار در فضای ابری داشته باشید. در مرحله بعد، به مکانی در فضای ابری نیاز داریم تا تصویر Docker خود را ذخیره کنیم و آن را برای استقرار در سرویسهای Google Cloud مانند Cloud Run در دسترس قرار دهیم. این راهکار ذخیرهسازی، Artifact Registry نام دارد که یک سرویس Google Cloud کاملاً مدیریتشده است که برای ذخیره مصنوعات برنامه، از جمله تصاویر کانتینر Docker، بستههای Maven، ماژولهای npm و موارد دیگر طراحی شده است.
بیایید با استفاده از دکمهی مربوطه، یک مخزن در Google Cloud Artifact Registry ایجاد کنیم.

یک نام معتبر، قالب و منطقه مناسب برای ذخیره آثار باستانی انتخاب کنید.

به تگ محیط توسعه محلی خود برگردید و تصویر کانتینر App را به مخزن رجیستری Artifact که اخیراً ایجاد شده است، وارد کنید. برای انجام این کار، دستورات زیر را تکمیل کنید.
- تگ داکر SOURCE_IMAGE[:TAG] TARGET_IMAGE[:TAG]
- داکر، TARGET_IMAGE[:TAG] را فشار میدهد
نتیجه باید شبیه تصویر زیر باشد.

هورا 🎉🎉🎉 شما میتوانید به سطح بعدی بروید. قبل از آن، شاید ۲ دقیقه وقت بگذارید و آپلود/ورود/خروج را امتحان کنید و با نقاط پایانی برنامه آشنا شوید. بعداً به آنها نیاز خواهید داشت.
خطاهای احتمالی
اگر با خطاهای کانتینرسازی مواجه شدید، سعی کنید از Gemini برای توضیح و رفع خطا استفاده کنید، با ارائه موارد زیر:
- داکرفایل فعلی شما
- خطای دریافت شده
- [در صورت نیاز] کد PHP در حال اجرا.
مجوزهای آپلود . همچنین نقطه پایانی /upload.php را امتحان کنید و یک تصویر را آپلود کنید. ممکن است با خطای زیر مواجه شوید. در این صورت، باید در Dockerfile chmod/chown مشکل را برطرف کنید.
هشدار : move_uploaded_file(uploads/image (3).png): باز کردن جریان ناموفق بود: مجوز در /var/www/html/upload.php در خط 11 رد شد
خطای PDOException "نمیتوان درایور را پیدا کرد" (یا "خطای ارتباطی: درایور پیدا نشد") . مطمئن شوید که Dockerfile شما کتابخانههای PDO مناسب برای mysql ( pdo_mysql ) را برای اتصال به پایگاه داده دارد. از راهحلهای اینجا الهام بگیرید.
امکان ارسال درخواست شما به سرور پشتیبان وجود ندارد. نمیتوان به سروری روی پورت ۸۰۸۰ متصل شد. این بدان معناست که احتمالاً پورت اشتباهی را در معرض نمایش قرار میدهید. مطمئن شوید که پورتی را که آپاچی/انجینایکس واقعاً از آن سرویس میدهد، در معرض نمایش قرار میدهید. این موضوع سادهای نیست. در صورت امکان، سعی کنید آن پورت را ۸۰۸۰ کنید (با Cloud Run کار آسانتر میشود). اگر میخواهید پورت ۸۰ را نگه دارید (مثلاً چون آپاچی اینطور میخواهد)، از دستور دیگری برای اجرای آن استفاده کنید:
$ 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
۵. ماژول ۳: استقرار برنامه در 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 در مرورگرتان احراز هویت کند. مطمئن شوید که از طریق کروم و با همان کاربری (مثلاً vattelapesca@gmail.com) که با فعال بودن صورتحساب در Google Cloud وارد شده است، وارد سیستم میشوید.
با دستور زیر، Cloud Run API را فعال کنید:
-
$ gcloud services enable run.googleapis.com cloudbuild.googleapis.com
در این مرحله همه چیز برای استقرار در Cloud Run آماده است.
برنامه خود را از طریق gcloud روی Cloud Run مستقر کنید
دستوری که به شما امکان میدهد برنامه را روی Cloud Run مستقر کنید، gcloud run deploy است. برای رسیدن به هدفتان، گزینههای مختلفی برای تنظیم وجود دارد. حداقل مجموعه گزینهها (که میتوانید از طریق خط فرمان ارائه دهید، یا ابزار از شما با اعلان تعاملی سوال میکند) موارد زیر است:
- نام سرویس Cloud Run که میخواهید برای برنامه خود مستقر کنید. یک سرویس Cloud Run یک URL به شما برمیگرداند که یک نقطه پایانی برای برنامه شما فراهم میکند.
- منطقهی گوگل کلود که برنامهی شما در آن اجرا خواهد شد. (
--regionREGION) - تصویر کانتینر که برنامه شما را در بر میگیرد.
- متغیرهای محیطی که برنامه شما در طول اجرا باید از آنها استفاده کند.
- پرچم Allow-Unauthenticated که به همه اجازه میدهد بدون احراز هویت بیشتر به برنامه شما دسترسی داشته باشند.
برای مشاهده نحوه اعمال این گزینه در خط فرمان خود، به مستندات مراجعه کنید (یا برای یافتن راهحل احتمالی به پایین اسکرول کنید).
استقرار چند دقیقه طول خواهد کشید. اگر همه چیز درست باشد، باید چیزی شبیه به این را در کنسول Google Cloud مشاهده کنید.


روی URL ارائه شده توسط Cloud Run کلیک کنید و برنامه خود را آزمایش کنید. پس از تأیید اعتبار، باید چیزی شبیه به این را ببینید.

«gcloud run deploy» بدون هیچ آرگومانی
شاید متوجه شده باشید که gcloud run deploy از شما سوالات درست را میپرسد و جاهای خالی را که شما باقی گذاشتهاید پر میکند. این فوقالعاده است!
با این حال، در چند ماژول، ما قصد داریم این دستور را به یک تریگر Cloud Build اضافه کنیم، بنابراین نمیتوانیم از پس سوالات تعاملی برآییم. ما باید هر گزینه را در دستور پر کنیم. بنابراین شما میخواهید gcloud run deploy --option1 blah --foo bar --region your-fav-region . چگونه این کار را انجام میدهید؟
- مراحل ۲-۳-۴ را تا زمانی که gcloud دیگر سوال نپرسد، تکرار کنید:
- [LOOP]
gcloud run deployبا گزینههای یافت شده تاکنون - سیستمهای [LOOP] گزینه X را درخواست میکنند
- [LOOP] در مستندات عمومی جستجو کنید که چگونه X را از طریق رابط خط فرمان (CLI) تنظیم کنید و گزینه
--my-option [my-value]را اضافه کنید. - حالا به مرحله ۲ برگردید، مگر اینکه gcloud بدون سوال بیشتر تکمیل کند.
- این دستور gcloud اجرا میشود و BLAH BLAH BLAH عالی است! دستور را جایی ذخیره کنید، بعداً برای مرحله ساخت ابر به آن نیاز خواهید داشت!
یک راه حل ممکن اینجا است. اسناد اینجا هستند.
هورا 🎉🎉🎉 شما با موفقیت برنامه خود را در Google Cloud مستقر کردید و به اولین مرحله از نوسازی رسیدید.
۶. ماژول ۴: پاک کردن رمز عبور با Secret Manager

در مرحله قبل توانستیم برنامه خود را با موفقیت در Cloud Run مستقر و اجرا کنیم. با این حال، ما این کار را با یک رویه امنیتی نادرست انجام دادیم: ارائه برخی از اسرار به صورت cleartext .
اولین تکرار: فایل config.php خود را برای استفاده از ENV بهروزرسانی کنید
شاید متوجه شده باشید که ما رمز عبور پایگاه داده را مستقیماً در کد فایل config.php قرار دادهایم. این برای اهداف آزمایشی و بررسی عملکرد برنامه مناسب است. اما نمیتوانید کد را به این صورت در محیط عملیاتی کامیت/استفاده کنید. رمز عبور (و سایر پارامترهای اتصال پایگاه داده) باید به صورت پویا خوانده شوند و در زمان اجرا در اختیار برنامه قرار گیرند. فایل config.php را طوری تغییر دهید که پارامترهای پایگاه داده را از متغیرهای ENV بخواند. در صورت عدم موفقیت، باید تنظیم مقادیر پیشفرض را در نظر بگیرید. این در صورتی که نتوانید ENV را بارگذاری کنید، خوب است، بنابراین خروجی صفحه به شما میگوید که آیا از مقادیر پیشفرض استفاده میکند یا خیر. جاهای خالی را پر کنید و کد موجود در 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();
?>
از آنجایی که برنامه شما کانتینر شده است، باید راهی برای ارائه متغیرهای ENV به برنامه فراهم کنید. این کار را میتوان به چند روش انجام داد:
- در زمان ساخت ، روی Dockerfile. با استفاده از سینتکس ENV DB_VAR=ENV_VAR_VALUE، 4 پارامتر را به Dockerfile قبلی خود اضافه کنید. این کار مقادیر پیشفرضی را تنظیم میکند که میتوانند در زمان اجرا لغو شوند. برای مثال، 'DB_NAME' و 'DB_USER' میتوانند اینجا و نه هیچ جای دیگر تنظیم شوند.
- در زمان اجرا . میتوانید این متغیرها را برای Cloud Run، چه از طریق CLI و چه از طریق UI، تنظیم کنید . این مکان مناسبی برای قرار دادن هر 4 متغیر شماست (مگر اینکه بخواهید مقادیر پیشفرض را در Dockerfile نگه دارید).
در لوکال هاست، شاید بخواهید متغیرهای ENV خود را در یک فایل .env قرار دهید (پوشه solutions را بررسی کنید).
همچنین مطمئن شوید که .env به .gitignore شما اضافه شده است: شما نمیخواهید اسرار خود را به Github ارسال کنید!
echo .env >> .gitignore
پس از آن، میتوانید نمونه را به صورت محلی آزمایش کنید:
docker run -it -p 8080:8080 --env-file .env my-php-app-docker
حالا شما به موارد زیر دست یافتهاید:
- برنامه شما متغیر را به صورت پویا از ENV شما میخواند.
- شما با حذف رمز عبور پایگاه داده از کد خود، امنیت را بهبود بخشیدهاید.)
اکنون میتوانید یک نسخه جدید را در Cloud Run مستقر کنید. بیایید به رابط کاربری برویم و متغیرهای محیطی را به صورت دستی تنظیم کنیم:
- به آدرس https://console.cloud.google.com/run بروید
- روی برنامه خود کلیک کنید
- روی «ویرایش و استقرار یک نسخه جدید» کلیک کنید
- در تب اول "Container(s)" روی تب پایینی "Variables and secrets" کلیک کنید.
- روی «+ افزودن متغیر» کلیک کنید و تمام متغیرهای مورد نیاز را اضافه کنید. در نهایت باید چیزی شبیه به این داشته باشید:


آیا این بینقص است؟ خیر. رمز عبور شما هنوز برای اکثر اپراتورها قابل مشاهده است. این مشکل را میتوان با Google Cloud Secret Manager کاهش داد.
تکرار دوم: مدیر مخفی
رمزهای عبور شما از کد خودتان ناپدید شدهاند: پیروزی! اما صبر کنید - آیا ما هنوز در امان هستیم؟
رمزهای عبور شما همچنان برای هر کسی که به کنسول گوگل کلود دسترسی دارد قابل مشاهده است. در واقع، اگر به فایل استقرار YAML در Cloud Run دسترسی پیدا کنید، میتوانید آن را بازیابی کنید. یا اگر سعی کنید یک نسخه جدید از Cloud Run را ویرایش یا مستقر کنید، رمز عبور در بخش متغیرها و اسرار، همانطور که در تصاویر زیر نشان داده شده است، قابل مشاهده است.
Google Cloud Secret Manager یک سرویس امن و متمرکز برای مدیریت اطلاعات حساس مانند کلیدهای API، رمزهای عبور، گواهینامهها و سایر اطلاعات محرمانه است.
این ابزار به شما امکان میدهد تا اسرار را با مجوزهای دقیق و رمزگذاری قوی ذخیره، مدیریت و به آنها دسترسی داشته باشید. Secret Manager که با مدیریت هویت و دسترسی (IAM) گوگل کلود یکپارچه شده است، به شما امکان میدهد کنترل کنید چه کسی میتواند به اسرار خاص دسترسی داشته باشد و امنیت دادهها و انطباق با مقررات را تضمین میکند.
همچنین از چرخش و نسخهبندی خودکار اطلاعات محرمانه پشتیبانی میکند، مدیریت چرخه حیات اطلاعات محرمانه را ساده کرده و امنیت برنامههای کاربردی در سرویسهای ابری گوگل را افزایش میدهد.
برای دسترسی به Secret Manager، از منوی همبرگری به Security services بروید و آن را در بخش Data Protection همانطور که در تصویر زیر نشان داده شده است، پیدا کنید.

وقتی مطابق تصویر زیر به آنجا رسیدید، Secret Manager API را فعال کنید.

- حالا روی « ایجاد یک راز » کلیک کنید: بیایید منطقی بگوییم:
- نام:
php-amarcord-db-pass - مقدار مخفی: « رمز عبور پایگاه داده شما » (بخش «بارگذاری فایل» را نادیده بگیرید).
- این لینک مخفی را حاشیهنویسی کنید، باید شبیه
projects/123456789012/secrets/php-amarcord-db-passباشد. این اشارهگر منحصر به فرد به رمز شماست (برای Terraform، Cloud Run و موارد دیگر). عدد، شماره منحصر به فرد پروژه شماست.
نکته : سعی کنید از یک قرارداد نامگذاری ثابت برای رمزهای خود استفاده کنید، و از چپ به راست نامگذاری کنید، برای مثال: cloud-devrel-phpamarcord-dbpass
- سازمان (با شرکت)
- تیم (درون سازمان)
- درخواست (درون تیمی)
- نام متغیر (درون برنامه)
این به شما امکان میدهد تا به راحتی بتوانید با استفاده از regexes تمام اسرار یک برنامه را پیدا کنید.
یک نسخه جدید از Cloud Run ایجاد کنید
حالا که یک راز جدید داریم، باید از شر متغیر DB_PASS ENV خلاص شویم و آن را با راز جدید جایگزین کنیم. بنابراین:
- دسترسی به Cloud Run با استفاده از کنسول Google Cloud
- برنامه را انتخاب کنید.
- روی «ویرایش و استقرار یک نسخه جدید» کلیک کنید
- برگه «متغیرها و اسرار» را پیدا کنید.
- برای تنظیم مجدد متغیر DB_PASS ENV از دکمهی «+ ارجاع به یک راز» استفاده کنید.
- از همان "DB_PASS" برای اسرار ارجاع شده استفاده کنید و از آخرین نسخه استفاده کنید.

پس از انجام این کار، باید خطای زیر را دریافت کنید

سعی کنید بفهمید چگونه آن را برطرف کنید. برای حل این مشکل، باید به بخش IAM & Admin دسترسی پیدا کنید و مجوزهای اعطایی را تغییر دهید. اشکالزدایی خوبی داشته باشید!
وقتی متوجه شدید، به Cloud Run برگردید و یک نسخه جدید را دوباره مستقر کنید. نتیجه باید مانند شکل زیر باشد:

نکته : کنسول توسعهدهنده (UI) در نشان دادن مشکلات مجوز عالی است. برای پیمایش تمام لینکهای مربوط به موجودیتهای ابری خود وقت بگذارید!
۷. ماژول ۵: راهاندازی CI/CD با Cloud Build

چرا یک خط لوله CI/CD؟
تا الان، شما باید چند بار gcloud run deploy تایپ کرده باشید، شاید بارها و بارها به یک سوال پاسخ داده باشید.
از اینکه برنامهتان را با gcloud run deploy به صورت دستی مستقر کنید خسته شدهاید؟ عالی نمیشد اگر برنامهتان میتوانست هر بار که تغییر جدیدی را به مخزن گیت خود اضافه میکنید، به طور خودکار مستقر شود؟
برای استفاده از CI/CD pipeline، به دو چیز نیاز دارید:
- یک مخزن گیت شخصی : خوشبختانه، شما باید قبلاً مخزن کارگاه را در مرحله ۲ به حساب گیتهاب خود فورک کرده باشید. اگر اینطور نیست، به عقب برگردید و آن مرحله را تکمیل کنید. مخزن فورک شده شما باید چیزی شبیه به این باشد:
https://github.com/<YOUR_GITHUB_USER>/app-mod-workshop - ساخت ابری . این سرویس شگفتانگیز و ارزان به شما امکان میدهد اتوماسیون ساخت را برای تقریباً همه چیز پیکربندی کنید: Terraform، برنامههای dockerized، ..
این بخش بر روی راهاندازی Cloud Build تمرکز خواهد کرد.
وارد فضای ابری شوید!
ما برای انجام این کار از Cloud Build استفاده خواهیم کرد:
- سورس خود را بسازید (با Dockerfile). این را به عنوان یک "فایل زیپ بزرگ" در نظر بگیرید که شامل تمام چیزهایی است که برای ساخت و اجرای آن نیاز دارید ("مصنوع ساخت" شما).
- این مصنوع را به فهرست مصنوعات (AR) منتقل کنید.
- سپس یک استقرار از AR به Cloud Run برای برنامه "php-amarcord" صادر کنید
- این یک نسخه جدید ("نسخه") از برنامه موجود ایجاد میکند (به یک لایه با کد جدید فکر کنید) و ما آن را پیکربندی میکنیم تا در صورت موفقیتآمیز بودن ارسال، ترافیک را به نسخه جدید هدایت کند.
این نمونهای از برخی از نسخههای ساخته شده برای برنامه php-amarcord من است:

چطور همه این کارها را انجام دهیم؟
- با ساخت یک فایل YAML بینقص:
cloudbuild.yaml - با ایجاد یک تریگر Cloud Build.
- با اتصال به مخزن گیتهاب ما از طریق رابط کاربری Cloud Build .
ایجاد تریگر (و اتصال به مخزن)
- به آدرس https://console.cloud.google.com/cloud-build/triggers بروید
- روی «ایجاد تریگر» کلیک کنید.
- کامپایل:
- نام: چیزی معنادار مانند
on-git-commit-build-php-app - رویداد: ارسال به شاخه
- منبع: "اتصال مخزن جدید"

- این یک پنجره در سمت راست باز میکند: "اتصال مخزن"
- ارائه دهنده منبع: "Github" (اول)
- «ادامه»
- Authenticate پنجرهای در گیتهاب برای احراز هویت متقابل باز میکند. روند کار را دنبال کنید و صبور باشید. اگر مخازن زیادی دارید، ممکن است کمی طول بکشد.
- «انتخاب مخزن» حساب/مخزن خود را انتخاب کنید و قسمت «متوجه هستم...» را علامت بزنید.
- اگر با خطای «برنامه گیتهاب روی هیچ یک از مخازن شما نصب نشده است» مواجه شدید، روی «نصب Google Cloud Build» کلیک کنید و دستورالعملها را دنبال کنید.
روی اتصال کلیک کنید 
- مخزن شما اکنون متصل شده است.
- برگردیم به بخش تریگر....
- پیکربندی: شناسایی خودکار (*)
- پیشرفته: حساب سرویس "[PROJECT_NUMBER]- compute@developer.gserviceaccount.com " را انتخاب کنید.
- xxxxx شناسه پروژه شماست
- حساب کاربری پیشفرض سرویس محاسباتی برای رویکرد آزمایشگاهی مناسب است - از آن در محیط عملیاتی استفاده نکنید! ( اطلاعات بیشتر ).
- هر چیز دیگری را همانطور که هست بگذارید.
- روی دکمه «ایجاد» کلیک کنید.
(*) این سادهترین راه است زیرا Dockerfile یا cloudbuild.yaml را بررسی میکند. با این حال، cloudbuild.yaml به شما قدرت واقعی میدهد تا تصمیم بگیرید که در هر مرحله چه کاری انجام دهید.
من قدرتش رو دارم!
حالا، تریگر کار نمیکند مگر اینکه حساب کاربری سرویس Cloud Build (حساب کاربری سرویس چیست؟ ایمیل یک "ربات" که از طرف شما برای انجام یک کار - در این مورد ساخت چیزها در Cloud - عمل میکند!) را به آن بدهید.
اگر به SA اجازه ندهید که این کار را انجام دهد، او در ساخت و استقرار آن شکست خواهد خورد. خوشبختانه این کار آسان است!
- به «ساخت ابری» > « تنظیمات » بروید.
- حساب سرویس "[PROJECT_NUMBER]- compute@developer.gserviceaccount.com "
- این کادرها را علامت بزنید:
- اجرای ابری
- مدیر مخفی
- حسابهای خدماتی
- ساخت ابری
- همچنین تیک گزینه «تنظیم به عنوان حساب سرویس ترجیحی» را بزنید.

نسخه ابری YAML کجاست؟
ما اکیداً شما را تشویق میکنیم که زمانی را صرف ایجاد فضای ابری خودتان (Build YAML) کنید.
با این حال، اگر وقت ندارید، یا نمیخواهید وقت بگذارید، میتوانید در این پوشه راهحلها الهام بگیرید: .solutions
حالا میتوانید یک تغییر را به گیتهاب ارسال کنید و Cloud Build را در آن قسمت مشاهده کنید.
راهاندازی Cloud Build میتواند کمی پیچیده باشد. انتظار کمی اختلاف نظر را داشته باشید:
- بررسی گزارشها در https://console.cloud.google.com/cloud-build/builds;region=global
- Finding your error.
- Fixing in the code, and re-issuing 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:

You can do this in two ways:
- 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

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:

What just happened?
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?
Prerequisites
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.
- Enable the APIs
- 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:

Conclusions
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.
Bingo!
Final result
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.
Security
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!
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.
