feat(infra): make clean deployments reproducible

This commit is contained in:
Alex 2026-08-28 00:37:52 +02:00
parent 09c60df3c5
commit 12a395a33c
24 changed files with 525 additions and 32 deletions

View file

@ -51,6 +51,18 @@ jobs:
docker run --rm -v "$PWD:/repo" -w /repo bash:5.3 \ docker run --rm -v "$PWD:/repo" -w /repo bash:5.3 \
bash -ec 'for file in scripts/k3d/lab.sh scripts/provision/bootstrap.sh scripts/provision/lab.sh; do bash -n "$file"; done' bash -ec 'for file in scripts/k3d/lab.sh scripts/provision/bootstrap.sh scripts/provision/lab.sh; do bash -n "$file"; done'
- name: Validate Terraform
run: |
for stack in terraform/infra terraform/platform
do
docker run --rm -v "$PWD:/work" -w "/work/$stack" \
hashicorp/terraform:1.15.9 fmt -check
docker run --rm -v "$PWD:/work" -w "/work/$stack" \
hashicorp/terraform:1.15.9 init -backend=false -lockfile=readonly
docker run --rm -v "$PWD:/work" -w "/work/$stack" \
hashicorp/terraform:1.15.9 validate
done
- name: Validate Compose and Kubernetes configuration - name: Validate Compose and Kubernetes configuration
env: env:
CLUSTER_API_UPSTREAM: 192.0.2.1:8080 CLUSTER_API_UPSTREAM: 192.0.2.1:8080

View file

@ -74,3 +74,21 @@ jobs:
if: steps.probe.outputs.ready == 'true' if: steps.probe.outputs.ready == 'true'
with: with:
working-directory: apps/api working-directory: apps/api
terraform:
name: terraform
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: format and validate
run: |
for stack in terraform/infra terraform/platform
do
docker run --rm -v "$PWD:/work" -w "/work/$stack" \
hashicorp/terraform:1.15.9 fmt -check
docker run --rm -v "$PWD:/work" -w "/work/$stack" \
hashicorp/terraform:1.15.9 init -backend=false -lockfile=readonly
docker run --rm -v "$PWD:/work" -w "/work/$stack" \
hashicorp/terraform:1.15.9 validate
done

1
.gitignore vendored
View file

@ -85,6 +85,7 @@ CLAUDE.local.md
*.tfstate.* *.tfstate.*
*.tfvars *.tfvars
!*.tfvars.example !*.tfvars.example
terraform/**/inventory.yml
crash.log crash.log
crash.*.log crash.*.log
override.tf override.tf

View file

@ -342,24 +342,20 @@ pre-promotion analysis fails the pipeline.
Stated plainly so it does not have to be discovered. Stated plainly so it does not have to be discovered.
`terraform/infra/` and `terraform/platform/` are empty. Provisioning is Ansible, `terraform/infra/` creates the two Fedora 44 libvirt machines and emits the
and it does the job, but the brief asks for Terraform and this is where it would inventory consumed by Ansible. `terraform/platform/` installs Argo Rollouts,
go. The Ansible path was written to accept hosts from anywhere, so a Terraform Sealed Secrets and kube-prometheus-stack. The separation is deliberate:
module that creates machines and emits an inventory would slot in without Terraform owns machines and cluster-wide controllers, Ansible owns the hosts,
touching the roles. and Kustomize owns the application.
Sealed Secrets is designed for but not installed. `deploy/secrets/README.md` Sealed Secrets and Discord alert routing are configured, but real encrypted
documents the sealing workflow and `kubectl get crd | grep sealed` still comes values cannot be committed until the owner supplies them. `deploy/secrets/README.md`
back empty, so secrets are currently created out of band. The controller keeps both plaintext values in local pipelines and commits only encrypted
generates a fresh keypair on install, so backing up its private key has to be `SealedSecret` resources. The controller key must be backed up outside this
the first step, not the last. repository before it becomes the recovery path.
Alertmanager routing to Discord is planned and not committed. The alert rules The remaining acceptance gap is environmental: the reproducible fresh-VM path
fire; nothing forwards them yet. has not been rerun on a second workstation with no dependencies installed.
Two items on `docs/roadmap.md` remain open: the QEMU lab has not been tested on a
workstation with no dependencies installed, and end-to-end acceptance has not
been rerun from two freshly installed Fedora hosts.
## Fedora traps worth knowing ## Fedora traps worth knowing
@ -386,7 +382,7 @@ deploy/observability/ Loki, Tempo, collectors, Grafana data sources
observability/ dashboards, alert rules, collector configs, local compose observability/ dashboards, alert rules, collector configs, local compose
scripts/k3d/ portable rollback lab scripts/k3d/ portable rollback lab
scripts/provision/ Ansible roles, bootstrap, QEMU lab scripts/provision/ Ansible roles, bootstrap, QEMU lab
terraform/ empty, see above terraform/ libvirt machines, Ansible inventory and cluster-wide Helm releases
docs/ roadmap, evidence, CI/CD and Mini PC notes docs/ roadmap, evidence, CI/CD and Mini PC notes
``` ```

