Go to file
2026-08-05 18:13:32 +03:00
.gitea/workflows ++ add STACKS filter bootstrap escape hatch for cross-stack read dependencies 2026-08-05 18:13:32 +03:00
live ++ fall back to in-cluster kubernetes auth when KUBECONFIG file doesn't exist 2026-08-05 18:02:44 +03:00
modules ++ add declarative secrets contract v2 (schema/ownership/targets/extra_fields) 2026-08-05 12:41:55 +03:00
scripts ++ add STACKS filter bootstrap escape hatch for cross-stack read dependencies 2026-08-05 18:13:32 +03:00
test-app ++ add secrets-contract-probe test app for brusnika-stage acceptance test 2026-08-05 12:50:26 +03:00
.gitignore ++ gitignore secrets contract plan 2026-08-05 12:02:03 +03:00
.gitlab-ci.yml ++ aaahhh 2026-07-17 11:08:54 +00:00
.sops.yaml ++ wrap all vault secret values under data key for selective sops encryption 2026-08-04 15:20:08 +03:00
Dockerfile ++ port sops secret-values adopt mechanism to prod 2026-07-06 17:10:46 +03:00
infrastructure-secrets.yaml ++ 2026-08-05 19:59:52 +05:00
infrastructure.yaml ++ add declarative secrets contract v2 (schema/ownership/targets/extra_fields) 2026-08-05 12:41:55 +03:00
README.md add regcred namespace propagation 2026-07-20 12:03:50 +03:00
SOPS.md ++ declarative single-file secrets, drop resource_key, sync kafka with cloud 2026-07-09 09:21:02 +03:00

Terraform Infrastructure

Infrastructure as Code для управления ресурсами Yandex Cloud и Kubernetes.

Структура

terraform/
├── infrastructure.yaml     # Конфигурация всех ресурсов (единый источник правды)
├── infrastructure-secrets.yaml # sops-зашифрованные секретные значения
├── .sops.yaml              # правило шифрования
├── SOPS.md                 # как работать с sops и infrastructure-secrets.yaml
├── live/                   # Terragrunt конфигурации по окружениям
│   ├── terragrunt.hcl      # Общие настройки (backend, providers)
│   └── stage/              # Stage окружение
│       ├── env.hcl
│       ├── namespace/
│       ├── database/
│       ├── s3/
│       └── secrets/
└── modules/                # Terraform модули
    ├── k8s-namespace/      # Создание Kubernetes namespaces
    ├── k8s-secret/         # Создание Kubernetes secrets (с зависимостями)
    ├── k8s-secrets/        # Простое создание secrets
    ├── yc-database/        # PostgreSQL users и databases в Yandex Cloud
    ├── yc-valkey-user/     # Valkey/Redis users в Yandex Cloud
    └── yc-s3/              # S3 бакеты с изолированным доступом

Принцип работы

infrastructure.yaml

Единый файл конфигурации для всех ресурсов. Структура:

environments:
  stage:                    # Имя окружения
    namespaces:             # Kubernetes namespaces
      - name: pulse
        labels: {}
        annotations: {}

    buckets:                # S3 бакеты
      - name: pulse-stage
        acl: private
        role: storage.uploader  # опционально
        versioning:
          enabled: false
        cors:
          enabled: true
          allowed_methods: [GET, PUT]

    databases:              # PostgreSQL databases
      - cluster_id: "xxx"
        database:
          name: mydb
          extensions: [pg_stat_statements]
        user:
          name: myuser
          password_length: 32
          conn_limit: 10

    valkey_users:           # Valkey/Redis users
      - cluster_id: "xxx"
        host: rc1a-example.mdb.yandexcloud.net
        user:
          name: myuser
        permissions:
          patterns: allkeys
          pubSubChannels: allchannels

    # k8s-секретов здесь НЕТ — они описываются целиком в infrastructure-secrets.yaml
    # (форма + значения, шифруется только data). См. раздел «k8s-secret» ниже.

Terragrunt

Terragrunt читает infrastructure.yaml и передаёт данные в модули:

locals {
  infra_config = yamldecode(file("${local.repo_root}/infrastructure.yaml"))
  env_config   = local.infra_config.environments[local.env_name]
}

inputs = {
  buckets = local.env_config.buckets
}

Порядок применения

Модули имеют зависимости и применяются в порядке:

namespace → database / s3 / valkey-users → secrets

Модули

yc-s3

Создание S3 бакетов с изолированным доступом.

Особенности:

  • Каждый бакет получает отдельный Service Account
  • SA не имеет глобальных IAM ролей на storage на уровне folder
  • Доступ к бакету через IAM binding (yandex_storage_bucket_iam_binding)
  • SA может работать только со своим бакетом

Роли (Yandex Cloud IAM):

  • storage.uploader (default) — загрузка объектов
  • storage.viewer — чтение объектов
  • storage.editor — полный доступ

Пример:

buckets:
  - name: my-bucket
    acl: private
    role: storage.uploader  # опционально

yc-database

Создание PostgreSQL users и databases в существующем кластере.

Особенности:

  • Пароль генерируется автоматически
  • Поддержка extensions
  • Поддержка permissions на другие БД
  • ignore_changes на password

Пример:

databases:
  - cluster_id: "c9qa2coo5ukgcg93fldm"
    database:
      name: mydb
      extensions: [pg_stat_statements]
    user:
      name: myuser
      password_length: 32
      conn_limit: 10
      permissions: [other_db]

yc-valkey-user

Создание Valkey/Redis users в существующем кластере Yandex Cloud.

Особенности:

  • Пароль генерируется автоматически
  • Поддержка ACL permissions
  • ignore_changes на password

Пример:

valkey_users:
  - cluster_id: "c9qa2coo5ukgcg93fldm"
    host: rc1a-example.mdb.yandexcloud.net
    port: "6380"
    user:
      name: myuser
      password_length: 32
    permissions:
      patterns: allkeys
      pubSubChannels: allchannels
      categories: "+@read +@write"
      commands: "+GET -FLUSHALL"
      sanitizePayload: sanitize-payload

k8s-secret

Создаёт Kubernetes secrets. Секреты описываются целиком в одном файле — зашифрованном infrastructure-secrets.yaml (форма + значения). В infrastructure.yaml секретов нет. Ресурс в state адресуется парой namespace/name (поле resource_key удалено).

Каждый секрет описывается в одном из двух режимов (не смешивать):

Режим A — adopt / статический: явная data. Значения заданы прямо (base64, как в k8s .data), шифруется только data. Модуль кладёт их в секрет 1:1 и ничего не вычисляет. Используется для существующих секретов, взятых под terraform, и для секретов со статическими значениями.

- name: my-secret
  namespace: myns
  type: opaque            # kafka|rabbitmq|database|database_url|valkey|s3|yc_sa|dockerconfigjson|opaque
  lifecycle: { ignore_changes: false }
  data:
    username: <base64>
    password: <base64>

Режим B — динамический: depends_on + маппинг ключей. Значения собираются из outputs других юнитов (kafka/rabbitmq/database/valkey/s3/yc_sa), которые создал terragrunt. data НЕ указывается.

- name: my-kafka-secret
  namespace: myns
  type: kafka
  depends_on: { kafka_ref: prod, kafka_user: myservice }   # какой облачный ресурс читать
  credential_keys:            # какие поля источника и под каким ключом положить в секрет
    username: username
    password: password
    host: host
  custom_keys: { ssl: "true" }        # статические несекретные литералы
  constant_keys: { ca.crt: kafka_ca } # значения из общих constants (CA и т.п.)
  random_keys: { token: { length: 32, special: false } }  # сгенерировать random_password

Что означают поля режима B:

  • depends_on — указатель на облачный ресурс-источник кредов. Для kafka — kafka_ref:kafka_user, для rabbitmq — rabbitmq_ref:rabbitmq_vhost:rabbitmq_user, для database — cluster:db:user, для valkey — valkey_cluster:valkey_user, для s3 — bucket, для yc_sa — service_account. Модуль берёт host/port/user/password/… из outputs этого юнита.
  • credential_keys{ ключ_в_секрете: имя_поля_источника }; выбирает/переименовывает поля. Если не задан — берутся все дефолтные поля типа.
  • custom_keys — статические несекретные литералы прямо в секрет.
  • constant_keys — значения из общих constants (kafka_ca, postgres_ca, …).
  • random_keys — генерируемые random_password (токены/сессии).

