Google Kubernetes Engine に Memcached をデプロイする

このチュートリアルでは、KubernetesHelmMcrouter を使用して Google Kubernetes Engine(GKE) に分散型の Memcached サーバーのクラスタをデプロイする方法について学習します。 Memcached は一般的なオープンソースの多目的キャッシュ システムです。 通常これは頻繁に使用されるデータの一時的な保管場所として機能し、ウェブ アプリケーションの処理速度を高め、データベースの負荷を軽減します。

Memcached の特徴

Memcached には、2 つの主な設計目標があります。

  • シンプル: Memcached は大きなハッシュ テーブルのように機能し、キーを使って任意の形式のオブジェクトを格納して取り出すシンプルな API を提供します。
  • 速度: Memcached はキャッシュ データをランダム アクセス メモリ(RAM)に独占的に保存し、きわめて高速なデータアクセスを実現します。

Memcached は、ハッシュ テーブルの容量をサーバープール全体で水平スケーリングできるようにする分散型システムです。Memcached の各サーバーは、プール内の他のサーバーとは完全に独立して動作します。 したがって、クライアント レベルでサーバー間のルーティングと負荷分散を行う必要があります。Memcached クライアントではコンシステント ハッシュ スキームが適用され、ターゲット サーバーが適切に選択されます。このスキームでは、次の条件が保証されます。

  • 同じキーに対して常に同じサーバーが選択されます。
  • サーバー間のメモリ使用量が均等に分散されます。
  • サーバープールが縮小または拡大されるとき、最小数のキーが再配置されます。

次の図は、Memcached クライアントと Memcached サーバーの分散されたプールとの間のやり取りの概要を示しています。

Memcached と Memcached サーバープール間のやり取り
図 1: Memcached クライアントと Memcached サーバーの分散されたプールとの間のやり取りの概要

目標

  • Memcached の分散型アーキテクチャの特徴について学習します。
  • Kubernetes と Helm を使用して、Memcached サービスを GKE にデプロイします。
  • オープンソースの Memcached プロキシである Mcrouter をデプロイし、システムのパフォーマンスを向上させます。

料金

このチュートリアルでは、以下の課金対象の Google Cloud Platform コンポーネントを使用します。

  • Compute Engine

料金計算ツールを使用して、予測される使用量に基づき、費用の見積もりを出すことができます。 GCP を初めてご利用の場合は、無料トライアルをご利用いただけます。

始める前に

  1. Google アカウントにログインします。

    Google アカウントをまだお持ちでない場合は、新しいアカウントを登録します。

  2. GCP プロジェクトを選択または作成します。

    [リソースの管理] ページに移動

  3. プロジェクトに対して課金が有効になっていることを確認します。

    課金を有効にする方法について

  4. Compute Engine API を有効にします。

    APIを有効にする

  5. Cloud Shell インスタンスを起動します。
    Cloud Shell を開く

Memcached サービスのデプロイ

Memcached サービスを GKE にデプロイする簡単な方法の 1 つは、Helm チャートを使用することです。デプロイを進めるには、Cloud Shell で次の手順を行います。

  1. 3 つのノードからなる新しい 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. ノードごとに 1 つずつ、3 つのレプリカで新しい Memcached Helm チャートのリリースをインストールします。

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

    Memcached Helm チャートでは、StatefulSet コントローラが使用されています。StatefulSet コントローラを使用するメリットの 1 つは、ポッドの名前が順序付けられて予測可能になることです。この場合、名前は mycache-memcached-{0..2} になります。この順序付けによって、Memcached クライアントはサーバーを参照しやすくなります。

  8. 実行中のポッドを表示するには、次のコマンドを実行します。

    kubectl get pods
    

    Google Cloud Platform 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 グラフではヘッドレス サービスが使用されています。ヘッドレス サービスでは、個別に検出できるようにすべてのポッドの 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 になります。このホスト名は、サービスによって公開された 3 つのポッドすべての IP アドレスとドメインのセットに解決されます。 今後、いくつかのポッドがプールに追加されたり、古いポッドが削除されたりすると、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 の各ポッドには個別の IP アドレス、10.36.0.3210.36.0.3310.36.1.25 があります。これらの IP アドレスは、実際のサーバー インスタンスごとに異なる場合があります。各ポッドは Memcached のデフォルトのポート 11211 をリッスンします。

  3. ステップ 2 の代わりに nslookup コマンドで標準 DNS クエリを使用しても、同じレコードを取得できます。

    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 ポッドのドメインは次のようになります。

    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. アプリケーションは kube-dns に対して mycache-memcached.default.svc.cluster.local の DNS レコードのクエリを実行します。
  2. アプリケーションは、そのレコードに関連付けられている IP アドレスを取得します。
  3. アプリケーションは新しい Memcached クライアントをインスタンス化して、取得した IP アドレスで提供します。
  4. Memcached クライアントの統合ロードバランサは、指定された IP アドレスの Memcached サーバーに接続します。

このサービス ディスカバリ ロジックを Python を使用して実装します。

  1. 新しい Python 対応ポッドをクラスタにデプロイし、ポッド内で 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. ポッドの Shell セッションを終了するには、Control + D を押します。

接続プールの有効化

キャッシングの需要が高まり、プールが数十、数百、数千もの Memcached サーバーまで拡大されると、いくつかの制限が発生する可能性があります。特に、次の図のように Memcached クライアントからの多数のオープン接続がサーバーに負荷をかける可能性があります。

すべての Memcached クライアントがすべての Memcached サーバーに直接アクセスする場合の多数のオープン接続
図 3: すべての Memcached クライアントがすべての Memcached サーバーに直接アクセスする場合の多数のオープン接続

オープン接続の数を減らすには、次の図のように接続プールを有効にするためにプロキシを導入する必要があります。

接続プールを有効にするプロキシ
図 4: プロキシを使用してオープン接続の数を減らす

高度なオープンソースの Memcached プロキシである Mcrouter(「ミックルーター」と発音)は、接続プールを有効にします。標準の Memcached ASCII プロトコルが使用されているため、Mcrouter はシームレスに統合できます。Memcached クライアントに対して、Mcrouter は通常の Memcached サーバーのように動作します。Memcached サーバーに対して、Mcrouter は通常の Memcached クライアントのように動作します。

Mcrouter をデプロイするには、Cloud Shell で次のコマンドを実行します。

  1. 前にインストールした mycache Helm チャートのリリースを削除します。

    helm delete mycache --purge
    
  2. 新しい Mcrouter Helm チャートのリリースをインストールし、新しい Memcached ポッドと Mcrouter ポッドをデプロイします。

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

    これで、プロキシポッドでクライアント アプリケーションからのリクエストを受け入れる準備が整いました。

  3. いずれかのプロキシポッドに接続してこの設定をテストします。Mcrouter のデフォルト ポート 5000telnet コマンドを使用します。

    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

    これらのコマンドは、キーの値を設定してエコーします。

これで、接続プールを有効にするプロキシがデプロイされました。

レイテンシの削減

復元力を高めるには、複数のノードを持つクラスタを使用するのが一般的です。このチュートリアルでは、3 ノードのクラスタを使用しています。ただし、複数ノードの使用によってノード間のネットワーク トラフィックが増加するため、レイテンシが増加するリスクもあります。

プロキシポッドを同じ場所に配置する

クライアント アプリケーションのポッドを同じノード上にある Memcached プロキシポッドにのみ接続することで、このリスクを軽減できます。次の図は、この構成を示しています。

ポッド間のやり取りのトポロジ
図 5: 3 ノードのクラスタにまたがるアプリケーション ポッド、Mcrouter ポッド、Memcached ポッド間のやり取りのトポロジ

この構成は次のように行います。

  1. 各ノードに実行中のプロキシポッドが 1 つ含まれていることを確認します。一般的な方法は、DaemonSet コントローラを使用してプロキシポッドをデプロイすることです。 ノードがクラスタに追加されると、新しいプロキシポッドが自動的に追加されます。ノードがクラスタから削除されると、ポッドはガベージ コレクションされます。このチュートリアルで以前にデプロイした Mcrouter Helm チャートでは、デフォルトで DaemonSet コントローラを使用しています。そのため、このステップはすでに完了しています。
  2. プロキシ コンテナの Kubernetes パラメータで hostPort 値を設定することにより、ノードがそのポートをリッスンしてトラフィックをプロキシにリダイレクトするようにします。このチュートリアルの Mcrouter Helm チャートでは、デフォルトでこのパラメータをポート 5000 で使用しています。そのため、このステップもすでに完了しています。
  3. spec.env エントリを使用して spec.nodeName fieldRef 値を選択し、アプリケーションのポッド内の環境変数としてノード名を公開します。この方法の詳細については、Kubernetes のドキュメントをご覧ください。

    1. いくつかのアプリケーションのサンプルポッドをデプロイします。

      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=$(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

ポッドを接続する

これでアプリケーションのサンプルポッドを、Mcrouter のデフォルト ポート 5000 の対応する相互ノード上で動作する Mcrouter ポッドに接続する準備ができました。

  1. 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. 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 が提供するその他の多くの機能を確認する。
  • それぞれの Kubernetes の構成について、Memcached チャートMcrouter チャートのソースファイルを確認する。
  • App Engine で Memcached を使用する効果的なテクニックについてのページを読む。このテクニックの一部は、GKE など他のプラットフォームに適用できます。
  • Google Cloud Platform のその他の機能を試す。チュートリアルをご覧ください。
このページは役立ちましたか?評価をお願いいたします。

フィードバックを送信...