| .gitea/workflows | ||
| live | ||
| modules | ||
| scripts | ||
| .gitignore | ||
| .gitlab-ci.yml | ||
| .sops.yaml | ||
| Dockerfile | ||
| infrastructure-secrets.yaml | ||
| infrastructure.yaml | ||
| README.md | ||
| SOPS.md | ||
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 автоматически:
- Читает изменённые файлы
- Определяет затронутые модули
- Запускает
terragrunt plan/terragrunt apply
Требования
- Terraform >= 1.0
- Terragrunt >= 0.45
- Yandex Cloud провайдер ~> 0.100
- IAM токен с правами
storage.adminдля создания бакетов и bucket policy