Como implantar o Memcached no GKE


Neste tutorial, você aprenderá a implantar um cluster de servidores distribuídos do Memcached no Google Kubernetes Engine (GKE) usando Kubernetes, Mcrouter e Mcrouter. O Memcached é um famoso sistema de armazenamento em cache de código aberto e multiuso. Ele normalmente funciona como um armazenamento temporário para os dados mais usados a fim de agilizar aplicativos da Web e diminuir cargas do banco de dados.

Características do Memcached

O Memcached tem duas metas de projeto principais:

  • Simplicidade: o Memcached funciona como uma grande tabela de hash e oferece uma API simples para armazenar e recuperar objetos de maneira arbitrária por chave.
  • Velocidade: o Memcached mantém dados de cache de maneira exclusiva em memória de acesso aleatório (RAM, na sigla em inglês), acelerando muito o acesso a dados.

Memcached é um sistema distribuído que permite que a capacidade da tabela de hash seja escalada horizontalmente em um grupo de servidores. Cada servidor do Memcached opera em completo isolamento dos outros servidores no grupo. Portanto, o roteamento e o balanceamento de carga entre os servidores precisam ser feitos no nível do cliente. Os clientes do Memcached aplicam um esquema de hash consistente para selecionar corretamente os servidores de destino. Esse esquema garante as seguintes condições:

  • O mesmo servidor sempre é selecionado para a mesma chave.
  • O uso da memória é equilibrado de maneira uniforme entre os servidores.
  • Um número mínimo de chaves é realocado quando o grupo de servidores é reduzido ou expandido.

O diagrama a seguir ilustra detalhadamente a interação entre um cliente do Memcached e um grupo distribuído de servidores do Memcached.

interação entre memcached e um grupo de servidores do memcached
Figura 1: interação detalhada entre um cliente do Memcached e um grupo distribuído de servidores do Memcached.

Objetivos

  • Saiba mais sobre algumas características da arquitetura distribuída do Memcached.
  • Implante um serviço do Memcached para o GKE usando o Kubernetes e o Helm.
  • Implante o Mcrouter, um proxy do Memcached de código aberto, para melhorar o desempenho do sistema.

Custos

Neste documento, você usará os seguintes componentes faturáveis do Google Cloud:

  • Compute Engine

Para gerar uma estimativa de custo baseada na projeção de uso deste tutorial, use a calculadora de preços. Novos usuários do Google Cloud podem estar qualificados para uma avaliação gratuita.

Antes de começar

  1. 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.
  2. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Go to project selector

  3. Make sure that billing is enabled for your Google Cloud project.

  4. Enable the Compute Engine and GKE APIs.

    Enable the APIs

  5. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Go to project selector

  6. Make sure that billing is enabled for your Google Cloud project.

  7. Enable the Compute Engine and GKE APIs.

    Enable the APIs

  8. Inicie uma instância do Cloud Shell.
    Abra o Cloud Shell

Como implantar um serviço do Memcached

Uma maneira simples de implantar um serviço Memcached no GKE é usar um gráfico de Helm. Para continuar a implantação, siga estas etapas no Cloud Shell:

  1. Crie um novo cluster GKE de três nós:

    gcloud container clusters create demo-cluster --num-nodes 3 --zone us-central1-f
    
  2. Faça o download do arquivo binário helm:

    HELM_VERSION=3.7.1
    cd ~
    wget https://get.helm.sh/helm-v${HELM_VERSION}-linux-amd64.tar.gz
    
  3. Descompacte o arquivo no sistema local:

    mkdir helm-v${HELM_VERSION}
    tar zxfv helm-v${HELM_VERSION}-linux-amd64.tar.gz -C helm-v${HELM_VERSION}
    
  4. Adicione o diretório do binário helm à variável de ambiente PATH:

    export PATH="$(echo ~)/helm-v${HELM_VERSION}/linux-amd64:$PATH"
    

    Esse comando torna o binário helm detectável a partir de qualquer diretório durante a sessão atual do Cloud Shell. Para tornar essa configuração persistente em várias sessões, adicione o comando ao arquivo ~/.bashrc do usuário do Cloud Shell.

  5. Instale um novo gráfico do Memcached Helm com a arquitetura de alta disponibilidade:

    helm repo add bitnami https://charts.bitnami.com/bitnami
    helm install mycache bitnami/memcached --set architecture="high-availability" --set autoscaling.enabled="true"
    

    O gráfico do Memcached Helm usa um controlador StatefulSet. Uma vantagem de usar um controlador StatefulSet é que os nomes dos pods são ordenados e previsíveis. Neste caso, os nomes são mycache-memcached-{0..2}. Essa ordem facilita a consulta aos servidores pelos clientes do Memcached.

  6. Para ver os pods em execução, execute o seguinte comando:

    kubectl get pods
    

    O resultado do Console do Google Cloud será como este:

    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

Como descobrir endpoints do serviço Memcached

