사람함께에이전트▾
사람 — 직접 정하고 책임지는 비즈니스 규칙과 흐름. 직접 읽어보세요.
함께 — 개념은 알아두고, 세부 규칙은 에이전트가 따릅니다.
에이전트 — 에이전트가 따르는 규칙과 레퍼런스. 필요할 때 찾아보세요.
Kubernetes
Akan 앱은 모두 같은 Helm chart인
infra/app으로 배포합니다. chart는 <appName>-<branch> 네임스페이스에 리소스 네 개를 만듭니다.| 리소스 | 이름 | 하는 일 |
|---|---|---|
| Deployment | app-deployment | 앱 이미지를 pod 하나로 실행합니다. |
| Service | app-svc | 클러스터 안에서 8282 포트로 앱을 노출합니다. |
| Ingress | app-ingress | 도메인을 Service에 연결하고 TLS 인증서를 발급받습니다. |
| PersistentVolumeClaim | sqlite-data | pod가 재시작돼도 /workspace/sqlite의 sqlite 데이터를 유지합니다. |
브랜치는 네임스페이스가 정합니다
branch는 값으로 적지 않습니다. chart가 release 네임스페이스에서 읽어 옵니다.
<appName>-<branch>네임스페이스에 배포합니다. 예를 들면myapp-main입니다.- chart가 이름을
-로 나눠 두 번째 조각인main을 branch로 씁니다. - values의
main:블록을 읽고, pod에AKAN_PUBLIC_ENV=main을 넘깁니다.


네임스페이스를 잘못 적으면 조용히 다른 values 블록이 선택됩니다.
myapp-develop에 배포하면 pod는 develop 설정으로 뜹니다. 같은 이유로 appName에는 -를 넣으면 안 됩니다.구조
요청은 Ingress로 들어와 Service를 거쳐 pod에 닿고, pod는 데이터를 PVC에 저장합니다. 그 옆에서 kubelet이 pod의 상태를 확인합니다.
요청 경로
도메인<appName>-<branch>.<serveDomain>
IngressTLS, subRoute·도메인마다 호스트 하나
Service :8282
Podreplicas: 1
PVC/workspace/sqlite
kubelet
도메인<appName>-<branch>.<serveDomain>
kubelet
IngressTLS, subRoute·도메인마다 호스트 하나
Service :8282
/_akan/app/health
Podreplicas: 1
PVC/workspace/sqlite
- pod는 언제나 하나입니다.
replicas: 1은 template에 고정돼 있습니다. 확장하려면 pod를 늘리지 말고 pod 안의AKAN_REPLICA를 올립니다. - 하나인 이유. sqlite PVC가
ReadWriteOnce라서 한 번에 노드 하나에만 붙습니다. 그래서 pod를 여러 노드에 나눠 띄울 수 없습니다. - 인증서는 하나입니다. 기본 호스트, subRoute 호스트, 운영 도메인이 모두 TLS secret
cert-<appName>-<branch>하나를 함께 씁니다.
Values
values.yaml 하나로 끝나지 않습니다. Helm이 파일 네 개를 순서대로 읽고, 뒤의 파일이 앞의 값을 덮어씁니다. 그래서 앱 자신의 파일에는 보통 이름과 운영 도메인만 적습니다.| # | 파일 | 담는 값 |
|---|---|---|
| 1 | _common-values.yaml | debug, develop, main 브랜치별 replica, 리소스, 스토리지 기본값입니다. |
| 2 | _common-secret.yaml | 모든 앱이 함께 쓰는 repoName, serveDomain, image.registry 값입니다. |
| 3 | <appName>-values.yaml | 앱 자신의 appName, subRoutes, 도메인, 덮어쓸 값입니다. |
| 4 | <appName>-secret.yaml | 앱 전용 비밀값이며 비어 있어도 됩니다. |
두
*-secret.yaml 파일은 git에 올리지 않습니다. bun run downloadSecret은 infra/master/jenkins/getSecrets.sh에 적힌 파일을 받아 오므로, 새 앱의 파일도 거기에 추가합니다.배포 명령
Jenkins 배포 단계는 앱마다
infra/에서 아래 두 명령을 실행합니다.Terminal
-f순서가 곧 우선순위입니다. 뒤에 오는 파일이 같은 키를 덮어씁니다.-n이 branch를 정합니다.myapp-main이면main:블록을 씁니다.rollout restart가 새 빌드를 반영합니다. 재시작한 pod가 이미지를 다시 받아 오므로, 방금 올린 빌드로 뜹니다.
앱 values 파일
기본값보다 용량이 더 필요한 운영 앱이라면 파일이 이 정도가 됩니다.
infra/app/values/myapp-values.yaml
- 최상위 키는 모든 브랜치에 적용됩니다.
appName과subRoutes는 맨 위에 적습니다. main:블록은main에만 적용됩니다. 적지 않은 키는_common-values.yaml의 기본값을 따르고,domains같은 목록은 통째로 바뀝니다.
최상위 키
_common-secret.yaml 표시가 붙은 키는 그 파일에 한 번만 적고 모든 앱이 함께 씁니다.appNamestring
네임스페이스
<appName>-<branch>, 이미지 경로, 기본 호스트 이름에 쓰입니다.repoNamestring_common-secret.yaml
이미지 경로
<registry>/<repoName>/<appName>의 workspace 부분입니다.serveDomainstring_common-secret.yaml
기준 도메인이며, 기본 호스트는
<appName>-<branch>.<serveDomain>입니다.subRoutesstring[]기본값 []
basePath마다
<subRoute>-<branch>.<serveDomain> 호스트와 TLS 이름을 하나씩 더합니다.image.registrystring_common-secret.yaml
이미지 레지스트리 호스트입니다.
image.tagstring기본값 <branch>-live
특정 빌드에 고정할 때만 적습니다.
브랜치별 키
main: 같은 브랜치 블록 아래에 적습니다. 기본값은 _common-values.yaml에서 옵니다.<branch>.domainsstring[]기본값 []
운영 도메인처럼 그 브랜치에 더할 호스트와 TLS 이름입니다.
<branch>.app.replicastring기본값 "0,0,1"
pod 안에서
AKAN_REPLICA, 즉 federation, batch, all 순서의 프로세스 수가 됩니다.<branch>.app.solostring기본값 없음
AKAN_SOLO가 되며, replica가 하나여도 gateway를 두려면 false를 적습니다.<branch>.app.resources.requests{ memory, cpu }기본값 250M / 0.05 (main: 1G / 1)
pod가 요청하는 메모리와 CPU입니다.
<branch>.app.resources.limits{ memory, cpu }기본값 1G / 0.5 (main: 4G / 4)
pod의 limit이며, CPU limit은 이미지 최적화가 한 번에 인코딩하는 수도 정합니다.
<branch>.app.resources.storagestring기본값 2Gi (main: 5Gi)
/workspace/sqlite에 마운트되는 ReadWriteOnce PVC의 크기입니다.

