Prepara una aplicación para Anthos Service Mesh


Anthos Service Mesh es una herramienta potente para administrar y supervisar aplicaciones distribuidas. Para aprovechar al máximo Anthos Service Mesh, es útil comprender sus abstracciones subyacentes, incluidos los contenedores y Kubernetes. En este instructivo, se explica cómo preparar una aplicación para Anthos Service Mesh desde el código fuente a un contenedor que se ejecuta en GKE, hasta el punto justo antes de instalar Anthos Service Mesh.

Si ya estás familiarizado con los conceptos de Kubernetes y la malla de servicios, puedes omitir este instructivo y dirigirte directamente a la guía de instalación de Anthos Service Mesh.

Objetivos

En este instructivo, harás lo siguiente:

  1. Explorarás una aplicación “Hello World” simple de varios servicios.
  2. Ejecutarás la aplicación desde la fuente.
  3. Organizarás la aplicación en contenedores.
  4. Crearás un clúster de Kubernetes.
  5. Implementarás los contenedores en el clúster.

Antes de comenzar

Sigue estos pasos para habilitar la API de Anthos Service Mesh:
  1. Visita la página de Kubernetes Engine en la consola de Google Cloud.
  2. Crea o selecciona un proyecto.
  3. Espera a que la API y los servicios relacionados se habiliten. Esto puede tomar varios minutos.
  4. Make sure that billing is enabled for your Google Cloud project.

En este instructivo, se usa Cloud Shell, que aprovisiona una máquina virtual (VM) g1-small de Google Compute Engine que ejecuta un sistema operativo Linux basado en Debian.

Prepara Cloud Shell

Las ventajas de usar Cloud Shell son las siguientes:

  • Los entornos de desarrollo de Python 2 y Python 3 (incluido virtualenv) están configurados.
  • Las herramientas de línea de comandos de gcloud, docker, git y kubectl que se usan en este instructivo ya están instaladas.
  • Puedes elegir entre los editores de texto:

    • El editor de código, al que puedes acceder desde  en la parte superior de la ventana de Cloud Shell

    • Emacs, Vim o Nano, a los que puedes acceder desde la línea de comandos en Cloud Shell.

Para usar Cloud Shell, sigue estos pasos:

  1. Ve a la consola de Google Cloud.
  2. Haz clic en el botón Activar Cloud Shell en la parte superior de la consola de Google Cloud.

    Google Cloud Platform Console

    Se abrirá una sesión de Cloud Shell en un marco nuevo en la parte inferior de la consola de Google Cloud, que mostrará una ventana de la línea de comandos.

    Sesión de Cloud Shell

Descarga el código de muestra

  1. Descarga el código fuente de helloserver:

    git clone https://github.com/GoogleCloudPlatform/istio-samples
    
  2. Cambia al directorio del código de muestra:

    cd istio-samples/sample-apps/helloserver
    

Explora la aplicación de varios servicios

La aplicación de muestra está escrita en Python y tiene dos componentes que se comunican mediante REST:

  • server: Es un servidor simple con un extremo GET, /, que imprime “Hello World” en la consola.
  • loadgen: Es una secuencia de comandos que envía tráfico al server, con una cantidad configurable de solicitudes por segundo (RPS).

Aplicación de muestra

Ejecuta la aplicación desde la fuente

