0% found this document useful (0 votes)
9 views

Ansible Freebsd Linux

Uploaded by

Maciej Krakowski
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
9 views

Ansible Freebsd Linux

Uploaded by

Maciej Krakowski
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 13

Ansible: Quick Start Guide for FreeBSD,

CentOS and Ubuntu


by Kliment Andreev February 8, 2021 5.3K views

Post Views: 6,147


In this post/howto, I’ll explain how to install Ansible as control and managed node on FreeBSD
12, CentOS 8 and Ubuntu 18. Then, I’ll explain how to create SSH keys so the nodes can
communicate and some basic tasks. Then, I’ll show you how to create a playbook to install the
latest updates and also install an Apache server with the default settings. Finally, I’ll show an
example of how to use variables.

Table of Contents

 Control and managed nodes


o CentOS 8
o Ubuntu
o FreeBSD
 SSH keys
 Inventory and the config file
 Playbooks
 Service handlers
 Variables
 Roles
 Error handling
 Tags
 Ansible Vault
 Prompts
 Useful options

Control and managed nodes


A control node is where you execute all of your Ansible commands and eventually keep your
playbooks, configs, inventory etc. It’s pretty much your workstation. A managed node is where the
actual playbooks are executed. These are the main concepts and the terminology.
In my case, I have 4 VMs/instances. The main one which is the control node and 3 managed
nodes.

CentOS 8
If you want to install Ansible on the control node, you have to install Python 3.x first.

1 sudo dnf install python3


This will also install pip. Type python3 to test, CTRL-D to exit and then install ansible.

1 sudo pip3 install ansible


Type ansible to test.
Ubuntu
Ubuntu comes with python installed, but not with pip. Install pip with:

1 sudo apt install python3-pip


Then install ansible with:

1 sudo pip3 install ansible


Type ansible to test.
FreeBSD
FreeBSD comes with python installed but pip is not. Check the version and then install the same
pip version.

1 ls -l /usr/local/bin/python*
If your output is for example python37, install the same pip version.

1 pkg install py37-pip


Then install ansible.

1 pip install ansible


Type ansible to test.
SSH keys
While Ansible can use standard *nix username/password authentication, it’s recommended that
you use SSH keys to communicate from control node to the managed nodes. For that, you’ll have
to create your SSH keys. Let’s say you have an account on your control node and the username
is admin. You also want to use the user ansible on the managed nodes. It really doesn’t matter
what usernames you are going to choose. You can always override the keys to use, but in this
case, I’ll create a key on the control node and send it to all managed nodes.
On the control node, regardless of your OS, do:

1 ssh-keygen -b 4096
This will create a subfolder .ssh with two files: id_rsa and id_rsa.pub. The former is your private
key and the later is your public key.
Copy the key to your managed nodes.

1 ssh-copy-id [email protected]
This will copy my key for the user admin to the node X under the ansible user. Then test the
passwordless connection.

1 ssh [email protected]
Mind that the use of FQDN (nodex.andreev.local) vs. hostname (nodex) is important. For SSH
these two are different. Once you log to the managed node, the node will be added to the list of
known hosts in the file .ssh/known_hosts.
Inventory and the config file
Ansible uses the inventory files to execute an action against using the options and parameters
specified in the config file. The config file is /etc/ansible/ansible.cfg for CentOS and Ubuntu
and /usr/local/etc/ansible/ansible.cfg for FreeBSD. You can also put the inventory file in the
same directory and name it as you wish, but you have to specify the inventory as a parameter on
the command line or an entry in the config file. In addition, you can have your config file in your
current directory or under the .ansible directory in your home folder. ansible.cfg in the current
directory has a precedence over .ansible.cfg in the home directory which has a precedence
over /etc/ansible/ansible.cfg. Here is how that looks.
Let’s list all the inventory.

1 ansible --list-hosts all


You’ll get a message that there is no inventory file. Let’s create one, we’ll name it inventory.txt.

1 [freebsd]
node1.andreev.local
2
[centos]
3
4 node2.andreev.local
5 [ubuntu]
6 node3.andreev.local
[bsd]
7 node1.andreev.local
8 [linux]
9 node2.andreev.local
10 node3.andreev.local
11
If we specify the inventory file, we’ll get this.

