{"id":2651,"date":"2014-11-25T18:51:29","date_gmt":"2014-11-26T00:51:29","guid":{"rendered":"http:\/\/benincosa.com\/blog\/?p=2651"},"modified":"2014-11-25T18:51:29","modified_gmt":"2014-11-26T00:51:29","slug":"ansible-from-osx-to-aws","status":"publish","type":"post","link":"https:\/\/benincosa.com\/?p=2651","title":{"rendered":"Ansible: From OSX to AWS"},"content":{"rendered":"<p>My goal in this post is to go from 0 to Ansible installed on my Mac and then be able to provision AWS instances ready to run Docker containers. \u00a0The code for this post is public on my <a href=\"https:\/\/github.com\/vallard\/Ansible\">github<\/a> account.<\/p>\n<h2>OSX Setup<\/h2>\n<p>I am running OS X Yosemite. \u00a0I use brew to make things easy. \u00a0Install <a href=\"http:\/\/brew.sh\">homebrew<\/a> first. \u00a0This makes it easy to install Ansible:<\/p>\n<p><span class=\"lang:default decode:true  crayon-inline \">brew install ansible<\/span><\/p>\n<p>I have one machine up at AWS right now. \u00a0So let&#8217;s test talking to it. \u00a0First, we create the hosts file:<\/p>\n<pre class=\"nums:false lang:default decode:true\">mkdir -p \/usr\/local\/etc\/ansible \r\nvim \/usr\/local\/etc\/ansible\/hosts<\/pre>\n<p><code><\/code>Now we put in our host:<\/p>\n<p><code><span class=\"lang:default decode:true  crayon-inline\">instance1<\/span><br \/>\n<\/code><\/p>\n<p>I can do it this way because I have a file <span class=\"lang:default decode:true  crayon-inline \">~\/.ssh\/config<\/span>\u00a0\u00a0that looks like the following:<code><br \/>\n<\/code><\/p>\n<pre class=\"lang:default decode:true \">Host instance1\r\nHostname ec2-54-191-126-65.us-west-2.compute.amazonaws.com\r\nUser ubuntu\r\nIdentityFile ~\/.ssh\/awskeypair.pem<\/pre>\n<p>Now we can test:<\/p>\n<pre class=\"lang:default decode:true \">$ ansible all -m ping\r\ninstance1 | success &gt;&gt; {\r\n\"changed\": false,\r\n\"ping\": \"pong\"\r\n}<\/pre>\n<p>Where to go next? I downloaded the free PDF by <a href=\"https:\/\/twitter.com\/lhochstein\">@lhochstein<\/a> that has 3 chapters that goes over the nuts and bolts of Ansible so I was ready to bake an image. \u00a0But first let&#8217;s look at how Ansible is installed on OS X:<\/p>\n<p>The default hosts file is, as we already saw, in <span class=\"lang:default decode:true  crayon-inline \">\/usr\/local\/etc\/ansible\/hosts<\/span>\u00a0. \u00a0We also have a config file we can create in <span class=\"lang:default decode:true  crayon-inline\">~\/.ansible.cfg<\/span>\u00a0. \u00a0More on that later.<\/p>\n<p>The other thing we have is the default modules that shipped with Ansible. \u00a0These are located in <span class=\"lang:default decode:true  crayon-inline \">\/usr\/local\/Cellar\/ansible\/1.7.2\/share\/ansible\/<\/span>\u00a0\u00a0(if you&#8217;re running my same version)<\/p>\n<p>If you look in this directory and subdirectories you&#8217;ll all the modules that Ansible comes with. \u00a0I think all of these modules have documentation in the code, but the easiest way to read the documentation is to run<\/p>\n<p><span class=\"lang:default decode:true  crayon-inline \">ansible-doc &lt;module-name&gt;<\/span><\/p>\n<p>Since we need to provision instances then we can look at the ec2 module:<\/p>\n<pre class=\"lang:default decode:true \">ansible-doc ec2<\/pre>\n<p>This gives us a lot of information on modules you can use to deploy ec2 instances.<\/p>\n<h2>An nginx Playbook<\/h2>\n<p>Let&#8217;s take a step back and do something simple like deploy nginx on our instance using an Ansible Playbook.<\/p>\n<p>I create an ansible file called <span class=\"lang:default decode:true  crayon-inline \">~\/Code\/ansible\/nginx.yml<\/span>\u00a0. \u00a0The contents are the following:<\/p>\n<pre class=\"lang:yaml decode:true\" title=\"nginx.xml\">- name: Configure webserver with nginx\r\n  hosts: instance1\r\n  sudo: True\r\n  tasks:\r\n    - name: install nginx\r\n      apt: name=nginx update_cache=yes\r\n    - name: copy nginx config file\r\n      copy: src=files\/nginx.conf dest=\/etc\/nginx\/sites-available\/default \r\n      notify: restart nginx\r\n    - name: enable configuration\r\n      file: &gt;\r\n        dest=\/etc\/nginx\/sites-enabled\/default\r\n        src=\/etc\/nginx\/sites-available\/default\r\n        state=link\r\n    - name: copy index.html\r\n      copy: src=files\/index.html dest=\/usr\/share\/nginx\/html\/index.html mode=0644\r\n      notify: restart nginx\r\n  handlers:\r\n    - name: restart nginx\r\n      service: name=nginx state=restarted<\/pre>\n<p>I then created the file\u00a0<span class=\"lang:default decode:true  crayon-inline \">~\/Code\/ansible\/files\/nginx.conf<\/span><\/p>\n<pre class=\"lang:sh decode:true \" title=\"nginx.conf\">server {\r\n  listen 80 default_server;\r\n  listen [::]:80 default_server ipv6only=on;\r\n  \r\n  root \/usr\/share\/nginx\/html;\r\n  index index.html index.htm;\r\n  \r\n  server_name localhost;\r\n  \r\n  location \/ { \r\n    try_files $uri $uri\/ =404;\r\n  }\r\n}<\/pre>\n<p>Finally, I created the<span class=\"lang:default decode:true  crayon-inline \"> ~\/Code\/ansible\/files\/index.html<\/span><\/p>\n<pre class=\"lang:xhtml decode:true \" title=\"index.html\">&lt;html&gt;\r\n&lt;head&gt;Ansible test deployment&lt;\/head&gt;\r\n&lt;body&gt;\r\n&lt;h1&gt;Hello Internet. I'm on AWS, provisioned by Ansible&lt;\/h1&gt;\r\n&lt;\/body&gt;\r\n&lt;\/html&gt;<\/pre>\n<p>With this I run the command:<\/p>\n<div><span class=\"lang:default decode:true  crayon-inline \">ansible-playbook nginx.yml<\/span><\/div>\n<div><\/div>\n<div>If you are lucky, you have cowsay installed. \u00a0If so, then you get the cow telling you what&#8217;s happening. \u00a0If not, then you can install it:<\/div>\n<div><span class=\"lang:default decode:true  crayon-inline \">brew install cowsay<\/span><\/div>\n<div><\/div>\n<div>Now, navigate to the IP address of the instance, and magic! \u00a0You have a web server configured by Ansible. \u00a0You can already see how useful this is! \u00a0Now, configuring a web server on an AWS instance is not todays hotness. \u00a0The real hotness is creating a docker container that runs a web server. \u00a0So we can just tell Ansible to install docker. \u00a0From there, we would just install our docker containers and run them.<\/div>\n<h2>A Docker Playbook<\/h2>\n<div>In one of <a href=\"http:\/\/benincosa.com\/blog\/?p=2629\">my previous blog entries<\/a>, I showed the steps I took to get docker running on an Ubuntu image. \u00a0Let&#8217;s take those steps and put them in an Ansible playbook:<\/div>\n<div>\n<pre class=\"lang:yaml decode:true \" title=\"ansible.yml\"># installs docker on Ubuntu 14.04. hosts\r\n- name: Install Docker on an Ubuntu 14.04 host\r\n  hosts: instance1\r\n  sudo: true\r\n  tasks:  \r\n  - name: Get Docker\r\n    apt: name=docker.io update_cache=yes\r\n\r\n  - name: Link docker\r\n    file: &gt;\r\n      dest=\/usr\/local\/bin\/docker\r\n      src=\/usr\/bin\/docker.io\r\n      state=link\r\n\r\n  - name: Get Docker key \r\n    apt_key: keyserver=hkp:\/\/keyserver.ubuntu.com:80 id=36A1D7869245C8950F966E92D8576A8BA88D21E9\r\n\r\n  - name: Add key to Deb source list\r\n    apt_repository: repo=\"deb https:\/\/get.docker.com\/ubuntu docker main\"  state=present\r\n\r\n  - name: Get lxc-docker\r\n    apt: name=lxc-docker update_cache=yes state=present<\/pre>\n<p>Here we use some of the built in modules from Ansible that deal with package management. \u00a0 You can see the descriptions and what&#8217;s available by reading <a href=\"http:\/\/docs.ansible.com\/list_of_packaging_modules.html\">Ansible&#8217;s documentation<\/a>.<\/p>\n<p>We run this on our host:<\/p>\n<p><span class=\"lang:default decode:true  crayon-inline \">ansible-playbook -vvv docker.yml\u00a0<\/span><\/p>\n<p>And now we can ssh into the host and launch a container:<\/p>\n<p><span class=\"lang:default decode:true  crayon-inline \">sudo docker run &#8211;rm -i -t ubuntu \/bin\/bash<\/span><\/p>\n<p>This to me is the ultimate way to automate our infrastructure: \u00a0We use Ansible to create our instances. \u00a0We use Ansible to set up the environment for docker, then we use Ansible to deploy our containers.<\/p>\n<p>All the work for our specific application settings is done with the Dockerfile.<\/p>\n<\/div>\n<h2>Provisioning AWS Machines<\/h2>\n<div><\/div>\n<div>Up until now, all of our host information has been done with one host: instance1 that we configured in our hosts file. \u00a0Ansible is much more powerful than that. \u00a0 We&#8217;re going to modify our <span class=\"lang:default decode:true  crayon-inline \">~\/.ansible.cfg<\/span>\u00a0\u00a0file to point to a different place for hosts:<\/div>\n<div>\n<pre class=\"lang:default decode:true\" title=\"~\/.ansible.cfg\">[defaults]\r\nnocows = 1\r\nhostfile = ~\/Code\/Ansible\/inventory\r\nprivate_key_file = ~\/.ssh\/awskeypair.pem\r\nremote_user = ubuntu<\/pre>\n<p>This uses my AWS keypair for logging into the remote servers I&#8217;m going to create. \u00a0I now need to create the inventory directory:<\/p>\n<p><span class=\"lang:default decode:true  crayon-inline \">mkdir ~\/Code\/Ansible\/inventory<\/span><\/p>\n<p>Inside this directory I&#8217;m going to put a script: <a href=\"https:\/\/github.com\/ansible\/ansible\/blob\/devel\/plugins\/inventory\/ec2.py\">ec2.py<\/a>. \u00a0This script comes with Ansible but the one that came with my distribution didn&#8217;t work.<\/p>\n<\/div>\n<div>\n<pre class=\"lang:default decode:true \">cd ~\/Code\/Ansible\/inventory\r\nwget https:\/\/raw.githubusercontent.com\/ansible\/ansible\/devel\/plugins\/inventory\/ec2.py\r\nchmod 755 ec2.py<\/pre>\n<p>The ec2.py file also expects an accompanying ec2.ini file:<\/p>\n<pre class=\"lang:default decode:true \">[ec2]\r\nregions = us-west-2\r\nregions_exclude = all \r\ndestination_variable = public_dns_name\r\nvpc_destination_variable = public_dns_name\r\nroute53 = False\r\nall_instances = False\r\nall_rds_instances = False\r\ncache_path = ~\/.ansible\/tmp\r\ncache_max_age = 300<\/pre>\n<p>You can modify this to suit your environment. \u00a0I&#8217;m also assuming you have boto installed already and a ~\/.boto file. \u00a0If not, see how I <a href=\"http:\/\/benincosa.com\/blog\/?p=2609\">created mine here<\/a>.<\/p>\n<p>Let&#8217;s see if we can now talk to our hosts:<\/p>\n<\/div>\n<div><span class=\"lang:default decode:true  crayon-inline \">ansible all -a \u201cdate\u201d<\/span><\/div>\n<div><\/div>\n<div>Hopefully you got something back that looked like a date and not an error. \u00a0 The nodes returned from this list will all be in the ec2 group. \u00a0I think there is a way to use tags to further make them distinct, but I haven&#8217;t had a chance to do that yet.<\/div>\n<div><\/div>\n<div>We now need to lay our directory structure out for something a little bigger. \u00a0The best practices for this <a href=\"http:\/\/docs.ansible.com\/playbooks_best_practices.html#directory-layout\">is listed here<\/a>. \u00a0My project is a little more simple as I only have my ec2 hosts and I&#8217;m just playing with them. \u00a0This stuff can get serious. \u00a0You can explore how I lay out my directories and files by viewing my <a href=\"https:\/\/github.com\/vallard\/Ansible\">github repository<\/a>.<\/div>\n<div><\/div>\n<div>The most interesting file of the new layout is my ~\/Code\/Ansible\/roles\/ec2\/tasks\/main.yml file. \u00a0This file looks like the below:<\/div>\n<div>\n<pre class=\"lang:default decode:true \">--- \r\n- name: Launch Instance\r\n  local_action: ec2 key_name={{ key_pair }}\r\n                group={{ security_group }}  \r\n                instance_type={{ instance_type }}  \r\n                image={{ image }}  \r\n                region={{ region }}  \r\n                wait=true \r\n  register: ec2 \r\n- debug: var=ec2\r\n\r\n- name: Add new instance to host group\r\n  local_action: add_host hostname={{ item.public_dns_name }} groupname=\"ec2\"\r\n  with_items: ec2.instances\r\n\r\n- name: Wait for SSH to come up\r\n  local_action: wait_for host={{ item.public_dns_name }} port=22 delay=60 timeout=320 state=started\r\n  with_items: ec2.instances<\/pre>\n<p>I use a variable file that has these variables in the {{ something }} defined. \u00a0Again, check out my \u00a0<a href=\"https:\/\/github.com\/vallard\/Ansible\">github repo<\/a>. \u00a0 This file provisions a machine (similar to the configuration from my python post I did) and then waits for SSH to come up.<\/p>\n<p>In my root directory I have a file called <a href=\"https:\/\/github.com\/vallard\/Ansible\/blob\/master\/site.yml\">site.yml<\/a> that tells the instances to come up and then go configure the instances. \u00a0Can you see how magic this is?<\/p>\n<p>we run:<\/p>\n<p><span class=\"lang:default decode:true  crayon-inline \">ansible-playbook site.yml<\/span><\/p>\n<p>This makes Ansible go and deploy one ec2 host. \u00a0It waits for it to become available, and then it ssh&#8217;s into the instance and sets up docker. \u00a0Our next step would be to create a few docker playbooks to run our applications. \u00a0Then we can completely create our production environment.<\/p>\n<p>One step closer to automating all the things!<\/p>\n<p>If you found errors, corrections, or just enjoyed the article, I&#8217;d love to hear from you: <a href=\"twitter.com\/vallard\">@vallard<\/a>.<\/p>\n<p>&nbsp;<\/p>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>My goal in this post is to go from 0 to Ansible installed on my Mac and then be able to provision AWS instances ready to run Docker containers. \u00a0The code for this post is public on my github account. OSX Setup I am running OS X Yosemite. \u00a0I use brew to make things easy&#8230;.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":[],"categories":[1013,463,464,478,38],"tags":[530,532,1010,1011,531,52],"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/benincosa.com\/index.php?rest_route=\/wp\/v2\/posts\/2651"}],"collection":[{"href":"https:\/\/benincosa.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/benincosa.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/benincosa.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/benincosa.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=2651"}],"version-history":[{"count":5,"href":"https:\/\/benincosa.com\/index.php?rest_route=\/wp\/v2\/posts\/2651\/revisions"}],"predecessor-version":[{"id":2872,"href":"https:\/\/benincosa.com\/index.php?rest_route=\/wp\/v2\/posts\/2651\/revisions\/2872"}],"wp:attachment":[{"href":"https:\/\/benincosa.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=2651"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/benincosa.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=2651"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/benincosa.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=2651"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}