This blog post continues the discussion on features regarding playbook in Ansible.
In the previous article, we covered the basic ad-hoc commands and playbooks for installing packages to all the managed nodes. However, there are more features for flexibly configuring servers that are often required in the real projects. Hence, in this article, we will continue the discussion on other features of Ansible playbooks.
Targetting Nodes & Tasks
In the previous article, we installed the same package on all nodes, but we often have different installation requirements for different servers, such as reverse proxies, web servers, database servers, and file servers. To run different commands for different sets of servers, we can group them in the inventory as shown below.
[web_servers]
253.78.154.178
84.246.7.193
[db_servers]
156.72.61.90In the playbook, we can set the value of the hosts field to web_servers or
db_servers to perform tasks on the specified nodes, such as installing Apache
and PHP on web servers while installing PostgreSQL on the database servers.
However, we might not want to perform those tasks simultaneously. To target
the tasks to perform, we can add a tag field for each task, like apache
and postgres, and run the ansible-playbook command with the --tags <tag> flag attached.
For the tasks that need to be run regardless of tags specified, the special
tag always can be utilized, and for the tasks that need to be run
before all other tasks, we can use pre_tasks instead of tasks.
Files & Services
So far, we've been dealing with package managers and installing packages onto the nodes.
However, we often need to copy local and remote files to the nodes, which we can do
with the copy module, as shown below. We can specify the source file with src
(the file name in the files directory in the same parent directory as the playbook),
the destination with dest, and user and group permissions with other fields.
...
# Copy local html
- name: copy html file
tags: apache
copy:
src: site.html
dest: /var/www/html/index.html
owner: root
group: root
mode: 0644
# Install unzip & Install and unzip remote file
- name: install unzip
package:
name: unzip
- name: install terraform
unarchive:
src: https://releases.hashicorp.com/terraform/0.12.28/terraform_0.12.28_linux_amd64.zip
dest: /usr/local/bin
remote_src: yes
owner: root
group: root
mode: 0755
...We can also install and unzip remote archives, such as Terraform (although Terraform is
often required for a local workstation rather than a web server). Copying and installing
files onto the managed nodes enables much more than just installing packages. In addition,
some packages, like httpd, require systemctl to start the service on certain nodes,
which can be achieved using the service module.
...
- name: start httpd (CentOS)
tags: apache
service:
name: httpd
state: started
enabled: yes
when: ansible_distribution == "CentOS"
- name: change http config
tags: apache
lineinfile:
path: /etc/httpd/conf/httpd.conf
regexp: '^ServerAdmin'
line: ServerAdmin example@exmaple.com
when: ansible_distribution == "CentOS"
register: httpd
- name: restart httpd upon config change
tags: apache
service:
name: httpd
state: restarted
when: httpd.changed
...The first task starts the httpd service and ensures that the service is enabled,
even if the service is already started and somehow disabled. The second task uses
the lineinfile module to apply changes to httpd.conf. The httpd variable is
registered for the third task to be executed upon change in configuration,
detectable with httpd.changed. It's important to note that httpd.changed
returns true only when the immediately preceding task with httpd registered
is performed. (If there's another task in between with httpd registered,
and it didn't run, httpd.changed will evaluate to false.)
Users & Boostrapping
So far, we've been using --ask-become-pass and providing the sudo password in
every command execution to run sudo commands on the managed nodes. However, we
can avoid this flag by creating a new user for Ansible (that isn't the default user),
registering an SSH key for the user, adding the user to the sudoers file,
and adding remote_user = <usr_name> in ansible.cfg. We can do these processes
manually for each node or utilize and bootstrap with an Ansible playbook.
- hosts: all
become: true
vars:
ansible_manager_user: ansible_user
ssh_public_key: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}"
tasks:
- name: create ansible user
tags: user
user:
name: "{{ ansible_manager_user }}"
group: "{{ 'wheel' if ansible_os_family == 'RedHat' else 'sudo' }}"
create_home: true
shell: /bin/bash
- name: add ssh key for ansible
tags: user
authorized_key:
user: "{{ ansible_manager_user }}"
key: "{{ ssh_public_key }}"
- name: add sudoer file for ansible
tags: user
copy:
src: sudoer_ansible # 'ansible ALL=(ALL) NOPASSWD: ALL' in it
dest: /etc/sudoers.d/{{ ansible_manager_user }}
owner: root
group: root
mode: 0440
validate: /usr/sbin/visudo -cf %sThe above bootstrap.yaml playbook utilizes the vars field, user,
authorized_key, and copy modules to set up the user, its SSH key,
and sudoer privileges. We can execute the playbook with flags like
-u <initial_user>, --ask-pass, and --ask-become-pass, and edit
ansible.cfg to set the default user to be ansible_user. After running
this successfully, we can run future playbooks without --ask-become-pass
and providing the sudo password.
Conclusion
In this article, we covered how we can target specific nodes and tasks, manage files and services, and set up users in Ansible playbooks. These capabilities enable much more flexible and convenient server configurations for real-world applications. However, the playbooks are getting longer and messier. Hence, in the next article, we're going to cover the additional features that we can utilize to cleanly organize the required components in Ansible.
Resources
- Ansible. n.d. Ansible Documentation. Ansible Community Documentation.
- Learn Linux TV. 2020. Getting started with Ansible. YouTube.