Profile picture

[Ansible] 앤서블 기본 사용방법

JaehyoJJAng2023년 04월 02일

1. 자동화 대상 호스트 선정

자동화를 위해 해야할 일은 먼저 어떤 시스템을 자동화할 것인지 그 대상 호스트를 선정해줘야 한다.

대상 호스트 선정이 되면 인벤토리(Inventory)를 통해 대상 호스트를 설정한다.


1-1. 인벤토리로 자동화 대상 호스트 설정

인벤토리(Inventory) 파일은 텍스트 파일이며, 앤서블이 자동화 대상으로 하는 관리 호스트를 지정한다.

이 파일은 INI 형식(이름 = 값) 또는 YAML을 포함한 다양한 형식을 사용하요 작성할 수 있다.

가장 간단한 형식은 아래와 같이 관리 호스트의 호스트명 또는 IP 주소가 한 줄에 하나씩 있는 목록 형태이다.

web1.example.com
web2.example.com
db1.example.com
192.168.219.5

1-1 챕터에서는 두 개의 실습을 진행해 볼 것이다.


IP를 이용한 인벤토리 파일

$ cd /ansible/practice

# IP를 이용한 인벤토리 파일 생성
vi inventory
192.168.219.192
192.168.219.193

호스트명을 이용한 인벤토리 파일

이번에는 앤서블이 호스트명으로 접근하도록 해볼 것이다.

그 전에 먼저 /etc/hosts 파일에 다음과 같이 자동화할 대상의 호스트가 지정되어 있어야 한다.

vi /etc/hosts

...
192.168.219.192 ubuntu1
192.168.219.193 ubuntu2

# 호스트명을 이용한 인벤토리 파일 생성
vi inventory
ubuntu1
ubuntu2

1-2. 역할에 따른 호스트 그룹 설정

작업을 하다보면 호스트별로 Role(역할)을 주고 롤별로 특정 작업을 수행해야 하는 경우가 종종 발생한다.

예를 들어 웹 서비스를 구성한다고 가정해보자.

웹 서비스를 구성하기 위해서는 웹 서버와 데이터베이스 서버가 필요하다. 그런데 이런 서버들을 고가용성을 위해 여러 대로 분리하는 경우 인벤토리도 유형별로 호스트를 설정해줄 수 있다.


그룹별 호스트 설정

그룹별로 호스트를 설정하여 사용하면 앤서블 플레이북 실행 시 그룹별로 작업을 처리할 수 있어 효과적이다.

이 경우 다음과 같이 그룹명을 대괄호([])내에 작성하고 해당 그룹에 속하는 호스트명이나 IP를 한 줄에 하나씩 입력한다.

아래 예제는 두 개의 호스트 그룹인 webdb를 정의한 것이다.

[web]
ubuntu1

[db]
ubuntu2

또한 호스트는 여러 그룹에 있을 수 있다. 실제 호스트를 여러 그룹으로 구성하면 호스트의 역할, 실제 위치, 프로덕션 여부에 따라 다양한 방식으로 구성해 볼 수 있다.

이렇게하면 특성, 용도 또는 위치에 따라 특정 호스트 집합에 앤서블 플레이를 쉽게 적용 가능하다.

[web]
ubuntu1

[db]
ubuntu2

[production]
ubuntu1
ubuntu2

[development]
192.168.219.200

중첩 그룹 정의

앤서블 인벤토리는 호스트 그룹에 기존에 정의한 호스트 그룹을 포함할 수도 있다.

이 경우 호스트 그룹 이름 생성 시 :children 이라는 접미사를 추가하면 된다.

아래 예제는 webdb 그룹의 모든 호스트를 포함하는 datacenter 그룹을 생성하는 예제이다.

[web]
ubuntu1

[db]
ubuntu2

[datacenter:children]
web
db

1-3. 인벤토리 확인

앤서블을 이용하여 사용자만의 자동화 플레이북을 작성해 볼 수도 있지만, 기존에 이미 구성되어 있는 플레이북을 확인하고 실행해야 하는 경우도 발생한다.

이때는 기존 인벤토리 파일의 내용을 ansible-inventory 라는 명령어로 확인해 볼 수 있다.


인벤토리 그룹 구성

앞에서 구성한 인벤토리 파일을 vi 에디터로 열어 그룹별로 설정하자.

예를 들어 web 그룹에는 manage1을, db 그룹에는 manage2를 추가하고

all 그룹에는 web 그룹과 db 그룹을 추가한다.

[web]
ubuntu1

[db]
ubuntu2

[all]
web
db

여기서 명령어 ansible-inventory를 이용하여 다음과 같이 특정 인벤토리 정보를 JSON 형태로 확인할 수 있다.

이 때 -i 옵션을 사용하면 특정 인벤토리 지정이 가능하다.

ansible-inventory -i ./inventory --list

{
    "_meta": {
        "hostvars": {}
    },
    "all": {
        "children": [
            "ungrouped",
            "web",
            "db"
        ]
    },
    "db": {
        "hosts": [
            "manage2"
        ]
    },
    "ungrouped": {
        "children": [
            "web",
            "db"
        ]
    },
    "web": {
        "hosts": [
            "manage1"
        ]
    }
}

--graph 옵션을 사용하면 트리 형태로 인벤토리 정보 열람이 가능하다.

ansible-inventory -i ./inventory --graph

@all:
  |--@ungrouped:
  |  |--@web:
  |  |  |--manage1
  |  |--@db:
  |  |  |--manage2
  |--@web:
  |  |--manage1
  |--@db:
  |  |--manage2

2. 첫 번째 플레이북 작성

