운영 설치

Kubernetes 설치

게시 chart로 운영 서버를 설치한다

무엇을 하게 되나

게시 Helm chart로 운영 서버를 설치한다. PostgreSQL과 영속 S3 호환 bucket은 따로 준비한다. chart 설치 성공과 세션 데이터 복원 성공을 구분한다.

준비물

항목요구
클러스터Kubernetes. NetworkPolicy를 강제하는 CNI가 필요하다. 강제하지 않으면 worker 격리가 없다
도구helm 3, kubectl, curl
PostgreSQL16. 클러스터 안에서 닿는 주소
S3 호환 object storebucket 하나. 기본 보호 모드 locked는 versioning, Object Lock, 기본 암호화 SSE-S3(AES256)가 켜진 bucket을 요구한다. 아니면 api가 기동을 거부한다
외부ghcr.io에서 chart와 이미지를 받을 수 있다. 패키지는 로그인 없이 받는다

chart와 패키지의 선택 방법은 배포 패키지 계약을 따른다. AWS 이외 endpoint는 config.aws.endpointUrl와 credential allowlist를 함께 맞추고 보호 계약을 확인한다. 실제 AWS 설치는 실행 확인하지 않았다.

chart는 PostgreSQL, object store, Secret 값을 만들지 않는다. worker Pod도 만들지 않는다. scheduler가 세션마다 만든다. 게시 이미지는 linux/amd64와 linux/arm64를 담은 index이므로 노드가 자기 아키텍처를 받는다.

Git 서버도 chart가 만들지 않는다. 설정 문서의 저장소 URL은 worker가 egress proxy로 닿을 수 있어야 한다. kind의 시험 Git 서버는 운영 저장소가 아니다.

아래 예시는 namespace와 release가 모두 werft인 설치다. 실행 전에 kubectl config current-context와 KUBECONFIG가 이번 설치의 클러스터를 고르는지 확인한다. 다른 설치는 모든 -n·--namespace·release 이름과 deploy/werft-*·svc/werft-*를 실제 fullname으로 바꾼다. release 이름에 werft가 없으면 fullname은 <release>-werft다.

checkpoint bucket과 workspace 저장소 계약을 먼저 확인한다. 설치 id는 backend가 받는 32자 이하 소문자 DNS label이어야 한다. chart가 더 넓게 허용해도 이 제약을 따른다. 원격 bootstrap 전 접속 경로를 준비한다.

단계

1. 정확한 chart 버전 선택

VERSION='<published-chart-version>'
helm show chart oci://ghcr.io/jeongjaesoon/werft --version "$VERSION"
helm show values oci://ghcr.io/jeongjaesoon/werft --version "$VERSION" | sed -n '/^images:/,/^$/p'

images 아래 세 이미지의 digest와 worker.runtime이 나오면 된다. 이 값은 chart에 들어 있으므로 설치별 values에 다시 적지 않는다. 적으면 다음 chart의 값을 계속 덮는다.

2. namespace와 Secret

Secret은 키마다 파일 하나인 디렉터리(0700, 파일 이름이 키)에서 만든다. 값이 명령줄과 셸 history에 남지 않는다.

키값
DATABASE_URLPostgreSQL 연결 URL
EGRESS_AUTHORIZER_TOKEN무작위 값. api와 egress proxy가 같은 값을 쓴다
PLATFORM_SECRET_KEYopenssl rand -hex 32의 64자 hex. 저장한 자격 증명을 봉인한다. 없으면 Pod가 뜨지 않는다
AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEYobject store key. IRSA로 서명하려면 두 키를 빼고 values에 secrets.objectStoreKeys: false와 serviceAccounts.api.annotations를 둔다
BOOTSTRAP_TOKEN기본 API replica 2개에 같은 32–256자 토큰을 공급해야 한다. Secret에 만들고 아래 values의 secrets.catalogEnv에 포함한다
ANTHROPIC_API_KEY선택. 세션이 쓸 Anthropic key의 기본 경로는 설치 뒤 콘솔의 설정 > 에이전트 연결 > 모델 연결 > 연결 추가다. 이 키는 환경 변수로 주는 대안이고, 두면 api에만 간다. 두지 않으면 아래 values의 secrets.catalogEnv에서 이 이름만 뺀다. BOOTSTRAP_TOKEN은 남긴다. chart 기본값은 이 키를 api에 넘기므로 Secret에 없으면 Pod가 뜨지 않는다
kubectl create namespace werft
kubectl -n werft create secret generic werft --from-file=<secret-dir>

Secret 디렉터리는 0700, 파일은 운영자만 읽을 수 있게 둔다. 기본 API replica는 2개다. replica가 둘 이상이면 같은 BOOTSTRAP_TOKEN(32–256자)을 Secret에 추가하고 secrets.catalogEnv에 포함한다. 모델 키를 콘솔로 넣을 때는 목록을 ["BOOTSTRAP_TOKEN"]으로, 환경 변수도 쓸 때는 필요한 이름을 함께 적는다. 목록의 모든 키가 Secret에 있어야 한다.

3. 설치별 values

설치별 값은 values-<installation>.yaml 하나에 둔다. 최소한 checkpoint bucket과 worker가 bucket에 닿는 경로가 들어간다.

