RancherのKubernetesにサービスをデプロイしてみる

さくらのクラウドRancherOSでKubernetes環境を構築」の続きです。さくらのクラウドで Rancher + RancherOS を使って構築した Kubernetes 環境にサービスをデプロイしてみます。Kubernetes への deploy 自体は minikube でやったことがある(Kubernetes Secrets を使って minikube に netbox を deploy してみる)ので Rancher を使った場合のネットワーク構成とかを調査していきたい。

Caddy で Rancher の HTTPS 化

Kubernetes の前に、前回は Rancher サーバーに直接アクセスしていましたが、HTTPS 化のために Caddy を入れてみました。勝手に Let’s Encrypt っから証明書を取得して設定してくれるので便利です。

Caddy については先日「Caddy という高機能 HTTPS サーバー」を書きました。

適当な Dockerfile を書いて Docker Hub に push して使いました。実行時に Caddyfile をテンプレートから生成したかったので Entrykit を使いました。(Entrykit の使い方

FROM alpine
EXPOSE 80 443
ENV CADDYPATH /etc/ssl/caddy
STOPSIGNAL SIGQUIT
COPY ./caddy /usr/bin/caddy
COPY ./entrykit /usr/bin/entrykit
COPY ./Caddyfile.tmpl /etc/Caddyfile.tmpl
RUN mkdir -p /usr/share/caddy/html; mkdir -p /etc/ssl/caddy; chmod 755 /usr/bin/caddy /usr/bin/entrykit; /usr/bin/entrykit --symlink; apk --update add ca-certificates; rm -fr /var/cache/apk
ENTRYPOINT ["/usr/bin/render", "/etc/Caddyfile", \
            "--", \
            "/usr/bin/caddy", \
              "-log=stdout", "-agree=true", \
              "-conf=/etc/Caddyfile", "-root=/usr/share/caddy/html"]

普通の Reverse Proxy で良いのだろうと、こんな出来上がりになるようにしてみたところ、Rancher Agent からのアクセスは WebSocket が通る必要がありました。

rancher.teraoka.me {
    proxy / 172.17.0.2:8080 {
        header_upstream Host {host}
        header_upstream X-Forwarded-Proto {scheme}
    }
}

そこで -e RANCHER_USE_WEBSOCKET=true とした場合に1行 websocket と追加されるようにしました。

rancher.teraoka.me {
    proxy / 172.17.0.2:8080 {
        header_upstream Host {host}
        header_upstream X-Forwarded-Proto {scheme}
        websocket
    }
}

これで無事ブラウザからも Rancher Agent からのアクセスもできるようになりました。

無駄骨・・・

わざわざ別サーバーを間に入れなくても Rancher サーバーは 8080/tcp で HTTP にも HTTPS にも両方対応しているのでした!!Caddy サーバーをセットアップした後に気づきました・・・ 😢

Kubectl で Kubernetes にアクセス

Rancher 上部の「KUBERNETES」から「CLI」を選択すると次の画面になるのでここでブラウザから kubectl コマンドを実行することもできますが、「Generate Config」ボタンをクリックして生成される設定を ~/.kube/config にコピペすればローカル PC から kubectl コマンドでアクセスできるようになります。

Rancher Kubernetes CLI

Rancher Kubernetes CLI

ブラウザ内のコンソールから kubectl version を実行した出力

# Run kubectl commands inside here
# e.g. kubectl get rc

> kubectl version
Client Version: version.Info{Major:"1", Minor:"5", GitVersion:"v1.5.4", GitCommit:"7243c69eb523aa4377bce883e7c0dd76b84709a1", GitTreeState:"clean", BuildDate:"2017-03-07T23:53:09Z", GoVersion:"go1.7.4", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"5+", GitVersion:"v1.5.4-rancher1", GitCommit:"6ed2b64b2e1df9637661077d877a0483c58a6ae5", GitTreeState:"clean", BuildDate:"2017-03-17T16:58:04Z", GoVersion:"go1.7.4", Compiler:"gc", Platform:"linux/amd64"}

ローカル PC から試した出力(クライアントのバージョンが 1.6.0)

$ kubectl version
Client Version: version.Info{Major:"1", Minor:"6", GitVersion:"v1.6.0", GitCommit:"fff5156092b56e6bd60fff75aad4dc9de6b6ef37", GitTreeState:"clean", BuildDate:"2017-03-28T16:36:33Z", GoVersion:"go1.7.5", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"5+", GitVersion:"v1.5.4-rancher1", GitCommit:"6ed2b64b2e1df9637661077d877a0483c58a6ae5", GitTreeState:"clean", BuildDate:"2017-03-17T16:58:04Z", GoVersion:"go1.7.4", Compiler:"gc", Platform:"linux/amd64"}

kubectl

$ source <(kubectl completion bash)

とすれば補完が効いて便利になるようだzsh なら bash のところを zsh すればよし。

Guestbook Example アプリを Kubernetes にデプロイしてみる

https://github.com/kubernetes/kubernetes/tree/master/examples/guestbook にある Guestbook アプリをデプロイしてみる(Kubernetes の紹介で時々見かけるやつですね)。 guestbook-all-in-one.yaml を使うと一発でできちゃうんですが一箇所だけ修正します。 コメントアウトされている type: LoadBalancer をアンコメントします。

apiVersion: v1
kind: Service
metadata:
  name: frontend
  labels:
    app: guestbook
    tier: frontend
spec:
  # if your cluster supports it, uncomment the following to automatically create
  # an external load-balanced IP for the frontend service.
  type: LoadBalancer
  ports:
    # the port that this service should serve on
  - port: 80
  selector:
    app: guestbook
    tier: frontend
$ kubectl create -f guestbook-all-in-one.yaml --record
service "redis-master" created
deployment "redis-master" created
service "redis-slave" created
deployment "redis-slave" created
service "frontend" created
deployment "frontend" created

Kubernetes の Dashboard から Deployments を確認すると frontend という Apache + mod_php のアプリ Container (Pod) が3つと redis のマスターが1つ、 redis のレプリカが2つ起動しているのが確認できます。

Kubernetes Dashboard Deployments - guestbook

Kubernetes Dashboard Deployments - guestbook

Services を確認するとそれぞれの Cluster IP が確認できます。

Kubernetes Dashboard Services - guestbook

Kubernetes Dashboard Services - guestbook

External Endpoints に表示されているIPアドレス、ポート番号にブラウザからアクセスすると Guestbook アプリにアクセスできます。次のような表示になります。

guestbook-app

guestbook-app

名前解決

guestbook.php の中身は次のようになっており redis のサーバー名は GET_HOSTS_FROM という環境変数が env の場合は環境変数 REDIS_MASTER_SERVICE_HOST, REDIS_SLAVE_SERVICE_HOST から取得し、そうでない場合は redis-master, redis-slave という名前で DNS によって解決しています。guestbook-all-in-one.yaml では GET_HOSTS_FROMdns になっていますから DNS ですね。rancher-dns ってのが動いてるっぽいけど resolv.conf にある 10.43.0.10 というアドレスがどこにどう定義されているのか要調査。

<?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();
} ?>

