このチュートリアルでは、Kubernetes、Helm、Mcrouter を使用して Google Kubernetes Engine(GKE)に分散型 Memcached サーバー クラスタをデプロイする方法について学習します。Memcached は、よく使われているオープンソースの多目的キャッシュ システムです。通常は、ウェブ アプリケーションの処理速度を高めてデータベースの負荷を軽減するために、使用頻度の高いデータの一時的な保管場所として機能します。
Memcached の特長
Memcached には、2 つの主な設計目標があります。
- シンプル: Memcached は大きなハッシュ テーブルのように機能し、キーを使って任意の形式のオブジェクトを格納して取り出すシンプルな API を提供します。
- 速度: Memcached はキャッシュ データをランダム アクセス メモリ(RAM)に独占的に保存し、きわめて高速なデータアクセスを実現します。
Memcached は、ハッシュ テーブルの容量をサーバープール全体で水平スケーリングできるようにする分散型システムです。Memcached の各サーバーは、プール内の他のサーバーとは完全に独立して動作します。したがって、クライアント レベルでサーバー間のルーティングとロード バランシングを行う必要があります。Memcached クライアントではコンシステント ハッシュ スキームが適用され、ターゲット サーバーが適切に選択されます。このスキームでは、次の条件が保証されます。
- 同じキーに対して常に同じサーバーが選択されます。
- サーバー間のメモリ使用量が均等に分散されます。
- サーバープールが縮小または拡大されるとき、最小数のキーが再配置されます。
次の図は、Memcached クライアントと Memcached サーバーの分散されたプールとの間のやり取りの概要を示しています。
目標
- Memcached の分散型アーキテクチャの特徴について学習します。
- Kubernetes と Helm を使用して、Memcached サービスを GKE にデプロイします。
- オープンソースの Memcached プロキシである Mcrouter をデプロイし、システムのパフォーマンスを向上させます。
費用
このドキュメントでは、Google Cloud の次の課金対象のコンポーネントを使用します。
- Compute Engine
料金計算ツールを使うと、予想使用量に基づいて費用の見積もりを生成できます。
始める前に
- 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.
-
In the Google Cloud console, on the project selector page, select or create a Google Cloud project.
-
Make sure that billing is enabled for your Google Cloud project.
-
Enable the Compute Engine and GKE APIs.
-
In the Google Cloud console, on the project selector page, select or create a Google Cloud project.
-
Make sure that billing is enabled for your Google Cloud project.
-
Enable the Compute Engine and GKE APIs.
- Cloud Shell インスタンスを起動します。
Cloud Shell を開く
Memcached サービスのデプロイ
Memcached サービスを GKE にデプロイする簡単な方法の 1 つは、Helm チャートを使用することです。デプロイを進めるには、Cloud Shell で次の手順を行います。
3 つのノードからなる新しい GKE クラスタを作成します。
gcloud container clusters create demo-cluster --num-nodes 3 --zone us-central1-f
helm
バイナリ アーカイブをダウンロードします。HELM_VERSION=3.7.1 cd ~ wget https://get.helm.sh/helm-v${HELM_VERSION}-linux-amd64.tar.gz
アーカイブ ファイルをローカル システムに解凍します。
mkdir helm-v${HELM_VERSION} tar zxfv helm-v${HELM_VERSION}-linux-amd64.tar.gz -C helm-v${HELM_VERSION}
helm
バイナリのディレクトリをPATH
環境変数に追加します。export PATH="$(echo ~)/helm-v${HELM_VERSION}/linux-amd64:$PATH"
このコマンドは、現在の Cloud Shell セッション中に任意のディレクトリから
helm
バイナリを検出可能にします。この構成を複数のセッションにわたって保持するには、このコマンドを Cloud Shell ユーザーの~/.bashrc
ファイルに追加します。高可用性アーキテクチャで新しい Memcached Helm チャートのリリースをインストールします。
helm repo add bitnami https://charts.bitnami.com/bitnami helm install mycache bitnami/memcached --set architecture="high-availability" --set autoscaling.enabled="true"
Memcached Helm チャートでは、StatefulSet コントローラが使用されています。StatefulSet コントローラを使用するメリットの 1 つは、Pod の名前が順序付けられて予測可能になることです。この場合、名前は
mycache-memcached-{0..2}
になります。この順序付けによって、Memcached クライアントはサーバーを参照しやすくなります。実行中のポッドを表示するには、次のコマンドを実行します。
kubectl get pods
Google Cloud コンソールの出力は次のようになります。
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 アドレスが公開されるため、個別に検出できるようになります。
デプロイされたサービスがヘッドレス サービスであることを確認します。
kubectl get service mycache-memcached -o jsonpath="{.spec.clusterIP}"
None
という出力により、サービスにclusterIP
がないこと、したがってヘッドレスであることを確認できます。このサービスでは、次の形式でホスト名の DNS レコードが作成されます。
[SERVICE_NAME].[NAMESPACE].svc.cluster.local
このチュートリアルでは、サービス名は
mycache-memcached
になります。Namespace が明示的に定義されていないため、デフォルトの Namespace が使用され、ホスト名全体はmycache-memcached.default.svc.cluster.local
になります。このホスト名は、サービスによって公開された 3 つの Pod すべての IP アドレスとドメインのセットに解決されます。今後、プールに Pod が追加されたり古い Pod が削除されたりすると、kube-dns
によって自動的に DNS レコードが更新されます。次のステップで説明するように、Memcached サービス エンドポイントの検出はクライアントが担います。
エンドポイントの 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.32
、10.36.0.33
、10.36.1.25
を持っています。これらの IP アドレスは、実際のサーバー インスタンスごとに異なる場合があります。各 Pod は Memcached のデフォルトのポート11211
をリッスンします。さらにステップ 2 の代わりに、Python などのプログラミング言語を使用して DNS 検査を行うこともできます。
クラスタ内で Python のインタラクティブ コンソールを起動します。
kubectl run -it --rm python --image=python:3.10-alpine --restart=Never python
Python のコンソールで、次のコマンドを実行します。
import socket print(socket.gethostbyname_ex('mycache-memcached.default.svc.cluster.local')) exit()
出力は次のようになります。
('mycache-memcached.default.svc.cluster.local', ['mycache-memcached.default.svc.cluster.local'], ['10.36.0.32', '10.36.0.33', '10.36.1.25'])
実行中のいずれかの Memcached サーバーとの
telnet
セッションをポート11211
で開き、デプロイのテストを行います。kubectl run -it --rm busybox --image=busybox:1.33 --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
サービス ディスカバリ ロジックの実装
これで、次の図に示す基本的なサービス ディスカバリ ロジックを実装する準備が整いました。
大まかに言えば、サービス ディスカバリ ロジックは次の手順で構成されます。
- アプリケーションは
kube-dns
に対してmycache-memcached.default.svc.cluster.local
の DNS レコードのクエリを実行します。 - アプリケーションは、そのレコードに関連付けられている IP アドレスを取得します。
- アプリケーションは新しい Memcached クライアントをインスタンス化して、取得した IP アドレスで提供します。
- Memcached クライアントの統合ロードバランサは、指定された IP アドレスの Memcached サーバーに接続します。
このサービス ディスカバリ ロジックを Python を使用して実装します。
新しい Python 対応 Pod をクラスタにデプロイし、Pod 内で Shell セッションを開始します。
kubectl run -it --rm python --image=python:3.10-alpine --restart=Never sh
pymemcache
ライブラリをインストールします。pip install pymemcache
python
コマンドを実行して、Python のインタラクティブ コンソールを起動します。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 のデータ保存形式がバイトリテラルであることを示しています。Python のコンソールを終了します。
exit()
Pod のシェル セッションを終了するには、
Control
+D
を押します。
接続プールの有効化
キャッシングの需要が高まり、プールが数十、数百、数千もの Memcached サーバーまで拡大されると、いくつかの制限が発生する可能性があります。特に、次の図のように Memcached クライアントからの多数のオープン接続がサーバーに負荷をかける可能性があります。
オープン接続の数を減らすには、次の図のように接続プールを有効にするためにプロキシを導入する必要があります。
高度なオープンソースの Memcached プロキシである Mcrouter(「ミックルーター」と発音)は、接続プールを有効にします。標準の Memcached ASCII プロトコルが使用されているため、Mcrouter はシームレスに統合できます。Memcached クライアントに対して、Mcrouter は通常の Memcached サーバーのように動作します。Memcached サーバーに対して、Mcrouter は通常の Memcached クライアントのように動作します。
Mcrouter をデプロイするには、Cloud Shell で次のコマンドを実行します。
前にインストールした
mycache
Helm チャートのリリースを削除します。helm delete mycache
新しい Mcrouter Helm チャートのリリースをインストールし、新しい Memcached Pod と Mcrouter Pod をデプロイします。
helm repo add stable https://charts.helm.sh/stable helm install mycache stable/mcrouter --set memcached.replicaCount=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 busybox --image=busybox:1.33 --restart=Never telnet $MCROUTER_POD_IP 5000
telnet
のプロンプトで、以下のコマンドを実行します。set anotherkey 0 0 15 Mcrouter is fun get anotherkey quit
これらのコマンドは、キーの値を設定してエコーします。
これで、接続プールを有効にするプロキシがデプロイされました。
レイテンシの削減
復元力を高めるには、複数のノードを持つクラスタを使用するのが一般的です。このチュートリアルでは、3 ノードのクラスタを使用しています。ただし、複数ノードの使用によってノード間のネットワーク トラフィックが増加するため、レイテンシが増加するリスクもあります。
プロキシポッドを同じ場所に配置する
クライアント アプリケーションのポッドを同じノード上にある Memcached プロキシポッドにのみ接続することで、このリスクを軽減できます。次の図は、この構成を示しています。
この構成は次のように行います。
- 各ノードに実行中のプロキシ Pod が 1 つ含まれていることを確認します。一般的な方法は、DaemonSet コントローラを使用してプロキシ Pod をデプロイすることです。ノードがクラスタに追加されると、そのノードに新しいプロキシ Pod が自動的に追加されます。ノードがクラスタから削除されると、Pod はガベージ コレクションされます。このチュートリアルで以前にデプロイした Mcrouter Helm チャートでは、デフォルトで DaemonSet コントローラを使用しています。このステップはすでに完了しています。
- プロキシ コンテナの Kubernetes パラメータで
hostPort
値を設定することにより、ノードがそのポートをリッスンしてトラフィックをプロキシにリダイレクトするようにします。このチュートリアルの Mcrouter Helm チャートでは、デフォルトでこのパラメータをポート5000
で使用しています。そのため、このステップもすでに完了しています。 spec.env
エントリを使用しspec.nodeName
fieldRef
値を選択し、アプリケーションの Pod 内の環境変数としてノード名を公開します。この方法の詳細については、Kubernetes のドキュメントをご覧ください。サンプル アプリケーション Pod をデプロイします。次のコマンドは、Kubernetes Deployment を適用します。Deployment は、クラスタ内のノード間で Pod の複数のレプリカを実行できる Kubernetes API です。
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
いずれかのアプリケーションのサンプルポッドを調べて、ノード名が公開されていることを確認します。
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 Pod に Mcrouter のデフォルト ポート 5000
で接続する準備ができました。
いずれかの Pod の接続を開始するには、
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'
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 アカウントに課金されないようにするには、リソースを含むプロジェクトを削除するか、プロジェクトを維持して個々のリソースを削除します。
次のコマンドを実行して、GKE クラスタを削除します。
gcloud container clusters delete demo-cluster --zone us-central1-f
必要に応じて、Helm バイナリを削除します。
cd ~ rm -rf helm-v3.7.1 rm helm-v3.7.1-linux-amd64.tar.gz
次のステップ
- フェイルオーバー レプリカ、信頼性の高い削除ストリーム、コールド キャッシュ ウォームアップ、マルチクラスタ ブロードキャストなど、単純な接続プール以外に Mcrouter が提供するその他の多くの機能を確認する。
- それぞれの Kubernetes の構成について、Memcached チャートと Mcrouter チャートのソースファイルを確認する。
- App Engine で Memcached を使用する効果的なテクニックについてのページを読む。このテクニックの一部は、GKE など他のプラットフォームに適用できます。