[ Over the last year I’ve been using Rancher with Ansible, and have found that using the two together can be incredibly useful. If you aren’t 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 ][documentation][ for Ansible on the company’s website..] [In this post, I will be using Ansible with Docker to automate the build out of a simple wordpress environment on a ]Rancher[ deployment. Specifically, I will include the following steps:]

[Installing Docker on my hosts using Ansible.]

[Setting up a fresh Rancher installation using Ansible.]

[Registering hosts with Rancher using Ansible.]

[Deploying the Application containers on the Hosts.]

Preparing the Playbook

[Ansible uses “playbooks’ which are Ansible’s configuration and orchestration language, These playbooks are expressed in YAML format, and describes set of tasks that will run on remote hosts, see this ][introduction][ for more information on how to use Ansible playbooks] [in our case the ][playbook][ will run on 3 servers, one server for the ]Rancher[ platform, the second server for the ]MySQL[ database, and the last one for the ]Wordpress[ 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:] inventory file:

[Rancher] rancher ansible_ssh_port=22 ansible_ssh_host=x.x.x.x [nodes:children] application database [application] node1 ansible_ssh_port=22 ansible_ssh_host=y.y.y.y [database] node2 ansible_ssh_port=22 ansible_ssh_host=z.z.z.z

[Note that I used grouping in the inventory to better describe the list of machines used in this deployment, ][The playbook itself will consists of five ]plays[, which will result in deploying the Wordpress application:]

Play #1[ Installing and configuring Docker ]

[The first play will install and configure ]Docker[ on all machines, it uses the “docker” role which we will see in the next section.]

Play #2[ Setting up Rancher server]

[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.]

Play #3[ Registering Rancher hosts]

[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.]

Play #4[ Deploy MySQL Container]

[This is a simple play to deploy the MySQL container on the database server.]

play #5[ Deploy Wordpress App]

[This play will install the Wordpress application on the second machine and link it to the MySQL container.] rancher.yml (the playbook file)

--- # play 1 - name: Installing and configuring Docker hosts: all sudo: yes roles: - { role: docker, tags: ["docker"] } # play 2 - name: Setting up Rancher Server hosts: "rancher" sudo: yes roles: - { role: rancher, tags: ["rancher"] } # play 3 - name: Register Rancher Hosts hosts: "nodes" sudo: yes roles: - { role: rancher_reg, tags: ["rancher_reg"] } # play 4 - name: Deploy MySQL Container hosts: 'database' sudo: yes roles: - { role: mysql_docker, tags: ["mysql_docker"] } # play 5 - name: Deploy Wordpress App hosts: "application" sudo: yes roles: - { role: wordpress_docker, tags: ["wordpress_docker"] }

Docker role

[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 ][documentation][ for more information on installing Docker on different platforms.]

- name: Fail if OS distro is not Ubuntu 14.04 fail: msg="The role is designed only for Ubuntu 14.04" when: "{{ ansible_distribution_version | version_compare('14.04', '!=') }}"

[The Docker module in Ansible requires ]docker-py[ 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:]

- name: Install dependencies apt: name={{ item }} update_cache=yes with_items: - python-dev - python-setuptools - name: Install pip easy_install: name=pip - name: Install docker-py pip: name=docker-py state=present version=1.1.0

The next tasks will import the Docker apt repo and install Docker:

- name: Add docker apt repo apt_repository: repo='deb https://apt.dockerproject.org/repo ubuntu-{{ ansible_distribution_release }} main' state=present - name: Import the Docker repository key apt_key: url=https://apt.dockerproject.org/gpg state=present id=2C52609D - name: Install Docker package apt: name=docker-engine update_cache=yes

Finally the next three tasks will create a system group for Docker and add any user defined in “docker_users” variable to this group, and it will copy template for Docker configuration then restart Docker.

- name: Create a docker group group: name=docker state=present - name: Add user(s) to docker group user: name={{ item }} group=docker state=present with_items: docker_users when: docker_users is defined - name: Configure Docker template: src=default_docker.j2 dest=/etc/default/docker mode=0644 owner=root group=root notify: restart docker

The “default_docker.j2” template will check for the variable “docker_opts” which is not defined by default, and if it is defined will add the options defined in the variable to the file:

# Docker Upstart and SysVinit configuration file # Use DOCKER_OPTS to modify the daemon startup options. {% if docker_opts is defined %} DOCKER_OPTS="{{ docker_opts | join(' ')}}" {% endif %}

Rancher role

[The rancher role is really simple, its goal is to pull and run the Rancher’s Docker image from the hub, and then wait for the Rancher server to start and listen for incoming connections:]

--- - name: Pull and run the Rancher/server container docker: name: "{{ rancher_name }}" image: rancher/server restart_policy: always ports: - "{{ rancher_port }}:8080" - name: Wait for the Rancher server to start action: command docker logs {{ rancher_name }} register: rancher_logs until: rancher_logs.stdout.find("Listening on") != -1 retries: 30 delay: 10 - name: Print Rancher's URL debug: msg="You can connect to rancher server http://{{ ansible_default_ipv4.address }}:{{ rancher_port }}"

