Crear una API simple con Python, Flask y Google Cloud Kubernetes Engine

Por: Andrew Copley, Solutions Architect en Apiux Tecnología
Es irlandés y vive en Chile hace varios años. Tiene un PhD en Física del Imperial College de Londres y hace parte del equipo fundador de Apiux. Es de los que va a la oficina todos los días. Le gusta caminar, pero disfruta más pedalear. En este blog explicará más sobre Kubernetes. 

 

En mi último artículo vimos que es bastante fácil crear una API usando una herramienta de orquestación de contenedores, Docker Compose. Sin embargo, si queremos una aplicación que sea capaz de responder a cambios en la carga experimentada y de forma dinámica, tendremos que usar una herramienta mucho más poderosa, Kubernetes, y en nuestro caso, su implementación en la nube de Google, Google Cloud Kubernetes Engine (GKE). 

 

La idea es introducir los conceptos básicos de GKE. El producto en sí es gigante y es posible solo dar una vista preliminar de los conceptos. Sin embargo, uno no tiene que dominar GKE para aprovechar su potencial. 

 

¿Qué es Kubernetes?

 

Kubernetes fue desarrollado internamente por Google para aliviar problemas de escalabilidad y disponibilidad en sus servicios core como Gmail, Google Maps etc. 

 

Fue tan exitosa que Google decidió entregarlo al Cloud Native Computing Foundation (CNCF) en 2018. 

 

Básicamente un despliegue de una aplicación en Kubernetes consiste en un conjunto de objetos, deployments, services, load balancers, configmaps, etc, definidos en una forma declarativa en uno o más archivos .yaml. 

 

Luego estos archivos se aplican a un cluster de Nodes (VMs), se levantan uno o más Pods (la unidad básica de un despliegue) y dentro de cada Pod corren uno o más contenedores.

 

 

Para acceder a la funcionalidad de los Pods, se definen servicios como LoadBalancers que permiten exponer la app al internet. 

 

Todos estos objetos (Pods, Services, Nodes) se controlan a través de un plano de control (Control Plane) y el usuario Administrador ocupa una ejecutable llamado kubectl para hablar con la API de este Control Plane.

 

Arquitectura de despliegue

 

Previamente nuestra arquitectura era como se ve en la imagen de abajo. Sin embargo, cuando movimos a GKE, necesitamos intercambiar algunos componentes. Vea la tabla abajo.

 

 

Componente Docker Compose

Componente GKE

Comentario

Nginx Container Servicio External UDP/TCP Loadbalancer No es un reemplazo exacto, dado que el LoadBalancer no es reverse proxy sino pass-through L4. Es decir, no tiene acceso al URL de la llamada http.
Web Application Container Uno o más Pods Cada Pod va a tener un contenedor con instancia de la App Flask.
Postgres Container Un solo Pod En este caso solo usamos un solo Pod porque el demonio de Postgresql previene que la BdeD pueda ser accedido por otro demonio.
Data Volume PersistentStorageClaim GKE se encarga de proveer espacio según la definición del Claim.
.env Uno o más ConfigMaps ConfigMaps son una especie de almacenamiento dentro del Cluster. En este caso lo usamos para guardar los variables de entorno.
Docker-compose.yaml Uno o más .yaml de Pods, Services, Configmaps etc.  

 

Entonces, con estos cambios, la nueva arquitectura es así:

 

 

Y sus elementos son los siguientes:

 

  • Un cluster simple-python-api. La colección de Nodes (VMs) dentro de la cual corren la instancia de GKE y nuestro despliegue.

 

  • Un External UDP/TCP Loadbalancer service, flask-service. Este objeto nos provee una dirección IP externa para llegar a nuestra aplicación Flask y también ofrece un balanceador de carga al nivel L4.

 

  • Un deployment de Pods, flask. Dentro de estos Pods corren instancias de nuestra aplicación flask en contenedores. La aplicación flask responde a las llamadas http recibidas desde el servicio flask-service. Típicamente un Pod contiene un solo contenedor. 

 

  • Un servicio tipo Cluster IP, postgres-service, que gestiona las peticiones de datos desde el deployment, flask. El servicio recibe llamadas en el puerto 5432 y las traspasa al deployment postgres. A diferencia del servicio en B, un servicio Cluster IP no está disponible fuera del cluster.

 

  • Un deployment de un solo Pod, postgres. Este deployment es de un solo Pod con uno solo contenedor dentro de lo cual corre una instancia del demonio de Postgresql.

 

  • Un Persistent Volume Claim (PVC) postgres-pv. El deployment postgres ocupa este PVC para guardar los datos. Funciona de forma muy similar a los ‘named volumes’ en Docker Compose.

 

  • Un ConfigMap, postgres-conf, en donde se guardan las variables de entorno para el Pod postgres.

 

  • Un ConfigMap, flask-conf, en donde se guardan las variables de entorno para los Pods flask. Se declaran de forma key:value.

 

