/ Docker

Continuous Deployment mit GitLab-CI

Die Zukunft des Betrieb von Web-Anwendungen liegt in Containern. Dieser Artikel beschreibt eine einfache Möglichkeit Docker-Container automatisiert zu bauen und zu veröffentlichen. Alles was gebraucht wird, sind GitLab, GitLab-CI-Runner und ein Server mit installiertem Docker-Daemon.

Voraussetzungen

GitLab Runner

GitLab-CI mit Docker-Registry und einem CI-Runner mit Freigeschaltetem Zugriff auf den Docker-Service des Hosts ( kein Basis-Image notwendig, kein Docker in Docker, ...) sowie der Ziel-Server mit installiertem Docker Deamon werden benötigt.

Docker Deamon

Der Docker-Deamon sollte von außen erreichbar sein. Dafür müssen Server und Client-Zertifikate generiert werden, um den Docker-Socket über Internet erreichbar zu machen: https://docs.docker.com/engine/security/https/. Der Deamon wird gestartet und angewiesen die generierten Zertifikate zu verwenden.

  dockerd --tlsverify \
    --tlscacert=~/.docker/ca.pem \
    --tlscert=~/.docker/server-cert.pem \
    --tlskey=~/.docker/server-key.pem \
    -H=0.0.0.0:2376 \
    -H=unix:///var/run/docker.sock &

Deployment

Vorbereitungen

Bevor unsere Applikation ausgeliefert werden kann, muss in GitLab die Verbindung zum Docker-Host ermöglicht werden.

Die Client-Zertifikate werden als Secret-Variables gespeichert unter Settings --> CI / CD --> Secret variables. Einfach die Inhalte der Dateien in die Textfelder eintragen.

Die Inhalte der Variablen (z.B. CAPEM, CERTPEM, KEYPEM) werden dann bei jedem Start des CI-Vorgangs in temporäre Deteien geschrieben, um den Zugriff auf unseren Docker-Deamon zu ermöglichen. In der gitlab-ci.yml:

before_script:
    - mkdir -p ~/.docker
    - echo "${CAPEM}" > ~/.docker/ca.pem
    - echo "${CERTPEM}" > ~/.docker/cert.pem
    - echo "${KEYPEM}" > ~/.docker/key.pem

Weitere Einstellungen, wie der Hostname unseres Docker-Servers und die Aktivierung der TLS-Verifizierung können als globale Variablen am Anfang der gitlab-ci.yml deklariert werden:

variables:
  DOCKER_HOST: "tcp://example.com:2376"
  DOCKER_TLS_VERIFY: 1

Bauen und ausliefern

Wenn die Verbindung zum Docker-Host steht, kann die Applikation gebaut und ausgeliefert werden. Docker- oder Docker-Compose-Befehle können wie lokal verwendet werden. Mit einer lokaler Docker-Compose-Datei können z-B. alle benötigten Container auf dem Entfernten Docker-Host gestartet werden.

script:
    - docker build -t ${CI_REGISTRY}/${CI_PROJECT_NAMESPACE}/${CI_PROJECT_NAME}:${CI_COMMIT_SHA} .
    - echo ${CI_REGISTRY_PASSWORD} | docker login ${CI_REGISTRY} -u ${CI_REGISTRY_USER} --password-stdin
    - docker push ${CI_REGISTRY}/${CI_PROJECT_NAMESPACE}/${CI_PROJECT_NAME}:${CI_COMMIT_SHA}
    - docker-compose up -d --force-recreate
    - docker logout ${CI_REGISTRY}

Die ganze .gitlab-ci.yml könnte so aussehen:

before_script:
  - mkdir -p ~/.docker
  - echo "${CAPEM}" > ~/.docker/ca.pem
  - echo "${CERTPEM}" > ~/.docker/cert.pem
  - echo "${KEYPEM}" > ~/.docker/key.pem

stages:
  - staging // ToDo: add stages "build" and "test"
  - production

staging:
  stage: staging
  variables:
    DOCKER_IMAGE: "${CI_REGISTRY}/${CI_PROJECT_NAMESPACE}/${CI_PROJECT_NAME}:staging_${CI_COMMIT_SHA}"
    DOCKER_HOST: "tcp://docker.example.com:2376"
    DOCKER_TLS_VERIFY: 1
    DOCKER_CERT_PATH: "/home/gitlab-runner/.docker/"
  script:
    - docker build -t ${DOCKER_IMAGE} -f Dockerfile.stage .
    - echo ${CI_REGISTRY_PASSWORD} | docker login ${CI_REGISTRY} -u ${CI_REGISTRY_USER} --password-stdin
    - docker push ${DOCKER_IMAGE}
    - docker-compose -p inettech_staging -f docker-compose-staging.yml up -d --force-recreate
    - docker logout ${CI_REGISTRY}
    - docker ps -a
  only:
    - master

production:
  stage: production
  when: manual
  variables:
    DOCKER_IMAGE: "${CI_REGISTRY}/${CI_PROJECT_NAMESPACE}/${CI_PROJECT_NAME}:production_${CI_COMMIT_SHA}"
    DOCKER_HOST: "tcp://docker.example.com:2376"
    DOCKER_TLS_VERIFY: 1
    DOCKER_CERT_PATH: "/home/gitlab-runner/.docker/"
  script:
    - docker build -t ${DOCKER_IMAGE} .
    - echo ${CI_REGISTRY_PASSWORD} | docker login ${CI_REGISTRY} -u ${CI_REGISTRY_USER} --password-stdin
    - docker push ${DOCKER_IMAGE}
    - docker-compose -p inettech_production up -d --force-recreate
    - docker logout ${CI_REGISTRY}
    - docker ps -a
  only:
    - master