{"id":3321,"date":"2018-05-07T18:38:40","date_gmt":"2018-05-07T23:38:40","guid":{"rendered":"https:\/\/dev.iachieved.it\/iachievedit\/?p=3321"},"modified":"2020-07-04T16:29:24","modified_gmt":"2020-07-04T21:29:24","slug":"ansible-and-aws-part-1","status":"publish","type":"post","link":"https:\/\/dev.iachieved.it\/iachievedit\/ansible-and-aws-part-1\/","title":{"rendered":"Ansible and AWS &#8211; Part 1"},"content":{"rendered":"<p>It&#8217;s been some time since I&#8217;ve posted to this blog, which is a shame, because I do indeed enjoy writing as well as sharing what I&#8217;ve learned with the hope it helps someone else.  The truth is, however, that writing quality articles takes a lot of time.  I also suffer from that thought that surely someone else out there has written on a given topic and done a much better job that I could do.  And the reality is, someone probably has, but that shouldn&#8217;t stop me from doing something I enjoy.<\/p>\n<p>So with that, here&#8217;s the first of what I hope to be several more articles on how to harness the power of <a href=\"https:\/\/en.wikipedia.org\/wiki\/Ansible_(software)\">Ansible<\/a> with <a href=\"https:\/\/en.wikipedia.org\/wiki\/Amazon_Web_Services\">AWS<\/a>.  I firmly believe in <i>learning by doing<\/i> and I also believe that <i>at first<\/i>, you should take few shortcuts.  What I mean by that is this:  are you the type of person that&#8217;s saddened by the fact that some schools allow calculators in grade school?  Or, even better, not bothering teaching someone how to use a library?  And by that I mean how to get off of your ass, go to the library, navigate the stacks, and find a wealth of information you just might not find on Google?  To be fair, I&#8217;m not really the <a href=\"https:\/\/en.wikipedia.org\/wiki\/You_kids_get_off_my_lawn!\">Get off my lawn!<\/a> type, but it does irk me somewhat when I see people automating things they don&#8217;t have a fundamental understanding of in the first place.<\/p>\n<p>With that, my approach will be to start off with doing <i>some things manually<\/i>.  Then I&#8217;ll illustrate how to take the manual steps and automate them.  Sometimes it isn&#8217;t <i>worth<\/i> automating something; usually it&#8217;s something you do infrequently enough and the energy required to automate it outweighs the benefit.  I usually use the DevOps equivalent of the <a href=\"https:\/\/en.wikipedia.org\/wiki\/Rule_of_three_(computer_programming)\">three times rule<\/a>.  If I&#8217;ve done something by hand three times in a row, it&#8217;s a good candidate for automation, but not necessarily before then.<\/p>\n<h2>Ansible<\/h2>\n<p><a href=\"http:\/\/docs.ansible.com\/ansible\/latest\/index.html\">Ansible<\/a> is just so wonderful.  If you&#8217;ve never heard of it, it falls into the general class of automation tools that allow you to <i>codify a set of instructions<\/i> for <i>doing or building something<\/i>.  I tried to make that as abstract as possible, because the <i>something<\/i> could be:<\/p>\n<ul>\n<li>installing a web server application<\/li>\n<li>updating a configuration file<\/li>\n<li>adding a new user to a set of servers<\/li>\n<li>applying a new software patch<\/li>\n<li>building a virtual machine<\/li>\n<\/ul>\n<p>and so on.  It&#8217;s almost as if these sets of instructions are like <i>playbooks<\/i> or <i>recipes<\/i> or <i>cookbooks<\/i>.  So much so many of the popular tools call their files variations of these phrases.  Ansible uses <i>tasks<\/i>, <i>roles<\/i>, and <i>playbooks<\/i>.<\/p>\n<h3>Installing Ansible<\/h3>\n<p>To use a tool you&#8217;ll have to install it.  I develop on a Mac and can tell you that <code>brew install ansible<\/code> works great (if you don&#8217;t have <a href=\"https:\/\/brew.sh\/\">Brew<\/a> installed on your Mac you should be ashamed of yourself).  As of this writing I&#8217;m using Ansible version 2.5.2, as that is what <code>brew<\/code> installed.<\/p>\n<h3>Using Ansible<\/h3>\n<p>Ansible <i>is<\/i> easy to use, though at first it may not seem like it.  I know you <i>want<\/i> to cut to the chase and just figure out how to automate that thing and you want it <i>now<\/i>.  Why do I have to do all this stuff just to do this thing I think should be easy!  I can hear you say it because I say it all the time.  That initial learning curve is always a pain in the ass.<\/p>\n<p>Here are some simple hints to get started as quickly as possible.  First, create a folder called <code>ansible-helloworld<\/code> and use it as home base.  Then, you&#8217;re going to create two files:  <code>ansible.cfg<\/code> and <code>ansible_hosts<\/code>.<\/p>\n<p><code>ansible.cfg<\/code>:<\/p>\n<p>[code lang=text]<br \/>\n[defaults]<br \/>\ninventory=ansible_hosts<br \/>\nhost_key_checking=False<\/p>\n<p>[ssh_connection]<br \/>\npipelining=True<br \/>\n[\/code]<\/p>\n<p>Now, for <code>ansible_hosts<\/code>, this is where you put the names of your servers that you&#8217;re going to be applying your tasks to.  We don&#8217;t have any hosts yet, so we need to create one.  You can, of course, apply Ansible tasks to your own Mac, but we won&#8217;t do that.  You could also install VirtualBox and create a virtual machine to play with, but we&#8217;ll skip that too and go straight to AWS.<\/p>\n<h2>Amazon Web Services<\/h2>\n<p>Like Ansible, <a href=\"https:\/\/aws.amazon.com\/\">AWS<\/a> is awesome.  I have on my bookshelf a copy of <a href=\"https:\/\/www.amazon.com\/Cloud-Your-Service-Enterprise-Computing\/dp\/1935182528\">The Cloud at Your Service<\/a>, where I was first introduced to AWS.  This is <i>awesome<\/i>, I thought.  Rather than hosting a webserver from this old Sony Vaio (whatever happened to that thing anyway?), now I can create a VM in <i>the cloud<\/i>.  Forgive those that are middle-aged and still look back at where computing was, and where it is today, and think <i>wow<\/i>.<\/p>\n<p>Since AWS was my first love, I&#8217;ve tended to stick with it, and to be honest, for good reason.  The product has become cheaper over the years even while adding more services such as S3, Route 53, VPCs (there was a time VPCs didn&#8217;t exist), and now things like RDS and much more.  Yes, there are other cloud computing platforms out there (Azure, Google, Linode, Digital Ocean, Scaleway, and many more), and given the need I <i>could<\/i> go and probably master them all, but for now I&#8217;m quite content with AWS.  If you find yourself using another platform, Ansible <i>will still work<\/i>, you just may need to tweak some of the techniques and steps presented here.<\/p>\n<p>Finally, this isn&#8217;t an AWS tutorial by any means, though we will get into some handy Ansible modules that take some of the drudgery out of creating instances and databases (in which case if you&#8217;re on another platform you will definitely need to find some other module that does the equivalent).  So if you don&#8217;t know how to create an EC2 instance and set some basic security groups, you might start <a href=\"https:\/\/aws.amazon.com\/getting-started\/\">here<\/a>.<\/p>\n<p>One last note before we get back to Ansible, and that\u2019s I\u2019ll be using Ubuntu Server 16.04, also known as Xenial Xerus. You may prefer to work with CentOS, Red Hat Enterprise, or plain Debian. While Ansible is generally distribution agnostic, some things may not work exactly the same.<\/p>\n<h2>Back to Ansible<\/h2>\n<p>Alright, I hope you created an EC2 instance in AWS. I created one in the Ohio region (us-east-2), and as promised used Ubuntu 16.04 LTS (ami-916f59f4). For basic tutorials go ahead and use the t2.micro instance, it\u2019s a lot cheaper. You should also have ensured that you have access to this instance, that is, your public SSH key was installed on this new instance and your security groups permit you to access it (protecting your EC2 infrastructure with security groups is another great topic that I hope to get to one day).<\/p>\n<p>My instance was given a public IP of 18.188.72.168 and my key was installed, so <code>ssh ubuntu@18.188.72.168<\/code> logged me right in:<\/p>\n<p>[code lang=text]<br \/>\nssh ubuntu@18.188.72.168<br \/>\nWelcome to Ubuntu 16.04.4 LTS (GNU\/Linux 4.4.0-1052-aws x86_64)<br \/>\n[\/code]<\/p>\n<p>Hot damn.<\/p>\n<p>One of the unfortunate things about the default AMI for Ubuntu 16.04 is that it doesn\u2019t include <code>python<\/code> on it. Now granted, Python has only been around since 1991, but still, you\u2019d think it was worth including as a default package in an Ubuntu instance. This is important because Ansible requires <code>python<\/code> to be on the target instance (we&#8217;re talking specifically about Linux instances here).  So, until we show you how to create your own AMI, we need to install Python on this VM.  Easy enough:<\/p>\n<p>[code lang=text]<br \/>\nubuntu@ip-172-31-22-141:~$ sudo apt-get install python<br \/>\nReading package lists&#8230; Done<br \/>\nBuilding dependency tree<br \/>\nReading state information&#8230; Done<br \/>\nThe following additional packages will be installed:<br \/>\n  libpython-stdlib libpython2.7-minimal libpython2.7-stdlib python-minimal<br \/>\n  python2.7 python2.7-minimal<br \/>\nSuggested packages:<br \/>\n  python-doc python-tk python2.7-doc binutils binfmt-support<br \/>\nThe following NEW packages will be installed:<br \/>\n  libpython-stdlib libpython2.7-minimal libpython2.7-stdlib python<br \/>\n  python-minimal python2.7 python2.7-minimal<br \/>\n0 upgraded, 7 newly installed, 0 to remove and 0 not upgraded.<br \/>\nNeed to get 3,877 kB of archives.<br \/>\nAfter this operation, 16.6 MB of additional disk space will be used.<br \/>\nDo you want to continue? [Y\/n]<br \/>\n[\/code]<\/p>\n<p>Of course we entered <em>Y<\/em>.<\/p>\n<p>Now for the fun part, some Ansible!<\/p>\n<h3>Your `ansible_hosts` File<\/h3>\n<p>In your <code>ansible_hosts<\/code> file just put this:<\/p>\n<p>[code lang=text]<br \/>\n[all]<br \/>\n18.188.72.168<br \/>\n[\/code]<\/p>\n<p>Now, that&#8217;s the public IP address of <i>my<\/i> instance, and you aren&#8217;t going to use that, you&#8217;ll use your instance&#8217;s IP.  The point is your <code>ansible_hosts<\/code> file needs either an IP or FQDN of the machine(s) that you&#8217;re going to be working with.  It&#8217;s that simple really.<\/p>\n<h3>Your First Playbook<\/h3>\n<p>You&#8217;re ready for your first playbook.  Call it whatever your like, we&#8217;ll just use <code>playbook.yml<\/code> for now.  And here we go:<\/p>\n<p><code>playbook.yml<\/code>:<\/p>\n<pre class=\"lang:yaml\">\n---\n- hosts: all\n  remote_user:  ubuntu\n  become:       true\n  become_user:  root\n  become_method: sudo\n  tasks:\n  # Set our hostname\n  - name:  Set our hostname\n    hostname:\n      name:  ansible-helloworld\n<\/pre>\n<p>That&#8217;s it!  See you in Part 2!<\/p>\n<h3>Ansible Modules<\/h3>\n<p>Just kidding.  We&#8217;ll add a bit more to our first playbook, but first, let&#8217;s take a look at the syntax of the file.  The extension <code>.yml<\/code> is on purpose as Ansible roles, tasks, variable configurations, playbooks, etc. are all written using <a href=\"http:\/\/yaml.org\/\">YAML<\/a>, a &#8220;human friendly data serialization standard for all programming languages.&#8221;  YAML&#8217;s syntax is indentation-driven (like Python), so you need to make sure everything is properly indented or you&#8217;ll get incomprehensible errors.  For a quick check you can install something like <a href=\"https:\/\/github.com\/adrienverge\/yamllint\"><code>yamllint<\/code><\/a> (availabe on the Mac with <code>brew install yamllint<\/code>).<\/p>\n<p>For a brief moment we&#8217;ll ignore the <code>hosts<\/code> and <code>remote_user<\/code> tags and focus instead on the <code>tasks<\/code> section.  We have one task listed here:  <code>hostname<\/code>.  Ansible provides <a href=\"http:\/\/docs.ansible.com\/ansible\/latest\/modules\/hostname_module.html\">great reference documentation<\/a> to look up all of the things you can do with this module.  This is a simple one as it only takes one parameter, <code>name<\/code>.<\/p>\n<p>Now, don&#8217;t confuse the <code>name<\/code> parameter of the <code>hostname<\/code> module with that of the <code>name<\/code> parameter of the <code>task<\/code>.  This used to trip me up.  It&#8217;s easier to see task entries when there are more than one, say let&#8217;s do that.  I&#8217;m going to be very specific on how this is written (and then tell you not to do it):<\/p>\n<pre class=\"lang:yaml\">\n---\n- hosts: all\n  remote_user:  ubuntu\n  become:       true\n  become_user:  root\n  become_method: sudo\n  tasks:\n  # Set our hostname\n  - name:  Set our hostname\n    hostname:\n      name:  ansible-helloworld\n\n  - apt:\n      name:  htop\n      state: present\n      update_cache: yes\n    name:  Install htop\n<\/pre>\n<p>Notice how the <code>apt<\/code> module (which also has <a href=\"http:\/\/docs.ansible.com\/ansible\/latest\/modules\/apt_module\">great reference documentation<\/a>) is the second list entry under <code>tasks<\/code>; we know this because of the dash.  But then there is some indentation (two spaces) and three key-value pairs (<code>name<\/code>, <code>state<\/code>, and <code>update_cache<\/code>).  <i>Then<\/i> the indentation is backed out and we have <code>name<\/code>.  The second <code>name<\/code> here (which has the value <code>Install htop<\/code>) is associated with the <i>task<\/i> at hand (in our case, <code>apt<\/code>).<\/p>\n<p>If you&#8217;re confused, don&#8217;t worry.  YAML (and any serialization syntax) can be baffling and bewildering.  But if you follow some basic conventions and keep playbooks simple (which will involve breaking things down over time into roles and other tasks), it&#8217;ll be easy to read.<\/p>\n<p>Now, what you aren&#8217;t supposed to do is put the <code>name<\/code> parameter for the task at the bottom.  Clean that up!<\/p>\n<pre class=\"lang:yaml\">\n---\n- hosts: all\n  remote_user:  ubuntu\n  become:       true\n  become_user:  root\n  become_method: sudo\n  tasks:\n  # Set our hostname\n  - name:  Set our hostname\n    hostname:\n      name:  ansible-helloworld\n\n  - name:  Install htop\n    apt:\n      name:  htop\n      state: present\n      update_cache: yes\n<\/pre>\n<p>Much better.  Now, let&#8217;s run it!<\/p>\n<p><code>ansible-playbook playbook.yml<\/code><\/p>\n<p>[code lang=text]<br \/>\nPLAY [all] *********************************************************************<\/p>\n<p>TASK [Gathering Facts] *********************************************************<br \/>\nok: [18.188.72.168]<\/p>\n<p>TASK [Set our hostname] ********************************************************<br \/>\nchanged: [18.188.72.168]<\/p>\n<p>TASK [Install htop] ************************************************************<br \/>\nchanged: [18.188.72.168]<\/p>\n<p>PLAY RECAP *********************************************************************<br \/>\n18.188.72.168              : ok=3    changed=2    unreachable=0    failed=0<br \/>\n[\/code]<\/p>\n<h3>Idempotency<\/h3>\n<p>In general, an operation is <i>idempotent<\/i> if it produces the <i>same result<\/i> even after being executed multiple times.  This is an important property to strive for in Ansible playbooks, though my experience is that it isn&#8217;t always achievable.  It may appear on the surface that our above playbook is idempotent though it isn&#8217;t.  For argument&#8217;s sake let&#8217;s look at what we would desire from an idempotent playbook:<\/p>\n<ul>\n<li>if the hostname isn&#8217;t set to <code>ansible-helloworld<\/code>, set it to <code>ansible-helloworld<\/code><\/li>\n<li>if <code>htop<\/code> isn&#8217;t installed, install it, otherwise do nothing<\/li>\n<\/ul>\n<p>On the first pass of the playbook we see <code>changed=2<\/code>, indicating that two tasks <i>changed something<\/i> on the server.  Indeed, we set the hostname and then installed <code>htop<\/code>.  Let&#8217;s run it again:<\/p>\n<p>[code lang=text]<br \/>\nPLAY RECAP *********************************************************************<br \/>\n18.188.72.168              : ok=3    changed=0    unreachable=0    failed=0<br \/>\n[\/code]<\/p>\n<p>Now, nothing has changed.  Run it again!<\/p>\n<p>[code lang=text]<br \/>\nPLAY RECAP *********************************************************************<br \/>\n18.188.72.168              : ok=3    changed=0    unreachable=0    failed=0<br \/>\n[\/code]<\/p>\n<p>Success!  An idempotent playbook.<\/p>\n<p>Until <code>update_cache: true<\/code> triggers an upgrade of the <code>htop<\/code> application from the underlying repository.  Strictly speaking this one line prevents the playbook from being idempotent, and if it were critical that the server didn&#8217;t receive <i>any<\/i> <code>apt-get<\/code> updates, removing <code>update_cache<\/code> can help but would likely be insufficient.<\/p>\n<h2>Final Thoughts<\/h2>\n<p>We didn&#8217;t cover the <code>remote_user<\/code>, <code>become<\/code>, etc. directives in our playbook, but that&#8217;s okay.  If you remove them the playbook won&#8217;t work at all.  Let&#8217;s add one last interesting twist to the playbook, and that&#8217;s installing more than <code>htop<\/code> to the server.  I happen to like <a href=\"https:\/\/en.wikipedia.org\/wiki\/Z_shell\">Z Shell<\/a> (and even more, <a href=\"http:\/\/ohmyz.sh\/\">Oh My Zsh<\/a>) installed on my servers.  So let&#8217;s install it.<\/p>\n<p>We could create a separate <code>apt<\/code> task (separate from the one installing <code>htop<\/code>) to install <code>zsh<\/code>, but that seems a bit silly.  Let&#8217;s use the <code>with_items<\/code> capability like this:<\/p>\n<pre class=\"lang:yaml\">\n  - name:  Install htop\n    apt:\n      name:  \"{{ item }}\"\n      state: present\n      update_cache: yes\n    with_items:\n      - htop\n      - zsh\n<\/pre>\n<p>Confusing syntax alert!  Believe it or not, it took me some time before this syntax felt natural, because it&#8217;s sort of like writing <code>for<\/code> loops in <a href=\"https:\/\/ant.apache.org\/\">Apache Ant<\/a>.  It&#8217;s <i>clunky<\/i>, and namely because you&#8217;re taking a markup-type language and trying to create logical constructs in it.  It just feels weird at first.<\/p>\n<p>The first thing that&#8217;s weird is the <code>\"{{ item }}\"<\/code> syntax.  What&#8217;s with the quotes? There weren&#8217;t quotes before.  Why those braces?  Two braces? Not one brace?  What is <code>item<\/code>?  Who sets that? Is it magic?<\/p>\n<p>Try removing the quotes.  Go ahead.  You&#8217;ll be sorry you did when you&#8217;re greeted with <b><code>We could be wrong, but this one looks like it might be an issue with missing quotes.  Always quote template expression brackets when they start a value.<\/code><\/b>  Assholes.  So the quotes need to stay there.<\/p>\n<p>The braces mark off template interpolation.  Whatever the variable <code>item<\/code> is set to will be used.  The <code>with_items<\/code> parameter supplies, in turn, each of the items, substituting the items (no pun intended) in the list into the <code>item<\/code> variable.  So in this way you can see how the playbook will run now:<\/p>\n<p>[code lang=text]<br \/>\n# ansible-playbook playbook.yml<\/p>\n<p>PLAY [all] *********************************************************************<\/p>\n<p>TASK [Gathering Facts] *********************************************************<br \/>\nok: [18.188.72.168]<\/p>\n<p>TASK [Set our hostname] ********************************************************<br \/>\nok: [18.188.72.168]<\/p>\n<p>TASK [Install htop] ************************************************************<br \/>\nchanged: [18.188.72.168] =&gt; (item=[u&#039;htop&#039;, u&#039;zsh&#039;])<\/p>\n<p>PLAY RECAP *********************************************************************<br \/>\n18.188.72.168              : ok=3    changed=1    unreachable=0    failed=0<br \/>\n[\/code]<\/p>\n<p>That&#8217;s it for Part 1 of this series!  If you&#8217;ve never worked with Ansible before, I hope this was helpful in getting you started; yes, there are other tutorials out there on Ansible but I wanted to lay the foundation for a series of articles that will walk you through how to go from a simple playbook that installs a couple of packages to roles that can install, configure, and back up MySQL databases and much, much more.<\/p>\n<p>If you have any suggestions for things <i>you&#8217;d<\/i> like to see in an article, let me know by posting a comment.  Thanks!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>It&#8217;s been some time since I&#8217;ve posted to this blog, which is a shame, because I do indeed enjoy writing as well as sharing what I&#8217;ve learned with the hope it helps someone else. The truth is, however, that writing quality articles takes a lot of time. I also suffer from that thought that surely [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":3318,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[21],"tags":[],"class_list":["post-3321","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-devops"],"_links":{"self":[{"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/posts\/3321"}],"collection":[{"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/comments?post=3321"}],"version-history":[{"count":19,"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/posts\/3321\/revisions"}],"predecessor-version":[{"id":4164,"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/posts\/3321\/revisions\/4164"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/media\/3318"}],"wp:attachment":[{"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/media?parent=3321"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/categories?post=3321"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/tags?post=3321"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}