13년차의 서버실

서버, 인프라, 홈랩 기술 블로그

[태그:] DevOps

  • [Kubernetes] StatefulSet vs Deployment: 상태 저장 애플리케이션 배포 비교

    [Kubernetes] StatefulSet vs Deployment: 상태 저장 애플리케이션 배포 비교

    [Kubernetes] Kubernetes StatefulSet vs Deployment 비교 분석

    Kubernetes StatefulSet vs Deployment를 처음 제대로 구분하게 된 건, 홈랩에서 데이터베이스를 올렸다가 볼륨이 꼬이면서 한참 삽질했을 때였습니다. 처음엔 둘 다 그냥 Pod(파드)를 여러 개 띄우는 Kubernetes 워크로드(workload, 애플리케이션 실행 단위)겠거니 싶었거든요. 근데 실제로 운영해보니까 차이가 꽤 크더라고요. 특히 상태 저장 애플리케이션(stateful application, 데이터와 식별성이 중요한 앱) 쪽은 정말 그랬습니다. 웹 API처럼 가볍게 교체되는 애플리케이션 배포는 Deployment(디플로이먼트)가 정말 편한데, MySQL이나 PostgreSQL 같은 건 접근을 잘못하면 나중에 복구할 때 진짜 식은땀이 나더라고요.

    혹시 이런 경험 있으신가요? 애플리케이션은 잘 떴는데, 재시작 후 데이터 경로가 달라지거나 Pod 이름이 바뀌면서 클러스터 구성이 꼬이는 상황 말입니다. 여기서 중요한 포인트! Kubernetes StatefulSet vs Deployment는 단순히 생성 방식만 다른 게 아니라, 애플리케이션의 정체성(identity), 저장소(storage), 배포 순서(ordering)를 다루는 철학 자체가 다릅니다.

    이번 글에서는 제가 직접 홈랩에서 써보며 정리한 기준으로, Kubernetes StatefulSet vs Deployment 차이를 실무 감각으로 풀어보겠습니다. 단순 정의만 보면 헷갈리기 쉬우니까, 실제 YAML 예제와 트러블슈팅까지 같이 보시죠. 이전 글에서 다룬 Persistent Volume(PV, 영구 볼륨)과 StorageClass(스토리지 클래스) 내용을 같이 보시면 이해가 더 빨라집니다. 다음 글에서는 Helm(헬름, 쿠버네티스 패키지 매니저)으로 상태 저장 애플리케이션 배포 자동화하는 방법도 다룰 예정입니다.

    Kubernetes StatefulSet vs Deployment 구조 비교 아키텍처 이미지

    Deployment와 StatefulSet이 Pod, Service, Volume을 어떻게 다르게 다루는지 한눈에 보는 개요 이미지입니다.

    1. 왜 Kubernetes StatefulSet vs Deployment가 중요한가

    쉽게 말해 Deployment는 언제든 교체 가능한 복제본을 잘 다루고, StatefulSet은 각 인스턴스가 자기 이름과 저장소를 유지해야 하는 경우를 잘 다룹니다.

    예를 들어 보겠습니다.

    • 웹 프론트엔드나 API 서버는 Pod가 하나 죽어도 새 Pod가 뜨면 보통 괜찮습니다.
    • 반면 데이터베이스나 메시지 큐(message queue, 비동기 메시지 처리 시스템)는 각 인스턴스가 가진 데이터와 순서가 중요거든요.
    • 캐시 서버도 단일 노드냐 클러스터 구성이냐에 따라 선택이 달라집니다.

    제가 처음엔 Redis를 무조건 Deployment로 올렸었는데요. 단일 캐시 테스트 용도에선 괜찮았는데, 나중에 persistent volume을 붙이고 노드 재스케줄링이 일어나니까 예상과 다르게 동작하는 부분이 있었어요. 그때 느낀 게, 상태 저장 애플리케이션은 살아 있는 데이터만 보는 게 아니라, 재시작 이후의 동일성까지 봐야 한다는 점이었습니다.

    2. 개념부터 쉽게 정리해보겠습니다

    2-1. Deployment란?

    Deployment는 ReplicaSet(레플리카셋, 동일 Pod 복제 관리)을 통해 여러 Pod를 선언적으로 관리하는 방식입니다. 주 목적은 무중단에 가깝게 애플리케이션 배포를 반복 가능하게 만드는 것입니다.

    • Pod 이름은 매번 바뀔 수 있습니다.
    • 특정 Pod가 죽어도 새 Pod가 대체되면 됩니다.
    • Rolling Update(롤링 업데이트, 순차 교체)가 편하죠.
    • 웹 서버, API 서버, 워커(worker, 백그라운드 작업 프로세스)에 잘 맞습니다.

    2-2. StatefulSet이란?

    StatefulSet은 이름 그대로 상태(state)를 가진 워크로드를 위한 리소스입니다. 각 Pod가 고정된 네트워크 식별자와 안정적인 스토리지 연결을 유지하도록 설계되어 있거든요.

    • Pod 이름이 ordinal(순번) 기반으로 고정됩니다. 예: db-0, db-1
    • 생성/삭제 순서가 제어됩니다.
    • 각 Pod마다 독립적인 PersistentVolumeClaim(PVC, 영구 볼륨 요청)이 붙을 수 있습니다.
    • 데이터베이스, ZooKeeper, Kafka 계열, 복제 구조가 있는 저장소에 자주 씁니다.

    저도 처음엔 이게 뭔가 싶었는데, 실제로 써보니까 StatefulSet은 Pod를 복제한다기보다, 번호가 붙은 개별 인스턴스를 관리한다는 느낌으로 이해하는 게 가장 쉽더라고요.

    3. 핵심 차이점 비교: Kubernetes StatefulSet vs Deployment

    항목 Deployment StatefulSet
    주 용도 Stateless 애플리케이션 배포 Stateful 애플리케이션 배포
    Pod 이름 가변적 고정적 순번 부여
    스토리지 공유 또는 외부 스토리지 중심 Pod별 고정 스토리지 할당 가능
    생성/종료 순서 순서 보장 약함 순차 생성, 순차 종료
    네트워크 식별성 Pod 교체 시 변경 가능 안정적인 DNS 이름 유지
    대표 사용 사례 웹, API, 배치 워커 DB, 분산 저장소, 클러스터형 메시지 시스템

    여기서 독자분들이 가장 헷갈리는 부분이 스토리지입니다. Deployment에도 PVC를 붙일 수는 있습니다. 그래서 겉보기엔 둘 차이가 없어 보일 수 있어요. 근데 StatefulSet은 각 Pod가 자기 볼륨을 안정적으로 유지해야 하는 구조를 전제로 설계되어 있거든요. 이 차이가 운영 중엔 꽤 크게 작용합니다.

    3-1. 언제 Deployment를 선택하나

    • Pod가 교체돼도 서비스만 유지되면 되는 경우
    • 세션 상태를 외부 Redis나 DB에 저장하는 경우
    • 스케일 아웃/인 빈도가 잦은 경우
    • CI/CD로 잦은 애플리케이션 배포가 필요한 경우

    3-2. 언제 StatefulSet을 선택하나

    • 각 인스턴스마다 고유 ID가 필요한 경우
    • 볼륨을 Pod별로 안정적으로 유지해야 하는 경우
    • 클러스터 합류 순서나 부팅 순서가 중요한 경우
    • 상태 저장 애플리케이션 특성상 재시작 후에도 동일한 엔드포인트가 필요한 경우

    실제로 써보니까, 애매하면 먼저 데이터의 소유권이 누구에게 붙는지를 보면 판단이 쉽습니다. 데이터가 서비스 전체에 느슨하게 연결되면 Deployment 쪽, 데이터가 특정 인스턴스에 강하게 묶이면 StatefulSet 쪽이더라고요.

    4. 실전 구현 1: Deployment로 Stateless 앱 배포

    먼저 Nginx(엔진엑스, 웹 서버)를 Deployment로 배포해보겠습니다. 이 예제는 구조를 이해하기 위한 가장 기본적인 형태입니다.

    1. Deployment 매니페스트를 작성합니다.
    2. Service(서비스, 네트워크 접근 추상화)로 노출합니다.
    3. 롤링 업데이트와 스케일링을 확인합니다.
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: web-deployment
    spec:
      replicas: 3
      selector:
        matchLabels:
          app: web
      template:
        metadata:
          labels:
            app: web
        spec:
          containers:
            - name: nginx
              image: nginx:stable
              ports:
                - containerPort: 80
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: web-service
    spec:
      selector:
        app: web
      ports:
        - port: 80
          targetPort: 80
      type: ClusterIP
    kubectl apply -f web-deployment.yaml
    kubectl get deploy,pods,svc -o wide

    이 상태에서 Pod 하나가 내려가도 새 Pod가 올라오면 됩니다. 이름이 바뀌어도 크게 문제되지 않죠. 바로 이 특성이 Deployment의 장점입니다.

    업데이트도 간단합니다.

    kubectl set image deployment/web-deployment nginx=nginx:latest
    kubectl rollout status deployment/web-deployment

    제가 직접 해보니 테스트 환경이나 프론트엔드 계층은 거의 이 패턴으로 끝나더라고요. 단순하고, 빠르고, 운영 피로도가 낮습니다. 드디어 됐다! 싶은 순간이 자주 오는 쪽이죠.

    Kubernetes StatefulSet vs Deployment 중 Deployment 롤링 업데이트 설명 이미지

    Deployment가 ReplicaSet과 함께 Pod를 순차 교체하는 흐름을 설명하는 이미지입니다.

    5. 실전 구현 2: StatefulSet으로 상태 저장 애플리케이션 배포

    이번엔 StatefulSet 예제를 보겠습니다. 여기서는 이해를 위해 간단한 Nginx 이미지를 쓰되, 핵심은 고정된 Pod 이름과 volumeClaimTemplates 구조를 보는 데 있습니다. 실제 운영에서는 MySQL, PostgreSQL, Redis 클러스터, RabbitMQ 같은 워크로드에서 더 의미가 커요.

    StatefulSet은 보통 Headless Service(헤드리스 서비스, 개별 Pod 식별을 위한 서비스)와 함께 사용합니다.

    apiVersion: v1
    kind: Service
    metadata:
      name: web-headless
    spec:
      clusterIP: None
      selector:
        app: web-stateful
      ports:
        - port: 80
          name: http
    ---
    apiVersion: apps/v1
    kind: StatefulSet
    metadata:
      name: web-stateful
    spec:
      serviceName: web-headless
      replicas: 3
      selector:
        matchLabels:
          app: web-stateful
      template:
        metadata:
          labels:
            app: web-stateful
        spec:
          containers:
            - name: nginx
              image: nginx:stable
              ports:
                - containerPort: 80
                  name: http
              volumeMounts:
                - name: web-data
                  mountPath: /usr/share/nginx/html
      volumeClaimTemplates:
        - metadata:
            name: web-data
          spec:
            accessModes: ["ReadWriteOnce"]
            resources:
              requests:
                storage: 1Gi
    kubectl apply -f web-statefulset.yaml
    kubectl get statefulset,pods,pvc,svc

    적용 후 보면 Pod가 이런 식으로 생성됩니다.

    • web-stateful-0
    • web-stateful-1
    • web-stateful-2

    그리고 PVC도 Pod별로 따로 붙습니다. 이게 진짜 중요합니다. 예를 들어 web-data-web-stateful-0 같은 식으로 각 인스턴스가 자기 스토리지를 계속 들고 가거든요.

    여기서 중요한 포인트! StatefulSet은 삭제 후 다시 생성되어도 같은 이름 규칙과 볼륨 연결을 유지하는 데 초점이 있습니다. 이 특성 덕분에 상태 저장 애플리케이션 운영이 가능해집니다.

    kubectl delete pod web-stateful-1
    kubectl get pods -w

    이렇게 해보면 동일한 ordinal을 가진 Pod가 다시 올라옵니다. 제가 홈랩에서 PostgreSQL 실험할 때도 이 패턴 덕분에 노드 교체 후 구조를 이해하기 쉬웠습니다. 물론 데이터 정합성은 애플리케이션 레벨에서 별도로 봐야 하지만요.

    6. ⚠️ 실제로 많이 겪는 문제와 트러블슈팅

    6-1. Deployment에 데이터베이스를 올리고 나중에 후회하는 경우

    이거 진짜 자주 봅니다. 처음엔 빠르게 띄우려고 Deployment로 시작하거든요. 근데 운영 중에 Pod가 교체되고, 스토리지 붙는 방식이 예상과 다르면 문제를 마주하게 됩니다.

    • Pod 이름이 바뀌어 클러스터 노드 인식이 꼬임
    • 단일 PVC 공유 구조가 애플리케이션 특성과 맞지 않음
    • 복제본 간 데이터 소유권이 불명확해짐

    해결 방향: 데이터가 인스턴스별로 귀속되는 구조라면 StatefulSet으로 전환을 검토해야 합니다.

    6-2. Headless Service를 빼먹는 경우

    저도 처음엔 왜 서비스가 꼭 필요하지? 했었는데, StatefulSet에서 안정적인 네트워크 식별성을 얻으려면 Headless Service가 사실상 핵심이거든요.

    kubectl get svc web-headless
    kubectl describe statefulset web-stateful

    Pod 간 통신이나 클러스터 초기화가 필요한 앱이라면 이 부분을 꼭 확인하세요.

    6-3. PVC가 남는 걸 보고 당황하는 경우

    StatefulSet을 줄였는데 볼륨이 바로 안 지워져서 당황하는 경우가 있습니다. 근데 이건 오히려 안전장치에 가깝습니다. 실수로 데이터가 날아가면 더 큰일이거든요.

    주의: StatefulSet 삭제가 곧 데이터 삭제를 의미하지는 않습니다. 스토리지 정책과 reclaim policy를 같이 확인해야 합니다.

    6-4. 순서 의존성을 무시하고 병렬처럼 다루는 경우

    StatefulSet은 생성과 종료 순서가 의미가 있습니다. 특히 클러스터형 데이터베이스나 합의 기반 시스템은 더 그렇습니다. 그래서 readiness probe, startup probe 같은 헬스체크도 같이 설계해야 하죠. 저도 이걸 대충 봤다가 부팅 순서 꼬여서 한참 로그만 들여다봤습니다 ㅎㅎ

    Kubernetes StatefulSet vs Deployment 중 StatefulSet 스토리지 구조 이미지

    StatefulSet에서 각 Pod가 독립적인 영구 볼륨과 DNS 이름을 갖는 구조를 설명하는 이미지입니다.

    7. 검증 방법: 내가 만든 배포가 의도대로 동작하는지 확인

    설정이 끝났으면 꼭 검증해야 합니다. YAML만 맞다고 끝이 아니더라고요. 실제로 재시작, 스케일링, 이름 유지 여부를 봐야 합니다.

    7-1. Deployment 검증

    kubectl get deployment web-deployment
    kubectl rollout history deployment/web-deployment
    kubectl scale deployment web-deployment --replicas=5
    kubectl get pods -l app=web
    • Pod 수가 바로 조정되는지
    • 업데이트 중 서비스 중단이 없는지
    • 새 Pod 이름이 생성되어도 서비스 접근이 유지되는지

    7-2. StatefulSet 검증

    kubectl get statefulset web-stateful
    kubectl get pods -l app=web-stateful
    kubectl get pvc
    kubectl scale statefulset web-stateful --replicas=2
    kubectl get pods,pvc
    • Pod가 역순으로 종료되는지
    • 줄였다가 다시 늘렸을 때 ordinal이 유지되는지
    • PVC가 각 Pod에 맞게 보존되는지

    🎉 여기서 원하는 대로 동작하면 거의 감이 옵니다. Deployment는 교체 가능한 인스턴스 관리에 최적화되어 있고, StatefulSet은 상태와 순서를 존중하는 구조라는 점이 실제 결과에서 드러납니다.

    Kubernetes StatefulSet vs Deployment 검증 결과 대시보드 이미지

    배포 후 Pod, PVC, 롤아웃 상태를 검증하는 운영 화면 느낌의 이미지입니다.

    8. 자주 묻는 질문과 정리

    8-1. Kubernetes StatefulSet vs Deployment: PVC를 붙이면 차이가 없나요?

    비슷해 보일 수는 있지만 다릅니다. 핵심은 Pod별 고정 정체성과 순서 보장입니다. PVC 하나 붙였다고 StatefulSet의 운영 특성이 생기지는 않습니다.

    8-2. 모든 데이터베이스는 무조건 StatefulSet인가요?

    대체로 그렇지만, 실제 운영 구조에 따라 외부 매니지드 데이터베이스를 쓰면 Kubernetes 안에 직접 올리지 않을 수도 있습니다. 즉, 워크로드 선택은 애플리케이션 구조 전체를 봐야 합니다.

    8-3. 캐시는 어떤 걸 써야 하나요?

    단순 캐시, 세션 캐시처럼 날아가도 되는 구조는 Deployment가 편합니다. 반면 복제, 영속성, 노드 식별이 중요해지면 StatefulSet 쪽을 봐야 합니다.

    8-4. 결국 어떤 기준으로 결정하면 되나요?

    1. Pod가 바뀌어도 되는가?
    2. 각 인스턴스가 자기 데이터를 가져야 하는가?
    3. 이름과 네트워크 식별성이 유지되어야 하는가?
    4. 생성/종료 순서가 중요한가?

    이 네 가지에 하나라도 강하게 해당되면 StatefulSet을 우선 검토해보세요.

    정리해보겠습니다.

    • Deployment: 빠르고 유연한 애플리케이션 배포, stateless 워크로드에 적합
    • StatefulSet: 상태 저장 애플리케이션, 고정 이름, 고정 스토리지, 순서 제어에 적합

    저도 처음엔 Kubernetes StatefulSet vs Deployment를 너무 단순하게 봤었는데, 실제로 운영해보니까 선택이 잘못되면 나중에 구조를 다시 뜯어고쳐야 하더라고요. 특히 홈랩처럼 이것저것 실험하는 환경에서는 처음부터 정답을 맞히기 어렵습니다. 그래서 더더욱 워크로드의 상태 특성을 먼저 보는 습관이 중요합니다.

    💡 팁 하나 남기자면, 처음 설계할 때는 “이 Pod가 내일 사라져도 괜찮은가?”를 스스로에게 물어보세요. 괜찮으면 Deployment일 가능성이 높고, 안 괜찮으면 StatefulSet을 봐야 합니다.

    마지막으로, 다음 글에서는 StatefulSet 기반 데이터베이스를 백업/복구 관점에서 어떻게 설계하면 좋은지 다뤄보겠습니다. 그 글까지 같이 보시면 Kubernetes 워크로드 선택 감이 훨씬 또렷해지실 겁니다.

    어떤 워크로드에 Deployment를 쓰고, 어떤 경우 StatefulSet을 써야 하는지 요약한 비교 이미지입니다.

  • [보안] HashiCorp Vault, 1년 사용 후기: 보안 강화와 운영 효율성 회고

    [보안] HashiCorp Vault, 1년 사용 후기: 보안 강화와 운영 효율성 회고

    [DevOps] HashiCorp Vault 1년 사용 후기와 운영 회고

    인프라를 오래 만지다 보면 결국 한 번은 부딪히는 문제가 있습니다. 비밀번호, API Token(토큰, 인증용 비밀값), 인증서, 데이터베이스 계정 같은 비밀 정보(secret)를 어디에 두고 어떻게 돌볼 것인가 하는 문제입니다. 저도 예전에는 환경 변수, CI/CD 변수, 사설 위키, 심지어 급할 때는 메신저 DM까지 섞여 있던 시절이 있었거든요. 그런데 팀이 커지고 서비스 수가 늘어나니까 그 방식이 한계가 너무 واضح해졌습니다. 그래서 지난 1년 동안 HashiCorp Vault 사용 후기를 쌓아 보자는 마음으로 운영에 붙여 봤고, 결론부터 말씀드리면 보안 강화와 운영 효율성 둘 다 꽤 체감했습니다.

    물론 처음부터 매끈하진 않았습니다. 오히려 초반엔 정책(policy, 접근 제어 규칙) 설계 때문에 삽질 좀 했습니다 ㅎㅎ 그래도 실제로 써보니까, 비밀 관리가 사람 손에 덜 의존하게 되고 누가 무엇에 접근했는지 추적하기 쉬워지더라고요. 이번 글에서는 제가 겪은 HashiCorp Vault 운영 경험을 바탕으로, 왜 도입했고 어떤 식으로 붙였는지, 그리고 1년 돌려보니 무엇이 달라졌는지를 솔직하게 정리해보겠습니다.

    HashiCorp Vault 사용 후기를 설명하는 비밀 관리 아키텍처 다이어그램

    Vault를 중심으로 애플리케이션, CI/CD, 운영자 접근 흐름을 한눈에 보여주는 아키텍처 예시입니다.

    1. 왜 굳이 Vault였을까: 비밀 관리가 운영 비용이 되는 순간

    쉽게 말해 Vault는 Secret Management(비밀 관리) 전용 금고입니다. 그냥 값을 저장하는 저장소가 아니라, 누가 어떤 비밀을 언제 읽었는지 통제하고, 필요하면 동적으로 자격 증명(credentials, 인증 정보)을 발급하고, 주기적으로 회전(rotation, 교체)하는 데 초점이 맞춰져 있습니다.

    제가 직접 해보니 중요한 건 저장보다 수명 주기 관리였습니다. 비밀은 한 번 넣고 끝나는 데이터가 아니더라고요. 생성, 배포, 접근 통제, 만료, 교체, 폐기까지 계속 관리해야 합니다. 이걸 사람이 엑셀이나 문서로 붙잡고 있으면 언젠가 사고가 납니다. 혹시 이런 경험 있으신가요? 퇴사자 계정은 막았는데, 그 사람이 발급해 둔 외부 API 키는 그대로 살아 있는 상황이요. 저는 실제로 그런 걸 정리하면서 Vault의 필요성을 절실하게 느꼈습니다.

    • 보안 강화: 비밀을 평문 파일이나 문서에 두지 않게 됩니다.
    • 접근 통제: 애플리케이션별, 팀별로 읽을 수 있는 경로(path)를 나눌 수 있습니다.
    • 감사 추적: Audit Log(감사 로그) 관점에서 누가 접근했는지 확인하기 편합니다.
    • 운영 효율: 만료와 교체를 자동화하기 쉬워집니다.

    2. HashiCorp Vault 개념 설명: 처음엔 복잡해 보여도 핵심은 단순합니다

    저도 처음엔 용어가 많아서 헷갈렸는데, 딱 몇 가지만 이해하면 흐름이 잡힙니다.

    개념 쉽게 말하면 운영에서 체감한 포인트
    Secrets Engine(시크릿 엔진) 비밀을 저장하거나 발급하는 기능 단위 KV, PKI, Database처럼 목적별로 분리해서 쓰기 좋습니다.
    Auth Method(인증 방식) 사용자나 서비스가 Vault에 로그인하는 방법 Token, AppRole, Kubernetes Auth 등을 상황별로 나눌 수 있습니다.
    Policy(정책) 어디까지 읽고 쓰게 할지 정하는 권한 규칙 처음 설계를 잘해야 운영이 편합니다.
    Lease(임대) 발급된 비밀의 유효 기간 영구 자격 증명 대신 짧게 쓰는 습관이 생깁니다.
    Audit Device(감사 장치) 접근 기록을 남기는 기능 사고 대응과 추적에 정말 중요합니다.

    여기서 중요한 포인트! Vault를 도입한다고 해서 갑자기 모든 비밀이 안전해지는 건 아닙니다. 어떤 인증 방식으로 접속시키고, 어떤 정책으로 경로를 나누고, 애플리케이션이 어떻게 비밀을 가져가게 할지까지 같이 설계해야 진짜 효과가 납니다. 그래서 저는 Vault를 제품 하나로 보기보다, 비밀 관리 운영 체계를 만드는 도구로 보는 편입니다.

    3. 도입 전후 비교: 운영 습관이 어떻게 바뀌었는가

    1년 전과 지금을 비교하면 가장 큰 차이는 “비밀을 사람이 들고 다니지 않게 됐다”는 점입니다. 예전엔 신규 서비스가 뜰 때마다 환경 변수 파일을 복사해 배포하고, 누가 최신값인지 물어보는 일이 잦았거든요. 지금은 최소한 기준 저장소와 접근 절차가 분리되어 있어서 훨씬 낫습니다.

    항목 도입 전 도입 후
    비밀 저장 위치 여러 군데 흩어짐 Vault 경로 기준으로 정리
    권한 관리 사람 기억과 문서 의존 Policy 기반 통제
    교체 작업 수동 공지 후 반영 주기화와 자동화 설계 가능
    장애 대응 누가 뭘 바꿨는지 찾기 어려움 감사 로그로 추적 쉬움
    DevOps 협업 운영팀 병목 발생 경로와 역할을 나눠 위임 가능

    이 변화가 생각보다 큽니다. 특히 DevOps 환경에서는 CI/CD 파이프라인, 컨테이너 워크로드, 운영자 접근이 동시에 얽히거든요. Vault를 넣고 나면 적어도 “어디가 원본이냐”는 질문은 줄어듭니다. 이거 진짜 편하더라고요.

    4. 실전 구현: 제가 초기에 잡았던 최소 구성

    이번 섹션은 처음 구축할 때 제가 사용했던 접근을 최대한 단순하게 정리한 것입니다. 운영 환경에서는 네트워크 분리, TLS(전송 구간 암호화), 백업, 접근 통제, 고가용성 설계를 반드시 더해야 합니다. 아래 예시는 흐름 이해용에 가깝습니다.

    4-1. Vault 서버 기본 구성 예시

    storage "raft" {
      path    = "/opt/vault/data"
      node_id = "vault-1"
    }
    
    listener "tcp" {
      address     = "0.0.0.0:8200"
      tls_disable = 0
      tls_cert_file = "/opt/vault/tls/tls.crt"
      tls_key_file  = "/opt/vault/tls/tls.key"
    }
    
    api_addr = "https://vault.example.internal:8200"
    cluster_addr = "https://vault.example.internal:8201"
    ui = true

    처음엔 외부 스토리지와 따로 연동할지 고민했는데, 실제로 써보니까 Integrated Storage(통합 스토리지, Raft 기반 저장소)가 운영 복잡도를 낮추는 데 도움이 되더라고요. 물론 조직 상황에 따라 선택은 달라질 수 있습니다.

    4-2. 초기화와 언실(unseal) 절차

    vault operator init
    vault operator unseal
    vault login

    여기서 나오는 Unseal Key(언실 키)와 초기 Root Token(루트 토큰)은 정말 조심해서 다뤄야 합니다. 저는 초반에 테스트 환경에서만 가볍게 생각했다가, 나중에 누가 어떤 키를 갖고 있는지 정리하느라 고생했습니다. 운영 환경에서는 보관 정책부터 먼저 정해야 합니다.

    4-3. KV 엔진으로 애플리케이션 비밀 저장

    vault secrets enable -path=kv kv-v2
    vault kv put kv/apps/payment DB_USER="app_user" DB_PASSWORD="change-me"
    vault kv get kv/apps/payment

    처음 시작은 대부분 KV(Key-Value) Engine부터 하게 됩니다. 저도 그랬습니다. 일단 애플리케이션이 읽어 가는 기준 경로를 만드는 것만으로도 운영 정리가 꽤 됩니다.

    HashiCorp Vault 운영에서 정책과 KV 경로 구성을 보여주는 이미지

    서비스별 경로, 팀별 정책, 인증 방식이 어떻게 연결되는지 보여주는 구성 예시입니다.

    4-4. 정책 분리: 서비스별 최소 권한으로 시작

    path "kv/data/apps/payment" {
      capabilities = ["read"]
    }
    vault policy write payment-read payment-read.hcl

    제가 초기에 가장 많이 했던 실수가 이 부분입니다. 귀찮아서 넓게 열어두면 나중에 정리하기가 더 어렵습니다. Least Privilege(최소 권한) 원칙으로 서비스별 경로를 쪼개 두는 게 결국 덜 힘듭니다.

    4-5. 애플리케이션 인증 연결

    환경에 따라 Token, AppRole, Kubernetes Auth를 선택할 수 있는데, 저는 사람이 직접 접근하는 경우와 워크로드가 접근하는 경우를 분리해서 생각했습니다. 사람은 SSO나 운영 계정 기준으로, 서비스는 AppRole 또는 플랫폼 네이티브 인증을 우선 검토하는 식이었습니다.

    vault auth enable approle
    vault write auth/approle/role/payment-role token_policies="payment-read"
    vault read auth/approle/role/payment-role/role-id
    vault write -f auth/approle/role/payment-role/secret-id

    이렇게 발급한 값으로 애플리케이션이 로그인해서 필요한 값만 읽게 만들 수 있습니다. 처음엔 번거로워 보여도, 장기적으로는 운영자가 직접 비밀번호를 전달하는 일 자체가 줄어듭니다.

    5. 1년 써보니 좋았던 점: 보안 강화와 운영 효율성은 같이 갑니다

    HashiCorp Vault 사용 후기를 한 줄로 줄이면, “보안 때문에 시작했는데 운영 효율까지 따라왔다”입니다. 제가 체감한 장점은 아래와 같았습니다.

    1. 비밀의 원본 위치가 명확해졌습니다. 장애가 나도 어디를 봐야 하는지 알 수 있습니다.
    2. 비밀 관리가 요청 기반에서 정책 기반으로 바뀝니다. 운영자가 매번 전달하지 않아도 됩니다.
    3. 회전(rotation) 설계를 붙이기 쉬워집니다. 특히 만료 개념이 들어오면 영구 계정에 대한 경각심이 생깁니다.
    4. 감사 로그가 남습니다. 누가 읽었는지, 어느 경로에 접근했는지 확인이 쉬워집니다.

    특히 여러 팀이 같이 쓰는 환경에서 효과가 컸습니다. 이전에는 “이 값 최신 맞나요?”라는 질문이 자주 왔는데, 이제는 “이 서비스가 읽을 권한이 있나요?”로 질문의 성격이 바뀌었습니다. 이 차이가 큽니다. 운영의 초점이 값 전달에서 권한 설계로 이동하거든요.

    6. ⚠️ 실제로 겪은 문제들: HashiCorp Vault 운영에서 막혔던 지점

    좋은 점만 있던 건 아닙니다. 저도 처음엔 “금고 하나 세우면 끝이겠지”라고 생각했었는데, 현실은 그렇지 않았습니다. 아래는 제가 실제로 자주 부딪힌 문제들입니다.

    6-1. 정책 경로를 잘못 잡아 접근이 안 되는 문제

    Vault는 경로와 정책 문법이 익숙해질 때까지 헷갈립니다. KV v2는 내부적으로 data 경로가 들어가서, 눈으로 보는 경로와 정책 대상 경로가 달라 보일 때가 있거든요. 처음엔 이게 뭔가 싶었는데, 경로 규칙을 문서화해 두고 나서야 정리가 됐습니다.

    • 증상: 로그인은 되는데 secret read가 거부됩니다.
    • 원인: 정책 경로와 실제 엔진 경로 불일치
    • 해결: 엔진 버전과 정책 대상 경로를 분리해서 표준 문서로 정리

    6-2. 루트 토큰 의존

    초반엔 급하니까 Root Token으로 다 해버리기 쉽습니다. 저도 테스트 환경에서 그랬고요. 근데 여기서 운영 습관이 잘못 들면 나중에 정리 비용이 큽니다. 관리자 역할도 세분화해서 일상 작업은 일반 관리 정책으로 하시는 걸 추천드립니다.

    6-3. 비밀 주입 방식 미정

    Vault를 넣어도 애플리케이션이 비밀을 어떻게 받아갈지 정하지 않으면 반쪽짜리입니다. 시작 전에 아래 셋 중 하나는 정해야 합니다.

    1. 애플리케이션 시작 시 가져와 환경 변수로 주입
    2. 사이드카(sidecar, 보조 컨테이너) 또는 에이전트(agent)로 파일 렌더링
    3. 애플리케이션이 직접 API로 조회

    저는 서비스 특성에 따라 다르게 갔습니다. 단순한 배치 작업은 시작 시 주입이 편했고, 회전이 중요한 워크로드는 갱신이 쉬운 방식이 낫더라고요.

    6-4. 운영자 교육 비용

    이건 의외로 큽니다. Vault는 강력하지만, 익숙하지 않은 팀에게는 진입장벽이 있습니다. 용어도 많고 정책 문법도 낯설거든요. 그래서 저는 내부 위키에 “자주 쓰는 경로, 정책 예시, 장애 시 체크 순서”를 짧게 정리해 두었습니다. 이거 하나만 있어도 온보딩 속도가 꽤 달라집니다.

    HashiCorp Vault 사용 후기의 초기 설정과 CLI 구성 흐름 이미지

    초기화, 언실, 정책 적용, 인증 방식 연결까지의 흐름을 단계적으로 보여주는 이미지입니다.

    7. 검증과 결과: 무엇이 실제로 달라졌는지

    1년 운영하면서 제가 가장 중요하게 본 건 “얼마나 화려한 기능을 썼는가”가 아니라, 실제 운영에서 반복 작업이 줄었는가였습니다. 그 기준으로 보면 결과는 꽤 만족스러웠습니다.

    • ✅ 신규 서비스 온보딩 때 비밀 전달 절차가 단순해졌습니다.
    • ✅ 운영자가 개별 비밀번호를 전달하는 횟수가 줄었습니다.
    • ✅ 접근 경로와 책임 범위가 명확해졌습니다.
    • ✅ 감사 로그 중심으로 사고 대응 흐름을 잡기 쉬워졌습니다.

    물론 모든 문제가 자동으로 해결되진 않습니다. 예를 들어 애플리케이션 코드가 비밀 갱신을 고려하지 않으면 Vault를 써도 회전 효과를 충분히 못 누릴 수 있습니다. 그래서 저는 보안 강화를 제품 도입만으로 보지 않고, 애플리케이션 수명 주기까지 함께 보는 편입니다.

    vault status
    vault token lookup
    vault audit list
    vault kv get kv/apps/payment

    운영 중에는 이런 기본 확인 명령만 자주 써도 상태 파악이 빨라집니다. 특히 audit 설정 여부는 꼭 챙기세요. “나중에 붙여야지” 하다가 잊기 쉽습니다. 저도 한 번 그랬다가 식은땀 흘렸습니다.

    HashiCorp Vault 운영 결과와 보안 강화 상태를 보여주는 대시보드 이미지

    비밀 접근 통제, 감사 로그, 서비스별 정책 적용 결과를 대시보드 형태로 표현한 이미지입니다.

    8. 정리와 FAQ: HashiCorp Vault 사용 후기에서 남은 교훈

    정리해보면, HashiCorp Vault 사용 후기에서 제가 얻은 가장 큰 교훈은 “비밀 관리는 저장이 아니라 운영”이라는 점입니다. Vault는 분명 강력합니다. 하지만 진짜 효과는 제품 기능보다 정책, 인증, 배포 흐름, 운영 문서화가 맞물릴 때 나옵니다.

    처음 도입하시는 분이라면 욕심내서 모든 기능을 한 번에 붙이기보다, 아래 순서로 가시는 걸 추천드립니다.

    1. KV 엔진으로 비밀의 원본 위치를 단일화합니다.
    2. 서비스별 정책을 최소 권한으로 나눕니다.
    3. 사람과 애플리케이션의 인증 방식을 분리합니다.
    4. 감사 로그와 백업 절차를 운영 문서에 포함합니다.
    5. 그다음에 동적 자격 증명이나 회전 자동화를 확장합니다.

    💡 개인적으로는 이 순서가 가장 덜 아프더라고요. 처음부터 거대한 보안 플랫폼처럼 접근하면 지칩니다. 작게 시작해서 운영 습관을 바꾸는 쪽이 오래 갑니다.

    HashiCorp Vault 사용 후기 기반 도입 전후 비교와 운영 체크리스트 인포그래픽

    도입 전후 차이, 권장 구축 순서, 운영 체크리스트를 요약한 인포그래픽입니다.

    자주 묻는 질문

    Q. 소규모 팀도 Vault가 필요할까요?
    A. 비밀이 여러 서비스에 걸쳐 있고, 담당자가 둘 이상이면 충분히 검토할 만합니다. 반대로 단일 서비스, 단일 운영자, 짧은 수명 프로젝트라면 과할 수도 있습니다.

    Q. 도입하면 바로 운영 효율이 올라가나요?
    A. 바로라기보다는, 정책과 인증 구조를 정리하는 과정에서 효율이 올라옵니다. 초반 학습 비용은 분명 있습니다.

    Q. 무엇부터 시작하는 게 좋을까요?
    A. KV 기반 비밀 통합과 정책 분리부터입니다. 동적 비밀은 그다음입니다.

    다음 글에서는 Vault Agent(에이전트)나 Kubernetes Auth 같은 실제 연동 패턴을 조금 더 깊게 다뤄볼 예정입니다. 이전 글에서 다룬 CI/CD 비밀 주입 방식과 같이 보시면 흐름이 더 잘 잡히실 거예요. 혹시 지금 비밀 관리가 점점 사람 손에 의존하고 있다면, 이 시점이 구조를 바꿀 타이밍일 수도 있습니다. 저도 처음엔 헷갈렸지만, 하나씩 정리하니까 분명히 운영이 가벼워졌습니다. 🎉

  • [Cloud] Vercel에서 GitLab CI/CD로 마이그레이션: 실제 경험과 고려사항

    [Cloud] Vercel에서 GitLab CI/CD로 마이그레이션: 실제 경험과 고려사항

    [DevOps] Vercel GitLab CI/CD 마이그레이션 실제 경험과 고려사항

    프론트엔드 배포를 빠르게 시작할 때는 Vercel이 정말 편합니다. 저도 처음엔 Git push만 하면 미리보기 배포(Preview Deployment, 변경사항 확인용 임시 배포)가 바로 올라오는 흐름이 너무 좋아서 한동안 만족하면서 썼거든요. 그런데 서비스가 조금씩 커지고, 백엔드와 인프라 정책까지 같이 맞춰야 하다 보니 Vercel GitLab CI/CD 마이그레이션을 진지하게 검토하게 되더라고요. 특히 팀에서 이미 GitLab을 중심으로 이슈, 머지 리퀘스트(Merge Request, 코드 리뷰 요청), 배포 이력을 관리하고 있다면 CI/CD 전환 자체가 단순한 툴 변경이 아니라 DevOps 워크플로우를 정리하는 작업이 됩니다. 이번 글에서는 제가 직접 정리하면서 겪었던 판단 포인트, 삽질했던 부분, 그리고 안정적으로 옮기는 방법을 차근차근 풀어보겠습니다.

    Vercel GitLab CI/CD 마이그레이션 전체 아키텍처 다이어그램

    Vercel 중심 배포에서 GitLab CI/CD 중심 배포로 흐름이 바뀌는 전체 구조를 보여주는 이미지입니다.

    1. 왜 Vercel에서 GitLab CI/CD로 옮기게 됐는가

    쉽게 말해, Vercel은 프론트엔드 배포 경험을 극도로 단순화해주는 플랫폼이고, GitLab CI/CD는 배포 과정을 내가 더 많이 통제할 수 있게 해주는 자동화 파이프라인입니다. 둘 중 뭐가 절대적으로 낫다기보다는, 팀 상황에 따라 기준이 달라집니다.

    제가 마이그레이션을 고민한 가장 큰 이유는 세 가지였습니다.

    • 배포 흐름 통합: 프론트엔드만 따로 Vercel에 있으면, 백엔드 배포와 인프라 변경 이력이 분리되기 쉽습니다.
    • 권한과 정책 일원화: GitLab 프로젝트 권한, 브랜치 보호(Protected Branch), 승인 규칙을 한 곳에서 다루는 게 편하더라고요.
    • 비용 절감: 서비스 규모와 팀 사용 방식에 따라 별도 플랫폼 비용보다 기존 GitLab Runner(러너, 작업 실행기)를 활용하는 쪽이 더 맞을 때가 있습니다.

    여기서 중요한 포인트! CI/CD 전환은 단순히 배포 속도만 보는 게 아닙니다. 로그를 어디서 볼지, 실패 시 누가 복구할지, 시크릿(Secret, 비밀값)을 어디서 관리할지까지 같이 봐야 합니다.

    2. Vercel과 GitLab CI/CD의 차이, 쉽게 설명해보면

    저도 처음엔 이게 뭔가 싶었는데, 아주 단순하게 비유하면 이렇습니다.

    • Vercel: 잘 차려진 배포 전문 주방입니다. 재료만 넣으면 빠르게 요리가 나옵니다.
    • GitLab CI/CD: 주방을 직접 설계할 수 있습니다. 대신 가스불, 조리 순서, 청소 방식까지 내가 정해야 합니다.

    그래서 소규모 프로젝트나 정적 사이트(Static Site, 서버 렌더링 없이 빌드 결과물만 배포하는 형태)는 Vercel이 아주 매력적입니다. 반대로 프론트엔드 빌드, 테스트, 컨테이너 이미지(Container Image), 쿠버네티스(Kubernetes, 컨테이너 오케스트레이션) 배포까지 한 줄로 이어야 한다면 GitLab CI/CD 쪽이 더 자연스러울 수 있습니다.

    항목 Vercel GitLab CI/CD
    초기 설정 매우 간단 직접 설계 필요
    프론트엔드 미리보기 강점 직접 구현 필요
    배포 통제력 플랫폼 기준 높음
    조직 내 표준화 분리 운영 가능성 GitLab 중심 통합 유리
    확장성 프론트엔드 친화적 전체 파이프라인 확장 유리

    3. 마이그레이션 전에 먼저 체크한 항목

    실제로 써보니까, 코드를 옮기는 것보다 현재 Vercel이 대신 해주던 것을 목록화하는 게 훨씬 중요했습니다. 이걸 빼먹으면 나중에 꼭 터집니다 ㅎㅎ

    1. 빌드 명령: 예를 들어 npm run build 또는 pnpm build가 정확히 무엇을 수행하는지 확인합니다.
    2. 출력 디렉터리: 정적 결과물이 dist, build, out 중 어디에 생성되는지 봅니다.
    3. 환경 변수(Environment Variable, 실행 환경별 설정값): API URL, 토큰, 공개 키 등을 개발/스테이징/운영으로 나눠 정리합니다.
    4. 리라이트/리다이렉트: Vercel 설정에 있던 경로 재작성(Rewrite) 규칙이 있다면 웹 서버나 인그레스에서 다시 구현해야 합니다.
    5. 프리뷰 배포 전략: 머지 리퀘스트마다 미리보기 URL이 꼭 필요한지 결정합니다.
    6. 도메인과 TLS: 커스텀 도메인(Custom Domain)과 인증서(TLS Certificate) 종료 지점이 어디인지 확인합니다.

    이 단계에서 제가 한 실수는, 빌드만 되면 끝이라고 생각한 거였습니다. 근데 실제 운영은 빌드보다 배포 후 라우팅과 환경 변수 관리에서 더 많이 흔들리더라고요.

    4. GitLab CI/CD로 기본 파이프라인 구성하기

    이제 실전입니다. 여기서는 가장 보편적인 방식으로, 프론트엔드 앱을 빌드한 뒤 정적 파일을 서버나 스토리지로 배포하는 흐름을 예시로 들겠습니다. 프레임워크는 Next.js, React, Vue 등 무엇이든 응용 가능하지만, 설정은 프로젝트 성격에 맞게 조정하셔야 합니다.

    4-1. 기본 디렉터리와 환경 준비

    먼저 프로젝트 루트에 .gitlab-ci.yml 파일을 둡니다. GitLab Runner가 Node.js 환경에서 의존성을 설치하고, 테스트 후 빌드를 수행하게 만들 겁니다.

    stages:
      - install
      - test
      - build
      - deploy
    
    variables:
      NODE_ENV: production
      npm_config_cache: .npm
    
    cache:
      paths:
        - .npm/
        - node_modules/
    
    install:
      stage: install
      image: node:20
      script:
        - npm ci
    
    unit_test:
      stage: test
      image: node:20
      script:
        - npm ci
        - npm run test -- --runInBand
      rules:
        - if: '$CI_COMMIT_BRANCH'
    
    build_app:
      stage: build
      image: node:20
      script:
        - npm ci
        - npm run build
      artifacts:
        paths:
          - dist/
        expire_in: 1 day
    
    deploy_production:
      stage: deploy
      image: alpine:latest
      script:
        - echo "Deploy step runs here"
      rules:
        - if: '$CI_COMMIT_BRANCH == "main"'

    위 예시는 아주 기본 골격입니다. 핵심은 install – test – build – deploy 단계를 분리해서 실패 지점을 명확하게 보는 겁니다. 나중에 장애가 나도 어디서 깨졌는지 바로 보이거든요.

    Vercel GitLab CI/CD 마이그레이션 파이프라인 구성 이미지

    설치, 테스트, 빌드, 배포 단계가 GitLab Runner에서 어떻게 순차 실행되는지 보여주는 이미지입니다.

    4-2. 정적 파일 서버로 배포하는 예시

    만약 Nginx(엔진엑스, 웹 서버)로 정적 파일을 서빙한다면 이런 식으로 배포 스크립트를 둘 수 있습니다.

    #!/usr/bin/env bash
    set -euo pipefail
    
    TARGET_DIR="/var/www/my-frontend"
    
    rm -rf "${TARGET_DIR:?}"/*
    cp -r dist/* "$TARGET_DIR"/
    
    echo "deploy completed"

    그리고 GitLab CI에서는 SSH(Secure Shell, 원격 접속 프로토콜)로 원격 서버에 접속해 배포할 수 있습니다.

    deploy_production:
      stage: deploy
      image: alpine:latest
      before_script:
        - apk add --no-cache openssh-client rsync
        - eval $(ssh-agent -s)
        - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
        - mkdir -p ~/.ssh
        - chmod 700 ~/.ssh
      script:
        - rsync -avz --delete dist/ deploy@your-server:/var/www/my-frontend/
        - ssh deploy@your-server "sudo systemctl reload nginx"
      rules:
        - if: '$CI_COMMIT_BRANCH == "main"'

    여기서 중요한 포인트는 시크릿을 코드에 넣지 않는 겁니다. SSH 키, API 토큰, 배포 대상 주소는 GitLab CI/CD Variables에 넣어야 합니다.

    5. 프리뷰 배포와 브랜치 전략은 어떻게 바꿨는가

    Vercel을 쓰다가 GitLab으로 넘어오면 가장 아쉬운 부분 중 하나가 프리뷰 배포입니다. 저도 이 부분이 꽤 컸습니다. Vercel은 이 경험이 워낙 매끄럽거든요. 근데 GitLab에서도 포기할 필요는 없습니다.

    • 옵션 1: 스테이징 환경 하나를 두고, 머지 전 검증은 공용 URL에서 진행
    • 옵션 2: 브랜치별 또는 머지 리퀘스트별 임시 환경을 생성
    • 옵션 3: 정적 아티팩트만 확인하고 실제 프리뷰 URL은 운영하지 않음

    제가 해보니 팀 규모가 크지 않다면, 처음부터 브랜치별 임시 환경을 만들기보다 스테이징 하나를 안정적으로 운영하는 쪽이 훨씬 덜 피곤했습니다. 특히 프론트엔드 배포만 있는 게 아니라 API, 인증, CORS(Cross-Origin Resource Sharing, 교차 출처 요청 정책)까지 얽혀 있으면 프리뷰 환경 증식이 오히려 운영 복잡도를 키우더라고요.

    6. ⚠️ 실제로 겪었던 문제와 트러블슈팅

    이 섹션은 꼭 넣고 싶었습니다. 마이그레이션 자체보다 여기서 시간을 더 썼거든요.

    6-1. SPA 라우팅이 깨지는 문제

    React Router 같은 클라이언트 라우팅(Client-side Routing, 브라우저에서 경로 처리)을 쓰는 앱은 새로고침 시 404가 날 수 있습니다. Vercel에서는 비교적 자연스럽게 처리되던 부분이, Nginx에서는 별도 설정이 필요합니다.

    location / {
      try_files $uri $uri/ /index.html;
    }

    처음엔 정적 파일만 복사하면 끝인 줄 알았는데, 이 설정 빠져서 새로고침할 때마다 페이지가 죽더라고요. 여기서 좀 삽질했습니다 ㅎㅎ

    6-2. 환경 변수 이름이 달라서 빌드가 실패한 문제

    Vercel에 등록해 둔 환경 변수와 GitLab Variables 이름이 다르면 빌드는 되는데 런타임에서 깨지기도 하고, 아예 빌드 시점에 실패하기도 합니다. 그래서 저는 아래처럼 체크리스트를 따로 뒀습니다.

    • NODE_ENV
    • PUBLIC_* 또는 프레임워크별 공개 변수 prefix
    • API 엔드포인트 URL
    • 서드파티 인증 키

    6-3. 캐시 때문에 이전 빌드 결과가 섞이는 문제

    CI 캐시는 속도에는 도움이 되지만, 설정이 애매하면 오히려 독이 됩니다. 의존성 캐시와 빌드 산출물 캐시는 분리해서 보시는 걸 추천드립니다. 저는 한 번은 캐시를 과하게 잡아놔서, 수정했는데도 예전 결과물이 살아남는 바람에 한참 헷갈렸습니다.

    6-4. 배포는 성공인데 서비스는 실패한 문제

    이거 많이 놓칩니다. 파이프라인이 초록불이라고 서비스가 정상이라는 뜻은 아니거든요. 배포 후 헬스 체크(Health Check, 상태 확인)나 간단한 smoke test(스모크 테스트, 핵심 기능 점검)를 꼭 넣어야 합니다.

    Vercel GitLab CI/CD 마이그레이션 트러블슈팅 로그 이미지

    배포 로그에서 자주 만나는 오류와 원인 분석 포인트를 시각적으로 정리한 이미지입니다.

    7. 검증은 이렇게 했습니다

    마이그레이션 후에는 감으로 보면 안 됩니다. 저는 최소한 아래 순서로 확인했습니다.

    1. 빌드 재현성: 같은 커밋에서 동일한 결과가 나오는지 확인
    2. 정적 자산 로딩: JS, CSS, 이미지 경로가 깨지지 않는지 확인
    3. 라우팅: 직접 URL 접근과 새로고침 테스트
    4. 환경별 설정: 개발/스테이징/운영에서 API 호출 대상이 올바른지 확인
    5. 롤백 가능성: 이전 결과물로 빠르게 되돌릴 수 있는지 점검

    여기서 제가 특히 중요하게 본 건 롤백 전략이었습니다. Vercel은 비교적 되돌리기 경험이 좋았는데, GitLab 기반으로 직접 운영할 때는 내가 롤백 절차를 설계해야 하거든요. 배포 디렉터리를 버전별로 보관하거나, 아티팩트를 일정 기간 유지하는 방식이 실무적으로 꽤 유용했습니다.

    curl -I https://your-service.example.com/
    curl -s https://your-service.example.com/health
    

    아주 단순한 명령이지만, 배포 직후 이 두 개만 자동으로 돌려도 문제를 빨리 찾는 데 도움이 됩니다.

    Vercel GitLab CI/CD 마이그레이션 결과 검증 대시보드 이미지

    파이프라인 성공 상태와 서비스 헬스 체크 결과를 함께 보여주는 검증 이미지입니다.

    8. 그래서 누구에게 적합한가: 선택 기준 정리

    결론적으로 Vercel GitLab CI/CD 마이그레이션은 모든 팀에 정답은 아닙니다. 다만 아래에 가깝다면 꽤 의미가 있습니다.

    • 프론트엔드, 백엔드, 인프라 배포를 한 플랫폼에서 관리하고 싶은 팀
    • 머지 리퀘스트 승인과 배포 이력을 강하게 연결하고 싶은 팀
    • 러너 운영이 가능하고, YAML 기반 파이프라인 관리에 거부감이 없는 팀
    • 배포 자동화와 권한 모델을 조직 표준에 맞추고 싶은 팀

    반대로, 빠른 배포 경험과 프리뷰 환경이 최우선이고 운영 인력이 많지 않다면 Vercel 유지가 더 좋은 선택일 수도 있습니다. 사실 이건 기술 우열보다 운영 철학 차이에 가깝습니다.

    상황 추천 방향
    작은 팀, 프론트엔드 중심, 빠른 배포 우선 Vercel 유지 검토
    조직 표준 CI/CD 필요, 배포 통합 필요 GitLab CI/CD 전환 검토
    스테이징/운영 분리와 승인 규칙 중요 GitLab CI/CD 유리
    프리뷰 경험이 가장 중요 Vercel 강점 큼
    Vercel GitLab CI/CD 마이그레이션 선택 기준 비교 인포그래픽

    도입 난이도, 통제력, 프리뷰 배포, 운영 표준화 관점에서 두 방식을 비교한 요약 이미지입니다.

    9. 마무리: 배포 도구보다 중요한 건 운영 기준입니다

    이번에 정리하면서 다시 느낀 건, 도구를 바꾸는 것보다 배포 기준을 문서화하는 일이 더 중요하다는 점이었습니다. 제가 직접 해보니 GitLab CI/CD로 옮긴 뒤 얻은 가장 큰 장점은 화려한 기능보다도, 누가 봐도 같은 절차로 배포할 수 있는 상태가 됐다는 거였습니다. 이거 진짜 편하더라고요.

    물론 처음엔 귀찮습니다. YAML도 손봐야 하고, 시크릿도 다시 넣어야 하고, 웹 서버 설정도 만져야 하거든요. 근데 한 번 기준을 잡아두면 이후에는 프론트엔드 배포뿐 아니라 배치 작업, 백엔드, 운영 스크립트까지 같은 방식으로 확장하기가 좋습니다. 이게 결국 장기적으로는 비용 절감과 운영 안정성으로 이어집니다.

    혹시 지금 CI/CD 전환을 고민하고 계신다면, 먼저 현재 Vercel이 대신 해주고 있는 기능부터 목록으로 적어보세요. 그다음 GitLab CI/CD에서 반드시 재현해야 할 항목과, 과감히 포기해도 되는 항목을 나누는 게 좋습니다. 다음 글에서는 GitLab Runner를 Docker Executor(도커 실행기)로 운영할 때 주의할 점도 다뤄볼 예정입니다. 이전 글에서 다뤘던 Nginx 리버스 프록시 구성과 같이 보시면 흐름 잡는 데 더 도움이 되실 겁니다.

    자주 묻는 질문

    Q1. Vercel에서 GitLab CI/CD로 옮기면 무조건 비용 절감이 되나요?

    꼭 그렇지는 않습니다. Runner 운영 비용, 저장소, 로그 관리, 엔지니어링 시간까지 같이 봐야 합니다. 다만 기존 GitLab 중심 운영 체계가 이미 있다면 중복 도구 비용을 줄이는 효과는 기대할 수 있습니다.

    Q2. 프리뷰 배포가 꼭 필요하면 GitLab CI/CD는 불리한가요?

    기본 경험은 Vercel이 더 좋다고 느끼는 경우가 많습니다. 대신 GitLab에서도 머지 리퀘스트 기반 임시 환경을 설계할 수 있으니, 팀이 감당할 운영 복잡도와 맞는지 보는 게 중요합니다.

    Q3. 가장 먼저 검증해야 할 한 가지는 뭔가요?

    저는 라우팅과 환경 변수라고 봅니다. 빌드는 됐는데 실제 서비스가 안 뜨는 경우가 대부분 여기서 나왔습니다. 특히 SPA 라우팅과 공개 환경 변수 prefix는 꼭 확인해보세요.

  • [k8s] ArgoCD 도입 실패 사례 분석: GitOps 전환 시 흔한 실수와 방지 전략

    [k8s] ArgoCD 도입 실패 사례 분석: GitOps 전환 시 흔한 실수와 방지 전략

    [DevOps] ArgoCD 도입 실패 사례 분석과 GitOps 실수 방지

    ArgoCD 도입 실패 이야기는 생각보다 흔합니다. 저도 처음엔 GitOps(깃옵스, Git을 단일 진실 공급원으로 삼는 운영 방식)만 붙이면 배포가 깔끔해질 줄 알았거든요. 그런데 실제로 해보니, ArgoCD(아르고CD, Kubernetes 선언형 배포 도구)를 넣는 순간 오히려 배포가 더 복잡해지는 팀도 많았습니다. 특히 CI/CD 전환 사례를 보면, 기술 문제가 아니라 역할 분리, 저장소 구조, 승인 흐름 같은 운영 설계에서 먼저 무너지는 경우가 많더라고요. 이번 글은 제가 현업과 홈랩에서 반복해서 본 ArgoCD 도입 실패 패턴을 중심으로, 왜 그런 일이 생기는지와 어떻게 피해야 하는지를 정리해보겠습니다.

    혹시 이런 경험 있으신가요? 배포 자동화는 넣었는데 누가 어떤 값을 바꿨는지 더 헷갈리고, 장애가 나면 Git이 문제인지 클러스터가 문제인지부터 추적하게 되는 상황 말입니다. 여기서 중요한 포인트는, ArgoCD 자체가 문제라기보다 GitOps 실수가 누적되면서 운영 복잡도가 폭발한다는 점입니다.

    ArgoCD 도입 실패와 GitOps 전환 흐름을 설명하는 아키텍처 이미지

    Git 저장소, CI 파이프라인, ArgoCD, Kubernetes 클러스터 사이의 흐름과 실패 포인트를 한눈에 보여주는 개요 이미지입니다.

    1. 왜 ArgoCD 도입 실패가 반복될까요?

    쉽게 말해 ArgoCD는 배포 버튼을 없애는 도구가 아니라, 변경 관리 방식 자체를 바꾸는 도구입니다. 그래서 예전 CI/CD에서는 잘 통하던 습관이 GitOps로 오면 바로 문제를 만듭니다. 예를 들면 이런 식입니다.

    • 운영값을 Git이 아니라 사람 손으로 클러스터에서 직접 수정함
    • 애플리케이션 코드 저장소와 배포 매니페스트 저장소의 책임이 불명확함
    • 자동 동기화(auto-sync)를 켰는데 승인 절차는 그대로 수동 운영을 기대함
    • Helm(헬름, Kubernetes 패키지 관리 도구) 값 파일이 환경별로 제멋대로 늘어남
    • 장애가 나도 누가 마지막 변경을 만들었는지 추적이 어려움

    저도 처음엔 이게 뭔가 싶었는데, 결국 핵심은 하나였습니다. ArgoCD는 배포 도구이면서 동시에 운영 규율을 강제하는 도구라는 점입니다. 팀이 그 규율을 합의하지 않은 상태에서 도입하면, ArgoCD 문제점처럼 보이는 현상이 사실은 프로세스 문제로 터집니다.

    2. GitOps와 ArgoCD를 아주 쉽게 설명해보면

    GitOps는 “실제 운영 상태는 Git에 적힌 선언대로 맞춘다”는 철학입니다. ArgoCD는 그 선언과 클러스터 상태를 계속 비교해서 drift(드리프트, 선언과 실제 상태가 어긋난 상태)를 감지하고 맞춰주는 역할을 하죠.

    예전 방식은 대체로 이랬습니다. CI 서버가 빌드하고, 누군가 kubectl(쿠버네티스 CLI)이나 Helm으로 운영 클러스터에 직접 배포합니다. 반면 GitOps 방식은 배포 변경도 Pull Request(PR, 변경 제안 요청)로 남고, 승인 후 Git이 바뀌면 ArgoCD가 그걸 따라갑니다. 감사 추적(audit trail, 변경 이력 추적)이 되는 대신, 우회 수정이 어려워집니다. 이 점이 장점이자 초반 저항 포인트입니다.

    항목 기존 CI/CD GitOps + ArgoCD
    배포 트리거 파이프라인 또는 운영자 수동 실행 Git 변경 반영
    변경 이력 파이프라인 로그 중심 Git 커밋과 PR 중심
    긴급 수정 클러스터에서 직접 수정 가능 직접 수정 시 drift 발생
    권한 통제 클러스터 권한 중심 저장소 권한과 승인 흐름 중요
    실패 원인 스크립트 불안정, 환경 차이 저장소 구조, 책임 분리 실패

    그래서 ArgoCD 도입 실패를 줄이려면 설치보다 운영 모델을 먼저 설계해야 합니다. 이걸 건너뛰면 나중에 진짜 많이 돌아갑니다. 저도 삽질 좀 했습니다 ㅎㅎ

    3. 실제로 많이 본 실패 사례 4가지

    3-1. 저장소 구조를 너무 늦게 정한 경우

    가장 흔한 실수입니다. 앱 소스와 배포 설정이 한 저장소에 섞여 있거나, 반대로 너무 잘게 쪼개져서 어디를 기준으로 봐야 하는지 모호한 경우죠. 처음엔 편해 보였는데, 팀이 커질수록 충돌이 심해집니다. 누가 이미지 태그를 관리하고, 누가 replica 수를 바꾸고, 누가 Ingress(인그레스, 외부 트래픽 진입점)를 수정하는지 경계가 안 잡히거든요.

    3-2. 자동 동기화만 켜고 승인 체계를 안 만든 경우

    auto-sync는 정말 편합니다. 드디어 됐다! 싶은 순간이 오거든요. 근데 여기서 바로 사고가 납니다. 운영 반영 전 검토가 필요한 팀인데도 자동 배포를 먼저 켜버리면, 잘못된 값 하나가 바로 프로덕션으로 갑니다. 도구는 빨라졌는데 프로세스는 그대로인 상태죠.

    3-3. Secret 관리 방식을 정하지 않은 경우

    GitOps 실수에서 빠지지 않는 항목입니다. 민감 정보(Secret)를 평문으로 저장하면 안 되고, 그렇다고 운영자가 클러스터에만 몰래 만들어두면 Git과 실제 상태가 분리됩니다. 저는 이 부분에서 팀마다 가장 오래 멈추는 걸 많이 봤습니다. Sealed Secrets(시일드 시크릿)나 External Secrets Operator 같은 접근을 검토하되, 핵심은 “Git에 무엇을 남기고 실제 값은 어디서 주입할지”를 팀 단위로 합의하는 겁니다.

    3-4. Drift를 장애로 볼지, 운영 유연성으로 볼지 합의가 없는 경우

    운영자가 급한 패치를 직접 넣는 문화가 남아 있으면 ArgoCD는 계속 OutOfSync(아웃오브싱크) 상태를 만들 겁니다. 이걸 경고로 볼지, 바로 되돌릴지, 특정 리소스는 예외로 둘지 정하지 않으면 경보만 쌓이고 신뢰가 깨집니다. 이 지점이 대표적인 ArgoCD 문제점처럼 보이는데, 사실은 정책 부재에 가깝습니다.

    4. 실전 구현: 실패 확률을 낮추는 최소 전환 절차

    제가 직접 해보니, 처음부터 전 서비스에 GitOps를 거는 것보다 작은 서비스 하나로 운영 규칙을 검증하는 게 훨씬 낫더라고요. 아래는 많이 무리하지 않는 최소 절차입니다.

    1. 배포 대상 네임스페이스(namespace) 하나를 파일럿으로 고릅니다.
    2. 애플리케이션 코드 저장소와 배포 매니페스트 저장소의 책임을 분리합니다.
    3. ArgoCD 프로젝트(AppProject)로 허용 대상 클러스터/네임스페이스를 제한합니다.
    4. 자동 동기화는 바로 켜지 말고, 먼저 수동 sync로 운영 흐름을 익힙니다.
    5. 변경 승인 기준을 PR 템플릿과 리뷰 규칙으로 문서화합니다.
    6. Drift 발생 시 대응 절차를 정합니다.

    예시로는 아래처럼 시작하면 무난합니다.

    kubectl create namespace argocd
    kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
    kubectl get pods -n argocd

    설치 자체보다 더 중요한 건 프로젝트 경계를 먼저 두는 일입니다.

    apiVersion: argoproj.io/v1alpha1
    kind: AppProject
    metadata:
      name: sample-project
      namespace: argocd
    spec:
      description: sample project for controlled GitOps rollout
      sourceRepos:
        - 'https://github.com/example/platform-manifests.git'
      destinations:
        - namespace: sample-app
          server: 'https://kubernetes.default.svc'
      clusterResourceWhitelist:
        - group: '*'
          kind: '*'

    그 다음 애플리케이션은 이렇게 붙일 수 있습니다.

    apiVersion: argoproj.io/v1alpha1
    kind: Application
    metadata:
      name: sample-app
      namespace: argocd
    spec:
      project: sample-project
      source:
        repoURL: 'https://github.com/example/platform-manifests.git'
        targetRevision: main
        path: apps/sample-app/overlays/prod
      destination:
        server: 'https://kubernetes.default.svc'
        namespace: sample-app
      syncPolicy: {}

    여기서 일부러 자동 동기화 설정을 비워둔 것이 포인트입니다. 처음엔 수동 sync로 팀이 변경 흐름을 이해하게 만드는 게 좋습니다. 자동화는 익숙해진 뒤에 켜도 늦지 않습니다.

    ArgoCD 도입 실패를 줄이기 위한 AppProject와 저장소 구조 구성 이미지

    AppProject 경계, Application 연결, PR 승인 후 반영되는 흐름을 시각적으로 설명하는 이미지입니다.

    5. CI/CD 전환 사례에서 특히 조심해야 할 체크포인트

    기존 파이프라인에서 이미지 빌드까지는 잘 돌아가는데 GitOps로 넘기면서 꼬이는 경우가 많습니다. 보통 CI는 artifact(아티팩트, 배포 가능한 결과물)를 만들고, CD는 배포를 실행합니다. GitOps에선 이 경계가 바뀝니다. CI가 직접 배포하지 않고, 이미지 태그나 차트 값을 Git에 반영하는 식으로 역할이 이동하죠.

    예를 들면 이런 방식입니다.

    # CI job example
    export IMAGE_TAG=${GIT_COMMIT_SHA}
    sed -i "s/tag: .*/tag: ${IMAGE_TAG}/" apps/sample-app/overlays/prod/values.yaml
    git add apps/sample-app/overlays/prod/values.yaml
    git commit -m "chore: deploy sample-app ${IMAGE_TAG}"
    git push origin main

    이 방식은 단순하지만, 바로 main 브랜치에 반영하면 위험합니다. 제가 추천하는 건 별도 배포 브랜치나 PR 자동 생성 방식입니다. 사람이 마지막으로 한 번 더 보고 머지하는 흐름이 초반 안정화에 꽤 도움이 됩니다.

    • 프로덕션 반영은 PR 승인 후에만 가능하게 설정
    • 리뷰어를 플랫폼 담당자와 서비스 담당자로 분리
    • 롤백 기준을 Git revert 중심으로 문서화
    • 수동 kubectl 적용은 예외 상황에서만 허용

    이런 기본선만 있어도 CI/CD 전환 사례에서 실패 확률이 꽤 내려갑니다.

    6. ⚠️ 트러블슈팅: 제가 실제로 많이 본 문제와 해결법

    여기서부터는 정말 많이 부딪히는 부분들입니다. 처음엔 저도 “왜 sync는 성공인데 앱은 안 뜨지?” 같은 상황을 자주 만났거든요.

    증상 원인 해결 방향
    OutOfSync가 계속 발생 운영자가 클러스터에서 직접 수정 직접 수정 금지 원칙 정리, 예외 리소스만 ignore 설정 검토
    Sync 성공인데 서비스 장애 매니페스트 문법은 맞지만 런타임 의존성 누락 readinessProbe, Secret 참조, ConfigMap 값 검증 강화
    배포가 너무 자주 일어남 이미지 태그나 공통 값 파일이 과도하게 변경됨 환경별 경로 분리, 변경 범위 최소화
    리뷰 병목 발생 모든 변경이 한 저장소에 몰림 팀 단위 경계 재설계, CODEOWNERS 활용
    롤백이 헷갈림 Git revert와 수동 핫픽스가 섞임 롤백 절차를 Git 기준으로 단일화

    6-1. ignoreDifferences 남용

    ArgoCD에는 특정 필드 차이를 무시하는 기능이 있습니다. 편하긴 한데, 이걸 남용하면 drift 감지 의미가 사라집니다. 정말 컨트롤러가 자동으로 바꾸는 필드처럼 불가피한 경우에만 제한적으로 쓰는 게 맞습니다.

    6-2. Helm 값 파일 난립

    환경이 늘수록 values 파일이 너무 많아집니다. dev, stage, prod는 그렇다 쳐도 팀별 패치, 긴급 패치, 지역별 패치가 늘어나면 나중에 아무도 구조를 설명 못 합니다. 저도 홈랩에서 비슷하게 풀었다가, 결국 Kustomize(커스터마이즈, 오버레이 기반 설정 관리)와 역할을 분리하면서 정리했었습니다.

    6-3. 알림 없이 운영

    Sync 실패나 Health degraded(헬스 저하) 이벤트를 아무도 못 받으면, ArgoCD UI를 매번 열어보기 전까지 장애를 놓치게 됩니다. 알림 체계는 초반부터 붙이는 게 좋습니다. 최소한 운영 채널로 실패 이벤트는 전달되게 구성해두세요.

    7. 검증: 전환이 잘 되고 있는지 어떻게 확인할까

    성공 기준도 미리 정의해야 합니다. 그냥 “ArgoCD가 떴다”로 끝내면 안 됩니다. 저는 보통 아래 항목으로 봅니다.

    1. 배포 변경이 모두 PR과 커밋으로 추적되는가
    2. 직접 클러스터 수정 비율이 줄고 있는가
    3. 장애 시 마지막 변경점 확인 시간이 짧아졌는가
    4. 롤백 절차가 Git revert 기준으로 일관되게 동작하는가
    5. 서비스별 소유권(owner)이 저장소 구조와 일치하는가

    ArgoCD UI에서 Health, Sync 상태를 보는 것도 좋지만, 그것만으론 부족합니다. 진짜 중요한 건 운영팀과 개발팀이 같은 변경 이력을 보고 같은 언어로 이야기하게 됐는지입니다. 이게 되면 도입이 절반은 성공한 겁니다. 실제로 써보니까 배포 속도보다 커뮤니케이션 비용이 더 크게 줄더라고요.

    ArgoCD 도입 실패 검증을 위한 Sync와 Health 상태 대시보드 이미지

    배포 상태 확인, 이상 징후 탐지, 롤백 판단 포인트를 한눈에 보여주는 결과 검증 이미지입니다.

    8. 정리 FAQ: 많이 받는 질문

    Q1. ArgoCD는 무조건 자동 동기화로 써야 하나요?

    아닙니다. 초반에는 수동 sync가 오히려 안전합니다. 팀이 GitOps 흐름에 익숙해지고 승인 절차가 자리 잡으면 그때 auto-sync를 검토해도 됩니다.

    Q2. 기존 CI/CD를 전부 버려야 하나요?

    그건 아닙니다. 빌드와 테스트는 CI가 계속 담당하고, 배포 실행만 Git 기반으로 넘기는 식이 일반적입니다.

    Q3. ArgoCD 문제점은 결국 도구 한계 아닌가요?

    일부는 맞지만, 현장에서 보이는 대부분은 정책과 구조 문제였습니다. 특히 저장소 책임 분리와 승인 체계가 약하면 같은 문제가 반복됩니다.

    Q4. 작은 팀도 GitOps가 필요할까요?

    작은 팀도 필요할 수 있습니다. 다만 처음부터 무겁게 가지 말고, 단일 서비스와 단순한 저장소 구조로 시작하는 편이 좋습니다.

    ArgoCD 도입 실패 방지 전략과 GitOps 실수 비교 요약 이미지

    도입 전후 비교, 실패 패턴, 예방 전략을 요약한 인포그래픽 형태의 정리 이미지입니다.

    9. 마무리: ArgoCD 도입 실패를 줄이는 진짜 핵심

    ArgoCD 도입 실패는 대개 설치 실패가 아니라 운영 모델 설계 실패입니다. GitOps는 도구를 붙이는 프로젝트가 아니라, 변경을 기록하고 승인하고 되돌리는 방식을 다시 정의하는 작업이거든요. 저도 처음엔 UI가 예쁘고 sync가 자동으로 돌아가니까 금방 안정화될 줄 알았는데, 실제론 저장소 구조와 팀 규칙을 먼저 잡아야 효과가 났습니다.

    정리하면 이렇습니다. GitOps 실수를 줄이려면 작은 범위로 시작하고, 수동 sync로 흐름을 익히고, 저장소 책임과 승인 규칙을 먼저 고정해야 합니다. 그리고 drift, Secret, 롤백 기준을 문서로 남겨야 합니다. 이 4가지만 해도 현장에서 체감 차이가 큽니다.

    다음 글에서는 App of Apps 패턴과 멀티 클러스터 운영에서 어디까지 표준화해야 하는지 다뤄볼 예정입니다. 이전 글에서 다뤘던 Kubernetes 배포 기본기와 함께 보시면 흐름이 더 잘 잡히실 겁니다. 혹시 지금 ArgoCD 도입 실패를 겪고 계시다면, 설치 로그보다 먼저 운영 규칙부터 점검해보세요. 그게 생각보다 훨씬 빠른 지름길입니다. 🎉

  • [Cloud] Jenkins on Kubernetes 마이그레이션: 클라우드 네이티브 CI/CD 전환 사례

    [Cloud] Jenkins on Kubernetes 마이그레이션: 클라우드 네이티브 CI/CD 전환 사례

    안녕하세요, 13년차의 서버실 운영자입니다. 오늘은 많은 분들이 고민하고 계실 법한 주제, 바로 Jenkins on Kubernetes 마이그레이션 경험담을 풀어보려고 합니다. 사실 저도 기존 Jenkins 환경을 운영하면서 여러 가지 페인 포인트(Pain Point)를 겪었거든요. 빌드가 몰리면 서버 리소스가 부족하고, 특정 에이전트(Agent)에 문제가 생기면 전체 파이프라인이 멈추고… 이런 경험, 혹시 여러분도 있으신가요?

    클라우드 네이티브(Cloud-Native) 환경으로의 전환은 이제 선택이 아닌 필수가 되어가고 있습니다. CI/CD(Continuous Integration/Continuous Deployment, 지속적 통합/지속적 배포) 파이프라인의 핵심인 Jenkins 역시 예외는 아니죠. 기존 VM 기반의 Jenkins를 Kubernetes(쿠버네티스) 위로 옮기면서 얻게 된 이점과, 그 과정에서 겪었던 삽질 경험을 솔직하게 공유해 드릴게요. 이 글이 여러분의 Jenkins Kubernetes 마이그레이션 여정에 멘토 같은 역할을 해주었으면 좋겠습니다.

    Jenkins on Kubernetes 클라우드 네이티브 CI/CD 아키텍처 다이어그램

    Jenkins on Kubernetes 아키텍처는 효율적인 CI/CD 파이프라인을 위한 핵심입니다. 동적 에이전트가 필요할 때마다 생성되어 리소스를 최적화합니다.

    Jenkins on Kubernetes, 왜 필요할까요?

    기존 Jenkins는 보통 고정된 마스터(Master)와 여러 개의 에이전트(Agent) 서버로 구성됩니다. 작업이 많아지면 에이전트 서버를 증설해야 하고, 사용하지 않을 때도 리소스를 계속 점유하죠. 특정 에이전트에 문제가 생기면 해당 에이전트에서 동작하는 모든 작업이 실패할 수 있으니 정말 골치 아픈 거거든요.

    하지만 Jenkins on Kubernetes 환경은 다릅니다. 쉽게 말해, 필요한 순간에만 에이전트(Agent)를 띄워서 작업을 처리하고, 끝나면 바로 없애는 방식입니다. Jenkins 마스터는 Kubernetes 클러스터 내의 컨트롤러(Controller)로 동작하고, CI/CD 파이프라인 작업이 필요할 때마다 Kubernetes Pod(파드) 형태로 에이전트를 동적으로 생성합니다. 작업이 완료되면 해당 Pod는 자동으로 소멸됩니다. 이게 바로 클라우드 네이티브 CI/CD의 핵심 장점 중 하나예요.

    ✅ 주요 장점

    • 탄력적인 확장성 (Elastic Scalability): 빌드 요청이 폭증해도 Kubernetes가 알아서 에이전트 Pod를 늘려줍니다.
    • 리소스 효율성 (Resource Efficiency): 작업이 없을 때는 에이전트 Pod가 존재하지 않으므로 불필요한 리소스 낭비가 없습니다.
    • 고가용성 (High Availability): Kubernetes의 자체 복구(Self-healing) 기능 덕분에 에이전트 Pod에 문제가 생겨도 다른 Pod로 대체되어 안정성이 높아집니다.
    • 선언적 구성 (Declarative Configuration): Jenkinsfile(젠킨스파일)과 JCasC(Jenkins Configuration as Code)를 통해 CI/CD 파이프라인과 Jenkins 자체 구성을 코드로 관리할 수 있습니다.

    Jenkins Kubernetes 마이그레이션, 실전 구현 단계

    자, 그럼 이제 제가 실제로 어떻게 Jenkins 현대화를 진행했는지 단계별로 알려드릴게요. 처음엔 어디서부터 손대야 할지 막막했는데, 하나씩 해나가다 보니 길이 보이더라고요.

    1. Kubernetes 클러스터 준비

    가장 먼저 할 일은 Jenkins를 올릴 Kubernetes 클러스터를 준비하는 겁니다. 클라우드 환경에서는 AWS EKS(Elastic Kubernetes Service), Azure AKS(Azure Kubernetes Service), Google GKE(Google Kubernetes Engine) 같은 관리형 서비스를 이용하는 게 편합니다. 저는 홈랩에서 K3s 클러스터를 운영하고 있어서 그걸 활용했어요. 온프레미스(On-premise) 환경이라면 kubeadm이나 OpenShift 등을 고려할 수 있겠죠.

    2. Helm을 이용한 Jenkins 설치

    Kubernetes에 애플리케이션을 배포하는 가장 일반적인 방법 중 하나가 Helm(헬름) 차트입니다. Jenkins도 공식 Helm 차트를 제공하고 있어서 쉽게 설치할 수 있어요. 물론, 설치 전에 PersistentVolume(영구 볼륨)과 PersistentVolumeClaim(영구 볼륨 클레임)을 설정해서 Jenkins 설정과 데이터를 보존할 수 있도록 해야 합니다. 이 부분이 제일 중요해요. 데이터 날리면 대형사고잖아요?

    # Helm 리포지토리 추가
    helm repo add jenkins https://charts.jenkins.io
    helm repo update
    
    # values.yaml 파일 커스터마이징 (예시)
    # persistentVolume 셋팅, resource limit, ingress 설정 등
    # 자세한 내용은 공식 Helm 차트 문서를 참고하세요!
    
    # Jenkins 설치
    helm install jenkins -f my-jenkins-values.yaml jenkins/jenkins -n jenkins --create-namespace
    

    위 명령어는 기본적인 설치 예시입니다. 실제 운영 환경에서는 `my-jenkins-values.yaml` 파일에 Ingress(인그레스, 외부 트래픽 진입점), 리소스 제한(Resource Limits), 플러그인 설정 등을 상세하게 정의해야 해요. 이 파일 하나로 Jenkins의 거의 모든 설정을 제어할 수 있거든요.

    Jenkins Helm values.yaml 설정 파일 예시

    Helm `values.yaml` 파일은 Jenkins 배포의 핵심입니다. 여기서 영구 스토리지, 리소스, 에이전트 설정을 세밀하게 제어할 수 있습니다.

    3. Kubernetes Cloud Plugin 설정

    Jenkins 마스터가 Kubernetes 클러스터와 통신하며 동적 에이전트를 생성하려면 Kubernetes Cloud Plugin(쿠버네티스 클라우드 플러그인)을 설치하고 설정해야 합니다. Jenkins UI에서 ‘Jenkins 관리’ → ‘시스템 설정’ → ‘클라우드’ 섹션으로 이동해서 Kubernetes 클러스터 정보를 입력합니다. 이때 Service Account(서비스 어카운트)와 RBAC(Role-Based Access Control) 설정을 통해 Jenkins가 Kubernetes API에 접근할 수 있도록 권한을 부여하는 것이 매우 중요합니다.

    여기서 Pod Template(파드 템플릿)을 정의하게 되는데, 이 템플릿이 바로 동적 에이전트 Pod의 설계도입니다. 어떤 도커(Docker) 이미지를 사용할지, 얼마나 많은 CPU와 메모리를 할당할지, 필요한 도구들은 무엇인지 등을 정의하죠. 예를 들어, Node.js 빌드가 필요하면 Node.js 런타임이 포함된 이미지를 지정하는 식이에요.

    4. Jenkinsfile 업데이트

    기존 Jenkinsfile도 약간의 수정이 필요할 수 있어요. 특히 `agent any`나 `agent { label ‘my-fixed-agent’ }` 같은 부분을 `agent { kubernetes { yaml ”’…”’ } }` 형태로 바꿔서 동적 Pod를 사용하도록 유도해야 합니다.

    // 기존 Jenkinsfile (예시)
    // pipeline {
    //     agent any
    //     stages {
    //         stage('Build') {
    //             steps {
    //                 sh 'npm install'
    //                 sh 'npm build'
    //             }
    //         }
    //     }
    // }
    
    // Kubernetes 동적 에이전트를 사용하는 Jenkinsfile (예시)
    pipeline {
        agent {
            kubernetes {
                // Kubernetes Pod 템플릿을 직접 정의
                yaml """
    apiVersion: v1
    kind: Pod
    spec:
      containers:
      - name: jnlp
        image: jenkins/inbound-agent:4.11.2-1
        resources:
          limits:
            memory: "512Mi"
            cpu: "500m"
      - name: nodejs
        image: node:16-alpine
        command: ["cat"]
        tty: true
        resources:
          limits:
            memory: "1Gi"
            cpu: "1000m"
    """
                defaultContainer 'nodejs' // 이 컨테이너에서 스크립트 실행
            }
        }
        stages {
            stage('Build Frontend') {
                steps {
                    container('nodejs') { // 'nodejs' 컨테이너에서 실행
                        sh 'npm install'
                        sh 'npm run build'
                    }
                }
            }
            stage('Package Docker Image') {
                // 다른 컨테이너 또는 스크립트 실행
            }
        }
    }
    

    위 Jenkinsfile 예시처럼, 하나의 Pod 안에 여러 컨테이너를 띄워서 각 컨테이너의 역할을 분리할 수 있어요. 예를 들어, `nodejs` 컨테이너에서 프론트엔드 빌드를 하고, `maven` 컨테이너에서 백엔드를 빌드하는 식이죠. 이 덕분에 빌드 환경을 더욱 유연하게 구성할 수 있거든요.

    5. Configuration as Code (JCasC) 적용

    Jenkins 설정을 UI에서 하나하나 하는 건 사실 귀찮고 실수를 유발하기 쉽습니다. JCasC(Jenkins Configuration as Code)는 Jenkins의 모든 설정을 YAML(야믈) 파일로 관리할 수 있게 해줘요. 이 파일을 버전 관리 시스템(VCS)에 커밋(Commit)해두면, Jenkins 인스턴스를 새로 띄우거나 설정을 변경할 때 코드로 관리할 수 있어서 매우 편리합니다. 저도 이걸 적용하고 나서 “이거 진짜 편하더라고요!”라고 감탄했었어요. Jenkins 현대화의 필수 요소라고 생각합니다.

    ⚠️ 주의사항 및 트러블슈팅

    마이그레이션 과정에서 저도 꽤 삽질 좀 했습니다. 여러분은 저 같은 시행착오를 겪지 않으시길 바라며 몇 가지 팁을 공유합니다.

    • Persistent Volume(PV) 설정: Jenkins 마스터의 홈 디렉토리(`JENKINS_HOME`)는 반드시 영구 볼륨으로 설정해야 합니다. 안 그러면 Jenkins Pod가 재시작될 때마다 모든 설정과 플러그인이 날아가는 대참사가 발생합니다. 😱
    • 리소스 제한 (Resource Limits): 에이전트 Pod 템플릿에 CPU와 메모리 리소스 제한을 명확하게 설정해야 합니다. 너무 적으면 빌드가 실패하고, 너무 많으면 클러스터 리소스가 고갈될 수 있어요.
    • 이미지 크기 및 빌드 시간: 동적 에이전트에 사용할 도커 이미지는 필요한 도구만 포함하여 최대한 가볍게 만드는 것이 좋습니다. 이미지가 너무 크면 Pod가 스케줄링(Scheduling)되고 컨테이너(Container)가 시작되는 시간이 길어져 빌드 성능에 악영향을 줄 수 있거든요.
    • 네트워크 문제: Jenkins 마스터에서 GitHub, Nexus, SonarQube 등 외부 서비스에 접근할 수 있는지, 또는 그 반대로 외부에서 Ingress를 통해 Jenkins UI에 접근할 수 있는지 네트워크 설정을 꼼꼼히 확인해야 합니다.
    • RBAC 권한: Jenkins 컨트롤러가 Kubernetes API에 접근할 수 있도록 적절한 Role(역할)과 RoleBinding(역할 바인딩)을 가진 Service Account를 생성하고 연결해야 합니다. 권한 부족으로 Pod 생성이 안 되는 경우가 많아요.

    ✅ 검증 및 마이그레이션 결과

    모든 설정을 마치고 파이프라인을 실행하면, Jenkins 대시보드에서 동적으로 생성되는 에이전트 Pod들을 확인할 수 있습니다. Kubernetes 클러스터에서도 `kubectl get pods -n jenkins` 명령어로 새로운 Pod들이 생성되었다가 작업 완료 후 사라지는 것을 볼 수 있을 거예요. 드디어 됐다! 하고 외쳤던 순간이 기억나네요. 🎉

    Jenkins 대시보드에서 Kubernetes 동적 에이전트 빌드 성공 확인

    Jenkins 대시보드에서 동적 에이전트의 효율적인 동작을 확인하는 것은 마이그레이션 성공의 중요한 지표입니다.

    Jenkins Kubernetes 마이그레이션을 통해 저희 팀은 다음과 같은 이점들을 누릴 수 있었습니다.

    • 빌드 시간 단축: 필요한 리소스를 즉시 할당받아 빌드 시간이 단축되었습니다.
    • 운영 비용 절감: 유휴 리소스가 없어 불필요한 클라우드 비용을 줄일 수 있었어요.
    • 안정성 향상: 특정 에이전트 문제로 인한 빌드 실패가 줄어들고, Kubernetes의 복원력 덕분에 전체 시스템의 안정성이 높아졌습니다.
    • CI/CD 파이프라인 관리의 용이성: JCasC와 Jenkinsfile을 통해 파이프라인과 Jenkins 자체를 코드로 관리하게 되면서, 변경 사항 추적 및 재현성이 크게 향상되었습니다.
    Jenkins on Kubernetes 마이그레이션 핵심 이점 인포그래픽

    Jenkins on Kubernetes 마이그레이션은 탄력적 확장성, 비용 효율성, 그리고 안정성을 동시에 달성하는 효과적인 방법입니다.

    마무리하며: 클라우드 네이티브 CI/CD의 미래

    오늘은 제가 직접 경험했던 Jenkins on Kubernetes 마이그레이션 사례를 공유해 드렸습니다. 처음엔 VM 기반 Jenkins 환경에 익숙해서 변화가 어렵게 느껴졌지만, 클라우드 네이티브 환경으로의 전환은 분명 더 나은 CI/CD 파이프라인을 위한 투자라고 생각해요.

    물론 Jenkins 외에도 Argo CD(아르고 CD)나 Tekton(텍톤) 같은 훌륭한 클라우드 네이티브 CI/CD 도구들이 많이 있습니다. 하지만 기존 Jenkins 환경에 익숙하고, 점진적인 Jenkins 현대화를 원한다면 Kubernetes 위로 Jenkins를 옮기는 것이 좋은 시작점이 될 수 있다고 확신합니다.

    이 글이 여러분의 인프라 여정에 작은 도움이 되었기를 바랍니다. 다음번에는 또 다른 삽질 경험과 해결책으로 찾아오겠습니다. 궁금한 점이나 공유하고 싶은 경험이 있다면 언제든지 댓글 남겨주세요! 😉

  • [Cloud] AWX를 활용한 클라우드 인프라 자동화 1년 회고: 운영 효율성과 도전 과제

    [Cloud] AWX를 활용한 클라우드 인프라 자동화 1년 회고: 운영 효율성과 도전 과제

    AWX 클라우드 인프라 자동화 1년 회고: 운영 효율성과 도전 과제

    안녕하세요, 13년차 서버실 지킴이입니다. 오늘은 제가 지난 1년간 AWX를 활용한 클라우드 인프라 자동화를 경험하며 느꼈던 점들을 솔직하게 회고해 보려고 해요. 클라우드 환경이 복잡해질수록 수동 작업의 한계를 뼈저리게 느끼곤 하잖아요? 저도 처음엔 정말 막막했습니다. 혹시 여러분도 이런 고민 해보신 적 있으신가요? “수동 작업 줄이고 싶은데, Ansible 플레이북은 많아지고 관리도 어렵고…” 딱 제가 그랬거든요. 그래서 이 녀석, AWX를 도입하게 됐습니다.

    AWX 클라우드 자동화 아키텍처 개요 다이어그램

    AWX 클라우드 자동화 아키텍처 개요 다이어그램

    AWX, 너는 누구니? (핵심 개념)

    자, 그럼 AWX가 뭔지부터 간단히 짚고 넘어갈게요. AWX는 Ansible Tower의 오픈소스 버전이라고 생각하시면 됩니다. 쉽게 말해, Ansible(앤서블) 플레이북(Playbook)을 웹 UI(User Interface)로 관리하고 실행할 수 있게 해주는 도구예요. 단순히 플레이북 실행뿐만 아니라, 인벤토리(Inventory, 관리 대상 호스트 목록), 크리덴셜(Credential, 자격 증명), 프로젝트(Project, 플레이북 저장소), 작업 템플릿(Job Template, 실행 단위), 그리고 워크플로우(Workflow, 여러 작업 템플릿 연결)까지 체계적으로 관리할 수 있게 해줍니다. 💡 특히 팀 단위로 Ansible을 활용할 때, 누가 어떤 작업을 언제 실행했는지, 성공 여부는 어땠는지 한눈에 파악할 수 있어서 정말 편하더라고요.

    AWX 도입 및 초기 셋업 경험

    저도 처음엔 “이거 설치부터 만만치 않겠는데?” 하고 살짝 긴장했었는데, 생각보다 어렵지 않았어요. 저는 주로 Docker Compose(도커 컴포즈)를 이용해서 컨테이너 환경에 AWX를 배포했습니다. 공식 문서에 잘 나와 있어서 따라 하는 데 큰 무리는 없었죠. 하지만 역시 초반 삽질은 피해 갈 수 없더라고요. 😅

    • 인벤토리 구성: 클라우드 환경이다 보니, 정적인 인벤토리보다는 동적 인벤토리(Dynamic Inventory) 스크립트를 활용해야 했습니다. AWS EC2 같은 경우는 플러그인을 활용해서 현재 떠 있는 인스턴스 목록을 자동으로 가져오게 설정했죠. 처음엔 필터링 규칙 잡는 게 좀 헷갈렸는데, 몇 번 해보니 감이 오더라고요.
    • 자격 증명(Credential) 관리: SSH 키나 클라우드 API 키 같은 민감 정보를 안전하게 관리하는 게 중요했습니다. AWX의 크리덴셜 기능을 사용해서 암호화된 형태로 저장하고, 필요한 작업 템플릿에만 연결해서 사용했습니다.
    • Git(깃) 연동: 저희 팀은 모든 Ansible 플레이북을 Git 저장소에 관리하고 있었거든요. AWX 프로젝트와 Git을 연동해서, Git에 푸시(Push)만 하면 AWX가 자동으로 최신 플레이북을 가져오도록 설정했습니다. 이 부분이 정말 편리했어요!

    클라우드 인프라 자동화, 이렇게 해봤습니다

    AWX를 도입하고 나서 저희 팀의 클라우드 인프라 자동화는 날개를 달았습니다. 지난 1년간 다양한 작업들을 AWX를 통해 자동화했는데요, 몇 가지 대표적인 사례를 소개해 드릴게요.

    1. EC2(혹은 VM) 프로비저닝 및 초기 설정 자동화: 새로운 서버를 띄울 때마다 수동으로 OS 설정, 보안 설정, 기본 에이전트 설치 등을 하는 게 큰일이었거든요. AWX에 작업 템플릿을 만들어두고, 인스턴스 ID만 입력하면 모든 초기 설정이 자동으로 완료되도록 했습니다.
    2. 보안 그룹(Security Group) 변경 자동화: 개발자들이 특정 IP를 열어달라고 요청할 때마다 일일이 AWS 콘솔에 들어가서 작업했었는데, 이젠 AWX 작업 템플릿에 IP와 설명을 입력하고 실행하면 끝! 휴먼 에러도 줄고, 작업 속도도 훨씬 빨라졌죠.
    3. 로드밸런서(Load Balancer) 대상 그룹(Target Group) 등록/해제: 배포 시 서비스 인스턴스를 로드밸런서에서 빼고 다시 넣는 작업을 AWX 워크플로우로 묶어 자동화했습니다.
    4. 주기적인 서버 패치 및 재부팅 관리: 매달 특정 요일에 정해진 서버 그룹에 OS 패치를 적용하고 필요시 재부팅하는 작업을 스케줄러(Scheduler) 기능을 활용해서 자동화했습니다.

    간단한 Ansible 플레이북 예시를 하나 보여드릴게요. 특정 EC2 인스턴스의 특정 포트를 보안 그룹에 추가하는 플레이북입니다.

    - name: Add a specific IP to security group
      hosts: localhost
      connection: local
      gather_facts: no
    
      vars:
        instance_id: 'i-xxxxxxxxxxxxxxxxx'
        port_to_open: 8080
        ip_to_allow: '192.168.1.1/32'
        description: 'Allow access from specific IP'
    
      tasks:
        - name: Get security group ID from instance
          amazon.aws.ec2_instance_info:
            instance_ids: "{{ instance_id }}"
          register: ec2_info
    
        - name: Set security group ID fact
          set_fact:
            sg_id: "{{ ec2_info.instances[0].security_groups[0].group_id }}"
    
        - name: Authorize ingress rule
          amazon.aws.ec2_group:
            group_id: "{{ sg_id }}"
            region: ap-northeast-2
            rules:
              - proto: tcp
                ports:
                  - "{{ port_to_open }}"
                cidr_ip: "{{ ip_to_allow }}"
                rule_desc: "{{ description }}"
            purge_rules: no
    

    이런 플레이북을 AWX에 등록하고, instance_id, port_to_open, ip_to_allow 같은 변수들만 작업 템플릿에서 설정하면 되는 거죠. 정말 편하더라고요! 🚀

    AWX 작업 템플릿 설정 화면 예시

    AWX 작업 템플릿 설정 화면 예시

    1년 회고: AWX가 가져온 운영 효율성

    지난 1년간 AWX를 운영하면서 가장 크게 체감한 건 바로 운영 효율성의 극대화였습니다. 🎉

    • 수동 작업 시간 획기적으로 감소: 과거에 1시간씩 걸리던 수동 설정 작업들이 이제는 AWX 작업 템플릿 한 번 클릭으로 5분 안에 끝나는 마법을 경험했습니다.
    • 휴먼 에러(Human Error) 감소: 정형화된 작업 템플릿을 사용하니, 사람이 실수할 여지가 거의 없어졌습니다. 특히 야간 작업이나 긴급 상황에서 빛을 발하더라고요.
    • 작업 표준화 및 가시성 확보: 모든 자동화 작업이 AWX를 통해 이루어지면서, 어떤 팀원이든 동일한 절차로 작업을 수행할 수 있게 되었고, 대시보드에서 모든 작업 이력을 한눈에 볼 수 있게 되었습니다.
    • 팀원 간 협업 용이: Ansible 플레이북을 공유하고, 각자의 권한에 맞춰 작업 템플릿을 실행할 수 있으니 팀원 간의 협업이 훨씬 부드러워졌어요.

    AWX 대시보드를 보면 성공률이 압도적으로 높은 걸 확인할 수 있는데, 정말 뿌듯하더라고요. 👍

    AWX 대시보드 자동화 작업 성공률 통계

    AWX 대시보드 자동화 작업 성공률 통계

    마주했던 도전 과제와 삽질 (Feat. 해결 과정)

    물론 AWX와 함께한 1년이 장밋빛만 있었던 건 아닙니다. 저도 꽤 많은 삽질을 했습니다. ⚠️ 특히 다음과 같은 부분에서 도전 과제가 있었어요.

    1. 자격 증명(Credential) 관리의 복잡성: 초기에는 AWS IAM(Identity and Access Management) 역할(Role)을 AWX에 연동하는 데 애를 먹었습니다. AWX가 EC2 인스턴스에서 실행될 때 IAM 역할을 상속받아 사용하는 방식과, 직접 AWS Access Key를 등록하는 방식 중 보안과 편리성을 저울질하는 데 시간이 걸렸죠. 결국, IAM 역할 기반 인증을 적극 활용하는 방향으로 정착했습니다.
    2. 동적 인벤토리(Dynamic Inventory) 스크립트 작성의 어려움: 클라우드 환경은 인스턴스가 수시로 뜨고 꺼지기 때문에, 항상 최신 인벤토리 정보를 유지하는 게 중요합니다. AWX에 내장된 클라우드 플러그인들을 최대한 활용했지만, 특정 태그(Tag)나 조건에 맞는 인스턴스만 필터링하는 복잡한 스크립트를 작성할 때는 시행착오가 많았습니다. 파이썬(Python)으로 직접 스크립트를 짜면서 디버깅하는 과정이 꽤 힘들었네요.
    3. 워크플로우(Workflow) 설계의 난이도: 여러 작업 템플릿을 연결해서 복잡한 배포 파이프라인(Pipeline)이나 운영 절차를 자동화할 때, 조건부 실행이나 실패 시 롤백(Rollback) 같은 로직을 AWX 워크플로우로 구현하는 게 생각보다 쉽지 않았습니다. 처음에는 너무 복잡하게 얽혀서 디버깅도 어려웠는데, 점차 작은 단위의 작업 템플릿으로 쪼개고, 명확한 성공/실패 조건을 정의하면서 해결해 나갔습니다.
    4. AWX 업그레이드의 험난함: 오픈소스 프로젝트이다 보니, 새로운 버전이 나올 때마다 업그레이드 가이드라인이 명확하지 않거나, 의존성 문제로 애를 먹는 경우가 있었습니다. 특히 컨테이너 환경에서 볼륨(Volume)이나 데이터베이스 마이그레이션(Migration) 과정에서 여러 번 백업/복구를 반복하며 땀 좀 흘렸습니다. 💦

    이 모든 과정들이 저를 더 단단하게 만들어주었네요. 역시 인프라는 삽질하면서 배우는 게 최고인 것 같습니다! ㅎㅎ

    AWX, 앞으로의 방향은? (마무리)

    지난 1년간 AWX 클라우드 자동화를 경험하면서 정말 많은 것을 배우고, 팀의 운영 효율성을 크게 향상시킬 수 있었습니다. 수동 작업을 줄이고, 휴먼 에러를 방지하며, 표준화된 절차를 통해 안정적인 서비스 운영을 할 수 있게 된 것이 가장 큰 성과라고 생각합니다.

    물론 아직 가야 할 길이 멀지만, 앞으로는 AWX를 CI/CD(Continuous Integration/Continuous Deployment) 파이프라인에 더욱 깊숙이 통합하고, GitOps(깃옵스) 철학을 도입하여 모든 인프라 변경 사항을 Git을 통해 관리하는 시스템을 구축하고 싶습니다. IaC(Infrastructure as Code, 코드형 인프라)를 넘어, 모든 것이 코드로 정의되고 자동으로 배포되는 이상적인 환경을 꿈꾸고 있거든요.

    혹시 여러분도 AWX를 활용한 클라우드 자동화 경험이 있으시다면, 어떤 점이 좋았고 어떤 어려움이 있었는지 댓글로 공유해 주시면 좋겠습니다. 다음 글에서는 AWX와 CI/CD 연동에 대한 좀 더 구체적인 내용을 다뤄볼까 합니다. 기대해주세요! 😊

    AWX와 클라우드 인프라 자동화의 미래 비전 인포그래픽

    AWX와 클라우드 인프라 자동화의 미래 비전 인포그래픽

  • [Kubernetes] Helm 차트 비용 최적화 전략: 클라우드 리소스 낭비 줄이기

    [Kubernetes] Helm 차트 비용 최적화 전략: 클라우드 리소스 낭비 줄이기

    [Kubernetes] Helm 차트 비용 최적화 전략: 클라우드 리소스 낭비 줄이기

    안녕하세요, 13년차 서버실 지킴이입니다. 오늘은 클라우드 비용 때문에 밤잠 설치셨던 분들을 위한 이야기를 해볼까 합니다. <code>Kubernetes 환경에서 Helm을 쓰다 보면 편리함 뒤에 숨겨진 클라우드 리소스 낭비라는 복병을 만나게 될 때가 많거든요. 저도 처음엔 이게 뭔가 싶었는데, 월말 청구서를 받아보고 깜짝 놀라 아찔했던 경험이 한두 번이 아닙니다. 😮

    특히 Helm 차트는 재사용성과 배포 편의성을 높여주지만, 기본값이 너무 후하거나, 최적화 없이 무심코 배포하다 보면 불필요하게 많은 리소스를 할당하게 됩니다. 결국 이 모든 게 불어나는 클라우드 비용으로 이어지죠. 그래서 오늘은 제가 직접 삽질하며 터득한 Helm 비용 최적화 전략에 대해 이야기해볼까 합니다. 쿠버네티스 비용 절감, 함께 고민하고 해결해나가 봐요!

    클라우드 환경에서 Helm을 사용하여 Kubernetes 리소스를 배포하고, 이 과정에서 리소스 낭비가 발생하는 상황을 개념적으로 보여주는 다이어그램입니다.

    Helm과 클라우드 비용 낭비 이해하기

    Helm(헬름)은 Kubernetes(쿠버네티스) 애플리케이션을 관리하는 패키지 매니저입니다. 복잡한 Kubernetes 배포를 Chart(차트)라는 형태로 묶어서 쉽게 설치, 업데이트, 삭제할 수 있게 해주죠. 마치 apt나 yum처럼요. 정말 편리한 도구인데, 이 편리함이 때로는 방심을 부르기도 합니다.

    많은 Helm 차트들은 ‘일단 잘 돌아가게’ 만드는 데 초점이 맞춰져 있습니다. 그래서 기본 values.yaml 파일에 명시된 리소스 요청(requests)이나 제한(limits)이 실제 필요한 것보다 훨씬 크게 잡혀있는 경우가 많아요. 예를 들어, 작은 개발 환경인데도 CPU 1코어, 메모리 2GB를 기본으로 할당한다거나 하는 식이죠. 이게 여러 개의 Pod(파드)로 복제되고, 여러 서비스가 배포되면 눈덩이처럼 불어나는 건 순식간입니다. 😱

    • 리소스 요청 (requests): Kubernetes 스케줄러가 Pod를 노드에 할당할 때 필요한 최소 리소스입니다. 이만큼은 보장해달라는 의미죠.
    • 리소스 제한 (limits): Pod가 사용할 수 있는 최대 리소스입니다. 이 이상은 사용하지 못하게 막는 거죠.

    이 두 가지 설정이 너무 높게 잡히면, 사용하지도 않는 리소스를 미리 예약하고 돈을 내는 꼴이 됩니다. 클라우드 비용 분석을 해보면 예상치 못한 곳에서 돈이 새고 있다는 걸 알 수 있어요. K8s 배포 최적화의 첫걸음은 바로 여기서 시작됩니다.

    실전 Helm 차트 리소스 최적화 단계

    1. 정확한 리소스 요청/Limit 설정

    가장 기본적이면서도 중요한 단계입니다. values.yaml 파일에서 각 컨테이너의 resources 섹션을 꼼꼼히 검토해야 합니다. 제가 직접 운영하던 서비스에서 컨테이너들이 실제 얼마나 리소스를 쓰는지 모니터링 툴로 쭉 살펴봤거든요. 처음엔 그냥 대충 넣어놨었는데, 실제로 써보니 CPU는 0.1코어, 메모리는 256MB면 충분하더라고요. 이걸 1코어 1GB로 해놓고 있었다니… 🤦‍♂️

    예시 values.yaml:

    # myapp/values.yaml
    replicaCount: 2
    
    image:
      repository: myapp/web
      pullPolicy: IfNotPresent
      tag: "1.0.0"
    
    resources:
      requests:
        cpu: 100m  # 0.1 core
        memory: 256Mi
      limits:
        cpu: 200m  # 0.2 core
        memory: 512Mi
    
    service:
      type: ClusterIP
      port: 80
    

    여기서 cpu: 100m은 0.1 코어를 의미하고, memory: 256Mi는 256 메비바이트를 의미합니다. 처음에는 조금 타이트하게 설정하고, 서비스 운영 중 모니터링하면서 점진적으로 늘려나가는 것을 추천해요. ⚠️ 너무 타이트하게 잡으면 OOMKilled(메모리 부족으로 인한 강제 종료)나 CPU Throttling(CPU 사용 제한으로 인한 성능 저하)이 발생할 수 있으니 주의해야 합니다.

    2. HPA, VPA를 활용한 자동 스케일링

    수동으로 리소스를 조정하는 건 한계가 있습니다. 사용량에 따라 자동으로 리소스를 조절해주는 Horizontal Pod Autoscaler (HPA, 수평 Pod 자동 확장)와 Vertical Pod Autoscaler (VPA, 수직 Pod 자동 확장)를 적극 활용해야 합니다. 제가 홈랩에서 여러 서비스에 적용해봤는데, 확실히 피크 타임에는 리소스를 늘려주고, 한가할 때는 줄여주니 Helm 리소스 관리에 엄청난 도움이 되더라고요!

    • HPA: Pod의 개수를 자동으로 늘리거나 줄입니다. CPU 사용률, 메모리 사용률, 또는 커스텀 메트릭을 기준으로 합니다.
    • VPA: Pod의 requests와 limits를 자동으로 조절합니다. Pod를 재시작해야 적용되는 경우가 많으니 주의해야 합니다.

    values.yaml에 HPA를 추가하는 예시:

    # myapp/values.yaml
    ...
    hpa:
      enabled: true
      minReplicas: 1
      maxReplicas: 5
      targetCPUUtilizationPercentage: 70 # CPU 사용률이 70%를 넘으면 Pod 개수 증가
      targetMemoryUtilizationPercentage: 80 # 메모리 사용률이 80%를 넘으면 Pod 개수 증가
    ...
    

    VPA는 조금 더 복잡한 설정이 필요하지만, Kubernetes 클러스터에 VPA 컨트롤러가 설치되어 있다면 values.yaml에서 간단히 활성화할 수 있습니다. K8s 배포 최적화를 위한 필수 요소라고 생각합니다.

    Helm 차트 values.yaml 리소스, HPA, VPA 설정 구성 다이어그램

    Helm 차트의 values.yaml 파일에서 리소스 요청(requests), 제한(limits), HPA, VPA 설정을 보여주는 YAML 코드 스니펫과 설명이 포함된 구성 다이어그램입니다.

    3. 불필요한 리소스 제거 및 효율적인 차트 설계

    가끔 Helm 차트를 보면 기본적으로 따라오는 사이드카 컨테이너나 불필요한 리소스들이 있어요. 예를 들어, 로깅 에이전트나 모니터링 에이전트가 모든 Pod에 기본적으로 포함되어 있는데, 이미 클러스터 레벨에서 DaemonSet(데몬셋)으로 관리하고 있다면 중복일 수 있습니다. 이런 것들은 과감히 values.yaml에서 enabled: false로 꺼버려야 합니다.

    • 불필요한 컨테이너/Sidecar(사이드카) 비활성화: values.yaml을 확인하여 사용하지 않는 기능을 끄세요.
    • 효율적인 _helpers.tpl 활용: 공통적으로 사용되는 라벨, 어노테이션 등을 _helpers.tpl 파일에 정의하여 중복을 줄이고 일관성을 유지합니다.
    • Node Affinity(노드 어피니티) / Tolerations(톨러레이션): 특정 워크로드를 특정 노드에 배치하여 리소스 활용도를 높일 수 있습니다. 예를 들어, GPU 노드에만 특정 머신러닝 워크로드를 배치하는 거죠.

    ⚠️ 주의사항과 삽질 경험

    Helm 비용 최적화는 한 번에 끝나지 않는 여정입니다. 저도 여러 번 뼈아픈 경험을 했는데요.

    • 과도한 리소스 절감은 독이 됩니다: 너무 아끼려다가 서비스가 느려지거나 뻗어버리는 경험, 저도 해봤습니다. 😭 결국 비싼 야간 작업비와 고객 불만으로 이어지더라고요. 항상 모니터링과 테스트가 병행되어야 합니다.
    • Helm Release(헬름 릴리즈) 정리: helm uninstall만으로는 완전히 사라지지 않는 경우가 있습니다. --purge 옵션을 사용하거나, Kubernetes 리소스를 직접 확인해서 삭제하는 습관을 들이세요. Secret(시크릿)이나 PersistentVolumeClaim(영구 볼륨 클레임) 같은 것들이 남아있어 불필요한 비용을 발생시키기도 합니다.
    • VPA와 HPA의 충돌: VPA와 HPA를 동시에 사용할 때 주의해야 합니다. 서로 다른 방식으로 스케일링을 시도하기 때문에 충돌이 발생할 수 있습니다. 일반적으로 VPA는 requests/limits를 조절하고, HPA는 Pod 개수를 조절하므로, VPA가 requests/limits를 관리하고 HPA는 replicaCount만 조절하도록 설정하는 것이 좋습니다.

    검증 및 지속적인 모니터링

    최적화 작업을 마쳤다면, 반드시 그 효과를 검증해야 합니다. Prometheus(프로메테우스)와 Grafana(그라파나) 같은 모니터링 툴을 활용해서 Pod별 CPU, 메모리 사용량을 꾸준히 지켜봐야 합니다. 클라우드 제공업체의 대시보드에서 실제 청구되는 비용도 확인해야겠죠. 저는 최적화 작업 후 한 달 정도 지켜보니, 이전보다 CPU 사용률은 높아졌지만, 전체 Pod 개수는 줄고 클라우드 비용도 눈에 띄게 줄어드는 걸 보면서 얼마나 뿌듯했는지 모릅니다. 🎉

    Helm 비용 최적화 전후 클라우드 리소스 사용량 및 비용 변화 대시보드 그래프

    최적화 전후의 클라우드 리소스 사용량 및 비용 변화를 보여주는 대시보드 그래프입니다.

    마무리하며: Helm 비용 최적화는 끝없는 여정

    Helm 차트 비용 최적화는 한 번 설정하고 끝나는 작업이 아닙니다. 서비스의 트래픽 패턴이 바뀌거나, 새로운 기능을 추가할 때마다 리소스 요구사항도 달라지거든요. 그래서 주기적인 검토와 튜닝이 필수입니다. 마치 자동차 정비하듯이요.

    오늘 제가 공유한 Helm 리소스 관리 팁들이 여러분의 클라우드 비용 절감에 조금이나마 도움이 되었으면 좋겠습니다. 처음엔 복잡하고 어렵게 느껴질 수 있지만, 한번 손에 익으면 클라우드 자원을 훨씬 효율적으로 사용할 수 있게 될 거예요. FinOps(파인옵스)의 관점에서 봐도, 엔지니어가 직접 비용 최적화에 참여하는 것은 매우 중요합니다.

    혹시 이런 경험 있으신가요? 아니면 더 좋은 팁이 있다면 댓글로 공유해주세요! 저도 계속 배우고 있습니다. 다음 글에서는 더 재미있는 Kubernetes 이야기로 찾아오겠습니다. 그때까지 모두 즐거운 삽질(?) 되시길 바랍니다! 😊

    Helm 비용 최적화 핵심 전략 요약 및 비용 절감 효과 비교 인포그래픽

    Helm 비용 최적화의 핵심 전략들을 요약하고, 최적화 전후의 비용 절감 효과를 인포그래픽 형태로 비교하는 이미지입니다.

  • [Cloud] Flux CD 마이그레이션 경험기: Argo CD와 비교하며

    [Cloud] Flux CD 마이그레이션 경험기: Argo CD와 비교하며

    13년차 인프라 엔지니어의 GitOps 전환기: Flux CD 마이그레이션과 Argo CD 비교

    안녕하세요! 13년차 인프라 엔지니어입니다. 오늘은 많은 분들이 관심을 갖고 계신 Flux CD 마이그레이션 경험에 대해 얘기해볼까 합니다. 기존에 Argo CD를 쓰다가 Flux CD로 전환을 고민 중이신 분들이나 이제 막 GitOps를 시작하려는 분들께 제 경험이 도움이 되길 바랍니다. 저도 처음에는 두 도구 사이에서 정말 많이 고민했거든요. 😅

    쿠버네티스 환경에서 애플리케이션 배포를 자동화하는 GitOps는 이제 선택이 아닌 필수가 되어가고 있습니다. Git 저장소를 단일 진실 공급원(Single Source of Truth)으로 삼아 인프라와 애플리케이션의 상태를 관리하는 방식인데요. 이 GitOps 워크플로우를 구현하는 데 핵심적인 역할을 하는 도구가 바로 Argo CD와 Flux CD입니다. 오늘은 이 두 도구를 비교하며, 제가 Flux CD v2로 마이그레이션하면서 겪었던 실제 경험과 배운 점들을 솔직하게 공유해볼게요.

    GitOps 아키텍처 개요 다이어그램: Git 저장소, GitOps 컨트롤러, 쿠버네티스 클러스터 간의 동기화 흐름

    GitOps의 기본적인 흐름과 도구의 역할을 보여주는 아키텍처 다이어그램입니다.

    1. GitOps와 CI/CD 도구, 왜 이렇게 중요할까요?

    개발자라면 누구나 ‘배포’라는 단어에 복잡한 감정을 느낄 겁니다. 빠르고 정확하게 배포하고 싶지만, 현실은 늘 예상치 못한 문제들로 가득하죠. 수동 배포는 실수를 유발하기 쉽고, 스크립트 기반 자동화는 관리 포인트가 자꾸 늘어납니다. GitOps는 이런 고민을 해결해주는 강력한 방법론입니다. Git 저장소를 통해 인프라와 애플리케이션의 상태를 선언적으로 관리하기 때문에, 누가 언제 무엇을 바꿨는지 명확하게 추적할 수 있고, 문제가 생기면 이전 상태로 롤백하기도 쉬워요. 롤백? 네, Git 커밋 하나면 끝입니다! 🎉

    이런 GitOps 환경을 구축할 때 Argo CD와 Flux CD는 대표적인 선택지입니다. 두 도구 모두 Git 저장소의 상태와 쿠버네티스 클러스터의 실제 상태를 동기화하는 역할을 하지만, 동작 방식이나 기능, 설정 방법 등에서 확실히 달라요. 제가 Flux CD로 마이그레이션하게 된 배경도 이런 차이점들과 저희 팀의 특정 요구사항 때문이었습니다.

    2. Argo CD vs Flux CD: 핵심 개념 비교

    마이그레이션 얘기를 시작하기 전에, 두 도구의 기본적인 차이를 짚고 넘어가겠습니다. 쉽게 말해, Argo CD는 Pull 방식에 집중하고, Flux CD는 GitOps Toolkit이라는 모듈식 접근 방식을 사용합니다.

    Argo CD는 클러스터 내부에 설치되어 Git 저장소를 주기적으로 폴링(polling)하며 변경 사항을 감지합니다. 사용자는 Argo CD UI나 CLI를 통해 Git 저장소와 클러스터의 동기화 상태를 쉽게 확인하고 관리할 수 있죠. 다양한 Git provider와의 통합이 잘 되어 있고, 사용자 친화적인 웹 UI가 강점입니다.

    반면 Flux CD는 GitOps Toolkit이라는 여러 컴포넌트(Source Controller, Kustomize Controller, Helm Controller, Notification Controller 등)로 구성돼 있어요. 각 컴포넌트가 특정 역할을 수행하며, 필요한 컴포넌트만 선택적으로 구성할 수 있습니다. Flux CD는 Git 저장소를 주기적으로 폴링하기보다는 Git hook이나 내부 Controller를 통해 변경 사항을 감지하는 방식을 선호하며, 좀 더 세밀한 제어가 가능하다는 특징이 있어요. 특히 Flux CD v2부터는 이런 모듈식 아키텍처가 훨씬 강화됐습니다.

    구분 Argo CD Flux CD (v2)
    아키텍처 단일 컨트롤러 기반 모듈식 GitOps Toolkit (Source, Kustomize, Helm Controller 등)
    설정 방식 Application CRD, UI, CLI Kustomization/HelmRelease CRD, CLI
    동기화 방식 주기적 폴링 (기본), Git hook 지원 Git hook (권장), 주기적 폴링
    UI 풍부하고 사용자 친화적인 웹 UI CLI 중심, 웹 UI는 제한적 (지속 개선 중)
    확장성/유연성 높음 매우 높음 (모듈식)
    학습 곡선 비교적 완만함 초기 학습 곡선 있음 (GitOps Toolkit 이해 필요)
    Flux CD v2 GitOps Toolkit 구성 요소 다이어그램: Source, Kustomize, Helm Controller 등 모듈 설명

    Flux CD v2의 핵심 컴포넌트인 GitOps Toolkit의 구조를 나타내는 다이어그램입니다.

    3. Flux CD v2 마이그레이션 여정: 삽질과 깨달음 😅

    저희는 기존에 Argo CD를 잘 사용하고 있었어요. 하지만 몇 가지 이유로 Flux CD v2로의 전환을 결정했습니다. 첫째, 클러스터의 상태를 좀 더 세밀하게 제어하고 싶다는 생각이 들었어요. Flux CD의 모듈식 아키텍처가 이런 요구를 충족해줄 수 있다고 판단했거든요. 둘째, Helm Chart 관리를 더 효율적으로 하고 싶었는데, Flux CD의 Helm Controller가 이를 잘 지원한다는 정보를 얻었습니다.

    마이그레이션 단계는 크게 다음과 같았습니다.

    1. Flux CD 설치 및 기본 설정: 먼저 클러스터에 Flux CD v2를 설치했어요. bootstrapping 과정을 통해 Git 저장소와 연결하고, 초기 동기화 설정을 완료합니다. 이때 Git 저장소의 디렉토리 구조나 접근 권한 등을 꼼꼼히 확인해야 했습니다.
    2. 기존 Argo CD 애플리케이션 전환: Argo CD에서 관리하던 Kubernetes Manifests (YAML 파일)나 Helm Charts를 Flux CD가 인식할 수 있는 형태로 변경했습니다. 저는 주로 Kustomize를 사용하고 있었기 때문에, Flux CD의 Kustomization Controller가 참조할 수 있도록 Git 저장소 구조를 조정하는 작업이 필요했어요.
    3. Helm Chart 관리 전환: Argo CD에서 Helm Release를 사용했다면, Flux CD의 Helm Controller를 사용하도록 설정 파일을 변경합니다. Helm Chart의 `values.yaml` 파일 관리 방식이나 Release 이름, 네임스페이스 등을 Flux CD의 `HelmRelease` CRD(Custom Resource Definition)에 맞게 수정해야 했어요. 이 과정에서 몇 가지 오타와 설정 누락으로 배포가 실패하는 경험도 했습니다. ⚠️
    4. 동기화 방식 검토 및 적용: Git hook을 사용할지, 주기적인 폴링을 사용할지 결정하고 설정했어요. 보안과 효율성을 고려했을 때 Git hook이 낫다고 판단하여, Git Provider (GitHub/GitLab 등)와 Flux CD 간의 Webhook 설정을 진행했습니다.
    5. 검증 및 테스트: 모든 설정이 완료된 후, 실제 애플리케이션 배포 및 업데이트를 반복적으로 테스트했습니다. 변경 사항이 제대로 Git에 반영되고, Flux CD가 이를 감지하여 클러스터에 적용하는지, 롤백은 잘 되는지 등을 꼼꼼히 확인했어요.
    Flux CD CLI를 이용한 Git 저장소 부트스트랩 및 초기 설정 화면

    Flux CD CLI를 사용하여 Git 저장소와 클러스터를 연결하고 초기 설정을 진행하는 과정의 일부입니다.

    4. ⚠️ 주의사항 및 트러블슈팅 경험

    마이그레이션 과정에서 몇 가지 예상 밖의 문제들을 겪었어요. 여러분도 이런 상황에 마주칠 수 있으니 미리 알아두시면 좋을 겁니다.

    • Git 저장소 구조 및 권한 문제: Flux CD는 Git 저장소의 특정 경로를 바라보고 동기화합니다. 처음에는 Git 저장소 구조를 Argo CD 방식 그대로 사용하려다 보니 Flux CD가 파일을 제대로 찾지 못하더라고요. Git 저장소 구조를 Flux CD가 이해하기 쉬운 형태로 재구성하는 게 중요했습니다. 또한 Git 저장소에 대한 클러스터의 접근 권한 (SSH Key 또는 Access Token) 설정이 올바르지 않으면 동기화 자체가 실패합니다.
    • HelmRelease CRD 설정 오류: Helm Chart를 관리할 때 `HelmRelease` CRD의 필드 이름을 잘못 입력하거나, 필수 값을 누락하는 경우가 있었어요. 예를 들어, `chart.spec.version` 대신 `chart.version`으로 잘못 입력한다거나, `releaseName`을 지정하지 않아 예상치 못한 이름으로 Helm Release가 생성되는 등의 문제가 발생했습니다. Flux CD의 공식 문서를 옆에 끼고 설정하는 걸 추천합니다.
    • Controller 간의 의존성 문제: Flux CD v2는 여러 Controller가 협력하여 동작합니다. Source Controller가 Git 저장소에서 소스를 가져오면, Kustomize Controller나 Helm Controller가 이를 받아 실제 리소스를 생성/업데이트하는 식이죠. Source Controller에 문제가 생기면 후속 Controller들도 제대로 동작하지 않아요. Flux CD의 각 Controller 상태를 `flux get kustomization`, `flux get helmrelease` 같은 명령어로 자주 확인하는 습관이 중요합니다.
    • 네임스페이스 (Namespace) 관리: Argo CD에서는 Application CRD에 네임스페이스를 지정하는 방식이 Flux CD의 `HelmRelease`나 `Kustomization` CRD와 조금 달라요. 기존 Argo CD에서 여러 네임스페이스에 걸쳐 리소스를 관리하고 있었다면, Flux CD에서는 각 CRD에 네임스페이스를 명확히 지정해주거나, Git 저장소 구조를 네임스페이스별로 분리하는 전략이 필요했습니다.

    가장 큰 삽질은 아마 Helm Chart의 `values.yaml`을 GitOps 방식으로 관리하면서 발생했던 버전 충돌 문제였어요. Git 저장소에서 Helm Chart를 가져오고, 이를 Kustomize로 패치하는 과정에서 버전 관리가 꼬여버렸거든요. 결국에는 Flux CD의 **`HelmRelease` CRD 내에서 `valuesFrom` 필드를 활용하여 Git 파일 참조 방식을 명확히 지정**해주고, Kustomize를 통한 패치보다는 `HelmRelease` 자체에서 값을 관리하는 방식으로 변경했어요. 이게 가장 큰 깨달음 중 하나였습니다. 💡

    Flux CD CLI를 이용한 동기화 상태 및 리소스 정보 확인 화면

    Flux CD의 CLI 명령어로 확인한 동기화 상태 및 리소스 정보를 보여주는 화면입니다.

    5. Flux CD 마이그레이션 결과: 뭐가 달라졌나? ✅

    Flux CD v2로 성공적으로 마이그레이션한 후, 저희는 몇 가지 긍정적인 변화를 경험했어요.

    • 향상된 모듈성과 유연성: GitOps Toolkit 덕분에 필요한 기능만 선택적으로 활성화하고 설정할 수 있게 됐어요. 이건 클러스터 리소스 사용량을 최적화하는 데도 도움이 되더라고요.
    • 강력해진 Helm Chart 관리: Helm Controller를 통해 Helm Chart 버전을 관리하고, `values.yaml`을 GitOps 방식으로 선언적으로 관리하는 게 훨씬 수월해졌어요.
    • 세밀한 제어 기능: Git hook을 통한 실시간 동기화, Controller별 상태 확인 등 이전보다 클러스터 상태를 더 세밀하게 제어하고 모니터링할 수 있게 됐습니다.
    • 개발팀의 GitOps 경험 개선: Git 저장소에 대한 변경 사항이 자동으로 클러스터에 반영되는 경험은 개발팀의 만족도를 높였어요. Pull Request를 통해 변경 사항을 리뷰하고 머지하는 과정이 자연스럽게 CI/CD 파이프라인으로 통합됐거든요.

    물론, Argo CD의 풍부한 웹 UI가 제공하는 편함은 Flux CD에서는 조금 줄어들었어요. 하지만 CLI의 발전과 커뮤니티의 노력으로 많은 부분이 개선되고 있고, 저희 팀은 CLI 중심의 워크플로우에 금방 익숙해졌습니다. 오히려 **CLI의 강력한 자동화 가능성** 덕분에 더 많은 부분을 스크립트로 관리할 수 있게 된 점을 긍정적으로 보고 있어요.

    Flux CD 마이그레이션 이후 CI/CD 파이프라인의 성공률 및 배포 속도 개선 지표를 시각화한 그래프입니다.

    6. 마무리하며: Flux CD와 함께하는 GitOps 여정

    Flux CD로의 마이그레이션은 분명 쉽지 않은 과정이었어요. Argo CD와는 다른 철학과 구조를 가지고 있었기에, 새로운 학습이 필요했고 예상치 못한 문제들과도 많이 씨름했거든요. 하지만 결과적으로 저희 팀은 GitOps 환경을 더욱 견고하고 효율적으로 구축할 수 있었습니다.

    핵심은 각 도구의 철학을 이해하고, 우리 환경에 맞는 방식을 선택하는 것입니다.

    • 단순하고 직관적인 GitOps 환경을 원하고, 풍부한 웹 UI가 중요하다면 Argo CD가 좋은 선택이 될 거예요.
    • 세밀한 제어, 모듈성, 그리고 강력한 Helm/Kustomize 통합을 원한다면 Flux CD v2가 매력적인 대안이 될 겁니다.

    아직 GitOps를 도입하지 않으셨거나, CI/CD 도구 전환을 고민하고 계신다면, 오늘 제가 나눈 경험이 조금이라도 도움이 되었으면 좋겠어요. 다음 글에서는 Flux CD의 특정 기능 (예: Policy as Code, Observability)에 대해 더 깊이 파고드는 내용을 다룰 예정이니 기대해주세요!

    궁금한 점이 있다면 언제든지 댓글로 남겨주세요. 함께 성장하는 엔지니어들이 되겠습니다! 감사합니다. 😊

  • [Cloud] GitLab CI/CD, 월 300달러 린트 비용 낭비 막는 법

    [Cloud] GitLab CI/CD, 월 300달러 린트 비용 낭비 막는 법

    안녕하세요, 13년차 인프라 엔지니어 ’13년차의 서버실’입니다. 오늘은 많은 분들이 공감하실 만한, 하지만 간과하기 쉬운 CI/CD 비용 문제에 대해 이야기해보려고 해요. 특히 GitLab CI/CD 비용 최적화와 관련해서 제가 직접 겪었던 뼈아픈 경험과 그 해결 과정을 공유합니다. 사실 저도 처음엔 CI/CD를 구축하고 나면 다 끝난 줄 알았거든요. 그런데 시간이 지나면서 예상치 못한 곳에서 비용이 새고 있더라고요. 바로 불필요하게 돌아가는 린트(Lint) 비용이었죠. 한 달에 무려 300달러나 되는 돈이 린트 때문에 나가고 있었다니, 처음엔 믿을 수가 없었어요. 오늘은 이 낭비를 어떻게 막았는지, 그 노하우를 풀어볼게요!

    GitLab CI/CD 린트 비용 낭비 및 최적화 전후 비교 다이어그램

    GitLab CI/CD 파이프라인에서 불필요한 린트(Lint) 작업으로 인해 비용이 낭비되고, 이를 최적화하여 절감하는 과정을 시각적으로 보여주는 다이어그램.

    CI/CD 린트(Lint)가 왜 비용 낭비의 주범이 될까요?

    CI/CD(Continuous Integration/Continuous Deployment, 지속적 통합/지속적 배포)는 정말 신기한 도구거든요. 코드를 푸시할 때마다 자동으로 테스트하고 배포하니까요. 이 과정에서 린트(Lint, 코드 스타일 및 잠재적 오류 검사)는 코드 품질을 유지하는 데 정말 중요한 역할을 합니다. 문제가 크기 전에 미리 잡아주니 정말 고맙죠.

    그런데 여기서 문제가 발생합니다. 대부분의 CI/CD 설정은 코드가 변경될 때마다, 즉 <code>git push 할 때마다 모든 파이프라인 작업을 실행하도록 되어 있더라고요. 작은 오타 수정이나 README 파일 업데이트 같은 사소한 변경에도 린트 작업을 포함한 모든 CI 작업이 돌아가는 거죠. 린트 작업 자체는 비교적 가볍다고 생각하기 쉽지만, 이게 수십, 수백 번 반복되면 이야기가 달라집니다. 특히 GitLab.com 같은 SaaS(Software as a Service) 환경에서는 사용량에 따라 CI/CD Runner minutes(러너 사용 시간) 비용이 발생하거든요. 제가 운영하는 홈랩에서도 처음엔 이런 비용을 크게 신경 쓰지 않았는데, 프로젝트가 많아지고 커밋이 잦아지면서 슬금슬금 비용이 올라가는 걸 보고 깜짝 놀랐습니다.

    비용 낭비의 핵심 원인:

    • 잦은 커밋: 개발 과정에서 수많은 중간 커밋이 발생합니다.
    • 불필요한 실행: 코드를 변경하지 않는 파일(예: 주석, 문서)의 변경에도 린트가 실행됩니다.
    • 모든 브랜치 실행: 개발 브랜치, 피처 브랜치 등 모든 브랜치에 푸시될 때마다 린트가 실행됩니다.

    이런 상황을 제가 직접 겪어보니, “아, 이건 뭔가 바꿔야겠다!” 싶더라고요. 그래서 CI/CD 최적화 방안을 진지하게 고민하기 시작했습니다.

    GitLab CI/CD 비용 최적화를 위한 핵심 전략: 조건부 실행 (Conditional Execution)

    GitLab CI/CD에서 비용을 절감하는 가장 효과적인 방법 중 하나는 조건부 실행(Conditional Execution)입니다. 말 그대로 특정 조건이 충족될 때만 CI/CD 작업을 실행하도록 하는 거죠. 린트 작업의 경우, 모든 커밋에 대해 실행하기보다는 특정 상황에서만 실행하도록 제한할 수 있습니다.

    제가 주로 활용한 전략은 다음과 같습니다:

    1. 머지 리퀘스트(Merge Request) 시에만 실행: 피처 브랜치에서 개발할 때는 린트 작업을 건너뛰고, 메인 브랜치로 머지하기 위한 MR이 생성될 때만 린트 검사를 실행합니다. 이렇게 하면 불필요한 중간 커밋에 대한 린트 실행을 대폭 줄일 수 있거든요.
    2. 코드 변경이 있는 파일에 대해서만 실행: 정말 필요한 경우에만 린트 작업을 실행하도록 조건을 더 세분화할 수 있습니다. 예를 들어, 특정 코드 파일이 변경되었을 때만 린트 작업을 실행하는 방식이죠.

    GitLab CI/CD는 rules 키워드를 통해 이런 조건부 실행을 강력하게 지원합니다. 처음엔 only/except 문법을 사용하기도 했는데, rules가 훨씬 유연하고 강력하더라고요. rules를 사용하면 여러 조건을 조합해서 원하는 시나리오를 만들 수 있습니다.

    실전 구현: `.gitlab-ci.yml`에 조건부 린트 잡(Job) 추가하기

    자, 그럼 이제 제가 실제로 어떻게 GitLab CI 비용 절감을 이뤄냈는지 `.gitlab-ci.yml` 설정과 함께 설명해 드릴게요. 핵심은 린트 작업을 위한 잡(Job)에 rules를 적용하는 겁니다.

    1. 머지 리퀘스트(MR) 시에만 린트 실행하기

    가장 기본적이고 효과적인 방법입니다. 개발 브랜치에 푸시할 때는 린트를 건너뛰고, MR이 생성되거나 업데이트될 때만 린트를 실행합니다. 이렇게 하면 개발 중 발생하는 수많은 중간 커밋의 CI 비용을 아낄 수 있거든요.

    
    lint_job:
      stage: lint
      image: python:3.9-slim # 린트 도구에 맞는 이미지 사용
      script:
        - pip install flake8 # 예시: Python flake8 린터 설치
        - flake8 .
      rules:
        - if: '$CI_PIPELINE_SOURCE == "merge_request_event"' # MR이 생성되거나 업데이트될 때만 실행
          when: on_success
        - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH' # 기본 브랜치(master/main)에 푸시될 때 실행 (최종 검증)
          when: on_success
    

    위 코드에서 $CI_PIPELINE_SOURCE == "merge_request_event"는 머지 리퀘스트 파이프라인에서만 이 잡(Job)을 실행하라는 의미입니다. 그리고 $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH는 메인 브랜치(보통 main 또는 master)에 직접 푸시될 때도 린트가 실행되도록 해서 최종적인 코드 품질을 보장합니다. 이렇게 설정하니 월 300달러나 나가던 린트 비용이 확 줄어들더라고요! 🎉

    GitLab CI/CD 린트 조건부 실행을 위한 .gitlab-ci.yml rules 설정

    GitLab CI/CD 설정 파일(.gitlab-ci.yml)에서 ‘rules’ 키워드를 사용하여 린트(Lint) 작업을 조건부로 실행하도록 구성된 YAML 코드 스니펫.

    2. 특정 파일 변경 시에만 린트 실행하기 (고급)

    좀 더 세밀한 제어가 필요하다면 rules:changes를 활용할 수 있습니다. 예를 들어, Python 코드 파일(.py)이 변경되었을 때만 Python 린트를 실행하고, JavaScript 파일(.js)이 변경되었을 때만 JavaScript 린트를 실행하는 식이죠.

    
    python_lint_job:
      stage: lint
      image: python:3.9-slim
      script:
        - pip install flake8
        - flake8 .
      rules:
        - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
          changes:
            - "**/*.py" # .py 파일 변경 시에만 실행
          when: on_success
        - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
          changes:
            - "**/*.py"
          when: on_success
    
    javascript_lint_job:
      stage: lint
      image: node:16-slim # Node.js 린터에 맞는 이미지 사용
      script:
        - npm install eslint
        - npx eslint .
      rules:
        - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
          changes:
            - "**/*.js" # .js 파일 변경 시에만 실행
          when: on_success
        - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
          changes:
            - "**/*.js"
          when: on_success
    

    이 방식은 파이프라인을 더욱 효율적으로 만들지만, rules:changes는 GitLab Runner가 변경된 파일을 확인하는 과정에서 약간의 오버헤드가 발생할 수 있거든요. 하지만 특정 언어의 린트가 매우 무겁거나, 모노레포(Monorepo)처럼 여러 프로젝트가 한 레포지토리에 있을 때는 정말 유용하게 활용할 수 있습니다. 제가 홈랩에서 여러 마이크로서비스를 한 레포에 넣어두고 관리할 때 이 방법을 써봤는데, 확실히 비용 절감 효과가 좋았어요.

    ⚠️ 주의사항 및 트러블슈팅: 꼼꼼함이 핵심!

    GitLab CI/CD 최적화는 비용 절감이라는 큰 장점이 있지만, 몇 가지 주의할 점도 있습니다. 제가 삽질 좀 하면서 겪었던 시행착오들을 공유해 드릴게요.

    1. 조건 설정의 오작동: rules 문법이 생각보다 까다로울 수 있거든요. 조건을 너무 복잡하게 설정하면 예상치 못하게 잡(Job)이 실행되지 않거나, 반대로 불필요하게 실행될 수 있어요. 항상 테스트를 통해 의도한 대로 동작하는지 확인해야 합니다. rules:if와 rules:changes를 함께 사용할 때는 특히 주의해야 합니다.
    2. 개발자의 실수: 머지 리퀘스트 파이프라인에서만 린트를 실행하도록 했는데, 개발자가 실수로 로컬에서 린트를 돌리지 않고 MR을 올리는 경우가 생길 수 있거든요. 이렇게 되면 MR 파이프라인에서 린트 에러가 발생하고, 다시 수정해서 커밋해야 하는 번거로움이 생기죠. 이를 방지하기 위해 프리-커밋 훅(Pre-commit Hook) 같은 도구를 도입하여 로컬에서도 린트 검사를 강제하는 것을 고려해볼 수 있습니다. 저도 이 문제 때문에 초기에는 좀 곤란했는데, husky나 lint-staged 같은 도구를 도입해서 해결했어요.
    3. 초기 러너 비용: rules:changes를 사용할 때, GitLab Runner는 변경된 파일 목록을 가져오기 위해 Git 히스토리를 확인해야 합니다. 이 과정에서 필요한 최소한의 Git 클론(clone) 작업이 발생하므로, 아주 미미하지만 초기 러너 사용 시간이 발생할 수 있거든요. 대부분의 경우 무시할 만한 수준이지만, 극단적인 최적화를 목표로 한다면 고려할 만한 요소입니다.

    가장 중요한 건, 변경 사항을 적용한 후에 GitLab CI/CD 파이프라인을 여러 시나리오(새 브랜치 푸시, MR 생성, 메인 브랜치 푸시 등)로 테스트해보고, CI/CD Analytics(분석) 탭에서 러너 사용 시간을 주기적으로 확인하는 겁니다. 저도 처음엔 설정이 제대로 됐는지 확신이 없어서 파이프라인 로그를 꼼꼼히 뜯어봤거든요. 💡

    결과 검증: 실제로 비용이 줄었는지 확인하기

    그럼 이제 가장 중요한 부분이죠. 이렇게 설정하고 나면 정말 비용 절감이 되는지 어떻게 확인할까요? GitLab은 친절하게도 CI/CD 사용량에 대한 통계를 제공하더라고요. 저는 주로 Settings → CI/CD → Usage Quotas 섹션과 Analytics → CI/CD Analytics를 활용했습니다.

    제 경험으로는, 조건부 린트 잡을 적용하기 전과 후의 Runner minutes(러너 사용 시간) 그래프가 확연히 달라지는 것을 확인할 수 있었습니다. 특히 월말에 청구되는 금액을 비교해보니, 월 300달러 가까이 나가던 CI/CD 비용이 100달러 미만으로 줄어드는 것을 보고 정말 뿌듯했습니다. 🎉 이 정도면 꽤 괜찮은 성과 아닌가요?

    GitLab CI/CD 러너 사용 시간 및 비용 최적화 효과 차트

    GitLab CI/CD Usage Quotas 또는 CI/CD Analytics 대시보드에서 러너 사용 시간(Runner minutes)이 최적화 전후로 극적으로 감소한 것을 보여주는 차트 또는 그래프.

    ✅ 핵심 검증 포인트:

    • Runner minutes 감소: 월별, 주별 러너 사용 시간이 줄어들었는지 확인.
    • 파이프라인 실행 횟수 감소: 불필요한 린트 잡의 실행 횟수가 줄었는지 확인.
    • 청구서 확인: 실제 청구되는 금액이 줄었는지 최종적으로 확인.

    마무리: 작은 변화가 큰 절약을 만듭니다

    오늘은 GitLab CI/CD 비용 최적화, 특히 불필요한 린트 비용을 절감하는 방법에 대해 제 경험을 바탕으로 이야기해 봤습니다. 사실 린트 작업 하나에 월 300달러라는 비용이 나가는 건 좀 과하다 싶었거든요. 하지만 조건부 실행(Conditional Execution)이라는 작은 변화를 통해 예상보다 훨씬 큰 비용 절감 효과를 볼 수 있었습니다.

    이러한 비용 최적화는 린트 작업뿐만 아니라, 빌드(Build), 테스트(Test) 등 다른 CI/CD 잡에도 확장해서 적용할 수 있습니다. 예를 들어, 프론트엔드 코드만 변경되었을 때는 백엔드 테스트를 건너뛰는 식으로요. 항상 “이 잡이 지금 꼭 실행되어야 하는가?”라는 질문을 던져보면 최적화 포인트를 찾기 쉬울 겁니다.

    결국, CI/CD 최적화는 단순히 비용을 줄이는 것을 넘어, 파이프라인의 효율성을 높이고 개발자들이 더 빠르고 정확하게 피드백을 받을 수 있도록 돕는 중요한 과정입니다. 저도 이 과정을 통해 CI/CD에 대한 이해를 한 단계 더 높일 수 있었어요. 여러분도 이 글을 통해 비용 절감과 효율적인 CI/CD 운영에 도움이 되셨기를 바랍니다!

    GitLab CI/CD 비용 절감 전략 요약 인포그래픽

    GitLab CI/CD 비용 절감 전략을 요약하고, 지속적인 최적화의 중요성을 강조하는 인포그래픽 또는 비교표.

    다음 글에서는 GitHub Actions에서도 유사한 비용 최적화 전략을 어떻게 적용할 수 있는지 알아보는 시간을 가져볼게요. 궁금한 점이 있다면 언제든지 댓글로 남겨주세요!

  • [Cloud] 기존 인프라를 Pulumi로 전환하기: 마이그레이션 전략 및 체크리스트

    [Cloud] 기존 인프라를 Pulumi로 전환하기: 마이그레이션 전략 및 체크리스트

    안녕하세요, 13년차의 서버실 운영자입니다. 오늘은 많은 인프라 엔지니어분들이 한 번쯤 고민해봤을 주제, 기존 인프라를 Pulumi(풀루미)로 전환하는 마이그레이션 전략에 대해 이야기해보려고 해요.

    혹시 지금도 수동으로 서버를 만들고, 설정 변경할 때마다 손으로 클릭하거나 스크립트를 돌리고 계신가요? 처음엔 괜찮았는데, 인프라가 커질수록 관리하기 너무 힘들잖아요. 저도 그랬거든요. 예전에는 직접 하나하나 다 세팅했었는데, 시간이 지나니 너무 비효율적이라는 걸 깨달았어요. 그러다 IaC(Infrastructure as Code, 코드형 인프라)에 관심을 가지게 되었고, 특히 Pulumi를 접하면서 기존 인프라를 코드로 관리하는 것에 매력을 느꼈습니다. 기존 인프라를 Pulumi로 마이그레이션(Migration)하는 과정이 쉽지는 않았지만, 그만큼 얻는 것이 많았기에 제 경험을 공유하고 싶었어요.

    이 글을 통해 여러분의 Pulumi 도입과 IaC 전환에 도움이 될 만한 실질적인 전략과 마이그레이션 체크리스트를 함께 살펴보겠습니다. 삽질했던 경험도 솔직하게 풀어낼 테니, 비슷한 고민을 하고 계신다면 분명 도움이 될 거예요. 자, 그럼 시작해볼까요?

    수동 인프라와 Pulumi 코드형 인프라 비교 다이어그램

    수동으로 관리되는 복잡한 인프라와 Pulumi로 코드화되어 정돈된 인프라의 개념적인 비교 다이어그램입니다.

    Pulumi, 왜 도입해야 할까요? IaC 전환의 필요성

    우선, Pulumi가 정확히 무엇이고 왜 우리가 굳이 기존 인프라를 Pulumi로 전환(Pulumi Migration)해야 하는지 간단하게 짚고 넘어가 볼까요?

    Pulumi(풀루미)는 Python, TypeScript, Go, C# 같은 익숙한 프로그래밍 언어로 클라우드 인프라를 코드로 정의하고 배포할 수 있는 IaC(Infrastructure as Code, 코드형 인프라) 도구예요. 쉽게 말해, AWS EC2 인스턴스나 S3 버킷 같은 리소스들을 CLI(Command Line Interface)나 콘솔에서 직접 만드는 게 아니라, Python 스크립트처럼 코드로 작성해서 관리하는 거죠.

    제가 처음 Pulumi를 써봤을 때 가장 인상 깊었던 건, 기존 프로그래밍 언어의 강점을 그대로 가져온다는 점이었어요. 조건문, 반복문, 함수 같은 일반적인 프로그래밍 로직을 인프라 코드에 적용할 수 있으니 훨씬 유연하고 강력하게 인프라를 관리할 수 있더라고요. Terraform(테라폼)도 훌륭한 IaC 도구지만, HCL(HashiCorp Configuration Language)이라는 자체 언어를 배워야 했거든요. Pulumi는 제가 이미 아는 언어로 인프라를 다룰 수 있다는 게 정말 큰 장점이었어요.

    그럼 왜 IaC로 전환(IaC 전환)해야 할까요? 기존 인프라가 잘 돌아가고 있는데 굳이 건드려야 하나 싶을 수도 있어요. 하지만 IaC는 다음과 같은 명확한 이점을 제공합니다.

    • 일관성(Consistency): 코드로 정의되니 휴먼 에러가 줄어들고, 항상 동일한 환경을 배포할 수 있어요.
    • 반복 가능성(Repeatability): 개발, 스테이징, 프로덕션 환경을 똑같이 빠르게 구축할 수 있습니다.
    • 버전 관리(Version Control): Git(깃) 같은 VCS(Version Control System)로 인프라 변경 이력을 추적하고, 필요하면 롤백(Rollback)도 가능해져요.
    • 협업(Collaboration): 여러 엔지니어가 함께 인프라를 정의하고 검토할 수 있습니다.
    • 재사용성(Reusability): 공통 모듈을 만들어 인프라 배포 시간을 단축할 수 있죠.

    이런 장점들 때문에 기존 인프라를 인프라 코드화하는 건 이제 선택이 아닌 필수가 되어가고 있어요.

    기존 인프라 Pulumi 마이그레이션 전략 및 체크리스트

    자, 이제 본론으로 들어와서, 기존에 수동으로 구축된 인프라를 Pulumi로 마이그레이션(Migration)하는 구체적인 전략과 제가 직접 겪으면서 만들어 본 체크리스트(Checklist)를 공유해 드릴게요. 한 번에 모든 걸 바꾸려고 하면 대혼란이 올 수 있으니, 점진적인 접근이 중요합니다.

    1. 사전 준비 및 환경 설정 (Pre-requisites & Setup)

    가장 먼저 Pulumi를 사용할 환경을 구축해야겠죠? 제 경험상 이 단계에서 꼼꼼하게 준비해야 나중에 삽질을 줄일 수 있더라고요.

    1. Pulumi CLI 설치: 운영체제에 맞는 Pulumi CLI를 설치합니다.
      curl -fsSL https://get.pulumi.com | sh
    2. 클라우드 프로바이더 인증 설정: AWS, Azure, GCP 등 여러분이 사용하는 클라우드에 Pulumi가 접근할 수 있도록 인증 정보를 설정해야 합니다. 예를 들어 AWS의 경우, ~/.aws/credentials 파일이나 환경 변수를 통해 설정하죠.
    3. 프로젝트 생성 및 초기화: Pulumi 프로젝트를 생성하고 사용할 언어를 선택합니다. 저는 주로 TypeScript나 Python을 사용하는데, 여기서는 Python을 예시로 들어볼게요.
      mkdir my-pulumi-infra
      cd my-pulumi-infra
      pulumi new python

      이 명령어를 실행하면 기본 템플릿 파일들이 생성됩니다.

    4. 백엔드 상태 저장소 결정: Pulumi는 인프라의 상태(State)를 관리하는데, 이 상태 파일을 어디에 저장할지 결정해야 합니다. 기본적으로 Pulumi Service(클라우드)에 저장되지만, AWS S3나 Azure Blob Storage 같은 자체 스토리지를 사용할 수도 있습니다. 프로덕션 환경에서는 자체 스토리지를 권장해요.

    2. 리소스 임포트 전략 (Resource Import Strategy)

    기존 인프라를 Pulumi로 가져오는 가장 중요한 단계입니다. Pulumi는 이미 존재하는 리소스를 코드로 임포트(Import)하는 기능을 제공하는데, 이게 정말 유용하더라고요. 수동으로 하나하나 코드를 작성하는 것보다 훨씬 빠르고 정확합니다.

    제가 해보니, 한 번에 모든 리소스를 임포트하는 것보다는 작은 단위로 쪼개서 임포트하는 것이 훨씬 안전하고 관리하기 편했습니다. 예를 들어 VPC(Virtual Private Cloud), 서브넷(Subnet), 보안 그룹(Security Group)부터 먼저 임포트하고, 그다음에 EC2 인스턴스, RDS(Relational Database Service) 데이터베이스 순으로 진행하는 식이죠.

    1. 임포트할 리소스 식별: 마이그레이션할 인프라 리스트를 작성하고, 어떤 순서로 임포트할지 우선순위를 정합니다. 의존성(Dependency)이 낮은 리소스부터 시작하는 게 좋아요.
    2. 임포트 대상 리소스 ID 확인: 클라우드 콘솔이나 CLI에서 각 리소스의 ID(또는 ARN)를 확인합니다. 이 ID는 임포트 명령에 필요해요.
    3. Pulumi 임포트 코드 작성 및 실행: Pulumi는 pulumi import 명령어를 통해 기존 리소스를 가져올 수 있습니다.
    # my_pulumi_infra/__main__.py 에 임포트할 리소스 코드를 먼저 작성합니다.
    import pulumi_aws as aws
    
    # 이 리소스는 아직 실제 AWS에 존재하지 않습니다.
    # 임포트를 위해 임시로 코드를 작성하고, 실제 리소스 ID를 지정해야 합니다.
    my_vpc = aws.ec2.Vpc("my-existing-vpc",
        cidr_block="10.0.0.0/16",
        # 기타 속성들은 실제 VPC의 속성과 일치시켜야 합니다.
        # 예를 들어, tags={ "Name": "my-existing-vpc" } 등
    )
    
    # 임포트 명령 실행 예시 (터미널에서)
    # pulumi import aws:ec2/vpc:Vpc my-existing-vpc vpc-xxxxxxxxxxxxxxxxx
    # 여기서 'vpc-xxxxxxxxxxxxxxxxx'는 실제 AWS VPC의 ID입니다.
    

    pulumi import 명령을 실행하면 Pulumi가 해당 리소스의 현재 상태를 읽어와서 __main__.py 파일에 코드를 생성해 줍니다. 이 코드를 기반으로 Pulumi 스택(Stack)과 클라우드 리소스 간의 상태가 동기화되는 거죠. 이 과정에서 diff(차이점)를 잘 확인하는 게 중요해요.

    Pulumi 기존 리소스 임포트 과정 플로우차트

    Pulumi CLI를 이용해 기존 클라우드 리소스를 코드로 임포트하는 과정의 흐름을 보여주는 플로우차트입니다.

    3. 코드 리팩토링 및 모듈화 (Code Refactoring & Modularization)

    임포트된 코드는 보통 원시적인 형태입니다. 이걸 그대로 사용하는 것보다는 리팩토링(Refactoring)하고 모듈화(Modularization)하는 과정이 꼭 필요해요.

    1. 하드코딩된 값 제거: IP 주소, 리소스 이름 등 하드코딩된 값들을 pulumi.Config나 변수로 분리하여 유연성을 높입니다.
    2. 재사용 가능한 모듈 생성: VPC, 네트워크 구성, 공통 보안 그룹 등 여러 스택에서 재사용될 수 있는 부분은 별도의 모듈(예: Python 파일)로 분리합니다. 이렇게 하면 코드가 훨씬 깔끔해지고 유지보수가 쉬워집니다.
      # modules/network.py 예시
      import pulumi_aws as aws
      
      def create_vpc(name: str, cidr_block: str):
          vpc = aws.ec2.Vpc(name, cidr_block=cidr_block)
          # 서브넷, 인터넷 게이트웨이 등 추가 생성 로직
          return vpc
      
      # my_pulumi_infra/__main__.py 에서 사용
      # from .modules import network
      # my_vpc = network.create_vpc("my-app-vpc", "10.0.0.0/16")
      
    3. Pulumi 컴포넌트 리소스 활용: 복잡한 인프라 패턴을 추상화하고 싶다면 Pulumi 컴포넌트 리소스(Component Resources)를 직접 만들어 사용하는 것도 좋은 방법입니다. 여러 개의 기본 리소스를 하나의 논리적인 단위로 묶을 수 있거든요.

    4. 테스트 및 검증 (Testing & Validation)

    코드 리팩토링 후에는 반드시 테스트(Testing)를 거쳐야 합니다. 특히 기존에 잘 운영되던 서비스에 영향을 주면 안 되니까요.

    1. Dry Run (pulumi preview): pulumi preview 명령어를 통해 실제 변경 사항이 어떻게 적용될지 미리 확인합니다. 이 단계에서 예상치 못한 변경이 없는지 꼼꼼하게 검토해야 합니다. ⚠️
    2. Staging 환경 배포: 프로덕션 환경에 바로 적용하기보다는, 최대한 프로덕션과 유사한 스테이징(Staging) 환경에 먼저 배포해보고 문제가 없는지 철저히 검증합니다.
    3. 서비스 영향도 최소화: 마이그레이션 중 서비스 중단이 불가피하다면, 다운타임(Downtime)을 최소화할 수 있는 전략(예: Blue/Green Deployment, Canary Deployment)을 고려해야 합니다.

    ⚠️ Pulumi 마이그레이션 시 주의사항 및 삽질 경험

    제가 13년간 인프라를 만져보면서 느낀 건데, 항상 계획대로만 되는 건 아니더라고요. Pulumi 마이그레이션(Migration) 과정에서도 예상치 못한 문제들이 발생했습니다. 몇 가지 주의사항(Caveats)과 제가 삽질(Troubleshooting)했던 경험을 공유해 드릴게요.

    • 상태 불일치 (State Drift): 가장 흔한 문제 중 하나입니다. Pulumi가 관리하는 코드 상태와 실제 클라우드 리소스 상태가 달라지는 경우죠. 예를 들어, Pulumi 코드를 배포한 후에 누군가 수동으로 AWS 콘솔에서 보안 그룹 설정을 변경했다면, 다음 pulumi up 실행 시 Pulumi는 이 변경 사항을 되돌리려고 할 수 있습니다. 이걸 막으려면 Pulumi가 관리하는 리소스는 절대로 수동으로 변경하지 않도록 팀원들과 약속해야 합니다. 만약 상태 불일치가 발생하면, pulumi refresh 명령으로 현재 클라우드 상태를 읽어와 Pulumi 스택 상태를 업데이트하거나, pulumi state delete 후 다시 임포트하는 등의 방법을 사용해야 합니다.
    • 의존성 문제 (Dependency Issues): 리소스 간의 의존성을 Pulumi가 정확히 파악하지 못해서 배포 순서가 꼬이는 경우가 가끔 있었습니다. 특히 복잡한 네트워크 구성에서 이런 문제가 발생하더라고요. 이럴 때는 depends_on 옵션을 명시적으로 사용해서 의존성을 지정해 주면 해결할 수 있습니다.
      my_resource_b = aws.some.ResourceB("my-resource-b",
          # ...
          opts=pulumi.ResourceOptions(depends_on=[my_resource_a])
      )
      
    • 기존 리소스 삭제 위험: pulumi import를 잘못 사용하거나, 임포트 후 코드를 수정하는 과정에서 기존 리소스가 삭제될 수 있다는 경고를 보지 못하고 진행했던 적이 있어요. 😱 항상 pulumi preview 결과를 꼼꼼히 확인하고, 삭제(Delete) 또는 교체(Replace) 작업이 있다면 다시 한번 심사숙고해야 합니다. 특히 프로덕션 환경에서는 더욱 조심해야겠죠.
    • 권한 문제: Pulumi CLI를 실행하는 계정이 필요한 클라우드 리소스에 대한 모든 권한을 가지고 있지 않아 배포가 실패하는 경우가 많았습니다. 특히 IAM(Identity and Access Management) 정책이 복잡할 때 자주 발생하더라고요. 최소한의 권한(Least Privilege) 원칙은 중요하지만, 마이그레이션 초기에는 필요한 권한을 충분히 부여하고, 안정화된 후에 점진적으로 줄여나가는 전략도 고려해 볼 만합니다.

    이런 삽질들을 겪으면서 Pulumi 마이그레이션(Pulumi Migration)은 기술적인 부분만큼이나 프로세스(Process)와 팀원 간의 소통(Communication)이 중요하다는 걸 깨달았습니다. 항상 변경 사항을 공유하고, 리뷰하는 과정을 거치는 것이 중요해요.

    마이그레이션 완료 후 검증 및 관리

    성공적으로 Pulumi 마이그레이션(Pulumi Migration)을 마쳤다면, 이제 모든 것이 제대로 작동하는지 검증(Validation)해야 합니다. 그리고 앞으로 Pulumi로 관리되는 인프라를 어떻게 운영할지에 대한 계획도 세워야겠죠?

    1. Pulumi 스택 상태 확인: pulumi stack ls, pulumi stack select <stack_name>, pulumi stack output 명령어를 통해 현재 스택의 상태와 배포된 리소스 정보를 확인합니다.
    2. 클라우드 리소스 확인: 실제 클라우드 콘솔이나 CLI에서 Pulumi가 배포한 리소스들이 의도한 대로 생성되고 설정되었는지 다시 한번 확인합니다. 불필요한 리소스가 남아있지는 않은지도 확인해야 해요.
    3. 모니터링 연동: 기존에 사용하던 모니터링 시스템(예: Prometheus, Grafana, CloudWatch)과 Pulumi로 배포된 리소스들이 제대로 연동되어 데이터를 수집하고 있는지 확인합니다.
    4. CI/CD 파이프라인 구축: Pulumi 코드를 Git 저장소에 올리고, CI/CD(Continuous Integration/Continuous Delivery) 파이프라인을 구축하여 코드 변경 시 자동으로 pulumi preview, pulumi up이 실행되도록 자동화하는 것이 좋습니다. 제가 직접 해보니, 이렇게 자동화했을 때 인프라 배포 속도와 안정성이 엄청나게 향상되더라고요.
    Pulumi 클라우드 리소스 관리 대시보드 예시

    Pulumi를 통해 배포 및 관리되는 클라우드 리소스들의 현황을 한눈에 볼 수 있는 가상의 대시보드 화면입니다.

    마이그레이션을 마치며: Pulumi와 함께 성장하기

    지금까지 기존 인프라를 Pulumi로 전환(Pulumi 마이그레이션)하는 전략과 제가 겪었던 경험들을 공유해 드렸습니다. 처음에는 낯선 IaC 도구와 씨름하는 것이 쉽지 않게 느껴질 수 있어요. 저도 처음엔 기존 스크립트나 수동 작업에 익숙해서 ‘이걸 언제 다 코드로 바꾸나’ 막막했었거든요. 하지만 한 번 전환하고 나니, 인프라 관리가 훨씬 수월해지고, 개발 생산성(Developer Productivity)도 눈에 띄게 좋아지는 걸 체감할 수 있었습니다.

    Pulumi 도입(Pulumi 도입)은 단순히 도구 하나를 바꾸는 것을 넘어, 인프라를 바라보는 관점을 바꾸는 중요한 과정이라고 생각합니다. 코드로 인프라를 정의하고 관리함으로써, 우리는 더 예측 가능하고, 안정적이며, 확장 가능한 시스템을 구축할 수 있게 되죠. 인프라 코드화의 여정은 계속될 거예요.

    이 글이 여러분의 Pulumi 마이그레이션(Pulumi 마이그레이션) 여정에 작은 도움이 되었기를 바랍니다. 다음번에는 Pulumi의 고급 기능이나 특정 클라우드 서비스 연동에 대해 더 깊이 파고들어 보는 시간을 가져볼게요. 혹시 궁금한 점이나 공유하고 싶은 삽질 경험이 있다면 언제든지 댓글로 남겨주세요! 저도 배우는 게 많을 겁니다. 🎉

    Pulumi IaC 및 DevOps 요약 인포그래픽

    Pulumi 로고와 함께 IaC, DevOps, 자동화, 클라우드 관리 등의 핵심 개념을 시각적으로 요약한 인포그래픽입니다.