feat(ci,iac): Consolidate Forgejo CI workflows and add Talos Terraform IaC

Consolidate three separate Forgejo Actions (argocd-sync, security-scan, validate-k8s) into single cluster-ci workflow for cleaner CI/CD pipeline with proper job sequencing and reduced auth overhead.

Add Terraform configuration for Talos cluster machine configs:
- Provider setup for Talos
- Centralized variables for CP and worker configs
- Template-based config generation for controlplane.yaml and worker-*.yaml
- Sensitive data separated in terraform.tfvars (gitignored)
- Local state tracking for infrastructure
This commit is contained in:
Story Crater Bot
2026-07-17 23:44:08 -07:00
parent 7437078f23
commit c6493f14ae
14 changed files with 831 additions and 876 deletions
+15
View File
@@ -0,0 +1,15 @@
# Terraform state
*.tfstate
*.tfstate.*
.terraform/
.terraform.lock.hcl
# Variables with secrets
terraform.tfvars
*.auto.tfvars
# IDE
.vscode/
*.swp
*.swo
*~
+126
View File
@@ -0,0 +1,126 @@
# Generate Talos machine configurations
locals {
pod_cidr = var.cluster_config.pod_subnets[0]
service_cidr = var.cluster_config.service_subnets[0]
controlplane_ip = var.cluster_config.controlplane_ip
cluster_dns_ip = "10.96.0.10"
kubelet_image = "ghcr.io/siderolabs/kubelet:${var.kubernetes_version}"
kube_apiserver_img = "registry.k8s.io/kube-apiserver:${var.kubernetes_version}"
controller_mgr_img = "registry.k8s.io/kube-controller-manager:${var.kubernetes_version}"
kube_proxy_img = "registry.k8s.io/kube-proxy:${var.kubernetes_version}"
scheduler_img = "registry.k8s.io/kube-scheduler:${var.kubernetes_version}"
factory_image = "factory.talos.dev/installer/613e1592b2da41ae5e265e8789429f22e121aab91cb4deb6bc3c0b6262961245:${var.talos_version}"
}
# Control plane machine configuration
resource "local_file" "controlplane_config" {
filename = "${path.module}/../cluster-config/controlplane.yaml"
content = templatefile("${path.module}/templates/controlplane.tftpl", {
version = "v1alpha1"
hostname = var.controlplane_config.hostname
token = var.machine_token
ca_crt = var.machine_ca_crt
ca_key = var.machine_ca_key
lan_ip = var.controlplane_config.lan_ip
lan_subnet = var.controlplane_config.lan_subnet
lan_gateway = var.controlplane_config.lan_gateway
wg0_ip = var.controlplane_config.wg0_ip
wg0_subnet = var.controlplane_config.wg0_subnet
wg0_port = var.controlplane_config.wg0_port
wg0_private_key = var.controlplane_config.wg0_private_key
wg0_peers = var.controlplane_config.wg0_peers
wg1_ip = var.controlplane_config.wg1_ip
wg1_subnet = var.controlplane_config.wg1_subnet
wg1_port = var.controlplane_config.wg1_port
wg1_private_key = var.controlplane_config.wg1_private_key
wg1_peers = var.controlplane_config.wg1_peers
kubelet_image = local.kubelet_image
cluster_dns_ip = local.cluster_dns_ip
install_disk = var.controlplane_config.install_disk
factory_image = local.factory_image
longhorn_disks = var.controlplane_config.longhorn_disks
dns_servers = var.cluster_config.dns_servers
forgejo_registry_ip = var.forgejo_registry_ip
forgejo_hostname = var.forgejo_hostname
# Cluster config
cluster_id = var.cluster_id
cluster_secret = var.cluster_secret
controlplane_ip = local.controlplane_ip
cluster_name = var.cluster_name
pod_subnets = var.cluster_config.pod_subnets
service_subnets = var.cluster_config.service_subnets
dns_domain = var.cluster_config.dns_domain
bootstrap_token = var.bootstrap_token
# Kubernetes certs
kubernetes_ca_crt = var.kubernetes_ca_crt
kubernetes_ca_key = var.kubernetes_ca_key
etcd_ca_crt = var.etcd_ca_crt
etcd_ca_key = var.etcd_ca_key
aggregator_ca_crt = var.aggregator_ca_crt
aggregator_ca_key = var.aggregator_ca_key
service_account_key = var.service_account_key
secretbox_encryption_secret = var.secretbox_encryption_secret
# Component images
kube_apiserver_img = local.kube_apiserver_img
controller_mgr_img = local.controller_mgr_img
kube_proxy_img = local.kube_proxy_img
scheduler_img = local.scheduler_img
})
}
# Worker machine configurations
resource "local_file" "worker_configs" {
for_each = var.worker_configs
filename = "${path.module}/../cluster-config/${each.key}.yaml"
content = templatefile("${path.module}/templates/worker.tftpl", {
version = "v1alpha1"
hostname = each.value.hostname
token = var.machine_token
ca_crt = var.machine_ca_crt
lan_ip = each.value.lan_ip
lan_subnet = each.value.lan_subnet
lan_gateway = each.value.lan_gateway
kubelet_image = local.kubelet_image
cluster_dns_ip = local.cluster_dns_ip
install_disk = each.value.install_disk
factory_image = local.factory_image
node_labels = each.value.node_labels
# Cluster config
cluster_id = var.cluster_id
cluster_secret = var.cluster_secret
controlplane_ip = local.controlplane_ip
cluster_name = var.cluster_name
pod_subnets = var.cluster_config.pod_subnets
service_subnets = var.cluster_config.service_subnets
dns_domain = var.cluster_config.dns_domain
bootstrap_token = var.bootstrap_token
# Kubernetes certs
kubernetes_ca_crt = var.kubernetes_ca_crt
# Component images
kube_proxy_img = local.kube_proxy_img
})
}
# Output paths for reference
output "controlplane_config_path" {
value = local_file.controlplane_config.filename
description = "Path to generated controlplane config"
}
output "worker_config_paths" {
value = {
for k, v in local_file.worker_configs : k => v.filename
}
description = "Paths to generated worker configs"
}
+12
View File
@@ -0,0 +1,12 @@
terraform {
required_providers {
talos = {
source = "siderolabs/talos"
version = "~> 0.7"
}
}
required_version = ">= 1.0"
}
provider "talos" {}
+179
View File
@@ -0,0 +1,179 @@
version: ${version}
debug: false
persist: true
machine:
type: controlplane
token: ${token}
ca:
crt: ${ca_crt}
key: ${ca_key}
certSANs:
- ${lan_ip}
- ${wg0_ip}
network:
hostname: ${hostname}
interfaces:
- interface: eno1
addresses:
- ${lan_ip}/24
routes:
- network: 0.0.0.0/0
gateway: ${lan_gateway}
dhcp: false
dhcpOptions:
ipv6: false
- interface: wg0
addresses:
- ${wg0_ip}/24
wireguard:
privateKey: "${wg0_private_key}"
listenPort: ${wg0_port}
peers:
%{ for peer in wg0_peers ~}
- publicKey: "${peer.public_key}"
allowedIPs:
%{ for ip in peer.allowed_ips ~}
- ${ip}
%{ endfor ~}
%{ endfor ~}
- interface: wg1
addresses:
- ${wg1_ip}/24
wireguard:
privateKey: "${wg1_private_key}"
listenPort: ${wg1_port}
peers:
%{ for peer in wg1_peers ~}
- publicKey: "${peer.public_key}"
allowedIPs:
%{ for ip in peer.allowed_ips ~}
- ${ip}
%{ endfor ~}
persistentKeepaliveInterval: ${peer.persistent_keepalive_secs}s
%{ endfor ~}
nameservers:
%{ for ns in dns_servers ~}
- ${ns}
%{ endfor ~}
extraHostEntries:
- ip: ${forgejo_registry_ip}
aliases:
- ${forgejo_hostname}
kubelet:
image: ${kubelet_image}
defaultRuntimeSeccompProfileEnabled: true
disableManifestsDirectory: true
clusterDNS:
- ${cluster_dns_ip}
extraArgs:
rotate-server-certificates: true
nodeIP:
validSubnets:
- 192.168.1.0/24
install:
disk: ${install_disk}
image: factory.talos.dev/installer/613e1592b2da41ae5e265e8789429f22e121aab91cb4deb6bc3c0b6262961245:${talos_version}
wipe: true
grubUseUKICmdline: true
disks:
%{ for disk in longhorn_disks ~}
- device: ${disk.device}
partitions:
- mountpoint: ${disk.mountpoint}
%{ endfor ~}
features:
diskQuotaSupport: true
kubePrism:
enabled: true
port: 7445
hostDNS:
enabled: false
nodeLabels:
node.kubernetes.io/exclude-from-external-load-balancers: ""
topology.kubernetes.io/region: homelab
topology.kubernetes.io/zone: az-a
cluster:
id: ${cluster_id}
secret: ${cluster_secret}
controlPlane:
endpoint: https://${controlplane_ip}:6443
clusterName: ${cluster_name}
allowSchedulingOnControlPlanes: true
network:
dnsDomain: ${dns_domain}
podSubnets:
%{ for subnet in pod_subnets ~}
- ${subnet}
%{ endfor ~}
serviceSubnets:
%{ for subnet in service_subnets ~}
- ${subnet}
%{ endfor ~}
cni:
name: none
token: ${bootstrap_token}
secretboxEncryptionSecret: ${secretbox_encryption_secret}
ca:
crt: ${kubernetes_ca_crt}
key: ${kubernetes_ca_key}
aggregatorCA:
crt: ${aggregator_ca_crt}
key: ${aggregator_ca_key}
serviceAccount:
key: ${service_account_key}
apiServer:
certSANs:
- ${controlplane_ip}
- ${wg0_ip}
image: ${kube_apiserver_img}
admissionControl:
- name: PodSecurity
configuration:
apiVersion: pod-security.admission.config.k8s.io/v1alpha1
defaults:
audit: restricted
audit-version: latest
enforce: baseline
enforce-version: latest
warn: restricted
warn-version: latest
exemptions:
namespaces:
- kube-system
runtimeClasses: []
usernames: []
kind: PodSecurityConfiguration
auditPolicy:
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
- level: Metadata
controllerManager:
image: ${controller_mgr_img}
proxy:
image: ${kube_proxy_img}
disabled: true
scheduler:
image: ${scheduler_img}
discovery:
enabled: true
registries:
kubernetes:
disabled: true
service: {}
etcd:
ca:
crt: ${etcd_ca_crt}
key: ${etcd_ca_key}
extraManifests:
- https://raw.githubusercontent.com/alex1989hu/kubelet-serving-cert-approver/main/deploy/standalone-install.yaml
- https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
inlineManifests:
- name: cilium
contents: |
apiVersion: v1
kind: Namespace
metadata:
name: kube-system
+83
View File
@@ -0,0 +1,83 @@
version: ${version}
debug: false
persist: true
machine:
type: worker
token: ${token}
ca:
crt: ${ca_crt}
key: ""
certSANs: []
network:
hostname: ${hostname}
interfaces:
- interface: eno1
addresses:
- ${lan_ip}/24
routes:
- network: 0.0.0.0/0
gateway: ${lan_gateway}
dhcp: false
nameservers:
- ${cluster_dns_ip}
- 8.8.8.8
- 1.1.1.1
kubelet:
image: ${kubelet_image}
defaultRuntimeSeccompProfileEnabled: true
disableManifestsDirectory: true
extraArgs:
rotate-server-certificates: true
install:
disk: ${install_disk}
image: factory.talos.dev/installer/613e1592b2da41ae5e265e8789429f22e121aab91cb4deb6bc3c0b6262961245:${talos_version}
wipe: true
grubUseUKICmdline: true
registries: {}
features:
diskQuotaSupport: true
kubePrism:
enabled: true
port: 7445
hostDNS:
enabled: true
forwardKubeDNSToHost: true
nodeLabels:
%{ for k, v in node_labels ~}
${k}: ${v}
%{ endfor ~}
cluster:
id: ${cluster_id}
secret: ${cluster_secret}
controlPlane:
endpoint: https://${controlplane_ip}:6443
clusterName: ${cluster_name}
network:
dnsDomain: ${dns_domain}
podSubnets:
%{ for subnet in pod_subnets ~}
- ${subnet}
%{ endfor ~}
serviceSubnets:
%{ for subnet in service_subnets ~}
- ${subnet}
%{ endfor ~}
cni:
name: none
token: ${bootstrap_token}
ca:
crt: ${kubernetes_ca_crt}
key: ""
discovery:
enabled: true
registries:
kubernetes:
disabled: true
service: {}
proxy:
disabled: true
extraManifests:
- https://raw.githubusercontent.com/alex1989hu/kubelet-serving-cert-approver/main/deploy/standalone-install.yaml
- https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
+168
View File
@@ -0,0 +1,168 @@
variable "talos_version" {
type = string
default = "v1.13.3"
description = "Talos version"
}
variable "kubernetes_version" {
type = string
default = "v1.36.1"
description = "Kubernetes version"
}
variable "cluster_name" {
type = string
default = "homelab-cluster"
description = "Cluster name"
}
variable "cluster_id" {
type = string
sensitive = true
description = "Globally unique cluster ID (base64 encoded)"
}
variable "cluster_secret" {
type = string
sensitive = true
description = "Shared cluster secret (base64 encoded)"
}
variable "bootstrap_token" {
type = string
sensitive = true
description = "Bootstrap token for joining cluster"
}
variable "machine_token" {
type = string
sensitive = true
description = "Machine PKI token"
}
variable "machine_ca_crt" {
type = string
sensitive = true
description = "Machine CA certificate (base64 encoded)"
}
variable "machine_ca_key" {
type = string
sensitive = true
description = "Machine CA private key (base64 encoded)"
}
variable "kubernetes_ca_crt" {
type = string
sensitive = true
description = "Kubernetes CA certificate (base64 encoded)"
}
variable "kubernetes_ca_key" {
type = string
sensitive = true
description = "Kubernetes CA private key (base64 encoded)"
}
variable "etcd_ca_crt" {
type = string
sensitive = true
description = "Etcd CA certificate (base64 encoded)"
}
variable "etcd_ca_key" {
type = string
sensitive = true
description = "Etcd CA private key (base64 encoded)"
}
variable "aggregator_ca_crt" {
type = string
sensitive = true
description = "Aggregator CA certificate (base64 encoded)"
}
variable "aggregator_ca_key" {
type = string
sensitive = true
description = "Aggregator CA private key (base64 encoded)"
}
variable "service_account_key" {
type = string
sensitive = true
description = "Service account private key (base64 encoded)"
}
variable "secretbox_encryption_secret" {
type = string
sensitive = true
description = "Secretbox encryption secret (base64 encoded)"
}
variable "controlplane_config" {
type = object({
hostname = string
lan_ip = string
lan_subnet = string
lan_gateway = string
wg0_ip = string
wg0_subnet = string
wg0_port = number
wg0_peers = list(object({
public_key = string
allowed_ips = list(string)
}))
wg1_ip = string
wg1_subnet = string
wg1_port = number
wg1_peers = list(object({
public_key = string
allowed_ips = list(string)
persistent_keepalive_secs = number
}))
wg0_private_key = string
wg1_private_key = string
install_disk = string
longhorn_disks = list(object({
device = string
mountpoint = string
}))
})
description = "Control plane machine configuration"
}
variable "worker_configs" {
type = map(object({
hostname = string
lan_ip = string
lan_subnet = string
lan_gateway = string
install_disk = string
node_labels = map(string)
}))
description = "Worker machine configurations"
}
variable "cluster_config" {
type = object({
controlplane_ip = string
pod_subnets = list(string)
service_subnets = list(string)
dns_servers = list(string)
dns_domain = string
})
description = "Cluster-wide configuration"
}
variable "forgejo_registry_ip" {
type = string
default = "10.107.155.96"
description = "Forgejo registry (container repo) ClusterIP for host DNS rewrite"
}
variable "forgejo_hostname" {
type = string
default = "forgejo.riotpiao.homelab.com"
description = "Forgejo external hostname"
}