쿠버네티스란 무엇인가: 컨테이너 오케스트레이션 개념과 구성요소

컨테이너를 몇 개 띄우는 일과 수십 개를 안정적으로 굴리는 일은 다릅니다. 어느 노드에 올릴지, 죽으면 누가 다시 세울지, 주소가 바뀌면 앞단이 어떻게 찾아갈지를 사람이 매번 판단할 수는 없습니다. 쿠버네티스는 원하는 상태를 파일에 적어 두면 현재 상태를 거기에 맞춰 주는 플랫폼입니다. 아래에서는 쿠버네티스가 무엇을 자동으로 해 주고 무엇은 해 주지 않는지, 클러스터를 어디서 얻고 무엇을 적어 두면 되는지를 공식 문서 기준으로 정리합니다.
이름의 유래와 컨테이너 오케스트레이션의 등장
쿠버네티스 공식 문서의 쿠버네티스란 무엇인가는 쿠버네티스를 컨테이너화된 워크로드와 서비스를 관리하기 위한 이식할 수 있고 확장 가능한 오픈소스 플랫폼으로 정의합니다. 선언적 구성과 자동화를 모두 지원한다는 설명이 정의 안에 함께 들어 있습니다. 이름은 키잡이나 파일럿을 뜻하는 그리스어에서 왔습니다. 자주 보이는 K8s는 K와 s 사이에 여덟 글자가 있다는 뜻의 약식 표기입니다.
구글은 2014년에 쿠버네티스 프로젝트를 오픈소스화했습니다. 쿠버네티스는 프로덕션 워크로드를 대규모로 운영해 온 구글의 15년 이상 경험에 커뮤니티의 아이디어와 적용 사례가 결합된 결과물입니다. 가상 머신이 운영체제째 격리했다면, 컨테이너는 애플리케이션 단위로 격리합니다. 덕분에 이미지가 가볍고, 환경을 옮겨도 같은 방식으로 실행되며, 개수를 늘리는 비용도 낮습니다.
어려워지는 지점은 그다음입니다. 컨테이너가 수십 개로 늘어나면 배치와 복구, 노출을 손으로 붙잡고 있을 수 없습니다. 공식 문서는 쿠버네티스가 단순한 오케스트레이션 시스템이 아니라고 표현하면서, 정해진 순서대로 워크플로를 처리하는 방식 대신 독립적이고 조합 가능한 제어 프로세스들이 현재 상태를 의도한 상태로 계속 조정한다고 설명합니다.
쿠버네티스가 제공하는 것과 제공하지 않는 것
쿠버네티스가 기본으로 제공하는 기능은 여덟 가지입니다.
- 서비스 디스커버리와 로드 밸런싱: DNS 이름이나 자체 IP 주소로 컨테이너 노출, 트래픽이 많을 때 로드 밸런싱
- 스토리지 오케스트레이션: 로컬 저장소나 퍼블릭 클라우드 공급자의 저장소를 원하는 대로 자동 탑재
- 자동화된 롤아웃과 롤백: 배포된 컨테이너의 의도한 상태를 서술하면 정해진 속도로 현재 상태를 그쪽으로 변경
- 자동화된 빈 패킹: 컨테이너가 필요로 하는 CPU와 메모리를 지정하면 리소스를 가장 잘 쓰도록 노드에 배치
- 자동화된 복구: 실패한 컨테이너 재시작과 교체, 상태 검사에 응답하지 않는 컨테이너 종료
- 시크릿과 구성 관리: 암호와 OAuth 토큰, SSH 키를 컨테이너 이미지 재구성 없이 배포하고 갱신
- 배치 실행: 배치와 CI 워크로드 관리, 필요하면 실패한 컨테이너 교체
- 수평 확장: 명령어나 UI, CPU 사용량을 기준으로 확장과 축소
빠진 것도 같이 봐야 오해가 줄어듭니다. 같은 문서는 쿠버네티스가 전통적인 PaaS가 아니라고 못 박습니다. 하드웨어 수준이 아니라 컨테이너 수준에서 동작하고, 구성 요소는 필요에 따라 고르거나 뺄 수 있습니다. 소스 코드를 배포하거나 애플리케이션을 빌드하지도 않습니다. CI/CD 워크플로는 조직의 문화와 기술적 요구사항을 따르는 영역으로 남겨 둡니다.
미들웨어나 데이터베이스, 캐시 같은 애플리케이션 레벨 서비스도 기본 제공 대상이 아닙니다. 쿠버네티스 위에서 돌릴 수는 있지만 플랫폼이 챙겨 주지는 않습니다. 로깅과 모니터링, 경보 솔루션 역시 포함되지 않고 메트릭을 수집하고 노출하는 메커니즘까지만 제공합니다. 대신 지원하는 애플리케이션 유형은 제약하지 않아서, 상태를 들고 있는 워크로드와 그렇지 않은 워크로드, 데이터 처리 워크로드를 모두 올릴 수 있습니다.
클러스터를 관리형으로 받을지 직접 세울지
클러스터는 컨트롤 플레인과 노드로 나뉩니다. 관리형 서비스를 쓰면 컨트롤 플레인은 클라우드 쪽이 맡기 때문에 이 구분을 들여다볼 일이 드뭅니다. 직접 세울 때는 이야기가 달라집니다. kubeadm으로 마스터와 워커 노드를 붙이는 과정을 직접 밟아야 합니다.
컨트롤 플레인은 kube-apiserver와 etcd, kube-scheduler, kube-controller-manager로 돌아가고, 각 노드에서는 kubelet과 kube-proxy, 컨테이너 런타임이 돕니다. 관리형이라면 이름만 알아 두면 되고, 각각이 맡는 일은 쿠버네티스 컴포넌트 문서에 정리되어 있습니다.
YAML에 적는 것과 배포·롤백 명령
원하는 상태를 파일에 적어 두면 쿠버네티스가 현재 상태를 거기에 맞춥니다. 무엇을 적는지는 디플로이먼트 문서의 예제가 가장 짧게 보여 줍니다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
레플리카 수와 이미지, 레이블을 적어 두면 그 상태가 유지됩니다. 이미지를 바꾸면 롤아웃이 시작되고, 문제가 생기면 되돌립니다. 문서에 나오는 명령은 아래와 같습니다.
kubectl apply -f https://k8s.io/examples/controllers/nginx-deployment.yaml
kubectl rollout status deployment/nginx-deployment
kubectl set image deployment/nginx-deployment nginx=nginx:1.16.1
kubectl rollout history deployment/nginx-deployment
kubectl rollout undo deployment/nginx-deployment
kubectl rollout undo deployment/nginx-deployment --to-revision=2
얼마나 되돌릴 수 있는지도 설정으로 정합니다. 문서는 spec.revisionHistoryLimit 이 디플로이먼트가 보관하는 이전 레플리카셋 개수를 제한한다고 설명합니다. 이 값을 줄이면 오래된 리비전이 정리되고, 그만큼 되돌릴 수 있는 범위도 좁아집니다.
파드를 노드에 배치하는 기준과 서비스로 노출하기
파드 문서는 파드를 쿠버네티스에서 생성하고 관리할 수 있는 배포 가능한 가장 작은 컴퓨팅 단위로 정의합니다. 파드는 하나 이상의 컨테이너 그룹이고, 그 안의 컨테이너들은 스토리지와 네트워크를 공유하며 항상 함께 배치되고 함께 스케줄됩니다. 문서는 싱글톤 파드를 포함해 파드를 직접 만들 필요가 대개 없다고 안내합니다. 디플로이먼트나 잡 같은 워크로드 리소스로 만들라는 뜻입니다.
스케줄러가 노드를 고르는 기준은 리소스 요청입니다. 파드와 컨테이너 리소스 관리 문서에 따르면 requests 는 스케줄링 판단에 쓰이고 limits 는 kubelet이 실행 중에 강제합니다. 그 노드에 이미 올라간 컨테이너들의 요청에 새 파드의 요청을 더한 값이 노드 용량을 넘지 않아야 파드가 들어갑니다. 실제 사용량이 낮아도 요청 합계로 판단합니다.
apiVersion: v1
kind: Pod
metadata:
name: frontend
spec:
containers:
- name: app
image: images.my-company.example/app:v4
resources:
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "128Mi"
cpu: "500m"
CPU는 절대량으로 적고 250m 처럼 밀리 단위를 씁니다. 메모리는 128Mi 와 128M 이 서로 다른 값이라는 점을 조심해야 합니다. 앞은 2의 거듭제곱, 뒤는 10의 거듭제곱 기준입니다. 제한만 지정하고 요청을 지정하지 않으면 제한값이 요청값으로 복사됩니다.
파드는 비영구적 리소스입니다. 디플로이먼트가 파드를 지우고 새로 만들면 IP도 바뀌기 때문에, 앞단이 뒷단 파드의 IP를 직접 추적하는 방식은 오래가지 못합니다. 서비스 문서는 서비스를 파드의 논리적 집합과 그것들에 접근할 수 있는 정책을 정의하는 추상적 개념으로 설명하고, 셀렉터가 대상 파드 집합을 결정한다고 밝힙니다. 마이크로서비스끼리 서로를 찾을 때도 이 서비스를 거칩니다.
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app.kubernetes.io/name: MyApp
ports:
- protocol: TCP
port: 80
targetPort: 9376
서비스 타입은 클러스터 안에서만 닿는 ClusterIP, 각 노드의 포트로 여는 NodePort, 외부 로드 밸런서를 붙이는 LoadBalancer, 외부 DNS 이름으로 넘기는 ExternalName으로 나뉩니다. 여기에 상태를 들고 있는 워크로드가 들어오면 스토리지 문제가 겹칩니다. 파드와 PVC를 함께 지운 뒤 클러스터를 다시 세운 사례는 EKS에서 Vault 파드와 PVC를 삭제한 뒤 Raft 클러스터를 복구한 과정에 정리해 뒀습니다.
도입을 가르는 조건
쿠버네티스를 들일 만한 때는 서비스가 여럿이라 서로를 찾아야 하고, 트래픽에 따라 인스턴스 수가 바뀌며, 배포가 잦아 롤백이 필요한 경우입니다. 컨테이너 한둘을 가끔 올리는 규모라면 자동 복구와 빈 패킹의 이득보다 컨트롤 플레인과 노드 구성 요소를 직접 떠안는 비용이 큽니다.
빌드와 CI/CD, 데이터베이스, 로깅·모니터링은 제공 범위 밖이라 따로 붙여야 하고, 이걸 빠뜨리면 도입 비용을 실제보다 낮게 잡게 됩니다. 빈 패킹 결과는 파드에 적는 CPU·메모리 요청값에 달려 있으므로, 파드와 컨테이너 리소스 관리 기준으로 요청값을 어떻게 잡느냐에 따라 필요한 노드 수와 비용이 달라집니다.
이미 컨테이너로 돌리는 서비스가 하나라도 있다면 디플로이먼트 문서의 예시대로 그 서비스를 올려 롤아웃과 롤백을 직접 돌려 보는 것부터 시작하면 됩니다.
출처: 쿠버네티스란 무엇인가 · 쿠버네티스 컴포넌트 · 컨트롤러 · 디플로이먼트 · 파드 · 파드와 컨테이너 리소스 관리 · 서비스