Con este instructivo, aprenderás a implementar un clúster de servidores distribuidos de Memcached en Google Kubernetes Engine (GKE) con Kubernetes, Helm y Mcrouter. Memcached es un sistema de almacenamiento en caché de código abierto y multifuncional de uso muy extendido. Suele funcionar como un almacén temporal de datos que se usan con frecuencia a fin de acelerar las aplicaciones web y aligerar las cargas de bases de datos.
Características de Memcached
Memcached tiene dos objetivos de diseño principales:
- Simplicidad: Memcached funciona como una gran tabla hash y ofrece una API simple con la que puedes almacenar y recuperar objetos de formas arbitrarias por medio de claves.
- Rapidez: Memcached conserva datos almacenados en caché exclusivamente en la memoria RAM, lo que hace que el acceso a los datos sea extremadamente rápido.
Memcached es un sistema distribuido que permite a su tabla hash la capacidad de escalar horizontalmente a través de un grupo de servidores. Cada servidor de Memcached opera de forma completamente aislada de los otros servidores del grupo. Por lo tanto, el enrutamiento y el balanceo de cargas entre los servidores debe realizarse a nivel del cliente. Los clientes de Memcached aplican un esquema de hashing coherente para seleccionar los servidores meta de forma adecuada. Este esquema garantiza las siguientes condiciones:
- Siempre se selecciona el mismo servidor para la misma clave.
- El uso de memoria se balancea de forma uniforme entre los servidores.
- Se reubica un número mínimo de claves cuando el grupo de servidores se reduce o expande.
En el diagrama siguiente, se muestra un alto nivel de interacción entre un cliente de Memcached y un grupo distribuido de servidores de Memcached.
Objetivos
- Aprender acerca de algunas características de la arquitectura distribuida de Memcached
- Implementar un servicio de Memcached en GKE con Kubernetes y Helm
- Implementar Mcrouter, un proxy de código abierto de Memcached, para mejorar el rendimiento del sistema
Costos
En este documento, usarás los siguientes componentes facturables de Google Cloud:
- Compute Engine
Para generar una estimación de costos en función del uso previsto, usa la calculadora de precios.
Antes de comenzar
- Sign in to your Google Cloud account. If you're new to Google Cloud, create an account to evaluate how our products perform in real-world scenarios. New customers also get $300 in free credits to run, test, and deploy workloads.
-
In the Google Cloud console, on the project selector page, select or create a Google Cloud project.
-
Make sure that billing is enabled for your Google Cloud project.
-
Enable the Compute Engine and GKE APIs.
-
In the Google Cloud console, on the project selector page, select or create a Google Cloud project.
-
Make sure that billing is enabled for your Google Cloud project.
-
Enable the Compute Engine and GKE APIs.
- Inicia una instancia de Cloud Shell
Abre Cloud Shell
Implemente un servicio de Memcached
Una forma sencilla de implementar un servicio de GKE es usar un gráfico de Helm. Para proceder con la implementación, sigue estos pasos en Cloud Shell:
Crea un clúster nuevo de GKE de tres nodos:
gcloud container clusters create demo-cluster --num-nodes 3 --zone us-central1-f
Descarga el archivo binario
helm
:HELM_VERSION=3.7.1 cd ~ wget https://get.helm.sh/helm-v${HELM_VERSION}-linux-amd64.tar.gz
Descomprime el archivo en tu sistema local:
mkdir helm-v${HELM_VERSION} tar zxfv helm-v${HELM_VERSION}-linux-amd64.tar.gz -C helm-v${HELM_VERSION}
Agrega el directorio binario
helm
a la variable de entornoPATH
:export PATH="$(echo ~)/helm-v${HELM_VERSION}/linux-amd64:$PATH"
Este comando permite que el archivo binario
helm
se pueda descubrir desde cualquier directorio durante la sesión de Cloud Shell actual. Para que esta configuración persista en varias sesiones, agrega el comando al archivo~/.bashrc
del usuario de Cloud Shell.Instala una nueva actualización del gráfico de Helm para Memcached con la arquitectura de alta disponibilidad:
helm repo add bitnami https://charts.bitnami.com/bitnami helm install mycache bitnami/memcached --set architecture="high-availability" --set autoscaling.enabled="true"
El gráfico de Helm para Memcached usa un controlador StatefulSet. Uno de los beneficios de usar este controlador es que los nombres de los pods están ordenados y son predecibles. En este caso, los nombres son
mycache-memcached-{0..2}
. Este orden facilita que los clientes de Memcached hagan referencia a los servidores.Para ver los pods en ejecución, ejecuta el siguiente comando:
kubectl get pods
El resultado de la consola de Google Cloud se ve así:
NAME READY STATUS RESTARTS AGE mycache-memcached-0 1/1 Running 0 45s mycache-memcached-1 1/1 Running 0 35s mycache-memcached-2 1/1 Running 0 25s
Descubra extremos de servicio de Memcached
El gráfico de Helm para Memcached usa Headless service. Este expone las direcciones IP para todos sus pods de modo que puedan detectarse individualmente.
Verifica que el servicio implementado no tenga interfaz gráfica:
kubectl get service mycache-memcached -o jsonpath="{.spec.clusterIP}"
Con la salida
None
, confirmas que el servicio no tieneclusterIP
y que, por lo tanto, no tiene interfaz gráfica.El servicio crea un registro DNS para el nombre del host con la forma siguiente:
[SERVICE_NAME].[NAMESPACE].svc.cluster.local
En este instructivo, el nombre del servicio es
mycache-memcached
. Debido a que no se definió de forma explícita un espacio de nombres, se usa el espacio de nombres predeterminado y, por lo tanto, el nombre de host completo esmycache-memcached.default.svc.cluster.local
. El nombre del host se resuelve como un conjunto de direcciones IP y dominios para los tres pods expuestos por el servicio. Si luego se agregan algunos pods al grupo o se quitan los más antiguos,kube-dns
actualizará de forma automática el registro DNS.Es responsabilidad del cliente descubrir los extremos de servicio de Memcached, como se describe en los pasos siguientes.
Recupera las direcciones IP de los extremos:
kubectl get endpoints mycache-memcached
La salida es similar a esta:
NAME ENDPOINTS AGE mycache-memcached 10.36.0.32:11211,10.36.0.33:11211,10.36.1.25:11211 3m
Ten en cuenta que cada pod de Memcached tiene una dirección IP diferente:
10.36.0.32
,10.36.0.33
y10.36.1.25
respectivamente. Estas direcciones IP pueden variar para tus propias instancias de servidor. Cada pod queda a la escucha del puerto11211
, que es el puerto predeterminado de Memcached.Como alternativa al paso 2, puedes llevar a cabo una inspección de DNS mediante un lenguaje de programación como Python:
Inicie una consola interactiva de Python dentro de su clúster:
kubectl run -it --rm python --image=python:3.10-alpine --restart=Never python
En la consola de Python, ejecuta estos comandos:
import socket print(socket.gethostbyname_ex('mycache-memcached.default.svc.cluster.local')) exit()
La salida es similar a esta:
('mycache-memcached.default.svc.cluster.local', ['mycache-memcached.default.svc.cluster.local'], ['10.36.0.32', '10.36.0.33', '10.36.1.25'])
Para probar la implementación, abre una sesión
telnet
con uno de los servidores de Memcached en ejecución en el puerto11211
:kubectl run -it --rm busybox --image=busybox:1.33 --restart=Never telnet mycache-memcached-0.mycache-memcached.default.svc.cluster.local 11211
En el símbolo del sistema de
telnet
, ejecuta estos comandos mediante el protocolo ASCII de Memcached:set mykey 0 0 5 hello get mykey quit
La salida se muestra aquí en negrita:
set mykey 0 0 5 hello STORED get mykey VALUE mykey 0 5 hello END quit
Implementar la lógica de detección del servicio
Ya puedes implementar la lógica básica de descubrimiento de servicios que se muestra en el diagrama siguiente.
A un alto nivel, la lógica de descubrimiento de servicios consiste en los pasos siguientes:
- La aplicación consulta
kube-dns
para el registro DNS demycache-memcached.default.svc.cluster.local
. - La aplicación recupera las direcciones IP asociadas con ese registro.
- La aplicación ejemplifica un nuevo cliente de Memcached y le proporciona la dirección IP recuperada.
- El balanceador de cargas integrado del cliente de Memcached se conecta con los servidores de Memcached en las direcciones IP proporcionadas.
Ahora vamos a implementar esta lógica de detección de servicio usando Python:
Implementa un pod habilitado para Python nuevo en tu clúster e inicia una sesión de shell dentro del pod:
kubectl run -it --rm python --image=python:3.10-alpine --restart=Never sh
Instala la biblioteca de
pymemcache
:pip install pymemcache
Inicia una consola interactiva de Python con el comando de
python
.En la consola de Python, ejecuta estos comandos:
import socket from pymemcache.client.hash import HashClient _, _, ips = socket.gethostbyname_ex('mycache-memcached.default.svc.cluster.local') servers = [(ip, 11211) for ip in ips] client = HashClient(servers, use_pooling=True) client.set('mykey', 'hello') client.get('mykey')
El resultado es el siguiente:
b'hello'
El prefijo
b
representa un literal de bytes, que es el formato en el que Memcached almacena los datos.Cierra la consola de Python:
exit()
Para salir de la sesión de shell del pod, presiona
Control
+D
.
Habilita la agrupación de conexiones
A medida que tus necesidades de almacenamiento en caché crezcan, y el grupo escale a decenas, cientos o miles de servidores de Memcached, puedes toparte con algunas limitaciones. En particular, el gran número de conexiones abiertas desde clientes de Memcached puede representar una gran carga para los servidores, como se muestra en el diagrama siguiente.
A fin de reducir el número de conexiones abiertas, debes ingresar un proxy para permitir la reducción de conexiones, como se muestra en el diagrama siguiente.
Mcrouter, un potente proxy de código abierto de Memcached, habilita la reducción de conexiones. Integrar Mcrouter resulta sencillo porque usa el protocolo ASCII estándar de Memcached. Para un cliente de Memcached, Mcrouter se comporta como el servidor normal de Memcached. Para un servidor de Memcached, Mcrouter se comporta como un cliente normal de Memcached.
Para implementar Mcrouter, ejecuta los comandos siguientes en Cloud Shell.
Borra la versión del gráfico de Helm
mycache
instalada antes:helm delete mycache
Implementa pods de Memcached y de Mcrouter nuevos mediante la instalación de una versión nueva del gráfico de Helm para Mcrouter.
helm repo add stable https://charts.helm.sh/stable helm install mycache stable/mcrouter --set memcached.replicaCount=3
Los pods del proxy ya están listos para aceptar solicitudes desde aplicaciones cliente.
A fin de probar esta configuración, conéctate a uno de los pods del proxy. Usa el comando
telnet
en el puerto5000
, que es el puerto predeterminado de Mcrouter.MCROUTER_POD_IP=$(kubectl get pods -l app=mycache-mcrouter -o jsonpath="{.items[0].status.podIP}") kubectl run -it --rm busybox --image=busybox:1.33 --restart=Never telnet $MCROUTER_POD_IP 5000
En el símbolo del sistema de
telnet
, ejecuta estos comandos:set anotherkey 0 0 15 Mcrouter is fun get anotherkey quit
Los comandos configuran y reproducen el valor de tu clave.
Ya has implementado un proxy que habilita la agrupación de conexiones.
Reducir la latencia
Para aumentar la resiliencia, se suele usar un clúster con varios nodos. En este instructivo, se usa un clúster con tres nodos. Sin embargo, usar varios nodos también acarrea el riesgo de una creciente latencia a causa del mayor tráfico de red entre los nodos.
Colocar pods del proxy
Puedes reducir este riesgo si conectas los pods de aplicaciones cliente solo a un pod proxy de Memcached que se encuentra en el mismo nodo. En el diagrama siguiente, se muestra esta configuración.
Aplica esta configuración como se indica a continuación:
- Asegúrate de que cada nodo contenga un pod del proxy en ejecución. Un enfoque común consiste en implementar los pods del proxy con un controlador DaemonSet. A medida que se agregan los nodos al clúster, se les incorporan nuevos pods del proxy automáticamente. A medida que se quitan los nodos del clúster, los pods se recolectan como elementos no utilizados. En este instructivo, en el gráfico de Helm para Mcrouter que implementaste antes se usa un controlador DaemonSet de forma predeterminada. Por lo tanto, este paso ya está completo.
- Establece un valor
hostPort
en los parámetros de Kubernetes del contenedor del proxy para que el nodo quede a la escucha de ese puerto y redireccione el tráfico al proxy. En este instructivo, en el gráfico de Helm para Mcrouter se usa este parámetro de forma predeterminada para el puerto5000
. Por lo tanto, este paso ya está completo. Expón el nombre del nodo como una variable de entorno dentro de los pods de la aplicación mediante el uso de la entrada
spec.env
y la selección del valorspec.nodeName
fieldRef
. Consulta más información sobre este método en la documentación de Kubernetes.Implementa algunos pods de aplicación de muestra:
cat <<EOF | kubectl create -f - apiVersion: apps/v1 kind: Deployment metadata: name: sample-application spec: selector: matchLabels: app: sample-application replicas: 9 template: metadata: labels: app: sample-application spec: containers: - name: busybox image: busybox:1.33 command: [ "sh", "-c"] args: - while true; do sleep 10; done; env: - name: NODE_NAME valueFrom: fieldRef: fieldPath: spec.nodeName EOF
Para verificar que el nombre del nodo esté expuesto, mira dentro de los pods de la aplicación de muestra:
POD=$(kubectl get pods -l app=sample-application -o jsonpath="{.items[0].metadata.name}") kubectl exec -it $POD -- sh -c 'echo $NODE_NAME'
Este comando da como resultado el nombre del nodo con la forma siguiente:
gke-demo-cluster-default-pool-XXXXXXXX-XXXX
Conecta los pods
A partir de ahora, los pods de la aplicación de muestra están listos para conectarse al pod de Mcrouter que se ejecuta en sus respectivos nodos mutuos en el puerto 5000
, que es el puerto predeterminado de Mcrouter.
A fin de iniciar una conexión para uno de los pods, abre una sesión de
telnet
:POD=$(kubectl get pods -l app=sample-application -o jsonpath="{.items[0].metadata.name}") kubectl exec -it $POD -- sh -c 'telnet $NODE_NAME 5000'
En el símbolo del sistema de
telnet
, ejecuta estos comandos:get anotherkey quit
Salida:
Mcrouter is fun
Por último, a modo de ejemplo, el siguiente código de Python es un programa de muestra que realiza esta conexión mediante la recuperación de la variable NODE_NAME
del entorno y el uso de la biblioteca pymemcache
:
import os
from pymemcache.client.base import Client
NODE_NAME = os.environ['NODE_NAME']
client = Client((NODE_NAME, 5000))
client.set('some_key', 'some_value')
result = client.get('some_key')
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.
Ejecuta el siguiente comando para borrar el clúster de GKE:
gcloud container clusters delete demo-cluster --zone us-central1-f
De forma opcional, puedes borrar el archivo binario de Helm:
cd ~ rm -rf helm-v3.7.1 rm helm-v3.7.1-linux-amd64.tar.gz
Pasos siguientes
- Explora las muchas otras características que ofrece Mcrouter más allá de la simple agrupación de conexiones, como las réplicas de conmutación por error, la transmisión de borrado confiable, el precalentamiento de memorias caché frías y la emisión de varios clústeres.
- Explora los archivos fuente del gráfico de Memcached y el gráfico de Mcrouter si quieres obtener más información acerca de las opciones de configuración respectivas de Kubernetes.
- Lee acerca de las técnicas efectivas para usar Memcached en App Engine. Algunas de ellas también se aplican a otras plataformas, como GKE.