본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
AI Generated
2025. 12. 30. · 37 Views
Ansible Playbook 기초 완벽 가이드
Ansible Playbook의 기본 구조와 핵심 개념을 실무 중심으로 학습합니다. YAML 문법부터 Handler까지, 처음 시작하는 분들도 쉽게 따라할 수 있도록 구성했습니다.
목차
1. YAML 문법 기초
어느 날 김개발 씨가 처음으로 Ansible Playbook을 작성하려고 파일을 열었습니다. 그런데 코드가 이상합니다.
중괄호도 없고, 세미콜론도 없습니다. "이게 대체 무슨 언어죠?" 옆자리 박시니어 씨가 미소를 지으며 말합니다.
"YAML이에요. 배우는 데 10분이면 충분해요."
YAML은 사람이 읽기 쉽도록 설계된 데이터 직렬화 언어입니다. 마치 들여쓰기로 계층을 표현하는 개요서처럼, YAML도 공백으로 구조를 나타냅니다.
Ansible Playbook은 모두 YAML 형식으로 작성되므로, YAML 문법을 이해하는 것이 첫걸음입니다.
다음 코드를 살펴봅시다.
# YAML 기본 문법 예제
---
# 키-값 쌍 (Key-Value)
name: webserver
port: 80
# 리스트 (List)
packages:
- nginx
- python3
- git
# 중첩 구조 (Nested)
server:
host: 192.168.1.10
user: admin
settings:
timeout: 30
retry: 3
김개발 씨는 JSON과 XML만 사용해본 경험이 있었습니다. 중괄호와 꺾쇠 괄호로 가득한 설정 파일들을 보면서 항상 "왜 이렇게 복잡하지?"라고 생각했습니다.
그런데 박시니어 씨가 보여준 YAML 파일은 달랐습니다. 마치 메모장에 메모하듯이 깔끔했습니다.
"YAML은 YAML Ain't Markup Language의 약자예요. 마크업 언어가 아니라는 뜻이죠." 박시니어 씨가 설명을 시작했습니다.
YAML이 무엇인지 이해하려면 먼저 비유를 들어보겠습니다. 책의 목차를 생각해보세요.
1장, 2장이 있고, 그 아래 1.1절, 1.2절이 있습니다. 우리는 들여쓰기만으로도 어떤 절이 어떤 장에 속하는지 한눈에 알 수 있습니다.
YAML도 똑같습니다. 들여쓰기로 데이터의 계층 구조를 표현하는 것입니다.
YAML이 없던 시절에는 어땠을까요? 설정 파일을 작성할 때 XML을 사용했습니다.
태그를 열고 닫아야 했고, 속성을 정의하는 문법도 복잡했습니다. JSON이 나왔을 때는 조금 나아졌지만, 여전히 중괄호와 대괄호, 쉼표를 빠뜨리는 실수가 잦았습니다.
특히 큰 설정 파일을 읽을 때는 눈이 아팠습니다. 바로 이런 문제를 해결하기 위해 YAML이 등장했습니다.
YAML을 사용하면 가독성이 극적으로 좋아집니다. 들여쓰기만으로 구조를 파악할 수 있으니까요.
또한 작성 속도도 빨라집니다. 괄호나 쉼표를 일일이 신경 쓸 필요가 없기 때문입니다.
무엇보다 유지보수가 쉽습니다. 6개월 후에 다시 봐도 무슨 내용인지 바로 이해됩니다.
위의 코드를 한 줄씩 살펴보겠습니다. 먼저 맨 위의 세 개의 대시(---)는 YAML 문서의 시작을 알립니다.
필수는 아니지만, 관례적으로 작성합니다. 다음으로 name: webserver처럼 키와 값을 콜론으로 구분합니다.
콜론 뒤에는 반드시 공백이 하나 있어야 합니다. 리스트는 대시(-)로 표현합니다.
packages: 아래에 들여쓰기를 하고 각 항목 앞에 대시를 붙입니다. 중첩 구조는 더 깊게 들여쓰기하면 됩니다.
server: 아래에 host:와 user:가 있고, settings: 아래에는 또 다른 키-값 쌍이 있습니다. 실제 현업에서는 어떻게 활용할까요?
클라우드 인프라를 관리하는 회사를 예로 들어봅시다. 수백 대의 서버 설정을 관리해야 하는데, YAML로 작성하면 누가 봐도 이해할 수 있는 문서가 됩니다.
신입 사원이 입사해도 30분이면 설정 파일을 읽고 이해할 수 있습니다. 많은 DevOps 도구들이 YAML을 표준으로 채택한 이유입니다.
하지만 주의할 점도 있습니다. 초보자들이 가장 많이 하는 실수는 들여쓰기를 탭과 스페이스를 섞어서 사용하는 것입니다.
YAML은 스페이스만 인정합니다. 탭을 사용하면 파싱 에러가 발생합니다.
또한 들여쓰기 칸수가 일정하지 않아도 에러가 납니다. 따라서 에디터 설정에서 탭을 스페이스 2칸으로 변환하도록 설정하는 것이 좋습니다.
다시 김개발 씨의 이야기로 돌아가 봅시다. 박시니어 씨의 설명을 들은 김개발 씨는 직접 YAML을 작성해보았습니다.
10분 후, "생각보다 훨씬 쉽네요!"라고 외쳤습니다. YAML 문법을 제대로 이해하면 Ansible뿐만 아니라 Docker Compose, Kubernetes, CI/CD 파이프라인 등 다양한 도구를 쉽게 다룰 수 있습니다.
여러분도 오늘 배운 내용을 실제로 타이핑해보세요.
실전 팁
💡 - 들여쓰기는 항상 스페이스 2칸을 사용하세요. 탭은 절대 금지입니다.
- VSCode나 Vim에 YAML 플러그인을 설치하면 문법 오류를 실시간으로 확인할 수 있습니다.
- 복잡한 구조는 yamllint 도구로 검증하면 실수를 줄일 수 있습니다.
2. Playbook 기본 구조
김개발 씨가 YAML 문법을 익히고 나서 드디어 첫 번째 Playbook을 작성하려고 합니다. 빈 파일을 열고 "자, 이제 뭘 써야 하지?"라고 고민하는 순간, 박시니어 씨가 화면을 가리키며 말합니다.
"Playbook에는 정해진 뼈대가 있어요. 이 구조만 기억하면 됩니다."
Playbook은 Ansible의 핵심 설정 파일로, 어떤 서버에 무엇을 할 것인지 정의합니다. 마치 연극 대본처럼 Play, Task, Module이라는 계층 구조로 이루어져 있습니다.
하나의 Playbook에는 여러 개의 Play가 있을 수 있고, 각 Play는 특정 서버 그룹에 대한 작업을 담당합니다.
다음 코드를 살펴봅시다.
---
# webserver.yml - 웹서버 설정 Playbook
- name: Configure web servers
hosts: webservers
become: yes
vars:
http_port: 80
doc_root: /var/www/html
tasks:
- name: Install nginx
apt:
name: nginx
state: present
- name: Start nginx service
service:
name: nginx
state: started
김개발 씨는 처음 Playbook이라는 단어를 들었을 때 "왜 이런 이름이 붙었을까?"하고 궁금해했습니다. 박시니어 씨가 웃으며 설명했습니다.
"연극 대본을 생각해보세요. 어떤 배우가 무슨 대사를 하는지 적혀있잖아요.
Playbook도 똑같아요." Playbook의 구조를 이해하려면 연극에 비유해봅시다. 연극에는 여러 막(Act)이 있고, 각 막에는 배우들이 등장해서 대사를 합니다.
Playbook도 마찬가지입니다. Play는 연극의 한 막과 같고, hosts는 배우(서버), tasks는 대사(실행할 명령)입니다.
이렇게 생각하면 구조가 머릿속에 쏙 들어옵니다. Playbook이 없던 시절에는 어땠을까요?
시스템 관리자들은 서버마다 SSH로 접속해서 명령어를 일일이 입력했습니다. 서버가 10대면 10번, 100대면 100번 반복해야 했습니다.
실수라도 하면 일부 서버는 설정이 다르게 되어버렸습니다. 표준화도 어렵고, 누가 언제 무엇을 했는지 추적하기도 힘들었습니다.
바로 이런 문제를 해결하기 위해 Playbook이 등장했습니다. Playbook을 사용하면 재현성이 보장됩니다.
한 번 작성한 Playbook을 몇 번이고 실행할 수 있으니까요. 또한 문서화도 자동으로 됩니다.
Playbook 자체가 "무엇을 했는지"에 대한 기록이기 때문입니다. 무엇보다 확장성이 뛰어납니다.
서버가 1000대로 늘어나도 실행 명령은 똑같습니다. 위의 코드를 한 줄씩 살펴보겠습니다.
먼저 - name:은 이 Play의 이름입니다. 실행할 때 화면에 표시되므로 알아보기 쉽게 작성합니다.
hosts: webservers는 이 Play를 실행할 대상 서버 그룹입니다. inventory 파일에 정의된 webservers 그룹의 모든 서버에 적용됩니다.
become: yes는 sudo 권한으로 실행하라는 의미입니다. 패키지 설치 같은 관리자 작업에 필요합니다.
vars: 섹션에는 이 Play에서 사용할 변수를 정의합니다. 나중에 {{ http_port }}처럼 참조할 수 있습니다.
tasks: 아래에는 실제로 수행할 작업들을 나열합니다. 각 Task는 - name:으로 시작하고, 그 아래에 실행할 모듈을 지정합니다.
위에서 아래로 순서대로 실행됩니다. 실제 현업에서는 어떻게 활용할까요?
전자상거래 회사를 예로 들어봅시다. 블랙프라이데이를 앞두고 웹서버 50대를 급하게 추가해야 하는 상황입니다.
Playbook이 있다면 inventory 파일에 새 서버 IP만 추가하고 ansible-playbook webserver.yml을 실행하면 끝입니다. 30분 안에 모든 서버가 똑같이 설정됩니다.
수동으로 했다면 하루는 걸렸을 일입니다. 하지만 주의할 점도 있습니다.
초보자들이 흔히 하는 실수는 하나의 Playbook에 너무 많은 것을 담는 것입니다. Playbook은 역할별로 분리하는 것이 좋습니다.
웹서버는 webserver.yml, 데이터베이스는 database.yml처럼 말이죠. 또한 hosts를 잘못 지정하면 엉뚱한 서버에 작업이 실행될 수 있으므로 항상 확인해야 합니다.
다시 김개발 씨의 이야기로 돌아가 봅시다. 박시니어 씨가 준 템플릿을 바탕으로 김개발 씨는 첫 번째 Playbook을 완성했습니다.
"구조만 알면 생각보다 간단하네요!" Playbook의 기본 구조를 제대로 이해하면 복잡한 인프라도 코드로 관리할 수 있습니다. 여러분도 간단한 Playbook부터 직접 작성해보세요.
실전 팁
💡 - Playbook 파일명은 용도를 명확히 드러내도록 짓습니다. (예: setup-webserver.yml)
- become은 필요한 작업에만 사용하세요. 보안상 최소 권한 원칙을 지킵니다.
- 실행 전 --check 옵션으로 드라이런(시뮬레이션)을 해보면 안전합니다.
3. Play와 Task 정의
김개발 씨가 Playbook 구조를 익히고 나니 이제 궁금한 게 생겼습니다. "Play와 Task의 차이가 정확히 뭐죠?
그리고 Task는 몇 개까지 만들 수 있나요?" 박시니어 씨가 화이트보드를 꺼내 들며 답했습니다. "좋은 질문이에요.
지금부터 확실히 짚고 넘어갑시다."
Play는 특정 서버 그룹에 대한 작업 단위이고, Task는 그 안에서 실행되는 개별 명령입니다. 하나의 Play에는 여러 개의 Task가 순차적으로 실행됩니다.
마치 요리 레시피에서 "파스타 만들기"가 Play라면, "물 끓이기", "면 삶기", "소스 만들기"가 각각의 Task인 것과 같습니다.
다음 코드를 살펴봅시다.
---
# multi-play.yml - 여러 Play가 있는 Playbook
- name: Setup database servers
hosts: databases
tasks:
- name: Install PostgreSQL
apt:
name: postgresql
state: present
- name: Configure PostgreSQL
template:
src: postgresql.conf.j2
dest: /etc/postgresql/postgresql.conf
- name: Setup web servers
hosts: webservers
tasks:
- name: Install nginx
apt:
name: nginx
state: present
- name: Copy nginx config
copy:
src: files/nginx.conf
dest: /etc/nginx/nginx.conf
김개발 씨는 처음에 Play와 Task를 헷갈려했습니다. "둘 다 작업을 하는 건데, 왜 이름을 다르게 부르지?" 박시니어 씨가 화이트보드에 그림을 그리기 시작했습니다.
Play와 Task의 관계를 이해하려면 프로젝트 관리에 비유해봅시다. 큰 프로젝트를 진행할 때 우리는 단계별로 나눕니다.
"요구사항 분석", "설계", "개발", "테스트"처럼 말이죠. 각 단계가 바로 Play입니다.
그리고 "요구사항 분석" 단계 안에는 "고객 인터뷰", "문서 작성", "검토 회의" 같은 세부 작업이 있습니다. 이것이 Task입니다.
Play와 Task를 구분하지 않던 시절에는 어땠을까요? 모든 작업을 한 덩어리로 작성했습니다.
데이터베이스 설정도 하고, 웹서버 설정도 하고, 모니터링 도구도 설치하고... 하나의 거대한 스크립트였습니다.
문제가 생기면 어디서 실패했는지 찾기 어려웠습니다. 일부만 다시 실행하고 싶어도 전체를 돌려야 했습니다.
바로 이런 문제를 해결하기 위해 Play와 Task로 분리하게 되었습니다. Play로 분리하면 서버 역할별로 관리할 수 있습니다.
데이터베이스 서버는 따로, 웹서버는 따로 작업할 수 있으니까요. Task로 나누면 단계별 실행이 가능합니다.
특정 Task만 건너뛰거나 다시 실행할 수도 있습니다. 무엇보다 가독성이 좋아집니다.
누가 봐도 "아, 이 부분은 데이터베이스 설정이구나"라고 이해할 수 있습니다. 위의 코드를 한 줄씩 살펴보겠습니다.
첫 번째 Play는 hosts: databases로 시작합니다. 데이터베이스 서버들에 대한 작업입니다.
이 Play에는 2개의 Task가 있습니다. PostgreSQL을 설치하고, 설정 파일을 배포하는 작업입니다.
두 번째 Play는 hosts: webservers로 시작합니다. 웹서버들에 대한 작업입니다.
역시 2개의 Task가 있습니다. nginx를 설치하고, 설정 파일을 복사합니다.
중요한 점은 Play는 독립적으로 실행된다는 것입니다. 첫 번째 Play가 끝나면 두 번째 Play가 시작됩니다.
하지만 각 Play 내의 Task들은 순서대로 실행됩니다. 실제 현업에서는 어떻게 활용할까요?
마이크로서비스 아키텍처를 운영하는 회사를 예로 들어봅시다. API 서버, 프론트엔드 서버, 캐시 서버, 메시지 큐 서버가 각각 다른 역할을 합니다.
하나의 Playbook에 4개의 Play를 만들어서 한 번에 전체 인프라를 구축할 수 있습니다. 각 Play는 독립적이므로 나중에 API 서버만 업데이트하고 싶으면 해당 Play만 실행하면 됩니다.
하지만 주의할 점도 있습니다. 초보자들이 흔히 하는 실수는 Task 이름을 대충 짓는 것입니다.
"task1", "task2"처럼 이름을 붙이면 나중에 로그를 봐도 무슨 작업인지 알 수 없습니다. "Install nginx web server", "Copy application config file"처럼 명확하게 작성해야 합니다.
또한 Task 순서도 중요합니다. 패키지를 설치하기 전에 설정 파일을 복사하면 에러가 납니다.
다시 김개발 씨의 이야기로 돌아가 봅시다. 박시니어 씨의 설명을 듣고 김개발 씨는 화이트보드를 사진으로 찍었습니다.
"이제 확실히 이해했어요!" Play와 Task를 적절히 나누면 유지보수하기 쉬운 Playbook을 작성할 수 있습니다. 여러분도 역할별로 Play를 나누고, 단계별로 Task를 정의해보세요.
실전 팁
💡 - Play 이름은 "Setup", "Configure", "Deploy" 같은 동사로 시작하면 명확합니다.
- Task 이름은 실행 결과를 보고할 때 그대로 출력되므로 신중하게 작성하세요.
- 하나의 Task는 한 가지 일만 하도록 만드세요. (단일 책임 원칙)
4. 모듈 사용하기
김개발 씨가 Task를 작성하려고 하는데 막막했습니다. "파일을 복사하고 싶은데, 어떤 명령을 써야 하죠?" 박시니어 씨가 브라우저를 열어 Ansible 문서를 보여주며 말했습니다.
"여기 수천 개의 모듈이 있어요. 웬만한 작업은 다 있습니다."
모듈은 Ansible에서 실제 작업을 수행하는 재사용 가능한 코드 단위입니다. 파일 복사, 패키지 설치, 서비스 시작 등 각각의 작업마다 전용 모듈이 존재합니다.
마치 레고 블록처럼 필요한 모듈을 조합해서 원하는 작업을 만들어냅니다. Ansible은 수천 개의 내장 모듈을 제공하며, 커뮤니티에서 만든 모듈도 사용할 수 있습니다.
다음 코드를 살펴봅시다.
---
# modules-example.yml - 다양한 모듈 사용 예제
- name: Common module examples
hosts: servers
tasks:
# apt 모듈 - 패키지 관리
- name: Install multiple packages
apt:
name:
- git
- curl
- vim
state: present
update_cache: yes
# copy 모듈 - 파일 복사
- name: Copy application config
copy:
src: /local/path/app.conf
dest: /etc/app/app.conf
owner: www-data
mode: '0644'
# service 모듈 - 서비스 관리
- name: Ensure nginx is running
service:
name: nginx
state: started
enabled: yes
# user 모듈 - 사용자 관리
- name: Create deploy user
user:
name: deploy
shell: /bin/bash
groups: sudo
append: yes
김개발 씨는 처음에 모든 작업을 쉘 스크립트로 하려고 했습니다. cp 명령으로 파일을 복사하고, apt-get으로 패키지를 설치하려고 했죠.
박시니어 씨가 손을 저으며 말했습니다. "그렇게 하면 Ansible을 쓰는 의미가 없어요." 모듈의 개념을 이해하려면 도구 상자에 비유해봅시다.
집에서 수리를 할 때 우리는 전용 도구를 사용합니다. 나사를 조이려면 드라이버, 못을 박으려면 망치를 쓰죠.
손으로 직접 하려고 하지 않습니다. Ansible 모듈도 마찬가지입니다.
파일을 복사하려면 copy 모듈, 패키지를 설치하려면 apt 모듈을 사용합니다. 각 모듈은 그 작업에 최적화되어 있습니다.
모듈이 없던 시절에는 어땠을까요? 모든 작업을 shell이나 command 모듈로 직접 명령어를 실행했습니다.
apt-get install nginx를 그대로 작성했죠. 문제는 멱등성이 보장되지 않는다는 것입니다.
같은 명령을 두 번 실행하면 에러가 나거나 예상치 못한 결과가 나올 수 있습니다. 또한 OS마다 명령어가 달라서 Ubuntu와 CentOS에서 각각 다른 스크립트를 유지해야 했습니다.
바로 이런 문제를 해결하기 위해 모듈이 탄생했습니다. 모듈을 사용하면 멱등성이 자동으로 보장됩니다.
nginx가 이미 설치되어 있으면 다시 설치하지 않습니다. 또한 플랫폼 독립성도 얻을 수 있습니다.
apt 모듈은 Ubuntu에서는 apt-get을, CentOS에서는 yum을 알아서 사용합니다. 무엇보다 에러 처리가 자동화됩니다.
파일 복사가 실패하면 적절한 에러 메시지를 출력하고 중단됩니다. 위의 코드를 한 줄씩 살펴보겠습니다.
첫 번째 Task는 apt 모듈을 사용합니다. name: 아래에 리스트로 여러 패키지를 지정할 수 있습니다.
state: present는 "설치되어 있어야 한다"는 의미입니다. update_cache: yes는 apt-get update를 먼저 실행하라는 뜻입니다.
두 번째 Task는 copy 모듈입니다. src는 Ansible을 실행하는 컴퓨터의 경로, dest는 대상 서버의 경로입니다.
owner와 mode로 파일 권한도 함께 설정합니다. 세 번째 Task는 service 모듈입니다.
state: started는 "실행 중이어야 한다"는 의미입니다. 이미 실행 중이면 아무것도 하지 않습니다.
enabled: yes는 부팅 시 자동 시작되도록 설정합니다. 네 번째 Task는 user 모듈입니다.
deploy라는 사용자를 생성하고, sudo 그룹에 추가합니다. append: yes는 기존 그룹은 유지하고 sudo만 추가하라는 의미입니다.
실제 현업에서는 어떻게 활용할까요? 스타트업에서 새로운 개발 환경을 구축한다고 가정해봅시다.
개발자마다 동일한 환경이 필요합니다. Playbook에 user 모듈로 계정을 만들고, apt 모듈로 필요한 도구를 설치하고, copy 모듈로 설정 파일을 배포하면 됩니다.
신입 개발자가 입사하면 10분 만에 완전히 동일한 개발 환경을 제공할 수 있습니다. 하지만 주의할 점도 있습니다.
초보자들이 흔히 하는 실수는 shell 모듈을 남용하는 것입니다. 전용 모듈이 있는데도 익숙한 쉘 명령어를 쓰려고 합니다.
이러면 멱등성이 깨지고, 에러 처리도 직접 해야 합니다. 또한 모듈 문서를 읽지 않고 추측으로 작성하면 원하지 않는 결과가 나올 수 있습니다.
모듈마다 미묘하게 다른 옵션이 있으므로 공식 문서를 확인하는 습관이 중요합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.
박시니어 씨가 보여준 예제를 따라 작성한 김개발 씨는 감탄했습니다. "이렇게 많은 모듈이 있다니, 거의 모든 작업을 할 수 있겠네요!" 모듈을 제대로 활용하면 복잡한 인프라 작업도 간결한 코드로 표현할 수 있습니다.
여러분도 자주 쓰는 모듈부터 익혀보세요.
실전 팁
💡 - ansible-doc 명령으로 터미널에서 모듈 문서를 바로 볼 수 있습니다. (예: ansible-doc copy)
- shell이나 command 모듈은 정말 대안이 없을 때만 사용하세요.
- 모듈의 changed 상태를 확인하면 실제로 변경이 일어났는지 알 수 있습니다.
5. 핸들러(Handler) 개념
김개발 씨가 nginx 설정 파일을 수정하는 Playbook을 작성했습니다. 실행 후 nginx를 재시작해야 하는데, 매번 재시작하면 비효율적입니다.
"설정이 실제로 바뀌었을 때만 재시작할 수는 없나요?" 박시니어 씨가 고개를 끄덕이며 답했습니다. "바로 그럴 때 Handler를 쓰는 겁니다."
Handler는 특정 Task가 변경사항을 만들었을 때만 실행되는 특별한 Task입니다. 주로 서비스 재시작이나 설정 리로드처럼 "변경이 있을 때만 해야 하는 작업"에 사용됩니다.
마치 문서 편집기의 "저장되지 않은 변경사항이 있을 때만 저장 알림"처럼 동작합니다. Handler는 모든 Task가 끝난 후에 한 번만 실행됩니다.
다음 코드를 살펴봅시다.
---
# handlers-example.yml - Handler 사용 예제
- name: Configure nginx with handlers
hosts: webservers
become: yes
tasks:
- name: Install nginx
apt:
name: nginx
state: present
- name: Copy nginx configuration
copy:
src: files/nginx.conf
dest: /etc/nginx/nginx.conf
validate: nginx -t -c %s
notify: Restart nginx
- name: Copy site configuration
template:
src: templates/site.conf.j2
dest: /etc/nginx/sites-available/mysite
notify:
- Restart nginx
- Send notification
handlers:
- name: Restart nginx
service:
name: nginx
state: restarted
- name: Send notification
debug:
msg: "Nginx configuration has been updated and restarted"
김개발 씨는 처음에 nginx를 재시작하는 Task를 맨 마지막에 추가했습니다. 그런데 문제가 있었습니다.
설정 파일이 바뀌지 않았는데도 매번 재시작되는 것입니다. "이건 좀 비효율적인데요?" 박시니어 씨가 웃으며 Handler를 소개했습니다.
Handler의 개념을 이해하려면 자동차 경고등에 비유해봅시다. 자동차는 엔진 오일이 부족할 때만 경고등이 켜집니다.
오일이 충분하면 아무 일도 일어나지 않죠. Handler도 똑같습니다.
Task가 실제로 **변경(changed)**을 만들었을 때만 실행됩니다. 변경이 없으면 Handler는 건너뜁니다.
Handler가 없던 시절에는 어떻게 했을까요? 모든 Task 끝에 재시작 Task를 추가했습니다.
설정 파일이 바뀌든 안 바뀌든 무조건 재시작했죠. 서비스 재시작은 다운타임을 유발하므로 불필요한 재시작은 피해야 합니다.
또한 여러 설정 파일을 수정할 때 각각 재시작하면 서비스가 여러 번 내려갔다 올라갑니다. 이것도 문제였습니다.
바로 이런 문제를 해결하기 위해 Handler가 도입되었습니다. Handler를 사용하면 조건부 실행이 가능합니다.
변경이 있을 때만 동작하니까요. 또한 중복 실행 방지도 됩니다.
여러 Task가 같은 Handler를 notify해도 Handler는 마지막에 딱 한 번만 실행됩니다. 무엇보다 의도 명확화가 됩니다.
"이 작업은 변경이 있을 때만 하는 거구나"라고 바로 이해할 수 있습니다. 위의 코드를 한 줄씩 살펴보겠습니다.
두 번째 Task에서 notify: Restart nginx를 주목하세요. 이 Task가 파일을 실제로 복사했을 때(즉, 파일 내용이 달라서 변경이 발생했을 때)만 Handler를 호출합니다.
파일이 이미 같으면 Handler는 호출되지 않습니다. 세 번째 Task는 여러 Handler를 notify합니다.
리스트로 작성하면 됩니다. 템플릿이 변경되면 nginx를 재시작하고, 알림도 보냅니다.
handlers: 섹션은 tasks와 같은 레벨에 작성합니다. Handler도 Task와 똑같이 name과 모듈을 가집니다.
차이점은 자동으로 실행되지 않는다는 것입니다. notify를 받았을 때만 실행됩니다.
중요한 점은 Handler는 모든 Task가 끝난 후에 실행된다는 것입니다. Task 중간에 notify가 있어도 즉시 실행되지 않습니다.
Playbook의 맨 마지막에 한 번에 실행됩니다. 실제 현업에서는 어떻게 활용할까요?
대규모 웹 서비스를 운영하는 팀을 생각해봅시다. 설정 파일 배포 Playbook을 매일 실행합니다.
대부분은 변경사항이 없습니다. Handler 덕분에 변경이 실제로 있는 날만 서비스가 재시작됩니다.
불필요한 다운타임을 피할 수 있고, 로그에도 "언제 실제로 변경이 있었는지" 명확히 남습니다. 하지만 주의할 점도 있습니다.
초보자들이 흔히 하는 실수는 Handler 이름을 잘못 쓰는 것입니다. notify에 쓴 이름과 handlers의 name이 정확히 일치해야 합니다.
대소문자도 구분됩니다. 또한 Task가 실패하면 Handler는 실행되지 않습니다.
중간에 에러가 나면 notify를 했어도 Handler는 호출되지 않으니 주의해야 합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.
Handler를 적용한 Playbook을 실행한 김개발 씨는 신기해했습니다. "첫 번째 실행에서는 재시작이 되는데, 두 번째 실행에서는 안 되네요.
완벽해요!" Handler를 제대로 활용하면 효율적이고 안전한 배포 자동화를 만들 수 있습니다. 여러분도 변경이 있을 때만 해야 하는 작업에는 Handler를 사용해보세요.
실전 팁
💡 - Handler 이름은 명확하고 일관되게 짓습니다. (예: "Restart nginx", "Reload apache")
- 여러 Task가 같은 Handler를 notify해도 Handler는 한 번만 실행됩니다.
- --force-handlers 옵션을 사용하면 Task 실패 시에도 Handler를 강제 실행할 수 있습니다.
6. 첫 번째 Playbook 작성 및 실행
드디어 김개발 씨가 모든 개념을 배우고 첫 번째 Playbook을 처음부터 끝까지 직접 작성할 시간이 왔습니다. "조금 떨리는데요?" 박시니어 씨가 격려했습니다.
"지금까지 배운 걸 다 넣어보세요. 간단한 웹서버 구축부터 시작합시다."
실전 Playbook 작성은 배운 모든 요소를 종합하는 과정입니다. YAML 문법으로 구조를 잡고, Play와 Task를 정의하고, 적절한 모듈을 선택하고, Handler로 마무리합니다.
마치 요리 레시피를 보고 실제로 음식을 만드는 것처럼, 이제는 직접 인프라를 코드로 구축할 차례입니다.
다음 코드를 살펴봅시다.
---
# first-playbook.yml - 첫 번째 완전한 Playbook
- name: Setup web server with basic security
hosts: webservers
become: yes
vars:
app_user: webapp
app_port: 8080
tasks:
- name: Update apt cache
apt:
update_cache: yes
cache_valid_time: 3600
- name: Install required packages
apt:
name:
- nginx
- ufw
- python3-pip
state: present
- name: Create application user
user:
name: "{{ app_user }}"
shell: /bin/bash
create_home: yes
- name: Copy nginx configuration
template:
src: templates/nginx.conf.j2
dest: /etc/nginx/sites-available/default
validate: nginx -t -c %s
notify: Restart nginx
- name: Enable UFW firewall
ufw:
state: enabled
policy: deny
- name: Allow SSH and HTTP
ufw:
rule: allow
port: "{{ item }}"
loop:
- '22'
- '80'
- '443'
- name: Ensure nginx is running
service:
name: nginx
state: started
enabled: yes
handlers:
- name: Restart nginx
service:
name: nginx
state: restarted
notify: Check nginx status
- name: Check nginx status
command: systemctl status nginx
register: nginx_status
changed_when: false
김개발 씨는 빈 파일을 열고 심호흡을 했습니다. "자, 이제 진짜 시작이다." 박시니어 씨가 옆에서 지켜보며 힌트를 주기 시작했습니다.
"먼저 무엇을 할 건지 목록을 적어보세요." 실전 Playbook 작성을 계획에 비유해봅시다. 여행을 떠나기 전에 우리는 체크리스트를 만듭니다.
"비행기 예약", "호텔 예약", "짐 싸기", "환전하기" 같은 항목들이죠. Playbook도 마찬가지입니다.
먼저 해야 할 일의 목록을 정리합니다. 그 다음 각 항목을 어떤 모듈로 구현할지 결정합니다.
마지막으로 순서를 정합니다. 계획 없이 Playbook을 작성하면 어떻게 될까요?
Task를 생각나는 대로 추가하다 보면 순서가 꼬입니다. nginx 설정 파일을 nginx 설치 전에 복사하려고 하는 실수를 하죠.
변수를 일관성 없이 사용해서 나중에 수정하기 어려워집니다. 무엇보다 빠진 부분을 나중에 발견하게 됩니다.
바로 이런 문제를 피하기 위해 체계적으로 작성해야 합니다. 먼저 변수를 정의합니다.
여러 곳에서 반복되는 값(포트 번호, 사용자 이름 등)은 vars에 모읍니다. 다음으로 의존성 순서를 고려합니다.
패키지를 먼저 설치하고, 사용자를 만들고, 설정 파일을 배포합니다. 마지막으로 멱등성을 확인합니다.
여러 번 실행해도 같은 결과가 나오는지 점검합니다. 위의 코드를 섹션별로 살펴보겠습니다.
vars 섹션에서 app_user와 app_port를 정의했습니다. 나중에 {{ app_user }}처럼 참조할 수 있습니다.
포트를 바꾸고 싶으면 이 한 곳만 수정하면 됩니다. 첫 번째 Task는 apt 캐시를 업데이트합니다.
cache_valid_time: 3600은 "1시간 이내에 업데이트했으면 건너뛰기"라는 의미입니다. 불필요한 네트워크 요청을 줄입니다.
두 번째 Task는 필요한 패키지를 한꺼번에 설치합니다. 리스트로 작성하면 깔끔합니다.
세 번째 Task는 애플리케이션 전용 사용자를 생성합니다. 보안상 root로 직접 실행하지 않기 위함입니다.
네 번째 Task는 template 모듈을 사용합니다. .j2 파일은 Jinja2 템플릿으로, 변수를 치환할 수 있습니다.
validate 옵션은 설정 파일 문법을 미리 검증합니다. 잘못된 설정이면 배포 자체가 실패합니다.
다섯 번째와 여섯 번째 Task는 방화벽을 설정합니다. loop를 사용해서 여러 포트를 한 번에 허용합니다.
{{ item }}에 리스트의 각 값이 들어갑니다. 마지막 Task는 nginx가 실행 중이고 부팅 시 자동 시작되도록 보장합니다.
Handler 섹션에서는 nginx 재시작 후 상태를 확인하는 Handler를 연결했습니다. register로 명령 결과를 저장하고, changed_when: false로 이 Task는 "변경"으로 카운트하지 않도록 했습니다.
실제 현업에서는 어떻게 실행할까요? 먼저 inventory 파일을 준비합니다.
webservers 그룹에 실제 서버 IP를 추가합니다. 다음으로 템플릿 파일(templates/nginx.conf.j2)을 작성합니다.
준비가 끝나면 이렇게 실행합니다. ansible-playbook -i inventory first-playbook.yml 처음에는 --check 옵션으로 드라이런을 해봅니다.
실제로 변경하지 않고 시뮬레이션만 합니다. 문제가 없으면 --check를 빼고 실제로 실행합니다.
하지만 주의할 점도 있습니다. 초보자들이 흔히 하는 실수는 한 번에 모든 걸 작성하려는 것입니다.
Task를 하나씩 추가하면서 실행해보세요. 각 단계가 제대로 동작하는지 확인한 후 다음 Task를 추가합니다.
또한 에러 메시지를 잘 읽어야 합니다. Ansible은 어디서 무엇이 잘못됐는지 친절하게 알려줍니다.
다시 김개발 씨의 이야기로 돌아가 봅시다. 첫 번째 Playbook을 실행한 김개발 씨는 화면에 초록색 "ok"와 노란색 "changed"가 출력되는 것을 보며 환호했습니다.
"성공했어요!" 첫 번째 Playbook을 완성하면 자신감이 생깁니다. 이제 여러분도 더 복잡한 인프라를 자동화할 준비가 되었습니다.
작은 것부터 시작해서 점진적으로 확장해보세요.
실전 팁
💡 - 처음에는 --check와 --diff 옵션으로 무엇이 바뀔지 미리 확인하세요.
- -v, -vv, -vvv 옵션으로 상세도를 조절할 수 있습니다. 디버깅 시 유용합니다.
- Playbook을 Git으로 관리하면 변경 이력을 추적하고 협업할 수 있습니다.
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
실전 인프라 자동화 프로젝트 완벽 가이드
Ansible을 활용하여 멀티 티어 웹 애플리케이션 인프라를 자동으로 구축하는 실전 프로젝트입니다. 웹 서버, 데이터베이스, 로드 밸런서를 코드로 관리하며 반복 가능한 인프라 배포를 경험합니다.
CI/CD 파이프라인 통합 완벽 가이드
Jenkins, GitLab CI와 Ansible을 연동하여 자동화된 배포 파이프라인을 구축하는 방법을 다룹니다. Ansible Tower/AWX의 활용법과 실무에서 바로 적용 가능한 워크플로우 설계 패턴을 단계별로 설명합니다.
Ansible 성능 최적화와 디버깅 완벽 가이드
Ansible 플레이북의 실행 속도를 극적으로 향상시키고, 문제 발생 시 효과적으로 디버깅하는 방법을 다룹니다. 병렬 실행, 캐싱, SSH 최적화부터 디버그 모드와 프로파일링까지 실무에서 바로 적용할 수 있는 기법들을 소개합니다.
Vault를 통한 시크릿 관리 완벽 가이드
Ansible Vault를 활용한 시크릿 관리의 모든 것을 다룹니다. 민감한 정보를 안전하게 암호화하고, CI/CD 파이프라인에서 효과적으로 활용하는 방법까지 실무 중심으로 설명합니다.
Ansible Galaxy와 컬렉션 완벽 가이드
Ansible Galaxy를 통해 커뮤니티가 만든 Role과 컬렉션을 검색하고 설치하는 방법을 배웁니다. requirements.yml 작성법부터 자체 컬렉션 생성까지, 인프라 자동화의 생산성을 높이는 핵심 기술을 다룹니다.