0. 개요
인프라 관련해 좀 무지한 것 같아서, K8S 공부할 겸 CKAD 자격증 딸 겸 K8S를 공부하는 중이다.
1. Cluster, Node, Pod란?
- Cluster: Control Plane과 Node들로 구성된 전체 Kubernetes 환경
- Control Plane은 클러스터의 상태를 관리하고 Pod를 적절한 Node에 배치한다.
- 여러 Node를 사용하면 애플리케이션 부하를 분산하고, 한 Node에 장애가 발생했을 때 다른 Node에서 Pod를 실행할 수 있다.
- Node: Pod가 실제로 실행되는 물리적 또는 가상의 머신(쿠버네티스가 설치되어 있음)
- 보통 컨테이너(격리된 애플리케이션 실행 환경)를 pod에 넣어 노드에 배치한다.
- Pod: Kubernetes가 배포하고 관리하는 가장 작은 단위.
- 일반적으로 하나의 애플리케이션 인스턴스를 실행하는 하나의 컨테이너를 포함.
- 애플리케이션의 처리 용량을 늘릴 때는 일반적으로 Pod 내부에 자원을 추가하기보다 Pod 개수를 늘리는 스케일 아웃을 사용
- Pod 수는 일반적으로 Deployment가 관리한다. (Deployment에 대해서는 뒤에서 자세히 다룬다.)
- Node에 Pod를 배치할 충분한 자원이 없으면 Pod는 Pending 상태가 된다. Cluster Autoscaler가 설정된 환경이라면 새 Node가 자동으로 추가될 수 있지만, 설정되지 않았다면 관리자가 직접 Node를 추가해야 한다.
- 긴밀하게 협력해야 하는 컨테이너라면, 하나의 pod에 여러 컨테이너가 있을 수 있다.
- 같은 생명주기와 네트워크·스토리지를 공유해야 할 정도로 밀접한 경우에 함께 넣는다.
ex) spring boot, alloy(로그 수집기) *보조 컨테이너를 helper라고 부르기도 한다. - Pod 내부는 하나의 독립적인 네트워크 공간이기에 pod 내부의 컨테이너들은 localhost를 통해 서로 통신할 수 있고, 같은 포트 번호를 동시에 사용할 수 없다.
- 동일한 Volume을 마운트하면 데이터를 공유할 수 있다.
- 같은 생명주기와 네트워크·스토리지를 공유해야 할 정도로 밀접한 경우에 함께 넣는다.
2. Master Node vs Worker Node

