Estrategias de despliegue para microservicios con Nomad

Escrito por el , actualizado el .
planeta-codigo programacion software
Comentarios

Ciertos servicios requieren que al hacer un despliegue el servicio continue funcionando. Para esto no es posible parar todas las instancias de un servicio a la vez, actualizarlar y volverlas a iniciar porque durante este proceso se dejaría de prestar el servicio durante un corto periodo de tiempo en el mejor de los casos. Hay que hacer el despliegue de forma progresiva en las instancias. Algunas estrategias son Rolling Update, Blue/Green y Canary, el orquestador de servicios Nomad soporta y realiza de forma automatizada los despliegues usando una de estas estrategias.

Nomad
HashiCorp

El ciclo de vida de una aplicación no consiste solo en desarrollarla, incluye también su puesta en producción o despliegue en un entorno de pruebas, pero también una vez la aplicación está desplegada en algún momento será necesario actualizarla con una nueva versión.

Las aplicaciones monolíticas tienen otros problemas pero en el aspecto de despliegue es sencillo ya que solo hay una aplicación, basta con desplegar la nueva versión. En una aplicación con arquitectura de microservicios es un reto mayor debido a que hay múltiples aplicaciones.

En cualquiera de ellas puede darse el caso de que para ganar en escalabilidad o para aumentar la disponibilidad o tolerancia a fallos es posible que haya varias instancias, las cuales han de ser actualizadas con el requisito si es necesario de que el servicio no deje de prestar su servicio, es decir que el despliegue no suponga una caída del servicio.

Hay varias estrategias para desplegar una nueva versión de una aplicación:

  • Rolling update: actualizar todas las instancias de forma progresiva. Una vez se termina de actualizar una se espera un tiempo y se actualiza la siguiente hasta que todas estén actualizadas.
  • Blue/Green: manteniendo en funcionamiento las instancias con la versión antigua se crea el mismo número de instancias con la nueva versión y se redirige tráfico hacia ellas. Una vez se ha comprobado que la nueva versión funciona correctamente se promociona la nueva versión y se eliminan las instancias de con la versión antigua. Esta estrategia permite volver a la versión anterior rápidamente si se detecta algún problema.
  • Canary: se siguen manteniendo las instancias con la versión antigua, a diferencia de la estrategia blue/green se crea un número menor de instancias con la versión nueva que el número de instancias con la versión antigua. Una vez comprobado que la nueva versión es correcta se promociona la nueva versión y se actualizan todas las instancias restantes mediante rolling update a la nueva versión. También permite volver a la versión antigua si se detecta algún problema.

Docker Swarm permite la estrategia de despliegue rolling update sin embargo las estrategias blue/green y canary son interesante para tratar de que un error en una versión nueva no afecte al funcionamiento de la aplicación y obligue hacer un rollback que posiblemente tarde más tiempo durante el cual el servicio funcionará con el defecto descubierto. Nomad permite despliegues con las estrategias blue/gree y canary.

Para actualizar un servicio en Nomad basta con modificar la definición del job y enviarlo a Nomad, y este se encarga de orquestar la actualización en las instancias según la estrategia de despliegue configurada. En este caso se actualiza la versión de nginx de la versión nginx:stable-alpine a nginx:alpine usando una estrategia rolling update para las cinco instancias del servicio.

La estrategia de despliegue en Nomad se define en la sección de configuración update. El parámetro min_healthy_time es el tiempo que se espera cuando se hace un rolling update para considerar una instancia como sana y continuar la actualización con la siguiente, max_parallel indica el número de instancias que se migran al mismo tiempo. El parámetro canary indica el número de instancias que se crean en las estrategias blue/green y canary, en la primera el número de instancias coincidirá con el parámetro canary que indica el número de instancias de un servicio. Nomad con los parámetros health_check, min_healthy_time, healthy_deadline, progress_deadline, stagger y auto_revert se puede poner unos límites para considerar válido un despliegue y en caso de no serlo realizar un rollback de forma autmática.

 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
