One place for hosting & domains

      sécuriser

      Comment dimensionner et sécuriser une application Django avec Docker, Nginx et Let’s Encrypt


      Introduction

      Dans les environnements basés sur le cloud, il existe de multiples façons de faire évoluer et de sécuriser une application Django. En dimensionnant horizontalement et en exécutant plusieurs copies de votre application, vous pouvez construire un système plus tolérant aux défauts et très disponible, tout en augmentant également son débit, afin que les demandes puissent être traitées simultanément. Une manière de faire évoluer à l’échelle horizontale une application Django consiste à fournir des serveurs d’application supplémentaires qui exécutent votre application Django et son serveur HTTP WSGI (comme Gunicorn ou uWSGI). Pour acheminer et distribuer les demandes entrantes sur cet ensemble de serveurs d’application, vous pouvez utiliser un équilibreur de charge et un proxy inverse comme Nginx. Nginx peut également mettre en cache le contenu statique et mettre fin aux connexions TLS (Transport Layer Security Security), utilisées pour fournir des connexions HTTPS, et sécuriser les connexions à votre application.

      L’exécution de votre application Django et du proxy Nginx à l’intérieur des conteneurs Docker garantit que ces composants se comportent de la même manière quel que soit l’environnement dans lequel ils sont déployés. De plus, les conteneurs fournissent de nombreuses fonctionnalités qui facilitent l’emballage et la configuration de votre application.

      Dans ce tutoriel, vous allez à mettre l’échelle horizontalement une application de sondages pour Django et Gunicorn conteneurisé en fournissant deux serveurs d’application qui exécuteront chacune une copie d’un conteneur d’application Django et Gunicorn.

      Vous allez également activer HTTPS, en fournissant et en configurant un troisième serveur proxy qui exécutera un conteneur proxy inverse Nginx et un conteneur client Certbot. Certbot fournira des certificats TLS pour Nginx à partir de l’autorité de certification Let’s Encrypt. Cela garantira que votre site bénéficie d’une cote de sécurité élevée de la part de SSL Labs. Ce serveur proxy recevra toutes les demandes externes de votre application et sera en face des deux serveurs d’application Django en amont. Enfin, vous allez renforcer ce système distribué en limitant l’accès externe au serveur proxy uniquement.

      Conditions préalables

      Pour suivre ce tutoriel, vous aurez besoin de :

      • Trois serveurs Ubuntu 18.04 :

        • Deux serveurs seront des serveurs d’application, utilisés pour exécuter vos applications Django et Gunicorn.
        • Un serveur sera un serveur proxy utilisé pour exécuter Nginx et Certbot.
        • Tous devraient avoir un non-root user avec des privilèges sudo et un pare-feu actif. Pour savoir comment les configurer, veuillez consulter le présent Guide de configuration initiale du serveur.
      • Docker installé sur les trois serveurs. Pour obtenir de l’aide sur l’installation de Docker, suivez les Étapes 1 et 2 de Comment installer et utiliser Docker sur Ubuntu 18.04.

      • Un nom de domaine enregistré. Ce tutoriel utilisera your_domain.comtout au long. Vous pouvez en obtenir un gratuitement sur Freenom ou utiliser le registre de domaine de votre choix.

      • Un enregistrement DNS A avec your_domain.com pointant sur l’adresse IP publique de votre serveur proxy. Vous pouvez suivre cette introduction au DNS DigitalOcean pour plus de détails sur la façon de l’ajouter à un compte DigitalOcean, si c’est ce que vous utilisez.

      • Un object storage bucket S3 comme un espace DigitalOceanpour stocker les fichiers statiques de votre projet Django et un ensemble de clés d’accès pour cet espace. Pour apprendre à créer un espace, consultez la documentation produit Comment créer des espaces. Pour apprendre à créer des clés d’accès pour les espaces, consultez Partager l’accès aux espaces avec les clés d’accès. Avec des modifications mineures, vous pouvez utiliser n’importe quel service de stockage d’objets que le plugin django-storages prend en charge.

      • Une instance serveur PostgreSQL, une base de données et un utilisateur pour votre application Django. Avec des modifications mineures, vous pouvez utiliser n’importe quelle base de données que Django prend en charge.

      Étape 1 — Configuration du premier serveur d’application Django

      Pour commencer, nous allons cloner le référentiel d’application Django sur le premier serveur d’application. Ensuite, nous allons configurer et construire l’image de l’application Docker, et tester l’application en exécutant le conteneur Django.

      Remarque : si vous continuez en suivant Comment construire une application Django et Gunicorn avec Docker, vous aurez déjà terminé Étape 1 et pouvez passer à l’étape 2 pour configurer le deuxième serveur d’application.

      Commencez par vous connecter au premier des deux serveurs d’application Django et utilisez git pour cloner la branche polls-docker du référentiel GitHub du tutoriel des applications Django Polls. Ce référentiel contient le code pour l’application de sondages dans la documentation de Django. La branche de polls-docker contient une version Dockerisée de l’application de sondage. Pour savoir comment l’application de sondage a été modifiée pour fonctionner efficacement dans un environnement conteneurisé, consultez Comment construire une application Django et Gunicorn avec Docker.

      • git clone --single-branch --branch polls-docker https://github.com/do-community/django-polls.git

      Naviguez dans le répertoire django-polls :

      cd django-polls
      

      Ce répertoire contient le code Python de l’application Django, un Dockerfile que Docker utilisera pour construire l’image du conteneur, ainsi qu’un fichier env contenant une liste de variables d’environnement à passer dans l’environnement d’exécution du conteneur. Inspectez le Dockerfile à l’aide de cat :

      cat Dockerfile
      

      Output

      FROM python:3.7.4-alpine3.10 ADD django-polls/requirements.txt /app/requirements.txt RUN set -ex && apk add --no-cache --virtual .build-deps postgresql-dev build-base && python -m venv /env && /env/bin/pip install --upgrade pip && /env/bin/pip install --no-cache-dir -r /app/requirements.txt && runDeps="$(scanelf --needed --nobanner --recursive /env | awk '{ gsub(/,/, "nso:", $2); print "so:" $2 }' | sort -u | xargs -r apk info --installed | sort -u)" && apk add --virtual rundeps $runDeps && apk del .build-deps ADD django-polls /app WORKDIR /app ENV VIRTUAL_ENV /env ENV PATH /env/bin:$PATH EXPOSE 8000 CMD ["gunicorn", "--bind", ":8000", "--workers", "3", "mysite.wsgi"]

      Ce Dockerfile utilise l’image Docker officielle de Python 3.7.4 comme base, et installe les exigences du paquet Python de Django et Gunicorn, telles que définies dans le fichier django-polls/requirements.txt. Il supprime ensuite quelques fichiers de construction inutiles, copie le code de l’application dans l’image, et définit le PATH d’exécution. Enfin, il déclare que le port 8000 sera utilisé pour accepter les connexions de conteneurs entrantes, et exécute gunicorn avec 3 travailleurs, en écoutant sur le port 8000.

      Pour en savoir plus sur chacune des étapes de ce Dockerfile, consultez l’Étape 6 de Comment construire une application Django et Gunicorn avec Docker.

      Maintenant, construisez l’image à l’aide de docker build :

      Nous nommons l’image polls en utilisant le drapeau -t et passons dans le répertoire courant comme contexte de construction, l’ensemble de fichiers à faire référence lors de la construction de l’image.

      Après que Docker ait construit et étiqueté l’image, listez les images disponibles à l’aide de docker images :

      docker images
      

      Vous devriez voir l’image polls listée :

      Output

      REPOSITORY TAG IMAGE ID CREATED SIZE polls latest 80ec4f33aae1 2 weeks ago 197MB python 3.7.4-alpine3.10 f309434dea3a 8 months ago 98.7MB

      Avant de lancer le conteneur Django, nous devons configurer son environnement d’exécution à l’aide du fichier env présent dans le répertoire actuel. Ce fichier sera transmis dans la commande docker run utilisée pour exécuter le conteneur, et Docker injectera les variables d’environnement configurées dans l’environnement d’exécution du conteneur.

      Ouvrez le fichier env avec nano ou votre éditeur préféré :

      nano env
      

      Nous allons configurer le fichier comme ceci, et vous devrez ajouter quelques valeurs supplémentaires comme indiqué ci-dessous.

      django-polls/env

      DJANGO_SECRET_KEY=
      DEBUG=True
      DJANGO_ALLOWED_HOSTS=
      DATABASE_ENGINE=postgresql_psycopg2
      DATABASE_NAME=polls
      DATABASE_USERNAME=
      DATABASE_PASSWORD=
      DATABASE_HOST=
      DATABASE_PORT=
      STATIC_ACCESS_KEY_ID=
      STATIC_SECRET_KEY=
      STATIC_BUCKET_NAME=
      STATIC_ENDPOINT_URL=
      DJANGO_LOGLEVEL=info
      

      Remplissez les valeurs manquantes pour les clés suivantes :

      • DJANGO_SECRET_KEY : définissez cette valeur à une valeur unique et imprévisible, comme indiqué dans les docs de Django. Une méthode de génération de cette clé est fournie dans Ajustement des paramètres du tutoriel sur les applications Django dimensionnables.
      • DJANGO_ALLOWED_HOSTS: : cette variable sécurise l’application et prévient les attaques d’en-tête d’hôte HTTP. Pour les besoins de test, définissez cette variable à *, un joker qui correspondra à tous les hôtes. En production, vous devriez la définir sur your_domain.com. Pour en savoir plus sur ce paramètre Django, consultez les paramètres de base dans les docs Django.
      • DATABASE_USERNAME : définissez ce paramètre sur l’utilisateur de la base de données PostgreSQL créé dans les étapes préalables.
      • DATABASE_NAME : définissez ce paramètres sur polls ou le nom de la base de données PostgreSQL créée dans les étapes préalables.
      • DATABASE_PASSWORD : définissez ce paramètre sur le mot de passe de l’utilisateur PostgreSQL créé dans les étapes préalables.
      • DATABASE_HOST : définissez ce paramètre sur le nom d’hôte de votre base de données.
      • DATABASE_PORT : définissez ce paramètre sur le port de votre base de données.
      • STATIC_ACCESS_KEY_ID : définissez ce paramètre sur votre bucket S3 ou sur la clé d’accès à l’espace.
      • STATIC_ACCESS_KEY_ID : définissez ce paramètre sur votre bucket S3 ou sur la clé secrète d’accès à l’espace.
      • STATIC_BUCKET_NAME : définissez ce paramètre sur votre bucket S3 ou votre nom d’espace.
      • STATIC_ENDPOINT_URL : définissez ce paramètre sur le bucket S3 approprié ou l’URL de votre espace, par exemple https://space-name.nyc3.digitaloceanspaces.com si votre espace se trouve dans la région nyc3.

      Une fois que vous avez terminé de le modifier, enregistrez et fermez le fichier.

      Nous allons maintenant utiliser docker run pour remplacer le paramètre CMD dans le Dockerfile et créer le schéma de base de données à l’aide des commandes manage.py et manage.py migrate :

      docker run --env-file env polls sh -c "python manage.py makemigrations && python manage.py migrate"
      

      Nous lançons le container d’images polls:latest, nous passons dans le fichier variable d’environnement que nous venons de modifier, et remplacons la commande Dockerfile par sh -c "python manage.py makemigrations python manage.py image", qui créera le schéma de base de données défini par le code de l’application. Si vous exécutez cette opération pour la première fois, vous devriez voir ce qui suit :

      Output

      No changes detected Operations to perform: Apply all migrations: admin, auth, contenttypes, polls, sessions Running migrations: Applying contenttypes.0001_initial... OK Applying auth.0001_initial... OK Applying admin.0001_initial... OK Applying admin.0002_logentry_remove_auto_add... OK Applying admin.0003_logentry_add_action_flag_choices... OK Applying contenttypes.0002_remove_content_type_name... OK Applying auth.0002_alter_permission_name_max_length... OK Applying auth.0003_alter_user_email_max_length... OK Applying auth.0004_alter_user_username_opts... OK Applying auth.0005_alter_user_last_login_null... OK Applying auth.0006_require_contenttypes_0002... OK Applying auth.0007_alter_validators_add_error_messages... OK Applying auth.0008_alter_user_username_max_length... OK Applying auth.0009_alter_user_last_name_max_length... OK Applying auth.0010_alter_group_name_max_length... OK Applying auth.0011_update_proxy_permissions... OK Applying polls.0001_initial... OK Applying sessions.0001_initial... OK

      Cela indique que le schéma de base de données a été créé avec succès.

      Si vous exécutez migrate une, fois de plus, Django effectuera un no-op à moins que le schéma de base de données ait changé.

      Ensuite, nous allons exécuter une autre instance du conteneur de l’application et utiliser un shell interactif à l’intérieur de celui-ci pour créer un utilisateur administratif pour le projet Django.

      docker run -i -t --env-file env polls sh
      

      Vous obtiendrez une invite shell à l’intérieur du conteneur en cours d’exécution que vous pouvez utiliser pour créer l’utilisateur Django :

      python manage.py createsuperuser
      

      Entrez un nom d’utilisateur, une adresse email et un mot de passe pour votre utilisateur, et après avoir créé l’utilisateur, appuyez sur CTRL+D pour quitter le conteneur et le fermer.

      Enfin, nous allons générer les fichiers statiques pour l’application et les télécharger sur l’espace DigitalOcean à l’aide de collectstatic. Notez que cela peut prendre un peu de temps.

      docker run --env-file env polls sh -c "python manage.py collectstatic --noinput"
      

      Une fois que ces fichiers sont générés et téléchargés, vous obtiendrez la sortie suivante.

      Output

      121 static files copied.

      Nous pouvons maintenant exécuter l’application :

      docker run --env-file env -p 80:8000 polls
      

      Output

      [2019-10-17 21:23:36 +0000] [1] [INFO] Starting gunicorn 19.9.0 [2019-10-17 21:23:36 +0000] [1] [INFO] Listening at: http://0.0.0.0:8000 (1) [2019-10-17 21:23:36 +0000] [1] [INFO] Using worker: sync [2019-10-17 21:23:36 +0000] [7] [INFO] Booting worker with pid: 7 [2019-10-17 21:23:36 +0000] [8] [INFO] Booting worker with pid: 8 [2019-10-17 21:23:36 +0000] [9] [INFO] Booting worker with pid: 9

      Ici, nous exécutons la commande par défaut définie dans le Dockerfile, gunicorn --bind :8000 --workers 3 mysite.wsgi:application, et exposons le port de conteneur 8000 afin que le port 80 sur le serveur Ubuntu soit mappé sur le port 8000 du conteneur polls.

      Vous devriez maintenant pouvoir naviguez jusqu’à l’application de sondages à l’aide de votre navigateur web en tapant : http://APP_SERVER_1_IP dans la barre d’URL. Comme il n’y a pas de route définie pour le chemin d’accès / , vous obtiendrez probablement une erreur de recherche 404 Page Not Found, qui est prévisible.

      Attention : Lorsque vous utilisez le pare-feu UFW avec Docker, Docker contourne les règles de pare-feu UFW, comme indiqué dans ce numéro de GitHub. Cela explique pourquoi vous avez accès au port 80 de votre serveur, même si vous n’avez pas explicitement créé de règle d’accès UFW dans aucune étape préalable. Dans l’Étape 5, nous aborderons cette faille de sécurité en corrigeant la configuration d’UFW. Si vous n’utilisez pas UFW et que vous utilisez les pare-feu Cloud de DigitalOcean, vous pouvez ignorer cet avertissement.

      Naviguez sur http://APP_SERVER_1_IP/polls pour voir l’interface de l’application de sondage :

      Interface des applications de sondage

      Pour voir l’interface administrative, allez à http://APP_SERVER_1_IP/admin. Vous devriez voir la fenêtre d’authentification de l’application de sondage :

      Page Auth admin des sondages

      Entrez le nom d’utilisateur administratif et le mot de passe que vous avez créé avec la commande createsuperuser.

      Après avoir été authentifié, vous pouvez accéder à l’interface administrative de l’application de sondage :

      Interface principale de l'administration de sondages

      Notez que les actifs statiques pour les applications d’administration et de sondage sont livrées directement depuis le stockage d’objets. Pour confirmer ceci, consultez Testing Spaces Static File Delivery.

      Lorsque vous avez terminé d’explorer, appuyez sur CTRL+C dans la fenêtre de terminal en exécutant le conteneur de Docker pour terminer le conteneur.

      Maintenant que vous avez confirmé que le conteneur d’application fonctionne comme prévu, vous pouvez l’exécuter en mode détaché, qui l’exécutera en arrière-plan et vous permettra de vous déconnecter de votre session SSH :

      docker run -d --rm --name polls --env-file env -p 80:8000 polls
      

      Le drapeau -d demande à Docker d’exécuter le conteneur en mode détaché, le drapeau -rm nettoie le système de fichiers du conteneur après la fermeture du conteneur, et nous donnons un nom aux sondages de conteneur.

      Déconnectez-vous du premier serveur d’application Django, et naviguez vers http://APP_SERVER_1_IP/polls pour confirmer que le conteneur fonctionne comme prévu.

      Maintenant que votre premier serveur d’application Django est opérationnel, vous pouvez configurer votre deuxième serveur d’application Django.

      Étape 2 — Configuration du deuxième serveur d’application Django

      Comme beaucoup de commandes pour configurer ce serveur seront les mêmes que celles de l’étape précédente, elles seront présentées ici sous forme abrégée. Veuillez consulter l’Étape 1 pour plus d’informations sur une commande particulière de cette étape.

      Commencez par vous connecter au deuxième serveur d’application Django.

      Clonez la branche polls-docker du référentiel GitHub django-polls :

      • git clone --single-branch --branch polls-docker https://github.com/do-community/django-polls.git

      Naviguez dans le répertoire django-polls :

      cd django-polls
      

      Maintenant, construisez l’image à l’aide de docker build :

      Ouvrez le fichier env avec nano ou votre éditeur préféré :

      nano env
      

      django-polls/env

      DJANGO_SECRET_KEY=
      DEBUG=True
      DJANGO_ALLOWED_HOSTS=
      DATABASE_ENGINE=postgresql_psycopg2
      DATABASE_NAME=polls
      DATABASE_USERNAME=
      DATABASE_PASSWORD=
      DATABASE_HOST=
      DATABASE_PORT=
      STATIC_ACCESS_KEY_ID=
      STATIC_SECRET_KEY=
      STATIC_BUCKET_NAME=
      STATIC_ENDPOINT_URL=
      DJANGO_LOGLEVEL=info
      

      Remplissez les valeurs manquantes comme à l’Étape 1. Lorsque vous avez terminé les modifications, enregistrez et fermez le fichier.

      Enfin, exécutez le conteneur d’application en mode détaché :

      docker run -d --rm --name polls --env-file env -p 80:8000 polls
      

      Naviguez jusqu’à http://APP_SERVER_2_IP/polls pour confirmer que le conteneur fonctionne comme prévu. Vous pouvez vous déconnecter en toute sécurité du deuxième serveur d’application sans mettre fin à votre conteneur en cours d’exécution.

      Une fois les conteneurs d’application Django opérationnels, vous pouvez passer à la configuration du conteneur proxy inverse Nginx.

      Étape 3 — Configuration du conteneur Docker Nginx

      Nginx est un serveur web polyvalent qui offre un certain nombre de fonctionnalités, dont le proxy inverse, l’équilibrage de la charge et la mise en cache. Dans ce tutoriel, nous avons déchargé les ressources statiques de Django pour le stockage d’objets, nous n’utiliserons donc pas les capacités de mise en cache de Nginx. Cependant, nous utiliserons Nginx comme proxy inverse sur nos deux serveurs d’application Django de backend et distribuerons les demandes entrantes entre eux. En outre, Nginx effectuera la terminaison et la redirection TLS à l’aide d’un certificat TLS fourni par Certbot. Cela signifie qu’il forcera les clients à utiliser HTTPS, redirigeant les requêtes HTTPS vers le port 443. Il déchiffrera ensuite les requêtes HTTPS, puis les enverra par proxy aux serveurs Django en amont.

      Dans ce tutoriel, nous avons pris la décision de découpler les conteneurs Nginx des serveurs de backend. En fonction de votre cas d’utilisation, vous pouvez choisir d’exécuter le conteneur Nginx sur l’un des serveurs d’application Django, en envoyant les requêtes par proxy localement et ainsi que vers l’autre serveur Django. Une autre architecture possible serait d’exécuter deux conteneurs Nginx, un sur chaque serveur de backend avec un load balancer en cloud en avant. Chaque architecture présente différents avantages de sécurité et de performance, et vous devriez tester la charge de votre système pour découvrir les goulets d’étranglement. L’architecture flexible décrite dans ce tutoriel vous permet d’évaluer à la fois la couche d’application Django de backend et la couche proxy Nginx. Une fois que le conteneur Nginx unique devient un goulot d’étranglement, vous pouvez échelonner sur plusieurs proxies Nginx, et ajouter un load balancer en cloud ou un load balancer L4 rapide comme HAProxy.

      Une fois les deux serveurs d’applications Django en place, nous pouvons commencer à configurer le serveur proxy Nginx. Connectez-vous à votre serveur proxy et créez un répertoire appelé conf :

      mkdir conf
      

      Créez un fichier de configuration appelé nginx.conf à l’aide de nano ou de votre éditeur préféré :

      nano conf/nginx.conf
      

      Collez dans la configuration Nginx suivante :

      conf/nginx.conf

      
      upstream django {
          server APP_SERVER_1_IP;
          server APP_SERVER_2_IP;
      }
      
      server {
          listen 80 default_server;
          return 444;
      }
      
      server {
          listen 80;
          listen [::]:80;
          server_name your_domain.com;
          return 301 https://$server_name$request_uri;
      }
      
      server {
          listen 443 ssl http2;
          listen [::]:443 ssl http2;
          server_name your_domain.com;
      
          # SSL
          ssl_certificate /etc/letsencrypt/live/your_domain.com/fullchain.pem;
          ssl_certificate_key /etc/letsencrypt/live/your_domain.com/privkey.pem;
      
          ssl_session_cache shared:le_nginx_SSL:10m;
          ssl_session_timeout 1440m;
          ssl_session_tickets off;
      
          ssl_protocols TLSv1.2 TLSv1.3;
          ssl_prefer_server_ciphers off;
      
          ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384";
      
          client_max_body_size 4G;
          keepalive_timeout 5;
      
              location / {
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Forwarded-Proto $scheme;
                proxy_set_header Host $http_host;
                proxy_redirect off;
                proxy_pass http://django;
              }
      
          location ^~ /.well-known/acme-challenge/ {
              root /var/www/html;
          }
      
      }
      

      Ces blocs upstream, server et location configurent Nginx pour rediriger les requêtes HTTP vers HTTPS, et et équilibrent la charge entre les deux serveurs d’applications Django configurés aux Étapes 1 et 2. Pour en savoir plus sur la structure du fichier de configuration Nginx, consultez cet article sur la compréhension de la structure du fichier de configuration Nginx et les contextes de configuration. En outre, cet article sur La compréhension des algorithmes de sélection des serveurs Nginx et des blocs de localisation peut être utile.

      Cette configuration a été assemblée à partir d’exemplesde fichiers de configuration fournis par Gunicorn, Cerbot et Nginx et est conçue comme une configuration Nginx minimale pour que cette architecture soit opérationnelle. L’adaptation de cette configuration Nginx dépasse la portée de cet article, mais vous pouvez utiliser un outil comme NGINXConfig pour générer des fichiers de configuration Nginx performants et sécurisés pour votre architecture.

      Le bloc upstream définit le groupe de serveurs utilisés pour les demandes de proxy à l’aide de la directive proxy_pass :

      conf/nginx.conf

      upstream django {
          server APP_SERVER_1_IP;
          server APP_SERVER_2_IP;
      }
      . . .
      

      Dans ce bloc, nous nommons le upstream django et incluons les adresses IP des deux serveurs d’application Django. Si les serveurs d’application fonctionnent sur DigitalOcean et que le réseau VPC est activé, vous devriez utiliser leurs adresses IP privées ici. Pour apprendre à activer le VPC Networking sur DigitalOcean, consultez Comment activer le VPC Networking sur les droplets existants.

      Le premier bloc server capture les demandes qui ne correspondent pas à votre domaine et met fin à la connexion. Par exemple, une requête HTTP directe vers l’adresse IP de votre serveur serait traitée par ce bloc :

      conf/nginx.conf

      . . .
      server {
          listen 80 default_server;
          return 444;
      }
      . . .
      

      Le bloc server suivant redirige les requêtes HTTP, vers votre domaine, à l’aide d’un redirect HTTP 301 redirect. Ces demandes sont ensuite traitées par le bloc server final :

      conf/nginx.conf

      . . .
      server {
          listen 80;
          listen [::]:80;
          server_name your_domain.com;
          return 301 https://$server_name$request_uri;
      }
      . . .
      

      Ces deux directives définissent les chemins d’accès au certificat TLS et à la clé secrète. Ceux-ci seront fournis à l’aide de Certbot et montés dans le conteneur Nginx à l’étape suivante.

      conf/nginx.conf

      . . .
      ssl_certificate /etc/letsencrypt/live/your_domain.com/fullchain.pem;
      ssl_certificate_key /etc/letsencrypt/live/your_domain.com/privkey.pem;
      . . .
      

      Ces paramètres sont les valeurs par défaut de la sécurité SSL recommandées par Certbot. Pour en savoir plus sur ces questions, consultez le Module ngx_http_ssl_module dans les docs Nginx. Security/Server Side TLS de Mozilla est un autre guide utile que vous pouvez utiliser pour ajuster la configuration SSL.

      conf/nginx.conf

      . . .
          ssl_session_cache shared:le_nginx_SSL:10m;
          ssl_session_timeout 1440m;
          ssl_session_tickets off;
      
          ssl_protocols TLSv1.2 TLSv1.3;
          ssl_prefer_server_ciphers off;
      
          ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384";
      . . .
      

      Ces deux directives d’exemple de configuration Nginx de Gunicorn définissent la taille maximale autorisée du corps de requête client et attribuent le timeout pour les connexions keep-alive avec le client. Nginx fermera les connexions avec le client après keepalive_timeout secondes.

      conf/nginx.conf

      . . .
      client_max_body_size 4G;
      keepalive_timeout 5;
      . . .
      

      Le premier bloc location permet à Nginx d’accéder aux demandes de proxy vers les serveurs upstream django sur HTTP. Il préserve également les en-têtes HTTP du client qui capturent l’adresse IP d’origine, le protocole utilisé pour la connexion et l’hôte cible :

      conf/nginx.conf

      . . .
      location / {
          proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
          proxy_set_header X-Forwarded-Proto $scheme;
          proxy_set_header Host $http_host;
          proxy_redirect off;
          proxy_pass http://django;
      }
      . . .
      

      Pour en savoir plus sur ces directives, consultez Deploying Gunicorn et Module ngx_http_proxy_module à partir des docs Deploying

      Le bloc location final capture les demandes vers le chemin /well-known/acme-challenge/ utilisé par Certbot pour les défis HTTP-01 pour vérifier votre domaine avec Let’s Encrypt et fournir ou renouveler les certificats TLS. Pour plus d’informations sur le défi HTTP-01 utilisé par Certbot, consultez les Types de défi dans les docs Let’s Encrypt.

      conf/nginx.conf

      . . .
      location ^~ /.well-known/acme-challenge/ {
              root /var/www/html;
      }
      

      Une fois que vous avez terminé de le modifier, enregistrez et fermez le fichier.

      Vous pouvez maintenant utiliser ce fichier de configuration pour exécuter un conteneur Docker Nginx. Dans ce tutoriel, nous utiliserons l’image nginx:1.19.0, version 1.19.0 de l’image Docker officielle maintenue par Nginx.

      Lorsque nous exécutons le conteneur pour la première fois, Nginx lancera une erreur et échouera car nous n’avons pas encore fourni les certificats définis dans le fichier de configuration. Cependant, nous allons toujours exécuter la commande pour télécharger l’image Nginx localement et tester que tout le reste fonctionne correctement :

      docker run --rm --name nginx -p 80:80 -p 443:443 
          -v ~/conf/nginx.conf:/etc/nginx/conf.d/nginx.conf:ro 
          -v /var/www/html:/var/www/html 
          nginx:1.19.0
      

      Ici, nous nommons le conteneur nginx et cartographions les ports hôtes 80 et 443 vers les ports conteneur respectifs. Le drapeau -v monte le fichier de configuration dans le conteneur Nginx à /etc/nginx/conf.d/nginx.conf, que l’image Nginx est préconfigurée pour charger. Il est monté en mode ro ou “read only”, de sorte que le conteneur ne peut pas modifier le fichier. Le répertoire racine web /var/www/html est également monté dans le conteneur. Enfin, nginx:1.19.0 demande à Docker de tirer et d’exécuter l’image nginx:1.19.0 depuis Dockerhub.

      Docker tirera et exécutera l’image, puis Nginx lancera une erreur lorsqu’il ne trouvera pas le certificat TLS configuré et la clé secrète. Dans la prochaine étape, nous allons les fournir à l’aide d’un client Certbot Dockerizé et de l’autorité de certification Let’s Encrypt.

      Étape 4 — Configuration de Cerbot et renouvellement du certificat Let’s Encrypt

      Certbot est un client Let’s Encrypt développé par la Electronic Frontier Foundation. Il fournit des certificats TLS gratuits depuis l’autorité de certification Let’s Encrypt qui permet aux navigateurs de vérifier l’identité de vos serveurs web. Comme Docker a été installé sur notre serveur proxy Nginx, nous utiliserons l’image Certbot Docker pour fournir et renouveler les certificats TLS.

      Commencez par vous assurer que vous disposez d’un enregistrement DNS A mappé à l’adresse IP publique du serveur proxy. Ensuite, sur votre serveur proxy, proposez une version de mise en scène des certificats à l’aide de l’image certbot Docker :

      docker run -it --rm -p 80:80 --name certbot 
               -v "/etc/letsencrypt:/etc/letsencrypt" 
               -v "/var/lib/letsencrypt:/var/lib/letsencrypt" 
               certbot/certbot certonly --standalone --staging -d your_domain.com
      

      Cette commande exécute l’image certbot Docker en mode interactif, et achemine le port 80 de l’hôte au port 80 du conteneur. Il crée et monte deux répertoires hôtes dans le conteneur : /etc/letsencrypt/ et /var/lib/letsencrypt/ certbot est exécuté en mode stand alone, sans Nginx, et utilisera les serveurs staging de Let’s Encrypt pour effectuer la validation du domaine.

      Lorsque vous y êtes invité, entrez votre adresse email et acceptez les conditions de service. Si la validation du domaine a réussi, vous devriez voir la sortie suivante :

      Output

      Obtaining a new certificate Performing the following challenges: http-01 challenge for stubb.dev Waiting for verification... Cleaning up challenges IMPORTANT NOTES: - Congratulations! Your certificate and chain have been saved at: /etc/letsencrypt/live/your_domain.com/fullchain.pem Your key file has been saved at: /etc/letsencrypt/live/your_domain.com/privkey.pem Your cert will expire on 2020-09-15. To obtain a new or tweaked version of this certificate in the future, simply run certbot again. To non-interactively renew *all* of your certificates, run "certbot renew" - Your account credentials have been saved in your Certbot configuration directory at /etc/letsencrypt. You should make a secure backup of this folder now. This configuration directory will also contain certificates and private keys obtained by Certbot so making regular backups of this folder is ideal.

      Vous pouvez inspecter le certificat à l’aide de cat :

      sudo cat /etc/letsencrypt/live/your_domain.com/fullchain.pem
      

      Une fois le certificat TLS fourni, nous pouvons tester la configuration Nginx assemblée à l’étape précédente :

      docker run --rm --name nginx -p 80:80 -p 443:443 
          -v ~/conf/nginx.conf:/etc/nginx/conf.d/nginx.conf:ro 
          -v /etc/letsencrypt:/etc/letsencrypt 
          -v /var/lib/letsencrypt:/var/lib/letsencrypt 
          -v /var/www/html:/var/www/html 
          nginx:1.19.0
      

      C’est la même commande exécutée à l’Étape 3, avec l’ajout des deux répertoires Let’s Encrypt récemment créés.

      Une fois que Nginx est opérationnel, naviguez jusqu’à http://your_domain.com. Vous pouvez recevoir un avertissement dans votre navigateur signalant que l’autorité de certification est invalide. C’est normal car nous avons fourni des certificats de mise en scène et non pas des certificats Let’s Encrypt. Vérifiez la barre d’URL de votre navigateur pour confirmer que votre requête HTTP a été redirigée vers HTTPS.

      Entrez CTRL+C dans votre terminal pour quitter Nginx et exécutez à nouveau le client certbot, cette fois en omettant le drapeau --staging flag :

      docker run -it --rm -p 80:80 --name certbot 
               -v "/etc/letsencrypt:/etc/letsencrypt" 
               -v "/var/lib/letsencrypt:/var/lib/letsencrypt" 
               certbot/certbot certonly --standalone -d your_domain.com
      

      Lorsque vous êtes invité à conserver le certificat existant ou à le renouveler et à le remplacer, tapez 2 pour le renouveler et ensuite ENTER (ENTRÉE) pour confirmer votre choix.

      Une fois le certificat de production TLS fourni, exécutez à nouveau le serveur Nginx :

      docker run --rm --name nginx -p 80:80 -p 443:443 
          -v ~/conf/nginx.conf:/etc/nginx/conf.d/nginx.conf:ro 
          -v /etc/letsencrypt:/etc/letsencrypt 
          -v /var/lib/letsencrypt:/var/lib/letsencrypt 
          -v /var/www/html:/var/www/html 
          nginx:1.19.0
      

      Dans votre navigateur, naviguez vers http://your_domain.com. Dans la barre d’URL, vérifiez que la requête HTTP a été redirigée vers HTTPS. Comme l’application de sondage n’a pas de route configurée par défaut, vous devriez voir une erreur Django Page not found. Naviguez vers https://your_domain.com/polls et vous verrez l’interface standard de l’application de sondage :

      Interface des applications de sondage

      Vous avez maintenant fourni un certificat TLS de production à l’aide du client Certbot Docker, et vous effectuez un proxying inverse et un équilibrage de charge des requêtes externes vers les deux serveurs d’application Django.

      Les certificats Let’s Encrypt expirent tous les 90 jours. Pour vous assurer que votre certificat reste valide, vous devriez le renouveler régulièrement avant son expiration prévue. Avec Nginx en cours d’exécution, vous devriez utiliser le client Certbot en mode webroot au lieu d’un mode standalone. Cela signifie que Certbot effectuera la validation en créant un fichier dans le répertoire /var/www/html/.well-known/acme-challenge/ et les demandes de validation Let’s Encrypt vers ce chemin seront captées par la règle location définie dans la configuration Nginx à l’Étape 3. Certbot effectuera ensuite la rotation des certificats, et vous pouvez recharger Nginx afin qu’il utilise ce certificat qui vient d’être fourni.

      Il existe de multiples façons d’automatiser cette procédure et le renouvellement automatique des certificats TLS va au-delà de la portée de ce tutoriel. Pour effectuer un processus similaire à l’aide de l’utilitaire de planification cron, consultez l’Étape 6 de Comment sécuriser une application Node.js avec Nginx, Let’s Encrypt et Docker Compose.

      Dans votre terminal, entrez sur CTRL+C pour terminer le conteneur Nginx. Exécutez-le à nouveau en mode détaché en apposant le drapeau -d :

      docker run --rm --name nginx -d -p 80:80 -p 443:443 
          -v ~/conf/nginx.conf:/etc/nginx/conf.d/nginx.conf:ro 
          -v /etc/letsencrypt:/etc/letsencrypt 
          -v /var/lib/letsencrypt:/var/lib/letsencrypt 
        -v /var/www/html:/var/www/html 
          nginx:1.19.0
      

      Avec Nginx en cours d’exécution en arrière-plan, utilisez la commande suivante pour effectuer un essai de la procédure de renouvellement du certificat :

      docker run -it --rm --name certbot 
          -v "/etc/letsencrypt:/etc/letsencrypt" 
        -v "/var/lib/letsencrypt:/var/lib/letsencrypt" 
        -v "/var/www/html:/var/www/html" 
        certbot/certbot renew --webroot -w /var/www/html --dry-run
      

      Nous utilisons le plugin --webroot, spécifions le chemin racine web, et utilisons le drapeau --dry-run pour vérifier que tout fonctionne correctement sans réellement effectuer le renouvellement du certificat.

      Si la simulation de renouvellement réussit, vous devriez voir la sortie suivante :

      Output

      Cert not due for renewal, but simulating renewal for dry run Plugins selected: Authenticator webroot, Installer None Renewing an existing certificate Performing the following challenges: http-01 challenge for your_domain.com Using the webroot path /var/www/html for all unmatched domains. Waiting for verification... Cleaning up challenges - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - new certificate deployed without reload, fullchain is /etc/letsencrypt/live/your_domain.com/fullchain.pem - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ** DRY RUN: simulating 'certbot renew' close to cert expiry ** (The test certificates below have not been saved.) Congratulations, all renewals succeeded. The following certs have been renewed: /etc/letsencrypt/live/your_domain.com/fullchain.pem (success) ** DRY RUN: simulating 'certbot renew' close to cert expiry ** (The test certificates above have not been saved.) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

      Dans un environnement de production, après avoir renouvelé les certificats, vous devez recharger Nginx pour que les modifications prennent effet. Pour recharger Nginx, exécutez la commande suivante :

      docker kill -s HUP nginx
      

      Cette commande enverra un signal Unix HUP au processus Nginx en cours d’exécution dans le conteneur Docker nginx. À la réception de ce signal, Nginx rechargera sa configuration et les certificats renouvelés.

      Une fois HTTPS activé, et tous les composants de cette architecture opérationnels, la dernière étape consiste à verrouiller la configuration en empêchant l’accès externe aux deux serveurs d’application backend ; toutes les requêtes HTTP devraient passer par le proxy Nginx.

      Étape 5 — Prévention de l’accès externe aux serveurs d’application Django

      Dans l’architecture décrite dans ce tutoriel, la terminaison SSL se produit au niveau du proxy Nginx. Cela signifie que Nginx décrypte la connexion SSL, et que les paquets sont acheminés vers les serveurs d’application Django non cryptés. Pour de nombreux cas d’utilisation, ce niveau de sécurité est suffisant. Pour les applications impliquant des données financières ou sanitaires, vous souhaiterez peut-être mettre en place le cryptage de bout en bout. Vous pouvez le faire en envoyant des paquets cryptés via le répartiteur de charge et en les décryptant sur les serveurs d’application, ou en les cryptant à niveau du proxy et en décryptant une fois de plus sur les serveurs d’application Django. Ces techniques vont au-delà de la portée de cet article, mais pour en savoir plus veuillez consulter le Cryptage de bout en bout.

      Le proxy Nginx agit comme une passerelle entre le trafic externe et le réseau interne. En théorie, aucun client externe ne doit avoir un accès direct aux serveurs d’application internes, et toutes les demandes doivent passer par le serveur Nginx. La remarque de l’Étape 1 décrit brièvement un problème ouvert avec Docker où Docker contourne les paramètres du pare-feu ufw par défaut et ouvre les ports à l’extérieur, ce qui peut se révéler dangereux. Pour répondre à cette préoccupation de sécurité, il est recommandé d’utiliser les pare-feu en cloud lors de l’utilisation de serveurs Docker. Pour obtenir plus d’informations sur la création de pare-feu en cloud avec DigitalOcean, consultez Comment créer des pare-feu. Vous pouvez également manipuler directement les iptables au lieu d’utiliser ufw. Pour en savoir plus sur l’utilisation d’iptables avec Docker, consultez Docker et iptables.

      Au cours de cette étape, nous allons modifier la configuration d’UFW pour bloquer l’accès externe aux ports hôtes ouverts par Docker. En exécutant Django sur les serveurs d’application, nous avons passé le drapeau -p 80:8000 à docker, qui achemine le port 80 de l’hôte au port de conteneur 8000. Cela a également ouvert le port 80 à des clients externes, ce que vous pouvez vérifier en visitant http://your_app_server_1_IP. Pour éviter un accès direct, nous allons modifier la configuration d’UFW à l’aide de la méthode décrite dans le référentiel GitHub ufw-docker.

      Commencez par vous connecter au premier serveur d’application Django. Ensuite, ouvrez le fichier /etc/ufw/after.rules avec des privilèges super-utilisateur, à l’aide de nano ou de votre éditeur préféré :

      sudo nano /etc/ufw/after.rules
      

      Entrez votre mot de passe lorsque vous y serez invité, et appuyez sur ENTER pour confirmer.

      Vous devriez voir les règles ufw suivantes :

      /etc/ufw/after.rules

      #
      # rules.input-after
      #
      # Rules that should be run after the ufw command line added rules. Custom
      # rules should be added to one of these chains:
      #   ufw-after-input
      #   ufw-after-output
      #   ufw-after-forward
      #
      
      # Don't delete these required lines, otherwise there will be errors
      *filter
      :ufw-after-input - [0:0]
      :ufw-after-output - [0:0]
      :ufw-after-forward - [0:0]
      # End required lines
      
      # don't log noisy services by default
      -A ufw-after-input -p udp --dport 137 -j ufw-skip-to-policy-input
      -A ufw-after-input -p udp --dport 138 -j ufw-skip-to-policy-input
      -A ufw-after-input -p tcp --dport 139 -j ufw-skip-to-policy-input
      -A ufw-after-input -p tcp --dport 445 -j ufw-skip-to-policy-input
      -A ufw-after-input -p udp --dport 67 -j ufw-skip-to-policy-input
      -A ufw-after-input -p udp --dport 68 -j ufw-skip-to-policy-input
      
      # don't log noisy broadcast
      -A ufw-after-input -m addrtype --dst-type BROADCAST -j ufw-skip-to-policy-input
      
      # don't delete the 'COMMIT' line or these rules won't be processed
      COMMIT
      

      Faites défiler vers le bas, et collez dans le bloc suivant des règles de configuration UFW :

      /etc/ufw/after.rules

      . . .
      
      # BEGIN UFW AND DOCKER
      *filter
      :ufw-user-forward - [0:0]
      :DOCKER-USER - [0:0]
      -A DOCKER-USER -j RETURN -s 10.0.0.0/8
      -A DOCKER-USER -j RETURN -s 172.16.0.0/12
      -A DOCKER-USER -j RETURN -s 192.168.0.0/16
      
      -A DOCKER-USER -p udp -m udp --sport 53 --dport 1024:65535 -j RETURN
      
      -A DOCKER-USER -j ufw-user-forward
      
      -A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 192.168.0.0/16
      -A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 10.0.0.0/8
      -A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 172.16.0.0/12
      -A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 192.168.0.0/16
      -A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 10.0.0.0/8
      -A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 172.16.0.0/12
      
      -A DOCKER-USER -j RETURN
      COMMIT
      # END UFW AND DOCKER
      

      Ces règles limitent l’accès du public aux ports ouverts par Docker, et permettent un accès à partir des plages IP privées 10.0.0/8, 172.16.0.0/12 et 192.168.0.0/16. Si vous utilisez VPC avec DigitalOcean, alors les Droplets de votre réseau VPC aura accès au port ouvert sur l’interface réseau privée, mais les clients externes ne l’auront pas. Pour plus d’informations sur VPC, consultez la documentation officielle de VPC. Pour en savoir plus sur les règles appliquées dans ce snippet, consultez Comment ça marche ? du fichier README ufw-docker.

      Si vous n’utilisez pas VPC avec DigitalOcean et que vous avez entré les adresses IP publiques des serveurs d’application dans le bloc upstream de votre configuration Nginx, vous devrez modifier explicitement le pare-feu UFW pour autoriser le trafic depuis le serveur Nginx par le port 80 sur les serveurs d’application Django. Pour obtenir des conseils sur la création de règles allow avec le pare-feu UFW, consultez les Essentiels  d’UFW: Règles et commandes communes du pare-feu.

      Quand vous avez terminé de le modifier, enregistrez et fermez le fichier.

      Redémarrez ufw afin qu’il adopte la nouvelle configuration :

      sudo systemctl restart ufw
      

      Naviguez vers http://APP_SERVER_1_IP dans votre navigateur web pour vérifier que vous ne pouvez plus accéder au serveur d’application sur le port 80.

      Répétez ce processus sur le deuxième serveur d’application Django.

      Déconnectez-vous du premier serveur d’application ou ouvrez une autre fenêtre de terminal, et connectez-vous au deuxième serveur d’application Django. Ensuite, ouvrez le fichier /etc/ufw/after.rules avec des privilèges super-utilisateur, à l’aide de nano ou de votre éditeur préféré :

      sudo nano /etc/ufw/after.rules
      

      Entrez votre mot de passe lorsque vous y serez invité, et appuyez sur ENTER pour confirmer.

      Faites défiler vers le bas, et collez dans le bloc suivant des règles de configuration UFW :

      /etc/ufw/after.rules

      . . .
      
      # BEGIN UFW AND DOCKER
      *filter
      :ufw-user-forward - [0:0]
      :DOCKER-USER - [0:0]
      -A DOCKER-USER -j RETURN -s 10.0.0.0/8
      -A DOCKER-USER -j RETURN -s 172.16.0.0/12
      -A DOCKER-USER -j RETURN -s 192.168.0.0/16
      
      -A DOCKER-USER -p udp -m udp --sport 53 --dport 1024:65535 -j RETURN
      
      -A DOCKER-USER -j ufw-user-forward
      
      -A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 192.168.0.0/16
      -A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 10.0.0.0/8
      -A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 172.16.0.0/12
      -A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 192.168.0.0/16
      -A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 10.0.0.0/8
      -A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 172.16.0.0/12
      
      -A DOCKER-USER -j RETURN
      COMMIT
      # END UFW AND DOCKER
      

      Quand vous avez terminé de le modifier, enregistrez et fermez le fichier.

      Redémarrez ufw afin qu’il adopte la nouvelle configuration :

      sudo systemctl restart ufw
      

      Naviguez vers http://APP_SERVER_2_IP dans votre navigateur web pour vérifier que vous ne pouvez plus accéder au serveur d’application sur le port 80.

      Enfin, naviguez jusqu’à https://your_domain_here/polls pour confirmer que le proxy Nginx a toujours accès aux serveurs Django en amont. Vous devriez voir l’interface de ‘application de sondage.

      Conclusion

      Dans ce tutoriel, vous avez mis en place une application de sondage Django évolutive à l’aide de conteneurs Docker. Au fur et à mesure que votre trafic et la charge sur le système grandissent, vous pouvez faire évoluer chaque couche séparément : la couche proxy Nginx, la couche d’application backend Django et la couche de base de données PostgreSQL.

      Lorsque vous construisez un système distribué, vous devez souvent faire face à de multiples décisions de conception, et plusieurs architectures peuvent satisfaire votre cas d’utilisation. L’architecture décrite dans ce tutoriel est conçue comme un modèle flexible pour la conception d’applications évolutives avec Django et Docker.

      Vous pouvez vouloir contrôler le comportement de vos conteneurs lorsqu’ils rencontrent des erreurs, ou exécuter automatiquement les conteneurs lorsque votre système démarre. Pour ce faire, vous pouvez utiliser un gestionnaire de processus comme Systemd ou mettre en œuvre des politiques de redémarrage. Pour plus d’informations à ce propos, consultez la section Démarrer les conteneurs automatiquement dans la documentation Docker.

      Lorsque vous travaillez à l’échelle avec plusieurs hôtes exécutant la même image Docker, il peut être plus efficace d’automatiser les étapes à l’aide d’un outil de gestion de configuration comme Ansible ou Chef. Pour en savoir plus sur la gestion de la configuration, consultez Une Introduction à la gestion de la configuration et Configuration de serveur automatisée avec Ansible : un kit d’atelier DigitalOcean.

      Au lieu de construire la même image sur chaque hôte, vous pouvez également rationaliser le déploiement à l’aide d’un registre d’images comme Docker Hub, qui construit, stocke et distribue des images Docker à plusieurs serveurs de manière centralisée. En plus d’un registre d’images, un pipeline d’intégration et de déploiement continu peut vous aider à construire, tester et déployer des images sur vos serveurs d’application. Pour plus d’informations sur les CI/CD, veuillez consulter Une Introduction aux meilleures pratiques de CI/CD.



      Source link

      Comment sécuriser MongoDB sur Ubuntu 20.04


      Une version antérieure de ce tutoriel a été écrite par Brennan Bearnes.

      Introduction

      MongoDB , également connu sous le nom de Mongo , est une base de données de documents open-source utilisée dans de nombreuses applications web modernes. Elle est classée comme une base de données NoSQL car elle ne repose pas sur une structure de base de données relationnelle traditionnelle basée sur des tableaux. Elle utilise plutôt des documents de type JSON avec des schémas dynamiques.

      MongoDB n’a pas d’authentification activée par défaut, ce qui signifie que tout utilisateur ayant accès au serveur où la base de données est installée peut ajouter et supprimer des données sans restriction. Afin de sécuriser cette vulnérabilité, ce tutoriel vous guidera dans la création d’un administrative user et l’activation de l’authentification. Vous ferez ensuite des tests pour confirmer que seul cet administrative user a accès à la base de données.

      Conditions préalables

      Pour terminer ce tutoriel, vous aurez besoin des éléments suivants :

      • Un serveur fonctionnant sous Ubuntu 20.04 Ce serveur doit avoir un utilisateur administratif non root et un pare-feu configuré avec UFW. Pour cela, suivez notre guide de configuration initiale du serveur pour Ubuntu 20.04 .
      • MongoDB installé sur votre serveur. Ce tutoriel a été validé en utilisant la version de MongoDB 4.4, bien qu’il devrait généralement fonctionner pour les anciennes versions de MongoDB également. Pour installer Mongo sur votre serveur, suivez notre tutoriel sur Comment installer MongoDB sur Ubuntu 20.04.

      Étape 1 – Ajouter un administrative user

      Depuis la sortie de la version 3.0, le démon MongoDB est configuré pour n’accepter que les connexions provenant de la socket Unix locale, et il n’est pas automatiquement ouvert à l’Internet dans toute son étendue. Toutefois, l’authentification est toujours désactivée par défaut. Cela signifie que tous les utilisateurs qui ont accès au serveur où MongoDB est installé ont également un accès complet aux bases de données.

      Dans un premier temps, pour sécuriser cette vulnérabilité, vous créerez un administrative user. Plus tard, vous activerez l’authentification et vous vous connecterez en tant qu’administrative user pour accéder à la base de données.

      Pour ajouter un administrative user, vous devez d’abord vous connecter au shell Mongo. L’authentification étant désactivée, vous pouvez le faire avec la commande mongo, sans aucune autre option :

      Il y aura une sortie au-dessus de l’invite du shell Mongo. Comme vous n’avez pas encore activé l’authentification, vous recevrez un avertissement indiquant que le contrôle d’accès n’est pas activé pour la base de données et que l’accès en lecture et en écriture aux données et la configuration de la base de données ne sont pas restreints :

      Output

      MongoDB shell version v4.4.0 . . . 2020-06-09T13:26:51.391+0000 I CONTROL [initandlisten] ** WARNING: Access control is not enabled for the database. 2020-06-09T13:26:51.391+0000 I CONTROL [initandlisten] ** Read and write access to data and configuration is unrestricted. . . . >

      Ces avertissements disparaîtront une fois que vous aurez activé l’authentification, mais pour l’instant, ils signifient que toute personne pouvant accéder à votre serveur Ubuntu pourrait également prendre le contrôle de votre base de données.

      Pour illustrer le tout, faites fonctionner la commande show dbs de Mongo :

      Cette commande renvoie une liste de toutes les bases de données sur le serveur. Cependant, lorsque l’authentification est activée, la liste change en fonction du rôle de l’utilisateur Mongo ou de son niveau d’accès à certaines bases de données. Cependant, comme l’authentification est désactivée, elle renvoie toutes les bases de données actuellement présentes sur le système sans restriction :

      Output

      admin 0.000GB config 0.000GB local 0.000GB

      Dans cet exemple de sortie, seules les bases de données par défaut apparaissent. Cependant, si vous avez des bases de données contenant des données sensibles sur votre système, n’importe quel utilisateur pourrait les trouver avec cette commande.

      Dans le cadre de l’atténuation de cette vulnérabilité, cette étape est axée sur l’ajout d’un administrative user. Pour ce faire, vous devez d’abord vous connecter à la base de données admin. C’est là que sont stockées les informations sur les utilisateurs, comme leurs noms d’utilisateur, mots de passe et rôles :

      Output

      switched to db admin

      MongoDB est installé avec un certain nombre de méthodes de shell basées sur JavaScript que vous pouvez utiliser pour gérer votre base de données. L’une d’entre elles, la méthode db.createUser, est utilisée pour créer de nouveaux utilisateurs au sein de la base de données sur laquelle la méthode est exécutée.

      Lancez la méthode db.createUser :

      Cette méthode exige que vous spécifiiez un nom d’utilisateur et un mot de passe pour l’utilisateur, ainsi que les rôles que vous souhaitez lui attribuer. Rappelons que MongoDB stocke ses données dans des documents de type JSON. Ainsi, lorsque vous créez un nouvel utilisateur, tout ce que vous faites est de créer un document contenant les données appropriées de l’utilisateur sous forme de champs individuels.

      Comme pour les objets dans JSON, les documents dans MongoDB commencent et se terminent par des accolades ( { et } ). Pour commencer à ajouter un utilisateur, entrez une accolade d’ouverture :

      Note : Mongo n’enregistrera pas la méthode db.createUser comme complète tant que vous n’aurez pas entré une parenthèse de fermeture. En attendant, l’invite passera d’un signe plus grand que ( > ) à une ellipse ( ... ).

      Ensuite, remplissez un champ user: en indiquant entre guillemets le nom d’utilisateur souhaité, suivi d’une virgule. L’exemple suivant précise le nom d’utilisateur AdminSammy, mais vous pouvez entrer le nom d’utilisateur que vous souhaitez :

      Ensuite, entrez un champ pwd avec la méthode passwordPrompt() comme valeur. Lorsque vous exécutez la méthode db.createUser, la méthode passwordPrompt() vous invite à saisir votre mot de passe. Cette méthode est plus sûre que l’autre, ce qui consiste à taper votre mot de passe en clair comme vous l’avez fait pour votre nom d’utilisateur.

      Note : La méthode passwordPrompt() est uniquement compatible avec les versions de MongoDB 4.2 et plus récentes.  Si vous utilisez une ancienne version de Mongo, vous devrez écrire votre mot de passe en clair, de la même manière que vous avez écrit votre nom d’utilisateur :

      Veillez également à faire suivre ce champ d’une virgule :

      Saisissez ensuite les rôles que vous souhaitez attribuer à votre administrative user. Comme vous créez un administrative user, vous devez au minimum lui accorder le rôle de userAdminAnyDatabase par rapport à la base de données admin. Cela permettra à l’administrative user de créer et de modifier de nouveaux utilisateurs et rôles. Comme l’administrative user a ce rôle dans la base de données admin, cela lui donnera également un accès de superuser à l’ensemble du cluster.

      En outre, l’exemple suivant accorde également à l’administrative user le rôle de ReadWriteAnyDatabase.  Cela donne à l’administrative user la possibilité de lire et de modifier les données de n’importe quelle base de données du cluster, à l’exception des bases de données config et local, qui sont pour la plupart à usage interne :

      • roles: [ { role: "userAdminAnyDatabase", db: "admin" }, "readWriteAnyDatabase" ]

      Puis entrez une accolade de fermeture pour marquer la fin du document :

      Ensuite, entrez une parenthèse de fermeture pour fermer et exécutez la méthode db.createUser :

      En résumé, voici à quoi devrait ressembler votre méthode db.createUser :

      > db.createUser(
      ... {
      ... user: "AdminSammy",
      ... pwd: passwordPrompt(),
      ... roles: [ { role: "userAdminAnyDatabase", db: "admin" }, "readWriteAnyDatabase" ]
      ... }
      ... )
      

      Si la syntaxe de chaque ligne est correcte, la méthode s’exécutera correctement et vous serez invité(e) à entrer un mot de passe :

      Output

      Enter password:

      Entrez un mot de passe fort de votre choix. Ensuite, vous recevrez une confirmation que l’utilisateur a été ajouté :

      Output

      Successfully added user: { "user" : "AdminSammy", "roles" : [ { "role" : "userAdminAnyDatabase", "db" : "admin" }, "readWriteAnyDatabase" ] }

      Ensuite, vous pouvez quitter le client MongoDB :

      À ce stade, votre utilisateur sera autorisé à saisir des informations d’identification. Cependant, il ne sera pas tenu de le faire tant que vous n’aurez pas activé l’authentification et redémarré le démon MongoDB.

      Étape 2 – Activer l’authentification

      Pour activer l’authentification, vous devez éditer mongod.conf, le fichier de configuration de MongoDB. Une fois que vous l’aurez activé et redémarré le service Mongo, les utilisateurs pourront toujours se connecter à la base de données sans s’authentifier. Toutefois, ils ne pourront lire ou modifier aucune donnée tant qu’ils n’auront pas fourni un nom d’utilisateur et un mot de passe corrects.

      Ouvrez le fichier de configuration avec votre éditeur. Ici, nous utiliserons nano :

      • sudo nano /etc/mongod.conf

      Faites défiler la page vers le bas pour trouver la section security commentée :

      /etc/mongod.conf

      . . .
      #security:
      
      #operationProfiling:
      
      . . .
      

      Décommentez cette ligne en enlevant le signe dièse ( # ) :

      /etc/mongod.conf

      . . .
      security:
      
      #operationProfiling:
      
      . . .
      

      Ajoutez ensuite le paramètre authorization et réglez-le sur « enabled ». Quand vous aurez terminé, les lignes devraient ressembler à ceci :

      /etc/mongod.conf

      . . .
      security:
        authorization: "enabled"
      . . .
      

      Notez que la ligne security: ne comporte pas d’espace au début, tandis que la ligne authorization: est en retrait de deux espaces.

      Après avoir ajouté ces lignes, enregistrez et fermez le fichier. Si vous avez utilisé nano pour ouvrir le fichier, faites-le en appuyant sur CTRL + X, Y, puis ENTER 

      Ensuite, redémarrez le démon pour que ces nouveaux changements soient pris en compte :

      • sudo systemctl restart mongod

      Ensuite, vérifiez l’état du service pour vous assurer qu’il a redémarré correctement :

      • sudo systemctl status mongod

      Si la commande restart a réussi, vous recevrez une sortie qui indique que le mongod est actif et a été lancé récemment :

      Output

      ● mongod.service - MongoDB Database Server Loaded: loaded (/lib/systemd/system/mongod.service; enabled; vendor preset: enabled) Active: active (running) since Tue 2020-06-09 22:06:20 UTC; 7s ago Docs: https://docs.mongodb.org/manual Main PID: 15370 (mongod) Memory: 170.1M CGroup: /system.slice/mongod.service └─15370 /usr/bin/mongod --config /etc/mongod.conf Jun 09 22:06:20 your_host systemd[1]: Started MongoDB Database Server.

      Après avoir vérifié que le démon est de nouveau opérationnel, vous pouvez tester que le paramètre d’authentification que vous avez ajouté fonctionne comme prévu.

      Étape 3 – Tester les paramètres d’authentification

      Pour vérifier que les exigences d’authentification que vous avez ajoutées à l’étape précédente fonctionnent correctement, commencez par vous connecter sans spécifier d’identifiants, pour vous assurer que vos actions sont bien limitées :

      Maintenant que vous avez activé l’authentification, aucun des avertissements que vous avez rencontrés précédemment n’apparaîtra :

      Output

      MongoDB shell version v4.4.0 connecting to: mongodb://127.0.0.1:27017/?compressors=disabled&gssapiServiceName=mongodb Implicit session: session { "id" : UUID("5d50ed96-f7e1-493a-b4da-076067b2d898") } MongoDB server version: 4.4.0 >

      Confirmez si votre accès est restreint en exécutant à nouveau la commande show dbs :

      Rappelez-vous de l’étape 1 qu’il y a au moins quelques bases de données par défaut sur votre serveur. Cependant, dans ce cas, la commande n’aura aucune sortie, car vous ne vous êtes pas authentifié en tant qu’utilisateur privilégié.

      Comme cette commande ne renvoie aucune information, on peut dire sans se tromper que le paramètre d’authentification fonctionne comme prévu. Vous ne pourrez pas non plus créer d’utilisateurs ou effectuer d’autres tâches privilégiées sans vous authentifier au préalable.

      Continuez et sortez du shell MongoDB :

      Note : Au lieu d’exécuter la commande exit suivante comme vous l’avez fait précédemment à l’étape 1, une autre façon de fermer le shell est d’appuyer simplement sur CTRL + C .

      Ensuite, assurez-vous que votre administrative user est capable de s’authentifier correctement en exécutant la commande mongo suivante pour vous connecter en tant qu’utilisateur. Cette commande comprend le drapeau -u, qui précède le nom de l’utilisateur sous lequel vous voulez vous connecter.  Veillez à remplacer AdminSammy par le nom d’utilisateur de votre propre administrative user. Il comprend également le drapeau -p, qui vous demandera le mot de passe de l’utilisateur, et spécifie admin comme la base de données d’authentification où le nom d’utilisateur spécifié a été créé :

      • mongo -u AdminSammy -p --authenticationDatabase admin

      Saisissez le mot de passe de l’utilisateur lorsque vous y êtes invité, et vous serez ensuite déposé dans le shell. Une fois sur place, essayez de lancer à nouveau la commande show dbs :

      Cette fois, parce que vous vous êtes authentifié correctement, la commande retournera avec succès une liste de toutes les bases de données actuellement sur le serveur :

      Output

      admin 0.000GB config 0.000GB local 0.000GB

      Cela confirme que l’authentification a été activée avec succès.

      Conclusion

      En complétant ce guide, vous avez mis en place un administrative MongoDB user que vous pouvez utiliser pour créer et modifier de nouveaux utilisateurs et rôles, et pour gérer votre instance MongoDB. Vous avez également configuré votre instance MongoDB pour exiger que les utilisateurs s’authentifient avec un nom d’utilisateur et un mot de passe valides avant de pouvoir interagir avec des données.

      Pour plus d’informations sur la gestion des utilisateurs de MongoDB, consultez la documentation officielle sur le sujet .  Vous pourriez également vouloir en savoir plus sur le fonctionnement de l’authentification sur MongoDB.

      De plus, si vous envisagez d’interagir avec votre instance MongoDB à distance, vous pouvez suivre notre guide sur Comment configurer l’accès à distance pour MongoDB sur Ubuntu 20.04 .



      Source link

      Comment mettre en place et sécuriser un cluster etcd avec Ansible sur Ubuntu 18.04


      L’auteur a choisi Wikimedia Foundation pour recevoir un don dans le cadre du programme Write for Donations.

      Introduction

      etcd est un magasin de valeurs clés distribué qui repose sur de nombreuses plateformes et outils, dont Kubernetes , Vulcand et Doorman . Dans Kubernetes, etcd est utilisé comme magasin de configuration globale qui stocke l’état du cluster. Savoir administrer etcd est essentiel pour administrer un cluster Kubernetes. Bien qu’il existe de nombreuses offres Kubernetes gérées, également connues sous le nom de Kubernetes-as-a-Service , qui vous déchargent de cette charge administrative, de nombreuses entreprises choisissent encore de gérer des clusters Kubernetes autogérés sur site en raison de la flexibilité qu’ils apportent.

      La première moitié de cet article vous guidera dans la mise en place d’un cluster de 3 nœuds etcd sur les serveurs Ubuntu 18.04. La seconde moitié se concentrera sur la sécurisation du cluster en utilisant la Transport Layer Secutiry (Sécurité de la couche transport), ou TLS  Pour exécuter chaque installation de manière automatisée, nous utiliserons Ansible tout au long de ce tutoriel. Ansible est un outil de gestion de configuration similaire à Puppet , Chef , et SaltStack ; il nous permet de définir chaque étape de configuration de manière déclarative, dans des fichiers appelés playbooks .

      À la fin de ce tutoriel, vous disposerez d’un cluster sécurisé de 3 nœuds etcd fonctionnant sur vos serveurs.  Vous disposerez également d’un playbook Ansible qui vous permettra de recréer de manière répétée et cohérente la même configuration sur un nouvel ensemble de serveurs.

      Conditions préalables

      Avant de commencer ce guide, vous aurez besoin des éléments suivants :

      • Python , pip , et le package pyOpenSSL installé sur votre machine locale.  Pour savoir comment installer Python3, pip et les packages Python, reportez-vous à Comment installer Python 3 et mettre en place un environnement de programmation local sur Ubuntu 18.04 .

      • Trois serveurs Ubuntu 18.04 sur le même réseau local, avec au moins 2 Go de RAM et un accès SSH root.  Vous devez également configurer les serveurs pour qu’ils aient les noms d’hôtes etcd1 , etcd2 , et etcd3 . Les étapes décrites dans cet article fonctionneraient sur n’importe quel serveur générique, pas nécessairement DigitalOcean Droplets. Cependant, si vous souhaitez héberger vos serveurs sur DigitalOcean, vous pouvez suivre le guide Comment créer un droplet du panneau de contrôle de DigitalOcean pour remplir cette condition. Notez que vous devez activer l’option Private Networking lors de la création de votre droplet.  Pour activer la mise en réseau privée sur les droplets existants, reportez-vous à la section Comment activer la mise en réseau privée sur les droplets .

      Avertissement : Le but de cet article étant de fournir une introduction à la mise en place d’un cluster etcd sur un réseau privé, les trois serveurs Ubuntu 18.04 de cette configuration n’ont pas été testés avec un pare-feu et sont accessibles en tant que root user Dans une installation de production, tout nœud exposé à l’internet public nécessiterait un pare-feu et un sudo user pour adhérer aux meilleures pratiques de sécurité. Pour plus d’informations, consultez le tutoriel Configuration initiale du serveur avec Ubuntu 18.04.

      Etape 1 – Configuration d’Ansible pour le nœud de contrôle

      Ansible est un outil utilisé pour gérer les serveurs.  Les serveurs qu’Ansible gère sont appelés les nœuds gérés, et la machine qui fait tourner Ansible est appelée le nœud de contrôle. Ansible fonctionne en utilisant les clés SSH sur le nœud de contrôle pour accéder aux nœuds gérés. Une fois qu’une session SSH est établie, Ansible exécute un ensemble de scripts pour fournir et configurer les nœuds gérés. Dans cette étape, nous allons tester que nous sommes capables d’utiliser Ansible pour nous connecter aux nœuds gérés et exécuter la commande hostname .

      Une journée typique pour un administrateur système peut impliquer la gestion de différents ensembles de nœuds. Par exemple, vous pouvez utiliser Ansible pour mettre à disposition de nouveaux serveurs, mais l’utiliser ensuite pour reconfigurer un autre ensemble de serveurs. Pour permettre aux administrateurs de mieux organiser l’ensemble des nœuds gérés, Ansible propose le concept d’inventaire sur les hôtes (ou inventaire en abrégé). Vous pouvez définir chaque nœud que vous souhaitez gérer avec Ansible à l’intérieur d’un fichier d’inventaire et les organiser en groupes. Ensuite, lorsque vous exécutez les commandes ansible et ansible-playbook, vous pouvez spécifier à quels hôtes ou groupes la commande s’applique.

      Par défaut, Ansible lit le fichier d’inventaire à partir de /etc/ansible/hosts ; cependant, nous pouvons spécifier un fichier d’inventaire différent en utilisant le drapeau --inventory (ou -i en abrégé).

      Pour commencer, créez un nouveau répertoire sur votre machine locale (le nœud de contrôle) pour y placer tous les fichiers de ce tutoriel :

      • mkdir -p $HOME/playground/etcd-ansible

      Ensuite, entrez dans le répertoire que vous venez de créer :

      • cd $HOME/playground/etcd-ansible

      À l’intérieur du répertoire, créez et ouvrez un fichier d’inventaire vierge nommé hosts à l’aide de votre éditeur :

      • nano $HOME/playground/etcd-ansible/hosts

      Dans le fichier hosts, listez chacun de vos nœuds gérés selon le format suivant, en remplaçant les adresses IP publiques mises en évidence par les adresses IP publiques réelles de vos serveurs :

      ~/playground/etcd-ansible/hosts

      [etcd]
      etcd1 ansible_host=etcd1_public_ip  ansible_user=root
      etcd2 ansible_host=etcd2_public_ip  ansible_user=root
      etcd3 ansible_host=etcd3_public_ip  ansible_user=root
      

      La ligne [etcd] définit un groupe appelé etcd . Dans la définition du groupe, nous énumérons tous les nœuds que nous gérons. Chaque ligne commence par un alias (par exemple, etcd1 ), ce qui nous permet de nous référer à chaque hôte en utilisant un nom facile à retenir au lieu d’une longue adresse IP. Les variables ansible_host et ansible_user sont des variables Ansible . Dans ce cas, elles sont utilisées pour fournir à Ansible les adresses IP publiques et les noms d’utilisateur SSH à utiliser lors de la connexion via SSH.

      Pour s’assurer qu’Ansible est capable de se connecter à nos nœuds gérés, nous pouvons tester la connectivité en utilisant Ansible pour exécuter la commande hostname sur chacun des hôtes du groupe etcd :

      • ansible etcd -i hosts -m command -a hostname

      Décomposons cette commande pour apprendre ce que chaque partie signifie :

      • etcd : spécifie le modèle d’hôte à utiliser pour déterminer quels hôtes de l’inventaire sont gérés avec cette commande.  Ici, nous utilisons le nom du groupe comme modèle d’hôte.
      • -i hosts : spécifie le fichier d’inventaire à utiliser.
      • Commande -m : la fonctionnalité derrière Ansible est fournie par des modules . Le module de commande prend l’argument transmis et l’exécute comme une commande sur chacun des nœuds gérés. Ce tutoriel introduira quelques autres modules Ansible au fur et à mesure de notre progression.
      • -a hostname : l’argument à passer dans le module. Le nombre et les types d’arguments dépendent du module.

      Après avoir exécuté la commande, vous trouverez la sortie suivante, ce qui signifie qu’Ansible est configuré correctement :

      Output

      etcd2 | CHANGED | rc=0 >> etcd2 etcd3 | CHANGED | rc=0 >> etcd3 etcd1 | CHANGED | rc=0 >> etcd1

      Chaque commande qu’Ansible exécute est appelée une tâche _. L’utilisation d’ansible sur la ligne de commande pour exécuter des tâches est appelée exécution de commandes _ad hoc. L’avantage des commandes ad hoc est qu’elles sont rapides et ne nécessitent que peu de réglages ; l’inconvénient est qu’elles fonctionnent manuellement et ne peuvent donc pas être confiées à un système de contrôle de version comme Git .

      Une légère amélioration serait d’écrire un script shell et d’exécuter nos commandes en utilisant le module de script d’Ansible .  Cela nous permettrait d’enregistrer les étapes de configuration que nous avons suivies pour le contrôle de version. Cependant, les scripts shell sont impératifs, ce qui signifie que nous sommes responsables de la détermination des commandes à exécuter (les « comment ») pour configurer le système dans l’état souhaité. Ansible, d’autre part, préconise une approche déclarative, où nous définissons « quel » état souhaité de notre serveur doit se trouver à l’intérieur des fichiers de configuration, et Ansible est responsable d’amener le serveur à cet état souhaité.

      L’approche déclarative est préférable parce que l’intention du fichier de configuration est immédiatement communiquée, ce qui signifie qu’il est plus facile à comprendre et à maintenir. De plus, il incombe à Ansible, et non plus à l’administrateur, de traiter les cas marginaux, ce qui nous épargne beaucoup de travail.

      Maintenant que vous avez configuré le nœud de contrôle Ansible pour communiquer avec les nœuds gérés, nous vous présenterons, dans une prochaine étape, les playbooks Ansible, qui vous permettent de spécifier des tâches de manière déclarative.

      Étape 2 – Obtenir les noms d’hôte des nœuds gérés à l’aide de playbooks Ansible

      Au cours de cette étape, nous reproduirons ce qui a été fait à l’étape 1 en imprimant les noms d’hôte des nœuds gérés, mais au lieu d’exécuter des tâches ad hoc, nous définirons chaque tâche de manière déclarative comme un playbook ansible et nous l’exécuterons. L’objectif de cette étape est de démontrer comment fonctionnent les playbooks Ansible ; nous réaliserons des tâches beaucoup plus substantielles avec des playbooks dans des étapes ultérieures.

      Dans le répertoire de votre projet, créez un nouveau fichier nommé playbook.yaml en utilisant votre éditeur :

      • nano $HOME/playground/etcd-ansible/playbook.yaml

      À l’intérieur de playbook.yaml, ajoutez les lignes suivantes :

      ~/playground/etcd-ansible/playbook.yaml

      - hosts: etcd
        tasks:
          - name: "Retrieve hostname"
            command: hostname
            register: output
          - name: "Print hostname"
            debug: var=output.stdout_lines
      

      Fermez et enregistrez le fichier playbook.yaml en appuyant sur CTRL+X, puis sur Y.

      Le playbook contient une liste de plays ; chaque play contient une liste de tâches qui doivent être exécutées sur tous les hôtes correspondant au modèle d’hôte spécifié par la clé d’hôtes. Dans ce playbook, nous avons un play qui contient deux tâches. La première tâche exécute la commande hostname en utilisant le module de commande et enregistre la sortie dans une variable nommée output . Dans la deuxième tâche, nous utilisons le module debug pour imprimer la propriété stdout_lines de la variable output.

      Nous pouvons maintenant exécuter ce playbook en utilisant la commande ansible-playbook :

      • ansible-playbook -i hosts playbook.yaml

      Vous trouverez la sortie suivante, ce qui signifie que votre playbook fonctionne correctement :

      Output

      PLAY [etcd] *********************************************************************************************************************** TASK [Gathering Facts] ************************************************************************************************************ ok: [etcd2] ok: [etcd3] ok: [etcd1] TASK [Retrieve hostname] ********************************************************************************************************** changed: [etcd2] changed: [etcd3] changed: [etcd1] TASK [Print hostname] ************************************************************************************************************* ok: [etcd1] => { "output.stdout_lines": [ "etcd1" ] } ok: [etcd2] => { "output.stdout_lines": [ "etcd2" ] } ok: [etcd3] => { "output.stdout_lines": [ "etcd3" ] } PLAY RECAP ************************************************************************************************************************ etcd1 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 etcd2 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 etcd3 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

      Note : ansible-playbook utilise parfois cowsay comme moyen ludique d’imprimer les titres. Si vous trouvez beaucoup de vaches en ASCII imprimées sur votre terminal, vous savez maintenant pourquoi. Pour désactiver cette fonctionnalité, réglez la variable d’environnement ANSIBLE_NOCOWS sur 1 avant de lancer ansible-playbook en exécutant export ANSIBLE_NOCOWS=1 dans votre shell.

      Dans cette étape, nous sommes passés de l’exécution de tâches ad hoc impératives à l’exécution de playbooks déclaratifs. Dans la prochaine étape, nous remplacerons ces deux tâches de démonstration par des tâches qui mettront en place notre cluster etcd.

      Étape 3 – Installation d’etcd sur les nœuds gérés

      Dans cette étape, nous vous montrerons les commandes pour installer etcd manuellement et nous vous montrerons comment traduire ces mêmes commandes en tâches à l’intérieur de notre playbook Ansible.

      etcd et son client etcdctl sont disponibles sous forme de binaires, que nous allons télécharger, extraire et déplacer dans un répertoire qui fait partie de la variable d’environnement PATH. Lorsqu’ils sont configurés manuellement, voici les étapes que nous suivons pour chacun des nœuds gérés :

      • mkdir -p /opt/etcd/bin
      • cd /opt/etcd/bin
      • wget -qO- https://storage.googleapis.com/etcd/v3.3.13/etcd-v3.3.13-linux-amd64.tar.gz | tar --extract --gzip --strip-components=1
      • echo 'export PATH="$PATH:/opt/etcd/bin"' >> ~/.profile
      • echo 'export ETCDCTL_API=3" >> ~/.profile

      Les quatre premières commandes permettent de télécharger et d’extraire les binaires dans le répertoire /opt/etcd/bin/. Par défaut, le client etcdctl utilisera l’API v2 pour communiquer avec le serveur etcd. Puisque nous utilisons etcd v3.x, la dernière commande fixe la variable d’environnement ETCDCTL_API à 3 .

      Note : Ici, nous utilisons etcd v3.3.13 construit pour une machine avec des processeurs qui utilisent le jeu d’instructions AMD64. Vous pouvez trouver des binaires pour d’autres systèmes et d’autres versions sur la page officielle des versions de GitHub.

      Pour reproduire les mêmes étapes dans un format standardisé, nous pouvons ajouter des tâches à notre playbook. Ouvrez le fichier playbook.yaml dans votre éditeur :

      • nano $HOME/playground/etcd-ansible/playbook.yaml

      Remplacez la totalité du fichier playbook.yaml par le contenu suivant :

      ~/playground/etcd-ansible/playbook.yaml

      - hosts: etcd
        become: True
        tasks:
          - name: "Create directory for etcd binaries"
            file:
              path: /opt/etcd/bin
              state: directory
              owner: root
              group: root
              mode: 0700
          - name: "Download the tarball into the /tmp directory"
            get_url:
              url: https://storage.googleapis.com/etcd/v3.3.13/etcd-v3.3.13-linux-amd64.tar.gz
              dest: /tmp/etcd.tar.gz
              owner: root
              group: root
              mode: 0600
              force: True
          - name: "Extract the contents of the tarball"
            unarchive:
              src: /tmp/etcd.tar.gz
              dest: /opt/etcd/bin/
              owner: root
              group: root
              mode: 0600
              extra_opts:
                - --strip-components=1
              decrypt: True
              remote_src: True
          - name: "Set permissions for etcd"
            file:
              path: /opt/etcd/bin/etcd
              state: file
              owner: root
              group: root
              mode: 0700
          - name: "Set permissions for etcdctl"
            file:
              path: /opt/etcd/bin/etcdctl
              state: file
              owner: root
              group: root
              mode: 0700
          - name: "Add /opt/etcd/bin/ to the $PATH environment variable"
            lineinfile:
              path: /etc/profile
              line: export PATH="$PATH:/opt/etcd/bin"
              state: present
              create: True
              insertafter: EOF
          - name: "Set the ETCDCTL_API environment variable to 3"
            lineinfile:
              path: /etc/profile
              line: export ETCDCTL_API=3
              state: present
              create: True
              insertafter: EOF
      

      Chaque tâche utilise un module ; pour cet ensemble de tâches, nous utilisons les modules suivants :

      • file : pour créer le répertoire /opt/etcd/bin, et pour définir plus tard les permissions des fichiers pour les binaires etcd et etcdctl.
      • get_url : pour télécharger le tarball gzippé sur les nœuds gérés.
      • unarchive : pour extraire et déballer les binaires etcd et etcdctl du tarball gzippé.
      • lineinfile : pour ajouter une entrée dans le fichier .profile.

      Pour appliquer ces changements, fermez et enregistrez le fichier playbook.yaml en appuyant sur CTRL+X suivi de Y . Ensuite, sur le terminal, exécutez à nouveau la même commande ansible-playbook :

      • ansible-playbook -i hosts playbook.yaml

      La section PLAY RECAP de la sortie n’affichera que ok et changed :

      Output

      ... PLAY RECAP ************************************************************************************************************************ etcd1 : ok=8 changed=7 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 etcd2 : ok=8 changed=7 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 etcd3 : ok=8 changed=7 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

      Pour confirmer une installation correcte d’etcd, il faut lancer manuellement SSH dans l’un des nœuds gérés et exécuter etcd et etcdctl :

      etcd1_public_ip sont les adresses IP publiques du serveur nommé etcd1 . Une fois que vous avez obtenu un accès SSH, lancez etcd --version pour imprimer la version d’etcd installée :

      Vous trouverez une sortie similaire à ce qui est montré dans la suite, ce qui signifie que le binaire etcd est installé avec succès :

      Output

      etcd Version: 3.3.13 Git SHA: 98d3084 Go Version: go1.10.8 Go OS/Arch: linux/amd64

      Pour confirmer qu’etcdctl est installé avec succès, lancez la version d'etcdctl :

      Vous recevrez un résultat similaire à celui qui suit :

      Output

      etcdctl version: 3.3.13 API version: 3.3

      Notez que la sortie indique API version: 3.3, ce qui confirme également que notre variable d’environnement ETCDCTL_API a été définie correctement.

      Sortez du serveur etcd1 pour revenir à votre environnement local.

      Nous avons maintenant installé avec succès etcd et etcdctl sur tous nos nœuds gérés. Dans la prochaine étape, nous ajouterons d’autres tâches à notre play pour faire fonctionner etcd comme un service de fond.

      Etape 4 – Création d’un fichier d’unité pour etcd

      La façon la plus rapide d’exécuter etcd avec Ansible peut être d’utiliser le module command pour exécuter /opt/etcd/bin/etcd . Cependant, cela ne fonctionnera pas car cela fera fonctionner etcd comme un processus de premier plan. L’utilisation du module command entraînera le blocage d’Ansible en attendant le retour de la commande etcd, ce qu’il ne fera jamais. Dans cette étape, nous allons donc mettre à jour notre playbook afin d’utiliser notre binaire etcd en tant que service de fond à la place.

      Ubuntu 18.04 utilise systemd comme système d’initialisation, ce qui signifie que nous pouvons créer de nouveaux services en écrivant des fichiers unitaires et en les plaçant dans le répertoire /etc/systemd/system/.

      Tout d’abord, dans le répertoire de notre projet, créez un nouveau répertoire nommé files/ :

      Ensuite, à l’aide de votre éditeur, créez un nouveau fichier nommé etcd.service dans ce répertoire :

      Ensuite, copiez le bloc de code suivant dans le fichier files/etcd.service :

      ~/playground/etcd-ansible/files/etcd.service

      [Unit]
      Description=etcd distributed reliable key-value store
      
      [Service]
      Type=notify
      ExecStart=/opt/etcd/bin/etcd
      Restart=always
      

      Ce fichier d’unité définit un service qui exécute l’exécutable dans /opt/etcd/bin/etcd , notifie à systemd la fin de l’initialisation, et redémarre toujours s’il se termine.

      Note : Si vous souhaitez en savoir plus sur les fichiers systemd et les fichiers d’unité, ou si vous voulez adapter le fichier d’unité à vos besoins, lisez le guide Comprendre les unités et les fichiers d’unité systemd.

      Fermez et enregistrez le fichier files/etcd.service en appuyant sur CTRL+X suivi de Y .

      Ensuite, nous devons ajouter une tâche à l’intérieur de notre playbook qui copiera le fichier local files/etcd.service dans le répertoire /etc/systemd/system/etcd.service pour chaque noeud géré. Nous pouvons le faire en utilisant le module copy.

      Ouvrez votre playbook :

      • nano $HOME/playground/etcd-ansible/playbook.yaml

      Ajoutez la tâche surlignée suivante à la fin de nos tâches existantes :

      ~/playground/etcd-ansible/playbook.yaml

      - hosts: etcd
        become: True
        tasks:
          ...
          - name: "Set the ETCDCTL_API environment variable to 3"
            lineinfile:
              path: /etc/profile
              line: export ETCDCTL_API=3
              state: present
              create: True
              insertafter: EOF
          - name: "Create a etcd service"
            copy:
              src: files/etcd.service
              remote_src: False
              dest: /etc/systemd/system/etcd.service
              owner: root
              group: root
              mode: 0644
      

      En copiant le fichier unitaire dans le fichier /etc/systemd/system/etcd.service , un service est maintenant défini.

      Sauvegardez et quittez le playbook.

      Exécutez à nouveau la même commande ansible-playbook pour appliquer les nouveaux changements :

      • ansible-playbook -i hosts playbook.yaml

      Pour confirmer que les changements ont bien été appliqués, il faut d’abord faire entrer les SSH dans l’un des nœuds gérés :

      Ensuite, lancez systemctl status etcd pour interroger systemd sur l’état du service etcd :

      Vous trouverez la sortie suivante, qui indique que le service est chargé :

      Output

      ● etcd.service - etcd distributed reliable key-value store Loaded: loaded (/etc/systemd/system/etcd.service; static; vendor preset: enabled) Active: inactive (dead) ...

      Note : La dernière ligne ( Active : inactive (dead)) de la sortie indique que le service est inactif, ce qui signifie qu’il ne sera pas exécuté automatiquement au démarrage du système. Cela est prévu et n’est pas une erreur.

      Appuyez sur q pour revenir au shell, puis exécutez exit pour sortir du nœud géré et revenir à votre shell local :

      Dans cette étape, nous avons mis à jour notre playbook pour faire fonctionner le binaire etcd comme un service systemd. Dans la prochaine étape, nous continuerons à mettre en place etcd en lui fournissant un espace pour stocker ses données.

      Étape 5 – Configurer le répertoire de données

      etcd est un magasin de données à valeur clé, ce qui signifie que nous devons lui fournir de l’espace pour stocker ses données. Dans cette étape, nous allons mettre à jour notre playbook afin de définir un répertoire de données dédié à l’usage d’etcd.

      Ouvrez votre playbook :

      • nano $HOME/playground/etcd-ansible/playbook.yaml

      Ajoutez la tâche suivante à la fin de la liste des tâches :

      ~/playground/etcd-ansible/playbook.yaml

      - hosts: etcd
        become: True
        tasks:
          ...
          - name: "Create a etcd service"
            copy:
              src: files/etcd.service
              remote_src: False
              dest: /etc/systemd/system/etcd.service
              owner: root
              group: root
              mode: 0644
          - name: "Create a data directory"
            file:
              path: /var/lib/etcd/{{ inventory_hostname }}.etcd
              state: directory
              owner: root
              group: root
              mode: 0755
      

      Ici, nous utilisons /var/lib/etcd/hostname.etcd comme répertoire de données, où hostname est le nom d’hôte du nœud géré actuel. inventory_hostname est une variable qui représente le nom d’hôte du nœud géré actuel ; sa valeur est automatiquement alimentée par Ansible. La syntaxe des accolades (c’est-à-dire {{ inventory_hostname }} ) est utilisée pour la substitution de variables, supportée par le moteur de modèle Jinja2, qui est le moteur de modèle par défaut pour Ansible.

      Fermez l’éditeur de texte et enregistrez le fichier.

      Ensuite, nous devons demander à etcd d’utiliser ce répertoire de données. Nous le faisons en passant le point terminal data-dir à etcd. Pour définir les points terminaux etcd, nous pouvons utiliser une combinaison de variables d’environnement, de drapeaux de ligne de commande et de fichiers de configuration. Pour ce tutoriel, nous utiliserons un fichier de configuration, car il est beaucoup plus judicieux d’isoler toutes les configurations dans un fichier, plutôt que d’avoir la configuration éparpillée dans notre playbook.

      Dans le répertoire de votre projet, créez un nouveau répertoire nommé templates/ :

      Ensuite, à l’aide de votre éditeur, créez un nouveau fichier nommé etcd.conf.yaml.j2 dans le répertoire :

      • nano templates/etcd.conf.yaml.j2

      Ensuite, copiez la ligne suivante et collez-la dans le fichier :

      ~/playground/etcd-ansible/templates/etcd.conf.yaml.j2

      data-dir: /var/lib/etcd/{{ inventory_hostname }}.etcd
      

      Ce fichier utilise la même syntaxe de substitution de variables Jinja2 que notre playbook. Pour substituer les variables et télécharger le résultat sur chaque hôte géré, nous pouvons utiliser le module template. Il fonctionne de la même manière que la copie, sauf qu’il effectue une substitution variable avant le téléchargement.

      Sortez d’etcd.conf.yaml.j2 , puis ouvrez votre playbook :

      • nano $HOME/playground/etcd-ansible/playbook.yaml

      Ajoutez les tâches suivantes à la liste des tâches pour créer un répertoire et y télécharger le fichier de configuration modèle :

      ~/playground/etcd-ansible/playbook.yaml

      - hosts: etcd
        become: True
        tasks:
          ...
          - name: "Create a data directory"
            file:
              ...
              mode: 0755
          - name: "Create directory for etcd configuration"
            file:
              path: /etc/etcd
              state: directory
              owner: root
              group: root
              mode: 0755
          - name: "Create configuration file for etcd"
            template:
              src: templates/etcd.conf.yaml.j2
              dest: /etc/etcd/etcd.conf.yaml
              owner: root
              group: root
              mode: 0600
      

      Enregistrez et fermez le fichier.

      Comme nous avons effectué ce changement, nous devons mettre à jour le fichier d’unité de notre service pour lui transmettre l’emplacement de notre fichier de configuration (c’est-à-dire /etc/etcd/etcd.conf.yaml ).

      Ouvrez le fichier de service etcd sur votre machine locale :

      Mettez à jour le fichier files/etcd.service en ajoutant l’indicateur --config-file mis en évidence dans ce qui suit :

      ~/playground/etcd-ansible/files/etcd.service

      [Unit]
      Description=etcd distributed reliable key-value store
      
      [Service]
      Type=notify
      ExecStart=/opt/etcd/bin/etcd --config-file /etc/etcd/etcd.conf.yaml
      Restart=always
      

      Enregistrez et fermez le fichier.

      Dans cette étape, nous avons utilisé notre playbook pour fournir un répertoire de données à etcd afin de stocker ses données. Dans l’étape suivante, nous ajouterons quelques tâches supplémentaires pour redémarrer le service etcd et le lancer au démarrage.

      Étape 6 – Activation et démarrage du service etcd

      Chaque fois que nous apportons des modifications au fichier d’unité d’un service, nous devons redémarrer le service pour qu’elles prennent effet. Nous pouvons le faire en exécutant la commande systemctl restart etcd. En outre, pour que le service etcd démarre automatiquement au démarrage du système, nous devons exécuter systemctl enable etcd . Dans cette étape, nous allons exécuter ces deux commandes en utilisant le playbook.

      Pour exécuter les commandes, nous pouvons utiliser le module de commande :

      • nano $HOME/playground/etcd-ansible/playbook.yaml

      Ajoutez les tâches suivantes à la fin de la liste des tâches :

      ~/playground/etcd-ansible/playbook.yaml

      - hosts: etcd
        become: True
        tasks:
          ...
          - name: "Create configuration file for etcd"
            template:
              ...
              mode: 0600
          - name: "Enable the etcd service"
            command: systemctl enable etcd
          - name: "Start the etcd service"
            command: systemctl restart etcd
      

      Enregistrez et fermez le fichier.

      Exécutez ansible-playbook -i hosts playbook.yaml une fois de plus :

      • ansible-playbook -i hosts playbook.yaml

      Pour vérifier que le service etcd est maintenant redémarré et activé, SSH dans l’un des nœuds gérés :

      Ensuite, lancez systemctl status etcd pour vérifier l’état du service etcd :

      Vous trouverez enabled and active (en cours) comme mis en surbrillance ci-dessous ; cela signifie que les modifications que nous avons apportées à notre playbook ont pris effet :

      Output

      ● etcd.service - etcd distributed reliable key-value store Loaded: loaded (/etc/systemd/system/etcd.service; static; vendor preset: enabled) Active: active (running) Main PID: 19085 (etcd) Tasks: 11 (limit: 2362)

      Dans cette étape, nous avons utilisé le module command pour exécuter des commandes systemctl qui redémarrent et activent le service etcd sur nos nœuds gérés. Maintenant que nous avons mis en place une installation etcd, nous allons, dans la prochaine étape, tester sa fonctionnalité en effectuant quelques opérations de base de création, lecture, mise à jour et suppression (CRUD).

      Étape 7 – Tester etcd

      Bien que nous disposions d’une installation etcd en état de marche, elle n’est pas sûre et n’est pas encore prête à être utilisée pour la production. Mais avant de sécuriser notre installation etcd dans les étapes ultérieures, il faut d’abord comprendre ce qu’etcd peut faire en termes de fonctionnalités. Dans cette étape, nous allons envoyer manuellement des demandes à etcd pour ajouter, récupérer, mettre à jour et supprimer des données.

      Par défaut, etcd expose une API qui écoute sur le port 2379 pour la communication avec les clients. Cela signifie que nous pouvons envoyer des requêtes API brutes à etcd en utilisant un client HTTP. Cependant, il est plus rapide d’utiliser le client officiel etcd (etcdctl), qui permet de créer/mettre à jour, de récupérer et de supprimer des paires clé-valeur en utilisant respectivement les sous-commandes put , get et del.

      Assurez-vous que vous êtes toujours à l’intérieur du nœud géré par etcd1, et exécutez les commandes etcdctl suivantes pour confirmer que votre installation etcd fonctionne.

      D’abord, créez une nouvelle entrée en utilisant la sous-commande put.

      La sous-commande put a la syntaxe suivante :

      etcdctl put key value
      

      Sur etcd1 , lancez la commande suivante :

      La commande que nous venons d’exécuter demande à etcd d’écrire la valeur « bar » dans la clé foo du magasin.

      Vous trouverez alors OK imprimé dans la sortie, ce qui indique que les données ont persisté :

      Output

      OK

      Nous pouvons ensuite récupérer cette entrée en utilisant la sous-commande get, qui a la syntaxe etcdctl get key :

      Vous trouverez ce résultat, qui indique la clé sur la première ligne et la valeur que vous avez insérée plus tôt sur la deuxième ligne :

      Output

      foo bar

      Nous pouvons supprimer l’entrée en utilisant la sous-commande del, qui a la syntaxe etcdctl del key :

      Vous trouverez la sortie suivante, qui indique le nombre d’entrées supprimées :

      Output

      1

      Maintenant, lançons à nouveau la sous-commande get pour tenter de récupérer une paire clé-valeur supprimée :

      Vous ne recevrez pas de sortie, ce qui signifie qu’etcdctl n’est pas en mesure de récupérer la paire clé-valeur. Cela confirme qu’une fois l’entrée supprimée, elle ne peut plus être récupérée.

      Maintenant que vous avez testé les opérations de base d’etcd et d’etcdctl, sortons de notre nœud géré et retournons à votre environnement local :

      Dans cette étape, nous avons utilisé le client etcdctl pour envoyer des demandes à etcd. À ce stade, nous gérons trois instances distinctes d’etcd, chacune agissant indépendamment des autres. Cependant, etcd est conçu comme un magasin de valeurs clés distribué, ce qui signifie que plusieurs instances d’etcd peuvent se regrouper pour former un seul groupe ; chaque instance devient alors un membre du groupe. Après avoir formé un cluster, vous pourrez récupérer une paire clé-valeur insérée à partir d’un autre membre du cluster. Dans la prochaine étape, nous utiliserons notre playbook pour transformer nos trois clusters à un seul nœud en un seul cluster à trois nœuds.

      Étape 8 – Formation d’un cluster à l’aide de la découverte statique

      Pour créer un cluster à 3 nœuds au lieu de trois clusters à 1 nœud, nous devons configurer ces installations etcd pour qu’elles communiquent entre elles. Cela signifie que chacun doit connaître les adresses IP des autres. Ce processus est appelé découverte. La découverte peut se faire soit par configuration statique, soit par découverte dynamique de services. Dans cette étape, nous discuterons de la différence entre les deux, et nous mettrons à jour notre playbook pour créer un cluster etcd en utilisant la découverte statique.

      La découverte par configuration statique est la méthode qui nécessite le moins de configuration ; c’est là que les points terminaux de chaque membre sont passés dans la commande etcd avant qu’elle ne soit exécutée. Pour utiliser la configuration statique, les conditions suivantes doivent être remplies avant l’initialisation du cluster :

      • le nombre de membres est connu
      • les points terminaux de chaque membre sont connus
      • les adresses IP de tous les points terminaux sont statiques

      Si ces conditions ne peuvent être remplies, vous pouvez alors utiliser un service de découverte dynamique. Avec le service de découverte dynamique, toutes les instances s’enregistreraient auprès du service de découverte, qui permet à chaque membre de récupérer des informations sur la localisation des autres membres.

      Comme nous savons que nous voulons un cluster à 3 nœuds etcd, et que tous nos serveurs ont des adresses IP statiques, nous utiliserons la découverte statique. Pour lancer notre cluster en utilisant la découverte statique, nous devons ajouter plusieurs points terminaux à notre fichier de configuration. Utilisez un éditeur pour ouvrir le fichier modèle templates/etcd.conf.yaml.j2 :

      • nano templates/etcd.conf.yaml.j2

      Ajoutez les lignes en surbrillance suivantes :

      ~/playground/etcd-ansible/templates/etcd.conf.yaml.j2

      data-dir: /var/lib/etcd/{{ inventory_hostname }}.etcd
      name: {{ inventory_hostname }}
      initial-advertise-peer-urls: http://{{ hostvars[inventory_hostname]['ansible_facts']['eth1']['ipv4']['address'] }}:2380
      listen-peer-urls: http://{{ hostvars[inventory_hostname]['ansible_facts']['eth1']['ipv4']['address'] }}:2380,http://127.0.0.1:2380
      advertise-client-urls: http://{{ hostvars[inventory_hostname]['ansible_facts']['eth1']['ipv4']['address'] }}:2379
      listen-client-urls: http://{{ hostvars[inventory_hostname]['ansible_facts']['eth1']['ipv4']['address'] }}:2379,http://127.0.0.1:2379
      initial-cluster-state: new
      initial-cluster: {% for host in groups['etcd'] %}{{ hostvars[host]['ansible_facts']['hostname'] }}=http://{{ hostvars[host]['ansible_facts']['eth1']['ipv4']['address'] }}:2380{% if not loop.last %},{% endif %}{% endfor %}
      

      Fermez et enregistrez le fichier templates/etcd.conf.yaml.j2 en appuyant sur CTRL+X suivi de Y .

      Voici une brève explication de chaque point terminal :

      • nom – un nom lisible pour le membre. Par défaut, etcd utilise un identifiant unique, généré de manière aléatoire, pour identifier chaque membre ; cependant, un nom lisible par l’utilisateur nous permet de le référencer plus facilement à l’intérieur des fichiers de configuration et sur la ligne de commande. Ici, nous utiliserons les noms d’hôtes comme noms de membres (c’est-à-dire, etcd1 , etcd2 , et etcd3 ).
      • initial-advertise-peer-urls – une liste d’adresses IP/combinaisons de ports que les autres membres peuvent utiliser pour communiquer avec ce membre. En plus du port API (2379), etcd expose également le port 2380 pour la communication entre les membres d’etcd, qui leur permet de s’envoyer des messages et d’échanger des données. Notez que ces URL doivent être accessibles par ses pairs (et ne pas être une adresse IP locale).
      • listen-peer-urls – une liste d’adresses IP/combinaisons de ports où le membre actuel écoutera les communications des autres membres. Cela doit inclure toutes les URL du drapeau -initial-advertise-peer-urls, mais aussi les URL locales comme 127.0.0.1:2380 . L’adresse IP de destination/le port des messages de pairs entrants doit correspondre à l’une des URL énumérées ici.
      • advertise-client-urls – une liste des combinaisons Adresse IP/port que les clients doivent utiliser pour communiquer avec ce membre. Ces URL doivent être accessibles par le client (et ne pas être une adresse locale). Si le client accède au cluster via l’Internet public, il doit s’agir d’une adresse IP publique.
      • listen-client-urls – une liste des combinaisons Adresses IP/ports où le membre actuel écoutera les communications des clients. Cela doit inclure toutes les URL du drapeau --advertise-client-urls, mais aussi les URL locales comme 127.0.0.1:2379 L’adresse IP de destination/le port des messages clients entrants doit correspondre à l’une des URL énumérées ici.
      • groupe initial – une liste de points terminaux pour chaque membre du groupe. Chaque point terminal doit correspondre aux URL initial-advertise-peer-urls du membre correspondant.
      • initial-cluster-state soi new ou existing.

      Pour assurer la cohérence, etcd ne peut prendre de décisions que lorsque la majorité des nœuds marche bien. C’est ce qu’on appelle établir le quorum. En d’autres termes, dans un groupe de trois membres, le quorum est atteint si deux ou plusieurs des membres fonctionnent bien.

      Si le paramètre initial-cluster-state est réglé sur new,etcd saura qu’il s’agit d’un nouveau cluster en cours de démarrage et permettra aux membres de commencer en parallèle, sans attendre que le quorum soit atteint. Plus concrètement, après l’entrée en fonction du premier membre, le quorum ne sera pas atteint car un tiers (33,33%) est inférieur ou égal à 50%. Normalement, etcd s’arrêtera et refusera d’engager d’autres actions et le cluster ne sera jamais formé. Cependant, comme initial-cluster-state a été fixé comme new, il ignorera le manque initial de quorum.

      S’il est fixé comme existing, le membre essaiera de rejoindre un groupe existant, et s’attend à ce que le quorum soit déjà établi.

      Note : Vous pouvez trouver plus de détails sur tous les drapeaux de configuration pris en charge dans la section Configuration de la documentation d’etcd.

      Dans le fichier modèle mis à jour templates/etcd.conf.yaml.j2, on trouve quelques exemples de hostvars .  Lorsque Ansible s’exécute , il recueillera des variables auprès d’une variété sources. Nous avons déjà utilisé la variable inventory_hostname auparavant, mais il y en a beaucoup d’autres disponibles. Ces variables sont disponibles sous hostvars [inventory_hostname]['ansible_facts'] . Ici, nous extrayons les adresses IP privées de chaque nœud et les utilisons pour construire la valeur de notre point terminal.

      Note : Comme nous avons activé l’option Private Networking lors de la création de nos serveurs, chaque serveur serait associé à trois adresses IP :

      • Une adresse IP debouclage – une adresse qui n’est valable qu’à l’intérieur de la même machine. Elle est utilisée pour que la machine se réfère à elle-même, par exemple, 127.0.0.1
      • Une adresse IP publique – une adresse qui peut être acheminée sur l’Internet public, par exemple, 178.128.169.51 
      • Une adresse IP privée – une adresse qui n’est routable qu’au sein du réseau privé ; dans le cas de DigitalOcean Droplets, il y a un réseau privé au sein de chaque centre de données, par exemple, 10.131.82.225

      Chacune de ces adresses IP est associée à une interface réseau différente : l’adresse de bouclage est associée à l’interface lo, l’adresse IP publique est associée à l’interface eth0, et l’adresse IP privée à l’interface eth1. Nous utilisons l’interface eth1 pour que tout le trafic reste dans le réseau privé, sans jamais atteindre Internet.

      La compréhension des interfaces de réseau n’est pas nécessaire pour cet article, mais si vous souhaitez en savoir plus, Une introduction à la terminologie, aux interfaces et aux protocoles de réseau est un excellent point de départ.

      La syntaxe {% %} Jinja2 définit la structure de la boucle for qui itére à travers chaque nœud du groupe etcd pour construire la chaîne du cluster initial dans un format requis par etcd.

      Pour former le nouveau cluster de trois membres, vous devez d’abord arrêter le service etcd et effacer le répertoire de données avant de lancer le cluster. Pour ce faire, utilisez un éditeur afin d’ouvrir le fichier playbook.yaml sur votre machine locale :

      • nano $HOME/playground/etcd-ansible/playbook.yaml

      Ensuite, avant la tâche « Create a data directory », ajoutez une tâche pour arrêter le service etcd :

      ~/playground/etcd-ansible/playbook.yaml

      - hosts: etcd
        become: True
        tasks:
          ...
              group: root
              mode: 0644
          - name: "Stop the etcd service"
            command: systemctl stop etcd
          - name: "Create a data directory"
            file:
          ...
      

      Ensuite, mettez à jour la tâche « Create a data directory » pour d’abord supprimer le répertoire de données et le recréer :

      ~/playground/etcd-ansible/playbook.yaml

      - hosts: etcd
        become: True
        tasks:
          ...
          - name: "Stop the etcd service"
            command: systemctl stop etcd
          - name: "Create a data directory"
            file:
              path: /var/lib/etcd/{{ inventory_hostname }}.etcd
              state: "{{ item }}"
              owner: root
              group: root
              mode: 0755
            with_items:
              - absent
              - directory
          - name: "Create directory for etcd configuration"
            file:
          ...
      

      La propriété with_items définit une liste de chaînes de caractères sur lesquelles cette tâche va itérer. Cela équivaut à répéter deux fois la même tâche, mais avec des valeurs différentes pour la propriété state. Dans ce cas, nous répétons la liste avec les éléments absents et le répertoire, ce qui garantit que le répertoire de données est d’abord supprimé et ensuite recréé.

      Fermez et enregistrez le fichier playbook.yaml en appuyant sur CTRL+X, puis sur Y. Ensuite, lancez à nouveau ansible-playbook. Ansible va maintenant créer un cluster unique de 3 membres etcd :

      • ansible-playbook -i hosts playbook.yaml

      Vous pouvez vérifier cela en entrant en langage SSH dans n’importe quel nœud membre etcd :

      Ensuite, lancez l’application etcdctl endpoint health --cluster :

      • etcdctl endpoint health --cluster

      Le statut de chaque membre du groupe sera ainsi répertorié :

      Output

      http://etcd2_private_ip:2379 is healthy: successfully committed proposal: took = 2.517267ms http://etcd1_private_ip:2379 is healthy: successfully committed proposal: took = 2.153612ms http://etcd3_private_ip:2379 is healthy: successfully committed proposal: took = 2.639277ms

      Nous avons maintenant réussi à créer un cluster de 3 nœuds etcd. Nous pouvons confirmer cela en ajoutant une entrée à etcd sur un nœud membre, et en la récupérant sur un autre nœud membre. Sur l’un des nœuds membres, lancez etcdctl put :

      Ensuite, utilisez un nouveau terminal pour SSH dans un autre nœud membre :

      Ensuite, essayez de retrouver la même entrée en utilisant la clé :

      Vous pourrez récupérer l’entrée, qui prouve que le cluster fonctionne :

      Output

      foo bar

      Enfin, sortez de chacun des nœuds gérés et revenez à votre machine locale :

      Dans cette étape, nous avons mis en place un nouveau cluster de 3 nœuds. À l’heure actuelle, la communication entre les membres d’etcd et leurs pairs et clients se fait par HTTP. Cela signifie que la communication n’est pas cryptée et que toute partie qui peut intercepter le trafic peut lire les messages. Ce n’est pas un gros problème si le cluster etcd et les clients sont tous déployés dans un réseau privé ou un réseau privé virtuel (VPN) que vous contrôlez entièrement. Toutefois, si une partie du trafic doit passer par un réseau partagé (privé ou public), vous devez alors vous assurer que ce trafic est crypté. En outre, un mécanisme doit être mis en place pour qu’un client ou un pair puisse vérifier l’authenticité du serveur.

      Dans la prochaine étape, nous examinerons comment sécuriser la communication client-serveur et la communication entre pairs en utilisant le TLS.

      Étape 9 – Obtenir les adresses IP privées des nœuds gérés

      Pour crypter les messages entre les nœuds membres, etcd utilise Hypertext Transfer Protocol Secure , ou HTTPS , qui est une couche au-dessus du protocole TLS , ou Transport Layer Security . TLS utilise un système de clés privées, de certificats et d’entités de confiance appelées Certificate Authorities (Autorités de certification) (CA) pour s’authentifier et s’envoyer des messages cryptés.

      Dans ce tutoriel, chaque nœud membre doit générer un certificat pour s’identifier, et faire signer ce certificat par une AC. Nous allons configurer tous les nœuds membres pour qu’ils fassent confiance à cette AC, et donc aussi à tous les certificats qu’elle signe. Cela permet aux nœuds membres de s’authentifier mutuellement.

      Le certificat qu’un nœud membre génère doit permettre aux autres nœuds membres de s’identifier. Tous les certificats comportent le Common Name (Nom Commun) (CN) de l’entité à laquelle il est associé. Cela est souvent utilisé comme identité de l’entité. Toutefois, lors de la vérification d’un certificat, les clients peuvent comparer si les informations qu’ils ont recueillies sur l’entité correspondent à celles qui figurent dans le certificat. Par exemple, lorsqu’un client télécharge le certificat TLS avec l’objet CN=foo.bar.com, mais qu’il se connecte en fait au serveur en utilisant une adresse IP (par exemple 167.71.129.110), il y a alors un décalage et le client peut ne pas faire confiance au certificat. En spécifiant un nom alternatif du sujet (SAN) dans le certificat, il informe le vérificateur que les deux noms appartiennent à la même entité.

      Comme nos membres etcd s’échangent des informations en utilisant leurs adresses IP privées, lorsque nous définirons nos certificats, nous devrons fournir ces adresses IP privées comme noms alternatifs du sujet.

      Pour connaître l’adresse IP privée d’un nœud géré, il faut y entrer en SSH :

      Exécutez ensuite la commande suivante :

      • ip -f inet addr show eth1

      Vous trouverez des résultats similaires aux lignes suivantes :

      Output

      3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000 inet 10.131.255.176/16 brd 10.131.255.255 scope global eth1 valid_lft forever preferred_lft forever

      Dans notre exemple de sortie, 10.131.255.176 est l’adresse IP privée du nœud géré, et la seule information qui nous intéresse. Pour filtrer tout le reste, à l’exception de la propriété intellectuelle privée, nous pouvons transmettre la sortie de la commande ip à l’utilitaire sed, qui est utilisé pour filtrer et transformer le texte.

      • ip -f inet addr show eth1 | sed -En -e 's/.*inet ([0-9.]+).*/1/p'

      Maintenant, la seule sortie est l’adresse IP privée elle-même :

      Output

      10.131.255.176

      Une fois que vous êtes convaincu que la commande précédente fonctionne, quittez le nœud géré :

      Pour incorporer les commandes précédentes dans notre playbook, ouvrez d’abord le fichier playbook.yaml :

      • nano $HOME/playground/etcd-ansible/playbook.yaml

      Ensuite, ajoutez une nouvelle pièce avec une seule tâche avant notre play existante :

      ~/playground/etcd-ansible/playbook.yaml

      ...
      - hosts: etcd
        tasks:
          - shell: ip -f inet addr show eth1 | sed -En -e 's/.*inet ([0-9.]+).*/1/p'
            register: privateIP
      - hosts: etcd
        tasks:
      ...
      

      La tâche utilise le module shell pour exécuter les commandes ip et sed, qui récupère l’adresse IP privée du nœud géré. Elle enregistre ensuite la valeur de retour de la commande shell dans une variable appelée privateIP , que nous utiliserons plus tard.

      Dans cette étape, nous avons ajouté une tâche au playbook pour obtenir l’adresse IP privée des nœuds gérés. Dans l’étape suivante, nous allons utiliser ces informations pour générer des certificats pour chaque nœud membre, et faire signer ces certificats par une autorité de certification (CA).

      Étape 10 – Générer les clés privées et les RSE des membres d’etcd

      Pour qu’un nœud membre puisse recevoir un trafic crypté, l’expéditeur doit utiliser la clé publique du nœud membre pour crypter les données, et le nœud membre doit utiliser sa clé privée pour décrypter le texte chiffré et récupérer les données originales. La clé publique est emballée dans un certificat et signée par une AC pour garantir son authenticité.

      Par conséquent, nous devrons générer une clé privée et une demande de signature de certificat (CSR) pour chaque nœud membre etcd. Pour nous faciliter la tâche, nous allons générer toutes les paires de clés et signer tous les certificats localement, sur le nœud de contrôle, puis copier les fichiers pertinents vers les hôtes gérés.

      Tout d’abord, créez un répertoire appelé artefacts/, où nous placerons les fichiers (clés et certificats) générés au cours du processus. Ouvrez le fichier playbook.yaml avec un éditeur :

      • nano $HOME/playground/etcd-ansible/playbook.yaml

      Dans celui-ci, utilisez le module file pour créer le répertoire artefacts/ :

      ~/playground/etcd-ansible/playbook.yaml

      ...
          - shell: ip -f inet addr show eth1 | sed -En -e 's/.*inet ([0-9.]+).*/1/p'
            register: privateIP
      - hosts: localhost
        gather_facts: False
        become: False
        tasks:
          - name: "Create ./artifacts directory to house keys and certificates"
            file:
              path: ./artifacts
              state: directory
      - hosts: etcd
        tasks:
      ...
      

      Ensuite, ajoutez une autre tâche à la fin du play pour générer la clé privée :

      ~/playground/etcd-ansible/playbook.yaml

      ...
      - hosts: localhost
        gather_facts: False
        become: False
        tasks:
              ...
          - name: "Generate private key for each member"
            openssl_privatekey:
              path: ./artifacts/{{item}}.key
              type: RSA
              size: 4096
              state: present
              force: True
            with_items: "{{ groups['etcd'] }}"
      - hosts: etcd
        tasks:
      ...
      

      La création de clés privées et de CSR peut se faire en utilisant respectivement les modules openssl_privatekey et openssl_csr.

      L’attribut force : True garantit que la clé privée est régénérée à chaque fois, même si elle existe déjà.

      De même, ajoutez la nouvelle tâche suivante au même play pour générer les RSE pour chaque membre, en utilisant le module openssl_csr :

      ~/playground/etcd-ansible/playbook.yaml

      ...
      - hosts: localhost
        gather_facts: False
        become: False
        tasks:
          ...
          - name: "Generate private key for each member"
            openssl_privatekey:
              ...
            with_items: "{{ groups['etcd'] }}"
          - name: "Generate CSR for each member"
            openssl_csr:
              path: ./artifacts/{{item}}.csr
              privatekey_path: ./artifacts/{{item}}.key
              common_name: "{{item}}"
              key_usage:
                - digitalSignature
              extended_key_usage:
                - serverAuth
              subject_alt_name:
                - IP:{{ hostvars[item]['privateIP']['stdout']}}
                - IP:127.0.0.1
              force: True
            with_items: "{{ groups['etcd'] }}"
      

      Nous précisons que ce certificat peut être impliqué dans un mécanisme de signature numérique à des fins d’authentification du serveur. Ce certificat est associé au nom d’hôte (par exemple, etcd1 ), mais le vérificateur doit également traiter les adresses IP privées et de boucle locale de chaque nœud comme des noms alternatifs.  Notez que nous utilisons la variable privateIP que nous avons enregistrée dans le play précédente.

      Fermez et enregistrez le fichier playbook.yaml en appuyant sur CTRL+X suivi de Y. Ensuite, lancez à nouveau ansible-playbook.

      • ansible-playbook -i hosts playbook.yaml

      Nous allons maintenant trouver un nouveau répertoire appelé artefacts dans notre répertoire de projets ; utilisez ls pour en énumérer le contenu :

      Vous trouverez les clés privées et les RSE de chacun des membres d’etcd :

      Output

      etcd1.csr etcd1.key etcd2.csr etcd2.key etcd3.csr etcd3.key

      Dans cette étape, nous avons utilisé plusieurs modules Ansible pour générer des clés privées et des certificats de clé publique pour chacun des nœuds membres. Dans la prochaine étape, nous examinerons comment signer une demande de signature de certificat (CSR).

      Étape 11 – Génération des certificats CA

      Au sein d’un cluster etcd, les nœuds membres cryptent les messages en utilisant la clé publique du destinataire. Pour garantir l’authenticité de la clé publique, le destinataire l’emballe dans une demande de signature de certificat (CSR) et la fait signer par une entité de confiance (c’est-à-dire l’AC). Comme nous contrôlons tous les nœuds membres et les AC auxquelles ils font confiance, nous n’avons pas besoin d’utiliser une AC externe et pouvons agir comme notre propre AC. Dans cette étape, nous allons agir comme notre propre AC, ce qui signifie que nous devrons générer une clé privée et un certificat auto-signé pour fonctionner etant qu’AC.

      Ouvrez le fichier playbook.yaml avec votre éditeur :

      • nano $HOME/playground/etcd-ansible/playbook.yaml

      Ensuite, comme pour l’étape précédente, ajoutez une tâche au play localhost pour générer une clé privée pour l’AC :

      ~/playground/etcd-ansible/playbook.yaml

      - hosts: localhost
        ...
        tasks:
          ...
        - name: "Generate CSR for each member"
          ...
          with_items: "{{ groups['etcd'] }}"
          - name: "Generate private key for CA"
            openssl_privatekey:
              path: ./artifacts/ca.key
              type: RSA
              size: 4096
              state: present
              force: True
      - hosts: etcd
        become: True
        tasks:
          - name: "Create directory for etcd binaries"
      ...
      

      Ensuite, utilisez le module openssl_csr pour générer un nouveau CSR. Cette étape est similaire à la précédente, mais dans ce CSR, nous ajoutons la contrainte de base et l’extension d’utilisation des clés pour indiquer que ce certificat peut être utilisé comme un certificat CA :

      ~/playground/etcd-ansible/playbook.yaml

      - hosts: localhost
        ...
        tasks:
          ...
          - name: "Generate private key for CA"
            openssl_privatekey:
              path: ./artifacts/ca.key
              type: RSA
              size: 4096
              state: present
              force: True
          - name: "Generate CSR for CA"
            openssl_csr:
              path: ./artifacts/ca.csr
              privatekey_path: ./artifacts/ca.key
              common_name: ca
              organization_name: "Etcd CA"
              basic_constraints:
                - CA:TRUE
                - pathlen:1
              basic_constraints_critical: True
              key_usage:
                - keyCertSign
                - digitalSignature
              force: True
      - hosts: etcd
        become: True
        tasks:
          - name: "Create directory for etcd binaries"
      ...
      

      Enfin, utilisez le module openssl_certificate pour auto-signer le CSR :

      ~/playground/etcd-ansible/playbook.yaml

      - hosts: localhost
        ...
        tasks:
          ...
          - name: "Generate CSR for CA"
            openssl_csr:
              path: ./artifacts/ca.csr
              privatekey_path: ./artifacts/ca.key
              common_name: ca
              organization_name: "Etcd CA"
              basic_constraints:
                - CA:TRUE
                - pathlen:1
              basic_constraints_critical: True
              key_usage:
                - keyCertSign
                - digitalSignature
              force: True
          - name: "Generate self-signed CA certificate"
            openssl_certificate:
              path: ./artifacts/ca.crt
              privatekey_path: ./artifacts/ca.key
              csr_path: ./artifacts/ca.csr
              provider: selfsigned
              force: True
      - hosts: etcd
        become: True
        tasks:
          - name: "Create directory for etcd binaries"
      ...
      

      Fermez et enregistrez le fichier playbook.yaml en appuyant sur CTRL+X suivi de Y. Ensuite, relancez notre playbook pour appliquer les changements :

      • ansible-playbook -i hosts playbook.yaml

      Vous pouvez également lancer ls pour vérifier le contenu du répertoire artefacts/ :

      Vous trouverez maintenant le certificat CA nouvellement généré ( ca.crt ) :

      Output

      ca.crt ca.csr ca.key etcd1.csr etcd1.key etcd2.csr etcd2.key etcd3.csr etcd3.key

      Dans cette étape, nous avons généré une clé privée et un certificat auto-signé pour l’AC. Dans la prochaine étape, nous utiliserons le certificat de l’AC pour signer la RSE de chaque membre.

      Étape 12 – Signature des RSE des membres d’etcd

      Dans cette étape, nous allons signer la RSE de chaque nœud membre. Cela sera similaire à la manière dont nous avons utilisé le module openssl_certificate pour auto-signer le certificat CA, mais au lieu d’utiliser le fournisseur auto-signé, nous utiliserons le fournisseur ownca, qui nous permet de signer en utilisant notre propre certificat CA.

      Ouvrez votre playbook :

      • nano $HOME/playground/etcd-ansible/playbook.yaml

      Ajoutez la tâche en surbrillance suivante à la tâche « Generate self-signed CA certificate » (Générer un certificat CA auto-signé) :

      ~/playground/etcd-ansible/playbook.yaml

      - hosts: localhost
        ...
        tasks:
          ...
          - name: "Generate self-signed CA certificate"
            openssl_certificate:
              path: ./artifacts/ca.crt
              privatekey_path: ./artifacts/ca.key
              csr_path: ./artifacts/ca.csr
              provider: selfsigned
              force: True
          - name: "Generate an `etcd` member certificate signed with our own CA certificate"
            openssl_certificate:
              path: ./artifacts/{{item}}.crt
              csr_path: ./artifacts/{{item}}.csr
              ownca_path: ./artifacts/ca.crt
              ownca_privatekey_path: ./artifacts/ca.key
              provider: ownca
              force: True
            with_items: "{{ groups['etcd'] }}"
      - hosts: etcd
        become: True
        tasks:
          - name: "Create directory for etcd binaries"
      ...
      

      Fermez et enregistrez le fichier playbook.yaml en appuyant sur CTRL+X suivi de Y . Ensuite, relancez le playbook pour appliquer les modifications :

      • ansible-playbook -i hosts playbook.yaml

      Maintenant, énumérez le contenu du répertoire artefacts :

      Vous y trouverez la clé privée, le CSR et le certificat de chaque membre de l’etcd et de l’AC :

      Output

      ca.crt ca.csr ca.key etcd1.crt etcd1.csr etcd1.key etcd2.crt etcd2.csr etcd2.key etcd3.crt etcd3.csr etcd3.key

      Dans cette étape, nous avons signé les RSE de chaque nœud membre en utilisant la clé de l’AC. Dans l’étape suivante, nous allons copier les fichiers pertinents dans chaque nœud géré, de sorte qu’etcd ait accès aux clés et aux certificats pertinents pour établir les connexions TLS.

      Étape 13 – Copie de clés privées et de certificats

      Chaque nœud doit avoir une copie du certificat auto-signé de l’AC ( ca.crt ). Chaque nœud membre d’etcd doit également avoir sa propre clé privée et son certificat. Dans cette étape, nous allons télécharger ces fichiers et les placer dans un nouveau répertoire /etc/etcd/ssl/.

      Pour commencer, ouvrez le fichier playbook.yaml avec votre éditeur :

      • nano $HOME/playground/etcd-ansible/playbook.yaml

      Pour effectuer ces changements sur notre playbook Ansible, il faut d’abord mettre à jour la propriété du chemin de la tâche Create directory for etcd configuration pour créer le répertoire /etc/etcd/ssl/ :

      ~/playground/etcd-ansible/playbook.yaml

      - hosts: etcd
        ...
        tasks:
          ...
            with_items:
              - absent
              - directory
          - name: "Create directory for etcd configuration"
            file:
              path: "{{ item }}"
              state: directory
              owner: root
              group: root
              mode: 0755
            with_items:
              - /etc/etcd
              - /etc/etcd/ssl
          - name: "Create configuration file for etcd"
            template:
      ...
      

      Ensuite, à la suite de la tâche modifiée, ajoutez trois autres tâches pour copier les fichiers par-dessus :

      ~/playground/etcd-ansible/playbook.yaml

      - hosts: etcd
        ...
        tasks:
          ...
          - name: "Copy over the CA certificate"
            copy:
              src: ./artifacts/ca.crt
              remote_src: False
              dest: /etc/etcd/ssl/ca.crt
              owner: root
              group: root
              mode: 0644
          - name: "Copy over the `etcd` member certificate"
            copy:
              src: ./artifacts/{{inventory_hostname}}.crt
              remote_src: False
              dest: /etc/etcd/ssl/server.crt
              owner: root
              group: root
              mode: 0644
          - name: "Copy over the `etcd` member key"
            copy:
              src: ./artifacts/{{inventory_hostname}}.key
              remote_src: False
              dest: /etc/etcd/ssl/server.key
              owner: root
              group: root
              mode: 0600
          - name: "Create configuration file for etcd"
            template:
      ...
      

      Fermez et enregistrez le fichier playbook.yaml en appuyant sur CTRL+X, puis sur Y.

      Lancez de nouveau ansible-playbook pour apporter ces changements :

      • ansible-playbook -i hosts playbook.yaml

      Dans cette étape, nous avons réussi à télécharger les clés privées et les certificats vers les nœuds gérés. Après avoir copié les fichiers, nous devons maintenant mettre à jour notre fichier de configuration etcd pour pouvoir les utiliser.

      Étape 14 – Activer le TLS sur etcd

      Dans la dernière étape de ce tutoriel, nous allons mettre à jour certaines configurations Ansible pour activer TLS dans un cluster etcd.

      Tout d’abord, ouvrez le fichier modèle templates/etcd.conf.yaml.j2 à l’aide de votre éditeur :

      • nano $HOME/playground/etcd-ansible/templates/etcd.conf.yaml.j2

      Une fois à l’intérieur, modifiez toutes les URL pour utiliser le protocole https au lieu de http.  De plus, ajoutez une section à la fin du modèle pour spécifier l’emplacement du certificat de l’AC, du certificat du serveur et de la clé du serveur :

      ~/playground/etcd-ansible/templates/etcd.conf.yaml.j2

      data-dir: /var/lib/etcd/{{ inventory_hostname }}.etcd
      name: {{ inventory_hostname }}
      initial-advertise-peer-urls: https://{{ hostvars[inventory_hostname]['ansible_facts']['eth1']['ipv4']['address'] }}:2380
      listen-peer-urls: https://{{ hostvars[inventory_hostname]['ansible_facts']['eth1']['ipv4']['address'] }}:2380,https://127.0.0.1:2380
      advertise-client-urls: https://{{ hostvars[inventory_hostname]['ansible_facts']['eth1']['ipv4']['address'] }}:2379
      listen-client-urls: https://{{ hostvars[inventory_hostname]['ansible_facts']['eth1']['ipv4']['address'] }}:2379,https://127.0.0.1:2379
      initial-cluster-state: new
      initial-cluster: {% for host in groups['etcd'] %}{{ hostvars[host]['ansible_facts']['hostname'] }}=https://{{ hostvars[host]['ansible_facts']['eth1']['ipv4']['address'] }}:2380{% if not loop.last %},{% endif %}{% endfor %}
      
      client-transport-security:
        cert-file: /etc/etcd/ssl/server.crt
        key-file: /etc/etcd/ssl/server.key
        trusted-ca-file: /etc/etcd/ssl/ca.crt
      peer-transport-security:
        cert-file: /etc/etcd/ssl/server.crt
        key-file: /etc/etcd/ssl/server.key
        trusted-ca-file: /etc/etcd/ssl/ca.crt
      

      Fermez et enregistrez le fichier templates/etcd.conf.yaml.j2

      Ensuite, lancez votre playbook Ansible :

      • ansible-playbook -i hosts playbook.yaml

      Puis, SSH dans l’un des nœuds gérés :

      Une fois à l’intérieur, lancez la commande etcdctl endpoint health pour vérifier si les terminaux utilisent le HTTPS, et si tous les membres sont en bon état :

      • etcdctl --cacert /etc/etcd/ssl/ca.crt endpoint health --cluster

      Comme notre certificat CA n’est pas, par défaut, un certificat CA root de confiance installé dans le répertoire /etc/ssl/certs/, nous devons le passer à etcdctl en utilisant le drapeau --cacert.

      Cela donnera le résultat suivant :

      Output

      https://etcd3_private_ip:2379 is healthy: successfully committed proposal: took = 19.237262ms https://etcd1_private_ip:2379 is healthy: successfully committed proposal: took = 4.769088ms https://etcd2_private_ip:2379 is healthy: successfully committed proposal: took = 5.953599ms

      Pour confirmer que le cluster etcd fonctionne réellement, nous pouvons, une fois de plus, créer une entrée sur un nœud membre et la récupérer à partir d’un autre nœud membre :

      • etcdctl --cacert /etc/etcd/ssl/ca.crt put foo "bar"

      Ensuite, utilisez un nouveau terminal pour SSH dans un autre nœud membre :

      Récupérez maintenant la même entrée en utilisant la clé foo :

      • etcdctl --cacert /etc/etcd/ssl/ca.crt get foo

      Ceci renverra l’entrée, montrant la sortie ci-dessous :

      Output

      foo bar

      Vous pouvez faire la même chose sur le troisième nœud pour vous assurer que les trois membres sont opérationnels.

      Conclusion

      Vous avez maintenant approvisionné avec succès un cluster de 3 nœuds etcd, l’avez sécurisé avec TLS et avez confirmé qu’il fonctionne.

      etcd est un outil créé à l’origine par CoreOS .  Pour comprendre l’utilisation d’etcd par rapport à CoreOS, vous pouvez lire Comment utiliser Etcdctl et Etcd, le Distributed Key-Value Store de CoreOS .  L’article vous guide également dans la mise en place d’un modèle de découverte dynamique, ce qui a été discuté mais non démontré dans ce tutoriel.

      Comme mentionné au début de ce tutoriel, etcd est une partie importante de l’écosystème de Kubernetes. Pour en savoir plus sur Kubernetes et le rôle d’etcd en son sein, vous pouvez lire Une introduction à Kubernetes. Si vous déployez etcd dans le cadre d’un cluster Kubernetes, sachez que d’autres outils sont disponibles, tels que kubespray et kubeadm . Pour plus de détails sur ce dernier point, vous pouvez lire Comment créer un cluster Kubernetes en utilisant Kubeadm sur Ubuntu 18.04.

      Enfin, ce tutoriel a fait appel à de nombreux outils, mais n’a pu se plonger dans chacun d’entre eux de manière trop détaillée. Vous trouverez ci-dessous des liens qui vous permettront d’examiner plus en détail chaque outil :



      Source link