본문 바로가기

DevOps

[Ansible] 반복문, 조건문, 핸들러, 블록, Recsue 등 개념 정리

반응형

가시다님 주관 A101 스터디에 대한 정리 - 2주차 입니다.

 

이번 주차에는 반복문, 조건문, 핸들러 개념에 대해 정리합니다.


# 반복문

Ansible은 loop, with_<lookup>, until 키워드를 통해 반복문을 사용할 수 있도록 제공한다.

( with_ 구문은 Ansible 2.5 버전부터 권장되지 않으며 loop을 사용할 것을 권장함 )

 

예를 들어 여러개의 file을 만든다던지 여러 user를 생성한다던지 등이 그 예이다.

 

https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_loops.html

 

Loops — Ansible Documentation

As of Ansible 2.8, you can get extended loop information using the extended option to loop control. This option will expose the following information. Note When using loop_control.extended more memory will be utilized on the control node. This is a result

docs.ansible.com

 

 

1. 단순 반복문

user 모듈을 사용하여 여러명의 user를 생성한다고 가정해보자.

 

반복문이 없다면 Add testuser1 user, Ass testuser2 user 라는 Task를 각각 총 2개 만들어야 한다.

- name: Add user testuser1
  ansible.builtin.user:
    name: "testuser1"
    state: present
    groups: "wheel"

- name: Add user testuser2
  ansible.builtin.user:
    name: "testuser2"
    state: present
    groups: "wheel"

 

 

딱봐도 비효율적인 것처럼 보이는데 이를 loop 키워드를 통해 간단하게 나타낼 수 있다.

- name: Add several users
  ansible.builtin.user:
    name: "{{ item }}"
    state: present
    groups: "wheel"
  loop:
     - testuser1
     - testuser2

loop에는 ["testuser1", "testuser2"] 라는 2개의 Element가 전달되고 실제로 사용하는 user 모듈에서는 item 이라는 key를 통해 각 value를 사용할 수 있게 된다.

 

 

2. 리스트 안에 hash가 있을 경우 반복문

각 loop 안에 name과 groups라는 subkey가 존재한다.

 

이럴 땐 각 item에 대해서 subkey를 .으로 구분하여 접근하면 된다.

- name: Add several users
  ansible.builtin.user:
    name: "{{ item.name }}"
    state: present
    groups: "{{ item.groups }}"
  loop:
    - { name: 'testuser1', groups: 'wheel' }
    - { name: 'testuser2', groups: 'root' }

 


# 조건문

https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_conditionals.html

 

Conditionals — Ansible Documentation

In a playbook, you may want to execute different tasks or have different goals, depending on the value of a fact (data about the remote system), a variable, or the result of a previous task. You may want the value of some variables to depend on the value o

docs.ansible.com

 

조건문은 말 그대로 특정 조건이 충족되어야 작업 또는 플레이를 실행시킬 수 있도록 해준다.

when 키워드를 사용하여 조건문을 사용하게 된다.

 

when 키워드는 가장 기본적으로 Boolean 변수가 true인지 false인지 유무를 확인한 뒤 true일 경우 Task를 수행하게 된다.

 

예를 들어 아래의 when 조건절들은 모두 true인지 false인지를 나타내게 된다.

 

ansible_facts['machine'] == 'x86_64'

min_memory < 512

ansible_facts['distribution'] in support_dists

 

 

1. 기본 사용법

ansible_facts 변수를 사용하여 Centos6이거나 Debian7일 경우 Shutdown 명령을 수행하는 Task이다.

 

이 때 when 키워드를 통해 조건문을 사용할 수 있다.

tasks:
  - name: Shut down CentOS 6 and Debian 7 systems
    ansible.builtin.command: /sbin/shutdown -t now
    when: (ansible_facts['distribution'] == "CentOS" and ansible_facts['distribution_major_version'] == "6") or
          (ansible_facts['distribution'] == "Debian" and ansible_facts['distribution_major_version'] == "7")

 

 

그럼 어떤 조건들을 사용할 수 있을까? 아래의 예시를 확인해보자.

 

ansible_facts[’machine’] == “x86_64”

-> 문자열 변수의 특정 값을 체크하여 true | false를 반환하는 조건이다.

 

max_memory == 512

-> 정수값을 체크하여 true | false를 반환

 

min_memory > 256

-> 정수값의 크고 낮음을 체크한다.

 

min_memory != 512

-> 정수값의 특정 값이 아닐 경우를 체크한다.

 

min_memory is defined // min_memory is not defined

-> 변수가 있는지 없는지를 체크한다.

 

ansible_facts[’distribution’] in supported_distros

