Como implantar o Memcached no Google Kubernetes Engine

Neste tutorial, você aprenderá a implantar um cluster de servidores distribuídos do Memcached no Google Kubernetes Engine (GKE) usando Kubernetes, Helm 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 qualquer formato por chave.
  • Velocidade: o Memcached mantém dados de cache somente em memória de acesso aleatório (RAM), acelerando muito o acesso a dados.

Memcached é um sistema distribuído que permite que a capacidade da tabela de hash seja escalonada 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

plain

Antes de começar

compute_component Compute Engine
  1. Faça login na sua Conta do Google.

    Se você ainda não tiver uma, inscreva-se.

  2. No Console do GCP, na página do seletor de projetos, selecione ou crie um projeto do GCP.

    Acesse a página do seletor de projetos

  3. Verifique se o faturamento foi ativado no projeto do Google Cloud Platform. Saiba como confirmar que o faturamento está ativado para seu projeto.

  4. Inicie uma instância do Cloud Shell.
    Abrir 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 do Helm (em inglês). 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:

    cd ~
        wget https://kubernetes-helm.storage.googleapis.com/helm-v2.6.0-linux-amd64.tar.gz
        
  3. Descompacte o arquivo no sistema local:

    mkdir helm-v2.6.0
        tar zxfv helm-v2.6.0-linux-amd64.tar.gz -C helm-v2.6.0
        
  4. Adicione o diretório do binário helm à variável de ambiente PATH:

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

    Esse comando torna o binário helm detectável em 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. Crie uma conta de serviço com papel de administrador do cluster do Tiller, o servidor do Helm:

    kubectl create serviceaccount --namespace kube-system tiller
        kubectl create clusterrolebinding tiller --clusterrole=cluster-admin --serviceaccount=kube-system:tiller
        
  6. Inicialize o Tiller no cluster e atualize as informações dos gráficos disponíveis:

    helm init --service-account tiller
        helm repo update
        
  7. Instale um novo gráfico do Memcached Helm (em inglês) com três réplicas, uma para cada nó:

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

    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.

  8. Para ver os pods em execução, execute o comando a seguir:

    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 (em inglês). Esse serviço 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 endpoints:

    kubectl get endpoints mycache-memcached
        

    A resposta será assim:

    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: 10.36.0.32, 10.36.0.33 e 10.36.1.25, respectivamente. 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, recupere esses mesmos registros usando uma consulta DNS padrão com o comando nslookup:

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

    A resposta será assim:

    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
        

    Cada servidor tem o próprio nome de domínio da seguinte forma:

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

    Por exemplo, o domínio do conjunto mycache-memcached-0 é:

    mycache-memcached-0.mycache-memcached.default.svc.cluster.local
        
  4. Para outra alternativa à etapa 2, realize a mesma inspeção de 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.6-alpine --restart=Never python
          
    2. No console Python, execute estes comandos:

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

      A resposta será assim:

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

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

    No prompt telnet, execute esses comandos usando o protocolo Memcached ASCII (em inglês):

    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 de serviços

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

lógica da descoberta de serviço
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 etapas a seguir:

  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.6-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, que é um 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, talvez você enfrente algumas limitações. Em especial, o grande número de conexões abertas de clientes Memcached pode colocar uma carga pesada sobre os servidores, conforme mostra o diagrama a seguir.

Número elevado de conexões abertas quando todos os clientes do Memcached acessam todos os servidores Memcached diretamente
Figura 3: número elevado de conexões abertas quando todos os clientes do Memcached acessam todos os servidores Memcached diretamente.

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

Proxy para ativar o agrupamento de conexões.
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 --purge
        
  2. Implante novos pods do Memcached e do Mcrouter instalando uma nova versão do gráfico do Mcrouter Helm (em inglês):

    helm install stable/mcrouter --name=mycache --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 alpine --image=alpine:3.6 --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

Esse risco pode ser reduzido 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.

topologia para interações entre pods
Figura 5: topologia das interações entre os pods de aplicativo, os pods do Mcrouter e os pods do Memcached em um cluster de três nós.

Realize essa configuração da maneira a seguir:

  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 (em inglês) 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 do Kubernetes (em inglês).

    1. Implante alguns pods de aplicativos de amostra:

      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. 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')
    

Limpeza

Para evitar que os recursos usados neste tutorial sejam cobrados na conta do Google Cloud Platform:

  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-v2.6.0
        rm helm-v2.6.0-linux-amd64.tar.gz
        

A seguir

  • Explore os 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 e transmissão multicluster.
  • Explore os arquivos de origem do gráfico do Memcached e do gráfico do Mcrouter (links em inglês) para ver mais detalhes sobre as respectivas configurações do Kubernetes.
  • Leia mais sobre técnicas efetivas de como usar o Memcached no App Engine. Algumas delas se aplicam a outras plataformas, como o GKE.
  • Teste outros recursos do Google Cloud. Veja nossos tutoriais.