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