Master Node (=Control Plane Node)
:클러스터 전체 상태를 관리하고, Pod를 어느 Worker Node에 배치할지 결정한다.
- api server: Kubernetes Control Plane의 프런트엔드로, Kubernetes API를 제공한다.
- kubectl, Dashboard, 다른 Control Plane 구성요소들은 API Server를 통해 클러스터 상태를 조회하거나 변경한다. 인증·인가와 요청 검증도 담당한다.
- etcd: Kubernetes 클러스터의 상태를 저장하는 일관성과 고가용성을 갖춘 분산 Key-Value 저장소다.
- Pod, Deployment, Service 등의 설정, Node 정보, ConfigMap과 Secret, 클러스터의 현재 상태와 원하는 상태를 저장한다.
- 여러 Control Plane이 하나의 etcd 클러스터를 공유하며, etcd의 합의 알고리즘을 통해 데이터의 일관성을 유지한다.
- controller: 오케스트레이션의 두뇌 역할로 여러 Controller를 실행하며, 클러스터의 현재 상태를 사용자가 선언한 원하는 상태로 맞춘다.
- ex) Deployment에서 replicas: 3을 선언했는데 Pod가 2개뿐이라면, 관련 Controller가 API Server를 통해 새로운 Pod 리소스를 만들고, 배치와 실행은 Scheduler와 kubelet에게 넘긴다.
- scheduler: 아직 Node가 지정되지 않은 새로운 Pod를 찾아 적절한 Node에 할당한다.
- CPU와 메모리 여유, Pod의 리소스 요청량, Node Selector 등을 고려하여 Node를 결정한다.
- Scheduler는 컨테이너가 아니라 Pod 단위로 배치한다.
Worker Node
: 실제 애플리케이션 Pod가 실행되는 머신이다.
- kubelet: 각 Node에서 실행되는 에이전트다.
- API Server를 통해 해당 Node에 할당된 Pod의 명세를 확인하고, Container Runtime에 컨테이너 실행을 요청한다. 또한 Pod와 Node의 상태를 API Server에 보고한다.
- 해당 Node에 할당된 Pod 실행, 컨테이너 상태와 헬스 체크, 컨테이너 재시작, Volume 마운트, Pod 및 Node 상태 보고를 수한다.
- container runtime: 컨테이너 이미지를 내려받고 컨테이너를 실제로 실행하는 소프트웨어다.
- ex) containerd, CRI-O
- 현재 Kubernetes는 CRI(Container Runtime Interface) 호환 Runtime을 사용한다. Kubernetes에 내장됐던 Docker 연동 계층인 dockershim은 1.24부터 제거됐지만, Docker로 만든 이미지는 containerd나 CRI-O에서도 정상적으로 실행할 수 있다.
- proxy: 각 Node의 네트워크 규칙을 관리하여 Service로 들어온 트래픽이 적절한 Pod로 전달되도록 한다.
https://kubernetes.io/docs/concepts/overview/components
Kubernetes Components
An overview of the key components that make up a Kubernetes cluster.
kubernetes.io
3. Replication Controller와 ReplicaSet
: 지정한 개수만큼 동일한 pod가 항상 실행되도록 관리하는 컨트롤러.
selector의 label에 맞는 pod들을 모니터링하다가 하나가 삭제되거나 사라지면 새 pod를 만들어 개수를 맞춘다.
왜 필요한가?
Pod를 직접 생성하면 해당 Pod가 삭제되거나 Node 장애로 사라졌을 때 자동으로 복구되지 않는다.
-> ReplicaSet을 사용하면 원하는 Pod 개수를 선언할 수 있다. 그리고 ReplicaSet은 실제 Pod 개수를 계속 확인하며 원하는 상태를 유지한다.(조정)
* 트래픽 분산을 위해서는 일반적으로 Service를 함께 사용한다.
ReplicationController (RC)
: 지정된 개수만큼 동일한 Pod가 실행되도록 관리하는 구형 컨트롤러다.
아래 yml을 보면 Label과 Selector 필드가 있는데, 이는 모니터링할 대상을 판단하기 위함이다. Pod를 생성할 때 사용한 것과 동일한 label을 사용하여 모니터링 대상을 특정할 수 있게 한다.
apiVersion: v1
kind: ReplicationController
metadata:
name: nginx-rc
spec:
replicas: 3 # 몇 개를 유지할 것인가?
# 어떤 Pod를 관리할 것인가?
# spec.selector를 생략할 수 있다. 생략하면 Pod 템플릿의 label을 selector로 사용한다.
selector:
app: nginx
# 부족할 때 어떤 Pod를 만들 것인가?
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest
ReplicaSet (RS)
: ReplicationController의 후속 컨트롤러다.
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: nginx-rs
spec:
replicas: 3
# ReplicaSet에서는 spec.selector가 필수다. selector는 반드시 Pod 템플릿의 label과 일치해야 한다.
selector:
matchLabels:
app: nginx
tier: backend
template:
metadata:
labels: # selector의 조건을 템플릿이 만족하지 못하면 생성이 거부된다.
app: nginx
tier: backend
version: v1 # 템플릿에는 selector에 없는 label을 추가해도 된다.
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
ReplicaSet는 ReplicationController보다 풍부한 집합 기반 selector를 지원한다.
ReplicaSet은 다음과 같은 조건도 사용할 수 있다.
selector: # environment label이 production 또는 qa인 Pod를 선택한다.
matchExpressions:
- key: environment
operator: In
values:
- production
- qa
그리고 두 ReplicaSet의 selector가 겹치면 동일한 Pod 집합을 대상으로 경쟁하거나 예상치 못한 관리가 발생할 수 있기에 겹치지 않게 라벨링하자.
scale 수를 up하려면?
1. replicas 매개변수를 사용해 새 복제본 수를 제공, 입력과 동일한 파일을 지정
kubectl scale --replicas=6 -f replicaset-definition.yml
2. replicas 매개변수를 사용해 새 복제본 수를 제공, type과 이름으로 지정
kubectl scale --replicas=6 replicaset myapp-replicaset
*위 두 명령어로 scale up하면 리소스 수는 바뀌는데, 파일 자체가 바뀌진 않아서,
파일과 클러스터 상태를 맞추기 위해서는 파일 수정 후 apply해야 한다.
ReplicaSet이 ReplicationController의 후속 리소스이지만,
Deployment를 만들면 Kubernetes가 내부적으로 ReplicaSet을 자동 생성하고 관리하기에
사용자가 ReplicaSet을 직접 만들일이 없다.
공식 문서도 Deployment가 ReplicaSet을 관리하는 방식을 권장한다. 전체 과정은 다음과 같다.
사용자
↓ Deployment 생성
Deployment
↓ ReplicaSet 자동 생성 + 배포와 업데이트, 롤백 관리
ReplicaSet
↓ Pod 생성 및 개수 유지
Pod (실제 컨테이너 실행)
4. Deployment
: 동일한 Pod를 원하는 개수만큼 실행하고, 애플리케이션의 배포와 업데이트를 관리하는 상위 컨트롤러다.
이전에 언급했던 대로, 아래처럼 deployment를 생성하면
kubectl create deployment nginx --image=nginx --replicas=3
아래와 같이 rs도 함께 생성된다.
Deployment: nginx
└─ ReplicaSet: nginx-7854ff8877
├─ Pod: nginx-7854ff8877-abcde
├─ Pod: nginx-7854ff8877-fghij
└─ Pod: nginx-7854ff8877-klmno
더불어 deployment는 아래와 같은 기능을 추가로 제공한다.
- Rolling Update (기본 업데이트 전략) : 기존 Pod를 한 번에 전부 종료하지 않고 새로운 Pod를 점진적으로 생성하며 교체
- 이전 버전으로 롤백
- 배포 일시 중지 및 재개
- 배포 이력 관리
- 애플리케이션 재시작
ReplicaSet만으로도 Pod 개수는 유지할 수 있지만, Pod 이미지를 안전하게 교체하는 Rolling Update 등의 기능을 사용하려면 deployment를 사용해야 하는 것이다.
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-deployment
spec:
replicas: 3
# Deployment가 관리할 Pod를 찾는 조건
selector:
matchLabels:
app: myapp
tier: backend
# 생성할 Pod의 템플릿
template:
metadata:
labels: # selector.matchLabels의 모든 조건을 Pod 템플릿의 label이 만족해야 한다.
app: myapp
tier: backend
version: v1 # rs와 같이 템플릿에 selector 이외의 추가 label을 넣는 것은 가능하다.
spec:
containers:
- name: nginx-container
image: nginx:1.27
ports:
- containerPort: 80
관계는 이름에서도 확인할 수 있다.
deployment를 사용하여 deploy, rs, pod를 생성한 후, kubectl get 명령어를 사용하여 조회하면 아래와 같은 name을 같는다.
myapp-deployment Deployment
myapp-deployment-6b55d94f87 ReplicaSet
myapp-deployment-6b55d94f87-f7x8p Pod
kubectl
배포 진행 상태를 확인할 수도 있다.
kubectl rollout status deployment myapp-deployment
배포 이력 확인
kubectl rollout history deployment/myapp-deployment
이전 버전으로 롤백
kubectl rollout undo deployment/myapp-deployment
특정 revision 확인
kubectl rollout history deployment/myapp-deployment --revision=2
특정 revision으로 롤백
kubectl rollout undo deployment/myapp-deployment --to-revision=2
진행 중인 Deployment 변경을 일시 중지
kubectl rollout pause deployment/myapp-deployment
일시 중지된 동안 Pod 템플릿을 여러 번 수정한 후, 다시 rollout을 진행할 수 있다.
kubectl set image deployment/myapp-deployment nginx-container=nginx:1.28
kubectl set env deployment/myapp-deployment ENV=production
kubectl rollout resume deployment/myapp-deployment
이미지를 변경하지 않고 모든 Pod를 순차적으로 다시 시작
kubectl rollout restart deployment/myapp-deployment
Deployment와 ReplicaSet은 Pod를 생성하고 관리하지만 네트워크 접근 지점을 제공하지 않는다.
kubectl expose deployment myapp-deployment \
--name=myapp-service \
--port=80 \
--target-port=80
이렇게 service를 만들면,
클러스터 내부 Pod에서 myapp-service:80으로 접속 시 Deployment가 관리하는 Pod들:80로 트래픽을 전달한다.
*외부에서 접근하려면 NodePort를 만들어줘야 한다. (자세한건 뒤에서..!)
5. Namespace
: 하나의 Kubernetes 클러스터 안에서 리소스를 논리적인 그룹으로 나누는 방법이다.
Namespace는 물리적으로 별도의 클러스터를 만드는 것이 아니라, 같은 클러스터의 리소스를 논리적으로 나누는 것이다.
Namespace는 중첩할 수 없으며 하나의 namespaced 리소스는 하나의 Namespace에만 속할 수 있다.
- namespaced 리소스 : Pod, Deployment, Service, Configmap, Secret, Role, Pvc
- cluster-scoped 리소스(특정 ns에 속하지 않고 클러스터 전체에서 관리됨): Node, Namespace, StorageClass, ClusterRole 등..
필요한 이유
- 리소스 이름 구분
같은 종류의 리소스는 동일한 Namespace 안에서 이름이 중복될 수 없다. Namespace가 다르면 같은 이름을 사용할 수 있다.
단, 종류(pod, deployment..)가 다르면 같은 Namespace에서도 같은 이름을 사용할 수 있다. -
개발과 운영 환경을 분리할 수 있다.애플리케이션 설정에서 Service 이름을 동일하게 사용할 수 있기에 각 Pod에서 db-service를 조회하면 자신이 속한 Namespace의 Service를 찾는다.
- 팀과 환경 구분
- 권한 범위 설정
Namespace를 기준으로 사용자 또는 ServiceAccount의 권한을 나눌 수 있다.
Namespace는 권한을 나눌 수 있는 경계를 제공하지만, Role과 RoleBinding을 만들지 않으면 권한이 자동으로 제한되는 것은 아니다. - 리소스 사용량 제한
ResourceQuota를 사용하면 특정 Namespace에서 사용할 수 있는 전체 리소스의 상한을 설정할 수 있다.
Kubernetes에는 일반적으로 존재하는 Namespace
- default: Namespace를 명시하지 않은 일반 사용자 리소스가 생성되는 기본 공간이다.
- 단, 현재 kubectl context의 기본 Namespace를 바꿨다면 해당 Namespace에 생성된다.
- kube-system: DNS, 네트워크 플러그인 등 Kubernetes 시스템 구성요소가 사용하는 Namespace다.
- kube-public: 클러스터 전체에 공개할 수 있는 일부 정보를 위한 Namespace다.
- kube-node-lease: 각 Node의 heartbeat와 관련된 Lease 리소스가 저장된다. Control Plane은 이를 통해 Node 상태를 효율적으로 확인한다.
Namespace와 Service DNS
Service를 만들면 클러스터 DNS가 Service에 해당하는 DNS 레코드를 제공한다.
기본 형식은 다음과 같다.
<service-name>.<namespace>.svc.<cluster-domain>
기본 cluster domain이 cluster.local인 경우
<service-name>.<namespace>.svc.cluster.local
예를 들어 dev Namespace에 db-service가 있다면 아래와 같다.
db-service.dev.svc.cluster.local
*같은 ns에서 접근한다면 service 이름만 사용할 수 있다. (db-service)
다른 ns에서 접근하려면 전체 dns 이름을 다음과 같이 사용할 수 있다.
jdbc:mysql://db-service.dev.svc.cluster.local:3306/app
kubectl
네임스페이스 생성
kubectl create namespace dev
kubectl create ns dev // 축약형
모든 네임스페이스의 pod 출력
kubectl get pods --all-namespaces
kubectl get pods -A // 축약형
특정 ns 지정
kubectl get pods -—n=kube-system
kubectl run redis --image=redis -n=finance
kubectl get svc -n=marketing
kubectl create -f rs.yml -n=finance
ns 영구적 전환 (namespace=dev 자원 접근시 옵션 안붙여도 됨)
kubectl config set-context --curent --namespace=dev
다시 default로 되돌리려면
kubectl config set-context --current --namespace=default
현재 설정 확인
kubectl config view --minify
현재 context 확인
kubectl config current-context
ResourceQuota
: ResourceQuota는 하나의 Namespace에서 사용할 수 있는 전체 리소스의 상한을 설정한다.
다음은 dev Namespace에 적용하는 예다. (resource-quota-dev.yml)
apiVersion: v1
kind: ResourceQuota
metadata:
name: compute-quota
namespace: dev
spec:
hard:
pods: "10"
requests.cpu: "4"
requests.memory: 5Gi
limits.cpu: "10"
limits.memory: 10Gi
ns 생성, resouce quota 적용
kubectl create namespace dev
kubectl apply -f resource-quota-dev.yml
확인
kubectl get resourcequota -n dev
kubectl get quota -n dev
kubectl describe quota compute-quota -n dev
6. Services
: 변경될 수 있는 여러 Pod 앞에 고정된 IP 주소와 DNS 이름(단일 인터페이스)을 제공하는 Kubernetes 리소스다.
왜 필요한가?
Pod는 재생성되면 IP가 바뀔 수 있다. 클라이언트가 Pod IP를 직접 사용한다면 Pod가 재생성될 때마다 접속 주소를 변경해야 한다.
-> Service를 사용하면 클라이언트는 Pod IP를 몰라도 된다.
Service는 다음 기능을 제공한다.
- 고정된 Service IP
- 안정적인 DNS 이름
- label을 이용한 Pod 탐색
- 여러 Pod로 트래픽 전달 (부하 분산)
- 클러스터 내부 또는 외부에 애플리케이션 공개
- 새로 생성된 Pod의 label이 Service의 selector와 일치하고 Pod가 Ready 상태가 되면, Kubernetes가 해당 Pod의 IP와 포트를 EndpointSlice에 추가하여 Service 트래픽 전달 대상으로 포함한다.
- Pod가 추가·삭제되거나 Ready 상태가 변하면 Kubernetes가 EndpointSlice를 자동으로 갱신한다.
- 네트워크 구현체는 이 정보를 기준으로 트래픽 전달 대상을 갱신한다.
Service는 Deployment가 아니라 Pod를 선택한다. 즉, Deployment가 만든 Pod든 직접 생성한 Pod든 label이 일치하면 Service의 대상이 될 수 있다. (자신과 같은 Namespace의 Pod만)
*kubectl expose deployment를 실행하면 Deployment를 네트워크 대상으로 직접 사용하는 것이 아니라, Deployment의 selector를 가져와 Pod를 선택하는 Service를 만드는 것이다.
Service의 대표 타입
| ClusterIP | 클러스터 내부 | 내부 마이크로서비스, DB, Redis |
| NodePort | Node IP와 포트를 통한 외부 접근 | 테스트, 직접 Node 노출 |
| LoadBalancer | 외부 Load Balancer | 클라우드 환경의 외부 서비스 |
| ExternalName | 외부 DNS 이름 연결 | 외부 서비스에 Kubernetes DNS 이름 제공 |
하나의 Service가 상위 타입의 기능을 가지면서 하위 타입의 접근 방식도 함께 제공하는 구조에 가깝다.
LoadBalancer (클라우드 로드밸런서를 생성해 외부 트래픽을 Service로 전달)
└─ NodePort (각 노드의 특정 포트를 열어 외부에서 노드IP:포트로 접근 가능하게 함)
└─ ClusterIP (클러스터 내부에서만 접근 가능한 기본 Service. Pod 간 통신에 사용)
└─ Pod
- NodePort Service에는 일반적으로 ClusterIP도 할당됨
- LoadBalancer Service에는 일반적으로 ClusterIP와 NodePort도 할당됨
Port
ports:
- port: 80 // Service가 요청을 받는 포트
targetPort: 8080 // 실제 애플리케이션이 실행되는 Pod의 포트
nodePort: 30080 // 외부에서 접근하는 Node의 포트
*targetPort를 생략하면 기본적으로 port와 같은 값을 사용한다.

