01. Kubernetes 기본 개념

정세현·2026년 7월 22일

NHN AX Internship Notes

목록 보기
6/16

0. 한 문장 정의

컨테이너화된 워크로드와 서비스를 여러 서버에 걸쳐 자동으로 배치·복구·확장·노출해주는 오픈소스 오케스트레이션 플랫폼

Docker가 "컨테이너 하나를 만들고 실행"하는 도구라면,
Kubernetes는 "컨테이너 수백 개를 서버 수십 대 위에서 관리"하는 도구다.


1. 6대 핵심 기능 — 각각 어떤 고통을 해결하나

1-1. 서비스 디스커버리와 로드 밸런싱

문제
파드는 죽고 다시 뜨는 게 정상이다. 그때마다 IP가 바뀐다.
order-servicepayment-service를 부르려는데 IP를 어떻게 알까? 하드코딩하면 다음 배포에 바로 깨진다.

해결

  • Service 오브젝트가 파드 앞에 고정된 가상 IP(ClusterIP)와 DNS 이름을 제공
  • 뒤에 붙은 파드가 몇 개든, 어떤 IP든 Service가 알아서 라우팅 + 부하 분산
payment-service.default.svc.cluster.local
        │
        ├──► Pod A (10.0.1.5)
        ├──► Pod B (10.0.2.9)
        └──► Pod C (10.0.1.7)

Spring Boot 관점

# application.yml — IP가 아니라 서비스 이름으로 호출
payment:
  base-url: http://payment-service:8080

Eureka 같은 별도 서비스 디스커버리 서버 없이도 이게 된다. K8s가 DNS 레벨에서 해결해주기 때문.

Service 타입 4가지

타입용도
ClusterIP (기본)클러스터 내부에서만 접근. 내부 MSA 통신용
NodePort노드의 특정 포트(30000~32767)로 외부 노출. 테스트용
LoadBalancer클라우드 LB를 자동 생성해 외부 노출. NKS에서 실서비스 표준
ExternalName외부 도메인으로 CNAME 매핑

Ingress: L7(HTTP) 라우팅. 경로/호스트 기반으로 여러 Service를 하나의 LB 뒤에 묶는다.
api.example.com/orders → order-service, api.example.com/pay → payment-service


1-2. 스토리지 오케스트레이션

문제
컨테이너는 무상태(stateless)가 원칙이지만, DB·파일 업로드·로그처럼 살아남아야 하는 데이터가 있다.
파드가 다른 노드로 옮겨가면 로컬 디스크 데이터는 사라진다.

해결

  • PV (PersistentVolume): 실제 스토리지 조각. 관리자(또는 StorageClass)가 준비
  • PVC (PersistentVolumeClaim): "10Gi짜리 읽기/쓰기 볼륨 주세요"라는 개발자의 요청서
  • StorageClass: PVC가 오면 PV를 자동으로 만들어주는 프로비저너
Pod ──uses──► PVC ──bound──► PV ──backed by──► NHN Cloud Block Storage
                 ▲
            StorageClass가 동적 생성

접근 모드

모드의미블록 스토리지NAS
ReadWriteOnce (RWO)노드 1개에서만 읽기/쓰기oo
ReadOnlyMany (ROX)여러 노드에서 읽기만xo
ReadWriteMany (RWX)여러 노드에서 읽기/쓰기xo

파드 여러 개가 같은 파일을 공유해야 하면 블록 스토리지(RWO)로는 안 된다. NAS 계열이 필요.

주의
프로덕션 DB를 K8s 안에 넣는 건 신중해야 한다. 대부분은 관리형 DB(RDS 등)를 클러스터 밖에 두고 K8s에는 stateless 앱만 올리는 게 안전하다.


1-3. 자동화된 롤아웃과 롤백

문제
docker compose down && up 하면 그 사이 서비스가 죽는다. 새 버전에 버그가 있으면 되돌리는 것도 수작업.

해결 — Rolling Update
Deployment가 파드를 한 번에 다 바꾸지 않고 조금씩 교체한다.

v1 v1 v1 v1     (시작)
v1 v1 v1 v2     (1개 교체 → readinessProbe 통과 대기)
v1 v1 v2 v2
v1 v2 v2 v2
v2 v2 v2 v2     (완료, 다운타임 0)
strategy:
  type: RollingUpdate
  rollingUpdate:
    maxSurge: 1        # 목표 개수보다 최대 몇 개 더 띄울지
    maxUnavailable: 0  # 동시에 몇 개까지 죽어도 되는지 (0이면 무중단 보장)

롤백
Deployment는 이전 ReplicaSet을 히스토리로 보관한다.

kubectl rollout history deployment/my-api
kubectl rollout undo deployment/my-api              # 직전 버전으로
kubectl rollout undo deployment/my-api --to-revision=3
kubectl rollout status deployment/my-api           # 진행 상황 모니터링

핵심 전제 — Probe 설정이 필수
readinessProbe가 없으면 K8s는 "컨테이너 프로세스가 떴다 = 준비 완료"로 착각하고 다음 파드를 교체한다. Spring Boot는 기동에 20~60초가 걸리므로, probe 없이 롤링 업데이트하면 트래픽이 아직 준비 안 된 파드로 흘러가 502가 난다.

readinessProbe:                    # 트래픽 받을 준비가 됐나? (실패 시 Service에서 제외)
  httpGet: { path: /actuator/health/readiness, port: 8080 }
  initialDelaySeconds: 20
  periodSeconds: 5
livenessProbe:                     # 살아있나? (실패 시 컨테이너 재시작)
  httpGet: { path: /actuator/health/liveness, port: 8080 }
  initialDelaySeconds: 60
  periodSeconds: 10
startupProbe:                      # 기동 중인가? (통과 전까지 liveness 유예)
  httpGet: { path: /actuator/health, port: 8080 }
  failureThreshold: 30
  periodSeconds: 5

Spring Boot Actuator는 management.endpoint.health.probes.enabled=true 설정 시
/actuator/health/liveness, /actuator/health/readiness를 제공한다. K8s와 궁합이 좋다.


1-4. 자동화된 빈 패킹 (Bin Packing)

문제
노드가 5대 있고 파드를 20개 띄워야 한다. 어느 노드에 무엇을 넣어야 자원이 안 남고 안 터질까?

해결
스케줄러가 각 파드의 resources.requests를 보고 남은 자원에 가장 잘 맞는 노드를 골라 배치한다. (상자에 짐을 빈틈없이 채우는 bin packing 문제에서 유래)

resources:
  requests:          # 스케줄링 기준. "최소 이만큼은 보장해줘"
    cpu: "500m"      # 0.5 코어
    memory: "512Mi"
  limits:            # 상한. 넘으면 CPU는 스로틀링, 메모리는 OOMKilled
    cpu: "1000m"
    memory: "1Gi"

꼭 알아야 할 함정

  1. requests를 안 적으면 스케줄러가 판단할 근거가 없다. 한 노드에 파드가 몰려 다 같이 죽는다.
  2. 메모리 limits 초과 = 즉시 OOMKilled. CPU와 달리 유예가 없다.
  3. JVM은 컨테이너 메모리를 자동으로 못 본다 (구버전). 최신 JDK는 UseContainerSupport가 기본이지만, 명시하는 게 안전:
    JAVA_OPTS=-XX:MaxRAMPercentage=75.0
    limits가 1Gi면 힙을 768Mi 정도로 잡아, 나머지는 메타스페이스·스레드 스택용으로 남긴다.

QoS 클래스 (자원 부족 시 축출 우선순위)

클래스조건축출 순서
Guaranteedrequests == limits (전 컨테이너)마지막
Burstablerequests < limits중간
BestEffort아무것도 안 적음첫 번째로 죽음

1-5. 자동화된 복구 (Self-healing)

문제
새벽 3시에 앱이 죽었다. 알람 받고 일어나서 SSH 접속해서 재시작?

해결 — 선언적 상태 유지 (Reconciliation Loop)
사용자가 "replicas: 3"이라고 원하는 상태(desired state)를 선언하면, 컨트롤러가 실제 상태(current state)를 계속 감시하며 차이를 메운다.

원하는 상태: 3개
실제 상태:   2개  ← 파드 하나 죽음
   ↓
컨트롤러가 차이를 감지 → 새 파드 1개 생성 → 3개 복구

복구되는 레벨 3가지

레벨감지 방법동작
컨테이너프로세스 종료 / livenessProbe 실패컨테이너 재시작 (restartPolicy)
파드파드 삭제·축출ReplicaSet이 새 파드 생성
노드노드 NotReady (기본 5분)다른 정상 노드로 파드 재배치

주의
Self-healing은 일시적 장애를 고치지 재현되는 버그는 못 고친다.
CrashLoopBackOff는 "재시작 → 즉시 죽음"이 반복되는 상태로, 재시작 간격이 점점 늘어난다(10s → 20s → 40s ... 최대 5분). 이걸 보면 자동 복구를 기다릴 게 아니라 로그를 봐야 한다.

kubectl logs <pod> --previous   # 죽기 직전 로그
kubectl describe pod <pod>      # 이벤트 확인

1-6. 시크릿과 구성 관리

문제
DB 비밀번호를 application.yml에 적어 이미지에 굽는다 → 이미지 유출 = 계정 유출.
게다가 dev/staging/prod마다 값이 다른데 이미지를 3개 빌드해야 하나?

해결
설정을 이미지 밖으로 빼서 런타임에 주입한다.

용도저장 방식
ConfigMap비민감 설정 (프로파일, 로그 레벨, 외부 API URL)평문
Secret민감 정보 (DB 비번, JWT 키, API 토큰)base64 인코딩
# ConfigMap
apiVersion: v1
kind: ConfigMap
metadata: { name: app-config }
data:
  SPRING_PROFILES_ACTIVE: "prod"
  LOGGING_LEVEL_ROOT: "INFO"
---
# Secret
apiVersion: v1
kind: Secret
metadata: { name: app-secret }
type: Opaque
stringData:                        # stringData면 평문으로 써도 알아서 인코딩
  SPRING_DATASOURCE_PASSWORD: "s3cr3t!"

주입 방법 2가지

# (a) 환경 변수로 — Spring Boot는 SPRING_DATASOURCE_PASSWORD 를 자동 인식
envFrom:
  - configMapRef: { name: app-config }
  - secretRef:    { name: app-secret }

# (b) 파일로 마운트 — application.yml 통째로 넣거나 인증서 파일
volumeMounts:
  - name: config
    mountPath: /config
volumes:
  - name: config
    configMap: { name: app-config }

Secret은 암호화가 아니다. base64는 인코딩일 뿐 base64 -d로 즉시 풀린다.
실무에서는 (1) etcd 저장 시 암호화 활성화, (2) RBAC로 Secret 조회 권한 제한,
(3) 외부 Vault / Sealed Secrets / External Secrets Operator 사용을 병행한다.
Secret YAML을 Git에 커밋하면 안 된다.


2. 클러스터 구조

┌──────────────── Control Plane (제어 영역) ────────────────┐
│  kube-apiserver     모든 요청의 관문. 인증/인가/검증        │
│  etcd               클러스터의 모든 상태를 담은 K-V 저장소  │
│  kube-scheduler     새 파드를 어느 노드에 놓을지 결정       │
│  controller-manager Reconciliation Loop를 돌리는 컨트롤러들 │
│  cloud-controller   클라우드 LB/볼륨 등 연동               │
└──────────────────────────────────────────────────────────┘
                          ▲  ▼  (통신)
┌──────────────── Worker Node (여러 대) ───────────────────┐
│  kubelet            apiserver 명령을 받아 컨테이너 실행    │
│  kube-proxy         Service 가상 IP → 실제 파드 네트워크 규칙│
│  Container Runtime  containerd 등 실제 컨테이너 실행 엔진   │
│                                                          │
│  [Pod] [Pod] [Pod]  ← 사용자 애플리케이션                  │
└──────────────────────────────────────────────────────────┘
  • 컨트롤 플레인: 클러스터의 두뇌. 스케줄링, 스케일링, 업데이트 등 모든 활동을 관리. 고가용성을 위해 보통 3대 이상으로 구성
  • 노드: 실제 앱이 돌아가는 워커 머신. 컨트롤 플레인과 연결되어야만 동작하고, 명령을 받아 그대로 수행

NKS의 핵심 가치: 위 컨트롤 플레인을 NHN Cloud가 대신 관리해준다. etcd 백업, apiserver HA, 버전 업그레이드 같은 난이도 높은 운영을 안 해도 된다. 사용자는 노드, 서비스, 파드만 신경 쓰면 된다.


3. 반드시 알아야 할 오브젝트

계층 구조

Deployment          "이 앱을 3개 유지해줘"
   └─ ReplicaSet    "파드 개수를 세고 맞춰라" (버전마다 하나씩 생김)
        └─ Pod      "컨테이너 1개 이상을 묶은 최소 배포 단위"
             └─ Container

워크로드 종류

오브젝트언제 쓰나예시
Deploymentstateless 앱. 대부분의 경우API 서버, 웹
StatefulSet고유 ID·순서·전용 볼륨이 필요한 앱Kafka, Redis, MySQL
DaemonSet모든 노드에 하나씩로그 수집기, 모니터링 에이전트
Job한 번 실행하고 끝DB 마이그레이션, 배치
CronJob주기적 실행매일 새벽 정산

Pod에 대한 오해 정정

  • Pod ≠ 컨테이너 1개. Pod 안에 여러 컨테이너가 들어갈 수 있고, 그들은 네트워크(localhost)와 볼륨을 공유한다
  • 대표 패턴: 사이드카 — 메인 앱 옆에 로그 수집기나 프록시를 붙임
  • Pod는 일회용(ephemeral)이다. 이름·IP가 유지된다고 가정하면 안 된다

네임스페이스

논리적 격리 단위. dev, staging, prod를 한 클러스터 안에서 분리하거나 팀별로 나눈다.
같은 네임스페이스 안에서는 Service를 짧은 이름(payment-service)으로 부를 수 있고,
다른 네임스페이스는 payment-service.other-ns처럼 붙여야 한다.


4. 오토스케일링 3종

이름무엇을 늘리나
HPA (Horizontal Pod Autoscaler)파드 개수 — CPU/메모리/커스텀 메트릭 기준
VPA (Vertical Pod Autoscaler)파드 하나의 requests/limits 크기
Cluster Autoscaler노드 개수 — 파드가 Pending이면 노드 추가
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata: { name: my-api-hpa }
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: my-api
  minReplicas: 2
  maxReplicas: 10
  metrics:
    - type: Resource
      resource:
        name: cpu
        target: { type: Utilization, averageUtilization: 70 }

HPA는 requests 대비 사용률로 계산한다. requests를 안 적으면 HPA도 동작하지 않는다.
HPA(파드 증가) + Cluster Autoscaler(노드 증가)를 같이 써야 실제로 확장된다.


5. 선언형(Declarative) 사고방식

쿠버네티스를 이해하는 가장 중요한 관점.

명령형 (Imperative)선언형 (Declarative)
"컨테이너를 켜라, 꺼라""최종 상태는 이래야 한다"
docker run ...kubectl apply -f deploy.yaml
실패하면 사람이 개입시스템이 알아서 수렴

이 사고방식이 GitOps(ArgoCD 등)로 이어진다.
Git 저장소의 YAML = 원하는 상태, 클러스터는 그걸 계속 따라간다.


6. 학습 체크리스트

  • Pod / ReplicaSet / Deployment의 관계를 그림으로 그릴 수 있다
  • ClusterIP와 LoadBalancer의 차이를 설명할 수 있다
  • readinessProbe가 없으면 무슨 일이 생기는지 안다
  • requests와 limits의 차이, OOMKilled 조건을 안다
  • ConfigMap과 Secret을 Spring Boot에 주입해봤다
  • PVC를 만들어 파드에 마운트해봤다
  • kubectl rollout undo로 롤백해봤다
  • CrashLoopBackOff를 만나 로그로 원인을 찾아봤다
profile
I'm the best

0개의 댓글