HashiCorp Vault in Hochverfügbarkeit mit Consul

Apr. 19, 2021·
Julio Batista Silva
Julio Batista Silva
· 7 Min Lesezeit

Consul

HashiCorp Consul wurde 2014 als DNS-SD (DNS based Service Discovery) entwickelt, um das Problem der Kommunikation zwischen Microservices zu lösen. Darüber hinaus bietet es Funktionen wie Health Checks und einen Key-Value-Store (KV Store).

Auf jedem Knoten läuft ein Agent, der dafür verantwortlich ist, Informationen des Knotens sicher an einen oder mehrere Server zu senden.

Die Server pflegen einen Katalog mit den Informationen der Knoten, z. B. welche Dienste auf welchem Knoten verfügbar sind und deren Status.
Für Hochverfügbarkeit empfiehlt es sich, 3 bis 5 Server zu betreiben. Einer davon wird als Leader gewählt.

Manuelle Konfiguration

Um die Architektur im Detail zu verstehen, ist es sinnvoll, die Installation Schritt für Schritt durchzuführen. Weiter unten zeige ich, wie man die Konfigurationen mit Docker Compose umsetzt.

Der Installationsprozess für Consul und Vault ist in der offiziellen Dokumentation gut beschrieben.

Kurz zusammengefasst sind die Schritte wie folgt:

  1. Starte 5 Linux-Maschinen

    Das kann in einer Cloud oder lokal sein. Drei werden für die Consul-Server verwendet und zwei für Vault.
    In der kostenlosen Version ist nur ein Vault-Knoten aktiv; die anderen Knoten sind im Standby und leiten Anfragen an den aktiven Knoten weiter. Falls der aktive Knoten ausfällt, wird ein anderer Knoten zum aktiven gewählt.
    Consul wird auf den Vault-Maschinen ebenfalls installiert, jedoch im Client-Modus.

  2. Setze die 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. Installiere Consul auf allen Maschinen

    Die offizielle Seite enthält Installationsanleitungen für die gängigsten Distributionen.

    Unter Arch Linux wäre es beispielsweise:

    # pacman -S consul
    
  4. Bearbeite die Konfigurationsdatei der Server

    # 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. Bearbeite die Konfigurationsdatei der Clients

    # 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. Bearbeite die systemd-Unit-Datei der Server

    # 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. Bearbeite die systemd-Unit-Datei der Clients

    ### 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. Prüfe, ob alle Dienste laufen

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

    Einer der Server sollte als Leader angezeigt werden.

  9. Installiere Vault auf den beiden VMs

    # pacman -S vault
    
  10. Bearbeite die Konfigurationsdatei von 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. Bearbeite die systemd-Unit-Datei der Vaults

    # 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. Prüfe, ob alle Dienste laufen

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

    $ vault operator init
    

    Speichere die Schlüssel und das Root-Token an einem sicheren Ort.

  14. Unseal

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

    Ab hier kannst du wie im anderen Artikel beschrieben fortfahren.

Konfiguration mit 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

Selbstsigniertes Zertifikat für 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

UI-Adressen:


Julio Batista Silva
Autoren
Senior Cloud-Entwickler
comments powered by Disqus