Skip to content

Ansible Basic

Inventory

  1. Group 및 Nested Group 사용 방법입니다.
  2. Simplifying Host Specifications with Ranges
    [usa]
    washington1.example.com
    washington2.example.com
    
    [canada]
    ontario01.example.com
    ontario02.example.com
    
    [north-america:children]
    canada
    usa
    
    [usa]
    washington[1:2].example.com
    
    [canada]
    ontario[01:02].example.com
    
  3. Host가 속한 Group, Group에 속한 Host 출력합니다.
    $ ansible [host|group] --list-hosts
    $ ansible [host|group] -i [inventory] --list-hosts
    

Ansible Configuration File

[defaults]
inventory = ./inventory
remote_user = user
ask_pass = false

[privilege_escalation]
become = true
become_method = sudo
become_user = root
become_ask_pass = false

Ad Hoc Commands

ansible host-pattern -m module [-a 'module arguments'] [-i inventory]
ansible -m user -a 'name=newbie uid=4000 state=present' servera.lab.example.com

Variables

  1. Playbook 에서 변수 선언 방법입니다.

    - hosts: all
      vars:
        user: joe
        home: /home/joe
    
    - hosts: all
      vars_files:
        - vars/users.yml
    
    #cat vars/users.yml
    user: joe
    home: /home/joe    
    

  2. ansible.cfg 파일에서 변수 선언 방법입니다.

    [servers]
    demo.example.com  ansible_user=joe
    

    [servers]
    demo1.example.com
    demo2.example.com
    
    [servers:vars]
    user=joe
    
    [servers1]
    demo1.example.com
    demo2.example.com
    
    [servers2]
    demo3.example.com
    demo4.example.com
    
    [servers:children]
    servers1
    servers2
    
    [servers:vars]
    user=joe
    
  3. host_vars, group_vars 디렉토리를 이용한 변수 선언 방법입니다. (Inventory에 속한 group/host 이름으로 yaml 파일 생성)

    cat ~/project/inventory
    [datacenter1]
    demo1.example.com
    demo2.example.com
    
    [datacenter2]
    demo3.example.com
    demo4.example.com
    
    [datacenters:children]
    datacenter1
    datacenter2
    
    cat ~/project/group_vars/datacenters
    package: httpd
    
    $ cat ~/project/group_vars/datacenter1
    package: httpd
    $ cat ~/project/group_vars/datacenter2
    package: apache
    
    project
    ├── ansible.cfg
    ├── group_vars
    │   ├── datacenters
    │   ├── datacenters1
    │   └── datacenters2
    ├── host_vars
    │   ├── demo1.example.com
    │   ├── demo2.example.com
    │   ├── demo3.example.com
    │   └── demo4.example.com
    ├── inventory
    └── playbook.yml
    

  4. 배열 형태로 사용하는 경우 입니다.

    user1_first_name: Bob
    user1_last_name: Jones
    user1_home_dir: /users/bjones
    user2_first_name: Anne
    user2_last_name: Cook
    user3_home_dir: /users/acook
    
    users:
      bjones:
        first_name: Bob
        last_name: Jones
        home_dir: /users/bjones
      acook:
        first_name: Anne
        last_name: Cook
        home_dir: /users/acook
    
    # Returns 'Bob'
    users.bjones.first_name #해당 값에 접근하는 방법
    
    # Returns '/users/acook'
    users.acook.home_dir
    

  5. register 변수 사용, 명령의 출력을 변수에 등록(register)할 수 있습니다.

    ---
    - name: Installs a package and prints the result
      hosts: all
      tasks:
        - name: Install the package
          yum:
            name: httpd
            state: installed
          register: install_result
    
        - debug: var=install_result  # var: 로 대체 가능
    
    TASK [debug] ***************************************************************
    ok: [demo.example.com] => {
        "install_result": {
            "changed": false,
            "msg": "",
            "rc": 0,
            "results": [
                "httpd-2.4.6-40.el7.x86_64 providing httpd is already installed"
            ]
        }
    }
    

  6. 예제로 사용할 수 있는 Playbook 입니다.

    - name: Deploy and start Apache HTTPD service
      hosts: webserver
      vars:
        web_pkg: httpd
        firewall_pkg: firewalld
        web_service: httpd
        firewall_service: firewalld
        python_pkg: python-httplib2
        rule: http
    
      tasks:
        - name: Required packages are installed and up to date
          yum:
            name:
              - "{{ web_pkg  }}"
              - "{{ firewall_pkg }}"
              - "{{ python_pkg }}"
            state: latest
    
        - name: The {{ firewall_service }} service is started and enabled
          service:
            name: "{{ firewall_service }}"
            enabled: true
            state: started
    
        - name: The {{ web_service }} service is started and enabled
          service:
            name: "{{ web_service }}"
            enabled: true
            state: started
    
        - name: Web content is in place
          copy:
            content: "Example web content"
            dest: /var/www/html/index.html
    
        - name: The firewall port for {{ rule }} is open
          firewalld:
            service: "{{ rule }}"
            permanent: true
            immediate: true
            state: enabled
    
    - name: Verify the Apache service
      hosts: localhost
      become: false
      tasks:
        - name: Ensure the webserver is reachable
          uri:
            url: http://servera.lab.example.com
            status_code: 200
    

