「さくらのクラウド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 コマンドでアクセスできるようになります。
ブラウザ内のコンソールから 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つ起動しているのが確認できます。
Services を確認するとそれぞれの Cluster IP が確認できます。
External Endpoints
に表示されているIPアドレス、ポート番号にブラウザからアクセスすると Guestbook アプリにアクセスできます。次のような表示になります。
名前解決
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_FROM
は dns
になっていますから 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.yaml
の type: LoadBalancer
行をアンコメントしましたが、これによって何ができたかというと「KUBERNETES
」の「Infrastructure Stacks
」を確認すると「kubernetes loadbalancers
」に次のような表示が確認できます。
この中で lb-a9a2059bd2efb11e7a82402a939d3449
を見てみると次のような情報も確認できます。「Ports
」ではどのホストのIPアドレスで外からのアクセスを受け付けるようになっているかが確認できます。今回の例では k8s-01 のIPアドレスになっています。
Balancer Rules
タブではどのホストのどのポート (container) に転送するかがわかります。
この 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 でアクセスする方はすぐに切り替わってました。