Héberger votre forge Gitea/Drone avec Docker en HTTPS (partie 2)

Posted by Trouverie Joachim on Sat 26 October 2019

Vous devrez récupérer les identifiants OAuth2 que vous aurez créés lors de la précédente étape et les stocker sous forme de variables d'environnement DRONE_GITEA_CLIENT_ID et DRONE_GITEA_CLIENT_SECRET dans votre fichier .env.

De plus nous aurons besoin du nom de domaine principal sur lequel vous allez déployer votre infrastructure. Pour plus de facilité stockez le sous la forme de la variable d'environnement DOMAIN dans le même fichier.

Drone fonctionne grâce à un système d'agents pour lancer les différentes étapes de votre intégration continue. Dans cet exemple nous mettrons en place un serveur Drone avec un seul agent, libre à vous de le modifier pour en ajouter de nouveaux selon vos besoins.

Modification du conteneur de base de données

Drone, tout comme Gitea, nécessite une connexion avec une base de données pour fonctionner. L'installation de base repose sur une base de données SQLite embarquée directement dans le conteneur. Pour plus de robustesse et comme nous avons mis en place un conteneur Postgresql pour notre Gitea, nous allons utiliser ce même conteneur avec Drone.

Il vous est possible de déployer un nouveau conteneur dédié à la base de données de Drone. Il est cependant possible en s'appuyant sur un script d'héberger plusieurs base de données dans le même conteneur.

Ce script va récupérer les données stockées dans la variable d'environnement POSTGRES_MULTIPLE_DATABASES afin de créer plusieurs bases de données, chacune avec un utilisateur dédié, au lieu du nom de l'unique base de données précédemment stocké dans la variable POSTGRES_DB.

Créons dans un premier temps le dossier que nous montrons en volume dans le conteneur et téléchargeons y le script en question.

$ mkdir volumes/dbconf
$ wget -O volumes/dbconf/multipledb.sh https://raw.githubusercontent.com/mrts/docker-postgresql-multiple-databases/master/create-multiple-postgresql-databases.sh
$ chmod +x volumes/dbconf/multipledb.sh

Modifions ensuite notre service db comme indiqué dans la documentation du projet:

  db:
    image: postgres:9.6
    container_name: db
    restart: always
    environment:
      - POSTGRES_USER=${DB_USER}
      - POSTGRES_PASSWORD=${DB_PASSWORD}
      - POSTGRES_MULTIPLE_DATABASES=gitea,drone
    volumes:
      - ./volumes/db:/var/lib/postgresql/data/
      - ./volumes/dbconf:/docker-entrypoint-initdb.d/

Les différences avec le conteneur précédemment mis en place sont les suivantes

  • Suppression de la clé POSTGRES_DB
  • Ajout d'une nouvelle clé POSTGRES_MULTIPLE_DATABASES qui contient l'ensemble des bases de données que nous voulons initialiser
  • Ajout du volume ./volumes/dbconf:/docker-entrypoint-initdb.d/ dont le contenu sera exécuté lors de la mise en place du conteneur

Ajout des conteneurs Drone

Tout comme Gitea nous nous baserons sur la dernière image disponible sur le hub pour cette installation, 1.6.1 lors de la rédaction de cet article.

Drone lance des conteneurs pour pouvoir effectuer les étapes définies dans vos fichier d'intégration continue. Pour cela il partage la socket docker de votre host via un volume que vous pourrez retrouver ci-dessous sous l'appellation /var/run/docker.sock.

Drone et son agent communique via le réseau par l'intermédiaire d'un secret partagé. Ce secret est à configurer au niveau des variables d'environnement des conteneurs.

Créons un mot de passe de 50 caractères via le script Python ci-dessous puis enregistrons le dans notre fichier .env avec la clé DRONE_SECRET.

import string
import random

print(
    "".join([
        random.choice(
            string.ascii_uppercase +
            string.ascii_lowercase +
            string.digits
        )
        for _ in range(50)
    ])
)

Le serveur Drone nécessitera la mise en place d'un volume pour les données persistantes de l'application, créons le dossier correspondant dans notre dossier volumes.

mkdir volumes/drone

Ajoutons maintenant deux services dans notre fichier docker-compose pour notre serveur Drone et son agent.

drone-server:
  image: drone/drone:latest
  container_name: drone-server
  restart: always
  environment:
    - DRONE_GITEA_SERVER=https://git.${DOMAIN}
    - DRONE_GITEA_CLIENT_ID=${DRONE_GITEA_CLIENT_ID}
    - DRONE_GITEA_CLIENT_SECRET=${DRONE_GITEA_CLIENT_SECRET}
    - DRONE_GIT_ALWAYS_AUTH=false
    - DRONE_RPC_SECRET=${DRONE_SECRET}
    - DRONE_SERVER_PROTO=https
    - DRONE_SERVER_HOST=drone.${DOMAIN}
    - VIRTUAL_HOST=drone.${DOMAIN}
    - VIRTUAL_PORT=80
    - DRONE_DATABASE_DRIVER=postgres
    - DRONE_DATABASE_DATASOURCE=postgres://${DB_USER}:${DB_PASSWORD}@db:5432/drone?sslmode=disable
    - DRONE_USER_CREATE=username:admin,admin:true
  volumes:
    - ./volumes/drone:/data
    - /var/run/docker.sock:/var/run/docker.sock
  depends_on:
    - git
    - db

drone-agent:
  image: drone/agent:1
  container_name: drone-agent
  restart: always
  depends_on:
    - drone-server
  volumes:
    - ./volumes/drone/cache:/var/lib/cache
    - /var/run/docker.sock:/var/run/docker.sock
  environment:
    - DRONE_RPC_HOST=drone-server
    - DRONE_RPC_PROTO=http
    - DRONE_RPC_SECRET=${DRONE_SECRET}

Quelques explications sur les clés utilisées ici:

DRONE SERVER

Clé Valeur
DRONE_GITEA_SERVER Url pour accéder à notre instance Gitea
DRONE_GITEA_CLIENT_ID et DRONE_GITEA_CLIENT_SECRET Les identifiants de l'application OAuth2 paramétrés lors de la précédente étape
DRONE_RPC_SECRET La clé permettant la communication entre Drone et l'agent
DRONE_SERVER_HOST et VIRTUAL_HOST L'url de notre instance Drone
VIRTUAL_PORT Port sur lequel tourne l'application
DRONE_DATABASE_DRIVER Type de base de données utilisé
DRONE_DATABASE_DATASOURCE Url pour accéder au conteneur de notre base de données
DRONE_USER_CREATE Utilisateur admin (il doit correspondre à votre utilisateur Gitea)

DRONE AGENT

Clé Valeur
DRONE_RPC_HOST Nom du conteneur du serveur Drone

Vous prendrez bien un peu d'HTTPS

Pour le HTTPS, nous allons mettre en place un conteneur Nginx qui servira de proxy vers nos deux applications. Nous ne traiterons pas de la génération des certificats en eux même. Je vous laisse vous renseigner il existe déjà beaucoup de tutoriels sur le sujet.

Commençons, comme d'habitude, par la création de nos volumes pour le conteneur. Le premier, proxy, contiendra la configuration pour Nginx. Le second certs permettra de stocker les certificats afin que le conteneur y ait accès.

Nous téléchargerons également les configurations ssl du certbot de Letsencrypt qui référence les bonnes pratiques à mettre en place sur notre serveur Nginx.

$ mkdir volumes/{proxy,certs}
$ mkdir volumes/certs/letsencrypt/
$ cd volumes/certs/letsencrypt/
$ wget https://raw.githubusercontent.com/certbot/certbot/master/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx.conf
$ wget https://raw.githubusercontent.com/certbot/certbot/master/certbot/ssl-dhparams.pem

Ajoutons notre nouveau service proxy à notre fichier docker-compose. Nous lierons les ports 80 et 443 de notre host au conteneurs afin que ce dernier puisse faire office de serveur vers l'extérieur.

proxy:
  image: nginx:latest
  container_name: proxy
  restart: always
  ports:
    - "80:80"
    - "443:443"
  volumes:
    - ./volumes/proxy:/etc/nginx/conf.d/
    - ./volumes/certs:/etc/certs
  depends_on:
    - git
    - drone-server

Nous en avons fini avec le fichier docker-compose, le résultat final peut être téléchargé ici

La configuration Nginx

Les services drone-server et gitea servent respectivement leurs applications sur les ports 80 et 3000. Nous allons mettre en place une configuration pour Nginx afin que ce dernier serve de proxy selon le nom de domaine vers ces dernières.

Les applications seront accessibles uniquement en HTTPS grâce aux redirections que nous mettrons en place.

Le fichier default.conf va être placé dans notre dossier ./volumes/proxy/ et servir de fichier de configuration à Nginx:

upstream drone {
    # le nom du conteneur pour le serveur drone dans notre docker-compose
    # pas de port renseigné => port 80
    server drone-server;
}
upstream git {
    # le nom du conteneur pour le serveur gitea dans notre docker-compose
    server gitea:3000;
}
server {
    listen 80;
    listen [::]:80;

    # changer le nom de domaine
    server_name git.domaine.com;

    rewrite ^ https://$host$request_uri? permanent;
}
server {
    listen 80;
    listen [::]:80;

    # changer le nom de domaine
    server_name drone.domaine.com;

    rewrite ^ https://$host$request_uri? permanent;
}
server {
    listen 443 ssl;

    # changer le nom de domaine
    server_name git.domaine.com;
    client_max_body_size 300m;
    include /etc/certs/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/certs/letsencrypt/ssl-dhparams.pem;

    ssl_certificate /etc/certs/git-fullchain.pem;
    ssl_certificate_key /etc/certs/git-privkey.pem;

    location / {
        proxy_pass http://git;
    }
}
server {
    listen 443 ssl;

    # changer le nom de domaine
    server_name drone.domaine.com;
    include /etc/certs/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/certs/letsencrypt/ssl-dhparams.pem;

    ssl_certificate /etc/certs/drone-fullchain.pem;
    ssl_certificate_key /etc/certs/drone-privkey.pem;

    location / {
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        # changer le nom de domaine
        proxy_set_header Host drone.domaine.com;
        proxy_pass http://drone;
        proxy_redirect off;
        proxy_http_version 1.1;
        proxy_buffering off;
        chunked_transfer_encoding off;
    }
}

À partir de maintenant, vous pouvez lancer votre docker-compose et avoir votre forge et votre CI qui fonctionnent de concert.