Automatiser le déploiement de clusters Kubernetes EKS avec Github Action
Nous avons pour habitude de déployer des clusters Kubernetes avec EKS via Terraform et notamment via le module officiel que nous automatisons via Terragrunt.
Nous allons voir aujourd’hui comment connecter cette partie Terraform/Terragrunt à Github Action, l’outil de CI/CD de Github.
Le dépôt de code tEKS présente un squelette que vous pouvez réutiliser en tant que template Github :
La structure de fichier est la suivante :
.
├── CODEOWNERS
├── LICENSE
├── README.md
├── mkdocs.yml
├── requirements.txt
└── terraform
└── live
└── demo
├── common_tags.yaml
├── common_values.yaml
├── eu-west-3
│ ├── ecr
│ ├── eks
│ ├── eks-addons
│ ├── eks-namespaces
│ └── vpc
└── terragrunt.hcl
Chaque dossier dans eu-west-3
représente un module Terraform donné, nous
allons préparer la pipeline suivante pour chaque dossier dans live
:
- Installation des prérequis
- Déploiement des modules par ordres de dépendance
- Vérification du bon déploiement des pods
- Destruction des modules dans l’ordre inverse de dépendance.
Déclaration des secrets Github
Dans un premier temps nous avons besoin de déclarer des secrets pour accéder à AWS, pour cela dans la console Github dans les paramètres du repository ou de l’organisation :
Il faut déclarer les variables d’environnement AWS suivantes :
AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY
Déclaration de la pipeline éphémère demo
Dans le dossier demo
, si vous souhaitez tester en live sur un compte AWS, il
faudra adapter les valeurs présentes dans common_values.yaml
afin de
représenter votre configuration :
---
aws_account_id: 161285725140
aws_region: eu-west-3
prefix: cfd-particule
default_domain_name: clusterfrak-dynamics.io
Si vous possédez une zone AWS route53, la remplir ici vous permettra d’accéder au monitoring du cluster out of the box.
Dans le dépôt, dans .github/workflows
le fichier demo.yml
défini la
pipeline pour le dossier terraform/live/demo/*
. Tous les changements dans ce
dossier déclencherons cette pipeline :
name: 'terragrunt:env:demo'
on:
push:
branches:
- master
paths:
- 'terraform/live/demo/**'
pull_request:
Ensuite nous allons installer les prérequis, cette partie installe les différents outils nécessaire tels que Terraform, Helm, Terragrunt, etc ainsi que de préparer les variables d’environnement AWS :
jobs:
terraform:
name: 'terragrunt:env:demo'
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v2
- name: 'asdf:install'
uses: asdf-vm/actions/install@v1.0.0
- name: 'terraform:provider:kubectl'
run: |
mkdir -p ~/.terraform.d/plugins
curl -Ls https://api.github.com/repos/gavinbunney/terraform-provider-kubectl/releases/latest | jq -r ".assets[] | select(.browser_download_url | contains(\"$(uname -s | tr A-Z a-z)\")) | select(.browser_download_url | contains(\"amd64\")) | .browser_download_url" | xargs -n 1 curl -Lo ~/.terraform.d/plugins/terraform-provider-kubectl
chmod +x ~/.terraform.d/plugins/terraform-provider-kubectl
- name: 'terraform:setup'
uses: hashicorp/setup-terraform@v1
with:
terraform_wrapper: false
- name: 'aws:credentials'
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: eu-west-1
Puis, pour chaque module nous allons terraform init
, terraform fmt
et
terraform validate
puis terraform apply
:
- name: 'terragrunt:init:vpc'
run: terragrunt init --terragrunt-non-interactive
working-directory: terraform/live/demo/eu-west-3/vpc
- name: 'terragrunt:fmt:vpc'
run: terragrunt fmt -check
working-directory: terraform/live/demo/eu-west-3/vpc
- name: 'terragrunt:validate:vpc'
run: terragrunt validate
working-directory: terraform/live/demo/eu-west-3/vpc
- name: 'terragrunt:plan:vpc'
run: terragrunt plan --terragrunt-non-interactive
working-directory: terraform/live/demo/eu-west-3/vpc
- name: 'terragrunt:apply:vpc'
run: terragrunt apply -auto-approve
working-directory: terraform/live/demo/eu-west-3/vpc
- name: 'terragrunt:init:eks'
run: terragrunt init --terragrunt-non-interactive
working-directory: terraform/live/demo/eu-west-3/eks
- name: 'terragrunt:fmt:eks'
run: terragrunt fmt -check
working-directory: terraform/live/demo/eu-west-3/eks
- name: 'terragrunt:validate:eks'
run: terragrunt validate
working-directory: terraform/live/demo/eu-west-3/eks
- name: 'terragrunt:plan:eks'
run: terragrunt plan --terragrunt-non-interactive
working-directory: terraform/live/demo/eu-west-3/eks
- name: 'terragrunt:apply:eks'
run: terragrunt apply -auto-approve
working-directory: terraform/live/demo/eu-west-3/eks
- name: 'terragrunt:apply:eks:upload-kubeconfig'
uses: actions/upload-artifact@v1
with:
name: kubeconfig
path: kubeconfig
working-directory: terraform/live/demo/eu-west-3/eks
- name: 'terragrunt:init:eks-addons'
run: terragrunt init --terragrunt-non-interactive
working-directory: terraform/live/demo/eu-west-3/eks-addons
- name: 'terragrunt:fmt:eks-addons'
run: terragrunt fmt -check
working-directory: terraform/live/demo/eu-west-3/eks-addons
- name: 'terragrunt:validate:eks-addons'
run: terragrunt validate
working-directory: terraform/live/demo/eu-west-3/eks-addons
- name: 'terragrunt:plan:eks-addons'
run: terragrunt plan --terragrunt-non-interactive
working-directory: terraform/live/demo/eu-west-3/eks-addons
- name: 'terragrunt:apply:eks-addons'
run: terragrunt apply -auto-approve
working-directory: terraform/live/demo/eu-west-3/eks-addons
Nous allons attendre que tous les pods soient Ready
:
- name: 'kubectl:wait-for-pods'
run: kubectl --kubeconfig=kubeconfig wait --for=condition=Ready pods --all --all-namespaces --timeout 300s
working-directory: terraform/live/demo/eu-west-3/eks
Et finalement détruire l’infrastructure dans l’ordre inverse de dépendance :
- name: 'terragrunt:destroy:eks-addons'
run: terragrunt destroy -auto-approve
working-directory: terraform/live/demo/eu-west-3/eks-addons
continue-on-error: true
if: "!contains(github.event.head_commit.message, 'ci keep')"
- name: 'terragrunt:destroy:eks-addons:cleanup-stale-state'
run: terragrunt state list 2>/dev/null | xargs terragrunt state rm
working-directory: terraform/live/demo/eu-west-3/eks-addons
continue-on-error: true
if: "!contains(github.event.head_commit.message, 'ci keep')"
- name: 'terragrunt:destroy:eks'
run: terragrunt destroy -auto-approve
working-directory: terraform/live/demo/eu-west-3/eks
continue-on-error: true
if: "!contains(github.event.head_commit.message, 'ci keep')"
- name: 'terragrunt:destroy:vpc'
run: terragrunt destroy -auto-approve
working-directory: terraform/live/demo/eu-west-3/vpc
continue-on-error: true
if: "!contains(github.event.head_commit.message, 'ci keep')"
Ici, la partie if: "!contains(github.event.head_commit.message, 'ci keep')"
permet de garder l’infra et de ne pas la détruire dans le cas où le message de
commit contient la chaine de caractère ci keep
.
Une fois le code push, la pipeline s’exécute dans la console Github :
Cette pipeline n’est pas parfaite, c’est un premier jet qui peut être amélioré au fur et à mesure des évolutions de Github Action :
- Pour le moment pas d’approval manuel comme c’est le cas sur Gitlab
- Impossible d’empêcher l’exécution en parallèle de deux pipelines (2 Pull request en même temps bloqueront le state terraform)
- Pas de possibilité de force trigger une pipeline (sauf avec des hack
Pour le moment, cette pipeline est faite pour tester une infrastructure éphémère.
Pour aller plus loin, déployons un nouvel environnement, pour cela, dupliquons
l’environnement de demo
en prod
.
Création de l’environnement permanent de production
cp -ar demo/prod
Il faut ensuite changer les valeurs de common_tags
et common_values
:
---
Owner: clusterfrak-dynamics
Env: prod
Terraform: "0.12"
---
aws_account_id: 161285725140
aws_region: eu-west-3
prefix: cfd-particule-prod
default_domain_name: clusterfrak-dynamics.io
Reprenons la même pipeline en enlevant la partie destruction et en remplaçant
demo
par prod
dans .github/workflows/prod.yml
et supprimons le build sur
les pull-requests
:
name: 'terragrunt:env:prod'
on:
push:
branches:
- master
paths:
- 'terraform/live/prod/**'
jobs:
terraform:
name: 'terragrunt:env:prod'
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v2
- name: 'asdf:install'
uses: asdf-vm/actions/install@v1.0.0
- name: 'terraform:provider:kubectl'
run: |
mkdir -p ~/.terraform.d/plugins
curl -Ls https://api.github.com/repos/gavinbunney/terraform-provider-kubectl/releases/latest | jq -r ".assets[] | select(.browser_download_url | contains(\"$(uname -s | tr A-Z a-z)\")) | select(.browser_download_url | contains(\"amd64\")) | .browser_download_url" | xargs -n 1 curl -Lo ~/.terraform.d/plugins/terraform-provider-kubectl
chmod +x ~/.terraform.d/plugins/terraform-provider-kubectl
- name: 'terraform:setup'
uses: hashicorp/setup-terraform@v1
with:
terraform_wrapper: false
- name: 'aws:credentials'
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: eu-west-1
- name: 'terragrunt:init:vpc'
run: terragrunt init --terragrunt-non-interactive
working-directory: terraform/live/prod/eu-west-3/vpc
- name: 'terragrunt:fmt:vpc'
run: terragrunt fmt -check
working-directory: terraform/live/prod/eu-west-3/vpc
- name: 'terragrunt:validate:vpc'
run: terragrunt validate
working-directory: terraform/live/prod/eu-west-3/vpc
- name: 'terragrunt:plan:vpc'
run: terragrunt plan --terragrunt-non-interactive
working-directory: terraform/live/prod/eu-west-3/vpc
- name: 'terragrunt:apply:vpc'
run: terragrunt apply -auto-approve
working-directory: terraform/live/prod/eu-west-3/vpc
- name: 'terragrunt:init:eks'
run: terragrunt init --terragrunt-non-interactive
working-directory: terraform/live/prod/eu-west-3/eks
- name: 'terragrunt:fmt:eks'
run: terragrunt fmt -check
working-directory: terraform/live/prod/eu-west-3/eks
- name: 'terragrunt:validate:eks'
run: terragrunt validate
working-directory: terraform/live/prod/eu-west-3/eks
- name: 'terragrunt:plan:eks'
run: terragrunt plan --terragrunt-non-interactive
working-directory: terraform/live/prod/eu-west-3/eks
- name: 'terragrunt:apply:eks'
run: terragrunt apply -auto-approve
working-directory: terraform/live/prod/eu-west-3/eks
- name: 'terragrunt:init:eks-addons'
run: terragrunt init --terragrunt-non-interactive
working-directory: terraform/live/prod/eu-west-3/eks-addons
- name: 'terragrunt:fmt:eks-addons'
run: terragrunt fmt -check
working-directory: terraform/live/prod/eu-west-3/eks-addons
- name: 'terragrunt:validate:eks-addons'
run: terragrunt validate
working-directory: terraform/live/prod/eu-west-3/eks-addons
- name: 'terragrunt:plan:eks-addons'
run: terragrunt plan --terragrunt-non-interactive
working-directory: terraform/live/prod/eu-west-3/eks-addons
- name: 'terragrunt:apply:eks-addons'
run: terragrunt apply -auto-approve
working-directory: terraform/live/prod/eu-west-3/eks-addons
- name: 'kubectl:wait-for-pods'
run: kubectl --kubeconfig=kubeconfig wait --for=condition=Ready pods --all --all-namespaces --timeout 300s
working-directory: terraform/live/prod/eu-west-3/eks
Une fois tous le code pushé, le workflow de prod démarre :
Une fois le run terminé, le fichier
kubeconfig
est récupérable dans la partie Artifacts
:
Avec le compte AWS correspondant correctement configurer sur votre machine :
$ kubectl --kubeconfig=/home/klefevre/Downloads/kubeconfig get nodes
NAME STATUS ROLES AGE VERSION
ip-10-0-1-126.eu-west-3.compute.internal Ready <none> 19m v1.16.8-eks-e16311
ip-10-0-2-150.eu-west-3.compute.internal Ready <none> 19m v1.16.8-eks-e16311
ip-10-0-3-224.eu-west-3.compute.internal Ready <none> 19m v1.16.8-eks-e16311
Si vous aviez déjà une zone route53 configurée sur votre compte et que vous
l’avez renseignée dans common_values.yaml
, vous pouvez accéder directement à
Grafana :
$ kubectl --kubeconfig=/home/klefevre/Downloads/kubeconfig -n monitoring get ingress
NAME HOSTS ADDRESS PORTS AGE
karma karma.clusterfrak-dynamics.io ad8315d46de3c416694732864d785dfc-463529602.eu-west-3.elb.amazonaws.com 80, 443 16m
prometheus-operator-grafana grafana.clusterfrak-dynamics.io ad8315d46de3c416694732864d785dfc-463529602.eu-west-3.elb.amazonaws.com 80, 443 17m
$ kubectl --kubeconfig=/home/klefevre/Downloads/kubeconfig -n monitoring get secrets/prometheus-operator-grafana -o json | jq -r '.data."admin-password"' | base64 -d
gTj5cmPwqBh0Ojye%
Vous pouvez vous authentifier sur grafana.$VOTRE_DOMAINE
(sans %
a la fin du
mot de passe) :
Conclusion
Cette méthode, que ce soit via Github action ou un autre outil de CI/CD permet de tendre vers le principe GitOps pour l’infrastructure, bien sur elle n’est pas parfaite et nous la feront évoluer au fur et à mesure de nos et cela dépend bien évidemment des différents use case que vous pourriez avoir.
Elle constitue néanmoins une bonne base sur laquelle construire votre infrastructure as code et commencer à déporter l’exécution de celle ci vers un environnement mieux maitrisé que les laptops des DevOps.
Github action est aujourd’hui gratuit pour les dépôts publics.
N’hésitez pas à jouer avec notre template et à remplir des issues et pull request si besoin :)
A bientôt,
Kevin Lefevre, CTO & Co-founder