ansible-playbook에서 반복문 사용

반복문을 사용하면 관리자는 동일한 모듈을 사용하는 여러개의 작업을 작성할 필요가 없고, 구문을 간단하게 만들 수 있다. 예를 들어, 5명의 사용자가 존재하는지 확인하기 위해 5개의 작업을 만들 필요가 없고, 5명의 사용자에 대해 반복하는 하나의 작업을 작성하면 된다.

 

ansible에서는 작업 반복에 대해 loop 키워드를 사용한다. 여기서는 간단하게만 설명하며, 더 자세한 반복문 시나리오와 관련 설명은 설명서 링크를 참고할 것.

 

반복문 ansible 설명서

https://docs.ansible.com/ansible/2.7/user_guide/playbooks_loops.html 

 

Loops — Ansible Documentation

Docs » User Guide » Working With Playbooks » Loops You are reading an unmaintained version of the Ansible documentation. Unmaintained Ansible versions can contain unfixed security vulnerabilities (CVE). Please upgrade to a maintained version. See the la

docs.ansible.com

 

단순 반복문

loop 지시문과 변수를 받아 반복을 수행한다. 변수에 방식에 따라 3가지의 방식이 있다.

 

1. 기본 item 변수 사용

- name: test1
  hosts: web1.example.com
  tasks:
    - name: postfix and httpd are running
      service:
        - name: "{{ item }}"
          state: started
      loop:
        - postfix
        - httpd

- loop 지시문에 반복할 값들을 명시하면 순서대로 한번씩 item 변수에 넣는다. item 변수는 따로 정의한 게 아니고 반복문에서 자동으로 쓰는 전용 변수이다. 즉 loop가 돌 때마다 loop안에 있는 요소가 위에서부터 순차적으로 하나씩 넣고, 하나하나 다 넣으면 루프가 끝난다.

 

 

2. 사용자 정의 변수 사용 

- name: test1
  hosts: web1.example.com
  
  vars:
    check_services:
      - postfix
      - httpd
    
  tasks:
    - name: postfix and httpd are running
      service:
        - name: "{{ item }}"
          state: started
      loop: "{{ check_services }}"

- 사용자가 check_services 라는 변수 목록을 만들고, loop에 해당 변수를 명시하는 방식이다. 1번과 동일하게 순서대로 한번씩 item 변수에 넣는다.

 

3.  hash/dictionary 목록으로 된 변수 사용

- name: group loop
  hosts: web1.example.com

  tasks:
    - name: users eaist and are in the correct groups
      user:
        name: "{{ item.name }}"
        state: present
        groups: "{{ item.groups }}"
      loop:
        - name: jane
          groups: wheel
        - name: joe
          groups: root

- 여기서는 loop 밑에 - name group 이 하나이고 그 다음 또 - name group 가 하나로써 루프에 들어간다.

- 즉 맨 처음에는 name에 jane, groups에 wheel이 들어가서 반복 한번이 끝나고, 그 다음 반복으로 name에 joe, groups에 root가 들어가게 된다.

 

 

# 참고 : 이전 버전의 반복문 키워드

ansible 2.5 이전에는 다른 구문을 사용했음. with_ 가 앞에 오고, ansible 조회 플러그인의 이름이 뒤에 오는 여러개의 반복문 키워드가 제공되었다. (with_items, with_files 등) 이러한 구문은 이전 버전에선 매우 일반적이지만, 향후 없어질 것이다. 여기서는 따로 이야기하지 않으므로 필요하다면 따로 찾아볼 것.

 

# 참고 : loop 지시문 위치

- loop 지시문은 각각의 모듈 이하에 있으므로, 여러개의 모듈이 있는 경우 loop의 위치를 정확히 해야한다.

- 아래 예시에서는 에러가 난다. loop가 첫번째 debug 밑에 있어야 하기 때문이다.

- hosts: servera
  tasks:
    - debug:
        msg: "{{ item }} started"
    - debug:
        msg: hello world
      loop:
        - postfix
        - dovecot

 

 

 

반복문에서 레지스터 변수 사용하기

- 아래와 같이 loop 지시문 다음에 register를 쓰면, 각 loop 당 register에 값이 들어간다.

