kubernetes the hard way on PC (９．etcd)

#初めに
「[kubernetes the hard way](https://github.com/kelseyhightower/kubernetes-the-hard-way) を PC でやってみる」の９回目、「Bootstrapping the etcd Cluster」についてです。（ [目次](https://qiita.com/shimauma_Zzzzz/items/84ecf5c748ceafd05380#%E8%AA%AC%E6%98%8E%E8%A8%98%E4%BA%8B%E7%9B%AE%E6%AC%A1) ）
そろそろ疲れてきましたね。私も疲れてミスを多発させた回でした。

kubernetes では etcd という キーバリューストアに設定や情報を格納します
その etcd のセットアップに関して、 Control plane ノード3台を使った高可用構成の etcd を構成します。
(なお、 世界的にいろいろあって master ノードとは言わなくなったようです）

やることは以下の通りです。
- etcd の入手
- etcd サービスの構成
- etcd の検証

図で言うと赤い枠の部分になります（３台分）

![46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/310495/30d3db1e-ba07-ba63-7c92-1fe35f97d3a1.png)



また、末尾につまづいたエラーとその調査・対応方法も記載しておきます。

---
# etcd の入手

etcd の最新版は git hub 上で確認することができます。
以下の URL アクセス後、右側の列の Releases 欄に最新版が記載されています。
https://github.com/etcd-io/etcd

2020/07 時点では、 v3.4.10 でした。
Releases 欄の バージョン部分をクリックすると insatallation guide に遷移します。　そのページ下部にダウンロードできるリンクがあります。

私が実施した際は、 v3.4.7 で、 URL としては以下の通りでした。
https://github.com/etcd-io/etcd/releases/download/v3.4.7/etcd-v3.4.7-linux-amd64.tar.gz

これを wget でダウンロードします。

```shell-session
# wget -q --show-progress --https-only --timestamping \
> "https://github.com/etcd-io/etcd/releases/download/v3.4.7/etcd-v3.4.7-linux-amd64.tar.gz"
etcd-v3.4.7-linux-amd64.tar.gz                           100%[================================================================================================================================>]  16.51M  5.42MB/s    in 3.0s
# ls -l
total 16908
-rw-r--r-- 1 root root 17310840 Apr  1 20:07 etcd-v3.4.7-linux-amd64.tar.gz
```

ダウンロード完了後、 展開します。 

```shell-session
# tar zxf etcd-v3.4.7-linux-amd64.tar.gz
# ls
etcd-v3.4.7-linux-amd64  etcd-v3.4.7-linux-amd64.tar.gz

# cd etcd-v3.4.7-linux-amd64/
# ls
Documentation  etcd  etcdctl  README-etcdctl.md  README.md  READMEv2-etcdctl.md
```


**※※ここからは Control Plane ノード3台で実施です※※**

（もちろん、最初から Control Plane ノード 3台で実施してもよいです。）
3台で一度に実施する場合、 kubernetes the hard way の Prerequisites の
Running Commands in Parallel with tmux　で紹介されている tmux を使うと、一度に3台コントロールできます。
https://github.com/kelseyhightower/kubernetes-the-hard-way/blob/master/docs/01-prerequisites.md
なれると面白いです。

展開して得られた `etcd` を、 `/usr/local/bin` にコピーします。

```shell-session
# cp -p etcd* /usr/local/bin
```

#etcd サービスの構成

etcd を systemd 配下のサービスとして稼働させるための準備を行います。
関連ディレクトリを作成し、 etcd で利用する各種証明書類を配置します。
（具体的には CAの証明書、 kubernetes (API サーバー用の) 秘密鍵・証明書です）
証明書類は マスターノードの作業ユーザーのホームディレクトリに配置していたはずです。　（なければ持ってくる）

```shell-session
# mkdir -p /etc/etcd /var/lib/etcd
# ls
admin.kubeconfig  ca-key.pem  ca.pem  encryption-config.yaml  kube-controller-manager.kubeconfig  kubernetes-key.pem  kubernetes.pem  kube-scheduler.kubeconfig  service-account-key.pem  service-account.pem  snap

# cp ca.pem kubernetes-key.pem kubernetes.pem /etc/etcd
```

次に、 systemd 配下で etcd をサービスとして稼働させるための unit ファイルを作成します。

kubernetes the hard way では、 tmux を使って3台の control plane で同時に操作しています。
その際、コマンド出力を変数に入れることにより、各ノードで違う部分をうまくカバーするようにしています。
例）
`HOSTNAME=$( hostname )`
`echo $HOSTNAME`
これを3台の control plane で実施すればそれぞれのホスト名が変数に格納されます。

ここでは、　そこまでせず各ノード上で以下のファイルを作成します。
それを使って 3台のノードに一度でファイルを作成しているようでした。
（と言いつつ、一部べた書きの部分もあった）

ここでは、それは行わず、下で紹介するファイルを3ノード分作成しました。
（当然、ホスト名などの部分はノードごとに読み替える必要はあります）

下に記載している etcd.service ファイルでは、
kubernetes.pem,  kubernetes-key.pem ( APIサーバー用に作った秘密鍵と証明書）を利用しています。
kubernetes の世界では、 etcd にアクセスするのは API Server のみなので、上記 API サーバー用の 鍵と証明書
並びに CA の証明書が無ければ通信できませんね。　詳しくは 第6回を参照ください。
また、同様に etcd 間の通信も同じ証明書と鍵を使っています。
なお、サーバーの IP アドレス等忘れていると思いますので再掲します。
ポートも2種類使われていますね。ここはまぁよく見ておいた方が良いかもしれません。

|ノード|IPアドレス|
|:--|:--|
|k8smaster0|192.168.199.200|
|k8smaster1|192.168.199.201|
|k8smaster2|192.168.199.202|

下記の例は k8smaster0 のファイルの例です、他のノードではそれぞれ IP 部分などに修正が必要です。

```shell-session
# cat /etc/systemd/system/etcd.service

[Unit]
Description=etcd
Documentation=https://github.com/coreos

[Service]
Type=notify
ExecStart=/usr/local/bin/etcd \
  --name k8smaster0 \
  --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://192.168.199.200:2380 \
  --listen-peer-urls https://192.168.199.200:2380 \
  --listen-client-urls https://192.168.199.200:2379,https://127.0.0.1:2379 \
  --advertise-client-urls https://192.168.199.200:2379 \
  --initial-cluster-token etcd-cluster-0 \
  --initial-cluster k8smaster0=https://192.168.199.200:2380,k8smaster1=https://192.168.199.201:2380,k8smaster2=https://192.168.199.202:2380 \
  --initial-cluster-state new \
  --data-dir=/var/lib/etcd
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

```

さて、注意事項ですが、 kubernetes the hard way では上記の部分は "\" が2つ並んでいます。
上記の例では "\" は1個です。　なぜでしょう？
kubernetes the hard way では、  cat コマンドでテキストファイルに流し込んでいるため、
"\" を "\" でエスケープしているんですね。結果としてできたファイル内には "\" が1つになり、
コマンド途中での改行の意味での "\" になっています。
ここでは、 cat コマンドでファイルを作成していませんので、 "\" は初めから1個にしています。
それに気づかず漫然とテキストファイルにコピペすると次のステップでエラーが出ます。

systemd 配下のファイルを変更したときは、 daemon をリロードします。

```shell-session
# systemctl daemon-reload
```

リロード完了後、 unit-files を確認（badになっていない）します。
確認後、　自動起動設定を行います。

```shell-session
# systemctl list-unit-files | grep etcd
etcd.service                           disabled        enabled

# systemctl enable etcd
Created symlink /etc/systemd/system/multi-user.target.wants/etcd.service → /etc/systemd/system/etcd.service.

# systemctl list-unit-files | grep etcd
etcd.service                           enabled         enabled

```

問題なければ起動します。

```shell-session
# systemctl start etcd
root@k8smaster0:/# echo $?
0
```

起動後には、ログを確認しておきましょう。
少し長いですが、
heartbeat = 100ms,  election = 1000ms  とタイムアウト値が設定されており、
それを超えると失敗します。
（略４）の後に Leader election が開始されています。
（略５）の後に、36549770f2856ae8 の etcd が Leader に選出されていることがわかります。



```shell-session
May 09 04:49:25 k8smaster0 systemd[1]: Starting etcd...
（略１）
May 09 04:49:25 k8smaster0 etcd[2229]: setting maximum number of CPUs to 2, total number of available CPUs is 2
May 09 04:49:25 k8smaster0 etcd[2229]: peerTLS: cert = /etc/etcd/kubernetes.pem, key = /etc/etcd/kubernetes-key.pem, trusted-ca = /etc/etcd/ca.pem, client-cert-auth = true, crl-file =
May 09 04:49:25 k8smaster0 etcd[2229]: name = k8smaster0
May 09 04:49:25 k8smaster0 etcd[2229]: data dir = /var/lib/etcd
May 09 04:49:25 k8smaster0 etcd[2229]: member dir = /var/lib/etcd/member
May 09 04:49:25 k8smaster0 etcd[2229]: heartbeat = 100ms
May 09 04:49:25 k8smaster0 etcd[2229]: election = 1000ms
May 09 04:49:25 k8smaster0 etcd[2229]: snapshot count = 100000
May 09 04:49:25 k8smaster0 etcd[2229]: advertise client URLs = https://192.168.199.200:2379
（略２）
May 09 04:49:25 k8smaster0 etcd[2229]: starting peer 13f2e6747ca8e776...
May 09 04:49:25 k8smaster0 etcd[2229]: started HTTP pipelining with peer 13f2e6747ca8e776
May 09 04:49:25 k8smaster0 etcd[2229]: started streaming with peer 13f2e6747ca8e776 (writer)
May 09 04:49:25 k8smaster0 etcd[2229]: started streaming with peer 13f2e6747ca8e776 (writer)
May 09 04:49:25 k8smaster0 etcd[2229]: started peer 13f2e6747ca8e776
May 09 04:49:25 k8smaster0 etcd[2229]: added peer 13f2e6747ca8e776
（略３）
May 09 04:49:25 k8smaster0 etcd[2229]: starting server... [version: 3.4.7, cluster version: to_be_decided]
（略４）
May 09 04:49:25 k8smaster0 etcd[2229]: raft2020/05/09 04:49:25 INFO: 36549770f2856ae8 is starting a new election at term 1
May 09 04:49:25 k8smaster0 etcd[2229]: raft2020/05/09 04:49:25 INFO: 36549770f2856ae8 became candidate at term 2
（略５）
May 09 04:49:25 k8smaster0 etcd[2229]: raft2020/05/09 04:49:25 INFO: 36549770f2856ae8 has received 2 MsgVoteResp votes and 0 vote rejections
May 09 04:49:25 k8smaster0 etcd[2229]: raft2020/05/09 04:49:25 INFO: 36549770f2856ae8 became leader at term 2
May 09 04:49:25 k8smaster0 etcd[2229]: raft2020/05/09 04:49:25 INFO: raft.node: 36549770f2856ae8 elected leader 36549770f2856ae8 at term 2
May 09 04:49:25 k8smaster0 etcd[2229]: published {Name:k8smaster0 ClientURLs:[https://192.168.199.200:2379]} to cluster fddffde8fce80533

May 09 04:49:25 k8smaster0 etcd[2229]: ready to serve client requests
May 09 04:49:25 k8smaster0 systemd[1]: Started etcd.
May 09 04:49:25 k8smaster0 etcd[2229]: ready to serve client requests
May 09 04:49:25 k8smaster0 etcd[2229]: serving client requests on 192.168.199.200:2379
May 09 04:49:25 k8smaster0 etcd[2229]: serving client requests on 127.0.0.1:2379
May 09 04:49:25 k8smaster0 etcd[2229]: setting up the initial cluster version to 3.4
May 09 04:49:25 k8smaster0 etcd[2229]: set the initial cluster version to 3.4
May 09 04:49:25 k8smaster0 etcd[2229]: enabled capabilities for version 3.4

```

#etcd の検証

etcd が起動したら、 確認をしておきます。

```shell-session
# 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
13f2e6747ca8e776, started, k8smaster2, https://192.168.199.202:2380, https://192.168.199.202:2379, false
25ceda074dba0436, started, k8smaster1, https://192.168.199.201:2380, https://192.168.199.201:2379, false
36549770f2856ae8, started, k8smaster0, https://192.168.199.200:2380, https://192.168.199.200:2379, false
```

ETCDCTL_API=3 についてですが、 etcd には API v2 と v3 があり、2020/7 時点では主に v3 が使われているようなのですが
なぜかデフォルトでは v2 になるらしく、環境変数で v3 を指定します。
各サーバーが "started" であることが見て取れます。　
なお、リーダーは k8smaster0 です。　（ 前述のログと、36549770f2856ae8　から）

kubernetes the hard way に記載の上記の方法では出力にヘッダーがついていないので不親切です。
その場合は `-w table` オプションを付けましょう

```shell-session
# ETCDCTL_API=3 etcdctl -w table 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
+------------------+---------+------------+------------------------------+------------------------------+------------+
|        ID        | STATUS  |    NAME    |          PEER ADDRS          |         CLIENT ADDRS         | IS LEARNER |
+------------------+---------+------------+------------------------------+------------------------------+------------+
| 13f2e6747ca8e776 | started | k8smaster2 | https://192.168.199.202:2380 | https://192.168.199.202:2379 |      false |
| 25ceda074dba0436 | started | k8smaster1 | https://192.168.199.201:2380 | https://192.168.199.201:2379 |      false |
| 36549770f2856ae8 | started | k8smaster0 | https://192.168.199.200:2380 | https://192.168.199.200:2379 |      false |
+------------------+---------+------------+------------------------------+------------------------------+------------+
```

また、etcd のレスポンスが問題ないかも確認することが可能です。

```shell-session
# ETCDCTL_API=3 etcdctl check perf   --endpoints=https://127.0.0.1:2379    --cacert=/etc/etcd/ca.pem    --cert=/etc/etcd/kubernetes.pem    --key=/etc/etcd/kubernetes-key.pem
 60 / 60 Boooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo! 100.00% 1m0s
PASS: Throughput is 150 writes/s
PASS: Slowest request took 0.188862s
PASS: Stddev is 0.010128s
PASS
```

これで PASS しなければ問題です。



#エラー並びに調査・対応方法

##1. systemd 設定ファイルエラー

今回、 etcd 用のサービス設定ファイルを作成しましたが、
コピー＆ペーストでミスっているのに気づかず、エラーになりました。
（※以下は、 etcd.service ファイルを作成し、 `systemctl daemon-reload` した後です）

```shell-session
# systemctl enable etcd
Failed to enable unit: File etcd.service: Invalid argument

# systemctl is-enabled etcd
Failed to get unit file state for etcd.service: Invalid argument

# systemctl list-unit-files  | grep etcd
etcd.service                           bad
```

まぁこのような場合 etcd.service ファイルが間違っているに違いないのですが、
systemd 関連でエラーが出た場合は、 ログを確認しましょう

```shell-session
# journalctl -u etcd
May 03 01:29:06 k8smaster01 systemd[1]: /etc/systemd/system/etcd.service:7: Missing '='.
```

結局これは、 etcd.services を作るときに "\" の数を間違い、
適切に改行されていなかったために出たエラーでした。

`systemctl daemon-reload` 後は、  `systemctl list-unit-files | grep xxxx `  で
`bad` になっていないことを確認しましょう。

##2. etcd 起動時エラー
なぜか知りませんが、この回はやたらとつまらないミスが多くエラーを多発させました。。。

etcd 起動時にエラーになった、という話ですが、これもログを見ればすぐに解決するお話です。

```shell-session
# systemctl start etcd
Job for etcd.service failed because the control process exited with error code.
See "systemctl status etcd.service" and "journalctl -xe" for details.
#
```

```shell-session
# journalctl -xe
May 09 04:46:10 k8smaster0 etcd[1379]: Go Version: go1.12.17
May 09 04:46:10 k8smaster0 etcd[1379]: Go OS/Arch: linux/amd64
May 09 04:46:10 k8smaster0 etcd[1379]: setting maximum number of CPUs to 2, total number of available CPUs is 2
May 09 04:46:10 k8smaster0 etcd[1379]: peerTLS: cert = /etc/etcd/kubernetes.pem, key = /etc/etcd/kubernetes-key.pem, trusted-ca = /etc/etcd/ca.pem, client-cert-auth = true, crl-file =
May 09 04:46:10 k8smaster0 etcd[1379]: open /etc/etcd/kubernetes.pem: no such file or directory
May 09 04:46:10 k8smaster0 systemd[1]: etcd.service: Main process exited, code=exited, status=1/FAILURE
```
ログに no such file or directory と出ていますね。。。

##3.etcd connection refused

さて、厄介な問題でした。解決方法も反則的です（販促的です）。　解決方法といっていいのかすら怪しいです。

etcd 起動時のログに以下のような出力が記録されることがあります。

`journalctl -u etcd -f`

```shell-session
May 03 02:41:10 k8smaster01 etcd[2078]: raft2020/05/03 02:41:10 INFO: 25ceda074dba0436 is starting a new election at term 1
May 03 02:41:10 k8smaster01 etcd[2078]: raft2020/05/03 02:41:10 INFO: 25ceda074dba0436 became candidate at term 2
May 03 02:41:10 k8smaster01 etcd[2078]: raft2020/05/03 02:41:10 INFO: 25ceda074dba0436 received MsgVoteResp from 25ceda074dba0436 at term 2
May 03 02:41:10 k8smaster01 etcd[2078]: raft2020/05/03 02:41:10 INFO: 25ceda074dba0436 [logterm: 1, index: 3] sent MsgVote request to 13f2e6747ca8e776 at term 2
May 03 02:41:10 k8smaster01 etcd[2078]: raft2020/05/03 02:41:10 INFO: 25ceda074dba0436 [logterm: 1, index: 3] sent MsgVote request to 7b1c68dc0b42688a at term 2
May 03 02:41:11 k8smaster01 etcd[2078]: sync duration of 1.040853586s, expected less than 1s
May 03 02:41:12 k8smaster01 etcd[2078]: raft2020/05/03 02:41:12 INFO: 25ceda074dba0436 is starting a new election at term 2
May 03 02:41:12 k8smaster01 etcd[2078]: raft2020/05/03 02:41:12 INFO: 25ceda074dba0436 became candidate at term 3
May 03 02:41:12 k8smaster01 etcd[2078]: raft2020/05/03 02:41:12 INFO: 25ceda074dba0436 received MsgVoteResp from 25ceda074dba0436 at term 3
May 03 02:41:12 k8smaster01 etcd[2078]: raft2020/05/03 02:41:12 INFO: 25ceda074dba0436 [logterm: 1, index: 3] sent MsgVote request to 13f2e6747ca8e776 at term 3
May 03 02:41:12 k8smaster01 etcd[2078]: raft2020/05/03 02:41:12 INFO: 25ceda074dba0436 [logterm: 1, index: 3] sent MsgVote request to 7b1c68dc0b42688a at term 3
May 03 02:41:14 k8smaster01 etcd[2078]: health check for peer 13f2e6747ca8e776 could not connect: dial tcp 192.168.199.202:2380: connect: connection refused
May 03 02:41:14 k8smaster01 etcd[2078]: health check for peer 13f2e6747ca8e776 could not connect: dial tcp 192.168.199.202:2380: connect: connection refused
May 03 02:41:14 k8smaster01 etcd[2078]: health check for peer 7b1c68dc0b42688a could not connect: dial tcp 192.168.199.203:2380: connect: connection refused
May 03 02:41:14 k8smaster01 etcd[2078]: health check for peer 7b1c68dc0b42688a could not connect: dial tcp 192.168.199.203:2380: connect: connection refused
May 03 02:41:14 k8smaster01 etcd[2078]: raft2020/05/03 02:41:14 INFO: 25ceda074dba0436 is starting a new election at term 3
```

上記のようなログが延々繰り返される状態です。
ポイントとしては以下の通りです。
`sync duration of 1.040853586s, expected less than 1s `
とあり、election timeout の 1000ms を超えてしまっています。
そのため、Leader 選出がうまくいっていません。
さらに、 health chek については `Connection Refused` が出力されています。
Connection Refused は、簡単に言うと peer (etcd) への接続ができないということです。
NW 通信が異常な場合に発生します。ただし、ここでは Leader 選出が行われていないため、etcd が起動完了していないためだと思われます。（ここは確証が取れませんでした）

何度か繰り返すうちに、たまたまうまく動いたタイミングがあったので先に進んだのですが、
結局 kube-scheduler (次回記載予定) の Leader election も etcd の応答が遅く失敗する、ということで詰まりました。

さて、対応ですが、この時は timeout 値を変更する、という案は思いつきませんでした。（よって確認していません。。）
残念ながら詳細なログを消してしまったのですが、　以下の順で対応しました。

1. etcd を 3台構成ではなく 1台構成に変える　(etcd をいったん初期化する）
  ・ etcd 停止
  ・ /var/lib/etcd/member ディレクトリ削除 (削除しないと etcd が初期状態に戻りません。ここに状態を記録しているようです）
  ・ etcd.services ファイル修正　（ #2, #3 エントリーの削除）
  ・ etcd サービスの無効化（ #2, #3 のみ )
  ・ etcd 起動（#1 のみ）

2. I/O が遅いことが分かったので SSD を追加購入

I/Oが遅い、という判断ですが、 Control plane ノードの リソース使用状況（CPU, Memory, Disk I/O) を見ていると
I/O だけに負荷がかかっていることが確認できました。
（もう少し詳しく言うと、　CPU の状態を見たときに  usr, sys, idle, wait の CPU使用状況の割合でほぼ wait 100% になっていました）

そのため、 ノート PC の SATA の Disk では無理、と判断し、急遽 SSD を購入しました。
思っていたよりは安くなっていました（256GB で 5000円ぐらいだった気が…）

（※ SSD を入れるという物理手術をしていますので、実際には OS の入れ直しからすべてやり直しています）

---
今回はここまでとして、次回は Bootstrapping the Kubernetes Control Plane の部分を実施します。
いよいよ本格的になってきますね！


← [８．Encryption Config and Key](https://qiita.com/shimauma_Zzzzz/items/c51fe9ba789bb9dfa1d4)
↑ [目次](https://qiita.com/shimauma_Zzzzz/items/84ecf5c748ceafd05380#%E8%AA%AC%E6%98%8E%E8%A8%98%E4%BA%8B%E7%9B%AE%E6%AC%A1)
→ [１０．Control Plane](https://qiita.com/shimauma_Zzzzz/items/5822813313a7aae049a0)

----