View file

@ -24,12 +24,43 @@ Postgres only reads `POSTGRES_PASSWORD` when it initialises the data directory.
## prod (sealed) ## prod (sealed)
Write the plaintext to `nereus-db.plain.yaml` (gitignored), then: Install the controller through `terraform/platform` first. It is named
`sealed-secrets-controller`, which is the default expected by `kubeseal`.
```fish Create the database Secret locally and pipe it straight into `kubeseal`. The
kubeseal --format yaml < nereus-db.plain.yaml > nereus-db-sealed.yaml plaintext exists only in the pipeline and the password variable:
```bash
db_password="$(openssl rand -hex 16)"
kubectl create secret generic nereus-db --namespace nereus \
--from-literal=POSTGRES_PASSWORD="$db_password" \
--from-literal=DATABASE_URL="postgres://nereus:${db_password}@nereus-postgres:5432/nereus?sslmode=disable" \
--dry-run=client -o yaml | \
kubeseal --format yaml >deploy/secrets/nereus-db-sealed.yaml
unset db_password
``` ```
Commit only `nereus-db-sealed.yaml` and add it to the prod overlay. The Commit only `nereus-db-sealed.yaml` and add it to the prod overlay. Back up the
controller isn't installed yet — `kubectl get crd | grep sealed` comes back controller key outside the repository before relying on sealed secrets for
empty. recovery. Losing that key makes every committed `SealedSecret` undecryptable.
## Discord alert routing
Alertmanager reads its Discord webhook from the `nereus-discord` Secret as a
mounted file. Create the webhook in Discord, keep it in the shell environment,
and seal it without writing plaintext to disk:
```bash
test -n "${DISCORD_WEBHOOK_URL:-}"
printf '%s' "$DISCORD_WEBHOOK_URL" | \
kubectl create secret generic nereus-discord --namespace observability \
--from-file=webhook-url=/dev/stdin --dry-run=client -o yaml | \
kubeseal --format yaml >deploy/secrets/nereus-discord-sealed.yaml
unset DISCORD_WEBHOOK_URL
```
Apply the sealed secret, confirm it produced the `nereus-discord` Secret, then
set `enable_discord = true` in the platform Terraform variables and apply
again. Discord routing defaults off so a missing owner-provided webhook cannot
stop Alertmanager from starting. The committed opt-in values route warning and
critical Nereus alerts to Discord and discard alerts that do not match.

View file

@ -1,6 +1,6 @@
# Nereus delivery roadmap # Nereus delivery roadmap
Updated: 2026-08-27 Updated: 2026-08-28
`[x]` means locally verified. `[~]` means implemented but awaiting a real `[x]` means locally verified. `[~]` means implemented but awaiting a real
integration environment. `[ ]` means not implemented. `[H]` is human-owned and integration environment. `[ ]` means not implemented. `[H]` is human-owned and
@ -124,6 +124,7 @@ task. Mark a task `[x]` only after its stated verification passes.
- [x] Add a deployed-version dashboard variable. - [x] Add a deployed-version dashboard variable.
- [x] Correlate logs and traces using `trace_id` after cluster log ingestion exists. - [x] Correlate logs and traces using `trace_id` after cluster log ingestion exists.
- [x] Add API error-rate, latency, readiness, and scrape alerts. - [x] Add API error-rate, latency, readiness, and scrape alerts.
- [~] Route warning and critical alerts to Discord; configuration validates and awaits the owner-provided sealed webhook.
- [x] Load the dashboard and evaluate all six PromQL queries. - [x] Load the dashboard and evaluate all six PromQL queries.
- [x] Trigger chaos and distinguish the failing version in Grafana. - [x] Trigger chaos and distinguish the failing version in Grafana.
@ -177,7 +178,7 @@ task. Mark a task `[x]` only after its stated verification passes.
- [x] Execute `lab.sh check` after a workstation reboot. - [x] Execute `lab.sh check` after a workstation reboot.
- [x] Test recovery from an interrupted image download and validate the resumed QCOW2. - [x] Test recovery from an interrupted image download and validate the resumed QCOW2.
- [x] Test recovery after node 2 is deleted manually and rejoin it with a fresh identity. - [x] Test recovery after node 2 is deleted manually and rejoin it with a fresh identity.
- [ ] Test on a workstation with no dependencies installed. - [~] Test on a workstation with no dependencies installed; the reproducible fresh-VM path is implemented and awaits a second host.
- [x] Decide whether an aarch64 image path is required; keep the lab x86_64-only. - [x] Decide whether an aarch64 image path is required; keep the lab x86_64-only.
- [ ] After final sign-off, destroy the QEMU lab and remove packages installed only for it with Pacman's dependency-aware cleanup. - [ ] After final sign-off, destroy the QEMU lab and remove packages installed only for it with Pacman's dependency-aware cleanup.
@ -198,8 +199,8 @@ task. Mark a task `[x]` only after its stated verification passes.
## Human-owned deployment and infrastructure ## Human-owned deployment and infrastructure
- [H] Define infrastructure modules under `terraform/`. - [x] Define libvirt infrastructure and Helm platform stacks under `terraform/`.
- [H] Export provisioned addresses for Ansible inventory. - [x] Export stable node addresses to an Ansible inventory without provisioner hooks.
- [H] Create Kubernetes base resources and overlays. Agent-authored 2026-08-21 at the owner's request; builds and server dry-runs pass, not applied. - [H] Create Kubernetes base resources and overlays. Agent-authored 2026-08-21 at the owner's request; builds and server dry-runs pass, not applied.
- [H] Create API and load-generator workloads. Agent-authored 2026-08-21; not applied. - [H] Create API and load-generator workloads. Agent-authored 2026-08-21; not applied.
- [H] Create active and preview Services. Agent-authored 2026-08-21; not applied. - [H] Create active and preview Services. Agent-authored 2026-08-21; not applied.
@ -207,7 +208,7 @@ task. Mark a task `[x]` only after its stated verification passes.
- [H] Create the real error-rate AnalysisTemplate. Agent-authored 2026-08-21; PromQL never evaluated against real series. - [H] Create the real error-rate AnalysisTemplate. Agent-authored 2026-08-21; PromQL never evaluated against real series.
- [H] Configure blue-green promotion and automated rollback. Agent-authored 2026-08-21; promotion and abort paths untested with the real API. - [H] Configure blue-green promotion and automated rollback. Agent-authored 2026-08-21; promotion and abort paths untested with the real API.
- [H] Maintain Forgejo Actions and GitHub mirror workflows. - [H] Maintain Forgejo Actions and GitHub mirror workflows.
- [H] Create sealed secrets from off-repository plaintext inputs. - [~] Install Sealed Secrets and document off-repository sealing; real encrypted values await owner-provided secrets.
## Forgejo CI/CD ## Forgejo CI/CD

View file

