Comment configurer et sécuriser un cluster etcd avec Ansible sur Ubuntu 18.04

De Get Docs
Aller à :navigation, rechercher

L'auteur a sélectionné la Wikimedia Foundation pour recevoir un don dans le cadre du programme Write for DOnations.

Introduction

etcd est un magasin clé-valeur distribué utilisé par de nombreuses plates-formes et outils, notamment Kubernetes, Vulcand et Doorman. Dans Kubernetes, etcd est utilisé comme magasin de configuration global 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 enlèvent cette charge administrative, de nombreuses entreprises choisissent toujours d'exécuter des clusters Kubernetes autogérés sur site en raison de la flexibilité qu'il apporte.

La première moitié de cet article vous guidera dans la configuration d'un cluster etcd à 3 nœuds sur les serveurs Ubuntu 18.04. La seconde moitié se concentrera sur la sécurisation du cluster à l'aide de Transport Layer Security, ou TLS. Pour exécuter chaque configuration de manière automatisée, nous utiliserons Ansible tout au long. 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, à l'intérieur de fichiers appelés playbooks.

À la fin de ce didacticiel, vous disposerez d'un cluster etcd sécurisé à 3 nœuds exécuté sur vos serveurs. Vous aurez également 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 :

Avertissement : Étant donné que le but de cet article est de fournir une introduction à la configuration 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 [ X235X]utilisateur racine. Dans une configuration de production, tout nœud exposé à l'Internet public nécessiterait un pare-feu et un utilisateur sudo pour adhérer aux meilleures pratiques de sécurité. Pour plus d'informations, consultez le didacticiel Configuration initiale du serveur avec Ubuntu 18.04.


Étape 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 exécute 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écutera un ensemble de scripts pour provisionner et configurer les nœuds gérés. Dans cette étape, nous testerons 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 provisionner de nouveaux serveurs, mais l'utiliser plus tard 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 d'hôtes (ou inventaire en abrégé). Vous pouvez définir chaque nœud que vous souhaitez gérer avec Ansible dans un fichier d'inventaire, et les organiser en groupes. Ensuite, lors de l'exécution des 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 héberger tous les fichiers de ce tutoriel :

mkdir -p $HOME/playground/etcd-ansible

Entrez ensuite dans le répertoire que vous venez de créer :

cd $HOME/playground/etcd-ansible

Dans le répertoire, créez et ouvrez un fichier d'inventaire vide nommé hosts à l'aide de votre éditeur :

nano $HOME/playground/etcd-ansible/hosts

Dans le fichier hosts, répertoriez chacun de vos nœuds gérés dans le format suivant, en remplaçant les adresses IP publiques mises en surbrillance 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. Sous la définition de groupe, nous listons tous nos nœuds gérés. Chaque ligne commence par un alias (par exemple, etcd1), ce qui nous permet de faire référence à chaque hôte en utilisant un nom facile à retenir au lieu d'une longue adresse IP. Les ansible_host et ansible_user sont des variables Ansible '. Dans ce cas, ils sont utilisés 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 savoir 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.
  • -m command : la fonctionnalité derrière Ansible est fournie par modules. Le module command prend l'argument transmis et l'exécute comme une commande sur chacun des nœuds gérés. Ce didacticiel présentera quelques modules Ansible supplémentaires 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 correctement configuré :

Outputetcd2 | CHANGED | rc=0 >>
etcd2

etcd3 | CHANGED | rc=0 >>
etcd3

etcd1 | CHANGED | rc=0 >>
etcd1

Chaque commande exécutée par Ansible est appelée une tâche. L'utilisation de ansible sur la ligne de commande pour exécuter des tâches s'appelle l'exécution de commandes ad-hoc. L'avantage des commandes ad hoc est qu'elles sont rapides et nécessitent peu de configuration ; l'inconvénient est qu'ils s'exécutent manuellement et ne peuvent donc pas être affectés à un système de contrôle de version tel que Git.