job "nginx" {
  datacenters = ["localhost"]

  type = "service"

  update {
    min_healthy_time = "15s"
    max_parallel = 1
  }

  group "services" {
    count = 5

    task "nginx" {
      driver = "docker"

      config {
        image = "nginx:stable-alpine"
        port_map {
          http = 80
        }
      }

      resources {
        memory = 1024 # MB

        network {
          port "http" {}
        }
      }
    }
  }
}
1
2
$ nomad job run nginx.nomad

En el caso de los despliegues blue/green y canary una vez comprobado que la versión de los nuevos servicios funcionan correctamente se promocionan y actualizan el resto de instancias en el caso de canary o se detienen las instancias antiguas en el caso de blue/green.

1
2
$ nomad job promote nginx

Desde la línea de comandos se puede observar el estado del servicio y el proceso de actualización, el primero es el estado previo a realizar el despliegue, el segundo durante el proceso de actualización con rolling update y el tercero una vez finalizado el proceso de despliegue y marcado como exitoso en el que todas las instancias han pasado de la versión 0 a la 1.

 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
$ nomad job status nginx
ID            = nginx
Name          = nginx
Submit Date   = 2019-04-18T19:13:07+02:00
Type          = service
Priority      = 50
Datacenters   = localhost
Status        = running
Periodic      = false
Parameterized = false

Summary
Task Group  Queued  Starting  Running  Failed  Complete  Lost
services    0       0         5        0       0         0

Latest Deployment
ID          = 81f57b6d
Status      = successful
Description = Deployment completed successfully

Deployed
Task Group  Desired  Placed  Healthy  Unhealthy  Progress Deadline
services    5        5       5        0          2019-04-18T19:23:23+02:00

Allocations
ID        Node ID   Task Group  Version  Desired  Status   Created  Modified
3747eb07  d18851d5  services    0        run      running  29s ago  13s ago
500575e9  d18851d5  services    0        run      running  29s ago  13s ago
c8094cf3  d18851d5  services    0        run      running  29s ago  13s ago
ea58300c  d18851d5  services    0        run      running  29s ago  13s ago
ead6d23f  d18851d5  services    0        run      running  29s ago  13s ago

$ nomad alloc status 3747eb07
ID                  = 3747eb07
Eval ID             = 781fb5f2
Name                = nginx.services[3]
Node ID             = d18851d5
Job ID              = nginx
Job Version         = 0
Client Status       = running
Client Description  = Tasks are running
Desired Status      = run
Desired Description = <none>
Created             = 56s ago
Modified            = 40s ago
Deployment ID       = 81f57b6d
Deployment Health   = healthy

Task "nginx" is "running"
Task Resources
CPU        Memory           Disk     Addresses
0/100 MHz  788 KiB/1.0 GiB  300 MiB  http: 127.0.0.1:29939

Task Events:
Started At     = 2019-04-18T17:13:08Z
Finished At    = N/A
Total Restarts = 0
Last Restart   = N/A

Recent Events:
Time                       Type        Description
2019-04-18T19:13:08+02:00  Started     Task started by client
2019-04-18T19:13:07+02:00  Task Setup  Building Task Directory
2019-04-18T19:13:07+02:00  Received    Task received by client
 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
$ nomad job status nginx
ID            = nginx
Name          = nginx
Submit Date   = 2019-04-18T19:17:54+02:00
Type          = service
Priority      = 50
Datacenters   = localhost
Status        = running
Periodic      = false
Parameterized = false

Summary
Task Group  Queued  Starting  Running  Failed  Complete  Lost
services    0       0         5        0       5         0

Latest Deployment
ID          = ba20066a
Status      = successful
Description = Deployment completed successfully

Deployed
Task Group  Desired  Placed  Healthy  Unhealthy  Progress Deadline
services    5        5       5        0          2019-04-18T19:29:19+02:00

Allocations
ID        Node ID   Task Group  Version  Desired  Status    Created    Modified
fabcf384  d18851d5  services    1        run      running   2m36s ago  2m20s ago
ccb57008  d18851d5  services    1        run      running   2m53s ago  2m37s ago
b06c743d  d18851d5  services    1        run      running   3m10s ago  2m54s ago
56733896  d18851d5  services    1        run      running   3m28s ago  3m12s ago
71c8bb5b  d18851d5  services    1        run      running   3m45s ago  3m29s ago
500575e9  d18851d5  services    0        stop     complete  8m31s ago  3m44s ago
c8094cf3  d18851d5  services    0        stop     complete  8m31s ago  3m10s ago
3747eb07  d18851d5  services    0        stop     complete  8m31s ago  2m53s ago
ea58300c  d18851d5  services    0        stop     complete  8m31s ago  3m27s ago
ead6d23f  d18851d5  services    0        stop     complete  8m31s ago  2m35s ago
Progreso del despliegue rolling update en Nomad