- 즉, loop의 첫번째 값 one으로 루프를 돌고 echo_results에 결과를 저장하고, 그다음 loop의 두번째 값 two로 루프를 돌고 echo_results에 저장해서 총 2개의 결과값이 echo_results에 저장되게 된다.

- name: loop register test
  hosts: web.example.com
  gather_facts: no

  tasks:
    - name: looping echo task
      shell: "echo this is my item: {{ item }}"
      loop:
        - one
        - two
      register: echo_results

    - name: show result
      debug:
        var: echo_results

- 결과가 너무 길어 show result 부분만 캡쳐했다. one과 two가 결과로 나온 것을 확인할 수 있다.

- echo 명령의 출력 결과는 "stdout" 부분에 나오게 된다.

 

ansible-playbook에서 조건문 사용

ansible에서는 조건문을 사용하여 특정 조건을 충족하면 작동하는 playbook을 실행할 수 있다.  예를 들어 다음과 같은 예시 시나리오를 생각해볼 수 있다.

 

- 하드웨어 제한을 변수에 정의하고 (예 : min_memory) 관리 호스트에서 사용가능한 메모리와 비교할 수 있다.

- ansible에서 이후 작업을 수행하기 전에 작업 완료 여부를 확인하기 위해 명령 출력을 캡쳐하고 평가할 수 있다. 예를 들어 프로그램이 실패하는 경우, 배치를 건너뛴다.

- ansible_facts를 사용하여 관리 호스트 네트워크 구성을 확인하고, 보내려는 템플릿 파일을 결정할 수 있다.

- cpu 수를 평가하여 웹 서버를 적절히 조정할 수 있다.

- 등록된 변수를 사전 정의된 변수와 비교화여 서비스가 변경되었는지 확인한다. 예를 들어 서비스 구성파일의 md5 체크섬을 테스트해 서비스가 변경되었는지 확인할 수 있다.

 

* 조건식 연산자

- ansible에서의 조건문은 기본적으로 아래와 같이 조건식 연산자를 사용해 조건을 평가하여 task를 실행할지 말지 여부를 결정하게 된다. 

연산

예시

같음(숫자)
같음(문자열)

max_memory == 512
ansible_machine == "x86_64"

미만

min_memory < 128

보다 큼

min_memory > 256

작거나 같음

min_memory <= 256

크거나 같음

min_memory >= 256

같지 않음

min_memory != 512

변수 있음

min_memory is defined
min_memory라는 변수가 존재하면 true 

변수 없음

min_memory is not defined

부울 변수가 false이다.

not memory_available

부울 변수가 true이다.

memory_available

첫 번째 변수의 값이 두번째 변수 목록의 값으로 표시됨

ansible_distribution in supported_distros

이 예시는 ansible_distribution 이라는 팩트변수이며, supported_distros 안에 ansible_distribution이라는 항목이 있으면 true 가 되는 것이다.

 

기본 조건문

when 지시문을 사용하여 조건문을 사용하며, when은 테스트할 조건이 값으로 들어간다. 위에서 명시한 조건식 연산자를 사용하여 특정 조건이 충족되면 해당 task가 실행된다. 조건을 충족하지 않으면 작업을 건너뛰게 된다. when은 보통 task 아래 name과 같은 수준의 최상위이며(즉, 모듈 안에 포함되는 것이 아님) task, name, module명 보다 맨 마지막에 두는 것이 일반적이다. 아래는 몇가지 조건문의 예시이다.

 

* 조건문이 true인 경우

- 단순히 when 부분에 true가 들어가면 조건대로 작동한다. 만약 run_my_task: false 였으면 작동하지 않았을 것.

- name: loop register test
  hosts: all
  vars:
    run_my_task: true

  tasks:
    - name: install http package
      yum:
        name: httpd
        state: latest
      when: run_my_task

- 참고로, when 구문에서는 변수를 써도 "{{ }}" 를 써지 않아도 되는 듯하다.

 

* 변수에 값이 있는 경우 true

is defined는 변수가 존재하는지를 확인하며, 존재하므로 true이다. 그러므로 해당 task가 실행된다.

- name: test virable is defined demo
  hosts: web.example.com
  vars:
    my_service: httpd

  tasks:
    - name: "{{ my_service }} package will be removed"
      yum:
        name: httpd
        state: absent
      when: my_service is defined

 

