본문으로 건너뛰기
Kubernetes 로고

생성 수정

Kubernetes · 1장

쿠버네티스 이론

손을 대기 전에 그림을 먼저 그린다. 오케스트레이터가 필요한 이유, 선언형 API와 제어 루프, 클러스터를 이루는 부품과 핵심 객체를 정리한다.

1장 · 총 8개 절 · 전체 9개 장 중 1번째

컨테이너에서 오케스트레이션으로

1장 · 01

컨테이너는 "어디서 실행해도 같게"를 풀었습니다. 남은 문제는 그 다음입니다 — 수십 개의 컨테이너를 어느 서버에 배치하고, 죽으면 누가 살리고, 버전을 바꾸는 동안 서비스는 어떻게 유지하는가. 오케스트레이터는 이 반복 노동을 대신 맡는 층입니다.

  • docker run 은 한 대에서 한 개를 띄우는 명령이다 — 배치·복구·확장은 사람 몫으로 남는다
  • 서버가 여러 대가 되는 순간 "어디에 띄울지" 정하는 스케줄러가 필요해진다
  • 장애는 예외가 아니라 일상이다 — 복구를 사람의 대응 속도에 맡기지 않는 것이 출발점이다
  • 쿠버네티스는 구글의 Borg 운영 경험에서 나왔고 지금은 CNCF가 관리하는 사실상의 표준이다
  • 표준이라는 말의 실질 — 온프레미스든 AWS든 같은 YAML과 같은 kubectl 명령이 통한다

사람이 하던 일이 어디까지 넘어가는가

컨테이너만 있을 때

어느 서버에 띄울지

사람이 고른다

죽으면 다시 띄우기

알림 보고 사람이 대응

트래픽 늘면 늘리기

사람이 명령

새 버전 교체

내리고 올리는 순서를 사람이

오케스트레이터가 있을 때

스케줄러

자원·제약을 보고 배치

레플리카셋 컨트롤러

개수를 계속 맞춘다

replicas 값 하나

선언만 바꾸면 된다

롤링 업데이트

준비된 뒤 교체·되돌리기

왼쪽은 사람이 매번 판단하고 명령하던 일, 오른쪽은 컨트롤러가 대신 도는 일이다

도커 컴포즈와 무엇이 다른가

둘 다 여러 컨테이너를 한 번에 다루지만 경계가 다르다 — 컴포즈는 한 대(호스트)를, 쿠버네티스는 여러 대(클러스터)를 전제한다.

기준도커 컴포즈쿠버네티스
대상 범위호스트 한 대노드 여러 대로 이루어진 클러스터
장애 복구restart 정책까지스케줄러가 다른 노드에 다시 배치
확장한 대 안에서 scale노드를 가로질러 수평 확장
배포 전략직접 내리고 올린다롤링 업데이트·롤백이 기본 기능
쓰는 자리로컬 개발·단일 서버운영 클러스터·다중 노드

쿠버네티스 객체 — 컨테이너·파드·네임스페이스

1장 · 02

컨테이너는 실행되는 프로세스이고, 파드는 쿠버네티스가 관리하는 최소 단위입니다 — 둘은 대체 관계가 아니라 포함 관계입니다. 그 파드를 감싸는 바깥 경계가 네임스페이스이고, 클러스터는 이 경계로 리소스를 논리적으로 나눕니다.

  • 컨테이너는 실행되는 프로세스, 파드는 그 프로세스(들)를 쿠버네티스가 관리하도록 감싼 최소 단위다 — 포함 관계이지 대체 관계가 아니다
  • 같은 파드 안 컨테이너들은 네트워크·볼륨을 공유하고 생사를 함께한다 — 하나가 죽으면 파드 전체가 다시 스케줄링된다
  • 파드는 되살아나지 않는다 — 디플로이먼트가 만드는 것은 같은 파드가 아니라 새 IP를 가진 새 파드다
  • 여러 컨테이너를 한 파드에 묶는 대표적인 이유는 사이드카 패턴이다 — 로그 수집기나 Istio 프록시처럼 본체는 그대로 두고 기능만 옆에 붙인다
  • 1.24부터 쿠버네티스는 도커 엔진을 직접 부르지 않는다(dockershim 제거, containerd·CRI-O 사용) — 그래도 OCI 이미지 표준 덕분에 docker build 로 만든 이미지는 그대로 호환된다
  • 네임스페이스는 파드를 논리적으로 가르는 경계다 — default 는 사용자 워크로드, kube-system 은 클러스터 자체 부품이 쓴다
  • "필요할 때만 생성된다"는 서버리스(Cloud Run·Lambda)를 설명하는 말이지 쿠버네티스의 기본 동작이 아니다 — replicas: 3 이면 트래픽이 없어도 파드 3개는 계속 떠 있다

그림으로 보는 포함 관계 — 네임스페이스 안의 파드, 파드 안의 컨테이너

쿠버네티스 기본 오브젝트 — 컨테이너·파드·네임스페이스 포함 관계와 서비스
포함 관계는 네 겹이다 — 안쪽부터 컨테이너 → 파드 → 네임스페이스, 그리고 그 전체를 가로지르는 서비스. 청록은 컨테이너, 보라는 파드, 노랑은 볼륨, 회색은 네임스페이스, 산호색 라벨은 네임스페이스 이름이다.

볼륨이 파드 안에 있는 이유 — 컨테이너 안이 아니라 파드 레벨에 붙어 있어야 같은 파드의 컨테이너들이 볼륨을 공유할 수 있습니다. 사이드카가 앱 로그를 읽어가는 것도 이 구조 덕분입니다.

kube-system 은 건드리지 마세요 — 쿠버네티스가 자기 자신을 돌리는 데 쓰는 네임스페이스입니다. CoreDNS·kube-proxy 같은 부품이 여기 있고, K3s 라면 Traefik 도 들어갑니다. 실습에서 잘못 지우면 클러스터가 멈춥니다.

metallb-system 은 기본 구성이 아닙니다 — 온프레미스 클러스터에서 LoadBalancer 타입 서비스를 쓰려고 따로 설치하는 것입니다. 클라우드에서는 프로바이더가 로드밸런서를 붙여주지만 온프레미스에는 그게 없어 MetalLB 가 대신합니다. K3s 에는 ServiceLB(Klipper)가 기본 내장이라 굳이 안 깔아도 됩니다.

서비스가 왼쪽에서 세로로 걸쳐 있는 이유 — 서비스는 네임스페이스 안에 속하지만, 파드 IP 가 계속 바뀌어도 변하지 않는 접점 역할을 합니다. 앞서 말한 "파드는 가축"이라는 개념이 여기서 그림으로 드러납니다.

네임스페이스보통 담기는 것
default사용자가 만드는 워크로드 — 별도 지정 없으면 여기로 간다
kube-systemCoreDNS·kube-proxy 등 클러스터 자체 부품(K3s 는 Traefik 도) — 함부로 지우면 클러스터가 멈춘다
metallb-system온프레미스에서 LoadBalancer 타입 서비스를 쓰려고 따로 설치하는 애드온(K3s 는 ServiceLB 내장이라 없어도 된다)

컨테이너와 파드 — 네 가지 핵심 차이

컨테이너는 실행되는 프로세스, 파드는 쿠버네티스가 관리하는 최소 단위입니다. 대체 관계가 아니라 포함 관계입니다.

도커 컨테이너 하나파드
여러 개를 묶으면독립된 네트워크·스토리지같은 IP·볼륨을 공유
하나가 죽으면그 컨테이너만 재시작파드 전체가 생사를 함께한다
복구 방식같은 컨테이너를 재시작같은 파드가 아니라 새 IP의 새 파드
실행 방식docker run 명령형YAML 로 선언한 상태

왜 굳이 파드로 묶는가 — 사이드카

본체 컨테이너는 그대로 두고, 로그 수집기나 Istio 프록시 같은 보조 기능을 같은 파드 안 옆 컨테이너로 붙입니다. 네트워크·볼륨을 공유하므로 보조 컨테이너가 본체의 로그 파일이나 트래픽을 바로 볼 수 있습니다.

한 가지 오해 — 쿠버네티스는 도커를 직접 쓰지 않는다