1 ansible --list-hosts all -i inventory.txt


2 hosts (3):
3 node1.andreev.local
4 node2.andreev.local
node3.andreev.local
5
If we create a config file, we can tell ansible where to look for the inventory. Create a
file ansible.cfg in the same directory.

1 [defaults]
2 inventory=/home/<somewhere>/inventory.txt
If you do ansible –list-hosts all now, you’ll get the same result as before, but without specifying
the inventory file.
Or something like this.

1 ansible all -m shell -a "uname -a"


2 node1.andreev.local | CHANGED | rc=0 >>
3 FreeBSD node1.andreev.local 12.1-RELEASE FreeBSD 12.1-RELEASE r354233 GENERIC amd64
4 node2.andreev.local | CHANGED | rc=0 >>
5 Linux node2.andreev.local 4.18.0-193.14.2.el8_2.x86_64 #1 SMP Sun Jul 26 03:54:29 UT
node3.andreev.local | CHANGED | rc=0 >>
6 Linux node3.andreev.local 4.15.0-129-generic #132-Ubuntu SMP Thu Dec 10 14:02:26 UTC
7
By default, ansible runs on the managed node with the currently logged user that executes the
playbook on the control node. If you get an error saying that the previous command cannot
connect to the host, you have to specify the same user that you used when you test the
connection with ssh -user-@managednode. So, edit ansible.cfg and add this line.

1 remote_user=<user>
We can target only the group linux which consists of Linux hosts only in the inventory file.

1 ansible linux -m shell -a "date"


2 node3.andreev.local | CHANGED | rc=0 >>
3 Fri Jan 8 15:23:40 UTC 2021
4 node2.andreev.local | CHANGED | rc=0 >>
Fri Jan 8 10:23:40 EST 2021
5
Playbooks
The playbooks are the blueprints of the automation tasks. Instead of running the ansible
command to execute each task separately, we combine these tasks in a YAML file and execute
them sequentially. Here are 3 playbooks that update each of our managed nodes. There is no
update module for FreeBSD, so we use the shell command.

1 # freebsd-update.yml
---
2
- hosts: freebsd
3 become: yes
4
5 tasks:
6 - name: Fetch all packages
shell: freebsd-update fetch
7 - name: Install FreeBSD updates
8 shell: freebsd-update install
9 - name: Reboot
10 reboot:
11
For CentOS we’ll use yum.

1
# centos-update.yml
2 ---
3 - hosts: centos
4 become: yes
5 tasks:
6 - name: Update all packages
yum: name=* state=latest
7 - name: Reboot
8 reboot:
9
…and for Ubuntu we’ll use apt.

1
# ubuntu-update.yml
2 ---
3 - hosts: ubuntu
4 become: yes
5 tasks:
6 - name: Update all packages
apt: name=* state=latest
7 - name: Reboot
8 reboot:
9
Save these files with a YAML extension and you can execute them with the following command.

1 ansible-playbook <filename>
All of them will probably fail. That’s because your ansible user on the managed nodes is required
a password when executing a sudo command. In order to fix that, you’ll have to add a line in the
sudoers file. Edit this file using the visudo command.

1 visudo
…and then add this line right before the @includedir <...> which is the last line in the file.

1 ansible ALL=(ALL) NOPASSWD:ALL


Where ansible is the user that runs the playbooks on the managed nodes. FreeBSD doesn’t
come with sudo preinstalled, so you’ll have to install it first on the managed node.

1 pkg install sudo


These playbooks will update the OS and the packages for the Linux. For FreeBSD, it will update
only the OS. Here is another example of playbooks that will install Apache server in a default
configuration and change the ServerName and ServerAdmin lines. We’ll also install PHP and
test our server.
If you have a firewall enabled, make sure you open it up first on CentOS. Ubuntu and FreeBSD
do not come with the firewall enabled.

1 firewall-cmd --zone=public --permanent --add-service=http


