{"id":2951,"date":"2025-03-14T10:00:43","date_gmt":"2025-03-14T10:00:43","guid":{"rendered":"https:\/\/blog.ssdnodes.com\/blog\/?p=2477"},"modified":"2026-01-04T15:49:29","modified_gmt":"2026-01-04T15:49:29","slug":"step-by-step-ansible-guide","status":"publish","type":"post","link":"https:\/\/www.ssdnodes.com\/blog\/step-by-step-ansible-guide\/","title":{"rendered":"Ansible Tutorial for Beginners &#8211; A Step By Step Guide"},"content":{"rendered":"\r\n<p>The DevOps field as we know it is changing rapidly, and manually configuring servers isn't just inefficient, it's a dangerous liability that's costing you insane amounts of time. This Ansible step by step tutorial for beginners is your guide to mastering server automation before your competition leaves you behind. Fortune 500 companies are already using Ansible to manage thousands of servers with just a few keystrokes, and after this comprehensive guide, you'll have these same powerful capabilities at your fingertips.<br \/><br \/>I'll break everything down into bite-sized, actionable steps that you can immediately apply to your infrastructure. By the end of this tutorial, you'll be automating tasks that used to take hours in just minutes; and who knows? This could be the technological edge that transforms your entire operation sooner than you expect.<\/p>\r\n\r\n\r\n\r\n<h2 class=\"wp-block-heading\">Notes On This Ansible Tutorial for Beginners<\/h2>\r\n\r\n\r\n\r\n<ul class=\"wp-block-list\">\r\n<li>You need at least two servers to follow this tutorial, a server for the control host where you install Ansible, and a server for a remote host that will be controlled by Ansible. If you haven't noticed, we offer extremely affordable VPS plans. <a href=\"https:\/\/ssdnodes.com\/\" target=\"_blank\" rel=\"noopener\">Check out our website<\/a> and check out our incredible <a href=\"https:\/\/www.ssdnodes.com\/cheap-vps-hosting\/?utm_source=blog&amp;utm_medium=ansible-tutorial\">deals<\/a>!<\/li>\r\n<li>Whenever you see the <strong>SUBDOMAIN<\/strong>, <strong>DOMAIN<\/strong>, or <strong>TLD<\/strong> variables, replace them with the details of your domain name. In <strong>example.ssdnodes.com<\/strong>, the <strong>SUBDOMAIN<\/strong> is example, <strong>ssdnodes<\/strong> is the <strong>DOMAIN<\/strong> and <strong>.com<\/strong> is the <strong>TLD<\/strong>. Or you can just use an IP address instead of a domain.<\/li>\r\n<\/ul>\r\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-9940 size-full\" style=\"border-radius: 25px;\" src=\"https:\/\/www.ssdnodes.com\/wp-content\/uploads\/2018\/08\/Ansible-Blog.png\" alt=\"Ansible Tutorial for Beginners\" width=\"600\" height=\"400\" srcset=\"https:\/\/www.ssdnodes.com\/wp-content\/uploads\/2018\/08\/Ansible-Blog.png 600w, https:\/\/www.ssdnodes.com\/wp-content\/uploads\/2018\/08\/Ansible-Blog-300x200.png 300w\" sizes=\"auto, (max-width: 600px) 100vw, 600px\" \/><\/p>\r\n\r\n<div class=\"wp-block-rank-math-faq-block\">\r\n<div class=\"rank-math-faq-item\">\r\n<div class=\"rank-math-faq-item\">\r\n<h2 class=\"rank-math-question\">Ansible for Beginners - A Simple Definition<\/h2>\r\n<div class=\"rank-math-answer\"><a href=\"https:\/\/www.ansible.com\/\" target=\"_blank\" rel=\"noopener\">Ansible<\/a>\u00a0is an open source IT <a href=\"https:\/\/www.ssdnodes.com\/blog\/tools-to-manage-multiple-linux-servers-free\/\">configuration management<\/a>, deployment, and orchestration tool. It empowers DevOps teams to define their infrastructure as a code in a simple and declarative manner. Imagine being able to set up and control 100 servers as easily as you control one server. That's what Ansible does. It's like a remote control for servers. Instead of doing the same tasks over and over on different machines, you write a simple instruction list once, and Ansible automatically follows those instructions on all your servers at once.<br \/><br \/>A lot of people compare Ansible to similar tools like\u00a0<a href=\"https:\/\/www.chef.io\/chef\/\" target=\"_blank\" rel=\"noopener\">Chef<\/a>\u00a0or\u00a0<a href=\"https:\/\/puppet.com\/\" target=\"_blank\" rel=\"noopener\">Puppet<\/a>. They all help automate and provision infrastructure, but there are a few features that make me prefer Ansible over the others.<\/div>\r\n<\/div>\r\n<\/div>\r\n<\/div>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:heading {\"level\":3} -->\r\n<h2>Step 1: Installing Ansible<\/h2>\r\n<p>To take your first steps with Ansible, you first need to install it on your control machine. This is the machine you\u2019ll use to dispatch tasks. For most people, this will be your desktop machine at home or your laptop, but you can also use one VPS as a control host to connect to other VPSs.<\/p>\r\n<h3>Installing Ansible on Ubuntu 24.04<\/h3>\r\n<!-- \/wp:heading -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>You can install Ansible using standard package managers like <code>apt\/yum<\/code> or Python\u2019s <code>pip<\/code> command. To install it using standard package manager in Ubuntu, add its repository information <code>apt-add-repository<\/code>. Next, update the system and install Ansible using <code>apt-get<\/code>.<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:code -->\r\n<pre class=\"wp-block-code\"><code>$ sudo apt-get install software-properties-common\r\n$ sudo apt-add-repository ppa:ansible\/ansible\r\n$ sudo apt-get update\r\n$ sudo apt-get install ansible\r\n<\/code><\/pre>\r\n<!-- \/wp:code -->\r\n\r\n<!-- wp:heading {\"level\":3} -->\r\n<h3>Installing Ansible on AlmaLinux 9<\/h3>\r\n<!-- \/wp:heading -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>To install Ansible on AlmaLinux 9, update the package list, then install the\u00a0<code>ansible-core<\/code>\u00a0package:<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:code -->\r\n<pre class=\"wp-block-code\"><code>$ <span class=\"token\">sudo<\/span> dnf update<br \/><span class=\"token\">$ sudo<\/span> dnf <span class=\"token\">install<\/span> ansible-core<\/code><\/pre>\r\n<!-- \/wp:code -->\r\n\r\n<!-- wp:heading --><!-- \/wp:heading -->\r\n\r\n<!-- wp:paragraph -->\r\n<h2>Step 2: Understanding Ansible Control Host<\/h2>\r\n<p>The<a href=\"https:\/\/docs.ansible.com\/ansible\/latest\/getting_started\/introduction.html\" target=\"_blank\" rel=\"noopener\"><strong> control host<\/strong><\/a> is the main server on which you've installed Ansible, which you will use to dispatch tasks to the remote managed Ansible hosts.<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>Before you start delegating tasks to a managed host, make sure you have non-root, a sudo-enabled user on your control host\u2014it\u2019s always a bad idea to connect to a remote VPS via a root user.<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:heading {\"level\":3} -->\r\n<h2>Step 3: Ansible Inventory Files<\/h2>\r\n<!-- \/wp:heading -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>The <a href=\"https:\/\/docs.ansible.com\/ansible\/latest\/getting_started\/get_started_inventory.html\" target=\"_blank\" rel=\"noopener\">Ansible inventory file<\/a> lists which hosts will receive commands from the control host. The inventory can list individual hosts, or group them under categories you distinguish. This file is crucial for defining the managed infrastructure and can include IP addresses or domain names of the hosts. Additionally, you can organize hosts into groups based on various criteria such as their roles, environments (e.g., development, staging, production), or geographic locations.<\/p>\r\n<h3>Ansible Inventory File Location<\/h3>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>The default location for the inventory file is\u00a0<code>\/etc\/ansible\/hosts<\/code>, but it\u2019s also possible to change the location of the inventory file by uncommenting and modifying the inventory parameter in\u00a0<code>\/etc\/ansible\/ansible.cfg<\/code>. This flexibility allows you to maintain multiple inventory files tailored for different environments or projects. For instance, you might have separate inventory files for development, testing, and production environments. Ansible Inventory files are mostly in the INI and YAML formats.<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:paragraph -->\r\n<h3>Ansible Inventory File Example<\/h3>\r\n<p>A typical inventory file sample can list the managed host either by IP address or by domain names. It is also possible to list one managed host in more than one group. Here\u2019s an example of listing two hosts under the <code>webservers<\/code> and <code>dbservers<\/code> categories.<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:code -->\r\n<pre class=\"wp-block-code\"><code>[webservers]\r\n123.45.67.89\r\nSUBDOMAIN.DOMAIN.TLD<br \/><br \/>[dbservers]<br \/>123.45.67.89<br \/>SUBDOMAIN.DOMAIN.TLD<br \/><\/code><\/pre>\r\n<!-- \/wp:code -->\r\n\r\n<!-- wp:shortcode --><!-- \/wp:paragraph -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>To test if all the hosts are discoverable by the inventory file, use the following ad-hoc command.<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:code -->\r\n<p class=\"wp-block-code\"><code>$ ansible all --list-hosts<\/code><\/p>\r\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-9856\" src=\"https:\/\/www.ssdnodes.com\/wp-content\/uploads\/2018\/08\/ansible-all-list-hosts.webp\" alt=\"Ansible Tutorial for Beginners: List all Hosts\" width=\"600\" height=\"205\" srcset=\"https:\/\/www.ssdnodes.com\/wp-content\/uploads\/2018\/08\/ansible-all-list-hosts.webp 700w, https:\/\/www.ssdnodes.com\/wp-content\/uploads\/2018\/08\/ansible-all-list-hosts-300x102.webp 300w\" sizes=\"auto, (max-width: 600px) 100vw, 600px\" \/><\/p>\r\n<p class=\"wp-block-code\">You can also list the hosts by group name:<\/p>\r\n<!-- \/wp:code -->\r\n\r\n<!-- wp:paragraph --><!-- \/wp:paragraph -->\r\n\r\n<!-- wp:code -->\r\n<pre class=\"wp-block-code\"><code>$ ansible dbservers --list-hosts\r\nhosts (2):\r\n    123.45.67.89\r\n    SUBDOMAIN.DOMAIN.TLD\r\n<\/code><\/pre>\r\n<!-- \/wp:code -->\r\n\r\n<!-- wp:paragraph -->\r\n<p><strong>Ad-hoc<\/strong> commands in Ansible are merely those that perform a single command across one or many hosts. They don\u2019t use tasks but allow you to do a lot of things quite easily without building out playbooks (more on those in the second part of this guide).<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>To find out if all the hosts are up and running, use the following ad-hoc command that uses the <code>ping<\/code> module of Ansible. The <code>-u<\/code> switch specifies which user Ansible will connect to via SSH\u2014change it according to the non-root user you created earlier.<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:code -->\r\n<pre class=\"wp-block-code\"><code>$ ansible all -m ping -u USER\r\n<\/code><\/pre>\r\n<!-- \/wp:code -->\r\n\r\n<!-- wp:paragraph -->\r\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-9858\" src=\"https:\/\/www.ssdnodes.com\/wp-content\/uploads\/2018\/08\/ansible-all-m-ping-u-USER.webp\" alt=\"Ansible Ping\" width=\"600\" height=\"239\" srcset=\"https:\/\/www.ssdnodes.com\/wp-content\/uploads\/2018\/08\/ansible-all-m-ping-u-USER.webp 700w, https:\/\/www.ssdnodes.com\/wp-content\/uploads\/2018\/08\/ansible-all-m-ping-u-USER-300x120.webp 300w\" sizes=\"auto, (max-width: 600px) 100vw, 600px\" \/><\/p>\r\n<p>&nbsp;<\/p>\r\n<p>The <code>\"changed\": false<\/code> in the above JSON result tells us that the <code>ping<\/code> Ansible task didn\u2019t change anything on the remote server.<br \/><br \/><strong>Note:<\/strong> You might get an error saying that your host is unreachable. To solve this, simply create an ssh key pair if you don't already have one on your control host:\u00a0 <code>ssh-keygen -t rsa -b 4096<\/code>, then copy it to your remote host:\u00a0 <code>ssh-copy-id USER@REMOTE_HOST_IP<\/code>.<\/p>\r\n<p>Rather than specifying all the hosts as in the above command, you can also ping a group of hosts. Specify the group name in place of \u2018all\u2019 with the following command:<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:code -->\r\n<pre class=\"wp-block-code\"><code>$ ansible webservers -m ping -u USER\r\n<\/code><\/pre>\r\n<!-- \/wp:code -->\r\n\r\n<!-- wp:heading {\"level\":3} -->\r\n<h2>Step 4: Ansible Modules<\/h2>\r\n<!-- \/wp:heading -->\r\n\r\n<!-- wp:paragraph -->\r\n<p><strong>Modules<\/strong> are the discrete <a href=\"https:\/\/docs.ansible.com\/ansible\/latest\/module_plugin_guide\/index.html\" target=\"_blank\" rel=\"noopener\">units of code<\/a> that can be used from the terminal or in a playbook task. They simplify Ansible tasks by installing software, copying files, using templates, and so on.<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>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 machine will not change.<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>To find the list of available modules, use the following command:<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:code -->\r\n<pre class=\"wp-block-code\"><code>$ ansible-doc -l\r\n<\/code><\/pre>\r\n<!-- \/wp:code -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>Let\u2019s try to install Nginx on an Ubuntu\/Debian host using an ad-hoc command in Ansible:<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:code -->\r\n<pre class=\"wp-block-code\"><code>$ ansible webservers -b --become-user=root -m shell -a 'apt -y install nginx' -u USER\r\n\r\n172.104.160.8 | SUCCESS | rc=0 &gt;&gt;\r\nReading package lists...\r\nBuilding dependency tree...\r\n<\/code><\/pre>\r\n<!-- \/wp:code -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>The following flags were used with the above command:<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:list -->\r\n<ul>\r\n<li><code>-b<\/code>: Instruct ansible to become another user to run the command<\/li>\r\n<li><code>--become-user=root<\/code>: Run the command as a <code>root<\/code> user<\/li>\r\n<li><code>-m<\/code>: Declares which module is used in the command<\/li>\r\n<li><code>-a<\/code>: Declares which arguments are passed to the module<\/li>\r\n<\/ul>\r\n<!-- \/wp:list -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>The alternate and preferred way of installing software using an ad-hoc command is to use <code>apt<\/code> module. If your remote managed host is running RHEL\/CentOS, then change the module name from <code>apt<\/code> to <code>yum<\/code>.<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:code -->\r\n<pre class=\"wp-block-code\"><code>$ ansible webservers -b --become-user=root -m apt -a 'name=nginx state=present update_cache=true' -u USER\r\n<\/code><\/pre>\r\n<!-- \/wp:code -->\r\n\r\n<!-- wp:paragraph -->\r\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-9859\" src=\"https:\/\/www.ssdnodes.com\/wp-content\/uploads\/2018\/08\/alternate-and-preferred-way.webp\" alt=\"Ansible Modules\" width=\"600\" height=\"273\" srcset=\"https:\/\/www.ssdnodes.com\/wp-content\/uploads\/2018\/08\/alternate-and-preferred-way.webp 700w, https:\/\/www.ssdnodes.com\/wp-content\/uploads\/2018\/08\/alternate-and-preferred-way-300x137.webp 300w\" sizes=\"auto, (max-width: 600px) 100vw, 600px\" \/><\/p>\r\n<p>&nbsp;<\/p>\r\n<p>In the above Ansible command, the <code>-a<\/code> switch passes the arguments to the <code>apt<\/code> module by specifying the name of the package to be installed, the desired state, and whether to update the package repository cache or not.<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>The line <code>change: true<\/code> in the result section of the above ad-hoc command signifies that the state of the system has been changed. If you run the above ad-hoc command again, the value of <code>changed<\/code> field will be <code>false<\/code>, which means the state of the system remains unchanged, because Ansible is aware that Nginx is already present in the system and will not try to alter the state again.<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>That\u2019s what we call Ansible <strong>idempotent<\/strong>. You can run the same ad-hoc command as many times as you\u2019d like and it won\u2019t change anything unless it needs to.<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:code -->\r\n<pre class=\"wp-block-code\"><code>172.104.160.8 | SUCCESS =&gt; {\r\n    \"cache_update_time\": 1530378676,\r\n    \"cache_updated\": true,\r\n    \"changed\": false\r\n}\r\n<\/code><\/pre>\r\n<!-- \/wp:code -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>So far, we have understood the ansible modules and its usages through ad-hoc way, but this is not so useful until we use the modules in ansible playbooks to run multiple tasks in the remote managed host.<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:heading {\"level\":3} -->\r\n<h2>Step 5: Tasks in Ansible<\/h2>\r\n<!-- \/wp:heading -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>When you dispatch a job from a control host to a managed host using an Ansible module, it is known as a <strong>task<\/strong>. Tasks can be implemented using ad-hoc commands, as we\u2019ve done just above, or you can use an Ansible <strong>playbook<\/strong> (more on those in a moment).<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>One example of a task is copying a file from the control host to a managed host, since it requires the use of \u2018copy\u2019 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. How many modules are there by default in Ansible? Let\u2019s see:<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:code -->\r\n<pre class=\"wp-block-code\"><code>$ ansible-doc -l | wc -l\r\n1852\r\n<\/code><\/pre>\r\n<!-- \/wp:code -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>If you haven\u2019t guessed, there are a lot of things you can do when combining Ansible tasks and modules.<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:heading {\"level\":3} --><!-- \/wp:paragraph -->\r\n\r\n<!-- wp:heading {\"level\":3} -->\r\n<h2>Step 6: Ansible Playbooks<\/h2>\r\n<!-- \/wp:heading -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>No Ansible tutorial would be complete without a guide to <a href=\"https:\/\/docs.ansible.com\/ansible\/latest\/playbook_guide\/index.html\" target=\"_blank\" rel=\"noopener\">Playbooks<\/a>. And some concrete Ansible Playbook examples.<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>Ansible<strong> Playbooks<\/strong> are composed of one or more plays and offer more advanced functionality for sending tasks to managed host compared to running many ad-hoc commands.<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>The tasks in Ansible playbooks are written in Yet Another Markup Language (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.<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>Let\u2019s create a simple Ansible playbook example that will install Nginx and a MySQL server on the managed hosts that we had already defined in the inventory file.<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>To be more precise, we want Nginx installed on hosts in the <code>webservers<\/code> group and a MySQL server installed on hosts in the <code>dbservers<\/code> group.<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:code -->\r\n<pre class=\"wp-block-code\"><code>$ vi playbook.yml\r\n\r\n---\r\n- hosts: webservers\r\n  gather_facts: yes\r\n  become_user: root\r\n  tasks:\r\n  - name: Install Nginx\r\n    apt: pkg=nginx state=present\r\n    notify:\r\n    - restart nginx\r\n  - name: Enable Nginx during boot\r\n    service: name=nginx state=started enabled=yes\r\n  handlers:\r\n    - name: restart nginx\r\n      service: name=nginx state=restarted\r\n\r\n- hosts: dbservers\r\n  become_user: root\r\n  tasks:\r\n  - name: Install mysql\r\n    apt: pkg=mysql-server state=present\r\n\r\n<\/code><\/pre>\r\n<!-- \/wp:code -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>The <code>hosts<\/code> tells Ansible on which hosts to run the tasks. The above Ansible playbook includes two host groups from the inventory file. The tasks for <code>webservers<\/code> group are to <em>install Nginx<\/em> and <em>enable Nginx during boot<\/em>, and the <code>dbservers<\/code> group includes a single task to <em>install MySQL<\/em>.<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>The <code>become_user<\/code> in both the host section tells ansible to use <code>sudo<\/code> to run the tasks.<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>The <code>gather_facts<\/code> option gathers information about managed hosts such as distribution, OS family, and more. In ansible terminology, this information is known as <code>FACTS<\/code>.<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>The <code>handlers<\/code> section restarts Nginx when Ansible gets notified that Nginx has been installed.<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:paragraph -->\r\n<p><em>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.<\/em><\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>Now run the above playbook example using <code>ansible-playbook<\/code>. Append the name of the user from a remote managed host in the command using <code>-u<\/code> switch.<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:code -->\r\n<pre class=\"wp-block-code\"><code>$ ansible-playbook playbook.yml -u USER\r\n<\/code><\/pre>\r\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-9857\" src=\"https:\/\/www.ssdnodes.com\/wp-content\/uploads\/2018\/08\/ansible-playbook.webp\" alt=\"Ansible Playbook\" width=\"600\" height=\"371\" srcset=\"https:\/\/www.ssdnodes.com\/wp-content\/uploads\/2018\/08\/ansible-playbook.webp 1000w, https:\/\/www.ssdnodes.com\/wp-content\/uploads\/2018\/08\/ansible-playbook-300x185.webp 300w, https:\/\/www.ssdnodes.com\/wp-content\/uploads\/2018\/08\/ansible-playbook-768x475.webp 768w\" sizes=\"auto, (max-width: 600px) 100vw, 600px\" \/><\/p>\r\n<p>The last line contains information about the current run of the above playbook. The four points of data are:<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:list -->\r\n<ul>\r\n<li><code>ok<\/code>: The number of tasks that were either executed correctly or didn\u2019t result in a change.<\/li>\r\n<li><code>changed<\/code>: The number of things that were modified by Ansible.<\/li>\r\n<li><code>unreachable<\/code>: The number of hosts that were unreachable for some reason.<\/li>\r\n<li><code>failed<\/code>: The number of tasks failed to execute correctly.<\/li>\r\n<\/ul>\r\n<!-- \/wp:list -->\r\n\r\n<!-- wp:heading {\"level\":3} -->\r\n<h2>Step 7: Ansible Roles<\/h2>\r\n<!-- \/wp:heading -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>In Ansible, a <strong>role<\/strong> 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.<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>You can\u2019t execute roles directly, the way you do a playbook, and you can\u2019t specify which host you want to execute a role, the way you would an ad-hoc command. Instead, they\u2019re built into the playbooks you use to define a host.<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>The <a href=\"https:\/\/galaxy.ansible.com\/\" target=\"_blank\" rel=\"noopener\">Ansible Galaxy<\/a> repository has thousands of pre-built roles for you to choose from, although you\u2019re free to create your role framework. Let\u2019s dig into how you might want to do just that.<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:heading {\"level\":3} -->\r\n<h2>Step 8: Ansible Variables<\/h2>\r\n<!-- \/wp:heading -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>In Ansible, <strong>variables<\/strong> are similar to variables in any programming language\u2014they 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.<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>There are few places where you can define variables in an Ansible playbook.<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:list -->\r\n<ul>\r\n<li>In the playbook<\/li>\r\n<li>In the inventory file<\/li>\r\n<li>In a separate variable file<\/li>\r\n<li>Using <code>group_vars<\/code><\/li>\r\n<\/ul>\r\n<!-- \/wp:list -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>To define variables in a playbook, use <code>vars<\/code> key just above the task where you want to use the variable. Once declared, you can use it inside the <code>{{ }}<\/code> tag. Let\u2019s declare a variable by the name <code>pkgname<\/code> and assign it the value of the package name that we want to install, which is <code>nginx<\/code>. Once done, we can use the variable in a task.<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:code -->\r\n<pre class=\"wp-block-code\"><code>---\r\n- hosts: webservers\r\n  gather_facts: yes\r\n  become_user: root\r\n\r\n  vars:\r\n    pkgname: nginx\r\n\r\n  tasks:\r\n  - name: Install \"{{ pkgname }}\"\r\n    apt: pkg=\"{{ pkgname }}\" state=present\r\n    ...\r\n    ...\r\n<\/code><\/pre>\r\n<!-- \/wp:code -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>It is also possible to declare a variable in the inventory file using the syntax <code>[host_group_name:vars]<\/code>. Let\u2019s define the variable <code>pkgname<\/code> in the inventory file.<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:code -->\r\n<pre class=\"wp-block-code\"><code>[webservers:vars]\r\npkgname=nginx\r\n<\/code><\/pre>\r\n<!-- \/wp:code -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>Now the variable <code>pkgname<\/code> can be used anywhere in the <code>webservers<\/code> hosts section in the playbook.<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>You can also define variables in a separate variable file and import it into the playbook. Create a variable file using <code>vi<\/code> another text editor and define the variable <code>pkgname<\/code> here.<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:code -->\r\n<pre class=\"wp-block-code\"><code>$ vi ansible_vars.yml\r\n\r\n---\r\npkgname: nginx\r\n<\/code><\/pre>\r\n<!-- \/wp:code -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>To use the variable <code>pkgname<\/code>, import the above file using the <code>vars_files<\/code> keyword in the playbook.<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:code -->\r\n<pre class=\"wp-block-code\"><code>$ vi playbook.yml\r\n\r\n---\r\n- hosts: webservers\r\n  gather_facts: yes\r\n  become_user: root\r\n\r\n  vars_files:\r\n    - .\/ansible_vars.yml\r\n...\r\n...\r\n<\/code><\/pre>\r\n<!-- \/wp:code -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>Another preferred way of managing variables is to create a <code>group_vars<\/code> directory inside your Ansible working directory. Ansible will load any YAML files in this directory with the name of any Ansible group.<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>Create the directory <code>group_vars<\/code> in your Ansible working directory, and then create the variable files matching with the group name from the inventory file. In our example, this would be <code>webservers<\/code> and <code>dbservers<\/code>. This allows you to separate variables according to host groups, which can make everything easier to manage.<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:code -->\r\n<pre class=\"wp-block-code\"><code>$ cd &lt;your_ansible_working_directory&gt;\r\n$ mkdir group_vars\r\n$ cd group_vars\r\n$ vi webservers\r\n<\/code><\/pre>\r\n<!-- \/wp:code -->\r\n\r\n<!-- wp:paragraph -->\r\n<p><br \/><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-9860\" src=\"https:\/\/www.ssdnodes.com\/wp-content\/uploads\/2018\/08\/can-make-everything-easier-to-manage.webp\" alt=\"Ansible Variables\" width=\"600\" height=\"309\" srcset=\"https:\/\/www.ssdnodes.com\/wp-content\/uploads\/2018\/08\/can-make-everything-easier-to-manage.webp 700w, https:\/\/www.ssdnodes.com\/wp-content\/uploads\/2018\/08\/can-make-everything-easier-to-manage-300x155.webp 300w\" sizes=\"auto, (max-width: 600px) 100vw, 600px\" \/><br \/><br \/>You don\u2019t need to declare the variable in your playbook, as Ansible will automatically pull the variables from each <code>group_vars<\/code> files and will substitute them during runtime.<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>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 <code>all<\/code> inside <code>group_vars<\/code> directory. The <code>group_vars\/all<\/code> files are used to set variables for every host that Ansible connects to.<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:heading {\"level\":3} -->\r\n<h2>Step 9: Ansible Conditionals<\/h2>\r\n<!-- \/wp:heading -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>In Ansible, <a href=\"https:\/\/docs.ansible.com\/ansible\/latest\/playbook_guide\/playbooks_conditionals.html\" target=\"_blank\" rel=\"noopener\">conditionals<\/a> 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. For example, if you only want to install a package on a remote server if it is not already installed, you would use a conditional to check the package's status before proceeding with the installation task.<\/p>\r\n<h3>Ansible Conditionals Example<\/h3>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>In our last playbook example, we installed Nginx, so let\u2019s extend that by creating a task that installs Nginx when Apache <em>is not<\/em> present on the host. We can add another task to the playbook we\u2019ve already built.<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:code -->\r\n<pre class=\"wp-block-code\"><code>...\r\n...\r\n  tasks:\r\n  - name: Check if Apache is already installed\r\n    shell: dpkg -s apache2 | grep Status\r\n    register: apache2_is_installed  \r\n    failed_when: no\r\n  - name: Install \"{{ pkgname }}\"\r\n    apt: pkg=\"{{ pkgname }}\" state=present\r\n    when: apache2_is_installed.rc == 1\r\n    notify:\r\n    - restart nginx\r\n...\r\n...\r\n<\/code><\/pre>\r\n<!-- \/wp:code -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>The first task in the above playbook checks if Apache is installed using <code>dpkg -s<\/code> command and stores the output of the task to <code>apache2_is_installed<\/code> variable. The return value of the task will be a non-zero value if Apache is <em>not<\/em> installed on the host.<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>Usually, Ansible would stop executing other tasks because of this non-zero value, but the <code>failed_when: no<\/code> gives Ansible permission to continue with the next set of tasks when it encounters a non-zero value.<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>The second task will install Nginx only when the return value of <code>rc<\/code> is equal to one, which is declared via <code>when: apache2_is_installed.rc == 1<\/code>.<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:heading {\"level\":3} -->\r\n<h2>Step 10: Ansible Loops<\/h2>\r\n<!-- \/wp:heading -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>All programming languages provide a way to iterate over data to perform some repetitive task. Ansible also provides a way to do the same using a <a href=\"https:\/\/docs.ansible.com\/ansible\/latest\/playbook_guide\/playbooks_loops.html\" target=\"_blank\" rel=\"noopener\">concept called <strong>looping<\/strong><\/a>, 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.<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:paragraph -->\r\n<h3>How to use Ansible Loops<\/h3>\r\n<p>While there are many ways to use loops in Ansible, we\u2019ll cover just one of them to get you started. The easiest way to use loops in ansible is to use <code>with_items<\/code> 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 <code>with_items<\/code>.<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:code -->\r\n<pre class=\"wp-block-code\"><code>---\r\n- hosts: webservers\r\n  gather_facts: yes\r\n  become_user: root\r\n\r\n  tasks:\r\n\r\n  - name: Installing packages using loops\r\n    apt: pkg={{ item }} state=present update_cache=yes\r\n    with_items:\r\n      - sysstat\r\n      - htop\r\n      - git    \r\n<\/code><\/pre>\r\n<!-- \/wp:code -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>Run the above playbook from your command line, and you\u2019ll see that you\u2019ve installed all three packages on the remote host with a single task!<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:heading {\"level\":3} -->\r\n<h2>Step 11: Ansible Tags<\/h2>\r\n<!-- \/wp:heading -->\r\n\r\n<!-- wp:paragraph -->\r\n<p><strong>Ansible Tags<\/strong> allow you to run only specific tasks from your playbook via the command line. Just add the <code>tags<\/code> keyword for each task and run only the task(s) that you want by using <code>--tags<\/code> switch at the end of the ansible command.<\/p>\r\n<h3>How to Use Ansible Tags<\/h3>\r\n<p>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.<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:code -->\r\n<pre class=\"wp-block-code\"><code>---\r\n- hosts: webservers\r\n  gather_facts: yes\r\n  become_user: root\r\n\r\n  tasks:\r\n  - name: Check if Apache is already installed\r\n    shell: dpkg -s apache2 | grep Status\r\n    register: apache2_is_installed  \r\n    failed_when: no\r\n  - name: Install \"{{ pkgname }}\"\r\n    apt: pkg=\"{{ pkgname }}\" state=present\r\n    when: apache2_is_installed.rc == 1\r\n    notify:\r\n    - restart nginx\r\n  - name: ensure nginx is running and enable it at boot\r\n    service: name=nginx state=started enabled=yes\r\n    tags:\r\n    - mytag1\r\n\r\n  handlers:\r\n    - name: restart nginx\r\n      service: name=nginx state=restarted\r\n\r\n- hosts: dbservers\r\n  become_user: root\r\n  tasks:\r\n  - name: Install mysql\r\n    apt: pkg=\"{{ pkgname }}\" state=present\r\n    tags:\r\n    - mytag2\r\n\r\n<\/code><\/pre>\r\n<!-- \/wp:code -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>Now run any of the tasks by specifying tag name at the end of ansible command.<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:code -->\r\n<pre class=\"wp-block-code\"><code>$ ansible-playbook playbook.yml -u ansadm --tags 'mytag2'\r\n<\/code><\/pre>\r\n<!-- \/wp:code -->\r\n\r\n<!-- wp:heading {\"level\":3} -->\r\n<h2>Step 12: Using Ansible Templates<\/h2>\r\n<!-- \/wp:heading -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>Typically, after installing a web server like Nginx, you need to configure a virtual hosts file to properly serve a given website on your VPS. <a href=\"https:\/\/www.ssdnodes.com\/blog\/connecting-vps-ssh-security\/\">Instead of using SSH to log into your VPS<\/a> to configure it <em>after<\/em> running Ansible, or using Ansible\u2019s <code>copy<\/code> module to copy many unique configuration files individually, you can take advantage of Ansible\u2019s <strong>templates<\/strong> features.<\/p>\r\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-9861\" src=\"https:\/\/www.ssdnodes.com\/wp-content\/uploads\/2018\/08\/Ansible-Templates.webp\" alt=\"Ansible Templates\" width=\"600\" height=\"131\" srcset=\"https:\/\/www.ssdnodes.com\/wp-content\/uploads\/2018\/08\/Ansible-Templates.webp 700w, https:\/\/www.ssdnodes.com\/wp-content\/uploads\/2018\/08\/Ansible-Templates-300x66.webp 300w\" sizes=\"auto, (max-width: 600px) 100vw, 600px\" \/><\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>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.<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>To begin working with templates, create a directory for template files in your Ansible working directory.<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:code -->\r\n<pre class=\"wp-block-code\"><code>$ mkdir templates\r\n<\/code><\/pre>\r\n<!-- \/wp:code -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>Create two template files. The first template file will be the default <code>index.html<\/code> file for each site, and the second template file will contain configuration settings for the Nginx virtual host.<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:code -->\r\n<pre class=\"wp-block-code\"><code>$ cd templates\r\n$ vi index.html.j2\r\n&lt;html&gt;\r\nYou are visiting {{ domain_name }} !\r\n&lt;\/html&gt;\r\n<\/code><\/pre>\r\n<!-- \/wp:code -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>Similarly, create a template file for the Nginx virtual host:<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:code -->\r\n<pre class=\"wp-block-code\"><code>$ vi nginx-vh.j2\r\nserver {\r\n        listen       80;\r\n        server_name  {{ domain_name }};\r\n        client_max_body_size 20m;\r\n        index index.php index.html index.htm;\r\n        root   \/var\/www\/html\/{{ domain_name }};\r\n\r\n        location \/ {\r\n                    try_files $uri $uri\/ \/index.html?q=$uri&amp;$args;\r\n        }\r\n        location ~* \\.(js|css|png|jpg|jpeg|gif|ico|woff|ttf|svg|otf)$ {\r\n               expires 30d;\r\n               add_header Pragma public;\r\n               add_header Cache-Control \"public\";\r\n               access_log off;\r\n    }\r\n}\r\n<\/code><\/pre>\r\n<!-- \/wp:code -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>Notice that the variables <code>domain_name<\/code> in the above two template files are enclosed within <code>{{ }}<\/code>, which means they will be substituted during runtime by the value of this variable. To define the variable <code>domain_name<\/code>, navigate to the <code>group_vars<\/code> directory and edit the file <code>webservers<\/code> and add the following lines in it.<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:code -->\r\n<pre class=\"wp-block-code\"><code>$ cd group_vars\r\n$ vi webservers\r\n\r\n---\r\ndomain_name: SUBDOMAIN.DOMAIN.TLD\r\n<\/code><\/pre>\r\n<!-- \/wp:code -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>Finally, edit the ansible playbook to create a root folder for sites, copy the <code>index.html<\/code> file to the site\u2019s root folder, and copy the virtual host file to the Nginx virtual host directory <code>\/etc\/nginx\/sites-enabled<\/code> one by one.<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:code -->\r\n<pre class=\"wp-block-code\"><code>$ vi playbook.yml\r\n\r\n---\r\n- hosts: webservers\r\n  gather_facts: yes\r\n  become_user: root\r\n\r\n  tasks:\r\n  - name: Check if Apache is already installed\r\n    shell: dpkg -s apache2 | grep Status\r\n    register: apache2_is_installed  \r\n    failed_when: no\r\n\r\n  - name: Install \"{{ pkgname }}\"\r\n    apt: pkg=\"{{ pkgname }}\" state=present\r\n    when: apache2_is_installed.rc == 1\r\n    notify:\r\n    - restart nginx\r\n\r\n  - name: ensure nginx is running and enable it at boot\r\n    service: name=nginx state=started enabled=yes\r\n\r\n  - name: create virtual host root directory\r\n    file: name=\/var\/www\/html\/{{ domain_name }} state=directory\r\n\r\n  - name: Copying index file to webroot\r\n    template:\r\n      src: templates\/index.html.j2\r\n      dest: \/var\/www\/html\/{{ domain_name }}\/index.html\r\n\r\n  - name: Enables nginx virtual host\r\n    template:\r\n      src: templates\/nginx-vh.j2\r\n      dest: \/etc\/nginx\/sites-enabled\/{{ domain_name }}\r\n\r\n  - name: restart nginx\r\n    service: name=nginx state=restarted\r\n\r\n    tags:\r\n    - mytag1\r\n\r\n  handlers:\r\n    - name: restart nginx\r\n      service: name=nginx state=restarted\r\n\r\n- hosts: dbservers\r\n  become_user: root\r\n  tasks:\r\n  - name: Install mysql\r\n    apt: pkg=\"{{ pkgname }}\" state=present\r\n    tags:\r\n    - mytag2\r\n<\/code><\/pre>\r\n<!-- \/wp:code -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>The template task in the above Ansible playbook takes two mandatory parameters <code>src<\/code> and <code>dest<\/code>. There are also a few optional parameters that can be specified in a template task but is not required at this stage.<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:list -->\r\n<ul>\r\n<li>The <code>src<\/code> parameter specifies the name of the template file from templates directory that Ansible will copy to the remote server. In our case, the two templates files that we have created are <code>index.html.j2<\/code> and <code>nginx-vh.j2<\/code><\/li>\r\n<li>The <code>dest<\/code> parameter is the path in the remote server where the file should be placed.<\/li>\r\n<\/ul>\r\n<!-- \/wp:list -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>Finally, run the playbook from your ansible working directory:<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:code -->\r\n<pre class=\"wp-block-code\"><code>$ ansible-playbook playbook.yml -u USER\r\n<\/code><\/pre>\r\n<!-- \/wp:code -->\r\n\r\n<!-- wp:heading {\"level\":3} -->\r\n<h2>Step 13: Ansible Blocks<\/h2>\r\n<!-- \/wp:heading -->\r\n\r\n<!-- wp:paragraph -->\r\n<p><a href=\"https:\/\/docs.ansible.com\/ansible\/latest\/playbook_guide\/playbooks_blocks.html\" target=\"_blank\" rel=\"noopener\">Ansible blocks<\/a>, which were introduced in version 2.0, allow you to logically group tasks and better handle errors, which is useful when you want to execute multiple tasks under a single condition. With blocks, you can group a set of tasks together and apply a conditional to the entire block rather than each individual task. This helps in organizing the playbooks and making them more readable.<\/p>\r\n<h3>How to use Ansible Blocks<\/h3>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>To end the block, use the <code>when<\/code> keyword once you\u2019re done defining all the tasks you want to be executed. If the evaluation of the <code>when<\/code> condition returns <code>true<\/code>, then all the tasks within the blocks will be executed one by one. All tasks within the blocks will inherit the common data or directives that you set just after the \u2018when\u2019 keyword.<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:code -->\r\n<pre class=\"wp-block-code\"><code>---\r\n- hosts: webservers\r\n\r\n  tasks:\r\n  - name: Install Nginx\r\n\r\n    block:\r\n    - apt: pkg=nginx state=present\r\n    - service: name=nginx state=started enabled=yes\r\n\r\n    when: ansible_distribution == 'Ubuntu'\r\n    become: true\r\n    become_user: root\r\n<\/code><\/pre>\r\n<!-- \/wp:code -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>The <code>block<\/code> section in the above playbook includes two related tasks to install <code>nginx<\/code> and start\/enable it. The <code>when<\/code> evaluation specifies that these tasks should only be run when the remote managed host is using Ubuntu as its operating system. Both the tasks will inherit the privilege escalation directives after the \u2018when\u2019 keyword.<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>You can also use blocks to handle failures, similar to exceptions in most programming languages. The aim is to gracefully handle failures within the <code>block<\/code> rather than withdrawing the entire deployment.<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>Here is an example of how to use blocks to handle failures:<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:code -->\r\n<pre class=\"wp-block-code\"><code>tasks:  \r\n  - block:  \r\n\r\n  - name: Enable Nginx during boot\r\n    service: name=nginx state=started enabled=yes\r\n\r\n    rescue:  \r\n      - name: This section runs only when there is an error in the block.  \r\n        debug: msg=\"There was an error in starting\/enabling nginx.\"  \r\n    always:  \r\n      - name: This section will run always.  \r\n        debug: msg=\"This always executes.\"`\r\n\r\n<\/code><\/pre>\r\n<!-- \/wp:code -->\r\n\r\n<!-- wp:heading {\"level\":3} -->\r\n<h2>Conclusion<\/h2>\r\n<!-- \/wp:heading -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>Okay...so if you followed along with this Ansible tutorial step by step, you\u2019ve gotten a simplified but practical lesson in most how to take your first steps with Ansible:<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:list -->\r\n<ul>\r\n<li>How to install Ansible<\/li>\r\n<li>Running ad-hoc commands<\/li>\r\n<li>Understanding how modules work<\/li>\r\n<li>Creating Ansible playbooks<\/li>\r\n<li>Running your first playbook<\/li>\r\n<li>And a few other key fundamentals to get you started.<\/li>\r\n<\/ul>\r\n<!-- \/wp:list -->\r\n\r\n<!-- wp:heading {\"level\":3} -->\r\n<h2>More Ansible Tutorials<\/h2>\r\n<!-- \/wp:heading -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>For more on Ansible, check out the official <a href=\"https:\/\/docs.ansible.com\/\" target=\"_blank\" rel=\"noopener\">Ansible documentation<\/a>.<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>Check out our secure Ansible playbook tutorial to the complete playbook we put together for securing new VPSs:<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:list -->\r\n<ul>\r\n<li><a href=\"https:\/\/www.ssdnodes.com\/blog\/secure-ansible-playbook\/\">Ansible playbook for a more secure VPS<\/a> (part 1)<\/li>\r\n<li><a href=\"https:\/\/www.ssdnodes.com\/blog\/secure-ansible-playbook-2\/\">A More Secure Ansible Playbook<\/a> (part 2)<\/li>\r\n<\/ul>\r\n<!-- \/wp:list -->\r\n\r\n<!-- wp:paragraph --><!-- \/wp:paragraph -->","protected":false},"excerpt":{"rendered":"<p>Once you get the hang of Ansible, you&#8217;ll use tasks and playbooks to deploy secure servers with ease. Our step-by-step Ansible guide will make you an expert.<\/p>\n","protected":false},"author":19,"featured_media":12075,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"inline_featured_image":false,"footnotes":""},"categories":[18,30],"tags":[209],"class_list":["post-2951","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-devops","category-tutorials","tag-ansible"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.ssdnodes.com\/wp-json\/wp\/v2\/posts\/2951","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.ssdnodes.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.ssdnodes.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.ssdnodes.com\/wp-json\/wp\/v2\/users\/19"}],"replies":[{"embeddable":true,"href":"https:\/\/www.ssdnodes.com\/wp-json\/wp\/v2\/comments?post=2951"}],"version-history":[{"count":43,"href":"https:\/\/www.ssdnodes.com\/wp-json\/wp\/v2\/posts\/2951\/revisions"}],"predecessor-version":[{"id":15079,"href":"https:\/\/www.ssdnodes.com\/wp-json\/wp\/v2\/posts\/2951\/revisions\/15079"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.ssdnodes.com\/wp-json\/wp\/v2\/media\/12075"}],"wp:attachment":[{"href":"https:\/\/www.ssdnodes.com\/wp-json\/wp\/v2\/media?parent=2951"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.ssdnodes.com\/wp-json\/wp\/v2\/categories?post=2951"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.ssdnodes.com\/wp-json\/wp\/v2\/tags?post=2951"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}