taxin's notes

読書、勉強メモ etc.

Kubernetes the hard way (GCP版) #13 (スモークテスト)

Kubernetes the hard way (GCP版)の続きです。

前回の記事はこちら

taxintt.hatenablog.com

Chap13. Smake Test(スモークテスト)

Chap12まででクラスターに関連する設定を一通り行いました。
このチャプターではクラスターが正常に動作しているかを確認するために、一連のタスクを実施します。

一部の作業ログが欠落しているので、手順の内容をそのまま記載している部分もありますがご了承下さい。

github.com

データの暗号化

Kubernetesで利用可能なリソースの一種であるSecretの暗号化機能を確認します。

kubernetes.io

SecretはKubernetesクラスター内で機密情報を管理するためのリソースとして利用されます。
データはControl Plane上のetcdに保管されるので、セキュリティの観点ではetcdの暗号化が必須になります。

qiita.com

最初に、機密情報のKey-Valueのペアを--from-literalで渡して検証用のSecretを作成します。
渡した値は自動的にBase64でencodeされます。

~/w/k/h/04 ❯❯❯ kubectl create secret generic kubernetes-the-hard-way \
  --from-literal="mykey=mydata"
secret/kubernetes-the-hard-way created
~/w/k/h/04 ❯❯❯ k get secrets
NAME                      TYPE                                  DATA   AGE
default-token-bn72d       kubernetes.io/service-account-token   3      178m
kubernetes-the-hard-way   Opaque                                1      14s


Secretの作成後に、Control Planeにログインしてetcd内のデータを確認します。
作成したSecret名を用いてディレクトリ(/registry/secrets/default/kubernetes-the-hard-way)を指定して、Key, Valueを取得します。

github.com

さらに取得したデータをhexdumpコマンドを利用して16進数とASCII文字でダンプします。
etcd上のデータにはk8s:enc:aescbc:v1:key1が付与されており、aescbcプロバイダがキー(key1)のデータを暗号化するために使用されたことを示しています。

kubernetes.io

~/w/k/h/04 ❯❯❯ gcloud compute ssh controller-0 \
  --command "sudo ETCDCTL_API=3 etcdctl get \
  --endpoints=https://127.0.0.1:2379 \
  --cacert=/etc/etcd/ca.pem \
  --cert=/etc/etcd/kubernetes.pem \
  --key=/etc/etcd/kubernetes-key.pem\
  /registry/secrets/default/kubernetes-the-hard-way | hexdump -C"
