Las funcionalidades de un service mesh en una arquitectura de microservicios

Escrito por el .
programacion planeta-codigo
Enlace permanente Comentarios

Los monolitos son la opción más sencilla y mejor en la mayoría de casos, los microservicios solo son recomendables cuando los problemas que resuelven compensan los problemas asociados que generan, entre ellos una mayor complejidad en la infraestructura y de comunicación entre servicios que utilizan un medio con fallos, la red de comunicaciones entre diferentes computadoras. Esta mayor complejidad, mayor número de servicios y mayor probabilidad de que alguna falle requiere utilizar herramientas para en el sistema tener seguridad, observabilidad, resiliencia y control de tráfico. Estas son funcionalidades que como una capa de red para los servicios proporciona un service mesh.

HashiCorp Consul

HashiCorp

Los sistemas basados en microservicios tienen sus ventajas como utilizar servicios independientes que pueden ser desarrollados y desplegados de forma independiente incluso por distintos equipos de desarrolladores al menos mientras solo se cambien sus detalles de implementación y mientras sus interfaces de comunicación no cambien. Otras ventajas son una mejor escalabilidad y mejor tolerancia a errores.

Un monolito es más que suficiente en muchos casos y los microservicios solo son necesarios en sistemas grandes y complejos formados por varios equipos de desarrollo, escalabilidad y tolerancia a fallos. Cuando tiene sentido utilizar un sistema basado en los microservicios se resuelve algunos problemas de los monolitos pero surgen otros propios de los microservicios tan o más complicados que los de los monolitos.

Uno de los mayores problemas de los microservicios es que el sistema es más complejo, formado por un conjunto mayor de piezas individuales que han de colaborar para proporcionar la funcionalidad global, han de tener en cuenta problemas que en las aplicaciones monolíticas no tienen, los microservicios son independientes pero se relacionan entre ellos a través de la red un medio de comunicación que es poco fiable comparado con la llamada a una función dentro de la misma computadora en un monolito.

Para tener bajo control las complicaciones de los microservicios son necesarias herramientas que hagan del sistema más fácil de mantener y desarrollar. Una de las herramientas específicamente desarrollada para los microservicios es Consul de HashiCorp que proporciona varias funcionalidades como descubrimiento de servicios y un almacén compartido de información. Otra de las funcionalidades que implementa y provee Consul es el concepto de service mesh que proporciona seguridad, observabilidad, resiliencia y control de tráfico.

Qué es un service mesh

La definición de Consul de un service mesh es una capa de red dedicada que proporciona seguridad en la comunicación de servicio a servicio dentro y a través de la infraestructura, incluyendo el sistemas propios y entornos en la nube. Un service mesh es utilizado habitualmente en arquitecturas basadas en microservicios pero son útiles en cualquier escenario en que hay una comunicación por red compleja.

Los microservicios se comunican entre ellos formado una red distribuida, un service mesh es el nombre que se le da a este tipo de comunicación. En el aspecto de las herramientas de infraestructura para facilitar y dar solución a varios de los problemas de la comunicación por red un service mesh trata todos estos problemas y aporta soluciones.

En gran medida la solución a los problemas de los microservicios en un service mesh es implementada estableciendo un intermediario proxy en la comunicación entre los servicios siendo en este intermediario denominado sidecar proxy el que implementa la solución. Se denomina sidecar proxy ya que al mismo tiempo que se despliega el servicio se despliega el intermediario adicionalmente para la comunicación entre los diferentes servicios que usa y los servicios en vez de comunicarse entre ellos directamente se comunican con el sidecar proxy.

El service mesh a través de los proxy sidecar permiten delegar en la infraestructura y no en las aplicaciones varios aspectos que son necesarios resolver en un sistema basado en microservicios y todo ello de forma trasnparente y sin tener que hacer modificaciones en los microservicios más a llá de configuración de puertos.

Seguridad

La computación en la nube de los proveedores de infraestructura como Amazon AWS, Google Cloud y Microsoft Azure no dejan de ser entornos compartidos de computación donde todos aun con las medidas de seguridad y lógicas utilizan los mismos medios de red y computación. Por eso se considera que la computación en la nube utiliza un medio no confiable en el que el descubrimiento de un fallo de seguridad tan importante como el fallo de seguridad Meltdown y Spectre de los procesadores aún con las medidas de mitigación implementadas es un grave problema.

