Build Docker sur Amazon ECS : Slaves Jenkins éphémères avec Kaniko

janv. 14, 2021·
Benjamin Rabiller
Benjamin Rabiller
· 4 min. de lecture
Sécuriser le build Docker sur Jenkins & ECS

Contexte : Optimisation des coûts et des ressources

Dans mon cas d’usage, j’avais à disposition plusieurs clusters Amazon ECS allumés 24h/24 et 7j/7. Je trouvais donc inutile d’instancier des instances EC2 supplémentaires (même éphémères) pour installer des agents slaves Jenkins. Financièrement, cela n’avait aucun intérêt, tout comme l’utilisation d’AWS CodeBuild qui aurait engendré un coût additionnel par build.

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 en détail ici, car il existe déjà d’excellents articles sur le sujet :

L’idée de cet article 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 (docker.sock) dans mes conteneurs. On évite ainsi d’exposer la socket Docker dans nos slaves Jenkins et on s’affranchit du mode privileged.


Faire du Docker-in-Docker sans démon Docker

Comment construire une image Docker sans Docker ? La réponse tient en un mot : Kaniko. C’est un outil open-source qui permet de construire des images à partir d’un Dockerfile et de les pousser dans une registry, le tout à l’intérieur d’un conteneur ou d’un cluster Kubernetes, sans dépendre d’un démon Docker.

Le défi principal avec Jenkins (contrairement à GitLab CI) est que nous sommes obligés d’utiliser une image source contenant l’agent Jenkins (JNLP). On ne peut pas simplement utiliser l’image officielle de Kaniko. Il faut donc soit surcharger une image, soit construire la sienne. J’ai choisi de partir de l’image officielle : jenkins/inbound-agent.

Construction de l’image esclave hybride

Pour intégrer Kaniko, j’utilise le MULTI-STAGE build dans le 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

# Installation AWS CLI v2
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

# Installation du Docker CLI (pour l'auth ECR uniquement)
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

# Copie des binaires Kaniko
COPY --from=kaniko /kaniko /kaniko

## USER root est nécessaire pour que Kaniko puisse déballer les couches (FS)
# USER jenkins

Alors oui, j’installe docker-ce-cli, mais c’est uniquement pour m’authentifier auprès d’Amazon ECR afin que Kaniko puisse pousser l’image.

Note technique : J’ai configuré le rôle IAM de la Task Definition pour permettre l’interaction avec ECR, mais Kaniko ne semble pas assumer nativement ce rôle sans configuration complexe. L’usage d’AWS CLI pour générer le ~/.docker/config.json reste la méthode la plus simple et fiable.


Configuration de la sécurité (IAM)

Voici la policy IAM minimale à attacher au rôle de votre Task Definition ECS pour permettre le push vers ECR :

{
    "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 dans un Jenkinsfile

Voici un exemple de pipeline utilisant notre agent ECS. Le job est composé de deux étapes : le build/push et l’attente du scan de sécurité ECR.

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 '''
                # Authentification ECR
                aws ecr get-login-password --region eu-west-1 | docker login --username AWS --password-stdin ${ACCOUNT_ID}.dkr.ecr.eu-west-1.amazonaws.com
                
                # Exécution Kaniko
                /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}
                '''
            }
        }
    }
}

Conclusion

Nous avons vu qu’il est tout à fait possible de builder des images Docker de manière sécurisée via Kaniko au sein de slaves Jenkins éphémères sur Amazon ECS.

Certes, ce n’est pas aussi “out-of-the-box” que sur GitLab CI à cause de la gestion des agents JNLP de Jenkins, mais c’est une solution robuste qui combine isolation de sécurité et optimisation des coûts sur AWS.

Benjamin Rabiller
Auteurs
DevOps/Cloud Architect

Actuellement ingénieur DevOps/Architecte Cloud, j’étais initialement interessé par l’administration système et grâce aux entreprises dans lesquelles j’ai pu travailler Oxalide et maintenant Claranet j’ai eu la chance de découvrir l’univers du Cloud et de l’automatisation.

Je me suis décidé a publier ce blog pour vous faire partager ma passion mais également pour enrichir avec modestie tout ce que l’on peut trouver sur internet. Bonne lecture !