Arquitectura de referencia de Consul, Vault y Nomad para un centro de datos

Escrito por el .
gnu-linux planeta-codigo
Enlace permanente Comentarios

HashiCorp proporciona una buena documentación de sus productos con tutoriales, documentación de las herramientas y vídeos de formación en su canal de Youtube. Aunque en la documentación está todo explicado para poner en práctica una arquitectura para un caso más real y con cierto grado de producción requiere leer múltiples artículos para aplicar y aglutinar todo lo descrito. En este artículo muestro una arquitectura de referencia de varios productos de HashiCorp como Consul, Vault y Nomad que forman un centro de datos o entorno de ejecución para servicios en un ejemplo aprovisionado con Vagrant en máquinas virtuales de VirtualBox.

HashiCorp Consul

HashiCorp Vault

HashiCorp Nomad

La empresa HashiCorp cataloga las necesidades de las aplicaciones basadas en la computación en la nube u orientadas a microservicios en cuatro aspectos que necesitan: aprovisionamiento, registro y conexión, seguridad y ejecución. HashiCorp en base a estos cuatro aspectos de las aplicaciones ha desarrollado para cada una de ellas una aplicación específica integrable con las otras pero también con la posibilidad de usar cada una de forma individual e independiente que permiten disponer de un centro de datos y cubre varias de las necesidades computacionales de una empresa en entornos modernos basados en nube que son dinámicos y requieren elasticidad.

En muchas organizaciones los desarrolladores solo se encargan del desarrollo de la aplicación o servicio, del despliegue y administración se encargan los administradores de sistemas. Los centros de datos se operan en base a peticiones entre los desarrolladores y los administradores de sistemas, este modelo organización es lento y propenso a ineficiencias, para evitarlo hay que tratar que los desarrolladores sean lo más autosuficientes posible para operar el entorno de ejecución de sus servicios haciendo que estos no solo desarrollen sus aplicaciones sino que también tengan capacidad de desplegarlas y monitorizarlas.

En este artículo muestro un ejemplo usando tres de estos mismos productos que se usarían en un entorno de producción Consul para el registro de servicios y conexión de forma segura, Vault para seguridad y servicios de cifrado y Nomad para la ejecución de servicios con contenedores Docker. Para el aprovisionamiento y ejecutar el ejemplo en la propia máquina el ejemplo usa Vagrant que permite crear y aprovisionar máquinas virtuales sobre VirtualBox, en un entorno basado en la nube se usaría Terraform. Consul, Vault y Nomad forman lo que sería un centro de datos de un entorno de ejecución para aplicaciones y servicios.

HashiQube proporciona un entorno para hacer pruebas de desarrollo con todas las herramientas de HashiCorp, pero no es utilizable en un entorno de producción. Para construir un entorno con grado de producción hay que leer mucha de la buena documentación que ofrece HashiCorp de sus productos. Los productos de HashiCorp tienen una buena documentación en formato guía y tutoriales divididos en secciones individuales sobre un tema en concreto.

Diferencias entre entornos estáticos anteriores y dinámicos nuevos

Diferencias entre entornos estáticos anteriores y dinámicos nuevos

Este artículo hace uso, está relacionado y se complementa con otros artículos que muestran varias de las funcionalidades individuales utilizadas en el ejemplo.

Servicios de HashiCorp

Productos de HashiCorp Productos de HashiCorp Productos de HashiCorp

Productos de HashiCorp

Antes y después con Consul Antes y después con Vault Antes y después con Nomad

Antes y después con Terraform Ecosistema con Nomad

Ecosistema con Nomad

Consul

En una aplicación basada en servicios a lo largo del tiempo estos se añaden y se eliminan, varía el número de instancias, cambian de ubicación, pueden dejar de estar accesibles de forma inesperada y los servicios necesitan comunicarse de forma segura.

La herramienta Consul proporciona proporcionan funcionalidades para todas estas necesidades con un registro de registro y descubrimiento que mantiene el catálogo de servicios e instancias con su ubicación, proporciona comprobaciones de estado o health checks para conocer en todo momento que las instancias están funcionado correctamente, proporciona comunicaciones seguras entre los servicios con Consul Connect y permite o deniega la comunicación con intenciones y basándose en la identidad de los servicios.

El catálogo de servicios es accesible mediante una API REST, también ofrece su catálogo de servicios a través de una interfaz DNS ya que incorpora la funcionalidad de servidor DNS. Consul además proporciona un almacén de clave/valor que los servicios pueden utilizar como valores de configuración que cambian de forma dinámica sin necesidad de reiniciar las instancias del servicio para que los nuevos valores surtan efecto.

Vault

Vault proporciona servicios de seguridad. En vez de implementar las necesidades de seguridad en cada aplicación con el lenguaje de programación en el que esté desarrollado Vault permite delegar y centralizar en él los requerimientos y políticas de seguridad.

Proporciona funcionalidades de cifrado y descifrado, cifrado y descifrado conservando el formato y enmascaramiento basado en políticas. También permite generar certificados con un tiempo de expiración pequeño y generación de credenciales dinámicas, esto es, en vez de generar unas credenciales de conexión para cada aplicación a una base de datos la aplicación puede solicitar a Vault unas credenciales de conexión para la base de datos que únicamente son válidas mientras la aplicación necesita la conexión. Las credenciales dinámicas permiten aumentar la seguridad.

Nomad

Los servicios necesitan ejecutarse y crear instancias para proporcionar sus funcionalidades, Nomad hace las funciones de orquestador de servicios. Se encarga de planificar el nodo de entre los disponibles en los que se ejecutara cada instancia del servicio, de mantener el número de instancias indicadas en la descripción de cada servicio y de aplicar la estrategia de despliegue según la política definida para cada servicio, como blue/green o canary.

Nomad se integra con Consul y permite que al iniciar la instancia de un servicio se registre en Consul y configurar un health check para que Consul monitorice el estado de la instancia del servicio.

Vagrant y Terraform

Para automatizar la creación de los entornos, crear los entornos como código y que estos queden descritos en archivos se utilizan herramientas de aprovisionamiento. HashiCorp proporciona dos, una es Vagrant para crear máquinas virtuales en local con VirtualBox para un entorno de desarrollo. Para un entorno de producción se usará un proveedor de computación en la nube como Amazon Web Services, Google Cloud Platform, Microsoft Azure u otros como Digital Ocean o Linode.

Cada uno de estos entornos de computación tiene sus peculiaridades, Terraform permite describir las necesidades de sistemas en cuanto a infraestructura del entorno de ejecución de forma independiente a cada uno de estos proveedores, automatizar el aprovisionamiento y definir la infraestructura como código lo que permite guardar en un repositorio de control de versiones los cambios que se realizan a la infraestructura.

Packer

La herramienta Packer permite construir las imágenes base para los sistemas en la nube y de los contenedores. Es agnóstica de los diferentes sistemas en la nube permitiendo construir imágenes para cada una de ellas, esto permite no estar encadenado a un proveedor determinado.

Comparación con Kubernetes

Kubernetes es otro sistema con la funcionalidad de orquestador de contenedores desarrollado originalmente por Google y al que ahora contribuyen numerosas empresas como Red Hat. Proporciona todas las características que necesitan las aplicaciones basadas en contenedores incluyendo gestión del clúster, planificación, descubrimiento de servicios, monitorización, gestión de secretos y otros más.

HashiCorp proporciona la mayoría de las funcionalidades de Kubernetes en cada una de las diferentes herramientas que tiene siguiendo la filosofía Unix de herramientas con un ámbito reducido y agregables, por ejemplo, Consul proporciona descubrimiento de servicios, Vault seguridad y Nomad está diseñado específicamente para proporcionar gestión del cluster y planificación.

Kubernetes se centra en Docker mientras que Nomad soporta aplicaciones de propósito general como pueden ser comandos del sistema, aplicaciones Java, Docker o Podman. Kubernetes está diseñado como una colección de más de media docena de servicios que interoperan para proporcionar la funcionalidad completa. Soporta alta disponibilidad pero operacionalmente es complejo de configurar.

La arquitectura de las herramientas de HashiCorp es más simple, cada una de las herramientas es un único binario que sirve tanto para actuar como servidor como cliente y que se pueden utilizar sin requerir al resto, tanto es así que Consul se puede utilizar y algunos lo utilizan junto a Kubernetes. Los servidores son distribuidos, con alta disponibilidad y operacionalmente más fáciles. Los servidores soportan configuraciones multicentro de datos y multiregión llegando a tamaños que superan los 10K nodos en entornos de producción.