Para proporcionar mayor seguridad en las comunicaciones de los microservicios en un medio no confiable Consul ofrece una forma de comunicación cifrada, con autenticación mutua entre ambos servicios y la gestión de la autorización en un plano lógico a nivel de servicio con el mecanismo de intenciones y no de más bajo nivel de direcciones IP, puertos y reglas de firewall.

La seguridad entre los servicios en Consul es implementada mediante el componente denominado sidecar proxy que hace de intermediario o proxy entre los dos servicios. Consul administra el sidecar proxy y este proporciona las funcionalidades comentadas de cifrado, autenticación mutua y autorización. Toda comunicación se realiza de forma cifrada y con autenticación mutua ningún servicio ajeno al service mesh puede comunicarse con sus servicios, para que un servicio externo al service mesh pueda comunicarse con un servicio del service mesh el tráfico entra por un ingress gateway que es un servicio dedicado a esta función.

Una gran característica es que todas estas funcionalidades son proporcionadas sin que los servicios sean conscientes de que realmente la comunicación la están realizando a través de los sidecar proxy.

En GNU/Linux la seguridad a nivel de red en la misma máquina en la comunicación entre el servicio y el sidecar proxy se protege con namespaces de red que proporciona Linux.

Observabilidad

Dado el gran número de elementos de una arquitectura de microservicios es imprescindible tener visibilidad del estado y comportamiento de cada uno de ellos. Se necesitan métricas que permitan conocer el estado normal de los servicios y en caso de obtener unas métricas diferentes saber que está ocurriendo en el sistema en su conjunto y en cada pieza individual.

Varias medidas de observabilidad es posible implementarlas en los propios microservicios como parte de su implementación de código, sin embargo, añadir código a cada uno de los microservicios requiere hacerlo individualmente, los hace más complicados y dependientes del lenguaje de implementación para cada uno de ellos para una funcionalidad de infraestructura que no está relacionada con la lógica de negocio que proporcionan los microservicios.

Por ello en la medida de lo posible es mejor delegar la observabilidad a la infraestructura que además permite aplicar la observabilidad de forma homogénea y con las mismas herramientas para todos los microservicios. Algunas métricas de observabilidad son el número de peticiones que está recibiendo un servicio por unidad de tiempo, la tasa de errores o la latencia de red en diferente percentiles.

Dado que todo el tráfico entre los microservicios fluye a través de los sidecar que hacen de proxy Consul es capaz de proporcionar estas métricas de forma independiente a los microservicios. Consul exporta el formato de Prometheus las métricas, proporciona una visualización básica de las mismas y con Grafana es posible obtener visualizaciones de los datos más detalladas y complejas.

Para obtener el flujo completo desde que entra en el service mesh y fluye entre las diferentes llamadas entre los microservicios Jaeger con Zipkin o OpenTelemetry permite obtener una foto de todos los pasos y saltos que ha tenido una petición entre los diferentes servicios.

Resiliencia

Dado el número de servicios y el número de instancias de cada uno de ellos y la no completa fiabilidad de un medio de red que puede fallar es necesario que los microservicios sean resilientes ante la aparición de estos fallos.

La resiliencia se puede implementar de forma activa donde Consul comprueba que cada servicio mediante un health check esté funcionando ya sea realizando una petición HTTP o TCP al servicio que si responde correctamente se considera que el servicio tiene un buen estado de funcionamiento.

Los health check no son infalibles y los microservicios para aumentar su resiliencia pueden implementar otras medidas adicionales como reintentos o retries realizando peticiones con la esperanza de que sean problemas temporales y en un siguiente intento funcione. Otra medida son los timeout para poner límites a los tiempos de espera de modo que si un microservicio tarda en responder no ocasiones fallos en otros microservicios y el sistema caiga en cascada. Estas son medidas pasivas que implementan cada microservicio.

