카탈로그 앱 내장 bitnami postgresql → cnpg-cluster 전환 (1차 5개 차트)
이슈 #9에서 결정된 CloudNativePG 채택을 카탈로그 앱에 확장 적용한다. airflow/lakekeeper/mlflow/superset/flowise 5개 차트가 내장하던 bitnami postgresql 서브차트를 끄고 앱 전용 cnpg-cluster 인스턴스를 외부 DB로 쓰도록 전환했다. 5개 모두 dev 클러스터 격리 네임스페이스에서 배포 테스트로 실측 검증했다. gitea/keycloak/dnsup 는 서브차트가 아니라 공유 postgresql-ha 를 외부 참조하며 paasup/dipup 레포 관리 대상이라 제외했다 — 인수인계 문서만 추가했다. 배포 구조: - ArgoCD ApplicationSet 으로 DB(syncWave 0) → 앱(syncWave 1) 순서를 보장한다. 기존 openmetadata/victoria-metrics 관례를 따랐다. cnpg-cluster 차트는 범용 상태로 유지하고 앱별 값은 manifests/applicationset/<app>/ 에 둔다. 검증 중 발견해 함께 고친 문제: - lakekeeper: cnpg 의 -ro 는 replica 전용이라 instances:1 에서 엔드포인트가 0개다. 읽기 연결을 -r(전체 라운드로빈)로 교체했다. - airflow/superset: ingressClassName 누락 + kong 애노테이션 잔존으로 이 클러스터(apisix 전용)에서 ingress 접근이 아예 불가능했다. apisix + regex path 로 교체했다. - 배포 테스트가 PV 만 지우고 Longhorn Volume CR 을 남겨 storageScheduled 가 누적됐다(orphan 112개 ~1TB 로 배포 차단). 두 스크립트의 정리 로직을 고쳤다. 재사용 구조화: - .claude/skills/chart-to-cnpg/ 신규. 남은 4개 차트(langflow-ide, langfuse, litellm, nemo)에 같은 절차를 재사용한다. flowise 에 실제 적용해 검증했다. - 배포 테스트 공통 절차는 .claude/deploy-test-procedure.md, 환경 함정은 .claude/pitfalls.md 로 단일화하고 앱별 README 는 참조만 남겼다. 관련: #9, #14 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+291
@@ -0,0 +1,291 @@
|
||||
#!/usr/bin/env bash
|
||||
# =============================================================================
|
||||
# deploy-test-app-with-cnpg.sh
|
||||
# 내장 bitnami postgresql 서브차트를 끄고 전용 cnpg-cluster 인스턴스를 외부 DB로
|
||||
# 쓰도록 바꾼 카탈로그 앱(airflow/lakekeeper/mlflow/superset)이 실제로 그 DB에
|
||||
# 연결해 기동하는지 격리된 테스트 네임스페이스에서 확인한다.
|
||||
#
|
||||
# scripts/deploy-test/deploy-test-cnpg-cluster.sh 와 같은 원칙을 따른다 —
|
||||
# operator(cloudnative-pg)는 상시 컴포넌트로 재사용하고, 앱/DB 릴리스와 테스트
|
||||
# 네임스페이스는 매 실행 재생성한다. 정리는 성공/실패와 무관하게 항상 수행한다(trap).
|
||||
#
|
||||
# 이 스크립트가 확인하는 것은 "DB 연결이 되는가"이지 앱의 전체 기능 검증이 아니다.
|
||||
# ingress/cert-manager/OIDC(keycloak) 등 DB와 무관한 의존성 때문에 앱 차트의
|
||||
# `helm install --wait`가 타임아웃할 수 있어, 앱 설치는 --wait 없이 진행하고
|
||||
# 파드 상태 + 로그의 DB 연결 실패 패턴 부재로 판단한다.
|
||||
#
|
||||
# 사용:
|
||||
# APP_CHART_DIR=manifests/helm/airflow/1.16.0 \
|
||||
# APP_RELEASE=airflow \
|
||||
# DB_CUSTOM_VALUES=manifests/applicationset/airflow/1.16.0/airflow-db-values.yaml \
|
||||
# DB_SECRET_USER=airflow DB_SECRET_PASSWORD=airflow \
|
||||
# TEST_NAMESPACE=airflow-test-build \
|
||||
# bash scripts/deploy-test/deploy-test-app-with-cnpg.sh /tmp/deploy-test-out
|
||||
#
|
||||
# 환경변수:
|
||||
# APP_CHART_DIR (필수) 앱 차트 경로 (예: manifests/helm/airflow/1.16.0)
|
||||
# APP_RELEASE (필수) 앱 helm 릴리스명
|
||||
# APP_CUSTOM_VALUES 기본 "$APP_CHART_DIR/custom-values.yaml"
|
||||
# DB_CUSTOM_VALUES (필수) cnpg-cluster용 앱 전용 오버레이 — 각 앱의
|
||||
# manifests/applicationset/<app>/<version>/<app>-db-values.yaml
|
||||
# (ArgoCD ApplicationSet의 valuesPath와 동일한 파일)
|
||||
# DB_RELEASE 기본 "${APP_RELEASE}-db"
|
||||
# DB_SECRET_NAME 기본 "${DB_RELEASE}-app-secret" (각 앱 <app>-db-values.yaml의
|
||||
# bootstrap.initdb.secretName과 반드시 일치해야 한다)
|
||||
# DB_SECRET_USER (필수) DB owner 계정명
|
||||
# DB_SECRET_PASSWORD (필수) DB owner 비밀번호 — 앱 custom-values.yaml의 평문 값과 동일해야 함
|
||||
# OPERATOR_CHART_DIR 기본 manifests/helm/cloudnative-pg/0.29.0
|
||||
# OPERATOR_NAMESPACE 기본 cnpg-system (상시 — 이 스크립트가 지우지 않는다)
|
||||
# OPERATOR_RELEASE 기본 cnpg
|
||||
# TEST_NAMESPACE (필수) 격리된 테스트 네임스페이스 (매 실행 재생성)
|
||||
# DB_WAIT_TIMEOUT 기본 300 (초) — Cluster healthy 대기 한도
|
||||
# APP_WAIT_SECONDS 기본 180 (초) — 앱 파드 상태 관찰 시간
|
||||
# APP_EXTRA_VALUES 선택. custom-values.yaml 위에 추가로 얹을 배포-테스트 전용 values 파일
|
||||
# (예: 외부 keycloak 등 이 테스트 환경에 없는 의존성을 끄는 용도).
|
||||
# 카탈로그의 실제 권장 설정(custom-values.yaml)은 건드리지 않는다 —
|
||||
# scripts/deploy-test/fixtures/ 에 보관한다.
|
||||
# APP_POD_SELECTOR (강력 권장) DB에 실제로 연결하는 파드를 가리키는 label selector
|
||||
# (예: airflow "component=webserver", mlflow
|
||||
# "app.kubernetes.io/component=tracking", superset "app=superset",
|
||||
# lakekeeper "app.kubernetes.io/component=catalog"). 지정하면 이
|
||||
# selector 에 매치되는 파드가 Running+Ready 가 될 때까지만 기다린다 —
|
||||
# git-sync/ingress 등 DB 와 무관한 다른 파드의 상태는 무시한다.
|
||||
# 미지정 시 네임스페이스의 아무 파드나 1개 Running 이면 통과로 간주하는
|
||||
# 느슨한 판정으로 대체된다(권장하지 않음 — 실제 DB 소비 파드를 못 볼 수 있다).
|
||||
#
|
||||
# 산출물 (OUT_DIR): deploy-test.log
|
||||
# 종료 코드: 0 = DB 연결 성공(파드 Running + 로그에 DB 연결 실패 패턴 없음), 그 외 = 실패.
|
||||
# 정리는 종료 코드와 무관하게 항상 수행된다.
|
||||
# =============================================================================
|
||||
set -uo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
|
||||
OUT_DIR="${1:?사용법: deploy-test-app-with-cnpg.sh <OUT_DIR>}"
|
||||
mkdir -p "$OUT_DIR"
|
||||
LOG="$OUT_DIR/deploy-test.log"
|
||||
: > "$LOG"
|
||||
|
||||
APP_CHART_DIR="${APP_CHART_DIR:?APP_CHART_DIR 환경변수가 필요하다 (앱 차트 경로)}"
|
||||
APP_RELEASE="${APP_RELEASE:?APP_RELEASE 환경변수가 필요하다}"
|
||||
APP_CUSTOM_VALUES="${APP_CUSTOM_VALUES:-$APP_CHART_DIR/custom-values.yaml}"
|
||||
DB_CUSTOM_VALUES="${DB_CUSTOM_VALUES:?DB_CUSTOM_VALUES 환경변수가 필요하다 (cnpg-cluster 앱 전용 오버레이)}"
|
||||
DB_RELEASE="${DB_RELEASE:-${APP_RELEASE}-db}"
|
||||
DB_SECRET_NAME="${DB_SECRET_NAME:-${DB_RELEASE}-app-secret}"
|
||||
DB_SECRET_USER="${DB_SECRET_USER:?DB_SECRET_USER 환경변수가 필요하다}"
|
||||
DB_SECRET_PASSWORD="${DB_SECRET_PASSWORD:?DB_SECRET_PASSWORD 환경변수가 필요하다}"
|
||||
OPERATOR_CHART_DIR="${OPERATOR_CHART_DIR:-$REPO_ROOT/manifests/helm/cloudnative-pg/0.29.0}"
|
||||
OPERATOR_NAMESPACE="${OPERATOR_NAMESPACE:-cnpg-system}"
|
||||
OPERATOR_RELEASE="${OPERATOR_RELEASE:-cnpg}"
|
||||
TEST_NAMESPACE="${TEST_NAMESPACE:?TEST_NAMESPACE 환경변수가 필요하다 (격리된 테스트 네임스페이스)}"
|
||||
DB_WAIT_TIMEOUT="${DB_WAIT_TIMEOUT:-300}"
|
||||
# 이미지가 큰 앱(flowise 등)은 최초 pull 에만 5분 넘게 걸려 300s 로는 ImagePullBackOff 상태에서
|
||||
# 타임아웃한다(실측). 캐시된 뒤에는 1분 내로 끝나므로 넉넉히 잡아도 손해가 없다.
|
||||
APP_WAIT_SECONDS="${APP_WAIT_SECONDS:-600}"
|
||||
APP_EXTRA_VALUES="${APP_EXTRA_VALUES:-}"
|
||||
APP_POD_SELECTOR="${APP_POD_SELECTOR:-}"
|
||||
CNPG_CLUSTER_CHART_DIR="${CNPG_CLUSTER_CHART_DIR:-$REPO_ROOT/manifests/helm/cnpg-cluster/1.0.0}"
|
||||
|
||||
log() { echo "$(date -u +%H:%M:%S) $*" | tee -a "$LOG"; }
|
||||
|
||||
log "== 사전 확인 =="
|
||||
for t in kubectl helm python3; do
|
||||
command -v "$t" >/dev/null || { log "::error:: $t 없음"; exit 2; }
|
||||
done
|
||||
[ -d "$OPERATOR_CHART_DIR" ] || { log "::error:: operator 차트 없음: $OPERATOR_CHART_DIR"; exit 2; }
|
||||
[ -d "$APP_CHART_DIR" ] || { log "::error:: 앱 차트 없음: $APP_CHART_DIR"; exit 2; }
|
||||
[ -f "$APP_CUSTOM_VALUES" ] || { log "::error:: 앱 custom-values 없음: $APP_CUSTOM_VALUES"; exit 2; }
|
||||
[ -f "$DB_CUSTOM_VALUES" ] || { log "::error:: DB custom-values 없음: $DB_CUSTOM_VALUES"; exit 2; }
|
||||
if ! kubectl get ns >/dev/null 2>>"$LOG"; then
|
||||
log "::error:: 클러스터 연결 실패 — kubeconfig/네트워크 확인"
|
||||
exit 2
|
||||
fi
|
||||
log " OK (kubectl context: $(kubectl config current-context 2>/dev/null))"
|
||||
|
||||
# --- operator: 상시 컴포넌트. 없으면 1회 설치, 있으면 재사용 --------------------
|
||||
ensure_operator() {
|
||||
if helm status "$OPERATOR_RELEASE" -n "$OPERATOR_NAMESPACE" >/dev/null 2>>"$LOG"; then
|
||||
log "== operator: 기존 릴리스 재사용 ($OPERATOR_RELEASE/$OPERATOR_NAMESPACE) =="
|
||||
return 0
|
||||
fi
|
||||
if kubectl get validatingwebhookconfiguration cnpg-validating-webhook-configuration >/dev/null 2>&1; then
|
||||
owner_ns="$(kubectl get validatingwebhookconfiguration cnpg-validating-webhook-configuration \
|
||||
-o jsonpath='{.metadata.annotations.meta\.helm\.sh/release-namespace}' 2>/dev/null)"
|
||||
owner_rel="$(kubectl get validatingwebhookconfiguration cnpg-validating-webhook-configuration \
|
||||
-o jsonpath='{.metadata.annotations.meta\.helm\.sh/release-name}' 2>/dev/null)"
|
||||
if [ "$owner_ns" != "$OPERATOR_NAMESPACE" ] || [ "$owner_rel" != "$OPERATOR_RELEASE" ]; then
|
||||
log "::error:: 다른 CNPG operator 가 이미 클러스터에 있다 (release=$owner_rel ns=$owner_ns)."
|
||||
exit 3
|
||||
fi
|
||||
fi
|
||||
log "== operator: 신규 설치 ($OPERATOR_RELEASE/$OPERATOR_NAMESPACE, 차트 기본값) =="
|
||||
helm install "$OPERATOR_RELEASE" "$OPERATOR_CHART_DIR" \
|
||||
-n "$OPERATOR_NAMESPACE" --create-namespace --wait --timeout 600s \
|
||||
>>"$LOG" 2>&1 || { log "::error:: operator 설치 실패 — $LOG 확인"; exit 1; }
|
||||
log " OK"
|
||||
}
|
||||
|
||||
# --- 테스트 네임스페이스 정리 (선-정리·후-정리 공용) --------------------------
|
||||
cleanup_test_ns() {
|
||||
kubectl get ns "$TEST_NAMESPACE" >/dev/null 2>&1 || return 0
|
||||
|
||||
log "== 정리: $TEST_NAMESPACE =="
|
||||
if helm status "$APP_RELEASE" -n "$TEST_NAMESPACE" >/dev/null 2>&1; then
|
||||
helm uninstall "$APP_RELEASE" -n "$TEST_NAMESPACE" >>"$LOG" 2>&1
|
||||
fi
|
||||
if helm status "$DB_RELEASE" -n "$TEST_NAMESPACE" >/dev/null 2>&1; then
|
||||
helm uninstall "$DB_RELEASE" -n "$TEST_NAMESPACE" >>"$LOG" 2>&1
|
||||
fi
|
||||
kubectl -n "$TEST_NAMESPACE" delete pvc --all --wait --timeout=120s >>"$LOG" 2>&1
|
||||
|
||||
# storageClass longhorn은 reclaimPolicy Retain이라 PVC 삭제만으로는 볼륨이 안 지워진다
|
||||
# (.claude/pitfalls.md) — 이 네임스페이스가 남긴 PV만 골라 지운다.
|
||||
local pv
|
||||
for pv in $(kubectl get pv -o json 2>/dev/null | python3 -c "
|
||||
import json,sys
|
||||
d=json.load(sys.stdin)
|
||||
for p in d.get('items', []):
|
||||
cr = p['spec'].get('claimRef') or {}
|
||||
if cr.get('namespace') == '$TEST_NAMESPACE':
|
||||
print(p['metadata']['name'])
|
||||
" 2>/dev/null); do
|
||||
kubectl delete pv "$pv" --ignore-not-found >>"$LOG" 2>&1 \
|
||||
|| log "::warning:: PV $pv 삭제 실패 — 수동 확인 필요"
|
||||
# k8s PV 오브젝트를 지워도 Longhorn 자체의 Volume 커스텀리소스는 남는다(실측 —
|
||||
# storageScheduled가 계속 누적돼 결국 "insufficient storage"로 다른 배포가 막힘).
|
||||
# PV와 이름이 같은 volumes.longhorn.io를 명시적으로 지워야 실제 디스크가 반환된다.
|
||||
kubectl -n longhorn-system delete volumes.longhorn.io "$pv" --ignore-not-found >>"$LOG" 2>&1
|
||||
done
|
||||
|
||||
kubectl delete ns "$TEST_NAMESPACE" --wait --timeout=120s >>"$LOG" 2>&1
|
||||
log " 완료"
|
||||
}
|
||||
|
||||
RESULT=1
|
||||
trap 'cleanup_test_ns' EXIT
|
||||
|
||||
ensure_operator
|
||||
cleanup_test_ns # 직전 실행이 비정상 종료했을 경우를 대비한 선-정리
|
||||
|
||||
log "== 테스트 네임스페이스 생성: $TEST_NAMESPACE =="
|
||||
kubectl create ns "$TEST_NAMESPACE" >>"$LOG" 2>&1
|
||||
|
||||
log "== DB 계정 시크릿 생성: $DB_SECRET_NAME (user=$DB_SECRET_USER) =="
|
||||
kubectl -n "$TEST_NAMESPACE" create secret generic "$DB_SECRET_NAME" \
|
||||
--from-literal=username="$DB_SECRET_USER" --from-literal=password="$DB_SECRET_PASSWORD" \
|
||||
>>"$LOG" 2>&1 || { log "::error:: 시크릿 생성 실패"; exit 1; }
|
||||
|
||||
log "== DB: cnpg-cluster 설치 ($DB_RELEASE/$TEST_NAMESPACE) =="
|
||||
# DB_CUSTOM_VALUES는 ArgoCD ApplicationSet용 템플릿(manifests/applicationset/<app>/<version>/
|
||||
# <app>-db-values.yaml)이라 secretName이 "$INFISICAL_SECRET" 플레이스홀더로 되어 있다 —
|
||||
# dip-console이 실제 배포 시 렌더링해 치환하는 값이다. 여기서는 위에서 실제로 만든
|
||||
# $DB_SECRET_NAME 시크릿으로 --set 오버라이드한다.
|
||||
if ! helm install "$DB_RELEASE" "$CNPG_CLUSTER_CHART_DIR" \
|
||||
-f "$CNPG_CLUSTER_CHART_DIR/custom-values.yaml" -f "$DB_CUSTOM_VALUES" \
|
||||
--set "bootstrap.initdb.secretName=$DB_SECRET_NAME" \
|
||||
-n "$TEST_NAMESPACE" --wait --timeout "${DB_WAIT_TIMEOUT}s" \
|
||||
>>"$LOG" 2>&1; then
|
||||
log "::error:: cnpg-cluster 설치 실패 — $LOG 확인"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "== DB: Cluster healthy 대기 (최대 ${DB_WAIT_TIMEOUT}s) =="
|
||||
# FQN 필수 — kubectl get cluster 는 Rancher/CAPI 의 동명 CRD 와 충돌한다(.claude/pitfalls.md)
|
||||
instances="$(kubectl -n "$TEST_NAMESPACE" get clusters.postgresql.cnpg.io "$DB_RELEASE" \
|
||||
-o jsonpath='{.spec.instances}' 2>/dev/null)"
|
||||
deadline=$((SECONDS + DB_WAIT_TIMEOUT))
|
||||
phase=""
|
||||
ready=0
|
||||
while [ "$SECONDS" -lt "$deadline" ]; do
|
||||
phase="$(kubectl -n "$TEST_NAMESPACE" get clusters.postgresql.cnpg.io "$DB_RELEASE" \
|
||||
-o jsonpath='{.status.phase}' 2>/dev/null)"
|
||||
ready="$(kubectl -n "$TEST_NAMESPACE" get clusters.postgresql.cnpg.io "$DB_RELEASE" \
|
||||
-o jsonpath='{.status.readyInstances}' 2>/dev/null)"
|
||||
log " phase=$phase ready=${ready:-0}/$instances"
|
||||
[ "$phase" = "Cluster in healthy state" ] && [ "${ready:-0}" = "$instances" ] && break
|
||||
sleep 10
|
||||
done
|
||||
if [ "$phase" != "Cluster in healthy state" ] || [ "${ready:-0}" != "$instances" ]; then
|
||||
log "::error:: 타임아웃 — DB Cluster 가 healthy 상태에 도달하지 못함"
|
||||
kubectl -n "$TEST_NAMESPACE" get pods -l "cnpg.io/cluster=$DB_RELEASE" >>"$LOG" 2>&1
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "== DB: 부트스트랩 확인 (role/database) =="
|
||||
primary_pod="$(kubectl -n "$TEST_NAMESPACE" get pods \
|
||||
-l "cnpg.io/cluster=$DB_RELEASE,cnpg.io/instanceRole=primary" \
|
||||
-o jsonpath='{.items[0].metadata.name}' 2>/dev/null)"
|
||||
kubectl -n "$TEST_NAMESPACE" exec "$primary_pod" -c postgres -- \
|
||||
psql -U postgres -Atc "\du" >>"$LOG" 2>&1
|
||||
kubectl -n "$TEST_NAMESPACE" exec "$primary_pod" -c postgres -- \
|
||||
psql -U postgres -Atc "\l" >>"$LOG" 2>&1
|
||||
|
||||
log "== 앱: 설치 ($APP_RELEASE/$TEST_NAMESPACE, --wait 없이 진행) =="
|
||||
# ingress/cert-manager/OIDC 등 DB 와 무관한 의존성 때문에 helm --wait 가 타임아웃할 수 있어
|
||||
# 설치만 하고 파드 상태는 아래에서 직접 관찰한다.
|
||||
app_values_args=(-f "$APP_CUSTOM_VALUES")
|
||||
if [ -n "$APP_EXTRA_VALUES" ]; then
|
||||
[ -f "$APP_EXTRA_VALUES" ] || { log "::error:: APP_EXTRA_VALUES 파일 없음: $APP_EXTRA_VALUES"; exit 2; }
|
||||
log " 추가 배포-테스트 전용 values 사용: $APP_EXTRA_VALUES"
|
||||
app_values_args+=(-f "$APP_EXTRA_VALUES")
|
||||
fi
|
||||
if ! helm install "$APP_RELEASE" "$APP_CHART_DIR" "${app_values_args[@]}" \
|
||||
-n "$TEST_NAMESPACE" --timeout 300s \
|
||||
>>"$LOG" 2>&1; then
|
||||
log "::error:: 앱 차트 설치 실패 — $LOG 확인"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "== 앱: 파드 상태 관찰 (최대 ${APP_WAIT_SECONDS}s) — DB 연결 여부 판단 =="
|
||||
if [ -n "$APP_POD_SELECTOR" ]; then
|
||||
log " 대상 파드 selector: $APP_POD_SELECTOR"
|
||||
else
|
||||
log " ::warning:: APP_POD_SELECTOR 미지정 — 네임스페이스의 아무 파드나 1개 Running 이면 통과로 판정한다"
|
||||
fi
|
||||
deadline=$((SECONDS + APP_WAIT_SECONDS))
|
||||
DB_ERROR_PATTERN='password authentication failed|could not connect to server|connection refused|no pg_hba.conf entry|FATAL:.*database|OperationalError|could not translate host name'
|
||||
db_connected=""
|
||||
while [ "$SECONDS" -lt "$deadline" ]; do
|
||||
kubectl -n "$TEST_NAMESPACE" get pods >>"$LOG" 2>&1
|
||||
target_ready_count="$(kubectl -n "$TEST_NAMESPACE" get pods ${APP_POD_SELECTOR:+-l "$APP_POD_SELECTOR"} -o json 2>/dev/null | python3 -c "
|
||||
import json,sys
|
||||
d=json.load(sys.stdin)
|
||||
n=0
|
||||
for p in d.get('items', []):
|
||||
phase = p['status'].get('phase')
|
||||
statuses = p['status'].get('containerStatuses') or []
|
||||
ready = bool(statuses) and all(c.get('ready') for c in statuses)
|
||||
if phase == 'Running' and ready:
|
||||
n += 1
|
||||
print(n)
|
||||
" 2>/dev/null)"
|
||||
# DB 관련 여부와 무관하게 네임스페이스 전체 로그에서 연결 실패 패턴을 찾는다(넓게 감지).
|
||||
db_errors="$(kubectl -n "$TEST_NAMESPACE" get pods -o name 2>/dev/null | while read -r pod; do
|
||||
kubectl -n "$TEST_NAMESPACE" logs "$pod" --all-containers --tail=200 2>/dev/null
|
||||
done | grep -Ei "$DB_ERROR_PATTERN" | head -5)"
|
||||
|
||||
if [ -n "$db_errors" ]; then
|
||||
log "::error:: 파드 로그에서 DB 연결 실패 패턴 발견:"
|
||||
echo "$db_errors" | tee -a "$LOG"
|
||||
db_connected="false"
|
||||
break
|
||||
fi
|
||||
if [ "${target_ready_count:-0}" -ge 1 ]; then
|
||||
log " 대상 파드 Running/Ready ${target_ready_count}개 확인, DB 연결 실패 로그 없음"
|
||||
db_connected="true"
|
||||
break
|
||||
fi
|
||||
sleep 15
|
||||
done
|
||||
|
||||
if [ "$db_connected" = "true" ]; then
|
||||
log "== 결과: PASS (DB 연결 확인) =="
|
||||
RESULT=0
|
||||
else
|
||||
log "::error:: 타임아웃 또는 DB 연결 실패 — 파드/로그 상세는 $LOG 참고"
|
||||
kubectl -n "$TEST_NAMESPACE" get pods -o wide >>"$LOG" 2>&1
|
||||
RESULT=1
|
||||
fi
|
||||
|
||||
exit "$RESULT"
|
||||
Reference in New Issue
Block a user