인벤토리를 이용하여 대상 호스트를 정의해주었다면, 이번에는 대상 호스트에 수행될 작업들을 정의하기 위한 플레이북을 작성해보자.

ansible.cfg 라는 환경 설정 파일이 존재하는 디렉터리가 앤서블 프로젝트 디렉터리가 될 수 있다.


2-1. 플레이북 환경 설정

플레이북을 작성하고 실행하려면 여러 가지 설정을 사전에 해주어야 한다.

예를 들어 어떤 호스트에서 플레이북을 실행할 것인지, 플레이북을 root 권한으로 실행할 것인지,

대상 호스트에 접근할 때는 SSH 키를 사용할 것인지 패스워드를 사용할 것인지 등을 설정해줘야 한다.


2-1-1. 앤서블 환경 설정 파일

앤서블 프로젝트 디렉터리에 ansible.cfg 파일을 생성하면 다양한 앤서블 설정을 적용할 수 있다.

앤서블 환경 설정 파일에는 각 섹션에 "키-값" 쌍으로 정의된 설정이 포함되며, 여러 개의 섹션으로 구성된다.

[defaults]
inventory = ./inventory
remote_user = user
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]

보안과 감사로 인해 앤서블을 원격 호스트에 권한이 없는 사용자로 먼저 연결한 경우 관리 액세스 권한을 에스켈레이션하여 루트 사용자로 가져와야 할 때가 있다.

이 경우 해당 권한은 [privilege_escalation] 섹션에 설정할 수 있다.

매개 변수 설명
become 기본적으로 권한 에스컬레이션을 활성화할 때 사용하며, 연결 후 관리 호스트에서 자동으로 사용자를 전환할지 여부 지정. 일반적으로는 root로 전환되며, 플레이북에서도 지정 가능
become_method 권한을 에스컬레이션하는 사용자 전환 방식. 일반적으로 기본 값은 sudo를 사용.
become_user 관리 호스트에서 전환할 사용자 지정. 일반적으로 root
become_ask_pass become_method 매개 변수에 대한 암호를 묻는 메시지 표시 여부 지정. 기본 값은 false

2-1-2. 앤서블 접근을 위한 SSH 인증

앤서블은 로컬 사용자에게 개인 SSH 키가 있거나 관리 호스트에서 원격 사용자임을 인증 가능한 키가 구성된 경우 자동으로 로그인 된다.

SSH 키 기반의 인증을 구성할 때는 ssh-keygen 명령어를 사용하여 다음과 같이 생성할 수 있다.

ssh-keygen -t rsa -f ~/.ssh/forAnsible_id_rsa

SSH 키가 생성되면 ssh-copy-id를 사용하여 관리 노드로 복사한다.

for i in {2..3}; do ssh-copy-id root@192.168.219.19$i; done

/ansible.cfg 파일의 remote_user를 다음과 같이 수정하자.

테스트를 위해 inventory 파일을 아래와 같이 만들자.

[web]
ubuntu1

[db]
ubuntu2

[all:children]
web
db

그리고 ansible 명령을 사용하여 ping 테스트를 진행해보자.

$ ansible -m ping -i ./inventory web

ubuntu1 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3.10"
    },
    "changed": false,
    "ping": "pong"
}

이번에는 --ask-pass 옵션을 추가하여 ping을 테스트해보자. 그러면 SSH password를 입력하라는 프롬프트가 나오고, 정확한 패스워드를 입력하면 ping 테스트가 진행된다.

$ ansible -m ping -i ./inventory --ask-pass web

ubuntu1 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3.10"
    },
    "changed": false,
    "ping": "pong"
}

Permission denied

참고로 Ubuntu에서는 기본적으로 root 계정으로의 SSH 접근이 비활성화 되어있다. 이를 활성화하기 위해서는 아래 작업을 수행해주자.

# 1. Ubuntu 관리 노드에서 SSH 설정 파일 수정
sudo vim /etc/ssh/sshd_config

# 2. 파일에서 아래 설정을 찾고 수정
#PermitRootLogin prohibit-password

# 3. 위 줄을 아래와 같이 수정
PermitRootLogin yes

2-2. 플레이북 작성해보기

가장 간단한 플레이북을 작성해보자.

앤서블이 설치된 ansible-server에 접속하고 /etc/my-ansible 디렉터리로 전환하자.

그리고 vi 에디터를 사용해 다음과 같이 입력하고 저장하도록 하자.

$ vi first-playbook.yaml

---
- hosts: all
  tasks:
    - name: Print message
      debug:
        msg: Hello Ansible!

위 플레이북은 debug 모듈을 사용하여 Hello Ansible!라는 문자열을 출력한다.

플레이북을 작성할 때는 데이터 구조를 표현하기 위해 들여쓰기를 하는데, 이 때 공백 문자만 허용되므로 탭 문자는 사용하지 않는 것을 권장한다.


2-2-1. 플레이북 문법 검사

플레이북 작성 시 엄격한 공백 문자 때문에 문법 오류가 발생할 확률이 높다.

앤서블은 플레이북 실행 시 자체적으로 제공하는 모듈을 사용했는지, 그리고 공백은 들여쓰기가 정상적으로 되었는지 확인하기 위해 문법을 체크할 수 있는 옵션을 제공한다.

다음과 같이 ansible-playbook 명령어에 --syntax-check 옵션을 추가한 후 실행하고자 하는 플레이북 yaml 파일명을 입력하면 문법 체크를 수행한다.

$ ansible-playbook --syntax-check ./first-playbook.yaml

[WARNING]: No inventory was parsed, only implicit localhost is available
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'

playbook: ./first-playbook.yaml

정상적으로 실행된 것을 볼 수 있다.


2-2-2. 플레이북 실행하기

$ ansible-playbook -i ./inventory ./first-playbook.yaml