1.24부터 dockershim 이 제거돼 containerd·CRI-O 로 컨테이너를 실행합니다. 그래도 docker build 로 만든 이미지가 그대로 도는 이유는 이미지 포맷이 도커 고유가 아니라 OCI(Open Container Initiative) 표준이기 때문입니다 — docker buildx 로 만들어 GHCR 같은 레지스트리에 올린 이미지를 쿠버네티스가 그대로 당겨 씁니다.

파드는 "필요할 때만 생성"이 아니라 "언제든 교체될 수 있다"

"필요할 때만 생성된다"는 서버리스(Cloud Run·Lambda)를 설명하는 말입니다. 디플로이먼트의 replicas: 3 은 트래픽이 있든 없든 파드 3개를 항상 유지합니다 — 요청이 없어도 파드는 떠 있고, 수요에 따라 늘리려면 HPA·KEDA 같은 오토스케일러를 별도로 붙여야 합니다(뒤 장의 k3s 실습에서 다룬다).

반려동물(pets)가축(cattle) — 쿠버네티스 파드
관리 대상이름 붙여 개별 관리번호로 관리, 서로 대체 가능
죽으면치료해서 되살린다같은 것을 되살리지 않고 새로 만든다
IP·이름고정, 애착을 갖는다바뀌어도 상관없다 — 서비스가 가린다
애착있다없다 — 언제든 교체될 수 있다는 전제로 설계한다

선언형 API와 제어 루프

1장 · 03

쿠버네티스를 이해하는 핵심 한 문장은 "명령하지 않고 선언한다"입니다. 사용자는 원하는 상태(spec)를 YAML로 제출하고, 컨트롤러는 현재 상태(status)를 그 선언에 맞추려 끊임없이 돌아갑니다.

  • 명령형 — "컨테이너 3개를 띄워라". 실행 시점의 동작만 있고 그 뒤는 책임지지 않는다
  • 선언형 — "컨테이너가 항상 3개여야 한다". 하나가 죽으면 시스템이 스스로 채운다
  • 그래서 kubectl apply 는 "실행"이 아니라 "등록"에 가깝다 — 여러 번 적용해도 결과가 같다(멱등)
  • 손으로 고친 상태는 다음 조정 주기에 되돌려진다 — 변경은 선언(YAML) 쪽에서 해야 한다
  • 모든 리소스가 spec(원하는 상태)과 status(현재 상태)를 갖는다 — 이 대비가 전체 설계의 뼈대다

제어 루프 — 끝나지 않는 세 단계

관찰 (observe)

현재 상태를 읽는다

비교 (diff)

spec 과 status 의 차이

조정 (act)

차이를 메우는 동작

다시 관찰

루프는 멈추지 않는다

컨트롤러는 이 루프를 계속 돈다. 파드를 지워도 다시 생기는 이유가 여기 있다

모든 객체가 갖는 네 부분

object-skeleton.yaml
apiVersion: apps/v1     # 이 객체를 다루는 API 그룹과 버전
kind: Deployment        # 객체의 종류
metadata:               # 이름·네임스페이스·라벨 등 식별 정보
  name: web
spec:                   # 원하는 상태 — 사람이 적는 부분
  replicas: 3
# status:               # 현재 상태 — 시스템이 채우는 부분(사람이 적지 않는다)

클러스터 구성 — 컨트롤 플레인과 노드

1장 · 04

클러스터는 결정을 내리는 컨트롤 플레인과, 실제로 컨테이너를 실행하는 워커 노드로 나뉩니다. 사용자의 모든 요청은 API 서버 하나를 거치고, 클러스터의 모든 상태는 etcd 한 곳에 저장됩니다.

  • kube-apiserver — 유일한 출입구. kubectl·컨트롤러·kubelet 모두 여기를 통해서만 대화한다
  • etcd — 클러스터의 단일 진실 공급원. 여기가 날아가면 상태가 사라진다(백업 1순위)
  • kube-scheduler — 새 파드를 어느 노드에 놓을지 정한다(자원 요청과 제약 조건을 본다)
  • kube-controller-manager — 각종 컨트롤러의 제어 루프를 한 프로세스에서 돌린다
  • kubelet — 각 노드에서 파드를 실제로 띄우고 상태를 보고하는 에이전트
  • kube-proxy·CNI — 노드 간 네트워크와 서비스 트래픽 전달을 담당한다
  • 컨테이너 런타임 — containerd·CRI-O 등(도커 엔진 직접 연동은 1.24에서 제거됐다)

