AWS EKS에서 엔드투엔드 마이크로서비스 배포: Jenkins, Docker, Kubernetes 및 Argo CD를 활용한 CI/CD

발행: (2025년 12월 27일 오전 09:12 GMT+9)
9 분 소요
원문: Dev.to

Source: Dev.to

위의 링크에 있는 전체 글을 번역하려면, 번역하고자 하는 텍스트(본문)를 제공해 주세요.
본문을 그대로 복사해서 보내 주시면, 마크다운 형식과 코드 블록, URL은 그대로 유지하면서 한국어로 번역해 드리겠습니다.

사용자–주문 마이크로서비스 애플리케이션

아키텍처 개요

두 개의 서비스(User ServiceOrder Service)로 구성된 간단한 시스템입니다. 두 서비스 모두 Spring Boot으로 구축하고 Docker 이미지로 패키징한 뒤 Amazon EKS 클러스터에 배포합니다.

프로젝트 구조 (학습용 모노레포)

microservices-project/
├─ user-service/
│  ├─ src/
│  └─ Dockerfile
├─ order-service/
│  ├─ src/
│  └─ Dockerfile
└─ k8s/
   ├─ db-deployment.yaml
   ├─ user-deployment.yaml
   ├─ order-deployment.yaml
   └─ ingress.yaml

Spring Boot 예시 스니펫

@Entity
public class User {
    @Id @GeneratedValue
    private Long id;
    // …
}
@RestController
@RequestMapping("/users")
public class UserController {
    @PostMapping
    public ResponseEntity create(@RequestBody User user) { … }

    @GetMapping("/{id}")
    public ResponseEntity get(@PathVariable Long id) { … }

    @GetMapping("/health")
    public String health() { return "OK"; }
}
@RestController
@RequestMapping("/orders")
public class OrderController {
    @Autowired
    private RestTemplate restTemplate;

    @PostMapping
    public ResponseEntity create(@RequestBody Order order) {
        // Call user service
        String url = "http://user-service:8080/users/" + order.getUserId();
        restTemplate.getForObject(url, User.class);
        // …
    }
}

빌드 – 두 서비스 도커화

# user-service/Dockerfile
FROM openjdk:17-jdk-slim
WORKDIR /app
COPY target/user-service.jar app.jar
ENTRYPOINT ["java","-jar","/app/app.jar"]
# order-service/Dockerfile
FROM openjdk:17-jdk-slim
WORKDIR /app
COPY target/order-service.jar app.jar
ENTRYPOINT ["java","-jar","/app/app.jar"]
# Build and tag images
mvn clean package -f user-service/pom.xml
docker build -t user-service:1.0 ./user-service

mvn clean package -f order-service/pom.xml
docker build -t order-service:1.0 ./order-service

쿠버네티스 – 데이터베이스 배포

# k8s/db-deployment.yaml
apiVersion: v1
kind: Service
metadata:
  name: postgres
spec:
  ports:
    - port: 5432
  selector:
    app: postgres
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: postgres
spec:
  replicas: 1
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
        - name: postgres
          image: postgres:13
          env:
            - name: POSTGRES_DB
              value: microservices
            - name: POSTGRES_USER
              value: admin
            - name: POSTGRES_PASSWORD
              value: secret
          ports:
            - containerPort: 5432

쿠버네티스 – 사용자 서비스 배포

# k8s/user-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: user-service
spec:
  replicas: 2
  selector:
    matchLabels:
      app: user-service
  template:
    metadata:
      labels:
        app: user-service
    spec:
      containers:
        - name: user-service
          image: user-service:1.0
          ports:
            - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: user-service
spec:
  selector:
    app: user-service
  ports:
    - port: 8080
      targetPort: 8080

Kubernetes – 주문 서비스 배포

# k8s/order-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
spec:
  replicas: 2
  selector:
    matchLabels:
      app: order-service
  template:
    metadata:
      labels:
        app: order-service
    spec:
      containers:
        - name: order-service
          image: order-service:1.0
          ports:
            - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: order-service
spec:
  selector:
    app: order-service
  ports:
    - port: 8080
      targetPort: 8080

Ingress – 단일 진입점

# k8s/ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: microservices-ingress
spec:
  rules:
    - http:
        paths:
          - path: /users
            pathType: Prefix
            backend:
              service:
                name: user-service
                port:
                  number: 8080
          - path: /orders
            pathType: Prefix
            backend:
              service:
                name: order-service
                port:
                  number: 8080

전체 배포

kubectl apply -f k8s/

기능 증명 (중요 테스트)

curl -X POST http:///orders \
  -H "Content-Type: application/json" \
  -d '{"userId":1,"productId":42,"quantity":2}'

롤백 시나리오

# Revert to previous Git commit that contains the older manifests
git revert 
# Argo CD (or kubectl) will apply the reverted state automatically

프로덕션‑급 개선

  • 헬스 체크와 레디니스 프로브를 추가합니다.
  • 리소스 제한 및 요청을 활성화합니다.
  • 로깅/메트릭을 위한 사이드카 사용 (예: Prometheus exporter).
  • 비밀을 AWS Secrets Manager 또는 KMS로 암호화된 Kubernetes Secrets에 저장합니다.
  • 블루‑그린 또는 카나리 배포를 구현합니다 (아래 참고).

Part 2 – 전체 CI 파이프라인 (Jenkins)

pipeline {
    agent any
    environment {
        REGISTRY   = 'your-registry.io'
        IMAGE_TAG  = "${env.BUILD_NUMBER}"
    }
    stages {
        stage('Checkout') {
            steps {
                git branch: 'main',
                    url: 'https://github.com/your-org/microservices-project.git'
            }
        }
        stage('Build User Service') {
            steps {
                dir('user-service') {
                    sh 'mvn clean package'
                }
            }
        }
        stage('Build Order Service') {
            steps {
                dir('order-service') {
                    sh 'mvn clean package'
                }
            }
        }
        stage('Docker Build') {
            steps {
                sh '''
                docker build -t $REGISTRY/user-service:$IMAGE_TAG user-service/
                docker build -t $REGISTRY/order-service:$IMAGE_TAG order-service/
                '''
            }
        }
        stage('Docker Push') {
            steps {
                withDockerRegistry([credentialsId: 'dockerhub-creds', url: '']) {
                    sh '''
                    docker push $REGISTRY/user-service:$IMAGE_TAG
                    docker push $REGISTRY/order-service:$IMAGE_TAG
                    '''
                }
            }
        }
        stage('Update K8s Manifests Repo') {
            steps {
                sh '''
                git clone https://github.com/your-org/k8s-manifests.git
                cd k8s-manifests
                sed -i "s|image:.*user-service.*|image: $REGISTRY/user-service:$IMAGE_TAG|" user-deployment.yaml
                sed -i "s|image:.*order-service.*|image: $REGISTRY/order-service:$IMAGE_TAG|" order-deployment.yaml
                git commit -am "Update images to $IMAGE_TAG"
                git push
                '''
            }
        }
    }
}

파이프라인은 고전적인 흐름을 따릅니다: Trigger → Checkout → Build → Test (생략) → Docker Build → Docker Push → Update Git → CD via Argo CD.

Part 3 – Argo CD (GitOps 배포)

Install Argo CD

kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
kubectl port-forward svc/argocd-server -n argocd 8080:443

초기 admin 비밀번호 가져오기:

kubectl get secret argocd-initial-admin-secret -n argocd -o jsonpath="{.data.password}" | base64 -d

Connect Argo CD to the Manifests Repository

# application.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: microservices-app
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/your-org/k8s-manifests.git
    targetRevision: HEAD
    path: .
  destination:
    server: https://kubernetes.default.svc
    namespace: default
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

애플리케이션 적용:

kubectl apply -f application.yaml

Argo CD는 Git 매니페스트와 실시간 클러스터 상태를 지속적으로 조정하며, 롤아웃, 롤백 및 헬스 체크를 자동으로 처리합니다.

Automatic Operations

  • Sync: kubectl apply -f k8s/ → Argo CD가 드리프트를 감지하고 동기화합니다.
  • Rollback: git revert → Argo CD가 클러스터를 이전 상태로 되돌립니다.
  • Status: kubectl get pods 또는 Argo CD UI를 사용합니다.

Source:

Argo CD를 활용한 블루‑그린 및 카나리 배포

블루‑그린 배포 (Zero Downtime)

  1. “그린” 배포(새 버전)를 기존 “블루” 배포와 함께 생성합니다.
# user-service-green.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: user-service-green
spec:
  replicas: 2
  selector:
    matchLabels:
      app: user-service
      version: green
  template:
    metadata:
      labels:
        app: user-service
        version: green
    spec:
      containers:
        - name: user-service
          image: your-registry.io/user-service:2.0
          ports:
            - containerPort: 8080
  1. 서비스 셀렉터는 초기에는 블루 버전을 가리키도록 설정합니다.
# user-service-service.yaml
apiVersion: v1
metadata:
  name: user-service
spec:
  selector:
    app: user-service
    version: blue   # 트래픽 전환 시 "green"으로 변경
  ports:
    - port: 8080
      targetPort: 8080
  1. Git에서 셀렉터를 업데이트하여 트래픽을 전환합니다.
selector:
  app: user-service
  version: green
  1. 커밋 & 푸시 → Argo CD가 동기화 → 포드 재시작 없이 즉시 트래픽이 이동합니다.
  2. 롤백: 셀렉터 변경을 되돌리기(git revert) 하면 Argo CD가 블루 배포를 복구합니다.

카나리 배포 (점진적 롤아웃)

  1. 작은 복제본 수(예: 1 pod ≈ 10 % 트래픽)로 카나리 배포를 생성합니다.
# user-service-canary.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: user-service-canary
spec:
  replicas: 1
  selector:
    matchLabels:
      app: user-service
      version: canary
  template:
    metadata:
      labels:
        app: user-service
        version: canary
    spec:
      containers:
        - name: user-service
          image: your-registry.io/user-service:2.0
          ports:
            - containerPort: 8080
  1. 서비스 셀렉터에 안정 버전과 카나리 포드 모두를 포함시킵니다(두 버전을 모두 매칭하는 라벨 셀렉터 사용).
selector:
  app: user-service
  1. Git에서 매니페스트를 편집하여 카나리 복제본 수를 점진적으로 늘립니다(예: 2 → 3 pod). Argo CD가 변경 사항을 적용합니다.

  2. 안정 버전으로 승격: 신뢰도가 충분히 높아지면 안정 배포의 이미지를 새 버전으로 교체하고 카나리 배포를 삭제합니다.

  3. 롤백: 카나리 복제본을 0으로 스케일 다운(kubectl scale deployment user-service-canary --replicas=0)하거나 카나리를 도입한 Git 커밋을 되돌립니다.

Back to Blog

관련 글

더 보기 »