Te puede interesar: Testing de aplicaciones en React

 

Pasos para el despliegue

 

  1. Accede a GCP y Cloud Shell
  2. Crear nueva carpeta en GCP Cloud Shell y clonar repositorio GitHub
  3. Crear imagen Docker y empujar a Container Registry en GCP
  4. Crear Cluster en GKE
  5. Crear PersistentVolumeClaims en cluster
  6. Crear ConfigMaps en cluster
  7. Crear Deployments y Services en Cluster

 

1. Accede a GCP y Cloud Shell

 

Para acceder a GCP y Cloud Shell, uno tiene que tener una cuenta developer Google. Google ofrece un crédito de $300 a través del siguiente enlace https://cloud.google.com/free.

 

Una vez que esté configurada la cuenta GCP, hay que crear un Proyecto. Todo lo que ocurre en GCP, ocurre dentro de un Proyecto y es el nivel más alto de cobranza dentro de GCP.

 

Seleccionar su nuevo proyecto desde la lista desplegable de Proyectos e iniciar el Cloud Shell usando el icono CLI a la derecha.

 

 

Vamos a configurar nuestro Proyecto por defecto y nuestra Compute Zone por defecto con:

 

gcloud config set compute/zone us-central1-a

 

y

 

gcloud config set project <mi-nombre-proyecto>

 

Para permitir la creación de nuestro despliegue, también tenemos que habilitar unos servicios de GCP con:

 

gcloud services enable cloudbuild.googleapis.com container.googleapis.com containerregistry.googleapis.com compute.googleapis.com storage.googleapis.com

 

Este comando habilita los siguientes servicios dentro de tu cuenta: Cloud Build, Kubernetes, Container Registry, Compute Engine y Cloud Storage.

 

2. Crear una nueva carpeta en GCP Cloud Shell y clonar repositorio GitHub:

 

Dentro de tu carpeta home/<mi_nombre_usuario>/ crear nueva carpeta y ingresar dicha carpeta con: 

 

mkdir mi_api_k8s && cd mi_api_k8s

 

Ahora, clonar repositorio de código con:

 

git clone https://github.com/agcopley/SimplePythonApiK8s.git .

 

Revisar y confirmar estructura abajo:

 

 

Ahora, estamos en condiciones para crear nuestra imagen Docker y empujarla a GCP.

 

3. Crear imagen en Docker y empujar a Container Registry en GCP:

 

El comando gcloud builds submit no solo crea la imagen sino también la empuja al Container repository en GCP.

 

Revisemos en la consola GCP, a través de la opción menú de Container Registry. ¡No olvides el punto a final!

 

gcloud builds submit -t gcr.io/<mi_proyect>/k8s_api/simplepythonapik8s:v0.1.0 .

 

 

4. Crear Cluster en GKE

 

El próximo paso es crear el cluster en sí. Vamos a crear un cluster regional que nos da algo de redundancia en nuestros Nodes. 

 

gcloud container clusters create «simple-python-api» \
–region «us-central1» \
–machine-type «e2-standard-2» –disk-type «pd-standard» –disk-size «100» \
–num-nodes «1» –node-locations «us-central1-b»,»us-central1-c» \
–enable-autoscaling –min-nodes «1» –max-nodes «2»

 

Examinemos el comando arriba. Vamos a crear un cluster regional de tipo estándar en us-central1. 

 

El hecho que es un cluster regional nos replica el Control Plane por las tres zonas (a, b y c) en la región. 

 

Se especifica, para los Nodes, una máquina tipo e2-standard-2 con dos cores usando un permanente disk (pd) de 100Gb. 

 

Después se especifican la cantidad de Nodes y sus ubicaciones, en Zones us-central1-b y us-central1-c, dando 2 Nodes en total y con habilitar auto escalamiento se permite un máximo de 2 Nodes en cada zona, según la carga del cluster.

 

