Ansible과 Jenkins 통합: CI/CD 파이프라인 자동화

Jenkins와 Ansible을 통합하여 CI/CD 파이프라인에서 플레이북을 실행하고, SSH 자격 증명을 관리하며, 일관된 배포를 수행하는 방법을 알아봅니다.

Ansible과 Jenkins 통합: CI/CD 파이프라인 자동화

Ansible을 Jenkins와 통합하면 CI/CD 파이프라인이 아티팩트를 빌드하고, 테스트를 실행하며, Jenkins 외부에서 사용하는 것과 동일한 플레이북으로 배포할 수 있습니다. 주요 과제는 자격 증명, 인벤토리, 플레이북 실행을 파이프라인이 셸 명령어 덩어리로 변질되지 않도록 연결하는 것입니다.

이 가이드는 실용적인 설정을 보여줍니다: Jenkins는 파이프라인을 오케스트레이션하고, Ansible은 대상 호스트에서 배포 및 구성을 처리합니다. 예제는 SSH 기반 Linux 배포를 가정하지만, 동일한 패턴이 환경별 인벤토리 및 역할에서도 작동합니다.

Jenkins와 Ansible의 역할 분담

파이프라인을 작성하기 전에 책임을 분리하세요:

  • Jenkins: 코드 체크아웃, 아티팩트 빌드, 테스트 실행, 로그 수집, 배포 단계 실행 시점 결정.
  • Ansible: 대상 호스트 연결, 아티팩트 복사, 구성 작성, 서비스 관리, 배포 단계의 멱등성 유지.

Jenkins와 Ansible을 통합하는 이유

이러한 분리는 팀에 명확한 경계를 제공합니다:

  1. Jenkins는 Jenkinsfile에 릴리스 흐름을 저장합니다.
  2. Ansible은 플레이북과 역할에 인프라 작업을 저장합니다.
  3. 인벤토리 파일은 스테이징, 프로덕션 등 어떤 호스트가 어떤 환경에 속하는지 결정합니다.
  4. Jenkins 자격 증명은 SSH 키, Vault 비밀번호, 토큰을 보호합니다.

통합을 위한 사전 요구 사항

시작하기 전에 다음이 준비되어 있는지 확인하세요:

  • 작동 중인 Jenkins 컨트롤러와 배포 작업을 실행할 수 있는 최소 하나의 에이전트.
  • ansible-playbook을 실행하는 Jenkins 에이전트에 Ansible이 설치되어 있어야 함.
  • 해당 에이전트에서 SSH로 접근 가능한 대상 머신.
  • 자동화 전용 SSH 키 쌍.
  • 버전 관리 시스템에 저장된 애플리케이션 코드, 인벤토리, 플레이북, 역할.

Jenkins를 Ansible에 맞게 설정

Jenkins가 Ansible을 안전하게 실행하려면 두 가지가 필요합니다: 에이전트에 Ansible 런타임과 대상 호스트에 대한 자격 증명.

1. Ansible 플러그인 설치

셸 단계에서 직접 ansible-playbook을 호출할 수 있지만, Jenkins Ansible 플러그인을 사용하면 구성과 출력을 더 쉽게 관리할 수 있습니다.

  1. Manage Jenkins > Manage Plugins로 이동합니다.
  2. Available 탭으로 이동하여 "Ansible"을 검색합니다.
  3. "Ansible" 플러그인을 선택하고 Install without restart 또는 Download now and install after restart를 클릭합니다.

2. SSH 자격 증명 구성

Ansible은 SSH를 사용하여 대상 서버에 연결합니다. 리포지토리가 아닌 Jenkins 자격 증명에 개인 키를 저장하세요.

  1. Manage Jenkins > Manage Credentials로 이동합니다.
  2. 작업에서 사용하는 자격 증명 저장소를 선택합니다.
  3. Add Credentials를 클릭합니다.
  4. SSH Username with private key를 선택합니다.
  5. ansible-ssh-key와 같은 안정적인 ID를 설정합니다.
  6. 대상 머신에서 사용되는 SSH 사용자 이름을 입력합니다.
  7. 개인 키를 추가하고 자격 증명을 저장합니다.

