chore: enhance DB connectivity diagnostics and cross-cluster support

- Added split-cluster detection with detailed app and DB context logging.
- Introduced robust DB connectivity checks for DNS resolution and TCP probes.
- Enabled automated cross-cluster DB ILB service creation and validation.
- Improved logging for DB migration failures with enhanced diagnostics and connectivity hints.
This commit is contained in:
chrisfu 2026-04-14 20:21:41 -07:00
parent ff5b73a435
commit e6374e3e46

View File

@ -174,10 +174,28 @@ check_gitlab_migrations_blocked() {
if [[ "$_waiting_reason" == "CrashLoopBackOff" || ( -n "$_exit_code" && "$_exit_code" != "0" ) ]]; then
log "GitLab migrations job is actively failing: ${_latest_job} (pod: ${_latest_pod}, restarts: ${_restarts}, exitCode: ${_exit_code:-unknown})"
# Enhanced DB connectivity diagnostics for split-cluster visibility
local _db_info="DB_HOST=${DB_HOST}, DB_PORT=${DB_PORT}"
local _split_cluster="No"
local app_ctx="${APP_CLUSTER_KUBECONTEXT:-${KUBECONTEXT:-}}"
local db_ctx="${DB_CLUSTER_KUBECONTEXT:-}"
if [[ -n "$db_ctx" && "$db_ctx" != "$app_ctx" ]]; then _split_cluster="Yes (APP: ${app_ctx}, DB: ${db_ctx})"; fi
local _diag
_diag=$(get_gitlab_migrations_diagnostics "$_latest_job")
local _connectivity_hint=""
if echo "$_diag" | grep -qi "connecting with your hostname"; then
_connectivity_hint=" (Likely DNS/resolution failure)"
elif echo "$_diag" | grep -qiE "connection refused|timeout"; then
_connectivity_hint=" (Likely TCP connectivity/firewall failure)"
fi
repair_blocked "GitLab migrations job is actively failing" \
"Job: ${_latest_job}. Pod: ${_latest_pod}. Status: ${_waiting_reason:-Terminated}. ExitCode: ${_exit_code:-unknown}. Restarts: ${_restarts}.
DB Config: ${_db_info}${_connectivity_hint}
Split-cluster: ${_split_cluster}
Diagnostics:
${_diag}"
fi
@ -260,6 +278,39 @@ check_host_resolves() {
return 0
}
check_db_connectivity() {
local target_host="$1"
local target_port="${2:-5432}"
local host_only="${target_host%:*}"
log "Validating GitLab database connectivity to ${target_host}:${target_port}..."
# DNS sanity check
if [[ "$host_only" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
log "Host ${host_only} is an IP address, skipping DNS check."
else
# In k8s mode, internal DNS resolution might fail from the script runner
# but work from within the cluster. We do a basic 'host' check as a hint.
if ! host "$host_only" >/dev/null 2>&1; then
warn "DNS resolution (host) failed for database host: ${host_only}. This may be expected if only resolvable in-cluster."
else
log "DNS resolution successful for ${host_only}"
fi
fi
# TCP check via temporary pod in the APP cluster namespace
log "Performing in-cluster TCP connectivity probe to ${host_only}:${target_port} (namespace: ${NAMESPACE})..."
if kubectl -n "$NAMESPACE" run db-probe \
--image=alpine:latest --restart=Never --rm --attach --timeout=30s \
--command -- sh -c "nc -zv -w 5 ${host_only} ${target_port}" >/dev/null 2>&1; then
log "TCP connectivity to ${host_only}:${target_port} SUCCESSFUL."
return 0
else
warn "TCP connectivity probe to ${host_only}:${target_port} FAILED."
return 1
fi
}
check_gitlab_pre_apply_blocked() {
# --- Registry Endpoint Placeholder Check ---
if [[ "$GARAGE_S3_ENDPOINT" == *"<private-db-cluster-garage-endpoint>"* ]]; then
@ -276,10 +327,27 @@ check_gitlab_pre_apply_blocked() {
repair_blocked "Redis host does not resolve" \
"Resource: REDIS_HOST. Value: ${REDIS_HOST} does not resolve. Fix: Ensure Redis is deployed and REDIS_NAMESPACE is correct."
fi
# --- Database Connectivity Check ---
if [[ "$MODE" == "k8s" ]]; then
if ! check_db_connectivity "$DB_HOST" "$DB_PORT"; then
local app_ctx="${APP_CLUSTER_KUBECONTEXT:-${KUBECONTEXT:-}}"
local db_ctx="${DB_CLUSTER_KUBECONTEXT:-}"
repair_blocked "GitLab database host is not reachable from APP cluster" \
"Host: ${DB_HOST}. Port: ${DB_PORT}. App Context: ${app_ctx}. DB Context: ${db_ctx}. Fix: Ensure cross-cluster networking (ILB) is functional."
fi
fi
}
get_gitlab_blocker_context() {
local ctx=""
local app_ctx="${APP_CLUSTER_KUBECONTEXT:-${KUBECONTEXT:-}}"
local db_ctx="${DB_CLUSTER_KUBECONTEXT:-}"
if [[ -n "$db_ctx" && "$db_ctx" != "$app_ctx" ]]; then
ctx+="Split-cluster (APP: ${app_ctx}, DB: ${db_ctx}). "
fi
ctx+="DB_HOST: ${DB_HOST}. "
local _last_mig=$(kubectl -n "$NAMESPACE" get jobs -l "app=migrations" --sort-by=.metadata.creationTimestamp -o jsonpath='{.items[-1:].metadata.name}' 2>/dev/null || true)
if [[ -n "$_last_mig" ]]; then
local _m_ts=$(kubectl -n "$NAMESPACE" get job "$_last_mig" -o jsonpath='{.metadata.creationTimestamp}' 2>/dev/null || true)
@ -626,6 +694,83 @@ kubectl_garage_admin() {
fi
}
db_kubectl() {
local db_ctx
db_ctx="$(resolve_db_cluster_context || true)"
if [[ -n "$db_ctx" ]]; then
command kubectl --context "$db_ctx" "$@"
else
kubectl "$@"
fi
}
ensure_cross_cluster_db_host() {
if [[ "$MODE" != "k8s" ]]; then
return 0
fi
local app_ctx db_ctx
app_ctx="${APP_CLUSTER_KUBECONTEXT:-${KUBECONTEXT:-}}"
if [[ -z "$app_ctx" ]]; then
app_ctx="$(kubectl config current-context 2>/dev/null || true)"
fi
db_ctx="$(resolve_db_cluster_context || true)"
if [[ -z "$db_ctx" || -z "$app_ctx" || "$db_ctx" == "$app_ctx" ]]; then
return 0
fi
local db_cluster_name="${CNPG_CLUSTER_NAME:-knoe-db}"
local db_ns="${KNOE_DB_NAMESPACE:-${DATABASE_NAMESPACE:-knoe-db}}"
local ilb_service="${GITLAB_DB_ILB_SERVICE:-knoe-db-rw-ilb}"
log "Split-cluster mode detected (APP: ${app_ctx}, DB: ${db_ctx})"
log "Ensuring cross-cluster Postgres ILB service '${ilb_service}' in namespace '${db_ns}'..."
db_kubectl -n "$db_ns" apply -f - <<EOF >/dev/null
apiVersion: v1
kind: Service
metadata:
name: ${ilb_service}
annotations:
networking.gke.io/load-balancer-type: "Internal"
spec:
type: LoadBalancer
selector:
cnpg.io/cluster: ${db_cluster_name}
cnpg.io/instanceRole: primary
ports:
- name: postgres
port: 5432
targetPort: 5432
protocol: TCP
EOF
local db_host=""
local attempts=0
log "Waiting for cross-cluster DB host (ILB IP/hostname)..."
while (( attempts < 60 )); do
attempts=$((attempts + 1))
db_host="$(db_kubectl -n "$db_ns" get svc "$ilb_service" -o jsonpath='{.status.loadBalancer.ingress[0].ip}' 2>/dev/null || true)"
if [[ -z "$db_host" ]]; then
db_host="$(db_kubectl -n "$db_ns" get svc "$ilb_service" -o jsonpath='{.status.loadBalancer.ingress[0].hostname}' 2>/dev/null || true)"
fi
if [[ -n "$db_host" ]]; then
break
fi
sleep 5
done
if [[ -z "$db_host" ]]; then
warn "Cross-cluster Postgres ILB '${ilb_service}' has no ingress address yet after 5 minutes."
return 0
fi
log "Successfully resolved cross-cluster DB host: ${db_host}"
GITLAB_CROSS_CLUSTER_DB_HOST="$db_host"
export GITLAB_CROSS_CLUSTER_DB_HOST
}
enforce_app_cluster_targeting
is_truthy() {
@ -865,10 +1010,13 @@ command -v helm >/dev/null || die "helm not found (required for GitLab Operat
# ---------------------------------------------------------------------------
DB_NAMESPACE="${KNOE_DB_NAMESPACE:-${DATABASE_NAMESPACE:-knoe-db}}"
CNPG_CLUSTER_NAME="${CNPG_CLUSTER_NAME:-${CLUSTER_NAME:-knoe-db}}"
ensure_cross_cluster_db_host
GITLAB_DB_NAME="${GITLAB_DB_NAME:-gitlabhq_production}"
GITLAB_DB_USER="${GITLAB_DB_USER:-gitlab}"
GITLAB_DB_PASSWORD="${GITLAB_DB_PASSWORD:-}"
DB_HOST="${CNPG_CLUSTER_NAME}-rw.${DB_NAMESPACE}.svc.cluster.local"
DB_HOST="${GITLAB_DB_HOST:-${GITLAB_CROSS_CLUSTER_DB_HOST:-${CNPG_CLUSTER_NAME}-rw.${DB_NAMESPACE}.svc.cluster.local}}"
DB_PORT="${GITLAB_DB_PORT:-5432}"
# ---------------------------------------------------------------------------