<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Automation on blog.iankulin.com</title><link>https://blog.iankulin.com/tags/automation/</link><description>Recent content in Automation on blog.iankulin.com</description><generator>Hugo</generator><language>en-AU</language><lastBuildDate>Wed, 26 Jul 2023 00:00:00 +0000</lastBuildDate><atom:link href="https://blog.iankulin.com/tags/automation/index.xml" rel="self" type="application/rss+xml"/><item><title>First Ansible Playbook</title><link>https://blog.iankulin.com/first-ansible-playbook/</link><pubDate>Wed, 26 Jul 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/first-ansible-playbook/</guid><description>&lt;p&gt;In the &lt;a href="https://blog.iankulin.com/getting-started-with-ansible/"&gt;previous post&lt;/a&gt;, we looked at getting up and running with Ansible, including using the ad-hoc mode to send commands to our servers. We had a inventory file called hosts that had groups of server IP addresses and a simple &lt;code&gt;ansible.cfg&lt;/code&gt; file that pointed to our inventory file.&lt;/p&gt;
&lt;h3 id="playbooks"&gt;Playbooks&lt;/h3&gt;
&lt;p&gt;Ansible playbooks are used to collect together a description of the state we want in a server. When the playbook is executed, Ansible figures out what things need need changed, and changes them. If you&amp;rsquo;re used to the procedural nature of a bash script, where things proceed from one step to the next, and there might be decision branches, this requires an adjustment in your thinking. This is similar to the adjustment I had getting my head around &lt;a href="https://betterprogramming.pub/swiftui-understanding-declarative-programming-aaf05b2383bd"&gt;SwiftUI&lt;/a&gt;, and moving from JS to &lt;a href="https://levelup.gitconnected.com/why-react-is-declarative-a300d1e930b7?gi=3d11485226b4"&gt;React&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Before we dive in and look at a playbook, I should probably say a couple of things about the &lt;a href="https://www.tutorialspoint.com/yaml/yaml_basics.htm"&gt;YAML&lt;/a&gt; format used for these files. It&amp;rsquo;s yet another attempt to strike a compromise between human readable and machine processable files. Spacing is important, it doesn&amp;rsquo;t like tabs, it&amp;rsquo;s case sensitive, and begins with three hyphens. The rest, you&amp;rsquo;ll figure out.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s the files we currently have in our working directory:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-07-04-at-2.11.16-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-04-at-2.11.16-pm.png" width="1000" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-07-04-at-2.11.02-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-04-at-2.11.02-pm.png" width="1000" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The config file just specifies our inventory file, and the inventory file (named &lt;code&gt;hosts&lt;/code&gt;) lists the servers in groups, and provides some &lt;em&gt;variables&lt;/em&gt; for the servers.&lt;/p&gt;
&lt;p&gt;Our web servers are going to need something to serve web pages. Let&amp;rsquo;s write a playbook to ensure they have NGINX installed. If you don&amp;rsquo;t know what &lt;a href="https://www.nginx.com/"&gt;NGINX&lt;/a&gt; is, don&amp;rsquo;t worry about it, it&amp;rsquo;s a web server.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-07-04-at-2.38.27-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-04-at-2.38.27-pm.png" width="1000" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;- name:&lt;/code&gt; - YAML files are hierarchical. Ansible YAML files are a collection of &lt;em&gt;plays&lt;/em&gt;. This file only has one play, named &amp;ldquo;nginx for all web servers&amp;rdquo;. All the plays will be at the top level like this starting with a single hyphen in column 1. Names are great; pick good ones and you won&amp;rsquo;t need much in the way of comments. These names also appear in the output, helping anyone using the playbook to understand what&amp;rsquo;s happening.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;hosts: web&lt;/code&gt; - tells Ansible that we are only running this play against our &lt;code&gt;web&lt;/code&gt; servers. These are defined in the &lt;code&gt;hosts&lt;/code&gt; file that we kept from the last post. If you look back up at the top for that file, you can see we&amp;rsquo;re specifying it for 192.168.100.37 and 192.168.100.38&lt;/p&gt;
&lt;p&gt;&lt;code&gt;become: yes&lt;/code&gt; - To install packages with &lt;code&gt;apt&lt;/code&gt;, we need to &lt;code&gt;sudo&lt;/code&gt;. &lt;code&gt;become yes&lt;/code&gt; is telling Ansible that we need to do this. I guess if we were already the &lt;code&gt;root&lt;/code&gt; user we wouldn&amp;rsquo;t have to do that, but in our &lt;code&gt;hosts&lt;/code&gt; file, we&amp;rsquo;ve said to use the user &lt;code&gt;ian&lt;/code&gt; so &lt;code&gt;ssh&lt;/code&gt; in. We&amp;rsquo;ll see later how to deal with needing the password for this escalation.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;tasks:&lt;/code&gt; - In our hierarchy, each &lt;em&gt;play&lt;/em&gt; consists of a number of &lt;em&gt;tasks&lt;/em&gt;. Here&amp;rsquo;s our list of tasks. Because we&amp;rsquo;re changing levels, they&amp;rsquo;ll be indented.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;- name:&lt;/code&gt; - This time, it&amp;rsquo;s the name of the task. Again, pick good ones.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;apt:&lt;/code&gt; Ansible functionality is organised according to modules. Here we are saying we are going to use the &lt;a href="https://docs.ansible.com/ansible/latest/collections/ansible/builtin/apt_module.html"&gt;apt module&lt;/a&gt;. &lt;code&gt;apt&lt;/code&gt; is the command to install packages on Debian flavoured systems&lt;/p&gt;
&lt;p&gt;&lt;code&gt;name : nginx&lt;/code&gt; - This time, it&amp;rsquo;s the name of the package we want installed. It&amp;rsquo;s the same name as you would have used if using &lt;code&gt;apt&lt;/code&gt; manually.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;state: latest&lt;/code&gt; - we&amp;rsquo;re saying we want the nginx package to be installed, and we want it to be the latest one. This is were you should really be noticing the declarative nature of the playbook. We could also say &lt;code&gt;state: absent&lt;/code&gt; and Ansible would uninstall it if it was installed, or &lt;code&gt;state: present&lt;/code&gt; in which case Ansible would just check it&amp;rsquo;s there but not worry about the version.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;- name:&lt;/code&gt; - Okay, we&amp;rsquo;re back up at the lists of tasks level. Here&amp;rsquo;s the name of a new task.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;service:&lt;/code&gt; In the previous task we were using the apt module. This task is going to use the &lt;a href="https://docs.ansible.com/ansible/latest/collections/ansible/builtin/service_module.html"&gt;service module&lt;/a&gt;. If you&amp;rsquo;re wondering how you get to know what things are in each module, the &lt;a href="https://docs.ansible.com/ansible/latest/module_plugin_guide/index.html#"&gt;documentation at Ansible&lt;/a&gt; is pretty great. Sometimes you&amp;rsquo;ll get pretty close by thinking of what you&amp;rsquo;re doing. In this case, we want to check if the NGINX service is running, and if not start it - so it&amp;rsquo;s logical that the module we want is going to be &lt;code&gt;service&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;name : nginx&lt;/code&gt; - This time, it&amp;rsquo;s the name of the service.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;state: started&lt;/code&gt; - declaratively saying what we want the state of the NGINX service to be.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;enabled: yes&lt;/code&gt; - it will also be started on a reboot.&lt;/p&gt;
&lt;p&gt;Phew. Okay. We want NGINX to be installed on these machines, and for the service to be running and for that to still be the case after a reboot. Let&amp;rsquo;s run this playbook and see what happens. Here&amp;rsquo;s the command we&amp;rsquo;re going to do that with.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ansible-playbook web_installs.yaml --ask-become-pass
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;--ask-become-pass&lt;/code&gt; piece of this command is telling Ansible to ask us for the password for this user so it can have sudo privileges to install things. We could have just added the password in the hosts file like we have the user name, but that would be quite insecure. Especially when we push our code up to github. Scanning pubic github commits for passwords and API keys is a popular pastime.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-04-at-2.38.19-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;After asking me for the password, Ansible has correctly identified the two servers and gathered facts from them. The facts are a lot of information that&amp;rsquo;s then stored in variables that we can then use in our playbooks. For example this playbook is assuming a Linux distro that uses the apt package manager. If we wanted to check for that, one of the facts variables would contain the distro name and we could use that to conditionally use apt or some other package manager.&lt;/p&gt;
&lt;p&gt;You&amp;rsquo;ve probably noticed the colours. Green messages mean something&amp;rsquo;s in the correct state, yellow means it wasn&amp;rsquo;t in the correct state before, but is now, and red means it&amp;rsquo;s not in the correct state, and couldn&amp;rsquo;t be made to be for some reason.&lt;/p&gt;
&lt;p&gt;Since this is the first time this playbook&amp;rsquo;s been run against these servers, we expected the &amp;rsquo;nginx installed&amp;rsquo; tasks to be yellow for both servers. The highlighted IP address under &amp;rsquo;nginx running&amp;rsquo; is just because I was copying it to go and check the web server was working. Let&amp;rsquo;s have a look.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-04-at-3.36.22-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Well done Ansible.&lt;/p&gt;
&lt;p&gt;In regard to those yellow messages where Ansible found that NGINX wasn&amp;rsquo;t installed, so it went ahead and installed them, you might be thinking &amp;ldquo;if we run the playbook again, shouldn&amp;rsquo;t they be green?&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-04-at-2.50.42-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;So that&amp;rsquo;s our first playbook done. We&amp;rsquo;ve only learned the commands for installing packages and working with services, but Ansible can do pretty much anything. Certainly anything you can do by sshing in and running a script of some kind. I don&amp;rsquo;t think I want to go any further with trying to show the range of things that can be accomplished (although it is tempting to now install a web page into our servers) - it makes more sense for you to just find what you need as you need it.&lt;/p&gt;
&lt;p&gt;There is however one problem I ran into almost immediately and couldn&amp;rsquo;t find a simple description of that I&amp;rsquo;ll cover in the next post. Every Saturday morning, I ssh into my local and remote servers (15 of them) and run &lt;code&gt;apt update&lt;/code&gt; and &lt;code&gt;apt upgrade&lt;/code&gt;. You can see from the yaml above, that&amp;rsquo;s going to be quite easy to automate with Ansible and save me heaps of time and effort. My problem is - all my servers have unique user names and passwords. It&amp;rsquo;s not possible to just add a &amp;ndash;ask-become-pass to my command; that would only work for the first one.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;ll look at how to solve that securely in a future post.&lt;/p&gt;</description></item><item><title>Getting Started with Ansible</title><link>https://blog.iankulin.com/getting-started-with-ansible/</link><pubDate>Wed, 19 Jul 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/getting-started-with-ansible/</guid><description>&lt;p&gt;Ansible is a system for executing commands on remote systems. It allows a declarative approach - so if you run a playbook (the system configuration files are called playbooks) that says a system has a Docker container running Jellyfin, Ansible will check if that&amp;rsquo;s true, and if not, make it so. Ansible is best used when you have a large number of systems to maintain, but even with a small number, it serves to document systems as well as to automate their creation.&lt;/p&gt;
&lt;p&gt;Since, with Ansible, system configurations can be completely described, it&amp;rsquo;s a step in the journey to &amp;ldquo;infrastructure as code&amp;rdquo; and allows infrastructure to be version controlled, and lends itself to Git-Ops where you push a change to a playbook file, and it&amp;rsquo;s executed to make that description of the configuration reality on your servers. The list of servers is stored in a file called the inventory.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=wgQ3rHFTM4E"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-02-at-11.28.10-am.jpg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve considered implementing it a couple of times, but put it off as soon as I started looking at these complicated yaml files. Jeff Geerling&amp;rsquo;s &lt;a href="https://www.ansiblefordevops.com/"&gt;&amp;ldquo;Ansible for DevOps&amp;rdquo;&lt;/a&gt; seemed like the perfect place to start, but then he uses Vagrant and VirtualBox in his early examples, and Vagrant&amp;rsquo;s integration with Ansible means things are not being done in a standard way and I couldn&amp;rsquo;t follow along without mirroring his setup. I don&amp;rsquo;t want to run VM&amp;rsquo;s on my laptop, I want to use my homelab VMs or a VPS - both of which I think would be a more common setup.&lt;/p&gt;
&lt;p&gt;This mini guide is just a start. I&amp;rsquo;ll step through to the point where you have a yaml file describing a system configuration that can be applied to a VM to install some software. After that, you probably want to buy Jeff&amp;rsquo;s book, hit up some &lt;a href="https://www.youtube.com/watch?v=wgQ3rHFTM4E"&gt;good v&lt;/a&gt;ideos, or head to the &lt;a href="https://docs.ansible.com/"&gt;Ansible documentation&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="prerequisites"&gt;Prerequisites&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-07-02-at-11.16.33-am.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-02-at-11.16.33-am.png" width="118" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;For this to be helpful to you, you probably need to have been mucking about running Linux servers. You know how to ssh into them and have set up key pairs to allow that without typing your password each time. You can write a bash script (but don&amp;rsquo;t want to), You know how to install software with apt/yum/pip/homebrew etc. You should go and install it now. Note that you also need python (preferably 3) on the host.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;ve saved a run book of the things you need to do to recreate particular setups or deal with common issues, then you are at the exact point that Ansible is going to make your life better.&lt;/p&gt;
&lt;p&gt;Get &lt;a href="https://docs.ansible.com/ansible/latest/installation_guide/installation_distros.html"&gt;Ansible installed&lt;/a&gt;, you do need an up to date Python. You also need to have ssh set up for each of the nodes (servers) you are going to manage, preferably including using keys rather than passwords.&lt;/p&gt;
&lt;h3 id="starting-concepts"&gt;Starting Concepts&lt;/h3&gt;
&lt;p&gt;Ansible can execute &lt;em&gt;playbooks&lt;/em&gt; which are yaml files setting out the actions needed or final state of the node to be achieved. Alternatively, single commands can be executed from the command line in &amp;lsquo;ad-hoc mode&amp;rsquo;. When setting things up, ad-hoc mode is a good starting place to check you&amp;rsquo;ve installed everything correctly since it&amp;rsquo;s simpler.&lt;/p&gt;
&lt;p&gt;Ansible &lt;em&gt;modules&lt;/em&gt; are bits of code to support particular pieces of functionality. You could think of them as code libraries. For example, there&amp;rsquo;s an &lt;code&gt;apt&lt;/code&gt; module that enables Ansible to execute commands related to package management on the Debian family of Linux distros. Similar to code libraries, you&amp;rsquo;ll need to know which library is needed for the functionality you want to use. Luckily, Ansible&amp;rsquo;s documentation is excellent, and as with your programming, you&amp;rsquo;ll soon become familiar with the ones you use all the time.&lt;/p&gt;
&lt;h3 id="demo-environment"&gt;Demo Environment&lt;/h3&gt;
&lt;p&gt;For the following examples, I&amp;rsquo;ve set up three virtual machines (VM&amp;rsquo;s) 192.168.100.37 - 192.168.100.39 running Debian. I use Proxmox on my servers, so it looks a bit like this.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-03-at-6.46.26-am.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re trying things from a single machine, you could install something like &lt;a href="https://www.virtualbox.org/"&gt;VirtualBox&lt;/a&gt; to create VM&amp;rsquo;s, or I&amp;rsquo;d probably recommend just commissioning a VPS on &lt;a href="https://www.linode.com/lp/podcasts/?ifso=ssh&amp;amp;utm_source=podcast&amp;amp;utm_medium=audio&amp;amp;utm_campaign=ssh"&gt;Linode&lt;/a&gt; or &lt;a href="https://cloud.digitalocean.com/registrations/new"&gt;Digital Ocean&lt;/a&gt;. They both have deals whereby you get a dollar amount credit for signing up, for the minimal machine you need to try these things out, you&amp;rsquo;re probably looking at a cost of $0.30 an hour. I&amp;rsquo;m in Australia, so my VPS&amp;rsquo;s are on &lt;a href="https://www.binarylane.com.au/register"&gt;Binary Lane&lt;/a&gt; which costs less that AUD5 a month for a low-end instance.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re running against multiple machines, you&amp;rsquo;ll make your life easier by having the same user name on each one. For example, the commands I use to &lt;code&gt;ssh&lt;/code&gt; into mine are:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ssh ian@192.168.100.37
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ssh ian@192.168.100.38
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ssh ian@192.168.100.39
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="inventory"&gt;Inventory&lt;/h3&gt;
&lt;p&gt;Ansible has the concept of an &lt;em&gt;Inventory&lt;/em&gt;. The Inventory is a text file of the servers/nodes (I&amp;rsquo;m just going to say nodes from now on). We need this inventory whether using playbooks or ad-hoc commands. Here&amp;rsquo;s mine, which I&amp;rsquo;ve saved in the directory I&amp;rsquo;m working from as &lt;code&gt;hosts&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;192.168.100.37
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;192.168.100.38
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;192.168.100.39
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Note that these could also be domain names if your nodes are set up on DNS.&lt;/p&gt;
&lt;h3 id="first-command"&gt;First Command&lt;/h3&gt;
&lt;p&gt;Finally, we&amp;rsquo;re at the point we can run something. Let&amp;rsquo;s try this command to find the host name of each node. There&amp;rsquo;s a lot going on, so we&amp;rsquo;ll break it down after we&amp;rsquo;ve looked at the output.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ansible -i hosts all -u ian -a &amp;#34;hostname&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-03-at-7.41.00-am.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s break down all those arguments:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-i hosts&lt;/code&gt; - the inventory flag points to the inventory file. In my example the file is named &amp;ldquo;hosts&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;all&lt;/code&gt; - we&amp;rsquo;re saying to execute this against all of the nodes in the &lt;code&gt;hosts&lt;/code&gt; file. Later on we&amp;rsquo;ll see how to separate the nodes into groups inside the inventory file and this will make more sense.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-u ian&lt;/code&gt; - the ssh user name for each node&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-a &amp;quot;hostname&amp;quot;&lt;/code&gt; - the command to run&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What Ansible has actually done here is ssh into each node and use python to execute the command. Collected the output, then formatted that for us to see. Here it is:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;192.168.100.37 | CHANGED | rc=0 &amp;gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;vm321-deb
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;192.168.100.38 | CHANGED | rc=0 &amp;gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;vm322-deb
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;192.168.100.39 | CHANGED | rc=0 &amp;gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;vm323-deb
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;There&amp;rsquo;s our node IP addresses. The &lt;code&gt;rc=0&lt;/code&gt; is the successful return code, then there&amp;rsquo;s the actual host names - &lt;code&gt;vm321-deb&lt;/code&gt; etc.&lt;/p&gt;
&lt;p&gt;But, what&amp;rsquo;s going on with &lt;code&gt;CHANGED&lt;/code&gt;? Ansible always indicates some sort of status - things like &lt;code&gt;CHANGED&lt;/code&gt;, &lt;code&gt;SUCCESS&lt;/code&gt;, &lt;code&gt;FAILED&lt;/code&gt; etc. In this case, there should not have been any change - we were just retrieving the hose names, not altering them. The best answer is just ignore this for now. The long answer is that when we&amp;rsquo;re using &lt;code&gt;-a&lt;/code&gt; to run commands on a node, Ansible&amp;rsquo;s &lt;code&gt;command&lt;/code&gt; module isn&amp;rsquo;t able to tell if there have been changes or not, so it reports &lt;code&gt;CHANGED&lt;/code&gt; as a better safe than sorry approach.&lt;/p&gt;
&lt;p&gt;Even though it&amp;rsquo;s possible to use Ansible to run native commands, when there is an equivalent Ansible module that can carry out the same action, it&amp;rsquo;s always better to use that. The reason is that that module code is smart enough to see if something needs done or not. If it does not need done, it will just return &lt;code&gt;SUCCESS&lt;/code&gt;, if it needs done, it will carry out what&amp;rsquo;s needed and return &lt;code&gt;CHANGED&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="idempotence"&gt;Idempotence&lt;/h3&gt;
&lt;p&gt;Every Ansible tutorial includes this word, which I have never encountered anywhere else. A command is idempotent if the result is the same no matter how many times it is executed. In the case of Ansible, this is because it checks if something is needed before it does it.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s look at an example. If I wanted to create a test directory in the home folder of each of my machines, the Ansible module for this is the file module. I could use this command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ansible -i hosts all -u ian -m file -a &amp;#34;path=test state=directory&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;-m&lt;/code&gt; tells Ansible with module to use, and our arguments after the &lt;code&gt;-a&lt;/code&gt; flag tell Ansible that the state we want to achieve is a directory named &lt;code&gt;test&lt;/code&gt;. Let&amp;rsquo;s run that and have a look at the output:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-03-at-9.03.12-am.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;That makes sense, each one is CHANGED because we needed to create the directory. Let&amp;rsquo;s run it again and see what happens.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-03-at-9.03.25-am.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;This time, since the directory is there, there&amp;rsquo;s no need to change it. Ansible checks for the directories existence before it bothers to create it - because it is idempotent.&lt;/p&gt;
&lt;h3 id="ansiblecfg"&gt;ansible.cfg&lt;/h3&gt;
&lt;p&gt;I&amp;rsquo;m getting a bit sick of this long command. We can move the inventory file name to a config file to save the typing. Create an ansible.cfg file in your working directory like this.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[defaults]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;inventory = hosts
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now we can eliminate that from our command line input.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-03-at-9.14.25-am.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;d also like to get rid of the &lt;code&gt;-u ian&lt;/code&gt; from each command. That&amp;rsquo;s not stored in the .cfg file. Since it&amp;rsquo;s likely that your nodes will have different user names in a real situation, they can be stored in the inventory file.&lt;/p&gt;
&lt;h3 id="inventory-file"&gt;Inventory file&lt;/h3&gt;
&lt;p&gt;We started off with a very simple inventory file - literally just a list of IP addresses. let&amp;rsquo;s revisit that to add the ssh user, and while we&amp;rsquo;re there, we can group the nodes according to their functions - this will come in handy later.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;[&lt;/span&gt;web&lt;span style="color:#eceff4"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#b48ead"&gt;192.168&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;&lt;span style="color:#b48ead"&gt;100.37&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#b48ead"&gt;192.168&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;&lt;span style="color:#b48ead"&gt;100.38&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;[&lt;/span&gt;db&lt;span style="color:#eceff4"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#b48ead"&gt;192.168&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;&lt;span style="color:#b48ead"&gt;100.39&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;[&lt;/span&gt;web&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;vars&lt;span style="color:#eceff4"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ansible_ssh_user&lt;span style="color:#81a1c1"&gt;=&lt;/span&gt;ian
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;[&lt;/span&gt;db&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;vars&lt;span style="color:#eceff4"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ansible_ssh_user&lt;span style="color:#81a1c1"&gt;=&lt;/span&gt;ian
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here I&amp;rsquo;ve created two groups for my nodes, a &lt;code&gt;web&lt;/code&gt; group and a &lt;code&gt;db&lt;/code&gt; group. I&amp;rsquo;ve also set the ssh_user for each group. Now that argument can be left out of out commands. So to get the hostnames now, we can just say:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ansible all -a &amp;#34;hostname&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So much neater! Additionally, since our nodes are in groups now, we can specify the group if we don&amp;rsquo;t want to execute the command on all nodes.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-07-03-at-9.38.41-am.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-03-at-9.38.41-am.png" width="472" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s probably as far as I want to go in this post. We&amp;rsquo;ve got our heads around some early Ansible concepts, learned how to use the Ad-Hoc commands to do things to our nodes, learned a big word that won&amp;rsquo;t ever come up again except in coding interviews, and seen how to set up the ansible.cfg and inventory files.&lt;/p&gt;
&lt;p&gt;The real power to be unleashed is using Ansible playbooks. We&amp;rsquo;ll look at them next.&lt;/p&gt;</description></item></channel></rss>