Aller au contenu

Configurer Kubernetes pour Botalista

Ce document décrit la configuration spécifique de Kubernetes pour la mise en place du projet Botalista.

Prérequis

Avant de poursuivre, il est indispensable d'avoir mis en place le cluster Kubernetes en suivant les guides Kubernetes installation et Kubernetes à haute disponibilité.

Le label node-role.kubernetes.io/db-host doit être ajouté aux 3 workers qui seront responsables des bases de données.

Bash
1
2
3
kubectl label node isc-botalista-06 node-role.kubernetes.io/db-host=
kubectl label node isc-botalista-07 node-role.kubernetes.io/db-host=
kubectl label node isc-botalista-08 node-role.kubernetes.io/db-host=

Système de stockage distribué avec Longhorn

Longhorn est utilisé pour mettre en place un système de stockage par bloc au sein du cluster. Ce dernier permet la création de volume persistants, pour les applications au sein du cluster, qui sont accessibles depuis n'importe quel nœud du cluster.

Installation

L'installation est effectuée avec Helm 3.

Bash
# Add the Helm repo and fetch the latest charts
helm repo add longhorn https://charts.longhorn.io
helm repo update

# Install Longhorn
helm install longhorn longhorn/longhorn --create-namespace -n longhorn-system \
    --set longhornUI.replicas=1 \
    --set persistence.defaultClassReplicaCount=2 \
    --set defaultSettings.storageMinimalAvailablePercentage=15 \
    --set defaultSettings.storageReservedPercentageForDefaultDisk=15 \
    --set defaultSettings.nodeDrainPolicy="allow-if-replica-is-stopped"

Attention, il est recommandé de protéger l'accès à l'UI de Longhorn pour protéger les volumes des applications. Si vous ne prévoyez pas d'utiliser l'UI, vous pouvez définir le nombre de replicas à 0.

Configuration

Il n'est pas nécessaire d'utiliser le mécanisme de réplication de volumes de Longhorn pour les composants qui gèrent déjà la réplication des données, tels que les bases de données en cluster.

Par conséquent, une nouvelle StorageClass doit être créée afin de n'avoir qu'un seul replica se trouvant localement sur le même noeud du pod.

Bash
cat <<EOF > sc_longhorn-local.yaml
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: longhorn-local
  namespace: longhorn-system
provisioner: driver.longhorn.io
allowVolumeExpansion: true
reclaimPolicy: "Delete"
volumeBindingMode: Immediate
parameters:
  numberOfReplicas: "1"
  staleReplicaTimeout: "30"
  fromBackup: ""
  fsType: "ext4"
  dataLocality: "strict-local"
  unmapMarkSnapChainRemoved: "ignored"
EOF

kubectl apply -f sc_longhorn-local.yaml

Vérifier la présence des 2 StorageClass gérées par Longhorn :

Bash
1
2
3
4
$ kubectl get sc
NAME                 PROVISIONER          RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
longhorn (default)   driver.longhorn.io   Delete          Immediate           true                   15m
longhorn-local       driver.longhorn.io   Delete          Immediate           true                   36s

RabbitMQ

RabbitMQ est installé en cluster à partir de son opérateur Kubernetes.

Installation

Bash
# Install the RabbitMQ Cluster Kubernetes Operator
kubectl apply -f https://github.com/rabbitmq/cluster-operator/releases/latest/download/cluster-operator.yml

# Create the cluster
cat <<EOF> rabbitmq.yaml
apiVersion: rabbitmq.com/v1beta1
kind: RabbitmqCluster
metadata:
  name: rabbitmq
  namespace: rabbitmq
spec:
  replicas: 3
  image: rabbitmq:3.13.6-management
  persistence:
    storageClassName: longhorn
    storage: 1Gi
  resources:
    requests:
      cpu: 200m
      memory: 500Mi
    limits:
      cpu: 500m
      memory: 1Gi
  rabbitmq:
    # The RabbitMQ operator set the disk_free_limit.absolute to 2GB instead of 50 MB
    # You should override this value and/or take it into account for the persistence
    # storage size.
    additionalConfig: |
      disk_free_limit.absolute = 50MB
  affinity:
    podAntiAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: app.kubernetes.io/name
            operator: In
            values:
            - rabbitmq
        topologyKey: kubernetes.io/hostname
EOF
kubectl apply -f rabbitmq.yaml

Accéder à la page d'administration

Les commandes suivantes permettent de récupérer les identifiants pour accéder à la page d'administration de RabbitMQ.

Bash
1
2
3
4
5
# Get the username
kubectl -n rabbitmq get secret rabbitmq-default-user -o jsonpath="{.data.username}" | base64 --decode; echo

# Get the password
kubectl -n rabbitmq get secret rabbitmq-default-user -o jsonpath="{.data.password}" | base64 --decode; echo

Accès à l'application

Chaque instance de Botalista est composée d'une application web et d'un service associé.

L'accès à une instance spécifique s'effectue à l'aide de plusieurs composants :

  • Tout d'abord via l'adressse IP virtuelle de notre load balancer. Il s'agit du point d'entrée du cluster ;
  • Le load balancer redirige les requêtes sur deux services NodePort associés à Traefik (un pour les requêtes HTTP et l'autre pour HTTPS), qui sont donc accessible depuis l'extérieur du cluster Kubernetes.
  • Des objets IngressRoute du proxy Traefik sont déployés dans chaque espace de nom que l'on souhaite accéder.

HAProxy

HAProxy doit être configuré pour rediriger les requêtes web sur un port spécifique des workers du cluster. Ce port correspondra à un service NodePort de Traefik.

Les noeuds du plan de contrôle pourraient également recevoir les requêtes, étant donné que le port est ouvert sur tous les noeuds du cluster. Il est cependant préférable de laisser ce rôle aux worker afin d'exposer le moins que possible le plan de contrôle.

Modifier le fichier de configuration avec un nouveau backend qui redirige les requêtes HTTP sur le port 30080 des workers et les requêtes HTTPS sur le port 30443 des workers.

Bash
sudo nano /etc/haproxy/haproxy.cfg

Pour l'HTTP :

Unix/Linux Config Files
...
frontend default
    bind :80
    default_backend traefik

backend traefik
    balance roundrobin
    server worker01 10.136.26.71:30080 check
    server worker02 10.136.26.72:30080 check
    server worker06 10.136.26.76:30080 check
    server worker07 10.136.26.77:30080 check
    server worker08 10.136.26.78:30080 check
    server worker11 10.136.26.86:30080 check
    server worker12 10.136.26.87:30080 check
...

Pour l'HTTPS, un certificat SSL est requis. Ce dernier peut être auto-signé.

Unix/Linux Config Files
frontend default_https
    bind :443 ssl crt /etc/ssl/certs/bota-hepia.pem
    default_backend traefik_https

backend traefik_https
    balance roundrobin
    server worker01 10.136.26.71:30443 check ssl verify none
    server worker02 10.136.26.72:30443 check ssl verify none
    server worker06 10.136.26.76:30443 check ssl verify none
    server worker07 10.136.26.77:30443 check ssl verify none
    server worker08 10.136.26.78:30443 check ssl verify none
    server worker11 10.136.26.86:30443 check ssl verify none
    server worker12 10.136.26.87:30443 check ssl verify none

Ne pas oublier de redémarrer le service.

Bash
sudo systemctl restart haproxy

Il faut maintenant mettre en place Traefik sur le cluster Kubernetes avec un service NodePort ayant les port 30080 et 30443.

Traefik

Cette section explique la mise en place de Traefik au sein de notre cluster. L'image traefik/whoami est utilisée afin de s'assurer du bon fonctionnement de Traefik.

La mise en place de Traefik est effectuée dans l'espace de nom default.

Bash
# Create a traefik folder:
mkdir -p ~/k8s/traefik && cd ~/k8s/traefik

# Install Traefik Resource Definitions:
kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.1/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml

# Install RBAC for Traefik:
kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.1/docs/content/reference/dynamic-configuration/kubernetes-crd-rbac.yml

# Create the ServiceAccount required:
cat <<EOF > account.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: traefik-ingress-controller
  namespace: default
EOF
kubectl apply -f account.yaml

Déployer plusieurs instances de Traefik en créant le fichier traefik.yaml suivant.

YAML
apiVersion: v1
kind: Service
metadata:
  name: traefik
  namespace: default
spec:
  type: NodePort
  ports:
    - name: web
      port: 80
      nodePort: 30080
    - name: websecure
      port: 443
      nodePort: 30443
  selector:
    app: traefik
---
kind: Deployment
apiVersion: apps/v1
metadata:
  name: traefik
  namespace: default
  labels:
    app: traefik

spec:
  replicas: 3
  selector:
    matchLabels:
      app: traefik
  template:
    metadata:
      labels:
        app: traefik
    spec:
      serviceAccountName: traefik-ingress-controller # Must match the ServiceAccount name
      containers:
        - name: traefik
          image: traefik:v3.1.1
          args:
            - --accesslog
            - --providers.kubernetescrd
            - --entryPoints.web.Address=:80 # Must match a containerPort
            - --entryPoints.websecure.Address=:443 # Must match a containerPort
          ports:
            - containerPort: 80
            - containerPort: 443
          resources:
            requests:
              memory: 60Mi
            limits:
              memory: 200Mi
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - traefik
            topologyKey: kubernetes.io/hostname
Bash
kubectl apply -f traefik.yaml

L'image traefik/whoami permet de vérifier le bon fonctionnement de Traefik. Pour cela, créer le fichier whoami.yaml suivant.

YAML
kind: Deployment
apiVersion: apps/v1
metadata:
  name: whoami
  namespace: default
  labels:
    app: whoami
spec:
  replicas: 1
  selector:
    matchLabels:
      app: whoami
  template:
    metadata:
      labels:
        app: whoami
    spec:
      containers:
        - name: whoami
          image: traefik/whoami
          ports:
            - name: web
              containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: whoami
  namespace: default
spec:
  ports:
    - name: web
      port: 80
      targetPort: web

  selector:
    app: whoami
Bash
kubectl apply -f whoami.yaml

Pour finir, il faut créer un objet IngressRoute qui s'occupe de rediriger les requêtes au service approprié selon le nom de domaine de la requête. Créer le fichier routes.yaml suivant :

Bash
cat <<EOF > routes.yaml
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: traefik-ir
  namespace: default # must match the namespace of services to access
spec:
  entryPoints:
    - web # Defined in traefik.yaml args
  routes:
  - match: Host(\`traefik.bota-hepia.ch\`)
    kind: Rule
    services:
    - name: whoami
      port: 80
EOF
kubectl apply -f routes.yaml

Vérifier le fonctionnement avec la commande curl.

Note: En l'absence de DNS publique, le fichier /etc/hosts/ peut être modifié pour valider le fonctionnement)

Bash
$ curl -v traefik.bota-hepia.ch
*   Trying 10.136.26.70:80...
* Connected to traefik.bota-hepia.ch (10.136.26.70) port 80 (#0)
> GET / HTTP/1.1
> Host: traefik.bota-hepia.ch
> User-Agent: curl/7.81.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< content-length: 435
< content-type: text/plain; charset=utf-8
< date: Mon, 01 Jul 2024 16:21:58 GMT
<
Hostname: whoami-57b48994d9-b26cp
IP: 127.0.0.1
IP: ::1
IP: 192.168.169.27
IP: fe80::c846:25ff:fe50:37dd
RemoteAddr: 192.168.91.26:53942
GET / HTTP/1.1
Host: traefik.bota-hepia.ch
User-Agent: curl/7.81.0
Accept: */*
Accept-Encoding: gzip
X-Forwarded-For: 10.136.26.76
X-Forwarded-Host: traefik.bota-hepia.ch
X-Forwarded-Port: 80
X-Forwarded-Proto: http
X-Forwarded-Server: traefik-64f455ff76-hcl45
X-Real-Ip: 10.136.26.76

* Connection #0 to host traefik.bota-hepia.ch left intact

Traefik (SSL)

L'option insecureSkipVerify de Traefik doit être activé afin d'établir des connexions TLS avec un certificat auto-signé. Un objet ServersTransport doit être créé afin d'activer cette option pour des services spécifique.

YAML
1
2
3
4
5
6
7
8
apiVersion: traefik.io/v1alpha1
kind: ServersTransport
metadata:
  name: transportinsecure
  namespace: elastic-stack

spec:
  insecureSkipVerify: true

Il est ensuite possible de créer un objet IngressRoute qui utilise ce ServersTransport et qui active les connexions TLS. Ces deux objets doivent se trouver dans le même espace de nom.

YAML
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: traefik-ir-ssl
  namespace: elastic-stack # must match the namespace of services to access
spec:
  entryPoints:
    - websecure # Defined in traefik.yaml args
  routes:
  - match: Host(`kibana.bota-hepia.ch`)
    kind: Rule
    services:
    - name: eck-stack-eck-kibana-kb-http
      port: 5601
      serversTransport: transportinsecure # allow self signed certificates
  tls:
    domains:
    - main: bota-hepia.ch

Keycloak

Ce document explique comment mettre en place Keycloak.

Créer un nouvel espace de nom pour keycloak : kubectl create ns keycloak

Configuration de la base de données PostgreSQL

Keycloak se connectera à une base de données PostgreSQL externe, qu'il faut donc créer au préalable.

Exécuter les commandes ci-dessous une fois connecté à l'instance PostgreSQL.

Bash
1
2
3
4
5
# psql
CREATE DATABASE keycloak_db;
\c keycloak_db
CREATE USER keycloak_user WITH PASSWORD 'Pass1234';
GRANT ALL ON SCHEMA public TO keycloak_user;

Créer ensuite un secret kubernetes qui contient les données de connexion de la base.

YAML
apiVersion: v1
kind: Secret
metadata:
  name: external-pg
  namespace: keycloak
type: kubernetes.io/basic-auth
stringData:
  host: postgres.postgres
  port: "5432"
  username: keycloak_user
  database: keycloak_db
  password: Pass1234

Déployer Keycloak

Keycloak est installé avec Helm et est configuré à partir d'un fichier values.yaml.

Bash
helm install keycloak bitnami/keycloak -f values.yaml -n keycloak

Configuration

Une fois Keycloak déployé, il faut le configurer.

Par instance Botalista :

  • Créer un nouvel REALM associé, puis créer les utilisateurs et clients ci-dessous
  • Créer un nouvel utilisateur avec un mot de passe
  • Créer un client nommé frontends
    • Access type: public
    • Root URL: https://chat.bota-hepia.ch
    • Valid redirect URIs: https://chat.bota-hepia.ch/*
    • Web Origins : https://chat.bota-hepia.ch
  • Créer un client nommé backends
    • Access type: bearer only

Ressources

Documentation officielle de Traefik :

HAProxy ACL :

Longhorn :

Keycloak :