0. Contents

1. Overview

Stateless Service 패턴의 핵심무상태 서비스동일하고 교체 가능한 인스턴스들의 집합Pod를 유지ReplicaSet트래픽 분산Service저장소 관리PVC
Stateless Service 패턴의 핵심: 세 가지 추상화가 하나씩 맡는다

패턴의 정의와 적합한 환경

  • Stateless Service 패턴은 동일한 일시적(ephemeral) 복제본들로 구성된 애플리케이션을 만들고 운영하는 방법을 설명한다
  • 빠른 확장과 높은 가용성이 필요한 동적 클라우드 환경에 가장 적합한 패턴이다

2. Problem

일시적 서비스의 조건

  • 마이크로서비스 아키텍처에서 서비스를 일시적(ephemeral) 으로 만들려면
    • 서비스가 부작용 없이 생성·확장·삭제될 수 있어야 한다
  • 이 특성은 서비스가 무상태(Stateless) 일 때 훨씬 쉽게 달성된다
Stateful (상태 있음)Stateless (상태 없음)
비유단골 카페자판기
요청 처리사장님이 내 취향을 기억누가 눌렀는지 모르고 버튼만 보고 음료를 줌
교체 시사장님이 바뀌면 다시 알려야 함고장나도 옆 걸로 바꾸면 됨

무상태 서비스의 요청 처리

  • 내부적으로 어떤 상태도 유지하지 않는다
  • 과거 요청에 대한 저장된 지식이나 참조가 없다
    • 그래서 각 요청은 마치 처음부터 새로 시작하는 것처럼 처리된다

무상태 판별 사고 실험

실험 가정

  • 서비스 인스턴스들이 서로 다른 노드에 배포된다
  • 로드 밸런서가 sticky session 없이 요청을 무작위로 분배한다
로드밸런서랜덤 분배, sticky session 없음다른 노드서버A다른 노드서버B다른 노드서버C어느 서버가 요청을 받아도 정상 동작한다면=Stateless특정 서버만 요청을 받아야 한다면=Stateful
아무 서버나 받아도 되면 Stateless, 특정 서버여야 하면 Stateful

판별 기준

  • 이 설정에서도 서비스가 제 역할을 다할 수 있다면, 그것은 무상태 서비스다

3. Solution

애플리케이션 = 여러 요소의 집합

  • Kubernetes에는 “애플리케이션(Application)“이나 “컨테이너(Container)“를 최상위 개체로 갖는 개념이 없다
  • 대신 애플리케이션은 여러 요소들의 집합으로 구성된다

하나의 애플리케이션

  • Deployment / ReplicaSet → Pod 생성 및 유지
  • ConfigMap / Secret → 설정 및 민감 정보
  • Service → 네트워크 진입점
  • PersistentVolumeClaim → 영구 저장소

Deployment와 ReplicaSet의 관계

  • 무상태 Pod를 관리하는 컨트롤러는 ReplicaSet이다
    • 하지만 이것은 Deployment가 내부적으로 사용하는 하위 레벨 구조다
  • Deployment가 사용자가 직접 다루는 권장 추상화다
사용자가 직접 다루는 것Deployment업데이트/롤백 관리 (권장)내부적으로 생성·관리ReplicaSet복제본 수 유지 (하위 레벨)실제로 생성PodPodPod실제 컨테이너 실행
사용자는 Deployment 만 다루고, 그 아래는 알아서 만들어진다

ReplicaSet 직접 사용: 두 경우로 한정

  • Deployment의 업데이트 전략이 맞지 않을 때
  • 커스텀 메커니즘이 필요할 때

4. Instances: ReplicaSet

ReplicaSet의 주요 목적

  • 언제든지 지정된 수의 동일한 Pod 복제본이 실행되도록 보장하는 것이다

ReplicaSet의 3가지 핵심 구성요소

구성요소역할비유
replicas유지할 Pod 수 지정“직원은 항상 3명이어야 해”
selector관리할 Pod를 식별“이름표에 ‘my-app’ 붙은 직원만 관리”
template새 Pod 생성 설계도“새 직원 채용 공고문”

Example 11-1. ReplicaSet 명세

apiVersion: apps/v1        # ReplicaSet은 apps API 그룹 소속 (core v1이 아님)
kind: ReplicaSet
metadata:
  name: rg                 # ReplicaSet 자체의 이름 — Pod 이름은 이 뒤에 랜덤 접미사가 붙어 생성됨(rg-xxxxx)
  labels:
    app: random-generator  # ReplicaSet 리소스 자체에 붙는 레이블(관리용). selector와는 별개
