El autor seleccionó a Wikimedia Foundation para recibir una donación como parte del programa Write for DOnations.
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.
Para completar esta guía, necesitará lo siguiente:
Python, pip
y el paquete pyOpenSSL
instalados en su máquina local. Para obtener información sobre cómo instalar Python3, pip y paquetes de Python, consulte Cómo instalar Python 3 y configurar un entorno de programación local en Ubuntu 18.04.
Tres servidores de Ubuntu 18.04 en la misma red local, con un mínimo de 2 GB de RAM acceso SSH root. También debe configurar los servidores con los nombres de host etcd1, etcd2 y etcd3. Los pasos que se describen en este artículo funcionan en cualquier servidor genérico, no necesariamente en Droplets de DigitalOcean. Sin embargo, si desea alojar sus servidores en DigitalOcean, puede seguir la guía Cómo crear un Droplet desde el panel de control de DigitalOcean para cumplir con este requisito. Tenga en cuenta que debe habilitar la opción Red privada al crear su Droplet. Para habilitar redes privadas en Droplets existentes, consulte Cómo habilitar redes privadas en Droplets.
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.
Un par de claves SSH que permita a su máquina local acceder a los servidores etcd1, etcd2 y etcd3. Si no sabe qué es SSH o no tiene un par de claves SSH, puede obtener información al respecto en el artículo Conceptos básicos de SSH: Cómo trabajar con servidores, clientes y claves SSH.
Ansible instalado en su máquina local. Por ejemplo, si está ejecutando Ubuntu 18.04, puede instalar Ansible siguiendo el Paso 1 del artículo Cómo instalar y configurar Ansible en Ubuntu 18.04. Esto hará que los comandos ansible
y ansible-playbook
estén disponibles en su equipo. También es conveniente mantener Cómo usar Ansible: guía de referencia a mano. Los comandos que se utilizan en este tutorial deberían funcionar con Ansible v2.x; los probamos en Ansible v2.9.7 con Python v3.8.2.
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:
[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:
Outputetcd2 | 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.
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
:
- 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:
OutputPLAY [etcd] ***********************************************************************************************************************
TASK [Gathering Facts] ************************************************************************************************************
ok: [etcd2]
ok: [etcd3]
ok: [etcd1]
TASK [Retrieve hostname] **********************************************************************************************************
changed: [etcd2]
changed: [etcd3]
changed: [etcd1]
TASK [Print hostname] *************************************************************************************************************
ok: [etcd1] => {
"output.stdout_lines": [
"etcd1"
]
}
ok: [etcd2] => {
"output.stdout_lines": [
"etcd2"
]
}
ok: [etcd3] => {
"output.stdout_lines": [
"etcd3"
]
}
PLAY RECAP ************************************************************************************************************************
etcd1 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
etcd2 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
etcd3 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
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.
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:
- 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
:
- ssh root@etcd1_public_ip
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:
- etcd --version
Obtendrá un resultado similar al siguiente, que indica que el binario etcd
está instalado correctamente:
Outputetcd 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
:
- etcdctl version
Verá un resultado similar al siguiente:
Outputetcdctl 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.
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:
- mkdir files
A continuación, cree un archivo nuevo denominado etcd.service
en ese directorio con su editor:
- nano files/etcd.service
Luego, copie el siguiente bloque de código en el archivo 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:
- 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:
- ssh root@etcd1_public_ip
A continuación, ejecute systemctl status etcd
para consultar a systemd el estado del servicio etcd
:
- systemctl status 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:
- exit
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.
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:
- 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:
- mkdir templates
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:
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:
- 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:
- nano files/etcd.service
Actualice el archivo files/etcd.service
añadiendo el indicador --config-file
resaltado en lo siguiente:
[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.
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:
- 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:
- ssh root@etcd1_public_ip
A continuación, ejecute systemctl status etcd
para consultar el estado del servicio etcd
:
- systemctl status 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).
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:
- etcdctl put foo "bar"
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:
OutputOK
Luego, podemos recuperar esta entrada utilizando el subcomando get
, que tiene la sintaxis etcdctl get key
:
- etcdctl get foo
Obtendrá este resultado, que muestra la clave en la primera línea y el valor que insertó anteriormente en la segunda:
Outputfoo
bar
Podemos eliminar la entrada utilizando el subcomando del
, que tiene la sintaxis etcdctl del key
:
- etcdctl del foo
Obtendrá el siguiente resultado, que indica la cantidad de entradas eliminadas:
Output1
Ahora, vamos a ejecutar el subcomando get
una vez más para intentar recuperar un par clave-valor eliminado:
- etcdctl get foo
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:
- exit
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.
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:
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:
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:
127.0.0.1
178.128.169.51
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
:
- 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:
- 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:
- ssh root@etcd1_public_ip
A continuación, ejecute etcdctl endpoint health --cluster
:
- etcdctl endpoint health --cluster
Esto enumerará el estado de cada miembro del clúster:
Outputhttp://etcd2_private_ip:2379 is healthy: successfully committed proposal: took = 2.517267ms
http://etcd1_private_ip:2379 is healthy: successfully committed proposal: took = 2.153612ms
http://etcd3_private_ip:2379 is healthy: successfully committed proposal: took = 2.639277ms
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
:
- etcdctl put foo "bar"
A continuación, utilice una terminal nueva para ingresar mediante SSH en un nodo miembro diferente:
- ssh root@etcd2_public_ip
Luego, intente recuperar la misma entrada usando la clave:
- etcdctl get foo
Podrá recuperar la entrada, lo que demuestra que el clúster está funcionando:
Outputfoo
bar
Por último, salga de todos los nodos administrados y regrese a su equipo local:
- exit
- exit
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.
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:
- ssh root@etcd1_public_ip
Luego, ejecute el siguiente comando:
- ip -f inet addr show eth1
Verá un resultado similar a las siguientes líneas:
Output3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
inet 10.131.255.176/16 brd 10.131.255.255 scope global eth1
valid_lft forever preferred_lft forever
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:
Output10.131.255.176
Una vez que esté satisfecho con el funcionamiento del comando anterior, salga del nodo administrado:
- exit
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:
...
- 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).
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:
...
- 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:
...
- 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
:
...
- 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:
- ls artifacts
Encontrará las claves privadas y las CSR de cada miembro de etcd:
Outputetcd1.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).
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:
- 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:
- 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:
- 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/
:
- ls artifacts/
Ahora, encontrará el certificado de CA generado recientemente (ca.crt
):
Outputca.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.
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"
:
- 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/
:
- ls artifacts/
Encontrará la clave privada, la CSR y el certificado de cada miembro de etcd y la CA:
Outputca.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.
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/
:
- 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:
- 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.
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:
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:
- ssh root@etcd1_public_ip
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:
Outputhttps://etcd3_private_ip:2379 is healthy: successfully committed proposal: took = 19.237262ms
https://etcd1_private_ip:2379 is healthy: successfully committed proposal: took = 4.769088ms
https://etcd2_private_ip:2379 is healthy: successfully committed proposal: took = 5.953599ms
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:
- ssh root@etcd2_public_ip
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:
Outputfoo
bar
Puede hacer lo mismo en el tercer nodo para garantizar que los tres miembros estén en funcionamiento.
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í:
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!