在 Google Kubernetes Engine 上部署 Memcached

在本教程中,您将学习如何使用 KubernetesHelmMcrouterGoogle Kubernetes Engine (GKE) 上部署分布式 Memcached 服务器集群。Memcached 是一种流行的开源、多用途缓存系统。 它通常用作常用数据的临时存储,以加速 Web 应用的速度并减轻数据库负载。

Memcached 的特点

Memcached 有两大设计目标:

  • 简单性:Memcached 功能类似于一个大型的哈希表,并且会提供一个简单的 API 以按照键来存储和检索任意形状的对象。
  • 速度:Memcached 专门在随机存取存储器 (RAM) 上保存缓存数据,从而实现极快速数据访问。

Memcached 是一个分布式系统,可让其哈希表的容量在服务器池中横向扩缩。由于每台 Memcached 服务器都在与池中的其他服务器完全隔离的环境中运行, 因此,服务器之间的路由和负载平衡必须在客户端层级执行。此外,Memcached 客户端使用一致的哈希技术架构相应地选择目标服务器。此架构保证可满足以下条件:

  • 始终为同一个键选择同一台服务器。
  • 在服务器之间保持内存使用量的平衡。
  • 服务器池缩小或扩大时,仅需重定位最少数量的键。

下图概括说明了 Memcached 客户端与分布式 Memcached 服务器池之间的交互。

memcached 与 memcached 服务器池之间的交互
图 1:Memcached 客户端与分布式 Memcached 服务器池之间的交互概略。

目标

  • 了解 Memcached 的分布式架构的一些特点。
  • 使用 Kubernetes 和 Helm 将 Memcached 服务部署至 GKE。
  • 部署开源 Memcached 代理 Mcrouter,提高系统的性能。

费用

本教程使用 Google Cloud 的以下收费组件:

  • Compute Engine

您可使用价格计算器根据您的预计使用量来估算费用。 Google Cloud 新用户可能有资格申请免费试用

准备工作

  1. 登录您的 Google 帐号。

    如果您还没有 Google 帐号,请注册一个新帐号

  2. 在 Cloud Console 的项目选择器页面上,选择或创建 Cloud 项目。

    转到项目选择器页面

  3. 确保您的 Google Cloud 项目已启用结算功能。 了解如何确认您的项目已启用结算功能

  4. 启用 Compute Engine API。

    启用 API

  5. 启动 Cloud Shell 实例。
    打开 Cloud Shell

部署 Memcached 服务

将 Memcached 服务部署至 GKE 的一个简单方法是使用 Helm 图表。要继续进行部署,请在 Cloud Shell 中执行以下步骤:

  1. 创建一个新的有三个节点的 GKE 集群:

    gcloud container clusters create demo-cluster --num-nodes 3 --zone us-central1-f
    
  2. 下载 helm 二进制存档文件:

    cd ~
    wget https://kubernetes-helm.storage.googleapis.com/helm-v2.6.0-linux-amd64.tar.gz
    
  3. 将存档文件解压缩到本地系统:

    mkdir helm-v2.6.0
    tar zxfv helm-v2.6.0-linux-amd64.tar.gz -C helm-v2.6.0
    
  4. helm 二进制文件的目录添加到 PATH 环境变量中:

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

    使用此命令,可在当前 Cloud Shell 会话期间让 helm 二进制变得可检测到。要使此配置存在于多个会话中,请将该命令添加到 Cloud Shell 用户的 ~/.bashrc 文件中。

  5. 为 Helm 服务器 Tiller 创建一个带集群管理员角色的服务帐号:

    kubectl create serviceaccount --namespace kube-system tiller
    kubectl create clusterrolebinding tiller --clusterrole=cluster-admin --serviceaccount=kube-system:tiller
    
  6. 在集群中初始化 Tiller,并更新可用图表的信息:

    helm init --service-account tiller
    helm repo update
    
  7. 安装一个具有三个副本的新 Memcached Helm 图表版本,一个副本对应一个节点:

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

    Memcached Helm 图表使用 StatefulSet 控制器。 这样做的好处之一是 pod 的名称变得有条理、可预测。在这种情况下,名称是 mycache-memcached-{0..2}。这种排序使 Memcached 客户端引用服务器变得更容易。

  8. 要查看正在运行的 pod,请运行以下命令:

    kubectl get pods
    

    Google Cloud Console 输出如下所示:

    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