spec:
  replicas: 3                        # ① 유지할 Pod 복제본의 목표 수 — 컨트롤러가 이 값을 계속 감시
  selector:
    matchLabels:
      app: random-generator          # ② 관리할 Pod를 찾는 레이블 조건 — template.metadata.labels와 반드시 일치해야 함
  template:                          # ③ 새 Pod를 만들 때 사용할 템플릿 (이하는 일반 Pod spec과 동일)
    metadata:
      labels:
        app: random-generator        # selector가 찾는 바로 그 레이블 — 여기서 Pod가 실제로 부여받음
    spec:
      containers:
      - name: random-generator       # 컨테이너 이름 (Pod 안에서 로그/exec 시 식별자로 사용)
        image: k8spatterns/random-generator:1.0   # 3개 Pod 모두 동일한 이미지로 복제됨

자동 복구 동작

목표: Pod 3개 유지정상 상태Pod1정상Pod2정상Pod3정상Pod 하나가 죽으면Pod1정상Pod2정상Pod3죽음 💥ReplicaSet이 감지!Pod1정상Pod2정상Pod3죽음 💥Pod4 🆕생성 중...자동 복구 완료Pod1정상Pod2정상Pod4정상
ReplicaSet 은 죽은 Pod 를 살리지 않고, 새 Pod 로 개수를 다시 맞춘다

selector와 template label이 일치해야 하는 이유

위치레이블의미
selector.matchLabelsapp: random-generator“이 레이블을 가진 Pod를 관리하겠다”
template.metadata.labelsapp: random-generator“새로 만드는 Pod에 이 레이블을 붙인다”

이 둘이 반드시 같아야 함!

  • 다르면 ReplicaSet이 자신이 만든 Pod를 관리 대상으로 인식 못함 ❌

ReplicaSet의 범위: 클러스터 전체가 아닌 네임스페이스

동작 범위와 자원 소비

  • ReplicaSet은 클러스터 전체가 아니라 특정 네임스페이스 안에서만 동작한다
  • 단, Pod들이 실행되면서 클러스터 전체 노드의 CPU/메모리 자원을 소비한다

클러스터 전체

  • namespace: default ← ReplicaSet “rg” 여기 있음
    • [Pod1] [Pod2] [Pod3]
  • namespace: production ← 여기엔 영향 없음
  • namespace: staging ← 여기도 영향 없음

5. Networking: Service

Pod의 근본적인 문제

Pod의 수명: 일시적

  • ReplicaSet이 생성한 Pod는 일시적(ephemeral) 이며 언제든지 사라질 수 있다
기존 Pod새 Pod
이름pod-abcpod-xyz (바뀜)
IP10.0.0.110.0.0.5 (바뀜!)
hostnamepod-abcpod-xyz (바뀜)

핵심 문제: 찾아오는 방법

  • IP가 바뀌면 클라이언트가 이전 IP로 요청을 보내도 찾을 수 없다
  • Stateless라면 어떤 Pod가 요청을 받아도 동일하게 처리할 수 있다
    • 그러므로 문제는 “찾아오는 방법"에 있다

Kubernetes Service가 해결하는 것

Service IP는 고정Pod IP는 계속 바뀜클라이언트Service10.96.0.1 고정Pod110.0.0.1Pod210.0.0.2Pod310.0.0.3Pod가 죽고 새로 생겨도 클라이언트는 항상 10.96.0.1로만 보내면 됨!
Pod IP 는 바뀌어도 클라이언트는 Service IP 하나만 안다

Service가 필요한 경우 vs 불필요한 경우

Service 불필요앱이 먼저 외부로 연결을 시작할 때Pod메시지 브로커/DB"내가 먼저 연결할게"내 IP가 바뀌어도 상관없음Service 필요다른 서비스가 내 Pod로 요청을 보낼 때다른 서비스내 Pod"너한테 요청 보낼게"내 Pod IP가 바뀌면 상대가 못 찾음Service로 고정 IP 제공해야 함
연결을 누가 먼저 거느냐가 Service 필요 여부를 가른다

Example 11-2. 무상태 서비스 노출하기

