Gitlab.com - CI/CD di un sito statico su Kubernetes
Buongiorno a tutti, in questa guida vi mostrerò un esempio di CI/CD di un deployment di un container contenente un sito statico in html. Questo esempio può essere tranquillamente…
> Articolo d'archivio del 2018, dal vecchio nutellino.it: comandi e versioni potrebbero non essere più attuali.

Pipeline, pipeline ovunque!
Buongiorno a tutti, in questa guida vi mostrerò un esempio di CI/CD di un deployment di un container contenente un sito statico in html. Questo esempio può essere tranquillamente adattato ad ogni tipo di deployment stateless che richiede un continuous integration/deployment.
Qui il link del progetto di esempio che ho creato ad hoc su gitlab.com: https://gitlab.com/nutellinoit/k8s-ci-cd-gitlab-example
Non utilizzerò l'integrazione nativa di Gitlab con Kubernetes bensì utilizzerò semplicemente il comando kubectl durante la pipeline.
Requisiti
- Dimestichezza con Docker
- Un cluster Kubernetes funzionante
Progetto Demo
L'alberatura del progetto è la seguente:
├── build_container
│ ├── Dockerfile
│ └── test.sh
├── kubernetes_manual
│ ├── create_login_docker_registry.sh
│ └── namespace.yml
├── rootfs
│ ├── canary
│ └── index.html
├── kube-deployment.yml
├── .gitlab-ci.yml
└── README.mdLa cartella build_container contiene il file Dockerfile per le direttive di build, e un file di test che andremo ad aggiungere per testare successivamente se il container è stato creato correttamente.
La cartella rootfs contiene tutto il nostro stupendo e fantasmagorico sito html, formato da un file index.html e un file che ho chiamato canary su cui andremo ad eseguire il check di correttezza di build del container.
Sarà un sito fantasmagorico e automagico!
Infine nella root del progetto troviamo il file che useremo per aggiornare il deployment su kubernetes, kube-deployment.yml e il file importantissimo .gitlab-ci.yml che configura la pipeline del progetto gitlab.
In aggiunta a queste cartelle, ho messo anche una cartella di esempio dei pre-requisiti necessari per poter fare il deploy in produzione (la creazione del namespace e la creazione delle eventuali credenziali del registro da utilizzare per il pull, se il registro non è pubblico)
Il file .gitlab-ci.yml
Partiamo dal cuore del progetto, il file che definisce la pipeline.
# This file is a template, and might need editing before it works on your project.
# Official docker image.
image: docker:latest
build_website:
services:
- docker:dind
stage: build
before_script:
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
script:
- rm -rf build_container/rootfs || true
- cp -r rootfs build_container/rootfs
- docker build --pull -t "$CI_REGISTRY_IMAGE":"$CI_PIPELINE_ID" build_container/
- docker tag "$CI_REGISTRY_IMAGE":"$CI_PIPELINE_ID" "$CI_REGISTRY_IMAGE":latest
- docker push "$CI_REGISTRY_IMAGE":"$CI_PIPELINE_ID"
- docker push "$CI_REGISTRY_IMAGE":latest
- rm -rf build_container/rootfs || true
test_website:
services:
- docker:dind
stage: test
before_script:
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
script:
- >
docker run -t --rm
-p 80:80
$CI_REGISTRY_IMAGE:$CI_PIPELINE_ID
/test.sh
deploy_website:
stage: deploy
image: alpine:3.7
environment:
name: Production
script:
- apk update && apk add --no-cache curl
- curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl
- chmod +x ./kubectl && mv ./kubectl /usr/local/bin/kubectl
- mkdir -p $HOME/.kube
- echo $KUBE_CONFIG > ~/.kube/config
- kubectl -n hellocicd get pods
- sed -i "s/latest/$CI_PIPELINE_ID/g" kube-deployment.yml
- kubectl -n hellocicd create -f kube-deployment.yml || true
- kubectl -n hellocicd apply -f kube-deployment.ymlUtilizzeremo tre stage, build, test e deploy.
Build
Il primo stage, quello di build, si preoccupa di eseguire:
build_website:
services:
- docker:dind
stage: build
before_script:
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
script:
- rm -rf build_container/rootfs || true
- cp -r rootfs build_container/rootfs
- docker build --pull -t "$CI_REGISTRY_IMAGE":"$CI_PIPELINE_ID" build_container/
- docker tag "$CI_REGISTRY_IMAGE":"$CI_PIPELINE_ID" "$CI_REGISTRY_IMAGE":latest
- docker push "$CI_REGISTRY_IMAGE":"$CI_PIPELINE_ID"
- docker push "$CI_REGISTRY_IMAGE":latest
- rm -rf build_container/rootfs || true
....Descrizione dei passaggi:
- Prima di eseguire i comandi dello script, il processo di build si loggherà all'interno del registro del progetto utilizzando delle variabili d'ambiente di sistema di gitlab già valorizzate (su gitlab sono valorizzate di default)
- Si pulisce la cartella
build_container/rootfsse per qualche motivo è presente - Si copia ricorsivamente il contenuto della cartella
rootfsall'interno della cartellabuild_containerin modo che durante il build Docker possa copiarla all'interno dell'immagine, essendo build_container il contesto. - Si va ad eseguire il build dalla cartella build_container, nominando l'immagine come il nome dato da gitlab, nel nostro caso
registry.gitlab.com/nutellinoit/k8s-ci-cd-gitlab-exampletaggandola con l'id della pipeline - Si aggiunge il tag
latest - Si pushano le due immagini
- Si pulisce la cartella rootfs all'interno di
build_container
Test
Una volta effettuato il build, andremo a vedere se effettivamente i file del nostro sito statico siano stati aggiunti nella directory corretta. Utilizzeremo lo script di test.sh che non è altro che un controllo se il file canary esiste nella cartella /usr/share/nginx/html
#!/usr/bin/env sh
if [ ! -f /usr/share/nginx/html/canary ]; then
echo "something wrong"
exit 127
fiIl nostro stage sarà quindi il seguente:
...
test_website:
services:
- docker:dind
stage: test
before_script:
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
script:
- >
docker run -t --rm
-p 80:80
$CI_REGISTRY_IMAGE:$CI_PIPELINE_ID
/test.sh
...Descrizione dei passaggi
- Come prima il processo deve loggarsi nel registro prima di eseguire il pull del container
- Lanciamo il container appena creato, utilizzando l'id della pipeline corrente lanciando come entrypoint principale il nostro file
test.sh
In questo modo se il nostro build per qualche motivo non sia andato come doveva, riusciamo a bloccare il deploy.
Deploy
Requisiti del deploy
Qui la faccenda diventa interessante, ci servirà:
- Un file kubeconfig contenente le credenziali per poter accedere al cluster
- Python, per lanciare una riga da command line per trasformarlo da yaml a json
- Un namespace già configurato su kubernetes, nel nostro caso dovrà chiamarsi
hellocicde gli eventuali permessi per poterci interagire
Partiamo quindi a convertire il nostro file kubeconfig in json, lanciamo il comando:
python -c 'import sys, yaml, json; json.dump(yaml.load(sys.stdin), sys.stdout, indent=4)' < kubeconfigdemo.yml > kubeconfigdemo.jsonPrendiamo quindi il nostro json e andiamo su gitlab. Dovremmo aggiungere una variabile pipeline nelle impostazioni del progetto, KUBE_CONFIG. Di seguito un piccolo video guida:
[Gitlab Video: immagine non recuperata]
Fatto questo, siamo pronti per vedere lo stage deploy
deploy_website:
stage: deploy
image: alpine:3.7
environment:
name: Production
script:
- apk update && apk add --no-cache curl
- curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl
- chmod +x ./kubectl && mv ./kubectl /usr/local/bin/kubectl
- mkdir -p $HOME/.kube
- echo $KUBE_CONFIG > ~/.kube/config
- kubectl -n hellocicd get pods
- sed -i "s/latest/$CI_PIPELINE_ID/g" kube-deployment.yml
- kubectl -n hellocicd create -f kube-deployment.yml || true
- kubectl -n hellocicd apply -f kube-deployment.ymlDescrizione dei passaggi
- Utilizziamo l'immagine alpine e installiamo l'utility kubectl per interagire con il cluster kubernetes
- Creiamo la cartella
.kubeall'interno della home corrente (è la cartella di default che l'utilitykubectlutilizza per caricare il config) - Copiamo il nostro JSON salvato nella variabile pipeline
KUBE_CONFIGnel file~/.kube/config - Sostituiamo "latest" dal file di deployment di kubernetes con l'id della pipeline, per avere sempre il container con l'immagine esatta corrispondente alla pipeline
- Creiamo il deployment se non esiste ( il
|| trueserve nel caso esista, non vogliamo che il processo di deploy si fermi) - Aggiorniamo il deployment
Se tutto va bene ci troviamo nella situazione di avere nel nostro cluster kubernetes il nostro nuovo fiammante deployment!
$ kubectl -n hellocicd get pods
NAME READY STATUS RESTARTS AGE
hellocicd-webiste-78d566856d-9xhq6 1/1 Running 0 3malla prossima!
$ cat ~/archivio/gitlab-com-ci-cd-di-un-sito-statico-su-kubernetes.md
WARNING: questo file è del 2018.
L'ho scritto io, ma un io di qualche anno fa: potrebbe non rispecchiare più quello che penso né quello che so fare.
Continuare? [s/N]