LoadBalancer

guestbook-all-in-one.yamltype: LoadBalancer 行をアンコメントしましたが、これによって何ができたかというと「KUBERNETES」の「Infrastructure Stacks」を確認すると「kubernetes loadbalancers」に次のような表示が確認できます。

rancher kubernetes loadbalancers

この中で lb-a9a2059bd2efb11e7a82402a939d3449 を見てみると次のような情報も確認できます。「Ports」ではどのホストのIPアドレスで外からのアクセスを受け付けるようになっているかが確認できます。今回の例では k8s-01 のIPアドレスになっています。

Rancher Kubernetes LB Service (port)

Balancer Rules タブではどのホストのどのポート (container) に転送するかがわかります。

Rancher Kubernetes LB Service (rules)

この Load Balancer は HAProxy コンテナで実装されています。haproxy.cfg を確認してみると次のようになっていました。proxy 先は Global IP Address なのですね。ホストがインターネットに晒されている場合は iptables や手間でのどこかで閉じていないとここに直接アクセスできてしまいますね。Service の Cluster IP に転送するのかと思っていたが違っていたようだ。Cluster IP は Kubernetes 内でアクセスするためのアドレスだから外から転送するには NodePort を使わざるを得ないということか。

global
    chroot /var/lib/haproxy
    daemon
    group haproxy
    maxconn 4096
    maxpipes 1024
    ssl-default-bind-ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA
    ssl-default-bind-options no-sslv3 no-tlsv10
    ssl-default-server-ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA
    tune.ssl.default-dh-param 2048
    user haproxy

defaults
    errorfile 400 /etc/haproxy/errors/400.http
    errorfile 403 /etc/haproxy/errors/403.http
    errorfile 408 /etc/haproxy/errors/408.http
    errorfile 500 /etc/haproxy/errors/500.http
    errorfile 502 /etc/haproxy/errors/502.http
    errorfile 503 /etc/haproxy/errors/503.http
    errorfile 504 /etc/haproxy/errors/504.http
    maxconn 4096
    mode tcp
    option forwardfor
    option http-server-close
    option redispatch
    retries 3
    timeout client 50000
    timeout connect 5000
    timeout server 50000

resolvers rancher
 nameserver dnsmasq 169.254.169.250:53

listen default
bind *:42

frontend 80
bind *:80
mode tcp
default_backend 80_

backend 80_
acl forwarded_proto hdr_cnt(X-Forwarded-Proto) eq 0
acl forwarded_port hdr_cnt(X-Forwarded-Port) eq 0
    http-request add-header X-Forwarded-Port %[dst_port] if forwarded_port
    http-request add-header X-Forwarded-Proto https if { ssl_fc } forwarded_proto
mode tcp
server 9c51f1b00fd9e1eb2211cde0ed46d9456d0213f5 153.120.129.113:31877
server aa73f7199039e58a2c5c6081e56edf6d27e06c31 153.120.82.8:31877
server cf4c62546250ba397c98196c8ce9c08ceef88342 133.242.49.48:31877

この LB は1台のホストでしか稼働していないのでその1台が止まってしまうとこまります。でも Service ページの左側にある「Scale」欄の「+ / -」で増減できます。3に増やすことで k8s-01, k8s-02, k8s-03 のどのサーバーでも受けられるようにできます。

LB 1台の状態で当該ホストを強制シャットダウンしたら別の当該ホストで稼働していた他のコンテナ同様に別のホストでえ起動してきました。Rancher の Proxy 経由でアクセスする Kubernetes の dashboard はなぜかなかなか切り替わってくれなかったけど、kubectl でアクセスする方はすぐに切り替わってました。

続き「RancherのKubernetesにサービスをデプロイしてみる(2)

Built with Hugo
テーマ StackJimmy によって設計されています。