Plus d’un an après mon article sur Hugo avec gitea, drone, minio et traefik, et beaucoup de modifications, je me suis dit qu’il serait temps de vous partager les évolutions.
Je reste cependant sur la forge gitea + drone, tout simplement parce que je l’aime bien ce petit duo.

Pourquoi changer ?!

Déjà je n’ai pas trouvé d’autre vrai utilité à mon S3 local, donc je maintenais minio que pour mon blog, je trouvais la solution un peu lourde pour un pauvre petit blog, malgré que très intéressant à mettre en place.
Ensuite je voulais sortir mon blog de mon serveur qui est chez moi dans mon garage, pour surtout 2 raisons. La première est que j’ai eu une longue période avec une connexion très instable, ce qui le rendait souvent indisponible. Ensuite ma machine sert également de seedbox, mais surtout de labs, donc je teste beaucoup de choses dessus, je casse souvent des trucs également et ayant de moins en moins de temps pour ces bidouilles, je peux laisser mon lab en carafe pendant plusieurs semaines.

J’ai donc décidé de passer chez scaleway, sur un petit stardust, ce qui est largement suffisant. Sur lequel j’ai installé un nginx avec une configuration un peu spécial. Et c’est tout, c’est ultra light, et ça déploie en 30 secondes.

Workflow de déploiement

J’ai décidé de partir sur un workflow très simple, je déploie en production que la branche main, et dès que je crée un nouvel article, je crée une branche pour celui ci. Celle-ci sera déployée sur mon serveur, dans un répertoire spécifique qui sera accessible via https://.blog.xataz.net.
Puisque quasiment tout ce que je fais est également à but pédagogique, j’ai décidé d’utiliser des concepts de droneio, et notamment les promotes.

Donc pour résumer :

  1. Création d’une branche pour mon article
  2. Push de mon article
  3. La pipeline build se lance, build mon blog, test la validité html, les fautes d’orthographe et upload un tarball de mon blog sur mon gitea
  4. La pipeline de deploiement se lance, me crée un environnement de review, et me notifie si c’est ok
  5. Je check que la mise en page est ok
  6. Je promote mon build sur drone
  7. Une autre pipeline se lance sur cette branche, supprime mon environnement de review, et merge sur la main
  8. Lancement d’une autre pipeline qui fait mon déploiement en production

Sachant que l’étape 6 et 7 peuvent être remplacé par un merge manuel, ou via gitea.

Configuration

Je pars du principe que vous avez déjà un gitea ainsi qu’un drone qui tourne, il existe des tonnes de ressources sur le sujet, je ne vois pas l’utilité d’en ajouter un. Je ne détaille pas également l’installation de nginx et la configuration de acme.sh, idem on trouve des ressources sur le sujet.

Configuration nginx

Pour permettre d’avoir un blog par branche, sans devoir reconfigurer mon nginx à chaque fois, j’ai créé 2 vhosts, un pour la prod, et un pour les reviews :

prod.conf :

server {
        listen 80 default_server;
        listen [::]:80 default_server;
        server_name xataz.net blog.xataz.net www.xataz.net;
        return 301 https://$host$request_uri;
}

server {
        listen 443 http2 ssl default_server;
        listen [::]:443 http2 ssl default_server;
        server_name xataz.net blog.xataz.net www.xataz.net;
        ssl_certificate /etc/nginx/ssl/certs/xataz.net.pem;
        ssl_certificate_key /etc/nginx/ssl/certs/xataz.net.key;
        ssl_session_timeout 1d;
        ssl_session_cache shared:MozSSL:10m;
        ssl_session_tickets off;
        ssl_protocols TLSv1.2 TLSv1.3;
        ssl_prefer_server_ciphers on;
        ssl_ciphers ECDH+AESGCM:ECDH+AES256:ECDH+AES128:DH+3DES:!ADH:!AECDH:!MD5;
        ssl_dhparam /etc/nginx/ssl/certs/dhparam.pem;
        ssl_stapling on;
        ssl_stapling_verify on;
        ssl_trusted_certificate /etc/nginx/ssl/certs/xataz.net.pem;
        add_header Strict-Transport-Security "max-age=63072000" always;

        location / {
                root /opt/www/blog/main;
                index index.html index.htm;
        }
}

Pour la prod rien de complexe pour ceux qui connaissent un peu nginx, mon blog est simplement stocké dans /opt/www/blog/main.

reviews.conf :

server {
        listen 80;
        server_name *.blog.xataz.net;
        return 301 https://$host$request_uri;
}