팁: 키 관리 모범 사례

  • Jenkinsfile, 인벤토리 또는 플레이북에 SSH 개인 키를 하드코딩하지 마세요.
  • 개인 키가 아닌 전용 자동화 키를 사용하세요.
  • 팀의 보안 정책에 맞는 일정에 따라 키를 교체하세요.

Ansible을 사용한 Jenkins 파이프라인 설계

배포 로직이 애플리케이션 코드와 함께 검토될 수 있도록 Jenkinsfile에서 선언적 파이프라인을 사용하세요.

기본 파이프라인 구조

다음은 빌드, 스테이징 배포, 승인된 프로덕션 배포를 위한 간결한 예제입니다:

// Jenkinsfile
pipeline {
    agent any

    environment {
        // 필요한 경우 환경 변수 정의
        ANSIBLE_HOST_KEY_CHECKING = 'False' // 프로덕션에서는 주의, known_hosts 사용 권장
    }

    stages {
        stage('소스 체크아웃') {
            steps {
                git 'https://your-scm-url/your-repo.git'
            }
        }

        stage('애플리케이션 빌드') {
            // 이 단계는 JAR, WAR, Docker 이미지 등을 빌드할 수 있습니다.
            // 예: Spring Boot JAR 빌드
            steps {
                sh 'mvn clean package'
            }
        }

        stage('단위 테스트 실행') {
            steps {
                sh 'mvn test'
            }
        }

        stage('스테이징에 배포') {
            steps {
                script {
                    // SSH 에이전트를 사용하여 개인 키를 Ansible에 제공
                    sshagent(credentials: ['ansible-ssh-key']) {
                        // Ansible 플레이북 실행
                        sh 'ansible-playbook -i inventory/staging.ini playbooks/deploy_app.yml \
                            -e "app_version=$(cat target/VERSION)"'
                    }
                }
            }
        }

        stage('프로덕션에 배포') {
            // 이 단계는 수동 승인이 필요할 수 있습니다.
            input {
                message "프로덕션에 배포를 진행하시겠습니까?"
                ok "프로덕션에 배포"
            }
            steps {
                script {
                    sshagent(credentials: ['ansible-ssh-key']) {
                        sh 'ansible-playbook -i inventory/production.ini playbooks/deploy_app.yml \
                            -e "app_version=$(cat target/VERSION)"'
                    }
                }
            }
        }
    }

    post {
        always {
            echo '파이프라인이 완료되었습니다.'
        }
        success {
            echo '파이프라인이 성공했습니다!'
            // slackSend channel: '#deployments', message: "배포 성공: ${env.BUILD_URL}"
        }
        failure {
            echo '파이프라인이 실패했습니다!'
            // slackSend channel: '#deployments', message: "배포 실패: ${env.BUILD_URL}"
        }
    }
}

주요 파이프라인 세부 사항

  • agent any는 데모용으로 작동합니다. 프로덕션에서는 Ansible이 설치되어 있음을 보장하는 에이전트 레이블을 사용하세요.
  • sshagent(credentials: ['ansible-ssh-key'])는 해당 블록 내에서만 개인 키를 노출합니다.
  • ansible-playbook -i inventory/staging.ini playbooks/deploy_app.yml은 대상 환경을 명시적으로 유지합니다.
  • 프로덕션 input 블록은 민감한 배포 전에 수동 게이트를 추가합니다.
  • 프로덕션에서는 호스트 키 확인을 비활성화하지 마세요. 대신 Jenkins 에이전트에서 known_hosts를 관리하세요.

예제 Ansible 플레이북: 웹 애플리케이션 배포

다음은 Java 웹 애플리케이션을 위한 간소화된 inventory/staging.iniplaybooks/deploy_app.yml입니다. 아티팩트 이름, 서비스 관리자, 경로는 다를 수 있습니다.

inventory/staging.ini

[web_servers]
web1.example.com
web2.example.com

[database_servers]
db1.example.com