Arquitectura de referencia

En una arquitectura de referencia para proporcionar alta disponibilidad y tolerancia a fallos es necesario crear varias instancias de cada servicio. Las diferentes instancias de Consul, Vault y Nomad forman un clúster algunas funcionando en modo servidor y otras funcionando en modo cliente. Para los servidores de Consul, Vault y Nomad se recomiendan al menos 3 instancias de cada uno de ellos, en total 9 instancias. En cada instancia de Vault y Nomad se crea un agente de Consul que actúa en modo cliente que atiende a las peticiones de forma local y realiza las comunicaciones con los servidores de Consul. De Nomad habrá varias instancias adicionales más actuando en modo cliente que serán donde se ejecuten las instancias de los servicios de aplicación como un un contenedor Docker con Nginx, una base de datos PostgresQL o una aplicación Java con Spring Boot.

En total un centro de datos para un entorno puede estar compuesto como mínimo entre 9, 15 o 21 para el clúster con 3, 5, 7 servidores de Consul, Vault y Nomad según las necesidades de computación del centro de datos. De Nomad en modo cliente donde se ejecutan los servicios el número de instancias variará según las necesidades de computación y número de servicios que necesitan las aplicaciones, también puede ser un número significativo de entre 10 a 100 o más instancias. El número de máquinas e instancias es elevado por eso hay que tratarlas como ganado y no como animales de compañía que requieran administrarlas de forma individual.

Estos son los esquemas de las arquitecturas de referencia de Consul, Vault y Nomad para configuraciones de producción formado por varios servidores, clientes y regiones o centros de datos.

Arquitectura de referencia de Consul Arquitectura de referencia de Vault Arquitectura de referencia de Nomad

Arquitecturas de referencia de Consul, Vault y Nomad

Precio

Dependiendo del tamaño de las máquinas de cómputo elegidas para cada instancia el coste de la infraestructura varia. En AWS una instancia t3a.medium, t3a.large y t3a.xlarge con 4, 8 y 16 GiB tienen un coste aproximado de $125, $250 y $400 al año si se paga por adelantado una reserva de 3 años. Multiplicado esto por el número de instancias el coste no es despreciable pero asequible para una empresa que llega a necesitar 100 instancias para sus servicios. La ventaja de la computación en la nube y estas herramientas de HashiCorp es que permiten adaptar el entorno del centro de datos a las necesidades suficientes para cada momento, si se necesitan más máquinas e instancias se añaden si dejan de necesitar se eliminan, esta la elasticidad ofrecida por la computación en la nube que los costes sean únicamente los necesarios.

Es difícil calcular el coste exacto de un centro de datos ya que tendrá una mezcla de diferentes tipos de instancias, instancias reservadas y bajo demanda aprovechando la elasticidad de la computación en la nube, también hay que tener en cuanta que una organización necesitará entornos de pruebas aún siendo de menor tamaño y número que el de producción. El mayor coste lo determina el número de nodos de Nomad, para 50 nodos estarán sobre los $20K y para 1000 los $400K anuales teniendo en cuenta que una empresa que necesita esas cantidades de nodos ya son muy grandes seguramente sean del tamaño multinacionales con facturaciones de varios cientos o miles de millones.

Coste anual por instancia reservada durante 3 años (aproximado)
Tipo instancia Cores Memoria (GiB) Precio/año (reserva 3 años)
t3a.medium 2 4 $125
t3a.large 2 8 $250
t3a.xlarge 4 16 $400
Coste anual del clúster de servidores Consul, Nomad y Vault con diferente número de instancias (aproximado)
Tipo Número de instancias Precio/año (reserva 3 años)
t3a.xlarge 3 (1x3) $1200
t3a.xlarge 9 (3x3) $3600
t3a.xlarge 15 (5x3) $6000
Coste anual nodos Nomad con diferente número de instancias (aproximado)
Tipo 10 50 100 300 1000
t3a.medium $1.25K $6.25K $12.5K $37.5K $125K
t3a.large $2.5K $12.5K $25K $75K $250K
t3a.xlarge $4K $20K $40K $120K $400K

Ejemplo de la arquitectura de referencia con Vagrant

Para ejecutar la arquitectura de referencia en vez de crear 3 o 5 instancias de Consul, Vault y Nomad en el siguiente ejemplo se utiliza solo 1 instancia de cada uno de ellos. Para ejecutarlos en local se usa Vagrant para crear y aprovisionar las máquinas virtuales de VirtualBox.

Vault se comunica con Consul para utilizar el almacén de clave-valor o KV en el que Vault almacena los datos cifrados, Nomad se comunica con Vault para proporcionar a las aplicaciones algunos requerimientos de seguridad y con Consul para obtener datos de su KV y proporcionarlos a las instancias de los servicios, también para registrar en Consul las instancias de los servicios y si lo utilizan proporcionales sus necesidades de comunicación con Consul Connect.

Máquinas virtuales en VirtuaBox aprovisionadas con Vagrant

Máquinas virtuales en VirtuaBox aprovisionadas con Vagrant

La comunicación entre Consul, Vault y Nomad se realiza utilizando diferentes protocolos y puertos de red, el entorno de computación como el que proporcionan los proveedores de computación en la nube no es confiable por ser compartido aún con las medidas de aislamiento de red que implementan. Para mayor seguridad Consul, Vault y Nomad permite utilizar comunicaciones cifradas para todo el tráfico de red entrante y saliente para lo que es necesario generar certificados que permitan cifrar las comunicaciones y permitan identificar tanto al servidor como al cliente con autenticación mutua basada en certificados.

Los certificados se generan con OpenSSL con una pequeña autoridad de certificación (CA) propia.

1
2
3
#!/usr/bin/env bash
./ca.sh
./server-certs.sh
generate-certificates.sh

Vault requiere una inicialización para generar un token raíz y hacer el unseal. El unseal es un proceso que permite desbloquear el almacén cifraddo de Vault en el que interviene una clave dividida en varias partes de las cuales un número igual o menor al total son necesarias para desbloquear Vault cuando se inicia el clúster de servidores Vault.

Las máquinas virtuales utilizan la distribución Ubuntu de GNU/Linux en las que se instala las herramientas de HashiCorp para los servidores y adicionalmente Docker en las instancia cliente de Nomad. En la instancia que da acceso a las consolas web de administración se instala el servidor web Nginx que hace de proxy con el servidor de Consul, Vault y Nomad. El aprovisionamiento de las maquinas se realiza con varios scripts de bash que contienen los comandos necesarios para instalar los paquetes, realizar la misma configuración que se realizaría de forma manual desde una terminal y copiar los archivos de configuración y certificados desde la máquina anfitrión a las máquinas virtuales.

Dado que el ejemplo aún con el mínimo número de máquinas virtuales necesarias requiere varias es necesario una máquina host tenga entre 8 y 16 GiB de memoria RAM como mínimo, se puede ajustar la cantidad de memoria que usa cada máquina virtual en el archivo de configuración de Vagrant.

El ejemplo consta de las siguientes máquinas virtuales.

  • 1 máquina virtual servidor Consul, nomad-server-1.
  • 1 máquina virtual servidor Vault, vault-server-1.
  • 1 máquina virtual servidor Nomad, nomad-server-1.
  • 1 máquina virtual cliente Nomad, nomad-agent-1.
  • 1 máquina virtual con Nginx como proxy a consolas de administración web Consul, Vault, Nomad, ui-server.

Estos son los scripts de aprovisionamiento con sus funciones:

  • hashicorp_role_script: instala los binarios de Consul, Vault, Nomad, Envoy para Consul Connect y los cni-plugins.
  • docker_role_script: instala Docker.
  • nginx_role_script: instala Nginx.
  • consul_dns_server_role_script: redirige las consultas DNS del puerto 53 al puerto 8600 e instala iptables-persistent. Esto permite ejecutar Consul sin requerir permisos de administrador requeridos para abrir un puerto privilegiado como el 53.
  • create_provision_directories_script: inicializa los directorios de aprovisionamiento en la máquina virtual.
  • consul_provision_script, vault_provision_script, nomad_provision_script, ui_server_provision_script: aprovisionan los archivos de configuración de la máquina anfitrión a las máquinas virtuales en los directorios apropiados.
  • vault_configure_script, nomad_configure_script: realizan la configuración de los servidores Vault y Nomad.
  • consul_services_script, vault_services_script, nomad_services_script, ui_server_services_script, docker_services_script: inician los servicios de systemd.