2 firewall-cmd --reload
For FreeBSD, the playbook looks like this.
1
2
3 # freebsd-apache.yml
4 ---
5 - hosts: freebsd
become: yes
6 tasks:
7 - name: Install apache and php
8 pkgng:
9 name:
- apache24
10 - php74
11 - mod_php74
12 state: present
13 - name: Start on reboot
14 service: name=apache24 enabled=yes
15 - name: Copy index.php
copy:
16 src: ../files/index.php
17 dest: /usr/local/www/apache24/data
18 mode: 0755
19 - name: Copy mod_php.conf
20 copy:
src: ../files/mod_php.conf
21 dest: /usr/local/etc/apache24/modules.d
22 mode: 0755
23 - name: Start apache now
24 service: name=apache24 state=started
25
26
For CentOS, it looks like this.

1
2 # centos-apache.yml
3 ---
4 - hosts: centos
5 become: yes
tasks:
6 - name: Install apache and php
7 yum:
8 name:
9 - httpd
10 - php
11 state: present
- name: Start apache now and on reboot
12 service: name=httpd state=started enabled=yes
13 - name: Copy index.php
14 copy:
15 src: ../files/index.php
16 dest: /var/www/html
mode: 0755
17
18
…and for Ubuntu it looks like this.

1 # ubuntu-apache.yml
---
2
- hosts: ubuntu
3 become: yes
4
5 tasks:
6 - name: Install apache and php
7 apt:
8 name:
9 - apache2
- php
10
state: present
11 - name: Start apache now and on reboot
12 service: name=apache2 state=started enabled=yes
13 - name: Copy index.php
14 copy:
15 src: ../files/index.php
dest: /var/www/html
16 mode: 0755
17
18
You will also need these two files in a directory called files. In my case it’s one level above the
directory where I keep my playbooks.
index.php

1 <?php
2 phpinfo();
3 ?>
001_mod-php.conf

1
<IfModule dir_module>
2 DirectoryIndex index.php index.html
3 <FilesMatch "\.php$">
4 SetHandler application/x-httpd-php
5 </FilesMatch>
6 <FilesMatch "\.phps$">
SetHandler application/x-httpd-php-source
7 </FilesMatch>
8 </IfModule>
9
The index.php file is the standard test file to test the PHP distributions and the 001_mod-
php.conf is needed for FreeBSD only. As you can see the playbooks differ quite a bit for these
three OSes. Once you deploy the playbooks, you can test the result by going
to http://[nodeX]/index.php.
Looks like this.

Service handlers
Service handlers are used only when a change is made on the managed node. For example, we
can restart a service only if the config file was changed. If the file is not changed, then there is no
need to restart. Here is an example of a service handler. We’ll restart postfix service only if the
main.cf file was changed.

1
2 # service-handler.yml
3 ---
4 - hosts: centos
become: yes
5 tasks:
6 - name: Configure main.cf
7 lineinfile:
8 path: /etc/postfix/main.cf
regexp: ^#mydomain
9
line: 'mydomain = example.com'
10 notify: restart postfix
11
12 handlers:
13 - name: restart postfix
14 service: name=postfix state=restarted
15
Make sure that the name for the handler is the same (lines 11 and 14), so Ansible knows what
service handlers is referred.

Variables
When ansible runs a playbook on a managed node, the first task is to gather info about the
managed node. The info is a bunch of settings that we can use in our playbooks. For example, if
you execute the following command, you can see the IP address, the CPU model, python version
etc.

1 ansible -m setup <node>


We can use these settings and use them as variables if we need them. For example, this
playbook displays the hostname and the IP.

1
# showip.yml
2 ---
3 - hosts: freebsd
4 become: yes
5 tasks:
- name: Show the IP address
6
debug:
7 msg: "The hostname is {{ inventory_hostname}} and the IP is {{ansible_def
8
And if you run the playbook, you’ll see something like this.

1 TASK [Show the IP address] *********************************************************


2 ok: [node1.andreev.local] => {
3 "msg": "The hostname is node1.andreev.local and the IP is 192.168.1.211"
}
4
Here is another example of using variables. In this case, we’ll specify a file and change the
ownership and the mode.

1 # owner.yml
---
2
- hosts: centos
3 become: yes
4 vars:
5
6 filename: "/var/www/html/index.php"
7 tasks:
8 - name: Change the owner of the file
file:
9 path: "{{ filename }}"
10 owner: apache
11 group: apache
12 mode: '0755'
13
In case we want to assign an output to a variable, we’ll have to use the keyword register. Here is
an example of how to get the output from a command and print it on the screen with the
keyword debug.