* 특정 리스트 안에 특정 값이 있다면 true

- ansible_distribution은 해당 서버의 OS  종류를 알려준다. 확인한 OS 종류를 supported_distros에 있는 변수들과 비교해서 일치하는 것이 있으면 ture가 된다.

- 다음의 형태를 따른다.  (when: 일치할내용 in 검색할값들모음변수)

- name : list when test
  hosts: web.example.com
  vars:
    supported_distros:
      - RedHat
      - CentOS
      - Fedora
      - ubuntu

  tasks:
    - name : debug
      debug:
        msg: "{{ ansible_distribution }}"

    - name: install httpd using yum, where supported
      yum:
        name: httpd
        state: present
      when: ansible_distribution in supported_distros

 

여러개의 조건이 합쳐진 조건문

하나의 when 지시자에 여러개의 조건문을 평가할 수 있다. 이를 위해 and, or, 괄호( ) 를 사용할 수 있다.

 

* or

- 두 조건 중 하나만 true 여도 되는 경우 사용

- 예시 : 시스템에서 RHEL 또는 Fedora 둘 중 아무거나 하나면 되는 경우

- 구문 :  when: ansible_distribution == "RedHat" or ansible_distribution == "Fedora"

 

* and

- 두 조건 모두 true 여야만 하는 경우 사용

- 예시 : RHEL 버전이 7.5여야 하고 또한 커널버전이 3.10.0-327 이어야 하는 경우

- 구문 : when: ansible_distribution_version == "7.5" and ansible_kernel == "3.10.0-327.el7.x86_64"

 

# 참고 : when 지시문에서 목록으로 표시를 하면 자동으로 and 연산자가 사용된다.

when:
  - ansible_distribution_version == "7.5"
  - ansible_kernel == "3.10.0-327.el7.x86_64"

 

* ( ) 괄호

- 복잡한 조건문은 괄호를 사용해서 알아보기 쉽게 만들 수 있다.

- 참고로, > 는 긴 한줄을 여러 줄로 나누어 읽기 쉽게 한다. (실제로는 한줄이다)

when: >
  ( ansible_distribution == "RedHat" and
    ansible_distribution_major_version == "7" )
  or
  ( ansible_distribution == "Fedora" and
    ansible_distribution_major_version == "28" )

 

 

register 값을 조건문에 사용하기

- register는 앞에도 말했듯이 task의 결과를 변수로 가진다. 따라서 task의 결과값의 내용 중 특정 값을 가지고 조건문을 사용할수도 있다.

 

- 아래 예시는 chronyd가 현재 실행중인지지 확인하고, 실행중이라면 httpd를 재시작하는 구문이다.

---
- name: Restart httpd if chronyd is running
  hosts: web.example.com
  tasks:
    - name: get chronyd server status
      command: systemctl is-active chronyd
      ignore_errors: yes
      register: result

    - name: restart httpd based on chronyd status
      service:
        name: httpd
        state: restarted
      when: result.rc == 0

- ignore_errors: yes를 하는 이유는, 만약 chronyd가 active가 아니라면 systemctl is-active chronyd의 결과 리턴코드가 0이 아니게 된다. ansible에서는 모든 작업의 리턴코드가 0이어야만 정상이라고 인식한다. 따라서 리턴코드가 0이 아니면 playbook 진행상에 에러가 발생하며 에러가 발생하면 ansible 작업이 즉시 종료된다. 따라서 이 에러가 발생해도 그대로 진행하도록 하기 위해서 사용한다. (즉, 에러가 발생하는 것 자체가 이 playbook에 예상되어 있다는 것이다)

- result.rc == 0 이 구문은 register 변수에 저장된 task의 결과가 result에 저장되어 있고 그 저장된 값 중 rc라는 값이 0인지 검사하는 구문이다. 여기서 rc값은 리턴코드를 의미한다.

 

# 참고 : 리턴코드(rc) 란?

리눅스에서 어떤 명령이든 실행한 후 프롬프트에서 echo $? 를 입력하면, 바로 전 명령의 리턴코드를 보여준다. 정상적으로 명령이 실행되었다면 0이 나오고, 정상이 아니라면 0이 아닌 다른 값이 나온다. ansible을 비롯한 많은 프로그램들이 특정 명령이 제대로 잘 실행되었다는 것을 판단하기 위해 리턴코드를 사용한다.

 

 

 

 

 

