Skip to content

Latest commit

 

History

History
3649 lines (2625 loc) · 111 KB

플레이북.md

File metadata and controls

3649 lines (2625 loc) · 111 KB

플레이북 (Playbook)

플레이북은 Ansible의 핵심으로서 설정, 배포 및 지휘 언어로 동작해주게 하는 것입니다. 이것을 통하여 원격 시스템이 일련의 IT 작업을 수행하도록 하는 정책을 기술합니다.

만약 Ansible 모듈이 작업장의 공구이고 관리대상목록(Inventory)의 호스트들이 작업할 대상 이라면 플레이북은 실제 작업을 어떻게 수행하고 처리되는지를 나타내는 사용설명서와 같은 것입니다.

기본적으로 플레이북은 원격 머신을 설정하고 배포하는 등의 관리를 할 수 있습니다. 좀 더 고급스럽게는 다단계의 롤링 업데이트를 수행하고 다른 호스트에 수행할 행동을 지정하고 서버를 모니터링하여 로드 밸런싱을 하는 등의 고급 사용까지도 가능합니다.

비록 플레이북에 관한 많은 정보가 이 문서에서 제공된다 하더라도 모든 정보를 단번에 모두 알려고 하지 않아도 됩니다. 시간이 되는데로 원하는 기능을 하나씩 익혀가도 됩니다.

플레이북은 사람이 읽기 쉽게 설계되어 있으며 기본적인 텍스트로 구성되어 있습니다. 또한 플레이북을 구성하는 파일의 포함 방법 등을 비롯하여 아주 다양한 방법이 존재합니다.

이 책을 읽어가면서 예제 플레이북를 참고하시는게 많은 도움이 될 것입니다. 단지 어떻게 사용하는가 뿐만 아니라 다양한 배포 및 지휘 등에 대한 최선의 방법 등도 배울 수 있습니다.

플레이북 소개

플레이북에 관하여

플레이북은 단순히 Ansible의 애드-혹 명령을 사용하는 것 과는 완전히 다른 방식으로 강력한 기능을 제공합니다.

간단히 이야기 해서, 플레이북은 기존의 유사한 시스템이 아닌, 설정 관리의 기본 바탕을 제공하고 여러 머신의 배포 시스템으로 사용될 수 있으며 여러 다양한 응용을 할 수 있도록 되어 있습니다.

플레이북은 설정을 정의할 뿐만 아니라 일일이 지정하는 절차에 따라 수행되는 단계를 지휘할 수 있는데 관리 대상 장비를 특정 순서에 따라 앞에서 혹은 뒤에서 등의 다른 단계로 적용할 수 있습니다. 또한 작업을 동기 또는 비동기식으로 실행할 수 있습니다.

애드-혹 명령을 수행할 때 /usr/bin/ansible 명령을 실행했던 것과는 달리, 플레이북은 소스 버전관리 하듯이 설정을 밀어넣고 원격 시스템에서 해당 설정이 잘 적용되었는지 확인합니다.

플레이북에 관한 좀더 자세한 예제를 얻으려면 Ansible 예제 저장소를 참고합니다. 브라우저의 새로운 탭에서 이 예제 저장소를 열고 살펴보십시오.

플레이북 언어 예제

플레이북은 YAML 형식 (YAML 문법 참조)으로 표현됩니다.

각 플레이북은 하나 이상의 plays 목록으로 구성됩니다.

play의 목적은 일련의 호스트를 어떤 잘 정의된 역할(task라 불리우는)과 매핑되도록 하는 것입니다. 기본적으로 task는 Ansible 모듈을 호출하는 것과 다름 없습니다. (모듈에 관하여참조)

플레이북에 여러 play를 구성함으로써 여러 머신의 배포 뿐만 아니라 웹서버 그룹에 있는 모든 머신에 특정 작업을 수행하고 데이터베이스 서버 그룹에 다른 작업을 수행하는 지휘체계를 가집니다.

다음과 같이 하나의 플레이를 담고 있는 플레이북을 살펴보면,

---
- hosts: webservers
  vars:
    http_port: 80
    max_clients: 200
  remote_user: root
  tasks:
  - name: ensure apache is at the latest version
    yum: name=httpd state=latest
  - name: write the apache config file
    template: src=/srv/httpd.j2 dest=/etc/httpd.conf
    notify:
    - restart apache
  - name: ensure apache is running (and enable it at boot)
    service: name=httpd state=started enabled=yes
  handlers:
    - name: restart apache
      service: name=httpd state=restarted

다음은 동일한 내용이지만 모듈 안에 인자를 YAML의 Key:Value 형식으로 나누어 표시한 것입니다.

---
- hosts: webservers
  vars:
    http_port: 80
    max_clients: 200
  remote_user: root
  tasks:
  - name: ensure apache is at the latest version
    yum:
      name: httpd
      state: latest
  - name: write the apache config file
    template:
      src: /srv/httpd.j2
      dest: /etc/httpd.conf
    notify:
    - restart apache
  - name: ensure apache is running
    service:
      name: httpd
      state: started
  handlers:
    - name: restart apache
      service:
        name: httpd
        state: restarted

플레이북은 또한 ​한개 이상의 플레이를 가질 수 있습니다.

---
- hosts: webservers
  remote_user: root

  tasks:
  - name: ensure apache is at the latest version
    yum: name=httpd state=latest
  - name: write the apache config file
    template: src=/srv/httpd.j2 dest=/etc/httpd.conf

- hosts: databases
  remote_user: root

  tasks:
  - name: ensure postgresql is at the latest version
    yum: name=postgresql state=latest
  - name: ensure that postgresql is started
    service: name=postgresql state=started

위의 예제에서는 우선 웹서버 관련 플레이를 진행한 다음 데이터베이스 작업을 수행하는 것을 보여줍니다.

다른 사용자로 로그인하고 필요 시 sudo 명령을 하는 등, 호스트 그룹 간에 다른 대상의 작업을 진행할 수 있습니다. 태스크와 마찬가지로 플레이도 위에서 아래로 순차적으로 작업이 진행됩니다.

기본기

호스트와 사용자

각 플레이에는 작업 대상 호스트가 어느 것인지 지정해야 합니다.

hosts 라인이 하나 이상의 호스트나 호스트 그룹으로 작업 대상을 나타냅니다. 표기 방식은 패턴에 나타난 것과 동일합니다. remote_user 내용은 원격 사용자 ID를 나타냅니다.

---
- hosts: webservers
  remote_user: root

노트 : 버전 1.4 이전까지는 remote_user 대신 user 라고 했지만 user 모듈과 혼동될 우려가 있어 이름이 바뀌었습니다.

remote_user는 플레이 안에 있는 태스크에 나올 수도 있습니다.

---
- hosts: webservers
  remote_user: root
  tasks:
    - name: test connection
      ping:
      remote_user: yourname

노트 : 태스크 안에 remote_user 항목의 지원은 버전 1.4 이후입니다.

become을 이용하여 권한 상승 (sudo, su등, Become 참조)도 지원합니다.

---
- hosts: webservers
  remote_user: yourname
  become: yes

또는 다음과 같이 특정 태스크에만 권한 상승을 할 수 있습니다.

---
- hosts: webservers
  remote_user: yourname
  tasks:
    - service: name=nginx state=started
      become: yes
      become_method: sudo

노트 : became은 버전 1.9 이전에는 sudo/su 이었습니다.

또한 어떤 사용자로 로그인한 후 root 가 아닌 다른 사용자가 될 수 있습니다.

---
- hosts: webservers
  remote_user: yourname
  become: yes
  become_user: postgres

become_method 를 이용하여 권한 상승을 위한 방법을 제시할 수 있습니다.

---
- hosts: webservers
  remote_user: yourname
  become: yes
  become_method: su

만약 sudo 권한 상승을 하는데 암호가 필요하다면 ansible-playbook을 실행하는데 --ask-become-pass 옵션을 지정하여 암호를 입력하도록 하기 바랍니다. (이전 방법인 --ask-sudo-pass 또는 -K을 이용해도 됩니다.) 만약 플레이북이 실행되는데 있어 멈춘듯이 보인다면 아마도 이런 권한 상승을 위하여 사용자 입력을 대기하고 있는 것일 수 있습니다. 이런 경우에는 Ctrl-C를 눌러 해당 프로그램을 멈춘 다음 적절한 암호를 지정해 주시기 바랍니다.

!중요 만약 become_user 항목을 이용하여 root가 아닌 다른 사용자 권한을 얻을 때, 해당 모듈 인자는 /tmp 위치내의 임의 임시파일로 저장됩니다. 이 모듈은 해당 명령이 실행되지마자 바로 삭제됩니다. 이런 방식은 bob이라는 사용자가 timmy 권한을 가질 때도 동일하게 나타납니다. 하지만 bob 사용자가 root가 되거나 bob 또는 root 로 로그인 될 때에는 적용되지 않습니다. 만약 이런 데이터가 보안에 취약하다고 생각된다면 become_user 등과 같이 암호화되지 않은 암호를 전송하지 않도록 합니다. Ansible은 password 관련 인자의 내용을 로깅하지 않도록 하고 있습니다.

태스크 목록

각 플레이는 하나 이상의 태스크 목록을 포함합니다. 태스크는 순서대로 모든 작업 대상 호스트에 대해서 작업이 수행되는데 모든 호스트의 작업이 끝나고 나서 다음 태스크로 이동합니다. 플레이에서 수행되는 태스크는 모든 호스트의 태스크 명령들에 의해 모두 동일하게 수행되고 있음을 이해해야 합니다. 이것이 호스트 목록과 태스크를 매핑시키는 플레이의 목적입니다.

위에서 부터 아래로 차례대로 플레이북을 수행할 때 작업 실패가 발생한 호스트들은 전체 작업에서 제외됩니다. 만약 작업이 실패하면 플레이북 파일을 수정하고 다시 수행합니다.

각 태스크의 목표는 특정 인자를 지정하는 각각의 모듈을 실행하는 것입니다. 변수들은 이런 모듈 인자에 사용될 수 있습니다.

모듈은 멱등성(idempotent)을 갖는데 만약 다시 수행되어도 원하는 상태가 되기를 바라는 만큼만 수행됩니다.

commandshell 모듈은 chmod 또는 setsebool 등과 같은 명령이 수행되는 것과 같은 명령을 그대로 수행합니다.

모든 태스크는 name 을 갖는데 플레이북이 실행되는 결과 중에 해당 이름이 나타납니다. 이 결과는 사람이 읽을 수 있는 형식이고 각 단계의 작업을 살펴보는데 유용합니다. 만약 name이 지정되니 않는다면 결과에는 action 이라고만 나타날 것입니다.

이전 버전에는 action: module options 처럼 나타날 수 있지만 가능하면 module: options 와 같은 형식으로 사용하시기 바랍니다. 종종 오래된 플레이북을 살펴보면 이전 처럼 나타날 수 있지만 새로운 형시기 좀 더 읽기 좋습니다.

다음과 같이 위에 이야기한 태스크의 예가 있습니다.

tasks:
  - name: make sure apache is running
    service: name=httpd state=started

위의 예에서는 service 모듈에 대해서 key=value 방식으로 name=httpdstate=started 를 지정했습니다.

그러나,

tasks:
  - name: disable selinux
    command: /sbin/setenforce 0

위에서 처럼 commandshelll 모듈은 key=value 방식의 인자를 사용하지 않는 유일한 모듈입니다.

또한 commandshelll 모듈은 수행결과를 코드로 리턴하기 때문에 다음과 같이 성공인 경우, 또는 실패한 경우 다른 명령을 덧붙일 수 있습니다.

tasks:
  - name: run this command and ignore the result
    shell: /usr/bin/somecommand || /bin/true

또는,

tasks:
  - name: run this command and ignore the result
    shell: /usr/bin/somecommand
    ignore_errors: True

만약 모듈의 인자가 많아서 한줄을 넘어가는 경우에는 다음과 같이,

tasks:
  - name: Copy ansible inventory file to client
    copy: src=/etc/ansible/hosts dest=/etc/ansible/hosts
            owner=root group=root mode=0644

그 다음줄에 계속 인자를 사용하면 됩니다.

vars 섹션에 정의된 vhost 라는 변수를 태스크의 액션 라인에 사용하는 예 입니다.

tasks:
  - name: create a virtual host file for {{ vhost }}
    template: src=somefile.j2 dest=/etc/httpd/conf.d/{{ vhost }}

template 라는 곳에서도 변수를 사용하였습니다.

Action 축약

버전 0.8 이후

action: template src=templates/foo.j2 dest=/etc/foo.conf

0.8 이후에는

template: src=templates/foo.j2 dest=/etc/foo.conf

핸들러: 이벤트 발생시 수행

모듈은 멱등성을 갖도록 되어 있기 때문에 원격 시스템에서 변경이 이루어 졌을 때 전달 기능을 합니다. 플레이북은 이런 변경에 응답하기 위한 기본 핸들러를 가지고 있습니다.

플레이에 각 태스크 불럭의 마지막에서 액션을 트리거되고 다른 태스크에서도 알림이 떠도 한번만 핸들러가 동작합니다.

예를 들어, 웹서버의 설정 파일 변경되어 아파치 서버를 재기동시키라는 요구가 여러번 발생된다 하더라도 중간 중간 계속 아파치 서버가 재기동 될 필요는 없고 관련 작업이 모두 마친 다음 한번만 서비스 재기동을 하면 됩니다.

다음과 예는 특정 파일이 변경되었을 때 두 개의 서비스를 재시작하라는 것입니다.

- name: template configuration file
  template: src=template.j2 dest=/etc/foo.conf
  notify:
     - restart memcached
     - restart apache

위와 같이 notify 섹션에 있는 태스크를 핸들러라 합니다. 핸들러는 개별 handlers 섹션에 기술됩니다.

핸들러는 다른 일반 태스크와 동일한 태스크 목록이며 전체적으로 고유 이름을 갖고 핸들러 알림에 의해 발생합니다. 만약 핸들러에게 아무도 알려주지 않으면 핸들러는 수행되지 않습니다. 얼마나 많은 태스크에서 핸들러에게 알림을 하였던지 간에 모든 태스크가 끝나고 나서야 핸들러는 한번 수행됩니다.