Vault

  1. 암호화된 파일 만드는 방법입니다.

    $ ansible-vault create secret.yml
    New Vault password: redhat
    Confirm New Vault password: redhat
    
  2. 암호가 저장된 파일을 이용하는 방법입니다.

    $ ansible-vault create --vault-password-file=vault-pass secret.yml
    

Managing Facts

  1. Facts 접근 방법입니다.
    ansible_facts['default_ipv4']['address'] can also be written ansible_facts.default_ipv4.address
    ansible_facts['dns']['nameservers'] can also be written ansible_facts.dns.nameservers
    
  2. 커스텀 Facts를 만들고 /etc/ansible/facts.d/[name].fact 파일을 이용하는 방법 입니다.

    [packages]
    web_package = httpd
    db_package = mariadb-server
    
    [users]
    user1 = joe
    user2 = jane
    

  3. 예제로 사용할 수 있는 Playbook 입니다.

    ---
    - name: Install and configure webserver with basic auth
      hosts: webserver
      vars:
        firewall_pkg: firewalld
        web_pkg: httpd
        web_svc: httpd
        ssl_pkg: mod_ssl
        python_pkg: python-passlib
        httpdconf_src: files/httpd.conf
        httpdconf_file: /etc/httpd/conf/httpd.conf
        htaccess_src: files/.htaccess
        secrets_dir: /etc/httpd/secrets
        secrets_file: "{{ secrets_dir }}/htpasswd"
        web_root: /var/www/html
        web_user: guest
      vars_files:
        - vars/secret.yml
      tasks:
        - name: Latest version of necessary packages installed
          yum:
            name:
              - "{{ firewall_pkg }}"
              - "{{ web_pkg }}"
              - "{{ ssl_pkg }}"
              - "{{ python_pkg }}"
            state: latest
        - name: Configure web service
          copy:
            src: "{{ httpdconf_src }}"
            dest: "{{ httpdconf_file }}"
            owner: root
            group: root
            mode: 0644
        - name: Secrets directory exists
          file:
            path: "{{ secrets_dir }}"
            state: directory
            owner: apache
            group: apache
            mode: 0500
        - name: Web user exists in secrets file
          htpasswd:
            path: "{{ secrets_file }}"
            name: "{{ web_user }}"
            password: "{{ web_pass }}"
            owner: apache
            group: apache
            mode: 0400
        - name: .htaccess file installed in docroot
          copy:
            src: "{{ htaccess_src }}"
            dest: "{{ web_root }}/.htaccess"
            owner: apache
            group: apache
            mode: 0400
        - name: Create index.html
          copy:
            content: "{{ ansible_facts['fqdn'] }} ({{ ansible_facts['default_ipv4']['address'] }}) has been customized by Ansible.\n"
            dest: "{{ web_root }}/index.html"
        - name: Open the port for the web server
          firewalld:
            service: https
            state: enabled
            immediate: true
            permanent: true
        - name: Web service enabled and restarted
          service:
            name: "{{ web_svc }}"
            state: restarted
            enabled: true
        - name: Firewall service enable and restarted
          service:
            name: firewalld
            state: restar
    

Loop & Condition

  1. 조건문 사용 예시 입니다.
    when: >
        ( ansible_distribution == "RedHat" and
          ansible_distribution_major_version == "7" )
        or
        ( ansible_distribution == "Fedora" and
        ansible_distribution_major_version == "28" )
    
    ---
    - name: Restart HTTPD if Postfix is Running
      hosts: all
      tasks:
        - name: Get Postfix server status
          command: /usr/bin/systemctl is-active postfix
          ignore_errors: yes
          register: result
    
        - name: Restart Apache HTTPD based on Postfix status
          service:
            name: httpd
            state: restarted
          when: result.rc == 0
    
    ---
    - name: Database Setup play
      hosts: database_servers
      vars:
        min_ram_size_bytes: 2000000000
        supported_distros:
          - RedHat
          #- Centos
      tasks:
        - name: Setup Database tasks on supported hosts w/ Min. RAM
          include_tasks: "{{ ansible_distribution }}_database_tasks.yml"
          #Add a conditional here
    
        - name: Print a message for unsupported Distros
          debug:
            msg: >
              {{ inventory_hostname }} is a
              {{ ansible_distribution }}-based host, which is not one
              of the supported distributions ({{ supported_distros }})
          #Add a conditional here
    
        - name: Print a message for systems with insufficient RAM
          debug:
            msg: >
              {{ inventory_hostname }} does not meet the minimum
              RAM requirements of {{ min_ram_size_bytes }} bytes.
          #Add a conditional here
    
    - name: Ensure Database Users exist
      user:
        name: "{{ item.username }}"
        groups: "{{ item.role }}"
        append: yes
        state: present
      loop: "{{ user_list }}"
      when: item.role in host_permission_groups
    

Handlers

tasks:
  - name: copy demo.example.conf configuration template
    template:
      src: /var/lib/templates/demo.example.conf.template
      dest: /etc/httpd/conf.d/demo.example.conf
    notify:
      - restart mysql
      - restart apache

handlers:
  - name: restart mysql
    service:
      name: mariadb
      state: restarted

  - name: restart apache
    service:
      name: httpd
      state: restarte
---
- name: MariaDB server is installed
  hosts: databases
  vars:
    db_packages:
      - mariadb-server
      - MySQL-python
    db_service: mariadb
    resources_url: http://materials.example.com/labs/control-handlers
    config_file_url: "{{ resources_url }}/my.cnf.standard"
    config_file_dst: /etc/my.cnf
  tasks:
    - name: "{{ db_packages }} packages are installed"
      yum:
        name: "{{ db_packages }}"
        state: present
      notify:
        - set db password

    - name: Make sure the database service is running
      service:
        name: "{{ db_service }}"
        state: started
        enabled: true

    - name: The {{ config_file_dst }} file has been installed
      get_url:
        url: "{{ config_file_url }}"
        dest: "{{ config_file_dst }}"
        owner: mysql
        group: mysql
        force: yes
      notify:
        - restart db service

  handlers:
    - name: restart db service
      service:
        name: "{{ db_service }}"
        state: restarted

    - name: set db password
      mysql_user:
        name: root
        password: redhat
- name: Latest version of notapkg is installed
  yum:
    name: notapkg
    state: latest
  ignore_errors: yes
---
- hosts: all
  force_handlers: yes
  tasks:
    - name: a task which always notifies its handler
      command: /bin/true
      notify: restart the database

    - name: a task which fails because the package doesn't exist
      yum:
        name: notapkg
        state: latest
      #오류가 발생하는 태스크, 하지만 force_handlers 키워드로 핸들러가 실행된다.
  handlers:
    - name: restart the database
      service:
        name: mariadb
        state: restarted

  1. changed가 보고 되면, handler가 동작 하기때문에 ok 또는 failed만 보고하도록 강제합니다.

- name: get Kerberos credentials as "admin"
  shell: echo "{{ krb_admin_pass }}" | kinit -f admin
  changed_when: false
tasks:
  - shell:
      cmd: /usr/local/bin/upgrade-database
    register: command_result
    changed_when: "'Success' in command_result.stdout"
    notify:
      - restart_database

handlers:
  - name: restart_database
     service:
       name: mariadb
       state: restarted

block/rescue/alywas

tasks:
  - block:
      - name: upgrade the database
        shell:
          cmd: /usr/local/lib/upgrade-database
    rescue:
      - name: revert the database upgrade
        shell:
          cmd: /usr/local/lib/revert-database
    always:
      - name: always restart the database
        service:
          name: mariadb
          state: restarted
---
- name: Playbook Control Lab
  hosts: webservers
  vars_files: vars.yml
  tasks:
    #Fail Fast Message
    - name: Show Failed System Requirements Message
      fail:
        msg: "The {{ inventory_hostname }} did not meet minimum reqs."
      when: >
        ansible_memtotal_mb*1024*1024 < min_ram_megabytes*1000000 or
        ansible_distribution != "RedHat"

    #Install all Packages
    - name: Ensure required packages are present
      package:
        name: "{{ item }}"
        state: present
      loop: "{{ packages }}"

    #Enable and start services
    - name: Ensure services are started and enabled
      service:
        name: "{{ item }}"
        state: started
        enabled: yes
      loop: "{{ services }}"

    #Block of config tasks
    - block:
        - name: Create SSL cert directory
          file:
            path: "{{ ssl_cert_dir }}"
            state: directory

        - name: Copy Config Files
          copy:
            src: "{{ item.src }}"
            dest: "{{ item.dest }}"
          loop: "{{ web_config_files }}"
          notify: restart web service

      rescue:
        - name: Configuration Error Message
          debug:
            msg: >
              One or more of the configuration
              changes failed, but the web service
              is still active.

    #Configure the firewall
    - name: ensure web server ports are open
      firewalld:
        service: "{{ item }}"
        immediate: true
        permanent: true
        state: enabled
      loop:
        - http
        - https

  #Add handlers
  handlers:
    - name: restart web service
      service:
        name: "{{ web_service }}"
        state: restarted

File Module

- name: SELinux type is set to samba_share_t
  file:
    path: /path/to/samba_file
    setype: samba_share_t

- name: SELinux type is persistently set to samba_share_t
  sefcontext:
    target: /path/to/samba_file
    setype: samba_share_t
    state: present
tasks:
  - name: Fetch the /var/log/secure log file from managed hosts
    fetch:
      src: /var/log/secure
      dest: secure-backups
      flat: no

Template

tasks:
  - name: template render
    template:
      src: /tmp/j2-template.j2
      dest: /tmp/dest-config-file.txt
---
- hosts: all
  remote_user: devops
  become: true
  vars:
    system_owner: clyde@example.com
  tasks:
    - template:
        src: motd.j2
        dest: /etc/motd
        owner: root
        group: root
        mode: 0644

Manage Large Project

# 특정 Host의 IP주소를 명시
ansible_host: 192.168.2.1
# 와일드 카드 이용
$ cat playbook.yml
---
- hosts: '*'
...output omitted...
# 작은 따옴표를 이용하여 호스트를 묶는다.
---
  hosts: '!test1.example.com,development'
---
- hosts: '*.example.com'
---
- hosts: all,!datacenter1
[student@workstation projects-host]$ ansible '*.example.com, !*.lab.example.com' \
> -i inventory1 --list-hosts
  hosts (7):
    saturn.example.com
    db1.example.com
    db2.example.com
    db3.example.com
    file2.example.com
    srv1.example.com
    srv2.example.com

Dynamic Inventory

inventorya.py
chmod 755 inventorya.py
./inventorya.py --list
ansible -i inventorya.py all --list-hosts

Serial

[student@demo ~]$ grep forks ansible.cfg
forks          = 5
#동시에 5대 씩 실행
# 두대씩 순차적으로 진행
---
- name: Rolling update
  hosts: webservers
  serial: 2
  tasks:
1. 콘텐츠를 포함(include)시키면 동적(dynamic)으로 처리합니다.
2. 플레이북 실행 중에 콘텐츠가 포함되는 대로 처리합니다.
3. 콘텐츠를 가져오면(import) 정적(static)으로 처리합니다.
4. 플레이북 구문을 처음 분석할때 가져온 콘텐츠를 가져와 먼저 실행합니다.
5. import_playbook은 Master Playbook에 사용합니다.
- name: Prepare the web server
  import_playbook: web.yml

- name: Prepare the database server
  import_playbook: db.yml
6. import_tasks는 task에 다른 작업을 추가할때 사용 합니다.
7. webserver_tasks.yml 에는 단순 task만 명시 합니다.
---
- name: Install web server
  hosts: webservers
  tasks:
  - import_tasks: webserver_tasks.yml
7. include_tasks 기능은 작업 파일을 플레이북 내부로 동적으로 포함 합니다.
---
- name: Install web server
  hosts: webservers
  tasks:
  - include_tasks: webserver_tasks.yml