Rancher Registration Role

The rancher_reg role will pull and run the rancher_agent Docker image, first it will use Rancher’s 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:

--- - name: Install httplib2 apt: name=python-httplib2 update_cache=yes - name: Get the default project id action: uri method=GET status_code=200 url="http://{{ rancher_server }}:{{ rancher_port }}/v1/projects" return_content=yes register: project_id - name: Return the registration token URL of Rancher server action: uri method=POST status_code=201 url="http://{{ rancher_server }}:{{ rancher_port }}/v1/registrationtokens?projectId={{ project_id.json['data'][0]['id'] }}" return_content=yes register: rancher_token_url - name: Return the registration URL of Rancher server action: uri method=GET url={{ rancher_token_url.json['links']['self'] }} return_content=yes register: rancher_token

Then it will make sure that no other agent is running on the server and it will run the Rancher Agent:

- name: Check if the rancher-agent is running command: docker ps -a register: containers - name: Register the Host machine with the Rancher server docker: image: rancher/agent:v{{ rancher_agent_version }} privileged: yes detach: True volumes: /var/run/docker.sock:/var/run/docker.sock command: "{{ rancher_token.json['registrationUrl'] }}" state: started when: "{{ 'rancher-agent' not in containers.stdout }}"

MySQL and Wordpress Roles

[The two roles are using Ansible ][Docker’s][ module to run Docker images on the server, you will note that each Docker container will start with ]RANCHER_NETWORK=true[ environment variable, which will cause the Docker container to use Rancher’s managed network so that containers can communicate on different hosts in the same private network.] [I will use the official ][MySQL][ and ][Wordpress][ images, the MySQL image requires the ]MYSQL_ROOT_PASSWORD[ environment variable to start, you can also start it with default database and user which will be granted superuser permissions on this database.]

- name: Create a mysql docker container docker: name: mysql image: mysql:{{ mysql_version }} detach: True env: RANCHER_NETWORK=true, MYSQL_ROOT_PASSWORD={{ mysql_root_password }} - name: Wait a few minutes for the IPs to be set to the container wait_for: timeout=120 # The following tasks help with the connection of the containers in different hosts in Rancher - name: Fetch the MySQL Container IP shell: docker exec mysql ip -o -4 addr list eth0 | awk '{print $4}' | cut -d/ -f1 | sed -n 2p register: mysql_sec_ip - name: print the mysql rancher's ip debug: msg={{ mysql_sec_ip.stdout }}

[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’s secondary ip which is the ip used in Rancher’s network and save it to the ]mysql_sec_ip[ variable which will survive through the playbook, Wordpress image on other hand will start with ]WORDPRESS_DB_HOST[ set to the ip of the mysql container we just started.]

- name: Create a wordpress docker container docker: name: wordpress image: wordpress:{{ wordpress_version }} detach: True ports: - 80:80 env: RANCHER_NETWORK=true, WORDPRESS_DB_HOST={{ mysql_host }}:3306, WORDPRESS_DB_PASSWORD={{ mysql_root_password }}, WORDPRESS_AUTH_KEY={{ wordpress_auth_key }}, WORDPRESS_SECURE_AUTH_KEY={{ wordpress_secure_auth_key }}, WORDPRESS_LOGGED_IN_KEY={{ wordpress_logged_in_key }}, WORDPRESS_NONCE_KEY={{ wordpress_nonce_key }}, WORDPRESS_AUTH_SALT={{ wordpress_auth_salt }}, WORDPRESS_SECURE_AUTH_SALT={{ wordpress_secure_auth_salt }}, WORDPRESS_NONCE_SALT={{ wordpress_nonce_salt }}, WORDPRESS_LOGGED_IN_SALT={{ wordpress_loggedin_salt }}

Managing Variables

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.

├── group_vars │ ├── all.yml │ ├── nodes.yml │ └── Rancher.yml ├── hosts ├── rancher.yml ├── README.md └── roles ├── docker ├── mysql_docker ├── rancher ├── rancher_reg └── wordpress_docker

The nodes.yml variables 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:

--- rancher_server: "{{ hostvars['rancher']['ansible_ssh_host'] }}" # MySQL variables mysql_root_password: "{{ lookup('password', mysql_passwd_tmpfile + ' length=20 chars=ascii_letters,digits') }}" mysql_passwd_tmpfile: /tmp/mysqlpasswd.file mysql_host: "{{ hostvars.node2.mysql_sec_ip.stdout }}" mysql_port: 3306 mysql_version: 5.5 # Wordpress variables wordpress_version: latest

[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 ][vault][ to encrypt sensitive data like passwords or keys.]

Running the Playbook

[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:]

$ ansible-playbook -u root -i hosts rancher.yml

[After the playbook finishes its work, you can access the Rancher server and you will see the following:] [And when accessing the IP of node1 on port 80 you will access Wordpress:]

Conclusion

[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’ll be demonstrating how to run microservices in Docker containers and orchestration application upgrades using Rancher. Please join us for this meetup to learn more. ]