CLOUDWAVE

IaC : Ansible 변수관리 및 작업제어

갬짱 2025. 2. 1. 00:07

[ Ansible 변수관리 ]

변수란? 재사용할 수 있는 값을 저장하는 대상, 동적인 환경에 따라서 바뀔 수 있는 값

  • 변수는 여러 위치에 정의가능 ( *총 23개의 위치 ) → 특정 호스트 그룹, 개별 호스트 단위 & 플레이, 플레이북 단위 등 다양한 범위에서 적용
  • 동일한 이름의 변수를 여러 위치에 정의했을때 우선순위가 높은 것이 적용됨 ( 위치에 따라 다른 우선순위 )

Scope이 좁은 것이 우선순위가 높다.

  • 명령어 인자 옵션(CLI) : 특정실행에만 한정되는 변수정의로 가장 낮은 우선순위를 가짐
    ansible-playbook playbook.yml -e "var1=value1”
    ansible-playbook playbook.yml --extra-vars "var1=value1”
  • Playbook 내부변수 : 특정 Playbook 내에서만 유효한 정의( inventory보다 좁은 스코프 )
  • Inventory 파일변수 : 여러 호스트에 걸쳐 적용되는 정의
  • Ansible Configuration 파일 : 전체 Ansible 환경에 적용

 

플레이북 변수

[ 변수선언 ]

  • 플레이북 내부에서 변수선언 : vars 키워드 하에 변수를 나열 ( 이때 -이 없음에 유의 )
- hosts: all
	vars:
		user: user01
		home: /home/user01
  • 플레이북에서 변수파일 사용 : vars_files 키워드 하에 분리된 변수파일(yaml)을 호출하여 읽어들임
# user.yaml
user: user01
home: /home/user01
- hosts: all
	vars_files:
		- user.yaml

 
 
[ 변수사용 ]
변수참조식 : 이중 중괄호를 사용 {{ }} 하여 변수명을 호출

vars:
	user: user01

tasks:
	- name: create user {{ user }}
		user:
			name: "{{ user }}"

값(val)부분의 시작으로 들어오는 값일때는 “”를 붙임( 준수하지 않을 경우 문법오류로 에러 발생 )
create user {{ user }} ↔ name: "{{ user }}"
 
 

인벤토리 변수

인벤토리에서 변수선언 → 특정 호스트에 적용되는 호스트 변수 & 그룹에 적용되는( 중첩그룹, 모든 호스트의 그룹 등 포함 ) 그룹변수

  • 호스트 변수가 그룹변수보다 우선하게 됨
    플레이북에서 정의한 변수가 두 변수보다 우선하게 됨
  • 인벤토리 파일내에서 호스트 변수 선언 : 옆에서 선언
[webservers]
web01.test.com    user=user01

 

  • 인벤토리 파일내에서 그룹변수 선언 : [ 그룹명 : vars ] 섹션에서 선언
[ servers1 ]
test1.example.com   pkg=httpd
test2.example.com

[server1:vars]
pkg=nginx

test1.example.com에서는 httpd가 작동 → scope이 더 작은 것이 적용됨
test2.example.com에서는 nginx가 작동
 

  • 인벤토리에서 외부 변수파일이용 → 분리된 변수들을 통합적으로 정의
    • 호스트 및 그룹과 동일한 파일이름을 통해 변수파일이 자동으로 사용(매핑)되도록함 ( 작업디렉토리 하위의 host_vars/호스트이름, group_vars/그룹이름 )
    • 이때 그룹scope에서 정의한 변수(group_vars하위)보다 개별 호스트 scope에서 정의한 변수(host_vars하위)의 우선순위가 높음

 
[ 실습1 : playbook에 변수지정 ]

 
변수들을 저장한 파일 user.yaml

serverc에 user모듈을 사용하여 유저를 생성한다 → 이때 여러 변수들은 파일을 참조
플레이북파일의 host와 task사이에 변수를 삽입하는 vars_files를 사용
이때 첫번째 요소의 유일한 변수이기에 “”를 꼭 사용함