El proceso de despliegue también se puede monitorizar desde la interfaz web que ofrece Nomad.

Progreso del despliegue rolling update en Nomad

En este ejemplo los servicios están en contenedores docker, también se observa que la versión de los contenedores en ejecución pasan de la versión stable-alpine a alpine.

1
2
3
4
5
6
7
$ docker ps
CONTAINER ID        IMAGE                 COMMAND                  CREATED              STATUS              PORTS                                              NAMES
476d2063b64b        nginx:stable-alpine   "nginx -g 'daemon of…"   About a minute ago   Up About a minute   127.0.0.1:29939->80/tcp, 127.0.0.1:29939->80/udp   nginx-3747eb07-f9da-a9f5-0720-1b2314baac12
e0a533348f44        nginx:stable-alpine   "nginx -g 'daemon of…"   About a minute ago   Up About a minute   127.0.0.1:21085->80/tcp, 127.0.0.1:21085->80/udp   nginx-ea58300c-c4a1-cc3d-46a0-d5b30c276ede
4da9babdd549        nginx:stable-alpine   "nginx -g 'daemon of…"   About a minute ago   Up About a minute   127.0.0.1:30061->80/tcp, 127.0.0.1:30061->80/udp   nginx-c8094cf3-5c3c-eaaa-1bcf-9368100bb0ef
068c6db6a86c        nginx:stable-alpine   "nginx -g 'daemon of…"   About a minute ago   Up About a minute   127.0.0.1:26606->80/tcp, 127.0.0.1:26606->80/udp   nginx-ead6d23f-abdd-8b33-7b61-c2ad64dede5c
19190e778a5a        nginx:stable-alpine   "nginx -g 'daemon of…"   About a minute ago   Up About a minute   127.0.0.1:29835->80/tcp, 127.0.0.1:29835->80/udp   nginx-500575e9-62ce-868a-f142-869475aacde5
1
2
3
4
5
6
7
$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED              STATUS              PORTS                                              NAMES
03faf7ed4467        nginx:alpine        "nginx -g 'daemon of…"   About a minute ago   Up About a minute   127.0.0.1:27212->80/tcp, 127.0.0.1:27212->80/udp   nginx-fabcf384-5675-fe7f-4c61-5fa7385c54d2
4185977fbddb        nginx:alpine        "nginx -g 'daemon of…"   2 minutes ago        Up 2 minutes        127.0.0.1:26686->80/tcp, 127.0.0.1:26686->80/udp   nginx-ccb57008-e426-1684-fd39-97cb0f3b51f7
c6586d22406e        nginx:alpine        "nginx -g 'daemon of…"   2 minutes ago        Up 2 minutes        127.0.0.1:23508->80/tcp, 127.0.0.1:23508->80/udp   nginx-b06c743d-d2b3-0be3-d82b-762184dda4ec
046253c1972b        nginx:alpine        "nginx -g 'daemon of…"   2 minutes ago        Up 2 minutes        127.0.0.1:31800->80/tcp, 127.0.0.1:31800->80/udp   nginx-56733896-ab20-ab8f-36c9-ac1d13b0f1a2
88788b4133ea        nginx:alpine        "nginx -g 'daemon of…"   3 minutes ago        Up 3 minutes        127.0.0.1:30844->80/tcp, 127.0.0.1:30844->80/udp   nginx-71c8bb5b-a23b-7edc-cbb3-f9d0b6bdffe6

Nomad y Consul se inician con los siguientes comandos en modo desarrollo comentados en el artículo Introducción a Nomad para gestionar aplicaciones y microservicios.

1
2
$ consul agent -dev -datacenter localhost
$ sudo nomad agent -dev -dc localhost