# 1.포드, 레플리카 셋, 디플로이먼트, 서비스

# 개요

  • 컨테이너 오케스트레이션 도구
  • 서버 자원 클러스터링, MSA 구조의 컨테이너 배포, 페일오버 처리 등
  • 컨테이너 기반의 클라우드를 운영할 때 필요한 기능과 컴포넌트 커스터마이징 가능
    • (e.g. PV, 장애 복구, 스케줄링, 오토 스케일링 등)
  • 여러 개의 노드로 구성됨(마스터, 워커)

# 쿠버네티스 노드의 역할

  • 마스터 : 쿠버네티스가 제대로 동작할 수 있게 클러스터 관리하는 역할
  • 워커 : 애플리케이션 컨테이너 생성

# Pod

  • 컨테이너 애플리케이션의 기본 단위
  • 1개 이상의 컨테이너로 구성된 컨테이너의 집합
# nginx-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: my-nginx-pod
spec:
  containers:
    - name: my-nginx-container
      image: nginx:latest
      ports:
        - containerPort: 80
          protocol: TCP
  • kubectl apply -f 명령어로 쿠버네티스에 생성 가능
  • kubectl describe 명령어로 리소스 정보를 볼 수 있음
$ kubectl describe pods my-nginx-pod
Name:             my-nginx-pod
Namespace:        default
Priority:         0
Service Account:  default
Node:             rpi4b/192.168.0.7
Start Time:       Fri, 10 Oct 2025 12:38:38 +0900
Labels:           <none>
Annotations:      <none>
Status:           Running
IP:               10.42.0.129 # 클러스터 내부에서 접속가능한 IP
IPs:
  IP:  10.42.0.129
Containers:
  my-nginx-container:
    ...
  • kubectl logs로 로그 확인 가능
  • kubectl delete -f 명령어로 쿠버네티스 오브젝트 삭제 가능

# Pod vs Docker Container

  • 쿠버네티스가 'Pod'라는 새로운 개념을 도입한 이유는 리눅스 네임스페이스를 공유하는 여러 컨테이너를 추상화된 집합으로 사용하기 위함
  • 하나의 포드는 하나의 완전한 애플리케이션으로써 만들어져야 함
    • nginx 컨테이너 1개 (o)
    • nginx 컨테이너 2개 (x : nginx 컨테이너는 그 자체만으로 완전한 애플리케이션이기 때문)
    • nginx 컨테이너 + 로그 수집 컨테이너 (o : 주 컨테이너에 대해 기능 확장을 위한 사이드카 컨테이너가 포함되어야 할 수 있음)

# 사이드카 컨테이너

  • 포드에 정의된 부가적인 컨테이너
  • 주 컨테이너에 대해 기능 확장을 위한 컨테이너
  • 한 포드 내의 다른 컨테이너와 네트워크 환경 등을 공유

# Replica Set

  • 일정 개수의 포드를 유지하는 컨트롤러

# 레플리카 셋을 사용하는 이유?

  • 정해진 수의 동일한 포드가 항상 실행되도록 관리
    • 포드만 사용한다면 원하는 컨테이너 개수만큼 직접 생성해줘야 함
  • 노드 장애 등의 이유로 포드를 사용할 수 없다면 다른 노드에서 포드 다시 실행 * 포드만 사용한다면 사용 불가능한 상태로 남아있음 → 결국 동일한 포드를 안정적으로 여러 개 실행하고 장애가 생기더라도 정해진 포드 개수를 유지하기 위함
# ---레플리카 셋 정의---
apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: replicaset-nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-nginx-pods-label
  # ---포드 정의---
  template:
    metadata:
      name: my-nginx-pod
      labels:
        app: my-nginx-pods-label
    spec:
      containers:
        - name: nginx
          image: nginx:latest
          ports:
            - containerPort: 80
  • spec.selector.matchLabels를 통해 생성해야 하는 포드를 찾음
  • app: my-nginx-pods-label 라벨을 가지는 포드의 개수가 replicas 항목에 정의된 숫자와 일치하지 않으면 포드 템플릿으로 포드 생성