installationId: werft-main
secrets:
  catalogEnv: ["BOOTSTRAP_TOKEN"]
config:
  checkpoints:
    bucket: <bucket>
  egress:
    credentialAllowlist: "api.anthropic.com:443,registry.npmjs.org:443,rubygems.org:443,proxy.golang.org:443,sum.golang.org:443,github.com:443,<bucket>.s3.<region>.amazonaws.com:443"

credentialAllowlist는 기본값을 통째로 바꾼다. 기본 목록(provider와 패키지 registry)을 모두 다시 적고, 저장소 호스트(github.com:443, 다른 Git 호스트를 쓰면 그 호스트도)와 bucket 호스트를 이어 적는다.

config.aws.region의 기본값은 ap-northeast-1이다. 다른 리전의 bucket이면 바꾼다. 연결, 저장소, MCP 서버, 상한은 values에 없다. 설치한 뒤 웹 콘솔에서 더한다. bun run settings:k8s import를 쓰는 대안은 설치 버전의 저장소 checkout과 호스트 Bun·kubectl이 필요하다. 기본값과 설명은 helm show values에 있다.

config.egress.credentialAllowlist 값은 기본값을 통째로 대체하므로 GitHub·checkpoint bucket host를 넣을 때 provider와 패키지 registry 등 필요한 항목을 모두 다시 적는다. API와 proxy가 같은 credential 두 목록을 읽는다.

다른 설치와 겹치지 않는 installationId를 같은 파일에 둔다. images.*의 digest는 게시 chart의 값을 유지한다. DB·S3·Secret·CNI가 준비됐는지 확인한 뒤 진행한다.

4. 설치와 rollout

helm upgrade --install werft oci://ghcr.io/jeongjaesoon/werft --version "$VERSION" \
  --namespace werft -f values-<installation>.yaml --timeout 10m

migrate Job이 먼저 schema를 올리고, 끝나면 api, egress proxy, reconciler, scheduler, slack이 한꺼번에 적용된다. 첫 설치가 이미지 경로나 pull 권한 오류로 실패하면 값을 고치고 같은 명령을 다시 돌린다.

kubectl -n werft get job werft-migrate
for d in api egress-proxy reconciler scheduler; do
  kubectl -n werft rollout status deploy/werft-$d --timeout=600s
done

release 이름에 werft가 들어가지 않으면 리소스 이름은 <release>-werft-…가 된다.

첫 설치가 실패하면 값을 고치고 같은 명령을 다시 실행한다. Deployment까지 생긴 실패에서 worker 이미지를 바꿨다면 업그레이드 검사 규칙이 적용된다. 세션이 없다면 Kubernetes 제거로 설치를 정리한 뒤 다시 시작한다.

확인

kubectl -n werft port-forward svc/werft-api 13000:3000 &
PF=$!
trap 'kill "$PF" 2>/dev/null || true' EXIT INT TERM
READY=0
for attempt in $(seq 1 30); do
  if curl --max-time 2 -fsS -o /dev/null http://127.0.0.1:13000/readyz; then READY=1; break; fi
  sleep 1
done
if [ "$READY" != 1 ]; then
  kill "$PF" 2>/dev/null || true
  trap - EXIT INT TERM
  false
else
  curl --max-time 10 -fsS -w '\n%{http_code}\n' http://127.0.0.1:13000/readyz
  curl --max-time 10 -fsS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:13000/docs
  kill "$PF" 2>/dev/null || true
  trap - EXIT INT TERM
fi

ready 본문과 200, docs 200을 확인한다. 30번 안에 ready가 되지 않으면 이 셸이 띄운 port-forward PID를 정리하고 실패로 남긴다. 실제 사용자 경로에서는 접속 경로 검사도 수행한다. 이 확인은 빈 설치의 준비 상태이며 worker 실행·checkpoint 복원을 증명하지 않는다.

자주 막히는 곳

첫날에는 설치 직후·1시간 뒤·업무 종료 전에 kubectl -n werft get job,deploy,pods,pvc와 api·scheduler·reconciler 로그, 각 --health, 디스크·inode·PVC 여유를 확인한다. 백업 실패 알림·checkpoint GC 일정·key 폐기 연락 경로·provider 비용 한도를 점검한다. release와 namespace가 예시와 다르면 모든 명령을 해당 설치 값으로 바꾼다.

NetworkPolicy를 강제하지 않는 CNI에는 worker 격리가 없다. Secret 키 누락, 잘못된 installationId, S3 보호 설정 오류, image pull 실패를 Pod와 Job 로그에서 구분한다. fixture chart의 render 성공은 게시 chart의 설치 성공과 다르다.

되돌리기

이전 revision으로 자동 rollback하지 않는다. 첫 설치 실패는 이 namespace의 리소스를 확인한 뒤 제거를 따른다. PVC와 bucket의 미수거 checkpoint는 보존한다.

다음에 읽을 곳

접속 경로 → 첫 owner와 key → 모델 연결. 버전 변경은 Kubernetes 업그레이드를 따른다.

이전 주소 안내

이전 절에서 현재 문서 찾기

Kubernetes 설치

저장소와 상한

첫 owner와 사용자 key 발급

kind에서 시험 설치

Kubernetes 중지·제거

이 페이지