PLAY [all] *****************************************************************************************************************************************************************************************************************************************************************

TASK [Gathering Facts] *****************************************************************************************************************************************************************************************************************************************************
[WARNING]: Platform linux on host ubuntu2 is using the discovered Python interpreter at /usr/bin/python3.10, but future installation of another Python interpreter could change the meaning of that path. See https://docs.ansible.com/ansible-
core/2.17/reference_appendices/interpreter_discovery.html for more information.
ok: [ubuntu2]
[WARNING]: Platform linux on host ubuntu1 is using the discovered Python interpreter at /usr/bin/python3.10, but future installation of another Python interpreter could change the meaning of that path. See https://docs.ansible.com/ansible-
core/2.17/reference_appendices/interpreter_discovery.html for more information.
ok: [ubuntu1]

TASK [Print message] *******************************************************************************************************************************************************************************************************************************************************
ok: [ubuntu1] => {
    "msg": "Hello Ansible!"
}
ok: [ubuntu2] => {
    "msg": "Hello Ansible!"
}

PLAY RECAP *****************************************************************************************************************************************************************************************************************************************************************
ubuntu1                    : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
ubuntu2                    : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0  

2-2-3. 플레이북 실행 점검

플레이북을 실행하기 전 플레이북이 제대로 실행될지 궁금하다면 --check 옵션을 사용해 플레이북 실행 상태를 미리 점검할 수 있다.

먼저 sshd 서비스를 재시작하는 restart-sshd.yaml 이라는 파일을 다음과 같은 내용으로 생성해주자.

---
- hosts: all
  tasks:
    - name: Restart sshd service
      ansible.builtin.service:
        name: sshd
        state: restarted

--check 옵션을 사용해 ansible-playbook을 실행해보자. 그러면 다음과 같이 ssh 서비스가 재시작되어 서비스 상태가 변경될 예정이므로 Restart sshd service 태스크에서 'changed' 라는 문구를 볼 수 있을 것이다.

$ ansible-playbook -i ./inventory --check restart-sshd.yaml

TASK [Restart sshd service] ************************************************************************************************************************************************************************************************************************************************
changed: [ubuntu2]
changed: [ubuntu1]

PLAY RECAP *****************************************************************************************************************************************************************************************************************************************************************
ubuntu1                    : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
ubuntu2                    : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

3. 변수와 팩트

입력되는 값에 따라 동작을 변경하여 반복적인 사용이 가능하도록 설계하기 위해 앤서블 플레이북에서도 변수를 정의하고 사용할 수 있다.

변수를 어디에 정의하느냐에 따라 그룹 변수가 되거나, 호스트 변수가 되거나, 플레이 변수가 될 수도 있다.

변수에는 일반적인 내용을 저장하는 일반 변수와

패스워드와 같이 암호화가 필요한 민감한 정보들을 저장하는 변수가 있다.

또한, 시스템에서 수집한 값들을 사용하는 변수도 존재한다.


3-1. 변수의 종류와 사용법

앤서블은 변수를 사용하여 사용자, 설치하고자 하는 패키지, 재시작할 서비스, 생성 또는 삭제할 파일명 등 시스템 작업 시 사용되는 다양한 값들을 저장할 수 있다.


3-1-1. 그룹 변수

그룹 변수는 인벤토리(inventory)에 정의된 호스트 그룹에 적용하는 변수를 의미한다.

따라서 인벤토리에 정의해야 하고, 선언하고자 하는 그룹명과 함께 :vars 라는 문자열을 추가해 변수를 선언한다는 것을 알려주어야 한다.

예제를 통해 알아보자.


하나의 Inventory를 아래와 같이 생성해주자.

[web]
ubuntu1

[db]
ubuntu2

[all:children]
web
db

[all:vars]
user=ansible

이렇게하면 all 그룹에서 user 라는 변수를 사용할 수 있다.


이번에는 create-user.yaml 이라는 파일을 생성한다.

해당 파일은 사용자를 생성하는 태스크를 포함한다.

앤서블에서 시스템 사용자(account)를 생성하기 위해서는 ansible.builtin.user 라는 모듈을 사용하면 된다.

그리고 인벤토리에서 선언한 user 라는 변수를 {{ }} 사이에 넣어주면 해당 변수를 플레이북에서도 사용할 수 있다.

이 때 {{ }}와 변수 사이는 항상 한 칸씩 띄워주어야 한다.

---

- hosts: all
  tasks:
    - name: Create User {{ user }}
      ansible.builtin.user:
        name: "{{ user }}"
        state: present

앞에서 생성한 플레이북을 실행해보자.

$ ansible-playbook -i ./inventory create-user.yaml

TASK [Create User ansible] *************************************************************************************************************************************************************************************************************************************************
changed: [ubuntu2]
changed: [ubuntu1]

PLAY RECAP *****************************************************************************************************************************************************************************************************************************************************************
ubuntu1                    : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
ubuntu2                    : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0 

실제 대상 호스트(ubuntu1)에 접근하여 ansible 이라는 사용자가 생성되었는지 확인하자.

$ grep 'ansible' /etc/passwd

ansible:x:1001:1001::/home/ansible:/bin/sh

3-1-2. 호스트 변수

호스트 변수는 해당 호스트에서만 사용할 수 있는 변수이다.

인벤토리(Inventory) 파일을 열고 db 그룹 호스트 옆에 user=ansible 이라는 변수를 선언해보자.

[web]
ubuntu1

[db]
ubuntu2 user=ansible1

[all:children]
web
db

앞에서 작성했었던 create-user.yaml 파일을 create-user1.yaml라는 이름의 파일로 복사해주자.

cp create-user.yaml create-user1.yaml

