← همه آموزش‌ها

CI/CD چیست؟

از تغییر کد تا تست، ساخت و استقرار خودکار؛ ساده، مرحله‌به‌مرحله و کاربردی

بنر CI/CD: چهار مرحله Commit، Build، Test و Deploy برای پروژه دات‌نت.
هر commit مسیر بیلد، تست و استقرار را خودش طی می‌کند.

در این آموزش چه می‌سازیم؟

یک پروژه .NET را طوری تنظیم می‌کنیم که بعد از هر Pull Request به‌صورت خودکار restore، build و test شود. بعد از merge در شاخه main نیز Docker image ساخته، در GitHub Container Registry منتشر و روی یک سرور لینوکسی deploy می‌شود.

قبل از کدنویسی، تمام مفاهیم را با مثال‌های ساده توضیح می‌دهیم؛ بنابراین برای شروع به تجربه قبلی DevOps نیاز ندارید.

۱. CI/CD چیست؟

CI/CD مجموعه‌ای از روش‌ها و ابزارهاست که کارهای تکراری مسیر انتشار نرم‌افزار را خودکار می‌کند. یعنی به‌جای اینکه هر بار دستی پروژه را build، تست و روی سرور کپی کنیم، یک Pipeline این کارها را با ترتیب مشخص انجام می‌دهد.

CI یعنی Continuous Integration

ترجمه ساده آن «یکپارچه‌سازی مداوم» است. توسعه‌دهندگان تغییرات کوچک خود را مرتب وارد مخزن می‌کنند و سیستم بعد از هر تغییر به‌صورت خودکار کد را build و test می‌کند.

هدف CI این است که خیلی زود بفهمیم کد جدید چیزی را خراب کرده است؛ نه چند هفته بعد و هنگام انتشار.

CD دو معنی نزدیک دارد

اصطلاحمعنی سادهمرحله آخر
Continuous Delivery نسخه همیشه آماده انتشار است. انتشار Production با تأیید دستی انجام می‌شود.
Continuous Deployment هر تغییر تأییدشده خودکار منتشر می‌شود. انتشار Production بدون دخالت دستی انجام می‌شود.

هر دو رویکرد معتبرند. برای سیستم‌های حساس مثل بانک، تأیید دستی قبل از Production معمولاً منطقی است. برای محصولی با انتشارهای کوچک و تست‌های قوی، Continuous Deployment می‌تواند مناسب باشد.

چرخه CI/CD از نوشتن کد و build تا تست، انتشار، استقرار و مانیتورینگ
چرخه کامل CI/CD: بازخورد مانیتورینگ دوباره به تیم توسعه برمی‌گردد و چرخه بعدی را بهتر می‌کند.

۲. چرا به CI/CD نیاز داریم؟

فرض کنید انتشار دستی این مراحل را دارد:

  1. توسعه‌دهنده پروژه را روی لپ‌تاپ خود build می‌کند.
  2. فایل‌ها را ZIP می‌کند و برای مسئول سرور می‌فرستد.
  3. مسئول سرور نسخه قبلی را پاک و نسخه جدید را کپی می‌کند.
  4. کسی یادش می‌رود تست‌ها یا migration را اجرا کند.
  5. سرویس خراب می‌شود و دقیقاً معلوم نیست چه نسخه‌ای نصب شده است.

CI/CD این مشکلات را کمتر می‌کند:

  • تکرارپذیری: انتشار هر بار با دستورهای یکسان انجام می‌شود.
  • سرعت: کارهای تکراری به‌جای انسان توسط ماشین اجرا می‌شوند.
  • کیفیت: کد بدون عبور از build و test منتشر نمی‌شود.
  • ردیابی: می‌دانیم چه commit و چه image روی سرور است.
  • کاهش ریسک: تغییرات کوچک‌تر و دفعات انتشار بیشتر، پیدا کردن مشکل و rollback را آسان‌تر می‌کند.

۳. اصطلاحات مهم

