Infrastructure As Code (Iac) Is The Way of Defining Computing and Network Infrastructure Through Source
Infrastructure As Code (Iac) Is The Way of Defining Computing and Network Infrastructure Through Source
Infrastructure As Code (Iac) Is The Way of Defining Computing and Network Infrastructure Through Source
code, the same way you do for applications. Rather than manually configuring your infrastructure or using an
one-off isolated script,
IaC gives you the power to write code, using a high-level language, to decide how infrastructure should be
configured and deployed. IaC is different from infrastructure automation, which involves repeating the steps
multiple times and
spawning them on several servers.
The guiding principle behind IaC is to enforce consistency among DevOps team members by representing
the desired state of their infrastructure via code. Moreover, the code can be kept in source control (version
control - SCM), which
means it can be audited, tested on, and used to create reproducible builds with continuous delivery.
• Ansible is agentless. Ansible doesn’t need any agents to be installed on remote systems to be
managed, which means less maintenance overhead and performance issues. Instead, Ansible uses a
push-based approach leveraging
existing ssh connections to run tasks on the remote managed hosts. Chef or Puppet work by installing
an agent on the hosts to be managed and the agent pulls changes from the control host using its own
channel.
• It’s written in Python. Ansible is written in Python, which means installing and running Ansible in any
Linux distribution is very easy, and only a little more difficult on OS X. Being a popular language,
there’s also a good chance that you
are familiar with it, or at least can find enough resources online to start learning. Or, you’ll easily be able
to find a developer with Python experience to help you out.
• Learn Ansible in minutes. The fact that a new user can get up to speed and run Ansible tasks in a
matter of minutes, thanks to clear and easy-to-follow documentation, is one of the most appealing
features of Ansible. Troubleshooting
in Ansible is also very easy for beginners, and the fact that all tasks are idempotent reduces the risk of
making a mistake.
Deploy infrastructure in record time. Ansible can dispatch tasks to multiple remote managed hosts in
parallel. This means you can execute Ansible tasks on a second remote managed host without having to
wait for them to complete
on the first one to reduce provision time and deploy your infrastructure faster than ever.
Ansible Inventory File
The Ansible inventory file lists which hosts will receive commands from the control host (ansible
management node). The inventory file can list individual hosts, or group them under categories you
distinguish. The default location of the
inventory file (the default inventory file) is /etc/ansible/hosts, but it’s also possible to change the location
of the inventory file by uncommenting the inventory parameter in /etc/ansible/ansible.cfg file
(Ansible configuration
file). Refer to the inventory parameter of this file. In most cases, you will have to create a custom
inventory file where you will list the remote hosts.
A typical inventory file can list the managed hosts either by IP addresses or by domain names. It is also
possible to list one managed host in more than one group. The groups are seperated with tags defined with
square brackets [ ].
Example of ansible inventory file:
[sdl_ops_instances]
10.40.13.144
10.40.13.145
10.40.13.146
[sdl_tele_instances]
10.40.13.152
10.40.13.153
[test_vnf_instances]
10.94.24.10
10.94.24.20
ansible --version
Command to get the installed ansible version, the ansible .cfg file, the employed Python version etc.
ansible --help
Command to print the ansible help message.
------------------------------------------------------------------------------------------------------------------------------------------------
-----
Ad-hoc commands
Ad-hoc commands in Ansible are merely those that perform a single command across one or many
managed hosts. They don’t use Ansible tasks but allow you to do a lot of things quite easily without building
out playbooks. The exact
above are simple examples of ansible ad-hoc commands.
The pattern in the ansible ad-hoc command is where we want run the command in the inventory file.
Patterns let you run commands and playbooks against specific hosts and/or groups in your inventory. An
Ansible pattern can refer to a
single host, an IP address, an inventory group, a set of groups, or all hosts in your inventory. Patterns are
highly flexible - you can exclude or require subsets of hosts, use wildcards or regular expressions, and
more.
Other example of ansible ad-hoc commands (the default module in Ansible ad-hoc commands is the
command module):
------------------------------------------------------------------------------------------------------------------------------------------------
-----
Ansible modules
Ansible Modules are the discrete units of code that can be used from the terminal (ad-hoc commands) or
in a playbook file. They simplify Ansible tasks by installing software, copying files, using templates, and so
on. Modules use the
available context to determine what actions - if any - needed to bring the managed host to the desired state
and are idempotent, that means if you run the same task again and again, the state of the managed host
will not change.
Lets use the most common ansible module, the shell in order to execute commands on the hosts group:
------------------------------------------------------------------------------------------------------------------------------------------------
-----
Tasks in ansible
When you dispatch a job from a control host (Ansible Management Node) to a managed host using an
Ansible module, it is known as a task. Tasks can be implemented using ad-hoc commands, as we’ve done
just above, or you can
use an Ansible playbook (more on those in a moment). One example of a task is copying a file from the
control host to a managed host, since it requires the use of copy module. There are thousands of modules
in Ansible, which means
a task can use any of the modules to bring a managed host to the desired state.
------------------------------------------------------------------------------------------------------------------------------------------------
-----
Play in Ansible
An Ansible play is a set of tasks that are run on one or more managed hosts. A play may include one or
many different tasks, and the most common way to execute a play is to use a playbook.
------------------------------------------------------------------------------------------------------------------------------------------------
-----
Ansible Playbooks
Ansible Playbooks are composed of one or more plays and offer more advanced functionality for sending
tasks to managed hosts compared to running many ad-hoc commands. The Ansible playbooks (containing
the plays) are written in
YAML, which is easier to understand than a JSON or XML file. Each task in the playbook is executed
sequentially for each host in the inventory file before moving on to the next task.
hosts: The group of hosts (as defined in the inventory file) where we will run the tasks described in this
playbook.
gather_facts: This option gathers information about managed hosts such as distribution, OS family, and
more. In ansible terminology, this information is known as facts.
become_user : Run the tasks as the specified user through this key.
tasks: The list of tasks to be executed.
name: The name of the task. We see that we have 2 tasks using respectively the yum and service
modules.
handlers: A handler is the same as a task, but it will be executed when called by another task. It is like an
event-driven system. A handler will run a task only when it is called by an event it listens for.
notify: This parameter takes as input a handler name to run after the execution of the task it belongs to,
------------------------------------------------------------------------------------------------------------------------------------------------
-----
Roles
In Ansible, a role provides a mechanism to break a complicated playbook into multiple reusable
components. Each component offers a small function that can be used independently within the playbook.
So rather than creating one complex
playbook, you can create many roles and simply drop them into your playbooks (a role is called by
playbook).
You can’t execute roles directly, the way you do a playbook, and you can’t specify which host you want to
execute a role, the way you would an ad-hoc command. Instead, they’re built into the playbooks you use to
define a host. The Ansible
Galaxy repository has thousands of pre-built roles for you to choose from, although you’re free to create
your role framework.
https://galaxy.ansible.com/
------------------------------------------------------------------------------------------------------------------------------------------------
-----
Variables
In Ansible, variables are similar to variables in any programming language - they let you input values and
numbers dynamically into your playbook. Variables simplify operations by allowing you define and declare
them throughout all the
various roles and tasks you want to perform. There are few places where you can define variables in an
Ansible playbook:
• Inside the playbook using the vars key.
• In the inventory file using the :vars syntax.
• In a separate variable file called inside the playbook using the vars_files key.
• Using group_vars directory of variable files.
To define variables in a playbook, use vars key just above the task where you want to use the variable.
Once declared, you can access its value using the {{ }} operator. Let’s declare a variable with the name
package_name and assign
to it the value of the package name that we want to install, which is nginx. Once done, we can use the
variable in a task:
---
- name: Simple ansible playbook
hosts: test_vnf_instances
gather_facts: yes
become_user: root
vars:
package_name: nginx
tasks:
- debug:
msg: "Install using yum"
- name: Install "{{package_name}}"
yum: name="{{package_name}}" state=present
notify:
- restart "{{package_name}}"
- name: Enable "{{package_name}}" during boot
service: name="{{package_name}}" state=started enabled=yes
handlers:
- name: restart "{{package_name}}"
service: name="{{package_name}}"state=restarted
In the above playbook example, the debug task is used to print a statement during execution and it is used
for debugging reasons.
It is also possible to declare a variable in the inventory file using the syntax [<host_group_name>:vars].
Let’s define the variable package_name in the inventory file:
[test_vnf_instances:vars]
package_name=nginx
Now the variable package_name can be used anywhere in the test_vnf_instances hosts section in the
playbook, so this variable has a specified scope.
You can also define variables in a separate variable file (.yml type) and import it into the playbook using a
dedicated key (vars_files). Create a variable file and define the variable(s) as key/value pairs:
---
package_name: nginx
This variable file can be called inside the playbook using the key vars_files which takes as value a list of
wanted imported variables files:
---
- hosts: testvnf_servers
...
vars_files:
- ./ansible_vars.yml
Another preferred way of managing variables is to create a group_vars directory inside your Ansible
working directory. Ansible will load any YAML files in this directory with their names matching to any Ansible
existing hosts group.
Create the directory group_vars in your Ansible working directory, and then inside it create a variable files
with its name matching a group name from the inventory file. In our example, this would be
test_vnf_instances.yml. This
allows you to separate variables according to host groups, which can make everything easier to manage:
Example of variables file content (same as the separate variable file imported in playbook with the
vars_files key):
---
package_name: nginx
You don’t need to declare the variable in your playbook, as Ansible will automatically pull the variables from
each group_vars file and will substitute them during runtime.
Now suppose you want to have variables that will apply to all the host groups mentioned in the inventory file.
To accomplish it, name a file by the name all inside group_vars directory. The group_vars/all.yml file
is used to set
ariables for every host that Ansible connects to.
------------------------------------------------------------------------------------------------------------------------------------------------
-----
Conditionals
In Ansible, conditionals are analogous to an if statement in any programming language. You use a
conditional when you want to execute a task based on certain conditions. In our last playbook example, we
installed nginx, so let’s extend
that by creating a task that installs nginx when httpd is not activated on the host. We can add another task
in the playbook we’ve already built:
tasks:
- name: Check if httpd is active
shell: systemctl status httpd
register: httpd_is_active
failed_when: no
- name: Install "{{ package_name }}"
yum: name="{{ package_name }}" state=present
when: httpd_is_active.rc == 3
notify:
- restart nginx
The first task (Check if httpd is active) in the above playbook checks if httpd is active using
systemctl status httpd command and stores the output of the task to httpd_is_active variable
using the register key. The
return value of the task will be a non-zero value if httpd is not activated on the host. Usually, Ansible would
stop executing other tasks because of this non-zero value, but the failed_when: no gives Ansible
permission to continue with the
next set of tasks when it encounters a non-zero value. The second task will install nginx only when the
return value rc is equal to 3, which is declared via when key:
when: httpd_is_active.rc == 3
------------------------------------------------------------------------------------------------------------------------------------------------
-----
Loops
All programming languages provide a way to iterate over data to perform some repetitive tasks. Ansible also
provides a way to do the same using a concept called looping, which is supplied by Ansible lookup plugins.
With loops, a single
task in one playbook can be used to create multiple users, install many packages, and more.
While there are many ways to use loops in Ansible, we’ll cover just one of them to get you started. The
easiest way to use loops in ansible is to use with_items keyword, which is used to iterate over an item list
to perform some repetitive
tasks. The following playbook includes a task which installs packages in a loop using the keyword
with_items:
The value of with_items key is a list of items to iterate over.
---
- hosts: testvnf_instances
gather_facts: yes
become_user: root
tasks:
- name: Installing packages using loops
apt: pkg={{ item }} state=present
with_items:
- nginx
- sysstat
------------------------------------------------------------------------------------------------------------------------------------------------
-----
Tags
Tags allow you to run only specific tasks from your playbook via the command line. Just add the tags
keyword in each task and run only the task(s) that you want by using --tags switch at the end of the
ansible-playbook command.
In the following playbook, we have added tags at the end of each task, thereby allowing us to run tasks
separately from a single playbook:
- hosts: dbservers
become_user: root
tasks:
- name: Install mysql
apt: pkg="{{ pkgname }}" state=present
tags:
- mytag2
------------------------------------------------------------------------------------------------------------------------------------------------
-----
Typically, after installing a web server like Nginx, you need to configure a virtual hosts file to properly serve a
given website on your server. Instead of using SSH to log into your server to configure it after running
Ansible, or using Ansible’s
copy module to copy many unique configuration files individually, you can take advantage of Ansible’s
templates features. A template file contains all of the configuration parameters you need, such as the
Nginx virtual host settings, and
uses variables, which are replaced by the appropriate values when the playbook is executed. Template files
usually end with the .j2 extension that denotes the Jinja2 templating engine. The concept behind
templates is that with templates
we can use variables inside.
To begin working with templates, create a directory for template files in your Ansible working directory -
suppose we name it as templates.
We will demonstrate an example with one template file. Under the directory we have created for storing the
templates, create the following template file with name index.html.j2:
<html>
You are visiting {{ domain_name }} !
</html>
As we can see, we have an ansible variable referenced in this file. This is the concept behind using template
files in ansible, we can modify their contents using variables. The above variable domain_name should be
stored under the
respective variable file (and referenced with the vars_files: key) or the host group variable file of the
group_vars directory:
---
package_name: nginx
domain_name: testvnf.sdl.athens
Finally, we edit the ansible playbook to create a root folder for sites, copy the index.html file to the site’s
root folder (this is done through a new task named template as we see):
---
- hosts: testvnf_instances
gather_facts: yes
become_user: root
tasks:
- name: Install "{{package_name}}"
apt: pkg="{{package_name}}" state=present
- name: Copying index file to webroot
template:
src: templates/index.html.j2
dest: /var/www/html/index.html
The template task (actually a module) in the above Ansible playbook takes two mandatory parameters src
and dest. There are also a few optional parameters that can be specified in a template task but is not
required at this stage:
• The src parameter specifies the name of the template file from templates directory that Ansible will
copy to the remote server.
• The dest parameter is the path in the remote server where the file should be placed.
------------------------------------------------------------------------------------------------------------------------------------------------
-----
Ansible Roles
Roles are a robust feature of Ansible that facilitate reuse and further promote modularization of
configuration, but Ansible roles are often overlooked in lieu of straightforward playbooks for the task at hand.
The good news is that Ansible
roles are simple to get set up and allow for complexity when necessary.
The concept of an Ansible role is simple; it is a group of variables, tasks, files, and handlers that are stored
in a standardized file structure and only called inside an ansible playbook.
ansible role = group of variables, task, files and handlers that are stored in a standardized file
(directory) structue.
The most complicated part of a role is recalling the directory structure, but there is help. The built-in
ansible-galaxy command has a subcommand that will create our role skeleton for us. Simply use
ansible-galaxy init to create
a new role in your present working directory. You will see here that several directories and files are created
within the new role:
With the above command we created a directory named after the given role. You will see here that several
directories and files are created within the new role (directory):
We have the following 8 directories created, defaults, files, handlers, meta, tasks, templates, tests
and vars. All directories apart from tests, templates and files contain a main.yml file. Ansible uses
each of those files as the
entry point for reading the contents of the directory.
The defaults directory is designated for variable defaults that take the lowest precedence. Put another
way: If a variable is defined nowhere else, the definition given in defaults/main.yml will be used.
The files and templates directories serve a similar purpose. They contain affiliated files and Ansible
templates (respectively) that are used within the role. The beautiful part about these directories is that
Ansible does not need a path for
resources stored in them when working in the role. Ansible checks them first. You may still use the full path
if you want to reference files outside of the role, however, best practices suggest that you keep all of the role
components together.
The handlers directory contains the handlers of our role which may be used within or outside this role.
The meta directory contains authorship information which is useful if you choose to publish your role on
galaxy.ansible.com. The meta directory may also be used to define role dependencies. As you may
suspect, a role dependency allows
you to require that other roles be installed prior to the role in question.
The README.md file is simply a README file in markdown format. This file is essential for roles published to
galaxy.ansible.com and, honestly, the file should include a general description of how your role operates
even if you do not make it
publicly available.
The tasks directory is where most of your role will be written. This directory includes all the tasks that your
role will run. Ideally, each logically related series of tasks would be laid out in their own files, and simply
included through the main.yml
file in the tasks directory.
The test directory contains a sample inventory and a test.yml playbook. This may be useful if you have
an automated testing process built around your role. It can also be handy as you are constructing your role
but use of it is not mandatory.
The last directory created is the vars directory. This is where you create variable files that define necessary
variables for your role. The variables defined in this directory are meant for role internal use only. It is a good
idea to namespace your
role variable names, to prevent potential naming conflicts with variables outside of your role. For example, if
you needed a variable named config_file in your baseline playbook, you may want to name your variable
baseline_config_file,
to avoid conflicts with another possible config_file variable defined elsewhere.
As stated earlier, Ansible roles can be as complex or as simple as you need. Sometimes, it is helpful to start
simple and iterate into a more complex role as you shore up the base functionality. Let’s try that, and define
a role called base_httpd
that installs httpd with a simple configuration and a very simple website.
First we will create the index page of our web servers. The index page is a simple .html page, we will just
create a file named index.html under the files directory with a very simple message:
As we already have defined the content of our website in the files directory, we will proceed with the
configuration. We will use the template feature of ansible, so we will create a .j2 file (jinja template)
under the templates directory with
the following content (the name of our file is httpd.conf.j2):
Listen {{ base_httpd_listen_port }}
LogLevel {{ base_httpd_log_level }}
As we can see, we have defined 2 variables inside the template using the {{ }} syntax for variable
declaration. These are 2 default variables and as we know, we will add them in the defaults/main.yml file
for the default variables:
---
# defaults file for base_httpd
base_httpd_listen_port: 80
base_httpd_log_level: warn
Last part for the creation of the role is the tasks module. We will create a separate task (this means a
separate .yml file) under the tasks directory and we will include it in the tasks/main.yml file which acts as
the entry point of the tasks.
So our task will be declared inside the tasks/httpd.yml file:
---
# Single task for httpd provisioning
As we can see, we have utilised the modules yum, template, copy, service to install, activate and
configure the remote hosts. Finally we will include this task in the tasks/main.yml file using the include
key:
---
# tasks file for base_httpd
- include : httpd.yml
Notice that the source files (src) are referenced without the full path as they are stored inside the role.
Most of the hard work was completed when we constructed the role itself. Deployment of the role, by
comparison, is easy. We only need to set up a simple playbook and pull in the role using the appropriate
keyword roles:
---
- hosts: test_vnf_instances
gather_facts: yes
become_user: root
roles:
- base_httpd
The key roles is where we declare the roles we want to execute. The role is nothing more than a directory
where we have defined all the role's files.
We could easily add typical tasks after the role deployment, or we could also deploy additional roles by
simply adding them to the list. Also, we can override the default variables we configured using the same
variable names as shown below
using the vars key:
---
- hosts: test_vnf_instances
gather_facts: yes
become_user: root
vars:
base_http_log_level: error
roles:
- base_httpd
------------------------------------------------------------------------------------------------------------------------------------------------
-----
ansible-config view
Command to display the current config file.