serverc에 접속하여 사용자를 확인해보면(/etc/passwd) user01이라는 이름으로 새롭게 사용자가 생성됨
 
 
[ 실습2 : inventory에 변수 지정 → 파일이름이 자동으로 매칭되는 디렉토리 이용 ]

pkg라는 변수명의 패키지가 설치되도록 하는 플레이북을 작성해보았다.

그룹변수파일( group_vars/prod )에는 httpd라는 변수값을 지정, 호스트변수파일( host_vars/httpd )에는 mariadb라는 변수값을 지정
넓은 범위의 호스트 그룹 prod에는 httpd를 설치 & 좁은 범위의 개별 호스트 serverb에는 mariadb를 설치
 

prod그룹에 속하는 servera에는 httpd가 설치됨
prod그룹에 속하지만 httpd라는 호스트명을 가진 serverb에는 mariadb만이 설치됨
⇒ 이들은 모두 플레이 실행 명령어 입력 시점에서 CLI의 -e 옵션으로 재지정이 가능함
 

팩트변수

ansible facts 변수 : 플레이북 실행시 관리 호스트에서 자동으로 검색한 호스트별 정보가 포함되어있는 변수
호스트 시스템에 대한 정보 수집 ( 호스트 이름, 커널 버전, 네트워크 인터페이스 이름, 네트워크 인터페이스 IP주소, 운영체제 버전, CPU개수, 사용가능 메모리, 스토리지 장치의 크기 및 여유 공간 )

  • 호스트의 상태를 검색하고 관리하는 용도 → 팩트 변수 값에 따라 수행조치를 결정가능( 제어문 이용 )
    ex. 팩트에서 보고하는 사용가능한 메모리에 다라 플레이에서 MySQL 구성 파일을 사용자지정할 수 있음, 구성파일에 사용된 IPv4 주소는 팩트값에 따라 설정가능
    • 하위 속성 gather_subset : 특정 변수만 취합하게 조절가능( 성능 개선 ) or 특정변수만 제외하게 조절가능
      setup모듈 : fact변수를 수집( setup gather_fact )하는 역할
      Ansible 2.3이상 플레이북의 task항목에서 가장 상위에서 자동실행되어 gathering facts 작업으로 결과에 보고됨( 별도의 정의X )

 

  • 팩트 변수의 접근
    • ansible_facts.변수명 = ansible_facts.hostname
    • ansible_facts[ 변수명 ] = ansible_facts[’hostname’]
    • 플레이북에서 사용할때에는 이중 중괄호를 입력
- hosts: all
	tasks:
		- debug:
			msg: >
				Hostname is {{ ansible_facts.hostname }}
				and IP is {{ ansible_facts.default_ipv4.address }}

 

  • 자동으로 수집실행되는 팩트변수작업을 비활성화 → 플레이 속도를 높이고 부하를 줄임 ( 플레이의 gather_facts 를 false로 설정 )
gathering facts를 수행하지 않음

 
 

  • 사용자지정 팩트 : 관리 호스트의 특정값을 임의로 정의가능
    • 시스템의 정보를 가지고 오는 것이기에 수정대상이 아님 → 관리자모드에서 제한적으로 사용 ( 보통 잘 사용하지 않음 )
    • 사용자 지정 팩트는 각 관리 호스트에 로컬로 저장 → 호스트에서 setup모듈이 실행될때 수집되는 표준 팩트 목록에 통합됨
    • setup 모듈은 관리호스트내의 /etc/ansible/facts.d 디렉토리 내부의 파일, 스크립트를 수집 ⇒ 파일 및 스크립트는 .fact 형태 & 스크립트는 JSON 형식의 팩트를 출력하고 실행가능 해야함
