GitHub·GitLab부터 AWS EKS 배포·관측·복구까지
인프라 초보자가 FlowNote 하나로 Git, Docker, ECR, EKS Auto Mode, OIDC CI/CD, Kubernetes 운영과 장애 복구를 완주한다.
상태: 출간 후보 웹교정쇄 · AWS 현장 검수 대기 · 28개 장
이 책은 클라우드 용어를 외우는 책이 아니다. 코드 한 줄이 Git에 들어간 뒤 검토되고, container image가 되고, Amazon ECR에 보관되고, Amazon EKS에서 안전하게 교체되고, 문제가 생기면 이전 버전으로 돌아가는 전 과정을 한 서비스로 완주한다.
주 실습 서비스는 FlowNote다. 현재 실행 환경·버전·commit SHA를 보여 주고 liveness와 readiness endpoint를 제공한다. 화려한 기능보다 “어떤 코드가 지금 어느 환경에서 실행되는가”를 눈으로 확인하기 좋게 설계했다. 예제의 ACCOUNT_ID, role ARN, domain은 독자의 값으로 바꿔야 한다.
비용 주의: EKS control plane, EC2 managed instance, load balancer, NAT gateway, CloudWatch Logs 등은 비용이 발생할 수 있다. 각 실습 끝의 확인과 최종 삭제 절차를 같은 작업으로 취급한다. 책을 덮기 전에 28장의 비용 잔존물 점검을 완료한다.
이 책을 마치면 다음을 설명하고 수행할 수 있다.
- GitHub와 GitLab 중 무엇을 써도 동일한 CI/CD 원리를 설명한다.
- VPC·subnet·route·NAT·security group이 요청 경로에서 맡는 역할을 그린다.
- EKS Auto Mode cluster를 선언적으로 만들고 Access Entry로 접근을 분리한다.
- Deployment·Service·Ingress·probe·resource·PDB를 이유와 함께 작성한다.
- 장기 AWS access key 없이 OIDC로 CI job에 짧은 권한을 준다.
- immutable image를 build once, promote many 원칙으로 배포한다.
- 장애를 DNS부터 Pod·node·IAM까지 증거 순서로 진단한다.
- 비용·보안·관측·upgrade·rollback·삭제를 배포 정의에 포함한다.
1장 브라우저 요청 하나를 끝까지 따라간다
사용자가 https://app.example.invalid을 연다고 하자. DNS가 이름을 load balancer 주소로 바꾸고, TLS 연결 뒤 HTTP 요청이 ALB listener에 도착한다. Ingress rule은 path와 host를 보고 Kubernetes Service를 고른다. Service selector가 Ready 상태인 Pod의 EndpointSlice를 만들고, ALB는 Pod IP의 8080 port로 보낸다. FlowNote process가 응답하면 같은 길을 거슬러 브라우저로 돌아온다.
여기서 VPC는 “서버 한 대”가 아니라 주소·route·보안 경계를 가진 network다. EKS cluster는 Kubernetes control plane과 workload가 만나는 관리 단위다. Node는 Pod를 실행할 compute이고, Pod는 하나 이상의 container가 함께 배치되는 최소 실행 단위다. Service는 바뀌는 Pod 주소 앞의 안정된 이름이며, Ingress는 HTTP routing 선언이다.
처음에는 명령보다 경계표를 만든다.
| 경계 | 입력 | 출력 | 실패 증거 |
|---|---|---|---|
| DNS | domain | ALB name/address | dig, hosted zone |
| ALB | TLS·HTTP | target request | listener·target health |
| Ingress | host·path | Service | kubectl describe ingress |
| Service | selector·port | EndpointSlice | kubectl get endpointslice |
| Pod | HTTP request | response | probe·log·event |
| AWS IAM | signed request | allow/deny | CloudTrail·AccessDenied |
장애 때 “EKS가 이상하다”라고 말하지 않는다. 어느 경계까지 성공했고 다음 경계에서 어떤 증거가 달라졌는지 말한다. 이 습관이 클라우드를 작게 만든다.
2장 Git·CI·registry·CD의 책임을 분리한다
Git은 원하는 코드와 설정의 history다. GitHub 또는 GitLab은 원격 저장소·review·automation trigger를 제공한다. CI는 test하고 image를 만든다. Registry인 ECR은 배포 가능한 image와 digest를 보관한다. CD는 원하는 image digest를 cluster에 반영하고 rollout 결과를 관찰한다.
좋은 pipeline은 “main에 push했으니 production 완료”라고 단정하지 않는다. 각 단계는 다음 단계가 확인할 증거를 남긴다.
source evidence commit SHA + review
build evidence test result + image digest + SBOM
security evidence vulnerability finding + exception owner
change evidence approver + target environment + time
runtime evidence rollout revision + ready replicas + smoke result
image에 latest만 쓰면 source와 runtime 사이가 끊어진다. 이 책은 Git SHA를 image tag로 쓰고, 더 엄격한 환경에서는 registry digest까지 기록한다. staging에서 검증한 image를 production에서 다시 build하지 않는다. 다시 build하면 같은 source라도 base image나 package repository 상태 때문에 다른 artifact가 나올 수 있다.
GitHub Actions와 GitLab CI의 YAML 문법은 다르지만 계약은 같다. job identity를 OIDC token으로 증명하고, AWS STS가 제한된 role의 temporary credential을 발급하고, build role과 deploy role을 분리한다.
3장 EKS를 쓰지 않아도 되는 경우부터 판단한다
Kubernetes는 많은 workload를 선언적으로 배치하고 복구하는 강력한 platform이다. 동시에 API version, scheduling, network, identity, policy, observability, upgrade를 운영해야 한다. 작은 단일 웹 앱이라면 AWS App Runner, ECS/Fargate, Lambda가 더 단순할 수 있다.
다음 질문에서 “예”가 많을수록 EKS의 이유가 생긴다.
- 여러 팀이 공통 deployment policy와 platform API를 써야 하는가?
- 여러 종류의 long-running service·worker·scheduled job을 운영하는가?
- Kubernetes ecosystem이나 portable workload contract가 필요한가?
- traffic·resource·placement를 세밀하게 제어해야 하는가?
- cluster lifecycle과 incident response를 맡을 platform owner가 있는가?
“요즘 대세”는 선택 근거가 아니다. EKS를 택했다면 ADR에 대안과 비용을 남긴다.
ADR-001: FlowNote lab은 EKS Auto Mode를 사용한다.
상황: 팀은 Kubernetes deployment contract를 학습해야 한다.
선택: EKS Auto Mode + eksctl + ALB Ingress.
대안: ECS/Fargate가 더 단순하지만 Kubernetes API 학습 목표를 충족하지 않는다.
결과: AWS가 node와 핵심 networking component를 더 관리하지만,
application availability·security·cost·manifest는 팀 책임이다.
4장 공유 책임과 비용 책임을 같은 그림에 놓는다
EKS Auto Mode는 control plane뿐 아니라 node provisioning, compute autoscaling, Pod networking, load balancing, block storage 같은 영역을 더 자동화한다. 하지만 container 취약점, probe, resource request, replica, PDB, application log, data backup은 사라지지 않는다. “managed”는 “아무도 운영하지 않아도 됨”이 아니다.
비용도 resource별로 생각한다.
| 비용 축 | 생성 계기 | 줄이는 법 | 삭제 증거 |
|---|---|---|---|
| EKS cluster | cluster 생성 | lab 시간을 제한 | cluster 목록에서 없음 |
| compute | Pod request | right-size·replica·NodePool ceiling | managed node 없음 |
| ALB | Ingress 생성 | 공유 정책·불필요 Ingress 제거 | load balancer 없음 |
| NAT | private egress | endpoint·architecture 검토 | NAT와 EIP 삭제 |
| logs | logging 활성화 | retention 지정 | log group retention/삭제 |
| ECR | image push | lifecycle policy | 불필요 digest 없음 |
가격표 숫자를 본문에 박아 넣지 않는다. region과 시점에 따라 바뀌기 때문이다. 생성 직전 AWS Pricing Calculator와 서비스 pricing page에서 예상 월 비용을 기록하고, AWS Budget 경보는 비용을 막는 차단기가 아니라 알려 주는 경보다.
5장 실습 안전선을 만들고 도구를 확인한다
실습 계정은 가능하면 production account와 분리한다. root user에는 MFA를 설정하고 일상 작업에 쓰지 않는다. 조직에서는 IAM Identity Center나 federation으로 short-lived role을 사용한다. 개인 학습에서도 별도 account·budget·tag를 권한다.
git --version
docker --version
aws --version
kubectl version --client
eksctl version
jq --version
명령이 존재하는 것과 올바른 identity로 실행되는 것은 다르다.
aws sts get-caller-identity
aws configure get region
aws eks describe-cluster-versions \
--query 'clusterVersions[?status==`STANDARD_SUPPORT`].[clusterVersion,endOfStandardSupportDate]' \
--output table
출력의 account ID, ARN, region을 실습 기록에 남긴다. access key 자체는 기록하지 않는다. 명령을 실행하기 전에 다음 세 가지를 말할 수 있어야 한다.
- 어느 account와 region을 바꾸는가?
- 비용이 생기는 resource는 무엇인가?
- 되돌리거나 삭제하는 명령과 확인 명령은 무엇인가?
2부 저장소에서 배포 가능한 image를 만든다
6장 저장소를 애플리케이션과 운영 계약으로 나눈다
FlowNote 저장소는 다음처럼 구성한다.
examples/
app/ # server.mjs, Dockerfile
cluster/cluster.yaml # EKS Auto Mode 선언
k8s/ # namespace, workload, availability, ingress, policy
pipelines/ # GitHub Actions, GitLab CI 비교본
docs/ # ADR, runbook, release evidence
application과 manifest를 같은 repository에 둘지 별도의 configuration repository로 나눌지는 팀 규모에 따라 결정한다. 처음에는 한 repository가 추적하기 쉽다. production에서는 build 권한과 deployment 변경 승인 주체가 다르면 분리할 가치가 생긴다.
branch policy의 핵심은 이름이 아니라 protected state로 들어가는 조건이다. main에는 pull request 또는 merge request, 최소 한 명 review, required test, 직접 push 제한을 둔다. 긴 develop branch를 자동으로 정답으로 취급하지 않는다. 짧은 feature branch와 자주 통합하는 흐름이 conflict와 배포 차이를 줄인다.
commit에는 결과가 아니라 의도를 적는다.
feat: expose separate readiness endpoint
fix: keep zero unavailable pods during rollout
ops: cap FlowNote namespace resource usage
7장 FlowNote를 로컬에서 실행하고 건강 상태를 분리한다
프로젝트 root에서 실행한다.
npm run app
curl -s http://127.0.0.1:8080/api/runtime | jq
curl -i http://127.0.0.1:8080/health/live
curl -i http://127.0.0.1:8080/health/ready
liveness는 process가 교착되어 재시작이 필요한지를 묻는다. readiness는 지금 traffic을 받아도 되는지를 묻는다. DB가 잠시 느리다고 liveness를 실패시키면 모든 replica가 재시작되어 장애를 키울 수 있다. 반대로 readiness가 항상 200이면 준비되지 않은 Pod에 traffic이 간다. startup probe는 느린 초기화 동안 liveness가 성급하게 죽이지 않게 한다.
FlowNote는 학습을 위해 dependency가 없다. 실무 서비스의 readiness는 필수 의존성만 확인하고, 외부 시스템 전체를 연쇄 호출하지 않는다. /api/runtime의 version과 commit은 smoke test와 incident timeline을 연결한다. secret이나 internal host는 절대 노출하지 않는다.
8장 container image를 작은 실행 계약으로 만든다
examples/app/Dockerfile은 Node 24 기반, non-root user, 고정 port라는 최소 계약을 표현한다.
docker build -t flownote:book examples/app
docker run --rm -p 18080:8080 \
-e APP_VERSION=book-local \
-e GIT_SHA=$(git rev-parse --short HEAD 2>/dev/null || echo local) \
flownote:book
다른 terminal에서 확인한다.
curl --fail http://127.0.0.1:18080/health/ready
docker image inspect flownote:book --format '{{json .Config.User}}'
image에 .env, AWS credential, private key를 COPY하지 않는다. build secret이 필요하면 BuildKit secret mount를 쓰고 layer에 남지 않는지 검사한다. base image tag는 학습 예제에서 읽기 쉬운 major tag를 사용했지만 release pipeline에서는 검토한 digest로 pin하고 자동 update PR로 교체한다.
multi-stage build는 compiler와 runtime을 나누는 데 유용하지만 무조건 작은 image를 보장하지 않는다. 최종 stage에 필요한 artifact만 복사하고 SBOM과 provenance를 함께 생성한다.
9장 ECR에는 사람이 읽는 tag와 기계가 믿는 digest를 남긴다
repository를 만들고 scan-on-push와 tag immutability 정책을 조직 표준에 맞게 설정한다.
aws ecr create-repository \
--repository-name flownote \
--image-tag-mutability IMMUTABLE \
--image-scanning-configuration scanOnPush=true \
--region ap-northeast-2
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
REGISTRY="$ACCOUNT_ID.dkr.ecr.ap-northeast-2.amazonaws.com"
SHA=$(git rev-parse HEAD)
aws ecr get-login-password --region ap-northeast-2 \
| docker login --username AWS --password-stdin "$REGISTRY"
docker tag flownote:book "$REGISTRY/flownote:$SHA"
docker push "$REGISTRY/flownote:$SHA"
docker login password를 shell history에 쓰지 않고 stdin으로 전달한다. push 뒤 digest를 확인한다.
aws ecr describe-images --repository-name flownote \
--image-ids imageTag="$SHA" \
--query 'imageDetails[0].[imageDigest,imagePushedAt]' --output table
ECR basic scanning은 OS vulnerability를 중심으로 보고, enhanced scanning은 Inspector와 연동해 OS와 language package를 지속 평가한다. finding이 있다고 무조건 배포 금지하거나, 없다고 안전을 보증하지 않는다. severity·exploitability·노출 경로·수정 가능 version·예외 만료일을 정책으로 만든다.
10장 build와 deploy 권한을 하나의 관리자 role로 합치지 않는다
build job은 ECR authorization token과 특정 repository push가 필요하다. deploy job은 cluster 정보를 읽고 EKS Kubernetes API에 제한된 작업을 수행한다. cluster 생성 role은 VPC·IAM·EKS를 만들 수 있으므로 pipeline의 일상 deploy role로 쓰지 않는다.
권한을 세 개로 나눈다.
| Role | 맡는 주체 | 허용 예 | 금지 예 |
|---|---|---|---|
| bootstrap | platform admin | cluster·OIDC·role 생성 | 일상 app deploy |
| build | CI build job | ECR push·scan read | EKS admin |
| deploy | 승인된 deploy job | cluster describe·namespace 배포 | IAM 변경·다른 namespace |
IAM permission만 줄이면 충분하지 않다. role trust policy에서 repository, branch 또는 protected environment를 제한한다. AWS 권한과 Kubernetes RBAC는 직렬 gate다. IAM role이 cluster에 인증되어도 Access Entry·access policy·RoleBinding이 허용하지 않으면 resource를 바꾸지 못해야 한다.
3부 AWS network와 EKS cluster를 만든다
11장 VPC를 집, subnet을 방으로 외우지 않는다
비유는 시작에 도움 되지만 route를 설명하지 못하면 실무에서 멈춘다. VPC CIDR은 사용할 private address 범위다. Subnet은 한 Availability Zone 안의 address 구간이다. Route table은 destination별 다음 hop을 결정한다. Internet Gateway는 public route의 출입구이고 NAT Gateway는 private subnet workload가 외부로 나갈 때 사용하는 translation 지점이다.
두 AZ에 public subnet과 private subnet을 둔다. ALB는 public subnet에, EKS managed node와 Pod는 private subnet에 배치한다. public subnet은 “public IP가 무조건 붙는 곳”이 아니라 Internet Gateway로 가는 route가 있는 subnet이다.
Internet
↓
Internet Gateway
↓
public subnet A/B: ALB, NAT Gateway
↓ private route
private subnet A/B: EKS Auto Mode node와 Pod
Security Group은 stateful virtual firewall이다. Network ACL과 혼동하지 않는다. 허용 규칙을 “0.0.0.0/0이 편함”으로 시작하지 않는다. ALB의 443은 필요한 source에, workload port는 ALB가 사용하는 security group에서만 허용하는 관계를 목표로 한다.
CIDR은 미래 Pod 수까지 고려한다. EKS에서 Pod가 VPC address를 소비하므로 작은 subnet은 EC2가 남아 있어도 IP 부족으로 Pod가 Pending이 될 수 있다. 운영 전 availableIpAddressCount, 예상 replica, rollout surge, autoscaling buffer를 함께 계산한다.
12장 EKS Auto Mode와 Managed Node Group의 차이를 선택한다
Standard EKS에서 Managed Node Group은 EC2 lifecycle의 일부를 AWS가 돕지만 node group 크기·AMI update·autoscaling component 같은 결정이 남는다. Self-managed Karpenter는 instance 선택이 유연하지만 controller 운영 책임이 팀에 있다. Auto Mode는 Karpenter 기반 compute scaling, managed node OS, networking, load balancing, storage component를 더 AWS 쪽으로 이동한다.
Auto Mode managed instance에는 직접 SSH나 SSM으로 들어가 패키지를 설치하는 운영 방식을 전제로 하지 않는다. node를 수리하는 pet이 아니라 교체되는 appliance로 본다. daemon을 host에 깔아야 하거나 특수 kernel 설정이 필요한 workload는 제약을 먼저 확인한다.
이 책이 Auto Mode를 고른 이유는 초보자가 Kubernetes workload contract에 집중하도록 하기 위해서다. 실무 선택표는 다음과 같다.
| 조건 | 우선 검토 |
|---|---|
| 일반 웹·API, 운영 부담 최소화 | EKS Auto Mode |
| 기존 node group 표준과 daemon 의존 | Managed Node Group |
| 세밀한 provisioning 정책을 직접 운영할 역량 | self-managed Karpenter |
| Kubernetes 자체가 불필요 | ECS/Fargate·App Runner 등 |
13장 “최신 version” 대신 지원 기간을 확인한다
2026년 8월 12일 AWS 공식 EKS lifecycle 문서에서 standard support version은 1.36, 1.35, 1.34, 1.33이다. 이 숫자는 곧 바뀐다. 따라서 책의 1.36을 맹목적으로 복사하지 말고 생성 당일 조회한다.
aws eks describe-cluster-versions \
--query 'clusterVersions[].{version:clusterVersion,status:status,standardEnd:endOfStandardSupportDate}' \
--output table
EKS minor version은 처음 14개월 standard support, 이후 12개월 extended support를 제공한다. extended support는 추가 비용이 있으므로 “나중에 upgrade”가 비용 정책이 된다. 새 cluster는 application과 add-on이 지원하는 가장 최신 standard version을 택한다.
upgrade는 control plane 숫자 변경이 아니다. deprecated API, admission policy, controller, workload manifest, node, add-on, client kubectl을 확인한다. 한 번에 한 minor version씩 올리고 pre-production에서 같은 순서를 연습한다. in-place upgrade의 rollback window 같은 기능은 정책과 조건이 바뀔 수 있으므로 변경 당일 공식 문서를 확인한다.
14장 eksctl 선언으로 lab cluster를 만든다
examples/cluster/cluster.yaml을 연다. metadata.region과 version, logging retention을 확인한다. Auto Mode의 기본 NodePool을 사용하므로 nodePools를 굳이 지정하지 않는다. Access Entry를 쓰기 위해 authentication mode는 API로 둔다.
eksctl create cluster -f examples/cluster/cluster.yaml
생성은 여러 AWS resource를 만들고 시간이 걸린다. terminal을 닫기 전에 CloudFormation stack과 EKS cluster status를 확인한다.
aws eks describe-cluster --name flownote-lab \
--query 'cluster.{status:status,version:version,endpoint:endpoint}'
kubectl get nodes -o wide
kubectl get nodepool
kubectl get nodeclass
처음에는 workload가 없어 Auto Mode node가 보이지 않을 수 있다. Pod가 scheduling을 요청하면 compute가 생긴다. EC2 console에서 instance가 안 보인다고 즉시 실패로 판단하지 않는다. 2026년 4월 이후 새 Auto Mode managed resource는 기본 list view에서 숨겨질 수 있으므로 EKS compute tab, kubectl get nodes, managed resource visibility 설정을 확인한다.
생성 실패는 마지막 error 한 줄만 보지 않는다.
eksctl utils describe-stacks --cluster flownote-lab
aws cloudformation describe-stack-events \
--stack-name eksctl-flownote-lab-cluster \
--query 'StackEvents[?ResourceStatus==`CREATE_FAILED`].[LogicalResourceId,ResourceStatusReason]'
15장 kubeconfig·Access Entry·RBAC 세 문을 구분한다
aws eks update-kubeconfig는 cluster endpoint와 authentication command를 local kubeconfig에 적는다. 권한 자체를 새로 주는 명령이 아니다.
aws eks update-kubeconfig --name flownote-lab --region ap-northeast-2
kubectl config current-context
kubectl auth can-i get pods --all-namespaces
사람은 federation role, CI는 전용 deploy role을 사용한다. Access Entry는 IAM principal을 EKS access policy 또는 Kubernetes group에 연결한다. namespace 단위 application deploy라면 cluster-admin을 주지 않는다.
aws eks create-access-entry \
--cluster-name flownote-lab \
--principal-arn arn:aws:iam::111122223333:role/FlowNoteDeployRole \
--type STANDARD
aws eks associate-access-policy \
--cluster-name flownote-lab \
--principal-arn arn:aws:iam::111122223333:role/FlowNoteDeployRole \
--policy-arn arn:aws:eks::aws:cluster-access-policy/AmazonEKSEditPolicy \
--access-scope type=namespace,namespaces=flownote
AWS access policy는 편리한 시작점이다. 더 세밀한 verb·resource 제한이 필요하면 Access Entry의 Kubernetes group과 Role/RoleBinding을 사용한다. kubectl auth can-i --list -n flownote 출력은 identity가 실제로 가진 Kubernetes 권한을 확인하는 증거다.
4부 Kubernetes에 서비스를 안전하게 올린다
16장 YAML을 주문서가 아니라 reconciliation 계약으로 읽는다
Kubernetes object에는 desired state가 있다. Deployment controller는 replica 수와 Pod template을 보고 실제 상태를 맞춘다. Pod가 죽으면 새 Pod를 만들고, image가 바뀌면 새 ReplicaSet으로 점진적으로 교체한다. kubectl apply가 Pod를 직접 계속 관리하는 것이 아니다.
핵심 object 관계를 먼저 외운다.
Namespace
├─ ConfigMap
├─ ServiceAccount
├─ Deployment → ReplicaSet → Pod
├─ Service → EndpointSlice → Ready Pod
├─ HPA → Deployment replicas 조정
├─ PDB → voluntary eviction 허용량
└─ Ingress → ALB → Service
metadata.labels는 장식이 아니다. Service selector, PDB selector, monitoring grouping을 연결한다. selector가 template label과 다르면 Pod가 Running이어도 traffic이 가지 않는다. YAML indentation만 확인하지 말고 reference가 실제 이름과 label을 가리키는지 검사한다.
kubectl apply --dry-run=client -f examples/k8s/00-namespace.yaml
kubectl apply --dry-run=client -f examples/k8s/10-app.yaml
kubectl diff -f examples/k8s/
client dry-run은 schema와 일부 형식을 확인할 뿐 cluster의 custom resource, admission policy, IAM 권한을 보증하지 않는다. production 전 server-side dry-run과 별도 environment 검증이 필요하다.
17장 Namespace·Quota로 실습의 폭발 반경을 자른다
먼저 namespace와 quota를 적용한다.
kubectl apply -f examples/k8s/00-namespace.yaml
kubectl get resourcequota -n flownote
kubectl describe resourcequota flownote-budget -n flownote
Namespace는 완전한 보안 경계가 아니다. cluster-scoped resource, shared node, network policy 지원 여부, IAM과 admission policy를 함께 봐야 한다. 그래도 이름 충돌, RBAC scope, quota, lifecycle을 나누는 중요한 단위다.
ResourceQuota만 만들고 container request/limit을 빠뜨리면 admission 또는 scheduling에서 예상과 다른 결과가 날 수 있다. LimitRange로 default를 줄 수도 있지만, source manifest에 workload 의도를 명시하는 편이 review에 유리하다.
quota 실패는 다음처럼 보인다.
Error from server (Forbidden): exceeded quota: flownote-budget,
requested: requests.cpu=3, used: requests.cpu=1, limited: requests.cpu=2
이때 node를 늘리기 전에 namespace budget과 새 release의 request 합계를 비교한다. rollout 중에는 old Pod와 surge Pod가 함께 있어 평소보다 더 많은 quota가 필요하다.
18장 Deployment·Service로 첫 내부 배포를 완주한다
먼저 manifest의 placeholder를 실제 ECR image로 바꾼 복사본을 만든다. 원본을 sed -i로 훼손하지 않고 envsubst, Kustomize 또는 pipeline의 kubectl set image를 사용한다.
kubectl apply -f examples/k8s/10-app.yaml
kubectl -n flownote set image deployment/flownote \
app="$ACCOUNT_ID.dkr.ecr.ap-northeast-2.amazonaws.com/flownote:$SHA"
kubectl -n flownote rollout status deployment/flownote --timeout=5m
kubectl -n flownote get pod,service,endpointslice -o wide
외부 ALB를 만들기 전에 port-forward로 내부 계약을 검사한다.
kubectl -n flownote port-forward service/flownote 18080:80
curl --fail http://127.0.0.1:18080/health/ready
curl -s http://127.0.0.1:18080/api/runtime | jq
Service의 port: 80은 client가 보는 port이고 targetPort: http는 container port의 name을 가리킨다. name을 사용하면 container port 숫자가 바뀌어도 Service reference가 더 명확하다. EndpointSlice에 address가 없다면 Pod phase보다 readiness와 selector를 먼저 본다.
19장 probe·resource·securityContext를 장애 예방 장치로 쓴다
공식 Kubernetes 문서의 경계는 명확하다. request는 scheduler가 Pod를 놓을 node를 판단하는 기준이고, limit은 runtime에서 사용량을 제한하는 기준이다. CPU limit 초과는 throttling, memory limit 초과는 OOMKill로 나타날 수 있다.
FlowNote의 시작값은 측정 전 가설이다.
resources:
requests: { cpu: 100m, memory: 96Mi }
limits: { cpu: 500m, memory: 256Mi }
실제 traffic에서 usage percentile, latency, throttling, OOM, node bin-packing을 보고 조정한다. request를 무조건 낮추면 scheduler가 node를 과밀하게 채우고, 너무 높이면 node가 불필요하게 생긴다.
runAsNonRoot, allowPrivilegeEscalation: false, readOnlyRootFilesystem, capability drop은 기본 방어선이다. 애플리케이션이 임시 파일을 써야 한다면 root filesystem write를 다시 여는 대신 emptyDir volume을 필요한 path에 mount한다. container가 root를 요구한다면 이유와 제거 계획을 기록한다.
20장 ALB Ingress로 외부 traffic을 연결한다
EKS Auto Mode의 최신 ALB 경로는 기존 AWS Load Balancer Controller 예제와 API가 다르다. IngressClassParams는 eks.amazonaws.com/v1, controller는 eks.amazonaws.com/alb를 사용한다. 오래된 블로그의 kubernetes.io/ingress.class: alb를 그대로 섞지 않는다.
kubectl apply -f examples/k8s/30-ingress.yaml
kubectl -n flownote get ingress flownote --watch
hostname이 생기면 확인한다.
ALB=$(kubectl -n flownote get ingress flownote \
-o jsonpath='{.status.loadBalancer.ingress[0].hostname}')
curl --fail "http://$ALB/health/ready"
ALB 생성이 늦으면 Ingress event, subnet tag, cluster role permission, target health를 순서대로 본다. EKS Auto Mode cluster를 eksctl로 새로 만들었다면 subnet discovery tag가 준비되지만, 기존 VPC를 가져오면 public/private tag를 직접 확인한다.
실무 HTTPS에서는 ACM certificate, Route 53 alias, 443 listener, HTTP→HTTPS redirect, security policy를 설계한다. certificate ARN은 environment별 설정이며 공개 repository의 범용 manifest에 production 값을 고정하지 않는다. DNS cutover 전 ALB hostname과 Host header로 smoke test하고 TTL·rollback을 준비한다.
5부 GitHub와 GitLab에서 안전하게 자동 배포한다
21장 OIDC로 장기 access key를 pipeline에서 없앤다
CI secret에 AWS_ACCESS_KEY_ID와 AWS_SECRET_ACCESS_KEY를 넣으면 유출·회전·퇴사자·복제 repository 문제가 따라온다. OIDC에서는 CI provider가 job identity를 signed token으로 발급하고, AWS STS가 IAM role의 trust condition과 비교한 뒤 짧은 session credential을 돌려준다.
두 policy를 구분한다.
- trust policy: 누가 이 role을 맡을 수 있는가. provider, audience, repository/project, branch/environment를 제한한다.
- permission policy: role을 맡은 뒤 무엇을 할 수 있는가. ECR repository, EKS cluster, 필요한 action을 제한한다.
GitHub trust의 개념 예시는 다음과 같다. account·organization·repository는 실제 값으로 바꾼다.
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::111122223333:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
"token.actions.githubusercontent.com:sub": "repo:WITHAI/flownote:environment:production"
}
}
}]
}
wildcard로 모든 repository·branch를 믿지 않는다. reusable workflow를 중앙 통제한다면 AWS가 지원하는 claim과 GitHub token claim 변경 사항을 공식 문서에서 재확인한다. token payload를 log에 통째로 출력하지 않는다.
22장 GitHub Actions에서 build once를 구현한다
examples/pipelines/github-actions.yaml의 build job은 다음 순서다.
- source checkout
id-token: write로 GitHub OIDC token 요청 권한 부여- build role assume
- ECR login
- Git SHA tag로 image build·push
- image URI를 deploy job output으로 전달
id-token: write는 AWS resource를 직접 쓸 권한이 아니다. OIDC token을 요청할 권한이며, AWS trust와 permission이 추가로 통과해야 한다. job-level permissions를 최소화하고 pull request job에는 production role이 발급되지 않게 environment와 event 조건을 나눈다.
예제는 이해를 위해 verified action의 major tag를 쓴다. production은 action source를 review하고 full commit SHA로 pin하며 Dependabot 또는 Renovate가 update PR을 만들게 한다. third-party action은 repository secret과 token에 접근할 supply-chain code이므로 dependency와 같다.
image output을 눈으로 확인한다.
111122223333.dkr.ecr.ap-northeast-2.amazonaws.com/flownote:
8f30c51f0d8f42d2...
tag를 줄여 사람이 보기 쉽게 만들더라도 배포 증거에는 full SHA와 digest를 보관한다. provenance와 SBOM 생성이 실제 registry에 첨부되는지 사용 중인 build action·registry 조합에서 검증한다.
23장 GitHub production deploy에 승인과 rollout gate를 둔다
deploy job은 build가 성공해야 시작하고 GitHub environment: production의 reviewer·branch protection을 적용한다. 승인 화면에는 commit, image digest, 변경 요약, migration 여부, rollback image를 보여 주어야 한다. 의미 없는 “승인” 버튼은 통제 장치가 아니다.

aws eks update-kubeconfig --name flownote-lab --region "$AWS_REGION"
kubectl -n flownote set image deployment/flownote app="$IMAGE"
kubectl -n flownote rollout status deployment/flownote --timeout=5m
rollout status 성공은 새 replica가 Available이라는 뜻이지 business 기능 전체가 정상이라는 뜻은 아니다. 외부 smoke와 error rate를 추가한다.
curl --fail --retry 5 --retry-all-errors \
"https://app.example.invalid/health/ready"
curl --fail "https://app.example.invalid/api/runtime" | jq -e \
--arg sha "$GITHUB_SHA" '.commit == $sha'
실패하면 새 build부터 하지 않는다. 먼저 상태와 event를 보존하고 이전 image로 되돌린다.
kubectl -n flownote rollout history deployment/flownote
kubectl -n flownote rollout undo deployment/flownote
kubectl -n flownote rollout status deployment/flownote --timeout=5m
DB schema change가 이전 application과 호환되지 않으면 image rollback만으로 복구되지 않는다. expand→migrate→contract 순서로 backward-compatible migration을 설계한다.
24장 GitLab CI에서 같은 계약을 ID token으로 옮긴다
GitLab은 job의 id_tokens에서 audience를 지정하고 AssumeRoleWithWebIdentity로 temporary credential을 받는다. CI_JOB_JWT_V2는 GitLab 17.0에서 제거되었으므로 오래된 예제를 따라가지 않는다.
GitLab.com에서는 AWS trust condition에 stable namespace_id, project_id를 지원하는지 공식 문서에서 확인하고 path 기반 sub와 함께 사용한다. group·project rename에도 identity가 뜻밖에 바뀌지 않게 하기 위해서다. Self-Managed는 공개 가능한 OIDC discovery endpoint와 올바른 certificate chain이 필요하다.
examples/pipelines/gitlab-ci.yml의 build job은 다음 evidence를 남긴다.
CI_PROJECT_ID + CI_PIPELINE_ID
CI_COMMIT_SHA
assumed role ARN
ECR image URI + digest
scan result
deploy job에는 protected environment와 manual approval을 둔다. GitLab Runner 자체가 privileged Docker-in-Docker를 쓰면 runner isolation과 network·cache·concurrent job 위험을 별도로 review한다. 조직에서는 rootless BuildKit, managed build service 또는 격리된 ephemeral runner를 비교한다.
GitHub에서 GitLab으로 옮길 때 YAML을 번역하는 데 집중하지 않는다. 다음 invariant를 보존한다.
- untrusted merge request는 production role을 못 맡는다.
- build와 deploy role이 분리된다.
- image는 SHA 또는 digest로 불변 식별된다.
- production에는 사람 승인과 protected branch/tag가 있다.
- rollout·smoke·rollback 증거가 남는다.
25장 direct deploy와 GitOps를 환경에 맞게 선택한다
이 책의 main path는 CI job이 kubectl set image를 실행하는 push-based deploy다. 흐름이 눈에 보여 처음 배우기 쉽다. 그러나 CI runner가 cluster network와 deploy credential을 가져야 하고, 실제 상태가 Git manifest와 달라질 수 있다.
GitOps에서는 Argo CD 같은 in-cluster reconciler가 Git의 desired state를 pull한다. CI는 image를 만들고 configuration repository에 새 digest PR을 만든다. review 후 merge되면 controller가 반영한다.
| 항목 | Direct CI deploy | GitOps pull |
|---|---|---|
| 시작 난이도 | 낮음 | controller·repository 추가 |
| cluster credential | CI에 필요 | controller 내부에 유지 |
| desired state 감사 | 별도 evidence 필요 | Git history가 중심 |
| 긴급 수동 변경 | drift 가능 | 자동 되돌림 가능 |
| 추천 | 작은 팀·학습·단순 환경 | 여러 cluster·엄격한 변경 통제 |
GitOps가 자동으로 안전을 보장하지 않는다. 잘못된 manifest가 merge되면 정확하게 잘못된 상태를 만든다. sync window, health check, rollback, secret strategy, controller upgrade와 repository 권한이 필요하다. 두 방식을 한 environment에서 무계획하게 섞어 서로 image를 덮어쓰게 하지 않는다.
6부 가용성·보안·비용을 운영한다
26장 rolling update·PDB·autoscaling을 하나의 가용성 계약으로 본다
FlowNote Deployment는 replica 3, maxUnavailable: 0, maxSurge: 1이다. 새 Pod가 Ready가 되기 전 old Pod를 줄이지 않아 application capacity를 보존하지만 rollout 순간 Pod가 4개가 되어 quota와 compute 여유가 필요하다.
Kubernetes API object 이름으로는 PDB가 PodDisruptionBudget, HPA가 HorizontalPodAutoscaler다. 약어만 외우지 말고 각각 eviction 허용량과 replica 목표를 조정하는 서로 다른 control loop라는 점을 구분한다.
PDB의 maxUnavailable: 1은 node drain 같은 voluntary disruption에서 동시에 unavailable 가능한 Pod를 제한한다. node hard failure까지 막는 보증이 아니다. replica 1에 PDB 0을 두면 maintenance가 영원히 막힐 수 있다.
HPA는 CPU utilization 65%를 목표로 3~10 replica를 조정한다. CPU request가 utilization 계산의 분모이므로 request가 부정확하면 scaling도 왜곡된다. HPA가 Pod를 늘리면 Auto Mode가 unschedulable Pod를 보고 node를 만들 수 있다. 이 두 control loop의 시간차를 고려해 latency spike와 cold start를 관찰한다.
kubectl apply -f examples/k8s/20-availability.yaml
kubectl -n flownote get deployment,hpa,pdb
kubectl -n flownote rollout restart deployment/flownote
kubectl -n flownote rollout status deployment/flownote
test environment에서는 의도적으로 readiness를 실패시키고 rollout이 멈추는지, 이전 Pod가 traffic을 유지하는지 본다. 단, production에서 장애 실험을 즉흥적으로 하지 않는다.
27장 observability와 장애 진단을 증거 순서로 훈련한다
EKS control plane logging은 API server, audit, authenticator, controller manager, scheduler log를 CloudWatch로 보낸다. 기본적으로 자동 전송되지 않으므로 필요한 type과 retention을 cluster 생성 때 선언한다. log ingestion과 storage 비용을 함께 추적한다.
application은 최소 다음 신호를 남긴다.
- request count·error rate·latency
- Pod restart·readiness failure·OOMKill
- desired/ready replica와 HPA 상태
- node provisioning·Pending Pod 이유
- ALB target health·5xx·response time
- release version·commit·deploy time
진단 runbook의 첫 묶음이다.
kubectl -n flownote get deployment,pod,service,endpointslice,ingress -o wide
kubectl -n flownote describe deployment flownote
kubectl -n flownote get events --sort-by=.lastTimestamp | tail -40
kubectl -n flownote logs deployment/flownote --all-pods --tail=100
kubectl -n flownote logs POD_NAME --previous --tail=100
kubectl get nodes
kubectl get nodepool
대표 증상을 경계로 분리한다.
| 증상 | 먼저 볼 증거 | 흔한 원인 |
|---|---|---|
ImagePullBackOff |
Pod event·ECR URI | tag 오타·permission·network |
CrashLoopBackOff |
current/previous log | process exit·config·liveness |
Pending |
scheduler event | request·quota·taint·capacity·IP |
| ALB 503 | target health·EndpointSlice | readiness·selector·port |
AccessDenied |
caller identity·CloudTrail | wrong role·permission scope |
Unauthorized kubectl |
kubeconfig·Access Entry | identity가 cluster access 없음 |
| rollout timeout | new ReplicaSet·probe | bad image·startup·quota |
28장 upgrade·복구·비용 정리까지 최종 실전을 완주한다
최종 실전은 새 version 배포가 아니라 운영 cycle 전체다.
- PR에서 application·manifest diff와 rollback plan을 review한다.
- CI가 test하고 SHA image를 ECR에 한 번 build한다.
- scan 결과와 예외를 확인한다.
- staging에 같은 digest를 배포해 probe·smoke·metrics를 본다.
- production 승인 후 같은 digest를 rollout한다.
- release evidence를 남기고 관찰 window를 지킨다.
- 실패하면 event·log·revision을 보존한 뒤 rollback한다.
cluster upgrade 전에는 다음을 실행한다.
aws eks list-insights --cluster-name flownote-lab --filter categories=UPGRADE_READINESS
kubectl api-resources
kubectl get --raw /metrics >/dev/null
control plane, managed component, workload를 순서대로 확인하고 한 minor version씩 올린다. data가 있다면 EBS snapshot, database backup, restore drill은 cluster backup과 분리해 검증한다. Kubernetes manifest만 보관해도 database 내용은 돌아오지 않는다.
학습을 마쳤다면 비용 순서대로 삭제한다. Ingress를 먼저 지워 ALB controller가 load balancer를 정리할 시간을 준다.
kubectl delete -f examples/k8s/30-ingress.yaml --ignore-not-found
kubectl delete namespace flownote --ignore-not-found
eksctl delete cluster -f examples/cluster/cluster.yaml --wait
aws ecr delete-repository --repository-name flownote --force \
--region ap-northeast-2
삭제 명령 성공이 끝이 아니다. 잔존물을 확인한다.
aws eks list-clusters --region ap-northeast-2
aws elbv2 describe-load-balancers --region ap-northeast-2
aws ec2 describe-nat-gateways --filter Name=state,Values=available,pending
aws ec2 describe-addresses
aws logs describe-log-groups --log-group-name-prefix /aws/eks/flownote-lab
aws ecr describe-repositories --region ap-northeast-2
실무 졸업 조건은 “화면이 한 번 열림”이 아니다.
- 다른 팀원이 빈 계정 또는 test account에서 문서만 보고 재현한다.
- repository와 pipeline에 장기 AWS key가 없다.
- build role과 deploy role, 사람 access가 분리되어 있다.
- image SHA/digest와 runtime version이 일치한다.
- readiness 실패가 traffic을 막고 liveness가 불필요한 재시작을 만들지 않는다.
- rollout·smoke 실패가 자동 중단되고 rollback time을 측정했다.
- audit log와 application signal로 누가 무엇을 언제 바꿨는지 설명한다.
- monthly cost owner와 budget, tag, cleanup runbook이 있다.
실무 장애 24개 훈련 카드
| 번호 | 주입 상황 | 관찰할 증거 | 첫 조치 |
|---|---|---|---|
| 1 | 잘못된 ECR tag | Pod event | image URI와 digest 대조 |
| 2 | ECR pull 권한 없음 | AccessDenied event |
node/workload identity scope 확인 |
| 3 | process 즉시 종료 | previous log | exit code·config 확인 |
| 4 | readiness path 오타 | EndpointSlice 없음 | probe path와 app route 비교 |
| 5 | liveness가 DB를 검사 | 연쇄 restart | liveness 의존성 제거 |
| 6 | request가 지나치게 큼 | Pending event | quota·allocatable·request 비교 |
| 7 | memory limit 부족 | OOMKilled |
peak·heap·limit 측정 |
| 8 | Service selector 오타 | endpoint 0개 | label set 비교 |
| 9 | targetPort 오타 | ALB unhealthy | Service→container port 추적 |
| 10 | subnet tag 누락 | Ingress event | public/private discovery tag |
| 11 | ALB security group 차단 | target timeout | SG source와 port 확인 |
| 12 | DNS가 이전 ALB를 가리킴 | dig 결과 |
alias·TTL·변경 시각 확인 |
| 13 | certificate domain 불일치 | curl -v |
ACM SAN·listener 확인 |
| 14 | OIDC audience 불일치 | STS AccessDenied | token aud와 provider 비교 |
| 15 | GitHub sub 불일치 |
assume role 실패 | branch/environment claim 확인 |
| 16 | GitLab project rename | trust 실패 | stable project_id 조건 검토 |
| 17 | deploy role이 cluster 미등록 | kubectl unauthorized | Access Entry 확인 |
| 18 | RBAC namespace 불일치 | forbidden | kubectl auth can-i |
| 19 | quota가 surge를 막음 | rollout timeout | old+new request 합산 |
| 20 | PDB가 drain을 막음 | disruptionsAllowed 0 | replica·PDB 목적 재평가 |
| 21 | HPA가 scale하지 않음 | metrics unknown | metrics·request 확인 |
| 22 | Auto Mode node가 안 생김 | NodePool condition | requirement·quota·capacity |
| 23 | log 비용 급증 | ingestion bytes | verbosity·retention 조정 |
| 24 | cluster 삭제 뒤 ALB 잔존 | ELB inventory | Ingress 삭제 순서·tag 확인 |
팀 실습 워크북: 문서 없이도 복구할 수 있는가
미션 1 현재 실행 identity를 설명한다
aws sts get-caller-identity, kubectl config current-context, kubectl auth can-i --list -n flownote를 저장한다. 세 출력의 identity가 왜 다르게 보이는지 설명한다. “내 노트북에서는 됨” 대신 어떤 federation role이 어떤 Access Entry와 Kubernetes permission을 통과했는지 기록한다.
완료 증거: account ID, assumed role ARN, cluster name, namespace, 허용·거부 verb 표. credential 원문은 포함하지 않는다.
미션 2 network를 손으로 다시 그린다
VPC CIDR, 두 AZ, public/private subnet CIDR, route table, Internet Gateway, NAT Gateway, ALB, Pod를 빈 종이에 그린다. 각 화살표에 source/destination과 port를 적는다. outbound가 필요한 이유와 VPC endpoint로 대체 가능한 traffic을 표시한다.
완료 증거: “public/private”라는 이름 없이 route만 보고 subnet 성격을 판정할 수 있다.
미션 3 image와 source의 양방향 추적을 만든다
running Pod의 imageID digest에서 ECR image detail과 CI run을 찾아 source commit까지 이동한다. 반대로 commit SHA에서 CI run, ECR digest, Deployment revision, Pod를 찾는다.
kubectl -n flownote get pod -l app.kubernetes.io/name=flownote \
-o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.containerStatuses[0].imageID}{"\n"}{end}'
완료 증거: 임의 Pod 하나의 source reviewer와 production approver를 10분 안에 찾는다.
미션 4 readiness 실패를 안전하게 관찰한다
test namespace에서만 readiness path가 존재하지 않는 release를 만든다. 새 Pod가 Running이지만 Ready가 되지 않고 Service EndpointSlice에 들어가지 않는지 본다. rollout timeout 뒤 event와 ReplicaSet 상태를 보존하고 undo한다.
완료 증거: 사용자 traffic은 old replica에 남았고, 원인과 rollback 시간이 timeline에 기록됐다.
미션 5 quota와 surge 충돌을 계산한다
replica 3, Pod당 CPU request 500m, maxSurge: 1이면 rollout peak CPU request는 2 vCPU다. namespace quota가 1.5 vCPU라면 네 번째 Pod가 admission에서 거부될 수 있다. 평시 합계만 보지 말고 rollout peak를 표로 계산한다.
peak request = (replicas + maxSurge) × Pod request
headroom = quota - peak request
완료 증거: production의 replica·surge·quota·node 여유를 같은 단위로 비교한 표.
미션 6 OIDC trust를 의도적으로 좁힌다
test role이 main branch 또는 production environment에서만 발급되도록 sub 조건을 제한한다. feature branch job이 role assume에 실패하고 main의 승인된 job은 성공하는지 본다. permission policy를 비워 둔 trust test role을 사용하면 AWS 변경 위험을 줄일 수 있다.
완료 증거: 허용 case와 거부 case의 claim 조건, STS result, CloudTrail event.
미션 7 build role로 deploy가 안 되는지 증명한다
build role로 ECR push는 허용하되 EKS 변경을 시도하면 거부되어야 한다. deploy role은 기존 digest를 적용할 수 있지만 ECR repository 삭제와 IAM 변경은 거부되어야 한다.
완료 증거: allow test만큼 deny test가 포함된 permission matrix.
미션 8 Service selector를 고장 내고 역추적한다
test 환경에서 Service selector를 존재하지 않는 label로 바꾼다. Pod는 Ready지만 EndpointSlice endpoint가 0개가 되고 ALB target이 unhealthy해지는 순서를 기록한다. 진단은 ALB에서 Pod로 무작정 뛰지 않고 Ingress→Service→EndpointSlice→label을 따른다.
완료 증거: 각 경계의 정상/비정상 명령 출력과 수정 diff.
미션 9 PDB가 보호하는 실패와 못 막는 실패를 비교한다
kubectl drain 같은 voluntary eviction에서 PDB가 동시 중단을 제한하는지 test cluster에서 확인한다. node hard failure는 PDB가 막지 못한다는 가정을 tabletop으로 분석한다. replica가 서로 다른 AZ에 배치되는지 topology spread 또는 anti-affinity 필요성도 검토한다.
완료 증거: maintenance scenario와 AZ failure scenario의 예상 available replica 수.
미션 10 HPA와 node scaling의 시간차를 측정한다
허가된 load test 환경에서 요청을 늘려 HPA desired replica가 변하는 시각, 새 Pod Pending 시각, node provisioning 시각, Pod Ready 시각을 기록한다. 이 합이 scale-out latency다. traffic spike가 그보다 빠르면 minimum replica·scheduled scaling·queue 같은 완충이 필요하다.
완료 증거: T0 traffic → T1 HPA → T2 node → T3 Ready timeline과 p95 latency.
미션 11 log retention을 비용과 incident 요구에 맞춘다
control plane과 application log의 일일 ingestion, retention, query 빈도를 구한다. audit log는 보안 보존 요구를 확인하고, debug verbosity를 상시 켜는 대신 incident 시 임시로 올리는 절차를 만든다.
완료 증거: log group별 owner·retention·예상 비용·삭제 금지 사유.
미션 12 RTO와 RPO를 숫자로 검증한다
RTO(Recovery Time Objective)는 허용 가능한 복구 시간, RPO(Recovery Point Objective)는 허용 가능한 data 손실 시점이다. “고가용성”이라는 말 대신 FlowNote의 목표를 숫자로 적는다.
Application rollback RTO: 10분
Cluster 재생성 RTO: 90분
Configuration RPO: Git에 merge된 마지막 commit
Database RPO: 15분
새 cluster에 manifest를 apply하는 것만으로 data RPO가 검증되지는 않는다. backup을 실제 restore하고 application이 읽을 수 있는지 확인한다.
완료 증거: stopwatch로 측정한 restore drill 결과와 목표 차이.
미션 13 minor version upgrade rehearsal을 한다
EKS upgrade insight와 deprecated API scanner 결과를 읽고 add-on·client·manifest compatibility 표를 만든다. test cluster에서 control plane과 workload를 upgrade하고 rollout·PDB·node replacement를 관찰한다.
완료 증거: go/no-go 기준, rollback 가능 조건, upgrade 뒤 smoke와 24시간 관찰 항목.
미션 14 비용 잔존물 game day를 연다
cluster를 삭제한 뒤 일부 resource가 남았다고 가정한다. ALB, target group, NAT Gateway, Elastic IP, EBS volume, snapshot, log group, ECR image를 tag와 생성 시각으로 찾는다. 자동 삭제된다고 믿지 말고 inventory가 비었거나 보존 이유와 owner가 있는지 확인한다.
완료 증거: 생성 resource 수와 삭제 후 resource 수를 비교한 reconciliation 표.
미션 15 새 팀원이 runbook만 보고 배포한다
저자가 옆에서 구두로 도와주지 않는다. 새 팀원은 account·region·cost warning을 확인하고 staging 배포, smoke, rollback, cleanup을 수행한다. 막힌 문장은 사람 탓으로 돌리지 않고 runbook 결함으로 기록한다.
완료 증거: 소요 시간, 잘못 이해한 용어, 위험했던 명령, 문서 수정 PR.
출간용 실습 채점표
| 영역 | 0점 | 1점 | 2점 |
|---|---|---|---|
| 재현 | 저자만 가능 | 도움을 받아 가능 | 새 계정·새 팀원이 문서만으로 가능 |
| identity | admin 한 개 | role 분리는 했으나 deny 미검증 | 사람·build·deploy 분리와 deny 증거 |
| artifact | latest |
SHA tag | digest·SBOM·source 양방향 추적 |
| availability | replica만 여러 개 | probe 또는 PDB | probe·surge·quota·PDB·AZ 시나리오 |
| rollback | 명령만 존재 | test 성공 | data compatibility와 RTO 측정 |
| observability | log 열람 | dashboard | release marker·SLO·incident query |
| cost | 월말 확인 | budget alert | resource owner·ceiling·삭제 reconciliation |
| 문서 | command 모음 | 설명 포함 | 타인이 완주하고 수정 PR 제출 |
총 16점 중 13점 이상이면서 identity·rollback·cost가 각각 2점이어야 production 후보로 본다. 자동 test 통과는 출발점이며 실제 account의 cloud architect·security·FinOps review를 대체하지 않는다.
부록 A 45분 로컬 무비용 실습
AWS account 없이도 application과 image contract, manifest structure, readiness gate를 검증한다.
cd /Users/honi/WithAI/books/git-to-eks-cloud-infra
npm run qa
npm start
# http://127.0.0.1:4196/dist/lab/index.html
npm run qa는 unit test, FlowNote image build, non-root container smoke, 28장 원고 build, 실제 실습 화면 capture, 필수 주제·asset 검증을 실행한다. cloud resource는 만들지 않는다.
부록 B production 변경 요청서
서비스/환경:
변경 이유와 범위:
source commit SHA:
image URI와 digest:
manifest diff:
DB migration 호환성:
예상 replica·node·ALB·log 비용 변화:
배포 전 SLO와 baseline:
rollout 중단 조건:
smoke scenario:
rollback image와 명령:
관찰 담당자와 시간:
승인자:
release evidence URL:
부록 C 명령을 안전하게 읽는 법
create,apply,set,associate,update는 state를 바꾼다. 대상 account·region·namespace를 먼저 출력한다.delete --force, wildcard IAM, public CIDR, secret 출력은 예제를 그대로 실행하지 않는다.- pipe 앞 명령이 실패해도 뒤 명령이 실행될 수 있다. script에는
set -Eeuo pipefail과 명시적 검사 단계를 둔다. kubectl apply전에diff; deploy 뒤rollout status; 삭제 뒤 inventory 조회를 한 세트로 둔다.- CI log에는 token, credential, kubeconfig, Secret data를 출력하지 않는다.
부록 D 공식 자료와 시점
- EKS Auto Mode 시작
- eksctl로 Auto Mode cluster 생성
- EKS Kubernetes version lifecycle
- EKS Access Entry
- EKS Pod Identity
- EKS Auto Mode ALB
- GitHub Actions 보안
- GitLab OIDC with AWS
- Kubernetes Deployment
- Kubernetes probe
- Kubernetes resource management
- Kubernetes PDB
자료는 2026년 8월 12일에 확인했다. 당시 EKS standard support version은 1.36~1.33이었지만 살아 있는 문서의 값은 바뀐다. 생성 당일 aws eks describe-cluster-versions와 각 제품 release note를 다시 확인한다.
부록 E 저작권·상표·보안
본문, FlowNote code, pipeline fixture, Kubernetes manifest, SVG와 실습 화면은 이 책을 위해 독자적으로 작성했다. AWS, Amazon EKS, Amazon ECR, GitHub, GitLab, Kubernetes, Docker, Node.js는 각 권리자의 명칭 또는 상표이며 제품 식별을 위해 사용했다. 제휴나 보증을 뜻하지 않는다.
공식 console 화면·logo·타 도서의 표·긴 문장을 포함하지 않았다. 예제 domain은 .invalid, account ID는 문서용 placeholder다. 실제 credential, customer data, internal host, production log를 이 원고나 외부 AI 서비스에 넣지 않는다. 공개 전 cloud architect·security·FinOps 담당자의 현재 환경 검수를 거친다.
공식 참고 자료
기술·가격·정책은 바뀔 수 있으므로 실제 적용 전에 아래 원문을 다시 확인하세요.
