DeamonSet(ds)
DaemonSet: 쿠버네티스 클러스터의 모든 노드(또는 특정 노드)에 Pod를 하나씩 자동으로 배포하는 리소스
- 조건(nodeselector)에 맞는, 즉 해당하는 라벨을 가진 노드가 없다면 Pod는 생성되지 않음
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: goapp-on-ssd
spec:
selector:
matchLabels:
app: goapp-pod
template:
metadata:
labels:
app: goapp-pod
spec:
nodeSelector:
disk: ssd
containers:
- name: goapp-container
image: dangtong/goapp
- matchLabels: 라벨이 app=goapp-pod인 Pod만 이 DaemonSet이 관리
- template:metedata:labels: 위와 매치되는 라벨을 pod template에 부여(pod 생성)
- nodeSelector: ‘disk:ssd' 라벨을 가진 노드에만 Pod를 배포할 수 있음
Redis
- Sentinel Mode: Master/Slave 노드의 IP를 Sentinel이 보유 중
→ **Master와 Slave 노드 상태를 감시(monitoring)**하고, Master 장애 시 자동으로 Failover를 수행
→ 이때 Sentinel은 현재 Master/Slave의 IP 및 역할 정보를 저장하고 관리하며, 클라이언트에게 새 Master 정보를 전달하는 역할
- Cluster Mode: Sentinel(Routing table)을 Master에 내장해놓음
→ 수평 확장과 분산 저장이 가능한 구조
- Master-Slave
- Maste - 읽기/쓰기 가능
- Slave - 쓰기 불가, 읽기 전용
- Slave는 Master로부터 비동기 복제
- 지리적으로 멀리 떨어진 Slave는 로컬 클라이언트를 위한 읽기 처리용으로 활용될 수 있으나, 복제 지연이 있을 수 있음
- Multi-Master
- Redis는 기본적으로 단일 Master 구조
- 하지만 외부 도구(예: Redis Enterprise, CRDB by Redis, RedisRaft 등)를 사용하면 Multi-Master 구성을 지원
- 이 경우, 여러 노드에서 동시 쓰기 가능하지만, 복잡한 충돌 해결과 일관성 보장 메커니즘이 필요
Deployment(deploy)
Deployment: 쿠버네티스에서 애플리케이션(Pod)을 배포하고 관리하기 위한 상위 레벨의 객체
내부적으로 ReplicaSet을 생성하고 이를 통해 원하는 수의 Pod를 안정적으로 실행
- 애플리케이션(Pod) 배포용
- 쿠버네티스에서 가장 많이 작성되는 것들 중 하나
- Deployment는 Pod 와 ReplicaSet 에 대한 선언적 업데이트를 제공하며, 배포에 대한 세분화된 기능 제공
- 아!!! Deployment는 stateless임!!!
- PV를 하나만 두고 사용 -> 이 때문에 저장소(volume)은 EFS(공유 가능)를 사용해야 유리!!!
- Stateless App은 어떤 인스턴스에서 요청을 처리해도 결과가 같으므로 스케일링이 쉽고 장애 복구가 빠름
- Session이 없는 App이 stateless App
→ 마이크로서비스 아키텍처에서 각 서비스를 stateless하게 구성하는 것이 권장
선언적 업데이트(Declarative Update): 새로운 버전의 이미지나 설정을 적용하면, 기존 리소스를 자동으로 갱신 (예: 롤링 업데이트)
Deployment strategy

- Recreate(재가동): LB에서 v1으로 가동 중에 모든 서비스를 중단 후, LB에서 v2로 모든 서비스 가동(80%로 재가동 전략을 사용)
- 설정이 쉬움(대다수 비율 차지의 이유)
- Ramped → Rolling update: v1 클러스터에 3개의 노드가 LB에 연결되어 있음. 이후 하나씩 v2 클러스터에 노드가 하나씩 생성되면서 LB에 연결되고 v1 클러스터의 노드의 연결도 하나씩 끊김
- 서비스 중단이 없다는 장점!!
- 설정이 쉽다!!
- 데이터 Rebalancing이 진행되는 상태 유지 애플리케이션에 유리
- maxSurge? replicas에 더해서 생성할 수 있는 최대 노드의 수(replicas에 플러스)
- masUnavailable? 안 띄워도 되는 노드의 수(replicas에 마이너스)
- 재가동할 수 있는 범위를 정하는 변수들!!( maxSurge, masUnavailable )
- 두 변수의 차이가 재가동의 속도를 결정한다!!
- 다만 사용자가 한 번은 v1에, 다음은 v2에 요청이 가는 문제가 발생할 수 있음
- 이때 사용하는 것 → Sticky Session(***)
- Sticky Session: "같은 클라이언트가 한 번 연결된 서버(v1 또는 v2)에 계속 연결되도록 만드는 세션 유지 방식”
- Blue/Green: v1과 v2를 똑같은 양으로 띄어놓고, LB를 v1에 연결해놓던 도중 v2로 한번에 전환!!!!
- 트래픽 유실(서비스 중단) 없이 진행
- 똑같은 배포 환경이 두 개 존재할 때 서로 왔다 갔다 하는 상황에서 유리!!
- 다만, 2배의 리소스가 필요해서 비용이 비쌈.
- Canary: LB에서 v1에 100%로 트래픽을 전달하고 있는 도중에 v2를 띄워놓고 10%씩 천천히 트래픽을 옮기는 전략
- v2가 좀 민감한 서비스이기 때문에 천천히 트래픽을 옮기는 전략
- Ramped(Rollingupdate)와 비슷, 먼저 체크를 시작한다는 점이 다름
- 트래픽 이동에 istio 또는 linkerd와 같은 추가 도구 필요
- istio 사용시 커스텀 리소스인 RouteRule이 yml에 생성됨
- A/B Testing: v1, v2의 헤더의 쿠키를 파악해서 LB에서 트래픽을 v1, v2에 전달
- istio 필요
- Shadow(Mirrored, Dark): LB에서 v1으로 들어오는 트래픽을 v2로 그대로 복사
- istio 필요
- 모놀리식 서비스를 마이크로 서비스(MSA)로 전환할 때 사용 가능
- filter trafic 필요
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.29
ports:
- containerPort: 80
- 기본적으로 Replicaset의 상위호환이므로 필드(a.k.a속성)는 Replicaset과 매우 비슷
- deployment를 kubectl apply로 생성 시 -> deployment, service, pod 전부 한번에 생성
- spec:strategy: 해당 코드에는 없지만 배포 전략 설정 가능. Default는 RollingUpdate
RollingUpdate? -> 기존 Pod를 하나씩 교체해 무중단 배포 가능
- maxSurge: 기존 replicas 수보다 몇 개 더 생성할 수 있는지 (용량 초과 허용). Default=1
- maxUnavailable: 몇 개까지 일시적으로 비워도 되는지 (용량 부족 허용), Default=1
StateFulSet(sfs)
Statefulset: Pod 각각이 고유한 정체성과 저장소를 유지해야 할 때 사용하는 쿠버네티스 리소스(deployment와 비슷)
-> 주로 데이터베이스, 메시지 큐, 분산 시스템의 멤버 노드 등에 사용
- StatefulSet은 stateful, 각 Pod별로 고유 디스크 생성 → EBS
-> volumeClaimTemplates(→자동으로 PVC + PV + EBS 생성)
- Pod 순차 생성/삭제
- 각 Pod에 고정된 이름과 네트워크 ID
- 고유한 영구 저장소(PV, Persistent Volume)와 연결
<StatefulSet vs Deployment 비교>
| 항목 | StatefulSet | Deployment |
| Pod 이름 | 고정 (예: myapp-0, myapp-1) | 랜덤 (예: myapp-6c9f7f48) |
| 네트워크 ID | 고정 (DNS로 접근 가능, 예: myapp-0.myapp-headless.default.svc.cluster.local) | 변경될 수 있음 |
| 스토리지 | Pod마다 고유, PersistentVolumeClaim 유지 | 스토리지를 공유하거나 삭제 시 함께 제거 |
| 생성 순서 | 순차적으로 생성 (0번 → 1번 → ...) | 병렬 생성 |
| 삭제 순서 | 역순으로 삭제 | 병렬 삭제 |
| 디스크 타입 | 고유 디스크 사용 가능 → EBS 적합 | 공유 어려움 → 보통 EFS 필요 |
| PVC 연결 방식 | 각 Pod에 고유 PVC 자동 생성 (volumeClaimTemplates) | 여러 Pod가 하나의 PVC를 쓰기 어려움 |
| RWX(stateful) | RWO(stateless) |
- StatefulSet은 Stateful, 각 Pod별로 고유 디스크 생성 → EBS + volumeClaimTemplates(→자동으로 PVC + PV + EBS 생성)
- 각 Pod에 고정된 이름과 네트워크 ID
- Pod 순차 생성/삭제
- *고유한 영구 저장소(PV, Persistent Volume)**와 연결
- Deployment는 Stateless, EBS 쓰기 어려움 → EFS + SC(RWX) 필요
<Deployment or ReplicaSet에서의 1개의 PV>

- ReplicaSet or Deployment에서 PVC-PV가 한개면, 또한 EBS면 문제가 발생
- EBS(PV)는 기본적으로 RWO(ReadWriteOnce) → 한 Pod만 마운트 가능
- deployment은 애플리케이션을 위한 것(공유 어려움 → 보통 EFS 필요)
- stateless
- RWO
- 공유 어려움
- PV는 기본적으로 블록 디스크 DB(EBS)이므로 → 오직 하나의 Pod만 쓰기 가능하게 마운트,
→ 한 번에 여러 Pod에서 동시에 쓰려고 하면 에러 발생, 또한 블록 디스크는 읽기만 가능(쓰기가 안됨)
- 읽고 쓸 수 있는 Disk를 마운트해야 함. 그게 바로 뭐야? EFS(NFS)~~
<StatefullSet의 특징>

- StatefulSet은 volumeClaimTemplates를 통해 각 Pod에 고유 PVC를 자동 생성
- StatefulSet은 분산 데이터베이스 형태로 많이 사용
- Pod - PVC - PV가 순차적으로 생성되는 특징
- Pod 삭제 시도 시 순차적 ID 값이 가장 큰 것 부터 삭제됨
- 즉, 마지막부터 죽기 시작

- 왜 큰 ID부터 죽이냐?
→ 대부분의 첫 번째 생성된 리소스는 마스터로 중요한 정보가 들어있기 때문!!!(0이 마지막에 죽어야 함)
- 분산 DB 서비스를 운영하기 위해 이렇게 설계됨

- 삭제된 Pod가 다른 노드에 스케줄 되어 생성 되더라도, StatefulSet 특성 상 Pod가 동일한 ID로 생성되기 때문에 이전에 생성되었던 PVC로 다시 부착됨!!
StatefulSet 실습

1. app.js
// docker/app.js
const http = require('http');
const os = require('os');
const fs = require('fs');
const dataFile = "/var/data/kubia.txt";
const port = 8080;
// 파일 존재 유/무 검사
function fileExists(file) {
try {
fs.statSync(file);
return true;
} catch (e) {
return false;
}
}
var handler = function(request, response) {
// POST 요청일 경우 BODY에 있는 내용을 파일에 기록 함
if (request.method == 'POST') { # 데이터를 저장
var file = fs.createWriteStream(dataFile);
file.on('open', function (fd) { # 파일을 열어서 데이터를 저장했다는 표시를 남김
request.pipe(file);
console.log("New data has been received and stored.");
response.writeHead(200);
response.end("Data stored on pod " + os.hostname() + "\n");
});
// GET 요청일 경우 호스트명과 파일에 기록된 내용을 반환 함
} else { # POST가 아닐 경우는 GET!!, 저장된 데이터를 보여줌
var data = fileExists(dataFile) ? fs.readFileSync(dataFile, 'utf8') : "No data posted yet";
response.writeHead(200);
response.write("You've hit " + os.hostname() + "\n");
response.end("Data stored on this pod: " + data + "\n");
}
};
var www = http.createServer(handler);
www.listen(port);
→ Node.js 웹서버 소스 코드
- POST 요청이 들어오면 데이터를 파일에 저장 (/data/kubia.txt)
- GET 요청이 들어오면 저장된 파일 내용을 응답
- hostname을 통해 어떤 Pod가 응답했는지도 보여줌
🔧 데이터 저장 로직이 있고 Pod 고유성을 테스트할 수 있음
2. Dockerfile
# docker/Dockerfile
FROM node
ADD app.js /app.js
ENTRYPOINT ["node", "app.js"]
→ 위의 app.js를 컨테이너 이미지로 빌드하기 위한 파일
- ADD app.js로 복사
- ENTRYPOINT ["node", "app.js"]로 실행
🔧 컨테이너 생성용 빌드 정의
3. sts-lb.yml
# kubetemp/sts-lb.yml
apiVersion: v1
kind: Service
metadata:
name: nodesjs-sfs-lb
annotations:
# VPC 외부접속 가능
service.beta.kubernetes.io/aws-load-balancer-type: external
# 노드가 아니라 Pod ip로 직접 전달
service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
# NLB가 인터넷에 공개되는 퍼블릭 로드밸런서가 되도록 지정
service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
spec:
type: LoadBalancer # NLB
sessionAffinity: None
# 이거 왜해?
# -> 한번에 계속 하나에 집중해서 분산시키지 말고 여러군데로 분산시켜라~
# 즉, sticky session 사용 안하겠다!!
ports:
- port: 80
targetPort: 8080
selector:
app: nodejs-sfs
→ LoadBalancer 타입의 Service 정의
- 외부 접근을 위해 LoadBalancer 사용
- selector: app: nodejs-sts
- targetPort: 8080으로 app.js 서버 연결
🔧 외부에서 StatefulSet Pod로 접근 가능하게 해줌
4. sts.yml
# kubetemp/sts.yml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: nodejs-sfs
spec:
selector:
matchLabels:
app: nodejs-sfs
serviceName: nodejs-sfs
replicas: 2
template: # Deployment에는 template만 존재했으나
metadata:
labels:
app: nodejs-sfs
spec:
containers:
- name: nodejs-sfs
image: sss654654/nodejs:sfs
ports:
- name: http
containerPort: 8080
volumeMounts:
- name: data
mountPath: /var/data
volumeClaimTemplates: # statefullset에 따로 존재하는 인자
- metadata:
name: data
spec:
resources:
requests:
storage: 1Mi
accessModes:
- ReadWriteOnce (RWO)
# storageClassName: gp2 # 현재 default storage class는 gp2!!
→ StatefulSet 리소스 정의
- replicas: 2 → 2개의 Pod 생성
- 각 Pod는 고유 이름(nodejs-sts-0, nodejs-sts-1)을 가지고 생성됨 -> StatefulSet 특성
- volumeClaimTemplates를 통해 각 Pod가 개별 PVC를 갖도록 설정
- /data 경로에 영구 저장소 마운트
🔧 Pod마다 고유한 스토리지를 갖는 Stateful한 배포 방식
[클라이언트]
↓ (HTTP)
[LoadBalancer Service]
↓
-------------------------------
| nodejs-sts-0 (Pod) |
| - /data → PVC (EBS 등) |
| - app.js 서버 (8080포트) |
-------------------------------
-------------------------------
| nodejs-sts-1 (Pod) |
| - /data → PVC (EBS 등) |
| - app.js 서버 (8080포트) |
-------------------------------
- StatefulSet → Pod 이름이 고정되고 PVC가 Pod마다 1:1 대응됨
- LoadBalancer → 외부에서 nodejs-sts-{0,1}에 접근 가능
- 영속성 → kubia.txt는 각 Pod의 개별 볼륨에 저장됨 (재시작해도 보존)
Service
Kubernetes에서는 Pod가 언제든 죽고 다시 생성될 수 있고
이때 Pod는 항상 다른 IP 주소를 가짐
⇒ 클라이언트가 직접 Pod IP로 통신하면 불안정, 접속 오류 발생
<해결책: Service + DNS(CoreDNS) 조합>
Kubernetes Service: Pod의 IP가 바뀌어도 항상 고정된 방식으로 접근할 수 있게 해주는 가상 IP(DNS 이름)
- Pod는 재생성 시 IP가 변경됨
- Kubernetes 클러스터에는 기본적으로 DNS 서비스(CoreDNS)가 내장
- CoreDNS는 kube-system이라는 네임스페이스에서 실행됨
- CoreDNS가 변경된 Pod IP를 자동으로 반영해서 DNS로 서비스 연결 유지
CoreDNS: Service에 자동으로 부여된 DNS 이름을 실제 Pod IP로 자동 변환
- CoreDNS는 Service에 대한 DNS 이름을 자동으로 생성
- 이 DNS 이름을 조회하면, CoreDNS가 현재 살아 있는 Pod의 IP로 자동 라우팅
Service의 종류

1.ClusterIP: Default Service, 쿠버네티스 안에 존재, Layer 4(IP, Port)
2. NodePort: ClusterIP와 NodePort가 자동으로 생성, Layer 4(IP, Port)
- 성능이 별로임 -> 개발용
3. LoadBalancer: 쿠버네티스 바깥에 존재, Layer 4 LB(IP, Port)
- Service 리소스의 type이 LoadBalancer이고, 특정 annotations가 설정되어 있으면,
→ k8s에 Helm chart로 미리 설치한 aws-load-balancer-controller가 AWS NLB(Network Load Balancer) 생성
# fortune-lb.yml
apiVersion: v1
kind: Service
metadata:
name: fortune-lb
# NLB 생성 시 annotation이 없다면 외부와 연결이 안된다!!!
annotations:
# VPC 외부접속 가능
service.beta.kubernetes.io/aws-load-balancer-type: external
# 노드가 아니라 Pod ip로 직접 전달
service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
# NLB가 인터넷에 공개되는 퍼블릭 로드밸런서가 되도록 지정
service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
spec:
selector:
app: fortune
ports:
- port: 80
targetPort: 80
type: LoadBalancer
# externalIPs: # local 쿠버네티스 노드의 IP를 작성하면 LB 생성 가능
# - 192.168.0.2
4. Ingress: 쿠버네티스 바깥에 존재, Layer 7(실제 서비스 확인), url을 확인하고 트래픽 경로를 설정 가능 → 헤더까지 다 까서 볼 수 있음
- Helm chart로 설치된 **aws-load-balancer-controller**는 Kubernetes Ingress 리소스와 LoadBalancer 타입의 Service 리소스 중 일부를 감지
- Ingress 리소스를 감지하면, ALB를 생성
- Service 리소스의 type: LoadBalancer이고 특정 annotation이 설정되어 있으면 NLB(Network Load Balancer) 생성
→ 이 컨트롤러가 Ingress 리소스를 감지하여 ALB 생성
Ingress 사용 예
[클라이언트 브라우저]
↓
[ALB (DNS: *.elb.amazonaws.com)]
├── ENI-1 (AZ-a, IP: 54.x.x.x)
├── ENI-2 (AZ-b, IP: 52.x.x.x)
└── ENI-3 (AZ-c, IP: 3.x.x.x)
↓
[Kubernetes Ingress Controller (ALB Ingress Controller)]
↓
[Service: ClusterIP]
↓
[Pod] ← [Pod] ← [Pod] (예: nginx-deployment, replica: 3개)
↓
[EKS EC2 Worker Node 1] (예: ip-10-1-4-167.ap-northeast-2)
[EKS EC2 Worker Node 2] (예: ip-10-1-6-172.ap-northeast-2)
🔹 ALB (Application Load Balancer)
- AWS에서 생성된 Load Balancer
- kubectl get ingress로 확인 시 여러 IP가 나오는 이유는 Multi-AZ 대응용 ENI(Elastic Network Interface)가 각 가용영역에 자동 생성되기 때문
- IP 예: 54.x.x.x, 52.x.x.x, 3.x.x.x
🔹 Ingress Controller
- Kubernetes 클러스터 외부에 위치하여 ALB와 클러스터 내부를 연결
- aws-load-balancer-controller가 Ingress 리소스를 감지하여 ALB를 생성하고 관리함
🔹 Service (ClusterIP)
- 클러스터 내부에 위치하며, 클러스터 내부 Pod들을 하나의 IP로 묶는 역할
- 외부에서 직접 접근 불가 (Ingress가 중간에서 연결 다리 역할 수행)
🔹 Pod (nginx 등 앱)
- Deployment에 의해 관리됨
- 예: nginx-deployment가 replica 3개로 Pod를 생성함
🔹 Worker Node (EC2)
- 실제 Pod가 배포되는 물리적/가상 인스턴스
- 예: ip-10-1-4-167, ip-10-1-6-172 등
쿠버네티스의 3가지 네트워크
[ Kubernetes 클러스터 네트워크 구성 ]
├── [Node: master] ────> 호스트 네트워크 IP: 192.168.56.10
│ ├─ClusterIP: 10.96.0.1 ---> 서비스 네트워크 → port(80)
│ └─ Pod1 (10.244.0.2) ---> 파드 네트워크 → targetPort(8080)
├── [Node: worker1] ──> 호스트 네트워크 IP: 192.168.56.11
└── [Node: worker2] ──> 호스트 네트워크 IP: 192.168.56.12
클러스터 외부(사용자) -> 호스트 네트워크 -> 서비스 네트워크 -> 파드 네트워크
- 호스트 네트워크
- 각 Node(Master,worker)의 실 IP(docker inspect kind)
- 이 IP는 클러스터 외부 또는 클러스터 내부의 통신 경로로 사용됨
- 서비스 네트워크(서비스는 모든 노드마다 설정으로 변환됨!!)
- Kubernetes 내부에서 Service 객체가 할당받는 IP
- 이 IP는 모든 노드에서 접근 가능하게 kube-proxy가 설정해줌
- 생성된 NodePort 타입의 Service는 → 각 노드의 HostNetworkIP:NodePort 형태로 외부에서 접근 가능 (하지만 Service Network 자체에는 직접 접근 불가)
- Service: 10.96.0.1 (ClusterIP)
- NodePort: 30001 → 각 노드의 192.168.56.10:30001 등으로 접근 가능
- 파드 네트워크
- Pod에 직접 할당되는 IP
- Pod는 이 네트워크를 통해 서로 통신
- Pod 간에는 클러스터 내 어디에서든 IP로 직접 통신 가능해야 함
Headless Service

- 일반 ClusterIP 서비스는 고정된 IP를 제공하며 로드밸런싱 수행
- Service를 거치지 않고(고정 IP 없이, 로드 밸런싱 없이) Pod로 직접 접속하기 위해 사용하는 서비스
- ClusterIP에 clusterIP: None을 추가하는 것
- clusterIP가 아니라 Headless Service로 움직임
- Headless Service는 ClusterIP를 생성하지 않고, DNS 쿼리에 개별 Pod들의 IP를 직접 반환

- headless 옵션이 없으면 정상적으로 Service(Cluster IP)의 IP를 반환

- headless를 사용 -> clusterIP: None
- 개별 Pod들의 IP를 직접 반환된 모습(headless)
언제 Headless Service를 쓰나?
1. 분산형 데이터 저장 서비스 사용 시
- 예시:
- Cassandra, MongoDB, Redis, CouchDB 등
- DB, 메시지 큐, 인메모리 DB처럼 노드 간 데이터 분산이 중요한 구조
- 데이터를 자동으로 분산시키는 알고리즘 사용:
→ Consistent Hashing 등
2. Pod 하나하나에 직접 접근해야 할 때
- 클라이언트가 직접 특정 Pod에 연결해야 할 때
- 각 노드에 데이터 고유하게 저장해야 할 때(중요!!)
- 특정 Pod가 Leader일 때 (예: 리더 선출된 노드 직접 연결)
- 각 Pod의 상태 (예: 살아있는지) 개별 확인이 필요할 때
'Cloudwave > 쿠버네티스 강의' 카테고리의 다른 글
| Kubernetes 5일차 개념 정리 (0) | 2025.07.20 |
|---|---|
| Kubernetes 4일차 개념 정리 (1) | 2025.07.20 |
| Kubernetes 3일차 개념 정리 (1) | 2025.07.20 |
| Kubernetes 1일차 개념 정리 (2) | 2025.07.19 |