اصطلاحتوضیح ساده
Pipelineکل مسیر خودکار از دریافت کد تا تست و انتشار.
Workflowفایل تعریف Pipeline؛ در GitHub Actions معمولاً یک فایل YAML است.
Triggerرویدادی که Workflow را شروع می‌کند؛ مثل push یا Pull Request.
Jobیک گروه از کارها که روی یک Runner اجرا می‌شود؛ مثل job تست.
Stepیک مرحله داخل Job؛ مثل اجرای dotnet test.
Runnerماشین مجازی یا سروری که دستورهای Pipeline را اجرا می‌کند.
Artifactخروجی قابل نگه‌داری یک اجرا؛ مثل فایل publish یا گزارش تست.
Registryمحل نگه‌داری Docker image؛ مثل GHCR یا Docker Hub.
Environmentمحیط مقصد مثل Development، Staging و Production.
Secretاطلاعات محرمانه مثل SSH key که رمزگذاری‌شده نگه‌داری می‌شود.
رابطه Trigger، Workflow، Job، Step، Runner و Artifact در یک Pipeline
Trigger یک Workflow را شروع می‌کند؛ Workflow از Jobها، هر Job از Stepها تشکیل شده و روی Runner اجرا می‌شود.

۴. جریان استاندارد Pipeline

۱. Source: توسعه‌دهنده کد را push و Pull Request ایجاد می‌کند.
↓
۲. Restore: وابستگی‌ها و پکیج‌ها دریافت می‌شوند.
↓
۳. Build: کامپایل کد بررسی می‌شود.
↓
۴. Test: تست‌های unit و integration اجرا می‌شوند.
↓
۵. Package: خروجی یا Docker image ساخته می‌شود.
↓
۶. Publish: image با یک نسخه مشخص در Registry قرار می‌گیرد.
↓
۷. Deploy: نسخه جدید ابتدا در Staging و سپس Production اجرا می‌شود.
↓
۸. Verify: health check و smoke test سلامت نسخه را بررسی می‌کنند.
یک Pipeline خوب در اولین خطا متوقف می‌شود. اگر تست شکست خورد، ساخت image و deploy نباید اجرا شود.
مسیر عملی CI/CD از GitHub Actions و تست تا Docker Registry و استقرار API روی سرور لینوکسی
معماری مثال عملی این آموزش: GitHub Actions کد را بررسی می‌کند، image را می‌سازد و نسخه سالم را روی VPS اجرا می‌کند.

۵. آماده‌سازی پروژه نمونه

ساختار فرضی مخزن ما:

SampleApi/
├── src/
│   └── SampleApi/
│       ├── SampleApi.csproj
│       ├── Program.cs
│       └── Dockerfile
├── tests/
│   └── SampleApi.Tests/
│       └── SampleApi.Tests.csproj
├── .github/
│   └── workflows/
│       └── ci-cd.yml
└── SampleApi.sln

پیش از ساخت Pipeline، این دستورها باید روی سیستم خودتان موفق باشند:

dotnet restore SampleApi.sln
dotnet build SampleApi.sln --configuration Release --no-restore
dotnet test SampleApi.sln --configuration Release --no-build
Pipeline جای رفع مشکلات یک پروژه خراب نیست. ابتدا دستورها را محلی اجرا کنید، بعد همان دستورهای موفق را وارد Pipeline کنید.

یک health check ساده

برای اینکه بعد از deploy بتوانیم سلامت برنامه را بررسی کنیم، در Program.cs یک endpoint اضافه کنید:

builder.Services.AddHealthChecks();

var app = builder.Build();

app.MapHealthChecks("/health");
app.MapControllers();

app.Run();

۶. ساخت CI با GitHub Actions

داخل مخزن فایل .github/workflows/ci-cd.yml را بسازید. YAML به فاصله‌ها حساس است؛ از Tab استفاده نکنید.

name: CI/CD

on:
  pull_request:
    branches: [main]
  push:
    branches: [main]

permissions:
  contents: read