[all:vars]
ansible_user=ubuntu

playbooks/deploy_app.yml

---
- name: 웹 애플리케이션 배포
  hosts: web_servers
  become: yes
  vars:
    app_name: my-webapp
    app_path: /opt/{{ app_name }}
    app_port: 8080
    app_version: "{{ app_version | default('1.0.0') }}"

  tasks:
    - name: 애플리케이션 디렉토리 확인
      ansible.builtin.file:
        path: "{{ app_path }}"
        state: directory
        owner: "{{ ansible_user }}"
        group: "{{ ansible_user }}"
        mode: '0755'

    - name: 애플리케이션 JAR을 대상에 복사
      ansible.builtin.copy:
        src: "target/{{ app_name }}-{{ app_version }}.jar"
        dest: "{{ app_path }}/{{ app_name }}.jar"
        owner: "{{ ansible_user }}"
        group: "{{ ansible_user }}"
        mode: '0644'
      notify: 앱 서비스 재시작

    - name: systemd 서비스 파일 확인
      ansible.builtin.template:
        src: templates/my-webapp.service.j2
        dest: /etc/systemd/system/{{ app_name }}.service
        owner: root
        group: root
        mode: '0644'
      notify: 앱 서비스 재시작

    - name: 앱 서비스가 시작되고 활성화되었는지 확인
      ansible.builtin.systemd:
        name: "{{ app_name }}"
        state: started
        enabled: yes

  handlers:
    - name: 앱 서비스 재시작
      ansible.builtin.systemd:
        name: "{{ app_name }}"
        state: restarted
        daemon_reload: yes

templates/my-webapp.service.j2 (Systemd 서비스 템플릿)

[Unit]
Description={{ app_name }} 애플리케이션
After=network.target

[Service]
User={{ ansible_user }}
ExecStart=/usr/bin/java -jar {{ app_path }}/{{ app_name }}.jar --server.port={{ app_port }}
SuccessExitStatus=143

[Install]
WantedBy=multi-user.target

이 플레이북은 앱 디렉토리를 생성하고, 빌드된 JAR을 복사하며, systemd 유닛을 작성하고, 서비스를 시작합니다. 핸들러는 아티팩트나 유닛 파일이 변경된 경우에만 서비스를 재시작합니다.

모범 사례 및 팁

  • 플레이북을 멱등성 있게 유지하여 실패한 파이프라인을 다시 실행해도 추가 손상이 발생하지 않도록 하세요.
  • Vault 비밀번호는 Jenkins 자격 증명에 저장하고 임시 비밀번호 파일이나 승인된 자격 증명 바인딩을 통해 전달하세요.
  • 배포 로직을 webserver, database, app_deploy와 같은 역할로 구성하세요.
  • 스테이징과 프로덕션에 대해 별도의 인벤토리 또는 group_vars를 사용하세요.
  • Jenkins 컨트롤러가 아닌 전용 Jenkins 에이전트에서 Ansible을 실행하세요.
  • 더 많은 세부 정보가 필요할 때만 -v 또는 -vv를 사용하고, 로그에 비밀 정보가 유출되지 않도록 주의하세요.
  • Jenkins가 실행하기 전에 플레이북을 테스트하세요. 특히 프로덕션 역할의 경우 더욱 중요합니다.

전문가의 도움이 필요할 때

파이프라인이 프로덕션에 배포하거나, 공유 자격 증명을 관리하거나, 여러 호스트에 쓰거나, 감사 제어가 필요한 경우 Jenkins 또는 플랫폼 관리자의 도움을 받으세요. 자격 증명 처리, 호스트 키 확인, 롤백 동작은 첫 번째 프로덕션 실행 전에 재검토가 필요합니다.

결론

Jenkins는 오케스트레이션에, Ansible은 배포 상태 관리에 사용하세요. 자격 증명은 Jenkins에, 플레이북은 버전 관리에 유지하고, 인벤토리를 명시적으로 만들고, 프로덕션 배포를 자동화하기 전에 파이프라인 외부에서 동일한 Ansible 명령어를 테스트하세요.