들어가며
1주차에서는 kubeadm/kubespray 같은 자동화 도구를 사용하지 않고, Kubernetes The Hard Way 방식으로 구성 요소를 직접 설치 및 연결하면서 쿠버네티스가 어떻게 부팅되고 동작하는지 큰 흐름을 잡았습니다.
이번 2주차에서는 그 흐름을 확장하여, 반복되는 운영 작업을 코드로 표준화하고 재현 가능하게 만드는 자동화 도구인 Ansible을 학습합니다.
Ansible은 에이전트를 설치하지 않는(Agentless) 방식으로, 주로 SSH를 통해 원격 호스트에 접속하여 원하는 상태를 만들고 그 과정을 플레이북(Playbook) 으로 기록합니다.
즉, 서버 한 대에 수동으로 명령어를 입력하는 방식이 아니라, “어떤 호스트들이 어떤 상태가 되어야 하는지”를 선언하고 실행하여 일관성을 확보하는 방식입니다.
이번 주차의 목표는 다음과 같습니다.
- Ansible의 기본 개념과 문법을 실습을 통해 익힙니다. (인벤토리, 플레이북, 변수, Facts, 반복문/조건문, 핸들러 등)
- 현업에서 많이 사용하는 구조로 확장합니다. (Role, Tags, Galaxy, Collection)
Ansible 기초 전체 흐름 살펴보기
이제 2주차에서 다룰 Ansible 기초 학습 흐름이 어떤 단계로 구성되어 있는지 전체 흐름을 먼저 정리하고 넘어가겠습니다.
이번 주차는 Ansible이 어떤 순서로 자동화를 확장해 나가는지를 따라가며 이해하는 방식입니다.
처음에는 한 대에 명령을 던지는 수준(ad-hoc)에서 시작해서, 점점 플레이북으로 구조화하고, 변수/팩트로 유연성을 확보하고, 반복/조건으로 분기를 만들고, 마지막에는 롤/태그/갤럭시/컬렉션으로 재사용과 확장까지 가는 흐름입니다.
실습 환경과 프로젝트 구성
가장 먼저 Ansible을 “실행할 자리”와 “대상 서버”를 분리해서 구성합니다.
- 제어 노드(Ansible 실행 서버)와 관리 호스트(대상 노드들)를 준비합니다.
- 이후 모든 실습을 한 디렉터리(예: ~/my-ansible)에서 진행하면서,
- inventory 로 대상 그룹을 정의하고
- ansible.cfg 로 기본 동작(원격 사용자, privilege escalation 등)을 고정합니다.
즉, 이 단계의 핵심은 “Ansible을 쓰기 좋은 형태로 환경을 고정”해두는 것입니다.
뒤 단계 플레이북/롤 실습도 결국 이 inventory + ansible.cfg를 계속 재사용합니다.
Ad-hoc 실행과 인벤토리 점검
환경이 준비되면, 플레이북보다 먼저 ad-hoc 명령으로 감을 잡습니다.
- ansible -m ping web 처럼 “대상 그룹에 연결이 되는지” 확인하고,
- ansible -m shell -a "uptime" all 처럼 “원격에서 명령이 실행되고 결과가 돌아오는지” 확인합니다.
이 단계에서 확인하는 건 두 가지입니다.
- 인벤토리가 제대로 대상(host/group)을 잡고 있는지
- SSH/원격 사용자/권한 상승 설정이 의도대로 동작하는지
여기서 문제가 없으면, 이제 “명령 한 줄 자동화”를 넘어 플레이북으로 작업을 정리할 준비가 된 상태입니다.
플레이북으로 작업 구조화
이제부터는 ad-hoc처럼 명령을 흩뿌리는 방식이 아니라, **플레이북(YAML)**으로 작업을 “문서+자동화” 형태로 고정합니다.
- 가장 단순한 형태로는 debug로 메시지를 출력하며 구조를 익히고,
- 실제 관리 작업으로는 service 모듈을 이용해 서비스 재시작 같은 작업을 수행합니다.
- 그리고 --syntax-check, --check 같은 옵션으로
- 문법 오류를 잡고
- 실행 전 시뮬레이션도 해보면서
플레이북을 “운영 가능한 형태”로 다루는 감각을 잡습니다.
이 단계부터는 “실행”이 목적이 아니라, 반복 실행해도 안전한(멱등한) 작업 정의가 목적입니다.
변수와 Facts 활용
플레이북을 쓰기 시작하면 곧바로 마주치는 문제가 있습니다.
- 호스트가 달라지면 값이 달라지고(OS/버전/인터페이스/패키지명 등)
- 매번 하드코딩하면 플레이북이 금방 깨집니다.
그래서 다음 흐름이 변수와 Facts입니다.
- Facts는 관리 호스트에서 자동 수집되는 “시스템 정보 변수”이고,
- 플레이북은 이를 이용해
- 특정 값만 뽑아 출력하거나(예: hostname, default IPv4),
- OS 계열/버전에 따라 작업을 달리 수행할 기반을 마련합니다.
- 또한 필요할 때는 팩트 수집을 끄거나(gather_facts: no), 수동으로 수집(setup)하는 흐름도 함께 다룹니다.
이 단계의 결과는 간단합니다.
플레이북이 “특정 환경에만 맞는 스크립트”가 아니라, 여러 노드/여러 조건에서도 재사용 가능한 형태로 바뀝니다.
반복과 조건 적용
변수와 Facts로 재료를 준비했다면, 이제 플레이북이 진짜 자동화처럼 보이기 시작합니다.
- 반복문(loop)으로는
- 같은 작업을 여러 대상에 반복 적용(서비스/패키지/파일 목록 등)하고,
- dictionary 형태의 데이터(경로+권한 등)를 반복 처리합니다.
- register와 결합하면 반복 실행 결과(result.results)를 다시 루프에서 활용할 수도 있습니다.
- 조건문(when)으로는
- boolean 기반 실행/스킵,
- 연산자 비교(==, >, in, is defined 등),
- 복수 조건(and/or),
- 그리고 loop와 결합해 “특정 mount만 걸러 출력” 같은 흐름까지 만듭니다.
- 마지막으로 register 결과(stdout 등)를 조건으로 연결해, “어떤 명령의 결과가 특정 값일 때만 다음 작업 실행” 같은 흐름을 구성합니다.
여기까지 오면 플레이북은 단순한 순차 실행이 아니라, 데이터(변수/팩트)에 따라 동작이 달라지는 자동화 규칙이 됩니다.
핸들러와 실패 처리
기능이 돌아가더라도, 운영에서는 다음 문제가 중요해집니다.
- “변경이 발생했을 때만 재시작해야 한다”
- “중간에 실패가 나도 필요한 정리는 해야 한다”
- “특정 조건이면 의도적으로 실패 처리해야 한다”
그래서 다음 묶음이 핸들러 + 에러 핸들링입니다.
- notify/handlers로 “변경이 있을 때만” 후속 작업(재시작 등)을 트리거하고
- ignore_errors로 실패를 무시하며 흐름을 유지하거나
- force_handlers로 “실패로 플레이가 중단돼도” 이전에 notify된 핸들러가 실행되게 만들고
- failed_when / fail로 “실행은 됐지만 실패로 간주해야 하는 조건”을 명시합니다.
- block/rescue/always로 실패 시 대체 처리(rescue)와 항상 실행(always)을 구성합니다.
이 단계는 플레이북을 “실습 스크립트”에서 “운영 자동화”로 끌어올리는 구간입니다.
재사용과 확장
마지막은 “잘 돌아가는 플레이북을 어떻게 관리하고 확장할 것인가”입니다.
- Roles는 플레이북 내용을 기능 단위로 분해해서
- tasks/handlers/files/defaults/vars 같은 표준 구조로 재사용 가능하게 만듭니다.
- import_role/include_role 또는 roles 섹션으로 호출하면서 “부품 조립”처럼 구성합니다.
- Tags는 큰 플레이북에서
- 특정 작업만 선택 실행하거나,
- 특정 작업만 건너뛰는 운영 편의 기능으로 붙습니다.
- --list-tags, --list-tasks로 실행 범위를 미리 확인하는 흐름도 함께 다룹니다.
- Galaxy는 “남이 만든 롤을 가져와 쓰는 방법”이고,
- role search/info/install/list/remove 같은 흐름으로 실제 설치 → 실행까지 이어집니다.
- Collections는 모듈/플러그인/롤을 묶은 배포 단위로,
- 필요한 기능만 설치하거나,
- 버전을 고정하거나,
- 오프라인 설치를 위해 tar로 내려받는 흐름까지 다룹니다.
실습 환경 구성
Vagrant로 가상 머신을 배포하고, provision 스크립트(init_cfg.sh, init_cfg2.sh)로 초기 설정을 적용하는 방식입니다. 이후 server에 접속하여 기본 정보와 노드 간 통신을 확인합니다.
가상 머신 구성
아래 구성으로 총 4개의 노드를 준비합니다.
| Node | OS | Kernel | vCPU | Memory | Disk | NIC2 IP | 관리자 계정(기본) | 일반 계정 |
| server | Ubuntu 24.04 | 6.8.0 | 2 | 1.5GB | 30GB | 10.10.1.10 | root / qwe123 | vagrant / qwe123 |
| tnode1 | Ubuntu 24.04 | 6.8.0 | 2 | 1.5GB | 30GB | 10.10.1.11 | root / qwe123 | vagrant / qwe123 |
| tnode2 | Ubuntu 24.04 | 6.8.0 | 2 | 1.5GB | 30GB | 10.10.1.12 | root / qwe123 | vagrant / qwe123 |
| tnode3 | Rocky Linux 9 | 5.14.0 | 2 | 1.5GB | 60GB | 10.10.1.13 | root / qwe123 | vagrant / qwe123 |
실습 환경 배포
실습 디렉터리를 만들고, 배포에 필요한 파일 3개를 내려받은 뒤 VM을 생성합니다.
mkdir ansible
cd ansible
curl -O https://raw.githubusercontent.com/gasida/vagrant-lab/refs/heads/main/ansible/Vagrantfile
curl -O https://raw.githubusercontent.com/gasida/vagrant-lab/refs/heads/main/ansible/init_cfg.sh
curl -O https://raw.githubusercontent.com/gasida/vagrant-lab/refs/heads/main/ansible/init_cfg2.sh
vagrant up
여기서 사용되는 파일은 다음과 같습니다.
- Vagrantfile : VM 스펙(CPU/Mem/Disk), 네트워크(IP), provision 스크립트 적용 대상을 정의합니다.
- init_cfg.sh : Ubuntu 계열 노드 초기 설정 스크립트입니다.
- init_cfg2.sh : Rocky Linux 노드 초기 설정 스크립트입니다.
Vagrantfile 확인
Vagrantfile은 아래 내용을 포함합니다.
- N = 2로 설정되어 있으며, tnode1 ~ tnode2는 반복문으로 생성됩니다.
- server는 10.10.1.10, tnode1~2는 10.10.1.1{i}, tnode3는 10.10.1.13으로 private network IP가 지정됩니다.
- Ubuntu 계열 노드(server, tnode1, tnode2)는 init_cfg.sh를 사용합니다.
- Rocky Linux 노드(tnode3)는 init_cfg2.sh를 사용합니다.
# Variables
# max number of T nodes
N = 2
# Base Image https://portal.cloud.hashicorp.com/vagrant/discover/bento/ubuntu-24.04
BOX_IMAGE = "bento/ubuntu-24.04"
BOX_VERSION = "202510.26.0"
Vagrant.configure("2") do |config|
#-Server Node : Ubuntu
config.vm.define "server" do |subconfig|
subconfig.vm.box = BOX_IMAGE
subconfig.vm.box_version = BOX_VERSION
subconfig.vm.provider "virtualbox" do |vb|
vb.customize ["modifyvm", :id, "--groups", "/Ansible-Lab"]
vb.name = "server"
vb.cpus = 2
vb.memory = 1536 # 2048
vb.linked_clone = true
end
subconfig.vm.host_name = "server"
subconfig.vm.network "private_network", ip: "10.10.1.10"
subconfig.vm.network "forwarded_port", guest: 22, host: 60000, auto_correct: true, id: "ssh"
subconfig.vm.synced_folder "./", "/vagrant", disabled: true
subconfig.vm.provision "shell", path: "init_cfg.sh"
end
# Test Node : Ubuntu
(1..N).each do |i|
config.vm.define "tnode#{i}" do |subconfig|
subconfig.vm.box = BOX_IMAGE
subconfig.vm.box_version = BOX_VERSION
subconfig.vm.provider "virtualbox" do |vb|
vb.customize ["modifyvm", :id, "--groups", "/Ansible-Lab"]
vb.name = "tnode#{i}"
vb.cpus = 2
vb.memory = 1536 # 2048
vb.linked_clone = true
end
subconfig.vm.host_name = "tnode#{i}"
subconfig.vm.network "private_network", ip: "10.10.1.1#{i}"
subconfig.vm.network "forwarded_port", guest: 22, host: "6000#{i}", auto_correct: true, id: "ssh"
subconfig.vm.synced_folder "./", "/vagrant", disabled: true
subconfig.vm.provision "shell", path: "init_cfg.sh", args: [ N ]
end
end
# Test Node : Rocky Linux
config.vm.define "tnode3" do |subconfig|
subconfig.vm.box = "bento/rockylinux-9"
subconfig.vm.box_version = "202510.26.0"
subconfig.vm.provider "virtualbox" do |vb|
vb.customize ["modifyvm", :id, "--groups", "/Ansible-Lab"]
vb.name = "tnode3"
vb.cpus = 2
vb.memory = 1536 # 2048
vb.linked_clone = true
end
subconfig.vm.host_name = "tnode3"
subconfig.vm.network "private_network", ip: "10.10.1.13"
subconfig.vm.network "forwarded_port", guest: 22, host: 60003, auto_correct: true, id: "ssh"
subconfig.vm.synced_folder "./", "/vagrant", disabled: true
subconfig.vm.provision "shell", path: "init_cfg2.sh"
end
end
초기 설정 스크립트 확인
init_cfg.sh (Ubuntu)
init_cfg.sh는 스크립트 내부에서 [TASK 1] ~ [TASK 5] 형태로 수행 단계가 나뉘어 있습니다.
- [TASK 1] Profile 설정 및 Timezone 변경
- [TASK 2] UFW/AppArmor 중지 및 비활성화
- [TASK 3] 패키지 설치
- [TASK 4] 계정 패스워드 설정 및 SSH 설정 변경 후 ssh 재시작
- [TASK 5] /etc/hosts에 노드 정보 추가
#!/usr/bin/env bash
echo ">>>> Initial Config Start <<<<"
echo "[TASK 1] Setting Profile & Change Timezone"
echo 'alias vi=vim' >> /etc/profile
echo "sudo su -" >> /home/vagrant/.bashrc
ln -sf /usr/share/zoneinfo/Asia/Seoul /etc/localtime
echo "[TASK 2] Disable AppArmor"
systemctl stop ufw && systemctl disable ufw >/dev/null 2>&1
systemctl stop apparmor && systemctl disable apparmor >/dev/null 2>&1
echo "[TASK 3] Install Packages"
apt update -qq >/dev/null 2>&1
apt-get install tree sshpass unzip -y -qq >/dev/null 2>&1
echo "[TASK 4] Config account & ssh config"
echo 'vagrant:qwe123' | chpasswd
echo 'root:qwe123' | chpasswd
sed -i "s/^#PasswordAuthentication yes/PasswordAuthentication yes/g" /etc/ssh/sshd_config
sed -i "s/^#PermitRootLogin prohibit-password/PermitRootLogin yes/g" /etc/ssh/sshd_config
systemctl restart ssh
echo "[TASK 5] Setting Local DNS Using Hosts file"
sed -i '/^127\.0\.\(1\|2\)\.1/d' /etc/hosts
cat << EOF >> /etc/hosts
10.10.1.10 server
10.10.1.11 tnode1
10.10.1.12 tnode2
10.10.1.13 tnode3
EOF
echo ">>>> Initial Config End <<<<"
init_cfg2.sh (Rocky Linux)
init_cfg2.sh도 동일하게 단계가 구분되어 있으며, Rocky Linux 환경에 맞게 firewalld/selinux 관련 설정과 dnf 기반 패키지 설치가 포함되어 있습니다.
- [TASK 1] Profile 설정 및 Timezone 변경
- [TASK 2] firewalld 중지 및 비활성화, selinux 설정 변경
- [TASK 5] 패키지 설치
- [TASK 4] 계정 패스워드 설정 및 SSH 설정 변경 후 sshd 재시작
- [TASK 5] /etc/hosts에 노드 정보 추가
#!/usr/bin/env bash
echo ">>>> Initial Config Start <<<<"
echo "[TASK 1] Setting Profile & Change Timezone"
echo 'alias vi=vim' >> /etc/profile
echo "sudo su -" >> /home/vagrant/.bashrc
ln -sf /usr/share/zoneinfo/Asia/Seoul /etc/localtime
echo "[TASK 2] Disable firewalld and selinux"
systemctl stop firewalld && systemctl disable firewalld >/dev/null 2>&1
setenforce 0
sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config
echo "[TASK 5] Install Packages"
dnf install -y yum sshpass jq git >/dev/null 2>&1
echo "[TASK 4] Config account & ssh config"
echo 'vagrant:qwe123' | chpasswd
echo 'root:qwe123' | chpasswd
sed -i "s/^#PasswordAuthentication yes/PasswordAuthentication yes/g" /etc/ssh/sshd_config
sed -i "s/^#PermitRootLogin prohibit-password/PermitRootLogin yes/g" /etc/ssh/sshd_config
systemctl restart sshd
echo "[TASK 5] Setting Local DNS Using Hosts file"
sed -i '/^127\.0\.\(1\|2\)\.1/d' /etc/hosts
cat << EOF >> /etc/hosts
10.10.1.10 server
10.10.1.11 tnode1
10.10.1.12 tnode2
10.10.1.13 tnode3
EOF
echo ">>>> Initial Config End <<<<"
vagrant ssh server : 기본 정보 확인
배포가 끝나면 server에 접속하여 노드 정보와 네트워크 상태를 확인합니다.
# 계정 정보 확인
whoami
id
# Kernel, CPU, Mem, Disk, NIC 확인
uname -r
hostnamectl
htop
free -h
lsblk
df -hT /
ip -c addr
# /etc/hosts 확인
cat /etc/hosts
# 노드간 통신 확인
for i in {1..3}; do ping -c 1 tnode$i; done
Ansible 소개
이번 장에서는 Ansible이 어떤 도구인지, 그리고 Ansible을 구성하는 핵심 개념(제어 노드/관리 노드, 인벤토리, 플레이북 등)을 정리합니다.
소개
Ansible은 오픈소스 IT 자동화 도구로, IT 업무를 코드 기반으로 작성하여 여러 환경에 동일하게 적용될 수 있도록 돕는 역할을 합니다.
앤서블은 리눅스, MacOS, BSC 계열 유닉스, WSL을 지원하는 윈도우에 파이썬과 앤서블 코어만 설치하면 어디에서나 플레이북(Playbook) 을 작성하고 실행시킬 수 있습니다.
플레이북은 YAML 형식으로 작업들을 순서대로 작성해 놓은 파일입니다.
앤서블은 아래 구성으로 이해할 수 있습니다.
- 제어 노드(Control Node) : 앤서블 코어가 설치되고 플레이북을 작성/실행하는 노드입니다.
- 관리 노드(Managed Node) : 플레이북이 실행되어 애플리케이션 설치나 시스템 작업이 수행되는 노드입니다.
- 앤서블은 제어 노드에만 설치되며, 관리 노드에는 설치되지 않습니다.
- 제어 노드는 사용자가 정의한 플레이북과 관리 노드를 정의한 인벤토리 파일을 기반으로, SSH 프로토콜을 사용하여 관리 노드의 업무 자동화를 수행할 수 있습니다.
특징
Ansible의 특징은 다음과 같습니다.
- 에이전트 불필요 (Agentless)
- 퍼펫(Puppet)이나 셰프(Chef)는 관리 대상 서버에 별도의 에이전트를 설치하고 이를 통해 자동화 업무를 수행합니다.
- 데몬 형태 에이전트 기반 도구는 운영체제 버전에 따른 추가 패키지/모듈 설치 등 추가 작업이 발생할 수 있습니다.
- 앤서블은 에이전트 설치 없이 SSH로 접속하여 대상 서버들을 관리할 수 있습니다.
- 멱등성(Idempotent)
- 멱등성은 동일한 연산을 여러 번 적용하더라도 결과가 달라지지 않는 성질을 의미합니다.
- 앤서블은 멱등성과 함께 시스템을 원하는 상태로 표현하여 유지하도록 설계되어 있어, 동일한 운영 작업을 여러 번 실행해도 같은 결과를 냅니다.
- 쉬운 사용법과 다양한 모듈 제공
- 다른 자동화 도구에 비해 복잡하지 않아 자동화 절차 및 과정을 이해하기 쉽습니다.
- 자동화 단계는 에디터만 있으면 YAML 문법으로 쉽게 작성하고 읽을 수 있습니다.
- 파일 복사 같은 일반 시스템 관리 모듈부터 퍼블릭 클라우드 관련 모듈 및 컬렉션까지 제공하므로, 플레이북 예제를 찾아보고 자동화를 수행할 수 있습니다.
분류
앤서블은 다음과 같이 분류할 수 있습니다.
- 커뮤니티 앤서블 : 오픈소스 형태로 운영체제가 리눅스라면 어디에나 설치하여 사용할 수 있습니다.
- 레드햇 앤서블 오토메이션 플랫폼 : 레드햇 서브스크립션을 통해 사용합니다.
커뮤니티 앤서블 아키텍처
커뮤니티 앤서블은 크게 제어 노드 + 관리 노드 구조로 이해할 수 있습니다.
앤서블에는 다양한 모듈과 플러그인이 함께 설치되어 있으며, 관리 노드 목록을 저장하는 인벤토리, 그리고 관리 노드에서 수행될 작업 절차를 정의하는 플레이북이 존재합니다.