O gráfico do Memcached Helm usa um serviço sem cabeça. Um serviço sem cabeça expõe endereços IP de todos os pods, de maneira que eles possam ser detectados individualmente.

  1. Verifique se o serviço implantado está sem cabeça:

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

    O resultado None confirma que o serviço não tem clusterIP e que, portanto, é sem cabeça.

    O serviço cria um registro DNS para um nome do host do formulário:

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

    Neste tutorial, o nome do serviço é mycache-memcached. Como um namespace não foi definido explicitamente, o namespace padrão é usado e, portanto, todo o nome do host é mycache-memcached.default.svc.cluster.local. Esse nome de host é resolvido para um conjunto de endereços IP e domínios para todos os três pods expostos pelo serviço. Se, no futuro, alguns pods forem adicionados ao grupo ou os antigos forem removidos, kube-dns atualizará automaticamente o registro DNS.

    É responsabilidade do cliente descobrir os endpoints do serviço Memcached, conforme descrito nas próximas etapas.

  2. Recupere os endereços IP dos pontos de extremidade:

    kubectl get endpoints mycache-memcached
    

    O resultado será semelhante a:

    NAME                ENDPOINTS                                            AGE
    mycache-memcached   10.36.0.32:11211,10.36.0.33:11211,10.36.1.25:11211   3m
    

    Cada pod do Memcached tem um endereço IP separado, respectivamente 10.36.0.32, 10.36.0.33, e 10.36.1.25. Esses endereços IP podem ser diferentes para as próprias instâncias de servidor. Cada pod escuta a porta 11211, que é a porta padrão do Memcached.

  3. Como alternativa à etapa 2, execute uma inspeção DNS usando uma linguagem de programação como Python:

    1. Inicie um console interativo Python dentro do cluster:

      kubectl run -it --rm python --image=python:3.10-alpine --restart=Never python
      
    2. No console Python, execute estes comandos:

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

      O resultado será semelhante a:

      ('mycache-memcached.default.svc.cluster.local', ['mycache-memcached.default.svc.cluster.local'], ['10.36.0.32', '10.36.0.33', '10.36.1.25'])
  4. Teste a implantação abrindo uma sessão telnet com um dos servidores Memcached em execução na porta 11211:

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

    No prompt telnet, execute esses comandos usando o protocolo Memcached ASCII:

    set mykey 0 0 5
    hello
    get mykey
    quit

    O resultado é mostrado aqui em negrito:

    set mykey 0 0 5
    hello
    STORED
    get mykey
    VALUE mykey 0 5
    hello
    END
    quit

Como implementar a lógica de descoberta do serviço

Agora está tudo pronto para você implementar a lógica de descoberta do serviço básico mostrada no diagrama a seguir.

<img <="" alt="service discovery logic" img="" src="/static/architecture/images/memcached-fig-2.svg" />
Figura 2: lógica de descoberta do serviço.

Em um nível mais detalhado, a lógica de descoberta de serviços consiste nas seguintes etapas:

  1. O aplicativo consulta kube-dns sobre o registro DNS de mycache-memcached.default.svc.cluster.local.
  2. O aplicativo recupera os endereços IP associados a esse registro.
  3. O aplicativo instancia um novo cliente do Memcached e fornece a ele os endereços IP recuperados.
  4. O balanceador de carga integrado do cliente do Memcached se conecta aos servidores do Memcached nos endereços IP fornecidos.

Você agora implementa essa lógica de descoberta de serviço usando o Python:

  1. Implante um novo pod ativado para Python no cluster e inicie uma sessão de shell dentro do pod:

    kubectl run -it --rm python --image=python:3.10-alpine --restart=Never sh
    
  2. Instale a biblioteca pymemcache:

    pip install pymemcache
    
  3. Inicie um console interativo do Python executando o comando python.

  4. No console Python, execute estes 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')
    

    A resposta é a seguinte:

    b'hello'

    O prefixo b indica um literal de bytes, formato de armazenamento de dados no Memcached.

  5. Saia do console Python:

    exit()
    
  6. Para sair da sessão de shell do pod, pressione Control + D.

Como ativar o agrupamento de conexões

À medida que as necessidades de armazenamento em cache crescem e o grupo chega a dezenas, centenas ou milhares de servidores Memcached, você pode enfrentar algumas limitações. Em especial, o grande número de conexões abertas de clientes do Memcached pode colocar uma carga pesada sobre os servidores, como mostra o diagrama a seguir.

<img <="" alt="High number of open connections when all Memcached clients access all Memcached servers directly" img="" src="/static/architecture/images/memcached-fig-3.svg" />
Figura 3: número elevado de conexões abertas quando todos os clientes do Memcached acessam todos os servidores do Memcached diretamente.

Para reduzir o número de conexões abertas, é preciso introduzir um proxy para ativar o agrupamento de conexões, como no diagrama a seguir.

<img <="" alt="Proxy to enable connection pooling." img="" src="/static/architecture/images/memcached-fig-4.svg" />
Figura 4: como usar um proxy para reduzir o número de conexões abertas.