Типы (type) и дефолтные поля: database (host/port/database/username/password/ca.crt), database_url (postgresql+asyncpg URL), valkey (cert/host/login/password/port/url), kafka/rabbitmq (host/port/user/password/…), s3 (access_key/secret_key/bucket/endpoint), yc_sa (access_key/secret_key/service_account_id/endpoint), dockerconfigjson, opaque.

Как расшифровать/отредактировать секрет: sops infrastructure-secrets.yaml. После правки руками — sops -e -i infrastructure-secrets.yaml. Расшифрованный файл не коммитить.

Как завести секрет после создания kafka-юзера/топиков (или rmq vhost/user): добавить запись в режиме B — type, depends_on на созданный ресурс, credential_keys для нужных полей; data не указывать. terragrunt подставит значения из outputs соответствующего юнита.

Подробнее про sops: SOPS.md.

k8s-namespace

Создание Kubernetes namespaces.

Пример:

namespaces:
  - name: pulse
    manage: true
    labels:
      environment: stage
    annotations:
      managed-by: terraform

manage по умолчанию равен true. Для уже существующего неймспейс укажите manage: false: модуль не будет пытаться создать или изменить его, но запись можно использовать как цель для других стеков.

Раскатка общего regcred в неймспейс

Единый источник учётных данных — environments.<env>.vault.common.regcred.dockerconfigjson в зашифрованном infrastructure-secrets.yaml. Из него независимо работают два направления:

  • vault-платформа записывает значение в Vault, если включён vault.features.create_regcred;
  • стек live/secrets создаёт секрет regcred типа kubernetes.io/dockerconfigjson в каждом неймспейс с image_pull_secret: true.

Для уже существующих неймспейс:

environments:
  yc-k8s-test:
    namespaces:
      - name: django
        manage: false
        image_pull_secret: true
      - name: issues
        manage: false
        image_pull_secret: true

manage: false обязателен, если неймспейс уже существует и не находится в state live/namespace: иначе terraform попытается создать его заново.

Если regcred в целевом неймспейс уже существует, перед первым apply его нужно один раз принять в state стека live/secrets:

cd live/secrets

terragrunt import \
  'kubernetes_secret.without_ignore["django/regcred"]' \
  django/regcred

terragrunt import \
  'kubernetes_secret.without_ignore["issues/regcred"]' \
  issues/regcred

terragrunt plan
terragrunt apply

Если секрета ещё нет, импорт не требуется: apply создаст его. После одноразового импорта дальнейшие изменения общего dockerconfigjson раскатываются обычным apply. Для k8s fan-out включать vault.features.create_regcred не требуется — этот флаг управляет только записью в Vault.

Использование

Локальный запуск

cd terraform/live/stage/s3
terragrunt init
terragrunt plan
terragrunt apply

Применение всех модулей

cd terraform/live/stage
terragrunt run-all plan
terragrunt run-all apply

Переменные окружения

export YC_TOKEN="..."                      # IAM токен (или YC_SERVICE_ACCOUNT_KEY_FILE)
export YC_CLOUD_ID="..."
export YC_FOLDER_ID="..."                  # Default folder
export YC_STAGE_FOLDER_ID="..."            # Stage folder (опционально)
export KUBECONFIG="~/.kube/config"
export KUBE_CONTEXT="stage"
export S3_ACCESS_KEY="..."                 # Для terraform state
export S3_SECRET_KEY="..."
export DOCKER_REGISTRY_USERNAME="..."      # Для dockerconfigjson secrets
export DOCKER_REGISTRY_PASSWORD="..."

CI/CD

Pipeline автоматически:

  1. Читает изменённые файлы
  2. Определяет затронутые модули
  3. Запускает terragrunt plan / terragrunt apply

Требования

  • Terraform >= 1.0
  • Terragrunt >= 0.45
  • Yandex Cloud провайдер ~> 0.100
  • IAM токен с правами storage.admin для создания бакетов и bucket policy