Avec le confinement et le télé-travail, de nombreuses sociétés ont proposé des outils pour le travail collaboratif. Et notamment des outils de discussion en ligne pour vous faciliter la vie, et ainsi vous permettre d'échanger plus facilement avec vos collègues. Top ! 🤨

Non ? Comment ça ?

Les outils de discussion en ligne sont nombreux et parfois gratuits. Mais vous connaissez sûrement cette phrase : "Si c'est gratuit, c'est que vous êtes le produit !". Personnellement j'aime tout simplement avoir la maîtrise de ma donnée. Peu importe laquelle, y compris des conversations professionnelles ou sur des projets communautaires. Si j'ai besoin d'un outil, je regarde d'abord si je trouve une version Open-Source de mon besoin... Et dans le cas des outils de tchat en ligne, ça tombe bien puisqu'on trouve un outil formidable pour ça : Rocket.Chat !

De plus son installation étant assez simple, et n'exigeant que peu de ressources, il est possible d'installer ça très rapidement et à moindre frais. Par exemple chez OVH avec un serveur de la gamme Kimsufi ou même un VPS.

Nous allons donc voir dans cet article comment installer Rocket.Chat et y accéder. Pour cela nous allons pouvoir nous aider de nos outils préférés. Une première dans cet article, je vais donner la configuration pour Docker et Kubernetes !

Let's go !

Rocket.Chat  c'est quoi ?

Rocket.Chat est donc un logiciel de communication collaboratif. Le  fonctionnement de l'outil est très proche de celui de Slack : vous  pouvez échanger avec les autres membres sur de fils publics, ou par le biais de discussion privées de groupe ou même par messages privés.

La mise en forme des messages se fait avec le langage Markdown. Il intègre également de nombreuses fonctionnalités comme par exemple :

  • Le partage de fichiers
  • Des messages vocaux
  • Mais également de l'échange par vidéo.

Techniquement, Rocket.Chat est développé en javascript et nécessite l'utilisation d'une base MongoDB afin de fonctionner.

Afin de réaliser ce tutoriel, je vais estimer que vous avez à disposition un des deux environnements suivants :

  • Docker et docker-compose d'installer...
  • Ou un environnement Kubernetes fonctionnel.

Au besoin vous pouvez utiliser K3S et suivre cet installation de K3S sur un VPS OVH pour cet essai. De même pour Docker, vous trouverez ce tutoriel d'installation de Docker sur un dédié Kimsufi sur le blog.

Nous allons commencer par l'installation de cette dépendance, la base de donnée MongoDB.

MongoDB

Comme je l'ai précisé en début de cet article, je vais détailler la mise en place de cet outil avec Docker mais également avec Kubernetes. Dans tous les cas je vais suivre l'arborescence suivante sur les deux projets :

folder docker

Et oui surprise, Traefik sera de la partie afin de me servir de reverse-proxy et de fournisseur de certificat SSL pour Rocket.Chat ! Ce sera l'occasion d'utiliser une nouvelle fois Traefik 2.2 et le DnsChallenge pour délivrer des certificats SSL.

Commençons tout d'abord par Docker.

  1. Initialisation de notre base de données avec Docker

Je vais donc créer un fichier docker-compose.yml pour créer un conteneur pour la base de donnée. J'utilise l'image officielle de MongDB dans sa dernière version :

version: '3.7'
services:
  mongo:
    image: mongo:4
    container_name: mongo
    restart: unless-stopped
    command: mongod --oplogSize 128 --replSet rs0
    volumes:
      - mongodb_data:/data/db
    networks:
      - lan

Je passe plusieurs arguments au démarrage de l'instance :

--oplogSize 128 --replSet rs0

Il s'agit d'option pour la mise en place d'un réplica de base. C'est une nécessité pour Rocket.Chat car je n'ai pas prévu de faire de haute-disponibilité sur mes services :

Rocket.Chat uses the MongoDB replica set to improve performance via Meteor Oplog tailing.

