Implementar Memcached en Google Kubernetes Engine

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:

  • Simpleza: 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.

El diagrama siguiente muestra un alto nivel de interacción entre un cliente de Memcached y un grupo distribuido de servidores de Memcached.

interacción entre Memcached y un grupo de servidores de Memcached
Figura 1: Interacción de alto nivel 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 instructivo se usan los siguientes componentes facturables de Google Cloud Platform:

  • Compute Engine

Usa la calculadora de precios a fin de generar una estimación de los costos según el uso previsto. Los usuarios nuevos de GCP pueden optar a una prueba gratuita.

Antes de comenzar

  1. Sign in to your Google Account.

    If you don't already have one, sign up for a new account.

  2. Select or create a GCP project.

    Go to the Project selector page

  3. Comprueba que la facturación esté habilitada en tu proyecto.

    Descubre cómo puedes habilitar la facturación

  4. Habilita las Compute Engine API necesarias.

    Habilita las API

  5. Inicia una instancia de Cloud Shell
    ABRIR Cloud Shell

Implementar 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:

  1. Crea un nuevo clúster de GKE en tres nodos:

    gcloud container clusters create demo-cluster --num-nodes 3 --zone us-central1-f
    
  2. Descarga el archivo binario helm.

    cd ~
    wget https://kubernetes-helm.storage.googleapis.com/helm-v2.6.0-linux-amd64.tar.gz
    
  3. Descomprime el archivo en tu sistema local:

    mkdir helm-v2.6.0
    tar zxfv helm-v2.6.0-linux-amd64.tar.gz -C helm-v2.6.0
    
  4. Agrega el directorio del archivo binario helm a tu variable de entorno PATH.

    export PATH="$(echo ~)/helm-v2.6.0/linux-amd64:$PATH"
    

    Este comando permite que el archivo binario helm resulte detectable desde cualquier directorio durante la sesión de Cloud Shell actual. Para permitir que esta configuración se conserve entre varias sesiones, agrega el comando al archivo ~/.bashrc de tu usuario de Cloud Shell.

  5. Crea una cuenta de servicio con la función de administrador del clúster para Tiller, el servidor de Helm:

    kubectl create serviceaccount --namespace kube-system tiller
    kubectl create clusterrolebinding tiller --clusterrole=cluster-admin --serviceaccount=kube-system:tiller
    
  6. Inicializa Tiller en tu clúster y actualiza la información sobre los gráficos disponibles:

    helm init --service-account tiller
    helm repo update
    
  7. Instala una nueva actualización del gráfico de Helm para Memcached con tres réplicas, una para cada nodo:

    helm install stable/memcached --name mycache --set replicaCount=3
    

    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.

  8. Para ver los pods en ejecución, ejecuta el siguiente comando:

    kubectl get pods
    

    El resultado en Google Cloud Platform Console presenta este aspecto:

    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

Detectar 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.

  1. Verifica que el servicio implementado no tenga periféricos:

    kubectl get service mycache-memcached -o jsonpath="{.spec.clusterIP}"
    

    El resultado None confirma que el servicio no tiene clusterIP, por lo que no tiene periféricos.

    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. Como el espacio de nombres no se definió de forma explícita, se usa el espacio de nombres predeterminado y, por lo tanto, el nombre entero del host es mycache-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, en un futuro, se agregan pods al grupo, o se quitan algunos, kube-dns actualizará automáticamente el registro DNS.

    Es responsabilidad del cliente detectar los extremos de servicio de Memcached, como se describe en los pasos siguientes.

  2. Recupera las direcciones IP de los extremos:

    kubectl get endpoints mycache-memcached
    

    El resultado es similar al siguiente:

    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 y 10.36.1.25 respectivamente. Estas direcciones IP pueden variar para tus propias instancias de servidor. Cada pod responde al puerto 11211, que es el puerto predeterminado de Memcached.

  3. Como alternativa al paso 2, recupera los mismos registros usando una consulta DNS estándar con el comando nslookup:

    kubectl run -it --rm alpine --image=alpine:3.6 --restart=Never nslookup mycache-memcached.default.svc.cluster.local
    

    El resultado es similar al siguiente:

    Name:      mycache-memcached.default.svc.cluster.local
    Address 1: 10.36.0.32 mycache-memcached-0.mycache-memcached.default.svc.cluster.local
    Address 2: 10.36.0.33 mycache-memcached-2.mycache-memcached.default.svc.cluster.local
    Address 3: 10.36.1.25 mycache-memcached-1.mycache-memcached.default.svc.cluster.local
    

    Ten en cuenta que cada servidor tiene su propio nombre del dominio con la forma siguiente:

    [POD_NAME].[SERVICE_NAME].[NAMESPACE].svc.cluster.local
    

    Por ejemplo, el dominio para el pod mycache-memcached-0 es:

    mycache-memcached-0.mycache-memcached.default.svc.cluster.local
    
  4. Como alternativa al paso 2, lleva a cabo la misma inspección de DNS usando un lenguaje de programación como Python:

    1. Inicia una consola interactiva de Python dentro de tu clúster:

      kubectl run -it --rm python --image=python:3.6-alpine --restart=Never python
      
    2. En la consola de Python, ejecuta estos comandos:

      import socket
      print(socket.gethostbyname_ex('mycache-memcached.default.svc.cluster.local'))
      exit()
      

      El resultado es similar al siguiente:

      ('mycache-memcached.default.svc.cluster.local', [], ['10.36.0.32', '10.36.0.33', '10.36.1.25'])
  5. Prueba la implementación abriendo una sesión telnet con uno de los servidores de Memcached que se ejecuten en el puerto 11211:

    kubectl run -it --rm alpine --image=alpine:3.6 --restart=Never telnet mycache-memcached-0.mycache-memcached.default.svc.cluster.local 11211
    

    Ante la indicación de telnet, ejecuta estos comandos con el protocolo ASCII de Memcached:

    set mykey 0 0 5
    hello
    get mykey
    quit

    El resultado 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 estás listo para implementar la lógica básica de detección del servicio que se muestra en el diagrama siguiente.

