Usa Istio Multicluster para "aumentar" las cargas de trabajo entre clústeres

1. Te damos la bienvenida

Gracias por unirte a nosotros en el codelab de Istio Multi Cloud Burst de Google.Para realizar este codelab, debes tener experiencia práctica a nivel principiante con Kubernetes, Node y Go.

Requisitos

  • Cuenta de Google Cloud Platform (usa una existente o te daremos cuentas gratuitas)
  • Tu laptop (instala "kubectl", "gcloud", etc.) o puedes usar Google Cloud Shell.

Qué aprenderá

  • Cómo crear un clúster de Kubernetes en GKE
  • Cómo instalar Istio en un clúster de Kubernetes con Helm
  • Cómo instalar Istio multiclúster con Helm
  • Implementa una aplicación web desde la fuente a Kubernetes
  • Cómo escribir y aplicar reglas de enrutamiento de tráfico a Istio
  • Métricas de Prometheus
  • Compila y envía imágenes de contenedores dentro de un clúster de Kubernetes

2. Cómo prepararte

Puedes seguir este codelab en cualquiera de los siguientes entornos:

  • Google Cloud Shell (recomendado): Shell integrado en el navegador, viene con herramientas instaladas
  • tu laptop (sigue las instrucciones que se indican a continuación)

Comienza con Google Cloud Platform

  1. Si no tienes una cuenta de GCP, pide al instructor que te entregue la tarjeta de cuenta de usuario gratuita.
  2. Ve a la consola de Google Cloud y haz clic en "Seleccionar un proyecto": 5c2d9bf74c78f7e4.png
  3. Toma nota del "ID" del proyecto en algún lugar y, luego, haz clic en el proyecto para elegirlo: ecc5e8e97bfa6559.png

Cloud Shell proporciona una shell de línea de comandos dentro de tu navegador con las herramientas que necesitas instaladas y autenticadas automáticamente en tu cuenta de Google Cloud Platform. (Si no deseas ejecutar este ejercicio en Cloud Shell, ve a la siguiente sección).

Ve a Cloud Console y haz clic en "Activar Cloud Shell" en la barra de herramientas de la esquina superior derecha:

68a17b036ce24ccb.png

Agrega herramientas a Cloud Shell

  1. Instala kubectx****: Descarga las secuencias de comandos de Bash desde aquí a una ubicación en $PATH.
  2. Instala helm****: Sigue estas instrucciones.

Como alternativa, ejecuta estos comandos para instalar ambos en ~/.bin y agregarlos a tu $PATH:

mkdir -p ~/.bin && \
cd ~/.bin && \
curl -LO https://raw.githubusercontent.com/ahmetb/kubectx/master/kubectx && \
chmod +x kubectx && \
curl -LO https://raw.githubusercontent.com/ahmetb/kubectx/master/kubens && \
chmod +x kubens && \
curl -LO  https://storage.googleapis.com/kubernetes-helm/helm-v2.12.0-linux-amd64.tar.gz && \
tar xzf helm-v2.12.0-linux-amd64.tar.gz && \
rm helm-v2.12.0-linux-amd64.tar.gz && \
mv linux-amd64/helm ./helm && \
rm -r linux-amd64 && \
export PATH=${HOME}/.bin:${PATH}

Estas son algunas sugerencias rápidas que pueden facilitar el uso de Cloud Shell:

1. Separa la shell en una ventana nueva:

2. Con el editor de archivos: Haz clic en el ícono de lápiz en la parte superior derecha para iniciar un editor de archivos en el navegador. Esto te resultará útil, ya que copiaremos fragmentos de código en archivos.

3. Iniciar pestañas nuevas: Si necesitas más de una instrucción de la terminal.

4. Agrandar el texto: El tamaño de fuente predeterminado en Cloud Shell puede ser demasiado pequeño para leerlo.

Ctrl + en Linux o Windows, ⌘ + en macOS.

Si te sientes más cómodo usando tu propio entorno de estación de trabajo que Cloud Shell, configura las siguientes herramientas:

  1. Instala gcloud: (preinstalado en Cloud Shell). Sigue las instrucciones para instalar gcloud en tu plataforma. Lo usaremos para crear un clúster de Kubernetes.
  2. Instala kubectl:(preinstalado en Cloud Shell). Ejecuta el siguiente comando para instalar:
gcloud components install kubectl

Ejecuta el siguiente comando para autenticar gcloud. Se te pedirá que accedas con tu Cuenta de Google. Luego, elige el proyecto creado previamente (que se muestra arriba) como el proyecto predeterminado. (Puedes omitir la configuración de una zona de procesamiento):

gcloud init
  1. Instala curl:. Está preinstalado en la mayoría de los sistemas Linux/macOS. Es probable que ya lo tengas. De lo contrario, busca en Internet cómo instalarlo.
  2. Instala kubectx****: Descarga las secuencias de comandos de Bash desde aquí a una ubicación en $PATH.
  3. Instala helm****: Sigue estas instrucciones.

3. Configura el proyecto de GCP

Habilita las APIs de GKE (Google Kubernetes Engine), GCR (Google Container Registry) y GCB (Google Cloud Build) en tu proyecto:

gcloud services enable \
  cloudapis.googleapis.com \
  container.googleapis.com \
  containerregistry.googleapis.com \
  cloudbuild.googleapis.com

Configura las variables de entorno

Trabajaremos mucho con nuestro proyecto de Google Cloud durante la configuración. Establezcamos una variable de entorno para consultarla rápidamente.

export GCLOUD_PROJECT=$(gcloud config get-value project)

Crearemos algunos archivos de código y configuración durante este taller, así que crearemos un directorio de proyecto y lo cambiaremos.

mkdir -p src/istio-burst && \
cd src/istio-burst && \
export proj=$(pwd)

4. Crea un clúster de Kubernetes "principal"

Puedes crear fácilmente un clúster de Kubernetes administrado con Google Kubernetes Engine (GKE).

El siguiente comando creará un clúster de Kubernetes:

  • llamado "primary",
  • en la zona us-west1-a,
  • La versión más reciente de Kubernetes disponible
  • con 4 nodos iniciales
export cluster=primary
export zone=us-west1-a
gcloud container clusters create $cluster --zone $zone --username "admin" \
--cluster-version latest --machine-type "n1-standard-2" \
--image-type "COS" --disk-size "100" \
--scopes "https://www.googleapis.com/auth/compute",\
"https://www.googleapis.com/auth/devstorage.read_only",\
"https://www.googleapis.com/auth/logging.write",\
"https://www.googleapis.com/auth/monitoring",\
"https://www.googleapis.com/auth/servicecontrol",\
"https://www.googleapis.com/auth/service.management.readonly",\
"https://www.googleapis.com/auth/trace.append" \
--num-nodes "4" --network "default" \
--enable-cloud-logging --enable-cloud-monitoring --enable-ip-alias

