Hashicorp Vault en alta disponibilidad con Consul

abr 19, 2021·
Julio Batista Silva
Julio Batista Silva
· 7 min de lectura
blog

Consul

HashiCorp Consul nació en 2014 como un DNS-SD (DNS based Service Discovery) para resolver el problema de la comunicación entre microservicios, pero también tiene otras funciones, como health checking y almacenamiento de clave-valor (KV Store).

Cada nodo tiene instalado un agente de monitorización que se encarga de enviar de forma segura la información del nodo a uno o varios servidores.

Los servidores mantienen un catálogo con la información de los nodos: qué servicios hay disponibles en cada uno y su estado.
Para alta disponibilidad se recomienda tener de 3 a 5 servidores. Uno de ellos será elegido líder.

Configuración manual

Para entender la arquitectura en detalle conviene hacer la instalación paso a paso. Más adelante se verá cómo hacer la configuración con Docker-compose.

El proceso de instalación de Consul y de Vault está bien detallado en la documentación oficial.

En resumen, el paso a paso es este:

  1. Arranca 5 máquinas con Linux

    Pueden estar en la nube o en local. Tres serán para el servidor Consul y dos para Vault.
    En la versión gratuita solo un nodo de Vault está activo; los demás quedan en standby redirigiendo las peticiones al nodo activo. Si el nodo activo deja de estar disponible, se elige otro como nodo activo.
    Consul también se instalará en las máquinas de Vault, pero en modo cliente.

  2. Define los hostnames

    (vault-01)# echo "vault-01" > /etc/hostname
    (vault-02)# echo "vault-02" > /etc/hostname
    (consul-01)# echo "consul-01" > /etc/hostname
    (consul-02)# echo "consul-02" > /etc/hostname
    (consul-03)# echo "consul-03" > /etc/hostname
    
  3. Instala Consul en todas las máquinas

    La web oficial tiene instrucciones de instalación para las distribuciones más populares.

    En Arch Linux sería así:

    # pacman -S consul
    
  4. Edita el archivo de configuración de los servidores

    # cat /etc/consul.d/consul_s1.json
    
    {
      "server": true,
      "node_name": "$NODE_NAME",
      "datacenter": "dc1",
      "data_dir": "$CONSUL_DATA_PATH",
      "bind_addr": "0.0.0.0",
      "client_addr": "0.0.0.0",
      "advertise_addr": "$ADVERTISE_ADDR",
      "bootstrap_expect": 3,
      "retry_join": ["$JOIN1", "$JOIN2", "$JOIN3"],
      "ui": true,
      "log_level": "DEBUG",
      "enable_syslog": true,
      "acl_enforce_version_8": false
    }
    
  5. Edita el archivo de configuración de los clientes

    # cat /etc/consul.d/consul_c1.json
    
    {
      "server": false,
      "datacenter": "dc1",
      "node_name": "$NODE_NAME",
      "data_dir": "$CONSUL_DATA_PATH",
      "bind_addr": "$BIND_ADDR",
      "client_addr": "127.0.0.1",
      "retry_join": ["$JOIN1", "$JOIN2", "$JOIN3"],
      "log_level": "DEBUG",
      "enable_syslog": true,
      "acl_enforce_version_8": false
    }
    
  6. Edita el systemd unit file de los servidores

    # cat /etc/systemd/system/consul.service
    ### BEGIN INIT INFO
    # Provides:          consul
    # Required-Start:    $local_fs $remote_fs
    # Required-Stop:     $local_fs $remote_fs
    # Default-Start:     2 3 4 5
    # Default-Stop:      0 1 6
    # Short-Description: Consul agent
    # Description:       Consul service discovery framework
    ### END INIT INFO
    
    [Unit]
    Description=Consul server agent
    Requires=network-online.target
    After=network-online.target
    
    [Service]
    User=consul
    Group=consul
    PIDFile=/var/run/consul/consul.pid
    PermissionsStartOnly=true
    ExecStartPre=-/bin/mkdir -p /var/run/consul
    ExecStartPre=/bin/chown -R consul:consul /var/run/consul
    ExecStart=/usr/local/bin/consul agent \
        -config-file=/usr/local/etc/consul/consul_s1.json \
        -pid-file=/var/run/consul/consul.pid
    ExecReload=/bin/kill -HUP $MAINPID
    KillMode=process
    KillSignal=SIGTERM
    Restart=on-failure
    RestartSec=42s
    
    [Install]
    WantedBy=multi-user.target
    
  7. Edita el systemd unit file de los clientes

    ### BEGIN INIT INFO
    # Provides:          consul
    # Required-Start:    $local_fs $remote_fs
    # Required-Stop:     $local_fs $remote_fs
    # Default-Start:     2 3 4 5
    # Default-Stop:      0 1 6
    # Short-Description: Consul agent
    # Description:       Consul service discovery framework
    ### END INIT INFO
    
    [Unit]
    Description=Consul client agent
    Requires=network-online.target
    After=network-online.target
    
    [Service]
    User=consul
    Group=consul
    PIDFile=/var/run/consul/consul.pid
    PermissionsStartOnly=true
    ExecStartPre=-/bin/mkdir -p /var/run/consul
    ExecStartPre=/bin/chown -R consul:consul /var/run/consul
    ExecStart=/usr/local/bin/consul agent \
        -config-file=/usr/local/etc/consul/consul_c1.json \
        -pid-file=/var/run/consul/consul.pid
    ExecReload=/bin/kill -HUP $MAINPID
    KillMode=process
    KillSignal=SIGTERM
    Restart=on-failure
    RestartSec=42s
    
    [Install]
    WantedBy=multi-user.target
    
  8. Comprueba que todos los servicios están funcionando

    # systemctl daemon-reload
    # systemctl start consul
    # systemctl status consul
    
    $ consul members
    $ consul operator raft list-peers
    

    Uno de los servidores debe figurar como líder.

  9. Instala Vault en las dos VM

    # pacman -S vault
    
  10. Edita el archivo de configuración de Vault

    # cat /etc/vault/vault_s1.hcl
    
    listener "tcp" {
      address          = "127.0.0.1:8200"
      cluster_address  = "127.0.0.1:8201"
      tls_disable      = "true"
    }
    
    storage "consul" {
      address = "127.0.0.1:8500"
      path    = "vault/"
    }
    
    api_addr =  "$API_ADDR"
    cluster_addr = "$CLUSTER_ADDR"
    
  11. Edita el systemd unit file de los Vault

    # cat /etc/systemd/system/vault.service
    
    ### BEGIN INIT INFO
    # Provides:          consul
    # Required-Start:    $local_fs $remote_fs
    # Required-Stop:     $local_fs $remote_fs
    # Default-Start:     2 3 4 5
    # Default-Stop:      0 1 6
    # Short-Description: Consul agent
    # Description:       Consul service discovery framework
    ### END INIT INFO
    
    [Unit]
    Description=Consul client agent
    Requires=network-online.target
    After=network-online.target
    
    [Service]
    User=consul
    Group=consul
    PIDFile=/var/run/consul/consul.pid
    PermissionsStartOnly=true
    ExecStartPre=-/bin/mkdir -p /var/run/consul
    ExecStartPre=/bin/chown -R consul:consul /var/run/consul
    ExecStart=/usr/local/bin/consul agent \
        -config-file=/usr/local/etc/consul/consul_c1.json \
        -pid-file=/var/run/consul/consul.pid
    ExecReload=/bin/kill -HUP $MAINPID
    KillMode=process
    KillSignal=SIGTERM
    Restart=on-failure
    RestartSec=42s
    
    [Install]
    WantedBy=multi-user.target
    
  12. Comprueba que todos los servicios están funcionando

    # systemctl daemon-reload
    # systemctl start vault
    # systemctl status vault
    
  13. Inicializa Vault

    $ vault operator init
    

    Guarda las claves y el root token en un sitio seguro.

  14. Unseal

    $ vault operator unseal <unseal_key_1>
    $ vault operator unseal <unseal_key_2>
    $ vault operator unseal <unseal_key_3>
    

    A partir de aquí podemos seguir como se detalla en otro artículo.

