Kubernetes, Node Components
kubelet은 Pod를 실행하고, Container Runtime은 컨테이너를 관리하며, kube-proxy는 네트워크 트래픽을 라우팅한다.
kubelet
kubelet은 모든 Worker Node에서 실행되는 에이전트이다.
API Server로부터 PodSpec을 받아 해당 노드에서 컨테이너가 정상적으로 실행되고 있는지 보장하는 역할을 한다.
flowchart LR
API["kube-apiserver"] -->|Watch PodSpec| kubelet
kubelet -->|CRI 호출| CR["Container Runtime"]
kubelet -->|상태 보고| API
CR --> Container1["Container"]
CR --> Container2["Container"]
-
노드 등록: kubelet이 시작되면 API Server에 자신(노드)을 등록한다
-
Pod 라이프사이클 관리: API Server를 Watch하여 해당 노드에 스케줄된 Pod를 감지하고, Container Runtime에 컨테이너 생성/삭제를 지시한다
-
상태 보고: 노드 상태(CPU, 메모리, 디스크 등)와 Pod 상태를 주기적으로 API Server에 보고한다
-
헬스체크 실행: 컨테이너 Probe(Liveness, Readiness, Startup)를 실행하여 컨테이너 상태를 모니터링한다
PLEG (Pod Lifecycle Event Generator)
kubelet은 PLEG라는 컴포넌트를 통해 컨테이너 상태 변화를 감지한다.
-
PLEG는 주기적으로(기본 1초) Container Runtime에 질의하여 Pod/컨테이너의 현재 상태를 확인한다
-
이전 상태와 비교하여 변화가 있으면 이벤트를 생성한다 (ContainerStarted, ContainerDied 등)
-
생성된 이벤트를 kubelet의 메인 루프에 전달하여 적절한 조치를 취하게 한다
PLEG가 응답하지 않으면 노드가
NotReady상태에 빠진다.PLEG is not healthy에러는 Container Runtime이 느리거나 노드에 과부하가 걸렸을 때 흔히 발생한다.
kubelet은 세 가지 Probe를 통해 컨테이너의 상태를 확인한다.
| Probe | 목적 | 실패 시 동작 | 사용 시점 |
|---|---|---|---|
| Liveness | 컨테이너가 살아있는지 확인 | 컨테이너 재시작 | 데드락, 무한 루프 탐지 |
| Readiness | 트래픽을 받을 준비가 되었는지 확인 | Service 엔드포인트에서 제거 | 초기화 완료 전, 일시적 과부하 |
| Startup | 앱이 시작되었는지 확인 | 컨테이너 재시작 | 시작 시간이 긴 레거시 앱 |
Probe 방식:
-
httpGet: HTTP GET 요청을 보내고 2xx~3xx 응답을 확인한다
-
tcpSocket: TCP 연결을 시도한다
-
exec: 컨테이너 내부에서 명령을 실행하고 exit code 0을 확인한다
-
grpc: gRPC Health Check Protocol을 사용한다
Static Pod
kubelet은 API Server 없이도 로컬 파일 시스템의 매니페스트를 읽어 Pod를 직접 실행할 수 있다. 이를 Static Pod라 한다.
-
기본 경로:
/etc/kubernetes/manifests/ -
Control Plane 컴포넌트(kube-apiserver, etcd, kube-scheduler 등)가 Static Pod로 실행되는 대표적 사례이다
-
kubelet이 API Server에 Mirror Pod를 생성하여
kubectl get pods로 조회할 수 있지만, 삭제/수정은 매니페스트 파일을 통해서만 가능하다
리소스 관리
kubelet은 노드의 리소스를 관리하고 보호하는 여러 메커니즘을 제공한다.
cgroups v2
- Linux 커널의 cgroups(Control Groups)를 사용하여 컨테이너별 CPU/메모리 할당을 제어한다
Eviction (축출)
kubelet은 노드 리소스가 부족할 때 Pod를 축출한다:
-
Soft Eviction: 유예 기간(grace period) 후 축출
-
Hard Eviction: 즉시 축출 (기본값: 메모리 100Mi, 디스크 10%, 노드 PID 등)
Device Plugin
-
GPU, FPGA 등 특수 하드웨어를 Kubernetes에서 관리하기 위한 프레임워크이다
-
kubelet과 gRPC로 통신하며, 디바이스 등록/할당/해제를 처리한다
Container Runtime
Container Runtime은 실제로 컨테이너를 생성하고 실행하는 소프트웨어이다.
kubelet은 CRI(Container Runtime Interface)를 통해 Container Runtime과 통신한다.
CRI (Container Runtime Interface)
flowchart LR
kubelet -->|CRI gRPC| Runtime["Container Runtime"]
Runtime -->|OCI 표준| Container["Container"]
subgraph "CRI 서비스"
RS["RuntimeService<br />(컨테이너 생성/삭제/실행)"]
IS["ImageService<br />(이미지 Pull/삭제/조회)"]
end
CRI는 kubelet과 Container Runtime 사이의 표준 gRPC 인터페이스이다.
-
RuntimeService: 컨테이너와 Pod Sandbox의 라이프사이클을 관리한다
-
ImageService: 컨테이너 이미지의 Pull, 삭제, 조회를 관리한다
CRI 덕분에 kubelet은 특정 Container Runtime에 종속되지 않으며, CRI를 구현한 어떤 런타임이든 사용할 수 있다.
kube-proxy
kube-proxy는 모든 노드에서 실행되며 Service의 네트워크 규칙을 관리하는 컴포넌트이다.
Pod 간 통신과 외부 트래픽의 Pod 라우팅을 담당한다.
Calico 같은 일부 CNI(Container Network Interface) 플러그인은 kube-proxy 없이 Service 라우팅을 직접 처리할 수 있다.
아래는 간단한 예시이다.
sequenceDiagram
participant Client as Client Pod
participant KP as kube-proxy<br/>(네트워크 규칙)
participant EP as EndpointSlice
participant Pod as Backend Pod
Note over KP: API Server를 Watch하여<br/>Service/EndpointSlice 변경 감지
Client->>KP: Service ClusterIP로 요청
KP->>KP: DNAT 수행<br/>(Service IP → Pod IP)
KP->>Pod: 실제 Pod로 전달
kube-proxy는 API Server를 감시하여 Service와 EndpointSlice의 변경을 감지하고, 노드의 네트워크 규칙을 업데이트한다.
실제 트래픽이 kube-proxy 프로세스를 경유하지는 않으며, 커널 레벨의 규칙(iptables/IPVS/nftables)으로 패킷을 전달한다.
Service 유형별 라우팅
ClusterIP
-
Service 생성 시 가상 IP(ClusterIP)가 할당된다
-
kube-proxy가 모든 노드에 네트워크 규칙을 설정한다
-
ClusterIP로 향하는 패킷에 DNAT(Destination NAT)를 수행하여 백엔드 Pod 중 하나로 전달한다
NodePort
-
ClusterIP의 모든 기능을 포함한다
-
추가로 모든 노드의 특정 포트(30000-32767)를 개방한다
-
<NodeIP>:<NodePort>로 들어온 트래픽을 ClusterIP를 거쳐 Pod로 전달한다
LoadBalancer
-
NodePort의 모든 기능을 포함한다
-
클라우드 프로바이더의 로드 밸런서가 모든 노드의 NodePort로 트래픽을 분산한다
EndpointSlice
Kubernetes v1.33부터 기존 Endpoints API는 공식 deprecated 되었으며, EndpointSlice로 대체된다.
-
기존 Endpoints는 한 Service의 모든 백엔드를 하나의 객체에 저장했다
-
EndpointSlice는 최대 100개씩 분할하여 저장한다
flowchart TB
subgraph "Worker Node"
kubelet["kubelet<br/>(노드 에이전트)"]
kp["kube-proxy<br/>(네트워크 규칙)"]
cr["Container Runtime<br/>(containerd)"]
kubelet -->|CRI gRPC| cr
cr --> C1["Container"]
cr --> C2["Container"]
cr --> C3["Container"]
kp -->|iptables/IPVS/nftables| NET["네트워크 규칙"]
end
API["kube-apiserver"] -->|PodSpec Watch| kubelet
API -->|Service/EndpointSlice Watch| kp
kubelet -->|노드/Pod 상태 보고| API
각 컴포넌트의 역할을 한 줄로 정리하면:
-
kubelet: “이 노드에서 어떤 Pod가 실행되어야 하는가”를 관리한다
-
Container Runtime: “컨테이너를 어떻게 생성/실행/삭제하는가”를 처리한다
-
kube-proxy: “Service로 들어온 트래픽을 어떤 Pod로 보내는가”를 결정한다