Ansible 플레이북: 여러 Linux 서버의 패키지, 설정 파일, 서비스 상태를 선언적으로 관리하면 수동 명령의 누락과 환경 차이를 줄일 수 있습니다. 그러나 ping 성공만 확인한 뒤 전체 서버에 바로 실행하거나, command·shell 작업을 무분별하게 사용하고 비밀번호를 YAML에 넣으면 자동화가 장애와 정보 노출을 더 빠르게 확산시킬 수 있습니다.

이 가이드는 특정 배포판이나 오래된 Ansible 버전에 고정하지 않습니다. 격리된 Python 환경, YAML 인벤토리, ansible.builtin FQCN, 멱등성, Handler, check·diff, Vault, serial과 block·rescue를 묶어 검증 가능한 운영 흐름으로 설명합니다. 적용 전에는 사용 중인 ansible-core와 컬렉션의 공식 문서를 확인하십시오.

Ansible 플레이북 자동화: 인벤토리와 제어 노드, SSH, 서버 그룹, 검증과 복구 흐름
인벤토리와 선언적 작업을 제어 노드에서 검증한 뒤 SSH를 통해 환경별 서버에 순차 적용하고 결과를 확인하는 흐름

Ansible 플레이북 핵심 구성 요소

구성 요소 역할 운영 기준
Inventory 대상 호스트·그룹·연결 변수 dev·stage·prod 경계를 분리하고 중복 변수를 최소화
Play 대상 그룹과 실행 정책 hosts, become, serial, failure 정책을 명시
Task 모듈 호출 한 단위 이름·FQCN·명시적 상태를 사용
Module 패키지·파일·서비스 등의 실제 작업 가능하면 command·shell보다 전용 모듈 선택
Handler 변경 시에만 실행하는 후속 작업 설정 변경 뒤 검증된 서비스 재시작에 사용
Role 재사용 가능한 tasks·handlers·templates·defaults 묶음 책임을 작게 나누고 인터페이스 변수 문서화
Collection 모듈·플러그인·Role 배포 단위 테스트한 버전을 고정하고 변경 내역 검토
Ansible의 상태 보고에서 ok는 이미 원하는 상태, changed는 실제 변경, failed는 실패를 뜻합니다. 성공 여부만 보지 말고 재실행 시 changed=0이 되는지, 예상된 Handler만 실행되는지 확인해야 멱등성을 검증할 수 있습니다.

Ansible 플레이북 실행 환경 준비

제어 노드의 시스템 Python에 직접 패키지를 섞지 말고 프로젝트별 venv를 만듭니다. 설치한 ansible-core, Python, 설정 파일과 컬렉션 경로를 기록해야 같은 자동화를 다시 재현할 수 있습니다.

Python venv에 Ansible 설치

python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install ansible-core ansible-lint

ansible --version
ansible-playbook --version
ansible-lint --version
python -m pip freeze > requirements-lock.txt

운영 프로젝트는 검증한 ansible-core와 라이브러리 버전을 lock 파일 또는 승인된 패키지 저장소로 고정합니다. 최신 버전을 무조건 설치하는 방식은 컬렉션 호환성과 Python 요구사항이 바뀔 때 재현성을 잃습니다.

프로젝트 디렉터리

install -d inventories/dev/group_vars inventories/prod/group_vars
install -d roles/web/{tasks,handlers,templates,defaults}
install -d playbooks

find . -maxdepth 3 -type d | sort

안전한 ansible.cfg 기본값

[defaults]
inventory = inventories/dev/hosts.yml
roles_path = roles
host_key_checking = True
retry_files_enabled = False
interpreter_python = auto_silent
forks = 10
timeout = 15

[privilege_escalation]
become = False
become_ask_pass = True

host_key_checking을 끄는 예제는 중간자 공격을 탐지하지 못하게 합니다. 대상 호스트의 SSH 공개키 지문을 신뢰할 수 있는 경로로 확인한 뒤 known_hosts에 등록하십시오. become은 필요한 Play나 Task에서만 켜고, 비밀번호를 설정 파일에 저장하지 않습니다.

Ansible 플레이북 YAML 인벤토리

그룹 이름은 역할과 장애 범위를 나타내고, ansible_host는 실제 접속 주소를 담습니다. 운영과 개발을 한 파일의 태그로만 구분하기보다 inventory source 자체를 나누면 실수로 prod를 선택할 가능성을 줄일 수 있습니다.

inventories/dev/hosts.yml

all:
  children:
    web:
      hosts:
        dev-web-01:
          ansible_host: 192.0.2.11
        dev-web-02:
          ansible_host: 192.0.2.12
    database:
      hosts:
        dev-db-01:
          ansible_host: 192.0.2.21
  vars:
    ansible_user: automation
    ansible_become: true

인벤토리 구문과 대상 확인

ansible-inventory -i inventories/dev/hosts.yml --graph
ansible-inventory -i inventories/dev/hosts.yml --list | jq .

ansible web   -i inventories/dev/hosts.yml   -m ansible.builtin.ping   --limit dev-web-01
ansible.builtin.ping은 ICMP ping이 아니라 원격 Python 실행과 Ansible 연결을 확인합니다. SSH 접속, Python, 권한이 정상이라는 신호일 뿐 패키지 저장소, 디스크 공간, 서비스 의존성까지 준비됐다는 뜻은 아닙니다.

Ansible 플레이북 첫 작성과 Handler

아래 Play는 패키지 설치, 설정 템플릿, 서비스 상태를 선언합니다. 각 Task에는 사람이 읽을 수 있는 이름과 FQCN을 사용합니다. template 결과가 실제로 바뀌었을 때만 Handler가 통지되어 불필요한 재시작을 피합니다.

playbooks/web.yml

---
- name: Configure web servers
  hosts: web
  become: true
  gather_facts: true

  tasks:
    - name: Ensure Nginx is installed
      ansible.builtin.package:
        name: nginx
        state: present

    - name: Render Nginx virtual host
      ansible.builtin.template:
        src: nginx-vhost.conf.j2
        dest: /etc/nginx/conf.d/app.conf
        owner: root
        group: root
        mode: '0644'
        validate: '/usr/sbin/nginx -t -c %s'
      notify: Restart Nginx

    - name: Ensure Nginx is enabled and running
      ansible.builtin.service:
        name: nginx
        enabled: true
        state: started

  handlers:
    - name: Restart Nginx
      ansible.builtin.service:
        name: nginx
        state: restarted

template의 validate는 임시 파일을 실제 경로에 쓰기 전에 명령으로 검사합니다. Nginx 배포판별 기본 설정과 include 구조에 따라 -c로 단일 vhost를 검사하는 방식이 맞지 않을 수 있으므로, 스테이징에서 전체 설정 검증 명령을 확인하고 필요하면 별도 wrapper를 사용하십시오.

Jinja 템플릿 예제

server {
    listen 80;
    server_name {{ app_server_name }};

    location /healthz {
        access_log off;
        return 200 "ok\n";
    }

    location / {
        proxy_pass http://127.0.0.1:{{ app_port }};
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

Ansible 플레이북 사전 검증: syntax·check·diff

구문 검사 후 개발 inventory에서 단일 호스트에 check mode를 실행하고 diff를 검토합니다. check mode는 변경을 시뮬레이션하지만 모든 모듈이 지원하는 것은 아니며, 이전 Task가 만든 값을 조건문에서 쓰는 Play는 실제 실행과 다르게 보일 수 있습니다.

ansible-playbook   -i inventories/dev/hosts.yml   playbooks/web.yml   --syntax-check

ansible-playbook   -i inventories/dev/hosts.yml   playbooks/web.yml   --check --diff   --limit dev-web-01

diff에는 설정 전후 값이 표시되어 secret이 노출될 수 있습니다. 민감한 템플릿 Task에는 diff: false와 no_log: true를 검토하고, CI 로그 접근 권한과 보존 기간도 제한합니다. 검증이 끝나면 –limit을 유지한 채 실제 실행하고, 이후 한 그룹씩 범위를 넓힙니다.

ansible-lint playbooks/web.yml

ansible-playbook   -i inventories/dev/hosts.yml   playbooks/web.yml   --limit dev-web-01

ansible-playbook   -i inventories/dev/hosts.yml   playbooks/web.yml   --check --limit dev-web-01

Ansible 플레이북 멱등성 만들기

불안정한 접근 권장 접근 이유
shell: echo >> file lineinfile·blockinfile·template 재실행 시 중복을 방지
shell: yum install package·dnf 현재 상태를 읽고 필요한 변경만 수행
command로 매번 초기화 creates·removes 또는 전용 모듈 완료 조건을 명시
매번 서비스 restart Handler notify 구성이 바뀔 때만 재시작
모든 오류를 무시 failed_when·block/rescue 예상 오류와 실제 실패를 구분

command에 완료 조건 지정

- name: Initialize application database once
  ansible.builtin.command:
    argv:
      - /usr/local/bin/myapp-init
      - --database
      - /var/lib/myapp/app.db
    creates: /var/lib/myapp/.initialized
  register: init_result
  changed_when: init_result.rc == 0

creates는 지정 경로가 있으면 command를 실행하지 않습니다. 초기화 프로그램이 성공한 뒤 marker를 실제로 만드는지 확인해야 합니다. changed_when을 무조건 false로 두면 변경 사실과 Handler 트리거를 숨기므로, 명령의 종료 코드와 출력 의미를 근거로 작성하십시오.

Ansible 플레이북 Vault와 secret 보호

Ansible Vault는 변수와 파일을 암호화해 저장소의 평문 secret을 줄입니다. 그러나 공식 문서가 강조하듯 Vault는 저장 상태의 데이터만 보호합니다. 실행 중 복호화된 값은 모듈 인수, diff, debug, 오류 출력에 나타날 수 있으므로 no_log, diff 제한, 로그 접근 제어가 함께 필요합니다.

문자열을 터미널 입력으로 암호화

read -rsp 'Secret value: ' SECRET_VALUE
echo
printf '%s' "$SECRET_VALUE"   | ansible-vault encrypt_string       --vault-id prod@prompt       --stdin-name 'db_password'   > inventories/prod/group_vars/all.vault.yml
unset SECRET_VALUE

chmod 0600 inventories/prod/group_vars/all.vault.yml

Vault ID로 실행

ansible-vault view   --vault-id prod@prompt   inventories/prod/group_vars/all.vault.yml

ansible-playbook   -i inventories/prod/hosts.yml   playbooks/web.yml   --vault-id prod@prompt   --check --limit prod-web-01

민감한 Task 출력 차단

- name: Render application secret configuration
  ansible.builtin.template:
    src: app-secret.conf.j2
    dest: /etc/myapp/secret.conf
    owner: root
    group: root
    mode: '0600'
  no_log: true
  diff: false
no_log는 해당 Task 출력을 줄이지만 악성 코드, 별도 debug Task, 대상 시스템 로그까지 모두 보호하는 보안 경계는 아닙니다. 가능하면 외부 secret manager와 짧은 수명 자격 증명을 사용하고, Vault 비밀번호 파일도 Git에 넣지 마십시오.

Ansible 플레이북 롤링 배포

운영 서버 전체를 동시에 변경하지 말고 serial로 배치 크기를 제한합니다. max_fail_percentage는 현재 배치에서 실패 비율이 설정값을 초과할 때 중단합니다. 2대 중 1대 실패에서 멈추게 하려면 50이 아니라 49처럼 초과 조건을 고려해야 합니다.

---
- name: Roll out application safely
  hosts: web
  become: true
  serial: 2
  max_fail_percentage: 49

  pre_tasks:
    - name: Confirm target batch
      ansible.builtin.debug:
        msg: "Deploying to {{ ansible_play_batch }}"

  tasks:
    - name: Deploy application package
      ansible.builtin.package:
        name: myapp
        state: present
      notify: Restart MyApp

    - name: Verify local health endpoint
      ansible.builtin.uri:
        url: http://127.0.0.1:8080/healthz
        status_code: 200
        return_content: false
      register: health
      retries: 10
      delay: 3
      until: health.status == 200

  handlers:
    - name: Restart MyApp
      ansible.builtin.service:
        name: myapp
        state: restarted

이 예제는 구조를 설명하기 위한 최소형입니다. 실제 로드밸런서 환경에서는 배치의 각 호스트를 트래픽에서 제외하고 연결 소진을 기다린 뒤 배포·헬스체크·재등록해야 합니다. delegate_to와 API 모듈을 사용할 때도 인증서 검증, 실패 처리와 재실행 안전성을 확인하십시오.

Ansible 플레이북 오류 처리와 복구

block은 관련 작업에 공통 become, when 등을 적용하고 rescue와 always로 실패 흐름을 표현합니다. rescue가 성공하면 원래 실패가 복구된 것으로 간주되어 Play가 계속될 수 있습니다. 구문 오류와 unreachable host는 rescue가 처리하지 않으므로 연결 실패 정책과 모니터링은 따로 필요합니다.

- name: Update service configuration with recovery
  block:
    - name: Back up current configuration
      ansible.builtin.copy:
        src: /etc/myapp/myapp.conf
        dest: /var/backups/myapp.conf.pre-ansible
        remote_src: true
        owner: root
        group: root
        mode: '0600'

    - name: Render candidate configuration
      ansible.builtin.template:
        src: myapp.conf.j2
        dest: /etc/myapp/myapp.conf
        owner: root
        group: root
        mode: '0640'
        validate: '/usr/local/bin/myapp --check-config %s'
      notify: Restart MyApp

  rescue:
    - name: Restore previous configuration
      ansible.builtin.copy:
        src: /var/backups/myapp.conf.pre-ansible
        dest: /etc/myapp/myapp.conf
        remote_src: true
        owner: root
        group: root
        mode: '0640'

    - name: Stop this host after recovery
      ansible.builtin.fail:
        msg: Configuration deployment failed and was restored

  always:
    - name: Record completion state
      ansible.builtin.debug:
        msg: "Configuration block finished for {{ inventory_hostname }}"

복구 파일 하나만으로 애플리케이션과 데이터베이스 스키마까지 되돌릴 수 있다고 가정하면 안 됩니다. 배포 전후 검증, 패키지 롤백 가능성, 데이터 변경, Handler 실행 시점을 포함한 별도 runbook을 만드십시오.

Ansible 플레이북 품질 검사와 실행 순서

  1. ansible-core, Python, 컬렉션 버전과 적용할 Git commit을 기록합니다.
  2. inventory –graph와 –list로 대상·변수·그룹을 확인합니다.
  3. ansible-lint와 –syntax-check를 통과시킵니다.
  4. 개발 환경 단일 호스트에서 –check –diff를 실행합니다.
  5. 민감한 diff와 no_log 적용 범위를 검토합니다.
  6. 실제 단일 호스트 실행 후 헬스체크와 재실행 changed=0을 확인합니다.
  7. stage의 작은 serial 배치로 장애 중단과 복구 절차를 시험합니다.
  8. 승인된 변경 창에 prod를 배포하고 recap·애플리케이션 지표·로그를 보관합니다.
ansible-config dump --only-changed
ansible-inventory -i inventories/prod/hosts.yml --graph
ansible-lint playbooks roles
ansible-playbook -i inventories/prod/hosts.yml playbooks/web.yml --syntax-check

ansible-playbook   -i inventories/prod/hosts.yml   playbooks/web.yml   --check --diff   --limit prod-web-01   --vault-id prod@prompt

Ansible 플레이북 운영 체크리스트

  • dev·stage·prod inventory source와 쓰기 권한을 분리했습니다.
  • SSH host key 검증을 유지하고 자동화 계정과 sudo 권한을 최소화했습니다.
  • 모든 Task에 이름과 FQCN을 사용하고 command·shell에는 완료·변경 조건이 있습니다.
  • 설정 변경은 validate와 Handler로 검증하며 재실행 시 changed=0을 확인합니다.
  • Vault, no_log, diff 제한과 로그 접근 제어를 함께 적용했습니다.
  • syntax, lint, check, diff, 단일 호스트, 작은 serial 배치 순으로 범위를 넓힙니다.
  • block/rescue가 처리하지 못하는 unreachable·구문 오류와 데이터 롤백 절차가 있습니다.
  • 실행한 commit, inventory, limit, 실행자, recap과 사후 검증을 변경 기록에 남깁니다.

Ansible 플레이북 공식 문서와 관련 글

정리

Ansible 플레이북: 안전한 자동화는 명령을 많이 모으는 것이 아니라 원하는 상태, 변경 조건, 실패 경계와 검증 순서를 코드로 표현하는 일입니다. inventory를 환경별로 분리하고 FQCN 전용 모듈과 Handler로 멱등성을 만들며, Vault의 보호 범위를 정확히 이해하십시오. syntax·lint·check·diff·단일 호스트·serial 배치와 복구 시험을 통과한 자동화만 운영에 확대해야 합니다.