Цели модуля

После модуля студент сможет:

  • Проектировать CI/CD pipeline для Go-сервисов
  • Настраивать test pipeline с unit, integration, e2e тестами
  • Интегрировать security scanning в CI
  • Управлять build artifacts и версионированием
  • Контейнеризировать сервисы для production
  • Автоматизировать инфраструктуру через Terraform
  • Управлять конфигурацией через Ansible
  • Реализовывать deployment automation
  • Понимать принципы GitOps

10.1 CI/CD pipeline design

Что такое CI/CD

CI (Continuous Integration):

  • Разработчики часто мержат код в main
  • Каждый merge запускает автоматические проверки
  • Проблемы обнаруживаются рано

CD (Continuous Delivery / Deployment):

  • Delivery: код всегда готов к деплою, деплой вручную
  • Deployment: каждый merge автоматически деплоится

Принципы хорошего pipeline

  1. Быстрая обратная связь

    • Unit тесты: < 5 минут
    • Линтеры: < 2 минуты
    • Полные тесты: < 15 минут
  2. Fail fast

    • Сначала быстрые проверки (lint, unit)
    • Потом долгие (integration, e2e)
    • Если быстрая проверка упала — долгие не запускаем
  3. Детерминизм

    • Одинаковый результат при одинаковом коде
    • Фиксированные версии зависимостей
    • Изолированное окружение
  4. Кэширование

    • Go modules cache
    • Docker layers cache
    • Test results cache

Этапы pipeline

text
Commit -> Lint -> Unit Tests -> Build -> Integration Tests
    -> Security Scan -> Docker Build -> Push to Registry
    -> Deploy Staging -> E2E Tests -> Approval
    -> Deploy Production

Пример GitHub Actions workflow

yaml
name: CI/CD

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

env:
  GO_VERSION: "1.23"
  REGISTRY: registry.example.com
  IMAGE_NAME: git-server

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v5
        with:
          go-version: ${{ env.GO_VERSION }}
      - name: golangci-lint
        uses: golangci/golangci-lint-action@v4
        with:
          version: latest

  test:
    runs-on: ubuntu-latest
    needs: lint
    services:
      postgres:
        image: postgres:16-alpine
        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-go@v5
        with:
          go-version: ${{ env.GO_VERSION }}
      - name: Run unit tests
        run: go test -race -coverprofile=coverage.out ./...
      - name: Run integration tests
        run: go test -tags=integration ./...
        env:
          TEST_DATABASE_DSN: postgres://test:test@localhost:5432/testdb?sslmode=disable
      - name: Upload coverage
        uses: codecov/codecov-action@v4
        with:
          file: coverage.out

  build:
    runs-on: ubuntu-latest
    needs: test
    steps:
      - uses: actions/checkout@v4
      - name: Build binary
        run: |
          CGO_ENABLED=0 GOOS=linux go build \
            -ldflags="-w -s -X main.version=${{ github.sha }}" \
            -o bin/git-server ./cmd/app
      - name: Upload artifact
        uses: actions/upload-artifact@v4
        with:
          name: git-server
          path: bin/git-server

Разделение workflows

Для разных событий — разные workflows:

.github/workflows/
├── ci.yml              # Lint, test, build (на каждый PR)
├── security.yml        # Security scanning (ежедневно + на PR)
├── deploy-staging.yml  # Деплой в staging (на merge в main)
├── deploy-prod.yml     # Деплой в production (по тегу)
├── dr-test.yml         # DR тесты (еженедельно)
└── e2e.yml             # E2E тесты (на staging)

10.2 Test pipeline

Пирамида тестирования

    /    E2E Tests    \        <- Мало, медленные
   /-------------------\
  /  Integration Tests  \      <- Средне
 /-----------------------\
/      Unit Tests         \    <- Много, быстрые
ТипСкоростьПокрытиеЗависимости
UnitМиллисекундыФункции, методыMock'и
IntegrationСекундыВзаимодействие компонентовТестовая БД
E2EМинутыПолные сценарииРеальное окружение

Unit tests в CI

yaml
- name: Run unit tests
  run: go test -race -cover ./internal/...

Флаги:

  • -race: race detector
  • -cover: покрытие кода
  • -short: пропускать долгие тесты

Integration tests с testcontainers

go
// internal/adapters/postgres/repo_repository_integration_test.go
//go:build integration

func TestRepoRepository_Integration(t *testing.T) {
    ctx := context.Background()

    // Запуск PostgreSQL в Docker
    container, err := postgres.Run(ctx,
        "postgres:16-alpine",
        postgres.WithDatabase("testdb"),
        postgres.WithUsername("test"),
        postgres.WithPassword("test"),
    )
    require.NoError(t, err)
    defer container.Terminate(ctx)

    dsn, err := container.ConnectionString(ctx)
    require.NoError(t, err)

    pool, err := pgxpool.New(ctx, dsn)
    require.NoError(t, err)
    defer pool.Close()

    // Применение миграций
    db := stdlib.OpenDBFromPool(pool)
    goose.SetBaseFS(migrations.FS)
    goose.Up(db, ".")

    // Тесты
    repo := postgres.NewRepoRepository(pool)
    // ...
}

Запуск в CI:

yaml
- name: Run integration tests
  run: go test -tags=integration -race ./internal/adapters/...

E2E tests

go
// cmd/e2e/main.go
func main() {
    baseURL := os.Getenv("E2E_BASE_URL")

    // Тест: создание репозитория
    resp := createRepo(baseURL, CreateRepoRequest{
        Name:    "e2e-test-repo",
        OwnerID: "user-123",
    })
    assertStatus(resp, 201)

    // Тест: получение репозитория
    repo := getRepo(baseURL, resp.ID)
    assertEqual(repo.Name, "e2e-test-repo")

    // Тест: удаление репозитория
    deleteRepo(baseURL, resp.ID)
    assertStatus(resp, 204)
}

Запуск в CI после деплоя в staging:

yaml
- name: Run E2E tests
  run: go run ./cmd/e2e
  env:
    E2E_BASE_URL: https://staging.example.com

Test strategy для API

Что тестируемТип тестаГде
Domain modelsUnitВсегда
ServicesUnit с mocksВсегда
Use casesUnit с mocksВсегда
HandlersIntegration с httptestВсегда
RepositoriesIntegration с testcontainersНа PR
Полный APIE2EПосле деплоя в staging

10.3 Security scanning в CI

Инструменты security scanning

ИнструментЧто проверяет
gosecСтатический анализ Go кода
govulncheckИзвестные уязвимости в зависимостях
TrivyУязвимости в Docker образах и файловой системе
SemgrepКастомные правила безопасности
gitleaksСекреты в git истории

gosec: статический анализ

bash
# Установка
go install github.com/securego/gosec/v2/cmd/gosec@latest

# Запуск
gosec ./...

Пример вывода:

[gosec] G101: Potential hardcoded credentials
--> internal/config/config.go:25
const defaultPassword = "secret123"

govulncheck: уязвимости в зависимостях

bash
# Установка
go install golang.org/x/vuln/cmd/govulncheck@latest

# Запуск
govulncheck ./...

Пример вывода:

Vulnerability #1: GO-2024-0001
Found in: golang.org/x/net@v0.1.0
Fixed in: golang.org/x/net@v0.7.0
HTTP/2 rapid reset attack

Trivy: сканирование Docker образов

bash
# Сканирование образа
trivy image --severity HIGH,CRITICAL myapp:latest

# Сканирование файловой системы
trivy fs --security-checks vuln ./

# Фейл при наличии CRITICAL
trivy image --exit-code 1 --severity CRITICAL myapp:latest

gitleaks: секреты в git

bash
# Установка
brew install gitleaks

# Запуск
gitleaks detect --source . -v

Находит:

  • Захардкоженные пароли
  • API ключи
  • Приватные ключи
  • Токены

Security workflow в GitHub Actions

yaml
name: Security

on:
  push:
    branches: [main]
  pull_request:
  schedule:
    - cron: '0 2 * * *'  # Ежедневно в 2:00

jobs:
  gosec:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v5
        with:
          go-version: "1.23"
      - name: Run gosec
        run: gosec ./...

  govulncheck:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v5
        with:
          go-version: "1.23"
      - name: Run govulncheck
        run: |
          go install golang.org/x/vuln/cmd/govulncheck@latest
          govulncheck ./...

  trivy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Build image
        run: docker build -t myapp:${{ github.sha }} .
      - name: Scan with Trivy
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: myapp:${{ github.sha }}
          format: table
          exit-code: 1
          severity: CRITICAL,HIGH

  gitleaks:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - name: Run gitleaks
        uses: gitleaks/gitleaks-action@v2

Policy для security findings

SeverityДействие
CRITICALБлокирует merge/deploy, фикс немедленно
HIGHБлокирует merge, фикс в текущем спринте
MEDIUMФикс в следующем спринте
LOWПо возможности

10.4 Build artifacts и versioning

Что такое build artifact

Артефакт сборки — результат CI который можно задеплоить:

  • Бинарник (для VDS)
  • Docker образ (для Kubernetes)
  • Пакет (deb, rpm)

Версионирование

Semantic Versioning (SemVer):

MAJOR.MINOR.PATCH
|      |     |
|      |     +-- Bug fixes (backward compatible)
|      +-------- New features (backward compatible)
+--------------- Breaking changes

Примеры:

  • 1.0.0: первый релиз
  • 1.1.0: добавлена новая фича
  • 1.1.1: исправлен баг
  • 2.0.0: breaking change

Version injection при сборке

bash
# Inject version через ldflags
VERSION=$(git describe --tags --always)
COMMIT=$(git rev-parse HEAD)
BUILD_TIME=$(date -u +%Y-%m-%dT%H:%M:%SZ)

go build -ldflags="
  -X main.version=$VERSION
  -X main.commit=$COMMIT
  -X main.buildTime=$BUILD_TIME
" -o bin/git-server ./cmd/app

В коде:

go
package main

var (
    version   = "dev"
    commit    = "unknown"
    buildTime = "unknown"
)

func main() {
    if len(os.Args) > 1 && os.Args[1] == "version" {
        fmt.Printf("version: %s\n", version)
        fmt.Printf("commit: %s\n", commit)
        fmt.Printf("build time: %s\n", buildTime)
        return
    }
    // ...
}

Docker image tagging

bash
# Build с тегами
docker build -t $REGISTRY/$IMAGE_NAME:$VERSION .
docker build -t $REGISTRY/$IMAGE_NAME:$COMMIT .
docker build -t $REGISTRY/$IMAGE_NAME:latest .

# Push всех тегов
docker push $REGISTRY/$IMAGE_NAME:$VERSION
docker push $REGISTRY/$IMAGE_NAME:$COMMIT
docker push $REGISTRY/$IMAGE_NAME:latest

Правила:

  • Каждый образ имеет уникальный тег (commit SHA или version)
  • latest всегда указывает на последнюю стабильную версию
  • Никогда не перезаписываем существующие теги

Artifact storage

Для бинарников:

  • GitHub Releases
  • S3 bucket
  • Artifact registry (Nexus, Artifactory)

Для Docker образов:

  • Docker Hub
  • GitHub Container Registry
  • AWS ECR, Yandex Container Registry

10.5 Containerization для production

Production-ready Dockerfile

dockerfile
# Stage 1: Builder
FROM golang:1.23-alpine AS builder

RUN apk add --no-cache git ca-certificates

WORKDIR /app

# Кэширование зависимостей
COPY go.mod go.sum ./
RUN go mod download

# Копирование исходников
COPY . .

