CloudNetaStudy/[Study] Ansible

[2주차] Ansible 반복문과 조건문

HeeWorld 2024. 1. 20. 12:22

 

이 글은 CloudNet@ 팀 gasida님의 스터디 A101 1기 내용 및 실습으로 작성된 글입니다.

 


 

 

반복문

 

1. 단순 반복문

- loop 키워드를 작업에 추가하면 반복해야 하는 작업을 항목의 목록을 값으로 사용한다.

 

sshd와 rsyslog 서비스가 시작되어 있지 않다면, 시작하는 명령의 yml 파일을 생성한다.

 

---
- hosts: all
  tasks:
  - name: Check sshd and rsyslog state
    ansible.builtin.service:
      name: "{{ item }}"
      state: started
    loop:
      - sshd
      - rsyslog

 

loop 반복문 1

 

사용하는 아이템을 변수에 저장하면 loop 키워드에서 변수 목록을 변수로 사용할 수 있다.

 

---
- hosts: all
  vars:
    services:
      - sshd
      - rsyslog

  tasks:
  - name: Check sshd and rsyslog state
    ansible.builtin.service:
      name: "{{ item }}"
      state: started
    loop: "{{ services }}"

 

 

loop 반복문 2

 

 

2. 사전 목록에 의한 반복문

- 하나의 아이템을 사용할 수도 있지만, 동시에 다른 여러 개의 아이템이 필요할 때, loop문에서 사전 목록으로 사용할 수 있다.

 

log 파일을 생성하면서, mode로 권한을 바꾸는 파일을 생성하여 실행한다.

실행할 때 실제 로그 파일이 잘 생성되는지 watch를 걸어서 모니터링하고, 실제 ansible-playbook이 돌 때 log 파일이 생기면서 권한이 바뀌는 것을 확인할 수 있었다.

 

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

 

 

반복문

 

3. 반복문과 Register 변수 사용

- 반복실행되는 작업들이 모두 정상적으로 잘 수행되었는지 확인할 수 있고, 이 값을 이용해서 다음 작업을 수행할수도 있다.

 

shell 모듈을 이용하여 "I can speak ~" 라는 메시지를 출력하게 하고, loop 키워드를 사용해 Korean과 English가 사용되도록 아이템을 나열한다.

그리고 결과를 result 변수로 저장하여 debuh 모듈을 통해 내용을 확인한다.

 

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

 

Korean

 

English

 

register 키워드에 저장된 result 내용에는 대괄호 사이에 Key-Value 쌍으로 구성된 결과 값이 모두 저장되며, 배열 형식으로 출력된 것을 확인할 수 있다.

출력 결과를 보면 Korean과 English가 각각 2개의 Item으로 출력된 것을 확인할 수 있다.

 

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

 

 

위와 같이 출력 값을 stdout을 사용하여 한 줄(표준 출력)로 출력할 수 있는데, 확실히 위에서 편하게 볼 수 있던 것과 달리 이번에는 조금 보기 불편하게 출력되었다.

 

 

조건문

 

1. 조건 작업 구문

- when 문은 조건부로 작업을 실행할 때 테스트할 조건을 값으로 사용하며, 조건이 충족되면 작업을 실행하고 조건을 충족하지 않는다면 작업을 건너뛴다.

- when 문을 테스트하는 가장 쉬운 조건 중 하나는 Boolean 변수가 True, False 여부이다.

 

run_my_task 변수가 true로 값을 주고, when문에서 run_my_task를 사용하면 true일 때만 작업을 실행한다.

 

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

 

True 일 때 결과 값

 

run_my_task 값을 false 수정 후 playbook을 실행하니, skipped 된 것을 확인할 수 있다.

 

 

 

2. 조건 연산자

- when문에 bool 변수 외에도 조건 연산자를 사용할 수 있다.

 

- != : 값이 같지 않을 때 참 (true)

- >, >=, <=, < : 초과, 이상, 미만, 이하 일 때 참 (true)

- not : 조건의 부정

- and, or :  그리고, 또는 의 의미로 여러 조건으로 조합 가능

- in : 값이 포함된 경우에 true