Consul implementa medidas de resiliencia tanto activas como pasivas, y en caso de que una instancia de un servicio falle redirigir el tráfico a otras instancias con buen estado de salud, este enrutamiento se realiza mediante el control de tráfico.

Control de tráfico

Al ser los microservicios independientes estos se despliegan de forma independiente mientras los cambios sean solo de implementación. Al desplegar una nueva versión de un microservicio es muy útil tener la capacidad del enrutamiento de las peticiones entre los microservicios para usar diferentes estrategias de despliegue como rollout, canary, blue/green y cambiar el enrutamiento.

Consul proporciona tres conceptos para controlar el enrutamiento entre los servicios los resolvers que identifican las instancias de los servicios en grupos, splitters que permite establecer la cantidad de tráfico que es enviada a los grupos de servicios y finalmente los routers que permiten mediante reglas de la petición o de la instancia del servicio a que servicio se le envía el tráfico.

Ejemplo práctico del service mesh de Consul

En el libro Consul: Up & Running se explica más detalladamente los puntos anteriores junto con un ejemplo con los necesarias configuraciones para Consul de cada uno de ellos utilizando Kubernetes y máquinas virtuales.

En el libro se usa simplemente Consul, en este artículo muestro la misma configuración y práctica de los conceptos comentados pero usando Nomad el orquestador de procesos de HashiCorp equivalente a Kubernetes pero más simple de usar aunque no tan nombrado como Kuberntes. Lo ejecuto dentro de una máquina virtual de VirtualBox aprovisionada y creada con Vagrant otro producto de HashiCorp.

El ejemplo consiste en dos procesos uno frontend que muestra la información de una guía de aves y un servicio de backend que devuelve los datos de las aves. Los procesos están empaquetados en sus imágenes de Docker. Ambas aplicaciones se comunican mediante una API REST y permiten probar en la práctica todos los conceptos comentados.

Creación de la máquina virtual

Docker es un software desarrollado para GNU/Linux que en otros sistemas no funciona de forma nativa y requiere adaptaciones y virtualización, para poder ejecutar el ejemplo en cualquier sistema operativo y no instalar nada en la máquina local más allá de VirtualBox y Vagrant se utiliza una máquina virtual que permiten crear estas dos herramientas de forma fácil.

El siguiente es el archivo de configuración para Vagrant de la máquina virtual que aprovisiona una distribución Ubuntu con las herramientas utilizadas en el ejemplo entre ellas Consul, Nomad, Envoy y Docker. También se configura la cantidad de memoria asignada a la máquina virtual y la dirección IP que se le asigna.

Al iniciar la máquina virtual se aprovisiona, con Vagrant simplemente consiste en ejecutar el siguiente comando. Una vez iniciada se puede hacer SSH para iniciar una sesión de terminal dentro de la máquina virtual.

1
2
$ vagrant up
$ vagrant ssh
vagrant.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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# -*- mode: ruby -*-
# vi: set ft=ruby :

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

    config.vm.define "ubuntu" do |instance|
      instance.vm.box = "ubuntu/jammy64"
      instance.vm.network "private_network", ip: "192.168.56.10"
      instance.vm.synced_folder ".", "/home/vagrant/ConsulServiceMesh/"

      instance.vm.provider :virtualbox do |vb|
        vb.name = "Ubuntu 22.04 (Vagrant)"
        vb.memory = "4096"

        instance.vm.provision "shell", inline: $consul_role_script
      end
    end

    $consul_role_script = <<-SCRIPT
      echo "Updating..."
      sudo apt-get update
      sudo apt-get upgrade

      echo "Installing Consul..."
      wget -O- https://apt.releases.hashicorp.com/gpg | gpg --dearmor | sudo tee /usr/share/keyrings/hashicorp-archive-keyring.gpg > /dev/null
      echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
      sudo apt-get update && sudo apt-get install consul

      echo "Installing Nomad..."
      wget -O- https://apt.releases.hashicorp.com/gpg | gpg --dearmor | sudo tee /usr/share/keyrings/hashicorp-archive-keyring.gpg  > /dev/null
      echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
      sudo apt-get update && sudo apt-get install nomad

      echo "Installing Envoy..."
      curl -L https://func-e.io/install.sh | sudo bash -s -- -b /usr/local/bin && func-e use 1.23.0 && sudo cp ~/.func-e/versions/1.23.0/bin/envoy /usr/local/bin/

      echo "Installing cni plugins..."
      curl -L -o cni-plugins.tgz https://github.com/containernetworking/plugins/releases/download/v1.1.1/cni-plugins-linux-amd64-v1.1.1.tgz
      sudo mkdir -p /opt/cni/bin
      sudo tar -C /opt/cni/bin -xzf cni-plugins.tgz

      echo 1 | sudo tee /proc/sys/net/bridge/bridge-nf-call-arptables
      echo 1 | sudo tee /proc/sys/net/bridge/bridge-nf-call-ip6tables
      echo 1 | sudo tee /proc/sys/net/bridge/bridge-nf-call-iptables

      sudo cat << EOF > /etc/sysctl.d/
  net.bridge.bridge-nf-call-arptables = 1
  net.bridge.bridge-nf-call-ip6tables = 1
  net.bridge.bridge-nf-call-iptables = 1
  EOF

      echo "Installing Docker..."
      curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker.gpg
      echo \
          "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
          $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
      sudo apt-get update && sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin && sudo systemctl enable docker.service
      sudo usermod -aG docker vagrant
    SCRIPT
  end
  
Vagrantfile

VirtualBox

VirtualBox

Inicio de Consul y Nomad y microservicios del ejemplo

Una de las ventajas de Consul y Nomad es que son muy simples de usar ya que sus programas son simplemente un único binario. Consul y Nomad se inician con los siguientes comandos en modo desarrollo con sus archivos de configuración.

1
2
$ consul agent -dev -config-dir=consul.d/

consul.sh
1
2
$ sudo nomad agent -dev-connect -config=nomad.d/

nomad.sh
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
server = true
bootstrap_expect = 1
bind_addr = "127.0.0.1"
client_addr = "0.0.0.0"

ports {
    grpc = 8502
}

connect {
    enabled = true
}

ui_config {
    enabled = true
    metrics_provider = "prometheus"
    metrics_proxy {
        base_url = "http://localhost:9090"
    }
}
consul.d/consul.hcl
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
plugin "docker" {
  config {
    volumes {
      enabled = true
    }
  }

  volumes {
    enabled = true
  }
}
nomad.d/nomad.hcl

Consul requiere la definición del servicio ingress gateway que permite que entre tráfico al service mesh, en esta configuración a través del puerto 8080, el tráfico entrante es redirigido al servicio frontend. Esta configuración se proporciona como un Config Entry de Consul y se proporciona a Consul con un comando.

1
2
3
$ consul config write consul.d/config-entries/proxy-defaults.hcl
$ consul config write consul.d/config-entries/ingress-gateway.hcl
$ consul connect envoy -gateway=ingress -service ingress-gateway -admin-bind 127.0.0.1:19002 -address 127.0.0.1:20000
consul-ingress-gateway.sh
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Kind = "ingress-gateway"
Name = "ingress-gateway"

Listeners = [{
    Port = 8080
    Protocol = "http"
    Services = [{
        Name = "frontend"
        Hosts = ["localhost", "192.168.56.10"]
    }]
}]
consul.d/config-entries/ingress-gateway.hcl

La configuración de los sidecar proxy que crear Consul en este ejemplo es necesario para exportar métricas de Prometheus y enviar la trazabilidad a Jaeger usando Zipkin.

 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
Kind = "proxy-defaults"
Name = "global"

