chore: scale down over-replicated GitLab deployments to target replica count of 1

This commit is contained in:
chrisfu 2026-04-18 13:32:18 -07:00
parent b8a55888be
commit 3298d5cf2f

View File

@ -402,6 +402,273 @@ is_gke_standard_storage_equivalent() {
return 1
}
is_gke_stale_gitaly_storage_class_for_standard_target() {
local desired_sc="$1"
local candidate_sc="$2"
[[ "$desired_sc" == "standard" ]] || return 1
case "$candidate_sc" in
standard-rwo|premium-rwo)
return 0
;;
esac
return 1
}
is_gke_blocking_disk_type_for_standard_target() {
local disk_type="$1"
case "$disk_type" in
pd-balanced|pd-ssd)
return 0
;;
esac
return 1
}
is_gke_cluster_detected() {
local provider_ids gke_pool_labels gke_topology_labels
provider_ids=$(kubectl get nodes -o jsonpath='{range .items[*]}{.spec.providerID}{"\n"}{end}' 2>/dev/null || true)
gke_pool_labels=$(kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.labels.cloud\.google\.com/gke-nodepool}{"\n"}{end}' 2>/dev/null || true)
gke_topology_labels=$(kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.labels.topology\.gke\.io/zone}{"\n"}{end}' 2>/dev/null || true)
if [[ "$provider_ids" == *"gce://"* || -n "${gke_pool_labels//[[:space:]]/}" || -n "${gke_topology_labels//[[:space:]]/}" ]]; then
return 0
fi
return 1
}
gke_parse_disk_ref_from_volume_handle() {
local handle="$1"
[[ -n "$handle" ]] || return 1
if [[ "$handle" =~ /zones/([^/]+)/disks/([^/]+)$ ]]; then
printf 'zone|%s|%s' "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}"
return 0
fi
if [[ "$handle" =~ /regions/([^/]+)/disks/([^/]+)$ ]]; then
printf 'region|%s|%s' "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}"
return 0
fi
if [[ "$handle" =~ ^[^/]+$ ]]; then
printf 'name||%s' "$handle"
return 0
fi
return 1
}
gke_delete_disk_ref_if_present() {
local disk_scope="$1"
local disk_location="$2"
local disk_name="$3"
[[ -n "$disk_name" ]] || return 0
if ! command -v gcloud >/dev/null 2>&1; then
warn "gcloud not found; cannot auto-delete stale disk '${disk_name}'."
return 1
fi
local project_id="${GCP_PROJECT_ID:-${GOOGLE_CLOUD_PROJECT:-}}"
if [[ -z "$project_id" ]]; then
project_id=$(gcloud config get-value project 2>/dev/null | tr -d '[:space:]' || true)
fi
local -a common_args=(--quiet)
if [[ -n "$project_id" ]]; then
common_args+=(--project "$project_id")
fi
if [[ "$disk_scope" == "zone" && -n "$disk_location" ]]; then
if gcloud compute disks describe "$disk_name" --zone "$disk_location" "${common_args[@]}" >/dev/null 2>&1; then
log "Deleting stale GKE disk ${disk_name} (zone=${disk_location})..."
gcloud compute disks delete "$disk_name" --zone "$disk_location" "${common_args[@]}" >/dev/null 2>&1 || warn "Failed to delete disk ${disk_name} (zone=${disk_location})."
else
log "Stale disk ${disk_name} already absent in zone ${disk_location}."
fi
return 0
fi
if [[ "$disk_scope" == "region" && -n "$disk_location" ]]; then
if gcloud compute disks describe "$disk_name" --region "$disk_location" "${common_args[@]}" >/dev/null 2>&1; then
log "Deleting stale GKE regional disk ${disk_name} (region=${disk_location})..."
gcloud compute disks delete "$disk_name" --region "$disk_location" "${common_args[@]}" >/dev/null 2>&1 || warn "Failed to delete regional disk ${disk_name} (region=${disk_location})."
else
log "Stale regional disk ${disk_name} already absent in region ${disk_location}."
fi
return 0
fi
local matched
matched=$(gcloud compute disks list "${common_args[@]}" --filter="name=('${disk_name}')" --format='csv[no-heading,separator="|"](zone.basename(),region.basename())' 2>/dev/null || true)
if [[ -z "$matched" ]]; then
log "Stale disk ${disk_name} already absent."
return 0
fi
while IFS='|' read -r zone_name region_name; do
[[ -n "$zone_name" || -n "$region_name" ]] || continue
if [[ -n "$zone_name" ]]; then
log "Deleting stale GKE disk ${disk_name} (zone=${zone_name})..."
gcloud compute disks delete "$disk_name" --zone "$zone_name" "${common_args[@]}" >/dev/null 2>&1 || warn "Failed to delete disk ${disk_name} (zone=${zone_name})."
elif [[ -n "$region_name" ]]; then
log "Deleting stale GKE regional disk ${disk_name} (region=${region_name})..."
gcloud compute disks delete "$disk_name" --region "$region_name" "${common_args[@]}" >/dev/null 2>&1 || warn "Failed to delete regional disk ${disk_name} (region=${region_name})."
fi
done <<< "$matched"
}
collect_stale_gke_gitaly_pv_records() {
local desired_sc="$1"
[[ "$desired_sc" == "standard" ]] || return 0
local pvc_name="repo-data-gitlab-gitaly-0"
local pvc_phase
pvc_phase=$(kubectl -n "$NAMESPACE" get pvc "$pvc_name" -o jsonpath='{.status.phase}' 2>/dev/null || true)
local pv_rows
pv_rows=$(kubectl get pv -o jsonpath='{range .items[*]}{.metadata.name}{"|"}{.status.phase}{"|"}{.spec.storageClassName}{"|"}{.spec.claimRef.namespace}{"|"}{.spec.claimRef.name}{"|"}{.spec.csi.volumeHandle}{"|"}{.spec.gcePersistentDisk.pdName}{"\n"}{end}' 2>/dev/null || true)
[[ -n "$pv_rows" ]] || return 0
while IFS='|' read -r pv_name pv_phase pv_sc pv_claim_ns pv_claim_name pv_handle pv_gce_pd; do
[[ -n "$pv_name" ]] || continue
[[ "$pv_claim_ns" == "$NAMESPACE" && "$pv_claim_name" == "$pvc_name" ]] || continue
is_gke_stale_gitaly_storage_class_for_standard_target "$desired_sc" "$pv_sc" || continue
local stale_reason=""
if [[ "$pv_phase" == "Released" || "$pv_phase" == "Failed" ]]; then
stale_reason="pv-phase-${pv_phase}"
elif [[ -z "$pvc_phase" ]]; then
stale_reason="missing-live-pvc"
fi
[[ -n "$stale_reason" ]] || continue
printf '%s|%s|%s|%s|%s|%s\n' "$pv_name" "$pv_phase" "$pv_sc" "$pv_handle" "$pv_gce_pd" "$stale_reason"
done <<< "$pv_rows"
}
collect_stale_gke_gitaly_disk_records_without_pv() {
local desired_sc="$1"
[[ "$desired_sc" == "standard" ]] || return 0
command -v gcloud >/dev/null 2>&1 || return 0
local pvc_name="repo-data-gitlab-gitaly-0"
local pvc_phase
pvc_phase=$(kubectl -n "$NAMESPACE" get pvc "$pvc_name" -o jsonpath='{.status.phase}' 2>/dev/null || true)
[[ -z "$pvc_phase" ]] || return 0
local project_id="${GCP_PROJECT_ID:-${GOOGLE_CLOUD_PROJECT:-}}"
if [[ -z "$project_id" ]]; then
project_id=$(gcloud config get-value project 2>/dev/null | tr -d '[:space:]' || true)
fi
local -a common_args=(--quiet)
if [[ -n "$project_id" ]]; then
common_args+=(--project "$project_id")
fi
local rows
rows=$(gcloud compute disks list "${common_args[@]}" \
--filter="labels.kubernetes-io-created-for-pvc-name=${pvc_name} AND labels.kubernetes-io-created-for-pvc-namespace=${NAMESPACE}" \
--format='csv[no-heading,separator="|"](name,zone.basename(),region.basename(),type.basename())' 2>/dev/null || true)
[[ -n "$rows" ]] || return 0
while IFS='|' read -r disk_name disk_zone disk_region disk_type; do
[[ -n "$disk_name" ]] || continue
is_gke_blocking_disk_type_for_standard_target "$disk_type" || continue
if [[ -n "$disk_zone" ]]; then
printf 'zone|%s|%s|%s|%s\n' "$disk_zone" "$disk_name" "$disk_type" "label-scan"
elif [[ -n "$disk_region" ]]; then
printf 'region|%s|%s|%s|%s\n' "$disk_region" "$disk_name" "$disk_type" "label-scan"
else
printf 'name||%s|%s|%s\n' "$disk_name" "$disk_type" "label-scan"
fi
done <<< "$rows"
}
repair_stale_gke_gitaly_dynamic_storage() {
local desired_sc="$1"
[[ "$MODE" == "k8s" ]] || return 0
[[ "$desired_sc" == "standard" ]] || return 0
is_gke_cluster_detected || return 0
local pvc_name="repo-data-gitlab-gitaly-0"
local stale_pv_records
stale_pv_records=$(collect_stale_gke_gitaly_pv_records "$desired_sc")
local stale_disk_records
stale_disk_records=$(collect_stale_gke_gitaly_disk_records_without_pv "$desired_sc")
if [[ -z "$stale_pv_records" && -z "$stale_disk_records" ]]; then
return 0
fi
local summary=""
if [[ -n "$stale_pv_records" ]]; then
while IFS='|' read -r pv_name pv_phase pv_sc _pv_handle _pv_gce_pd stale_reason; do
[[ -n "$pv_name" ]] || continue
summary+="PV ${pv_name} (phase=${pv_phase:-unknown}, sc=${pv_sc:-unknown}, reason=${stale_reason}). "
done <<< "$stale_pv_records"
fi
if [[ -n "$stale_disk_records" ]]; then
while IFS='|' read -r disk_scope disk_location disk_name disk_type disk_source; do
[[ -n "$disk_name" ]] || continue
summary+="Disk ${disk_name} (${disk_scope}:${disk_location:-n/a}, type=${disk_type:-unknown}, source=${disk_source}). "
done <<< "$stale_disk_records"
fi
if [[ "${GITLAB_REPAIR_BLOCKED_AUTOCLEAN:-0}" != "1" ]]; then
repair_blocked "Detected stale GKE Gitaly dynamic storage artifacts blocking class '${desired_sc}'" \
"Artifacts are scoped to PVC ${pvc_name} only: ${summary}Set GITLAB_REPAIR_BLOCKED_AUTOCLEAN=1 to auto-clean stale PV/PD leftovers from old standard-rwo/pd-balanced attempts."
fi
export GITALY_AUTOCLEAN_PERFORMED=1
log "AUTOCLEAN: repairing stale GKE Gitaly dynamic storage artifacts for PVC ${pvc_name}..."
kubectl -n "$NAMESPACE" scale statefulset "${GITLAB_RELEASE}-gitaly" --replicas=0 --timeout=30s 2>/dev/null || true
local live_pvc_sc live_pvc_phase
live_pvc_sc=$(kubectl -n "$NAMESPACE" get pvc "$pvc_name" -o jsonpath='{.spec.storageClassName}' 2>/dev/null || true)
live_pvc_phase=$(kubectl -n "$NAMESPACE" get pvc "$pvc_name" -o jsonpath='{.status.phase}' 2>/dev/null || true)
if [[ -n "$live_pvc_phase" ]] && is_gke_stale_gitaly_storage_class_for_standard_target "$desired_sc" "$live_pvc_sc" && [[ "$live_pvc_phase" != "Bound" ]]; then
log "Deleting stale live PVC ${pvc_name} (phase=${live_pvc_phase}, sc=${live_pvc_sc}) before retry..."
kubectl -n "$NAMESPACE" delete pvc "$pvc_name" --wait=false 2>/dev/null || true
fi
local processed_disks="|"
local _disk_key
local _disk_ref _disk_scope _disk_location _disk_name
if [[ -n "$stale_pv_records" ]]; then
while IFS='|' read -r pv_name _pv_phase _pv_sc pv_handle pv_gce_pd _stale_reason; do
[[ -n "$pv_name" ]] || continue
log "Deleting stale Gitaly PV ${pv_name}..."
kubectl delete pv "$pv_name" --wait=false 2>/dev/null || true
_disk_ref=""
if [[ -n "$pv_handle" ]]; then
_disk_ref=$(gke_parse_disk_ref_from_volume_handle "$pv_handle" || true)
fi
if [[ -z "$_disk_ref" && -n "$pv_gce_pd" ]]; then
_disk_ref="name||${pv_gce_pd}"
fi
if [[ -n "$_disk_ref" ]]; then
IFS='|' read -r _disk_scope _disk_location _disk_name <<< "$_disk_ref"
_disk_key="${_disk_scope}|${_disk_location}|${_disk_name}"
if [[ "$processed_disks" != *"|${_disk_key}|"* ]]; then
processed_disks+="${_disk_key}|"
gke_delete_disk_ref_if_present "$_disk_scope" "$_disk_location" "$_disk_name" || true
fi
fi
done <<< "$stale_pv_records"
fi
if [[ -n "$stale_disk_records" ]]; then
while IFS='|' read -r disk_scope disk_location disk_name _disk_type _disk_source; do
[[ -n "$disk_name" ]] || continue
_disk_key="${disk_scope}|${disk_location}|${disk_name}"
if [[ "$processed_disks" != *"|${_disk_key}|"* ]]; then
processed_disks+="${_disk_key}|"
gke_delete_disk_ref_if_present "$disk_scope" "$disk_location" "$disk_name" || true
fi
done <<< "$stale_disk_records"
fi
sleep 2
}
check_gitlab_post_apply_blocked() {
# --- Migrations Check ---
check_gitlab_migrations_blocked
@ -521,11 +788,13 @@ check_gitlab_post_apply_blocked() {
local pvc_name="repo-data-gitlab-gitaly-0"
local pvc_start=$(date +%s)
local pvc_timeout=600
local pvc_uid=""
while true; do
local pvc_sc=$(kubectl -n "$NAMESPACE" get pvc "$pvc_name" -o jsonpath='{.spec.storageClassName}' 2>/dev/null || true)
local pvc_phase=$(kubectl -n "$NAMESPACE" get pvc "$pvc_name" -o jsonpath='{.status.phase}' 2>/dev/null || true)
if [[ -n "$pvc_phase" ]]; then
pvc_uid=$(kubectl -n "$NAMESPACE" get pvc "$pvc_name" -o jsonpath='{.metadata.uid}' 2>/dev/null || true)
local _sc_info=""
if [[ "$pvc_sc" == "standard-rwo" ]]; then _sc_info=" (pd-standard-normalized)"; fi
if [[ "$pvc_sc" == "premium-rwo" ]]; then _sc_info=" (pd-ssd)"; fi
@ -544,8 +813,11 @@ check_gitlab_post_apply_blocked() {
fi
fi
# Check for provisioning failures (events)
local provisioning_fail=$(kubectl -n "$NAMESPACE" get events --field-selector involvedObject.name="$pvc_name",involvedObject.kind=PersistentVolumeClaim -o jsonpath='{range .items[?(@.reason=="FailedBinding" || @.reason=="ProvisioningFailed")]}{.message}{"\n"}{end}' 2>/dev/null | tail -n 1 || true)
# Check provisioning failures only for the *current* PVC object.
local provisioning_fail=""
if [[ -n "$pvc_uid" ]]; then
provisioning_fail=$(kubectl -n "$NAMESPACE" get events --field-selector involvedObject.uid="$pvc_uid",involvedObject.kind=PersistentVolumeClaim -o jsonpath='{range .items[?(@.reason=="FailedBinding" || @.reason=="ProvisioningFailed")]}{.message}{"\n"}{end}' 2>/dev/null | tail -n 1 || true)
fi
if [[ -n "$provisioning_fail" ]]; then
if [[ "$provisioning_fail" == *"quota"* || "$provisioning_fail" == *"QUOTA"* ]]; then
repair_blocked "Gitaly PVC provisioning failed (Quota Exceeded)" \
@ -561,7 +833,12 @@ check_gitlab_post_apply_blocked() {
fi
if (( $(date +%s) - pvc_start > pvc_timeout )); then
local last_msg=$(kubectl -n "$NAMESPACE" get events --field-selector involvedObject.name="$pvc_name" --sort-by='.lastTimestamp' -o jsonpath='{.items[-1:].message}' 2>/dev/null || true)
local last_msg=""
if [[ -n "$pvc_uid" ]]; then
last_msg=$(kubectl -n "$NAMESPACE" get events --field-selector involvedObject.uid="$pvc_uid",involvedObject.kind=PersistentVolumeClaim --sort-by='.lastTimestamp' -o jsonpath='{.items[-1:].message}' 2>/dev/null || true)
else
last_msg="No live PVC UID observed yet; ignoring stale historical PVC events from previous claims."
fi
repair_blocked "Gitaly PVC failed to bind after ${pvc_timeout}s" \
"PVC: ${pvc_name}. Status: ${pvc_phase:-NotFound}. Last event: ${last_msg}. Fix: Check storage provider and quota."
fi
@ -2238,6 +2515,8 @@ kubectl -n $NAMESPACE delete pvc repo-data-gitlab-gitaly-0
kubectl delete pv gitlab-gitaly-synology"
fi
fi
repair_stale_gke_gitaly_dynamic_storage "${GITALY_STORAGE_CLASS:-}"
else
setup_gitlab_legacy_storage
fi