1
2 # variables.yml
---
3 - hosts: freebsd
4
5 tasks:
6 - name: Get the uptime manually
7 command: uptime
register: var_uptime
8
9
- name: Print the uptime
10 debug:
11 msg: The uptime is "{{ var_uptime }}"
12
Roles
Roles let you automatically load related vars_files, tasks, handlers, and other Ansible artifacts
based on a known file structure. Once you group your content in roles, you can easily reuse them
and share them with other users. The idea is to separate the tasks, handlers and vars in different
files. Let’s see this playbook for example. It changes a line in main.cf file, restarts postfix and
copies a file under the postfix main directory.

1 # roles.yml
---
2
- hosts: centos
3 become: yes
4
5 vars:
6 filevd: "/etc/postfix/virtual_domains"
7 cfgpostfix: "/etc/postfix/main.cf"
8
9
tasks:
10 - name: Configure main.cf
11 lineinfile:
12 path: "{{ cfgpostfix }}"
13 regexp: ^#mydomain
14 line: 'mydomain = example.com'
notify: restart postfix
15 - name: Copy virtual_domains
16 copy:
17 src: ../files/virtual_domains
18 dest: "{{ filevd }}"
mode: 0755
19
20
21
22
handlers:
23 - name: restart postfix
24 service: name=postfix state=restarted
25
26
We can rewrite this file by separating the tasks, variables, files and handlers. Run this command.

1 ansible-galaxy role init postfix


If you look at the file/directory structure of the newly created directory postfix, it looks like this.

1
2 tree postfix
3 postfix
4 ├── defaults
5 │ └── main.yml
6 ├── files
├── handlers
7 │ └── main.yml
8 ├── meta
9 │ └── main.yml
10 ├── README.md
├── tasks
11
│ └── main.yml
12 ├── templates
13 ├── tests
14 │ ├── inventory
15 │ └── test.yml
└── vars
16 └── main.yml
17
18
Create a file virtual_domains under the files directory.

1 echo "mydomain.com" > postfix/files/virtual_domains


Move the config for vars, handlers and tasks in the separate main.yml files. For example, this is
how my files look like.
postfix/vars/main.yml

1 ---
2 # vars file for postfix
3
4 filevd: "/etc/postfix/virtual_domains"
cfgpostfix: "/etc/postfix/main.cf"
5
As you can see the keyword vars: does not exists. Ansible knows that this file is for vars so there
is no need to enter the vars keyboard.
postfix/tasks/main.yml

1 ---
# tasks file for postfix
2
3
- name: Configure main.cf
4 lineinfile:
5 path: "{{ cfgpostfix }}"
6 regexp: ^#mydomain
7 line: 'mydomain = example.com'
notify: restart postfix
8 - name: Copy virtual_domains
9
10 copy:
11 src: ../files/virtual_domains
12 dest: "{{ filevd }}"
mode: 0755
13
14
postfix/handlers/main.yml

1 ---
2 # handlers file for postfix
3
4 - name: restart postfix
service: name=postfix state=restarted
5
Finally, create a file called something.yml that will be your main file. This file has to be outside the
postfix directory structure.
In my case it looks like this.
something.yml

1 # something.yml
2 ---
3 - hosts: centos
4 become: yes
roles:
5
- postfix
6
Now, if you execute this playbook, ansible will automatically execute the rest of the dependant
playbooks as well.

1 ansible-playbook something.yml

Error handling
Sometimes we want certain changes to be ignored. Sometimes, we know the behavior of certain
commands and we know that they might return non-zero code and we want that ignored. For
example, consider this part of a playbook.

1
- hosts: centos
2 tasks:
3 - name: Type something that will fail
4 command: thiscommanddoesntexist
5 ignore_errors: yes
6
7 - name: Run command remotely
command: /usr/local/bin/somecommand
8 register: cmd_result
9 changed_when: cmd_result == 2
10
Ansible would report a task as changed as long as the command (or) script gives zero return
code.
In the first part, we know that the task will fail, but we decide to ignore it using the
keyword ignore_errors. No matter what the command returns, ignore_errors: yes will never
report to ansible that the command failed.
In the second command we can ignore the error based on the output of the command. For
example, if the output is 2, the the error will be ignored. If cmd_result is not equal to 2, the task
will be marked as changed.
So whenever this condition is true, the task will be marked as changed.
Tags
Tags are used when you have a playbook with several tasks and you need to run only specific
parts of it instead of running the entire playbook. You use tags to execute or skip selected tasks.
Let’s say we have this playbook that installs Docker on Centos and has multiple tasks. As you
can notice in lines 12, 24, 37 and 45 we have a new line with a keyword tags that we use to tag
certain tasks. The purpose of this is to include or exclude these tasks from the playbook.

1
2
3
4 # centos-docker.yml
5 ---
6 - name: Install docker
7 hosts: centos
become: true
8
9 tasks:
10 - name: Install yum utils
11 yum:
12 name: yum-utils
13 state: latest
tags: install
14
15 - name: Install device-mapper-persistent-data
16 yum:
17 name: device-mapper-persistent-data
18 state: latest
tags: install
19
20 - name: Install lvm2
21 yum:
22 name: lvm2
23 state: latest
24 tags: install
25
- name: Add Docker repo
26 get_url:
27 url: https://download.docker.com/linux/centos/docker-ce.repo
28 dest: /etc/yum.repos.d/docer-ce.repo
29 become: yes
30
- name: Install Docker
31
package:
32 name: docker-ce
33 state: latest
34 become: yes
35 tags: install
36
- name: Start Docker service
37 service:
38 name: docker
39 state: started
40 enabled: yes
41 become: yes
tags: start
42
43
44
45
Now, with the command below, we can execute the playbook and only the tasks tagged
with install will be executed.

1 ansible-playbook centos-docker.yaml --tags install


We can also tell ansible to NOT run those tasks tagged with install.

1 ansible-playbook centos-docker.yaml --skip-tags install


You can add multiple tags per task, e.g.

1 tags:
2 - cleanup_app
3 - cleanup_web
Ansible reserves two tag names for special behavior: always and never. If you assign
the always tag to a task or play, Ansible will always run that task or play, unless you specifically
skip it (–skip-tags always). If you assign the never tag to a task or play, Ansible will skip that
task or play unless you specifically request it (–tags never).
Ansible Vault
Ansible Vault encrypts variables and files so you can protect sensitive content such as passwords
or keys rather than leaving it visible as plaintext in playbooks or roles.
First, you have to create a vaulted file where we’ll store the passwords. When you run this
command it will ask you to create a password and then an empty file will show up.

1 ansible-vault create secrets.yml


Add some passwords there and save the file.

1 mysql_pwd: "DifficultPassword"
2 ht_pwd: "PasswordXYZ"
If you look at the file now, you’ll see that it’s encrypted and you can’t see the passwords anymore.
If you want to edit the file do ansible-vault edit secrets.yaml and enter the vault password.
Create a small playbook that displays the password.

1
# centos-vault.yml
2 ---
3 - hosts: centos
4 vars_files:
5 - secrets.yml
6
7 tasks:
- name: Show mysql pwd
8 debug:
9 msg: "{{ mysql_pwd }}"
10
If you run the playbook now, ansible will throw an error saying ERROR! Attempting to decrypt but
no vault secrets found. You have to specify the parameter –ask-vault-pass and enter the vault
password when prompted.
Prompts
In case you need to pause the playbook execution and ask the user for some input such as
confirmation or password, use prompts.
If you want the output to echo, use private: no, otherwise what you type won’t show up on the
screen. Here is an example of a playbook that asks you to confirm if a file needs to be copied to
the node. If you type yes and hit enter, the file will be copied, otherwise it won’t.

1 # centos-prompt.yml
---
2
- hosts: centos
3 become: yes
4
5
6 vars_prompt:
7 name: upload
8 private: no
9 prompt: "Do you want to upload xyz.txt?"
10
tasks:
11 - name: Upload xyz.txt
12 copy:
13 src: xyz.txt
14 dest: /var/log
when: upload == "yes"
15
16
Useful options
1 ansible-playbook <name> --syntax-check # checks the syntax of the playbook
2 ansible-playbook <name> --check # does a dry run, reports the will-be changes, but t
3 ansible-playbook <name> --step # ask to confirm each-step

You might also like