发现 Memcached 服务端点

Memcached Helm 图表使用无头服务。 无头服务公开其所有 pod 的 IP 地址,以便它们可以一个一个地被发现。

  1. 验证所部署的服务是否无头:

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

    输出 None 确认该服务没有 clusterIP,因此是无头的。

    服务为以下格式的主机名创建 DNS 记录:

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

    在本教程中,服务名称为 mycache-memcached。由于命名空间未明确定义,因此使用默认命名空间,完整的主机名为 mycache-memcached.default.svc.cluster.local。此主机名会解析为服务所公开的三个 pod 的一组 IP 地址和域名。如果将来某些 pod 被添加到池中,或者旧的 pod 被移除,则 kube-dns 会自动更新 DNS 记录。

    客户端会发现 Memcached 服务端点,如后续步骤所述。

  2. 检索端点的 IP 地址:

    kubectl get endpoints mycache-memcached
    

    输出类似于以下内容:

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

    请注意,每个 Memcached 的 pod 都有一个独立的 IP 地址,分别为 10.36.0.3210.36.0.3310.36.1.25。对于您自己的服务器实例,这些 IP 地址可能不同。每个 pod 都会侦听 Memcached 的默认端口 11211

  3. 如果要使用不同的方法完成第 2 步操作,可使用标准 DNS 查询以及 nslookup 命令,检索相同的记录:

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

    输出类似于以下内容:

    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
    

    请注意,每台服务器都有自己的域名格式,如下所示:

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

    例如,mycache-memcached-0 pod 的域名为:

    mycache-memcached-0.mycache-memcached.default.svc.cluster.local
    
  4. 如果要使用另一种方法完成第 2 步操作,可使用 Python 等编程语言执行相同的 DNS 检查:

    1. 启动集群内的 Python 交互式控制台:

      kubectl run -it --rm python --image=python:3.6-alpine --restart=Never python
      
    2. 在 Python 控制台中,运行以下命令:

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

      输出类似于以下内容:

      ('mycache-memcached.default.svc.cluster.local', [], ['10.36.0.32', '10.36.0.33', '10.36.1.25'])
  5. 通过打开与端口 11211 上正在运行的一个 Memcached 服务器建立的 telnet 会话,测试部署:

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

    看到 telnet 提示后,使用 Memcached ASCII 协议运行以下命令:

    set mykey 0 0 5
    hello
    get mykey
    quit

    生成的输出以粗体显示:

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

实现服务发现逻辑

现在,您可以实现下图中所示的基本服务发现逻辑。

服务发现逻辑
图 2:服务发现逻辑。

概括来讲,服务发现逻辑包括以下步骤:

  1. 应用为 mycache-memcached.default.svc.cluster.local 的 DNS 记录查询 kube-dns
  2. 应用检索与该记录关联的 IP 地址。
  3. 应用实例化新的 Memcached 客户端,并为其提供所检索到的 IP 地址。
  4. Memcached 客户端的集成负载平衡器连接到指定 IP 地址上的 Memcached 服务器。

现在,您可使用 Python 实现此服务发现逻辑:

  1. 在您的集群中部署新的已启用 Python 的 pod,并在该 pod 中开启 shell 会话:

    kubectl run -it --rm python --image=python:3.6-alpine --restart=Never sh
    
  2. 安装 pymemcache 库:

    pip install pymemcache
    
  3. 通过运行 python 命令启动 Python 交互式控制台。

  4. 在 Python 控制台中,运行以下命令:

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

    输出如下所示:

    b'hello'

    b 前缀表示字节字面量,字节字面量是 Memcached 存储数据所采用的格式。

  5. 退出 Python 控制台:

    exit()
    
  6. Control+D 退出 pod 的 shell 会话。

启用连接池

随着缓存需求的增长,以及池扩展到数十、数百或数千个 Memcached 服务器,您可能会遇到一些限制。特别是,大量来自 Memcached 客户端的打开连接可能会使服务器负载过重,如下图所示。

所有 Memcached 客户端直接访问所有 Memcached 服务器时产生的大量打开连接
图 3:所有 Memcached 客户端直接访问所有 Memcached 服务器时产生的大量打开连接。

要减少打开连接数,您必须引入代理来启用连接池,如下图所示。

用于启用连接池的代理。
图 4:使用代理减少打开连接数。

