One place for hosting & domains

      Cómo configurar y proteger un clúster etcd con Ansible en Ubuntu 18.04


      El autor seleccionó a Wikimedia Foundation para recibir una donación como parte del programa Write for DOnations.

      Introducción

      etcd es un almacén de clave-valor distribuido en el que se basan muchas plataformas y herramientas, como Kubernetes, Vulcand y Doorman. En Kubernetes, etcd se utiliza como una tienda de configuración global que almacena el estado del clúster. Saber administrar etcd es esencial para gestionar clústeres de Kubernetes. Si bien hay muchas ofertas de Kubernetes gestionados, conocidos como Kubernetes como servicio, que eliminan esta carga administrativa, muchas empresas siguen prefiriendo ejecutar clústeres autogestionados de Kubernetes en sus instalaciones debido a la flexibilidad que ofrecen.

      La primera mitad de este artículo lo guiará en el proceso de configuración de un clúster etcd de 3 nodos en servidores de Ubuntu 18.04. La segunda mitad se centrará en la protección del clúster usando Transport Layer Security, o TLS. Para ejecutar cada configuración de forma automatizada, utilizaremos Ansible en todo momento. Ansible es una herramienta de administración de configuración similar a Puppet, Chef y SaltStack que nos permite definir cada paso de configuración de manera declarativa dentro de archivos denominados cuadernos de estrategias.

      Al final de este tutorial, tendrá un clúster etcd de 3 nodos en ejecución en sus servidores. También tendrá un cuaderno de estrategias de Ansible que le permite recrear de manera repetida y consistente la misma configuración en conjuntos de servidores nuevos.

      Requisitos previos

      Para completar esta guía, necesitará lo siguiente:

      Advertencia: Como el propósito de este artículo es proporcionar una introducción a los ajustes de un clúster etcd en una red privada, los tres servidores de Ubuntu 18.04 de esta configuración no se probaron con un firewall y se accedió a ellos con un usuario root. En una configuración de producción, todos los nodos expuestos a una red pública de Internet requerirían un firewall y un usuario sudo para respetar las prácticas recomendadas de seguridad. Para obtener más información, consulte el tutorial Configuración inicial para servidores con Ubuntu 18.04.

      Paso 1: Configurar Ansible para el nodo de control

      Ansible es una herramienta que se utiliza para administrar servidores. Los servidores que administra Ansible se denominan nodos administrados y el equipo que ejecuta Ansible, nodo de control. Ansible utiliza las claves SSH del nodo de control para obtener acceso a los nodos administrados. Una vez que se establezca una sesión SSH, Ansible ejecutará un conjunto de secuencias de comandos para proporcionar y configurar los nodos administrados. En este paso, comprobaremos que podemos usar Ansible para establecer conexión con nodos administrados y ejecutar el comando hostname.

      Un día típico de un administrador de sistemas puede implicar administrar diferentes conjuntos de nodos. Por ejemplo, puede usar Ansible para proporcionar servidores nuevos, pero utilizarlo más adelante para volver a configurar otro conjunto de servidores. Ansible proporciona el concepto de inventario de host (o inventario para abreviar) para permitir a los administradores organizar mejor el conjunto de nodos administrados. Puede definir todos los nodos que desee administrar con Ansible en un archivo de inventario y organizarlos en grupos. Luego, al ejecutar los comandos ansible y ansible-playbook, puede especificar los hosts o los grupos a los que se aplica el comando.

      Ansible lee el archivo de inventario de /etc/ansible/hosts de forma predeterminada; sin embargo, podemos especificar un archivo de inventario diferente utilizando el indicador --inventory (o -i para abreviar).

      Para comenzar, cree un directorio nuevo en su equipo local (el nodo de control) para alojar todos los archivos de este tutorial:

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

      Luego, ingrese el directorio que acaba de crear:

      • cd $HOME/playground/etcd-ansible

      Dentro del directorio, cree y abra un archivo de inventario en blanco denominado hosts con su editor:

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

      Dentro del archivo hosts, enumere cada uno de sus nodos administrados con el siguiente formato, sustituyendo las direcciones IP públicas resaltadas por las de sus servidores:

      ~/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 línea [etcd] define un grupo denominado etcd. En la definición del grupo, enumeramos todos nuestros nodos administrados. Cada línea comienza con un alias (por ejemplo, etcd1), lo que nos permite referirnos a cada host usando un nombre fácil de recordar en vez de una dirección IP larga. ansible_host y ansible_user son variables de Ansible. En este caso, se utilizan para proporcionarle a Ansible las direcciones IP públicas y los nombres de usuario de SSH que se deben utilizar para la conexión a través de SSH.

      Para asegurarnos de que Ansible pueda conectarse con nuestros nodos administrados, podemos probar la conectividad al utilizar Ansible para ejecutar el comando hostname en cada uno de los hosts del grupo etcd:

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

      Desglosemos este comando para aprender lo que significa cada sección:

      • etcd: especifica el patrón de host que se utiliza para determinar qué hosts del inventario se administran con este comando. Aquí, utilizamos el nombre del grupo como patrón de host.
      • -i hosts: especifica el archivo de inventario que se debe utilizar.
      • -m command: la funcionalidad detrás de Ansible la proporcionan los módulos. El módulo command toma el argumento pasado y lo ejecuta como comando en cada uno de los nodos administrados. Presentaremos algunos módulos más de Ansible más adelante en este tutorial.
      • -a hostname: es el argumento que se pasa al módulo. La cantidad y los tipos de argumentos dependen del módulo.

      Después de ejecutar el comando, obtendrá el siguiente resultado, que indica que Ansible está configurado correctamente:

      Output

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

      Los comandos que ejecuta Ansible se denominan tareas. El uso de ansible en la línea de comandos para ejecutar tareas se denomina comandos ad-hoc. La ventaja de los comandos ad-hoc es que son rápidos y requieren poca configuración; la desventaja es que se ejecutan manualmente y, por lo tanto, no pueden utilizarse con sistemas de control de versiones como Git.

      Una leve mejora sería escribir una secuencia de comandos de shell y ejecutar nuestros comandos usando el módulo script de Ansible. Esto nos permitiría registrar los pasos de configuración que tomamos en un control de versiones. Sin embargo, las secuencias de comandos de shell son imperativas, lo que significa que debemos asumir la tarea de determinar los comandos que se deben ejecutar para configurar el sistema de acuerdo al estado deseado (es decir, la manera de hacerlo). Ansible, por otro lado, promueve un enfoque declarativo en el que nosotros definimos el estado deseado que debería tener nuestro servidor en los archivos de configuración y Ansible se encarga de que lo alcance.

      El enfoque declarativo es preferible porque la intención del archivo de configuración se transmite de forma inmediata, por lo que es más fácil de entender y mantener. También atribuye la manipulación de casos de borde a Ansible en lugar de al administrador, lo que nos ahorra mucho trabajo.

      Ahora que configuró el nodo de control de Ansible para establecer conexión con los nodos administrados, en el siguiente paso, le presentaremos los cuadernos de estrategias de Ansible, que permiten especificar tareas de forma declarativa.

      Paso 2: Obtener nombres de los host de nodos administrados con cuadernos de estrategias de Ansible

      En este paso, vamos a replicar lo que hicimos en el Paso 1, imprimir los nombres de host de los nodos administrados, pero, en vez de ejecutar tareas ad-hoc, definiremos todas las tareas de forma declarativa como cuadernos de estrategias de Ansible y las ejecutaremos. El propósito de este paso es demostrar cómo funcionan los cuadernos de estrategias de Ansible; realizaremos tareas mucho más importantes con ellos en pasos posteriores.

      Cree un archivo nuevo denominado playbook.yaml en el directorio de su proyecto con su editor:

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

      Añada las siguientes líneas en playbook.yaml:

      ~/playground/etcd-ansible/playbook.yaml

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

      Cierra y guarde el archivo playbook.yaml presionando CTRL+X y, luego, Y.

      El playbook contiene una lista de estrategias; y cada una de ellas contiene una lista de tareas que se deben ejecutar en todos los hosts que coincidan con el patrón de host que especifica la clave hosts. En este cuaderno de estrategias, tenemos una estrategia que contiene dos tareas. La primera tarea ejecuta el comando hostname utilizando el módulo command y registra el resultado en una variable denominada output. En la segunda tarea, usamos el módulo debug para imprimir la propiedad stdout_lines de la variable output.

      Ahora, podemos ejecutar este cuaderno de estrategias utilizando el comando ansible-playbook:

      • ansible-playbook -i hosts playbook.yaml

      Obtendrá el siguiente resultado, que indica que su cuaderno de estrategias está funcionando correctamente:

      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

      Nota: a veces, ansible-playbook utiliza cowsay como manera lúdica de imprimir los encabezamientos. Si encuentra muchos dibujos de una vaca en código ASCII en su terminal, ahora sabe el motivo. Para desactivar esta función, establezca la variable de entorno ANSIBLE_NOCOWS en 1 antes de ejecutar ansible-playbook ejecutando export ANSIBLE_NOCOWS=1 en su shell.

      En este paso, pasamos de ejecutar tareas ad-hoc imperativas a ejecutar cuadernos de estrategias declarativos. En el siguiente paso, sustituiremos estas dos tareas de demostración con tareas que configurarán nuestro clúster etcd.

      Paso 3: Instalar etcd en nodos administrados

      En este paso, le presentaremos los comandos necesarios para instalar etcd de forma manual y le demostraremos cómo convertir esos mismos comandos en tareas de nuestro cuaderno de estrategias de Ansible.

      etcd y su cliente etcdctl están disponibles como binarios, que descargaremos, extraeremos y colocaremos en un directorio que es parte de la variable de entorno PATH. Estos son los pasos que se deben llevar a cabo en cada uno de los nodos administrados al realizar la configuración de forma manual:

      • 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

      Con los primeros cuatro comandos, se descargan los binarios y se extraen en el directorio /opt/etcd/bin/ . El cliente etcdctl usará API v2 para comunicarse con el servidor etcd de manera predeterminada. Como estamos ejecutando etcd v3.x, el último comando establece la variable de entorno ETCDCTL_API en 3.

      Nota: Aquí, utilizamos etcd v3.3.13 en un equipo con procesadores que utilizan el conjunto de instrucciones AMD64. Puede encontrar binarios de otros sistemas y otras versiones en la página oficial de Lanzamientos de GitHub.

      Para replicar los mismos pasos en un formato estandarizado, podemos agregar tareas a nuestro cuaderno de estrategias. Abra el archivo playbook.yaml en su editor:

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

      Sustituya el archivo playbook.yaml en su totalidad por el siguiente contenido:

      ~/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
      

      Cada tarea utiliza un módulo; en este conjunto de tareas, estamos utilizando los siguientes:

      • file: para crear el directorio /opt/etcd/bin y, más adelante, establecer los permisos del archivo para los binarios etcd y etcdctl.
      • get_url: para descargar el paquete tarball comprimido con gzip en los nodos administrados.
      • unarchive: para extraer y desempaquetar los binarios etcd y etcdctl del paquete tarball comprimido con gzip.
      • lineinfile: para agregar una entrada en el archivo .profile.

      Para aplicar estos cambios, cierre y guarde el archivo playbook.yaml presionando CTRL+X y, luego, Y. A continuación, vuelva a ejecutar el mismo comando ansible-playbook en la terminal:

      • ansible-playbook -i hosts playbook.yaml

      La sección PLAY RECAP del resultado solo mostrará ok y 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

      Para confirmar si etcd se instaló de forma correcta, ingrese con SSH de forma manual a uno de los nodos administrados y ejecute etcd y etcdctl:

      etcd1_public_ip es la dirección IP pública del servidor denominado etcd1. Una vez que haya obtenido acceso mediante SSH, ejecute etcd --version para imprimir la versión de etcd instalada:

      Obtendrá un resultado similar al siguiente, que indica que el binario etcd está instalado correctamente:

      Output

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

      Para confirmar que etcdctl se haya instalado correctamente, ejecute la versión etcdctl:

      Verá un resultado similar al siguiente:

      Output

      etcdctl version: 3.3.13 API version: 3.3

      Tenga en cuenta que el resultado dice API version: 3.3, lo que también confirma que nuestra variable de entorno ETCDCTL_API se estableció correctamente.

      Salga del servidor etcd1 para regresar a su entorno local.

      Con esto, instalamos etcd y etcdctl en todos nuestros nodos administrados correctamente. En el siguiente paso, añadiremos más tareas a nuestra estrategia para ejecutar etcd como servicio en segundo plano.

      Paso 4: Crear un archivo de unidad para etcd

      La manera más rápida de ejecutar etcd con Ansible parece ser ejecutar /opt/etcd/bin/etcd con el módulo command. Sin embargo, no funcionará, porque hará que etcd se ejecute como proceso en primer plano. El uso del módulo command hará que Ansible no responda mientras espera que se devuelva el comando etcd, lo que no sucederá en ningún momento. Por lo tanto, en este paso, vamos a actualizar nuestro cuaderno de estrategias para ejecutar nuestro binario etcd como servicio en segundo plano en su lugar.

      Ubuntu 18.04 utiliza systemd como sistema init, lo que significa que podemos crear servicios nuevos al escribir archivos de unidades y colocarlos en el directorio /etc/systemd/system/.

      Primero, cree un directorio nuevo denominado files/ en el directorio de nuestro proyecto:

      A continuación, cree un archivo nuevo denominado etcd.service en ese directorio con su editor:

      Luego, copie el siguiente bloque de código en el archivo 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
      

      Este archivo de unidad define un servicio que ejecuta el ejecutable en /opt/etcd/bin/etcd, notifica a systemd cuando termina de inicializarse y, si sale en algún momento, se reinicia.

      Nota: Si desea obtener más información sobre los archivos de unidades y systemd o adaptar el archivo de unidad a sus necesidades, consulte la guía Información sobre unidades y archivos de unidades de Systemd.

      Cierre y guarde el archivo files/etcd.service presionando CTRL+X y, luego, Y.

      A continuación, debemos agregar una tarea en nuestro cuaderno de estrategias para copiar el archivo local files/etcd.service al directorio /etc/systemd/system/etcd.service de cada nodo administrado. Podemos hacerlo usando el módulo copy.

      Abra su cuaderno de estrategias:

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

      Añada la siguiente tarea resaltada al final de nuestras tareas existentes:

      ~/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
      

      Al copiar el archivo de unidad en /etc/systemd/system/etcd.service, definimos un servicio.

      Guarde y cierre el cuaderno de estrategias.

      Vuelva a ejecutar el mismo comando ansible-playbook para aplicar los cambios nuevos:

      • ansible-playbook -i hosts playbook.yaml

      Para confirmar que los cambios se hayan aplicado, primero, ingrese mediante SSH a uno de los nodos administrados:

      A continuación, ejecute systemctl status etcd para consultar a systemd el estado del servicio etcd:

      Obtendrá el siguiente resultado, que indica que el servicio está cargado:

      Output

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

      Nota: La última línea (Active: inactive (dead)) del resultado indica que el servicio está inactivo, lo que significa que no se ejecutará de forma automática cuando el sistema se inicie. Esto es de esperar, no es un error.

      Presione q para regresar al shell y, luego, ejecute exit para salir del nodo administrado y volver a su shell local:

      En este paso, actualizamos nuestro cuaderno de estrategias para ejecutar el binario etcd como servicio de systemd. En el siguiente paso, continuaremos configurando etcd al proporcionarle espacio para almacenar sus datos.

      Paso 5: Configurar el directorio de datos

      etcd es un almacén de datos de valor clave, lo que significa que debemos proporcionarle espacio para almacenar sus datos. En este paso, vamos a actualizar nuestro cuaderno de estrategias para definir un directorio de datos específico para etcd.

      Abra su cuaderno de estrategias:

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

      Añada la siguiente tarea al final de la lista de tareas:

      ~/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
      

      En este caso, utilizamos /var/lib/etcd/hostname.etcd como directorio de datos, donde hostname es el nombre de host del nodo administrado actual. inventory_hostname es una variable que representa el nombre de host del nodo administrado actual; Ansible completa su valor de forma automática. La sintaxis de llaves (es decir, {{ inventory_hostname }}) se utiliza para sustituir variables con el motor de plantillas Jinja2, que es el motor predeterminado de Ansible.

      Cierre el editor de texto y guarde el archivo.

      A continuación, debemos indicarle a etcd que utilice este directorio de datos. Lo haremos pasando el parámetro data-dir a etcd. Para establecer los parámetros de etcd, podemos utilizar una combinación de variables de entorno, indicadores de la línea de comandos y archivos de configuración. En este tutorial, utilizaremos un archivo de configuración, dado que es mucho más fácil aislar todas las configuraciones en un archivo en lugar de tenerlas esparcidas por todo nuestro cuaderno de estrategias.

      Cree un directorio nuevo denominado templates/ en el directorio de su proyecto:

      A continuación, cree un archivo nuevo denominado etcd.conf.yaml.j2 en ese directorio con su editor:

      • nano templates/etcd.conf.yaml.j2

      Luego, copie la siguiente línea y péguela en el archivo:

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

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

      Este archivo utiliza la misma sintaxis de sustitución de variables de Jinja2 que nuestro cuaderno de estrategias. Podemos usar el módulo template para sustituir las variables y cargar el resultado en cada host administrado. Funciona de manera similar a copy, con la excepción de que realiza la sustitución de variables antes de la carga.

      Salga de etcd.conf.yaml.j2 y abra su cuaderno de estrategias:

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

      Añada las siguientes tareas a la lista de tareas para crear un directorio y cargar el archivo de configuración basado en una plantilla en su interior:

      ~/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
      

      Guarde y cierre este archivo.

      Como realizamos este cambio, debemos actualizar el archivo de unidad de nuestro servicio para que pase la ubicación de nuestro archivo de configuración (es decir, /etc/etcd/etcd.conf.yaml).

      Abra el archivo del servicio etcd en su equipo local:

      Actualice el archivo files/etcd.service añadiendo el indicador --config-file resaltado en lo siguiente:

      ~/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
      

      Guarde y cierre este archivo.

      En este paso, utilizamos nuestro cuaderno de estrategias para proporcionar un directorio de datos para que etcd almacene sus datos. En el siguiente paso, añadiremos algunas tareas adicionales para reiniciar el servicio etcd y hacer que se ejecute en el inicio.

      Paso 6: Habilitar e iniciar el servicio etcd

      Siempre que realizamos cambios en el archivo de unidad de un servicio, debemos reiniciarlo para que surtan efecto. Podemos hacerlo ejecutando el comando systemctl restart etcd. Además, para que el servicio etcd se inicie de forma automática en el inicio del sistema, debemos ejecutar systemctl enabled etcd. En este paso, ejecutaremos esos dos comandos utilizando el cuaderno de estrategias.

      Podemos usar el módulo command para ejecutar comandos:

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

      Añada las siguientes tareas al final de la lista de tareas:

      ~/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
      

      Guarde y cierre el archivo.

      Vuelva a ejecutar ansible-playbook -i hosts playbook.yaml una vez más:

      • ansible-playbook -i hosts playbook.yaml

      Para verificar que el servicio etcd se haya reiniciado y habilitado, ingrese mediante SSH a uno de los nodos administrados:

      A continuación, ejecute systemctl status etcd para consultar el estado del servicio etcd:

      Encontrará enabled y active (running), como indica lo resaltado en el siguiente ejemplo, lo que significa que se implementaron los cambios que hicimos en nuestro cuaderno de estrategias:

      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)

      En este paso, utilizamos el módulo command para ejecutar comandos systemctl para reiniciar y habilitar el servicio etcd en nuestros nodos administrados. Ahora que configuramos una instalación de etcd, en el siguiente paso, vamos a probar su funcionalidad al llevar a cabo algunas operaciones básicas de creación, lectura, actualización y eliminación (CRUD).

      Paso 7: Probar etcd

      Si bien tenemos una instalación de etcd en funcionamiento, no es segura ni está listo para usarse en producción. Pero antes de proteger nuestra configuración de etcd en pasos posteriores, primero, vamos comprender lo que puede hacer etcd en términos de funcionalidad. En este paso, vamos a enviar solicitudes a etcd de forma manual para añadir, recuperar, actualizar y eliminar sus datos.

      Por defecto, etcd expone una API que escucha en el puerto 2379 para la comunicación con el cliente. Esto significa que podemos enviar solicitudes de API sin procesar a etcd utilizando un cliente HTTP. Sin embargo, es más rápido usar el cliente oficial de etcd, etcdctl, que permite crear/actualizar, recuperar y eliminar pares clave-valor usando los subcomandos put, get y del respectivamente.

      Asegúrese de seguir estando en el nodo administrado etcd1 y ejecute los siguientes comandos de etcdctl para confirmar que su instalación de etcd esté funcionando.

      Primero, cree una nueva entrada utilizando el subcomando put.

      El subcomando put tiene la siguiente sintaxis:

      etcdctl put key value
      

      En etcd1, ejecute el siguiente comando:

      El comando que acabamos de ejecutar le indica a etcd que escriba el valor "bar" en la clave foo del almacén.

      A continuación, encontrará OK impreso en el resultado, lo que indica que los datos se mantuvieron:

      Output

      OK

      Luego, podemos recuperar esta entrada utilizando el subcomando get, que tiene la sintaxis etcdctl get key:

      Obtendrá este resultado, que muestra la clave en la primera línea y el valor que insertó anteriormente en la segunda:

      Output

      foo bar

      Podemos eliminar la entrada utilizando el subcomando del, que tiene la sintaxis etcdctl del key:

      Obtendrá el siguiente resultado, que indica la cantidad de entradas eliminadas:

      Output

      1

      Ahora, vamos a ejecutar el subcomando get una vez más para intentar recuperar un par clave-valor eliminado:

      No obtendrá ningún resultado, lo que significa que etcdctl no puede recuperar el par clave-valor. Esto confirma que las entradas eliminadas no pueden recuperarse.

      Ahora que probó las operaciones básicas de etcd y etcdctl, salga del nodo administrado y regrese a su entorno local:

      En este paso, utilizamos el cliente etcdctl para enviar solicitudes a etcd. En este punto, estamos ejecutando tres instancias separadas de etcd que actúan de forma independiente unas de otras. Sin embargo, etcd se diseñó como un almacén de clave-valor distribuido, lo que significa que se pueden agrupar varias instancias de etcd para formar un clúster único; luego, cada instancia se convierte en miembro del clúster. Después de crear un clúster, podrá recuperar pares clave-valor insertados desde distintos miembro del clúster. En el siguiente paso, utilizaremos nuestro playbook para transformar nuestros tres clústeres de un solo nodo en un clúster único de tres nodos.

      Paso 8: Crear un clúster utilizando descubrimiento estático

      Para crear un clúster de tres nodos en lugar de tres de un nodo, debemos configurar estas instalaciones de etcd para que se comuniquen entre sí. Esto significa que cada una de ellas debe conocer las direcciones IP de las demás. Este proceso se denomina descubrimiento. El descubrimiento se puede realizar utilizando una configuración estática o un descubrimiento de servicio dinámico. En este paso, analizaremos la diferencia entre ambas y, también, actualizaremos nuestro cuaderno de estrategias para configurar un clúster de etcd con descubrimiento estático.

      El descubrimiento mediante configuración estática es el método que requiere menos configuración; los puntos de conexión de cada miembro se pasan al comando etcd antes de su ejecución. Para usar la configuración estática, se deben cumplir las siguientes condiciones antes de la inicialización del clúster:

      • se debe conocer la cantidad de miembros
      • se deben conocer los puntos de conexión de cada miembro
      • las direcciones IP de todos los puntos de conexión deben ser estáticas

      Si no se pueden cumplir estas condiciones, puede usar un servicio de descubrimiento dinámico. Con el descubrimiento de servicios dinámicos, todas las instancias se registran en el servicio de descubrimiento, lo que permite a cada miembro recuperar información sobre la ubicación de los demás.

      Como sabemos que queremos tener un clúster de etcd de tres nodos y todos nuestros servidores tienen direcciones IP estáticas, utilizaremos el descubrimiento estático. Para iniciar nuestro clúster utilizando descubrimiento estático, debemos agregar varios parámetros a nuestro archivo de configuración. Utilice un editor para abrir el archivo de plantilla templates/etcd.conf.yaml.j2 :

      • nano templates/etcd.conf.yaml.j2

      A continuación, añada las siguientes líneas resaltadas:

      ~/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 %}
      

      Cierre y guarde el archivo templates/etcd.conf.yaml.j2  presionando CTRL+X y, luego, Y.

      A continuación, presentamos una breve explicación de cada parámetro:

      • name: el nombre del miembro en lenguaje natural. Por defecto, etcd utiliza un identificador único generado al azar para identificar cada miembro; sin embargo, un nombre en lenguaje natural nos permite referirnos a ellos con mayor facilidad tanto en los archivos de configuración como en la línea de comandos. Aquí, utilizaremos los nombres de host como nombres de los miembros (es decir, etcd1, etcd2 y etcd3).
      • initial-advertise-peer-urls: una lista de combinaciones de direcciones IP y puertos que pueden usar otros miembros para comunicarse con este. Además del puerto de la API (2379) de etcd también expone el puerto 2380 para la comunicación de punto a punto entre los miembros de etcd, lo que les permite enviar mensajes e intercambiar datos. Tenga en cuenta que estas URL deben poder ser accesibles para sus pares (y no deben ser una dirección IP local).
      • listen-peer-urls: una lista de combinaciones de direcciones IP y puertos que el miembro actual utilizará para escuchar comunicaciones de otros miembros. Debe incluir todas las URL del indicador --initial-advertise-peer-urls, así como las URL locales, como 127.0.0.1:2380. La dirección IP y el puerto de destino de los mensajes entrantes de pares deben coincidir con una de las URL que se enumeran aquí.
      • advertise-client-urls: una lista de combinaciones de direcciones IP y puertos que deben usar los clientes para comunicarse con este miembro. El cliente debe poder acceder a estas URL (y no deben ser una dirección local). Si el cliente está accediendo al clúster a través de una red pública de Internet, la dirección IP debe ser pública.
      • listen-client-urls: una lista de combinaciones de direcciones IP y puertos que el miembro actual utilizará para escuchar comunicaciones de los clientes. Debe incluir todas las URL del indicador --advertise-client-urls, así como las URL locales, como 127.0.0.1:2379. La dirección IP y el puerto de destino de los mensajes entrantes de clientes deben coincidir con una de las URL que se enumeran aquí.
      • initial-cluster: una lista de los puntos de conexión de cada miembro del clúster. Cada punto de conexión debe coincidir con una de las URL de initial-advertise-peer-urls del miembro correspondiente.
      • initial-cluster-state: puede ser new o existing.

      Para asegurar consistencia, etcd solo puede tomar decisiones cuando la mayoría de los nodos tienen un estado correcto. Esto se conoce como establecimiento de quorum. En otras palabras, en un clúster de tres miembros, hay quorum cuando dos o más de ellos tienen un estado correcto.

      Si el parámetro initial-cluster-state se establece en new, etcd sabrá que se trata de un clúster nuevo que se está iniciando y permitirá que los miembros se inicien en paralelo sin esperar a que se alcance el quorum. Más concretamente, una vez que se inicie el primer miembro, no habrá quorum porque un tercio (33,33 %) es menor o igual que el 50 %. Normalmente, etcd se detendría y se negaría a realizará más acciones, por lo que el clúster nunca se crearía. Sin embargo, con initial-cluster-state establecido en new, ignorará la falta de quorum inicial.

      Si se establece en existing, el miembro intentará unirse a un clúster existente, y esperará que el quorum ya esté establecido.

      Nota: Puede obtener más información sobre todos los indicadores de configuración compatibles en la sección Configuración de la documentación de etcd.

      En el archivo de plantilla templates/etcd.conf.yaml.j2 actualizado, hay algunas instancias de hostvars. Cuando Ansible se ejecute, recopilará variables de diversas fuentes. Ya hemos utilizado la variable inventory_hostname, pero hay mucho más disponible. Estas variables están disponibles en hostvars[inventory_hostname]['ansible_facts']. Aquí, estamos extrayendo las direcciones IP privadas de cada nodo y las estamos utilizando para crear el valor de nuestro parámetro.

      Nota: Como habilitamos la opción Redes privadas cuando creamos nuestros servidores, habrá tres direcciones IP asociadas a cada servidor:

      • Una dirección IP de bucle invertido: una dirección que solo es válida dentro del mismo equipo. Se utiliza para que el equipo haga referencia a sí mismo, por ejemplo, 127.0.0.1
      • Una dirección IP pública: una dirección que se puede enrutar mediante una red pública de Internet, por ejemplo, 178.128.169.51
      • Una dirección IP privada: una dirección que solo se puede enrutar mediante una red privada; en el caso de Droplets de DigitalOcean, hay una red privada en cada centro de datos, por ejemplo, 10.131.82.225

      Cada una de estas direcciones IP está asociada con una interfaz de red distinta: la dirección de bucle invertido se asocia con la interfaz lo, la dirección IP pública se asocia con la interfaz eth0 y la dirección IP privada con la interfaz eth1. Estamos utilizando la interfaz eth1 para que todo el tráfico se mantenga en la red privada, sin llegar a Internet.

      Para los fines de este artículo, no es necesario tener conocimientos sobre las interfaces de red, pero, si desea obtener más información, el artículo Introducción a terminología de redes, interfaces y protocolos es un buen punto de partida.

      La sintaxis {% %} de Jinja2 define la estructura de bucle for que se itera a través de cada nodo del grupo etcd para crear la cadena initial-cluster en un formato requerido por etcd.

      Para crear el clúster nuevo de tres miembros, debe detener el servicio etcd y borrar el directorio de datos antes de iniciar el clúster. Para hacerlo, utilice un editor para abrir el archivo playbook.yaml en su equipo local:

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

      Luego, antes de la tarea "Create a data directory", añada una tarea para detener el servicio 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:
          ...
      

      A continuación, actualice la tarea "Create a data directory" para eliminar el directorio de datos y recrearlo:

      ~/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 propiedad with_items define una lista de cadenas sobre la cual iterará en esta tarea. Es equivalente a repetir la misma tarea dos veces, pero con valores diferentes para la propiedad state. Aquí, estamos iterando sobre la lista con elementos absent y directory, lo que garantiza que el directorio de datos se elimine primero y, luego, se vuelva a crear.

      Cierra y guarde el archivo playbook.yaml presionando CTRL+X y, luego, Y. A continuación, vuelva a ejecutar ansible-playbook. Ahora, Ansible creará un clúster de etcd único, de 3 miembros:

      • ansible-playbook -i hosts playbook.yaml

      Puede verificarlo al ingresar mediante SSH en cualquier nodo miembro de etcd:

      A continuación, ejecute etcdctl endpoint health --cluster:

      • etcdctl endpoint health --cluster

      Esto enumerará el estado de cada miembro del clúster:

      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

      Ahora, hemos creado correctamente un clúster de etcd de tres nodos. Podemos confirmar esto añadiendo una entrada a etcd en un nodo miembro y recuperándola de otro. En uno de los nodos miembro, ejecute etcdctl put:

      A continuación, utilice una terminal nueva para ingresar mediante SSH en un nodo miembro diferente:

      Luego, intente recuperar la misma entrada usando la clave:

      Podrá recuperar la entrada, lo que demuestra que el clúster está funcionando:

      Output

      foo bar

      Por último, salga de todos los nodos administrados y regrese a su equipo local:

      En este paso, proporcionamos un clúster de tres nodos. En este momento, las comunicaciones entre los miembros de etcd y sus pares y clientes se llevan a cabo mediante HTTP. Esto significa que la comunicación no está cifrada y cualquier parte que pueda interceptar el tráfico puede leer los mensajes. Esto no es un problema importante si el clúster y los clientes de etcd están implementados en una red privada o una red privada virtual (VPN) que controle por completo. Sin embargo, si algún tráfico debe transmitirse a través de una red compartida (privada o pública), deberá asegurarse de que ese tráfico esté cifrado. Además, se debe establecer un mecanismo para que un cliente o par compruebe la autenticidad del servidor.

      En el siguiente paso, veremos cómo proteger las comunicaciones de cliente a servidor y las de punto a punto utilizando TLS.

      Paso 9: Obtener direcciones IP privadas de nodos administrados

      Para cifrar mensajes entre nodos miembros, etcd utiliza el Protocolo seguro de transferencia de hipertexto, o HTTPS, que es una capa por encima de la Seguridad de la capa de transporte, o TLS. TLS utiliza un sistema de claves privadas, certificados y entidades de confianza denominadas Entidades de certificación (CA) para autenticarse y enviar mensajes cifrados.

      En este tutorial, cada nodo miembro debe generar un certificado para identificarse y este debe estar firmado por una CA. Configuraremos todos los nodos miembros para que confíen en esta CA y, por lo tanto, también en cualquier certificado firmado por ella. Esto les permite a los nodos miembro autenticarse mutuamente.

      El certificado que genere cada nodo miembro debe permitir que los demás lo identifiquen. Todos los certificados incluyen el Nombre común (CN) de la entidad a la que están asociados. Esto se suele utilizar como la identidad de la entidad. Sin embargo, al verificar un certificado, las implementaciones de los clientes pueden comparar si la información recopilada sobre la entidad concuerda con la proporcionada en el certificado. Por ejemplo, si un cliente descarga un certificado TLS con firmante CN=foo.bar.com, pero, en realidad, se está conectando al servidor usando una dirección IP (por ejemplo, 167.71.129.110), hay una incongruencia y es posible que el cliente no confíe en el certificado. Al especificar un nombre alternativo del firmante (SAN) en el certificado, le informa que ambos nombres pertenecen a la misma entidad.

      Como nuestros miembros de etcd se emparejan utilizando sus direcciones IP privadas, al definir nuestros certificados, tendremos que proporcionar estas direcciones IP privadas como nombres alternativos del firmante.

      Para obtener la dirección IP privada de un nodo administrado, ingrese a él mediante SSH:

      Luego, ejecute el siguiente comando:

      • ip -f inet addr show eth1

      Verá un resultado similar a las siguientes líneas:

      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

      En nuestro resultado ejemplo, 10.131.255.176 es la dirección IP privada del nodo administrado, y la única información que nos interesa. Para filtrar todo lo que no corresponde a la IP privada, podemos canalizar el resultado del comando ip a la utilidad sed, que se utiliza para filtrar y transformar texto.

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

      Ahora, el único resultado es la dirección IP privada:

      Output

      10.131.255.176

      Una vez que esté satisfecho con el funcionamiento del comando anterior, salga del nodo administrado:

      Para incorporar los comandos anteriores a nuestro playbook, abra el archivo playbook.yaml:

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

      A continuación, añada una nueva estrategia con una sola tarea antes de nuestra estrategia existente:

      ~/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 tarea utiliza el módulo shell para ejecutar los comandos ip y sed, con lo cual se obtiene la dirección IP privada del nodo administrado. A continuación, registra el valor que devolvió el comando shell dentro de una variable denominada privateIP, que utilizaremos más adelante.

      En este paso, añadimos una tarea al cuaderno de estrategias para obtener la dirección IP privada de los nodos administrados. En el siguiente paso, vamos a utilizar esta información para generar certificados para cada nodo miembro que firmaremos con una Entidad de certificación (CA).

      Paso 10: Generar claves privadas y CSR para los miembros de etcd

      Para que un nodo miembro pueda recibir tráfico cifrado, el remitente debe utilizar la clave pública del nodo miembro para cifrar los datos y el nodo miembro debe usar su clave privada para descifrar el texto cifrado y obtener los datos originales. La clave pública se empaqueta en un certificado y la firma una CA para garantizar que sea auténtica.

      Por lo tanto, debemos generar una clave privada y una solicitud de firma de certificado (CSR) para cada nodo miembro de etcd. Para facilitar la tarea, generaremos todos los pares de claves y firmaremos todos los certificados de forma local, en el nodo de control y, luego, copiaremos los archivos pertinentes a los hosts administrados.

      Primero, cree un directorio denominado artifacts/, donde colocaremos los archivos (claves y certificados) que se generarán durante el proceso. Abra el archivo playbook.yaml con un editor:

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

      Utilice el módulo file para crear el directorio artifacts/ en su interior:

      ~/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:
      ...
      

      A continuación, añada otra tarea al final de la estrategia para generar la clave privada:

      ~/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 creación de claves privadas y CSR se puede llevar a cabo utilizando los módulos openssl_privatekey y openssl_csr respectivamente.

      El atributo force: True garantiza que siempre se genere una clave privada, incluso si ya existe.

      Del mismo modo, añada la siguiente tarea nueva al misma estrategia para generar las CSR para cada miembro utilizando el módulo 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'] }}"
      

      Estamos especificando que este certificado puede estar involucrado en un mecanismo de firma digital para la autenticación del servidor. Este certificado se asocia con el nombre de host (por ejemplo, etcd1), pero el verificador debe tratar las direcciones IP de bucle inverso privadas y locales de cada nodo como nombres alternativos. Tenga en cuenta que estamos utilizando la variable privateIP que registramos en la estrategia anterior.

      Cierre y guarde el archivo playbook.yaml presionando CTRL+X y, luego, Y. A continuación, vuelva a ejecutar el playbook:

      • ansible-playbook -i hosts playbook.yaml

      Ahora, encontraremos un nuevo directorio denominado artifacts en el directorio de nuestro proyecto; usaremos ls para enumerar su contenido:

      Encontrará las claves privadas y las CSR de cada miembro de etcd:

      Output

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

      En este paso, usamos varios módulos de Ansible para generar claves privadas y certificados de clave pública para cada uno de los nodos miembro. En el siguiente paso, veremos cómo firmar una solicitud de firma de certificado (CSR).

      Paso 11: Generar certificados de CA

      Dentro de un clúster de etcd, los nodos miembro cifran los mensajes usando la clave pública del receptor. Para garantizar que la clave pública sea genuina, el receptor empaqueta la clave pública en una solicitud de firma de certificado (CSR) y hace que una entidad de confianza (es decir, la CA) lo firme. Como controlamos todos los nodos miembro y las CA en las que confían, no necesitamos usar una CA externa y podemos actuar como nuestra propia CA. En este paso, vamos a actuar como nuestra propia CA, lo que significa que debemos generar una clave privada y un certificado autofirmado para que funcione como CA.

      Primero, abra el archivo playbook.yaml con su editor:

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

      A continuación, al igual que en el paso anterior, añada una tarea a la estrategia localhost para generar una clave privada para la CA:

      ~/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"
      ...
      

      A continuación, utilice el módulo openssl_csr para generar una CSR nueva. Esto es similar a lo que hicimos en el paso anterior, con la diferencia de que, en esta CSR, estamos añadiendo la extensión de uso de la clave y la restricción básica para indicar que este certificado puede utilizarse como certificado de 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"
      ...
      

      Por último, utilice el módulo openssl_certificate para autofirmar la 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"
      ...
      

      Cierre y guarde el archivo playbook.yaml presionando CTRL+X y, luego, Y. A continuación, vuelva a ejecutar el playbook para aplicar los cambios:

      • ansible-playbook -i hosts playbook.yaml

      También puede ejecutar ls para verificar el contenido del directorio artifacts/ :

      Ahora, encontrará el certificado de CA generado recientemente (ca.crt):

      Output

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

      En este paso, generamos una clave privada y un certificado autofirmado para la CA. En el siguiente paso, usaremos el certificado de CA para firmar la CSR de cada miembro.

      Paso 12: Firmar las CSR de los miembros de etcd

      En este paso, vamos a firmar la CSR de cada nodo miembro. Lo haremos de forma similar a la manera en que usamos el módulo openssl_certificate para firmar el certificado de CA, pero, en lugar de usar el proveedor self-signed, utilizaremos el proveedor ownca, que nos permite firmar con nuestro propio certificado de CA.

      Abra su cuaderno de estrategias:

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

      Añada la siguiente tarea resaltada a la tarea "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"
      ...
      

      Cierre y guarde el archivo playbook.yaml presionando CTRL+X y, luego, Y. A continuación, vuelva a ejecutar el cuaderno de estrategias para aplicar los cambios:

      • ansible-playbook -i hosts playbook.yaml

      Ahora, enumere el contenido del directorio artifacts/:

      Encontrará la clave privada, la CSR y el certificado de cada miembro de etcd y la CA:

      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

      En este paso, firmamos las CSR de cada nodo miembro usando la clave de la CA. En el siguiente paso, vamos a copiar los archivos pertinentes en cada nodo administrado, para que etcd tenga acceso a las claves y los certificados correspondientes para configurar las conexiones de TLS.

      Paso 13: Copiar claves privadas y certificados

      Cada nodo debe tener una copia del certificado de la CA autofirmado (ca.crt). Cada nodo de miembro de etcd también debe tener su clave privada y certificado propios. En este paso, vamos a cargar estos archivos y los colocaremos en un directorio nuevo /etc/etcd/ssl/.

      Para comenzar, abra el archivo playbook.yaml con su editor:

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

      Para realizar estos cambios en nuestro playbook de Ansible, primero, actualice la propiedad path de la tarea Create directory for etcd configuration para crear el directorio /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:
      ...
      

      A continuación, detrás de la tarea modificada, añada tres tareas más para copiar los archivos:

      ~/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:
      ...
      

      Cierre y guarde el archivo playbook.yaml presionando CTRL+X y, luego, Y.

      Vuelva a ejecutar ansible-playbook para realizar estos cambios:

      • ansible-playbook -i hosts playbook.yaml

      En este paso, cargamos correctamente las claves privadas y los certificados a los nodos administrados. Ahora que copiamos los archivos, debemos actualizar nuestro archivo de configuración para usarlos.

      Paso 14: Habilitar TLS en etcd

      En el último paso de este tutorial, vamos a actualizar algunas configuraciones de Ansible para habilitar TLS en un clúster de etcd.

      Primero, abra el archivo de plantillas templates/etcd.conf.yaml.j2 con su editor:

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

      En su interior, cambie todas las URL para usar https como protocolo en lugar de http. Adicionalmente, añada una sección al final de la plantilla para especificar la ubicación del certificado de CA, el certificado del servidor y la clave del servidor:

      ~/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
      

      Cierre y guarde el archivo templates/etcd.conf.yaml.j2.

      A continuación, ejecute su playbook de Ansible:

      • ansible-playbook -i hosts playbook.yaml

      Luego, ingrese mediante SSH a uno de los nodos administrados:

      Cuando haya ingresado, ejecute el comando etcdctl endpoint health para verificar si los puntos de conexión están usando HTTPS y si el estado de todos los miembros es correcto:

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

      Como nuestro certificado de CA no es, por defecto, un certificado root de CA de confianza instalado en el directorio /etc/ssl/certs/, debemos pasarlo a etcdctl usando el indicador --cacert.

      Esto generará el siguiente resultado:

      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

      Para confirmar que el clúster etcd efectivamente esté funcionando, podemos crear una entrada en un nodo miembro y recuperarla desde otro:

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

      A continuación, utilice una terminal nueva para ingresar mediante SSH en un nodo diferente:

      Ahora, recupere la misma entrada utilizando la clave foo:

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

      Esto devolverá la entrada y verá el siguiente resultado:

      Output

      foo bar

      Puede hacer lo mismo en el tercer nodo para garantizar que los tres miembros estén en funcionamiento.

      Conclusión

      Proporcionó correctamente un clúster de etcd de tres nodos, lo protegió con TLS y confirmó que esté funcionando.

      etcd es una herramienta creada originalmente por CoreOS. Para entender el uso de etcd en relación con CoreOS, puede consultar el artículo Cómo usar Etcdctl y Etcd, el almacén de clave-valor distribuido de CoreOS. El artículo también lo guía a través del proceso de configuración de un modelo de descubrimiento dinámico, algo que mencionamos, pero no demostramos en este tutorial.

      Como se mencionó al principio de este tutorial, etcd es un componente importante del ecosistema de Kubernetes. Para obtener más información sobre Kubernetes y la función que desempeña etcd en él, puede consultar el artículo Introducción a Kubernetes. Si está implementando etcd como parte de un clúster de Kubernetes, tenga en cuenta que puede utilizar otras herramientas, como kubespray y kubeadm. Para obtener más información al respecto, puede consultar el artículo Cómo crear un clúster de Kubernetes usando Kubeadm en Ubuntu 18.04.

      Por último, en este tutorial, se utilizaron muchas herramientas, pero no se pudo entrar en demasiado detalle en cada una de ellas. Encontrará enlaces que proporcionarán un análisis más detallado de cada herramienta aquí:



      Source link

      Como configurar e proteger um cluster etcd com o Ansible no Ubuntu 18.04


      O autor selecionou a Wikimedia Foundation para receber uma doação como parte do programa Write for DOnations.

      Introdução

      O etcd é um armazenamento distribuído de chaves-valores utilizado em muitas plataformas e ferramentas, incluindo o Kubernetes, Vulcand e o Doorman. Dentro do Kubernetes, o etcd é usado como um armazenamento de configuração global que armazena o estado do cluster. Saber administrar o etcd é essencial para administrar um cluster Kubernetes. Embora existam muitas opções gerenciadas do Kubernetes, também conhecidas como Kubernetes-as-a-Service, que removem esse peso administrativo de você, muitas empresas ainda escolhem utilizar clusters Kubernetes locais autogeridos devido à flexibilidade que eles trazem.

      A primeira metade deste artigo irá guiá-lo através da configuração de um cluster etcd de 3 nós em servidores Ubuntu 18.04. A segunda metade irá focar na proteção do cluster usando o Transport Layer Security, ou TLS. Para executar cada configuração de forma automatizada, usaremos o Ansible durante todo o processo. O Ansible é uma ferramenta de gerenciamento de configuração semelhante ao Puppet, Chef e ao SaltStack. Ele nos permite definir cada passo da configuração de maneira declarativa, dentro de arquivos chamados playbooks.

      No final deste tutorial, você terá um cluster etcd de 3 nós protegido em execução nos seus servidores. Você também terá um playbook do Ansible que lhe permite recriar repetidamente e consistentemente a mesma configuração em um conjunto de servidores novos.

      Pré-requisitos

      Antes de iniciar este guia, será necessário o seguinte:

      Aviso: como o objetivo deste artigo é fornecer uma introdução para configurar de um cluster etcd em uma rede privada, os três servidores Ubuntu 18.04 nesta configuração não foram testados com um firewall e são acessados com o usuário root. Em uma configuração de produção, qualquer nó exposto à internet pública exigiria um firewall e um usuário sudo para que ele se adequasse às práticas recomendadas de segurança. Para mais informações, confira o tutorial Configuração inicial de servidor com o Ubuntu 18.04.

      Passo 1 — Configurando o Ansible para o nó de controle

      O Ansible é uma ferramenta usada para gerenciar servidores. Os servidores que o Ansible está gerenciando são chamados de nós gerenciados, e a máquina que está executando o Ansible é chamada de nó de controle. O Ansible funciona usando as chaves SSH no nó de controle para obter acesso aos nós gerenciados. Depois que uma sessão SSH for estabelecida, o Ansible irá executar um conjunto de scripts para provisionar e configurar os nós gerenciados. Neste passo, vamos testar se somos capazes de usar o Ansible para nos conectar aos nós gerenciados e executar o comando hostname.

      Um dia típico para um administrador de sistemas pode envolver o gerenciamento de conjuntos diferentes de nós. Por exemplo, é possível usar o Ansible para provisionar alguns novos servidores, mas mais tarde usá-lo para reconfigurar outro conjunto de servidores. Para permitir que os administradores organizem melhor o conjunto de nós gerenciados, o Ansible oferece o conceito de inventário de host (ou somente inventário). Você pode definir todos os nós que deseja gerenciar com o Ansible dentro de um arquivo de inventário, e organizá-los em grupos. Dessa forma, ao executar os comandos ansible e ansible-playbook, é possível especificar a quais hosts ou grupos o comando se aplica.

      Por padrão, o Ansible lê o arquivo de inventário a partir de /etc/ansible/hosts. No entanto, podemos especificar um arquivo de inventário diferente usando o sinalizador --inventory (ou somente -i).

      Para iniciar, crie um novo diretório em sua máquina local (o nó de controle) para abrigar todos os arquivos para este tutorial:

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

      Em seguida, entre no diretório que acabou de criar:

      • cd $HOME/playground/etcd-ansible

      Dentro do diretório, crie e abra um arquivo de inventário em branco chamado hosts usando seu editor:

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

      Dentro do arquivo hosts, liste todos os seus seus nós gerenciados no seguinte formato, substituindo os endereços IP públicos destacados pelos endereços IP públicos reais dos seus servidores:

      ~/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
      

      A linha [etcd] define um grupo chamado etcd. Sob a definição de grupo, listamos todos os nossos nós gerenciados. Cada linha começa com um alias (por exemplo, etcd1), que nos permite fazer referência a cada host usando um nome fácil de lembrar, em vez de um endereço IP longo. O ansible_host e o ansible_user são variáveis do Ansible. Neste caso, são usados para fornecer ao Ansible os endereços IP públicos e os nomes de usuários SSH para usar ao conectar-se via SSH.

      Para garantir que o Ansible seja capaz de se conectar com nossos nós gerenciados, podemos testar a conectividade usando o Ansible para executar o comando hostname em cada um dos hosts dentro do grupo etcd:

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

      Vamos dividir esse comando para aprender o que cada parte significa:

      • etcd: especifica o padrão de host a ser usado para determinar quais hosts do inventário estão sendo gerenciados com este comando. Aqui, usamos o nome do grupo como padrão de host.
      • -i hosts: especifica o arquivo de inventário a ser usado.
      • -m command: as funcionalidades por trás do Ansible são fornecidas através de módulos. O módulo command recebe o argumento passado e executa-o como um comando em cada um dos nós gerenciados. Este tutorial irá introduzir alguns outros módulos do Ansible à medida que progredimos.
      • -a hostname: o argumento a ser passado para o módulo. A quantidade e os tipos de argumentos dependem do módulo.

      Depois de executar o comando, você verá o seguinte resultado, o que significa que o Ansible está configurado corretamente:

      Output

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

      Cada comando que o Ansible executa é chamado de tarefa. Usar o ansible na linha de comando para executar tarefas é um processo conhecido como executar comandos ad-hoc. A vantagem dos comandos ad-hoc é que eles são rápidos e exigem pouca configuração. O lado negativo é que eles são executados manualmente, e assim não podem ser consignados a um sistema de controle de versão como o Git.

      Uma ligeira melhoria seria escrever o script do shell e executar nossos comandos usando o módulo script do Ansible. Isso permitiria que gravássemos os passos de configuração que fizemos no controle de versão. No entanto, os scripts do shell são imperativos, ou seja, somos responsáveis por arranjar os comandos a serem executados (os “como”s) para configurar o sistema no estado desejado. Por outro lado, o Ansible defende uma abordagem declarativa, onde definimos “qual” o estado desejado em que o nosso servidor deve estar dentro dos arquivos de configuração, e o Ansible é responsável por levar o servidor até esse estado desejado.

      A abordagem declarativa é mais interessante porque a intenção do arquivo de configuração é imediatamente transmitida, sendo assim mais fácil entendê-lo e mantê-lo. Ela também coloca a responsabilidade de casos extremos nas costas do Ansible ao invés do administrador, poupando-nos muito trabalho.

      Agora que o nó de controle do Ansible foi configurado para se comunicar com os nós gerenciados, no próximo passo, vamos apresentar os playbooks do Ansible, que permitem especificar tarefas de forma declarativa.

      Neste passo, vamos replicar o que foi feito no Passo 1 — impressão dos nomes de host dos nós gerenciados— mas em vez de executar tarefas ad-hoc, iremos definir cada tarefa declarativamente como um playbook do Ansible para então executá-lo. O objetivo deste passo é demonstrar como os playbooks do Ansible funcionam. Vamos realizar tarefas muito mais robustas com os playbooks em etapas posteriores.

      Dentro do seu diretório de projeto, crie um novo arquivo chamado playbook.yaml usando seu editor:

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

      Dentro do playbook.yaml, adicione as seguintes linhas:

      ~/playground/etcd-ansible/playbook.yaml

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

      Feche e salve o arquivo playbook.yaml pressionando CTRL+X e depois Y.

      O playbook contém uma lista de peças; cada peça contém uma lista de tarefas que devem ser executadas em todos os hosts que correspondem ao padrão de host especificado pela chave hosts. Neste playbook, temos uma peça que contém duas tarefas. A primeira tarefa executa o comando hostname usando o módulo command e registra o resultado em uma variável chamada output. Na segunda tarefa, usamos o módulo debug para imprimir a propriedade stdout_lines da variável output.

      Agora, podemos executar este playbook usando o comando ansible-playbook:

      • ansible-playbook -i hosts playbook.yaml

      Você verá o seguinte resultado, o que significa que seu playbook está funcionando corretamente:

      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

      Nota: o ansible-playbook às vezes usa o cowsay como uma maneira brincalhona de imprimir os cabeçalhos. Caso encontre muitas vacas em arte ASCII impressas em seu terminal, agora sabe porquê. Para desativar esse recurso, defina a variável de ambiente ANSIBLE_NOCOWS como 1 antes de executar o ansible-playbook. Faça isso executando export ANSIBLE_NOCOWS=1 no seu shell

      Neste passo, passamos a executar playbooks declarativos ao invés de tarefas ad-hoc imperativas. No próximo passo, vamos substituir essas duas tarefas demonstrativas por tarefas que irão configurar nosso cluster do etcd.

      Passo 3 — Instalando o etcd nos nós gerenciados

      Neste passo, vamos mostrar os comandos para instalar o etcd manualmente e demonstrar como traduzir esses mesmos comandos para tarefas dentro do nosso playbook do Ansible.

      O etcd e seu cliente etcdctl estão disponíveis como binários, os quais vamos baixar, extrair e mover para um diretório que faz parte da variável de ambiente PATH. Quando configurados manualmente, esses são os passos que vamos seguir em cada um dos nós gerenciados:

      • 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

      Os primeiros quatro comandos baixam e extraem os binários para o diretório /opt/etcd/bin/. Por padrão, o cliente etcdctl usará a API v2 para se comunicar com o servidor do etcd. Como estamos executando etcd v3.x, o último comando define a variável de ambiente ETCDCTL_API como 3.

      Nota: aqui, estamos usando o etcd v3.3.13 desenvolvido para uma máquina com processadores que usam o conjunto de instruções AMD64. Você pode encontrar binários para outros sistemas e outras versões na página oficial de Lançamentos no GitHub.

      Para replicar os mesmos passos em um formato padronizado, podemos adicionar tarefas ao nosso playbook. Abra o arquivo de playbook playbook.yaml no seu editor:

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

      Substitua tudo no arquivo playbook.yaml pelo seguinte conteúdo:

      ~/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
      

      Cada tarefa usa um módulo e para este conjunto de tarefas, estamos usando os seguintes módulos:

      • file: usado para criar o diretório /opt/etcd/bin, e para definir mais tarde as permissões de arquivo para os binários etcd e etcdctl.
      • get_url: usado para baixar o tarball gzipped nos nós gerenciados.
      • unarchive: usado para extrair e descompactar os binários etcd e etcdctl do tarball gzipped.
      • lineinfile: usado para adicionar uma entrada no arquivo .profile.

      Para aplicar essas alterações, feche e salve o arquivo playbook.yaml pressionando CTRL+X e depois Y. Então, no terminal, execute o mesmo comando ansible-playbook novamente:

      • ansible-playbook -i hosts playbook.yaml

      A seção PLAY RECAP da saída mostrará apenas ok e changed (alterado):

      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

      Para confirmar se houve uma instalação correta do etcd, entre manualmente via SSH em um dos nós gerenciados e execute o etcd e o etcdctl:

      etcd1_public_ip é o endereço IP público do servidor chamado etcd1. Depois que tiver obtido acesso ao SSH, execute etcd --version para imprimir a versão instalada do etcd:

      Você verá um resultado semelhante ao que será mostrado a seguir, que significa que o binário etcd foi instalado com sucesso:

      Output

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

      Para confirmar se o etcdctl foi instalado com sucesso, execute version etcdctl:

      Você irá obter um resultado similar ao seguinte:

      Output

      etcdctl version: 3.3.13 API version: 3.3

      Observe que o resultado diz API version: 3.3, o que também confirma que nossa variável de ambiente ETCDCTL_API foi definida corretamente.

      Saia do servidor etcd1 para voltar ao seu ambiente local.

      Agora, instalamos com sucesso o etcd e o etcdctl em todos os nossos nós gerenciados. No próximo passo, vamos adicionar mais tarefas à nossa peça para executar o etcd como um serviço em segundo plano.

      Passo 4 — Criando um arquivo de unidade para o etcd

      Aparentemente, a maneira mais rápida de executar o etcd com o Ansible é usando o módulo command para executar /opt/etcd/bin/etcd. No entanto, isso não funcionará porque fará com que o etcd seja executado como um processo em primeiro plano. Usar o módulo command fará com que o Ansible pare enquanto espera que o comando etcd gere um retorno, o que nunca acontecerá. Sendo assim, neste passo, vamos atualizar nosso playbook para que execute nosso binário etcd como um serviço em segundo plano.

      O Ubuntu 18.04 usa o systemd como seu sistema init, o que significa que podemos criar novos serviços criando arquivos de unidade e colocando-os dentro do diretório /etc/systemd/system/.

      Primeiro, dentro do nosso diretório de projeto, crie um novo diretório chamado files/:

      Em seguida, usando seu editor, crie um novo arquivo chamado etcd.service dentro desse diretório:

      Depois disso, copie o seguinte bloco de código no arquivo 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
      

      Este arquivo de unidade define um serviço que executa o arquivo executável /opt/etcd/bin/etcd, notifica o systemd assim que tiver terminado a inicialização e sempre reinicializa-se caso seja fechado.

      Nota: se quiser entender mais sobre o systemd e os arquivos de unidade, ou quiser adaptar o arquivo de unidade às suas necessidades, leia o guia Entendendo as unidades e arquivos de unidade do Systemd.

      Feche e salve o arquivo files/etcd.service pressionando CTRL+X e depois Y.

      Em seguida, é necessário adicionar uma tarefa dentro do nosso playbook que irá criar uma cópia do arquivo local files/etcd.service no diretório /etc/systemd/system/etcd.service para cada nó gerenciado. Podemos fazer isso usando o módulo copy.

      Abra seu playbook:

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

      Adicione a seguinte tarefa destacada no final das tarefas já existentes:

      ~/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
      

      Ao copiar o arquivo de unidade em /etc/systemd/system/etcd.service, um serviço agora está definido.

      Salve e saia do playbook.

      Execute novamente o mesmo comando ansible-playbook para aplicar as novas alterações:

      • ansible-playbook -i hosts playbook.yaml

      Para confirmar se as alterações foram aplicadas, primeiro entre via SSH em um dos nós gerenciados:

      Em seguida, execute systemctl status etcd para consultar o systemd sobre o status do serviço etcd:

      Você receberá o seguinte resultado, que afirma que o serviço está carregado:

      Output

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

      Nota: a última linha (Active: inactive (dead)) do resultado afirma que o serviço está inativo, o que significa que ele não seria executado automaticamente quando o sistema iniciasse. Isso é esperado e não representa um erro.

      Pressione q para voltar ao shell e então execute exit para sair do nó gerenciado e voltar para seu shell local:

      Neste passo, atualizamos nosso playbook para executar o binário etcd como um serviço do systemd. No próximo passo, vamos continuar a configuração do etcd fornecendo espaço para armazenar seus dados.

      Passo 5 — Configurando o diretório de dados

      O etcd é um armazenamento de dados de chaves-valores, o que significa que devemos fornecer-lhe espaço para armazenar seus dados. Neste passo, vamos atualizar nosso playbook para definir um diretório de dados dedicado para o etcd usar.

      Abra seu playbook:

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

      Acrescente a seguinte tarefa no final da lista de tarefas:

      ~/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
      

      Aqui, estamos usando o /var/lib/etcd/hostname.etcd como o diretório de dados, onde hostname é o nome de host do nó gerenciado em questão e inventory_hostname é uma variável que representa o nome de host do nó gerenciado em questão; seu valor é preenchido pelo Ansible automaticamente. A sintaxe com chaves (ou seja, {{ inventory_hostname }}) é usada para a substituição de variável, suportada pelo mecanismo de modelo padrão para o Ansible, Jinja2.

      Feche o editor de texto e salve o arquivo.

      Em seguida, é necessário instruir o etcd a usar este diretório de dados. Fazemos isso passando o parâmetro data-dir para o etcd. Para definir parâmetros do etcd, podemos usar uma combinação de variáveis de ambiente, sinalizadores de linha de comando e arquivos de configuração. Para este tutorial, usaremos um único arquivo de configuração, pois é muito mais simples isolar todas as configurações em apenas um arquivo, do que ter partes da configuração espalhadas em todo o nosso playbook.

      Em seu diretório de projeto, crie um novo diretório chamado templates/:

      Em seguida, usando seu editor, crie um novo arquivo chamado etcd.conf.yaml.j2 dentro do diretório:

      • nano templates/etcd.conf.yaml.j2

      Depois disso, copie a linha a seguir e cole no arquivo:

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

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

      Esse arquivo usa a mesma sintaxe de substituição de variável do Jinja2 que nosso playbook. Para substituir as variáveis e fazer upload do resultado para cada host gerenciado, podemos usar módulo template. Ele funciona de maneira semelhante ao copy, exceto que ele irá realizar a substituição de variáveis antes do upload.

      Saia do etcd.conf.yaml.j2 e abra seu playbook:

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

      Acrescente as seguintes tarefas na lista de tarefas para criar um diretório e fazer upload do arquivo de configuração modelado nele:

      ~/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
      

      Salve e feche esse arquivo.

      Como fizemos essa alteração, precisamos atualizar o arquivo de unidade do nosso serviço para passar-lhe a localização do nosso arquivo de configuração (ou seja, /etc/etcd/etcd.conf.yaml).

      Abra o arquivo do serviço etcd em sua máquina local:

      Atualize o arquivo files/etcd.service adicionando o sinalizador --config-file destacado a seguir:

      ~/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
      

      Salve e feche esse arquivo.

      Neste passo, usamos nosso playbook para fornecer um diretório de dados para o etcd armazenar seus dados. No próximo passo, iremos adicionar mais algumas tarefas para reiniciar o serviço etcd e fazê-lo ser executado na inicialização.

      Passo 6 — Habilitando e iniciando o serviço etcd

      Sempre que fazemos alterações no arquivo de unidade de um serviço, precisamos reiniciar o serviço para elas entrem em vigor. Podemos fazer isso executando o comando systemctl restart etcd. Além disso, para fazer com que o serviço etcd seja iniciado automaticamente na inicialização do sistema, precisamos executar systemctl enable etcd. Neste passo, vamos executar esses dois comandos usando o playbook.

      Para executar comandos, podemos usar o módulo command:

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

      Acrescente as seguintes tarefas no final da lista de tarefas:

      ~/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
      

      Salve e feche o arquivo.

      Execute ansible-playbook -i hosts playbook.yaml mais uma vez:

      • ansible-playbook -i hosts playbook.yaml

      Para verificar se o serviço etcd foi reiniciado e está habilitado, entre via SSH em um dos nós gerenciados:

      Em seguida, execute systemctl status etcd para verificar o status do serviço etcd:

      Você verá enabled e active (running) como destacado a seguir; isso significa que as alterações que fizemos em nosso playbook tiveram efeito:

      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)

      Neste passo, usamos o módulo command para executar comandos systemctl que reiniciam e habilitam o serviço etcd em nossos nós gerenciados. Agora que configuramos uma instalação etcd, vamos testar, no próximo passo, sua funcionalidade realizando algumas operações básicas de criação, leitura, atualização e exclusão (CRUD).

      Passo 7 — Testando o etcd

      Embora tenhamos uma instalação funcional do etcd, ela é insegura e ainda não está pronta para o uso na produção. Mas antes de adicionarmos segurança à nossa configuração do etcd em etapas posteriores, vamos primeiro entender o que o etcd pode fazer em termos de funcionalidade. Neste passo, vamos enviar manualmente solicitações ao etcd para adicionar, coletar, atualizar e excluir dados dele.

      Por padrão, o etcd expõe uma API que escuta na porta 2379 para a comunicação com o cliente. Isso significa que podemos enviar solicitações de API brutas para o etcd usando um cliente HTTP. No entanto, é mais rápido usar o cliente oficial do etcd, o etcdctl, que permite criar/atualizar, recuperar e excluir pares de chave-valor usando os subcomandos put, get e del, respectivamente.

      Certifique-se de ainda estar dentro do nó gerenciado etcd1 e execute os seguintes comandos do etcdctl para confirmar que sua instalação do etcd está funcionando.

      Primeiro, crie uma nova entrada usando o subcomando put.

      O subcomando put possui a seguinte sintaxe:

      etcdctl put key value
      

      No etcd1, execute o seguinte comando:

      O comando que acabamos de executar instrui o etcd a escrever o valor "bar" na chave foo no armazenamento.

      Em seguida, você verá um OK impresso no resultado, que indica que os dados persistiram:

      Output

      OK

      Em seguida, podemos recuperar essa entrada usando o subcomando get, que possui a sintaxe etcdctl get key:

      Você verá este resultado, que mostra a chave na primeira linha e o valor que você inseriu mais cedo na segunda linha:

      Output

      foo bar

      Podemos excluir a entrada usando o subcomando del, que possui a sintaxe etcdctl del key:

      Você verá o seguinte resultado, que indica o número de entradas excluídas:

      Output

      1

      Agora, vamos executar o subcomando get mais uma vez para tentar recuperar um par chave-valor excluído:

      Você não receberá um resultado, o que significa que o etcdctl não é capaz de recuperar o par chave-valor. Isso confirma que depois que a entrada é excluída, ela não pode mais ser recuperada.

      Agora que você testou as operações básicas do etcd e do etcdctl, vamos sair do nosso nó gerenciado e voltar para o ambiente local:

      Neste passo, usamos o cliente etcdctl para enviar solicitações ao etcd. Neste ponto, estamos executando três instâncias separadas do etcd sendo que cada uma age independentemente uma da outra. No entanto, o etcd é projetado como um armazenamento de chaves-valores distribuído, o que significa que várias instâncias do etcd podem se agrupar para formar um único cluster e cada instância torna-se então um membro do cluster. Depois de formar um cluster, seria possível recuperar um par chave-valor que foi inserido a partir de um membro diferente do cluster. No próximo passo, usaremos nosso playbook para transformar nossos 3 clusters de nó único em um único cluster de três nós.

      Passo 8 — Formando um cluster usando a descoberta estática

      Para criar um cluster de três nós ao invés de três clusters de somente 1 nó, devemos configurar essas instalações do etcd para se comunicar entre si. Isso significa que cada uma deve conhecer os endereços IP umas das outras. Este processo é chamado de descoberta. A descoberta pode ser feita usando a configuração estática ou a descoberta de serviço dinâmica. Neste passo, vamos discutir a diferença entre as duas, bem como atualizar nosso playbook para configurar um cluster do etcd usando a descoberta estática.

      A descoberta por configuração estática é o método cuja configuração é mais curta. É aqui que os pontos de extremidade de cada membro são passados no comando etcd antes que ele seja executado. Para usar a configuração estática, as seguintes condições devem ser atendidas antes da inicialização do cluster:

      • o número de membros é conhecido
      • os pontos de extremidade de cada membro são conhecidos
      • os endereços IP para todos os pontos de extremidade são estáticos

      Se essas condições não puderem ser atendidas, então será preciso usar um serviço de descoberta dinâmico. Com a descoberta de serviço dinâmica, todas as instâncias seriam registradas com o serviço de descoberta. Isso permite que cada membro obtenha informações sobre a localização de outros membros.

      Como sabemos que queremos um cluster do etcd de 3 nós e todos os nossos servidores têm endereços IP estáticos, usaremos a descoberta estática. Para iniciar nosso cluster usando a descoberta estática, devemos adicionar diversos parâmetros ao nosso arquivo de configuração. Use um editor para abrir o arquivo modelo templates/etcd.conf.yaml.j2:

      • nano templates/etcd.conf.yaml.j2

      Então, adicione as linhas destacadas a seguir:

      ~/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 %}
      

      Feche e salve o arquivo templates/etcd.conf.yaml.j2 pressionando CTRL+X e depois Y.

      Aqui está uma breve explicação de cada parâmetro:

      • name – um nome humanamente legível para o membro. Por padrão, o etcd usa uma ID único gerado aleatoriamente para identificar cada membro. No entanto, um nome humanamente legível nos permite fazer referência a ele com maior facilidade dentro dos arquivos de configuração e na linha de comando. Aqui, usaremos os nomes de host como os nomes de membro (ou seja, etcd1, etcd2 e etcd3).
      • initial-advertise-peer-urls – uma lista de combinações de endereço IP/porta que outros membros podem usar para se comunicar com este membro. Além da porta da API (2379), o etcd também expõe a porta 2380 para a comunicação ponto a ponto entre membros do etcd. Isso permite que eles enviem mensagens entre si e troquem dados. Observe que essas URLs devem ser acessíveis pelos seus pares (e não ser um endereço IP local).
      • listen-peer-urls – uma lista de combinações de endereço IP/porta onde o membro atual irá escutar a comunicação de outros membros. Isso deve incluir todas as URLs do sinalizador --initial-advertise-peer-urls, mas também as URLs locais como 127.0.0.1:2380. O endereço IP/ porta de destino das mensagens de pares recebidas deve corresponder a uma das URLs listadas aqui.
      • advertise-client-urls – uma lista de combinações de endereço IP/porta que os clientes devem usar para se comunicar com este membro. Essas URLs devem ser acessíveis ao cliente (e não ser um endereço local). Se o cliente estiver acessando o cluster através da internet pública, este deve ser um endereço IP público.
      • listen-peer-urls – uma lista de combinações de endereço IP/porta onde o membro atual irá escutar a comunicação de outros clientes. Isso deve incluir todas as URLs do sinalizador --advertise-client-urls, mas também as URLs locais como 127.0.0.1:2379. O endereço IP/ porta de destino das mensagens de clientes recebidas deve corresponder a uma das URLs listadas aqui.
      • initial-cluster – uma lista de pontos de extremidade para cada membro do cluster. Cada ponto de extremidade deve corresponder a uma das URLs do initial-advertise-peer-urls do membro correspondente.
      • initial-cluster-state – ou new (novo) ou existing (existente).

      Para garantir a consistência, o etcd só pode tomar decisões quando a maioria dos nós estiver em boas condições. Isso é conhecido como estabelecer o quorum. Em outras palavras, em um cluster de três membros, o quorum é alcançado se dois ou mais membros estiverem em boas condições.

      Se o parâmetro initial-cluster-state estiver definido como new, o etcd irá saber que este é um novo cluster sendo inicializado e permitirá que os membros sejam inicializados em paralelo, sem esperar que o quorum seja alcançado. De forma mais concreta, depois que o primeiro membro é iniciado, o quorum não será atingido porque um terço (33,33%) é menor ou igual a 50%. Normalmente, o etcd irá parar e recusar-se a realizar mais ações e o cluster nunca será formado. No entanto, com o initial-cluster-state definido para new, ele irá ignorar a falta inicial de quorum.

      Se for definido como existente, o membro irá tentar se juntar a um cluster existente e espera-se que o quorum já esteja estabelecido.

      Nota: é possível encontrar mais detalhes sobre todos os sinalizadoras de configuração suportados na seção Configuração da documentação do etcd.

      No arquivo modelo templates/etcd.conf.yaml.j2 atualizado, existem algumas instâncias de hostvars. Quando o Ansible for executado, ele irá coletar variáveis vindas de diversas de fontes. Já usamos a variável inventory_hostname anteriormente, mas há muitas outras disponíveis. Essas variáveis estão disponíveis em hostvars[inventory_hostname]['ansible_facts']. Aqui, estamos extraindo os endereços IP privados de cada nó e usamos isso para construir nosso valor de parâmetro.

      Nota: como ativamos a opção de Rede privada quando criamos nossos servidores, cada servidor têm três endereços IP associados a eles:

      • Um endereço IP de loopback – um endereço que é válido apenas dentro da mesma máquina. É usado para que a máquina faça referência a si mesma, por exemplo, 127.0.0.1
      • Um endereço endereço IP público – um endereço que é compartilhado através da internet pública, por exemplo, 178.128.169.51
      • Um endereço IP privado – um endereço que é compartilhável apenas dentro da rede privada; no caso das Droplets da DigitalOcean, existe uma rede privada dentro de cada datacenter, por exemplo, 10.131.82.225

      Cada um desses endereços endereço IP está associado a uma interface de rede diferente. O endereço loopback está associado à interface lo, o endereço IP público à interface eth0, e o endereço IP privado à interface eth1. Estamos usando a interface eth1 para que todo o tráfego fique dentro da rede privada, sem nunca chegar à internet.

      Não é necessário entender as interfaces de rede para este artigo, mas se você quiser aprender mais, Uma introdução à terminologia, interfaces e protocolos de rede é um ótimo lugar para começar.

      A sintaxe {% %} do Jinja2 define a estrutura de loop for que itera através de todos os nós no grupo do etcd para compilar a string initial-cluster em um formato exigido pelo etcd.

      Para formar o novo cluster de três membros, é necessário primeiro interromper o serviço etcd e limpar o diretório de dados antes de inicializar o cluster. Para fazer isso, use um editor para abrir o arquivo playbook.yaml em sua máquina local:

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

      Em seguida, antes da tarefa "Create a data directory", adicione uma tarefa para interromper o serviço 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:
          ...
      

      Em seguida, atualize a tarefa "Create a data directory" para primeiro excluir o diretório de dados e depois recriá-lo:

      ~/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:
          ...
      

      A propriedade with_items define uma lista de strings através das quais esta tarefa irá iterar. Isso é o equivalente a repetir a mesma tarefa duas vezes, mas com valores diferentes para a propriedade state. Aqui, estamos iterando através da lista com os itens absent e directory, garantindo que o diretório de dados seja excluído primeiro e recriado em seguida.

      Feche e salve o arquivo playbook.yaml pressionando CTRL+X e depois Y. Em seguida, execute o ansible-playbook novamente. Agora, o Ansible irá criar um cluster do etcd único com 3 membros:

      • ansible-playbook -i hosts playbook.yaml

      Você pode verificar isso entrando via SSH em qualquer nó que seja membro do etcd:

      Em seguida, execute etcdctl endpoint health --cluster:

      • etcdctl endpoint health --cluster

      Isso irá listar a integridade de cada membro do cluster:

      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

      Agora, criamos com sucesso um cluster do etcd de 3 nós. Podemos verificar isso adicionando uma entrada ao etcd em um dos nós membros, e recuperando-a em outro nó membro. Em um dos nós membros, execute o etcdctl put:

      Em seguida, use um novo terminal para entrar via SSH em um nó membro diferente:

      Depois disso, tente recuperar a mesma entrada usando a chave:

      Você será capaz de recuperar essa entrada, o que prova que o cluster está funcionando:

      Output

      foo bar

      Por fim, saia de cada um dos nós gerenciados e volte para a sua máquina local:

      Neste passo, criamos um novo cluster de 3 nós. Neste momento, a comunicação entre os membros do etcd e seus pares e clientes é realizada através do HTTP. Isso significa que a comunicação é descriptografada e qualquer pessoa capaz de interceptar o tráfego pode ler as mensagens. Isso não representa um problema grande se o cluster e os clientes do etcd estiverem todos implantados dentro de uma rede privada, ou uma rede privada virtual (VPN) que você tem pleno controle. No entanto, se qualquer um dos sistemas de tráfego precisar viajar através de uma rede compartilhada (privada ou pública), então deve-se garantir que este tráfego seja criptografado. Além disso, um mecanismo precisa ser implantado para que um cliente ou um par verifique a autenticidade do servidor.

      No próximo passo, vamos analisar como proteger a comunicação cliente para servidor, bem como a comunicação por pares usando o TLS.

      Passo 9 — Obtendo os endereços IP privados dos nós gerenciados

      Para criptografar mensagens entre nós membros, o etcd usa o Hypertext Transfer Protocol Secure, ou HTTPS, que é uma camada além do protocolo Transport Layer Security, TLS. O TLS usa um sistema de chaves privadas, certificados e entidades confiáveis chamadas Autoridades de certificação (CAs) para autenticar-se e enviar mensagens criptografadas.

      Neste tutorial, cada nó membro precisa gerar um certificado para se identificar e ter este certificado assinado por uma CA. Vamos configurar todos os nós membros para confiar nesta CA e, dessa forma, também confiar em todos os certificados assinados por ela. Isso permite que os nós membros se autentiquem mutuamente entre si.

      O certificado gerado por um nó membro deve permitir que outros nós membros se identifiquem. Todos os certificados incluem um Nome comum (CN) da entidade com a qual estão associados. Isso é usado com frequência como a identidade da entidade. No entanto, ao verificar um certificado, pode ser que as implementações do cliente comparem se as informações coletadas sobre a entidade correspondem às fornecidas no certificado. Por exemplo, quando um cliente baixa o certificado TLS com a entidade CN=foo.bar.com, mas o cliente está na verdade se conectando ao servidor usando endereço IP (por exemplo, 167.71.129.110), então existe uma incompatibilidade e o cliente pode não confiar no certificado. Ao especificar um nome alternativo de entidade (SAN) no certificado, ele informa ao verificador que ambos os nomes pertencem à mesma entidade.

      Como nossos membros do etcd estão emparelhados uns com os outros usando seus endereços IP privados, quando definirmos nossos certificados, vamos precisar fornecer esses endereços IP privados como os nomes alternativos de entidade.

      Para descobrir o endereço IP privado de um nó gerenciado, entre via SSH nele:

      Em seguida, execute o seguinte comando:

      • ip -f inet addr show eth1

      Você verá um resultado parecido com as seguintes linhas:

      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

      Em nosso exemplo de resultado, 10.131.255.176 é o endereço IP privado do nó gerenciado, e é a única informação na qual estamos interessados. Para filtrar tudo exceto o IP privado, podemos canalizar a saída do comando ip para o utilitário sed, que é usado para filtrar e transformar textos.

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

      Agora, a única saída é o próprio endereço IP privado:

      Output

      10.131.255.176

      Após verificar que o comando anterior funciona, saia do nó gerenciado:

      Para incorporar os comandos anteriores em nosso playbook, abra primeiro o arquivo playbook.yaml:

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

      Em seguida, adicione uma nova peça com uma única tarefa antes da nossa peça existente:

      ~/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:
      ...
      

      A tarefa usa o módulo o shell para executar os comandos ip e sed, os quais buscam o endereço IP privado do nó gerenciado. Em seguida, ela registra o valor de retorno do comando shell dentro de uma variável chamada privateIP, que usaremos mais tarde.

      Neste passo, adicionamos uma tarefa ao playbook para obter o endereço IP privado dos nós gerenciados. No próximo passo, vamos usar essas informações para gerar certificados para cada nó membro. Além disso, faremos eles serem assinados por uma Autoridade de Certificação (CA).

      Passo 10 — Gerando as chaves privadas e CSRs dos membros do etcd

      Para que um nó membro receba tráfego criptografado, o remetente deve usar a chave pública do nó membro criptografar os dados. Além disso, o nó membro deve usar sua chave privada para descriptografar o texto cifrado e recuperar os dados originais. A chave pública é empacotada em um certificado e assinada por uma CA para garantir que seja genuína.

      Portanto, vamos precisar gerar uma chave privada e uma solicitação de assinatura de certificado (CSR) para cada nó membro do etcd. Para facilitar o nosso trabalho, vamos gerar todos os pares de chaves e assinar todos os certificados localmente no nó de controle e então copiar os arquivos relevantes para os hosts gerenciados.

      Primeiro, crie um diretório chamado artifacts/, onde vamos colocar os arquivos (chaves e certificados) gerados durante o processo. Abra o arquivo playbook.yaml com um editor:

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

      Nele, use o módulo file para criar o diretório 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:
      ...
      

      Em seguida, adicione uma outra tarefa no final da peça para gerar a chave privada:

      ~/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:
      ...
      

      Criar chaves privadas e CSRs pode ser feito usando os módulos openssl_privatekey e openssl_csr respectivamente.

      O atributo force: True garante que a chave privada seja regenerada toda vez, mesmo que já exista.

      De maneira similar, adicione a nova tarefa a seguir na mesma peça gerar as CSRs para cada membro, usando o módulo 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'] }}"
      

      Estamos especificando que este certificado pode estar envolvido em um mecanismo de assinatura digital para fins da autenticação de servidor. Este certificado é associado ao nome do host (por exemplo, etcd1), mas o verificador também deve tratar os endereços IP privados, locais e de loopback de cada nó como nomes alternativos. Observe que estamos usando a variável privateIP que registramos na peça anterior.

      Feche e salve o arquivo playbook.yaml pressionando CTRL+X e depois Y. Em seguida, execute o playbook novamente:

      • ansible-playbook -i hosts playbook.yaml

      Agora, vamos encontrar um novo diretório chamado artifacts dentro do nosso diretório de projeto. Use ls para listar seu conteúdo:

      Você verá as chaves privadas e as CSRs para cada um dos membros do etcd:

      Output

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

      Neste passo, usamos vários módulos do Ansible para gerar chaves privadas e certificados de chave pública para cada um dos nós membros. No próximo passo, vamos analisar como assinar uma solicitação de assinatura de certificado (CSR).

      Passo 11 — Gerando os certificados CA

      Dentro de um cluster do etcd, os nós membros criptografam mensagens usando a chave pública do receptor. Para garantir que a chave pública seja genuína, o receptor empacota a chave pública em uma solicitação de assinatura de certificado (CSR) e tem uma entidade confiável (ou seja, a CA) para assinar a CSR. Como controlamos todos os nós membros e as CA em que eles confiam, não precisamos usar uma CA externa e podemos atuar como nossa própria CA. Neste passo, atuaremos como nossa própria CA. Isso significa que vamos precisar gerar uma chave privada e um certificado auto-assinado que funcionarão como a CA.

      Primeiro, abra o arquivo playbook.yaml com seu editor:

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

      Em seguida, de maneira similar ao passo anterior, adicione uma tarefa à peça localhost para gerar uma chave privada para a CA:

      ~/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"
      ...
      

      Em seguida, use o módulo openssl_csr para gerar uma nova CSR. O processo é semelhante ao passo anterior, mas neste CSR, estamos adicionando a restrição básica e a extensão de uso de chave para indicar que este certificado pode ser usado como um certificado 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"
      ...
      

      Por fim, use o módulo openssl_certificate para auto-assinar a 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"
      ...
      

      Feche e salve o arquivo playbook.yaml pressionando CTRL+X e depois Y. Em seguida, execute o playbook para aplicar as alterações:

      • ansible-playbook -i hosts playbook.yaml

      Você também pode executar o ls para verificar o conteúdo do diretório artifacts/:

      Agora, você verá o certificado CA recém-gerado (ca.crt):

      Output

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

      Neste passo, geramos uma chave privada e um certificado auto-assinado para a CA. No próximo passo, usaremos o certificado CA para assinar a CSR de cada membro.

      Passo 12 — Assinando as CSRs dos membros do etcd

      Neste passo, vamos assinar a CSR de cada nó membro. Isso será feito de maneira semelhante a como usamos o módulo openssl_certificate para auto-assinar o certificado CA, mas em vez de usar o provedor selfsigned, usaremos o provedor ownca. Isso nos permite assinar usando nosso próprio certificado CA.

      Abra seu playbook:

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

      Acrescente a tarefa destacada a seguir na tarefa "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"
      ...
      

      Feche e salve o arquivo playbook.yaml pressionando CTRL+X e depois Y. Em seguida, execute o playbook novamente para aplicar as alterações:

      • ansible-playbook -i hosts playbook.yaml

      Agora, liste o conteúdo do diretório artifacts/:

      Você verá a chave privada, a CSR e o certificado para cada membro do etcd, além da CA:

      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

      Neste passo, assinamos as CSRs de cada nó membro usando a chave da CA. No próximo passo, vamos copiar os arquivos relevantes em cada nó gerenciado, para que o etcd tenha acesso às chaves e certificados relevantes para configurar as conexões TLS.

      Passo 13 — Copiando chaves privadas e certificados

      Todos os nós precisam ter uma cópia do certificado auto-assinado da CA (ca.crt). Cada nó membro do etcd também precisa ter sua própria chave privada e certificado. Neste passo, vamos fazer upload desses arquivos e colocá-los em um novo diretório /etc/etcd/ssl/.

      Para começar, abra o arquivo playbook.yaml com seu editor:

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

      A fim de fazer essas alterações em nosso playbook do Ansible, atualize primeiro a propriedade path da tarefa Create directory for etcd configuration para criar o diretório /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:
      ...
      

      Em seguida, adicione mais três tarefas após a tarefa modificada para copiar os arquivos:

      ~/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:
      ...
      

      Feche e salve o arquivo playbook.yaml pressionando CTRL+X e depois Y.

      Execute o ansible-playbook novamente para fazer essas alterações:

      • ansible-playbook -i hosts playbook.yaml

      Neste passo, fizemos upload das chaves privadas e certificados para os nós gerenciados. Após termos copiado os arquivos, precisamos agora atualizar nosso arquivo de configuração do etcd para fazer uso deles.

      Passo 14 — Habilitando o TLS no etcd

      No último passo deste tutorial, vamos atualizar algumas configurações do Ansible para habilitar o TLS em um cluster do etcd.

      Primeiro, abra o arquivo modelo templates/etcd.conf.yaml.j2 usando seu editor:

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

      Uma vez dentro, mude todas as URLs para que usem o https como protocolo em vez de http. Além disso, adicione uma seção no final do modelo para especificar a localização do certificado CA, certificado do servidor e chave do servidor:

      ~/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
      

      Feche e salve o arquivo templates/etcd.conf.yaml.j2.

      Em seguida, execute seu playbook do Ansible:

      • ansible-playbook -i hosts playbook.yaml

      Depois disso, entre via SSH em um dos nós gerenciados:

      Uma vez dentro, execute o comando etcdctl endpoint health para verificar se os pontos de extremidade estão usando o HTTPS e se todos os membros estão íntegros:

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

      Como nosso certificado CA não é, por padrão, um certificado CA root confiável instalado no diretório /etc/ssl/certs/, precisamos passá-lo para o etcdctl usando o sinalizador --cacert.

      Isso dará o seguinte resultado:

      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

      Para confirmar se o cluster do etcd está realmente funcionando, podemos mais uma vez criar uma entrada em um nó membro, e recuperá-la em outro nó membro:

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

      Use um novo terminal para entrar via SSH em um nó membro diferente:

      Agora, recupere a mesma entrada usando a chave foo:

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

      Isso irá retornar a entrada, exibindo o resultado abaixo:

      Output

      foo bar

      Você pode fazer o mesmo no terceiro nó para garantir que todos os três membros estejam operacionais.

      Conclusão

      Agora, você provisionou com sucesso um cluster de 3 nós do etcd, protegeu ele com o TLS e confirmou que está funcionando.

      O etcd é uma ferramenta originalmente criada pelo CoreOS. Para entender o uso do etcd em relação ao CoreOS, consulte Como usar o etcdctl e o etcd, o armazenamento de chaves-valores distribuído do CoreOS. O artigo também dá orientação na configuração de um modelo de descoberta dinâmico, algo que foi discutido, mas não demonstrado neste tutorial.

      Como mencionado no início deste tutorial, o etcd é uma parte importante do ecossistema do Kubernetes. Para aprender mais sobre o papel do Kubernetes e do etcd dentro dele, leia Uma introdução ao Kubernetes. Se estiver implantando o etcd como parte de um cluster do Kubernetes, saiba que existem outras ferramentas disponíveis, como o kubespray e o kubeadm. Para mais detalhes sobre o kubeadm, leia Como criar um cluster do Kubernetes usando o Kubeadm no Ubuntu 18.04.

      Por fim, deve-se destacar que este tutorial fez uso de muitas ferramentas, mas não conseguiu se aprofundar em todos eles com muitos detalhes. A seguir, estão disponíveis links que irão fornecer uma análise mais detalhada sobre cada ferramenta:



      Source link

      Настройка и обеспечение безопасности кластера etcd с помощью Ansible в Ubuntu 18.04


      Автор выбрал фонд Wikimedia Foundation для получения пожертвования в рамках программы Write for DOnations.

      Введение

      etcd — это распределенное хранилище типа «ключ-значение», на которое опирается множество платформ и инструментов, включая Kubernetes, Vulcand и Doorman. Внутри Kubernetes etcd используется в качестве глобального хранилища, где хранится состояние кластера. Знание того, как управлять etcd, обязательно для управления кластером Kubernetes. Хотя существует большое количество предложений, использующих Kubernetes, также известных как Kubernetes как услуга, которые избавляют вас от необходимости выполнения административной работы, многие компании все еще предпочитают запускать управляемые кластеры Kubernetes самостоятельно, используя собственные ресурсы, по причине гибкости, которую дает этот подход.

      Первая половина этой статьи поможет вам настроить состоящий из 3 узлов кластер etcd на серверах Ubuntu 18.04. Вторая половина будет посвящена обеспечению безопасности кластера с помощью протокола безопасности транспортного уровня или TLS. Для автоматического запуска каждой настройки мы будем на протяжении всего руководства использовать Ansible. Ansible — это инструмент управления конфигурацией, аналогичный Puppet, Chef и SaltStack, который позволяет нам определять каждый шаг настройки в декларативной манере внутри файлов, которые называются плейбуками.

      В конце этого руководства у вас будет защищенный кластер etcd из 3 узлов, запущенный на ваших серверах. Также у вас будет плейбук Ansible, который позволяет вам многократно и последовательно воссоздавать одну и ту же настройку на новом наборе серверов.

      Предварительные требования

      Для прохождения этого обучающего руководства вам потребуется следующее:

      • Python, pip и пакет pyOpenSSL, установленные на вашем локальном компьютере. Чтобы узнать, как установить Python3, pip и пакеты Python, воспользуйтесь руководством по установке Python 3 и настройке локальной среды программирования в Ubuntu 18.04.

      • Три сервера Ubuntu 18.04 в одной локальной сети с минимум 2 ГБ оперативной памяти и доступом root через SSH. Также вам необходимо задать для серверов имена хостов etcd1, etcd2 и etcd3. Шаги, описанные в этой статье, будут работать на любом базовом сервере, а не только на дроплетах DigitalOcean. Однако если вы хотите разместить ваши серверы в DigitalOcean, вы можете воспользоваться руководством по созданию дроплета в панели управления DigitalOcean, чтобы выполнить это требование. Обратите внимание, что вы должны активировать опцию Private Networking (Частная сеть) при создании вашего дроплета. Чтобы активировать частную сеть для существующих дроплетов, воспользуйтесь руководством по активации частной сети в дроплетах.

      Предупреждение. Поскольку цель этой статьи состоит в знакомстве с настройкой кластера etcd в частной сети, три сервера Ubuntu 18.04 в рамках данной настройки не были протестированы с брандмауэром и доступны при работе с пользователем root. В производственной среде для любого узла, открытого для публичного Интернета, необходимо придерживаться передовых практик обеспечения безопасности при настройке брандмауэра и пользователя sudo. Дополнительную информацию см. в руководстве по начальной настройке сервера Ubuntu 18.04.

      • Пара ключей SSH, обеспечивающая для локального компьютера доступ к серверам etcd1, etcd2 и etcd3. Если вы не знаете, что такое SSH, или у вас нет пары ключей SSH, вы можете получить необходимую информацию, прочитав статью Основы SSH: работа с серверами, клиентами и ключами SSH.

      • Система Ansible, установленная на локальном компьютере. Например, если вы используете Ubuntu 18.04, вы можете установить Ansible, выполнив указания в шаге 1 статьи Установка и настройка Ansible в Ubuntu 18.04. После этого команды ansible и ansible-playbook будут доступны на вашем компьютере. Также вам может пригодиться статья Использование Ansible: справочное руководство. Команды в этом руководстве должны работать с Ansible версии 2.х; мы протестировали его на Ansible 2.9.7 с Python 3.8.2.

      Шаг 1 — Настройка Ansible для узла управления

      Ansible — это инструмент, используемый для управления серверами. Серверы, которыми управляет Ansible, называются управляемыми узлами, а компьютер, на котором запущен Ansible, называется узлом управления. Ansible использует ключи SSH на узле управления, чтобы получить доступ к управляемым узлам. После установки сеанса SSH Ansible запускает набор скриптов для предоставления и настройки управляемых узлов. На этом шаге мы протестируем возможность использования Ansible для подключения к управляемым узлам и запустим команду hostname.

      Типичный день системного администратора может включать управление различными наборами узлов. Например, вы можете использовать Ansible для предоставления новых серверов, а позже использовать Ansible для изменения конфигурации другого набора серверов. Чтобы позволить администраторам лучше организовать набор управляемых узлов, Ansible предоставляет концепцию inventory хостов (или inventory для краткости). Вы можете определить каждый узел, которым вы хотите управлять с помощью Ansible, внутри inventory-файла и организовать их в группы. Затем при запуске команд ansible и ansible-playbook вы можете указать, к каким хостам или группам применяется эта команда.

      По умолчанию Ansible считывает inventory-файл в каталоге /etc/ansible/hosts, однако мы можем указать другой inventory-файл с помощью флага --inventory (или -i для краткости).

      Для начала создайте новый каталог на локальном компьютере (узле управления) для размещения всех файлов данного руководства:

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

      Затем перейдите в только что созданный каталог:

      • cd $HOME/playground/etcd-ansible

      Внутри каталога создайте и откройте пустой inventory-файл с именем hosts с помощью вашего редактора:

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

      Внутри файла hosts перечислите все ваши управляемые узлы в следующем формате, заменив выделенные публичные IP-адреса на реальные IP-адреса ваших серверов:

      ~/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
      

      Строка [etcd] определяет группу с именем etcd. Под определением группы мы перечисляем все наши управляемые узлы. Каждая строка начинается с псевдонима (например, etcd1), который позволяет нам обращаться к каждому хосту, используя простое для запоминания имя вместо длинного IP-адреса. ansible_host и ansible_user — это переменные Ansible. В этом случае они используются для предоставления Ansible публичных IP-адресов и пользовательских имен SSH, которые используются при подключении через SSH.

      Чтобы гарантировать, что Ansible сможет подключаться к нашим управляемым узлам, мы можем протестировать подключение с помощью Ansible и запустить команду hostname на каждом хосте в группе etcd:

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

      Давайте подробно разберем эту команду, чтобы узнать, что означает каждая часть:

      • etcd: указывает шаблон хоста, который используется для определения того, какие хосты из inventory управляются с помощью этой команды. Здесь мы используем имя группы в качестве шаблона хоста.
      • -i hosts: указывает inventory-файл, который нужно использовать.
      • -m command: функциональность Ansible обеспечивается модулями. Модуль command принимает передаваемый в него аргумент и выполняет его как команду на каждом из управляемых узлов. В этом руководстве мы будем внедрять несколько дополнительных модулей Ansible по мере нашего прогресса.
      • -a hostname: аргумент, который необходимо передать в модуль. Количество и типы аргументов зависят от модуля.

      После запуска команды вы получите следующий вывод, который означает, что Ansible настроен корректно:

      Output

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

      Каждая команда, которую запускает Ansible, называется задачей. Использование ansible в командной строке для запуска задач называется запуском ситуативных команд. Преимущество ситуативных команд состоит в том, что они быстрые и требуют минимальной настройки, а недостаток состоит в том, что они запускаются вручную, а значит не могут быть добавлены в систему контроля версий, например Git.

      Небольшим улучшением может быть запись скрипта оболочки и запуск команд с помощью модуля script Ansible. Это позволит нам записать этапы конфигурации, которые мы передали в систему контроля версий. Однако скрипты оболочки имеют императивный характер, что означает, что нам нужно определить команды для запуска («как») для приведения системы в желаемое состояние. Ansible, с другой стороны, выступает за декларативный подход, где мы определяем «какое» состояние сервера нам нужно внутри файлов конфигурации, а Ansible отвечает за приведение сервера в это желаемое состояние.

      Декларативный метод является предпочтительным, поскольку назначение файла конфигурации передается немедленно, что означает, что его легче понять и поддерживать. Также подобный подход возлагает ответственность за обработку пограничных случаев на Ansible, а не на администратора, избавляя от большого объема работы.

      Теперь, когда вы настроили узел управления Ansible для связи с управляемыми узлами, в следующем шаге мы познакомим вас с плейбуками Ansible, которые позволяют определять задачи декларативным образом.

      Шаг 2 — Получение имен хостов управляемых узлов с помощью плейбуков Ansible

      На этом шаге мы воспроизведем то, что было сделано в шаге 1, т. е. выведем имена хостов управляемых узлов, но вместо запуска ситуативных задач мы определим каждую задачу декларативно в виде плейбука Ansible и запустим ее. Цель этого шага — продемонстрировать, как работают плейбуки Ansible. В последующих шагах мы будем выполнять гораздо более серьезные задачи с помощью плейбуков.

      Внутри каталога проекта создайте новый файл с именем playbook.yaml с помощью вашего редактора:

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

      Внутри playbook.yaml добавьте следующие строки:

      ~/playground/etcd-ansible/playbook.yaml

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

      Закройте и сохраните файл playbook.yaml, нажав CTRL+X, а затем Y.

      Плейбук содержит список инструкций; каждая инструкция содержит список задач, которые следует запускать на всех хостах, соответствующих шаблону хоста, указанному ключом hosts. В этом плейбуке у нас есть одна инструкция, содержащая две задачи. Первая задача запускает команду hostname, используя модуль command, и записывает вывод в переменную с именем output. Во второй задаче мы используем модуль debug для вывода свойства stdout_lines переменной output.

      Теперь мы можем запустить этот плейбук с помощью команды ansible-playbook:

      • ansible-playbook -i hosts playbook.yaml

      Вы получите следующий вывод, что означает, что ваш плейбук работает корректно:

      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

      Примечание: ansible-playbook иногда использует cowsay для нестандартного вывода заголовков. Если вы обнаружите в своем терминале много нарисованных с помощью ASCII-графики коров, то впредь будете знать, почему это происходит. Чтобы отключить эту функцию, задайте для переменной среды ANSIBLE_NOCOWS значение 1 перед запуском ansible-playbook, запустив export ANSIBLE_NOCOWS=1 в оболочке.

      На этом шаге мы перешли от запуска императивных ситуативных задач к декларативным плейбукам. В следующем шаге мы заменим эти две демонстрационные задачи на задачи, которые будут настраивать наш кластер etcd.

      Шаг 3 — Установка etcd на управляемые узлы

      На этом шаге мы покажем вам команды для ручной установки etcd и продемонстрируем, как перевести эти самые команды в задачи внутри нашего плейбука Ansible.

      etcd и соответствующий клиент etcdctl доступны в качестве бинарных файлов, которые мы загрузим, извлечем и переместим в каталог, являющийся частью переменной среды PATH. При ручной настройке эти шаги необходимо выполнять для каждого управляемого узла:

      • 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

      Первые четыре команды загружают и извлекают бинарный файл в каталог /opt/etcd/bin/. По умолчанию клиент etcdctl использует API версии 2 для связи с сервером etcd. Поскольку мы запускаем etcd версии 3.x, последняя команда устанавливает для переменной среды ETCDCTL_API значение 3.

      Примечание. Здесь мы используем etcd версии 3.3.13 для компьютеров с процессорами, использующими набор инструкций AMD64. Вы можете найти бинарные файлы для других систем и других версий на официальной странице выпусков на GitHub.

      Чтобы воспроизвести аналогичные шаги в стандартизированном формате, мы можем добавить задачи в наш плейбук. Откройте файл playbook.yaml в вашем редакторе:

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

      Замените все содержимое файла playbook.yaml на следующее содержимое:

      ~/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
      

      Каждая задача использует модуль; для этого набора задач мы используем следующие модули:

      • file: для создания каталога /opt/etcd/bin и последующей настройки разрешений файлов для бинарных файлов etcd и etcdctl.
      • get_url: для загрузки тарбола в формате GZIP на управляемые узлы.
      • unarchive: для извлечения и распаковки бинарных файлов etcd и etcdctl из тарбола в формате GZIP.
      • lineinfile: для добавления записи в файл .profile.

      Чтобы применить эти изменения, закройте и сохраните файл playbook.yaml, нажав CTRL+X, а затем Y. После этого в терминале снова запустите ту же команду ansible-playbook:

      • ansible-playbook -i hosts playbook.yaml

      Раздел PLAY RECAP в выводе будет отображать только ok и 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

      Чтобы подтвердить правильную установку etcd, выполните ручное подключение через SSH к одному из управляемых узлов и запустите etcd и etcdctl:

      etcd1_public_ip — это публичные IP-адреса сервера с именем etcd1. После получения доступа через SSH запустите etcd --version для вывода версии установленного etcd:

      Вы получите вывод, соответствующий представленному ниже, что означает, что бинарный файл etcd успешно установлен:

      Output

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

      Чтобы подтвердить успешную установку etcdctl, запустите etcdctl version:

      Вы увидите примерно следующий результат:

      Output

      etcdctl version: 3.3.13 API version: 3.3

      Обратите внимание, что в выводе указано API version: 3.3, что также подтверждает, что наша переменная среды ETCDCTL_API была настроена корректно.

      Выйдите из сервера etcd1, чтобы вернуться в локальную среду.

      Мы успешно установили etcd и etcdctl на все наши управляемые узлы. В следующем шаге мы добавим дополнительные задачи в нашу инструкцию для запуска etcd в качестве фоновой службы.

      Шаг 4 — Создание юнит-файла для etcd

      Может показаться, что самым быстрым способом запуска etcd с помощью Ansible может быть использование модуля command для запуска /opt/etcd/bin/etcd. Однако этот способ не сработает, поскольку он будет запускать etcd в качестве активного процесса. Использование модуля command будет приводить к зависанию Ansible в ожидании результата, возвращаемого командой etcd, чего никогда не произойдет. Поэтому в этом шаге мы обновим наш плейбук для запуска нашего бинарного файла etcd в качестве фоновой службы.

      Ubuntu 18.04 использует systemd в качестве инит-системы, что означает, что мы можем создавать новые службы, записывая юнит-файлы и размещая их внутри каталога /etc/systemd/system/.

      Во-первых, внутри каталога нашего проекта создайте новый каталог с именем files/:

      Затем с помощью вашего редактора создайте в этом каталоге новый файл с именем etcd.service:

      Скопируйте следующий блок кода в файл 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
      

      Этот юнит-файл определяет службу, которая запускает исполняемый файл в /opt/etcd/bin/etcd, уведомляет systemd о завершении инициализации и перезапускается при каждом случае сбоя.

      Примечание. Если вы хотите узнать больше о systemd и юнит-файлах или хотите настроить юнит-файл согласно вашим нуждам, ознакомьтесь с руководством Знакомство с юнитами systemd и юнит-файлами.

      Закройте и сохраните файл files/etcd.service, нажав CTRL+X, а затем Y.

      Далее нам нужно добавить в наш плейбук задачу, которая будет копировать локальный файл files/etcd.service в каталог /etc/systemd/system/etcd.service для каждого управляемого узла. Мы можем сделать это с помощью модуля copy.

      Откройте ваш плейбук:

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

      Добавьте следующую выделенную задачу после существующих задач:

      ~/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
      

      После копирования юнит-файла в /etc/systemd/system/etcd.service служба будет определена.

      Сохраните и закройте плейбук.

      Запустите ту же команду ansible-playbook снова для применения изменений:

      • ansible-playbook -i hosts playbook.yaml

      Чтобы убедиться, что изменения вступили в силу, выполните подключение через SSH к одному из управляемых узлов:

      Затем запустите systemctl status etcd для отправки systemd запроса о состоянии службы etcd:

      Вы получите следующий вывод, который подтверждает, что служба загружена:

      Output

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

      Примечание. Последняя строка (Active: inactive (dead)) вывода указывает на неактивный статус службы, что означает, что она не будет запускаться автоматически при запуске системы. Это ожидаемое поведение, которое не является ошибкой.

      Нажмите q для возврата в оболочку, а затем запустите команду exit для выхода из управляемого узла и возврата в локальную оболочку:

      В этом шаге мы обновили наш плейбук для запуска бинарного файла etcd в качестве службы systemd. В следующем шаге мы продолжим настройку etcd, предоставив службе пространство для хранения данных.

      Шаг 5 — Настройка каталога данных

      etcd — это хранилище данных типа «ключ-значение», а это значит, что мы должны предоставить ему пространство для хранения данных. В этом шаге мы обновим наш плейбук для определения специального каталога хранения данных, который будет использовать etcd.

      Откройте ваш плейбук:

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

      Добавьте следующую задачу в конец списка задач:

      ~/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
      

      Здесь мы используем /var/lib/etcd/hostname.etcd в качестве каталога данных, где hostname — это имя хоста текущего управляемого узла. inventory_hostname — это переменная, представляющая имя хоста текущего управляемого узла; Ansible подставляет ее значение автоматически. Конструкция с фигурными скобками (например, {{ inventory_hostname }}) применяется для подстановки переменной, поддерживаемой в механизме шаблонов Jinja2, используемом по умолчанию в Ansible.

      Закройте текстовый редактор и сохраните файл.

      Далее нам нужно будет указать etcd на необходимость использования этого каталога данных. Мы сделаем это, передав параметр data-dir в etcd. Чтобы задать параметры etcd, мы можем использовать сочетание переменных среды, флагов командной строки и файлов конфигурации. В этом руководстве мы будем использовать файл конфигурации, поскольку гораздо удобнее изолировать все конфигурации внутри файла вместо их размещения по всему плейбуку.

      В каталоге вашего проекта создайте новый каталог с именем templates/:

      Затем с помощью вашего редактора создайте в этом каталоге новый файл с именем etcd.conf.yaml.j2:

      • nano templates/etcd.conf.yaml.j2

      Затем скопируйте следующую строку и вставьте ее в файл:

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

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

      Этот файл использует тот же синтаксис для подстановки переменной в Jinja2, что и наш плейбук. Чтобы подставить переменные и загрузить результат в каждый управляемый хост, мы можем использовать модуль template. Он работает схожим с модулем copy образом, но выполняет подстановку переменных перед загрузкой.

      Выйдите из etcd.conf.yaml.j2, а затем откройте ваш плейбук:

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

      Добавьте в список задач следующие задачи по созданию каталога и загрузке в него шаблонного файла конфигурации:

      ~/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
      

      Сохраните и закройте файл.

      Поскольку мы внесли это изменение, нам нужно обновить юнит-файл нашей службы, чтобы передать ему расположение нашего файла конфигурации (например, /etc/etcd/etcd.conf.yaml).

      Откройте файл службы etcd на локальном компьютере:

      Обновите файл files/etcd.service, добавив флаг --config-file, как показано в следующем выделенном фрагменте:

      ~/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
      

      Сохраните и закройте файл.

      В этом шаге мы использовали наш плейбук для предоставления etcd каталога для хранения данных. В следующем шаге мы добавим еще несколько задач для перезапуска службы etcd и ее автоматической загрузки при запуске.

      Шаг 6 — Включение и запуск службы etcd

      При внесении изменений в юнит-файл службы мы должны перезапустить службу для вступления этих изменений в силу. Мы можем сделать это, запустив команду systemctl restart etcd. Кроме того, для автоматического запуска службы etcd при запуске системы нам нужно воспользоваться командой systemctl enable etcd. В этом шаге мы запустим эти две команды с помощью плейбука.

      Чтобы запустить команды, воспользуемся модулем command:

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

      Добавьте следующие задачи в конец списка задач:

      ~/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
      

      Сохраните и закройте файл.

      Запустите ansible-playbook -i hosts playbook.yaml еще раз:

      • ansible-playbook -i hosts playbook.yaml

      Чтобы убедиться, что служба etcd теперь перезапущена и активирована, подключитесь через SSH к одному из управляемых узлов:

      Затем запустите systemctl status etcd для проверки состоянии службы etcd:

      Вы получите значения enabled и active (running) в выделенных ниже местах; это означает, что изменения, которые мы внесли в наш плейбук, вступили в силу:

      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)

      В этом шаге мы использовали модуль command для запуска команд systemctl, которые перезапускают и активируют службу etcd на наших управляемых узлах. Теперь, когда мы выполнили установку etcd, в следующем шаге мы протестируем ее функциональность, выполнив несколько базовых операций создания, чтения, обновления и удаления (CRUD).

      Шаг 7 — Тестирование etcd

      Хотя у нас есть работающая установка etcd, она небезопасна и еще не готова к производственному использованию. Но прежде чем мы сможем обеспечить безопасность нашей установки etcd в последующих шагах, давайте сначала поймем, что может делать etcd с точки зрения функциональности. В этом шаге мы будем вручную отправлять запросы в etcd для добавления, получения, обновления и удаления хранимых данных.

      По умолчанию etcd предоставляет API, который прослушивает порт 2379 для связи с клиентом. Это означает, что мы можем отправлять etcd запросы API в чистом виде с помощью клиента HTTP. Однако быстрее будет использовать официальный клиент etcd etcdctl, который позволяет вам создавать/обновлять, получать и удалять пары «ключ-значение» с помощью подкоманд put, get и del соответственно.

      Убедитесь, что вы все еще находитесь внутри управляемого узла etcd1, и запустите следующие команды etcdctl, чтобы убедиться, что ваша установка etcd работает.

      Во-первых, создайте новую запись с помощью подкоманды put.

      Подкоманда put имеет следующий синтаксис:

      etcdctl put key value
      

      В etcd1 запустите следующую команду:

      Команда, которую мы только что запустили, указывает etcd записать значение "bar" для ключа foo в хранилище.

      После этого вы получите сообщение OK в выводе, что сигнализирует о сохранении данных:

      Output

      OK

      После этого вы можете получить эту запись с помощью подкоманды get, которая имеет синтаксис etcdctl get key:

      Вы получите данный вывод, который показывает ключ в первой строке и значение, которое вы вставили ранее, во второй строке:

      Output

      foo bar

      Мы можем удалить запись с помощью подкоманды del, которая имеет синтаксис etcdctl del key:

      Вы получите следующий вывод, который указывает количество удаленных записей:

      Output

      1

      Теперь давайте запустим подкоманду get еще раз, чтобы попытаться получить удаленную пару «ключ-значение».

      Вы не получите вывод, что означает, что etcdctl не может получить пару «ключ-значение». Это подтверждает, что запись удалена и не может быть найдена.

      Теперь, когда вы протестировали основные операции etcd и etcdctl, давайте выйдем из нашего управляемого узла и вернемся в локальную среду:

      В этом шаге мы использовали клиент etcdctl для отправки запросов в etcd. На этом этапе мы используем три отдельных экземпляра etcd, каждый из которых действует независимо друг от друга. Однако etcd — это распределенное хранилище данных типа «ключ-значение», что означает, что несколько экземпляров etcd можно сгруппировать для формирования одного кластера. Каждый экземпляр в этом случае становится членом кластера. После формирования кластера вы сможете получить пару «ключ-значение», которая была вставлена из другого члена кластера. В следующем шаге мы используем наш плейбук для преобразования трех кластеров с одним узлом в один кластер с 3 узлами.

      Шаг 8 — Создание кластера с помощью статического обнаружения

      Чтобы создать один кластер из 3 узлов вместо трех кластеров с 1 узлом, нам нужно сконфигурировать эти установки etcd, чтобы они могли общаться друг с другом. Это означает, что каждая из них должна знать IP-адреса остальных. Этот процесс называется обнаружением. Обнаружение можно реализовать в форме статической конфигурации или динамического обнаружения службы. В этом шаге мы будем обсуждать разницу между этими двумя подходами, а также обновим наш плейбук для настройки кластера etcd с помощью статического обнаружения.

      Обнаружение с помощью статической конфигурации — это метод, который требует наименьшей настройки. Именно в этом случае конечные точки каждого члена передаются в команду etcd перед ее выполнением. Для использования статической конфигурации необходимо соблюсти следующие условия перед инициализацией кластера:

      • известное количество членов
      • известные конечные точки каждого члена
      • статические IP-адреса для всех конечных точек

      Если соблюсти эти условия невозможно, вы можете использовать службу динамического обнаружения. При динамическом обнаружении службы все экземпляры будут регистрироваться в службе обнаружения, что позволяет каждому члену получать информацию о расположении других членов.

      Поскольку мы знаем, что нам нужен кластер etcd с 3 узлами, а все наши серверы имеют статические IP-адреса, мы будем использовать статическое обнаружение. Чтобы инициировать создание кластера с помощью статического обнаружения, нам нужно добавить несколько параметров в наш файл конфигурации. Воспользуйтесь редактором, чтобы открыть файл шаблона templates/etcd.conf.yaml.j2:

      • nano templates/etcd.conf.yaml.j2

      Затем добавьте следующие выделенные строки:

      ~/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 %}
      

      Закройте и сохраните файл templates/etcd.conf.yaml.j2, нажав CTRL+X, а затем Y.

      Ниже представлено краткое разъяснение каждого параметра:

      • name — это человекочитаемое имя члена. По умолчанию etcd использует уникальный, генерируемый случайным образом идентификатор для каждого члена, а человекочитаемое имя позволяет легче ссылаться на них внутри файлов конфигурации и в командной строке. Здесь мы будем использовать имена хостов в качестве имен членов (т. е. etcd1, etcd2 и etcd3).
      • initial-advertise-peer-urls — это список комбинаций IP-адреса/порта, которые могут использовать другие члены для связи с этим членом. Помимо порта API (2379) etcd также предоставляет порт 2380 для коммуникации между членами etcd, что позволяет им отправлять сообщения друг другу и обмениваться данными. Обратите внимание, что эти URL-адреса должны быть доступны для других членов (и не быть локальными IP-адресами).
      • listen-peer-urls — это список комбинаций IP-адреса/порта, с помощью которых текущий член будет прослушивать данные, поступающие от других членов. Он должен включать все URL-адреса, переданные с флагом --initial-advertise-peer-urls, а также локальные URL-адреса, такие как 127.0.0.1:2380. Комбинации IP-адреса/порта назначения входящих сообщений других членов должны соответствовать одному из перечисленных здесь URL-адресов.
      • advertise-client-urls — это список комбинаций IP-адреса/порта, которые клиенты должны использовать для коммуникации с этим членом. Эти URL-адреса должны быть доступны клиенту (и не быть локальными адресами). Если клиент получает доступ к кластеру через общедоступную часть Интернета, это должен быть публичный IP-адрес.
      • listen-client-urls — это список комбинаций IP-адреса/порта, с помощью которых текущий член будет прослушивать данные, поступающие от клиентов. Он должен включать все URL-адреса, переданные с флагом --advertise-client-urls, а также локальные URL-адреса, такие как 127.0.0.1:2380. Комбинации IP-адреса/порта назначения входящих сообщений других клиентов должны соответствовать одному из перечисленных здесь URL-адресов.
      • initial-cluster — это список конечных точек для каждого члена кластера. Каждая конечная точка должна соответствовать одному из URL-адресов списка initial-advertise-peer-urls соответствующего члена.
      • initial-cluster-state — либо значение new, либо existing.

      Чтобы гарантировать последовательность, etcd может принимать решения только в том случае, когда большинство узлов являются рабочими. Подобная практика известна как достижение кворума. Другими словами, в кластере из трех членов кворум достигается, если два или более членов являются рабочими.

      Если для параметра initial-cluster-state установлено значение new, etcd будет знать, что это новый кластер, который будет запущен, и позволит членам начинать работу параллельно, не ожидая достижения кворума. Если говорить конкретнее, после запуска первого члена у него не будет кворума, поскольку одна треть (33,33%) меньше или равна 50%. Обычно etcd будет приостанавливать работу и отказывать в совершении любых действий, а кластер не будет сформирован. Однако, если для initial-cluster-state установлено значение new, отсутствие кворума будет игнорироваться.

      Если установлено значение existing, член будет пытаться присоединиться к существующему кластеру и ожидать, что кворум будет достигнут.

      Примечание. Дополнительную информацию обо всех поддерживаемых флагах конфигурации вы можете найти в разделе конфигурации документации etcd.

      В обновленном файле шаблонов templates/etcd.conf.yaml.j2 существует несколько экземпляров hostvars. Во время работы Ansible собирает переменные из разных источников. Мы уже использовали переменную inventory_hostname ранее, но существует большое количество других переменных. Эти переменные доступны в виде hostvars[inventory_hostname]['ansible_facts']. Здесь мы извлекаем частные IP-адреса каждого узла и используем их для построения значения нашего параметра.

      Примечание. Поскольку мы включили опцию Private Networking (Частная сеть) при создании наших серверов, каждый сервер будет иметь три связанных с ним IP-адреса.

      • Кольцевой IP-адрес — адрес, который действителен внутри одного компьютера. Он используется для ссылки компьютера на себя самого, например, 127.0.0.1.
      • Публичный IP-адрес — адрес, который размещается в общедоступной части Интернета, например, 178.128.169.51.
      • Частный IP-адрес — адрес, который маршрутизируется только в частной сети; в случае с дроплетами DigitalOcean внутри каждого набора данных существует частная сеть, например, 10.131.82.225.

      Каждый из этих IP-адресов ассоциируется с другим сетевым интерфейсом — кольцевой адрес ассоциируется с интерфейсом lo, публичный — с интерфейсом eth0, а частный — с интерфейсом eth1. Мы используем интерфейс eth1, чтобы весь трафик оставался внутри частной сети, не попадая в Интернет.

      Понимание сетевых интерфейсов в рамках этой статьи не обязательно, но если вы хотите узнать больше по этой теме, рекомендуем вам начать со статьи Знакомство с сетевой терминологией, интерфейсами и протоколами.

      Синтаксис {% %} Jinja2 определяет структуру цикла for для итерации по каждому узлу в группе etcd и получения строки initial-cluster в требуемом etcd формате.

      Чтобы сформировать новый кластер из трех членов, необходимо сначала остановить работу службы etcd и очистить каталог данных перед запуском кластера. Для этого откройте в редакторе файл playbook.yaml на локальном компьютере:

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

      Затем перед задачей "Create a data directory" (Создать каталог данных) добавьте задачу для остановки службы 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:
          ...
      

      Затем обновите задачу "Create a data directory" (Создать каталог данных) таким образом, чтобы каталог данных сначала удалялся, а потом создавался снова:

      ~/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:
          ...
      

      Свойство with_items определяет список строк, по которым эта задача будет итерироваться. Это эквивалентно повторению одной и той же задачи дважды, но с разными значениями свойства state. Здесь мы итерируемся по списку с элементами absent и directory, что гарантирует, что каталог данных сначала удаляется, а потом создается повторно.

      Закройте и сохраните файл playbook.yaml, нажав CTRL+X, а затем Y. Затем запустите ansible-playbook повторно. Ansible теперь создаст отдельный кластер etcd с 3 членами:

      • ansible-playbook -i hosts playbook.yaml

      Вы можете проверить это, выполнив подключение через SSH к любому узлу etcd:

      После установления подключения запустите команду etcdctl endpoint health --cluster:

      • etcdctl endpoint health --cluster

      В результате выполнения команды будут выведены данные о состоянии каждого члена кластера:

      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

      Мы успешно создали кластер etcd из 3 узлов. Мы можем подтвердить это, добавив запись в etcd на одном узле и получив ее на другом узле. На одном из узлов запустите etcdctl put:

      Затем используйте новый терминал для подключения через SSH к другому узлу:

      Далее попытайтесь получить ту же запись с помощью ключа:

      Вы сможете получить запись, что доказывает, что кластер работает:

      Output

      foo bar

      В заключение выйдите из каждого управляемого узла и вернитесь на локальный компьютер:

      В этом шаге мы предоставили новый кластер с 3 узлами. На данный момент связь между членами etcd и другими узлами и клиентами осуществляется через HTTP. Это означает, что коммуникации не зашифрованы, и любая сторона, которая может перехватить трафик, сможет прочитать сообщения. Это не является большой проблемой, если кластер etcd и клиенты размещены внутри частной сети или виртуальной частной сети (VPN), которую вы полностью контролируете. Однако, если какой-либо трафик должен проходить через общую сеть (частную или публичную), вам нужно гарантировать, что этот трафик будет зашифрован. Кроме того, необходимо создать механизм, который позволяет клиенту или другому узлу проверить аутентичность сервера.

      В следующем шаге мы рассмотрим то, как обеспечить безопасность коммуникации между клиентом и серверами, а также другими узлами, используя TLS.

      Шаг 9 — Получение частных IP-адресов управляемых узлов

      Для шифрования сообщений, пересылаемых между узлами, etcd использует протокол защищенного переноса гипертекста, или HTTPS, который представляет собой слой поверх протокола безопасности транспортного уровня, или TLS. TLS использует систему приватных ключей, сертификатов и доверенных объектов, называемых центрами сертификации (ЦС), для аутентификации и отправки зашифрованных сообщений друг другу.

      В этом руководстве каждый узел должен генерировать сертификат для собственной идентификации и получать подпись ЦС для этого сертификата. Мы настроим все узлы членов так, чтобы они доверяли этому ЦС, а значит доверяли любым сертификатам, которые он подписал. Это позволяет узлам взаимно аутентифицировать друг друга.

      Сертификат, который генерирует узел, должен позволить другим узлам идентифицировать себя. Все сертификаты включают стандартное имя (CN) объекта, с которым они ассоциируются. Часто оно используется как идентификатор объекта. Однако при проверке сертификата клиентские реализации могут сравнить собранную информацию об объекте с информацией, представленной в сертификате. Например, когда клиент загрузил сертификат TLS с субъектом CN=foo.bar.com, но клиент фактически подключился к серверу с помощью IP-адреса (например, 167.71.129.110), возникает противоречие, и клиент может не доверять сертификату. Указанное в сертификате дополнительное имя субъекта (SAN) во время верификации сообщает, что оба имени принадлежат одному и тому же объекту.

      Поскольку наши члены etcd обмениваются данными, используя свои частные IP-адреса, когда мы определяем наши сертификаты, нам нужно будет предоставить эти частные IP-адреса в качестве дополнительных имен субъекта.

      Чтобы узнать частный IP-адрес управляемого узла, выполните подключение через SSH к этому узлу:

      Затем запустите следующую команду:

      • ip -f inet addr show eth1

      Вы увидите вывод примерно следующего содержания:

      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

      В нашем примере вывод 10.131.255.176 — это частный IP-адрес управляемого узла, который является единственной интересующей нас информацией. Чтобы отфильтровать все остальное содержание помимо частного IP-адреса, мы можем передать вывод команды ip в утилиту sed, которая используется для фильтрации и преобразования текста.

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

      Теперь единственной информацией в выводе является частный IP-адрес:

      Output

      10.131.255.176

      Когда вы убедитесь, что предыдущая команда работает, выйдите из управляемого узла:

      Чтобы включить предшествующие команды в наш плейбук, откройте файл playbook.yaml:

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

      Затем добавьте новую инструкцию с одной задачей перед существующей инструкцией:

      ~/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:
      ...
      

      Задача использует модуль shell для запуска команд ip и sed, которые получают частный IP-адрес управляемого узла. Затем он регистрирует возвращаемое значение команды shell внутри переменной с именем privateIP, которую мы будем использовать позже.

      В этом шаге мы добавили в плейбук задачу получения частного IP-адреса управляемых узлов. В следующем шаге мы будем использовать эту информацию для генерирования сертификатов для каждого узла и получим подпись для этих сертификатов в центре сертификации (ЦС).

      Шаг 10 — Генерация приватных ключей и запросов на подпись сертификата для членов etcd

      Чтобы узел мог принимать шифрованный трафик, отправитель должен использовать публичный ключ узла для шифрования данных, а узел должен использовать свой приватный ключ для расшифровки зашифрованного сообщения и получения оригинальных данных. Публичный ключ упаковывается в сертификат и подписывается ЦС для гарантии его подлинности.

      Следовательно, нам нужно будет создать приватный ключ и запрос на подпись сертификата (CSR) для каждого узла etcd. Чтобы облегчить эту задачу, мы сгенерируем все пары ключей и подпишем все сертификаты локально, на узле управления, а затем скопируем соответствующие файлы на управляемые хосты.

      Сначала создайте каталог с именем artifacts/, куда мы поместим файлы (ключи и сертификаты), сгенерированные в ходе этого процесса. Откройте файл playbook.yaml в редакторе:

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

      Используйте модуль file для создания каталога 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:
      ...
      

      Затем добавьте еще одну задачу в конец инструкции для генерирования приватного ключа:

      ~/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:
      ...
      

      Создание приватных ключей и запросов на подпись сертификата выполняется с помощью модулей openssl_privatekey и openssl_csr соответственно.

      Атрибут force: True гарантирует, что приватный ключ генерируется заново, даже если он уже существует.

      Аналогичным образом добавьте следующую новую задачу в ту же инструкцию для генерирования запроса на подпись сертификата для каждого члена с помощью модуля 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'] }}"
      

      Мы указываем, что данный сертификат может быть использован в механизме цифровой подписи для аутентификации сервера. Этот сертификат ассоциируется с именем хоста (например, etcd1), но при проверке необходимо также рассматривать приватные и локальные кольцевые IP-адреса каждого узла в качестве альтернативных имен. Обратите внимание на использование переменной privateIP, которую мы зарегистрировали в предыдущей инструкции.

      Закройте и сохраните файл playbook.yaml, нажав CTRL+X, а затем Y. Затем запустите наш плейбук повторно:

      • ansible-playbook -i hosts playbook.yaml

      Теперь мы найдем новый каталог с именем artifacts внутри каталога проекта; используйте ls для вывода его содержимого:

      Вы получите приватные ключи и запросы на подпись сертификата для каждого члена etcd:

      Output

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

      В этом шаге мы использовали несколько модулей Ansible для генерирования приватных ключей и сертификатов публичного ключа для каждого узла. В следующем шаге мы узнаем, как подписать запрос на подпись сертификата (CSR).

      Шаг 11 — Генерирование сертификатов ЦС

      В кластере etcd узлы шифруют сообщения с помощью публичного ключа получателя. Чтобы убедиться в подлинности публичного ключа, получатель упаковывает публичный ключ в запрос на подпись сертификата (CSR) и просит доверенный объект (например, ЦС) подписать CSR. Поскольку мы контролируем все узлы и ЦС, которым они доверяют, нам не нужно использовать внешний ЦС, и мы можем выступать в качестве собственного ЦС. В этом шаге мы будем действовать в качестве собственного ЦС, что означает, что нам нужно будет генерировать приватный ключ и самоподписанный сертификат, который будет функционировать в качестве ЦС.

      Во-первых, откройте файл playbook.yaml в редакторе:

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

      Затем, как и в предыдущем шаге, добавьте задачу в инструкцию localhost для генерирования приватного ключа для ЦС:

      ~/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"
      ...
      

      Затем воспользуйтесь модулем openssl_csr для генерирования нового запроса на подпись сертификата. Это похоже на предыдущий шаг, но в этом запросе на подпись сертификата мы добавляем базовое ограничение и расширение использования ключа, чтобы показать, что данный сертификат можно использовать в качестве сертификата ЦС:

      ~/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"
      ...
      

      Наконец, воспользуйтесь модулем openssl_certificate для самостоятельной подписи 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"
      ...
      

      Закройте и сохраните файл playbook.yaml, нажав CTRL+X, а затем Y. Затем запустите наш плейбук повторно для применения изменений:

      • ansible-playbook -i hosts playbook.yaml

      Также вы можете запустить команду ls для проверки содержимого каталога artifacts/:

      Теперь вы получите заново созданный сертификат ЦС (ca.crt):

      Output

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

      В этом шаге мы сгенерировали приватный ключ и самоподписанный сертификат для ЦС. В следующем шаге мы будем использовать сертификат ЦС для подписи CSR каждого члена.

      Шаг 12 — Подписание запросов на подпись сертификата для членов etcd

      В этом шаге мы будем подписывать CSR каждого узла. Это процесс аналогичен тому, как мы использовали модуль openssl_certificate для самостоятельной подписи сертификата ЦС, но вместо использования поставщика selfsigned мы будем использовать поставщика ownca, который позволяет добавлять подпись с помощью нашего собственного сертификата ЦС.

      Откройте ваш плейбук:

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

      Добавьте следующую выделенную задачу в задачу "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"
      ...
      

      Закройте и сохраните файл playbook.yaml, нажав CTRL+X, а затем Y. Затем запустите плейбук повторно для применения изменений:

      • ansible-playbook -i hosts playbook.yaml

      Теперь выведите содержимое каталога artifacts/:

      Вы получите приватный ключ, CSR и сертификат для каждого члена etcd и ЦС:

      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

      В этом шаге мы подписали CSR каждого узла с помощью ключа ЦС. В следующем шаге мы скопируем соответствующие файлы на каждый управляемый узел, чтобы etcd смог получить доступ к соответствующим ключам и сертификатам для настройки подключений TLS.

      Шаг 13 — Копирование приватных ключей и сертификатов

      Каждый узел должен иметь копию самоподписанного сертификата ЦС (ca.crt). Каждый узел etcd также должен иметь свой собственный приватный ключ и сертификат. В этом шаге мы загрузим эти файлы и поместим их в новый каталог /etc/etcd/ssl/.

      Для начала откройте файл playbook.yaml в редакторе:

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

      Чтобы внести эти изменения в наш плейбук Ansible, сначала обновите свойство path задачи Create directory for etcd configuration (Создать каталог для конфигурации ectd), чтобы создать каталог /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:
      ...
      

      Затем сразу после измененной задачи добавьте еще три задачи для копирования файлов:

      ~/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:
      ...
      

      Закройте и сохраните файл playbook.yaml, нажав CTRL+X, а затем Y.

      Запустите ansible-playbook снова для внесения этих изменений:

      • ansible-playbook -i hosts playbook.yaml

      В этом шаге мы успешно загрузили приватные ключи и сертификаты на управляемые узлы. После копирования файлов нам нужно обновить наш файл конфигурации etcd, чтобы мы могли использовать эти файлы.

      Шаг 14 — Активация TLS на etcd

      В последнем шаге данного руководства мы обновим ряд конфигураций Ansible для активации TLS в кластере etcd.

      Сначала откройте файл templates/etcd.conf.yaml.j2 с помощью редактора:

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

      Внутри файла измените все URL-адреса для использования https в качестве протокола вместо http. Кроме того, добавьте раздел в конце шаблона для указания расположения сертификата ЦС, сертификата сервера и ключа сервера:

      ~/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
      

      Закройте и сохраните файл templates/etcd.conf.yaml.j2.

      Затем запустите ваш плейбук Ansible:

      • ansible-playbook -i hosts playbook.yaml

      Подключитесь по SSH к одному из управляемых узлов:

      Выполнив подключение, запустите команду etcdctl endpoint health для проверки использования HTTPS конечными точками и состояния всех членов:

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

      Поскольку наш сертификат ЦС по умолчанию не является доверенным корневым сертификатом ЦС, установленным в каталоге /etc/ssl/certs/, нам нужно передать его etcdctl с помощью флага --cacert.

      Результат будет выглядеть следующим образом:

      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

      Чтобы убедиться, что кластер etcd действительно работает, мы можем снова создать запись на узле и получить ее из другого узла:

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

      Затем используйте новый терминал для подключения через SSH к другому узлу:

      Теперь попробуйте получить ту же запись с помощью ключа foo:

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

      Это позволит получить запись, показанную в выводе ниже:

      Output

      foo bar

      Вы можете сделать то же самое на третьем узле, чтобы убедиться, что все три члена работоспособны.

      Заключение

      Вы успешно создали кластер etcd, состоящий из 3 узлов, обеспечили его безопасность с помощью TLS и подтвердили его работоспособность.

      etcd — это инструмент, первоначально созданный для CoreOS. Чтобы понять, как etcd используется вместе с CoreOS, прочитайте статью Использование Etcdctl и Etcd, распределенного хранилища типа «ключ-значение» для CoreOS. В этой статье вы также можете ознакомиться с настройкой модели динамического обнаружения, которая была описана, но не продемонстрирована в данном руководстве.

      Как отмечалось в начале данного руководства, etcd является важной частью экосистемы Kubernetes. Дополнительную информацию о Kubernetes и роли etcd в рамках этой системы вы можете найти в статье Знакомство с Kubernetes. Если вы развертываете etcd в рамках кластера Kubernetes, вам могут пригодиться другие доступные инструменты, такие как kubespray и kubeadm. Дополнительную информацию о последних можно найти в статье Создание кластера Kubernetes с помощью kubeadm в Ubuntu 18.04.

      Наконец, в этом руководстве мы использовали множество инструментов, но не могли достаточно подробно описать каждый из них. Ниже вы найдете ссылки, которые позволят вам более детально познакомиться с каждым из этих инструментов:



      Source link