Je vais également utiliser un volume persistant pour stocker les données :

mongodb_data:/data/db

La seule difficulté réside dans le fait que la réplication de base dans MongoDB nécessite une initialisation. Je pourrai réaliser cette initialisation à l'aide d'une connexion manuelle sur le container mais il existe une autre solution.

Je vais utiliser un conteneur pour lancer le processus :

  mongo-init-replica:
    image: mongo:4
    container_name: mongo_replica
    restart: on-failure
    command: 'mongo mongo/rocketchat --eval "printjson(rs.initiate())"'
    depends_on:
      - mongo
    networks:
      - lan

J'utilise une politique de redémarrage on-failure , une fois son action exécutée avec succès il ne se relancera plus. Par contre en cas d'imprévu, comme le conteneur de la base pas encore prêt, il se relancera : pratique.

Du côté de MongoDB c'est terminé ! Regardons de plus prêt la configuration avec Kubernetes !

2. Initialisation de notre base de donnée avec Kubernetes

Voici un rapide aperçu de l'organisation de mes fichiers :

folder k8s

J'ai volontairement séparé un maximum les ressources Kubernetes afin de rendre votre relecture des fichiers plus fluide. Dans l'ensemble ces fichiers vont réaliser les actions suivantes :

  • Création d'un Namespace dédié,
  • Mise en place d'un volume persistant et de son attribution ( claim ) à MongoDB,
  • Le service,
  • Le déploiement du container.

Je mets bien évidement les fichiers sur github, le lien se trouvera en bas de cet article. Je ne vais détailler ici que le déploiement du conteneur :

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: rocketchat-mongo
  namespace: rocketchat-mongodb
spec:
  selector:
    matchLabels:
      app: rocketchat-mongo
  serviceName: "rocketchat-mongo"
  replicas: 1
  template:
    metadata:
      labels:
        app: rocketchat-mongo
    spec:
      terminationGracePeriodSeconds: 10
      containers:
      - name: mongodb
        image: mongo:4
        imagePullPolicy: Always
        ports:
          - containerPort: 27017
        command: ["mongod"]
        args: ["--oplogSize","128","--replSet","rs0","--bind_ip_all"]
        volumeMounts:
        - name: mongo-persistent-storage
          mountPath: /data/db          
      volumes:
        - name: mongo-persistent-storage
          persistentVolumeClaim:
             claimName: mongo-persistent-storage-claim

Finalement la configuration du conteneur ressemble fortement à Docker ( encore heureusement, ceux sont des conteneurs quand même ! 😉  ).

À noter que sur Kubernetes je dois passer l'option --bind_ip_all à MongoDB sinon celui-ci n'écoute que sur l'interface locale.

La seule spécificité provient de l'utilisation d'une ressource Stateful plutôt qu'un déploiement :

Contrairement à un déploiement, un StatefulSet conserve une identité unique pour chacun de ses pods. Ces pods sont créés à partir de la même spécification, mais ne sont pas interchangeables : chacun a un identifiant persistant qu'il conserve à travers toute replanification.

Je ne compte pas créer de Replica, j'aurais donc pu passer par un déploiement classique mais pour une base de données distribuées l'utilisation de cette ressource est préférable. Autant prendre une bonne habitude tout de suite même si ce n'est pas nécessaire !

Enfin je vais utiliser la même astuce pour réaliser l’initialisation du réplica :

apiVersion: batch/v1
kind: Job
metadata:
  name: mongo-replica
  namespace: rocketchat-mongodb

spec:
  template:
    metadata:
      name: mongo-replica
    spec:
      containers:
      - name: mongo-replica
        image: mongo:4
        command: ["bash"]
        args: ["-c", "mongo rocketchat-mongo-service.rocketchat-mongodb/rocketchat --eval \"printjson(rs.initiate())\""]
      restartPolicy: Never

Vos conteneurs MongoDB sont prêts, aussi bien sur Docker que Kubernetes ! Attaquons-nous maintenant à Rocket.Chat !

Rocket.Chat

Comme pour MongoDB, commençons par Docker !

  1. Lancement de Rocket.Chat avec Docker

On utilise des conteneurs alors rien de complexe non ? 😂

  rocket:
    image: rocket.chat:3
    depends_on:
      - mongo
    container_name: rocket
    restart: unless-stopped
    environment:
      - PORT=3000
      - ROOT_URL=https://rocket.mydomain.com
      - MONGO_URL=mongodb://mongo:27017/rocketchat
      - MONGO_OPLOG_URL=mongodb://mongo:27017/local?replSet=rs0
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=traefik"
      - "traefik.http.services.myrocketservice.loadbalancer.server.port=3000"
      - "traefik.http.routers.myrocketrouter.rule=Host(`rocket.mydomain.com`)"
      - "traefik.http.routers.myrocketrouter.entrypoints=websecure"
    networks:
      - lan
      - traefik

Rocket.Chat utilise le port 3000 par défaut, je vais garder cette configuration puisque je vais utiliser Traefik comme reverse-proxy. Je mets d'ailleurs en place les labels afin de créer le routeur pour mon application :

      - "traefik.http.services.myrocketservice.loadbalancer.server.port=3000"
      - "traefik.http.routers.myrocketrouter.rule=Host(`rocket.mydomain.com`)"
      - "traefik.http.routers.myrocketrouter.entrypoints=websecure"

J'utilise un entrypoint websecure, qui sera sur le port 443 et permettra le HTTPS avec un certificat délivré par let's encrypt.

Je précise également l'emplacement de la base de données et de l'OPLOG :

      - MONGO_URL=mongodb://mongo:27017/rocketchat
      - MONGO_OPLOG_URL=mongodb://mongo:27017/local?replSet=rs0

En lançant mes containeurs avec docker-compose up, j'obtiens le retour suivant :

Rocket.Chat start

Maintenant regardons la configuration avec Kubernetes.

2.  Lancement de Rocket.Chat avec Kubernetes

Comme pour MongoDB, j'ai séparé au maximum les ressources dans des fichiers distincts :

Rocket folders

Je vais également :

  • Créer un Namespace dédié,
  • Le service,
  • Le déploiement du container.

Le Ingress servira pour router mon application sur Traefik. Nous pourrons le détailler juste après. Regardons d'abord la déclaration du conteneur Rocket.Chat :

apiVersion: apps/v1
kind: Deployment
metadata:
  name: rocketchat-server-deploy
  namespace: rocketchat-server
  labels:
    app: rocketchat-server

spec:
  replicas: 1
  selector:
    matchLabels:
      app: rocketchat-server
  template:
    metadata:
      labels:
        app: rocketchat-server
    spec:
      containers:
      - name: rocketchat-server
        image: rocket.chat:3
        env:
          - name: PORT
            value: "3000"
          - name: ROOT_URL
            value: "https://rocket.dubarbu.fr:3000"
          - name: MONGO_URL
            value: "mongodb://rocketchat-mongo-service.rocketchat-mongodb:27017/rocketchat" 
          - name: MONGO_OPLOG_URL
            value: "mongodb://rocketchat-mongo-service.rocketchat-mongodb:27017/local?replSet=rs0"
        ports:
        - containerPort: 3000

Comme pour Docker, Rocket.Chat utilisant le port 3000 par défaut, je vais garder cette configuration puisque je vais utiliser Traefik comme reverse-proxy. On précise également l'emplacement de la base de données et de l'OPLOG.

La configuration de Traefik va se faire au travers d'une route ingress que je définis comme ceci :

apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: ingressroutetls
  namespace: rocketchat-server
spec:
  entryPoints:
    - websecure
  routes:
  - match: Host(`rocket.dubarbu.fr`)
    kind: Rule
    services:
    - name: rocketchat-server-service
      port: 3000

J'utilise également un entrypoint websecure, qui va nous permettre le HTTPS avec un certificat délivré par let's encrypt.

Tout comme pour Docker, le lancement peut se faire avec kubectl apply -f rocket. Si vous n'avez pas lancé Traefik vous allez avoir une erreur avec la route. Aucun soucis vous pouvez l'appliquer par la suite.

Et si justement on s'occupait de notre reverse-proxy !?

Traefik 2.2

  1. Lancement de Traefik avec Docker

J'ai décidé d'utiliser le challenge DNS. Aucun intérêt il s'agit purement de vous montrer comment l'utiliser en cas réel avec Docker et Kubernetes. Voici mon fichier docker-compose.yml suivant :

version: '3.7'
services:
    traefik:
        image: traefik:2.2
        container_name: traefik
        restart: unless-stopped
        ports:
            - "80:80"
            - "443:443"
        environment:
            - "TZ=Europe/Paris"
            - "OVH_ENDPOINT=ovh-eu"
            - "OVH_APPLICATION_KEY=XXXXXXX"
            - "OVH_APPLICATION_SECRET=XXXXXXXXXX"
            - "OVH_CONSUMER_KEY=XXXXXXXXXXXXXXXXXXXXXXXX"
        volumes:
            - /var/run/docker.sock:/var/run/docker.sock
            - ./traefik.yml:/etc/traefik/traefik.yml:ro
            - traefik_ssl:/letsencrypt
        networks:
            - traefik

volumes:
    traefik_ssl:
        name: traefik_ssl

networks:
    traefik:
        name: traefik

et le fichier traefik.yml pour la configuration statique :

entryPoints:
  web:
    address: :80
    http:
      redirections:
        entryPoint:
          to: websecure
          scheme: https

  websecure:
    address: :443
    http:
      tls:
        certResolver: myresolver
 
providers:
  docker:
    exposedByDefault: false

certificatesResolvers:
  caServer: "https://acme-staging-v02.api.letsencrypt.org/directory"
  myresolver:
    acme:
      email: "foo.bar@domain.com"
      storage: "/letsencrypt/acme.json"
      dnsChallenge:
        provider: ovh
        delayBeforeCheck: 10

Aussi bien pour Docker que pour Kubernetes, si vous suivez régulièrement le blog, rien de nouveau ! Si ce n'est pas le cas, je vous invite à passer très souvent pour avoir un maximum d'information sur Traefik 😍

2.  Lancement de Traefik avec Kubernetes

Voici le fichier de déploiement pour Traefik avec l'utilisation du challenge DNS :

apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: default
  name: traefik
  labels:
    app: traefik

spec:
  replicas: 1
  selector:
    matchLabels:
      app: traefik
  template:
    metadata:
      labels:
        app: traefik
    spec:
      serviceAccountName: traefik-ingress-controller
      containers:
        - name: traefik
          image: traefik:v2.2
          imagePullPolicy: Always
          env:
            - name: OVH_ENDPOINT
              value: ovh-eu
            - name: OVH_APPLICATION_KEY
              value: XXXXXXX
            - name: OVH_APPLICATION_SECRET
              value: XXXXXXXXXX
            - name: OVH_CONSUMER_KEY
              value: XXXXXXXXXXXXXXXXXXXXXXXX
          volumeMounts:
            - name: traefik-config-static
              mountPath: /etc/traefik/
            - name: letsencrypt 
              mountPath: "/letsencrypt/"
      volumes:
        - name: traefik-config-static
          configMap:
            name: traefik-config-static
        - name: letsencrypt
          persistentVolumeClaim:
             claimName: traefik-letsencrypt-pvc

Je déclare donc les mêmes variables d'environnement dans les deux cas. Voici le fichier ConfigMap avec la configuration statique de Traefik :

apiVersion: v1
kind: ConfigMap
metadata:
  name: traefik-config-static
  namespace: default
  labels:
    app: traefik
data:
  traefik.yml: |
    entryPoints:
      web:
        address: :80
        http:
          redirections:
            entryPoint:
              to: websecure
              scheme: https

      websecure:
        address: :443
        http:
          tls:
            certResolver: myresolver

    providers:
      kubernetesCRD: {} 

    certificatesResolvers:
      caServer: "https://acme-staging-v02.api.letsencrypt.org/directory"
      myresolver:
        acme:
          email: "foo.bar@domain.com"
          storage: "/letsencrypt/acme.json"
          dnsChallenge: 
            provider: ovh
            delayBeforeCheck: 10

Je ne vais pas détailler les autres fichiers qui servent à initialiser Traefik et que j'ai déjà pu détailler au cours de précédents articles :

Nous voila prêts pour lancer notre fusée...

Go 🚀

Pour les plus impatients, ou les plus pragmatiques, voici comment réaliser cette installation en quelques secondes.

Avec Docker :

Récupérer le dépôt git avec les fichiers docker-compose :

git clone https://github.com/lfache/dockerfiles

Allez dans le dossier Rocket.Chat :

cd dockerfiles/Rocket.Chat

🚩 Pensez à modifier les variables d'environnement concernant le challenge DNS ou passez en TLS challenge.

Modifiez également les variables pour votre nom de domaine. 🚩

Enfin lancez les conteneurs, commençons avec Traefik :

cd traefik 
docker-compose up -d

Puis MongoDB :

cd ../mongodb 
docker-compose up -d

Enfin Rocket.Chat :

cd ../rocket 
docker-compose up -d

Et en vidéo, ça donne :

Avec kubernetes :

Récupérer le dépôt git avec les fichiers yaml :

git clone https://github.com/lfache/kubernetes

Allez dans le dossier Rocket.Chat :

cd kubernetes/Rocket.Chat

🚩 Pensez à modifier les variables d'environnement concernant le challenge DNS ( adresse e-mail par exemple ) ou passez en TLS challenge.

Modifiez également les variables pour votre nom de domaine et l'emplacement des volumes persistants 🚩

Enfin lancez les conteneurs, commençons avec Traefik :

kubectl apply -f traefik/

Puis MongoDB :

kubectl apply -f mongodb/Namespaces.yaml
kubectl apply -f mongodb/
kubectl apply -f mongodb_replica/

Et enfin Rocket.Chat !

kubectl apply -f rocket/Namespaces.yaml
kubectl apply -f rocket/

Et en vidéo également :

Ces deux vidéos vous permettent également de constater que tout fonctionne aussi bien sur Docker que Kubernetes avec l’utilisation de Docker Windows et WSL2 !


Nous  venons donc de voir ensemble comment installer Rocket.Chat en quelques minutes à l'aide de technologie de conteneurisation. Que vous utilisiez Docker ou Kubernetes, vous voila prêts pour installer cet outil collaboratif !

Cela nous a également permis de revoir, toujours et encore, notre reverse-proxy favori : Traefik ! Tout est tellement plus simple lorsque l'on souhaite router du flux HTTP(S) avec cet outil... Comment s'en passer 😂

Bien évidement la solution que je propose ici n'est certainement pas la seule façon d'installer Rocket.Chat. Il existe par exemple un template Helm pour l'installer facilement sur Kubernetes. Mais je trouve qu'il est préférable de connaître un minimum l'outil qu'on souhaite installer avant d'en automatiser son installation !

D’où cette installation plus manuelle afin d'en découvrir - un peu - le fonctionnement !

Cet article - double tutoriel - vous a t-il plu ? Souhaitez vous voir ce type d'installation séparée dans deux articles différents ou cela vous permet-il de comparer l'utilisation des deux solutions ?

En tout cas  n'hésitez pas à m'apporter des remarques ou des commentaires sur Twitter ! C'est toujours un plaisir d'avoir des retours ! 😇