Mcrouter(发音为“mick router”)是一个功能强大的开源 Memcached 代理,可启用连接池。集成 Mcrouter 是无缝的,因为它使用标准的 Memcached ASCII 协议。对于 Memcached 客户端,Mcrouter 会像一个正常的 Memcached 服务器运行。对于 Memcached 服务器,Mcrouter 会像一个正常的 Memcached 客户端运行。

要部署 Mcrouter,请在 Cloud Shell 中运行以下命令。

  1. 删除以前安装的 mycache Helm 图表版本:

    helm delete mycache --purge
    
  2. 通过安装新的 Mcrouter Helm 图表版本,部署新的 Memcached pod 和 Mcrouter pod:

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

    现在,代理 pod 可接受来自客户端应用的请求。

  3. 通过连接到其中一个代理 pod,测试此设置。在 Mcrouter 的默认端口 5000 上使用 telnet 命令。

    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
    

    看到 telnet 提示后,运行以下命令:

    set anotherkey 0 0 15
    Mcrouter is fun
    get anotherkey
    quit

    这些命令设置并回显您的键值。

现在,您就部署了一个启用连接池的代理。

降低延时

要提高弹性,常见做法是使用带有多个节点的集群。在本教程中,使用了带有三个节点的集群。不过,使用多个节点还会带来因节点之间网络流量较大而引起的延迟时间增加的风险。

并置代理 pod

您可以通过将客户端应用 pod 仅连接到同一节点上的 Memcached 代理 pod 来降低此风险。下图说明了此配置。

pod 之间的交互拓扑
图 5:三个节点集群中的应用 pod、Mcrouter pod 和 Memcached pod 之间的交互拓扑。

执行以下配置:

  1. 确保每个节点包含一个正在运行的代理 pod。一种常见方法是使用 DaemonSet 控制器部署代理 pod。 节点添加到集群中后,新的代理 pod 会自动添加到节点中。从集群中删除节点后,会对这些 pod 进行垃圾回收。在本教程中,您早些时候部署的 Mcrouter Helm 图表默认使用 DaemonSet 控制器。因此,此步骤已完成。
  2. 在代理容器的 Kubernetes 参数中设置 hostPort 值,以使节点侦听该端口并将流量重定向到代理。在本教程中,Mcrouter Helm 图表默认将此参数用于端口 5000。因此,此步骤也已完成。
  3. 通过使用 spec.env 条目并选择 spec.nodeName fieldRef 值,将节点名称作为应用 pod 中的环境变量公开。如需详细了解此方法,请参阅 Kubernetes 文档

    1. 部署一些示例应用 pod:

      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. 通过查看一个示例应用 pod,验证是否已公开节点名称:

    POD=$(kubectl get pods -l app=sample-application -o jsonpath="{.items[0].metadata.name}")
    
    kubectl exec -it $POD -- sh -c 'echo $NODE_NAME'
    

    此命令输出如下格式的节点名称:

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

连接 pod

示例应用 pod 现在可以连接到 Mcrouter 的默认端口 5000 上的各自配对节点中运行的 Mcrouter pod。

  1. 通过打开 telnet 会话,启动其中一个 pod 连接:

    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. 看到 telnet 提示后,运行以下命令:

    get anotherkey
    quit
    

    生成的输出如下:

    Mcrouter is fun

最后,作为说明,以下 Python 代码是可通过检索环境中的 NODE_NAME 变量并使用 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')

清理

为避免因本教程中使用的资源而导致我们向您的 Google Cloud Platform 帐号收取费用,请执行以下操作:

  1. 执行以下命令,删除 GKE 集群:

    gcloud container clusters delete demo-cluster --zone us-central1-f
    
  2. (可选)删除 Helm 二进制文件:

    cd ~
    rm -rf helm-v2.6.0
    rm helm-v2.6.0-linux-amd64.tar.gz
    

后续步骤

  • 探索除简单连接池之外,Mcrouter 提供的其他多项功能,如故障转移副本、可靠的删除流、冷缓存预热、多集群广播。
  • 探索 Memcached 图表Mcrouter 图表的源文件,详细了解各自的 Kubernetes 配置。
  • 了解在 App Engine 上使用 Memcached 的有效方法。其中一些方法还适用于其他平台,如 GKE。
  • 试用其他 Google Cloud 功能。查阅我们的教程