Builder des images Docker avec des slaves Jenkins éphémères sur Amazon ECS

Image credit: medium.com

Contexte

Dans mon use case, j’avais a disposition plusieurs clusters ECS allumés 24/24 et 7/7 et je trouvais inutile de devoir instancier des EC2 supplémentaires (même éphémeres) pour installer des agents slave Jenkins. Cela n’avait financierement aucun interêt. Idem concernant l’utilisation d’AWS Codebuild qui par définition aurait tout de même eu un coût.

Je me suis donc tourné vers le plugin Jenkins Amazon ECS. Ce plugin permet d’instancier des conteneurs Docker en tant qu’agent Jenkins sur un cluster ECS de type EC2 ou bien Fargate.

Je ne vais pas décrire cette architecture dans cet article étant donné qu’il existe déjà un grand nombre d’articles de qualités sur ce sujet (ci-dessous quelques exemples sélectionnés) :

L’idée ici est plutôt de décrire comment j’exploite ces slaves pour builder mes conteneurs Docker sans Docker et sans montage de la socket Docker dans mes conteneurs. On evite donc d’exposer la socket Docker dans nos slaves jenkins et on évite de devoir exécuter nos slaves en mode privileged.

Faire du docker in docker sans docker

Comment on peut construire une image Docker sans docker ? Et bien tout simplement en utilisant Kaniko. Kaniko est un outil opensource qui permet de constuire des images Docker et de les push dans des registry sans Docker.

Le petit problème c’est qu’à l’image de Gitlab CI, on est obligé d’avoir une image Docker source qui contient l’agent Jenkins. Donc impossible de prendre n’importe quelle image Docker. Il faut forcement soit la surcharger et installer l’agent jenkins, soit prendre l’image proposée par Jenkins (ce qui j’ai choisi de faire) : jenkins/inbound-agent

Du coup, il va falloir surcharger cette image proposée pour intégrer Kaniko. Et ça c’est pénible… Avec Gitlab CI, on aurait pu directement prendre l’image Docker Kaniko.

Pour se faire, on va utiliser le MULTI FROM au sein de notre Dockerfile :

FROM gcr.io/kaniko-project/executor:v1.3.0 as kaniko

FROM jenkins/inbound-agent

ARG DOCKER_VERSION=5:20.10.2~3-0~debian-buster
ARG AWS_CLI_VERSION=2.1.17

USER root

RUN curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64-${AWS_CLI_VERSION}.zip" -o "awscliv2.zip" && \
    unzip awscliv2.zip && \
    ./aws/install && \
    rm awscliv2.zip

RUN apt-get update && apt-get install --no-install-recommends -y \
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg-agent \
    software-properties-common && \
    curl -fsSL https://download.docker.com/linux/debian/gpg | apt-key add - && \
    add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/debian $(lsb_release -cs) stable" && \
    apt-get update && \
    apt-get --no-install-recommends -y install docker-ce-cli=${DOCKER_VERSION} && \
    apt-get clean

COPY --from=kaniko /kaniko /kaniko

## J'explique dans l'article pourquoi je commente cette ligne
#USER jenkins

On va me dire que je suis un menteur parce que j’installe docker-ce-cli dans mon image. Alors oui c’est vrai, mais c’est juste pour m’authentifier auprès de ECR pour que Kaniko puisse push l’image.

Je configure pourtant le plugin Jenkins pour que la task definition qui soit créée soit associée à un rôle IAM qui permette d’intéragir avec la registry ECR. Mais Kaniko ne semble pas assumer ce rôle. J’ai donc du exploiter AWS CLI et Docker CLI pour générer le secret dans ~/.docker/config.json.

Si vous avez la solution. N’hésitez pas à me le dire dans un commentaire.

Je voulais remettre l’usage du user Jenkins pour éviter de lancer le slave jenkins avec le user root mais il m’était impossible de construire mon image Docker. Je ne suis pas dans le cas décrit dans le Readme du projet Kaniko :

If you have a minimal base image (SCRATCH or similar) that doesn't require permissions to unpack, and your Dockerfile doesn't execute any commands as the root user, you can run kaniko without root permissions. It should be noted that Docker runs as root by default, so you still require (in a sense) privileges to use kaniko.

Ci-dessous la policy IAM liée au rôle de la task-definiton :

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ecr:GetAuthorizationToken",
                "ecr:GetDownloadUrlForLayer",
                "ecr:BatchGetImage",
                "ecr:BatchCheckLayerAvailability",
                "ecr:PutImage",
                "ecr:InitiateLayerUpload",
                "ecr:UploadLayerPart",
                "ecr:CompleteLayerUpload"
            ],
            "Resource": "*"
        }
    ]
}

Utilisation au sein d’un Jenkinsfile

pipeline {
   agent {
    label 'cluster-ecs'
   }
   environment {
      ACCOUNT_ID = 'xxxxxxxxxx'
      ECR_URL = 'xxxxxxxxx.dkr.ecr.eu-west-1.amazonaws.com/apache-php'
   }
   stages {
      stage('Docker Build/Push image') {
        steps {
          sh '''
          aws ecr get-login-password --region eu-west-1 | docker login --username AWS --password-stdin xxxxxxx.dkr.ecr.eu-west-1.amazonaws.com
          /kaniko/executor --dockerfile ./Dockerfile --destination ${ECR_URL}:${BRANCH_NAME} --context ./ --force && echo "Successfully pushed image ${ECR_URL}:${BRANCH_NAME}"
          '''
        }
      }
      stage('Security Scan') {
        steps {
          sh '''
          aws ecr wait image-scan-complete --repository-name apache-php --image-id imageTag=${BRANCH_NAME}
          '''
        }
      }
   }
}

Ce job est composé de deux stages :

  • Le premier permet de build et push notre image dans notre registry ECR
  • Le second attend un retour de l’API AWS pour que le scan de sécurité se termine. On pourra alors par lui suite exploiter plus finement le résultat.

Aussi, on précise le label de notre agent qui correspond à celui qui aura été configuré dans la plugin jenkins Amazon ECS pour pouvoir lancer ces agents sur le cluster souhaité.

Bien entendu, il faut imaginer que ce job soit configuré pour prendre en source un SCM comme Github, Gitlab ou encore Bitbucket… On trouve donc à la racine de notre agent Jenkins slave les sources du dépôt contenant notre Dockerfile a builder.

Conclusion

On a pu voir dans cet article qu’il était possible de builder une image Docker sans Docker de maniere sécurisé via l’outil Kaniko au sein de slaves Jenkins éphéméres sur des clusters Amazon Elastic Container Service (ECS).

Finalement, ce n’etait pas si simple que ca en avait l’air. Jenkins et sa gestion des agents via JNLP rend l’usage beaucoup moins simple que sur Gitlab CI à mon sens.

Benjamin Rabiller
Benjamin Rabiller
Cloud architect/DevOps engineer

Passionné et investi dans l’informatique

comments powered by Disqus

Related