server {
        listen 443 http2 ssl;
        listen [::]:443 http2 ssl;
        server_name ~^(.+)\.blog\.xataz\.net$;
        set $file_path $1;

        ssl_certificate /etc/nginx/ssl/certs/blog.dev.xataz.net.pem;
        ssl_certificate_key /etc/nginx/ssl/certs/blog.dev.xataz.net.key;
        ssl_session_timeout 1d;
        ssl_session_cache shared:MozSSL:10m;
        ssl_session_tickets off;
        ssl_protocols TLSv1.2 TLSv1.3;
        ssl_prefer_server_ciphers on;
        ssl_ciphers ECDH+AESGCM:ECDH+AES256:ECDH+AES128:DH+3DES:!ADH:!AECDH:!MD5;
        ssl_dhparam /etc/nginx/ssl/certs/dhparam.pem;
        ssl_stapling on;
        ssl_stapling_verify on;
        ssl_trusted_certificate /etc/nginx/ssl/certs/blog.dev.xataz.net.pem;
        add_header Strict-Transport-Security "max-age=63072000" always;


        location / {
                root    /opt/www/blog/$file_path;
                index index.html index.htm;
        }
}

Là, c’est un peu plus spécifique :

  • server_name ~^(.+)\.blog\.xataz\.net$; : Regex pour récupérer le bon domaine, (.+) permets d’en récupérer le sous-domaine et le mettre dans une variable $1
  • set $file_path $1; : Pour que se soit plus propre, je crée une variable $file_path avec la valeur de $1
  • root /opt/www/blog/$file_path; : Qui permets d’indiquer le bon répertoire

Par exemple pour cet article, j’ai créé une branche blog-cicd, qui sera copié dans /opt/www/blog/blog-cicd, et qui sera accessible via https://blog-cicd.blog.xataz.net.

Les pipelines

Pour permettre tout ceci, j’ai créé 3 pipelines différentes. Toutes dans le fichier .drone.yml.
C’est un concept un peu particulier, mais pour ceux qui viennent de gitlab, les pipelines sont un peu comme les stages, et les steps, sont plutôt les jobs. Une pipeline peut être dépendante d’une autre, mais un step peut aussi être dépendant d’un autre. Cela permets d’avoir une gestion très fine de l’ordonnancement de notre workflow.
Nous n’avons cependant pas la possibilité d’avoir des jobs manuels sous drone. Pour pallier ceci, nous pouvons utiliser les promotes, qui permettent d’exécuter une autre pipeline par exemple.
Pour ceci, il me faut créer quelques secrets dans drone :

  • hosts : Le noms des hôtes sur lesquels je déploie
  • Private_Key : La clé ssh pour le déploiement
  • discord_webhook_id : Je m’envoie des notifications une fois déployer, ceci est l’id du webhook discord
  • discord_webhook_token : et son token
  • gitea_token : Token qui me permet la création de package
  • gitea_url : l’url de gitea

Pipeline de build

kind: pipeline
type: docker
name: build

steps:
- name: build
  image: plugins/hugo
  settings:
    hugo_version: 0.102.1
    validate: false

- name: html5validator
  image: painless/html5validator
  commands:
    - html5validator --root public/
  failure: ignore
  depends_on:
    - build

- name: languagetool
  image: alpine
  commands:
    - apk add --no-cache openjdk8-jre git
    - git fetch origin main
    - wget https://languagetool.org/download/LanguageTool-5.6.zip
    - unzip -q LanguageTool-5.6.zip
    - for file in $(git diff --name-only origin/main | grep ".md"); do sed -e '/---/,/---/d' -e '/\`\`\`/,/\`\`\`/d' $file | java -jar LanguageTool-5.6/languagetool-commandline.jar -d WHITESPACE_RULE -l fr -c utf-8 -; done
  failure: ignore
  depends_on:
    - build

- name: Push package to gitea
  image: alpine
  environment:
    DRONE_TO_GITEA_TOKEN:
      from_secret: gitea_token
    GITEA_URL:
      from_secret: gitea_url
  commands:
    - apk update && apk add curl
    - tar czvf blog.tar.gz public/
    - 'curl -XPUT -H "Authorization: token $DRONE_TO_GITEA_TOKEN" --upload-file blog.tar.gz $GITEA_URL/api/packages/${DRONE_REPO_OWNER}/generic/${DRONE_REPO_NAME}/${CI_COMMIT_SHA}/blog.tar.gz'
  depends_on:
    - build
    - html5validator
    - languagetool

trigger:
  branch:
    exclude:
      - main
      - master
  event:
    exclude:
      - promote
    include:
      - push

