Salta al contenuto principale

DevOps GitLab CI/CD + Ansible Molecule Pipeline

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: 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:

  variables:
    # Additional options for Kaniko executor.
    # For more details see https://github.com/GoogleContainerTools/kaniko/blob/master/README.md#additional-flags
    KANIKO_ARGS: ""
    KANIKO_BUILD_CONTEXT: $CI_PROJECT_DIR

  stage: build
  image:
    # For latest releases see https://github.com/GoogleContainerTools/kaniko/releases
    # Only debug/*-debug versions of the Kaniko image are known to work within Gitlab CI
    name: gcr.io/kaniko-project/executor:debug
    entrypoint: [""]

  script:
    # if the user provide IMAGE_TAG then use it, else build the image tag using the default logic.
    # Default logic
    # Compose docker tag name
    # Git Branch/Tag to Docker Image Tag Mapping
    #   * Default Branch: main -> latest
    #   * Branch: feature/my-feature -> branch-feature-my-feature
    #   * Tag: v1.0.0/beta2 -> v1.0.0-beta2
    - |
      if [ -z ${IMAGE_TAG+x} ]; then
        if [ "$CI_COMMIT_REF_NAME" = $CI_DEFAULT_BRANCH ]; then
            VERSION="latest"
          elif [ -n "$CI_COMMIT_TAG" ];then
            NOSLASH=$(echo "$CI_COMMIT_TAG" | tr -s / - )
            SANITIZED="${NOSLASH//[^a-zA-Z0-9.-]/}"
            VERSION="$SANITIZED"
          else \
            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
    # Write credentials to access Gitlab Container Registry within the runner/ci
    - echo "{\"auths\":{\"$CI_REGISTRY\":{\"auth\":\"$(echo -n ${CI_REGISTRY_USER}:${CI_REGISTRY_PASSWORD} | base64 | tr -d '\n')\"}}}" > /kaniko/.docker/config.json
    # Build and push the container. To disable push add --no-push
    - DOCKERFILE_PATH=${DOCKERFILE_PATH:-"$KANIKO_BUILD_CONTEXT/Dockerfile"}
    - /kaniko/executor --context $KANIKO_BUILD_CONTEXT --dockerfile $DOCKERFILE_PATH --destination $IMAGE_TAG $KANIKO_ARGS

  # Run this job in a branch/tag where a Dockerfile exists
  rules:
    - exists:
        - Dockerfile
    # only tags
    - if: "$CI_COMMIT_TAG"
    # custom Dockerfile path
    - if: $DOCKERFILE_PATH
    # custom build context without an explicit Dockerfile path
    - if: $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

  image:
    # For latest releases see https://github.com/GoogleContainerTools/kaniko/releases
    # Only debug/*-debug versions of the Kaniko image are known to work within Gitlab CI
    name: docker:latest
    entrypoint: [""]

  services:
    - docker:dind

  before_script:
    # Connect to GitLab Docker in Docker DIND
    - mkdir -pv ~/.docker
    - cp -v $DOCKER_TLS_CERTDIR/client/* ~/.docker   # Copy default docker certs
    - docker info   # Show connection to docker daemon
    # Login... if necessary
    - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY

  script:
    # The Docker script use another tag format `:7-8-5`, use Kaniko style for coerence
    - |
      if [ -z ${IMAGE_TAG+x} ]; then
        if [ "$CI_COMMIT_REF_NAME" = $CI_DEFAULT_BRANCH ]; then
            VERSION="latest"
          elif [ -n "$CI_COMMIT_TAG" ];then
            NOSLASH=$(echo "$CI_COMMIT_TAG" | tr -s / - )
            SANITIZED="${NOSLASH//[^a-zA-Z0-9.-]/}"
            VERSION="$SANITIZED"
          else \
            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
      fi
    # Example Kaniko tag: registry.gitlab.com/aleliga/docker-ansible-runner:7.8.5
    # Pull images
    - docker pull $IMAGE_TAG-arm64
    - docker pull $IMAGE_TAG-amd64
    # Fill variables for `docker manifest`
    - export MANIFEST_LIST=$IMAGE_TAG && export MANIFEST_BASE=$MANIFEST_LIST
    - echo "Debug vars $MANIFEST_LIST $MANIFEST_BASE"
    # Create docker manifest
    - echo "docker manifest create $MANIFEST_LIST $MANIFEST_BASE-arm64 $MANIFEST_BASE-amd64"
    - docker manifest create $MANIFEST_LIST $MANIFEST_BASE-arm64 $MANIFEST_BASE-amd64
    # Annotate arch
    - 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
    # Push manifest
    - echo "docker manifest push $MANIFEST_LIST"
    - docker manifest push $MANIFEST_LIST

  rules:
    - exists:
        - Dockerfile
    # only tags
    - if: "$CI_COMMIT_TAG"
    # custom Dockerfile path
    - if: $DOCKERFILE_PATH
    # custom build context without an explicit Dockerfile path
    - if: $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
RUN python3 -m venv venv
ENV PATH="/venv/bin:$PATH"

# Add a collection
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"

# Create link to recreate path as in the container
ln -s $CI_PROJECT_DIR/ansible /ansible

# Enter ansible directory
PWD="/ansible" ; cd /ansible

# Add single variables to molecule
MOLECULE="true" \
    molecule $1

Configurazione di GitLab CI

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

stages:
  - molecule-test

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

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

molecule:
  image: registry.gitlab.com/aleliga/docker-ansible-molecule:latest
  stage: molecule-test
  services:
    - docker:dind
  tags:
    - private-runner
    - molecule
  script:
    - echo "Run molecule destroy"
    - "$CI_PROJECT_DIR/bin/molecule-ci-direct.sh destroy"
    - echo "Remove temp files"
    - rm -fr ~/.cache/molecule/
    - echo "Run molecule test"
    - "$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)

variables:
  # Deploy repository
  PRIVATE_REPO: "gitlab.com/USER/PRIVATE_REPO.git"
  # Docker DIND vars
  DOCKER_HOST: "tcp://docker:2376"
  DOCKER_TLS_CERTDIR: "/certs"
  DOCKER_TLS_VERIFY: 1

ansible-deploy:
  image: registry.gitlab.com/aleliga/docker-ansible-molecule:latest
  stage: deploy
  services:
    - 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
  # Run Ansible "upgrade-hosts" to deploy
    - cd $PERSONAL_REPO_DIR
    - echo "Deploy to servers"
    - 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