다음은 이전 예제의 핸들러 입니다.

handlers:
    - name: restart memcached
      service: name=memcached state=restarted
    - name: restart apache
      service: name=apache state=restarted

Ansible 2.2 에서 핸들러는 더 일반 토픽을 listen 할 수 있으며 태스크는 이런 토픽에 알림을 보낼 수 있습니다.

handlers:
    - name: restart memcached
      service: name=memcached state=restarted
      listen: "restart web services"
    - name: restart apache
      service: name=apache state=restarted
      listen: "restart web services"

tasks:
    - name: restart everything
      command: echo "this task will restart the web services"
      notify: "restart web services"

위의 예제에서는 각각의 해당 이름이 아니라 listen 하고 있는 핸들러에 대해서 해당 태스크에서 알림을 보냄으로서 결과적으로 모든 핸들러가 수행되도록 하는 역할을 합니다.

노트

  • 핸들러는 notify 에 호출된 순서나 listen 하는 순서가 아닌 항상 정의된 순서에 따라 수행됩니다.
  • 핸들러 이름과 listen 토픽은 전역 이름 영역입니다.
  • 만약 두 개의 핸들러가 동일 이름을 가지고 있다면 하나만 호출됩니다.
  • include 안에 정의된 핸들러는 호출될 수 없으나, 버전 2.1 이후 static으로 선언된 외부 include에 정의된 핸들러는 불릴 수 있습니다.

핸들러에 대한 역할(Role)에 대한 것으로서,

  • pre_tasks, taskspost_tasks 섹션 에서 발생한 핸들러 호출은 발생한 섹션이 끝남과 동시에 자동으로 깨끗해집니다.
  • roles 섹션에 발생한 핸들러는 tasks 섹션이 끝남과 따라 자동으로 깨끗이 되지만 다른 어떤 tasks 핸들러 앞에서는 해당되지 않습니다.
tasks:
   - shell: some tasks go here
   - meta: flush_handlers
   - shell: some other tasks

플레이북 실행

$ ansible-playbook playbook.yml -f 10

Ansible-Pull

설정을 관리 대상 장비에 밀어 넣고 작업하는 대신 설정 파일이 변경된 것을 찾아 적용할 필요가 있을 수 있습니다.

ansible-pull 은 작은 크기의 스크립트로서 git에 설정이 변경된 것을 체크아웃하여 ansible-playbook을 실행합니다.

팁 및 트릭

플레이북이 실행되고 나서 나오는 결과 메시지를 확인하십시오. 어느 대상에 대하여 어떤 작업이 수행되었는지 나옵니다.

--verbose 플래그를 같이 이용하면 더 자세한 결과 메시지를 얻을 수 있습니다.

실제 플레이북 명령을 수행하기 전, 작업할 관리 대상 호스트 목록을 미리 구해보려면,

$ ansible-playbook playbook.yml --list-hosts

라고 하면 됩니다.

플레입북 Role 과 Include

소개

플레이북을 매우 큰 하나의 파일로 작성하는 것도 가능하지만 (초기에 플레이북을 배우면서는 이렇게 하기도 합니다) 점차 플레이북을 재사용하거나 계층적으로 관리할 필요도 생깁니다.

기본적으로는 플레이북의 내용을 여러 개로 나누어 단순히 포함하여 사용할 수 있습니다. 태스크 Incdlue 는 다른 파일에 있는 태스크를 가져올 수 있습니다. 핸들러 역시 외부에서 가져올 수 있기에 handler 섹션의 내용을 개벌 파일로 나눌 수 있습니다.

플레이북은 이처럼 안의 내용을 나누는 것 뿐만 아니라 다른 플레이북 파일을 불러올 수 있습니다. 이러면 플레이북이 실행되면서 다른 내용을 플레이북도 실행됩니다.

Ansible의 역할(Role)은 깨끗하고 재사용 가능한 추상화를 형성하는 파일들을 구성하는 것입니다.

우선 include 를 살펴본 다음 role을 살펴보도록 하겠습니다만 궁극적인 목적은 role을 잘 이해하는 것입니다. 플레이북을 더 잘 구성하면 할 수록 role을 잘 사용하게 될 것입니다.

태스크 가져오기(include) 및 재사용 의미

만약 플레이북의 플레이에 있는 일련의 태스크를 재사용한다고 가정해봅니다. include 를 이용하여 이 작업을 하면 됩니다. 가져온 태스크 목록은 특정 역할(role)을 수행하기에 알맞습니다. 다시한번 강조하면 플레이북은 관리 대상 시스템 그룹에 다양한 역할을 매핑하는 작업입니다.

---
# possibly saved as tasks/foo.yml

- name: placeholder foo
  command: /bin/foo

- name: placeholder bar
  command: /bin/bar

위와 같은 작업이 tasks/foo.yml에 있다고 하였을 때,

tasks:

  - include: tasks/foo.yml

위와 같이 include를 이용하여 해당 태스크를 포함시킵니다.

include 할 때 변수도 포함해서 가져올 수 있습니다. (parameterized include 라고 합니다)

예를 들어, 다양한 wordpress 사이트를 배포한다고 할 때,

tasks:
  - include: wordpress.yml wp_user=timmy
  - include: wordpress.yml wp_user=alice
  - include: wordpress.yml wp_user=bob

위와 같이 wp_user 라는 변수를 지정하여 가져올 수 있습니다.

버전 1.0부터 시작하여 include에서의 패러미터를 다음과 같이 지정할 수도 있습니다.

tasks:

  - include: wordpress.yml
    vars:
        wp_user: timmy
        ssh_keys:
          - keys/one.txt
          - keys/two.txt

include에 작접 패러미터를 전달하던 아니면 vars를 이용하던 상관없습니다. include 되는 파일에서는

{{ wp_user }}

와 같은 식으로 참조하면 됩니다.

아래의 예에서와 같이 handlers.yml 이라는 파일로 핸들러를 외부 파일로 빼 놓을 수 있습니다.

---
# this might be in a file like handlers/handlers.yml
- name: restart apache
  service: name=apache state=restarted

그리고 다음과 같이 include 합니다.

handlers:
  - include: handlers/handlers.yml

또한 다음과 같이 상위 플레이북에서 여러개의 하위 플레이북을 가져올 수 있습니다.

예를 들어,

- name: this is a play at the top level of a file
  hosts: all
  remote_user: root

  tasks:

  - name: say hi
    tags: foo
    shell: echo "hi..."

- include: load_balancers.yml
- include: webservers.yml
- include: dbservers.yml

플레이북에서 다른 플레이북을 include 하는 경우에는 변수 치환이 안된다는 것을 명심해야 한다.

주의

vars_files에서와 같이 include 파일에 조건을 주어 그 위치를 전달할 수 없습니다.

동적 혹은 정적 Include

Ansible 2.0 부터는 어떤 방식으로 include 시키는 방법이 약간 달라졌습니다. 이전 버전에서는 전처리기가 플레이북을 파싱하면서 단순 가져오기를 하였습니다. 이것은 그룹이나 호스트 변수와 같이 파싱 시에 알 수 없는 인벤토리 변수를 알 수 없다는 문제가 존재합니다.

Ansible 2.0에서는 동적(dynamic) include가 있어 실제 플레이가 실행되기 전 까지는 확장되지 않을 수 있습니다. 이런 변화는 다음의 예에서 처럼,

- include: foo.yml param={{item}}
  with_items:
  - 1
  - 2
  - 3

include 에 반복을 할 수 있습니다.

또한 동적 include 에서는 변수를 사용하는 것도 가능합니다.

- include: "{{inventory_hostname}}.yml"

하지만 동적 include는 다음과 같은 제약이 있음을 알아야 합니다.

  • 동적 include에 존재하는 핸들러 이름을 이용하여 notify 할 수 없다.
  • 동적 include에 있는 태스크를 --start-at-task 를 사용하여 수행할 수 없다.
  • 동적 include에 있는 태그는 -list-tags 결과에 나오지 않는다.
  • 동적 include에 있는 태스크는 -list-tags 결과에 나오지 않는다.

위의 문제를 해결하기 위하여 버전 2.1 이후에 정적(static) 옵션이 추가되었습니다.

- include: foo.yml
  static: <yes|no|true|false>

Ansible 2.1 이후 버전부터는 다음과 같은 조건을 만족하는 한 자동으로 동적(dynamic)이 아닌 정적(static) include를 합니다.

  • include 가 loop을 이용하지 않음
  • include 파일 이름이 변수를 사용하지 않음
  • static 옵션이 no 인 경우
  • ansible.cfg 옵션에서 static include가 비활성화 되어 있음

   ansible.cfg 설정파일에 정적 include 를 위한 다음과 같은 두 가지 옵션이 있습니다.

  • task_includes_static - 태스크 섹션에 있는 모든 include는 정적(static) 임
  • handler_includes_static - 핸들러 섹션에 있는 모든 include는 정적(static) 임

Role

버전 1.2 이후.

태스크와 핸들러에 관하여 알았으므로 이제는 플레이북을 어떻게 잘 구성하는가를 알아낼 차례입니다. Role을 이용 하면 됩니다. Role은 파일 구조에 따라 vars_files, tasks 와 핸들러 등을 자동으로 로딩하는 것입니다. Role에 따른 내용을 그룹화 하는 것 또한 다른 사용자와 쉽게 공유하기 위함입니다.

다음과 같은 프로젝트 구조가 되어 있다고 합시다.

site.yml
webservers.yml
fooservers.yml
roles/
   common/
     files/
     templates/
     tasks/
     handlers/
     vars/
     defaults/
     meta/
   webservers/
     files/
     templates/
     tasks/
     handlers/
     vars/
     defaults/
     meta/

플레이북에는 다음과 같이:

---
- hosts: webservers
  roles:
     - common
     - webservers

할 수 있습니다.

다음과 같이 role x 에 대하여 작업이 진행됩니다.

  • 만약 roles/x/tasks/main.yml 이 존재하면, 그곳에 있는 태스크 들이 플레이에 추가됩니다
  • 만약 roles/x/handlers/main.yml 이 존재하면, 그곳에 있는 핸들러 들이 플레이에 추가됩니다
  • 만약 roles/x/vars/main.yml 이 존재하면, 그곳에 있는 변수 들이 플레이에 추가됩니다
  • 만약 roles/x/defaults/main.yml 이 존재하면, 그곳에 있는 변수 들이 플레이에 추가됩니다
  • 만약 roles/x/meta/main.yml 이 존재하면, 그곳에 있는 role 의존성이 role 목록에 추가됩니다 (role 의존성은 아래에 따로 설명합니다)
  • Role에 있는 다른 어떤 copy, script, template 또는 include task 들은 role/x/{files,templates,tasks}/ 에 있는 것을 참조합니다.

버전 1.4 이후에는 role을 찾기위한 roles_path 설정 변수를 사용할 수 있습니다.

만약 해당 파일이 존재하지 않으면 해당 내용은 무시됩니다. 예를 들어 role을 위한 vars/ 하위 디렉터리는 없을 수도 있습니다.

다음과 같이 패러미터화 된 role 이 있을 수 있습니다.

---
- hosts: webservers
  roles:
    - common
    - { role: foo_app_instance, dir: '/opt/a',  app_port: 5000 }
    - { role: foo_app_instance, dir: '/opt/b',  app_port: 5001 }

자주 사용하지 않을 수 있지만 다음과 같이 조건적으로 사용할 수도 있습니다.

---
- hosts: webservers
  roles:
    - { role: some_role, when: "ansible_os_family == 'RedHat'" }

이것은 role 에 있는 모든 태스크에 조건적으로 적용됩니다. (자세한 것은 이후에)

마지막으로 태그를 지정하여 즉정 로그를 명시할 수 있습니다.

---
- hosts: webservers
  roles:
    - { role: foo, tags: ["bar", "baz"] }

role의 태스크에 있는 이런 tag는 role 안에 단순 정의된 tag를 우선합니다. 만약 만약 특정 role에 있는 여러 task를 부분적으로 사용할 필요가 생긴다면 해당 role을 더 작은 단위로 나눌 필요가 있습니다.

만약 플레이가 tasks 섹션을 그대로 가지고 있다면 이것은 role 이 먼저 적용되고 난 다음 실행됩니다.

만약 role 이전에 수행되어야 할 작업이 있다면,

---

- hosts: webservers

  pre_tasks:
    - shell: echo 'hello'

  roles:
    - { role: some_role }

  tasks:
    - shell: echo 'still busy'

  post_tasks:
    - shell: echo 'goodbye'

위에서 처럼 pre_tasks를 이용합니다.

노트

만약 태스크와 함께 태그를 사용하면 pre_tasks와 post_tasks에도 또한 태그가 있도록 합니다.

Role 디폴트 변수

버전 1.3 이후.

Role 디폴트 변수는 포함되거나 의존적인 Role에 디폴트 변수를 설정할 수 있도록 합니다. 디폴트를 생성하려면 role 디렉터리에 있는 defaults/main.yml 파일에 추가합니다. 이런 변수는 우선순위가 가장 낮으며 쉽게 다른 곳에 있는 동일 이름의 변수에 의해 덮어쓸 수 있습니다.

Role 의존성

버전 1.3 이후.

Role을 이용할 때 Role 의존성에 의해서 자동으로 다른 role을 수행하도록 하는 것입니다. 이 의존성은 meta/main.yml 파일에 포합됩니다. 이 파일은 role과 패러미터로 의존성을 가지는 rule 목록으로 구성됩니다.

---
dependencies:
  - { role: common, some_parameter: 3 }
  - { role: apache, apache_port: 80 }
  - { role: postgres, dbname: blarg, other_parameter: 12 }

또는 다음과 같이 role 경로를 직접 지정할 수도 있습니다.

---
dependencies:
   - { role: '/path/to/common/roles/foo', x: 1 }

Role 의존성은 버전관리나 tar 파일 (galaxy)을 이용해 설치될 수 있습니다. Role 의존성은 해당 Role을 포함하는 것이 실행되기 전에 재귀적으로 실행됩니다. 디폴트로 Role은 의존성으로 단 한번만 추가됩니다. 만약 또다른 role 목록이 의존성으로 나타난다면 다시 실행되지 않을 겁니다. 이런 방식은 allow_duplicates: yes 내용을 meta/main.yml file에 넣어 줌으로써 여러번 실행될 수 있습니다. 예를 들어 car 라는 role이 wheel이라는 rule 의존성을 갖도록 아래와 같이,

