# 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 Единый файл конфигурации для всех ресурсов. Структура: ```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` и передаёт данные в модули: ```hcl 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` — полный доступ **Пример:** ```yaml buckets: - name: my-bucket acl: private role: storage.uploader # опционально ``` ### yc-database Создание PostgreSQL users и databases в существующем кластере. **Особенности:** - Пароль генерируется автоматически - Поддержка extensions - Поддержка permissions на другие БД - `ignore_changes` на password **Пример:** ```yaml 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 **Пример:** ```yaml 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, и для секретов со статическими значениями. ```yaml - name: my-secret namespace: myns type: opaque # kafka|rabbitmq|database|database_url|valkey|s3|yc_sa|dockerconfigjson|opaque lifecycle: { ignore_changes: false } data: username: password: ``` **Режим B — динамический: `depends_on` + маппинг ключей.** Значения собираются из outputs других юнитов (kafka/rabbitmq/database/valkey/s3/yc_sa), которые создал terragrunt. **`data` НЕ указывается.** ```yaml - 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](SOPS.md). ### k8s-namespace Создание Kubernetes namespaces. **Пример:** ```yaml namespaces: - name: pulse labels: environment: stage annotations: managed-by: terraform ``` ## Использование ### Локальный запуск ```bash cd terraform/live/stage/s3 terragrunt init terragrunt plan terragrunt apply ``` ### Применение всех модулей ```bash cd terraform/live/stage terragrunt run-all plan terragrunt run-all apply ``` ### Переменные окружения ```bash 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