jobs:
  test:
    name: Build and test
    runs-on: ubuntu-latest

    steps:
      - name: Checkout source
        uses: actions/checkout@v4

      - name: Setup .NET
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: "8.0.x"

      - name: Restore dependencies
        run: dotnet restore SampleApi.sln

      - name: Build
        run: dotnet build SampleApi.sln --configuration Release --no-restore

      - name: Test
        run: >
          dotnet test SampleApi.sln
          --configuration Release
          --no-build
          --logger "trx"
          --results-directory TestResults

      - name: Upload test results
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: test-results
          path: TestResults
          if-no-files-found: ignore

خط‌به‌خط چه اتفاقی می‌افتد؟

  • on مشخص می‌کند Workflow روی Pull Request و push به main اجرا شود.
  • permissions دسترسی پیش‌فرض Workflow را محدود می‌کند.
  • runs-on یک Runner لینوکسی موقت از GitHub می‌گیرد.
  • checkout کد مخزن را داخل Runner قرار می‌دهد.
  • setup-dotnet نسخه موردنیاز SDK را نصب می‌کند.
  • --no-restore و --no-build از تکرار کار مرحله قبل جلوگیری می‌کنند.
  • if: always() گزارش تست را حتی هنگام شکست تست‌ها نگه می‌دارد.

Branch protection

فقط داشتن CI کافی نیست. در تنظیمات GitHub برای شاخه main یک Branch Protection Rule بسازید و این موارد را فعال کنید:

  • تغییرات فقط از طریق Pull Request وارد شوند.
  • حداقل یک review لازم باشد.
  • Job مربوط به build و test قبل از merge موفق باشد.
  • push مستقیم یا force push به main غیرفعال باشد.

یک Pipeline در GitHub، GitLab و Microsoft Azure DevOps

مفهوم CI/CD در هر سه پلتفرم یکی است؛ فقط نام‌ها و شکل فایل YAML فرق می‌کند. تب‌ها را عوض کنید تا نمونه معادل build و test پروژه .NET و امکانات هر سرویس را ببینید.

GitHub Actions

فایل Pipeline در مسیر .github/workflows/ci.yml قرار می‌گیرد. GitHub-hosted Runner، توکن داخلی GITHUB_TOKEN و اتصال مستقیم به GHCR از مزیت‌های اصلی آن هستند.

name: CI

on:
  pull_request:
    branches: [main]
  push:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-dotnet@v4
        with:
          dotnet-version: "8.0.x"
      - run: dotnet restore SampleApi.sln
      - run: dotnet build SampleApi.sln -c Release --no-restore
      - run: dotnet test SampleApi.sln -c Release --no-build
  • Secrets: در Settings → Secrets and variables → Actions
  • Runner: GitHub-hosted یا self-hosted
  • Registry: معمولاً GHCR، ولی Docker Hub و ACR نیز قابل استفاده‌اند.
  • کنترل انتشار: Environment، Required Reviewers و Branch Protection
پلتفرمفایل اصلیانتخاب مناسب وقتی…
GitHub Actions.github/workflows/*.ymlکد روی GitHub است و راه‌اندازی سریع و Marketplace بزرگ می‌خواهید.
GitLab CI/CD.gitlab-ci.ymlیک پلتفرم یکپارچه یا نصب داخل سازمان و Runnerهای قابل‌کنترل می‌خواهید.
Azure DevOpsazure-pipelines.ymlسازمان شما از اکوسیستم Microsoft، Azure Repos، Boards و Service Connection استفاده می‌کند.
ابزار را بر اساس محل کد، زیرساخت، مهارت تیم و محدودیت‌های سازمان انتخاب کنید. اصول مهم (تست خودکار، artifact تغییرناپذیر، secrets امن و rollback) در هر سه پلتفرم یکسان‌اند.

۷. ساخت و انتشار Docker image

اگر هنوز با Dockerfile، build چندمرحله‌ای و اجرای پروژه .NET داخل container آشنا نیستید، ابتدا آموزش Dockerize کردن پروژه .NET را بخوانید؛ در آن آموزش ساخت Dockerfile استاندارد، restore پکیج‌ها و نکات امنیتی با جزئیات توضیح داده شده است.

داخل مسیر پروژه API یک Dockerfile چندمرحله‌ای قرار دهید:

FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src

COPY ["src/SampleApi/SampleApi.csproj", "src/SampleApi/"]
RUN dotnet restore "src/SampleApi/SampleApi.csproj"

COPY . .
RUN dotnet publish "src/SampleApi/SampleApi.csproj" \
    --configuration Release \
    --output /app/publish \
    --no-restore

FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime
WORKDIR /app
COPY --from=build /app/publish .

ENV ASPNETCORE_URLS=http://+:8080
EXPOSE 8080

USER $APP_UID
ENTRYPOINT ["dotnet", "SampleApi.dll"]

و یک .dockerignore ساده:

**/bin/
**/obj/
.git/
.github/
.vs/
.vscode/
TestResults/
README.md

