docs: spec — migrate Terraform state GitLab -> Scaleway Object Storage (Phase 2A)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
jgrusewski
2026-06-21 15:16:28 +02:00
parent 2c08b49b4f
commit 2eb8cd8333

View File

@@ -0,0 +1,94 @@
# Migrate Terraform State to Scaleway Object Storage — Design (Phase 2A)
**Date:** 2026-06-21
**Status:** Design (approved)
**Repo:** foxhunt (IaC current home; relocation to fxhnt is Phase 2D)
**Part of:** Phase 2 (GitLab → Gitea consolidation). Sub-projects: **2A state migration (this)**, 2B Gitea
stand-up + repo migration + Argo wiring, 2C GitLab decommission, 2D IaC relocation into fxhnt.
## Motivation
Terraform state for the whole production cluster currently lives in the **self-hosted GitLab** http
backend (project ID=1), whose object data sits in **in-cluster MinIO**. That is a chicken-and-egg
fragility: the state describing the cluster lives *inside* the cluster it manages. GitLab is also slated
for decommission (Phase 2C, replaced by lightweight Gitea). Moving state to **external Scaleway Object
Storage** decouples it from both GitLab and the cluster — a prerequisite for removing GitLab and a
durability win on its own (state survives cluster loss).
## Decision (keystone)
**Target backend: Scaleway Object Storage (native S3), bucket `foxhunt-tfstate`** (already exists,
fr-par, created ~3 months ago but never wired). Reuse it (no rename — avoids a needless create; the
`foxhunt-` prefix is cosmetic and harmless). Locking via **OpenTofu native S3 lockfile** (`use_lockfile`,
OpenTofu v1.11.5 present) — no DynamoDB.
## Scope — what changes
1. **`infra/live/root.hcl`** — replace the single `remote_state` block: `backend = "http"` (GitLab) →
`backend = "s3"` (Scaleway). New config:
```hcl
remote_state {
backend = "s3"
generate = { path = "backend.tf", if_exists = "overwrite" }
config = {
bucket = "foxhunt-tfstate"
key = "${path_relative_to_include()}/terraform.tfstate"
region = "fr-par"
endpoints = { s3 = "https://s3.fr-par.scw.cloud" }
skip_credentials_validation = true
skip_region_validation = true
skip_requesting_account_id = true
skip_metadata_api_check = true
use_lockfile = true
}
}
```
Credentials: Scaleway access/secret keys exported as `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY`
(the s3 backend reads AWS-style env vars) from the existing `scaleway-credentials` k8s secret.
2. **State migration, per module, sequential** (`kapsule`, `public-gateway`, `dns`, `block-storage`):
- Back up first: `terragrunt state pull > /tmp/tfstate-backup-<module>.tfstate` (from GitLab, BEFORE
changing the backend for that module).
- `terragrunt init -migrate-state -force-copy` → copies GitLab state → Scaleway S3.
- `terragrunt plan` → **must report `No changes`** before proceeding to the next module.
3. **Lock smoke test** (once, on one module): confirm `use_lockfile` works against Scaleway — a lock
object is created during apply/plan-with-lock and released after (verify no orphan `.tflock` left).
## Migration order & gates
`kapsule` → `public-gateway` → `dns` → `block-storage`. After EACH: backup taken, `-migrate-state`
succeeded, `plan = No changes` confirmed. **STOP and revert if any module's post-migration plan shows
drift** (revert = restore `http` backend in root.hcl + `terragrunt init` to re-attach GitLab state; the
GitLab copy is never deleted in 2A).
## Safety / reversibility
- **Old GitLab state is NOT deleted in 2A** — it stays as a live fallback until GitLab is removed in 2C.
So the entire migration is reversible by flipping `root.hcl` back to the http backend.
- `/tmp` state backups taken before each module (belt-and-suspenders; never committed — contains
resource IDs, not secrets, but treat as sensitive).
- One module at a time with a `plan = No changes` gate prevents a bad backend config from cascading.
- `endpoints.s3` (not deprecated top-level `endpoint`) + the four `skip_*` flags are required for the
AWS s3 backend to talk to Scaleway's S3-compatible API without AWS-specific preflight calls.
## Risks
- **Scaleway S3 quirks:** some provider/backend versions need `skip_s3_checksum = true` for Scaleway. If
`-migrate-state` errors on checksum, add it. (Documented as a known fallback in the plan.)
- **Lock semantics:** Scaleway Object Storage must honor conditional-write for `use_lockfile`. The lock
smoke test catches this; fallback is to run with locking disabled (single maintainer) if unsupported.
- **Credential confusion:** the s3 backend reads `AWS_*` env vars, NOT `SCW_*`. Plan must export both
(SCW_* for the provider, AWS_* for the backend) — a common foot-gun.
## Out of scope (later sub-projects)
Gitea stand-up + repo migration + Argo wiring (2B); GitLab helm removal + GitLab state/bucket cleanup
(2C); `git mv` of IaC from foxhunt → fxhnt + path updates (2D). Files stay in foxhunt for 2A; only the
backend changes.
## Acceptance criteria
- `infra/live/root.hcl` uses the Scaleway s3 backend; committed.
- All 4 modules migrated: each shows `terragrunt plan = No changes` against the new backend.
- State objects present in `foxhunt-tfstate` at `<module>/terraform.tfstate` (verified via `scw`/`mc`).
- Lock smoke test passes (lock acquired + released, no orphan lockfile).
- GitLab http state still intact (untouched fallback) — not yet deleted.