terraform-contour-mirror/README.md

355 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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: <base64>
password: <base64>
```
**Режим 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
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`.
Для уже существующих неймспейс:
```yaml
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`:
```bash
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.
## Использование
### Локальный запуск
```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