- in defined: 변수가 정의된 경우 true

 

var 키워드로 supported_distro라는 변수를 주어, ansible_facts의 값이 Ubuntu 나 Centos이면 true (출력)

 

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

 

 

 

2. 복수 조건문

- when 문은 단일 조건문 뿐만 아니라 복수 조건문도 사용할 수 있다.

 

1. 운영체제가 CentOS이거나 Ubuntu일 경우에 작업이 수행되도록 yml 파일을 생성하여 실행해본다. (해당 when 문에는 or (또는) 조건을 사용)

 

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

 

or 조건

 

 

2. 운영체제가 Ubuntu이고, 해당 OS의 버전이 22.04인 경우에만 작업이 수행되도록 yml 파일을 생성하여 실행해본다. (해당 when 문에는 and(그리고) 조건을 사용, 두 개의 조건이 일치해야 실행됨.)

 

---
- 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'] == "22.04"

 

 

and 조건

 

ubuntu@server:~/my-ansible$ hostnamectl
 Static hostname: server
       Icon name: computer-vm
         Chassis: vm
      Machine ID: ec29acc53df305fd06148dbcea6601ca
         Boot ID: ad28e87c32bb4c6ebb5f6e10455cad21
  Virtualization: amazon
Operating System: Ubuntu 22.04.3 LTS
          Kernel: Linux 6.2.0-1017-aws
    Architecture: x86-64
 Hardware Vendor: Amazon EC2
  Hardware Model: t3.medium

 

os의 버전이 ubuntu 22.04.3 버전인 것을 확인할 수 있고, 위에서 기재한 조건문 운영체제가 Ubuntu이고, OS의 버전이 22.04인 조건이 맞았기 때문에 실행된 것이다.

만약에 조건이 하나라도 틀어지면 어떻게 되나 보려고 조건문에 OS 버전을 22.02로 바꾸어서 다시 실행했더니 조건이 맞지 않아 모두 skipped 된 것을 볼 수 있다.

 

조건이 맞지 않았을 때

 

3. 반복문과 조건문을 함께 사용

 

ansible facts에서 mounts라는 타입의 변수 값을 반복(현재 mount된 것들을 확인)하면서, mount가 "/"이고, size_available의 값이 "300000000"보다 큰 경우에만 메시지를 출력하고, 그렇지 않을 경우에는 작업을 건너뛰는 조건문 파일을 만들어서 실행해본다.

기존 cache가 아닌 새로운 cache로 수집을 하기위해 기존 cache를 삭제하고, ansible을 사용할 때 다시 cache 하도록 명령어를 추가하여 어떤 변화가 있을 지 watch 명령어로 모니터링을 걸어둔 상태로 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

 

 

수집된 mount 파일은 많지만 조건에 많지 않아 skpping 된 것을 볼 수 있고, 한개의 mount 파일만 조건에 만족한 것을 확인할 수 있다.

 

 

핸들러 및 작업 실패 처리

 

 

1. 앤서블 핸들러

- 앤서블에서 핸들러를 사용하려면, notify 문을 사용해서 명시적으로 호출된 경우에만 사용할 수 있고, 핸들러를 정의할 때 같은 이름으로 여러 개의 핸들러를 정의하기 보다 각각의 고유한 이름으로 정의하는 것이 좋다.

 

rsyslog 재시작 작업이 실행되면 notify(핸들러) 키워드를 통해 print msg라는 핸들러를 호출하도록 플레이북을 생성하였으며, 핸들러는 handlers 라는 키워드로 시작한다.

 

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

 

 

 

2. 작업 실패 무시

- 앤서블은 작동 시 각 작업의 반환 코드를 평가하여 작업의 성공 여부를 판단하고, 일반적으로 작업을 실패하면 앤서블은 그 이후의 모든 작업을 건너뛴다. 하지만 작업이 실해되어도 계속 실행할 수 있도록 ignore_erros 라는 키워드로 구현할 수 있다.

 

apache3라는 apt는 없기 때문에 (2는 있음) 실패되었을 때 다음 작업을 어떻게 처리하나 보기 위해 각각 yml 파일을 생성하였다.

 

---
- hosts : tnode1

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

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

 

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

 

 

1번 파일은 실패가 되자마자 바로 failed가 되어버려 결과 값에서도 failed=1이 된 것을 확인할 수 있었고, ignore_errors가 들어간 2번 파일을 실패하니까 ignoring 이라고 뜨면서 다음 작업 (메시지를 프린트)을 실행하여 결과 값에도 ignored=1 이 뜬 것을 볼 수 있다.

 

 

2. 작업 실패 후 핸들러 실행

- 앤서블은 일반적으로 작업이 실패하고 해당 호스트에서 실행이 중단되면 이전 작업에서 받은 알림을 모든 핸들러가 실행하지 않는다. 하지만, 플레이북에 force_handlers 라는 키워드를 추가하게 되면, 이후 작업이 실패하여 실행이 중단되어도 알람을 받은 핸들러가 호출된다.

 

force_handlers=yes를 추가하지 않은 파일과, force_handlers=yes를 추가한 파일을 생성하여 playbook을 실행해보았다.

 

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

 

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

 

 

1번 파일은 실패가 되자마자 바로 failed가 되어버려 핸들러가 실행되지 않은 것을 확인할 수 있었고, force_handlers=yes가 들어간 2번 파일은 실패해도 이전에 정상적으로 수행되었던 부분에 대해서 핸들러 작업 (메시지를 프린트)이 실행된 것을 확인할 수 있다.

 

 

3. 작업 실패 조건 지정

- command 계열 모듈을 사용 시, 앤서블에서 셸 스크립트를 실행한 후에 결과로 실패나 에러 메시지를 출력해도 앤서블은 해당 작업이 성공했다고 인지한다. 어떤 명령이라도 실행된 경우에는 task 실행 상태를 항상 changed 로 한다. 이런 경우 failed_when을 사용하여 작업이 실패했을 때를 나타내는 조건을 지정할 수 있다.

 

책 저자분께서 만들어주신 스크립트를 사용하여 실습을 진행해보았다.

사용자를 추가하는 스크립트 파일을 tnode1에 복사 후 확인하고, shell 모듈 사용하는 태스트를 사용하여 플레이북을 생성한다. 1개는 failed_when 조건식이 없는 yml, 1개는 failed_when 조건식이 들어간 yml 파일을 만들어 실행한다.

 

---
- hosts: tnode1

  tasks:
    - name: Run user add script
      ansible.builtin.shell: /home/ubuntu/adduser-script.sh
      register: command_result

    - name: Print msg
      ansible.builtin.debug:
        msg: "{{ command_result.stdout }}"

 

---
- hosts: tnode1

  tasks:
    - name: Run user add script
      ansible.builtin.shell: /home/ubuntu/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 }}"

 

 

 

1번의 경우 계정 정보를 넣어주지 않아 계정 생성을 실패했음에도 ok라는 결과를 보여주고, 2번의 경우 failed 이라는 결과 값을 보여준다.

만약 잡아내고 싶은(?) 문구가 많다면, 그걸 다 넣어줘야해서 엄청 편한 거라고 말하기는 어려울 것 같다는 생각이 들었다.

 

4. 앤서블 블록 및 오류처리

- 앤서블은 block이라는 오류를 제어하는 문법을 제공하고, 이것은 작업을 논리적으로 그룹화하는 문법절이며 작업 실행 방법을 제어하는데 사용할 수 있다. 그리고 블록문을 통해 rescue과 always를 함께 사용해서 오류를 처리할 수 있다.

 

- block : 실행할 기본 작업을 정의

- rescure : block에 정의된 작업이 실패했을 때 실행할 작업을 정의

- always : block 및 rescue 절에 정의된 작업의 성공, 실패 여부와 관계 없이 항상 실행되는 작업을 정의

 

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

 

 

Find Directory를 실패하여 rescue 구문을 통해 디렉터리가 없기 때문에 디렉터리를 생성했고, always 구문을 통해 로그 파일을 생성한 것을 볼 수 있다.

tnode2에 해당 경로를 보면 위와 같이 log 파일이 있는 것을 확인할 수 있다.