Ver3.0 Docker & Kubernetes로 시작하는 컨테이너 오케스트레이션과 DevOps 자동화 완벽 가이드

Docker & Kubernetes로 시작하는 컨테이너 오케스트레이션과 DevOps 자동화 완벽 가이드
🎯 왜 지금 Docker와 Kubernetes를 배워야 할까?
요즘 개발자 채용공고 보면 진짜 Docker, Kubernetes 안 나오는 곳이 없더라고요 ㅋㅋㅋ
예전에는 "서버 개발자만 알면 되는 거 아냐?"라고 생각했는데, 이제는 프론트엔드 개발자도 컨테이너 기술을 이해해야 하는 시대가 왔어요.
실제로 2024년 기준으로 전 세계 기업의 약 85% 이상이 컨테이너 기술을 프로덕션 환경에서 사용하고 있다는 통계가 있습니다.
특히 스타트업부터 대기업까지, 클라우드 네이티브 환경으로 전환하면서 Docker와 Kubernetes는 선택이 아닌 필수가 되었죠.
"내 컴퓨터에서는 잘 되는데요?" 이 말 진짜 개발자들 사이에서 밈이 됐잖아요 ㅋㅋㅋ
컨테이너는 바로 이런 환경 차이 문제를 근본적으로 해결해줍니다.
개발 환경, 테스트 환경, 운영 환경이 완전히 동일하게 유지되니까 배포할 때 생기는 예상치 못한 오류가 확 줄어들어요.
🐳 Docker 기초부터 제대로 이해하기
Docker가 뭔데 이렇게 난리야?
Docker는 2013년에 등장한 오픈소스 컨테이너 플랫폼인데요, 솔직히 처음 나왔을 때는 "또 새로운 기술이네" 정도로 생각했던 사람들이 많았어요.
근데 지금은 완전히 업계 표준이 되어버렸죠 ㅋㅋㅋ
Docker의 핵심 개념은 애플리케이션과 그 실행 환경을 하나의 패키지로 묶는 것입니다.
이게 왜 혁명적이냐면, 예전에는 서버에 애플리케이션을 배포하려면 운영체제 설정부터 시작해서 필요한 라이브러리, 의존성 패키지들을 일일이 설치해야 했거든요.
많은 분들이 헷갈려하시는 부분인데, 컨테이너와 가상머신은 완전히 다른 개념이에요.
가상머신 (VM):
• 하드웨어를 가상화
• 각 VM마다 완전한 OS가 필요
• 무겁고 느림 (부팅에 몇 분 소요)
• 리소스 오버헤드가 큼
컨테이너:
• OS 레벨에서 가상화
• 호스트 OS의 커널을 공유
• 가볍고 빠름 (부팅에 몇 초)
• 리소스 효율적
실제로 같은 서버에서 VM은 10개 정도 돌릴 수 있다면, 컨테이너는 수백 개도 가능해요!
Docker의 핵심 구성 요소들
Docker를 제대로 이해하려면 세 가지 핵심 개념을 알아야 해요.
1. Docker Image (이미지) 📦
이미지는 컨테이너를 만들기 위한 템플릿이에요. 쉽게 말하면 "붕어빵 틀"이라고 생각하면 됩니다.
이미지 안에는 애플리케이션 코드, 런타임, 시스템 도구, 라이브러리 등 실행에 필요한 모든 것이 들어있어요.
이미지는 레이어 구조로 되어 있는데, 이게 진짜 천재적인 설계예요.
예를 들어 Ubuntu 기본 이미지 위에 Node.js를 설치하고, 그 위에 내 애플리케이션을 올리는 식으로 레이어가 쌓이는 거죠.
이렇게 하면 같은 베이스 이미지를 공유하는 여러 컨테이너가 디스크 공간을 효율적으로 사용할 수 있어요.
2. Docker Container (컨테이너) 🎁
컨테이너는 이미지를 실행한 인스턴스예요. 붕어빵 틀로 만든 "실제 붕어빵"이라고 보면 됩니다.
하나의 이미지로 여러 개의 컨테이너를 만들 수 있고, 각 컨테이너는 독립적으로 실행돼요.
컨테이너는 격리된 환경에서 실행되기 때문에 서로 영향을 주지 않아요.
한 컨테이너가 죽어도 다른 컨테이너는 멀쩡하게 돌아가는 거죠.
3. Dockerfile 📝
Dockerfile은 이미지를 만들기 위한 설계도예요.
텍스트 파일로 되어 있고, 어떤 베이스 이미지를 사용할지, 어떤 파일을 복사할지, 어떤 명령어를 실행할지 등을 정의합니다.
Node.js 애플리케이션을 위한 간단한 Dockerfile을 볼까요?
# Node.js 18 버전을 베이스 이미지로 사용
FROM node:18-alpine
# 작업 디렉토리 설정
WORKDIR /app
# package.json과 package-lock.json 복사
COPY package*.json ./
# 의존성 설치
RUN npm install
# 애플리케이션 소스 복사
COPY . .
# 포트 노출
EXPOSE 3000
# 애플리케이션 실행
CMD ["npm", "start"]
이 Dockerfile 하나면 어디서든 동일한 환경에서 애플리케이션을 실행할 수 있어요.
로컬 개발 환경이든, AWS든, Azure든, Google Cloud든 상관없이요!
특히 alpine 이미지를 사용한 건 주목할 만한데요, Alpine Linux는 5MB 정도밖에 안 되는 초경량 리눅스 배포판이에요.
일반 Node.js 이미지가 900MB 정도 되는 것에 비하면 엄청난 차이죠 ㅋㅋㅋ
Docker 명령어 마스터하기
Docker를 실제로 사용하려면 기본 명령어들을 알아야 하는데, 처음에는 좀 헷갈릴 수 있어요.
하지만 자주 쓰다 보면 손에 익어요!
🔹 이미지 관련 명령어
# Docker Hub에서 이미지 다운로드
docker pull nginx:latest
# 로컬 이미지 목록 확인
docker images
# Dockerfile로 이미지 빌드
docker build -t myapp:1.0 .
# 이미지 삭제
docker rmi myapp:1.0
# 사용하지 않는 이미지 일괄 삭제
docker image prune
🔹 컨테이너 관련 명령어
# 컨테이너 실행 (백그라운드)
docker run -d -p 8080:80 --name my-nginx nginx
# 실행 중인 컨테이너 확인
docker ps
# 모든 컨테이너 확인 (중지된 것 포함)
docker ps -a
# 컨테이너 중지
docker stop my-nginx
# 컨테이너 시작
docker start my-nginx
# 컨테이너 재시작
docker restart my-nginx
# 컨테이너 삭제
docker rm my-nginx
# 컨테이너 로그 확인
docker logs my-nginx
# 실행 중인 컨테이너에 접속
docker exec -it my-nginx /bin/bash
여기서 -d는 detached 모드(백그라운드 실행), -p는 포트 매핑, --name은 컨테이너 이름 지정이에요.
-it는 interactive terminal의 약자로, 컨테이너 안에서 명령어를 직접 실행할 수 있게 해줍니다.
실무에서는 docker logs 명령어를 엄청 많이 쓰게 될 거예요.
애플리케이션에 문제가 생겼을 때 로그를 확인하는 게 첫 번째 디버깅 단계니까요!
실제 애플리케이션은 보통 여러 개의 컨테이너로 구성되잖아요?
웹 서버, 데이터베이스, 캐시 서버 등등...
이걸 하나하나 docker run으로 실행하면 진짜 귀찮아요 ㅋㅋㅋ
Docker Compose는 YAML 파일 하나로 여러 컨테이너를 정의하고 한 번에 실행할 수 있게 해줍니다.
개발 환경 구축할 때 완전 필수템이에요!
version: '3.8'
services:
web:
build: .
ports:
- "3000:3000"
depends_on:
- db
- redis
environment:
- NODE_ENV=production
- DB_HOST=db
volumes:
- ./logs:/app/logs
db:
image: postgres:15
environment:
- POSTGRES_PASSWORD=mysecretpassword
- POSTGRES_DB=myapp
volumes:
- postgres-data:/var/lib/postgresql/data
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
postgres-data:
이 docker-compose.yml 파일 하나면 웹 서버, PostgreSQL 데이터베이스, Redis 캐시를 한 번에 띄울 수 있어요.
docker-compose up -d 명령어 하나로 끝!
depends_on은 컨테이너 시작 순서를 정의하고, volumes는 데이터를 영구적으로 저장하기 위한 설정이에요.
컨테이너를 삭제해도 볼륨에 저장된 데이터는 남아있어서 데이터베이스 같은 경우 필수로 사용해야 합니다.
☸️ Kubernetes: 컨테이너 오케스트레이션의 왕
Docker만으로는 부족한 이유
Docker로 컨테이너를 만들고 실행하는 건 배웠는데, 실제 프로덕션 환경에서는 문제가 생겨요.
"컨테이너 100개를 어떻게 관리하지?"
"트래픽이 갑자기 늘어나면 자동으로 컨테이너를 늘릴 수 있나?"
"컨테이너 하나가 죽으면 자동으로 다시 시작시킬 수 있나?"
"무중단 배포는 어떻게 하지?"
이런 문제들을 해결하기 위해 등장한 게 바로 컨테이너 오케스트레이션 도구들이에요.
그중에서도 Kubernetes(쿠버네티스, 줄여서 K8s)가 사실상 표준이 되었죠.
Kubernetes는 원래 Google이 내부적으로 사용하던 Borg라는 시스템을 오픈소스로 공개한 거예요.
Google이 수십억 개의 컨테이너를 관리하면서 쌓은 노하우가 고스란히 담겨있는 거죠 ㅋㅋㅋ
1. 자동 스케일링: CPU 사용률이나 메모리 사용량에 따라 자동으로 컨테이너 개수 조절
2. 자동 복구: 컨테이너나 노드가 죽으면 자동으로 다시 시작
3. 로드 밸런싱: 트래픽을 여러 컨테이너에 자동으로 분산
4. 롤링 업데이트: 무중단으로 새 버전 배포
5. 서비스 디스커버리: 컨테이너들이 서로를 자동으로 찾을 수 있게 함
6. 시크릿 관리: 비밀번호, API 키 같은 민감한 정보를 안전하게 관리
Kubernetes 아키텍처 이해하기
Kubernetes는 구조가 좀 복잡한데, 크게 Control Plane과 Worker Node로 나뉘어요.
🎛️ Control Plane (마스터 노드)
클러스터 전체를 관리하는 두뇌 역할이에요. 주요 구성 요소는:
• API Server: Kubernetes의 모든 작업은 이 API를 통해 이루어져요. kubectl 명령어도 결국 API Server와 통신하는 거죠.
• etcd: 클러스터의 모든 데이터를 저장하는 분산 키-값 저장소예요. 클러스터 상태 정보가 여기 저장됩니다.
• Scheduler: 새로운 Pod를 어느 노드에 배치할지 결정해요. 리소스 사용량, 제약 조건 등을 고려해서 최적의 노드를 선택하죠.
• Controller Manager: 클러스터 상태를 지속적으로 모니터링하고 원하는 상태로 유지해요. 예를 들어 Pod가 3개여야 하는데 2개만 실행 중이면 자동으로 1개를 더 생성합니다.
⚙️ Worker Node
실제로 컨테이너가 실행되는 서버들이에요. 각 노드에는:
• kubelet: 노드의 에이전트로, Control Plane의 지시를 받아 컨테이너를 실행하고 관리해요.
• kube-proxy: 네트워크 프록시로, 서비스 간 통신을 관리합니다.
• Container Runtime: 실제로 컨테이너를 실행하는 소프트웨어예요. Docker, containerd, CRI-O 등을 사용할 수 있어요.
Kubernetes 핵심 오브젝트들
Kubernetes에서는 모든 것이 오브젝트로 관리돼요.
YAML 파일로 오브젝트를 정의하고, kubectl 명령어로 클러스터에 적용하는 방식이죠.
🎪 Pod (파드)
Kubernetes에서 가장 작은 배포 단위예요.
하나 이상의 컨테이너를 포함할 수 있고, 같은 Pod 안의 컨테이너들은 네트워크와 스토리지를 공유해요.
보통은 하나의 Pod에 하나의 컨테이너를 넣지만, 밀접하게 연관된 컨테이너들은 같은 Pod에 넣기도 해요.
예를 들어 메인 애플리케이션 컨테이너와 로그 수집 사이드카 컨테이너를 같은 Pod에 배치하는 식이죠.
apiVersion: v1
kind: Pod
metadata:
name: nginx-pod
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.25
ports:
- containerPort: 80
resources:
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "128Mi"
cpu: "500m"
🎯 Deployment (디플로이먼트)
실무에서는 Pod를 직접 만들기보다는 Deployment를 사용해요.
Deployment는 Pod의 복제본을 관리하고, 롤링 업데이트, 롤백 등의 기능을 제공합니다.
예를 들어 "nginx Pod를 3개 유지해줘"라고 선언하면, Deployment가 알아서 3개를 만들고 유지해요.
하나가 죽으면 자동으로 새로 만들어주고요 ㅋㅋㅋ
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.25
ports:
- containerPort: 80
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
여기서 replicas: 3은 Pod를 3개 유지하라는 뜻이고,
RollingUpdate 전략은 무중단 배포를 위한 설정이에요.
maxSurge: 1은 업데이트 중 최대 1개까지 추가로 생성 가능하다는 뜻이고,
maxUnavailable: 0은 업데이트 중에도 모든 Pod가 사용 가능해야 한다는 의미예요.
🌐 Service (서비스)
Pod는 언제든지 죽고 다시 생성될 수 있어요. 그럼 IP 주소가 바뀌겠죠?
Service는 Pod들에 대한 안정적인 네트워크 엔드포인트를 제공해요.
Service 타입은 여러 가지가 있는데:
• ClusterIP: 클러스터 내부에서만 접근 가능 (기본값)
• NodePort: 각 노드의 특정 포트로 외부 접근 가능
• LoadBalancer: 클라우드 제공자의 로드 밸런서 사용
• ExternalName: 외부 DNS 이름으로 매핑
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
type: LoadBalancer
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
이 Service는 app: nginx 레이블을 가진 모든 Pod로 트래픽을 분산시켜요.
로드 밸런서가 자동으로 생성되고, 외부 IP가 할당됩니다.
📦 ConfigMap & Secret
애플리케이션 설정과 민감한 정보를 관리하는 오브젝트예요.
ConfigMap은 일반적인 설정 데이터를 저장하고,
Secret은 비밀번호, API 키 같은 민감한 정보를 base64로 인코딩해서 저장해요.
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
database_url: "postgres://db:5432/myapp"
log_level: "info"
max_connections: "100"
---
apiVersion: v1
kind: Secret
metadata:
name: app-secret
type: Opaque
data:
db_password: bXlzZWNyZXRwYXNzd29yZA== # base64 인코딩된 값
api_key: YWJjZGVmZ2hpamtsbW5vcA==
이렇게 설정을 분리하면 코드 변경 없이 환경별로 다른 설정을 적용할 수 있어요.
개발, 스테이징, 프로덕션 환경마다 다른 ConfigMap과 Secret을 사용하는 거죠.
💾 PersistentVolume & PersistentVolumeClaim
컨테이너는 기본적으로 상태가 없어요(stateless).
컨테이너가 재시작되면 데이터가 사라지죠.
데이터베이스처럼 데이터를 영구적으로 저장해야 하는 경우 PersistentVolume을 사용해요.
PVC(PersistentVolumeClaim)로 필요한 스토리지를 요청하고, PV(PersistentVolume)가 실제 스토리지를 제공하는 구조예요.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
storageClassName: fast-ssd
🔄 Ingress
여러 Service를 하나의 진입점으로 통합하고, HTTP/HTTPS 라우팅을 관리해요.
도메인 기반 라우팅, 경로 기반 라우팅, SSL/TLS 종료 등의 기능을 제공합니다.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- host: myapp.example.com
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: api-service
port:
number: 8080
- path: /
pathType: Prefix
backend:
service:
name: frontend-service
port:
number: 80
tls:
- hosts:
- myapp.example.com
secretName: tls-secret
이 Ingress는 myapp.example.com/api로 오는 요청은 api-service로,
나머지 요청은 frontend-service로 라우팅해요.
HTTPS도 자동으로 처리하고요!
kubectl 명령어 완전 정복
Kubernetes를 다루려면 kubectl 명령어를 잘 알아야 해요.
자주 쓰는 명령어들을 정리해볼게요!
📋 리소스 조회
# 모든 Pod 조회
kubectl get pods
# 모든 네임스페이스의 Pod 조회
kubectl get pods --all-namespaces
# 상세 정보 포함
kubectl get pods -o wide
# YAML 형식으로 출력
kubectl get pod nginx-pod -o yaml
# 특정 리소스의 상세 정보
kubectl describe pod nginx-pod
# 실시간 모니터링
kubectl get pods --watch
🚀 리소스 생성 및 적용
# YAML 파일로 리소스 생성
kubectl apply -f deployment.yaml
# 여러 파일 한 번에 적용
kubectl apply -f ./configs/
# 리소스 삭제
kubectl delete -f deployment.yaml
# 특정 리소스 삭제
kubectl delete pod nginx-pod
# 네임스페이스의 모든 리소스 삭제
kubectl delete all --all -n my-namespace
🔍 디버깅 및 로그
# Pod 로그 확인
kubectl logs nginx-pod
# 실시간 로그 스트리밍
kubectl logs -f nginx-pod
# 이전 컨테이너 로그 (재시작된 경우)
kubectl logs nginx-pod --previous
# 여러 컨테이너 중 특정 컨테이너 로그
kubectl logs nginx-pod -c sidecar-container
# Pod 안에서 명령어 실행
kubectl exec -it nginx-pod -- /bin/bash
# 파일 복사
kubectl cp nginx-pod:/var/log/app.log ./app.log
⚙️ 스케일링 및 업데이트
# Deployment 스케일링
kubectl scale deployment nginx-deployment --replicas=5
# 자동 스케일링 설정
kubectl autoscale deployment nginx-deployment --min=2 --max=10 --cpu-percent=80
# 이미지 업데이트
kubectl set image deployment/nginx-deployment nginx=nginx:1.26
# 롤아웃 상태 확인
kubectl rollout status deployment/nginx-deployment
# 롤아웃 히스토리
kubectl rollout history deployment/nginx-deployment
# 이전 버전으로 롤백
kubectl rollout undo deployment/nginx-deployment
# 특정 리비전으로 롤백
kubectl rollout undo deployment/nginx-deployment --to-revision=2
🏷️ 레이블 및 셀렉터
# 레이블로 필터링
kubectl get pods -l app=nginx
# 레이블 추가
kubectl label pod nginx-pod environment=production
# 레이블 수정
kubectl label pod nginx-pod environment=staging --overwrite
# 레이블 삭제
kubectl label pod nginx-pod environment-
1. 별칭(alias) 설정: alias k=kubectl 이렇게 하면 타이핑이 훨씬 편해져요 ㅋㅋㅋ
2. 자동완성 설정: bash나 zsh 자동완성을 활성화하면 탭으로 명령어를 완성할 수 있어요.
3. 컨텍스트 관리: 여러 클러스터를 다룰 때는 kubectx, kubens 같은 도구를 사용하면 편해요.
4. k9s 사용: 터미널 기반 UI 도구로, 마우스 없이도 직관적으로 클러스터를 관리할 수 있어요.
🔄 DevOps 자동화: CI/CD 파이프라인 구축
DevOps가 뭐길래 이렇게 난리야?
DevOps는 Development(개발)와 Operations(운영)의 합성어예요.
예전에는 개발팀과 운영팀이 완전히 분리되어 있었어요.
개발자: "코드 다 짰어요! 배포해주세요~"
운영자: "이거 왜 안 돌아가요? 문서도 없고..."
개발자: "제 컴퓨터에서는 잘 되는데요?" ㅋㅋㅋ
이런 식으로 서로 책임을 떠넘기는 일이 많았죠.
DevOps는 이런 장벽을 없애고, 개발부터 배포, 운영까지 전체 라이프사이클을 자동화하고 협업하는 문화예요.
핵심은 자동화와 지속적인 개선입니다.
1. 자동화: 반복적인 작업은 모두 자동화
2. 지속적 통합/배포: 작은 변경을 자주, 빠르게 배포
3. 인프라 코드화: 인프라도 코드로 관리
4. 모니터링과 피드백: 실시간 모니터링으로 빠른 대응
5. 협업 문화: 개발과 운영의 경계 없애기
CI/CD 파이프라인 이해하기
CI/CD는 DevOps의 핵심 실천 방법이에요.
🔄 CI (Continuous Integration, 지속적 통합)
개발자들이 코드를 자주 메인 브랜치에 통합하고, 자동으로 빌드와 테스트를 수행하는 거예요.
예전에는 각자 개발하다가 나중에 합치면 충돌이 엄청 났잖아요?
CI는 작은 변경을 자주 통합해서 이런 문제를 조기에 발견하고 해결해요.
일반적인 CI 프로세스:
1. 개발자가 코드를 Git에 푸시
2. CI 서버가 자동으로 감지
3. 코드 빌드
4. 자동화된 테스트 실행
5. 코드 품질 검사 (린트, 정적 분석)
6. 결과를 개발자에게 알림
🚀 CD (Continuous Delivery/Deployment, 지속적 배포)
CI를 통과한 코드를 자동으로 프로덕션 환경에 배포하는 거예요.
Continuous Delivery는 배포 준비까지 자동화하고 최종 배포는 수동으로 하는 거고,
Continuous Deployment는 완전 자동 배포예요.
일반적인 CD 프로세스:
1. CI 통과한 코드를 스테이징 환경에 배포
2. 통합 테스트, E2E 테스트 실행
3. 승인 프로세스 (선택적)
4. 프로덕션 환경에 배포
5. 모니터링 및 롤백 준비
GitHub Actions로 CI/CD 구축하기
GitHub Actions는 GitHub에 내장된 CI/CD 플랫폼이에요.
별도의 서버 설정 없이 YAML 파일만으로 자동화 워크플로우를 만들 수 있어서 진짜 편해요 ㅋㅋㅋ
실제 프로젝트에서 사용하는 워크플로우 예제를 볼게요.
name: CI/CD Pipeline
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
env:
DOCKER_IMAGE: myapp
DOCKER_TAG: ${{ github.sha }}
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: 코드 체크아웃
uses: actions/checkout@v3
- name: Node.js 설정
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: 의존성 설치
run: npm ci
- name: 린트 검사
run: npm run lint
- name: 단위 테스트
run: npm test
- name: 테스트 커버리지
run: npm run test:coverage
- name: 커버리지 리포트 업로드
uses: codecov/codecov-action@v3
build:
needs: test
runs-on: ubuntu-latest
steps:
- name: 코드 체크아웃
uses: actions/checkout@v3
- name: Docker 빌드엑스 설정
uses: docker/setup-buildx-action@v2
- name: Docker Hub 로그인
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Docker 이미지 빌드 및 푸시
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: |
${{ secrets.DOCKER_USERNAME }}/${{ env.DOCKER_IMAGE }}:${{ env.DOCKER_TAG }}
${{ secrets.DOCKER_USERNAME }}/${{ env.DOCKER_IMAGE }}:latest
cache-from: type=gha
cache-to: type=gha,mode=max
deploy:
needs: build
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- name: 코드 체크아웃
uses: actions/checkout@v3
- name: Kubernetes 설정
uses: azure/k8s-set-context@v3
with:
method: kubeconfig
kubeconfig: ${{ secrets.KUBE_CONFIG }}
- name: Kubernetes 배포
run: |
kubectl set image deployment/myapp \
myapp=${{ secrets.DOCKER_USERNAME }}/${{ env.DOCKER_IMAGE }}:${{ env.DOCKER_TAG }}
kubectl rollout status deployment/myapp
- name: 배포 알림
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
text: '배포가 완료되었습니다!'
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
if: always()
이 워크플로우는 세 단계로 구성되어 있어요:
1. Test 단계: 코드 품질 검사와 테스트
2. Build 단계: Docker 이미지 빌드 및 레지스트리에 푸시
3. Deploy 단계: Kubernetes 클러스터에 배포
needs 키워드로 단계 간 의존성을 정의했고,
if 조건으로 main 브랜치에만 배포하도록 설정했어요.
GitHub Secrets에 민감한 정보(Docker 자격증명, Kubernetes 설정 등)를 저장하고,
워크플로우에서 ${{ secrets.XXX }} 형태로 참조해요.
GitOps: Git을 단일 진실 공급원으로
GitOps는 Git을 인프라와 애플리케이션의 단일 진실 공급원(Single Source of Truth)으로 사용하는 방법론이에요.
모든 설정을 Git 저장소에 코드로 관리하고,
Git에 변경사항이 생기면 자동으로 클러스터에 반영되는 거죠.
🎯 GitOps의 핵심 원칙
1. 선언적 정의: 시스템의 원하는 상태를 선언적으로 정의
2. Git 저장: 모든 것을 Git에 버전 관리
3. 자동 적용: Git 변경사항을 자동으로 시스템에 반영
4. 지속적 조정: 실제 상태와 원하는 상태를 지속적으로 비교하고 조정
대표적인 GitOps 도구로는 ArgoCD와 Flux가 있어요.
ArgoCD는 Kubernetes를 위한 선언적 GitOps CD 도구예요.
Git 저장소의 매니페스트 파일과 클러스터의 실제 상태를 지속적으로 비교하고,
차이가 있으면 자동으로 동기화해요.
웹 UI가 정말 직관적이어서 배포 상태를 한눈에 볼 수 있고,
롤백도 클릭 한 번으로 가능해요!
재능넷 같은 플랫폼에서 DevOps 관련 재능을 공유하거나 배우고 싶다면,
ArgoCD 실습 프로젝트를 만들어보는 것도 좋은 방법이에요.
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myapp
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/myorg/myapp-config
targetRevision: HEAD
path: k8s/production
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true
selfHeal: true
allowEmpty: false
syncOptions:
- CreateNamespace=true
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3m
이 ArgoCD Application 정의는:
• Git 저장소의 k8s/production 경로를 모니터링
• 변경사항을 자동으로 프로덕션 네임스페이스에 동기화
• 수동으로 삭제된 리소스를 자동으로 다시 생성(selfHeal)
• Git에서 삭제된 리소스를 클러스터에서도 삭제(prune)
이렇게 하면 Git 커밋 하나로 배포가 완료되고, 모든 변경 이력이 Git에 남아요.
롤백도 그냥 Git revert 하면 끝! 완전 편하죠 ㅋㅋㅋ
Infrastructure as Code (IaC)
인프라를 코드로 관리하는 것도 DevOps의 핵심이에요.
서버를 수동으로 설정하는 대신, 코드로 정의하고 자동으로 프로비저닝하는 거죠.
🔧 Terraform
Terraform은 가장 인기 있는 IaC 도구예요.
AWS, Azure, GCP 등 다양한 클라우드 제공자를 지원하고,
선언적 문법으로 인프라를 정의할 수 있어요.
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "ap-northeast-2"
}
# VPC 생성
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "main-vpc"
}
}
# 서브넷 생성
resource "aws_subnet" "public" {
count = 2
vpc_id = aws_vpc.main.id
cidr_block = "10.0.${count.index}.0/24"
availability_zone = data.aws_availability_zones.available.names[count.index]
tags = {
Name = "public-subnet-${count.index + 1}"
}
}
# EKS 클러스터 생성
resource "aws_eks_cluster" "main" {
name = "my-cluster"
role_arn = aws_iam_role.cluster.arn
version = "1.28"
vpc_config {
subnet_ids = aws_subnet.public[*].id
}
depends_on = [
aws_iam_role_policy_attachment.cluster_policy
]
}
# 노드 그룹 생성
resource "aws_eks_node_group" "main" {
cluster_name = aws_eks_cluster.main.name
node_group_name = "main-node-group"
node_role_arn = aws_iam_role.node.arn
subnet_ids = aws_subnet.public[*].id
scaling_config {
desired_size = 3
max_size = 5
min_size = 1
}
instance_types = ["t3.medium"]
depends_on = [
aws_iam_role_policy_attachment.node_policy
]
}
이 Terraform 코드는 AWS에 완전한 Kubernetes 클러스터를 생성해요.
VPC, 서브넷, EKS 클러스터, 노드 그룹까지 모두 자동으로 만들어지죠.
terraform apply 명령어 하나면 몇 분 안에 프로덕션급 인프라가 완성돼요!
terraform destroy로 한 번에 삭제할 수도 있고요.
🎨 Helm: Kubernetes 패키지 매니저
Helm은 Kubernetes 애플리케이션을 패키징하고 배포하는 도구예요.
복잡한 애플리케이션을 차트(Chart)라는 패키지로 만들어서 쉽게 배포할 수 있어요.
# values.yaml
replicaCount: 3
image:
repository: myapp
tag: "1.0.0"
pullPolicy: IfNotPresent
service:
type: LoadBalancer
port: 80
ingress:
enabled: true
className: nginx
hosts:
- host: myapp.example.com
paths:
- path: /
pathType: Prefix
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 250m
memory: 256Mi
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 80
env:
- name: NODE_ENV
value: production
- name: DB_HOST
valueFrom:
secretKeyRef:
name: db-secret
key: host
Helm 차트를 사용하면 환경별로 다른 설정을 쉽게 적용할 수 있어요.
개발 환경에서는 values-dev.yaml,
프로덕션에서는 values-prod.yaml을 사용하는 식이죠.
배포도 간단해요:
# Helm 차트 설치
helm install myapp ./myapp-chart -f values-prod.yaml
# 업그레이드
helm upgrade myapp ./myapp-chart -f values-prod.yaml
# 롤백
helm rollback myapp 1
# 삭제
helm uninstall myapp
📊 모니터링과 로깅: 시스템 가시성 확보
모니터링 없는 운영은 눈 감고 운전하는 것
아무리 완벽한 시스템을 만들어도 문제는 생기기 마련이에요.
중요한 건 문제가 생겼을 때 빠르게 감지하고 대응하는 거죠.
그래서 모니터링과 로깅이 필수예요!
🎯 관찰 가능성(Observability)의 3가지 기둥
1. 메트릭(Metrics): 시스템의 수치 데이터 (CPU, 메모리, 요청 수 등)
2. 로그(Logs): 시스템에서 발생하는 이벤트 기록
3. 트레이스(Traces): 요청의 전체 흐름 추적
Prometheus & Grafana로 메트릭 모니터링
Prometheus는 시계열 데이터베이스 기반의 모니터링 시스템이에요.
Kubernetes와 찰떡궁합이라 거의 표준처럼 사용되고 있죠.
Grafana는 Prometheus 데이터를 시각화하는 대시보드 도구예요.
예쁜 그래프로 시스템 상태를 한눈에 볼 수 있어요 ㅋㅋㅋ
# Prometheus 설정 (prometheus.yml)
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: 'kubernetes-pods'
kubernetes_sd_configs:
- role: pod
relabel_configs:
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
action: keep
regex: true
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path]
action: replace
target_label: __metrics_path__
regex: (.+)
- source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_port]
action: replace
regex: ([^:]+)(?::\d+)?;(\d+)
replacement: $1:$2
target_label: __address__
- job_name: 'kubernetes-nodes'
kubernetes_sd_configs:
- role: node
relabel_configs:
- action: labelmap
regex: __meta_kubernetes_node_label_(.+)
애플리케이션에서 메트릭을 노출하는 것도 간단해요.
Node.js 예제를 볼게요:
const express = require('express');
const promClient = require('prom-client');
const app = express();
// 기본 메트릭 수집
const collectDefaultMetrics = promClient.collectDefaultMetrics;
collectDefaultMetrics({ timeout: 5000 });
// 커스텀 메트릭 정의
const httpRequestDuration = new promClient.Histogram({
name: 'http_request_duration_seconds',
help: 'Duration of HTTP requests in seconds',
labelNames: ['method', 'route', 'status_code']
});
const httpRequestTotal = new promClient.Counter({
name: 'http_requests_total',
help: 'Total number of HTTP requests',
labelNames: ['method', 'route', 'status_code']
});
// 미들웨어로 메트릭 수집
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = (Date.now() - start) / 1000;
httpRequestDuration
.labels(req.method, req.route?.path || req.path, res.statusCode)
.observe(duration);
httpRequestTotal
.labels(req.method, req.route?.path || req.path, res.statusCode)
.inc();
});
next();
});
// 메트릭 엔드포인트
app.get('/metrics', async (req, res) => {
res.set('Content-Type', promClient.register.contentType);
res.end(await promClient.register.metrics());
});
app.listen(3000);
이제 Prometheus가 /metrics 엔드포인트를 주기적으로 스크랩해서 데이터를 수집해요.
Grafana에서 이 데이터로 멋진 대시보드를 만들 수 있죠!
Grafana는 커뮤니티에서 만든 수천 개의 대시보드 템플릿을 제공해요.
• Node Exporter Full: 서버 리소스 모니터링
• Kubernetes Cluster Monitoring: K8s 클러스터 전체 현황
• Kubernetes Pod Monitoring: Pod별 상세 메트릭
• NGINX Ingress Controller: 인그레스 트래픽 분석
이런 템플릿들을 import해서 바로 사용할 수 있어요!
ELK/EFK 스택으로 로그 관리
분산 시스템에서 로그를 관리하는 건 진짜 어려워요.
수백 개의 컨테이너에서 나오는 로그를 어떻게 수집하고 검색하죠?
ELK 스택 (Elasticsearch, Logstash, Kibana) 또는
EFK 스택 (Elasticsearch, Fluentd, Kibana)이 이 문제를 해결해줘요.
• Fluentd/Logstash: 로그 수집 및 전처리
• Elasticsearch: 로그 저장 및 검색
• Kibana: 로그 시각화 및 분석
Kubernetes에서는 Fluentd를 DaemonSet으로 배포해서 모든 노드의 로그를 수집해요.
apiVersion: v1
kind: ConfigMap
metadata:
name: fluentd-config
namespace: logging
data:
fluent.conf: |
<source>
@type tail
path /var/log/containers/*.log
pos_file /var/log/fluentd-containers.log.pos
tag kubernetes.*
read_from_head true
<parse>
@type json
time_format %Y-%m-%dT%H:%M:%S.%NZ
</parse>
</source>
<filter kubernetes.**>
@type kubernetes_metadata
@id filter_kube_metadata
</filter>
<filter kubernetes.**>
@type parser
key_name log
reserve_data true
<parse>
@type json
</parse>
</filter>
<match kubernetes.**>
@type elasticsearch
host elasticsearch.logging.svc.cluster.local
port 9200
logstash_format true
logstash_prefix kubernetes
<buffer>
@type file
path /var/log/fluentd-buffers/kubernetes.system.buffer
flush_mode interval
retry_type exponential_backoff
flush_interval 5s
retry_forever false
retry_max_interval 30
chunk_limit_size 2M
queue_limit_length 8
overflow_action block
</buffer>
</match>
이 설정으로 모든 컨테이너 로그가 자동으로 Elasticsearch에 저장되고,
Kibana에서 검색하고 분석할 수 있어요.
"지난 1시간 동안 에러가 발생한 Pod는?"
"특정 사용자 ID와 관련된 모든 로그는?"
이런 복잡한 쿼리도 몇 초 만에 결과가 나와요!
분산 추적(Distributed Tracing)
마이크로서비스 아키텍처에서는 하나의 요청이 여러 서비스를 거쳐가요.
어느 서비스에서 느려지는지, 어디서 에러가 나는지 파악하기 어렵죠.
Jaeger나 Zipkin 같은 분산 추적 도구를 사용하면
요청의 전체 경로를 시각화해서 볼 수 있어요.
OpenTelemetry를 사용하면 코드에 계측(instrumentation)을 추가할 수 있어요:
const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node');
const { registerInstrumentations } = require('@opentelemetry/instrumentation');
const { HttpInstrumentation } = require('@opentelemetry/instrumentation-http');
const { ExpressInstrumentation } = require('@opentelemetry/instrumentation-express');
const { JaegerExporter } = require('@opentelemetry/exporter-jaeger');
const { BatchSpanProcessor } = require('@opentelemetry/sdk-trace-base');
// Tracer Provider 설정
const provider = new NodeTracerProvider();
// Jaeger Exporter 설정
const exporter = new JaegerExporter({
endpoint: 'http://jaeger:14268/api/traces',
});
provider.addSpanProcessor(new BatchSpanProcessor(exporter));
provider.register();
// 자동 계측 등록
registerInstrumentations({
instrumentations: [
new HttpInstrumentation(),
new ExpressInstrumentation(),
],
});
// 이제 모든 HTTP 요청이 자동으로 추적됩니다!
이렇게 설정하면 Jaeger UI에서 요청의 전체 플로우를 볼 수 있어요.
각 서비스에서 얼마나 시간이 걸렸는지, 어디서 병목이 발생하는지 한눈에 파악 가능하죠!
🔐 보안: DevSecOps 실천하기
보안은 나중에? 그건 옛날 얘기!
예전에는 개발 다 끝나고 배포 직전에 보안 검토를 했어요.
근데 그때 보안 문제가 발견되면? 완전 난리 나죠 ㅋㅋㅋ
DevSecOps는 개발 초기 단계부터 보안을 통합하는 접근 방식이에요.
"Shift Left Security"라고도 하는데, 보안을 왼쪽(개발 초기)으로 당기자는 의미죠.
1. 최소 권한 원칙: 컨테이너를 root가 아닌 사용자로 실행
2. 이미지 스캔: 취약점이 있는 이미지 사용 금지
3. 시크릿 관리: 비밀번호를 코드나 이미지에 하드코딩하지 않기
4. 네트워크 정책: 필요한 통신만 허용
5. 리소스 제한: CPU, 메모리 제한 설정
6. 읽기 전용 파일시스템: 가능하면 읽기 전용으로 마운트
7. 정기적 업데이트: 베이스 이미지와 의존성 최신 상태 유지
이미지 보안 스캔
Docker 이미지에는 수많은 라이브러리와 패키지가 포함되어 있어요.
이 중에 알려진 취약점이 있을 수 있죠.
Trivy는 컨테이너 이미지 취약점 스캐너예요.
사용법이 엄청 간단해요:
# 이미지 스캔
trivy image nginx:latest
# 심각도 높은 취약점만 표시
trivy image --severity HIGH,CRITICAL nginx:latest
# JSON 형식으로 출력
trivy image -f json -o results.json myapp:1.0
# Dockerfile 스캔
trivy config Dockerfile
CI/CD 파이프라인에 통합하면 취약점이 있는 이미지는 자동으로 배포를 막을 수 있어요:
- name: 이미지 보안 스캔
run: |
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
aquasec/trivy image --exit-code 1 --severity CRITICAL \
${{ secrets.DOCKER_USERNAME }}/myapp:${{ github.sha }}
--exit-code 1 옵션을 주면 CRITICAL 취약점이 발견되면 빌드가 실패해요.
프로덕션에 위험한 이미지가 배포되는 걸 막을 수 있죠!
Kubernetes 보안 강화
Kubernetes 클러스터 자체의 보안도 중요해요.
🔒 Pod Security Standards
Kubernetes 1.25부터 Pod Security Admission이 기본으로 활성화되었어요.
세 가지 보안 레벨이 있어요:
• Privileged: 제한 없음 (권장하지 않음)
• Baseline: 최소한의 제한
• Restricted: 강력한 보안 제한
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restricted
🔐 RBAC (Role-Based Access Control)
누가 어떤 리소스에 접근할 수 있는지 세밀하게 제어해요.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: production
name: pod-reader
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "watch", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: read-pods
namespace: production
subjects:
- kind: User
name: developer
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io
🔑 Secrets 암호화
Kubernetes Secret은 기본적으로 base64 인코딩만 되어 있어요.
암호화가 아니라서 etcd에 접근하면 그냥 볼 수 있죠.
Sealed Secrets나 External Secrets Operator를 사용하면
Secret을 안전하게 Git에 저장하고 관리할 수 있어요.
# Sealed Secret 생성
kubectl create secret generic mysecret \
--from-literal=password=mypassword \
--dry-run=client -o yaml | \
kubeseal -o yaml > mysealedsecret.yaml
# 이제 mysealedsecret.yaml을 Git에 커밋해도 안전해요!
# 클러스터에 적용하면 자동으로 복호화됩니다
kubectl apply -f mysealedsecret.yaml
네트워크 정책으로 트래픽 제어
기본적으로 Kubernetes의 모든 Pod는 서로 통신할 수 있어요.
하지만 보안상 필요한 통신만 허용하는 게 좋겠죠?
Network Policy로 Pod 간 트래픽을 제어할 수 있어요:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: api-network-policy
namespace: production
spec:
podSelector:
matchLabels:
app: api
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8080
egress:
- to:
- podSelector:
matchLabels:
app: database
ports:
- protocol: TCP
port: 5432
- to:
- namespaceSelector:
matchLabels:
name: kube-system
ports:
- protocol: TCP
port: 53
이 정책은:
• API Pod는 frontend Pod로부터만 8080 포트로 요청을 받을 수 있음
• API Pod는 database Pod의 5432 포트로만 나가는 연결을 할 수 있음
• DNS 조회를 위해 kube-system의 53 포트는 허용
이렇게 하면 공격자가 한 Pod를 장악해도 다른 Pod로 쉽게 이동할 수 없어요!
🎓 실전 프로젝트: 완전한 CI/CD 파이프라인 구축
이론은 이제 그만! 실제로 만들어보자
지금까지 배운 내용을 종합해서 실제 프로젝트를 만들어볼게요.
간단한 Node.js 애플리케이션을 Docker로 컨테이너화하고,
Kubernetes에 배포하고, CI/CD 파이프라인을 구축하는 전체 과정이에요!
📋 프로젝트 구조
myapp/
├── src/
│ ├── index.js
│ ├── routes/
│ └── controllers/
├── tests/
│ ├── unit/
│ └── integration/
├── k8s/
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── ingress.yaml
│ └── configmap.yaml
├── .github/
│ └── workflows/
│ └── ci-cd.yaml
├── Dockerfile
├── .dockerignore
├── package.json
└── README.md
1️⃣ 애플리케이션 코드 (src/index.js)
const express = require('express');
const promClient = require('prom-client');
const winston = require('winston');
const app = express();
const port = process.env.PORT || 3000;
// 로거 설정
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.Console()
]
});
// Prometheus 메트릭 설정
const collectDefaultMetrics = promClient.collectDefaultMetrics;
collectDefaultMetrics();
const httpRequestDuration = new promClient.Histogram({
name: 'http_request_duration_seconds',
help: 'Duration of HTTP requests in seconds',
labelNames: ['method', 'route', 'status_code']
});
// 헬스체크 엔드포인트
app.get('/health', (req, res) => {
res.json({ status: 'healthy', timestamp: new Date().toISOString() });
});
// 레디니스 체크
app.get('/ready', (req, res) => {
// 실제로는 DB 연결 등을 확인
res.json({ status: 'ready' });
});
// 메트릭 엔드포인트
app.get('/metrics', async (req, res) => {
res.set('Content-Type', promClient.register.contentType);
res.end(await promClient.register.metrics());
});
// 메인 API
app.get('/api/hello', (req, res) => {
const start = Date.now();
logger.info('Hello endpoint called', { ip: req.ip });
res.json({
message: 'Hello from Kubernetes!',
version: process.env.APP_VERSION || '1.0.0',
hostname: require('os').hostname()
});
const duration = (Date.now() - start) / 1000;
httpRequestDuration.labels(req.method, req.route.path, res.statusCode).observe(duration);
});
// 에러 핸들링
app.use((err, req, res, next) => {
logger.error('Unhandled error', { error: err.message, stack: err.stack });
res.status(500).json({ error: 'Internal server error' });
});
// Graceful shutdown
process.on('SIGTERM', () => {
logger.info('SIGTERM signal received: closing HTTP server');
server.close(() => {
logger.info('HTTP server closed');
process.exit(0);
});
});
const server = app.listen(port, () => {
logger.info(`Server running on port ${port}`);
});
2️⃣ Dockerfile
# 멀티 스테이지 빌드로 이미지 크기 최적화
FROM node:18-alpine AS builder
WORKDIR /app
# 의존성 파일만 먼저 복사 (캐시 활용)
COPY package*.json ./
RUN npm ci --only=production
# 소스 코드 복사
COPY src ./src
# 프로덕션 이미지
FROM node:18-alpine
# 보안: root가 아닌 사용자로 실행
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001
WORKDIR /app
# builder 스테이지에서 복사
COPY --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=nodejs:nodejs /app/src ./src
COPY --chown=nodejs:nodejs package*.json ./
USER nodejs
EXPOSE 3000
# 헬스체크
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD node -e "require('http').get('http://localhost:3000/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"
CMD ["node", "src/index.js"]
3️⃣ Kubernetes Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
namespace: production
labels:
app: myapp
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "3000"
prometheus.io/path: "/metrics"
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1001
fsGroup: 1001
containers:
- name: myapp
image: myregistry/myapp:latest
imagePullPolicy: Always
ports:
- containerPort: 3000
name: http
env:
- name: NODE_ENV
value: "production"
- name: APP_VERSION
value: "1.0.0"
- name: PORT
value: "3000"
envFrom:
- configMapRef:
name: myapp-config
- secretRef:
name: myapp-secret
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "200m"
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
readinessProbe:
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
volumeMounts:
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- myapp
topologyKey: kubernetes.io/hostname
이 Deployment는 프로덕션급 설정을 포함하고 있어요:
• 보안 컨텍스트로 권한 최소화
• 리소스 제한으로 안정성 확보
• Liveness/Readiness Probe로 자동 복구
• Pod Anti-Affinity로 고가용성 보장
• 읽기 전용 파일시스템으로 보안 강화
4️⃣ Service & Ingress
apiVersion: v1
kind: Service
metadata:
name: myapp
namespace: production
spec:
selector:
app: myapp
ports:
- port: 80
targetPort: 3000
name: http
type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapp
namespace: production
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
nginx.ingress.kubernetes.io/rate-limit: "100"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
ingressClassName: nginx
tls:
- hosts:
- myapp.example.com
secretName: myapp-tls
rules:
- host: myapp.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myapp
port:
number: 80
5️⃣ HorizontalPodAutoscaler
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: myapp
namespace: production
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: myapp
minReplicas: 3
maxReplicas: 10
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: 50
periodSeconds: 60
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Percent
value: 100
periodSeconds: 30
- type: Pods
value: 2
periodSeconds: 30
selectPolicy: Max
이제 트래픽이 증가하면 자동으로 Pod가 늘어나고,
트래픽이 줄면 천천히 줄어들어요!
CPU 70% 또는 메모리 80%를 넘으면 스케일 아웃이 시작되고,
최대 10개까지 늘어날 수 있어요.
🚀 마무리하며: 지속적인 학습의 중요성
와... 여기까지 읽느라 고생 많으셨어요! ㅋㅋㅋ
Docker, Kubernetes, DevOps 자동화까지 정말 방대한 내용이었죠?
솔직히 이 모든 걸 한 번에 마스터하기는 불가능해요.
저도 몇 년 동안 삽질하면서 배웠거든요 ㅋㅋㅋ
중요한 건 시작하는 거예요!
간단한 애플리케이션 하나를 Docker로 컨테이너화해보고,
로컬에서 Minikube나 Kind로 Kubernetes를 돌려보고,
GitHub Actions로 간단한 CI 파이프라인을 만들어보세요.
처음에는 에러 투성이일 거예요.
"왜 안 돼?" 하면서 구글링하고, 스택오버플로우 뒤지고...
근데 그게 다 실력이 되는 거예요!
공식 문서:
• Docker Documentation: docs.docker.com
• Kubernetes Documentation: kubernetes.io/docs
• CNCF Landscape: landscape.cncf.io
실습 플랫폼:
• Play with Docker: labs.play-with-docker.com
• Play with Kubernetes: labs.play-with-k8s.com
• Katacoda (O'Reilly): katacoda.com
커뮤니티:
• Kubernetes Slack: slack.k8s.io
• CNCF Slack: cloud-native.slack.com
• Reddit r/kubernetes, r/docker
인증:
• CKA (Certified Kubernetes Administrator)
• CKAD (Certified Kubernetes Application Developer)
• CKS (Certified Kubernetes Security Specialist)
그리고 혼자 공부하기 힘들다면 재능넷 같은 플랫폼을 활용해보세요!
실무 경험이 풍부한 전문가들에게 1:1로 배울 수도 있고,
여러분이 배운 내용을 다른 사람들에게 가르쳐주면서 더 깊이 이해할 수도 있어요.
실제로 가르치다 보면 "아, 이 부분을 제대로 이해 못 했구나" 하는 걸 깨닫게 되거든요.
재능넷에서 DevOps 관련 재능을 공유하거나 배우면서 성장하는 것도 좋은 방법이에요!
현업에서 자주 마주치는 문제들
마지막으로 실무에서 자주 겪는 문제들과 해결 방법을 공유할게요.
❌ 문제 1: "컨테이너가 계속 재시작돼요!"
→ kubectl logs로 로그 확인
→ kubectl describe pod로 이벤트 확인
→ Liveness Probe 설정이 너무 빡빡한지 확인
→ 메모리 부족(OOMKilled)인지 확인
❌ 문제 2: "이미지 빌드가 너무 느려요!"
→ 멀티 스테이지 빌드 사용
→ .dockerignore 파일로 불필요한 파일 제외
→ 레이어 캐싱 최적화 (자주 변경되는 파일은 나중에 COPY)
→ BuildKit 활성화 (DOCKER_BUILDKIT=1)
❌ 문제 3: "배포했는데 이전 버전이 계속 떠요!"
→ 이미지 태그를 latest 대신 구체적인 버전 사용
→ imagePullPolicy: Always 설정
→ 롤아웃 상태 확인: kubectl rollout status deployment/myapp
❌ 문제 4: "서비스 간 통신이 안 돼요!"
→ Service 이름으로 통신하는지 확인 (IP 말고)
→ 네임스페이스가 다르면 FQDN 사용: service-name.namespace.svc.cluster.local
→ Network Policy가 통신을 막고 있는지 확인
→ DNS 문제인지 확인: nslookup service-name
❌ 문제 5: "프로덕션에서만 에러가 나요!"
→ 환경 변수 설정 차이 확인
→ ConfigMap/Secret이 제대로 마운트되었는지 확인
→ 리소스 제한으로 인한 성능 저하는 아닌지 확인
→ 로그 레벨을 DEBUG로 올려서 상세 로그 확인
1. 로그부터 확인 (kubectl logs)
2. Pod 상태 확인 (kubectl describe pod)
3. 이벤트 확인 (kubectl get events)
4. 리소스 사용량 확인 (kubectl top pods)
5. 네트워크 연결 테스트 (kubectl exec -it pod -- curl)
6. 설정 확인 (kubectl get configmap/secret -o yaml)
7. 롤아웃 히스토리 확인 (kubectl rollout history)
이 순서대로 체크하면 대부분의 문제는 해결돼요!
미래의 트렌드
기술은 계속 발전하고 있어요.
앞으로 주목해야 할 트렌드들을 소개할게요:
🔮 서버리스 컨테이너
AWS Fargate, Google Cloud Run 같은 서비스는
Kubernetes 없이도 컨테이너를 쉽게 실행할 수 있게 해줘요.
인프라 관리 부담을 더 줄일 수 있죠.
🔮 WebAssembly (Wasm)
컨테이너보다 더 가볍고 빠른 실행 환경으로 주목받고 있어요.
Docker도 Wasm 지원을 추가했고, Kubernetes에서도 실행 가능해요.
🔮 eBPF
리눅스 커널 레벨에서 동작하는 기술로,
네트워킹, 보안, 관찰 가능성 분야에서 혁신을 일으키고 있어요.
Cilium, Falco 같은 도구들이 eBPF를 활용하고 있죠.
🔮 Platform Engineering
개발자 경험(Developer Experience)을 개선하는 내부 플랫폼 구축이 트렌드예요.
Backstage, Crossplane 같은 도구들이 인기를 얻고 있어요.
🔮 AI/ML Ops
머신러닝 모델을 컨테이너로 배포하고 관리하는 MLOps가 중요해지고 있어요.
Kubeflow, MLflow 같은 도구들을 알아두면 좋아요.
마지막 조언
기술은 도구일 뿐이에요.
중요한 건 문제를 해결하는 것이죠.
"이 기술을 써야 해!"가 아니라
"이 문제를 해결하려면 어떤 기술이 적합할까?"를 고민하세요.
Docker와 Kubernetes가 만능은 아니에요.
작은 프로젝트라면 단순한 배포 방식이 더 나을 수도 있어요.
하지만 확장성, 안정성, 자동화가 중요한 프로젝트라면?
컨테이너 오케스트레이션과 DevOps 자동화는 정말 강력한 무기가 될 거예요!
여러분의 DevOps 여정을 응원합니다! 🚀
궁금한 점이 있다면 커뮤니티에 질문하고,
배운 내용은 블로그나 재능넷 같은 곳에서 공유하면서
함께 성장해나가요!
화이팅! 💪
🎉 여기까지 읽어주셔서 감사합니다! 🎉
이제 직접 실습하면서 경험을 쌓아보세요!
댓글 0
지식인의 숲 - 지적 재산권 보호 고지
지적 재산권 보호 고지
- 저작권 및 소유권: 본 컨텐츠는 재능넷의 독점 AI 기술로 생성되었으며, 대한민국 저작권법 및 국제 저작권 협약에 의해 보호됩니다.
- AI 생성 컨텐츠의 법적 지위: 본 AI 생성 컨텐츠는 재능넷의 지적 창작물로 인정되며, 관련 법규에 따라 저작권 보호를 받습니다.
- 사용 제한: 재능넷의 명시적 서면 동의 없이 본 컨텐츠를 복제, 수정, 배포, 또는 상업적으로 활용하는 행위는 엄격히 금지됩니다.
- 데이터 수집 금지: 본 컨텐츠에 대한 무단 스크래핑, 크롤링, 및 자동화된 데이터 수집은 법적 제재의 대상이 됩니다.
- AI 학습 제한: 재능넷의 AI 생성 컨텐츠를 타 AI 모델 학습에 무단 사용하는 행위는 금지되며, 이는 지적 재산권 침해로 간주됩니다.

댓글 작성
이 글에 대한 여러분의 생각을 들려주세요
로그인이 필요합니다
댓글을 작성하려면 먼저 로그인해주세요.