GHCR چیست؟

GHCR مخفف GitHub Container Registry است؛ سرویسی از GitHub برای نگه‌داری و توزیع container imageها. آدرس آن ghcr.io است.

Dockerfile مثل «دستور پخت» برنامه است، Docker image بسته نهایی و آماده اجراست، و Registry انباری است که این بسته‌ها را نگه می‌دارد. GHCR همان انبار متصل به حساب و مخزن GitHub شماست.

۱. Pipeline با Dockerfile یک image می‌سازد.
↓
۲. image با یک نام و tag به ghcr.io push می‌شود.
↓
۳. سرور مقصد همان image را از GHCR pull می‌کند.
↓
۴. Docker از روی image یک container اجرا می‌کند.

نام یک image در GHCR

ghcr.io/OWNER/IMAGE_NAME:TAG

# مثال
ghcr.io/mortenaho/sample-api:latest
ghcr.io/mortenaho/sample-api:sha-a1b2c3d
  • ghcr.io: آدرس Registry
  • mortenaho: نام کاربر یا سازمان GitHub
  • sample-api: نام image یا package
  • latest یا sha-a1b2c3d: نسخه یا tag image

ورود، Push و Pull به‌صورت دستی

برای کار محلی، یک Personal Access Token با حداقل دسترسی لازم بسازید. رمز حساب GitHub برای docker login استفاده نمی‌شود.

# ورود؛ توکن را مستقیم داخل تاریخچه shell ننویسید
echo "$CR_PAT" | docker login ghcr.io \
  -u YOUR_GITHUB_USERNAME --password-stdin

# ساخت image
docker build -t ghcr.io/mortenaho/sample-api:v1.0.0 .

# ارسال به GHCR
docker push ghcr.io/mortenaho/sample-api:v1.0.0

# دریافت روی سرور
docker pull ghcr.io/mortenaho/sample-api:v1.0.0

# اجرای container
docker run -d --name sample-api -p 8080:8080 \
  ghcr.io/mortenaho/sample-api:v1.0.0
نوع عملیاتدسترسی لازم
دریافت package خصوصیread:packages
ارسال یا به‌روزرسانی packagewrite:packages
حذف نسخه‌های packagedelete:packages (فقط در صورت نیاز واقعی)
توکن را داخل Dockerfile، image، فایل compose یا Git ذخیره نکنید. آن را در Secretهای Pipeline یا secret manager سرور قرار دهید و کمترین دسترسی لازم را بدهید.

Public یا Private؟

  • Public: همه می‌توانند image را pull کنند؛ برای پروژه متن‌باز مناسب است.
  • Private: فقط کاربران و سرورهای مجاز می‌توانند image را دریافت کنند و قبل از pull باید login انجام شود.

package جدید ممکن است به‌صورت Private ساخته شود. از صفحه Package در GitHub می‌توانید visibility، دسترسی repositoryها و نسخه‌های موجود را مدیریت کنید.

Tag و Digest چه فرقی دارند؟

Tag یک نام قابل‌فهم مثل v1.0.0 یا latest است و می‌تواند بعداً به image دیگری اشاره کند. Digest شناسه محتوایی تغییرناپذیر image است:

ghcr.io/mortenaho/sample-api@sha256:abc123...

برای Production از tag مبتنی بر commit یا digest استفاده کنید. latest برای مشاهده سریع خوب است، اما به‌تنهایی مشخص نمی‌کند دقیقاً کدام نسخه deploy شده است.

GHCR چه تفاوتی با Docker Hub دارد؟

GHCRDocker Hub
مستقیم با repository، GitHub Actions و دسترسی‌های GitHub یکپارچه است.Registry عمومی و مستقل با اکوسیستم گسترده Docker است.
در Workflow می‌تواند از GITHUB_TOKEN داخلی استفاده کند.معمولاً username و access token جدا لازم دارد.
برای پروژه‌ای که کد آن روی GitHub است انتخاب طبیعی و ساده‌ای است.برای imageهای عمومی و استفاده مستقل از میزبان کد بسیار رایج است.
GHCR برنامه را اجرا نمی‌کند؛ فقط image را نگه می‌دارد. اجرای واقعی روی VPS، Kubernetes، Azure Container Apps یا سرویس میزبان دیگری انجام می‌شود.

حالا Job زیر را بعد از Job تست به همان فایل Workflow اضافه کنید. این Job فقط هنگام push به main اجرا می‌شود؛ یعنی Pull Requestها اجازه انتشار image ندارند.

  publish-image:
    name: Build and publish image
    runs-on: ubuntu-latest
    needs: test
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'

    permissions:
      contents: read
      packages: write

    steps:
      - name: Checkout source
        uses: actions/checkout@v4

      - name: Login to GHCR
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Create image tags
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ghcr.io/${{ github.repository }}
          tags: |
            type=raw,value=latest
            type=sha

      - name: Build and push
        uses: docker/build-push-action@v6
        with:
          context: .
          file: src/SampleApi/Dockerfile
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
در GitHub Actions معمولاً برای push به GHCR به Personal Access Token نیاز ندارید. GitHub برای هر اجرا یک GITHUB_TOKEN کوتاه‌عمر می‌سازد؛ کافی است Job مجوز packages: write داشته باشد. PAT بیشتر برای سیستم شخصی، سرور یا دسترسی بین repositoryها لازم می‌شود.

برای هر commit دو tag ساخته می‌شود:

  • latest: آخرین نسخه؛ برای انسان ساده است ولی دقیق و تغییرناپذیر نیست.
  • sha-abc1234: به یک commit مشخص اشاره می‌کند و برای deploy و rollback بهتر است.
در Production فقط به latest تکیه نکنید. با tag یا digest تغییرناپذیر دقیقاً می‌دانید چه کدی اجرا شده است.

۸. آماده‌سازی سرور لینوکسی

روی سرور Docker و Docker Compose Plugin را نصب کنید. سپس یک کاربر مخصوص deploy بسازید؛ از کاربر root برای Pipeline استفاده نکنید.

sudo useradd --create-home --shell /bin/bash deploy
sudo usermod -aG docker deploy
sudo mkdir -p /opt/sample-api
sudo chown deploy:deploy /opt/sample-api

کلید SSH مخصوص CI بسازید:

ssh-keygen -t ed25519 -C "github-actions-deploy" \
  -f github-actions-deploy
  • محتوای کلید عمومی github-actions-deploy.pub را در فایل ~deploy/.ssh/authorized_keys سرور قرار دهید.
  • محتوای کلید خصوصی github-actions-deploy را بعداً در GitHub Secret بگذارید.
  • این کلید فقط باید دسترسی لازم برای deploy داشته باشد.

فایل docker-compose.yml روی سرور

services:
  api:
    image: ghcr.io/mortenaho/sampleapi:${IMAGE_TAG:-latest}
    container_name: sample-api
    restart: unless-stopped
    ports:
      - "127.0.0.1:8080:8080"
    environment:
      ASPNETCORE_ENVIRONMENT: Production
      ConnectionStrings__Default: ${DB_CONNECTION}
    healthcheck:
      test: ["CMD", "curl", "--fail", "http://localhost:8080/health"]
      interval: 30s
      timeout: 5s
      retries: 3

