Ansible Concepts

From NovaOrdis Knowledge Base
Revision as of 04:24, 13 April 2021 by Ovidiu (talk | contribs) (→‎Task)
Jump to navigation Jump to search

External

Internal

Overview

Ansible is a configuration management and provisioning tool, similar to Chef, Puppet or Salt. What sets it aside is that it does not require agents on the hosts it configures - it only requires ssh access and sudo. The target hosts are specified in an inventory file, located on the "ansible host", the machine that is use to conduct operations from. Then ansible is executed on that host. During the execution, ansible connects via SSH to the target hosts, gets the context and executes tasks being cognizant of the context. This allows tasks to be idempotent. In the beginning, the inventory can be probed for availability for running a ping module:

ansible -i ./hosts.yaml <group-name|all> -m ping

The idempotence of tasks comes from the idempotence of their components, the modules. Modules are also aware of the facts of the context.

Multiple tasks are grouped together in playbooks. A playbook is executed with the ansible-playbook command.

Inventory File

https://docs.ansible.com/ansible/latest/user_guide/intro_inventory.html

Ansible works against multiple systems at the same time. It does this by selecting portions of systems listed in Ansible’s inventory file. The default location of the inventory file is /etc/ansible/host, but it can be placed anywhere and referred from the command line with:

-i <path>

The conventional name is hosts.yaml.

Inventory File Structure

host1.example.com

[webservers]
web1.example.com
web2.example.com

[dbservers]
db1.example.com
db2.example.com

Groups

The headings in brackets are group names, which are arbitrary strings used in classifying systems and deciding what systems you are controlling at what times and for what purpose. A host can be part of more than one group.

Default Groups

There are two default groups: "all" and "ungrouped". "all" contains every host. "ungrouped" contains all hosts that don’t have another group aside from "all".

Recursive Groups

Recursive groups are declared with the [<group-name>:children].

[A]
host1
host2

[B]
host3
host4

[AandB:children]
A
B

[AandB:vars]
something=something-else

Dynamic Inventory

https://docs.ansible.com/ansible/latest/user_guide/intro_dynamic_inventory.html

Playbook

https://docs.ansible.com/ansible/latest/user_guide/playbooks_intro.html

Playbooks are YAML files that contain one or more "plays" in an ordered list. Each play executes part of the overall goal of the playbook. Each play includes at minimum two things:

  • the managed hosts to target.
  • at least one task, possible more than one, to be executed against the target hosts.

Optionally, a playbook may include:

  • variables, under the "vars." subtree.
  • roles, under the "roles." subtree. All roles specified here are are assigned to the configured hosts. If a list of roles is provided in a play, it is said that the roles are used "at the play" level, meaning that their tasks are added to the play.
  • remote_users.
  • pre_tasks.
  • etc.
---
- name: play one
  hosts: all
  become: yes
  become_user: root
  vars:
    some_var: something
  pre_tasks:
    - import_tasks: tasks/some_tasks.yml
  roles:
    - kubernetes-local-storage
  tasks:
    - name: Install Nginx
      apt:
        name: nginx
        state: installed
        update_cache: true
- name: play two
  hosts: ...
  tasks:
    - ...

import_playbook

A playbook can include other playbooks:

---
- import_playbook: some-other-playbook.yml

ansible-playbook

A playbook is executed with "ansible-playbook" command:

ansible-playbook -i ./hosts.yaml <playbook-name>.yaml

Task

A task combines an action (a module and its arguments) with a name and optionally some other keywords like looping directives. Handlers are also tasks, but they do not run unless they are notified by name when a task reports an underlying change on a remote system. Playbooks exist to run tasks.

Ansible tasks are idempotent, they can be safely run repeatedly. Idempotence is achieved by the fact that ansible first gathers the context, which consists of facts, before running the tasks. Ansible "checks the facts" first to decide whether it needs to change anything. If the outcome it seeks is already achieved, the task is not executed.

- name: Create some directories
  file:
    path: "{{ item }}"
    state: directory
    owner: ec2-user
  when: inventory_hostname in groups['blue']
  become: true
  with_items:
    - "/tmp/a"
    - "/tmp/b"
    - "{{ some_dir_variable }}"

Task Configuration

https://docs.ansible.com/ansible/latest/reference_appendices/playbooks_keywords.html#task

name

A task identifier that can be used for documentation.

vars

A map of variables.

become

Boolean that controls if privilege escalation is used or not on task execution. Implemented by the become plugin.

- name: Some task
  become: true
  [...]

become_exe

The path of the executable used to elevate privileges. Implemented by the become plugin.

become_flags

A string of flag(s) to pass to the privilege escalation program when become is true.

become_method

Which method of privilege escalation to use (such as sudo or su).

become_user

User that you ‘become’ after using privilege escalation. The remote/login user must have permissions to become this user.

import_tasks

Tasks can be imported recursively with import_tasks:

---
- import_tasks: some-file-that-contains-tasks.yaml
- import_tasks: some-dir/some-other-file-that-contains-tasks.yaml

Conditional Task Execution

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

Tasks can be conditionally executed based on facts, registered variables or playbook or inventory variables.

- name: Download Amazon Corretto
  get_url:
    url: https://corretto.aws/downloads/latest/amazon-corretto-11-x64-macos-jdk.pkg
    dest: /tmp
  when: java_vendor == "Amazon"

Action

An action is a module and its arguments.

Module

Modules are the "verbs" in Ansible, they do things, like install software, copy files, use templates and so on. Modules can use available context (facts) in order to determine what actions, if any, need to be done to accomplish a task. Thus, they insure idempotence of the tasks that execute them.

A module can be executed individually, on command line, in the same way as it would be executed as part of a playbook.

ansible -m <module-name> -a "arg1 arg2 ..."

Example:

ansible -m shell -a "echo {{ a_global_variable }} > /tmp/test.txt"

A module is declared as follows:

- name: This is a descriptive name of what the module execution will achieve
  <module-name>: ...
  ...

Example:

- name: This is a descriptive name of what the module execution will achieve
  shell: echo {{ openshift_dns_ip }} > /etc/origin/node/openshift-dns-ip
  args:
    executable: /bin/bash

Module Index

https://docs.ansible.com/ansible/latest/modules/modules_by_category.html

Often-Used Modules

Module Loop

jdks:
  - corretto8
  - corretto11
...
- name: Multiple items in a loop
  homebrew_cask:
    name: "{{ item }}"
    state: present
  loop: "{{ jdks }}"

Handler

A handler is similar to a regular tasks, in that it can do anything a task can, but it will only run when called by another tasks: handles are only run if the task contains a notify directive and also indicates that it changed something. They can be though as part of an event-based system: a handler will take an action when called by an event it listens for. This is useful for "secondary" actions that might be required after running a task, such as starting a new service after installation or reloading a service after a configuration change. For example, if a config file is changed, then the task referencing the config file templating operation may notify a service restart handler. This means services can be bounced only if they need to be restarted. Handlers can be used for things other than service restarts, but service restarts are the most common usage.

- hosts: all
  become: yes
  become_user: root
  tasks:
   - name: Install Nginx
     apt:
       name: nginx
       state: installed
       update_cache: true
     notify:
      - Start Nginx

  handlers:
   - name: Start Nginx
     service:
       name: nginx
       state: started

Role

https://docs.ansible.com/ansible/latest/user_guide/playbooks_reuse_roles.html

Roles are units of organization that are good for organizing multiple, related tasks and encapsulating data needed to accomplish those tasks. Assigning a role to a group of hosts implies that they should implement a specific behavior. A role may include applying certain variable values, certain tasks, and certain handlers. Roles are redistributable units that allow you to share behavior among playbooks. A playbook includes its roles typically in a 'roles' directory. Conventionally, the elements associated with a role are stored in a directory named after the role:

roles
  |
  +- role1
  |    |
  .    +-- files
  .    +-- handlers
  .    +-- meta
       +-- tasks
       +-- templates
       +-- vars

  |
  +- role2
  +- role3
  ...

Within each directory, Ansible will search for and read any Yaml file called main.yml automatically.

Roles can be created with the following command:

cd .../roles
ansible-galaxy init <role-name>

Role Structure

Role Files

The "files" directory contains files that we want copied into servers. via the copy module.

Role Handlers

Example of handlers/main.yaml:

---
- name: Start Nginx
  service:
    name: nginx
    state: started

- name: Reload Nginx
  service:
    name: nginx
    state: reloaded

Role Meta

The main.yml file within the meta directory contains role meta data, including dependencies on another role:

---
dependencies:
  - { role: ssl }

Role Tasks

tasks/main.yml cotains the role's tasks.

Example:

---
#
# tasks file for kubernetes-local-storage
#

- name: format ESB block storage device
  filesystem:
    fstype: xfs
    dev: /dev/xvdb
    force: no

- name: create local storage mount point
  file:
    path: "{{ item }}"
    state: directory
    owner: root 
  with_items:
    - "/mnt/ebs0"
  when:
    - inventory_hostname in groups['kube-node']

- name: mount storage and update /etc/fstab
  mount:
    state: mounted
    path: /mnt/ebs0
    src: /dev/xvdb
    fstype: xfs

- name: create volume directories
  file:
    path: "/mnt/ebs0/{{ item }}"
    state: directory
    owner: root
  with_items:
    - "local-pv0"
    - "local-pv1"
    - "local-pv2"

Role Templates

