Suite à l’article de tferdinand sur l’installation de hugo sur un S3 (d’ailleurs vivement les prochaines parties), et surtout suite au commentaire de Lord, je me suis demandé s’il était possible de faire la même chose en on-premise (en fait j’avais déjà la réponse). Et vous savez quoi ? C’est possible.
Pour ceci nous utiliserons ceci :
- gitea pour remplacer github
- drone pour remplacer github actions
- minio pour remplacer s3
- traefik en reverse proxy
Nous installerons le tout sur docker, simplement parce que mon infra tourne actuellement dessus (en l’attente d’ansibiliser tout, mais ça prend beaucoup de temps).
Je pars donc du principe que vous avez déjà un traefik d’installé et configuré. Personnellement pour traefik, j’utilise des fichiers de configuration à plat, au lieu des labels, tout simplement pour avoir un docker-compose.yml beaucoup plus clair.
Actuellement mon CI/CD est géré par gitlab-ci, avec création d’une image docker puis déploiement sur monde serveur. Je vais donc faire une migration de mon blog vers cette nouvelle stack.
Gitea
Pour Gitea, nous utiliserons l’image officielle, avec une base de donnée postgreSQL.
Docker-compose
gitea:
image: gitea/gitea:1
mem_limit: "500m"
cpus: "0.2"
environment:
- USER_UID=1000
- USER_GID=1000
restart: always
networks:
- net
- lan
volumes:
- ./data/gitea:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
ports:
- "222:22"
depends_on:
- gitea-db
gitea-db:
image: postgres:13
mem_limit: "100m"
cpus: "0.2"
restart: always
environment:
- POSTGRES_USER=gitea
- POSTGRES_PASSWORD=gitea
- POSTGRES_DB=gitea
networks:
- lan
volumes:
- ./data/gitea-db:/var/lib/postgresql/data
Pensez à adapter les variables et les volumes
Puis on lance :
$ docker-compose up -d
Traefik
Nous créons donc un service et un router coté traefik :
http:
services:
gitea:
loadBalancer:
servers:
- url: "http://gitea:3000"
routers:
gitea:
rule: "Host(`gitea.domaine.fr`)"
entryPoints:
- "websecure"
service: "gitea@file"
tls: {}
Configurer gitea
Pour ceci, il suffit d’aller sur https://gitea.domaine.fr, et de suivre les instructions, pour configurer la base de donnée, votre utilisateur administrateur, et quelques options.
Une fois ceci fait, vous retrouverez toutes les options de votre instance dans ./data/gitea/gitea/conf/app.yml
, vous pourrez modifier au besoin.
Drone
Gitea
Avant d’installer drone, il faudra créer une application Oauth sur gitea, pour ceci, vous pouvez suivre cette page de la documentation de Drone.
Mais en gros :
- Allez dans configuration
- Puis dans l’onglet Applications
- Dans l’encadrer “Gérer les applications OAuth2”, choisir un nom d’application comme drone puis ajouter l’url de redirection https://drone.domaine.fr/login
- Cliquez sur Créer une application
- Sur cette nouvelle page, copier l’ID du client et le secret, car il ne serons plus accessibleune fois Enrigistré.
- Pour finir, on clique sur enregistrer
Docker-compose
Tout comme gitea, nous utiliserons une base de donnée postgreSQL avec drone, une base sqlite peux vous être suffisant. Voici donc le docker-compose.yml :
drone:
image: drone/drone:2
restart: always
mem_limit: "500m"
cpus: "0.2"
environment:
- DRONE_GITEA_SERVER=https://gitea.domaine.fr
- DRONE_GITEA_CLIENT_ID=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXXX
- DRONE_GITEA_CLIENT_SECRET=XXXXXXXXXXXXXXXXXXXXXXXXXXXX
- DRONE_RPC_SECRET=XXXXXXXXXXXXXXXXXXXX
- DRONE_SERVER_HOST=drone.domaine.fr
- DRONE_SERVER_PROTO=https
- DRONE_DATABASE_DRIVER=postgres
- DRONE_DATABASE_DATASOURCE=postgres://drone:drone@drone-db:5432/drone?sslmode=disable
- DRONE_GIT_ALWAYS_AUTH=true
networks:
- lan
- net
depends_on:
- drone-db
drone-db:
image: postgres:13
mem_limit: "100m"
cpus: "0.2"
restart: always
environment:
- POSTGRES_USER=drone
- POSTGRES_PASSWORD=drone
- POSTGRES_DB=drone
networks:
- lan
volumes:
- ./data/drone-db:/var/lib/postgresql/data
Pensez bien à remplacer les variables DRONE_GITEA_CLIENT_ID
et DRONE_GITEA_CLIENT_SECRET
obtenus par la création d’une application Oauth sur gitea.
Pour la variable DRONE_RPC_SECRET
, c’est à vous de la générer, la doc officiel conseille cette commande : openssl rand -hex 16
.
Traefik
Nous créons également un service et un router coté traefik :
http:
services:
drone:
loadBalancer:
servers:
- url: "http://drone:80"
routers:
drone:
rule: "Host(`drone.domaine.fr`)"
entryPoints:
- "websecure"
service: "drone@file"
tls: {}
Il ne vous reste plus qu’à vous rendre sur https://drone.domaine.fr, et vous authentifier via Gitea.
Installation d’un runner
Pour exécuter nos pipelines, nous avons besoin d’un runner. Ici nous utiliserons un runner docker, pour ceci voici comment le configurer :
drone-runner:
image: drone/drone-runner-docker:1
mem_limit: "100m"
cpus: "0.2"
restart: always
networks:
- lan
- net
environment:
- DRONE_RPC_PROTO=http
- DRONE_RPC_HOST=drone:80
- DRONE_RPC_SECRET=XXXXXXXXXXXXXXXXXXXXXXX # Doit être le même que sur drone
- DRONE_RUNNER_CAPACITY=2
- DRONE_RUNNER_NAME=runner
- DRONE_RUNNER_NETWORKS=docker_net # Networks créer par docker-compose
volumes:
- /var/run/docker.sock:/var/run/docker.sock
Minio
Maintenant que nous avons notre stack de CI/CD, nous allons installer Minio.
Minio est un serveur de fichier objet comme AWS S3, avec la même API, mais en auto-hébergé. Il est fortement scalable au besoin. Ici nous n’utiliserons qu’une seule instance.
Docker-compose
minio:
image: minio/minio
mem_limit: '500m'
cpus: '0.2'
networks:
- lan
- net
volumes:
- ./data/minio:/data
environment:
- MINIO_ROOT_USER=admin
- MINIO_ROOT_PASSWORD=unicornIsReal
ports:
- "9000:9000"
- "9001:9001"
command: server --console-address ":9001" --address ":9000" /data
Pas de création de configuration traefik cette fois-ci, n’ayant pas encore regardé la sécurité du côté minio, je préfère garder ceci en local pour le moment.
Création du bucket
Il nous faut un bucket pour stocker notre blog, nous commençons par créer un alias pour nous connecter à notre S3 :
$ docker run -ti --rm --network docker_lan -v /docker/data/mc:/root/.mc minio/mc alias set minio http://minio:9000 admin unicornIsReal --api s3v4
Puis nous pouvons créer un bucket :
docker run -ti --rm --network docker_lan -v /docker/data/mc:/root/.mc minio/mc mb minio/blog
Puis surtout, il faut autoriser à télécharger les fichiers sans authentification :
docker run -ti --rm --network docker_lan -v /docker/data/mc:/root/.mc minio/mc policy set download minio/blog
A partir de maintenant, vous pouvez déjà tester si ça fonctionne, en tapant directement sur http://<URL_DE_VOTRE_SERVEUR>:9000/blog, mais bon nous n’avons rien actuellement.
Pipeline
Activation du repo
D’abord, il faut activer le repo sur drone, il suffit de se rendre sur le dashboard de drone, de choisir le repository et de cliquer sur “Activate Repository”. Le repo est maintenant surveillé par drone.
Création de la pipeline
Dans notre repo git, nous allons créer un fichier .drone.yml, où nous aurons les instructions pour la construction et le déploiement de notre blog :
kind: pipeline
type: docker
name: blog
steps:
- name: build
image: plugins/hugo
settings:
hugo_version: 0.91.2
validate: true
- name: deploy
image: plugins/s3
settings:
bucket: blog
access_key: admin
secret_key: unicornIsReal
source: public/**/*
target: /
endpoint: http://minio:9000
strip_prefix: public/
path_style: true
depends_on:
- build
Là nous avons de la chance, il existe des plugins spécialement pour notre besoin, donc même pas besoin de scripter.
Nous pouvons donc pousser notre code avec un git push
, et notre pipeline devrait automatiquement s’exécuter.
Une fois notre pipeline ok, nous pouvons tester via http://<URL_DE_VOTRE_SERVEUR>:9000/blog, vous devriez avoir la liste des fichiers présents dans votre bucket.
Vous pouvez également afficher directement votre page, via http://<URL_DE_VOTRE_SERVEUR>:9000/blog/index.html, cependant vous aller avoir quelques soucis avec l’affichage. Car tout simplement, il n’arrive pas à charger les librairies CSS et/ou JS. C’est là que traefik va rentrer en jeux.
Traefik
Pour traefik, cette fois-ci nous aurons quelques spécificités.
- Suppression d’un préfixe entre le reverse et minio, effectivement, j’accède à mon blog via https://blog.domaine.fr, mais derrière nous avons http://minio:9000/blog.
- Forcer la lecture du index.html, en effet, minio n’est pas capable de faire ceci, et de base ce n’est pas le rôle de traefik non plus mais nous pouvons tricher pour le faire. Si nous ne mettons pas ceci, il affichera toujours le contenu du répertoire.
- Et au final, il faudra prévoir également une page d’erreur en cas de 404 par exemple, afin de ne pas générer une page d’erreur minio.
Donc voici ma configuration de traefik :
http:
services:
blog:
loadBalancer:
servers:
- url: "http://minio:9000"
middlewares:
add-blog:
addPrefix:
prefix: "/blog"
add-index:
replacePathRegex:
regex: "^(.*)/$"
replacement: "$1/index.html"
error:
errors:
status:
- "400-499"
- "500-599"
service: "blog@file"
query: "/blog/404.html"
routers:
blog:
rule: "Host(`blog.domaine.fr`)"
entryPoints:
- "websecure"
middlewares:
- "add-blog@file"
- "add-index@file"
- "error@file"
service: "blog@file"
tls: {}
Nous avons donc 3 middlewares :
- add-blog : De type addPrefix qui permet entre le router et le service de rajouter
/blog
. Grace à ceci, quand vous irez sur https://blog.domaine.fr/index.html, il tapera sur http://minio:9000/blog/index.html. - add-index : De type replacePathRegex qui permet l’ajout automatique du index.html si l’url se termine par /.
- error: De type errors pour pouvoir afficher une page spécifique si une erreur entre 400 et 599. Pour l’instant j’ai mis qu’une 404.html pour tout.
Avec ceci, normalement vous avez un blog fonctionnel.
Amélioration
Utilisation de secret
Pour sécuriser un peu notre pipeline, nous pouvons ajouter des secrets pour la gestion des identifiants minio, ce que nous donne pour cette étape :
- name: deploy
image: plugins/s3
settings:
bucket: blog
access_key:
from_secret: minio_access_key_id
secret_key:
from_secret: minio_access_key_secret
source: public/**/*
target: /
endpoint: http://minio:9000
strip_prefix: public/
path_style: true
depends_on:
- build
Plusieurs environnements
Là j’ai déjà déployé sur ma production, mais je peux vouloir plusieurs environnements, dev, staging et production par exemple. Pour ceci il va falloir multiplier les buckets, revoir un peu la configuration de traefik, et ajouter des étapes au .drone.yml. Nous pourrions même imaginer des environnements à la volée, dès qu’on pousse sur une branche, nous créons un domaine, un bucket et une règle traefik pour ceci.
Mon .drone.yml final (ou presque)
Voilà mon .drone.yml actuel, il est fonctionnel mais pas encore optimal :
kind: pipeline
type: docker
name: blog
steps:
- name: build
image: plugins/hugo
settings:
hugo_version: 0.91.2
validate: true
- name: deploy dev
image: plugins/s3
settings:
bucket: blog-dev
access_key:
from_secret: minio_access_key_id
secret_key:
from_secret: minio_access_key_secret
source: public/**/*
target: /
endpoint: http://minio:9000
strip_prefix: public/
path_style: true
when:
branch:
- dev
depends_on:
- build
- name: deploy staging
image: plugins/s3
settings:
bucket: blog-staging
access_key:
from_secret: minio_access_key_id
secret_key:
from_secret: minio_access_key_secret
source: public/**/*
target: /
endpoint: http://minio:9000
strip_prefix: public/
path_style: true
when:
branch:
- staging
depends_on:
- build
- name: deploy production
image: plugins/s3
settings:
bucket: blog
access_key:
from_secret: minio_access_key_id
secret_key:
from_secret: minio_access_key_secret
source: public/**/*
target: /
endpoint: http://minio:9000
strip_prefix: public/
path_style: true
when:
branch:
- main
depends_on:
- build
Il n’est pas encore parfait, car drone a beaucoup changé depuis ma dernière utilisation, et là j’ai tout de même un souci avec une altération de livrable entre les environnements, je verrais par la suite comment bien configurer tout ça.
Conclusion
Comme vous pouvez le voir, ça fonctionne bien … Ba oui, ce blog est maintenant hébergé sur Minio.
Je ne suis pas sûr que je vais garder cette installation, mais elle me plait bien, et j’ai adoré la mettre en place, ça change des installations plus classique.