HardПрактика4 min

CI/CD пайплайны

GitHub Actions для автоматического тестирования и деплоя CRC на AWS, Azure и GCP

Зачем CI/CD

CI/CD (Continuous Integration / Continuous Deployment) -- автоматизация процесса: код попадает в репозиторий, тесты запускаются автоматически, и при успехе код деплоится в облако без ручных шагов.

Без CI/CD ваш процесс деплоя CRC выглядит так:

  1. Изменить код
  2. Запустить тесты вручную
  3. Собрать пакет вручную
  4. Загрузить в S3/Lambda вручную
  5. Инвалидировать CloudFront вручную

С CI/CD: git push -- и всё происходит автоматически.

GitHub Actions: основы

Структура workflow

# .github/workflows/deploy.yml
name: Deploy CRC

on:
  push:
    branches: [main]  # Запуск при пуше в main

jobs:
  test:              # Название job
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4    # Получить код
      - name: Run tests              # Шаг
        run: python -m pytest tests/

  deploy:
    needs: test       # Запускается только после test
    runs-on: ubuntu-latest
    steps:
      - name: Deploy
        run: echo "deploying..."

Ключевые концепции

Концепция Описание
Workflow YAML-файл с описанием пайплайна
Trigger Событие, запускающее workflow (push, PR, schedule)
Job Набор шагов, выполняемых на одной машине
Step Отдельная команда или action
Secret Зашифрованные переменные (ключи, пароли)
Environment Окружение с approvals (dev, staging, prod)

Полный CI/CD для AWS CRC

Backend Pipeline

# .github/workflows/backend.yml
name: Backend CI/CD

on:
  push:
    branches: [main]
    paths: ['backend/**', 'tests/**']

permissions:
  id-token: write
  contents: read

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-python@v5
        with:
          python-version: '3.12'

      - name: Install dependencies
        run: |
          cd backend
          pip install -r requirements.txt
          pip install pytest moto boto3

      - name: Run tests
        run: python -m pytest tests/ -v

  deploy:
    needs: test
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v4

      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
          aws-region: us-east-1

      - uses: actions/setup-python@v5
        with:
          python-version: '3.12'

      - name: SAM Build & Deploy
        run: |
          pip install aws-sam-cli
          sam build
          sam deploy --no-confirm-changeset --no-fail-on-empty-changeset

Frontend Pipeline

# .github/workflows/frontend.yml
name: Frontend CI/CD

on:
  push:
    branches: [main]
    paths: ['frontend/**']

permissions:
  id-token: write
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v4

      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
          aws-region: us-east-1

      - name: Deploy to S3
        run: |
          aws s3 sync frontend/ s3://${{ secrets.S3_BUCKET }}/ \
            --delete \
            --cache-control "max-age=3600"

      - name: Invalidate CloudFront
        run: |
          aws cloudfront create-invalidation \
            --distribution-id ${{ secrets.CF_DISTRIBUTION_ID }} \
            --paths "/*"

CI/CD для Azure

# .github/workflows/azure.yml
name: Azure CRC Deploy

on:
  push:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'
      - run: |
          pip install -r requirements.txt
          python -m pytest tests/ -v

  deploy-backend:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

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

      - uses: azure/functions-action@v1
        with:
          app-name: ${{ secrets.AZURE_FUNCTION_NAME }}
          package: './backend'

  deploy-frontend:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

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

      - name: Upload to Blob Storage
        run: |
          az storage blob upload-batch \
            --account-name ${{ secrets.STORAGE_ACCOUNT }} \
            --source frontend/ \
            --destination '$web' \
            --overwrite

      - name: Purge CDN
        run: |
          az cdn endpoint purge \
            --resource-group crc-resources \
            --profile-name ${{ secrets.CDN_PROFILE }} \
            --name ${{ secrets.CDN_ENDPOINT }} \
            --content-paths "/*"

CI/CD для GCP

# .github/workflows/gcp.yml
name: GCP CRC Deploy

on:
  push:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.12'
      - run: |
          pip install -r requirements.txt
          python -m pytest tests/ -v

  deploy-backend:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: google-github-actions/auth@v2
        with:
          credentials_json: ${{ secrets.GCP_CREDENTIALS }}

      - uses: google-github-actions/setup-gcloud@v2

      - name: Deploy Cloud Function
        run: |
          gcloud functions deploy visitor-counter \
            --gen2 \
            --runtime=python312 \
            --trigger-http \
            --allow-unauthenticated \
            --entry-point=visitor_counter \
            --source=backend/ \
            --region=us-central1

  deploy-frontend:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: google-github-actions/auth@v2
        with:
          credentials_json: ${{ secrets.GCP_CREDENTIALS }}

      - uses: google-github-actions/setup-gcloud@v2

      - name: Upload to Cloud Storage
        run: gsutil -m rsync -d -r frontend/ gs://${{ secrets.GCS_BUCKET }}/

OIDC: безопасная аутентификация без секретов

Вместо хранения long-lived credentials в GitHub Secrets используйте OIDC (OpenID Connect):

# AWS OIDC - рекомендуемый подход
permissions:
  id-token: write
  contents: read

steps:
  - uses: aws-actions/configure-aws-credentials@v4
    with:
      role-to-assume: arn:aws:iam::123456789012:role/github-actions-role
      aws-region: us-east-1
      # Нет access key/secret key!
      # GitHub получает временные credentials через OIDC

Secrets Management

# Добавление секретов в GitHub
# Settings → Secrets and variables → Actions → New repository secret

# Необходимые секреты для AWS:
# AWS_ROLE_ARN (для OIDC) или AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY
# S3_BUCKET
# CF_DISTRIBUTION_ID

# Для Azure:
# AZURE_CREDENTIALS (JSON с service principal)
# STORAGE_ACCOUNT
# AZURE_FUNCTION_NAME

# Для GCP:
# GCP_CREDENTIALS (service account JSON)
# GCS_BUCKET

Проверь себя

Что делает 'paths' фильтр в GitHub Actions trigger?

Зачем используется 'needs: test' в deploy job?

Зачем после деплоя фронтенда в S3 нужно инвалидировать CloudFront?

Почему OIDC предпочтительнее long-lived access keys для CI/CD?

Что такое CI/CD в контексте CRC?