Consul tiene la función de servidor DNS con el catálogo de servicios registrados. Para dar acceso a las máquinas virtuales al servidor de nombres DNS de Consul es necesario redirigir el tráfico del puerto 53 al puerto 8600 y modificar el archivo resolv.conf. Esto requiere un poco de configuración en Ubuntu modificando iptables y utilizar iptables-persistent para que los cambios sean permanentes entre reinicios.

Servicio DNS de Consul accedido desde el servidor de Nomad

Servicio DNS de Consul que expone el catálogo de servicios accedido desde el servidor de Nomad

Este es el archivo de aprovisionamiento de Vagrant para crear las máquinas virtuales en VirtualBox. En todos los servidores no son necesarias todas las herramienta pero por simplicidad en el ejemplo se instalan, por ejemplo, en el servidor de Nomad no es necesario instalar el binario de Vault.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
# -*- mode: ruby -*-
# vi: set ft=ruby :

VAULT_ROOT_TOKEN = "s.ltQXU2wZeWYNSEf946CefIuG"
VAULT_UNSEAL_KEY_1 = "GmSsvWlRtimeVb4ikBYKGJeAWGgkB1h8NpLpGEu0oqTe"
VAULT_UNSEAL_KEY_2 = "zPBru7ivXOhOZmytYHN5gFhusX2kpNR5TOgvxrjxnL"

Vagrant.configure("2") do |config|

  config.vm.define "consul-server-1" do |instance|
    instance.vm.box = "ubuntu/focal64"
    instance.vm.network "private_network", ip: "192.168.33.10"

    instance.vm.provider "virtualbox" do |vb|
      vb.name = "HashiCorp Consul Server 1"
      vb.memory = "1024"
      
      vb.customize ["modifyvm", :id, "--uart1", "0x3F8", "4"]
      vb.customize ["modifyvm", :id, "--uartmode1", "file", File::NULL]
    end
    
    instance.vm.provision "shell", inline: $hashicorp_role_script, privileged: false
    instance.vm.provision "shell", inline: $create_provision_directories_script, privileged: false

    instance.vm.provision "file", source: "ca/intermediate/certs/ca-chain.cert.pem", destination: "/home/vagrant/ca.cert.pem"

    instance.vm.provision "file", source: "consul-server/consul.hcl", destination: "/home/vagrant/consul/consul.hcl"

    instance.vm.provision "file", source: "server-certs/certs/consul-server.cert.pem", destination: "/home/vagrant/consul/consul-server.cert.pem"
    instance.vm.provision "file", source: "server-certs/private/consul-server.key.pem", destination: "/home/vagrant/consul/consul-server.key.pem"
    instance.vm.provision "file", source: "server-certs/certs/consul-agent.cert.pem", destination: "/home/vagrant/consul/consul-agent.cert.pem"
    instance.vm.provision "file", source: "server-certs/private/consul-agent.key.pem", destination: "/home/vagrant/consul/consul-agent.key.pem"

    instance.vm.provision "shell", inline: $consul_provision_script, privileged: false
    instance.vm.provision "shell", inline: $remove_provision_directories_script, privileged: false
    instance.vm.provision "shell", inline: $consul_dns_server_role_script, privileged: false
    instance.vm.provision "shell", inline: $consul_services_script, privileged: false
  end
  
  config.vm.define "vault-server-1" do |instance|
    instance.vm.box = "ubuntu/focal64"

    instance.vm.network "private_network", ip: "192.168.33.20"

    instance.vm.provider "virtualbox" do |vb|
      vb.name = "HashiCorp Vault Server 1"
      vb.memory = "1024"

      vb.customize ["modifyvm", :id, "--uart1", "0x3F8", "4"]
      vb.customize ["modifyvm", :id, "--uartmode1", "file", File::NULL]
    end
    
    instance.vm.provision "shell", inline: $hashicorp_role_script, privileged: false
    instance.vm.provision "shell", inline: $create_provision_directories_script, privileged: false

    instance.vm.provision "file", source: "ca/intermediate/certs/ca-chain.cert.pem", destination: "/home/vagrant/ca.cert.pem"

    instance.vm.provision "file", source: "vault-server/consul-agent.hcl", destination: "/home/vagrant/consul/consul.hcl"
    instance.vm.provision "file", source: "vault-server/vault.hcl", destination: "/home/vagrant/vault/vault.hcl"

    instance.vm.provision "file", source: "vault-server/nomad-server-policy.hcl", destination: "/home/vagrant/vault/nomad-server-policy.hcl"
    instance.vm.provision "file", source: "vault-server/nomad-cluster-role.json", destination: "/home/vagrant/vault/nomad-cluster-role.json"

    instance.vm.provision "file", source: "server-certs/certs/consul-agent.cert.pem", destination: "/home/vagrant/consul/consul-agent.cert.pem"
    instance.vm.provision "file", source: "server-certs/private/consul-agent.key.pem", destination: "/home/vagrant/consul/consul-agent.key.pem"
    instance.vm.provision "file", source: "server-certs/certs/consul-agent.cert.pem", destination: "/home/vagrant/vault/consul-agent.cert.pem"
    instance.vm.provision "file", source: "server-certs/private/consul-agent.key.pem", destination: "/home/vagrant/vault/consul-agent.key.pem"
    instance.vm.provision "file", source: "server-certs/certs/vault-server.cert.pem", destination: "/home/vagrant/vault/vault-server.cert.pem"
    instance.vm.provision "file", source: "server-certs/private/vault-server.key.pem", destination: "/home/vagrant/vault/vault-server.key.pem"
    instance.vm.provision "file", source: "server-certs/certs/vault-agent.cert.pem", destination: "/home/vagrant/vault/vault-agent.cert.pem"
    instance.vm.provision "file", source: "server-certs/private/vault-agent.key.pem", destination: "/home/vagrant/vault/vault-agent.key.pem"

    instance.vm.provision "shell", inline: $vault_provision_script, privileged: false
    instance.vm.provision "shell", inline: $remove_provision_directories_script, privileged: false
    instance.vm.provision "shell", inline: $consul_dns_server_role_script, privileged: false
    instance.vm.provision "shell", inline: $consul_dns_agent_role_script, privileged: false
    instance.vm.provision "shell", inline: $consul_services_script, privileged: false
    instance.vm.provision "shell", inline: $vault_services_script, privileged: false
    instance.vm.provision "shell", inline: $vault_configure_script, privileged: false, env: {"VAULT_ROOT_TOKEN" => VAULT_ROOT_TOKEN, "VAULT_UNSEAL_KEY_1" => VAULT_UNSEAL_KEY_1, "VAULT_UNSEAL_KEY_1" => VAULT_UNSEAL_KEY_1}
  end
  
  config.vm.define "nomad-server-1" do |instance|
    instance.vm.box = "ubuntu/focal64"

    instance.vm.network "private_network", ip: "192.168.33.30"

    instance.vm.provider "virtualbox" do |vb|
      vb.name = "HashiCorp Nomad Server 1"
      vb.memory = "1024"

      vb.customize ["modifyvm", :id, "--uart1", "0x3F8", "4"]
      vb.customize ["modifyvm", :id, "--uartmode1", "file", File::NULL]
    end
    
    instance.vm.provision "shell", inline: $hashicorp_role_script, privileged: false
    instance.vm.provision "shell", inline: $create_provision_directories_script, privileged: false

    instance.vm.provision "file", source: "ca/intermediate/certs/ca-chain.cert.pem", destination: "/home/vagrant/ca.cert.pem"

    instance.vm.provision "file", source: "nomad-server/consul-agent.hcl", destination: "/home/vagrant/consul/consul.hcl"
    instance.vm.provision "file", source: "nomad-server/nomad.hcl", destination: "/home/vagrant/nomad/nomad.hcl"

    instance.vm.provision "file", source: "server-certs/certs/consul-agent.cert.pem", destination: "/home/vagrant/consul/consul-agent.cert.pem"
    instance.vm.provision "file", source: "server-certs/private/consul-agent.key.pem", destination: "/home/vagrant/consul/consul-agent.key.pem"
    instance.vm.provision "file", source: "server-certs/certs/consul-agent.cert.pem", destination: "/home/vagrant/nomad/consul-agent.cert.pem"
    instance.vm.provision "file", source: "server-certs/private/consul-agent.key.pem", destination: "/home/vagrant/nomad/consul-agent.key.pem"
    instance.vm.provision "file", source: "server-certs/certs/vault-agent.cert.pem", destination: "/home/vagrant/nomad/vault-agent.cert.pem"
    instance.vm.provision "file", source: "server-certs/private/vault-agent.key.pem", destination: "/home/vagrant/nomad/vault-agent.key.pem"
    instance.vm.provision "file", source: "server-certs/certs/nomad-server.cert.pem", destination: "/home/vagrant/nomad/nomad-server.cert.pem"
    instance.vm.provision "file", source: "server-certs/private/nomad-server.key.pem", destination: "/home/vagrant/nomad/nomad-server.key.pem"
    instance.vm.provision "file", source: "server-certs/certs/nomad-agent.cert.pem", destination: "/home/vagrant/nomad/nomad-agent.cert.pem"
    instance.vm.provision "file", source: "server-certs/private/nomad-agent.key.pem", destination: "/home/vagrant/nomad/nomad-agent.key.pem"

    instance.vm.provision "shell", inline: $nomad_provision_script, privileged: false
    instance.vm.provision "shell", inline: $remove_provision_directories_script, privileged: false
    instance.vm.provision "shell", inline: $consul_dns_server_role_script, privileged: false
    instance.vm.provision "shell", inline: $consul_dns_agent_role_script, privileged: false
    instance.vm.provision "shell", inline: $consul_services_script, privileged: false
    instance.vm.provision "shell", inline: $nomad_configure_script, privileged: false, env: {"VAULT_ROOT_TOKEN" => VAULT_ROOT_TOKEN}
    instance.vm.provision "shell", inline: $nomad_services_script, privileged: false
  end
  
  config.vm.define "nomad-agent-1" do |instance|
    instance.vm.box = "ubuntu/focal64"

    instance.vm.network "private_network", ip: "192.168.33.40"

    instance.vm.provider "virtualbox" do |vb|
      vb.name = "HashiCorp Nomad Agent 1"
      vb.memory = "2048"

      vb.customize ["modifyvm", :id, "--uart1", "0x3F8", "4"]
      vb.customize ["modifyvm", :id, "--uartmode1", "file", File::NULL]
    end
    
    instance.vm.provision "shell", inline: $hashicorp_role_script, privileged: false
    instance.vm.provision "shell", inline: $docker_role_script, privileged: false    
    instance.vm.provision "shell", inline: $create_provision_directories_script, privileged: false

    instance.vm.provision "file", source: "ca/intermediate/certs/ca-chain.cert.pem", destination: "/home/vagrant/ca.cert.pem"

    instance.vm.provision "file", source: "nomad-agent/consul-agent.hcl", destination: "/home/vagrant/consul/consul.hcl"
    instance.vm.provision "file", source: "nomad-agent/nomad.hcl", destination: "/home/vagrant/nomad/nomad.hcl"

    instance.vm.provision "file", source: "server-certs/certs/consul-agent.cert.pem", destination: "/home/vagrant/consul/consul-agent.cert.pem"
    instance.vm.provision "file", source: "server-certs/private/consul-agent.key.pem", destination: "/home/vagrant/consul/consul-agent.key.pem"
    instance.vm.provision "file", source: "server-certs/certs/consul-agent.cert.pem", destination: "/home/vagrant/nomad/consul-agent.cert.pem"
    instance.vm.provision "file", source: "server-certs/private/consul-agent.key.pem", destination: "/home/vagrant/nomad/consul-agent.key.pem"
    instance.vm.provision "file", source: "server-certs/certs/vault-agent.cert.pem", destination: "/home/vagrant/nomad/vault-agent.cert.pem"
    instance.vm.provision "file", source: "server-certs/private/vault-agent.key.pem", destination: "/home/vagrant/nomad/vault-agent.key.pem"
    instance.vm.provision "file", source: "server-certs/certs/nomad-agent.cert.pem", destination: "/home/vagrant/nomad/nomad-agent.cert.pem"
    instance.vm.provision "file", source: "server-certs/private/nomad-agent.key.pem", destination: "/home/vagrant/nomad/nomad-agent.key.pem"

    instance.vm.provision "shell", inline: $nomad_provision_script, privileged: false
    instance.vm.provision "shell", inline: $remove_provision_directories_script, privileged: false
    instance.vm.provision "shell", inline: $consul_dns_server_role_script, privileged: false
    instance.vm.provision "shell", inline: $consul_dns_agent_role_script, privileged: false
    instance.vm.provision "shell", inline: $consul_services_script, privileged: false
    instance.vm.provision "shell", inline: $nomad_configure_script, privileged: false, env: {"VAULT_ROOT_TOKEN" => VAULT_ROOT_TOKEN}
    instance.vm.provision "shell", inline: $docker_services_script, privileged: false
    instance.vm.provision "shell", inline: $nomad_services_script, privileged: false
  end
  
  config.vm.define "ui-server" do |instance|
    instance.vm.box = "ubuntu/focal64"
    instance.vm.network "private_network", ip: "192.168.33.50"

    instance.vm.provider "virtualbox" do |vb|
      vb.name = "HashiCorp Web Server"
      vb.memory = "512"
      
      vb.customize ["modifyvm", :id, "--uart1", "0x3F8", "4"]
      vb.customize ["modifyvm", :id, "--uartmode1", "file", File::NULL]
    end
    
    instance.vm.provision "shell", inline: $hashicorp_role_script, privileged: false
    instance.vm.provision "shell", inline: $nginx_role_script, privileged: false
    instance.vm.provision "shell", inline: $create_provision_directories_script, privileged: false

    instance.vm.provision "file", source: "ca/intermediate/certs/ca-chain.cert.pem", destination: "/home/vagrant/ca.cert.pem"

    instance.vm.provision "file", source: "ui-server/consul-agent.hcl", destination: "/home/vagrant/consul/consul.hcl"

    instance.vm.provision "file", source: "ui-server/consul.conf", destination: "/home/vagrant/nginx/consul.conf"
    instance.vm.provision "file", source: "ui-server/vault.conf", destination: "/home/vagrant/nginx/vault.conf"
    instance.vm.provision "file", source: "ui-server/nomad.conf", destination: "/home/vagrant/nginx/nomad.conf"
    instance.vm.provision "file", source: "ui-server/consul-htpasswd", destination: "/home/vagrant/nginx/consul-htpasswd"
    instance.vm.provision "file", source: "ui-server/vault-htpasswd", destination: "/home/vagrant/nginx/vault-htpasswd"
    instance.vm.provision "file", source: "ui-server/nomad-htpasswd", destination: "/home/vagrant/nginx/nomad-htpasswd"

    instance.vm.provision "file", source: "server-certs/certs/consul-agent.cert.pem", destination: "/home/vagrant/consul/consul-agent.cert.pem"
    instance.vm.provision "file", source: "server-certs/private/consul-agent.key.pem", destination: "/home/vagrant/consul/consul-agent.key.pem"

    instance.vm.provision "file", source: "server-certs/certs/ui-server.cert.pem", destination: "/home/vagrant/nginx/ui-server.cert.pem"
    instance.vm.provision "file", source: "server-certs/private/ui-server.key.pem", destination: "/home/vagrant/nginx/ui-server.key.pem"
    instance.vm.provision "file", source: "server-certs/certs/consul-agent.cert.pem", destination: "/home/vagrant/nginx/consul-agent.cert.pem"
    instance.vm.provision "file", source: "server-certs/private/consul-agent.key.pem", destination: "/home/vagrant/nginx/consul-agent.key.pem"
    instance.vm.provision "file", source: "server-certs/certs/vault-agent.cert.pem", destination: "/home/vagrant/nginx/vault-agent.cert.pem"
    instance.vm.provision "file", source: "server-certs/private/vault-agent.key.pem", destination: "/home/vagrant/nginx/vault-agent.key.pem"
    instance.vm.provision "file", source: "server-certs/certs/nomad-agent.cert.pem", destination: "/home/vagrant/nginx/nomad-agent.cert.pem"
    instance.vm.provision "file", source: "server-certs/private/nomad-agent.key.pem", destination: "/home/vagrant/nginx/nomad-agent.key.pem"

    instance.vm.provision "shell", inline: $ui_server_provision_script, privileged: false
    instance.vm.provision "shell", inline: $remove_provision_directories_script, privileged: false
    instance.vm.provision "shell", inline: $consul_dns_server_role_script, privileged: false
    instance.vm.provision "shell", inline: $consul_dns_agent_role_script, privileged: false
    instance.vm.provision "shell", inline: $ui_server_services_script, privileged: false
  end
  
  $hashicorp_role_script = <<-SCRIPT
    sudo apt-get -y update
    sudo apt-get -y upgrade

    curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo apt-key add -
    sudo apt-add-repository "deb [arch=amd64] https://apt.releases.hashicorp.com $(lsb_release -cs) main"
    sudo apt-get -y update

    sudo apt-get -y install consul vault nomad
    
    sudo systemctl disable consul.service
    sudo systemctl disable vault.service
    sudo systemctl disable nomad.service

    if [ ! -f "/usr/local/bin/envoy" ]; then
        curl -L https://getenvoy.io/cli | sudo bash -s -- -b /usr/local/bin 
        getenvoy run standard:1.15.0 -- --version
        sudo cp /home/vagrant/.getenvoy/builds/standard/1.15.0/linux_glibc/bin/envoy /usr/local/bin/
    fi
    
    if [ ! -d "/opt/cni/bin" ]; then
        curl -L -o cni-plugins.tgz https://github.com/containernetworking/plugins/releases/download/v0.8.6/cni-plugins-linux-amd64-v0.8.6.tgz
        sudo mkdir -p /opt/cni/bin
        sudo tar -C /opt/cni/bin -xzf cni-plugins.tgz
        rm cni-plugins.tgz
    fi
  SCRIPT

  $docker_role_script = <<-SCRIPT
    sudo apt-get -y update
    sudo apt-get -y upgrade

    sudo apt-get -y install apt-transport-https ca-certificates curl gnupg-agent software-properties-common

    curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
    sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
    sudo apt-get -y update

    sudo apt-get -y install docker-ce docker-ce-cli containerd.io
    sudo usermod -aG docker vagrant
    sudo usermod -aG docker nomad

    sudo systemctl enable docker.service
  SCRIPT

  $nginx_role_script = <<-SCRIPT
    sudo apt-get -y update
    sudo apt-get -y upgrade
    
    sudo apt-get -y install nginx
    
    sudo systemctl enable nginx.service
  SCRIPT
  
  $consul_dns_server_role_script = <<-SCRIPT
    sudo iptables -t nat -A PREROUTING -p udp -m udp --dport 53 -j REDIRECT --to-ports 8600
    sudo iptables -t nat -A PREROUTING -p tcp -m tcp --dport 53 -j REDIRECT --to-ports 8600
    sudo iptables -t nat -A OUTPUT -d localhost -p udp -m udp --dport 53 -j REDIRECT --to-ports 8600
    sudo iptables -t nat -A OUTPUT -d localhost -p tcp -m tcp --dport 53 -j REDIRECT --to-ports 8600

    sudo DEBIAN_FRONTEND=noninteractive apt-get -y install iptables-persistent
    sudo iptables-save -f /etc/iptables/rules.v4
  SCRIPT
  
  $consul_dns_agent_role_script = <<-SCRIPT
    if [ ! $(grep -q "nameserver 127.0.0.1" /etc/resolv.conf) ]; then
      sudo sed -i "s/^$/\\nnameserver 127.0.0.1/g" /etc/resolv.conf
    fi
  SCRIPT
  
  $create_provision_directories_script = <<-SCRIPT
    rm -rf /home/vagrant/consul/
    rm -rf /home/vagrant/vault/
    rm -rf /home/vagrant/nomad/
    rm -rf /home/vagrant/nginx/
    rm -f /home/vagrant/*.pem

    mkdir -p /home/vagrant/consul/
    mkdir -p /home/vagrant/vault/
    mkdir -p /home/vagrant/nomad/
    mkdir -p /home/vagrant/nginx/
  SCRIPT
  
  $remove_provision_directories_script = <<-SCRIPT
    rm -rf /home/vagrant/consul/
    rm -rf /home/vagrant/vault/
    rm -rf /home/vagrant/nomad/
    rm -rf /home/vagrant/nginx/
    rm -f /home/vagrant/*.pem
  SCRIPT
  
  $consul_provision_script = <<-SCRIPT
    sudo cp /home/vagrant/ca.cert.pem /etc/consul.d/ca.cert.pem

    sudo cp /home/vagrant/consul/consul.hcl /etc/consul.d/consul.hcl

    sudo cp /home/vagrant/consul/consul-server.cert.pem /etc/consul.d/consul-server.cert.pem 2>/dev/null || :
    sudo cp /home/vagrant/consul/consul-server.key.pem /etc/consul.d/consul-server.key.pem 2>/dev/null || :
    sudo cp /home/vagrant/consul/consul-agent.cert.pem /etc/consul.d/consul-agent.cert.pem 2>/dev/null || :
    sudo cp /home/vagrant/consul/consul-agent.key.pem /etc/consul.d/consul-agent.key.pem 2>/dev/null || :

    sudo chmod 444 /etc/consul.d/*
    sudo chmod 644 /etc/consul.d/*.hcl
    sudo chown root:root /etc/consul.d/*
  SCRIPT

  $vault_provision_script = <<-SCRIPT
    sudo cp /home/vagrant/ca.cert.pem /etc/consul.d/ca.cert.pem
    sudo cp /home/vagrant/ca.cert.pem /etc/vault.d/ca.cert.pem

    sudo cp /home/vagrant/consul/consul.hcl /etc/consul.d/consul.hcl
    sudo cp /home/vagrant/vault/vault.hcl /etc/vault.d/vault.hcl

    sudo cp /home/vagrant/vault/nomad-server-policy.hcl /etc/vault.d/nomad-server-policy.hcl
    sudo cp /home/vagrant/vault/nomad-cluster-role.json /etc/vault.d/nomad-cluster-role.json

    sudo cp /home/vagrant/consul/consul-agent.cert.pem /etc/consul.d/consul-agent.cert.pem
    sudo cp /home/vagrant/consul/consul-agent.key.pem /etc/consul.d/consul-agent.key.pem
    sudo cp /home/vagrant/vault/consul-agent.key.pem /etc/vault.d/consul-agent.key.pem
    sudo cp /home/vagrant/vault/consul-agent.cert.pem /etc/vault.d/consul-agent.cert.pem
    sudo cp /home/vagrant/vault/vault-server.cert.pem /etc/vault.d/vault-server.cert.pem
    sudo cp /home/vagrant/vault/vault-server.key.pem /etc/vault.d/vault-server.key.pem
    sudo cp /home/vagrant/vault/vault-agent.cert.pem /etc/vault.d/vault-agent.cert.pem
    sudo cp /home/vagrant/vault/vault-agent.key.pem /etc/vault.d/vault-agent.key.pem

    sudo chmod 444 /etc/consul.d/* /etc/vault.d/*
    sudo chmod 644 /etc/consul.d/*.hcl /etc/vault.d/*.hcl
    sudo chown root:root /etc/consul.d/* /etc/vault.d/*

    sudo sed -i "s/^StartLimitIntervalSec=*$//g" /lib/systemd/system/vault.service
  SCRIPT

  $nomad_provision_script = <<-SCRIPT
    sudo cp /home/vagrant/ca.cert.pem /etc/consul.d/ca.cert.pem
    sudo cp /home/vagrant/ca.cert.pem /etc/nomad.d/ca.cert.pem

    sudo cp /home/vagrant/consul/consul.hcl /etc/consul.d/consul.hcl
    sudo cp /home/vagrant/nomad/nomad.hcl /etc/nomad.d/nomad.hcl

    sudo cp /home/vagrant/consul/consul-agent.cert.pem /etc/consul.d/consul-agent.cert.pem
    sudo cp /home/vagrant/consul/consul-agent.key.pem /etc/consul.d/consul-agent.key.pem
    sudo cp /home/vagrant/nomad/consul-agent.cert.pem /etc/nomad.d/consul-agent.cert.pem
    sudo cp /home/vagrant/nomad/consul-agent.key.pem /etc/nomad.d/consul-agent.key.pem
    sudo cp /home/vagrant/nomad/vault-agent.cert.pem /etc/nomad.d/vault-agent.cert.pem
    sudo cp /home/vagrant/nomad/vault-agent.key.pem /etc/nomad.d/vault-agent.key.pem
    sudo cp /home/vagrant/nomad/nomad-server.cert.pem /etc/nomad.d/nomad-server.cert.pem 2>/dev/null || :
    sudo cp /home/vagrant/nomad/nomad-server.key.pem /etc/nomad.d/nomad-server.key.pem 2>/dev/null || :
    sudo cp /home/vagrant/nomad/nomad-agent.cert.pem /etc/nomad.d/nomad-agent.cert.pem 2>/dev/null || :
    sudo cp /home/vagrant/nomad/nomad-agent.key.pem /etc/nomad.d/nomad-agent.key.pem 2>/dev/null || :

    sudo chmod 444 /etc/consul.d/* /etc/nomad.d/*
    sudo chmod 644 /etc/consul.d/*.hcl /etc/nomad.d/*.hcl
    sudo chown root:root /etc/consul.d/* /etc/nomad.d/*

    sudo sed -i "s/^StartLimitIntervalSec=*$//g" /lib/systemd/system/vault.service
    sudo sed -i "s/^#Wants=consul.service$/Wants=consul.service/g" /lib/systemd/system/nomad.service
    sudo sed -i "s/^#After=consul.service$/After=consul.service/g" /lib/systemd/system/nomad.service 
  SCRIPT
  
  $ui_server_provision_script = <<-SCRIPT
    sudo cp /home/vagrant/ca.cert.pem /etc/consul.d/ca.cert.pem

    sudo cp /home/vagrant/consul/consul.hcl /etc/consul.d/consul.hcl

    sudo cp /home/vagrant/nginx/consul.conf /etc/nginx/sites-enabled/consul.conf
    sudo cp /home/vagrant/nginx/vault.conf /etc/nginx/sites-enabled/vault.conf
    sudo cp /home/vagrant/nginx/nomad.conf /etc/nginx/sites-enabled/nomad.conf
    sudo cp /home/vagrant/nginx/consul-htpasswd /etc/nginx/conf.d/consul-htpasswd
    sudo cp /home/vagrant/nginx/vault-htpasswd /etc/nginx/conf.d/vault-htpasswd
    sudo cp /home/vagrant/nginx/nomad-htpasswd /etc/nginx/conf.d/nomad-htpasswd

    sudo cp /home/vagrant/ca.cert.pem /etc/nginx/certs/ca.cert.pem

    sudo cp /home/vagrant/consul/consul-agent.cert.pem /etc/consul.d/consul-agent.cert.pem
    sudo cp /home/vagrant/consul/consul-agent.key.pem /etc/consul.d/consul-agent.key.pem

    sudo mkdir -p /etc/nginx/certs/
    sudo mkdir -p /etc/nginx/private/
    sudo cp /home/vagrant/nginx/ui-server.cert.pem /etc/nginx/certs/ui-server.cert.pem
    sudo cp /home/vagrant/nginx/ui-server.key.pem /etc/nginx/private/ui-server.key.pem
    sudo cp /home/vagrant/nginx/consul-agent.cert.pem /etc/nginx/certs/consul-agent.cert.pem
    sudo cp /home/vagrant/nginx/consul-agent.key.pem /etc/nginx/private/consul-agent.key.pem
    sudo cp /home/vagrant/nginx/vault-agent.cert.pem /etc/nginx/certs/vault-agent.cert.pem
    sudo cp /home/vagrant/nginx/vault-agent.key.pem /etc/nginx/private/vault-agent.key.pem
    sudo cp /home/vagrant/nginx/nomad-agent.cert.pem /etc/nginx/certs/nomad-agent.cert.pem
    sudo cp /home/vagrant/nginx/nomad-agent.key.pem /etc/nginx/private/nomad-agent.key.pem

    sudo chmod 444 /etc/consul.d/* /etc/nginx/sites-enabled/* /etc/nginx/certs/* /etc/nginx/private/* /etc/nginx/conf.d/*-htpasswd
    sudo chmod 644 /etc/consul.d/*.hcl /etc/nginx/sites-enabled/*.conf /etc/nginx/certs/* /etc/nginx/private/* /etc/nginx/conf.d/*-htpasswd
    sudo chown root:root /etc/consul.d/* /etc/nginx/sites-enabled/*.conf /etc/nginx/certs/* /etc/nginx/private/* /etc/nginx/conf.d/*-htpasswd
  SCRIPT

  $vault_configure_script = <<-SCRIPT
    export VAULT_ADDR="https://192.168.33.20:8200"
    export VAULT_CLIENT_CERT="/etc/vault.d/vault-agent.cert.pem"
    export VAULT_CLIENT_KEY="/etc/vault.d/vault-agent.key.pem"
    export VAULT_CAPATH="/etc/vault.d/ca.cert.pem"

    vault operator unseal "$VAULT_UNSEAL_KEY_1" || :
    vault operator unseal "$VAULT_UNSEAL_KEY_2" || :
    sleep 5

    vault login "$VAULT_ROOT_TOKEN" || :
    vault policy write nomad-server /etc/vault.d/nomad-server-policy.hcl || :
    vault write auth/token/roles/nomad-cluster @/etc/vault.d/nomad-cluster-role.json || :
  SCRIPT
  
  $nomad_configure_script = <<-SCRIPT
    export VAULT_ADDR="https://vault.service.consul:8200"
    export VAULT_CLIENT_CERT="/etc/nomad.d/vault-agent.cert.pem"
    export VAULT_CLIENT_KEY="/etc/nomad.d/vault-agent.key.pem"
    export VAULT_CAPATH="/etc/nomad.d/ca.cert.pem"
 
    vault login "$VAULT_ROOT_TOKEN"
    VAULT_TOKEN=$(vault token create -policy nomad-server -period 72h -orphan -field token)

    sudo sed -i "s/\$VAULT_TOKEN/$VAULT_TOKEN/g" /etc/nomad.d/nomad.hcl
  SCRIPT

  $consul_services_script = <<-SCRIPT
    sudo systemctl enable consul.service
    sudo systemctl restart consul.service
    sleep 5
  SCRIPT
  
  $vault_services_script = <<-SCRIPT
    sudo systemctl enable consul.service
    sudo systemctl restart consul.service
    sleep 5

    sudo systemctl enable vault.service
    sudo systemctl restart vault.service
    sleep 5
  SCRIPT
  
  $nomad_services_script = <<-SCRIPT
    sudo systemctl enable nomad.service
    sudo systemctl restart nomad.service
    sleep 5
  SCRIPT
  
  $ui_server_services_script = <<-SCRIPT
    sudo systemctl enable consul.service
    sudo systemctl restart consul.service
    sleep 5

    sudo systemctl enable nginx.service
    sudo systemctl restart nginx.service
    sleep 5
  SCRIPT

  $docker_services_script = <<-SCRIPT
    sudo systemctl enable docker.service
    sudo systemctl restart docker.service
    sleep 5
  SCRIPT
end
Vagrantfile

El comando que permite crear y aprovisionar las máquinas es el siguiente.

1
2
#!/usr/bin/env bash
vagrant up --provision
vagrant-up.sh

En el primer inicio Vault requiere generar el token root que tiene los permisos de superusuario, también se divide una clave en varias partes de las cuales un número mínimo son necesarias para realizar la operación de unseal, las partes de la clave son distribuidas entre varias personas y son necesarias varias para realizar la operación unseal, para que la persona que genera las partes de las claves no conozca todas las partes de la clave y para transmitirlas de forma segura las partes de la clave se cifran con PGP. Para la creación del token root y las claves en la creación de más máquinas en vez de usar vagrant-up.sh hay que aprovisionar las máquinas en varios pasos.

Se inicializa la máquina virtual del servidor Consul, luego del servidor Vault y se genera el token root, se inicia sesión con el token root y se realiza la operación unseal con el número mínimo de claves necesarias para desbloquear los datos cifrados del servidor.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
$ vagrant up --provision consul-server-1
$ vagrant up --provision vault-server-1

$ export VAULT_ADDR="https://192.168.33.20:8200"
$ export VAULT_CLIENT_CERT="server-certs/certs/vault-agent.cert.pem"
$ export VAULT_CLIENT_KEY="server-certs/private/vault-agent.key.pem"
$ export VAULT_CACERT="ca/intermediate/certs/ca-chain.cert.pem"

$ vault operator init -key-shares=5 -key-threshold=2
Unseal Key 1: GmSsvWlRtimeVb4ikBYKGJeAWGgkB1h8NpLpGEu0oqTe
Unseal Key 2: zPBru7ivXOhOZmytYHN5gFhusX2kpNR5TOgvxrjxnL+B
Unseal Key 3: CZ4T0oqwLvniuufFimkpPLxSeo6nWAWpHrrJn+utDLlF
Unseal Key 4: aVkAonumA5ryLy7hpWDCG+bWb1RboxnhXfk7tvybmFpx
Unseal Key 5: a5mCVlK4E+wEtH2m7Tc+KMaXBYWVqSRv3HhmFs5yL93A

Initial Root Token: s.ltQXU2wZeWYNSEf946CefIuG

Vault initialized with 5 key shares and a key threshold of 2. Please securely
distribute the key shares printed above. When the Vault is re-sealed,
restarted, or stopped, you must supply at least 2 of these keys to unseal it
before it can start servicing requests.

Vault does not store the generated master key. Without at least 2 key to
reconstruct the master key, Vault will remain permanently sealed!

It is possible to generate new unseal keys, provided you have a quorum of
existing unseal keys shares. See "vault operator rekey" for more information.
$ vault status
Key                Value
---                -----
Seal Type          shamir
Initialized        true
Sealed             true
Total Shares       5
Threshold          2
Unseal Progress    0/2
Unseal Nonce       n/a
Version            1.5.0
HA Enabled         true

$ vault operator unseal GmSsvWlRtimeVb4ikBYKGJeAWGgkB1h8NpLpGEu0oqTe
$ vault operator unseal zPBru7ivXOhOZmytYHN5gFhusX2kpNR5TOgvxrjxnL+B
Key             Value
---             -----
Seal Type       shamir
Initialized     true
Sealed          false
Total Shares    5
Threshold       2
Version         1.5.0
Cluster Name    vault-cluster-28313ce3
Cluster ID      dcb97a2c-520c-2d41-9dc3-979b1cda5423
HA Enabled      true
HA Cluster      https://192.168.33.20:8201
HA Mode         active

$ vault login "s.ltQXU2wZeWYNSEf946CefIuG"
Success! You are now authenticated. The token information displayed below
is already stored in the token helper. You do NOT need to run "vault login"
again. Future Vault requests will automatically use this token.

Key                  Value
---                  -----
token                s.ltQXU2wZeWYNSEf946CefIuG
token_accessor       l2YGtM1V4z3Sv0Gey0l646BX
token_duration       ∞
token_renewable      false
token_policies       ["root"]
identity_policies    []
policies             ["root"]
vagrant-up-1.sh

El token raíz devuelto en la inicialización de Vault es necesario ponerlo en la variable VAULT_ROOT_TOKEN del archivo Vagranfile junto con 2 de las 5 claves para desellar Vault en las variables VAULT_UNSEAL_KEY_1 y VAULT_UNSEAL_KEY_2. Luegos se inician y aprovisionan el resto de máquinas virtuales.

1
2
3
$ vagrant up --provision nomad-server-1
$ vagrant up --provision nomad-agent-1
$ vagrant up --provision ui-server
vagrant-up-2.sh

Tanto Consul, Vault como Nomad necesitan un archivo de configuración, en él se describe las direcciones IP de los servidores en las máquinas virtuales, se usan las propiedades de configuración necesarias para que la comunicaciones estén cifradas y diferentes propiedades adicionales para configurar cada servicio servidor.

  • El servidor Consul utiliza la dirección IP 192.168.33.10
  • El servidor Vault utiliza la dirección IP 192.168.33.20
  • El servidor Nomad utiliza la dirección IP 192.168.33.30
  • El cliente de Nomad utiliza la dirección IP 192.168.33.40
  • El servidor Nginx con las consolas de administración web utiliza la dirección IP 192.168.33.50
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
node_name = "consul-server-1"
server = true
bootstrap_expect = 1
bind_addr = "192.168.33.10"
data_dir = "/opt/consul/"

cert_file = "/etc/consul.d/consul-server.cert.pem"
key_file = "/etc/consul.d/consul-server.key.pem"
ca_file = "/etc/consul.d/ca.cert.pem"
verify_incoming = true
verify_outgoing = true
verify_server_hostname = true

recursors = ["8.8.8.8", "8.8.4.4"]

retry_join = []

ui = true
client_addr = "0.0.0.0"

ports {
  grpc = 8502
  http = -1
  https = 8501
}

connect {
  enabled = true
}

auto_encrypt {
  allow_tls = true
}
consul-server/consul.hcl
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
ui = true
api_addr = "https://192.168.33.20:8200"

service_registration "consul" {
  scheme = "https"
  address = "127.0.0.1:8501"
  tls_cert_file = "/etc/vault.d/consul-agent.cert.pem"
  tls_key_file = "/etc/vault.d/consul-agent.key.pem"
  tls_ca_file = "/etc/vault.d/ca.cert.pem"
  tls_min_version = "tls13"
}

storage "consul" {
  scheme = "https"
  address = "127.0.0.1:8501"
  path = "vault/"
  tls_cert_file = "/etc/vault.d/consul-agent.cert.pem"
  tls_key_file = "/etc/vault.d/consul-agent.key.pem"
  tls_ca_file = "/etc/vault.d/ca.cert.pem"
  tls_min_version = "tls13"
}

listener "tcp" {
  address = "0.0.0.0:8200"
  tls_cert_file = "/etc/vault.d/vault-server.cert.pem"
  tls_key_file = "/etc/vault.d/vault-server.key.pem"
  tls_client_ca_file = "/etc/vault.d/ca.cert.pem"
  tls_min_version = "tls13"
}
vault-server/vault.hcl
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
name = "nomad-server-1"
data_dir = "/opt/nomad"

advertise {
  http = "192.168.33.30"
  rpc  = "192.168.33.30"
  serf = "192.168.33.30"
}

server {
  enabled = true
  bootstrap_expect = 1
  encrypt = "aUcxnl/42fcutmLyRO/TVg=="
}

consul {
  address = "127.0.0.1:8501"
  cert_file = "/etc/consul.d/consul-agent.cert.pem"
  key_file = "/etc/consul.d/consul-agent.key.pem"
  ca_file = "/etc/consul.d/ca.cert.pem"
  ssl = true
  server_auto_join = true
  client_auto_join = true
}

vault {
  enabled = true
  address = "https://vault.service.consul:8200"
  token = "$VAULT_TOKEN"
  create_from_role = "nomad-cluster"

  cert_file = "/etc/nomad.d/vault-agent.cert.pem"
  key_file = "/etc/nomad.d/vault-agent.key.pem"
  ca_file = "/etc/nomad.d/ca.cert.pem"  
}

tls {
  http = true
  rpc  = true
  cert_file = "/etc/nomad.d/nomad-server.cert.pem"
  key_file = "/etc/nomad.d/nomad-server.key.pem"
  ca_file = "/etc/nomad.d/ca.cert.pem"
  tls_min_version = "tls12"
  verify_https_client = true
  verify_server_hostname = true
}
nomad-server/nomad.hcl
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
name = "nomad-agent-1"
data_dir = "/opt/nomad"

advertise {
  http = "192.168.33.40"
  rpc  = "192.168.33.40"
  serf = "192.168.33.40"
}

client {
  enabled = true
  network_interface = "enp0s8"
}

consul {
  address = "127.0.0.1:8501"
  cert_file = "/etc/consul.d/consul-agent.cert.pem"
  key_file = "/etc/consul.d/consul-agent.key.pem"
  ca_file = "/etc/consul.d/ca.cert.pem"
  ssl = true
  auto_advertise = true
  server_auto_join = true
  client_auto_join = true
}

vault {
  enabled = true
  address = "https://vault.service.consul:8200"
  token = "$VAULT_TOKEN"
  create_from_role = "nomad-cluster"

  cert_file = "/etc/nomad.d/vault-agent.cert.pem"
  key_file = "/etc/nomad.d/vault-agent.key.pem"
  ca_file = "/etc/nomad.d/ca.cert.pem"  
}

tls {
  http = true
  rpc  = true
  cert_file = "/etc/nomad.d/nomad-agent.cert.pem"
  key_file = "/etc/nomad.d/nomad-agent.key.pem"
  ca_file = "/etc/nomad.d/ca.cert.pem"
  tls_min_version = "tls12"
  verify_https_client = true
  verify_server_hostname = true
}
nomad-agent/nomad.hcl

Como se muestra en los esquemas de de arquitectura de referencia Vault y Nomad no se comunican directamente con los servidores Consul sino que se comunica con un cliente de Consul en la propia máquina y es el cliente de Consul el que se comunica con los servidores de Consul. Consul se instala con la función de cliente en las máquinas servidor Vault, servidor Nomad y cliente de Nomad, el archivo de configuración de Consul en modo cliente son similares a este variando la dirección IP de la interfaz de red a la que se asocian.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
node_name = "vault-server-1"
server = false
bind_addr = "192.168.33.20"
data_dir = "/opt/consul/"

cert_file = "/etc/consul.d/consul-agent.cert.pem"
key_file = "/etc/consul.d/consul-agent.key.pem"
ca_file = "/etc/consul.d/ca.cert.pem"
verify_incoming = true
verify_outgoing = true
verify_server_hostname = true

recursors = ["8.8.8.8", "8.8.4.4"]

retry_join = ["192.168.33.10"]

ports {
  grpc = 8502
  http = -1
  https = 8501
}

auto_encrypt {
  tls = true
}
vault-server/consul-agent.hcl

Interfaz web para Consul, Vault y Nomad

Las consolas de administración web de Consul, Vault y Nomad están accesibles en estas direcciones URL que están protegidas por una autenticación básica, dado que el certificado de la CA no es de confianza para el navegador se muestra previamente un mensaje de advertencia. En la consola web de Consul se observan los servicios y nodos registrados registrados y su estado de salud.

  • Dirección, usuario y contraseña de consola web Consul: https://consul.192.168.33.50.xip.io, consul y consul.
  • Dirección, usuario y contraseña de consola web Vault: https://vault.192.168.33.50.xip.io, vault y vault.
  • Dirección, usuario y contraseña de consola web Nomad: https://nomad.192.168.33.50.xip.io, nomad y nomad.

Esta es la definición del servidor web virtual de la consola de Consul, los servidores web virtuales de Vault y Nomad son similares.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
upstream consul.upstream {
    server 192.168.33.10:8501;
}

server {
    listen 80;
    server_name consul.192.168.33.50.xip.io;

    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;

    server_name consul.192.168.33.50.xip.io;

    ssl_certificate      certs/ui-server.cert.pem;
    ssl_certificate_key  private/ui-server.key.pem;

    proxy_ssl_certificate certs/consul-agent.cert.pem;
    proxy_ssl_certificate_key private/consul-agent.key.pem;
    proxy_ssl_trusted_certificate certs/ca.cert.pem;
    proxy_ssl_verify off;
    proxy_ssl_protocols TLSv1.3;

    location / {
        proxy_pass https://consul.upstream;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Proto https;

        auth_basic "Restricted Area";
        auth_basic_user_file conf.d/consul-htpasswd;
    }
}
ui-server/consul.conf

Las consolas de administración muestran el estado de centro de datos y con todos los servicios está disponibles.

Consola web de Consul con los servicios registrados y su estado de salud Consola web de Consul con los nodos registrados y su estado de salud

Consola web de Vault Consola web de Nomad

Consolas web de Consul, Vault y Nomad y estados de salud de los servicios y nodos

Interfaz de línea de comandos para Consul, Vault y Nomad

Los mismos binarios de Consul, Vault y Nomad que tienen las funciones de servidor y cliente permiten utilizar su línea de comandos para realizar y automatizar con scripts las mismas acciones que se pueden hacer desde las consolas web pero la línea de comandos.

Dado que en el ejemplo los servidores requieren identificar al cliente es necesario configurar variables de entorno que indiquen la dirección IP del servidor y los certificados para el cifrado y autenticación. Una vez configuradas las variables de entorno se pueden lanzar comandos que permiten realizar tareas de administración para obtener información y para enviar jobs a Nomad.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#!/usr/bin/env bash
set -e

export CONSUL_HTTP_ADDR="https://192.168.33.10:8501"
export CONSUL_CLIENT_CERT="server-certs/certs/consul-agent.cert.pem"
export CONSUL_CLIENT_KEY="server-certs/private/consul-agent.key.pem"
export CONSUL_CACERT="ca/intermediate/certs/ca-chain.cert.pem"
export CONSUL_HTTP_SSL=true
export CONSUL_HTTP_SSL_VERIFY=true

export VAULT_ADDR="https://192.168.33.20:8200"
export VAULT_CLIENT_CERT="server-certs/certs/vault-agent.cert.pem"
export VAULT_CLIENT_KEY="server-certs/private/vault-agent.key.pem"
export VAULT_CACERT="ca/intermediate/certs/ca-chain.cert.pem"

export NOMAD_ADDR="https://192.168.33.30:4646"
export NOMAD_CLIENT_CERT="server-certs/certs/nomad-agent.cert.pem"
export NOMAD_CLIENT_KEY="server-certs/private/nomad-agent.key.pem"
export NOMAD_CACERT="ca/intermediate/certs/ca-chain.cert.pem"
environment.sh
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ consul members
Node             Address             Status  Type    Build  Protocol  DC   Segment
consul-server-1  192.168.33.10:8301  alive   server  1.8.3  2         dc1  <all>
nomad-agent-1    192.168.33.40:8301  alive   client  1.8.3  2         dc1  <default>
nomad-server-1   192.168.33.30:8301  alive   client  1.8.3  2         dc1  <default>
ui-server-1      192.168.33.50:8301  alive   client  1.8.3  2         dc1  <default>
vault-server-1   192.168.33.20:8301  alive   client  1.8.3  2         dc1  <default>
$ consul catalog services
consul
nginx
nomad
nomad-client
vault
$ nomad server members
Name                   Address        Port  Status  Leader  Protocol  Build   Datacenter  Region
nomad-server-1.global  192.168.33.30  4648  alive   true    2         0.12.2  dc1         global
$ nomad node status
ID        DC   Name           Class   Drain  Eligibility  Status
7469d6e5  dc1  nomad-agent-1  <none>  false  eligible     ready
$ nomad job status
ID     Type     Priority  Status   Submit Date
nginx  service  50        running  2020-08-14T18:25:59+02:00
environment-status.sh

Ejecución de un servicio

El siguiente es la definición de un servicio o job para Nomad que define un servidor Nginx ejecutado en un contenedor de Docker. Una vez Nomad lo pone en ejecución se registra en Consul de forma automática de forma que pudiera ser encontrado por otros servicios que consulten el catálogo de servicios de Consul.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
job "nginx" {
  datacenters = ["dc1"]

  group "services" {
    count = 1

    task "nginx" {
      driver = "docker"

      config {
        image = "nginx:alpine"

         network_mode = "bridge"
         port_map {
           http = 80
         }
      }

      resources {
        memory = 512 # MB

        network {
          port "http" {
            static = 8080
          }
        }
      }

      service {
        name = "nginx"
        port = "http"
        check {
          name     = "nginx-check"
          type     = "tcp"
          interval = "10s"
          timeout  = "2s"
        }
      }
    }
  }
}
nomad-agent/nginx.nomad
1
2
3
4
5
6
$ nomad run nomad-agent/nginx.nomad
==> Monitoring evaluation "6332818e"
    Evaluation triggered by job "nginx"
    Evaluation within deployment: "c9a44a2d"
    Evaluation status changed: "pending" -> "complete"
==> Evaluation "6332818e" finished with status "complete"
nomad-job.sh

Estado de un job en la consola web de Nomad Servicio de Nginx ejecutado en Nomad como un contenedor de Docker

Ejecución, estado de un job con un servicio de Nginx ejecutando en un contenedor de Docker en la consola web de Nomad

Servicios en consul con el servicio de Nginx registrado por Nomad Información de estado del centro de datos obtenido desde la línea de comandos

Servicios en consul con el servicio de Nginx registrado por Nomad e información de estado del centro de datos obtenido desde la línea de comandos

Requerimientos de operaciones

Este artículo solo tiene un ejemplo básico pero que sirve como base para crear un entorno de producción real basado en las herramientas de HashiCorp.

Hay otras funcionalidades que un entorno real de producción requeriría, como telemetría y monitorización, cual sería el proceso para actualizar el centro de datos a nuevas versiones, aprovisionamiento con Terraform para un centro de computación proporcionado por un proveedor en la nube, utilizar un sistema de autenticación como OAuth en vez autenticación básica en la consolas de administración, quizá consul-template en algunos servicios para actualizar de forma dinámica configuraciones que tomen datos de Vault o Consul o utilizar Traefik para que haga de proxy de los servicios que utilicen el protocolo HTTP.

Terminal

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando siguiente comando:
./vagrant-up.sh


Comparte el artículo: