S3 자격증명 회전
SeaweedFS 의 S3 자격증명을 서비스 중단 없이 교체하는 절차입니다. 2026-08-09 에 실제로 수행했고(#3088), 그때 부딪힌 함정을 그대로 적었습니다.
:::danger seaweedfs 를 재시작하면 S3 가 전면 중단된다
replicas: 1 · strategy: Recreate
Trino 쿼리·MLflow 아티팩트·Dagster 파이프라인·KServe 모델 로딩이 동시에 끊깁니다. 이 절차는 재시작 없이 SIGHUP 재적재만 씁니다. :::
전제 확인 (하기 전에)
POD=$(kubectl -n gend get pod -l app=seaweedfs -o jsonpath='{.items[0].metadata.name}')
# PID 1 이 weed 여야 하고, kill 이 있어야 하고, UID 가 같아야 한다
kubectl -n gend exec "$POD" -c seaweedfs -- sh -c '
echo "PID1=$(cat /proc/1/comm) kill=$(command -v kill) uid=$(id -u)/$(stat -c %u /proc/1)"'
# 설정 경로 확인 — 파일 기반이어야 한다 (filer 저장 방식이면 절차가 다르다)
kubectl -n gend exec "$POD" -c seaweedfs -- sh -c 'tr "\0" " " < /proc/1/cmdline' | tr ' ' '\n' | grep s3.config
weed shell 의 s3.configure 는 filer 에 쓰므로, -s3.config=<파일> 로 뜬
인스턴스에는 영향이 없습니다. 2026-08-09 시점 prod 는 파일 기반이었습니다
(s3.configure 조회 결과 identities: []).
절차
0. 백업
mkdir -p ~/rotate-backup && chmod 700 ~/rotate-backup
for s in seaweedfs-credentials gend-s3-credentials kserve-s3-credentials seaweedfs-s3-config; do
kubectl -n gend get secret "$s" -o yaml > ~/rotate-backup/$s.yaml
done
chmod 600 ~/rotate-backup/*.yaml
1. 소비자 전수 조사 — Deployment 목록만 보면 놓친다
kubectl -n gend get pod -o json | jq -r '
.items[] | select(.status.phase=="Running") |
. as $p | ($p.spec.containers[], $p.spec.initContainers[]?) |
(.env[]? | select(.valueFrom.secretKeyRef.name | test("s3|seaweedfs"))) |
"\($p.metadata.name)\t\(.valueFrom.secretKeyRef.name)"' | sort -u
★ KServe predictor 는 Deployment 스펙에 나타나지 않습니다. kserve-sa 에 붙은
Secret 이 admission 시점에 storage-initializer 로 주입되므로, 실행 중인 파드를
기준으로 세야 합니다. 2026-08-09 실측: Deployment 기준 11개 → 실제 15개.
2. 새 키를 credentials[] 에 추가 (신구 병존)
kubectl -n gend get secret seaweedfs-s3-config -o jsonpath='{.data.s3\.json}' | base64 -d > s3.json
# identity 의 credentials 배열에 항목을 추가한다 — 새 identity 를 만들지 않는다
python3 - <<'PY'
import json
d = json.load(open("s3.json"))
ident = next(i for i in d["identities"] if i["name"] == "gend")
ident["credentials"].append({"accessKey": "<새 AK>", "secretKey": "<새 SK>"})
json.dump(d, open("s3.json.new", "w"), indent=2)
PY
★ 새 identity 를 만들지 마십시오. actions 를 손으로 다시 적어야 하고, 그것이
권한 드리프트의 원인이 됩니다. 기존 identity 의 credentials[] 에 더하면 권한이
그대로 상속됩니다.
3. Secret 갱신 — apply 대신 create | replace
kubectl -n gend create secret generic seaweedfs-s3-config \
--from-file=s3.json=s3.json.new --dry-run=client -o yaml \
| kubectl -n gend replace -f -
★ apply 는 값을 last-applied-configuration 어노테이션에 복제합니다
(#3231).
:::warning replace 는 어노테이션을 통째로 갈아엎는다
kserve-s3-credentials 에는 KServe 가 읽는 엔드포인트 설정이 어노테이션으로 붙어
있습니다:
serving.kserve.io/s3-endpoint = seaweedfs.gend.svc:8333
serving.kserve.io/s3-region = us-east-1
serving.kserve.io/s3-usehttps = 0
serving.kserve.io/s3-verifyssl = 0
create | replace 로 갱신하면 이것들이 사라지고, KServe 는 엔드포인트 없이
실제 AWS S3 로 붙어 InvalidAccessKeyId 로 실패합니다. 2026-08-09 에 실제로 겪었고
백업과 대조해서야 원인을 찾았습니다.
갱신 후 반드시 어노테이션을 되돌리십시오:
kubectl -n gend annotate secret kserve-s3-credentials \
serving.kserve.io/s3-endpoint=seaweedfs.gend.svc:8333 \
serving.kserve.io/s3-region=us-east-1 \
serving.kserve.io/s3-usehttps=0 \
serving.kserve.io/s3-verifyssl=0 --overwrite
:::
4. 파일 반영을 기다린 뒤 SIGHUP
# kubelet 이 Secret 을 파드 파일로 내리는 데 시간이 걸린다 — 먼저 확인한다
until kubectl -n gend exec "$POD" -c seaweedfs -- grep -q "<새 AK>" /etc/seaweedfs/s3.json; do sleep 10; done
kubectl -n gend exec "$POD" -c seaweedfs -- kill -HUP 1
kubectl -n gend logs "$POD" -c seaweedfs --since=2m | grep Loaded
# → "Loaded N identities from config file ..." 가 나와야 한다
★ 재시작이 아님을 확인하십시오 — restartCount 가 그대로여야 합니다.
5. 새 키 단독 검증 — 소비자를 건드리기 전에
kubectl -n gend run s3check --rm -it --restart=Never \
--image=minio/mc:RELEASE.2024-11-21T17-21-54Z --command -- sh -c '
mc alias set t http://seaweedfs.gend.svc:8333 "<새 AK>" "<새 SK>" && mc ls t'
:::caution 검증 클라이언트를 신중히 고르십시오
amazon/aws-cli 는 이 SeaweedFS 에서 오류를 <botocore.awsrequest.AWSRequest object at 0x…> 로만 내보내 성공/실패 판별이 불가능했습니다. 2026-08-09 에 이 때문에
"새 키가 거부된다" 고 오판하고 설계를 통째로 바꿀 뻔했습니다(별도 identity 방식으로).
mc 로 다시 재고서야 원래 방식이 옳았음이 드러났습니다.
★ 그리고 판정에 파이프를 쓰지 마십시오 — cmd | head 뒤의 $? 는 head 의
종료코드입니다. 반드시 대조군(틀린 키)을 함께 찍어 거부되는지까지 확인하십시오.
:::
6. 소비자 전환 — 하나씩
kubectl -n gend rollout restart deploy/<name>
kubectl -n gend rollout status deploy/<name>
kubectl -n gend logs deploy/<name> --since=3m | grep -ciE "AccessDenied|SignatureDoesNotMatch|InvalidAccessKeyId"
:::warning 롤아웃에는 여분 CPU 가 필요하다
RollingUpdate 는 새 파드를 띄운 뒤 옛 파드를 내리므로 순간적으로 두 배를 씁니다.
2026-08-09 에 클러스터가 CPU request 94~99% 라 dagster-daemon 이 스케줄되지 못해
서비스가 내려갔습니다(옛 파드는 이미 종료된 뒤). trino-worker 도 롤아웃이 멈췄습니다.
회전 전에 여유를 확인하십시오:
for n in $(kubectl get nodes -o name | cut -d/ -f2); do
echo "$n $(kubectl describe node $n | sed -n '/Allocated resources/,/^Events/p' | grep -E '^\s+cpu')"
done
여유가 없으면 회전 전에 과할당 워크로드를 right-size 하십시오 (관련 #2961). :::
7. 구 키 제거
소비자 전부가 새 키로 전환된 것을 확인한 뒤에만 합니다.
# credentials[] 에서 옛 항목만 제거 → replace → 파일 반영 대기 → SIGHUP
kubectl -n gend logs "$POD" -c seaweedfs --since=2m | grep Loaded
검증: 옛 키로 list-buckets 가 실패해야 하고, 소비자는 전부 정상이어야 합니다.
2026-08-09 수행 결과
| 항목 | 결과 |
|---|---|
| seaweedfs 재시작 | 0회 (restartCount 불변) |
| 소비자 | 15개 전환 완료 (KServe predictor 4종 포함) |
| S3 인증 오류 | 전 소비자 0건 |
| 구 키 | 제거 후 거부 확인 |
| 부수 사고 | dagster-daemon 스케줄 실패로 일시 중단 → CPU right-size 로 복구 |
| 부수 사고 | KServe 어노테이션 유실 → 백업 대조로 복구 |