diff --git a/docker/offline-runner/Dockerfile b/docker/offline-runner/Dockerfile new file mode 100644 index 0000000..a4c5400 --- /dev/null +++ b/docker/offline-runner/Dockerfile @@ -0,0 +1,7 @@ +FROM cr.yandex/crp3ccidau046kdj8g9q/terraform/terragrunt:v9.11 + +COPY provider-mirror /opt/terraform/provider-mirror +COPY terraformrc /root/.terraformrc + +ENV TF_CLI_CONFIG_FILE=/root/.terraformrc +ENV CHECKPOINT_DISABLE=1 diff --git a/docker/offline-runner/README.md b/docker/offline-runner/README.md new file mode 100644 index 0000000..d9eb1b7 --- /dev/null +++ b/docker/offline-runner/README.md @@ -0,0 +1,65 @@ +# Offline terraform/terragrunt runner + +Образ `terraform/terragrunt:v9.11` из `cr.yandex/crp3ccidau046kdj8g9q` тянет провайдеры terraform (`kubernetes`, `vault`, `mongey/kafka`, `cyrilgdn/postgresql`, `cyrilgdn/rabbitmq`, `aminueza/minio`, `random`, `tls`) с `registry.terraform.io` при каждом `terraform init`. В закрытом контуре без доступа к этому хосту `init` виснет или падает. + +Этот образ решает именно это: провайдеры зашиты внутрь как файловое зеркало, `terraform init` их больше никуда не тянет. Доступ к `cr.yandex` при этом всё ещё нужен — сам образ раннер тянет оттуда. + +## Что внутри + +Поверх `cr.yandex/crp3ccidau046kdj8g9q/terraform/terragrunt:v9.11` добавлено: + +- `/opt/terraform/provider-mirror` — файловое зеркало (`terraform providers mirror`), собранное под `linux_amd64` по версиям из `required_providers` в `live/terragrunt.hcl` +- `/root/.terraformrc` с `provider_installation { filesystem_mirror {...} }`, указывающим terraform брать провайдеры оттуда, а не с `registry.terraform.io` +- `TF_CLI_CONFIG_FILE=/root/.terraformrc` +- `CHECKPOINT_DISABLE=1` — отключает фоновую проверку новой версии terraform на `checkpoint.hashicorp.com` (не блокирует прогон при недоступности, но незачем тратить время на таймаут) + +`sops` уже был в базовом образе `v9.11` — отдельно решать не пришлось, шаг "Decrypt secret values" в workflow ничего с интернета не тянет ни в базовом, ни в этом образе. + +## Проверено, не только собрано + +`terraform init` реально прогонялся в контейнере с `docker run --network none` — все 8 провайдеров разрешились из локального зеркала, `Terraform has been successfully initialized!`. Не теория, а факт. + +## Собрать и залить заново + +Нужно, когда меняется набор/версии провайдеров в `live/terragrunt.hcl`, или обновляется базовый образ `terraform/terragrunt`. + +```bash +cd docker/offline-runner +PUSH=true ./build.sh +``` + +`build.sh` сам: +1. качает `terraform/terragrunt:v9.11` под `linux/amd64` (с маком без этого флага уедет arm64, и джоба под упадёт `exec format error`) +2. пересобирает `provider-mirror/` через `terraform providers mirror -platform=linux_amd64` из `versions.tf` в этой же папке +3. собирает образ `cr.yandex/crp3ccidau046kdj8g9q/contour/terraform-terragrunt-offline:v9.11` +4. заливает его, если `PUSH=true` + +Локальную папку `provider-mirror/` (около 75 MB бинарников) в git не кладём — она пересобирается заново каждый билд, коммитить туда нечего, кроме `Dockerfile`/`terraformrc`/`versions.tf`/`build.sh`. + +**`versions.tf` в этой папке должен быть зеркалом `required_providers` из `live/terragrunt.hcl`.** Если там появится новый провайдер или сменится версия — сначала правится `live/terragrunt.hcl`, потом та же запись копируется сюда и гонится пересборка. Рассинхрон тихий: `terraform init` в офлайн-контуре просто не найдёт новый провайдер в зеркале и упадёт с ошибкой резолва версии, а не с понятным "provider not in mirror". + +Текущий образ уже собран и залит: `cr.yandex/crp3ccidau046kdj8g9q/contour/terraform-terragrunt-offline:v9.11`, `linux/amd64`, digest `sha256:9beff996644afad1429f5568f1549501fcb1efe58df8dcd5882ea9dc5a8ffed4`. + +## Как подключить в конкретном контуре + +Ничего в `.gitea/workflows/terraform.yml` менять не нужно — там уже есть переопределение: + +```yaml +image: ${{ vars.RUNNER_IMAGE || 'cr.yandex/crp3ccidau046kdj8g9q/terraform/terragrunt:v9.11' }} +``` + +В Gitea нужного контура (Settings → Actions → Variables) добавить репозиторную переменную: + +``` +RUNNER_IMAGE = cr.yandex/crp3ccidau046kdj8g9q/contour/terraform-terragrunt-offline:v9.11 +``` + +`vars.*` в Gitea Actions — переменные конкретного репозитория в конкретном gitea-инстансе, они не синкаются вместе с содержимым ветки. Установка `RUNNER_IMAGE` в одном контуре не задевает остальные — у них как был, так и останется дефолтный образ, пока кто-то явно не пропишет то же самое там. + +После этого прогоняется обычный `validate`/`plan`/`apply` — разницы в шагах нет, разница только в том, откуда `terraform init` берёт провайдеры. + +## Что офлайн-образ не решает + +- Доступ к `cr.yandex` всё равно нужен — и на пул этого образа раннером, и на пул образов приложений (`kafka-contour`, `camunda-contour`, `postgresql-contour` и так далее — их тянет flux/kubelet, не terraform, к этому образу отношения не имеет). +- Шаг "Configure module registry auth" в workflow настраивает `git config` на `gitlab.sarex.io` под внешние `terraform { source }` — в самом контуре все `source` локальные (`../../modules//...`), так что это безвредная команда без сети. Если когда-нибудь появится стек с внешним source на gitlab, для него нужен будет реальный доступ к `gitlab.sarex.io`, этот образ его не даст. +- S3-бэкенд стейта, providers kubernetes/vault/postgresql/kafka/rabbitmq/minio в рантайме ходят на локальные адреса контура (MinIO, Vault, кластер, VM) — они и без этого образа не лезли в интернет, тут ничего не поменялось. diff --git a/docker/offline-runner/build.sh b/docker/offline-runner/build.sh new file mode 100755 index 0000000..e818eef --- /dev/null +++ b/docker/offline-runner/build.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd "$(dirname "$0")" + +BASE_IMAGE="cr.yandex/crp3ccidau046kdj8g9q/terraform/terragrunt:v9.11" +TARGET_IMAGE="${TARGET_IMAGE:-cr.yandex/crp3ccidau046kdj8g9q/contour/terraform-terragrunt-offline:v9.11}" + +rm -rf provider-mirror +terraform providers mirror -platform=linux_amd64 provider-mirror + +docker pull --platform linux/amd64 "$BASE_IMAGE" +docker build --platform linux/amd64 -t "$TARGET_IMAGE" . + +if [[ "${PUSH:-false}" == "true" ]]; then + docker push "$TARGET_IMAGE" +fi + +rm -rf provider-mirror diff --git a/docker/offline-runner/terraformrc b/docker/offline-runner/terraformrc new file mode 100644 index 0000000..4646acc --- /dev/null +++ b/docker/offline-runner/terraformrc @@ -0,0 +1,9 @@ +provider_installation { + filesystem_mirror { + path = "/opt/terraform/provider-mirror" + include = ["registry.terraform.io/*/*"] + } + direct { + exclude = ["registry.terraform.io/*/*"] + } +} diff --git a/docker/offline-runner/versions.tf b/docker/offline-runner/versions.tf new file mode 100644 index 0000000..b08462d --- /dev/null +++ b/docker/offline-runner/versions.tf @@ -0,0 +1,14 @@ +terraform { + required_version = ">= 1.5.0" + + required_providers { + kubernetes = { source = "hashicorp/kubernetes", version = "~> 2.23" } + vault = { source = "hashicorp/vault", version = "~> 4.2" } + kafka = { source = "Mongey/kafka", version = "0.10.4" } + postgresql = { source = "cyrilgdn/postgresql", version = "~> 1.21" } + rabbitmq = { source = "cyrilgdn/rabbitmq", version = "~> 1.8" } + minio = { source = "aminueza/minio", version = "~> 2.0" } + random = { source = "hashicorp/random", version = "~> 3.1" } + tls = { source = "hashicorp/tls", version = "~> 4.0" } + } +}