Kubernetes
K8s / 쿠버네티스
컨테이너 오케스트레이션 표준. Google 개발. 자동 배포, 스케일링, 복구.
K8s / 쿠버네티스
컨테이너 오케스트레이션 표준. Google 개발. 자동 배포, 스케일링, 복구.
Kubernetes(쿠버네티스, K8s)는 컨테이너화된 애플리케이션의 배포, 스케일링, 관리를 자동화하는 오픈소스 플랫폼입니다. Google이 내부에서 사용하던 Borg 시스템을 기반으로 개발했으며, 현재는 CNCF(Cloud Native Computing Foundation)에서 관리합니다. 이름은 그리스어로 '조타수'를 의미합니다.
핵심 개념으로 Pod는 가장 작은 배포 단위로, 하나 이상의 컨테이너를 포함합니다. Deployment는 Pod의 선언적 업데이트를 관리하며, 롤링 업데이트와 롤백을 지원합니다. Service는 Pod 집합에 대한 네트워크 접근을 제공하고, Ingress는 외부에서 클러스터 내 서비스로의 HTTP/HTTPS 라우팅을 정의합니다.
아키텍처는 Control Plane과 Worker Node로 구성됩니다. Control Plane에는 API Server(모든 요청의 진입점), etcd(클러스터 상태 저장), Scheduler(Pod 배치 결정), Controller Manager(상태 감시 및 조정)가 있습니다. Worker Node에는 kubelet(Pod 라이프사이클 관리), kube-proxy(네트워크 규칙 관리), Container Runtime(Docker, containerd 등)이 실행됩니다.
주요 클라우드 관리형 서비스로 AWS EKS, GCP GKE, Azure AKS가 있습니다. 관리형 서비스는 Control Plane을 자동 관리해주어 운영 부담을 줄여주고, 클라우드 서비스와의 통합(IAM, 로드밸런서, 스토리지)이 용이합니다.
# 클러스터 정보 확인
kubectl cluster-info
kubectl get nodes -o wide
# 네임스페이스 생성 및 기본 설정
kubectl create namespace production
kubectl config set-context --current --namespace=production
# Pod 생성 및 관리
kubectl run nginx --image=nginx:1.25 --port=80
kubectl get pods -o wide
kubectl describe pod nginx
kubectl logs nginx -f
kubectl exec -it nginx -- /bin/bash
# Deployment 생성 (명령형)
kubectl create deployment web --image=nginx:1.25 --replicas=3
# 스케일링
kubectl scale deployment web --replicas=5
# 롤링 업데이트
kubectl set image deployment/web nginx=nginx:1.26
kubectl rollout status deployment/web
kubectl rollout history deployment/web
# 롤백
kubectl rollout undo deployment/web
kubectl rollout undo deployment/web --to-revision=2
# 서비스 노출
kubectl expose deployment web --port=80 --type=LoadBalancer
# 리소스 모니터링
kubectl top nodes
kubectl top pods
kubectl get events --sort-by='.lastTimestamp'
# 디버깅
kubectl describe pod
kubectl logs --previous # 이전 컨테이너 로그
kubectl debug -it --image=busybox
# 리소스 삭제
kubectl delete deployment web
kubectl delete pod nginx --grace-period=0 --force
# deployment.yaml - 프로덕션 웹 애플리케이션
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
namespace: production
labels:
app: web-app
version: v1.0.0
spec:
replicas: 3
revisionHistoryLimit: 5 # 롤백용 히스토리 보관
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # 업데이트 중 추가 Pod
maxUnavailable: 0 # 항상 최소 replicas 유지
selector:
matchLabels:
app: web-app
template:
metadata:
labels:
app: web-app
version: v1.0.0
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "8080"
spec:
terminationGracePeriodSeconds: 30
serviceAccountName: web-app-sa
# 토폴로지 분산 (가용영역 분산)
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: web-app
containers:
- name: web
image: myregistry/web-app:v1.0.0
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 8080
protocol: TCP
# 환경 변수
env:
- name: NODE_ENV
value: "production"
- name: DB_HOST
valueFrom:
secretKeyRef:
name: db-credentials
key: host
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
# 리소스 제한
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "1000m"
memory: "512Mi"
# Liveness Probe - 컨테이너 재시작 결정
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 15
periodSeconds: 20
timeoutSeconds: 5
failureThreshold: 3
# Readiness Probe - 트래픽 수신 결정
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 3
successThreshold: 1
failureThreshold: 3
# Startup Probe - 시작 시간이 긴 앱용
startupProbe:
httpGet:
path: /health
port: 8080
failureThreshold: 30
periodSeconds: 10
# 볼륨 마운트
volumeMounts:
- name: config
mountPath: /app/config
readOnly: true
- name: tmp
mountPath: /tmp
volumes:
- name: config
configMap:
name: web-app-config
- name: tmp
emptyDir: {}
# 이미지 Pull Secret
imagePullSecrets:
- name: regcred
---
# HorizontalPodAutoscaler
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: web-app-hpa
namespace: production
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: web-app
minReplicas: 3
maxReplicas: 50
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
behavior:
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 10
periodSeconds: 60
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Pods
value: 4
periodSeconds: 15
# ClusterIP Service (내부 통신용)
apiVersion: v1
kind: Service
metadata:
name: web-app
namespace: production
spec:
type: ClusterIP
selector:
app: web-app
ports:
- name: http
port: 80
targetPort: 8080
protocol: TCP
---
# LoadBalancer Service (외부 노출)
apiVersion: v1
kind: Service
metadata:
name: web-app-lb
namespace: production
annotations:
# AWS NLB 설정
service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
service.beta.kubernetes.io/aws-load-balancer-scheme: "internet-facing"
spec:
type: LoadBalancer
selector:
app: web-app
ports:
- name: http
port: 80
targetPort: 8080
- name: https
port: 443
targetPort: 8080
---
# Ingress (HTTP 라우팅)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web-app-ingress
namespace: production
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
tls:
- hosts:
- api.example.com
- www.example.com
secretName: example-tls
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web-app
port:
number: 80
- host: www.example.com
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: api-backend
port:
number: 80
- path: /
pathType: Prefix
backend:
service:
name: frontend
port:
number: 80
---
# NetworkPolicy (네트워크 보안)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: web-app-policy
namespace: production
spec:
podSelector:
matchLabels:
app: web-app
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: ingress-nginx
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8080
egress:
- to:
- podSelector:
matchLabels:
app: database
ports:
- protocol: TCP
port: 5432
- to:
- namespaceSelector: {}
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
PM: "신규 프로젝트를 Kubernetes에서 운영하려고 하는데, 직접 클러스터를 구축할지 관리형 서비스를 쓸지 고민입니다."
DevOps: "프로덕션 환경이라면 관리형 서비스를 추천드립니다. AWS EKS나 GKE를 쓰면 Control Plane 고가용성, etcd 백업, 버전 업그레이드를 자동으로 처리해줍니다. 직접 구축하면 초기 비용은 절감되지만, 운영 인력과 장애 대응 부담이 큽니다. 저희 팀 규모에서는 관리형으로 시작하고, 트래픽 10만 DAU 넘어가면 멀티 클러스터 전략을 검토하는 게 좋을 것 같습니다."
면접관: "Kubernetes에서 무중단 배포를 어떻게 구현하시나요?"
지원자: "Deployment의 RollingUpdate 전략을 사용합니다. maxSurge를 1, maxUnavailable을 0으로 설정하면 항상 기존 replica 수가 유지됩니다. 핵심은 readinessProbe 설정인데요, 새 Pod가 실제로 트래픽을 처리할 준비가 됐을 때만 Service에 추가되도록 합니다. 또한 preStop hook에 sleep을 넣어 기존 연결이 정리될 시간을 주고, terminationGracePeriodSeconds를 충분히 설정합니다. Blue-Green이 필요하면 Argo Rollouts를 사용하기도 합니다."
개발자: "Pod가 계속 CrashLoopBackOff 상태로 재시작돼요. 무슨 문제일까요?"
SRE: "먼저 kubectl describe pod로 Events를 확인하세요. OOMKilled면 메모리 limit 부족이고, livenessProbe 실패면 앱 상태 확인이 필요해요. kubectl logs --previous로 이전 컨테이너 로그 보시고, 시작 시간이 오래 걸리는 앱이면 startupProbe를 추가하거나 initialDelaySeconds를 늘려보세요. 최근 배포 후 발생했다면 kubectl rollout undo로 롤백하고 원인 분석하는 게 빠릅니다."
resources.requests와 limits를 설정하지 않으면 노드 리소스를 과다 사용하거나, 스케줄링 실패가 발생합니다. 특히 메모리 limit 없이 운영하면 OOM으로 다른 Pod까지 영향받을 수 있습니다. LimitRange와 ResourceQuota로 네임스페이스별 기본값을 설정하세요.
이미지 태그에 :latest를 사용하면 어떤 버전이 배포됐는지 추적이 불가능합니다. 롤백도 어렵고, imagePullPolicy: Always와 함께 사용하면 동일 Deployment에서 다른 버전의 Pod가 실행될 수 있습니다. 반드시 고정 버전 태그(v1.2.3 또는 git commit SHA)를 사용하세요.
노드 업그레이드나 스케일 다운 시 PDB(PodDisruptionBudget)를 설정하면 최소 가용 Pod 수를 보장합니다. minAvailable이나 maxUnavailable을 설정해 클러스터 유지보수 중에도 서비스가 중단되지 않도록 하세요.