Para familiarizarte con la aplicación de ejemplo, ejecútala en Cloud Shell.

  1. Desde el directorio sample-apps/helloserver, ejecuta el server:

    python3 server/server.py
    

    En el inicio, el server muestra lo siguiente:

    INFO:root:Starting server...
    
  2. Haz clic en para abrir otra sesión.

  3. Envía una solicitud al server:

    curl http://localhost:8080
    

    Esta es la respuesta del server:

    Hello World!
    
  4. Desde el directorio en el que descargaste el código de muestra, cambia al directorio que contiene loadgen:

    cd YOUR_WORKING_DIRECTORY/istio-samples/sample-apps/helloserver/loadgen
    
  5. Crea las siguientes variables de entorno:

    export SERVER_ADDR=http://localhost:8080
    export REQUESTS_PER_SECOND=5
    
  6. Inicia virtualenv:

    virtualenv --python python3 env
    
  7. Activa el entorno virtual:

    source env/bin/activate
    
  8. Instala los requisitos de loadgen:

    pip3 install -r requirements.txt
    
  9. Ejecuta loadgen:

    python3 loadgen.py
    

    En el inicio, loadgen genera un mensaje similar al siguiente:

    Starting loadgen: 2019-05-20 10:44:12.448415
    5 request(s) complete to http://localhost:8080
    

    En la otra ventana de la terminal, el server escribe mensajes similares al siguiente en la consola:

    127.0.0.1 - - [21/Jun/2019 14:22:01] "GET / HTTP/1.1" 200 -
    INFO:root:GET request,
    Path: /
    Headers:
    Host: localhost:8080
    User-Agent: python-requests/2.22.0
    Accept-Encoding: gzip, deflate
    Accept: */*
    

    Desde el punto de vista de las herramientas de redes, toda la aplicación ahora se ejecuta en el mismo host. Por este motivo, puedes usar localhost para enviar solicitudes a server.

  10. Para detener loadgen y el server, ingresa Ctrl-c en cada ventana de la terminal.

  11. En la ventana de la terminal de loadgen, desactiva el entorno virtual:

    deactivate
    

Organiza la aplicación en contenedores

Para ejecutar la aplicación en GKE, debes empaquetar la aplicación de muestra, el server y loadgen, en containers. Un contenedor es una forma de empaquetar una aplicación de modo que esté aislada del entorno subyacente.

Para organizar la aplicación en contenedores, necesitas un Dockerfile. Un Dockerfile es un archivo de texto que define los comandos necesarios para juntar el código fuente de la aplicación y sus dependencias en una imagen de Docker. Después de compilar la imagen, debes subirla a un registro de contenedores, como Docker Hub o Container Registry.

La muestra incluye un Dockerfile para el server y loadgen con todos los comandos necesarios a fin de compilar las imágenes. A continuación, se incluye el Dockerfile para el server:

FROM python:3.9-slim as base
FROM base as builder
RUN apt-get -qq update \
    && apt-get install -y --no-install-recommends \
        g++ \
    && rm -rf /var/lib/apt/lists/*

# Enable unbuffered logging
FROM base as final
ENV PYTHONUNBUFFERED=1

RUN apt-get -qq update \
    && apt-get install -y --no-install-recommends \
        wget

WORKDIR /helloserver

# Grab packages from builder
COPY --from=builder /usr/local/lib/python3.9/ /usr/local/lib/python3.9/

# Add the application
COPY . .

EXPOSE 8080
ENTRYPOINT [ "python", "server.py" ]
  • Con el comando FROM python:3-slim as base, se le indica a Docker que use la imagen de Python 3 más reciente como imagen base.
  • Con el comando COPY . ., se copian los archivos fuente del directorio de trabajo actual (en este caso, solo server.py) en el sistema de archivos del contenedor.
  • El ENTRYPOINT define el comando que se usa para ejecutar el contenedor. En este caso, el comando es casi el mismo que usaste para ejecutar server.py desde el código fuente.
  • Con el comando EXPOSE, e especifica que el server escucha en el puerto 8080. Este comando no expone ningún puerto, pero sirve como documentación que necesitas para abrir el puerto 8080 cuando ejecutas el contenedor.

Prepárate para organizar la aplicación en contenedores

  1. Configura las siguientes variables de entorno: Reemplaza PROJECT_ID por el ID de tu proyecto de Google Cloud.

    export PROJECT_ID="PROJECT_ID"
    
    export GCR_REPO="asm-ready"
    

    Usa el valor de PROJECT_ID y GCR_REPO para etiquetar la imagen de Docker durante la compilación y envía la imagen a tu Container Registry privado.

  2. Configura el proyecto de Google Cloud predeterminado para Google Cloud CLI.

    gcloud config set project $PROJECT_ID
    
  3. Establece la zona predeterminada para la CLI de Google Cloud.

    gcloud config set compute/zone us-central1-b
    
  4. Asegúrate de que el servicio de Container Registry esté habilitado en tu proyecto de Google Cloud.

    gcloud services enable containerregistry.googleapis.com
    

Crea contenedores para el server

  1. Cambia al directorio en el que se encuentra el server de muestra:

    cd YOUR_WORKING_DIRECTORY/istio-samples/sample-apps/helloserver/server/
    
  2. Compila la imagen mediante el Dockerfile y las variables de entorno que definiste antes:

    docker build -t gcr.io/$PROJECT_ID/$GCR_REPO/helloserver:v0.0.1 .
    

    La marca -t representa la etiqueta de Docker. Este es el nombre de la imagen que debes usar en la implementación del contenedor.

  3. Envía la imagen a Container Registry:

    docker push gcr.io/$PROJECT_ID/$GCR_REPO/helloserver:v0.0.1
    

Crea contenedores para loadgen

  1. Cambia al directorio en el que se encuentra la muestra de loadgen:

    cd ../loadgen
    
  2. Compila la imagen:

    docker build -t gcr.io/$PROJECT_ID/$GCR_REPO/loadgen:v0.0.1 .
    
  3. Envía la imagen a Container Registry:

    docker push gcr.io/$PROJECT_ID/$GCR_REPO/loadgen:v0.0.1
    

Obtén una lista de las imágenes

Obtén una lista de las imágenes en el repositorio para confirmar que se enviaron:

gcloud container images list --repository gcr.io/$PROJECT_ID/asm-ready

El comando responde con los nombres de las imágenes que acabas de enviar:

NAME
gcr.io/PROJECT_ID/asm-ready/helloserver
gcr.io/PROJECT_ID/asm-ready/loadgen

Crea un clúster de GKE

Puedes ejecutar estos contenedores en la VM de Cloud Shell mediante el comando docker run. Sin embargo, en la producción, debes organizar los contenedores de una manera más unificada. Por ejemplo, necesitas un sistema que se asegure de que los contenedores estén siempre en ejecución, además de una forma de escalar verticalmente y, luego, iniciar instancias adicionales de un contenedor para controlar los aumentos de tráfico.

Puedes usar GKE para ejecutar aplicaciones en contenedores. GKE es una plataforma de organización de contenedores que funciona mediante la conexión de VM a un clúster. Cada VM se conoce como un nodo. Los clústeres de GKE funcionan con el sistema de administración de clústeres de código abierto de Kubernetes. Kubernetes proporciona los mecanismos que usas para interactuar con el clúster.

Creación de un clúster de GKE:

  1. Crea el clúster:

    gcloud container clusters create asm-ready \
      --cluster-version latest \
      --machine-type=n1-standard-4 \
      --num-nodes 4
    

    El comando gcloud crea un clúster en el proyecto y la zona de Google Cloud que configuraste antes. Para ejecutar Anthos Service Mesh, recomendamos tener al menos 4 nodos y el tipo de máquina n1-standard-4.

    El comando para crear el clúster toma unos minutos en completarse. Cuando el clúster está listo, el comando genera un mensaje similar al siguiente:

    NAME        LOCATION       MASTER_VERSION  MASTER_IP      MACHINE_TYPE   NODE_VERSION   NUM_NODES  STATUS
    asm-ready  us-central1-b  1.13.5-gke.10   203.0.113.1    n1-standard-2  1.13.5-gke.10  4          RUNNING
    
  2. Proporciona credenciales a la herramienta de línea de comandos de kubectl para que puedas usarla a fin de administrar el clúster:

    gcloud container clusters get-credentials asm-ready
    
  3. Ahora puedes usar kubectl para comunicarte con Kubernetes. Por ejemplo, puedes ejecutar el siguiente comando para obtener el estado de los nodos:

    kubectl get nodes
    

    El comando da como resultado una lista de los nodos, similar a la siguiente:

    NAME                                       STATUS   ROLES    AGE    VERSION
    gke-asm-ready-default-pool-dbeb23dc-1vg0   Ready    <none>   99s    v1.13.6-gke.13
    gke-asm-ready-default-pool-dbeb23dc-36z5   Ready    <none>   100s   v1.13.6-gke.13
    gke-asm-ready-default-pool-dbeb23dc-fj7s   Ready    <none>   99s    v1.13.6-gke.13
    gke-asm-ready-default-pool-dbeb23dc-wbjw   Ready    <none>   99s    v1.13.6-gke.13
    

Comprende los conceptos clave de Kubernetes

En el siguiente diagrama, se muestra la aplicación que se ejecuta en GKE:

Una aplicación en contenedores

Antes de implementar los contenedores en GKE, te recomendamos revisar algunos conceptos clave de Kubernetes. Al final de este instructivo, se proporcionan vínculos para que puedas obtener más información sobre cada concepto.

  • Nodos y clústeres: en GKE, un nodo es una VM. En otras plataformas de Kubernetes, un nodo puede ser una máquina física o virtual. Un clúster es un conjunto de nodos que se pueden tratar en conjunto como una sola máquina y en el que implementas una aplicación en contenedores.

  • Pods: en Kubernetes, los contenedores se ejecutan dentro de un Pod. Un Pod es la unidad atómica en Kubernetes. Un Pod contiene uno o más contenedores. Debes implementar los contenedores de server y loadgen, cada uno en su propio Pod. Cuando un Pod ejecuta varios contenedores (por ejemplo, un servidor de aplicaciones y un servidor proxy), los contenedores se administran como una sola entidad y comparten los recursos del Pod.

  • Deployments: Un Deployment es un objeto de Kubernetes que representa un conjunto de Pods idénticos. Un Deployment ejecuta varias réplicas de los Pods distribuidos entre los nodos de un clúster. Un Deployment reemplaza de forma automática cualquier Pod que falla o deja de responder.

  • Service de Kubernetes: la ejecución del código de la aplicación en GKE cambia las herramientas de redes entre loadgen y server. Cuando ejecutaste los servicios en una VM de Cloud Shell, pudiste enviar solicitudes al server con la dirección localhost:8080. Después de realizar la implementación en GKE, los Pods están programados para ejecutarse en los nodos disponibles. De forma predeterminada, no puedes controlar en qué nodo se ejecuta el Pod, por lo que los Pods no tienen direcciones IP estables.

    A fin de obtener una dirección IP para el server, debes definir una abstracción de herramientas de redes además de los Pods, lo que se denomina Service de Kubernetes. Un Service de Kubernetes proporciona un extremo de herramientas de redes estable para un conjunto de Pods. Existen varios tipos de objetos Service. El server usa un LoadBalancer, que expone una dirección IP externa para que puedas llegar al server desde fuera del clúster.

    Kubernetes también tiene un sistema DNS integrado, que asigna nombres de DNS (por ejemplo, helloserver.default.cluster.local) a los objetos Service. Esto permite que los Pods del clúster lleguen a otros Pods dentro del clúster con una dirección estable. No puedes usar este nombre de DNS fuera del clúster, como desde Cloud Shell.

Manifiestos de Kubernetes

Cuando ejecutaste la aplicación desde el código fuente, usaste un comando imperativo: python3 server.py

“Imperativo” significa basado en un verbo: “haz esto”.

Por el contrario, Kubernetes opera en función de un modelo declarativo. Esto significa que, en lugar de indicarle a Kubernetes qué hacer con exactitud, le proporcionas a Kubernetes el estado deseado. Por ejemplo, Kubernetes inicia y finaliza los Pods según sea necesario para que el estado real del sistema coincida con el estado deseado.

Debes especificar el estado deseado en un conjunto de manifiestos o archivos YAML. Un archivo YAML contiene la especificación de uno o más objetos de Kubernetes.

La muestra contiene un archivo YAML para server y loadgen. Cada archivo YAML especifica el estado deseado para los objetos Deployment y Service de Kubernetes.

Ajuste de escala automático

apiVersion: apps/v1
kind: Deployment
metadata:
  name: helloserver
spec:
  replicas: 1
  selector:
    matchLabels:
      app: helloserver
  template:
    metadata:
      labels:
        app: helloserver
    spec:
      containers:
      - image: gcr.io/google-samples/istio/helloserver:v0.0.1
        imagePullPolicy: Always
        name: main
      restartPolicy: Always
      terminationGracePeriodSeconds: 5
  • kind indica el tipo de objeto.
  • metadata.name especifica el nombre del objeto Deployment.
  • El primer campo spec contiene una descripción del estado deseado.
  • spec.replicas especifica la cantidad de Pods deseados.
  • La sección spec.template define una plantilla de Pod. En la especificación para los Pods, se incluye el campo image, que es el nombre de la imagen que se extraerá de Container Registry.

El Service se define de la siguiente manera:

apiVersion: v1
kind: Service
metadata:
  name: hellosvc
spec:
  ports:
  - name: http
    port: 80
    targetPort: 8080
  selector:
    app: helloserver
  type: LoadBalancer
  • LoadBalancer: Los clientes envían solicitudes a la dirección IP de un balanceador de cargas de redes, que tiene una dirección IP estable y a la que se puede acceder desde fuera del clúster.
  • targetPort: Recuerda que el comando EXPOSE 8080 en el Dockerfile no expone ningún puerto. Debes exponer el puerto 8080 para poder acceder al contenedor de server fuera del clúster. En este caso, hellosvc.default.cluster.local:80 (nombre corto: hellosvc) se asigna al puerto 8080 de la IP del Pod helloserver.
  • port: Este es el número de puerto que usan otros servicios en el clúster cuando envían solicitudes.

Generador de cargas

El objeto Deployment en loadgen.yaml es similar a server.yaml. Una diferencia notable es que el objeto Deployment contiene una sección llamada env. En esta sección, se definen las variables de entorno que requiere loadgen, que configuraste antes cuando ejecutaste la aplicación desde la fuente.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: loadgenerator
spec:
  replicas: 1
  selector:
    matchLabels:
      app: loadgenerator
  template:
    metadata:
      labels:
        app: loadgenerator
    spec:
      containers:
      - env:
        - name: SERVER_ADDR
          value: http://hellosvc:80/
        - name: REQUESTS_PER_SECOND
          value: '10'
        image: gcr.io/google-samples/istio/loadgen:v0.0.1
        imagePullPolicy: Always
        name: main
        resources:
          limits:
            cpu: 500m
            memory: 512Mi
          requests:
            cpu: 300m
            memory: 256Mi
      restartPolicy: Always
      terminationGracePeriodSeconds: 5

Debido a que loadgen no acepta solicitudes entrantes, el campo type se establece en ClusterIP. Este tipo proporciona una dirección IP estable que los servicios en el clúster pueden usar, pero la dirección IP no está expuesta a clientes externos.

apiVersion: v1
kind: Service
metadata:
  name: loadgensvc
spec:
  ports:
  - name: http
    port: 80
    targetPort: 8080
  selector:
    app: loadgenerator
  type: ClusterIP

Implementa los contenedores en GKE

  1. Cambia al directorio en el que se encuentra el server de muestra:

    cd YOUR_WORKING_DIRECTORY/istio-samples/sample-apps/helloserver/server/
    
  2. Abre server.yaml en un editor de texto.

  3. Reemplaza el nombre en el campo image por el nombre de tu imagen de Docker.

    image: gcr.io/PROJECT_ID/asm-ready/helloserver:v0.0.1
    

    Reemplaza PROJECT_ID por el ID del proyecto de Google Cloud.

  4. Guarda y cierra server.yaml.

  5. Implementa el archivo YAML en Kubernetes:

    kubectl apply -f server.yaml
    

    Si se ejecuta de forma correcta, el comando responde con lo siguiente:

    deployment.apps/helloserver created
    service/hellosvc created
    

  6. Cambia al directorio en el que se encuentra loadgen.

    cd ../loadgen
    
  7. Abre loadgen.yaml en un editor de texto.

  8. Reemplaza el nombre en el campo image por el nombre de tu imagen de Docker.

    image: gcr.io/PROJECT_ID/asm-ready/loadgen:v0.0.1
    

    Reemplaza PROJECT_ID por el ID del proyecto de Google Cloud.

  9. Guarda y cierra loadgen.yaml, y cierra el editor de texto.

  10. Implementa el archivo YAML en Kubernetes:

    kubectl apply -f loadgen.yaml
    

    Si se ejecuta de forma correcta, el comando responde con lo siguiente:

    deployment.apps/loadgenerator created
    service/loadgensvc created
    

  11. Verifica el estado de los Pods:

    kubectl get pods
    

    El comando responde con un estado similar al siguiente:

    NAME                             READY   STATUS    RESTARTS   AGE
    helloserver-69b9576d96-mwtcj     1/1     Running   0          58s
    loadgenerator-774dbc46fb-gpbrz   1/1     Running   0          57s
    
  12. Obtén los registros de la aplicación desde el Pod loadgen. Reemplaza POD_ID por el identificador del resultado anterior.

    kubectl logs loadgenerator-POD_ID
    
  13. Obtén las direcciones IP externas de hellosvc:

    kubectl get service
    

    La respuesta del comando es similar a la siguiente:

    NAME         TYPE           CLUSTER-IP     EXTERNAL-IP     PORT(S)        AGE
    hellosvc     LoadBalancer   10.81.15.158   192.0.2.1       80:31127/TCP   33m
    kubernetes   ClusterIP      10.81.0.1      <none>          443/TCP        93m
    loadgensvc   ClusterIP      10.81.15.155   <none>          80/TCP         4m52s
    
  14. Envía una solicitud a hellosvc. Reemplaza EXTERNAL_IP por la dirección IP externa de tu hellosvc.

    curl http://EXTERNAL_IP
    

Estás listo para Anthos Service Mesh

Ahora tienes la aplicación implementada en GKE. Es posible que loadgen use el DNS de Kubernetes (hellosvc:80) para enviar solicitudes al server, y tú puedes enviar solicitudes al server con una dirección IP externa. Aunque Kubernetes te brinda muchas funciones, falta información sobre los servicios:

  • ¿Cómo interactúan los servicios? ¿Cuál es la relación entre los servicios? ¿Cómo fluye el tráfico entre los servicios? Sabes que loadgen envía solicitudes al server, pero imagínate que no estás familiarizado con la aplicación. No puedes responder estas preguntas si consultas la lista de Pods en ejecución en GKE.
  • Métricas: ¿Cuánto tarda el server en responder a las solicitudes entrantes? ¿Cuántas solicitudes por segundo (RPS) recibe el server? ¿Hay alguna respuesta de error?
  • Información de seguridad: ¿El tráfico entre loadgen y el server es HTTP sin formato o mTLS?

Anthos Service Mesh puede brindar respuestas a estas preguntas. Anthos Service Mesh es una versión administrada por Google Cloud del proyecto Istio de código abierto. Funciona si se coloca un proxy de sidecar Envoy en cada Pod. El proxy Envoy intercepta todo el tráfico de entrada y de salida de los contenedores de la aplicación. Esto significa que el server y loadgen obtienen un proxy de sidecar Envoy cada uno, y todo el tráfico de loadgen al server está mediado por los proxies Envoy. Las conexiones entre estos proxies Envoy forman la malla de servicios. Esta arquitectura de la malla de servicios proporciona una capa de control sobre Kubernetes.

malla de servicios

Debido a que los proxies Envoy se ejecutan en sus propios contenedores, puedes instalar Anthos Service Mesh en un clúster de GKE sin modificar de manera significativa el código de tu aplicación. Sin embargo, hay algunas maneras clave en las que preparaste la aplicación para que se instrumente con Anthos Service Mesh:

  • Servicios para todos los contenedores: los objetos Deployment de server y loadgen tienen un servicio de Kubernetes adjunto. Incluso el loadgen, que no recibe solicitudes entrantes, tiene un servicio.
  • Los puertos en los servicios deben tener nombre: aunque GKE te permite definir puertos de servicio sin nombre, Anthos Service Mesh requiere que proporciones un nombre para los puertos que coincida con el protocolo del puerto. En el archivo YAML, el puerto del server se llama http porque el server usa el protocolo de comunicación HTTP. Si el service usara gRPC, le asignarías el nombre grpc al puerto.
  • Los objetos Deployment están etiquetados: esto te permite usar las funciones de administración de tráfico de Anthos Service Mesh, como la división del tráfico entre versiones del mismo servicio.

Instale Anthos Service Mesh

Consulta la guía de instalación de Anthos Service Mesh y sigue las instrucciones para instalar Anthos Service Mesh en tu clúster.

Limpia

Para evitar que se apliquen cargos a tu cuenta de Google Cloud por los recursos usados en este instructivo, borra el proyecto que contiene los recursos o conserva el proyecto y borra los recursos individuales.

Para descartar recursos, borra el clúster de GKE. Cuando borras el clúster, se borran todos los recursos que conforman el clúster de contenedor, como las instancias de procesamiento, los discos y los recursos de red.

gcloud container clusters delete asm-ready

¿Qué sigue?