{"id":6032,"date":"2018-04-26T11:35:05","date_gmt":"2018-04-26T15:35:05","guid":{"rendered":"https:\/\/www.webperformance.com\/load-testing-tools\/blog\/?p=6032"},"modified":"2018-04-26T11:35:05","modified_gmt":"2018-04-26T15:35:05","slug":"bootstrapping-an-alpine-ec2-instance-for-ansible-and-docker","status":"publish","type":"post","link":"https:\/\/www.webperformance.com\/load-testing-tools\/blog\/2018\/04\/bootstrapping-an-alpine-ec2-instance-for-ansible-and-docker\/","title":{"rendered":"Bootstrapping an Alpine EC2 instance for Ansible and Docker"},"content":{"rendered":"<p>One of the challenges in running a large load test is the orchestration of a large number of machines to generate load. This involves a series of steps:<\/p>\n<ol>\n<li>Creating the instances<\/li>\n<li>Install the load testing software<\/li>\n<li>Sending the test configuration<\/li>\n<li>Run the test<\/li>\n<li>Collect test results<\/li>\n<li>Shutdown the instances<\/li>\n<\/ol>\n<p><a href=\"https:\/\/www.webperformance.com\/load-testing-tools\/pro-features.html\">Load Tester<\/a> does that pretty effortlessly in EC2 &#8211; our customers as well as our own test engineers love not having to worry about those steps. It just works. As I evolve our next generation of testing tools, I am revisiting this problem and looking at solutions from a different angle. Last week, I decided to investigate some of the popular Linux orchestration tools (<a href=\"https:\/\/www.chef.io\/chef\/\">Chef<\/a>, <a href=\"https:\/\/puppet.com\/\">Puppet<\/a>, <a href=\"https:\/\/www.ansible.com\/\">Ansible<\/a>, etc) to see if they would be suitable for the task of running a distributed load test.<\/p>\n<p>Ansible immediately appealed to me for this task, primarily due to it&#8217;s <em>agentless<\/em> nature &#8211; it only needs SSH and Python on the client to manage it. This would mean that I can use any EC2 Linux image. I have recently played a little with <a href=\"https:\/\/docker.com\">Docker<\/a> as well and came across the ultralight\u00a0<a href=\"https:\/\/alpinelinux.org\/\">Alpine Linux<\/a>\u00a0distro, which struck me as perfect for running a load-generating agent.<\/p>\n<p>I immediately ran into a problem with my first Ansible command &#8211; The EC2 Alpine image doesn&#8217;t have Python. Did I mention it is an <em>ultralight<\/em> distro? As it turns out, Ansible really only needs SSH. Python is needed for most tasks (which are performed with Alpine modules written in&#8230;Python), but thanks to the <em>raw<\/em>\u00a0module, I was able to use Ansible to install Python and then all the rest of the Ansible modules will work.<\/p>\n<h2>Step 1 &#8211; Update and install Python<\/h2>\n<p>Before doing anything else, I want to update Alpine with the latest packages. Alpine uses the <em>apk<\/em> package manager, so normally I would use Ansible&#8217;s <em>apk<\/em> module for this. But, with Python still MIA, I used the <em>raw<\/em> module instead. Note that the ONLY recommended use of the raw module is for systems that do not yet have Python on them. This Ansible task will get it done:<\/p>\n<p>(note that all of these steps require <em>sudo<\/em>, so you either need to add a &#8220;become: yes&#8221; line to each task or add it before the <em>tasks<\/em> declaration, to become <em>sudo<\/em> for all tasks)<\/p>\n<pre>- name: Ensure updated\r\n  raw: apk update<\/pre>\n<p>But it failed. I logged into the Alpine instance and tried the update manually. I found that the\u00a0update fails with these errors:<\/p>\n<blockquote><p>fetch http:\/\/dl-cdn.alpinelinux.org\/alpine\/v3.7\/main\/x86_64\/APKINDEX.tar.gz<br \/>\nfetch http:\/\/dl-cdn.alpinelinux.org\/alpine\/v3.7\/community\/x86_64\/APKINDEX.tar.gz<br \/>\nfetch https:\/\/mcrute-build-artifacts.s3.us-west-2.amazonaws.com\/alpine-packages\/3.7\/testing\/x86_64\/APKINDEX.tar.gz<br \/>\nERROR: https:\/\/mcrute-build-artifacts.s3.us-west-2.amazonaws.com\/alpine-packages\/3.7\/testing: Permission denied<br \/>\nWARNING: Ignoring APKINDEX.4dabf18d.tar.gz: No such file or directory<br \/>\nv3.7.0-151-gf417903f18 [http:\/\/dl-cdn.alpinelinux.org\/alpine\/v3.7\/main]<br \/>\nv3.7.0-153-g3d6fac5d3f [http:\/\/dl-cdn.alpinelinux.org\/alpine\/v3.7\/community]<br \/>\n1 errors; 9056 distinct packages available<\/p><\/blockquote>\n<p>The cause is an errant line in the <em>\/etc\/apk\/repositories<\/em> file in this AMI (it references an inaccessible apk repository). Fortunately, this line is easily removed with a bit of <em>grep<\/em> and <em>sed<\/em>. I inserted another Ansible command before the update step, again using the <em>raw<\/em> module:<\/p>\n<pre>- name: Ensure apk repository settings are correct\r\n  raw: if echo $(tail -n 1 \/etc\/apk\/repositories) | grep -q \"testing\"; then sed -i '$ d' \/etc\/apk\/repositories; fi<\/pre>\n<p>Yay! The system is updated, so now I can install Python. This is the last time I will use the <em>raw<\/em> module:<\/p>\n<pre>- name: Ensure Python and pip are installed\r\n  raw: apk add python2 py2-pip<\/pre>\n<p>If I didn&#8217;t need Docker, then I could stop here. For our load-generating engines, I won&#8217;t need Docker, but I will for running Chronograf, which I will eventually use for viewing live metrics during a test. That is a post for another day. Setting up Docker is easy now, with Ansible at full strength, so let&#8217;s get to it:<\/p>\n<h2>Step 2 &#8211; Install and run Docker<\/h2>\n<p>Now that Python is present, I can use the <em>apk<\/em> Ansible module to install Docker:<\/p>\n<pre>- name: Ensure docker is installed\r\n  apk:\r\n    name: docker\r\n    state: present<\/pre>\n<p>Knowing that it is present, I can start it with the <em>service<\/em> module:<\/p>\n<pre>- name: Ensure docker is running\r\n  service:\r\n    name: docker\r\n    state: started<\/pre>\n<p>Finally, we can install the dependencies for the <em>docker<\/em>\u00a0Ansible module. This is not strictly required, but future tasks, such running containers, is easier with the <em>docker<\/em> module. That module needs the <em>docker-py<\/em> Python package. Since I installed <em>pip<\/em> alongside Python in the previous step, I can use the Ansible <em>pip<\/em> module:<\/p>\n<pre>- name: Ensure docker-py is installed\r\n  pip:\r\n    name: docker-py<\/pre>\n<p>Done. This Alpine\/EC2 image is now ready to run Docker containers. Enjoy!<\/p>\n<p>Chris<\/p>\n","protected":false},"excerpt":{"rendered":"<p>One of the challenges in running a large load test is the orchestration of a large number of machines to generate load. This involves a series of steps:<\/p>\n<p>Creating the instances<br \/>\nInstall the load testing software<br \/>\nSending the test configuration<br \/>\nRun the test<br \/>\nCollect test results<br \/>\nShutdown the instances<\/p>\n<p><a href=\"https:\/\/www.webperformance.com\/load-testing-tools\/pro-features.html\">Load Tester<\/a> does that pretty effortlessly in EC2 &#8211; our customers as well as our own test engineers love not having to worry about those steps. It just works. As I evolve our next generation of testing tools, I am revisiting this problem and looking at solutions from a different angle. Last week, I decided to investigate &hellip; <a href=\"https:\/\/www.webperformance.com\/load-testing-tools\/blog\/2018\/04\/bootstrapping-an-alpine-ec2-instance-for-ansible-and-docker\/\">Continue reading &raquo;<\/a><\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[299,301,303,47],"class_list":["post-6032","post","type-post","status-publish","format-standard","hentry","category-virtualization","tag-alpine","tag-ansible","tag-docker","tag-ec2"],"_links":{"self":[{"href":"https:\/\/www.webperformance.com\/load-testing-tools\/blog\/wp-json\/wp\/v2\/posts\/6032","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.webperformance.com\/load-testing-tools\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.webperformance.com\/load-testing-tools\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.webperformance.com\/load-testing-tools\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.webperformance.com\/load-testing-tools\/blog\/wp-json\/wp\/v2\/comments?post=6032"}],"version-history":[{"count":8,"href":"https:\/\/www.webperformance.com\/load-testing-tools\/blog\/wp-json\/wp\/v2\/posts\/6032\/revisions"}],"predecessor-version":[{"id":6040,"href":"https:\/\/www.webperformance.com\/load-testing-tools\/blog\/wp-json\/wp\/v2\/posts\/6032\/revisions\/6040"}],"wp:attachment":[{"href":"https:\/\/www.webperformance.com\/load-testing-tools\/blog\/wp-json\/wp\/v2\/media?parent=6032"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.webperformance.com\/load-testing-tools\/blog\/wp-json\/wp\/v2\/categories?post=6032"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.webperformance.com\/load-testing-tools\/blog\/wp-json\/wp\/v2\/tags?post=6032"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}