إعداد مسار CI/CD الذي يعمل بالفعل في الإنتاج
أنماط CI/CD مجربة في المعارك للمشاريع الحقيقية — من سير عمل GitHub Actions إلى استراتيجيات النشر التي لا تعطل الإنتاج يوم الجمعة.

كان أول مسار CI/CD قمت بإعداده لمشروع إنتاجي عبارة عن سير عمل واحد من GitHub Actions يقوم بتشغيل npm test ثم ينشر إلى خادم افتراضي خاص (VPS) عبر SSH. لقد عمل حتى توقف عن العمل — ترك نشر فاشل الخادم في حالة تحديث جزئي مساء يوم الجمعة، وقضيت عطلة نهاية الأسبوع في استعادة الملفات يدويًا. علمتني تلك التجربة أن مسار النشر ليس مجرد "تشغيل الاختبارات ثم النشر". إنه النظام الكامل من الفحوصات والبوابات وآليات التراجع التي تقف بين git push ورؤية المستخدمين للتعليمات البرمجية الجديدة.
تغطي هذه المقالة بنية المسار التي قمت بتحسينها عبر مشاريع إنتاج متعددة، مع أمثلة عملية لـ GitHub Actions يمكنك تكييفها.
هندسة المسار
يمتلك مسار الإنتاج مراحل مميزة، ولكل مرحلة غرض محدد. تخطي المراحل يوفر دقائق الآن ويكلف ساعات لاحقًا.
المراحل
Code Push
│
├─→ Stage 1: Validation (lint, format, type-check)
│
├─→ Stage 2: Testing (unit, integration)
│
├─→ Stage 3: Build (compile, bundle, containerize)
│
├─→ Stage 4: Deploy to Staging
│
├─→ Stage 5: Smoke Tests / E2E on Staging
│
└─→ Stage 6: Deploy to Production
تعمل كل مرحلة كبوابة. إذا فشل التحقق، فلن يتم تشغيل الاختبارات أبدًا. إذا فشلت الاختبارات، فلن يبدأ البناء أبدًا. هذا يوفر وقت الحوسبة ويوفر ملاحظات سريعة — يعرف المطورون في غضون 30 ثانية ما إذا كانوا قد نسوا تشغيل المدقق، بدلاً من الانتظار 8 دقائق حتى يفشل مجموعة الاختبارات.
استراتيجية التشغيل
ليس كل دفع يتطلب المسار الكامل. إليك تكوين المشغل الذي أستخدمه:
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
types: [opened, synchronize, reopened]
طلبات السحب (Pull requests) تشغل المراحل 1-3 (التحقق، الاختبار، البناء). عمليات الدمج إلى develop تنشر إلى بيئة الاختبار (staging). عمليات الدمج إلى main تنشر إلى بيئة الإنتاج. هذا يحافظ على سرعة ملاحظات طلبات السحب مع ضمان أن عمليات النشر تحدث فقط من الفروع المحمية.
المرحلة 1: التحقق
يكتشف التحقق عدم اتساق التنسيق وأخطاء النوع قبل تشغيل الاختبارات. هذه الفحوصات سريعة (أقل من 30 ثانية) وتلتقط المشكلات الأكثر شيوعًا.
jobs:
validate:
name: Validate
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Check formatting
run: npx prettier --check "src/**/*.{ts,tsx,js,jsx,json,css}"
- name: Lint
run: npx eslint src/ --max-warnings 0
- name: Type check
run: npx tsc --noEmit
قاعدة --max-warnings 0
يميز ESLint بين الأخطاء (التي تفشل العملية) والتحذيرات (التي لا تفعل ذلك). بدون --max-warnings 0، تتراكم لدى الفرق مئات التحذيرات التي يتجاهلها الجميع. معاملة التحذيرات كأخطاء في CI تجبر الفريق إما على إصلاحها أو تعطيل القاعدة صراحةً. لا يوجد حل وسط.
التنسيق كفحص CI، وليس مجرد اقتراح
تشغيل Prettier في CI (مع --check، وليس --write) يضمن تنسيقًا متسقًا دون الاعتماد على امتلاك كل مطور لملحق المحرر الصحيح. إذا فشل التنسيق في CI، يقوم المطور بتشغيل npx prettier --write . محليًا ويلتزم بالإصلاح. هذا غير قابل للتفاوض — تنتهي نقاشات التنسيق عندما تتخذ الأداة القرارات.
المرحلة 2: الاختبار
الاختبار هو العمود الفقري للمسار. أقوم بتقسيم الاختبارات إلى مهام متوازية بناءً على النوع للحصول على ملاحظات أسرع.
unit-tests:
name: Unit Tests
runs-on: ubuntu-latest
needs: validate
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run unit tests
run: npx vitest run --coverage --reporter=verbose
- name: Upload coverage report
if: always()
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: coverage/
integration-tests:
name: Integration Tests
runs-on: ubuntu-latest
needs: validate
services:
postgres:
image: postgres:16
env:
POSTGRES_USER: test
POSTGRES_PASSWORD: test
POSTGRES_DB: testdb
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
redis:
image: redis:7
ports:
- 6379:6379
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run database migrations
run: npx prisma migrate deploy
env:
DATABASE_URL: postgresql://test:test@localhost:5432/testdb
- name: Run integration tests
run: npx vitest run --config vitest.integration.config.ts
env:
DATABASE_URL: postgresql://test:test@localhost:5432/testdb
REDIS_URL: redis://localhost:6379
حاويات الخدمة لاختبارات التكامل
حاويات خدمة GitHub Actions غير مستخدمة بالقدر الكافي. بدلاً من محاكاة قاعدة بياناتك في اختبارات التكامل (التي تختبر المحاكاة، وليس التعليمات البرمجية الخاصة بك)، قم بتشغيل مثيل PostgreSQL حقيقي. يتعامل كتلة services مع إدارة دورة الحياة — تبدأ الحاوية قبل اختباراتك وتتوقف بعدها.
يضيف هذا حوالي 15-20 ثانية للمهمة لبدء تشغيل الحاوية، ولكن الثقة التي تحصل عليها من الاختبار مقابل قاعدة بيانات حقيقية تستحق العناء.
تنفيذ الاختبار المتوازي
تُشغل اختبارات الوحدة واختبارات التكامل بالتوازي (كلاهما needs: validate، وليس needs: unit-tests). هذا يقلل من إجمالي وقت المسار. إذا استغرقت اختبارات الوحدة دقيقتين واختبارات التكامل 4 دقائق، فإن التنفيذ المتوازي يعني أنك تنتظر 4 دقائق بدلاً من 6.
المرحلة 3: البناء
تتحقق مرحلة البناء من أن المشروع يتم تجميعه وينتج عناصر قابلة للنشر.
build:
name: Build
runs-on: ubuntu-latest
needs: [unit-tests, integration-tests]
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build application
run: npm run build
env:
NODE_ENV: production
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: build-output
path: dist/
retention-days: 7
تخزين البناء المؤقت
بالنسبة لعمليات النشر المستندة إلى Docker، يعمل التخزين المؤقت للطبقات على تسريع عمليات البناء بشكل كبير:
build-docker:
name: Build Docker Image
runs-on: ubuntu-latest
needs: [unit-tests, integration-tests]
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: |
ghcr.io/${{ github.repository }}:${{ github.sha }}
ghcr.io/${{ github.repository }}:latest
cache-from: type=gha
cache-to: type=gha,mode=max
يستخدم cache-from: type=gha ذاكرة التخزين المؤقت لـ GitHub Actions لتخزين طبقات Docker بين عمليات التشغيل. بالنسبة لتطبيق Node.js نموذجي، يقلل هذا من وقت البناء من 3-4 دقائق إلى 30-60 ثانية للتغييرات التي تعتمد على التبعيات فقط.
أفضل ممارسات Dockerfile لـ CI
يؤثر هيكل Dockerfile بشكل مباشر على كفاءة ذاكرة التخزين المؤقت للبناء. رتب الطبقات من الأقل تغييرًا إلى الأكثر تغييرًا:
FROM node:20-alpine AS base
# System dependencies (rarely changes)
RUN apk add --no-cache libc6-compat
# Package manifests (changes when dependencies change)
WORKDIR /app
COPY package.json package-lock.json ./
# Install dependencies (cached unless manifests change)
FROM base AS deps
RUN npm ci --production
FROM base AS build
RUN npm ci
COPY . .
RUN npm run build
# Production image
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=deps /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
COPY --from=build /app/package.json ./
EXPOSE 3000
CMD ["node", "dist/server.js"]
تحافظ عمليات البناء متعددة المراحل على حجم الصورة النهائية صغيرًا (لا توجد تبعيات تطوير، ولا يوجد كود مصدري)، ويضمن ترتيب الطبقات أن npm ci يعمل فقط عند تغيير package.json أو package-lock.json.
المرحلة 4: النشر إلى بيئة الاختبار (Staging)
يحدث النشر إلى بيئة الاختبار تلقائيًا عند الدمج مع فرع develop. يجب أن تعكس بيئة الاختبار بيئة الإنتاج بأكبر قدر ممكن — نفس البنية التحتية، ونفس متغيرات البيئة (بقِيَم مختلفة)، ونفس تكوين التحجيم.
deploy-staging:
name: Deploy to Staging
runs-on: ubuntu-latest
needs: build
if: github.ref == 'refs/heads/develop' && github.event_name == 'push'
environment:
name: staging
url: https://staging.example.com
steps:
- uses: actions/checkout@v4
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
name: build-output
path: dist/
- name: Deploy to Cloud Run (Staging)
uses: google-github-actions/deploy-cloudrun@v2
with:
service: my-api-staging
region: us-central1
image: ghcr.io/${{ github.repository }}:${{ github.sha }}
env_vars: |
NODE_ENV=staging
DATABASE_URL=${{ secrets.STAGING_DATABASE_URL }}
بيئات GitHub
يمكّن مفتاح environment في سير العمل قواعد حماية البيئة في GitHub. يمكنك طلب موافقة يدوية، وتقييد الفروع التي يمكنها النشر، وتعيين أسرار خاصة بالبيئة. بالنسبة لبيئة الاختبار، لا أطلب عادةً أي موافقة (نشر تلقائي). بالنسبة للإنتاج، أطلب مراجعًا واحدًا على الأقل.
المرحلة 5: اختبارات الدخان (Smoke Tests)
بعد النشر إلى بيئة الاختبار، قم بتشغيل مجموعة أساسية من الاختبارات على التطبيق المنشور للتحقق من أنه يعمل بالفعل في البيئة الحقيقية.
smoke-tests:
name: Smoke Tests
runs-on: ubuntu-latest
needs: deploy-staging
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Wait for deployment
run: |
for i in {1..30}; do
status=$(curl -s -o /dev/null -w "%{http_code}" https://staging.example.com/health)
if [ "$status" = "200" ]; then
echo "Service is healthy"
exit 0
fi
echo "Waiting for service... (attempt $i)"
sleep 10
done
echo "Service did not become healthy"
exit 1
- name: Run smoke tests
run: npx playwright test --config=playwright.smoke.config.ts
env:
BASE_URL: https://staging.example.com
اختبارات الدخان ليست اختبارات شاملة (E2E) كاملة. إنها تتحقق من المسارات الحيوية: هل يمكن تحميل الصفحة الرئيسية، هل يمكن للمستخدم تسجيل الدخول، هل نقطة نهاية API الرئيسية تُرجع البيانات. من خمسة إلى عشرة سيناريوهات تستغرق أقل من دقيقتين. إذا فشلت اختبارات الدخان، يتم التراجع عن نشر بيئة الاختبار ولا يستمر نشر الإنتاج.
المرحلة 6: النشر إلى الإنتاج
النشر إلى الإنتاج هو حيث تكتسب استراتيجية النشر أهمية قصوى. هناك عدة طرق، لكل منها مقايضات مختلفة.
النشر المتدحرج (Rolling Deployment)
الاستراتيجية الأبسط. يتم تشغيل مثيلات جديدة بينما يتم إيقاف المثيلات القديمة تدريجيًا. في أي نقطة أثناء النشر، تصل بعض الطلبات إلى الإصدار القديم وبعضها إلى الإصدار الجديد. هذا هو الافتراضي لمعظم منصات الحاويات.
الإيجابيات: بسيطة، لا توجد تكلفة بنية تحتية إضافية. السلبيات: إصداران يخدمان حركة المرور في وقت واحد، مما قد يسبب مشكلات في تغييرات مخطط قاعدة البيانات أو تغييرات عقد API.
النشر الأزرق-الأخضر (Blue-Green Deployment)
توجد بيئتان متطابقتان (زرقاء وخضراء). إحداهما تخدم حركة المرور بينما الأخرى خاملة. يتم النشر إلى البيئة الخاملة، والتحقق من عملها، ثم تبديل الموجه. إذا حدث خطأ ما، يتم التبديل مرة أخرى.
deploy-production:
name: Deploy to Production
runs-on: ubuntu-latest
needs: smoke-tests
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
environment:
name: production
url: https://api.example.com
steps:
- name: Deploy new revision
uses: google-github-actions/deploy-cloudrun@v2
with:
service: my-api-production
region: us-central1
image: ghcr.io/${{ github.repository }}:${{ github.sha }}
flags: '--no-traffic'
- name: Run production health check
run: |
REVISION_URL=$(gcloud run revisions describe my-api-production-${{ github.sha }} \
--region us-central1 --format='value(status.url)')
status=$(curl -s -o /dev/null -w "%{http_code}" "$REVISION_URL/health")
if [ "$status" != "200" ]; then
echo "Health check failed"
exit 1
fi
- name: Migrate traffic
run: |
gcloud run services update-traffic my-api-production \
--region us-central1 \
--to-latest
تُستخدم علامة --no-traffic لنشر المراجعة الجديدة دون إرسال أي حركة مرور إليها. بعد اجتياز فحص السلامة، يتم تحويل حركة المرور إلى المراجعة الجديدة. إذا فشل فحص السلامة، يتوقف سير العمل وتستمر المراجعة القديمة في الخدمة.
النشر الكناري (Canary Deployment)
قم بتوجيه نسبة صغيرة من حركة المرور (5-10%) إلى الإصدار الجديد مع مراقبة معدلات الأخطاء، وزمن الاستجابة، ومقاييس الأعمال الرئيسية. إذا بدت المقاييس جيدة بعد فترة محددة، قم بزيادة حركة المرور تدريجيًا. إذا تدهورت المقاييس، قم بالتراجع.
- name: Canary - route 10% of traffic
run: |
gcloud run services update-traffic my-api-production \
--region us-central1 \
--to-revisions=my-api-production-${{ github.sha }}=10
- name: Monitor canary (5 minutes)
run: |
sleep 300
# Check error rate for the canary revision
ERROR_RATE=$(curl -s "$MONITORING_API/error-rate?revision=${{ github.sha }}")
if (( $(echo "$ERROR_RATE > 1.0" | bc -l) )); then
echo "Error rate too high: $ERROR_RATE%. Rolling back."
gcloud run services update-traffic my-api-production \
--region us-central1 \
--to-latest
exit 1
fi
- name: Promote canary to 100%
run: |
gcloud run services update-traffic my-api-production \
--region us-central1 \
--to-latest
متى تستخدم النشر الكناري: عندما يكون لديك ما يكفي من حركة المرور بحيث لا تزال نسبة 10% تولد معدلات أخطاء ذات دلالة إحصائية. بالنسبة لخدمة تتعامل مع 100 طلب/دقيقة، تمنحك نسبة 10% 10 طلبات/دقيقة — وهو ما يكفي لاكتشاف معدلات أخطاء مرتفعة في غضون دقائق قليلة. بالنسبة لخدمة تتعامل مع 10 طلبات/دقيقة، فإن عمليات النشر الكناري ليست ذات مغزى.
آليات التراجع
يجب أن يكون لكل عملية نشر مسار تراجع موثق. "إعادة نشر الإصدار القديم" هي استراتيجية تراجع، لكنها بطيئة. خيارات أفضل:
التراجع الفوري عبر تحويل حركة المرور
إذا قمت بالنشر باستخدام --no-traffic وقمت بتحويل حركة المرور، فإن التراجع هو أمر واحد:
# Roll back to the previous revision
gcloud run services update-traffic my-api-production \
--region us-central1 \
--to-revisions=PREVIOUS_REVISION=100
يصبح هذا ساري المفعول في ثوانٍ لأن المراجعة القديمة لا تزال قيد التشغيل.
التراجع التلقائي
أضف خطوة مراقبة بعد النشر تقوم بالتراجع تلقائيًا إذا ارتفعت معدلات الأخطاء بشكل حاد:
- name: Post-deploy monitor
run: |
for i in {1..10}; do
sleep 60
ERROR_RATE=$(curl -s "$MONITORING_API/error-rate?window=5m")
if (( $(echo "$ERROR_RATE > 2.0" | bc -l) )); then
echo "Error rate $ERROR_RATE% exceeds threshold. Rolling back."
gcloud run services update-traffic my-api-production \
--region us-central1 \
--to-revisions=$PREVIOUS_REVISION=100
exit 1
fi
echo "Minute $i: Error rate $ERROR_RATE% — OK"
done
التراجع عن ترحيل قاعدة البيانات
هذا هو الجزء الأصعب. إذا تضمن نشرك تغييرات في مخطط قاعدة البيانات، فإن التراجع عن التطبيق دون التراجع عن قاعدة البيانات يؤدي إلى عدم تطابق. الحل هو ترحيلات التوسع والتقليص (expand-and-contract migrations):
- التوسع: أضف أعمدة/جداول جديدة دون إزالة القديمة. اجعل الأعمدة الجديدة قابلة للقيم الفارغة (nullable) أو ذات قيم افتراضية.
- النشر: يكتب الكود الجديد إلى الأعمدة القديمة والجديدة على حد سواء. يقرأ من الأعمدة الجديدة مع الرجوع إلى القديمة.
- ترحيل البيانات: املأ الأعمدة الجديدة ببيانات من الأعمدة القديمة.
- التقليص: بمجرد التحقق، انشر الكود الذي يستخدم الأعمدة الجديدة فقط. ثم قم بإسقاط الأعمدة القديمة.
هذا يجعل كل خطوة قابلة للعكس بشكل مستقل.
إدارة الأسرار
لا تقم أبدًا بتضمين الأسرار في الكود مباشرةً. لا تقم أبدًا بتثبيت ملفات .env. إليك كيفية تعاملي مع الأسرار في GitHub Actions:
أسرار GitHub
في معظم الحالات، تكون أسرار GitHub المدمجة كافية. إنها مشفرة، ولا تُكشف أبدًا في السجلات، ومحددة النطاق للمستودعات أو المؤسسات.
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
API_KEY: ${{ secrets.API_KEY }}
أسرار محددة النطاق للبيئة
أسرار مختلفة لبيئة الاختبار والإنتاج:
deploy-staging:
environment: staging
# ${{ secrets.DATABASE_URL }} resolves to the staging value
deploy-production:
environment: production
# ${{ secrets.DATABASE_URL }} resolves to the production value
مديرو الأسرار الخارجيون
للفرق الأكبر أو متطلبات الامتثال الأكثر صرامة، استخدم AWS Secrets Manager، أو Google Secret Manager، أو HashiCorp Vault. يجلب التطبيق الأسرار في وقت التشغيل بدلاً من تلقيها كمتغيرات بيئة.
import { SecretManagerServiceClient } from '@google-cloud/secret-manager';
const client = new SecretManagerServiceClient();
async function getSecret(name: string): Promise<string> {
const [version] = await client.accessSecretVersion({
name: `projects/my-project/secrets/${name}/versions/latest`,
});
return version.payload?.data?.toString() || '';
}
مراقبة عمليات النشر
لا تكتمل عملية النشر عندما يكون الكود مباشرًا. بل تكتمل عندما تكون قد تأكدت من أن الكود يعمل.
إشعارات النشر
أرسل أحداث النشر إلى Slack مع السياق ذي الصلة:
- name: Notify Slack
if: always()
uses: slackapi/slack-github-action@v2
with:
webhook: ${{ secrets.SLACK_WEBHOOK }}
webhook-type: incoming-webhook
payload: |
{
"text": "${{ job.status == 'success' && 'Deployed' || 'Failed to deploy' }} `${{ github.sha }}` to production",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*${{ job.status == 'success' && ':white_check_mark: Deployment Successful' || ':x: Deployment Failed' }}*\nCommit: `${{ github.sha }}`\nAuthor: ${{ github.actor }}\nMessage: ${{ github.event.head_commit.message }}"
}
}
]
}
فحوصات السلامة بعد النشر
بعد كل نشر للإنتاج، تحقق من أن نقاط النهاية الحيوية تستجيب بشكل صحيح:
- name: Verify production health
run: |
endpoints=("/" "/api/health" "/api/v1/status")
for endpoint in "${endpoints[@]}"; do
status=$(curl -s -o /dev/null -w "%{http_code}" "https://api.example.com$endpoint")
if [ "$status" != "200" ]; then
echo "FAILED: $endpoint returned $status"
exit 1
fi
echo "OK: $endpoint returned $status"
done
مثال على مسار كامل
بجمع كل ذلك معًا، إليك مسار مكثف ولكنه كامل لتطبيق Node.js يتم نشره إلى Cloud Run:
name: CI/CD Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20, cache: 'npm' }
- run: npm ci
- run: npx prettier --check "src/**/*.{ts,tsx}"
- run: npx eslint src/ --max-warnings 0
- run: npx tsc --noEmit
test:
needs: validate
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env: { POSTGRES_USER: test, POSTGRES_PASSWORD: test, POSTGRES_DB: testdb }
ports: ['5432:5432']
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20, cache: 'npm' }
- run: npm ci
- run: npx vitest run --coverage
env:
DATABASE_URL: postgresql://test:test@localhost:5432/testdb
build-and-push:
needs: test
if: github.event_name == 'push'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
deploy:
needs: build-and-push
runs-on: ubuntu-latest
environment:
name: ${{ github.ref == 'refs/heads/main' && 'production' || 'staging' }}
steps:
- uses: google-github-actions/auth@v2
with:
credentials_json: ${{ secrets.GCP_SA_KEY }}
- uses: google-github-actions/deploy-cloudrun@v2
with:
service: my-api-${{ github.ref == 'refs/heads/main' && 'prod' || 'staging' }}
region: us-central1
image: ghcr.io/${{ github.repository }}:${{ github.sha }}
تستحق كتلة concurrency الملاحظة — فهي تلغي عمليات التشغيل الجارية لنفس الفرع في طلبات السحب، لذا فإن دفع إصلاح بينما لا يزال CI قيد التشغيل لا يضع مسارين في قائمة الانتظار.
ماذا سأفعل بشكل مختلف لو بدأت من جديد
إذا كنت سأقوم بإعداد مسار من الصفر اليوم، فسأبدأ بمراحل التحقق والاختبار فقط — بدون أتمتة النشر. سأقوم بالنشر يدويًا (أو باستخدام نص برمجي بسيط للنشر) للأسابيع القليلة الأولى بينما يستقر الكود. أضف أتمتة النشر بمجرد أن تصبح العملية اليدوية هي عنق الزجاجة، وليس قبل ذلك. تحسين المسار المبكر حقيقي تمامًا مثل تحسين الكود المبكر، وتصحيح أخطاء مسار نشر معطل في الساعة 2 صباحًا أسوأ بكثير من تشغيل ./deploy.sh يدويًا.
مشاريع ذات صلة
RestoHub
المطاعم تتوقف عن خسارة 30% لصالح Uber Eats — تحصل على نظام طلبات وقوائم وموقع إلكتروني وبرنامج ولاء خاص بها في منصة واحدة. تجربة طلب كاملة بمستوى Uber Eats، لكن المطعم يحتفظ بكل قرش.
TakeCare
ممرض واحد يراقب الآن 250 مريضًا عن بُعد — ليحل محل المكالمات الهاتفية والزيارات المنزلية اليدوية في أكبر مستشفيات كيبيك. يعمل حاليًا في مستشفى Jewish General وCHUM ومعهد Douglas للصحة النفسية.