Restructure and variablize config
This commit is contained in:
parent
419bb286d4
commit
c6eed359e1
14 changed files with 122 additions and 28 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -4,3 +4,5 @@ worker.yaml
|
||||||
talosconfig
|
talosconfig
|
||||||
terraform/.terraform/
|
terraform/.terraform/
|
||||||
terraform/.terraform.lock.hcl
|
terraform/.terraform.lock.hcl
|
||||||
|
terraform.tfstate
|
||||||
|
terraform/terraform.tfvars
|
||||||
|
|
|
||||||
25
Makefile
25
Makefile
|
|
@ -1,6 +1,6 @@
|
||||||
.PHONY: init plan apply destroy kubeconfig talosconfig argocd-password bootstrap decrypt post-apply fluentbit-secret
|
.PHONY: init plan apply destroy clean kubeconfig talosconfig argocd-password bootstrap decrypt post-apply fluentbit-secret
|
||||||
|
|
||||||
SECRET=dumpnet
|
SECRET ?= dumpnet
|
||||||
TF_DIR=terraform
|
TF_DIR=terraform
|
||||||
SOPS_FILES=controlplane.yaml worker.yaml talosconfig
|
SOPS_FILES=controlplane.yaml worker.yaml talosconfig
|
||||||
|
|
||||||
|
|
@ -12,11 +12,29 @@ plan:
|
||||||
cd $(TF_DIR) && terraform plan
|
cd $(TF_DIR) && terraform plan
|
||||||
|
|
||||||
apply:
|
apply:
|
||||||
|
cd $(TF_DIR) && terraform apply -target=talos_cluster_kubeconfig.this
|
||||||
cd $(TF_DIR) && terraform apply
|
cd $(TF_DIR) && terraform apply
|
||||||
|
|
||||||
destroy:
|
destroy:
|
||||||
cd $(TF_DIR) && terraform destroy
|
cd $(TF_DIR) && terraform destroy
|
||||||
|
|
||||||
|
# Full clean slate - run before destroy when recreating from scratch
|
||||||
|
# Removes resources that block terraform from recreating cleanly
|
||||||
|
clean:
|
||||||
|
@echo "==> Force-deleting Secrets Manager secret..."
|
||||||
|
-aws secretsmanager delete-secret --secret-id $(SECRET) --force-delete-without-recovery 2>/dev/null || true
|
||||||
|
@echo "==> Emptying S3 buckets..."
|
||||||
|
-aws s3 rm s3://dumpnet-logs --recursive 2>/dev/null || true
|
||||||
|
-aws s3 rm s3://dumpnet-athena-results --recursive 2>/dev/null || true
|
||||||
|
@echo "==> Clearing stale kubeconfig contexts..."
|
||||||
|
-kubectl config delete-context admin@dumpnet 2>/dev/null || true
|
||||||
|
-kubectl config delete-cluster dumpnet 2>/dev/null || true
|
||||||
|
-kubectl config delete-user admin@dumpnet 2>/dev/null || true
|
||||||
|
@echo "==> Clearing stale talosctl contexts..."
|
||||||
|
-talosctl config remove dumpnet 2>/dev/null || true
|
||||||
|
-talosctl config remove dumpnet-1 2>/dev/null || true
|
||||||
|
@echo "==> Done. Run 'make destroy' then 'make apply' to rebuild from scratch."
|
||||||
|
|
||||||
# Bootstrap ArgoCD app of apps (run once after cluster is up)
|
# Bootstrap ArgoCD app of apps (run once after cluster is up)
|
||||||
bootstrap:
|
bootstrap:
|
||||||
kubectl apply -f apps/apps.yaml
|
kubectl apply -f apps/apps.yaml
|
||||||
|
|
@ -32,6 +50,9 @@ talosconfig:
|
||||||
talosctl config context dumpnet
|
talosctl config context dumpnet
|
||||||
|
|
||||||
kubeconfig:
|
kubeconfig:
|
||||||
|
-kubectl config delete-context admin@dumpnet 2>/dev/null || true
|
||||||
|
-kubectl config delete-cluster dumpnet 2>/dev/null || true
|
||||||
|
-kubectl config delete-user admin@dumpnet 2>/dev/null || true
|
||||||
aws secretsmanager get-secret-value --secret-id $(SECRET) \
|
aws secretsmanager get-secret-value --secret-id $(SECRET) \
|
||||||
--query SecretString --output text | python3 -c \
|
--query SecretString --output text | python3 -c \
|
||||||
"import sys,json; print(json.load(sys.stdin)['cluster']['kubeconfig'])" \
|
"import sys,json; print(json.load(sys.stdin)['cluster']['kubeconfig'])" \
|
||||||
|
|
|
||||||
42
README.md
42
README.md
|
|
@ -23,7 +23,7 @@ GitOps cluster management for dumpnet — a single-node Talos/Kubernetes cluster
|
||||||
|
|
||||||
Sensitive files (`talosconfig`, `controlplane.yaml`, `worker.yaml`) are **gitignored** and never stored in the repo in any form. They are managed as follows:
|
Sensitive files (`talosconfig`, `controlplane.yaml`, `worker.yaml`) are **gitignored** and never stored in the repo in any form. They are managed as follows:
|
||||||
|
|
||||||
- **Cluster credentials** (kubeconfig, talosconfig, ArgoCD password) → stored in AWS Secrets Manager at `dumpnet/cluster`
|
- **Cluster credentials** (kubeconfig, talosconfig, ArgoCD password) → stored in AWS Secrets Manager at `dumpnet`
|
||||||
- **Talos machine configs** → encrypted with SOPS + age, stored outside the repo
|
- **Talos machine configs** → encrypted with SOPS + age, stored outside the repo
|
||||||
- **Age private key** → lives at `~/.age/key.txt` — **back this up securely**
|
- **Age private key** → lives at `~/.age/key.txt` — **back this up securely**
|
||||||
|
|
||||||
|
|
@ -57,6 +57,13 @@ cp terraform/terraform.tfvars.example terraform/terraform.tfvars
|
||||||
make apply
|
make apply
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> **Note:** `make apply` runs two Terraform passes internally. The first pass
|
||||||
|
> (`-target=talos_cluster_kubeconfig.this`) brings up the EC2 instance, bootstraps
|
||||||
|
> the cluster, and retrieves the kubeconfig. The second pass then uses that live
|
||||||
|
> kubeconfig to provision Kubernetes resources (namespaces, ArgoCD Helm release).
|
||||||
|
> This two-phase approach is necessary because the Helm and Kubernetes Terraform
|
||||||
|
> providers need a reachable cluster to initialize.
|
||||||
|
|
||||||
This will:
|
This will:
|
||||||
- Allocate an EIP
|
- Allocate an EIP
|
||||||
- Create a dedicated subnet and security group
|
- Create a dedicated subnet and security group
|
||||||
|
|
@ -90,11 +97,23 @@ make argocd-password
|
||||||
|
|
||||||
Then log in at https://argocd.dumpnet.chat — ArgoCD will finish deploying ingress-nginx and metrics-server automatically.
|
Then log in at https://argocd.dumpnet.chat — ArgoCD will finish deploying ingress-nginx and metrics-server automatically.
|
||||||
|
|
||||||
|
## Rebuilding from Scratch
|
||||||
|
|
||||||
|
To fully tear down and recreate the cluster:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make clean # force-delete secret, empty S3, clear stale kube/talos contexts
|
||||||
|
make destroy # tear down all terraform resources
|
||||||
|
make apply # recreate everything
|
||||||
|
make post-apply # wait for cluster ready, bootstrap ArgoCD
|
||||||
|
make bootstrap # apply App of Apps
|
||||||
|
```
|
||||||
|
|
||||||
## Day-to-Day
|
## Day-to-Day
|
||||||
|
|
||||||
- **Add a new app**: add a manifest to `manifests/` and values to `charts/` — ArgoCD picks it up on next sync
|
- **Add a new app**: add a manifest to `manifests/` and values to `charts/` — ArgoCD picks it up on next sync
|
||||||
- **Add a DNS record**: add the subdomain to `dns_records` in `terraform/terraform.tfvars` and run `make apply`
|
- **Add a DNS record**: add the subdomain to `dns_records` in `terraform/terraform.tfvars` and run `make apply`
|
||||||
- **Cluster access**: `make kubeconfig` or `make talosconfig` (pulls from Secrets Manager)
|
- **Cluster access**: `make kubeconfig` or `make talosconfig` (pulls from Secrets Manager, safe to re-run — automatically clears any stale context from a previous cluster before merging)
|
||||||
- **Emergency talosctl access**: `make decrypt` to get configs in `/tmp`
|
- **Emergency talosctl access**: `make decrypt` to get configs in `/tmp`
|
||||||
|
|
||||||
## Makefile Reference
|
## Makefile Reference
|
||||||
|
|
@ -104,9 +123,10 @@ Then log in at https://argocd.dumpnet.chat — ArgoCD will finish deploying ingr
|
||||||
| `make apply` | Create/update cluster infrastructure |
|
| `make apply` | Create/update cluster infrastructure |
|
||||||
| `make plan` | Preview infrastructure changes |
|
| `make plan` | Preview infrastructure changes |
|
||||||
| `make destroy` | Tear down everything |
|
| `make destroy` | Tear down everything |
|
||||||
|
| `make clean` | Pre-destroy cleanup (force-delete secret, empty S3 buckets, clear stale kube/talos contexts) |
|
||||||
| `make post-apply` | One-time bootstrap after fresh cluster creation |
|
| `make post-apply` | One-time bootstrap after fresh cluster creation |
|
||||||
| `make bootstrap` | Apply App of Apps only |
|
| `make bootstrap` | Apply App of Apps only |
|
||||||
| `make kubeconfig` | Pull kubeconfig from Secrets Manager |
|
| `make kubeconfig` | Pull kubeconfig from Secrets Manager (clears stale context first) |
|
||||||
| `make talosconfig` | Pull talosconfig from Secrets Manager |
|
| `make talosconfig` | Pull talosconfig from Secrets Manager |
|
||||||
| `make argocd-password` | Print ArgoCD admin password |
|
| `make argocd-password` | Print ArgoCD admin password |
|
||||||
| `make decrypt` | Decrypt Talos configs to /tmp |
|
| `make decrypt` | Decrypt Talos configs to /tmp |
|
||||||
|
|
@ -141,6 +161,22 @@ dumpnet-argo/
|
||||||
└── outputs.tf
|
└── outputs.tf
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Forking / Multiple Environments
|
||||||
|
|
||||||
|
Global per-cluster config lives in **`values.yaml`** at the repo root:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
clusterName: dumpnet
|
||||||
|
domain: dumpnet.chat
|
||||||
|
repoURL: https://forge.keane.sh/ian/dumpnet-argo.git
|
||||||
|
certEmail: dumpnetcerts@keane.sh
|
||||||
|
awsRegion: us-east-1
|
||||||
|
```
|
||||||
|
|
||||||
|
This file is passed as the first `valueFiles` entry to every Helm chart, so `domain`, `certEmail`, `clusterName`, and `awsRegion` are available as `{{ .Values.* }}` in all chart values. Terraform variables in `terraform/variables.tf` mirror these same settings for the infrastructure side.
|
||||||
|
|
||||||
|
**The one thing that can't be templated** is `repoURL` in the ArgoCD `Application` manifests themselves (under `apps/` and `manifests/`). These are plain YAML consumed by ArgoCD before any Helm rendering happens — there's no way to interpolate them without a [Config Management Plugin](https://argo-cd.readthedocs.io/en/stable/operator-manual/config-management-plugins/). When forking this repo, do a global find/replace on `forge.keane.sh/ian/dumpnet-argo` with your own repo URL.
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
- The control plane taint is disabled via `allowSchedulingOnControlPlanes: true` in the Talos machine config — no manual taint removal needed
|
- The control plane taint is disabled via `allowSchedulingOnControlPlanes: true` in the Talos machine config — no manual taint removal needed
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ server:
|
||||||
ingress:
|
ingress:
|
||||||
enabled: true
|
enabled: true
|
||||||
ingressClassName: nginx
|
ingressClassName: nginx
|
||||||
hostname: argocd.dumpnet.chat
|
hostname: argocd.{{ .Values.domain }}
|
||||||
tls: true
|
tls: true
|
||||||
annotations:
|
annotations:
|
||||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ metadata:
|
||||||
spec:
|
spec:
|
||||||
acme:
|
acme:
|
||||||
server: https://acme-v02.api.letsencrypt.org/directory
|
server: https://acme-v02.api.letsencrypt.org/directory
|
||||||
email: dumpnetcerts@keane.sh
|
email: {{ .Values.certEmail }}
|
||||||
privateKeySecretRef:
|
privateKeySecretRef:
|
||||||
name: letsencrypt-prod
|
name: letsencrypt-prod
|
||||||
solvers:
|
solvers:
|
||||||
|
|
@ -20,7 +20,7 @@ metadata:
|
||||||
spec:
|
spec:
|
||||||
acme:
|
acme:
|
||||||
server: https://acme-staging-v02.api.letsencrypt.org/directory
|
server: https://acme-staging-v02.api.letsencrypt.org/directory
|
||||||
email: dumpnetcerts@keane.sh
|
email: {{ .Values.certEmail }}
|
||||||
privateKeySecretRef:
|
privateKeySecretRef:
|
||||||
name: letsencrypt-staging
|
name: letsencrypt-staging
|
||||||
solvers:
|
solvers:
|
||||||
|
|
|
||||||
|
|
@ -47,8 +47,8 @@ config:
|
||||||
[OUTPUT]
|
[OUTPUT]
|
||||||
Name s3
|
Name s3
|
||||||
Match kube.*
|
Match kube.*
|
||||||
bucket dumpnet-logs
|
bucket {{ .Values.clusterName }}-logs
|
||||||
region us-east-1
|
region {{ .Values.awsRegion }}
|
||||||
s3_key_format /logs/%Y/%m/%d/$TAG[4].%H%M%S.gz
|
s3_key_format /logs/%Y/%m/%d/$TAG[4].%H%M%S.gz
|
||||||
total_file_size 100M
|
total_file_size 100M
|
||||||
upload_timeout 10m
|
upload_timeout 10m
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ spec:
|
||||||
targetRevision: "*"
|
targetRevision: "*"
|
||||||
helm:
|
helm:
|
||||||
valueFiles:
|
valueFiles:
|
||||||
|
- $values/values.yaml
|
||||||
- $values/charts/argocd/values.yaml
|
- $values/charts/argocd/values.yaml
|
||||||
destination:
|
destination:
|
||||||
server: https://kubernetes.default.svc
|
server: https://kubernetes.default.svc
|
||||||
|
|
|
||||||
|
|
@ -32,12 +32,17 @@ metadata:
|
||||||
namespace: argocd
|
namespace: argocd
|
||||||
spec:
|
spec:
|
||||||
project: default
|
project: default
|
||||||
source:
|
sources:
|
||||||
repoURL: https://forge.keane.sh/ian/dumpnet-argo.git
|
- repoURL: https://forge.keane.sh/ian/dumpnet-argo.git
|
||||||
targetRevision: HEAD
|
targetRevision: HEAD
|
||||||
path: charts/cert-manager
|
ref: values
|
||||||
directory:
|
- repoURL: https://forge.keane.sh/ian/dumpnet-argo.git
|
||||||
include: "cluster-issuers.yaml"
|
targetRevision: HEAD
|
||||||
|
path: charts/cert-manager
|
||||||
|
helm:
|
||||||
|
valueFiles:
|
||||||
|
- $values/values.yaml
|
||||||
|
releaseName: cert-manager-issuers
|
||||||
destination:
|
destination:
|
||||||
server: https://kubernetes.default.svc
|
server: https://kubernetes.default.svc
|
||||||
namespace: cert-manager
|
namespace: cert-manager
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ spec:
|
||||||
targetRevision: "*"
|
targetRevision: "*"
|
||||||
helm:
|
helm:
|
||||||
valueFiles:
|
valueFiles:
|
||||||
|
- $values/values.yaml
|
||||||
- $values/charts/fluent-bit/values.yaml
|
- $values/charts/fluent-bit/values.yaml
|
||||||
destination:
|
destination:
|
||||||
server: https://kubernetes.default.svc
|
server: https://kubernetes.default.svc
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,10 @@ resource "random_password" "argocd_admin" {
|
||||||
override_special = "!#$%&*()-_=+[]{}?"
|
override_special = "!#$%&*()-_=+[]{}?"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Single secrets manager secret for all dumpnet secrets
|
# Single secrets manager secret for all cluster secrets
|
||||||
resource "aws_secretsmanager_secret" "dumpnet" {
|
resource "aws_secretsmanager_secret" "dumpnet" {
|
||||||
name = "dumpnet"
|
name = var.cluster_name
|
||||||
description = "All dumpnet secrets"
|
description = "All ${var.cluster_name} secrets"
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "aws_secretsmanager_secret_version" "dumpnet" {
|
resource "aws_secretsmanager_secret_version" "dumpnet" {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# S3 bucket for logs
|
# S3 bucket for logs
|
||||||
resource "aws_s3_bucket" "logs" {
|
resource "aws_s3_bucket" "logs" {
|
||||||
bucket = "dumpnet-logs"
|
bucket = "${var.cluster_name}-logs"
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "aws_s3_bucket_lifecycle_configuration" "logs" {
|
resource "aws_s3_bucket_lifecycle_configuration" "logs" {
|
||||||
|
|
@ -10,7 +10,9 @@ resource "aws_s3_bucket_lifecycle_configuration" "logs" {
|
||||||
id = "expire-old-logs"
|
id = "expire-old-logs"
|
||||||
status = "Enabled"
|
status = "Enabled"
|
||||||
|
|
||||||
filter {}
|
filter {
|
||||||
|
prefix = ""
|
||||||
|
}
|
||||||
|
|
||||||
transition {
|
transition {
|
||||||
days = 30
|
days = 30
|
||||||
|
|
@ -34,16 +36,16 @@ resource "aws_s3_bucket_server_side_encryption_configuration" "logs" {
|
||||||
|
|
||||||
# Athena database + workgroup
|
# Athena database + workgroup
|
||||||
resource "aws_athena_database" "logs" {
|
resource "aws_athena_database" "logs" {
|
||||||
name = "dumpnet_logs"
|
name = "${replace(var.cluster_name, "-", "_")}_logs"
|
||||||
bucket = aws_s3_bucket.athena_results.bucket
|
bucket = aws_s3_bucket.athena_results.bucket
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "aws_s3_bucket" "athena_results" {
|
resource "aws_s3_bucket" "athena_results" {
|
||||||
bucket = "dumpnet-athena-results"
|
bucket = "${var.cluster_name}-athena-results"
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "aws_athena_workgroup" "logs" {
|
resource "aws_athena_workgroup" "logs" {
|
||||||
name = "dumpnet-logs"
|
name = "${var.cluster_name}-logs"
|
||||||
|
|
||||||
configuration {
|
configuration {
|
||||||
result_configuration {
|
result_configuration {
|
||||||
|
|
@ -58,7 +60,7 @@ resource "aws_athena_workgroup" "logs" {
|
||||||
|
|
||||||
# IAM user for fluent-bit to write to S3
|
# IAM user for fluent-bit to write to S3
|
||||||
resource "aws_iam_user" "fluentbit" {
|
resource "aws_iam_user" "fluentbit" {
|
||||||
name = "dumpnet-fluentbit"
|
name = "${var.cluster_name}-fluentbit"
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "aws_iam_access_key" "fluentbit" {
|
resource "aws_iam_access_key" "fluentbit" {
|
||||||
|
|
|
||||||
|
|
@ -3,5 +3,5 @@ output "public_ip" {
|
||||||
}
|
}
|
||||||
|
|
||||||
output "argocd_password_secret" {
|
output "argocd_password_secret" {
|
||||||
value = "aws secretsmanager get-secret-value --secret-id dumpnet/cluster --query SecretString --output text | python3 -m json.tool"
|
value = "aws secretsmanager get-secret-value --secret-id ${var.cluster_name} --query SecretString --output text | python3 -m json.tool"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,8 @@ variable "talos_version" {
|
||||||
}
|
}
|
||||||
|
|
||||||
variable "cluster_name" {
|
variable "cluster_name" {
|
||||||
default = "dumpnet"
|
description = "Name used for cluster resources, S3 buckets, secrets, etc."
|
||||||
|
default = "dumpnet"
|
||||||
}
|
}
|
||||||
|
|
||||||
variable "instance_type" {
|
variable "instance_type" {
|
||||||
|
|
@ -19,8 +20,13 @@ variable "ami_id" {
|
||||||
default = "ami-000f1b0ad9d8ceafd"
|
default = "ami-000f1b0ad9d8ceafd"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
variable "aws_region" {
|
||||||
|
description = "AWS region"
|
||||||
|
default = "us-east-1"
|
||||||
|
}
|
||||||
|
|
||||||
variable "hosted_zone_id" {
|
variable "hosted_zone_id" {
|
||||||
description = "Route53 hosted zone ID for dumpnet.chat"
|
description = "Route53 hosted zone ID"
|
||||||
}
|
}
|
||||||
|
|
||||||
variable "domain" {
|
variable "domain" {
|
||||||
|
|
@ -33,3 +39,13 @@ variable "dns_records" {
|
||||||
type = list(string)
|
type = list(string)
|
||||||
default = ["argocd"]
|
default = ["argocd"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
variable "cert_manager_email" {
|
||||||
|
description = "Email for Let's Encrypt certificate notifications"
|
||||||
|
default = "dumpnetcerts@keane.sh"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "state_bucket" {
|
||||||
|
description = "S3 bucket for Terraform remote state (set in backend config, documented here for reference)"
|
||||||
|
default = "iankeane-tfstate"
|
||||||
|
}
|
||||||
|
|
|
||||||
10
values.yaml
Normal file
10
values.yaml
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
# Global cluster configuration
|
||||||
|
# Override any of these in terraform.tfvars or by forking the repo.
|
||||||
|
# NOTE: repoURL in apps/apps.yaml and manifests/*.yaml must be updated manually —
|
||||||
|
# ArgoCD Application spec fields cannot be templated via Helm values.
|
||||||
|
|
||||||
|
clusterName: dumpnet
|
||||||
|
domain: dumpnet.chat
|
||||||
|
repoURL: https://forge.keane.sh/ian/dumpnet-argo.git
|
||||||
|
certEmail: dumpnetcerts@keane.sh
|
||||||
|
awsRegion: us-east-1
|
||||||
Loading…
Add table
Add a link
Reference in a new issue