(Este proceso puede tardar alrededor de 5 minutos. Puedes observar cómo se crea el clúster en Cloud Console.

Después de crear el clúster de Kubernetes, gcloud configura kubectl con las credenciales que apuntan al clúster.

gcloud container clusters get-credentials $cluster --zone=$zone

Ahora deberías poder usar kubectl con tu clúster nuevo.

Ejecuta el siguiente comando para obtener una lista de los nodos de Kubernetes de tu clúster (deberían mostrar el estado "Listo"):

kubectl get nodes

Modifica los nombres de Kubeconfig para facilitar su uso

Cambiaremos de contexto con frecuencia, por lo que es útil tener un alias corto para nuestros clústeres.

Este comando cambiará el nombre de la entrada de kubeconfig que acabas de crear a primary.

kubectx ${cluster}=gke_${GCLOUD_PROJECT}_${zone}_${cluster}

Establece permisos:

Para implementar Istio, debes ser administrador del clúster. Este comando establecerá el correo electrónico asociado con tu cuenta de Google Cloud como el administrador del clúster.

kubectl create clusterrolebinding cluster-admin-binding \
    --clusterrole=cluster-admin \
    --user=$(gcloud config get-value core/account)

5. Crea un clúster "burst"

El siguiente comando creará un clúster de Kubernetes:

  • llamada "burst",
  • en la zona us-west1-a,
  • La versión más reciente de Kubernetes disponible
  • Con 1 nodo inicial
  • Ajuste de escala automático habilitado para hasta 5 nodos
export cluster=burst
export zone=us-west1-a
gcloud container clusters create $cluster --zone $zone --username "admin" \
--cluster-version latest --machine-type "n1-standard-2" \
--image-type "COS" --disk-size "100" \
--scopes "https://www.googleapis.com/auth/compute",\
"https://www.googleapis.com/auth/devstorage.read_only",\
"https://www.googleapis.com/auth/logging.write",\
"https://www.googleapis.com/auth/monitoring",\
"https://www.googleapis.com/auth/servicecontrol",\
"https://www.googleapis.com/auth/service.management.readonly",\
"https://www.googleapis.com/auth/trace.append" \
--num-nodes "1" --enable-autoscaling --min-nodes=1 --max-nodes=5 \
--network "default" \
--enable-cloud-logging --enable-cloud-monitoring --enable-ip-alias

(Este proceso puede tardar alrededor de 5 minutos. Puedes observar cómo se crea el clúster en Cloud Console.

Después de crear el clúster de Kubernetes, gcloud configura kubectl con las credenciales que apuntan al clúster.

gcloud container clusters get-credentials $cluster --zone=$zone

Ahora deberías poder usar kubectl con tu clúster nuevo.

Ejecuta el siguiente comando para obtener una lista de los nodos de Kubernetes de tu clúster (deberían mostrar el estado "Listo"):

kubectl get nodes

Modifica los nombres de Kubeconfig para facilitar su uso

Este comando modificará la entrada de kubeconfig que acabas de crear en burst.

kubectx ${cluster}=gke_${GCLOUD_PROJECT}_${zone}_${cluster}

Establece permisos:

Para implementar Istio Remote, debes ser administrador del clúster. Este comando establecerá el correo electrónico asociado con tu cuenta de Google Cloud como el administrador del clúster.

kubectl create clusterrolebinding cluster-admin-binding \
    --clusterrole=cluster-admin \
    --user=$(gcloud config get-value core/account)

6. Aplica reglas de firewall

Para que nuestros dos clústeres se comuniquen entre sí, necesitaremos crear una regla de firewall.

Ejecuta los siguientes comandos para crear una regla de firewall en Google Cloud Platform que permita que nuestros clústeres se comuniquen.

function join_by { local IFS="$1"; shift; echo "$*"; }
ALL_CLUSTER_CIDRS=$(gcloud container clusters list \
--filter="(name=burst OR name=primary) AND zone=$zone" \
--format='value(clusterIpv4Cidr)' | sort | uniq)
ALL_CLUSTER_CIDRS=$(join_by , $(echo "${ALL_CLUSTER_CIDRS}"))
ALL_CLUSTER_NETTAGS=$(gcloud compute instances list \
--filter="(metadata.cluster-name=burst OR metadata.cluster-name=primary) AND metadata.cluster-location=us-west1-a" \
--format='value(tags.items.[0])' | sort | uniq)
ALL_CLUSTER_NETTAGS=$(join_by , $(echo "${ALL_CLUSTER_NETTAGS}"))
gcloud compute firewall-rules create istio-multicluster-test-pods \
  --allow=tcp,udp,icmp,esp,ah,sctp \
  --direction=INGRESS \
  --priority=900 \
  --source-ranges="${ALL_CLUSTER_CIDRS}" \
  --target-tags="${ALL_CLUSTER_NETTAGS}" --quiet

Tenemos ambos clústeres configurados y listos para implementar nuestra aplicación y Istio en ellos.

7. Introducción a Istio

¿Qué es Istio?

Istio es un plano de control de malla de servicios que tiene como objetivo "conectar, proteger, controlar y observar servicios". Lo hace de varias maneras, pero principalmente agregando un contenedor de proxy ( Envoy) a cada uno de tus pods de Kubernetes implementados. El contenedor de proxy controla toda la comunicación de red entre microservicios en conjunto con un centro de telemetría y una política de uso general ( Mixer).

a25613cd581825da.png

Estas políticas se pueden aplicar independientemente de tus implementaciones y servicios de Kubernetes, lo que significa que el operador de red puede observar la actividad de la red, restringir, redireccionar o reescribir las políticas de red sin volver a implementar las aplicaciones asociadas.

Estas son algunas de las funciones de administración de tráfico que admite Istio:

  • Disyuntores
  • División de tráfico basada en el porcentaje
  • Reescritura de URL
  • finalización de TLS
  • Verificaciones de estado
  • Balanceo de cargas

A los efectos de este taller, nos enfocaremos en la división del tráfico basada en porcentajes.

Términos de Istio con los que trabajaremos

VirtualService

Un VirtualService define un conjunto de reglas de enrutamiento de tráfico que se aplican cuando se dirige un host.

Puerta de enlace

Una puerta de enlace es un balanceador de cargas que opera en el perímetro de la malla que recibe conexiones HTTP/TCP entrantes o salientes. Las puertas de enlace pueden especificar puertos, configuraciones de SNI, etcétera.

Regla del destino

Un DestinationRule define las políticas que se aplican al tráfico destinado a un servicio después de que se produce el enrutamiento. Especifican la configuración para el balanceo de cargas, el tamaño del grupo de conexiones del sidecar y la configuración de detección de valores atípicos.

Istio multiclúster

Es posible que hayas notado cuando creamos nuestros dos clústeres que nuestro clúster de primary tenía 4 nodos sin ajuste de escala automático y nuestro clúster de burst tenía 1 nodo con ajuste de escala automático de hasta 5 nodos.

Existen dos motivos para esta configuración.

Primero, queremos simular una migración de "local" a Cloud. En un entorno local, no tienes acceso a los clústeres de ajuste de escala automático, ya que tienes una infraestructura fija.

En segundo lugar, una configuración de 4 nodos (como se definió anteriormente) es el requisito mínimo para ejecutar Istio. Esto plantea la pregunta: si Istio requiere un mínimo de 4 nodos, ¿cómo puede nuestro clúster de burst ejecutar Istio con 1 nodo? La respuesta es que Istio Multicluster instala un conjunto mucho más pequeño de los servicios de Istio y se comunica con la instalación de Istio en el clúster principal para recuperar las reglas de políticas y publicar información de telemetría.

8. Descripción general de la arquitectura de la aplicación

Descripción general de los componentes

Implementaremos una aplicación de tres niveles con NodeJS y Redis.

Trabajador

La aplicación de trabajo está escrita en NodeJS y escuchará las solicitudes HTTP POST entrantes, realizará una operación de hash en ellas y, si se define una variable de entorno llamada PREFIX, agregará el hash con ese valor al principio. Una vez que se calcula el hash, la aplicación envía el resultado en el canal "calculation" en el servidor de Redis especificado.

Más adelante, usaremos la variable de entorno PREFIX para demostrar la funcionalidad de varios clústeres.

A modo de referencia, estos son los paquetes que usa la aplicación.

  • body-parser: nos permite analizar nuestras solicitudes HTTP.
  • cors: Permite el uso del uso compartido de recursos entre dominios.
  • dotenv: Análisis sencillo de variables de entorno
  • express: Hosting de sitios web fácil
  • ioredis: Biblioteca cliente para comunicarse con bases de datos de Redis
  • morgan: Proporciona un registro estructurado agradable.

Frontend

Nuestro frontend también es una aplicación de NodeJS que aloja una página web con Express. Toma una frecuencia ingresada por el usuario y envía solicitudes a nuestra aplicación worker a esa velocidad. Esta aplicación también se suscribe a mensajes en un canal de Redis llamado "calculation" y muestra los resultados en una página web.

La aplicación usa las siguientes dependencias.

  • body-parser: nos permite analizar nuestras solicitudes HTTP.
  • dotenv: Análisis sencillo de variables de entorno
  • express: Hosting de sitios web fácil
  • ioredis: Biblioteca cliente para comunicarse con bases de datos de Redis
  • morgan: Proporciona registros estructurados agradables.
  • request: Permite realizar solicitudes HTTP.
  • socket.io: Permite la comunicación bidireccional de la página web al servidor.

Esta página web usa Bootstrap para aplicar diseño y, cuando se ejecuta, se ve de la siguiente manera:

e5e3b9cbede4cac4.png

Diagrama de arquitectura

7ae4bc22a58f80a6.png

Diagrama de implementación

Implementaremos nuestra aplicación final en los dos clústeres que creamos. El clúster de primary tendrá todos los componentes (frontend, worker y Redis) implementados, pero el clúster de burst solo tendrá implementada la aplicación worker.

A continuación, se muestra un diagrama que describe los dos clústeres. Los cuadros delineados en rojo son los servicios de Kubernetes, y los delineados en azul son las implementaciones de Kubernetes. Los cuadros amarillos representan nuestra instalación de Istio.

561db37c510944bd.png

Observa cómo el clúster burst aún tiene un servicio para Redis implementado, a pesar de que no hay ninguna implementación para Redis en el clúster. Necesitamos tener este servicio en el clúster para que el DNS de Kubernetes pueda resolver la solicitud, pero cuando se realice la solicitud, el proxy de Istio redireccionará la solicitud a la implementación de Redis en el clúster primary.

La aplicación final tendrá una implementación adicional que se ejecutará en el clúster primary llamado istiowatcher.. Esto nos permitirá redireccionar el tráfico de forma dinámica a burst automáticamente cuando nuestro tráfico supere un umbral determinado.

8f6183bdfc3f813c.png

9. Crea archivos de implementación de la aplicación

Necesitamos crear un conjunto de manifiestos de Kubernetes para implementar nuestra aplicación.

Cambia al directorio raíz del proyecto y crea una carpeta nueva llamada kubernetes.

mkdir ${proj}/kubernetes && cd ${proj}/kubernetes

Escribe frontend.yaml

Esto creará un objeto Deployment y un objeto Service de Kubernetes para acceder a nuestra imagen de frontend.

Inserta lo siguiente en frontend.yaml.

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: frontend-deployment
  labels:
    app: frontend
spec:
  replicas: 1
  selector:
    matchLabels:
      app: frontend
  template:
    metadata:
      labels:
        app: frontend
    spec:
      containers:
      - name: frontend
        image: gcr.io/istio-burst-workshop/frontend
        ports:
        - containerPort: 8080
        readinessProbe:
            initialDelaySeconds: 10
            httpGet:
              path: "/_healthz"
              port: 8080
              httpHeaders:
              - name: "Cookie"
                value: "istio_session-id=x-readiness-probe"
        livenessProbe:
          initialDelaySeconds: 10
          httpGet:
            path: "/"
            port: 8080
            httpHeaders:
            - name: "Cookie"
              value: "istio_session-id=x-liveness-probe"
        env:
        - name: PORT
          value: "8080"
        - name: PROCESSOR_URL
          value: "http://worker-service"
        - name: REDIS_URL
          value: "redis-cache-service:6379"
---
apiVersion: v1
kind: Service
metadata:
  name: frontend-service
spec:
  type: ClusterIP
  selector:
    app: frontend
  ports:
  - name: http
    port: 80
    targetPort: 8080

Aspectos clave que debes tener en cuenta en Deployment

  • Especificamos que el puerto en el que se ejecutará la aplicación sea 8080.
  • Establecimos la dirección del trabajador como “http://worker-service” y usaremos la función de DNS integrada de Kubernetes para resolver el servicio resultante.
  • Establecimos la dirección de nuestro REDIS_URL como "redis-cache-service:6379" y usaremos la función de DNS integrada de Kubernetes para resolver las direcciones IP resultantes.
  • También configuramos los sondeos liveness y readiness en el contenedor para ayudar a informar a Kubernetes cuando el contenedor está en funcionamiento.

Escribe worker-service.yaml

Escribimos la definición del servicio de Kubernetes en un archivo independiente de la definición de Deployment, ya que volveremos a usar este servicio en varios clústeres, pero escribiremos un Deployment diferente para cada clúster.

Inserta lo siguiente en worker-service.yaml

apiVersion: v1
kind: Service
metadata:
 name: worker-service
spec:
 type: ClusterIP
 selector:
   app: worker
 ports:
 - name: http
   port: 80
   targetPort: 8081

Escribe worker-primary.yaml

Esta será la implementación de worker que enviaremos al clúster principal.

Inserta lo siguiente en worker-primary.yaml.

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
 name: worker-deployment
 labels:
   app: worker
spec:
 replicas: 1
 selector:
   matchLabels:
     app: worker
 template:
   metadata:
     labels:
       app: worker
       cluster-type: primary-cluster
   spec:
     containers:
     - name: worker
       image: gcr.io/istio-burst-workshop/worker
       imagePullPolicy: Always
       ports:
       - containerPort: 8081
       readinessProbe:
           initialDelaySeconds: 10
           httpGet:
             path: "/_healthz"
             port: 8081
             httpHeaders:
             - name: "Cookie"
               value: "istio_session-id=x-readiness-probe"
       livenessProbe:
         initialDelaySeconds: 10
         httpGet:
           path: "/"
           port: 8081
           httpHeaders:
           - name: "Cookie"
             value: "istio_session-id=x-liveness-probe"
       env:
       - name: PORT
         value: "8081"
       - name: REDIS_URL
         value: "redis-cache-service:6379"

Ten en cuenta que, en este caso, seguimos el mismo patrón de proporcionar sondas liveness y readiness, así como especificar las variables de entorno PORT y REDIS_URL para que las use nuestra aplicación.

Otro aspecto que se debe tener en cuenta en esta implementación es la falta de la variable de entorno PREFIX. Esto significa que los resultados de nuestro cálculo serán valores hash sin procesar (sin prefijos).

El punto clave final de esta implementación es la etiqueta cluster-type: primary-cluster. Lo usaremos más adelante cuando realicemos el enrutamiento de tráfico en Istio multiclúster.

Escribe redis.yaml

La comunicación de nuestro trabajador al frontend se realiza a través de un canal de Redis y, por lo tanto, debemos implementar una aplicación de Redis en nuestro clúster.

Inserta lo siguiente en redis.yaml

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
 name: redis-cache
spec:
 template:
   metadata:
     labels:
       app: redis-cache
   spec:
     containers:
     - name: redis
       image: redis:alpine
       ports:
       - containerPort: 6379
       readinessProbe:
         periodSeconds: 5
         tcpSocket:
           port: 6379
       livenessProbe:
         periodSeconds: 5
         tcpSocket:
           port: 6379
       volumeMounts:
       - mountPath: /data
         name: redis-data
       resources:
         limits:
           memory: 256Mi
           cpu: 125m
         requests:
           cpu: 70m
           memory: 200Mi
     volumes:
     - name: redis-data
       emptyDir: {}

Esta es una implementación semiestándar de una aplicación de Redis. Inicia un contenedor basado en la imagen redis:alpine, expone los puertos adecuados y establece límites de recursos razonables.

Escribe redis-service.yaml

Necesitamos un servicio de Kubernetes para comunicarnos con nuestra aplicación de Redis.

Inserta lo siguiente en redis-service.yaml

apiVersion: v1
kind: Service
metadata:
 name: redis-cache-service
spec:
 type: ClusterIP
 selector:
   app: redis-cache
 ports:
 - port: 6379
   targetPort: 6379

Esto proporciona un servicio llamado redis-cache-service para acceder a nuestra implementación de Redis.

10. Implementa la aplicación

Con nuestras imágenes enviadas a GCR y nuestros manifiestos de Kubernetes escritos, este es un buen momento para implementar nuestra aplicación y ver cómo funciona.

Ejecuta los siguientes comandos para implementar la aplicación.

  1. Asegúrate de que estamos en el clúster correcto
kubectx primary
  1. Implementa Redis Cache
kubectl apply -f redis.yaml
  1. Implementa el servicio de Redis
kubectl apply -f redis-service.yaml
  1. Implementa el frontend
kubectl apply -f frontend.yaml
  1. Implementa el trabajador
kubectl apply -f worker-primary.yaml
  1. Implementa el servicio de trabajador
kubectl apply -f worker-service.yaml

Ya implementamos nuestra aplicación en GKE. ¡Felicitaciones!

Prueba

Espera a que los pods estén en línea.

kubectl get pods -w

Una vez que todos los pods estén en "Running", presiona Ctrl + C.

NAME                                   READY     STATUS    RESTARTS   AGE
frontend-deployment-695d95fbf7-76sd8   1/1       Running   0          2m
redis-cache-7475999bf5-nxj8x           1/1       Running   0          2m
worker-deployment-5b9cf9956d-g975p     1/1       Running   0          2m

Notarás que no expusimos nuestro frontend a través de un LoadBalancer. Esto se debe a que más adelante accederemos a la aplicación a través de Istio. Para probar que todo esté en funcionamiento, usaremos kubectl port-forward. Ejecuta el siguiente comando para reenviar el puerto 8080 de tu máquina local (o Cloud Shell) al puerto 8080 que ejecuta la implementación de frontend.

kubectl port-forward \
$(kubectl get pods -l app=frontend -o jsonpath='{.items[0].metadata.name}') \
8080:8080

Si ejecutas la app de forma local, abre un navegador web y dirígete a http://localhost:8080.

Si ejecutas la app en Cloud Shell: Haz clic en el botón "Vista previa en la Web" y selecciona "Vista previa en el puerto 8080".

bdb5dc75f415be11.png

Deberías ver el frontend. Si ingresas un número en el cuadro "frecuencia", deberías ver que comienzan a aparecer los valores hash.

1caafaffab26897a.png

¡Felicitaciones! Ya está todo listo.

Presiona Ctrl+C para dejar de redirigir el puerto.

11. Limpieza de la aplicación implementada

Aplicaremos Istio a nuestro clúster y, luego, volveremos a implementar nuestra aplicación. Por lo tanto, primero debemos limpiar nuestra aplicación actual.

Ejecuta los siguientes comandos para borrar todas las implementaciones y los servicios que acabas de crear.

  1. Borrar redis-cache-service
kubectl delete -f redis-service.yaml
  1. Borrar redis
kubectl delete -f redis.yaml
  1. Borrar frontend
kubectl delete -f frontend.yaml
  1. Borrar worker
kubectl delete -f worker-primary.yaml
  1. Borrar worker-service
kubectl delete -f worker-service.yaml

12. Instala Istio en el clúster principal

Obtén Istio

Las versiones de Istio se alojan en GitHub. Los siguientes comandos descargarán la versión 1.0.0 de istio y la descomprimirán.

  1. Cambia a la raíz de tu proyecto
cd ${proj}
  1. Descarga el archivo
curl -LO https://github.com/istio/istio/releases/download/1.0.0/istio-1.0.0-linux.tar.gz
  1. Extrae y quita el archivo
tar xzf istio-1.0.0-linux.tar.gz && rm istio-1.0.0-linux.tar.gz

Crea una plantilla de Istio

Si ejecutas el siguiente comando de Helm, se creará la plantilla para instalar Istio en tu clúster.

helm template istio-1.0.0/install/kubernetes/helm/istio \
--name istio --namespace istio-system \
--set prometheus.enabled=true \
--set servicegraph.enabled=true  > istio-primary.yaml

Se creará un archivo llamado istio-primary.yaml en tu directorio actual que contiene todas las definiciones y especificaciones necesarias para implementar y ejecutar Istio.

Observa los dos parámetros --set. Estos agregan compatibilidad con Prometheus y ServiceGraph al sistema de Istio. Más adelante en el lab, usaremos el servicio de Prometheus.

Implementa Istio

Para implementar Istio, primero debemos crear un espacio de nombres llamado istio-system en el que se puedan ejecutar las implementaciones y los servicios de Istio.

kubectl create namespace istio-system

Por último, aplica el archivo istio-primary.yaml que creamos con Helm.

kubectl apply -f istio-primary.yaml

Espacio de nombres predeterminado de la etiqueta

Istio funciona inyectando un servicio de proxy de sidecar en cada una de tus implementaciones. Esto se hace de forma optativa, por lo que debemos etiquetar nuestro espacio de nombres default con istio-injection=enabled para que Istio pueda insertar automáticamente el sidecar por nosotros.

kubectl label namespace default istio-injection=enabled

¡Felicitaciones! Tenemos un clúster en funcionamiento con Istio listo para implementar nuestra aplicación.

13. Implementa nuestra aplicación con la administración de tráfico de Istio

Crea archivos de configuración de la administración de tráfico de Istio

Istio funciona de manera similar a Kubernetes, ya que usa archivos yaml para la configuración. En ese sentido, debemos crear un conjunto de archivos que le indiquen a Istio cómo exponer y enrutar nuestro tráfico.

Crea un directorio llamado istio-manifests y cámbialo

mkdir ${proj}/istio-manifests && cd ${proj}/istio-manifests

Escribe frontend-gateway.yaml

Este archivo expondrá nuestro clúster de Kubernetes de una manera similar a un LoadBalancer de GKE y enrutará todo el tráfico entrante a nuestro servicio de frontend.

Crea un archivo llamado frontend-gateway.yaml y, luego, inserta lo siguiente:

apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
 name: frontend-gateway
spec:
 selector:
   istio: ingressgateway # use Istio default gateway implementation
 servers:
 - port:
     number: 80
     name: http
     protocol: HTTP
   hosts:
   - "*"
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
 name: frontend-ingress-virtual-service
spec:
 hosts:
 - "*"
 gateways:
 - frontend-gateway
 http:
 - route:
   - destination:
       host: frontend-service
       port:
         number: 80

Escribe redis-virtualservice.yaml

Crea un archivo llamado redis-virtualservice.yaml y, luego, inserta lo siguiente:

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
 name: redis-virtual-service
spec:
 hosts:
 - redis-cache-service
 gateways:
 - mesh
 tcp:
 - route:
   - destination:
       host: redis-cache-service.default.svc.cluster.local

Escribe worker-virtualservice.yaml

Crea un archivo llamado worker-virtualservice.yaml y, luego, inserta lo siguiente:

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
 name: worker-virtual-service
spec:
 hosts:
 - worker-service
 gateways:
 - mesh
 http:
 - route:
   - destination:
       host: worker-service.default.svc.cluster.local   
       port:
         number: 80

Implementa políticas de administración de tráfico de Istio

La implementación de las políticas de Istio se realiza de la misma manera que otros recursos de Kubernetes, con kubectl apply.

  1. Aplica nuestra puerta de enlace
kubectl apply -f frontend-gateway.yaml
  1. Aplica nuestro VirtualService de Redis
kubectl apply -f redis-virtualservice.yaml
  1. Aplica nuestro VirtualService de trabajador
kubectl apply -f worker-virtualservice.yaml

Implementa la aplicación

  1. Regresa a nuestro directorio kubernetes
cd ${proj}/kubernetes
  1. Implementa Redis Cache
kubectl apply -f redis.yaml
  1. Implementa el servicio de Redis
kubectl apply -f redis-service.yaml
  1. Implementa el frontend
kubectl apply -f frontend.yaml
  1. Implementa el trabajador
kubectl apply -f worker-primary.yaml
  1. Implementa el servicio de trabajador
kubectl apply -f worker-service.yaml

Verificar

En este punto, volvimos a implementar nuestra aplicación en un clúster con Istio y políticas de administración de tráfico.

Esperemos a que todas nuestras cargas de trabajo estén en línea

Una vez que estén todos en línea, obtén la puerta de enlace de Ingress que configuramos en frontend-ingressgateway.yaml.

$ kubectl -n istio-system get svc istio-ingressgateway
NAME                   TYPE           CLUSTER-IP    EXTERNAL-IP     PORT(S)                                                                                                     AGE
istio-ingressgateway   LoadBalancer   10.36.3.112   35.199.158.10   80:31380/TCP,

Navega a la dirección <EXTERNAL-IP> o usa curl y deberías ver el frontend.

$ curl 35.199.158.10
<!doctype html>
<html>

<head>
    <title>String Hashr</title>
    <!-- Bootstrap -->
...

14. Instala Istio en un clúster "burst"

Pasamos mucho tiempo configurando y realizando implementaciones en nuestro clúster de primary, pero tenemos otro clúster completo en el que implementar.

En esta sección, necesitaremos obtener variables de configuración de ambos clústeres, así que presta mucha atención al clúster al que nos dirigimos para cada comando.

Crea el manifiesto remoto de Istio

Al igual que cuando implementamos Istio en el clúster primary, usaremos Helm para crear una plantilla de nuestra implementación de Istio remoto en el clúster burst. Sin embargo, antes de hacerlo, necesitamos obtener información sobre nuestro clúster de primary.

Cómo recopilar información del clúster principal

Cambia al clúster primary

kubectx primary

Los siguientes comandos recuperan las direcciones IP de varios pods en el clúster principal. Istio Remote las usa para comunicarse con el clúster principal.

export PILOT_POD_IP=$(kubectl -n istio-system get pod -l istio=pilot -o jsonpath='{.items[0].status.podIP}')
export POLICY_POD_IP=$(kubectl -n istio-system get pod -l istio-mixer-type=policy -o jsonpath='{.items[0].status.podIP}')
export STATSD_POD_IP=$(kubectl -n istio-system get pod -l istio=statsd-prom-bridge -o jsonpath='{.items[0].status.podIP}')
export TELEMETRY_POD_IP=$(kubectl -n istio-system get pod -l istio-mixer-type=telemetry -o jsonpath='{.items[0].status.podIP}')
export ZIPKIN_POD_IP=$(kubectl -n istio-system get pod -l app=jaeger -o jsonpath='{range .items[*]}{.status.podIP}{end}')

Crea una plantilla remota

Ahora, usaremos helm para crear un archivo llamado istio-remote-burst.yaml, que luego podremos implementar en el clúster burst.

Cómo cambiar a la raíz del proyecto

cd $proj
helm template istio-1.0.0/install/kubernetes/helm/istio-remote --namespace istio-system \
--name istio-remote \
--set global.remotePilotAddress=${PILOT_POD_IP} \
--set global.remotePolicyAddress=${POLICY_POD_IP} \
--set global.remoteTelemetryAddress=${TELEMETRY_POD_IP} \
--set global.proxy.envoyStatsd.enabled=true \
--set global.proxy.envoyStatsd.host=${STATSD_POD_IP} \
--set global.remoteZipkinAddress=${ZIPKIN_POD_IP} > istio-remote-burst.yaml

Instala Istio Remote en el clúster de ráfaga

Para instalar Istio en nuestro clúster burst, debemos seguir los mismos pasos que cuando lo instalamos en el clúster primary, pero debemos usar el archivo istio-remote-burst.yaml.

Cómo cambiar kubecontext a burst

kubectx burst

Crea el espacio de nombres istio-system

kubectl create ns istio-system

Aplica istio-burst.yaml

kubectl apply -f istio-remote-burst.yaml

Espacio de nombres predeterminado de la etiqueta

Una vez más, debemos etiquetar el espacio de nombres default para que se pueda insertar automáticamente el proxy.

kubectl label namespace default istio-injection=enabled

¡Felicitaciones! En este punto, configuramos Istio Remote en el clúster burst. Sin embargo, en este punto, los clústeres aún no pueden comunicarse. Necesitamos generar un archivo kubeconfig para el clúster burst que podamos implementar en el clúster primary para vincularlos.

Crea un archivo kubeconfig para el clúster "burst"

Cambia al clúster de ráfaga

kubectx burst

Configura el entorno

Necesitamos recopilar información sobre el clúster para crear un archivo kubeconfig para él.

  1. Obtén el nombre del clúster
CLUSTER_NAME=$(kubectl config view --minify=true -o "jsonpath={.clusters[].name}")
  1. Obtén el nombre del servidor del clúster
SERVER=$(kubectl config view --minify=true -o "jsonpath={.clusters[].cluster.server}")
  1. Obtén el nombre del secreto de la AC de la cuenta de servicio istio-multi
SECRET_NAME=$(kubectl get sa istio-multi -n istio-system -o jsonpath='{.secrets[].name}')
  1. Obtén los datos de la AC almacenados en el secreto anterior
CA_DATA=$(kubectl get secret ${SECRET_NAME} -n istio-system -o "jsonpath={.data['ca\.crt']}")
  1. Obtén el token almacenado en el secreto anterior
TOKEN=$(kubectl get secret ${SECRET_NAME} -n istio-system -o "jsonpath={.data['token']}" | base64 --decode)

Crea un archivo kubeconfig

Con todas esas variables de entorno configuradas, debemos crear nuestro archivo kubeconfig.

cat <<EOF > burst-kubeconfig
apiVersion: v1
clusters:
   - cluster:
       certificate-authority-data: ${CA_DATA}
       server: ${SERVER}
     name: ${CLUSTER_NAME}
contexts:
   - context:
       cluster: ${CLUSTER_NAME}
       user: ${CLUSTER_NAME}
     name: ${CLUSTER_NAME}
current-context: ${CLUSTER_NAME}
kind: Config
preferences: {}
users:
   - name: ${CLUSTER_NAME}
     user:
       token: ${TOKEN}
EOF

Se creará un archivo nuevo llamado burst-kubeconfig en tu directorio actual que el clúster de primary podrá usar para autenticar y administrar el clúster de burst.

Cómo volver al clúster principal

kubectx primary

Aplica el kubeconfig para "burst". Para ello, crea un secreto y etiquétalo.

kubectl create secret generic burst-kubeconfig --from-file burst-kubeconfig -n istio-system

Etiqueta el secreto para que Istio sepa que debe usarlo para la autenticación de varios clústeres

kubectl label secret burst-kubeconfig istio/multiCluster=true -n istio-system

¡Felicitaciones! Tenemos ambos clústeres autenticados y comunicándose entre sí a través de Istio Multicluster. Implementemos nuestra aplicación en varios clústeres

15. Implementa una aplicación entre clústeres

Crea implementaciones

Cambia al directorio kubernetes.

cd ${proj}/kubernetes

Crea una implementación de trabajador para el clúster "burst": worker-burst.yaml

Crea un archivo llamado worker-burst.yaml y, luego, inserta el siguiente contenido:

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: worker-deployment
  labels:
    app: worker
spec:
  replicas: 1
  selector:
    matchLabels:
      app: worker
  template:
    metadata:
      labels:
        app: worker
        cluster-type: burst-cluster
    spec:
      containers:
      - name: worker
        image: gcr.io/istio-burst-workshop/worker
        imagePullPolicy: Always
        ports:
        - containerPort: 8081
        readinessProbe:
            initialDelaySeconds: 10
            httpGet:
              path: "/_healthz"
              port: 8081
              httpHeaders:
              - name: "Cookie"
                value: "istio_session-id=x-readiness-probe"
        livenessProbe:
          initialDelaySeconds: 10
          httpGet:
            path: "/"
            port: 8081
            httpHeaders:
            - name: "Cookie"
              value: "istio_session-id=x-liveness-probe"
        env:
        - name: PORT
          value: "8081"
        - name: REDIS_URL
          value: "redis-cache-service:6379"
        - name: PREFIX
          value: "bursty-"

Observa que esto es casi idéntico al archivo worker-primary.yaml que creamos antes. Existen dos diferencias clave.

La primera diferencia clave es que agregamos la variable de entorno PREFIX con el valor "bursty-".

env:
- name: PORT
  value: "8081"
- name: REDIS_URL
  value: "redis-cache-service:6379"
- name: PREFIX
  value: "bursty-"

Esto significa que nuestro trabajador en el clúster burst agregará el prefijo "bursty-" a todos los valores hash que envíe. Podemos usar esto para saber que nuestra aplicación es realmente multiclúster.

La segunda diferencia clave es que cambiamos la etiqueta cluster-type de esta implementación de primary-cluster a burst-cluster.

labels:
  app: worker
  cluster-type: burst-cluster

Usaremos esta etiqueta más adelante cuando actualicemos nuestro VirtualService.

Cómo modificar los servicios de Istio

En este momento, nuestros servicios de Istio no aprovechan ambas implementaciones. El 100% de nuestro tráfico se enruta al clúster "principal". Cambiemos eso.

Cambia al directorio istio-manifests

cd ${proj}/istio-manifests

Edita worker-virtualservice.yaml para incluir DestinationRules

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: worker-virtual-service
spec:
  hosts:
  - worker-service
  gateways:
  - mesh
  http:
  - route:
    - destination:
        host: worker-service.default.svc.cluster.local    
        subset: primary
        port:
          number: 80        
      weight: 50
    - destination:
        host: worker-service.default.svc.cluster.local     
        subset: burst  
        port:
          number: 80        
      weight: 50
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: worker-destination-rule
spec:
  host: worker-service
  trafficPolicy:
    loadBalancer:
      simple: RANDOM
  subsets:
  - name: primary
    labels:
      cluster-type: primary-cluster
  - name: burst
    labels:
     cluster-type: burst-cluster

Puedes ver que agregamos un segundo destino a nuestro VirtualService. Aún hace referencia al mismo host (worker-service.default.svc.cluster.local)), pero el 50% del tráfico se enruta al subconjunto primary y el otro 50% al subconjunto burst.

Definimos el subconjunto primary como las implementaciones que tienen la etiqueta cluster-type: primary-cluster y el subconjunto burst como las implementaciones que tienen la etiqueta cluster-type: burst-cluster.

Esto divide nuestro tráfico de manera eficaz en partes iguales entre los dos clústeres.

Implementa en el clúster

Implementa redis-service.yaml en el clúster de aumento

Cambia a kubeconfig de burst

kubectx burst

Cambia a la raíz de nuestro proyecto

cd ${proj}

Luego, implementa

Implementa redis-service.yaml en el clúster de aumento

kubectl apply -f kubernetes/redis-service.yaml

Implementa worker-burst.yaml en el clúster de aumento

kubectl apply -f kubernetes/worker-burst.yaml

Implementa worker-service.yaml en el clúster de aumento

kubectl apply -f kubernetes/worker-service.yaml

Aplica VirtualServices de Istio

Cambia a kubeconfig de primary

kubectx primary

Luego, implementa

kubectl apply -f istio-manifests/worker-virtualservice.yaml

Verifica que funcione

Para verificar que funcione, navega a tu punto de entrada de Istio y observa que alrededor del 50% de los valores hash tienen el prefijo “burst-”.

78fb6e235e9f4a07.png

Esto significa que estamos hablando correctamente entre clústeres. Intenta cambiar los pesos en los diferentes servicios y aplicar el archivo worker-virtualservice.yaml. Esta es una excelente manera de equilibrar el tráfico entre los clústeres, pero ¿qué sucede si podemos hacerlo automáticamente?

16. Aprovecha las métricas de Prometheus

Introducción a Prometheus

Prometheus es un kit de herramientas de alerta y supervisión de sistemas de código abierto que se compiló originalmente en SoundCloud. Mantiene un modelo de datos multidimensional con datos de series temporales identificados por nombre de métrica y pares clave-valor.

A modo de referencia, este es el diagrama de la arquitectura de Prometheus:

601e1155a825e0c2.png

Cuando se implementa con Prometheus, Istio informa automáticamente varias métricas al servidor de Prometheus. Podemos usar estas métricas para administrar nuestros clústeres sobre la marcha.

Explorando nuestras métricas de Prometheus

Para comenzar, debemos exponer la implementación de Prometheus.

Navega a la pestaña Cargas de trabajo en GKE y desplázate hasta la carga de trabajo "prometheus".

b4a7a3cd67db05b3.png

Una vez que veas los detalles de la implementación, ve a Acciones -> Exponer.

c04a482e55bdfd41.png

Elige reenviar al puerto 9090 y escribe "Balanceador de cargas".

d5af3ba22a7a6ebb.png

Y elige "Exponer".

Se creará un servicio en una dirección IP de acceso público que podemos usar para explorar nuestras métricas de Prometheus.

Espera a que el extremo esté en funcionamiento y, luego, haz clic en la dirección IP junto a "Extremos externos". b1e40ad90851da29.png

Ahora deberías ver la IU de Prometheus.

ed273552270337ec.png

Prometheus proporciona suficientes métricas para ser su propio taller. Por ahora, comenzaremos por explorar la métrica istio_requests_total.

Si ejecutas esa consulta, se mostrarán muchos datos. Son métricas de todas las solicitudes que pasan por la malla de servicios de Istio, y eso es mucho. Cambiaremos nuestra expresión para filtrar lo que realmente nos interesa:

Solicitudes en las que el servicio de destino es worker-service.default.svc.cluster.local y cuya fuente es frontend-deployment limitada a los últimos 15 segundos

Esa consulta se ve de la siguiente manera:

istio_requests_total{reporter="destination",
destination_service="worker-service.default.svc.cluster.local",
source_workload="frontend-deployment"}[15s]

Y nos brinda un conjunto de datos mucho más manejable con el que trabajar.

19d551fd5eac3785.png

Pero aún es un poco densa. Queremos conocer las solicitudes por segundo, no todas las solicitudes.

Para obtener eso, podemos usar la función rate integrada.

rate(istio_requests_total{reporter="destination",
destination_service="worker-service.default.svc.cluster.local",
source_workload="frontend-deployment"}[15s])

dbb9dc063a18da9b.png

Esto nos acerca más, pero debemos reducir esas métricas un poco más en un grupo lógico.

Para ello, podemos usar las palabras clave sum y by para agrupar y sumar nuestros resultados.

sum(rate(istio_requests_total{reporter="destination",
destination_service="worker-service.default.svc.cluster.local",
source_workload="frontend-deployment"}[15s])) by (source_workload,
source_app, destination_service)

898519966930ec56.png

¡Perfecto! Podemos obtener las métricas exactas que necesitamos de Prometheus.

Nuestra consulta final de Prometheus

Con todo lo que aprendimos, la consulta final que debemos hacer a Prometheus es

sum(rate(istio_requests_total{reporter="destination",
destination_service="worker-service.default.svc.cluster.local",
source_workload="frontend-deployment"}[15s])) by (source_workload,
source_app, destination_service)

Ahora podemos usar su API de HTTP para obtener la métrica.

Podemos consultar su API con nuestra consulta si realizamos una solicitud HTTP GET de la siguiente manera. Reemplaza <prometheus-ip-here>

curl http://<prometheus-ip-here>/api/v1/query?query=sum\(rate\(istio_requests_total%7Breporter%3D%22destination%22%2C%0Adestination_service%3D%22worker-service.default.svc.cluster.local%22%2C%0Asource_workload%3D%22frontend-deployment%22%7D%5B15s%5D\)\)%20by%20\(source_workload%2C%0Asource_app%2C%20destination_service\)

Esta es una respuesta de ejemplo:

{
    "status": "success",
    "data": {
        "resultType": "vector",
        "result": [
            {
                "metric": {
                    "destination_service": "worker-service.default.svc.cluster.local",
                    "source_app": "frontend",
                    "source_workload": "frontend-deployment"
                },
                "value": [
                    1544404907.503,
                    "18.892886390062788"
                ]
            }
        ]
    }
}

Ahora, podemos extraer nuestro valor de métrica del JSON.

Haz limpieza

Debemos borrar el servicio que acabamos de usar para exponer Prometheus. En la consola de Google Cloud, ve a la parte superior del servicio que acabamos de crear y haz clic en "Borrar".

d58cb51b4c922751.png

Próximos pasos:

Después de encontrar una forma de descubrir cómo se mueve el tráfico a través del clúster y a qué velocidad, nuestro siguiente paso es escribir un pequeño objeto binario que consulte Prometheus de forma periódica y, si las solicitudes por segundo a worker superan un umbral determinado, aplique diferentes pesos de destino en nuestro servicio virtual de trabajador para enviar todo el tráfico al clúster burst. Una vez que las solicitudes por segundo sean inferiores a un umbral inferior, vuelve a enviar todo el tráfico a primary.

17. Crea un aumento entre clústeres

Configuración

Establece todo el tráfico del servicio de trabajo en el clúster principal

Consideraremos que todo el tráfico destinado a worker-service que se enruta al clúster primary es el estado “predeterminado” de nuestra aplicación.

Edita $proj/istio-manifests/worker-virtualservice.yaml para que se vea de la siguiente manera:

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: worker-virtual-service
spec:
  hosts:
  - worker-service
  gateways:
  - mesh
  http:
  - route:
    - destination:
        host: worker-service.default.svc.cluster.local    
        subset: primary
        port:
          number: 80        
      weight: 100
    - destination:
        host: worker-service.default.svc.cluster.local     
        subset: burst  
        port:
          number: 80        
      weight: 0
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: worker-destination-rule
spec:
  host: worker-service
  trafficPolicy:
    loadBalancer:
      simple: RANDOM
  subsets:
  - name: primary
    labels:
      cluster-type: primary-cluster
  - name: burst
    labels:
     cluster-type: burst-cluster

Asegúrate de estar conectado al clúster de primary.

kubectx primary

Aplica istio-manifests/worker-virtualservice.yaml

kubectl apply -f istio-manifests/worker-virtualservice.yaml

Escribe el daemon de istiowatcher

Usaremos Go para escribir este servicio por su velocidad y portabilidad. El flujo general de la aplicación será iniciarse y, cada segundo, consultar Prometheus.

Crea un directorio nuevo en src llamado istiowatcher.

mkdir -p ${proj}/src/istiowatcher && cd ${proj}/src/istiowatcher

Llamaremos a istioctl desde nuestro contenedor para manipular el plano de control de Istio desde el clúster.

Escribe istiowatcher.go

Crea un archivo en ese directorio llamado istiowatcher.go y, luego, insértale lo siguiente:

package main

import (
        "github.com/tidwall/gjson"
        "io/ioutil"
        "log"
        "net/http"
        "os/exec"
        "time"
)

func main() {
        //These are in requests per second
        var targetLow float64 = 10
        var targetHigh float64 = 15
        // This is for the ticker in milliseconds
        ticker := time.NewTicker(1000 * time.Millisecond)

        isBurst := false

        // Our prometheus query
        reqQuery := `/api/v1/query?query=sum(rate(istio_requests_total{reporter="destination",destination_service="worker-service.default.svc.cluster.local",source_workload="frontend-deployment"}[15s]))by(source_workload,source_app,destination_service)`

        for t := range ticker.C {
                log.Printf("Checking Prometheus at %v", t)

                // Check prometheus
                // Note that b/c we are querying over the past 5 minutes, we are getting a very SLOW ramp of our reqs/second
                // If we wanted this to be a little "snappier" we can scale it down to say 30s
                resp, err := http.Get("http://prometheus.istio-system.svc.cluster.local:9090" + reqQuery)
                if err != nil {
                        log.Printf("Error: %v", err)
                        continue
                }
                defer resp.Body.Close()
                body, _ := ioutil.ReadAll(resp.Body)

                val := gjson.Get(string(body), "data.result.0.value.1")
                log.Printf("Value: %v", val)

                currentReqPerSecond := val.Float()
                log.Printf("Reqs per second %f", currentReqPerSecond)

                if currentReqPerSecond > targetHigh && !isBurst {
                        applyIstio("burst.yaml")
                        log.Println("Entering burst mode")
                        isBurst = true
                } else if currentReqPerSecond < targetLow && isBurst {
                        applyIstio("natural.yaml")
                        log.Println("Returning to natural state.")
                        isBurst = false
                }
        }
}

func applyIstio(filename string) {
        cmd := exec.Command("istioctl", "replace", "-f", filename)
        if err := cmd.Run(); err != nil {
                log.Printf("Error hit applying istio manifests: %v", err)
        }
}

Escribe el Dockerfile

Crea un archivo nuevo llamado Dockerfile y, luego, inserta el siguiente contenido en él.

FROM golang:1.11.2-stretch as base

FROM base as builder

WORKDIR /workdir
RUN curl -LO https://github.com/istio/istio/releases/download/1.0.0/istio-1.0.0-linux.tar.gz
RUN tar xzf istio-1.0.0-linux.tar.gz
RUN cp istio-1.0.0/bin/istioctl ./istioctl

FROM base 

WORKDIR /go/src/istiowatcher
COPY . .
COPY --from=builder /workdir/istioctl /usr/local/bin/istioctl

RUN go get -d -v ./...
RUN go install -v ./...

CMD ["istiowatcher"]

Este Dockerfile de varias etapas descarga y extrae la versión 1.0.0 de Istio en la primera etapa. La segunda etapa copia todo de nuestro directorio en la imagen y, luego, copia istioctl de la etapa de compilación a /usr/local/bin (para que nuestra aplicación pueda llamarlo), obtiene las dependencias, compila el código y establece CMD como "istiowatcher".

Escribe burst.yaml

Este es el archivo que istiowatcher aplicará cuando las solicitudes por segundo a worker desde frontend superen las 15.

Crea un archivo nuevo llamado burst.yaml y, luego, inserta el siguiente contenido en él.

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
 name: worker-virtual-service
spec:
 hosts:
 - worker-service
 gateways:
 - mesh
 http:
 - route:
   - destination:
       host: worker-service.default.svc.cluster.local   
       subset: primary
       port:
         number: 80       
     weight: 0
   - destination:
       host: worker-service.default.svc.cluster.local    
       subset: burst 
       port:
         number: 80       
     weight:  100

Escribe natural.yaml

Consideraremos este el estado "natural" al que volveremos cuando las solicitudes por segundo de frontend a worker disminuyan a menos de 10. En este estado, el 100% del tráfico se enruta al clúster primary.

Crea un archivo nuevo llamado natural.yaml y, luego, inserta el siguiente contenido en él:

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
 name: worker-virtual-service
spec:
 hosts:
 - worker-service
 gateways:
 - mesh
 http:
 - route:
   - destination:
       host: worker-service.default.svc.cluster.local   
       subset: primary
       port:
         number: 80       
     weight: 100
   - destination:
       host: worker-service.default.svc.cluster.local    
       subset: burst 
       port:
         number: 80       
     weight: 0

Cómo compilar y enviar istiowatcher

Ejecuta el siguiente comando para enviar el directorio actual a Google Cloud Build (GCB), que compilará y etiquetará la imagen en GCR.

gcloud builds submit -t gcr.io/${GCLOUD_PROJECT}/istiowatcher

Implementa istiowatcher

Cambia al directorio kubernetes

cd ${proj}/kubernetes/

Escribe un archivo de implementación: istiowatcher.yaml

Crea un archivo llamado istiowatcher.yaml y, luego, inserta el siguiente contenido (reemplaza <your-project-id>).

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: istiowatcher-deployment
  labels:
    app: istiowatcher
spec:
  replicas: 1
  selector:
    matchLabels:
      app: istiowatcher
  template:
    metadata:
      labels:
        app: istiowatcher
    spec:
      serviceAccountName: istio-pilot-service-account
      automountServiceAccountToken: true
      containers:
      - name: istiowatcher
        image: gcr.io/<your-project-id>/istiowatcher
        imagePullPolicy: Always

Implementación

Asegúrate de que estemos ejecutando en el clúster principal

kubectx primary

Implementa istiowatcher.yaml en el espacio de nombres istio-system

kubectl apply -n istio-system -f istiowatcher.yaml

Es importante tener en cuenta las directivas serviceAccountName y automountServiceAccountToken en el archivo yaml. Esto nos brinda las credenciales necesarias para ejecutar istioctl desde el clúster.

También debemos implementar esto dentro del espacio de nombres istio-system para asegurarnos de tener las credenciales para istio-pilot-service-account. (no existe en el espacio de nombres default).

Mira cómo se cambia el tráfico automáticamente.

Ahora llega el momento mágico. Vayamos a nuestro frontend y aumentemos la cantidad de solicitudes por segundo a 20.

Observa que tarda unos segundos, pero se acelera y todos nuestros valores hash tienen el prefijo “bursty”.

Esto se debe a que estamos tomando muestras de Prometheus en el rango 15s, lo que hace que nuestro tiempo de respuesta se retrase un poco. Si quisiéramos una banda mucho más ajustada, podríamos cambiar nuestra consulta a prometheus para que sea 5s..

18. Próximos pasos

Haz limpieza

No es necesario que realices la limpieza si usas una cuenta temporal proporcionada para este taller.

Puedes borrar tus clústeres de Kubernetes, la regla de firewall y las imágenes de GCR.

gcloud container clusters delete primary --zone=us-west1-a
gcloud container clusters delete burst --zone=us-west1-a
gcloud compute firewall-rules delete istio-multicluster-test-pods 
gcloud container images delete gcr.io/$GCLOUD_PROJECT/istiowatcher

Qué sigue