클러스터 구성도

컨트롤 플레인

kube-apiserver

유일한 출입구

etcd

상태 저장소

scheduler

배치 결정

controller-manager

제어 루프

워커 노드 1

kubelet

파드 실행·보고

kube-proxy

서비스 트래픽

런타임

containerd

파드들

실제 컨테이너

워커 노드 2

kubelet

파드 실행·보고

kube-proxy

서비스 트래픽

런타임

containerd

파드들

실제 컨테이너

위쪽이 결정하는 쪽(컨트롤 플레인), 아래쪽이 실행하는 쪽(워커 노드)이다

요청 하나가 지나가는 길

  1. 1kubectl apply -f web.yaml인증·인가·검증을 거쳐 API 서버로
  2. 2API 서버 → etcd원하는 상태를 기록한다
  3. 3스케줄러노드가 정해지지 않은 파드를 발견해 노드를 배정
  4. 4kubelet배정을 감지해 런타임에 컨테이너 실행을 요청
  5. 5API 서버 → etcd실행 결과가 status 로 되돌아온다
kubectl 이 한 일은 API 서버에 원하는 상태를 등록한 것뿐이다 — 나머지는 각자의 루프가 한다

워크로드 객체 — 파드·레플리카셋·디플로이먼트

1장 · 05

실습에서 직접 만들 객체는 사실상 셋입니다 — 디플로이먼트, 서비스, 그리고 필요할 때의 설정·볼륨. 파드는 결과로 생기는 것이지 손으로 만드는 것이 아닙니다.

  • 파드(Pod) — 스케줄링의 최소 단위. 같은 IP·볼륨을 공유하는 컨테이너 묶음이다
  • 레플리카셋(ReplicaSet) — 같은 파드를 몇 개 유지할지 책임진다. 사람이 직접 만들지 않는다
  • 디플로이먼트(Deployment) — 레플리카셋을 세대별로 관리해 롤링 업데이트·롤백을 가능하게 한다
  • 라벨(label)과 셀렉터(selector)가 이 모든 것을 잇는다 — 서비스가 파드를 찾는 방법도 라벨이다
  • 그래서 파드를 지워도 사라지지 않는다 — 지운 것은 결과물이고, 선언은 위층에 남아 있다

디플로이먼트가 파드까지 내려오는 층

Deployment — web

버전과 세대를 관리한다 (replicas: 3)

ReplicaSet — web-5dd9d5dc5b

이 세대의 파드 개수를 3으로 유지한다

Pod

app=web

Pod

app=web

Pod

app=web

kubectl create deployment 한 줄이 만드는 것은 맨 위 한 층이고, 아래 두 층은 그 결과로 생긴다

워크로드 종류 — 무엇을 쓸지 고르는 기준

종류쓰는 자리특징
Deployment웹·API 같은 무상태 앱개수 유지 + 롤링 업데이트·롤백
StatefulSetDB처럼 상태를 가진 앱파드 이름과 저장소가 고정된다
DaemonSet로그 수집·모니터링 에이전트모든 노드에 하나씩 배치
Job / CronJob끝나는 작업 · 주기 작업완료가 목표다(계속 떠 있지 않는다)

네트워킹 — 서비스·인그레스·클러스터 DNS

1장 · 06

네트워킹의 전제는 "모든 파드는 서로 통신할 수 있고, 파드 IP는 언제든 바뀐다"입니다. 그래서 IP가 아니라 이름으로 부르는 층이 필요하고, 서비스와 클러스터 DNS가 그 역할을 맡습니다.

  • 서비스(Service) — 라벨로 파드 묶음을 고르고 그 앞에 고정 이름·고정 IP를 둔다
  • 클러스터 DNS(CoreDNS) — <서비스>.<네임스페이스>.svc.cluster.local 로 이름이 풀린다
  • 인그레스(Ingress) — 호스트·경로 규칙으로 여러 서비스를 한 진입점 뒤에 모으고 TLS를 종료한다
  • 인그레스는 객체일 뿐이고 트래픽은 인그레스 컨트롤러(Traefik·NGINX)가 처리한다 — 컨트롤러가 없으면 아무 일도 일어나지 않는다
  • port-forward 는 노출이 아니라 디버깅용 임시 터널이다(6장에서 쓴 그것이다)

바깥에서 파드까지 오는 길

사용자

브라우저

Ingress

host·path 규칙, TLS

Service

고정 이름 · selector: app=web

Pod

10.42.0.7

Pod

10.42.1.3

파드가 죽고 새로 떠도 서비스 이름은 그대로다 — 그래서 앞단은 아무것도 바꾸지 않아도 된다

서비스 타입 — 어디까지 열 것인가

타입접근 범위쓰는 자리
ClusterIP클러스터 내부만(기본값)백엔드·DB 등 내부 통신
NodePort노드의 30000~32767 포트실습에서 가장 빠른 외부 확인(6장에서 쓴 방식)
LoadBalancer외부 로드밸런서 주소클라우드, 또는 K3s의 ServiceLB·MetalLB
Ingress80/443 한 진입점 뒤도메인·경로로 여러 서비스를 가를 때

설정과 스토리지 — 컨피그맵·시크릿·볼륨

1장 · 07

이미지는 불변이어야 하고, 환경마다 달라지는 값과 남아야 하는 데이터는 이미지 바깥에 둡니다. 설정은 컨피그맵·시크릿으로, 데이터는 볼륨과 PVC로 분리합니다.

  • 컨피그맵(ConfigMap) — 평문 설정값. 환경변수 또는 파일로 파드에 주입한다
  • 시크릿(Secret) — 비밀번호·토큰. base64 인코딩일 뿐 암호화가 아니다(RBAC·저장소 암호화가 함께 필요하다)
  • 볼륨(Volume) — 파드 수명에 묶인 저장 공간. emptyDir 은 파드와 함께 사라진다
  • PVC/PV — 저장 공간에 대한 요청서(PVC)와 실제 공간(PV)을 분리해 다룬다
  • 스토리지클래스 — PVC가 오면 볼륨을 자동으로 만들어주는 규칙(동적 프로비저닝)
  • 원칙 한 줄 — 설정은 코드가 아니라 환경에 있다

파드 안에 두지 않는 것들

ConfigMap

LOG_LEVEL=info

Secret

API_KEY=***

PVC

1Gi 요청

Pod

env

환경변수로 주입

volumeMounts

파일·디렉터리로 마운트

파드가 사라져도 위 세 가지는 남는다

설정과 데이터를 밖에 두면, 파드는 언제든 지웠다 다시 만들어도 되는 것이 된다

배포판 고르기 — 도커 데스크톱·미니쿠베·K3s·kubeadm

1장 · 08

쿠버네티스는 하나의 제품이 아니라 규격에 가깝습니다. 같은 API를 여러 배포판이 다른 방식으로 구현하고, 목적에 따라 고를 배포판이 달라집니다. 이 시리즈가 다음 네 장에서 밟는 순서가 곧 그 선택지입니다.

  • 5장 도커 데스크톱 — 이미 깔린 도구의 체크박스 하나로 클러스터를 켠다. 가장 빠른 출발점이다
  • 6장 미니쿠베 — 도커 데스크톱 밖에서 클러스터를 띄운다. 버전을 직접 고를 수 있다
  • 7장 K3s — 단일 바이너리 경량 배포판. 실제 서버에 올려 노드를 붙여본다
  • 8장 K8s(kubeadm) — 표준 부품을 직접 조립해 컨트롤 플레인을 뜯어본다
  • 9장 헬름 — 배포판이 아니라 그 위에서 매니페스트를 패키징하는 도구다
  • 어느 쪽을 고르든 kubectl·YAML·개념은 같다 — 배운 것이 그대로 옮겨간다는 것이 표준의 값어치다

이 시리즈가 가는 길

1장 이론

지금 여기

2~4장 종합 개요

구조·객체·운영

5장 도커 데스크톱

체크박스 하나

6장 미니쿠베

노트북 한 대

7장 K3s

서버·다중 노드

8장 K8s

부품을 직접 조립

9장 헬름

패키징해서 배포

왼쪽에서 오른쪽으로 갈수록 감춰진 것이 줄고 직접 다루는 부품이 늘어난다