13년차의 서버실

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

[태그:] 데브옵스

  • [Proxmox] Ansible로 Proxmox 자동화, 실패 없는 10가지 베스트 프랙티스

    [Proxmox] Ansible로 Proxmox 자동화, 실패 없는 10가지 베스트 프랙티스

    안녕하세요! 13년차 인프라 엔지니어, ’13년차의 서버실’ 운영자입니다. 오늘은 제가 홈랩과 실무 환경에서 늘 고민하고 적용해왔던 주제, 바로 Ansible로 Proxmox 자동화에 대해 이야기해보려고 합니다. 많은 분들이 Proxmox VE(Virtual Environment)를 사용하시면서 가상 머신(VM)이나 컨테이너(LXC)를 수동으로 생성하고 설정하는 데 시간을 많이 쏟으셨을 거예요. 저도 처음엔 그랬습니다. 매번 클릭하고 타이핑하고… 그러다 어느 순간 ‘이거 자동화할 수 없을까?’ 하는 생각이 머리를 스치더군요. 그래서 Ansible을 들고 Proxmox에 뛰어들었고, 수많은 삽질 끝에 얻은 귀한 경험들을 여러분과 나누고자 합니다.

    오늘 제가 준비한 내용은 ‘실패 없는 Proxmox 자동화를 위한 10가지 베스트 프랙티스’입니다. 단순한 기능 소개를 넘어, 제가 직접 부딪히고 해결했던 문제들과 그 과정에서 배운 노하우를 멘토처럼 알려드릴게요. 이 글을 읽고 나면 여러분의 홈랩 자동화는 물론, 실제 인프라 관리 효율도 한층 더 높아질 거라고 확신합니다. 자, 그럼 시작해볼까요? 🎉

    Ansible과 Proxmox 연동 아키텍처 다이어그램

    Ansible 컨트롤러가 Proxmox 호스트들을 관리하며 VM, LXC, 스토리지, 네트워크 등을 자동화하는 아키텍처 다이어그램입니다.

    Proxmox와 Ansible, 왜 함께해야 할까요? (개념 설명)

    Proxmox VE(프로모스 VE)는 강력한 오픈소스 가상화 플랫폼입니다. KVM(Kernel-based Virtual Machine) 기반의 가상 머신과 LXC(Linux Container) 컨테이너를 지원하며, 웹 기반 GUI로 손쉽게 관리할 수 있죠. 하지만 아무리 GUI가 편해도 수십, 수백 대의 VM을 일일이 설정하는 건 비효율적이거든요. 여기서 Ansible(앤서블)이 등장합니다. Ansible은 IT 자동화를 위한 오픈소스 도구로, SSH 기반의 Agentless(에이전트리스) 방식이라 별도의 에이전트 설치 없이도 원격 서버를 제어할 수 있더라고요. YAML(야믈) 기반의 플레이북(Playbook)으로 인프라의 ‘상태’를 정의하면, Ansible이 그 상태를 맞춰주죠.

    쉽게 말해, Proxmox가 튼튼한 서버실이라면 Ansible은 이 서버실을 알아서 척척 관리해주는 유능한 비서인 셈입니다. VM 생성, 네트워크 설정, 스토리지 연결 등 반복적이고 오류 발생 가능성이 높은 작업들을 Ansible로 자동화하면, 시간을 절약하고 휴먼 에러(human error)를 크게 줄일 수 있거든요. 특히 저처럼 홈랩 자동화에 관심이 많은 분들에게는 정말 최고의 조합이라고 할 수 있습니다.

    실패 없는 Proxmox 자동화를 위한 10가지 베스트 프랙티스 (실전 구현)

    제가 13년 동안 쌓아온 경험과 숱한 삽질 끝에 얻은 Proxmox 자동화 팁들을 공유합니다. 이 원칙들을 지키면 여러분의 Ansible Proxmox 자동화 여정이 훨씬 수월할 거예요.

    1. 동적 인벤토리(Dynamic Inventory) 활용하기

    가장 먼저 강조하고 싶은 건 바로 동적 인벤토리입니다. VM이 수시로 생성되고 삭제되는 환경에서는 정적 인벤토리(Static Inventory) 파일을 매번 수정하기가 어렵거든요. Proxmox의 API를 활용해서 현재 떠 있는 VM 목록을 동적으로 가져오는 스크립트를 사용하면 정말 편합니다. community.general 컬렉션에 Proxmox 동적 인벤토리 플러그인이 있으니 활용해보세요. 저는 주로 Python 스크립트를 직접 짜서 사용했는데, 이게 훨씬 유연하더라고요.

    # ansible.cfg 예시
    [inventory]
    enable_plugins = proxmox
    
    # inventory/proxmox.yml
    plugin: community.proxmox.proxmox
    base_url: https://your-proxmox-host:8006/api2/json
    api_user: root@pam
    api_password: your_password_or_token
    validate_certs: False # 실제 환경에서는 True 권장
    

    이렇게 설정하면 ansible-inventory -i inventory/proxmox.yml --list 명령으로 현재 Proxmox에 있는 VM들을 Ansible 인벤토리로 불러올 수 있습니다. 💡 팁: 보안을 위해 API 토큰을 사용하는 것을 강력히 추천합니다.

    2. 항상 멱등성(Idempotency)을 지키세요

    Ansible의 핵심 철학은 멱등성입니다. 즉, 플레이북을 여러 번 실행해도 항상 동일한 결과를 내고, 이미 목표 상태에 도달했다면 아무 작업도 하지 않아야 한다는 뜻이거든요. Proxmox VM 생성/수정 시 이 멱등성을 지키는 것이 매우 중요합니다. 예를 들어, VM이 이미 존재하면 생성하지 않거나, 설정이 동일하면 변경하지 않도록 플레이북을 작성해야 하더라고요. Proxmox 모듈들은 대부분 멱등성을 지원하지만, 스크립트나 커맨드를 직접 사용할 때는 주의가 필요합니다. 저도 초기에 이 부분을 간과해서 이미 생성된 VM에 또 생성 명령이 들어가 오류가 나던 경험이 많았거든요.

    3. 변수 관리, 이렇게 해야 깔끔합니다

    플레이북 내에 하드코딩된 값은 피하고, 변수(Variables)를 적극적으로 활용하세요. 특히 group_vars와 host_vars 디렉토리를 사용하면 VM의 종류나 호스트별로 유연하게 설정을 관리할 수 있더라고요. 민감한 정보(API 키, 비밀번호 등)는 Ansible Vault(앤서블 볼트)를 사용해서 암호화하세요. 보안은 아무리 강조해도 지나치지 않습니다. 저도 한때 vars 파일에 비밀번호를 그냥 넣었다가 식겁한 적이 있습니다. 😅

    # group_vars/all.yml
    pve_api_user: 'root@pam'
    pve_api_token_id: 'ansible-token'
    pve_api_token_secret: '{{ vault_pve_api_token_secret }}' # vault로 관리
    
    # host_vars/my-vm.yml
    vm_id: 101
    vm_name: 'web-server-01'
    vm_memory: 2048
    vm_cores: 2
    vm_net_bridge: 'vmbr0'
    vm_ip: '192.168.1.101'
    

    4. Proxmox 전용 모듈, 똑똑하게 쓰기

    Ansible은 community.proxmox 컬렉션을 통해 Proxmox를 위한 다양한 모듈을 제공합니다. proxmox_kvm, proxmox_lxc, proxmox_storage 등 목적에 맞는 모듈을 사용하세요. 이 모듈들은 Proxmox API를 활용하여 안정적이고 멱등성 있는 작업을 수행할 수 있도록 설계되었더라고요. 셸 스크립트(shell script)로 API를 직접 호출하는 것보다 훨씬 안전하고 관리하기 편합니다. 예전에는 uri 모듈로 직접 HTTP 요청을 날리기도 했는데, 전용 모듈이 훨씬 편리하더라고요.

    - name: Create a new KVM virtual machine
      community.proxmox.proxmox_kvm:
        api_host: "{{ pve_api_host }}"
        api_user: "{{ pve_api_user }}"
        api_token_id: "{{ pve_api_token_id }}"
        api_token_secret: "{{ pve_api_token_secret }}"
        vmid: "{{ vm_id }}"
        name: "{{ vm_name }}"
        memory: "{{ vm_memory }}"
        cores: "{{ vm_cores }}"
        net:
          net0: 'model=virtio,bridge={{ vm_net_bridge }}'
        state: present
      delegate_to: localhost
    

    5. 오류 핸들링(Error Handling), 미리미리 대비하기

    자동화 작업은 예상치 못한 문제로 실패할 수 있습니다. 네트워크 문제, Proxmox API 응답 지연, 잘못된 변수 값 등 다양한 원인이 있죠. failed_when, ignore_errors, block/rescue를 활용하여 견고한 플레이북을 만드세요. 특히 중요한 작업은 block으로 묶고, 실패 시 rescue 블록에서 정리 작업을 하도록 구성하면 정말 안전하더라고요. 저도 처음에 에러가 나면 그냥 멈추게 만들었는데, 나중에는 중단된 상태를 수동으로 정리하는 게 더 힘들더라고요. ⚠️

    - name: Critical VM creation block
      block:
        - name: Create VM
          community.proxmox.proxmox_kvm:
            # ... VM 생성 설정 ...
            state: present
          delegate_to: localhost
        - name: Configure VM network
          ansible.builtin.shell: 'some_network_config_command {{ vm_ip }}'
      rescue:
        - name: Clean up VM if creation failed
          community.proxmox.proxmox_kvm:
            api_host: "{{ pve_api_host }}"
            api_user: "{{ pve_api_user }}"
            api_token_id: "{{ pve_api_token_id }}"
            api_token_secret: "{{ pve_api_token_secret }}"
            vmid: "{{ vm_id }}"
            state: absent
          delegate_to: localhost
    

    6. 태그(Tags)로 원하는 작업만 골라 실행하기

    플레이북이 점점 커지면 특정 작업만 실행하거나 건너뛰고 싶을 때가 많습니다. 이때 태그(Tags)를 사용하면 정말 유용하더라고요. 각 태스크(task)에 의미 있는 태그를 부여하고, --tags 또는 --skip-tags 옵션으로 플레이북 실행을 제어할 수 있습니다. 예를 들어, ‘network’ 태그를 부여한 태스크만 실행하거나, ‘cleanup’ 태그가 붙은 태스크는 건너뛰는 식이죠. 저도 처음엔 플레이북 전체를 매번 돌렸는데, 태그를 사용하니 개발 및 테스트 시간이 엄청 단축되더라고요. ✅

    # 'create_vm' 태그가 붙은 작업만 실행
    ansible-playbook -i inventory/proxmox.yml playbook.yml --tags create_vm
    
    # 'cleanup' 태그가 붙은 작업만 건너뛰고 실행
    ansible-playbook -i inventory/proxmox.yml playbook.yml --skip-tags cleanup
    

    7. 역할(Roles) 기반으로 플레이북 구조화하기

    복잡한 Proxmox 스크립트와 플레이북은 역할(Roles) 기반으로 구조화하는 것이 좋습니다. 역할은 변수, 태스크, 핸들러(handler), 파일, 템플릿(template) 등을 논리적으로 묶어 재사용성을 높여주거든요. 예를 들어, ‘proxmox-vm-create’ 역할, ‘proxmox-net-config’ 역할 등으로 나누어 관리하면 가독성이 높아지고 유지보수가 훨씬 쉬워집니다. 저의 홈랩에서도 VM 생성, 네트워크 설정, 특정 애플리케이션 설치 등을 각각의 역할로 만들어서 사용하고 있습니다.

    # roles/proxmox-vm-create/tasks/main.yml
    - name: Create VM
      community.proxmox.proxmox_kvm:
        # ... VM 생성 설정 ...
    
    # playbook.yml
    - name: Deploy web server VM on Proxmox
      hosts: localhost
      connection: local
      roles:
        - role: proxmox-vm-create
          vars:
            vm_name: 'web-server-02'
            vm_id: 102
            # ... 기타 변수 ...
        - role: proxmox-net-config
          vars:
            vm_ip: '192.168.1.102'
    

    8. 플레이북 실행 전 반드시 테스트하기 (–check –diff)

    실제 변경 사항을 적용하기 전에 --check (dry run, 드라이 런) 모드로 플레이북을 실행하여 어떤 변경이 일어날지 미리 확인하세요. --diff 옵션을 함께 사용하면 변경될 내용을 자세히 볼 수 있더라고요. 이 두 옵션은 실제 환경에 적용하기 전에 발생할 수 있는 잠재적인 문제를 미리 발견하는 데 정말 중요한 역할을 합니다. 저의 수많은 ‘대형 사고’를 막아준 고마운 기능입니다. 😅

    # 실제로 변경하지 않고, 어떤 변경이 일어날지 미리 확인
    ansible-playbook -i inventory/proxmox.yml playbook.yml --check --diff
    

    9. 버전 관리 시스템(Git)은 필수입니다

    모든 플레이북, 인벤토리, 역할 파일은 Git과 같은 버전 관리 시스템에 저장해야 합니다. 변경 이력을 추적하고, 여러 사람이 협업하며, 필요할 경우 이전 버전으로 되돌릴 수 있게 해주거든요. Ansible Proxmox 자동화 스크립트는 인프라의 ‘코드’와 같으므로, 코드 관리의 모범 사례를 따르는 것이 중요합니다. 저도 처음엔 그냥 로컬 폴더에 저장해두고 관리했는데, 실수로 파일을 날리거나 변경 이력을 잃어버려서 다시 처음부터 작성했던 뼈아픈 경험이 있습니다.

    10. AWX/Tower(자동화 컨트롤러)로 더 강력하게

    Ansible 자동화가 복잡해지고 규모가 커지면, AWX(앤서블 워크스)나 Ansible Tower(앤서블 타워)와 같은 자동화 컨트롤러의 도입을 고려해보세요. 웹 기반 UI를 통해 플레이북 실행, 인벤토리 관리, 사용자 권한 제어, 스케줄링, 로깅 등 모든 자동화 작업을 중앙에서 관리할 수 있거든요. AWX Proxmox 연동은 특히 강력합니다. 저는 홈랩에서 AWX를 구축해서 복잡한 VM 배포 파이프라인을 만들었는데, 이젠 버튼 하나로 모든 게 되니 정말 편하더라고요. 이젠 홈랩 자동화의 끝판왕이라고 부르고 싶을 정도입니다.

    AWX 대시보드에서 성공적으로 실행된 Proxmox 자동화 작업 목록

    AWX 대시보드에서 Proxmox 관련 자동화 작업이 성공적으로 실행된 모습을 보여주는 스크린샷입니다.

    ⚠️ 삽질 경험담: 제가 겪었던 문제들 (주의사항/트러블슈팅)

    제가 Ansible Proxmox 자동화를 하면서 가장 많이 겪었던 문제들을 몇 가지 공유해 드릴게요. 여러분은 저처럼 삽질하지 마시라고요! ㅎㅎ

    권한 문제 (Permission Denied)

    Proxmox API를 사용할 때 가장 많이 만나는 에러 중 하나가 바로 권한 문제입니다. root@pam 계정을 직접 사용하는 것은 보안상 좋지 않고, 특정 권한만 가진 API 토큰을 생성해서 사용하는 것이 모범 사례거든요. Proxmox의 ‘Datacenter -> Permissions -> API Tokens’에서 필요한 권한(예: VM.Audit, VM.PowerMgmt, VM.Allocate)만 부여된 토큰을 만들고, 이 토큰 ID와 시크릿(secret)을 Ansible Vault로 안전하게 관리해야 합니다. 처음엔 root 권한으로 대충 했는데, 나중에 보안 감사 때 혼쭐이 날 뻔했습니다. 😱

    네트워크 설정 복잡성

    Proxmox의 네트워크 설정은 처음엔 좀 복잡하게 느껴질 수 있습니다. 특히 브리지(bridge) 설정이나 VLAN(Virtual Local Area Network) 태깅 같은 부분이요. Ansible로 VM을 생성하고 네트워크를 붙일 때, Proxmox 호스트의 네트워크 설정(/etc/network/interfaces)과 정확히 일치하는지 확인해야 합니다. 잘못된 브리지 이름을 사용하거나, VLAN ID를 놓치면 VM이 네트워크에 연결되지 않아 한참을 헤맸던 적이 많습니다. 네트워크 자동화는 항상 신중하게 테스트하는 게 정말 중요해요.

    이 외에도 Proxmox API 응답 지연, 모듈 인자(argument) 오류, 템플릿(Template) 이미지 경로 문제 등 다양한 변수들이 있었습니다. 중요한 건 에러 메시지를 꼼꼼히 읽고, Proxmox 공식 문서와 Ansible 모듈 문서를 참고하며 차분하게 해결해나가는 인내심입니다. 저도 처음엔 ‘이게 왜 안 돼!’ 하면서 키보드를 던질 뻔한 적이 한두 번이 아니었거든요. 하지만 포기하지 않고 해결했을 때의 쾌감은 정말 짜릿합니다! ✨

    Ansible로 자동화된 Proxmox 가상 머신 및 컨테이너 목록

    Ansible로 자동 생성 및 설정된 Proxmox 가상 머신(VM) 및 컨테이너(LXC) 목록을 보여주는 Proxmox 웹 인터페이스 화면입니다.

    마치며: 자동화, 결국 시간을 벌어주는 일 (마무리)

    오늘은 13년차 인프라 엔지니어의 시선으로 Ansible Proxmox 자동화의 베스트 프랙티스 10가지를 자세히 소개해 드렸습니다. 동적 인벤토리, 멱등성, 변수 관리, Proxmox 모듈 활용, 오류 핸들링, 태그, 역할, 테스트, 버전 관리, 그리고 AWX/Tower까지, 이 모든 것이 여러분의 홈랩 자동화와 실무 인프라 관리에 큰 도움이 될 거라고 생각합니다.

    처음에는 복잡해 보일 수 있지만, 한 번 구축해두면 반복적인 작업에서 정말 완전히 해방될 수 있습니다. Proxmox 스크립트를 일일이 작성하고 실행하는 시간 대신, 더 중요하고 창의적인 일에 집중할 수 있게 되는 거죠. 자동화는 결국 우리에게 ‘시간’이라는 가장 소중한 자원을 벌어다 줍니다. 여러분도 오늘 배운 내용을 바탕으로 자신만의 Ansible Proxmox 자동화 시스템을 구축해보시고, 그 편리함을 직접 경험해보시길 바랍니다. 다음 글에서는 AWX를 활용한 Proxmox VM 배포 파이프라인 구축에 대해 더 자세히 다뤄볼 예정이니 기대해주세요! 😉

    Ansible과 Proxmox 연동의 이점을 요약한 인포그래픽

    Ansible과 Proxmox 로고를 중심으로 자동화, 효율성, 확장성, 홈랩, 인프라 관리 등 주요 이점을 시각적으로 요약한 인포그래픽입니다.

  • [k8s] K3s/MicroK8s로 구축하는 경량 엣지 쿠버네티스: 실제 운영 사례 분석

    [k8s] K3s/MicroK8s로 구축하는 경량 엣지 쿠버네티스: 실제 운영 사례 분석

    안녕하세요, 13년차 서버실 지킴이입니다. 오늘도 제 홈랩 서버에서 굴러가는 작은 친구들 이야기를 좀 해볼까 합니다. 요즘 엣지 컴퓨팅(Edge Computing)에 대한 관심이 정말 뜨거우니까요? 클라우드의 강점을 작은 엣지 디바이스까지 끌어내려는 움직임이 활발합니다. 그런데 막상 작은 디바이스에 쿠버네티스(Kubernetes)를 올리려고 하면, 덩치 큰 녀석이라 부담스러울 때가 많았거든요.

    저도 처음엔 라즈베리 파이(Raspberry Pi) 같은 작은 보드 컴퓨터에 풀 스택 쿠버네티스를 올려보려다가 리소스 부족으로 꽤나 삽질을 했어요. 😅 그러다 발견한 보석 같은 친구들이 바로 K3s와 MicroK8s거든요. 이 경량 쿠버네티스 배포판들이 엣지 환경에서 얼마나 쓸모 있게 쓰일 수 있는지, 그리고 제가 직접 운영하면서 겪었던 경험담들을 공유해볼까 합니다. 저처럼 작은 디바이스에서 쿠버네티스를 돌려보고 싶으셨던 분들이라면 오늘 글이 좋은 가이드가 될 거예요!

    엣지 컴퓨팅 환경에서 경량 쿠버네티스 클러스터가 어떻게 구성되는지 보여주는 아키텍처 다이어그램

    이미지: 엣지 환경에서 경량 쿠버네티스 클러스터가 어떻게 구성되는지 보여주는 아키텍처 다이어그램입니다.

    K3s와 MicroK8s, 뭐가 다를까? (개념 설명 및 비교)

    경량 쿠버네티스라는 카테고리 안에 K3s와 MicroK8s가 대표주자인데, 이 둘은 각각 다른 접근 방식으로 ‘가벼움’을 추구해요. 핵심부터 쉽게 설명해 드릴게요.

    K3s: Rancher Labs의 선택

    K3s는 Rancher Labs에서 만든 경량 쿠버네티스 배포판이에요. ‘K8s’에서 숫자 8을 3으로 바꾼 건, 일반적인 쿠버네티스보다 절반도 안 되는 리소스로 동작한다는 의미를 담고 있더라고요. 💡 K3s의 가장 큰 특징은 단일 바이너리(Single Binary) 형태로 배포된다는 점이에요. 모든 핵심 컴포넌트(kube-apiserver, kube-controller-manager 등)가 하나의 파일에 쏙 들어있어서 설치가 정말 간편합니다. 게다가 SQLite를 기본 데이터베이스로 써서 외부 DB(데이터베이스) 없이도 잘 동작하거든요. 엣지 환경처럼 리소스가 제한적이거나 네트워크 연결이 불안정한 곳에 정말 안성맞춤이죠.

    MicroK8s: Canonical의 유연성

    MicroK8s는 Canonical(우분투를 만드는 회사)에서 만든 경량 쿠버네티스예요. K3s만큼 설치가 간단한 건 맞는데, 애드온(Add-ons) 시스템이 정말 강점이거든요. Ingress(인그레스, 외부 트래픽 진입점), DNS, Storage(스토리지) 같은 필수 기능들을 필요할 때마다 쉽게 켜고 끄는 식으로 활성화할 수 있어요. 스냅(Snap) 패키지로 배포되기 때문에 우분투(Ubuntu) 환경에서 특히 설치와 관리가 편리합니다. 저는 홈랩에서 우분투를 주로 쓰는데, MicroK8s는 정말 친숙하게 느껴지더라고요.

    간단한 비교 표

    두 솔루션의 특징을 한눈에 보기 위해 표로 정리해봤어요.

    특징 K3s MicroK8s
    개발사 Rancher Labs Canonical
    설치 방식 단일 바이너리, 스크립트 Snap 패키지
    데이터베이스 SQLite (기본), PostgreSQL, MySQL 등 etcd
    장점 극강의 경량성, 빠른 설치, 단일 파일 모듈식 애드온, 스냅 자동 업데이트, 고가용성 용이
    적합 환경 초소형 엣지, IoT, CI/CD 개발/테스트, 데스크톱, 엣지

    경량 쿠버네티스 설치, 어디서부터 시작할까? (실전 구현)

    이제 직접 설치해보는 과정을 보여드릴게요. 저는 주로 라즈베리 파이나 집에 굴러다니는 미니 PC에 설치해서 쓰고 있거든요. 여기서는 범용적으로 사용할 수 있는 K3s 설치 예시를 들어볼 텐데, MicroK8s도 과정이 비슷하게 간단합니다.

    K3s 설치 예시

    제가 실제로 경험해본 K3s는 정말 😎 쉽습니다. 단 한 줄의 명령어로 서버(Master)와 에이전트(Agent)를 세팅할 수 있거든요.

    1. 서버 노드(Master) 설치

    먼저 클러스터의 컨트롤 플레인(Control Plane) 역할을 할 서버 노드를 깔아봅시다. 보통 가장 먼저 설치하고, 이 노드가 나머지 에이전트 노드들을 관리하게 되거든요. 다음 명령어를 실행하면 K3s가 알아서 설치되고 시작돼요.

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

    설치가 끝나면 kubectl 명령어를 바로 사용할 수 있게 돼요. 잘 설치됐는지 확인해볼까요?

    sudo k3s kubectl get nodes
    

    자신의 서버 노드가 Ready 상태로 보일 텐데, 이때 느끼는 쾌감은 정말 🎉 입니다.

    2. 에이전트 노드(Agent) 추가

    이제 클러스터에 워커 노드를 추가할 차례예요. 다른 라즈베리 파이나 미니 PC에 접속해서 다음 명령어를 실행하면 되는데, 여기서 중요한 게 K3S_URL과 K3S_TOKEN입니다.

    • K3S_URL: 서버 노드의 IP 주소를 지정합니다.
    • K3S_TOKEN: 서버 노드에서 생성된 토큰이에요. 이 토큰은 /var/lib/rancher/k3s/server/node-token 파일에서 확인할 수 있습니다.
    # 서버 노드에서 토큰 확인
    sudo cat /var/lib/rancher/k3s/server/node-token
    
    # 에이전트 노드에서 설치 (TOKEN과 SERVER_IP는 자신의 환경에 맞게 변경)
    curl -sfL https://get.k3s.io | K3S_URL="https://YOUR_SERVER_IP:6443" K3S_TOKEN="K10xxxxxxxxxxxxxxxxxxxx" sh -
    

    모든 에이전트 노드에 이 과정을 반복하면 되는데, 몇 분이 지난 후 서버 노드에서 다시 sudo k3s kubectl get nodes를 실행해보면 새로 추가된 노드들이 보일 거예요. 정말 신기하지 않나요? 😁

    K3s 클러스터에 연결된 노드들의 상태를 보여주는 터미널 화면

    이미지: K3s 클러스터에 연결된 노드들의 상태를 보여주는 터미널 화면입니다.

    홈랩에서 겪은 삽질 경험과 해결책 (주의사항/트러블슈팅)

    13년차 엔지니어라고 해서 삽질을 안 하는 건 아닙니다. 오히려 더 많이 해요. 😅 특히 엣지 환경에서는 예상치 못한 변수들이 많아서 몇 번은 머리를 쥐어뜯었거든요. 제가 직접 겪었던 몇 가지 문제와 그 해결책을 공유해볼게요.

    ⚠️ 1. 네트워크 문제: 노드 간 통신 장애

    정말 흔한 문제예요. 처음엔 분명 잘 연결된 것 같았는데, 어느 순간 노드(Node)들이 NotReady 상태로 바뀌는 경우가 있더라고요. 대부분 방화벽(Firewall)이나 네트워크 설정 때문이었습니다. 특히 라즈베리 파이 같은 작은 디바이스들은 무선 LAN(랜)을 많이 쓰는데, 공유기 설정 때문에 서로 통신이 안 되는 경우도 있었어요.

    • 해결책:
    • 방화벽 확인: ufw나 firewalld 같은 방화벽이 6443 포트(Port)나 다른 쿠버네티스 관련 포트를 막고 있지 않은지 확인하고 열어줍니다.
    • IP 주소 고정: DHCP(동적 호스트 구성 프로토콜)로 IP가 자꾸 바뀌면 노드 통신이 끊기기도 해요. 각 노드의 IP 주소를 고정(Static IP)으로 설정하는 게 좋습니다.
    • Cilium/Flannel 로그 확인: CNI(Container Network Interface) 플러그인(Plug-in)인 Cilium이나 Flannel 로그를 확인하면 네트워크 문제를 진단하는 데 정말 큰 도움이 돼요.

    ⚠️ 2. 리소스 제한: 메모리/CPU 부족

    라즈베리 파이 3B+나 4B 모델에 1GB, 2GB 램(RAM)을 가진 노드들을 운영하다 보면 파드(Pod) 몇 개만 돌려도 메모리가 부족하다고 아우성칠 때가 많아요. 특히 메트릭 서버(Metrics Server)나 모니터링 툴(Monitoring Tool)까지 올리면 순식간에 리소스가 바닥나더라고요.

    • 해결책:
    • 오버프로비저닝 금지: 😭 욕심부리지 마세요! 필요한 파드만 최소한으로 배포합니다.
    • 리소스 요청/제한 설정: 디플로이먼트(Deployment) YAML 파일에 resources.requests와 resources.limits를 명확하게 설정해서 파드가 필요한 만큼만 쓰도록 제한하는 거예요.
    • 스왑(Swap) 공간 활용: 성능 저하를 감수하고라도 메모리가 정말 부족하다면 스왑 공간을 활성화하는 것도 한 방법입니다. 하지만 이건 최후의 수단으로 생각하세요.

    ⚠️ 3. SD 카드 수명 문제

    라즈베리 파이에서 SD 카드를 부팅 디스크로 쓸 때, 쿠버네티스는 로그(Log)나 데이터베이스 쓰기 작업이 정말 많아서 SD 카드 수명이 급격히 줄어들 수 있어요. 실제로 저도 몇 번 SD 카드가 사망하는 경험을 했습니다. 💀

    • 해결책:
    • USB SSD 사용: 가능하면 USB 외장 SSD(솔리드 스테이트 드라이브)를 부팅 디스크로 쓰는 게 정말 좋아요. 훨씬 빠르고 안정적이며 수명도 길거든요.
    • 로그 회전(Log Rotation) 설정: 로그 파일이 너무 커지지 않도록 logrotate를 설정해서 관리하는 게 중요합니다.

    성능 모니터링 및 리소스 관리 팁 (추가적인 팁)

    경량 쿠버네티스라고 해서 모니터링을 소홀히 하면 안 돼요. 오히려 작은 시스템일수록 더욱 꼼꼼하게 지켜봐야 합니다. 제가 실제로 쓰는 몇 가지 팁을 알려드릴게요.

    1. Prometheus/Grafana로 시각화

    작은 규모의 클러스터라도 프로메테우스(Prometheus)와 그라파나(Grafana)는 거의 필수라고 생각해요. 노드의 CPU(중앙 처리 장치), 메모리(Memory) 사용량, 네트워크 트래픽(Traffic) 등을 실시간으로 시각화해서 볼 수 있거든요. K3s나 MicroK8s 모두 Prometheus를 쉽게 설치할 수 있는 방법을 제공합니다.

    # MicroK8s의 경우
    microk8s enable prometheus
    
    # K3s의 경우, Helm 차트(Chart)로 설치
    helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
    helm repo update
    helm install prometheus prometheus-community/kube-prometheus-stack
    

    저는 Grafana 대시보드를 띄워놓고 커피 마시면서 클러스터 상태를 확인하는 게 소소한 낙이에요. 😄

    2. 리소스 쿼터(Resource Quotas) 설정

    여러 애플리케이션(Application)을 배포할 때, 특정 네임스페이스(Namespace)나 파드가 너무 많은 리소스를 독점하지 않도록 리소스 쿼터를 설정하는 게 중요해요. 특히 홈랩처럼 리소스가 제한적인 환경에서는 더욱 그렇거든요.

    # resource-quota.yaml
    apiVersion: v1
    kind: ResourceQuota
    metadata:
      name: pod-quota
    spec:
      hard:
        pods: "10"
        requests.cpu: "1"
        requests.memory: "1Gi"
        limits.cpu: "2"
        limits.memory: "2Gi"
    
    kubectl apply -f resource-quota.yaml -n my-namespace
    

    이렇게 하면 my-namespace 안에서는 최대 10개의 파드만 생성할 수 있고, CPU와 메모리도 지정된 범위 내에서만 쓸 수 있게 된답니다. 💸

    Grafana 대시보드에서 경량 쿠버네티스 클러스터의 실시간 리소스 사용량을 모니터링하는 모습

    이미지: Grafana 대시보드에서 경량 쿠버네티스 클러스터의 실시간 리소스 사용량을 모니터링하는 모습입니다.

    실제 운영 사례와 얻은 교훈 (검증/결과)

    저는 홈랩에서 K3s 클러스터를 여러 대 운영하고 있어요. 주로 하는 일들을 정리해보면 다음과 같습니다.

    • 개인 웹사이트/블로그 호스팅: Nginx Ingress Controller(엔진엑스 인그레스 컨트롤러)와 Cert-Manager(서트 매니저)를 활용해서 Let’s Encrypt(렛츠 인크립트) SSL(보안 소켓 계층) 인증서를 자동으로 발급받아 제 블로그를 안전하게 운영하고 있어요.
    • IoT 데이터 수집 및 처리: 여러 센서(Sensor)에서 들어오는 데이터를 Kafka(카프카)로 수집하고, Go(고) 언어로 작성된 경량 워커(Worker) 파드에서 데이터를 처리한 후 InfluxDB(인플럭스디비)에 저장하는 파이프라인(Pipeline)을 구축했어요.
    • CI/CD 테스트 환경: 작은 규모의 개발 프로젝트를 할 때, 젠킨스(Jenkins)나 아르고 CD(Argo CD)를 K3s 클러스터에 배포해서 CI/CD(지속적 통합/지속적 배포) 파이프라인 테스트 환경으로 활용하고 있습니다.

    이런 실제 운영 사례들을 통해 얻은 가장 큰 교훈은, ‘작은 것이 아름답다’는 거예요. 😊 복잡하고 무거운 쿠버네티스 기능을 모두 필요로 하지 않는 엣지 환경에서는 K3s나 MicroK8s처럼 핵심 기능에만 집중하고 가볍게 동작하는 솔루션이 훨씬 더 효율적이더라고요.

    물론, 완전한 프로덕션(Production) 환경에서 엔터프라이즈(Enterprise)급 요구사항을 만족시키기에는 제약이 있을 수 있어요. 하지만 소규모 팀, 홈랩, IoT 디바이스, 임베디드(Embedded) 시스템 같은 곳에서는 정말 ‘게임 체인저(Game Changer)’라고 할 수 있죠. 경량 쿠버네티스 덕분에 엣지 컴퓨팅의 문턱이 훨씬 낮아졌다고 저는 생각합니다.

    경량 쿠버네티스의 주요 장점과 고려해야 할 한계점을 시각적으로 요약한 인포그래픽

    이미지: 경량 쿠버네티스의 주요 장점과 고려해야 할 한계점을 시각적으로 요약한 인포그래픽입니다.

    마무리: 엣지 쿠버네티스의 미래와 다음 단계

    오늘은 K3s와 MicroK8s를 중심으로 경량 엣지 쿠버네티스를 구축하고 운영했던 제 경험을 공유해드렸어요. 처음엔 단순히 ‘작은 쿠버네티스’ 정도로 생각했는데, 직접 써보니까 엣지 환경에 특화된 기능과 철학이 담겨 있더라고요. 설치는 쉽고 리소스 소모는 적어서, 저처럼 홈랩을 꾸리거나 소규모 엣지 프로젝트를 진행하는 분들께는 정말 강력 추천하고 싶은 솔루션입니다. 💪

    물론, 모든 기술이 그렇듯 장점만 있는 건 아니에요. 풀 스택 쿠버네티스가 제공하는 모든 기능을 기대하기보다는, 자신의 환경과 필요한 기능에 맞춰 현명하게 선택하고 활용하는 지혜가 필요합니다. 하지만 ‘경량’이라는 이름표를 달고 엣지 컴퓨팅의 중요한 축으로 자리 잡은 K3s와 MicroK8s의 미래는 앞으로도 계속 밝을 거라고 확신해요.

    다음번에는 이 경량 쿠버네티스 클러스터 위에 GitOps(깃옵스) 환경을 구축해서 애플리케이션 배포를 자동화하는 방법에 대해 다뤄볼까 합니다. 기대해주세요! 혹시 K3s나 MicroK8s를 사용하면서 겪었던 재미있는 경험이나 삽질 스토리가 있다면 댓글로 공유해주세요. 저도 배우는 게 많을 것 같습니다. 😊

  • [Cloud] Docker Compose 실전 가이드: 로컬 개발 환경 구축 및 관리

    “팀원 컴퓨터에서는 되는데 제 컴퓨터에서는 왜 안 되죠?”

    인프라 일을 13년 하면서 개발팀에서 가장 많이 들은 말 중 하나입니다. 그리고 솔직히 말씀드리면, 저도 초창기엔 이 문제로 꽤 많이 고생했거든요. 환경변수 하나 차이로 밤새 디버깅하고, OS 버전 달라서 라이브러리 충돌 나고… 생각만 해도 아찔하네요.

    그래서 오늘은 Docker Compose를 활용해서 로컬 개발 환경을 제대로 구축하는 방법을 공유하려고 합니다. “내 컴퓨터에서는 되는데”라는 말을 팀에서 영원히 추방할 수 있는 방법이에요. 실제로 제가 홈랩이랑 팀 프로젝트에서 직접 써보면서 정리한 내용이라 꽤 실용적일 거라 생각합니다.

    ▲ Docker Compose로 구성한 로컬 개발 환경의 전체 구조 — 웹 서버, 앱 서버, 데이터베이스가 하나의 네트워크로 묶이는 모습

    Docker Compose가 뭔지 먼저 짚고 넘어가요

    Docker 자체는 이미 알고 계신 분들이 많을 텐데, Docker Compose(도커 컴포즈)는 조금 다른 개념이에요. 쉽게 말해서, 여러 개의 컨테이너(Container)를 하나의 파일로 정의하고 한 번에 관리하는 도구거든요.

    예를 들어 웹 애플리케이션 하나를 띄우려면 보통 이런 것들이 필요하잖아요:

    • 프론트엔드 서버 (React, Vue 등)
    • 백엔드 API 서버 (Node.js, FastAPI 등)
    • 데이터베이스 (PostgreSQL, MySQL 등)
    • 캐시 서버 (Redis 등)

    이걸 Docker 명령어로 하나하나 실행하면… 솔직히 너무 번거롭더라고요. 컨테이너마다 네트워크 연결하고, 볼륨 설정하고, 환경변수 넣고… 저도 처음엔 그렇게 했었는데 한 달도 안 돼서 포기했어요 ㅎㅎ.

    그런데 Docker Compose를 쓰면 docker-compose.yml이라는 파일 하나에 이 모든 걸 정의하고, 명령어 한 줄로 전체를 올리고 내릴 수 있어요. 개발 생산성 측면에서 정말 게임 체인저였습니다.

    Docker Compose vs. 일반 Docker 명령어 비교

    항목 Docker 단독 사용 Docker Compose 사용
    서비스 시작 컨테이너마다 개별 명령어 실행 docker compose up 하나로 끝
    네트워크 설정 수동으로 네트워크 생성 및 연결 자동으로 네트워크 생성 및 연결
    환경 관리 각 명령어에 -e 옵션 반복 입력 yml 파일에 한 번에 정의
    팀 공유 실행 명령어 문서화 필요 yml 파일을 Git에 올리면 끝
    재현성 낮음 (사람마다 다르게 실행 가능) 높음 (동일한 환경 보장)

    시작 전 준비사항

    본격적으로 들어가기 전에 환경 세팅부터 확인해 봅시다. 요즘은 Docker Desktop을 설치하면 Docker Compose도 함께 딸려오거든요. 예전엔 따로 설치해야 했는데, 이 부분은 많이 편해졌어요.

    1. Docker Desktop 공식 사이트에서 OS에 맞는 버전 다운로드 및 설치
    2. 설치 완료 후 터미널에서 버전 확인
    # Docker 버전 확인
    docker --version
    
    # Docker Compose 버전 확인 (v2 기준)
    docker compose version

    💡 팁: 예전에는 docker-compose(하이픈 포함)로 실행했는데, 요즘 Compose V2부터는 docker compose(스페이스)로 바뀌었어요. 둘 다 동작하긴 하는데, 새 프로젝트라면 V2 방식으로 쓰는 게 좋습니다.

    실전: docker-compose.yml 파일 작성하기

    자, 이제 진짜 시작입니다. 실제 웹 애플리케이션 개발 환경을 예시로 만들어볼게요. Node.js 백엔드 + PostgreSQL 데이터베이스 + Redis 캐시 조합으로 가겠습니다. 제가 홈랩에서 사이드 프로젝트 할 때 자주 쓰는 스택이거든요.

    프로젝트 디렉토리 구조

    my-project/
    ├── docker-compose.yml       # 핵심 설정 파일
    ├── docker-compose.override.yml  # 로컬 개발용 오버라이드
    ├── .env                     # 환경변수 파일
    ├── backend/
    │   ├── Dockerfile
    │   └── src/
    └── frontend/
        ├── Dockerfile
        └── src/

    기본 docker-compose.yml 작성

    version: '3.8'
    
    services:
      # 백엔드 API 서버
      backend:
        build:
          context: ./backend
          dockerfile: Dockerfile
        container_name: myapp-backend
        ports:
          - "3000:3000"
        environment:
          - NODE_ENV=development
          - DATABASE_URL=postgresql://myuser:mypassword@db:5432/mydb
          - REDIS_URL=redis://cache:6379
        volumes:
          - ./backend:/app          # 소스 코드 마운트 (핫 리로드 가능)
          - /app/node_modules       # node_modules는 컨테이너 것 사용
        depends_on:
          db:
            condition: service_healthy  # DB 헬스체크 통과 후 시작
          cache:
            condition: service_started
        networks:
          - app-network
        restart: unless-stopped
    
      # PostgreSQL 데이터베이스
      db:
        image: postgres:15-alpine
        container_name: myapp-db
        environment:
          - POSTGRES_USER=myuser
          - POSTGRES_PASSWORD=mypassword
          - POSTGRES_DB=mydb
        volumes:
          - postgres-data:/var/lib/postgresql/data  # 데이터 영속성 보장
          - ./db/init.sql:/docker-entrypoint-initdb.d/init.sql  # 초기화 스크립트
        ports:
          - "5432:5432"   # 로컬에서 DB 클라이언트로 직접 접속 가능
        healthcheck:
          test: ["CMD-SHELL", "pg_isready -U myuser -d mydb"]
          interval: 10s
          timeout: 5s
          retries: 5
        networks:
          - app-network
    
      # Redis 캐시 서버
      cache:
        image: redis:7-alpine
        container_name: myapp-redis
        ports:
          - "6379:6379"
        volumes:
          - redis-data:/data
        networks:
          - app-network
    
    # 볼륨(Volume) 정의: 컨테이너가 삭제돼도 데이터 유지
    volumes:
      postgres-data:
      redis-data:
    
    # 네트워크 정의: 서비스 간 내부 통신
    networks:
      app-network:
        driver: bridge

    여기서 중요한 포인트! depends_on을 쓸 때 단순히 서비스 이름만 쓰면 컨테이너가 시작된 것만 확인하고 실제로 준비가 됐는지는 모릅니다. condition: service_healthy와 healthcheck를 함께 써야 PostgreSQL이 실제로 쿼리를 받을 준비가 됐을 때 백엔드가 시작돼요. 이거 몰라서 처음에 백엔드가 DB 연결 못 한다고 에러 뜨는 삽질을 꽤 했었더라고요 ㅎㅎ.

    환경변수 파일(.env) 관리

    민감한 정보는 절대 docker-compose.yml에 직접 넣으면 안 됩니다. .env 파일을 따로 만들고 Git에는 올리지 마세요.

    # .env 파일
    POSTGRES_USER=myuser
    POSTGRES_PASSWORD=super_secret_password
    POSTGRES_DB=mydb
    NODE_ENV=development
    APP_PORT=3000
    # docker-compose.yml에서 .env 변수 참조
    services:
      db:
        environment:
          - POSTGRES_USER=${POSTGRES_USER}
          - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
          - POSTGRES_DB=${POSTGRES_DB}
    # .gitignore에 반드시 추가
    .env
    .env.local
    .env.production

    ▲ docker-compose.yml로 정의된 서비스들이 내부 네트워크(app-network)로 연결되고, 필요한 포트만 호스트에 노출되는 구조

    개발 환경 전용 오버라이드 파일

    이건 좀 꿀팁인데요. docker-compose.override.yml을 만들면 docker compose up할 때 자동으로 합쳐져서 적용됩니다. 로컬 개발에서만 필요한 설정을 분리할 수 있어서 정말 편하더라고요.

    # docker-compose.override.yml (로컬 개발 전용)
    version: '3.8'
    
    services:
      backend:
        # 개발 중엔 빌드 없이 소스 코드 직접 마운트
        command: npm run dev   # nodemon 등 핫 리로드 명령어
        environment:
          - DEBUG=true
          - LOG_LEVEL=debug
    
      # 개발 환경에서만 DB 관리 UI 추가
      adminer:
        image: adminer
        container_name: myapp-adminer
        ports:
          - "8080:8080"
        networks:
          - app-network

    자주 쓰는 Docker Compose 명령어 모음

    이제 실제로 실행해 봅시다. 제가 매일 쓰는 명령어들 위주로 정리했어요.

    # 전체 서비스 시작 (백그라운드 실행)
    docker compose up -d
    
    # 빌드 포함해서 시작 (코드 변경 후)
    docker compose up -d --build
    
    # 특정 서비스만 시작
    docker compose up -d backend
    
    # 전체 서비스 중지
    docker compose down
    
    # 중지 + 볼륨까지 삭제 (DB 초기화할 때)
    docker compose down -v
    
    # 실행 중인 서비스 상태 확인
    docker compose ps
    
    # 특정 서비스 로그 보기 (실시간)
    docker compose logs -f backend
    
    # 실행 중인 컨테이너에 접속
    docker compose exec backend sh
    
    # 데이터베이스 컨테이너에서 psql 실행
    docker compose exec db psql -U myuser -d mydb
    
    # 서비스 재시작
    docker compose restart backend

    ⚠️ 실제로 겪은 트러블슈팅 사례들

    이론은 깔끔한데, 실제로 쓰다 보면 별별 문제가 다 생기더라고요. 제가 겪었던 것들 공유합니다.

    문제 1: 포트가 이미 사용 중이라는 에러

    # 에러 메시지
    Error response from daemon: Ports are not available: 
    exposing port TCP 0.0.0.0:5432 -> 0.0.0.0:0: listen tcp 0.0.0.0:5432: 
    bind: address already in use

    로컬에 PostgreSQL이 이미 설치돼서 실행 중인 경우 자주 발생해요. 해결법은 두 가지예요:

    # 방법 1: 로컬 PostgreSQL 서비스 중지
    sudo systemctl stop postgresql   # Linux
    brew services stop postgresql    # macOS
    
    # 방법 2: docker-compose.yml에서 포트 변경
    ports:
      - "5433:5432"   # 호스트 포트를 5433으로 변경

    문제 2: 볼륨 마운트 후 node_modules 사라짐

    이거 처음 겪으면 진짜 당황스러워요. 소스 코드 볼륨 마운트하면 로컬의 node_modules(없는 경우)가 컨테이너 안의 것을 덮어씌워버리는 현상이거든요.

    # 해결법: node_modules를 별도 볼륨으로 분리
    services:
      backend:
        volumes:
          - ./backend:/app
          - /app/node_modules   # 이 한 줄이 핵심!
                                # 익명 볼륨으로 컨테이너 내부 것을 보호

    문제 3: 컨테이너 간 통신이 안 될 때

    백엔드에서 DB 접속할 때 localhost로 접속하려다가 실패하는 경우가 많아요. 컨테이너 안에서 localhost는 그 컨테이너 자신을 가리키거든요.

    # ❌ 잘못된 방법 (백엔드 컨테이너 안에서)
    DATABASE_URL=postgresql://myuser:mypassword@localhost:5432/mydb
    
    # ✅ 올바른 방법 (서비스 이름을 호스트로 사용)
    DATABASE_URL=postgresql://myuser:mypassword@db:5432/mydb
    # 'db'는 docker-compose.yml에서 정의한 서비스 이름

    같은 네트워크에 묶인 컨테이너끼리는 서비스 이름이 곧 호스트명이 됩니다. Docker의 내장 DNS가 자동으로 처리해줘요. 이거 알고 나면 진짜 편해집니다.

    문제 4: M1/M2 Mac에서 이미지 아키텍처 오류

    # 에러 메시지
    WARNING: The requested image's platform (linux/amd64) does not match 
    the detected host platform (linux/arm64/v8)
    
    # 해결법: platform 명시
    services:
      db:
        image: postgres:15-alpine
        platform: linux/amd64   # 또는 linux/arm64/v8

    요즘 Apple Silicon Mac 쓰시는 분들 많은데, arm64를 지원하는 이미지를 쓰거나 platform을 명시해주면 해결돼요. 대부분의 공식 이미지들은 멀티 아키텍처를 지원하니까 크게 걱정 안 하셔도 됩니다.

    ✅ 결과 확인: 제대로 떴는지 검증하기

    드디어 됐다! 이제 제대로 동작하는지 확인해봅시다.

    # 전체 서비스 상태 확인
    docker compose ps
    
    # 예상 출력
    NAME              IMAGE                COMMAND                  SERVICE    CREATED        STATUS                    PORTS
    myapp-backend     myapp-backend        "docker-entrypoint.s…"   backend    2 minutes ago  Up 2 minutes              0.0.0.0:3000->3000/tcp
    myapp-db          postgres:15-alpine   "docker-entrypoint.s…"   db         2 minutes ago  Up 2 minutes (healthy)    0.0.0.0:5432->5432/tcp
    myapp-redis       redis:7-alpine       "docker-entrypoint.s…"   cache      2 minutes ago  Up 2 minutes              0.0.0.0:6379->6379/tcp

    STATUS 컬럼에서 Up이면 실행 중, (healthy)면 헬스체크까지 통과한 것입니다. 🎉

    # 네트워크 확인
    docker network ls
    docker network inspect myproject_app-network
    
    # 컨테이너 간 통신 테스트
    docker compose exec backend ping db
    docker compose exec backend ping cache
    
    # 백엔드 API 응답 확인
    curl http://localhost:3000/health

    ▲ docker compose ps 명령어로 확인한 서비스 상태 — 모든 서비스가 Up 상태이고 DB는 healthy 헬스체크까지 통과한 모습

    🎉 팀 협업에서 빛나는 Docker Compose의 진짜 가치

    제가 Docker Compose를 팀 프로젝트에 도입하고 나서 가장 크게 달라진 점은 온보딩 시간이었어요. 예전엔 새 팀원이 오면 환경 세팅하는 데 하루 이상 걸리는 경우도 있었거든요. 근데 이제는 이렇게 끝납니다:

    # 새 팀원 온보딩 전체 과정
    git clone https://github.com/myteam/myproject.git
    cd myproject
    cp .env.example .env   # 환경변수 파일 복사 후 값 채우기
    docker compose up -d   # 끝!

    이 세 줄이면 끝이에요. 진짜로요. OS가 다르든, 로컬에 뭐가 설치돼 있든 상관없이 동일한 환경이 뜹니다. 개발 생산성 측면에서 이게 얼마나 큰 차이인지는 직접 경험해보시면 바로 느끼실 거예요.

    Docker Compose 활용의 주요 이점

    • ✅ 환경 일관성: “내 컴퓨터에서는 되는데” 문제 완전 해결
    • ✅ 빠른 온보딩: 새 팀원도 몇 분 안에 개발 시작 가능
    • ✅ 격리된 환경: 프로젝트별로 독립된 환경, 충돌 없음
    • ✅ 버전 관리: 인프라 설정도 코드로 관리 (Infrastructure as Code)
    • ✅ 프로덕션 근접: 로컬과 운영 환경의 차이 최소화

    ▲ Docker Compose 도입 전후 로컬 개발 환경 비교 — 온보딩 시간, 환경 일관성, 협업 효율성 측면에서의 개선 효과

    자주 묻는 질문 (FAQ)

    Q. Docker Desktop 없이 Docker Compose만 쓸 수 있나요?

    네, Linux 환경에서는 Docker Engine을 설치하고 Compose 플러그인을 별도로 설치하면 돼요. macOS나 Windows라면 Docker Desktop이 가장 간편한 방법이더라고요.

    Q. docker-compose.yml 파일을 Git에 올려도 되나요?

    네, 올려야 합니다! 이게 바로 팀 환경 공유의 핵심이거든요. 단, .env 파일은 절대 올리면 안 됩니다. .env.example 파일을 만들어서 어떤 변수가 필요한지만 공유하세요.

    Q. 프로덕션 배포에도 Docker Compose를 쓰나요?

    소규모 서비스라면 쓸 수 있어요. 하지만 스케일링이나 고가용성이 필요하다면 Kubernetes(쿠버네티스) 같은 오케스트레이션 도구를 고려해야 합니다. 이 부분은 다음 글에서 다룰 예정이에요.

    Q. 컨테이너를 내렸다 올리면 데이터가 사라지나요?

    docker compose down만 하면 볼륨은 유지돼요. 데이터까지 지우려면 docker compose down -v를 써야 하니까 주의하세요!

    마무리: 이제 로컬 개발 환경 걱정은 끝

    오늘 다룬 내용을 정리해볼게요:

    1. Docker Compose의 기본 개념과 일반 Docker 대비 장점
    2. 실전 docker-compose.yml 작성법 (서비스, 볼륨, 네트워크)
    3. 환경변수 분리와 오버라이드 파일 활용
    4. 자주 쓰는 명령어와 실제 트러블슈팅 사례

    처음 Docker Compose를 접하면 yml 파일 문법이 좀 낯설게 느껴질 수 있어요. 저도 들여쓰기 하나 틀려서 에러 뜨는 걸 수십 번은 겪었으니까요. 근데 한 번 손에 익으면 이것 없이 개발하는 게 상상이 안 될 정도로 편해집니다.

    다음 글에서는 Docker Compose로 구성한 환경을 기반으로 CI/CD 파이프라인과 연동하는 방법을 다뤄볼 예정이에요. 로컬에서 테스트한 환경을 그대로 자동화 배포에 활용하는 내용인데, 꽤 실용적인 내용이 될 것 같습니다.

    궁금한 점이나 삽질 경험 있으시면 댓글로 편하게 남겨주세요. 저도 비슷한 거 겪어봤을 확률이 높거든요 😄

  • [Cloud] Terraform State 관리 완벽 가이드: S3, DynamoDB 활용 모범 사례

    [Cloud] Terraform State 관리 완벽 가이드: S3, DynamoDB 활용 모범 사례

    안녕하세요, 13년차의 서버실 주인장입니다. 오늘은 인프라 엔지니어라면 한 번쯤은 머리를 싸매고 고민했을 주제, 바로 Terraform State 관리에 대해 이야기해보려고 합니다.

    제가 처음 Terraform을 접했을 때, 로컬에 생성되는 <code>terraform.tfstate 파일이 그렇게 귀찮을 수가 없더라고요. 혼자 작업할 때는 괜찮았는데, 팀원들과 함께 프로젝트를 진행하면서부터는 ‘이거 큰일 나겠다!’ 싶었습니다. 서로 다른 사람이 동시에 terraform apply를 실행하면 어떻게 될까요? 네, 상상만 해도 끔찍하죠. State 파일이 꼬여서 인프라가 엉망진창이 되는 대참사를 막기 위해, 오늘은 AWS S3와 DynamoDB를 활용한 Terraform State 관리 모범 사례를 완벽하게 파헤쳐 보려고 합니다. 💡

    Terraform State 관리의 전체 아키텍처 다이어그램입니다. S3와 DynamoDB가 어떻게 상호작용하는지 한눈에 볼 수 있습니다.

    Terraform State, 왜 중요한가요?

    Terraform State(테라폼 상태 파일)는 Terraform이 관리하는 실제 인프라 자원들의 현황을 기록한 파일입니다. 쉽게 말해, “지금 내가 관리하고 있는 인프라가 어떤 모습으로 생겼는지”를 알려주는 지도 같은 거죠. 이 파일이 있어야 Terraform은 다음에 apply를 실행했을 때, 현재 인프라 상태와 새로운 설정 파일(.tf) 간의 차이점을 파악하고, 필요한 변경 사항만 적용할 수 있습니다.

    만약 State 파일이 없거나, 여러 사람이 각자 다른 버전을 가지고 있다면 어떻게 될까요? Terraform은 인프라의 실제 상태를 알 수 없어서 엉뚱한 자원을 생성하거나, 심지어는 멀쩡한 자원을 삭제해버리는 불상사가 발생할 수 있습니다. 그래서 안정적인 IaC(Infrastructure as Code, 코드형 인프라) 운영을 위해서는 Terraform State 관리가 정말 중요합니다. 특히 팀 협업 환경에서는 이 State 파일을 안전하고 일관되게 관리하는 것이 핵심 중의 핵심입니다.

    S3 백엔드로 원격 State 관리하기

    로컬에 State 파일을 두는 것은 개인적인 실험 환경에서는 괜찮지만, 실제 프로덕션 환경이나 팀 프로젝트에서는 절대 금물입니다. 제가 예전에 홈랩에서 이것저것 테스트하다가 USB를 날려먹으면서 State 파일도 같이 날려버린 적이 있었거든요. 그때 복구하느라 며칠 밤낮을 새웠던 걸 생각하면 아직도 아찔합니다. 😨

    그래서 우리는 S3 백엔드(S3 backend)를 사용해서 Terraform State 파일을 원격으로 저장할 겁니다. AWS S3는 객체 스토리지 서비스로, 높은 내구성(durability), 가용성(availability), 그리고 버전 관리(versioning) 기능을 제공해서 Terraform State를 저장하기에 아주 적합합니다. S3에 State 파일을 저장하면 팀원 누구나 안전하게 접근할 수 있거든요.

    1. S3 버킷 생성하기

    먼저 Terraform State 파일을 저장할 S3 버킷을 생성해야 합니다. 버킷 이름은 전역적으로 고유해야 하며, 버전 관리(Versioning)를 활성화하는 것을 강력히 권장합니다. 혹시 모를 실수로 State 파일이 변경되거나 삭제되더라도 이전 버전으로 복구할 수 있거든요. 저도 예전에 실수로 terraform destroy를 잘못 날렸다가 S3 버전 관리 덕분에 한숨 돌린 적이 있습니다.

    aws s3api create-bucket \
      --bucket my-terraform-state-bucket-13years-infra \
      --region ap-northeast-2 \
      --create-bucket-configuration LocationConstraint=ap-northeast-2
    
    aws s3api put-bucket-versioning \
      --bucket my-terraform-state-bucket-13years-infra \
      --versioning-configuration Status=Enabled
    

    콘솔에서 직접 생성하셔도 됩니다. 이때 중요한 건, “버전 관리”를 꼭 켜주세요! 그리고 필요하다면 서버 측 암호화(Server-side encryption)도 적용하는 게 좋습니다. 💡

    Terraform State 저장을 위한 AWS S3 백엔드 버킷 설정 화면입니다. 버전 관리와 암호화 설정이 중요합니다.

    2. Terraform 구성 파일에 S3 백엔드 설정 추가하기

    이제 Terraform 프로젝트의 main.tf (또는 별도의 backend.tf) 파일에 S3 백엔드 설정을 추가합니다.

    terraform {
      backend "s3" {
        bucket         = "my-terraform-state-bucket-13years-infra" # 위에서 생성한 S3 버킷 이름
        key            = "global/terraform.tfstate"                 # S3 버킷 내 State 파일 경로
        region         = "ap-northeast-2"                           # S3 버킷 리전
        encrypt        = true                                       # State 파일 암호화 활성화
        # dynamodb_table = "my-terraform-locks"                     # State Locking을 위해 나중에 추가 (지금은 주석 처리)
      }
    }
    
    # 예시: VPC를 생성하는 리소스
    resource "aws_vpc" "main" {
      cidr_block = "10.0.0.0/16"
      tags = {
        Name = "MyVPC-ManagedByTerraform"
      }
    }
    

    여기서 key는 S3 버킷 내에서 State 파일이 저장될 경로와 파일명을 지정합니다. 저는 보통 프로젝트별로 또는 환경별로 구분해서 project-name/env/terraform.tfstate 이런 식으로 관리하곤 합니다.

    3. `terraform init` 실행하기

    설정을 추가했다면, 이제 terraform init 명령어를 실행해야 합니다. 이 명령어가 백엔드 설정을 초기화하고, S3 버킷에 State 파일을 생성하거나 기존 파일을 연결합니다.

    terraform init
    

    성공적으로 실행되면 다음과 같은 메시지를 볼 수 있습니다.

    Initializing the backend...
    Successfully configured the backend "s3"! Terraform will now
    persist and retrieve its state from this configuration.
    
    ... (다른 초기화 메시지) ...
    
    Terraform has been successfully initialized!
    

    이제 여러분의 State 파일은 안전하게 S3에 저장됩니다. ✅

    DynamoDB로 State Locking 구현하기

    S3 백엔드만으로는 협업 시 발생할 수 있는 문제를 완전히 해결할 수 없습니다. 바로 동시성 문제(concurrency issue)죠. 여러 사람이 동시에 terraform apply를 실행하면 어떻게 될까요? State 파일이 꼬여버리거나, 한 사람이 작업하는 동안 다른 사람이 같은 자원을 변경해서 예상치 못한 결과가 나올 수 있습니다. 제가 처음 이 문제에 부딪혔을 때, “이거 진짜 난감하네…” 싶었습니다. 😅

    그래서 필요한 게 바로 State Locking(상태 잠금)입니다. Terraform은 AWS DynamoDB를 활용하여 이 State Locking을 구현할 수 있습니다. DynamoDB는 NoSQL 데이터베이스 서비스로, 높은 성능과 가용성을 제공하며, 특히 잠금 메커니즘을 구현하기에 아주 적합합니다. 한 번에 한 사람만 State 파일을 변경할 수 있도록 잠금을 걸어주는 거죠.

    1. DynamoDB 테이블 생성하기

    DynamoDB 테이블을 생성합니다. 이때 테이블 이름은 자유롭게 지정할 수 있지만, 파티션 키(Partition Key)는 반드시 LockID로 설정해야 합니다. 이 LockID를 기준으로 잠금 레코드가 저장되기 때문입니다.

    AWS CLI를 사용한 생성 예시입니다.

    aws dynamodb create-table \
      --table-name my-terraform-locks \
      --attribute-definitions AttributeName=LockID,AttributeType=S \
      --key-schema AttributeName=LockID,KeyType=HASH \
      --billing-mode PAY_PER_REQUEST
    

    PAY_PER_REQUEST 모드를 사용하면 사용한 만큼만 비용을 지불하므로, 트래픽이 많지 않은 State Locking 용도로는 비용 효율적입니다.

    2. Terraform 구성 파일에 DynamoDB 설정 추가하기

    이제 이전에 설정했던 S3 백엔드 블록에 dynamodb_table 인자를 추가합니다.

    terraform {
      backend "s3" {
        bucket         = "my-terraform-state-bucket-13years-infra"
        key            = "global/terraform.tfstate"
        region         = "ap-northeast-2"
        encrypt        = true
        dynamodb_table = "my-terraform-locks" # 새로 생성한 DynamoDB 테이블 이름
      }
    }
    
    # ... (나머지 리소스 정의) ...
    

    3. `terraform init` 다시 실행하기

    백엔드 설정을 변경했으니, terraform init 명령어를 다시 실행해야 합니다. Terraform이 변경된 백엔드 설정을 인식하고 DynamoDB 테이블을 활용하기 시작합니다.

    terraform init
    

    이제부터 terraform apply나 terraform destroy 같은 State를 변경하는 명령어를 실행할 때, Terraform은 DynamoDB 테이블에 잠금 레코드를 생성하여 다른 사용자가 동시에 작업을 하지 못하도록 막아줍니다. 협업 환경에서 안정성을 크게 높여주는 핵심 기능이죠! 🎉

    ⚠️ 삽질 경험: 흔한 실수와 해결책

    제가 이 설정을 하면서 겪었던 몇 가지 삽질 경험과 그 해결책을 공유해 드릴게요. 여러분은 저처럼 고생하지 마시라고요! 😅

    • S3 버킷 권한 문제: terraform init이나 apply 시 “Access Denied” 오류가 발생하면, Terraform을 실행하는 IAM 사용자 또는 역할에 S3 버킷에 대한 s3:GetObject, s3:PutObject, s3:ListBucket 등의 권한이 제대로 부여되었는지 확인해야 합니다. 특히 S3 버킷 정책(Bucket Policy)이 설정되어 있다면 더욱 꼼꼼히 봐야 합니다.
    • DynamoDB 테이블 이름 오타 또는 권한 부족: DynamoDB 테이블 이름을 backend "s3" 블록에 잘못 적거나, 테이블에 대한 dynamodb:GetItem, dynamodb:PutItem, dynamodb:DeleteItem 권한이 없으면 잠금 오류가 발생합니다. 반드시 LockID 파티션 키로 테이블이 생성되었는지, 그리고 IAM 권한이 충분한지 확인하세요.
    • `terraform init` 재실행 누락: 백엔드 설정을 변경하고 terraform init을 다시 실행하지 않아서 변경사항이 적용되지 않는 경우가 많습니다. 저도 자주 까먹어서 “분명히 설정했는데 왜 안 되지?” 하고 한참 헤맸던 적이 있습니다. 🤦‍♂️ 백엔드 설정이 바뀌면 무조건 terraform init! 기억하세요.
    • S3 버전 관리 미활성화: 실수로 terraform state rm 같은 명령어를 잘못 실행해서 State 파일이 삭제되었을 때, S3 버전 관리가 활성화되어 있다면 이전 버전으로 복구할 수 있습니다. 이거 정말 생명줄 같은 기능이니 꼭 활성화하세요!

    이런 작은 실수가 큰 문제로 이어질 수 있으니, 항상 주의 깊게 확인하는 습관을 들이는 것이 중요합니다.

    Terraform State 관리 모범 사례를 요약한 인포그래픽입니다. 핵심 원칙들을 한눈에 파악할 수 있습니다.

    검증 및 결과: 이제 안심하고 협업하세요!

    모든 설정이 완료되었다면, 이제 팀원들과 함께 걱정 없이 Terraform을 사용할 수 있습니다. State Locking이 제대로 작동하는지 확인하는 가장 좋은 방법은, 두 명의 사용자가 동시에 terraform apply를 실행해보는 것입니다. 한 명의 작업이 진행되는 동안 다른 한 명은 잠금 오류 메시지를 받게 될 거예요. 이렇게 되면 성공입니다!

    또한, terraform state list 명령어를 통해 S3에 저장된 State 파일의 내용을 확인하거나, AWS 콘솔에서 S3 버킷과 DynamoDB 테이블의 항목들을 직접 확인해볼 수도 있습니다. DynamoDB 테이블에는 LockID를 가진 항목이 잠금 상태를 나타냅니다.

    terraform state list
    

    이제 여러분의 IaC 상태 관리(IaC state management)는 훨씬 더 안정적이고 견고해졌을 겁니다. 팀원들과의 Terraform 협업(Terraform collaboration)도 한층 부드러워질 거고요. 드디어 됐다! 이 안정감, 진짜 편하더라고요. 👍

    Terraform 명령어를 실행하여 State가 성공적으로 관리되고 있음을 보여주는 스크린샷입니다. DynamoDB 락도 확인해볼 수 있습니다.

    마무리하며: 더 나은 IaC 여정을 위해

    오늘은 Terraform State를 S3와 DynamoDB를 활용하여 안전하게 관리하는 방법에 대해 자세히 알아봤습니다. 13년차 엔지니어로서 직접 겪었던 경험과 삽질까지 솔직하게 공유해 드렸는데, 도움이 되셨으면 좋겠습니다.

    Terraform State 관리는 단순한 파일 저장을 넘어, 안정적인 인프라 운영과 효율적인 팀 협업을 위한 필수적인 요소입니다. S3의 내구성과 버전 관리, 그리고 DynamoDB의 강력한 잠금 기능을 활용하면, 여러분의 IaC 여정은 훨씬 더 순탄해질 거예요.

    다음 글에서는 Terraform State 파일을 더욱 안전하게 보호하기 위한 추가적인 보안 설정(예: IAM 정책 강화, S3 버킷 정책, VPC Endpoint 활용 등)에 대해 더 깊게 다뤄볼 예정입니다. 기대해주세요! 😊

  • [k8s] k3s로 경량 쿠버네티스 클러스터 구축: 홈랩과 엣지 완벽 가이드

    [k8s] k3s로 경량 쿠버네티스 클러스터 구축: 홈랩과 엣지 완벽 가이드

    k3s로 경량 쿠버네티스 클러스터 구축하기: 13년차 엔지니어의 삽질 완전 정복

    안녕하세요, 13년차의 서버실 주인장입니다. 오늘은 제가 최근 홈랩에서 직접 구축해보고, 엣지 컴퓨팅 환경에서도 정말 유용하겠다 싶었던 기술, k3s(케이쓰리 에스)에 대해 이야기해보려고 해요. 기존 쿠버네티스(Kubernetes)는 아무래도 무겁고 복잡해서 홈랩이나 리소스가 제한적인 엣지 환경에 도입하기가 쉽지 않았거든요. 근데 k3s를 만나고 나서 생각이 확 바뀌었습니다. 가볍고 빠르면서도 쿠버네티스의 강력한 기능을 고스란히 쓸 수 있다는 게 정말 매력적이더라고요!

    저도 처음엔 이게 뭔가 싶었는데, 실제로 써보니까 ‘아, 이거 진짜 물건이다!’ 싶었습니다. 제 경험을 바탕으로 k3s 경량 쿠버네티스를 활용해서 클러스터를 어떻게 구축하는지, 그리고 어떤 삽질을 겪었고 어떻게 해결했는지 솔직하게 공유해볼게요. 특히 엣지 컴퓨팅(Edge Computing)이나 저처럼 홈랩(Home Lab)을 운영하는 분들께 큰 도움이 될 거라 생각합니다.

    💡 k3s, 왜 선택했을까? 경량 쿠버네티스의 진짜 매력

    혹시 이런 경험 있으신가요? 작은 라즈베리 파이(Raspberry Pi)나 오래된 미니 PC에 k3s를 포함한 쿠버네티스를 올려보고 싶었는데, 리소스 문제나 복잡한 설치 과정 때문에 포기했던 경험 말이죠. 저도 그랬거든요. 일반적인 쿠버네티스 배포판은 마스터 노드(Master Node) 하나만 해도 꽤 많은 메모리와 CPU를 요구합니다. 그런데 k3s는 이런 제약을 확 낮춰줍니다. 말 그대로 ‘경량(Lightweight)’ 쿠버네티스죠.

    k3s는 Rancher Labs에서 개발한 CNCF(Cloud Native Computing Foundation) 샌드박스 프로젝트로, 리소스가 제한적인 환경, 예를 들면 IoT 디바이스, 엣지 서버, 그리고 저의 사랑스러운 홈랩 같은 곳에 최적화되어 있습니다. 바이너리 파일 하나로 설치가 끝나고, 필요 없는 기능들을 쳐내서 메모리 사용량도 훨씬 적어요. 제가 직접 해보니, 512MB RAM 이상이면 마스터 노드를 구동할 수 있더라고요! 정말 놀랍지 않습니까? 🎉

    k3s 경량 쿠버네티스 클러스터의 간소화된 아키텍처 다이어그램: 마스터 노드와 여러 워커 노드가 연결되어 엣지 및 홈랩 환경에서 효율적으로 동작하는 모습을 보여줍니다.

    k3s 기본 이해: 경량 쿠버네티스의 핵심 개념

    쉽게 말해 k3s는 쿠버네티스(Kubernetes)의 모든 핵심 기능을 제공하면서도, 배포판을 경량화한 버전입니다. k3s는 기존 쿠버네티스가 제공하는 API Server, Controller Manager, Scheduler, etcd 같은 핵심 컴포넌트들을 모두 포함하고 있어요. 하지만 몇 가지 차이점이 있습니다.

    • 단일 바이너리(Single Binary): 설치 파일이 하나로 되어 있어 배포가 정말 간편합니다.
    • SQLite3 내장(Embedded SQLite3): 기본적으로 외부 etcd 대신 SQLite3를 데이터 저장소로 사용합니다. 물론 PostgreSQL, MySQL, etcd 등 외부 DB도 연결할 수 있어요.
    • 불필요한 기능 제거: 레거시(Legacy) 코드나 클라우드 제공업체(Cloud Provider) 관련 기능을 제거하여 경량화했습니다.
    • 컨테이너 런타임(Container Runtime) 변경: 도커(Docker) 대신 containerd(컨테이너디)를 기본 컨테이너 런타임으로 사용합니다.

    이런 특징들 덕분에 k3s는 일반적인 쿠버네티스에 비해 설치도 빠르고 리소스 소모도 적어서, 소규모 프로젝트나 학습용, 그리고 저처럼 홈랩에서 다양한 실험을 해보고 싶을 때 최적의 선택이 됩니다. 컨테이너 오케스트레이션(Container Orchestration)이라는 쿠버네티스의 핵심 개념은 그대로 가져가면서도, 훨씬 쉽게 접근할 수 있게 해주는 거죠.

    실전! k3s 경량 쿠버네티스 클러스터 구축 스텝별 가이드

    자, 이제 직접 k3s 클러스터를 구축해볼 시간입니다. 저는 라즈베리 파이 4B 두 대와 오래된 미니 PC 한 대를 이용해서 클러스터를 만들어봤어요. OS는 모두 Ubuntu Server 22.04 LTS를 사용했습니다. 여러분도 리눅스(Linux) 기반의 어떤 장비든 준비하시면 됩니다.

    1. k3s 마스터 노드(Server) 설치

    가장 먼저 클러스터의 ‘뇌’ 역할을 할 마스터 노드를 설치합니다. k3s는 정말 설치가 간단해서 명령어 한 줄이면 끝나버려요. 💡 팁: 실제 운영 환경에서는 스크립트 내용을 확인하고 사용하는 것이 좋습니다.

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

    이 명령어를 실행하면 k3s가 자동으로 설치되고 서비스로 등록되어 실행됩니다. 설치가 끝나면 제대로 작동하는지 확인해볼까요?

    
    sudo k3s kubectl get nodes
    

    기본적으로 k3s는 자체적으로 제공하는 k3s kubectl 명령어를 사용하도록 설정되어 있어요. 처음엔 조금 어색하지만 익숙해지니 괜찮더라고요. k3s가 정상적으로 설치되었다면 마스터 노드가 Ready 상태로 보일 겁니다. 예를 들면 이런 식이죠:

    
    NAME         STATUS   ROLES                  AGE   VERSION
    master-node   Ready    control-plane,master   2m    v1.28.3+k3s1
    

    2. k3s 워커 노드(Agent) 추가

    이제 마스터 노드에 연결할 워커 노드(Agent Node)를 추가해봅시다. 워커 노드 역시 명령어 한 줄로 설치되는데, 다만 마스터 노드와 연결하기 위한 토큰(Token)이 필요해요. 이 토큰은 마스터 노드에서 확인할 수 있습니다.

    
    sudo cat /var/lib/rancher/k3s/server/node-token
    

    이 명령어를 실행하면 긴 문자열이 나올 텐데, 이게 바로 워커 노드를 연결할 때 필요한 토큰입니다. 이 토큰과 마스터 노드의 IP 주소를 알고 있다면, 워커 노드에서 다음 명령어를 실행해주세요. <master-ip> 부분은 마스터 노드의 실제 IP 주소로, <token> 부분은 위에서 확인한 토큰으로 바꿔주셔야 합니다.

    
    curl -sfL https://get.k3s.io | K3S_URL=https://<master-ip>:6443 K3S_TOKEN=<token> sh -
    

    워커 노드 설치 후, 다시 마스터 노드에서 sudo k3s kubectl get nodes 명령어를 실행해보세요. 몇 분 뒤에 워커 노드가 Ready 상태로 보인다면 성공입니다! 🎉

    k3s 마스터 및 워커 노드 구성과 kubectl 환경 설정이 완료된 명령줄 인터페이스 스크린샷입니다.

    3. kubectl 환경 설정으로 편하게 사용하기

    매번 sudo k3s kubectl이라고 치는 게 귀찮으시죠? 저도 그랬거든요. 일반적인 kubectl 명령어를 사용할 수 있도록 환경 설정을 해봅시다. 마스터 노드에서 다음 명령어를 실행하여 kubeconfig 파일을 홈 디렉토리로 복사하고 환경 변수를 설정합니다.

    
    mkdir -p ~/.kube
    sudo cp /etc/rancher/k3s/k3s.yaml ~/.kube/config
    sudo chown $(id -u):$(id -g) ~/.kube/config
    chmod 600 ~/.kube/config
    export KUBECONFIG=~/.kube/config
    

    마지막 export 명령어는 현재 세션에만 적용되므로, 로그인할 때마다 자동으로 적용되게 하려면 .bashrc나 .zshrc 파일에 추가하는 것이 좋습니다. 이제 kubectl get nodes 명령어를 실행해보세요. 잘 동작할 겁니다!

    ⚠️ k3s 설치 중 만난 난관들 & 해결법

    제가 13년차 엔지니어라고 해도 삽질은 피할 수 없죠! k3s 설치 중 저를 괴롭혔던 몇 가지 문제와 해결법을 공유합니다. 혹시 여러분도 비슷한 문제를 겪는다면 참고가 되셨으면 좋겠어요.

    1. 워커 노드가 연결되지 않는 문제 (방화벽!)

      처음엔 마스터 노드와 워커 노드 간 통신이 안 되는 거예요. kubectl get nodes를 하면 워커 노드가 NotReady 상태로 계속 머물러 있었죠. 알고 보니 방화벽(Firewall) 문제였습니다. Ubuntu Server는 기본적으로 ufw(Uncomplicated Firewall)가 활성화되어 있는 경우가 많거든요. k3s는 기본적으로 6443/tcp 포트를 사용하는데, 이 포트가 막혀있었던 겁니다. 🤯

      해결법: 마스터 노드와 워커 노드 모두에서 필요한 포트를 열어줬습니다.

      
      sudo ufw allow 6443/tcp  # k3s API Server
      sudo ufw allow 10250/tcp # Kubelet (필수)
      sudo ufw reload
      

      특히 마스터 노드는 워커 노드의 Kubelet과 통신해야 하므로 10250 포트도 열어주는 것이 좋습니다. 이 문제를 해결하고 나니 워커 노드가 바로 Ready 상태로 바뀌더라고요. 역시 네트워크 문제는 언제나 기본부터 체크해야 합니다.

    2. 토큰(Token) 불일치로 인한 연결 실패

      간혹 워커 노드를 추가할 때 K3S_TOKEN 값을 잘못 입력하는 경우가 있어요. 아니면 복사 붙여넣기 할 때 공백이 들어간다거나요. 그럼 당연히 워커 노드가 마스터에 연결되지 않습니다.

      해결법: 워커 노드에서 k3s 서비스를 중지하고 삭제한 후, 정확한 토큰 값으로 다시 설치했습니다.

      
      sudo systemctl stop k3s-agent
      sudo systemctl disable k3s-agent
      sudo k3s-uninstall.sh # 워커 노드에 설치된 k3s 삭제 스크립트
      # 정확한 토큰으로 다시 설치
      curl -sfL https://get.k3s.io | K3S_URL=https://<master-ip>:6443 K3S_TOKEN=<correct-token> sh -
      
    3. 로그 확인의 중요성

      어떤 문제가 발생하든, 가장 먼저 해야 할 일은 로그를 확인하는 것입니다. k3s 서비스의 로그는 journalctl 명령어를 통해 볼 수 있어요.

      
      sudo journalctl -u k3s --since "10 minutes ago" -f
      # 워커 노드는
      sudo journalctl -u k3s-agent --since "10 minutes ago" -f
      

      로그를 보면 에러 메시지나 경고 메시지를 통해 문제의 원인을 파악할 수 있는 경우가 많습니다. 저는 방화벽 문제도 로그에서 connection refused 같은 메시지를 보고 눈치챘거든요.

    🎉 완성! k3s 클러스터에 애플리케이션 배포하기

    클러스터가 성공적으로 구축되었다면, 이제 간단한 애플리케이션을 배포해서 잘 동작하는지 확인해봐야죠. 저는 웹 서버로 유명한 Nginx(엔진엑스)를 배포해볼게요. 다음 YAML 파일을 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:latest
            ports:
            - containerPort: 80
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: nginx-service
    spec:
      selector:
        app: nginx
      ports:
        - protocol: TCP
          port: 80
          targetPort: 80
      type: NodePort
    

    저장한 파일을 클러스터에 배포합니다.

    
    kubectl apply -f nginx-deployment.yaml
    

    배포가 성공적으로 완료되었다면, 파드(Pod)와 서비스(Service)가 잘 생성되었는지 확인해봅시다.

    
    kubectl get pods -o wide
    kubectl get svc
    

    kubectl get svc 결과에서 nginx-service의 NodePort를 확인해보세요. 예를 들어 30000번대 포트가 할당될 겁니다. 이제 워커 노드의 IP 주소와 할당된 NodePort를 이용하여 웹 브라우저에서 Nginx 웹 페이지에 접속할 수 있습니다. 예를 들어, http://<워커-노드-IP>:<NodePort> 이런 식으로요. 드디어 제 홈랩 k3s 클러스터에서 Nginx가 동작하는 걸 보니 정말 뿌듯하더라고요! 🎉

    k3s 클러스터에 Nginx 애플리케이션 배포 후 웹 브라우저 접속 화면 및 kubectl 명령 결과를 보여주는 스크린샷입니다.

    🚀 13년차 엔지니어의 제언: k3s 경량 쿠버네티스 활용처

    k3s를 직접 구축하고 사용해보니, 이 경량 쿠버네티스가 얼마나 유용한지 다시 한번 깨달았습니다. 제가 생각하는 k3s의 주요 활용처는 다음과 같습니다.

    활용 분야 k3s의 장점 예시
    홈랩 (Home Lab) 저렴한 비용으로 쿠버네티스 학습 및 개인 서비스 운영 라즈베리 파이 클러스터, 개인 웹서버, 미디어 서버
    엣지 컴퓨팅 (Edge Computing) 제한된 리소스 환경에서 컨테이너 애플리케이션 배포 및 관리 스마트 팩토리, 스마트 시티 IoT 게이트웨이, 지점 서버
    개발/테스트 환경 로컬 머신이나 가상 머신에 빠르고 가볍게 k3s 클러스터 구축 CI/CD 파이프라인 테스트, 개발자 개인 환경
    IoT (사물 인터넷) 수많은 IoT 디바이스의 애플리케이션 배포 및 업데이트 관리 자율주행 차량, 스마트 농장 센서 데이터 처리

    k3s는 쿠버네티스의 복잡성을 줄여주면서도 강력한 기능을 제공하기 때문에, 위와 같은 환경에서 컨테이너 기반 애플리케이션을 효율적으로 배포하고 관리하는 데 탁월한 선택이 될 수 있습니다. 저처럼 인프라를 직접 만져보고 실험하는 걸 좋아하는 분들에게는 정말 최고의 장난감이 될 거예요. 💡

    물론 k3s가 모든 상황에 완벽한 만능 해결책은 아니에요. 대규모 엔터프라이즈 환경에서는 여전히 표준 쿠버네티스 배포판이 더 적합할 수 있습니다. 하지만 소규모, 리소스 제한 환경에서는 k3s가 제공하는 가치는 엄청나다고 생각합니다. 저도 처음엔 ‘이 작은 걸로 뭘 할 수 있을까?’ 싶었는데, 지금은 제 홈랩의 핵심 인프라가 되었습니다. 다음 글에서는 k3s 위에 Helm(헬름)으로 애플리케이션을 배포하거나 Persistent Storage(영구 스토리지)를 구성하는 방법에 대해서도 다뤄볼 예정이니 기대해주세요!

    k3s의 주요 장점과 활용 사례를 간결하게 요약한 인포그래픽입니다.

    오늘 제가 공유한 삽질 경험과 해결 과정이 여러분의 k3s 경량 쿠버네티스 클러스터 구축에 조금이나마 도움이 되었기를 바랍니다. 혹시 궁금한 점이나 또 다른 삽질 경험이 있다면 언제든지 댓글로 남겨주세요! 13년차의 서버실은 언제나 열려있습니다. 다음 글에서 또 만나요! 👋

  • [k8s] ArgoCD 프로덕션 환경 GitOps 베스트 프랙티스: 안정적인 배포와 보안 강화

    [k8s] ArgoCD 프로덕션 환경 GitOps 베스트 프랙티스: 안정적인 배포와 보안 강화

    ArgoCD 프로덕션 환경 GitOps 베스트 프랙티스: 안정적인 배포와 보안 강화

    안녕하세요, 13년차 인프라 엔지니어입니다. 오늘은 쿠버네티스(Kubernetes) 배포의 핵심 툴 중 하나인 ArgoCD와 GitOps에 대해 이야기해보려고 해요. 사실 제가 처음 인프라 엔지니어를 시작했을 때는 배포라고 하면 SSH로 서버에 접속해서 스크립트 돌리고, 수동으로 설정을 변경하고, 문제가 터지면 눈으로 찾아 헤매는 게 일상이었거든요. 그러다 쿠버네티스 세상이 열리고 CI/CD 파이프라인이 중요해지면서, 더 안정적이고 효율적인 배포 방식에 대한 갈증이 커졌습니다.

    수많은 삽질 끝에 제가 정착한 방법이 바로 GitOps였습니다. 그리고 그 중심에는 ArgoCD가 있었죠. 처음엔 이게 뭔가 싶었는데, 막상 써보니까 ‘아, 이거 진짜 편하고 안전하네!’ 싶더라고요. 프로덕션 환경에서 ArgoCD를 안정적으로 운영하고 GitOps 보안을 강화하기 위한 저만의 경험과 베스트 프랙티스를 공유해볼까 합니다. 혹시 쿠버네티스 배포 때문에 밤잠 설치고 계신 분들이 있다면, 이 글이 조금이나마 도움이 되었으면 좋겠네요. 💡

    ArgoCD와 GitOps의 전체 아키텍처는 위 그림처럼 구성됩니다. Git을 중심으로 모든 것이 돌아가는 모습을 보실 수 있어요.

    왜 ArgoCD와 GitOps인가요? – 삽질 끝에 찾은 안정성

    저도 처음에는 Jenkins 같은 전통적인 CI/CD 툴로 쿠버네티스 배포를 시도했어요. 하지만 배포 스크립트 관리도 어렵고, 배포 이력 추적도 쉽지 않더라고요. 특히 긴급 롤백(Rollback) 상황에서는 정말 아찔한 경험도 많았습니다. 😱

    GitOps는 쉽게 말해, Git을 인프라의 ‘단 하나의 진실된 소스(Single Source of Truth)’로 삼는 운영 방식입니다. 모든 인프라와 애플리케이션의 상태를 Git 리포지토리(Repository)에 코드(Code)로 저장하고, Git에 변경사항이 푸시(Push)되면 자동으로 인프라에 반영하는 방식이죠. ArgoCD는 이 GitOps 철학을 쿠버네티스 환경에서 구현해주는 강력한 선언적(Declarative) GitOps 지속적 배포(Continuous Delivery) 툴입니다.

    ArgoCD는 쿠버네티스 클러스터(Cluster) 내부에서 동작하면서, Git 리포지토리의 상태와 실제 클러스터의 상태를 계속 비교합니다. 만약 두 상태가 다르면, Git 리포지토리의 상태(원하는 상태)로 클러스터의 상태를 동기화(Sync)하려고 시도하죠. 이걸 풀 기반(Pull-based) 배포라고 하는데, 기존의 푸시 기반(Push-based) CI/CD 방식보다 훨씬 안정적이고 보안에도 유리합니다. 직접 써보니, 이 방식이 정말 믿음직하더라고요. ✅

    ArgoCD와 GitOps, 쉽게 이해하기

    복잡하게 생각할 것 없이, GitOps는 우리가 늘 쓰던 Git으로 인프라도 관리하자는 이야기입니다. 애플리케이션 코드를 Git으로 관리하듯이, 쿠버네티스 매니페스트(Manifest) 파일들도 Git에 넣고 관리하는 거죠. 이렇게 하면 다음과 같은 장점들이 생깁니다.

    • 버전 관리(Version Control): 모든 변경 이력이 Git에 남으니, 누가 언제 무엇을 바꿨는지 명확하게 알 수 있습니다.
    • 롤백(Rollback) 용이: 문제가 생기면 Git 커밋(Commit)을 되돌리는 것만으로 이전 상태로 쉽게 돌아갈 수 있습니다.
    • 감사(Auditing) 및 보안 강화: 모든 변경이 Git을 통해 이루어지므로, 승인된 변경만 반영될 수 있도록 워크플로우(Workflow)를 구축하기 좋습니다.
    • 단일 진실 공급원(Single Source of Truth): 개발, 운영팀 모두 Git만 보면 현재 인프라 상태를 파악할 수 있습니다.

    ArgoCD는 이 GitOps를 쿠버네티스에서 실현시켜주는 컨트롤러(Controller)입니다. 주요 특징은 다음과 같아요.

    • 선언적(Declarative) 관리: 원하는 상태를 YAML 파일로 선언해두면, ArgoCD가 알아서 그 상태를 유지합니다.
    • 자동 동기화(Automatic Synchronization): Git 리포지토리의 변경을 감지하고 자동으로 클러스터에 반영할 수 있습니다.
    • 시각적인 UI: 웹 UI를 통해 애플리케이션의 배포 상태, 리소스(Resource) 현황, 동기화 이력 등을 한눈에 확인할 수 있습니다. 저처럼 눈으로 확인해야 직성이 풀리는 사람에겐 정말 최고더라고요.
    • 다양한 배포 전략 지원: 롤링 업데이트(Rolling Update), 카나리 배포(Canary Deployment), 블루/그린 배포(Blue/Green Deployment) 등 다양한 배포 전략을 연동하여 구현할 수 있습니다.

    프로덕션 환경을 위한 ArgoCD 설정 팁

    이제 본격적으로 프로덕션 환경에서 ArgoCD를 효과적으로 사용하는 베스트 프랙티스를 공유해볼게요. 제가 직접 겪으면서 터득한 노하우들이니, 꼭 참고하시면 좋겠습니다.

    1. Git Repository 구조화: Monorepo vs. Multirepo

    Git 리포지토리를 어떻게 구성할지는 GitOps 전략의 첫 단추입니다. 크게 모노레포(Monorepo)와 멀티레포(Multirepo) 두 가지 방식이 있어요.

    • 모노레포(Monorepo): 모든 애플리케이션과 인프라 설정 파일을 하나의 Git 리포지토리에 저장하는 방식입니다. 초기 설정이 간단하고, 모든 것을 한곳에서 관리할 수 있다는 장점이 있습니다.
    • 멀티레포(Multirepo): 각 애플리케이션이나 환경별로 별도의 Git 리포지토리를 사용하는 방식입니다. 팀별 권한 분리나 대규모 서비스 운영에 유리할 수 있습니다.

    저 같은 경우, 초기에는 모노레포로 시작했다가 규모가 커지면서 멀티레포 형태로 전환했어요. 하지만 ArgoCD를 통해 여러 애플리케이션을 관리할 때는 애플리케이션별 Git 리포지토리 + 인프라 설정용 Git 리포지토리를 분리하는 방식이 가장 효율적이라고 느꼈습니다. 프로덕션 환경에서는 GitOps 보안과 관리의 용이성을 위해 인프라 설정과 애플리케이션 코드를 분리하는 것을 추천합니다.

    예시: 인프라 설정 Git 리포지토리 구조

    ├── applications # ArgoCD Application 정의
    │   ├── app1.yaml
    │   ├── app2.yaml
    │   └── ...
    ├── environments
    │   ├── dev
    │   │   ├── kustomization.yaml
    │   │   ├── namespace.yaml
    │   │   └── ...
    │   ├── stage
    │   │   ├── kustomization.yaml
    │   │   └── ...
    │   └── prod
    │       ├── kustomization.yaml
    │       └── ...
    └── base # 공통 설정
        ├── deployment.yaml
        ├── service.yaml
        └── ...
    

    위 다이어그램은 제가 주로 사용하는 Git Repository 구조를 시각적으로 보여줍니다. 환경별로 설정을 분리하고, 공통 설정은 base에 두는 방식이에요.

    2. 환경별 분리 및 Kustomize/Helm 활용

    개발(dev), 스테이징(stage), 프로덕션(prod) 환경은 각각 다른 설정(리소스 요구 사항, 환경 변수 등)을 가집니다. 이를 효과적으로 관리하려면 Kustomize나 Helm을 적극적으로 활용해야 합니다.

    • Kustomize: 기존 YAML 파일을 수정하지 않고 덮어쓰기(Overlay) 방식으로 환경별 설정을 관리하는 데 매우 유용합니다. base 디렉터리에 공통 설정 파일을 두고, 각 환경별 overlays 디렉터리에서 필요한 부분만 변경하는 방식은 정말 깔끔하죠.
    • Helm: 재사용 가능한 쿠버네티스 애플리케이션 패키징(Packaging) 도구입니다. 데이터베이스(Database)나 메시지 큐(Message Queue)처럼 공통적으로 사용되는 미들웨어(Middleware)를 배포할 때 매우 편리합니다. 저도 처음엔 Helm이 좀 어렵게 느껴졌는데, 익숙해지니 없으면 안 되는 존재가 되더라고요.

    ArgoCD 애플리케이션 정의에서 Kustomize나 Helm을 소스(Source)로 지정하면, ArgoCD가 알아서 해당 툴을 사용해서 매니페스트를 렌더링(Rendering)하고 배포해줍니다. 🚀

    apiVersion: argoproj.io/v1alpha1
    kind: Application
    metadata:
      name: my-app-prod
      namespace: argocd
    spec:
      project: default
      source:
        repoURL: https://github.com/my-org/my-infra-gitops.git
        targetRevision: HEAD
        path: environments/prod/my-app # Kustomize overlay 경로
      destination:
        server: https://kubernetes.default.svc
        namespace: my-app-prod
      syncPolicy:
        automated:
          prune: true
          selfHeal: true
        syncOptions:
          - CreateNamespace=true
    

    3. Sync Options와 Health Checks

    ArgoCD의 syncPolicy는 배포의 안정성을 결정하는 중요한 부분입니다. 프로덕션 환경에서는 다음 옵션들을 신중하게 설정해야 합니다.

    • automated.prune: true: Git 리포지토리에서 삭제된 리소스를 클러스터에서도 자동으로 삭제합니다. 클러스터의 불필요한 리소스 잔여물을 방지해줍니다.
    • automated.selfHeal: true: 클러스터의 상태가 Git 리포지토리의 상태와 다를 경우, ArgoCD가 자동으로 Git의 상태로 되돌립니다. 누군가 수동으로 클러스터 설정을 변경했을 때 원래대로 복구해주는 강력한 기능이죠. GitOps의 핵심 정신과 일치하는 부분입니다.
    • syncOptions: - CreateNamespace=true: 해당 네임스페이스(Namespace)가 없으면 ArgoCD가 자동으로 생성하도록 합니다.
    • 커스텀 헬스 체크(Custom Health Checks): 애플리케이션이 단순히 배포되었다고 끝이 아닙니다. 실제로 정상 작동하는지 확인하는 헬스 체크가 중요하죠. ArgoCD는 기본적으로 Pod의 Readiness/Liveness Probe를 활용하지만, 경우에 따라 ConfigMap에 커스텀 헬스 체크를 정의하여 더 정교한 상태 감지를 할 수 있습니다.

    4. Rollback 전략

    아무리 잘 준비해도 문제는 발생하기 마련이죠. 이때 빠르고 안전하게 롤백하는 것이 중요합니다. ArgoCD 환경에서의 롤백은 크게 두 가지 방식이 있어요.

    1. Git Revert: 가장 권장되는 방식입니다. 문제가 발생한 커밋을 Git에서 되돌리면(Revert) ArgoCD가 이를 감지하고 자동으로 이전 상태로 클러스터를 동기화합니다. 이 방식은 Git의 변경 이력이 그대로 남으므로 감사 추적에도 유리합니다.
    2. ArgoCD UI 롤백: ArgoCD 웹 UI에서 특정 애플리케이션의 History and Rollback 탭을 통해 이전 동기화 지점(Sync Point)으로 롤백할 수 있습니다. 긴급 상황에서 빠르게 대처할 수 있는 방법이지만, Git의 변경 이력에는 남지 않으므로 나중에 Git Revert로 맞춰주는 게 좋습니다.

    저도 예전에 새벽에 긴급 롤백을 해야 했던 적이 있었는데, Git Revert 하나로 모든 상황이 깔끔하게 정리되었을 때의 안도감이란… 정말 겪어봐야 알 수 있습니다. 👍

    GitOps 보안 강화: ArgoCD 프로덕션 환경 보안 설정

    프로덕션 환경에서는 GitOps 보안이 최우선 고려사항입니다. ArgoCD를 통해 모든 것이 배포되므로, ArgoCD 자체의 보안 설정은 물론 주변 환경과의 연동도 중요합니다.

    1. RBAC (Role-Based Access Control)

    ArgoCD는 자체적인 RBAC(Role-Based Access Control)를 지원합니다. 이를 통해 누가 어떤 애플리케이션을 조회, 동기화, 삭제할 수 있는지 세밀하게 권한을 제어할 수 있어요. argocd-cm ConfigMap이나 argocd-rbac-cm ConfigMap을 수정하여 정책을 정의합니다.

    또한, ArgoCD 프로젝트(Project)를 활용하여 애플리케이션들을 논리적으로 묶고, 프로젝트별로 RBAC 정책을 적용하면 관리 효율성과 보안을 동시에 잡을 수 있습니다. 예를 들어, 개발팀은 dev-project에만 접근 가능하고, 운영팀은 prod-project에만 접근 가능하도록 설정하는 식이죠.

    # argocd-rbac-cm ConfigMap 예시
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: argocd-rbac-cm
      namespace: argocd
    data:
      policy.csv: |
        p, role:admin, applications, *, */*, allow
        p, role:dev, applications, get, dev-project/*, allow
        g, myuser, role:dev
      policy.default: role:readonly
    

    2. Secret Management (외부 Secret 연동)

    민감한 정보인 시크릿(Secret)을 Git 리포지토리에 평문으로 저장하는 것은 절대 금물입니다. 🙅‍♂️ GitOps 보안을 위해 외부 시크릿 관리 솔루션과 연동하는 것이 필수입니다.

    • Sealed Secrets: 클러스터에 배포된 컨트롤러가 시크릿을 암호화하고, Git에 암호화된 시크릿을 저장합니다. 암호화된 시크릿은 해당 클러스터에서만 복호화(Decrypt)될 수 있어 안전합니다. 제가 홈랩에서도 가장 즐겨 쓰는 방식이에요.
    • External Secrets Operator: AWS Secrets Manager, Azure Key Vault, Google Secret Manager, HashiCorp Vault 등 클라우드(Cloud) 기반 시크릿 관리 서비스와 연동하여 시크릿을 쿠버네티스 시크릿으로 동기화합니다. 프로덕션 환경에서는 이 방식이 가장 일반적입니다.

    어떤 방법을 선택하든, 절대 시크릿을 Git에 직접 올리는 일은 없어야 합니다! 이건 제가 정말 피땀 흘려 배운 교훈입니다. ⚠️

    3. Private Repository 접근

    대부분의 프로덕션 환경에서는 프라이빗 Git 리포지토리(Private Git Repository)를 사용합니다. ArgoCD가 이 리포지토리에 접근하려면 인증 정보가 필요하겠죠. 다음 방법들을 사용할 수 있습니다.

    • SSH 키(SSH Key): 가장 일반적이고 강력한 방법입니다. ArgoCD에 SSH 키를 등록하고, 해당 키를 사용하여 리포지토리에 접근합니다.
    • HTTPS with Username/Password 또는 Personal Access Token (PAT): Git 서비스에서 발급받은 PAT를 ArgoCD에 등록하여 사용할 수 있습니다. 보안상 일반적인 비밀번호보다는 PAT를 사용하는 게 좋습니다.

    인증 정보를 ArgoCD에 등록할 때는 쿠버네티스 시크릿으로 안전하게 관리해야 합니다. 당연한 이야기지만, 이걸 놓쳐서 문제가 되는 경우를 꽤 많이 봤거든요. 😅

    ArgoCD 트러블슈팅: 제가 겪었던 문제들 (그리고 해결책)

    13년차 엔지니어라고 해도 삽질은 피할 수 없는 운명이죠. ArgoCD를 쓰면서도 여러 번 머리를 쥐어뜯었습니다. 몇 가지 흔한 문제와 해결책을 공유해볼게요.

    • 문제: ImagePullBackOff 에러 (프라이빗 레지스트리)
      상황: 배포된 Pod에서 프라이빗 컨테이너 이미지 레지스트리(Private Container Image Registry)의 이미지를 당겨오지 못하고 ImagePullBackOff 에러가 발생하는 경우.
      해결: 해당 네임스페이스에 ImagePullSecrets를 제대로 설정했는지 확인해야 합니다. ArgoCD 애플리케이션이 배포될 때 이 시크릿이 같이 생성되도록 매니페스트에 포함하거나, 수동으로 시크릿을 생성하고 Pod Spec에 추가해야 합니다. 저는 주로 ArgoCD가 CreateNamespace=true와 함께 시크릿도 함께 배포하도록 설정합니다.

    • 문제: Resource is not available 또는 Failed to install CRD
      상황: 애플리케이션 배포 시 커스텀 리소스 정의(CRD: Custom Resource Definition)가 먼저 설치되어야 하는데, 순서가 맞지 않아 발생하는 에러.
      해결: ArgoCD의 Sync Waves 기능을 활용하면 배포 순서를 제어할 수 있습니다. CRD를 먼저 배포하고, 그 이후에 CRD를 사용하는 애플리케이션 리소스를 배포하도록 설정하는 거죠. 예를 들어, CRD는 sync-wave: "-1", 일반 리소스는 sync-wave: "0"으로 설정하면 됩니다. 이 기능을 알게 된 후로 정말 속이 시원했습니다. 🎉

    • 문제: 롤백 후에도 문제가 지속됨
      상황: Git Revert나 ArgoCD UI 롤백을 했는데도 애플리케이션이 정상 상태로 돌아오지 않는 경우.
      해결: 롤백 대상이 되는 커밋이 정말 이전의 ‘정상’ 상태를 가리키는지 확인해야 합니다. 때로는 환경 변수나 외부 의존성(예: 데이터베이스 스키마 변경) 때문에 롤백만으로는 해결되지 않을 수 있거든요. 이럴 때는 문제 발생 시점을 정확히 파악하고, 관련된 모든 변경사항(Git, DB 스키마, 외부 서비스 설정 등)을 함께 되돌려야 합니다. 그리고 롤백 후 ArgoCD UI에서 애플리케이션의 Health 상태와 Events 탭을 꼼꼼히 확인하는 습관을 들이는 게 좋습니다.

    결과 및 검증: 안정적인 배포, 이제 Git만 바라봅니다.

    ArgoCD와 GitOps 베스트 프랙티스를 적용하고 나니, 정말 배포 과정이 안정적이고 예측 가능해졌습니다. 더 이상 불안에 떨며 배포 버튼을 누를 필요가 없어졌죠. ✅

    배포가 완료되면 ArgoCD 웹 UI에서 각 애플리케이션의 상태를 한눈에 확인할 수 있습니다. 모든 리소스가 Healthy 상태이고, Synced 상태인지 확인하는 것이 중요해요. 혹시 OutOfSync 상태라면, 어떤 리소스가 Git과 다른지 명확하게 보여주므로 문제 파악이 매우 쉽습니다. 이 시각적인 피드백(Feedback) 덕분에 트러블슈팅 시간도 대폭 줄었습니다.

    ArgoCD UI에서 모든 애플리케이션이 건강하고(Healthy) 동기화(Synced)된 상태를 보여주는 화면입니다. 이 화면을 볼 때마다 뿌듯하더라고요!

    실제로 저는 홈랩에서 여러 서비스를 ArgoCD로 관리하고 있는데, Git에 커밋만 하면 알아서 배포가 되고, 문제가 생겨도 Git Revert 한 번으로 해결되는 경험은 정말 혁신적이었습니다. CI/CD 파이프라인의 완성도를 높이는 데 ArgoCD가 결정적인 역할을 했다고 생각합니다.

    ArgoCD GitOps를 프로덕션 환경에 적용하기 위한 핵심 베스트 프랙티스를 요약한 인포그래픽입니다.

    마무리: GitOps 여정, 계속됩니다

    오늘은 ArgoCD 프로덕션 환경 GitOps 베스트 프랙티스에 대해 저의 경험을 바탕으로 이야기해봤습니다. 안정적인 배포와 GitOps 보안 강화를 위해 Git 리포지토리 구조화, 환경별 설정 관리, Sync Policy, 롤백 전략, RBAC, 시크릿 관리 등 다양한 측면을 다루었네요.

    처음에는 복잡하게 느껴질 수도 있지만, 한 번 제대로 구축해두면 개발팀과 운영팀 모두에게 엄청난 효율성과 안정성을 가져다줄 겁니다. 저도 아직 배워야 할 것이 많고, 계속해서 새로운 기술을 실험하고 있습니다. 이 글이 여러분의 쿠버네티스 배포 여정에 작은 등불이 되기를 바랍니다. 궁금한 점이나 공유하고 싶은 삽질 경험이 있다면 언제든지 댓글로 남겨주세요! 다음에는 ArgoCD와 함께 CI/CD 파이프라인을 더욱 자동화하는 방법에 대해 다뤄보겠습니다. 그때까지 모두 즐거운 서버실 라이프 즐기시길 바랍니다! 😊