نام واقعی image را مطابق نام مخزن خود تغییر دهید. فایل .env سرور نیز باید شامل اطلاعات محیط باشد و هرگز وارد Git نشود:

DB_CONNECTION=Server=db;Database=Sample;User Id=app;Password=CHANGE_ME
image پایه ASP.NET ممکن است ابزار curl نداشته باشد. یا curl را در image نصب کنید، یا health check را بیرون container با ابزار موجود انجام دهید. وجود endpoint به‌تنهایی کافی نیست؛ دستور health check باید واقعاً در image اجرا شود.

دسترسی سرور به GHCR خصوصی

اگر package خصوصی است، یک GitHub Personal Access Token با حداقل دسترسی read:packages بسازید و یک بار روی سرور login کنید:

echo "YOUR_READ_ONLY_TOKEN" | docker login ghcr.io \
  -u YOUR_GITHUB_USERNAME --password-stdin

اگر package عمومی باشد، معمولاً می‌توان بدون login آن را pull کرد.

۹. استقرار خودکار روی سرور

در GitHub به مسیر Settings → Secrets and variables → Actions بروید و این Secretها را بسازید:

ناممقدار
VPS_HOSTIP یا دامنه سرور
VPS_USERمثلاً deploy
VPS_SSH_KEYمحتوای کامل کلید خصوصی
VPS_KNOWN_HOSTSاثر انگشت SSH سرور

مقدار known hosts را از یک سیستم مورداعتماد بگیرید و fingerprint را بررسی کنید:

ssh-keyscan -H your-server.example.com

Job استقرار را به انتهای Workflow اضافه کنید:

  deploy:
    name: Deploy to production
    runs-on: ubuntu-latest
    needs: publish-image
    environment: production

    steps:
      - name: Configure SSH
        env:
          SSH_KEY: ${{ secrets.VPS_SSH_KEY }}
          KNOWN_HOSTS: ${{ secrets.VPS_KNOWN_HOSTS }}
        run: |
          install -m 700 -d ~/.ssh
          printf '%s\n' "$SSH_KEY" > ~/.ssh/deploy_key
          chmod 600 ~/.ssh/deploy_key
          printf '%s\n' "$KNOWN_HOSTS" > ~/.ssh/known_hosts

      - name: Pull and start the new version
        env:
          VPS_HOST: ${{ secrets.VPS_HOST }}
          VPS_USER: ${{ secrets.VPS_USER }}
        run: |
          IMAGE_TAG="sha-${GITHUB_SHA::7}"
          ssh -i ~/.ssh/deploy_key "$VPS_USER@$VPS_HOST" \
            "cd /opt/sample-api && \
             IMAGE_TAG=$IMAGE_TAG docker compose pull && \
             IMAGE_TAG=$IMAGE_TAG docker compose up -d --remove-orphans"

      - name: Verify deployment
        env:
          VPS_HOST: ${{ secrets.VPS_HOST }}
        run: |
          for attempt in {1..10}; do
            if curl --fail --silent "https://$VPS_HOST/health"; then
              exit 0
            fi
            sleep 6
          done
          echo "Health check failed"
          exit 1

Environment و تأیید دستی

در GitHub یک Environment به نام production بسازید. می‌توانید Required Reviewer تعیین کنید. در این حالت Pipeline بعد از ساخت image متوقف می‌شود و فقط پس از تأیید فرد مسئول، deploy را انجام می‌دهد؛ این همان Continuous Delivery است.

بهتر است قبل از Production یک Environment به نام staging داشته باشید، نسخه را آنجا deploy و smoke test کنید، سپس همان image بدون build مجدد وارد Production شود.

۱۰. Secrets و امنیت Pipeline

