mirror of
https://gitlab.kube.huskypup.net/Scooby/Homelabv4.git
synced 2026-08-20 23:16:49 +00:00
Initial commit
This commit is contained in:
+16
@@ -0,0 +1,16 @@
|
||||
# Environment-specific overrides
|
||||
environments/local.yaml
|
||||
|
||||
# Temporary files
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
.DS_Store
|
||||
|
||||
# Helm
|
||||
charts/*/charts/
|
||||
charts/*/tmpcharts/
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
.vscode/
|
||||
@@ -0,0 +1,106 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code when working with this repository.
|
||||
|
||||
## Overview
|
||||
|
||||
Homelabv4 is a fully automated Kubernetes homelab deployment using **ArgoCD App-of-Apps pattern** for GitOps-based continuous delivery. It replaces the Helmfile-based Homelabv5 with declarative ArgoCD Application CRDs, sync waves for ordering, and Kubernetes Jobs for complex hooks.
|
||||
|
||||
## Deployment
|
||||
|
||||
### Bootstrap (one-time)
|
||||
```bash
|
||||
# Prerequisites: ArgoCD must already be running on the cluster
|
||||
# Replace <GIT_REPO_URL> in all Application CRDs with your actual repo URL
|
||||
# Then:
|
||||
kubectl apply -f bootstrap.yaml
|
||||
```
|
||||
|
||||
### How It Works
|
||||
1. `bootstrap.yaml` creates a root Application pointing to `argocd-apps/`
|
||||
2. ArgoCD discovers all Application CRDs in `argocd-apps/infrastructure/` and `argocd-apps/apps/`
|
||||
3. Sync waves control deployment order (lower waves first)
|
||||
4. ArgoCD waits for resources to be healthy before advancing waves
|
||||
5. PreSync/PostSync hook Jobs handle complex initialization
|
||||
|
||||
### Verify Deployment
|
||||
```bash
|
||||
kubectl get applications -n argocd
|
||||
kubectl get pods -A | grep -v Running | grep -v Completed
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
### Sync Wave Scheme
|
||||
| Wave | Phase | Components |
|
||||
|------|-------|-----------|
|
||||
| -1 | Bootstrap | Prometheus CRDs |
|
||||
| 1-7 | Core Net | Cilium, Istio, cert-manager, metrics-server, envoy-gateway |
|
||||
| 10-14 | Storage | Rook-Ceph, CNPG, Redis, MariaDB, Scylla operators |
|
||||
| 20-23 | Security | Vault, ESO, Reloader, Authentik, Kyverno, Tetragon, CrowdSec |
|
||||
| 30-31 | Net Extra | External-DNS, Netbird, Mosquitto |
|
||||
| 40-43 | Monitoring | Prometheus, Grafana, Kiali, Checkov |
|
||||
| 50-52 | Apps | GitLab, ArgoCD, n8n, Nextcloud, TeslaMate, HA, Frigate, etc. |
|
||||
|
||||
### Directory Structure
|
||||
```
|
||||
Homelabv4/
|
||||
├── bootstrap.yaml # Root App-of-Apps
|
||||
├── argocd-apps/ # Application CRDs
|
||||
│ ├── infrastructure/ # ~35 infra Applications
|
||||
│ └── apps/ # ~10 user Applications
|
||||
├── infrastructure/ # Source files for infra
|
||||
│ └── <service>/
|
||||
│ ├── values.yaml # Helm values
|
||||
│ └── manifests/ # Raw K8s manifests
|
||||
├── apps/ # Source files for apps
|
||||
│ └── <app>/
|
||||
│ ├── values.yaml
|
||||
│ └── manifests/
|
||||
├── charts/ # Local Helm charts
|
||||
├── hooks/ # Shared hook RBAC
|
||||
├── environments/ # Environment config
|
||||
└── scripts/ # Manual scripts
|
||||
```
|
||||
|
||||
### Key Patterns
|
||||
|
||||
#### Multi-Source Applications
|
||||
Most Applications use 3 sources:
|
||||
1. Remote Helm chart repo
|
||||
2. Git repo ref for values files
|
||||
3. Git repo path for raw manifests
|
||||
|
||||
#### Hook Jobs
|
||||
Complex initialization (Vault init, OAuth sync) uses K8s Jobs with ArgoCD hook annotations:
|
||||
- `argocd.argoproj.io/hook: PreSync` or `PostSync`
|
||||
- `argocd.argoproj.io/hook-delete-policy: BeforeHookCreation`
|
||||
- Jobs use `argocd-hook-sa` ServiceAccount with cluster-admin
|
||||
|
||||
#### Secrets Flow
|
||||
Vault → External Secrets Operator → Kubernetes Secrets → Apps
|
||||
|
||||
## Common Operations
|
||||
|
||||
### Add a New Application
|
||||
1. Create `apps/<name>/values.yaml` with Helm values
|
||||
2. Create `apps/<name>/manifests/` with any raw manifests
|
||||
3. Create `argocd-apps/apps/<name>.yaml` Application CRD
|
||||
4. Commit and push — ArgoCD auto-syncs
|
||||
|
||||
### Update Helm Values
|
||||
1. Edit `infrastructure/<service>/values.yaml` or `apps/<app>/values.yaml`
|
||||
2. Commit and push — ArgoCD auto-syncs
|
||||
|
||||
### Force Sync
|
||||
```bash
|
||||
argocd app sync <app-name>
|
||||
```
|
||||
|
||||
## Important Notes
|
||||
- **Git repo URL**: Replace `<GIT_REPO_URL>` placeholder in all Application CRDs
|
||||
- **Secrets**: Never commit secrets — use Vault + ExternalSecret
|
||||
- **Domain**: All services use `*.kube.huskypup.net`
|
||||
- **Storage**: Default StorageClass is `rook-ceph-block`
|
||||
- **Ingress**: Istio VirtualServices + Envoy Gateway HTTPRoutes
|
||||
- **mTLS**: Istio ambient mesh with STRICT PeerAuthentication
|
||||
@@ -0,0 +1,84 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: argocd-cm-custom-health
|
||||
namespace: argocd
|
||||
labels:
|
||||
app.kubernetes.io/part-of: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "-1"
|
||||
data:
|
||||
resource.customizations.health.ceph.rook.io_CephCluster: |
|
||||
hs = {}
|
||||
if obj.status ~= nil then
|
||||
if obj.status.phase == "Ready" and obj.status.ceph ~= nil and obj.status.ceph.health == "HEALTH_OK" then
|
||||
hs.status = "Healthy"
|
||||
hs.message = "CephCluster is healthy"
|
||||
elseif obj.status.phase == "Progressing" then
|
||||
hs.status = "Progressing"
|
||||
hs.message = obj.status.message or "CephCluster is progressing"
|
||||
else
|
||||
hs.status = "Degraded"
|
||||
hs.message = obj.status.message or "CephCluster is not healthy"
|
||||
end
|
||||
else
|
||||
hs.status = "Progressing"
|
||||
hs.message = "Waiting for CephCluster status"
|
||||
end
|
||||
return hs
|
||||
resource.customizations.health.postgresql.cnpg.io_Cluster: |
|
||||
hs = {}
|
||||
if obj.status ~= nil then
|
||||
if obj.status.phase == "Cluster in healthy state" then
|
||||
hs.status = "Healthy"
|
||||
hs.message = "CNPG Cluster is healthy"
|
||||
elseif obj.status.phase == "Setting up primary" or obj.status.phase == "Creating primary" then
|
||||
hs.status = "Progressing"
|
||||
hs.message = obj.status.phase
|
||||
else
|
||||
hs.status = "Degraded"
|
||||
hs.message = obj.status.phase or "CNPG Cluster is not healthy"
|
||||
end
|
||||
else
|
||||
hs.status = "Progressing"
|
||||
hs.message = "Waiting for CNPG Cluster status"
|
||||
end
|
||||
return hs
|
||||
resource.customizations.health.external-secrets.io_ClusterSecretStore: |
|
||||
hs = {}
|
||||
if obj.status ~= nil and obj.status.conditions ~= nil then
|
||||
for i, condition in ipairs(obj.status.conditions) do
|
||||
if condition.type == "Ready" then
|
||||
if condition.status == "True" then
|
||||
hs.status = "Healthy"
|
||||
hs.message = "ClusterSecretStore is ready"
|
||||
else
|
||||
hs.status = "Degraded"
|
||||
hs.message = condition.message or "ClusterSecretStore is not ready"
|
||||
end
|
||||
return hs
|
||||
end
|
||||
end
|
||||
end
|
||||
hs.status = "Progressing"
|
||||
hs.message = "Waiting for ClusterSecretStore status"
|
||||
return hs
|
||||
resource.customizations.health.kiali.io_Kiali: |
|
||||
hs = {}
|
||||
if obj.status ~= nil then
|
||||
if obj.status.conditions ~= nil then
|
||||
for i, condition in ipairs(obj.status.conditions) do
|
||||
if condition.type == "Successful" and condition.status == "True" then
|
||||
hs.status = "Healthy"
|
||||
hs.message = "Kiali is healthy"
|
||||
return hs
|
||||
end
|
||||
end
|
||||
end
|
||||
hs.status = "Progressing"
|
||||
hs.message = "Kiali is being deployed"
|
||||
else
|
||||
hs.status = "Progressing"
|
||||
hs.message = "Waiting for Kiali status"
|
||||
end
|
||||
return hs
|
||||
@@ -0,0 +1,20 @@
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: HTTPRoute
|
||||
metadata:
|
||||
name: argocd
|
||||
namespace: argocd
|
||||
spec:
|
||||
parentRefs:
|
||||
- name: edge
|
||||
namespace: gateway
|
||||
sectionName: https
|
||||
hostnames:
|
||||
- argocd.kube.huskypup.net
|
||||
rules:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: argocd-server
|
||||
port: 80
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
apiVersion: external-secrets.io/v1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: argocd-oauth
|
||||
namespace: argocd
|
||||
spec:
|
||||
refreshInterval: 1h
|
||||
secretStoreRef:
|
||||
kind: ClusterSecretStore
|
||||
name: vault-backend
|
||||
target:
|
||||
name: argocd-oauth-secret
|
||||
creationPolicy: Owner
|
||||
data:
|
||||
- secretKey: client-id
|
||||
remoteRef:
|
||||
key: argocd-oauth
|
||||
property: client-id
|
||||
- secretKey: client-secret
|
||||
remoteRef:
|
||||
key: argocd-oauth
|
||||
property: client-secret
|
||||
@@ -0,0 +1,16 @@
|
||||
apiVersion: networking.istio.io/v1beta1
|
||||
kind: VirtualService
|
||||
metadata:
|
||||
name: argocd
|
||||
namespace: argocd
|
||||
spec:
|
||||
hosts:
|
||||
- argocd.kube.huskypup.net
|
||||
gateways:
|
||||
- istio-system/edge
|
||||
http:
|
||||
- route:
|
||||
- destination:
|
||||
host: argocd-server.argocd.svc.cluster.local
|
||||
port:
|
||||
number: 80
|
||||
@@ -0,0 +1,78 @@
|
||||
# values/argocd.values.yaml
|
||||
|
||||
global:
|
||||
domain: argocd.kube.huskypup.net
|
||||
|
||||
configs:
|
||||
cm:
|
||||
url: https://argocd.kube.huskypup.net
|
||||
oidc.config: |
|
||||
name: Authentik
|
||||
issuer: https://auth.kube.huskypup.net/application/o/argocd/
|
||||
clientID: $oidc.authentik.clientId
|
||||
clientSecret: $oidc.authentik.clientSecret
|
||||
requestedScopes:
|
||||
- openid
|
||||
- profile
|
||||
- email
|
||||
- groups
|
||||
|
||||
rbac:
|
||||
policy.default: role:readonly
|
||||
policy.csv: |
|
||||
g, Authentik Admins, role:admin
|
||||
g, ArgoCD Admins, role:admin
|
||||
|
||||
server:
|
||||
extraArgs:
|
||||
- --insecure # TLS is terminated at Istio gateway
|
||||
|
||||
ingress:
|
||||
enabled: false
|
||||
|
||||
env:
|
||||
- name: oidc.authentik.clientId
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: argocd-oauth-secret
|
||||
key: client-id
|
||||
- name: oidc.authentik.clientSecret
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: argocd-oauth-secret
|
||||
key: client-secret
|
||||
|
||||
dex:
|
||||
enabled: false # using Authentik instead of built-in Dex
|
||||
|
||||
# Enable Prometheus metrics
|
||||
metrics:
|
||||
enabled: true
|
||||
serviceMonitor:
|
||||
enabled: true
|
||||
additionalLabels:
|
||||
release: prometheus
|
||||
|
||||
controller:
|
||||
metrics:
|
||||
enabled: true
|
||||
serviceMonitor:
|
||||
enabled: true
|
||||
additionalLabels:
|
||||
release: prometheus
|
||||
|
||||
repoServer:
|
||||
metrics:
|
||||
enabled: true
|
||||
serviceMonitor:
|
||||
enabled: true
|
||||
additionalLabels:
|
||||
release: prometheus
|
||||
|
||||
applicationSet:
|
||||
metrics:
|
||||
enabled: true
|
||||
serviceMonitor:
|
||||
enabled: true
|
||||
additionalLabels:
|
||||
release: prometheus
|
||||
@@ -0,0 +1,20 @@
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: HTTPRoute
|
||||
metadata:
|
||||
name: frigate
|
||||
namespace: frigate
|
||||
spec:
|
||||
parentRefs:
|
||||
- name: edge
|
||||
namespace: gateway
|
||||
sectionName: https
|
||||
hostnames:
|
||||
- frigate.kube.huskypup.net
|
||||
rules:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: frigate
|
||||
port: 5000
|
||||
@@ -0,0 +1,27 @@
|
||||
apiVersion: networking.istio.io/v1beta1
|
||||
kind: VirtualService
|
||||
metadata:
|
||||
name: frigate
|
||||
namespace: frigate
|
||||
spec:
|
||||
hosts:
|
||||
- frigate.kube.huskypup.net
|
||||
gateways:
|
||||
- istio-system/edge
|
||||
http:
|
||||
# Authentik forward-auth endpoints must be reachable on the protected host
|
||||
- match:
|
||||
- uri:
|
||||
prefix: /outpost.goauthentik.io
|
||||
route:
|
||||
- destination:
|
||||
host: authentik-server.authentik.svc.cluster.local
|
||||
port:
|
||||
number: 80
|
||||
|
||||
- timeout: 3600s
|
||||
route:
|
||||
- destination:
|
||||
host: frigate.frigate.svc.cluster.local
|
||||
port:
|
||||
number: 5000
|
||||
@@ -0,0 +1,29 @@
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: frigate-config
|
||||
namespace: frigate
|
||||
labels:
|
||||
app.kubernetes.io/name: frigate
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
storageClassName: rook-ceph-block
|
||||
resources:
|
||||
requests:
|
||||
storage: 500Mi
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: frigate-media
|
||||
namespace: frigate
|
||||
labels:
|
||||
app.kubernetes.io/name: frigate
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
storageClassName: rook-ceph-block
|
||||
resources:
|
||||
requests:
|
||||
storage: 500Gi
|
||||
@@ -0,0 +1,11 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: frigate-rtsp-credentials
|
||||
namespace: frigate
|
||||
type: Opaque
|
||||
stringData:
|
||||
# Update these with your actual camera credentials
|
||||
# Then update apps/frigate/values.yaml with your camera RTSP URLs
|
||||
FRIGATE_RTSP_USERNAME: your_camera_username
|
||||
FRIGATE_RTSP_PASSWORD: your_camera_password
|
||||
@@ -0,0 +1,182 @@
|
||||
# Frigate Helm Chart Values
|
||||
# NVR with realtime object detection for IP cameras
|
||||
#
|
||||
# FEATURES:
|
||||
# ✓ Object detection for IP cameras
|
||||
# ✓ Persistent storage for recordings via Rook-Ceph
|
||||
# ✓ TLS certificates via cert-manager
|
||||
# ✓ Ingress via Istio VirtualService
|
||||
# ✓ MQTT integration (optional)
|
||||
|
||||
# Reduce Istio sidecar CPU request - frigate already uses 1 CPU request
|
||||
# and the cluster runs at ~98% CPU allocation. Default 10m sidecar request
|
||||
# can prevent scheduling.
|
||||
podAnnotations:
|
||||
sidecar.istio.io/proxyCPU: "1m"
|
||||
|
||||
image:
|
||||
repository: ghcr.io/blakeblackshear/frigate
|
||||
tag: "0.13.2"
|
||||
pullPolicy: IfNotPresent
|
||||
|
||||
service:
|
||||
main:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
http:
|
||||
port: 5000
|
||||
|
||||
# Ingress disabled - Istio VirtualService handles routing
|
||||
ingress:
|
||||
main:
|
||||
enabled: false
|
||||
|
||||
env:
|
||||
TZ: America/New_York
|
||||
FRIGATE_MQTT_HOST: "mosquitto.mqtt"
|
||||
FRIGATE_MQTT_PORT: "1883"
|
||||
|
||||
envFromSecrets:
|
||||
- frigate-rtsp-credentials
|
||||
|
||||
config: |
|
||||
mqtt:
|
||||
host: mosquitto.mqtt
|
||||
port: 1883
|
||||
topic_prefix: frigate
|
||||
client_id: frigate
|
||||
detectors:
|
||||
cpu1:
|
||||
type: cpu
|
||||
cameras:
|
||||
basement:
|
||||
ffmpeg:
|
||||
inputs:
|
||||
- path: rtsp://admin:admin!@172.28.200.10:554/cam/realmonitor?channel=1&subtype=1
|
||||
roles:
|
||||
- detect
|
||||
- path: rtsp://admin:admin!@172.28.200.10:554/cam/realmonitor?channel=1&subtype=0
|
||||
roles:
|
||||
- record
|
||||
detect:
|
||||
width: 704
|
||||
height: 480
|
||||
fps: 5
|
||||
record:
|
||||
enabled: true
|
||||
retain:
|
||||
days: 5
|
||||
mode: all
|
||||
snapshots:
|
||||
enabled: true
|
||||
timestamp: true
|
||||
bounding_box: true
|
||||
patio:
|
||||
ffmpeg:
|
||||
inputs:
|
||||
- path: rtsp://admin:admin!@172.28.200.11:554/cam/realmonitor?channel=1&subtype=1
|
||||
roles:
|
||||
- detect
|
||||
- path: rtsp://admin:admin!@172.28.200.11:554/cam/realmonitor?channel=1&subtype=0
|
||||
roles:
|
||||
- record
|
||||
detect:
|
||||
width: 704
|
||||
height: 480
|
||||
fps: 5
|
||||
record:
|
||||
enabled: true
|
||||
retain:
|
||||
days: 5
|
||||
mode: all
|
||||
snapshots:
|
||||
enabled: true
|
||||
timestamp: true
|
||||
bounding_box: true
|
||||
backyard:
|
||||
ffmpeg:
|
||||
inputs:
|
||||
- path: rtsp://admin:admin!@172.28.200.12:554/cam/realmonitor?channel=1&subtype=1
|
||||
roles:
|
||||
- detect
|
||||
- path: rtsp://admin:admin!@172.28.200.12:554/cam/realmonitor?channel=1&subtype=0
|
||||
roles:
|
||||
- record
|
||||
detect:
|
||||
width: 704
|
||||
height: 480
|
||||
fps: 5
|
||||
record:
|
||||
enabled: true
|
||||
retain:
|
||||
days: 5
|
||||
mode: all
|
||||
snapshots:
|
||||
enabled: true
|
||||
timestamp: true
|
||||
bounding_box: true
|
||||
go2rtc:
|
||||
streams:
|
||||
basement: rtsp://admin:admin!@172.28.200.10:554/cam/realmonitor?channel=1&subtype=0
|
||||
basement_alt: rtsp://admin:admin!@172.28.200.10:554/cam/realmonitor?channel=1&subtype=1
|
||||
patio: rtsp://admin:admin!@172.28.200.11:554/cam/realmonitor?channel=1&subtype=0
|
||||
patio_alt: rtsp://admin:admin!@172.28.200.11:554/cam/realmonitor?channel=1&subtype=1
|
||||
backyard: rtsp://admin:admin!@172.28.200.12:554/cam/realmonitor?channel=1&subtype=0
|
||||
backyard_alt: rtsp://admin:admin!@172.28.200.12:554/cam/realmonitor?channel=1&subtype=1
|
||||
objects:
|
||||
track:
|
||||
- person
|
||||
- car
|
||||
- dog
|
||||
- cat
|
||||
- vehicle
|
||||
motion:
|
||||
threshold: 25
|
||||
|
||||
persistence:
|
||||
config:
|
||||
enabled: true
|
||||
storageClass: rook-ceph-block
|
||||
accessMode: ReadWriteOnce
|
||||
size: 500Mi
|
||||
media:
|
||||
enabled: true
|
||||
storageClass: rook-ceph-block
|
||||
accessMode: ReadWriteOnce
|
||||
size: 500Gi
|
||||
|
||||
shmSize: 2Gi
|
||||
|
||||
tmpfs:
|
||||
enabled: true
|
||||
sizeLimit: 2Gi
|
||||
|
||||
securityContext:
|
||||
privileged: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 1Gi
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 4Gi
|
||||
|
||||
probes:
|
||||
liveness:
|
||||
enabled: true
|
||||
initialDelaySeconds: 5
|
||||
failureThreshold: 5
|
||||
readiness:
|
||||
enabled: true
|
||||
initialDelaySeconds: 5
|
||||
failureThreshold: 5
|
||||
startup:
|
||||
enabled: true
|
||||
failureThreshold: 5
|
||||
|
||||
nodeSelector:
|
||||
kubernetes.io/arch: amd64
|
||||
@@ -0,0 +1,74 @@
|
||||
apiVersion: postgresql.cnpg.io/v1
|
||||
kind: Cluster
|
||||
metadata:
|
||||
name: pg-gitlab
|
||||
namespace: gitlab
|
||||
spec:
|
||||
imageName: ghcr.io/cloudnative-pg/postgresql:16
|
||||
instances: 3 # 3 instances for production HA
|
||||
|
||||
# Database resources (homelab-friendly requests, burstable CPU)
|
||||
resources:
|
||||
requests:
|
||||
memory: "1Gi"
|
||||
cpu: "50m"
|
||||
limits:
|
||||
memory: "4Gi"
|
||||
cpu: "500m"
|
||||
|
||||
# Spread replicas across different nodes
|
||||
affinity:
|
||||
topologyKey: kubernetes.io/hostname
|
||||
|
||||
storage:
|
||||
size: 50Gi # Increased for production
|
||||
storageClass: rook-ceph-block
|
||||
|
||||
primaryUpdateStrategy: unsupervised
|
||||
|
||||
bootstrap:
|
||||
initdb:
|
||||
database: gitlabhq_production
|
||||
owner: app
|
||||
postInitSQL:
|
||||
- CREATE EXTENSION IF NOT EXISTS pg_trgm;
|
||||
- CREATE EXTENSION IF NOT EXISTS btree_gist;
|
||||
|
||||
# PostgreSQL configuration tuning for GitLab
|
||||
postgresql:
|
||||
parameters:
|
||||
max_connections: "400"
|
||||
shared_buffers: "1GB"
|
||||
effective_cache_size: "3GB"
|
||||
maintenance_work_mem: "256MB"
|
||||
checkpoint_completion_target: "0.9"
|
||||
wal_buffers: "16MB"
|
||||
default_statistics_target: "100"
|
||||
random_page_cost: "1.1"
|
||||
effective_io_concurrency: "200"
|
||||
work_mem: "16MB"
|
||||
min_wal_size: "1GB"
|
||||
max_wal_size: "4GB"
|
||||
max_worker_processes: "4"
|
||||
max_parallel_workers_per_gather: "2"
|
||||
max_parallel_workers: "4"
|
||||
|
||||
# Backup configuration to MinIO
|
||||
backup:
|
||||
barmanObjectStore:
|
||||
destinationPath: s3://gitlab-backups/pg-gitlab
|
||||
endpointURL: http://gitlab-minio-svc.gitlab.svc.cluster.local:9000
|
||||
s3Credentials:
|
||||
accessKeyId:
|
||||
name: gitlab-minio-secret
|
||||
key: accesskey
|
||||
secretAccessKey:
|
||||
name: gitlab-minio-secret
|
||||
key: secretkey
|
||||
wal:
|
||||
compression: gzip
|
||||
maxParallel: 2
|
||||
retentionPolicy: "30d"
|
||||
|
||||
monitoring:
|
||||
enablePodMonitor: true
|
||||
@@ -0,0 +1,86 @@
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: HTTPRoute
|
||||
metadata:
|
||||
name: gitlab-web
|
||||
namespace: gitlab
|
||||
spec:
|
||||
parentRefs:
|
||||
- name: edge
|
||||
namespace: gateway
|
||||
sectionName: https
|
||||
hostnames:
|
||||
- gitlab.kube.huskypup.net
|
||||
rules:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: gitlab-webservice-default
|
||||
port: 8181
|
||||
|
||||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: HTTPRoute
|
||||
metadata:
|
||||
name: gitlab-registry
|
||||
namespace: gitlab
|
||||
spec:
|
||||
parentRefs:
|
||||
- name: edge
|
||||
namespace: gateway
|
||||
sectionName: https
|
||||
hostnames:
|
||||
- registry.gitlab.kube.huskypup.net
|
||||
rules:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: gitlab-registry
|
||||
port: 5000
|
||||
|
||||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: HTTPRoute
|
||||
metadata:
|
||||
name: gitlab-minio
|
||||
namespace: gitlab
|
||||
spec:
|
||||
parentRefs:
|
||||
- name: edge
|
||||
namespace: gateway
|
||||
sectionName: https
|
||||
hostnames:
|
||||
- minio.gitlab.kube.huskypup.net
|
||||
rules:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: gitlab-minio-svc
|
||||
port: 9000
|
||||
|
||||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: HTTPRoute
|
||||
metadata:
|
||||
name: gitlab-kas
|
||||
namespace: gitlab
|
||||
spec:
|
||||
parentRefs:
|
||||
- name: edge
|
||||
namespace: gateway
|
||||
sectionName: https
|
||||
hostnames:
|
||||
- kas.kube.huskypup.net
|
||||
rules:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: gitlab-kas
|
||||
port: 8154
|
||||
@@ -0,0 +1,27 @@
|
||||
apiVersion: external-secrets.io/v1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: gitlab-saml
|
||||
namespace: gitlab
|
||||
spec:
|
||||
refreshInterval: 1h
|
||||
secretStoreRef:
|
||||
kind: ClusterSecretStore
|
||||
name: vault-backend
|
||||
target:
|
||||
name: gitlab-saml-secret
|
||||
creationPolicy: Owner
|
||||
template:
|
||||
type: Opaque
|
||||
data:
|
||||
GITLAB_SAML_IDP_SSO_URL: "{{ .idp_sso_url }}"
|
||||
GITLAB_SAML_IDP_FINGERPRINT: "{{ .idp_fingerprint }}"
|
||||
data:
|
||||
- secretKey: idp_sso_url
|
||||
remoteRef:
|
||||
key: gitlab/saml
|
||||
property: idp_sso_url
|
||||
- secretKey: idp_fingerprint
|
||||
remoteRef:
|
||||
key: gitlab/saml
|
||||
property: idp_fingerprint
|
||||
@@ -0,0 +1,19 @@
|
||||
# GitLab Unified TLS Certificate
|
||||
# Covers all GitLab domains in a single certificate
|
||||
# This prevents issues with GitLab chart creating separate certificates
|
||||
# that may have incorrect domain names
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: Certificate
|
||||
metadata:
|
||||
name: gitlab-unified-tls
|
||||
namespace: gitlab
|
||||
spec:
|
||||
secretName: gitlab-tls
|
||||
issuerRef:
|
||||
name: letsencrypt-production
|
||||
kind: ClusterIssuer
|
||||
dnsNames:
|
||||
- gitlab.kube.huskypup.net
|
||||
- registry.gitlab.kube.huskypup.net
|
||||
- minio.gitlab.kube.huskypup.net
|
||||
- kas.kube.huskypup.net
|
||||
@@ -0,0 +1,74 @@
|
||||
apiVersion: networking.istio.io/v1beta1
|
||||
kind: VirtualService
|
||||
metadata:
|
||||
name: gitlab
|
||||
namespace: gitlab
|
||||
spec:
|
||||
hosts:
|
||||
- gitlab.kube.huskypup.net
|
||||
gateways:
|
||||
- istio-system/edge
|
||||
http:
|
||||
- timeout: 3600s
|
||||
route:
|
||||
- destination:
|
||||
host: gitlab-webservice-default.gitlab.svc.cluster.local
|
||||
port:
|
||||
number: 8181
|
||||
|
||||
---
|
||||
apiVersion: networking.istio.io/v1beta1
|
||||
kind: VirtualService
|
||||
metadata:
|
||||
name: gitlab-registry
|
||||
namespace: gitlab
|
||||
spec:
|
||||
hosts:
|
||||
- registry.gitlab.kube.huskypup.net
|
||||
gateways:
|
||||
- istio-system/edge
|
||||
http:
|
||||
- timeout: 3600s
|
||||
route:
|
||||
- destination:
|
||||
host: gitlab-registry.gitlab.svc.cluster.local
|
||||
port:
|
||||
number: 5000
|
||||
|
||||
---
|
||||
apiVersion: networking.istio.io/v1beta1
|
||||
kind: VirtualService
|
||||
metadata:
|
||||
name: gitlab-minio
|
||||
namespace: gitlab
|
||||
spec:
|
||||
hosts:
|
||||
- minio.gitlab.kube.huskypup.net
|
||||
gateways:
|
||||
- istio-system/edge
|
||||
http:
|
||||
- timeout: 3600s
|
||||
route:
|
||||
- destination:
|
||||
host: gitlab-minio-svc.gitlab.svc.cluster.local
|
||||
port:
|
||||
number: 9000
|
||||
|
||||
---
|
||||
apiVersion: networking.istio.io/v1beta1
|
||||
kind: VirtualService
|
||||
metadata:
|
||||
name: gitlab-kas
|
||||
namespace: gitlab
|
||||
spec:
|
||||
hosts:
|
||||
- kas.kube.huskypup.net
|
||||
gateways:
|
||||
- istio-system/edge
|
||||
http:
|
||||
- timeout: 3600s
|
||||
route:
|
||||
- destination:
|
||||
host: gitlab-kas.gitlab.svc.cluster.local
|
||||
port:
|
||||
number: 8154
|
||||
@@ -0,0 +1,206 @@
|
||||
---
|
||||
# ServiceAccount for the CronJob that monitors PostgreSQL secret changes
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: pg-restart-sa
|
||||
namespace: gitlab
|
||||
---
|
||||
# Role to allow patching Deployments, StatefulSets, Clusters and reading Secrets
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: pg-restart-role
|
||||
namespace: gitlab
|
||||
rules:
|
||||
- apiGroups: ["apps"]
|
||||
resources: ["deployments", "statefulsets"]
|
||||
verbs: ["get", "patch"]
|
||||
- apiGroups: ["postgresql.cnpg.io"]
|
||||
resources: ["clusters"]
|
||||
verbs: ["get", "patch"]
|
||||
- apiGroups: [""]
|
||||
resources: ["secrets"]
|
||||
verbs: ["get"]
|
||||
- apiGroups: ["apps"]
|
||||
resources: ["deployments/status", "statefulsets/status"]
|
||||
verbs: ["get"]
|
||||
- apiGroups: ["postgresql.cnpg.io"]
|
||||
resources: ["clusters/status"]
|
||||
verbs: ["get"]
|
||||
---
|
||||
# RoleBinding to grant permissions to the ServiceAccount
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: pg-restart-binding
|
||||
namespace: gitlab
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: pg-restart-role
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: pg-restart-sa
|
||||
namespace: gitlab
|
||||
---
|
||||
# CronJob to monitor pg-gitlab-app secret and trigger restarts on changes
|
||||
apiVersion: batch/v1
|
||||
kind: CronJob
|
||||
metadata:
|
||||
name: pg-gitlab-secret-monitor
|
||||
namespace: gitlab
|
||||
spec:
|
||||
# Run every 30 minutes to check for secret changes (rotations happen at most daily)
|
||||
schedule: "*/30 * * * *"
|
||||
concurrencyPolicy: Forbid
|
||||
successfulJobsHistoryLimit: 1
|
||||
failedJobsHistoryLimit: 1
|
||||
jobTemplate:
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: pg-gitlab-secret-monitor
|
||||
spec:
|
||||
serviceAccountName: pg-restart-sa
|
||||
restartPolicy: OnFailure
|
||||
containers:
|
||||
- name: monitor
|
||||
image: docker.io/alpine/k8s:1.32.13
|
||||
securityContext:
|
||||
runAsUser: 10000
|
||||
runAsGroup: 10000
|
||||
runAsNonRoot: true
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
capabilities:
|
||||
drop: ["ALL"]
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
volumeMounts:
|
||||
- name: tmp
|
||||
mountPath: /tmp
|
||||
command:
|
||||
- /bin/bash
|
||||
- -c
|
||||
- |
|
||||
set -e
|
||||
|
||||
# Get current secret version
|
||||
SECRET_VERSION=$(kubectl get secret -n gitlab pg-gitlab-app -o jsonpath='{.metadata.resourceVersion}')
|
||||
|
||||
# Get last known secret version from pgbouncer deployment annotation
|
||||
LAST_VERSION=$(kubectl get deployment -n gitlab pgbouncer-gitlab -o jsonpath='{.spec.template.metadata.annotations.secret-version/pg-password}' 2>/dev/null || echo "")
|
||||
|
||||
echo "Current secret version: $SECRET_VERSION"
|
||||
echo "Last known version: $LAST_VERSION"
|
||||
|
||||
# If versions differ, update database password and restart resources
|
||||
if [ "$SECRET_VERSION" != "$LAST_VERSION" ]; then
|
||||
echo "Secret has changed! Updating database password and resources..."
|
||||
|
||||
# Get the new password from the secret
|
||||
NEW_PASSWORD=$(kubectl get secret -n gitlab pg-gitlab-app -o jsonpath='{.data.password}' | base64 -d)
|
||||
|
||||
# Update the database user password
|
||||
# Try both pg-gitlab-1 and pg-gitlab-2 in case one is restarting
|
||||
kubectl exec -n gitlab pg-gitlab-1 -c postgres -- psql -U postgres -d gitlabhq_production -c "ALTER USER app PASSWORD '$NEW_PASSWORD';" 2>/dev/null || \
|
||||
kubectl exec -n gitlab pg-gitlab-2 -c postgres -- psql -U postgres -d gitlabhq_production -c "ALTER USER app PASSWORD '$NEW_PASSWORD';" 2>/dev/null || \
|
||||
echo "Database password update failed"
|
||||
|
||||
# Update password table with new hash for PgBouncer SCRAM auth
|
||||
kubectl exec -n gitlab pg-gitlab-1 -c postgres -- psql -U postgres -d gitlabhq_production -c "INSERT INTO public.user_passwords (usename, passwd) SELECT rolname, rolpassword FROM pg_authid WHERE rolname = 'app' ON CONFLICT (usename) DO UPDATE SET passwd = EXCLUDED.passwd;" 2>/dev/null || \
|
||||
kubectl exec -n gitlab pg-gitlab-2 -c postgres -- psql -U postgres -d gitlabhq_production -c "INSERT INTO public.user_passwords (usename, passwd) SELECT rolname, rolpassword FROM pg_authid WHERE rolname = 'app' ON CONFLICT (usename) DO UPDATE SET passwd = EXCLUDED.passwd;" 2>/dev/null || \
|
||||
echo "Password table update failed, PgBouncer may need manual restart"
|
||||
|
||||
# Patch pgbouncer deployments to trigger restart
|
||||
kubectl patch deployment -n gitlab pgbouncer-gitlab -p "{\"spec\":{\"template\":{\"metadata\":{\"annotations\":{\"secret-version/pg-password\":\"$SECRET_VERSION\",\"restarted-at\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}}}}}" 2>/dev/null || echo "Deployment patch failed"
|
||||
|
||||
echo "Database password updated and resources will restart."
|
||||
else
|
||||
echo "Secret has not changed. No restart needed."
|
||||
fi
|
||||
volumes:
|
||||
- name: tmp
|
||||
emptyDir: {}
|
||||
---
|
||||
# CronJob to monitor pg-praefect-app secret and trigger restarts on changes
|
||||
apiVersion: batch/v1
|
||||
kind: CronJob
|
||||
metadata:
|
||||
name: pg-praefect-secret-monitor
|
||||
namespace: gitlab
|
||||
spec:
|
||||
# Run every 30 minutes to check for secret changes (rotations happen at most daily)
|
||||
schedule: "*/30 * * * *"
|
||||
concurrencyPolicy: Forbid
|
||||
successfulJobsHistoryLimit: 1
|
||||
failedJobsHistoryLimit: 1
|
||||
jobTemplate:
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: pg-praefect-secret-monitor
|
||||
spec:
|
||||
serviceAccountName: pg-restart-sa
|
||||
restartPolicy: OnFailure
|
||||
containers:
|
||||
- name: monitor
|
||||
image: docker.io/alpine/k8s:1.32.13
|
||||
securityContext:
|
||||
runAsUser: 10000
|
||||
runAsGroup: 10000
|
||||
runAsNonRoot: true
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
capabilities:
|
||||
drop: ["ALL"]
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
volumeMounts:
|
||||
- name: tmp
|
||||
mountPath: /tmp
|
||||
command:
|
||||
- /bin/bash
|
||||
- -c
|
||||
- |
|
||||
set -e
|
||||
|
||||
# Get current secret version
|
||||
SECRET_VERSION=$(kubectl get secret -n gitlab pg-praefect-app -o jsonpath='{.metadata.resourceVersion}')
|
||||
|
||||
# Get last known secret version from gitaly statefulset annotation
|
||||
LAST_VERSION=$(kubectl get statefulset -n gitlab gitlab-gitaly-default -o jsonpath='{.spec.template.metadata.annotations.secret-version/pg-password}' 2>/dev/null || echo "")
|
||||
|
||||
echo "Current secret version: $SECRET_VERSION"
|
||||
echo "Last known version: $LAST_VERSION"
|
||||
|
||||
# If versions differ, update database password and restart resources
|
||||
if [ "$SECRET_VERSION" != "$LAST_VERSION" ]; then
|
||||
echo "Secret has changed! Updating database password and resources..."
|
||||
|
||||
# Get the new password from the secret
|
||||
NEW_PASSWORD=$(kubectl get secret -n gitlab pg-praefect-app -o jsonpath='{.data.password}' | base64 -d)
|
||||
|
||||
# Update the database user password
|
||||
# Try both pg-praefect-3 and pg-praefect-4
|
||||
kubectl exec -n gitlab pg-praefect-3 -c postgres -- psql -U postgres -d gitlabhq_production -c "ALTER USER app PASSWORD '$NEW_PASSWORD';" 2>/dev/null || \
|
||||
kubectl exec -n gitlab pg-praefect-4 -c postgres -- psql -U postgres -d gitlabhq_production -c "ALTER USER app PASSWORD '$NEW_PASSWORD';" 2>/dev/null || \
|
||||
echo "Database password update failed"
|
||||
|
||||
# Patch gitaly and praefect statefulsets
|
||||
kubectl patch statefulset -n gitlab gitlab-gitaly-default -p "{\"spec\":{\"template\":{\"metadata\":{\"annotations\":{\"secret-version/pg-password\":\"$SECRET_VERSION\",\"restarted-at\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}}}}}"
|
||||
kubectl patch statefulset -n gitlab gitlab-praefect -p "{\"spec\":{\"template\":{\"metadata\":{\"annotations\":{\"secret-version/pg-password\":\"$SECRET_VERSION\",\"restarted-at\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}}}}}"
|
||||
|
||||
# Patch CNPG cluster
|
||||
kubectl patch cluster -n gitlab pg-praefect -p "{\"metadata\":{\"annotations\":{\"secret-version/pg-password\":\"$SECRET_VERSION\",\"restarted-at\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}}}" --type merge
|
||||
|
||||
echo "Database password updated and resources will restart."
|
||||
else
|
||||
echo "Secret has not changed. No restart needed."
|
||||
fi
|
||||
volumes:
|
||||
- name: tmp
|
||||
emptyDir: {}
|
||||
@@ -0,0 +1,116 @@
|
||||
---
|
||||
# PgBouncer Pooler for GitLab PostgreSQL
|
||||
# Managed by CloudNativePG Operator
|
||||
apiVersion: postgresql.cnpg.io/v1
|
||||
kind: Pooler
|
||||
metadata:
|
||||
name: pgbouncer-gitlab
|
||||
namespace: gitlab
|
||||
spec:
|
||||
cluster:
|
||||
name: pg-gitlab
|
||||
|
||||
# Number of PgBouncer instances (keep small; GitLab points directly at CNPG RW service)
|
||||
instances: 1
|
||||
|
||||
# PgBouncer configuration
|
||||
type: rw # Read-Write pooler (connects to primary)
|
||||
|
||||
pgbouncer:
|
||||
poolMode: transaction
|
||||
authQuerySecret:
|
||||
name: pg-gitlab-app
|
||||
# Use custom user_search function for SCRAM-SHA-256 authentication
|
||||
# This function is created by gitlab-bootstrap.sh script
|
||||
authQuery: "SELECT usename, passwd FROM public.user_search($1)"
|
||||
|
||||
parameters:
|
||||
max_client_conn: "2000"
|
||||
default_pool_size: "50"
|
||||
reserve_pool_size: "10"
|
||||
server_idle_timeout: "600" # Keep connections alive for 10 minutes
|
||||
log_connections: "1"
|
||||
log_disconnections: "1"
|
||||
log_pooler_errors: "1"
|
||||
stats_period: "60"
|
||||
|
||||
# Template for PgBouncer pods
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: pgbouncer-gitlab
|
||||
spec:
|
||||
containers:
|
||||
- name: pgbouncer
|
||||
resources:
|
||||
requests:
|
||||
cpu: 25m
|
||||
memory: 256Mi
|
||||
limits:
|
||||
memory: 512Mi
|
||||
|
||||
# Anti-affinity to spread PgBouncer pods across nodes
|
||||
affinity:
|
||||
podAntiAffinity:
|
||||
preferredDuringSchedulingIgnoredDuringExecution:
|
||||
- weight: 100
|
||||
podAffinityTerm:
|
||||
labelSelector:
|
||||
matchLabels:
|
||||
app: pgbouncer-gitlab
|
||||
topologyKey: kubernetes.io/hostname
|
||||
---
|
||||
# Read-only Pooler for Database Load Balancing
|
||||
apiVersion: postgresql.cnpg.io/v1
|
||||
kind: Pooler
|
||||
metadata:
|
||||
name: pgbouncer-gitlab-ro
|
||||
namespace: gitlab
|
||||
spec:
|
||||
cluster:
|
||||
name: pg-gitlab
|
||||
|
||||
instances: 3
|
||||
|
||||
type: ro # Read-Only pooler (connects to replicas)
|
||||
|
||||
pgbouncer:
|
||||
poolMode: transaction
|
||||
authQuerySecret:
|
||||
name: pg-gitlab-app
|
||||
# Use custom user_search function for SCRAM-SHA-256 authentication
|
||||
# This function is created by gitlab-bootstrap.sh script
|
||||
authQuery: "SELECT usename, passwd FROM public.user_search($1)"
|
||||
|
||||
parameters:
|
||||
max_client_conn: "2000"
|
||||
default_pool_size: "50"
|
||||
reserve_pool_size: "10"
|
||||
max_db_connections: "100"
|
||||
server_idle_timeout: "600" # Keep connections alive for 10 minutes
|
||||
log_connections: "1"
|
||||
log_disconnections: "1"
|
||||
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: pgbouncer-gitlab-ro
|
||||
spec:
|
||||
containers:
|
||||
- name: pgbouncer
|
||||
resources:
|
||||
requests:
|
||||
cpu: 25m
|
||||
memory: 256Mi
|
||||
limits:
|
||||
memory: 512Mi
|
||||
|
||||
affinity:
|
||||
podAntiAffinity:
|
||||
preferredDuringSchedulingIgnoredDuringExecution:
|
||||
- weight: 100
|
||||
podAffinityTerm:
|
||||
labelSelector:
|
||||
matchLabels:
|
||||
app: pgbouncer-gitlab-ro
|
||||
topologyKey: kubernetes.io/hostname
|
||||
@@ -0,0 +1,35 @@
|
||||
apiVersion: postgresql.cnpg.io/v1
|
||||
kind: Cluster
|
||||
metadata:
|
||||
name: pg-praefect
|
||||
namespace: gitlab
|
||||
spec:
|
||||
imageName: ghcr.io/cloudnative-pg/postgresql:16
|
||||
instances: 2
|
||||
|
||||
# Resources: keep memory, keep CPU requests low for scheduling.
|
||||
# CPU limits omitted so Postgres can burst when available.
|
||||
resources:
|
||||
requests:
|
||||
memory: "512Mi"
|
||||
cpu: "100m"
|
||||
limits:
|
||||
memory: "2Gi"
|
||||
|
||||
# Spread replicas across different nodes
|
||||
affinity:
|
||||
topologyKey: kubernetes.io/hostname
|
||||
|
||||
storage:
|
||||
size: 10Gi
|
||||
storageClass: rook-ceph-block
|
||||
|
||||
primaryUpdateStrategy: unsupervised
|
||||
|
||||
bootstrap:
|
||||
initdb:
|
||||
database: praefect_production
|
||||
owner: app
|
||||
|
||||
monitoring:
|
||||
enablePodMonitor: true
|
||||
@@ -0,0 +1,108 @@
|
||||
---
|
||||
# ServiceAccount for the CronJob that monitors Redis secret changes
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: redis-restart-sa
|
||||
namespace: gitlab
|
||||
---
|
||||
# Role to allow patching StatefulSets and reading Secrets
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: redis-restart-role
|
||||
namespace: gitlab
|
||||
rules:
|
||||
- apiGroups: ["apps"]
|
||||
resources: ["statefulsets"]
|
||||
verbs: ["get", "patch"]
|
||||
- apiGroups: [""]
|
||||
resources: ["secrets"]
|
||||
verbs: ["get"]
|
||||
- apiGroups: ["apps"]
|
||||
resources: ["statefulsets/status"]
|
||||
verbs: ["get"]
|
||||
---
|
||||
# RoleBinding to grant permissions to the ServiceAccount
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: redis-restart-binding
|
||||
namespace: gitlab
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: redis-restart-role
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: redis-restart-sa
|
||||
namespace: gitlab
|
||||
---
|
||||
# CronJob to monitor Redis secret and trigger StatefulSet restart on changes
|
||||
apiVersion: batch/v1
|
||||
kind: CronJob
|
||||
metadata:
|
||||
name: redis-secret-monitor
|
||||
namespace: gitlab
|
||||
spec:
|
||||
# Run every hour to check for secret changes
|
||||
# This aligns with the 24-hour secret rotation schedule
|
||||
schedule: "*/60 * * * *"
|
||||
concurrencyPolicy: Forbid
|
||||
successfulJobsHistoryLimit: 1
|
||||
failedJobsHistoryLimit: 1
|
||||
jobTemplate:
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: redis-secret-monitor
|
||||
spec:
|
||||
serviceAccountName: redis-restart-sa
|
||||
restartPolicy: OnFailure
|
||||
containers:
|
||||
- name: monitor
|
||||
image: docker.io/alpine/k8s:1.32.13
|
||||
securityContext:
|
||||
runAsUser: 10000
|
||||
runAsGroup: 10000
|
||||
runAsNonRoot: true
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
capabilities:
|
||||
drop: ["ALL"]
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
volumeMounts:
|
||||
- name: tmp
|
||||
mountPath: /tmp
|
||||
command:
|
||||
- /bin/bash
|
||||
- -c
|
||||
- |
|
||||
set -e
|
||||
|
||||
# Get current secret version
|
||||
SECRET_VERSION=$(kubectl get secret -n gitlab redis-gitlab-secret -o jsonpath='{.metadata.resourceVersion}')
|
||||
|
||||
# Get last known secret version from StatefulSet annotation
|
||||
LAST_VERSION=$(kubectl get statefulset -n gitlab redis-gitlab -o jsonpath='{.spec.template.metadata.annotations.secret-version/redis-password}' 2>/dev/null || echo "")
|
||||
|
||||
echo "Current secret version: $SECRET_VERSION"
|
||||
echo "Last known version: $LAST_VERSION"
|
||||
|
||||
# If versions differ, restart StatefulSet
|
||||
if [ "$SECRET_VERSION" != "$LAST_VERSION" ]; then
|
||||
echo "Secret has changed! Updating StatefulSet with new version annotation..."
|
||||
|
||||
# Patch StatefulSet with new secret version annotation
|
||||
# This will trigger a rolling restart of the Redis pod
|
||||
kubectl patch statefulset -n gitlab redis-gitlab -p "{\"spec\":{\"template\":{\"metadata\":{\"annotations\":{\"secret-version/redis-password\":\"$SECRET_VERSION\",\"restarted-at\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}}}}}"
|
||||
|
||||
echo "StatefulSet will now perform a rolling restart to pick up the new password."
|
||||
else
|
||||
echo "Secret has not changed. No restart needed."
|
||||
fi
|
||||
volumes:
|
||||
- name: tmp
|
||||
emptyDir: {}
|
||||
@@ -0,0 +1,95 @@
|
||||
# Redis standalone instance for GitLab (used instead of Sentinel for simplicity)
|
||||
# Password auth is required - GitLab reads the password from redis-gitlab-secret
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: redis-gitlab-additional
|
||||
namespace: gitlab
|
||||
spec:
|
||||
ports:
|
||||
- port: 6379
|
||||
targetPort: 6379
|
||||
name: redis
|
||||
selector:
|
||||
app: redis-gitlab-standalone
|
||||
type: ClusterIP
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: redis-gitlab-standalone
|
||||
namespace: gitlab
|
||||
spec:
|
||||
ports:
|
||||
- port: 6379
|
||||
targetPort: 6379
|
||||
name: redis
|
||||
selector:
|
||||
app: redis-gitlab-standalone
|
||||
type: ClusterIP
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: redis-gitlab-standalone
|
||||
namespace: gitlab
|
||||
spec:
|
||||
serviceName: redis-gitlab-additional
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: redis-gitlab-standalone
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: redis-gitlab-standalone
|
||||
spec:
|
||||
securityContext:
|
||||
fsGroup: 1000
|
||||
containers:
|
||||
- name: redis
|
||||
image: redis:7.0-alpine
|
||||
securityContext:
|
||||
runAsUser: 999
|
||||
runAsGroup: 1000
|
||||
runAsNonRoot: true
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop: ["ALL"]
|
||||
ports:
|
||||
- containerPort: 6379
|
||||
name: redis
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- redis-server --appendonly yes --requirepass "$REDIS_PASSWORD"
|
||||
env:
|
||||
- name: REDIS_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: redis-gitlab-secret
|
||||
key: password
|
||||
resources:
|
||||
requests:
|
||||
cpu: 25m
|
||||
memory: 256Mi
|
||||
limits:
|
||||
memory: 512Mi
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /data
|
||||
- name: tmp
|
||||
mountPath: /tmp
|
||||
volumes:
|
||||
- name: tmp
|
||||
emptyDir: {}
|
||||
volumeClaimTemplates:
|
||||
- metadata:
|
||||
name: data
|
||||
spec:
|
||||
accessModes: ["ReadWriteOnce"]
|
||||
storageClassName: rook-ceph-block
|
||||
resources:
|
||||
requests:
|
||||
storage: 5Gi
|
||||
@@ -0,0 +1,33 @@
|
||||
---
|
||||
# Password generator and ESO for GitLab Redis
|
||||
apiVersion: generators.external-secrets.io/v1alpha1
|
||||
kind: Password
|
||||
metadata:
|
||||
name: gitlab-redis-password
|
||||
namespace: gitlab
|
||||
spec:
|
||||
length: 32
|
||||
digits: 5
|
||||
symbols: 0
|
||||
noUpper: false
|
||||
allowRepeat: true
|
||||
---
|
||||
apiVersion: external-secrets.io/v1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: gitlab-redis-password
|
||||
namespace: gitlab
|
||||
spec:
|
||||
refreshInterval: "0" # Generate once, never rotate (Password generator creates new value each refresh)
|
||||
target:
|
||||
name: redis-gitlab-secret
|
||||
creationPolicy: Owner
|
||||
template:
|
||||
data:
|
||||
password: "{{ .password }}"
|
||||
dataFrom:
|
||||
- sourceRef:
|
||||
generatorRef:
|
||||
apiVersion: generators.external-secrets.io/v1alpha1
|
||||
kind: Password
|
||||
name: gitlab-redis-password
|
||||
@@ -0,0 +1,114 @@
|
||||
---
|
||||
# Job to sync GitLab admin status from Authentik groups
|
||||
# Run this after users login via Authentik SSO to grant them admin access
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: gitlab-sync-admin
|
||||
namespace: gitlab
|
||||
spec:
|
||||
ttlSecondsAfterFinished: 3600 # Clean up after 1 hour
|
||||
template:
|
||||
spec:
|
||||
restartPolicy: OnFailure
|
||||
containers:
|
||||
- name: sync-admin
|
||||
image: docker.io/library/alpine:3.21
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop: ["ALL"]
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- |
|
||||
set -e
|
||||
|
||||
apk add --no-cache postgresql-client curl
|
||||
|
||||
echo "🔄 Syncing GitLab admin permissions from Authentik..."
|
||||
|
||||
# Get list of users in "authentik Admins" group
|
||||
ADMIN_USERS=$(PGPASSWORD="$AUTHENTIK_DB_PASSWORD" psql -h pg-authentik-rw.authentik.svc.cluster.local -U app -d app -t -c "
|
||||
SELECT DISTINCT u.email
|
||||
FROM authentik_core_user u
|
||||
JOIN authentik_core_user_groups ug ON u.id = ug.user_id
|
||||
JOIN authentik_core_group g ON ug.group_id = g.group_uuid
|
||||
WHERE g.name = 'authentik Admins' AND u.is_active = true;
|
||||
" | xargs)
|
||||
|
||||
if [ -z "$ADMIN_USERS" ]; then
|
||||
echo "⚠️ No users found in 'authentik Admins' group"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "✓ Found admin users: $ADMIN_USERS"
|
||||
echo ""
|
||||
|
||||
# For each admin user, grant admin access in GitLab
|
||||
for email in $ADMIN_USERS; do
|
||||
echo "🔐 Checking user: $email"
|
||||
|
||||
# Use GitLab Rails runner to promote user
|
||||
kubectl exec -n gitlab deployment/gitlab-toolbox -- \
|
||||
gitlab-rails runner "
|
||||
user = User.find_by(email: '$email')
|
||||
if user
|
||||
if user.admin?
|
||||
puts ' ✓ Already admin'
|
||||
else
|
||||
user.update(admin: true)
|
||||
puts ' ✅ Promoted to admin'
|
||||
end
|
||||
else
|
||||
puts ' ⚠️ User not found (needs to login via SSO first)'
|
||||
end
|
||||
" || echo " ❌ Failed to update user"
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "✅ Admin sync complete"
|
||||
env:
|
||||
- name: AUTHENTIK_DB_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: pg-authentik-app
|
||||
namespace: authentik
|
||||
key: password
|
||||
serviceAccountName: gitlab-sync-admin
|
||||
---
|
||||
# ServiceAccount for the sync job
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: gitlab-sync-admin
|
||||
namespace: gitlab
|
||||
---
|
||||
# Role to allow exec into toolbox pod
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: gitlab-sync-admin
|
||||
namespace: gitlab
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["pods", "pods/exec"]
|
||||
verbs: ["get", "list", "create"]
|
||||
- apiGroups: ["apps"]
|
||||
resources: ["deployments"]
|
||||
verbs: ["get", "list"]
|
||||
---
|
||||
# RoleBinding
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: gitlab-sync-admin
|
||||
namespace: gitlab
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: gitlab-sync-admin
|
||||
namespace: gitlab
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: gitlab-sync-admin
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
@@ -0,0 +1,333 @@
|
||||
# values/gitlab.values.yaml
|
||||
# GitLab with Authentik OIDC SSO Integration
|
||||
|
||||
global:
|
||||
hosts:
|
||||
domain: kube.huskypup.net
|
||||
gitlab:
|
||||
name: gitlab.kube.huskypup.net
|
||||
registry:
|
||||
name: registry.gitlab.kube.huskypup.net
|
||||
tls:
|
||||
secretName: gitlab-tls
|
||||
minio:
|
||||
name: minio.gitlab.kube.huskypup.net
|
||||
tls:
|
||||
secretName: gitlab-tls
|
||||
kas:
|
||||
name: kas.kube.huskypup.net
|
||||
tls:
|
||||
secretName: gitlab-tls
|
||||
|
||||
# Ingress disabled - Istio VirtualServices handle routing
|
||||
ingress:
|
||||
enabled: false
|
||||
configureCertmanager: false # Use cluster-wide cert-manager, not GitLab's
|
||||
|
||||
# Edition: Community Edition
|
||||
edition: ce
|
||||
|
||||
# Time zone
|
||||
time_zone: UTC
|
||||
|
||||
# Email configuration (configure as needed)
|
||||
email:
|
||||
from: 'gitlab@kube.huskypup.net'
|
||||
display_name: GitLab
|
||||
reply_to: 'noreply@kube.huskypup.net'
|
||||
|
||||
# External PostgreSQL configuration
|
||||
# NOTE: PgBouncer service currently has no endpoints (replicas=0),
|
||||
# so GitLab is pointed directly at the CNPG primary service.
|
||||
psql:
|
||||
host: pg-gitlab-rw.gitlab.svc.cluster.local
|
||||
port: 5432
|
||||
database: gitlabhq_production
|
||||
username: app
|
||||
password:
|
||||
secret: pg-gitlab-app
|
||||
key: password
|
||||
|
||||
# Gitaly configuration - using Praefect for HA
|
||||
gitaly:
|
||||
enabled: true # Enabled to deploy Gitaly pods
|
||||
internal:
|
||||
names: [] # No internal Gitaly, using Praefect
|
||||
external: [] # Praefect configured below
|
||||
|
||||
# Praefect configuration
|
||||
praefect:
|
||||
enabled: true
|
||||
|
||||
# Use CNPG database secret
|
||||
dbSecret:
|
||||
secret: pg-praefect-app
|
||||
key: password
|
||||
|
||||
virtualStorages:
|
||||
- name: default
|
||||
gitalyReplicas: 3 # Production HA
|
||||
maxUnavailable: 1
|
||||
|
||||
# Praefect PostgreSQL configuration
|
||||
psql:
|
||||
host: pg-praefect-rw.gitlab.svc.cluster.local
|
||||
port: 5432
|
||||
dbName: praefect_production
|
||||
user: app
|
||||
|
||||
# External Redis configuration - using standalone Redis for writes
|
||||
# (replicated Redis service causes READONLY errors from replicas)
|
||||
redis:
|
||||
host: redis-gitlab-standalone.gitlab.svc.cluster.local
|
||||
port: 6379
|
||||
auth:
|
||||
enabled: true
|
||||
secret: redis-gitlab-secret
|
||||
key: password
|
||||
|
||||
# Application Configuration
|
||||
appConfig:
|
||||
# OmniAuth SSO Configuration
|
||||
omniauth:
|
||||
enabled: true
|
||||
allowSingleSignOn: ['openid_connect']
|
||||
blockAutoCreatedUsers: false
|
||||
autoLinkUser: ['openid_connect']
|
||||
syncProfileFromProvider: ['openid_connect']
|
||||
syncProfileAttributes: ['email', 'name']
|
||||
providers:
|
||||
- secret: gitlab-oidc-secret
|
||||
key: provider
|
||||
|
||||
# Settings for Let's Encrypt ACME Issuer - disabled, using cluster-wide cert-manager
|
||||
certmanager-issuer:
|
||||
email: admin@kube.huskypup.net
|
||||
|
||||
# Authentik OIDC Configuration via Rails omnibus config
|
||||
# Note: Credentials are loaded from gitlab-oidc-secret via environment variables
|
||||
|
||||
# SAML Configuration via Rails omnibus config
|
||||
# Note: SAML provider configuration is loaded from gitlab-saml-secret
|
||||
|
||||
# PostgreSQL (external via CloudNativePG)
|
||||
postgresql:
|
||||
install: false
|
||||
|
||||
# Redis (external)
|
||||
redis:
|
||||
install: false
|
||||
|
||||
# PgBouncer connection pooler
|
||||
# NOTE: PgBouncer is deployed via CNPG Pooler CRD (see pgbouncer-pooler.yaml)
|
||||
# This setting disables GitLab's bundled PgBouncer chart
|
||||
pgbouncer:
|
||||
enabled: false # Using external CNPG Pooler instead
|
||||
|
||||
# MinIO for object storage
|
||||
minio:
|
||||
persistence:
|
||||
storageClass: rook-ceph-block
|
||||
size: 100Gi # Production storage
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 512Mi
|
||||
limits:
|
||||
memory: 2Gi
|
||||
|
||||
# Container Registry - enabled with S3 storage
|
||||
registry:
|
||||
enabled: true
|
||||
hpa:
|
||||
minReplicas: 1
|
||||
maxReplicas: 2
|
||||
resources:
|
||||
requests:
|
||||
cpu: 25m
|
||||
memory: 128Mi
|
||||
limits:
|
||||
memory: 1Gi
|
||||
|
||||
# GitLab components
|
||||
gitlab:
|
||||
# GitLab Webservice - Main application
|
||||
webservice:
|
||||
minReplicas: 1 # Homelab sizing
|
||||
maxReplicas: 3
|
||||
|
||||
# Note: hostAliases for OIDC SSL validation are applied via helmfile postsync hook
|
||||
# (GitLab chart doesn't support hostAliases in values.yaml)
|
||||
|
||||
extraEnvFrom:
|
||||
GITLAB_OIDC_CLIENT_ID:
|
||||
secretKeyRef:
|
||||
name: gitlab-oidc-secret
|
||||
key: GITLAB_OIDC_CLIENT_ID
|
||||
GITLAB_OIDC_CLIENT_SECRET:
|
||||
secretKeyRef:
|
||||
name: gitlab-oidc-secret
|
||||
key: GITLAB_OIDC_CLIENT_SECRET
|
||||
GITLAB_SAML_IDP_FINGERPRINT:
|
||||
secretKeyRef:
|
||||
name: gitlab-saml-secret
|
||||
key: GITLAB_SAML_IDP_FINGERPRINT
|
||||
GITLAB_SAML_IDP_SSO_URL:
|
||||
secretKeyRef:
|
||||
name: gitlab-saml-secret
|
||||
key: GITLAB_SAML_IDP_SSO_URL
|
||||
extraEnv:
|
||||
GITLAB_OMNIBUS_CONFIG: |
|
||||
# Authentik OIDC Configuration
|
||||
gitlab_rails['omniauth_enabled'] = true
|
||||
gitlab_rails['omniauth_allow_single_sign_on'] = ['openid_connect', 'saml']
|
||||
gitlab_rails['omniauth_block_auto_created_users'] = false
|
||||
gitlab_rails['omniauth_auto_link_user'] = ['openid_connect', 'saml']
|
||||
gitlab_rails['omniauth_auto_sign_in_with_provider'] = nil
|
||||
gitlab_rails['omniauth_sync_profile_from_provider'] = ['openid_connect', 'saml']
|
||||
gitlab_rails['omniauth_sync_profile_attributes'] = ['email', 'name']
|
||||
|
||||
gitlab_rails['omniauth_providers'] = [
|
||||
{
|
||||
'name' => 'openid_connect',
|
||||
'label' => 'Authentik',
|
||||
'args' => {
|
||||
'name' => 'openid_connect',
|
||||
'scope' => ['openid', 'profile', 'email'],
|
||||
'response_type' => 'code',
|
||||
'issuer' => 'https://auth.kube.huskypup.net/application/o/gitlab/',
|
||||
'discovery' => true,
|
||||
'client_auth_method' => 'query',
|
||||
'uid_field' => 'sub',
|
||||
'send_scope_to_token_endpoint' => true,
|
||||
'pkce' => true,
|
||||
'client_options' => {
|
||||
'identifier' => ENV['GITLAB_OIDC_CLIENT_ID'],
|
||||
'secret' => ENV['GITLAB_OIDC_CLIENT_SECRET'],
|
||||
'redirect_uri' => 'https://gitlab.kube.huskypup.net/users/auth/openid_connect/callback'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
'name' => 'saml',
|
||||
'label' => 'Authentik SAML',
|
||||
'args' => {
|
||||
'assertion_consumer_service_url' => 'https://gitlab.kube.huskypup.net/users/auth/saml/callback',
|
||||
'idp_cert_fingerprint' => ENV['GITLAB_SAML_IDP_FINGERPRINT'],
|
||||
'idp_sso_target_url' => ENV['GITLAB_SAML_IDP_SSO_URL'],
|
||||
'issuer' => 'https://gitlab.kube.huskypup.net',
|
||||
'name_identifier_format' => 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent',
|
||||
'attribute_statements' => {
|
||||
'email' => ['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress'],
|
||||
'name' => ['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name'],
|
||||
'first_name' => ['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname'],
|
||||
'last_name' => ['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname']
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
# Homelab resource allocation
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 512Mi
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 2Gi
|
||||
|
||||
# GitLab KAS (Kubernetes Agent Server) - enabled for Kubernetes cluster integration
|
||||
kas:
|
||||
enabled: true
|
||||
minReplicas: 1
|
||||
maxReplicas: 1
|
||||
resources:
|
||||
requests:
|
||||
cpu: 25m
|
||||
memory: 256Mi
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 1Gi
|
||||
|
||||
# Gitaly Cluster (Praefect) - High Availability Git storage
|
||||
gitaly:
|
||||
# Note: enabled is in global.gitaly
|
||||
persistence:
|
||||
storageClass: rook-ceph-block
|
||||
size: 200Gi # Production storage
|
||||
# Production resources
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 512Mi
|
||||
limits:
|
||||
cpu: 250m
|
||||
memory: 2Gi
|
||||
|
||||
# GitLab Runner - enabled for CI/CD
|
||||
gitlab-runner:
|
||||
install: false # Will be installed separately
|
||||
|
||||
# Praefect - Gitaly Cluster routing and transaction manager
|
||||
praefect:
|
||||
enabled: true
|
||||
minReplicas: 1 # Homelab sizing
|
||||
maxReplicas: 1
|
||||
|
||||
# Note: Praefect PostgreSQL config is in global.praefect.psql
|
||||
|
||||
# Use CNPG-generated database secret
|
||||
dbSecret:
|
||||
secret: pg-praefect-app
|
||||
key: password
|
||||
|
||||
# Resources
|
||||
resources:
|
||||
requests:
|
||||
cpu: 25m
|
||||
memory: 256Mi
|
||||
limits:
|
||||
cpu: 250m
|
||||
memory: 512Mi
|
||||
|
||||
# Virtual storage configuration
|
||||
virtualStorages:
|
||||
- name: default
|
||||
gitalyReplicas: 1 # Homelab sizing
|
||||
maxUnavailable: 1
|
||||
|
||||
# GitLab Exporter for Prometheus metrics
|
||||
gitlab-exporter:
|
||||
enabled: true
|
||||
metrics:
|
||||
enabled: true
|
||||
|
||||
# Sidekiq background jobs
|
||||
sidekiq:
|
||||
minReplicas: 1 # Homelab sizing
|
||||
maxReplicas: 1
|
||||
|
||||
# Note: hostAliases applied via helmfile postsync hook
|
||||
|
||||
# Homelab resources
|
||||
resources:
|
||||
requests:
|
||||
cpu: 25m
|
||||
memory: 512Mi
|
||||
limits:
|
||||
cpu: 250m
|
||||
memory: 2Gi
|
||||
|
||||
# Disable components we already have in the cluster
|
||||
certmanager:
|
||||
install: false # Using cluster-wide cert-manager
|
||||
installCRDs: false
|
||||
|
||||
prometheus:
|
||||
install: false # Using existing Prometheus
|
||||
|
||||
nginx-ingress:
|
||||
enabled: false # Istio handles ingress
|
||||
|
||||
# Disable GitLab Runner (configure separately if needed)
|
||||
gitlab-runner:
|
||||
install: false
|
||||
@@ -0,0 +1,50 @@
|
||||
apiVersion: postgresql.cnpg.io/v1
|
||||
kind: Cluster
|
||||
metadata:
|
||||
name: pg-guacamole
|
||||
namespace: guacamole
|
||||
spec:
|
||||
imageName: ghcr.io/cloudnative-pg/postgresql:16
|
||||
instances: 2
|
||||
|
||||
# Resource limits to prevent OOM
|
||||
resources:
|
||||
requests:
|
||||
memory: "256Mi"
|
||||
cpu: "50m"
|
||||
limits:
|
||||
memory: "1Gi"
|
||||
|
||||
# Spread replicas across different nodes
|
||||
affinity:
|
||||
topologyKey: kubernetes.io/hostname
|
||||
|
||||
storage:
|
||||
size: 10Gi
|
||||
storageClass: rook-ceph-block
|
||||
|
||||
primaryUpdateStrategy: unsupervised
|
||||
|
||||
# PostgreSQL configuration for Guacamole
|
||||
postgresql:
|
||||
parameters:
|
||||
max_connections: "100"
|
||||
shared_buffers: "256MB"
|
||||
effective_cache_size: "768MB"
|
||||
maintenance_work_mem: "64MB"
|
||||
checkpoint_completion_target: "0.9"
|
||||
wal_buffers: "8MB"
|
||||
default_statistics_target: "100"
|
||||
random_page_cost: "1.1"
|
||||
effective_io_concurrency: "200"
|
||||
work_mem: "1310kB"
|
||||
min_wal_size: "1GB"
|
||||
max_wal_size: "4GB"
|
||||
|
||||
bootstrap:
|
||||
initdb:
|
||||
database: guacamole
|
||||
owner: guacamole
|
||||
|
||||
monitoring:
|
||||
enablePodMonitor: true
|
||||
@@ -0,0 +1,114 @@
|
||||
# Job to initialize Guacamole database schema
|
||||
# This should run once after the PostgreSQL cluster is ready
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: guacamole-init-schema
|
||||
namespace: guacamole
|
||||
spec:
|
||||
ttlSecondsAfterFinished: 300
|
||||
template:
|
||||
spec:
|
||||
restartPolicy: OnFailure
|
||||
initContainers:
|
||||
- name: wait-for-postgres
|
||||
image: postgres:16-alpine
|
||||
securityContext:
|
||||
runAsUser: 10000
|
||||
runAsGroup: 10000
|
||||
runAsNonRoot: true
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
capabilities:
|
||||
drop: ["ALL"]
|
||||
volumeMounts:
|
||||
- name: tmp
|
||||
mountPath: /tmp
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- |
|
||||
echo "Waiting for PostgreSQL to be ready..."
|
||||
until pg_isready -h pg-guacamole-rw -p 5432 -U guacamole; do
|
||||
echo "PostgreSQL not ready, waiting..."
|
||||
sleep 5
|
||||
done
|
||||
echo "PostgreSQL is ready!"
|
||||
env:
|
||||
- name: PGPASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: pg-guacamole-app
|
||||
key: password
|
||||
- name: generate-initdb
|
||||
image: guacamole/guacamole:1.6.0
|
||||
securityContext:
|
||||
runAsUser: 1000
|
||||
runAsGroup: 1000
|
||||
runAsNonRoot: true
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
capabilities:
|
||||
drop: ["ALL"]
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- |
|
||||
set -e
|
||||
/opt/guacamole/bin/initdb.sh --postgresql > /initdb/initdb.sql
|
||||
test -s /initdb/initdb.sql
|
||||
volumeMounts:
|
||||
- name: initdb
|
||||
mountPath: /initdb
|
||||
containers:
|
||||
- name: init-schema
|
||||
image: postgres:16-alpine
|
||||
securityContext:
|
||||
runAsUser: 10000
|
||||
runAsGroup: 10000
|
||||
runAsNonRoot: true
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
capabilities:
|
||||
drop: ["ALL"]
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- |
|
||||
set -e
|
||||
echo "Checking if schema already exists..."
|
||||
TABLES=$(PGPASSWORD="$PGPASSWORD" psql -h pg-guacamole-rw -U guacamole -d guacamole -tA -c "SELECT COUNT(*) FROM information_schema.tables WHERE table_name = 'guacamole_user';" 2>/dev/null | tr -d '[:space:]' || printf '0')
|
||||
TABLES=${TABLES:-0}
|
||||
|
||||
if [ "$TABLES" -gt "0" ]; then
|
||||
echo "Schema already exists."
|
||||
else
|
||||
echo "Initializing Guacamole database schema..."
|
||||
PGPASSWORD="$PGPASSWORD" psql -v ON_ERROR_STOP=1 -h pg-guacamole-rw -U guacamole -d guacamole -f /initdb/initdb.sql
|
||||
echo "Schema initialization complete!"
|
||||
fi
|
||||
|
||||
echo "Setting up Authentik Admins group permissions..."
|
||||
PGPASSWORD="$PGPASSWORD" psql -v ON_ERROR_STOP=1 -h pg-guacamole-rw -U guacamole -d guacamole -f /schema/permissions.sql
|
||||
echo "Authentik Admins group permissions configured!"
|
||||
env:
|
||||
- name: PGPASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: pg-guacamole-app
|
||||
key: password
|
||||
volumeMounts:
|
||||
- name: initdb
|
||||
mountPath: /initdb
|
||||
- name: schema
|
||||
mountPath: /schema
|
||||
- name: tmp
|
||||
mountPath: /tmp
|
||||
volumes:
|
||||
- name: tmp
|
||||
emptyDir: {}
|
||||
- name: initdb
|
||||
emptyDir: {}
|
||||
- name: schema
|
||||
configMap:
|
||||
name: guacamole-schema
|
||||
@@ -0,0 +1,256 @@
|
||||
# Guacamole complete deployment with OpenID/Authentik support
|
||||
# This deployment includes:
|
||||
# - Environment-based OpenID configuration
|
||||
# - PostgreSQL backend for connections/users
|
||||
# - Authentik uses Let's Encrypt (no custom cert import needed)
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: guacamole
|
||||
namespace: guacamole
|
||||
labels:
|
||||
app: guacamole
|
||||
component: client
|
||||
spec:
|
||||
replicas: 1
|
||||
strategy:
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
maxSurge: 0
|
||||
maxUnavailable: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: guacamole
|
||||
component: client
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: guacamole
|
||||
component: client
|
||||
spec:
|
||||
automountServiceAccountToken: false
|
||||
containers:
|
||||
- name: guacamole
|
||||
image: guacamole/guacamole:1.6.0
|
||||
securityContext:
|
||||
runAsUser: 10000
|
||||
runAsGroup: 10000
|
||||
runAsNonRoot: true
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
volumeMounts:
|
||||
- name: tmp
|
||||
mountPath: /tmp
|
||||
- name: tomcat-work
|
||||
mountPath: /usr/local/tomcat/work
|
||||
- name: tomcat-logs
|
||||
mountPath: /usr/local/tomcat/logs
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8080
|
||||
protocol: TCP
|
||||
env:
|
||||
- name: GUACD_HOSTNAME
|
||||
value: "guacd"
|
||||
- name: GUACD_PORT
|
||||
value: "4822"
|
||||
- name: POSTGRESQL_HOSTNAME
|
||||
value: "pg-guacamole-rw"
|
||||
- name: POSTGRESQL_PORT
|
||||
value: "5432"
|
||||
- name: POSTGRESQL_DATABASE
|
||||
value: "guacamole"
|
||||
- name: POSTGRESQL_ENABLED
|
||||
value: "true"
|
||||
- name: POSTGRESQL_USERNAME
|
||||
value: "guacamole"
|
||||
- name: POSTGRESQL_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: pg-guacamole-app
|
||||
key: password
|
||||
- name: POSTGRESQL_AUTO_CREATE_ACCOUNTS
|
||||
value: "true"
|
||||
- name: ENABLE_ENVIRONMENT_PROPERTIES
|
||||
value: "true"
|
||||
- name: LOG_LEVEL
|
||||
value: "debug"
|
||||
- name: WEBAPP_CONTEXT
|
||||
value: "ROOT"
|
||||
# FIX: Enabled WebSockets to stop the 10-second tunnel timeout
|
||||
- name: ENABLE_WEBSOCKET
|
||||
value: "true"
|
||||
- name: EXTENSION_PRIORITY
|
||||
value: "*,openid"
|
||||
- name: OPENID_RESPONSE_TYPE
|
||||
value: "code"
|
||||
- name: OPENID_AUTHORIZATION_ENDPOINT
|
||||
value: "https://auth.kube.huskypup.net/application/o/authorize/"
|
||||
- name: OPENID_JWKS_ENDPOINT
|
||||
value: "https://auth.kube.huskypup.net/application/o/guacamole/jwks/"
|
||||
- name: OPENID_ISSUER
|
||||
value: "https://auth.kube.huskypup.net/application/o/guacamole/"
|
||||
- name: OPENID_TOKEN_ENDPOINT
|
||||
value: "https://auth.kube.huskypup.net/application/o/token/"
|
||||
- name: OPENID_REDIRECT_URI
|
||||
value: "https://guacamole.kube.huskypup.net/"
|
||||
- name: OPENID_USERNAME_CLAIM_TYPE
|
||||
value: "preferred_username"
|
||||
- name: OPENID_GROUPS_CLAIM_TYPE
|
||||
value: "groups"
|
||||
- name: OPENID_SCOPE
|
||||
value: "openid email profile groups"
|
||||
- name: OPENID_ALLOWED_CLOCK_SKEW
|
||||
value: "30"
|
||||
- name: OPENID_MAX_TOKEN_VALIDITY
|
||||
value: "300"
|
||||
- name: OPENID_MAX_NONCE_VALIDITY
|
||||
value: "60"
|
||||
- name: OPENID_CLIENT_ID
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: guacamole-oauth-secret
|
||||
key: client-id
|
||||
- name: OPENID_CLIENT_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: guacamole-oauth-secret
|
||||
key: client-secret
|
||||
resources:
|
||||
requests:
|
||||
memory: "512Mi"
|
||||
cpu: "250m"
|
||||
limits:
|
||||
memory: "1Gi"
|
||||
cpu: "1000m"
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /api/languages
|
||||
port: http
|
||||
initialDelaySeconds: 60
|
||||
periodSeconds: 30
|
||||
# FIX: Relaxed readiness probe so minor DB lags don't kill the Endpoint
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /api/languages
|
||||
port: http
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 15
|
||||
failureThreshold: 5
|
||||
volumes:
|
||||
- name: tmp
|
||||
emptyDir: {}
|
||||
- name: tomcat-work
|
||||
emptyDir: {}
|
||||
- name: tomcat-logs
|
||||
emptyDir: {}
|
||||
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: guacd
|
||||
namespace: guacamole
|
||||
labels:
|
||||
app: guacamole
|
||||
component: guacd
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: guacamole
|
||||
component: guacd
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: guacamole
|
||||
component: guacd
|
||||
spec:
|
||||
automountServiceAccountToken: false
|
||||
containers:
|
||||
- name: guacd
|
||||
image: guacamole/guacd:1.6.0
|
||||
securityContext:
|
||||
runAsUser: 10000
|
||||
runAsGroup: 10000
|
||||
runAsNonRoot: true
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
volumeMounts:
|
||||
- name: tmp
|
||||
mountPath: /tmp
|
||||
- name: home
|
||||
mountPath: /home
|
||||
ports:
|
||||
- name: guacd
|
||||
containerPort: 4822
|
||||
protocol: TCP
|
||||
env:
|
||||
- name: LOG_LEVEL
|
||||
value: "debug"
|
||||
resources:
|
||||
requests:
|
||||
memory: "256Mi"
|
||||
cpu: "10m"
|
||||
limits:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
readinessProbe:
|
||||
tcpSocket:
|
||||
port: 4822
|
||||
initialDelaySeconds: 2
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 1
|
||||
successThreshold: 1
|
||||
failureThreshold: 3
|
||||
volumes:
|
||||
- name: tmp
|
||||
emptyDir: {}
|
||||
- name: home
|
||||
emptyDir: {}
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: guacamole
|
||||
namespace: guacamole
|
||||
labels:
|
||||
app: guacamole
|
||||
component: client
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- port: 8080
|
||||
targetPort: http
|
||||
protocol: TCP
|
||||
name: http
|
||||
selector:
|
||||
app: guacamole
|
||||
component: client
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: guacd
|
||||
namespace: guacamole
|
||||
labels:
|
||||
app: guacamole
|
||||
component: guacd
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- port: 4822
|
||||
targetPort: guacd
|
||||
protocol: TCP
|
||||
name: guacd
|
||||
selector:
|
||||
app: guacamole
|
||||
component: guacd
|
||||
@@ -0,0 +1,67 @@
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: Certificate
|
||||
metadata:
|
||||
name: guacamole-envoy-tls
|
||||
namespace: guacamole
|
||||
spec:
|
||||
secretName: guacamole-envoy-tls
|
||||
issuerRef:
|
||||
kind: ClusterIssuer
|
||||
name: letsencrypt-production
|
||||
dnsNames:
|
||||
- guacamole.kube.huskypup.net
|
||||
|
||||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: Gateway
|
||||
metadata:
|
||||
name: guacamole
|
||||
namespace: guacamole
|
||||
spec:
|
||||
gatewayClassName: envoy-gateway
|
||||
listeners:
|
||||
- name: https
|
||||
hostname: guacamole.kube.huskypup.net
|
||||
port: 443
|
||||
protocol: HTTPS
|
||||
tls:
|
||||
mode: Terminate
|
||||
certificateRefs:
|
||||
- kind: Secret
|
||||
name: guacamole-envoy-tls
|
||||
|
||||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: HTTPRoute
|
||||
metadata:
|
||||
name: guacamole
|
||||
namespace: guacamole
|
||||
spec:
|
||||
parentRefs:
|
||||
- name: guacamole
|
||||
sectionName: https
|
||||
hostnames:
|
||||
- guacamole.kube.huskypup.net
|
||||
rules:
|
||||
# Legacy path support: https://host/guacamole/* -> https://host/*
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /guacamole
|
||||
filters:
|
||||
- type: URLRewrite
|
||||
urlRewrite:
|
||||
path:
|
||||
type: ReplacePrefixMatch
|
||||
replacePrefixMatch: /
|
||||
backendRefs:
|
||||
- name: guacamole
|
||||
port: 8080
|
||||
# Standard root routing
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: guacamole
|
||||
port: 8080
|
||||
@@ -0,0 +1,33 @@
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: HTTPRoute
|
||||
metadata:
|
||||
name: guacamole
|
||||
namespace: guacamole
|
||||
spec:
|
||||
parentRefs:
|
||||
- name: edge
|
||||
namespace: gateway
|
||||
sectionName: https
|
||||
hostnames:
|
||||
- guacamole.kube.huskypup.net
|
||||
rules:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /guacamole
|
||||
filters:
|
||||
- type: URLRewrite
|
||||
urlRewrite:
|
||||
path:
|
||||
type: ReplacePrefixMatch
|
||||
replacePrefixMatch: /
|
||||
backendRefs:
|
||||
- name: guacamole
|
||||
port: 8080
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: guacamole
|
||||
port: 8080
|
||||
@@ -0,0 +1,29 @@
|
||||
# kubernetes/apps/guacamole/external-secret.yaml
|
||||
# ExternalSecrets for Guacamole - pulls credentials from Vault
|
||||
|
||||
# Note: Database password is managed by CNPG cluster (pg-guacamole-app secret)
|
||||
# We reference it directly in the guacamole values.yaml
|
||||
|
||||
---
|
||||
apiVersion: external-secrets.io/v1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: guacamole-oauth
|
||||
namespace: guacamole
|
||||
spec:
|
||||
refreshInterval: 1h
|
||||
secretStoreRef:
|
||||
kind: ClusterSecretStore
|
||||
name: vault-backend
|
||||
target:
|
||||
name: guacamole-oauth-secret
|
||||
creationPolicy: Owner
|
||||
data:
|
||||
- secretKey: client-id
|
||||
remoteRef:
|
||||
key: guacamole-oauth
|
||||
property: client-id
|
||||
- secretKey: client-secret
|
||||
remoteRef:
|
||||
key: guacamole-oauth
|
||||
property: client-secret
|
||||
@@ -0,0 +1,16 @@
|
||||
apiVersion: networking.istio.io/v1beta1
|
||||
kind: VirtualService
|
||||
metadata:
|
||||
name: guacamole
|
||||
namespace: guacamole
|
||||
spec:
|
||||
hosts:
|
||||
- guacamole.kube.huskypup.net
|
||||
gateways:
|
||||
- istio-system/edge
|
||||
http:
|
||||
- route:
|
||||
- destination:
|
||||
host: guacamole.guacamole.svc.cluster.local
|
||||
port:
|
||||
number: 8080
|
||||
@@ -0,0 +1,20 @@
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: HTTPRoute
|
||||
metadata:
|
||||
name: esphome
|
||||
namespace: home-assistant
|
||||
spec:
|
||||
parentRefs:
|
||||
- name: edge
|
||||
namespace: gateway
|
||||
sectionName: https
|
||||
hostnames:
|
||||
- esphome.kube.huskypup.net
|
||||
rules:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: esphome
|
||||
port: 6052
|
||||
@@ -0,0 +1,16 @@
|
||||
apiVersion: networking.istio.io/v1beta1
|
||||
kind: VirtualService
|
||||
metadata:
|
||||
name: esphome
|
||||
namespace: home-assistant
|
||||
spec:
|
||||
hosts:
|
||||
- esphome.kube.huskypup.net
|
||||
gateways:
|
||||
- istio-system/edge
|
||||
http:
|
||||
- route:
|
||||
- destination:
|
||||
host: esphome.home-assistant.svc.cluster.local
|
||||
port:
|
||||
number: 6052
|
||||
@@ -0,0 +1,57 @@
|
||||
# ESPHome Helm Chart Values
|
||||
# Deployed in home-assistant namespace as a companion to Home Assistant
|
||||
#
|
||||
# FEATURES:
|
||||
# ✓ ESPHome Dashboard for managing ESP devices
|
||||
# ✓ Persistent storage via Rook-Ceph
|
||||
# ✓ TLS certificates via cert-manager
|
||||
# ✓ Ingress via Istio VirtualService
|
||||
|
||||
image:
|
||||
repository: ghcr.io/esphome/esphome
|
||||
tag: "2024.11.3"
|
||||
pullPolicy: IfNotPresent
|
||||
|
||||
service:
|
||||
main:
|
||||
ports:
|
||||
http:
|
||||
port: 6052
|
||||
|
||||
# Ingress disabled - Istio VirtualService handles routing
|
||||
ingress:
|
||||
main:
|
||||
enabled: false
|
||||
|
||||
persistence:
|
||||
config:
|
||||
enabled: true
|
||||
storageClass: rook-ceph-block
|
||||
accessMode: ReadWriteOnce
|
||||
size: 5Gi
|
||||
retain: true
|
||||
|
||||
env:
|
||||
TZ: America/New_York
|
||||
ESPHOME_DASHBOARD_USE_PING: "true"
|
||||
|
||||
securityContext:
|
||||
privileged: false
|
||||
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 256Mi
|
||||
limits:
|
||||
memory: 512Mi
|
||||
|
||||
probes:
|
||||
liveness:
|
||||
enabled: true
|
||||
readiness:
|
||||
enabled: true
|
||||
startup:
|
||||
enabled: true
|
||||
|
||||
nodeSelector:
|
||||
kubernetes.io/arch: amd64
|
||||
@@ -0,0 +1,20 @@
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: HTTPRoute
|
||||
metadata:
|
||||
name: home-assistant
|
||||
namespace: home-assistant
|
||||
spec:
|
||||
parentRefs:
|
||||
- name: edge
|
||||
namespace: gateway
|
||||
sectionName: https
|
||||
hostnames:
|
||||
- homeassistant.kube.huskypup.net
|
||||
rules:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: home-assistant
|
||||
port: 8123
|
||||
@@ -0,0 +1,31 @@
|
||||
---
|
||||
# Home Assistant OIDC Credentials - External Secret
|
||||
#
|
||||
# This ExternalSecret syncs Home Assistant OIDC credentials from Vault
|
||||
# Credentials are stored in Vault by the sync-homeassistant-oauth.sh script
|
||||
# after Authentik blueprint creates the OAuth provider
|
||||
#
|
||||
# The secret is used by Home Assistant's hass-openid integration
|
||||
# to authenticate users via Authentik OIDC
|
||||
apiVersion: external-secrets.io/v1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: homeassistant-oauth
|
||||
namespace: home-assistant
|
||||
spec:
|
||||
secretStoreRef:
|
||||
kind: ClusterSecretStore
|
||||
name: vault-backend
|
||||
target:
|
||||
name: homeassistant-oidc-secret
|
||||
creationPolicy: Owner
|
||||
refreshInterval: 1h
|
||||
data:
|
||||
- secretKey: client_id
|
||||
remoteRef:
|
||||
key: secret/homeassistant-oauth
|
||||
property: client-id
|
||||
- secretKey: client_secret
|
||||
remoteRef:
|
||||
key: secret/homeassistant-oauth
|
||||
property: client-secret
|
||||
@@ -0,0 +1,17 @@
|
||||
apiVersion: networking.istio.io/v1beta1
|
||||
kind: VirtualService
|
||||
metadata:
|
||||
name: home-assistant
|
||||
namespace: home-assistant
|
||||
spec:
|
||||
hosts:
|
||||
- homeassistant.kube.huskypup.net
|
||||
gateways:
|
||||
- istio-system/edge
|
||||
http:
|
||||
- timeout: 3600s
|
||||
route:
|
||||
- destination:
|
||||
host: home-assistant.home-assistant.svc.cluster.local
|
||||
port:
|
||||
number: 8123
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
# Home Assistant OIDC Configuration ConfigMap
|
||||
#
|
||||
# This ConfigMap contains the OIDC configuration snippet that gets appended
|
||||
# to Home Assistant's configuration.yaml file during bootstrap
|
||||
#
|
||||
# The configuration enables hass-openid integration for Authentik OIDC auth
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: homeassistant-oidc-config
|
||||
namespace: home-assistant
|
||||
data:
|
||||
oidc.yaml: |
|
||||
# OIDC Authentication via Authentik
|
||||
# Users authenticate via Authentik and are auto-created in Home Assistant
|
||||
openid:
|
||||
client_id: !secret oidc_client_id
|
||||
client_secret: !secret oidc_client_secret
|
||||
configure_url: "https://auth.kube.huskypup.net/application/o/home-assistant/.well-known/openid-configuration"
|
||||
scope: "openid profile email"
|
||||
username_field: "preferred_username"
|
||||
create_user: true
|
||||
block_login: false
|
||||
@@ -0,0 +1,152 @@
|
||||
# Disable Istio sidecar on home-assistant pod because the init container
|
||||
# (install-hass-openid) needs unrestricted network access to git clone.
|
||||
# Istio CNI redirects traffic to the proxy port during init, but the proxy
|
||||
# isn't running yet, causing TLS/connection failures.
|
||||
# ESPHome (same namespace) still gets sidecar injection normally.
|
||||
podAnnotations:
|
||||
sidecar.istio.io/inject: "false"
|
||||
|
||||
image:
|
||||
repository: ghcr.io/home-assistant/home-assistant
|
||||
tag: "2025.1.2"
|
||||
pullPolicy: IfNotPresent
|
||||
|
||||
initContainers:
|
||||
install-hass-openid:
|
||||
image: docker.io/alpine/git:2.47.2
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- |
|
||||
set -e
|
||||
apk add --no-cache git
|
||||
|
||||
# Install hass-openid custom integration
|
||||
mkdir -p /config/custom_components /config/includes
|
||||
cd /tmp
|
||||
git clone --depth 1 https://github.com/cavefire/hass-openid.git
|
||||
cp -r hass-openid/custom_components/openid /config/custom_components/
|
||||
rm -rf hass-openid
|
||||
|
||||
# Write MQTT addons config
|
||||
printf 'mqtt:\n broker: mqtt.kube.huskypup.net\n port: 1883\n username: homeassistant\n password: YOUR_MQTT_PASSWORD' > /config/includes/ha-addons.yaml
|
||||
|
||||
# Write OIDC credentials to secrets.yaml (from mounted k8s secret)
|
||||
if [ -f /oidc-secret/client_id ] && [ -f /oidc-secret/client_secret ]; then
|
||||
CLIENT_ID=$(cat /oidc-secret/client_id)
|
||||
CLIENT_SECRET=$(cat /oidc-secret/client_secret)
|
||||
# Preserve existing secrets, update OIDC entries
|
||||
if [ -f /config/secrets.yaml ]; then
|
||||
sed -i '/^oidc_client_id:/d' /config/secrets.yaml
|
||||
sed -i '/^oidc_client_secret:/d' /config/secrets.yaml
|
||||
fi
|
||||
echo "oidc_client_id: \"${CLIENT_ID}\"" >> /config/secrets.yaml
|
||||
echo "oidc_client_secret: \"${CLIENT_SECRET}\"" >> /config/secrets.yaml
|
||||
echo "OIDC credentials written to secrets.yaml"
|
||||
else
|
||||
echo "WARNING: OIDC secret not mounted, skipping secrets.yaml"
|
||||
fi
|
||||
|
||||
# Ensure OIDC config is in configuration.yaml
|
||||
if [ ! -f /config/configuration.yaml ]; then
|
||||
echo "Creating configuration.yaml with OIDC config..."
|
||||
cat > /config/configuration.yaml <<'OIDCEOF'
|
||||
# Home Assistant Configuration
|
||||
default_config:
|
||||
|
||||
# OIDC Authentication via Authentik
|
||||
openid:
|
||||
client_id: !secret oidc_client_id
|
||||
client_secret: !secret oidc_client_secret
|
||||
configure_url: "https://auth.kube.huskypup.net/application/o/home-assistant/.well-known/openid-configuration"
|
||||
scope: "openid profile email"
|
||||
username_field: "preferred_username"
|
||||
create_user: true
|
||||
block_login: false
|
||||
OIDCEOF
|
||||
elif ! grep -q "^openid:" /config/configuration.yaml; then
|
||||
echo "Appending OIDC config to existing configuration.yaml..."
|
||||
cat >> /config/configuration.yaml <<'OIDCEOF'
|
||||
|
||||
# OIDC Authentication via Authentik
|
||||
openid:
|
||||
client_id: !secret oidc_client_id
|
||||
client_secret: !secret oidc_client_secret
|
||||
configure_url: "https://auth.kube.huskypup.net/application/o/home-assistant/.well-known/openid-configuration"
|
||||
scope: "openid profile email"
|
||||
username_field: "preferred_username"
|
||||
create_user: true
|
||||
block_login: false
|
||||
OIDCEOF
|
||||
else
|
||||
echo "OIDC config already present in configuration.yaml"
|
||||
fi
|
||||
volumeMounts:
|
||||
- name: config
|
||||
mountPath: /config
|
||||
- name: oidc-secret
|
||||
mountPath: /oidc-secret
|
||||
readOnly: true
|
||||
|
||||
env:
|
||||
TZ: America/New_York
|
||||
|
||||
service:
|
||||
main:
|
||||
ports:
|
||||
http:
|
||||
port: 8123
|
||||
|
||||
# Ingress disabled - Istio VirtualService handles routing
|
||||
ingress:
|
||||
main:
|
||||
enabled: false
|
||||
|
||||
persistence:
|
||||
config:
|
||||
enabled: true
|
||||
storageClass: rook-ceph-block
|
||||
accessMode: ReadWriteOnce
|
||||
size: 10Gi
|
||||
retain: true
|
||||
oidc-secret:
|
||||
enabled: true
|
||||
type: secret
|
||||
name: homeassistant-oidc-secret
|
||||
mountPath: /oidc-secret
|
||||
readOnly: true
|
||||
|
||||
securityContext:
|
||||
privileged: false
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 512Mi
|
||||
limits:
|
||||
cpu: 1000m
|
||||
memory: 2Gi
|
||||
|
||||
probes:
|
||||
liveness:
|
||||
enabled: true
|
||||
readiness:
|
||||
enabled: true
|
||||
startup:
|
||||
enabled: true
|
||||
|
||||
mariadb:
|
||||
enabled: false
|
||||
|
||||
postgresql:
|
||||
enabled: false
|
||||
|
||||
influxdb:
|
||||
enabled: false
|
||||
|
||||
metrics:
|
||||
enabled: false
|
||||
@@ -0,0 +1,51 @@
|
||||
apiVersion: postgresql.cnpg.io/v1
|
||||
kind: Cluster
|
||||
metadata:
|
||||
name: pg-n8n
|
||||
namespace: n8n
|
||||
spec:
|
||||
imageName: ghcr.io/cloudnative-pg/postgresql:16
|
||||
instances: 2
|
||||
|
||||
# Resource limits to prevent OOM
|
||||
resources:
|
||||
requests:
|
||||
memory: "512Mi"
|
||||
cpu: "25m"
|
||||
limits:
|
||||
memory: "2Gi"
|
||||
cpu: "250m"
|
||||
|
||||
# Spread replicas across different nodes
|
||||
affinity:
|
||||
topologyKey: kubernetes.io/hostname
|
||||
|
||||
storage:
|
||||
size: 10Gi
|
||||
storageClass: rook-ceph-block
|
||||
|
||||
primaryUpdateStrategy: unsupervised
|
||||
|
||||
# PostgreSQL configuration for better performance
|
||||
postgresql:
|
||||
parameters:
|
||||
max_connections: "200"
|
||||
shared_buffers: "512MB"
|
||||
effective_cache_size: "1536MB"
|
||||
maintenance_work_mem: "128MB"
|
||||
checkpoint_completion_target: "0.9"
|
||||
wal_buffers: "16MB"
|
||||
default_statistics_target: "100"
|
||||
random_page_cost: "1.1"
|
||||
effective_io_concurrency: "200"
|
||||
work_mem: "2621kB"
|
||||
min_wal_size: "1GB"
|
||||
max_wal_size: "4GB"
|
||||
|
||||
bootstrap:
|
||||
initdb:
|
||||
database: n8n
|
||||
owner: n8n
|
||||
|
||||
monitoring:
|
||||
enablePodMonitor: true
|
||||
@@ -0,0 +1,40 @@
|
||||
---
|
||||
apiVersion: generators.external-secrets.io/v1alpha1
|
||||
kind: Password
|
||||
metadata:
|
||||
name: n8n-cnpg-secret
|
||||
namespace: n8n
|
||||
spec:
|
||||
length: 42
|
||||
digits: 5
|
||||
symbols: 5
|
||||
symbolCharacters: "-_$@"
|
||||
noUpper: false
|
||||
allowRepeat: true
|
||||
|
||||
---
|
||||
apiVersion: external-secrets.io/v1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: n8n-cnpg-secret
|
||||
namespace: n8n
|
||||
spec:
|
||||
# Rotate database password every 24 hours
|
||||
refreshInterval: "24h"
|
||||
target:
|
||||
# This will merge the generated password into the existing pg-n8n-app secret
|
||||
name: pg-n8n-app
|
||||
creationPolicy: Merge
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
cnpg.io/reload: "true"
|
||||
data:
|
||||
# Override the password field with our ESO-generated password
|
||||
password: "{{ .password }}"
|
||||
dataFrom:
|
||||
- sourceRef:
|
||||
generatorRef:
|
||||
apiVersion: generators.external-secrets.io/v1alpha1
|
||||
kind: Password
|
||||
name: n8n-cnpg-secret
|
||||
@@ -0,0 +1,20 @@
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: HTTPRoute
|
||||
metadata:
|
||||
name: n8n
|
||||
namespace: n8n
|
||||
spec:
|
||||
parentRefs:
|
||||
- name: edge
|
||||
namespace: gateway
|
||||
sectionName: https
|
||||
hostnames:
|
||||
- n8n.kube.huskypup.net
|
||||
rules:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: n8n
|
||||
port: 80
|
||||
@@ -0,0 +1,49 @@
|
||||
# kubernetes/apps/n8n/external-secret.yaml
|
||||
# ExternalSecrets for n8n - pulls credentials from Vault
|
||||
|
||||
# Note: Database password is managed by CNPG cluster (pg-n8n-app secret)
|
||||
# We reference it directly in the n8n values.yaml extraEnv section
|
||||
|
||||
---
|
||||
apiVersion: external-secrets.io/v1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: n8n-config
|
||||
namespace: n8n
|
||||
spec:
|
||||
refreshInterval: 1h
|
||||
secretStoreRef:
|
||||
kind: ClusterSecretStore
|
||||
name: vault-backend
|
||||
target:
|
||||
name: n8n-config-secret
|
||||
creationPolicy: Owner
|
||||
data:
|
||||
- secretKey: encryption-key
|
||||
remoteRef:
|
||||
key: n8n-config
|
||||
property: encryption-key
|
||||
|
||||
---
|
||||
apiVersion: external-secrets.io/v1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: n8n-oauth
|
||||
namespace: n8n
|
||||
spec:
|
||||
refreshInterval: 1h
|
||||
secretStoreRef:
|
||||
kind: ClusterSecretStore
|
||||
name: vault-backend
|
||||
target:
|
||||
name: n8n-oauth-secret
|
||||
creationPolicy: Owner
|
||||
data:
|
||||
- secretKey: client-id
|
||||
remoteRef:
|
||||
key: n8n-oauth
|
||||
property: client-id
|
||||
- secretKey: client-secret
|
||||
remoteRef:
|
||||
key: n8n-oauth
|
||||
property: client-secret
|
||||
@@ -0,0 +1,81 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: n8n-hooks
|
||||
namespace: n8n
|
||||
data:
|
||||
hooks.js: |
|
||||
// n8n v2.0.3 compatible hooks for Authentik forward auth integration
|
||||
const { resolve, dirname } = require('path');
|
||||
|
||||
module.exports = {
|
||||
credentials: {
|
||||
create: [],
|
||||
delete: [],
|
||||
update: []
|
||||
},
|
||||
workflow: {
|
||||
create: [],
|
||||
delete: [],
|
||||
update: []
|
||||
},
|
||||
server: {
|
||||
started: [
|
||||
async function (app) {
|
||||
console.log('[n8n-hooks] Initializing forward auth middleware');
|
||||
|
||||
// Get Express app
|
||||
const expressApp = app?.app;
|
||||
if (!expressApp) {
|
||||
console.error('[n8n-hooks] Express app not available');
|
||||
return;
|
||||
}
|
||||
|
||||
let issueCookie, UserRepository, Container;
|
||||
try {
|
||||
const n8nPath = dirname(require.resolve('n8n'));
|
||||
issueCookie = require(resolve(n8nPath, 'dist/auth/jwt')).issueCookie;
|
||||
UserRepository = require(resolve(n8nPath, 'dist/databases/repositories/user.repository')).UserRepository;
|
||||
Container = require('typedi').Container;
|
||||
} catch (error) {
|
||||
console.error('[n8n-hooks] Failed to load dependencies:', error.message);
|
||||
return;
|
||||
}
|
||||
|
||||
const ignoreAuthRegexp = /^\/(assets|healthz|webhook|rest\/oauth2-credential|rest\/settings|static|icons|types)/;
|
||||
|
||||
// Add middleware for forward auth
|
||||
expressApp.use(async (req, res, next) => {
|
||||
try {
|
||||
if (ignoreAuthRegexp.test(req.url)) return next();
|
||||
if (req.cookies?.['n8n-auth']) return next();
|
||||
if (!process.env.N8N_FORWARD_AUTH_HEADER) return next();
|
||||
|
||||
const headerName = process.env.N8N_FORWARD_AUTH_HEADER.toLowerCase().replace(/_/g, '-');
|
||||
const email = req.headers[headerName];
|
||||
if (!email) return next();
|
||||
|
||||
const userRepo = Container.get(UserRepository);
|
||||
const user = await userRepo.findOne({ where: { email } });
|
||||
|
||||
if (!user) {
|
||||
console.warn(`[n8n-hooks] User not found: ${email}`);
|
||||
res.statusCode = 401;
|
||||
res.end(`User ${email} not found. Please contact an admin.`);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`[n8n-hooks] Auto-login: ${email}`);
|
||||
issueCookie(res, user);
|
||||
next();
|
||||
} catch (error) {
|
||||
console.error('[n8n-hooks] Middleware error:', error.message);
|
||||
next();
|
||||
}
|
||||
});
|
||||
|
||||
console.log('[n8n-hooks] Forward auth middleware active');
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
apiVersion: networking.istio.io/v1beta1
|
||||
kind: VirtualService
|
||||
metadata:
|
||||
name: n8n
|
||||
namespace: n8n
|
||||
spec:
|
||||
hosts:
|
||||
- n8n.kube.huskypup.net
|
||||
gateways:
|
||||
- istio-system/edge
|
||||
http:
|
||||
- route:
|
||||
- destination:
|
||||
host: n8n.n8n.svc.cluster.local
|
||||
port:
|
||||
number: 80
|
||||
@@ -0,0 +1,161 @@
|
||||
# kubernetes/apps/n8n/values.yaml
|
||||
# n8n workflow automation platform
|
||||
# Using chart: https://riatlas.github.io/chart__n8n
|
||||
|
||||
# --- n8n image ---
|
||||
image:
|
||||
repository: n8nio/n8n
|
||||
tag: "2.0.3"
|
||||
|
||||
# --- n8n encryption key (loaded from secret) ---
|
||||
n8n:
|
||||
encryption_key: # Will be set via extraEnv from n8n-config-secret
|
||||
|
||||
# --- n8n configuration ---
|
||||
config:
|
||||
database:
|
||||
type: postgresdb
|
||||
postgresdb:
|
||||
host: pg-n8n-rw # CNPG read-write service
|
||||
port: 5432
|
||||
database: n8n
|
||||
user: n8n
|
||||
schema: public
|
||||
generic:
|
||||
timezone: America/New_York
|
||||
path: /
|
||||
host: n8n.kube.huskypup.net
|
||||
port: 5678
|
||||
protocol: https
|
||||
executions:
|
||||
mode: regular
|
||||
saveDataOnError: all
|
||||
saveDataOnSuccess: all
|
||||
saveDataManualExecutions: true
|
||||
pruneData: true
|
||||
pruneDataMaxAge: 3760 # 1 year in hours
|
||||
|
||||
# --- Secret values (passwords, etc.) ---
|
||||
# Note: password is injected via extraEnvSecrets below
|
||||
secret: {}
|
||||
|
||||
# --- Deployment replicas ---
|
||||
# Increased to 2 for high availability and faster response times
|
||||
replicaCount: 2
|
||||
|
||||
# --- Service configuration ---
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 80
|
||||
|
||||
# --- Security context ---
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
|
||||
# --- Ingress disabled - Istio VirtualService handles routing ---
|
||||
ingress:
|
||||
enabled: false
|
||||
|
||||
# --- Persistence for workflows and data ---
|
||||
persistence:
|
||||
enabled: true
|
||||
type: existing
|
||||
existingClaim: n8n-main-persistence
|
||||
storageClass: rook-ceph-block
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
size: 10Gi
|
||||
|
||||
# --- Resources ---
|
||||
# No CPU limits (burst allowed). Keep requests minimal for scheduling.
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 512Mi
|
||||
|
||||
# --- Startup probe ---
|
||||
# Allows app to start without being killed by liveness probe
|
||||
startupProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: http
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 3
|
||||
failureThreshold: 12 # 60 seconds total startup time
|
||||
|
||||
# --- Readiness probe ---
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: http
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 3
|
||||
failureThreshold: 2
|
||||
|
||||
# --- Liveness probe ---
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: http
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
|
||||
# --- Extra environment variables (from secrets) ---
|
||||
# Secrets are loaded from Kubernetes secrets
|
||||
extraEnvSecrets:
|
||||
# Database password from CNPG-managed secret
|
||||
DB_POSTGRESDB_PASSWORD:
|
||||
name: pg-n8n-app
|
||||
key: password
|
||||
|
||||
# n8n encryption key
|
||||
N8N_ENCRYPTION_KEY:
|
||||
name: n8n-config-secret
|
||||
key: encryption-key
|
||||
|
||||
# --- Extra environment variables (plain values) ---
|
||||
extraEnv:
|
||||
# Webhook & Editor URLs
|
||||
WEBHOOK_URL: https://n8n.kube.huskypup.net/
|
||||
N8N_EDITOR_BASE_URL: https://n8n.kube.huskypup.net
|
||||
|
||||
# Logging
|
||||
N8N_LOG_LEVEL: error
|
||||
|
||||
# --- Disable built-in PostgreSQL (we'll deploy it separately with Bitnami) ---
|
||||
postgresql:
|
||||
enabled: false
|
||||
|
||||
# --- Disable Redis (not needed for single instance) ---
|
||||
redis:
|
||||
enabled: false
|
||||
|
||||
# --- Scaling (disabled for single instance) ---
|
||||
scaling:
|
||||
enabled: false
|
||||
|
||||
# --- Extra volumes ---
|
||||
extraVolumes: []
|
||||
|
||||
# --- Extra volume mounts ---
|
||||
extraVolumeMounts: []
|
||||
|
||||
|
||||
|
||||
# --- Node selector ---
|
||||
nodeSelector: {}
|
||||
|
||||
# --- Tolerations ---
|
||||
tolerations: []
|
||||
|
||||
# --- Affinity ---
|
||||
affinity: {}
|
||||
@@ -0,0 +1,52 @@
|
||||
# CloudNative PostgreSQL cluster for Nessus
|
||||
# Low resource configuration for memory-constrained environments
|
||||
apiVersion: postgresql.cnpg.io/v1
|
||||
kind: Cluster
|
||||
metadata:
|
||||
name: pg-nessus
|
||||
namespace: nessus
|
||||
spec:
|
||||
imageName: ghcr.io/cloudnative-pg/postgresql:16
|
||||
instances: 2
|
||||
|
||||
# Low resource limits for constrained environments
|
||||
resources:
|
||||
requests:
|
||||
memory: "512Mi"
|
||||
cpu: "50m"
|
||||
limits:
|
||||
memory: "1Gi"
|
||||
|
||||
# Spread replicas across different nodes for HA
|
||||
affinity:
|
||||
topologyKey: kubernetes.io/hostname
|
||||
|
||||
storage:
|
||||
size: 20Gi
|
||||
storageClass: rook-ceph-block
|
||||
|
||||
primaryUpdateStrategy: unsupervised
|
||||
|
||||
# PostgreSQL configuration optimized for low memory
|
||||
postgresql:
|
||||
parameters:
|
||||
max_connections: "100"
|
||||
shared_buffers: "256MB"
|
||||
effective_cache_size: "768MB"
|
||||
maintenance_work_mem: "64MB"
|
||||
checkpoint_completion_target: "0.9"
|
||||
wal_buffers: "8MB"
|
||||
default_statistics_target: "100"
|
||||
random_page_cost: "1.1"
|
||||
effective_io_concurrency: "200"
|
||||
work_mem: "2621kB"
|
||||
min_wal_size: "512MB"
|
||||
max_wal_size: "2GB"
|
||||
|
||||
bootstrap:
|
||||
initdb:
|
||||
database: nessus
|
||||
owner: nessus
|
||||
|
||||
monitoring:
|
||||
enablePodMonitor: true
|
||||
@@ -0,0 +1,44 @@
|
||||
# External Secrets Operator configuration for Nessus PostgreSQL password rotation
|
||||
# This auto-generates and rotates the database password in Vault
|
||||
---
|
||||
apiVersion: generators.external-secrets.io/v1alpha1
|
||||
kind: Password
|
||||
metadata:
|
||||
name: nessus-db-password
|
||||
namespace: nessus
|
||||
spec:
|
||||
length: 32
|
||||
digits: 10
|
||||
symbols: 0 # No special chars to avoid escaping issues
|
||||
symbolCharacters: ""
|
||||
noUpper: false
|
||||
allowRepeat: true
|
||||
|
||||
---
|
||||
apiVersion: external-secrets.io/v1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: nessus-cnpg-secret
|
||||
namespace: nessus
|
||||
spec:
|
||||
refreshInterval: 1h
|
||||
secretStoreRef:
|
||||
name: vault-backend
|
||||
kind: ClusterSecretStore
|
||||
target:
|
||||
name: nessus-db-secret
|
||||
creationPolicy: Owner
|
||||
template:
|
||||
engineVersion: v2
|
||||
data:
|
||||
password: "{{ .password }}"
|
||||
username: "nessus"
|
||||
host: "pg-nessus-rw"
|
||||
port: "5432"
|
||||
database: "nessus"
|
||||
dataFrom:
|
||||
- sourceRef:
|
||||
generatorRef:
|
||||
apiVersion: generators.external-secrets.io/v1alpha1
|
||||
kind: Password
|
||||
name: nessus-db-password
|
||||
@@ -0,0 +1,134 @@
|
||||
# Nessus Vulnerability Scanner Deployment
|
||||
# Optimized for low resource usage in constrained environments
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: nessus
|
||||
namespace: nessus
|
||||
labels:
|
||||
app: nessus
|
||||
app.kubernetes.io/name: nessus
|
||||
app.kubernetes.io/component: security-scanner
|
||||
spec:
|
||||
replicas: 1
|
||||
strategy:
|
||||
type: Recreate # Only one instance can mount the PVC at a time
|
||||
selector:
|
||||
matchLabels:
|
||||
app: nessus
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: nessus
|
||||
annotations:
|
||||
# Reloader auto-restart when secrets change
|
||||
secret.reloader.stakater.com/reload: "nessus-admin-credentials"
|
||||
spec:
|
||||
containers:
|
||||
- name: nessus
|
||||
image: docker.io/tenable/nessus:10.8.3-ubuntu
|
||||
imagePullPolicy: IfNotPresent
|
||||
|
||||
# Low resource configuration for constrained environments
|
||||
resources:
|
||||
requests:
|
||||
memory: "1Gi"
|
||||
cpu: "50m"
|
||||
limits:
|
||||
memory: "2Gi"
|
||||
|
||||
ports:
|
||||
- name: https
|
||||
containerPort: 8834
|
||||
protocol: TCP
|
||||
|
||||
# Environment variables for Nessus configuration
|
||||
env:
|
||||
# Admin credentials from Vault (via ESO)
|
||||
- name: USERNAME
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: nessus-admin-credentials
|
||||
key: NESSUS_USERNAME
|
||||
- name: PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: nessus-admin-credentials
|
||||
key: NESSUS_PASSWORD
|
||||
|
||||
# Auto-update settings (plugins only to save bandwidth/time)
|
||||
- name: AUTO_UPDATE
|
||||
value: "plugins"
|
||||
|
||||
# Volume mounts for persistent data
|
||||
volumeMounts:
|
||||
- name: nessus-data
|
||||
mountPath: /opt/nessus/var/nessus
|
||||
|
||||
# Startup probe - give Nessus time to initialize
|
||||
startupProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 8834
|
||||
scheme: HTTPS
|
||||
initialDelaySeconds: 60
|
||||
periodSeconds: 15
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 60 # 15 minutes total startup time
|
||||
|
||||
# Liveness probe
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 8834
|
||||
scheme: HTTPS
|
||||
initialDelaySeconds: 300
|
||||
periodSeconds: 30
|
||||
timeoutSeconds: 10
|
||||
failureThreshold: 5
|
||||
|
||||
# Readiness probe
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 8834
|
||||
scheme: HTTPS
|
||||
initialDelaySeconds: 120
|
||||
periodSeconds: 15
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 5
|
||||
|
||||
# Security context
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
runAsNonRoot: false # Nessus requires root
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
add:
|
||||
- NET_ADMIN # Required for network scanning
|
||||
- NET_RAW # Required for raw socket scanning
|
||||
|
||||
volumes:
|
||||
- name: nessus-data
|
||||
persistentVolumeClaim:
|
||||
claimName: nessus-data
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: nessus
|
||||
namespace: nessus
|
||||
labels:
|
||||
app: nessus
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- name: https
|
||||
port: 8834
|
||||
targetPort: 8834
|
||||
protocol: TCP
|
||||
selector:
|
||||
app: nessus
|
||||
@@ -0,0 +1,30 @@
|
||||
# External Secrets Operator configuration for Nessus admin credentials
|
||||
# Pulls Nessus admin username and password from Vault
|
||||
---
|
||||
apiVersion: external-secrets.io/v1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: nessus-admin-secret
|
||||
namespace: nessus
|
||||
spec:
|
||||
refreshInterval: 1h
|
||||
secretStoreRef:
|
||||
name: vault-backend
|
||||
kind: ClusterSecretStore
|
||||
target:
|
||||
name: nessus-admin-credentials
|
||||
creationPolicy: Owner
|
||||
template:
|
||||
engineVersion: v2
|
||||
data:
|
||||
NESSUS_USERNAME: "{{ .username }}"
|
||||
NESSUS_PASSWORD: "{{ .password }}"
|
||||
data:
|
||||
- secretKey: username
|
||||
remoteRef:
|
||||
key: secret/nessus
|
||||
property: admin-username
|
||||
- secretKey: password
|
||||
remoteRef:
|
||||
key: secret/nessus
|
||||
property: admin-password
|
||||
@@ -0,0 +1,31 @@
|
||||
apiVersion: networking.istio.io/v1beta1
|
||||
kind: DestinationRule
|
||||
metadata:
|
||||
name: nessus
|
||||
namespace: nessus
|
||||
spec:
|
||||
host: nessus.nessus.svc.cluster.local
|
||||
trafficPolicy:
|
||||
tls:
|
||||
mode: SIMPLE
|
||||
sni: nessus.nessus.svc.cluster.local
|
||||
insecureSkipVerify: true
|
||||
|
||||
---
|
||||
apiVersion: networking.istio.io/v1beta1
|
||||
kind: VirtualService
|
||||
metadata:
|
||||
name: nessus
|
||||
namespace: nessus
|
||||
spec:
|
||||
hosts:
|
||||
- nessus.kube.huskypup.net
|
||||
gateways:
|
||||
- istio-system/edge
|
||||
http:
|
||||
- timeout: 3600s
|
||||
route:
|
||||
- destination:
|
||||
host: nessus.nessus.svc.cluster.local
|
||||
port:
|
||||
number: 8834
|
||||
@@ -0,0 +1,13 @@
|
||||
# PersistentVolumeClaim for Nessus scan data and plugin storage
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: nessus-data
|
||||
namespace: nessus
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
storageClassName: rook-ceph-block
|
||||
resources:
|
||||
requests:
|
||||
storage: 50Gi
|
||||
@@ -0,0 +1,20 @@
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: HTTPRoute
|
||||
metadata:
|
||||
name: nextcloud
|
||||
namespace: nextcloud
|
||||
spec:
|
||||
parentRefs:
|
||||
- name: edge
|
||||
namespace: gateway
|
||||
sectionName: https
|
||||
hostnames:
|
||||
- nextcloud.kube.huskypup.net
|
||||
rules:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: nextcloud
|
||||
port: 8080
|
||||
@@ -0,0 +1,26 @@
|
||||
# kubernetes/apps/nextcloud/external-secret.yaml
|
||||
# ExternalSecrets for Nextcloud - pulls OAuth credentials from Vault
|
||||
|
||||
---
|
||||
apiVersion: external-secrets.io/v1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: nextcloud-oauth
|
||||
namespace: nextcloud
|
||||
spec:
|
||||
refreshInterval: 1h
|
||||
secretStoreRef:
|
||||
kind: ClusterSecretStore
|
||||
name: vault-backend
|
||||
target:
|
||||
name: nextcloud-oauth-secret
|
||||
creationPolicy: Owner
|
||||
data:
|
||||
- secretKey: client-id
|
||||
remoteRef:
|
||||
key: nextcloud-oauth
|
||||
property: client-id
|
||||
- secretKey: client-secret
|
||||
remoteRef:
|
||||
key: nextcloud-oauth
|
||||
property: client-secret
|
||||
@@ -0,0 +1,17 @@
|
||||
apiVersion: networking.istio.io/v1beta1
|
||||
kind: VirtualService
|
||||
metadata:
|
||||
name: nextcloud
|
||||
namespace: nextcloud
|
||||
spec:
|
||||
hosts:
|
||||
- nextcloud.kube.huskypup.net
|
||||
gateways:
|
||||
- istio-system/edge
|
||||
http:
|
||||
- timeout: 3600s
|
||||
route:
|
||||
- destination:
|
||||
host: nextcloud.nextcloud.svc.cluster.local
|
||||
port:
|
||||
number: 8080
|
||||
@@ -0,0 +1,92 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: mariadb-nextcloud
|
||||
namespace: nextcloud
|
||||
labels:
|
||||
app: mariadb-nextcloud
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: mariadb-nextcloud
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: mariadb-nextcloud
|
||||
spec:
|
||||
securityContext:
|
||||
runAsUser: 999
|
||||
runAsGroup: 999
|
||||
fsGroup: 999
|
||||
containers:
|
||||
- name: mariadb
|
||||
image: mariadb:11.4
|
||||
securityContext:
|
||||
runAsUser: 999
|
||||
runAsGroup: 999
|
||||
runAsNonRoot: true
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop: ["ALL"]
|
||||
args:
|
||||
- --character-set-server=utf8mb4
|
||||
- --collation-server=utf8mb4_unicode_ci
|
||||
- --init-connect=SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci
|
||||
env:
|
||||
- name: MARIADB_ROOT_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mariadb-nextcloud
|
||||
key: root-password
|
||||
- name: MARIADB_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mariadb-nextcloud
|
||||
key: password
|
||||
- name: MARIADB_USER
|
||||
value: nextcloud
|
||||
- name: MARIADB_DATABASE
|
||||
value: nextcloud
|
||||
ports:
|
||||
- containerPort: 3306
|
||||
volumeMounts:
|
||||
- name: mariadb-data
|
||||
mountPath: /var/lib/mysql
|
||||
- name: tmp
|
||||
mountPath: /tmp
|
||||
- name: run-mysqld
|
||||
mountPath: /run/mysqld
|
||||
volumes:
|
||||
- name: tmp
|
||||
emptyDir: {}
|
||||
- name: run-mysqld
|
||||
emptyDir: {}
|
||||
- name: mariadb-data
|
||||
persistentVolumeClaim:
|
||||
claimName: mariadb-nextcloud-pvc
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: mariadb-nextcloud
|
||||
namespace: nextcloud
|
||||
spec:
|
||||
selector:
|
||||
app: mariadb-nextcloud
|
||||
ports:
|
||||
- port: 3306
|
||||
targetPort: 3306
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: mariadb-nextcloud-pvc
|
||||
namespace: nextcloud
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
storageClassName: rook-ceph-block
|
||||
resources:
|
||||
requests:
|
||||
storage: 10Gi
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
apiVersion: generators.external-secrets.io/v1alpha1
|
||||
kind: Password
|
||||
metadata:
|
||||
name: nextcloud-mariadb-password
|
||||
namespace: nextcloud
|
||||
spec:
|
||||
length: 32
|
||||
digits: 5
|
||||
symbols: 3
|
||||
symbolCharacters: "-_$"
|
||||
noUpper: false
|
||||
allowRepeat: true
|
||||
---
|
||||
apiVersion: external-secrets.io/v1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: nextcloud-mariadb-secret
|
||||
namespace: nextcloud
|
||||
spec:
|
||||
refreshInterval: "24h"
|
||||
target:
|
||||
name: mariadb-nextcloud
|
||||
creationPolicy: Owner
|
||||
template:
|
||||
data:
|
||||
password: "{{ .password }}"
|
||||
root-password: "{{ .password }}"
|
||||
user: nextcloud
|
||||
dataFrom:
|
||||
- sourceRef:
|
||||
generatorRef:
|
||||
apiVersion: generators.external-secrets.io/v1alpha1
|
||||
kind: Password
|
||||
name: nextcloud-mariadb-password
|
||||
@@ -0,0 +1,19 @@
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: nextcloud-nextcloud
|
||||
namespace: nextcloud
|
||||
labels:
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
app.kubernetes.io/name: nextcloud
|
||||
app.kubernetes.io/instance: nextcloud
|
||||
annotations:
|
||||
meta.helm.sh/release-name: nextcloud
|
||||
meta.helm.sh/release-namespace: nextcloud
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
storageClassName: rook-ceph-block # Rook-Ceph block storage
|
||||
resources:
|
||||
requests:
|
||||
storage: 50Gi # whatever size you want
|
||||
@@ -0,0 +1,111 @@
|
||||
# values/nextcloud.values.yaml
|
||||
#
|
||||
# For chart: nextcloud/nextcloud
|
||||
# Repo: https://nextcloud.github.io/helm/
|
||||
#
|
||||
# Optimized for large file uploads over 1Gbps link
|
||||
|
||||
replicaCount: 1
|
||||
|
||||
nextcloud:
|
||||
host: nextcloud.kube.huskypup.net
|
||||
username: ""
|
||||
password: ""
|
||||
|
||||
# PHP configuration for large uploads
|
||||
# Use /var/www/tmp (on Ceph PVC) instead of /tmp (on root overlay)
|
||||
phpConfigs:
|
||||
upload.ini: |
|
||||
upload_tmp_dir = /var/www/tmp
|
||||
sys_temp_dir = /var/www/tmp
|
||||
|
||||
# Environment variables for PHP
|
||||
extraEnv:
|
||||
- name: TMPDIR
|
||||
value: /var/www/tmp
|
||||
- name: PHP_MEMORY_LIMIT
|
||||
value: "2G"
|
||||
- name: PHP_UPLOAD_LIMIT
|
||||
value: "10G"
|
||||
|
||||
configs:
|
||||
oidc.config.php: |-
|
||||
<?php
|
||||
$CONFIG = array (
|
||||
'oidc_login_provider_url' => 'https://auth.kube.huskypup.net/application/o/nextcloud/',
|
||||
'oidc_login_client_id' => '',
|
||||
'oidc_login_client_secret' => '',
|
||||
'oidc_login_button_text' => 'Log in with Authentik',
|
||||
'oidc_login_auto_redirect' => false,
|
||||
'oidc_login_hide_password_form' => false,
|
||||
'oidc_login_verify_ssl' => false,
|
||||
'user_oidc' => array(
|
||||
'httpclient.allowselfsigned' => true,
|
||||
),
|
||||
);
|
||||
proxy.config.php: |-
|
||||
<?php
|
||||
$CONFIG = array (
|
||||
'trusted_proxies' => array(
|
||||
0 => '10.0.0.0/8',
|
||||
1 => '172.16.0.0/12',
|
||||
),
|
||||
'overwriteprotocol' => 'https',
|
||||
'overwrite.cli.url' => 'https://nextcloud.kube.huskypup.net',
|
||||
'allow_local_remote_servers' => true,
|
||||
);
|
||||
# Use Ceph-backed temp directory for large uploads
|
||||
temp.config.php: |-
|
||||
<?php
|
||||
$CONFIG = array (
|
||||
'tempdirectory' => '/var/www/tmp',
|
||||
);
|
||||
|
||||
existingSecret:
|
||||
enabled: true
|
||||
secretName: nextcloud-admin-secret
|
||||
usernameKey: username
|
||||
passwordKey: password
|
||||
|
||||
# Ingress disabled - Istio VirtualService handles routing
|
||||
ingress:
|
||||
enabled: false
|
||||
|
||||
externalDatabase:
|
||||
enabled: true
|
||||
type: mysql
|
||||
host: mariadb-nextcloud
|
||||
port: 3306
|
||||
user: nextcloud
|
||||
database: nextcloud
|
||||
existingSecret:
|
||||
enabled: true
|
||||
secretName: mariadb-nextcloud
|
||||
passwordKey: password
|
||||
usernameKey: user
|
||||
|
||||
persistence:
|
||||
enabled: true
|
||||
storageClass: rook-ceph-block
|
||||
accessMode: ReadWriteOnce
|
||||
size: 50Gi
|
||||
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 512Mi
|
||||
limits:
|
||||
cpu: 1000m
|
||||
memory: 2Gi
|
||||
|
||||
redis:
|
||||
enabled: false
|
||||
|
||||
metrics:
|
||||
enabled: false
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
apiVersion: external-secrets.io/v1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: rancher-oauth
|
||||
namespace: cattle-system
|
||||
spec:
|
||||
refreshInterval: 1h
|
||||
secretStoreRef:
|
||||
kind: ClusterSecretStore
|
||||
name: vault-backend
|
||||
target:
|
||||
name: rancher-oauth-secret
|
||||
creationPolicy: Owner
|
||||
data:
|
||||
- secretKey: client-id
|
||||
remoteRef:
|
||||
key: secret/rancher-oauth
|
||||
property: client-id
|
||||
- secretKey: client-secret
|
||||
remoteRef:
|
||||
key: secret/rancher-oauth
|
||||
property: client-secret
|
||||
@@ -0,0 +1,16 @@
|
||||
apiVersion: networking.istio.io/v1beta1
|
||||
kind: VirtualService
|
||||
metadata:
|
||||
name: rancher
|
||||
namespace: cattle-system
|
||||
spec:
|
||||
hosts:
|
||||
- rancher.kube.huskypup.net
|
||||
gateways:
|
||||
- istio-system/edge
|
||||
http:
|
||||
- route:
|
||||
- destination:
|
||||
host: rancher.cattle-system.svc.cluster.local
|
||||
port:
|
||||
number: 80
|
||||
@@ -0,0 +1,47 @@
|
||||
# apps/rancher/values.yaml
|
||||
# Rancher - Kubernetes Management Platform with Authentik SSO
|
||||
|
||||
hostname: rancher.kube.huskypup.net
|
||||
|
||||
# Exposed via Istio edge Gateway + VirtualService (apps/rancher/istio-virtualservice.yaml)
|
||||
ingress:
|
||||
enabled: false
|
||||
|
||||
# TLS terminates at Istio ingressgateway
|
||||
tls: external
|
||||
|
||||
# Single replica for homelab (scale up if needed)
|
||||
replicas: 1
|
||||
|
||||
# Storage class for Rancher's internal state
|
||||
# Note: Rancher itself is mostly stateless; state is in etcd/k8s
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 512Mi
|
||||
limits:
|
||||
memory: 2Gi
|
||||
|
||||
# Allow Rancher to manage the local cluster
|
||||
addLocal: "true"
|
||||
|
||||
# Audit logging
|
||||
auditLog:
|
||||
level: 1
|
||||
maxAge: 7
|
||||
maxBackup: 3
|
||||
maxSize: 100
|
||||
|
||||
# Bootstrap password - will be rotated post-deploy via Authentik SSO
|
||||
# This is used only for initial access before SSO is configured
|
||||
bootstrapPassword: "ChangeMe-BootstrapOnly"
|
||||
|
||||
# Extra environment variables (not used for OIDC - configured via AuthConfig CRD post-deploy)
|
||||
extraEnv: []
|
||||
|
||||
# Disable Rancher's bundled cert-manager (we already have it)
|
||||
certmanager:
|
||||
version: ""
|
||||
|
||||
# Feature flags
|
||||
features: "multi-cluster-management=true"
|
||||
@@ -0,0 +1,55 @@
|
||||
apiVersion: postgresql.cnpg.io/v1
|
||||
kind: Cluster
|
||||
metadata:
|
||||
name: pg-teslamate
|
||||
namespace: teslamate
|
||||
spec:
|
||||
imageName: ghcr.io/cloudnative-pg/postgresql:16
|
||||
instances: 2
|
||||
|
||||
# Resource limits to prevent OOM
|
||||
resources:
|
||||
requests:
|
||||
memory: "512Mi"
|
||||
cpu: "25m"
|
||||
limits:
|
||||
memory: "2Gi"
|
||||
cpu: "250m"
|
||||
|
||||
# Spread replicas across different nodes
|
||||
affinity:
|
||||
topologyKey: kubernetes.io/hostname
|
||||
|
||||
storage:
|
||||
size: 10Gi
|
||||
storageClass: rook-ceph-block
|
||||
|
||||
primaryUpdateStrategy: unsupervised
|
||||
|
||||
# PostgreSQL configuration for better performance
|
||||
postgresql:
|
||||
parameters:
|
||||
max_connections: "200"
|
||||
shared_buffers: "512MB"
|
||||
effective_cache_size: "1536MB"
|
||||
maintenance_work_mem: "128MB"
|
||||
checkpoint_completion_target: "0.9"
|
||||
wal_buffers: "16MB"
|
||||
default_statistics_target: "100"
|
||||
random_page_cost: "1.1"
|
||||
effective_io_concurrency: "200"
|
||||
work_mem: "2621kB"
|
||||
min_wal_size: "1GB"
|
||||
max_wal_size: "4GB"
|
||||
|
||||
bootstrap:
|
||||
initdb:
|
||||
database: teslamate
|
||||
owner: teslamate
|
||||
postInitSQL:
|
||||
- ALTER USER teslamate WITH SUPERUSER
|
||||
- CREATE EXTENSION IF NOT EXISTS cube
|
||||
- CREATE EXTENSION IF NOT EXISTS earthdistance
|
||||
|
||||
monitoring:
|
||||
enablePodMonitor: true
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
apiVersion: generators.external-secrets.io/v1alpha1
|
||||
kind: Password
|
||||
metadata:
|
||||
name: teslamate-cnpg-secret
|
||||
namespace: teslamate
|
||||
spec:
|
||||
length: 42
|
||||
digits: 5
|
||||
symbols: 5
|
||||
symbolCharacters: "-_$@"
|
||||
noUpper: false
|
||||
allowRepeat: true
|
||||
---
|
||||
apiVersion: external-secrets.io/v1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: teslamate-cnpg-secret
|
||||
namespace: teslamate
|
||||
spec:
|
||||
# how often to rotate the DB password
|
||||
refreshInterval: "24h"
|
||||
target:
|
||||
# This will merge the generated password into the existing pg-teslamate-app secret
|
||||
name: pg-teslamate-app
|
||||
creationPolicy: Merge
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
cnpg.io/reload: "true"
|
||||
data:
|
||||
# Override the password field with our ESO-generated password
|
||||
password: "{{ .password }}"
|
||||
dataFrom:
|
||||
- sourceRef:
|
||||
generatorRef:
|
||||
apiVersion: generators.external-secrets.io/v1alpha1
|
||||
kind: Password
|
||||
name: teslamate-cnpg-secret
|
||||
@@ -0,0 +1,20 @@
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: HTTPRoute
|
||||
metadata:
|
||||
name: teslamate
|
||||
namespace: teslamate
|
||||
spec:
|
||||
parentRefs:
|
||||
- name: edge
|
||||
namespace: gateway
|
||||
sectionName: https
|
||||
hostnames:
|
||||
- teslamate.kube.huskypup.net
|
||||
rules:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: teslamate
|
||||
port: 4000
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
apiVersion: external-secrets.io/v1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: teslamate-config
|
||||
namespace: teslamate
|
||||
spec:
|
||||
refreshInterval: 1h
|
||||
secretStoreRef:
|
||||
name: vault-backend
|
||||
kind: ClusterSecretStore
|
||||
target:
|
||||
name: teslamate-config-secret
|
||||
creationPolicy: Owner
|
||||
data:
|
||||
- secretKey: encryption-key
|
||||
remoteRef:
|
||||
key: teslamate/config
|
||||
property: encryption_key
|
||||
@@ -0,0 +1,16 @@
|
||||
apiVersion: networking.istio.io/v1beta1
|
||||
kind: VirtualService
|
||||
metadata:
|
||||
name: teslamate
|
||||
namespace: teslamate
|
||||
spec:
|
||||
hosts:
|
||||
- teslamate.kube.huskypup.net
|
||||
gateways:
|
||||
- istio-system/edge
|
||||
http:
|
||||
- route:
|
||||
- destination:
|
||||
host: teslamate.teslamate.svc.cluster.local
|
||||
port:
|
||||
number: 4000
|
||||
@@ -0,0 +1,112 @@
|
||||
# Teslamate - Tesla data logger
|
||||
# Uses CNPG PostgreSQL with auto-rotating passwords via ESO
|
||||
|
||||
image:
|
||||
repository: teslamate/teslamate
|
||||
tag: 1.30.1
|
||||
pullPolicy: IfNotPresent
|
||||
|
||||
env:
|
||||
TZ: America/Chicago
|
||||
|
||||
# PostgreSQL configuration (uses CNPG-managed database)
|
||||
DATABASE_HOST: pg-teslamate-rw.teslamate.svc
|
||||
DATABASE_USER: teslamate
|
||||
DATABASE_NAME: teslamate
|
||||
|
||||
# Database password from CNPG-generated secret
|
||||
DATABASE_PASS:
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: pg-teslamate-app
|
||||
key: password
|
||||
|
||||
# Encryption key from Vault
|
||||
ENCRYPTION_KEY:
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: teslamate-config-secret
|
||||
key: encryption-key
|
||||
|
||||
# MQTT settings (disabled by default, can enable later)
|
||||
DISABLE_MQTT: "true"
|
||||
|
||||
# Virtual host for proper URL generation
|
||||
VIRTUAL_HOST: teslamate.kube.huskypup.net
|
||||
|
||||
# Port configuration
|
||||
PORT: "4000"
|
||||
|
||||
service:
|
||||
main:
|
||||
ports:
|
||||
http:
|
||||
port: 4000
|
||||
|
||||
# Ingress disabled - Istio VirtualService handles routing
|
||||
ingress:
|
||||
main:
|
||||
enabled: false
|
||||
|
||||
persistence:
|
||||
import:
|
||||
enabled: false
|
||||
|
||||
# Disable built-in postgresql (we use CNPG)
|
||||
postgresql:
|
||||
enabled: false
|
||||
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
|
||||
# Resource limits
|
||||
resources:
|
||||
requests:
|
||||
memory: "256Mi"
|
||||
cpu: "50m"
|
||||
limits:
|
||||
cpu: "500m"
|
||||
memory: "512Mi"
|
||||
|
||||
# Health probes
|
||||
probes:
|
||||
liveness:
|
||||
enabled: true
|
||||
custom: true
|
||||
spec:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 4000
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
readiness:
|
||||
enabled: true
|
||||
custom: true
|
||||
spec:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 4000
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 3
|
||||
failureThreshold: 2
|
||||
startup:
|
||||
enabled: true
|
||||
custom: true
|
||||
spec:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 4000
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 3
|
||||
failureThreshold: 12
|
||||
|
||||
# Reloader annotations for auto-restart on secret changes
|
||||
podAnnotations:
|
||||
secret.reloader.stakater.com/reload: "pg-teslamate-app,teslamate-config-secret"
|
||||
@@ -0,0 +1,34 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: argocd
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "50"
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
spec:
|
||||
project: applications
|
||||
sources:
|
||||
- repoURL: https://argoproj.github.io/argo-helm
|
||||
chart: argo-cd
|
||||
targetRevision: "*"
|
||||
helm:
|
||||
valueFiles:
|
||||
- $values/apps/argocd/values.yaml
|
||||
- repoURL: <GIT_REPO_URL>
|
||||
targetRevision: main
|
||||
ref: values
|
||||
- repoURL: <GIT_REPO_URL>
|
||||
targetRevision: main
|
||||
path: apps/argocd/manifests
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: argocd
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
- ServerSideApply=true
|
||||
@@ -0,0 +1,31 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: esphome
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "51"
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
spec:
|
||||
project: applications
|
||||
sources:
|
||||
- repoURL: https://charts.gabe565.com
|
||||
chart: esphome
|
||||
targetRevision: 0.15.0
|
||||
helm:
|
||||
valueFiles:
|
||||
- $values/apps/home-assistant/esphome/values.yaml
|
||||
- repoURL: <GIT_REPO_URL>
|
||||
targetRevision: main
|
||||
ref: values
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: home-assistant
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
- ServerSideApply=true
|
||||
@@ -0,0 +1,34 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: frigate
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "51"
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
spec:
|
||||
project: applications
|
||||
sources:
|
||||
- repoURL: https://blakeblackshear.github.io/blakeshome-charts
|
||||
chart: frigate
|
||||
targetRevision: 7.8.0
|
||||
helm:
|
||||
valueFiles:
|
||||
- $values/apps/frigate/values.yaml
|
||||
- repoURL: <GIT_REPO_URL>
|
||||
targetRevision: main
|
||||
ref: values
|
||||
- repoURL: <GIT_REPO_URL>
|
||||
targetRevision: main
|
||||
path: apps/frigate/manifests
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: frigate
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
- ServerSideApply=true
|
||||
@@ -0,0 +1,34 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: gitlab
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "50"
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
spec:
|
||||
project: applications
|
||||
sources:
|
||||
- repoURL: https://charts.gitlab.io
|
||||
chart: gitlab
|
||||
targetRevision: 7.7.0
|
||||
helm:
|
||||
valueFiles:
|
||||
- $values/apps/gitlab/values.yaml
|
||||
- repoURL: <GIT_REPO_URL>
|
||||
targetRevision: main
|
||||
ref: values
|
||||
- repoURL: <GIT_REPO_URL>
|
||||
targetRevision: main
|
||||
path: apps/gitlab/manifests
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: gitlab
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
- ServerSideApply=true
|
||||
@@ -0,0 +1,25 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: guacamole
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "51"
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
spec:
|
||||
project: applications
|
||||
source:
|
||||
repoURL: <GIT_REPO_URL>
|
||||
targetRevision: main
|
||||
path: apps/guacamole/manifests
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: guacamole
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
- ServerSideApply=true
|
||||
@@ -0,0 +1,34 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: home-assistant
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "51"
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
spec:
|
||||
project: applications
|
||||
sources:
|
||||
- repoURL: https://geek-cookbook.github.io/charts/
|
||||
chart: home-assistant
|
||||
targetRevision: 13.5.0
|
||||
helm:
|
||||
valueFiles:
|
||||
- $values/apps/home-assistant/values.yaml
|
||||
- repoURL: <GIT_REPO_URL>
|
||||
targetRevision: main
|
||||
ref: values
|
||||
- repoURL: <GIT_REPO_URL>
|
||||
targetRevision: main
|
||||
path: apps/home-assistant/manifests
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: home-assistant
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
- ServerSideApply=true
|
||||
@@ -0,0 +1,25 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: istio-app-routes
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "52"
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
spec:
|
||||
project: applications
|
||||
source:
|
||||
repoURL: <GIT_REPO_URL>
|
||||
targetRevision: main
|
||||
path: infrastructure/istio/manifests/apps
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: istio-system
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
- ServerSideApply=true
|
||||
@@ -0,0 +1,34 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: n8n
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "50"
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
spec:
|
||||
project: applications
|
||||
sources:
|
||||
- repoURL: https://riatlas.github.io/chart__n8n
|
||||
chart: n8n
|
||||
targetRevision: "*"
|
||||
helm:
|
||||
valueFiles:
|
||||
- $values/apps/n8n/values.yaml
|
||||
- repoURL: <GIT_REPO_URL>
|
||||
targetRevision: main
|
||||
ref: values
|
||||
- repoURL: <GIT_REPO_URL>
|
||||
targetRevision: main
|
||||
path: apps/n8n/manifests
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: n8n
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
- ServerSideApply=true
|
||||
@@ -0,0 +1,25 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: nessus
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "51"
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
spec:
|
||||
project: applications
|
||||
source:
|
||||
repoURL: <GIT_REPO_URL>
|
||||
targetRevision: main
|
||||
path: apps/nessus/manifests
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: nessus
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
- ServerSideApply=true
|
||||
@@ -0,0 +1,34 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: nextcloud
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "50"
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
spec:
|
||||
project: applications
|
||||
sources:
|
||||
- repoURL: https://nextcloud.github.io/helm/
|
||||
chart: nextcloud
|
||||
targetRevision: "*"
|
||||
helm:
|
||||
valueFiles:
|
||||
- $values/apps/nextcloud/values.yaml
|
||||
- repoURL: <GIT_REPO_URL>
|
||||
targetRevision: main
|
||||
ref: values
|
||||
- repoURL: <GIT_REPO_URL>
|
||||
targetRevision: main
|
||||
path: apps/nextcloud/manifests
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: nextcloud
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
- ServerSideApply=true
|
||||
@@ -0,0 +1,34 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: rancher
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "51"
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
spec:
|
||||
project: applications
|
||||
sources:
|
||||
- repoURL: https://releases.rancher.com/server-charts/stable
|
||||
chart: rancher
|
||||
targetRevision: 2.13.2
|
||||
helm:
|
||||
valueFiles:
|
||||
- $values/apps/rancher/values.yaml
|
||||
- repoURL: <GIT_REPO_URL>
|
||||
targetRevision: main
|
||||
ref: values
|
||||
- repoURL: <GIT_REPO_URL>
|
||||
targetRevision: main
|
||||
path: apps/rancher/manifests
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: cattle-system
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
- ServerSideApply=true
|
||||
@@ -0,0 +1,34 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: teslamate
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "50"
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
spec:
|
||||
project: applications
|
||||
sources:
|
||||
- repoURL: https://geek-cookbook.github.io/charts/
|
||||
chart: teslamate
|
||||
targetRevision: 7.2.0
|
||||
helm:
|
||||
valueFiles:
|
||||
- $values/apps/teslamate/values.yaml
|
||||
- repoURL: <GIT_REPO_URL>
|
||||
targetRevision: main
|
||||
ref: values
|
||||
- repoURL: <GIT_REPO_URL>
|
||||
targetRevision: main
|
||||
path: apps/teslamate/manifests
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: teslamate
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
- ServerSideApply=true
|
||||
@@ -0,0 +1,22 @@
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: argocd-hook-sa
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "-5"
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: argocd-hook-sa-admin
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "-5"
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: cluster-admin
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: argocd-hook-sa
|
||||
namespace: argocd
|
||||
@@ -0,0 +1,34 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: authentik
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "23"
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
spec:
|
||||
project: infrastructure
|
||||
sources:
|
||||
- repoURL: https://charts.goauthentik.io
|
||||
chart: authentik
|
||||
targetRevision: 2026.2.1
|
||||
helm:
|
||||
valueFiles:
|
||||
- $values/infrastructure/authentik/values.yaml
|
||||
- repoURL: <GIT_REPO_URL>
|
||||
targetRevision: main
|
||||
ref: values
|
||||
- repoURL: <GIT_REPO_URL>
|
||||
targetRevision: main
|
||||
path: infrastructure/authentik/manifests
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: authentik
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
- ServerSideApply=true
|
||||
@@ -0,0 +1,34 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: cert-manager
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "2"
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
spec:
|
||||
project: infrastructure
|
||||
sources:
|
||||
- repoURL: https://charts.jetstack.io
|
||||
chart: cert-manager
|
||||
targetRevision: v1.13.2
|
||||
helm:
|
||||
valueFiles:
|
||||
- $values/infrastructure/cert-manager/values.yaml
|
||||
- repoURL: <GIT_REPO_URL>
|
||||
targetRevision: main
|
||||
ref: values
|
||||
- repoURL: <GIT_REPO_URL>
|
||||
targetRevision: main
|
||||
path: infrastructure/cert-manager/manifests
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: cert-manager
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
- ServerSideApply=true
|
||||
@@ -0,0 +1,25 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: checkov-scanner
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "43"
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
spec:
|
||||
project: infrastructure
|
||||
source:
|
||||
repoURL: <GIT_REPO_URL>
|
||||
targetRevision: main
|
||||
path: infrastructure/checkov/manifests
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: checkov
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
- ServerSideApply=true
|
||||
@@ -0,0 +1,34 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: cilium
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "1"
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
spec:
|
||||
project: infrastructure
|
||||
sources:
|
||||
- repoURL: https://helm.cilium.io
|
||||
chart: cilium
|
||||
targetRevision: 1.17.1
|
||||
helm:
|
||||
valueFiles:
|
||||
- $values/infrastructure/cilium/values.yaml
|
||||
- repoURL: <GIT_REPO_URL>
|
||||
targetRevision: main
|
||||
ref: values
|
||||
- repoURL: <GIT_REPO_URL>
|
||||
targetRevision: main
|
||||
path: infrastructure/cilium/manifests
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: kube-system
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
- ServerSideApply=true
|
||||
@@ -0,0 +1,25 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: cnpg
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "12"
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
spec:
|
||||
project: infrastructure
|
||||
source:
|
||||
repoURL: https://cloudnative-pg.github.io/charts
|
||||
chart: cloudnative-pg
|
||||
targetRevision: "*"
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: cnpg-system
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
- ServerSideApply=true
|
||||
@@ -0,0 +1,34 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: crowdsec
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "22"
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
spec:
|
||||
project: infrastructure
|
||||
sources:
|
||||
- repoURL: https://crowdsecurity.github.io/helm-charts
|
||||
chart: crowdsec
|
||||
targetRevision: "*"
|
||||
helm:
|
||||
valueFiles:
|
||||
- $values/infrastructure/crowdsec/values.yaml
|
||||
- repoURL: <GIT_REPO_URL>
|
||||
targetRevision: main
|
||||
ref: values
|
||||
- repoURL: <GIT_REPO_URL>
|
||||
targetRevision: main
|
||||
path: infrastructure/crowdsec/manifests
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: crowdsec
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
- ServerSideApply=true
|
||||
@@ -0,0 +1,25 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: envoy-edge
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "7"
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
spec:
|
||||
project: infrastructure
|
||||
source:
|
||||
repoURL: <GIT_REPO_URL>
|
||||
targetRevision: main
|
||||
path: infrastructure/envoy-gateway/manifests
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: gateway
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
- ServerSideApply=true
|
||||
@@ -0,0 +1,31 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: envoy-gateway
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "6"
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
spec:
|
||||
project: infrastructure
|
||||
sources:
|
||||
- repoURL: https://gateway.envoyproxy.io/charts
|
||||
chart: gateway-helm
|
||||
targetRevision: v1.6.3
|
||||
helm:
|
||||
valueFiles:
|
||||
- $values/infrastructure/envoy-gateway/values.yaml
|
||||
- repoURL: <GIT_REPO_URL>
|
||||
targetRevision: main
|
||||
ref: values
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: envoy-gateway-system
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
- ServerSideApply=true
|
||||
@@ -0,0 +1,34 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: external-dns
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "30"
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
spec:
|
||||
project: infrastructure
|
||||
sources:
|
||||
- repoURL: https://kubernetes-sigs.github.io/external-dns/
|
||||
chart: external-dns
|
||||
targetRevision: 1.20.0
|
||||
helm:
|
||||
valueFiles:
|
||||
- $values/infrastructure/external-dns/values.yaml
|
||||
- repoURL: <GIT_REPO_URL>
|
||||
targetRevision: main
|
||||
ref: values
|
||||
- repoURL: <GIT_REPO_URL>
|
||||
targetRevision: main
|
||||
path: infrastructure/external-dns/manifests
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: external-dns
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
- ServerSideApply=true
|
||||
@@ -0,0 +1,31 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: external-secrets
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "21"
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
spec:
|
||||
project: infrastructure
|
||||
sources:
|
||||
- repoURL: https://charts.external-secrets.io
|
||||
chart: external-secrets
|
||||
targetRevision: 0.20.4
|
||||
helm:
|
||||
valueFiles:
|
||||
- $values/infrastructure/external-secrets/values.yaml
|
||||
- repoURL: <GIT_REPO_URL>
|
||||
targetRevision: main
|
||||
ref: values
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: external-secrets
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
- ServerSideApply=true
|
||||
@@ -0,0 +1,37 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: grafana
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "41"
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
spec:
|
||||
project: infrastructure
|
||||
sources:
|
||||
- repoURL: https://grafana.github.io/helm-charts
|
||||
chart: grafana
|
||||
targetRevision: "*"
|
||||
helm:
|
||||
valueFiles:
|
||||
- $values/infrastructure/grafana/values.yaml
|
||||
- repoURL: <GIT_REPO_URL>
|
||||
targetRevision: main
|
||||
ref: values
|
||||
- repoURL: <GIT_REPO_URL>
|
||||
targetRevision: main
|
||||
path: infrastructure/grafana/manifests
|
||||
- repoURL: <GIT_REPO_URL>
|
||||
targetRevision: main
|
||||
path: infrastructure/grafana/dashboards
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: grafana
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
- ServerSideApply=true
|
||||
@@ -0,0 +1,25 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: istio-base
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "2"
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
spec:
|
||||
project: infrastructure
|
||||
source:
|
||||
repoURL: https://istio-release.storage.googleapis.com/charts
|
||||
chart: base
|
||||
targetRevision: 1.28.3
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: istio-system
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
- ServerSideApply=true
|
||||
@@ -0,0 +1,31 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: istio-cni
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "3"
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
spec:
|
||||
project: infrastructure
|
||||
sources:
|
||||
- repoURL: https://istio-release.storage.googleapis.com/charts
|
||||
chart: cni
|
||||
targetRevision: 1.29.0
|
||||
helm:
|
||||
valueFiles:
|
||||
- $values/infrastructure/istio/cni-values.yaml
|
||||
- repoURL: <GIT_REPO_URL>
|
||||
targetRevision: main
|
||||
ref: values
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: kube-system
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
- ServerSideApply=true
|
||||
@@ -0,0 +1,25 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: istio-edge
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "6"
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
spec:
|
||||
project: infrastructure
|
||||
source:
|
||||
repoURL: <GIT_REPO_URL>
|
||||
targetRevision: main
|
||||
path: infrastructure/istio/manifests/edge
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: istio-system
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
- ServerSideApply=true
|
||||
@@ -0,0 +1,31 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: istio-ingressgateway
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "5"
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
spec:
|
||||
project: infrastructure
|
||||
sources:
|
||||
- repoURL: https://istio-release.storage.googleapis.com/charts
|
||||
chart: gateway
|
||||
targetRevision: 1.29.0
|
||||
helm:
|
||||
valueFiles:
|
||||
- $values/infrastructure/istio/ingressgateway-values.yaml
|
||||
- repoURL: <GIT_REPO_URL>
|
||||
targetRevision: main
|
||||
ref: values
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: istio-system
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
- ServerSideApply=true
|
||||
@@ -0,0 +1,25 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: istio-mesh-config
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "42"
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
spec:
|
||||
project: infrastructure
|
||||
source:
|
||||
repoURL: <GIT_REPO_URL>
|
||||
targetRevision: main
|
||||
path: infrastructure/istio/manifests/mesh
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: istio-system
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
- ServerSideApply=true
|
||||
@@ -0,0 +1,31 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: istiod
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "4"
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
spec:
|
||||
project: infrastructure
|
||||
sources:
|
||||
- repoURL: https://istio-release.storage.googleapis.com/charts
|
||||
chart: istiod
|
||||
targetRevision: 1.29.0
|
||||
helm:
|
||||
valueFiles:
|
||||
- $values/infrastructure/istio/istiod-values.yaml
|
||||
- repoURL: <GIT_REPO_URL>
|
||||
targetRevision: main
|
||||
ref: values
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: istio-system
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
- ServerSideApply=true
|
||||
@@ -0,0 +1,31 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: kiali-operator
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "42"
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
spec:
|
||||
project: infrastructure
|
||||
sources:
|
||||
- repoURL: https://kiali.org/helm-charts
|
||||
chart: kiali-operator
|
||||
targetRevision: "*"
|
||||
helm:
|
||||
valueFiles:
|
||||
- $values/infrastructure/kiali/operator-values.yaml
|
||||
- repoURL: <GIT_REPO_URL>
|
||||
targetRevision: main
|
||||
ref: values
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: kiali-operator
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
- ServerSideApply=true
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user