Skip to content

Commit 10fb198

Browse files
yoon6yoclaude
andauthored
perf: 스택 배포에서 admin_be·프론트엔드를 config-server와 같이 띄우고 계정 대장 도우미 Pod를 미리 띄움 (#266)
- admin_be·프론트엔드는 적용만 해 두고 config-server(helm --wait)를 올린 뒤 한꺼번에 준비를 확인함. 하나씩 기다리던 시간이 겹쳐짐. helm의 대기와 실패 처리(failed 릴리스 재설치)는 그대로임. - 계정 대장 도우미 Pod를 네임스페이스 직후 띄우고, 뜨는 동안 Secret 준비 단계를 진행함. UID 대역 확인은 스택 구성요소를 올리기 전(MySQL 앞)에 그대로 수행함. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 94b5c6d commit 10fb198

1 file changed

Lines changed: 68 additions & 55 deletions

File tree

‎ops/proposed-stack/stack-up.sh‎

Lines changed: 68 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ getpw() { kubectl -n "$NS" get secret stack-db -o jsonpath="{.data.$1}" | base64
105105
# 배포가 돌도록, 스택 네임스페이스에 그 경로만 붙인 임시 도우미 Pod를 띄워 읽고 끝나면 지운다.
106106
LEDGER_POD=ledger-helper
107107
ledger_cleanup() { kubectl -n "$NS" delete pod "$LEDGER_POD" --ignore-not-found --wait=false >/dev/null 2>&1 || true; }
108+
# 뜨는 데 몇 초 걸리므로 띄우기(start)와 기다리기(wait)를 나눠, 그 사이에 대장이 필요 없는 단계를 먼저 진행한다.
108109
start_ledger_helper() { # $1: NFS 서버, $2: 계정 대장 경로
109110
kubectl -n "$NS" delete pod "$LEDGER_POD" --ignore-not-found --wait=true --timeout=60s >/dev/null 2>&1 || true
110111
# kube_share NFS 마운트를 NAS가 허용하는 노드는 csid-dgu-desktop 하나뿐이다 — config-server
@@ -131,8 +132,8 @@ $pin
131132
- name: kube-share
132133
nfs: {server: "$1", path: "$2"}
133134
YAML
134-
kubectl -n "$NS" wait --for=condition=Ready pod/"$LEDGER_POD" --timeout=5m >/dev/null
135135
}
136+
wait_ledger_helper() { kubectl -n "$NS" wait --for=condition=Ready pod/"$LEDGER_POD" --timeout=5m >/dev/null; }
136137
ledger() { kubectl -n "$NS" exec "$LEDGER_POD" -- "$@"; }
137138
BASE=$(mktemp); trap 'rm -f "$BASE"; ledger_cleanup' EXIT
138139

@@ -156,6 +157,8 @@ echo "config-server nodePort $CONFIG_NODEPORT 사용 가능, 화면 호스트 $F
156157
step "네임스페이스 $NS"
157158
kubectl create namespace "$NS" --dry-run=client -o yaml | kubectl apply -f -
158159
kubectl label namespace "$NS" ailab.dgu/proposed-stack="$STACK" --overwrite >/dev/null
160+
# 계정 대장 확인(아래 MySQL 앞 단계)에 쓸 도우미 Pod를 지금 띄워 두고, 뜨는 동안 Secret 준비 단계를 진행한다.
161+
start_ledger_helper "$NFS_SERVER" "$KUBE_SHARE"
159162

160163
step "사용자 컨테이너 우선순위 등급"
161164
# 노드 디스크가 쪼들리면 kubelet이 Pod를 쫓아낸다. 그 순서는 자원 요청 초과 여부와 우선순위로
@@ -194,19 +197,6 @@ description: "스택 구성요소 — 사용자 컨테이너보다도 늦게 축
194197
YAML
195198
echo "ailab-stack-component 적용"
196199

197-
step "계정 대장 확인 (임시 도우미 Pod)"
198-
start_ledger_helper "$NFS_SERVER" "$KUBE_SHARE"
199-
USED=$(ledger awk -F: -v lo="$UID_MIN" -v hi="$UID_MAX" '$3>=lo && $3<=hi {n++} END {print n+0}' /kube_share/passwd)
200-
[ "$USED" = 0 ] || { echo "운영 계정 대장에 UID $UID_MIN~$UID_MAX 계정이 ${USED}개 있음. 대역을 옮겨야 함"; exit 1; }
201-
# config-server는 nfs.kubeSharePath를 /kube_share로 마운트해 passwd/group/shadow를 둔다. 운영 경로의
202-
# 하위 디렉터리를 스택 전용으로 쓰므로 테스트 계정이 운영 대장에 섞이지 않는다. 비어 있으면
203-
# config-server가 처음 계정을 만들 때 기본 파일로 채운다.
204-
# (실운영도 같은 방식이다 — 원본 경로는 NAS가 마운트를 거부해 격리 방식으로 되돌렸다. 위 UID_MAX가
205-
# 이제 겹치지 않으므로 세 스택과 다를 게 없다.)
206-
STACK_KUBE_SHARE="$KUBE_SHARE/exp-$STACK"
207-
ledger mkdir -p "/kube_share/exp-$STACK"
208-
echo "UID $UID_MIN~$UID_MAX 비어 있음, 스택 계정 대장 /kube_share/exp-$STACK"
209-
210200
step "SSH 키 복사 ($PROD_NS → $NS)"
211201
for s in nas-ssh-key farm-ssh-key farm-ad-ssh-key; do
212202
TYPE=$(kubectl -n "$PROD_NS" get secret "$s" -o jsonpath='{.type}')
@@ -277,6 +267,20 @@ kubectl -n "$NS" create secret generic config-server-api-token --from-literal=to
277267
--dry-run=client -o yaml | kubectl apply -f - >/dev/null
278268
echo "$([ "$TOKEN_NEW" = 1 ] && echo 새로 생성 || echo 기존 값 유지)"
279269

270+
step "계정 대장 확인 (임시 도우미 Pod)"
271+
# 스택 구성요소를 하나라도 올리기 전에 확인한다(이 앞 단계는 Secret 준비뿐이다).
272+
wait_ledger_helper
273+
USED=$(ledger awk -F: -v lo="$UID_MIN" -v hi="$UID_MAX" '$3>=lo && $3<=hi {n++} END {print n+0}' /kube_share/passwd)
274+
[ "$USED" = 0 ] || { echo "운영 계정 대장에 UID $UID_MIN~$UID_MAX 계정이 ${USED}개 있음. 대역을 옮겨야 함"; exit 1; }
275+
# config-server는 nfs.kubeSharePath를 /kube_share로 마운트해 passwd/group/shadow를 둔다. 운영 경로의
276+
# 하위 디렉터리를 스택 전용으로 쓰므로 테스트 계정이 운영 대장에 섞이지 않는다. 비어 있으면
277+
# config-server가 처음 계정을 만들 때 기본 파일로 채운다.
278+
# (실운영도 같은 방식이다 — 원본 경로는 NAS가 마운트를 거부해 격리 방식으로 되돌렸다. 위 UID_MAX가
279+
# 이제 겹치지 않으므로 세 스택과 다를 게 없다.)
280+
STACK_KUBE_SHARE="$KUBE_SHARE/exp-$STACK"
281+
ledger mkdir -p "/kube_share/exp-$STACK"
282+
echo "UID $UID_MIN~$UID_MAX 비어 있음, 스택 계정 대장 /kube_share/exp-$STACK"
283+
280284
step "MySQL"
281285
render "$HERE/mysql.yaml" | kubectl apply -f -
282286
kubectl -n "$NS" rollout status statefulset/mysql --timeout=10m
@@ -325,36 +329,9 @@ else
325329
echo "임시 디스크 사용"
326330
fi
327331

328-
step "config-server ($RELEASE, $IMAGE)"
329-
# 운영 릴리스 값($BASE, 사전 확인에서 읽음)에 스택마다 달라야 하는 값만 덮어쓴다.
330-
# 첫 설치가 실패한 릴리스는 upgrade가 받지 않을 수 있으므로 지우고 다시 설치한다.
331-
if helm -n "$NS" status "$RELEASE" -o json 2>/dev/null | grep -q '"status":"failed"'; then
332-
helm -n "$NS" uninstall "$RELEASE" --wait >/dev/null && echo "실패한 이전 설치 정리"
333-
fi
334-
helm upgrade --install "$RELEASE" "$ROOT/config-server/Chart" -n "$NS" -f "$BASE" \
335-
--set namespace="$NS" --set config.namespace="$NS" \
336-
--set image.repository="${IMAGE%:*}" --set image.tag="${IMAGE##*:}" --set image.pullPolicy=IfNotPresent \
337-
--set service.nodePort="$CONFIG_NODEPORT" \
338-
--set nfs.kubeSharePath="$STACK_KUBE_SHARE" \
339-
--set infra.adminBeInternalUrl="http://admin-prod.$NS" \
340-
--set accounts.uidMin="$UID_MIN" --set accounts.uidMax="$UID_MAX" --set accounts.prefix="$PREFIX" \
341-
--set accounts.sharedGidMin="$SHARED_GID_MIN" --set accounts.sharedGidMax="$SHARED_GID_MAX" \
342-
--set nodeport.min="$NP_MIN" --set nodeport.max="$NP_MAX" \
343-
--set verifyMode="$VERIFY_MODE" --set faultInjection="$FAULT_INJECTION" \
344-
--set redis.host="redis-bg-master.$NS.svc.cluster.local" \
345-
--set db.host=infra-mysql --set logDb.host=log-mysql \
346-
--set imageStore.claimName= \
347-
--set controller.enabled=true \
348-
--set priorityClassName=ailab-stack-component \
349-
--wait --timeout 10m
350-
if [ "$TOKEN_NEW" = 1 ] || [ "$KH_CHANGED" = 1 ]; then
351-
# 토큰·호스트 키 Secret은 Pod 템플릿에 드러나지 않아 helm 업그레이드만으로는 새 값을 읽지 않는다.
352-
kubectl -n "$NS" rollout restart deployment/"$RELEASE" deployment/"$RELEASE-controller" >/dev/null
353-
kubectl -n "$NS" rollout status deployment/"$RELEASE" --timeout=10m
354-
kubectl -n "$NS" rollout status deployment/"$RELEASE-controller" --timeout=10m
355-
fi
356-
357332
step "admin_be"
333+
# admin_be·프론트엔드는 적용만 하고, 준비 확인은 config-server를 올린 뒤 "롤아웃 대기"에서 한꺼번에 한다 —
334+
# 하나씩 기다리면 각자 뜨는 시간이 그대로 더해진다. admin_be는 기동할 때 config-server를 부르지 않는다.
358335
ADMIN_IMAGE=$BE_IMAGE
359336
# 운영 admin_be는 설정 파일을 이미지가 아니라 admin-prod-config Secret으로 받는다. 같은 파일을 복사해
360337
# 같은 위치에 넣고, 운영 자원을 가리키는 값만 아래 SPRING_APPLICATION_JSON으로 덮어쓴다.
@@ -443,10 +420,57 @@ fi
443420
render "$HERE/admin-be.yaml" | sed -e "s|__ADMIN_IMAGE__|$ADMIN_IMAGE|" -e "s|__CONFIG_HASH__|$CONFIG_HASH|" \
444421
| API_EGRESS="$API_EGRESS" awk '{ if (index($0, "__API_EGRESS__")) print ENVIRON["API_EGRESS"]; else print }' \
445422
| kubectl apply -f -
423+
echo "admin_be 이미지: ${ADMIN_IMAGE##*[@:]}"
424+
425+
step "프론트엔드"
426+
if [ -n "$FE_IMAGE" ]; then
427+
render "$HERE/admin-fe.yaml" | sed -e "s|__FE_IMAGE__|$FE_IMAGE|" -e "s|__FE_HOST__|$FE_HOST|" | kubectl apply -f -
428+
if [ "$STACK" = "operation" ]; then
429+
kubectl apply -f "$HERE/main-entry.yaml"
430+
fi
431+
else
432+
echo "프론트엔드 이미지가 없어 건너뜀"
433+
fi
434+
435+
step "config-server ($RELEASE, $IMAGE)"
436+
# 위에서 적용만 해 둔 admin_be·프론트엔드가 뜨는 동안 config-server를 올린다. helm은 전처럼 준비될 때까지
437+
# 기다린다(--wait) — 제때 못 뜨면 릴리스가 failed로 남아 다음 배포가 지우고 다시 설치한다.
438+
# 운영 릴리스 값($BASE, 사전 확인에서 읽음)에 스택마다 달라야 하는 값만 덮어쓴다.
439+
# 첫 설치가 실패한 릴리스는 upgrade가 받지 않을 수 있으므로 지우고 다시 설치한다.
440+
if helm -n "$NS" status "$RELEASE" -o json 2>/dev/null | grep -q '"status":"failed"'; then
441+
helm -n "$NS" uninstall "$RELEASE" --wait >/dev/null && echo "실패한 이전 설치 정리"
442+
fi
443+
helm upgrade --install "$RELEASE" "$ROOT/config-server/Chart" -n "$NS" -f "$BASE" \
444+
--set namespace="$NS" --set config.namespace="$NS" \
445+
--set image.repository="${IMAGE%:*}" --set image.tag="${IMAGE##*:}" --set image.pullPolicy=IfNotPresent \
446+
--set service.nodePort="$CONFIG_NODEPORT" \
447+
--set nfs.kubeSharePath="$STACK_KUBE_SHARE" \
448+
--set infra.adminBeInternalUrl="http://admin-prod.$NS" \
449+
--set accounts.uidMin="$UID_MIN" --set accounts.uidMax="$UID_MAX" --set accounts.prefix="$PREFIX" \
450+
--set accounts.sharedGidMin="$SHARED_GID_MIN" --set accounts.sharedGidMax="$SHARED_GID_MAX" \
451+
--set nodeport.min="$NP_MIN" --set nodeport.max="$NP_MAX" \
452+
--set verifyMode="$VERIFY_MODE" --set faultInjection="$FAULT_INJECTION" \
453+
--set redis.host="redis-bg-master.$NS.svc.cluster.local" \
454+
--set db.host=infra-mysql --set logDb.host=log-mysql \
455+
--set imageStore.claimName= \
456+
--set controller.enabled=true \
457+
--set priorityClassName=ailab-stack-component \
458+
--wait --timeout 10m
459+
if [ "$TOKEN_NEW" = 1 ] || [ "$KH_CHANGED" = 1 ]; then
460+
# 토큰·호스트 키 Secret은 Pod 템플릿에 드러나지 않아 helm 업그레이드만으로는 새 값을 읽지 않는다.
461+
kubectl -n "$NS" rollout restart deployment/"$RELEASE" deployment/"$RELEASE-controller" >/dev/null
462+
kubectl -n "$NS" rollout status deployment/"$RELEASE" --timeout=10m
463+
kubectl -n "$NS" rollout status deployment/"$RELEASE-controller" --timeout=10m
464+
fi
465+
466+
step "롤아웃 대기 (admin_be·Redis·프론트엔드)"
467+
# config-server와 같이 뜨던 것들이라, 차례로 확인해도 걸리는 시간은 가장 느린 것 하나만큼이다.
446468
kubectl -n "$NS" rollout status deployment/admin-prod --timeout=10m
447469
kubectl -n "$NS" rollout status deployment/redis-bg-master --timeout=5m
448470
kubectl -n "$NS" rollout status deployment/admin-redis --timeout=5m
449-
echo "admin_be 이미지: ${ADMIN_IMAGE##*[@:]}"
471+
if [ -n "$FE_IMAGE" ]; then
472+
kubectl -n "$NS" rollout status deployment/ailab-frontend --timeout=5m
473+
fi
450474

451475
step "기준 데이터 확인 (스택 admin DB)"
452476
# 신청 화면에 필요한 서버·자원 그룹·노드·GPU·이미지와 메일 문구는 최초 1회 스택에 심어 두면 된다.
@@ -473,17 +497,6 @@ smy -e "UPDATE web_admin.container_image SET image_version = REGEXP_REPLACE(imag
473497
smy -e "SELECT CONCAT(image_version, ' (', COUNT(*), ')') FROM web_admin.container_image
474498
WHERE image_name LIKE '%dguailab/decs' GROUP BY image_version ORDER BY image_version" | sed 's/^/ /'
475499

476-
step "프론트엔드"
477-
if [ -n "$FE_IMAGE" ]; then
478-
render "$HERE/admin-fe.yaml" | sed -e "s|__FE_IMAGE__|$FE_IMAGE|" -e "s|__FE_HOST__|$FE_HOST|" | kubectl apply -f -
479-
kubectl -n "$NS" rollout status deployment/ailab-frontend --timeout=5m
480-
if [ "$STACK" = "operation" ]; then
481-
kubectl apply -f "$HERE/main-entry.yaml"
482-
fi
483-
else
484-
echo "프론트엔드 이미지가 없어 건너뜀"
485-
fi
486-
487500
step "검증 (admin_be·프론트엔드 연결, 접두어 제한)"
488501
CS_POD=$(running_pod "$NS" app=containerssh-config-server)
489502
kubectl -n "$NS" exec -i "$CS_POD" -- env NAME="${PREFIX}000" PREFIX="$PREFIX" UID_MIN="$UID_MIN" UID_MAX="$UID_MAX" FE="${FE_IMAGE:+http://ailab-frontend}" FE_HOST="$FE_HOST" python - <<'PY'

0 commit comments

Comments
 (0)