22 Commits

Author SHA1 Message Date
unkinben 5d6d6a9441 fix(stalwart): pin stalwart-external LoadBalancer to 198.18.199.2 2026-05-24 19:54:29 +10:00
unkinben 572b6bfd9f fix(stalwart): add purelb dmz service-group annotation to LoadBalancer 2026-05-24 19:52:03 +10:00
unkinben b465763302 feat(stalwart): use Valkey for in-memory store, improve health probes
- Replace PostgreSQL in-memory store with Valkey (Redis-compatible) for
  better performance on rate limiting, distributed locks, and OAuth codes
- Add single-replica Valkey deployment with no persistence (data is transient)
- Switch liveness/readiness probes to HTTP GET /healthz/live and
  /healthz/ready on port 8080 per official Kubernetes probe documentation
- Update webadmin resource URL to use artifactapi proxy instead of direct
  GitHub download
2026-05-24 12:56:32 +10:00
unkinben 0d89a69c18 feat(stalwart): deploy Stalwart mail server with CNPG and S3
- stalwart namespace with Deployment + HPA (2-6 replicas)
- CNPG PostgreSQL cluster (3 instances, 20Gi cephrbd-fast-delete) with PgBouncer pooler
- S3/Ceph-RGW for blob storage (stalwart-maildata bucket, lz4 compressed)
- Secrets from Vault: postgres-credentials, s3-credentials, stalwart-admin
- TLS cert via cert-manager (vault-issuer) for mail.main.unkin.net
- SMTP relay on port 25 (internal ClusterIP, trusted pod CIDRs)
- Submission on port 587, IMAP 143/993, HTTPS 443 via LoadBalancer
- HTTP port 8080 for Traefik reverse proxy (web admin at mail.k8s.syd1.au.unkin.net)
- Outbound mail routed through postfix.mailgateway.svc.cluster.local:25
- Spam filtering offloaded to postfix/rspamd (disabled internally)
2026-05-24 12:44:46 +10:00
unkinben cbc2c1cb9f fix(gateways): add explicit group: "" to all certificateRefs entries (#153)
The Gateway API admission server defaults certificateRefs[].group to ""
when it is omitted. ArgoCD diffed the desired state (no group field) against
the live state (group: "") and flagged every gateway as out of sync.

Fix: explicitly set group: "" in all certificateRefs entries so the
rendered manifest matches the API server's canonical form exactly.

Affected: artifactapi, cattle-system, consul, litellm, paperclip,
puppet (puppetboard + puppetdb), vault.

Reviewed-on: #153
2026-05-23 23:47:24 +10:00
unkinben c6f9893804 fix(argocd): add vault and consul to platform project destinations (#152)
Vault and consul namespaces were missing from the platform AppProject
allowed destinations, causing ArgoCD sync failures with:
  destination server 'https://kubernetes.default.svc' and namespace
  'vault' do not match any of the allowed destinations in project 'platform'

Reviewed-on: #152
2026-05-23 23:27:24 +10:00
unkinben e43fb742ad feat(artifactapi): add kanidm to ghcr docker immutable patterns (#151)
Prerequisite for kanidm deployment (PR benvin/kanidm).

Reviewed-on: #151
2026-05-23 23:09:38 +10:00
unkinben 11ac2ae91e feat(consul): deploy HashiCorp Consul 1.22.7 via Helm chart (5-replica cluster) (#149)
## Summary

- Deploys HashiCorp Consul 1.22.7 using Helm chart 1.9.7 with 5 server replicas
- Configuration modelled on production consul: \`datacenter=au-syd1\`, \`connect=true\`, \`raft_multiplier=10\`, HTTP on 8500, GRPC on 8502, HTTPS disabled
- 5-replica server cluster with \`bootstrapExpect=5\`
- 10Gi cephrbd-fast-delete PVC per server pod
- Gateway API: HTTPS gateway + HTTPRoute (443→consul-consul-ui:80→8500) at \`consul.k8s.syd1.au.unkin.net\`
- PodDisruptionBudget patched from \`policy/v1beta1\` to \`policy/v1\` (k8s 1.25+ compatibility)
- ArgoCD platform ApplicationSet updated to include consul overlay path
- Clients disabled (server-only deployment)
- ConnectInject disabled (can be enabled later for service mesh)

## Requires

- PR #147 (artifactapi: add hashicorp/consul to docker immutable patterns) to be merged first

## Test plan

- [ ] Sandbox tested in \`sandbox-consul\`: all 5 server pods 1/1 Running, cluster formed
- [ ] After merge: ArgoCD syncs consul namespace
- [ ] Verify \`consul.k8s.syd1.au.unkin.net\` is accessible via Gateway

Reviewed-on: #149
2026-05-23 22:40:49 +10:00
unkinben d2be521878 feat(vault): deploy HashiCorp Vault 2.0.1 via Helm chart (5-replica HA raft) (#148)
## Summary

- Deploys HashiCorp Vault 2.0.1 using Helm chart 0.32.0 in HA raft mode (5 replicas)
- Configuration modelled on production vault: \`disable_mlock=true\`, headless-DNS retry_join for all 5 pods
- IPC_LOCK capability added via \`server.statefulSet.securityContext.container\`
- 10Gi cephrbd-fast-delete PVC per pod via \`dataStorage\`
- Gateway API: HTTPS gateway + HTTPRoute (443→vault service port 8200) at \`vault.k8s.syd1.au.unkin.net\`
- ArgoCD platform ApplicationSet updated to include vault overlay path
- Injector disabled (no agent sidecar injection needed)

## Requires

- PR #147 (artifactapi: add hashicorp/vault to docker immutable patterns) to be merged first

## Test plan

- [ ] Sandbox tested in \`sandbox-vault\`: all 5 pods Running, raft cluster forming
- [ ] After merge: ArgoCD syncs vault namespace
- [ ] Operator runs \`vault operator init\` to initialize, then unseals all 5 nodes
- [ ] Verify \`vault.k8s.syd1.au.unkin.net\` is accessible via Gateway

Reviewed-on: #148
2026-05-23 22:39:41 +10:00
unkinben bcd4c1a722 feat(cert-manager): upgrade to v1.20.2 and enable Gateway API support (#150)
## Summary

- Upgrades cert-manager from v1.19.2 to v1.20.2
- Enables `enableGatewayAPI: true` via the `ControllerConfiguration` config block

## Why

cert-manager's Gateway API integration was not enabled. Without it, `cert-manager.io/*` annotations on Gateway resources are ignored and no certificates are issued. This is required for the consul and vault PRs (#148, #149) to have their TLS certs automatically provisioned from their Gateway annotations.

In v1.20.2, `ExperimentalGatewayAPISupport` is BETA and defaults to true — enabling `enableGatewayAPI` in the controller config activates the gateway-shim controller.

## Test plan

- [ ] cert-manager rolls out cleanly (v1.20.2 pods become Ready)
- [ ] After rollout, existing Gateway-annotated services (artifactapi, puppet, litellm) retain valid certs
- [ ] New Gateway resources with `cert-manager.io/cluster-issuer` annotations trigger Certificate creation

Reviewed-on: #150
2026-05-23 22:38:39 +10:00
unkinben 6d9530b1ee feat(artifactapi): add hashicorp/consul and hashicorp/vault to docker immutable patterns (#147)
## Summary

- Adds \`^hashicorp/consul\` and \`^hashicorp/vault\` to the dockerhub immutable_patterns in artifactapi's remote-docker.yaml
- Replaces the more specific \`^hashicorp/vault-secrets-operator\` pattern since \`^hashicorp/vault\` subsumes it
- Required for the benvin/vault and benvin/consul branches (vault:2.0.1 and consul:1.22.7)

## Test plan

- [ ] Verify artifactapi accepts requests for hashicorp/vault and hashicorp/consul images after merge

Reviewed-on: #147
2026-05-23 18:21:25 +10:00
unkinben dcea768c15 feat(woodpecker): upgrade to v3.14.1 (chart 3.6.3) (#146)
Reviewed-on: #146
2026-05-23 18:00:55 +10:00
unkinben e05f9bfd83 feat: increase litellm resources (#144)
finding litellm performance has dropped, crashed in multiple cases, and
then it had scaled to the maximum level using the majority of memory in
cluster.

- reduce the rate at which litellm autoscales
- increase the requests/limits to match usage

Reviewed-on: #144
2026-05-23 17:59:43 +10:00
unkinben 445d8b6e7e feat: add HTTP→HTTPS redirect to Gateway API services (#145)
Add port 80 HTTP listener and redirect HTTPRoute to artifactapi,
cattle-system (rancher), litellm, paperclip, and puppetboard — restoring
the redirect behaviour that existed on the previous nginx/traefik Ingress
resources.

Reviewed-on: #145
2026-05-23 17:34:07 +10:00
unkinben c2637da068 feat(artifactapi): migrate Ingress to Gateway API (#129)
## Summary

- Replace `Ingress` (nginx) with `Gateway` + `HTTPRoute` using `traefik-internal` GatewayClass
- TLS terminated at the Gateway listener; cert-manager provisions the certificate via `vault-issuer`
- external-dns annotations moved to the Gateway

## Notes

The original Ingress had nginx-specific annotations (`proxy-body-size: 10g`, `proxy-read-timeout: 600`) which are not portable to Gateway API. These can be re-introduced via a Traefik `Middleware` CRD if needed.

## Test plan

- [ ] ArgoCD syncs the app cleanly
- [ ] cert-manager issues the `artifactapi-tls` certificate
- [ ] external-dns creates the DNS record
- [ ] `https://artifactapi.k8s.syd1.au.unkin.net` is reachable

Reviewed-on: #129
2026-05-23 16:06:33 +10:00
unkinben 90ddd932fe feat(puppet): migrate puppetdb Ingress to Gateway API (#131)
## Summary

- Replace `Ingress` (nginx) with `Gateway` + `HTTPRoute` using `traefik-internal` GatewayClass
- TLS terminated at the Gateway listener; cert-manager provisions the certificate via `vault-issuer`
- external-dns annotations moved to the Gateway
- `ingress_puppetboard.yaml` is unchanged in this PR (separate PR)

## Test plan

- [ ] ArgoCD syncs the puppet app cleanly
- [ ] cert-manager issues the `puppetdb-tls` certificate
- [ ] external-dns creates the DNS record
- [ ] `https://puppetdb.k8s.syd1.au.unkin.net` is reachable

Reviewed-on: #131
2026-05-23 16:05:26 +10:00
unkinben 2c6d88aa6b feat(puppet): migrate puppetboard Ingress to Gateway API (#130)
## Summary

- Replace `Ingress` (nginx) with `Gateway` + `HTTPRoute` using `traefik-internal` GatewayClass
- TLS terminated at the Gateway listener; cert-manager provisions the certificate via `vault-issuer`
- external-dns annotations moved to the Gateway
- `ingress_puppetdb.yaml` is unchanged in this PR (separate PR)

## Test plan

- [ ] ArgoCD syncs the puppet app cleanly
- [ ] cert-manager issues the `puppetboard-tls` certificate
- [ ] external-dns creates the DNS record
- [ ] `https://puppetboard.k8s.syd1.au.unkin.net` is reachable

Reviewed-on: #130
2026-05-23 01:31:28 +10:00
unkinben 58368948d9 feat(paperclip): migrate Ingress to Gateway API (#133)
## Summary

- Replace `Ingress` (nginx) with `Gateway` + `HTTPRoute` using `traefik-internal` GatewayClass
- TLS terminated at the Gateway listener; cert-manager provisions the certificate via `vault-issuer`
- external-dns annotations moved to the Gateway

## Test plan

- [ ] ArgoCD syncs the paperclip app cleanly
- [ ] cert-manager issues the `paperclip-tls` certificate
- [ ] external-dns creates the DNS record
- [ ] `https://paperclip.k8s.syd1.au.unkin.net` is reachable

Reviewed-on: #133
2026-05-23 01:31:03 +10:00
unkinben 4f5c3f7ea0 feat(litellm): migrate Ingress to Gateway API (#134)
## Summary

- Replace `Ingress` (nginx) with `Gateway` + `HTTPRoute` using `traefik-internal` GatewayClass
- TLS terminated at the Gateway listener; cert-manager provisions the certificate via `vault-issuer`
- external-dns annotations moved to the Gateway

## Test plan

- [ ] ArgoCD syncs the litellm app cleanly
- [ ] cert-manager issues the `litellm-tls` certificate
- [ ] external-dns creates the DNS record
- [ ] `https://litellm.k8s.syd1.au.unkin.net` is reachable

Reviewed-on: #134
2026-05-23 01:29:54 +10:00
unkinben fd87cb96b5 feat(externaldns): upgrade to 1.21.1, fix sources for installed CRDs (#143)
## Changes

- Upgrade external-dns chart from 1.19.0 → 1.21.1 (app v0.19.0 → v0.21.0)
- Remove `gateway-tcproute` source — `TCPRoute` CRD is not installed, causing crash-loop
- Add `gateway-tlsroute` source — `TLSRoute` CRD is installed (Gateway API 1.5.1)

## Why

The pod was crash-looping every minute with `failed to list *v1alpha2.TCPRoute: the server could not find the requested resource` because the TCPRoute CRD doesn't exist in this cluster. TLSRoute was previously removed but its CRD does exist.

Reviewed-on: #143
2026-05-23 01:28:20 +10:00
unkinben d619f9195e benvin/externaldns_compatability (#142)
Reviewed-on: #142
2026-05-23 01:17:20 +10:00
unkinben 1944dbbfcd temp: enable debug logging on externaldns to diagnose TLSRoute sync timeout (#140)
Temporary: enable --log-level=debug to understand why the TLSRoute informer never reports HasSynced within the 1m interval. To be closed/reverted after root cause is found.
Reviewed-on: #140
2026-05-23 01:07:45 +10:00
60 changed files with 1704 additions and 180 deletions
+37
View File
@@ -0,0 +1,37 @@
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
labels:
traefik.io/instance: internal
annotations:
cert-manager.io/cluster-issuer: vault-issuer
cert-manager.io/common-name: artifactapi.k8s.syd1.au.unkin.net
cert-manager.io/private-key-size: "4096"
external-dns.alpha.kubernetes.io/hostname: artifactapi.k8s.syd1.au.unkin.net
external-dns.alpha.kubernetes.io/target: 198.18.200.4
name: artifactapi
namespace: artifactapi
spec:
gatewayClassName: traefik-internal
listeners:
- allowedRoutes:
namespaces:
from: Same
hostname: artifactapi.k8s.syd1.au.unkin.net
name: http
port: 80
protocol: HTTP
- allowedRoutes:
namespaces:
from: Same
hostname: artifactapi.k8s.syd1.au.unkin.net
name: https
port: 443
protocol: HTTPS
tls:
certificateRefs:
- group: ""
kind: Secret
name: artifactapi-tls
mode: Terminate
+42
View File
@@ -0,0 +1,42 @@
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: artifactapi-http-redirect
namespace: artifactapi
spec:
hostnames:
- artifactapi.k8s.syd1.au.unkin.net
parentRefs:
- name: artifactapi
sectionName: http
rules:
- filters:
- type: RequestRedirect
requestRedirect:
scheme: https
statusCode: 301
matches:
- path:
type: PathPrefix
value: /
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: artifactapi
namespace: artifactapi
spec:
hostnames:
- artifactapi.k8s.syd1.au.unkin.net
parentRefs:
- name: artifactapi
sectionName: https
rules:
- backendRefs:
- name: artifactapi-api
port: 80
matches:
- path:
type: PathPrefix
value: /
-32
View File
@@ -1,32 +0,0 @@
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
cert-manager.io/cluster-issuer: vault-issuer
cert-manager.io/common-name: artifactapi.k8s.syd1.au.unkin.net
cert-manager.io/private-key-size: "4096"
external-dns.alpha.kubernetes.io/hostname: artifactapi.k8s.syd1.au.unkin.net
external-dns.alpha.kubernetes.io/target: 198.18.200.0
nginx.ingress.kubernetes.io/proxy-body-size: 10g
nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
nginx.ingress.kubernetes.io/ssl-redirect: "false"
name: artifactapi-ingress
namespace: artifactapi
spec:
ingressClassName: nginx
rules:
- host: artifactapi.k8s.syd1.au.unkin.net
http:
paths:
- backend:
service:
name: artifactapi-api
port:
number: 80
path: /
pathType: Prefix
tls:
- hosts:
- artifactapi.k8s.syd1.au.unkin.net
secretName: artifactapi-tls
+2 -1
View File
@@ -6,7 +6,8 @@ resources:
- artifactapi-deployment.yaml
- artifactapi-hpa.yaml
- configmap.yaml
- ingress.yaml
- gateway.yaml
- httproute.yaml
- namespace.yaml
- postgres-deployment.yaml
- pvc.yaml
@@ -6,6 +6,7 @@ remotes:
immutable_patterns:
- "^cloudnative-pg/cloudnative-pg"
- "^emberstack/helm-charts"
- "^kanidm/"
- "^openvoxproject/"
- "^stakater/reloader"
- "^voxpupuli/puppetboard"
@@ -30,7 +31,8 @@ remotes:
- "^bitnami/"
- "^curlimages/curl"
- "^emberstack/kubernetes-reflector"
- "^hashicorp/vault-secrets-operator"
- "^hashicorp/consul"
- "^hashicorp/vault"
- "^jfrog/"
- "^rancher/"
- "^traefik/"
+9 -1
View File
@@ -15,6 +15,13 @@ metadata:
spec:
gatewayClassName: traefik-internal
listeners:
- allowedRoutes:
namespaces:
from: Same
hostname: rancher.k8s.syd1.au.unkin.net
name: http
port: 80
protocol: HTTP
- allowedRoutes:
namespaces:
from: Same
@@ -24,6 +31,7 @@ spec:
protocol: HTTPS
tls:
certificateRefs:
- kind: Secret
- group: ""
kind: Secret
name: rancher-tls
mode: Terminate
+22
View File
@@ -1,6 +1,28 @@
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: rancher-http-redirect
namespace: cattle-system
spec:
hostnames:
- rancher.k8s.syd1.au.unkin.net
parentRefs:
- name: rancher
sectionName: http
rules:
- filters:
- type: RequestRedirect
requestRedirect:
scheme: https
statusCode: 301
matches:
- path:
type: PathPrefix
value: /
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: rancher
namespace: cattle-system
+53
View File
@@ -0,0 +1,53 @@
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: consul
namespace: consul
labels:
app.kubernetes.io/name: consul
app.kubernetes.io/instance: consul
traefik.io/instance: internal
annotations:
cert-manager.io/cluster-issuer: vault-issuer
cert-manager.io/common-name: consul.k8s.syd1.au.unkin.net
cert-manager.io/private-key-size: "4096"
cert-manager.io/alt-names: consul.service.consul
external-dns.alpha.kubernetes.io/hostname: consul.k8s.syd1.au.unkin.net
external-dns.alpha.kubernetes.io/target: 198.18.200.4
spec:
gatewayClassName: traefik-internal
listeners:
- name: http
port: 80
protocol: HTTP
hostname: consul.k8s.syd1.au.unkin.net
allowedRoutes:
namespaces:
from: Same
- name: https
port: 443
protocol: HTTPS
hostname: consul.k8s.syd1.au.unkin.net
allowedRoutes:
namespaces:
from: Same
tls:
mode: Terminate
certificateRefs:
- group: ""
kind: Secret
name: consul-tls
- name: consul-svc
port: 443
protocol: HTTPS
hostname: consul.service.consul
allowedRoutes:
namespaces:
from: Same
tls:
mode: Terminate
certificateRefs:
- group: ""
kind: Secret
name: consul-tls
+71
View File
@@ -0,0 +1,71 @@
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: consul-http-redirect
namespace: consul
labels:
app.kubernetes.io/name: consul
app.kubernetes.io/instance: consul
spec:
hostnames:
- consul.k8s.syd1.au.unkin.net
parentRefs:
- name: consul
sectionName: http
rules:
- filters:
- type: RequestRedirect
requestRedirect:
scheme: https
statusCode: 301
matches:
- path:
type: PathPrefix
value: /
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: consul
namespace: consul
labels:
app.kubernetes.io/name: consul
app.kubernetes.io/instance: consul
spec:
hostnames:
- consul.k8s.syd1.au.unkin.net
parentRefs:
- name: consul
sectionName: https
rules:
- backendRefs:
- name: consul-ui
port: 80
matches:
- path:
type: PathPrefix
value: /
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: consul-svc
namespace: consul
labels:
app.kubernetes.io/name: consul
app.kubernetes.io/instance: consul
spec:
hostnames:
- consul.service.consul
parentRefs:
- name: consul
sectionName: consul-svc
rules:
- backendRefs:
- name: consul-ui
port: 80
matches:
- path:
type: PathPrefix
value: /
+8
View File
@@ -0,0 +1,8 @@
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- namespace.yaml
- gateway.yaml
- httproute.yaml
+5
View File
@@ -0,0 +1,5 @@
---
apiVersion: v1
kind: Namespace
metadata:
name: consul
+3 -3
View File
@@ -76,11 +76,11 @@ spec:
updateInterval: 30
resources:
limits:
cpu: 500m
memory: 512Mi
cpu: 1
memory: 1024Mi
requests:
cpu: 250m
memory: 256Mi
memory: 512Mi
smartShutdownTimeout: 180
startDelay: 3600
stopDelay: 1800
+2 -2
View File
@@ -56,10 +56,10 @@ spec:
resources:
limits:
cpu: "2"
memory: 6Gi
memory: 8Gi
requests:
cpu: 250m
memory: 2Gi
memory: 6Gi
volumeMounts:
- mountPath: /app/config.yaml
name: config
+37
View File
@@ -0,0 +1,37 @@
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
labels:
traefik.io/instance: internal
annotations:
cert-manager.io/cluster-issuer: vault-issuer
cert-manager.io/common-name: litellm.k8s.syd1.au.unkin.net
cert-manager.io/private-key-size: "4096"
external-dns.alpha.kubernetes.io/hostname: litellm.k8s.syd1.au.unkin.net
external-dns.alpha.kubernetes.io/target: 198.18.200.4
name: litellm
namespace: litellm
spec:
gatewayClassName: traefik-internal
listeners:
- allowedRoutes:
namespaces:
from: Same
hostname: litellm.k8s.syd1.au.unkin.net
name: http
port: 80
protocol: HTTP
- allowedRoutes:
namespaces:
from: Same
hostname: litellm.k8s.syd1.au.unkin.net
name: https
port: 443
protocol: HTTPS
tls:
certificateRefs:
- group: ""
kind: Secret
name: litellm-tls
mode: Terminate
+4 -4
View File
@@ -10,14 +10,14 @@ spec:
kind: Deployment
name: litellm
minReplicas: 2
maxReplicas: 10
maxReplicas: 4
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60
averageUtilization: 80
behavior:
scaleUp:
stabilizationWindowSeconds: 0
@@ -25,7 +25,7 @@ spec:
policies:
- type: Percent
value: 100
periodSeconds: 30
periodSeconds: 60
- type: Pods
value: 4
periodSeconds: 30
@@ -34,7 +34,7 @@ spec:
selectPolicy: Min
policies:
- type: Percent
value: 10
value: 30
periodSeconds: 60
- type: Pods
value: 2
+42
View File
@@ -0,0 +1,42 @@
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: litellm-http-redirect
namespace: litellm
spec:
hostnames:
- litellm.k8s.syd1.au.unkin.net
parentRefs:
- name: litellm
sectionName: http
rules:
- filters:
- type: RequestRedirect
requestRedirect:
scheme: https
statusCode: 301
matches:
- path:
type: PathPrefix
value: /
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: litellm
namespace: litellm
spec:
hostnames:
- litellm.k8s.syd1.au.unkin.net
parentRefs:
- name: litellm
sectionName: https
rules:
- backendRefs:
- name: litellm
port: 4000
matches:
- path:
type: PathPrefix
value: /
-29
View File
@@ -1,29 +0,0 @@
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: nginx
external-dns.alpha.kubernetes.io/hostname: litellm.k8s.syd1.au.unkin.net
external-dns.alpha.kubernetes.io/target: 198.18.200.0
cert-manager.io/cluster-issuer: vault-issuer
cert-manager.io/common-name: litellm.k8s.syd1.au.unkin.net
cert-manager.io/private-key-size: "4096"
name: litellm
namespace: litellm
spec:
rules:
- host: litellm.k8s.syd1.au.unkin.net
http:
paths:
- backend:
service:
name: litellm
port:
number: 4000
path: /
pathType: Prefix
tls:
- hosts:
- litellm.k8s.syd1.au.unkin.net
secretName: litellm-tls
+2 -1
View File
@@ -7,7 +7,8 @@ resources:
- cnpg_pooler.yaml
- deployment.yaml
- hpa.yaml
- ingress.yaml
- gateway.yaml
- httproute.yaml
- namespace.yaml
- redis-deployment.yaml
- redis-pvc.yaml
+37
View File
@@ -0,0 +1,37 @@
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
labels:
traefik.io/instance: internal
annotations:
cert-manager.io/cluster-issuer: vault-issuer
cert-manager.io/common-name: paperclip.k8s.syd1.au.unkin.net
cert-manager.io/private-key-size: "4096"
external-dns.alpha.kubernetes.io/hostname: paperclip.k8s.syd1.au.unkin.net
external-dns.alpha.kubernetes.io/target: 198.18.200.4
name: paperclip
namespace: paperclip
spec:
gatewayClassName: traefik-internal
listeners:
- allowedRoutes:
namespaces:
from: Same
hostname: paperclip.k8s.syd1.au.unkin.net
name: http
port: 80
protocol: HTTP
- allowedRoutes:
namespaces:
from: Same
hostname: paperclip.k8s.syd1.au.unkin.net
name: https
port: 443
protocol: HTTPS
tls:
certificateRefs:
- group: ""
kind: Secret
name: paperclip-tls
mode: Terminate
+42
View File
@@ -0,0 +1,42 @@
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: paperclip-http-redirect
namespace: paperclip
spec:
hostnames:
- paperclip.k8s.syd1.au.unkin.net
parentRefs:
- name: paperclip
sectionName: http
rules:
- filters:
- type: RequestRedirect
requestRedirect:
scheme: https
statusCode: 301
matches:
- path:
type: PathPrefix
value: /
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: paperclip
namespace: paperclip
spec:
hostnames:
- paperclip.k8s.syd1.au.unkin.net
parentRefs:
- name: paperclip
sectionName: https
rules:
- backendRefs:
- name: paperclip
port: 3100
matches:
- path:
type: PathPrefix
value: /
-29
View File
@@ -1,29 +0,0 @@
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: nginx
external-dns.alpha.kubernetes.io/hostname: paperclip.k8s.syd1.au.unkin.net
external-dns.alpha.kubernetes.io/target: 198.18.200.0
cert-manager.io/cluster-issuer: vault-issuer
cert-manager.io/common-name: paperclip.k8s.syd1.au.unkin.net
cert-manager.io/private-key-size: "4096"
name: paperclip
namespace: paperclip
spec:
rules:
- host: paperclip.k8s.syd1.au.unkin.net
http:
paths:
- backend:
service:
name: paperclip
port:
number: 3100
path: /
pathType: Prefix
tls:
- hosts:
- paperclip.k8s.syd1.au.unkin.net
secretName: paperclip-tls
+2 -1
View File
@@ -6,7 +6,8 @@ resources:
- cnpg_cluster.yaml
- cnpg_pooler.yaml
- deployment.yaml
- ingress.yaml
- gateway.yaml
- httproute.yaml
- namespace.yaml
- services.yaml
- vaultauth.yaml
+41
View File
@@ -0,0 +1,41 @@
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
annotations:
cert-manager.io/cluster-issuer: vault-issuer
cert-manager.io/common-name: puppetboard.k8s.syd1.au.unkin.net
cert-manager.io/private-key-size: "4096"
external-dns.alpha.kubernetes.io/hostname: puppetboard.k8s.syd1.au.unkin.net
external-dns.alpha.kubernetes.io/target: 198.18.200.4
labels:
traefik.io/instance: internal
app.kubernetes.io/component: puppetboard
app.kubernetes.io/instance: puppetserver
app.kubernetes.io/name: puppetserver
app.kubernetes.io/version: 8.8.0
name: puppetboard
namespace: puppet
spec:
gatewayClassName: traefik-internal
listeners:
- allowedRoutes:
namespaces:
from: Same
hostname: puppetboard.k8s.syd1.au.unkin.net
name: http
port: 80
protocol: HTTP
- allowedRoutes:
namespaces:
from: Same
hostname: puppetboard.k8s.syd1.au.unkin.net
name: https
port: 443
protocol: HTTPS
tls:
certificateRefs:
- group: ""
kind: Secret
name: puppetboard-tls
mode: Terminate
+34
View File
@@ -0,0 +1,34 @@
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
annotations:
cert-manager.io/cluster-issuer: vault-issuer
cert-manager.io/common-name: puppetdb.k8s.syd1.au.unkin.net
cert-manager.io/private-key-size: "4096"
external-dns.alpha.kubernetes.io/hostname: puppetdb.k8s.syd1.au.unkin.net
external-dns.alpha.kubernetes.io/target: 198.18.200.4
labels:
traefik.io/instance: internal
app.kubernetes.io/component: puppetdb
app.kubernetes.io/instance: puppetserver
app.kubernetes.io/name: puppetserver
app.kubernetes.io/version: 8.8.0
name: puppetdb
namespace: puppet
spec:
gatewayClassName: traefik-internal
listeners:
- allowedRoutes:
namespaces:
from: Same
hostname: puppetdb.k8s.syd1.au.unkin.net
name: https
port: 443
protocol: HTTPS
tls:
certificateRefs:
- group: ""
kind: Secret
name: puppetdb-tls
mode: Terminate
@@ -0,0 +1,52 @@
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
labels:
app.kubernetes.io/component: puppetboard
app.kubernetes.io/instance: puppetserver
app.kubernetes.io/name: puppetserver
app.kubernetes.io/version: 8.8.0
name: puppetboard-http-redirect
namespace: puppet
spec:
hostnames:
- puppetboard.k8s.syd1.au.unkin.net
parentRefs:
- name: puppetboard
sectionName: http
rules:
- filters:
- type: RequestRedirect
requestRedirect:
scheme: https
statusCode: 301
matches:
- path:
type: PathPrefix
value: /
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
labels:
app.kubernetes.io/component: puppetboard
app.kubernetes.io/instance: puppetserver
app.kubernetes.io/name: puppetserver
app.kubernetes.io/version: 8.8.0
name: puppetboard
namespace: puppet
spec:
hostnames:
- puppetboard.k8s.syd1.au.unkin.net
parentRefs:
- name: puppetboard
sectionName: https
rules:
- backendRefs:
- name: puppetboard
port: 80
matches:
- path:
type: PathPrefix
value: /
+25
View File
@@ -0,0 +1,25 @@
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
labels:
app.kubernetes.io/component: puppetdb
app.kubernetes.io/instance: puppetserver
app.kubernetes.io/name: puppetserver
app.kubernetes.io/version: 8.8.0
name: puppetdb
namespace: puppet
spec:
hostnames:
- puppetdb.k8s.syd1.au.unkin.net
parentRefs:
- name: puppetdb
sectionName: https
rules:
- backendRefs:
- name: puppetdb
port: 8080
matches:
- path:
type: PathPrefix
value: /
-34
View File
@@ -1,34 +0,0 @@
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: nginx
external-dns.alpha.kubernetes.io/hostname: puppetboard.k8s.syd1.au.unkin.net
external-dns.alpha.kubernetes.io/target: 198.18.200.0
cert-manager.io/cluster-issuer: vault-issuer
cert-manager.io/common-name: puppetboard.k8s.syd1.au.unkin.net
cert-manager.io/private-key-size: "4096"
labels:
app.kubernetes.io/component: puppetboard
app.kubernetes.io/instance: puppetserver
app.kubernetes.io/name: puppetserver
app.kubernetes.io/version: 8.8.0
name: puppetboard
namespace: puppet
spec:
rules:
- host: puppetboard.k8s.syd1.au.unkin.net
http:
paths:
- backend:
service:
name: puppetboard
port:
number: 80
path: /
pathType: Prefix
tls:
- hosts:
- puppetboard.k8s.syd1.au.unkin.net
secretName: puppetboard-tls
-34
View File
@@ -1,34 +0,0 @@
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: nginx
external-dns.alpha.kubernetes.io/hostname: puppetdb.k8s.syd1.au.unkin.net
external-dns.alpha.kubernetes.io/target: 198.18.200.0
cert-manager.io/cluster-issuer: vault-issuer
cert-manager.io/common-name: puppetdb.k8s.syd1.au.unkin.net
cert-manager.io/private-key-size: "4096"
labels:
app.kubernetes.io/component: puppetdb
app.kubernetes.io/instance: puppetserver
app.kubernetes.io/name: puppetserver
app.kubernetes.io/version: 8.8.0
name: puppetdb
namespace: puppet
spec:
rules:
- host: puppetdb.k8s.syd1.au.unkin.net
http:
paths:
- backend:
service:
name: puppetdb
port:
number: 8080
path: /
pathType: Prefix
tls:
- hosts:
- puppetdb.k8s.syd1.au.unkin.net
secretName: puppetdb-tls
+4 -2
View File
@@ -25,8 +25,10 @@ resources:
- horizontalpodautoscaler_puppetserver-masters-autoscaler.yaml
- horizontalpodautoscaler_puppetserver-puppetboard-autoscaler.yaml
- horizontalpodautoscaler_puppetserver-puppetdb-autoscaler.yaml
- ingress_puppetboard.yaml
- ingress_puppetdb.yaml
- gateway_puppetboard.yaml
- httproute_puppetboard.yaml
- gateway_puppetdb.yaml
- httproute_puppetdb.yaml
- service_puppetserver-agents-to-puppet.yaml
- service_puppet-headless.yaml
- service_puppet.yaml
+36
View File
@@ -0,0 +1,36 @@
---
# TLS cert for stalwart's own HTTPS/IMAPS/STARTTLS listeners (mail.main.unkin.net)
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: stalwart-tls
namespace: stalwart
spec:
secretName: stalwart-tls
issuerRef:
name: vault-issuer
kind: ClusterIssuer
commonName: mail.main.unkin.net
dnsNames:
- mail.main.unkin.net
privateKey:
size: 4096
algorithm: RSA
---
# TLS cert for Traefik Gateway (internal k8s admin URL)
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: stalwart-gateway-tls
namespace: stalwart
spec:
secretName: stalwart-gateway-tls
issuerRef:
name: vault-issuer
kind: ClusterIssuer
commonName: mail.k8s.syd1.au.unkin.net
dnsNames:
- mail.k8s.syd1.au.unkin.net
privateKey:
size: 4096
algorithm: RSA
+91
View File
@@ -0,0 +1,91 @@
---
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: stalwart-postgres
namespace: stalwart
spec:
affinity:
podAntiAffinityType: preferred
bootstrap:
initdb:
database: stalwart
encoding: UTF8
localeCType: C
localeCollate: C
owner: stalwart
secret:
name: postgres-credentials
enablePDB: true
enableSuperuserAccess: false
failoverDelay: 0
imageName: ghcr.io/cloudnative-pg/postgresql:17-minimal-trixie
instances: 3
logLevel: info
maxSyncReplicas: 0
minSyncReplicas: 0
monitoring:
customQueriesConfigMap:
- key: queries
name: cnpg-default-monitoring
disableDefaultQueries: false
enablePodMonitor: false
postgresql:
parameters:
archive_mode: "on"
archive_timeout: 5min
dynamic_shared_memory_type: posix
effective_cache_size: 256MB
full_page_writes: "on"
log_destination: csvlog
log_directory: /controller/log
log_filename: postgres
log_rotation_age: "0"
log_rotation_size: "0"
log_truncate_on_rotation: "false"
logging_collector: "on"
max_connections: "200"
max_parallel_workers: "16"
max_replication_slots: "16"
max_worker_processes: "16"
shared_buffers: 128MB
shared_memory_type: mmap
ssl_max_protocol_version: TLSv1.3
ssl_min_protocol_version: TLSv1.3
wal_keep_size: 256MB
wal_level: logical
wal_log_hints: "on"
wal_receiver_timeout: 5s
wal_sender_timeout: 5s
syncReplicaElectionConstraint:
enabled: false
primaryUpdateMethod: restart
primaryUpdateStrategy: unsupervised
probes:
liveness:
isolationCheck:
connectionTimeout: 1000
enabled: true
requestTimeout: 1000
replicationSlots:
highAvailability:
enabled: true
slotPrefix: _cnpg_
synchronizeReplicas:
enabled: true
updateInterval: 30
resources:
limits:
cpu: "2"
memory: 2Gi
requests:
cpu: 500m
memory: 1Gi
smartShutdownTimeout: 180
startDelay: 3600
stopDelay: 1800
storage:
resizeInUseVolumes: true
size: 20Gi
storageClass: cephrbd-fast-delete
switchoverDelay: 3600
+33
View File
@@ -0,0 +1,33 @@
---
apiVersion: postgresql.cnpg.io/v1
kind: Pooler
metadata:
name: stalwart-postgres-pooler
namespace: stalwart
spec:
cluster:
name: stalwart-postgres
instances: 2
pgbouncer:
parameters:
default_pool_size: "50"
max_client_conn: "200"
paused: false
poolMode: session
template:
metadata:
labels:
app: pooler
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- pooler
topologyKey: kubernetes.io/hostname
containers: []
type: rw
+38
View File
@@ -0,0 +1,38 @@
---
# Traefik gateway for stalwart web admin UI (separate from the LoadBalancer HTTPS port)
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
labels:
traefik.io/instance: internal
annotations:
cert-manager.io/cluster-issuer: vault-issuer
cert-manager.io/common-name: mail.k8s.syd1.au.unkin.net
cert-manager.io/private-key-size: "4096"
external-dns.alpha.kubernetes.io/hostname: mail.k8s.syd1.au.unkin.net
external-dns.alpha.kubernetes.io/target: 198.18.200.4
name: stalwart
namespace: stalwart
spec:
gatewayClassName: traefik-internal
listeners:
- allowedRoutes:
namespaces:
from: Same
hostname: mail.k8s.syd1.au.unkin.net
name: http
port: 80
protocol: HTTP
- allowedRoutes:
namespaces:
from: Same
hostname: mail.k8s.syd1.au.unkin.net
name: https
port: 443
protocol: HTTPS
tls:
certificateRefs:
- group: ""
kind: Secret
name: stalwart-gateway-tls
mode: Terminate
+16
View File
@@ -0,0 +1,16 @@
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: stalwart
namespace: stalwart
spec:
parentRefs:
- name: stalwart
namespace: stalwart
hostnames:
- mail.k8s.syd1.au.unkin.net
rules:
- backendRefs:
- name: stalwart
port: 8080
+24
View File
@@ -0,0 +1,24 @@
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- certificate.yaml
- cnpg_cluster.yaml
- cnpg_pooler.yaml
- gateway.yaml
- httproute.yaml
- namespace.yaml
- services.yaml
- stalwart-deployment.yaml
- stalwart-hpa.yaml
- valkey.yaml
- vaultauth.yaml
- vaultstaticsecret.yaml
configMapGenerator:
- name: stalwart-config
files:
- config.toml=resources/config.toml
options:
disableNameSuffixHash: true
+5
View File
@@ -0,0 +1,5 @@
---
apiVersion: v1
kind: Namespace
metadata:
name: stalwart
+228
View File
@@ -0,0 +1,228 @@
[server]
hostname = "mail.main.unkin.net"
greeting = "Stalwart ESMTP"
# SMTP relay listener (receives from postfix for local delivery)
[server.listener."smtp-relay"]
bind = ["0.0.0.0:25"]
protocol = "smtp"
greeting = "Stalwart SMTP Relay"
[server.listener."smtp-relay".proxy]
trusted-networks = ["127.0.0.0/8", "::1", "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]
# SMTP submission listener (user-facing, TLS required)
[server.listener."submission"]
bind = ["0.0.0.0:587"]
protocol = "smtp"
greeting = "Stalwart SMTP Submission"
tls.require = true
[server.listener."submission".proxy]
trusted-networks = ["127.0.0.0/8", "::1"]
# IMAP listener
[server.listener."imap"]
bind = ["0.0.0.0:143"]
protocol = "imap"
[server.listener."imap".proxy]
trusted-networks = ["127.0.0.0/8", "::1"]
# IMAPS listener (implicit TLS)
[server.listener."imaps"]
bind = ["0.0.0.0:993"]
protocol = "imap"
tls.implicit = true
[server.listener."imaps".proxy]
trusted-networks = ["127.0.0.0/8", "::1"]
# HTTPS (web admin + JMAP) for external LoadBalancer
[server.listener."https"]
bind = ["0.0.0.0:443"]
protocol = "http"
tls.implicit = true
[server.listener."https".proxy]
trusted-networks = ["127.0.0.0/8", "::1", "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]
# Plain HTTP for Traefik reverse proxy (TLS terminated at Traefik)
[server.listener."http-internal"]
bind = ["0.0.0.0:8080"]
protocol = "http"
[server.listener."http-internal".proxy]
trusted-networks = ["127.0.0.0/8", "::1", "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]
[server.tls]
enable = true
implicit = false
certificate = "default"
[server.http]
use-x-forwarded = true
permissive-cors = false
[webadmin]
path = "/var/lib/stalwart/webadmin"
auto-update = true
resource = "https://artifactapi.k8s.syd1.au.unkin.net/generic/github/stalwartlabs/webadmin/releases/latest/download/webadmin.zip"
# PostgreSQL store (via CNPG pooler)
[store."postgresql"]
type = "postgresql"
host = "stalwart-postgres-pooler.stalwart.svc.cluster.local"
port = 5432
database = "stalwart"
user = "stalwart"
password = "%{env:POSTGRES_PASSWORD}%"
timeout = "15s"
[store."postgresql".tls]
enable = true
allow-invalid-certs = false
[store."postgresql".pool]
max-connections = 10
[store."postgresql".purge]
frequency = "0 3 *"
# S3/Ceph-RGW store for blobs
[store."s3"]
type = "s3"
bucket = "stalwart-maildata"
region = "syd1"
access-key = "%{env:S3_ACCESS_KEY}%"
secret-key = "%{env:S3_SECRET_KEY}%"
endpoint = "https://radosgw.service.consul"
timeout = "30s"
key-prefix = "stalwart/"
compression = "lz4"
[store."s3".purge]
frequency = "30 5 *"
# Valkey in-memory store (rate limiting, locks, OAuth codes, greylisting)
[store."valkey"]
type = "redis"
urls = ["redis://stalwart-valkey.stalwart.svc.cluster.local:6379"]
# Storage assignment
[storage]
data = "postgresql"
fts = "postgresql"
blob = "s3"
lookup = "postgresql"
directory = "internal"
in-memory = "valkey"
# Directory configuration
[directory.internal]
type = "internal"
store = "postgresql"
# Fallback admin (password hash from Vault)
[authentication.fallback-admin]
user = "admin"
secret = "%{env:ADMIN_PASSWORD_HASH}%"
[authentication]
[authentication.directory]
directories = ["internal"]
[authorization]
directory = "internal"
# JMAP configuration
[jmap]
directory = "internal"
[jmap.protocol]
request-max-size = 10485760
get.max-objects = 500
query.max-results = 5000
changes.max-results = 5000
upload.max-size = 50000000
upload.ttl = "1h"
# IMAP configuration
[imap]
directory = "internal"
[imap.protocol]
max-requests = 64
# Inbound rate limiting
[[queue.limiter.inbound]]
key = ["remote_ip"]
rate = "500/1s"
enable = true
# Outbound routing through postfix relay
[queue]
path = "/var/lib/stalwart/queue"
[queue.schedule]
retry = ["2s", "5s", "1m", "5m", "15m", "30m", "1h", "2h"]
notify = ["1d", "3d"]
expire = "5d"
[session.extensions]
future-release = "7d"
# Route local domain mail locally, everything else through postfix relay
[queue.strategy]
route = [
{ if = "is_local_domain('', rcpt_domain)", then = "'local'" },
{ else = "'relay'" }
]
[queue.route."local"]
type = "local"
[queue.route."relay"]
type = "relay"
address = "postfix.mailgateway.svc.cluster.local"
port = 25
protocol = "smtp"
[queue.route."relay".tls]
implicit = false
allow-invalid-certs = false
# Session configuration
[session.ehlo]
reject-non-fqdn = false
[session.rcpt]
type = "internal"
store = "postgresql"
max-recipients = 25
[session.data]
max-messages = 10
max-message-size = 52428800
# Spam filtering offloaded to postfix/rspamd — disable internal spam filter
[spam-filter]
enable = false
# TLS certificate (cert-manager issues to /etc/stalwart/tls/)
[certificate."default"]
cert = "%{file:/etc/stalwart/tls/tls.crt}%"
private-key = "%{file:/etc/stalwart/tls/tls.key}%"
default = true
# Logging
[tracer]
type = "log"
level = "info"
ansi = false
multiline = true
# Metrics
[metrics]
prometheus.enable = true
prometheus.port = 9090
+57
View File
@@ -0,0 +1,57 @@
---
# Internal service - postfix connects here to deliver inbound mail
apiVersion: v1
kind: Service
metadata:
name: stalwart
namespace: stalwart
spec:
selector:
app: stalwart
ports:
- name: smtp-relay
port: 25
targetPort: 25
protocol: TCP
- name: http-internal
port: 8080
targetPort: 8080
protocol: TCP
- name: metrics
port: 9090
targetPort: 9090
protocol: TCP
---
# External LoadBalancer for user-facing mail services
apiVersion: v1
kind: Service
metadata:
name: stalwart-external
namespace: stalwart
annotations:
external-dns.alpha.kubernetes.io/hostname: mail.main.unkin.net
external-dns.alpha.kubernetes.io/target: 198.18.199.2
purelb.io/service-group: dmz
purelb.io/addresses: 198.18.199.2
spec:
type: LoadBalancer
externalTrafficPolicy: Local
selector:
app: stalwart
ports:
- name: submission
port: 587
targetPort: 587
protocol: TCP
- name: imap
port: 143
targetPort: 143
protocol: TCP
- name: imaps
port: 993
targetPort: 993
protocol: TCP
- name: https
port: 443
targetPort: 443
protocol: TCP
+109
View File
@@ -0,0 +1,109 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: stalwart
namespace: stalwart
spec:
selector:
matchLabels:
app: stalwart
template:
metadata:
annotations:
reloader.stakater.com/auto: "true"
labels:
app: stalwart
spec:
securityContext:
runAsUser: 2000
runAsGroup: 2000
fsGroup: 2000
containers:
- name: stalwart
image: ghcr.io/stalwartlabs/stalwart:v0.16.6
ports:
- containerPort: 25
name: smtp-relay
protocol: TCP
- containerPort: 587
name: submission
protocol: TCP
- containerPort: 143
name: imap
protocol: TCP
- containerPort: 993
name: imaps
protocol: TCP
- containerPort: 443
name: https
protocol: TCP
- containerPort: 8080
name: http-internal
protocol: TCP
- containerPort: 9090
name: metrics
protocol: TCP
env:
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-credentials
key: password
- name: S3_ACCESS_KEY
valueFrom:
secretKeyRef:
name: s3-credentials
key: access_key
- name: S3_SECRET_KEY
valueFrom:
secretKeyRef:
name: s3-credentials
key: secret_key
- name: ADMIN_PASSWORD_HASH
valueFrom:
secretKeyRef:
name: stalwart-admin
key: password_hash
livenessProbe:
httpGet:
path: /healthz/live
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /healthz/ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
resources:
requests:
cpu: 250m
memory: 512Mi
limits:
cpu: "2"
memory: 2Gi
volumeMounts:
- name: config
mountPath: /etc/stalwart/config.toml
subPath: config.toml
readOnly: true
- name: tls
mountPath: /etc/stalwart/tls
readOnly: true
- name: data
mountPath: /var/lib/stalwart
volumes:
- name: config
configMap:
name: stalwart-config
- name: tls
secret:
secretName: stalwart-tls
- name: data
emptyDir: {}
+38
View File
@@ -0,0 +1,38 @@
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: stalwart-hpa
namespace: stalwart
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: stalwart
minReplicas: 2
maxReplicas: 6
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
behavior:
scaleUp:
stabilizationWindowSeconds: 0
selectPolicy: Max
policies:
- type: Percent
value: 100
periodSeconds: 60
- type: Pods
value: 2
periodSeconds: 30
scaleDown:
stabilizationWindowSeconds: 300
selectPolicy: Min
policies:
- type: Percent
value: 30
periodSeconds: 60
+58
View File
@@ -0,0 +1,58 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: stalwart-valkey
namespace: stalwart
spec:
replicas: 1
selector:
matchLabels:
app: stalwart-valkey
template:
metadata:
labels:
app: stalwart-valkey
spec:
containers:
- name: valkey
image: valkey/valkey:8-alpine
args:
- "--save"
- ""
- "--appendonly"
- "no"
ports:
- containerPort: 6379
name: valkey
protocol: TCP
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 500m
memory: 256Mi
livenessProbe:
tcpSocket:
port: 6379
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
exec:
command: ["valkey-cli", "ping"]
initialDelaySeconds: 5
periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
name: stalwart-valkey
namespace: stalwart
spec:
selector:
app: stalwart-valkey
ports:
- port: 6379
targetPort: 6379
name: valkey
+18
View File
@@ -0,0 +1,18 @@
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
metadata:
name: default
namespace: stalwart
spec:
allowedNamespaces:
- stalwart
kubernetes:
audiences:
- vault
role: default
serviceAccount: default
tokenExpirationSeconds: 600
method: kubernetes
mount: k8s/au/syd1
vaultConnectionRef: vso-system/default
+51
View File
@@ -0,0 +1,51 @@
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: postgres-credentials
namespace: stalwart
spec:
destination:
create: true
name: postgres-credentials
overwrite: true
hmacSecretData: true
mount: kv
path: kubernetes/namespace/stalwart/default/postgres-credentials
refreshAfter: 5m
type: kv-v2
vaultAuthRef: default
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: s3-credentials
namespace: stalwart
spec:
destination:
create: true
name: s3-credentials
overwrite: true
hmacSecretData: true
mount: kv
path: kubernetes/namespace/stalwart/default/s3-credentials
refreshAfter: 5m
type: kv-v2
vaultAuthRef: default
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: stalwart-admin
namespace: stalwart
spec:
destination:
create: true
name: stalwart-admin
overwrite: true
hmacSecretData: true
mount: kv
path: kubernetes/namespace/stalwart/default/stalwart-admin
refreshAfter: 5m
type: kv-v2
vaultAuthRef: default
+52
View File
@@ -0,0 +1,52 @@
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: vault
namespace: vault
labels:
app.kubernetes.io/name: vault
app.kubernetes.io/instance: vault
traefik.io/instance: internal
annotations:
cert-manager.io/cluster-issuer: vault-issuer
cert-manager.io/common-name: vault.k8s.syd1.au.unkin.net
cert-manager.io/private-key-size: "4096"
cert-manager.io/alt-names: vault.service.consul,vault.query.consul
external-dns.alpha.kubernetes.io/hostname: vault.k8s.syd1.au.unkin.net
external-dns.alpha.kubernetes.io/target: 198.18.200.4
spec:
gatewayClassName: traefik-internal
listeners:
- name: http
port: 80
protocol: HTTP
hostname: vault.k8s.syd1.au.unkin.net
allowedRoutes:
namespaces:
from: Same
- name: https
port: 443
protocol: HTTPS
hostname: vault.k8s.syd1.au.unkin.net
allowedRoutes:
namespaces:
from: Same
tls:
mode: Terminate
certificateRefs:
- group: ""
kind: Secret
name: vault-tls
- name: vault-direct
port: 8200
protocol: HTTPS
allowedRoutes:
namespaces:
from: Same
tls:
mode: Terminate
certificateRefs:
- group: ""
kind: Secret
name: vault-tls
+72
View File
@@ -0,0 +1,72 @@
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: vault-http-redirect
namespace: vault
labels:
app.kubernetes.io/name: vault
app.kubernetes.io/instance: vault
spec:
hostnames:
- vault.k8s.syd1.au.unkin.net
parentRefs:
- name: vault
sectionName: http
rules:
- filters:
- type: RequestRedirect
requestRedirect:
scheme: https
statusCode: 301
matches:
- path:
type: PathPrefix
value: /
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: vault
namespace: vault
labels:
app.kubernetes.io/name: vault
app.kubernetes.io/instance: vault
spec:
hostnames:
- vault.k8s.syd1.au.unkin.net
parentRefs:
- name: vault
sectionName: https
rules:
- backendRefs:
- name: vault
port: 8200
matches:
- path:
type: PathPrefix
value: /
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: vault-consul
namespace: vault
labels:
app.kubernetes.io/name: vault
app.kubernetes.io/instance: vault
spec:
hostnames:
- vault.service.consul
- vault.query.consul
parentRefs:
- name: vault
sectionName: vault-direct
rules:
- backendRefs:
- name: vault
port: 8200
matches:
- path:
type: PathPrefix
value: /
+8
View File
@@ -0,0 +1,8 @@
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- namespace.yaml
- gateway.yaml
- httproute.yaml
+5
View File
@@ -0,0 +1,5 @@
---
apiVersion: v1
kind: Namespace
metadata:
name: vault
@@ -8,7 +8,7 @@ resources:
helmCharts:
- name: cert-manager
repo: https://artifactapi.k8s.syd1.au.unkin.net/api/v1/virtual/helm
version: "v1.19.2"
version: "v1.20.2"
releaseName: cert-manager
namespace: cert-manager
valuesFile: values.yaml
@@ -1,6 +1,11 @@
crds:
enabled: true
config:
apiVersion: controller.config.cert-manager.io/v1alpha1
kind: ControllerConfiguration
enableGatewayAPI: true
replicaCount: 2
resources:
@@ -0,0 +1,16 @@
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../../base/consul
helmCharts:
- name: consul
repo: https://helm.releases.hashicorp.com
version: "1.9.7"
releaseName: consul
namespace: consul
valuesFile: values.yaml
apiVersions:
- policy/v1/PodDisruptionBudget
+58
View File
@@ -0,0 +1,58 @@
global:
name: consul
datacenter: au-syd1
domain: consul
server:
image: hashicorp/consul:1.22.7
replicas: 5
bootstrapExpect: 5
storage: 10Gi
storageClass: cephrbd-fast-delete
connect: true
disruptionBudget:
maxUnavailable: 1
extraConfig: |
{
"disable_remote_exec": true,
"disable_update_check": true,
"performance": {
"raft_multiplier": 10
},
"ports": {
"dns": 8600,
"grpc": 8502,
"http": 8500,
"https": -1
},
"primary_datacenter": "au-syd1"
}
resources:
requests:
memory: 256Mi
cpu: 100m
limits:
memory: 2Gi
cpu: 1000m
client:
enabled: false
ui:
enabled: true
service:
type: ClusterIP
connectInject:
enabled: false
dns:
enabled: true
type: LoadBalancer
annotations: |
purelb.io/service-group: "common"
purelb.io/addresses: 198.18.200.5
@@ -8,7 +8,7 @@ resources:
helmCharts:
- name: external-dns
repo: https://artifactapi.k8s.syd1.au.unkin.net/api/v1/virtual/helm
version: "1.19.0"
version: "1.21.1"
releaseName: externaldns
namespace: externaldns
valuesFile: values.yaml
@@ -25,10 +25,7 @@ sources:
- service
- ingress
- gateway-httproute
- gateway-tlsroute
- gateway-grpcroute
- gateway-tcproute
- gateway-udproute
# Environment variables for TSIG secret and algorithm from Vault
env:
@@ -54,3 +51,5 @@ extraArgs:
- --rfc2136-tsig-axfr
- --rfc2136-tsig-secret=$(EXTERNAL_DNS_RFC2136_TSIG_SECRET)
- --ingress-class=nginx
logLevel: debug
@@ -0,0 +1,6 @@
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../../base/stalwart
@@ -94,5 +94,7 @@ ports:
port: 80
websecure:
port: 443
vault-direct:
port: 8200
enabled: true
@@ -0,0 +1,14 @@
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../../base/vault
helmCharts:
- name: vault
repo: https://helm.releases.hashicorp.com
version: "0.32.0"
releaseName: vault
namespace: vault
valuesFile: values.yaml
+73
View File
@@ -0,0 +1,73 @@
server:
image:
repository: hashicorp/vault
tag: "2.0.1"
ha:
enabled: true
replicas: 5
raft:
enabled: true
setNodeId: true
config: |
ui = true
disable_mlock = true
listener "tcp" {
address = "[::]:8200"
cluster_address = "[::]:8201"
tls_disable = "true"
}
storage "raft" {
path = "/vault/data"
retry_join {
leader_api_addr = "http://vault-0.vault-internal.vault.svc.cluster.local:8200"
}
retry_join {
leader_api_addr = "http://vault-1.vault-internal.vault.svc.cluster.local:8200"
}
retry_join {
leader_api_addr = "http://vault-2.vault-internal.vault.svc.cluster.local:8200"
}
retry_join {
leader_api_addr = "http://vault-3.vault-internal.vault.svc.cluster.local:8200"
}
retry_join {
leader_api_addr = "http://vault-4.vault-internal.vault.svc.cluster.local:8200"
}
}
service_registration "consul" {
address = "consul-server.consul.svc.cluster.local:8500"
}
dataStorage:
enabled: true
size: 10Gi
storageClass: cephrbd-fast-delete
accessMode: ReadWriteOnce
statefulSet:
securityContext:
container:
capabilities:
add:
- IPC_LOCK
resources:
requests:
memory: 256Mi
cpu: 100m
limits:
memory: 2Gi
cpu: 1000m
injector:
enabled: false
ui:
enabled: true
serviceType: ClusterIP
@@ -8,7 +8,7 @@ resources:
helmCharts:
- name: woodpecker
repo: oci://ghcr.io/woodpecker-ci/helm
version: "3.5.1"
version: "3.6.3"
releaseName: woodpecker
namespace: woodpecker
valuesFile: values.yaml
+3
View File
@@ -15,6 +15,7 @@ spec:
- path: apps/overlays/*/cert-manager
- path: apps/overlays/*/certificates
- path: apps/overlays/*/cnpg-system
- path: apps/overlays/*/consul
- path: apps/overlays/*/elastic-system
- path: apps/overlays/*/externaldns
- path: apps/overlays/*/inteldeviceplugins-system
@@ -25,8 +26,10 @@ spec:
- path: apps/overlays/*/reflector-system
- path: apps/overlays/*/reloader-system
- path: apps/overlays/*/reposync
- path: apps/overlays/*/stalwart
- path: apps/overlays/*/traefik-system
- path: apps/overlays/*/vm-system
- path: apps/overlays/*/vault
- path: apps/overlays/*/vso-system
- path: apps/overlays/*/woodpecker
template:
+4
View File
@@ -21,6 +21,8 @@ spec:
server: https://kubernetes.default.svc
- namespace: 'certificates'
server: https://kubernetes.default.svc
- namespace: 'consul'
server: https://kubernetes.default.svc
- namespace: 'externaldns'
server: https://kubernetes.default.svc
- namespace: 'jfrog'
@@ -33,6 +35,8 @@ spec:
server: https://kubernetes.default.svc
- namespace: 'reposync'
server: https://kubernetes.default.svc
- namespace: 'vault'
server: https://kubernetes.default.svc
- namespace: 'woodpecker'
server: https://kubernetes.default.svc
clusterResourceWhitelist: