March 17, 2017

Deploy to Docker Swarm using Gitlab CI


UPDATE 2017-06-07: Added info about secure connections


You might have heard of awesome Gitlab and it’s builtin CI. Also, Docker might ring some bells. And Gitlab loves Docker.

I’m using Gitlab CI with Docker for a long time now, even before Gitlab included the container registry. Playing around with Swarm some months ago immediately made me want to combine all of them. The whole Kubernetes thing is pretty cool but sometimes just too much. Your Dev’s probably rather want to code instead of playing DevOp or Sysadmin. Also i must admit that running Docker swarm is much easier than running a full blown Kubernetes stack. And Docker commands in swarm mode are almost the same, so hurdles for developers are almost gone if they are already using Docker ;)

Gitlab builds Docker

Gitlab can (and should) be used to build Docker images. I’ve created my little build snippet to build on changes to Dockerfile only.

So we have some mechanism which builds our image for CI, e.g. containing your Node JS or Python or Go or whatever you like most app. Let’s go on and use it somehow.

Gitlab uses Docker

Now, your CI runs some tests, linting and so on, probably creating artifacts or whatever. With Docker, of course. Using the image we just built. Right? ;)

Gitlab deploys to Swarm

Let’s assume tests were good. We now have a container which runs an app, e.g. a website. We now want to see, if things are really working or discuss things with the team or present it to a responsible person or …

Enter Swarm.

Let’s just deploy the container to swarm and make it available somehow. For development, we are just using unsecured Swarm nodes. I’ve also added some Swarm helpers to simplify CI files and manage code changes or additions in multiple projects.

Let’s assume we built Docker images tagged with $CI_BUILD_REF_NAME (see variables). Let’s deploy using Docker compose file and make available to the outside using flow proxy.

Content of docker-stack-compose.yml:

version: '3.1'
services:
  gui:
    image: "${CI_REGISTRY}/${CI_PROJECT_NAMESPACE}/${CI_PROJECT_NAME}:${CI_BUILD_REF_NAME}"
    networks:
      - gui-proxy
    volumes:
      - /etc/localtime:/etc/localtime:ro
    deploy:
      placement:
        constraints: [node.role == worker ]
      labels:
        com.df.notify: "true"
        com.df.distribute: "true"
        com.df.servicePath: "/"
        com.df.port: "3000"
        com.df.serviceDomain: "${CI_BUILD_REF_NAME}.example.com"

networks:  
  gui-proxy:
    external: true

Content of .gitlab-ci.yml:

deploy-to-swarm:
  stage: deploy
  variables:
    DOCKER_HOST: tcp://swarm.example.com:2375
    SERVICE_NAME: foobar
  image: docker:latest
  script:
    - docker login -u gitlab-ci-token -p "$CI_BUILD_TOKEN" "$CI_REGISTRY"
    - docker stack deploy --with-registry-auth --compose-file=docker-stack-compose.yml ${CI_PROJECT_NAMESPACE}-${CI_PROJECT_NAME}-${SERVICE_NAME}
  environment:
    name: master
    url: http://${CI_BUILD_REF_NAME}.example.com
  only:
    - master

If you’re using a secured docker daemon socket (and you should!), you’ll need to provide the client certificates. You could do this using Gitlab’s Secret Variables feature.

According to your level of protection, just add the variables with content of certs/keys and then create the neccessary layout for docker client during CI run. Also set DOCKER_TLS_VERIFY and use another socket (default 2376).

Example (Variables: TLSCACERT, TLSCERT, TLSKEY):

deploy-to-swarm:
  stage: deploy
  variables:
    DOCKER_HOST: tcp://swarm.example.com:2376
    DOCKER_TLS_VERIFY: 1
    SERVICE_NAME: foobar
  image: docker:latest
  script:
    - docker login -u gitlab-ci-token -p "$CI_BUILD_TOKEN" "$CI_REGISTRY"
    - mkdir -p ~/.docker
    - echo "$TLSCACERT" > ~/.docker/ca.pem
    - echo "$TLSCERT" > ~/.docker/cert.pem
    - echo "$TLSKEY" > ~/.docker/key.pem
    - docker stack deploy --with-registry-auth --compose-file=docker-stack-compose.yml ${CI_PROJECT_NAMESPACE}-${CI_PROJECT_NAME}-${SERVICE_NAME}
  environment:
    name: master
    url: http://${CI_BUILD_REF_NAME}.example.com
  only:
    - master

You get the idea? If not, just ping me, i’ll then provide posts following up on this. But as the folks at Docker and Gitlab both are doing a wonderful job maintaining their docs, you’ll find the answers probably somewhere in here: