TechDocs/TechnicalProcesses/DockerDeployment

Description

The goal of this document is to describe the recommended way of deploying a service to lund.fsfeurope.org with Docker. With this procedure, the service is automatically deployed and updated on lund. For this documentation we assume that the service is simple enough to fit in one Docker container and that the service is accessible through HTTP.

NOTE: Please make sure to follow the process for new services. Setting up a Docker container or VM is just the technical part but in order to make the FSFE's technical infrastructure clear and maintainable, we need proper communication and documentation.

General Idea

We have one server that hosts our containers (lund.fsfeurope.org). We have a Continuous Integration System, Drone, that builds and runs containers locally on it. Drone gets the commits from the git server, each commit triggers the creation of a "build" container, which in turn builds and runs containers with docker-compose. The reverse proxy container watches containers creation and create virtual hosts dynamically to route the HTTP traffic to the containers. Here is how it works:

1. Drone gets a message from Gitea (the git server) via a web hook. That can be commit, a tag, a push, etc. Drone sees the .drone.yml file of the repository the event is coming from, and execute the instructions in it.

2. The instructions in .drone.yml creates a container called docker:dind (Docker in Docker), then installs docker-compose and run 'docker-compose up --build -d'

3. docker-compose builds images and run container. As the container is "Docker in Docker', it controls the Docker daemon from lund directly, outside of the container.

4. As a final step, once the container of the application to be deployed is running, it gets connected to the "bridge" network so the reverse proxy container can route the traffic from the outside to the application.

Below are the configuration needed to deploy the application container on lund.

Dockerfile

The Dockerfile that builds the service should have 4 main items:

1. Selection of the base image with a FROM statement.

2. List of the ports that will be exposed so Docker knows what ports are used in the container. Use the EXPOSE statement

3. The actual build of the service. Install the service dependencies and the service itself

4. A CMD statement so the service is run automatically when the container starts.

An example Dockerfile is provided below:

# Base image selection
FROM debian:9

# List of ports that will be exposed to the outside world
EXPOSE 3000

# Installing the services and dependencies
ENV DEBIAN_FRONTEND=noninteractive LANG=en_US.UTF-8 LC_ALL=C.UTF-8 LANGUAGE=en_US.UTF-8
RUN [ "apt-get", "-q", "update" ]
RUN [ "apt-get", "-qy", "--force-yes", "upgrade" ]
RUN [ "apt-get", "-qy", "--force-yes", "dist-upgrade" ]
RUN [ "apt-get", "install", "--no-install-recommends", "-qy", "--force-yes", \
      "ca-certificates", \
      "perl", \
      "git", \
      "uuid-dev", \
      "sqlite3", \
      "build-essential", \
      "cpanminus" ]
RUN [ "apt-get", "clean" ]
RUN [ "rm", "-rf", "/var/lib/apt/lists/*", "/tmp/*", "/var/tmp/*" ]
RUN ["cpanm", "--notest", "DBI", "DBD::SQLite", "Mojolicious", "UUID" ]
RUN adduser --quiet --disabled-password --shell /bin/bash --home /home/perlscript --gecos "User" perlscript
USER perlscript
WORKDIR /home/perlscript
RUN git clone https://git.fsfe.org/vincent/mailman-reconfirm.git
WORKDIR /home/perlscript/mailman-reconfirm

# CMD to run the service on container start
CMD perl reconfirm.pl daemon -l http://0.0.0.0:3000

Here is the Dockerfile reference.

Docker compose file

Next, we need to create an docker-compose file. The docker-compose.yml file is responsible for building the images (with the Dockerfile) and running the containers. To run the container, there are mandatory options:

An example is provided below:

version: '3'
services:
  mailman-reconfirm:
    container_name: mailman-reconfirm
    build: .
    image: mailman-reconfirm
    restart: always
    expose:
      - 3000
    environment:
      - VIRTUAL_HOST=mailman-reconfirm.fsfe.org
      - LETSENCRYPT_HOST=mailman-reconfirm.fsfe.org
      - LETSENCRYPT_EMAIL=contact@fsfe.org
    volumes:
      - /srv/mailman-reconfirm/data:/home/perlscript/mailman-reconfirm/data/:rw

  # Connect the container which exposes the service to the 'bridge' network as
  # this is where the reverse proxy is
  connect-bridge:
    image: docker:dind
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    depends_on:
      - mailman-reconfirm
    command: /bin/sh -c 'docker network connect bridge mailman-reconfirm'

Here is the docker-compose file reference.

Drone configuration

Drone is the Continuous Integration and Continuous Delivery software we use. We need to tell Drone to build the image and run the container.

Add this file to your project and call it .drone.yml:

pipeline:
  deploy:
    commands:
      # Get docker-compose
      - wget -q -O /usr/local/bin/docker-compose https://github.com/docker/compose/releases/download/1.22.0/docker-compose-$(uname -s)-$(uname -m)
        # Make it executable
      - chmod 700 /usr/local/bin/docker-compose
      # Download and install glibc (Alpine comes with musl libc) because docker-compose relies on it
      - wget -q https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.27-r0/glibc-2.27-r0.apk https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.27-r0/glibc-bin-2.27-r0.apk
      - wget -q -O /etc/apk/keys/sgerrand.rsa.pub https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub
      - apk add -q --update glibc-2.27-r0.apk glibc-bin-2.27-r0.apk
      # Run docker compose
      - docker-compose up --build -d
    image: docker:dind
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    when:
       branch: master
       event: [push, tag, deployment]

This uses the Docker in Docker image (which is based on the alpine image) to run the docker-compose file inside a container. As the image is "Docker in Docker" and has the docker socket (/var.run/docker.sock) shared as a mountpoint, it controls the docker daemon directly on Lund, outside of the container. The command docker-compose up -d --build means that docker-compose will build the images, run the containers and exit.

In the current Drone version, you have to make the repository Trusted in the repository's Drone settings.

Commit

You can do a test commit to see if it deploys the container correctly.

git commit --allow-empty -m 'Trigger build'

How to use Drone secrets

Drone secrets allow you to securely insert secrets in your Docker images.

Add the secret in the Drone configuration

Make sure that Drone is activated for the repository, then connect to lund as root and run the following commands:

cd ~
. drone.cli.sh
./drone secret add --repository <repository path> --name <SECRET_NAME> --value <VALUE>

Add the secret to the .drone.yml file with the secrets keyword:

pipeline:
  deploy:
    image: docker:dind
    secrets: [ SECRET_NAME ]

Add the secret in docker-compose as a build argument

Instead of providing only the build context, provide the arguments as well.

Change the line:

    build: .

to:

    build:
       context: .
       args:
           - SECRET_NAME=${SECRET_NAME}

This will add the --build-arg flag to the docker build command.

Use the secret in the Dockerfile

Then you can add the argument in the Dockerfile by adding this line:

ARG SECRET_NAME

SECRET_NAME is considered as a environment variable for the image build and therefore can be used with $SECRET_NAME.

Note: This environment variable will not exist in the image, it's only available during the build.

Monitoring

Containers that have the VIRTUAL_HOST environment variable or the fsfe-monitoring: "true" label gets monitored by the container monitoring. See here for more details.

TechDocs/TechnicalProcesses/DockerDeployment (last edited 2018-11-15 11:22:13 by max.mehl)