6. Kubernetes (K8s)
Docker Compose는 단일 서버에서 여러 컨테이너를 관리하는 데 잘 동작해요. 하지만 서버가 10대, 100대로 늘어나면? 서버가 죽으면 누가 컨테이너를 다시 띄워주나요? 트래픽이 몰리면 자동으로 확장할 수는 없나요?
Kubernetes(K8s) 는 이 문제를 해결하는 컨테이너 오케스트레이션 도구예요. 원하는 상태를 선언하면, Kubernetes가 그 상태를 유지해줍니다.
이번 주에는 먼저 로컬에서 kind(Kubernetes IN Docker) 로 빠르게 실습한 뒤, EC2의 k3s 클러스터에 배포할 거예요.
공부할 내용
1. YAML 문법
Kubernetes는 리소스를 YAML 파일로 정의해요. JSON과 비슷하지만 더 읽기 쉬워요. 기본 문법을 익히고 오세요.
참고 자료
- 쿠버네티스 안내서 “YAML 문법”: YAML의 기본 문법을 정리한 글입니다.
2. Kubernetes 핵심 개념
아키텍처
Control Plane(클러스터 관리)과 Worker Node(워크로드 실행)의 구조를 이해하세요.
핵심 철학: 선언적(Declarative)
- 명령적: “컨테이너를 3개 시작해라” →
docker run을 3번 실행하는 방식 - 선언적: “컨테이너 3개가 항상 실행 중이어야 한다” → Kubernetes 방식
Docker Compose도 YAML로 상태를 선언하지만, docker compose up 실행 시점에만 상태를 맞춰요. Kubernetes는 한 발 더 나아가서, 컨트롤 루프가 현재 상태를 지속적으로 확인하며 선언한 상태에 맞춰줘요. 컨테이너가 죽으면 자동으로 다시 만들어요.
핵심 오브젝트
아래 오브젝트들의 역할과 관계를 이해하세요:
- Pod: 가장 작은 배포 단위. Pod는 언제든 죽을 수 있는 일회성 존재.
- Deployment: Pod의 복제본을 관리. 롤링 업데이트와 롤백 처리.
- Service: Pod들에 대한 안정적인 네트워크 엔드포인트. Pod는 재시작 시 IP가 바뀌지만, Service는 변하지 않는 DNS와 IP를 제공.
ClusterIP,NodePort,LoadBalancer타입의 차이를 알아보세요. - ConfigMap / Secret: 설정값과 민감한 정보를 Pod에 주입.
- Namespace: 클러스터 내 논리적 격리.
참고 자료
- Subicura “쿠버네티스 시작하기”: K8s 기본 개념을 다루는 인기 글입니다.
- 쿠버네티스 안내서: 실습과 함께 상세히 정리한 자료입니다.
- 삼성 SDS “쿠버네티스 알아보기”: 내부 구성 요소를 정리한 글입니다.
프로젝트 실습
EC2에 경량 Kubernetes인 k3s를 설치하고, 앱을 배포할 거예요.
kind create cluster --name study로 시작하세요. 아래 실습을 동일하게 진행할 수 있지만, Service type은 NodePort 대신 ClusterIP + kubectl port-forward를 사용하세요.Step 1: EC2 인스턴스 준비
요구사항:
- OS: Ubuntu 24.04 LTS
- 인스턴스 타입:
t3.small이상 (2 CPU, 2GB RAM) - 스토리지: SSD 20GB 이상
- Security Group: 22(SSH), 80(HTTP), 443(HTTPS), 6443(K8s API), 31000(NodePort) 인바운드 허용
Step 2: k3s 설치
# Docker 설치 (이미지 빌드 및 push용 — k3s 자체는 내장 containerd를 사용합니다)
curl -fsSL https://get.docker.com | sudo sh -
# k3s 설치
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="server --tls-san=$(curl -s ifconfig.me) --write-kubeconfig-mode=644" shStep 3: 로컬에서 원격 kubectl 접속 설정
EC2의 /etc/rancher/k3s/k3s.yaml을 로컬에 복사하고, server 항목의 IP를 EC2 Public IP로 변경하세요. KUBECONFIG 환경 변수를 설정한 뒤 kubectl get nodes로 연결을 확인하세요.
Step 4: Docker Hub에 이미지 Push
k3s가 이미지를 pull할 수 있도록, Session 3에서 빌드한 이미지를 Docker Hub에 push하세요.
docker tag my-server:v1 <your-dockerhub-username>/my-server:v1
docker push <your-dockerhub-username>/my-server:v1Step 5: Deployment 작성 및 배포
Deployment YAML을 직접 작성하세요. 아래 스펙을 만족해야 합니다:
- 이미지: Docker Hub에 올린 본인의 서버 이미지
- Replicas: 3
- livenessProbe:
/health엔드포인트 사용 — Session 1에서 만든 그 endpoint! 실패하면 컨테이너 재시작. - readinessProbe:
/health엔드포인트 사용 — 실패하면 Service에서 트래픽 제외 (죽이지는 않음). - resources: requests와 limits를 설정하세요. requests = 보장되는 최소치 (스케줄링에 사용), limits = 최대 사용량. CPU는 limits를 넘으면 쓰로틀링되고, 메모리는 limits를 넘으면 OOM으로 컨테이너가 종료됩니다.
selector.matchLabels와 Pod의 labels가 일치해야 해요. Service도 같은 labels로 Pod를 찾아요. K8s에서 모든 연결은 label 기반입니다.Step 6: Service 작성
Service YAML을 작성하세요. NodePort 타입으로, 외부 포트 31000을 Pod의 3000으로 매핑하세요.
NodePort는 클러스터의 모든 노드에서 지정한 포트(31000)를 열어서 외부 트래픽을 Pod로 전달해요. EC2의 IP와 이 포트로 직접 접속할 수 있습니다.
Step 7: 배포 및 확인
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
kubectl get pods -w
kubectl get svc
kubectl describe pod <pod-name>
kubectl logs <pod-name>
# 외부에서 접속
curl http://[EC2-IP]:31000/
curl http://[EC2-IP]:31000/api/info
curl http://[EC2-IP]:31000/health실험해보기
- Pod 삭제:
kubectl delete pod <pod-name>→ Deployment가 즉시 새 Pod 생성! 이게 선언적 관리의 핵심. - 스케일링:
kubectl scale deployment my-server --replicas=5→ 5개로 확장. 다시--replicas=2로 축소. - 잘못된 이미지: 이미지 태그를 오타로 변경해보세요 →
ImagePullBackOff.kubectl describe pod로 이벤트를 확인. - 셸 접속:
kubectl exec -it <pod-name> -- sh→ 컨테이너 안에서curl,env등 실행.
(선택) ALB 연결
Session 5에서 만든 ALB의 Target Group에 EC2 인스턴스의 31000 포트를 추가하면, ALB를 통해서도 접속할 수 있어요.
디버깅 치트시트
Pod가 안 뜨나요?
├── kubectl get pods → 상태 확인
│ ├── Pending? → kubectl describe pod → 이벤트 확인
│ │ (리소스 부족? 이미지 pull 실패?)
│ ├── CrashLoopBackOff? → kubectl logs <pod> → 앱 에러 확인
│ │ kubectl logs <pod> --previous → 이전 로그
│ └── Running인데 안 되나요? → 아래로
│
├── kubectl get svc → Service 확인
│ └── Endpoints가 비어있나? → Labels 불일치!
│
└── kubectl exec -it <pod> -- sh → 컨테이너 안에서 직접 확인
├── curl localhost:3000 → 앱이 내부에서 동작하나?
├── env → 환경 변수가 주입됐나?
└── nslookup <service-name> → DNS가 동작하나?