# Сборка с version injection
ARG VERSION=dev
ARG COMMIT=unknown
RUN CGO_ENABLED=0 GOOS=linux go build \
    -ldflags="-w -s -X main.version=$VERSION -X main.commit=$COMMIT" \
    -o /app/server \
    ./cmd/app

# Stage 2: Runtime
FROM alpine:3.19

RUN apk add --no-cache ca-certificates tzdata

# Non-root пользователь
RUN adduser -D -u 1000 appuser

# Копирование бинарника и конфигов
COPY --from=builder /app/server /usr/local/bin/server
COPY --from=builder /app/configs /etc/app

USER appuser

EXPOSE 8080

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD wget --no-verbose --tries=1 --spider http://localhost:8080/healthz || exit 1

ENTRYPOINT ["server"]
CMD ["serve"]

Multi-stage для разных целей

dockerfile
# Development: с отладчиком
FROM builder AS dev
RUN go install github.com/go-delve/delve/cmd/dlv@latest
CMD ["dlv", "exec", "/app/server", "--", "serve"]

# Production: минимальный
FROM alpine:3.19 AS prod
COPY --from=builder /app/server /usr/local/bin/server
CMD ["server", "serve"]

Security best practices

  1. Минимальный базовый образ: alpine или distroless

  2. Non-root пользователь

  3. Фиксированные версии образов

  4. .dockerignore для исключения секретов

  5. Сканирование через Trivy

  6. Подпись образов через cosign

    Подпись образа

    cosign sign --key cosign.key $REGISTRY/$IMAGE_NAME:$VERSION

    Проверка подписи

    cosign verify --key cosign.pub $REGISTRY/$IMAGE_NAME:$VERSION


10.6 Infrastructure as Code (Terraform)

Что такое IaC

Infrastructure as Code — описание инфраструктуры в коде:

  • Воспроизводимость: одинаковая инфраструктура везде
  • Версионирование: изменения в git
  • Автоматизация: нет ручных операций
  • Документация: код = документация

Структура Terraform проекта

deploy/terraform/
├── main.tf              # Provider и базовые настройки
├── vpc.tf               # Сеть
├── compute.tf           # VDS instances
├── database.tf          # PostgreSQL
├── storage.tf           # Object storage
├── iam.tf               # Service accounts
├── monitoring.tf        # Мониторинг
├── outputs.tf           # Outputs
└── variables.tf         # Variables

Пример: VPC и сеть

hcl
# vpc.tf
resource "yandex_vpc_network" "main" {
  name = "main-network"
}

resource "yandex_vpc_subnet" "app" {
  name           = "app-subnet"
  zone           = "ru-central1-a"
  network_id     = yandex_vpc_network.main.id
  v4_cidr_blocks = ["10.0.1.0/24"]
}

resource "yandex_vpc_subnet" "db" {
  name           = "db-subnet"
  zone           = "ru-central1-a"
  network_id     = yandex_vpc_network.main.id
  v4_cidr_blocks = ["10.0.2.0/24"]
}

Пример: VDS instance

hcl
# compute.tf
resource "yandex_compute_instance" "app" {
  name = "app-server"
  zone = "ru-central1-a"

  resources {
    cores  = 2
    memory = 4
  }

  boot_disk {
    initialize_param {
      image_id = "fd81234567890"  # Ubuntu 22.04
      size     = 20
    }
  }

  network_interface {
    subnet_id = yandex_vpc_subnet.app.id
    nat       = true
  }

  metadata = {
    user-data = file("${path.module}/cloud-init/bootstrap.sh")
  }

  labels = {
    environment = var.environment
    service     = "git-server"
  }

}

Пример: PostgreSQL

hcl
# database.tf
resource "yandex_mdb_postgresql_cluster" "main" {
  name        = "main-postgres"
  environment = "PRODUCTION"
  network_id  = yandex_vpc_network.main.id

  resources {
    resource_preset_id = "s2.micro"
    disk_type_id       = "network-ssd"
    disk_size          = 20
  }

  database {
    name  = "gitdb"
    owner = "app"
  }

  user {
    name     = "app"
    password = var.db_password
  }

  host {
    zone      = "ru-central1-a"
    subnet_id = yandex_vpc_subnet.db.id
  }

}