lógica de detección de servicios
Figure 2: Lógica de detección del servicio

A un alto nivel, la lógica de detección del servicio consiste en los pasos siguientes:

  1. Las consultas de la aplicación kube-dns para el registro DNS de mycache-memcached.default.svc.cluster.local.
  2. La aplicación recupera las direcciones IP asociadas con ese registro.
  3. La aplicación ejemplifica un nuevo cliente de Memcached y le proporciona la dirección IP recuperada.
  4. 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:

  1. 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.6-alpine --restart=Never sh
    
  2. Instala la biblioteca pymemcache:

    pip install pymemcache
    
  3. Inicia una consola interactiva de Python ejecutando el siguiente comando de python.

  4. 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 significa un bytes literal, que es el formato en que Memcached almacena los datos.

  5. Cierra la consola de Python.

    exit()
    
  6. Para cerrar la sesión de shell del pod, presiona Control+D.

Habilitar 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.

Número alto de conexiones abiertas cuando todos los clientes de Memcached acceden a todos los servicios de Memcached de forma directa
Figura 3: Alto número de conexiones abiertas cuando todos los clientes de Memcached acceden a los servidores de Memcached de forma directa.

A fin de reducir el número de conexiones abiertas, debes ingresar un proxy para permitir la agrupación de conexiones, como se muestra en el diagrama siguiente.

Proxy para permitir la agrupación de conexiones
Figura 4: Usa un proxy para reducir el número de conexiones abiertas.

Mcrouter, un poderoso proxy de código abierto de Memcached, habilita la agrupació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.

  1. Borra la actualización del gráfico de Helm mycache instalada anteriormente:

    helm delete mycache --purge
    
  2. Implementa pods de Memcached y de Mcrouter nuevos mediante la instalación de una actualización del gráfico de Helm para Mcrouter nueva.

    helm install stable/mcrouter --name=mycache --set memcached.replicaCount=3
    

    Los pods del proxy ya están listos para aceptar solicitudes desde aplicaciones cliente.

  3. Prueba esta configuración conectándote a uno de los pods del proxy. Usa el comando telnet en el puerto 5000, 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 alpine --image=alpine:3.6 --restart=Never telnet $MCROUTER_POD_IP 5000
    

    Ante la indicación 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. El diagrama siguiente muestra esta configuración.

topología de las interacciones entre los pods
Figura 5: Topología de las interacciones entre los pods de las aplicaciones, los pods de Mcrouter y los pods de Memcached en un clúster de tres nodos.

Aplica esta configuración como se indica a continuación:

  1. 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, el gráfico de Helm para Mcrouter que implementaste anteriormente usa un controlador DaemonSet de forma predeterminada. Por lo tanto, este paso ya está completo.
  2. Configura un valor hostPort en los parámetros de Kubernetes del contenedor del proxy para hacer que el nodo responda a ese puerto y redireccione el tráfico hacia el proxy. En este instructivo, el gráfico de Helm para Mcrouter usa este parámetro de forma predeterminada para el puerto 5000. Por lo tanto, este paso ya está completo.
  3. Expón el nombre del nodo como una variable de entorno dentro de los pods de la aplicación usando la entrada spec.env y seleccionando el valor spec.nodeName fieldRef. Consulta más información sobre este método en la documentación de Kubernetes.

    1. Implementa algunos pods de aplicación de muestra:

      cat <<EOF | kubectl create -f -
      apiVersion: extensions/v1beta1
      kind: Deployment
      metadata:
        name: sample-application
      spec:
        replicas: 9
        template:
          metadata:
            labels:
              app: sample-application
          spec:
            containers:
              - name: alpine
                image: alpine:3.6
                command: [ "sh", "-c"]
                args:
                - while true; do sleep 10; done;
                env:
                  - name: NODE_NAME
                    valueFrom:
                      fieldRef:
                        fieldPath: spec.nodeName
      EOF
      
  4. 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

Conectar los pods

Los pods de la aplicación de muestra están a partir de ahora listos para conectarse al pod de Mcrouter que se ejecuta en sus nodos mutuos respectivos en el puerto 5000, que es el puerto predeterminado de Mcrouter.

  1. Inicia una conexión para uno de los pods abriendo una sesión en 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'
    
  2. Ante la indicación de telnet, ejecuta estos comandos:

    get anotherkey
    quit
    

    Resultado:

    Mcrouter is fun

Finalmente, a modo de ilustración, el código de Python siguiente es un programa de muestra que realiza esta conexión recuperando la variable NODE_NAME a partir del entorno y usando 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')

Limpieza

Sigue estos pasos para evitar que se apliquen cargos a tu cuenta de Google Cloud Platform por los recursos que usaste en este instructivo:

  1. Ejecuta el siguiente comando para borrar el clúster de GKE:

    gcloud container clusters delete demo-cluster --zone us-central1-f
    
  2. De forma opcional, puedes borrar el archivo binario de Helm:

    cd ~
    rm -rf helm-v2.6.0
    rm helm-v2.6.0-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 fuentes del gráfico de Memcache y el gráfico de Mcrouter si quieres obtener más información acerca de las configuraciones 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.
  • Prueba otras características de Google Cloud Platform por ti mismo. Revisa nuestros instructivos.
¿Te ha resultado útil esta página? Enviar comentarios:

Enviar comentarios sobre...