Los servicios que necesitan almacenar datos como las bases de datos o un sistema de archivos tienen más restricciones que los servicios sin estado por la necesidad de tener acceso a esos datos. Esto hace que los contenedores puedan no tener tanta libertad para iniciarse en cualquier nodo. En Nomad una estrategia es imponer ciertas restricciones a los servicios que almacenen estado para que solo se puedan iniciar en el nodo que donde estén almacenados.
Algunos servicios no necesitan almacenar ningún estado porque no lo necesitan o porque el estado se mantiene en otro servicio. Que un servicio no necesite mantener estado es bueno porque de esta manera el servicio se puede escalar al número de instancias adecuadas para prestar el servicio, también porque si falla una instancia la petición puede ser reenviada a otra instancia, una instancia que falla puede ser reemplazada sin problema en otro host. Sin embargo, hay otro tipo de instancias que si almacenan estado como las bases de datos ya sea PostgreSQL, MySQL, Redis, MongoDB, otra o simplemente archivos en el sistema de archivos.
Las instancias que tienen estado no son tan fáciles de reemplazar dado que los datos son necesarios para su funcionamiento, una instancia de un servicio con estado como no puede iniciarse en otro nodo libremente solo se puede iniciar en el nodo que contenga los datos. Eso o cuando el servicio se inicia en otro nodo los datos son trasladados o por algún mecanismo transparente a los servicios están disponibles en el nuevo nodo.
En Docker Swarm ciertos drivers de volúmenes pueden proporcionar volúmenes accesibles desde cualquier host del cluster pero por defecto Swarm no lo ofrece. En Kubernetes los volúmenes pueden ser dispositivos de almacenamiento provenientes de EBS de modo que si un pod es movido a otro host basta con que el pod sea conectado de nuevo al EBS anterior y los datos están accesibles en el nuevo nodo.
Nomad no proporciona soporte para que el almacenamiento persistente sea migrado a un nuevo nodo de Nomad si el servicio cambiado de ubicación. Para solventar esta limitación en el caso de los servicios con estado estos pueden ser tratados en cierta forma como animales de compañía o pets haciendo que siempre se ubiquen en el mismo nodo, una vez tiene siempre la misma ubicación basta con proporcionar el almacenamiento en el host ya sea en su sistema de archivos o para externalizarlo montando un almacenamiento EBS.
Para conseguir que un job de Nomad se ubique siempre en un mismo nodo hay que usar restricciones o costraints en la especificación del job. Las restricciones son las reglas que utiliza Nomad para elegir como candidatos los posibles nodos en los que ubicar el job, task group o task. Se pueden utilizar varios operadores entre los que está el de igualdad utilizado en el ejemplo. Una de las variables utilizables es el identificativo del nodo de Nomad, con él es posible conseguir que el job se ubique siempre en el mismo nodo. Los identificativos de los nodos son asignados por Nomad cuando se unen al cluster.
Con los siguientes comandos se inspecciona los nodos que forman parte del cluster de Nomad, entre sus datos está el identificativo de cada nodo formado por una cadena de 36 caracteres. En el modo verboso se emite el identificativo completo y una lista de propiedades del nodo entre los que están detalles de Consul, la CPU, driver que soporta, kernel, sistema operativo, … En documentación sobre interpolación de variables hay una lista de variables disponibles.
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
|
$ nomad node status
ID DC Name Class Drain Eligibility Status
44511e01 localhost archlinux <none> false eligible ready
$ nomad node status 44511e01
ID = 44511e01
Name = archlinux
Class = <none>
DC = localhost
Drain = false
Eligibility = eligible
Status = ready
Uptime = 1h40m31s
Driver Status = docker,exec,java,qemu,raw_exec
Node Events
Time Subsystem Message
2019-04-26T17:57:41+02:00 Cluster Node registered
Allocated Resources
CPU Memory Disk
0/30400 MHz 0 B/31 GiB 0 B/16 GiB
Allocation Resource Utilization
CPU Memory
0/30400 MHz 0 B/31 GiB
Host Resource Utilization
CPU Memory Disk
3288/30400 MHz 5.2 GiB/31 GiB (tmpfs)
Allocations
No allocations placed
$ nomad node status -verbose 44511e01
ID = 44511e01-34b8-c1e6-7fe5-60be0ff35d0e
Name = archlinux
Class = <none>
DC = localhost
Drain = false
Eligibility = eligible
Status = ready
Uptime = 1h44m39s
Drivers
Driver Detected Healthy Message Time
docker true true Healthy 2019-04-26T17:57:41+02:00
exec true true Healthy 2019-04-26T17:57:41+02:00
java true true Healthy 2019-04-26T17:57:41+02:00
qemu true true Healthy 2019-04-26T17:57:41+02:00
raw_exec true true Healthy 2019-04-26T17:57:41+02:00
rkt false false Failed to execute rkt version: exec: "rkt": executable file not found in $PATH 2019-04-26T17:57:41+02:00
Node Events
Time Subsystem Message Details
2019-04-26T17:57:41+02:00 Cluster Node registered <none>
Allocated Resources
CPU Memory Disk
100/30400 MHz 1.0 GiB/31 GiB 300 MiB/16 GiB
Allocation Resource Utilization
CPU Memory
19/30400 MHz 38 MiB/31 GiB
Host Resource Utilization
CPU Memory Disk
4564/30400 MHz 5.5 GiB/31 GiB (tmpfs)
Allocations
ID Eval ID Node ID Task Group Version Desired Status Created Modified
cd45371d-501a-1373-dfde-bb16c4ff20d3 ab9f5675-b5cb-9e5f-8e20-cb308dbfba32 44511e01-34b8-c1e6-7fe5-60be0ff35d0e services 3 run running 2019-04-26T18:14:28+02:00 2019-04-26T18:16:32+02:00
Attributes
consul.datacenter = localhost
consul.revision = ea5210a30
consul.server = true
consul.version = 1.4.4
cpu.arch = amd64
cpu.frequency = 3800
cpu.modelname = Intel(R) Core(TM) i5-8259U CPU @ 2.30GHz
cpu.numcores = 8
cpu.totalcompute = 30400
driver.docker = 1
driver.docker.bridge_ip = 172.17.0.1
driver.docker.os_type = linux
driver.docker.runtimes = runc
driver.docker.version = 18.09.4-ce
driver.docker.volumes.enabled = true
driver.exec = 1
driver.java = 1
driver.java.runtime = OpenJDK Runtime Environment (build 1.8.0_212-b01)
driver.java.version = 1.8.0_212
driver.java.vm = OpenJDK 64-Bit Server VM (build 25.212-b01, mixed mode)
driver.qemu = 1
driver.qemu.version = 3.1.0
driver.raw_exec = 1
kernel.name = linux
kernel.version = 5.0.7-arch1-1-ARCH
memory.totalbytes = 33592107008
nomad.advertise.address = 127.0.0.1:4646
nomad.revision = 18dd59056ee1d7b2df51256fe900a98460d3d6b9
nomad.version = 0.9.0
os.name = arch
os.signals = SIGQUIT,SIGTTOU,SIGFPE,SIGTRAP,SIGTSTP,SIGINT,SIGURG,SIGTTIN,SIGUSR1,SIGIO,SIGIOT,SIGKILL,SIGSTOP,SIGCONT,SIGILL,SIGPROF,SIGSEGV,SIGSYS,SIGTERM,SIGXFSZ,SIGHUP,SIGWINCH,SIGABRT,SIGBUS,SIGCHLD,SIGPIPE,SIGUSR2,SIGXCPU,SIGALRM
unique.cgroup.mountpoint = /sys/fs/cgroup
unique.consul.name = archlinux
unique.hostname = archlinux
unique.network.ip-address = 127.0.0.1
unique.storage.bytesfree = 16795955200
unique.storage.bytestotal = 16796053504
unique.storage.volume = tmpfs
Meta
|
nomad-status.sh
En este caso solo hay un nodo registrado en Nomad, la siguiente definición de job en el fragmento constraint hace que Nomad lo ubique siempre en él.
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
|
job "mongodb" {
datacenters = ["localhost"]
type = "service"
constraint {
attribute = "${node.unique.id}"
value = "44511e01-34b8-c1e6-7fe5-60be0ff35d0e"
}
group "services" {
count = 1
task "mongodb" {
driver = "docker"
config {
image = "mongo:latest"
port_map {
port = 27017
}
volumes = [
"/home/picodotdev/Software/nomad/mongodb:/data/db/"
]
}
resources {
memory = 1024 # MB
network {
port "port" {}
}
}
}
}
}
|
mongodb.nomad
Como el job se ubica en el mismo nodo siempre montando un directorio del nodo como un volumen de datos en el job y contenedor de Docker, los datos se persisten en el sistema de archivos y transcienden al tiempo de vida del job, se puede iniciar el job, insertar datos en la base de datos en este caso de MongoDB, eliminar el job, volverlo a iniciar y los mismos datos están presentes en MongoDB.
1
2
|
$ nomad job run mongodb.nomad
|
nomad-job-run.sh
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
|
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b0d3f42c92fc mongo:latest "docker-entrypoint.s…" 3 minutes ago Up 3 minutes 127.0.0.1:20180->27017/tcp, 127.0.0.1:20180->27017/udp mongodb-ea10d440-1176-3bfb-5301-7ccd17af0281
$ docker exec -it b0d3f42c92fc bash
root@b0d3f42c92fc:/# mongo
MongoDB shell version v4.0.9
connecting to: mongodb://127.0.0.1:27017/?gssapiServiceName=mongodb
Implicit session: session { "id" : UUID("ba120679-b965-49d0-a774-dff39d6b630a") }
MongoDB server version: 4.0.9
Server has startup warnings:
2019-04-26T16:47:49.308+0000 I STORAGE [initandlisten]
2019-04-26T16:47:49.308+0000 I STORAGE [initandlisten] ** WARNING: Using the XFS filesystem is strongly recommended with the WiredTiger storage engine
2019-04-26T16:47:49.308+0000 I STORAGE [initandlisten] ** See http://dochub.mongodb.org/core/prodnotes-filesystem
2019-04-26T16:47:50.133+0000 I CONTROL [initandlisten]
2019-04-26T16:47:50.133+0000 I CONTROL [initandlisten] ** WARNING: Access control is not enabled for the database.
2019-04-26T16:47:50.133+0000 I CONTROL [initandlisten] ** Read and write access to data and configuration is unrestricted.
2019-04-26T16:47:50.133+0000 I CONTROL [initandlisten]
---
Enable MongoDB's free cloud-based monitoring service, which will then receive and display
metrics about your deployment (disk utilization, CPU, operation statistics, etc).
The monitoring data will be available on a MongoDB website with a unique URL accessible to you
and anyone you share the URL with. MongoDB may use this information to make product
improvements and to suggest MongoDB products and deployment options to you.
To enable free monitoring, run the following command: db.enableFreeMonitoring()
To permanently disable this reminder, run the following command: db.disableFreeMonitoring()
---
> db.articles.insert({title: "Introducción a la base de datos NoSQL MongoDB", author: "picodotdev", date: new Date(2017,05,18,12,30), tags: ['mongodb', 'database', 'NoSQL'], comments: [{user: "jones", message: "MongoDB is great!"}, {user: "lina", message: "MongoDB is great!"}]})
WriteResult({ "nInserted" : 1 })
> db.articles.insert({title: "Introducción a la base de datos relacional PostgreSQL", author: "picodotdev", date: new Date(2017,05,17,12,00), likes: 100, tags: ['postgresql', 'database', 'SQL'], comments: [{user: "katy", message: "PostgreSQL rocks!"}, {user: "smith", message: "SQL language is powerful!"}]})
WriteResult({ "nInserted" : 1 })
> db.articles.find()
{ "_id" : ObjectId("5cc335873b17081f2ca1d4d5"), "title" : "Introducción a la base de datos NoSQL MongoDB", "author" : "picodotdev", "date" : ISODate("2017-06-18T12:30:00Z"), "tags" : [ "mongodb", "database", "NoSQL" ], "comments" : [ { "user" : "jones", "message" : "MongoDB is great!" }, { "user" : "lina", "message" : "MongoDB is great!" } ] }
{ "_id" : ObjectId("5cc335993b17081f2ca1d4d6"), "title" : "Introducción a la base de datos relacional PostgreSQL", "author" : "picodotdev", "date" : ISODate("2017-06-17T12:00:00Z"), "likes" : 100, "tags" : [ "postgresql", "database", "SQL" ], "comments" : [ { "user" : "katy", "message" : "PostgreSQL rocks!" }, { "user" : "smith", "message" : "SQL language is powerful!" } ] }
> db.articles.count()
2
> exit
bye
root@b0d3f42c92fc:/# exit
exit
$ nomad job stop --purge mongodb
==> Monitoring evaluation "f10589c6"
Evaluation triggered by job "mongodb"
Evaluation status changed: "pending" -> "complete"
==> Evaluation "f10589c6" finished with status "complete"
|
mongodb.sh
1
2
|
$ nomad job stop --prune mongodb
|
nomad-job-stop.sh
Para iniciar Consul y Nomad hay que utilizar los siguientes comandos y para el ejecutar job es requisito haber instalado Docker dado que en este ejemplo lo utiliza.
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
|
$ consul agent -dev -datacenter localhost
==> Starting Consul agent...
==> Consul agent running!
Version: 'v1.4.4'
Node ID: '34294bf0-5802-0d94-4acd-cf8c9d090205'
Node name: 'archlinux'
Datacenter: 'localhost' (Segment: '<all>')
Server: true (Bootstrap: false)
Client Addr: [127.0.0.1] (HTTP: 8500, HTTPS: -1, gRPC: 8502, DNS: 8600)
Cluster Addr: 127.0.0.1 (LAN: 8301, WAN: 8302)
Encrypt: Gossip: false, TLS-Outgoing: false, TLS-Incoming: false
$ sudo nomad agent -dev -dc localhost
[sudo] password for picodotdev:
==> No configuration files loaded
==> Starting Nomad agent...
==> Nomad agent configuration:
Advertise Addrs: HTTP: 127.0.0.1:4646; RPC: 127.0.0.1:4647; Serf: 127.0.0.1:4648
Bind Addrs: HTTP: 127.0.0.1:4646; RPC: 127.0.0.1:4647; Serf: 127.0.0.1:4648
Client: true
Log Level: DEBUG
Region: global (DC: localhost)
Server: true
Version: 0.9.0
|
consul-nomad.sh
Las restricciones se han de cumplir para elegir un nodo, por otro lado está también la afinidad. La afinidad es una preferencia utilizada por Nomad al seleccionar los nodos que tratará de cumplir si hay algún nodo disponible con las propiedades de afinidad deseadas pero si no hay un nodo disponible se elige algún otro.