Configuración con docker-compose

$ consul keygen
LvqkL8MCukmz4/Z6n7v/38XSVh9jjbNoDXx5dsFd6Nw=
$ consul tls ca create
==> Saved consul-agent-ca.pem
==> Saved consul-agent-ca-key.pem
$ consul tls cert create -server -dc dc1    ### -additional-dnsname=consul.server1
==> Saved dc1-server-consul-0.pem
==> Saved dc1-server-consul-0-key.pem
$ consul tls cert create -server -dc dc1
==> Saved dc1-server-consul-1.pem
==> Saved dc1-server-consul-1-key.pem
$ consul tls cert create -server -dc dc1
==> Saved dc1-server-consul-2.pem
==> Saved dc1-server-consul-2-key.pem
$ consul tls cert create -client -dc dc1 -additional-dnsname=consul-c1
==> Using consul-agent-ca.pem and consul-agent-ca-key.pem
==> Saved dc1-client-consul-0.pem
==> Saved dc1-client-consul-0-key.pem
$ consul tls cert create -client -dc dc1 -additional-dnsname=consul-c2
==> Using consul-agent-ca.pem and consul-agent-ca-key.pem
==> Saved dc1-client-consul-1.pem
==> Saved dc1-client-consul-1-key.pem
$ mv dc1-server-consul-0.pem Pasta/consul/config/server1/certs/server.pem
$ mv dc1-server-consul-1.pem Pasta/consul/config/server2/certs/server.pem
$ mv dc1-server-consul-2.pem Pasta/consul/config/server3/certs/server.pem
$ cp dc1-client-consul-0.pem Pasta/consul/config/client1/certs/client.pem
$ cp dc1-client-consul-1.pem Pasta/consul/config/client2/certs/client.pem
$ mv dc1-client-consul-0.pem Pasta/vault/config/server1/consul_tls/certs/client.pem
$ mv dc1-client-consul-1.pem Pasta/vault/config/server2/consul_tls/certs/client.pem
$ mv dc1-server-consul-0-key.pem Pasta/consul/config/server1/private/server-key.pem
$ mv dc1-server-consul-1-key.pem Pasta/consul/config/server2/private/server-key.pem
$ mv dc1-server-consul-2-key.pem Pasta/consul/config/server3/private/server-key.pem
$ cp dc1-client-consul-0-key.pem Pasta/consul/config/client1/private/client-key.pem
$ cp dc1-client-consul-1-key.pem Pasta/consul/config/client2/private/client-key.pem
$ mv dc1-client-consul-0-key.pem Pasta/vault/config/server1/consul_tls/private/client-key.pem
$ mv dc1-client-consul-1-key.pem Pasta/vault/config/server2/consul_tls/private/client-key.pem
$ cp consul-agent-ca.pem Pasta/consul/config/server1/certs/root.pem
$ cp consul-agent-ca.pem Pasta/consul/config/server2/certs/root.pem
$ cp consul-agent-ca.pem Pasta/consul/config/server3/certs/root.pem
$ cp consul-agent-ca.pem Pasta/consul/config/client1/certs/root.pem
$ cp consul-agent-ca.pem Pasta/consul/config/client2/certs/root.pem
$ cp consul-agent-ca.pem Pasta/vault/config/server1/consul_tls/certs/root.pem
$ cp consul-agent-ca.pem Pasta/vault/config/server2/consul_tls/certs/root.pem

Certificado autofirmado para Vault:

$ cat selfsign_s1.cfr
[req]
distinguished_name = req_distinguished_name
x509_extensions = v3_req
prompt = no

[req_distinguished_name]
C = BR
ST = state
L =  city
O = company
CN = *

[v3_req]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
basicConstraints = CA:TRUE
subjectAltName = @alt_names

[alt_names]
DNS.1 = localhost
DNS.2 = s1.vault
DNS.3 = host.docker.internal
IP.1 = 172.16.238.151
IP.3 = 127.0.0.1
$ cat selfsign_s2.cfr
[req]
distinguished_name = req_distinguished_name
x509_extensions = v3_req
prompt = no

[req_distinguished_name]
C = BR
ST = state
L =  city
O = company
CN = *

[v3_req]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
basicConstraints = CA:TRUE
subjectAltName = @alt_names

[alt_names]
DNS.1 = localhost
DNS.2 = s2.vault
DNS.3 = host.docker.internal
IP.1 = 172.16.238.152
IP.2 = 127.0.0.1
$ openssl req -x509 -batch -nodes -newkey rsa:2048 -keyout server-key.pem -out server.pem -config selfsign_s1.cfr -days 9999
$ mv server.pem Pasta/vault/config/server1/tls/certs/server.pem
$ mv server-key.pem Pasta/vault/config/server1/tls/private/server-key.pem
$ openssl req -x509 -batch -nodes -newkey rsa:2048 -keyout server-key.pem -out server.pem -config selfsign_s2.cfr -days 9999
$ mv server.pem Pasta/vault/config/server2/tls/certs/server.pem
$ mv server-key.pem Pasta/vault/config/server2/tls/private/server-key.pem
$ cat docker-compose.yaml
version: "3.8"