# Deployment

  • 레플리카셋, 포드의 배포를 관리
  • 실제 쿠버네티스 운영환경에서는 레플리카셋과 포드의 정보를 정의하는 Deployment라는 오브젝트를 정의해 사용
  • 디플로이먼트는 레플리카셋의 상위 오브젝트 → 디플로이언트를 생성하면 해당 디플로이먼트에 대응하는 레플리카셋도 함께 생성됨
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-nginx
  template:
    metadata:
      name: my-nginx-pod
      labels:
        app: my-nginx
    spec:
      containers:
        - name: nginx
          image: nginx:latest
          ports:
            - containerPort: 80
  • 디플로이먼트, 레플리카 셋, 포드가 함께 생성되는 모습
$ kubectl get deployment
NAME                  READY   UP-TO-DATE   AVAILABLE   AGE
my-nginx-deployment   3/3     3            3           21s

$ kubectl get replicaset
NAME                            DESIRED   CURRENT   READY   AGE
my-nginx-deployment-9b459b4c5   3         3         3       26s

$ kubectl get pods
NAME                                  READY   STATUS    RESTARTS   AGE
my-nginx-deployment-9b459b4c5-cst47   1/1     Running   0          98s
my-nginx-deployment-9b459b4c5-gzwtc   1/1     Running   0          98s
my-nginx-deployment-9b459b4c5-lpklh   1/1     Running   0          98s

# 디플로이먼트를 사용하는 이유?

  • 애플리케이션의 업데이트와 배포를 더욱 편하기 만들기 위함
    • 애플리케이션 업데이트 시 레플리카 셋의 변경 사항을 관리하는 revision을 남겨 롤백을 가능하게 해줌
    • 무중단 서비스를 위해 포드의 롤링 업데이트 전략을 지정할 수 있음
  • ex : --record 옵션 사용
$ kubectl apply -f deployment-nginx.yaml --record # --record 옵션으로 이전 정보를 리비전으로써 보존
Flag --record has been deprecated, --record will be removed in the future
deployment.apps/my-nginx-deployment created

$ kubectl set image deployment my-nginx-deployment nginx=nginx:1.11 --record # 이미지 변경
Flag --record has been deprecated, --record will be removed in the future
deployment.apps/my-nginx-deployment image updated

$ kubectl get rs # 오류 발생
NAME                             DESIRED   CURRENT   READY   AGE
my-nginx-deployment-759b76bcd5   2         2         0       60s
my-nginx-deployment-9b459b4c5    2         2         2       99s

$ kubectl get pods # 오류 발생
NAME                                   READY   STATUS             RESTARTS      AGE
my-nginx-deployment-759b76bcd5-4jccd   0/1     CrashLoopBackOff   4 (29s ago)   119s
my-nginx-deployment-759b76bcd5-vk9ck   0/1     CrashLoopBackOff   3 (48s ago)   97s
my-nginx-deployment-9b459b4c5-n29cz    1/1     Running            0             2m38s
my-nginx-deployment-9b459b4c5-v4sq8    1/1     Running            0             2m38s

$ kubectl rollout history deployment my-nginx-deployment # 리비전 확인
deployment.apps/my-nginx-deployment
REVISION  CHANGE-CAUSE
1         kubectl apply --filename=deployment-nginx.yaml --record=true
2         kubectl set image deployment my-nginx-deployment nginx=nginx:1.11 --record=true

$ kubectl rollout undo deployment my-nginx-deployment --to-revision=1 # 리비전 1로 롤백
deployment.apps/my-nginx-deployment rolled back

