LoginSignup
4
2

More than 5 years have passed since last update.

Kubernetes1.10 チュートリアル (ステートレスアプリケーション, Pod間の接続)

Posted at

はじめに

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 を押すと、ページに文字が追加されていきます。

001.png

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ページで表示されていることがわかります

001.png

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/

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2