아키텍처를 구성하는 요소는 다음과 같습니다.
- 제어 노드(Control Node)
- 앤서블이 설치되는 노드이며, 운영체제가 리눅스라면 제어 노드가 될 수 있습니다.
- 앤서블은 파이썬 모듈을 이용하므로 설치/실행을 위해 파이썬이 함께 설치되어 있어야 합니다.
- 관리 노드(Managed Node)
- 앤서블이 제어하는 원격 시스템 또는 호스트를 의미합니다.
- 관리 노드는 리눅스 또는 윈도우가 설치된 노드일 수 있습니다.
- 앤서블은 별도의 에이전트를 설치하지 않으므로 관리 노드는 제어 노드와 SSH 통신이 가능해야 하며, 파이썬이 설치되어 있어야 합니다.
- 인벤토리(Inventory)
- 제어 노드가 제어하는 관리 노드의 목록을 나열해놓은 파일입니다.
- 앤서블은 인벤토리에 사전에 정의되어 있는 관리 노드에만 접근할 수 있습니다.
- 인벤토리는 관리 노드의 성격별로 그룹핑할 수 있습니다.
$ vi inventory
192.168.10.101
[WebServer]
web1.example.com
web2.example.com
[DBServer]
db1.example.com
db2.example.com
- 모듈(Modules)
- 앤서블은 관리 노드 작업 수행 시 SSH로 연결한 뒤, ‘앤서블 모듈(Modules)’이라는 스크립트를 푸시하여 작동합니다.
- 대부분의 모듈은 원하는 시스템 상태를 설명하는 매개 변수를 허용하며, 모듈 실행이 완료되면 제거됩니다.
- 플러그인(Plugins)
- 플러그인은 앤서블의 핵심 기능을 강화합니다.
- 모듈이 대상 시스템에서 별도의 프로세스로 실행되는 동안, 플러그인은 제어 노드에서 실행됩니다.
- 데이터 변환, 로그 출력, 인벤토리 연결 등 핵심 기능에 대한 옵션 및 확장 기능을 제공합니다.
- 플레이북(Playbook)
- 관리 노드에서 수행할 작업들을 YAML 문법으로 순서대로 작성해놓은 파일입니다.
- 앤서블은 작성된 플레이북을 활용하여 관리 노드에 SSH로 접근해 작업을 수행합니다.
- 플레이북은 자동화를 완성하는 가장 중요한 파일이며 사용자가 직접 작성합니다.
---
- hosts: webservers
serial: 5 # 한 번에 5대의 머신을 업데이트하라는 의미
roles:
- common
- webapp
- hosts: content_servers
roles:
- common
- content
Ansible concepts
Docs: https://docs.ansible.com/projects/ansible/latest/getting_started/basic_concepts.html
- Control node
- Ansible CLI 도구(ansible-playbook, ansible, ansible-vault 등)를 실행하는 머신입니다. 소프트웨어 요구사항을 충족하는 모든 컴퓨터(노트북, 데스크톱, 서버)에서 실행 가능합니다. 여러 개의 Control node를 사용할 수 있지만, Ansible 자체는 이들 간의 조율을 하지 않습니다. Managed nodes 'hosts'라고도 불리며, Ansible로 관리하려는 대상 디바이스(서버, 네트워크 장비 등)입니다.
- Managed nodes
- 'hosts'라고도 불리며, Ansible로 관리하려는 대상 디바이스(서버, 네트워크 장비 등)입니다.
- Inventory
- 하나 또는 여러 인벤토리 소스에서 제공되는 관리 노드 목록입니다.
- 인벤토리에는 노드별 정보(IP 등)를 적을 수 있고, 그룹을 구성하여 호스트 선택 및 변수 할당에도 사용합니다.
- Playbooks
- ansible-playbook이 실행하는 파일이며, 내부에 Play를 포함합니다.
- Plays : 관리 노드(호스트)와 Task를 매핑하는 실행 단위입니다.
- Roles : Task, Handler, 변수, 플러그인, 템플릿, 파일 등 재사용 가능한 콘텐츠 묶음입니다.
- Tasks : 관리 노드에 적용되는 ‘액션’ 정의입니다.
- Handlers : 이전 Task 결과가 changed일 때 알림(notify)에 의해 실행되는 특수한 Task입니다.
- Modules
- Task에서 정의한 액션을 수행하기 위해, 관리 노드에 복사되어 실행되는 코드/바이너리입니다.
- 모듈은 컬렉션 단위로 그룹핑됩니다.
- Plugins
- 앤서블의 핵심 기능을 확장하는 코드입니다. (연결 방식, 데이터 처리, 콘솔 출력 제어 등)
- Collections
- Playbook, Role, Module, Plugin 등을 포함할 수 있는 배포 단위이며, Ansible Galaxy를 통해 설치/사용할 수 있습니다.
Ansible 설치
이번 장에서는 ansible-server에서 앤서블을 설치하고, 관리 노드(tnode1~3)에 접근하기 위한 SSH 인증 구성을 진행합니다. 이후 실습 편의를 위해 Visual Studio Code(Remote-SSH)로 ansible-server에 접속합니다.
[ansible-server] 앤서블 설치
설치 전에 작업 디렉터리/계정과 파이썬 버전을 확인한 뒤, Ansible PPA를 추가하여 설치합니다.
# 작업 기본 디렉터리 확인
whoami
pwd
# 파이썬 버전 확인
python3 --version
# 설치
apt install software-properties-common -y
add-apt-repository --yes --update ppa:ansible/ansible
apt install ansible -y
설치 후 아래 명령으로 버전 및 설정 파일 경로를 확인합니다.
# 확인
ansible --version
출력 예시는 다음과 같습니다.
ansible [core 2.19.5]
config file = /etc/ansible/ansible.cfg
configured module search path = ['/root/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
ansible python module location = /usr/lib/python3/dist-packages/ansible
ansible collection location = /root/.ansible/collections:/usr/share/ansible/collections
executable location = /usr/bin/ansible
python version = 3.12.3 (main, Aug 14 2025, 17:47:21) [GCC 13.3.0] (/usr/bin/python3)
jinja version = 3.1.2
pyyaml version = 6.0.1 (with libyaml v0.2.5)
추가로 아래 항목도 확인합니다.
cat /etc/ansible/ansible.cfg
ansible-config list
which ansible
마지막으로 실습용 작업 디렉터리를 생성합니다.
mkdir my-ansible
cd my-ansible
앤서블 접근을 위한 SSH 인증 구성
관리 노드(tnode1~3)에 접근하기 위해 ansible-server에서 SSH 키를 생성하고, 공개 키를 각 노드에 복사합니다.
이후 ssh로 접속 테스트 및 python 버전도 함께 확인합니다.
# 모니터링
tree ~/.ssh
watch -d 'tree ~/.ssh'
# Create SSH Keypair
ssh-keygen -t rsa -N "" -f /root/.ssh/id_rsa
공개 키를 관리 노드로 복사합니다.
# 공개 키를 관리 노드에 복사
for i in {1..3}; do sshpass -p 'qwe123' ssh-copy-id -o StrictHostKeyChecking=no root@tnode$i; done
복사 결과는 authorized_keys로 확인합니다.
# 복사 확인
for i in {1..3}; do echo ">> tnode$i <<"; ssh tnode$i cat ~/.ssh/authorized_keys; echo; done
SSH 접속 및 hostname 확인입니다.
# ssh 접속 테스트
for i in {1..3}; do echo ">> tnode$i <<"; ssh tnode$i hostname; echo; done
그리고 관리 노드의 python 정보를 확인합니다.
# python 정보 확인
for i in {1..3}; do echo ">> tnode$i <<"; ssh tnode$i python3 -V; echo; done
Visual Studio Code에서 Remote-SSH로 ansible-server 연결
실습 편의를 위해 Visual Studio Code에서 Remote-SSH로 ansible-server에 접속합니다.
💡 보안상 권장하시는 않지만, 실습 편의를 위해 root 계정으로 실습을 진행합니다.
- Extension에서 remote development 입력 후 Remote - SSH 설치합니다.
- 커맨드창(Ctrl + Shift + P)에서 “Remote-SSH: Open Config…”를 선택하여 SSH Config를 수정합니다. (ls ~/.ssh/config)
# Read more about SSH config files: https://linux.die.net/man/5/ssh_config
Host ansible-server
HostName 10.10.1.10
User root
이후 “Remote-SSH: Connect…”로 ansible-server에 접속합니다. 암호는 qwe123입니다.
접속 후 /root/my-ansible 폴더를 열고, 터미널을 띄워 기본 정보를 확인합니다.
# 계정 정보 등 확인
whoami
id
pwd
tree
마지막으로 자동 저장을 설정합니다.
- 설정 → auto save 검색 → afterDelay 선택
- Auto Save Delay: 1000ms (=1초)
호스트 선정
Ansible은 자동화 대상으로 하는 관리 호스트(Managed Host) 를 인벤토리(Inventory) 파일로 지정합니다. 이번 장에서는 인벤토리 파일이 어떤 형태로 구성되는지 예시를 먼저 확인하고, 실습 환경(tnode1~3)에 맞게 인벤토리를 생성한 뒤 ansible-inventory로 검증합니다. 또한 실습 편의를 위해 프로젝트 디렉터리의 ansible.cfg에 인벤토리를 지정하고, 설정 적용 우선순위를 확인합니다.
kubespray 인벤토리 구조
kubespray 인벤토리 예시는 아래와 같이 호스트 정의 + 역할(그룹) 정의로 구성됩니다.
[all]
master01 ansible_host=192.168.10.10 ip=192.168.10.10 ansible_user=root
worker01 ansible_host=192.168.10.11 ip=192.168.10.11 ansible_user=root
worker02 ansible_host=192.168.10.12 ip=192.168.10.12 ansible_user=root
[kube_control_plane]
master01
[etcd]
master01
[kube_node]
worker01
worker02
[k8s_cluster:children]
kube_control_plane
kube_node
또 다른 예시에서는 :children을 이용해 그룹을 중첩시키는 형태도 확인할 수 있습니다.
[kube_control_plane]
# node1 ansible_host=95.54.0.12 # ip=10.3.0.1 etcd_member_name=etcd1
# node2 ansible_host=95.54.0.13 # ip=10.3.0.2 etcd_member_name=etcd2
# node3 ansible_host=95.54.0.14 # ip=10.3.0.3 etcd_member_name=etcd3
[etcd:children]
kube_control_plane
[kube_node]
# node4 ansible_host=95.54.0.15 # ip=10.3.0.4
# node5 ansible_host=95.54.0.16 # ip=10.3.0.5
# node6 ansible_host=95.54.0.17 # ip=10.3.0.6
인벤토리를 이용한 자동화 대상 호스트 설정
인벤토리 파일은 텍스트 파일이며, 앤서블이 자동화 대상으로 하는 관리 호스트를 지정합니다.
이 파일은 INI 스타일(이름=값) 형식 또는 YAML 등 다양한 형식을 사용할 수 있습니다.
가장 단순한 INI 스타일 인벤토리는 아래처럼 관리 호스트의 호스트명 또는 IP 주소를 한 줄에 하나씩 나열하는 형태입니다.
web1.example.com
web2.example.com
db1.example.com
db2.example.com
192.0.2.42
실습 인벤토리 생성 및 검증
IP를 이용한 인벤토리 파일 생성
my-ansible/inventory 파일을 생성합니다.
# inventory 파일 생성
cat <<EOT > inventory
10.10.1.11
10.10.1.12
10.10.1.13
EOT
# inventory 검증 : -i 특정 인벤토리 지정
ansible-inventory -i ./inventory --list | jq
호스트명을 이용한 인벤토리 파일 생성
호스트명을 사용하기 전에 /etc/hosts를 확인합니다.
# /etc/hosts 파일 확인
cat /etc/hosts
이후 호스트명 기반 인벤토리를 생성합니다.
# inventory 파일 생성
cat <<EOT > inventory
tnode1
tnode2
tnode3
EOT
# inventory 검증
ansible-inventory -i ./inventory --list | jq
역할에 따른 호스트 그룹 설정
호스트별로 롤을 부여하고 롤별로 작업을 수행해야 하는 경우가 있습니다.
관련 참고 파일은 아래에서 확인합니다.
# 참고 파일
cat /etc/ansible/hosts
그룹별 호스트 설정
그룹명을 대괄호([])로 정의하고, 그룹에 속하는 호스트명/IP를 한 줄에 하나씩 나열합니다.
[webservers]
web1.example.com
web2.example.com
[db-servers]
db01.example.com
db02.example.com
호스트는 여러 그룹에 포함될 수도 있습니다.
[webservers]
web1.example.com
web2.example.com
192.0.2.42
[db-servers]
db01.example.com
db02.example.com
[east-datacenter]
web1.example.com
db01.example.com
[west-datacenter]
web2.example.com
db02.example.com
[production]
web1.example.com
web2.example.com
db01.example.com
db02.example.com
[development]
192.168.0.42
중첩 그룹 정의(:children)
기존에 정의한 호스트 그룹을 다른 그룹에 포함할 수도 있습니다.
이 경우 그룹 이름에 :children 접미사를 붙입니다.
[webservers]
web1.example.com
web2.example.com
[db-servers]
db01.example.com
db02.example.com
[datacenter:children]
webservers
dbservers
범위를 사용한 호스트 사양 간소화
호스트 이름 또는 IP 주소를 설정할 때 범위를 지정하여 인벤토리를 간소화할 수 있습니다.
범위는 숫자 또는 영문자로 지정하며 대괄호 사이에 시작/종료 구문을 포함합니다.
[start:end]
예시는 아래와 같습니다.
[webservers]
web[1:2].example.com
[db-servers]
db[01:02].example.com
추가 예시는 아래와 같습니다.
# IP 범위 설정 : 192.168.4.0 ~ 192.168.4.255 사이의 IP 범위를 표현
[defaults]
192.168.4.[0:255]
# 호스트명 범위 설정 : com01.example.com ~ com20.example.com 의 범위를 표현
[compute]
com[01:20].example.com
# DNS 범위 설정 : a.dns.example.com , b.dns.example.com , c.dns.example.com 을 의미함
[dns]
[a:c].dns.example.com
# IPv6 범위 설정 : 2001:db08::a ~ 2001:db08::f 사이의 IPv6 범위를 표현
[ipv6]
2001:db8::[a:f]
실습 인벤토리 그룹 구성 및 ansible config 적용 우선순위
실습용 인벤토리 그룹 구성
실습을 위해 web, db 그룹을 구성하고, all:children으로 중첩 그룹을 정의합니다.
# inventory 그룹 구성
cat <<EOT > inventory
[web]
tnode1
tnode2
[db]
tnode3
[all:children]
web
db
EOT
# inventory 검증
ansible-inventory -i ./inventory --list | jq
ansible-inventory -i ./inventory --graph
프로젝트 디렉터리 ansible.cfg 생성 및 확인
현재 프로젝트 디렉터리 내에 ansible.cfg 설정 파일을 구성하면, -i 옵션을 사용하지 않아도 ansible.cfg에 정의된 인벤토리를 기준으로 확인할 수 있습니다.
# ansible.cfg 파일 생성
cat <<EOT > ansible.cfg
[defaults]
inventory = ./inventory
EOT
# inventory 목록 확인
ansible-inventory --list | jq
ansible config 적용 우선 순위 확인
- Ansible Configuration Settings: https://docs.ansible.com/ansible/latest/reference_appendices/config.html
아래 명령으로 현재 설정 적용 흐름을 확인합니다.
# ansible config 적용 우선 순위 확인
echo $ANSIBLE_CONFIG
cat $PWD/ansible.cfg # kubespary 실행 시, 디렉터리 위치 고정 이유
ls ~/.ansible.cfg
tree ~/.ansible
cat /etc/ansible/ansible.cfg
ansible-config dump
ansible-config list
(참고) ansible config 적용 우선 순위는 다음과 같습니다.
- ANSIBLE_CONFIG (environment variable if set)
- ansible.cfg (in the current directory)
- ~/.ansible.cfg (in the home directory)
- /etc/ansible/ansible.cfg
플레이북 작성
이번 장에서는 플레이북 실행을 위한 Ansible 환경 설정(ansible.cfg) 을 구성하고, ansible 명령으로 실행하는 ad-hoc 방식과 ansible-playbook으로 실행하는 플레이북 방식을 순서대로 확인합니다. 마지막으로 간단한 플레이북을 직접 작성하고 문법 체크/실행까지 진행합니다.
플레이북 환경 설정(ansible.cfg)
앤서블 프로젝트 디렉터리에 ansible.cfg 파일을 생성하면 다양한 앤서블 설정을 적용할 수 있습니다.
환경 설정 파일은 각 섹션에 키-값 쌍으로 정의된 설정이 포함되며, 여러 개의 섹션으로 구성됩니다. 섹션 제목은 대괄호([]) 로 묶여 있습니다.
기본적인 실행을 위해 아래 예제처럼 [defaults]와 [privilege_escalation] 두 개의 섹션으로 구성합니다.
[defaults]
inventory = ./inventory
remote_user = root
ask_pass = false
[privilege_escalation]
become = true
become_method = sudo
become_user = root
become_ask_pass = false
[defaults] 섹션: 앤서블 작업 기본값 설정
| 매개 변수 | 설명 |
| inventory | 인벤토리 파일의 경로를 지정함. |
| remote_user | 앤서블이 관리 호스트에 연결할 때 사용하는 사용자 이름을 지정함. 사용자 이름을 지정하지 않으면 현재 사용자 이름으로 지정됨. |
| ask_pass | SSH 암호를 묻는 메시지 표시 여부를 지정함. SSH 공개 키 인증을 사용하는 경우 기본값은 false임. |
[privilege_escalation] 섹션: 권한 에스컬레이션 설정
보안/감사로 인해 원격 호스트에 권한 없는 사용자로 연결한 뒤, 관리 액세스 권한을 에스컬레이션하여 루트 사용자로 전환할 때 사용합니다.
| 매개 변수 | 설명 |
| become | 권한 에스컬레이션 활성화 여부를 지정함. 연결 후 관리 호스트에서 자동으로 사용자를 전환할지 여부를 지정함. |
| become_method | 사용자 전환 방식이며, 일반적으로 기본값은 sudo를 사용함. |
| become_user | 관리 호스트에서 전환할 사용자를 지정함. 일반적으로 기본값은 root임. |
| become_ask_pass | become_method 매개 변수에 대한 암호를 묻는 메시지 표시 여부를 지정함. 기본값은 false임. |
앤서블은 리눅스에서 기본적으로 SSH 프로토콜을 사용하여 관리 호스트에 연결합니다.
또한 별도로 설정되어 있지 않으면, 실행 시 로컬 사용자와 같은 사용자 이름을 사용하여 관리 호스트에 연결합니다. 다른 사용자로 접속하려면 remote_user 매개 변수를 사용하여 사용자 이름을 지정할 수 있습니다.
실습에서는 아래처럼 my-ansible/ansible.cfg를 구성합니다.
cat <<EOT > ansible.cfg
[defaults]
inventory = ./inventory
remote_user = root
ask_pass = false
[privilege_escalation]
become = true
become_method = sudo
become_user = root
become_ask_pass = false
EOT
ad hoc commands
플레이북을 작성하지 않고도 ansible 명령으로 모듈을 바로 실행할 수 있습니다. 아래는 ping 모듈과 shell 모듈을 이용한 실행 예시입니다.
ansible ping 모듈
ping 모듈을 이용하여 web 그룹의 호스트로 정상 연결되어 pong이 반환되면 SUCCESS가 출력됩니다.
(icmp ping이 아니라 python 테스트 모듈입니다.)
ansible -m ping web
실행 중 아래와 같은 경고가 발생하는 경우, 인벤토리에 ansible_python_interpreter를 명시하여 동작을 확인합니다.
# inventory 그룹 구성
cat <<EOT > inventory
[web]
tnode1 ansible_python_interpreter=/usr/bin/python3
tnode2 ansible_python_interpreter=/usr/bin/python3
[db]
tnode3 ansible_python_interpreter=/usr/bin/python3
[all:children]
web
db
EOT
ansible-inventory -i ./inventory --list | jq
ansible -m ping web
ansible -m ping db
옵션을 사용하여 SSH 암호 입력 후 실행도 확인합니다.
# 암호 입력 후 실행
ansible -m ping --ask-pass web
SSH password: <암호입력>
다른 사용자 계정으로 실행도 확인합니다.
# root 계정 대신 vagrant 계정으로 실행
ansible -m ping web -u vagrant
ansible -m ping web -u vagrant --ask-pass
ansible -m ping db -u vagrant --ask-pass
(참고로 실습 과정에서 아래처럼 ssh/sshpass로 hostname 확인을 수행한 기록도 포함되어 있습니다.)
ssh vagrant@10.10.1.11 hostname
ssh vagrant@10.10.1.12 hostname
ssh vagrant@10.10.1.13 hostname
sshpass -p 'qwe123' ssh vagrant@tnode1 hostname
sshpass -p 'qwe123' ssh vagrant@tnode2 hostname
sshpass -p 'qwe123' ssh vagrant@tnode3 hostname
<CTRL+C>
sshpass -p 'qwe123' ssh vagrant@tnode3 -t hostname
exit
ansible shell 모듈
shell 모듈은 노드들에 명령 구문을 전달하고 해당 결과를 반환합니다.
ansible -m shell -a uptime db
ansible -m shell -a "free -h" web
ansible -m shell -a "tail -n 3 /etc/passwd" all
첫 번째 플레이북 작성하기
플레이북은 YAML 포맷의 텍스트 파일이며 일반적으로 .yml 확장자를 사용합니다.
대상 호스트(또는 호스트 집합)에 수행할 작업을 정의하고 이를 실행합니다. 이때 특정 작업 단위를 수행하기 위해 모듈을 적용합니다.
문서에서 정리하는 용어는 아래와 같습니다.
- Playbook: Ansible이 수행할 작업 순서를 위에서 아래로 정의한 Play 목록
- Play: 인벤토리의 managed node(hosts)와 task를 매핑한 task 목록
- Task: 단일 모듈 호출(작업 단위)
- Module: managed node에서 실행되는 코드/바이너리 (컬렉션 단위로 그룹핑)
플레이북 작성(debug 모듈)
my-ansible/first-playbook.yml
---
- hosts: all
tasks:
- name: Print message
debug:
msg: Hello CloudNet@ Ansible Study
문법 오류 예시도 함께 작성합니다.
my-ansible/first-playbook-with-error.yml
---
- hosts: all
tasks:
- name: Print message
debug:
msg: Hello CloudNet@ Ansible Study
플레이북 문법 체크
플레이북은 자체 문법 체크 옵션을 제공합니다.
ansible-playbook --syntax-check first-playbook.yml
ansible-playbook --syntax-check first-playbook-with-error.yml
플레이북 실행
플레이북을 실행할 때는 ansible-playbook 명령어를 사용합니다.
프로젝트 디렉터리 내에 ansible.cfg가 존재하는 상태에서 실행할 경우, 플레이북 파일명만 지정해 실행합니다.
ansible-playbook first-playbook.yml
서비스 재시작 플레이북 작성
my-ansible/restart-service.yml
---
- hosts: all
tasks:
- name: Restart sshd service
ansible.builtin.service:
name: ssh # sshd
state: restarted
- state 참고: https://docs.ansible.com/ansible/latest/collections/ansible/builtin/service_module.html#parameter-state
- started: httpd 서비스가 중지되어 있으면 시작
- stopped: httpd 서비스가 실행 중이면 중지
- restarted: httpd 서비스를 항상 재시작
- reloaded: httpd 서비스를 항상 리로드
플레이북 실행 및 모니터링
신규 터미널에서 tnode1 로그를 모니터링합니다.
# (신규터미널) 모니터링 : 서비스 재시작 실행 여부 확인
ssh tnode1 tail -f /var/log/syslog
실행 전 --check 옵션으로 실행 상태를 미리 점검할 수 있습니다.
ansible-playbook --check restart-service.yml
실제 실행은 아래와 같습니다.
ansible-playbook restart-service.yml
OS 별 조건 분리
- hosts: all
tasks:
- name: Restart SSH on Debian
ansible.builtin.service:
name: ssh
state: restarted
when: ansible_facts['os_family'] == 'Debian'
- name: Restart SSH on RedHat
ansible.builtin.service:
name: sshd
state: restarted
when: ansible_facts['os_family'] == 'RedHat'
각 호스트의 fact 정보는 아래 ad-hoc 명령으로 확인할 수 있습니다.
ansible tnode1 -m ansible.builtin.setup | grep -iE 'os_family|ansible_distribution'
ansible tnode3 -m ansible.builtin.setup | grep -iE 'os_family|ansible_distribution'
실행 전 점검 및 실제 실행은 아래와 같습니다.
ansible-playbook --check restart-service.yml
ansible-playbook restart-service.yml
참고로 Ubuntu도 os_family는 Debian으로 출력됩니다.
실습 완료 후 yml 파일 삭제
rm -r *.yml
변수
앤서블은 변수를 사용하여 사용자, 설치하고자 하는 패키지, 재시작할 서비스, 생성 또는 삭제할 파일명 등 시스템 작업 시 사용되는 다양한 값을 저장할 수 있습니다. 이런 변수를 활용하면 플레이북을 재사용할 수 있으며, 사용자로부터 받은 값도 쉽게 적용할 수 있습니다. 앤서블에서 사용되는 변수는 그룹 변수, 호스트 변수, 플레이 변수, 추가 변수가 있으며 플레이 결과를 저장하기 위한 작업 변수도 있습니다.
- ansible.builtin.user 모듈: https://docs.ansible.com/ansible/latest/collections/ansible/builtin/user_module.html
그룹 변수(Group Variables)
그룹 변수는 인벤토리에 정의된 호스트 그룹에 적용하는 변수입니다. 인벤토리에 선언하며, 선언하고자 하는 그룹명 뒤에 :vars를 붙여 변수를 선언합니다. 예제를 통해 확인합니다.
- my-ansible/inventory 파일 하단에 [all:vars] 섹션을 선언하고 user=ansible 변수를 추가합니다.
[web]
tnode1 ansible_python_interpreter=/usr/bin/python3
tnode2 ansible_python_interpreter=/usr/bin/python3
[db]
tnode3 ansible_python_interpreter=/usr/bin/python3
[all:children]
web
db
[all:vars]
user=ansible
user=ansible
사용자 생성을 위한 플레이북을 작성합니다. 인벤토리에 선언한 user 변수를 플레이북에서 {{ user }} 형태로 사용합니다. (겹 중괄호와 변수명 사이는 항상 한 칸씩 띄웁니다.)
my-ansible/create-user.yml
user=ansible
사용자 생성을 위한 플레이북을 작성합니다. 인벤토리에 선언한 user 변수를 플레이북에서 {{ user }} 형태로 사용합니다. (겹 중괄호와 변수명 사이는 항상 한 칸씩 띄웁니다.)
my-ansible/create-user.yml
플레이북을 실행합니다.
# (터미널2) 모니터링
watch -d "ssh tnode1 tail -n 3 /etc/passwd"
# 실행
ansible-playbook create-user.yml
# 한번 더 실행 : 멱등성 확인
ansible-playbook create-user.yml
대상 호스트에서 사용자 생성 여부를 확인합니다.
# tnode1~3에서 ansible 사용자 생성 확인
for i in {1..3}; do echo ">> tnode$i <<"; ssh tnode$i tail -n 3 /etc/passwd; echo; done
for i in {1..3}; do echo ">> tnode$i <<"; ssh tnode$i ls -l /home; echo; done
수동으로 tnode1에서 ansible 사용자를 삭제한 뒤, 다시 플레이북을 실행하여 재생성되는지 확인합니다.
# tnode1 에 ansible 사용자 삭제 후 확인
ssh tnode1 userdel -r ansible
ssh tnode1 tail -n 2 /etc/passwd
# 실행
ansible-playbook create-user.yml
호스트 변수(Host Variables)
호스트 변수는 말 그대로 해당 호스트에서만 사용할 수 있는 변수입니다.
인벤토리 파일에서 db 그룹의 tnode3 옆에 user=ansible1 변수를 선언합니다.
[web]
tnode1 ansible_python_interpreter=/usr/bin/python3
tnode2 ansible_python_interpreter=/usr/bin/python3
[db]
tnode3 ansible_python_interpreter=/usr/bin/python3 user=ansible1
[all:children]
web
db
[all:vars]
user=ansible
이번에는 플레이북의 대상(hosts)을 all이 아닌 db로 지정합니다.
---
- hosts: db
tasks:
- name: Create User {{ user }}
ansible.builtin.user:
name: "{{ user }}"
state: present
플레이북을 실행하여 태스크명에 표시되는 user 값이 ansible1인지 확인합니다.
# (터미널2) 모니터링
watch -d "ssh tnode3 tail -n 3 /etc/passwd"
# 실행
ansible-playbook create-user1.yml
# 확인
for i in {1..3}; do echo ">> tnode$i <<"; ssh tnode$i tail -n 3 /etc/passwd; echo; done
플레이 변수(Play Variables)
플레이 변수는 플레이북 내에서 선언되는 변수이며, 별도 파일로 분리할 수도 있습니다.
https://kimalarm.tistory.com/78
- 플레이북에서 hosts 아래에 vars:를 추가하고 user: ansible2를 선언합니다.
my-ansible/create-user2.yml
---
- hosts: all
vars:
user: ansible2
tasks:
- name: Create User {{ user }}
ansible.builtin.user:
name: "{{ user }}"
state: present
인벤토리의 그룹 변수/호스트 변수 상태를 확인한 뒤 플레이북을 실행합니다.
# (터미널2) 모니터링
watch -d "ssh tnode3 tail -n 3 /etc/passwd"
# 인벤토리에 선언한 그룹 변수와 호스트 변수 확인
cat inventory
# 실행
ansible-playbook create-user2.yml
# 확인
for i in {1..3}; do echo ">> tnode$i <<"; ssh tnode$i tail -n 3 /etc/passwd; echo; done
- 플레이 변수를 별도의 파일로 분리해 정의하고, 플레이북에서 vars_files로 불러옵니다.
my-ansible/vars/users.yml
mkdir vars
echo "user: ansible3" > vars/users.yml
my-ansible/create-user3.yml
---
- hosts: all
vars_files:
- vars/users.yml
tasks:
- name: Create User {{ user }}
ansible.builtin.user:
name: "{{ user }}"
state: present
플레이북 실행 및 확인입니다.
# (터미널2) 모니터링
watch -d "ssh tnode3 tail -n 3 /etc/passwd"
# 플레이 변수 파일 확인
cat vars/users.yml
# 실행
ansible-playbook create-user3.yml
# 확인
for i in {1..3}; do echo ">> tnode$i <<"; ssh tnode$i tail -n 4 /etc/passwd; echo; done
추가 변수(Extra Variables)
추가 변수는 외부에서 ansible-playbook 실행 시 함께 파라미터로 넘겨주는 변수를 의미하며, 앞에서 언급한 변수들 중 우선순위가 가장 높습니다.
-e(extra_vars 약자) 옵션으로 선언합니다.
my-ansible/create-user3.yml(바로 위에서 사용한 플레이북) 실행 시 아래처럼 추가 변수를 넘겨 확인합니다.
# (터미널2) 모니터링
watch -d "ssh tnode3 tail -n 3 /etc/passwd"
# 실행
ansible-playbook -e user=ansible4 create-user3.yml
# 확인
for i in {1..3}; do echo ">> tnode$i <<"; ssh tnode$i tail -n 5 /etc/passwd; echo; done
변수 우선 순위
정리하면 변수 우선 순위는 아래와 같습니다.
- 추가 변수(실행 시 파라미터) > 플레이 변수 > 호스트 변수 > 그룹 변수
작업 변수(Register)
작업 변수는 플레이북의 수행 결과를 저장합니다. 특정 작업 수행 후 결과를 후속 작업에서 사용할 때 주로 사용됩니다.
- ansible.builtin.debug 모듈: https://docs.ansible.com/ansible/latest/collections/ansible/builtin/debug_module.html
- register: result로 태스크 실행 결과를 저장하고, debug 모듈로 출력합니다.
my-ansible/create-user4.yml
---
- hosts: db
tasks:
- name: Create User {{ user }}
ansible.builtin.user:
name: "{{ user }}"
state: present
register: result
- ansible.builtin.debug:
var: result
- 추가 변수와 함께 실행합니다.
# (터미널2) 모니터링
watch -d "ssh tnode3 tail -n 3 /etc/passwd"
# 실행 : ok=3의 의미는?
ansible-playbook -e user=ansible5 create-user4.yml
실행 출력 예시는 아래와 같습니다.
PLAY [db] ***************************************************************************
TASK [Gathering Facts] **************************************************************
ok: [tnode3-ubuntu.local]
TASK [Create User ansible5] *********
changed: [tnode3-ubuntu.local]
TASK [ansible.builtin.debug] ********************************************************
ok: [tnode3-ubuntu.local] => {
"result": {
"changed": true,
"comment": "",
"create_home": true,
"failed": false,
"group": 1006,
"home": "/home/ansible5",
"name": "ansible5",
"shell": "/bin/sh",
"state": "present",
"system": false,
"uid": 1006
}
}
PLAY RECAP **************************************************************************
tnode3-ubuntu.local : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Facts
팩트(Facts)는 앤서블이 관리 호스트에서 자동으로 검색한 변수(자동 예약 변수) 입니다. 팩트에는 플레이, 조건문, 반복문 또는 관리 호스트에서 수집한 값에 의존하는 기타 명령문에서 일반 변수처럼 사용할 수 있는 호스트별 정보가 포함되어 있습니다.
관리 호스트에서 수집될 수 있는 팩트 예시는 다음과 같습니다.
- 호스트 이름
- 커널 버전
- 네트워크 인터페이스 이름
- 운영체제 버전
- CPU 개수
- 사용 가능한 메모리
- 스토리지 장치의 크기 및 여유 공간
- kubespray Facts playbook(예시): https://github.com/kubernetes-sigs/kubespray/blob/master/playbooks/internal_facts.yml
- kubespray Role(예시): https://github.com/kubernetes-sigs/kubespray/tree/master/roles/network_facts
- 공식 문서: https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_vars_facts.html
팩트 사용하기(ansible_facts)
팩트 수집은 기본적으로 활성화되어 있으며, 플레이북을 실행할 때 자동으로 수집됩니다. 수집된 값은 ansible_facts 변수로 사용할 수 있습니다.
- 팩트 전체 출력 플레이북을 작성합니다.
my-ansible/facts.yml
---
- hosts: db
tasks:
- name: Print all facts
ansible.builtin.debug:
var: ansible_facts
플레이북 실행 후 Gathering Facts 단계와 ansible_facts 출력 내용을 확인합니다.
ansible-playbook facts.yml
출력에서는 예를 들어 아래와 같은 값들을 확인할 수 있습니다.
- "hostname": "tnode3"
- "default_ipv4": { "address": "10.10.1.13" }
- "os_family": "Debian"
(참고: Ubuntu도 os_family는 Debian으로 출력 - https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_conditionals.html#ansible-facts-os-family)
팩트에서 필요한 값만 추출하기
팩트 전체를 출력하면 양이 많기 때문에, 필요한 값만 골라서 메시지 형태로 출력할 수도 있습니다.
my-ansible/facts1.yml
---
- hosts: db
tasks:
- name: Print all facts
ansible.builtin.debug:
msg: >
The default IPv4 address of {{ ansible_facts.hostname }}
is {{ ansible_facts.default_ipv4.address }}
실행 결과 예시는 다음과 같습니다.
ansible-playbook facts1.yml
...
ok: [tnode3] => {
"msg": "The default IPv4 address of tnode3 is 10.10.1.13"
}
9.3. ansible_facts.* vs ansible_* 표기법
앤서블 2.* 버전에서는 ansible_facts.* 네임스페이스 표기법을 따르는 것이 권장됩니다. (구 표기법인 ansible_*은 비권장)
- ansible_facts.* 표기법 예시
| 팩트 | ansible_facts.* 표기법 |
| 호스트명 | ansible_facts.hostname |
| 도메인 기반 호스트명 | ansible_facts.fqdn |
| 기본 IPv4 주소 | ansible_facts.default_ipv4.address |
| 네트워크 인터페이스 이름 목록 | ansible_facts.interfaces |
| /dev/vda1 디스크 파티션 크기 | ansible_facts.device.vda.partitions.vda1.size |
| DNS 서버 목록 | ansible_facts.dns.nameservers |
| 현재 실행 중인 커널 버전 | ansible_facts.kernel |
| 운영체제 종류 | ansible_facts.distribution |
구 표기법은 변수와 충돌 위험이 있고, 팩트는 우선순위가 매우 높기 때문에 비권장이라고 정리되어 있습니다.
| ansible_* 표기법 | ansible_facts.* 표기법 |
| ansible_hostname | ansible_facts.hostname |
| ansible_fqdn | ansible_facts.fqdn |
| ansible_default_ipv4.address | ansible_facts.default_ipv4.address |
| ansible_interfaces | ansible_facts.interfaces |
| ansible_device.vda.partitions.vda1.size | ansible_facts.device.vda.partitions.vda1.size |
| ansible_dns.nameservers | ansible_facts.dns.nameservers |
| ansible_kernel | ansible_facts.kernel |
| ansible_distribution | ansible_facts.distribution |
다만 두 표기법이 모두 동작하는 이유는 ansible.cfg의 [defaults] 섹션에 있는 inject_facts_as_vars 매개 변수 기본값이 true이기 때문이며, 이를 false로 설정하면 ansible_* 표기법을 비활성화할 수 있습니다. (대부분의 플레이북에서 이전 표기법을 사용하므로 기본값 true를 그대로 사용하는 것이 좋다고 정리되어 있습니다.)
- ansible_* 표기법을 사용하는 플레이북을 작성합니다.
my-ansible/facts2.yml
---
- hosts: db
tasks:
- name: Print all facts
ansible.builtin.debug:
msg: >
The node's host name is {{ ansible_hostname }}
and the ip is {{ ansible_default_ipv4.address }}
실행하여 출력이 정상인지 확인합니다.
ansible-playbook facts2.yml
ansible.cfg에서 inject_facts_as_vars = false로 수정 후 다시 실행합니다.
[defaults]
inventory = ./inventory
remote_user = root
ask_pass = false
inject_facts_as_vars = false
[privilege_escalation]
become = true
become_method = sudo
become_user = root
become_ask_pass = false
ansible-playbook facts2.yml
팩트 수집 끄기(gather_facts: no)
팩트 수집은 비활성화할 수도 있습니다.
팩트 수집을 위해 특정 패키지를 설치해야 하는데 설치가 불가능한 경우가 있거나, 팩트 수집으로 인해 호스트에 부하가 걸리는 것을 원치 않는 경우 등에 사용할 수 있다고 정리되어 있습니다.
- 팩트 수집 시 관리 호스트에 프로세스가 어떻게 보이는지 확인합니다.
# (터미널2) tnode3에 SSH 접속 후 아래 모니터링
ssh tnode3
watch -d -n 1 pstree -a
# [ansible-server]에서 아래 플레이북 실행
ansible-playbook facts.yml
ansible-playbook facts.yml
ansible-playbook facts.yml
- 이번에는 gather_facts: no를 설정한 플레이북을 작성합니다.
my-ansible/facts3.yml
---
- hosts: db
gather_facts: no
tasks:
- name: Print all facts
ansible.builtin.debug:
msg: >
The default IPv4 address of {{ ansible_facts.hostname }}
is {{ ansible_facts.default_ipv4.address }}
실행하면, 팩트를 수집하지 않았는데 팩트 변수를 사용하려 했기 때문에 에러가 발생하는 흐름을 확인합니다.
이후 파일을 복사하여 팩트 변수를 쓰지 않는 형태로 수정 후 실행합니다.
ansible-playbook facts3.yml
cp facts3.yml facts3-2.yml
facts3-2.yml 편집
---
- hosts: db
gather_facts: no
tasks:
- name: Print message
debug:
msg: Hello Ansible World
실행
ansible-playbook facts3-2.yml
팩트 수집을 수동으로 수행하기(setup 모듈)
팩트 수집을 기본으로 끄되, 필요한 경우 플레이북 내에서 매뉴얼하게 팩트를 수집할 수도 있습니다.
- ansible.builtin.setup 모듈: https://docs.ansible.com/ansible/latest/collections/ansible/builtin/setup_module.html
my-ansible/facts4.yml
---
- hosts: db
gather_facts: no
tasks:
- name: Manually gather facts
ansible.builtin.setup:
- name: Print all facts
ansible.builtin.debug:
msg: >
The default IPv4 address of {{ ansible_facts.hostname }}
is {{ ansible_facts.default_ipv4.address }}
실행
ansible-playbook facts4.yml
Gathering Facts 캐싱
facts 정보와 inventory 정보는 기본적으로 default 캐시 플러그인인 memory 플러그인을 사용합니다. 캐싱하여 영구 저장을 위해 파일 혹은 데이터베이스를 사용할 수 있습니다.
실습에서는 아래처럼 ansible.cfg에 캐싱 설정을 추가합니다.
[defaults]
inventory = ./inventory
remote_user = root
ask_pass = false
gathering = smart
fact_caching = jsonfile
fact_caching_connection = myfacts
[privilege_escalation]
become = true
become_method = sudo
become_user = root
become_ask_pass = false
- DEFAULT_GATHERING(수집 정책 3가지): implicit(기본값), explicit, smart
- fact_caching_connection: 연결 정의 또는 캐싱 경로
반복문
반복문을 사용하면 동일한 모듈을 사용하는 작업을 여러 번 작성하지 않아도 됩니다. 예를 들어 서비스에 필요한 포트를 방화벽에 추가한다고 하면, 포트를 추가하는 작업을 여러 개 작성하는 대신 loop 반복문을 이용해 작업 하나로 여러 개의 포트를 추가할 수 있습니다.
단순 반복문(loop + item)
loop 키워드를 작업에 추가하면, 반복해야 하는 항목의 목록을 값으로 사용합니다. 그리고 반복 중 현재 항목 값은 기본적으로 item 변수로 참조합니다.
(1) 반복문 없이 작성한 예시
아래 플레이북은 sshd(또는 ssh)와 rsyslog 서비스가 시작되어 있지 않다면 시작하는 목적의 예시입니다. OS 계열에 따라 서비스 이름이 달라지는 부분은 when 조건으로 분리되어 있습니다.
- ansible.builtin.service 모듈: https://docs.ansible.com/ansible/latest/collections/ansible/builtin/service_module.html
~/my-ansible/check-services.yml
---
- hosts: all
tasks:
- name: Check sshd state on Debian
ansible.builtin.service:
name: ssh
state: started
when: ansible_facts['os_family'] == 'Debian'
- name: Check sshd state on RedHat
ansible.builtin.service:
name: sshd
state: started
when: ansible_facts['os_family'] == 'RedHat'
- name: Check rsyslog state
ansible.builtin.service:
name: rsyslog
state: started
- state 참고: https://docs.ansible.com/ansible/latest/collections/ansible/builtin/service_module.html#parameter-state
- started : Start service httpd, if not started
- stooped : Stop service httpd, if started
- restarted : Restart service httpd, in all cases
- reloaded : Reload service httpd, in all cases
실행 전/후로 프로세스와 서비스 상태를 확인하고 플레이북을 실행합니다.
# 프로세스 동작 확인
ansible -m shell -a "pstree |grep sshd" all
ansible -m shell -a "pstree |grep rsyslog" all
# systemd로 실행 중인 서비스 목록을 확인
systemctl list-units --type=service
# 실행
ansible-playbook check-services.yml
(2) loop로 단순화한 예시
위처럼 작업을 나열하는 대신, 반복문으로 묶어 한 번에 처리할 수 있습니다.
~/my-ansible/check-services1.yml
---
- hosts: all
tasks:
- name: Check sshd and rsyslog state
ansible.builtin.service:
name: "{{ item }}"
state: started
loop:
- vboxadd-service # ssh
- rsyslog
실행하면 각 호스트별로 item 값이 출력되는 형태를 확인할 수 있습니다.
ansible-playbook check-services1.yml
예시 출력
TASK [Check sshd and rsyslog state] ****************************************************
ok: [tnode2] => (item=sshd)
ok: [tnode1] => (item=sshd)
ok: [tnode3] => (item=sshd)
ok: [tnode2] => (item=rsyslog)
ok: [tnode1] => (item=rsyslog)
ok: [tnode3] => (item=rsyslog)
(3) loop 항목을 변수로 분리한 예시
반복문에 사용하는 아이템을 변수에 저장해두면, loop 키워드에 해당 변수명을 그대로 사용할 수 있습니다. (변수 목록은 별도 파일로도 가능하다고 정리되어 있습니다.)
~/my-ansible/check-services2.yml
---
- hosts: all
vars:
services:
- vboxadd-service # ssh
- rsyslog
tasks:
- name: Check sshd and rsyslog state
ansible.builtin.service:
name: "{{ item }}"
state: started
loop: "{{ services }}"
실행
ansible-playbook check-services2.yml
사전 목록(Dictionary)으로 반복하기
반복 대상이 “문자열 목록”이 아니라, 항목마다 여러 값이 필요한 경우가 있습니다. 예를 들어 파일을 만들 때 경로와 권한(mode)을 함께 지정해야 한다면, loop 항목을 사전 목록(dictionary list) 형태로 구성할 수 있습니다.
- ansible.builtin.file 모듈: https://docs.ansible.com/ansible/latest/collections/ansible/builtin/file_module.html
~/my-ansible/make-file.yml
---
- hosts: all
tasks:
- name: Create files
ansible.builtin.file:
path: "{{ item['log-path'] }}"
mode: "{{ item['log-mode'] }}"
state: touch
loop:
- log-path: /var/log/test1.log
log-mode: '0644'
- log-path: /var/log/test2.log
log-mode: '0600'
실행 전에는 모니터링을 걸어두고, 실행 후에는 대상 호스트에서 파일 생성 여부를 확인합니다.
# (터미널2) 모니터링
watch -d "ssh tnode1 ls -l /var/log/test*.log"
# 실행
ansible-playbook make-file.yml
# 확인
ansible -m shell -a "ls -l /var/log/test*.log" all
10.3. 반복문과 Register 변수 함께 사용하기
반복문을 사용할 때 register 변수를 함께 쓰면, 반복 실행된 작업들의 출력 결과를 한 번에 캡처할 수 있습니다. 이를 통해 반복 작업이 모두 정상 수행되었는지 확인하거나, 저장된 결과를 다음 작업에서 사용할 수 있습니다.
- ansible.builtin.shell 모듈: https://docs.ansible.com/ansible/latest/collections/ansible/builtin/shell_module.html
- Return Values: https://docs.ansible.com/ansible/latest/reference_appendices/common_return_values.html
(1) loop 결과를 register로 저장하기
~/my-ansible/loop_register.yml
---
- hosts: localhost
tasks:
- name: Loop echo test
ansible.builtin.shell: "echo 'I can speak {{ item }}'"
loop:
- Korean
- English
register: result
- name: Show result
ansible.builtin.debug:
var: result
실행하면 result 안에 results: [ ... ] 형태로 반복 결과가 저장된 것을 확인합니다.
ansible-playbook loop_register.yml
(2) register 결과(result.results)를 다시 loop로 순회하기
반복 결과가 배열로 저장되기 때문에, 그 안의 특정 값(예: stdout)만 다시 꺼내서 출력하려면 result.results를 loop에 넣을 수 있습니다.
~/my-ansible/loop_register1.yml
---
- hosts: localhost
tasks:
- name: Loop echo test
ansible.builtin.shell: "echo 'I can speak {{ item }}'"
loop:
- Korean
- English
register: result
- name: Show result
ansible.builtin.debug:
msg: "Stdout: {{ item.stdout }}"
loop: "{{ result.results }}"
실행
ansible-playbook loop_register1.yml
반복문과 Register 변수 사용
반복문을 단순히 “여러 번 실행”하는 데서 끝내지 않고, 반복 실행된 작업의 출력 결과를 모두 캡처해야 하는 경우가 있습니다. 이때 사용하는 것이 register 변수입니다. register를 사용하면 반복 실행된 각 작업 결과가 하나의 변수에 저장되며, 이후 태스크에서 그 값을 다시 활용할 수 있습니다.
- ansible.builtin.shell 모듈: https://docs.ansible.com/ansible/latest/collections/ansible/builtin/shell_module.html
- Return Values: https://docs.ansible.com/ansible/latest/reference_appendices/common_return_values.html
반복 실행 결과를 register로 저장하기
아래 플레이북은 shell 모듈로 "I can speak {{ item }}" 메시지를 출력합니다.
loop로 Korean, English를 반복하고, 실행 결과를 register: result로 저장한 뒤 debug로 result 전체를 출력합니다.
~/my-ansible/loop_register.yml
---
- hosts: localhost
tasks:
- name: Loop echo test
ansible.builtin.shell: "echo 'I can speak {{ item }}'"
loop:
- Korean
- English
register: result
- name: Show result
ansible.builtin.debug:
var: result
실행
ansible-playbook loop_register.yml
실행 결과를 보면 register로 저장된 result 안에 results: [ ... ] 형태로 반복 실행 결과가 배열로 쌓이는 것을 확인할 수 있습니다. (아래는 예시 출력 형태입니다)
TASK [Loop echo test] *****************************************************************
changed: [localhost] => (item=Korean)
changed: [localhost] => (item=English)
TASK [Show result] ********************************************************************
ok: [localhost] => {
"result": {
"changed": true,
"msg": "All items completed",
"results": [
{
...
"item": "Korean",
"rc": 0,
"stdout": "I can speak Korean",
"stderr": ""
},
{
...
"item": "English",
"rc": 0,
"stdout": "I can speak English",
"stderr": ""
}
],
"skipped": false
}
}
register 결과(result.results)를 다시 loop로 순회하기
앞에서 result 내부의 results가 배열 형식으로 저장된 것을 확인했습니다.
이제 그 배열의 각 요소에서 필요한 값만 꺼내서 출력해보겠습니다.
아래 예제는 result.results를 loop의 반복 대상으로 사용하고, 각 아이템의 표준 출력(stdout)을 item.stdout으로 출력합니다.
~/my-ansible/loop_register1.yml
---
- hosts: localhost
tasks:
- name: Loop echo test
ansible.builtin.shell: "echo 'I can speak {{ item }}'"
loop:
- Korean
- English
register: result
- name: Show result
ansible.builtin.debug:
msg: "Stdout: {{ item.stdout }}"
loop: "{{ result.results }}"
실행
ansible-playbook loop_register1.yml
출력은 아래처럼 Stdout: ... 형태로 반복 결과가 순서대로 정리되어 보입니다.
TASK [Loop echo test] *****************************************************************
changed: [localhost] => (item=Korean)
changed: [localhost] => (item=English)
TASK [Show result] ********************************************************************
ok: [localhost] => ... => {
"msg": "Stdout: I can speak Korean"
}
ok: [localhost] => ... => {
"msg": "Stdout: I can speak English"
}
Return Values 공통 필드 정리
반복 실행 결과를 볼 때 주로 확인하는 값들은 다음과 같습니다.
링크: https://docs.ansible.com/ansible/latest/reference_appendices/common_return_values.html#common)
- stderr: 명령의 표준 에러
- stderr_lines: 표준 에러 출력을 행 단위로 구분한 목록
- stdout: 명령의 표준 출력
- stdout_lines: 표준 출력을 행 단위로 구분한 목록
- rc: return code(반환 코드)
| 반환 | 코드의미 |
| 0 | 성공 |
| 1 | 일반 오류 |
| 2 | 잘못된 인자 |
| 126 | 실행 권한 없음 |
| 127 | 명령 없음 |
| 130 | Ctrl+C 종료 |
| 137 | SIGKILL |
| 139 | Segmentation fault |
명령 실행 이후 반환 코드(exit status)는 아래처럼 확인합니다.
# 바로 직전 명령의 반환 코드 확인
echo $?
# rc = 0
ls -al
echo $?
# rc = 2
ls abc
echo $?
# rc = 127
aaa
echo $?
조건문
앤서블은 조건문을 사용하여 특정 조건이 충족될 때만 작업(Task) 또는 플레이(Play)를 실행할 수 있습니다. 예를 들면 조건문을 사용하여 호스트의 운영체제 버전에 따라 필요한 작업만 수행하도록 구성할 수 있습니다. 조건문에서는 플레이 변수, 작업 변수, 앤서블 팩트 등을 함께 사용할 수 있습니다.
- Conditionals when: https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_conditionals.html
조건 작업 구문: when
when 문은 조건부로 작업을 실행할 때 테스트할 조건을 값으로 사용합니다.
조건이 충족되면 작업이 실행되고, 조건이 충족되지 않으면 해당 작업은 건너뜁니다(skipping).
가장 단순한 형태는 Boolean 변수(true/false)를 조건으로 사용하는 방식입니다.
Boolean 변수가 true일 때만 실행
~/my-ansible/when_task.yml
---
- hosts: localhost
vars:
run_my_task: true
tasks:
- name: echo message
ansible.builtin.shell: "echo test"
when: run_my_task
register: result
- name: Show result
ansible.builtin.debug:
var: result
실행
ansible-playbook when_task.yml
실행 결과에서는 조건이 true이므로 echo message 태스크가 수행됩니다.
TASK [echo message] *******************************************************************
changed: [localhost]
Boolean 변수가 false이면 건너뜀(skipping)
run_my_task 값을 false로 수정한 뒤 실행해보면, 조건이 충족되지 않아 태스크가 수행되지 않고 skipping 처리됩니다.
---
- hosts: localhost
vars:
run_my_task: false
tasks:
- name: echo message
ansible.builtin.shell: "echo test"
when: run_my_task
register: result
- name: Show result
ansible.builtin.debug:
var: result
실행
ansible-playbook when_task.yml
출력 예시는 다음과 같습니다.
TASK [echo message] *******************************************************************
skipping: [localhost]
조건 연산자 사용
when 문에는 bool 변수(true/false) 외에도 다양한 조건 연산자를 사용할 수 있습니다. 예를 들어 다음과 같은 형태로 조건을 구성할 수 있습니다.
- ansible_facts['machine'] == "x86_64"
→ ansible_facts['machine'] 값이 x86_64일 때만 태스크가 수행됩니다.
아래는 주로 사용하는 조건 연산자 예시입니다.
| 연산 예시 | 설명 |
| ansible_facts[’machine’] == “x86_64” | ansible_facts[’machine’] 값이 x86_64와 같으면 true |
| max_memory == 512 | max_memory 값이 512와 같다면 true |
| min_memory < 128 | min_memory 값이 128보다 작으면 true |
| min_memory > 256 | min_memory 값이 256보다 크면 true |
| min_memory <= 256 | min_memory 값이 256보다 작거나 같으면 true |
| min_memory >= 512 | min_memory 값이 512보다 크거나 같으면 true |
| min_memory != 512 | min_memory 값이 512와 같지 않으면 true |
| min_memory is defined | min_memory 라는 변수가 있으면 true |
| min_memory is not defined | min_memory 라는 변수가 없으면 true |
| memory_available | memory 값이 true이며 true, 이때 해당 값이 1이거나 True 또는 yes면 true |
| not memory_available | memory 값이 false이며 true, 이때 해당 값이 0이거나 False 또는 no면 true |
| ansible_facts[’distribution’] in supported_distros | ansible_facts[’distribution’]의 값이 supported_distros 라는 변수에 있으면 true |
- != : 값이 같지 않을 때 참(true)입니다.
- >, >=, <=, < : 초과/이상/이하/미만일 때 참(true)입니다.
- not : 조건의 부정입니다.
- and, or : 여러 조건을 조합할 때 사용합니다.
- in : 값이 포함된 경우 참(true)입니다.
- is defined : 변수가 정의된 경우 참(true)입니다.
in 연산자로 지원 OS만 처리하기
OS 종류에 따라 태스크를 수행하는 예제로 조건 연산자를 적용해봅니다.
vars 키워드로 supported_distros 변수를 목록 형태로 저장하고, when에서 in을 사용합니다.
~/my-ansible/check-os.yml
---
- hosts: all
vars:
supported_distros:
- Ubuntu
- CentOS
tasks:
- name: Print supported os
ansible.builtin.debug:
msg: "This {{ ansible_facts['distribution'] }} need to use apt"
when: ansible_facts['distribution'] in supported_distros
실행
ansible-playbook check-os.yml
출력에서는 ansible_facts['distribution'] 값이 Ubuntu 또는 CentOS인 호스트에서만 메시지가 출력됩니다.
TASK [Print supported os] ************************************************************
ok: [tnode1-ubuntu.local] => {
"msg": "This Ubuntu need to use apt"
}
ok: [tnode2-ubuntu.local] => {
"msg": "This Ubuntu need to use apt"
}
복수 조건문(여러 조건 조합)
when 문은 단일 조건뿐 아니라 복수 조건문도 사용할 수 있습니다. 예를 들어 특정 OS이면서 특정 아키텍처인 경우에만 실행되도록 구성할 수 있습니다.
or 연산자로 여러 OS 허용
~/my-ansible/check-os1.yml
---
- hosts: all
tasks:
- name: Print os type
ansible.builtin.debug:
msg: "OS Type {{ ansible_facts['distribution'] }}"
when: ansible_facts['distribution'] == "CentOS" or ansible_facts['distribution'] == "Ubuntu"
실행
ansible-playbook check-os1.yml
and 연산자로 OS + 버전 조건 지정
Ubuntu이면서 Version이 24.04인 경우만 작업이 수행되도록 구성합니다.
~/my-ansible/check-os2.yml
---
- hosts: all
tasks:
- name: Print os type
ansible.builtin.debug:
msg: >
OS Type: {{ ansible_facts['distribution'] }}
OS Version: {{ ansible_facts['distribution_version'] }}
when: ansible_facts['distribution'] == "Ubuntu" and ansible_facts['distribution_version'] == "24.04"
실행
ansible-playbook check-os2.yml
출력 예시는 다음과 같습니다.
TASK [Print os type] *****************************************************************
ok: [tnode1-ubuntu.local] => {
"msg": "OS Type: Ubuntu OS Version: 24.04\n"
}
ok: [tnode2-ubuntu.local] => {
"msg": "OS Type: Ubuntu OS Version: 24.04\n"
}
and 조건을 목록 형태로 표현하기
and 연산자는 조건문에서 바로 사용하거나, 아래처럼 목록 형태로도 표현할 수 있습니다.
~/my-ansible/check-os3.yml
---
- hosts: all
tasks:
- name: Print os type
ansible.builtin.debug:
msg: >
OS Type: {{ ansible_facts['distribution'] }}
OS Version: {{ ansible_facts['distribution_version'] }}
when:
- ansible_facts['distribution'] == "Ubuntu"
- ansible_facts['distribution_version'] == "24.04"
실행
ansible-playbook check-os3.yml
and + or 혼합 조건
두 연산자를 함께 사용하여, Rocky 9.6 이거나 Ubuntu 24.04 인 경우를 표현합니다.
~/my-ansible/check-os4.yml
---
- hosts: all
tasks:
- name: Print os type
ansible.builtin.debug:
msg: >
OS Type: {{ ansible_facts['distribution'] }}
OS Version: {{ ansible_facts['distribution_version'] }}
when: >
( ansible_facts['distribution'] == "Rocky" and
ansible_facts['distribution_version'] == "9.6" )
or
( ansible_facts['distribution'] == "Ubuntu" and
ansible_facts['distribution_version'] == "24.04" )
실행 전 확인 예시는 다음과 같습니다.
ansible tnode1 -m ansible.builtin.setup | grep -iE 'os_family|ansible_distribution|fqdn'
ansible tnode3 -m ansible.builtin.setup | grep -iE 'os_family|ansible_distribution|fqdn'
실행
ansible-playbook check-os4.yml
반복문과 조건문 함께 사용하기
반복문과 조건문은 함께 사용할 수 있습니다. 반복으로 돌리는 각 아이템에 대해 when 조건을 걸면, 조건을 만족하는 아이템만 태스크가 실행되고 나머지는 skipping 처리됩니다.
- ansible.builtin.command 모듈: https://docs.ansible.com/ansible/latest/collections/ansible/builtin/command_module.html
Facts(mounts) + loop + when 조합
아래 플레이북은 ansible_facts['mounts'] 값을 반복하면서, mount가 / 이고 size_available 값이 300000000(300MB)보다 큰 경우에만 메시지를 출력합니다.
~/my-ansible/check-mount.yml
---
- hosts: db
tasks:
- name: Print Root Directory Size
ansible.builtin.debug:
msg: "Directory {{ item.mount }} size is {{ item.size_available }}"
loop: "{{ ansible_facts['mounts'] }}"
when: item['mount'] == "/" and item['size_available'] > 300000000
실행 전 모니터링(캐시 파일 확인)
watch -d -n 1 ls -l ~/my-ansible/myfacts
실행 시에는 캐시를 비우기 위해 --flush-cache 옵션을 함께 사용합니다.
ansible-playbook check-mount.yml --flush-cache
출력에서는 / 마운트에 해당하는 아이템만 ok로 메시지가 출력되고, 나머지 마운트 아이템들은 조건에 맞지 않아 skipping 처리되는 형태를 확인할 수 있습니다.
register 결과를 when 조건으로 사용하기
조건문을 사용할 때는 반복문뿐만 아니라, register 키워드로 저장한 작업 변수도 조건으로 사용할 수 있습니다.
아래 예제는 systemctl is-active rsyslog 명령을 실행해 결과를 result 변수에 저장하고, 다음 태스크에서 result.stdout 값이 "active"일 때만 출력합니다.
~/my-ansible/register-when.yml
---
- hosts: all
tasks:
- name: Get rsyslog service status
ansible.builtin.command: systemctl is-active rsyslog
register: result
- name: Print rsyslog status
ansible.builtin.debug:
msg: "Rsyslog status is {{ result.stdout }}"
when: result.stdout == "active"
실행
ansible-playbook register-when.yml
출력에서는 각 호스트에서 rsyslog 상태를 확인한 뒤, 조건에 맞는 경우에만 Print rsyslog status 태스크가 수행되어 상태가 출력됩니다.
핸들러 및 작업 실패 처리
앤서블 모듈은 멱등(idempotent)이 가능하도록 설계되어 있습니다. 즉 플레이북을 여러 번 실행해도 결과는 항상 동일합니다. 또한 플레이 및 해당 작업은 여러 번 실행할 수 있지만, 해당 호스트는 원하는 상태로 만드는 데 필요한 경우에만 변경됩니다.
하지만 한 작업에서 시스템을 변경해야 하는 경우 추가 작업을 실행해야 할 수도 있습니다. 예를 들어 서비스의 구성 파일을 변경하려면 변경 내용이 적용되도록 서비스를 다시 로드해야 합니다. 이때 핸들러는 다른 작업에서 트리거한 알림에 응답하는 작업이며, 해당 호스트에서 작업이 변경될 때만 핸들러에 통지합니다.
Handlers: running operations on change
https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_handlers.html
앤서블 핸들러
- 참고: https://gruuuuu.github.io/ansible/ansible-handler/
- 앤서블에서 핸들러를 사용하려면 notify 문을 사용하여 명시적으로 호출해야 합니다.
- 핸들러를 정의할 때는 같은 이름으로 여러 개를 정의하기보다는 각각의 고유한 이름을 사용하여 정의하는 것이 좋습니다.
아래 플레이북은 rsyslog 재시작 태스크가 실행되면 notify를 통해 print msg 핸들러를 호출합니다. 핸들러는 handlers 키워드로 시작합니다.
~/my-ansible/handler-sample.yml
---
- hosts: tnode2
tasks:
- name: restart rsyslog
ansible.builtin.service:
name: "rsyslog"
state: restarted
notify:
- print msg
handlers:
- name: print msg
ansible.builtin.debug:
msg: "rsyslog is restarted"
실행
ansible-playbook handler-sample.yml
출력에서는 restart rsyslog가 changed로 수행된 뒤, RUNNING HANDLER [print msg]가 이어서 실행되며 메시지를 출력하는 흐름을 확인할 수 있습니다. 이후 동일 플레이북을 다시 실행해보며 결과를 비교합니다.
ansible-playbook handler-sample.yml
- ansible.builtin.apt: https://docs.ansible.com/ansible/latest/collections/ansible/builtin/apt_module.html
- ansible.builtin.dnf: https://docs.ansible.com/ansible/latest/collections/ansible/builtin/dnf_module.html
작업 실패 무시(ignore_errors)
앤서블은 플레이 시 각 작업의 반환 코드를 평가하여 작업의 성공 여부를 판단합니다. 일반적으로 작업이 실패하면 앤서블은 이후의 모든 작업을 건너뜁니다.
하지만 작업이 실패해도 플레이를 계속 실행해야 하는 경우 ignore_errors 키워드를 사용할 수 있습니다.
Error handling in playbooks
https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_error_handling.html
아래는 apache3 패키지를 설치하는 작업을 기준으로 ignore_errors 적용 유무를 비교하는 예시입니다.
~/my-ansible/ignore-example-1.yml
12.2. 작업 실패 무시(ignore_errors)
앤서블은 플레이 시 각 작업의 반환 코드를 평가하여 작업의 성공 여부를 판단합니다. 일반적으로 작업이 실패하면 앤서블은 이후의 모든 작업을 건너뜁니다.
하지만 작업이 실패해도 플레이를 계속 실행해야 하는 경우 ignore_errors 키워드를 사용할 수 있습니다.
Error handling in playbooks
https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_error_handling.html
아래는 apache3 패키지를 설치하는 작업을 기준으로 ignore_errors 적용 유무를 비교하는 예시입니다.
~/my-ansible/ignore-example-1.yml
~/my-ansible/ignore-example-2.yml
---
- hosts: tnode1
tasks:
- name: Install apache3
ansible.builtin.apt:
name: apache3
state: latest
ignore_errors: yes
- name: Print msg
ansible.builtin.debug:
msg: "Before task is ignored"
실행하여 출력 내용을 비교합니다.
ansible-playbook ignore-example-1.yml
ansible-playbook ignore-example-2.yml
작업 실패 후에도 핸들러 실행(force_handlers)
일반적으로 작업이 실패하고 해당 호스트에서 플레이가 중단되면, 이전 작업에서 알림을 받은 모든 핸들러가 실행되지 않습니다.
하지만 플레이북에 force_handlers: yes를 설정하면 이후 작업이 실패하여 플레이가 중단되어도 알림을 받은 핸들러가 호출됩니다.
아래 두 플레이북은 동일하게 restart rsyslog에서 핸들러를 notify 하고, 이어서 apache3 설치 작업을 수행합니다. 차이는 force_handlers: yes 설정 유무입니다.
~/my-ansible/force-handler-1.yml
---
- hosts: tnode2
tasks:
- name: restart rsyslog
ansible.builtin.service:
name: "rsyslog"
state: restarted
notify:
- print msg
- name: install apache3
ansible.builtin.apt:
name: "apache3"
state: latest
handlers:
- name: print msg
ansible.builtin.debug:
msg: "rsyslog is restarted"
~/my-ansible/force-handler-2.yml
---
- hosts: tnode2
force_handlers: yes
tasks:
- name: restart rsyslog
ansible.builtin.service:
name: "rsyslog"
state: restarted
notify:
- print msg
- name: install apache3
ansible.builtin.apt:
name: "apache3"
state: latest
handlers:
- name: print msg
ansible.builtin.debug:
msg: "rsyslog is restarted"
실행하여 결과를 비교합니다.
ansible-playbook force-handler-1.yml
ansible-playbook force-handler-2.yml
작업 실패 조건 지정(failed_when)과 fail 모듈
Error handling in playbooks: defining failure
https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_error_handling.html#defining-failure
- ansible.builtin.fail: https://docs.ansible.com/ansible/latest/collections/ansible/builtin/fail_module.html
command/shell 계열 모듈을 사용하는 경우, 스크립트가 에러 메시지를 출력하더라도 앤서블은 성공으로 간주하는 상황이 발생할 수 있습니다. 이런 경우 failed_when으로 실패 처리 조건을 명시할 수 있습니다.
스크립트 동작 확인 및 정리
~/my-ansible/adduser-script.sh
wget https://raw.githubusercontent.com/naleeJang/Easy-Ansible/refs/heads/main/chapter_07.3/adduser-script.sh
chmod +x adduser-script.sh
ls -l adduser-script.sh
./adduser-script.sh
./adduser-script.sh "user1" "qwe123"
tail -n 3 /etc/passwd
sudo userdel -rf user1
tail -n 3 /etc/passwd
스크립트를 tnode1로 복사 후 실행 확인
ansible -m copy -a 'src=/root/my-ansible/adduser-script.sh dest=/root/adduser-script.sh' tnode1
ssh tnode1 ls -l /root
ssh tnode1
chmod +x adduser-script.sh
./adduser-script.sh
exit
failed_when 적용 전/후 비교
~/my-ansible/failed-when-1.yml
---
- hosts: tnode1
tasks:
- name: Run user add script
ansible.builtin.shell: /root/adduser-script.sh
register: command_result
- name: Print msg
ansible.builtin.debug:
msg: "{{ command_result.stdout }}"
~/my-ansible/failed-when-2.yml
- failed_when 조건식: command_result.stdout에 "Please..." 문자열이 포함되면 fail 처리
---
- hosts: tnode1
tasks:
- name: Run user add script
ansible.builtin.shell: /root/adduser-script.sh
register: command_result
failed_when: "'Please input user id and password' in command_result.stdout"
- name: Print msg
ansible.builtin.debug:
msg: "{{ command_result.stdout }}"
실행
ansible-playbook failed-when-1.yml
ansible -m shell -a "tail -n 3 /etc/passwd" tnode1
ansible-playbook failed-when-2.yml
ansible -m shell -a "tail -n 3 /etc/passwd" tnode1
fail 모듈로 사용자 정의 메시지 출력
~/my-ansible/failed-when-custom.yml
---
- hosts: tnode1
tasks:
- name: Run user add script
ansible.builtin.shell: /root/adduser-script.sh
register: command_result
ignore_errors: yes
- name: Report script failure
ansible.builtin.fail:
msg: "{{ command_result.stdout }}"
when: "'Please input user id and password' in command_result.stdout"
실행
ansible-playbook failed-when-custom.yml
블록(block) 기반 오류 처리(rescue/always)
Blocks
https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_blocks.html
- ansible.builtin.find: https://docs.ansible.com/ansible/latest/collections/ansible/builtin/find_module.html
앤서블은 block 문법으로 작업을 논리적으로 묶고, rescue, always를 통해 오류 처리를 구성할 수 있습니다.
- block: 실행할 기본 작업 정의
- rescue: block 절 작업 실패 시 실행할 작업 정의
- always: 성공/실패 여부와 관계없이 항상 실행
block/rescue/always 예제
~/my-ansible/block-example.yml
- block에서 failed_when을 사용하여 result.msg에 "Not.." 메시지가 포함되면 실패 처리
- rescue는 block 작업 실패 시 실행되며, 디렉터리가 없으면 생성
- always는 항상 실행되며, 로그 파일 생성
---
- hosts: tnode2
vars:
logdir: /var/log/daily_log
logfile: todays.log
tasks:
- name: Configure Log Env
block:
- name: Find Directory
ansible.builtin.find:
paths: "{{ logdir }}"
register: result
failed_when: "'Not all paths' in result.msg"
rescue:
- name: Make Directory when Not found Directory
ansible.builtin.file:
path: "{{ logdir }}"
state: directory
mode: '0755'
always:
- name: Create File
ansible.builtin.file:
path: "{{ logdir }}/{{ logfile }}"
state: touch
mode: '0644'
실행
ansible-playbook block-example.yml
실행 후 tnode2에서 디렉터리 및 로그 파일 생성 여부를 확인합니다.
ansible -m shell -a "ls -l /var/log/daily_log/" tnode2
다시 한 번 실행하여 결과를 비교합니다.
ansible-playbook block-example.yml
ansible -m shell -a "ls -l /var/log/daily_log/" tnode2
롤 구조 소개 및 사용법
롤(Role)은 플레이북 내용을 기능 단위로 분리해서 공통 부품처럼 관리/재사용하기 위한 표준 구조입니다. 프로젝트가 커질수록 플레이북이 길어지고 중복이 늘어나기 쉬운데, 롤을 도입하면 “웹 서버 설치”, “방화벽 정책 적용”, “설정 파일 배포” 같은 기능을 모듈화해서 유지보수와 협업이 훨씬 편해집니다.
참고 문서: https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_reuse_roles.html
롤을 쓰는 이유
롤은 다음 목적에 잘 맞습니다.
- 플레이북에서 전달된 변수를 받아 재사용할 수 있습니다. (변수 미설정 시 defaults로 기본값을 둡니다.)
- tasks/handlers/files/templates 등을 기능별로 그룹화하여 공유가 쉬워집니다.
- 웹/DB/깃 리포지터리 같은 시스템 구성의 필수 요소를 표준 방식으로 정의할 수 있습니다.
- 대규모 프로젝트에서 구조가 고정되므로 관리 난이도가 내려갑니다.
- 여러 사람이 동시에 개발하기도 쉬워집니다.
- 잘 만든 롤은 Ansible Galaxy를 통해 공유/재사용할 수 있습니다.
앤서블 롤 표준 디렉터리 구조
롤은 “롤 이름 디렉터리” 아래에 표준화된 하위 디렉터리로 구성됩니다.
| defaults | main.yml에 낮은 우선순위의 기본 변수값을 둡니다. 플레이/인벤토리에서 덮어쓸 수 있습니다. |
| files | 롤에서 참조하는 정적 파일을 둡니다. |
| handlers | main.yml에 롤의 핸들러를 정의합니다. |
| meta | main.yml에 롤 메타정보(작성자/라이선스/플랫폼/의존성 등)를 둡니다. |
| tasks | main.yml에 롤의 **주요 작업(Task)**을 정의합니다. |
| templates | Jinja2 템플릿 파일을 둡니다. |
| tests | 테스트용 인벤토리/플레이북을 둘 수 있습니다. |
| vars | main.yml에 롤 내부에서 쓰는 높은 우선순위 변수를 둡니다. 보통 플레이북에서 변경하지 않는 값에 둡니다. |
롤 생성(ansible-galaxy role init)
먼저 롤 생성 명령을 확인합니다.
# (옵션) 이전 실습 yml 파일 삭제
rm -r *.yml
# role 서브 명령어 확인
ansible-galaxy role -h
롤을 생성하고 구조를 확인합니다.
# 롤 생성
ansible-galaxy role init my-role
# 디렉터리 구조 확인
tree ./my-role/
예시 출력은 다음과 같은 형태입니다.
my-role/
├── defaults
│ └── main.yml
├── files
├── handlers
│ └── main.yml
├── meta
│ └── main.yml
├── README.md
├── tasks
│ └── main.yml
├── templates
├── tests
│ ├── inventory
│ └── test.yml
└── vars
└── main.yml
롤 기반 플레이북 개발(my-role)
여기서는 “웹 서버 설치 + index.html 배포 + 서비스 재시작”을 롤로 구성합니다. 롤이 호출되면 아래 흐름으로 동작하도록 만듭니다.
- 현재 호스트의 OS가 지원 목록에 포함되는지 확인합니다.
- httpd(예제에서는 Ubuntu의 apache2) 관련 패키지를 설치합니다. (패키지가 여러 개이므로 loop 사용)
- files 디렉터리의 index.html을 관리 노드의 웹 루트에 복사합니다.
- 복사가 완료되면 핸들러로 서비스를 재시작합니다.
롤 구성
- 롤 이름: my-role
- tasks: 패키지 설치 + 파일 복사(핸들러 notify)
- files: index.html
- handlers: 서비스 재시작
- defaults: 외부에서 바꿀 수 있는 가변 변수(예: service_title)
- vars: 롤 내부 고정 변수(서비스명/경로/패키지 목록/지원 OS 목록 등)
tasks 작성
cd ~/my-ansible/my-role
~/my-ansible/my-role/tasks/main.yml
---
# tasks file for my-role
- name: install service {{ service_title }}
ansible.builtin.apt:
name: "{{ item }}"
state: latest
loop: "{{ httpd_packages }}"
when: ansible_facts.distribution in supported_distros
- name: copy conf file
ansible.builtin.copy:
src: "{{ src_file_path }}"
dest: "{{ dest_file_path }}"
notify:
- restart service
- 첫 번째 태스크는 변수 service_title을 출력에 반영하고, 패키지 목록(httpd_packages)을 loop로 설치합니다.
- 두 번째 태스크는 정적 파일을 복사하고, 변경이 발생하면 restart service 핸들러를 호출합니다.
정적 파일(files) 생성
~/my-ansible/my-role/files/index.html
echo "Hello! Ansible" > files/index.html
handlers 작성
~/my-ansible/my-role/handlers/main.yml
---
# handlers file for my-role
- name: restart service
ansible.builtin.service:
name: "{{ service_name }}"
state: restarted
defaults 작성(가변 변수)
~/my-ansible/my-role/defaults/main.yml
echo 'service_title: "Apache Web Server"' >> defaults/main.yml
- defaults의 값은 우선순위가 낮아서, 롤을 호출하는 플레이북에서 덮어쓸 수 있습니다.
vars 작성(불변 변수)
~/my-ansible/my-role/vars/main.yml
---
# vars file for my-role
service_name: apache2
src_file_path: ../files/index.html
dest_file_path: /var/www/html
httpd_packages:
- apache2
- apache2-doc
supported_distros:
- Ubuntu
플레이북에서 롤 호출(import_role)
롤만 만들어서는 실행되지 않고, 롤을 호출하는 플레이북이 필요합니다.
참고:
- https://docs.ansible.com/ansible/latest/collections/ansible/builtin/import_role_module.html
- https://docs.ansible.com/ansible/latest/collections/ansible/builtin/include_role_module.html
- import_role: 롤을 정적으로 포함
- include_role: 롤을 동적으로 포함(반복/조건에 따라 포함 가능)
import_role로 호출
cd ~/my-ansible
pwd
~/my-ansible/role-example.yml
---
- hosts: tnode1
tasks:
- name: Print start play
ansible.builtin.debug:
msg: "Let's start role play"
- name: Install Service by role
ansible.builtin.import_role:
name: my-role
실행
ansible-playbook role-example.yml
실행 흐름은 “Print start play → my-role tasks 실행 → notify된 handler 실행(필요 시)” 순서로 진행됩니다. 적용 후 웹 응답도 확인합니다.
curl tnode1
defaults 변수 재정의(service_title)
service_title은 defaults에 정의되어 있으므로, 롤 호출 위치에서 재정의할 수 있습니다.
~/my-ansible/role-example.yml
---
- hosts: tnode1
tasks:
- name: Print start play
ansible.builtin.debug:
msg: "Let's start role play"
- name: Install Service by role
ansible.builtin.import_role:
name: my-role
vars:
service_title: Httpd
실행
ansible-playbook role-example.yml
curl tnode1
정적 파일(index.html) 변경 반영
echo "Hello! CloudNet@" > my-role/files/index.html
ansible-playbook role-example.yml
curl tnode1
대상 호스트 변경
hosts를 tnode2로 바꿔 실행한 뒤 curl tnode2로 확인합니다.
플레이북에서 roles 섹션 사용(여러 롤 조합)
롤을 호출하는 또 다른 방법은 플레이북 상단에 roles: 섹션으로 나열하는 방식입니다. 여러 롤을 조합할 때 특히 깔끔합니다.
여기서는 my-role(웹) + my-role2(방화벽) 를 조합합니다.
참고:
- ansible.posix.firewalld: https://docs.ansible.com/ansible/latest/collections/ansible/posix/firewalld_module.html
tnode1에 firewalld 설치 및 기본 설정
ssh tnode1
# firewalld 설치
apt install firewalld -y
systemctl status firewalld
# 기본 정책 확인
firewall-cmd --list-all
# 영구적으로 public zone에서 TCP 8080 포트 추가 설정
firewall-cmd --permanent --zone=public --add-port=8080/tcp
# 적용
firewall-cmd --reload
firewall-cmd --list-all
# 로컬에서 apache2 web 접속 확인
curl localhost
exit
이 상태에서 외부에서 curl tnode1이 실패하는 것을 확인합니다(방화벽 정책 영향 확인).
ping -c 1 tnode1
curl tnode1
my-role2 생성
cd ~/my-ansible
ansible-galaxy role init my-role2
tree my-role2
my-role2 tasks 작성(firewalld에 http/https 서비스 허용 + reload)
my-ansible/my-role2/tasks/main.yml
---
# tasks file for my-role2
- name: Config firewalld
ansible.posix.firewalld:
service: "{{ item }}"
permanent: true
state: enabled
loop: "{{ service_port }}"
- name: Reload firewalld
ansible.builtin.service:
name: firewalld
state: reloaded
my-role2 vars 작성(허용 서비스 목록)
my-ansible/my-role2/vars/main.yml
---
# vars file for my-role2
service_port:
- http
- https
role-example2.yml 작성(roles 섹션으로 두 롤 실행)
my-ansible/role-example2.yml
---
- hosts: tnode1
roles:
- my-role
- my-role2
tasks:
- name: Print finish role play
ansible.builtin.debug:
msg: "Finish role play"
--check로 흐름을 먼저 확인할 수 있고, 이후 실제 실행합니다.
ansible-playbook --check role-example2.yml
ansible-playbook role-example2.yml
# 확인
curl tnode1
ansible -m shell -a "firewall-cmd --list-all" tnode1
13.6.6. roles 섹션에서 가변 변수 전달(role-example3.yml)
my-ansible/role-example3.yml
---
- hosts: tnode1
roles:
- role: my-role
service_title: "Httpd Web"
- role: my-role2
tasks:
- name: Print finish role play
ansible.builtin.debug:
msg: "Finish role play"
실행
ansible-playbook role-example3.yml
특수 작업 섹션(pre_tasks / post_tasks)과 roles의 실행 순서
roles와 함께 자주 쓰는 섹션이 pre_tasks, post_tasks입니다.
- pre_tasks: roles보다 먼저 실행
- post_tasks: tasks 및 notify로 실행된 handlers 이후 실행
참고:
- ansible.builtin.uri: https://docs.ansible.com/ansible/latest/collections/ansible/builtin/uri_module.html
~/my-ansible/special_role.yml
---
- hosts: tnode1
pre_tasks:
- name: Print Start role
ansible.builtin.debug:
msg: "Let's start role play"
roles:
- role: my-role
- role: my-role2
tasks:
- name: Curl test
ansible.builtin.uri:
url: http://tnode1
return_content: true
register: curl_result
notify: Print result
changed_when: true
post_tasks:
- name: Print Finish role
ansible.builtin.debug:
msg: "Finish role play"
handlers:
- name: Print result
ansible.builtin.debug:
msg: "{{ curl_result.content }}"
실행
ansible-playbook special_role.yml
출력 순서는 다음 흐름으로 읽으면 됩니다.
- Gathering Facts
- pre_tasks 실행
- roles(my-role → my-role2) 실행
- tasks(Curl test) 실행 → notify 발생
- handlers(Print result) 실행
- post_tasks 실행
실습 정리(firewalld 제거)
ansible -m shell -a "systemctl stop firewalld" tnode1
ansible -m shell -a "apt remove firewalld -y" tnode1
Tags
플레이북이 커질수록 “전체를 매번 실행”하는 방식은 비효율적일 수 있습니다. 이때 **태그(tags)**를 사용하면, 특정 작업만 선택적으로 실행하거나 특정 작업을 건너뛰도록 구성할 수 있습니다.
태그 기반 실행 흐름은 아래 2단계로 이해하면 됩니다.
- 작업에 태그를 추가합니다.
태그는 개별 task에 붙일 수도 있고, block / play / role / import 수준에서 정의하여 **상속(inheritance)**시키는 방식도 가능합니다. - 플레이북 실행 시 명령줄에서 태그를 선택하거나(--tags) 건너뜁니다(--skip-tags).
즉, tags 키워드는 “태그를 정의하고 작업에 붙이는 역할”만 하며, 실행 대상을 고르는 건 실행 시점(명령줄)에서만 가능합니다.
tags 키워드로 태그 추가하기
태그는 단일 작업(task)에만 붙이는 것이 아니라, block / play / role / import / include에서도 활용됩니다. 어디에 태그를 붙이느냐에 따라 “같은 태그를 여러 작업에 반복해서 적어야 하는지”, “상속이 되는지”, “include에서만 동작하는지”가 달라집니다.
1) 개별 작업에 태그 추가
가장 기본적인 방식입니다. 작업마다 tags:를 붙여서 작업 단위로 선택/제외할 수 있습니다.
tasks:
- name: Install the servers
ansible.builtin.yum:
name:
- httpd
- memcached
state: present
tags:
- packages
- webservers
- name: Configure the service
ansible.builtin.template:
src: templates/src.j2
dest: /etc/foo.conf
tags:
- configuration
여러 작업에 동일한 태그를 붙이면, 실행 시 --tags <tag>로 해당 작업들만 모아서 실행할 수 있습니다.
2) 블록(block)에 태그 추가
작업 여러 개를 하나의 블록으로 묶어서 같은 태그를 적용할 수 있습니다. 즉 “N개의 작업에 tags 줄을 반복해서 넣는 것”을 줄이기 위한 방식입니다.
- name: ntp tasks
tags: ntp
block:
- name: Install ntp
ansible.builtin.yum:
name: ntp
state: present
- name: Configure ntp
ansible.builtin.template:
src: ntp.conf.j2
dest: /etc/ntp.conf
notify:
- restart ntpd
- name: Enable and run ntpd
ansible.builtin.service:
name: ntpd
state: started
enabled: true
또한, 태그 선택은 block의 오류 처리(rescue/always)와 같은 논리보다 우선한다는 점을 함께 인지해야 합니다. 예를 들어 block 안에만 태그가 있고 rescue/always에는 태그가 없으면, 특정 태그로 실행했을 때 rescue/always가 포함되지 않도록 만들 수도 있습니다.
3) play에 태그 추가
play 단위로 태그를 붙이면, 해당 play에 포함된 작업 전체에 같은 태그를 적용할 수 있습니다.
- hosts: all
tags: ntp
tasks:
- name: Install ntp
ansible.builtin.yum:
name: ntp
state: present
4) role에 태그 추가 (3가지)
role에 태그를 붙이는 방법은 크게 3가지 관점으로 정리됩니다.
roles 섹션에서 role에 태그 추가
역할 수준에서 태그를 추가하면 role 내부 모든 작업에 태그가 지정되고, 종속 작업에도 태그가 지정됩니다.
roles:
- role: webserver
vars:
port: 5000
tags: [ web, foo ]
static import_role에 태그 추가
import_role(정적)로 불러오는 모든 작업에 태그를 적용합니다.
- hosts: webservers
tasks:
- name: Import the foo role
import_role:
name: foo
tags:
- bar
- baz
role 내부의 개별 task 또는 block에 태그 추가
이 방식은 role 내부에서 “일부 작업만 선택/제외”하고 싶은 경우에 연결되는 방식입니다. (includes 설명과 함께 이어집니다)
5) includes에 태그 추가 + 태그 상속(inheritance) 이슈
dynamic include(include_role, include_tasks)에 태그를 붙일 수는 있지만, 기본적으로 태그 상속이 적용되지 않습니다.
즉, include 항목에 붙인 태그는 include 항목 자체에만 적용되고, 포함된 파일/role 내부 작업에는 자동으로 붙지 않습니다.
그래서 “include로 재사용하면서도 태그 상속이 필요”한 경우에는 아래처럼 apply 키워드 또는 block을 활용하는 형태를 사용합니다.
- name: Apply the db tag to the include and to all tasks in db.yml
include_tasks:
file: db.yml
apply:
tags: db
tags: db
또는 block으로 감싸서 태그를 붙이는 방식입니다.
- block:
- name: Include tasks from db.yml
include_tasks: db.yml
tags: db
Special tags
특별한 동작을 위해 예약된 태그들이 있습니다.
- always, never : 작업 자체에 태그로 붙여서 “항상 실행” / “특별히 요청하지 않으면 실행하지 않음” 같은 동작을 만들 때 사용합니다.
- tagged, untagged, all : 실행할/건너뛸 태그를 선택할 때 사용합니다.
또한 내부 fact gathering 작업은 기본적으로 always 태그가 지정되어 있다는 점도 같이 인지합니다.
플레이북 실행 시 태그 선택/건너뛰기
태그를 붙여두면 실행 시점에서 아래 옵션들을 활용할 수 있습니다.
- --tags all : 기본 동작(전체 실행)
- --tags tag1,tag2 : tag1 또는 tag2가 붙은 작업만 실행
- --skip-tags tag3,tag4 : tag3 또는 tag4가 붙은 작업은 제외하고 실행
- --tags tagged : 태그가 하나 이상 붙은 작업만 실행
- --tags untagged : 태그가 없는 작업만 실행(항상 태그와의 관계 포함)
그리고 **우선순위는 “skip이 tags보다 우선”**합니다. 즉 --tags와 --skip-tags를 같이 쓰면, skip 조건이 먼저 적용됩니다.
태그 사용 결과 미리보기
태그/작업을 “실행하지 않고 확인”할 때 아래 옵션을 사용할 수 있습니다.
- --list-tags : 사용 가능한 태그 목록 확인
- --list-tasks : 특정 태그 조건에서 어떤 작업이 실행될지 미리보기
다만 동적으로 포함된 파일이나 역할 내부 태그/작업은 표시되지 않는 제한이 있습니다.
Tags 실습
아래는 두 작업에 서로 다른 태그를 지정한 뒤, 실행 시 선택/건너뛰기를 확인하는 실습입니다.
#
cat << EOF > tags1.yml
---
- hosts: web
tasks:
- name: Install the servers
ansible.builtin.apt:
name:
- htop
state: present
tags:
- packages
- name: Restart the service
ansible.builtin.service:
name: rsyslog
state: restarted
tags:
- service
EOF
#
ansible-playbook tags1.yml --list-tags
*playbook: tags1.yml
play #1 (web): web TAGS: []
TASK TAGS: [packages, service]*
#
ansible-playbook tags1.yml --tags "packages" --list-tasks
*playbook: tags1.yml
play #1 (web): web TAGS: []
tasks:
Install the servers TAGS: [packages]*
# packages 태그가 포함된 task 만 실행 확인
ansible-playbook tags1.yml --tags "packages"
*...
TASK [Install the servers] **********************************************************************
ok: [tnode1]
ok: [tnode2]
...*
# packages 태그가 포함된 task 만 제외하고 실행 확인 : --skip-tags
ansible-playbook tags1.yml --skip-tags "packages"
*...
TASK [Restart the service] **********************************************************************
changed: [tnode2]
changed: [tnode1]
...*
# 태그가 하나 이상 있는 작업 실행 : --tags tagged
ansible-playbook tags1.yml --tags tagged
*...
TASK [Install the servers] **********************************************************************
ok: [tnode1]
ok: [tnode2]
TASK [Restart the service] **********************************************************************
changed: [tnode2]
changed: [tnode1]
...*
앤서블 갤럭시
롤을 생성할 때 사용했던 **앤서블 갤럭시(ansible-galaxy)**에 대해 알아봅니다.
ansible-galaxy - https://docs.ansible.com/ansible/latest/cli/ansible-galaxy.html
- 앤서블 갤럭시 소개 : 롤 공유, 다만 롤은 검증되지 않은 것이 대부분이기 때문에 사용 시 주의해야 합니다
- Roles : 키워드 ‘postgres’ 검색 + 분류 ‘Download count‘
- 검색될 롤 확인 - Link
- 롤 설치 방법, 커밋(commit)한 날짜, 다른 유저가 다운로드한 횟수, 설치 가능한 운영체제, 지원 가능한 버전 등 내용을 확인할 수 있습니다.
- Ansible Galaxy
- 명령어를 이용한 앤서블 갤럭시 활용 : 앤서블 갤럭시로부터 롤 가져오기
- 롤 서브 명령어 확인
ansible-galaxy role -h
usage: ansible-galaxy role [-h] ROLE_ACTION ...
positional arguments:
ROLE_ACTION
init Initialize new role with the base structure of a role.
remove Delete roles from roles_path.
delete Removes the role from Galaxy. It does not remove or alter the actual GitHub repository.
list Show the name and version of each role installed in the roles_path.
search Search the Galaxy database by tags, platforms, author and multiple keywords.
import Import a role into a galaxy server
setup Manage the integration between Galaxy and the given source.
info View more details about a specific role.
install Install role(s) from file(s), URL(s) or Ansible Galaxy
options:
-h, --help show this help message and exit
롤 검색
ansible-galaxy role search postgresql --platforms Ubuntu
Found 270 roles matching your search:
Name Description
---- -----------
aaronpederson.postgresql PostgreSQL is a powerful, open source object-relational database system. It has more than 15 years of active>
alainvanhoof.alpine_postgresql PostgreSQL for Alpine Linux
alikins.postgresql PostgreSQL server for Linux.
...
롤 상세 정보 확인
ansible-galaxy role info geerlingguy.postgresql
Role: geerlingguy.postgresql
description: PostgreSQL server for Linux.
commit: a7723eb017c618611da37531e5961632d9ae03b0
commit_message: Fedora support time for 37 and 38.
created: 2023-05-08T20:49:59.794667Z
download_count: 2454875
github_branch: master
github_repo: ansible-role-postgresql
github_user: geerlingguy
id: 10986
imported: 2023-06-15T23:23:15.275804-04:00
modified: 2023-10-29T18:44:43.644380Z
path: ('/root/.ansible/roles', '/usr/share/ansible/roles', '/etc/ansible/roles')
upstream_id: 12427
username: geerlingguy
롤 가져오기
my-ansible/roles/…
# -p 옵션으로 롤이 설치될 디렉터리 경로 지정
ansible-galaxy role install -p roles geerlingguy.postgresql
# 확인
tree roles
roles
└── geerlingguy.postgresql
├── defaults
│ └── main.yml
├── handlers
│ └── main.yml
...
ansible-galaxy role list -p roles
# /root/my-ansible/roles
- geerlingguy.postgresql, 3.5.0
# /etc/ansible/roles
[WARNING]: - the configured path /root/.ansible/roles does not exist.
[WARNING]: - the configured path /usr/share/ansible/roles does not exist.
(옵션) 앤서블 환경 설정에 롤 디렉터리 설정
my-ansible/ansible.cfg 파일 편집
[defaults]
inventory = ./inventory
remote_user = root
ask_pass = false
roles_path = ./roles
...
inject_facts_as_vars = false # <- 실습 사용 롤이 예전 facts 를 사용하니 이 옵션은 제거하자
# 확인
ansible-galaxy role list
ansible-galaxy role list -p roles
VSCODE에서 메인 태스크 등 확인 : my-ansible/roles/tasks/main.yml, my-ansible/roles/vars/Ubuntu-22.yml
---
# Variable configuration.
- include_tasks: variables.yml
# Setup/install tasks.
- include_tasks: setup-Archlinux.yml
when: ansible_os_family == 'Archlinux'
- include_tasks: setup-Debian.yml
when: ansible_os_family == 'Debian'
- include_tasks: setup-RedHat.yml
when: ansible_os_family == 'RedHat'
- include_tasks: initialize.yml
- include_tasks: configure.yml
- name: Ensure PostgreSQL is started and enabled on boot.
service:
name: "{{ postgresql_daemon }}"
state: "{{ postgresql_service_state }}"
enabled: "{{ postgresql_service_enabled }}"
# Configure PostgreSQL.
- import_tasks: users.yml
- import_tasks: databases.yml
- import_tasks: users_props.yml
---
__postgresql_version: "14"
__postgresql_data_dir: "/var/lib/postgresql/{{ __postgresql_version }}/main"
__postgresql_bin_path: "/usr/lib/postgresql/{{ __postgresql_version }}/bin"
__postgresql_config_path: "/etc/postgresql/{{ __postgresql_version }}/main"
__postgresql_daemon: postgresql
__postgresql_packages:
- postgresql
- postgresql-contrib
- libpq-dev
postgresql_python_library: python3-psycopg2
가져온 롤을 이용하여 설치 : 설치 중 root 권한 필요(become: yes)
my-ansible/role-galaxy.yml
---
- hosts: tnode1
roles:
- geerlingguy.postgresql
설치 실행
ansible-playbook role-galaxy.yml
PLAY [tnode1-ubuntu.local] ****************************************************************************************************************************************
TASK [Gathering Facts] ********************************************************************************************************************************************
ok: [tnode1-ubuntu.local]
TASK [geerlingguy.postgresql : include_tasks] *********************************************************************************************************************
included: /root/my-ansible/roles/geerlingguy.postgresql/tasks/variables.yml for tnode1-ubuntu.local
TASK [geerlingguy.postgresql : Include OS-specific variables (Debian).] *******************************************************************************************
ok: [tnode1-ubuntu.local]
...
설치 확인
ssh tnode1 systemctl status postgresql
가져온 롤 삭제
ansible-galaxy role remove geerlingguy.postgresql
ansible-galaxy role list
rm -r roles
콘텐츠 컬렉션
앤서블이 처음 개발되었을 때는 대부분의 모듈이 핵심 패키지에 포함되어 관리되었습니다. 하지만 모듈 수가 증가하면서, 모듈 업데이트를 핵심 앤서블 코드 업데이트와 동기화해야 했고, 모듈마다 고유한 이름을 유지하는 것도 부담이 커졌습니다. 이런 관리 문제를 해결하기 위해 등장한 것이 앤서블 콘텐츠 컬렉션입니다.
콘텐츠 컬렉션을 사용하면 핵심 앤서블 코드 업데이트와 모듈/플러그인 업데이트가 분리됩니다. 또한 플레이북에서 사용할 수 있는 관련 모듈, 역할(Role), 플러그인을 묶어서 제공하기 때문에, 벤더/개발자는 앤서블 릴리스와 독립적으로 컬렉션을 유지·관리·배포할 수 있습니다.
추가로 컬렉션을 사용하면, 모든 모듈을 다 설치하는 방식이 아니라 필요한 콘텐츠만 설치할 수 있고, **특정 버전(이전/이후 버전)**을 선택해서 설치할 수도 있습니다.
- Ansible 2.9 이상은 콘텐츠 컬렉션을 지원합니다.
- 업스트림 앤서블은 Ansible Base 2.10 및 Ansible Core 2.11의 코어 코드에서 대부분의 모듈을 번들 해제하고 컬렉션에 배치했습니다.
- 레드햇 앤서블 오토메이션 플랫폼 2.2는 Ansible Core 2.13 기반의 자동화 실행 환경을 제공합니다.
공식 문서에서 컬렉션 확인하기
- 앤서블 공식 문서의 컬렉션 인덱스: https://docs.ansible.com/ansible/latest/collections/index.html
- Collection Index에서 제공 중인 컬렉션 목록을 확인합니다.
- 예시로 openstack.cloud 컬렉션을 선택해 개발자 정보, 지원 ansible-core 버전, 제공 모듈 목록 등을 확인합니다.
- 제공 모듈 중 예를 들어 server 모듈 페이지로 들어가면,
- 컬렉션 설치 방법
- 모듈 기능
- 필요한 패키지 정보
- 파라미터 설명과 사용 예제
등을 한 번에 확인할 수 있습니다.
명령어로 컬렉션 관리하기 (ansible-galaxy collection)
사용 가능한 서브 명령어 확인
ansible-galaxy collection -h
현재 환경에 설치된 컬렉션 목록 확인
ansible-galaxy collection list
tree /usr/lib/python3/dist-packages/ansible_collections -L 1
특정 컬렉션 설치 여부(버전)만 확인
ansible-galaxy collection list openstack.cloud
컬렉션 삭제 후 상태 확인 (예: openstack.cloud)
# 삭제(시스템 경로에서 제거)
sudo rm -rf /usr/lib/python3/dist-packages/ansible_collections/openstack*
# 삭제 확인
ansible-galaxy collection list openstack.cloud
ansible-galaxy collection list
컬렉션 설치 (특정 버전 지정 가능)
# 특정 버전 설치
ansible-galaxy collection install openstack.cloud:2.1.0
# 설치 확인
ansible-galaxy collection list
ansible-galaxy collection list openstack.cloud
오프라인 환경을 위한 tar 파일 다운로드 및 설치
# 컬렉션 tar 다운로드 (-p로 저장 경로 지정)
ansible-galaxy collection download -p ./collection openstack.cloud
# 다운로드 파일 확인
tree collection/
tar tvf collection/openstack-cloud-2.2.0.tar.gz
cat collection/requirements.yml
# tar 파일로 설치
ansible-galaxy collection install ./collection/openstack-cloud-2.2.0.tar.gz
# 설치 확인
ansible-galaxy collection list
ansible-galaxy collection list openstack.cloud