@ -37,10 +37,11 @@ can later be managed by Ansible without rebuilding it.
## Terraform path ## Terraform path
Terraform is responsible only for creating machines, networks, and addresses. Terraform is responsible only for creating machines, networks, and addresses.
After `terraform apply`, put its resulting addresses into `inventory.yml` and `terraform/infra` creates two Fedora 44 libvirt machines and writes its resulting
run this playbook. Keeping configuration out of provisioner hooks makes the addresses to `terraform/infra/inventory.yml`. Pass that file to `bootstrap.sh`
same Ansible workflow usable for physical hardware, VMs, and manually created after `terraform apply`. Keeping configuration out of provisioner hooks makes
hosts. the same Ansible workflow usable for physical hardware, VMs, and manually
created hosts.
## One-command local QEMU lab ## One-command local QEMU lab

44
terraform/infra/.terraform.lock.hcl generated Normal file
View file

@ -0,0 +1,44 @@
# This file is maintained automatically by "terraform init".
# Manual edits may be lost in future updates.
provider "registry.terraform.io/dmacvicar/libvirt" {
version = "0.8.3"
constraints = "0.8.3"
hashes = [
"h1:Tttxr3E9O75MM+dDmq5sYHQEw29PwtIj+XDj/5drdfE=",
"zh:06ff0169beafd1891dc5a30616983abd32004a4f570d1d3dbb5851d84bd1c007",
"zh:2dbdd726d0987cda73b56ecdfbcb98a67485e86a7a44aec976c0081b7239d89d",
"zh:2e195a7bbdfcc13c45460571a5ba848a5c1e746b477c8381058767560f0ac93b",
"zh:3952da13080018c5aec498b73e343c4c22ad884afb8c983138fb7255617aa991",
"zh:478841bcf57df938726ddb90f55c7953fad09db4f6348747519afe7fc84b403b",
"zh:53bce78b03a82c4782acfe1f32c2b46a68fa5fb2fb90d4a5392c90b436b44244",
"zh:5c157f23e9768c67cddf9e847a571adca441607cb5adfb96dbfdd626ceadf92c",
"zh:6bc78d631959fb695664966851308e140c38f3f5cf648dd89756320c2d91765d",
"zh:8605d7d6915190836802654920a8eea3d751ae437273c4f4476dc0ebb9167a1d",
"zh:8b66a22b97331c2a56aed092fd39152d06ad957fd4810aa3f0c4ade0f9b15755",
"zh:92586a47a04082f70bb33f722672127a287caeed109beaaca2668e2e1d6a9caf",
"zh:99a9ee414f5c4268e287660ce8edec2efcba1f79351f83791b64c7e5ab04f569",
"zh:b7cff09fe74b0eb63b5b9aa94de5b33dadbd006d6d5b9578ac476039ea20b062",
"zh:d4188a343ff32c0e03ff28c7e84abce0f43cad2fdbcd9046eaafc247429039ff",
]
}
provider "registry.terraform.io/hashicorp/local" {
version = "2.5.3"
constraints = "2.5.3"
hashes = [
"h1:1Nkh16jQJMp0EuDmvP/96f5Unnir0z12WyDuoR6HjMo=",
"zh:284d4b5b572eacd456e605e94372f740f6de27b71b4e1fd49b63745d8ecd4927",
"zh:40d9dfc9c549e406b5aab73c023aa485633c1b6b730c933d7bcc2fa67fd1ae6e",
"zh:6243509bb208656eb9dc17d3c525c89acdd27f08def427a0dce22d5db90a4c8b",
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
"zh:885d85869f927853b6fe330e235cd03c337ac3b933b0d9ae827ec32fa1fdcdbf",
"zh:bab66af51039bdfcccf85b25fe562cbba2f54f6b3812202f4873ade834ec201d",
"zh:c505ff1bf9442a889ac7dca3ac05a8ee6f852e0118dd9a61796a2f6ff4837f09",
"zh:d36c0b5770841ddb6eaf0499ba3de48e5d4fc99f4829b6ab66b0fab59b1aaf4f",
"zh:ddb6a407c7f3ec63efb4dad5f948b54f7f4434ee1a2607a49680d494b1776fe1",
"zh:e0dafdd4500bec23d3ff221e3a9b60621c5273e5df867bc59ef6b7e41f5c91f6",
"zh:ece8742fd2882a8fc9d6efd20e2590010d43db386b920b2a9c220cfecc18de47",
"zh:f4c6b3eb8f39105004cf720e202f04f57e3578441cfb76ca27611139bc116a82",
]
}