Config {
    protocol = "http"
    envoy_tracing_json = <<EOF
{
    "http":{
        "name":"envoy.tracers.zipkin",
        "typedConfig":{
            "@type":"type.googleapis.com/envoy.config.trace.v3.ZipkinConfig",
            "collector_cluster":"jaeger_collector",
            "collector_endpoint_version":"HTTP_JSON",
            "collector_endpoint":"/api/v2/spans",
            "shared_span_context":false
        }
    }
}
EOF

     envoy_extra_static_clusters_json = <<EOF
{
     "connect_timeout":"3.000s",
     "dns_lookup_family":"V4_ONLY",
     "lb_policy":"ROUND_ROBIN",
     "load_assignment": {
          "cluster_name":"jaeger_collector",
          "endpoints":[{
              "lb_endpoints" :[{
                   "endpoint": {
                        "address": {
                            "socket_address": {
                                "address":"localhost",
                                "port_value":9411,
                                "protocol":"TCP"
                           }
                       }
                  }
             }]
        }]
    },
    "name":"jaeger_collector",
    "type":"STRICT_DNS"
}
EOF
}
consul.d/config-entries/proxy-defaults.hcl

Tanto Consul como Nomad ofrecen una interfaz web para consultar el estado de los servicios y realizar tareas de forma más intuitiva que utilizando la línea de comandos. La interfaz de Consul se inicia en el puerto 8500 y la de Nomad en el puerto 4646 la dirección IP es la de la máquina virtual.

Consul Nomad

Consul y Nomad

Inicio de Prometheus y Jaeger

Prometheus es una base de datos temporal que permite guardar los datos de métricas y monitorización que generan las aplicaciones, los sidecar proxy generan métricas que Prometheus recolecta y Consul permite mostrar algunos datos básicos de métricas como número de peticiones, tasa de fallos de las peticiones y percentiles de latencia.

Jaeger es una herramienta de trazabilidad que permite correlacionar todas las peticiones que se realizan entre los servicios, de esta forma es posible relacionar la petición del servicio frontend con la petición del servicio backend y mostrar ambas al mismo tiempo como si estuvieran relacionadas, esta relación se establece mediante un traceId que identifica al conjunto de las peticiones individuales.

En el ejemplo Prometheus y Jaeger se inician como contenedores de Docker con los siguientes comandos. También se podrían haber iniciado como servicios de Nomad. La configuración de Prometheus es necesaria para recolectar las métricas de Consul.

1
2
$ docker run --rm -p 9090:9090 --name prometheus --network host -v $(pwd)/prometheus.yml:/etc/prometheus/prometheus.yml prom/prometheus

prometheus.sh
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
global:
  scrape_interval: 10s

scrape_configs:
- job_name: consul
  metrics_path: /metrics
  consul_sd_configs:
  - server: 'localhost:8500'
  relabel_configs:
  - source_labels:
    - __meta_consul_tagged_address_lan
    - __meta_consul_service_metadata_prometheus_port
    regex: '(.*);(.*)'
    replacement: '${1}:${2}'
    target_label: '__address__'
    action: 'replace'
prometheus.yml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
$ docker run --rm --name jaeger \
  -e COLLECTOR_ZIPKIN_HOST_PORT=:9411 \
  -e COLLECTOR_OTLP_ENABLED=true \
  -p "6831:6831/udp" \
  -p "6832:6832/udp" \
  -p "5778:5778" \
  -p "16686:16686" \
  -p "4317:4317" \
  -p "4318:4318" \
  -p "14250:14250" \
  -p "14268:14268" \
  -p "14269:14269" \
  -p "9411:9411" \
  jaegertracing/all-in-one:1.38
jaeger.sh

Jaeger ofrece una interfaz web en el puerto 16686 para visualizar la trazabilidad de una petición y buscar una traza en concreto a través de su traceId y otros filtros.

Jaeger

Jaeger

Inicio de los servicios frontend y backend

La aplicación de ejemplo que muestra información de aves está compuesta de dos microservicios el frontend que muestra la información mediante una interfaz web y el servicio de backend que proporciona la información al frontend.

En el libro Consul: Up & Running los servicios se inician utilizando únicamente Consul que es en lo que se centra el libro, pero en este ejemplo inicio estos servicios utilizando el orquestador Nomad, Nomad permite definir la misma configuración incluyendo la configuración para los sidecar proxy y se integra con Consul.

La definición del job de Nomad que engloba ambos servicios es la siguiente. Con esta definición declarativa Nomad se encarga de iniciarlos según la descripción indicada, del servicio de backend se inician dos instancias una la de la versión v1 y otra de la v2.

1
2
$ nomad run birds.nomad

nomad-birds.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
 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