$ kubectl get rs # 롤백 완료
NAME                             DESIRED   CURRENT   READY   AGE
my-nginx-deployment-759b76bcd5   0         0         0       2m36s
my-nginx-deployment-9b459b4c5    3         3         3       3m15s

$ kubectl get pods # 롤백 완료
NAME                                  READY   STATUS    RESTARTS   AGE
my-nginx-deployment-9b459b4c5-7xcbx   1/1     Running   0          11s
my-nginx-deployment-9b459b4c5-n29cz   1/1     Running   0          3m21s
my-nginx-deployment-9b459b4c5-v4sq8   1/1     Running   0          3m21s

# Service

  • 포드를 연결하고 외부에 노출하는 역할
  • 기존에 kubectl describe 명령어로 확인한 포드 내부 IP는 클러스터 내부에서만 접근 가능 + 영속적이지 않음
  • 서비스는 포드에 접근하기 위한 규칙을 정의
    • 여러 개의 포드에 쉽게 접근할 수 있도록 고유한 도메인 네임 부여
    • 여러 개의 포드에 접근할 때, 요청을 분산하는 로드 밸런서 기능
    • 포드를 외부로 노출

# 서비스 종류

  • ClusterIP 타입 : 쿠버네티스 내부에서만 포드들에 접근할 때 사용. 외부로 포드를 노출하지 않고 쿠버네티스 클러스터 내부에서만 사용되는 포드에 적합
  • NodePort 타입 : 포드에 접근할 수 있는 포트를 클러스터의 모든 노드에 동일하게 개방. 외부에서 포드에 접근 가능. 접근 포트는 랜덤으로 정해지지만 특정 포트로 접근하도록 설정도 가능
  • LordBalancer 타입 : 클라우드 플랫폼(AWS, GCP 등)에서 제공하는 로드 밸런서를 동적으로 프로비저닝해 포드에 연결. NodePort 타입과 마찬가지로 외부에서 포드에 접근 가능. 일반적으로 클라우드 플랫폼 환경에서만 사용 가능

# ClusterIP

# Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: hostname-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      app: webserver
  template:
    metadata:
      name: my-webserver
      labels:
        app: webserver
    spec:
      containers:
        - name: my-webserver
          image: alicek106/rr-test:echo-hostname
          ports:
            - containerPort: 80

# Service

apiVersion: v1
kind: Service
metadata:
  name: hostname-svc-clusterip
spec:
  ports:
    - name: web-port
      port: 8080
      targetPort: 80
  selector:
    app: webserver
  type: ClusterIP
  • webserver라는 라벨을 가진 포드들을 8080 → 80 으로 바인딩
$ kubectl get svc
NAME                     TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
hostname-svc-clusterip   ClusterIP   10.43.49.255   <none>        8080/TCP   2m54s

$ kubectl run -i --tty --rm debug --image=alicek106/ubuntu:curl --restart=Nev
er -- bash

...

root@debug:/# curl 10.43.49.255:8080 --silent | grep Hello
    <p>Hello, hostname-deployment-96df6b9c4-t5lpr</p>
root@debug:/# curl hostname-svc-clusterip:8080 --silent | grep Hello
    <p>Hello, hostname-deployment-96df6b9c4-w4pfb</p>
  • 위와 같이 클러스터 내의 임시 포드를 만들어 요청을 전송해볼 수 있음
  • IP 뿐만 아니라 서비스 이름 그 자체로도 접근 가능
  • 여러 포드가 클러스터 내부에서 서로를 찾아 연결할 때는 서비스의 이름과 같은 도메인 이름을 사용하는 것이 일반적
root@debug:/# curl hostname-svc-clusterip:8080 --silent | grep Hello
    <p>Hello, hostname-deployment-96df6b9c4-w4pfb</p>
root@debug:/# curl hostname-svc-clusterip:8080 --silent | grep Hello
    <p>Hello, hostname-deployment-96df6b9c4-t5lpr</p>
root@debug:/# curl hostname-svc-clusterip:8080 --silent | grep Hello
    <p>Hello, hostname-deployment-96df6b9c4-zf8q2</p>
  • 서비스는 연결된 포드들에 대해 자동으로 로드 밸런싱 수행

# endpoint

$ kubectl get svc
NAME                     TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
hostname-svc-clusterip   ClusterIP   10.43.49.255   <none>        8080/TCP   11m

$ kubectl get endpoints
Warning: v1 Endpoints is deprecated in v1.33+; use discovery.k8s.io/v1 EndpointSlice
NAME                     ENDPOINTS                                      AGE
hostname-svc-clusterip   10.42.0.144:80,10.42.0.145:80,10.42.0.146:80   11m
kubernetes               192.168.0.7:6443                               25d
  • 서비스의 라벨 셀렉터와 포드의 라벨이 매칭되어 연결되면 자동으로 엔드포인트라는 오브젝트가 생성됨

# NodePort

  • 모든 노드의 특정 포트를 개방해 서비스에 접근하는 방식
apiVersion: v1
kind: Service
metadata:
  name: hostname-svc-nodeport
spec:
  ports:
    - name: web-port
      port: 8080
      targetPort: 80
  selector:
    app: webserver
  type: NodePort
$ kubectl get svc
NAME                    TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)          AGE
hostname-svc-nodeport   NodePort    10.43.36.241   <none>        8080:31294/TCP   4s
  • clusterip 때 처럼 CLUSTER-IP에 내부 IP가 할당됨 → NodePort 타입의 서비스가 ClusterIP의 기능을 포함하고 있기 때문
  • 하지만 실제 운영 환경에서 NodePort로 서비스를 외부에 제공하는 경우는 많지 않음
    • 포트 번호를 80, 443 으로 설정하기엔 적절하지 않음(기본적으로 NodePort)가 사용할 수 있는 포트 범위는 30000~32768
    • tls 인증서 적용, 라우팅 등과 같은 복잡한 설정을 서비스에 적용하기 어렵
  • 대신 인그레스라는 오브젝트를 통해 간접적으로 사용

# sessionAffinity

---
spec:
  sessionAffinity: ClientIP
  • sessinAffinity 설정으로 Sticky Session 기능 적용 가능
  • 특정 클라이언트의 요청이 같은 Pod에서만 처리되게 할 수 있음

# externalTrafficPolicy

  • NodePort, LoadBalancer 타입의 서비스를 사용하면 각 워커 노드로 들어온 요청이 포드 중 하나로 전달됨
  • 하지만 위 그림과 같이 워커0으로 들어온 요청이 다시 워커1의 포드로 전달되는 경우
    • 불필요한 네트워크 홉이 발생함
    • 노드 간의 리다이렉트가 발생하게 되어 트래픽의 출발지 주소가 바뀜 → 클라이언트의 IP 주소가 보존되지 않음
  • 이는 externalTrafficPolicyCluster로 설정되어 있기 때문
$ kubectl get svc hostname-svc-nodeport -o yaml
apiVersion: v1
kind: Service
...
spec:
  externalTrafficPolicy: Cluster
...
  • 이를 방지하기 위해 externalTrafficPolicy를 Local로 설정 가능 → 포드가 생성된 노드에서만 포드로 접근 가능
  • 그럼 접근한 노드에 포드가 없으면?
  • 요청 처리 실패함

결론 if 불필요한 네트워크 홉으로 인한 레이턴시나 클라이언트의 IP 보존이 중요하지 않음 → Cluster else → Local

# ExternalName

  • ExternalName 타입을 사용해 서비스를 생성하면 서비스가 외부 도메인을 가리키도록 설정할 수 있음
apiVersion: v1
kind: Service
metadata:
  name: externalname-svc
spec:
  type: ExternalName
  externalName: my.database.com
사용하는 도메인 이름 변환되는 주소
externalname-svc my.database.com