نگه‌داری امن Secretها و دسترسی حداقلی Runner به Registry و سرور
Secret باید فقط هنگام اجرا و با کمترین سطح دسترسی در اختیار Runner قرار بگیرد؛ نه داخل کد، image یا log.
  • رمز، token، connection string و private key را داخل YAML یا مخزن commit نکنید.
  • اصل Least Privilege را رعایت کنید؛ هر Job فقط دسترسی لازم را داشته باشد.
  • Secret را با echo چاپ نکنید. مخفی‌سازی خودکار GitHub مفید است، اما کامل و همیشه قابل‌اعتماد نیست.
  • Workflow مربوط به کد Pull Requestهای ناشناس نباید به Secretهای Production دسترسی داشته باشد.
  • اکشن‌های شخص ثالث را بررسی و ترجیحاً به commit SHA معتبر pin کنید تا تغییر ناگهانی آن‌ها به Pipeline آسیب نزند.
  • SSH key مخصوص Pipeline باشد تا بتوانید بدون تأثیر بر کاربران دیگر آن را تعویض یا لغو کنید.
  • Secretها را دوره‌ای rotate کنید و دسترسی اعضای تیم به Environment را بازبینی کنید.
  • Dependency و container image را از نظر آسیب‌پذیری اسکن کنید.
اگر پلتفرم شما پشتیبانی می‌کند، احراز هویت کوتاه‌عمر مبتنی بر OIDC از نگه‌داری credential دائمی بهتر است.

۱۱. Rollback و مهاجرت دیتابیس

بازگشت از نسخه خراب به نسخه سالم و مقایسه Rolling، Blue Green و Canary Deployment
اگر health check نسخه جدید شکست خورد، ترافیک باید سریع به نسخه سالم برگردد. Rolling، Blue/Green و Canary سه راهبرد رایج انتشار هستند.

Rollback برنامه

چون برای هر commit یک image جدا با tag مبتنی بر SHA داریم، بازگشت به نسخه قبلی ساده‌تر است:

cd /opt/sample-api
IMAGE_TAG=sha-PREVIOUS docker compose pull
IMAGE_TAG=sha-PREVIOUS docker compose up -d

شماره image موفق قبلی را در لاگ Pipeline یا GHCR پیدا کنید. بهتر است Pipeline نسخه deployشده، زمان و commit را در یک deployment log ثبت کند.

Migration دیتابیس

دیتابیس سخت‌ترین بخش rollback است. اگر نسخه جدید ستونی را حذف کند، برگشت کد قبلی ممکن است دیگر امکان‌پذیر نباشد. از روش Expand and Contract استفاده کنید:

  1. Expand: ستون یا جدول جدید را اضافه کنید، بدون حذف ساختار قدیمی.
  2. نسخه‌ای منتشر کنید که با ساختار قدیم و جدید سازگار باشد.
  3. داده‌ها را منتقل و صحت آن‌ها را بررسی کنید.
  4. Contract: در انتشار بعدی و پس از اطمینان، ساختار قدیمی را حذف کنید.
اجرای خودکار migration هنگام startup برای چند instance می‌تواند race condition یا downtime ایجاد کند. در سیستم جدی، migration را یک Job جدا، کنترل‌شده و قبل از بالا آمدن نسخه جدید اجرا کنید؛ قبل از تغییر مخرب نیز backup و برنامه بازگشت داشته باشید.

۱۲. بهترین روش‌ها برای Pipeline واقعی

  • Build once, deploy many: همان image تست‌شده در Staging را به Production ببرید؛ دوباره build نکنید.
  • Fast feedback: تست‌های سریع را اول اجرا کنید و مراحل سنگین را بعد از آن قرار دهید.
  • Immutable artifacts: image منتشرشده را تغییر ندهید؛ نسخه جدید، tag یا digest جدید داشته باشد.
  • Quality gates: شکست تست، lint، اسکن امنیتی یا حداقل coverage باید جلوی merge را بگیرد.
  • Cache: کش NuGet و لایه‌های Docker سرعت را زیاد می‌کند، اما نباید جایگزین restore واقعی شود یا صحت build را زیر سؤال ببرد.
  • Concurrency: اجازه ندهید دو deploy هم‌زمان Production را تغییر دهند.
  • Timeout: برای Jobها timeout بگذارید تا Runner گیرکرده بی‌نهایت منتظر نماند.
  • Observability: بعد از deploy فقط به سبز شدن Pipeline اکتفا نکنید؛ log، metric، error rate و latency را مانیتور کنید.
  • Notification: شکست Production باید از طریق Slack، Email یا ابزار مانیتورینگ اطلاع داده شود.
  • Runbook: مراحل rollback و مدیریت incident را مستند و دوره‌ای تمرین کنید.

