CI/CD¶
raya’s repository is hosted on GitHub, and
leverages GitHub Actions for continuous integration and deployment.
Workflows¶
docs.yml¶
This is the workflow generated by zensical new. It ensures this documentation
is re-generated and published every time a new commit lands on the main
branch. It needs no repository secret: it authenticates to GitHub Pages through
OIDC, thanks to the id-token: write permission.
provision-checks.yml¶
This workflow runs on Pull Requests. It
- Runs various Packer built-in checks to ensure the correctness of the CoreOS image Packer template.
- Runs various Terraform built-in checks to ensure the correctness of the committed files.
- Runs
terraform planto show the impact of the change on production, so it is reviewed before it lands.
This is a read-only workflow. No deployment happens when it runs.
provision.yml¶
This workflow runs on every push to the main branch. It
- Builds the Fedora CoreOS image used pervasively in
raya. - Runs
terraform applyto actually provisionraya. This job needs the previous one to have finished to gracefully handle a change in the Fedora CoreOS image.
Additionally, this workflow can be run manually from the GitHub interface. It
can be run against any branch, not only main. If it is run against a branch
different than main, only the first job (building the provisioning image) is
run.
This is useful when the image needs to be reconstructed following a dependency
bump (e.g., upgrading to a new k3s version). In that case, terraform plan
will fail because the selectors of data.hcloud_image.fcos won’t resolve (the
image does not exist yet). Building a new image does not affect the production,
and unblock running terraform apply.
Note
Images are snapshots stored in Hetzner Cloud, and they are never deleted by this IaC. Bookkeeping remains manual for now.
Terraform Backend¶
Terraform state is hosted on HCP Terraform, under
the raya workspace belonging to the lthms organization. Its execution mode
is set to Local.
Variable Management¶
Terraform inputs are split in two, depending on whether or not their values can be committed to the repository.
Non-sensitive inputs (sizing, locations, the subdomain the cluster manages, the
list of authorized GitHub handles) are declared in variables.tf, and their
production values live in prod.tfvars. Both provisioning workflows pass that
file explicitly, so terraform plan and terraform apply see the same
configuration.
Sensitive inputs are declared in secrets.tf. They never appear in
prod.tfvars, but instead come from repository secrets.
Secret Management¶
To let the GitHub runners connect to the various services involved in raya’s
deployment, several repository secrets were created:
| Secret | Role |
|---|---|
TF_API_TOKEN |
Authentication to HCP Terraform |
HCLOUD_TOKEN |
Authentication to Hetzner Cloud |
HCLOUD_CLUSTER_TOKEN |
Authentication to Hetzner Cloud, for the components running inside the cluster |
BETTERSTACK_TOKEN |
Authentication to BetterStack |
GCP_TERRAFORM_CREDENTIALS |
Authentication to Google Cloud, for Cloud DNS |
These secrets are exposed to both provisioning workflows
(.github/workflows/provision-checks.yml and
.github/workflows/provision.yml) as environment variables.
TF_API_TOKEN is fed to Terraform via the TF_TOKEN_app_terraform_io
environment variable (since we are using app.terraform.io)1.
The other ones hold values intended for sensitive Terraform variables declared
in secrets.tf. Such values are fed to Terraform using the TF_VAR_ prefix
convention. For instance, HCLOUD_TOKEN initializes the hcloud_token variable
by setting TF_VAR_hcloud_token2.
HCLOUD_TOKEN is also passed under its own name to the Packer jobs, where
image/build.sh queries the Hetzner API with it and Packer authenticates the
build. That use has nothing to do with Terraform variables, which is why the
same secret appears under two different names depending on the job.
HCLOUD_CLUSTER_TOKEN is a second, distinct Hetzner token, created by hand in
the console. It is never used by Terraform itself: it is handed to the cluster,
for the components running there that need to talk to the Hetzner API. Today
that is the CSI driver, which authenticates with it to create
and attach volumes. Hetzner tokens cannot be scoped, so keeping the two apart
does not reduce what the cluster can do — it means the cluster's token can be
revoked without locking the deployment pipeline out.
Warning
Because the workspace uses Local execution, terraform plan runs on the
GitHub runner and its output is written to a public Actions log. Any
sensitive Terraform variable must therefore be declared as such (sensitive
= true).
Note that this only redacts variables: resource attributes (addresses,
user_data, key fingerprints) may still appear in cleartext by default.
Wrap any expression carrying a secret in sensitive() when its value does
not already derive from a sensitive variable. For instance, user_data =
sensitive(data.ct_config.control_plane.rendered).
-
TF_VAR_-prefixed environment variables are consumed by Terraform because therayaworkspace uses theLocalexecution mode. ↩