ansible-playbook : playbook 파일을 읽어서 앤서블을 실행해라
ansible -m 모듈 대상 : 모듈을 지정하여 대상을 실행
ansible -m setup servera : setup모듈을 통해 host의 정보를 수집 = gathering facts 작업 ( 자동으로 실행됨 → 옵션을 통해 off가능 )

 

변수 파일 암호화

Ansible-vault 명령 : Ansible에서 사용하는 데이터 파일을 암호화하고 암호를 해독( 암호화 대상 : 인벤토리 변수파일, 플레이북 변수파일, Ansible 역할에서 정의된 변수 )

AES 암호화를 지원 ⇒ 해당파일에서 값을 호출하여 활용하기 위해서는(ansible-playbook) 암호를 입력해야함
 

  • ansible-vault encrypt : 기존의 파일에 암호화를 수행 ↔ ansible-vault decrypt : 암호화된 파일을 일반파일로 변경
  • ansible-vault create : 새로운 파일을 만들면서 암호화하는 명령 새로운 Vault 암호에 대한 메시지가 나타나며 기본 편집기 vi를 사용하여 파일이 열림
    • 옵션 --valut-password-file : 표준입력을 통해 암호를 입력하지 않고 암호가 저장된 파일을 인자로 전달하여 암호화
      ansible-vault create --vault-password-file=vault-pass site.yaml
  • ansible-vault view : 암호화된 파일을 열람하는 명령
  • ansible-vault edit : 암호화된 기존파일을 편집하는 명령
  • ansible-vault rekey : 암호화된 파일을 새로운 암호로 변경(대체)

 
[ 암호인증 ]
ansible-playbook 명령어로 실행할때 암호를 전달 -> 3가지 방

  • 프롬프트 입력
  • 암호파일 : 옵션 --valut-password-file을 이용
  • 환경변수 설정 : 환경변수 ANSIBLE_VAULT_PASSWORD에 설정 → 자동으로 암호가 입력되어 동작함

 

실습

[ fact.yaml : 팩트변수 출력 ]
debug모듈 : 변수 값, 사용자 정의 메시지 또는 조건부 디버그 메시지를 출력

 
 
[ fact2.yaml : 팩트변수 상세출력 ]
>설정 : 가독성을 위해 여러줄에 걸쳐서 표기하는 것을 한번에 처리
|설정 : 표기되는대로 여러줄에 걸쳐서 입력

---
- hosts: serverc
  tasks:
    - debug:
        msg: >
         The Defualt IPv4 address of {{ ansible_facts.fqdn }}
         is {{ ansible_facts.default_ipv4.address }}

 
 
[ secret.yaml : 변수파일 암호화 ]
ansible-vault create : 암호화된 파일 생성
ansible-vault view : 암호화된 파일 열람

암호화된 파일에 대해서는 일반 cat명령, vi명령으로 접근시 암호화된 내용만 접근가능

ansible-vault edit로만 편집가능
 
 
[ 새로운 암복호화 ]
실무에서 password는 해시값으로 사용하고(관리자도 명확한 암호를 알지못함) 주기적으로 변경 및 업데이트

(1) openssl을 이용한 24글자의 해시값을 생성 → vault-pass파일을 만들어 저장
(2) 호스트 변수파일(hosts_vars/serverb파일)을 암호화 : --vault-password-file 암호가담긴파일
 

install-pkg로 ansible-playbook하는 것은 암호화된 hosts_vars/serverb파일을 사용
 

암호화된 hosts_vars/serverb파일을 복호화(인증)하는데 파일을 사용함
ansible-playbook --vault-password-file 암호가담긴파일 플레이북파일
 
키를 순환하기
기존의 암호를 덮어쓰면 안됨 ( *순환순서 : 기존파일로 복호화 → 새로운 파일로 암호화 해야함 )

ansible-vault rekey --vault-password 기존파일 --new-vault-password-file 새파일 변수파일경로
--vault-password-file 로 복호화 & 동시에 --new-vault-password-file 로 새로운 암호화
이전의 vault_pass파일을 삭제 → vault_pass_new를 이것으로 대체

기존 명령어를 그대로 사용이 가능함
 
이렇게 수정이 적어지도록(최적화) 키교체 작업을 자동화 ( 스크립트, 도구를 사용 )

  • 스크립트의 경우 interpreter를 지정 → bash쉘을 통해 동작하고자 함( #!/bin/bash), 파이썬을 통해 실행하고자 함( #!/bin/bash )
  • 키 자동으로 생성, 적용, 기존키로 대체 & 기존키 삭제와 새로운키의 덮어쓰기 작업

⇒ 실행권한을 부여 & crontab에 등록해놓으면 주기적으로 키가 교체됨
 

정상구동되는 양상
 


 

[ Ansible 작업제어 ]

조건에 따라 특정 작업수행여부 결정 혹은 특정작업 반복 + 변수를 동적으로 선택 → 다양한 환경에서 사용할 수 있는 고도화된 플레이북을 생성

list항목에 item이 존재하는지 반복적으로 확인 → Run the task
 

반복문

  • play의 loop속성 : 모듈작업을 반복할 요소들을 지정 → 모듈의 내에서 item이라는 이름으로 반복요소를 순차적으로 접근
  •  반복문을 사용하지 않는경우 vs 반복문을 사용하는 경우( loop대상(반복대상)을 리스트로 지정 )
    - name: mail server check
    	service:
    		name: postfix
    		state: started
    - name: mail server check2
    	service:
    		name: dovecot
    		state: started
    ​
    - name: mail server check
    	service:
    		name: "{{ item }}"
    		state: started
    	loop:
    		- postfix
    		- dovecot
    ​
  • loop대상(반복대상)을 변수로 지정가능 ⇒ 이때 변수의 갯수가 중요한 것이 아니라 loop 리스트의 갯수가 중요함
    vars:
    	mail_services:
    		- postfix
    		- dovecot
    tasks:
    	- name: mail server check
    		service:
    			name: "{{ item }}"
    			state: started
    		loop: "{{ mail_services }}"
    
  • loop대상(반복대상)을 dictionary형으로 구성한 경우 ( item[ idx ]로 접근 )
    - user:
    		name: "{{ item['name'] }}"
    		state: present
    		groups: "{{ item['groups'] }}"
    	loop:
    		- name: user01
    			groups: wheel
    		- name: user02
    			groups: root
    ​

 

조건문

  • 호스트간의 차이점, 호스트의 상태를 구분하고 충족된 조건을 기반으로 관리 호스트에 기능 역할을 할당가능
  • play의 when속성 : 조건구문( 판별식 : 참과 거짓을 판단 )을 작성하는 영역
    • 플레이북 변수, 등록변수, 팩트 변수 + 문자열, 숫자 데이터 및 연산자 사용
    • 해당조건이 참이라고 판단되면 task내의 모듈을 실행한다.

 
예시) IN연산자를 이용한 조건문

---
- hosts: all
	vars:
		supported_distros:
			- RedHat
			- Fedora
			- Centos
	tasks:
		- yum:
				name: http
				state: present
			when: ansible_facts['distribution'] in supported_distro

facts변수내의 배포판 값이 목록(supported_distros)내의 값에 포함된다면 yum모듈을 실행한다.
( * 우분투는 yum모듈이 아닌 apt모듈을 이용하여 설치해야한다. )
 

[ansible-user@controller ansible-demo]$ ansible -m setup servera | grep distribution
        "ansible_distribution": "Rocky",
        "ansible_distribution_file_parsed": true,
        "ansible_distribution_file_path": "/etc/redhat-release",
        "ansible_distribution_file_variety": "RedHat",
        "ansible_distribution_major_version": "9",
        "ansible_distribution_release": "Blue Onyx",
        "ansible_distribution_version": "9.5",

다음과 같은 servera노드에 해당 task를 돌린다면 → yum이 실행되지 않음 ( 배포판이 리스트에 포함되지 않음 )
 
 

  • 다중조건을 이용한 판별식 = n개의 판별식을 결합한 형태( and연산자, or연산자 )
when: ansible_facts['distribution'] == "RedHat" or ansible_facts['distribution']  == "Fedora”
when: >
	ansible_facts['distribution_version'] == "9.0" and
	ansible_facts['kernel']  == "5.14.0-70.13.1.el9_0.x86_64"

 
Ansible에서 리스트 데이터형은 and연산자로 연결된 것으로 봄
이와달리 go로 쓰여진 쿠버네티스에서는 리스트가 or연산자로 연결됨

when:
	- ansible_facts['distribution_version'] == "9.0"
	- ansible_facts['kernel'] == "5.14.0-70.13.1.el9_0.x86_64"

 

  • 조건문과 반복문의 결합
- yum:
		name: mariadb-server
		state: latest
	loop: "{{ ansible_facts['mounts'] }}"
	when: item['mount'] == "/" and item['size_available'] > 300000000

mounts로 인덱싱이 가능한 것 : /와 /boot로 2개존재

/에 mount되었고 300M이상의 사이즈를 가지고 있는 것들에 대해 반복적으로 yum을 실행
 
 

Register 변수

  • 모듈이 실행했을때 결과를 담는 특수한 변수 → 실행한 결과를 확인( debug모듈 ) & when조건의 판별값으로 이용가능
  • register 변수값들은 모듈마다 상이함( 모듈마다 결과가 상이함 )
- tasks:
	- name: postfix status
		command: /usr/bin/systemctl is-active postfix
		register: result
	- name: debugging
		debug:
			var: result
			verbosity: 2
  • command 모듈 : 명령줄을 실행하는 모듈 → 멱등성을 보장하지 않음!! 단순실행
  • register변수로 사용할 변수 result를 선언 & 자동으로 결과값이 세팅
  • 디버그 메시지는 ansible-playbook 실행 시 -vv 이상의 옵션을 사용할 때만 표시
- tasks:
	- name: postfix status
		command: /usr/bin/systemctl is-active postfix
		register: result
	- name: debugging
		service:
		name: httpd
		state: restarted
		when: result.rc == 0
  • 결과값을 통해 후속작업을 실행유무를 제어
    첫번째 모듈 command에서 결과값을 result에 저장
    두번째 모듈 service에서 httpd 재시작 작업은 결과값의 return code에 의해 결정
  • result.rc == 0 : rc(return code)가 0이라면 ( 정상종료되었다는 의미 )
    $? : 직전에 수행했던 명령어의 결과값 ( 리눅스에서 $? , 앤서블에서 register변수로 확인가능 )

1번 → 파일이나 디렉토리 찾을 수 없음
127번 → 명령어를 찾을 수 없음
 
 

Handler를 통한 제어

  • 멱등성으로 상태유지 : 변경상태보고(changed) & 변경된 작업의 후속작업 ⇒ handler로 구현
    파일이 변경되었다면 변경된 내용을 다시 읽어서 알맞게 프로세스를 실행( ex. 실행중인 아파치의 포트를 변경하고 변경한 포트에 맞게 프로세스를 재기동 )
  • Handler : 다른 작업에서 트리거한 알림에 반응하는 후속작업 ( changed보고가 된 경우(시스템을 변경한 경우)에만 후속작업을 실행하도록 제어 )
    명시적으로 호출된 경우에만 트리거되는 비활성 작업
  • task의 notify속성 : 모듈로 인해 변경이 이루어졌다면( changed를 보고했다면 ) 후속적으로 수행할 handler이름을 지정

 

  • handler여러개를 list형태로 여러번 호출할 수 있음( 선언 순서중요 ) ⇒ 하나의 작업에서 해당 notify 섹션에 있는 핸들러를 두 개 이상 호출
- tasks:
	- name: notify tasks
		template:
			src: /var/lib/templates/demo.conf
			dest: /etc/httpd/conf.d/demo-httpd.conf
		notify:
			- restart mysql
			- restart apache
handlers:
	- name: restart mysql
		service:
			name: mariadb
			state: restarted
	- name: restart apache
		service:
			name: httpd
			state: restarted

 
[ handler의 실행순서 ]

tasks:
	- module
	  notify
	- module
	  notify
	- module
	- module
  • 핸들러는 일반적으로 플레이에서 다른 모든 작업이 완료된 후에 실행
    : 각 module → notify이렇게 가는게 아니라 module이 쫙실행되고 → 이후 handler가 실행(예약작업)
  • 핸들러는 handlers 섹션에서 지정한 순서대로 실행 ↔ notify문에서 호출된 순서
  • 2개의 모듈에서 notify로 중복적으로 handler를 호출한다면 → 한번만 호출됨( 프로세스는 2번연속 재기동할 필요가 없음 )
  • 핸들러가 중복되어 정의되었다면 → 잘못된 동작이 일어남( 첫번째 것만 실행 ) 중복이름이 허용되지 않는것은 아니지만 의도대로 동작하지 않음

 
changed_when속성 : 사용자가 임의로 changed를 보고하도록 조정가능

tasks:
	- shell:
			cmd: /usr/local/bin/upgrade-database
		register: command_result
		changed_when: "'Success' in command_result.stdout"
		notify:
			- restart_database

해당조건을 만족할때마다 changed를 보고하게 함 ( shell은 멱등을 보장하지 않음 )

- name: validate config
	command: httpd -t
	changed_when: false

false값을 입력하여 항상 changed를 보고하지 않도록 제어가능
 
 

작업오류제어

Ansible은 순차적으로 task실행 → 중간에 에러가 생길 경우 break(중단), 이후의 task는 실행되지 않음
이때 작업이 실패해도 플레이를 계속 진행하도록 설정가능

  • ignore_errors 속성 : 해당 task의 error를 무시함 ⇒ 특정 모듈에 지정 or 전체 task단위로 지정가능
- yum:
		name: http
		state: latest
	ignore_errors: yes
  • force_errors 속성 : 에러로 인해 task모듈은 수행되지 않더라도 handler는 수행되도록 함
  • failed_when 속성 : 조건이 참이라면 fail이 발생하도록 제어 해당 task가 ok로 보고하더라도 이곳에서 fail로 다시 제어를 변경 = fail모듈 + when조건 : fail모듈을 사용하면 메시지를 전달하는 원리
tasks:
- name: run script
	shell: /usr/local/bin/create_users.sh
	register: command_result
	failed_when: "'password missing' in command_result.stdout

스크립트를 실행하는데 스크립트 동작은 완료 되었지만 실패로 간주되는 상황이 발생

fail 모듈을 사용하여 오류를 생성하고 실패 메시지 입력가능

tasks:
- name: run script
	shell: /usr/local/bin/create_users.sh
	ignore_errors: yes
- name: fail message
	fail:
		msg: "Passowrd missing"
	when: "'password missing' in command_result.stdout"

 
 
block을 이용하여 논리적인 작업그룹을 만듬 -> 공통적으로 when조건을 사용 ( 단일 when 속성을 지정 )

tasks:
- name: install and configuration versionlock
	block:
	- name: package install
		yum:
			name: python3-dnf-plugin-versionlock
			state: present
	- name: config lock
		lineinfile:
			dest: /etc/yum/pluginconf.d/versionlock.list
			line: tzdata-2016j-1
			state: present
	when: ansible_distribution == "RedHat"

 
block -rescue-always절 : block절에서 실패한 것이 rescue가 동작 & always절은 실패/성공 모든 경우에도 동작

tasks:
- name: Upgrade DB
	block:
		- name: upgrade the database
			shell:
				cmd: /usr/local/lib/upgrade-database
	rescue:
		- name: revert the database upgrade
			shell:
				cmd: /usr/local/lib/revert-database
	always:
		- name: always restart the database
			service:
				name: mariadb
				state: restarted