Variables и outputs

hcl
# variables.tf
variable "environment" {
  description = "Environment name"
  type        = string
  default     = "staging"
}

variable "db_password" {
  description = "Database password"
  type        = string
  sensitive   = true
}

# outputs.tf
output "instance_ip" {
  description = "App server IP"
  value       = yandex_compute_instance.app.network_interface[0].nat_ip_address
}

output "db_connection_string" {
  description = "PostgreSQL connection string"
  value       = yandex_mdb_postgresql_cluster.main.connection_string
  sensitive   = true
}

Terraform workflow

bash
# Инициализация
terraform init

# План изменений
terraform plan

# Применение
terraform apply

# Удаление (осторожно!)
terraform destroy

Remote state

Для командной работы используйте remote state:

hcl
terraform {
  backend "s3" {
    bucket = "terraform-state"
    key    = "git-server/terraform.tfstate"
    region = "ru-central1"
  }
}

Или для Yandex Cloud:

hcl
terraform {
  backend "s3" {
    endpoint = "storage.yandexcloud.net"
    bucket   = "terraform-state"
    key      = "git-server/terraform.tfstate"
    region   = "ru-central1"
  }
}

Drift detection

Drift — расхождение между кодом и реальной инфраструктурой.

bash
# Проверка drift
terraform plan -detailed-exitcode

# Exit code 2 = есть изменения (drift)

Автоматическая проверка в CI:

yaml
- name: Check for drift
  run: |
    terraform plan -detailed-exitcode
    if [ $? -eq 2 ]; then
      echo "Drift detected!"
      exit 1
    fi

10.7 Configuration management (Ansible)

Что такое Ansible

Ansible — инструмент управления конфигурацией:

  • Установка пакетов
  • Настройка сервисов
  • Деплой приложений
  • Управление systemd units

Структура Ansible проекта

deploy/ansible/
├── playbook.yml
├── inventory.yml
├── roles/
│   ├── app/
│   │   ├── tasks/main.yml
│   │   ├── templates/
│   │   │   ├── app.service.j2
│   │   │   ├── config.yaml.j2
│   │   └── handlers/main.yml
│   ├── nginx/
│   │   ├── tasks/main.yml
│   │   └── templates/
│   │       └── site.conf.j2
│   └── monitoring/
│       ├── tasks/main.yml
│       └── templates/
│           └── prometheus.yml.j2
└── group_vars/
    └── all.yml

Inventory

yaml
# inventory.yml
all:
  children:
    staging:
      hosts:
        staging-server:
          ansible_host: 10.0.1.10
          ansible_user: deploy
    production:
      hosts:
        prod-server-1:
          ansible_host: 10.0.1.20
          ansible_user: deploy
        prod-server-2:
          ansible_host: 10.0.1.21
          ansible_user: deploy

Playbook

yaml
# playbook.yml
- name: Deploy git-server
  hosts: staging
  become: yes

  vars:
    app_version: "{{ lookup('env', 'APP_VERSION') }}"
    registry: registry.example.com

  roles:
    - app
    - nginx
    - monitoring

### Role: app