services:
  consul_s1:
    container_name: consul.server1
    image: consul:latest
    command: ["agent", "-config-file=/consul/config/server_agent.json"]
    volumes:
      - ./consul/config/server1:/consul/config
      - ./consul/data/data_s1:/consul/data
    restart: unless-stopped
    ports:
      - "8090:8080"
      - "8300:8300"
      - "8500:8500"
      - "8600:8600/udp"
    networks:
      internal_net:
        ipv4_address: 172.16.238.53

  consul_s2:
    container_name: consul.server2
    image: consul:latest
    command: ["agent", "-config-file=/consul/config/server_agent.json"]
    volumes:
      - ./consul/config/server2:/consul/config
      - ./consul/data/data_s2:/consul/data
    restart: unless-stopped
    ports:
      - "8091:8080"
      - "8310:8300"
      - "8510:8500"
      - "8610:8600/udp"
    networks:
      internal_net:
        ipv4_address: 172.16.238.54

  consul_s3:
    container_name: consul.server3
    image: consul:latest
    command: ["agent", "-config-file=/consul/config/server_agent.json"]
    volumes:
      - ./consul/config/server3:/consul/config
      - ./consul/data/data_s3:/consul/data
    restart: unless-stopped
    ports:
      - "8092:8080"
      - "8320:8300"
      - "8520:8500"
      - "8620:8600/udp"
    networks:
      internal_net:
        ipv4_address: 172.16.238.55

  consul_c1:
    container_name: consul.client1
    image: consul:latest
    command: ["agent", "-config-file=/consul/config/client_agent.json"]
    volumes:
      - ./consul/config/client1:/consul/config
      - ./consul/data/data_c1:/consul/data
    restart: unless-stopped
    ports:
      - "8093:8080"
      - "8330:8300"
      - "8530:8500"
      - "8630:8600/udp"
    depends_on:
      - consul_s1
    networks:
      internal_net:
        ipv4_address: 172.16.238.201
        aliases:
          - consul-c1

  consul_c2:
    container_name: consul.client2
    image: consul:latest
    command: ["agent", "-config-file=/consul/config/client_agent.json"]
    volumes:
      - ./consul/config/client2:/consul/config
      - ./consul/data/data_c2:/consul/data
    restart: unless-stopped
    ports:
      - "8094:8080"
      - "8340:8300"
      - "8540:8500"
      - "8640:8600/udp"
    depends_on:
      - consul_s2
    networks:
      internal_net:
        ipv4_address: 172.16.238.202
        aliases:
          - consul-c2

  vault_s1:
    container_name: vault.server1
    image: vault:latest
    ports:
      - "9200:8200"
    expose:
      - "8500"
    volumes:
      - ./vault/config/server1:/vault/config
      - ./ca-certificates.crt:/etc/ssl/certs/ca-certificates.crt
      - ./vault/logs_s1:/vault/logs
    cap_add:
      - IPC_LOCK
    command: ["server", "-log-level=info"]
    environment:
      - VAULT_LOCAL_CONFIG={"ui":"true", "backend":{"consul":{"address":"consul-c1:8080",
        "path":"vault", "scheme":"https", "tls_ca_file":"/vault/config/consul_tls/certs/root.pem",
        "tls_cert_file":"/vault/config/consul_tls/certs/client.pem",
        "tls_key_file":"/vault/config/consul_tls/private/client-key.pem", "tls_skip_verify":0}},
        "listener":{"tcp":{"address":"0.0.0.0:8200", "cluster_address":"0.0.0.0:8201",
        "tls_disable":0, "tls_cert_file":"/vault/config/tls/certs/server.pem",
        "tls_key_file":"/vault/config/tls/private/server-key.pem", "tls_min_version":"tls12"}},
        "api_addr":"http://s1.vault:8200", "cluster_addr":"https://s1.vault:8201"}
      - VAULT_SKIP_VERIFY=false
    depends_on:
      - consul_c1
    extra_hosts:
      - "host.docker.internal:host-gateway"
    networks:
      internal_net:
        ipv4_address: 172.16.238.151
        aliases:
          - s1.vault
    restart: unless-stopped

  vault_s2:
    container_name: vault.server2
    image: vault:latest
    ports:
      - "9210:8200"
    expose:
      - "8500"
    volumes:
      - ./vault/config/server2:/vault/config
      - ./ca-certificates.crt:/etc/ssl/certs/ca-certificates.crt
      - ./vault/logs_s2:/vault/logs
    cap_add:
      - IPC_LOCK
    command: ["server", "-log-level=info"]
    environment:
      - VAULT_LOCAL_CONFIG={"ui":"true", "backend":{"consul":{"address":"consul-c2:8080",
        "path":"vault", "scheme":"https", "tls_ca_file":"/vault/config/consul_tls/certs/root.pem",
        "tls_cert_file":"/vault/config/consul_tls/certs/client.pem",
        "tls_key_file":"/vault/config/consul_tls/private/client-key.pem", "tls_skip_verify":0}},
        "listener":{"tcp":{"address":"0.0.0.0:8200", "cluster_address":"0.0.0.0:8201",
        "tls_disable":0, "tls_cert_file":"/vault/config/tls/certs/server.pem",
        "tls_key_file":"/vault/config/tls/private/server-key.pem", "tls_min_version":"tls12"}},
        "api_addr":"http://s2.vault:8200", "cluster_addr":"https://s2.vault:8201"}
      - VAULT_SKIP_VERIFY=false
    depends_on:
      - consul_c2
    extra_hosts:
      - "host.docker.internal:host-gateway"
    networks:
      internal_net:
        ipv4_address: 172.16.238.152
        aliases:
          - s2.vault
    restart: unless-stopped

networks:
  internal_net:
    driver: bridge
    ipam:
      driver: default
      config:
        - subnet: 172.16.238.0/24
$ docker-compose -f docker-compose.yaml up -d --remove-orphans

Direcciones de las interfaces web:


Julio Batista Silva
Autores
Senior Cloud Developer

Soy un ingeniero informático brasileño radicado en Alemania, apasionado por la tecnología, la ciencia, la fotografía y los idiomas.

Llevo programando cerca de dos décadas, explorando desde apps móviles y desarrollo web hasta aprendizaje automático. Hoy me enfoco en SRE en la nube e ingeniería de datos.

Soy voluntario en las comunidades de open source y Python, ayudando a organizar PyCon DE y PyData Berlin, dando mentorías y contribuyendo con código y traducciones.

En mi blog comparto consejos de Linux, guías de configuración y notas personales que escribí como referencia futura. Espero que también sean útiles para otras personas. El contenido está disponible en varios idiomas.

Visita mi galería para ver algunas de mis fotografías.

Fuera del teclado, me encontrarás en conciertos, tocando el clarinete, en bicicleta, buceando o explorando nuevos lugares, culturas y cocinas.

¡Siempre feliz de conectar! 🙂

comments powered by Disqus