Enter passphrase for key '/Users/nishikawatakushi/.ssh/google_compute_engine':
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 4e ed 61 e8 69 49 fa  |:v1:key1:N.a.iI.|
00000050  19 c2 58 32 f3 7b 5b 7b  f6 2b 67 0e af de a7 0c  |..X2.{[{.+g.....|
00000060  d6 cf 48 b2 e2 7a 2d 44  9d 19 f8 06 8b e8 3d 74  |..H..z-D......=t|
00000070  89 eb 0e ba d4 ec b8 78  0b 84 b0 01 12 83 18 72  |.......x.......r|
00000080  c8 1e a6 25 a7 ca aa c5  a8 3a 0a f4 59 fa 2d 1d  |...%.....:..Y.-.|
00000090  03 54 5b 08 62 c6 5e b1  da 02 1b a6 ab 8f d6 c3  |.T[.b.^.........|
000000a0  ea c6 69 56 53 93 68 b4  29 93 df 21 79 36 1d dd  |..iVS.h.)..!y6..|
000000b0  d3 05 fa fd c5 40 d2 d5  4b e8 c8 da 48 60 3e 6b  |.....@..K...H`>k|
000000c0  29 33 6b 4a 9f f3 1b 8a  02 da 00 45 cd 93 98 ce  |)3kJ.......E....|
000000d0  ac cc d7 a4 a6 93 e0 ae  1d bc 9c 1b 7c e3 c0 ef  |............|...|
000000e0  c9 b6 4e 7a 6f 46 41 12  0e 0a                    |..NzoFA...|
000000ea

Deploymentの作成

次にWorkloadsリソースの1種であるDeploymentを作成します。
kubectl createコマンドを利用することで、マニュフェストファイルを事前に用意することなく作成することが可能です。

$ kubectl create deployment nginx --image=nginx

$ kubectl get pods -l app=nginx
NAME                     READY   STATUS    RESTARTS   AGE
nginx-554b9c67f9-vt5rn   1/1     Running   0          10s

ポートフォワーディング

作成したDeployment(pod)に対して、ポートフォワーディングを行いクラスタ内のコンテナへアクセスします。
今回はnginxのコンテナを作成しているので、コンテナの80番ポートにアクセスします。

~/w/k/h/04 ❯❯❯ POD_NAME=$(kubectl get pods -l app=nginx -o jsonpath="{.items[0].metadata.name}")
~/w/k/h/04 ❯❯❯ kubectl port-forward $POD_NAME 8080:80
Forwarding from 127.0.0.1:8080 -> 80
Forwarding from [::1]:8080 -> 80

~/w/k/h/04 ❯❯❯ kubectl port-forward $POD_NAME 8080:80                                                                                                                     
Forwarding from 127.0.0.1:8080 -> 80
Forwarding from [::1]:8080 -> 80
Handling connection for 8080

~~~~~~~

~ ❯❯❯ curl --head http://127.0.0.1:8080
HTTP/1.1 200 OK
Server: nginx/1.17.8
Date: Sat, 29 Feb 2020 07:28:01 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


ログ出力

作成したPodに対して、標準出力からPodのログをダンプします。
(この部分は作業ログが無かったため、手順の内容をそのまま記載しています。)

kubernetes.io

手順の中では標準出力の内容を直接取得していますが、実際の運用では標準出力・標準エラー出力をログファイルに書き出してロギング用のAgentを用いて転送したり、sidecarを用いたり様々なケースが考えられます。
こういったロギングの方法については、 Kubernetesの公式ドキュメントにまとめられています。

kubernetes.io

$ kubectl logs $POD_NAME
127.0.0.1 - - [14/Sep/2019:21:10:11 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.52.1" "-"


コンテナ内でのコマンド実行

docker execコマンドと同様に、Kubernetesでも作成したコンテナ上でコマンドを実行することが可能です。
今回はnginxのバージョンを確認するコマンドを実行します。

$ kubectl exec -ti $POD_NAME -- nginx -v
nginx version: nginx/1.17.3


Serviceを用いたアプリケーションの実行

最後に、Serviceを作成してクラスタ外部から作成したコンテナにアクセスできるようにします。
kubectl exposeコマンドを用いて、事前に作成したDeploymentに紐付ける形でServiceを作成します。

kubernetes.io

$ kubectl expose deployment nginx --port 80 --type NodePort

$ NODE_PORT=$(kubectl get svc nginx \
  --output=jsonpath='{range .spec.ports[0]}{.nodePort}')


次に、払い出されたNodePortのポート番号(NODE_PORT)でアクセスできるようにGCPFirewall Ruleでアクセスできるように修正します。

$ gcloud compute firewall-rules create kubernetes-the-hard-way-allow-nginx-service \
  --allow=tcp:${NODE_PORT} \
  --network kubernetes-the-hard-way


最後にNode(GCE)自体のIPアドレスを取得して、curlコマンドを実行します。
正常に設定が行われていれば、ポートフォワーディングの際と同じようなレスポンスが返ってくるはずです。

$ EXTERNAL_IP=$(gcloud compute instances describe worker-0 \
  --format 'value(networkInterfaces[0].accessConfigs[0].natIP)')

$ curl -I http://${EXTERNAL_IP}:${NODE_PORT}
HTTP/1.1 200 OK
Server: nginx/1.17.3
Date: Sat, 14 Sep 2019 21:12:35 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 13 Aug 2019 08:50:00 GMT
Connection: keep-alive
ETag: "5d5279b8-264"
Accept-Ranges: bytes


今回は、Chap13の内容をまとめました。

これにて、Kubernetes the hard wayのブログシリーズは終了です。
(Chap14は作成したリソース類の削除なので、ブログ記事の作成は行いません。)
機会があれば、AWS, Azure版など他のクラウドでのHard wayや手順を一部変更した改造版などを実施してみようと思います。

Kubernetes the hard way (GCP版) #12 (DNSクラスターアドオンのデプロイ)

Kubernetes the hard way (GCP版)の続きです。

前回の記事はこちら

taxintt.hatenablog.com

Chap12. Deploying the DNS Cluster Add-on(DNSクラスターアドオンのデプロイ)

このチャプターでは、サービスディスカバリの役割を果たすクラスターアドオン(CoreDNS)のデプロイを行います。

CoreDNSに関しては、公式ドキュメントにおいてもサービスディスカバリ(=Service Discovery)の一種として紹介されています。

kubernetes.io

(+α)Service Discoveryとは?

その名の通り、特定の条件に該当するサービスを発見するための機能のことを指します。
ただし、ここで記載しているサービスはクラスタ内外のNW疎通を取るために利用されるServiceのことではなく、クラスタ上で動作しているサービスを指しています。

特定のServiceに紐づいているPodを列挙したり、Serviceの名前からServiceのEndpoint(IP)を返すことをService Discoveryと言います。
(ServiceのEndpointに関する処理では、特定のサービスを発見した上でEndpointなど必要な情報を取得しています。)

Service Discoveryに関しては、主に環境変数DNSの2つのモードで実現可能です。

kubernetes.io

DNS Cluster Add-on

今回は、CoreDNSを利用してDNSを用いたService Discoveryを行うためのアドオンを環境にデプロイします。

下記のコマンドを実行して、アドオンとして利用するCoreDNSをデプロイします。

~/w/k/h/04 ❯❯❯ kubectl apply -f https://storage.googleapis.com/kubernetes-the-hard-way/coredns.yaml
serviceaccount/coredns created
clusterrole.rbac.authorization.k8s.io/system:coredns created
clusterrolebinding.rbac.authorization.k8s.io/system:coredns created
configmap/coredns created
deployment.apps/coredns created
service/kube-dns created

~/w/k/h/04 ❯❯❯ kubectl get pods -l k8s-app=kube-dns -n kube-system
NAME                     READY   STATUS    RESTARTS   AGE
coredns-5fb99965-2f569   1/1     Running   0          8s
coredns-5fb99965-4fbr9   1/1     Running   0          8s


applyしたyamlファイル内の設定値は確認できていないですが、おそらく下記と類似したような内容が定義されていると考えられます。
github.com

検証

今回はbusyboxのコンテナをデプロイして、コンテナ内からnslookupコマンドを実行してkubernetesドメイン名からIPアドレスを取得します。
名前解決に成功すると、API Server用に作成されているService(ClusterIP)のクラスターIPが返ってくるはずです。

~/w/k/h/04 ❯❯❯ kubectl run --generator=run-pod/v1 busybox --image=busybox:1.28 --command -- sleep 3600
pod/busybox created
~/w/k/h/04 ❯❯❯ kubectl get pods -l run=busybox
NAME      READY   STATUS    RESTARTS   AGE
busybox   1/1     Running   0          5s
~/w/k/h/04 ❯❯❯ POD_NAME=$(kubectl get pods -l run=busybox -o jsonpath="{.items[0].metadata.name}")
~/w/k/h/04 ❯❯❯ echo $POD_NAME
busybox


名前解決を行った結果、API Server用のServiceのクラスターIPを取得することができました。
これにより、クラスタ内での名前解決によるService Discoveryが可能になります。

~/w/k/h/04 ❯❯❯ kubectl exec -ti $POD_NAME -- nslookup kubernetes
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


今回は、Chap12の内容をまとめました。

Kubernetes the hard way (GCP版) #11 (Pod用ネットワーク経路のプロビジョニング)

Kubernetes the hard way (GCP版)の続きです。

前回の記事はこちら

taxintt.hatenablog.com

Chap11. Provisioning Pod Network Routes(Pod用ネットワーク経路のプロビジョニング)

このチャプターでは、他のNodeにスケジュールされたPodと通信する際に必要なルーティングの設定を行います。
(ハンズオンの手順も少ないので、今回の記事も比較的短めです。)

ルーティングテーブルについて

最初の説明でも記載しましたが、他のNodeに配置されたPodと通信する際にはルーティングの設定が必要となります。
今回のハンズオンではGCPでネットワーク経路を定義する際に利用するRoutesを用いて設定を行います。

cloud.google.com

最初に、各VMIPアドレスメタデータに設定したCIDRブロックを確認します。

~/w/k/h/04 ❯❯❯ for instance in worker-0 worker-1 worker-2; do
  gcloud compute instances describe ${instance} \
    --format 'value[separator=" "](networkInterfaces[0].networkIP,metadata.items[0].value)'
done
10.240.0.20 10.200.0.0/24
10.240.0.21 10.200.1.0/24
10.240.0.22 10.200.2.0/24

ネットワーク経路について

先ほど確認したIPアドレスとCIDRブロックの情報を基に、ルーティング設定を行います。
設定の際には、下記の項目を指定して引数として渡します。

  • --destination-range: 宛先となるネットワーク(CIDRブロックで定義される)
  • --next-hop-address: 宛先ネットワークに到達するための転送先

この設定によって、--destination-rangeで指定したIPアドレスに対する通信は--next-hop-addressで指定したIPアドレスに転送されます。
これにより、他のNodeにスケジュールされたPodと通信することが可能になります。


~/w/k/h/04 ❯❯❯ for i in 0 1 2; do
  gcloud compute routes create kubernetes-route-10-200-${i}-0-24 \
    --network kubernetes-the-hard-way \
    --next-hop-address 10.240.0.2${i} \
    --destination-range 10.200.${i}.0/24
done
Created [https://www.googleapis.com/compute/v1/projects/k8s-hardway-20200229/global/routes/kubernetes-route-10-200-0-0-24].
NAME                            NETWORK                  DEST_RANGE     NEXT_HOP     PRIORITY
kubernetes-route-10-200-0-0-24  kubernetes-the-hard-way  10.200.0.0/24  10.240.0.20  1000
Created [https://www.googleapis.com/compute/v1/projects/k8s-hardway-20200229/global/routes/kubernetes-route-10-200-1-0-24].
NAME                            NETWORK                  DEST_RANGE     NEXT_HOP     PRIORITY
kubernetes-route-10-200-1-0-24  kubernetes-the-hard-way  10.200.1.0/24  10.240.0.21  1000
Created [https://www.googleapis.com/compute/v1/projects/k8s-hardway-20200229/global/routes/kubernetes-route-10-200-2-0-24].
NAME                            NETWORK                  DEST_RANGE     NEXT_HOP     PRIORITY
kubernetes-route-10-200-2-0-24  kubernetes-the-hard-way  10.200.2.0/24  10.240.0.22  1000


コマンド実行後に、作成したRoutesを確認します。

~/w/k/h/04 ❯❯❯ gcloud compute routes list --filter "network: kubernetes-the-hard-way"
NAME                            NETWORK                  DEST_RANGE     NEXT_HOP                  PRIORITY
default-route-84429476facc48a9  kubernetes-the-hard-way  0.0.0.0/0      default-internet-gateway  1000
default-route-a30ac7a6303348c0  kubernetes-the-hard-way  10.240.0.0/24  kubernetes-the-hard-way   1000
kubernetes-route-10-200-0-0-24  kubernetes-the-hard-way  10.200.0.0/24  10.240.0.20               1000
kubernetes-route-10-200-1-0-24  kubernetes-the-hard-way  10.200.1.0/24  10.240.0.21               1000
kubernetes-route-10-200-2-0-24  kubernetes-the-hard-way  10.200.2.0/24  10.240.0.22               1000

以上で、ルーティングの設定は完了となります。

Kuberneteのネットワークの内部仕様も理解したい方は、NodeにログインしてRouting TableやARP Tableなどを眺めて見るのも面白いと思います。
(自分の環境で検証はできていないですが、PodをDeployすると上記のTableに記載された内容が書き換えられるはずです。)

www.slideshare.net

medium.com

今回は、Chap11の内容をまとめました。

余談ですが、Calicoなどの他のCNI Pluginを用いることでハンズオン自体を改造している人もいるようです。
CNI Pluginに関しては、ネットワーク方式(L3 Native, Overlay(L2 over L3))やNetworkPolicyなど設定内容も異なるので、代表的なPluginを試すのもありかと思います。

qiita.com

Kubernetes the hard way (GCP版) #10 (リモートアクセス用のkubectl設定)

Kubernetes the hard way (GCP版)の続きです。

前回の記事はこちら taxintt.hatenablog.com

Chap10. Configuring kubectl for Remote Access(リモートアクセス用のkubectl設定)

このチャプターでは、ユーザーがリモートからKubernetesクラスターにアクセスする際に利用するkubectlコマンドの設定を行います。
(ハンズオンの手順も少ないので、今回の記事は比較的短めです。)

Kubernetesの設定ファイル(Adminユーザー用)

ユーザー用のクライアント証明書と鍵ファイルを指定してkubeconfigファイル(接続設定ファイル)を作成します。
kubeconfigファイルの作成時に実行しているコマンドの内容については、Chap5の内容を参考にして頂ければと思います。

taxintt.hatenablog.com

以前のチャプターで作成した証明書・鍵ファイルを用いてkubectl config set-clusterコマンドで通信先を--serverとして指定して、Adminユーザーとしての権限を付与しています。

~/w/k/h/04 ❯❯❯ >{
  KUBERNETES_PUBLIC_ADDRESS=$(gcloud compute addresses describe kubernetes-the-hard-way \
    --region $(gcloud config get-value compute/region) \
    --format 'value(address)')

  kubectl config set-cluster kubernetes-the-hard-way \
    --certificate-authority=ca.pem \
    --embed-certs=true \
    --server=https://${KUBERNETES_PUBLIC_ADDRESS}:6443

  kubectl config set-credentials admin \
    --client-certificate=admin.pem \
    --client-key=admin-key.pem

  kubectl config set-context kubernetes-the-hard-way \
    --cluster=kubernetes-the-hard-way \
    --user=admin

  kubectl config use-context kubernetes-the-hard-way
}
Cluster "kubernetes-the-hard-way" set.
User "admin" set.
Context "kubernetes-the-hard-way" created.
Switched to context "kubernetes-the-hard-way".

検証1

kubectlコマンドの設定が正常にできたかを確認するために、リモートからコマンドを実行します。 kubectl get componentstatusesコマンドを実行して、手順内の想定では下記のような表示になるはずです。

NAME                 STATUS    MESSAGE             ERROR
controller-manager   Healthy   ok
scheduler            Healthy   ok
etcd-1               Healthy   {"health":"true"}
etcd-2               Healthy   {"health":"true"}
etcd-0               Healthy   {"health":"true"}


しかし、実際に実行したところ下記のような結果になりました。

サーバ側のversion情報を取得できなかったので、Kind(Kubernetes in Docker)で別のクラスタを用意して同じコマンドを実行した際には正常に結果が取得できていました。
そのため、特定のversionのKubernetesクラスターに依存する問題かと考えられます。

github.com

~/w/k/h/04 ❯❯❯ kubectl get componentstatuses
NAME                 AGE
scheduler            <unknown>
controller-manager   <unknown>
etcd-2               <unknown>
etcd-1               <unknown>
etcd-0               <unknown>

~/w/k/h/04 ❯❯❯ kubectl version                                   
Client Version: version.Info{Major:"1", Minor:"17", GitVersion:"v1.17.2", GitCommit:"59603c6e503c87169aea6106f57b9f242f64df89", GitTreeState:"clean", BuildDate:"2020-01-18T23:30:10Z", GoVersion:"go1.13.5", Compiler:"gc", Platform:"darwin/amd64"}
Unable to connect to the server: dial tcp 35.233.237.51:6443: i/o timeout


調べてみたところ、KubernetesのServer versionによってはkubectl get componentstatusesコマンドの出力結果が正常に表示されないケースがあるようです。

github.com

今回は、Chap10の内容をまとめました。

Kubernetes the hard way (GCP版) #9 (Kubernetesワーカーノードのブートストラップ)

Kubernetes the hard way (GCP版)の続きです。

前回の記事はこちら

taxintt.hatenablog.com

Chap9. Bootstrapping the Kubernetes Worker Nodes(ワーカーノードのブートストラップ)

このチャプターでは、ユーザーが作成したコンテナが動作するWorker Nodeのブートストラップを行います。
Worker NodeのコンポーネントであるKubelet・kube-proxyとコンテナランタイムのインストール・設定を行い、サービスとして起動します。

これからの作業は、Worker Node用の3つのインスタンスで同じ作業を行います。

パッケージのインストール

必要なパッケージのインストールを行います。

...@worker-0:~$ {
    >   sudo apt-get update
    >   sudo apt-get -y install socat conntrack ipset
    > }
    Hit:1 http://us-west1.gce.archive.ubuntu.com/ubuntu bionic InRelease
    Get:2 http://us-west1.gce.archive.ubuntu.com/ubuntu bionic-updates InRelease [88.7 kB]
    ...


Swapの無効化

ユーザーが作成したコンテナが動作するWorker Nodeでは、スワップの無効化が推奨されています。

It is recommended that swap be disabled to ensure Kubernetes can provide proper resource allocation and quality of service.

そのため、各Nodeでスワップが有効化されていないかを確認します。
(スワップは無効になっていたことを確認したので、次の手順に進みます。)

...@worker-0:~$ sudo swapon --show


Worker Node用のバイナリーのダウンロードとインストール

Worker Node用のコンポーネントなどを含んだバイナリーをダウンロードします。

...@worker-0:~$ wget -q --show-progress --https-only --timestamping \
    >   https://github.com/kubernetes-sigs/cri-tools/releases/download/v1.15.0/crictl-v1.15.0-linux-amd64.tar.gz \
    >   https://github.com/opencontainers/runc/releases/download/v1.0.0-rc8/runc.amd64 \
    >   https://github.com/containernetworking/plugins/releases/download/v0.8.2/cni-plugins-linux-amd64-v0.8.2.tgz \
    >   https://github.com/containerd/containerd/releases/download/v1.2.9/containerd-1.2.9.linux-amd64.tar.gz \
    >   https://storage.googleapis.com/kubernetes-release/release/v1.15.3/bin/linux/amd64/kubectl \
    >   https://storage.googleapis.com/kubernetes-release/release/v1.15.3/bin/linux/amd64/kube-proxy \
    >   https://storage.googleapis.com/kubernetes-release/release/v1.15.3/bin/linux/amd64/kubelet
    crictl-v1.15.0-linux-amd64.tar.gz              100%[=================================================================================================>]  11.50M  18.5MB/s    in 0.6s
    runc.amd64                                     100%[=================================================================================================>]   9.79M  15.8MB/s    in 0.6s
    cni-plugins-linux-amd64-v0.8.2.tgz             100%[=================================================================================================>]  34.96M  29.8MB/s    in 1.2s
    containerd-1.2.9.linux-amd64.tar.gz            100%[=================================================================================================>]  32.96M  30.5MB/s    in 1.1s
    kubectl                                        100%[=================================================================================================>]  40.99M   170MB/s    in 0.2s
    kube-proxy                                     100%[=================================================================================================>]  35.27M   117MB/s    in 0.3s
    kubelet                                        100%[=================================================================================================>] 114.10M   202MB/s    in 0.6s


次に、バイナリーのインストール用のディレクトリを作成します。

...@worker-0:~$ sudo mkdir -p \
    >   /etc/cni/net.d \
    >   /opt/cni/bin \
    >   /var/lib/kubelet \
    >   /var/lib/kube-proxy \
    >   /var/lib/kubernetes \
    >   /var/run/kubernetes


ディレクトリを作成した上で、ダウンロードしたバイナリーをインストールします。

Kubernetesコンポーネントに加えてcrictlもダウンロードしていますが、これはCRI互換のコンテナランタイムに対して利用可能なCLIツールのようです。

github.com

...@worker-0:~$ {
    >   mkdir containerd
    >   tar -xvf crictl-v1.15.0-linux-amd64.tar.gz
    >   tar -xvf containerd-1.2.9.linux-amd64.tar.gz -C containerd
    >   sudo tar -xvf cni-plugins-linux-amd64-v0.8.2.tgz -C /opt/cni/bin/
    >   sudo mv runc.amd64 runc
    >   chmod +x crictl kubectl kube-proxy kubelet runc
    >   sudo mv crictl kubectl kube-proxy kubelet runc /usr/local/bin/
    >   sudo mv containerd/bin/* /bin/
    > }
    crictl
    bin/
    bin/containerd-shim-runc-v1
    bin/ctr
    bin/containerd
    bin/containerd-stress
    bin/containerd-shim
    ./
    ./flannel
    ./ptp
    ./host-local
    ./firewall
    ./portmap
    ./tuning
    ./vlan
    ./host-device
    ./bandwidth
    ./sbr
    ./static
    ./dhcp
    ./ipvlan
    ./macvlan
    ./loopback
    ./bridge


CNIネットワーキングの設定

次にWorker Nodeに関連するネットワーク周りの設定を行います。
最初に、Node(VM)で確保されているpodに割り当てるためのCIDRブロック(IPアドレスレンジ)を確認します。

...@worker-0:~$POD_CIDR=$(curl -s -H "Metadata-Flavor: Google" \
    >   http://metadata.google.internal/computeMetadata/v1/instance/attributes/pod-cidr)
...@worker-0:~$ echo $POD_CIDR
10.200.0.0/24


事前に定義しておいたCIDRブロック(IPアドレスレンジ)を基に、下記のネットワーク設定ファイルを作成します。

  • 10-bridge.conf: Bridgeのネットワークの設定
  • 99-loopback.conf: Loopbackネットワークの設定
...@worker-0:~$ cat <<EOF | sudo tee /etc/cni/net.d/10-bridge.conf
    > {
    >     "cniVersion": "0.3.1",
    >     "name": "bridge",
    >     "type": "bridge",
    >     "bridge": "cnio0",
    >     "isGateway": true,
    >     "ipMasq": true,
    >     "ipam": {
    >         "type": "host-local",
    >         "ranges": [
    >           [{"subnet": "${POD_CIDR}"}]
    >         ],
    >         "routes": [{"dst": "0.0.0.0/0"}]
    >     }
    > }
    > EOF
    {
        "cniVersion": "0.3.1",
        "name": "bridge",
        "type": "bridge",
        "bridge": "cnio0",
        "isGateway": true,
        "ipMasq": true,
        "ipam": {
            "type": "host-local",
            "ranges": [
              [{"subnet": "10.200.0.0/24"}]
            ],
            "routes": [{"dst": "0.0.0.0/0"}]
        }
    }

...@worker-0:~$ cat <<EOF | sudo tee /etc/cni/net.d/99-loopback.conf
    > {
    >     "cniVersion": "0.3.1",
    >     "name": "lo",
    >     "type": "loopback"
    > }
    > EOF
    {
        "cniVersion": "0.3.1",
        "name": "lo",
        "type": "loopback"
    }


10-bridge.confでは、IPAM (IPアドレス管理) の設定項目でranges(IPアドレスの範囲) に対して事前に定義した値 ($POD_CIDR) を設定しています。

containerdの設定

続いて、コンテナランタイムの設定を行います。
最初に、containerdの設定ファイル(config.toml)を作成します。

github.com

今回作成するクラスタでは、High-levelのランタイムとしてcontainerd・Low-levelのランタイムとしてruncを利用します。
そのため、containerdの設定ファイル内の項目(runtime_engine)ではruncを指定しています。

High-level / Low-levelのコンテナランタイムの概念は事前に頭に入れておけば、この部分の設定内容も混乱しないと思います。

medium.com

...@worker-0:~$ sudo mkdir -p /etc/containerd/
...@worker-0:~$ cat << EOF | sudo tee /etc/containerd/config.toml
    > [plugins]
    >   [plugins.cri.containerd]
    >     snapshotter = "overlayfs"
    >     [plugins.cri.containerd.default_runtime]
    >       runtime_type = "io.containerd.runtime.v1.linux"
    >       runtime_engine = "/usr/local/bin/runc"
    >       runtime_root = ""
    > EOF
    [plugins]
      [plugins.cri.containerd]
        snapshotter = "overlayfs"
        [plugins.cri.containerd.default_runtime]
          runtime_type = "io.containerd.runtime.v1.linux"
          runtime_engine = "/usr/local/bin/runc"
          runtime_root = ""


別のLow-levelであるkata-containerと連携する場合も、この項目で利用するLow-levelランタイムを指定しています。

github.com


次に、containerd.serviceのsystemdのユニットファイルを作成します。

...@worker-0:~$ cat <<EOF | sudo tee /etc/systemd/system/containerd.service
    > [Unit]
    > Description=containerd container runtime
    > Documentation=https://containerd.io
    > After=network.target
    >
    > [Service]
    > ExecStartPre=/sbin/modprobe overlay
    > ExecStart=/bin/containerd
    > Restart=always
    > RestartSec=5
    > Delegate=yes
    > KillMode=process
    > OOMScoreAdjust=-999
    > LimitNOFILE=1048576
    > LimitNPROC=infinity
    > LimitCORE=infinity
    >
    > [Install]
    > WantedBy=multi-user.target
    > EOF
    [Unit]
    Description=containerd container runtime
    Documentation=https://containerd.io
    After=network.target

    [Service]
    ExecStartPre=/sbin/modprobe overlay
    ExecStart=/bin/containerd
    Restart=always
    RestartSec=5
    Delegate=yes
    KillMode=process
    OOMScoreAdjust=-999
    LimitNOFILE=1048576
    LimitNPROC=infinity
    LimitCORE=infinity

    [Install]
    WantedBy=multi-user.target


これでcontainerdの設定が完了となります。

Kubeletの設定

次にkubeletの設定を行います。
最初にkubelet用の秘密鍵・証明書ファイル、kubeconfigファイルを/var/lib/kubelet/に移動します。

...@worker-0:~$ {
    >   sudo mv ${HOSTNAME}-key.pem ${HOSTNAME}.pem /var/lib/kubelet/
    >   sudo mv ${HOSTNAME}.kubeconfig /var/lib/kubelet/kubeconfig
    >   sudo mv ca.pem /var/lib/kubernetes/
    > }


次に、kubeletの設定ファイルを作成します。

...@worker-0:~$ cat <<EOF | sudo tee /var/lib/kubelet/kubelet-config.yaml
    > kind: KubeletConfiguration
    > apiVersion: kubelet.config.k8s.io/v1beta1
    > authentication:
    >   anonymous:
    >     enabled: false
    >   webhook:
    >     enabled: true
    >   x509:
    >     clientCAFile: "/var/lib/kubernetes/ca.pem"
    > authorization:
    >   mode: Webhook
    > clusterDomain: "cluster.local"
    > clusterDNS:
    >   - "10.32.0.10"
    > podCIDR: "${POD_CIDR}"
    > resolvConf: "/run/systemd/resolve/resolv.conf"
    > runtimeRequestTimeout: "15m"
    > tlsCertFile: "/var/lib/kubelet/${HOSTNAME}.pem"
    > tlsPrivateKeyFile: "/var/lib/kubelet/${HOSTNAME}-key.pem"
    > EOF
    kind: KubeletConfiguration
    apiVersion: kubelet.config.k8s.io/v1beta1
    authentication:
      anonymous:
        enabled: false
      webhook:
        enabled: true
      x509:
        clientCAFile: "/var/lib/kubernetes/ca.pem"
    authorization:
      mode: Webhook
    clusterDomain: "cluster.local"
    clusterDNS:
      - "10.32.0.10"
    podCIDR: "10.200.0.0/24"
    resolvConf: "/run/systemd/resolve/resolv.conf"
    runtimeRequestTimeout: "15m"
    tlsCertFile: "/var/lib/kubelet/worker-0.pem"
    tlsPrivateKeyFile: "/var/lib/kubelet/worker-0-key.pem"


設定ファイル内で確認すべき点としては下記の部分です。

1. Authentication Mode

Authentication ModeとしてWebhook(webhook-token-authentication) を指定します。
これにより、Kubeletはトークンを受け取った上でTokenReviewAPIをcallし、API Serverに対して認証処理をDeligate(委任)します。

kubernetes.io

また、Authentication Modeの一つであるanonymousを無効化しています。
この設定が有効になっていると、Kubeletに対するリクエストを (Authenticationのフェーズで) 全て許可して、Kubeletに対するAPI Requestをanonymousとして扱います。

基本的にはAPI Requestはユーザー(もしくはService Account)と紐づけられますが、この場合は下記のユーザーとグループに紐づけられます。

  • user: system:anonymous
  • group: system:unauthenticated

kubernetes.io

labs.f-secure.com


公式のドキュメントでも、anonymousの設定は非推奨となっています。

kubernetes.io

加えて、クライアント側のCA証明書ファイル(clientCAFile)としてユーザー側で作成したca.pemを指定しています。
これにより、kubelet側でX509クライアント証明書を用いた認証が可能になります。
(kubelet側では、webhookとX509クライアント証明書認証の両方の方式を利用することができると認識していますが、この部分はまだ整理できていないので調査中です。)


2. Authorization Mode

Authorization ModeとしてWebhookを指定します。
先ほどのAuthenticationのフェーズで認証された後にユーザー情報が付与されて、それを基に認可(Authorization)を行うという流れになっています。

... and user information is added to its context. This gives future steps (such as authorization and admission controllers) the ability to access the previously established identity of the user.

github.com

Authorization ModeとしてWebhookを指定することで、API Requestに対する認可処理をKubernetesAPI ServerにDeligate(委任)します。
KubeletはSubjectAccessReviewAPIをcallして、API Serverに対して認可処理を依頼します。

kubernetes.io

3. resolvConf

kubeletではWorker Nodeではなく外部のDNSゾルバを参照するために/run/systemd/resolve/resolv.confを指定しています。
これにより、KubeletはControl Plane内のDNSを参照することになります。

qiita.com

次に、kubelet.serviceのsystemdのユニットファイルを作成します。
ユニットファイルの中では、先ほど作成した設定ファイル (kubelet-config.yaml) を参照しています。

...@worker-0:~$ cat <<EOF | sudo tee /etc/systemd/system/kubelet.service
    > [Unit]
    > Description=Kubernetes Kubelet
    > Documentation=https://github.com/kubernetes/kubernetes
    > After=containerd.service
    > Requires=containerd.service
    >
    > [Service]
    > ExecStart=/usr/local/bin/kubelet \\
    >   --config=/var/lib/kubelet/kubelet-config.yaml \\
    >   --container-runtime=remote \\
    >   --container-runtime-endpoint=unix:///var/run/containerd/containerd.sock \\
    >   --image-pull-progress-deadline=2m \\
    >   --kubeconfig=/var/lib/kubelet/kubeconfig \\
    >   --network-plugin=cni \\
    >   --register-node=true \\
    >   --v=2
    > Restart=on-failure
    > RestartSec=5
    >
    > [Install]
    > WantedBy=multi-user.target
    > EOF
    [Unit]
    Description=Kubernetes Kubelet
    Documentation=https://github.com/kubernetes/kubernetes
    After=containerd.service
    Requires=containerd.service

    [Service]
    ExecStart=/usr/local/bin/kubelet \
      --config=/var/lib/kubelet/kubelet-config.yaml \
      --container-runtime=remote \
      --container-runtime-endpoint=unix:///var/run/containerd/containerd.sock \
      --image-pull-progress-deadline=2m \
      --kubeconfig=/var/lib/kubelet/kubeconfig \
      --network-plugin=cni \
      --register-node=true \
      --v=2
    Restart=on-failure
    RestartSec=5

    [Install]
    WantedBy=multi-user.target


kube-proxyの設定

最後に、kube-proxyの設定を行います。
kubeletと同様に、設定ファイルを作成した上でsystemdのユニットファイルを作成します。

...@worker-0:~$ sudo mv kube-proxy.kubeconfig /var/lib/kube-proxy/kubeconfig
...@worker-0:~$ cat <<EOF | sudo tee /var/lib/kube-proxy/kube-proxy-config.yaml
    > kind: KubeProxyConfiguration
    > apiVersion: kubeproxy.config.k8s.io/v1alpha1
    > clientConnection:
    >   kubeconfig: "/var/lib/kube-proxy/kubeconfig"
    > mode: "iptables"
    > clusterCIDR: "10.200.0.0/16"
    > EOF
    kind: KubeProxyConfiguration
    apiVersion: kubeproxy.config.k8s.io/v1alpha1
    clientConnection:
      kubeconfig: "/var/lib/kube-proxy/kubeconfig"
    mode: "iptables"
    clusterCIDR: "10.200.0.0/16"

...@worker-0:~$ cat <<EOF | sudo tee /etc/systemd/system/kube-proxy.service
    > [Unit]
    > Description=Kubernetes Kube Proxy
    > Documentation=https://github.com/kubernetes/kubernetes
    >
    > [Service]
    > ExecStart=/usr/local/bin/kube-proxy \\
    >   --config=/var/lib/kube-proxy/kube-proxy-config.yaml
    > Restart=on-failure
    > RestartSec=5
    >
    > [Install]
    > WantedBy=multi-user.target
    > EOF
    [Unit]
    Description=Kubernetes Kube Proxy
    Documentation=https://github.com/kubernetes/kubernetes

    [Service]
    ExecStart=/usr/local/bin/kube-proxy \
      --config=/var/lib/kube-proxy/kube-proxy-config.yaml
    Restart=on-failure
    RestartSec=5

    [Install]
    WantedBy=multi-user.target


設定ファイル内で確認すべき点としては下記の部分です。

1. kube-proxyのmode

kube-proxyはWorker Node内で動作して、クラスタ内部または外部からPodにアクセスできるようにネットワーク周りを管理しています。
下記のモードから任意の実装を選択することが可能です。

  • iptables(デフォルト)
  • ipvs
  • userspace

kubernetes.io

Worker Node用のサービス起動

コンポーネントの設定を行なった上で、作成したサービスを起動します。

    {
      sudo systemctl daemon-reload
      sudo systemctl enable containerd kubelet kube-proxy
      sudo systemctl start containerd kubelet kube-proxy
    }

検証1

一通り設定が完了したら、検証を行います。
Control Planeにsshでログインして、Adminユーザー用のkubeconfigを指定した上でkubectl get nodesコマンドを実行します。

~/w/k/h/04 ❯❯❯ gcloud compute ssh controller-0 \
      --command "kubectl get nodes --kubeconfig admin.kubeconfig"
    Enter passphrase for key '/Users/nishikawatakushi/.ssh/google_compute_engine':
    NAME       STATUS   ROLES    AGE   VERSION
    worker-0   Ready    <none>   22s   v1.15.3
    worker-1   Ready    <none>   21s   v1.15.3
    worker-2   Ready    <none>   19s   v1.15.3


(+α) swap(スワップ)とは

メモリ不足時に、物理メモリ上のデータをHDDなどに書き出すことをスワップと言います。
OSのメモリ管理機能として提供されており、Worker Nodeの実態であるVMでもメモリのスワップは発生し得ます。

e-words.jp

しかし、Kubernetesでは一部のpodがメモリを消費することで、他のpodのメモリがswapして速度が低下するため、swap自体を無効化することが推奨されています。

github.com

Kubernetesのv1.8から、swapが有効になっている場合に、kubeletが起動しないように設定されているようです。

qiita.com


今回は、Chap9の内容をまとめました。
Chap8でまとめたControl Planeの設定と関連する部分もあるので、各コンポーネント同士の設定を紐づけて理解しておく必要があると感じました。

Kubernetes the hard way (GCP版) #8 (Kubernetesコントロールプレーンのブートストラップ)

Kubernetes the hard way (GCP版)の続きです。

前回の記事はこちら

taxintt.hatenablog.com

Chap8. Bootstrapping the Kubernetes Control Plane(コントロールプレーンのブートストラップ)

このチャプターでは、Control Planeのブートストラップを行います。
Control PlaneのコンポーネントであるAPI Server・Controller Manager・Schedulerの設定を行い、サービスとして起動します。
Control PlaneはHA構成なので3つのインスタンスで同じ作業を行います。

Control Plane用のバイナリーのダウンロードとインストール

最初にconfigファイル用のディレクトリを作成します。
この後の手順で作成されるconfigファイルはこのディレクトリに配置されます。

...@controller-0:~$ sudo mkdir -p /etc/kubernetes/config


次にControl Planeを構成するコンポーネントのバイナリーをダウンロードします。
ダウンロードしたバイナリーは/usr/local/bin/に配置します。

...@controller-0:~$ wget -q --show-progress --https-only --timestamping \
>   "https://storage.googleapis.com/kubernetes-release/release/v1.15.3/bin/linux/amd64/kube-apiserver" \
>   "https://storage.googleapis.com/kubernetes-release/release/v1.15.3/bin/linux/amd64/kube-controller-manager" \
>   "https://storage.googleapis.com/kubernetes-release/release/v1.15.3/bin/linux/amd64/kube-scheduler" \
>   "https://storage.googleapis.com/kubernetes-release/release/v1.15.3/bin/linux/amd64/kubectl"
kube-apiserver                                 100%[=================================================================================================>] 156.90M   203MB/s    in 0.8s
kube-controller-manager                        100%[=================================================================================================>] 111.03M   173MB/s    in 0.6s
kube-scheduler                                 100%[=================================================================================================>]  36.99M  99.8MB/s    in 0.4s
kubectl                                        100%[=================================================================================================>]  40.99M   118MB/s    in 0.3s
...@controller-0:~$ {
>   chmod +x kube-apiserver kube-controller-manager kube-scheduler kubectl
>   sudo mv kube-apiserver kube-controller-manager kube-scheduler kubectl /usr/local/bin/
> }


Kubernetes API Serverの設定

ここからは各コンポーネントの設定を行っていきます。
API Serverの設定を行う前に/var/lib/kubernetes/ディレクトリを作成して、そのディレクトリに各種証明書ファイルとetcdの暗号化用の設定ファイル(encryption-config.yaml)を配置します。

...@controller-0:~$ {
>   sudo mkdir -p /var/lib/kubernetes/
>
>   sudo mv ca.pem ca-key.pem kubernetes-key.pem kubernetes.pem \
>     service-account-key.pem service-account.pem \
>     encryption-config.yaml /var/lib/kubernetes/
> }


インスタンスのPrivate IPアドレスINTERNAL_IPとして取得し、API Serverのサービスファイルを作成します。
サービスファイルでは、下記のような項目が設定されています。

  • --authorization-mode: 利用する認可モジュール
  • --enable-admission-plugins: 利用するAdmission Controlモジュール

クライアントからのAPIリクエストに対して、API Serverは認証→認可→Admission Controlの順にクライアントの認証認可とリクエストの妥当性検証を行います。
上記のチェックを行なった上で許可された処理が実行されます。


認可モジュールに関しては、下記の項目が設定されています。
認可モジュールは複数のモジュールを指定でき、先に指定したモジュール(この場合はNodeモジュール)が処理の順番として優先されます。

  • Node: kubeletからのリクエストを認可するためのモジュール
  • RBAC: Role/ClusterRoleなどのリソースを用いて定義されたRBACのルールを利用して認可を行うためのモジュール

試せてはいないのですが、外部サービスへの問い合わせ結果を基に権限認可を行うWebhookモジュールも存在します。

tech.jxpress.net


また、Admission Controlモジュールに関しては、下記の項目が設定されています。
リソースの定義内の項目 (e.g. Namespaceの指定, リソースの使用量)に関連したものが多く、不正リクエストの実行を防止します。

  • NamespaceLifecycle: 存在しないNamespaceを利用したリソース作成の制限やシステム側で予約しているNamespaceの削除防止などを行う
  • NodeRestriction: kubeletによるNodeとpodの操作を制限する
  • LimitRanger: Namespaceに設定したLimitRange (podごとに適用されるリソースの使用量制限) に準拠しているかを検証する
  • ServiceAccount: ServiceAccountが設定されていないpodにDefaultServiceAccountを設定するなどの処理の自動化を行う
  • DefaultStorageClass: StorageClassが未指定のPVCリソースの作成時にDefaultStorageClassを指定する
  • ResourceQuota: ResourceQuota (Namespaceに対して利用可能なリソース量) に準拠しているかを検証する

この部分に関しては、下記の記事や最近発売された「Docker/Kubernetes 開発・運用のためのセキュリティ実践ガイド」にも詳しく記載されています。

knowledge.sakura.ad.jp

Amazon CAPTCHA

...@controller-0:~$ INTERNAL_IP=$(curl -s -H "Metadata-Flavor: Google" \
>   http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/0/ip)
...@controller-0:~$ echo $INTERNAL_IP
10.240.0.10
...@controller-0:~$ cat <<EOF | sudo tee /etc/systemd/system/kube-apiserver.service
> [Unit]
> Description=Kubernetes API Server
> Documentation=https://github.com/kubernetes/kubernetes
>
> [Service]
> ExecStart=/usr/local/bin/kube-apiserver \\
>   --advertise-address=${INTERNAL_IP} \\
>   --allow-privileged=true \\
>   --apiserver-count=3 \\
>   --audit-log-maxage=30 \\
>   --audit-log-maxbackup=3 \\
>   --audit-log-maxsize=100 \\
>   --audit-log-path=/var/log/audit.log \\
>   --authorization-mode=Node,RBAC \\
>   --bind-address=0.0.0.0 \\
>   --client-ca-file=/var/lib/kubernetes/ca.pem \\
>   --enable-admission-plugins=NamespaceLifecycle,NodeRestriction,LimitRanger,ServiceAccount,DefaultStorageClass,ResourceQuota \\
>   --etcd-cafile=/var/lib/kubernetes/ca.pem \\
>   --etcd-certfile=/var/lib/kubernetes/kubernetes.pem \\
>   --etcd-keyfile=/var/lib/kubernetes/kubernetes-key.pem \\
>   --etcd-servers=https://10.240.0.10:2379,https://10.240.0.11:2379,https://10.240.0.12:2379 \\
>   --event-ttl=1h \\
>   --encryption-provider-config=/var/lib/kubernetes/encryption-config.yaml \\
>   --kubelet-certificate-authority=/var/lib/kubernetes/ca.pem \\
>   --kubelet-client-certificate=/var/lib/kubernetes/kubernetes.pem \\
>   --kubelet-client-key=/var/lib/kubernetes/kubernetes-key.pem \\
>   --kubelet-https=true \\
>   --runtime-config=api/all \\
>   --service-account-key-file=/var/lib/kubernetes/service-account.pem \\
>   --service-cluster-ip-range=10.32.0.0/24 \\
>   --service-node-port-range=30000-32767 \\
>   --tls-cert-file=/var/lib/kubernetes/kubernetes.pem \\
>   --tls-private-key-file=/var/lib/kubernetes/kubernetes-key.pem \\
>   --v=2
> Restart=on-failure
> RestartSec=5
>
> [Install]
> WantedBy=multi-user.target
> EOF
[Unit]
Description=Kubernetes API Server
Documentation=https://github.com/kubernetes/kubernetes

[Service]
ExecStart=/usr/local/bin/kube-apiserver \
  --advertise-address=10.240.0.10 \
   ...
  --tls-cert-file=/var/lib/kubernetes/kubernetes.pem \
  --tls-private-key-file=/var/lib/kubernetes/kubernetes-key.pem \
  --v=2
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target


Kubernetes Controller Managerの設定

次に、Controller Managerの設定を行います。
API Server同様に必要な設定ファイルを/var/lib/kubernetes/に配置して、Controller Managerのサービスファイルを作成します。

サービスファイルでは、下記のような項目が設定されています。

  • --leader-elect: HA構成の場合にリーダーを選出するかどうかを指定するフラグ
  • --service-account-private-key-file: ServiceAccountの秘密鍵ファイル
  • --use-service-account-credentials: Controller毎にServiceAccount用のCredentialsを利用するかどうかを指定するフラグ

Controller Managerの起動時に--use-service-account-credentialsを指定して起動することで、Managerで管理される各ControllerはServiceAccount用のCredentials(トークン)を利用します。
(Credentialsの作成に必要な鍵ファイルはChap4ですでに作成済みなので、上記のフラグで指定することで鍵ファイルを基にトークンが生成されると考えられます。)

taxintt.hatenablog.com

...@controller-0:~$ sudo mv kube-controller-manager.kubeconfig /var/lib/kubernetes/
...@controller-0:~$ cat <<EOF | sudo tee /etc/systemd/system/kube-controller-manager.service
> [Unit]
> Description=Kubernetes Controller Manager
> Documentation=https://github.com/kubernetes/kubernetes
>
> [Service]
> ExecStart=/usr/local/bin/kube-controller-manager \\
>   --address=0.0.0.0 \\
>   --cluster-cidr=10.200.0.0/16 \\
>   --cluster-name=kubernetes \\
>   --cluster-signing-cert-file=/var/lib/kubernetes/ca.pem \\
>   --cluster-signing-key-file=/var/lib/kubernetes/ca-key.pem \\
>   --kubeconfig=/var/lib/kubernetes/kube-controller-manager.kubeconfig \\
>   --leader-elect=true \\
>   --root-ca-file=/var/lib/kubernetes/ca.pem \\
>   --service-account-private-key-file=/var/lib/kubernetes/service-account-key.pem \\
>   --service-cluster-ip-range=10.32.0.0/24 \\
>   --use-service-account-credentials=true \\
>   --v=2
> Restart=on-failure
> RestartSec=5
>
> [Install]
> WantedBy=multi-user.target
> EOF
[Unit]
Description=Kubernetes Controller Manager
Documentation=https://github.com/kubernetes/kubernetes

[Service]
ExecStart=/usr/local/bin/kube-controller-manager \
  --address=0.0.0.0 \
  --cluster-cidr=10.200.0.0/16 \
  --cluster-name=kubernetes \
  --cluster-signing-cert-file=/var/lib/kubernetes/ca.pem \
  --cluster-signing-key-file=/var/lib/kubernetes/ca-key.pem \
  --kubeconfig=/var/lib/kubernetes/kube-controller-manager.kubeconfig \
  --leader-elect=true \
  --root-ca-file=/var/lib/kubernetes/ca.pem \
  --service-account-private-key-file=/var/lib/kubernetes/service-account-key.pem \
  --service-cluster-ip-range=10.32.0.0/24 \
  --use-service-account-credentials=true \
  --v=2
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target


Kubernetes Schedulerの設定

同様に、Schedulerの設定を行います。
ここでは、kube-scheduler.yamlというscheduler用の設定ファイルを作成した上でサービスファイルを作成します。

...@controller-0:~$ sudo mv kube-scheduler.kubeconfig /var/lib/kubernetes/
...@controller-0:~$ cat <<EOF | sudo tee /etc/kubernetes/config/kube-scheduler.yaml
> apiVersion: kubescheduler.config.k8s.io/v1alpha1
> kind: KubeSchedulerConfiguration
> clientConnection:
>   kubeconfig: "/var/lib/kubernetes/kube-scheduler.kubeconfig"
> leaderElection:
>   leaderElect: true
> EOF
apiVersion: kubescheduler.config.k8s.io/v1alpha1
kind: KubeSchedulerConfiguration
clientConnection:
  kubeconfig: "/var/lib/kubernetes/kube-scheduler.kubeconfig"
leaderElection:
  leaderElect: true
nishikawatakushi@controller-0:~$ ls
admin.kubeconfig  etcd-v3.4.0-linux-amd64  etcd-v3.4.0-linux-amd64.tar.gz

nishikawatakushi@controller-0:~$ cat <<EOF | sudo tee /etc/systemd/system/kube-scheduler.service
> [Unit]
> Description=Kubernetes Scheduler
> Documentation=https://github.com/kubernetes/kubernetes
>
> [Service]
> ExecStart=/usr/local/bin/kube-scheduler \\
>   --config=/etc/kubernetes/config/kube-scheduler.yaml \\
>   --v=2
> Restart=on-failure
> RestartSec=5
>
> [Install]
> WantedBy=multi-user.target
> EOF
[Unit]
Description=Kubernetes Scheduler
Documentation=https://github.com/kubernetes/kubernetes

[Service]
ExecStart=/usr/local/bin/kube-scheduler \
  --config=/etc/kubernetes/config/kube-scheduler.yaml \
  --v=2
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target


Control Plane用のサービス起動

サービスファイルの作成が完了した段階で、Control Plane用のサービス (kube-apiserver, kube-controller-manager, kube-scheduler) を起動します。
手順にはありませんが、systemctl status <service_name>でサービスの起動を確認したり、journalctl -u <service_name>でログを確認するのも面白いと思います。

qiita.com

...@controller-0:~$ {
>   sudo systemctl daemon-reload
>   sudo systemctl enable kube-apiserver kube-controller-manager kube-scheduler
>   sudo systemctl start kube-apiserver kube-controller-manager kube-scheduler
> }
Created symlink /etc/systemd/system/multi-user.target.wants/kube-apiserver.service → /etc/systemd/system/kube-apiserver.service.
Created symlink /etc/systemd/system/multi-user.target.wants/kube-controller-manager.service → /etc/systemd/system/kube-controller-manager.service.
Created symlink /etc/systemd/system/multi-user.target.wants/kube-scheduler.service → /etc/systemd/system/kube-scheduler.service.

...@controller-0:~$ systemctl status kube-apiserver
● kube-apiserver.service - Kubernetes API Server
   Loaded: loaded (/etc/systemd/system/kube-apiserver.service; enabled; vendor preset: enabled)
   Active: active (running) since Sat 2020-02-29 04:27:20 UTC; 16s ago
     Docs: https://github.com/kubernetes/kubernetes
 Main PID: 4205 (kube-apiserver)
    Tasks: 8 (limit: 4394)
   CGroup: /system.slice/kube-apiserver.service
           └─4205 /usr/local/bin/kube-apiserver --advertise-address=10.240.0.10 --allow-privileged=true --apiserver-count=3 --audit-log-maxage=30 --audit-log-maxbackup=3 --audit-log-max

Feb 29 04:27:32 controller-0 kube-apiserver[4205]: [+]poststarthook/start-kube-aggregator-informers ok
Feb 29 04:27:32 controller-0 kube-apiserver[4205]: [+]poststarthook/apiservice-registration-controller ok
Feb 29 04:27:32 controller-0 kube-apiserver[4205]: [+]poststarthook/apiservice-status-available-controller ok
Feb 29 04:27:32 controller-0 kube-apiserver[4205]: [+]poststarthook/apiservice-openapi-controller ok
Feb 29 04:27:32 controller-0 kube-apiserver[4205]: [+]poststarthook/kube-apiserver-autoregistration ok
Feb 29 04:27:32 controller-0 kube-apiserver[4205]: [+]autoregister-completion ok
Feb 29 04:27:32 controller-0 kube-apiserver[4205]: healthz check failed
Feb 29 04:27:32 controller-0 kube-apiserver[4205]: I0229 04:27:32.410648    4205 controller.go:606] quota admission added evaluator for: endpoints
Feb 29 04:27:32 controller-0 kube-apiserver[4205]: W0229 04:27:32.428474    4205 lease.go:223] Resetting endpoints for master service "kubernetes" to [10.240.0.10]


HTTPヘルスチェックの有効化

サービスを起動したら、各Control Plane用のインスタンスにHTTPエンドポイント用のnginxを導入します。
(この部分に関しては日本語訳のページを見た方が、手順の意図がわかりやすいかと思います。)

github.com

API Serverはサービスの起動時に、HTTPSのエンドポイントを用意します。
しかし、今回の構成では前段に存在するLBがTLS終端となってしまい、API ServerのendpointにはHTTPでの通信のみ可能となるという状況になります。

そのため、今回はnginxをproxyとして利用し、ヘルスチェックのリクエスト (HTTP) をAPI ServerのEndpoint (HTTPS) に中継します。
プロキシの設定では下記の項目を設定します。

  • proxy_pass: 転送先のパス
  • proxy_ssl_trusted_certificate: プロキシサーバの証明書検証のために利用されるCA証明書

nginxのconfファイル内でproxy_ssl_certificateのように利用するTLS証明書を指定しなくてもいいのか...?という疑問は個人的に残っています。
(これに関しては、後日追記予定)

...@controller-0:~$ cat > kubernetes.default.svc.cluster.local <<EOF
> server {
>   listen      80;
>   server_name kubernetes.default.svc.cluster.local;
>
>   location /healthz {
>      proxy_pass                    https://127.0.0.1:6443/healthz;
>      proxy_ssl_trusted_certificate /var/lib/kubernetes/ca.pem;
>   }
> }
> EOF
...@controller-0:~$ {
>   sudo mv kubernetes.default.svc.cluster.local \
>     /etc/nginx/sites-available/kubernetes.default.svc.cluster.local
>
>   sudo ln -s /etc/nginx/sites-available/kubernetes.default.svc.cluster.local /etc/nginx/sites-enabled/
> }
...@controller-0:~$ sudo systemctl restart nginx
...@controller-0:~$ sudo systemctl enable nginx
Synchronizing state of nginx.service with SysV service script with /lib/systemd/systemd-sysv-install.
Executing: /lib/systemd/systemd-sysv-install enable nginx


検証1

HTTPエンドポイントの設定が完了したら、Control Planeの検証を行います。
最初にインスタンス内でadminユーザー用のconfigファイルを利用して、Control Planeのコンポーネントのステータスチェックを行います。

...@controller-0:~$ kubectl get componentstatuses --kubeconfig admin.kubeconfig
NAME                 STATUS    MESSAGE             ERROR
scheduler            Healthy   ok
controller-manager   Healthy   ok
etcd-2               Healthy   {"health":"true"}
etcd-0               Healthy   {"health":"true"}
etcd-1               Healthy   {"health":"true"}


次に、ヘルスチェックのリクエスト(HTTP)をnginxが指定のエンドポイントに中継しているかを確認します。
curlAPI Serverに対してリクエストを投げる際には、ホストヘッダーを指定します。
(これによって、kubernetes.default.svc.cluster.local (= API Serverのサービス名?) に対するリクエストの結果が返却されます。)

gihyo.jp

ここで実行している/healthzのHTTPリクエストの実態は、Kubernetesで利用できるヘルスチェックの内の一つであるLiveness Probeです。
ヘルスチェックによってpodの生死を確認することが可能です。

別のヘルスチェックとしてpodがリクエストを受け入れられるかを確認するReadiness Probeというものも存在します。

www.ianlewis.org

...@controller-0:~$ curl -H "Host: kubernetes.default.svc.cluster.local" -i http://127.0.0.1/healthz
HTTP/1.1 200 OK
Server: nginx/1.14.0 (Ubuntu)
Date: Sat, 29 Feb 2020 04:45:44 GMT
Content-Type: text/plain; charset=utf-8
Content-Length: 2
Connection: keep-alive
X-Content-Type-Options: nosniff


RBACを使ったKubeletの認可

次に、API Serverに関連してRBACの設定を行います。
API Server (Control Plane) から Kubelet (Worker Node) に対してアクセスできるようにするために、ClusterRole / ClusterRoleBindingを作成します。

system:kube-apiserver-to-kubeletという名前のClusterRoleには、Kubeletに対する基本的な処理を実行するための権限を付与します。

...@controller-0:~$ cat <<EOF | kubectl apply --kubeconfig admin.kubeconfig -f -
> apiVersion: rbac.authorization.k8s.io/v1beta1
> kind: ClusterRole
> metadata:
>   annotations:
>     rbac.authorization.kubernetes.io/autoupdate: "true"
>   labels:
>     kubernetes.io/bootstrapping: rbac-defaults
>   name: system:kube-apiserver-to-kubelet
> rules:
>   - apiGroups:
>       - ""
>     resources:
>       - nodes/proxy
>       - nodes/stats
>       - nodes/log
>       - nodes/spec
>       - nodes/metrics
>     verbs:
>       - "*"
> EOF
clusterrole.rbac.authorization.k8s.io/system:kube-apiserver-to-kubelet created


作成したClusterRoleはkubernetesユーザーに紐づけられます。
これにより、API Serverは--kubelet-client-certificateで指定されたクライアント証明書を利用してkubeletに認証を行います。

...@controller-0:~$ cat <<EOF | kubectl apply --kubeconfig admin.kubeconfig -f -
> apiVersion: rbac.authorization.k8s.io/v1beta1
> kind: ClusterRoleBinding
> metadata:
>   name: system:kube-apiserver
>   namespace: ""
> roleRef:
>   apiGroup: rbac.authorization.k8s.io
>   kind: ClusterRole
>   name: system:kube-apiserver-to-kubelet
> subjects:
>   - apiGroup: rbac.authorization.k8s.io
>     kind: User
>     name: kubernetes
> EOF
clusterrolebinding.rbac.authorization.k8s.io/system:kube-apiserver created


ネットワークロードバランサーのプロビジョニング

最後にAPI Serverの前段に配置するLoad Balancerを作成します。
(個々のコマンドを説明していると長くなってしまうので設定項目などの詳細については、この記事の下部にまとめます。)

{
  KUBERNETES_PUBLIC_ADDRESS=$(gcloud compute addresses describe kubernetes-the-hard-way \
    --region $(gcloud config get-value compute/region) \
    --format 'value(address)')

  gcloud compute http-health-checks create kubernetes \
    --description "Kubernetes Health Check" \
    --host "kubernetes.default.svc.cluster.local" \
    --request-path "/healthz"

  gcloud compute firewall-rules create kubernetes-the-hard-way-allow-health-check \
    --network kubernetes-the-hard-way \
    --source-ranges 209.85.152.0/22,209.85.204.0/22,35.191.0.0/16 \
    --allow tcp

  gcloud compute target-pools create kubernetes-target-pool \
    --http-health-check kubernetes

  gcloud compute target-pools add-instances kubernetes-target-pool \
   --instances controller-0,controller-1,controller-2

  gcloud compute forwarding-rules create kubernetes-forwarding-rule \
    --address ${KUBERNETES_PUBLIC_ADDRESS} \
    --ports 6443 \
    --region $(gcloud config get-value compute/region) \
    --target-pool kubernetes-target-pool
}


検証2

Load Balancer (LB) の作成・設定が正常に行われたかを確認するために、LB経由でAPI Requestを実行します。
curlコマンドでCA証明書を指定した上でLoad Balancerが持つPublic IPアドレスクラスタのバージョンを確認するリクエストを投げます。

json形式のresponseが返ってきたら検証成功となります。

~/w/k/h/04 ❯❯❯ KUBERNETES_PUBLIC_ADDRESS=$(gcloud compute addresses describe kubernetes-the-hard-way \
  --region $(gcloud config get-value compute/region) \
  --format 'value(address)')

~/w/k/h/04 ❯❯❯ curl --cacert ca.pem https://${KUBERNETES_PUBLIC_ADDRESS}:6443/version
{
  "major": "1",
  "minor": "15",
  "gitVersion": "v1.15.3",
  "gitCommit": "2d3c76f9091b6bec110a5e63777c332469e0cba2",
  "gitTreeState": "clean",
  "buildDate": "2019-08-19T11:05:50Z",
  "goVersion": "go1.12.9",
  "compiler": "gc",
  "platform": "linux/amd64"
}%


(+α) GCPロードバランサーの設定

GCPの勉強がてら、手順の中で実施したロードバランサーの作成手順についても順を追って説明を記載していきます。

Hard wayの手順の中ではロードバランシングを構成する複数のコンポーネントを作成することでロードバランサーを構築します。
AWSのようにリソースとしてロードバランサーを作成する方法ではありません。

下記のコンポーネントの作成・設定によって、ロードバランシングを実現します。

  • バックエンドの設定
  • ホストとパスのルール
  • フロンドエンドの設定

上記のコンポーネントと作業手順を対応させると下記のようになります。

  • バックエンドの設定 → ターゲットプールの作成
  • ホストとパスのルール → フォワーディングルールの作成
  • フロンドエンドの設定 → Public IPアドレスの設定


ここからは実際のコマンドの説明をしていきます。 Public IPアドレスを取得した後に、HTTPのヘルスチェックルールを作成 (gcloud compute http-health-checks create) します。

  • --host: ヘルスチェックの対象となるホスト名 (kubernetes.default.svc.cluster.local)
  • --request-path: ヘルスチェックのパス (/healthz)
  gcloud compute http-health-checks create kubernetes \
    --description "Kubernetes Health Check" \
    --host "kubernetes.default.svc.cluster.local" \
    --request-path "/healthz"


次にFirewallルールを作成 (gcloud compute firewall-rules create) して、インスタンスが属するNetworkに対してLoad Balancerからのヘルスチェックを許可します。

ヘルスチェックのソースIPアドレスの範囲は下記に記載されています。

cloud.google.com

  gcloud compute firewall-rules create kubernetes-the-hard-way-allow-health-check \
    --network kubernetes-the-hard-way \
    --source-ranges 209.85.152.0/22, 209.85.204.0/22, 35.191.0.0/16 \
    --allow tcp


最後に、リクエスト転送先の管理単位であるターゲットプールを作成 (gcloud compute target-pools create / add-instances) します。
その上で、作成したプールにリクエストを転送するようにフォワーディングルールを作成します。

最終的には--address / --portsで指定したIPアドレス(ポート)に対するリクエストをターゲットプールに転送するような処理になります。

cloud.google.com

  KUBERNETES_PUBLIC_ADDRESS=$(gcloud compute addresses describe kubernetes-the-hard-way \
    --region $(gcloud config get-value compute/region) \
    --format 'value(address)')
  
  ~~~~~

  gcloud compute target-pools create kubernetes-target-pool \
    --http-health-check kubernetes

  gcloud compute target-pools add-instances kubernetes-target-pool \
   --instances controller-0,controller-1,controller-2

  gcloud compute forwarding-rules create kubernetes-forwarding-rule \
    --address ${KUBERNETES_PUBLIC_ADDRESS} \
    --ports 6443 \
    --region $(gcloud config get-value compute/region) \
    --target-pool kubernetes-target-pool


今回は、Chap8の内容をまとめました。
Control Planeのブートストラップの手順では証明書ファイルの指定やRBACの設定など各コンポーネント間が正常に通信して動作するために必要な設定がされています。

次回のWorker Nodeのブートストラップのチャプターと合わせて、クラスタの全体像をきちんと理解しようと思います。

Kubernetes the hard way (GCP版) #7 (etcdクラスターのブートストラップ)

Kubernetes the hard way (GCP版)の続きです。

前回の記事はこちら

taxintt.hatenablog.com

Chap7. Bootstrapping the etcd Cluster(etcdクラスターのブートストラップ)

このチャプターでは、Control Planeを構成するコンポーネントの一つであるetcdのクラスターのブートストラップを行います。

etcdバイナリのダウンロードとインストール

このチャプターでの作業はControl Planeの各ノードにsshでログインした上で行います。
(筆者はtmuxを使わずに、各ノードごとにターミナルのウィンドウを開いて作業をしました。)

最初にetcd のバイナリをダウンロードします。
(CLIツールであるetcdctlも含まれているので、別でインストールを行う必要はありません。)

wget -q --show-progress --https-only --timestamping \
  "https://github.com/etcd-io/etcd/releases/download/v3.4.0/etcd-v3.4.0-linux-amd64.tar.gz"


バイナリを解凍して、etcdの本体とetcdctl (CLIツール)を/usr/local/binに移動することでセットアップ前の準備は完了となります。

{
  tar -xvf etcd-v3.4.0-linux-amd64.tar.gz
  sudo mv etcd-v3.4.0-linux-amd64/etcd* /usr/local/bin/
}


etcdサーバーの設定

バイナリのダウンロードが完了したら、etcdサーバーの設定を行っていきます。

最初に、ノード内で必要なディレクトリ (/etc/etcd, /var/lib/etcd) を作成して、Chap4で作成したAPI Server用の証明書ファイル類を配置します。

taxintt.hatenablog.com

...@controller-0:~$ {
>   sudo mkdir -p /etc/etcd /var/lib/etcd
>   sudo cp ca.pem kubernetes-key.pem kubernetes.pem /etc/etcd/
> }


次に、Control Plane(インスタンス)のPrivate IPとホスト名を変数として取得します。

インスタンスメタデータに関しては、メタデータサーバーから取得できるようになっています。
インスタンスからメタデータサーバへのAPIアクセスに関しては、追加で認証処理を行うことなくメタデータを取得することが可能です。

cloud.google.com

...@controller-0:~$ INTERNAL_IP=$(curl -s -H "Metadata-Flavor: Google" \
>   http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/0/ip)
...@controller-0:~$ echo $INTERNAL_IP
10.240.0.10

...@controller-0:~$ hostname -s
controller-0
...@controller-0:~$ ETCD_NAME=$(hostname -s)


最後にetcdをサービスとして登録するために、etcdのサービスファイル (etcd.service) を作成します。
先ほど取得したノードのPrivate IPとホスト名はここで利用します。

他にも下記のような項目が設定されています。

  • --client-cert-auth: etcdへの通信時TLSクライアント認証を有効化する
  • --peer-client-cert-auth: ピア同士の通信時にTLSクライアント認証を有効化する
  • --cert-file / --key-file: etcdへの通信時に利用するTLS証明書ファイル・秘密鍵ファイル
  • --peer-cert-file / --peer-key-file: ピア同士の通信に利用されるTLS証明書ファイル・秘密鍵ファイル
  • --listen-client-urls (port: 2379) : etcdへの通信時に接続を受け付けるURL
  • --listen-peer-urls (port: 2380) : ピア同士の通信時に接続を受け付けるURL
  • --advertise-client-urls (port: 2379) : etcdへの通信時に外部に広告するURL
  • --initial-advertise-peer-urls (port: 2380) : ピア同士の通信時に全てのピアに広告するURL

上記の設定項目に関する説明は下記のリンクに記載されています。

github.com

...@controller-0:~$ cat <<EOF | sudo tee /etc/systemd/system/etcd.service
> [Unit]
> Description=etcd
> Documentation=https://github.com/coreos
>
> [Service]
> Type=notify
> ExecStart=/usr/local/bin/etcd \\
>   --name ${ETCD_NAME} \\
>   --cert-file=/etc/etcd/kubernetes.pem \\
>   --key-file=/etc/etcd/kubernetes-key.pem \\
>   --peer-cert-file=/etc/etcd/kubernetes.pem \\
>   --peer-key-file=/etc/etcd/kubernetes-key.pem \\
>   --trusted-ca-file=/etc/etcd/ca.pem \\
>   --peer-trusted-ca-file=/etc/etcd/ca.pem \\
>   --peer-client-cert-auth \\
>   --client-cert-auth \\
>   --initial-advertise-peer-urls https://${INTERNAL_IP}:2380 \\
>   --listen-peer-urls https://${INTERNAL_IP}:2380 \\
>   --listen-client-urls https://${INTERNAL_IP}:2379,https://127.0.0.1:2379 \\
>   --advertise-client-urls https://${INTERNAL_IP}:2379 \\
>   --initial-cluster-token etcd-cluster-0 \\
>   --initial-cluster controller-0=https://10.240.0.10:2380,controller-1=https://10.240.0.11:2380,controller-2=https://10.240.0.12:2380 \\
>   --initial-cluster-state new \\
>   --data-dir=/var/lib/etcd
> Restart=on-failure
> RestartSec=5
>
> [Install]
> WantedBy=multi-user.target
> EOF
[Unit]
Description=etcd
Documentation=https://github.com/coreos

[Service]
Type=notify
...
RestartSec=5

[Install]
WantedBy=multi-user.target

etcdサーバーの起動

サービスファイルの作成が完了したら、systemctlコマンドでetcdのサービスを起動します。
作成したサービスファイルをsystemdに反映させるために、daemon-reloadコマンドを実行してからサービスを起動します。

enakai00.hatenablog.com

{
  sudo systemctl daemon-reload
  sudo systemctl enable etcd
  sudo systemctl start etcd
}


注意点として、etcdのメンバーを単独で起動するとサービスの起動は失敗します。
サービスのステータス確認時にfailedの状態になった場合は、systemctl reset-failedコマンドで異常状態のリセットを行います。

dev.classmethod.jp

...@controller-0:~$ {
>   sudo systemctl daemon-reload
>   sudo systemctl enable etcd
>   sudo systemctl start etcd
> }
Created symlink /etc/systemd/system/multi-user.target.wants/etcd.service → /etc/systemd/system/etcd.service.

Job for etcd.service failed because a timeout was exceeded.
See "systemctl status etcd.service" and "journalctl -xe" for details.

...@controller-0:~$ journalctl -xe
Feb 29 03:58:45 controller-0 etcd[3667]: raft2020/02/29 03:58:45 INFO: f98dc20bce6225a0 became candidate at term 85
Feb 29 03:58:45 controller-0 etcd[3667]: raft2020/02/29 03:58:45 INFO: f98dc20bce6225a0 received MsgVoteResp from f98dc20bce6225a0 at term 85
Feb 29 03:58:45 controller-0 etcd[3667]: raft2020/02/29 03:58:45 INFO: f98dc20bce6225a0 [logterm: 1, index: 3] sent MsgVote request to 3a57933972cb5131 at term 85
Feb 29 03:58:45 controller-0 etcd[3667]: raft2020/02/29 03:58:45 INFO: f98dc20bce6225a0 [logterm: 1, index: 3] sent MsgVote request to ffed16798470cab5 at term 85
Feb 29 03:58:47 controller-0 etcd[3667]: raft2020/02/29 03:58:47 INFO: f98dc20bce6225a0 is starting a new election at term 85
Feb 29 03:58:47 controller-0 etcd[3667]: raft2020/02/29 03:58:47 INFO: f98dc20bce6225a0 became candidate at term 86
Feb 29 03:58:47 controller-0 etcd[3667]: raft2020/02/29 03:58:47 INFO: f98dc20bce6225a0 received MsgVoteResp from f98dc20bce6225a0 at term 86
Feb 29 03:58:47 controller-0 etcd[3667]: raft2020/02/29 03:58:47 INFO: f98dc20bce6225a0 [logterm: 1, index: 3] sent MsgVote request to 3a57933972cb5131 at term 86
Feb 29 03:58:47 controller-0 etcd[3667]: raft2020/02/29 03:58:47 INFO: f98dc20bce6225a0 [logterm: 1, index: 3] sent MsgVote request to ffed16798470cab5 at term 86
Feb 29 03:58:48 controller-0 etcd[3667]: raft2020/02/29 03:58:48 INFO: f98dc20bce6225a0 is starting a new election at term 86
Feb 29 03:58:48 controller-0 etcd[3667]: raft2020/02/29 03:58:48 INFO: f98dc20bce6225a0 became candidate at term 87
Feb 29 03:58:48 controller-0 etcd[3667]: raft2020/02/29 03:58:48 INFO: f98dc20bce6225a0 received MsgVoteResp from f98dc20bce6225a0 at term 87
Feb 29 03:58:48 controller-0 etcd[3667]: raft2020/02/29 03:58:48 INFO: f98dc20bce6225a0 [logterm: 1, index: 3] sent MsgVote request to 3a57933972cb5131 at term 87
Feb 29 03:58:48 controller-0 etcd[3667]: raft2020/02/29 03:58:48 INFO: f98dc20bce6225a0 [logterm: 1, index: 3] sent MsgVote request to ffed16798470cab5 at term 87

// 単独でサービスを起動しているため、他のメンバーへの通信に失敗する
Feb 29 03:58:49 controller-0 etcd[3667]: health check for peer 3a57933972cb5131 could not connect: dial tcp 10.240.0.12:2380: connect: connection refused
Feb 29 03:58:49 controller-0 etcd[3667]: health check for peer ffed16798470cab5 could not connect: dial tcp 10.240.0.11:2380: connect: connection refused
Feb 29 03:58:49 controller-0 etcd[3667]: health check for peer ffed16798470cab5 could not connect: dial tcp 10.240.0.11:2380: connect: connection refused
Feb 29 03:58:49 controller-0 etcd[3667]: health check for peer 3a57933972cb5131 could not connect: dial tcp 10.240.0.12:2380: connect: connection refused

検証

全てのControl Planeでetcdのセットアップを実行してから、正常にクラスターが構成されているか検証を行います。
検証では、etcdctlを利用してetcdクラスターのメンバー一覧を取得します。

導入したetcdのバージョンがv3.4.0以下の場合は、環境変数をセットする必要があります。(ETCDCTL_API=3)

github.com

...@controller-1:~$ sudo ETCDCTL_API=3 etcdctl member list \
>   --endpoints=https://127.0.0.1:2379 \
>   --cacert=/etc/etcd/ca.pem \
>   --cert=/etc/etcd/kubernetes.pem \
>   --key=/etc/etcd/kubernetes-key.pem
3a57933972cb5131, started, controller-2, https://10.240.0.12:2380, https://10.240.0.12:2379, false
f98dc20bce6225a0, started, controller-0, https://10.240.0.10:2380, https://10.240.0.10:2379, false
ffed16798470cab5, started, controller-1, https://10.240.0.11:2380, https://10.240.0.11:2379, false


正常にetcdクラスターのメンバー一覧を取得できたので、etcdクラスタのセットアップは完了となります。

今回は、Chap7の内容をまとめました。
etcdで利用されている分散合意アルゴリズム (Raft) については関連リンクだけ記載しておきます。

qiita.com

Raft(分散合意アルゴリズム)について · GitHub