Ansible Playbook이란?
Ansible은 에이전트 없이 SSH만으로 서버를 구성·배포·관리하는 인프라 자동화 도구다. Playbook은 YAML로 작성하는 자동화 시나리오로, 서버 프로비저닝, 애플리케이션 배포, 설정 관리를 선언적으로 정의한다. Terraform이 인프라 생성에 집중한다면, Ansible은 인프라 위의 구성 관리에 강점이 있다.
| 구분 | Terraform | Ansible |
|---|---|---|
| 주 용도 | 인프라 프로비저닝 (VM, 네트워크) | 구성 관리, 앱 배포 |
| 에이전트 | 없음 (API 호출) | 없음 (SSH) |
| 상태 관리 | State 파일 | 멱등성 모듈 |
| 언어 | HCL | YAML + Jinja2 |
프로젝트 디렉터리 구조
Ansible 프로젝트는 표준 디렉터리 구조를 따르면 팀 협업과 유지보수가 쉬워진다.
ansible-project/
├── ansible.cfg # Ansible 설정
├── inventory/
│ ├── production/
│ │ ├── hosts.yml # 프로덕션 호스트
│ │ └── group_vars/
│ │ ├── all.yml # 공통 변수
│ │ ├── webservers.yml
│ │ └── databases.yml
│ └── staging/
│ ├── hosts.yml
│ └── group_vars/
│ └── all.yml
├── playbooks/
│ ├── site.yml # 메인 플레이북
│ ├── deploy.yml # 앱 배포
│ └── setup.yml # 서버 초기 설정
├── roles/
│ ├── common/
│ ├── nginx/
│ ├── app/
│ └── monitoring/
└── templates/
└── nginx.conf.j2
인벤토리: 호스트 그룹 관리
# inventory/production/hosts.yml
all:
children:
webservers:
hosts:
web-01:
ansible_host: 10.0.1.10
ansible_user: deploy
app_port: 3000
web-02:
ansible_host: 10.0.1.11
ansible_user: deploy
app_port: 3000
databases:
hosts:
db-primary:
ansible_host: 10.0.2.10
ansible_user: admin
pg_role: primary
db-replica:
ansible_host: 10.0.2.11
ansible_user: admin
pg_role: replica
monitoring:
hosts:
monitor-01:
ansible_host: 10.0.3.10
# group_vars/all.yml — 전체 공통 변수
---
app_name: myapp
app_env: production
deploy_user: deploy
ntp_servers:
- ntp1.example.com
- ntp2.example.com
ssh_allowed_keys:
- "ssh-ed25519 AAAA... admin@company"
Playbook 기본 구조
# playbooks/setup.yml — 서버 초기 설정
---
- name: 공통 서버 설정
hosts: all
become: true # sudo 권한
gather_facts: true
vars:
packages:
- curl
- vim
- htop
- jq
- fail2ban
tasks:
- name: 시스템 패키지 업데이트
apt:
update_cache: true
upgrade: safe
cache_valid_time: 3600
when: ansible_os_family == "Debian"
- name: 필수 패키지 설치
apt:
name: "{{ packages }}"
state: present
- name: 타임존 설정
timezone:
name: Asia/Seoul
- name: NTP 설정
template:
src: templates/ntp.conf.j2
dest: /etc/ntp.conf
owner: root
mode: '0644'
notify: restart ntp
- name: 배포 사용자 생성
user:
name: "{{ deploy_user }}"
shell: /bin/bash
groups: sudo
create_home: true
- name: SSH 키 등록
authorized_key:
user: "{{ deploy_user }}"
key: "{{ item }}"
state: present
loop: "{{ ssh_allowed_keys }}"
- name: 패스워드 인증 비활성화
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^PasswordAuthentication'
line: 'PasswordAuthentication no'
notify: restart sshd
handlers:
- name: restart ntp
service:
name: ntp
state: restarted
- name: restart sshd
service:
name: sshd
state: restarted
Role: 재사용 가능한 자동화 단위
Role은 태스크, 핸들러, 템플릿, 변수를 하나의 논리적 단위로 패키징한다. Ansible Galaxy에서 커뮤니티 Role을 가져오거나, 자체 Role을 만들어 재사용할 수 있다.
# roles/nginx/ 디렉터리 구조
roles/nginx/
├── tasks/
│ ├── main.yml
│ └── vhost.yml
├── templates/
│ ├── nginx.conf.j2
│ └── vhost.conf.j2
├── handlers/
│ └── main.yml
├── defaults/
│ └── main.yml # 기본 변수 (오버라이드 가능)
└── vars/
└── main.yml # 고정 변수
# roles/nginx/defaults/main.yml
---
nginx_worker_processes: auto
nginx_worker_connections: 1024
nginx_client_max_body_size: 50m
nginx_ssl_protocols: "TLSv1.2 TLSv1.3"
nginx_vhosts: []
# roles/nginx/tasks/main.yml
---
- name: Nginx 설치
apt:
name: nginx
state: present
- name: Nginx 메인 설정
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
owner: root
mode: '0644'
notify: reload nginx
- name: vhost 설정
include_tasks: vhost.yml
loop: "{{ nginx_vhosts }}"
loop_control:
loop_var: vhost
- name: Nginx 시작 및 활성화
service:
name: nginx
state: started
enabled: true
# roles/nginx/handlers/main.yml
---
- name: reload nginx
service:
name: nginx
state: reloaded
- name: restart nginx
service:
name: nginx
state: restarted
Jinja2 템플릿 실전
Ansible의 템플릿 엔진 Jinja2로 설정 파일을 동적으로 생성한다. 조건문, 반복문, 필터를 활용하면 복잡한 설정도 깔끔하게 관리할 수 있다.
# templates/nginx.conf.j2
worker_processes {{ nginx_worker_processes }};
events {
worker_connections {{ nginx_worker_connections }};
}
http {
client_max_body_size {{ nginx_client_max_body_size }};
ssl_protocols {{ nginx_ssl_protocols }};
{% for vhost in nginx_vhosts %}
server {
listen {{ vhost.port | default(80) }};
server_name {{ vhost.server_name }};
{% if vhost.ssl | default(false) %}
listen 443 ssl;
ssl_certificate /etc/ssl/certs/{{ vhost.server_name }}.pem;
ssl_certificate_key /etc/ssl/private/{{ vhost.server_name }}.key;
{% endif %}
{% for location in vhost.locations | default([]) %}
location {{ location.path }} {
proxy_pass http://{{ location.upstream }};
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
{% if location.websocket | default(false) %}
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
{% endif %}
}
{% endfor %}
}
{% endfor %}
}
배포 Playbook: 무중단 배포
# playbooks/deploy.yml — 롤링 배포
---
- name: 애플리케이션 배포
hosts: webservers
become: true
serial: 1 # 한 번에 1대씩 (롤링)
max_fail_percentage: 0 # 1대라도 실패하면 중단
vars:
app_repo: git@github.com:company/myapp.git
app_dir: /opt/{{ app_name }}
app_version: "{{ deploy_version | default('main') }}"
pre_tasks:
- name: 로드밸런서에서 제거
uri:
url: "http://lb.internal/api/deregister"
method: POST
body_format: json
body:
host: "{{ inventory_hostname }}"
delegate_to: localhost
- name: 활성 연결 드레이닝 대기
wait_for:
timeout: 10
tasks:
- name: 코드 배포
git:
repo: "{{ app_repo }}"
dest: "{{ app_dir }}"
version: "{{ app_version }}"
force: true
register: git_result
- name: 의존성 설치
command: npm ci --production
args:
chdir: "{{ app_dir }}"
when: git_result.changed
- name: 환경 변수 설정
template:
src: templates/env.j2
dest: "{{ app_dir }}/.env"
owner: "{{ deploy_user }}"
mode: '0600'
- name: 서비스 재시작
systemd:
name: "{{ app_name }}"
state: restarted
daemon_reload: true
- name: 헬스체크 대기
uri:
url: "http://localhost:{{ app_port }}/health"
status_code: 200
register: health
until: health.status == 200
retries: 30
delay: 2
post_tasks:
- name: 로드밸런서에 등록
uri:
url: "http://lb.internal/api/register"
method: POST
body_format: json
body:
host: "{{ inventory_hostname }}"
delegate_to: localhost
Docker BuildKit 캐시 최적화에서 다룬 Docker 빌드와 Ansible 배포를 결합하면 이미지 빌드부터 서버 배포까지 완전한 CI/CD 파이프라인을 구축할 수 있다.
Vault: 시크릿 관리
Ansible Vault는 비밀번호, API 키 등 민감한 변수를 암호화해서 Git에 안전하게 커밋할 수 있다.
# 파일 암호화
ansible-vault encrypt inventory/production/group_vars/vault.yml
# 암호화된 변수 파일
# inventory/production/group_vars/vault.yml
---
vault_db_password: "super-secret-password"
vault_api_key: "sk-1234567890"
vault_jwt_secret: "32-char-minimum-secret-here..."
# 일반 변수에서 vault 변수 참조
# inventory/production/group_vars/all.yml
---
db_password: "{{ vault_db_password }}"
api_key: "{{ vault_api_key }}"
# 실행 시 vault 비밀번호 입력
ansible-playbook playbooks/deploy.yml --ask-vault-pass
# 또는 비밀번호 파일 사용
ansible-playbook playbooks/deploy.yml --vault-password-file ~/.vault_pass
조건문·반복문·에러 핸들링
# 조건부 실행
- name: Ubuntu에서만 실행
apt:
name: nginx
when: ansible_distribution == "Ubuntu"
# 반복문
- name: 여러 사용자 생성
user:
name: "{{ item.name }}"
groups: "{{ item.groups | join(',') }}"
loop:
- { name: alice, groups: [sudo, docker] }
- { name: bob, groups: [docker] }
# 에러 무시하고 계속 진행
- name: 선택적 서비스 중지
service:
name: old-service
state: stopped
ignore_errors: true
# 블록 기반 에러 핸들링
- name: 배포 with 롤백
block:
- name: 새 버전 배포
command: ./deploy.sh
- name: 마이그레이션 실행
command: npm run migrate
rescue:
- name: 롤백 실행
command: ./rollback.sh
- name: 알림 전송
uri:
url: "{{ slack_webhook }}"
method: POST
body_format: json
body:
text: "배포 실패! 롤백 완료: {{ inventory_hostname }}"
always:
- name: 로그 수집
fetch:
src: /var/log/deploy.log
dest: "./logs/{{ inventory_hostname }}/"
flat: true
Terraform State 관리 심화에서 다룬 인프라 프로비저닝 후 Ansible로 구성 관리를 이어가면 완전한 IaC 파이프라인이 완성된다.
마무리
Ansible Playbook은 SSH 기반의 에이전트리스 구성 관리 표준이다. Role로 재사용 가능한 자동화 단위를 만들고, Jinja2 템플릿으로 동적 설정을 생성하며, Vault로 시크릿을 안전하게 관리한다. serial 옵션의 롤링 배포와 block/rescue 에러 핸들링을 조합하면 프로덕션 무중단 배포까지 안정적으로 자동화할 수 있다.