Une légère amélioration consisterait à écrire un script shell et à exécuter nos commandes à l'aide du module de script d'Ansible . Cela nous permettrait d'enregistrer les étapes de configuration que nous avons prises dans le contrôle de version. Cependant, les scripts shell sont impératifs, ce qui signifie que nous sommes responsables de déterminer les commandes à exécuter (les « comment ») pour configurer le système à l'état souhaité. Ansible, d'autre part, préconise une approche déclarative, où nous définissons « quel » l'état souhaité de notre serveur devrait être dans les fichiers de configuration, et Ansible est chargé d'amener le serveur à cet état souhaité.

L'approche déclarative est préférée car l'intention du fichier de configuration est immédiatement transmise, ce qui signifie qu'il est plus facile à comprendre et à maintenir. Cela place également la charge de gérer les cas extrêmes sur Ansible au lieu de l'administrateur, ce qui nous permet d'économiser beaucoup de travail.

Maintenant que vous avez configuré le nœud de contrôle Ansible pour communiquer avec les nœuds gérés, à l'étape suivante, nous vous présenterons Ansible playbooks, 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 avec des playbooks Ansible

Dans 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 l'exécuterons. Le but de cette étape est de démontrer le fonctionnement des playbooks Ansible ; nous effectuerons des tâches beaucoup plus substantielles avec des playbooks dans les étapes ultérieures.

Dans votre répertoire de projet, créez un nouveau fichier nommé playbook.yaml à l'aide de votre éditeur :

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

Dans 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 suivi de Y.

Le playbook contient une liste de plays ; chaque lecture 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 touche hosts. Dans ce playbook, nous avons un jeu qui contient deux tâches. La première tâche exécute la commande hostname à l'aide du module command 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 à l'aide de la commande ansible-playbook :

ansible-playbook -i hosts playbook.yaml

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

OutputPLAY [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   

Noter: ansible-playbook sometimes uses cowsay as a playful way to print the headings. If you find a lot of ASCII-art cows printed on your terminal, now you know why. To disable this feature, set the ANSIBLE_NOCOWS environment variable to 1 prior to running ansible-playbook by running export ANSIBLE_NOCOWS=1 in your 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 l'étape suivante, nous remplacerons ces deux tâches de démonstration par des tâches qui configureront notre cluster etcd.

Étape 3 - Installation d'etcd sur les nœuds gérés

Dans cette étape, nous allons vous montrer les commandes pour installer etcd manuellement et vous montrer comment traduire ces mêmes commandes en tâches dans notre playbook Ansible.

etcd et son client etcdctl sont disponibles sous forme de fichiers binaires, que nous allons télécharger, extraire et déplacer vers un répertoire faisant partie de la variable d'environnement PATH. En cas de configuration manuelle, voici les étapes que nous prendrions sur 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 téléchargent et extraient les fichiers 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 exécutons etcd v3.x, la dernière commande définit la variable d'environnement ETCDCTL_API sur 3.

Remarque : Ici, nous utilisons etcd v3.3.13 conçu 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 GitHub Releases.


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

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

Remplacez l'intégralité 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 :

  • fichier : pour créer le répertoire /opt/etcd/bin, et pour définir ultérieurement les autorisations de fichier pour les binaires etcd et etcdctl.
  • get_url : pour télécharger l'archive compressée gzippée sur les nœuds gérés.
  • unarchive : pour extraire et décompresser les fichiers binaires etcd et etcdctl de l'archive compressée gzippée.
  • lineinfile : pour ajouter une entrée dans le fichier .profile.

Pour appliquer ces modifications, 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 affichera uniquement 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, connectez manuellement SSH à l'un des nœuds gérés et exécutez etcd et etcdctl :

ssh [email protected]_public_ip

etcd1_public_ip correspond aux adresses IP publiques du serveur nommé etcd1. Une fois que vous avez obtenu l'accès SSH, exécutez etcd --version pour imprimer la version d'etcd installée :

etcd --version

Vous trouverez une sortie similaire à celle illustrée ci-dessous, ce qui signifie que le binaire etcd est installé avec succès :

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

Pour confirmer que etcdctl est installé avec succès, exécutez etcdctl version :

etcdctl version

Vous trouverez une sortie semblable à la suivante :

Outputetcdctl 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.

Quittez le 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 plus de tâches à notre jeu pour exécuter etcd en tant que service d'arrière-plan.

Étape 4 - Création d'un fichier d'unité pour etcd

Le moyen le plus rapide d'exécuter etcd avec Ansible peut sembler être d'utiliser le module command pour exécuter /opt/etcd/bin/etcd. Cependant, cela ne fonctionnera pas car cela fera fonctionner etcd en tant que 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. Donc, dans cette étape, nous allons mettre à jour notre playbook pour exécuter notre binaire etcd en arrière-plan service à la place.

Ubuntu 18.04 utilise systemd comme son init system, 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 notre répertoire de projet, créez un nouveau répertoire nommé files/ :

mkdir files

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

nano files/etcd.service

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 sur /opt/etcd/bin/etcd, notifie systemd lorsqu'il a fini de s'initialiser et redémarre toujours s'il se termine.

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


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

Ensuite, nous devons ajouter une tâche dans notre playbook qui copiera le fichier local files/etcd.service dans le répertoire /etc/systemd/system/etcd.service pour chaque nœud 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 en surbrillance 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 unité dans le /etc/systemd/system/etcd.service, un service est maintenant défini.

Enregistrez et quittez le playbook.

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

ansible-playbook -i hosts playbook.yaml

Pour confirmer que les modifications ont été appliquées, connectez-vous d'abord en SSH à l'un des nœuds gérés :

ssh [email protected]_public_ip

Ensuite, exécutez systemctl status etcd pour interroger systemd sur l'état du service etcd :

systemctl status 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)
...

Remarque : 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. Ceci est attendu et non une erreur.


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

exit

Dans cette étape, nous avons mis à jour notre playbook pour exécuter le binaire etcd en tant que service systemd. Dans l'étape suivante, nous continuerons à configurer etcd en lui fournissant un espace pour stocker ses données.

Étape 5 - Configuration du répertoire de données

etcd est un magasin de données clé-valeur, ce qui signifie que nous devons lui fournir un espace pour stocker ses données. Dans cette étape, nous allons mettre à jour notre playbook pour définir un répertoire de données dédié à utiliser par 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 remplie automatiquement par Ansible. La syntaxe des accolades (c'est-à-dire Modèle:Inventory hostname) est utilisée pour substitution de variable, prise en charge 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. Pour ce faire, nous passons le paramètre data-dir à etcd. Pour définir les paramètres etcd, nous pouvons utiliser une combinaison de variables d'environnement, d'indicateurs de ligne de commande et de fichiers de configuration. Pour ce didacticiel, nous utiliserons un fichier de configuration, car il est beaucoup plus simple d'isoler toutes les configurations dans un fichier, plutôt que d'avoir une configuration éparpillée dans notre playbook.

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

mkdir 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 variable Jinja2 que notre playbook. Pour remplacer 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 copy, sauf qu'il effectuera une substitution de variable avant le téléchargement.

Quittez 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 basé sur un 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 ce fichier.

Comme nous avons apporté cette modification, 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 ordinateur local :

nano files/etcd.service

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 ce fichier.

Dans cette étape, nous avons utilisé notre playbook pour fournir un répertoire de données pour etcd pour stocker ses données. Dans l'étape suivante, nous ajouterons quelques tâches supplémentaires pour redémarrer le service etcd et le faire fonctionner 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'il prenne effet. Nous pouvons le faire en exécutant la commande systemctl restart etcd. De plus, 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 à l'aide du playbook.

Pour exécuter des commandes, nous pouvons utiliser le module command :

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 :

ssh [email protected]_public_ip

Ensuite, exécutez systemctl status etcd pour vérifier l'état du service etcd :

systemctl status etcd

Vous trouverez enabled et active (running) comme mis en évidence dans ce qui suit ; 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 les commandes systemctl qui redémarrent et activent le service etcd sur nos nœuds gérés. Maintenant que nous avons configuré une installation d'etcd, nous allons, à l'étape suivante, 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 ayons une installation etcd fonctionnelle, elle n'est pas sécurisée et n'est pas encore prête pour une utilisation en production. Mais avant de sécuriser notre configuration d'etcd dans les étapes ultérieures, commençons par comprendre ce qu'etcd peut faire en termes de fonctionnalité. 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 client. 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 etcd officiel etcdctl, qui vous permet de créer/mettre à jour, de récupérer et de supprimer des paires clé-valeur à l'aide de put, get et Les sous-commandes del, respectivement.

Assurez-vous que vous êtes toujours dans le nœud géré etcd1 et exécutez les commandes etcdctl suivantes pour confirmer que votre installation etcd fonctionne.

Commencez par créer une nouvelle entrée à l'aide de la sous-commande put.

La sous-commande put a la syntaxe suivante :

etcdctl put key value

Sur etcd1, exécutez la commande suivante :

etcdctl put foo "bar"

La commande que nous venons d'exécuter indique à etcd d'écrire la valeur "bar" sur la clé foo dans le magasin.

Vous trouverez alors OK imprimé dans la sortie, qui indique les données persistantes :

OutputOK

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

etcdctl get foo

Vous trouverez cette sortie, qui affiche la clé sur la première ligne et la valeur que vous avez insérée précédemment sur la deuxième ligne :

Outputfoo
bar

Nous pouvons supprimer l'entrée à l'aide de la sous-commande del, qui a la syntaxe etcdctl del key :

etcdctl del foo

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

Output1

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

etcdctl get foo

Vous ne recevrez pas de sortie, ce qui signifie que etcdctl est incapable 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 de etcdctl, quittons notre nœud géré et revenons à votre environnement local :

exit

Dans cette étape, nous avons utilisé le client etcdctl pour envoyer des requêtes à etcd. À ce stade, nous exécutons trois instances distinctes d'etcd, chacune agissant indépendamment l'une de l'autre. Cependant, etcd est conçu comme un magasin clé-valeur distribué, ce qui signifie que plusieurs instances d'etcd peuvent se regrouper pour former un seul cluster ; chaque instance devient alors un membre du cluster. Après avoir formé un cluster, vous pourrez récupérer une paire clé-valeur qui a été insérée à partir d'un autre membre du cluster. Dans la prochaine étape, nous utiliserons notre playbook pour transformer nos 3 clusters à un seul nœud en un seul cluster à 3 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 être effectuée à l'aide de la configuration statique ou de la découverte de service dynamique. Dans cette étape, nous discuterons de la différence entre les deux et mettrons à jour notre playbook pour configurer un cluster etcd à l'aide de 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 extrémités de chaque membre sont transmises à la commande etcd avant son exécution. Pour utiliser la configuration statique, les conditions suivantes doivent être remplies avant l'initialisation du cluster :

  • le nombre de membres est connu
  • les extrémités de chaque membre sont connues
  • les adresses IP de tous les terminaux sont statiques

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

Puisque nous savons que nous voulons un cluster etcd à 3 nœuds et que tous nos serveurs ont des adresses IP statiques, nous utiliserons la découverte statique. Pour initier notre cluster à l'aide de la découverte statique, nous devons ajouter plusieurs paramètres à notre fichier de configuration. Utilisez un éditeur pour ouvrir le fichier modèle templates/etcd.conf.yaml.j2 :

nano templates/etcd.conf.yaml.j2

Ensuite, 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 paramètre :

  • name - un nom lisible pour le membre. Par défaut, etcd utilise un identifiant unique généré aléatoirement pour identifier chaque membre ; cependant, un nom lisible par l'homme nous permet de le référencer plus facilement dans les fichiers de configuration et sur la ligne de commande. Ici, nous utiliserons les noms d'hôte comme noms de membre (c'est-à-dire etcd1, etcd2 et etcd3).
  • initial-advertise-peer-urls - une liste de combinaisons adresse IP/port que d'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 pairs entre les membres d'etcd, ce 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 non être une adresse IP locale).
  • listen-peer-urls - une liste de combinaisons adresse IP/port où le membre actuel écoutera les communications des autres membres. Cela doit inclure toutes les URL du drapeau --initial-advertise-peer-urls, mais aussi des URL locales comme 127.0.0.1:2380. L'adresse IP/le port de destination des messages homologues entrants doit correspondre à l'une des URL répertoriées ici.
  • advertise-client-urls - une liste de combinaisons adresse IP/port que les clients doivent utiliser pour communiquer avec ce membre. Ces URL doivent être accessibles par le client (et non être une adresse locale). Si le client accède au cluster via Internet public, il doit s'agir d'une adresse IP publique.
  • listen-client-urls - une liste de combinaisons adresse IP/port où le membre actuel écoutera les communications des clients. Cela doit inclure toutes les URL du drapeau --advertise-client-urls, mais aussi des URL locales comme 127.0.0.1:2379. L'adresse IP/le port de destination des messages clients entrants doit correspondre à l'une des URL répertoriées ici.
  • initial-cluster - une liste de points de terminaison pour chaque membre du cluster. Chaque point de terminaison doit correspondre à l'une des URL initial-advertise-peer-urls du membre correspondant.
  • initial-cluster-state - soit new ou existing.

Pour assurer la cohérence, etcd ne peut prendre des décisions que lorsque la majorité des nœuds sont sains. C'est ce qu'on appelle l'établissement du quorum. En d'autres termes, dans un groupe de trois membres, le quorum est atteint si au moins deux des membres sont en bonne santé.

Si le paramètre initial-cluster-state est défini sur new, etcd saura qu'il s'agit d'un nouveau cluster en cours d'amorçage et permettra aux membres de démarrer en parallèle, sans attendre que le quorum soit atteint. être atteint. Plus concrètement, après l'entrée en fonction du premier membre, celui-ci n'aura pas le quorum car un tiers (33,33%) est inférieur ou égal à 50%. Normalement, etcd s'arrêtera et refusera de commettre d'autres actions et le cluster ne sera jamais formé. Cependant, avec initial-cluster-state réglé sur new, il ignorera le manque initial de quorum.

S'il est défini sur existing, le membre essaiera de rejoindre un cluster existant et s'attend à ce qu'un quorum soit déjà établi.

Remarque : Vous pouvez trouver plus de détails sur tous les indicateurs de configuration pris en charge dans la section Configuration de la documentation d'etcd.


Dans le fichier de modèle templates/etcd.conf.yaml.j2 mis à jour, il existe quelques instances de hostvars. Lorsqu'Ansible s'exécute, il collecte des variables à partir de diverses sources. Nous avons déjà utilisé la variable inventory_hostname auparavant, mais il y en a beaucoup plus 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 notre valeur de paramètre.

Remarque : Étant donné que nous avons activé l'option Réseau privé lors de la création de nos serveurs, chaque serveur aurait trois adresses IP associées :

  • Une adresse IP loopback - une adresse qui n'est valide qu'à l'intérieur de la même machine. Il est utilisé pour que la machine se réfère à elle-même, par exemple, 127.0.0.1
  • Une adresse IP publique - une adresse routable sur l'Internet public, par exemple, 178.128.169.51
  • Une adresse IP privée - une adresse routable uniquement au sein du réseau privé ; dans le cas de DigitalOcean Droplets, il existe un réseau privé dans 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 afin que tout le trafic reste dans le réseau privé, sans jamais atteindre Internet.

La compréhension des interfaces réseau n'est pas requise 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 boucle for qui parcourt chaque nœud du groupe etcd pour construire la chaîne initial-cluster dans un format requis par etcd .

Pour former le nouveau cluster à 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 pour 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 sur lesquelles cette tâche va itérer. Cela revient à répéter deux fois la même tâche mais avec des valeurs différentes pour la propriété state. Ici, nous parcourons la liste avec les éléments absent et directory, ce qui garantit que le répertoire de données est d'abord supprimé puis recréé après.

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

ansible-playbook -i hosts playbook.yaml

Vous pouvez vérifier cela en vous connectant en SSH à n'importe quel nœud membre etcd :

ssh [email protected]_public_ip

Exécutez ensuite etcdctl endpoint health --cluster :

etcdctl endpoint health --cluster

Cela répertoriera la santé de chaque membre du cluster :

Outputhttp://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 créé avec succès un cluster etcd à 3 nœuds. Nous pouvons le confirmer 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, exécutez etcdctl put :

etcdctl put foo "bar"

Ensuite, utilisez un nouveau terminal pour vous connecter en SSH à un autre nœud membre :

ssh [email protected]_public_ip

Ensuite, essayez de récupérer la même entrée à l'aide de la clé :

etcdctl get foo

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

Outputfoo
bar

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

exit
exit

Dans cette étape, nous avons provisionné un nouveau cluster à 3 nœuds. À l'heure actuelle, la communication entre les membres etcd et leurs pairs et clients s'effectue via 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 au sein d'un réseau privé ou d'un réseau privé virtuel (VPN) que vous contrôlez entièrement. Cependant, si le trafic doit transiter par un réseau partagé (privé ou public), vous devez vous assurer que ce trafic est crypté. De plus, un mécanisme doit être mis en place pour qu'un client ou un pair vérifie l'authenticité du serveur.

Dans l'étape suivante, nous verrons comment sécuriser la communication client-serveur ainsi que la communication entre pairs à l'aide de TLS.

Étape 9 - Obtention des adresses IP privées des nœuds gérés

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

Dans ce didacticiel, chaque nœud membre doit générer un certificat pour s'identifier et faire signer ce certificat par une autorité de certification. Nous allons configurer tous les nœuds membres pour qu'ils fassent confiance à cette autorité de certification, et donc également à tous les certificats qu'elle signe. Cela permet aux nœuds membres de s'authentifier mutuellement les uns avec les autres.

Le certificat généré par un nœud membre doit permettre aux autres nœuds membres de s'identifier. Tous les certificats incluent le Common Name (CN) de l'entité à laquelle il est associé. Ceci est souvent utilisé comme identité de l'entité. Cependant, lors de la vérification d'un certificat, les implémentations clientes peuvent comparer si les informations qu'elles ont collectées sur l'entité correspondent à ce qui a été donné dans le certificat. Par exemple, lorsqu'un client télécharge le certificat TLS avec pour objet CN=foo.bar.com, mais que le client se connecte en fait au serveur à l'aide d'une adresse IP (par exemple, 167.71.129.110), il y a alors une incompatibilité et le client peut ne pas faire confiance au certificat. En spécifiant un nom alternatif de sujet (SAN) dans le certificat, il informe le vérificateur que les deux noms appartiennent à la même entité.

Étant donné que nos membres etcd s'appairent les uns avec les autres en utilisant leurs adresses IP privées, lorsque nous définissons 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é, connectez-vous en SSH :

ssh [email protected]_public_ip

Exécutez ensuite la commande suivante :

ip -f inet addr show eth1

Vous trouverez une sortie similaire aux lignes suivantes :

Output3: 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 l'adresse IP privée, nous pouvons diriger la sortie de la commande ip vers 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 :

Output10.131.255.176

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

exit

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 un nouveau jeu avec une seule tâche avant notre jeu existant :

~/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èrent l'adresse IP privée du nœud géré. Il enregistre la valeur de retour de la commande shell dans une variable nommé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ération des clés privées et des CSR des membres etcd

Pour qu'un nœud membre reçoive du trafic chiffré, l'expéditeur doit utiliser la clé publique du nœud membre pour chiffrer les données, et le nœud membre doit utiliser sa clé privée pour déchiffrer le texte chiffré et récupérer les données d'origine. La clé publique est intégrée dans un certificat et signée par une autorité de certification 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 sur les hôtes gérés.

Tout d'abord, créez un répertoire appelé artifacts/, 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 artifacts/ :

~/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 jeu 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 être effectuée à l'aide des modules openssl_privatekey et openssl_csr, respectivement.

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 jeu pour générer les CSR 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 de bouclage privées et locales de chaque nœud comme des noms alternatifs. Notez que nous utilisons la variable privateIP que nous avons enregistrée dans le jeu précédent.

Fermez et enregistrez le fichier playbook.yaml en appuyant sur CTRL+X suivi de Y. Ensuite, exécutez à nouveau notre playbook :

ansible-playbook -i hosts playbook.yaml

Nous allons maintenant trouver un nouveau répertoire appelé artifacts dans notre répertoire de projet ; utilisez ls pour lister son contenu :

ls artifacts

Vous trouverez les clés privées et CSR pour chacun des membres d'etcd :

Outputetcd1.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 l'étape suivante, nous verrons comment signer une demande de signature de certificat (CSR).

Étape 11 - Génération de certificats CA

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

Commencez par ouvrir le fichier playbook.yaml avec votre éditeur :

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

Ensuite, comme à l'étape précédente, ajoutez une tâche au jeu localhost pour générer une clé privée pour l'autorité de certification :

~/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. Ceci est similaire à l'étape précédente, mais dans ce CSR, nous ajoutons la contrainte de base et l'extension d'utilisation de clé pour indiquer que ce certificat peut être utilisé comme 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, exécutez à nouveau notre playbook pour appliquer les modifications :

ansible-playbook -i hosts playbook.yaml

Vous pouvez également exécuter ls pour vérifier le contenu du répertoire artifacts/ :

ls artifacts/

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

Outputca.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'autorité de certification. Dans l'étape suivante, nous utiliserons le certificat CA pour signer le CSR de chaque membre.

Étape 12 — Signature des CSR des membres d'etcd

Dans cette étape, nous allons signer le CSR de chaque nœud membre. Ce sera similaire à la façon dont nous avons utilisé le module openssl_certificate pour auto-signer le certificat CA, mais au lieu d'utiliser le fournisseur selfsigned, 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" :

~/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, exécutez à nouveau le playbook pour appliquer les modifications :

ansible-playbook -i hosts playbook.yaml

Maintenant, répertoriez le contenu du répertoire artifacts/ :

ls artifacts/

Vous trouverez la clé privée, le CSR et le certificat pour chaque membre etcd et l'AC :

Outputca.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 CSR de chaque nœud membre à l'aide de la clé de l'autorité de certification. Dans l'étape suivante, nous allons copier les fichiers pertinents dans chaque nœud géré, afin qu'etcd ait accès aux clés et certificats pertinents pour configurer les connexions TLS.

Étape 13 - Copie des clés privées et des certificats

Chaque nœud doit avoir une copie du certificat auto-signé de l'autorité de certification (ca.crt). Chaque nœud membre etcd doit également avoir sa propre clé privée et son propre 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 modifications sur notre playbook Ansible, mettez d'abord à jour la propriété path 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, après la tâche modifiée, ajoutez trois autres tâches pour copier les fichiers :

~/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 suivi de Y.

Exécutez à nouveau ansible-playbook pour apporter ces modifications :

ansible-playbook -i hosts playbook.yaml

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

Étape 14 - Activation de TLS sur etcd

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

Commencez par ouvrir le fichier de 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 https comme protocole au lieu de http. De plus, ajoutez une section à la fin du modèle pour spécifier l'emplacement du certificat CA, du certificat de serveur et de la clé de 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, exécutez votre playbook Ansible :

ansible-playbook -i hosts playbook.yaml

Ensuite, connectez-vous en SSH à l'un des nœuds gérés :

ssh [email protected]_public_ip

Une fois à l'intérieur, exécutez la commande etcdctl endpoint health pour vérifier si les terminaux utilisent HTTPS et si tous les membres sont sains :

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

Étant donné que notre certificat CA n'est pas, par défaut, un certificat CA racine de confiance installé dans le répertoire /etc/ssl/certs/, nous devons le transmettre à etcdctl à l'aide de l'indicateur --cacert.

Cela donnera la sortie suivante :

Outputhttps://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, encore une fois, 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"

Utilisez un nouveau terminal pour vous connecter en SSH à un autre nœud :

ssh [email protected]_public_ip

Récupérez maintenant la même entrée à l'aide de la clé foo :

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

Cela renverra l'entrée, montrant la sortie ci-dessous :

Outputfoo
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 correctement provisionné un cluster etcd à 3 nœuds, l'avez sécurisé avec TLS et 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 How To Use Etcdctl and Etcd, CoreOS's Distributed Key-Value Store. L'article vous guide également dans la configuration d'un modèle de découverte dynamique, ce qui a été abordé mais non démontré dans ce didacticiel.

Comme mentionné au début de ce didacticiel, etcd est une partie importante de l'écosystème Kubernetes. Pour en savoir plus sur Kubernetes et le rôle d'etcd en son sein, vous pouvez lire An Introduction to Kubernetes. Si vous déployez etcd dans le cadre d'un cluster Kubernetes, sachez qu'il existe d'autres outils disponibles, tels que kubespray et kubeadm. Pour plus de détails sur ce dernier, vous pouvez lire Comment créer un cluster Kubernetes à l'aide de Kubeadm sur Ubuntu 18.04.

Enfin, ce didacticiel a utilisé de nombreux outils, mais n'a pas pu plonger dans chacun d'eux trop en détail. Dans ce qui suit, vous trouverez des liens qui fourniront un examen plus détaillé de chaque outil :