본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2025. 12. 23. · 3 Views
이스티오 기반 마이크로서비스 플랫폼 완벽 가이드
Kubernetes와 Istio를 활용한 엔터프라이즈급 마이크로서비스 플랫폼 구축 방법을 실전 프로젝트로 배웁니다. Helm 차트 작성부터 트래픽 관리, 보안, 모니터링까지 전체 과정을 다룹니다.
목차
1. 플랫폼 아키텍처
어느 날 김개발 씨는 팀 리더로부터 새로운 미션을 받았습니다. "우리 서비스를 마이크로서비스 아키텍처로 전환해야 할 것 같아요.
어떻게 시작하면 좋을까요?" 이제껏 모놀리식 애플리케이션만 개발해온 김개발 씨는 막막했습니다.
마이크로서비스 플랫폼 아키텍처는 여러 개의 작은 서비스들이 유기적으로 연결되어 하나의 큰 시스템을 이루는 구조입니다. 마치 오케스트라처럼 각 악기가 독립적으로 연주하지만 지휘자가 조화롭게 통합하는 것과 같습니다.
Kubernetes는 컨테이너 오케스트레이션을, Istio는 서비스 간 통신을 담당합니다.
다음 코드를 살펴봅시다.
# 플랫폼 아키텍처 구성도 (YAML 형태)
apiVersion: v1
kind: Namespace
metadata:
name: microservices-platform
labels:
istio-injection: enabled # Istio 자동 주입 활성화
---
# 플랫폼 구성 요소
# 1. Frontend Service (React/Next.js)
# 2. API Gateway (Istio Gateway)
# 3. Backend Services (Python/Go/Node.js)
# 4. Database Layer (PostgreSQL/MongoDB)
# 5. Message Queue (RabbitMQ/Kafka)
# 6. Monitoring Stack (Prometheus/Grafana)
김개발 씨는 선배 개발자 박시니어 씨를 찾아갔습니다. "선배님, 마이크로서비스가 뭔지는 알겠는데 어떻게 구성해야 할지 모르겠어요." 박시니어 씨는 화이트보드에 그림을 그리기 시작했습니다.
"자, 먼저 큰 그림을 봐야 해요. 마이크로서비스 플랫폼은 크게 다섯 가지 레이어로 구성됩니다." 첫 번째는 인프라 레이어입니다.
마치 건물의 기초 공사와 같습니다. 여기서는 Kubernetes 클러스터가 모든 서비스의 토대가 됩니다.
AWS EKS, GCP GKE, 또는 온프레미스 환경에 구축할 수 있습니다. 이 레이어가 튼튼해야 그 위의 모든 것이 안정적으로 작동합니다.
두 번째는 서비스 메시 레이어입니다. 여기서 Istio가 등장합니다.
Istio는 마치 도시의 도로망과 신호 체계 같은 역할을 합니다. 수많은 마이크로서비스들이 서로 통신할 때 누가 누구에게 어떻게 연결되는지, 트래픽을 어떻게 분배할지를 관리합니다.
개발자가 코드에서 일일이 처리할 필요 없이 Istio가 자동으로 해결해줍니다. 세 번째는 애플리케이션 레이어입니다.
실제 비즈니스 로직이 담긴 마이크로서비스들이 여기 위치합니다. 사용자 인증 서비스, 주문 처리 서비스, 결제 서비스, 상품 관리 서비스 등 각자의 역할을 가진 서비스들이 독립적으로 배포되고 운영됩니다.
네 번째는 데이터 레이어입니다. 각 마이크로서비스는 자신만의 데이터베이스를 가질 수 있습니다.
이것을 Database per Service 패턴이라고 부릅니다. 주문 서비스는 PostgreSQL을, 상품 카탈로그 서비스는 MongoDB를 사용하는 식입니다.
각 서비스가 최적의 데이터베이스를 선택할 수 있는 유연성을 제공합니다. 다섯 번째는 관측성 레이어입니다.
마이크로서비스가 많아질수록 전체 시스템을 모니터링하기 어려워집니다. 마치 여러 개의 CCTV로 건물 전체를 감시하는 것처럼, Prometheus로 메트릭을 수집하고 Grafana로 시각화하며, Jaeger로 분산 추적을 수행합니다.
"왜 이렇게 복잡하게 나눠야 하나요?" 김개발 씨가 물었습니다. 박시니어 씨는 미소를 지었습니다.
"좋은 질문이에요. 모놀리식 애플리케이션의 문제를 생각해보세요." 예전에 김개발 씨 팀이 개발했던 전자상거래 사이트는 하나의 거대한 애플리케이션이었습니다.
결제 모듈에 작은 버그가 생기면 전체 서비스를 재배포해야 했습니다. 주문이 폭주하는 블랙프라이데이 때는 결제 부분만 확장하고 싶었지만 전체 애플리케이션을 스케일 아웃해야 했습니다.
비효율적이었죠. 마이크로서비스는 이런 문제를 해결합니다.
결제 서비스만 독립적으로 배포할 수 있고, 트래픽이 많은 서비스만 선택적으로 확장할 수 있습니다. 한 서비스에 장애가 발생해도 다른 서비스는 계속 작동합니다.
하지만 여기에는 새로운 도전 과제가 생깁니다. 서비스 간 통신을 어떻게 관리할까요?
보안은 어떻게 적용할까요? 장애를 어떻게 추적할까요?
바로 이 문제를 해결하기 위해 서비스 메시가 등장했습니다. Istio는 각 서비스 Pod에 사이드카 프록시(Envoy)를 자동으로 주입합니다.
이 프록시들이 모든 네트워크 트래픽을 가로채서 암호화, 로드 밸런싱, 재시도, 서킷 브레이킹 등을 처리합니다. 실제 아키텍처를 설계할 때는 다음과 같은 원칙을 따릅니다.
첫째, 단일 책임 원칙입니다. 각 마이크로서비스는 하나의 비즈니스 기능만 담당해야 합니다.
둘째, 느슨한 결합입니다. 서비스 간 의존성을 최소화하고 API를 통해서만 통신합니다.
셋째, 독립적인 배포입니다. 한 서비스의 변경이 다른 서비스에 영향을 주지 않아야 합니다.
김개발 씨는 이제 전체 그림이 그려지기 시작했습니다. "그럼 Helm은 어디에 쓰이나요?" Helm은 Kubernetes 애플리케이션의 패키지 매니저입니다.
마치 npm이나 pip처럼 말이죠. 마이크로서비스 하나를 배포하려면 Deployment, Service, ConfigMap, Secret 등 수많은 YAML 파일이 필요합니다.
Helm을 사용하면 이것들을 하나의 차트로 묶어 버전 관리하고 쉽게 배포할 수 있습니다. 다시 김개발 씨의 미션으로 돌아가 봅시다.
전체 플랫폼 아키텍처를 설계할 때는 먼저 비즈니스 도메인을 분석해야 합니다. 전자상거래라면 사용자 관리, 상품 카탈로그, 장바구니, 주문, 결제, 배송 추적 등으로 나눌 수 있습니다.
각 도메인이 하나의 마이크로서비스가 됩니다. 다음으로 서비스 간 통신 패턴을 정의합니다.
동기식 REST API를 쓸지, 비동기식 메시지 큐를 쓸지 결정합니다. 주문 생성은 동기식으로, 이메일 발송은 비동기식으로 처리하는 것이 일반적입니다.
마지막으로 데이터 일관성 전략을 수립합니다. 분산 시스템에서는 ACID 트랜잭션을 보장하기 어렵습니다.
대신 Saga 패턴이나 Event Sourcing을 활용해 최종 일관성을 달성합니다. "생각보다 고려할 것이 많네요." 김개발 씨가 말했습니다.
박시니어 씨가 고개를 끄덕였습니다. "맞아요.
하지만 한 번 제대로 구축하면 엄청난 유연성과 확장성을 얻게 됩니다. 이제 실제로 만들어볼까요?"
실전 팁
💡 - 마이크로서비스는 보통 10-15개 정도로 시작하고 필요에 따라 확장합니다
- 서비스 경계는 비즈니스 도메인에 따라 나누는 것이 가장 자연스럽습니다
- 처음부터 완벽한 아키텍처를 설계하려 하지 말고 점진적으로 개선해 나가세요
2. Helm 차트 작성
박시니어 씨가 김개발 씨에게 노트북을 건넸습니다. "자, 이제 첫 번째 마이크로서비스를 Helm 차트로 만들어볼까요?" 화면에는 수많은 YAML 파일들이 보였습니다.
김개발 씨는 한숨을 쉬었습니다. "이걸 다 작성해야 하나요?"
Helm 차트는 Kubernetes 리소스들을 템플릿화하여 재사용 가능한 패키지로 만드는 도구입니다. 마치 레고 블록 세트처럼 한 번 만들어두면 다양한 환경에서 반복해서 사용할 수 있습니다.
values.yaml 파일로 설정을 변경하면 개발, 스테이징, 프로덕션 환경에 맞게 배포할 수 있습니다.
다음 코드를 살펴봅시다.
# Chart.yaml - Helm 차트 메타데이터
apiVersion: v2
name: user-service
description: 사용자 관리 마이크로서비스
type: application
version: 1.0.0
appVersion: "1.0"
# values.yaml - 기본 설정값
replicaCount: 3
image:
repository: myregistry/user-service
tag: "1.0.0"
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 8080
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 250m
memory: 256Mi
"Helm이 왜 필요한지 보여드릴게요." 박시니어 씨가 터미널을 열었습니다. Helm이 없던 시절, 개발자들은 수십 개의 YAML 파일을 수동으로 관리해야 했습니다.
개발 환경용 deployment.yaml, 스테이징용 deployment-staging.yaml, 프로덕션용 deployment-prod.yaml을 따로 만들었습니다. 이미지 태그를 변경하려면 모든 파일을 일일이 수정해야 했습니다.
실수하기 딱 좋은 환경이었죠. Helm은 이 문제를 해결하기 위해 템플릿 시스템을 도입했습니다.
마치 워드프로세서의 편지 병합 기능처럼 말입니다. 하나의 템플릿을 만들고 변수만 바꿔가며 여러 문서를 생성하는 것이죠.
Helm 차트의 구조를 살펴보겠습니다. 가장 중요한 파일이 세 가지 있습니다.
첫 번째는 Chart.yaml입니다. 이것은 차트의 신분증과 같습니다.
차트 이름, 버전, 설명 등 메타데이터가 담겨 있습니다. 여기서 중요한 것이 version과 appVersion의 차이입니다.
version은 차트 자체의 버전이고, appVersion은 차트가 배포하는 애플리케이션의 버전입니다. 예를 들어 같은 user-service 1.0.0을 배포하더라도 차트 설정을 개선하면 차트 버전을 1.0.1로 올립니다.
두 번째는 values.yaml입니다. 여기가 핵심입니다.
모든 설정값이 이 파일에 모여 있습니다. 레플리카 개수, 이미지 태그, 리소스 제한, 환경 변수 등 배포 시 바꿀 수 있는 모든 것이 정의됩니다.
마치 게임의 옵션 설정 화면 같은 것이죠. 세 번째는 templates/ 디렉토리입니다.
여기에 실제 Kubernetes YAML 템플릿들이 들어갑니다. deployment.yaml, service.yaml, configmap.yaml 등이 있습니다.
이 파일들은 일반 YAML이 아니라 Go 템플릿 문법을 사용합니다. 김개발 씨가 templates/deployment.yaml을 열어봤습니다.
처음 보는 문법들이 가득했습니다. 박시니어 씨가 설명을 시작했습니다.
"이 이중 중괄호 문법을 봐요. {{ .Values.replicaCount }} 이런 식으로 쓰면 values.yaml의 값을 가져옵니다." 실제로 작동하는 deployment 템플릿을 만들어 봅시다.
replicas 필드에 고정된 숫자 3을 쓰는 대신 **{{ .Values.replicaCount }}**를 씁니다. 그러면 values.yaml에서 replicaCount 값을 바꾸는 것만으로 레플리카 수를 조절할 수 있습니다.
더 흥미로운 것은 조건문과 반복문도 사용할 수 있다는 점입니다. 예를 들어 프로덕션 환경에서만 리소스 제한을 적용하고 싶다면 {{ if .Values.production }} 같은 조건을 사용합니다.
Helm의 진정한 위력은 의존성 관리에서 드러납니다. 마이크로서비스가 PostgreSQL 데이터베이스를 필요로 한다면 어떻게 할까요?
Chart.yaml에 의존성을 명시하면 Helm이 자동으로 PostgreSQL 차트도 함께 설치해줍니다. npm의 package.json처럼 말이죠.
실무에서는 보통 umbrella 차트 패턴을 사용합니다. 전체 마이크로서비스 플랫폼을 하나의 부모 차트로 만들고, 각 마이크로서비스를 자식 차트로 구성하는 것입니다.
그러면 helm install 명령 하나로 전체 플랫폼을 배포할 수 있습니다. "그럼 환경별로 다른 설정은 어떻게 관리하나요?" 김개발 씨가 물었습니다.
좋은 질문입니다. Helm은 values 파일 오버라이드 기능을 제공합니다.
기본 values.yaml 외에 values-dev.yaml, values-prod.yaml을 만들어둡니다. 배포할 때 helm install -f values-prod.yaml처럼 특정 values 파일을 지정하면 기본값을 덮어씁니다.
또 다른 방법은 --set 플래그입니다. helm install --set replicaCount=5 처럼 명령행에서 직접 값을 지정할 수 있습니다.
CI/CD 파이프라인에서 동적으로 값을 전달할 때 유용합니다. 김개발 씨는 실제로 user-service 차트를 만들어보기로 했습니다.
helm create user-service 명령을 실행하니 자동으로 차트 구조가 생성되었습니다. 기본 템플릿들이 이미 들어있었습니다.
이제 values.yaml을 수정합니다. 이미지 레포지토리를 자신의 Docker Hub 계정으로 바꾸고, 레플리카 수를 3으로 설정했습니다.
리소스 제한도 서비스 특성에 맞게 조정했습니다. 다음으로 ConfigMap을 추가했습니다.
데이터베이스 연결 정보나 API 키 같은 설정값들을 코드에서 분리하기 위해서입니다. templates/configmap.yaml을 만들고 환경 변수들을 정의했습니다.
Secret도 필요했습니다. 데이터베이스 비밀번호는 ConfigMap에 평문으로 저장하면 안 됩니다.
templates/secret.yaml을 만들고 민감한 정보들을 base64 인코딩하여 저장했습니다. "잠깐, Secret을 Git에 커밋하면 안 되지 않나요?" 김개발 씨가 걱정했습니다.
완전히 맞는 지적입니다. 실무에서는 Sealed Secrets나 External Secrets Operator를 사용해 이 문제를 해결합니다.
Secret을 암호화하거나 외부 vault에서 가져오는 방식입니다. 드디어 차트가 완성되었습니다.
helm lint user-service 명령으로 문법을 검증하고, helm template user-service로 렌더링 결과를 미리 확인했습니다. 문제가 없으면 helm install my-user-service ./user-service로 실제 배포합니다.
배포 후에는 helm list로 설치된 릴리스를 확인하고, helm status my-user-service로 상태를 점검합니다. 업그레이드가 필요하면 helm upgrade my-user-service ./user-service를 실행합니다.
문제가 생기면 helm rollback으로 이전 버전으로 되돌릴 수 있습니다. 김개발 씨는 뿌듯했습니다.
한 번 차트를 만들어두니 앞으로 다른 마이크로서비스도 같은 패턴으로 쉽게 만들 수 있을 것 같았습니다.
실전 팁
💡 - helm create 명령으로 생성된 기본 템플릿을 시작점으로 사용하세요
- values.yaml에 주석을 상세히 달아두면 나중에 설정 변경이 쉽습니다
- 민감한 정보는 절대 Git에 커밋하지 말고 Sealed Secrets를 사용하세요
3. 마이크로서비스 배포
차트를 만들었으니 이제 실제로 배포할 차례입니다. 김개발 씨는 kubectl get pods 명령을 실행했지만 아무것도 뜨지 않았습니다.
"뭔가 잘못된 건가요?" 박시니어 씨가 웃으며 말했습니다. "Istio 설정을 먼저 해야죠."
마이크로서비스 배포는 단순히 Pod를 띄우는 것 이상입니다. Istio 사이드카 주입, 서비스 등록, 헬스체크 설정, 롤링 업데이트 전략 등이 모두 포함됩니다.
마치 비행기를 이륙시키기 전 수많은 체크리스트를 확인하는 것처럼, 각 단계를 신중하게 진행해야 합니다.
다음 코드를 살펴봅시다.
# deployment.yaml with Istio
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service
namespace: microservices-platform
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0 # 무중단 배포
selector:
matchLabels:
app: user-service
version: v1
template:
metadata:
labels:
app: user-service
version: v1
annotations:
sidecar.istio.io/inject: "true" # Istio 사이드카 주입
spec:
containers:
- name: user-service
image: myregistry/user-service:1.0.0
ports:
- containerPort: 8080
name: http
livenessProbe:
httpGet:
path: /health/live
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /health/ready
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
resources:
requests:
cpu: 250m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
박시니어 씨가 화면을 가리켰습니다. "먼저 네임스페이스에 Istio 주입을 활성화해야 합니다." Istio의 핵심은 사이드카 패턴입니다.
각 마이크로서비스 Pod에 Envoy 프록시가 별도의 컨테이너로 함께 배포됩니다. 이 프록시가 모든 인바운드와 아웃바운드 트래픽을 가로채서 처리합니다.
개발자는 코드를 전혀 수정하지 않아도 됩니다. 사이드카를 자동으로 주입하려면 네임스페이스에 istio-injection=enabled 레이블을 붙입니다.
kubectl label namespace microservices-platform istio-injection=enabled 명령을 실행하면 이 네임스페이스에 배포되는 모든 Pod에 자동으로 사이드카가 추가됩니다. "그럼 이제 배포해볼까요?" 김개발 씨가 흥분했습니다.
잠깐, 아직 할 일이 남았습니다. 헬스체크를 제대로 설정해야 합니다.
Kubernetes는 두 가지 프로브를 제공합니다. Liveness Probe는 컨테이너가 살아있는지 확인합니다.
마치 맥박을 재는 것과 같습니다. 이 체크가 실패하면 Kubernetes는 컨테이너를 재시작합니다.
애플리케이션이 데드락에 걸리거나 응답 불능 상태가 되었을 때 자동으로 복구됩니다. Readiness Probe는 컨테이너가 트래픽을 받을 준비가 되었는지 확인합니다.
애플리케이션이 시작되는 데 시간이 걸리거나, 데이터베이스 연결을 기다리고 있을 수 있습니다. Readiness Probe가 성공할 때까지 Service는 해당 Pod로 트래픽을 보내지 않습니다.
실무에서 자주 하는 실수가 있습니다. Liveness Probe의 timeout을 너무 짧게 설정하는 것입니다.
애플리케이션이 일시적으로 느려졌을 때 계속 재시작되는 재시작 루프에 빠질 수 있습니다. initialDelaySeconds를 충분히 여유있게 설정하는 것이 중요합니다.
다음은 롤링 업데이트 전략입니다. 새 버전을 배포할 때 모든 Pod를 한 번에 교체하면 서비스가 중단됩니다.
대신 하나씩 순차적으로 교체하는 것이 무중단 배포의 핵심입니다. maxSurge는 업데이트 중 추가로 생성할 수 있는 Pod 수입니다.
레플리카가 3개인데 maxSurge가 1이면 업데이트 중 최대 4개까지 Pod가 존재할 수 있습니다. maxUnavailable은 업데이트 중 사용 불가능한 Pod 수입니다.
0으로 설정하면 완전한 무중단 배포가 됩니다. 이제 실제로 배포해봅시다.
helm install user-service ./user-service 명령을 실행합니다. 잠시 후 kubectl get pods -n microservices-platform을 확인해보니 Pod가 생성되고 있었습니다.
"어? 컨테이너가 2개네요?" 김개발 씨가 놀랐습니다.
맞습니다. 하나는 user-service 애플리케이션이고, 다른 하나는 istio-proxy입니다.
kubectl describe pod를 실행해보면 init container로 istio-init도 있었습니다. 이것이 네트워크 규칙을 설정하여 모든 트래픽을 프록시로 우회시킵니다.
Pod가 Running 상태가 되었지만 아직 READY가 0/2였습니다. Readiness Probe를 통과하지 못한 것입니다.
로그를 확인해보니 데이터베이스 연결에 실패하고 있었습니다. "아, ConfigMap에 DB 주소를 설정하지 않았네요." 김개발 씨가 values.yaml을 수정하고 helm upgrade를 실행했습니다.
잠시 후 Pod의 READY가 2/2가 되었습니다. kubectl get svc를 보니 user-service라는 ClusterIP 서비스도 자동으로 생성되어 있었습니다.
이제 다른 마이크로서비스에서 user-service.microservices-platform.svc.cluster.local이라는 DNS 이름으로 접근할 수 있습니다. 실제 프로덕션 환경에서는 HorizontalPodAutoscaler도 설정합니다.
CPU 사용률이 70%를 넘어가면 자동으로 Pod를 늘리는 식입니다. 블랙프라이데이처럼 트래픽이 급증할 때 시스템이 자동으로 확장됩니다.
또한 PodDisruptionBudget도 설정합니다. 클러스터 유지보수 중에도 최소한의 Pod는 항상 실행되도록 보장합니다.
예를 들어 레플리카 3개 중 최소 2개는 항상 가용하도록 설정할 수 있습니다. 김개발 씨는 이제 배포 프로세스가 이해되었습니다.
Helm 차트를 준비하고, values를 환경에 맞게 설정하고, helm install로 배포하고, 모니터링으로 상태를 확인하는 것. 문제가 생기면 helm rollback으로 즉시 되돌릴 수 있다는 점도 안심이 되었습니다.
"이제 다른 마이크로서비스도 같은 방식으로 배포하면 되겠네요!" 김개발 씨가 자신감을 얻었습니다. 박시니어 씨가 고개를 끄덕였습니다.
"맞아요. 이제 트래픽 관리를 배워볼까요?"
실전 팁
💡 - Readiness Probe는 /health/ready 같은 전용 엔드포인트를 만드는 것이 좋습니다
- initialDelaySeconds는 애플리케이션 시작 시간보다 충분히 길게 설정하세요
- 프로덕션에서는 항상 HPA와 PDB를 함께 설정하세요
4. Istio 트래픽 관리
다음 날 김개발 씨는 급한 전화를 받았습니다. "새 버전을 배포했는데 버그가 있어요!
빨리 롤백해야 해요!" 하지만 박시니어 씨는 침착했습니다. "괜찮아요.
Istio로 트래픽을 제어하면 됩니다."
Istio 트래픽 관리는 서비스 메시의 핵심 기능입니다. Virtual Service, Destination Rule, Gateway를 사용해 트래픽을 세밀하게 제어할 수 있습니다.
카나리 배포, A/B 테스트, 서킷 브레이킹, 재시도 정책 등을 코드 수정 없이 설정만으로 구현할 수 있습니다.
다음 코드를 살펴봅시다.
# VirtualService - 트래픽 라우팅 규칙
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: user-service
spec:
hosts:
- user-service
http:
- match:
- headers:
x-user-type:
exact: beta # 베타 유저는 v2로
route:
- destination:
host: user-service
subset: v2
- route: # 나머지는 90% v1, 10% v2 (카나리 배포)
- destination:
host: user-service
subset: v1
weight: 90
- destination:
host: user-service
subset: v2
weight: 10
---
# DestinationRule - 서비스 버전 정의
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: user-service
spec:
host: user-service
trafficPolicy:
connectionPool:
tcp:
maxConnections: 100
http:
http1MaxPendingRequests: 50
maxRequestsPerConnection: 2
outlierDetection: # 서킷 브레이킹
consecutiveErrors: 5
interval: 30s
baseEjectionTime: 30s
subsets:
- name: v1
labels:
version: v1
- name: v2
labels:
version: v2
박시니어 씨가 설명을 시작했습니다. "Istio의 가장 강력한 기능이 바로 트래픽 관리예요." 전통적인 방식에서는 새 버전을 배포하면 모든 트래픽이 즉시 새 버전으로 갑니다.
문제가 생기면 롤백해야 하는데, 이미 많은 사용자가 영향을 받은 후입니다. 더 좋은 방법은 없을까요?
카나리 배포가 답입니다. 새 버전을 배포하되 전체 트래픽의 10%만 보내는 것입니다.
마치 광산에서 카나리아를 먼저 보내 안전을 확인하는 것처럼 말입니다. 문제가 없으면 점진적으로 20%, 50%, 100%로 늘려갑니다.
Istio는 이것을 VirtualService로 구현합니다. VirtualService는 트래픽 라우팅 규칙을 정의하는 리소스입니다.
"user-service로 가는 트래픽 중 90%는 v1으로, 10%는 v2로 보내라"같은 규칙을 작성할 수 있습니다. 더 흥미로운 것은 헤더 기반 라우팅입니다.
예를 들어 베타 테스터들에게만 새 버전을 보여주고 싶다면 어떻게 할까요? x-user-type: beta 헤더가 있는 요청만 v2로 보내도록 설정합니다.
일반 사용자는 v1을 계속 사용하고, 베타 유저만 새 기능을 테스트합니다. 김개발 씨가 직접 VirtualService를 작성해봤습니다.
hosts 필드에 서비스 이름을 지정하고, http 섹션에 라우팅 규칙을 정의했습니다. match 조건으로 헤더를 확인하고, route로 목적지를 지정했습니다.
"잠깐, subset이 뭔가요?" 김개발 씨가 물었습니다. 좋은 질문입니다.
subset은 같은 서비스의 다른 버전을 구분하는 이름입니다. v1, v2처럼 버전을 나타내거나, blue, green처럼 환경을 나타낼 수 있습니다.
실제 Pod를 구분하는 것은 DestinationRule에서 합니다. DestinationRule은 서비스의 하위 집합을 정의하고 트래픽 정책을 적용합니다.
subsets 섹션에서 version: v1 레이블을 가진 Pod들을 v1 subset으로 정의합니다. VirtualService가 "v1으로 보내라"고 하면 실제로는 이 레이블을 가진 Pod들로 트래픽이 전달됩니다.
DestinationRule의 진짜 위력은 트래픽 정책에 있습니다. connectionPool 설정으로 동시 연결 수를 제한할 수 있습니다.
마이크로서비스가 과부하되지 않도록 보호하는 것입니다. 서킷 브레이킹은 더욱 중요합니다.
outlierDetection 설정으로 연속 5번 실패하면 해당 Pod를 30초간 로드 밸런싱에서 제외합니다. 장애가 발생한 Pod로 계속 트래픽을 보내지 않는 것입니다.
마치 전기 회로의 차단기처럼 말이죠. 실제 시나리오를 생각해봅시다.
user-service v2를 배포했는데 메모리 누수 버그가 있었습니다. 시간이 지나면서 Pod가 느려지고 결국 응답 불능 상태가 됩니다.
서킷 브레이킹이 없다면 로드 밸런서가 계속 해당 Pod로 트래픽을 보내 사용자 경험이 나빠집니다. 하지만 서킷 브레이킹이 활성화되어 있으면 어떻게 될까요?
Istio가 연속된 오류를 감지하고 문제 있는 Pod를 격리시킵니다. 정상적인 Pod들만 트래픽을 받아 서비스가 계속 작동합니다.
30초 후에 다시 시도해보고, 여전히 문제가 있으면 다시 격리합니다. "재시도는 어떻게 설정하나요?" 김개발 씨가 궁금해했습니다.
VirtualService에 retries 섹션을 추가하면 됩니다. 일시적인 네트워크 오류나 Pod 재시작 중에 발생한 실패를 자동으로 재시도합니다.
attempts를 3으로 설정하면 최대 3번까지 재시도합니다. perTryTimeout으로 각 시도의 타임아웃도 지정할 수 있습니다.
하지만 조심해야 합니다. 재시도는 양날의 검입니다.
서비스가 이미 과부하 상태인데 계속 재시도하면 문제를 악화시킬 수 있습니다. 멱등성이 보장되는 GET 요청에는 재시도가 안전하지만, POST 요청은 중복 생성 문제가 있을 수 있습니다.
타임아웃 설정도 중요합니다. VirtualService에 timeout 필드로 전체 요청의 타임아웃을 지정합니다.
마이크로서비스 간 호출이 연쇄적으로 일어날 때 한 서비스가 느려지면 전체가 느려집니다. 적절한 타임아웃으로 이를 방지합니다.
김개발 씨는 Gateway도 배웠습니다. Gateway는 클러스터 외부에서 들어오는 트래픽을 처리하는 진입점입니다.
마치 건물의 정문과 같습니다. HTTP, HTTPS, TCP 트래픽을 받아 내부 서비스로 라우팅합니다.
실무에서는 트래픽 미러링도 유용합니다. 프로덕션 트래픽을 복사하여 새 버전으로 보내되 응답은 버립니다.
실제 트래픽으로 새 버전을 테스트하지만 사용자에게는 영향을 주지 않습니다. "이제 안심하고 새 버전을 배포할 수 있겠어요." 김개발 씨가 미소 지었습니다.
점진적 롤아웃, 서킷 브레이킹, 자동 재시도가 있으니 훨씬 안전했습니다.
실전 팁
💡 - 카나리 배포는 10% → 25% → 50% → 100% 순으로 점진적으로 진행하세요
- 서킷 브레이킹의 consecutiveErrors는 너무 낮게 설정하지 마세요 (보통 5-10)
- POST 요청에는 재시도를 신중하게 적용하세요
5. 보안 정책 적용
어느 날 보안팀에서 연락이 왔습니다. "마이크로서비스 간 통신이 전부 암호화되어 있나요?
인증은 어떻게 하고 있죠?" 김개발 씨는 당황했습니다. 코드에 보안 로직을 하나도 추가하지 않았기 때문입니다.
Istio 보안 정책은 서비스 메시 내의 통신을 자동으로 암호화하고 인증/인가를 적용합니다. mTLS로 모든 서비스 간 통신을 암호화하고, Authorization Policy로 세밀한 접근 제어를 구현합니다.
개발자가 코드를 수정할 필요 없이 설정만으로 제로 트러스트 보안을 달성할 수 있습니다.
다음 코드를 살펴봅시다.
# PeerAuthentication - mTLS 활성화
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default
namespace: microservices-platform
spec:
mtls:
mode: STRICT # 모든 통신을 mTLS로 강제
---
# AuthorizationPolicy - 접근 제어
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: user-service-authz
namespace: microservices-platform
spec:
selector:
matchLabels:
app: user-service
action: ALLOW
rules:
- from:
- source:
principals: ["cluster.local/ns/microservices-platform/sa/api-gateway"]
to:
- operation:
methods: ["GET", "POST"]
paths: ["/api/users/*"]
- from:
- source:
namespaces: ["monitoring"]
to:
- operation:
methods: ["GET"]
paths: ["/health/*", "/metrics"]
---
# RequestAuthentication - JWT 검증
apiVersion: security.istio.io/v1beta1
kind: RequestAuthentication
metadata:
name: jwt-auth
namespace: microservices-platform
spec:
selector:
matchLabels:
app: user-service
jwtRules:
- issuer: "https://auth.example.com"
jwksUri: "https://auth.example.com/.well-known/jwks.json"
audiences:
- "user-service-api"
박시니어 씨가 심각한 표정으로 말했습니다. "보안은 선택이 아니라 필수입니다." 전통적인 마이크로서비스 환경에서는 보안이 골칫거리였습니다.
각 서비스가 다른 서비스를 호출할 때마다 인증 로직을 구현해야 했습니다. API 키를 관리하고, TLS 인증서를 갱신하고, 토큰을 검증하는 코드를 작성해야 했습니다.
서비스가 10개만 되어도 관리가 어려워집니다. Istio는 이 문제를 서비스 메시 수준에서 해결합니다.
사이드카 프록시가 모든 보안 작업을 대신 처리합니다. 개발자는 비즈니스 로직에만 집중할 수 있습니다.
가장 중요한 것이 mTLS입니다. mutual TLS의 약자로, 양방향 인증을 의미합니다.
일반적인 HTTPS는 클라이언트가 서버의 인증서를 검증합니다. 하지만 mTLS는 서버도 클라이언트의 인증서를 검증합니다.
서로가 서로를 인증하는 것이죠. Istio는 mTLS를 자동으로 설정합니다.
각 서비스의 사이드카 프록시에 인증서를 자동으로 발급하고, 주기적으로 갱신합니다. 개발자는 인증서 관리를 전혀 신경 쓸 필요가 없습니다.
PeerAuthentication 리소스를 만들어 봅시다. mode를 STRICT로 설정하면 mTLS가 강제됩니다.
암호화되지 않은 통신은 모두 거부됩니다. PERMISSIVE 모드는 mTLS와 평문 통신을 모두 허용합니다.
마이그레이션 중에 유용합니다. "그럼 모든 서비스가 서로 통신할 수 있나요?" 김개발 씨가 물었습니다.
그것이 바로 다음 문제입니다. mTLS는 통신을 암호화하지만 누가 누구와 통신할 수 있는지는 제어하지 않습니다.
이것을 인가라고 합니다. AuthorizationPolicy가 이 역할을 합니다.
제로 트러스트 보안 모델을 생각해봅시다. 기본적으로 모든 것을 거부하고, 명시적으로 허용된 것만 통과시키는 것입니다.
마치 성의 문처럼 말이죠. AuthorizationPolicy로 세밀한 규칙을 만들 수 있습니다.
"api-gateway 서비스만 user-service의 /api/users 경로에 GET, POST 요청을 할 수 있다"같은 규칙입니다. from 섹션에서 출처를, to 섹션에서 대상을 지정합니다.
Service Account가 중요한 역할을 합니다. Kubernetes의 각 Pod는 Service Account를 가집니다.
Istio는 이것을 신원 확인에 사용합니다. principals 필드에서 특정 Service Account만 허용할 수 있습니다.
실무 사례를 봅시다. 결제 서비스는 매우 민감한 서비스입니다.
오직 주문 서비스만 결제 서비스를 호출할 수 있어야 합니다. AuthorizationPolicy를 만들어 source.principals에 order-service의 Service Account만 지정합니다.
다른 모든 서비스는 차단됩니다. 모니터링 시스템은 어떻게 할까요?
Prometheus가 모든 서비스의 /metrics 엔드포인트에 접근해야 합니다. 별도의 규칙을 추가하여 monitoring 네임스페이스에서 오는 GET 요청만 /health와 /metrics 경로에 허용합니다.
RequestAuthentication은 최종 사용자 인증을 처리합니다. JWT 토큰을 검증하는 것입니다.
사용자가 로그인하면 인증 서버가 JWT를 발급합니다. 이후 모든 요청에 이 토큰을 포함합니다.
Istio가 자동으로 토큰을 검증하고, 유효하지 않으면 요청을 거부합니다. jwtRules에서 issuer와 jwksUri를 지정합니다.
Istio는 공개 키를 가져와 토큰 서명을 검증합니다. audiences로 토큰이 어떤 서비스를 대상으로 하는지 확인합니다.
이 모든 것이 애플리케이션 코드와 완전히 분리되어 있습니다. 김개발 씨는 실제로 적용해봤습니다.
PeerAuthentication을 배포하자 즉시 모든 서비스 간 통신이 암호화되었습니다. kubectl exec로 Pod에 접속해 다른 서비스를 직접 호출해보려 했지만 차단되었습니다.
mTLS 인증서가 없었기 때문입니다. 다음으로 AuthorizationPolicy를 단계적으로 적용했습니다.
먼저 ALLOW 정책으로 필요한 통신을 허용하고, 나중에 기본 DENY 정책을 추가했습니다. 순서가 중요합니다.
DENY부터 하면 모든 것이 차단되어 서비스가 작동하지 않습니다. "보안팀이 좋아하겠는데요?" 김개발 씨가 웃었습니다.
실제로 다음 보안 감사에서 높은 점수를 받았습니다. 암호화된 통신, 강력한 인증, 세밀한 인가가 모두 갖춰졌기 때문입니다.
한 가지 주의할 점이 있습니다. 너무 엄격한 정책은 개발을 어렵게 만듭니다.
개발 환경에서는 PERMISSIVE 모드를 사용하고, 스테이징과 프로덕션에서만 STRICT 모드를 적용하는 것이 좋습니다. 또한 정책을 변경할 때는 신중해야 합니다.
AuthorizationPolicy를 잘못 설정하면 서비스가 서로 통신하지 못해 전체 시스템이 마비될 수 있습니다. 항상 테스트 환경에서 먼저 검증하세요.
"이제 정말 엔터프라이즈급 플랫폼 같은데요." 김개발 씨가 뿌듯해했습니다. 박시니어 씨가 고개를 끄덕였습니다.
"마지막으로 모니터링을 설정해봅시다."
실전 팁
💡 - 처음에는 PERMISSIVE 모드로 시작해서 점진적으로 STRICT로 전환하세요
- AuthorizationPolicy는 ALLOW 규칙을 먼저 만들고 나중에 기본 DENY를 추가하세요
- Service Account를 서비스별로 분리하여 세밀한 접근 제어를 구현하세요
6. 모니터링 구성
밤 11시, 김개발 씨의 폰이 울렸습니다. "user-service가 응답하지 않아요!" 하지만 어떤 서비스가 문제인지, 왜 느린지 알 수 없었습니다.
박시니어 씨가 말했습니다. "관측성이 없으면 블랙박스나 마찬가지예요."
마이크로서비스 모니터링은 분산 시스템의 건강 상태를 파악하는 핵심입니다. Prometheus로 메트릭을 수집하고, Grafana로 시각화하며, Jaeger로 분산 추적을 수행하고, Kiali로 서비스 메시를 관찰합니다.
이 네 가지 도구가 모여 완전한 관측성을 제공합니다.
다음 코드를 살펴봅시다.
# ServiceMonitor - Prometheus 메트릭 수집
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: user-service-metrics
namespace: microservices-platform
spec:
selector:
matchLabels:
app: user-service
endpoints:
- port: http
path: /metrics
interval: 30s
---
# Grafana Dashboard ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: user-service-dashboard
namespace: monitoring
data:
dashboard.json: |
{
"dashboard": {
"title": "User Service Metrics",
"panels": [
{
"title": "Request Rate",
"targets": [{
"expr": "rate(istio_requests_total{destination_service=\"user-service\"}[5m])"
}]
},
{
"title": "Error Rate",
"targets": [{
"expr": "rate(istio_requests_total{destination_service=\"user-service\",response_code=~\"5..\"}[5m])"
}]
},
{
"title": "Latency P95",
"targets": [{
"expr": "histogram_quantile(0.95, rate(istio_request_duration_milliseconds_bucket[5m]))"
}]
}
]
}
}
---
# 분산 추적 설정
apiVersion: v1
kind: ConfigMap
metadata:
name: istio-tracing
namespace: istio-system
data:
sampling: "100" # 100% 트레이싱 (프로덕션에서는 1-5% 권장)
박시니어 씨가 화면을 켰습니다. "관측성은 세 가지 기둥으로 이루어져요.
메트릭, 로그, 트레이스입니다." 메트릭은 시스템의 건강 지표입니다. 마치 병원에서 체온, 혈압, 맥박을 재는 것과 같습니다.
요청 수, 에러율, 응답 시간 같은 수치들입니다. 시간에 따라 변화하는 이 숫자들이 시스템의 상태를 보여줍니다.
Istio는 자동으로 풍부한 메트릭을 생성합니다. 각 서비스로 들어오고 나가는 모든 요청을 사이드카 프록시가 관찰하고 측정합니다.
개발자가 코드에 메트릭을 추가할 필요가 없습니다. Prometheus는 이 메트릭들을 수집하고 저장합니다.
마치 센서들이 데이터를 보내면 중앙 데이터베이스가 저장하는 것처럼 말입니다. Prometheus는 주기적으로 각 서비스의 /metrics 엔드포인트를 폴링합니다.
ServiceMonitor 리소스를 만들어 어떤 서비스를 모니터링할지 지정합니다. selector로 레이블이 일치하는 서비스를 자동으로 발견합니다.
새 마이크로서비스를 배포하면 자동으로 모니터링이 시작됩니다. Istio가 제공하는 핵심 메트릭들을 살펴봅시다.
istio_requests_total은 전체 요청 수입니다. destination_service 레이블로 어떤 서비스인지, response_code로 성공/실패를 구분합니다.
istio_request_duration_milliseconds는 응답 시간 히스토그램입니다. 이것으로 P50, P95, P99 지연시간을 계산합니다.
"P95가 뭔가요?" 김개발 씨가 물었습니다. 백분위수입니다.
P95는 95%의 요청이 이 시간 안에 완료된다는 의미입니다. 평균보다 훨씬 유용합니다.
평균은 소수의 느린 요청에 의해 왜곡될 수 있지만, 백분위수는 실제 사용자 경험을 더 잘 반영합니다. Grafana는 이 메트릭들을 아름다운 대시보드로 시각화합니다.
차트와 그래프로 데이터를 표현하여 한눈에 시스템 상태를 파악할 수 있습니다. 김개발 씨는 Grafana에 로그인하여 대시보드를 만들었습니다.
첫 번째 패널에는 요청률을 표시했습니다. rate 함수로 초당 요청 수를 계산합니다.
두 번째 패널에는 에러율을 표시했습니다. response_code가 5xx인 요청의 비율입니다.
세 번째 패널에는 P95 지연시간을 표시했습니다. 대시보드가 완성되자 전체 시스템이 눈에 들어왔습니다.
어떤 서비스가 많은 트래픽을 받는지, 어디서 에러가 발생하는지, 어떤 엔드포인트가 느린지 한눈에 보였습니다. 하지만 메트릭만으로는 부족합니다.
"user-service가 느린 건 알겠는데, 왜 느린 거죠?" 이 질문에 답하려면 분산 추적이 필요합니다. Jaeger가 등장합니다.
분산 추적은 요청이 여러 마이크로서비스를 거쳐가는 전체 여정을 추적합니다. 마치 택배의 배송 추적처럼 각 단계를 기록합니다.
사용자가 주문을 생성하는 시나리오를 봅시다. API Gateway → Order Service → User Service → Payment Service → Inventory Service 순으로 호출됩니다.
전체 요청이 2초 걸렸다면, 어느 단계에서 시간이 소요되었을까요? Jaeger UI에서 트레이스를 열어보면 폭포수 차트가 나타납니다.
API Gateway 100ms, Order Service 200ms, User Service 50ms, Payment Service 1500ms, Inventory Service 150ms. 아하, Payment Service가 병목입니다!
Istio는 자동으로 분산 추적을 지원합니다. 각 사이드카가 트레이스 ID를 생성하고 전파합니다.
개발자가 할 일은 HTTP 헤더를 전달하는 것뿐입니다. x-request-id, x-b3-traceid 같은 헤더를 다음 서비스 호출에 포함시킵니다.
샘플링 비율도 중요합니다. 모든 요청을 추적하면 오버헤드가 큽니다.
프로덕션에서는 보통 1-5%만 샘플링합니다. 하지만 개발 환경에서는 100% 샘플링하여 모든 요청을 분석할 수 있습니다.
Kiali는 서비스 메시 전용 관측성 도구입니다. 서비스 간 의존성을 그래프로 시각화합니다.
어떤 서비스가 어떤 서비스를 호출하는지 화살표로 연결됩니다. 트래픽 양도 선의 굵기로 표현됩니다.
김개발 씨는 Kiali를 열어봤습니다. 전체 마이크로서비스 아키텍처가 한눈에 들어왔습니다.
빨간 화살표는 에러가 발생하는 통신을 의미했습니다. User Service에서 Database로 가는 화살표가 빨갔습니다.
클릭해보니 연결 타임아웃이 발생하고 있었습니다. 알람도 설정해야 합니다.
문제가 생겼을 때 자동으로 알려주는 것입니다. Prometheus의 AlertManager를 사용합니다.
PromQL로 조건을 정의하고, 슬랙이나 이메일로 알림을 보냅니다. 중요한 알람 규칙들을 만들어 봅시다.
첫째, 에러율이 5%를 넘으면 알람. 둘째, P95 지연시간이 1초를 넘으면 알람.
셋째, Pod의 CPU 사용률이 80%를 넘으면 알람. 이 세 가지만 있어도 대부분의 문제를 조기에 발견할 수 있습니다.
로그도 중요합니다. 메트릭과 트레이스는 "무엇이" 일어났는지 보여주지만, 로그는 "왜" 일어났는지 설명합니다.
ELK 스택(Elasticsearch, Logstash, Kibana)이나 Loki를 사용해 로그를 중앙 집중화합니다. 구조화된 로그를 작성하는 것이 중요합니다.
JSON 형식으로 로그를 출력하면 나중에 검색하고 분석하기 쉽습니다. trace_id를 로그에 포함시키면 특정 요청의 모든 로그를 추적할 수 있습니다.
김개발 씨는 이제 시스템을 완전히 파악할 수 있었습니다. Grafana 대시보드로 전체 상태를 모니터링하고, 문제가 발생하면 Jaeger로 원인을 추적하고, Kiali로 서비스 의존성을 확인하고, Kibana로 상세 로그를 분석합니다.
"다음에 또 밤 11시에 전화 오면 어떻게 하죠?" 김개발 씨가 걱정했습니다. 박시니어 씨가 웃었습니다.
"이제는 알람이 먼저 와서 문제를 예방할 수 있어요." 실제로 다음 주, Grafana 알람이 울렸습니다. Payment Service의 에러율이 증가하고 있었습니다.
하지만 이번에는 사용자가 문제를 느끼기 전에 발견했습니다. Jaeger로 추적해보니 외부 결제 API가 느려진 것이었습니다.
타임아웃을 조정하고 재시도 로직을 개선하여 문제를 해결했습니다. "관측성이 있으니 정말 안심되네요." 김개발 씨가 만족스러워했습니다.
이제 마이크로서비스 플랫폼이 완성되었습니다. Kubernetes로 오케스트레이션하고, Istio로 트래픽을 관리하고, 보안을 강화하고, 모니터링으로 건강을 지킵니다.
실전 팁
💡 - 프로덕션에서는 Jaeger 샘플링을 1-5%로 설정해 오버헤드를 줄이세요
- 알람은 실행 가능한 것만 설정하세요 (너무 많으면 무시하게 됩니다)
- 트레이스 ID를 로그에 포함시켜 메트릭-트레이스-로그를 연결하세요
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
VPC 네트워크의 기초 - CIDR과 서브넷 설계 완벽 가이드
초급 개발자를 위한 VPC와 서브넷 설계 입문서입니다. 도서관 비유로 CIDR 개념을 쉽게 이해하고, 실무에서 자주 사용하는 서브넷 분할 전략을 단계별로 배워봅니다. 점프 투 자바 스타일로 술술 읽히는 네트워크 입문 가이드입니다.
AWS 리소스 정리와 비용 관리 완벽 가이드
AWS 사용 후 리소스를 안전하게 정리하고 예상치 못한 과금을 방지하는 방법을 배웁니다. 프리티어 관리부터 비용 모니터링까지 실무에서 꼭 필요한 내용을 다룹니다.
AWS 고가용성과 내결함성 아키텍처 설계 기초
서비스가 멈추지 않는 시스템을 만들고 싶으신가요? AWS의 글로벌 인프라를 활용한 고가용성과 내결함성 아키텍처 설계 원칙을 실무 중심으로 배워봅시다. 초급 개발자도 쉽게 이해할 수 있도록 스토리와 비유로 풀어냈습니다.
Kubernetes 프로덕션 완벽 가이드
실전에서 꼭 알아야 할 Kubernetes 프로덕션 고려사항을 단계별로 학습합니다. 리소스 관리부터 보안, 모니터링까지 현업에서 사용하는 필수 설정들을 다룹니다.
오토스케일링 완벽 가이드
트래픽 변화에 자동으로 대응하는 오토스케일링의 모든 것을 배웁니다. HPA, VPA, Cluster Autoscaler까지 실전 예제와 함께 쉽게 설명합니다. 초급 개발자도 술술 읽히는 실무 중심 가이드입니다.