import { addBanner, addArticle, addTitle, addHeader, addParagraph, addSubHeader } from '/scripts/article.js'; import { addInset, addInsetList, addInsetCodeListing, addInsetBulletList } from '/scripts/inset.js'; import { addImageWithCaption, addButtonGroup } from '/scripts/visuals.js'; import { addSidebar} from '/scripts/sidebar.js'; import { addSyntax } from '/scripts/code.js'; import { global_menu } from '/scripts/grid_layout1.js'; import { local_menu } from '/scripts/linux.js'; const heading = document.querySelector(".heading"); const global = document.querySelector(".global_menu"); const local = document.querySelector(".local_menu"); const sidebar = document.querySelector(".sidebar"); const main = document.querySelector(".main_content"); heading.append(addTitle("Learning Ansible")); heading.append(addParagraph("Anthony Sequeira - LinkedIn Learning - August 2020")); heading.append(addParagraph("Chapter 1 - What is Ansible?")); main.append(addHeader("Ansible Ease of Use")); main.append(addParagraph("There are a couple of things that make Ansible easy to use including the following: ")); main.append(addInsetList(["It uses modules to provide external code. An example of this is the ping module.", "It extends functionality with plugins. For example, there is a plug that allows you to dynamically list the devices that are being managed."])); main.append(addHeader("Advantages of Ansible")); main.append(addParagraph("There are a lot of advantages to using Ansible and some of these are listed below:")); main.append(addInsetList(["It's easy to use.", "It's easy to learn.", "Since it is written in Python it is Python-friendly", "It's agentless so you don't have to install anything on the machines you want to manage with Ansible.", "Some of the critical compoments such as playbooks are written in YAML so these are easy to write but also easy to read.", "As it is used all over the world, there are lots of users creating things like roles. A good example of this can be found in GalaxyAnsible.com.", "Functionality can be extended with modules and plugins."])); main.append(addParagraph("What is a role? It is basically everything you would need to automate a guven task in a nice, downloadable component. They are similar to plugins although they don't add to the capabilities of Ansible, which is something that a plugin would do.")); main.append(addHeader("Installing Ansible")); main.append(addParagraph("You have to install Python first because we will use the Python installer, Pip3, to install Ansible so we can start by checking to see if Python is already installed")); main.append(addSyntax("python --version")); main.append(addParagraph("Similarly, we can check for Pip3/")); main.append(addSyntax("pip3 --version")); main.append(addParagraph("I am installing Ansible on my Raspberry Pi 4 (Dev Machine) and Python 3 is installed already, as is Pip3, so I can just go ahead and install it with the command")); main.append(addSyntax(["sudo pip3 install ansible"])); main.append(addParagraph("If everything goes well, Andible should be installed in")); main.append(addSyntax("/usr/local/bin/")); main.append(addParagraph("which it is. I don't think that it generates a config file when installed on a Mac but it did generate one on my Raspberry Pi. This is also in the /usr/local/bin/ folder and is shown in the image below.")); main.append(addImageWithCaption("./images/config.jpg", "The ansible-config file generated when Ansible was installed.")); main.append(addParagraph("You can move it to another location and reference that location when initiating a playbook.")); main.append(addParagraph("There are a couple of ways in which we can verify that Ansible is installed. The first is to use the --version option as we did with Python.")); main.append(addSyntax("ansible --version")); main.append(addParagraph("We can also run a simple Ansible ad-hoc command such as")); main.append(addSyntax("ansible localhost -m ping")); main.append(addParagraph("Since this is the first actual Ansible command we have seen, it's worth pointing out a couple of things about it before we move on. We would normally run a command against a list of hosts, but in this case, we are only running it against localhost since it is just a test. The -m option indicates that we are using a module, in this case the ping module and it does exactly what you would expect it to. The output we get from this is")); main.append(addImageWithCaption("./images/ping.jpg", "The output from our first Ansible command.")); main.append(addParagraph("If you do have any problems installing Ansible, check out the documentation at docs.ansible.com.")); main.append(addHeader("The Parts that Make Up Ansible")); main.append(addParagraph("There should be a folder in /etc/ called ansible and this folder should contain two files, the hosts file and a config file called ansible.cfg. I have had to manually create both the directory and the hosts file. You can get more info on the config file in the ansible docs for Ansible Configuration Settings and this also provides a command you can use to generate a file where everything is initially disabled so it is probably a good starting point. The command is:")); main.append(addSyntax("ansible-config init --disabled > ansible.cfg")); main.append(addParagraph("I got a permission error when I tried running this in the /etc/ansible folder so I retried it in the developer home folder and then moved it into the /etc/ansible folder and chnaged the owner to root.")); main.append(addParagraph("This is quite a large file and the first part of it is shown below.")); main.append(addImageWithCaption("./images/initial_config.jpg", "The first page of the generated config file.")); main.append(addParagraph("This is a very important file. For example, it includes the default path to the hosts file so if we want to put in a different folder, we can configure that here and we can also set a location for our config files.")); main.append(addParagraph("Playbooks are critical since they contain the Ansible code we want to run against the hosts. These are written in YAML and a sample playbook is shown below.")); main.append(addImageWithCaption("./images/sample_yml.jpg", "A sample playbook.")); main.append(addParagraph("As this is our first look at a playbook, there are a few things worth pointing out here. Firstly, we have three hyphens (---) on the first line which indicates that this file is using YAML syntax.")); main.append(addParagraph("Name is the name of the play that will be run. Hosts is set to all, so this playbook will be run on all of the devices listed in the hosts file. The become user indicates that the playbook will assume, in this case, root priviliges on the devices.")); main.append(addParagraph("In this example, the playbook contains two tasks that will be run on all the devices and each of these tasks makes use of a module. The first task is going to check that the version of Apache installed on the device is the latest version. This uses the yum module.")); main.append(addParagraph("The second task checks that Apache is running and this ises the service module.")); main.append(addParagraph("To run this playbook, we would use a command like")); main.append(addSyntax("ansible-playbook sample.yml")); main.append(addParagraph("This will generate some output as shown below.")); main.append(addImageWithCaption("./images/sample-playbook.jpg", "The output we get from running this sample playbook.")); main.append(addParagraph("Errors are shown in red , with successes shown in green. In thi case, it looks like there might be an issue with updating Apache on one of the hosts so this is really useful information and it can help you when you are troubleshooting an issue.")); main.append(addParagraph("There is also a handy summary at the end which will tell you where issues are found and in this case, it is telling is that for the Ubunti machine, one task completed successfully but we also had one that failed. On the other host, 10.0.1.184, all of the tasks completed successfully")); main.append(addHeader("Working with Hosts and Variables in Ansible")); main.append(addParagraph("The image below shows a sample hosts file.")); main.append(addImageWithCaption("./images/hosts.jpg", "A sample hosts file.")); main.append(addParagraph("THis defines the hosts tha we will manage with Ansible. Notice that we have groups and these are denoted with square brackets, an example of this is the southeast group.")); main.append(addSyntax("[southeast]")); main.append(addParagraph("In the southeast group, er have defined two hosts which are localhost and other1.example.com. We also have some Ansible variables and we can set these for each of the hosts. For example, we have set the variable ansible_connection to local for localhost. For other1.example.com, we set the ansible_connection to ssh and we also set the value of ansible_user to ubuntu.")); main.append(addParagraph("This allows us to define the connection methods for different hosts and it is a good illustration of how the concept of ansible variables, in general, give us quite a bit of flexibility. Variales can also be used in other places such as in a playbook.")); main.append(addParagraph("As you can also see, we can use several different connentions for naming the hosts. In addition to localhost, the sample hosts file also referes to hosts via an ip address or a fully qualified domain name such as other1.example.com.")); main.append(addParagraph("We can also refer to hosts via a previously defined group name which allows us to create groups of groups. An example of this is in the group")); main.append(addSyntax("[east:children]")); main.append(addParagraph("which has two entries, both of which are also groups, southeast and northeast. Again, this is giving us some flexiblity because we can have relatively fine-grained groups such as southeast and northeast and more general groups such as east:children so we could, for example, run some tasks against just the southeast group or just the norheast group but if we want to run a task against both groups at the same time, you can use the east:children group to do that.")); main.append(addParagraph("You might think that it would be useful to create one group that includes all of the subgroups and that is certainly a useful thing to do, but it's actually not necessary. The chances are that at some point, you will want to be able to treat all of the hosts as a single group and that is such a useful thing to do that Ansible already defines a kind of pseudo group that allows you to do that. For that reason, defining a group yourself that encompasses all hosts would be redundant.")); main.append(addHeader("Working with Code in Ansible")); main.append(addParagraph("Running code in Ansible means working with modules and you can get a list of these from the Ansible Documentation. These are the modules that come built into Ansible but you can get others from sites such as Ansible Galaxy. You could also try Googling something like 'Ansible Modules'.")); main.append(addParagraph("Just a brief aside, I came across an interesting main when I tried to Google 'Ansible Modules List'. It's under the title 10 Ansible modules you need to know by Shashank Hedge (September 11 2019). Most of the information here can be found in the official documentation, but Sheshank provides a concise and friendly introduction to each of the 10 modules with a few examples so it's a good introduction to modules in general. It also provides a link to the documentation.")); main.append(addParagraph("I think it is also useful in that it gives you an introduction to what you can do with modules so I would recommend checking it out if you are new to Ansible.")); main.append(addParagraph("Let's look at an example, the find module. The documentation provides a synopsis, a list of options, some examples and the return values.")); main.append(addParagraph("In addition to using a module in a playbook, we can also use modules in ad hoc queries. As an example, let's say we want to use the find module to look for files in a given directory. The syntax for that would be")); main.append(addSyntax("ansible localhost -m find -a \"paths=Downloads file_type=file\"")); main.append(addParagraph("This looks fairly strightforward, the -m indicates this is a module and the -a gives us the module options. For more informaion on ad hoc commands, see the page entitled Introduction to ad hoc commands in the online documentation. You might also find the main Ansible AD HOC Command Examples - Ansible Cheat Sheet by Sarav AK (January 6 2023) to be both interesting and useful and it provides a number of examples.")); main.append(addParagraph("THe syntax is copied from the course video and I had to tweak it on my Raspberry Pi to provide the full path name.")); main.append(addImageWithCaption("./images/find.jpg", "The syntax for find as an ad hoc command on a Raspbrerry Pi.")); main.append(addParagraph("This could be a peculiarity of Ansible on the Mac or, more likely, it is because of a setting in Ansible or on the computer (for example, it might work if Downloads was added to PATH). In any case, we can see that the output is almost all green indicating success, we don't see anything in red to indicate a failure and we do have a line of info in purple indicating a warning which is telling us that we are not parsing an inventory which is fine, because we only wanted to run the command on the localhost.")); main.append(addParagraph("The format for the output is JSON and we have a couple of useful bits of info near the top. We can see that changed shows false, which indicates that nothing was changed by this command (which is what you would expect for a find command), examined is 1 because there was only one file found in the Downloads folder and there weasn't anything that is not a file found. If we add a directory to the folder and run the same command again, we can see that the output changes to reflect that.")); main.append(addImageWithCaption("./images/find1.jpg", "The same ad hoc command with slightly more interesting output.")); main.append(addParagraph("This time, we can see that two items were examined and 1 was matched. Notice as well that although you may not be familiar with a lot of the values shown here, you can see that there is an awful lot of detail so this can be quite useful.")); main.append(addHeader("Working with Playbooks in Ansible")); main.append(addParagraph("One problem you will often find with this type of course is that you may not be able to follow on along with everything simply because you may not have multiple hosts to experiment with. I have a couple of Raspberry Pi including the dev machine I installed Ansible on and I could add two or three more. I also have my Windows PC. I will, as far as possible, try to run the examples in a live environment using these hosts and I will explain that in more detail as we go along.")); main.append(addParagraph("Bear in mind that ad hoc queries can be useful, but they don't really allow you to leverage the power of Ansible because really they are just fancy ways of running commands on a system. The find example is something that can be done on a Linux machien without using Ansible. Conversely, you can create a playbook that operates on a single host and so are just a different way of running an ad hoc query but this has the advantage of demonstrating some of the key points in Ansible in a way that you can easily replicate and experiment with, even if you only have access to one device.")); main.append(addParagraph("So let's go ahead and write our first playbook. You can do this in any text editor so I will use vim on my Pi and we will start with the three dashes that indicate the file is YAML, a name for the play and a list of hosts - a short list in this example!")); main.append(addInsetCodeListing(["---", " - name: \"My first play\"", " hosts: localhost"])); main.append(addParagraph("We are going to add two tasks for this playbook, the first is going to test that a machine is reachable so our next line will be")); main.append(addSyntax(" tasks:")); main.append(addParagraph("I believe indentation is important in the playbooks although I don't know the details of that, so for now I will copy the format used by Anthony. I will also use the same blank lines but I will insert a copy of the finished code so you will be able to see that.")); main.append(addParagraph("For the first task, we will give it the name, test reachability and we will specify the ping module. We don't need to do anything else, the ping module will ping all of the hosts - in this case all one! The task in full is therefore:")); main.append(addInsetCodeListing([" -name: \"test reachability\"", " ping:"])); main.append(addParagraph("The next task is a little bit more complicated in that we are going to install a package called stress which is used for stress testing a machine. We will give it the name install stress but where the course video uses the homebrew module, we will need to use a Raspberry Pi friendly module. Looking up how to install stress on a Raspberry Pi in Google shows that it is installed with the apt-get command so I will try it with the apt module. Unlike the first task, we will need a little bit more than the module name, we also need to provide a name property with the name of the package to be installed and we will also use a state property which will check whether the package is already installed. The full task is:")); main.append(addInsetCodeListing([" -name: \"install stress\"", " apt:", " name: stress", " state: present"])); main.append(addParagraph("So our final (not really as we will shortly see) file looks like this.")); main.append(addImageWithCaption("./images/playbook1.jpg", "Our first playbook, we have called the file first.yml.")); main.append(addParagraph("To run this, we will use the ansible-playbook command:")); main.append(addSyntax("ansible-playbook first.yml")); main.append(addParagraph("which gives us the following output.")); main.append(addImageWithCaption("./images/playbook2.jpg", "The output from our first playbook.")); main.append(addParagraph("This looks like we just got a bunch of errors which we did. The implication here is that we are missing a hyphen in front of tasks but actually, we just need to indent it properly. This was deliberate because it is demonstrating the fact that indentation is important and it's useful to see what sort of output we get this an error like that because it is something you will probably see quite often when you are starting out.")); main.append(addParagraph("If I add the proper indentation and tidy things up, the file now looks like this.")); main.append(addImageWithCaption("./images/playbook3.jpg", "Our first playbook, with, we hope, the proper indentation.")); main.append(addParagraph("We can use the same command to run this again and you might find yourself doing this several times in order to get the correct indentation but once you have, we should then start to see the output from running the play and it should look like this.")); main.append(addImageWithCaption("./images/playbook4.jpg", "The output from our correctly indented first playbook.")); main.append(addParagraph("When you actually run this, it takes a few moments but if there are no syntax errors, you will see play appear with the name of the play and it will then start to run tasks. The fact that we still got an error is sort of incidental in that we are interested in the running a task to demonstrate that process rather than the specific task. In this case, the install failed because we don't have root privileges. We saw a become-user parameter so we can try that but that doesn't help because become-user is not in the list of parameters for the apt module. Rather helpfully, it does give you a list of those parameters but a more sensible approach might have been to check the documentation for the apt module.")); main.append(addParagraph("In this case, we have a simpler option which is to run the command with sudo.")); main.append(addSyntax("sudo ansible-playbook first.yml")); main.append(addParagraph("The output I got with the become-user parameter and the sudo option is shown below.")); main.append(addImageWithCaption("./images/playbook5.jpg", "Fixing the permissions error so we can install the stress package with the playbook.")); main.append(addParagraph("You might notice that when I first ran the command with sudo, I forgot to take out the become-user parameter and so the task failed again with the same error. I also checked the version of stress and saw that it was an unrecognised command so this shows that the install had failed but we can also see that in the play recap. After removing this and trying again with sudo, we don't see the error, we do see tha the play recp us telling us that everything is okay, there are no failures but there is one change and we can confirm that the change is that stress was installed by checking the version number again. Now we can see that it is installed and has version number 1.0.4.")); main.append(addParagraph("Using sudo might not always be an option so an alternative approach would be to switch over to root (if you can) and create the playbook itself with root privileges or you might change owner to root or change permissions on apt (not a recommended option at all)!!")); main.append(addParagraph("So that's our first playbook and it achieved quite a lot. It checked hosts for reachability and then successfully installed a software package on that host.")); main.append(addHeader("Challenge: Write a Playbook in Ansible")); main.append(addParagraph("This shouldn't be too much of a challenge. What we want to do is to create two text files in a any directory. We then want to write a playbook that operates on the localhost only and performs two tasks. For the first task, we want to ping the machine and then gets some information on those two files.")); main.append(addParagraph("There isn't anything here we haven't done before so I will get started by creating a directory in the user's home directory on my dev machine. In this directory, I have two files called challenge1 and challenge2 and to give them some content, I have added the the transcript to the challenge and solution videos to those files.")); main.append(addParagraph("To write the script, I will start by copying over the first.yml file to a file called challenge.yml because this is essentially the same playbook, we just need to change that second task. In order to write the second task, I will check out the docs for the find module. You might recall that we already performed this task as an ad hoc command so we need to fgiure out how to phrase that as a task but the examples in the documentation should make that very easy to do.")); main.append(addImageWithCaption("./images/challenge1.jpg", "My sloution for the playbook challenge. In the next image, you will see if it works.")); main.append(addParagraph("As before, we can run this with the ansible-playbook command")); main.append(addSyntax("ansible-playbook challenge1.yml")); main.append(addParagraph("I have deliberately not tested this yet so it is possible that this won't work if there is a snafu in the syntax somewhere but that's part of the learning process so if it doesn't work, you will see how I go about troubleshooting it!")); main.append(addImageWithCaption("./images/challenge1.jpg", "My solution for the playbook challenge. It worked, sort of!")); main.append(addParagraph("Okay, so that did work but the output we got wasn't really what I was expecting. However, since we didn't see any errors, we can assume that the syntax is correct.")); main.append(addParagraph("Let's just remind ourselves of the syntax we used for the ad hoc query. Comparing this to the task, there are two things that jump out at me. Once is that I had added a trailing slash after the directory name which I think is not likely to be the issue. The second is that we missed the file_type parameter. Going bacl to the documentation, it looks as though the file_type parameter defaults to file so that shouldn't be an issue.")); main.append(addParagraph("We can use that ad hoc query to troubleshoot by simply running it again with the path changed to ~/any and remove the file_type parameter and that gives us the expected output.")); main.append(addSyntax("ansible localhost -m find -a \"paths=~/any\"")); main.append(addParagraph("We can also try that with the trailing / and we also get the correct output.")); main.append(addSyntax("ansible localhost -m find -a \"paths=~/any/\"")); main.append(addParagraph("Next, I am comparing the task in challenge1.yml to the examples in the docs and I sis notice that it uses the parameter paths rather than path so that looks like an error. Note that if this was actually an error, we would expect to see ansible throw up an incorrect parameter error but it does look wrong. I checked the paths parameter and found that path is an alias for paths so either should work. Just in case this is a weird foible of the Pi, I changed it but as I said, we didn't get an incorrect parameter error so it's unlikely that this will make a difference and it doesn't/ As a matter of interest, I changed this to an invalid value, pathway, to demonstrate what would have happened if path was unacceptable and the output is shown below.")); main.append(addImageWithCaption("./images/challenge_error.jpg", "This is what we see if we don't provide a valid name for the path/paths parameter.")); main.append(addParagraph("Actually, I had forgotten that paths is a required parameter for find so we actually get an error message that states that the paths argument is missing. I am a little surprised that we didn't also get an incorrect module error but I am guessing that this is because the playbook stops as soon as an error is found. To test this, I have added the correct paths parameter before the incorrect pathway parameter and rerun the playbook and this time, the error is")); main.append(addImageWithCaption("./images/challenge_error1.jpg", "This is what we see if we don't provide a valid name for the path/paths parameter.")); main.append(addParagraph("So that's the error we ad expected to see! However, it doesn't get us any closer to finding a solution.")); main.append(addParagraph("I then went back to the docs and noticed that their path name is in quotes which is something I had missed and so I put these quotes in and re-ran the playbook but that made no difference. I then realised that I may have been a bit blinkered in adapting the task to the format used for other tasks and I realised that the syntax is different for find. Rather than starting the task with -name, it should start with -find. As a kind of desperate hail-mary play, I scrapped the idea of searching in the any folder and decided to recreate one of the examples.")); main.append(addImageWithCaption("./images/challenge_example.jpg", "My challenge playbook with the find task amended to mimic an example from the docs.")); main.append(addParagraph("This is actually giving us the same sort of output so now I am beginning to think that this is how it is supposed to work as a task but that seems unlikely so I am now giving up and will check the course solution to see if I missed anything.")); main.append(addHeader("Solution: Write a Playbook in Ansible")); main.append(addParagraph("As a result of following along with the solution, I went back to my original version of the file (I noticed I had forgotten to change the name of the play so I fixed that). The only substantial difference is that I have added the file_type parameter and I am still seeing the same results whereas in the course wideo, the results seem to be correct.")); main.append(addImageWithCaption("./images/solution.jpg", "This is the version I ended up with after watching the solution video.")); main.append(addParagraph("I am still getting the same output although I do get the files returned when running this is an ad hoc query. Note that I called the playbook Challenge1 and the reason for that s that I want to extend the challenge to get it to work on my Raspberry Pi web server. I have added this to the hosts file on the Ansible node (the dev server where Ansible is installed) but I get an error with that see below.")); main.append(addImageWithCaption("./images/challenge2_error.jpg", "My web server shows as unreachable when I try to run ansible tasks on it from the challenge playbook.")); main.append(addParagraph("I will come back to this challenge later! One final point on this, you can add a -v option to ansible-project to get some more info about what's going on when it runs a playbook. There where a couple of interesting things I noticed with this option, the first is the fact that it mentions where it is getting the ansible.cfg file from which is, in this case")); main.append(addSyntax("/home/developer/ansible-project")); main.append(addParagraph("There is an ansible.cfg file there but it is empty - remember that we used the command")); main.append(addSyntax("ansible-config init --disabled > ansible.cfg")); main.append(addParagraph("to generate this in the /etc/ansible folder. The second is that we do get the output for the find command so this suggests that there might have been an issue with that config file. I generated a new one in the project folder but it doesn't seem to have made any difference, I still have to use the -v option to get the proper output and the web server is still unreachable.")); main.append(addParagraph("The image below shows the output I get from running")); main.append(addSyntax("ansible-playbook challenge2.yml -v")); main.append(addImageWithCaption("./images/challenge2_error1.jpg", "The output from running the playbook again with the -v option added.")); main.append(addParagraph("The course video does actually make mention of the config file and notes that the output from the find command is nicely formatted due to the settings in the ansible.cfg file. I have amended my file to match so it now looks like this.")); main.append(addImageWithCaption("./images/challenge2_error2.jpg", "The modified ansible.cfg file.")); main.append(addParagraph("I'm still not seeing any output from find when running the playbook without the -v option, but I do see the nicely formatted output when I do, as shown below.")); main.append(addImageWithCaption("./images/challenge2_error3.jpg", "The output I get running the playbook with the -v option and with the modified ansible.cfg file.")); main.append(addParagraph("I will leave that as it is for now, but I will come back to this challenge later.")); main.append(addHeader("Ansible and Remote Management")); main.append(addParagraph("If you cd to your home directory and get a listing which includes hidden files (with ll -a or ls -a), you will see that there is a file with the name .ssh which can be added to the authorised keys on a remote system as a way of configuring ssh. On my system, that file is empty which may be why I can't access my web server via ansible! I can generate a key using the command")); main.append(addSyntax("ssh-keygen")); main.append(addParagraph("and accept the default options. This will store both a private and public key in the .ssh folder with the id_rsa and id_rsa.pub. We can open up the id_rsa.pub folder and copy the contents and then copy that into the authorized_keys file on the system you want to access. I have tried that by generating an ssh key for the developer user (on the ansible node) and putting that into the known hosts folder for the ansible user on the web server but it still seems to be unreachable.")); main.append(addHeader("Ansible for Orchestration")); main.append(addParagraph("In spite of the fact that Ansible is fairly straightforward and easy to use, it can be vaery powerful and this is particularly true with orchestration. With orchestration, we are not just automating tasks, but managing the automation of many tasks and their dependencies. In this context, dependencies refers to task dependencies so not something like, for this program to run, you must have this library installed but rather, for this task to run, these conditions have to be met. Let's see an example of that in Anthony's orchestration.yaml file. The first few lines of this")); main.append(addInsetCodeListing(["name: \"Orchestration Example\"", "hosts: logicservers", "serial: 1"])); main.append(addParagraph("This indicates that we want to run some taskes on the group logicservers as defined in host and the serial directive indicates that we want to operate on, in this case, 1 server at a time so we will perform all the necessary tasks on each server before moving on to the next. This might be important for business reasons where you want disruption to be minimized because that only one server is being shut down at any one time.")); main.append(addParagraph("For each server we have several tasks. These are just dummy tasks at this moment so when the playbook is run, it should let us know that the tasks have been completed but it is not actually carrying out the tasks. The tasks will shutdown the server, upgrade the firmware, start it up again and finally verify it. The tasks look like this:")); main.append(addInsetCodeListing(["- name: \"Shutdown server1\"", "debug:", "\"Shutdown {{ inventory_hostname }}\""])); main.append(addParagraph("The playbook uses the debug module to print out a message so at this point, we are not concerning ourselves with which modules would need to be invoked for these tasks to be performed for real. For reference, the full playbook is shown below. You could think of this as a template where we later add the appropriate modules to make these things happen on each of our servers.")); main.append(addImageWithCaption("./images/orchestration.jpg", "A playbook demonstrating orchestration with Ansible.")); main.append(addParagraph("Notice that in the message we output, we are using a variable, inventory_hostname. Each time the tasks run on a given host, this will print out the name of that host.")); main.append(addParagraph("The next thing we want to do is to look at the hosts file, shown below.")); main.append(addImageWithCaption("./images/hosts1.jpg", "The hosts file with the logicservers group.")); main.append(addParagraph("The four servers in the logicservers group are all using the local loop back address so these are simulating servers. What that means is that basically, it's an ip address we can send our commands to but there isn't actually a server at the other end of that so we can't perform tasks but we can get the servers from the group and display messages saying the server has been stopped, started and so on. This means that we can demonstrate that ansible is finding the servers that we want to specify and is able to communicate with it. Notice as well that for this we are specifying the connection as local.")); addSidebar("linux");