{"id":1197,"date":"2019-02-06T06:03:44","date_gmt":"2019-02-06T06:03:44","guid":{"rendered":"https:\/\/www.appservgrid.com\/paw93\/?p=1197"},"modified":"2019-02-10T01:35:19","modified_gmt":"2019-02-10T01:35:19","slug":"ansible-docker-application-automation","status":"publish","type":"post","link":"https:\/\/www.appservgrid.com\/paw93\/index.php\/2019\/02\/06\/ansible-docker-application-automation\/","title":{"rendered":"Ansible Docker | Application Automation"},"content":{"rendered":"<p>[<a href=\"http:\/\/cdn.rancher.com\/wp-content\/uploads\/2015\/11\/03023838\/Ansible-Docker-Rancher.png\"><img decoding=\"async\" src=\"http:\/\/cdn.rancher.com\/wp-content\/uploads\/2015\/11\/03023838\/Ansible-Docker-Rancher-300x70.png\" alt=\"Ansible-Docker-Rancher\" \/><\/a>Over the last year I\u2019ve been using Rancher with Ansible, and have found that using the two together can be incredibly useful. If you aren\u2019t familiar with Ansible, it is a powerful configuration management tool which can be used to manage servers remotely without a daemon or agent running on the host. Instead, it uses SSH to connect with hosts, and applies tasks directly on the machines. Because of this, as long as you have SSH access to the host, (and Python) running on the host, you will be able to use Ansible to manage hosts remotely. You can find detailed ]<a href=\"http:\/\/docs.ansible.com\/\">[documentation]<\/a>[ for Ansible on the company\u2019s website..] [In this post, I will be using Ansible with Docker to automate the build out of a simple wordpress environment on a ]<strong>Rancher<\/strong>[ deployment. Specifically, I will include the following steps:]<\/p>\n<ul>\n<li>[Installing Docker on my hosts using Ansible.]<\/li>\n<li>[Setting up a fresh Rancher installation using Ansible.]<\/li>\n<li>[Registering hosts with Rancher using Ansible.]<\/li>\n<li>[Deploying the Application containers on the Hosts.]<\/li>\n<\/ul>\n<h2 id=\"preparing-the-playbook\"><strong>Preparing the Playbook<\/strong><\/h2>\n<p>[Ansible uses \u201cplaybooks\u2019 which are Ansible\u2019s configuration and orchestration language, These playbooks are expressed in YAML format, and describes set of tasks that will run on remote hosts, see this ]<a href=\"http:\/\/docs.ansible.com\/ansible\/playbooks_intro.html\">[introduction]<\/a>[ for more information on how to use Ansible playbooks] [in our case the ]<a href=\"https:\/\/github.com\/galal-hussein\/Rancher-Ansible\">[playbook]<\/a>[ will run on 3 servers, one server for the ]<strong>Rancher<\/strong>[ platform, the second server for the ]<strong>MySQL<\/strong>[ database, and the last one for the ]<strong>WordPress<\/strong>[ application.] [The addresses and information about the previous servers are listed in the following Ansible inventory file, the inventory is the file that contains names, addresses, and ports of the remote hosts where the Ansible playbook is going to execute:]\u00a0<strong>inventory file:<\/strong><\/p>\n<div class=\"highlight\">\n<pre>[Rancher]\r\nrancher ansible_ssh_port=22 ansible_ssh_host=x.x.x.x\r\n\r\n[nodes:children]\r\napplication\r\ndatabase\r\n\r\n[application]\r\nnode1 ansible_ssh_port=22 ansible_ssh_host=y.y.y.y\r\n\r\n[database]\r\nnode2 ansible_ssh_port=22 ansible_ssh_host=z.z.z.z<\/pre>\n<\/div>\n<p>[Note that I used\u00a0<a href=\"http:\/\/docs.ansible.com\/ansible\/intro_inventory.html#groups-of-groups-and-group-variables\">grouping<\/a>\u00a0in the inventory to better describe the list of machines used in this deployment, ][The playbook itself will consists of five ]<strong>plays<\/strong>[, which will result in deploying the WordPress application:]<\/p>\n<ul>\n<li><strong>Play #1<\/strong>[ Installing and configuring Docker ]<\/li>\n<\/ul>\n<p>[The first play will install and configure ]<strong>Docker<\/strong>[ on all machines, it uses the \u201cdocker\u201d role which we will see in the next section.]<\/p>\n<ul>\n<li><strong>Play #2<\/strong>[ Setting up Rancher server]<\/li>\n<\/ul>\n<p>[This play will install Rancher server and make sure it is up and running, this play will only run on one server which is considered to be the Rancher server.]<\/p>\n<ul>\n<li><strong>Play #3<\/strong>[ Registering Rancher hosts]<\/li>\n<\/ul>\n<p>[This play will run on two machines to register each of them with the Rancher server which should be up and running from the last play.]<\/p>\n<ul>\n<li><strong>Play #4<\/strong>[ Deploy MySQL Container]<\/li>\n<\/ul>\n<p>[This is a simple play to deploy the MySQL container on the database server.]<\/p>\n<ul>\n<li><strong>play #5<\/strong>[ Deploy WordPress App]<\/li>\n<\/ul>\n<p>[This play will install the WordPress application on the second machine and link it to the MySQL container.]\u00a0<strong>rancher.yml (the playbook file)<\/strong><\/p>\n<div class=\"highlight\">\n<pre>---\r\n# play 1\r\n- name: Installing and configuring Docker\r\n  hosts: all\r\n  sudo: yes\r\n  roles:\r\n    - { role: docker, tags: [\"docker\"] }\r\n\r\n# play 2\r\n- name: Setting up Rancher Server\r\n  hosts: \"rancher\"\r\n  sudo: yes\r\n  roles:\r\n    - { role: rancher, tags: [\"rancher\"] }\r\n\r\n# play 3\r\n- name: Register Rancher Hosts\r\n  hosts: \"nodes\"\r\n  sudo: yes\r\n  roles:\r\n    - { role: rancher_reg, tags: [\"rancher_reg\"] }\r\n\r\n# play 4\r\n- name: Deploy MySQL Container\r\n  hosts: 'database'\r\n  sudo: yes\r\n  roles:\r\n      - { role: mysql_docker, tags: [\"mysql_docker\"] }\r\n\r\n# play 5\r\n- name: Deploy WordPress App\r\n  hosts: \"application\"\r\n  sudo: yes\r\n  roles:\r\n    - { role: wordpress_docker, tags: [\"wordpress_docker\"] }<\/pre>\n<\/div>\n<h2 id=\"docker-role\"><strong>Docker role<\/strong><\/h2>\n<p>[This role will install the latest version of Docker on all the servers, the role assumes that you will use Ubuntu 14.04, because some other Ubuntu distros require some dependencies to run docker which is not discussed here, see the Docker ]<a href=\"https:\/\/docs.docker.com\/installation\/\">[documentation]<\/a>[ for more information on installing Docker on different platforms.]<\/p>\n<div class=\"highlight\">\n<pre>- name: Fail if OS distro is not Ubuntu 14.04\r\n  fail:\r\n      msg=\"The role is designed only for Ubuntu 14.04\"\r\n  when: \"{{ ansible_distribution_version | version_compare('14.04', '!=') }}\"<\/pre>\n<\/div>\n<p>[The Docker module in Ansible requires ]<strong>docker-py<\/strong>[ library to be installed on the remote server, so at first we use python-pip to install docker-py library on all servers before installing the Docker:]<\/p>\n<div class=\"highlight\">\n<pre>- name: Install dependencies\r\n  apt:\r\n      name={{ item }}\r\n      update_cache=yes\r\n  with_items:\r\n      - python-dev\r\n      - python-setuptools\r\n\r\n- name: Install pip\r\n  easy_install:\r\n      name=pip\r\n\r\n- name: Install docker-py\r\n  pip:\r\n      name=docker-py\r\n      state=present\r\n      version=1.1.0<\/pre>\n<\/div>\n<p>The next tasks will import the Docker apt repo and install Docker:<\/p>\n<div class=\"highlight\">\n<pre>- name: Add docker apt repo\r\n  apt_repository:\r\n      repo='deb https:\/\/apt.dockerproject.org\/repo ubuntu-{{ ansible_distribution_release }} main'\r\n      state=present\r\n\r\n- name: Import the Docker repository key\r\n  apt_key:\r\n      url=https:\/\/apt.dockerproject.org\/gpg\r\n      state=present\r\n      id=2C52609D\r\n\r\n- name: Install Docker package\r\n  apt:\r\n      name=docker-engine\r\n      update_cache=yes<\/pre>\n<\/div>\n<p>Finally the next three tasks will create a system group for Docker and add any user defined in\u00a0<strong>\u201cdocker_users\u201d<\/strong>\u00a0variable to this group, and it will copy template for Docker configuration then restart Docker.<\/p>\n<div class=\"highlight\">\n<pre>- name: Create a docker group\r\n  group:\r\n      name=docker\r\n      state=present\r\n\r\n- name: Add user(s) to docker group\r\n  user:\r\n      name={{ item }}\r\n      group=docker\r\n      state=present\r\n  with_items: docker_users\r\n  when: docker_users is defined\r\n\r\n- name: Configure Docker\r\n  template:\r\n      src=default_docker.j2\r\n      dest=\/etc\/default\/docker\r\n      mode=0644\r\n      owner=root\r\n      group=root\r\n  notify: restart docker<\/pre>\n<\/div>\n<p>The \u201cdefault_docker.j2\u201d template will check for the variable\u00a0<strong>\u201cdocker_opts\u201d<\/strong>\u00a0which is not defined by default, and if it is defined will add the options defined in the variable to the file:<\/p>\n<div class=\"highlight\">\n<pre># Docker Upstart and SysVinit configuration file\r\n\r\n# Use DOCKER_OPTS to modify the daemon startup options.\r\n{% if docker_opts is defined %}\r\nDOCKER_OPTS=\"{{ docker_opts | join(' ')}}\"\r\n{% endif %}<\/pre>\n<\/div>\n<h2 id=\"rancher-role\"><strong>Rancher role<\/strong><\/h2>\n<p>[The rancher role is really simple, its goal is to pull and run the Rancher\u2019s Docker image from the hub, and then wait for the Rancher server to start and listen for incoming connections:]<\/p>\n<div class=\"highlight\">\n<pre>---\r\n- name: Pull and run the Rancher\/server container\r\n  docker:\r\n      name: \"{{ rancher_name }}\"\r\n      image: rancher\/server\r\n      restart_policy: always\r\n      ports:\r\n        - \"{{ rancher_port }}:8080\"\r\n\r\n- name: Wait for the Rancher server to start\r\n  action: command docker logs {{ rancher_name }}\r\n  register: rancher_logs\r\n  until: rancher_logs.stdout.find(\"Listening on\") != -1\r\n  retries: 30\r\n  delay: 10\r\n\r\n- name: Print Rancher's URL\r\n  debug: msg=\"You can connect to rancher server http:\/\/{{ ansible_default_ipv4.address }}:{{ rancher_port }}\"<\/pre>\n<\/div>\n<h2 id=\"rancher-registration-role\">Rancher Registration Role<\/h2>\n<p>The\u00a0<strong>rancher_reg<\/strong>\u00a0role will pull and run the rancher_agent Docker image, first it will use Rancher\u2019s API to return the registration token to run each agent with the right registration url, this token is needed to register hosts in Rancher environment:<\/p>\n<div class=\"highlight\">\n<pre>---\r\n- name: Install httplib2\r\n  apt:\r\n      name=python-httplib2\r\n      update_cache=yes\r\n\r\n- name: Get the default project id\r\n   action: uri\r\n       method=GET\r\n       status_code=200\r\n       url=\"http:\/\/{{ rancher_server }}:{{ rancher_port }}\/v1\/projects\" return_content=yes\r\n   register: project_id\r\n\r\n- name: Return the registration token URL of Rancher server\r\n  action: uri\r\n      method=POST\r\n      status_code=201\r\n      url=\"http:\/\/{{ rancher_server }}:{{ rancher_port }}\/v1\/registrationtokens?projectId={{ project_id.json['data'][0]['id'] }}\" return_content=yes\r\n  register: rancher_token_url\r\n\r\n- name: Return the registration URL of Rancher server\r\n  action: uri\r\n      method=GET\r\n      url={{ rancher_token_url.json['links']['self'] }} return_content=yes\r\n  register: rancher_token<\/pre>\n<\/div>\n<p>Then it will make sure that no other agent is running on the server and it will run the Rancher Agent:<\/p>\n<div class=\"highlight\">\n<pre>- name: Check if the rancher-agent is running\r\n  command: docker ps -a\r\n  register: containers\r\n\r\n- name: Register the Host machine with the Rancher server\r\n  docker:\r\n      image: rancher\/agent:v{{ rancher_agent_version }}\r\n      privileged: yes\r\n      detach: True\r\n      volumes: \/var\/run\/docker.sock:\/var\/run\/docker.sock\r\n      command: \"{{ rancher_token.json['registrationUrl'] }}\"\r\n      state: started\r\n  when: \"{{ 'rancher-agent' not in containers.stdout }}\"<\/pre>\n<\/div>\n<h2 id=\"mysql-and-wordpress-roles\"><strong>MySQL and WordPress Roles<\/strong><\/h2>\n<p>[The two roles are using Ansible ]<a href=\"http:\/\/docs.ansible.com\/ansible\/docker_module.html\">[Docker\u2019s]<\/a>[ module to run Docker images on the server, you will note that each Docker container will start with ]<strong>RANCHER_NETWORK=true<\/strong>[ environment variable, which will cause the Docker container to use Rancher\u2019s managed network so that containers can communicate on different hosts in the same private network.] [I will use the official ]<a href=\"https:\/\/registry.hub.docker.com\/_\/mysql\/\">[MySQL]<\/a>[ and ]<a href=\"https:\/\/registry.hub.docker.com\/_\/wordpress\/\">[Wordpress]<\/a>[ images, the MySQL image requires the ]<strong>MYSQL_ROOT_PASSWORD<\/strong>[ environment variable to start, you can also start it with default database and user which will be granted superuser permissions on this database.]<\/p>\n<div class=\"highlight\">\n<pre>- name: Create a mysql docker container\r\n  docker:\r\n      name: mysql\r\n      image: mysql:{{ mysql_version }}\r\n      detach: True\r\n      env: RANCHER_NETWORK=true,\r\n           MYSQL_ROOT_PASSWORD={{ mysql_root_password }}\r\n\r\n- name: Wait a few minutes for the IPs to be set to the container\r\n  wait_for: timeout=120\r\n\r\n# The following tasks help with the connection of the containers in different hosts in Rancher\r\n- name: Fetch the MySQL Container IP\r\n  shell: docker exec mysql ip -o -4 addr list eth0 | awk '{print $4}' | cut -d\/ -f1 |  sed -n 2p\r\n  register: mysql_sec_ip\r\n\r\n- name: print the mysql rancher's ip\r\n  debug: msg={{ mysql_sec_ip.stdout }}<\/pre>\n<\/div>\n<p>[Note that role will wait for 2 minutes until to make sure that the container is configured with the right IPs, and then it will fetch the container\u2019s secondary ip which is the ip used in Rancher\u2019s network and save it to the ]<strong>mysql_sec_ip<\/strong>[ variable which will survive through the playbook, WordPress image on other hand will start with ]<strong>WORDPRESS_DB_HOST<\/strong>[ set to the ip of the mysql container we just started.]<\/p>\n<div class=\"highlight\">\n<pre>- name: Create a wordpress docker container\r\n  docker:\r\n      name: wordpress\r\n      image: wordpress:{{ wordpress_version }}\r\n      detach: True\r\n      ports:\r\n      - 80:80\r\n      env: RANCHER_NETWORK=true,\r\n         WORDPRESS_DB_HOST={{ mysql_host }}:3306,\r\n         WORDPRESS_DB_PASSWORD={{ mysql_root_password }},\r\n         WORDPRESS_AUTH_KEY={{ wordpress_auth_key }},\r\n         WORDPRESS_SECURE_AUTH_KEY={{ wordpress_secure_auth_key }},\r\n         WORDPRESS_LOGGED_IN_KEY={{ wordpress_logged_in_key }},\r\n         WORDPRESS_NONCE_KEY={{ wordpress_nonce_key }},\r\n         WORDPRESS_AUTH_SALT={{ wordpress_auth_salt }},\r\n         WORDPRESS_SECURE_AUTH_SALT={{ wordpress_secure_auth_salt }},\r\n         WORDPRESS_NONCE_SALT={{ wordpress_nonce_salt }},\r\n         WORDPRESS_LOGGED_IN_SALT={{ wordpress_loggedin_salt }}<\/pre>\n<\/div>\n<h2 id=\"managing-variables\">Managing Variables<\/h2>\n<p>Ansible defines variables in different layers, some of layers override the others, so for our case I added a default set of variables for each role to be used in different playbooks later, and added the currently used variables in the group_vars directory to override them.<\/p>\n<div class=\"highlight\">\n<pre>\u251c\u2500\u2500 group_vars\r\n\u2502   \u251c\u2500\u2500 all.yml\r\n\u2502   \u251c\u2500\u2500 nodes.yml\r\n\u2502   \u2514\u2500\u2500 Rancher.yml\r\n\u251c\u2500\u2500 hosts\r\n\u251c\u2500\u2500 rancher.yml\r\n\u251c\u2500\u2500 README.md\r\n\u2514\u2500\u2500 roles\r\n    \u251c\u2500\u2500 docker\r\n    \u251c\u2500\u2500 mysql_docker\r\n    \u251c\u2500\u2500 rancher\r\n    \u251c\u2500\u2500 rancher_reg\r\n    \u2514\u2500\u2500 wordpress_docker<\/pre>\n<\/div>\n<p>The\u00a0<strong>nodes.yml<\/strong>\u00a0variables will apply on the nodes group defined in the inventory file which contains the database and application servers, this file contains information used by mysql and wordpress containers:<\/p>\n<div class=\"highlight\">\n<pre>---\r\nrancher_server: \"{{ hostvars['rancher']['ansible_ssh_host'] }}\"\r\n\r\n# MySQL variables\r\nmysql_root_password: \"{{ lookup('password', mysql_passwd_tmpfile + ' length=20 chars=ascii_letters,digits') }}\"\r\nmysql_passwd_tmpfile: \/tmp\/mysqlpasswd.file\r\nmysql_host: \"{{ hostvars.node2.mysql_sec_ip.stdout }}\"\r\nmysql_port: 3306\r\nmysql_version: 5.5\r\n\r\n# WordPress variables\r\nwordpress_version: latest<\/pre>\n<\/div>\n<p>[You may note that I used password lookup to generate a random password for mysql root password, a good alternative for this method would be ]<a href=\"http:\/\/docs.ansible.com\/ansible\/playbooks_vault.html\">[vault]<\/a>[ to encrypt sensitive data like passwords or keys.]<\/p>\n<h2 id=\"running-the-playbook\"><strong>Running the Playbook<\/strong><\/h2>\n<p>[To run the playbook, I fired up 3 machines with Ubuntu 14.04 installed and added their IPs to the inventory we saw earlier, and then used the following command to start the playbook:]<\/p>\n<div class=\"highlight\">\n<pre>$ ansible-playbook -u root -i hosts rancher.yml<\/pre>\n<\/div>\n<p>[After the playbook finishes its work, you can access the Rancher server and you will see the following:]\u00a0<a href=\"http:\/\/cdn.rancher.com\/wp-content\/uploads\/2015\/10\/01160518\/rancher_nodes.png\"><img decoding=\"async\" src=\"http:\/\/cdn.rancher.com\/wp-content\/uploads\/2015\/10\/01160518\/rancher_nodes.png\" alt=\"rancher\\_nodes\" \/><\/a>[And when accessing the IP of node1 on port 80 you will access WordPress:]\u00a0<a href=\"http:\/\/cdn.rancher.com\/wp-content\/uploads\/2015\/10\/01160524\/wordpress_rancher.png\"><img decoding=\"async\" src=\"http:\/\/cdn.rancher.com\/wp-content\/uploads\/2015\/10\/01160524\/wordpress_rancher.png\" alt=\"wordpress\\_rancher\" \/><\/a><\/p>\n<h2 id=\"conclusion\"><strong>Conclusion<\/strong><\/h2>\n<p>[Ansible is a very powerful and simple automation tool that can be used to manage and configure a fleet of servers, using Ansible with Rancher can be a very efficient method to start your environment and manage your Docker containers. This month we are hosting an online meetup in which we\u2019ll be demonstrating how to run microservices in Docker containers and orchestration application upgrades using Rancher. Please join us for this meetup to learn more. ]<\/p>\n<div class=\"row middle p-v-sm\">\n<div class=\"addthis_inline_share_toolbox\" data-url=\"https:\/\/rancher.com\/using-ansible-with-docker-to-deploy-a-wordpress-service-on-rancher\/\" data-title=\"Using Ansible with Docker to Deploy a WordPress Service on Rancher\" data-description=\"Visit us to learn more about using Ansible with Docker to deploy a WordPress service on Rancher. For more tutorials and to request a demo, visit Rancher today.\">\n<div id=\"atstbx\" class=\"at-resp-share-element at-style-responsive addthis-smartlayers addthis-animated at4-show\" role=\"region\" aria-labelledby=\"at-ce3d1c90-9bd7-4d50-8316-c38f08bccb00\"><span id=\"at-ce3d1c90-9bd7-4d50-8316-c38f08bccb00\" class=\"at4-visually-hidden\"><\/span><\/p>\n<div class=\"at-share-btn-elements\"><\/div>\n<\/div>\n<\/div>\n<\/div>\n<p><a href=\"https:\/\/rancher.com\/using-ansible-with-docker-to-deploy-a-wordpress-service-on-rancher\/\" target=\"_blank\" rel=\"noopener\">Source<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>[Over the last year I\u2019ve been using Rancher with Ansible, and have found that using the two together can be incredibly useful. If you aren\u2019t familiar with Ansible, it is a powerful configuration management tool which can be used to manage servers remotely without a daemon or agent running on the host. Instead, it uses &hellip; <\/p>\n<p class=\"link-more\"><a href=\"https:\/\/www.appservgrid.com\/paw93\/index.php\/2019\/02\/06\/ansible-docker-application-automation\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Ansible Docker | Application Automation&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[],"class_list":["post-1197","post","type-post","status-publish","format-standard","hentry","category-kubernetes"],"_links":{"self":[{"href":"https:\/\/www.appservgrid.com\/paw93\/index.php\/wp-json\/wp\/v2\/posts\/1197","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.appservgrid.com\/paw93\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.appservgrid.com\/paw93\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.appservgrid.com\/paw93\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.appservgrid.com\/paw93\/index.php\/wp-json\/wp\/v2\/comments?post=1197"}],"version-history":[{"count":2,"href":"https:\/\/www.appservgrid.com\/paw93\/index.php\/wp-json\/wp\/v2\/posts\/1197\/revisions"}],"predecessor-version":[{"id":1224,"href":"https:\/\/www.appservgrid.com\/paw93\/index.php\/wp-json\/wp\/v2\/posts\/1197\/revisions\/1224"}],"wp:attachment":[{"href":"https:\/\/www.appservgrid.com\/paw93\/index.php\/wp-json\/wp\/v2\/media?parent=1197"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.appservgrid.com\/paw93\/index.php\/wp-json\/wp\/v2\/categories?post=1197"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.appservgrid.com\/paw93\/index.php\/wp-json\/wp\/v2\/tags?post=1197"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}