apiVersion: v1              # Service는 core v1 API 그룹 소속
kind: Service
metadata:
  name: random-generator          # ① Pod에 접근할 때 사용하는 서비스 이름 — 클러스터 내부 DNS도 이 이름으로 생성됨
                                   #    (예: random-generator.default.svc.cluster.local)
spec:
  # type을 생략하면 기본값은 ClusterIP — 클러스터 내부에서만 접근 가능(외부 노출은 NodePort/LoadBalancer 필요)
  selector:
    app: random-generator         # ② ReplicaSet의 Pod 레이블과 매칭되는 셀렉터
                                   #    이 레이블을 가진 Pod들이 자동으로 이 Service의 엔드포인트가 됨
  ports:
  - port: 80                      # ③ Service가 클러스터 내부에 노출하는 포트 (클라이언트가 호출하는 포트)
    targetPort: 8080              # ④ Pod 내부 컨테이너가 실제로 listening하는 포트 (컨테이너 spec의 포트와 일치해야 함)
    protocol: TCP                 # ⑤ 기본값이 TCP라 생략 가능하지만 명시적으로 적어두면 의도가 분명해짐

포트 개념: 건물 비유

  • 클라이언트가 보내는 곳 → port: 80
  • 실제 Pod 안에서 받는 곳 → targetPort: 8080
  • “안내데스크(80번 창구)로 오세요 → 실제 담당자는 8080호 사무실에 있어요”

clusterIP: 클러스터 내부 전용 고정 IP

clusterIP 할당과 특성

  • Service가 생성되면 clusterIP가 자동으로 할당된다
  • 이 IP는 Kubernetes 클러스터 내부에서만 접근 가능하다
  • Service가 삭제될 때까지 절대 바뀌지 않는다
클러스터 내부프론트엔드 Podlabel: app=frontendclusterIP로 접근 (10.96.100.5:80)Service: random-generator로드밸런싱Pod1Pod2Pod3label: app=random-generator외부 인터넷외부 인터넷 → 접근 불가내부 Pod → 접근 가능
clusterIP 는 클러스터 안에서만 통하는 대표 번호다

비유: 대표 전화번호

  • clusterIP는 서로 다른 서비스끼리 통신하는 대표 전화번호 같은 개념이다
  • 라벨만으로 직접 찾으려 하면
    • 로드밸런싱이 없다
    • 죽은 Pod의 IP를 추적하기 어렵다

Service의 3가지 핵심 기능

기능설명
고정 IP (clusterIP)Pod가 바뀌어도 Service IP는 그대로
로드 밸런싱요청을 여러 Pod에 자동으로 분산
헬스 체크readiness probe 통과한 정상 Pod에만 요청 전달

Deployment/ReplicaSet과 Service는 서로 모른다

Deployment/ReplicaSetService
하는 일“Pod 3개만 유지하면 됨”“app=random-generator Pod로 트래픽 보내면 됨”
상대에 대한 관심“Service가 있는지 없는지 전혀 관심 없음”“Deployment가 있는지 없는지 전혀 관심 없음”

연결 고리: label/selector

  • 이 둘은 label/selector라는 공통 언어를 통해서만 간접적으로 연결된다
DeploymentService서로 모름PodPodPodlabel: app=random-generatorPod 생성(label 부착)selector로 해당 label을가진 Pod를 찾아 연결
Deployment 와 Service 사이에 직접 선은 없다 — 공통 언어는 label

6. Storage: PersistentVolumeClaim

PVC란?

PV와 PVC

  • PV (PersistentVolume) = 실제 저장 공간 (창고)
  • PVC (PersistentVolumeClaim) = 창고 사용 신청서

신청서 방식의 동작

  • 개발자가 직접 창고를 만드는 게 아니다
  • “이런 창고가 필요해요” 라고 신청서를 내면
    • Kubernetes가 알아서 맞는 창고를 연결해줌

Example 11-3. PersistentVolume 요청(claim)

apiVersion: v1                 # PVC도 core v1 API 그룹 소속
kind: PersistentVolumeClaim
metadata:
  name: random-generator-log   # ① Pod 템플릿에서 참조할 claim 이름 — volumes.persistentVolumeClaim.claimName에 그대로 사용됨