Template files can contain template variables, based on Python's Jinja2 template engine. Files in here should end in .j2, but can otherwise have any name. Similar to files, we won't find a main.yml file within the templates directory.

Role Vars

The vars directory contains a main.yaml file that lists variables to use. This provides a convenient place for us to change configuration-wide settings.

---
domain: example.com
ssl_key: /etc/ssl/sfh/sfh.key
ssl_crt: /etc/ssl/sfh/sfh.crt

Using Roles

Using Roles at Play Level

https://docs.ansible.com/ansible/latest/user_guide/playbooks_reuse_roles.html#using-roles-at-the-play-level

If a list of roles is provided in a play, it is said that the roles are used "at the play" level. This is the classic way to use roles. If a role is specified in the play's "roles" list, then the tasks from 'roles/<role-name>/tasks/main.yaml' are added to the play. Note that usually 'main.yaml' imports other tasks, coming from other files, for modularization.

- hosts: servers
  roles:
     - common
     - webservers
- hosts: servers
  roles:
     - { role: username.rolename, x: 42 }

In what order are the tasks executed? Is the listing order honored?

Variables

https://docs.ansible.com/ansible/latest/user_guide/playbooks_variables.html

Places where to Define Variables

Variables can be defined in:

Variable Precedence

https://docs.ansible.com/ansible/latest/user_guide/playbooks_variables.html#understanding-variable-precedence

Simple Variables

https://docs.ansible.com/ansible/latest/user_guide/playbooks_variables.html#simple-variables

Simple variable can be declared as such:

amazon_corretto_package_name: amazon-corretto-11-x64-macos-jdk.pkg

jdk:
  version: 11

parameterized_amazon_corretto_package_name: amazon-corretto-{{ jdk.version }}-x64-macos-jdk.pkg

and can be used by enclosing them in double curly braces {{ ... }}:

- name: Download the latest Amazon Corretto {{ jdk.version }}
  get_url:
    url: https://corretto.aws/downloads/latest/{{ amazon_corretto_package_name }}
    dest: /tmp/{{ amazon_corretto_package_name }}

Registered Variables

https://docs.ansible.com/ansible/latest/user_guide/playbooks_variables.html#registering-variables

Variables can be created from the output of Ansible tasks, which is "registered" as a variable with the task keyword register. Variables registered as such can be used by any later tasks in the play. Registered variables may be simple variables, list variables, dictionary variables, or complex nested data structures. Registered variables are stored in memory. Registered variables cannot be cached for use in future plays. Registered variables are only valid on the host for the rest of the current playbook run. Registered variables are host-level variables.

Examples:

Registering Variables as Results of Command Execution

Host File Variables

Inventory File Variables

Group Variables

Group variables are declared with [<group-name>:vars] in the inventory file. If they are declared this way, the variables apply to an entire group at once.

[group-A]
host1
host2

[group-A:vars]
something=something-else

Host Variables

Variables that apply to a specific host are declared in the inventory file after the host name:

[group1]
host1 http_port=80 maxRequestsPerChild=808
host2 http_port=303 maxRequestsPerChild=909

Filters and Data Manipulation

Ansible file content can be manipulated with filters. TODO: https://docs.ansible.com/ansible/latest/user_guide/playbooks_filters.html#manipulating-strings

Fact

http://docs.ansible.com/ansible/glossary.html#term-facts

Before running any tasks, Ansible will gather information about the system it is provisioning. These are called facts. Facts are pieces of information about remote nodes: system and environment information. Facts are used in playbooks, tasks and templates just like variables, but they are inferred, rather than set, during automatic discovery when running plays, by executing the internal setup module on the remote node. Ansible facts all start with anisble_ and are globally available for use any place variables can be used: variable files, tasks, and templates.

Facts can also be explicitly with the set_fact module.

Directives

notify

register

tasks:
   - name: Add Nginx Repository
     apt_repository:
       repo: ppa:nginx/stable
       state: present
     register: ppastable

You can register the results of a modules action as well, and use the variable defined in register to conditionally perform actions when based on the registered variables values. For example, registering the result of the command run via the shell module can let you access the stdout of that command.

when

tasks:
- name: Install Nginx
     apt:
       pkg: nginx
       state: installed
       update_cache: true
     when: ppastable|success
     notify:
      - Start Nginx

Template

A template is a file to be installed on the target system after the declared variables are substituted with actual values. Variable values may come from the inventory file, host variables, group variables, or facts. Templates use the Jinja2 template engine and can also include logical constructs like loops and if statements.

Jinja2 Templating

https://docs.ansible.com/ansible/latest/user_guide/playbooks_templating.html

Vault

Privilege Escalation

Keywords: become, sudo, su, root:

Ansible Privilege Escalation

Plugins

Become Plugins

Privilege Escalation | Become Plugins