En gros j’ai 4 jobs

  • build : J’utilise le plugin hugo de drone, pas beaucoup de paramètre, et ça fonctionne directement.
  • html5validator : Pour valider mon html
  • languagetool : Pour valider mon orthographe
  • Push package to gitea : Pour pousser une archive de mon blog sur gitea

Cette pipeline ne sera exécuté que sur les branches non principale, car ça ne sert à rien de rebuild quelques choses de testé, en plus cela peut altérer le livrable entre la review et la production.
Pour la version de mon blog, je me base simplement sur le commit.

Voici ce que ça donne :

build

Pipeline de déploiement

kind: pipeline
type: docker
name: deploy 

steps:
- name: Download package from gitea
  image: alpine
  environment:
    DRONE_TO_GITEA_TOKEN:
      from_secret: gitea_token
    GITEA_URL:
      from_secret: gitea_url
  commands:
    - apk update && apk add curl
    - 'curl -XGET -H "Authorization: token $DRONE_TO_GITEA_TOKEN" $GITEA_URL/api/packages/${DRONE_REPO_OWNER}/generic/${DRONE_REPO_NAME}/${CI_COMMIT_SHA}/blog.tar.gz > blog.tar.gz'
    - tar xzvf blog.tar.gz

- name: deploy ${DRONE_BRANCH,,}
  image: drillster/drone-rsync
  settings:
    hosts: 
      from_secret: hosts
    username: root
    key:
      from_secret: Private_Key
    port: 22
    target: /opt/www/${DRONE_REPO_NAME}/${DRONE_BRANCH,,}
    source: public/
    prescript:
      - mkdir -p /opt/www/${DRONE_REPO_NAME}/${DRONE_BRANCH,,}
    script:
      - chown -R www-data:www-data /opt/www/${DRONE_REPO_NAME}/${DRONE_BRANCH,,}
  depends_on:
    - Download package

- name: Notify Success
  image: appleboy/drone-discord
  settings:
    webhook_id: 
      from_secret: discord_webhook_id
    webhook_token:
      from_secret: discord_webhook_token
    message: "Deployment on ${DRONE_BRANCH,,} done (https://${DRONE_BRANCH,,}.blog.xataz.net)"
    username: cicd
  when:
    status:
      - success
  depends_on:
    - deploy ${DRONE_BRANCH,,}

- name: Notify Failure
  image: appleboy/drone-discord
  settings:
    webhook_id: 
      from_secret: discord_webhook_id
    webhook_token:
      from_secret: discord_webhook_token
    message: "Deployment on ${DRONE_BRANCH,,} failed (https://${DRONE_BRANCH,,}.blog.xataz.net)"
    username: cicd
  when:
    status:
      - failure
  depends_on:
    - deploy ${DRONE_BRANCH,,}
    - Download package
    
trigger:
  event:
    - push
  • Download package from gitea : Récupères et extrait mon archive depuis gitea
  • Deploy ${DRONE_BRANCH,,} : Qui permets de copier les fichiers, via le plugin drillster/drone-rsync
  • Notify Success : Qui m’envoie un message sur discord si le déploiement est ok
  • Notify Failure : Idem, mais si c’est en erreur

Cette pipeline sera exécuté à chaque push, et permettra de déployer mon blog tout simplement.

Voici ce que ça donne :

deploy

Pipeline de promote

kind: pipeline
type: docker
name: Promote

steps:
- name: Merge to master
  image: alpine
  commands:
    - apk update
    - apk add git
    - git branch -a
    - git fetch origin main
    - git checkout main
    - git branch -a
    - git merge ${DRONE_BRANCH}
    - git branch -d ${DRONE_BRANCH}
    - git push origin main
    - git push -d origin ${DRONE_BRANCH}

- name: Delete environment
  image: appleboy/drone-ssh
  settings:
    host:
      from_secret: hosts
    username: root
    key:
      from_secret: Private_Key
    port: 22
    script:
      - rm -rf /opt/www/${DRONE_REPO_NAME}/${DRONE_BRANCH,,}
    
trigger:
  branch:
    exclude:
      - master
      - main
  event:
    - promote
  target:
    - production

Cette pipeline va simplement mergé ma branche en cours, vers la branche main, cela aura comme conséquence de lancer le déploiement sur la production. Le 2ème job permets de supprimer mon environnement de review.

Voici ce que ça donne :

promote

C’est fini

Et voilà, une fois tout ceci fait, mon article est en ligne. C’est de cette manière que celui-ci vous est partagé.
Alors bien sûr, ce n’est pas parfait, loin de là, mais ça fonctionne, et ça correspond parfaitement à mon besoin. Nous pourrions l’améliorer, avec par exemple un linter markdown,