spec:
  storageClassName: "manual"   # 어떤 StorageClass(=어떤 종류의 스토리지 프로비저너)를 쓸지 지정
                                # "manual"은 관리자가 PV를 미리 만들어둔 정적 프로비저닝을 의미
                                # 클라우드 환경이면 보통 "standard", "gp3" 같은 동적 프로비저닝용 클래스를 씀
  accessModes:
  - ReadWriteOnce               # ② 단일 노드에서만 읽기/쓰기 가능 (노드 단위 제한, Pod 단위 아님 — 아래 RWO vs RWOP 참고)
  resources:
    requests:
      storage: 1Gi              # ③ 1GiB 저장 공간 요청 — 실제 PV의 용량이 이보다 크거나 같아야 바인딩됨
# 이 PVC는 template.spec.volumes에서 이름으로 참조되고,
# containers[].volumeMounts에서 컨테이너 내부 경로에 마운트된다.

accessModes 4가지

accessModes의 역할

  • PVC의 핵심 필드인 accessModes는 저장소가 노드에 어떻게 마운트되고 Pod에 의해 어떻게 사용되는지를 제어한다
ReadWriteOnce (RWO)노드 단위Node APod1 · 읽기/쓰기Pod2 · 읽기/쓰기Node BPod3 · 접근불가ReadOnlyMany (ROX)여러 노드, 읽기만Node APod1 · 읽기만Node BPod2 · 읽기만Node CPod3 · 읽기만ReadWriteMany (RWX)여러 노드, 읽기/쓰기Node APod1 · 읽기/쓰기Node BPod2 · 읽기/쓰기Node CPod3 · 읽기/쓰기ReadWriteOncePod (RWOP)Pod 단위 (가장 엄격)Node APod1 · 읽기/쓰기Pod2 · 접근불가Node BPod3 · 접근불가Pod2: 같은 노드여도 다른 Pod는 NO
accessModes: 누가(노드? Pod?) 볼륨을 읽고 쓸 수 있나

RWO vs RWOP: 미묘하지만 중요한 차이

제한 단위: 노드 vs Pod

  • RWO는 노드 단위 제한이라 같은 노드의 여러 Pod가 동시에 쓸 수 있다
  • 오직 RWOP만이 단 하나의 Pod만 볼륨에 접근함을 보장한다
모드제한 단위Node A의 Pod1Node A의 Pod2
ReadWriteOnce노드 단위✅ 쓰기 가능✅ 쓰기 가능 ← 같은 노드면 여러 Pod도 OK
ReadWriteOncePodPod 단위✅ 쓰기 가능❌ 접근 불가 ← 같은 노드여도 다른 Pod는 NO

RWOP 주의점: 스케일 아웃

  • replicas: 3 으로 늘리려 했더니…
    • Pod1 ✅ 실행 중 (PVC 점유 중)
    • Pod2 ❌ Pending! 스케줄러가 노드에 배치하지 못함
    • Pod3 ❌ Pending! 같은 이유
    • kubectl describe pod 이벤트에 FailedScheduling 과 함께 같은 RWOP PVC를 쓰는 Pod가 이미 있다는 메시지가 남음
  • → 사실상 싱글톤(1개)으로 고정됨
  • → Stateless의 장점인 스케일 아웃이 불가능해짐!

RWOP 주의점: 선점(preemption)

  • 처음(알파)에는 RWOP PVC를 쓰는 Pod에 선점이 적용되지 않았다
  • Kubernetes 1.27(베타)부터 스케줄러가 RWOP PVC를 쓰는 Pod의 선점을 지원한다 (1.29에서 RWOP와 함께 GA)
  • 우선순위 높은 Pod가 같은 PVC를 필요로 하면, 그 PVC를 쓰던 낮은 우선순위 Pod를 밀어낼 수 있다

상황별 추천 모드

상황추천 모드
일반적인 단일 서버 디스크RWO
여러 Pod가 같은 파일 읽기 (설정파일 등)ROX
여러 Pod가 공유 파일 읽기/쓰기 (NFS 등)RWX
반드시 하나의 Pod만 써야 하는 중요 데이터RWOP (주의!)

ReplicaSet vs StatefulSet의 저장소 차이

ReplicaSet (Stateless)모든 Pod가 하나의 PVC/PV 공유 (RWO면 같은 노드의 Pod끼리만)Pod1Pod2Pod3PVCrandom-generator-logPV실제 저장소"우리 셋 다 같은 창고 씀"StatefulSet (Stateful)각 Pod마다 전용 PVC/PV 보유Pod1PVC-1PV-1: 저장소Pod2PVC-2PV-2: 저장소Pod3PVC-3PV-3: 저장소"우리 셋 다 각자 창고 있음"
같은 창고 하나 vs 각자 창고 — 무상태와 상태 저장의 큰 차이

무상태 vs 상태 저장: 저장소 방식

  • ReplicaSet의 Pod들은 PVC 하나를 함께 쓴다
    • 단, 예제 PVC는 ReadWriteOnce라서 같은 노드에 뜬 Pod끼리만 공유할 수 있다
    • 여러 노드의 Pod가 함께 쓰려면 RWX를 지원하는 스토리지가 필요하다
  • 이것이 Kubernetes에서 무상태와 상태 저장 워크로드를 처리하는 방식의 가장 큰 차이점 중 하나다

7. 전체 구조 한눈에 보기

클러스터 내부Deployment관리ReplicaSet생성 · 유지Pod1Pod2Pod3label: app=random-generatorliveness probe로 컨테이너 상태 감시readiness probe 통과한 Pod에만 연결Service: clusterIP 고정selector: app=random-generatorPVC: random-generator-logRWO: 같은 노드의 Pod끼리만 공유클러스터 내부다른 PodclusterIP로 접근 가능외부 인터넷외부 인터넷 → 접근 불가
Deployment · ReplicaSet · Service · PVC 가 한 앱을 이루는 모습

추상화별 해결 문제

추상화해결하는 문제핵심 동작
Pod컨테이너가 죽으면?liveness probe → 재시작
ReplicaSetPod 수가 부족하면?자동으로 새 Pod 생성
Deployment버전 업데이트/실패하면?롤링업데이트/롤백
Service어떤 Pod가 정상인지?readiness probe → 정상만 연결
PVC데이터를 영구 보관하려면?저장소 요청 및 마운트

8. Discussion

복잡한 분산 시스템의 구성

  • 일반적으로 여러 서비스들로 구성된다
구성예특징
Stateless 서비스 (이번 챕터)API 서버, 웹 서버복제 쉬움, 빠른 확장
Stateful 서비스 (다음 챕터)DB, 메시지 브로커각자 고유 데이터 보유
단기 작업 (Job/CronJob)배치 처리, 데이터 변환실행 후 종료

무상태 서비스의 4가지 특성

  1. 동일함 (Identical): Pod1 = Pod2 = Pod3
  2. 교체 가능함 (Swappable): Pod1이 Pod2로 교체되어도 문제 없음
  3. 일시적임 (Ephemeral): 언제든 사라져도 괜찮음
  4. 대체 가능함 (Replaceable): 죽으면 새걸로 바로 대체

조합의 책임: 개발자

  • Kubernetes는 이러한 구성 요소들을 제공한다
  • 하지만 이들 사이의 직접적인 관계를 강제하지는 않는다
  • 이것들을 애플리케이션의 특성에 맞게 조합하는 것은 개발자의 책임이다

확장 프레임워크와의 결합

  • Kubernetes 기본 요소만으로 부족할 때는 Knative Serving, KEDA 같은 프레임워크와 결합할 수 있다
프레임워크역할특징
Knative Serving서버리스 워크로드 실행트래픽 없으면 Pod를 0으로 축소
KEDA이벤트 기반 자동확장메시지 큐 길이 등 외부 지표로 확장

Knative의 구성

  • Knative는 Serving(요청 기반 자동확장), Eventing(이벤트 라우팅) 등을 포함하는 서버리스 프로젝트다
  • 스케일-투-제로는 그중 Knative Serving의 역할이다

자동확장 기준 비교

구성자동확장 방식
기본 KubernetesHPA로 자동확장: 기본은 CPU/메모리, 메트릭 어댑터를 붙이면 커스텀·외부 지표도 사용 (autoscaling/v2)
KEDA 추가 시메시지 큐 길이, DB 쿼리 수 등으로도 자동확장
Knative Serving 추가 시트래픽 0 → Pod 0개로 축소 (완전한 서버리스)

관련 장

  • 이러한 프레임워크들은 29장 “탄력적 확장(Elastic Scale)“에서 다룬다

Stateless Service는 동일하고 교체 가능한 인스턴스로 구성되며, Deployment + ReplicaSet + Service + PVC의 조합으로 생성·유지·확장·업데이트·복구 전 과정을 자동화할 수 있다. 각 구성 요소는 서로를 직접 알지 못하고 label이라는 공통 언어로만 연결된다.