Episode précédent:
# 🇫🇷 J'utilise K3S comme un PaaS - Partie 2 - Builder les images dans K3S avec Kaniko
Oui, je sais, comparer K3S à un PaaS, c'est provocateur 😉. C'est plus ma façon de "vivre" avec K3S qui s'inspire de la façon d'utiliser un PaaS.
# Introduction
Dans le précédent blog post je vous partageais une expérimentation où je poussais mon code par l'intermédiare d'un repository git dans un "pod runtime" construit à partir d'une image Docker contenant le runtime approprié (nodejs, java...) qui s'occupait de "compiler" (si nécessaire) le projet et de l'exécuter.
Alors, ça fonctionne (1), le but c'était d'éviter d'avoir à construire mon image Docker sur mon poste, mais ce n'est (peut-être 🤔) pas très orthodoxe (2) et pour être honnête 😉 je ne savais pas encore commment faire construire mon image Docker par un pod dans mon cluster.
Aujourd'hui, je vais utiliser le projet Kaniko (opens new window) pour construire mes images à l'intérieur de mon cluster K3S.
Donc cette fois-ci mon workflow sera le suivant:
- je code, je commit, je push vers un repository git (moi c'est sur GitLab.com (opens new window) mais cela fonctionne avec n'importe quel serveur de repositories git)
- je "fais" un
kubectl apply -f
de mon manifeste de déploiement - 1️⃣ j'ai mon 1er initContainer qui fait un
git clone
de mon projet - une fois le projet cloné, j'ai un 2ème container qui va utiliser Kanilo pour:
- 2️⃣ "builder" l'image de mon applcation
- 3️⃣ la pousser sur le Docker hub
- et enfin créer le pod applicatif 4️⃣ à partir de l'image générée
- (1): je me garde ça dans un coin, j'ai quelques idées pour l'utiliser dans d'autres expérimentation
- (2): j'aimais bien aussi l'idée de "cacher" l'adhérence à Docker (pas besoin de Dockerfile pour déployer mon application)
# Tout est dans le manifeste
# Dockerfile
🖐️ Bon, il faut quand même un Dockerfile. Je repars de mon application ExpressJS (je vous donnerais tout le code source un peu plus loin):
FROM node:13.12-slim
COPY . .
RUN npm install
CMD [ "node", "index.js" ]
# Credentials
Il faut aussi "transmettre" vos credentials Docker à Kaniko pour lui permettre de se connecter au Docker hub. Kaniko a besoin d'un fichier de configuration config.json
placé dans /kaniko/.docker
et qui ressemble à ceci:
{
"auths": {
"https://index.docker.io/v1/": {
"auth": "xxxxxxxxxxxxxxx"
}
}
}
le contenu de "xxxxxxxxxxxxxxx"
doit être votre_user_docker:votre_mot_de_passe_docker
encodé en base 64.
Donc si vous êtes bob
et que votre mot de passe est morane
, faites:
echo -n bob:morane | base64
Vous obtenez:
Ym9iOm1vcmFuZQ==
Utilisez cette chaîne de caractères dans votre fichier config.json
:
{
"auths": {
"https://index.docker.io/v1/": {
"auth": "Ym9iOm1vcmFuZQ=="
}
}
}
Pour fournir nos credentials au pod Kaniko, nous allons créer une ConfigMap:
kubectl create configmap docker-config -n funky-demo-prod --from-file=./config.json
- 🖐️
funky-demo-prod
c'est le nom du namespace que j'utilise, changez avec le vôtre- ✋ si vous laissez votre fichier
config.json
dans votre repository, N'OUBLIEZ PAS DE LE REFERENCER DANS.gitignore
Vérifiez que "tout est là":
kubectl describe configmap docker-config -n funky-demo-prod
Vous devriez obtenir ceci:
Name: docker-config
Namespace: funky-demo-prod
Labels: <none>
Annotations: <none>
Data
====
config.json:
----
{
"auths": {
"https://index.docker.io/v1/": {
"auth": "Ym9iOm1vcmFuZQ=="
}
}
}
Events: <none>
Maintenant on passe aux choses intéressantes.
# Le manifeste
Et enfin mon template de manifeste yaml:
# My Kube is a PaaS
---
# Service
apiVersion: v1
kind: Service
metadata:
name: ${APPLICATION_NAME}
spec:
selector:
app: ${APPLICATION_NAME}
ports:
- port: ${EXPOSED_PORT}
targetPort: ${CONTAINER_PORT}
---
# Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: ${APPLICATION_NAME}
spec:
replicas: 1
selector:
matchLabels:
app: ${APPLICATION_NAME}
template:
metadata:
labels:
app: ${APPLICATION_NAME}
spec:
initContainers:
# 1️⃣ This container clones the git repository to the EmptyDir volume (git-repository-${TAG})
- name: git-repository-container
image: alpine/git
imagePullPolicy: Always
args:
- clone
- -b
- ${BRANCH}
- ${GIT_REPOSITORY}
- /home/app
volumeMounts:
- name: git-repository-${TAG}
mountPath: /home/app
# 2️⃣ kaniko build
- name: build
image: gcr.io/kaniko-project/executor:latest
args:
- "--dockerfile=/home/app/Dockerfile"
- "--context=/home/app/"
- "--destination=${IMAGE}"
volumeMounts:
- name: git-repository-${TAG}
mountPath: /home/app
- name: docker-config
mountPath: /kaniko/.docker
containers:
# 3️⃣ web application container
- name: ${APPLICATION_NAME}
image: ${IMAGE}
ports:
- containerPort: ${CONTAINER_PORT}
imagePullPolicy: Always
volumes:
- name: git-repository-${TAG}
emptyDir: {}
- name: docker-config
configMap:
name: docker-config
---
# Ingress
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ${APPLICATION_NAME}
annotations:
kubernetes.io/ingress.class: traefik
spec:
rules:
- host: ${HOST}
http:
paths:
- backend:
serviceName: ${APPLICATION_NAME}
servicePort: ${EXPOSED_PORT}
Il est un peu long, mais les 3 choses importantes sont:
- 1️⃣ le 1er
initContainer
, qui va cloner notre repository dans un volume de typeemptyDir
- 2️⃣ le 2ème
initContainer
, qui va builder à l'aide de Kaniko l'image Docker et la pousser sur le Docker hub- vous remarquez qu'il utilise 2 volumes, le volume de type
emptyDir
et un volume "mappé" sur la ConfigMap
- vous remarquez qu'il utilise 2 volumes, le volume de type
- 3️⃣ et enfin le container applicatif construit à partir de l'image générée
# Et maintenant, on déploie
🤔 je dois encore travailler sur cette partie, mais pour le moment j'utilise les commandes suivantes:
export KUBECONFIG=/path_to_your_config_file/k3s.yaml
export SUB_DOMAIN="172.16.245.122.nip.io" # 1️⃣
export DOCKER_USER="k33g" # 2️⃣
COMMIT_MESSAGE="👋 update and 🚀 deploy"
git add .; git commit -m "${COMMIT_MESSAGE}"; git push
export CONTAINER_PORT=${CONTAINER_PORT:-8080}
export EXPOSED_PORT=${EXPOSED_PORT:-80}
export APPLICATION_NAME=$(basename $(git rev-parse --show-toplevel)) # 3️⃣
export TAG=$(git rev-parse --short HEAD)
export BRANCH=$(git symbolic-ref --short HEAD)
export IMAGE_NAME="${APPLICATION_NAME}-${BRANCH}-img"
export IMAGE="${DOCKER_USER}/${IMAGE_NAME}:${TAG}"
# 4️⃣ calculate the url of my git repository
url=$(git remote get-url origin)
url="${url/://}"
url="${url/git@/https://}"
export GIT_REPOSITORY="${url}"
export HOST="${APPLICATION_NAME}.${BRANCH}.${SUB_DOMAIN}"
# 5️⃣ 🖐️ the namespace must exist
export NAMESPACE="funky-demo-prod"
# === Deploy ===
rm ./kube/*.yaml
envsubst < ./deploy.template.yaml > ./kube/deploy.${TAG}.yaml
kubectl apply -f ./kube/deploy.${TAG}.yaml -n ${NAMESPACE}
echo "🌍 http://${HOST}" # 6️⃣
- 1️⃣
172.16.245.122
est l'ip de la VM qui contient mon cluster et j'utilise le servicenip.io
pour bénéficier d'un DNS wildcard- 2️⃣
k33g
est mon handle sur le Docker hub- 3️⃣ on est bien sûr dans un repository git
- 4️⃣ je calcule l'url de mon repository (rien de ne vous empêche de le mettre en dur)
- 5️⃣ n'oubliais pas de créer le namespace
- 6️⃣ l'url de votre application va ressembler à: http://${APPLICATION_NAME}.${BRANCH}.172.16.245.122.nip.io
# Remarque à propos du/des namespaces
Pour pouvoir déployer plusieurs versions de mon application je construis mon namespace comme ceci:
export NAMESPACE="funky-${APPLICATION_NAME}-${BRANCH}"
Donc pour créer ma ConfigMap, je fais ceci:
kubectl create configmap docker-config -n ${NAMESPACE} --from-file=./config.json
Et pour créer mon namespace de manière dynamique, uniquement s'il n'existe pas:
if [ -z "${NAMESPACE}" ]
then
export NAMESPACE="funky-${APPLICATION_NAME}-${BRANCH}"
fi
kubectl describe namespace ${NAMESPACE}
exit_code=$?
if [[ exit_code -eq 1 ]]; then
echo "🖐️ ${NAMESPACE} does not exist"
echo "⏳ Creating the namespace..."
kubectl create namespace ${NAMESPACE}
else
echo "👋 ${NAMESPACE} already exists"
fi
- 👋 Vous pouvez trouver le code complet de l'application ExpressJS ici: https://gitlab.com/bots-garden/funky/new-hello-js (opens new window)
- 🖐️ J'ai aussi un exemple Vert.x (à améliorer sur le Dockerfile) ici: https://gitlab.com/bots-garden/funky/hello-vert-x (opens new window)
🎉 Et voilà, c'est tout pour cette fois ci, maintenant vos build Docker se font dans votre cluster. J'ai un autre scénario où je le fais faire, toujours avec Kaniko, à un GitLab runner (vous pouvez trouver un exemple ici: https://tanuki-core-tutorials.gitlab.io/docker.registry (opens new window), j'écrirais probablement un article sur le sujet plus tard).
Bonne journée à tous. Bon courage pour le confinement, mettez le à profit pour apprendre si vous le pouvez (ça aide à ne pas tourner en rond). 😗
Pour rappel: Précédents blog posts de la série "Kit de survie K8S pour les dévs avec K3S":
- Partie 1: Intro et création du cluster (opens new window)
- Partie 2: Le déploiement (opens new window)
- Partie 2bis: Le déploiement: quelques améliorations (opens new window)
- Partie 3: Automatiser le déploiement (opens new window)
- Partie 4: Utiliser une registry privée (unsecure) (opens new window)
- Partie 5: Les Volumes (opens new window)
- Partie 6: Redis (opens new window)
- Partie 7: Vert-x (opens new window)
- Partie 8: Un peu de réseau (opens new window)
Last Articles
- 🇫🇷 Type Result en Kotlin | 2020-10-31 | Kotlin
- 🇫🇷 Type Result en Kotlin | 2020-10-31 | Kotlin
- 🇬🇧 Every GitLab Page deserves a real CI/CD | 2020-07-23 | GitLab CI
- 🇫🇷 Lit-Element, commencer doucement | 2020-07-20 | WebComponent
- 🇬🇧 Build quickly and host easily your Docker images with GitLab and GitLab CI | 2020-06-02 | GitLab CI
- 🇬🇧 Deploy quickly on Clever Cloud with GitLab CI | 2020-05-31 | GitLab CI
- 🇫🇷 Borg Collective, mes jouets pour apprendre Knative | 2020-05-30 | Knative
- 🇬🇧 Borg Collective, Toys to learn Knative | 2020-05-30 | Knative
- 🇫🇷 M5Stack, une petit device IOT bien sympathique, programmable en Python | 2020-05-09 | IOT
- 🇫🇷 Knative, l'outil qui rend Kubernetes sympathique | 2020-05-02 | kubernetes