-> ansible_facts 변수 중 distribution의 값이 supported_distros에 나열된 값들 중 하나일 경우 true를 반환하고 아닐 경우 false

 

  • != : 값이 같지 않을 때 true
  • >, >=, <=, < : ‘초과, ‘ 이상’, ‘이하’, ‘미만’ 일 때에 true
  • not : 조건의 부정
  • and, or : ‘그리고’, ‘또는’의 의미로 여러 조건의 조합 가능
  • in : 값이 포함된 경우에 true. 예를 들어 2 in “1, 2, 3” 은 true
  • is defined : 변수가 정의된 경우 true

 


# 핸들러 및 작업 실패 처리

https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_handlers.html

 

Handlers: running operations on change — Ansible Documentation

Sometimes you want a task to run only when a change is made on a machine. For example, you may want to restart a service if a task updates the configuration of that service, but not if the configuration is unchanged. Ansible uses handlers to address this u

docs.ansible.com

 

 

한 작업에서 시스템을 변경해야 하는 경우 추가 작업이 필요한 경우가 있을 수 있다.

예를 들어 서비스(httpd)의 구성 파일을 변경하려면 서비스가 재시작되도록 reload 해야 하는 경우가 발생할 수 있다.

 

이럴 때 Ansible Handler는 A 작업에서 Trigger한 알림에 응답하는 작업이며 해당 호스트에서 작업이 변경(changed)될 때만 핸들러에 통지한다.

 

 

아래 예시를 확인해보자.

 

1) Apache가 최신 버전인지 확인한다.

2) httpd.conf 파일을 Local에서 변경하고 dest(타겟 VM)에 있는 /etc/httpd.conf 파일로 복사한다. 즉, 파일의 내용이 변경되었을 수도 있고 아닐 수도 있다는 뜻이다.

3) 따라서 만약 /etc/httpd.conf 파일의 내용이 변경되었다면 changed가 될 것이고 이는 Restart apache라는 Handler를 트리거할것이다.

 

---
- name: Verify apache installation
  hosts: webservers
  vars:
    http_port: 80
    max_clients: 200
  remote_user: root
  tasks:
    - name: Ensure apache is at the latest version
      ansible.builtin.yum:
        name: httpd
        state: latest

    - name: Write the apache config file
      ansible.builtin.template:
        src: /srv/httpd.j2
        dest: /etc/httpd.conf
      notify:
        - Restart apache

    - name: Ensure apache is running
      ansible.builtin.service:
        name: httpd
        state: started

  handlers:
    - name: Restart apache
      ansible.builtin.service:
        name: httpd
        state: restarted

 

 

 

작업 실패 무시

ansible은 각 play 시 작업의 반환 코드를 평가하여 작업의 성공 유무를 판단하는데 일반적으로 작업이 실패하면 이후 모든 작업을 건너뜀

 

따라서 이러한 경우에는 ignore_errors란 키워드를 통해 에러가 발생하도 이후 작업들을 계속 실행할 수 있다.

 

아래 예시는 ignore_errors란 키워드가 없으니

당연히 apache3라는 존재하지 않는 package를 설치하려고 할 때 에러가 발생하고 Print msg task를 실행하지 못할 것이다.

---
- hosts : tnode1

  tasks:
    - name: Install apache3
      ansible.builtin.apt:
        name: apache3
        state: latest

    - name: Print msg
      ansible.builtin.debug:
        msg: "Before task is ignored"

 

아래 예시는 ignore_errors란 키워드를 통해 에러가 발생해도 다음 task를 수행할 수 있게 된다.

---
- 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"

 

 

 

 

 

작업 실패 후 핸들러 실행

 

작업 실패 후 Handler를 실행하고 싶다면?

force_handlers 키워드를 통해 에러가 발생했든 안했는 핸들러를 호출할 수 있다.

 

 

force_handlers 키워드가 없다면 rsyslog를 정상적으로 재시작했음에도 불구하고 install apache3 task가 실패함으로 인해 handler가 호출이 되지 않는다.

---
- 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"

 

 

 

force_handler 키워드 추가하면 install apache3 task가 실패해도 handler가 정상적으로 호출된다.

---
- 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"

 

 

 

 


# Block, Rescue

https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_blocks.html

 

Blocks — Ansible Documentation

All tasks in a block inherit directives applied at the block level. Most of what you can apply to a single task (with the exception of loops) can be applied at the block level, so blocks make it much easier to set data or directives common to the tasks. Th

docs.ansible.com

 

 

블록은 작업을 논리적으로 그룹화하는 절이며 작업 실행 방법을 제어하는 데 사용할 수 있다.

 

- block : 실행할 기본 작업을 블록화하여 정의한다.

- recsue : block 절에 정의된 작업이 실패할 경우 실행할 작업을 정의한다. ( rescue : 구출 )

- always : block, rescue 절에 정의된 작업 성공 / 실패 유무와 관계없이 항상 실행

 

 

다음 예시는 /var/log/daily_log 라는 디렉터리에 todays.log 파일을 생성하는 예시이다.

- Block 절에서는 Directory가 있는지 없는지 없는지 확인하고 없으면 recsue 절에서 file 모듈을 통해 생성한다.

- always 절을 통해 logfile을 해당 경로에 생성한다.

---
- 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'

 

 

처음 실행 때는 디렉터리가 없으니까 rescue 절을 통해 디렉터리를 만들었다.

두번째 실행 때는 디렉터리가 있으니까 rescue 절은 실행되지 않고 바로 파일을 만든다.

 

 

 


 

 

# 도전 과제

 

1. linux user1~10(10명) 를 반복문을 통해서 생성 후 확인 후 삭제를 해보자

 

아이디어

- ansible.builtin.user 모듈을 사용하자

- loop 키워드를 통해 반복문을 돌리자.

- user1, user2를 나열하는것 보다 user{{ item }} 이런식으로 변수를 지정해주고 1, 10까지 어떠한 함수를 통해 간소화하자.

 

- hosts: tnode1
  tasks:
  
    - name: Add Several users
      ansible.builtin.user:
        name: "user{{ item }}"
        state: present
      loop: "{{ range(1, 11)|list }}"

 

 

ansible-playbook 2weeks/homework1.yaml

 

 

- hosts: tnode1
  tasks:

    - name: Add Several users
      ansible.builtin.user:
        name: "user{{ item }}"
        state: absent
      loop: "{{ range(1, 11)|list }}"

 

 


 

2. loop 반복문 중 sequence 를 이용하여 /var/log/test1 ~ /var/log/test100 100개 파일(file 모듈)을 생성 확인 후 삭제를 해보자

 

- ansible.builtin.file 모듈을 사용하자 (https://docs.ansible.com/ansible/latest/collections/ansible/builtin/file_module.html)

- loop 키워드를 통해 반복문을 돌리자.
- 1, 100까지 range 함수를 사용하자 (위와 동일)

 

- hosts: tnode1
  tasks:

    - name: Add Several files
      ansible.builtin.file:
        path: "/var/log/test{{ item }}"
        state: touch
        mode: 0644
      loop: "{{ range(1, 31)|list }}"
    
    - name: Check files exist
      ansible.builtin.shell: |
        ls -l /var/log | grep test
      register: ls_output

    - name: Output
      debug:
        var: ls_output['stdout_lines']

 

 

 

- hosts: tnode1
  tasks:

    - name: Add Several files
      ansible.builtin.file:
        path: "/var/log/test{{ item }}"
        state: absent
        mode: 0644
      loop: "{{ range(1, 31)|list }}"
    
    - name: Check files exist
      ansible.builtin.shell: |
        ls -l /var/log | grep test
      register: ls_output

    - name: Output
      debug:
        var: ls_output['stdout_lines']

 

 


3. Ubuntu OS이면서 fqdn으로 tnode1 인 경우, debug 모듈을 사용하여 OS 정보fqdn 정보를 출력해보자

 

아이디어

- 결국 Ansible facts를 사용해야 한다.

- 그럼 Ansible Facts의 값들을 미리 확인해보자.

 

우리는 ansible facts를 캐싱해놨기 때문에 캐싱된 facts 값들을 확인할 수 있다.

ansible_nodename, ansible_hostname을 통해 tnode1인지 아닌지 확인할 수 있다.

 

 

또 Ubuntu인지 아닌지는 ansible_distribution을 통해 얻을 수 있을 것이다.

 

 

- hosts: all
  tasks:
    - name: Print os and fqdn
      ansible.builtin.debug:
        msg: "OS : {{ansible_facts['lsb']['description']}} , FQDN : {{ansible_facts['fqdn']}}"
      when: ansible_facts['distribution'] == "Ubuntu" and ansible_facts['nodename'] == "tnode1"

 

 

 

 


4. apache2 패키지를 apt 모듈을 통해서 설치 시, 핸들러를 호출하여 service 모듈로 apache2를 재시작 해보자

 

아이디어

- apache2 패키지는 ansible.builtin.apt 모듈을 통해 설치하자.

- 만약 해당 apache2 package가 설치되어 있지 않다면 changed를 통해 상태가 변경될 것이고 이는 특정 Handler를 트리거할 수 있다.

 

- hosts: tnode1
  vars:
    package: apache2
  tasks:
    - name: Install {{ package }}
      ansible.builtin.apt:
        name: "{{ package }}"
        state: present
      notify:
        - Restart {{ package }}
    
  handlers:
    - name: Restart {{ package }}
      ansible.builtin.service:
        name: "{{ package }}"
        state: restarted

 

 

 

 

 

반응형