Initial commit

This commit is contained in:
Scooby Husky
2026-03-09 20:21:35 -05:00
commit aacb8eebbe
314 changed files with 21766 additions and 0 deletions
+74
View File
@@ -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
+86
View File
@@ -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
+206
View File
@@ -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: {}
+116
View File
@@ -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: {}
+95
View File
@@ -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
+114
View File
@@ -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
+333
View File
@@ -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