کد روی لپتاپ شما درست کار میکند، تستها سبزند و حالا باید آن را وارد production کنید؛ همان نقطهای که سؤالها شروع میشوند: APP_KEY را کجا بگذاریم؟ Migration چه زمانی اجرا شود؟ فایلهای آپلودی با حذف پاد چه میشوند؟
دیپلوی لاراول روی Kubernetes فقط ساختن کانتینر و اجرای kubectl apply نیست. باید وباپلیکیشن، Queue Worker، تنظیمات محیط، Secretها، Probeها و فضای ذخیرهسازی را جداگانه ببینید. مسیر این راهنما از Dockerfile شروع میشود و به Deployment، Service، Ingress و عیبیابی production میرسد.
پاسخ سریع: برای اجرای Laravel روی Kubernetes به چه چیزهایی نیاز دارید؟
مسیر استقرار یک اپلیکیشن معمولی این است:
-
پروژه Laravel را داخل یک Image قرار دهید.
-
Image را در Registry در دسترس کلاستر بگذارید.
-
مقادیر غیرحساس را با ConfigMap یا متغیرهای محیطی تعریف کنید.
-
APP_KEY و اطلاعات اتصال دیتابیس را در Secret نگه دارید.
-
وباپلیکیشن را با Deployment اجرا کنید.
-
با Service یک آدرس ثابت داخلی بسازید.
-
ترافیک بیرونی را از Ingress به Service برسانید.
-
Migration را در یک Job جدا اجرا کنید.
-
Queue Worker و Horizon را از وباپلیکیشن جدا نگه دارید.
-
برای مسیر سلامت برنامه، readinessProbe و livenessProbe تعریف کنید.
این معماری از اجرای همه اجزا داخل یک پاد طولانیتر است، ولی عیبیابی را ساده میکند. اگر Worker از کار افتاد، وباپلیکیشن نباید همراهش Restart شود.
همین پروژه لاراول را بدون نوشتن YAML روی پاستا بالا بیاورید
قبل از دیپلوی لاراول چه چیزهایی را آماده کنیم؟
نمونههای این راهنما بر مبنای PHP 8.3 و نسخهای از Laravel نوشته شدهاند که Health Check آن در مسیر /up فعال است. Laravel جدید این مسیر را بهصورت پیشفرض دارد؛ در پروژههای قدیمیتر باید خودتان یک Health Route بسازید.
APP_ENV=production
APP_DEBUG=false
APP_URL=https://app.example.com
LOG_CHANNEL=stderr
LOG_LEVEL=warning
DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=app
ابتدا تنظیمات production را مرور کنید: فایل .env واقعی را داخل Image کپی نکنید و در Git نگذارید. Kubernetes متغیرها را هنگام ساخت پاد وارد کانتینر میکند؛ در نتیجه میتوانید تنظیمات staging و production را بدون قراردادن رمزها در لایههای Image تغییر دهید.
مقدار APP_DEBUG در production باید false باشد. روشنماندن آن ممکن است Stack Trace و بخشی از تنظیمات حساس برنامه را به کاربر نشان دهد.
پیش از ادامه، این دستورها را در CI یا محیط Build اجرا کنید:
composer install \
--no-dev \
--prefer-dist \
--no-interaction \
--no-progress \
--optimize-autoloader
php artisan optimize
دستور optimize تنظیمات، Routeها، Eventها و Viewها را Cache میکند. پس از اجرای config:cache، فراخوانی مستقیم env() بیرون از فایلهای پوشه config قابلاعتماد نیست. در کد برنامه از config() استفاده کنید.
چگونه برای Laravel یک Image قابلاستفاده بسازیم؟
این Dockerfile یک نمونه پایه است. اگر اپلیکیشن شما Extension دیگری مثل gd، intl یا zip میخواهد، آن را به مرحله Build اضافه کنید.
FROM composer:2 AS vendor
WORKDIR /app
COPY composer.json composer.lock ./
RUN composer install \
--no-dev \
--prefer-dist \
--no-interaction \
--no-progress \
--optimize-autoloader \
--no-scripts
COPY . .
RUN composer dump-autoload --optimize \
&& php artisan package:discover --ansi
FROM php:8.3-cli
WORKDIR /var/www/html
RUN docker-php-ext-install pdo_mysql
COPY --from=vendor /app /var/www/html
RUN chown -R www-data:www-data storage bootstrap/cache
USER www-data
EXPOSE 8000
CMD ["php", "artisan", "serve", "--host=0.0.0.0", "--port=8000"]
برای تست محلی:
docker build -t registry.example.com/team/laravel-app:1.0.0 .
docker run --rm -p 8000:8000 \
-e APP_ENV=production \
-e APP_DEBUG=false \
-e APP_KEY='base64:REPLACE_WITH_REAL_KEY' \
registry.example.com/team/laravel-app:1.0.0
عبارت REPLACE_WITH_REAL_KEY را باید با کلید واقعی پروژه جایگزین کنید. سپس مسیر Health Check را بررسی کنید:
curl -i http://127.0.0.1:8000/up
خروجی موفق باید Status Code برابر 200 داشته باشد. محتوای Response به نسخه Laravel و تنظیمات پروژه وابسته است.
اجرای php artisan serve برای سادهماندن نمونه انتخاب شده است. Runtime و وبسرور production را براساس تست فشار، Extensionهای پروژه و الگوی ترافیک انتخاب کنید. در محیط Production، پاستا پیشنهاد میکند اجزای مختلف Laravel را بر اساس مسئولیتشان از هم جدا کنید:
-
Web: اجرای Laravel با PHP production و وبسرور مناسب، با Document Root روی public
-
Database: استفاده از MySQL یا PostgreSQL مدیریتشده و تنظیم اتصال از طریق DATABASE_URL
-
Migration: اجرای php artisan migrate --force بهعنوان Release Command، پیش از انتقال ترافیک
-
Queue: اجرای php artisan queue:work در یک Worker مستقل
-
Scheduler: اجرای php artisan schedule:work یا Cron در یک Process جدا
-
Secrets: نگهداری APP_KEY و سایر اطلاعات حساس در متغیرهای امن پروژه، نه داخل Git
-
Storage: اتصال storage/app/public به Volume پایدار یا استفاده از S3 برای فایلهای آپلودی
-
Build: اجرای composer install --no-dev --optimize-autoloader و، در صورت استفاده از Vite، ساخت assetها با npm run build
Queue و Scheduler نباید داخل کانتینر Web اجرا شوند؛ جداسازی آنها مقیاسدهی، تخصیص منابع و Restart هر فرایند را مستقل و قابلاعتماد نگه میدارد. همچنین APP_KEY باید فقط یکبار تولید شود، زیرا تغییر آن دادههای رمزشده و نشستهای قبلی را نامعتبر میکند.
پاستا یک پلتفرم ابری استقرار و توزیع نرمافزار مبتنی بر Kubernetes و OpenShift است. در PaaSta Cooker میتوانید Build و استقرار را با تعریف گرافیکی یا مانیفست YAML انجام دهید. Paastafile نیز امکان استقرار بدون نوشتن Dockerfile را فراهم میکند. Namespace، Persistent Volume، Ingress، متغیر محیطی و Secret در اختیار شماست.
پاستا multi-cloud است و روی ارائهدهندههای مختلف اجرا میشود. اگر تیم شما میخواهد تمام اجزای کلاستر، Controllerها و سیاستهای سطح پایین Kubernetes را مستقیماً مدیریت کند، PaaS لزوماً انتخاب اول نیست. مقایسه هزینه عملیاتی دو مسیر را در مطلب «PaaS یا سرور مجازی؟ کدام برای تیم شما مقرونبهصرفهتر است» بخوانید.
و تنظیمات Laravel را چطور وارد کلاستر کنیم؟
ابتدا Namespace بسازید:
apiVersion: v1
kind: Namespace
metadata:
name: laravel-production
مقادیر حساس را در Secret قرار دهید:
apiVersion: v1
kind: Secret
metadata:
name: laravel-secrets
namespace: laravel-production
type: Opaque
stringData:
APP_KEY: "base64:REPLACE_WITH_REAL_APP_KEY"
DB_USERNAME: "REPLACE_WITH_REAL_USERNAME"
DB_PASSWORD: "REPLACE_WITH_REAL_PASSWORD"
مقادیر REPLACE_WITH_REAL_* را پیش از اجرا جایگزین کنید. این فایل نمونه را با رمز واقعی داخل Repository نگذارید.
Kubernetes Secret بهتنهایی معادل یک Secret Manager کامل نیست؛ رمزنگاری دادهها در حالت سکون و سطح دسترسی کاربران کلاستر را نیز بررسی کنید.
تنظیمات غیرحساس را جدا نگه دارید:
apiVersion: v1
kind: ConfigMap
metadata:
name: laravel-config
namespace: laravel-production
data:
APP_NAME: "Laravel"
APP_ENV: "production"
APP_DEBUG: "false"
APP_URL: "https://app.example.com"
LOG_CHANNEL: "stderr"
LOG_LEVEL: "warning"
DB_CONNECTION: "mysql"
DB_HOST: "mysql"
DB_PORT: "3306"
DB_DATABASE: "app"
با این تفکیک، تغییر LOG_LEVEL به ساخت Image جدید نیاز ندارد. تغییر ConfigMap یا Secret الزاماً متغیرهای پادهای فعلی را عوض نمیکند؛ پس بعد از تغییر، Rollout جدید اجرا کنید.
Deployment، Service و Probeها را چطور تعریف کنیم؟
Deployment زیر دو Replica میسازد. درخواستها فقط زمانی به پاد میرسند که مسیر /up پاسخ موفق بدهد:
apiVersion: apps/v1
kind: Deployment
metadata:
name: laravel-web
namespace: laravel-production
spec:
replicas: 2
selector:
matchLabels:
app: laravel-web
template:
metadata:
labels:
app: laravel-web
spec:
containers:
- name: web
image: registry.example.com/team/laravel-app:1.0.0
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 8000
envFrom:
- configMapRef:
name: laravel-config
- secretRef:
name: laravel-secrets
readinessProbe:
httpGet:
path: /up
port: http
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 2
failureThreshold: 3
livenessProbe:
httpGet:
path: /up
port: http
initialDelaySeconds: 20
periodSeconds: 20
timeoutSeconds: 2
failureThreshold: 3
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "512Mi"
این اعداد نقطه شروعاند. مقدار مناسب را از مصرف CPU و Memory زیر بار به دست بیاورید. Limit حافظه پایین ممکن است پاد را با OOMKilled متوقف کند؛ مقدار بالا هم ظرفیت کلاستر را اشغال میکند.
Service تغییر IP پادها را از مصرفکننده پنهان میکند:
apiVersion: v1
kind: Service
metadata:
name: laravel-web
namespace: laravel-production
spec:
selector:
app: laravel-web
ports:
- name: http
port: 80
targetPort: http
type: ClusterIP
برای دسترسی بیرونی، یک Ingress بسازید:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: laravel-web
namespace: laravel-production
spec:
ingressClassName: nginx
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: laravel-web
port:
number: 80
ام ingressClassName به Ingress Controller کلاستر شما بستگی دارد. وجود Resource از نوع Ingress کافی نیست؛ کلاستر باید Controller سازگار داشته باشد.
مانیفستها را اعمال کنید:
kubectl apply -f namespace.yaml
kubectl apply -f secret.yaml
kubectl apply -f configmap.yaml
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
kubectl apply -f ingress.yaml
kubectl -n laravel-production rollout status deployment/laravel-web
kubectl -n laravel-production get pods,service,ingress
Migration را بدون خرابکردن Rollout چگونه اجرا کنیم؟
Migration را داخل Startup Command همه Replicaها قرار ندهید. با بالا آمدن سه پاد، دستور سه بار همزمان اجرا میشود؛ بعضی Migrationها تحمل این وضعیت را ندارند.
یک Job جدا بسازید:
apiVersion: batch/v1
kind: Job
metadata:
name: laravel-migrate-100
namespace: laravel-production
spec:
backoffLimit: 2
template:
metadata:
labels:
app: laravel-migrate
spec:
restartPolicy: Never
containers:
- name: migrate
image: registry.example.com/team/laravel-app:1.0.0
command: ["php", "artisan", "migrate", "--force"]
envFrom:
- configMapRef:
name: laravel-config
- secretRef:
name: laravel-secrets
سپس وضعیت و Log را ببینید:
kubectl apply -f migration-job.yaml
kubectl -n laravel-production wait \
--for=condition=complete \
job/laravel-migrate-100 \
--timeout=180s
kubectl -n laravel-production logs job/laravel-migrate-100
نام Job را در هر Release عوض کنید یا Job قبلی را طبق فرآیند Release خود مدیریت کنید. ترتیب Migration و Rollout باید با سازگاری Schema هماهنگ باشد. حذف یک ستون پیش از انتشار کدی که هنوز آن را میخواند، حتی با Kubernetes درست نمیشود.
فایلهای آپلودی و Queue Worker کجا اجرا شوند؟
Filesystem کانتینر موقتی است و با جایگزینی پاد ممکن است فایلها از بین بروند. اگر پروژه شما Upload محلی دارد، Filesystem را بازطراحی کنید یا برای مسیر موردنیاز Persistent Volume در نظر بگیرید.
نمونه Mount:
volumeMounts:
- name: uploads
mountPath: /var/www/html/storage/app/public
volumes:
- name: uploads
persistentVolumeClaim:
claimName: laravel-uploads
پیش از استفاده، Access Mode و امکان Mount همزمان Volume روی چند پاد را بررسی کنید. هر Volume برای چند Replica مناسب نیست.
Queue Worker باید Deployment مستقل داشته باشد و Command خودش را اجرا کند:
command:
- php
- artisan
- queue:work
- redis
- --sleep=3
- --tries=3
- --timeout=90
یک Process طولانیمدت است. پس از انتشار کد جدید، آن را Restart کنید تا نسخه تازه را بارگذاری کند. جزئیات Queue و Horizon را در «Queue و Horizon در لاراول — اجرای درست در production» ببینید.
اگر تیم شما سرویسهای Python هم دارد، الگوی تفکیک Web و Worker در «دیپلوی FastAPI و Flask — از لوکال تا production» توضیح داده شده است.
خطاهای رایج هنگام اجرای Laravel روی Kubernetes چیست؟
خطای No application encryption key has been specified.
APP_KEY وجود ندارد، خالی است یا به پاد نرسیده. ابتدا مقدار Secret و اتصال آن به Deployment را بررسی کنید:
kubectl -n laravel-production describe pod POD_NAME
kubectl -n laravel-production exec POD_NAME -- printenv APP_KEY
مقدار Secret را در Log یا تیکت عمومی کپی نکنید. پس از اصلاح آن، Rollout را Restart کنید:
kubectl -n laravel-production rollout restart deployment/laravel-web
خطای SQLSTATE[HY000] [2002] Connection refused
برنامه به آدرس و Port دیتابیس دسترسی ندارد. DB_HOST اشتباه، آمادهنبودن دیتابیس یا بستهبودن مسیر شبکه میتواند این خطا را ایجاد کند.
از داخل همان پاد بررسی کنید که نام میزبان Resolve میشود:
kubectl -n laravel-production exec POD_NAME -- getent hosts mysql
اگر دیتابیس داخل Kubernetes است، نام Service را با DB_HOST تطبیق دهید. برای دیتابیس بیرون کلاستر، Route شبکه و Policyهای دسترسی را بررسی کنید.
خطای file_put_contents(...): Failed to open stream: Permission denied
Laravel نمیتواند در storage یا bootstrap/cache بنویسد. User داخل کانتینر، مالکیت فایلها و Permission مسیر Mountشده را بررسی کنید:
kubectl -n laravel-production exec POD_NAME -- \
+ sh -c 'id && ls -ld storage bootstrap/cache'
Permission برابر 777 راهحل مناسبی نیست. مالکیت مسیر را با User واقعی Process هماهنگ کنید. اگر Volume وصل شده است، ممکن است مالکیت آن با تنظیم مرحله Build فرق داشته باشد.
وضعیت CrashLoopBackOff
این پیام خطای Laravel نیست. کانتینر چند بار اجرا شده و بلافاصله متوقف شده است. دلیل را از Log اجرای قبلی بگیرید:
kubectl -n laravel-production logs POD_NAME --previous
kubectl -n laravel-production describe pod POD_NAME
Command اشتباه، Extension نصبنشده PHP، متغیر محیطی ناقص یا خطای اتصال میتواند این وضعیت را ایجاد کند. اول Log را بخوانید. افزایش initialDelaySeconds فقط وقتی مفید است که Probe زود اجرا شده باشد.
چکلیست عملی قبل از انتشار چیست؟
-
Image با Tag ثابت ساخته و در محیطی جدا تست شده است.
-
.env و رمزها داخل Image یا Repository نیستند.
-
APP_ENV=production و APP_DEBUG=false تنظیم شدهاند.
-
APP_KEY از Secret وارد پاد میشود.
-
مسیر /up با Status Code برابر 200 پاسخ میدهد.
-
Readiness و Liveness رفتار متفاوت و قابلاندازهگیری دارند.
-
Migration در Job مستقل اجرا میشود.
-
تغییر Schema با نسخه قبلی اپلیکیشن سازگار است.
-
Queue Worker از Deployment وب جداست.
-
Workerها پس از Release جدید Restart میشوند.
-
مسیرهای نوشتنی Permission درست دارند.
-
Uploadهای ماندگار روی Filesystem موقت کانتینر نیستند.
-
Request و Limit منابع با تست واقعی تعیین شدهاند.
-
kubectl rollout status و Logها در فرآیند Release بررسی میشوند.
-
Rollback نسخه قبلی پیش از Release تمرین شده است.
جمعبندی: مسیر کمریسکتر برای ورود Laravel به production چیست؟
یک Release قابلاعتماد مسئولیتها را جدا میکند: وباپلیکیشن در Deployment، Migration در Job و Queue Worker در Deployment خودش. تنظیمات محیط را از Image بیرون بکشید، Secretها را جدا نگه دارید و پیش از هدایت ترافیک، سلامت برنامه را با Probe بسنجید.
Kubernetes خرابی کد، Migration ناسازگار یا Permission اشتباه را حل نمیکند؛ فقط آنها را در مقیاس بزرگتر تکرار میکند. ابتدا یک Image سالم و قابلتکرار بسازید، بعد سراغ Replica و Rollout بروید.
اگر نمیخواهید مدیریت YAML و جزئیات مستقیم Kubernetes بخشی از کار روزانه تیم باشد، پروژه را با PaaSta Cooker یا Paastafile روی پاستا تعریف کنید و استقرار را از همانجا شروع کنید.
پاسخ کوتاه به پرسشهای این مطلب
آیا Kubernetes برای هر پروژه Laravel مناسب است؟
خیر. برای اپلیکیشنی کوچک با یک سرور و ترافیک قابلپیشبینی، هزینه نگهداری Kubernetes ممکن است بیشتر از منفعتش باشد. وقتی چند سرویس، چند Replica یا Release پرتکرار دارید، ارزش آن بیشتر میشود.
آیا میتوان Migration را در Dockerfile اجرا کرد؟
خیر. مرحله Build نباید به دیتابیس production وابسته باشد. Migration را هنگام Release و در Job جدا اجرا کنید.
چرا APP_KEY را داخل ConfigMap نگذاریم؟
چون APP_KEY حساس است. آن را داخل Secret نگه دارید و دسترسی به Secret را محدود کنید.
آیا برای Laravel حتماً Persistent Volume لازم است؟
نه. اگر برنامه فایل ماندگاری روی دیسک محلی نمینویسد، به آن نیاز ندارید. برای Upload محلی یا دادهای که باید بعد از حذف پاد باقی بماند، فضای ذخیرهسازی ماندگار لازم است.
فرق اجرای لاراول روی سرور با Kubernetes چیست؟
روی یک سرور، معمولاً خودتان Processها، وبسرور، Restart، شبکه و Release را مدیریت میکنید. در Kubernetes وضعیت مطلوب را تعریف میکنید و Controllerها پادها را با آن هماهنگ نگه میدارند. در عوض باید اجزای بیشتری را بشناسید.
آیا در پاستا باید Dockerfile و YAML بنویسیم؟
الزامی نیست. Paastafile استقرار بدون Dockerfile را ممکن میکند و PaaSta Cooker تعریف گرافیکی یا مانیفست YAML را میپذیرد.
پروژهتان را به وب بیاورید.
کد را بدهید؛ پاستا ساخت، دامنه، HTTPS و مسیر اجرا را آماده میکند.
نظرها
هنوز نظری ثبت نشده است. اولین نفر باشید.