Github Actions Runner self-hosted et AWS ECR
Utilisation d’un runner self-hosted sur Github Actions avec AWS ECR
Publié le
Do not index
Do not index
Primary Keyword
Lié à Analyse sémantique (Articles liés) 1
Lié à Analyse sémantique (Articles liés)
Statut rédaction
A optimiser SEO
Lié à Analyse sémantique (Articles liés) 2
Attention : cet article ne représente pas un guide d’installation, il ne se substitue pas aux instructions officielles. Les commandes données ne suffisent pas à une installation complète, et se contentent juste de donner une idée générale de la procédure à suivre.
IntroductionImage Docker à utiliserRessources liées au RunnerAccès à l’imageActions Runner ControllerLe Runner en lui-mêmeUtilisation dans la pipelineAméliorationsDurée de validité des jetons d’authentificationTemps à initialiser un nouveau conteneurComplexité d’utilisationConclusion
Introduction
Il y a peu, nous avons eu un problème intéressant avec l’infrastructure d’un client. Il voulait faire tourner ses jobs Github Actions dans des runners self-hosted sur Kubernetes utilisant une image stockée sur AWS ECR.
Rien d’incroyable me direz-vous ? Eh bien ça n’a finalement pas été si simple, et la solution trouvée n’est toujours pas parfaite.
Mettons des définitions sur les différents mots-clefs que nous venons de citer :
- Github Actions : service de CI/CD mis à disposition par Github
- Runner… : machine qui va exécuter les tâches demandées par la CI/CD
- …self-hosted : au lieu d’utiliser ceux fournis par Github, on déploie des nouveaux runners sur notre cluster Kubernetes
- Kubernetes : un orchestrateur qui simplifie le déploiement de conteneurs Docker
- AWS ECR : service de registres de conteneurs Docker fourni par AWS
Pour faire fonctionner tout cet ensemble, il faut donc mettre en place un nombre conséquent de composants. Voilà la manière dont nous avons traité ça.
Image Docker à utiliser
Dans un premier temps, nous avons créé le registre ECR, et avons construit une image, puis l’avons téléversée dans ce nouveau registre. L’image part d’un Ubuntu, et ajoute des outils nécessaires à tester l’application du client.
On peut grossièrement résumer le Dockerfile aux commandes suivantes :
$ cat Dockerfile
FROM ubuntu:22.04
# Required packages
RUN apt update && apt install -y curl git unzip make <etc>
# AWS CLI
RUN curl -fsSL -o /tmp/awscliv2.zip "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" && \
unzip /tmp/awscliv2.zip -d /tmp/awscli && \
/tmp/awscli/aws/install
# Custom scripts
COPY ./scripts /scripts
$ aws ecr create-repository --repository-name github-runner
$ aws ecr get-login-password | docker login --username AWS --password-stdin <account>.dkr.ecr.eu-west-3.amazonaws.com/github-runner
$ docker build -t <account>.dkr.ecr.eu-west-3.amazonaws.com/github-runner:v1 .
$ docker push <account>.dkr.ecr.eu-west-3.amazonaws.com/github-runner:v1
Maintenant que nous avons une image Docker, et qu’elle est mise à disposition sur AWS ECR, on va pouvoir commencer à créer les ressources nécessaires au Runner.
Ressources liées au Runner
Accès à l’image
Pour l’instant, le runner ne peut pas accéder au registre ECR, il faut pour ça créer un rôle IAM, et un rôle EKS, et les lier. On peut donc, dans un premier temps, définir un rôle IAM
github-runner
de la sorte :# IAM role `github-runner`: trust relationships
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::<account>:oidc-provider/oidc.eks.eu-west-3.amazonaws.com/id/<cluster>"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringLike": {
"oidc.eks.eu-west-3.amazonaws.com/id/<cluster>:sub": "system:serviceaccount:github-runner:github-runner*"
}
}
}
]
}
# IAM role `github-runner`: permissions
{
"Statement": [
{
"Action": [
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:GetRepositoryPolicy",
"ecr:DescribeRepositories",
"ecr:ListImages",
"ecr:DescribeImages",
"ecr:BatchGetImage",
"ecr:GetLifecyclePolicy",
"ecr:GetLifecyclePolicyPreview",
"ecr:ListTagsForResource",
"ecr:DescribeImageScanFindings"
],
"Effect": "Allow",
"Resource": "arn:aws:ecr:eu-west-3:<account>:repository/github-runner"
}
],
"Version": "2012-10-17"
}
Grâce au rôle créé, on autorise désormais le compte de service Kubernetes
github-runer:github-runner
à endosser ce rôle, qui lui, permet de lister et récupérer des images sur ECR.Actions Runner Controller
ARC est un logiciel open-source développé en collaboration avec Github qui permet de contrôler et gérer des Runners pour Actions. Il permet de gérer l’authentification auprès de Github (pour recevoir les nouveaux jobs), et de scale les Runners en cas de sur-utilisation.
Il faut dans un premier temps créer un secret qui va contenir l’authentification auprès de Github, les instructions sont disponibles sur la documentation officielle.
# Secret creation
$ kubectl create namespace github-runner
$ kubectl create secret generic controller-manager \
-n github-runner \
--from-literal=github_token=${GITHUB_TOKEN}
# ARC deployment
$ kubectl create -n github-runner -f \
https://github.com/actions/actions-runner-controller/releases/download/v0.27.4/actions-runner-controller.yaml
Nous avons donc à notre disposition le contrôleur, il faut ensuite créer ce qui nous intéresse le plus : le Runner.
Le Runner en lui-même
C’est ici que se passe la partie qui nous a causée beaucoup de problèmes.
Commençons par lancer un Runner, et le compte de service associé :
# runner.yaml
apiVersion: actions.summerwind.dev/v1alpha1
kind: RunnerDeployment
metadata:
name: github-runner
namespace: github-runner
spec:
template:
spec:
organization: my-org
serviceAccountName: github-runner
---
# service-account.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: github-runner
namespace: github-runner
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::<account>:role/github-runner
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: github-runner
namespace: github-runner
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: github-runner
namespace: github-runner
subjects:
- kind: ServiceAccount
name: github-runner
namespace: github-runner
apiGroup: ""
roleRef:
kind: Role
name: github-runner
apiGroup: rbac.authorization.k8s.io
Le Runner est désormais disponible à l’utilisation par Github Actions. Le compte de service existe, et notre Runner a les permissions nécessaires pour accéder à ECR sauf que… son démon Docker n’est pas authentifié.
Dans le cadre de la CI/CD de notre client, les jobs sont à lancer dans une image particulière, et donc la première étape du Runner est de récupérer cette image puis d’y lancer les différents jobs.
Pour récupérer cette image, le démon doit s’authentifier auprès d’ECR. Il existe pour cela plusieurs moyens :
amazon-credentials-ecr-helper
: ce programme est censé remplacer et abstraire l’authentification auprès d’ECR. Nous l’avons déjà utilisé dans d’autres projets, mais il nous a été impossible de l’intégrer au Runner, sans trop comprendre pourquoi.
aws ecr get-login-password
: la méthode “classique” utilisant le CLI d’AWS pour générer un token temporaire
C’est donc cette seconde option que nous avons choisie, mais elle cause encore des problèmes : comment effectuer la connexion à ECR, sans surcharger l’entrypoint de l’image Docker du runner ?
Nous avons choisi de contourner le problème, et d’ajouter un initContainer au Runner, et d’y effectuer la connexion. On peut donc modifier le déploiement du Runner avec nos nouvelles ressources :
# runner.yaml
apiVersion: actions.summerwind.dev/v1alpha1
kind: RunnerDeployment
metadata:
name: github-runner
namespace: github-runner
spec:
template:
spec:
organization: my-org
serviceAccountName: github-runner
volumes:
- name: dockerconfig
emptyDir: {}
volumeMounts:
- mountPath: /home/runner/.docker/config.json
name: dockerconfig
subPath: config.json
securityContext:
fsGroup: 1000
initContainers:
- name: login-ecr
image: ubuntu:latest
command:
- sh
- -c
- apt update && apt install -y --no-install-recommends curl unzip ca-certificates gnupg
&& curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
&& unzip awscliv2.zip > /dev/null
&& ./aws/install
&& install -m 0755 -d /etc/apt/keyrings
&& curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
&& chmod a+r /etc/apt/keyrings/docker.gpg
&& echo "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
&& apt update && apt install -y --no-install-recommends docker-ce-cli
&& aws ecr get-login-password | docker login --username AWS --password-stdin <account>.dkr.ecr.eu-west-3.amazonaws.com/github-runner
&& cp ~/.docker/config.json /dockerconfig/config.json
&& chown 1000:1000 /dockerconfig/config.json
&& chmod 644 /dockerconfig/config.json
volumeMounts:
- mountPath: /dockerconfig
name: dockerconfig
Le
initContainer
que nous avons ajouté se charge des tâches suivantes :- Installer le CLI de AWS
- Installer le client Docker
- Se connecter à ECR
- Déplacer la configuration créée dans le nouveau volume partagé entre les conteneurs du Runner
Il faut également changer le contexte de sécurité, afin que l’utilisateur dans le conteneur principal du runner puisse accéder aux fichiers montés.
Utilisation dans la pipeline
Maintenant que nous avons un Runner self-hosted, et une image à utiliser pour lancer les jobs la nécessitant, on peut mettre à jour les pipelines de CI/CD pour les utiliser, ce qui donne ce genre de jobs :
name: My Actions workflow
on: push
jobs:
my-job:
name: My first job
runs-on: [self-hosted]
container: <account>.dkr.ecr.eu-west-3.amazonaws.com/github-runner:v1
steps:
- run: /scripts/say 'Hey from the container :)'
Améliorations
Durée de validité des jetons d’authentification
Cette manière de faire n’est pas optimisée, en effet, les jetons générés par
aws ecr get-login-password
ne durent que 12h. Si aucun job n’est lancé pendant 12h, alors le jeton est expiré et le prochain job, s’il nécessite l’utilisation de l’image dans le ECR privé, ne fonctionnera pas et il faudra le relancer.Temps à initialiser un nouveau conteneur
Le
initContainer
met quelques dizaines de secondes à terminer, plusieurs paquets sont téléchargés, et ce à chaque fois qu’un runner s’initialise.On pourrait imaginer utiliser une image de base plus fournie (pour l’instant c’est
ubuntu:latest
), mais ça revient au même si cette image est stockée sur un registre privé. Le problème du serpent qui se mord la queue. On pourrait laisser cette image publique puisqu’elle ne contient que le CLI Docker et le CLI AWS.Complexité d’utilisation
Il ne faut pas non plus perdre de vue que l’infrastructure déployée reste assez complexe, on utilise : une image Docker, un registre ECR, un rôle IAM, un déploiement de ARC, un déploiement du Runner, un secret Kubernetes et enfin un compte de service. Au dessus de ça, il faut encore ajouter la gestion du volume dans le Runner, ainsi que le
initContainer
.C’est beaucoup. D’autant que la partie authentification auprès d’ECR aurait pu être beaucoup simplifiée si ARC intégrait des méthodes d’authentification plus simples auprès des cloud providers principaux (voir pour en apprendre davantage).
Using private ECR repos for actions
Github
Using private ECR repos for actions
Updated
Oct 2, 2022Conclusion
Durant cet article, nous avons vu comment déployer un Runner sur Kubernetes afin de ne plus utiliser les Runners publics de Github, nous avons également mis en place un rôle IAM destiné à ce runner pour qu’il puisse utiliser une image stockée sur ECR, construite par la même occasion.
Cette solution est en place depuis plusieurs semaines chez notre client, et pour l’instant, nous n’avons eu aucun problème à déployer. Mis à part les défauts que nous avons listés au dessus, le client et nous-mêmes sommes satisfaits de la solution.
Si vous avez déjà rencontré ce problème, contactez-nous avec grand plaisir pour que nous puissions comparer les solutions que nous avons trouvées !
Écrit par
Victor Franzi
Victor est notre première recrue, il est passionné depuis son plus jeune âge et ne veut jamais s’arrêter d’apprendre 🧠
Sujets