terraform: per-service backend/frontend image tags (two-image contract) (#2)
This commit is contained in:
parent
dde342f01b
commit
8f98bab89f
6 changed files with 59 additions and 38 deletions
|
|
@ -17,14 +17,16 @@ This config manages **two VPS hosts** today:
|
||||||
|
|
||||||
| key | role | VPS | branch | domain | notes |
|
| key | role | VPS | branch | domain | notes |
|
||||||
|--------|--------|-------------------------|-----------|-------------------|------------------------------------|
|
|--------|--------|-------------------------|-----------|-------------------|------------------------------------|
|
||||||
| `prod` | prod | NEW 48 GB / 12-core box (`169.58.46.181`) | `release` (of the APP repo — see `app_image_tag`) | `thermograph.org` | Caddy TLS; sized up (8/8/4/16g) |
|
| `prod` | prod | NEW 48 GB / 12-core box (`169.58.46.181`) | `release` (of the APP repos — see `backend_image_tag` / `frontend_image_tag`) | `thermograph.org` | Caddy TLS; sized up (8/8/4/16g) |
|
||||||
| `beta` | beta | old box `75.119.132.91` | `main` (of the APP repo) | `beta.thermograph.org` | Caddy TLS; also hosts Forgejo |
|
| `beta` | beta | old box `75.119.132.91` | `main` (of the APP repo) | `beta.thermograph.org` | Caddy TLS; also hosts Forgejo |
|
||||||
|
|
||||||
Each host's own checkout on disk (`app_dir`, `git_branch`) is **this infra
|
Each host's own checkout on disk (`app_dir`, `git_branch`) is **this infra
|
||||||
repo**, not the app repo — the "branch" column above is which app-repo tag a
|
repo**, not the app repo — the "branch" column above is which app-repo tag a
|
||||||
host is meant to track conceptually; the actual pinned version is
|
host is meant to track conceptually; the actual pinned versions are
|
||||||
`var.hosts[*].app_image_tag` (e.g. `"sha-<12 hex>"`), since the host has no app
|
`var.hosts[*].backend_image_tag` / `frontend_image_tag` (e.g. `"sha-<12 hex>"` each —
|
||||||
checkout to derive a tag from. The LAN dev server is **out of scope here** — it
|
the app is two separately-published images, `emi/thermograph-backend/app` and
|
||||||
|
`emi/thermograph-frontend/app`), since the host has no app checkout to derive a tag
|
||||||
|
from. The LAN dev server is **out of scope here** — it
|
||||||
builds from source via the app repo's `deploy/deploy-dev.sh` (a self-hosted
|
builds from source via the app repo's `deploy/deploy-dev.sh` (a self-hosted
|
||||||
Forgejo Actions runner), not Terraform.
|
Forgejo Actions runner), not Terraform.
|
||||||
|
|
||||||
|
|
@ -41,7 +43,8 @@ Per host, over SSH provisioners, Terraform:
|
||||||
`/etc/thermograph.env` from the SOPS+age vault — **Terraform itself never sees or
|
`/etc/thermograph.env` from the SOPS+age vault — **Terraform itself never sees or
|
||||||
carries an app secret** (see "Secrets" below);
|
carries an app secret** (see "Secrets" below);
|
||||||
- for a host **with** a domain, installs a rendered Caddyfile and reloads Caddy;
|
- for a host **with** a domain, installs a rendered Caddyfile and reloads Caddy;
|
||||||
- brings the stack up on the explicit `app_image_tag`: `docker login` to the registry,
|
- brings the stack up on the explicit per-service tags (exports `BACKEND_IMAGE_TAG` /
|
||||||
|
`FRONTEND_IMAGE_TAG` for the compose file): `docker login` to the registry,
|
||||||
`docker compose <-f each compose file> pull backend frontend`, then
|
`docker compose <-f each compose file> pull backend frontend`, then
|
||||||
`... up -d --remove-orphans`, running docker as root with both env files sourced;
|
`... up -d --remove-orphans`, running docker as root with both env files sourced;
|
||||||
- health-checks `http://127.0.0.1:8137/`.
|
- health-checks `http://127.0.0.1:8137/`.
|
||||||
|
|
@ -134,7 +137,8 @@ pinned across machines.
|
||||||
|
|
||||||
`terraform apply` runs `remote-exec` **on the server**: it resets the checkout to the
|
`terraform apply` runs `remote-exec` **on the server**: it resets the checkout to the
|
||||||
branch, renders topology config + secrets, and runs `docker compose pull && up -d`
|
branch, renders topology config + secrets, and runs `docker compose pull && up -d`
|
||||||
(pulling the pinned `app_image_tag` and recreating containers — a brief app restart).
|
(pulling the pinned `backend_image_tag` / `frontend_image_tag` and recreating
|
||||||
|
containers — a brief app restart).
|
||||||
Against the live production host this is a real deploy. Review the plan, apply in a
|
Against the live production host this is a real deploy. Review the plan, apply in a
|
||||||
maintenance window, and prefer `-target` to touch one host at a time.
|
maintenance window, and prefer `-target` to touch one host at a time.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,8 @@ module "host" {
|
||||||
ssh_private_key_path = each.value.ssh_private_key_path
|
ssh_private_key_path = each.value.ssh_private_key_path
|
||||||
role = each.value.role
|
role = each.value.role
|
||||||
git_branch = each.value.git_branch
|
git_branch = each.value.git_branch
|
||||||
app_image_tag = each.value.app_image_tag
|
backend_image_tag = each.value.backend_image_tag
|
||||||
|
frontend_image_tag = each.value.frontend_image_tag
|
||||||
domain = each.value.domain
|
domain = each.value.domain
|
||||||
compose_files = each.value.compose_files
|
compose_files = each.value.compose_files
|
||||||
app_dir = each.value.app_dir
|
app_dir = each.value.app_dir
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,8 @@ resource "null_resource" "host" {
|
||||||
compose_flags = local.compose_flags
|
compose_flags = local.compose_flags
|
||||||
caddy_sha = sha256(local.caddy_content)
|
caddy_sha = sha256(local.caddy_content)
|
||||||
branch = var.git_branch
|
branch = var.git_branch
|
||||||
app_image_tag = var.app_image_tag
|
backend_image_tag = var.backend_image_tag
|
||||||
|
frontend_image_tag = var.frontend_image_tag
|
||||||
app_dir = var.app_dir
|
app_dir = var.app_dir
|
||||||
sizing = "${var.workers}/${var.app_cpus}/${var.db_cpus}/${var.db_memory}/${var.timescaledb_tag}"
|
sizing = "${var.workers}/${var.app_cpus}/${var.db_cpus}/${var.db_memory}/${var.timescaledb_tag}"
|
||||||
# Re-provision when the archive self-hosting config changes. The rclone.conf is
|
# Re-provision when the archive self-hosting config changes. The rclone.conf is
|
||||||
|
|
@ -235,10 +236,13 @@ resource "null_resource" "host" {
|
||||||
|
|
||||||
# 3. Install /etc/thermograph-topology.env, render /etc/thermograph.env from the
|
# 3. Install /etc/thermograph-topology.env, render /etc/thermograph.env from the
|
||||||
# SOPS+age vault (deploy/render-secrets.sh, now part of this same checkout),
|
# SOPS+age vault (deploy/render-secrets.sh, now part of this same checkout),
|
||||||
# then bring the stack up on the EXPLICIT app image tag — there's no app
|
# then bring the stack up on the EXPLICIT per-service app image tags — there's no
|
||||||
# checkout on this host to derive a tag from (see var.app_image_tag). docker
|
# app checkout on this host to derive a tag from (see var.backend_image_tag /
|
||||||
# runs as root (sources both env files in the same shell) so it never depends
|
# frontend_image_tag). The compose file reads BACKEND_IMAGE_TAG / FRONTEND_IMAGE_TAG
|
||||||
# on the docker group membership taking effect in this session.
|
# (for emi/thermograph-backend/app and emi/thermograph-frontend/app), so we export
|
||||||
|
# those, not the old single IMAGE_PATH/IMAGE_TAG. docker runs as root (sources both
|
||||||
|
# env files in the same shell) so it never depends on the docker group membership
|
||||||
|
# taking effect in this session.
|
||||||
provisioner "remote-exec" {
|
provisioner "remote-exec" {
|
||||||
inline = [
|
inline = [
|
||||||
<<-EOT
|
<<-EOT
|
||||||
|
|
@ -252,7 +256,7 @@ resource "null_resource" "host" {
|
||||||
. deploy/render-secrets.sh
|
. deploy/render-secrets.sh
|
||||||
render_thermograph_secrets ${var.app_dir}
|
render_thermograph_secrets ${var.app_dir}
|
||||||
set -a; . /etc/thermograph-topology.env; . /etc/thermograph.env; set +a
|
set -a; . /etc/thermograph-topology.env; . /etc/thermograph.env; set +a
|
||||||
export REGISTRY_HOST="git.thermograph.org" IMAGE_PATH="emi/thermograph/app" IMAGE_TAG="${var.app_image_tag}"
|
export REGISTRY_HOST="git.thermograph.org" BACKEND_IMAGE_TAG="${var.backend_image_tag}" FRONTEND_IMAGE_TAG="${var.frontend_image_tag}"
|
||||||
echo "$REGISTRY_TOKEN" | docker login "$REGISTRY_HOST" --username emi --password-stdin
|
echo "$REGISTRY_TOKEN" | docker login "$REGISTRY_HOST" --username emi --password-stdin
|
||||||
docker compose ${local.compose_flags} pull backend frontend
|
docker compose ${local.compose_flags} pull backend frontend
|
||||||
docker compose ${local.compose_flags} up -d --remove-orphans
|
docker compose ${local.compose_flags} up -d --remove-orphans
|
||||||
|
|
|
||||||
|
|
@ -26,12 +26,17 @@ variable "role" {
|
||||||
}
|
}
|
||||||
|
|
||||||
variable "git_branch" {
|
variable "git_branch" {
|
||||||
description = "This INFRA repo's branch the host checkout is reset to (independent of which app image is deployed — see app_image_tag)."
|
description = "This INFRA repo's branch the host checkout is reset to (independent of which app images are deployed — see backend_image_tag / frontend_image_tag)."
|
||||||
type = string
|
type = string
|
||||||
}
|
}
|
||||||
|
|
||||||
variable "app_image_tag" {
|
variable "backend_image_tag" {
|
||||||
description = "App image tag to pull, e.g. \"sha-<12 hex>\" (build-push.yml's tag for the app-repo commit) or a semver tag. The host has no app-repo checkout to derive this from, so it's always explicit."
|
description = "Backend image tag to pull (emi/thermograph-backend/app), e.g. \"sha-<12 hex>\" (build-push.yml's tag for the backend-repo commit) or a semver tag. The host has no app-repo checkout to derive this from, so it's always explicit."
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "frontend_image_tag" {
|
||||||
|
description = "Frontend image tag to pull (emi/thermograph-frontend/app), e.g. \"sha-<12 hex>\" (build-push.yml's tag for the frontend-repo commit) or a semver tag. Always explicit, same as backend_image_tag."
|
||||||
type = string
|
type = string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,9 @@ hosts = {
|
||||||
ssh_user = "agent"
|
ssh_user = "agent"
|
||||||
ssh_private_key_path = "~/.ssh/thermograph_agent_ed25519"
|
ssh_private_key_path = "~/.ssh/thermograph_agent_ed25519"
|
||||||
role = "prod"
|
role = "prod"
|
||||||
git_branch = "main" # this INFRA repo's branch -- see app_image_tag for the app version
|
git_branch = "main" # this INFRA repo's branch -- see *_image_tag for the app versions
|
||||||
app_image_tag = "REPLACE_WITH_APP_IMAGE_TAG" # e.g. "sha-abcdef012345" -- from build-push.yml on the app repo
|
backend_image_tag = "REPLACE_WITH_BACKEND_IMAGE_TAG" # e.g. "sha-abcdef012345" -- from thermograph-backend build-push.yml
|
||||||
|
frontend_image_tag = "REPLACE_WITH_FRONTEND_IMAGE_TAG" # e.g. "sha-012345abcdef" -- from thermograph-frontend build-push.yml
|
||||||
domain = "thermograph.org" # Caddy TLS in front, app on loopback
|
domain = "thermograph.org" # Caddy TLS in front, app on loopback
|
||||||
compose_files = ["docker-compose.yml"]
|
compose_files = ["docker-compose.yml"]
|
||||||
app_dir = "/opt/thermograph"
|
app_dir = "/opt/thermograph"
|
||||||
|
|
@ -43,7 +44,8 @@ hosts = {
|
||||||
ssh_private_key_path = "~/.ssh/thermograph_agent_ed25519"
|
ssh_private_key_path = "~/.ssh/thermograph_agent_ed25519"
|
||||||
role = "beta"
|
role = "beta"
|
||||||
git_branch = "main"
|
git_branch = "main"
|
||||||
app_image_tag = "REPLACE_WITH_APP_IMAGE_TAG"
|
backend_image_tag = "REPLACE_WITH_BACKEND_IMAGE_TAG"
|
||||||
|
frontend_image_tag = "REPLACE_WITH_FRONTEND_IMAGE_TAG"
|
||||||
# No public domain by default: no Caddy/TLS, firewall opens the app port. NOTE:
|
# No public domain by default: no Caddy/TLS, firewall opens the app port. NOTE:
|
||||||
# with compose_files = ["docker-compose.yml"] the app binds 127.0.0.1 only, so
|
# with compose_files = ["docker-compose.yml"] the app binds 127.0.0.1 only, so
|
||||||
# until you either set a domain (e.g. "beta.thermograph.org", which fronts it with
|
# until you either set a domain (e.g. "beta.thermograph.org", which fronts it with
|
||||||
|
|
@ -68,7 +70,8 @@ hosts = {
|
||||||
# ssh_private_key_path = "~/.ssh/thermograph_agent_ed25519"
|
# ssh_private_key_path = "~/.ssh/thermograph_agent_ed25519"
|
||||||
# role = "uat"
|
# role = "uat"
|
||||||
# git_branch = "main"
|
# git_branch = "main"
|
||||||
# app_image_tag = "REPLACE_WITH_APP_IMAGE_TAG"
|
# backend_image_tag = "REPLACE_WITH_BACKEND_IMAGE_TAG"
|
||||||
|
# frontend_image_tag = "REPLACE_WITH_FRONTEND_IMAGE_TAG"
|
||||||
# domain = ""
|
# domain = ""
|
||||||
# compose_files = ["docker-compose.yml"]
|
# compose_files = ["docker-compose.yml"]
|
||||||
# app_dir = "/opt/thermograph"
|
# app_dir = "/opt/thermograph"
|
||||||
|
|
@ -89,7 +92,8 @@ hosts = {
|
||||||
# ssh_private_key_path = "~/.ssh/thermograph_agent_ed25519"
|
# ssh_private_key_path = "~/.ssh/thermograph_agent_ed25519"
|
||||||
# role = "uat"
|
# role = "uat"
|
||||||
# git_branch = "main"
|
# git_branch = "main"
|
||||||
# app_image_tag = "REPLACE_WITH_APP_IMAGE_TAG"
|
# backend_image_tag = "REPLACE_WITH_BACKEND_IMAGE_TAG"
|
||||||
|
# frontend_image_tag = "REPLACE_WITH_FRONTEND_IMAGE_TAG"
|
||||||
# size = "nano"
|
# size = "nano"
|
||||||
# }
|
# }
|
||||||
# }
|
# }
|
||||||
|
|
|
||||||
|
|
@ -24,14 +24,16 @@ variable "hosts" {
|
||||||
ssh_private_key_path = string # path to the private key for that user
|
ssh_private_key_path = string # path to the private key for that user
|
||||||
role = string # "prod" | "beta" (informational + outputs)
|
role = string # "prod" | "beta" (informational + outputs)
|
||||||
git_branch = string # this INFRA repo's branch the checkout is reset to
|
git_branch = string # this INFRA repo's branch the checkout is reset to
|
||||||
# Which app image to run, e.g. "sha-<12 hex>" (matches build-push.yml's tag for
|
# Which app images to run, e.g. "sha-<12 hex>" (each matching build-push.yml's tag
|
||||||
# the app-repo commit build-push.yml built) or a semver tag on a release push.
|
# for the commit that app repo built) or a semver tag on a release push. The app is
|
||||||
# Required, no default: since the host's checkout is this infra repo, not the app
|
# TWO separately-published images now — emi/thermograph-backend/app and
|
||||||
# repo, there is no "current commit" to derive a tag from — it must be explicit.
|
# emi/thermograph-frontend/app — pinned independently. Required, no default: the
|
||||||
# Bump this (via a normal tfvars edit + apply) whenever the app repo ships a
|
# host's checkout is this infra repo, not the app repos, so there's no "current
|
||||||
# commit you want this host running; the infra repo's own git_branch is
|
# commit" to derive a tag from; both must be explicit. Bump these (via a normal
|
||||||
# independent of that and rarely needs to change.
|
# tfvars edit + apply) whenever an app repo ships a commit you want this host
|
||||||
app_image_tag = string
|
# running; the infra repo's own git_branch is independent and rarely needs to change.
|
||||||
|
backend_image_tag = string
|
||||||
|
frontend_image_tag = string
|
||||||
domain = optional(string, "") # public domain; "" => no Caddy/TLS
|
domain = optional(string, "") # public domain; "" => no Caddy/TLS
|
||||||
compose_files = optional(list(string), ["docker-compose.yml"])
|
compose_files = optional(list(string), ["docker-compose.yml"])
|
||||||
app_dir = optional(string, "/opt/thermograph") # checkout path on the host
|
app_dir = optional(string, "/opt/thermograph") # checkout path on the host
|
||||||
|
|
@ -73,7 +75,8 @@ variable "gcp_hosts" {
|
||||||
ssh_private_key_path = string # path to the matching PRIVATE key (for the module's provisioner)
|
ssh_private_key_path = string # path to the matching PRIVATE key (for the module's provisioner)
|
||||||
role = string
|
role = string
|
||||||
git_branch = string
|
git_branch = string
|
||||||
app_image_tag = string
|
backend_image_tag = string
|
||||||
|
frontend_image_tag = string
|
||||||
domain = optional(string, "")
|
domain = optional(string, "")
|
||||||
compose_files = optional(list(string), ["docker-compose.yml"])
|
compose_files = optional(list(string), ["docker-compose.yml"])
|
||||||
app_dir = optional(string, "/opt/thermograph")
|
app_dir = optional(string, "/opt/thermograph")
|
||||||
|
|
@ -84,7 +87,7 @@ variable "gcp_hosts" {
|
||||||
}
|
}
|
||||||
|
|
||||||
variable "repo_url" {
|
variable "repo_url" {
|
||||||
description = "Git remote to clone from when a host has no checkout yet. Points at THIS repo (thermograph-infra) now, not the app repo -- the app's own source is never checked out on a host; only its published registry images are pulled (see var.hosts[*].app_image_tag). thermograph-infra is a private repo, so this typically needs embedded read credentials, e.g. a Forgejo deploy token: \"https://<token-name>:<token>@git.thermograph.org/emi/thermograph-infra.git\"."
|
description = "Git remote to clone from when a host has no checkout yet. Points at THIS repo (thermograph-infra) now, not the app repo -- the app's own source is never checked out on a host; only its published registry images are pulled (see var.hosts[*].backend_image_tag / frontend_image_tag). thermograph-infra is a private repo, so this typically needs embedded read credentials, e.g. a Forgejo deploy token: \"https://<token-name>:<token>@git.thermograph.org/emi/thermograph-infra.git\"."
|
||||||
type = string
|
type = string
|
||||||
default = "https://git.thermograph.org/emi/thermograph-infra.git"
|
default = "https://git.thermograph.org/emi/thermograph-infra.git"
|
||||||
sensitive = true
|
sensitive = true
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue