13년차의 서버실

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

[태그:] DevOps

  • [Cloud] Jenkins 빌드 실패 디버깅: 흔한 문제와 해결 전략 5가지

    [Cloud] Jenkins 빌드 실패 디버깅: 흔한 문제와 해결 전략 5가지

    [Jenkins] 빌드 실패 디버깅: 흔한 문제와 해결 전략 5가지

    안녕하세요, 13년차 인프라 엔지니어 ’13년차의 서버실’입니다. 오늘도 제 홈랩에서 밤늦게까지 삽질 좀 했네요. 😅

    1. 젠킨스 빌드 실패, 혹시 오늘도?

    인프라 엔지니어라면 누구나 한 번쯤은 젠킨스(Jenkins) 빌드 실패 화면 앞에서 한숨을 쉬어본 경험이 있으실 겁니다. 분명 로컬에서는 잘 되던 코드가 젠킨스만 올라가면 에러를 뿜어내고, 이유를 찾다 보면 어느새 퇴근 시간이 훌쩍 지나버리곤 하죠. 저도 13년차 베테랑이라고는 하지만, 가끔 젠킨스 빌드 실패 로그를 보면 머리를 쥐어뜯게 만듭니다.

    하지만 너무 좌절하지 마세요! 수많은 삽질 끝에 얻은 저만의 노하우가 있거든요. 오늘은 젠킨스(Jenkins) 빌드 실패의 흔한 원인들을 파헤치고, 13년차 인프라 엔지니어인 제가 실제로 겪고 해결했던 젠킨스 문제 해결 전략 5가지를 여러분께 멘토처럼 자세히 알려드리려고 합니다. CI/CD 트러블슈팅 시간을 단축하고 더 견고한 빌드 자동화 시스템을 만드는 데 큰 도움이 되기를 바랍니다. 자, 그럼 시작해볼까요? 🎉

    Jenkins CI/CD 파이프라인 개요 다이어그램

    Jenkins CI/CD 파이프라인 개요 다이어그램

    2. Jenkins 빌드 실패, 왜 늘 우리를 괴롭힐까요?

    젠킨스(Jenkins)는 CI/CD(Continuous Integration/Continuous Delivery, 지속적인 통합/지속적인 배포) 파이프라인의 핵심 도구로, 개발자가 코드를 커밋(Commit)하면 자동으로 빌드(Build), 테스트(Test), 배포(Deploy)까지 해주는 참 고마운 친구입니다. 하지만 이 과정에서 수많은 외부 요인과 내부 설정들이 복합적으로 작용하기 때문에, 빌드 실패는 피할 수 없는 숙명처럼 느껴질 때가 많습니다.

    쉽게 말해, 젠킨스 빌드는 여러 단계를 거치는데, 각 단계마다 환경 설정, 외부 라이브러리, 시스템 자원, 네트워크 등 고려해야 할 것이 한두 가지가 아닙니다. 그래서 로컬 환경과 젠킨스 서버 환경의 미묘한 차이 하나가 빌드 실패라는 거대한 벽으로 다가오곤 하죠. 결국, 젠킨스 에러를 해결하는 과정은 마치 탐정이 되어 단서를 찾아 범인을 잡는 과정과 비슷합니다. 그럼, 이제 실제 발생했던 흔한 문제들과 그 해결 전략들을 하나씩 살펴보겠습니다.

    3. 흔한 Jenkins 빌드 실패 유형과 해결 전략 5가지

    3.1. 🚨 환경 변수 (Environment Variable) 문제: “PATH가 왜 이래?”

    가장 흔하고도 사람을 미치게 하는 문제 중 하나가 바로 환경 변수(Environment Variable) 문제입니다. 제가 직접 해보니, 쉘 스크립트(Shell Script)로 특정 명령어를 실행했는데 ‘command not found’ 에러가 뜨는 경우가 부지기수였어요. 로컬에서는 분명 <code>JAVA_HOME이나 PATH가 잘 잡혀있는데 젠킨스에서는 딴판이더라고요. 삽질 좀 했습니다 ㅎㅎ.

    • 문제 발생 원인: 젠킨스 에이전트(Agent)가 실행되는 환경과 개발자의 로컬 환경의 PATH, JAVA_HOME, M2_HOME 등 주요 환경 변수가 다르게 설정되어 있거나 아예 없는 경우입니다. 젠킨스 에이전트는 일반 사용자 계정으로 실행되는 경우가 많아서, 시스템 전역(Global) 환경 변수를 제대로 인식하지 못할 수 있거든요.
    • 해결 전략:
      1. Jenkins Global Tool Configuration 활용: 젠킨스 관리(Manage Jenkins) > Global Tool Configuration 메뉴에서 JDK, Git, Maven, Gradle, Node.js 등 빌드에 필요한 도구들의 경로를 직접 설정하면 돼요. 젠킨스가 이 경로를 기준으로 환경 변수를 자동으로 설정해주니, 이 방법을 가장 먼저 고려해보세요.
      2. Jenkinsfile 내에서 환경 변수 설정: 파이프라인 스크립트(Pipeline Script)인 Jenkinsfile 내에서 withEnv 스텝을 사용해 특정 환경 변수를 명시적으로 설정할 수 있어요. 이는 해당 스텝 내에서만 유효한 환경 변수를 설정할 때 유용합니다.

    예시: Jenkinsfile에서 환경 변수 설정

    pipeline {
        agent any
        stages {
            stage('Build') {
                steps {
                    script {
                        withEnv([
                            "JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64",
                            "PATH+JDK=${env.JAVA_HOME}/bin"
                        ]) {
                            sh 'java -version'
                            sh 'mvn clean install'
                        }
                    }
                }
            }
        }
    }
    

    3.2. 📦 의존성 (Dependency) 누락 또는 버전 불일치: “라이브러리가 없다고?”

    로컬에서는 잘 되는데 젠킨스에서만 안 된다고요? 십중팔구 이 녀석 때문입니다. 제가 처음엔 이게 뭔가 싶었는데, 빌드 실패 로그를 자세히 살펴보니 특정 라이브러리를 찾을 수 없다는 에러가 뜨더라고요. 개발 환경과 젠킨스 환경의 의존성이 달라서 생기는 문제였습니다.

    • 문제 발생 원인: 프로젝트가 필요로 하는 라이브러리나 모듈(Module)이 젠킨스 에이전트 환경에 설치되어 있지 않거나, 로컬과 다른 버전이 설치되어 호환성 문제가 발생하는 경우입니다. 특히 Node.js 프로젝트의 node_modules나 Maven/Gradle 프로젝트의 로컬 리포지토리(Repository) 캐시 문제로 자주 발생합니다.
    • 해결 전략:
      1. 의존성 설치 스텝 추가: 빌드 전 항상 필요한 의존성 설치 명령어를 실행하도록 Jenkinsfile이나 빌드 스크립트에 추가하면 좋아요. 예를 들어, Node.js 프로젝트는 npm install, Maven 프로젝트는 mvn clean install, Gradle 프로젝트는 gradle build를 실행해야 합니다.
      2. 캐시(Cache) 관리: 젠킨스 워크스페이스(Workspace)를 매 빌드마다 클린(Clean)하게 유지하거나, 의존성 캐싱(Caching)을 위한 플러그인(Plugin)을 활용하여 다운로드 시간을 줄이고 일관성을 유지할 수 있어요.
      3. 버전 명시: package.json(Node.js), pom.xml(Maven), build.gradle(Gradle) 등 의존성 관리 파일에 라이브러리 버전을 명시하면 환경 간의 불일치를 최소화할 수 있습니다.

    예시: Maven 프로젝트 의존성 설치 스텝

    pipeline {
        agent any
        stages {
            stage('Dependencies & Build') {
                steps {
                    sh 'mvn clean install -DskipTests'
                }
            }
        }
    }
    
    Jenkins 빌드 설정 화면 (의존성 관리)

    Jenkins 빌드 설정 화면 (의존성 관리)

    3.3. 🔐 권한 (Permission) 문제: “파일 접근이 안 된다고?”

    빌드 스크립트가 특정 파일에 쓰려고 하는데 ‘Permission denied’ 에러를 뿜어낼 때의 그 허탈함이란… 저도 처음엔 젠킨스 실행 계정이 어떤 권한을 가지고 있는지 제대로 파악하지 못해서 꽤나 삽질했습니다. 특히 파일 시스템에 직접 접근하거나 특정 도구를 실행할 때 많이 발생하더라고요.

    • 문제 발생 원인: 젠킨스 에이전트가 실행되는 사용자 계정(보통 jenkins 사용자)이 빌드 과정에서 필요한 파일이나 디렉토리(Directory)에 대한 읽기/쓰기/실행 권한이 없는 경우입니다. 특정 서비스 계정으로만 접근 가능한 리소스(Resource)에 접근하려 할 때도 문제가 생깁니다.
    • 해결 전략:
      1. 젠킨스 사용자 권한 확인: 젠킨스 에이전트가 어떤 사용자로 실행되는지 확인하고, 해당 사용자가 빌드에 필요한 모든 리소스에 접근할 수 있도록 권한을 부여하면 돼요. ls -al 명령어로 파일/디렉토리의 소유자(Owner)와 권한을 확인해보세요.
      2. sudoers 설정: 젠킨스 사용자가 특정 명령어를 sudo로 실행해야 할 경우, /etc/sudoers 파일에 해당 명령어를 NOPASSWD 옵션으로 추가하면 비밀번호 없이 실행할 수 있어요. (⚠️ 보안에 유의하여 최소한의 권한만 부여해야 합니다.)
      3. 쓰기 권한 부여: 빌드 과정에서 파일을 생성하거나 수정하는 디렉토리에 젠킨스 사용자가 쓰기(Write) 권한을 가지고 있는지 chmod 명령어로 확인하고 조정하면 됩니다.

    예시: 권한 확인 및 변경

    # 젠킨스 워크스페이스 디렉토리 권한 확인
    ls -ld /var/lib/jenkins/workspace/MyProject
    
    # 필요한 경우 젠킨스 사용자에게 소유권 부여
    sudo chown -R jenkins:jenkins /var/lib/jenkins/workspace/MyProject
    
    # 특정 스크립트에 실행 권한 부여
    chmod +x myscript.sh
    

    3.4. 💾 메모리/디스크 공간 부족: “아니, 또 공간 부족이야?”

    어느 날 갑자기 빌드가 죽더니 ‘OutOfMemoryError’를 뱉는 겁니다. 슬레이브 서버에 가보니 디스크가 꽉 차 있었죠. 이런 상황은 처음엔 정말 당황스러운데, 13년차인 저도 겪어보니 꽤 흔한 문제더라고요. 특히 대규모 프로젝트나 많은 빌드 아티팩트(Artifact)가 쌓일 때 자주 발생합니다.

    • 문제 발생 원인: 젠킨스 에이전트 서버의 물리적 메모리(RAM)나 디스크 공간이 부족하여 빌드 프로세스가 중단되는 경우입니다. Java 기반 애플리케이션의 경우 JVM 힙(Heap) 메모리 부족으로 OutOfMemoryError가 발생할 수 있고, 빌드 아티팩트나 임시 파일이 과도하게 쌓여 디스크 공간이 고갈되기도 합니다.
    • 해결 전략:
      1. 디스크 사용량 확인 및 정리: df -h 명령어로 디스크 사용량을 확인하고, du -sh * 명령어로 어느 디렉토리가 공간을 많이 차지하는지 파악하면 돼요. 젠킨스 워크스페이스 클린업 플러그인(Workspace Cleanup Plugin)을 활용하여 오래된 빌드 파일들을 주기적으로 삭제하는 것도 효과적입니다.
      2. 메모리 증설 또는 JVM 설정 조정: 서버의 물리적 메모리를 증설하거나, 빌드 스크립트에서 JVM 옵션(예: -Xmx)을 조정하면 애플리케이션에 할당되는 최대 힙 메모리 크기를 늘릴 수 있어요.
      3. Swap 공간 활성화/증설: 물리적 메모리가 부족할 경우, 스왑(Swap) 공간을 활성화하거나 증설하면 시스템 안정성을 확보할 수 있습니다.

    예시: 디스크 사용량 확인

    df -h
    # /var/lib/jenkins/workspace 디렉토리의 용량 확인
    du -sh /var/lib/jenkins/workspace/*
    
    Jenkins 빌드 로그 (메모리 부족 에러)

    Jenkins 빌드 로그 (메모리 부족 에러)

    3.5. ⏳ 타임아웃 (Timeout) 및 네트워크 문제: “왜 이렇게 오래 걸려?”

    빌드가 아무런 에러 메시지 없이 그냥 ‘실패(Failure)’로 뜨는 경우가 있습니다. 처음엔 이게 뭔가 싶었는데, 로그를 자세히 보니 특정 작업이 너무 오래 걸려서 타임아웃(Timeout)으로 종료된 거였더라고요. 특히 Git Fetch가 너무 오래 걸려서 빌드가 실패하는 걸 보고, 네트워크 문제일 거라곤 상상도 못 했습니다.

    • 문제 발생 원인: 빌드 스텝(Step) 중 특정 작업(예: Git Clone/Fetch, 외부 API 호출, 대규모 컴파일)이 설정된 시간 안에 완료되지 못하고 타임아웃되어 빌드가 중단되는 경우입니다. 또한, 젠킨스 에이전트에서 외부 리소스(예: Git 리포지토리, 의존성 미러 서버)에 대한 네트워크 연결이 불안정하거나 프록시(Proxy) 설정이 잘못되어 통신에 실패하는 경우도 흔합니다.
    • 해결 전략:
      1. 타임아웃 설정 조정: 젠킨스 파이프라인(Pipeline) 스크립트 내에서 timeout 스텝을 사용해 개별 스텝 또는 전체 스테이지(Stage)의 실행 시간을 충분히 늘려주면 돼요. SCM(Source Code Management) 설정에서도 타임아웃을 조정할 수 있습니다.
      2. 네트워크 연결성 확인: 젠킨스 에이전트 서버에서 ping, curl, traceroute 등의 명령어를 사용해 외부 리소스에 대한 네트워크 연결성을 확인하면 돼요. 방화벽(Firewall)이나 보안 그룹(Security Group) 설정도 점검해보세요.
      3. 프록시 설정: 회사 네트워크 환경에서 프록시 서버를 통해 외부 네트워크에 접속해야 한다면, 젠킨스 시스템 설정(Manage Jenkins > System)이나 빌드 스크립트 내에서 http_proxy, https_proxy 환경 변수를 올바르게 설정해야 합니다.

    예시: Jenkinsfile에서 타임아웃 설정

    pipeline {
        agent any
        stages {
            stage('Long Running Task') {
                options {
                    timeout(time: 30, unit: 'MINUTES') // 30분 타임아웃 설정
                }
                steps {
                    sh 'your_long_running_command_here'
                }
            }
        }
    }
    

    4. 💡 젠킨스 로그, 최고의 디버깅 친구 (트러블슈팅/검증)

    제가 13년 동안 수많은 젠킨스 빌드 실패를 겪으면서 얻은 가장 큰 교훈은 바로 ‘로그를 꼼꼼히 읽는 습관’이 삽질 시간을 줄이는 가장 확실한 방법이라는 겁니다. 젠킨스 콘솔 출력(Console Output)은 단순히 빌드 진행 상황만 보여주는 것이 아니라, 실패의 원인을 알려주는 가장 중요한 단서들이 담겨 있거든요.

    • 로그 확인 방법: 실패한 빌드 번호를 클릭한 후, 좌측 메뉴에서 ‘Console Output’을 선택하면 빌드 전체 로그를 볼 수 있어요.
    • 핵심 에러 메시지 찾기: 로그가 너무 길어서 어디부터 봐야 할지 모르겠다면, ‘ERROR’, ‘FAILED’, ‘Exception’, ‘Permission denied’, ‘OutOfMemoryError’, ‘timeout’ 같은 키워드로 검색해보세요. 보통 에러 발생 지점 주변에 원인을 파악할 수 있는 중요한 정보들이 있습니다.
    • 스택 트레이스(Stack Trace) 분석: Java 기반 프로젝트의 경우, 스택 트레이스를 통해 어떤 코드 라인에서 예외(Exception)가 발생했는지 정확히 파악할 수 있어요.

    로그를 읽는 것은 마치 개발자와 젠킨스가 대화하는 내용을 엿듣는 것과 같습니다. 이 대화 속에서 우리는 문제의 핵심을 찾을 수 있어요. 처음엔 어렵겠지만, 꾸준히 하다 보면 어느새 여러분도 젠킨스 로그 전문가가 되어 있을 겁니다! 🎉

    Jenkins 빌드 실패 해결 전략 5가지 요약 인포그래픽

    Jenkins 빌드 실패 해결 전략 5가지 요약 인포그래픽

    5. 마무리: “삽질은 거름이 됩니다!”

    오늘은 젠킨스(Jenkins) 빌드 실패의 흔한 원인 5가지와 그 해결 전략에 대해 이야기해봤습니다. 환경 변수, 의존성, 권한, 자원 부족, 타임아웃 및 네트워크 문제까지, 젠킨스 빌드를 괴롭히는 다양한 요소들을 짚어봤는데요.

    CI/CD 파이프라인은 복잡하지만, 각 실패 원인을 체계적으로 파악하고 해결해나가는 과정은 여러분의 문제 해결 능력을 한층 더 성장시킬 겁니다. 저도 수많은 삽질을 통해 이 자리까지 올 수 있었거든요. 여러분의 삽질은 결코 헛되지 않을 겁니다! 오히려 더 견고한 시스템을 만드는 데 필요한 귀한 거름이 될 거예요.

    다음번에는 젠킨스 파이프라인(Jenkins Pipeline)을 더욱 효율적으로 관리하는 방법에 대해 다뤄볼까 합니다. 혹시 다루고 싶은 주제가 있다면 언제든지 댓글로 남겨주세요! 여러분의 성공적인 빌드 자동화를 응원하며, ’13년차의 서버실’은 다음 글에서 또 찾아뵙겠습니다. 감사합니다! 😊

  • [k8s] K3s와 AWX 통합 사례 연구: 경량 Kubernetes 자동화 워크플로우 구축

    [k8s] K3s와 AWX 통합 사례 연구: 경량 Kubernetes 자동화 워크플로우 구축

    안녕하세요, 13년차의 서버실입니다. 오늘은 제가 홈랩에서 직접 경험하고 삽질하면서 얻은 지식 하나를 여러분과 공유해볼게요. 바로 K3s(케이쓰리쓰)와 AWX(에이더블유엑스)를 통합하여 경량 Kubernetes(쿠버네티스) 환경에서 자동화 워크플로우를 구축하는 사례입니다.

    작은 규모의 인프라나 홈랩에서 Kubernetes를 운영하다 보면, 리소스 제약 때문에 고민이 많아지죠. 게다가 반복적인 배포나 설정 변경 작업을 수동으로 하다 보면 시간 낭비는 물론이고 휴먼 에러 발생 확률도 높아지고요. 저도 “이걸 어떻게 하면 좀 더 효율적으로 관리할 수 있을까?” 하는 고민에 빠져있었거든요. 그러다가 경량 Kubernetes인 K3s와 Ansible(앤서블) 기반의 자동화 플랫폼인 AWX의 조합을 떠올리게 됐습니다.

    이 둘을 잘 엮으면 리소스 효율적인 환경에서 강력한 자동화 시스템을 만들 수 있겠다는 확신이 들었죠. 그래서 직접 부딪혀가며 구축해봤고, 그 과정과 삽질 경험을 솔직하게 풀어보려 합니다. 혹시 여러분도 비슷한 고민을 하고 계셨다면, 이 글이 좋은 가이드가 될 수 있을 거예요. 💡

    K3s와 AWX를 통합한 자동화 워크플로우의 전체 아키텍처 다이어그램

    K3s 클러스터 위에 AWX가 배포되어 외부 Git 리포지토리의 Ansible 플레이북을 가져와 다른 K3s/Kubernetes 클러스터에 배포하는 통합 아키텍처 다이어그램입니다.

    K3s와 AWX, 왜 이 조합일까요?

    먼저, 이 두 가지 기술이 무엇인지, 그리고 왜 함께 사용하면 시너지가 나는지 간단하게 짚고 넘어가겠습니다.

    K3s: 가볍지만 강력한 경량 Kubernetes

    K3s(케이쓰리쓰)는 Rancher Labs(랜처 랩스)에서 만든 경량 Kubernetes(쿠버네티스) 배포판입니다. 이름에서 알 수 있듯이 ‘K8s(케이츠) – 5 = K3s’라는 유머러스한 의미를 가지고 있어요. 그만큼 바이너리 크기가 작고, 메모리 사용량도 적으니까요. 일반적인 Kubernetes는 컨트롤 플레인(Control Plane) 구성 요소들이 많은 리소스를 요구하는데, K3s는 SQLite를 기본 데이터베이스로 사용하고 불필요한 기능을 제거해서 엣지 컴퓨팅(Edge Computing), IoT(사물 인터넷), 또는 저사양 환경에 정말 딱 맞습니다. 제가 홈랩에서 운영하는 라즈베리 파이(Raspberry Pi) 클러스터 같은 곳에 정말 찰떡이더라고요. ✅

    AWX: Ansible 자동화 플랫폼의 중앙 허브

    AWX(에이더블유엑스)는 Red Hat(레드햇)의 Ansible Tower(앤서블 타워)의 오픈소스 업스트림 프로젝트입니다. Ansible(앤서블)은 강력한 IT 자동화 도구인데, AWX는 이 Ansible 플레이북(Playbook)들을 웹 UI를 통해 중앙에서 관리하고 실행할 수 있게 해줍니다. 그냥 복잡한 커맨드 라인(Command Line) 없이도 프로젝트, 인벤토리(Inventory), 자격 증명(Credential) 등을 관리하고, 잡 템플릿(Job Template)을 만들어 반복적인 작업을 예약하거나 실행 결과를 쉽게 확인할 수 있죠. 특히 팀 단위로 자동화 작업을 공유하고 접근 제어를 해야 할 때 진짜 빛을 발합니다. 🎉

    통합의 시너지: 경량 K3s 위의 자동화 플랫폼

    저는 K3s의 가벼움 위에 AWX를 올려서 경량 Kubernetes 환경을 위한 자동화 허브를 구축하고 싶었습니다. K3s에서 돌아가는 애플리케이션의 배포나 설정을 AWX를 통해 관리하려는 거였거든요. 예를 들어, 새로운 컨테이너 이미지를 빌드하면 AWX가 자동으로 K3s 클러스터에 배포하도록 하거나, 특정 서비스의 스케일링(Scaling) 작업을 AWX 잡 템플릿으로 만들어서 필요할 때마다 클릭 한 번으로 실행하는 식입니다. 이렇게 하면 인프라 관리가 정말 수월해지더라고요. 💡

    실전 구현: K3s 위에 AWX 배포하기

    이제 제가 직접 K3s 클러스터에 AWX를 배포했던 과정을 단계별로 설명해 드릴게요. 저는 이미 K3s 클러스터가 구성되어 있다는 가정하에 진행했습니다. 아직 K3s 설치가 안 되어 있다면, 다음 명령어로 간단하게 설치할 수 있습니다.

    curl -sfL https://get.k3s.io | sh -

    1. AWX Operator 설치

    AWX는 Kubernetes 환경에 AWX Operator(오퍼레이터)를 통해 배포하는 것이 가장 일반적입니다. Operator는 특정 애플리케이션(여기서는 AWX)을 Kubernetes의 컨트롤러처럼 관리해주는 도구라고 생각하시면 돼요. 배포부터 업데이트, 스케일링까지 알아서 처리해주니까요.

    AWX Operator를 설치하려면 공식 AWX Operator 저장소에서 최신 설치 가이드를 확인하시고, 다음 명령어로 설치합니다.

    kubectl apply -f https://raw.githubusercontent.com/ansible/awx-operator/devel/deploy/operator.yaml

    이 명령어가 AWX Operator와 필요한 CRD(Custom Resource Definition, 사용자 정의 리소스 정의)를 모두 설치해줍니다. CRD는 Kubernetes가 AWX라는 새로운 리소스 타입을 이해할 수 있도록 해주는 설정 파일이거든요. 이 과정을 거치면 awx-operator 파드(Pod)가 실행되면서 AWX 인스턴스를 배포할 준비가 완료됩니다.

    2. AWX 인스턴스 배포

    Operator가 준비되었다면, 이제 우리가 원하는 AWX 인스턴스를 생성할 차례입니다. AWX라는 Custom Resource(CR) 객체를 생성하면, Operator가 이 CR을 감지하고 실제 AWX 애플리케이션을 배포해줍니다. 저는 awx.yaml이라는 파일을 만들어서 다음과 같이 설정했습니다.

    apiVersion: awx.ansible.com/v1beta1
    kind: AWX
    metadata:
      name: my-awx
    spec:
      # AWX Operator가 배포할 AWX 인스턴스의 이름
      # 여기서는 my-awx로 지정했습니다.
    
      # ingressType: Ingress를 사용하여 Kubernetes Ingress를 통해 AWX에 접근하도록 설정합니다.
      # K3s는 기본적으로 Traefik Ingress Controller를 포함하고 있어 편리합니다.
      ingressType: Ingress
      # ingress_host: AWX에 접근할 호스트 이름을 지정합니다.
      # 실제 환경에서는 DNS에 이 호스트를 등록해야 합니다.
      ingress_host: awx.myhomelab.local
      
      # 기타 설정 (필요에 따라 주석 해제 및 수정)
      # postgres_storage_class: AWX 데이터베이스를 위한 Persistent Volume Claim(PVC)의 StorageClass를 지정합니다.
      # 홈랩 환경에서는 hostPath나 local-storage 등을 사용할 수 있습니다.
      # postgres_storage_class: standard
      
      # web_extra_volume_mounts: AWX 웹 파드에 추가 볼륨을 마운트할 때 사용합니다.
      # e.g., 인증서 마운트 등
      # tasks_extra_volume_mounts: AWX 태스크 파드에 추가 볼륨을 마운트할 때 사용합니다.
      
      # image_version: 사용할 AWX 이미지 버전 (특정 버전을 고정할 경우)
      # image_version: "23.3.0"
      
      # resource_requirements: AWX 파드들의 리소스 요청/제한을 설정합니다.
      # 작은 환경에서는 이 부분을 적절히 조절해야 합니다.
      # web_resource_requirements:
      #   requests:
      #     cpu: 500m
      #     memory: 1Gi
      #   limits:
      #     cpu: 1000m
      #     memory: 2Gi
    

    이 파일을 저장하고 kubectl apply -f awx.yaml 명령어를 실행하면 AWX Operator가 AWX 인스턴스에 필요한 Deployment(디플로이먼트), Service(서비스), Ingress(인그레스), Persistent Volume Claim(PVC) 등을 자동으로 생성하기 시작합니다.

    K3s 클러스터에 AWX 파드들이 정상적으로 실행 중임을 나타내는 터미널 화면

    AWX 웹 UI의 로그인 화면 또는 kubectl get pods -n awx 명령어를 실행했을 때 AWX 관련 파드들이 모두 Running 상태인 것을 보여주는 스크린샷입니다.

    ⚠️ 삽질 경험과 트러블슈팅

    제가 이 과정을 진행하면서 겪었던 몇 가지 삽질 경험과 해결 방법을 공유합니다. 여러분은 저처럼 헤매지 않으시길 바라요! 😂

    1. 리소스 부족 문제

    K3s는 가볍지만, AWX는 생각보다 많은 리소스를 필요로 합니다. 특히 데이터베이스(PostgreSQL)와 웹, 태스크 파드들이 동시에 돌아가기 때문에, 2GB RAM 이하의 시스템에서는 버벅이거나 파드가 계속 재시작되는 문제가 발생할 수 있어요. 최소 4GB RAM, 2코어 CPU 이상을 권장합니다. 저도 처음엔 라즈베리 파이 4(4GB 모델)에서 돌렸는데, 다른 서비스들과 함께 돌리니 정말 아슬아슬하더라고요. 결국 8GB 모델로 업그레이드하거나, AWX 전용 노드를 따로 두는 것을 고려하게 됐습니다.

    해결책: awx.yaml 파일의 resource_requirements 섹션을 통해 각 파드의 리소스 요청(requests)과 제한(limits)을 조절할 수 있습니다. 하지만 너무 낮추면 성능 문제가 발생하니, 하드웨어 사양을 충분히 확보하는 것이 최고의 방법입니다.

    2. Persistent Volume(영구 볼륨) 설정

    AWX는 데이터베이스(PostgreSQL)와 작업 실행 로그 등을 저장하기 위해 Persistent Volume(PV, 영구 볼륨)이 필요합니다. K3s는 기본적으로 local-path-provisioner(로컬 패스 프로비저너)를 내장하고 있어서 별도의 StorageClass(스토리지 클래스) 설정 없이도 PVC(Persistent Volume Claim)를 생성하면 자동으로 PV가 프로비저닝(Provisioning)됩니다. 하지만 이 방식은 특정 노드에 데이터가 종속되기 때문에 노드 장애 시 데이터 손실 위험이 있습니다.

    해결책: 홈랩 환경에서는 간단하게 hostPath 타입을 사용하여 특정 경로에 데이터를 저장할 수 있지만, 좀 더 안정적인 환경을 원한다면 NFS(네트워크 파일 시스템) 서버를 구축하고 NFS CSI Driver(드라이버)를 설치하여 공유 스토리지를 사용하는 것이 좋습니다. 저도 NFS를 사용해서 여러 노드에서 AWX 파드들이 유연하게 움직일 수 있도록 구성했거든요.

    3. Ingress(인그레스) 접근 문제

    ingress_host를 설정했지만, 웹 브라우저에서 접속이 안 되는 경우가 있었습니다. K3s는 기본적으로 Traefik(트래픽) Ingress Controller(인그레스 컨트롤러)를 내장하고 있어 편리합니다. 하지만 제 경우에는 다음과 같은 문제가 있었습니다.

    • DNS 설정: awx.myhomelab.local과 같은 호스트 이름을 사용하려면, 홈랩 내 DNS 서버에 해당 호스트를 등록하거나, 접속하려는 PC의 /etc/hosts (Linux/macOS) 또는 C:\Windows\System32\drivers\etc\hosts (Windows) 파일에 IP 주소와 호스트 이름을 직접 매핑해주어야 합니다.
      # /etc/hosts 예시
      192.168.1.100 awx.myhomelab.local # K3s 마스터 노드의 IP 주소
    • Traefik 설정: 가끔 Traefik이 외부 트래픽을 제대로 라우팅(Routing)하지 못하는 경우가 있는데, K3s 설치 시 Traefik 관련 옵션을 확인하거나, Traefik 대시보드(보통 http://<k3s_master_ip>:8080/dashboard/)에서 Ingress Route(인그레스 라우트)가 제대로 생성되었는지 확인해야 합니다.

    해결책: 저는 /etc/hosts 파일을 수정하고, kubectl get ingress -n awx 명령어로 AWX Ingress 리소스가 정상적으로 생성되었는지 확인하면서 문제를 해결했습니다.

    검증 및 결과: AWX로 K3s 자동화하기

    여러 삽질 끝에 드디어 AWX가 K3s 클러스터 위에 성공적으로 배포되었습니다! 🎉 이제 웹 브라우저를 열고 설정했던 ingress_host로 접속하면 AWX 로그인 화면이 보일 겁니다. 초기 관리자 비밀번호는 AWX Operator가 Secret(시크릿)으로 생성해두는데, 다음 명령어로 확인할 수 있습니다.

    kubectl get secret my-awx-admin-password -o jsonpath='{.data.password}' | base64 --decode

    로그인 후, 저는 간단한 Ansible 플레이북을 만들어서 K3s 클러스터의 노드 목록을 조회하는 작업을 자동화해봤습니다. AWX에서 Project(프로젝트), Inventory(인벤토리), Credential(자격 증명)을 설정하고 Job Template(잡 템플릿)을 생성한 뒤 실행하니, 깔끔하게 K3s 노드 정보가 출력되는 것을 확인할 수 있었습니다. 이 화면을 보는 순간, 그동안의 고생이 눈 녹듯 사라지더라고요! 정말 뿌듯했어요. 😄

    AWX 웹 UI에서 K3s 노드 목록을 조회하는 Ansible 잡이 성공적으로 실행된 결과 화면

    AWX 웹 UI에서 Ansible 플레이북이 성공적으로 실행되어 K3s 클러스터의 노드 목록을 출력하는 잡 실행 결과 화면 스크린샷입니다. 초록색 성공 메시지와 함께 결과가 표시됩니다.

    마무리: 경량 Kubernetes 자동화, 여러분도 해보세요!

    K3s와 AWX를 통합하여 경량 Kubernetes 환경에서 자동화 워크플로우를 구축한 경험은 저에게 정말 값진 시간이었습니다. 비록 중간중간 삽질도 많이 했지만, 그 과정에서 얻은 지식과 해결 능력은 어떤 책에서도 배울 수 없는 것이었거든요.

    주요 배운 점:

    • K3s는 가볍지만, AWX처럼 리소스를 많이 쓰는 애플리케이션을 올릴 때는 충분한 하드웨어 리소스 계획이 필요하다는 것.
    • Persistent Volume 설정은 안정적인 운영을 위한 핵심이라는 것. (홈랩이라도 NFS는 정말 중요합니다.)
    • Kubernetes Ingress와 DNS 설정은 항상 꼼꼼하게 확인해야 한다는 것.
    • AWX Operator를 사용하면 복잡한 AWX 배포를 Kubernetes 스타일로 정말 간단하게 할 수 있다는 것.

    이 조합은 특히 저처럼 홈랩을 운영하거나, 소규모 팀에서 효율적인 인프라 자동화를 목표로 할 때 정말 강력한 솔루션이 될 수 있다고 확신합니다. 여러분도 직접 K3s와 AWX를 설치하고 이것저것 만져보면서 자신만의 자동화 워크플로우를 구축해보시길 강력히 추천합니다. 직접 해보면서 얻는 경험만큼 값진 건 없으니까요! 💪

    다음 글에서는 AWX를 이용한 좀 더 복잡한 GitOps(깃옵스) 파이프라인 구축에 대해 더 깊이 다뤄볼 예정입니다. 기대해주세요! 😄

    K3s와 AWX 통합으로 얻을 수 있는 주요 장점을 시각적으로 요약한 인포그래픽

    K3s와 AWX 통합의 주요 장점 (예: 리소스 효율성, 중앙 집중식 자동화, 빠른 배포, 간편한 관리)을 시각적으로 요약한 인포그래픽입니다.

  • [k8s] Kubernetes Operator 활용 사례: 데이터베이스 관리 자동화로 운영 효율 높이기

    [k8s] Kubernetes Operator 활용 사례: 데이터베이스 관리 자동화로 운영 효율 높이기

    Kubernetes Operator 활용 사례: 데이터베이스 관리 자동화로 운영 효율 높이기

    안녕하세요, 13년차 서버실 지킴이입니다. 오늘은 쿠버네티스(Kubernetes, 컨테이너 오케스트레이션 플랫폼) 환경에서 데이터베이스(Database, DB)를 운영하며 겪었던 삽질과 그 해결책, 바로 쿠버네티스 오퍼레이터(Kubernetes Operator) 활용 경험을 여러분과 공유해볼까 합니다. 특히 데이터베이스처럼 상태 저장 애플리케이션(Stateful Application)을 쿠버네티스 위에서 안정적으로 운영하는 건 정말 만만치 않은 일이거든요. 혹시 이런 경험 있으신가요? 😥

    저도 처음엔 단순히 컨테이너화해서 배포하면 다 될 줄 알았습니다. 그런데 막상 해보니 백업, 복구, 스케일링, 고가용성(High Availability, HA) 구성까지, 수동으로 하려니 손이 너무 많이 가더라고요. 야간에 장애라도 나면 식은땀이 줄줄 흘렀죠. 그러다가 우연히 오퍼레이터라는 개념을 접하게 됐고, ‘이거다!’ 싶어서 바로 홈랩에 적용해봤습니다. 결과는 대성공이었죠. 운영 효율이 정말 몰라보게 좋아졌어요! 🎉

    쿠버네티스 오퍼레이터가 데이터베이스를 자동 관리하는 아키텍처 다이어그램

    쿠버네티스 오퍼레이터는 사용자 정의 자원(Custom Resource)을 통해 복잡한 애플리케이션을 쿠버네티스 안에서 자동 관리하는 모습을 보여줍니다.

    1. 쿠버네티스 오퍼레이터, 쉽게 말해 뭐죠?

    오퍼레이터는 한마디로 쿠버네티스 API를 확장해서 특정 애플리케이션의 운영 지식(Operational Knowledge)을 소프트웨어로 자동화한 것이라고 보시면 됩니다. 마치 숙련된 인프라 엔지니어가 24시간 상주하면서 데이터베이스를 관리해주는 것과 같아요. 쿠버네티스의 컨트롤러(Controller) 패턴과 사용자 정의 자원(Custom Resource Definition, CRD)을 활용해서 만들어지죠.

    • CRD (Custom Resource Definition): 쿠버네티스에 없는 새로운 API 객체(Object)를 정의할 수 있게 해줍니다. 예를 들어, ‘PostgreSQL’이라는 새로운 자원을 정의하고 싶다면 CRD를 만들 수 있어요.
    • 컨트롤러 (Controller): 정의된 CRD의 상태를 지속적으로 감시하고, 사용자가 원하는 상태(Desired State)와 실제 상태(Actual State)를 맞춰주는 역할을 합니다. 예를 들어, 사용자가 PostgreSQL 인스턴스 3개를 요청하면, 컨트롤러가 알아서 Pod를 3개 띄우고 복제(Replication)까지 설정해주는 식이죠.

    이게 왜 중요하냐면, 데이터베이스처럼 복잡한 상태 저장 애플리케이션은 단순히 컨테이너화하는 것만으로는 부족하거든요. 스케일링, 백업, 복원, 업그레이드, 장애 조치(Failover) 같은 작업들은 애플리케이션의 특성을 깊이 이해하고 있어야 합니다. 오퍼레이터는 이런 운영 지식을 코드로 만들어서, 우리가 직접 손댈 필요 없이 쿠버네티스 플랫폼 위에서 자동으로 처리하게 해주는 겁니다. 정말 편하더라고요!

    2. 왜 데이터베이스 관리에 오퍼레이터가 필요할까요?

    쿠버네티스는 본질적으로 무상태(Stateless) 애플리케이션에 최적화되어 있습니다. Pod가 언제 죽고 다시 생성될지 모르니, 중요한 데이터는 외부에 저장하라는 철학이죠. 하지만 데이터베이스는 핵심 중의 핵심 상태 저장(Stateful) 애플리케이션입니다. 이런 DB를 쿠버네티스 위에서 운영하려면 다음과 같은 문제에 부딪히게 됩니다.

    • 백업 및 복구(Backup & Restore): 주기적인 백업과 장애 시 신속한 복구는 필수인데, 이를 쿠버네티스 환경에 맞춰 자동화하는 것이 어렵습니다.
    • 고가용성(High Availability): 마스터-슬레이브(Master-Slave) 구조나 클러스터 구성으로 장애에 대비해야 하는데, Pod의 라이프사이클에 맞춰 동적으로 관리하기가 복잡합니다.
    • 스케일링(Scaling): 트래픽 증가에 따라 데이터베이스 인스턴스를 늘리거나 줄이는 작업도 수동으로는 어렵습니다.
    • 업그레이드(Upgrade): 무중단 업그레이드를 하려면 데이터베이스 버전별 특성을 고려해야 해서 많은 노력이 필요합니다.

    이런 문제들을 해결하기 위해 제가 직접 스크립트를 짜고 CronJob을 돌려보고… 정말 삽질 많이 했습니다. 하지만 오퍼레이터를 도입하고 나서는 이런 고민이 상당 부분 사라졌어요. 오퍼레이터가 알아서 DB의 상태를 모니터링하고, 백업을 수행하며, 장애가 발생하면 자동으로 페일오버까지 처리해주니, 정말 든든하더라고요.

    3. 실전 구현: 가상의 데이터베이스 오퍼레이터로 관리하기

    이제 실제로 어떻게 오퍼레이터를 활용하는지 간단한 예시를 통해 보여드릴게요. 여기서는 특정 데이터베이스 오퍼레이터의 이름을 직접 언급하기보다는, 개념적인 MyDatabase 오퍼레이터를 사용하겠습니다. 실제로는 Percona Operator for MySQL, Crunchy Data’s PostgreSQL Operator 같은 검증된 솔루션들이 많이 있습니다.

    가장 먼저 오퍼레이터 자체를 쿠버네티스 클러스터에 배포해야 합니다. 대부분 Helm Chart나 YAML 파일을 제공하니 어렵지 않아요. 배포가 완료되면, 이제 우리가 원하는 데이터베이스 인스턴스를 사용자 정의 자원(Custom Resource) 형태로 정의할 수 있게 됩니다.

    apiVersion: mydatabase.example.com/v1alpha1 # CRD에서 정의한 API 버전
    kind: MyDatabase                     # CRD에서 정의한 Kind
    metadata:
      name: my-prod-db
    spec:
      version: "14.5"                      # 원하는 데이터베이스 버전
      replicas: 3                          # 복제본 수 (고가용성)
      storageSize: 100Gi                   # 데이터 저장 공간
      backup:
        enabled: true
        schedule: "0 2 * * *"            # 매일 새벽 2시 백업
        retentionDays: 7                   # 7일치 백업 보관
      monitoring:
        enabled: true
      # ... 그 외 데이터베이스별 세부 설정들
    

    위 YAML 파일을 my-prod-db.yaml로 저장하고 kubectl apply -f my-prod-db.yaml 명령을 실행하면, MyDatabase 오퍼레이터가 이 요청을 감지하고 다음과 같은 작업을 수행합니다.

    1. MyDatabase CR(Custom Resource)에 정의된 스펙(Spec)을 파싱합니다.
    2. 지정된 버전(14.5)과 복제본 수(3개)에 맞춰 Pod, PersistentVolumeClaim(PVC), Service 같은 표준 쿠버네티스 자원들을 생성합니다.
    3. Pod들 간에 데이터베이스 복제 구성을 자동으로 수행합니다.
    4. 백업 스케줄(매일 새벽 2시)에 맞춰 백업 Pod를 실행하고, 지정된 스토리지에 데이터를 저장합니다.
    5. 데이터베이스 상태를 지속적으로 모니터링하며, 문제가 발생하면 자동으로 재시작하거나 페일오버를 수행합니다.
    쿠버네티스 대시보드에서 데이터베이스 오퍼레이터가 관리하는 Pod 상태

    쿠버네티스 대시보드에서 데이터베이스 오퍼레이터가 배포한 여러 컴포넌트(Pod, Service, PVC 등)와 그 상태를 한눈에 확인할 수 있습니다.

    4. ⚠️ 주의사항 및 트러블슈팅: 삽질은 국룰이죠!

    물론 오퍼레이터가 만능은 아닙니다. 저도 처음엔 ‘이거 하나면 다 되겠지!’ 생각했지만, 몇 번의 삽질을 통해 중요한 교훈을 얻었죠.

    • CRD 스펙 이해 부족: 오퍼레이터마다 CRD의 스펙이 다릅니다. 제가 사용하려던 백업 설정이 사실은 다른 필드에 있었던 적도 있어요. 반드시 해당 오퍼레이터의 공식 문서를 꼼꼼히 읽어봐야 합니다. 특히 버전별로 스펙이 달라지는 경우도 있으니 주의하세요.
    • PersistentVolume (PV) 문제: 데이터베이스는 PV를 사용하는데, PV 프로비저닝(Provisioning)에 문제가 생기거나 스토리지 클래스(StorageClass) 설정이 잘못되면 Pod가 Pending 상태에 머무는 경우가 많습니다. kubectl describe pod <pod-name>과 kubectl get events로 이벤트를 확인해서 원인을 찾아야 합니다.
    • 네트워크 설정: 데이터베이스 클러스터 내 통신이나 외부 애플리케이션과의 통신을 위해 Service와 Ingress(인그레스, 외부 트래픽 진입점) 설정을 잘 해주어야 합니다. 특히 StatefulSet을 사용하는 경우 Pod의 고정된 네트워크 ID를 활용하는 경우가 많으니 이 점도 고려해야 합니다.
    • 로그 확인의 중요성: 오퍼레이터 컨트롤러 Pod의 로그(kubectl logs -f <operator-pod-name>)를 확인하면 어떤 작업을 수행 중인지, 어떤 에러가 발생했는지 상세히 알 수 있습니다. 문제가 생기면 제일 먼저 확인해야 할 곳이죠.

    한번은 백업 스케줄을 설정했는데 백업이 계속 실패하는 거예요. 알고 보니 스토리지 크레덴셜(Credential) 설정이 잘못되어 외부 S3 버킷에 접근을 못 하고 있었던 거죠. 오퍼레이터 로그를 보고 나서야 겨우 해결했습니다. 삽질은 국룰이더라고요! ㅎㅎ

    5. 검증 및 결과: 드디어 안정적인 DB 운영!

    이런 시행착오를 겪으며 오퍼레이터를 제대로 이해하고 적용하자, 정말 운영 환경이 안정적으로 변했습니다. kubectl get mydatabase 명령으로 제가 배포한 데이터베이스 인스턴스들의 상태를 한눈에 볼 수 있게 되었고, Health Status나 백업 상태 등 중요한 정보를 쉽게 확인할 수 있었어요. 🎉

    $ kubectl get mydatabase
    NAME         VERSION   REPLICAS   STATUS    BACKUP_LAST_SUCCESS   AGE
    my-prod-db   14.5      3          Running   2023-10-26T02:00:00Z  3d
    my-dev-db    12.7      1          Running   -
    

    가장 좋았던 점은 바로 심리적인 안정감이었습니다. 밤에 잠을 자다가도 ‘혹시 DB 장애 나지 않았을까?’ 하는 불안감이 사라졌거든요. 백업과 복구 테스트도 오퍼레이터 덕분에 훨씬 쉬워졌고, 새로운 데이터베이스 인스턴스를 띄우는 시간도 단축되었습니다. 개발팀에서도 ‘DB 요청이 이렇게 빨리 처리되다니!’ 하며 놀라더라고요.

    데이터베이스 오퍼레이터 모니터링 대시보드의 건강 및 백업 성공률 그래프

    오퍼레이터가 제공하는 모니터링 대시보드에서 데이터베이스 클러스터의 전반적인 건강 상태와 백업 성공률을 시각적으로 확인하며 안정적인 운영을 검증합니다.

    6. 운영 효율 비교: 수동 vs. 오퍼레이터

    제가 직접 경험한 바에 따르면, 데이터베이스 운영 방식에 따른 효율 차이는 극명했습니다. 아래 표를 보시면 그 차이를 더 확실히 느끼실 수 있을 거예요.

    항목 수동 데이터베이스 관리 쿠버네티스 오퍼레이터 활용
    배포 시간 며칠 ~ 몇 주 (수동 설치, 설정, 복제 구성 등) 몇 분 ~ 몇 시간 (CRD 정의 후 적용)
    고가용성 수동 구성 및 모니터링, 장애 시 수동 복구 자동 복제, 자동 장애 감지 및 페일오버
    백업/복구 수동 스크립트 작성, 스케줄링, 복구 절차 복잡 자동 백업 스케줄링, 간편한 복구 명령
    스케일링 수동 인스턴스 추가, 복제 설정 변경 CRD Spec의 replicas 필드 변경으로 자동 스케일링
    업그레이드 복잡한 무중단 업그레이드 절차, 다운타임 위험 자동 롤링 업그레이드, 다운타임 최소화
    운영 복잡성 높음 (전문 지식 및 지속적인 수동 작업 필요) 낮음 (선언적 관리, 운영 지식 내재화)

    7. 마무리: 자동화, 이제 선택이 아닌 필수!

    쿠버네티스 오퍼레이터를 활용한 데이터베이스 관리 자동화는 저에게 정말 신세계였습니다. 처음에는 학습 비용과 삽질이 있었지만, 장기적으로 봤을 때 운영팀의 업무 부담을 줄이고 서비스 안정성을 크게 높이는 데 결정적인 역할을 했습니다. 특히 데이터베이스처럼 중요하고 복잡한 애플리케이션을 쿠버네티스 위에서 운영해야 한다면, 오퍼레이터는 이제 선택이 아니라 필수라고 감히 말씀드리고 싶네요.

    물론 모든 오퍼레이터가 완벽한 건 아닙니다. 각자의 환경과 데이터베이스 종류에 맞는 검증된 오퍼레이터를 신중하게 선택하고, 충분히 테스트해보는 과정은 여전히 중요합니다. 저도 아직 탐험할 부분이 많거든요! 다음번에는 또 다른 쿠버네티스 활용 사례나 홈랩에서 얻은 재미있는 경험담으로 찾아오겠습니다. 궁금한 점이 있다면 언제든지 댓글로 남겨주세요! 😉

    만족스러운 인프라 엔지니어와 안정적인 쿠버네티스 데이터베이스 클러스터

    인프라 엔지니어가 쿠버네티스 오퍼레이터 덕분에 안정적으로 운영되는 데이터베이스 클러스터를 보며 만족감을 느끼는 모습을 표현합니다.

  • [k8s] Helm 차트 관리의 진화: GitOps 시대의 베스트 프랙티스 분석

    [k8s] Helm 차트 관리의 진화: GitOps 시대의 베스트 프랙티스 분석

    안녕하세요, 13년차의 서버실입니다. 제가 처음 쿠버네티스(Kubernetes)를 접했을 때를 생각하면, 마치 새로운 대륙을 발견한 콜럼버스처럼 설레면서도 막막했던 기억이 나네요. 특히 애플리케이션 배포와 관리는 정말이지 끝없는 숙제 같았어요. 처음엔 kubectl apply -f 명령어로 YAML(야믈) 파일을 직접 하나하나 적용했었죠. 그러다 Helm(헬름, 쿠버네티스 패키지 매니저)을 만나고 나서야 비로소 숨통이 트이는 느낌이었습니다.

    Helm은 복잡한 쿠버네티스 애플리케이션을 패키징하고 배포하는 데 정말 혁신적인 도구였어요. 그런데 시간이 지나고 관리해야 할 차트(Chart)가 늘어나면서, 또 다른 고민이 생기더라고요. ‘이 많은 차트들을 어떻게 효과적으로 관리하고, 배포 이력을 추적하며, 안정적으로 운영할 수 있을까?’ 하는 문제였습니다. 수동으로 helm upgrade를 반복하는 건 실수 투성이였고, CI/CD(지속적 통합/지속적 배포) 파이프라인에 Helm 명령어를 넣는 것도 생각보다 손이 많이 갔거든요. ⚠️

    그러던 와중에 GitOps(깃옵스, Git 기반 운영 방식)라는 개념을 접하게 됐습니다. ‘아, 이거다!’ 싶었죠. 인프라와 애플리케이션의 상태를 Git 리포지토리(Repository)에 코드 형태로 정의하고, 이 Git을 유일한 진실의 원천(Single Source of Truth)으로 삼아 자동으로 클러스터 상태를 동기화하는 방식. 오늘은 이 GitOps 시대에 Helm 차트를 어떻게 하면 스마트하게 관리할 수 있는지, 제가 직접 홈랩에서 겪었던 경험과 함께 베스트 프랙티스(Best Practice)를 분석해 보려고 합니다. 여러분의 쿠버네티스 배포 여정에 이 글이 작은 등불이 되기를 바랍니다!

    GitOps 기반 Helm 차트 관리 아키텍처 개요 다이어그램

    GitOps 기반 Helm 차트 관리의 전체적인 아키텍처를 보여줍니다. 개발자가 Git에 변경사항을 푸시하면, GitOps 오퍼레이터(예: Flux CD)가 이를 감지하여 쿠버네티스 클러스터에 Helm 차트를 배포하는 흐름입니다.

    Helm과 GitOps, 그 관계를 파헤치다

    먼저 핵심 개념들을 잠시 짚고 넘어가 볼까요? 이미 잘 아시는 분도 많겠지만, 혹시 처음 접하는 분들을 위해 쉽게 설명해 드릴게요.

    • Helm (헬름): 쿠버네티스 패키지 매니저예요. 복잡한 애플리케이션을 차트(Chart)라는 패키지 형태로 정의하고, 이를 쉽게 설치, 업데이트, 삭제할 수 있도록 도와줍니다. 마치 리눅스의 apt나 yum처럼이죠. Deployment, Service, Ingress(인그레스, 외부 트래픽 진입점) 등 여러 쿠버네티스 리소스(Resource)들을 하나의 묶음으로 관리할 수 있게 해줘요.
    • GitOps (깃옵스): 쉽게 말해, Git을 중심으로 모든 것을 운영하는 방식입니다. 애플리케이션 코드뿐만 아니라, 인프라 구성(Infrastructure as Code)까지 전부 Git 리포지토리에 저장하고 관리합니다. 그리고 Git 리포지토리의 변경 사항이 감지되면, GitOps 에이전트(Agent)가 자동으로 쿠버네티스 클러스터에 배포하거나 상태를 동기화(Reconciliation)하는 구조예요. 사람이 직접 명령어를 입력할 필요 없이, Git에 커밋(Commit)만 하면 배포가 이뤄지는 거죠. 🎉

    그럼 Helm과 GitOps가 만나면 어떤 시너지를 낼까요? 기존에는 Helm 명령어를 CI/CD 파이프라인에서 실행하거나, 개발자가 직접 터미널에서 입력해서 배포했었죠. 하지만 GitOps를 도입하면, Helm 차트의 설정 파일(values.yaml)이나 차트 자체의 버전 변경을 Git에 커밋하는 것만으로 배포가 자동으로 트리거(Trigger)됩니다. ‘선언적(Declarative)’으로 상태를 정의하고, ‘자동으로 동기화(Automated Synchronization)’되는 거죠. 이 덕분에 배포의 투명성, 안정성, 감사(Audit) 용이성이 크게 향상됩니다. 제가 직접 해보니, 이게 진짜 편하더라고요!

    실전! GitOps 기반 Helm 차트 관리 구현: Flux CD를 중심으로

    홈랩에서 다양한 GitOps 툴을 실험해 봤는데, 개인적으로는 Flux CD(플럭스 CD)가 Helm 차트 관리에 참 잘 맞았어요. 물론 Argo CD(아르고 CD)도 훌륭한 대안이고요. 오늘은 Flux CD를 활용해서 GitOps 기반 Helm 차트 관리 환경을 구축하는 방법을 단계별로 설명해 드릴게요. 따라오시면 여러분도 쉽게 구현하실 수 있을 겁니다.

    1. Flux CD 설치 및 초기 설정

      먼저 쿠버네티스 클러스터(Cluster)에 Flux CD를 설치해야 합니다. Flux CLI(커맨드 라인 인터페이스)를 사용하면 정말 쉽게 설치할 수 있어요.

      
      # Flux CLI 설치 (macOS 기준, 다른 OS는 공식 문서 참고)
      brew install fluxcd/tap/flux
      
      # Flux CD 초기화 및 Git 리포지토리 연동
      # 여기서 your-git-repo는 GitOps 설정을 저장할 리포지토리 주소입니다.
      # --branch main은 기본 브랜치를 main으로 설정하는 것이고요.
      # --path clusters/my-cluster는 Flux가 모니터링할 Git 리포지토리 내 경로입니다.
      flux bootstrap git \
        --owner=<your-git-username> \
        --repository=<your-git-repo> \
        --branch=main \
        --path=clusters/my-cluster \
        --personal
              

      위 명령어를 실행하면 Flux CD가 필요한 컨트롤러(Controller)들을 쿠버네티스 클러스터에 설치하고, 지정된 Git 리포지토리와 연동합니다. 이제 Git 리포지토리 clusters/my-cluster 경로에 변경 사항이 생기면 Flux CD가 자동으로 감지하게 됩니다. ✅

    2. Git 리포지토리 구조 잡기

      GitOps의 핵심은 Git 리포지토리 구조를 잘 잡는 것입니다. 저는 보통 이런 식으로 구성합니다.

      
      .
      ├── clusters/
      │   └── my-cluster/ # Flux CD가 모니터링할 경로
      │       ├── flux-system/ # Flux CD가 생성한 리소스 (자동 관리)
      │       └── applications/ # 배포할 애플리케이션 HelmRelease 정의
      │           ├── nginx/
      │           │   └── nginx-helmrelease.yaml
      │           └── prometheus/
      │               └── prometheus-helmrelease.yaml
      └── helm-charts/ # 직접 만든 Helm 차트 (선택 사항, 외부 차트 사용 시 필요 없음)
          └── my-app/
              ├── Chart.yaml
              ├── values.yaml
              └── templates/
                  └── ...
              
    3. Helm 차트 정의 및 배포 (HelmRepository, HelmRelease)

      이제 애플리케이션을 배포해 볼까요? 예를 들어, 안정적인 Nginx(엔진엑스) Ingress Controller(인그레스 컨트롤러)를 배포한다고 가정해 봅시다. Flux CD에서는 HelmRepository와 HelmRelease라는 커스텀 리소스(Custom Resource)를 사용합니다.

      먼저 Helm 차트 저장소(Repository)를 정의합니다. Nginx Ingress Controller의 공식 Helm 저장소를 추가하는 HelmRepository 리소스입니다.

      
      # clusters/my-cluster/applications/nginx/nginx-helmrepository.yaml
      apiVersion: source.toolkit.fluxcd.io/v1beta2
      kind: HelmRepository
      metadata:
        name: ingress-nginx
        namespace: flux-system # Flux CD 시스템 네임스페이스
      spec:
        interval: 1h0m0s # 1시간마다 저장소 업데이트 확인
        url: https://kubernetes.github.io/ingress-nginx
              

      이 파일을 Git 리포지토리의 clusters/my-cluster/applications/nginx/ 경로에 저장하고 커밋(Commit)하면, Flux CD가 이를 감지하여 쿠버네티스 클러스터에 HelmRepository 리소스를 생성합니다. Flux CD는 이 저장소를 주기적으로 스캔하여 최신 차트 정보를 가져오게 됩니다. 💡

      Flux CD HelmRelease를 이용한 쿠버네티스 차트 배포 흐름

      Flux CD가 Git 리포토리의 HelmRelease 정의를 읽어 쿠버네티스 클러스터에 Helm 차트를 배포하는 과정을 시각화한 다이어그램입니다.

      다음으로, 실제로 Nginx Ingress Controller를 배포할 HelmRelease 리소스를 정의합니다. 여기에 어떤 차트를 어떤 버전으로, 어떤 값(values)으로 배포할지 상세하게 명시합니다.

      
      # clusters/my-cluster/applications/nginx/nginx-helmrelease.yaml
      apiVersion: helm.toolkit.fluxcd.io/v2beta1
      kind: HelmRelease
      metadata:
        name: ingress-nginx
        namespace: ingress-nginx # Nginx Ingress Controller가 배포될 네임스페이스
      spec:
        interval: 5m0s # 5분마다 상태 동기화 확인
        chart:
          spec:
            chart: ingress-nginx # HelmRepository에 정의된 차트 이름
            version: ">=4.0.0 <5.0.0" # 차트 버전 범위 지정
            sourceRef:
              kind: HelmRepository
              name: ingress-nginx # 위에서 정의한 HelmRepository 이름
              namespace: flux-system
        values: # values.yaml 내용과 동일
          controller:
            replicaCount: 2
            service:
              type: LoadBalancer
            nodeSelector:
              kubernetes.io/os: linux
          defaultBackend:
            enabled: true
              

      이 파일도 Git 리포지토리에 저장하고 커밋하면, Flux CD는 HelmRepository에서 차트를 가져와 이 HelmRelease 정의에 따라 Nginx Ingress Controller를 클러스터에 배포하게 됩니다. 와, 정말 깔끔하게 배포되는 거 보니까 속이 다 시원하더라고요! 이제 배포에 대한 모든 변경사항은 Git에서만 이뤄지게 됩니다. 🚀

    삽질 대잔치! 겪었던 문제와 해결 과정

    물론 이렇게 깔끔하게 설명했지만, 제가 이걸 처음 세팅할 때는 삽질 좀 했습니다 ㅎㅎ. 특히 몇 가지 문제로 머리를 싸맸던 기억이 나네요. 여러분은 저 같은 실수 하지 마시라고 몇 가지 팁을 공유해 드립니다. ⚠️

    • HelmRelease 동기화 실패:

      처음에는 Git에 커밋했는데도 클러스터에 아무런 변화가 없어서 당황했어요. 알고 보니 HelmRepository의 interval이나 HelmRelease의 interval 설정이 너무 길거나, Git 리포지토리 경로를 잘못 지정한 경우가 많았습니다. Flux CD의 로그를 꼼꼼히 확인하고, flux reconcile kustomization <kustomization-name> 명령어로 수동 동기화를 시도하면서 문제를 찾아냈습니다.

      
              # Flux CD Kustomization 상태 확인 (Git 리포지토리 동기화 상태)
              flux get kustomizations
      
              # Flux CD HelmRelease 상태 확인
              flux get hr -A # 모든 네임스페이스의 HelmRelease 확인
              
    • imagePullSecrets 적용 문제:

      프라이빗 레지스트리(Private Registry)에 있는 이미지를 사용해야 할 때, imagePullSecrets(이미지 풀 시크릿)을 ServiceAccount(서비스 어카운트)에 연결해야 하잖아요? 이걸 HelmRelease의 values에 직접 넣어도 되지만, 보안상 좋지 않거나 모든 ServiceAccount에 적용하고 싶을 때가 있습니다. 이럴 땐 Kustomize(커스터마이즈)를 활용하여 ServiceAccount에 imagePullSecrets를 패치(Patch)하는 방식으로 해결했습니다. Flux CD는 Kustomization 리소스도 지원해서, Helm과 Kustomize를 함께 사용하는 것도 가능합니다. (이건 다음 기회에 더 자세히 다뤄볼게요!)

    • 차트 버전 범위 지정 오류:

      HelmRelease에서 version: ">=4.0.0 <5.0.0"처럼 버전을 지정할 때, 세밀하게 지정하지 않으면 예상치 못한 마이너 버전 업데이트로 인해 문제가 생길 수 있습니다. 너무 넓은 범위보다는 안정성이 검증된 특정 버전이나, 패치 버전까지만 허용하는 것이 좋습니다.

    드디어! GitOps로 배포된 Helm 차트 확인

    이제 모든 설정이 완료되고 Git에 커밋까지 했다면, Flux CD가 자동으로 배포를 진행했을 겁니다. 배포가 제대로 되었는지 확인해 봐야겠죠? kubectl 명령어로 확인하면 됩니다.

    
    # Flux CD가 관리하는 Kustomization 리소스 상태 확인
    kubectl get kustomizations -n flux-system
    
    # Flux CD가 관리하는 HelmRelease 리소스 상태 확인
    kubectl get helmreleases -n ingress-nginx
    
    # Nginx Ingress Controller 파드(Pod) 확인
    kubectl get pods -n ingress-nginx
    

    helmreleases의 상태가 Ready이고, pods가 모두 Running 상태라면 성공입니다! 🎉 정말 깔끔하게 배포된 걸 보니까 속이 다 시원하더라고요. 이제 어떤 변경이든 Git 리포지토리에 반영하는 것만으로 배포가 자동으로 이뤄집니다. 롤백(Rollback)도 Git에서 이전 커밋으로 되돌리는 것만으로 가능하니, 얼마나 안정적이고 편리한지 몰라요.

    Kubernetes 클러스터에서 kubectl 명령어를 사용하여 Flux CD의 HelmRelease와 관련된 리소스들이 성공적으로 배포되고 실행 중인 상태를 보여주는 터미널 화면입니다.

    마무리하며: Helm GitOps, 더 나은 미래를 향해

    오늘은 13년차 인프라 엔지니어의 시선으로 Helm 차트 관리의 진화, 특히 GitOps 시대의 베스트 프랙티스를 Flux CD를 중심으로 알아봤습니다. 제가 직접 경험해 보니 GitOps는 단순히 도구를 사용하는 것을 넘어, 인프라 운영 방식 자체를 혁신하는 패러다임(Paradigm)이라는 생각이 들었습니다.

    GitOps 기반 Helm 차트 관리는 다음과 같은 장점들을 가져다줍니다.

    • 생산성 향상: 수동 작업 감소, 자동화된 배포.
    • 안정성 증대: Git을 통한 단일 진실의 원천, 쉬운 롤백.
    • 투명성 및 감사 용이성: 모든 변경 이력이 Git에 기록.
    • 협업 강화: 코드 리뷰(Code Review)를 통한 변경 사항 검증.
    전통적인 Helm 관리 방식과 GitOps 기반 Helm 관리 방식 비교표

    전통적인 Helm 관리 방식과 GitOps 기반 Helm 관리 방식을 주요 특징(자동화, 롤백, 투명성 등)별로 비교하는 인포그래픽 형태의 표입니다.

    물론 GitOps를 도입하는 것이 마냥 쉽지만은 않습니다. 초기 설정의 복잡성, Git 리포지토리 관리 전략 수립, 그리고 팀원들의 새로운 워크플로우(Workflow) 적응 등 넘어야 할 산들이 분명히 존재합니다. 하지만 그 모든 노력을 감수할 만큼의 가치가 충분하다고 저는 확신합니다. 결국 GitOps는 인프라 운영의 투명성과 안정성을 극대화하고, 개발팀과 운영팀 간의 협업을 더욱 매끄럽게 만드는 데 크게 기여할 겁니다.

    다음 글에서는 Flux CD의 고급 기능이나 다른 GitOps 툴인 Argo CD(아르고 CD)와의 비교, 또는 Kustomize를 활용한 Helm 차트 오버레이(Overlay) 방법에 대해서도 다뤄볼 예정입니다. 계속해서 “13년차의 서버실”에 많은 관심 부탁드립니다. 궁금한 점이나 여러분의 경험담이 있다면 댓글로 남겨주세요! 함께 고민하고 성장해 나가는 것이 저의 기쁨입니다. 😊

  • [k8s] AWX로 쿠버네티스 워크플로우 자동화: 실전 가이드

    [k8s] AWX로 쿠버네티스 워크플로우 자동화: 실전 가이드

    안녕하세요, 13년차 인프라 엔지니어 ’13년차의 서버실’ 주인장입니다. 오늘은 많은 분들이 고민하시는 쿠버네티스(Kubernetes) 워크플로우 자동화에 대해 이야기해보려고 해요. 특히 AWX를 활용해서 어떻게 복잡한 배포 과정을 효율적으로 관리할 수 있는지, 제가 직접 겪었던 경험과 실전 사례를 중심으로 풀어볼까 합니다. Ansible과 DevOps 문화에 관심 있는 분들이라면 이번 글이 정말 도움이 될 거예요.

    저도 처음엔 쿠버네티스 클러스터에 애플리케이션을 배포하는 과정이 꽤나 번거롭더라고요. 매번 kubectl apply -f 명령어를 입력하고, 버전 관리하고, 환경별로 설정 바꾸고… 반복적인 작업은 언제나 실수의 여지를 남기기 마련이잖아요. 그러다 보니 자연스럽게 자동화의 필요성을 느끼게 됐고, 홈랩에서 AWX(Ansible Workflow eXecutor)를 활용한 솔루션을 모색하게 됐습니다. 직접 삽질해가며 구축했던 과정, 지금부터 솔직하게 공유해볼게요.

    AWX와 쿠버네티스 연동을 통한 워크플로우 자동화 개요 아키텍처 다이어그램, AWX가 Git에서 Ansible Playbook을 가져와 쿠버네티스에 배포하는 과정을 보여줍니다.

    AWX와 쿠버네티스 연동을 통한 워크플로우 자동화 개요 아키텍처 다이어그램. AWX가 Git 저장소에서 Ansible Playbook을 가져와 쿠버네티스 API를 통해 리소스를 배포하는 과정을 보여줍니다.

    AWX와 쿠버네티스, 왜 함께 써야 할까요?

    먼저 핵심 개념부터 짚고 넘어갈게요. AWX는 Ansible Automation Platform의 업스트림 오픈소스 프로젝트로, 웹 기반 UI를 통해 Ansible Playbook을 실행하고 관리해주는 도구예요. 쉽게 말해, 터미널에서 명령어로 실행하던 Ansible 작업을 UI로 편하게 스케줄링하고, 권한을 관리하고, 실행 결과를 모니터링할 수 있다는 거죠. 제가 홈랩에서 다양한 자동화 실험을 할 때 정말 유용하게 써먹고 있는 녀석입니다.

    그리고 쿠버네티스(Kubernetes, K8s)는 컨테이너화된 워크로드와 서비스를 자동으로 배포, 스케일링 및 관리해주는 오픈소스 시스템이에요. 컨테이너 오케스트레이션(Container Orchestration)의 사실상 표준이 되었죠. 선언적(Declarative) 방식으로 원하는 상태를 정의하면, 쿠버네티스가 그 상태를 유지하도록 알아서 관리해줍니다. 저는 주로 Deployment(디플로이먼트)와 Service(서비스), Ingress(인그레스, 외부 트래픽 진입점) 같은 리소스들을 YAML 파일로 정의해서 사용하고 있어요.

    이 둘을 함께 쓰면 어떤 시너지가 생길까요? 바로 선언적 인프라(Declarative Infrastructure) 관리가 가능해진다는 거예요. Ansible Playbook으로 쿠버네티스 리소스의 최종 상태를 정의하고, AWX가 이 Playbook을 실행해서 클러스터에 배포하는 방식이죠. 이렇게 하면 수동 작업으로 인한 오류를 줄이고, 배포 과정을 표준화하며, 지속적인 통합/배포(CI/CD) 워크플로우를 구축하는 데 정말 큰 도움이 돼요. 실제 운영 환경에서 이 조합은 정말 강력하더라고요.

    실전 구현: AWX로 쿠버네티스 워크플로우 자동화하기

    자, 그럼 이제 제가 직접 구축했던 방법을 단계별로 보여드릴게요. 목표는 AWX를 통해 간단한 Nginx 웹 서버를 쿠버네티스 클러스터에 배포하고, 외부에 노출시키는 거예요.

    1단계: AWX 환경 설정

    1. 인벤토리(Inventory) 생성: AWX에서 Playbook을 실행할 대상(Host)을 정의합니다. 여기서는 쿠버네티스 클러스터의 API 서버에 접근해야 하므로, 로컬 호스트를 대상으로 하거나, 쿠버네티스 API 엔드포인트를 지정해요. 저는 주로 localhost를 대상으로 하고, kubeconfig 파일을 통해 클러스터에 접근하는 방식을 선호합니다.
    2. 자격 증명(Credential) 생성: 쿠버네티스 클러스터에 접근하기 위한 자격 증명이 필요해요. kubeconfig 파일을 AWX에 등록하는 방법이 가장 일반적입니다.
    # AWX Credential 설정 예시 (Kubeconfig Type)
    # --- Kubeconfig 내용 --- (실제 파일 내용)
    apiVersion: v1
    clusters:
    - cluster:
        certificate-authority-data: ...
        server: https://your-kubernetes-api-server:6443
      name: my-cluster
    contexts:
    - context:
        cluster: my-cluster
        user: my-user
      name: my-context
    current-context: my-context
    kind: Config
    preferences: {}
    users:
    - name: my-user
      user:
        client-certificate-data: ...
        client-key-data: ...
    

    2단계: Ansible Playbook 작성

    쿠버네티스 리소스를 배포하기 위한 Playbook을 작성해요. Ansible이 제공하는 kubernetes.core.k8s 모듈로 쿠버네티스 리소스 관리가 정말 쉬워진다니까요. 저는 Nginx Deployment와 Service를 배포하는 Playbook을 만들었어요.

    # playbook.yaml
    ---
    - name: Deploy Nginx to Kubernetes
      hosts: localhost
      connection: local
      collections:
        - kubernetes.core
    
      vars:
        namespace: default
        app_name: my-nginx
        image_version: 1.25.3
    
      tasks:
        - name: Ensure Namespace exists
          kubernetes.core.k8s:
            api_version: v1
            kind: Namespace
            name: "{{ namespace }}"
            state: present
    
        - name: Deploy Nginx Deployment
          kubernetes.core.k8s:
            state: present
            definition: |
              apiVersion: apps/v1
              kind: Deployment
              metadata:
                name: "{{ app_name }}-deployment"
                namespace: "{{ namespace }}"
                labels:
                  app: "{{ app_name }}"
              spec:
                replicas: 2
                selector:
                  matchLabels:
                    app: "{{ app_name }}"
                template:
                  metadata:
                    labels:
                      app: "{{ app_name }}"
                  spec:
                    containers:
                    - name: "{{ app_name }}"
                      image: nginx:"{{ image_version }}"
                      ports:
                      - containerPort: 80
    
        - name: Expose Nginx Service
          kubernetes.core.k8s:
            state: present
            definition: |
              apiVersion: v1
              kind: Service
              metadata:
                name: "{{ app_name }}-service"
                namespace: "{{ namespace }}"
                labels:
                  app: "{{ app_name }}"
              spec:
                selector:
                  app: "{{ app_name }}"
                ports:
                  - protocol: TCP
                    port: 80
                    targetPort: 80
                type: NodePort # 또는 LoadBalancer, ClusterIP 등 환경에 맞게
    

    이 Playbook을 Git 저장소(예: GitHub, GitLab)에 커밋해요. AWX가 이 저장소에서 Playbook을 가져와 실행할 거거든요.

    3단계: AWX 프로젝트 및 작업 템플릿(Job Template) 설정

    AWX UI에서 다음을 설정합니다.

    1. 프로젝트(Project) 생성: Git 저장소를 연결하여 Playbook을 가져와요.
    2. 작업 템플릿(Job Template) 생성: 이 템플릿에 위에서 만든 인벤토리, 자격 증명, 프로젝트, 그리고 실행할 Playbook 파일(playbook.yaml)을 연결합니다.
    AWX 작업 템플릿 설정 화면 스크린샷, Git 프로젝트, 인벤토리, 쿠버네티스 자격 증명, 실행할 Playbook 파일을 지정하는 모습

    AWX 작업 템플릿 설정 화면 스크린샷. Git 프로젝트, 인벤토리, 쿠버네티스 자격 증명, 실행할 Playbook 파일을 지정하는 모습을 보여줍니다.

    ⚠️ 삽질 경험 & 워크플로우 자동화 트러블슈팅

    제가 이 AWX와 쿠버네티스 자동화 과정을 진행하면서 겪었던 몇 가지 삽질과 그 해결책을 공유할게요. 혹시 비슷한 문제를 겪는 분들이 있다면 도움이 될 거예요.

    • 인증 오류 (Authentication Error): 가장 흔한 문제 중 하나예요. kubeconfig 파일의 권한 문제, 또는 파일 내용 자체가 잘못된 경우가 많았어요. 특히 client-certificate-data와 client-key-data가 정확한 base64 인코딩 값인지 여러 번 확인했거든요. AWX가 실행되는 컨테이너 환경에서 kubeconfig 파일에 접근 권한이 없어서 생기는 경우도 있었고요. 💡 팁: AWX 컨테이너 내부에서 kubectl get pods를 직접 실행해보며 인증 문제를 디버깅해보세요.
    • YAML 문법 오류: 쿠버네티스 리소스 정의는 YAML 파일로 하는데, 들여쓰기나 오타 하나로도 워크플로우가 실행 안 돼요. 특히 Ansible Playbook 내 definition 블록 안에 YAML을 넣을 때는 더욱 주의해야 합니다. ⚠️ 경고: definition: | 다음 줄부터는 반드시 두 칸 이상 들여쓰기 해야 합니다!
    • Ansible kubernetes.core.k8s 모듈 문제: 처음에는 이 모듈을 쓰는 게 익숙하지 않아서 헤맸어요. 특히 state: present로 리소스를 생성하고, state: absent로 삭제하는 방식에 익숙해지는 데 시간이 좀 걸렸거든요. 그리고 kubeconfig 파일 경로를 명시적으로 지정해야 하는 경우도 있었습니다 (kubeconfig: /path/to/kubeconfig).
    • 네트워크 연결 문제: AWX가 쿠버네티스 API 서버에 접근할 수 없는 네트워크 환경일 경우 당연히 실패하죠. 방화벽 규칙이나 네트워크 정책을 다시 확인해야 해요.

    ✅ 결과 검증 및 자동화의 힘!

    모든 설정이 끝나면, AWX UI에서 작업 템플릿을 실행(Launch)해요. Playbook이 성공적으로 실행되면 AWX 작업 로그에서 초록색 SUCCESS 메시지를 확인할 수 있어요. 저도 이 메시지를 처음 봤을 때, “드디어 됐다!” 하고 쾌재를 불렀던 기억이 생생해요.

    이제 쿠버네티스 클러스터에서 실제로 리소스가 배포되었는지 확인해볼 차례입니다.

    
    kubectl get deployments -n default
    # NAME                  READY   UP-TO-DATE   AVAILABLE   AGE
    # my-nginx-deployment   2/2     2            2           2m
    
    kubectl get services -n default
    # NAME              TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
    # my-nginx-service   NodePort   10.96.123.456   <none>        80:3xxxx/TCP   2m
    

    정상적으로 Nginx Deployment와 Service가 생성된 걸 확인할 수 있어요. 이제 NodePort로 할당된 포트를 통해 Nginx 웹 서버에 접속하면 “Welcome to Nginx!” 페이지를 볼 수 있을 거예요. 🎉 정말 편리하지 않나요? 몇 번의 클릭만으로 쿠버네티스에 애플리케이션을 배포하는 워크플로우 자동화가 완성된 거죠.

    AWX 작업 성공 로그와 kubectl get pods/deployments 명령 결과 비교 화면, AWX의 성공적인 Task 완료와 쿠버네티스 배포 리소스 확인

    AWX 작업 성공 로그와 kubectl get pods/deployments 명령 결과 비교 화면. AWX의 Job Output에서 Task가 성공적으로 완료된 것을 보여주고, 터미널에서 kubectl 명령어로 배포된 리소스들을 확인하는 모습을 나란히 보여줍니다.

    마무리하며: 자동화, 멈추지 않는 여정

    이번 AWX와 쿠버네티스 연동 사례를 통해 워크플로우 자동화가 얼마나 강력한지 다시 한번 느꼈어요. 단순한 배포를 넘어, IaC(Infrastructure as Code, 코드형 인프라)의 개념을 실현하고, DevOps 파이프라인의 핵심 구성 요소로 활용할 수 있다는 것이 핵심이죠.

    인프라 엔지니어로서 제가 얻은 가장 큰 교훈은 “반복되는 작업은 무조건 자동화하라”는 거예요. 처음엔 자동화 스크립트 작성에 시간이 들지언정, 장기적으로는 시간과 노력을 아끼고, 휴먼 에러를 줄여준다는 걸 뼈저리게 경험했거든요. 이 경험이 여러분의 인프라 관리에도 작은 영감이 되기를 바랍니다.

    다음 글에서는 이 워크플로우를 확장해서 Ingress 컨트롤러를 배포하고, 외부 도메인으로 서비스에 접근하는 방법을 다뤄볼까 해요. 기대해주세요!

    AWX, Ansible, Kubernetes, DevOps 로고들이 원형으로 배치된 인포그래픽. 각 로고들이 서로 연결되어 워크플로우 자동화를 이루는 모습을 시각적으로 보여줍니다.

  • [k8s] ArgoCD로 멀티 클러스터 GitOps 배포 자동화 완벽 가이드

    [k8s] ArgoCD로 멀티 클러스터 GitOps 배포 자동화 완벽 가이드

    ArgoCD로 멀티 클러스터 GitOps 배포 자동화 완벽 가이드

    안녕하세요, 13년차 서버실 지킴이입니다. 오늘도 어김없이 여러분의 인프라 여정에 도움이 될 만한 꿀팁을 들고 왔습니다. 혹시 여러 쿠버네티스 클러스터를 운영하면서 배포 때문에 머리 아팠던 경험 있으신가요? 개발, 스테이징, 운영 환경이 각각 다른 클러스터에 존재하거나, 온프레미스와 클라우드를 넘나드는 하이브리드 환경에서 일관된 배포를 유지하는 게 정말 쉽지 않거든요. 저도 처음엔 수동 배포 지옥에서 헤매던 시절이 있었죠. 클러스터마다 kubectl 명령어를 치고, YAML 파일을 수정하고, 휴먼 에러로 인해 서비스 장애를 겪었던 아찔한 순간들도 많았고요. 😱

    하지만 GitOps(깃옵스)를 만나고, 특히 ArgoCD(아르고CD)를 활용하면서 이런 배포 스트레스가 확 줄었습니다. 오늘은 저처럼 멀티 클러스터 환경에서 배포 자동화에 목마른 분들을 위해, ArgoCD로 GitOps를 구현하고 여러 클러스터에 애플리케이션을 자동으로 배포하는 멀티 클러스터 GitOps 배포 자동화 방법을 완벽하게 가이드해 드리려고 합니다. 제가 직접 삽질하며 터득한 경험들을 솔직하게 공유할 테니, 끝까지 따라오시면 분명 큰 도움이 될 겁니다! 자, 그럼 시작해 볼까요?

    ArgoCD를 활용한 멀티 클러스터 GitOps 배포 아키텍처는 위와 같이 구성될 수 있습니다. 하나의 ArgoCD 인스턴스가 여러 쿠버네티스 클러스터에 애플리케이션을 배포하고 관리하는 모습이죠. Git을 중심으로 모든 클러스터의 상태를 동기화하는 핵심 아이디어를 담고 있습니다.

    1. GitOps와 ArgoCD, 그리고 멀티 클러스터 배포, 이게 뭔가요?

    본격적인 실전에 앞서, 핵심 개념들을 짚고 넘어가는 게 중요하겠죠? 제가 늘 강조하는 부분인데, 도구를 잘 쓰는 것도 중요하지만, 그 도구가 왜 필요하고 어떤 철학을 담고 있는지 이해하는 게 진짜 실력입니다.

    • GitOps (깃옵스): 쉽게 말해, Git을 유일한 진실의 원천(Single Source of Truth)으로 삼아 인프라와 애플리케이션 배포를 자동화하는 운영 방식이에요. 모든 설정과 배포 상태를 Git 리포지토리에 코드로 관리하고, Git에 변경사항이 푸시되면 자동으로 인프라에 반영되도록 하는 거죠. 개발자들이 코드 관리하듯이 인프라를 관리한다고 생각하시면 됩니다. 일관성, 버전 관리, 감사 추적(Audit Trail)이 가능하다는 엄청난 장점이 있어요.

    • ArgoCD (아르고CD): 이 친구는 GitOps를 쿠버네티스(Kubernetes) 환경에서 구현해주는 선언적(Declarative) GitOps 지속적 배포(Continuous Delivery) 툴입니다. Git 리포지토리에 정의된 원하는 상태(Desired State)와 실제 쿠버네티스 클러스터의 현재 상태(Live State)를 끊임없이 비교하고, 만약 다르면 Git에 정의된 상태로 클러스터를 동기화(Sync)시켜 줍니다. 마치 클러스터 상태를 감시하는 파수꾼 같다고 할까요? 정말 든든한 친구더라고요.

    • 멀티 클러스터 배포 (Multi-Cluster Deployment): 여러 개의 쿠버네티스 클러스터에 동일하거나 다른 애플리케이션을 배포하고 관리하는 시나리오를 말합니다. 개발, 스테이징, 운영 환경을 분리하거나, 재해 복구(DR)를 위해 지리적으로 분산된 클러스터를 운영할 때 주로 사용하죠. ArgoCD는 하나의 중앙 인스턴스에서 여러 클러스터를 관리할 수 있어서, 멀티 클러스터 환경에서 배포를 중앙 집중화하고 자동화하는 데 아주 탁월한 솔루션입니다.

    2. 실전 구현: ArgoCD로 멀티 클러스터 GitOps 환경 구축하기

    자, 이제 이론은 충분히 봤으니, 저와 함께 직접 만들어 볼 시간입니다. 제가 홈랩에서 여러 번 시도하면서 가장 효율적이라고 생각했던 방법으로 안내해 드릴게요. 따라만 하시면 됩니다! 💡

    2.1. 사전 준비물 (Prerequisites)

    시작하기 전에 몇 가지 준비물이 필요합니다.

    • 쿠버네티스 클러스터 2개 이상: 하나는 ArgoCD를 설치할 ‘컨트롤 플레인 클러스터’로, 나머지는 애플리케이션을 배포할 ‘타겟 클러스터’로 사용할 겁니다. (저는 KIND나 K3s로 쉽게 구성했어요.)
    • kubectl: 쿠버네티스 클러스터를 제어하는 CLI 도구.
    • Helm (선택 사항): 쿠버네티스 패키지 매니저. ArgoCD 설치에 Helm을 사용하진 않지만, 애플리케이션 배포에 유용할 수 있습니다.
    • Git 리포지토리: 배포할 애플리케이션의 YAML 파일들을 저장할 공간 (GitHub, GitLab, Bitbucket 등).

    2.2. 단계 1: ArgoCD 설치 (컨트롤 플레인 클러스터)

    먼저 ArgoCD를 설치할 클러스터에 ArgoCD를 배포합니다. 이 클러스터가 모든 배포의 중앙 통제실 역할을 하게 됩니다.

    1. ArgoCD 네임스페이스 생성

      kubectl create namespace argocd
    2. ArgoCD 설치 YAML 적용

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

      이 명령은 ArgoCD의 모든 컴포넌트(API 서버, 컨트롤러, 레포 서버 등)를 설치합니다. 잠시 기다리면 파드들이 정상적으로 올라올 거예요.

    3. ArgoCD CLI 설치 (선택 사항이지만 강력 추천!)

      # macOS (Homebrew)
      brew install argocd
      
      # Linux (직접 다운로드)
      curl -sSL -o argocd-linux-amd64 https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-amd64
      sudo install -m 555 argocd-linux-amd64 /usr/local/bin/argocd
      rm argocd-linux-amd64
    4. 초기 비밀번호 확인 및 UI 접근

      ArgoCD API 서버의 초기 비밀번호는 컨트롤 플레인 클러스터의 argocd-initial-admin-secret 시크릿에 저장되어 있습니다.

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

      UI에 접근하려면 포트 포워딩(Port Forwarding)을 해줘야 합니다.

      kubectl port-forward svc/argocd-server -n argocd 8080:443

      이제 웹 브라우저에서 https://localhost:8080으로 접속한 후, 사용자명 admin과 위에서 확인한 비밀번호로 로그인하세요. 첫 로그인 후 비밀번호를 변경하는 것을 추천합니다.

    2.3. 단계 2: Git Repository 준비

    배포할 애플리케이션의 YAML 파일들을 Git 리포지토리에 준비해야 합니다. 저는 간단한 Nginx 배포 파일을 예시로 들어볼게요.

    my-gitops-repo/apps/nginx/deployment.yaml

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: nginx-deployment
      labels:
        app: nginx
    spec:
      replicas: 2
      selector:
        matchLabels:
          app: nginx
      template:
        metadata:
          labels:
            app: nginx
        spec:
          containers:
          - name: nginx
            image: nginx:1.14.2
            ports:
            - containerPort: 80

    my-gitops-repo/apps/nginx/service.yaml

    apiVersion: v1
    kind: Service
    metadata:
      name: nginx-service
    spec:
      selector:
        app: nginx
      ports:
        - protocol: TCP
          port: 80
          targetPort: 80
      type: LoadBalancer # 또는 ClusterIP, NodePort

    이 파일들을 Git 리포지토리에 푸시해 주세요. (예: https://github.com/your-org/my-gitops-repo.git)

    2.4. 단계 3: 타겟 클러스터 등록 (ArgoCD에 연결)

    이제 ArgoCD가 애플리케이션을 배포할 타겟 클러스터들을 ArgoCD에 등록해야 합니다. ArgoCD CLI를 사용하면 아주 쉽게 등록할 수 있어요.

    1. ArgoCD CLI 로그인

      argocd login localhost:8080

      초기 비밀번호로 로그인합니다.

    2. 타겟 클러스터 등록

      현재 kubeconfig 파일에 정의된 클러스터 컨텍스트(Context)를 사용해서 등록합니다. 등록할 클러스터의 컨텍스트로 kubectl config use-context <TARGET_CLUSTER_CONTEXT> 명령어를 실행한 후, 아래 명령어를 실행하세요.

      argocd cluster add <TARGET_CLUSTER_CONTEXT>

      예를 들어, dev-cluster와 prod-cluster라는 컨텍스트가 있다면 각각 등록해 줍니다.

      이 명령은 ArgoCD가 타겟 클러스터에 접근할 수 있도록 필요한 RBAC 설정과 시크릿을 자동으로 생성해 줍니다. ⚠️ 권한 문제로 많이들 삽질하시는데, 이 단계에서 정확한 컨텍스트를 선택했는지 꼭 확인하세요.

    3. 등록된 클러스터 확인

      argocd cluster list

      등록된 클러스터 목록이 보이면 성공입니다! ArgoCD UI에서도 ‘Clusters’ 메뉴에서 확인할 수 있습니다.

    ArgoCD UI에서 클러스터가 성공적으로 등록되고 ApplicationSet이 여러 클러스터에 배포되는 모습을 시각적으로 확인할 수 있습니다. 각 클러스터별로 애플리케이션 상태를 한눈에 파악할 수 있죠.

    2.5. 단계 4: ApplicationSet으로 멀티 클러스터 배포 자동화

    이제 이 글의 핵심인 ApplicationSet(애플리케이션셋)을 활용해서 여러 클러스터에 Nginx 애플리케이션을 배포해 볼 시간입니다. ApplicationSet은 여러 개의 ArgoCD Application 리소스를 동적으로 생성해주는 컨트롤러입니다. 특히 멀티 클러스터 환경에서 빛을 발하죠!

    ApplicationSet을 사용하면, 등록된 모든 클러스터에 동일한 애플리케이션을 배포하거나, 클러스터별로 약간 다른 설정을 적용하여 배포할 수 있습니다. 여기서는 Cluster Generator를 사용해서 ArgoCD에 등록된 모든 클러스터에 Nginx를 배포하도록 해볼게요.

    my-gitops-repo/applicationsets/nginx-applicationset.yaml

    apiVersion: argoproj.io/v1alpha1
    kind: ApplicationSet
    metadata:
      name: multi-cluster-nginx
      namespace: argocd
    spec:
      generators:
      - clusters: {}
      template:
        metadata:
          name: '{{name}}-nginx' # 클러스터 이름 기반으로 Application 이름 생성
          namespace: argocd
        spec:
          project: default
          source:
            repoURL: https://github.com/your-org/my-gitops-repo.git # 본인의 Git 리포지토리 URL로 변경
            targetRevision: HEAD
            path: apps/nginx
          destination:
            server: '{{server}}'
            namespace: default
          syncPolicy:
            automated:
              prune: true
              selfHeal: true
            syncOptions:
              - CreateNamespace=true # 대상 클러스터에 네임스페이스가 없으면 생성

    위 YAML 파일을 Git 리포지토리에 푸시한 후, ArgoCD가 설치된 컨트롤 플레인 클러스터에 적용합니다.

    kubectl apply -n argocd -f my-gitops-repo/applicationsets/nginx-applicationset.yaml

    잠시 후 ArgoCD UI의 ‘Applications’ 메뉴로 이동해 보세요. 등록된 클러스터 개수만큼 <클러스터이름>-nginx 형태의 애플리케이션이 자동으로 생성되고, 배포가 시작되는 것을 확인할 수 있을 겁니다. 정말 신기하더라고요! 🎉

    3. 주의사항 및 트러블슈팅: 저의 삽질 경험담 ⚠️

    제가 13년간 인프라 엔지니어로 일하면서 깨달은 건, 새로운 기술을 도입할 때 늘 예상치 못한 문제가 터진다는 겁니다. ArgoCD도 예외는 아니었죠. 제가 겪었던 주요 삽질 경험과 해결 팁을 공유해 드립니다.

    • RBAC (Role-Based Access Control) 문제: 타겟 클러스터 등록 시 ArgoCD가 해당 클러스터에 접근할 권한이 없어서 배포가 실패하는 경우가 많았습니다. argocd cluster add 명령이 자동으로 RBAC를 설정해주지만, 때로는 특정 리소스에 대한 추가 권한이 필요할 수 있어요. ArgoCD 서버의 서비스 어카운트(Service Account)에 필요한 ClusterRoleBinding이 제대로 되어있는지 확인하고, 필요하다면 수동으로 추가해줘야 합니다.

      # 예시: ArgoCD 서비스 어카운트에 Cluster-Admin 권한 부여 (실제 운영에서는 최소 권한 원칙 준수)
      apiVersion: rbac.authorization.k8s.io/v1
      kind: ClusterRoleBinding
      metadata:
        name: argocd-manager-role-binding
      subjects:
      - kind: ServiceAccount
        name: argocd-argocd-application-controller
        namespace: argocd
      roleRef:
        apiGroup: rbac.authorization.k8s.io
        kind: ClusterRole
        name: cluster-admin # or a more specific role
    • Kubeconfig 파일 접근 권한: ArgoCD가 타겟 클러스터에 접근하려면 컨트롤 플레인 클러스터에서 타겟 클러스터의 API 서버에 네트워크적으로 연결이 가능해야 합니다. 방화벽, VPC 설정 등을 꼼꼼히 확인하세요. 특히 온프레미스와 클라우드 하이브리드 환경에서는 네트워크 경로 설정이 복잡해질 수 있습니다.

    • Sync Policy 설정: automated: prune: true와 selfHeal: true 옵션은 배포를 매우 편리하게 해주지만, 자칫 잘못하면 예상치 못한 변경이 즉시 반영될 수 있습니다. 특히 운영 환경에서는 신중하게 접근하고, 처음에는 수동 동기화(Manual Sync)로 시작하여 동작을 충분히 이해한 후 자동화하는 것을 추천합니다.

    • ApplicationSet Generator 문제: Cluster Generator를 쓸 때, Git 리포지토리의 경로(path)나 targetRevision, 그리고 destination의 server와 namespace 설정에 오타가 없는지 여러 번 확인해야 합니다. 변수({{name}}, {{server}}) 사용법도 헷갈리기 쉽더라고요.

    4. 검증 및 결과: 드디어 됐다! ✅

    이제 ArgoCD UI에서 모든 애플리케이션들이 Synced 상태인지 확인해 보세요. 각 클러스터에 배포된 Nginx 파드들도 kubectl get pods -n default 명령으로 확인하면 정상적으로 실행되고 있을 겁니다. 드디어 모든 클러스터에 배포가 완료됐네요! 이 맛에 GitOps 하는 거 아니겠습니까? 🎉

    여기서 끝이 아닙니다! GitOps의 진정한 가치는 Git 변경 사항이 자동으로 반영되는 데 있거든요. my-gitops-repo/apps/nginx/deployment.yaml 파일의 replicas 수를 2에서 3으로 변경하고 Git에 푸시해 보세요. ArgoCD가 변경 사항을 감지하고, 몇 초 내에 모든 타겟 클러스터의 Nginx 파드 개수가 3개로 자동으로 업데이트되는 것을 볼 수 있을 겁니다. 정말 편하더라고요!

    ArgoCD 대시보드에서 여러 클러스터에 배포된 애플리케이션들의 실시간 상태를 모니터링하는 화면입니다. 모든 애플리케이션이 정상적으로 동기화(Synced)된 것을 확인할 수 있습니다.

    5. 마무리: GitOps로 한 단계 더 성장하기

    오늘은 ArgoCD를 활용하여 멀티 클러스터 GitOps 배포 자동화를 구현하는 과정을 저의 경험을 바탕으로 상세하게 설명해 드렸습니다. 어떠셨나요? 처음엔 복잡해 보이지만, 한 번 구축해 놓으면 배포의 일관성, 속도, 안정성이 비약적으로 향상되는 것을 체감하실 수 있을 겁니다.

    멀티 클러스터 환경에서 ArgoCD와 GitOps는 정말 강력한 조합입니다. 수동 작업에서 벗어나 휴먼 에러를 줄이고, Git을 통한 완벽한 버전 관리와 감사 추적이 가능해지거든요. 무엇보다 인프라 엔지니어가 더 중요하고 전략적인 업무에 집중할 수 있도록 도와주는 멋진 도구라고 생각합니다.

    물론 초기 설정에 시간과 노력이 필요하고, GitOps 문화에 적응하는 과정도 필요합니다. 하지만 그 투자 가치는 충분하다고 제가 직접 보증합니다. 제 홈랩에서도 이 방식으로 다양한 서비스를 배포하고 있거든요. 😉

    다음번에는 오늘 배운 ArgoCD와 GitOps 개념을 확장해서, Helm Chart 통합, Kustomize 활용, 그리고 Argo Rollouts(아르고 롤아웃)를 이용한 블루/그린, 카나리 배포 전략까지 자동화하는 경험담을 들려드릴게요! 이 글이 여러분의 인프라 여정에 작은 이정표가 되었기를 바랍니다. 다음에 또 만나요!

    GitOps와 기존 배포 방식, 그리고 ArgoCD의 멀티 클러스터 관리 장점을 요약한 인포그래픽입니다. 어떤 점들이 개선되었는지 한눈에 파악할 수 있습니다.

  • [k8s] ArgoCD GitOps CI/CD 구축: 쿠버네티스 자동 배포 완벽 가이드

    배포가 두려운 분들께 — GitOps가 바꿔놓은 제 일상

    솔직히 말씀드리면, 저도 예전에는 배포가 제일 무서웠어요. 스테이징에서는 멀쩡하던 게 프로덕션에 올리면 왜인지 모르게 터지고, “누가 언제 뭘 바꿨지?” 하고 kubectl로 이것저것 뒤지다 보면 어느새 새벽 2시가 되어 있는 그런 날들이요. 쿠버네티스를 도입하고 나서도 한동안은 이 상황이 크게 달라지지 않았습니다. 오히려 배포 방법이 늘어나면서 혼란이 더 심해지기도 했고요.

    그러다가 ArgoCD를 알게 됐습니다. 처음엔 “또 새로운 툴이네” 하고 반신반의했는데, 막상 써보니까 진짜 다르더라고요. GitOps 방식으로 쿠버네티스 배포를 자동화하면, Git 저장소가 클러스터의 “진실의 원천(Source of Truth)”이 됩니다. 코드를 푸시하면 ArgoCD가 알아서 클러스터 상태를 맞춰주는 거예요. 오늘은 제가 홈랩과 실무에서 직접 구축하면서 겪은 경험을 바탕으로, ArgoCD를 활용한 CI/CD 파이프라인 구축 방법을 처음부터 끝까지 같이 살펴보겠습니다.

    ArgoCD GitOps 전체 아키텍처 — Git 저장소 변경이 쿠버네티스 클러스터에 자동 반영되는 흐름

    GitOps와 ArgoCD, 쉽게 이해하기

    GitOps란 뭔가요?

    GitOps를 한 문장으로 정리하면 이렇습니다. “인프라와 애플리케이션의 원하는 상태(Desired State)를 Git에 선언적으로 저장하고, 자동화 도구가 실제 상태를 그것에 맞게 유지하는 방식”이에요.

    쉽게 말해서, 예전에는 “지금 클러스터에 뭐가 떠 있지?”를 확인하려면 kubectl get 명령어를 직접 쳐봐야 했잖아요. GitOps를 쓰면 Git 저장소만 봐도 현재 클러스터 상태를 알 수 있어요. 변경 이력도 다 남고, 롤백도 git revert 한 방이면 되고요.

    기존 CI/CD 방식과 무엇이 다른가요?

    구분 기존 Push 방식 CI/CD GitOps (ArgoCD Pull 방식)
    배포 트리거 CI 파이프라인이 kubectl apply 직접 실행 ArgoCD가 Git 변경 감지 후 자동 동기화
    클러스터 접근 권한 CI 서버가 클러스터 자격증명 보유 ArgoCD만 클러스터 내부에서 관리
    상태 추적 파이프라인 로그 확인 필요 Git 커밋 히스토리 = 배포 이력
    드리프트(Drift) 감지 수동 확인 필요 ArgoCD가 실시간 감지 및 알림
    롤백 파이프라인 재실행 or 수동 Git revert + 자동 동기화

    특히 드리프트(Drift) 감지가 저한테는 정말 킬러 피처였어요. 누군가 실수로 kubectl로 직접 설정을 바꿔놔도, ArgoCD가 “어? Git이랑 다른데?” 하고 바로 잡아주거든요. 이게 얼마나 안심되는지 모릅니다.

    ArgoCD 설치 — 생각보다 간단합니다

    사전 준비

    • 쿠버네티스 클러스터 (v1.19 이상 권장)
    • kubectl 설치 및 클러스터 접근 설정 완료
    • Git 저장소 (GitHub, GitLab, Bitbucket 모두 가능)
    • ArgoCD CLI (선택사항이지만 있으면 편해요)

    1단계: ArgoCD 네임스페이스 및 설치

    ArgoCD 공식 설치 방법은 정말 간단합니다. 공식 매니페스트를 그대로 적용하면 되거든요.

    # ArgoCD 전용 네임스페이스 생성
    kubectl create namespace argocd
    
    # ArgoCD 공식 매니페스트 적용
    kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
    
    # 설치 완료 확인 (모든 Pod가 Running 상태가 될 때까지 대기)
    kubectl get pods -n argocd -w

    처음에 저도 이게 너무 간단해서 “이게 맞나?” 싶었는데, 맞습니다 ㅎㅎ. 잠시 기다리면 아래와 같이 Pod들이 뜨기 시작해요.

    NAME                                                READY   STATUS    RESTARTS   AGE
    argocd-application-controller-0                     1/1     Running   0          2m
    argocd-applicationset-controller-xxx                1/1     Running   0          2m
    argocd-dex-server-xxx                               1/1     Running   0          2m
    argocd-notifications-controller-xxx                 1/1     Running   0          2m
    argocd-redis-xxx                                    1/1     Running   0          2m
    argocd-repo-server-xxx                              1/1     Running   0          2m
    argocd-server-xxx                                   1/1     Running   0          2m

    2단계: ArgoCD UI 접근 설정

    기본적으로 ArgoCD 서버는 외부에 노출되어 있지 않아요. 로컬에서 테스트할 때는 포트 포워딩을 쓰고, 실제 운영 환경에서는 Ingress를 설정하는 게 좋습니다.

    # 로컬 테스트용 포트 포워딩
    kubectl port-forward svc/argocd-server -n argocd 8080:443
    
    # 초기 admin 비밀번호 확인
    kubectl -n argocd get secret argocd-initial-admin-secret \
      -o jsonpath="{.data.password}" | base64 -d; echo

    💡 팁: 초기 비밀번호는 반드시 첫 로그인 후 바꿔주세요. 그리고 argocd-initial-admin-secret은 비밀번호 변경 후 삭제하는 게 보안상 좋습니다.

    3단계: ArgoCD CLI 설치 (선택 권장)

    # macOS
    brew install argocd
    
    # Linux
    curl -sSL -o /usr/local/bin/argocd \
      https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-amd64
    chmod +x /usr/local/bin/argocd
    
    # CLI로 ArgoCD 서버에 로그인
    argocd login localhost:8080 --username admin --password <위에서-확인한-비밀번호> --insecure

    ArgoCD 웹 UI 대시보드 — 애플리케이션 동기화 상태와 헬스 체크 결과를 한눈에 확인

    첫 번째 Application 등록 — 실전 GitOps 시작

    Git 저장소 구조 준비

    ArgoCD를 쓸 때 저장소 구조를 어떻게 잡느냐가 은근히 중요하더라고요. 저는 앱 코드와 쿠버네티스 매니페스트를 분리하는 방식을 선호합니다.

    my-gitops-repo/
    ├── apps/
    │   ├── dev/
    │   │   ├── deployment.yaml
    │   │   ├── service.yaml
    │   │   └── kustomization.yaml
    │   └── prod/
    │       ├── deployment.yaml
    │       ├── service.yaml
    │       └── kustomization.yaml
    └── README.md

    예시로 사용할 간단한 Deployment와 Service 매니페스트입니다.

    # apps/dev/deployment.yaml
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: sample-app
      namespace: default
    spec:
      replicas: 2
      selector:
        matchLabels:
          app: sample-app
      template:
        metadata:
          labels:
            app: sample-app
        spec:
          containers:
          - name: sample-app
            image: nginx:1.25
            ports:
            - containerPort: 80
            resources:
              requests:
                cpu: 100m
                memory: 128Mi
              limits:
                cpu: 200m
                memory: 256Mi
    # apps/dev/service.yaml
    apiVersion: v1
    kind: Service
    metadata:
      name: sample-app-svc
      namespace: default
    spec:
      selector:
        app: sample-app
      ports:
      - protocol: TCP
        port: 80
        targetPort: 80
      type: ClusterIP

    ArgoCD Application 생성

    이제 핵심입니다. ArgoCD에게 “이 Git 저장소의 이 경로를 이 클러스터에 배포해줘”라고 알려주는 Application 리소스를 만들어야 해요. YAML로 선언적으로 만드는 걸 추천합니다 — 이것 자체도 Git으로 관리하면 더 좋고요.

    # argocd-application.yaml
    apiVersion: argoproj.io/v1alpha1
    kind: Application
    metadata:
      name: sample-app-dev
      namespace: argocd
    spec:
      project: default
      source:
        repoURL: https://github.com/your-username/my-gitops-repo.git
        targetRevision: HEAD
        path: apps/dev
      destination:
        server: https://kubernetes.default.svc
        namespace: default
      syncPolicy:
        automated:
          prune: true        # Git에서 삭제된 리소스 자동 제거
          selfHeal: true     # 드리프트 감지 시 자동 복구
        syncOptions:
        - CreateNamespace=true
    # Application 생성
    kubectl apply -f argocd-application.yaml
    
    # 동기화 상태 확인
    argocd app get sample-app-dev
    
    # 수동 동기화 (처음 한 번)
    argocd app sync sample-app-dev

    여기서 selfHeal: true 옵션이 제가 아까 말씀드린 드리프트 자동 복구 기능이에요. 누군가 실수로 kubectl로 직접 replica 수를 바꿔도, ArgoCD가 Git에 선언된 값으로 되돌려줍니다. 처음에 이게 동작하는 걸 보고 “오오…” 했던 기억이 나네요.

    Private 저장소 연결

    실무에서는 당연히 Private 저장소를 쓰죠. SSH 키나 Personal Access Token으로 연결할 수 있어요.

    # HTTPS + Personal Access Token 방식
    argocd repo add https://github.com/your-username/my-gitops-repo.git \
      --username your-username \
      --password your-personal-access-token
    
    # SSH 키 방식
    argocd repo add [email protected]:your-username/my-gitops-repo.git \
      --ssh-private-key-path ~/.ssh/id_rsa

    CI 파이프라인과 연동 — 이미지 태그 자동 업데이트

    ArgoCD는 CD(Continuous Delivery) 도구입니다. CI(Continuous Integration)는 별도 도구를 써야 해요. 저는 GitHub Actions를 주로 쓰는데, 흐름은 이렇습니다.

    1. 개발자가 앱 코드를 main 브랜치에 push
    2. GitHub Actions가 Docker 이미지를 빌드하고 레지스트리에 push
    3. GitHub Actions가 GitOps 저장소의 이미지 태그를 새 버전으로 업데이트
    4. ArgoCD가 GitOps 저장소 변경을 감지하고 자동으로 클러스터에 배포
    # .github/workflows/deploy.yaml
    name: Build and Update GitOps Repo
    
    on:
      push:
        branches: [main]
    
    jobs:
      build-and-push:
        runs-on: ubuntu-latest
        steps:
        - name: Checkout app code
          uses: actions/checkout@v3
    
        - name: Set up Docker Buildx
          uses: docker/setup-buildx-action@v2
    
        - name: Login to Container Registry
          uses: docker/login-action@v2
          with:
            registry: ghcr.io
            username: ${{ github.actor }}
            password: ${{ secrets.GITHUB_TOKEN }}
    
        - name: Build and push
          uses: docker/build-push-action@v4
          with:
            push: true
            tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
    
        - name: Update GitOps repo image tag
          run: |
            git clone https://x-access-token:${{ secrets.GITOPS_TOKEN }}@github.com/your-username/my-gitops-repo.git
            cd my-gitops-repo
            # sed로 이미지 태그 업데이트
            sed -i "s|image: ghcr.io/your-username/sample-app:.*|image: ghcr.io/your-username/sample-app:${{ github.sha }}|"\
              apps/dev/deployment.yaml
            git config user.email "[email protected]"
            git config user.name "CI Bot"
            git add apps/dev/deployment.yaml
            git commit -m "chore: update image tag to ${{ github.sha }}"
            git push

    근데 여기서 주의할 점이 있어요. 앱 코드 저장소와 GitOps(매니페스트) 저장소를 분리하는 게 좋습니다. 같은 저장소에 두면 앱 코드 변경과 인프라 변경 이력이 섞여서 나중에 관리가 복잡해지더라고요. 처음에 같이 뒀다가 나중에 분리하는 삽질을 했었는데… 처음부터 분리하세요 ㅎㅎ.

    CI/CD 전체 파이프라인 흐름 — GitHub Actions가 이미지를 빌드하고 GitOps 저장소를 업데이트하면 ArgoCD가 자동 배포

    ⚠️ 삽질 모음 — 이것만 알면 시간 아낍니다

    1. OutOfSync 상태가 계속 유지되는 문제

    분명히 Git이랑 같은 내용인데 ArgoCD에서 계속 OutOfSync라고 뜨는 경우가 있어요. 대부분 리소스 정규화(normalization) 문제입니다. 쿠버네티스가 자동으로 추가하는 필드들(creationTimestamp, status 등)이 Git에 없는 거예요.

    # Application에 ignoreDifferences 추가로 해결
    spec:
      ignoreDifferences:
      - group: apps
        kind: Deployment
        jsonPointers:
        - /spec/replicas  # HPA가 관리하는 경우 replica 수 무시
      - group: ""
        kind: Service
        jsonPointers:
        - /spec/clusterIP

    2. Helm 차트 사용 시 values 파일 관리

    Helm을 ArgoCD와 함께 쓸 때 values 파일을 Git에 두면 됩니다.

    spec:
      source:
        repoURL: https://github.com/your-username/my-gitops-repo.git
        path: charts/my-app
        helm:
          valueFiles:
          - values-dev.yaml
          - values-secrets.yaml  # Sealed Secrets 등으로 암호화된 파일

    3. 시크릿(Secret) 관리 — 절대 Git에 평문으로 넣지 마세요

    이건 정말 중요해요. DB 비밀번호 같은 민감 정보를 실수로 Git에 넣는 사고가 생각보다 많이 일어납니다. 저는 Sealed Secrets나 External Secrets Operator를 씁니다.

    • Sealed Secrets: 공개키로 암호화된 SealedSecret 리소스를 Git에 저장, 클러스터 내 컨트롤러가 복호화
    • External Secrets Operator: AWS Secrets Manager, HashiCorp Vault 등 외부 시크릿 저장소와 연동

    4. ArgoCD 자체도 GitOps로 관리하기 — App of Apps 패턴

    ArgoCD Application 리소스가 많아지면 관리가 힘들어져요. 이럴 때 App of Apps 패턴을 씁니다. 루트 Application 하나가 다른 Application들을 관리하는 계층 구조예요.

    # root-application.yaml — 다른 Application들을 관리하는 루트
    apiVersion: argoproj.io/v1alpha1
    kind: Application
    metadata:
      name: root-app
      namespace: argocd
    spec:
      project: default
      source:
        repoURL: https://github.com/your-username/my-gitops-repo.git
        targetRevision: HEAD
        path: argocd-apps  # 이 폴더 안에 다른 Application YAML들이 있음
      destination:
        server: https://kubernetes.default.svc
        namespace: argocd
      syncPolicy:
        automated:
          prune: true
          selfHeal: true

    ✅ 배포 결과 확인 및 검증

    모든 설정이 끝났으면 이렇게 확인하시면 됩니다.

    # ArgoCD CLI로 앱 상태 확인
    argocd app list
    
    # 상세 상태 확인
    argocd app get sample-app-dev
    
    # 동기화 이력 확인
    argocd app history sample-app-dev
    
    # kubectl로 직접 확인
    kubectl get pods -n default
    kubectl get svc -n default

    ArgoCD UI에서 애플리케이션 상태가 Synced / Healthy로 표시되면 성공입니다. 🎉

    이제 Git 저장소에서 deployment.yaml의 이미지 태그만 바꿔서 커밋하면, 몇 분 안에 (기본 polling 주기는 3분) 클러스터에 자동으로 반영되는 걸 볼 수 있어요. 처음에 이게 자동으로 되는 걸 보고 “드디어 됐다!” 하고 혼자 좋아했던 기억이 납니다 ㅎㅎ.

    # 롤백도 이렇게 간단합니다
    argocd app rollback sample-app-dev 
    
    # 또는 Git에서 revert 후 push하면 ArgoCD가 자동으로 처리

    ArgoCD 애플리케이션 상세 화면 — Synced/Healthy 상태와 쿠버네티스 리소스 트리 시각화

    자주 묻는 질문 (FAQ)

    Q. ArgoCD는 무료인가요?

    네, ArgoCD는 오픈소스(Apache 2.0 라이선스)로 완전 무료입니다. CNCF(Cloud Native Computing Foundation) 졸업 프로젝트이기도 하고요.

    Q. Flux와 ArgoCD 중 어떤 걸 써야 하나요?

    둘 다 GitOps 도구이지만, ArgoCD는 UI가 풍부하고 직관적이라 팀에서 처음 GitOps를 도입할 때 진입 장벽이 낮습니다. Flux는 더 경량이고 CLI 친화적이에요. 저는 팀 협업 환경에서는 ArgoCD를, 단독 운영이나 자동화 중심이면 Flux를 추천합니다.

    Q. 여러 클러스터를 관리할 수 있나요?

    가능합니다. ArgoCD 하나로 여러 쿠버네티스 클러스터를 등록하고 관리할 수 있어요. argocd cluster add 명령어로 추가하면 됩니다.

    마무리 — GitOps, 한 번 맛보면 못 돌아갑니다

    ArgoCD와 GitOps를 도입하고 나서 제 일상이 꽤 달라졌어요. 배포가 무서운 이벤트에서 그냥 평범한 Git 커밋 하나로 바뀌었달까요. 팀원들도 “내가 뭘 배포했는지” Git 로그만 보면 바로 알 수 있으니 커뮤니케이션 비용도 줄었고요.

    물론 처음 셋업할 때 시크릿 관리나 멀티 클러스터 권한 설정 같은 부분에서 삽질이 좀 있긴 합니다. 근데 그 초기 투자를 하고 나면 이후에는 정말 편해요.

    오늘 다룬 내용을 정리하면 이렇습니다.

    • ✅ GitOps 개념과 기존 CI/CD 방식의 차이 이해
    • ✅ ArgoCD 설치 및 초기 설정
    • ✅ Application 리소스로 Git 저장소와 클러스터 연결
    • ✅ GitHub Actions와 연동한 완전 자동화 파이프라인 구축
    • ✅ 자주 겪는 문제와 해결법

    다음 글에서는 ArgoCD ApplicationSet을 활용해서 여러 환경(dev/staging/prod)을 템플릿 하나로 관리하는 방법을 다룰 예정입니다. App of Apps 패턴도 더 깊이 파볼 거고요. 이 글이 도움이 됐다면 댓글로 알려주세요! 궁금한 점도 편하게 물어봐 주시고요. 😊

  • [Cloud] Ansible로 멀티 클라우드 인프라 자동화: AWS, Azure, GCP 통합 관리 가이드

    [Cloud] Ansible로 멀티 클라우드 인프라 자동화: AWS, Azure, GCP 통합 관리 가이드

    Ansible로 멀티 클라우드 인프라 자동화: AWS, Azure, GCP 통합 관리 가이드

    안녕하세요, 13년차 서버실 지킴이입니다. 요즘 인프라 환경을 보면 단순히 한 클라우드에만 갇혀 있지 않죠? 멀티 클라우드(Multi-Cloud)는 이제 선택이 아니라 필수가 되어가는 시대인 것 같습니다. 저도 처음엔 AWS만 주력으로 썼었는데, 프로젝트가 커지고 요구사항이 다양해지면서 Azure나 GCP도 함께 다루게 되더라고요. 근데 이게 여러 클라우드를 동시에 관리하려니 여간 복잡한 게 아니었습니다. 각 클라우드마다 CLI도 다르고, API도 다르고… 수동으로 관리하다가는 퇴근은커녕 야근만 늘겠다 싶었죠. 😭

    이런 고민을 하던 중에 저의 든든한 동반자, Ansible(앤서블)을 떠올렸습니다. Ansible은 이미 온프레미스 환경에서 서버 자동화에 요긴하게 써왔던 도구거든요. ‘이걸로 멀티 클라우드도 통합 관리할 수 있지 않을까?’ 하는 생각에 홈랩에서 이것저것 실험해봤습니다. 결과는 대만족이었습니다! 오늘은 제가 직접 경험하며 삽질했던 내용과 함께, Ansible로 AWS, Azure, GCP 인프라를 한 번에 자동화하는 방법을 여러분께 알려드리려고 합니다. 이 글을 통해 멀티 클라우드 관리의 복잡성을 확 줄여보시길 바랍니다. 자, 그럼 시작해볼까요? 🎉

    Ansible을 활용한 멀티 클라우드 통합 관리 아키텍처 다이어그램.

    Ansible, 왜 멀티 클라우드 자동화에 최적일까요?

    먼저 Ansible이 어떤 녀석인지, 그리고 왜 멀티 클라우드 환경에 딱 맞는지 간단히 짚고 넘어가죠. 쉽게 말해 Ansible(앤서블)은 에이전트리스(Agentless) 기반의 자동화 도구(Automation Tool)입니다. 관리 대상 서버에 별도의 에이전트를 설치할 필요 없이 SSH(Secure Shell)나 WinRM(Windows Remote Management) 프로토콜을 이용해 명령을 실행하죠. 이게 멀티 클라우드 환경에서 엄청난 강점입니다.

    • 단일 제어 플레인(Single Control Plane): 각 클라우드 콘솔이나 CLI를 오갈 필요 없이, Ansible 컨트롤러 노드(Control Node) 하나에서 모든 클라우드 인프라를 관리할 수 있습니다.
    • 모듈(Modules) 기반의 추상화: Ansible은 각 클라우드 프로바이더(Cloud Provider)별로 수많은 모듈을 제공합니다. 예를 들어, AWS EC2 인스턴스를 만들 때는 amazon.aws.ec2_instance 모듈을, Azure VM을 만들 때는 azure.azcollection.azure_rm_virtualmachine 모듈을 사용하죠. 각 클라우드 API의 복잡성을 몰라도 모듈만 잘 사용하면 됩니다.
    • 멱등성(Idempotency): 플레이북(Playbook)을 여러 번 실행해도 항상 동일한 최종 상태(Desired State)를 보장합니다. 이미 생성된 리소스는 건드리지 않고, 필요한 변경사항만 적용되니 안심하고 재실행할 수 있습니다.
    • YAML(YAML Ain’t Markup Language) 기반의 쉬운 학습 곡선: 복잡한 프로그래밍 언어 대신 사람이 읽고 쓰기 쉬운 YAML 문법으로 플레이북을 작성합니다. 인프라 엔지니어에게는 정말 친숙한 방식이죠.

    결론적으로 Ansible은 코드로서의 인프라(Infrastructure as Code, IaC)를 구현하기에 아주 적합하며, 특히 이종 클라우드 환경을 통합 관리하는 데 강력한 성능을 발휘합니다.

    실전 구현: AWS, Azure, GCP 인프라 자동화

    자, 이제 직접 Ansible로 멀티 클라우드 인프라를 구축해볼 차례입니다. 목표는 각 클라우드에 웹 서버 역할을 할 가상 머신(Virtual Machine)을 하나씩 생성하고, 간단한 웹 서비스(Nginx)를 설치하는 것입니다.

    1. Ansible 및 클라우드 연동 환경 설정

    먼저 Ansible 컨트롤러 노드에 필요한 도구들을 설치해야 합니다. 저는 Ubuntu 환경에서 진행했습니다.

    #!/bin/bash
    # Ansible 설치
    sudo apt update && sudo apt install -y ansible python3-pip
    
    # AWS 연동을 위한 boto3 라이브러리 설치
    pip3 install boto3 botocore
    
    # Azure 연동을 위한 Azure CLI 및 모듈 설치
    sudo apt install -y azure-cli
    ansible-galaxy collection install azure.azcollection
    
    # GCP 연동을 위한 Google Cloud SDK 및 모듈 설치
    echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" | sudo tee -a /etc/apt/sources.list.d/google-cloud-sdk.list
    curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key --keyring /usr/share/keyrings/cloud.google.gpg add -
    sudo apt update && sudo apt install -y google-cloud-sdk
    ansible-galaxy collection install google.cloud
    

    각 클라우드 인증 정보 설정도 중요합니다:

    • AWS: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY 환경 변수 설정 또는 ~/.aws/credentials 파일 설정
    • Azure: az login 명령으로 로그인 또는 서비스 주체(Service Principal) 생성 후 환경 변수 설정
    • GCP: 서비스 계정(Service Account) 키 파일 다운로드 후 GOOGLE_APPLICATION_CREDENTIALS 환경 변수 설정

    ⚠️ 인증 정보 관리 중요: 실제 운영 환경에서는 AWS Secrets Manager, Azure Key Vault, GCP Secret Manager 같은 비밀 관리 서비스(Secret Management Service)를 활용하거나 Ansible Vault를 사용하여 인증 정보를 안전하게 보관해야 합니다. 절대 민감한 정보를 플레이북에 직접 넣지 마세요!

    2. 동적 인벤토리(Dynamic Inventory) 설정

    클라우드 환경은 리소스가 수시로 생성/삭제되기 때문에 고정된 인벤토리 파일보다는 동적 인벤토리를 사용하는 것이 효율적입니다. Ansible은 각 클라우드 프로바이더를 위한 동적 인벤토리 플러그인을 제공하거든요.

    # aws_ec2.yml (AWS EC2 인스턴스 동적 인벤토리)
    plugin: amazon.aws.ec2
    regions:
      - ap-northeast-2
    keyed_groups:
      - key: tags.env
        prefix: env_
    
    # azure_rm.yml (Azure VM 동적 인벤토리)
    plugin: azure.azcollection.azure_rm
    include_vm_resource_groups:
      - my-ansible-rg
    
    # gcp_compute.yml (GCP Compute Engine 동적 인벤토리)
    plugin: google.cloud.gcp_compute
    projects:
      - your-gcp-project-id
    zones:
      - asia-northeast3-a
    

    이제 ansible-inventory -i aws_ec2.yml --list 등으로 각 클라우드의 인벤토리를 확인할 수 있습니다.

    Ansible으로 생성된 각 클라우드 가상 머신 목록.

    3. 클라우드 리소스 생성 플레이북 작성

    각 클라우드에 가상 머신을 생성하고 Nginx를 설치하는 플레이북입니다. 하나의 플레이북에서 여러 클라우드를 대상으로 할 수 있다는 점이 핵심입니다.

    ---
    # multi_cloud_infra.yml
    # AWS, Azure, GCP에 동시에 웹 서버를 배포하는 플레이북
    
    - name: AWS 인프라 준비
      hosts: localhost
      connection: local
      gather_facts: no
    
      vars:
        aws_region: ap-northeast-2
        ssh_key_path: ~/.ssh/id_rsa.pub
    
      tasks:
        # AWS Security Group 먼저 생성
        - name: AWS 보안 그룹 생성
          amazon.aws.ec2_security_group:
            name: ansible-sg-aws
            description: "Ansible managed AWS webserver security group"
            region: "{{ aws_region }}"
            rules:
              - proto: tcp
                ports: 22
                cidr_ip: 0.0.0.0/0
              - proto: tcp
                ports: 80
                cidr_ip: 0.0.0.0/0
            rules_egress:
              - proto: all
                cidr_ip: 0.0.0.0/0
          register: aws_sg_output
    
        # AWS EC2 인스턴스 생성
        - name: AWS EC2 인스턴스 생성
          amazon.aws.ec2_instance:
            name: ansible-aws-webserver
            image_id: ami-0c802847a7dd848c0  # Ubuntu 22.04 LTS (ap-northeast-2)
            instance_type: t2.micro
            region: "{{ aws_region }}"
            security_group: ansible-sg-aws
            key_name: your_aws_keypair
            tags:
              env: dev
              project: ansible-multi-cloud
            state: running
            wait: yes
          register: aws_ec2_output
    
    - name: Azure 인프라 준비
      hosts: localhost
      connection: local
      gather_facts: no
    
      vars:
        azure_resource_group: my-ansible-rg
        azure_location: koreacentral
        ssh_key_path: ~/.ssh/id_rsa.pub
    
      tasks:
        # Azure Virtual Network 생성 (필수 선행 작업)
        - name: Azure Virtual Network 생성
          azure.azcollection.azure_rm_virtualnetwork:
            resource_group: "{{ azure_resource_group }}"
            name: ansible-vnet
            location: "{{ azure_location }}"
            address_prefixes:
              - "10.0.0.0/16"
          register: azure_vnet_output
    
        # Azure Subnet 생성
        - name: Azure Subnet 생성
          azure.azcollection.azure_rm_subnet:
            resource_group: "{{ azure_resource_group }}"
            name: default
            address_prefix: "10.0.0.0/24"
            virtual_network: ansible-vnet
          register: azure_subnet_output
    
        # Azure NSG 생성
        - name: Azure 네트워크 보안 그룹 생성
          azure.azcollection.azure_rm_networksecuritygroup:
            resource_group: "{{ azure_resource_group }}"
            name: ansible-azure-nsg
            location: "{{ azure_location }}"
            rules:
              - name: AllowSSH
                protocol: Tcp
                destination_port_range: 22
                access: Allow
                priority: 100
                direction: Inbound
              - name: AllowHTTP
                protocol: Tcp
                destination_port_range: 80
                access: Allow
                priority: 110
                direction: Inbound
          register: azure_nsg_output
    
        # Azure VM 생성
        - name: Azure VM 생성
          azure.azcollection.azure_rm_virtualmachine:
            resource_group: "{{ azure_resource_group }}"
            name: ansible-azure-webserver
            vm_size: Standard_B1s
            admin_username: azureuser
            ssh_password_enabled: false
            ssh_public_keys:
              - path: /home/azureuser/.ssh/authorized_keys
                key_data: "{{ lookup('file', ssh_key_path) }}"
            image:
              offer: 0001-com-ubuntu-server-jammy
              publisher: Canonical
              sku: 22_04-lts-gen2
              version: latest
            location: "{{ azure_location }}"
          register: azure_vm_output
    
    - name: GCP 인프라 준비
      hosts: localhost
      connection: local
      gather_facts: no
    
      vars:
        gcp_project: your-gcp-project-id
        gcp_zone: asia-northeast3-a
        gcp_network: default
        ssh_key_path: ~/.ssh/id_rsa.pub
        gcp_user: ansible
    
      tasks:
        # GCP Firewall 규칙 생성
        - name: GCP 방화벽 규칙 생성
          google.cloud.gcp_compute_firewall:
            name: ansible-gcp-allow-http-ssh
            project: "{{ gcp_project }}"
            allowed:
              - IPProtocol: tcp
                ports: [ '22', '80' ]
            source_ranges: [ '0.0.0.0/0' ]
            target_tags: [ 'http-server' ]
            state: present
          register: gcp_fw_output
    
        # GCP Compute Engine 인스턴스 생성
        - name: GCP Compute Engine 인스턴스 생성
          google.cloud.gcp_compute_instance:
            name: ansible-gcp-webserver
            project: "{{ gcp_project }}"
            zone: "{{ gcp_zone }}"
            machine_type: e2-micro
            disks:
              - auto_delete: 'true'
                boot: 'true'
                initialize_params:
                  source_image: projects/ubuntu-os-cloud/global/images/family/ubuntu-2204-lts
                  disk_size_gb: 20
            network_interfaces:
              - name: "{{ gcp_network }}"
                access_configs:
                  - name: "External NAT"
                    type: "ONE_TO_ONE_NAT"
            metadata:
              ssh-keys: "{{ gcp_user }}:{{ lookup('file', ssh_key_path) }}"
            tags:
              items:
                - http-server
            state: present
          register: gcp_instance_output
    
    - name: 모든 클라우드 웹서버에 Nginx 설치
      hosts: all
      gather_facts: yes
    
      pre_tasks:
        - name: SSH 연결 대기
          ansible.builtin.wait_for_connection:
            timeout: 300
    
      tasks:
        - name: Nginx 설치
          ansible.builtin.apt:
            name: nginx
            state: present
            update_cache: yes
          become: yes
    
        - name: Nginx 서비스 시작
          ansible.builtin.service:
            name: nginx
            state: started
            enabled: yes
          become: yes
    

    위 플레이북은 각 클라우드별로 분리된 구조로 작성되어 있습니다. 첫 번째부터 세 번째까지는 각각 AWS, Azure, GCP 인프라를 생성하는 hosts: localhost 플레이북입니다. 마지막 플레이북은 동적 인벤토리에서 수집된 모든 호스트에 Nginx를 설치합니다.

    플레이북 실행은 간단합니다:

    ansible-playbook -i aws_ec2.yml -i azure_rm.yml -i gcp_compute.yml multi_cloud_infra.yml
    

    ⚠️ 삽질 경험 및 트러블슈팅

    제가 이 과정을 진행하면서 겪었던 몇 가지 삽질 경험과 해결 방법을 공유합니다. 여러분은 저처럼 헤매지 마시길 바랍니다. ㅎㅎ

    1. 클라우드 인증 실패

    문제: 플레이북 실행 시 Unauthorized, Access Denied, Credential Error 같은 메시지가 뜨면서 클라우드 리소스 생성이 안 되는 경우가 많았습니다.

    해결:

    • AWS: ~/.aws/credentials 파일의 내용이 정확한지, 또는 환경 변수(AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY)가 올바르게 설정되었는지 확인했습니다. 그리고 해당 IAM 사용자에게 EC2 생성, Security Group 관리 등 필요한 권한이 제대로 부여되었는지 IAM 정책을 검토했습니다.
    • Azure: az login으로 로그인 세션이 유효한지 확인하거나, 서비스 주체(Service Principal)를 사용한다면 AZURE_CLIENT_ID, AZURE_SECRET, AZURE_TENANT, AZURE_SUBSCRIPTION_ID 환경 변수가 정확한지 확인했습니다. 해당 서비스 주체에 기여자(Contributor) 또는 그에 준하는 역할이 할당되어야 합니다.
    • GCP: 서비스 계정 키 파일(JSON)의 경로가 GOOGLE_APPLICATION_CREDENTIALS 환경 변수에 올바르게 지정되었는지 확인했습니다. 또한, 서비스 계정에 Compute Engine Instance Admin (v1), Service Account User 등 필요한 역할이 부여되어 있는지 GCP IAM 콘솔에서 확인했습니다.

    2. SSH 접속 문제 (Nginx 설치 단계)

    문제: 인스턴스는 잘 생성됐는데, Nginx 설치 단계에서 SSH Connection refused 또는 Timeout 에러가 발생했습니다.

    해결:

    • 보안 그룹/방화벽 규칙: 각 클라우드에서 생성된 가상 머신의 보안 그룹(AWS), 네트워크 보안 그룹(Azure), 방화벽 규칙(GCP)에 Ansible 컨트롤러 노드의 IP 주소 또는 0.0.0.0/0(테스트용)에서 22번 포트(SSH) 접속을 허용하는 규칙이 있는지 확인했습니다.
    • SSH 키페어: 플레이북에서 지정한 ssh_key_path의 퍼블릭 키가 각 클라우드 인스턴스에 올바르게 등록되었는지 확인했습니다. AWS는 키페어 이름을, Azure/GCP는 직접 퍼블릭 키 내용을 전달하는 방식이므로 차이에 유의했습니다.
    • 사용자 이름: 각 클라우드 인스턴스의 기본 사용자 이름이 다릅니다. AWS Ubuntu는 ubuntu, Azure Ubuntu는 azureuser, GCP Ubuntu는 ubuntu로 설정했습니다. 플레이북에 ansible_user: 사용자명으로 명시하면 더 안전합니다.
    • 인스턴스 부팅 시간: 가끔 인스턴스가 완전히 부팅되기 전에 Ansible이 SSH 접속을 시도하여 실패하는 경우가 있습니다. ansible.builtin.wait_for_connection 모듈을 사용해 SSH 포트가 열릴 때까지 기다리도록 플레이북에 추가했습니다. 이건 정말 중요해요!

    3. Azure 네트워크 구성 누락

    문제: Azure VM을 생성하려니 Virtual Network와 Subnet이 없다고 에러가 나왔습니다.

    해결: Azure는 AWS나 GCP와 달리 기본 네트워크 구조가 없으므로, VM 생성 전에 Virtual Network와 Subnet을 명시적으로 생성해야 합니다. 위의 플레이북에서 이를 추가했으니 참고하세요.

    검증 및 결과 확인 ✅

    플레이북 실행이 성공적으로 완료되었다면, 이제 각 클라우드 콘솔에 접속하여 리소스가 잘 생성되었는지 확인해볼 차례입니다. AWS EC2, Azure VM, GCP Compute Engine 목록에서 ansible-aws-webserver, ansible-azure-webserver, ansible-gcp-webserver라는 이름의 인스턴스가 실행 중인 것을 볼 수 있을 겁니다.

    각 인스턴스의 퍼블릭 IP 주소로 웹 브라우저에서 접속해보세요. Nginx 기본 페이지가 보인다면 성공입니다! 드디어 됐다! 🎉

    이 과정에서 여러분이 직접 각 클라우드의 콘솔을 열어 인스턴스를 생성하고, 보안 그룹을 설정하고, SSH로 접속해서 Nginx를 설치하는 수고를 덜 수 있었다는 것을 체감할 수 있을 겁니다. 자동화의 힘은 정말 대단하죠.

    성공적으로 배포된 Nginx 웹 서버 페이지.

    마무리하며: 클라우드 여정의 다음 단계

    오늘은 Ansible을 활용해서 AWS, Azure, GCP 멀티 클라우드 환경에 인프라를 자동화하고 웹 서버를 배포하는 과정을 함께 해봤습니다. 제가 직접 해보니, 처음엔 각 클라우드 모듈과 인증 방식에 적응하는 데 시간이 좀 걸렸지만, 한 번 체계를 잡아두니 그 이후부터는 정말 편하더라고요. 삽질 끝에 얻은 귀한 경험이었습니다. 💡

    이 글이 여러분의 멀티 클라우드 여정에 작은 등대가 되었기를 바랍니다. 여기서 멈추지 않고, 더 나아가 다음과 같은 내용들을 탐구해보시면 좋을 것 같습니다:

    • Ansible Vault: 민감한 정보를 안전하게 관리하는 방법.
    • CI/CD 파이프라인 통합: GitOps 워크플로우를 통해 변경 사항이 자동으로 배포되도록 구성.
    • Terraform과의 연동: Terraform으로 인프라를 프로비저닝하고, Ansible로 프로비저닝된 인스턴스에 소프트웨어를 구성하는 하이브리드 접근 방식.
    • 삭제 플레이북: 생성된 리소스를 깔끔하게 정리하는 삭제 플레이북 작성 (state: absent 활용).

    저도 홈랩에서 이런저런 실험을 계속하면서 새로운 인사이트를 얻고 있습니다. 다음번에는 더 재미있고 유익한 경험담으로 찾아뵙겠습니다. 혹시 이 글을 읽으시면서 궁금한 점이나 공유하고 싶은 삽질 경험이 있으시다면 언제든지 댓글로 남겨주세요! 감사합니다!

    Ansible을 통한 멀티 클라우드 자동화 요약.

  • [k8s] Flux CD GitOps 파이프라인 구축: 안정적인 쿠버네티스 배포 자동화

    [k8s] Flux CD GitOps 파이프라인 구축: 안정적인 쿠버네티스 배포 자동화

    안녕하세요, 13년차 서버실 지킴이입니다. 오늘은 많은 인프라 엔지니어분들이 한 번쯤은 고민해봤을 주제, 바로 쿠버네티스(Kubernetes) 배포 자동화에 대해 이야기해보려고 합니다. 특히 GitOps(깃옵스) 방법론과 그 구현체 중 하나인 Flux CD(플럭스 CD)를 활용한 파이프라인 구축 경험을 공유해 드릴게요.

    혹시 이런 경험 있으신가요? 개발팀에서 “배포해주세요!” 요청이 들어오면, kubectl apply -f 명령어를 조심스럽게 입력하면서, “혹시나 다른 설정이 덮어씌워지는 건 아닐까?”, “이전 버전으로 되돌리려면 어떻게 해야 하지?” 같은 걱정을 했던 순간 말이죠. 저는 셀 수 없이 많습니다. 수동 배포는 휴먼 에러의 온상이었고, 배포하다 새벽을 맞이하는 일도 비일비재했거든요. 이런 삽질을 거듭하다가 GitOps를 만나고 나서야 비로소 안정적인 배포의 빛을 보게 되었습니다. 제 홈랩에서도 여러 서비스를 GitOps로 관리하고 있는데, 정말 편하더라고요!

    GitOps 파이프라인은 Git 저장소를 ‘진리의 단일 출처(Single Source of Truth)’로 삼아, 쿠버네티스 클러스터의 상태를 Git에 선언된 대로 유지하는 방식입니다. 간단히 말해, Git에 저장된 설정 파일이 곧 클러스터의 현재 상태여야 한다는 거죠.

    GitOps와 Flux CD, 정말 좋은 이유

    처음 GitOps라는 개념을 들었을 때는 “어차피 Git에 YAML 파일 올리는 건 똑같은데, 뭐가 다르지?” 싶었어요. 근데 실제로 써보니 이 방식이 가진 장점이 정말 많더라고요. 제가 느낀 가장 큰 장점들을 꼽아보면 이렇습니다.

    • 일관성(Consistency): 모든 설정이 Git에 있으니, 개발, 스테이징, 운영 환경 간의 차이를 최소화할 수 있어요. “제 환경에서는 잘 되는데요?” 하는 말이 확 줄어듭니다.
    • 감사 및 추적(Auditability & Traceability): Git 커밋(Commit) 기록 자체가 변경 이력이 되니까요. 누가, 언제, 무엇을 변경했는지 명확하게 알 수 있죠. 문제 발생 시 롤백(Rollback)도 Git으로 너무나 쉽습니다.
    • 안정성(Reliability): Git에 선언된 상태와 클러스터의 실제 상태가 다르면, GitOps 에이전트가 자동으로 클러스터를 Git의 상태로 되돌립니다. 마치 자가 치유(Self-healing) 능력 같달까요?
    • 생산성(Productivity): 수동 작업이 줄어들고 자동화되면서, 엔지니어는 더 중요한 일에 집중할 수 있게 됩니다.

    이런 GitOps의 철학을 구현하는 대표적인 도구가 바로 Flux CD입니다. Flux CD는 쿠버네티스 클러스터 내에서 동작하는 컨트롤러(Controller)들의 집합인데요, 주기적으로 Git 저장소를 감시하다가 변경 사항이 감지되면 자동으로 클러스터에 적용해주는 역할을 합니다. 주요 컴포넌트로는 Source Controller(소스 컨트롤러), Kustomize Controller(커스터마이즈 컨트롤러), Helm Controller(헬름 컨트롤러) 등이 있습니다.

    Flux CD GitOps 파이프라인 구축 실전 가이드

    자, 그럼 이제 제 홈랩에서 Flux CD를 어떻게 구축했는지, 단계별로 자세히 알려드릴게요. 저도 처음엔 공식 문서를 보면서 여러 번 헤맸는데, 이 가이드가 여러분의 삽질 시간을 줄여주길 바랍니다!

    1단계: 사전 준비 및 Flux CLI 설치

    먼저, 쿠버네티스 클러스터에 접근 가능한 환경과 kubectl, git이 설치되어 있어야 합니다. 저는 로컬 PC에 flux CLI(Command Line Interface)를 설치하는 것부터 시작했어요.

    # Flux CLI 설치 (macOS 기준)
    brew install fluxcd/flux/flux
    
    # 설치 확인
    flux --version
    # flux version 2.x.x (최신 버전으로 설치됩니다)
    
    # 클러스터에 Flux CD가 설치될 네임스페이스 생성 (선택 사항)
    kubectl create namespace flux-system
    

    flux-system 네임스페이스는 Flux CD 컨트롤러들이 배포될 기본 공간입니다. 따로 지정하지 않으면 기본으로 이 네임스페이스에 설치되죠.

    2단계: Git 저장소 준비

    Flux CD는 Git 저장소를 바라보며 작동하기 때문에, 쿠버네티스 설정 파일(YAML)들을 저장할 Git 저장소가 필요합니다. 저는 GitHub에 gitops-k8s-homelab이라는 프라이빗(Private) 저장소를 만들었어요. 저장소 안에는 다음과 같은 구조로 파일을 구성할 예정입니다.

    gitops-k8s-homelab/
    ├── clusters/
    │   └── my-cluster/
    │       ├── flux-system/
    │       │   └── gotk-components.yaml
    │       │   └── gotk-sync.yaml
    │       └── apps/
    │           └── my-app/
    │               └── kustomization.yaml
    ├── apps/
    │   └── my-app/
    │       ├── deployment.yaml
    │       ├── service.yaml
    │       └── ingress.yaml
    └── README.md
    

    clusters/my-cluster/flux-system 경로에는 Flux CD 자체의 설정 파일들이, clusters/my-cluster/apps 경로에는 클러스터에 배포할 애플리케이션들을 정의하는 Kustomization 파일들이 들어갈 겁니다. 실제 애플리케이션 YAML 파일들은 apps/my-app 경로에 두고요.

    3단계: Flux CD 부트스트랩 (Bootstrap)

    이제 가장 중요한 단계입니다. flux bootstrap github 명령어를 사용해서 Flux CD를 쿠버네티스 클러스터에 설치하고, Git 저장소와 연결하는 작업을 수행합니다. 이 명령 한 방으로 Flux CD가 클러스터에 필요한 모든 컴포넌트를 배포하고, 지정된 Git 저장소를 바라보도록 설정해줍니다. 저는 GitHub를 사용했으니 github 옵션을 사용했어요.

    # 환경 변수 설정 (개인 GitHub 토큰과 사용자명으로 대체하세요!)
    export GITHUB_TOKEN="YOUR_GITHUB_TOKEN" # Personal Access Token
    export GITHUB_USER="YOUR_GITHUB_USERNAME"
    
    # Flux CD 부트스트랩 실행
    flux bootstrap github \
      --owner=${GITHUB_USER} \
      --repository=gitops-k8s-homelab \
      --branch=main \
      --path=clusters/my-cluster \
      --personal
    

    이 명령을 실행하면 Flux CD가 클러스터에 설치되고, clusters/my-cluster 경로를 기준으로 Git 저장소의 내용을 동기화하기 시작합니다. --personal 옵션은 개인 저장소에 주로 사용하고, 조직(Organization) 저장소의 경우 --owner를 조직명으로 지정하고 SSH 키 방식으로 인증하는 것이 일반적입니다. 저는 홈랩이라 개인 토큰으로 편하게 작업했어요.

    부트스트랩이 완료되면, Git 저장소의 clusters/my-cluster/flux-system 경로에 Flux CD 자체의 Kustomization 파일들이 자동으로 생성되어 커밋되는 것을 볼 수 있습니다. 이게 바로 Flux CD가 스스로를 GitOps 방식으로 관리하는 모습이죠. 신기하더라고요!

    4단계: 애플리케이션 배포를 위한 Kustomization 정의

    이제 클러스터에 배포할 애플리케이션을 정의하고 Flux CD가 이를 인식하도록 설정할 차례입니다. 먼저, apps/my-app 경로에 간단한 NGINX 애플리케이션을 정의하는 YAML 파일들을 만듭니다.

    # apps/my-app/deployment.yaml
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: my-nginx-app
      labels:
        app: my-nginx-app
    spec:
      replicas: 2
      selector:
        matchLabels:
          app: my-nginx-app
      template:
        metadata:
          labels:
            app: my-nginx-app
        spec:
          containers:
          - name: nginx
            image: nginx:latest
            ports:
            - containerPort: 80
    ---
    # apps/my-app/service.yaml
    apiVersion: v1
    kind: Service
    metadata:
      name: my-nginx-app-service
    spec:
      selector:
        app: my-nginx-app
      ports:
      - protocol: TCP
        port: 80
        targetPort: 80
      type: ClusterIP
    

    그리고 이 애플리케이션을 클러스터에 배포하도록 지시하는 Kustomization 파일을 clusters/my-cluster/apps/my-app/kustomization.yaml 경로에 생성합니다.

    # clusters/my-cluster/apps/my-app/kustomization.yaml
    apiVersion: kustomize.toolkit.fluxcd.io/v1
    kind: Kustomization
    metadata:
      name: my-nginx-app
      namespace: flux-system
    spec:
      interval: 1m0s
      sourceRef:
        kind: GitRepository
        name: flux-system
      path: ./apps/my-app
      prune: true
      validation: client
    

    이 파일들을 Git 저장소에 커밋(Commit)하고 푸시(Push)합니다.

    git add .
    git commit -m "Add my-nginx-app and kustomization"
    git push origin main
    

    Flux CD는 clusters/my-cluster/flux-system/gotk-sync.yaml 파일에 정의된 Kustomization에 의해 clusters/my-cluster 경로를 1분마다 동기화합니다. 그러면 방금 추가한 clusters/my-cluster/apps/my-app/kustomization.yaml 파일이 클러스터에 적용되고, 이 Kustomization이 다시 apps/my-app 경로의 NGINX 애플리케이션을 클러스터에 배포하게 됩니다. 이 모든 과정이 자동으로 이뤄지는 거죠!

    ⚠️ 삽질 경험 & 트러블슈팅 팁

    제가 Flux CD를 사용하면서 겪었던 몇 가지 삽질과 해결 팁을 공유해 드릴게요. 여러분은 저처럼 헤매지 마시라고요! ㅎㅎ

    • 동기화 지연 문제: Git에 변경사항을 푸시했는데, 클러스터에 바로 적용이 안 되는 경우가 있습니다. Kustomization 리소스의 interval 값이 기본 10분으로 설정되어 있거나, 네트워크 문제 등으로 Git 저장소에 접근이 안 되는 경우가 많았어요. 급할 때는 다음 명령어로 강제 동기화를 시도합니다.
      flux reconcile kustomization my-nginx-app --with-source
      

      이 명령은 해당 Kustomization과 그 소스 GitRepository를 즉시 동기화하도록 Flux CD에 지시합니다.

    • Git Repository 구조 설계: 처음에는 모든 YAML 파일을 한 폴더에 때려 넣었는데, 나중에 애플리케이션이 많아지고 환경이 복잡해지면서 관리가 힘들어지더라고요. clusters/[클러스터이름]/[환경]/apps/[앱이름] 같은 계층적인 구조나, clusters와 apps를 분리하는 구조로 가져가는 것이 훨씬 효율적입니다. 저는 지금 clusters와 apps를 분리해서 관리하고 있어요.
    • 시크릿(Secret) 관리: 데이터베이스 비밀번호 같은 민감한 정보는 Git에 평문으로 올릴 수 없죠. 저는 SOPS(Secrets OPerationS)를 사용해서 Git 저장소에 암호화된 시크릿을 저장하고, Flux CD가 배포 시 자동으로 복호화하도록 설정했습니다. EKS(Elastic Kubernetes Service) 같은 클라우드 환경에서는 KMS(Key Management Service)를 연동해서 SOPS를 사용하는 것이 일반적입니다.
    • CRD(Custom Resource Definition) 적용 순서: 때로는 특정 컨트롤러나 미들웨어의 CRD가 먼저 클러스터에 적용되어야만 해당 리소스를 배포할 수 있습니다. 예를 들어, Istio(이스티오)나 Cert-Manager(서트 매니저) 같은 도구들이 그렇죠. 이런 경우, Kustomization의 dependsOn 필드를 사용하거나, healthChecks를 설정하여 의존성을 명확히 해주는 것이 중요합니다.

    ✅ 배포 결과 확인 및 검증

    이제 모든 설정이 잘 적용되었는지 확인해볼 시간입니다. flux get kustomizations 명령어로 Flux CD가 관리하는 Kustomization 리소스들의 상태를 확인할 수 있습니다.

    flux get kustomizations
    NAME            REVISION        SUSPENDED   READY   MESSAGE                                         LAST ACTIVITY
    flux-system     main/a1b2c3d4   False       True    Kustomization reconciled successfully           2026-05-15T10:00:00Z
    my-nginx-app    main/e5f6g7h8   False       True    Kustomization reconciled successfully           2026-05-15T10:01:00Z
    

    READY 상태가 True이고 MESSAGE에 성공 메시지가 보인다면, Flux CD가 정상적으로 작동하고 있다는 뜻입니다. 이제 쿠버네티스 클러스터에 NGINX 애플리케이션이 잘 배포되었는지 확인해볼까요?

    kubectl get deployment my-nginx-app
    NAME           READY   UP-TO-DATE   AVAILABLE   AGE
    my-nginx-app   2/2     2            2           5m
    
    kubectl get service my-nginx-app-service
    NAME                     TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
    my-nginx-app-service     ClusterIP   10.96.100.123   <none>        80/TCP    5m
    

    🎉 드디어 성공입니다! my-nginx-app 배포가 정상적으로 2개의 레플리카(Replica)로 동작하고 있는 것을 확인할 수 있네요. 이제 Git 저장소의 apps/my-app/deployment.yaml 파일에서 replicas 수를 3으로 변경하고 푸시해보세요. 잠시 후 Flux CD가 이를 감지하고 자동으로 클러스터의 레플리카 수를 3으로 업데이트할 겁니다. 이 경험을 하고 나면 GitOps의 매력에서 헤어 나올 수 없을 거예요!

    GitOps는 이렇게 Git에 대한 변경만으로 클러스터의 상태를 관리할 수 있게 해줍니다. 저는 이 편리함 덕분에 홈랩에서 새로운 서비스를 배포할 때마다 kubectl 명령어를 직접 입력할 일이 거의 없어졌어요. 모든 변경사항은 Git에 기록되니, 누가 어떤 변경을 했는지도 명확하게 알 수 있고요. 정말 안정적이고 편리한 배포 파이프라인이 구축된 거죠.

    마무리하며: GitOps와 Flux CD, 현대 인프라의 필수 도구

    오늘은 13년차 인프라 엔지니어의 경험을 바탕으로 Flux CD GitOps 파이프라인 구축에 대해 상세히 이야기해봤습니다. 쿠버네티스 배포 자동화는 현대 인프라에서 선택이 아닌 필수가 되어가고 있습니다. 특히 GitOps는 그 중심에서 안정성, 일관성, 생산성이라는 세 마리 토끼를 동시에 잡을 수 있게 해주는 강력한 방법론이라고 생각합니다.

    물론 처음에는 GitOps 개념이나 Flux CD 사용법이 낯설고 어렵게 느껴질 수 있습니다. 저도 그랬거든요. 하지만 한 번 제대로 구축하고 나면, 인프라 관리의 패러다임이 확 바뀌는 것을 경험하실 수 있을 겁니다. 마치 수동으로 서버를 한 대 한 대 설치하다가 프로비저닝(Provisioning) 자동화를 접했을 때의 충격과 비슷하다고 할까요?

    다음 글에서는 오늘 다루지 못한 Helm Chart(헬름 차트)를 Flux CD로 관리하는 방법이나, 멀티 클러스터(Multi-cluster) 환경에서의 GitOps 전략, 그리고 SOPS를 이용한 시크릿 관리 등 좀 더 심화된 주제들을 다뤄볼 예정입니다. 이 글이 여러분의 쿠버네티스 여정에 작은 도움이 되었기를 바랍니다!

    궁금한 점이나 저의 삽질 경험에 대한 질문이 있으시면 언제든지 댓글 남겨주세요! 저도 계속 배우고 성장하는 중이거든요. 😊

  • [Cloud] GitHub Actions OIDC로 AWS 인증하기: 보안 강화 및 자격 증명 관리

    [Cloud] GitHub Actions OIDC로 AWS 인증하기: 보안 강화 및 자격 증명 관리

    GitHub Actions에서 AWS 인증, 아직도 IAM Access Key 쓰시나요?

    안녕하세요, 13년차 인프라 엔지니어이자 서버실 주인장입니다. 지난 몇 년간 수많은 CI/CD 파이프라인(Continuous Integration/Continuous Delivery Pipeline)을 구축하고 관리해왔는데, GitHub Actions와 AWS를 연동해서 사용하는 경우가 정말 많았거든요. 처음에는 저도 많은 분들처럼 AWS IAM(Identity and Access Management) 사용자의 Access Key와 Secret Key를 GitHub Secrets에 넣어 사용했습니다. 편리하긴 했죠. 근데 이게 뭔가 찜찜하더라고요.

    Access Key는 말 그대로 영구적인 자격 증명(Long-lived Credentials)이라 탈취 위험이 늘 존재하고, 정기적으로 Key를 교체(Rotation)해야 하는 관리 부담도 만만치 않았습니다. 만약 여러 레포지토리에서 같은 키를 쓴다면 더더욱 골치 아파지고요. 혹시 이런 경험 있으신가요? 저도 처음엔 이 방식밖에 없나 싶었는데, 다행히 더 안전하고 효율적인 방법이 등장했습니다. 바로 GitHub Actions OIDC(OpenID Connect)를 활용한 AWS 인증입니다! 오늘은 이 OIDC를 이용해 GitHub Actions에서 AWS에 안전하게 접근하는 방법을 제 경험을 바탕으로 자세히 알려드릴게요. CI/CD 파이프라인의 보안을 한 단계 끌어올리는 중요한 포인트가 될 겁니다.

    GitHub Actions OIDC를 이용한 AWS 인증의 전체적인 아키텍처 다이어그램입니다. GitHub Actions가 OIDC Provider 역할을 하고, AWS IAM이 이를 신뢰하여 임시 자격 증명을 발급하는 과정을 보여줍니다.

    OIDC(OpenID Connect)와 워크로드 아이덴티티(Workload Identity)가 뭔가요?

    본격적인 설정에 들어가기 전에, OIDC와 워크로드 아이덴티티라는 개념을 잠시 짚고 넘어가면 좋습니다. 복잡하게 들릴 수 있지만, 쉽게 말해볼게요.

    • OIDC (OpenID Connect, 오픈아이디 커넥트): OAuth 2.0 위에 구축된 인증(Authentication) 프로토콜입니다. 사용자(혹은 서비스)가 어떤 Identity Provider(신원 제공자)에게 “나 누구야”라고 인증받으면, Identity Provider는 ID Token이라는 걸 발급해줍니다. 이 토큰 안에는 “이 사람이 누구고, 어떤 방식으로 인증했는지” 같은 정보가 담겨있죠. AWS의 경우, GitHub Actions가 Identity Provider 역할을 하고, AWS IAM이 이 ID Token을 신뢰하는 Relying Party(의존 당사자)가 되는 겁니다.
    • 워크로드 아이덴티티 (Workload Identity): CI/CD 파이프라인의 워크로드(Workload)나 컨테이너처럼 사람이 아닌 시스템에 신원(Identity)을 부여하는 개념입니다. 기존의 Access Key 방식처럼 영구적인 자격 증명을 주지 않고, 필요할 때만 임시 자격 증명(Temporary Credentials)을 발급받아 사용하는 방식이에요. 이렇게 하면 자격 증명 탈취 위험을 최소화하고, 키 관리 부담을 없앨 수 있습니다.

    결론적으로, GitHub Actions OIDC를 사용하면 GitHub Actions 워크플로우가 직접 Access Key 없이 AWS에 “나 GitHub Actions의 이 레포지토리, 이 브랜치에서 실행된 애야!”라고 증명하고, AWS는 이 신원을 확인한 후 특정 권한을 가진 임시 자격 증명을 주는 방식이라고 이해하시면 됩니다. 진짜 편하고 안전하더라고요!

    GitHub Actions OIDC로 AWS 인증 설정 단계별 가이드

    자, 이제 실제로 GitHub Actions OIDC를 이용해 AWS에 인증하는 방법을 단계별로 따라해 볼까요? 제가 직접 해보니 몇 가지 포인트만 잘 기억하면 어렵지 않게 설정할 수 있었습니다.

    1단계: AWS IAM Identity Provider 생성하기

    가장 먼저 AWS IAM에서 GitHub Actions를 신뢰할 수 있는 OIDC Identity Provider로 등록해야 합니다. AWS 콘솔에서 진행하는 것이 가장 직관적입니다.

    1. AWS 콘솔에 로그인 후 IAM 서비스로 이동합니다.
    2. 왼쪽 탐색 메뉴에서 Access management (액세스 관리) > Identity Providers (자격 증명 공급자)를 클릭합니다.
    3. Add provider (공급자 추가) 버튼을 클릭합니다.
    4. Provider type (공급자 유형)으로 OpenID Connect를 선택합니다.
    5. Provider URL (공급자 URL)에 <code>https://token.actions.githubusercontent.com 을 입력합니다.
    6. Get thumbprint (지문 가져오기) 버튼을 클릭하여 서버 인증서 지문을 자동으로 가져옵니다.
    7. Audience (대상)에는 sts.amazonaws.com 을 입력하고 Add provider (공급자 추가)를 클릭하여 완료합니다.

    💡 팁: sts.amazonaws.com은 AWS Security Token Service의 기본 대상입니다. 다른 용도로 특정 서비스에만 OIDC를 사용한다면 해당 서비스의 Audience를 사용할 수도 있습니다.

    AWS IAM 콘솔에서 OIDC Identity Provider를 생성하는 화면입니다. Provider URL과 Audience를 정확히 입력하는 것이 중요합니다.

    2단계: AWS IAM Role 생성하기

    이제 이 OIDC Identity Provider를 통해 GitHub Actions가 임시 자격 증명을 요청할 수 있는 IAM Role을 생성해야 합니다. 이 역할에는 GitHub Actions가 AWS에서 수행할 작업에 대한 권한을 부여하게 됩니다.

    1. IAM 콘솔에서 Access management (액세스 관리) > Roles (역할)로 이동합니다.
    2. Create role (역할 생성) 버튼을 클릭합니다.
    3. Select type of trusted entity (신뢰할 수 있는 개체 유형 선택)에서 Custom trust policy (사용자 지정 신뢰 정책)를 선택합니다.
    4. 다음과 같이 신뢰 정책을 작성합니다. 여기서 StringEquals 조건이 핵심입니다!
    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Principal": {
            "Federated": "arn:aws:iam::YOUR_AWS_ACCOUNT_ID:oidc-provider/token.actions.githubusercontent.com"
          },
          "Action": "sts:AssumeRoleWithWebIdentity",
          "Condition": {
            "StringEquals": {
              "token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
              "token.actions.githubusercontent.com:sub": "repo:YOUR_GITHUB_ORG/YOUR_GITHUB_REPO:ref:refs/heads/main"
            }
          }
        }
      ]
    }

    ⚠️ 주의사항:

    • YOUR_AWS_ACCOUNT_ID: 여러분의 AWS 계정 ID로 교체해야 합니다.
    • YOUR_GITHUB_ORG/YOUR_GITHUB_REPO: GitHub 조직(Organization) 이름과 레포지토리(Repository) 이름으로 교체해야 합니다.
    • ref:refs/heads/main: 이 부분은 특정 브랜치(Branch)에서만 역할 가정을 허용하겠다는 의미입니다. *를 사용하여 모든 브랜치를 허용할 수도 있지만, 보안을 위해 특정 브랜치를 지정하는 것을 권장합니다. 예를 들어, 특정 태그(tag)에서만 허용하려면 ref:refs/tags/v*와 같이 설정할 수 있습니다.
    1. 정책 검토 후 다음 단계로 넘어갑니다.
    2. 이 역할에 필요한 Permissions (권한)을 부여합니다. 예를 들어 S3 버킷에 파일을 업로드해야 한다면 AmazonS3FullAccess 대신 필요한 최소한의 권한(Least Privilege)을 부여하는 것이 좋습니다. 저는 테스트를 위해 AmazonS3ReadOnlyAccess를 잠시 붙여봤습니다.
    3. 역할 이름을 지정하고(예: GitHubActionsOIDC-S3AccessRole) 역할을 생성합니다.

    3단계: GitHub Actions Workflow 설정하기

    마지막으로 GitHub Actions 워크플로우 YAML 파일에 OIDC를 통한 AWS 인증 로직을 추가합니다. configure-aws-credentials 액션을 사용하면 아주 쉽게 설정할 수 있습니다.

    name: Deploy to AWS S3 via OIDC
    
    on:
      push:
        branches:
          - main
    
    permissions:
      id-token: write # OIDC ID Token을 요청하기 위해 필수
      contents: read # 레포지토리 코드 읽기 권한
    
    jobs:
      deploy:
        runs-on: ubuntu-latest
        steps:
          - name: Checkout code
            uses: actions/checkout@v4
    
          - name: Configure AWS Credentials with OIDC
            uses: aws-actions/configure-aws-credentials@v4
            with:
              role-to-assume: arn:aws:iam::YOUR_AWS_ACCOUNT_ID:role/GitHubActionsOIDC-S3AccessRole # 2단계에서 생성한 역할 ARN
              aws-region: ap-northeast-2 # 여러분의 AWS 리전
              # role-session-name: optional-session-name # (선택 사항)
    
          - name: List S3 buckets (for verification)
            run: aws s3 ls

    ⚠️ 여기서 중요한 포인트! permissions: id-token: write 설정은 반드시 필요합니다. 이 권한이 없으면 GitHub Actions가 OIDC ID Token을 생성할 수 없어 AWS 인증이 실패합니다. 처음엔 이걸 몰라서 삽질 좀 했습니다 ㅎㅎ

    삽질 경험: “AccessDenied” 에러의 늪에서 벗어나기 ⚠️

    제가 처음 GitHub Actions OIDC를 설정하면서 가장 많이 겪었던 문제는 다름 아닌 “AccessDenied” 에러였습니다. 워크플로우 로그를 보면 An error occurred (AccessDenied) when calling the AssumeRoleWithWebIdentity operation: Not authorized to perform sts:AssumeRoleWithWebIdentity 이런 메시지가 뜨더라고요. 진짜 답답했죠.

    주요 원인은 대부분 IAM Role의 Trust Policy (신뢰 정책) 조건 문제였습니다. 제가 겪었던 실수와 해결 방법은 다음과 같습니다.

    • Audience 불일치: IAM Identity Provider를 생성할 때 Audience를 sts.amazonaws.com으로 설정했는데, IAM Role Trust Policy의 "token.actions.githubusercontent.com:aud" 조건에도 정확히 "sts.amazonaws.com"이 들어가야 합니다. 한 글자라도 다르면 인증이 실패합니다.
    • sub 조건 오류: 가장 흔한 실수입니다. "token.actions.githubusercontent.com:sub" 조건에 지정된 GitHub 레포지토리, 브랜치 정보가 정확해야 합니다. 예를 들어, repo:YOUR_GITHUB_ORG/YOUR_GITHUB_REPO:ref:refs/heads/main 에서 오타가 있거나, 워크플로우가 실행되는 브랜치와 다르면 에러가 발생합니다. 저는 처음에 main 브랜치로 설정해놓고 develop 브랜치에서 테스트하다가 한참을 헤맸습니다.
    • permissions: id-token: write 누락: GitHub Actions 워크플로우 자체에 OIDC 토큰을 생성할 권한이 없어서 생기는 문제입니다. 이 한 줄 때문에 몇 시간을 날린 적도 있어요. 꼭 확인하세요!
    • AWS 계정 ID 또는 역할 ARN 오타: 기본적인 실수지만, 의외로 자주 발생합니다. ARN을 복사 붙여넣기 할 때 다시 한번 확인하는 습관을 들이세요.

    문제가 발생하면 AWS CloudTrail 로그를 확인하는 것이 가장 좋습니다. AssumeRoleWithWebIdentity 호출이 실패한 이유가 자세히 기록되어 있으니 꼭 살펴보세요. 삽질 끝에 드디어 성공했을 때의 그 쾌감이란! 진짜 이 맛에 엔지니어 하는 것 같아요.

    드디어 성공! OIDC로 안전하게 AWS 리소스 접근하기 🎉

    위에 설명드린 대로 모든 설정을 마치고 GitHub Actions 워크플로우를 실행하면, 아래와 같이 성공적으로 AWS 리소스에 접근하는 모습을 볼 수 있습니다.

    GitHub Actions 워크플로우가 성공적으로 AWS CLI 명령어를 실행하여 S3 버킷 목록을 가져오는 로그입니다. OIDC 기반 인증이 정상적으로 작동했음을 보여줍니다.

    워크플로우 로그를 보면 aws s3 ls 명령어가 문제없이 실행되고, S3 버킷 목록이 출력되는 것을 확인할 수 있습니다. 이제 더 이상 GitHub Secrets에 Access Key를 저장할 필요가 없어졌습니다! 🎉

    이 방식의 가장 큰 장점은 바로 단기 자격 증명(Short-lived Credentials)이라는 점입니다. GitHub Actions 워크플로우가 실행될 때만 임시 자격 증명을 발급받아 사용하고, 워크플로우가 끝나면 만료되죠. 덕분에 키 탈취로 인한 보안 위험이 현저히 줄어들고, 키 교체 주기를 관리할 필요도 없어졌습니다. 관리적인 측면에서도 엄청난 이득이라고 생각해요.

    OIDC, CI/CD 보안의 새로운 표준이 되다

    오늘은 GitHub Actions OIDC를 이용해 AWS에 안전하게 인증하는 방법을 자세히 알아봤습니다. 제가 직접 경험하며 얻은 삽질 경험과 해결 과정도 솔직하게 공유해 드렸는데요. 이 방식은 단순히 편리함을 넘어 CI/CD 파이프라인의 보안을 근본적으로 강화하는 핵심 기술이라고 생각합니다.

    기존의 Access Key 방식과 OIDC를 활용한 AWS 인증 방식의 주요 특징을 비교한 표입니다. 보안성, 관리 용이성 등 여러 측면에서 OIDC의 장점을 한눈에 보여줍니다.

    핵심 장점을 다시 정리해볼까요?

    • 보안 강화: 영구적인 Access Key 없이 임시 자격 증명 사용으로 탈취 위험 최소화.
    • 관리 용이성: Access Key 생성, 배포, 교체 등의 관리 부담 해소.
    • 정교한 권한 제어: 특정 레포지토리, 특정 브랜치, 특정 태그에서만 AWS 리소스 접근 허용 가능.
    • 감사 추적 용이: CloudTrail을 통해 어떤 GitHub Actions 워크플로우가 어떤 역할로 AWS에 접근했는지 명확하게 추적 가능.

    저도 홈랩에서 다양한 CI/CD 환경을 구성하면서 OIDC의 강력함을 몸소 체험하고 있습니다. 앞으로는 OIDC 기반의 워크로드 아이덴티티가 CI/CD 보안의 표준으로 자리매김할 것이라고 확신합니다. 혹시 아직 Access Key를 사용하고 계시다면, 이번 기회에 OIDC로 전환해 보시는 것을 강력히 추천합니다! 처음엔 조금 낯설 수 있지만, 한번 설정해두면 정말 든든하거든요.

    다음 글에서는 AWS S3에 정적 웹사이트를 배포하는 과정을 GitHub Actions OIDC와 함께 자동화하는 방법에 대해 다뤄볼까 합니다. 기대해주세요! 그럼 다음 글에서 만나요! 👋