جلوگیری از deploy هم‌زمان

این تنظیم را می‌توان در سطح Workflow اضافه کرد:

concurrency:
  group: production
  cancel-in-progress: false

با این کار deploy بعدی منتظر قبلی می‌ماند. برای CI مربوط به Pull Request می‌توان گروه جدا تعریف و اجرای قدیمی را لغو کرد.

روش‌های انتشار بدون قطعی

روشایدهکاربرد
Rollinginstanceها به‌تدریج تعویض می‌شوند.روش رایج در Kubernetes و چند replica.
Blue/Greenنسخه قدیم و جدید هم‌زمان هستند؛ ترافیک یک‌باره جابه‌جا می‌شود.rollback سریع، با هزینه منابع بیشتر.
Canaryابتدا درصد کمی از کاربران نسخه جدید را می‌بینند.کاهش ریسک تغییرات بزرگ.

مثال ساده Docker Compose این آموزش ممکن است هنگام تعویض container چند ثانیه قطعی داشته باشد. برای zero-downtime واقعی به چند instance، load balancer و health-aware rollout نیاز دارید.

۱۳. خطاهای رایج

مشکلعلت احتمالی و راه‌حل
روی سیستم من کار می‌کند، در CI نهنسخه SDK، سیستم‌عامل یا فایل‌های commit‌نشده متفاوت است. نسخه‌ها را ثابت و محیط را تکرارپذیر کنید.
Workflow اجرا نمی‌شودمسیر فایل باید دقیقاً داخل .github/workflows باشد و trigger با نام branch هماهنگ باشد.
push به GHCR با 403 شکست می‌خوردpackages: write یا تنظیم Workflow permissions وجود ندارد.
سرور image را pull نمی‌کندنام image یا tag اشتباه است، package خصوصی است یا login سرور منقضی شده است.
SSH با Permission denied شکست می‌خوردکلید عمومی در authorized_keys نیست، user اشتباه است یا permission فایل‌ها مناسب نیست.
Deploy سبز است ولی برنامه خراب استفقط دستور start اجرا شده و health/smoke test وجود ندارد.
Secret خالی استنام Secret اشتباه است یا Workflow مربوط به fork اجازه دریافت Secret ندارد.
نسخه اشتباه deploy می‌شوداستفاده از latest و cache ممکن است ابهام ایجاد کند؛ tag مبتنی بر SHA یا digest استفاده کنید.

۱۴. جمع‌بندی

  • CI کد را مرتب build و test می‌کند تا خطا زود پیدا شود.
  • Continuous Delivery نسخه را آماده نگه می‌دارد ولی Production تأیید دستی می‌خواهد.
  • Continuous Deployment نسخه موفق را خودکار وارد Production می‌کند.
  • Pipeline نمونه ما پروژه .NET را تست، Docker image را در GHCR منتشر و با SSH روی سرور deploy می‌کند.
  • Secrets، دسترسی حداقلی، image تغییرناپذیر، health check و rollback بخش اصلی راه‌حل هستند؛ نه امکانات جانبی.
  • CI/CD خوب فقط انتشار را سریع نمی‌کند؛ انتشار را قابل‌تکرار، قابل‌ردیابی و کم‌ریسک‌تر می‌کند.

با یک Pipeline کوچک شروع کنید: build و test. سپس به‌تدریج انتشار image، Staging، تأیید Production و مانیتورینگ را اضافه کنید.