job "app-birds" {
  datacenters = ["dc1"]

  group "frontend" {
    network {
      mode = "bridge"

      port "http" {
        static = 21000
        to     = 21000
      }
      port "envoy_metrics" {
        static = 20200
        to = 20200
      }
    }

    service {
      name = "frontend"
      port = 6060

      meta {
        prometheus_port = "20200"
      }

      connect {
        sidecar_service {
          port = 21000

          proxy {
            upstreams {
              destination_name = "backend"
              local_bind_port = 6001
            }
            config {
              envoy_prometheus_bind_addr = "0.0.0.0:20200"
            }
          }
        }
      }
    }
    
    task "frontend" {
      driver = "docker"

      env {
        BIND_ADDR="0.0.0.0:6060"
        BACKEND_URL="http://localhost:6001"
        TRACING_URL="http://192.168.56.10:9411"
      }

      resources {
        cpu    = 350
        memory = 1024
      }

      config {
        image = "ghcr.io/consul-up/birdwatcher-frontend:1.0.0"
        memory_hard_limit = "1024"
      }
    }
  }


  group "backend" {
    network {
      mode = "bridge"

      port "envoy_metrics" {
        static = 20201
        to = 20201
      }
    }

    service {
      name = "backend"
      port = 7000
    
      meta {
        prometheus_port = "20201"
        version = "v1"
      }

      connect {
        sidecar_service {
          port = 22000
          proxy {
            config {
              envoy_prometheus_bind_addr = "0.0.0.0:20201"
            }
          }
        }
      }
    }
    
    task "backend" {
      driver = "docker"

      env {
        BIND_ADDR="0.0.0.0:7000"
        TRACING_URL="http://192.168.56.10:9411"
      }

      resources {
        cpu    = 350
        memory = 1024
      }

      config {
        image = "ghcr.io/consul-up/birdwatcher-backend:1.0.0"
        memory_hard_limit = "1024"
      }
    }
  }
  
  group "backend-v2" {
    network {
      mode = "bridge"

      port "envoy_metrics" {
        static = 20202
        to = 20202
      }
    }

    service {
      name = "backend"
      port = 7001
    
      meta {
        prometheus_port = "20202"
        version = "v2"
      }

      connect {
        sidecar_service {
          port = 22001
          proxy {
            config {
              envoy_prometheus_bind_addr = "0.0.0.0:20202"
            }
          }
        }
      }
    }

    task "backend" {
      driver = "docker"

      env {
        BIND_ADDR="0.0.0.0:7001"
        TRACING_URL="http://192.168.56.10:9411"
        VERSION="v2"
      }

      resources {
        cpu    = 350
        memory = 1024
      }

      config {
        image = "ghcr.io/consul-up/birdwatcher-backend:1.0.0"
        memory_hard_limit = "1024"
      }
    }
  }
}
birds.nomad

Frontend con datos de la versión v1 del backend

Frontend con datos de la versión v1 del backend

Demostraciones de las funcionalidades del service mesh

A la aplicación se accede a través del servicio de ingress-gateway en el puerto 8080 de la máquina virtual. El servicio de ingress-gateway simplemente redirige el tráfico a la aplicación de frontend. Esta es la topología de comunicación para el servicio de frontend proporcionada por Consul, recibe peticiones del ingress-gateway y a su vez realiza peticiones al servicio de backend.

Toppología de un servicio y métricas en Consul

Toppología de un servicio y métricas en Consul

Al arrancar Consul en modo desarrollo el comportamiento por defecto es permitir la comunicación entre los servicios, en una configuración en grado de producción por el contrario por defecto no se permite.

Creando una intención es posible denegar el tráfico entre el servicio de frontend al de backend. Las intenciones sirven para permitir o denegar el tráfico en un plano de servicio y no de direcciones IP y cortafuegos lo que lo hace mucho más sencillo de administrar, además de que cualquier cambio es posible hacerlo desde la interfaz web y se aplica de forma instantánea.

El tráfico entre los servicios a través de los sidecar proxy realizan la comunicación cifrada y utilizando autenticación mutua.

Intenciones en Consul

Intenciones en Consul