---
dependencies:
- { role: wheel, n: 1 }
- { role: wheel, n: 2 }
- { role: wheel, n: 3 }
- { role: wheel, n: 4 }

되어 있고 meta/main.yml 에 다음과 같이

---
allow_duplicates: yes
dependencies:
- { role: tire }
- { role: brake }

되어 있다면 그 실행 결과는,

tire(n=1)
brake(n=1)
wheel(n=1)
tire(n=2)
brake(n=2)
wheel(n=2)
...
car

와 같이 실행될 것입니다.

Role에 모듈 임베딩 및 플러그인

이 내용은 고급 사용자를 대상으로 합니다.

만약 커스텀 모듈을 개발(모듈 개발참조)하거나 플러그인을 개발(플러그인 개발참조)한다면 이것을 role의 일부로 배포하고 싶을 것입니다. 일반적으로 프로젝트로서의 Ansible은 핵심 모듈을 포함하여 잘 연동되어야 하는데 이런 부분이 어렵지 않도록 되어 있습니다.

AcmeWidgets 라는 회사에서 근무한다고 가정하고 내부 소프트웨어를 설정하는데 일익하는 내부 모듈을 개발하며, 같은 회사에 있는 다른 사람들이 당신이 개발한 모듈을 쉽게 이용하게 한다고 할때 모든 사람들에게 그 사용방법을 일일이 제공하지 않다고 됩니다.

role의 taskshandlers 구조와는 별개로 library라는 디렉터리를 추가합니다. 이 library 디렉터리에 필요한 모듈을 넣기만 하면 됩니다.

roles/
   my_custom_modules/
       library/
          module1
          module2

위와 같이 폴더 구성이 되어 있다면

다음과 같이,

- hosts: webservers
  roles:
    - my_custom_modules
    - some_other_role_using_my_custom_modules
    - yet_another_role_using_my_custom_modules

위와 같이 자신의 role을 미리 불러 놓으면 됩니다.

이와 같은 방식은 또한 테스트를 위하여 Ansible의 핵심 모듈을 직접 수정하여 테스트 해 보는데도 이용될 수 있습니다.

동일한 메카니즘으로 role에 플러그인이 사용될 수 있습니다.

roles/
   my_custom_filter/
       filter_plugins
          filter1
          filter2

Ansible Galaxy

Ansible Galaxy는 많은 사람들이 참여하는 커뮤니티로서 필요한 role을 찾고 다운로드하며 평가하고 리뷰하는 등의 일을 할 수 있는 사이트 이며 필요한 작업을 얻어 자신이 원하는 자동화를 실현하는 시작점으로 활용할 수 있습니다.

변수

자동화는 기본적으로 반복작업을 하는 것이지만 시스템에서는 항상 같은 상황일 수 없습니다. 어떤 시스템에서는 이렇게 설정하는 것도 다른 시스템에서는 약간 다르게 설정할 필요도 있습니다.

또한 실행에 따라 그 중간 결과를 보고 판단해야 할 수도 있습니다.

대부분 같지만 이렇게 부분 부분 다를 수 있는 것은 템플릿(template)을 이용하되 그 안에 변수로 다른 부분들을 설정합니다.

Ansible에서의 변수는 서로 다른 시스템을 유사하게 처리하는가를 의미합니다.

변수와 더불어 조건문반복문을 알아야 할 필요가 있습니다. group_by 모듈과 when 조건식 또한 변수를 이용하여 서로 다른 시스템을 활용합니다.

github를 포함하여 많은 예제를 살펴볼 것을 권해드립니다.

사용가능한 변수 이름

변수를 사용하기에 앞서 우선 사용가능한 변수 이름을 잘 알고 있는 것이 중요합니다. 영문자로 시작하고 영문자나 숫자 언더스코어로 구성된 변수명이 올바른 변수명입니다.

foo_port 또는 foo5는 올바는 변수명이고 foo-port, foo port, 12 는 모두 올바른 변수 이름이 아닙니다.

YAML 키:값 쌍으로 구성되어 있으며,

foo:
  field1: one
  field2: two

위와 같은 경우는 "foo" : { "field1" : "one", "field2" : "two" } 와 같은 딕셔너리로 구성되어 있습니다.

foo['field1']
foo.field1

위와 같이 두 가지 모두 접근 가능합니다. 모두 "one" 이라는 값을 가리킵니다. 하지만 .로 접근하게 되면 파이썬 딕셔너리의 메서드나 속성 참조에 헷갈릴 수 있으므로 가능하면 대괄호와 표현하도록 합니다. 다음 아래에 나오는 것은 키워드로서 변수에 사용되지 않는 것들입니다.

add, append, as_integer_ratio, bit_length, capitalize, center, clear, conjugate, copy, count, decode, denominator, difference, difference_update, discard, encode, endswith, expandtabs, extend, find, format, fromhex, fromkeys, get, has_key, hex, imag, index, insert, intersection, intersection_update, isalnum, isalpha, isdecimal, isdigit, isdisjoint, is_integer, islower, isnumeric, isspace, issubset, issuperset, istitle, isupper, items, iteritems, iterkeys, itervalues, join, keys, ljust, lower, lstrip, numerator, partition, pop, popitem, real, remove, replace, reverse, rfind, rindex, rjust, rpartition, rsplit, rstrip, setdefault, sort, split, splitlines, startswith, strip, swapcase, symmetric_difference, symmetric_difference_update, title, translate, union, update, upper, values, viewitems, viewkeys, viewvalues, zfill

관리대상목록(인벤토리)에 정의된 변수

플레이북에 정의된 변수

- hosts: webservers
  vars:
    http_port: 80

위와 같이 플레이북에서 한글을 잘 사용할 수 있습니다.

Import 된 파일이나 Role에서 정의된 변수

외부의 파일 등에서 정의된 변수도 불러오기 하여 사용할 수 있습니다.

변수 사용: Jinja2

지금까지 변수를 선언하는 것을 알아보았다면 다음에는 변수를 어떻게 이용해야 될까요?

Ansible에서는 Jinja2라는 템플릿 시스템을 이용하여 플레이북에서 변수를 참조합니다. Jinja2 템플릿 시스템은 아주 많은 일을 할 수 있지만 아주 기본적인 것부터 확인해 봅니다.

예를 들어 간단한 템플릿으로...

My amp goes to {{ max_amp_value }}

중괄호 2개를 연속해서 이용해서 해당 변수와 치환합니다.

또한 다음과 같이,

template: src=foo.cfg.j2 dest={{ remote_install_path }}/foo.cfg

변수를 파일 경로에 대응하도록 할 수도 있습니다.

템플릿 내에서는 호스트 네임 스페이스안에 있는 모든 변수에 자동으로 접근 가능합니다. 그 밖에 다른 호스트에서 정의된 변수까지도 접근할 수 있습니다.

노트

Ansible 은 템플릿에서 Jinja2의 반복문과 조건문을 사용할 수 있지만 플레이북에서는 불가능합니다. 플레이북 자체는 순수 YAML 구분입니다. 자동 코드로 생성되거나 다른 Ansible 파일을 사용하는 다른 시스템에서 생성한 플레이북이 가능합니다.

Jinja2 필터

노트

이 기능은 자주 사용되는 기능이 아닙니다.

Jinja2의 필터는 어떤 데이터를 다른 형태로 가공하는 변환 규칙의 방법입니다. Jinja2 내장 필터를 참고하시기 바랍니다.

잠시만요, YAML 좀 살펴보실께요

YAML에서 {{ foo }} 방식의 변수를 사용하려면 "를 이용하시기 바랍니다.

- hosts: app_servers
  vars:
      app_path: {{ base_path }}/22

위의 방식은 동작하지 않을 것입니다.

- hosts: app_servers
  vars:
       app_path: "{{ base_path }}/22"

대신 이렇게 이용하시기 바랍니다.

Facts : 시스템 발견 정보

변수와는 달리 사용자가 아닌 시스템에서 발견된 정보를 가지고 변수처럼 사용할 수 있는 것이 바로 Fact 입니다.

Fact는 원격 시스템에서 발견된 정보에서 생성됩니다.

$ ansible hostname -m setup

위와 같이 실행되면 다음과 같이 생각외로 많은 정보 (변수, facts)를 리턴합니다.

Ansible 1.4 버전에서 원격시스템, 우분투 12.04의 실행 결과 입니다.

"ansible_all_ipv4_addresses": [
    "REDACTED IP ADDRESS"
],
"ansible_all_ipv6_addresses": [
    "REDACTED IPV6 ADDRESS"
],
"ansible_architecture": "x86_64",
"ansible_bios_date": "09/20/2012",
"ansible_bios_version": "6.00",
"ansible_cmdline": {
    "BOOT_IMAGE": "/boot/vmlinuz-3.5.0-23-generic",
    "quiet": true,
    "ro": true,
    "root": "UUID=4195bff4-e157-4e41-8701-e93f0aec9e22",
    "splash": true
},
"ansible_date_time": {
    "date": "2013-10-02",
    "day": "02",
    "epoch": "1380756810",
    "hour": "19",
    "iso8601": "2013-10-02T23:33:30Z",
    "iso8601_micro": "2013-10-02T23:33:30.036070Z",
    "minute": "33",
    "month": "10",
    "second": "30",
    "time": "19:33:30",
    "tz": "EDT",
    "year": "2013"
},
"ansible_default_ipv4": {
    "address": "REDACTED",
    "alias": "eth0",
    "gateway": "REDACTED",
    "interface": "eth0",
    "macaddress": "REDACTED",
    "mtu": 1500,
    "netmask": "255.255.255.0",
    "network": "REDACTED",
    "type": "ether"
},
"ansible_default_ipv6": {},
"ansible_devices": {
    "fd0": {
        "holders": [],
        "host": "",
        "model": null,
        "partitions": {},
        "removable": "1",
        "rotational": "1",
        "scheduler_mode": "deadline",
        "sectors": "0",
        "sectorsize": "512",
        "size": "0.00 Bytes",
        "support_discard": "0",
        "vendor": null
    },
    "sda": {
        "holders": [],
        "host": "SCSI storage controller: LSI Logic / Symbios Logic 53c1030 PCI-X Fusion-MPT Dual Ultra320 SCSI (rev 01)",
        "model": "VMware Virtual S",
        "partitions": {
            "sda1": {
                "sectors": "39843840",
                "sectorsize": 512,
                "size": "19.00 GB",
                "start": "2048"
            },
            "sda2": {
                "sectors": "2",
                "sectorsize": 512,
                "size": "1.00 KB",
                "start": "39847934"
            },
            "sda5": {
                "sectors": "2093056",
                "sectorsize": 512,
                "size": "1022.00 MB",
                "start": "39847936"
            }
        },
        "removable": "0",
        "rotational": "1",
        "scheduler_mode": "deadline",
        "sectors": "41943040",
        "sectorsize": "512",
        "size": "20.00 GB",
        "support_discard": "0",
        "vendor": "VMware,"
    },
    "sr0": {
        "holders": [],
        "host": "IDE interface: Intel Corporation 82371AB/EB/MB PIIX4 IDE (rev 01)",
        "model": "VMware IDE CDR10",
        "partitions": {},
        "removable": "1",
        "rotational": "1",
        "scheduler_mode": "deadline",
        "sectors": "2097151",
        "sectorsize": "512",
        "size": "1024.00 MB",
        "support_discard": "0",
        "vendor": "NECVMWar"
    }
},
"ansible_distribution": "Ubuntu",
"ansible_distribution_release": "precise",
"ansible_distribution_version": "12.04",
"ansible_domain": "",
"ansible_env": {
    "COLORTERM": "gnome-terminal",
    "DISPLAY": ":0",
    "HOME": "/home/mdehaan",
    "LANG": "C",
    "LESSCLOSE": "/usr/bin/lesspipe %s %s",
    "LESSOPEN": "| /usr/bin/lesspipe %s",
    "LOGNAME": "root",
    "LS_COLORS": "rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arj=01;31:*.taz=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lz=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.axa=00;36:*.oga=00;36:*.spx=00;36:*.xspf=00;36:",
    "MAIL": "/var/mail/root",
    "OLDPWD": "/root/ansible/docsite",
    "PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
    "PWD": "/root/ansible",
    "SHELL": "/bin/bash",
    "SHLVL": "1",
    "SUDO_COMMAND": "/bin/bash",
    "SUDO_GID": "1000",
    "SUDO_UID": "1000",
    "SUDO_USER": "mdehaan",
    "TERM": "xterm",
    "USER": "root",
    "USERNAME": "root",
    "XAUTHORITY": "/home/mdehaan/.Xauthority",
    "_": "/usr/local/bin/ansible"
},
"ansible_eth0": {
    "active": true,
    "device": "eth0",
    "ipv4": {
        "address": "REDACTED",
        "netmask": "255.255.255.0",
        "network": "REDACTED"
    },
    "ipv6": [
        {
            "address": "REDACTED",
            "prefix": "64",
            "scope": "link"
        }
    ],
    "macaddress": "REDACTED",
    "module": "e1000",
    "mtu": 1500,
    "type": "ether"
},
"ansible_form_factor": "Other",
"ansible_fqdn": "ubuntu2.example.com",
"ansible_hostname": "ubuntu2",
"ansible_interfaces": [
    "lo",
    "eth0"
],
"ansible_kernel": "3.5.0-23-generic",
"ansible_lo": {
    "active": true,
    "device": "lo",
    "ipv4": {
        "address": "127.0.0.1",
        "netmask": "255.0.0.0",
        "network": "127.0.0.0"
    },
    "ipv6": [
        {
            "address": "::1",
            "prefix": "128",
            "scope": "host"
        }
    ],
    "mtu": 16436,
    "type": "loopback"
},
"ansible_lsb": {
    "codename": "precise",
    "description": "Ubuntu 12.04.2 LTS",
    "id": "Ubuntu",
    "major_release": "12",
    "release": "12.04"
},
"ansible_machine": "x86_64",
"ansible_memfree_mb": 74,
"ansible_memtotal_mb": 991,
"ansible_mounts": [
    {
        "device": "/dev/sda1",
        "fstype": "ext4",
        "mount": "/",
        "options": "rw,errors=remount-ro",
        "size_available": 15032406016,
        "size_total": 20079898624
    }
],
"ansible_nodename": "ubuntu2.example.com",
"ansible_os_family": "Debian",
"ansible_pkg_mgr": "apt",
"ansible_processor": [
    "Intel(R) Core(TM) i7 CPU         860  @ 2.80GHz"
],
"ansible_processor_cores": 1,
"ansible_processor_count": 1,
"ansible_processor_threads_per_core": 1,
"ansible_processor_vcpus": 1,
"ansible_product_name": "VMware Virtual Platform",
"ansible_product_serial": "REDACTED",
"ansible_product_uuid": "REDACTED",
"ansible_product_version": "None",
"ansible_python_version": "2.7.3",
"ansible_selinux": false,
"ansible_ssh_host_key_dsa_public": "REDACTED KEY VALUE"
"ansible_ssh_host_key_ecdsa_public": "REDACTED KEY VALUE"
"ansible_ssh_host_key_rsa_public": "REDACTED KEY VALUE"
"ansible_swapfree_mb": 665,
"ansible_swaptotal_mb": 1021,
"ansible_system": "Linux",
"ansible_system_vendor": "VMware, Inc.",
"ansible_user_id": "root",
"ansible_userspace_architecture": "x86_64",
"ansible_userspace_bits": "64",
"ansible_virtualization_role": "guest",
"ansible_virtualization_type": "VMware"

위와 같은 결과에서 첫번째 하드디스크는

{{ ansible_devices.sda.model }}

와 같이 접근할 수 있습니다.

반면, 호스트이름은 ( hosta.b.com 와 같이)

{{ ansible_nodename }}

와 같이 불러올 수 있고

{{ ansible_hostname }}

위와 같이 하여 해당 호스트명을 구해 올 수 있습니다.

Facts는 종종 조건문 혹은 템플릿에서 사용됩니다.

Facts는 또한 호스트를 동적으로 그룹화 하는데 사용되기도 합니다.

Facts 끄기

만약 이런 정보를 구해올 필요가 없거나 특별한 이유로 정보를 다 알거나 할 경우에는 이 기능을 끌 필요가 있습니다. 만약 실험적인 시스템을 테스트하거나 너무 많은 시스템을 다룰 때 이 기능을 끌 필요가 생깁니다.

- hosts: whatever
  gather_facts: no

플레이북의 플레이 안에 위와 같이 지정하면 됩니다.

로컬 Facts (Facts.d)

버전 1.3 이후.

Facts는 모통 setup 모듈에 의하여 원격 시스템의 정보를 구해오는 것입니다. 사용자는 개별 facts 모듈 API 가이드에 따라 작성할 수 있습니다. 그러나 이런 fact 모듈을 작성하지 않고 시스템 또는 데이터 정보를 지정할 수 있는 간단한 방법이 있을까요?

노트

로컬 이라는 의미가 원격시스템의 반대되는 로컬이라는 의미라기 보다는 원격 시스템 입장에서의 로컬 정보 설정으로 보아야 합니다.

만약 원격 관리 시스템이 /etc/ansible/facts.d 라는 디렉터리가 있고 그 아래에 .fact 로 끝나는 파일이 있는데 이 파일이 JSON, INI 또는 JSON을 리턴하는 실행파일이라면 이것은 Ansible에서 로컬 fact 라고 ㅁ합니다.

예를 들어, /etc/ansible/facts.d/preferences.fact 파일에

[general]
asdf=1
bar=2

라는 파일이 있다면

$ ansible <hostname> -m setup -a "filter=ansible_local"

라고 명령을 주면

"ansible_local": {
        "preferences": {
            "general": {
                "asdf" : "1",
                "bar"  : "2"
            }
        }
 }

라고 JSON 결과가 나옵니다. 또한 template/playbook에서 이 데이터를 접근가능합니다.

{{ ansible_local.preferences.general.asdf }}

로컬 네임스페이스는 시스템 fact 또는 플레이북에서 정의된 변수를 덮어쓰는 것을 막습니다.

Ansible 버전

"ansible_version": {
    "full": "2.0.0.2",
    "major": 2,
    "minor": 0,
    "revision": 0,
    "string": "2.0.0.2"
}

Fact 캐슁

버전 1.8 이후.

한 서버에서 다른 서버에 있는 변수를 참조할 수 있습니다.

{{ hostvars['asdf.example.com']['ansible_os_family'] }}

만약 Fact 캐슁이 활성화되어 있지 않다면 이런 역할을 하기 위하여 해당 asdf.example.com 호스트가 현재 플레이에 있으며 우선순위에 따라 미리 수행되어 있어야 합니다.

그렇지 않아도 가능하게 하기 위하여 플레이북이 실행되면서 각 호스트의 Fact가 캐슁되도록 합니다. 이것은 수동으로 설정에서 활성화시켜야 합니다. 예를 들어 수천개 이상 관리하는 매우 큰 시스템에서 이 기능을 활성화시키면 온갖 Fact를 저장하기 위하여 많은 자원을 소모할 것입니다.

Fact 캐슁이 활성화 되어 있으면 현재 /usr/bin/ansible-playbook이 실행되고 있지 않아도 어떤 그룹에 있는 머신이 다른 그룹에 있는 머신의 변수를 참조할 수 있습니다.

캐슁 방법은 redis 또는 jsonfile 입니다.

Fact 캐슁을 활성화 하려면 ansible.cfg 파일을

[defaults]
gathering = smart
fact_caching = redis
fact_caching_timeout = 86400
# seconds

와 같이 지정합니다.

redis 서버를 이용하려면

$ sudo apt-get install redis
$ sudo service redis start
$ sudo pip install redis

와 같은 식으로 시스템에 redis 관련 패키지를 설치해야 합니다.

json 파일을 이용한 캐슁을 하려고 한다면, ansible.cfg 파일에서

[defaults]
gathering = smart
fact_caching = jsonfile
fact_caching_connection = /path/to/cachedir
fact_caching_timeout = 86400
# seconds

와 같이 지정합니다.

등록 변수

변수의 중요한 사용방법 중에 하나는 특정 명령어를 수행하고 난 결과를 변수로 저장하는 것입니다. 결과는 모듈마다 틀릴 수 있으므로 -v 옵션을 이용하여 플레이북을 기동시키면 좀 더 자세한 결과를 살펴볼 수 있습니다.

현재 실행되고 있는 작업의 값은 변수에 저장되고 나중에 사용될 수 있습니다.

- hosts: web_servers

  tasks:

     - shell: /usr/bin/foo
       register: foo_result
       ignore_errors: True

     - shell: /usr/bin/bar
       when: foo_result.rc == 5

등록 변수는 플레이북이 실행되는 내내 유효하며 이것은 Fact 도 동일합니다.

반복문에서 register를 사용한다면 results 속성이 모듈 실행 결과의 목록을 담고 있습니다.

노트

만약 작업이 실패하거나 건너뛰었다면 해당 변수는 실패 혹은 건너뛴 상태를 담고 있습니다.

좀 더 복잡한 변수 접근

네트워킹 정보처럼 제공된 fact는 중첩된 데이터 구조로 되어 있습니다.

{{ ansible_eth0["ipv4"]["address"] }}

또는

{{ ansible_eth0.ipv4.address }}

와 같이 해당 fact 정보를 참조할 수 있습니다.

매직 변수, 다른 호스트 정보 접근

아무런 변수를 지정하지 않았더라도 Ansible이 자동으로 설정하는 변수들이 있습니다. 이 중, hostvars, group_names, groups, 그리고 environment 가 있습니다. 이런 정의 변수 이름을 사용자가 사용하면 안됩니다.

hostvars는 fact를 포함하여 다른 호스트의 변수를 참조하는 변수입니다.

만약 데이터베이스 서버가 다른 노드에 있는 fact 의 값 또는 인벤토리 변수를 참조한다고 할 때, 템플릿 또는 action 라인에 다음과 같이,

{{ hostvars['test.example.com']['ansible_distribution'] }}

참조를 합니다.

group_names는 현재 실행되고 있는 호스트가 속해있는 모든 그룹의 목록을 가지고 있습니다.

{% if 'webserver' in group_names %}
   # some part of a configuration file that only applies to webservers
{% endif %}

위와 같이 Jinja2 문법에 따른 템플릿을 구성할 수 있습니다.

groups 는 인벤토리에 있는 모드 그룹 ( 그리고 호스트 ) 를 담고 있는 목록 변수입니다.

{% for host in groups['app_servers'] %}
   # something that applies to all app servers.
{% endfor %}

위와 같이 모든 app_servers 그룹에 있는 모든 호스트를 열거할 수 있습니다.

한걸음 더 나아가 해당 그룹에 있는 모든 호스트의 IP 주소를 구하려면,

{% for host in groups['app_servers'] %}
   {{ hostvars[host]['ansible_eth0']['ipv4']['address'] }}
{% endfor %}

와 같이 하면 됩니다.

inventory_hostname 변수는 인벤토리 호스트 파일에 정의된 호스트이름을 담고 있습니다. 이것은 발견된 호스트 이름 (ansible_hostname)이 아니라 선언된 호스트이름을 이용할 때 유용합니다. 만약 긴 FQDN을 가지고 있다면 inventory_hostname_short을 이용해 FQDN의 첫번째 부분 (a.b.c 에서 a) 만 참조할 수 있습니다.

play_hosts 는 현재 실행되고 있는 플레이에서 접속가능한 호스트 목록을 담고 있습니다. 이것은 해당 머신의 로드 밸런싱 등을 할 때 유용하게 사용될 수 있습니다.

inventory_dir은 인벤토리 파일을 담고 있는 폴더를 알려줍니다. 반면 inventory_file은 인벤토리 파일의 전체 경로를 알려줍니다.

playbook_dir은 플레이북 파일의 베이스 디렉터리를 담고 있습니다.

role_path는 현재 role의 경로명을 나타내는데 role 에 있을 때만 동작합니다.

마지막으로 ansible_check_mode (2.1에서 추가)는 Ansible이 --check 옵션으로 실행되었을 때 True가 되는 블리언 변수입니다.

변수 파일

플레이북을 버전관리한다는 생각은 아주 뛰어난 생각입니다만 플레이북은 누구나 볼 수 있지만 그 안에 있는 내용 일부는 누구나 볼 수 없게 하고 싶을 수 있습니다. 비슷하게 메인 플레이북과 별도로 다른 파일에 정보를 담고 있을 수 있습니다.

---

- hosts: all
  remote_user: root
  vars:
    favcolor: blue
  vars_files:
    - /vars/external_vars.yml

  tasks:

  - name: this is just a placeholder
    command: /bin/echo foo

vars_files에 지정할 수 있는데, 그 내용은,

---
# in the above example, this would be vars/external_vars.yml
somevar: somevalue
password: magic

와 같습니다.

명령행에 변수 지정

vars_prompt vars_files과 더불어 Ansible 명령행에 다음과 같이 변수를 지정할 수 있습니다.

$ ansible-playbook release.yml --extra-vars "version=1.23.45 other_variable=foo"

1.2 이후 부터는 JSON 형식으로 --extra-vars를 지정해도 됩니다.

--extra-vars '{"pacman":"mrs","ghosts":["inky","pinky","clyde","sue"]}'

노트

값은 키=값 과 같은 문법이므로 문자열로 지정하는 반면 JSON 포맷은 불리언, 정수, 실수 등을 이용할 수 있습니다.

버전 1.3 이후로는 @를 붙여 파일을 지정할 수 있습니다.

--extra-vars "@some_file.json"

변수 우선 순위 : 어느 곳에 변수를 지정해야 하는가?

우선 동일한 이름의 변수를 사용하지 않습니다. 그러나 동일한 이름으로 사용되는 변수가 우선순위에 의해 어디에 사용된 변수를 더 우선적으로 사용하는가가 있습니다. 그것이 변수 사용 우선순위 입니다.

버전 1.x 에서는 다음과 같이 변수 우선순위가 높아집니다.

  • role defaults에 정의된 변수
  • 인벤토리에 정의된 변수
  • 시스템에서 발견된 facts
  • 기타 정의된 변수 (명령행 스위치, 플레이의 변수, 포함된 변수, role 등)
  • 연결 변수 (ansible_user 등)
  • 명령행에 지정한 -e 변수 (항상 최우선)

버전 2.x에서는 좀 더 상세히 우선순위가 나뉩니다.

  • role 디폴트
  • 인벤토리 변수
  • 인벤토리 group_vars
  • 인벤토리 host_vars
  • 플레이북 group_vars
  • 플레이북 host_vars
  • 호스트 facts
  • 플레이 변수
  • 플레이 vars_files
  • 등록 변수
  • set_facts
  • role과 포함된 변수
  • block 변수 (블럭에 있는 태스크)
  • 태스크 변수
  • 명령행에 지정한 -e 변수 (항상 최우선)

또 다른 예로서 연결 변수는 다른 변수를 우선시 합니다.

ansible_ssh_user will override `-u <user>` and `remote_user: <user>`

변수 적용 범위

다음과 같은 3가지 적용 범위를 갖습니다.

  • 글로벌 : 설정, 환경변수 명령줄 옵션 등으로 설정된 변수
  • 플레이 : 각각의 플레이와 포함한 구조체, 변수, 포함 변수, role defaults 와 변수 등
  • 호스트 : 호스트에 연관된 변수, facts, 등록 태스크 결과

변수 사용예

제일 먼저 그룹 변수의 사용예를 살펴보겠습니다.

group_vars/all에 그룹 변수를 지정합니다.

---
# file: /etc/ansible/group_vars/all
# this is the site wide default
ntp_server: default-time.example.com

지역 정보는 group_vars/region 변수에 정의됩니다. 만약 만약 이 그룹이 all 그룹에도 있었다면 거기에 우선하여 설정됩니다.

---
# file: /etc/ansible/group_vars/boston
ntp_server: boston-time.example.com

만약 특별한 이유로 특정 호스트가 더 우선하여 해당 정보를 지정해야 한다면

---
# file: /etc/ansible/host_vars/xyz.boston.example.com
ntp_server: override.example.com

와 같이 설정합니다.

기억할 내용: 자식 그룹은 부모 그룹을 우선하고 호스트는 항상 그룹을 우선한다.

다음은 role 변수의 우선순위에 대한 것입니다.

---
# file: roles/x/defaults/main.yml
# if not overridden in inventory or as a parameter, this is the value that will be used
http_port: 80

roles/x/defaults/main.yml에 정의되어 되어 있다면

---
# file: roles/x/vars/main.yml
# this will absolutely be used in this role
http_port: 80

이 변수가 더 우선 순위를 갖습니다.

role을 사용하다가 정의된 변수 대신 다른 것을 이용하려면,

roles:
   - { role: apache, http_port: 8080 }

이라고 합니다.

혹은,

roles:
   - { role: app_user, name: Ian    }
   - { role: app_user, name: Terry  }
   - { role: app_user, name: Graham }
   - { role: app_user, name: John   }

와 같이 사용할 수 있습니다.

Jinja2 필터

Jinja2 필터는 템플릿에서 데이터를 어떤 형태에서 다른 형태로 바꾸는 작업입니다. 이미 많은 내장 함수를 가지고 있습니다.

이런 필터 기능은 로컬 데이터를 다루는 Ansible의 컨트롤러에서 수행되는 것이며 타겟 머신의 태스크에서 이루어 지는 것이 아닙니다.

데이터 포맷을 위한 필터

어떤 결과를 다른 형태로 변경하는 것입니다. 디버깅을 할 때 유용한 경우가 있습니다.

{{ some_variable | to_json }}
{{ some_variable | to_yaml }}

또는 사람이 읽기 쉬운 형태로 출력합니다.

{{ some_variable | to_nice_json }}
{{ some_variable | to_nice_yaml }}

파이썬의 pprint 모듈처럼 indent를 줄 수도 있습니다. (버전 2.2 이후)

{{ some_variable | to_nice_json(indent=2) }}
{{ some_variable | to_nice_yaml(indent=8) }}

이제는 반대로 이미 포맷된 것에서 읽어 오는 경우도 있습니다.

{{ some_variable | from_json }}
{{ some_variable | from_yaml }}

예를 들어,

tasks:
  - shell: cat /some/path/to/file.json
    register: result

  - set_fact: myvar="{{ result.stdout | from_json }}"

어떤 변수가 정의되어야만 하도록 강제함

어떤 변수가 정의되어 있지 않다면 ansible의 ansible.cfg에 정의되어 있는 디폴트 행동을 따르게 되어 있는데 이를 끌 수 있습니다.

다음은 그 기능이 꺼지고 꼭 해당 변수가 존재하는지 체크하도록 하는 것입니다.

{{ variable | mandatory }}

만약 해당 변수가 정의되어 있지 않다면 템플릿이 동작하면서 오류가 발생합니다.

정의 되지 않은 변수의 디폴트 값 정의

Jinja2 는 default 필터를 제공하는데 해당 변수가 정의되지 않을 경우 디폴트 값을 갖게 됩니다.

{{ some_variable | default(5) }}

정의되지 않은 변수와 패러미터의 생략

버전 1.8 부터 omit이라는 특별 변수를 사용하여 변수나 모듈 패러미터를 생략하기위한 디폴트 필터를 사용할 수 있습니다.

- name: touch files with an optional mode
  file: dest={{item.path}} state=touch mode={{item.mode|default(omit)}}
  with_items:
    - path: /tmp/foo
    - path: /tmp/bar
    - path: /tmp/baz
      mode: "0444"

파일경로 path가 3개 있는데 앞에 2개는 시스템에서 제공하는 umask에 따라 touch 되고 마지막 파일경로 /tmp/baz 는 시스템 디폴트 umask와 상관없이 0444 로 설정됩니다.

노트

만약 default(omit) 필터 다음에 또다른 필터를 적용하려면 {{ foo | default(None) | some_filter or omit }} 와 같이 적용하면 될거라고 생각할 겁니다. 하지만 default 를 파이썬의 None에 적용하면 필터가 오류가 발생하므로 원하는 결과가 안나올 수 있습니다. 이런 경우에는 시행착오를 거쳐 해당 필터 체이닝(chaining) 테스트를 해 봐야 합니다.

List 필터

버전 1.8 이후.

해당 목록엣 제일 작은 값을 구하기 위하여,

{{ list1 | min }}

반대로 최대값을 구하려면,

{{ [3, 4, 2] | max }}

집합(SET) 이론 필터

버전 1.4 이후.

해당 목록에서 중복 항목을 제외한 set을 구하려면,

{{ list1 | unique }}

두 목록의 합집합을 구하려면,

{{ list1 | union(list2) }}

교집합을 구하려면,

{{ list1 | intersect(list2) }}

차집합을 구하려면,

{{ list1 | difference(list2) }}

합집합에서 교집합을 제외한 부분 (symmetric difference)을 구하려면,

{{ list1 | symmetric_difference(list2) }}

랜덤 숫자 필터

버전 1.6 이후.

어떤 목록에서 임의의 항목을 얻을 경우,

{{ ['a','b','c']|random }} => 'c'

0~n 까지의 임의의 숫자를 얻을 경우,

{{ 59 |random}} * * * * root /script/from/cron

0부터 100사이의 임의의 값을 구하는데 10 단위의 값을 구할 경우

{{ 100 |random(step=10) }}  => 70

1 부터 100 까지의 임의의 값을 구하는데 10 씩 증가하는 값을 구할 경우

{{ 100 |random(1, 10) }}    => 31
{{ 100 |random(start=1, step=10) }}    => 51

섞기 필터

버전 1.8 이후.

어떤 목록을 임의의 순서로 항목을 뒤섞을 경우,

{{ ['a','b','c']|shuffle }} => ['c','a','b']
{{ ['a','b','c']|shuffle }} => ['b','c','a']

Math

버전 1.9 이후.

log 값 구하기,

{{ myvar | log }}

10 base log 값 구하기,

{{ myvar | log(10) }}

해당 값의 제곱 또는 5제곱을 구할 때,

{{ myvar | pow(2) }}
{{ myvar | pow(5) }}

어떤 값의 제곱근, 5제곱근을 구할 떄,

{{ myvar | root }}
{{ myvar | root(5) }}

IP 주소 필터

버전 1.9 이후.

해당 값이 IP 주소인가 조사.

{{ myvar | ipaddr }}

ip 버전 4 또는 6을 지정할 경우,

{{ myvar | ipv4 }}
{{ myvar | ipv6 }}

CIDR에서 특정 정보를 구할 경우,

{{ '192.0.2.1/24' | ipaddr('address') }}

더 자세한 ipaddr 기능은 Jinja2 ipaddr() 필터를 참조하십시오.

해슁 필터

버전 1.9 이후.

특정 문자열의 sha1 해쉬 값을 구하려면,

{{ 'test1'|hash('sha1') }}

md5 해쉬는,

{{ 'test1'|hash('md5') }}

체크썸 문자열을 구하려면,

{{ 'test2'|checksum }}

기타 다른 해쉬 (플랫폼 의존적)

{{ 'test2'|hash('blowfish') }}

(random salt)를 이용한 sha512 해쉬인 경우,

{{ 'passwordsaresecret'|password_hash('sha512') }}

특정 sault를 이용한 sha256 해쉬를 구할 경우,

{{ 'secretpassword'|password_hash('sha256', 'mysecretsalt') }}

제공되는 해쉬 종류는 ansible을 동작시키는 마스터 시스템에 달려있고, 암호를 위해서는 password_hash에서 지정한 해쉬 라이브러리를 따릅니다.


해쉬와 딕셔너리 조합

2.0 이후.

combine 필터는 해쉬를 병합시킵니다. 예를 들어, 다음에서

{{ {'a':1, 'b':2}|combine({'b':3}) }}

위의 결과는,

{'a':1, 'b':3}

가 됩니다.

또한 recursive=True 패러미터를 통하여 사전의 내포 항목까지 찾아들어가 병합시킵니다.

{{ {'a':{'foo':1, 'bar':2}, 'b':2}|combine({'a':{'bar':3, 'baz':4}}, recursive=True) }}

위의 결과는,

{'a':{'foo':1, 'bar':3, 'baz':4}, 'b':2}

또한 combine 필터는 여러 인자를 받을 수 있습니다.

{{ a|combine(b, c, d) }}

컨테이너에서 값 추출

2.1 이후.

extract 필터는 해쉬나 어레이 같은 컨테이너에서 일련의 값을 뽑아 매핑하는데 사용됩니다.

{{ [0,2]|map('extract', ['x','y','z'])|list }}
{{ ['x','y']|map('extract', {'x': 42, 'y': 31})|list }}

위의 결과는

['x', 'z']
[42, 31]

해당 필터는 또다른 인자를 취할 수 있는데,

{{ groups['x']|map('extract', hostvars, 'ec2_ip_address')|list }}

이것은 그룹 x에서 호스트 목록을 얻은 다음 hostvars 에서 찾은 다음 ec2_ip_address 인 것을 찾습니다. 마지막 결과는 그룹 x에 있는 호스트의 IP 주소 목록을 결과로 얻습니다.

이 필터의 세번째 인자는 목록이 될 수 있는데,

{{ ['a']|map('extract', b, ['x','y'])|list }}

이 결과는 b[‘a’][‘x’][‘y’] 의 값을 갖는 목록을 리턴합니다.


코멘트 필터

버전 2.0 이후.

comment 필터는 해당 문자열을 코멘트 형식으로 출력합니다.

{{ "Plain style (default)" | comment }}

위의 결과는

#
# Plain style (default)
#