19
terraform/infra/README.md Normal file
View file

@ -0,0 +1,19 @@
# Libvirt infrastructure
This stack creates a dedicated NAT network and two Fedora 44 x86_64 machines.
It stops at the machine boundary. Ansible remains responsible for Fedora and
k3s configuration.
Keep real values in an untracked `terraform.tfvars` file:
```bash
cp terraform.tfvars.example terraform.tfvars
terraform init
terraform apply
K3S_TOKEN="$(openssl rand -hex 32)" \
../../scripts/provision/bootstrap.sh inventory.yml
```
The generated `inventory.yml`, Terraform state, and variable file are ignored.
The SSH public key is safe to place in Terraform input; the matching private key
is referenced by absolute path and is never read into Terraform state.

View file

@ -0,0 +1,12 @@
#cloud-config
hostname: ${hostname}
manage_etc_hosts: true
users:
- name: fedora
groups: [wheel]
sudo: ALL=(ALL) NOPASSWD:ALL
shell: /bin/bash
ssh_authorized_keys:
- ${ssh_public_key}
ssh_pwauth: false
disable_root: true

View file

@ -0,0 +1,24 @@
all:
vars:
ansible_user: fedora
ansible_ssh_private_key_file: ${private_key_file}
ansible_ssh_common_args: "-o StrictHostKeyChecking=accept-new"
k3s_version: v1.33.4+k3s1
k3s_cluster_cidr: 10.42.0.0/16
k3s_service_cidr: 10.43.0.0/16
k3s_node_cidr: ${node_cidr}
k3s_operator_cidrs:
%{ for cidr in operator_cidrs ~}
- ${cidr}
%{ endfor ~}
children:
k3s_server:
hosts:
node1:
ansible_host: ${server_address}
k3s_node_ip: ${server_address}
k3s_agent:
hosts:
node2:
ansible_host: ${agent_address}
k3s_node_ip: ${agent_address}

97
terraform/infra/main.tf Normal file
View file

@ -0,0 +1,97 @@
locals {
nodes = {
nereus-node1 = {
address = var.node_addresses["nereus-node1"]
role = "server"
}
nereus-node2 = {
address = var.node_addresses["nereus-node2"]
role = "agent"
}
}
}
resource "libvirt_network" "nereus" {
name = "nereus"
mode = "nat"
domain = "nereus.test"
addresses = [var.network_cidr]
dhcp {
enabled = true
}
dns {
enabled = true
}
}
resource "libvirt_volume" "fedora" {
name = "nereus-fedora-44-base.qcow2"
pool = "default"
source = var.fedora_image_url
format = "qcow2"
}
resource "libvirt_volume" "node" {
for_each = local.nodes
name = "${each.key}.qcow2"
pool = "default"
base_volume_id = libvirt_volume.fedora.id
size = 30 * 1024 * 1024 * 1024
}
resource "libvirt_cloudinit_disk" "node" {
for_each = local.nodes
name = "${each.key}-cloud-init.iso"
pool = "default"
user_data = templatefile("${path.module}/cloud-init.yaml.tftpl", {
hostname = each.key
ssh_public_key = var.ssh_public_key
})
}
resource "libvirt_domain" "node" {
for_each = local.nodes
name = each.key
memory = 3072
vcpu = 2
autostart = true
cloudinit = libvirt_cloudinit_disk.node[each.key].id
disk {
volume_id = libvirt_volume.node[each.key].id
}
network_interface {
network_id = libvirt_network.nereus.id
hostname = each.key
addresses = [each.value.address]
wait_for_lease = true
}
console {
type = "pty"
target_type = "serial"
target_port = "0"
}
graphics {
type = "spice"
listen_type = "address"
autoport = true
}
}
resource "local_sensitive_file" "inventory" {
filename = "${path.module}/inventory.yml"
file_permission = "0600"
content = templatefile("${path.module}/inventory.yml.tftpl", {
private_key_file = var.ssh_private_key_file
operator_cidrs = var.operator_cidrs
node_cidr = var.network_cidr
server_address = local.nodes["nereus-node1"].address
agent_address = local.nodes["nereus-node2"].address
})
}

