새벽 2시, 보안팀에서 연락이 왔습니다. 전체 30대 서버에 즉시 취약점 패치를 적용해야 한다는 공지입니다. 터미널 탭을 30개 열고 동일한 명령어를 반복 입력하다 보면 실수가 생기고, 한 대라도 빠트리면 감사에서 걸립니다. 더 나쁜 건, 다음 달에 또 같은 일이 생긴다는 것입니다. Ansible을 알면 이런 상황에서 명령 한 줄로 끝납니다.
Ansible 입문 — 인프라 자동화
서버가 3대를 넘는 순간, 같은 명령어를 여러 터미널에서 반복하는 자신을 발견하게 됩니다. Ansible은 그 반복을 코드로 바꿉니다. "어떤 상태여야 하는가"를 YAML로 정의하면, Ansible이 SSH를 통해 모든 서버를 동시에 그 상태로 만들어줍니다.
shell 스크립트와 Ansible의 차이는 '명령'과 '상태 선언'의 차이입니다. 스크립트는 "이 명령을 실행해라"고 하지만, Ansible은 "이 서버는 이런 상태여야 한다"고 선언합니다.
- 1에이전트리스 아키텍처 — SSH만으로 동작하는 원리
- 2Inventory — 관리할 서버 목록 정의
- 3Ad-hoc 명령 — Playbook 없이 빠른 단발 작업
- 4Playbook — YAML로 서버 상태를 코드로 정의
- 5변수(vars)와 핸들러(handlers) — 조건부 재시작 패턴
- 6멱등성 — 같은 Playbook을 안전하게 반복 실행
에이전트리스 — Ansible이 별도 소프트웨어 없이 동작하는 방식
동작 원리
제어 노드(Control Node)
│
├── SSH 연결
│
└── 대상 서버(Managed Node)
├── Python 모듈 임시 업로드
├── 실행
└── 결과 반환 → 제거
Ansible은 실행 시 Python 모듈 파일을 /tmp에 임시로 복사하고, 실행 후 삭제합니다. 대상 서버에는 SSH 접근 + Python만 있으면 됩니다.
# Ansible 설치 (제어 노드에만)
sudo apt install ansible -y
# 버전 확인
ansible --version
Chef, Puppet과의 차이: Chef/Puppet은 대상 서버에 에이전트 데몬을 상시 설치해야 합니다. Ansible은 SSH 세션만 사용하므로 온보딩 비용이 0에 가깝습니다.
Inventory — 관리할 서버를 그룹으로 정의
# /etc/ansible/hosts (또는 -i 옵션으로 파일 지정)
[webservers]
web01.example.com
web02.example.com
192.168.1.10
[dbservers]
db01.example.com ansible_user=ubuntu ansible_port=2222
[production:children]
webservers
dbservers
[all:vars]
ansible_python_interpreter=/usr/bin/python3
# Inventory 확인
ansible-inventory --list -y
# 특정 그룹의 서버 핑 테스트
ansible webservers -m ping
# 모든 서버 핑
ansible all -m ping
출력 예시:
web01.example.com | SUCCESS => {
"changed": false,
"ping": "pong"
}
- 모든 대상 서버에서 SUCCESS 응답이 돌아왔는가?
- UNREACHABLE 서버가 있다면 SSH 키 인증이 정상적으로 설정됐는지 확인했는가?
- ansible-inventory --list -y 로 그룹 구성이 의도대로 되어 있는가?
Ad-hoc 명령 — Playbook 없이 단발 작업
# 문법: ansible <패턴> -m <모듈> -a "<인수>"
# 모든 서버 업타임 확인
ansible all -m command -a "uptime"
# webservers 그룹에 패키지 설치
ansible webservers -m apt -a "name=nginx state=present" --become
# 서비스 재시작
ansible webservers -m service -a "name=nginx state=restarted" --become
# 파일 복사
ansible dbservers -m copy -a "src=/tmp/my.cnf dest=/etc/mysql/my.cnf"
# 자유 형식 명령 (shell 모듈 — 파이프, 리다이렉션 가능)
ansible all -m shell -a "df -h | grep /dev/sda1"
--become은 sudo 권한으로 실행하는 옵션입니다.
첫 Playbook 작성
Playbook은 YAML 형식으로 "어떤 서버에 어떤 상태를 적용할지"를 정의합니다.
# webserver-setup.yml
---
- name: 웹 서버 초기 설정
hosts: webservers
become: true
vars:
nginx_port: 80
tasks:
- name: nginx 설치
apt:
name: nginx
state: present
update_cache: true
- name: nginx 서비스 활성화 및 시작
service:
name: nginx
state: started
enabled: true
- name: 방화벽에 HTTP 허용
ufw:
rule: allow
port: "{{ nginx_port }}"
proto: tcp
- name: 커스텀 index.html 배포
copy:
content: "<h1>Deployed by Ansible</h1>"
dest: /var/www/html/index.html
owner: www-data
mode: '0644'
notify: reload nginx
handlers:
- name: reload nginx
service:
name: nginx
state: reloaded
# 드라이런 (실제 변경 없이 미리 확인)
ansible-playbook webserver-setup.yml --check
# 실행
ansible-playbook webserver-setup.yml
# 특정 태스크만 실행 (태그 사용)
ansible-playbook webserver-setup.yml --tags "install"
# 실행 결과 예시
PLAY [웹 서버 초기 설정] **************************
TASK [nginx 설치] ********************************
changed: [web01.example.com]
ok: [web02.example.com] ← 이미 설치됨, 변경 없음
PLAY RECAP **************************************
web01 : ok=4 changed=3 unreachable=0 failed=0
web02 : ok=4 changed=1 unreachable=0 failed=0
핵심: changed는 실제로 변경된 태스크, ok는 이미 원하는 상태라 변경 불필요. 이것이 멱등성입니다.
- PLAY RECAP에 failed=0이 표시됐는가?
- 같은 Playbook을 다시 실행했을 때 changed 수가 0으로 줄어드는가? (멱등성 확인)
- 대상 서버에서 nginx가 실제로 실행 중인지 systemctl status nginx로 확인했는가?
핸들러(Handler) — 변경이 있을 때만 재시작
Handler는 notify된 태스크가 changed를 반환할 때만 실행됩니다.
tasks:
- name: nginx 설정 파일 배포
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify: reload nginx # ← 변경 시에만 handler 호출
handlers:
- name: reload nginx
service:
name: nginx
state: reloaded
왜 중요한가: 설정 파일이 바뀌지 않았는데 매번 nginx를 reload하면 불필요한 서비스 중단이 발생합니다. Handler는 "변경이 있을 때만 재시작"을 자동으로 보장합니다.
SSH 키 인증 실패
# 증상
web01 | UNREACHABLE! => {
"msg": "Failed to connect to the host via ssh: Permission denied (publickey,gssapi-keyex,gssapi-with-mic)"
}
# 원인 1: SSH 키가 대상 서버에 등록되지 않음
ssh-copy-id -i ~/.ssh/id_rsa.pub user@web01
# 원인 2: ansible.cfg에 SSH 키 경로 지정 필요
cat ansible.cfg
[defaults]
private_key_file = ~/.ssh/ansible_key
# 원인 3: known_hosts 검사 비활성화 필요 (개발환경)
[defaults]
host_key_checking = False
# 연결 테스트
ansible web01 -m ping -vvv # -v 옵션으로 상세 디버그
대상 서버에 Python이 없는 경우
# Ubuntu 22.04는 python3가 기본, python 심볼릭링크 없음
# Inventory에 명시적으로 python3 지정
[all:vars]
ansible_python_interpreter=/usr/bin/python3
# 또는 대상 서버에 python-is-python3 패키지 설치
ansible all -m raw -a "apt install -y python3" --become
# raw 모듈은 Python 없이도 SSH 명령을 직접 실행
실무에서 Ansible이 쓰이는 순간
10대 서버에 보안 패치 동시 적용:
---
- name: 보안 패치 적용
hosts: all
become: true
serial: 2 # 동시에 2대씩 처리 (롤링 업데이트)
tasks:
- name: apt 업데이트
apt:
upgrade: safe
update_cache: true
- name: 재부팅이 필요한지 확인
stat:
path: /var/run/reboot-required
register: reboot_required
- name: 필요 시 재부팅
reboot:
reboot_timeout: 300
when: reboot_required.stat.exists
팀에서 Ansible을 쓰는 패턴:
Git 저장소 (roles/, playbooks/)
│
├── CI/CD 파이프라인이 git push 시 자동 실행
├── --check로 스테이징 검증
└── 승인 후 프로덕션 적용
신입이 자주 실수하는 것: shell 모듈 남용. apt install nginx를 shell 모듈로 실행하면 멱등성이 깨집니다. 반드시 전용 모듈(apt, yum, service 등)을 사용해야 합니다.