```yaml
# roles/app/tasks/main.yml
- name: Create app user
  user:
    name: git-server
    system: yes
    shell: /usr/sbin/nologin

- name: Create app directory
  file:
    path: /opt/git-server
    state: directory
    owner: git-server
    group: git-server

- name: Pull Docker image
  docker_image:
    name: "{{ registry }}/git-server:{{ app_version }}"
    source: pull

- name: Copy config
  template:
    src: config.yaml.j2
    dest: /etc/git-server/config.yaml
    owner: git-server
    mode: "0640"
  notify: restart git-server

- name: Copy systemd unit
  template:
    src: app.service.j2
    dest: /etc/systemd/system/git-server.service
  notify:
    - reload systemd
    - restart git-server

- name: Enable and start service
  systemd:
    name: git-server
    enabled: yes
    state: started
yaml
# roles/app/handlers/main.yml
- name: reload systemd
  systemd:
    daemon_reload: yes

- name: restart git-server
  systemd:
    name: git-server
    state: restarted

Templates

j2
# roles/app/templates/app.service.j2
[Unit]
Description=Git Server
After=network.target

[Service]
Type=simple
User=git-server
ExecStart=/usr/bin/docker run --rm \
  --name git-server \
  -p 8080:8080 \
  -v /etc/git-server:/etc/app:ro \
  {{ registry }}/git-server:{{ app_version }} serve
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

# roles/app/templates/config.yaml.j2
server:
  http_addr: ":8080"

database:
  dsn: "{{ database_dsn }}"
  max_conns: 25

logging:
  level: "{{ log_level | default('info') }}"
  format: json

Запуск Ansible

bash
# Деплой в staging
ansible-playbook -i inventory.yml playbook.yml --limit staging

# Деплой в production
ansible-playbook -i inventory.yml playbook.yml --limit production

# С проверкой (dry run)
ansible-playbook -i inventory.yml playbook.yml --check --diff

10.8 Deployment strategies automation

Rolling deployment

yaml
# GitHub Actions: deploy to staging
- name: Deploy to staging
  run: |
    ansible-playbook -i inventory.yml playbook.yml --limit staging

- name: Wait for health check
  run: |
    for i in {1..30}; do
      if curl -f https://staging.example.com/healthz; then
        echo "Service is healthy"
        exit 0
      fi
      sleep 10
    done
    echo "Service failed to start"
    exit 1

Canary deployment

yaml
# Шаг 1: Деплой на 1 инстанс
- name: Deploy canary
  run: |
    ansible-playbook -i inventory.yml playbook.yml \
      --limit prod-server-1 \
      -e app_version=$NEW_VERSION

# Шаг 2: Мониторинг 15 минут
- name: Monitor canary
  run: |
    sleep 900
    # Проверяем error rate
    ERROR_RATE=$(curl -s https://prometheus.example.com/api/query?query=... | jq -r '.data.result[0].value[1]')
    if (( $(echo "$ERROR_RATE > 0.05" | bc -l) )); then
      echo "Error rate too high, rolling back"
      exit 1
    fi

# Шаг 3: Деплой на все инстансы
- name: Deploy to all
  run: |
    ansible-playbook -i inventory.yml playbook.yml \
      --limit production \
      -e app_version=$NEW_VERSION

Blue-Green deployment

yaml
# Blue = текущая версия, Green = новая версия

# Шаг 1: Деплой Green
- name: Deploy green
  run: |
    ansible-playbook -i inventory.yml playbook.yml \
      --limit green \
      -e app_version=$NEW_VERSION

# Шаг 2: Health check Green
- name: Health check green
  run: |
    curl -f https://green.example.com/healthz

# Шаг 3: Переключение трафика
- name: Switch traffic
  run: |
    # Обновляем load balancer
    ansible-playbook switch-traffic.yml -e target=green

# Шаг 4: Откат если проблемы
- name: Rollback if needed
  if: failure()
  run: |
    ansible-playbook switch-traffic.yml -e target=blue

Automated rollback

yaml
# Rollback при неудачном health check
- name: Deploy new version
  run: |
    ansible-playbook -i inventory.yml playbook.yml \
      --limit staging \
      -e app_version=$NEW_VERSION
  continue-on-error: true
  id: deploy

- name: Health check
  if: steps.deploy.outcome == 'success'
  run: |
    curl -f https://staging.example.com/healthz
  id: healthcheck

- name: Rollback on failure
  if: steps.healthcheck.outcome == 'failure'
  run: |
    ansible-playbook -i inventory.yml playbook.yml \
      --limit staging \
      -e app_version=$PREVIOUS_VERSION

10.9 GitOps

Принципы GitOps

  1. Declarative: желаемое состояние в git
  2. Versioned: git history = audit log
  3. Pulled: оператор pull'ит изменения
  4. Reconciled: continuous reconciliation

ArgoCD для Kubernetes

yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: git-server
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/org/git-server
    targetRevision: main
    path: deploy/kubernetes
  destination:
    server: https://kubernetes.default.svc
    namespace: production
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true

Flux для Kubernetes

yaml
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
  name: git-server
  namespace: flux-system
spec:
  interval: 1m
  url: https://github.com/org/git-server
  ref:
    branch: main

apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: git-server
  namespace: flux-system
spec:
  interval: 5m
  path: ./deploy/kubernetes
  prune: true
  sourceRef:
    kind: GitRepository
    name: git-server

GitOps workflow

1. Разработчик мержит PR в main
2. CI собирает образ и пушит в registry
3. CI обновляет image tag в git (deploy/kubernetes/deployment.yaml)
4. ArgoCD/Flux видит изменение в git
5. ArgoCD/Flux синхронизирует кластер с git
6. Кластер обновляется автоматически

Преимущества GitOps

  • Аудит: все изменения в git
  • Rollback: git revert
  • Consistency: кластер всегда соответствует git
  • Automation: нет ручных kubectl apply
  • Self-healing: оператор восстанавливает drift

Привязка к шаблону проекта

Где CI/CD в шаблоне

.github/workflows/
├── test.yml              # go test -race
├── security.yml          # govulncheck + Trivy
├── deploy.yml            # terraform apply + drift detection
├── dr-test.yml           # DR тест с git fsck
└── e2e.yml               # E2E тесты

deploy/terraform/
├── main.tf
├── compute.tf
├── database.tf
├── storage.tf
└── ...

deploy/ansible/
├── playbook.yml
├── inventory.yml
└── roles/
    ├── app/
    ├── nginx/
    └── monitoring/

docker/
├── Dockerfile
├── docker-compose.yml
└── docker-compose.prod.yml

Что изучаем на примере шаблона

  1. GitHub Actions workflows для CI/CD
  2. Terraform для инфраструктуры
  3. Ansible для конфигурации
  4. Multi-stage Dockerfile
  5. Health checks и readiness probes

Задание: изучите .github/workflows/ и deploy/

  • Какие workflows существуют?
  • Что делает каждый workflow?
  • Как настроен Terraform?
  • Какие роли в Ansible?

💱 Компромиссы

  • Continuous Delivery vs Continuous Deployment: ручной approval перед production даёт контроль над каждым релизом, но замедляет доставку; автодеплой каждого merge в main быстр, однако баг, не пойманный тестами, сразу едет к пользователям.
  • Много unit vs много e2e: unit-тесты с mocks дёшевы и быстры, но не ловят рассинхрон компонентов; e2e на staging проверяет реальный сценарий целиком, однако платит минутами выполнения и flaky-падениями.
  • Alpine/distroless vs полный образ: минимальный runtime меньше весит и сужает поверхность CVE, но без shell трудно отладить упавший контейнер; полноценный образ удобен для debug, однако тянет лишние уязвимости.
  • Canary vs полный деплой: выкат новой версии на один инстанс с мониторингом ловит проблему на малой доле трафика, но удлиняет релиз; деплой сразу на все инстансы быстрее, однако ошибка бьёт по всем пользователям.
  • GitOps vs push из CI: ArgoCD с selfHeal держит кластер в состоянии git и даёт rollback через git revert, но добавляет оператора и задержку синка; прямой ansible-playbook из CI проще, однако drift серверов никто автоматически не чинит.

Практическое задание

Задание 1: CI pipeline

Создайте GitHub Actions workflow для CI:

yaml
name: CI

on: [push, pull_request]

jobs:
  lint:
    # golangci-lint

  test:
    # go test -race -cover

  build:
    # go build

Добавьте кэширование Go modules.

Задание 2: Security scanning

Добавьте security workflow:

yaml
name: Security

on:
  push:
  schedule:
    - cron: '0 2 * * *'

jobs:
  gosec:
    # gosec ./...

  govulncheck:
    # govulncheck ./...

  trivy:
    # trivy image

Задание 3: Docker build в CI

Добавьте сборку и push Docker образа:

yaml
- name: Build image
  run: docker build -t $REGISTRY/$IMAGE_NAME:${{ github.sha }} .

- name: Scan with Trivy
  run: trivy image --exit-code 1 --severity CRITICAL $REGISTRY/$IMAGE_NAME:${{ github.sha }}

- name: Push to registry
  run: docker push $REGISTRY/$IMAGE_NAME:${{ github.sha }}

Задание 4: Terraform для VDS

Напишите Terraform для:

  • VPC network
  • Subnet
  • VDS instance
  • PostgreSQL
  • Security groups

Примените: terraform plan, terraform apply

Задание 5: Ansible playbook

Напишите Ansible playbook для:

  • Создания app user
  • Копирования бинарника
  • Копирования конфига
  • Настройки systemd unit
  • Запуска сервиса

Примените: ansible-playbook -i inventory.yml playbook.yml

Задание 6: Deployment automation

Создайте deploy workflow:

yaml
name: Deploy

on:
  push:
    branches: [main]

jobs:
  deploy-staging:
    # Build image
    # Deploy to staging
    # Health check
    # Run E2E tests

  deploy-production:
    needs: deploy-staging
    environment: production
    # Deploy to production
    # Health check

Задание 7: Automated rollback

Добавьте автоматический rollback:

yaml
- name: Deploy
  id: deploy
  run: ansible-playbook deploy.yml
  continue-on-error: true

- name: Health check
  id: healthcheck
  run: curl -f https://example.com/healthz

- name: Rollback
  if: steps.healthcheck.outcome == 'failure'
  run: ansible-playbook rollback.yml

Задание 8: Drift detection

Добавьте проверку drift в CI:

yaml
- name: Check for drift
  run: |
    terraform plan -detailed-exitcode
    if [ $? -eq 2 ]; then
      echo "Drift detected!"
      exit 1
    fi

Quiz "Проверь себя"

1. Что такое пирамида тестирования?
2. Как автоматизировать rollback после неудачного деплоя?
yaml
- name: Health check
  id: healthcheck
  run: curl -f https://staging.example.com/healthz
- name: Rollback
  if: steps.healthcheck.outcome == 'failure'
  run: ansible-playbook rollback.yml
3. Что такое Infrastructure as Code?
4. Какие этапы включает CI/CD pipeline для Go-сервиса?
5. Какие deployment-стратегии существуют? — выберите все подходящие
6. Какие инструменты security scanning применяются в CI? — выберите все подходящие
7. Что такое GitOps?
8. Что означает результат этой команды?
text
$ terraform plan -detailed-exitcode
...
Exit code 2
9. Что означает версия 1.2.3 в semantic versioning?
10. Чем Terraform отличается от Ansible?

✅ Итоги модуля

Студент должен:

  • Проектировать CI/CD pipeline для Go-сервисов
  • Настраивать test pipeline с разными типами тестов
  • Интегрировать security scanning
  • Управлять build artifacts и версионированием
  • Контейнеризировать сервисы для production
  • Автоматизировать инфраструктуру через Terraform
  • Управлять конфигурацией через Ansible
  • Реализовывать deployment automation с rollback
  • Понимать принципы GitOps
  • Ответить на все вопросы Quiz

🔗 Мостик к Модулю 11

Следующий модуль: Модуль 11. Architecture Documentation - ADR writing, decision records, runbooks, migration plans, defense of technical decisions, C4 model. Привязка к docs/adr/ шаблона.