Aquí vemos en la consola, a través de la opción menú Kubernetes Engine:

 

 

También, en el Cloud Shell podemos revisar el estatus de nuestro cluster con:

 

gcloud container clusters list –region=us-central1

 

Finalmente, vamos a recuperar las credenciales de este cluster, que nos van a permitir interactuar con el cluster en sí en los siguientes pasos:

 

gcloud container clusters get-credentials simple-python-api –region us-central1

 

Devolviendo un mensaje de:

 

 

¿Cómo interactuar con un cluster en Cloud Shell? Google Cloud SDK, que está instalado por defecto en Cloud Shell provee un ejecutable kubectl, que nos permite interactuar con los Nodes y Pods, etc.

 

En el caso de que tengamos más de un cluster, hay que especificar el contexto actual. 

 

Cuando se corrió el comando de get-credentials, arriba, se actualizó el current-context para kubectl y kubectl config current-context nos confirma que el contexto es de nuestro cluster con devolver su nombre completo gke_<mi_proyecto>_<region>_<nombre_cluster>

 

Antes de seguir, revisar los Nodes del cluster con kubectl get nodes y confirmar los 2 Nodes:

 

 

Te puede interesar: Claves para implementar con éxito la Arquitectura de microservicios

 

5. Crear PersistentVolumeClaims en cluster

 

Antes de hacer un despliegue con nuestros deployments de postgres y flask, hay que crear el Persistent Volume, en lo cual el Pod de postgres guarda los datos de la base de datos.

 

El archivo postgres-pv.yaml tiene la definición:

 

kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: regionalpd-storageclass
provisioner: kubernetes.io/gce-pd
parameters:
type: pd-standard
  replication-type: regional-pd
allowedTopologies:
  – matchLabelExpressions:
    – key: failure-domain.beta.kubernetes.io/zone
      values:
        – us-central1-b
        – us-central1-c

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: postgresql-pv
spec:
  storageClassName: regionalpd-storageclass
  accessModes:
    – ReadWriteOnce
  resources:
    requests:
      storage: 200Gi

 

Hay 2 secciones: La primera especifica el tipo de almacenamiento que se necesita, con una definición de StorageClass. 

 

En nuestro caso, hemos especificado el tipo de almacenamiento pd-standard, que se basa en discos duros de platos. Con kubectl get storageclass, podemos examinar las otras opciones:

 

 

Se nota, que hay unas opciones rwo – por ejemplo, premium-rwo nos ofrece almacenamiento SSD. 

 

La segunda sección nos define un Persistent Volume a través de un Claim. Se usa el Storage Class definido en la primera sección y se pide 200Gi de espacio. 

 

Se nota que hay una declaración de accessModes: ReadWriteOnly. Esto significa que solo un Node a la vez puede tener acceso a ReadWrite. 

 

En nuestro caso está bien, pero si se necesita un modo ReadWriteMany se puede considerar Cloud Filestore o se puede considerar un NFS proxy (En otro blog será)

 

Vamos a aplicar nuestro archivo con:

 

kubectl apply -f postgres-pv.yaml

 

Recibiendo el mensaje de confirmación:

 

 

También podríamos examinar con kubectl get pvc, devolviendónos:

 

 

Ahora podremos seguir con los ConfigMap para los deployments postgres y flask:

 

6. Crear ConfigMaps en cluster

 

Usando los archivos postgres-conf.yaml y flask-conf.yaml podemos examinar cómo se definen los ConfigMap:

 

apiVersion: v1
kind: ConfigMap
metadata:
name: postgres-conf
namespace: default
data:
POSTGRES_PASSWORD: mtt
POSTGRES_USER: mtt
POSTGRES_DB: mtt_blog
PGDATA: /var/lib/postgresql/data/pgdata

 

Se define un tipo de ConfigMap, con un nombre y namespace y después, en la sección data, se especifican las variables. 

 

Cuando se levanta un Pod, se incluyen las variables de entorno, usando el mapeo. El ConfigMap para flask es muy parecido, conteniendo una variable adicional especificando POSTGRES_HOST. 

 

Se aplican al cluster con los siguientes comandos:

 

kubectl apply -f postgres-conf.yaml

y

 

kubectl apply -f flask-conf.yaml

 

Se confirma su creación exitosa con kubectl get configmaps, devolviendo nuestros dos ConfigMaps, más un ConfigMap del cluster:

 

 

