はじめに
Kubernetesの公式ページに、多くのチュートリアルが掲載されています。
Kubernetesの勉強の一環で、チュートリアルを消化していきます。
今回は statekess-applicationをKubernetes上で構築してみます。
https://kubernetes.io/docs/tutorials/stateless-application/guestbook/
なお、Kubernetesのバージョンは、2018年4月時点で最新の1.10を使用しています。
チュートリアルで構築するステートレスアプリケーションは、guestbookアプリケーションです。
ホテルの宿泊先にあるようなゲストブックのことなのでしょうか。
Webページを開くと、誰でもコメントを残すことが出来るシステムとなっています。
Start up the Redis Master
Creating the Redis Master Deployment
ゲストブックアプリケーションは、Redisを使用しています。Redisは、メモリを使用したデータ構造化ソフトウェアのようで、高速に key-value を読み書きできるもののようです。
ゲストブックアプリケーションは、データの書き込みにRedis Master Instanceを使用し、データの読み取りに複数のRedis Slave Instanceを使用します。
以下のYAMLマニフェストファイルを作成します。
cat <<'EOF' > /root/kube_yaml/guestbook/redis-master-deployment.yaml
apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2
kind: Deployment
metadata:
name: redis-master
spec:
selector:
matchLabels:
app: redis
role: master
tier: backend
replicas: 1
template:
metadata:
labels:
app: redis
role: master
tier: backend
spec:
containers:
- name: master
image: k8s.gcr.io/redis:e2e # or just image: redis
resources:
requests:
cpu: 100m
memory: 100Mi
ports:
- containerPort: 6379
EOF
Deploymentを作成し、Redis Master を作成します。
kubectl apply -f /root/kube_yaml/guestbook/redis-master-deployment.yaml
Pod一覧を確認します。
[root@sugi-kubernetes110-master01 guestbook(default kubernetes-admin)]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE
redis-master-55db5f7567-hkzhl 1/1 Running 0 5m 10.244.2.18 sugi-kubernetes110-node02.localdomain
Creating the Redis Master Service
guestbook アプリケーションは、Redis Master を接続して、データを書き込む必要があります。
Redis Master Pod に サービス ClusterIP を作成して、アクセス可能な状態にします。
なお、ClusterIPは、クラスタ内でのみアクセス可能なサービスなので、外部からはアクセスできません。
以下のマニフェストファイルを作成します。
cat <<'EOF' > /root/kube_yaml/guestbook/redis-master-service.yaml
apiVersion: v1
kind: Service
metadata:
name: redis-master
labels:
app: redis
role: master
tier: backend
spec:
ports:
- port: 6379
targetPort: 6379
selector:
app: redis
role: master
tier: backend
EOF
Serviceを作成します
kubectl apply -f /root/kube_yaml/guestbook/redis-master-service.yaml
Service一覧を確認します
[root@sugi-kubernetes110-master01 guestbook(default kubernetes-admin)]# kubectl get service -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 2d <none>
redis-master ClusterIP 10.110.158.172 <none> 6379/TCP 1m app=redis,role=master,tier=backend
Start up the Redis Slaves
Creating the Redis Slave Deployment
Redis Master のPodは1個ですが、RedisSlaveをいくつか立ち上げることにより、高可用性を実現することができます。
以下のマニフェストファイルを作成します。
cat <<'EOF' > /root/kube_yaml/guestbook/redis-slave-deployment.yaml
apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2
kind: Deployment
metadata:
name: redis-slave
spec:
selector:
matchLabels:
app: redis
role: slave
tier: backend
replicas: 2
template:
metadata:
labels:
app: redis
role: slave
tier: backend
spec:
containers:
- name: slave
image: gcr.io/google_samples/gb-redisslave:v1
resources:
requests:
cpu: 100m
memory: 100Mi
env:
- name: GET_HOSTS_FROM
value: dns
# Using `GET_HOSTS_FROM=dns` requires your cluster to
# provide a dns service. As of Kubernetes 1.3, DNS is a built-in
# service launched automatically. However, if the cluster you are using
# does not have a built-in DNS service, you can instead
# access an environment variable to find the master
# service's host. To do so, comment out the 'value: dns' line above, and
# uncomment the line below:
# value: env
ports:
- containerPort: 6379
EOF
Podを作成します。
kubectl apply -f /root/kube_yaml/guestbook/redis-slave-deployment.yaml
Podの一覧を確認します
[root@sugi-kubernetes110-master01 guestbook(default kubernetes-admin)]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE
redis-master-55db5f7567-hkzhl 1/1 Running 0 26m 10.244.2.18 sugi-kubernetes110-node02.localdomain
redis-slave-584c66c5b5-59d5k 1/1 Running 0 5m 10.244.2.19 sugi-kubernetes110-node02.localdomain
redis-slave-584c66c5b5-fl2cc 1/1 Running 0 5m 10.244.1.20 sugi-kubernetes110-node01.localdomain
Creating the Redis Slave Service
マニフェストファイルを作成します。
cat <<'EOF' > /root/kube_yaml/guestbook/redis-slave-service.yaml
apiVersion: v1
kind: Service
metadata:
name: redis-slave
labels:
app: redis
role: slave
tier: backend
spec:
ports:
- port: 6379
selector:
app: redis
role: slave
tier: backend
EOF
ClusterIP のサービスを作成します
kubectl apply -f /root/kube_yaml/guestbook/redis-slave-service.yaml
Service一覧を確認します
[root@sugi-kubernetes110-master01 guestbook(default kubernetes-admin)]# kubectl get svc -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 3d <none>
redis-master ClusterIP 10.110.158.172 <none> 6379/TCP 29m app=redis,role=master,tier=backend
redis-slave ClusterIP 10.104.192.49 <none> 6379/TCP 11s app=redis,role=slave,tier=backend
Set up and Expose the Guestbook Frontend
Creating the Guestbook Frontend Deployment
guestbookアプリケーションのweb frontend Podを作成します。Frontendは、PHP で書かれています。
Write Requestは、Redis Master に対して読み込みに行きます。Read Request は Redis Slave に読み込みにいきます。
以下のマニフェストファイルを作成します。
cat <<'EOF' > /root/kube_yaml/guestbook/frontend-deployment.yaml
apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2
kind: Deployment
metadata:
name: frontend
spec:
selector:
matchLabels:
app: guestbook
tier: frontend
replicas: 3
template:
metadata:
labels:
app: guestbook
tier: frontend
spec:
containers:
- name: php-redis
image: gcr.io/google-samples/gb-frontend:v4
resources:
requests:
cpu: 100m
memory: 100Mi
env:
- name: GET_HOSTS_FROM
value: dns
# Using `GET_HOSTS_FROM=dns` requires your cluster to
# provide a dns service. As of Kubernetes 1.3, DNS is a built-in
# service launched automatically. However, if the cluster you are using
# does not have a built-in DNS service, you can instead
# access an environment variable to find the master
# service's host. To do so, comment out the 'value: dns' line above, and
# uncomment the line below:
# value: env
ports:
- containerPort: 80
EOF
Podを作成します
kubectl apply -f /root/kube_yaml/guestbook/frontend-deployment.yaml
Podの一覧を確認します
kubectl get pods -o wide -l app=guestbook -l tier=frontend
Creating the Frontend Service
Redis Master と Redis Slave に作成したServiceは、クラスタ内でのみアクセス可能なサービスのClusterIPです。マニフェストファイル内でServiceのTypeを指定しない場合、DefaultとしてClusterIPが作成されます。
クラスタ外部からアクセスしたい場合、ServiceTypeの NodePort と LoadBalancer の2パターンが存在します。オンプレミスの現在の環境では、LoadBalancerが使用できないので、NodePortを使用して Frontend Pod を公開します。
(MetalLBを使用するとオンプレミスでもLoadBalancerが使用できるらしい。)
以下のマニフェストファイルを作成します。
NodePort で公開します。
cat <<'EOF' > /root/kube_yaml/guestbook/frontend-service.yaml
apiVersion: v1
kind: Service
metadata:
name: frontend
labels:
app: guestbook
tier: frontend
spec:
# comment or delete the following line if you want to use a LoadBalancer
type: NodePort
# if your cluster supports it, uncomment the following to automatically create
# an external load-balanced IP for the frontend service.
# type: LoadBalancer
ports:
- port: 80
selector:![001.png](https://qiita-image-store.s3.amazonaws.com/0/104247/23a02a3d-d5fe-a6b0-c5ab-d4140861d492.png)
app: guestbook
tier: frontend
EOF
Serviceを作成します
kubectl apply -f /root/kube_yaml/guestbook/frontend-service.yaml
Service一覧を確認します
NodePortが作成されています
[root@sugi-kubernetes110-master01 guestbook(default kubernetes-admin)]# kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
frontend NodePort 10.107.155.181 <none> 80:32423/TCP 10s
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 3d
redis-master ClusterIP 10.110.158.172 <none> 6379/TCP 3h
redis-slave ClusterIP 10.104.192.49 <none> 6379/TCP 2h
NodePortにアクセス
NodePortは、Kubernetesクラスタのどのホストに対してもアクセスが可能です。
クラスタ外のWindowsなどのクライアントマシンから、以下のURLへアクセスします。
ポート番号の32423は、kubectl get serviceで表示したNodePortのPORT(s) 列から確認できます。
Messages に文字を入力して、Submit を押すと、ページに文字が追加されていきます。
FrontendのPodにアクセスして、html, php, dns, 環境変数などを確認
Podの一覧を確認します
[root@sugi-kubernetes110-master01 ~(default kubernetes-admin)]# kubectl get pods
NAME READY STATUS RESTARTS AGE
frontend-5c548f4769-rktkj 1/1 Running 0 3h
frontend-5c548f4769-whgcm 1/1 Running 0 3h
frontend-5c548f4769-zv6wv 1/1 Running 0 3h
redis-master-55db5f7567-hkzhl 1/1 Running 0 4h
redis-slave-584c66c5b5-59d5k 1/1 Running 0 4h
redis-slave-584c66c5b5-fl2cc 1/1 Running 0 4h
FrontendPodの1個に遠隔からbashを起動して、プロンプトにはいります
kubectl exec -it frontend-5c548f4769-rktkj bash
Index.htmlファイルを確認します
HTMLのボタンなどが、JavaScriptと連動しているような雰囲気を感じます
root@frontend-5c548f4769-rktkj:/var/www/html# cat /var/www/html/index.html
<html ng-app="redis">
<head>
<title>Guestbook</title>
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.12/angular.min.js"></script>
<script src="controllers.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.13.0/ui-bootstrap-tpls.js"></script>
</head>
<body ng-controller="RedisCtrl">
<div style="width: 50%; margin-left: 20px">
<h2>Guestbook</h2>
<form>
<fieldset>
<input ng-model="msg" placeholder="Messages" class="form-control" type="text" name="input"><br>
<button type="button" class="btn btn-primary" ng-click="controller.onRedis()">Submit</button>
</fieldset>
</form>
<div>
<div ng-repeat="msg in messages track by $index">
{{msg}}
</div>
</div>
</div>
</body>
</html>
root@frontend-5c548f4769-rktkj:/var/www/html#
コンテナ内のJavaScriptファイルを確認します
同様に詳細はわかりませんが、PHPと連動している雰囲気を感じます
root@frontend-5c548f4769-rktkj:/var/www/html# cat /var/www/html/controllers.js
var redisApp = angular.module('redis', ['ui.bootstrap']);
/**
* Constructor
*/
function RedisController() {}
RedisController.prototype.onRedis = function() {
this.scope_.messages.push(this.scope_.msg);
this.scope_.msg = "";
var value = this.scope_.messages.join();
this.http_.get("guestbook.php?cmd=set&key=messages&value=" + value)
.success(angular.bind(this, function(data) {
this.scope_.redisResponse = "Updated.";
}));
};
redisApp.controller('RedisCtrl', function ($scope, $http, $location) {
$scope.controller = new RedisController();
$scope.controller.scope_ = $scope;
$scope.controller.location_ = $location;
$scope.controller.http_ = $http;
$scope.controller.http_.get("guestbook.php?cmd=get&key=messages")
.success(function(data) {
console.log(data);
$scope.messages = data.data.split(",");
});
});
root@frontend-5c548f4769-rktkj:/var/www/html#
PHPファイルを確認します
root@frontend-5c548f4769-rktkj:/var/www/html# cat /var/www/html/guestbook.php
<?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();
} ?>
root@frontend-5c548f4769-rktkj:/var/www/html#
PHPの内容を抜粋して確認していきます
12行目を抜粋します。
$host = 'redis-master'; <----------------------- FrontendPodからアクセスする RedisMaster を定義しています。これは、KubernetesのService名と同一の名称を指定しています
FrontendPodのresolv.confを確認します
root@frontend-5c548f4769-rktkj:/var/www/html# cat /etc/resolv.conf
nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local localdomain
options ndots:5
resolv.confで参照している 10.96.0.10 は、 kube-dns の ClusterIPサービスを参照しています
[root@sugi-kubernetes110-master01 manifests(default kubernetes-admin)]# kubectl get svc -o wide --all-namespaces
NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
default frontend NodePort 10.107.155.181 <none> 80:32423/TCP 2h app=guestbook,tier=frontend
default kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 3d <none>
default redis-master ClusterIP 10.110.158.172 <none> 6379/TCP 5h app=redis,role=master,tier=backend
default redis-slave ClusterIP 10.104.192.49 <none> 6379/TCP 4h app=redis,role=slave,tier=backend
kube-system kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP 3d k8s-app=kube-dns
FrontendPodから、redis-masterを名前解決してみます
redis-master.default.svc.cluster.local は、サービス名.ネームスペース名.svc.cluster.local というルールで、自動的に kube-dns にレコードが付与されます。
10.110.158.172は、redis-masterの ClusterIP サービスが名前解決されています
root@frontend-5c548f4769-rktkj:/var/www/html# ping redis-master
PING redis-master.default.svc.cluster.local (10.110.158.172): 56 data bytes
FrontendPodの環境変数を確認します
root@frontend-5c548f4769-rktkj:~# env
REDIS_SLAVE_PORT_6379_TCP=tcp://10.104.192.49:6379
REDIS_SLAVE_SERVICE_HOST=10.104.192.49
HOSTNAME=frontend-5c548f4769-rktkj
KUBERNETES_PORT_443_TCP_PORT=443
KUBERNETES_PORT=tcp://10.96.0.1:443
TERM=xterm
PHP_INI_DIR=/usr/local/etc/php
PHP_FILENAME=php-5.6.20.tar.xz
REDIS_SLAVE_PORT=tcp://10.104.192.49:6379
KUBERNETES_SERVICE_PORT=443
KUBERNETES_SERVICE_HOST=10.96.0.1
GET_HOSTS_FROM=dns
REDIS_MASTER_PORT_6379_TCP_ADDR=10.110.158.172
REDIS_MASTER_PORT_6379_TCP=tcp://10.110.158.172:6379
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
REDIS_SLAVE_PORT_6379_TCP_PROTO=tcp
GPG_KEYS=0BD78B5F97500D450838F95DFE857D9A90D90EC1 6E4F6AB321FDC07F2C332E3AC2BF0BC433CFC8B3
REDIS_MASTER_SERVICE_PORT=6379
PWD=/root
REDIS_SLAVE_SERVICE_PORT=6379
REDIS_MASTER_SERVICE_HOST=10.110.158.172
SHLVL=1
HOME=/root
REDIS_SLAVE_PORT_6379_TCP_ADDR=10.104.192.49
PHP_SHA256=2b87d40213361112af49157a435e0d4cdfd334c9b7c731c8b844932b1f444e7a
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBERNETES_SERVICE_PORT_HTTPS=443
REDIS_MASTER_PORT_6379_TCP_PORT=6379
REDIS_SLAVE_PORT_6379_TCP_PORT=6379
REDIS_MASTER_PORT_6379_TCP_PROTO=tcp
PHP_EXTRA_BUILD_DEPS=apache2-dev
REDIS_MASTER_PORT=tcp://10.110.158.172:6379
KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
PHP_VERSION=5.6.20
PHP_EXTRA_CONFIGURE_ARGS=--with-apxs2
_=/usr/bin/env
OLDPWD=/etc
上記の環境変数の中で、以下を抜粋します。
Kubernetesで作成したServiceは、Kubeletが自動的に環境変数に作成します。
https://kubernetes.io/docs/concepts/services-networking/service/#environment-variables
注意点として、Pod作成時点のServiceのみ環境変数へ定義するので、
Pod作成後に作成したServiceは環境変数には定義されません。kube-dnsは、動的に追加されるので、環境変数よりはDNSを使用するのが良いと思います
root@frontend-5c548f4769-rktkj:~# env | grep REDIS_MASTER
REDIS_MASTER_PORT_6379_TCP_ADDR=10.110.158.172
REDIS_MASTER_PORT_6379_TCP=tcp://10.110.158.172:6379
REDIS_MASTER_SERVICE_PORT=6379
REDIS_MASTER_SERVICE_HOST=10.110.158.172
REDIS_MASTER_PORT_6379_TCP_PORT=6379
REDIS_MASTER_PORT_6379_TCP_PROTO=tcp
REDIS_MASTER_PORT=tcp://10.110.158.172:6379
RedisMasterでRedis上のデータを確認
RedisMasterPodにbashログインします
[root@sugi-kubernetes110-master01 ~(default kubernetes-admin)]# kubectl exec -it redis-master-55db5f7567-hkzhl bash
[ root@redis-master-55db5f7567-hkzhl:/data ]$
redis-cli を起動し、全てのkeyを確認します
messagesが表示されました
[ root@redis-master-55db5f7567-hkzhl:/data ]$ redis-cli
127.0.0.1:6379>
127.0.0.1:6379> keys '*'
1) "messages"
get コマンドで messages に対応する value を確認します
127.0.0.1:6379> get messages
",test_one,aaa"
上記のvalueがWebページで表示されていることがわかります
cleaning up
作成したDeployment, Serviceを削除します
kubectl delete deployment frontend redis-master redis-slave
参考URL
チュートリアルページ
Example: Deploying PHP Guestbook application with Redis
https://kubernetes.io/docs/tutorials/stateless-application/guestbook/