HardПрактика3 min

CI/CD с GitHub Actions

Полный пайплайн: Build, Test, Deploy Infrastructure, Deploy App с blue-green деплоем

Автоматизация деплоя с GitHub Actions: от push в main до production через staging-слот.

Обзор пайплайна

Push to main -> Build -> Test -> Deploy Infrastructure -> Deploy App -> Smoke Test -> Swap

Четыре этапа (jobs):

  1. Build & Test -- сборка, юнит-тесты, создание артефактов
  2. Deploy Infrastructure -- обновление Azure-ресурсов через Bicep
  3. Deploy App -- деплой в staging-слот + smoke test + swap
  4. Deploy Functions -- деплой Azure Functions

Полный workflow

# .github/workflows/ci-cd.yml
name: CI/CD Pipeline

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

env:
  DOTNET_VERSION: '8.0.x'
  AZURE_RESOURCE_GROUP: rg-orderapp-dev
  AZURE_WEBAPP_NAME: orderapp-dev-app

jobs:
  # ============== BUILD & TEST ==============
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup .NET
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: ${{ env.DOTNET_VERSION }}

      - name: Restore dependencies
        run: dotnet restore

      - name: Build
        run: dotnet build --no-restore --configuration Release

      - name: Run Unit Tests
        run: |
          dotnet test tests/OrderManagement.Domain.Tests \
            --no-build -c Release --logger trx \
            --results-directory TestResults

      - name: Run Application Tests
        run: |
          dotnet test tests/OrderManagement.Application.Tests \
            --no-build -c Release --logger trx \
            --results-directory TestResults

      - name: Upload Test Results
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: test-results
          path: TestResults/*.trx

      - name: Publish API
        run: dotnet publish src/OrderManagement.API -c Release -o ./publish/api

      - name: Publish Functions
        run: dotnet publish src/OrderManagement.Functions -c Release -o ./publish/functions

      - name: Upload API artifact
        uses: actions/upload-artifact@v4
        with:
          name: api
          path: ./publish/api

      - name: Upload Functions artifact
        uses: actions/upload-artifact@v4
        with:
          name: functions
          path: ./publish/functions

  # ============== DEPLOY INFRASTRUCTURE ==============
  deploy-infra:
    needs: build
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Azure Login
        uses: azure/login@v2
        with:
          creds: ${{ secrets.AZURE_CREDENTIALS }}

      - name: Deploy Bicep
        uses: azure/arm-deploy@v2
        with:
          resourceGroupName: ${{ env.AZURE_RESOURCE_GROUP }}
          template: ./infra/main.bicep
          parameters: environment=dev

  # ============== DEPLOY APPLICATION ==============
  deploy-app:
    needs: [build, deploy-infra]
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    environment: production
    steps:
      - name: Download API artifact
        uses: actions/download-artifact@v4
        with:
          name: api
          path: ./api

      - name: Azure Login
        uses: azure/login@v2
        with:
          creds: ${{ secrets.AZURE_CREDENTIALS }}

      - name: Deploy to staging slot
        uses: azure/webapps-deploy@v3
        with:
          app-name: ${{ env.AZURE_WEBAPP_NAME }}
          slot-name: staging
          package: ./api

      - name: Smoke test staging
        run: |
          sleep 30
          response=$(curl -s -o /dev/null -w "%{http_code}" \
            https://${{ env.AZURE_WEBAPP_NAME }}-staging.azurewebsites.net/health)
          if [ "$response" != "200" ]; then
            echo "Smoke test failed with status $response"
            exit 1
          fi

      - name: Swap staging to production
        run: |
          az webapp deployment slot swap \
            --name ${{ env.AZURE_WEBAPP_NAME }} \
            --resource-group ${{ env.AZURE_RESOURCE_GROUP }} \
            --slot staging \
            --target-slot production

  # ============== DEPLOY FUNCTIONS ==============
  deploy-functions:
    needs: [build, deploy-infra]
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - name: Download Functions artifact
        uses: actions/download-artifact@v4
        with:
          name: functions
          path: ./functions

      - name: Azure Login
        uses: azure/login@v2
        with:
          creds: ${{ secrets.AZURE_CREDENTIALS }}

      - name: Deploy Azure Functions
        uses: azure/functions-action@v2
        with:
          app-name: func-orderapp-dev
          package: ./functions

Настройка секретов GitHub

# Создание Service Principal для GitHub
az ad sp create-for-rbac \
  --name "github-orderapp" \
  --role contributor \
  --scopes /subscriptions/{sub-id}/resourceGroups/rg-orderapp-dev \
  --json-auth

# Вывод (JSON) -> GitHub Settings -> Secrets -> AZURE_CREDENTIALS

Стратегии деплоя

Стратегия Описание Риск Rollback
Blue-Green (наш подход) Staging -> Swap Низкий Мгновенный (swap обратно)
Canary Постепенный перевод трафика Средний Переключение трафика
Rolling Обновление по одному экземпляру Средний Сложный
Recreate Остановка старой, запуск новой Высокий Деплой предыдущей версии

Rollback

# Мгновенный rollback через swap обратно
az webapp deployment slot swap \
  --name orderapp-dev-app \
  --resource-group rg-orderapp-dev \
  --slot staging \
  --target-slot production

# Или деплой конкретной версии
az webapp deployment source config-zip \
  --resource-group rg-orderapp-dev \
  --name orderapp-dev-app \
  --src previous-version.zip

Проверь себя

В каком порядке выполняются jobs в нашем CI/CD пайплайне?

Что нужно сохранить в GitHub Secrets для аутентификации в Azure?

Для чего используется smoke test в пайплайне?