반복문과 조건문을  함께 사용하기

- task 안에서 반복문 loop와 조건문 when을 함께 사용하면, 각 loop의 항목에 대해 when이 검사되는 형태가 된다. when은 맨 마지막에 쓴다.

- 아래 예시는 파일시스템의 특정 마운트 포인트의 용량이 300000000이상인 경우 패키지를 설치하는 구문이다.

- name : filesystem test
  hosts: web2.example.com
  tasks:
    - name: install mariadb-server if enough space on root
      yum:
        name: mariadb-server
        state: latest

      loop: "{{ ansible_mounts }}"
      when: item.mount == "/" and item.size_available > 300000000

 

- 여기서, ansible_mounts 라는 변수는 facts 변수에 있는 값이며, 이 값을 확인하면 아래와 같다. 

- 중괄호 3개가 있고, loop에서는 이 3개의 중괄호를 하나씩 loop로 검사한다. 이 3번 돌리는 값이 item 변수에 들어간다.

- loop를 돌리면 item변수에 해당 중괄호 값이 하나가 들어갈 것이고, 그 안에 조건문인 when이 작동하여, item.mount 값과 item.size_available의 조건을 확인해서 해당 조건과 맞으면 위의 task를 실행하게 된다.

 

- 다시 정리하면, loop와 when을 함께 사용하면 loop안에 when이 있는 것처럼 작동한다. when 안에 loop가 있는 방식이 존재하는지는 확실히 모르겠고, 이 강의에서는 이정도 선까지만 교육한다.

 

 

 

 

 

 

연습문제

- 지정한 ansible client에 데이터베이스 설치, 사용자 계정을 설정한다.

- 3가지의 yaml 파일을 사용한다.

     - database_setup.yml : 데이터베이스를 설치하는데, 최소조건에 맞는지 확인

     - CentOS_database_tasks.yml : 데이터베이스 패키지를 설치하고, 서비스를 시작하고, enable시킨다.  

      - database_user_tasks.yml : 특정 유저를 조건에 맞춰서 생성한다.

- 이렇게 나눈 이유는, 다른 패키지나 다른 유저설정을 할 때 재사용하기 쉽게 최소작업단위로 분할을 한 것이다. 

 

* database_setup.yml

---
- name: database setup play
  hosts: db.example.com
  vars:
    min_ram_size_bytes: 500000000
    supported_distros:
      - Redhat
      - CentOS
    host_permission_groups:
      - dbadmin
      - dbuser
    user_list:
      - name: john davis
        username: jdavis
        role: dbadmin
      - name: jennifer smith
        username: jsmith
        role: dbuser
      - name: alen page
        username: alpage
        role: dbuser
# 총 4개 종류의 변수를 지정한다.

  tasks:
    - name: Setup database tasks on supported hosts w/ Min. RAM
      include_tasks: "{{ ansible_distribution }}_database_tasks.yml"
      when:
        - ansible_distribution in supported_distros
        - ansible_memtotal_mb*1024*1024 >= min_ram_size_bytes
# 서버 OS와 램 용량이 조건에 맞으면 -> os명_database_tasks.yml 이 파일로 진행이 된다.

    - name: print a messages for unsupported Distros
      debug:
        msg: >
          {{ inventory_hostname }} is a
          {{ ansible_distribution }}-based host, which is not one
          of the supported distributions ({{ supported_distros }})
      when: ansible_distribution not in supported_distros
#  서버 OS에 문제가 있는경우 해당 내용이 작동한다. 만약 문제가 없으면 SKIP된다.

    - name: Print a message for systems with insufficient RAM
      debug:
        msg: >
          {{ inventory_hostname }} does not meet the minimum RAM
          requirements of {{ min_ram_size_bytes }} bytes.
      when: ansible_memtotal_mb*1024*1024 < min_ram_size_bytes
# 서버 메모리에 문제가 있는경우 해당 내용이 작동한다. 만약 문제가 없으면 SKIP 된다.
# 즉, 정리하면 아무 문제가 없는경우, 3개의 TASK 중 맨 위 한개만 작동하게 된다.

- 여기서 사용하는 변수는 2가지인데, vars에 정의한 사용자 정의 변수, 그리고 팩트변수이다.

- 팩트변수는 다음과 같다.

     - ansible_distribution

     - inventory_hostname

     - ansible_memtotal_mb

 

* CentOS_database_tasks.yml

#For CentOS, using mariadb as the database service
- name: Set the 'db_service' fact
  set_fact:
    db_service: mariadb
# set_fact 모듈은 mariadb로 설정하는 db_service 변수를 ansible_facts안에 저장한다.
# 이를 통해 각 배포시 다른 데이터베이스 서비스를 활용할 수 있다.

#CentOS packages for mariadb service
- name: Ensure database packages are installed
  yum:
    name:
      - mariadb-server
      - mariadb-bench
      - mariadb-libs
      - mariadb-test
    state: latest
# 패키지를 설치한다.

- name: Ensure the database service is started
  service:
    name: "{{ db_service }}"   # set_fact 모듈로 정의한 사용자 정의 팩트변수이다.
    state: started
    enabled: true
# 위에서 정의한 서비스명으로 서비스를 시작하고 enable한다.

#below tasks are also reused by other distros
- name: create database users
  include_tasks: database_user_tasks.yml
# 계정설정 작업을 수행하기 위해  -> database_user_tasks.yml 파일로 넘어간다.

* database_user_tasks.yml

- name: Ensure database permission groups exist
  group:
    name: "{{ item }}"
    state: present
  loop: "{{ host_permission_groups }}"
# 그룹명 2개가 있는데, 각각을 item으로 하여 present한다. 즉 그룹 2개를 추가한다는 것.

- name: Ensure database user exists
  user:
    name: "{{ item.username }}"
    groups: "{{ item.role }}"
    append: yes
    state: present
  loop: "{{ user_list }}"
  when: item.role in host_permission_groups
# 그룹 리스트(dbadmin, dbuser)를 item 에서 찾아서 일치하면 루프를 작동한다.
# 루프는 유저 리스트대로 진행한다. username으로 계정을 만들고 그 계정은 명시한 그룹에 들어간다.
# append는 yes로 하면 groups로 명시한 그룹을 "추가"한다. no로 하면 groups로 명시한 그룹"만" 포함한다.

 

* 첫 시도 - 서버의 메모리 용량이 적어서 실패

 

* 2차 시도 - 잘 적용됨

 

* 확인

- db.example.com 에 접속해서 아래 명령어들로 확인한다.

cat /etc/passwd
cat /etc/group
rpm -qa |grep mariadb
systemctl status mariadb

 

 

 

참고링크

 

조건문 ansible 설명서

https://docs.ansible.com/ansible/2.7/user_guide/playbooks_conditionals.html 

 

Conditionals — Ansible Documentation

Docs » User Guide » Working With Playbooks » Conditionals You are reading an unmaintained version of the Ansible documentation. Unmaintained Ansible versions can contain unfixed security vulnerabilities (CVE). Please upgrade to a maintained version. See

docs.ansible.com

올바른 변수 이름을 생성하는 법 ansible 설명서

https://docs.ansible.com/ansible/2.7/user_guide/playbooks_variables.html#what-makes-a-valid-variable-name 

 

Using Variables — Ansible Documentation

Docs » User Guide » Working With Playbooks » Using Variables You are reading an unmaintained version of the Ansible documentation. Unmaintained Ansible versions can contain unfixed security vulnerabilities (CVE). Please upgrade to a maintained version.

docs.ansible.com

테스트 ansible 설명서

https://docs.ansible.com/ansible/2.7/user_guide/playbooks_tests.html 

 

Tests — Ansible Documentation

You are reading an unmaintained version of the Ansible documentation. Unmaintained Ansible versions can contain unfixed security vulnerabilities (CVE). Please upgrade to a maintained version. See the latest Ansible documentation. Tests in Jinja are a way o

docs.ansible.com

 

 

'Ansible' 카테고리의 다른 글

13장. 작업 제어 구현 - 오류처리  (0) 2021.02.25
12장. 작업 제어 구현 - 핸들러  (0) 2021.02.24
10장. ansible vault  (0) 2021.02.23
9장. fact 변수 관리  (0) 2021.02.20
8장. 변수 사용하기  (0) 2021.02.20

+ Recent posts