Consul exporta métricas en formato Prometheus que el servidor de Prometheus recolecta de forma periódica, esta información puede ser visualizada con una herramienta como Grafana. También el propio Consul permite obtener una métricas básicas del servicio como el número de peticiones que se están realizando, tasa de errores y latencia agrupadas por percentiles. Esta información es muy valiosa ya que permite monitorizar el estado del servicio.

Métricas de Prometheus en Consul

Métricas de Prometheus en Consul

Consul y los sidecar proxy de Envoy ofrecen soporte para trazabilidad con Zipkin junto con cierto soporte de los propios servicios la trazabilidad es enviada a un servidor como Jaeger que permite visualizar y relacionar las llamadas entre los diferentes servicios. Desde que entra por el ingress-gateway hasta que llega al backend pasando por el servicio de frontend asi como el tiempo empleado en cada uno de ellos.

Trazabilidad en Jarger

Trazabilidad en Jarger

Consul conoce y monitoriza el estado de todos los servicios, a los servicios que presenten un mal funcionamiento no se les envía tráfico para evitar errores y tener resiliencia aún con fallos parciales. Consul realiza peticiones de estado de salud de forma activa de forma periódica generalmente cada pocos segundos, para comprobar el estado de cada servicio. En la consola de administración muestra el estado de cada uno de ellos que también es una valiosa información de observabilidad.

Salud de los servicios en Consul

Salud de los servicios en Consul

Como son los sidecar proxy los que hacen las peticiones de red entre servicios estos tienen capacidad de realizar reintentos y timeouts. Con los reintentos se mitigan los errores temporales y con los timeouts se evita que un servicio que esté tardando en responder afecte a otros servicios.

El control de tráfico es utilizado para diferentes funciones una de ellas para el despliegue de una nueva versión de forma controlada utilizando diferentes estrategias de despliegue. El servicio de backend tiene dos versiones la v1 que da información de aves y la v2 que da información de aves pero solo de la familia de canarios.

Con el control de tráfico es posible distribuir el número de peticiones que va a cada servicio en función de un peso o porcentaje. En una repartición al 50 la mitad de las peticiones van a la v1 y la otra mitad a la v2, en el frontend se muestra la información del ave y la versión del servicio que la ha proporcionado. Cada vez que se cambia un archivo de configuración y se quiere aplicar hay que escribir la configuración en Consul.

1
2
3
$ consul config write consul.d/config-entries/backend-service-resolver.hcl
$ consul config write consul.d/config-entries/backend-service-splitter.hcl
$ consul config write consul.d/config-entries/backend-service-router.hcl
consul-traffic-control.sh
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Kind = "service-resolver"
Name = "backend"
Subsets = {
  v1 = {
    Filter = "Service.Meta.version == v1"
  }
  v2 = {
    Filter = "Service.Meta.version == v2"
  }
}
consul.d/config-entries/backend-service-resolver.hcl
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
Kind = "service-router"
Name = "backend"

Routes = [{
  Match = {
    HTTP = {
      QueryParam = [{
        Name = "canary"
        Exact = "true"
      }]
    }
  }

  Destination = {
    ServiceSubset = "v2"
  }
}]
consul.d/config-entries/backend-service-router.hcl
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Kind = "service-splitter"
Name = "backend"

Splits = [{
  Weight = 75
  ServiceSubset = "v1"
},
{
  Weight = 25
  ServiceSubset = "v2"
}]
consul.d/config-entries/backend-service-splitter.hcl

Frontend con datos de la versión v1 del backend Frontend con datos de la versión v2 del backend

Frontend con datos de la versión v1 y v2 del backend

Libros sobre Consul

Este artículo es prácticamente un resumen del libro Consul: Up & Running que muestra y explica todo esto de forma más detallada y extensa además de mostrarlo usando Kubernetes. Está bien explicado y su ejemplo es muy fácil de seguir y probar además de muy didáctico para experimentar con los conceptos y funcionalidades comentadas de Consul. Otros libros sobre Consul y service mesh son Simplifying Service Management with Consul y Mastering Service Mesh.

Terminal

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub.


Comparte el artículo: