-
Notifications
You must be signed in to change notification settings - Fork 4
Ansible Fundamentals
The very basics on how to get started with Ansible
Install Ansible on your workstation
-
Install ssh key pair on your
workstation
andservers
- Generate ssh key-pair
# create a private key and a .pub public key ssh-keygen -t ed25519 -C "description" # follow the on-screen instructions to generate the ssh-key
- Add the generated ssh-keys to
known_hosts
ssh-add <keyname>
-
Dump/ copy
the public key to all the other servers to which the main ansible machine is going to talk to.
# comand to copy ssh public key from the main workstation to the server in order for the main server to talk to the target server ssh-copy-id -i ~/.ssh/<keyname>.pub <server-ip-or-hostname>
- List active keys:
ssh-add -L
- Remove an ssh-key:
ssh-add -D <keyname>
-
Keep the private key on the workstation that the Ansible will use to ssh to other servers
- Add all the
IP
orHostnames
of the server you want ansible to connect to and work with - Dynamic Inventory is also supported in Ansible
- For Dynamic just get the server info from the cloud provider.
- In order to check the connection to all the servers from the main where ansible is installed
# create a file `inventory`
# this contains server hostnames and ips
10.0.0.136
pihole
# this will create a connection and check if we can connect to our servers
# here the path till ssh private is given and the key name is `ansible` (private key)
# inventory contains all the server ips to which using this key the ansible workstation is going to connect.
# -i is for the inventory file
# -m is for the module
ansible all --key-file ~/.ssh/<keyname> -i inventory -m ping
# To list all the hosts
ansible all --list-hosts
# Lists all the data/information of all the servers
# m is the module flag
ansible all -m gather_facts
# Lists all the data of a specific server
ansible all -m gather_facts --limit <server-ip-or-hostname>
The ansible.cfg
file can be used to setup some default values when running the ansible command.
NOTE: There is a default
ansible.cfg
file in/etc/ansible/
, but the one we create in our working dir will take priority over the one defined in/etc/ansible
.
# this file is read by ansible when we run it
[defaults]
inventory = <filename>
private_key_file = ~/.ssh/<filename>
This will help us shorten our ansible commands and avoid passing long command line options.
# previous command:
# ansible all --key-file ~/.ssh/<filename> -i inventory -m ping
# is shortened to:
ansible all -m ping
Output:
pihole | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"ping": "pong"
}
- Some commands which require sudo privileges to work with.
- Elevated privileges can be achieved using the below syntax.
# Tell ansible to use sudo (become) -> asks for password!
ansible all -m apt -a update_cache=true --become --ask-become-pass
# -m apt is for ubuntu/debian distros
Output:
BECOME password:
pihole | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"cache_update_time": 1695323800,
"cache_updated": true,
"changed": true
}
# Install a package named tmux via the apt module
# Equivalent to sudo apt-get install tmux
ansible all -m apt -a name=tmux --become --ask-become-pass
To check what command was run by ansible on all your servers, login to any one server, navigate to
/var/log/apt/history.log
file.
# Install a package via the apt module, and also make sure it’s the latest version available
ansible all -m apt -a "name=snapd state=latest" --become --ask-become-pass
# Upgrade all the package updates that are available
ansible all -m apt -a upgrade=dist --become --ask-become-pass
Learn more about apt modules here: ansible.builtin.apt module
- Before Ansible runs any playbooks or before running any commands on servers it will
First Gather Facts
and do the further things. - To disable fact gathering for a play, set the
gather_facts
key to no - Follow a proper directory structure for defining an ansible playbook. Ref
# create the playbook
---
- hosts: all
become: true
tasks:
- name: "install the apache2 package"
apt:
name: apache2
# Run the playbook
ansible-playbook --ask-become-pass install_apache.yml
- Lets say you 3 servers one for front end, second one contains some business logic as in REST API, and last one is a DB server
- So, we would have to target specific servers for specific configurations
- Need to create groups in inventory file, ex: one for
db_servers
,file_servers
,web_servers
# inventory_group file
[web_servers]
172.16.250.132
172.16.250.248
[db_servers]
172.16.250.133
[file_servers]
172.16.250.134
[workstations]
172.16.250.135
---
# target specific nodes
# ansible-playbook --ask-become-pass site.yml
# runs on all servers
- hosts: all
become: true
tasks:
# `pre_tasks:` can be used with roles when we want to mandate some things to run before others
- name: install updates (CentOS)
dnf:
update_only: true
update_cache: true
when: ansible_distribution = "CentOS"
- name: install updates (Ubuntu)
apt:
upgrade: dist
update_cache: true
when: ansible_distribution == "Ubuntu"
# runs only on web_servers defined in the `inventory` file
- hosts: web_servers
become: true
tasks:
- name: "install the apache2 and php package for Ubuntu"
apt:
name:
- apache2
- libapache2-mod-php
state: latest
when: ansible_distribution == "Ubuntu"
- name: "install the apache and php package for CentOS"
dnf:
name:
- httpd
- php
state: latest
when: ansible_distribution == "CentOS"
# runs only on db_servers defined in the `inventory` file
- hosts: db_servers
become: true
tasks:
- name: install mariadb package (CentOS)
dnf:
name: mariadb
state: latest
when: ansible_distribution == "CentOS"
- name: install mariadb package (Ubuntu)
apt:
name: mariadb-server
state: latest
when: ansible_distribution == "Ubuntu"
# runs only on file_servers defined in the `inventory` file
- hosts: file_servers
become: true
tasks:
- name: install samba package
package:
name: samba
state: latest
- To include multiple distros in a single task:
---
tasks:
- name: "update repository index"
apt:
update_cache: true
when: ansible_distribution in ["Debian", "Ubuntu"]
We can make the above playbook even more modular and use variables in places for an easier configuration.
- Variables in the inventory
# inventory
172.16.250.132 apache_package=apache2 php_package=libapache2-mod-php
172.16.250.248 apache_package=apache2 php_package=libapache2-mod-php
172.16.250.133 apache_package=apache2 php_package=libapache2-mod-php
172.16.250.134 apache_package=httpd php_package=php
---
# ansible-playbook --ask-become-pass install_apache.yml
- hosts: all
become: true
tasks:
# Ubuntu server setup
- name: "install the apache2 and php package for Ubuntu"
apt:
name:
- "{{ apache_package }}"
- "{{ php_package }}"
state: latest
update_cache: true
when: ansible_distribution == "Ubuntu"
# CentOS server setup
- name: "install the apache and php package for CentOS"
dnf:
name:
- "{{ apache_package }}"
- "{{ php_package }}"
state: latest
update_cache: true
when: ansible_distribution == "CentOS"
We can further shorten our yaml
file to a single play by defining a single package
instead of 2 different plays for apt
and dnf
, for different distros.
- Using
package
Package is a module in ansible that is a generic package manager and it automatically uses the underlying package manager for the hosts or servers.
---
# ansible-playbook --ask-become-pass install_apache.yml
- hosts: all
become: true
tasks:
# Ubuntu server setup
- name: "install the apache and php"
package:
name:
- "{{ apache_package }}"
- "{{ php_package }}"
state: latest
update_cache: true
NOTE: It can be argued that
package
may or may not be the best way to handle multi-distribution linux shop since we are hard-coding the package names. But this again is scenario and admin based.
- Used to run
SPECIFIC TASK
in the playbook instead of running all the tasks for testing just one change in one task. - So, we dont need to run all the tasks just for one change in other task.
- We
Tag
all the tasks and then based on that tag we can run that particular task and skip all the other tasks.
---
# target specific nodes
# ansible-playbook --ask-become-pass site.yml
# runs on all servers
- hosts: all
become: true
tasks:
# `pre_tasks:` can be used with roles when we want to mandate some things to run before others
- name: install updates (CentOS)
tags: always
# dnf:
# update_only: true
# update_cache: true
# when: ansible_distribution = "CentOS"
- name: install updates (Ubuntu)
tags: always
# apt:
# upgrade: dist
# update_cache: true
# when: ansible_distribution == "Ubuntu"
# runs only on web_servers defined in the `inventory` file
- hosts: web_servers
become: true
tasks:
- name: "install the apache2 and php package for Ubuntu"
tags: apache,apache2, ubuntu
# apt:
# name:
# - apache2
# - libapache2-mod-php
# state: latest
# when: ansible_distribution == "Ubuntu"
- name: "install the apache and php package for CentOS"
tags: apache,centos,https
# dnf:
# name:
# - httpd
# - php
# state: latest
# when: ansible_distribution == "CentOS"
# runs only on db_servers defined in the `inventory` file
- hosts: db_servers
become: true
tasks:
- name: install mariadb package (CentOS)
tags: centos,mariadb
# dnf:
# name: mariadb
# state: latest
# when: ansible_distribution == "CentOS"
- name: install mariadb package (Ubuntu)
tags: ubuntu,db,mariadb
# apt:
# name: mariadb-server
# state: latest
# when: ansible_distribution == "Ubuntu"
# runs only on file_servers defined in the `inventory` file
- hosts: file_servers
become: true
tasks:
- name: install samba package
tags: samba
# package:
# name: samba
# state: latest
1. List all tags in a playbook
# List the available tags in a playbook
ansible-playbook --list-tags site_with_tags.yml
2. Run a playbook with a specific tag
# Examples of running a playbook but targeting specific tags
ansible-playbook --tags db --ask-become-pass site_with_tags.yml
ansible-playbook --tags centos --ask-become-pass site_with_tags.yml
ansible-playbook --tags apache --ask-become-pass site_with_tags.yml
3. Run a playbook with multiple tags
# target multiple tags
ansible-playbook --tags "apache,db" --ask-become-pass site_with_tags.yml
- You can copy files from Local System to all the other servers
- We can also download binaries like Terraform, Unzip, etc on a workstation locally or on other servers.
1. Example of copying file from local to all the web servers
# Example of copying file from local to all the web servers
- hosts: web_servers
become: true
tasks:
- name: copy html file for site
tags: apache,apache,apache2,httpd
copy:
src: default_site.html
dest: /var/www/html/index.html
owner: root
group: root
mode: 0644
2. Example of downloading binaries in local workstation
# Example of downloading binaries in local workstation
- hosts: workstations
become: true
tasks:
- 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 # download link address
dest: /usr/local/bin # binary will be downloaded here
remote_src: yes # notify ansible that this package is from the internet and not a package manager
mode: 0755
owner: root
group: root
3. Run the playbook
ansible-playbook --ask-become-pass file_management.yml
- It is a module that can be used to start, stop, restart, or reload services on remote hosts. This is useful for managing the state of services, especially when deploying updates or changes.
- This is not the best approach, a better approach is using
handlers
# changes related to start httpd automatically and
# enabling it in case the server goes down to restart httpd automatically
- name: start and enable httpd (CentOS)
tags: apache,centos,httpd
service: # module to start/stop/restart/enable a service
name: httpd
state: started
enabled: yes
when: ansible_distribution == "CentOS"
# If the configuration file is changed and needs a restart so this is how it can be done
- name: change e-mail address for admin
tags: apache,centos,httpd
lineinfile: #module to be used to change a line
path: /etc/httpd/conf/httpd.conf # path to the file which is to be changed
regexp: '^ServerAdmin' # line begin with ServerAdmin
line: ServerAdmin [email protected] #replace that with this line
when: ansible_distribution == "CentOS"
register: httpd # variable declare that stores the state of httpd whether its changed or not
- name: restart httpd (CentOS)
tags: apache,centos,httpd
service:
name: httpd
state: restarted
when: httpd.changed # this play check if changed then run this which will restart the server
- Registered Variables -> One can capture the output of a command by using the register statement.
- The output is saved into a variable that could be used later for either debugging purposes or in order to achieve something else, such as a particular configuration based on a command's output.
1. Adding users
- Create users and provide them privileges using ansible with the
user
module. - We can also add ssh-keys for these users to the servers using ansible module
authorized_keys
.
- hosts: all
become: true
tasks:
- name: create user simone
tags: always
user:
name: simone
groups: root
# ansible ssh public key
- name: add ssh-key for simone
tags: always
authorized_keys:
user: simone
key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQChspJkMu9vZ2BmL3X2R8hVBHzT1BXZklUTPwmd8vq5DrpX4SZ9wTymwPm0EoeS2Fp6KDyAAKY88p7m6SRSpC0/KJtBQK4JVjpVTnWI3DYATb2amkomJpicAsc2Hbb+EBkDgF7rznQo7VLmQ3nSNG72Qm1oL5/WU3fjroiIFXH09zf3Cv0FCdIuuwnfx6KIFA4OxT2a1Yh5XdEGCAR3ucmAjSNdsHvzjMsYjpiWbK9vMzmAp2P7Z9v/9xVVeCUzMick8JvOGBHpt3DwHP7i88/X5Ym/f1aZxF19LKdK12p1/P9qD5f0TkdTQI2nVcUUPPMghwqqs86kFmGL0rPBrAD/WjI1i53xcY+VxgRdcPUjZKbHbJE/wM8v8Gz7VvqmvYm601nHgKmeYekZ0DWJw5e1xucfYLgygnWDv24MSBCMsGIMFfRX0b+IOKH+YPoXWsKU9ZDyyBN4C38kHjgFHcJ8zQA0XlBMv7JilNxvIWHUKQlZlvXnDnhl3Zil+oTB/0s= [email protected]"
# allow sudo without password
- name: add sudoers file for simone
tags: always
copy:
src: sudoer_simone
dest: /etc/sudoers.d/simone
owner: root
group: root
- Add the
sudoer_simone
file inside the./files
dir.
simone ALL=(ALL) NOPASSWD: ALL
NOTE: we can remove the
--ask-become-pass
cmd line parameter by updating ouransivle.cfg
file.
- Update the
ansible.cfg
file to remove--ask-become-pass
cmd line parameter.
# this file is read by ansible whenever we run it
[defaults]
inventory = inventory
private_key_file = ~/.ssh/raspberrypi
remote_user = simone
NOTE: There are a couple of more ways to use the
authorized_keys
module.
- name: Set authorized key taken from file
ansible.posix.authorized_key:
user: charlie
state: present
key: "{{ lookup('file', '/home/sid/.ssh/raspberrypi.pub') }}"
- name: Set authorized keys taken from url
ansible.posix.authorized_key:
user: charlie
state: present
key: https://github.com/sid.keys
2. Bootstrapping
- We can now bootstrap our servers with package updates and setting up users before we configure the various servers, i.e.,
web_server
,db_server
andfile_server
. - This helps us setup servers with the default users and elevated permissions required on all the servers.
---
# ansible-playbook --ask-become-pass bootstrap.yml
# runs on all servers
- hosts: all
become: true
tasks:
# `pre_tasks:` can be used with roles when we want to mandate some things to run before others
- name: install updates (CentOS)
tags: always
dnf:
update_only: true
update_cache: true
when: ansible_distribution = "CentOS"
- name: install updates (Ubuntu)
tags: always
apt:
upgrade: dist
update_cache: true
when: ansible_distribution == "Ubuntu"
- hosts: all
become: true
tasks:
- name: create user simone
tags: always
user:
name: simone
groups: root
- name: add ssh-key for simone
tags: always
authorized_keys:
user: simone
# ansible ssh public key
key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQChspJkMu9vZ2BmL3X2R8hVBHzT1BXZklUTPwmd8vq5DrpX4SZ9wTymwPm0EoeS2Fp6KDyAAKY88p7m6SRSpC0/KJtBQK4JVjpVTnWI3DYATb2amkomJpicAsc2Hbb+EBkDgF7rznQo7VLmQ3nSNG72Qm1oL5/WU3fjroiIFXH09zf3Cv0FCdIuuwnfx6KIFA4OxT2a1Yh5XdEGCAR3ucmAjSNdsHvzjMsYjpiWbK9vMzmAp2P7Z9v/9xVVeCUzMick8JvOGBHpt3DwHP7i88/X5Ym/f1aZxF19LKdK12p1/P9qD5f0TkdTQI2nVcUUPPMghwqqs86kFmGL0rPBrAD/WjI1i53xcY+VxgRdcPUjZKbHbJE/wM8v8Gz7VvqmvYm601nHgKmeYekZ0DWJw5e1xucfYLgygnWDv24MSBCMsGIMFfRX0b+IOKH+YPoXWsKU9ZDyyBN4C38kHjgFHcJ8zQA0XlBMv7JilNxvIWHUKQlZlvXnDnhl3Zil+oTB/0s= [email protected]"
# allow sudo without password
- name: add sudoers file for simone
tags: always
copy:
src: sudoer_simone
dest: /etc/sudoers.d/simone
owner: root
group: root
mode: 0440
# site.yml -> updating the repository index
# update_only option in the package manager line (dnf/apt) -> now only updates repo cache
apt:
- update_only: true
update_cache: true
dnf:
- update_only: true
update_cache: true
# changed_when is a new ansible module
+ changed_when: false
- Once we configure the
bootstrap.yml
andsite.yml
files, we can then bootstrap our servers and run thesite.yml
playbook.
ansible-playbook --ask-become-pass bootstrap.yml # ``ask-become-pass required for new servers setup
ansible-playbook site.yml
- Create Roles basically an Ansible PlayBook only
- Add all the plays(tasks) in that particular role
- For example, you can add some Bootstrapping scripts as Plays like an apt-get update
- These types of roles can be used to set up a server which has nothing on it.
- So, this can actually download all the latest packages required by the system.
We need to create a new dir called roles
in the base repository and sub-folders within it according to the name of the roles we want.
- Each of these sub-directories for roles will have another directory called
tasks
. - The tasks folder will contain the playbook for the tasks associated with that role. These "task books" are where we define the actual task associated with the specific servers.
- We will name these "task books" as
main.yml
.
# create roles dir
mkdir -p roles && cd roles/
# create sub-dirs for each role
mkdir -p base workstations web_servers db_servers file_servers
# tasks dirs within each role sub-dir
mkdir -p base/tasks workstations/tasks web_servers/tasks db_servers/tasks file_servers/tasks
# creating the taskbooks
touch base/tasks/main.yml workstations/tasks/main.yml web_servers/tasks/main.yml db_servers/tasks/main.yml file_servers/tasks/main.yml
NOTE: Any role using files should also have a
files/
dir to store the files for that role (within therole
dir, alongside with thetasks
dir)
Refer the site.yml
and roles/
for detailed code.
- These help us generalize our playbooks and have more control, plus it is easier to read and manage longer playbooks.
- These variables co-relate to the variables defined in this section.
- Create a new dir in the root folder called
host_vars/
. - Create files for each host here with their hostname or ip-addresses or dns-name (in the inventory file).
mkdir -p host_vars && cd host_vars
touch 172.16.250.132.yml 172.16.250.133.yml 172.16.250.134.yml 172.16.250.135.yml 172.16.250.248.yml
# 172.16.250.132.yml Ubuntu server
apache_package_name: apache2
apache_service: apache2
php_package_name: libapache2-mod-php
# 172.16.250.132.yml CentOS server
apache_package_name: httpd
apache_service: httpd
php_package_name: php
# ... for other servers as well
We can now use these variables within our roles
for each "taskbook".
- A handler is
triggered
and any one of the plays in a playbook and taskbook can trigger that change. - Previous approach is not the best method to handle/register changes using ansible.
- We can use handlers to restart a service if the registered variables register a change.
- They are executed only once.
- They notify ansible and execute at the very end.
- Registers cannot handle multiple changes as efficiently as handlers do, hence it is best practice to use handlers.
We need to create a handlers
dir in the role we need to register changes and notify ansible about it and create a file called main.yml
inside it.
cd roles/web_service && mkdir -p handlers && cd handlers/
touch main.yml
# handlers/main.yml
- name: restart_apache
service:
name: "{{ apache_service }}"
state: restarted
- registered: apache
+ notify: restart_apache # notify a play called `restart_apache`
- - name: restart httpd (CentOS)
- tags: apache,centos,httpd
- service:
- name: "{{ apache_service }}"
- state: restarted
- when: apache.changed
_ Create ssh templates to configure the ssh service. The ssh config file is present at /etc/ssh/sshd_config
in most of the linux distros.
- The config may vary according to distros, but the location of the config file remains the same.
#since we want this config for all servers
cd roles/base
mkdir -p templates && cd templates
# copy default sshd_config in Jinja2 template
cp /etc/ssh/sshd_config sshd_config_ubuntu.j2
- In order to copy this file from a server:
scp 172.16.250.248:/etc/sshd/sshd_config sshd_config_centos.js
# in case the above cmd says permission denied, ssh into the server and type:
chmod a+r /etc/ssh/sshd_config
# to revert the permissions:
chmod o-r /etc/ssh/sshd_config
-
Add the following line to the ubuntu and centos jinja2 templates:
AllowUsers {{ ssh_users }}
-
Next, we need to update the host_vars files for each server.
# for ubuntu servers
+ ssh_users: "sid simone"
+ ssh_template_file: "sshd_config_ubuntu.js"
# for centos servers
+ ssh_users: "sid simone"
+ ssh_template_file: "sshd_config_centos.js"
- Finally, we will add a play to the
main.yml
in thebase
role:
- name: generate ssh_config from template
tags: ssh
template:
src: "{{ ssh_template_file }}"
dest: /etc/ssh/sshd_config
owner: root
group: root
mode: 0644
notify: restart_sshd
# create handlers in base role
cd roles/base && mkdir -p handlers && cd handlers && touch main.yml
- Add
restart_sshd
handler for thebase
role:
- name: restart_sshd
service:
name: sshd
state: restarted
- [WIP]
- Ansible’s
check
mode allows you to execute a playbook without applyingANY ALTERATIONS
to your systems. - You can use check mode to test playbooks before implementing them in a production environment.
-
Check
mode offers a safe and practical approach to examine the functionality of your playbooks without risking unintended changes to your systems. Moreover, it is a valuable tool for troubleshooting playbooks that are not functioning as expected.
ansible-playbook --check playbook.yaml
Subdirectories:
mkdir -p defaults files handlers meta tasks templates tests vars
NOTE: anything that is not a
Jinja2
template goes into thefiles
folder.
Use the following command to run an asible playbook with custom (local) variables:
# key_state: absent/present
ansible-playbook import-ssh-key.yml -e @vars.yml -vvvv