Aller au contenu

Les sidecars Kubernetes

logo 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.14
RUN pip install gunicorn flask
ADD mainapp.py /app/
EXPOSE 5000
WORKDIR /app
ENTRYPOINT [ "gunicorn","--bind","0.0.0.0:5000","mainapp:app" ]

On construit l’image et on la lance pour tester que tout fonctionne :

Terminal window
docker build -t monapp:0.1 .
...
Removing intermediate container 3e99a4cd788e
---> 337ebeef49aa
Successfully built 337ebeef49aa
Successfully tagged monapp:0.1
docker run --name monapp -d -p 5000:5000 monapp:0.1
curl http://0.0.0.0:5000
Hello
docker rm -f monapp

Mon application tourne ! Maintenant passons à la partie Kubernetes. Commençons par créer un namespace :

Terminal window
kubectl create namespace monapp
kubens monapp

Kubens permet de switcher de namespace facilement. Pour l’installer rien de plus simple avec asdf :

Terminal window
asdf plugin add helm
asdf install helm latest
asdf global helm latest

Passons à l’écriture du deployment, dans le fichier app-deployment.yml :

apiVersion: apps/v1
kind: Deployment
metadata:
name: app
labels:
app: monapp
spec:
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 :

Terminal window
kubectl create configmap monapp-nginx --from-file=nginx.conf

On charge le déployment :

Terminal window
kubectl apply -f app-deployment.yml
kubectl get deployments -o wide
NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
app 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: Service
apiVersion: v1
metadata:
name: monapp-service
labels:
app: monapp
spec:
selector:
app: monapp
ports:
- protocol: TCP
port: 80
targetPort: 80
type: ClusterIP

Chargeons-le et exposons-le :

Terminal window
kubectl get services -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
monapp-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 :

Terminal window
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/v1
kind: Deployment
metadata:
name: app
labels:
app: monapp
spec:
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 :

Terminal window
kubectl apply -f app-deployment.yml
kubectl port-forward svc/monapp-service 8080:80
curl 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.