ClusterIP
: ClusterIP는 클러스터 내부에서만 접근할 수 있는 기본 Service 타입이다.
apiVersion: v1
kind: Service
metadata:
name: backend-service
spec:
type: ClusterIP
selector:
app: myapp
tier: backend
ports:
- port: 80
targetPort: 8080
흐름
클러스터 내부 Pod
↓ backend-service:80
ClusterIP Service:80
↓
Backend Pod:8080
Pod를 Deployment로 관리하면 Pod가 계속 생성되고 삭제되지만 Service의 DNS 이름은 유지된다.
따라서 각 애플리케이션 계층은 다른 Pod의 위치나 개수 변화를 몰라도 된다.
kubectl expose deployment backend \
--name=backend-service \
--port=80 \
--target-port=8080
* type을 지정하지 않았으므로 ClusterIP Service가 생성된다.
Pod 생성하면서 동시에 expose 할 수도 있다. (=service 생성)
kubectl run httpd --image=nginx --port=80 --expose=true
NodePort
: NodePort는 각 Node IP의 동일한 포트를 통해 Service에 접근할 수 있도록 한다.
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
type: NodePort
selector:
app: nginx
tier: frontend
ports:
- port: 80
targetPort: 8080 # end point
nodePort: 30008
흐름
외부 클라이언트
↓ <NodeIP>:30008
NodePort
↓ Service port 80
Endpoint의 port 8080
*NodePort는 기본적으로 30000~32767 범위를 사용하지만, 클러스터 관리자가 범위를 변경할 수 있다.
kubectl expose pod nginx \
--port=80 \
--nodePort=30008 \
--name nginx-service \
--type=NodePort
*nodePort를 생략하면 Kubernetes가 사용 가능한 포트를 자동 할당한다.
Node가 세 개이고 nodePort: 30008이라면 일반적으로 다음 주소에서 같은 Service로 접근할 수 있다.
Node 1 IP:30008
Node 2 IP:30008
Node 3 IP:30008
그리고 사용자가 Node 1으로 요청해도 다른 Node의 준비된 Endpoint(pod)로 전달될 수 있다.
단, 방화벽이나 클라우드 Security Group에서 해당 NodePort 접근이 허용돼 있어야 한다.
externalTrafficPolicy
: NodePort와 LoadBalancer로 들어오는 외부 트래픽의 전달 범위를 설정한다.
- externalTrafficPolicy: Cluster (default)
→ 요청을 받은 Node에 대상 Pod가 없어도 다른 Node의 준비된 Endpoint로 전달할 수 있다.
(+) 전체 클러스터의 Endpoint를 활용, Pod가 없는 Node로 요청해도 처리 가능, 트래픽을 비교적 넓게 분산
(-) 다른 Node를 거치는 추가 네트워크 홉이 생길 수 있음 - externalTrafficPolicy: Local
→ 요청을 받은 Node에 존재하는 Endpoint로만 전달한다.
(+) 다른 Node를 거치는 추가 홉 방지, 원본 클라이언트 IP 보존에 유리
(-) 요청받은 Node에 준비된 Endpoint가 없으면 트래픽이 버려짐, Node별 Pod 수가 다르면 트래픽이 불균등해질 수 있음
*참고로 클러스터 내부 트래픽에도 이와 비슷한 internalTrafficPolicy가 있다.
spec:
internalTrafficPolicy: Local
이 경우 내부 Pod의 요청도 같은 Node의 Endpoint로만 전달한다.
LoadBalancer
: LoadBalancer Service는 외부 Load Balancer를 통해 애플리케이션을 공개한다.
LoadBalancer 생성은 Kubernetes 자체가 직접 수행하는 것이 아니라 클라우드 제공자나 설치된 Load Balancer Controller가 처리한다.
apiVersion: v1
kind: Service
metadata:
name: nginx-loadbalancer
spec:
type: LoadBalancer
selector:
app: nginx
ports:
- port: 80
targetPort: 8080
흐름
외부 클라이언트
↓ Load Balancer 주소:80
클라우드 Load Balancer
↓ NodePort 또는 직접 라우팅
Service
↓
Pod:8080
클라우드 환경에서는 다음과 같은 Load Balancer가 만들어질 수 있다.
- AWS ELB/NLB
- Google Cloud Load Balancer
- Azure Load Balancer