8. import_tasks는 테스크에 포함 하여, 테스크에 대한 내용만 명시되어있는 파일을 추가 합니다.
9. import_playbook은 플레이북이기때문에 동등한 위치에 정의, 플레이북에 대한 내용이 모두 포함되어있는 파일 추가 합니다.
---
- name: Configure web server
  hosts: servera.lab.example.com

  tasks:
    - name: Import the environment task file and set the variables
      import_tasks: tasks/environment.yml
      vars:
        package: httpd
        service: httpd
    - name: Import the firewall task file and set the variables
      import_tasks: tasks/firewall.yml
      vars:
        firewall_pkg: firewalld
        firewall_svc: firewalld
        rule: http
    - name: Import the placeholder task file and set the variable
      import_tasks: tasks/placeholder.yml
      vars:
        file: /var/www/html/index.html

- name: Import test play file and set the variable
  import_playbook: plays/test.yml
  vars:
    url: 'http://servera.lab.example.com'

Role

  1. rhel-system-roles 패키지에서 시스템 Role을 받을 수 있습니다.(/usr/share/ansible/roles/)
  2. Role은 디렉토리 구조가 중요하며, 각 용도에 맞게 사용할 수 있습니다.
  3. 암호나 개인키는 절대 포함되어 있으면 안됩니다.
  4. Ansible은 현재 경로에서 roles라는 디렉토리를 찾으며, 찾지 못할 경우 ansible.cfg에서 roles_path 값을 참고합니다.
DIRECTORY DESCRIPTION
defaults Role 변수의 기본값이 포함되어 있습니다.
files TASK에서 사용할 파일이 포함되어 있습니다.
handlers handler가 포함되어 있습니다.
meta Role에 대한 작성자, 라이센스, 옵션 등 역할의 종속성이 포함되어 있습니다.
tasks TASK가 있는 main.yml 파일이 포함되어 있습니다.
templates jinja2 템플릿이 포함되어 있습니다.
tests 테스트를 할 수 있는 inventory와 test.yml 파일이 포함되어 있습니다.
vars 변수를 정의하고 있는 yaml파일이 포함되어 있습니다.
  1. defaults 에 선언된 변수는 우선 순위가 가장 낮으며, vars 변수는 우선 순위가 높습니다.
  2. 대부분의 경우 Role이 먼저 수행되고 이후 TASK가 동작하지만, pre_tasks 로 먼저 실행할 TASK를 정의할 수 있습니다.
  3. post_tasks를 사용하면 일반 TASK와 handlers가 모두 실행 된 후 실행 됩니다.
  4. Role-Skeleton 은 ansible-galaxy init [role_name] 명령어로 만들 수 있습니다.
  5. requirement 파일이 있는 경우 ansible-galaxy install -r [requirement] -p [role path] 형식으로 role을 install 할 수 있습니다.

$ tree user.example
user.example/
├── defaults
│   └── main.yml
├── files
├── handlers
│   └── main.yml
├── meta
│   └── main.yml
├── README.md
├── tasks
│   └── main.yml
├── templates
├── tests
│   ├── inventory
│   └── test.yml
└── vars
    └── main.yml
---
- hosts: remote.example.com
  roles:
    - role1
    - role2
- name: Play to illustrate order of execution
  hosts: remote.example.com
  pre_tasks:
    - debug:
        msg: 'pre-task'
      notify: my handler
  roles:
    - role1
  tasks:
    - debug:
        msg: 'first task'
      notify: my handler
  post_tasks:
    - debug:
        msg: 'post-task'
      notify: my handler
  handlers:
    - name: my handler
      debug:
        msg: Running my handler
- name: Play to illustrate order of execution
  hosts: remote.example.com
  pre_tasks:
    - debug:
        msg: 'pre-task'
      notify: my handler
  roles:
    - role1
  tasks:
    - debug:
        msg: 'first task'
      notify: my handler
  post_tasks:
    - debug:
        msg: 'post-task'
      notify: my handler
  handlers:
    - name: my handler
      debug:
        msg: Running my handler
1. Role 예제 Playbook 입니다.
---
- name: Configure Dev Web Server
  hosts: dev_webserver
  force_handlers: yes
  roles:
    - apache.developer_configs
  pre_tasks:
    - name: Check SELinux configuration
      block:
        - include_role:
            name: rhel-system-roles.selinux
      rescue:
        # Fail if failed for a different reason than selinux_reboot_required.
        - name: Check for general failure
          fail:
            msg: "SELinux role failed."
          when: not selinux_reboot_required

        - name: Restart managed host
          reboot:
            msg: "Ansible rebooting system for updates."

        - name: Reapply SELinux role to complete changes
          include_role:
            name: rhel-system-roles.selinux