Con esto, estamos listos para comenzar con los deployments y services.

 

7. Crear Deployments y Services en Cluster

 

¿Qué es un Deployment? Un Deployment es una agrupación lógica de Pods, que entrega una funcionalidad específica.

 

Por ejemplo, un Deployment puede representar un solo microservicio, dentro de una aplicación de muchos microservicios. 

 

Cada microservicio, entonces, es una colección de Pods, todos ofreciendo la misma funcionalidad. Dado que, posiblemente, hay más de un Pod, no se habla directamente con un Pod, sino con una capa de abstracción que sabe de los Pods, un Service. 

 

Entonces, cada microservicio consiste en un Service y un Deployment. Examinemos el contenido de postgres-deployment.yaml para ver la anatomía de un Deployment:

 

apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres
spec:
strategy:
  rollingUpdate:
    maxSurge: 1
    maxUnavailable: 1
  type: RollingUpdate
replicas: 1
selector:
  matchLabels:
    app: postgres
template:
  metadata:
    labels:
      app: postgres
  spec:
    containers:
      – name: postgres
        image: postgres:10
        resources:
          limits:
            cpu: «1»
            memory: «4Gi»
          requests:
            cpu: «1»
            memory: «4Gi»
        ports:
          – containerPort: 5432
        envFrom:
        – configMapRef:
            name: postgres-conf          
        volumeMounts:
          – mountPath: /var/lib/postgresql/data
            name: postgredb
    volumes:
      – name: postgredb
        persistentVolumeClaim:
          claimName: postgresql-pv

 

La definición consiste en un sección spec en donde se especifican:

 

  • RollingUpdate: Una estrategia de actualizar los Pods, si hay una nueva imagen.
  • Réplicas: La cantidad de Pods que se requieren. Kubernetes intenta mantener esta cantidad siempre disponible como estado deseable. Si la cantidad baja de este número, GKE intentará agendar un nuevo Pod en uno de los Nodes para compensar.
  • Selector: Buscar plantilla de definición Pods etiquetado con app: postgres

 

Después viene la sección, template, que consiste en una o más definiciones de contenedores. La sección template viene etiquetada con app: postgres, así que va a ser incluida como parte del Deployment. 

 

En nuestro caso, solo tenemos uno solo contenedor por Pod, definido en la subsección containers:

 

  • Image: Imagen base postgres:10 desde el repositorio de imágenes publicas en GCP
  • Resources: Pedir 4Gi de memoria del Node al levantarse y con una máxima de 4Gi.
  • Ports: Exponer la instancia de postgres en puerto 5432
  • envFrom: Conseguir variables de entorno desde un ConfigMap llamado postgres-conf, creado antes.
  • VolumeMounts: De manera parecida a Docker Compose, especificar un volume para la Base de Datos de Postgresql.

Finalmente, hay una sección volumes, en donde usamos el PersistentVolumeClaim, postgres-pv, creado arriba, como espacio para nuestro volume. Apliquemos el Deployment al cluster con:

 

kubectl apply -f postgres-deployment.yaml

 

y revisar con:

 

kubectl get deployments

 

 

También lo podemos revisar en la consola GCP, a través del menú Kubernetes Engine/Workloads.

 

 

Investiguemos con kubectl get pods en Shell que tenemos un Pod disponible, y conseguimos el nombre generado de este Pod. 

 

 

Usando el nombre del Pod, el comando kubectl describe pods <nombre-pod> nos devuelve muchos detalles.

Ahora, necesitamos agregar un Service a nuestro cluster que hace referencia a nuestro conjunto de Pods postgresql (solo 1 Pod en este caso) y permite que la base de datos esté accesible desde otros Deployments. Veamos el contenido del archivo postgres-service.yaml.

 

apiVersion: v1
kind: Service
metadata:
  name: postgres-service
spec:
  type: ClusterIP
  selector:
    app: postgres
  ports:
    – protocol: TCP
      port: 5432
      targetPort: 5432

 

Las partes importantes de la definición del servicio son el type: ClusterIP, que nos da una dirección IP interna fija y el selector app: postgres, que busca los Pods etiquetados con app: postgres. 

 

GKE tiene 3 tipos de servicio, de los cuales, el tipo ClusterIP no permite acceso desde fuera del cluster al Pod de postgres. 

 

