Les sidecars Kubernetes
Ma problématique : utiliser nginx comme reverse proxy pour exposer une application Django tournant sur un serveur uWSGI, ainsi que ses statics afin de limiter les requêtes HTTP.
Un side-car fait référence à un siège attaché à une moto afin qu’ils puissent transporter un passager ou du matériel. C’est donc un objet qui s’attache à un autre et, ainsi, en fait partie. Le but principal de l’objet auxiliaire est d’aider le principal.
Kubernetes propose ce modèle side-car, qui utilise l’un des composants les
plus puissants de Kubernetes : le Pod
. Les conteneurs hébergés sur le même pod
partagent la même adresse réseau. Tous les conteneurs du même pod peuvent se
connecter les uns aux autres via l’adresse localhost de la même manière que les
processus communiquent entre eux via HTTP sur la même machine.
Je vais donc utiliser ce principe. Je vais créer un pod qui héberge deux conteneurs, un pour l’application principale tournant sous gunicorn et un conteneur annexe expose le site via un reverse proxy Nginx.
Mise en oeuvre du side-car
Je vais simplifier en prenant une simple application Flask tournant également sur un serveur uWSGI en l’occurence gunicorn.
L’application est composée d’un fichier mainapp.py
:
from flask import Flask
app = Flask(__name__)
@app.route("/")def hello(): return "Hello"
if __name__ == "__main__": app.run(host='0.0.0.0')
et son Dockerfile
FROM python:3.9-alpine3.14RUN pip install gunicorn flaskADD mainapp.py /app/EXPOSE 5000WORKDIR /appENTRYPOINT [ "gunicorn","--bind","0.0.0.0:5000","mainapp:app" ]
On construit l’image et on la lance pour tester que tout fonctionne :
docker build -t monapp:0.1 .
...
Removing intermediate container 3e99a4cd788e ---> 337ebeef49aaSuccessfully built 337ebeef49aaSuccessfully tagged monapp:0.1
docker run --name monapp -d -p 5000:5000 monapp:0.1
curl http://0.0.0.0:5000Hello
docker rm -f monapp
Mon application tourne ! Maintenant passons à la partie Kubernetes. Commençons par créer un namespace :
kubectl create namespace monappkubens monapp
Kubens permet de switcher de namespace facilement. Pour l’installer rien de plus simple avec asdf :
asdf plugin add helmasdf install helm latestasdf set --home helm latest
Passons à l’écriture du deployment, dans le fichier app-deployment.yml
:
apiVersion: apps/v1kind: Deploymentmetadata: name: app labels: app: monappspec: replicas: 1 selector: matchLabels: app: monapp template: metadata: labels: app: monapp spec: containers: - image: artefacts.sa-cim.local/monapp:0.1 name: monapp resources: requests: memory: "64Mi" cpu: "125m" limits: memory: "128Mi" cpu: "250m" command: ["gunicorn"] args: ["--bind","0.0.0.0:5000","mainapp:app"] volumeMounts: - mountPath: /app/static name: static - image: nginx:1.21-alpine name: nginx ports: - containerPort: 443 resources: requests: cpu: 200m memory: 256Mi limits: cpu: 300m memory: 512Mi volumeMounts: - mountPath: /etc/nginx/conf.d name: conf - mountPath: /app/static name: static
volumes: - name: conf configMap: name: monapp-nginx - name: static emptyDir: {}
Voici la configuration nginx permettant d’exposer l’application Django
upstream backend { server 0.0.0.0:5000;}
server { listen 80; charset utf-8; access_log /var/log/nginx/access.log; error_log /var/log/nginx/error.log;
location /static { alias /app/static; }
location / { proxy_pass http://backend; proxy_connect_timeout 30s; proxy_send_timeout 60s; proxy_read_timeout 120s; proxy_redirect off; proxy_set_header Host $http_host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; }}
On charge le tout dans le configmap :
kubectl create configmap monapp-nginx --from-file=nginx.conf
On charge le déployment :
kubectl apply -f app-deployment.yml
kubectl get deployments -o wideNAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTORapp 1/1 1 1 9m11s monapp,nginx artefacts.sa-cim.local/monapp:0.1,nginx:1.21-alpine app=monapp
On retrouve bien deux conteneurs accrochés au pod app.
Exposons le service. Pour cela créer un fichier app-service.yaml
kind: ServiceapiVersion: v1metadata: name: monapp-service labels: app: monappspec: selector: app: monapp ports: - protocol: TCP port: 80 targetPort: 80 type: ClusterIP
Chargeons-le et exposons-le :
kubectl get services -o wideNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTORmonapp-service ClusterIP 10.43.182.247 <none> 80:30020/TCP 2m21s app=monapp
kubectl port-forward svc/monapp-service 8080:80
Dans un autre terminal :
curl http://localhost:8080
Hello
Cool ça fonctionne :). Pour arrêter le port forwarding un simple CTRL + C.
Et pour les statics on a rien dedans. Utilisons un initContainer pour créer un fichier dedans. Dans le fichier app-deployment.yml ajoutez ceci :
apiVersion: apps/v1kind: Deploymentmetadata: name: app labels: app: monappspec: replicas: 1 selector: matchLabels: app: monapp template: metadata: labels: app: monapp spec: initContainers: - name: init-myservice image: busybox:1.28 command: ['sh', '-c', "echo "mon sidecar" > /app/static/sidecar.txt"] volumeMounts: - mountPath: /app/static name: static containers:
.....
Appliquons-le et testons :
kubectl apply -f app-deployment.yml
kubectl port-forward svc/monapp-service 8080:80curl http://localhost:8080/static/sidecar.txt
mon sidecar
Je suis sûr que vous allez trouver des applications à l’utilisation de sidecar dans vos applications hébergé dans des clusters Kubernetes. A la prochaine.