그리고 해당 파일에서 hosts를 all에서 db로 수정해주자.

---
- hosts: db
  tasks:
    - name: Create User {{ user }}
      ansible.builtin.user:
        name: "{{ user }}"
        state: present

플레이북을 실행해보자.

ansible-playbook -i ./inventory create-user1.yaml

3-1-3. 플레이 변수

플레이 변수는 플레이북 내에서 선언되는 변수를 의미한다.

앞에서 작성했었던 create-user1.yaml 파일을 create-user2.yaml라는 이름의 파일로 복사해주자.

cp create-user1.yaml create-user2.yaml

그리고 해당 파일을 다음과 같이 수정하자.

---
- hosts: all
  vars:
    user: ansible2

  tasks:
    - name: Create User {{ user }}
      ansible.builtin.user:
        name: "{{ user }}"
        state: present

플레이북을 실행해보자.

ansible-playbook -i ./inventory create-user2.yaml

3-2. 암호화 - Ansible Vault

앤서블을 사용할 때 패스워드나 API 키 등의 중요한 데이터에 대한 액세스 권한이 필요할 수 있다.

이런 정보들은 인벤토리 변수나 일반 앤서블 플레이북에 텍스트로 저장된다.

이 때 앤서블 파일에 접근 권한이 있는 사용자라면 모든 파일 내용을 볼 수가 있는데,

이렇게 되면 외부로 민감한 정보가 유출될 수 있다는 보안상의 위험을 야기하게 된다.

따라서 앤서블은 사용되는 모든 데이터 파일을 암호화하고 암호화된 파일의 내용을 해독할 수 있는 Ansible Vault라는 기능을 제공한다.


3-2-1. 암호화된 파일 생성

Vault는 ansible-vault 라는 명령어를 사용하여 파일을 생성하고 암호화하여 사용할 수 있다.

또한 암호화된 파일 내용을 해독하고 확인할 수도 있다.


ansible-vault- create 라는 명령어와 함께 생성하려는 플레이북 파일명을 입력해보자.

그러면 패스워드를 입력하는 프롬프트가 나오는데 여기에 해당 파일에서 사용할 패스워드와 확인용 패스워드도 한 번 더 입력한다

ansible-vault create mysecret.yaml

생성된 파일 접근 권한을 살펴보면 파일을 생성한 소유자만 읽고 쓸 수 있음을 확인할 수 있다.

-rw------- 1 a-user a-user 355  83017:20 mysecret.yaml

# 파일 내용또한 암호화 되어있다.
$ cat mysecret.yaml

$ANSIBLE_VAULT;1.1;AES256
30396630643338383233336163366161653530333538303636323734353464353035303363343331
3439653162623134653235316435353262383763636263350a666363656634366330326461373431
37343335393739653364356234396364653866656533626138383864666337373164396164373733
3361376630633330330a323865613130323133653961623134623831623638346438666136626164
6533

ansible-vault view 명령어와 해당 파일명을 입력하면 파일을 생성할 때 사용했던 vault 패스워드를 입력하라는 프롬프트가 나온다.

$ ansible-vault view mysecret.yaml
Vault password: 

3-2-2. 기존 파일 암호화

기존에 이미 만들어놓은 파일도 암호화가 가능하다.

ansible-vault encrypt 명령어로 기존 파일을 암호화하고 해당 파일을 다시 복호화할 수 있다.

ansible-vault encrypt create-user.yaml

복호화 방법은 아래와 같다.

ansible-vault encrypt decrypt create-user.yaml

3-2-2. 암호화된 파일의 패스워드 변경

종종 패스워드를 변경해야 할 때가 있다.

앤서블은 ansible-vault로 암호화된 파일의 패스워드를 변경할 수 있다.

$ ansible-vault rekey mysecret.yaml

Vault password: 
New Vault password: 
Confirm New Vault password: 
Rekey successful

3-2-3. 암호화된 플레이북 실행

암호화된 플레이북은 어떻게 실행할까?

먼저 암호화된 mysecret.yaml을 살펴보자.

$ ansible-vault view mysecret.yaml

user: ansible3
password: P@ssword!

user와 password에 대한 변수와 값이 설정되어 있다.


이번에는 처음에 생성했었던 create-user.yaml 파일을 열어 다음과 같이 hosts 아래에 vars_file 내용을 추가하자.

---
- hosts: all
  vars_files:
    - mysecret.yaml
  
  tasks:
    - name: Create user {{ user }}
      ansible.builtin.user:
        name: "{{ user }}"
        state: present

암호화된 플레이북 실행 방법은 다음과 같다.

$ ansible-playbook --vault-id @prompt create-user.yaml

TASK [Create user ansible3] ************************************************************************************************************************************************************************************************************************************************
changed: [ubuntu1]
changed: [ubuntu2]

PLAY RECAP *****************************************************************************************************************************************************************************************************************************************************************
ubuntu1                    : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
ubuntu2                    : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

3-3. 자동 예약 변수 - Fact

팩트(Facts)는 앤서블이 관리 호스트에서 자동으로 검색한 변수이다. 팩트에는 플레이, 조건문, 반복문 또는 관리 호스트에서 수집한 값에 의존하는 기타 명령문의 알번 변수처럼 사용 가능한 호스트별 정보가 포함된다.


관리 호스트에서 수집된 일부 팩트에는 다음과 같은 내용이 포함될 수 있다.

  • 호스트 이름
  • 커널 버전
  • 네트워크 인터페이스 이름
  • 운영체제 버전
  • CPU 개수
  • 사용 가능 메모리
  • 스토리지 장치 크기 및 여유 공간

3-3-1. 팩트 사용해보기

