Pod
컨테이너 실행 최소 단위다. ECS Task처럼 하나 이상의 컨테이너를 함께 스케줄링한다.
컨테이너가 실제로 어느 단위로 배치되는지 볼 때분류: Layer 5 - 플랫폼 엔지니어링 & 자동화 | 선수지식: Docker Basics, ECS vs EC2
Kubernetes(K8s)는 여러 서버에 흩어진 컨테이너를 원하는 상태(desired state) 로 계속 맞춰 주는 오픈소스 컨테이너 오케스트레이션 플랫폼이다. 여기서 오케스트레이션은 “컨테이너를 실행한다”보다 넓다. 어느 서버에 배치할지 정하고, 죽으면 다시 만들고, 트래픽을 안정적으로 연결하고, 설정·권한·네트워크 정책까지 하나의 API 모델로 관리한다.
처음 읽을 때는 Kubernetes를 “복잡한 Docker 실행기”로 보면 오해가 생긴다. 더 정확한 모델은 클러스터 전체의 상태를 저장하고, 상태가 어긋나면 컨트롤러가 다시 맞추는 시스템이다. Docker가 한 호스트 안에서 컨테이너 이미지와 실행 환경을 표준화했다면, Kubernetes는 여러 호스트를 하나의 배포 표면으로 묶는다.
Docker Basics에서 본 것처럼 컨테이너는 애플리케이션, 런타임, 의존성을 이미지로 묶어 “내 컴퓨터에서는 되는데 서버에서는 안 된다” 문제를 줄였다. 하지만 Docker만으로는 다음 질문이 남는다.
ECS는 이 문제를 AWS 관리형 방식으로 잘 풀어 준다. ECS Service는 desired count를 유지하고, ALB와 Target Group을 연결하고, Auto Scaling과 CloudWatch를 붙일 수 있다. 대신 모델이 AWS API에 강하게 붙는다. Task Definition, IAM Role, ALB, CloudWatch, Auto Scaling 정책을 조합하는 방식 자체가 AWS의 언어다.
Kubernetes는 같은 문제를 더 일반화된 API로 푼다. 사용자는 “이 컨테이너를 지금 이 서버에서 실행해”가 아니라 “이 애플리케이션은 항상 3개 복제본으로 떠 있어야 한다”를 선언한다. 그 뒤 API Server, etcd, Scheduler, Controller, kubelet이 현재 상태를 관찰하고 원하는 상태로 수렴시킨다.
이 문서의 lineage는 “ECS의 AWS 종속성과 Docker 다중호스트 관리 복잡성 극복”이다. 핵심은 두 가지다.
첫째, Docker 단독 운영은 다중 호스트에서 상태가 흩어진다. docker run 명령으로 서버마다 컨테이너를 띄울 수는 있지만, “클러스터 전체에서 어떤 상태가 정답인가”를 저장하는 공통 저장소가 없다. 서버 하나가 죽으면 그 서버에 있던 컨테이너를 어디에 다시 올릴지 별도 스크립트나 운영자가 판단해야 한다.
둘째, ECS는 운영 복잡도를 크게 줄이지만 AWS 바깥으로 모델을 옮기기 어렵다. 온프레미스, 다른 클라우드, 여러 클러스터, GitOps 도구, 커스텀 컨트롤러를 같은 방식으로 엮어야 하면 더 일반적인 제어면(control plane)이 필요해진다.
Kubernetes의 답은 선언형 API + 공유 상태 저장소 + 컨트롤러 루프다. 모든 리소스는 API 객체로 표현되고, API Server가 검증한 상태는 etcd에 저장된다. Controller는 “현재 상태”와 “원하는 상태”의 차이를 계속 비교하고, Scheduler는 아직 배치되지 않은 Pod를 적절한 Node에 붙인다. 이 철학을 이해하면 Pod, Deployment, Service, HPA, NetworkPolicy가 서로 다른 기능처럼 보여도 같은 패턴 위에 있다는 점이 보인다.
아래 용어는 이후 절에서 반복해서 나온다. 첫 회독에서는 이름을 외우기보다 “어떤 문제를 맡는가”로 기억하면 된다.
| 용어 | 첫 정의 | 왜 필요한가 |
|---|---|---|
| Cluster | Kubernetes가 관리하는 서버와 제어 컴포넌트의 묶음 | 여러 서버를 하나의 배포 표면으로 다룬다 |
| Control Plane | 클러스터의 상태를 저장하고 의사결정을 내리는 제어 계층 | 배포, 스케줄링, 권한, 상태 수렴의 기준점이다 |
| Node | 실제 Pod가 실행되는 워커 서버 | 컨테이너가 CPU·메모리를 소비하는 장소다 |
| API Server | 모든 Kubernetes 요청의 진입점 | 인증, 권한, 검증, 상태 변경을 한곳에서 통제한다 |
| etcd | 클러스터 상태를 저장하는 분산 key-value 저장소 | 원하는 상태와 객체 메타데이터의 원본이다 |
| Scheduler | 아직 Node가 정해지지 않은 Pod를 적절한 Node에 배치하는 컴포넌트 | 리소스 요청량, affinity, taint 등을 보고 배치한다 |
| Controller Loop | 현재 상태와 원하는 상태를 비교해 차이를 줄이는 반복 루프 | Pod가 죽어도 다시 만들고, replica 수를 유지한다 |
| Pod | Kubernetes가 스케줄링하는 최소 실행 단위 | 하나 이상의 컨테이너가 네트워크와 볼륨을 공유한다 |
| Deployment | 무상태 애플리케이션의 replica 수와 롤링 업데이트를 선언하는 객체 | Pod를 직접 관리하지 않고 안정적인 배포 단위를 만든다 |
| ReplicaSet | 특정 Pod 템플릿의 복제본 수를 맞추는 중간 객체 | Deployment가 버전별 Pod 집합을 관리할 때 사용한다 |
| Service | 바뀌는 Pod IP 앞에 고정된 네트워크 이름과 가상 IP를 제공하는 객체 | Pod 교체와 스케일링에도 호출 지점을 유지한다 |
| Ingress | 외부 HTTP/HTTPS 트래픽을 Service로 라우팅하는 규칙 | 도메인, path, TLS 같은 L7 노출을 정의한다 |
| ConfigMap / Secret | 설정값과 민감값을 이미지 밖에서 주입하는 객체 | 이미지를 다시 빌드하지 않고 환경별 설정을 바꾼다 |
| Namespace | 하나의 클러스터 안에서 리소스 이름, 권한, quota를 나누는 논리 공간 | 팀·환경·권한 경계를 만든다 |
Kubernetes에서 가장 중요한 문장은 “명령을 실행한다”가 아니라 “상태를 선언한다”다. kubectl apply는 컨테이너를 직접 실행하는 버튼이 아니다. API Server에 리소스 객체를 제출하고, 그 객체가 클러스터의 원하는 상태가 되도록 저장한다.
apiVersion: apps/v1kind: Deploymentmetadata: name: nestjs-apispec: replicas: 3 selector: matchLabels: app: nestjs-api template: metadata: labels: app: nestjs-api spec: containers: - name: api image: my-ecr/nestjs-api:v1.2.3 ports: - containerPort: 3000이 YAML의 핵심은 replicas: 3이다. “Pod를 3번 생성하라”가 아니라 “이 템플릿의 Pod가 항상 3개 존재해야 한다”는 뜻이다. 지금 0개라면 3개를 만들고, 1개가 죽으면 1개를 새로 만들고, 사용자가 5개로 바꾸면 2개를 더 만든다.
sequenceDiagram participant Kubectl as kubectl participant API as API Server participant Etcd as etcd participant Controller as Deployment Controller participant Scheduler participant Kubelet as kubelet Kubectl->>API: Deployment 생성/수정 요청 API->>Etcd: desired state 저장 Controller->>API: ReplicaSet/Pod 생성 요청 API->>Etcd: Pod 객체 기록 Scheduler->>API: Node가 없는 Pod 감지 Scheduler->>API: Node 배정 업데이트 API->>Etcd: 배치 결과 저장 Kubelet->>API: 자기 Node에 배정된 PodSpec 감시 Kubelet->>Kubelet: 컨테이너 실행 Kubelet->>API: Pod 상태 보고
손으로 상태 변화를 따라가 보자.
| 시점 | 원하는 상태 | 현재 상태 | 누가 움직이나 | 결과 |
|---|---|---|---|---|
| 배포 직후 | replicas=3 | Pod 0개 | Deployment Controller | Pod 3개 생성 요청 |
| 배치 전 | Pod 3개 | Node 미정 Pod 3개 | Scheduler | 각 Pod에 Node 지정 |
| 실행 중 | Pod 3개 | Running 3개 | kubelet | 컨테이너 상태 보고 |
| Pod 1개 죽음 | Pod 3개 | Running 2개 | ReplicaSet Controller | 새 Pod 1개 생성 |
| 사용자가 5개로 변경 | Pod 5개 | Running 3개 | Deployment Controller | Pod 2개 추가 |
이 예제의 관찰 포인트는 “복구 명령을 누가 실행했는가”다. 운영자가 docker run을 다시 친 것이 아니라, 컨트롤러가 상태 차이를 발견하고 수렴 액션을 만든다.
Kubernetes의 reconciliation loop는 플랫폼 엔지니어링 전반의 공통 패턴이다.
| 도메인 | 원하는 상태 선언 | 현재 상태 읽기 | 수렴 주체 | Drift 신호 |
|---|---|---|---|---|
| Kubernetes | Deployment.spec.replicas: 3 | API Server/etcd의 Pod 상태 | Controller | Pod 부족, rollout 정체 |
| Terraform | HCL 리소스 정의 | Cloud Provider API refresh | plan -> apply | plan에 변경 발생 |
| GitOps | Git 저장소의 YAML | 클러스터 live state | ArgoCD/Flux controller | OutOfSync |
| DB 마이그레이션 | 스키마 버전 | DB 메타데이터 | migration runner | 버전 불일치 |
공통 구조는 선언 -> 비교 -> 수렴이다. 이 구조를 잡아 두면 GitOps Basics에서 ArgoCD가 하는 일도 “새로운 배포 마술”이 아니라 Kubernetes식 reconciliation을 Git 저장소까지 확장한 것으로 읽힌다.
Kubernetes 클러스터는 크게 Control Plane과 Worker Node로 나뉜다.
Control Plane├── API Server├── etcd├── Scheduler└── Controller Manager
Worker Node├── kubelet├── kube-proxy 또는 CNI dataplane└── Container Runtime(containerd 등)API Server는 모든 상태 변경의 관문이다. kubectl, Controller, Scheduler, kubelet은 모두 API Server와 통신한다. API Server는 요청자를 인증하고, RBAC로 권한을 확인하고, 요청 객체가 스키마와 admission policy를 통과하는지 검사한 뒤 etcd에 저장한다.
중요한 경계는 “컴포넌트끼리 직접 명령하지 않는다”는 점이다. Scheduler가 kubelet에 직접 “이 Pod를 실행해”라고 전화하지 않는다. Scheduler는 Pod 객체의 spec.nodeName을 API Server에 업데이트하고, kubelet은 자기 Node에 배정된 PodSpec을 감시하다가 실행한다. 이 간접성이 Kubernetes를 느리게 만드는 것처럼 보일 수 있지만, 감사 가능성·권한 통제·장애 복구의 기준점이 된다.
etcd는 클러스터 상태 저장소다. Deployment, Pod, Service, ConfigMap, Secret 같은 객체의 원하는 상태와 메타데이터가 저장된다. etcd가 불안정하면 API Server는 새 상태를 안정적으로 기록할 수 없고, 컨트롤러는 상태 비교를 제대로 하지 못한다.
첫 학습에서는 etcd 운영 명령보다 failure boundary를 먼저 잡으면 된다. 이미 떠 있는 Pod는 kubelet이 계속 유지할 수 있지만, API Server가 etcd에 쓰지 못하면 새 배포, 스케일링, 복구, 권한 변경이 막힌다. 관리형 EKS를 쓰면 etcd 운영은 AWS가 맡지만, “컨트롤 플레인이 멈추면 새 상태 변경이 멈춘다”는 경계는 여전히 중요하다.
Scheduler는 아직 Node가 정해지지 않은 Pod를 보고 어느 Node에 배치할지 결정한다. 단순히 빈 서버를 찾는 것이 아니라, 먼저 불가능한 Node를 제거하고 남은 후보에 점수를 매긴다.
Filtering은 배치 불가능한 Node를 제거한다.
resources.requests보다 가용 CPU·메모리가 부족한 Node 제거nodeSelector, node affinity 조건을 만족하지 않는 Node 제거Scoring은 남은 Node 중 더 나은 Node를 고른다.
flowchart TD Pod["새 Pod: nestjs-api, CPU 250m / Memory 256Mi 요청"] --> Filter["Filtering: 배치 불가 Node 제거"] Filter --> Candidates["후보: node-1 80% 사용, node-2 30% 사용, node-3 50% 사용"] Candidates --> Score["Scoring: 여유 자원, 이미지 존재, zone 분산 점수화"] Score --> Pick["node-2 선택"] Pick --> Bind["API Server에 바인딩 업데이트"]
Controller Manager는 여러 컨트롤러의 집합이다. Deployment Controller, ReplicaSet Controller, Node Controller, Job Controller 등이 각자 맡은 리소스의 desired state와 current state를 비교한다.
Controller loop를 이해하는 가장 좋은 질문은 “이 객체가 직접 무엇을 실행하는가”가 아니라 “이 객체가 어떤 차이를 감지하는가”다.
| Controller | 감지하는 차이 | 만드는 액션 |
|---|---|---|
| ReplicaSet Controller | 원하는 Pod 수와 현재 Pod 수 차이 | Pod 생성 또는 삭제 |
| Deployment Controller | 새 템플릿과 기존 ReplicaSet 차이 | 새 ReplicaSet 생성, rollout 진행 |
| Node Controller | Node 상태가 Ready가 아님 | Pod 재스케줄 후보 표시 |
| Job Controller | 완료해야 할 작업 수와 완료된 Pod 수 차이 | 작업 Pod 생성 |
kubelet은 각 Node의 에이전트다. API Server에서 자기 Node에 배정된 PodSpec을 보고 containerd 같은 runtime에 컨테이너 실행을 요청한다. 또한 Pod의 readiness, liveness, resource 사용 상태를 API Server에 보고한다.
장애를 볼 때 kubelet은 “클러스터 전체 의사결정자”가 아니라 “자기 Node의 실행 담당자”라고 생각하면 된다. 이미지 pull 실패, 컨테이너 시작 실패, probe 실패, OOMKilled 같은 이벤트는 대개 kubelet Events에 남는다.
컨테이너 실행 최소 단위다. ECS Task처럼 하나 이상의 컨테이너를 함께 스케줄링한다.
컨테이너가 실제로 어느 단위로 배치되는지 볼 때ReplicaSet을 통해 Pod 복제본 수와 롤링 업데이트를 관리한다. ECS Service와 가장 가깝다.
무상태 API 서버의 replica 수와 배포 전략을 선언할 때바뀌는 Pod IP 앞에 안정적인 네트워크 이름과 가상 IP를 만든다. Target Group과 역할이 일부 겹친다.
Pod를 직접 IP로 호출하지 않고 이름 기반으로 연결해야 할 때외부 HTTP 트래픽을 Service로 라우팅하는 L7 규칙이다. ALB Listener Rule과 대응된다.
도메인, path, TLS 라우팅을 클러스터 경계에서 정의할 때설정값과 민감 정보를 이미지 밖에서 주입한다. ECS 환경변수와 Secrets Manager 조합에 가깝다.
이미지를 다시 빌드하지 않고 환경별 설정을 바꿔야 할 때리소스를 논리적으로 격리한다. dev/staging/prod 또는 팀 경계를 나누는 기본 단위다.
권한, quota, 이름 충돌, 네트워크 정책 범위를 분리할 때실무에서는 보통 “1 Pod = 1 애플리케이션 컨테이너”처럼 보인다. 그러나 정의상 Pod는 하나 이상의 컨테이너가 네트워크 namespace와 volume을 공유하는 단위다. sidecar 패턴을 쓰면 같은 Pod 안에 app 컨테이너와 로그 수집 컨테이너, 프록시 컨테이너가 함께 뜰 수 있다.
경계도 중요하다. Pod 안 컨테이너들은 같은 생명주기를 강하게 공유한다. 서로 독립적으로 스케일링해야 하는 API 서버와 worker를 같은 Pod에 넣으면, worker만 늘리고 싶어도 API도 같이 늘어난다. 반대로 항상 같이 배포되고 같은 localhost 통신이 필요한 sidecar는 같은 Pod가 자연스럽다.
Deployment는 사용자가 직접 다루는 배포 단위이고, ReplicaSet은 Deployment가 내부적으로 사용하는 복제본 관리 단위다. 이미지 태그가 v1.2.3에서 v1.2.4로 바뀌면 Deployment는 새 Pod 템플릿을 가진 ReplicaSet을 만들고, 기존 ReplicaSet의 replica를 줄이면서 새 ReplicaSet을 늘린다.
이 구조 덕분에 rollout history와 rollback이 가능하다. 단, rollback은 데이터베이스 마이그레이션이나 외부 API 호환성까지 되돌려 주지 않는다. Deployment는 Pod 템플릿을 되돌리는 도구이지, 애플리케이션의 모든 부작용을 되돌리는 트랜잭션 시스템이 아니다.
Pod IP는 재시작과 스케일링 때 바뀐다. 그래서 다른 Pod가 10.x.x.x 같은 Pod IP를 직접 호출하면 곧 깨진다. Service는 selector에 맞는 Pod 집합 앞에 고정된 ClusterIP와 DNS 이름을 제공한다.
apiVersion: v1kind: Servicemetadata: name: nestjs-apispec: selector: app: nestjs-api ports: - port: 80 targetPort: 3000 type: ClusterIP같은 Namespace 안에서는 http://nestjs-api로 접근할 수 있고, 다른 Namespace에서는 http://nestjs-api.production.svc.cluster.local처럼 더 긴 이름을 사용할 수 있다. CoreDNS가 Service 이름을 ClusterIP로 해석하고, kube-proxy 또는 CNI dataplane이 ClusterIP로 들어온 트래픽을 실제 Pod IP 중 하나로 보낸다.
DNS 실패를 볼 때도 이 구조로 나누면 된다. NXDOMAIN이면 Service 이름이나 Namespace가 틀렸을 가능성이 크고, DNS는 되는데 TCP timeout이면 NetworkPolicy나 대상 Pod readiness를 봐야 한다. 이름 해석과 네트워크 연결은 다른 문제다.
Service가 클러스터 내부의 안정 접점이라면, Ingress는 외부 HTTP/HTTPS 요청을 어떤 Service로 보낼지 선언하는 규칙이다. Ingress 자체가 로드밸런서를 실행하는 것은 아니다. 실제 ALB, NGINX, Traefik 같은 구현은 Ingress Controller가 맡는다.
apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: nestjs-apispec: rules: - host: api.example.com http: paths: - path: /v1 pathType: Prefix backend: service: name: nestjs-api port: number: 80이 예시는 https://api.example.com/v1/... 요청을 nestjs-api Service의 80번 포트로 보낸다. EKS에서 AWS Load Balancer Controller를 쓰면 이 YAML이 ALB와 Listener Rule 생성으로 이어질 수 있다. 따라서 Ingress를 읽을 때는 두 층을 분리한다.
| 질문 | Kubernetes 객체 | 클라우드/컨트롤러 구현 |
|---|---|---|
| 어떤 host/path를 어떤 Service로 보낼까? | Ingress | Ingress Controller가 규칙을 읽음 |
| 실제 외부 IP/DNS는 누가 만들까? | Service type 또는 Ingress status | ALB/NLB/NGINX 등 구현체 |
| TLS 인증서는 어디서 붙을까? | Ingress TLS 또는 annotation | cert-manager, ACM, controller 설정 |
Ingress 장애의 첫 신호도 층별로 다르다. Service는 정상인데 외부 404가 나면 path/host rule을 보고, 외부 DNS는 되는데 502가 나면 target Pod readiness와 Service endpoint를 본다. ALB 자체가 만들어지지 않으면 Ingress Controller 권한, annotation, subnet tag를 봐야 한다.
ConfigMap은 일반 설정값, Secret은 민감값을 담는다. 둘 다 컨테이너 이미지 안에 값을 bake하지 않기 위한 장치다. 같은 이미지 my-ecr/nestjs-api:v1.2.3을 dev/staging/prod에 배포하되, DB host와 feature flag는 환경별 객체로 주입할 수 있다.
apiVersion: v1kind: ConfigMapmetadata: name: api-configdata: LOG_LEVEL: info FEATURE_PAYMENT_V2: "false"---apiVersion: v1kind: Secretmetadata: name: api-secrettype: OpaquestringData: DB_PASSWORD: replace-me학습할 경계는 두 가지다. 첫째, Kubernetes Secret은 “민감값 타입”이지 그 자체로 완전한 secret 관리 체계가 아니다. etcd 암호화, RBAC, external secret 연동, audit이 함께 필요하다. 둘째, ConfigMap/Secret을 바꿔도 애플리케이션이 자동으로 새 값을 읽는지는 주입 방식에 따라 다르다. 환경변수로 주입한 값은 보통 Pod 재시작이 필요하고, volume으로 마운트한 값도 애플리케이션이 파일 변경을 다시 읽어야 반영된다.
Namespace는 이름 충돌, quota, RBAC, NetworkPolicy 범위를 나누는 논리 공간이다. production과 staging을 같은 클러스터에 둘 때 Namespace가 기본 경계가 된다. 그러나 Namespace만 만들었다고 네트워크와 권한이 자동 격리되지는 않는다.
| 기대 | Namespace만으로 되는가 | 추가로 필요한 것 |
|---|---|---|
| 리소스 이름 분리 | 대체로 가능 | naming convention |
| 팀별 kubectl 권한 분리 | 불충분 | Role/RoleBinding |
| CPU/메모리 사용량 제한 | 불충분 | ResourceQuota, LimitRange |
| Pod 간 네트워크 차단 | 불충분 | NetworkPolicy와 CNI 지원 |
| Secret 접근 제한 | 불충분 | RBAC, external secret 정책 |
즉 Namespace는 “폴더”에 가깝고, 그 폴더 안에서 무엇을 허용할지는 RBAC, quota, NetworkPolicy가 정한다.
Kubernetes에서 CPU와 메모리 설정은 비용 최적화 옵션이 아니라 스케줄링의 입력이다.
resources.requests: Scheduler가 Node를 고를 때 예약하는 최소 자원량resources.limits: 컨테이너가 사용할 수 있는 상한resources: requests: cpu: "250m" memory: "256Mi" limits: cpu: "500m" memory: "512Mi"CPU 250m은 0.25 vCPU에 해당한다. 메모리 256Mi는 약 256 MiB다. 위 설정은 “이 Pod는 배치 시 최소 0.25 vCPU와 256Mi를 예약해야 하고, 실행 중에는 0.5 vCPU와 512Mi를 넘지 않게 제한한다”는 뜻이다.
노드 1대에 allocatable CPU가 2 vCPU라고 하자. requests.cpu: 250m인 Pod는 이론상 8개까지 들어갈 수 있다. requests.cpu: 500m이면 4개가 한계다. 실제로는 system daemon, eviction threshold, topology 정책 때문에 더 적게 들어간다.
| Pod requests | 2 vCPU Node에 들어갈 수 있는 대략적 개수 | 의미 |
|---|---|---|
| 100m | 20개 | burst가 낮은 worker에는 가능하지만 과밀 위험이 크다 |
| 250m | 8개 | 작은 API 서버의 시작점으로 자주 쓰는 수준 |
| 500m | 4개 | CPU 사용량이 안정적으로 높은 서비스 |
| 1 | 2개 | 한 Pod가 Node 용량의 절반을 예약 |
requests를 너무 낮게 잡으면 Scheduler는 Node가 여유 있다고 판단해 Pod를 많이 밀어 넣는다. limits가 너무 낮으면 컨테이너는 정상 부하에서도 throttling 또는 OOMKilled를 겪는다. 반대로 requests를 과하게 잡으면 실제 사용량은 낮아도 Pod가 Pending이 되고 노드 비용이 늘어난다.
메모리 limit을 넘으면 컨테이너는 OOMKilled로 종료될 수 있다. Exit Code 137은 SIGKILL로 강제 종료됐다는 신호다. 하지만 “limit을 올린다”가 항상 정답은 아니다.
| 관찰 | 의미 | 먼저 볼 것 |
|---|---|---|
| 배포 직후 항상 OOMKilled | 시작 시점 메모리 peak가 limit보다 큼 | startup memory, Nest.js heap, 초기 캐시 로딩 |
| 특정 요청 뒤 OOMKilled | 요청 처리 경로에 누수 또는 큰 버퍼 | p95 payload 크기, stream 처리 여부 |
| Pod 여러 개가 동시에 OOMKilled | 트래픽 급증 또는 공통 downstream 지연 | HPA 지연, queue depth, retry 폭증 |
| Node memory pressure와 동반 | Pod limit보다 Node 전체 압박 | requests 합계, daemonset, eviction 이벤트 |
첫 확인은 kubectl describe pod의 Last State와 Events, kubectl top pods, 애플리케이션 메트릭이다. 장기적으로는 limit을 1.5~2배로 올리는 규칙보다 “정상 p95 사용량, peak 사용량, 누수 여부”를 분리해서 본다.
Kubernetes는 requests와 limits 설정에 따라 Pod의 QoS(Quality of Service) class를 나눈다.
| QoS class | 조건 | 압박 상황에서의 의미 |
|---|---|---|
| Guaranteed | 모든 컨테이너의 CPU/memory request와 limit이 같음 | 가장 마지막에 축출되는 편 |
| Burstable | request가 일부 있고 limit과 다르거나 일부만 설정 | 일반적인 서비스 기본값 |
| BestEffort | request/limit이 모두 없음 | Node 압박 시 가장 먼저 축출될 수 있음 |
BestEffort는 로컬 실험에서는 편하지만 운영에서는 위험하다. Scheduler가 예약량을 모른 채 배치하고, Node memory pressure가 오면 먼저 밀려날 수 있다. 최소한 requests를 잡아야 “이 Pod가 클러스터에서 어느 정도 자리를 차지하는가”를 Kubernetes가 계산할 수 있다.
작은 Nest.js API가 평소 120Mi를 쓰고, 배포 직후 350Mi까지 튄다고 하자.
| 설정 | 결과 |
|---|---|
| request 128Mi / limit 256Mi | 평소에는 좋아 보이지만 시작 peak에서 OOMKilled 가능 |
| request 512Mi / limit 512Mi | 안정적일 수 있으나 노드에 적게 들어가 비용 증가 |
| request 256Mi / limit 512Mi | 시작 peak를 견디면서 Scheduler 예약량도 과하지 않은 출발점 |
이 숫자는 정답이 아니라 사고 방식이다. 먼저 실제 메모리 분포를 보고, request는 정상 운영 p50~p90에 가깝게, limit은 peak와 누수를 구분할 수 있을 만큼 여유 있게 잡는다. latency가 memory pressure와 함께 튀면 GC, payload size, streaming 여부를 같이 본다.
Probe는 “컨테이너 프로세스가 떠 있다”와 “서비스가 트래픽을 받아도 된다”를 분리한다.
readinessProbe: httpGet: path: /ready port: 3000 initialDelaySeconds: 10 periodSeconds: 5 failureThreshold: 3livenessProbe: httpGet: path: /live port: 3000 initialDelaySeconds: 30 periodSeconds: 10 failureThreshold: 3/health를 둘 다 쓰면 위험하다/health가 DB 연결까지 검사한다고 하자. 잠깐 DB가 느려져 /health가 503을 반환하면 readiness 실패는 합리적이다. 그 Pod를 트래픽에서 빼면 된다. 그러나 liveness도 같은 DB 체크를 사용하면 DB가 느린 동안 모든 Pod가 재시작될 수 있다. 이때 장애 원인은 DB 지연인데 Kubernetes가 애플리케이션 컨테이너를 계속 죽여 장애를 키운다.
첫 기준은 이렇게 잡는다.
| Probe | 포함하기 좋은 체크 | 피해야 할 체크 |
|---|---|---|
| Readiness | 앱 초기화 완료, 필수 dependency 연결 가능, 캐시 준비 | 너무 무거운 전체 진단 |
| Liveness | 이벤트 루프 deadlock, 프로세스 내부 치명 상태 | 외부 DB/API의 일시 지연 |
| Startup | 긴 bootstrap, migration 전후 초기화 | 정상 실행 중 반복 상태 점검 |
Deployment의 rolling update는 새 Pod가 Ready가 되어야 기존 Pod를 줄일 수 있다. replicas=3, maxUnavailable=1, maxSurge=1이라고 단순화하면 한 번에 최대 4개까지 띄우고, 최소 2개는 사용 가능해야 한다.
초기 상태: old 3, new 0, ready 31단계: old 3, new 1 생성 중, ready 32단계: new 1 Ready, old 1 종료, ready 33단계: 다음 new Pod 생성, 같은 과정을 반복새 Pod의 readiness가 계속 실패하면 rollout은 멈춘다. 이때 kubectl rollout undo는 Pod 템플릿을 이전 버전으로 되돌릴 수 있지만, 이미 실행된 DB migration이나 외부 시스템 변경은 되돌리지 않는다. 그래서 배포 전략은 Kubernetes 객체만이 아니라 애플리케이션 호환성 설계와 함께 봐야 한다.
Kubernetes는 Pod를 종료할 때 SIGTERM을 보내고 terminationGracePeriodSeconds 동안 기다린 뒤 SIGKILL할 수 있다. HTTP 서버가 SIGTERM을 받았을 때 새 요청 수락을 멈추고, 처리 중인 요청을 마무리하고, DB connection을 닫아야 롤링 업데이트와 노드 drain이 안전해진다.
PodDisruptionBudget(PDB)은 자발적 중단(voluntary disruption) 동안 최소 가용 Pod 수를 보장하는 정책이다.
Spot 노드 회수, 노드 교체, 클러스터 업그레이드처럼 Pod가 한꺼번에 내려갈 수 있는 상황에서는 이 차이가 장애로 드러난다. terminationGracePeriodSeconds는 Pod가 종료 신호를 받은 뒤 정리할 시간을 주고, preStop hook은 LB에서 빠지거나 내부 작업을 멈추는 준비 단계를 넣을 때 쓴다. PDB는 동시에 너무 많은 Pod가 drain되지 않도록 막지만, 애플리케이션이 SIGTERM을 무시하면 요청 유실이나 504를 막지 못한다. 그래서 노드 종료 실패 신호를 볼 때는 “PDB가 있는가”와 함께 “앱이 종료 신호를 받고 실제로 요청을 비우는가”를 같이 확인해야 한다.
apiVersion: policy/v1kind: PodDisruptionBudgetmetadata: name: nestjs-api-pdbspec: minAvailable: 2 selector: matchLabels: app: nestjs-apiPDB의 경계도 있다. Node 장애, OOMKilled, 애플리케이션 crash 같은 비자발적 장애를 막지는 못한다. 또한 replicas=1인 서비스에 minAvailable=1을 걸면 노드 drain이 막힐 수 있다. PDB는 “항상 안전” 스위치가 아니라, 가용성과 운영 작업 사이의 제약 조건이다.
Kubernetes 네트워킹은 세 층으로 나누면 읽기 쉽다.
Ingress는 보통 Ingress Controller와 함께 동작한다. EKS에서는 AWS Load Balancer Controller가 Ingress를 보고 ALB를 만들 수 있다. 이 지점부터는 Kubernetes 객체와 AWS 리소스가 연결되므로 “Kubernetes라서 완전히 클라우드 중립”이라고 단정하면 안 된다.
기본 Kubernetes 네트워크는 보통 같은 클러스터 안 Pod 간 통신을 넓게 허용한다. NetworkPolicy는 “어떤 Pod가 어떤 Pod로 어떤 방향의 트래픽을 보낼 수 있는가”를 선언한다.
apiVersion: networking.k8s.io/v1kind: NetworkPolicymetadata: name: postgres-allow-api-only namespace: productionspec: podSelector: matchLabels: app: postgres policyTypes: - Ingress ingress: - from: - podSelector: matchLabels: app: nestjs-api ports: - protocol: TCP port: 5432가장 중요한 silent failure는 “YAML은 생성됐는데 실제 차단은 안 되는 경우”다. NetworkPolicy는 CNI(Container Network Interface) 플러그인이 구현한다. CNI가 NetworkPolicy를 지원하지 않거나 기능이 꺼져 있으면 kubectl get networkpolicy에는 보이지만 트래픽은 계속 통과할 수 있다.
첫 확인은 리소스 존재가 아니라 허용되지 않은 Pod에서 실제 연결이 실패하는지다. DNS가 막힌 것인지 TCP가 막힌 것인지도 나눈다. default deny 정책을 쓰면 DNS egress(UDP/TCP 53)를 허용하지 않아 “DB가 막힌 것”처럼 보이는 DNS 장애를 만들 수 있다.
RBAC(Role-Based Access Control)는 Kubernetes API에서 “누가, 어떤 리소스에, 어떤 행동을 할 수 있는가”를 정한다.
| 객체 | 범위 | 역할 |
|---|---|---|
| Role | Namespace | 특정 Namespace 안 리소스 권한 정의 |
| ClusterRole | Cluster | 클러스터 전체 또는 여러 Namespace에 걸친 권한 정의 |
| RoleBinding | Namespace | User/Group/ServiceAccount에 Role 또는 ClusterRole 연결 |
| ClusterRoleBinding | Cluster | User/Group/ServiceAccount에 ClusterRole을 클러스터 범위로 연결 |
apiVersion: rbac.authorization.k8s.io/v1kind: Rolemetadata: name: pod-reader namespace: productionrules: - apiGroups: [""] resources: ["pods", "pods/log"] verbs: ["get", "list", "watch"]---apiVersion: rbac.authorization.k8s.io/v1kind: RoleBindingmetadata: name: dev-team-pod-reader namespace: productionsubjects: - kind: Group name: dev-team apiGroup: rbac.authorization.k8s.ioroleRef: kind: Role name: pod-reader apiGroup: rbac.authorization.k8s.ioRBAC 실패는 대부분 명확한 에러로 보인다. forbidden: User ... cannot create resource ... 같은 메시지가 나오면 인증은 됐지만 권한이 없는 것이다. 첫 확인은 kubectl auth can-i다.
$ kubectl auth can-i get pods --namespace productionyes
$ kubectl auth can-i delete pods --namespace productionnoEKS에서는 Kubernetes RBAC와 AWS IAM을 구분해야 한다. kubectl get pods 권한은 Kubernetes RBAC 문제이고, Pod 안 애플리케이션이 S3를 읽는 권한은 IAM Role for Service Account 또는 EKS Pod Identity 문제다. 둘을 섞으면 “kubectl 권한은 있는데 앱이 S3 AccessDenied” 같은 상황을 잘못 해석한다.
스케일링은 세 층으로 나눈다.
| 도구 | 늘리는 대상 | 입력 신호 | 대표 사용 |
|---|---|---|---|
| HPA(Horizontal Pod Autoscaler) | Pod 개수 | CPU, memory, custom metric | API 서버 replica 조정 |
| KEDA(Kubernetes Event-driven Autoscaler) | Pod 개수 | SQS, Kafka lag, Redis, Prometheus 등 이벤트 | worker를 queue backlog에 맞춰 조정 |
| Karpenter | Node 개수와 타입 | Pending Pod의 CPU·메모리·아키텍처·zone 요구 | 부족한 노드를 자동 프로비저닝 |
HPA는 현재 metric과 목표 metric의 비율로 replica 수를 계산한다. 단순화하면 다음과 같다.
desiredReplicas = ceil(currentReplicas * currentMetricValue / desiredMetricValue)현재 Pod 2개의 평균 CPU가 78%이고 목표가 50%라면 ceil(2 * 78 / 50) = 4가 된다. 그래서 HPA는 replica를 2개에서 4개로 늘리려 한다.
apiVersion: autoscaling/v2kind: HorizontalPodAutoscalermetadata: name: nestjs-api-hpaspec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: nestjs-api minReplicas: 2 maxReplicas: 10 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 50반례가 중요하다. 외부 결제 API를 기다리는 Nest.js API는 CPU가 20%여도 p95 latency가 1초를 넘을 수 있다. 이때 CPU HPA는 “여유 있음”으로 판단해 스케일링하지 않는다. CPU가 낮고 latency와 queue depth가 높으면 Pod CPU가 아니라 downstream 대기, connection pool, timeout, retry 정책, KEDA 이벤트 metric을 먼저 본다.
퀴즈
힌트: CPU 사용률은 대기 시간이 긴 시스템의 병목을 숨길 수 있다.
CPU 포화가 아니라 외부 API/DB 대기, connection pool 부족, queue depth 증가 같은 I/O 병목을 먼저 의심한다. Pod 수를 CPU 기준으로 늘리기보다 KEDA, timeout, retry, downstream 병목을 함께 본다.
KEDA는 HPA 위에 이벤트 기반 metric을 연결한다고 보면 된다. SQS 메시지 수, Kafka consumer lag, Redis list length, Prometheus query 결과처럼 애플리케이션 밖의 신호를 보고 replica 수를 조정한다.
apiVersion: keda.sh/v1alpha1kind: ScaledObjectmetadata: name: nestjs-worker-scaledobjectspec: scaleTargetRef: name: nestjs-worker minReplicaCount: 0 maxReplicaCount: 20 triggers: - type: aws-sqs-queue metadata: queueURL: https://sqs.ap-northeast-2.amazonaws.com/123456789/my-queue queueLength: "5" awsRegion: "ap-northeast-2"queueLength: "5"를 “메시지 5개당 Pod 1개”라는 학습 모델로 보면 된다. 메시지 50개가 쌓이면 대략 10개 Pod가 필요하다고 판단할 수 있다. 실제 운영에서는 처리 시간, visibility timeout, batch size, downstream rate limit까지 같이 봐야 한다.
HPA나 KEDA가 Pod를 늘려도 Node 자원이 부족하면 Pod는 Pending에 머문다. 이때 Karpenter는 Pending Pod의 요구사항을 보고 적절한 EC2 인스턴스를 프로비저닝한다.
HPA/KEDA: "Pod가 더 필요하다"Scheduler: "현재 Node에는 들어갈 공간이 없다"Karpenter: "이 Pod 요구사항을 만족하는 Node를 새로 만든다"Karpenter와 HPA를 혼동하면 장애 해석이 꼬인다. replica 수가 늘지 않으면 HPA/KEDA metric을 보고, replica는 늘었지만 Pod가 Pending이면 Scheduler 이벤트와 Karpenter provisioning을 본다. Node는 충분한데 readiness가 실패하면 스케일링이 아니라 애플리케이션 준비 상태 문제다.
SQS 기반 이미지 처리 worker가 있다고 하자.
queueLength=10이면 KEDA는 대략 25개 replica가 필요하다고 판단한다.Pending이고 Events에는 Insufficient cpu가 보인다.이 연쇄에서 어느 지점이 막혔는지에 따라 보는 대상이 달라진다.
| 막힌 지점 | 증상 | 볼 것 |
|---|---|---|
| KEDA가 반응하지 않음 | queue는 쌓였는데 replica가 그대로 | ScaledObject 상태, 인증, metric 값 |
| replica는 늘었지만 Pod Pending | 원하는 Pod는 생겼지만 Node 부족 | Scheduler Events, Karpenter provisioning |
| Node는 늘었지만 처리량 그대로 | Pod Running인데 queue 감소 느림 | worker 처리 시간, downstream rate limit |
| 처리 후 비용이 내려가지 않음 | 부하 종료 뒤 Node가 남음 | Karpenter consolidation, PDB, daemonset |
이 예제는 HPA/KEDA/Karpenter가 서로 대체제가 아니라 연쇄적으로 붙는다는 점을 보여 준다. Pod 수를 늘리는 판단과 Node 용량을 늘리는 판단은 분리되어 있다.
Kubernetes 리소스는 보통 Deployment 하나로 끝나지 않는다. Service, ConfigMap, Secret, Ingress, HPA, ServiceAccount, RBAC, NetworkPolicy가 같이 필요하다. Helm은 이런 YAML 묶음을 Chart라는 패키지로 만들고, values 파일로 환경별 차이를 주입하는 도구다.
| Helm 용어 | 뜻 | 학습 포인트 |
|---|---|---|
| Chart | Kubernetes 리소스 템플릿 묶음 | npm package처럼 재사용 가능한 배포 단위 |
| values.yaml | 템플릿에 주입하는 설정값 | replica, image tag, resource limit을 환경별로 바꿈 |
| Release | 특정 클러스터/Namespace에 설치된 Chart 인스턴스 | 같은 Chart를 dev/prod에 독립적으로 설치 가능 |
helm install my-app ./charts/my-app -f values-prod.yamlhelm upgrade my-app ./charts/my-app --set image.tag=v1.2.4helm rollback my-app 1Helm의 경계도 잡아야 한다. Helm은 템플릿과 패키징 도구이지, 원하는 상태를 계속 감시하는 컨트롤러가 아니다. helm upgrade를 실행해야 변경이 적용된다. GitOps에서는 ArgoCD나 Flux가 Git의 Helm values 변경을 감지하고 클러스터에 적용한다. 이 차이가 다음 GitOps Basics의 입구다.
EKS 전환은 “Kubernetes가 표준이라서”가 아니라 “표준화 이득이 운영 복잡도와 고정비를 넘는가”로 판단한다. 아래 기준은 정답표가 아니라 논의 시작점이다.
| 판단 축 | ECS 유지가 자연스러운 경우 | EKS 전환 신호 |
|---|---|---|
| 팀 규모 | 개발자 10명 미만, 플랫폼 전담 없음 | 플랫폼/인프라 담당자가 있고 공통 배포 표준을 운영 |
| 서비스 수 | 5개 이하, 패턴이 단순 | 10개 이상, 공통 policy·observability·autoscaling 반복 |
| 배포 방식 | AWS 콘솔/API와 기본 CI/CD로 충분 | GitOps, Helm, Operator, admission policy 필요 |
| 스케일링 | CPU/ALB metric 기반으로 충분 | SQS/Kafka lag, custom metric, node 최적화 필요 |
| 이식성 | AWS 단일 환경이 명확 | 온프레미스, 멀티 클러스터, 멀티 클라우드 요구 |
| 운영 복잡도 | AWS 관리형 추상화 선호 | RBAC, CNI, add-on, upgrade 운영을 감당 |
EKS에는 컨트롤 플레인 고정비가 있다. 예를 들어 시간당 $0.10이면 730시간 기준 월 약 $73이다. 여기에 워커 노드, EBS, Load Balancer, NAT, cross-AZ 트래픽, 로그 저장 비용이 별도로 붙는다. 실제 요금과 지원 단계는 의사결정 시점에 공식 요금을 다시 확인해야 하지만, 학습 관점에서는 “작은 서비스 1~2개를 올리려고 클러스터를 하나 더 만들면 고정비와 운영면이 먼저 생긴다”는 감각이 중요하다.
반대로 서비스가 20개를 넘고 각 서비스마다 IAM, ALB, autoscaling, secret, rollout, observability 패턴을 반복한다면 ECS에 남는 비용도 커진다. 이 비용은 청구서보다 운영 반복, 정책 불일치, 배포 디버깅 시간으로 나타난다.
“Kubernetes를 쓰면 이식성이 생긴다”는 말은 절반만 맞다. Deployment와 Service YAML은 클라우드 중립에 가깝지만, 실제 EKS 운영은 ECR, AWS Load Balancer Controller, IAM Roles for Service Accounts, CloudWatch, VPC CNI, EBS CSI에 묶일 수 있다. API 표면은 K8s여도 운영 이식성은 낮을 수 있다.
전환 전 최소 기준은 운영자가 다음 출력을 해석할 수 있는가다.
kubectl rollout status deployment/nestjs-apikubectl auth can-i create deployments -n productionkubectl get events -A --sort-by=.lastTimestampkubectl describe pod <pending-or-crashing-pod>이 출력이 낯설다면 EKS 전환 자체보다 Kubernetes 운영 학습과 실패 신호 정리가 먼저다.
긴 troubleshooting 명령을 외우기보다, 증상이 어떤 개념의 실패인지 먼저 연결한다.
| 증상 | 연결할 개념 | 왜 그런가 | 첫 확인 |
|---|---|---|---|
CrashLoopBackOff | 컨테이너 생명주기, ConfigMap/Secret | 앱이 시작 직후 종료되고 kubelet이 재시작 backoff 중 | kubectl logs --previous, Events |
ImagePullBackOff | 이미지 레지스트리, Node IAM, imagePullSecret | kubelet이 이미지를 가져오지 못함 | describe pod의 pull error |
Pending + Insufficient cpu | Scheduler Filtering, requests | 요구 자원을 만족하는 Node가 없음 | Pod Events, Node allocatable |
OOMKilled / Exit 137 | limits, 메모리 사용 패턴 | 컨테이너가 memory limit을 초과해 강제 종료 | Last State, kubectl top pods |
Running인데 0/1 READY | Readiness Probe | 앱은 떠 있지만 트래픽 준비 조건 실패 | probe Events, /ready 응답 |
| rollout이 멈춤 | Deployment rollout, readiness | 새 ReplicaSet Pod가 Ready가 되지 않아 교체 진행 불가 | rollout status, new Pod Events |
Service 이름 NXDOMAIN | CoreDNS, Namespace | DNS 이름 또는 Namespace가 틀렸거나 CoreDNS 장애 | FQDN, Service 존재 여부 |
| DNS는 되는데 TCP timeout | NetworkPolicy, endpoint, app port | 이름 해석 이후 네트워크 또는 대상 Pod 문제 | EndpointSlice, NetworkPolicy |
forbidden | RBAC | 인증은 됐지만 verb/resource 권한이 없음 | kubectl auth can-i |
| NetworkPolicy가 있는데 통신 가능 | CNI policy 구현, default deny 부재 | 정책 리소스만 있고 dataplane 차단이 안 됨 | 차단 Pod에서 실제 연결 테스트 |
| Pod만 늘고 Node가 안 늘어남 | Karpenter/Cluster Autoscaler | replica는 증가했지만 Pending Pod를 받을 Node가 없음 | Pending Events, Karpenter logs |
| API Server timeout | Control Plane, etcd | 새 상태 읽기/쓰기가 불안정 | 관리형이면 provider status, self-managed면 etcd health |
kubectl get deploy,pod,svcPending인지, Node가 배정됐는지ContainerCreating, ImagePullBackOff, CrashLoopBackOff이 순서는 복구 런북이 아니라 사고 모델이다. 각 단계는 Kubernetes 컴포넌트 하나와 연결된다.
상황을 하나로 묶어 보자. CI/CD 파이프라인은 초록색이고 Deployment도 생성됐는데 외부 ALB에서 502가 난다.
ImagePullBackOff면 레지스트리/IAM 문제이고, CrashLoopBackOff면 앱 시작 문제다.0/1 READY면 readiness 조건을 만족하지 못해 Service endpoint에 들어가지 않았을 수 있다.여기서 핵심은 “502”를 곧바로 ALB 문제로 단정하지 않는 것이다. Kubernetes에서는 외부 502가 Pod readiness, Service endpoint, Ingress rule, NetworkPolicy, 애플리케이션 port mismatch 중 어디서든 나올 수 있다. 각 단계는 앞에서 배운 객체 책임과 연결된다.
worker replica를 5개에서 20개로 늘렸는데 queue가 줄지 않는다고 하자.
| 확인 | 해석 |
|---|---|
| replica가 실제 20개가 됐는가 | 아니면 HPA/KEDA 또는 rollout 문제 |
| Pod 중 Pending이 있는가 | 있으면 Node 용량 또는 requests 문제 |
| Running이지만 Ready가 아닌 Pod가 있는가 | readiness 또는 dependency 준비 문제 |
| worker당 처리 시간이 그대로인가 | downstream DB/API rate limit 가능 |
| retry가 증가했는가 | 처리량 증가가 실패 재시도로 상쇄될 수 있음 |
Kubernetes는 Pod를 늘릴 수 있지만, downstream 처리량을 자동으로 늘리지는 않는다. queue lag 기반 스케일링을 쓸수록 DB connection pool, 외부 API quota, idempotency, retry backoff가 같이 설계되어야 한다.
Kubernetes는 컨테이너 배포와 클러스터 API를 제공하지만, 좋은 개발자 경험을 자동으로 제공하지 않는다. 로그, metrics, tracing, secret 관리, rollout policy, preview environment, cost guardrail은 별도 플랫폼 설계가 필요하다. 그래서 Internal Developer Platform 문서에서 IDP가 다시 등장한다.
Deployment, Service, HPA, PDB, NetworkPolicy를 모두 만들었더라도 값이 틀리면 장애가 더 복잡해진다. 예를 들어 readiness가 너무 엄격하면 정상 Pod가 endpoint에서 빠지고, NetworkPolicy가 DNS를 막으면 앱은 DB 장애처럼 보이는 연결 실패를 낸다. Kubernetes 객체 수는 안전성의 증거가 아니다. 객체가 어떤 실패를 줄이는지 설명할 수 있어야 한다.
Kubernetes API가 공통이어도 storage class, load balancer, IAM, observability, node provisioning은 클라우드별 차이가 크다. 멀티 클라우드가 목표라면 “YAML이 적용되는가”보다 “운영 절차와 장애 해석이 옮겨지는가”를 물어야 한다.
StatefulSet은 Pod 이름과 volume identity를 안정적으로 유지한다. 하지만 backup, failover, replication, schema migration, split-brain 방지는 애플리케이션과 운영 체계의 책임이다. 데이터베이스를 Kubernetes에 올리는 판단은 별도 주제로 다루는 편이 낫다.
Kubernetes 생태계는 빠르게 바뀐다. CNCF 설문, EKS Auto Mode, EKS Capabilities, Kubernetes minor release의 새 기능 같은 내용은 “지금 의사결정의 근거”로 쓰기 전에 공식 문서를 다시 확인해야 한다. 이 문서에서는 그런 주장을 결론의 핵심으로 강화하지 않는다.
학습 관점에서는 다음 정도만 남기면 충분하다.
아래 명령은 본문 이해에 필수는 아니다. 로컬에서 실제 객체 상태가 어떻게 바뀌는지 관찰하고 싶을 때만 실행한다.
minikube startkubectl get nodeskubectl cluster-info볼 것:
Ready인지kubectl create deployment nginx-demo --image=nginx:1.25 --replicas=3kubectl get deploy nginx-demokubectl get pods -l app=nginx-demo볼 것:
READY가 3/3으로 수렴하는지kubectl delete pod -l app=nginx-demokubectl get pods -l app=nginx-demo --watchkubectl expose deployment nginx-demo --port=80 --target-port=80kubectl get svc nginx-demokubectl get endpointslice -l kubernetes.io/service-name=nginx-demo볼 것:
apiVersion: apps/v1kind: Deploymentmetadata: name: too-largespec: replicas: 1 selector: matchLabels: app: too-large template: metadata: labels: app: too-large spec: containers: - name: nginx image: nginx:1.25 resources: requests: cpu: "100" memory: "100Gi"위 YAML을 too-large.yaml로 저장하고 적용한 뒤 본다.
kubectl apply -f too-large.yaml적용 후 볼 것:
kubectl get pod -l app=too-largekubectl describe pod -l app=too-largeInsufficient cpu 또는 Insufficient memory 이벤트가 보이면 Scheduler Filtering 단계에서 실패한 것이다.
CrashLoopBackOff, Pending, 0/1 READY, forbidden 중 어디에 걸렸는지 보고 다음 확인 범위를 줄일 수 있다.| 개념 A | 개념 B | 차이 |
|---|---|---|
| ECS | EKS(Kubernetes) | ECS는 AWS 전용 관리형 오케스트레이션, EKS는 Kubernetes control plane을 AWS가 관리 |
| Pod | Container | Pod는 컨테이너 실행을 감싸는 스케줄링 단위, 보통 1개 앱 컨테이너가 들어가지만 sidecar도 가능 |
| Deployment | ReplicaSet | Deployment는 rollout 의도를 관리, ReplicaSet은 특정 Pod 템플릿의 개수를 맞춤 |
| Service | Ingress | Service는 클러스터 내부 안정 접점, Ingress는 외부 HTTP 라우팅 규칙 |
| ConfigMap | Secret | 둘 다 설정 주입이지만 Secret은 민감값 저장을 위한 타입이며 별도 암호화/외부 secret 연동을 고려 |
| Readiness | Liveness | Readiness 실패는 트래픽 제외, Liveness 실패는 컨테이너 재시작 |
| requests | limits | requests는 배치 예약량, limits는 실행 상한 |
| HPA | KEDA | HPA는 주로 리소스/custom metric 기반, KEDA는 이벤트 소스를 HPA 스케일링으로 연결 |
| HPA/KEDA | Karpenter | HPA/KEDA는 Pod 수, Karpenter는 Node 용량을 늘림 |
| RBAC | IAM | RBAC는 Kubernetes API 권한, IAM은 AWS API 권한 |
| Helm | kubectl apply | Helm은 템플릿 패키징과 release 관리, kubectl은 리소스 적용 기본 클라이언트 |
Helm, Kustomize, HPA(Horizontal Pod Autoscaler), VPA(Vertical Pod Autoscaler), KEDA, Karpenter, RBAC, NetworkPolicy, PersistentVolume, StorageClass, EKS, minikube, kind, kubectl, Lens, Cluster Autoscaler, metrics-server, Readiness Probe, Liveness Probe, Startup Probe, PodDisruptionBudget, topologySpreadConstraints, Admission Controller, CNI, CSI, CRD, Operator