Aller au contenu principal

Les sidecars Kubernetes

· 4 minutes de lecture
Stéphane ROBERT
Consultant DevOps

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 :

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 :

kubectl create namespace monapp
kubens monapp

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

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 :

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

On charge le déployment :

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 :

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 :

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 :

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.