CNCF Kubernetesドキュメントのチュートリアル Example: Deploying PHP Guestbook application with Redisをパソコン上のアップストリーム Kubenetesクラスタで実施しました。このパソコン上の環境は、15Stepで習得 Dockerから入るKubernetes コンテナ開発からK8s本番運用までの学習環境2です。もちろん、IBM Cloud OpenShift で、動かして試すこともできる。
この記事では、チュートリアルには書いていないポイントや、パソコン上のオンプレミス環境で動作せる場合の違いについて、書いてみたい。そのため、チュートリアルと合わせて参照することをお勧めしたい。
本記事で利用した学習用Kubenetes環境は、https://github.com/takara9/vagrant-kubernetes を使用している.これは macOS、Windows10、Linuxなどの環境でも利用できるので、パブリック・クラウドを利用するだけでは解らない裏側の様子も垣間見ることができ、オンプレミスでベンダー依存を制御した環境を構築するスキル獲得にも役立つと思う。
チュートリアルの構成
このチュートリアルで構築する構成は、次の図のようになる。
ブラウザからFrontendのURLをアクセスして、メッセージをサブミットすると、PHPのアプリケーションは、redis-masterを参照して、Redisマスターサーバーにデータが書き込まれる。また、FrontendのURLを再読みするなど、参照だけを実行した場合、PHPのアプリケーションは、サービス redis-slaveを参照して Redis スレーブサーバーからデータを取得する。
ウェブアプリケーションの参照と書込みの比率は、8対2などと見做されるので、参照トラフィックの負荷をRedisスレーブで処理して、書込みトラフィックはRedisマスターでうけるという構成が出来ている。
障害対策の点では、frontend、redis-master、redis-slave の各アプリケーションのポッドは、それぞれのデプロイメント・コントローラーで、起動数を監視されており、もし、ポッド停止するなどの異常が発生した場合、自己回復を実行する。
負荷対策の点では、frontendのアプリケーションは、ポッド数を増加させることで、アクセス増に対して処理能力を向上できる。これに対応するように、参照トラフィック用のredis-slaveの数を増加させる事ができる。redis-masterとredis-slaveのポッドは、redis-slaveの設定によって、redis-masterをマスターサーバーとして、データを同期するように設定されているため、redis-slaveのポッドを増やすだけで、参照側の性能を向上させることができる。
最終的にredis-masterのポッド(コンテナ)が全体の性能ネックになるが、redis-masterのポッド内のコンテナに与えるCPUやメモリ量を増やす対策(スケールアップ)によって対策することになる。
K8s構築コードの取得と修正
15Stepで習得 Dockerから入るKubernetes コンテナ開発からK8s本番運用までの学習環境2を利用する。
筆者が、最近リリースされたばかりの 1.17.0 で動作を試したところ、同一ノード上のredisマスターとスレーブ間で、データ同期中にタイムアウトが発生するという問題があったので、今回は、本書の内容と同じ、バージョン 1.14 を利用する。もちろん、1.14のサポート終了が迫っているため、継続して、1.17での原因と対策を追跡していきたい。
注)1.17.0 では動作不良を起こしていたが、その後にリリースされた1.17.1では問題が発生しなかった。そこでGitHubのブランチ 1.17 のバージョンは1.17.1以降を利用する。
tkr@luigi:~$ git clone https://github.com/takara9/vagrant-kubernetes k8s-1.14
tkr@luigi:~$ cd k8s-1.14
この作業は、オプションで、パソコン外部からアクセスしたい場合に、次のような方法で対処できる。IPアドレスは、パソコンと同じネットワークアドレスと同じである必要がある。パソコン上のブラウザでアクセスに限定する際には、この作業は必要ない。
Vagrantfileの13行目と41行目のコメントマークを外して、パソコン側のIPアドレスを有効化する。これでパソコンの外部からK8sクラスタへアクセスできる。
<中略>
11 machine.vm.hostname = 'node1'
12 machine.vm.network :private_network,ip: "172.16.20.12"
13 machine.vm.network :public_network, ip: "192.168.1.92", bridge: "en0: Ethernet"
14 machine.vm.provider "virtualbox" do |vbox|
<中略>
39 machine.vm.hostname = 'node2'
40 machine.vm.network :private_network,ip: "172.16.20.13"
41 machine.vm.network :public_network, ip: "192.168.1.93", bridge: "en0: Ethernet"
42 machine.vm.provider "virtualbox" do |vbox|
以下のコマンドで、K8sクラスタを起動する。
tkr@luigi:~/k8s-1.14$ vagrant up
環境変数をセットして、起動が完了したら、kubectl get node
を実行して動作を確認する。
tkr@luigi:~/k8s-1.14$ export KUBECONFIG=`pwd`/kubeconfig/config
tkr@luigi:~/k8s-1.14$ kubectl get node
NAME STATUS ROLES AGE VERSION
master Ready master 12m v1.14.3
node1 Ready <none> 12m v1.14.3
node2 Ready <none> 12m v1.14.3
アプリ専用の名前空間作成と切替
チュートリアルには無い作業であるが、他のアプリケーション環境と混ざらないように、専用の名前空間(Namespace)を作成して、チュートリアルのアプリケーションの実行環境を分離する。
ゲストブック専用の名前空間 guestbook を作成する。
tkr@luigi:~/k8s-1.14$ kubectl create ns guestbook
名前空間を切り替える為に、コンテキストを作成する。
tkr@luigi:~/k8s-1.17$ kubectl config set-context gb --namespace=guestbook --cluster=kubernetes --user=kubernetes-admin
コンテキストが出来たことを確認する。
tkr@luigi:~/k8s-1.17$ kubectl config get-contexts
CURRENT NAME CLUSTER AUTHINFO NAMESPACE
gb kubernetes kubernetes-admin guestbook
* kubernetes-admin@kubernetes kubernetes kubernetes-admin
次のコマンドでは、コンテキストの切替を固定的にできる。これで、名前空間 guestbook がデフォルトの名前空間になる。
tkr@luigi:~/k8s-1.17$ kubectl config use-context gb
Switched to context "gb".
現在、自分が、どのコンテキストを利用しているか、確認する。 NAMESPACE列 gustbookになっていればOKである。
tkr@luigi:~/k8s-1.17$ kubectl config get-contexts
CURRENT NAME CLUSTER AUTHINFO NAMESPACE
* gb kubernetes kubernetes-admin guestbook
kubernetes-admin@kubernetes kubernetes kubernetes-admin
Redisマスターの起動
ここからは、Example: Deploying PHP Guestbook application with Redis に従うので、重複を出来るだけ避けて進める。そのため、本家のページを合わせて参照すると、良くわかるとおもう。
Redisのマスターサーバーをデプロイメントによって起動して、サービスを設定する。
tkr@luigi:~/k8s-1.14$ kubectl apply -f https://k8s.io/examples/application/guestbook/redis-master-deployment.yaml
tkr@luigi:~/k8s-1.14$ kubectl apply -f https://k8s.io/examples/application/guestbook/redis-master-service.yaml
Redisスレーブの起動
Redisのマスターからデータを同期するスレーブを作成する。同様にサービスも作成する。
tkr@luigi:~/k8s-1.14$ kubectl apply -f https://k8s.io/examples/application/guestbook/redis-slave-deployment.yaml
tkr@luigi:~/k8s-1.14$ kubectl apply -f https://k8s.io/examples/application/guestbook/redis-slave-service.yaml
このマニフェストの書かれたコンテナ・イメージの作成は、次のGitリポジトリにあるので、アプリケーションや設定を変更することもできる。https://github.com/kubernetes/examples/tree/master/guestbook
Redisスレーブのコンテナは、次の起動スクリプトで起動される。elseの下が通常使われる起動コマンドで、redis-master
は前述のマスターのサービス名となる。
if [[ ${GET_HOSTS_FROM:-dns} == "env" ]]; then
redis-server --slaveof ${REDIS_MASTER_SERVICE_HOST} 6379
else
redis-server --slaveof redis-master 6379
fi
redis-master
は、K8sの内部DNSによって、IPアドレスが解決され、Redisマスターにアクセスできる。
tkr@luigi:~/k8s-1.14$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
redis-master ClusterIP 10.32.0.252 <none> 6379/TCP 3h10m
redis-slave ClusterIP 10.32.0.34 <none> 6379/TCP 3h10m
Redisマスターが止まってしまった場合、このチュートリアルの構成では、スレーブのマスターへの昇格は、自動では無いので、SREとして対応が必要となる。このチュートリアルでは、Redisに永続ストレージを利用していない為、ポッドの停止と共に、データは失われる。
フロントエンドの起動
最後にフロント側のアプリケーションを起動して、サービスをデプロイして、外部からアクセスできるようにする。これでパソコンのブラウザからアクセスできるようになる。
tkr@luigi:~/k8s-1.14$ kubectl apply -f https://k8s.io/examples/application/guestbook/frontend-deployment.yaml
tkr@luigi:~/k8s-1.14$ kubectl apply -f https://k8s.io/examples/application/guestbook/frontend-service.yaml
フロントエンドのサービスのタイプは、NodePortとして公開する。ノードのIPアドレスにNodePortのポート番号でアクセスができる。
tkr@luigi:~/k8s-1.14$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
frontend NodePort 10.32.0.178 <none> 80:31767/TCP 3h6m
redis-master ClusterIP 10.32.0.252 <none> 6379/TCP 3h10m
redis-slave ClusterIP 10.32.0.34 <none> 6379/TCP 3h10m
このアプリケーションは、PHPで開発されており、Gitリポジトリに、コードが置かれてる。https://github.com/kubernetes/examples/tree/master/guestbook/php-redis
以下のPHPのコードは、インプットをRedisマスターに書き込み、参照時は スレーブから読み取るように作られている。 読み込みと書き込みの切替は、controllers.js
からのパラメータを受け取りPHP側の if文で分岐する。 そして、else内のredis-slave
は、複数のRedisスレーブへのリクエスト分散を行うため、スレーブとして起動されたポッドのIPアドレスがランダムの変換される。
<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);
require 'Predis/Autoloader.php';
Predis\Autoloader::register();
if (isset($_GET['cmd']) === true) {
$host = 'redis-master';
if (getenv('GET_HOSTS_FROM') == 'env') {
$host = getenv('REDIS_MASTER_SERVICE_HOST');
}
header('Content-Type: application/json');
if ($_GET['cmd'] == 'set') {
$client = new Predis\Client([
'scheme' => 'tcp',
'host' => $host,
'port' => 6379,
]);
$client->set($_GET['key'], $_GET['value']);
print('{"message": "Updated"}');
} else {
$host = 'redis-slave';
if (getenv('GET_HOSTS_FROM') == 'env') {
$host = getenv('REDIS_SLAVE_SERVICE_HOST');
}
$client = new Predis\Client([
'scheme' => 'tcp',
'host' => $host,
'port' => 6379,
]);
$value = $client->get($_GET['key']);
print('{"data": "' . $value . '"}');
}
} else {
phpinfo();
} ?>
アクセステスト
ブラウザから、ノード1192.168.1.92 または ノード2192.168.1.93 などのIPアドレスに、kubectl get svc
で表示されるポート番号を合わせたURL 例えば、http://192.168.1.92:31767
をアクセスすることで、ゲストブックの入力フィールドが表示される。入力フィールドにメッセージをインプットして、「Submit」をクリックすることで、Redisへデータを書き込む。
このアプリケーションは、マルチバイト文字に対応していませんから、再読み込みで、日本語表示は正しく表示されなくなります。
まとめ
このチュートリアルでは、簡単なYAMLファイルを適用するだけで、ポッド数を増減させることによりスケール、デプロイメント・コントローラーによるポッドの起動で可用性を体験することができた。このKubernetesの特徴を活かすためには、適したアプリケーションの設計が必要であることも、理解できるとおもう。
本来 redisは、永続データを保持するステートフルなアプリケーションであるため、このチュートリアルの構成だけで本番適用は難しいが、この構成に付け足して行けば良い。