본문으로 건너뛰기

업그레이드

GenD 플랫폼의 버전 업그레이드 절차와 주의 사항입니다.

개요

  • 제품 버전은 SemVer(vX.Y.Z) 단일 버전으로 릴리스됩니다 (ADR-0036, v1.0.0+). 설치본의 현재 버전은 UI 하단 StatusBar 또는 GET /version 으로 확인합니다.
  • 릴리스마다 GitHub Release와 정확 태그 이미지 4종(gend-api/gend-ui/gend-pipelines/gend-relay, 태그 vX.Y.Z)이 발행됩니다 (v1.1.0 릴리스부터). 변경 사항은 릴리스 노트를 참조하세요.
  • 업그레이드 = ① DB 스키마 마이그레이션 → ② 컨테이너 이미지 교체 → ③ 검증 순서입니다.

업그레이드 전 체크리스트

  • 현재 버전 확인: curl -fsS https://<도메인>/version
  • 대상 버전의 릴리스 노트 검토 (스키마 변경·비호환 항목)
  • 데이터베이스 백업 완료 (pg_dump 또는 velero backup create백업/복원)
  • 롤백 계획 수립 (직전 버전 이미지 태그 기록)

표준 업그레이드 절차 (온프렘, v1.1.0+)

1. DB 스키마 마이그레이션 (alembic upgrade Job)

alembic 이력이 정상인 설치본(신규 설치 이후 본 절차로만 업그레이드한 설치본)은 동봉된 Job 매니페스트로 마이그레이션을 실행합니다:

# 0) 적용 대상 파일의 리소스 종류를 눈으로 확인 (아래 danger 박스 참고)
grep -n '^kind:' infra/onprem/alembic-upgrade-job.yaml

# 1) infra/onprem/alembic-upgrade-job.yaml 의 image :placeholder 를
# 대상 릴리스 태그로 치환 (예: <registry>/gend-api:v1.1.0)
# 2) 실행
kubectl apply -f infra/onprem/alembic-upgrade-job.yaml
kubectl logs -n gend job/gend-alembic-upgrade -f # "Running upgrade ..." 확인
kubectl wait -n gend --for=condition=complete job/gend-alembic-upgrade --timeout=300s

# 3) 완료 후 삭제 (Job spec 은 immutable — 재실행 시 삭제 후 재 apply)
kubectl delete job -n gend gend-alembic-upgrade

:::danger apply 대상 파일의 kind: 를 매번 눈으로 확인하세요 — 파일명을 믿지 마세요

이미지 :placeholder 치환을 잊으면 즉시 ImagePullBackOff 로 드러나지만, kind: Secret 문서가 다른 리소스와 한 파일에 섞여 있는 경우는 apply 가 조용히 성공합니다 — 실패로 드러나지 않는 만큼 더 위험합니다. 2026-08-06 prod 장애가 이 패턴이었습니다: infra/mlflow/configmap.yamlkind: ConfigMapkind: Secret 이 함께 있었고, 파일명만 보고 "ConfigMap 만 바뀐다"고 가정한 채 런북대로 apply 했다가 라이브 DB 비밀번호가 레포의 dev placeholder 값으로 덮어써져 CrashLoop 이 났습니다(상세 사고 경위·복구 절차는 MLflow artifact 프록시 운영 문서 참조). 이 alembic Job 매니페스트 자체는 kind: Job 하나만 담고 있지만 (위 0단계로 확인), 온프렘/AKS 를 막론하고 infra/ 하위 어떤 매니페스트를 apply 하기 전에도 같은 습관을 들이세요 — 파일명(configmap.yaml, deployment.yaml 등)은 그 파일이 담은 kind: 를 보증하지 않습니다.

scripts/test_manifest_secret_isolation.py (CI, #2991) 가 kind: Secret 문서가 다른 kind 와 같은 파일에 커밋되는 것은 막지만, 로컬 워킹카피의 임시 수정이나 오래된 브랜치/커밋을 체크아웃해 적용하는 경우까지는 지켜주지 않습니다 — 운영자의 apply 직전 grep -n '^kind:' <파일> 확인이 마지막 방어선입니다.

:::

TLS 가 강제된 PostgreSQL 은 Job 의 GEND_DB_URL?sslmode=verify-full 등 사이트 정책 파라미터를 덧붙이고 CA 볼륨을 마운트하세요 (helm deployment.yamlpostgresql-tls-ca 패턴 참조).

:::info AKS prod 이력 정합화 완료 — Job 절차를 그대로 씁니다 (v1.2+)

기존 prod(aks-genos-prod)는 init_db(auto_create_tables) + 수동 ALTER 로 관리되어 alembic_version 이 코드 head 와 불일치(동결) 상태였고, 그 상태에서는 alembic upgrade 가 이미 존재하는 객체와 충돌해 이 Job 의 실행이 금지되어 있었습니다.

2026-08-05 정합화를 마쳤습니다stamp 로 이력을 재정렬한 뒤 정합 리비전 r2869conforms2869jsonb 를 적용해 구조 드리프트를 133 → 13 으로 줄였습니다. alembic_version 이 코드 head 와 일치합니다.

Job 절차는 prod 에서 실제로 검증했습니다 — 같은 날 위 Job 매니페스트로 s2869jsonb 를 적용했고(TLS verify-full + PGOPTIONS 포함) 성공했습니다. 단, 아래 NetworkPolicy 전제를 먼저 확인하세요.

:::

:::tip 잔여 드리프트 0 — 예외 목록이 필요 없습니다

2026-08-07 prod 적용 완료. compare_metadata 결과가 0건입니다 (alembic 1.19.0, prod 파드에서 측정 · env.pyinclude_object 적용).

alembic head : ('y2989video',)
드리프트 : 0

:::caution "0건" 은 도구 버전과 함께 적어야 합니다

같은 DB 를 alembic 1.18.x 로 재면 CHECK 제약을 비교하지 않아 다른 답이 나옵니다. 실제로 2026-08-06 에 "0건" 으로 보고했던 것이 1.19.0 재측정에서 4건으로 드러났고 (uv.lock 도입 #2941 로 prod 이미지가 1.19.0 을 받으면서), 그 4건이 아래 u2869ck 로 해소된 것입니다. 재측정은 prod 가 실제로 쓰는 바이너리로 하세요.

또한 필터 없이 compare_metadata 를 부르면 6건이 나옵니다 — 전부 intel_patents (수동 DDL 고아, 테이블 1 + 인덱스 5)입니다. env.pyinclude_object 를 직접 넘겨야 정본 수치(0)가 나옵니다.

2026-08-07 이전에는 이 수치가 26건이었습니다 — _p4b_bak_* 백필 스냅샷 16개가 함께 잡혔기 때문입니다. 그 테이블들은 아래처럼 처분했습니다.

:::info _p4b_bak_* 백필 스냅샷 — 2026-08-07 덤프 보존 후 DROP

#2798 P4b 가 workspace_id 를 백필하기 직전 상태를 테이블째 복사해 둔 것입니다 (백업 쪽 workspace_id 는 전부 NULL). 백필이 끝나고 NOT NULL 까지 시행돼 롤백 창이 닫혔으므로 정리했습니다.

그냥 지우면 안 됐습니다 — 대부분은 원본에 그대로 있지만 18행은 백업에만 있었습니다(백필 이후 원본에서 삭제된 행):

테이블백업원본에 남음유일본
document_chunk_records432815
pipeline_templates101
ingested_files871
pii_column_registry101

그래서 16개 테이블 전체(674행)를 pg_dump -Fc 로 덤프하고, 격리 DB 에 pg_restore --exit-on-error실제 복원까지 검증한 뒤 DROP 했습니다 (--list 목차 확인만으로는 복원 가능성을 보증하지 않습니다).

이후 env.py_p4b_bak_ 접두어 필터는 죽은 코드라 제거했습니다 — 남겨두면 같은 이름의 테이블이 다시 생겨도 드리프트에서 조용히 빠집니다.

:::

:::

여기까지 온 경로: 정합화 직후 17 → s2869jsonb(모델 jsonb·팩 slug 유니크) 13 → t2869idx(인덱스 소유권 이관) 0(1.18.4 기준) → 1.19.0 재측정 4 → u2869ck(CHECK 제약 이름 rename) 0.

2026-08-07 적용분 (한 번의 alembic upgrade head, t2869idxy2989video):

리비전내용prod 영향
u2869ckCHECK 제약 이름 → ck_* (RENAME 만)드리프트 4 → 0
v2869fkingestion_run_items.workspace_id FK (RESTRICT)FK 1개 추가 (대상 0행)
q2989quota · x2989chunk데이터셋 쿼터·청크 세션 테이블 (#2989)no-opinit_db 가 이미 생성, 리비전이 가드
w2869mrg2-head 병합DDL 없음
y2989videotraining_datasets modality CHECK 에 video 추가제약 교체 (멱등)

스키마 델타는 정확히 3건이었습니다 — 제약 rename 2 + FK 1. 테이블 구성 변화 0, 행 수 변화 0.

마지막 단계가 핵심이었습니다 — init_db 의 raw SQL 과 모델이 같은 인덱스를 각자 만들고 있어서, 모델 메타데이터에 없는 11종을 autogenerate 가 영구히 "삭제 대상" 으로 보고했습니다. 지우면 다음 파드 부팅에 init_db 가 다시 만들어 무한 왕복이 됩니다. 부분/표현식 인덱스를 술어까지 그대로 모델로 옮겨 소유자를 하나로 만들었습니다.

이제 autogenerate 가 무언가를 보고하면 그건 진짜 드리프트입니다. scripts/../apps/api/tests/test_index_ownership_single_source.py 가 raw SQL 로 인덱스를 되살리는 것을 차단합니다.

kubectl -n gend exec deploy/gend-api -- python -m alembic check

:::

:::tip prod 마이그레이션 전 2가지

1) 논리 백업 — 목록 읽기가 아니라 실제 복원으로 검증

DUMP="gend-prod-$(date -u +%Y%m%dT%H%M%SZ)-pre.dump"
pg_dump -Fc -Z6 -h "$PGHOST" -p "$PGPORT" -U gend -d gend -f "$DUMP" # 58MB 기준 ~9초
sha256sum "$DUMP" | tee "$DUMP.sha256"

pg_restore --list "$DUMP" >/dev/null # ① 아카이브가 읽히는가 (최소 조건)
createdb -h "$PGHOST" -U gend gend_restore_check
pg_restore --exit-on-error -h "$PGHOST" -U gend -d gend_restore_check "$DUMP" # ② 실제 복원
dropdb -h "$PGHOST" -U gend gend_restore_check

pg_restore --list아카이브 목차만 읽습니다 — TABLE DATA 건수를 세도 복원 가능성을 보증하지 않습니다. 격리 DB 에 --exit-on-error 로 실제 복원해야 검증입니다.

2) 락 대기로 트래픽을 막지 않도록 fail-fast

export PGOPTIONS="-c lock_timeout=8000 -c statement_timeout=180000" # 로컬 실행 시

Job 으로 돌릴 때 셸의 export컨테이너에 닿지 않습니다infra/onprem/alembic-upgrade-job.yaml 의 컨테이너 envPGOPTIONS 가 들어 있습니다(수정 시 함께 유지할 것).

적용 직후 alembic current 와 헬스(/healthpostgresql: connected)를 확인하고, 파드 로그에 IntegrityError/NotNullViolation 이 없는지 봅니다.

:::

:::danger Job 을 쓰려면 NetworkPolicy allowlist 등재가 선행돼야 합니다

restrict-postgresql 이 PostgreSQL:5432 접근을 파드 라벨 app 로 제한합니다. Job 의 파드 라벨(app: gend-alembic-upgrade)이 목록에 없으면 연결 타임아웃으로 죽습니다 — 2026-08-05 prod 실증에서 정확히 그렇게 실패했고, 등재 후 성공했습니다.

kubectl -n gend get networkpolicy restrict-postgresql \
-o jsonpath='{.spec.ingress[0].from[0].podSelector.matchExpressions[0].values}'

정본은 infra/azure-deploy/k8s-azure/05-network-policy.yaml 이고, scripts/test_pg_client_allowlist.py 가 매니페스트와 allowlist 를 교차 대조합니다 (같은 유형의 실패가 #2646·#2683·#2869 세 번 재발했습니다).

★ NetworkPolicy 는 파드를 선택합니다 — CronJob/Deployment 객체의 라벨은 소용없습니다. 파드 템플릿에 붙여야 합니다.

:::

:::danger prod 는 PG TLS 를 클라이언트가 강제해야 합니다

prod PostgreSQL 의 pg_hbahostssl 이 아니라 host 로 열려 있어 평문 접속도 수락합니다. 클라이언트가 강제하지 않으면 조용히 평문으로 붙습니다.

Job 매니페스트는 gend-api 와 동일하게 ?ssl=verify-full + GEND_PG_SSL_CA_PATH=/etc/postgresql-tls/ca.crt + postgresql-tls 시크릿 마운트를 기본 활성으로 갖습니다. PG TLS 를 쓰지 않는 온프렘 사이트만 이 셋을 함께 제거하세요.

:::

2. 컨테이너 이미지 교체

REG=<registry> # 예: genosprodacr.azurecr.io 또는 사이트 미러 레지스트리
V=v1.1.0

kubectl -n gend set image deploy/gend-api gend-api=${REG}/gend-api:${V}
kubectl -n gend set image deploy/gend-ui ui=${REG}/gend-ui:${V}
kubectl -n gend rollout status deploy/gend-api --timeout=300s
kubectl -n gend rollout status deploy/gend-ui --timeout=300s
# Dagster 를 운영하는 경우
kubectl -n gend set image deploy/dagster-daemon dagster-daemon=${REG}/gend-pipelines:${V}
kubectl -n gend set image deploy/dagster-webserver dagster-webserver=${REG}/gend-pipelines:${V}

:::note UI 이미지는 도메인이 빌드타임에 새겨집니다

릴리스 gend-ui 이미지는 gend.genon.ai 기준으로 빌드됩니다. 도메인이 다른 온프렘 사이트는 부트스트랩 스크립트(infra/azure-deploy/scripts/01-push-images.sh, DOMAIN 환경변수)로 사이트 도메인에 맞게 재빌드하세요.

:::

3. 검증

curl -fsS https://<도메인>/version # version 이 대상 버전, build_sha 가 릴리스 커밋인지
curl -fsS https://<도메인>/health # status: healthy

UI 하단 StatusBar 의 버전 표기도 함께 확인합니다.

Kind 로컬 업그레이드

# 이미지 빌드 — ★레포 루트에서. gend-api 는 docs-site/docs/ 를 COPY 하므로
# apps/api 컨텍스트로 빌드하면 실패합니다.
TAG="$(cat VERSION)-dev.$(date +%Y%m%d)-$(git rev-parse --short HEAD)"
docker build -t gend/gend-api:${TAG} -f apps/api/Dockerfile .
kind load docker-image gend/gend-api:${TAG} --name gend-local
kubectl -n gend set image deploy/gend-api gend-api=gend/gend-api:${TAG}
kubectl -n gend rollout status deploy/gend-api

반드시 고유 태그를 사용하세요. latest 태그는 Kind에서 이미지 캐시 문제를 유발합니다.

Trino 업그레이드

# values.yaml에서 image.tag 변경
helm upgrade trino trino/trino -n gend -f infra/helm/trino/values.yaml

# Worker graceful shutdown 적용
kubectl apply -f infra/trino/worker-graceful-shutdown.yaml

Keycloak 업그레이드

Keycloak은 PostgreSQL DB 스키마를 자동 마이그레이션합니다.

# 이미지 태그 변경 후 재배포
kubectl apply -f infra/keycloak/deployment.yaml

롤백

# Deployment 롤백 (또는 직전 버전 태그로 set image 재실행)
kubectl rollout undo deployment/gend-api -n gend

# Helm 롤백
helm rollback trino -n gend

# ArgoCD 롤백
argocd app rollback gend-api

DB 마이그레이션이 포함된 업그레이드의 롤백은 이미지 롤백만으로 충분하지 않을 수 있습니다 — 업그레이드 전 백업으로 복원하는 경로를 기본으로 하세요.

버전 호환성

업그레이드 시 다음 호환성을 확인하세요:

구성 요소호환 주의
Trino카탈로그 커넥터 설정 변경 확인
KeycloakRealm 내보내기/가져오기 호환성
VaultAPI 버전 호환성
PostgreSQLpg_dump/pg_restore 버전 매칭

관련 문서