K8s 시리즈 01: Kubernetes란? 컨테이너부터 클러스터까지
왜 LLM 엔지니어가 Kubernetes를 알아야 하는가
LLM 엔지니어에게 Kubernetes는 “인프라팀이 알아서 해주는 것” 이상의 의미를 가진다. 모델을 학습하고 나면 결국 서빙, 스케일링, 비용 최적화 문제가 남고, 이 모든 것이 Kubernetes 위에서 돌아가기 때문이다.
- “vLLM 서빙 서버를 3대에서 10대로 늘리고 싶다” → Deployment replica, HPA
- “A100 노드에만 학습 Pod을 배치하고 싶다” → Node Selector, Taint/Toleration
- “모델 체크포인트를 여러 Pod에서 공유해야 한다” → EFS PVC (RWX)
- “추론 서버가 죽으면 자동으로 살아나야 한다” → Pod 자동 복구, Health Check
- “GPU 노드 비용이 너무 많이 나온다” → Spot Instance, Auto Mode, 스케일링 전략
모델 개발만이 아니다. 데이터와 평가 파이프라인에서도 Kubernetes가 핵심이다.
- “Annotation 툴, 데이터 공유 플랫폼 등 여러 내부 서비스를 운영해야 한다” → Deployment + Service + Ingress
- “매일 새로운 데이터를 전처리하고 정제해야 한다” → CronJob (주기적 배치)
- “벤치마크를 여러 모델에 대해 병렬로 돌리고 싶다” → Job (일회성 실행, 수평 확장)
결국 LLM 엔지니어가 하는 일의 상당 부분 — 학습, 서빙, 데이터 처리, 평가 — 이 Kubernetes 위에서 돌아간다. 인프라를 “모르겠고 알아서 해줘”로 넘기면 병목이 어디인지, 비용이 왜 나오는지, 왜 Pod이 안 뜨는지 파악할 수 없다.
이 시리즈에서는 Kubernetes의 기초부터 AWS EKS 실무까지를 다룬다. 첫 번째 글에서는 “Kubernetes가 뭔데?”에 답한다.
이 글은 K8s 시리즈의 첫 번째 글이다.
1. 전통적인 배포 vs 컨테이너 배포
1.1 가상 머신(VM)의 문제
전통적으로 애플리케이션은 가상 머신(VM) 단위로 배포한다. EC2 인스턴스 하나에 OS를 설치하고, 라이브러리를 깔고, 앱을 올린다.
문제는:
- 무겁다: Guest OS 전체를 포함하므로 이미지 크기가 수 GB~수십 GB
- 느리다: VM 부팅에 수 분이 걸린다
- 낭비가 크다: CPU 10%만 쓰는 앱에도 VM 1대를 할당
- 환경 차이: “내 로컬에서는 되는데 서버에서는 안 돼요”
1.2 컨테이너가 해결하는 것
컨테이너는 앱 + 실행에 필요한 라이브러리만 패키징한다. Guest OS가 없고, Host OS의 커널을 공유한다.
| 항목 | VM | 컨테이너 |
|---|---|---|
| 크기 | 수 GB (OS 포함) | 수십~수백 MB (앱만) |
| 시작 시간 | 수 분 | 수 초 |
| 격리 | 하드웨어 수준 (강함) | 프로세스 수준 (가벼움) |
| 밀도 | 서버당 수 개 | 서버당 수십~수백 개 |
| 이식성 | 환경 의존적 | 어디서든 동일하게 실행 |
핵심은 이식성이다. Dockerfile로 한 번 정의하면, 로컬이든 CI 서버든 프로덕션이든 완전히 동일한 환경에서 실행된다. “내 컴퓨터에서는 되는데”가 사라진다.
2. Kubernetes가 해결하는 문제
컨테이너 하나를 실행하는 것은 docker run으로 충분하다. 하지만 프로덕션에서는:
- 컨테이너가 수십~수백 개 동시에 돌아간다
- 컨테이너가 죽으면 자동으로 살려야 한다
- 트래픽이 늘면 자동으로 확장해야 한다
- 새 버전을 중단 없이 배포해야 한다
- 여러 서버에 컨테이너를 적절히 분배해야 한다
이 모든 것을 수동으로 하면 운영 부담이 막대하다. Kubernetes(K8s)는 이를 자동화하는 컨테이너 오케스트레이션 플랫폼이다.
| 기능 | K8s가 해주는 것 | 비유 |
|---|---|---|
| 자동 복구 | 컨테이너가 죽으면 자동 재시작 | 쓰러진 병사를 즉시 교체 |
| 수평 확장 | 트래픽에 따라 컨테이너 수를 늘리거나 줄임 | 바쁜 시간에 직원 추가 |
| 무중단 배포 | 새 버전을 하나씩 교체하며 배포 | 영업 중 인테리어 공사 |
| 선언적 관리 | “이 상태가 되어야 한다”고 정의하면 K8s가 알아서 맞춤 | 온도 설정하면 에어컨이 알아서 |
| 스케줄링 | 여러 서버 중 적절한 곳에 자동 배치 | 자리 배정 담당자 |
선언적(Declarative) 관리란?
Kubernetes의 핵심 철학이다. “이 명령을 실행해라”(명령형)가 아니라 “이 상태가 유지되어야 한다”(선언형)로 정의한다.
# "백엔드 Pod 3개가 항상 실행 중이어야 한다"
spec:
replicas: 3
이렇게 정의하면 Pod이 하나 죽어도 K8s가 자동으로 새 Pod을 띄워 3개를 맞춘다. 수동으로 “죽은 Pod을 재시작해”라고 명령할 필요가 없다.
3. 클러스터 구조
Kubernetes는 클러스터 단위로 동작한다. 클러스터는 Control Plane(두뇌)과 Worker Node(일꾼)로 구성된다.
3.1 Control Plane — 클러스터의 두뇌
클러스터 전체를 관리하는 컴포넌트다. 사용자가 직접 접근할 일은 거의 없고, kubectl이나 Rancher를 통해 API Server에 요청을 보내는 방식으로 사용한다.
| 구성 요소 | 역할 | 비유 |
|---|---|---|
| API Server | 모든 요청의 진입점. kubectl, Rancher, CI/CD 파이프라인이 여기에 요청 | 접수 창구 |
| etcd | 클러스터의 모든 상태를 저장하는 분산 Key-Value DB | 기록 보관소 |
| Scheduler | 새 Pod을 어느 노드에 배치할지 결정 (CPU, 메모리, GPU 고려) | 자리 배정 담당 |
| Controller Manager | Deployment, ReplicaSet 등의 “원하는 상태 = 실제 상태” 루프를 실행 | 자동 관리자 |
3.2 Worker Node — 실제 실행되는 곳
컨테이너(Pod)가 실제로 실행되는 서버다. EC2 인스턴스라고 생각하면 된다. 각 Worker Node에는 3개의 컴포넌트가 돌아간다.
kubelet — 노드의 관리자
- Control Plane(Scheduler)이 “이 노드에서 Pod A를 실행해”라고 지시하면, kubelet이 받아서 실제로 컨테이너를 띄운다
- Pod이 실행 중인지, 죽었는지 주기적으로 확인해서 Control Plane에 보고한다
- Health Check(Probe)도 kubelet이 실행한다
Container Runtime — 컨테이너를 실제로 실행하는 엔진
- kubelet이 “이 이미지로 컨테이너를 만들어”라고 요청하면, Container Runtime(containerd)이 실제로 컨테이너 프로세스를 생성한다
-
docker run을 대신 해주는 것이라고 생각하면 된다 - 과거에는 Docker를 사용했지만, 현재 K8s는 containerd나 CRI-O를 사용한다
kube-proxy — 네트워크 연결 담당
- Pod A가 Pod B에 요청을 보낼 때, kube-proxy가 네트워크 규칙을 관리해서 트래픽이 올바른 Pod에 도착하도록 한다
- Service를 만들면 kube-proxy가 해당 Service로 오는 트래픽을 여러 Pod에 분배(로드밸런싱)하는 규칙을 설정한다
전체 동작 흐름: “vLLM 서버 3대 띄워줘”
- 사용자가 kubectl 또는 Rancher로 “Deployment를 만들어줘” 요청
- API Server가 요청을 받고 etcd에 저장
- Controller Manager가 “Deployment에 Pod 3개가 필요하네” 인식
- Scheduler가 GPU 메모리, CPU 여유 등을 고려해 “Node 1에 2개, Node 2에 1개 배치” 결정
- 각 노드의 kubelet이 Scheduler의 지시를 받아 Container Runtime에게 컨테이너 실행을 요청
- Container Runtime이 ECR에서 이미지를 Pull하고 컨테이너를 실행
- kube-proxy가 Service의 트래픽을 3개 Pod에 분배하는 네트워크 규칙 설정
- Pod이 죽으면 kubelet이 감지 → Controller Manager가 새 Pod 자동 생성
4. 컨테이너 이미지
Kubernetes에서 실행하는 모든 애플리케이션은 컨테이너 이미지로 패키징되어야 한다.
4.1 주요 개념
| 개념 | 설명 | 비유 |
|---|---|---|
| Image | 컨테이너를 만들기 위한 읽기 전용 템플릿 | 붕어빵 틀 |
| Container | Image로부터 만들어진 실행 중인 인스턴스 | 구워진 붕어빵 |
| Dockerfile | Image를 빌드하기 위한 명령어 모음 | 레시피 |
| Registry | Image를 저장하고 배포하는 저장소 | 붕어빵 틀 창고 |
| Tag | Image의 버전 (예: v1.0, latest) | 버전 번호 |
4.2 이미지 빌드 흐름
4.3 Dockerfile 예시
# 1. 베이스 이미지
FROM python:3.11-slim
# 2. 작업 디렉토리
WORKDIR /app
# 3. 의존성 먼저 복사 (캐시 활용)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 4. 소스 코드 복사
COPY . .
# 5. 포트 노출
EXPOSE 8080
# 6. 실행 명령어
CMD ["python", "main.py"]
왜 의존성을 먼저 복사하는가?
Docker는 각 단계를 레이어로 캐싱한다. requirements.txt가 바뀌지 않으면 pip install 단계가 캐시에서 재사용되어 빌드가 훨씬 빨라진다. 소스 코드만 바꿨을 때 전체 의존성을 다시 설치하지 않아도 되는 것이다.
4.4 이미지 빌드·태그·푸시
# 이미지 빌드
docker build -t my-app:v1.0 .
# Registry에 맞게 태그
docker tag my-app:v1.0 123456789.dkr.ecr.ap-northeast-2.amazonaws.com/my-app:v1.0
# Registry에 푸시
docker push 123456789.dkr.ecr.ap-northeast-2.amazonaws.com/my-app:v1.0
이렇게 Registry에 올라간 이미지를 Kubernetes가 Pull해서 Pod으로 실행한다.
5. YAML — Kubernetes의 언어
Kubernetes의 모든 리소스는 YAML 파일로 정의한다. K8s를 쓴다는 것은 YAML을 읽고 쓴다는 것이다.
5.1 기본 구조
모든 K8s YAML은 4개의 필수 필드를 가진다.
apiVersion: apps/v1 # API 버전 — 어떤 API 그룹을 사용하는가
kind: Deployment # 리소스 종류 — 무엇을 만드는가
metadata: # 메타데이터 — 이름, 라벨 등
name: my-app
namespace: default
labels:
app: my-app
spec: # 스펙 — 원하는 상태를 정의
replicas: 3
selector:
matchLabels:
app: my-app
template:
spec:
containers:
- name: my-app
image: my-app:v1.0
| 필드 | 역할 | 예시 |
|---|---|---|
apiVersion | 사용할 API 버전 | v1, apps/v1, batch/v1 |
kind | 리소스 종류 | Pod, Deployment, Service, Job |
metadata | 이름, 네임스페이스, 라벨 | name: my-app |
spec | 원하는 상태 정의 (핵심) | replicas: 3, containers: [...] |
5.2 자주 쓰는 apiVersion
| apiVersion | 포함 리소스 |
|---|---|
v1 | Pod, Service, ConfigMap, Secret, PersistentVolumeClaim |
apps/v1 | Deployment, StatefulSet, DaemonSet, ReplicaSet |
batch/v1 | Job, CronJob |
networking.k8s.io/v1 | Ingress, NetworkPolicy |
처음에는 외울 필요 없다. kubectl explain <리소스>로 항상 확인할 수 있다.
5.3 kubectl 기본 명령어
YAML을 작성했으면 kubectl로 클러스터에 적용한다.
# YAML 적용 (생성 또는 업데이트)
kubectl apply -f deployment.yaml
# 리소스 목록 조회
kubectl get pods # Pod 목록
kubectl get deploy # Deployment 목록
kubectl get all # 모든 리소스
# 상세 정보 (문제 해결 시 필수)
kubectl describe pod <pod-name>
# 로그 확인
kubectl logs <pod-name> # 로그 출력
kubectl logs <pod-name> -f # 실시간 스트리밍
# Pod 쉘 접속
kubectl exec -it <pod-name> -- bash
# 리소스 삭제
kubectl delete -f deployment.yaml
Rancher를 쓰면 이 명령어를 몰라도 된다. 웹 UI에서 Deployment 생성, Pod 로그 확인, 스케일링 등 대부분의 작업을 클릭으로 할 수 있다. 하지만 자동화 스크립트나 CI/CD 파이프라인에서는 kubectl이 필수이므로, 기본 명령어 정도는 알아두는 것이 좋다.
6. 용어 정리
| 용어 | 설명 |
|---|---|
| Cluster | Control Plane + Worker Node로 구성된 K8s 실행 환경 전체 |
| Node | Pod이 실행되는 서버. Worker Node = EC2 인스턴스 |
| Pod | K8s에서 배포 가능한 최소 단위. 1개 이상의 컨테이너 포함 |
| Container | 앱 + 실행 환경을 하나로 패키징한 격리된 실행 단위 |
| Image | 컨테이너를 만들기 위한 읽기 전용 템플릿 |
| Registry | 이미지를 저장하는 저장소 (ECR, Docker Hub) |
| Deployment | Pod의 개수, 업데이트, 롤백을 관리하는 컨트롤러 |
| Service | Pod 집합에 대한 고정 네트워크 엔드포인트 |
| Namespace | 클러스터 내 리소스를 논리적으로 분리하는 범위 |
| kubectl | K8s 클러스터를 CLI로 제어하는 도구 |
| Rancher | K8s 클러스터를 웹 GUI로 관리하는 플랫폼 |
| YAML | K8s 리소스를 정의하는 파일 형식 |
| EKS | AWS 관리형 K8s 서비스 (Control Plane을 AWS가 관리) |
| ECR | AWS 관리형 컨테이너 이미지 저장소 |
다음 글
이번 글에서는 Kubernetes가 무엇이고, 왜 필요하며, 어떤 구조인지를 정리했다. 다음 글에서는 실제로 앱을 실행하는 핵심 리소스인 Pod, Deployment, Job, CronJob을 다룬다.
다음 글: K8s 시리즈 02: Pod, Deployment, Job, CronJob — K8s 워크로드 총정리
참고 문헌
Enjoy Reading This Article?
Here are some more articles you might like to read next: