Salta al contenuto principale

DevOps GitLab CI/CD + Ansible Molecule Pipeline

Introduzione

La semplificazione dello sviluppo, del test e della distribuzione di infrastrutture e applicazioni è una pietra miliare delle moderne pratiche DevOps. GitLab CI/CD fornisce una solida piattaforma per automatizzare le pipeline e Ansible Molecule offre un potente framework per testare i ruoli Ansible prima della distribuzione. In questo articolo, dimostrerò come sposare questi strumenti per creare una pipeline GitLab DevOps coesa, compresi i test di integrazione e il provisioning.

È possibile trovare il codice sorgente di questo progetto nel mio repository GitLab qui:
Testing Ansible with Molecule in Docker

Componenti chiave

  • GitLab CI/CD: La piattaforma di orchestrazione centrale per i nostri processi di build, test e deploy automatizzati.
  • Ansible: Il motore di gestione della configurazione e di automazione IT, usato per definire la nostra infrastruttura come codice.
  • Ansible Roles: Blocchi di codice Ansible modulari e riutilizzabili per strutturare la configurazione dell'infrastruttura.
  • Molecule: Un framework specificamente progettato per testare la logica e l'esecuzione dei ruoli di Ansible.

La pipeline

La nostra pipeline GitLab DevOps consisterà in tre fasi principali:

  1. Container Build:
    • Un Dockerfile definisce un'immagine del container contenente Ansible, Molecule e le dipendenze necessarie.
    • GitLab CI/CD automatizza la creazione dell'immagine e la invia a un registro di contenitori (GitLab Container Registry).
  2. Test dei ruoli Ansible con Molecule:
    • Sono definiti scenari di test con Molecule, per garantire che i ruoli Ansible funzionino come previsto nelle varie architetture e nei vari sistemi operativi.
    • I test vengono eseguiti all'interno del contenitore costruito, garantendo isolamento e coerenza.
  3. Provisioning:
    • Un playbook Ansible utilizza i ruoli testati per configurare l'infrastruttura di destinazione.
    • GitLab CI/CD esegue il playbook negli ambienti designati (dev, staging, production).

Esempio di codice (.gitlab-ci.yml)

Costruire immagini Docker multi-arch rootless con Kaniko

Definito in .gitlab-ci.yml il CI le fasi:

  1. I Runner GitLab costruiscono i container, per 2 architetture, arm64 e amd64, in istanze separate.
  2. Creano i manifest di Docker per le due architetture e inviano i container al registro di GitLab

Comprendere la struttura in profondità

  • Fasi: La pipeline è divisa in due fasi
    • build: Qui viene costruita l'immagine Docker per le diverse architetture.
    • multiarch-manifest: Questo crea un manifest multi-architettura per supportare senza problemi diverse architetture.
  • Jobs: Abbiamo due job di compilazione (kaniko-amd64, kaniko-arm64) e un manifest job (multiarch-manifest).
1. kaniko-amd64 e kaniko-arm64 Jobs
  • Scopo: Costruire immagini Docker per architetture AMD64 (kaniko-amd64) e ARM64 (kaniko-arm64).
  • Immagine: Utilizzare l'immagine dell'esecutore Kaniko (gcr.io/kaniko-project/executor:debug) per costruire immagini Docker senza Docker-in-Docker.
  • Variabili
    • KANIKO_ARGS: Flag aggiuntivi per l'esecutore Kaniko.
    • KANIKO_BUILD_CONTEXT: La directory in cui si trova il file Docker.
  • Runner Tags: associa il carico di lavoro a specifici runner
    • kaniko-arm64 è specificamente etichettato per essere eseguito su un ARM64, in questo caso un runner privato.
    • kaniko-amd64 è etichettato per essere eseguito su un runner AMD64.
kaniko-amd64:

  variabili:
    # Opzioni aggiuntive per l'esecutore Kaniko.
    # Per maggiori dettagli vedere https://github.com/GoogleContainerTools/kaniko/blob/master/README.md#additional-flags
    KANIKO_ARGS: ""
    KANIKO_BUILD_CONTEXT: $CI_PROJECT_DIR

  fase: costruire
  immagine:
    # Per gli ultimi rilasci vedere https://github.com/GoogleContainerTools/kaniko/releases
    # Solo le versioni debug/*-debug dell'immagine di Kaniko funzionano con Gitlab CI.
    nome: gcr.io/project/kaniko-project/executor:debug
    entrypoint: [""]

  script:
    # se l'utente fornisce IMAGE_TAG allora lo usa, altrimenti costruisce il tag immagine usando la logica predefinita.
    # Logica predefinita
    # Comporre il nome del tag Docker
    # Mappatura del ramo/tag Git al tag immagine Docker
    # * Ramo predefinito: main -> latest
    # * Ramo: feature/my-feature -> branch-feature-my-feature
    # * Tag: v1.0.0/beta2 -> v1.0.0-beta2
    
      se [ -z ${IMAGE_TAG+x} ]; allora
        se [ "$CI_COMMIT_REF_NAME" = $CI_DEFAULT_BRANCH ]; allora
            VERSIONE="ultima"
          elif [ -n "$CI_COMMIT_TAG" ];then
            NOSLASH=$(echo "$CI_COMMIT_TAG" | tr -s / - )
            SANITIZED="${NOSLASH//[^a-zA-Z0-9.-]/}"
            VERSION="$SANITIZED"
          altrimenti \
            NOSLASH=$(echo "$CI_COMMIT_REF_NAME" | tr -s / - )
            SANITIZED="${NOSLASH//[^a-zA-Z0-9-]/}"
            VERSION="branch-$SANITIZED"
          fi
        export IMAGE_TAG=$CI_REGISTRY_IMAGE:$VERSION-amd64
      fi
    - echo $IMAGE_TAG
    - mkdir -p /kaniko/.docker
    # Scrivere le credenziali per accedere al Gitlab Container Registry all'interno del runner/ci
    - echo "{\"auths\":{\"$CI_REGISTRY\":{\"auth\":\"$(echo -n ${CI_REGISTRY_USER}:${CI_REGISTRY_PASSWORD}} | base64 | tr -d '\n')\}}" > /kaniko/.docker/config.json
    # Costruire e spingere il contenitore. Per disabilitare il push, aggiungere --no-push
    - DOCKERFILE_PATH=${DOCKERFILE_PATH:-"$KANIKO_BUILD_CONTEXT/Dockerfile"}
    - /kaniko/executor --contesto $KANIKO_BUILD_CONTEXT --dockerfile $DOCKERFILE_PATH -destinazione $IMAGE_TAG $KANIKO_ARGS

  # Esegue questo lavoro in un ramo/tag in cui esiste un Dockerfile
  regole:
    - esiste:
        - Dockerfile
    # solo tag
    - se: "$CI_COMMIT_TAG"
    # percorso del file Docker personalizzato
    - if: $DOCKERFILE_PATH
    # contesto di compilazione personalizzato senza un percorso esplicito del file Docker
    - se: $KANIKO_BUILD_CONTEXT != $CI_PROJECT_DIR
2. multiarch-manifest Job
  • Scopo: Crea un manifest multi-architettura, consentendo a Docker di estrarre l'immagine appropriata in base all'architettura del sistema dell'utente.
  • Immagine: Utilizza un'immagine standard docker:latest, poiché questo task si basa sulla Docker CLI.
  • Servizi:
    • docker:dind: Sfrutta Docker-in-Docker per fornire contenitori Docker all'interno del runner GitLab.
stage: multiarch-manifest

  immagine:
    # Per gli ultimi rilasci vedere https://github.com/GoogleContainerTools/kaniko/releases
    # Solo le versioni debug/*-debug dell'immagine Kaniko sono note per funzionare all'interno di Gitlab CI.
    nome: docker:latest
    entrypoint: [""]

  servizi:
    - docker:dind

  before_script:
    # Connettersi a GitLab Docker in Docker DIND
    - mkdir -pv ~/.docker
    - cp -v $DOCKER_TLS_CERTDIR/client/* ~/.docker # Copiare i certificati docker predefiniti
    - docker info # Mostra la connessione al demone docker
    # Effettuare il login... se necessario
    - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY

  script:
    # Lo script Docker usa un altro formato di tag `:7-8-5`, usa lo stile Kaniko per la coerenza
    - |
      if [ -z ${IMAGE_TAG+x} ]; then
        if [ "$CI_COMMIT_REF_NAME" = $CI_DEFAULT_BRANCH ]; then
            VERSIONE="ultima"
          elif [ -n "$CI_COMMIT_TAG" ];then
            NOSLASH=$(echo "$CI_COMMIT_TAG" | tr -s / - )
            SANITIZED="${NOSLASH//[^a-zA-Z0-9.-]/}"
            VERSION="$SANITIZED"
          altrimenti \
            NOSLASH=$(echo "$CI_COMMIT_REF_NAME" | tr -s / - )
            SANITIZED="${NOSLASH//[^a-zA-Z0-9-]/}"
            VERSION="branch-$SANITIZED"
          fi
        esportare IMAGE_TAG=$CI_REGISTRY_IMAGE:$VERSION
      fi
    # Esempio di tag Kaniko: registry.gitlab.com/aleliga/docker-ansible-runner:7.8.5
    # Estrarre le immagini
    - docker pull $IMAGE_TAG-arm64
    - docker pull $IMAGE_TAG-amd64
    # Riempire le variabili per il "manifesto di docker".
    - export MANIFEST_LIST=$IMAGE_TAG && export MANIFEST_BASE=$MANIFEST_LIST
    - echo "Debug vars $MANIFEST_LIST $MANIFEST_BASE"
    # Creare il manifesto di docker
    - echo "docker manifest create $MANIFEST_LIST $MANIFEST_BASE-arm64 $MANIFEST_BASE-amd64"
    - docker manifest create $MANIFEST_LIST $MANIFEST_BASE-arm64 $MANIFEST_BASE-amd64
    # Annotate l'arco
    - echo "docker manifest annotate --os linux --arch arm64 $MANIFEST_LIST $MANIFEST_BASE-arm64"
    - docker manifest annotate --os linux --arch arm64 $MANIFEST_LIST $MANIFEST_BASE-arm64
    - echo "docker manifest annotate --os linux --arch amd64 $MANIFEST_LIST $MANIFEST_BASE-amd64"
    - docker manifest annotate --os linux --arch amd64 $MANIFEST_LIST $MANIFEST_BASE-amd64
    # Spingere il manifesto
    - echo "docker manifest push $MANIFEST_LIST"
    - docker manifest push $MANIFEST_LIST

  regole:
    - esiste:
        - Dockerfile
    # solo i tag
    - se: "$CI_COMMIT_TAG"
    # percorso del file Docker personalizzato
    - if: $DOCKERFILE_PATH
    # contesto di compilazione personalizzato senza un percorso esplicito del file Docker
    - se: $KANIKO_BUILD_CONTEXT != $CI_PROJECT_DIR

Eseguire una shell all'interno del container

Utile per i test, possiamo eseguire una shell interattiva all'interno del container, con montato il nostro codice Ansible come volume.

docker run -it --rm \
  -v $(pwd)/ansible:/ansible \
  registry.gitlab.com/aleliga/docker-ansible-molecule \
  bash

Aggiungi collezioni al contenitore

Il contenitore può essere usato come immagine base per aggiungere collezioni e ruoli

FROM registry.gitlab.com/aleliga/docker-ansible-molecule:latest
ESEGUIRE python3 -m venv venv
ENV PATH="/venv/bin:$PATH"

# Aggiungere una collezione
RUN ansible-galaxy collection install community.general

Eseguire Ansible Molecule in GitLab CI/CD

Una pipeline CI secondaria, è dedicata ai test di integrazione dei Playbook e dei Ruoli di Ansible con molecola, utilizzando il contenitore precedentemente costruito

Script di inizializzazione di molecola

Questo script viene eseguito all'interno del contenitore docker-ansible-molecule durante il CI. Impostare l'ambiente (link simbolico, attivazione dell'ambiente virtuale), quindi eseguire il sottocomando Molecule fornito nella directory Ansible del progetto.

#!/usr/bin/env bash
MOLECULE_COMMAND="$1"

# Creare un collegamento per ricreare il percorso come nel contenitore
ln -s $CI_PROJECT_DIR/ansible /ansible

# Entrare nella directory di ansible
PWD="/ansible" ; cd /ansible

# Aggiungere singole variabili alla molecola
MOLECULE="true" \
    molecule $1

Configurazione di GitLab CI

Il file .gitlab-ci.yaml definisce l'immagine, esegue una pulizia forzata e molecule test

stages:
  - molecola-test

variabili:
  CONTAINER_RELEASE_IMAGE: "$CI_REGISTRY_IMAGE:$CI_COMMIT_TAG"
  SHELL: /bin/bash
  CI_REGISTRY: registry.gitlab.com

before_script:
  - echo "Prima dello script"
  - mkdir -p ~/.docker/
  - echo "{\"auths\":{\"$CI_REGISTRY\":{\"auth\":\"$(echo -n ${CI_REGISTRY_USER}:${CI_REGISTRY_PASSWORD} | base64)\"}}" >> ~/.docker/config.json

molecola:
  immagine: registry.gitlab.com/aleliga/docker-ansible-molecule:latest
  fase: molecola-test
  servizi:
    - docker:dind
  tag:
    - private-runner
    - molecola
  script:
    - echo "Eseguire la distruzione della molecola"
    - "$CI_PROJECT_DIR/bin/molecule-ci-direct.sh destroy"
    - echo "Rimuovere i file temporanei"
    - rm -fr ~/.cache/molecule/
    - echo "Eseguire il test della molecola"
    - "$CI_PROJECT_DIR/bin/molecule-ci-direct.sh test"

Deploy con Ansible in CD

L'ultima pipeline, distribuisce i playbook Ansible da GitLab Runner, utilizzando DinD (Docker in Docker)

variabili:
  # Distribuzione del repository
  PRIVATE_REPO: "gitlab.com/USER/PRIVATE_REPO.git"
  # Parametri Docker DIND
  DOCKER_HOST: "tcp://docker:2376"
  DOCKER_TLS_CERTDIR: "/certs"
  DOCKER_TLS_VERIFY: 1

ansible-deploy:
  immagine: registry.gitlab.com/aleliga/docker-ansible-molecule:latest
  stage: deploy
  servizi:
    - docker:dind
  before_script:
    - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
    - eval $(ssh-agent -s)
    - echo -e $CI_SSH_PRIVATE_KEY
    - echo -e "$CI_SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - > /dev/null
    - mkdir -p ~/.ssh
    - chmod 700 ~/.ssh
    - cd ~/
    - mkdir -p $PERSONAL_REPO_DIR
    - apt-get update && apt-get install git -qqy
  script:
    - git clone https://"$ANSIBLE_USER":"$ANSIBLE_DEPLOY_TOKEN"@"$PRIVATE_REPO" $PERSONAL_REPO_DIR
  # Eseguire Ansible "upgrade-hosts" per eseguire il deploy
    - cd $PERSONAL_REPO_DIR
    - echo "Distribuire ai server"
    - bin/gitlabci-upgrade-hosts.sh

Benefici

  • Maggiore affidabilità: I test automatizzati con Molecule aumentano la fiducia nei ruoli di Ansible, impedendo che gli errori scivolino negli ambienti di produzione.
  • Iterazione più veloce: Una pipeline CI/CD ben definita consente uno sviluppo più rapido e modifiche con un maggior grado di sicurezza.
  • Infrastruttura come codice: Ansible garantisce distribuzioni prevedibili e ripetibili in tutti gli ambienti.

Considerazioni

  • Gestione sicura delle variabili: Usare le variabili segrete di GitLab CI/CD e le soluzioni Vault per memorizzare dati sensibili come le credenziali.
  • Gestione degli ambienti: Considerare la possibilità di separare le configurazioni della pipeline per gli ambienti di sviluppo, staging e produzione.

Riferimenti

Aggiornamento settembre 2024

Dal rilascio di GitLab 17, l'infrastruttura DevOps cloud, fornisce Runner SaaS ARM64 condivisi, utilizzabili definendo i tag corretti in kaniko-arm64 e altri lavori, ma a partire da settembre 2024 sono solo per i clienti Premium e Ultimate.

Per utilizzare i runner ospitati da GitLab ARM64, possiamo impostare i tag appropriati nel CI stage, ad esempio:

 tags:
   - saas-linux-medium-arm64

Fonte: GitLab Hosted runners on Linux

Riferimenti

Un grande ringraziamento a Jeff Geerling, per tutto il suo lavoro sulla comunità Ansible

Conclusione

L'integrazione di GitLab CI e Ansible Molecule favorisce un solido flusso di lavoro DevOps. Assicura modifiche affidabili all'infrastruttura, accelera i tempi di consegna e riduce il rischio di errori di distribuzione.

Sentitevi liberi di esplorare il repository, di contribuire o di usarlo come riferimento. Fatemi sapere se avete commenti o suggerimenti!

Se avete bisogno di un esperto di integrazione per le vostre pipeline Ansible, provate la nostra consulenza DevOps gratuita