코멘트 스타일을 C(//...), C 블럭(/*...*/), Erlang(%...) 그리고 XML(<!--....-->) 와 같이 줄 수 있습니다.

{{ "C style" | comment('c') }}
{{ "C block style" | comment('cblock') }}
{{ "Erlang style" | comment('erlang') }}
{{ "XML style" | comment('xml') }}

그리고 코멘트 스타일을 별도로 지정할 수 있는데,

{{ "Custom style" | comment('plain', prefix='#######\n#', postfix='#\n#######\n   ###\n    #') }}

위의 결과는

#######
#
# Custom style
#
#######
   ###
    #

예를 들어 ansible.cfg 파일에 ansible_managed 변수가 다음과 같이,

[defaults]

ansible_managed = This file is managed by Ansible.%n
  template: {file}
  date: %Y-%m-%d %H:%M:%S
  user: {uid}
  host: {host}

정의 되어 있었다면

{{ ansible_managed | comment }}

위와 같이 코멘트 필터를 적용한 결과는,

#
# This file is managed by Ansible.
#
# template: /home/ansible/env/dev/ansible_managed/roles/role1/templates/test.j2
# date: 2015-09-10 11:02:58
# user: ansible
# host: myhost
#

이 됩니다.


다른 유용한 필터들

쉘에서 따옴표를 추가하려면,

- shell: echo {{ string_value | quote }}

어떤 값이 true 면 아니면... 과 같은 구문을 적용하려면 (1.9 이후)

{{ (name == "John") | ternary('Mr','Ms') }}

목록을 하나의 문자열로 join

{{ list | join(" ") }}

파일의 경로명 /etc/asdf/foo.txt에서 파일명 foo.txt 만 추출하려면

{{ path | basename }}

윈도우 시스템에서 파일명만 추출하려면, (2.0 이후)

{{ path | win_basename }}

윈도우 시스템에서 드라이브명만 추출하려면, (2.0이후)

{{ path | win_splitdrive }}

드라이브명의 첫 글자만 구하려면,

{{ path | win_splitdrive | first }}

드라이브명 없이 나머지 경로만 구하려면,

{{ path | win_splitdrive | last }}

파일 경로에서 디렉터리만 구하려면,

{{ path | dirname }}

윈도우 시스템에서 디렉터리만 구하려면 (2.0 이후)

{{ path | win_dirname }}

만약 틸더 ~ 문자를 가진 경로를 확장시키려면 (버전 1.5 이후)

{{ path | expanduser }}

링크의 실제 경로를 구하려면 (버전 1.8 이후)

{{ path | realpath }}

주어진 경로의 상대 경로를 구하려면 (버전 1.7 이후)

{{ path | relpath('/etc') }}

파일이름에서 확장자를 분리하려면 (버전 2.0 이후)

# with path == 'nginx.conf' the return would be ('nginx', '.conf')
{{ path | splitext }}

Base64 인코딩 문자열을 구하려면,

{{ encoded | b64decode }}
{{ decoded | b64encode }}

문자열에서 UUID를 생성하려면, (버전 1.9 이후)

{{ hostname | to_uuid }}

필요 시 cast 기능을 이용할 수 있습니다.

- debug: msg=test
  when: some_string_value | bool

버전 1.6 이후 regex_replace 필터를 이용하여 정규식 치환을 적용할 수 있습니다.

# convert "ansible" to "able"
{{ 'ansible' | regex_replace('^a.*i(.*)$', 'a\\1') }}

# convert "foobar" to "bar"
{{ 'foobar' | regex_replace('^f.*o(.*)$', '\\1') }}

# convert "localhost:80" to "localhost, 80" using named groups
{{ 'localhost:80' | regex_replace('^(?P<host>.+):(?P<port>\\d+)$', '\\g<host>, \\g<port>') }}

노트

버전 2.0 이전에 YAML 인자에서 변수와 함께 regex_replace 필터가 사용되었다면 후방참조(backreference) (예, \\1) 은 이스케잎 처리가 되어 \\ 대신 \\\\ 로 사용해야 합니다.

버전 2.0 이후.

정규식 안에서 특수 문자를 사용하려면 regex_escape 필터를 사용합니다.

# convert '^f.*o(.*)$' to '\^f\.\*o\(\.\*\)\$'
{{ '^f.*o(.*)$' | regex_escape() }}

복잡한 변수 목록에서 각 항목의 속성을 이용하려면 map 필터를 사용합니다.

# get a comma-separated list of the mount points (e.g. "/,/mnt/stuff") on a host
{{ ansible_mounts|map(attribute='mount')|join(',') }}

문자열에서 date 객체를 이용하려면 (2.2 이후)

# get amount of seconds between two dates, default date format is %Y-%d-%m %H:%M:%S but you can pass your own one 
{{ ((“2016-08-04 20:00:12”|to_datetime) - (“2015-10-06”|to_datetime(‘%Y-%d-%m’))).seconds }}

Jinja2 테스트 관련 필터

Jinja2의 테스트는 템플릿을 돌려 True 또는 False 결과를 리턴하는가를 보고 판단합니다. Jinja2의 내장 테스트 문서를 참조합니다. 테스트 하는 것은 필터를 이용하는 것과 동일하지만 C(map()) 또는 C(select()) 와 같이 목록에서 항목을 선택하는 것과 같은 목록 처리 필터에도 사용될 수 있습니다.

필터와 마찬가지로 테스트도 로컬 데이터를 테스트하는 Ansible 컨트롤러에서 수행되며, 원격 대상 태스크에서 수행되지는 않습니다.

문자열 테스트

서브 문자열 혹은 정규식 등으로 문자열을 매칭하기 위하여 match 또는 search 필터를 사용합니다.

vars:
  url: "http://example.com/users/foo/resources/bar"

tasks:
    - shell: "msg='matched pattern 1'"
      when: url | match("http://example.com/users/.*/resources/.*")

    - debug: "msg='matched pattern 2'"
      when: url | search("/users/.*/resources/.*")

    - debug: "msg='matched pattern 3'"
      when: url | search("/users/")

match는 전체 문자열에 대한 매치를 하는 반면 search는 부분 매치를 합니다.

버전 비교 터스트

버전 1.6 이후.

ansible_distribution_version 버전이 12.04 보다 같거나 큰 가를 비교하기 위하여 version_compare 필터를 사용합니다.

{{ ansible_distribution_version | version_compare('12.04', '>=') }}

version_compare 필터는 다음과 같은 비교 연산자를 사용할 수 있습니다.

<, lt, <=, le, >, gt, >=, ge, ==, =, eq, !=, <>, ne

그룹 포함 테스트

어떤 그룹이 다른 그룹의 서브셑 또는 수퍼셑 인가를 체크하기 위한 issubset, issuperset 필터가 있습니다.

vars:
    a: [1,2,3,4,5]
    b: [2,3]
tasks:
    - debug: msg="A includes B"
      when: a|issuperset(b)

    - debug: msg="B is included in A"
      when: b|issubset(a)

경로 테스트

- debug: msg="path is a directory"
  when: mypath|isdir

- debug: msg="path is a file"
  when: mypath|is_file

- debug: msg="path is a symlink"
  when: mypath|is_link

- debug: msg="path already exists"
  when: mypath|exists

- debug: msg="path is {{ (mypath|is_abs)|ternary('absolute','relative')}}"

- debug: msg="path is the same file as path2"
  when: mypath|samefile(path2)

- debug: msg="path is a mount"
  when: mypath|ismount

테스크 결과 테스트

tasks:

  - shell: /usr/bin/foo
    register: result
    ignore_errors: True

  - debug: msg="it failed"
    when: result|failed

  # in most cases you'll want a handler, but if you want to do something right now, this is nice
  - debug: msg="it changed"
    when: result|changed

  - debug: msg="it succeeded in Ansible >= 2.1"
    when: result|succeeded

  - debug: msg="it succeeded"
    when: result|success

  - debug: msg="it was skipped"
    when: result|skipped

노트

버전 2.1 이후 부터는 success, failure, change, 그리고 skip 을 결과에 맞게 사용할 수 있습니다.

조건식

종종 플레이 되는 것은 변수, fact (원격 시스템에 얻은 정보) 또는 이전 태스크 수행 결과 등에 따라 달라질 수 있습니다. 어떤 경우 변수의 값은 다른 변수에 의존합니다. Ansible의 실행 제어를 위한 다양한 방법이 존재합니다.

When 문장

때로는 특정 호스트에 대해서 작업을 건너 뛸 필요가 있을 수 있습니다. 만약 특정 패키지가 없다거나 해당 운영체제가 지원 버전보다 오래되었거나 아니면 파일시스템이 꽉 차서 비우는 등의 특정 작업을 수행해야 하는 등 때문에 건너 뛸 필요가 생깁니다.

이런 경우 when 문장을 사용하면 됩니다.

tasks:
  - name: "shut down Debian flavored systems"
    command: /sbin/shutdown -t now
    when: ansible_os_family == "Debian"
    # note that Ansible facts and vars like ansible_os_family can be used
    # directly in conditionals without double curly braces

일련의 조건식을 주기 위하여 다음과 같이 괄호를 이용할 수도 있습니다.

tasks:
  - name: "shut down CentOS 6 and Debian 7 systems"
    command: /sbin/shutdown -t now
    when: (ansible_distribution == "CentOS" and ansible_distribution_major_version == "6") or
          (ansible_distribution == "Debian" and ansible_distribution_major_version == "7")

만약 여러 조건식이 모두 and 조건이라면 목록으로 기술할 수 있습니다.

tasks:
  - name: "shut down CentOS 6 systems"
    command: /sbin/shutdown -t now
    when:
      - ansible_distribution == "CentOS"
      - ansible_distribution_major_version == "6"

when 문장에 Jinja2 필터를 사용할 수 있습니다.

tasks:
  - command: /bin/false
    register: result
    ignore_errors: True

  - command: /bin/something
    when: result|failed

  # In older versions of ansible use |success, now both are valid but succeeded uses the correct tense.
  - command: /bin/something_else
    when: result|succeeded

  - command: /bin/still/something_else
    when: result|skipped

노트

버전 2.1부터 success, succeeded (fail/failed 등)작업이 업데이트 되었습니다.

register 문에는 좀 더 다른 의미를 갖습니다.

다시 이전 fact 를 상기하며,

$ ansible hostname.example.com -m setup

팁: 때로는 문자열 변수에서 산술식 비교를 할 필요가 있습니다. 그런 경우에는 다음과 같이,

tasks:
  - shell: echo "only on Red Hat 6, derivatives, and later"
    when: ansible_os_family == "RedHat" and ansible_lsb.major_release|int >= 6

노트

위의 예제는 관리 대상 호스트에서 ansible_lsb.major_release fact 를 구하기 위하여 lsb_release 패키지가 필요합니다.

vars:
  epic: true

위와 같이 플레이북이나 인벤토리에 정의된 변수는 모두 when 구문에서 사용가능합니다.

tasks:
    - shell: echo "This certainly is epic!"
      when: epic

또는,

tasks:
    - shell: echo "This certainly isn't epic!"
      when: not epic

만약 요구된 변수가 설정되지 않았다면 Jinja2의 defined 테스트를 이용하여 건너뛸 수 있습니다.

예를 들어,

tasks:
    - shell: echo "I've got '{{ foo }}' and am not afraid to use it!"
      when: foo is defined

    - fail: msg="Bailing out. this play requires 'bar'"
      when: bar is undefined

이 방법은 변수를 외부에서 불러와서 사용할 때 특히 유용합니다.

반복문과 조건식

when 구문을 with_items 과 같이 사용할 수 있습니다. when 구문은 for each 항목과 무관하게 동작함을 명심해야 합니다.

tasks:
    - command: echo {{ item }}
      with_items: [ 0, 2, 4, 6, 8, 10 ]
      when: item > 5

만약 선언되어 있지 않은 변수에 디폴트를 지정하고 싶다면,

- command: echo {{ item }}
  with_items: "{{ mylist|default([]) }}"
  when: item > 5

목록이 아니고 딕셔너리인 경우 with_dict를 사용합니다.

- command: echo {{ item.key }}
  with_dict: "{{ mydict|default({}) }}"
  when: item.value > 5

커스텀 Facts에서 로딩

개발중인 모듈에서 쉽게 fact를 제공할 수 있습니다.

tasks:
    - name: gather site specific fact data
      action: site_facts
    - command: /usr/bin/thingy
      when: my_custom_fact_just_retrieved_from_the_remote_system == '1234'

role 과 include 에 when 구문 적용하기

만약 동일한 조건식을 여러 태스크에서 동일하게 사용한다면 다음과 같이 include 구문을 태스크에 조건적으로 첨부할 수 있습니다. 모든 태스크는 수행되지만 조건식은 각각의 태스크 혹은 모든 태스크에 적용됩니다.

- include: tasks/sometasks.yml
  when: "'reticulating splines' in output"

노트

버전 2.0 이전에는 include 태스크에는 적용되었지만 플레이북에는 적용되지 않았었습니다.

role 에는,

- hosts: webservers
  roles:
     - { role: debian_stock_config, when: ansible_os_family == 'Debian' }

와 같이 사용가능합니다.

조건부 Import

노트

이 방법은 자주사용하는 것은 아닙니다.

때로는 어떤 조건에 따라 플레이북에서 다르게 동작할 필요가 있습니다. 멀티 플랫폼이나 OS에서 동작하는 하나의 플레이북을 예를 들 수 있습니다.

CentOS와 Debian 에서 다른 이름으로 되어 있는 아파치 패키지를 예를 들 수 있는데 다음과 같이 플레이북을 이용할 수 있습니다.

---
- hosts: all
  remote_user: root
  vars_files:
    - "vars/common.yml"
    - [ "vars/{{ ansible_os_family }}.yml", "vars/os_defaults.yml" ]
  tasks:
  - name: make sure apache is running
    service: name={{ apache }} state=running

노트

ansible_os_family 변수는 vars_files 에 정의된 파일이름의 목록으로 해석됩니다.

CentOS를 위한 YAML은 다음 처럼,

---
# for vars/CentOS.yml
apache: httpd
somethingelse: 42

구성할 수 있습니다. 유사하게 Debian 은

---
# for vars/Debian.yml
apache: apache2
somethingelse: 42

구성될 수 있겠네요. (역자주)

이런 조건부 import 기능을 사용하려면 factor 또는 ohai 모듈이 플레이북 실행에 앞서 설치되어 있어야 하는데 다음과 같은 명령을 미리 실행해 놓을 수 있습니다.

# for facter
ansible -m yum -a "pkg=facter state=present"
ansible -m yum -a "pkg=ruby-json state=present"

# for ohai
ansible -m yum -a "pkg=ohai state=present"

위와 같이 구성하는 장점은 추적 포인트를 최소화 한다는 것입니다. 변수를 태스크에서 분리시켜 플레이북이 여러 중첩된 if 코드 등으로 뒤죽 박죽하는 것을 막을 수 있습니다.

변수 기반의 파일과 템플릿 선택

노트

이 기능은 자주 사용하지 않습니다.

때로는 복사하려는 설정 파일이나 사용하려는 템플릿이 변수에 따라 다를 수 있습니다.

다음의 예는 CentOS와 Debian과 같이 서로 다른 시스템에 따라 템플릿에서 설정파일을 달리 사용할 수 있는 방법을 보여줍니다.

- name: template a file
  template: src={{ item }} dest=/etc/myapp/foo.conf
  with_first_found:
    - files:
       - {{ ansible_distribution }}.conf
       - default.conf
      paths:
       - search_location_one/somedir/
       - /opt/other_location/somedir/

Register 변수

종종 플레이북에서 실행한 명령의 결과를 변수로 저장했다가 나중에 재사용 할 필요가 있습니다. 이와 같은 방법은 특정 사이트의 fact나 인스턴스를 작성하지 않고도 쉽게 특정 프로그램이 존재하는가 확인할 수 있습니다.

register 키워드는 어느 변수에 실행 결과를 저장할 것인가를 나타냅니다. 결과 변수는 템플릿, action 라인 또는 when 구문 등에서 사용가능 합니다.

- name: test play
  hosts: all

  tasks:

      - shell: cat /etc/motd
        register: motd_contents

      - shell: echo "motd contains the word hi"
        when: motd_contents.stdout.find('hi') != -1

위의 결과에서 보듯이 등록된 변수 motd_contentsstdout 을 통하여 결과를 얻을 수 있습니다.

또는,

- name: registered variable usage as a with_items list
  hosts: all

  tasks:

      - name: retrieve the list of home directories
        command: ls /home
        register: home_dirs

      - name: add home dirs to the backup spooler
        file: path=/mnt/bkspool/{{ item }} src=/home/{{ item }} state=link
        with_items: "{{ home_dirs.stdout_lines }}"
        # same as with_items: "{{ home_dirs.stdout.split() }}"

with_items를 이용하여 stdout 결과의 각 라인별로 작업을 할 수 있습니다.

위와 같이 등록된 변수의 출력 결과는 stdout을 통하여 읽을 수 있고 또한 결과가 없는지 (emptiness) 확인할 수 있습니다.

- name: check registered variable for emptiness
  hosts: all

  tasks:

      - name: list contents of directory
        command: ls mydir
        register: contents

      - name: check contents for emptiness
        debug: msg="Directory is empty"
        when: contents.stdout == ""

반복문

표준 반복문

간단한 반복문은 다음과 같이 작성할 수 있습니다.

- name: add several users
  user: name={{ item }} state=present groups=wheel
  with_items:
     - testuser1
     - testuser2

만약 변수 파일에 YAML 목록으로 정의하였거나 vars 섹션에 목록 변수를 정의 하였다면,

with_items: "{{ somelist }}"

와 같이 사용가능합니다.

위의 반복문을 풀어 본다면 다음과 동일합니다.

- name: add user testuser1
  user: name=testuser1 state=present groups=wheel
- name: add user testuser2
  user: name=testuser2 state=present groups=wheel

yum과 apt 모듈 역시 with_items을 잘 이용할 수 있습니다. 만약 해쉬 목록을 가지고 있다면 다음과 같이 사용하면 됩니다.

- name: add several users
  user: name={{ item.name }} state=present groups={{ item.groups }}
  with_items:
    - { name: 'testuser1', groups: 'wheel' }
    - { name: 'testuser2', groups: 'root' }

만약 when 문장이 with_items과 같이 사용된다면 (또는 다른 반복문에서도 동일하게) 각 항목에 대하여 when 구문 체크를 합니다.

중첩 반복문

- name: give users access to multiple databases
  mysql_user: name={{ item[0] }} priv={{ item[1] }}.*:ALL append_privs=yes password=foo
  with_nested:
    - [ 'alice', 'bob' ]
    - [ 'clientdb', 'employeedb', 'providerdb' ]

with_nested 항목에 이전에 사용한 변수를 사용할 수 있습니다.

- name: here, 'users' contains the above list of employees
  mysql_user: name={{ item[0] }} priv={{ item[1] }}.*:ALL append_privs=yes password=foo
  with_nested:
    - "{{ users }}"
    - [ 'clientdb', 'employeedb', 'providerdb' ]

해쉬에 대한 반복문

버전 1.5 이후.

다음과 같은 변수가 있다고 가정하면,

---
users:
  alice:
    name: Alice Appleworth
    telephone: 123-456-7890
  bob:
    name: Bob Bananarama
    telephone: 987-654-3210

이 변수를 이용한 반복문 활용 예 입니다.

tasks:
  - name: Print phone records
    debug: msg="User {{ item.key }} is {{ item.value.name }} ({{ item.value.telephone }})"
    with_dict: "{{ users }}"

파일을 대상으로 하는 반복문

with_file 해당 파일의 내용을 목록으로 반복하는데 item은 반복되는 항목을 가리킵니다.

---
- hosts: all

  tasks:

    # emit a debug message containing the content of each file.
    - debug:
        msg: "{{ item }}"
      with_file:
        - first_example_file
        - second_example_file

위에서 first_example_filehello를 담고 있고 second_example_fileworld 라는 문자열을 가진다면 이 결과는,

TASK [debug msg={{ item }}] ******************************************************
ok: [localhost] => (item=hello) => {
    "item": "hello",
    "msg": "hello"
}
ok: [localhost] => (item=world) => {
    "item": "world",
    "msg": "world"
}

파일목록에 대한 반복문

with_fileglob은 어느 디렉터리에서 매칭되는 모든 파일 목록을 구해옵니다. (재귀적으로 탐색하지 않습니다.) 이것은 파이썬의 glob 라이브러리를 호출합니다.

---
- hosts: all

  tasks:

    # first ensure our target directory exists
    - file: dest=/etc/fooapp state=directory

    # copy each file over that matches the given pattern
    - copy: src={{ item }} dest=/etc/fooapp/ owner=root mode=600
      with_fileglob:
        - /playbooks/files/fooapp/*

노트

with_fileglob 이 role에서 상대 경로로 사용된다면 Ansible은 ***roles//files 디렉터리를 기준으로 상대경로로 인식할 겁니다.

데이터의 패러렐 셋의 반복

노트

이 기능은 일상적으로 사용하는 것은 아니지만 기술해 봅니다.

다음과 같은 변수가 있고,

---
alpha: [ 'a', 'b', 'c', 'd' ]
numbers:  [ 1, 2, 3, 4 ]

(a, 1) and (b, 2) ... 등과 같이 조합하여 반복하고 싶다면, with_together을 사용합니다.

tasks:
    - debug: msg="{{ item.0 }} and {{ item.1 }}"
      with_together:
        - "{{ alpha }}"
        - "{{ numbers }}"

하위항목에 대한 반복문

때로는 사용자 목록을 만들어 해당 사용자에 대한 반복문을 돌면서 일련의 SSH 키로 로그인하는 등의 일을 할 필요가 있습니다.

어떻게 이런 일을 수행할까요? group_vars/all 파일에 있거나 vars_files에 의해 다음과 같이 정의되고 로드된다면,

---
users:
  - name: alice
    authorized:
      - /tmp/alice/onekey.pub
      - /tmp/alice/twokey.pub
    mysql:
        password: mysql-password
        hosts:
          - "%"
          - "127.0.0.1"
          - "::1"
          - "localhost"
        privs:
          - "*.*:SELECT"
          - "DB1.*:ALL"
  - name: bob
    authorized:
      - /tmp/bob/id_rsa.pub
    mysql:
        password: other-mysql-password
        hosts:
          - "db1"
        privs:
          - "*.*:SELECT"
          - "DB2.*:ALL"

아래와 같이 반복문을 사용할 수 있습니다.

- user: name={{ item.name }} state=present generate_ssh_key=yes
  with_items: "{{ users }}"

- authorized_key: "user={{ item.0.name }} key='{{ lookup('file', item.1) }}'"
  with_subelements:
     - "{{ users }}"
     - authorized

비슷하게 mysql 에 대해서는,

- name: Setup MySQL users
  mysql_user: name={{ item.0.name }} password={{ item.0.mysql.password }} host={{ item.1 }} priv={{ item.0.mysql.privs | join('/') }}
  with_subelements:
    - "{{ users }}"
    - mysql.hosts

해쉬 (파이썬의 dictionary)의 목록에 대하여 하위 항목을 찾아가는 것은 해당 레코드의 키로 접근하는 방법입니다.

종종 해시 목록에서 하위 항목의 세번째 항목을 추가하거나 할 필요가 있는데 skip_missing 플래그를 추가하면 됩니다. 만약 이 플래그가 True 이면 주어진 하위키를 포함하지 않은 항목은 건너 뜁니다. 만약 해당 플래그가 False이거나 설정되지 않았다면 존재하지 않는 키를 접근하려면 오류가 발생합니다.

정수열에 대한 반복문

with_sequence 는 높임차순의 항목 목록을 생성합니다. start, end와 선택적 step을 지정할 수 있습니다.

인자는 key=value 와 같이 지정합니다.

정수값은 십진수, 16진수(예 0x3f8) 또는 8진수(0600) 등이 올 수 있습니다. 음수는 지원되지 않습니다. 다음과 같이 이용하면 됩니다.

---
- hosts: all

  tasks:

    # create groups
    - group: name=evens state=present
    - group: name=odds state=present

    # create some test users
    - user: name={{ item }} state=present groups=evens
      with_sequence: start=0 end=32 format=testuser%02x

    # create a series of directories with even numbers for some reason
    - file: dest=/var/stuff/{{ item }} state=directory
      with_sequence: start=4 end=16 stride=2

    # a simpler way to use the sequence plugin
    # create 4 groups
    - group: name=group{{ item }} state=present
      with_sequence: count=4

임의 선택

random_choice 기능은 임의로 어떤 항목을 선택할 수 있습니다.

- debug: msg={{ item }}
  with_random_choice:
     - "go through the door"
     - "drink from the goblet"
     - "press the red button"
     - "do nothing"

Do-Until 반복문

반복문 중에 다음과 같이 할 수 있습니다.

- action: shell /usr/bin/foo
  register: result
  until: result.stdout.find("all systems go") != -1
  retries: 5
  delay: 10

위의 예는 쉘 모듈을 재귀적으로 수행하는데 결과가 all systems go라는 문자열이 결과에 나올 때까지 실행하는데 10초의 delay를 두고 5회까지 반복합니다. 디폴트는 3회까지 반복에 5초의 delay입니다.

첫번째 매칭되는 파일 구하기

이것은 반복이라기 보다는 선택입니다. 참조할 파일이름은 변수로 나타내며, 참조할 파일 목록 중에서 주어진 조건식에 맞는 첫번째 파일을 찾으려면 어떻게 할까요? 다음과 같이 하면 됩니다.

- name: INTERFACES | Create Ansible header for /etc/network/interfaces
  template: src={{ item }} dest=/etc/foo.conf
  with_first_found:
    - "{{ ansible_virtualization_type }}_foo.conf"
    - "default_foo.conf"

좀 더 긴 버전으로는,

- name: some configuration template
  template: src={{ item }} dest=/etc/file.cfg mode=0444 owner=root group=root
  with_first_found:
    - files:
       - "{{ inventory_hostname }}/etc/file.cfg"
      paths:
       - ../../../templates.overwrites
       - ../../../templates
    - files:
        - etc/file.cfg
      paths:
        - templates

프로그램 실행 결과에 대한 반복

노트

자주 사용하는 기능이 아닙니다.

때로는 프로그램을 실행하고 그 결과를 줄 단위로 반복하여 작업할 필요가 있습니다. Ansible에서는 이런 작업을 하는데 있어 원격 관리 머신이 아니고 제어 머신에서 도는 결과입니다.

- name: Example of looping over a command result
  shell: /usr/bin/frobnicate {{ item }}
  with_lines: /usr/bin/frobnications_per_host --param {{ inventory_hostname }}

만약 명령을 원격에서 수행해야 한다면 다음과 같이 진행합니다.

- name: Example of looping over a REMOTE command result
  shell: /usr/bin/something
  register: command_result

- name: Do something with each result
  shell: /usr/bin/something_else --param {{ item }}
  with_items: "{{ command_result.stdout_lines }}"

색인으로 반복문 돌기

노트

자주 사용하는 기능이 아닙니다.

- name: indexed loop demo
  debug: msg="at array position {{ item.0 }} there is a value {{ item.1 }}"
  with_indexed_items: "{{ some_list }}"

ini 파일에서 반복하기

다음과 같은 ini 파일이 있고,

[section1]
value1=section1/value1
value2=section1/value2

[section2]
value1=section2/value1
value2=section2/value2

with_ini 을 사용하여,

- debug: msg="{{ item }}"
  with_ini: value[1-2] section=section1 file=lookup.ini re=true

와 같이 사용하면 그 결과는,

{
      "changed": false,
      "msg": "All items completed",
      "results": [
          {
              "invocation": {
                  "module_args": "msg=\"section1/value1\"",
                  "module_name": "debug"
              },
              "item": "section1/value1",
              "msg": "section1/value1",
              "verbose_always": true
          },
          {
              "invocation": {
                  "module_args": "msg=\"section1/value2\"",
                  "module_name": "debug"
              },
              "item": "section1/value2",
              "msg": "section1/value2",
              "verbose_always": true
          }
      ]
  }

목록 Flattening

노트

자주 사용하는 기능이 아닙니다.

다음과 같이 목록의 목록 등과 같이 목록이 중첩되어 있다면,

----
# file: roles/foo/vars/main.yml
packages_base:
  - [ 'foo-package', 'bar-package' ]
packages_apps:
  - [ ['one-package', 'two-package' ]]
  - [ ['red-package'], ['blue-package']]

모든 중첩된 목록도 모두 1차원의 목록으로만 하려면 with_flattened을 이용합니다.

- name: flattened loop demo
  yum: name={{ item }} state=installed
  with_flattened:
     - "{{ packages_base }}"
     - "{{ packages_apps }}"

register를 이용한 반복문

register를 이용한 결과는 results 속성에 들어있습니다.

- shell: echo "{{ item }}"
  with_items:
    - one
    - two
  register: echo

이것은 다음과 같이 반복문 없이 register�를 이용한 결과를 이용한 데이터 구조체와는 다릅니다.

{
    "changed": true,
    "msg": "All items completed",
    "results": [
        {
            "changed": true,
            "cmd": "echo \"one\" ",
            "delta": "0:00:00.003110",
            "end": "2013-12-19 12:00:05.187153",
            "invocation": {
                "module_args": "echo \"one\"",
                "module_name": "shell"
            },
            "item": "one",
            "rc": 0,
            "start": "2013-12-19 12:00:05.184043",
            "stderr": "",
            "stdout": "one"
        },
        {
            "changed": true,
            "cmd": "echo \"two\" ",
            "delta": "0:00:00.002920",
            "end": "2013-12-19 12:00:05.245502",
            "invocation": {
                "module_args": "echo \"two\"",
                "module_name": "shell"
            },
            "item": "two",
            "rc": 0,
            "start": "2013-12-19 12:00:05.242582",
            "stderr": "",
            "stdout": "two"
        }
    ]
}

이런 결과를 활용하기 위해서

- name: Fail if return code is not 0
  fail:
    msg: "The command ({{ item.cmd }}) did not have a 0 return code"
  when: item.rc != 0
  with_items: "{{ echo.results }}"

관리대상목록(Inventory) 반복

Inventory의 반복은 여러 방법으로 가능합니다. play_hosts 또는 groups 변수로 with_items를 이용할 수 있습니다.

# show all the hosts in the inventory
- debug: msg={{ item }}
  with_items: "{{ groups['all'] }}"

# show all the hosts in the current play
- debug: msg={{ item }}
  with_items: play_hosts

다른 방법으로 inventory_hostnames 플러그인을 이용한 것이 있습니다.

# show all the hosts in the inventory
- debug: msg={{ item }}
  with_inventory_hostnames: all

# show all the hosts matching the pattern, ie all but the group www
- debug: msg={{ item }}
  with_inventory_hostnames: all:!www

반복문 제어

버전 2.0에서 (플레이북 include 가 아닌) with_loops와 태스크 include를 사용할 수 있습니다. Ansible 2.1 이후 부터는 loop_control 이 반복문에 사용되는 변수이름을 지정하는데 사용할 수 있습니다.

# main.yml
- include: inner.yml
  with_items:
    - 1
    - 2
    - 3
  loop_control:
    loop_var: outer_item

# inner.yml
- debug: msg="outer item={{ outer_item }} inner item={{ item }}"
  with_items:
    - a
    - b
    - c

노트

만약 Ansible이 이미 정의된 변수를 현재 반복문에 사용된 것을 발견했다면 태스크에 오류가 발생할 것입니다.

복잡한 데이터 구조가 반복문에 사용되면 경우 c(label) 지시자를 이용할 수 있습니다.

- name: create servers
  digital_ocean: name={{item.name}} state=present ....
 with_items:
    - name: server1
      disks: 3gb
      ram: 15Gb
      netowrk:
        nic01: 100Gb
        nic02: 10Gb
        ...
  loop_control:
    label: "{{item.name}}"

이것은 디폴트 {{item}} �대신 label 필드를 보여줍니다.

반복문 제어를 위한 또다른 옵션은 c(pause)를 이용하여 반복문 사이에 얼마큼 (초 단위) 멈출지를 나타냅니다.

# main.yml
- name: create servers, pause 3s before creating next
  digital_ocean: name={{item}} state=present ....
 with_items:
    - server1
    - server2
 loop_control:
    pause: 3

2.0 에 추가된 반복문

Ansible 2.0 에는 loop_control이 존재하지 않기 떄문에 set_fact 를 이용합니다.

# main.yml
- include: inner.yml
  with_items:
    - 1
    - 2
    - 3

# inner.yml
- set_fact:
    outer_item: "{{ item }}"

- debug:
    msg: "outer item={{ outer_item }} inner item={{ item }}"
  with_items:
    - a
    - b
    - c

자신의 반복문 사용

임의의 데이터 구조에 대해서 자신만의 반복문을 이용할 경우가 있는데 플러그인 개발을 참고하시기 바랍니다.

Blocks

2.0 에서 태스크의 논리적 그룹 및 플레이의 오류 처리를 위하여 block 이라는 것을 추가했습니다. 단일 태스크에 데이터나 지시자를 쉽게 지정할 수 있는 block 단계가 적용될 수 있습니다.

블럭(block) 예제.

   tasks:
     - block:
         - yum: name={{ item }} state=installed
           with_items:
             - httpd
             - memcached
         - template: src=templates/src.j2 dest=/etc/foo.conf
         - service: name=bar state=started enabled=True

       when: ansible_distribution == 'CentOS'
       become: true
       become_user: root

위의 예제에서 각각 3개의 태스크 (yum, template, service)가 when 조건문 이후에 수행될 것입니다.

오류 처리

대부분의 프로그래밍 언어에서 지원하는 오류 예외처리와 block이 관계되어 있습니다.

block 에러 처리 예제:

 tasks:
  - block:
      - debug: msg='i execute normally'
      - command: /bin/false
      - debug: msg='i never execute, cause ERROR!'
    rescue:
      - debug: msg='I caught an error'
      - command: /bin/false
      - debug: msg='I also never execute :-('
    always:
      - debug: msg="this always executes"

block 문에 있는 태스크는 정상적으로 수행되는 것인데 만약 중간에 에러가 발생하면 rescue 섹션이 수행됩니다. always 섹션은 에러가 발생하던지 안하던지 상관없이 blockrescue 섹션 다음에 수행됩니다.

다른 예로 오류가 발생하였을 때 어떻게 핸들러를 수행하는가 입니다.

 tasks:
  - block:
      - debug: msg='i execute normally'
        notify: run me even after an error
      - command: /bin/false
    rescue:
      - name: make sure all handlers run
        meta: flush_handlers
 handlers:
   - name: run me even after an error
     debug: msg='this handler runs even on error'

Strategies

2.0에서 strategy라는 플레이 제어 방식이 추가되었는데, 디폴트로 플레이는 linear 전략(strategy) 으로 수행됩니다. 다음 태스크로 넘어가기 전에 모든 호스트에 해당 태스크 작업을 마치는데 디폴트로 5개의 병렬화로 작업을 합니다.

serial 지시자는 호스트의 일정 부분에 대한 batch 작업을 하는데 다음 batch 작업이 시작하기 전에 작업을 마무리합니다.

다음은 free 지시자가 있는데 각각의 호스트가 플레이별로 빨리 끝낼 수 있는데로 긑내는 방식입니다.

- hosts: all
  strategy: free
  tasks:
  ...

Strategy 플러그인

strategy는 사용자가 제공하거나 Ansible 코드로 새롭게 실행가능한 새로운 형태의 플러그인으로 구현되었습니다.

debug strategy 가 한 예입니다. 플레이북 디버거를 참고하십시오.

Best Practices

Content Organization

꼭 이렇게 하라는 것은 아니지만 role을 이용하는 것이 좋은 방법입니다.

디렉터리 구조

production                # inventory file for production servers
staging                   # inventory file for staging environment

group_vars/
   group1                 # here we assign variables to particular groups
   group2                 # ""
host_vars/
   hostname1              # if systems need specific variables, put them here
   hostname2              # ""

library/                  # if any custom modules, put them here (optional)
filter_plugins/           # if any custom filter plugins, put them here (optional)

site.yml                  # master playbook
webservers.yml            # playbook for webserver tier
dbservers.yml             # playbook for dbserver tier

roles/
    common/               # this hierarchy represents a "role"
        tasks/            #
            main.yml      #  <-- tasks file can include smaller files if warranted
        handlers/         #
            main.yml      #  <-- handlers file
        templates/        #  <-- files for use with the template resource
            ntp.conf.j2   #  <------- templates end in .j2
        files/            #
            bar.txt       #  <-- files for use with the copy resource
            foo.sh        #  <-- script files for use with the script resource
        vars/             #
            main.yml      #  <-- variables associated with this role
        defaults/         #
            main.yml      #  <-- default lower priority variables for this role
        meta/             #
            main.yml      #  <-- role dependencies

    webtier/              # same kind of structure as "common" was above, done for the webtier role
    monitoring/           # ""
    fooapp/               # ""

Cloud를 이용한 Dynamic Inventory 사용

동적 인벤토리를 이용한 관리대상 목록 관리를 클라우드 provider로 이용하는 것입니다.

Staging 과 Production의 차이점 이용

만약 정적 인벤토리를 이용한다면 환경 변수를 이용하여 관리하는 것이 보통이지만 동적 인벤토리에서는 그룹에 따라 작업하는 것이 가능합니다.

이런 스테이징이나 프로덕션 등의 목적에 따라 그룹을 나누거나 지역 (데이터 센터 등)에 따라 그룹을 나눕니다.

# file: production

[atlanta-webservers]
www-atl-1.example.com
www-atl-2.example.com

[boston-webservers]
www-bos-1.example.com
www-bos-2.example.com

[atlanta-dbservers]
db-atl-1.example.com
db-atl-2.example.com

[boston-dbservers]
db-bos-1.example.com

# webservers in all geos
[webservers:children]
atlanta-webservers
boston-webservers

# dbservers in all geos
[dbservers:children]
atlanta-dbservers
boston-dbservers

# everything in the atlanta geo
[atlanta:children]
atlanta-webservers
atlanta-dbservers

# everything in the boston geo
[boston:children]
boston-webservers
boston-dbservers

Group, Host 변수

그룹은 체계적으로 분류하는데 좋은 방법이지만 모든 그룹이 그렇지는 않습니다. 때로는 변수를 적용할 수도 있습니다.

---
# file: group_vars/atlanta
ntp: ntp-atlanta.example.com
backup: backup-atlanta.example.com

또는

---
# file: group_vars/webservers
apacheMaxRequestsPerChild: 3000
apacheMaxClients: 900

디폴트로는

---
# file: group_vars/all
ntp: ntp-boston.example.com
backup: backup-boston.example.com

각각의 호스트 변수는

---
# file: host_vars/db-bos-1.example.com
foo_agent_port: 86
bar_agent_port: 99

최상위 플레이북은 Rule에 의해 분리됨

전체 구조를 담고 있는 site.yml 은 다른 플레이북 등을 담고 있으므로 아주 간단한 것이 보통입니다.

---
# file: site.yml
- include: webservers.yml
- include: dbservers.yml

또한 같은 최상위 레벨에 있는 webservers.yml 같은 것은 해당 webservers 그룹이나 해당 그룹의 role 에 의해 작업이 이루어지는게 일반적입니다.

---
# file: webservers.yml
- hosts: webservers
  roles:
    - common
    - webtier

최상위에 site.yml 부터 시작하여 webservers.yml 등과 같이 각각의 역할 별 플레이북이 실행되도록 합니다.

때로는 명령줄에서 특정 플레이북만 선택적으로 실행가능합니다.

$ ansible-playbook site.yml --limit webservers
$ ansible-playbook webservers.yml

Role을 구성하는 태스크와 핸들러

다음은 role이 어떻게 동작하는지 설명하는 예제로써 NTP 를 필요에 따라 설정하는 것입니다.

---
# file: roles/common/tasks/main.yml

- name: be sure ntp is installed
  yum: name=ntp state=installed
  tags: ntp

- name: be sure ntp is configured
  template: src=ntp.conf.j2 dest=/etc/ntp.conf
  notify:
    - restart ntpd
  tags: ntp

- name: be sure ntpd is running and enabled
  service: name=ntpd state=started enabled=yes
  tags: ntp

다음은 핸들러 파일 예제입니다.

---
# file: roles/common/handlers/main.yml
- name: restart ntpd
  service: name=ntpd state=restarted

위의 구조에 따른 ansible-playbook 예제

전체 플레이 작업

$ ansible-playbook -i production site.yml

NTP 작업만 하려면

$ ansible-playbook -i production site.yml --tags ntp

webserver 만 작업하려면

$ ansible-playbook -i production webservers.yml

Boston에 있는 webserver에 대해 작업하면

$ ansible-playbook -i production webservers.yml --limit boston

호스트 중에 앞, 뒤 10개씩의 호스트만 적용하려면

$ ansible-playbook -i production webservers.yml --limit boston[1-10]
$ ansible-playbook -i production webservers.yml --limit boston[11-20]

애드-혹 작업을 각각 하려면

$ ansible boston -i production -m ping
$ ansible boston -i production -m command -a '/sbin/reboot'

버전 1.1 이후에 사용되는 유용한 명령행 옵션 등으로

# confirm what task names would be run if I ran this command and said "just ntp tasks"
$ ansible-playbook -i production webservers.yml --tags ntp --list-tasks

# confirm what hostnames might be communicated with if I said "limit to boston"
$ ansible-playbook -i production webservers.yml --limit boston --list-hosts

배포 대 설정관리 구성

위와 같은 구성은 일반적인 설정관리 구성방법입니다. 하지만 다단계 배포를 할 때에는 단계를 넘어가거나 응용프로그램을 마무리하는 추가 플레이북이 필요합니다. 이런 경우 site.ymldeploy_exampledotcom.yml 와 같은 추가 플레이북을 추가합니다.

Staging 대 Production

Staging (또는 테스팅)과 Production 환경을 분리하는 좋은 방법은 Inventory 에서 분리하는 방법입니다. 이것을 ansible-playbook을 실행할 때 -i 옵션으로 서로 다른 인벤토리를 이용합니다.

Production에서 테스트하기 전에 각각의 Staging 환경에서 테스팅을 해 보는 것이 중요합니다. 작은 범위의 스테이징에서 테스트를 충분히 하는 것이 좋습니다.

Rolling 업데이트

serial 키워드를 이해한다. 만약 sebserver 팜을 업데이트하고 제어하려면 얼마나 많은 기계를 배치작업에서 한번에 할지 결정할 필요가 있습니다.

항상 State 고려

state 패러미터는 많은 모듈에서 선택사항입니다. state=present 또는 state=absent 이건간에 각각의 플레이북에서 패러미터로 남겨두는 것이 좋습니다.

Role 별 그룹

그룹은 아무리 강조해도 지나치지 않습니다. 각 그룹에 webservers 또는 dbservers 와 같이 의미를 부여해 줍니다.

이렇게 하여 각 플레이북에서 역할별 작업을 할 수 있습니다.

운영체제 등의 차이점 분류

서로 다른 두 운영 체제에 차이를 패러미터로 처리하려고 할 때, group_by 모듈을 잘 사용하면 좋습니다.

이것은 인벤토리 파일에 미리 정의되어 있지 않더라도 일정 조건에 따른 호스트의 그룹을 동적으로 지정할 수 있습니다.

---

# talk to all hosts just so we can learn about them
- hosts: all
  tasks:
     - group_by: key=os_{{ ansible_distribution }}

# now just on the CentOS hosts...

- hosts: os_CentOS
  gather_facts: False
  tasks:
     - # tasks that only happen on CentOS go here

위와 같이 함으로써 운영체제에 따라 동적으로 그룹을 나눌 수 있습니다.

만약 그룹에 특화된 설정이 필요하다면 다음과 같이 지정합니다.

---
# file: group_vars/all
asdf: 10

---
# file: group_vars/os_CentOS
asdf: 42

위의 예에서는 CentOS 운영체제인 경우에는 asdf 라는 변수가 42 값을 갖고 그 밖에는 10 값을 갖는 예 입니다.

다른 대안으로 만약 변수만 필요하다면,

- hosts: all
  tasks:
    - include_vars: "os_{{ ansible_distribution }}.yml"
    - debug: var=asdf

플레이북으로 모듈 번들링

만약 플레이북이 YAML파일의 상대 위치에 ./library 디렉터리를 가지고 있다면 이 디렉터리는 ansible 모듈 경로에 자동으로 추가됩니다. 이런 방식으로 플레이북과 모듈을 함께 배포할 수 있는 좋은 방법이 될 수 있습니다.

공백 및 코멘트

가독성을 위해서 적절한 공백 및 코멘트 (#로 시작하는 라인)를 잘 넣기를 권장합니다.

항상 태스크에 이름 지정

비록 태스크에 name 항목을 비워놓을 수 있지만 항상 적절한 이름을 지정하기 바랍니다. 이것은 플레이북이 실행될 때 해당 이름을 출력하기 떄문에 여러모로 유용합니다.

간단하게 유지

가능하다면 복잡하지 않고 간단하게 합니다. 모든 사용가능한 Ansible 기능을 한데 사용하려고 하지 마십시오.예를 들어 vars, vars_files, vars_prompt--extra-vars` 또는 외부 인벤토리 파일 등을 모두 사용할 수는 있지만 결국 우선순위에 의해 마지막 변수만 사용될 것이므로 필요한 것을 이용하는 것이 좋습니다. 만약 중복해서 사용했다면 간단하게 할 수 있는 방법이 존재할 것입니다.

버전 관리

가능한한 버전관리를 이용하기 바랍니다. 이렇게 함으로써 수정된 내용 등을 추적할 수 있습니다.

변수 및 Vaults

Ansible 설정등에 대한 간단한 관리에는 grep과 같은 도구를 종종 사용하기도 합니다. 또한 vault라는 것을 이용하면 이런 변수의 내용을 직접 확인할 수 없게 함으로써 좀 더 보안에 안정적일 수 있습니다. 플레이북이 실행될 때 암호화 되지 않은 변수 뿐만 아니라 민감한 변수는 암호화된 파일에서 읽어 복호화하여 해당 내용을 확인합니다.

group_vars 하위 폴더에 그룹 이름을 딴 설정 파일을 이용하십시오. 아니면 대신 varsvault라는 이름의 두 파일을 만듭니다. vars 파일에는 민감한 변수를 포함한 모든 변수를 정의하고, vault_라고 시작하는 이름의 파일에는 암호화가 필요한 민감한 변수를 복사합니다. 이런 vault 파일은 암호화 됩니다.