Ansible ======================================================================== (2017, 2019) Ansible is an IT automation package. Its big competitor is SaltStack. - https://www.ansible.com/ - https://docs.ansible.com/ Overview: - Ansible works over SSH, so there no client-side component to install. - Ansible operates on an **inventory** of clients, which can be as simple as a list of hosts in a text file. - An Ansible **playbook** contains a sequence of instructions to run on hosts in an inventory. - Playbooks are simple, YAML-formatted lists of operations. - Each playbook item executes an Ansible **module**. One module copies a file, for example. Another module creates a user. - Although Ansible can execute arbitrary commands on clients, modules are designed to be idempotent. I.e., it's safe to execute the same operation on client that start in unknown/different states. For example, the user-creation module creates the user on systems without that user, but does something sane on systems where the user already exists. Installation & Getting Started ------------------------------------------------------------------------ Ansible works over ssh, so we don't need to install anything on clients. The control machine must have Python (2.6+ or 3.5+). # apt-get install ansible Even on the control machine, there's no database, no running daemon, just commands to run as needed. http://docs.ansible.com/ansible/latest/intro_installation.html Ansible uses any settings it finds in `~/.ssh/config` (e.g., jump host). By default, it uses SFTP but can be configured to use SCP. Ansible assumes the use of SSH keys, but can be given the `--ask-pass` or `--ask-become-pass` option. > Ansible doesn't just have to connect remotely over SSH. The transports are pluggable, and there are options for managing things locally, as well as managing chroot, lxc, and jail containers. A mode called 'ansible-pull' can also invert the system and have systems 'phone home' via scheduled git checkouts to pull configuration directives from a central repository. http://docs.ansible.com/ansible/latest/intro_getting_started.html Basic definitions: - **Control node**: the host where we use Ansible to execute tasks on managed nodes - **Managed node**: a host configured by the control node - **Host inventory**: a list of managed nodes - **Ad-hoc command**: a simple, one-off task - **Playbook**: a playbook groups repeatable tasks for complex configuration - **Module**: a module performs a common task like adding a user or installing a package - **Idempotent**: an operation that when performed repeatedly yields the same outcome as when performed once Ansible looks for its configuration in this order: 1. `ANSIBLE_CONFIG` environment variable 2. `$PWD/ansible.cfg` 3. `$HOME/ansible.cfg` 4. `/etc/ansible/ansible.cfg` The config file defines the location of the hosts inventory: ``` [defaults] inventory = /etc/ansible/hosts ``` Inventory ------------------------------------------------------------------------ Ansible acts on multiple machines from the inventory. A dynamic inventory is possible (LDAP, Cobbler, etc.), but the file `/etc/ansible/hosts` is the default inventory (or specify with the `-i` flag): # Ex 1: Ungrouped hosts, specify before any group headers. bigby.example.com 192.168.100.22 www[1:26].example.com [dbservers] everest.example.com db-[a:f].example.com [kvm] mizzen.example.com zol.example.com tiger.example.com For hosts with non-22 SSH ports, specify them like `foo.example.com:22000`. The inventory can contain variable definitions, per-host or per-group, for later use in playbooks: firefly.example.com http_port=8080 [flint] super_secret=true Make groups-of-groups using the `:children` suffix: [north_america:children] michigan illinois newyork Two implicit groups exist: `all` and `ungrouped`. Have Ansible scan the hosts file to list all hosts it recognizes or all hosts from a particular group: $ ansible all --list-hosts $ ansible webservers --list-hosts Check which of the hosts in the inventory respond to pings: $ansible all -m ping firefly.example.com | SUCCESS => { "changed": false, "ping": "pong" } tiger.example.com | SUCCESS => { "changed": false, "ping": "pong" } kosmokrator.example.com | SUCCESS => { "changed": false, "ping": "pong" } mizzen.example.com | SUCCESS => { "changed": false, "ping": "pong" } nostromo.example.com | SUCCESS => { "changed": false, "ping": "pong" } Ad-Hoc Commands ------------------------------------------------------------------------ Ad-hoc commands are things too quick, simple, or single-use to write as a playbook. http://docs.ansible.com/ansible/latest/intro_adhoc.html Ping all the hosts: $ ansible all -m ping We have a `[kvm]` group of hypervisors specified in `/etc/ansible/hosts`. What guests are on each? $ ansible kvm --ask-become-pass --become -a "virsh list --all" For commands that require shell features (even ones as basic as pipes) use the "shell" module: $ ansible detroit -m shell -a 'echo $TERM' (The default module is `command`.) The `-f` flag sets the number of parallel processes (default 5). $ ansible kvm -f 10 -m shell -a 'cat /etc/group | tac' > So far we’ve been demoing simple command execution, but most Ansible modules are not simple imperative scripts. Instead, they use a declarative model, calculating and executing the actions required to reach a specified final state. Furthermore, they achieve a form of idempotence by checking the current state before they begin, and if the current state matches the specified final state, doing nothing. However, we also recognize that running arbitrary commands can be valuable, so Ansible easily supports both. Transfer a file directly to many servers: $ ansible atlanta -m copy -a "src=/etc/hosts dest=/tmp/hosts" Change file ownership or permissions with the `file` module: $ ansible webservers -m file -a "dest=/srv/foo/b.txt mode=600 owner=mdehaan group=mdehaan" Delete directories (recursively) and delete files: $ ansible webservers -m file -a "dest=/path/to/c state=absent" Add or remove users: $ ansible all -m user -a "name=foo password=" $ ansible all -m user -a "name=foo state=absent" Trigger a Git pull: $ ansible webservers -m git -a "repo=https://foo.example.org/repo.git dest=/srv/myapp version=HEAD" Make sure a service is started, restarted, or stopped: $ ansible webservers -m service -a "name=httpd state=started" $ ansible webservers -m service -a "name=httpd state=restarted" $ ansible webservers -m service -a "name=httpd state=stopped Long-running operations can run in the background, with optional timeout and status polling: $ ansible all -B 3600 -P 0 -a "/usr/bin/long_running_operation --do-stuff" $ ansible web1.example.com -m async_status -a "jid=488359678239.2844" $ ansible all -B 1800 -P 60 -a "/usr/bin/long_running_operation --do-stuff" Report "facts" — discovered variables about the client(s): $ ansible all -m setup Playbooks ------------------------------------------------------------------------ Playbooks are Ansible’s configuration, deployment, and orchestration language. http://docs.ansible.com/ansible/latest/playbooks.html Write playbooks in YAML. http://docs.ansible.com/ansible/latest/YAMLSyntax.html Before trying to edit YAML with Vim, add this to your `.vimrc`: ``` au! BufNewFile,BufReadPost *.{yaml,yml} set filetype=yaml foldmethod=indent autocmd FileType yaml setl ts=2 sw=2 sts=2 expandtab ``` Each playbook lists one or more "plays". A "play" links hosts to tasks. Tasks are calls to Ansible modules. --- - 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 Run a playbook: $ ansible-playbook myplaybook.yml Check the playbook syntax: $ ansible-playbook --check myplaybook.yml Lint a playbook (Debian has an `ansible-lint` package): $ ansible-lint myplaybook.yml Walk through a playbook step-by-step: $ ansible-playbook --step myplaybook.yml Make a playbook executable and head it with this shebang like to run it like a shell script: #!/usr/bin/ansible-playbook A playbooks can contain `{{ variables }}`. Register the [results of shell commands as variables](http://docs.ansible.com/ansible/latest/user_guide/playbooks_variables.html#registered-variables) like: ``` tasks: - shell: /usr/bin/foo register: foo_result ignore_errors: True ``` Modules ------------------------------------------------------------------------ http://docs.ansible.com/ansible/latest/modules.html Show available modules: $ ansible-doc -l Ansible offers _many_ modules from the very basic and universal to the esoteric. Interesting modules include: apt, at, copy, cron, dnf, docker*, git, group, hostname, htpasswd, **include**, ip_netns (v2.5+), iptables, letsencrypt, lvol (lvm), mail, mount, mysql_*, nmcli, package, sysctl, systemd, user, virt (libvirt), vyos_*, win_*, yum. Ansible Vault ------------------------------------------------------------------------ Ansible Vault stores secrets. Vault does file-level symmetric encryption. One of the main problems it solves is preserving secrets as part of a versioned repository without exposing them. ``` $ ansible-vault create secrets.yml New Vault password: Confirm New Vault password: ``` ``` $ cat secrets.yml $ANSIBLE_VAULT;1.1;AES256 32323261326237346266386136343866346538303061336562326132313536663137656664306266 3435326361666537373536613638323935393662336263370a323065646233366464346432646664 39373837386531306435636438363066393838356364376533636230346630613163643437396235 3538326537346362300a363966386138363037643930393561663436653664653030666234303965 61333934633935383931646166613533643636666334303337353337396536653937 ``` ``` $ ansible-vault edit secrets.yml Vault password: ``` ``` $ ansible-vault view secrets.yml Vault password: A very secret thing. ``` ``` $ ansible-vault rekey secrets.yml Vault password: New Vault password: Confirm New Vault password: Rekey successful ``` How is this more useful that any generic symmetric encryption utility? We can name the secret values, and use them as variables in an Ansible playbook. ``` $ ansible-vault encrypt_string --stdin-name 'key-for-secret' New Vault password: Confirm New Vault password: Reading plaintext input from stdin. (ctrl-d to end input) My secret value.key-for-secret: !vault | $ANSIBLE_VAULT;1.1;AES256 32623531636165636338343630373936613133346130336532393338306330326663646132643339 6266363365643937333837383934663732646631353335620a333033363362383839336565323966 39616332323039363461363930396633643735366135633465653936643065356266313965343063 6436383833646163660a306162626136366563303261663636643330666263613839303235633532 38343964613037316434353762373765663138626464663466656631303633353331 Encryption successful ``` (Terminate value entry with CTRL-d, not ENTER!) Paste the resulting encrypted variable definition into the Ansible playbooks along with any other variables. Use the variable later in the playbook by enclosing it in (quoted for YAML safety) Jinja-style double-curly-brackets. ``` - hosts: webservers vars: http_port: 8080 key-for-secret: !vault | $ANSIBLE_VAULT;1.1;AES256 32663335323337646165663536633432303633396131346432643231353632316539626537343236 3064613431666565623237666136363561636634633936340a383236363137353663346666613334 65623831613664653036396130386461623066363532386133653363326566323539616336363065 6134313630303132350a393031353735626237356238653430373239653635323837363666303733 6261 […] - name: Add web config webconfig: set_passwd: "{{ key-for-secret }}" ``` When running a playbook with encrypted variables, use `--ask-vault-pass`: ``` ansible-playbook --ask-vault-pass -i inventory_file my_playabook.yml ``` Links ------------------------------------------------------------------------ - https://www.redhat.com/en/blog/system-administrators-guide-getting-started-ansible-fast - https://docs.ansible.com/ansible/latest/user_guide/vault.html - https://www.digitalocean.com/community/tutorials/how-to-use-vault-to-protect-sensitive-ansible-data-on-ubuntu-16-04 - https://docs.ansible.com/ansible/latest/user_guide/playbooks_variables.html