Ansible Freebsd Linux
Ansible Freebsd Linux
Table of Contents
CentOS 8
If you want to install Ansible on the control node, you have to install Python 3.x first.
1 ls -l /usr/local/bin/python*
If your output is for example python37, install the same pip version.
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 [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 [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 remote_user=<user>
We can target only the group linux which consists of Linux hosts only in the inventory file.
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
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
# 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 # 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
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 ---
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 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 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