kun432's blog

技術ネタ、読書記録、など。2015年から人生をやり直し中です。

Kubernetes The Hard Way をVagrantでやってみた②

前回からの続きです。

kun432.hatenablog.com

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に対するコマンド実行の際に使用されます。ちょっと細かいところは後で読むこととします。

qiita.com

以下が作成されます。

  • 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をやった後だと多分楽に感じるのではないかと。

参考にさせていただいたサイト

ありがとうございました。