values로 바꿀 수 없는 설정도 있습니다. 8282 포트,
replicas: 1, 40초 종료 유예, probe 세 개는 templates/app.yaml에 고정돼 있습니다. 바꾸려면 chart를 직접 고쳐야 합니다.확장
<branch>.app.replica는 pod 안에서 AKAN_REPLICA가 됩니다. 역할별 프로세스 수 세 개이며, CPU·메모리 limit과 함께 올립니다.역할설명
federation
요청을 처리하고,
serverMode: "batch"로 고정한 서비스와 internal은 건너뜁니다.batch
요청은 받지 않고,
batch로 고정한 것을 포함해 예약·큐 internal을 돌립니다.all
요청을 처리하고 모든 internal도 돌립니다.
자주 쓰는 값
AKAN_REPLICA
요청 처리
batch internal
serverMode: "batch"
Gateway
프로세스 하나, gateway 없음
0,0,1
✓
✓
chart 기본값으로, 무엇이든 하는 프로세스 하나입니다.
1,0,0
✓
요청 프로세스 하나라서 batch로 고정한 internal은 돌지 않습니다.
gateway 뒤의 여러 프로세스
2,1,0
✓
✓
✓
요청 프로세스 두 개와 batch worker 하나입니다.
✓함안 함
상태 확인 probe
프로세스가 하나면 재시작해 줄 gateway가 없어서 kubelet이 그 일을 맡습니다. chart는 probe 세 개를
/_akan/app/health로 보내고, solo 프로세스가 이 경로에 직접 응답합니다.| Probe | 주기 | 타임아웃 | 실패 한도 |
|---|---|---|---|
| ↳ 하는 일 | |||
| startupProbe | 5s | 1s | 24 |
| SSR은 라우트 산출물을 불러온 뒤에야 요청을 받으므로 부팅을 최대 2분까지 기다립니다. | |||
| livenessProbe | 15s | 3s | 3 |
| 세 번 연속 실패하면 멈춘 서버를 재시작합니다. | |||
| readinessProbe | 10s | 3s | 2 |
| 두 번 연속 실패하면 pod를 Service에서 뺍니다. | |||
- 종료 유예는 40초입니다. 서버는 종료할 때 최대 30초 동안 하던 일을 마무리합니다(
AKAN_SHUTDOWN_TIMEOUT_MS). 10초의 여유 덕분에 마무리가 끝나는 순간 SIGKILL이 떨어지지 않습니다. - gateway가 있으면 자식 프로세스는 gateway가 재시작합니다.
2,1,0처럼 프로세스가 여럿이면 죽은 프로세스를 gateway가 다시 띄우고, probe는 그대로 pod를 지켜봅니다.
콘솔 열기
빌드한 이미지에는
console.js가 이미 들어 있습니다. 실행 중인 pod 안에서 kubectl exec로 엽니다.Terminal
- 네임스페이스와 컨테이너.
-n에는<appName>-<branch>를 적고, 컨테이너 이름은 언제나app입니다. AKAN_CONSOLE=1은 이 명령에만 붙입니다. 이미지는 브랜치와 상관없이 운영 모드로 돌기 때문에, 이 값이 없으면 console이 열리지 않습니다.- 별도 프로세스입니다. console은 pod 안에서 요청을 받지 않고 internal 작업과 큐 worker도 돌리지 않는 서버 프로세스를 따로 띄웁니다. 실행 중인
main.js의 메모리에 붙는 것이 아닙니다. - 실행 중인 서버의 로그는 볼 수 있습니다.
.tail과.trace가 runtime 디렉터리의 제어 소켓을 통해 가져옵니다.
꿀팁
- request는 작게 시작합니다. 메트릭을 확인한 뒤에 limit을 올립니다.
- 스토리지는 미리 늘립니다. sqlite PVC는 급해지기 전에 여유 있게 키웁니다.
- 호스트는 빠짐없이 적습니다.
subRoutes와domains를 명시하고akan.config.ts의routes와 맞춰 두면 Ingress 규칙을 예측할 수 있습니다. chart는 호스트를 열어 줄 뿐이고, 어느 basePath가 응답할지는 앱이 정합니다. helm upgrade를 직접 돌렸다면 rollout을 재시작합니다. 기본 태그<branch>-live는 빌드가 바뀌어도 이름이 같아서 Deployment가 바뀌지 않습니다.kubectl rollout restart전까지 pod는 이전 빌드로 돕니다.
함께 보기