O Mcrouter (pronuncia-se "mick router"), um proxy Memcached de código aberto eficiente, permite o agrupamento de conexões. A integração do Mcrouter é perfeita porque ele usa o protocolo padrão Memcached ASCII. Para um cliente do Memcached, o Mcrouter se comporta como um servidor do Memcached normal. Para um servidor do Memcached, o Mcrouter se comporta como um cliente do Memcached normal.

Para implantar o Mcrouter, execute os comandos a seguir no Cloud Shell.

  1. Exclua a versão do gráfico do Helm mycache instalada anteriormente:

    helm delete mycache
    
  2. Instale uma nova versão do gráfico do Mcrouter Helm para implantar novos pods do Memcached e do Mcrouter:

    helm repo add stable https://charts.helm.sh/stable
    helm install mycache stable/mcrouter --set memcached.replicaCount=3
    

    Os pods de proxy agora estão prontos para aceitar solicitações de aplicativos de cliente.

  3. Teste essa configuração se conectando a um dos pods de proxy. Use o comando telnet na porta 5000, que é a porta padrão do 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
    

    No prompt telnet, execute estes comandos:

    set anotherkey 0 0 15
    Mcrouter is fun
    get anotherkey
    quit

    Os comandos definem e reproduzem o valor da chave.

Você já implantou um proxy que permite o agrupamento de conexões.

Como reduzir a latência

Para aumentar a resiliência, é comum usar um cluster com vários nós. Este tutorial usa um cluster com três nós. No entanto, usar vários nós também oferece o risco de latência aumentada causada pelo tráfego de rede mais pesado entre nós.

Como colocar pods de proxy

Você pode reduzir esse risco conectando pods de aplicativo do cliente apenas a um pod de proxy do Memcached que esteja no mesmo nó. O diagrama a seguir ilustra essa configuração.

<img <="" alt="topology for interactions between pods" img="" src="/static/architecture/images/memcached-fig-5.svg" />
Figura 5: topologia para as interações entre pods de aplicativos, pods do Mcrouter e pods do Memcached em um cluster de três nós.

Realize essa configuração da seguinte maneira:

  1. Verifique se cada nó contém um pod de proxy em execução. Uma abordagem comum é implantar os pods de proxy com um controlador DaemonSet. À medida que nós são adicionados ao cluster, novos pods de proxy são adicionados automaticamente a eles. À medida que nós são removidos do cluster, o lixo desses pods é coletado. Neste tutorial, o gráfico do Mcrouter Helm que você implantou anteriormente usa um controlador DaemonSet por padrão. Por isso, esta etapa já está completa.
  2. Defina um valor hostPort nos parâmetros do Kubernetes do contêiner do proxy para que o nó escute essa porta e redirecione o tráfego para o proxy. Neste tutorial, o gráfico do Mcrouter Helm usa esse parâmetro por padrão para a porta 5000. Assim, esta etapa também já está completa.
  3. Exponha o nome do nó como uma variável de ambiente dentro dos pods do aplicativo usando a entrada spec.env e selecionando o valor spec.nodeName fieldRef. Saiba mais sobre esse método na documentação de Kubernetes.

    1. Implante alguns pods de aplicativos de amostra:

      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
      
  4. Verifique se o nome do nó está exposto, observando dentro de um dos pods de aplicativos de amostra:

    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 produz o nome do nó na seguinte forma:

    gke-demo-cluster-default-pool-XXXXXXXX-XXXX

Como conectar os pods

Os pods de aplicativos de amostra agora estão prontos para se conectar ao pod do Mcrouter executado nos respectivos nós mútuos na porta 5000, que é a porta padrão do Mcrouter.

  1. Inicie uma conexão para um dos pods abrindo uma sessão 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. No prompt telnet, execute estes comandos:

    get anotherkey
    quit
    

    Resultado:

    Mcrouter is fun

Por fim, como ilustração, o código Python a seguir é um exemplo de um programa que realiza essa conexão recuperando a variável NODE_NAME do ambiente e usando a 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')

Limpar

Para evitar cobranças na sua conta do Google Cloud pelos recursos usados no tutorial, exclua o projeto que os contém ou mantenha o projeto e exclua os recursos individuais.

  1. Execute o comando a seguir para excluir o cluster do GKE:

    gcloud container clusters delete demo-cluster --zone us-central1-f
    
  2. Se quiser, exclua o binário do Helm:

    cd ~
    rm -rf helm-v3.7.1
    rm helm-v3.7.1-linux-amd64.tar.gz
    

A seguir

  • Explore muitos outros recursos que o Mcrouter oferece além do agrupamento de conexões simples, como réplicas de failover, streams de exclusão confiáveis, aquecimento de cache a frio, transmissão multicluster.
  • Explore os arquivos de origem do gráfico do Memcached e do gráfico do Mcrouter para ver mais detalhes sobre as respectivas configurações do Kubernetes.
  • Leia mais sobre técnicas efetivas de uso do Memcached no App Engine. Algumas delas se aplicam a outras plataformas, como o GKE.