Using Rancher-Gen to Dynamically Update Docker Configuration Files

Share

pitrhoThis
is a guest post by Alejandro Mesa, Full-Stack Software Engineer and
Chief Architect at Pit Rho.
Introduction
Docker and Rancher have made it far
easier to deploy and manage microservice-based applications. A key
challenge, however, is managing the configuration of services that
depend on other dynamic services. Imagine the following scenario: you
have multiple backend containers that run your web application, and a
few nginx containers that proxy all requests to the backend containers.
Now, there’s a new release of the web application that must be deployed,
which means new backend containers need to be built and deployed. After
they are deployed, the nginx configuration needs to change to point to
the new backend containers. So, what do you do with nginx? Do you change
the its configuration, build a new container and deploy it? What if
there was a way for you to automatically detect the changes on the
backend service and dynamically update nginx? That’s where
Rancher-Gen comes into play.
Rancher-Gen is a Python utility that listens for service changes in
Rancher and renders a user-specified Jinja2
template. This allows a user to generate configuration files for
existing services based on those changes. In addition, it provides a
mechanism to run a notification command after the template has been
rendered. Below is a tutorial that describes how to automatically
generate an nginx configuration file for a backend service running the
ghost blogging platform.

Tutorial

All configuration files described below can be found under the
demo directory
in the Rancher-Gen repository.
Step 1 -  Deploying the ghost service For simplicity, we’re going
to use the official ghost image from Docker hub. So, create a
docker-compose.yml file and add the ghost service as follows:

ghost:
  image: ghost
  expose:
    - "2368"

Now, deploy the ghost service using Rancher Compose:

$ rancher-compose -p demo up -d ghost

Step 2  –  Create the nginx image with rancher-gen Here is the
Dockerfile used to build the nginx image:

FROM phusion/baseimage:0.9.17
MAINTAINER pitrho

# Step 1 - Install nginx and python
ENV DEBIAN_FRONTEND noninteractive
RUN 
 apt-add-repository -y ppa:nginx/stable && 
 apt-get update && 
 apt-get install -y python-software-properties 
   wget 
   nginx 
   python-dev 
   python-pip 
   libev4 
   libev-dev 
   expect-dev && 
 rm -rf /var/lib/apt/lists/* && 
 chown -R www-data:www-data /var/lib/nginx && 
apt-get clean

# Step 2 - Install rancher-gen
ENV RANCHER_GEN_VERSION 0.1.2
RUN pip install rancher-gen==$RANCHER_GEN_VERSION

# Step 3 - Define services
RUN mkdir /etc/service/nginx /etc/service/rancher_gen /nginxconf
COPY nginx_run /etc/service/nginx/run
COPY rancher-gen_run /etc/service/rancher_gen/run
COPY default.j2 /nginxconf

# Step 4 - Use baseimage-docker's init system.
CMD ["/sbin/my_init"]

# Step 5 - Expose ports.
EXPOSE 80
EXPOSE 443

Let’s break down the Dockerfile step by step. Steps 1 and 2 are
self-explanatory: simply install nginx, python and rancher-gen. Step 3
is where we setup the services that run when the image starts. The first
service is nginx, and it runs using the file at
/etc/servce/nginx/run. The contents of this file are:

#!/bin/bash
 rancher-gen --host $RANCHER_GEN_HOST 
 --port $RANCHER_GEN_PORT 
 --access-key $RANCHER_GEN_ACCESS_KEY 
 --secret-key $RANCHER_GEN_SECRET_KEY 
 --project-id $RANCHER_GEN_PROJECT_ID 
 $RANCHER_GEN_OPTIONS 
 --notify "service nginx reload" /nginxconf/default.j2 /etc/nginx/sites-available/default

Notice how, after the notify step, we pass two paths, namely
/nginxconf/default.j2 and /etc/nginx/sites-available/default.
The former is the Jinja2 template and the latter is the output location
of the rendered template. Below are the contents of the default.j2 file:

upstream ghost.backend {
{% for container in containers %}
  {% if container['state'] == "running" %}
    server {{container['primaryIpAddress']}}:2368;
  {% endif %}
  {% endfor %}
}

server {
  listen 80;
  server_name ghost_demo;
  location / {
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header HOST $http_host;
    proxy_set_header X-NginX-Proxy true;
    proxy_pass http://ghost.backend;
    proxy_redirect off;
  }
}

Steps 4 and 5 of the Dockerfile set the run command
/sbin/my_init” in the
image and exposes ports 80 and 443. Now, it’s time to build the image:

$ docker build -t="pitrho/nginx-rancher-gen-demo" .

Step 3 — Create the nginx service and deploy it Now that we have
the nginx image, add the nginx service to the docker-compose.yml file
created in Step 1:

ghost:
 image: ghost
 expose:
 - "2368"

nginx:
 image: pitrho/nginx-rancher-gen-demo:latest
 ports:
 - 80:80
 links:
 - ghost
 environment:
 NGINX_RUN_TYPE: rancher-gen
 RANCHER_GEN_HOST: $RANCHER_HOST
 RANCHER_GEN_PORT: $RANCHER_PORT
 RANCHER_GEN_ACCESS_KEY: $RANCHER_ACCESS_KEY
 RANCHER_GEN_SECRET_KEY: $RANCHER_SECRET_KEY
 RANCHER_GEN_PROJECT_ID: $RANCHER_GEN_PROJECT_ID
 RANCHER_GEN_OPTIONS: --stack demo --service ghost

The RANCHER_GEN_OPTIONS environment variable above is used to pass
additional command-line options to rancher-gen. See the Rancher-Gen
documentation for an explanation of these options. Now run
rancher-compose to start the nginx service:

$ rancher-compose -p demo up -d nginx

At this point, both the ghost and nginx services should be up and
running. ghost nginx
check
and ghost can be accessed by pointing your browser to the IP address of
the host running the nginx container: ghost
screen
If you were to inspect the nginx container using the shell, and open the
rendered file /etc/nginx/sites-enabled/default, you will see the
following output:

upstream ghost.backend {
   server 10.42.136.216:2368;
 }

server {
     listen 80;
     server_name ghost_demo;

     location / {
         proxy_set_header X-Real-IP $remote_addr;
         proxy_set_header HOST $http_host;
         proxy_set_header X-NginX-Proxy true;
         proxy_pass http://ghost.backend;
         proxy_redirect off;
     }
  }

As expected, this is the rendered output based on the template specified
when running the rancher-gen command. At this point, if you were to
upgrade the ghost service, and again look at the rendered file, you
would notice that the IP address under the upstream section has changed.

Conclusion

To recap, Rancher-Gen is an automation utility that can be used to
generate files and run notification commands. With the expressiveness of
Jinja2 templates, and its clean command line interface, Rancher-Gen can
be used to generate most configuration files, and automate tasks that
otherwise would be tedious and repetitive for most sysadmins and
software engineers. If you have any questions or suggestion on how to
improve Rancher-Gen, feel free to reach us through the
github repository, or contact
us on Twitter @PitRho.

(Visited 1 times, 1 visits today)