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:
@@ -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
|
||||
Reference in New Issue
Block a user