View file

@ -0,0 +1,9 @@
output "node_addresses" {
description = "Addresses passed to Ansible."
value = { for name, node in local.nodes : name => node.address }
}
output "ansible_inventory" {
description = "Generated inventory consumed by scripts/provision/bootstrap.sh."
value = local_sensitive_file.inventory.filename
}

View file

@ -0,0 +1,4 @@
ssh_public_key = "replace with one public key"
ssh_private_key_file = "/absolute/path/to/the/matching/private/key"
operator_cidrs = ["192.168.123.1/32"]

View file

@ -0,0 +1,42 @@
variable "libvirt_uri" {
description = "Libvirt connection used to create the Fedora machines."
type = string
default = "qemu:///system"
}
variable "fedora_image_url" {
description = "Fedora 44 Cloud Base Generic x86_64 image URL."
type = string
default = "https://download.fedoraproject.org/pub/fedora/linux/releases/44/Cloud/x86_64/images/Fedora-Cloud-Base-Generic-44-1.7.x86_64.qcow2"
}
variable "ssh_public_key" {
description = "Public SSH key installed for the fedora user."
type = string
}
variable "ssh_private_key_file" {
description = "Absolute private-key path written into the generated Ansible inventory."
type = string
}
variable "operator_cidrs" {
description = "Networks allowed to administer k3s."
type = list(string)
default = ["192.168.123.1/32"]
}
variable "network_cidr" {
description = "Dedicated libvirt network for the two nodes."
type = string
default = "192.168.123.0/24"
}
variable "node_addresses" {
description = "Stable addresses assigned to each node."
type = map(string)
default = {
nereus-node1 = "192.168.123.10"
nereus-node2 = "192.168.123.11"
}
}

View file

@ -0,0 +1,18 @@
terraform {
required_version = ">= 1.10.0"
required_providers {
libvirt = {
source = "dmacvicar/libvirt"
version = "0.8.3"
}
local = {
source = "hashicorp/local"
version = "2.5.3"
}
}
}
provider "libvirt" {
uri = var.libvirt_uri
}

22
terraform/platform/.terraform.lock.hcl generated Normal file
View file

@ -0,0 +1,22 @@
# This file is maintained automatically by "terraform init".
# Manual edits may be lost in future updates.
provider "registry.terraform.io/hashicorp/helm" {
version = "3.1.1"
constraints = "3.1.1"
hashes = [
"h1:5b2ojWKT0noujHiweCds37ZreRFRQLNaErdJLusJN88=",
"zh:1a6d5ce931708aec29d1f3d9e360c2a0c35ba5a54d03eeaff0ce3ca597cd0275",
"zh:3411919ba2a5941801e677f0fea08bdd0ae22ba3c9ce3309f55554699e06524a",
"zh:81b36138b8f2320dc7f877b50f9e38f4bc614affe68de885d322629dd0d16a29",
"zh:95a2a0a497a6082ee06f95b38bd0f0d6924a65722892a856cfd914c0d117f104",
"zh:9d3e78c2d1bb46508b972210ad706dd8c8b106f8b206ecf096cd211c54f46990",
"zh:a79139abf687387a6efdbbb04289a0a8e7eaca2bd91cdc0ce68ea4f3286c2c34",
"zh:aaa8784be125fbd50c48d84d6e171d3fb6ef84a221dbc5165c067ce05faab4c8",
"zh:afecd301f469975c9d8f350cc482fe656e082b6ab0f677d1a816c3c615837cc1",
"zh:c54c22b18d48ff9053d899d178d9ffef7d9d19785d9bf310a07d648b7aac075b",
"zh:db2eefd55aea48e73384a555c72bac3f7d428e24147bedb64e1a039398e5b903",
"zh:ee61666a233533fd2be971091cecc01650561f1585783c381b6f6e8a390198a4",
"zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c",
]
}

View file

@ -0,0 +1,20 @@
# Cluster platform
This stack installs Argo Rollouts, Sealed Secrets, and kube-prometheus-stack on
an existing k3s cluster. Use it on a fresh cluster, or import matching Helm
releases before applying it to a cluster where they already exist.
```bash
cp terraform.tfvars.example terraform.tfvars
terraform init
terraform apply
```
The kubeconfig stays outside the repository. Terraform records only its path.
Application and observability manifests remain under `deploy/` and are applied
after the cluster-wide controllers are ready.
Discord routing is deliberately off by default so a missing webhook cannot
stop Alertmanager. After applying `nereus-discord-sealed.yaml` and confirming
that it produced the `nereus-discord` Secret, set `enable_discord = true` and
apply this stack again.

View file

@ -0,0 +1,23 @@
alertmanager:
alertmanagerSpec:
secrets:
- nereus-discord
config:
global:
resolve_timeout: 5m
route:
receiver: discard
group_by: [alertname, namespace]
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
routes:
- receiver: discord
matchers:
- severity=~"warning|critical"
receivers:
- name: discard
- name: discord
discord_configs:
- webhook_url_file: /etc/alertmanager/secrets/nereus-discord/webhook-url
send_resolved: true

View file

@ -0,0 +1,27 @@
kubeEtcd:
enabled: false
kubeProxy:
enabled: false
prometheus:
prometheusSpec:
retention: 6h
serviceMonitorSelectorNilUsesHelmValues: false
podMonitorSelectorNilUsesHelmValues: false
ruleSelectorNilUsesHelmValues: false
storageSpec:
volumeClaimTemplate:
spec:
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 5Gi
grafana:
defaultDashboardsTimezone: browser
alertmanager:
alertmanagerSpec:
resources:
requests: {cpu: 25m, memory: 64Mi}
limits: {memory: 256Mi}

View file

@ -0,0 +1,44 @@
resource "helm_release" "argo_rollouts" {
name = "argo-rollouts"
repository = "https://argoproj.github.io/argo-helm"
chart = "argo-rollouts"
version = "2.41.1"
namespace = "argo-rollouts"
create_namespace = true
wait = true
timeout = 300
}
resource "helm_release" "sealed_secrets" {
name = "sealed-secrets"
repository = "https://bitnami-labs.github.io/sealed-secrets"
chart = "sealed-secrets"
version = "2.19.3"
namespace = "kube-system"
create_namespace = false
wait = true
timeout = 300
values = [yamlencode({
fullnameOverride = "sealed-secrets-controller"
resources = {
requests = { cpu = "25m", memory = "64Mi" }
limits = { memory = "256Mi" }
}
})]
}
resource "helm_release" "kube_prometheus_stack" {
name = "kube-prometheus-stack"
repository = "https://prometheus-community.github.io/helm-charts"
chart = "kube-prometheus-stack"
version = "88.5.2"
namespace = "observability"
create_namespace = true
wait = true
timeout = 600
values = concat(
[file("${path.module}/kube-prometheus-stack.values.yaml")],
var.enable_discord ? [file("${path.module}/alertmanager-discord.values.yaml")] : []
)
}

View file

@ -0,0 +1,2 @@
kubeconfig_path = "/absolute/path/to/kubeconfig"
enable_discord = false

View file

@ -0,0 +1,10 @@
variable "kubeconfig_path" {
description = "Absolute path to the operator kubeconfig. Its contents never enter Terraform state."
type = string
}
variable "enable_discord" {
description = "Enable Discord routing after the nereus-discord SealedSecret has created its Secret."
type = bool
default = false
}

View file

@ -0,0 +1,16 @@
terraform {
required_version = ">= 1.10.0"
required_providers {
helm = {
source = "hashicorp/helm"
version = "3.1.1"
}
}
}
provider "helm" {
kubernetes = {
config_path = var.kubeconfig_path
}
}