前回からの続きです。
Bootstrapping the etcd Cluster
etcdはkubernetesのクラスタの状態などのデータを管理します。これを3台のmasterでHAを組む形で設定していきます。
まず、各controllerにログインします。tmuxとか使って並列でやると捗るかもしれません。
$ vagrant ssh controller-0
etcdをインストールします。スクリプトは、vagrantでフォルダ同期が予め設定されているので、ローカルのリポジトリは/vagrantに見えているはずです。
$ /vagrant/scripts/k8s-the-hard-way/0700-download-and-install-etcd.sh
結構時間かかりましたが、これで/usr/local/bin/etcdと/usr/local/bin/etcdctlが配置されます。
次にetcdの設定です。
- /etc/etcdディレクトリを作成し、ここに認証局の証明書とkube-api-server用のサーバ証明書・秘密鍵を配置します
- /var/lib/etcdディレクトリを作成します。ここに各種情報が保存されます。
- etcd間の通信に使用する内部IPは各ノードの10.240.0.X/24のIPアドレスになります。ここはGCPとは違うかも。
- etcdが動作するホスト名を取得します。このホスト名はクラスタ内でユニークでなければなりません。
- これらを踏まえたsystemd用のunitファイルを生成し配置します。
$ /vagrant/scripts/k8s-the-hard-way/0701-configure-etcd.sh
これで/etc/systemd/system/etcd.serviceが作成されます。
ではetcdの起動設定を行い起動します。
$ /vagrant/scripts/k8s-the-hard-way/0702-start-etcd.sh
etcdが正しく動作しているか確認します。スクリプト内ではetcdctl member listを使ってメンバー一覧を取得しています。。
$ /vagrant/scripts/k8s-the-hard-way/0710-verify-etcd.sh
以下のようにすべてのetcdメンバーが表示されればOKです。
xxxxxxxxxxxxxxxxxx, started, controller-2, https://10.240.0.12:2380, https://10.240.0.12:2379 xxxxxxxxxxxxxxxxxx, started, controller-0, https://10.240.0.10:2380, https://10.240.0.10:2379 xxxxxxxxxxxxxxxxxx, started, controller-1, https://10.240.0.11:2380, https://10.240.0.11:2379
残りのcontrollerノードについても同様に行います。
Bootstrapping the Kubernetes Control Plane
続けて、kubernetesのcontrollerノードとしての設定を行っていきます。
まず、各controllerにログインします。
$ vagrant ssh controller-0
controllerノードに必要なコンポーネントをインストールします。
- /etc/kubernetes/configディレクトリを作成します。設定ファイルはここに保存されます。
- kube-apiserver、kube-controller-manager、kube-scheduler、kubectlを/usr/local/bin配下にインストールします。
$ /vagrant/scripts/k8s-the-hard-way/0800-download-and-install-k8s-controllers.sh
kube-apiserverの設定を行います。
- /var/lib/kubernetesを作成し、認証局・kube-apiserver・サービスアカウント用の証明書と鍵、あと暗号化設定を配置します。
- 各contollerノードが他のメンバーにアドバタイズするkube-apiserver内部IPは各ノードの10.240.0.X/24のIPアドレスになります。
- これらを踏まえたsystemd用のunitファイルを生成し配置します。
$ /vagrant/scripts/k8s-the-hard-way/0801-configure-k8s-api-server.sh
kube-controller-managerの設定を行います。
- kube-controller-manager.kubeconfig を/var/lib/kubernetes配下にコピーします
- systemd用のunitファイルを生成し配置します。
$ /vagrant/scripts/k8s-the-hard-way/0802-configure-k8s-controller-manager.sh
kube-schedulerの設定を行います。
- kube-scheduler.kubeconfigを/var/lib/kubernetes配下にコピーします
- systemd用のunitファイルを生成し配置します。
$ /vagrant/scripts/k8s-the-hard-way/0803-configure-k8s-schedueler.sh
ではこれらの起動設定を行い起動します。
$ /vagrant/scripts/k8s-the-hard-way/0804-start-controller-services.sh
確認です。kubectl get componentstatusesで各コンポーネントを確認しています。
$ /vagrant/scripts/k8s-the-hard-way/0805-verify.sh
こんな感じですべてがHealthyであればOKです。
NAME STATUS MESSAGE ERROR
scheduler Healthy ok
controller-manager Healthy ok
etcd-1 Healthy {"health":"true"}
etcd-2 Healthy {"health":"true"}
etcd-0 Healthy {"health":"true"}
次にRBACの設定です。RBACはRole Based Access Controlの略で、masterのAPIサーバからworkerのkubeletにアクセスして認可を行うためのモジュールらしいです。ログやメトリクスの取得、podに対するコマンド実行の際に使用されます。ちょっと細かいところは後で読むこととします。
以下が作成されます。
- ClusterRole
- クラスタに対するロールを作成し、ノードに対するいろいろな権限を付与
- ClusterRoleBinding
- 上記のロールをkubernetesユーザと紐付ける
$ /vagrant/scripts/k8s-the-hard-way/0810-rbac-for-kubelet-auth.sh
フロントエンドのロードバランサーの確認です。オリジナルではGCPなのでNetwork Load Balancerを作成してましたが、Vagrant版ではlb-0になります。ローカルのMacから10.240.0.40に対して、curlでアクセスして、バージョンを確認します。
$ ./scripts/k8s-the-hard-way/0820-verify-frontend-lb.sh
以下のように表示されればOKです。
{ "major": "1", "minor": "10", "gitVersion": "v1.10.2", "gitCommit": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "gitTreeState": "clean", "buildDate": "2018-04-27T09:10:24Z", "goVersion": "go1.9.3", "compiler": "gc", "platform": "linux/amd64"
Bootstrapping the Kubernetes Worker Nodes
次はworkerノードの設定です。各workerにsshでログインします。
$ vagrant ssh worker-0
まずworkerノードに必要なパッケージやコンポーネントをインストール、配置します。個々のコンポーネントの意味はよく理解できてませんが、nodeの場合、コンテナのランタイムやネットワーク関連のものが多いですね。
- socat
- conntrack
- ipset
- crictl
- runsc
- cni-plugins
- containerd
- kubectl
- kube-proxy
- kubelet
$ /vagrant/scripts/k8s-the-hard-way/0900-download-and-install-workers.sh
CNIの設定を行います。CNI はContainer Network Interfaceの略で、Linuxコンテナ作成時・削除時にネットワーク周りをよしなに設定してくれるものらしいです。Linxuのネットワーク周り、ちょっと難しくなるとさっぱりわからない、ネットワークの基本知識が足りない気がする・・・
$ /vagrant/scripts/k8s-the-hard-way/0901-configure-cni.sh
コンテナランタイムであるcontainerdの設定を行います。dockerも内部的にはcontainerdを使ってるらしいです。
- 設定ファイルを作成
- systemdで起動するようにunitファイルを用意
$ /vagrant/scripts/k8s-the-hard-way/0902-configure-containerd.sh
kubeletの設定を行います。
- 証明書を所定のパスに配置
- 設定ファイルを作成
- systemdで起動するようにunitファイルを用意
$ /vagrant/scripts/k8s-the-hard-way/0903-configure-kubelet.sh
kube-proxyの設定を行います。
- kube-proxyの設定ファイルを所定のパスに配置
- systemdで起動するようにunitファイルを用意
$ /vagrant/scripts/k8s-the-hard-way/0904-configure-kube-proxy.sh
workerのサービス起動を設定し、起動します。
$ /vagrant/scripts/k8s-the-hard-way/0905-start-worker-services.sh
ローカルのmacからサービスが正常に動いているか確認します。といっても中身はcontrollerからkubectl get nodesしてますね。
$ ./scripts/k8s-the-hard-way/0910-verify-worker.sh
以下のように表示されればOKです。
AME STATUS ROLES AGE VERSION worker-0 Ready <none> 1m v1.10.2 worker-1 Ready <none> 1m v1.10.2 worker-2 Ready <none> 1m v1.10.2
Configuring kubectl for Remote Access
ローカルのmacで、リモートからadminユーザでkubectlするための設定を行っていきます。リモートからのアクセスはlb-0のIPアドレスに対して行います。
$ ./scripts/k8s-the-hard-way/1000-admin-kubeconfig.sh
確認します。
$ ./scripts/k8s-the-hard-way/1001-verify-admin-kubeconfig.sh
スクリプト内ではkubectl get componentstatusとkubectl get nodesが実行されており、以下のように表示されればOKです。
NAME STATUS MESSAGE ERROR scheduler Healthy ok etcd-0 Healthy {"health":"true"} etcd-2 Healthy {"health":"true"} controller-manager Healthy ok etcd-1 Healthy {"health":"true"} NAME STATUS ROLES AGE VERSION worker-0 Ready <none> 11m v1.10.2 worker-1 Ready <none> 11m v1.10.2 worker-2 Ready <none> 11m v1.10.2
Provisioning Pod Network Routes
ここはGCP向けなのでスキップします。
Deploying the DNS Cluster Add-on
CoreDNSを使ってDNSベースのサービスディスカバリーの設定を行います。kube-dnsのセットアップが行われているようです。
$ ./scripts/k8s-the-hard-way/1200-dns-cluster-add-on.sh ./scripts/k8s-the-hard-way/1200-dns-cluster-add-on.sh: line 5: watch: command not found
ということで、watchコマンドをインストールする必要がありました。brewでインストールします。
$ brew install watch
で、スクリプトを再度実行します。多分同じことを再度やっても大丈夫なはず。以下のように表示されればOKです。
Every 2.0s: kubectl get pods -l k8s-app=kube-dns -n kube-system xxxxxx: Sun Feb 16 22:01:44 2020 NAME READY STATUS RESTARTS AGE kube-dns-598d7bf7d4-4st68 3/3 Running 0 20m
では確認です。名前解決用にbusyboxのpodを用意します。
$ ./scripts/k8s-the-hard-way/1210-verify-dns.sh
暫く待つとこんな感じになります。
Every 2.0s: kubectl get pods -l run=busybox xxxxxx: Sun Feb 16 22:06:00 2020 NAME READY STATUS RESTARTS AGE busybox-68654f944b-vs5xm 1/1 Running 0 38s
このpodでnslookupします。
$ ./scripts/k8s-the-hard-way/1211-verify-dns.sh
ちょっと私の手元の環境では解決できているものもあるけど、そうでないものもあるって感じになってますね。。。。
Server: 10.32.0.10 Address: 10.32.0.10:53 Name: kubernetes.default.svc.cluster.local Address: 10.32.0.1 *** Can't find kubernetes.svc.cluster.local: No answer *** Can't find kubernetes.cluster.local: No answer *** Can't find kubernetes.lan: No answer *** Can't find kubernetes.default.svc.cluster.local: No answer *** Can't find kubernetes.svc.cluster.local: No answer *** Can't find kubernetes.cluster.local: No answer *** Can't find kubernetes.lan: No answer
で調べてみると、どうもbusyboxのタグ指定が必要らしい。
kubectl delete podで削除したんだけど、また上がってくるので、deployment消してからpod削除しました。タグ指定して実行してみる。
$ kubectl delete deployment busybox $ kubectl delete pod busybox-XXXXXXXX-XXXXX --grace-period=0 --force $ kubectl run busybox --image=busybox:1.28 --command -- sleep 3600
再度実行。
$ ./scripts/k8s-the-hard-way/1211-verify-dns.sh
うまくいきました。
Server: 10.32.0.10 Address 1: 10.32.0.10 kube-dns.kube-system.svc.cluster.local Name: kubernetes Address 1: 10.32.0.1 kubernetes.default.svc.cluster.local
Smoke Test
ではざっと確認していきます。ここはスクリプトの中身を見て実行していきたいと思います。
データの暗号化。
$ ./scripts/k8s-the-hard-way/1300-data-encryption.sh
以下のようにenc:aescbc:v1:key1
が表示されていればOKです。
secret/kubernetes-the-hard-way created 00000000 2f 72 65 67 69 73 74 72 79 2f 73 65 63 72 65 74 |/registry/secret| 00000010 73 2f 64 65 66 61 75 6c 74 2f 6b 75 62 65 72 6e |s/default/kubern| 00000020 65 74 65 73 2d 74 68 65 2d 68 61 72 64 2d 77 61 |etes-the-hard-wa| 00000030 79 0a 6b 38 73 3a 65 6e 63 3a 61 65 73 63 62 63 |y.k8s:enc:aescbc| 00000040 3a 76 31 3a 6b 65 79 31 3a a7 bd ef ee cc 8b 98 |:v1:key1:.......| 00000050 ed fc 27 69 a1 48 9e fb 82 29 38 10 e2 fb c6 a2 |..'i.H...)8.....| 00000060 12 18 c8 62 8e 15 22 1e 5a 90 87 66 26 7a 9a 5b |...b..".Z..f&z.[| 00000070 e6 05 c3 39 53 aa 14 74 01 66 bb 38 f7 8d 78 42 |...9S..t.f.8..xB| 00000080 b1 d7 34 21 63 62 c6 f8 34 33 6b 98 22 56 73 1a |..4!cb..43k."Vs.| 00000090 e1 16 14 2b 25 84 70 f7 7a 7b e5 91 ee b5 fc 87 |...+%.p.z{......| 000000a0 a9 69 f5 e8 86 85 0c 73 00 4f cc a2 aa e6 d3 cd |.i.....s.O......| 000000b0 f2 96 29 51 8c 80 2f 9f e5 eb db 3f a9 a5 1b 70 |..)Q../....?...p| 000000c0 16 1a c5 76 1e 7e c2 8f 1a 9f 99 12 7e 42 84 88 |...v.~......~B..| 000000d0 2f f5 5c dc a4 ff 0a 2b 54 fc 1f 2e 01 5f bb 92 |/.\....+T...._..| 000000e0 9c 0f 8e dc 59 77 8f 12 24 0a |....Yw..$.| 000000ea
次はdeployment。nginxでwebサーバを立てます。
$ ./scripts/k8s-the-hard-way/1310-deployments.sh
以下のように表示されればOK。
Every 2.0s: kubectl get pods -l run=nginx NAME READY STATUS RESTARTS AGE nginx-65899c769f-8qvdn 1/1 Running 0 1m
ポートフォワーディングでアプリにアクセスできることを確認します。ここはスクリプト内のコマンドを順次実行してみます。
$ POD_NAME=$(kubectl get pods -l run=nginx -o jsonpath="{.items[0].metadata.name}") $ kubectl port-forward $POD_NAME 8080:80 Forwarding from 127.0.0.1:8080 -> 80 Forwarding from [::1]:8080 -> 80
違うターミナルを開いて、HTTPでアクセスしてみる。レスポンスが帰ってくればOK。
$ curl --head http://127.0.0.1:8080 HTTP/1.1 200 OK Server: nginx/1.17.8 Date: Sun, 16 Feb 2020 14:00:43 GMT Content-Type: text/html Content-Length: 612 Last-Modified: Tue, 21 Jan 2020 13:36:08 GMT Connection: keep-alive ETag: "5e26fe48-264" Accept-Ranges: bytes
もとのターミナルはCtrl+Cで止めておく。
コンテナのログが取得できるか。POD_NAMEは前のを引き続き使う。
$ kubectl logs $POD_NAME
ちゃんと取れてます。
127.0.0.1 - - [16/Feb/2020:13:33:30 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.54.0" "-" 127.0.0.1 - - [16/Feb/2020:14:00:43 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.54.0" "-" 127.0.0.1 - - [16/Feb/2020:14:01:50 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.54.0" "-"
コンテナでコマンドを実行する。バージョンが取得できてますね。
$ kubectl exec -ti $POD_NAME -- nginx -v nginx version: nginx/1.17.8
serviceを使ってアプリケーションを公開します。nginxのdeploymentをNodePortで公開します。
$ kubectl expose deployment nginx --port 80 --type NodePort service/nginx exposed
nginxに割り当てられたNodePortを取得します。
$ NODE_PORT=$(kubectl get svc nginx --output=jsonpath='{range .spec.ports[0]}{.nodePort}') $ echo $NODE_PORT 32601
ノードのIPでアクセスしてみます。
$ curl -I http://10.240.0.20:${NODE_PORT} HTTP/1.1 200 OK Server: nginx/1.17.8 Date: Sun, 16 Feb 2020 14:08:31 GMT Content-Type: text/html Content-Length: 612 Last-Modified: Tue, 21 Jan 2020 13:36:08 GMT Connection: keep-alive ETag: "5e26fe48-264" Accept-Ranges: bytes
できてますね。ちなみにノードのIPを変更しても同じようにレスポンスが返ってきます。
$ curl -I http://10.240.0.21:${NODE_PORT} $ curl -I http://10.240.0.22:${NODE_PORT}
はい、これで終了です。
Cleaning Up
削除はvagrant destroyで削除します。
$ vagrant destroy
結構長かったし、まだよくわかってないことがたくさんありますが、いろんなコンポーネントで構成されていて、なんとなくでもイメージができたというのは収穫だったかな。
次はkubeadmを使っ他クラスタ構築をやってみようと思います。kubeadmでも面倒、というような印象がありますが、hard wayをやった後だと多分楽に感じるのではないかと。
参考にさせていただいたサイト
ありがとうございました。