카테고리 없음

[유레카 / 백엔드] TIL - 27 (Kubernetes)

coding-quokka101 2026. 3. 26. 15:29

 

📌 들어가며

오늘의 학습 목표:

컨테이너 오케스트레이션의 핵심, Kubernetes를 이해하고 실무에서 어떻게 활용되는지 감 잡기

 

안녕하세요! 오늘은 요즘 백엔드 개발자라면 피할 수 없는 키워드, Kubernetes(쿠버네티스) 를 본격적으로 파고들었습니다.

솔직히 처음엔 "Docker로 컨테이너 띄우면 되는 거 아닌가? 왜 또 다른 게 필요하지?" 싶었는데,

배우고 나니 운영 환경에서 Docker만으로는 한계가 있다는 걸 확실히 깨달았어요 😅

 

특히 컨테이너가 수십, 수백 개로 늘어났을 때 이걸 어떻게 관리하는지, 서버가 죽었을 때 자동으로 복구되는 원리가 뭔지 이해하고 나니 "아, 이래서 쿠버네티스가 필요하구나!" 하고 무릎을 탁 치게 됐습니다. 🙌


🎯 Today I Learned

✅ Kubernetes가 왜 필요한가? (Docker와의 차이)
✅ Kubernetes의 핵심 아키텍처 (Control Plane & Node)
✅ Pod, Deployment, Service의 개념과 관계
✅ ReplicaSet으로 가용성 보장하기
✅ ConfigMap & Secret으로 설정 분리하기
✅ Namespace로 리소스 격리하기
✅ kubectl 기본 명령어 실습
✅ 실무에서의 Kubernetes 활용 패턴

🤔 Kubernetes, 왜 필요할까?

https://th.bing.com/th/id/OIP.lRBrT9UGZg54sH1GE_LCcwHaE8?w=240&h=180&c=7&r=0&o=7&pid=1.7&rm=3

Docker만으로는 부족한 이유

Docker를 배울 때는 "컨테이너 하나 띄우고, 필요하면 더 띄우면 되지 않나?" 싶었어요. 그런데 실제 운영 환경에서는 이런 문제들이 생깁니다:

❌ 컨테이너가 갑자기 죽으면? → 직접 다시 띄워야 함
❌ 트래픽이 폭발적으로 늘어나면? → 수동으로 컨테이너 추가
❌ 서버가 3대 있는데 어떻게 분산? → 직접 배분
❌ 배포 중에 서비스 중단 없이 업데이트하려면? → 매우 복잡

수십 개 넘어가는 컨테이너를 사람이 직접 하나하나 관리한다는 건 현실적으로 불가능에 가깝습니다. Kubernetes는 이 문제를 자동화로 해결해줍니다.

Kubernetes의 핵심 가치

Kubernetes = 컨테이너 오케스트레이션 플랫폼

✅ Self-healing: 컨테이너가 죽으면 자동으로 재시작
✅ Auto Scaling: 트래픽에 따라 컨테이너 수 자동 조정
✅ Load Balancing: 여러 컨테이너에 트래픽 자동 분산
✅ Rolling Update: 서비스 중단 없이 무중단 배포
✅ Rollback: 배포 실패 시 이전 버전으로 자동 복구


🏗️ Kubernetes 아키텍처

Kubernetes 클러스터는 크게 Control Plane(컨트롤 플레인)Worker Node(워커 노드) 로 나뉩니다.

Control Plane - 두뇌 역할

Control Plane
├── API Server        → 모든 통신의 중심 (REST API)
├── etcd             → 클러스터 상태 저장소 (Key-Value DB)
├── Scheduler        → Pod를 어떤 노드에 배치할지 결정
└── Controller Manager → 원하는 상태 유지 담당

API Server가 특히 중요한데, kubectl 명령어를 치면 결국 API Server에 요청이 가는 거예요. 쿠버네티스의 모든 작업은 API Server를 통해 이루어집니다.

etcd는 클러스터의 모든 상태 정보를 저장하는 분산 저장소인데, "현재 Pod가 몇 개인지", "어떤 노드가 살아있는지" 같은 정보가 다 여기 담겨 있어요.

Worker Node - 실제 일꾼

Worker Node
├── kubelet    → Control Plane의 명령을 받아 Pod 관리
├── kube-proxy → 네트워크 룰 관리, 트래픽 라우팅
└── Container Runtime (Docker 등) → 실제 컨테이너 실행

Worker Node는 실제로 컨테이너가 실행되는 서버예요. kubelet이 Control Plane에서 오는 명령을 받아서 "이 컨테이너 띄워라", "저 컨테이너 삭제해라" 같은 작업을 실행합니다.


📦 핵심 오브젝트 이해하기

1️⃣ Pod - 가장 작은 배포 단위

Pod: 하나 이상의 컨테이너를 감싸는 Kubernetes의 최소 배포 단위

# pod.yaml 예시
apiVersion: v1
kind: Pod
metadata:
  name: my-app-pod
  labels:
    app: my-app
spec:
  containers:
    - name: my-app
      image: your-username/my-app:latest
      ports:
        - containerPort: 8080
      env:
        - name: SPRING_PROFILES_ACTIVE
          value: "prod"

Pod 안에 여러 컨테이너를 넣을 수 있는데, 같은 Pod 안의 컨테이너들은 localhost로 통신하고 같은 스토리지를 공유합니다. 보통 메인 앱 컨테이너 + 로그 수집 사이드카 컨테이너 이런 식으로 묶어요.

중요한 점은, Pod는 일회성이에요. 죽으면 그냥 사라지고 새로운 Pod가 생겨요. 그래서 Pod를 직접 쓰는 경우는 거의 없고, Deployment로 관리합니다.

2️⃣ Deployment - Pod의 관리자

Deployment: Pod를 선언적으로 관리하는 오브젝트. 몇 개의 Pod를 유지할지, 어떻게 업데이트할지 정의합니다.

# deployment.yaml 예시
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app-deployment
spec:
  replicas: 3          # Pod 3개 유지
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
        - name: my-app
          image: your-username/my-app:v2.0
          ports:
            - containerPort: 8080
  strategy:
    type: RollingUpdate   # 무중단 배포 전략
    rollingUpdate:
      maxSurge: 1         # 동시에 최대 1개 추가 생성
      maxUnavailable: 0   # 다운되는 Pod 0개 유지

replicas: 3으로 설정하면 Kubernetes가 항상 Pod 3개를 유지합니다. 하나가 죽으면 자동으로 새 Pod를 띄워줘요. 이게 Self-healing의 핵심이에요!

3️⃣ ReplicaSet - 복제본 보장

Deployment를 만들면 내부적으로 ReplicaSet이 자동으로 생성됩니다. ReplicaSet이 실제로 Pod 개수를 유지하는 역할을 해요.

Deployment (버전 관리, 업데이트 전략)
  └── ReplicaSet (Pod 개수 유지)
        ├── Pod 1
        ├── Pod 2
        └── Pod 3

직접 ReplicaSet을 만들기보다는 Deployment를 통해 간접적으로 사용하는 게 일반적이에요.

4️⃣ Service - Pod에 접근하는 방법

Pod는 생성될 때마다 IP가 바뀝니다. 그래서 Pod IP로 직접 통신하면 안 되고, Service라는 고정 엔드포인트를 거쳐야 해요.

# service.yaml 예시
apiVersion: v1
kind: Service
metadata:
  name: my-app-service
spec:
  selector:
    app: my-app       # 이 라벨을 가진 Pod에 연결
  ports:
    - protocol: TCP
      port: 80        # 서비스 포트
      targetPort: 8080 # Pod의 포트
  type: ClusterIP     # 클러스터 내부에서만 접근

Service 타입은 세 가지가 있어요:

타입 설명 사용 시점

ClusterIP 클러스터 내부 전용 내부 서비스 간 통신
NodePort 노드 IP + 포트로 외부 접근 테스트, 개발 환경
LoadBalancer 클라우드 로드밸런서 연동 실제 운영 환경

⚙️ ConfigMap & Secret - 설정 분리하기

하드코딩은 절대 금물! 환경별 설정값과 민감한 정보는 코드 밖으로 빼야 합니다.

ConfigMap - 일반 설정값

# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  APP_ENV: "production"
  MAX_POOL_SIZE: "10"
  LOG_LEVEL: "INFO"

Secret - 민감한 정보

# secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: app-secret
type: Opaque
data:
  DB_PASSWORD: base64로_인코딩된_값
  JWT_SECRET: base64로_인코딩된_값

Secret의 값은 Base64로 인코딩해서 저장해요. (암호화는 아니에요! 별도 암호화 솔루션이 필요합니다)

Pod에 주입하기

spec:
  containers:
    - name: my-app
      image: your-username/my-app:latest
      envFrom:
        - configMapRef:
            name: app-config     # ConfigMap 전체 주입
        - secretRef:
            name: app-secret     # Secret 전체 주입

🗂️ Namespace - 논리적 격리

# namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: dev
---
apiVersion: v1
kind: Namespace
metadata:
  name: prod

Namespace는 하나의 클러스터를 논리적으로 나누는 개념이에요. 개발/스테이징/운영 환경을 같은 클러스터 안에서 격리해서 관리할 수 있습니다.

# 네임스페이스별로 리소스 조회
kubectl get pods -n dev
kubectl get pods -n prod

# 특정 네임스페이스에 Deployment 배포
kubectl apply -f deployment.yaml -n prod

💻 kubectl 기본 명령어 실습

https://hestia.ghost.io/content/images/2024/01/image-39.png
https://yunyun3599.github.io/assets/img/2023/02/2023-02-25-kubernetes-kubectl_commands/kubectl_api-resources.png

# 클러스터 정보 확인
kubectl cluster-info
kubectl get nodes

# Pod 관련
kubectl get pods                          # Pod 목록
kubectl get pods -o wide                  # IP, 노드 정보 포함
kubectl describe pod my-app-pod           # 상세 정보
kubectl logs my-app-pod                   # 로그 확인
kubectl logs -f my-app-pod                # 실시간 로그
kubectl exec -it my-app-pod -- /bin/bash  # 컨테이너 접속

# Deployment 관련
kubectl apply -f deployment.yaml          # 배포 적용
kubectl get deployments                   # Deployment 목록
kubectl rollout status deployment/my-app  # 배포 상태 확인
kubectl rollout undo deployment/my-app    # 이전 버전 롤백

# 스케일링
kubectl scale deployment my-app --replicas=5  # Pod 5개로 확장

# 삭제
kubectl delete pod my-app-pod
kubectl delete -f deployment.yaml

처음에 명령어가 너무 많아서 외울 엄두가 안 났는데, kubectl get, kubectl describe, kubectl apply, kubectl delete 이 네 가지를 중심으로 익히니까 나머지는 자연스럽게 따라오더라고요!


💡 오늘 나는 무엇을 배웠는가

1. "선언적 관리"가 핵심 철학이다

명령형 방식 (어떻게 할지 지시):

# "컨테이너 3개 실행해라, 포트 8080 열어라, ..."
docker run -d -p 8080:8080 my-app
docker run -d -p 8081:8080 my-app
docker run -d -p 8082:8080 my-app

선언적 방식 (원하는 상태 선언):

# "이런 상태가 되면 돼, 어떻게 할지는 네가 알아서"
replicas: 3
image: my-app:latest

Kubernetes는 "현재 상태"를 "원하는 상태"로 계속 맞춰가는 게 핵심 원리입니다. 이 개념이 처음엔 낯설었는데, 이해하고 나니 왜 이렇게 설계됐는지 납득이 됐어요.

2. Label과 Selector가 모든 걸 연결한다

Deployment (selector: app=my-app)
    ↓ 연결
ReplicaSet (selector: app=my-app)
    ↓ 연결
Pod (label: app=my-app)
    ↑ 연결
Service (selector: app=my-app)

Label과 Selector가 쿠버네티스 오브젝트들을 서로 연결하는 접착제 역할을 합니다. 처음엔 왜 이렇게 복잡하게 하나 싶었는데, 유연한 구성이 가능해진다는 장점이 있더라고요.

3. Pod는 언제든 사라질 수 있다

Pod 는 가축(Cattle)이다, 애완동물(Pet)이 아니다.

Pet: 이름 있고, 죽으면 슬프고, 살려야 함
Cattle: 죽으면 새 걸로 교체

→ Pod는 죽으면 그냥 새로 교체하도록 설계됩니다
→ Pod에 데이터 저장? 절대 금물! (Persistent Volume 사용)

😊 좋았던 점 & 😅 아쉬웠던 점

좋았던 점

👍 직접 클러스터 구성해보기

Minikube로 로컬에 간이 클러스터 띄우고, YAML 파일 작성해서 실제 배포까지 해보니 개념이 훨씬 빠르게 잡혔어요. "아 이래서 yaml이 중요한 거구나" 싶었습니다.

👍 Self-healing 직접 확인

Pod를 강제로 삭제(kubectl delete pod)해봤는데, 몇 초 만에 새 Pod가 자동 생성되는 걸 보고 진짜 신기했어요 😮

👍 Docker와 연결되는 느낌

Docker를 먼저 배운 게 Kubernetes 이해에 많은 도움이 됐어요. 기반 기술이 Docker라는 걸 알고 나니 전체 그림이 그려졌습니다.

아쉬웠던 점

😢 YAML 파일이 너무 많고 복잡해요

Pod, Deployment, Service, ConfigMap, Secret... YAML 파일이 여러 개가 되니까 관리가 벌써부터 걱정돼요. Helm Chart 같은 걸 배워야 할 것 같아요.

😢 네트워킹이 여전히 어렵습니다

CNI, Ingress, NetworkPolicy 같은 네트워크 관련 개념은 아직 머릿속에서 정리가 안 돼요. 더 파봐야 할 것 같습니다.

😢 운영 환경과 로컬 환경의 차이

Minikube로 연습하는 것과 실제 클라우드(EKS, GKE)에서 쓰는 건 또 다르다고 해서, 실습이 좀 더 필요하겠다 싶었어요.


🤔 어려웠던 점과 해결

문제 1: Pod가 계속 Pending 상태

$ kubectl get pods
NAME                   READY   STATUS    RESTARTS
my-app-5d7f8c-xk9p2   0/1     Pending   0

원인을 몰라서 당황했는데, kubectl describe pod로 Events를 확인해보니 노드 리소스 부족이 원인이었어요.

kubectl describe pod my-app-5d7f8c-xk9p2

# Events 에서 확인:
# Insufficient memory: 0/1 nodes are available

해결: resources.requests를 낮춰서 해결했어요.

resources:
  requests:
    memory: "256Mi"  # 512Mi → 256Mi로 줄임
    cpu: "250m"
  limits:
    memory: "512Mi"
    cpu: "500m"

문제 2: Service에서 Pod로 연결이 안 됨

Service를 만들었는데 Pod에 접근이 안 됐어요. 알고 보니 selector 라벨이 Pod의 label과 정확히 일치하지 않아서 생긴 문제였습니다.

# ❌ 이렇게 하면 연결 안 됨
Service selector: app: my-app
Pod label:        app: myapp   # 하이픈 차이!

# ✅ 이렇게 정확히 맞춰야 함
Service selector: app: my-app
Pod label:        app: my-app

라벨은 오타 하나에도 아무것도 연결이 안 되니까 정말 꼼꼼하게 확인해야 해요!


🚀 다음 학습 목표

📌 단기 목표 (이번 주)
   ✓ Ingress 컨트롤러로 외부 트래픽 라우팅 이해하기
   ✓ Persistent Volume으로 데이터 영속성 적용
   ✓ HPA (Horizontal Pod Autoscaler) 실습

📌 중기 목표 (이번 달)
   ✓ Helm Chart로 복잡한 배포 관리
   ✓ GitHub Actions + Kubernetes 연동 (CI/CD 파이프라인 완성)
   ✓ AWS EKS 기초 실습

📌 장기 목표 (부트캠프 기간)
   ✓ 포트폴리오 프로젝트에 Kubernetes 배포 환경 구축
   ✓ 모니터링 (Prometheus + Grafana) 연동
   ✓ 이전에 배운 Docker Compose → Kubernetes 마이그레이션 경험 쌓기

🎬 마치며

오늘 Kubernetes를 배우기 전까지는 "Docker 잘 쓰면 충분하지 않나?"라는 생각이 있었는데, 완전히 바뀌었습니다.

Docker가 컨테이너 한 칸을 만드는 기술이라면, Kubernetes는 그 컨테이너들이 살아있는 건물 전체를 관리하는 기술이라는 느낌이에요. 스케일이 다르더라고요.

"원하는 상태를 선언하라, 그러면 쿠버네티스가 알아서 맞춰준다."

Self-healing, 무중단 배포, 자동 스케일링... 이 모든 게 YAML 파일 몇 줄로 가능하다는 게 아직도 신기합니다. 아직 네트워킹이나 스토리지 부분은 더 공부가 필요하지만, 기초 흐름을 잡았으니 하나씩 채워나가겠습니다! 💪

다음 주에는 Ingress와 Helm Chart에 도전해볼 예정이에요. 다들 화이팅! 🚀


📚 참고 자료

공식 문서

기술 블로그 & 아티클

도서

  • 『쿠버네티스 인 액션』 - 마르코 룩샤 저: 이론과 실습 균형이 좋은 바이블급 책
  • 『컨테이너 인프라 환경 구축을 위한 쿠버네티스/도커』 - 조훈 외: 국내 저자, 한국 환경 실습 중심

실습 환경