Al servicio postgres-service solo se accede desde el Deployment flask, a través de la variable de entorno POSTGRES_HOST. Apliquemoslo, como siempre, con kubectl:

 

kubectl apply -f postgres-service.yaml

 

Y revisemos con:

 

kubectl get services

 

 

Y en la consola, a través del menú Kubernetes Engine/Service & Ingress. Se nota la IP interna del servicio y la cantidad de Pods, 1, corriendo bajo este servicio.

 

 

Ahora nos toca armar nuestro Deployment, flask y su Service, flask-service. El Deployment es bastante parecido a lo de postgres, salvo por donde vamos a conseguir la imagen base. 

 

Tendremos que especificar la imagen que creamos en el Container Repository. Al acceder por la consola a nuestra imagen, el menú hamburguesa nos permite obtener el comando Pull de esta imagen:

 

 

Cualquier de los dos comandos nos sirve, pero nos interesa la parte final, sin docker pull. En mi caso es gcr.io/<mi-proyecto>/k8s_api/simplepythonapik8s:v0.1.0. 

 

En el archivo flask- deployment.yaml reemplazar el tag, image, para que vea así, y aplica con kubectl apply -f flask – deployment.yaml:

 

   containers:
      – name: flask
        image: gcr.io/tutorial-ms-gcp/k8s_api/simplepythonapik8s:v0.1.0
        resources:
          limits:
            cpu: «1»
            memory: «4Gi»
          requests:
            cpu: «1»
            memory: «4Gi»
        ports:
          – containerPort: 5000
        envFrom:
        – configMapRef:
            name: flask-conf

 

No olvide revisar con kubectl get deployments. Debiese ver algo así:

 

 

La parte final de la configuración de nuestra API es montar un servicio sobre los Pods flask. Dentro del archivo flask-service.yaml, encontramos la definición del Service, flask-service:

 

apiVersion: v1
kind: Service
metadata:
  name: flask-service
spec:
  type: LoadBalancer
  selector:
    app: flask
  ports:
    – protocol: TCP
      port: 80
      targetPort: 5000

 

Las dos partes importantes son el selector, en que estamos buscando Pods etiquetados con app: flask y el tipo de servicio, LoadBalancer. 

 

Este tipo nos va a proveer un LoadBalance UDP/TCP externo con una dirección IP externa. Esta IP externa forma la IP externa de nuestro endpoint y nos va a permitir acceder desde el internet público, fuera de GCP. 

 

También tenemos el mapeo de puertos, exponiendo puerto 80 y pasando el tráfico a puerto 5000 de nuestro contenedor. Al aplicar con kubectl apply -f flask-service.yaml y revisar con kubectl get services, se debiese ver algo así, con el flask-service con IP Externa especificada:

 

 

Y tal como hicimos en mi blog anterior, lo probamos con Postman, colocando la IP externa de nuestro servicio flask-service en el Url del endpoint en Postman:

 

 

Conclusión

 

Se introdujeron los conceptos claves de un despliegue dentro de Google Kubernetes Engine: Los Nodes, Pods, Deployments, Services, PersistentVolumes y ConfigMaps. 

 

Sin embargo, es claro que no es una arquitectura de producción. Por ejemplo, nuestro LoadBalancer es un balanceador de carga pass-thru a nivel L4, y lo que necesitamos es un LoadBalancer HTTP Reverse Proxy. 

 

Eso requiere otro componente de Google Kubernetes Engine, Ingress o Gateway, que examinamos en un blog siguiente. También nuestra Base de Datos postgresql, aunque cuenta con algo de redundancia, para nada cuenta con escalabilidad por la limitación de que solo un Pod de postgresql puede interactuar con el PersistentVolume a la vez. 

 

Sería mucho mejor colocar la Base de Datos en Cloud Sql, un ‘managed service’ en GCP y usar un Cloud Sql Proxy dentro de nuestro Cluster. 

 

Finalmente, nos queda pendiente investigar los conceptos de escalabilidad y despliegues automatizados con Cloud Build. 

 

¡Así que muchas gracias y nos vemos en el siguiente Blog!

 

Espero que este blog te haya servido. Te invito a ser parte de nuestra comunidad de desarrolladores. Da clic en el botón y revisa todas nuestras vacantes disponibles. Sé un yellower.

 

Apiux Jobs
No Comments

Sorry, the comment form is closed at this time.