앤서블은 팩트 기능이 기본 활성화가 되어있어 플레이북을 실행할 때 자동으로 팩트가 수집된다.

팩트는 ansible_facts 라는 변수를 통해 사용할 수 있다.


먼저 facts.yaml 라는 파일을 생성하고 다음과 같은 내용을 입력하자.

ansible.builtin.debug 라는 모듈을 사용하여 ansible_facts 라는 변수의 모든 내용을 출력하는 것이다.

---
- hosts: all
  tasks:
    - name: Print all facts
      ansible.builtin.debug:
        var: ansible_facts

플레이북을 실행해보자.

$ ansible-playbook -i ./inventory facts.yaml

ok: [ubuntu1]

TASK [Print all facts] *****************************************************************************************************************************************************************************************************************************************************
ok: [ubuntu1] => {
    "ansible_facts": {
        "all_ipv4_addresses": [
            "192.168.219.192"
        ],

  .....

이번에는 facts.yaml을 아래와 같이 수정한 후 다시 실행해보자.

---
- hosts: all
  tasks:
    - name: Print all facts
      ansible.builtin.debug:
        msg: >
          The default IPv4 address of {{ ansible_facts.fqdn }}
          is {{ ansible_facts.default_ipv4.address }}
$ ansible-playbook -i ./inventory facts.yaml

TASK [Print all facts] *****************************************************************************************************************************************************************************************************************************************************
ok: [ubuntu1] => {
    "msg": "The default IPv4 address of ubuntu1 is 192.168.219.192"
}
ok: [ubuntu2] => {
    "msg": "The default IPv4 address of ubuntu2 is 192.168.219.193"
}

3-3-2. 변수로 사용할 수 있는 팩트

앤서블 팩트를 통해 수집된 팩트 중에는 자주 사용하는 팩트들이 존재한다.

다음 표는 주로 사용되는 팩트를 정리한 표이다.

팩트 ansible_facts.* 표기법
호스트명 ansible_facts.hostname
도메인 기반 호스트명 ansible_facts.fqdn
기본 IPv4 주소 ansible_facts.default_ipv4.address
네트워크 인터페이스 이름 목록 ansible_facts.interfaces
/dev/sda1 디스크 파티션 크기 ansible_facts.sda.partitions.sda1.size
DNS 서버 목록 ansible_facts.nameservers
현재 실행중인 커널명 ansible_facts.kernel
운영체제 종류 ansible_facts.distribution

4. 반복문과 조건문으로 제어문 구현

플레이북으로 자동화 프로세스 개발을 하다 보면 애플리케이션 설치와 같이 동일한 작업을 여러 번 해야 하거나, 운영체제에 따라 다른 모듈을 써야 할 경우가 종종 발생한다.

이런 경우 앤서블에서 제공하는 loop 라는 반복문과, when 이라는 조건문을 사용할 수 있다.


4-1. 반복문

반복문을 사용하면 동일한 모듈을 사용하는 작업을 여러 번 작성하지 않아도 된다.

예를 들어, 서비스에 필요한 포트를 방화벽에 추가하는 작업이 있다고 가정해보자.

이 때 포트를 추가하는 작업을 여러 개 작성하는 대신 loop 반복문을 사용해 작업 하나로 여러 개의 포트를 추가할 수 있다.


4-1-1. 단순 반복문

단순 반복문은 특정 항목에 대한 작업을 반복한다. loop 키워드를 작업에 추가하면 작업을 반복해야 하는 항목의 목록을 값으로 사용한다.

그리고 해당하는 값을 사용하려면 item 변수를 이용할 수 있다.

예제를 통해 알아보자.


먼저 sshd 서비스와 rsyslog 서비스의 상태를 체크하는 플레이북을 ansible.builtin.servic 모듈을 사용하여 다음과 같이 작성한다.

---
- hosts: all
  tasks:
    - name: Check sshd state
      ansible.builtin.service:
        name: sshd
        state: started
    - name: Check rsyslog state
      ansible.builtin.service:
        name: rsyslog
        state: started

체크하려는 서비스를 각각의 작업 형태로 작성된 것을 볼 수 있다.


이번에는 하단에 loop 키워드를 추가하면 다시 작성해보자.

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

위 플레이북을 실행해보면 다음과 같다.

$ ansible-playbook -i ./inventory check-services.yaml

ok: [ubuntu1] => (item=sshd)
ok: [ubuntu2] => (item=sshd)
ok: [ubuntu1] => (item=rsyslog)
ok: [ubuntu2] => (item=rsyslog)

PLAY RECAP *****************************************************************************************************************************************************************************************************************************************************************
ubuntu1                    : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
ubuntu2                    : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0  

노드별, 서비스별로 서비스 상태가 체크되는 것을 볼 수 있을 것이다.


또한, loop 문에 사용하는 아이템을 변수에 저장하면 loop 키워드에 해당 변수명을 사용할 수 있다.

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

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

플레이북을 작성하다보면 단순하게 하나의 아이템만 사용할 때도 있을 것이다.

하지만 여러 개의 아이템이 필요한 경우도 발생한다.

예를 들어 여러 개의 사용자 계정을 생성하는 플레이북을 작성한다면 사용자 계정을 생성하기 위해 필요한 이름과 패스워드 등의

여러 항목을 loop 문에서 사전 목록으로 사용할 수도 있다.

파일을 생성하는 간단한 예제를 통해 알아보자.


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

위 파일을 단순 반복문 예제와 같이 vars 키워드를 사용할 수도 있다.

- hosts: all
  vars:
    files:
      - log-path: /var/log/test1.log
        log-mode: '0644'
      - log-path: /var/log/test2.log
        log-mode: '0600'
  tasks:
    - name: Create files
      ansible.builtin.file:
        path: "{{ item['log-path'] }}"
        mode: "{{ item['log-mode'] }}"
        state: touch
      loop: "{{ files }}"

4-1-3. Register 변수

Register 변수는 반복 실행되는 작업의 출력을 캡쳐할 수 있다.

이를 통해 반복되는 작업들이 모두 잘 수행되었는지 디버깅 해볼 수 있다.

---
- hosts: all
  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 -i ./inventory loop_register.yaml

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": [
            {
                "ansible_loop_var": "item",
                "changed": true,
                "cmd": "echo 'I can speak Korean'",
                "delta": "0:00:00.002777",
                "end": "2024-09-02 10:45:36.068628",
                "failed": false,
                "invocation": {
                    "module_args": {
                        "_raw_params": "echo 'I can speak Korean'",
                        "_uses_shell": true,
                        "argv": null,
                        "chdir": null,
                        "creates": null,
                        "executable": null,
                        "expand_argument_vars": true,
                        "removes": null,
                        "stdin": null,
                        "stdin_add_newline": true,
                        "strip_empty_ends": true
                    }
                },
                "item": "Korean",
                "msg": "",
                "rc": 0,
                "start": "2024-09-02 10:45:36.065851",
                "stderr": "",
                "stderr_lines": [],
                "stdout": "I can speak Korean",
                "stdout_lines": [
                    "I can speak Korean"
                ]
            },
                },
                "item": "English",
                "msg": "",
                "rc": 0,
                "start": "2024-09-02 10:45:36.202427",
                "stderr": "",
                "stderr_lines": [],
                "stdout": "I can speak English",
                "stdout_lines": [
                    "I can speak English"
                ]
            }
        ],
        "skipped": false
    }
}

4-2. 조건문

조건문을 사용하여 특정 조건이 충족될 때 작업 또는 플레이를 실행할 수 있다.


4-2-1. 조건 작업 구문

when 문은 조건부로 작업을 실행할 때 테스트할 조건을 값으로 사용한다.

조건이 충족되면 작업이 실행되고, 조건이 충족되지 않으면 작업을 건너뛴다.

when문을 테스트하는 가장 간단한 조건 중 하나는 Boolean 변수가 true 인지 false 인지의 여부이다.


아래 플레이북은 run_my_task 라는 변수에 true 라는 값을 저장하였다.

when 문에서 run_my_task를 사용하면 run_my_task가 true인 경우에만 작업이 실행될 것이다.

---
- hosts: localhost
  vars:
    run_my_task: true
  tasks:
    - name: echo message
      ansible.builtin.shell: "echo message"
      when: run_my_task 

위 플레이북을 실행하면 run_my_task가 true 이므로 echo message 태스크가 정상 수행될 것이다.

$ ansible-playbook -i ./inventory when_task.yaml

TASK [echo message] ********************************************************************************************************************************************************************************************************************************************************
changed: [localhost]

PLAY RECAP *****************************************************************************************************************************************************************************************************************************************************************
localhost                  : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0  

4-2-2. 조건 연산자

when 문을 사용할때는 true 또는 false 값을 갖는 Bool 변수 외에도 조건 연산자를 사용할 수 있다.

다음 표는 앤서블 when 문에서 사용할 수 있는 조건 연산자 예시이다.

연산 예시 설명
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
max_memory >= 256 max_memory 값이 256보다 크거나 같으면 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['machine'] in supported_distros ansible_facts['machine']의 값이 supported_distory 라는 변수에 있으면 true

그럼 간단한 예제로 OS 종류에 따라 태스크를 수행하는 플레이북을 작성해보도록 하자.

---
- hosts: all
  vars:
    supported_distros:
      - Ubuntu
  tasks:
    - name: Print supported os
      ansible.builtin.debug:
        msg: "This {{ ansible_facts['distribution'] }} need to use apt"
      when: ansible_facts['distribution'] in supported_distros

위 플레이북을 실행하면 결과는 다음과 같다.

TASK [Print supported os] **************************************************************************************************************************************************************************************************************************************************
ok: [ubuntu1] => {
    "msg": "This Ubuntu need to use apt"
}
ok: [ubuntu2] => {
    "msg": "This Ubuntu need to use apt"
}

PLAY RECAP *****************************************************************************************************************************************************************************************************************************************************************
ubuntu1                    : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
ubuntu2                    : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

관리 노드 모두 Ubuntu 이므로 플레이북의 Print supported os 작업이 정상 수행된 것을 볼 수 있을 것이다.


4-2-2-1. AND / OR

when 문에서는 and 연산자와 or 연산자를 함께 사용할 수 있다.

다음 플레이북은 두 연산자를 함께 사용하는 예제로, ansible_facts['distribution']의 값이 Ubuntu 이면서 ansible_facts['distribution_version']의 값이 '22.04'인 경우를 표현한다.

- hosts: all
  vars:
    supported_distros:
      - Ubuntu
  tasks:
    - name: Print supported os
      ansible.builtin.debug:
        msg: >
          OS Type: {{ ansible_facts['distribution'] }}
          OS Version: {{ ansible_facts['distribution_version'] }}
      when: >
        ( ansible_facts['distribution'] == 'CentOS' and
          ansible_facts['distribution_version'] == '8' )
          or
          ( ansible_facts['distribution'] == 'Ubuntu' and
            ansible_facts['distribution_version'] == '22.04' )

위 플레이북을 실행해보면 다음과 같다.

$ ansible-playbook -i ./inventory check-os.yaml

TASK [Print supported os] **************************************************************************************************************************************************************************************************************************************************
ok: [ubuntu1] => {
    "msg": "OS Type: Ubuntu OS Version: 22.04\n"
}
ok: [ubuntu2] => {
    "msg": "OS Type: Ubuntu OS Version: 22.04\n"
}

PLAY RECAP *****************************************************************************************************************************************************************************************************************************************************************
ubuntu1                    : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
ubuntu2                    : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0  

4-2-3. 반복문과 조건문 함께 사용

반복문과 조건문을 함께 사용해보자.


아래 플레이북에서 사용된 when 문의 item['mount']은 loop 문에서 ansible_facts의 mounts 중 mount 라는 값과 size_available 이라는 값을 사용해 구현한 것이다.

---
- hosts: web
  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

플레이북을 실행해보면 앤서블 팩트에서 mounts 라는 여러 개의 사전 타입의 변수값을 반복하면서 mount/(Root Directory)이고 size_available 값이 300000000 보다 큰 경우에만 메시지를 출력이 된다.

$ ansible-playbook -i ./inventory check-mount.yaml

ok: [ubuntu1]

TASK [Print Root directory size] *******************************************************************************************************************************************************************************************************************************************
ok: [ubuntu1] => (item={'mount': '/', 'device': '/dev/mapper/ubuntu--vg-ubuntu--lv', 'fstype': 'ext4', 'options': 'rw,relatime', 'dump': 0, 'passno': 0, 'size_total': 25180848128, 'size_available': 16377282560, 'block_size': 4096, 'block_total': 6147668, 'block_available': 3998360, 'block_used': 2149308, 'inode_total': 1572864, 'inode_available': 1490433, 'inode_used': 82431, 'uuid': 'efab5bcf-8a63-4463-a220-1ed261707ab1'}) => {
    "msg": "\"Directory / size is 16377282560\"\n"
}
skipping: [ubuntu1] => (item={'mount': '/snap/snapd/21759', 'device': '/dev/loop1', 'fstype': 'squashfs', 'options': 'ro,nodev,relatime,errors=continue', 'dump': 0, 'passno': 0, 'size_total': 40763392, 'size_available': 0, 'block_size': 131072, 'block_total': 311, 'block_available': 0, 'block_used': 311, 'inode_total': 651, 'inode_available': 0, 'inode_used': 651, 'uuid': 'N/A'}) 

4-3. 핸들러 및 작업 실패 처리

앤서블 모듈은 멱등이 가능하도록 설계되어 있다.

즉, 플레이북을 여러 번 실행해도 결과는 항상 동일하다.

또한 플레이 및 해당 작업은 여러 번 실행할 수 있지만, 해당 호스트는 원하는 상태로 만드는 데 필요한 경우에만 변경된다.

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

예를 들어 서비스의 구성 파일을 변경하려면 변경 내용이 적용되도록 서비스를 다시 로드해야 한다.

이 때 핸들러는 다른 작업에서 트리거한 알림에 응답하는 작업이며, 해당 호스트에서 작업이 변경될 때만 핸들러에 통지한다.


4-3-1. 앤서블 핸들러

앤서블에서 핸들러를 사용하려면 notify 문을 사용하여 명시적으로 호출된 경우에만 사용할 수 있다.

또한 핸들러를 정의할 때는 같은 이름으로 여러 개의 핸들러를 정의하기 보다는 각각 고유한 이름으로 정의하는 것을 권장한다.


다음 플레이북에 선언된 태스크는 rsyslog 서비스를 재시작하는 태스크이다.

해당 태스크가 실행되면 notify 키워드를 통해 print msg라는 핸들러를 호출한다.

핸들러는 handlers 라는 키워드로 시작하며, 태스크와 동일하게 작성한다.

---
- hosts: ubuntu1
  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"

플레이북을 실행하면 ubuntu1 노드의 rsyslog 서비스를 재시작하고, print msg 라는 핸들러를 호출해 rsyslog is restarted 라는 메시지를 출력한다.

$ ansible-playbook -i ./inventory handler-sample.yaml

TASK [restart rsyslog] *****************************************************************************************************************************************************************************************************************************************************
changed: [ubuntu1]

RUNNING HANDLER [print msg] ************************************************************************************************************************************************************************************************************************************************
ok: [ubuntu1] => {
    "msg": "rsyslog is restarted"
}

PLAY RECAP *****************************************************************************************************************************************************************************************************************************************************************
ubuntu1                    : ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

4-3-2. 작업 실패 무시

앤서블은 플레이 시 각 작업의 반환 코드를 평가하여 작업의 성공 여부를 판단한다.

일반적으로 작업이 실패하면 이후의 모든 작업을 건너뛴다. 하지만 작업해도 플레이를 계속 실행하도록 하려면 ignore_erros 라는 키워드를 추가하면 된다.

아래 플레이북은 에러를 발생시키기 위해 없는 패키지인 apache2를 설치하는 작업을 추가하고, 해당 작업 하단에 ignore_erros: yes 라는 구문을 입력하였다.

---
- hosts: ubuntu2
  tasks:
    - name: Install apache4
      ansible.builtin.apt:
        name: apache4
        state: latest
      ignore_errors: yes

    - name: print msg
      ansible.builtin.debug:
        msg: "before task is ignored"

플레이북 실행 시 Install apache2 태스크에서 에러가 발생하지만 ignoring 이라는 메시지와 함께 메시지가 무시되고 다음 태스크 수행으로 넘어간 것을 볼 수 있을 것이다.

$ ansible-playbook -i ./inventory ignore-example.yaml

TASK [Install apache4] *****************************************************************************************************************************************************************************************************************************************************
fatal: [ubuntu2]: FAILED! => {"changed": false, "msg": "No package matching 'apache4' is available"}
...ignoring

TASK [print msg] ***********************************************************************************************************************************************************************************************************************************************************
ok: [ubuntu2] => {
    "msg": "before task is ignored"
}

PLAY RECAP *****************************************************************************************************************************************************************************************************************************************************************
ubuntu2                    : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=1   

4-3-3. 작업 실패 조건 지정

앤서블에서 셸 스크립트 실행 뒤결과로 실패 또는 에러 메시지를 출력해도 앤서블에서는 작업이 성공했다고 간주한다.

이런 경우에는 failed_when 키워드를 사용하여 작업이 실패했음을 나타내는 조건을 지정할 수 있다.


먼저 아래와 같은 셸 스크립트를 작성해 ubuntu1 노드의 /root/scripts 경로에 adduser-script.sh 파일로 생성해주자.

#!/usr/bin/bash

# 사용자 계정 및 패스워드가 입력 되었는지 확인
if [[ -n ${1} ]] && [[ -n ${2} ]]
then	
	# 배열 선언
	declare -a UserList
	declare -a Password

	UserList=(${1})
	Password=(${2})
	
	# for문을 이용하여 사용자 계정 생성
	## i = 0 : 변수 'i' 에 초기값 선언
	## i < ${#UserList[@]} : UserList 배열의 인수 갯수가 i 보다 작을때까지
	## i++ : i 증감
	for (( i=0; i < ${#UserList[@]}; i++ ))
	do
		# if문을 사용하여 사용자 계정이 있는지 확인
		if [[ $(cat /etc/passwd | grep "${UserList[${i}]}" | wc -l ) == 0 ]]
		then
			# 사용자 생성 및 패스워드 설정
			/usr/sbin/useradd ${UserList[${i}]}
			echo "${Password[${i}]}" | passwd ${UserList[${i}]} --stdin
		else
			# 사용자가 있다고 메시지를 출력
			echo "This User ${UserList[${i}]} is existing"
		fi
	done

	# 사용자 계정이 입력 되지 않은 경우
else
	echo -e "Please Input user_id and password.\nUsage : adduser-script.sh "user01 user02" "pw01 pw02""
fi

간단히 설명하자면 인수로 생성할 계정명과 패스워드를 받아 사용자를 생성하는 스크립트이다.


그리고 플레이북에는 위 스크립트(add-user.sh)를 실행하기 위한 ansible.builtin.shell 모듈 사용 태스크를 추가하고,

해당 태스크 하단에 failed_when 구문을 추가한다.

failed_when 구문에는 조건식이 들어가는데 command_result.stdout 변수에 Please input user id and password 라는 문자열이 있으면 작업을 실패(fail)로 처리하겠다는 의미이다.

---
- hosts: ubuntu1
  tasks:
    - name: Run user add script
      ansible.builtin.shell: /root/scripts/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"

플레이북을 실행해보면 Run user add script 태스크는 정상 수행되지만 Report script failure 태스크에서 실패로 감지되어 플레이 수행이 중단된 것을 볼 수 있을 것이다.

$ ansible-playbook -i ./inventory failed-when2.yaml

TASK [Run user add script] *************************************************************************************************************************************************************************************************************************************************
changed: [ubuntu1]

TASK [Report script failure] ***********************************************************************************************************************************************************************************************************************************************
skipping: [ubuntu1]

PLAY RECAP *****************************************************************************************************************************************************************************************************************************************************************
ubuntu1                    : ok=2    changed=1    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0 

4-3-4. 블록 및 오류 처리

앤서블은 블록(Block) 이라는 오류 제어 문법을 제공한다.

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

또한 블록을 통해 rescue, always 문을 함께 사용함으로써 오류 처리가 가능하다.

  • block: 실행할 기본 작업 정의
  • rescue: block 절에 정의된 작업이 실패하는 경우 실행할 작업 정의
  • always: block 및 rescue 절에 정의된 작업의 성공 또는 실패 여부와 관계 없이 항상 실행되는 작업을 정의

아래 플레이북은 block 구문에서 ansible.builtin.find 모듈을 사용하여 /var/log/daily_log 라는 디렉토리를 찾는다.

이때 해당 디렉토리가 없는 경우 Warning을 발생시킨 후 다음 태스크로 넘어가지만, failed_wehen 구문을 사용하여

result.msg 변수에 Not all paths 라는 메시지가 발견되면 실패 처리하도록 하였다.

rescue 구문에는 디렉토리가 없는 경우 해당 디렉토리를 생성하며, always 구문에는 로그 파일을 생성한다.

---
- hosts: ubuntu1
  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 태스크에서 디렉토리를 찾지 못해 오류가 발생하고,

Make Directory when Not found Directory 태스크에서 디렉토리를 생성한다.

그리고 Create File 태스크가 실행됨을 볼 수 있다.

$ ansible-playbook -i ./inventory block-example.yaml

TASK [Find Directory] ******************************************************************************************************************************************************************************************************************************************************
[WARNING]: Skipped '/var/log/daily_log' path due to this access issue: '/var/log/daily_log' is not a directory
fatal: [ubuntu1]: FAILED! => {"changed": false, "examined": 0, "failed_when_result": true, "files": [], "matched": 0, "msg": "Not all paths examined, check warnings for details", "skipped_paths": {"/var/log/daily_log": "'/var/log/daily_log' is not a directory"}}

TASK [Make Directory when Not found Directory] *****************************************************************************************************************************************************************************************************************************
changed: [ubuntu1]

TASK [Create File] *********************************************************************************************************************************************************************************************************************************************************
changed: [ubuntu1]

PLAY RECAP *****************************************************************************************************************************************************************************************************************************************************************
ubuntu1                    : ok=3    changed=2    unreachable=0    failed=0    skipped=0    rescued=1    ignored=0  

Loading script...