kubernetes the hard way on PC (１５．Smoke Test)

#はじめに
「[kubernetes the hard way](https://github.com/kelseyhightower/kubernetes-the-hard-way) を PC でやってみる」の１５回目、「Smoke Test」についてです。 ([目次](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))


今回は Smoke Test です。　様々な機能の検証を行います。
なお、次の Cleaning Up はオンプレなのでやる気ありません（笑）
ですので、これで最後になります。
「その他」で何かやろうかと思いましたが、その候補の1つの iptables は Qiita にすでにあるようなのでやめておきます。
それ以外に何かあれば、 kubernetes the hard way on PC とは別にやろうかな、と思います。
ようやく終わりですね。。。

内容は以下の通りです。

- Data Encryption
- Deployments
    - Port Forwarding
    - Logs
    - Exec
- Service

# Data Encryption

Secret を作成し、それが etcd に格納されたことを確認します。

まず、Secret を作成します。

```shell-session
# kubectl create secret generic kubernetes-the-hard-way \
>   --from-literal="mykey=mydata"
secret/kubernetes-the-hard-way created

# kubectl get secret
NAME                      TYPE                                  DATA   AGE
default-token-vvcgl       kubernetes.io/service-account-token   3      28h
kubernetes-the-hard-way   Opaque                                1      21s
```

yaml で出してみても、secret の内容は直接は表示されないようですね。
kubernetes のドキュメントによると、盗み見をさけるために `get pod` や `describe pod` では直接的には表示されないようです。
下記の `mykey: bXlkYXRh` の部分ですね。

```shell-session
# kubectl get secret kubernetes-the-hard-way -o yaml
apiVersion: v1
data:
  mykey: bXlkYXRh
kind: Secret
metadata:
  creationTimestamp: "2020-05-10T10:05:30Z"
  name: kubernetes-the-hard-way
  namespace: default
  resourceVersion: "118358"
  selfLink: /api/v1/namespaces/default/secrets/kubernetes-the-hard-way
  uid: 178bbd1b-c6f6-4904-bfc9-1e70b2513b2a
type: Opaque
```

では、確認するためにはどうするのか、は kubernetes のドキュメントの 「[Secret のデコード](https://kubernetes.io/ja/docs/concepts/configuration/secret/#secret%E3%81%AE%E3%83%87%E3%82%B3%E3%83%BC%E3%83%89)」を参照してやってみます。

```shell-session
# echo bXlkYXRh | base64 --decode
mydata
```

表示されましたね。
重要なのは、　**Secret は別に暗号化されているわけではない** ということです。　
まぁちょっとした秘密、という感じですね。　その気になれば見られます。


次に、etcd に入っているかを確認します。

```shell-session
# 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
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 6f 0d 70 6f 7e 0d 76  |:v1:key1:o.po~.v|
00000050  e1 a7 12 b5 73 a9 78 88  6e ee 07 d6 6b 68 39 b0  |....s.x.n...kh9.|
00000060  b5 fa a0 cb 3e 1d 2c 01  d4 55 46 87 c5 c0 3c bc  |....>.,..UF...<.|
00000070  a8 b4 7e 87 40 77 9b 73  7b 99 8a 8a 3f ea 2b da  |..~.@w.s{...?.+.|
00000080  dd 8a 84 24 7c 60 78 3e  df 4e 69 2b 96 1b 65 38  |...$|`x>.Ni+..e8|
00000090  9f da 2a 0d 19 c1 06 76  e2 80 b5 0c 91 c3 cd 4e  |..*....v.......N|
000000a0  d3 06 db d6 48 a9 00 45  cc 7c cf ae 85 83 fc a5  |....H..E.|......|
000000b0  23 6d ea 89 aa 3b 70 ad  8f c7 5f 2e 38 89 a8 44  |#m...;p..._.8..D|
000000c0  3b bd 59 e9 0f 5b d6 e1  24 7e 48 d6 a2 ea e8 97  |;.Y..[..$~H.....|
000000d0  cd 54 aa 64 0e c4 98 8e  2e 4b ad 09 56 59 ab fd  |.T.d.....K..VY..|
000000e0  53 e4 94 b2 22 cc 87 9e  c2 0a                    |S...".....|
000000ea

```

etcd にも格納されていることがわかりました。
`k8s:enc:aescbc:v1:key1` という部分がありますが、
etcd の中では、 `aescbc` という暗号化プロバイダーが `key1` というキーで暗号化した、ということだそうです。

...残念ながらよくわかりませんね。 

「[第8回 Encryption Config](https://qiita.com/shimauma_Zzzzz/items/c51fe9ba789bb9dfa1d4) 」で etcd 用の Encryption Config を作成したのを覚えているでしょうか？
その時の yaml を再掲します。

```yaml
kind: EncryptionConfig
apiVersion: v1
resources:
  - resources:
      - secrets
    providers:
      - aescbc:
          keys:
            - name: key1
              secret: 77ditR6J（略）pKZL+AQj0=
      - identity: {}
```

きちんと `key1` という名前がついていますね！
こんなところにつながっているわけですね。　（つながっていないと困る）


#Deployments

検証用に nginx の deployment を作成します。

```shell-session
# kubectl create deployment nginx --image=nginx
deployment.apps/nginx created

# kubectl get pods -l app=nginx
NAME                     READY   STATUS    RESTARTS   AGE
nginx-86c57db685-tqhnw   1/1     Running   0          2m35s
```

##Port Forwarding

Port Forward を行って、外部からアクセスできるようにします。
kubernetes the hard way では、 80 -> 8080 に forward していますが、
ここでは 80 -> 8081 で実施しています。
（理由の詳細は後述)  
kubernetes 1.18 以降であれば、別に 8080 で問題ありませんのでどちらでもOKです。

なお、ここではターミナルを2枚使います。
1枚で Port Forward を行い、もう1枚で アクセスのテストを行います。

1枚目のターミナルで POD 名を取得します。　jsonpath が登場ですね。
Pod インスタンス配列の 0番目（つまり先頭）の  metadata.name を取得しています。

```shell-session
# POD_NAME=$(kubectl get pods -l app=nginx -o jsonpath="{.items[0].metadata.name}")

# echo $POD_NAME
nginx-86c57db685-tqhnw
```

同じターミナルで Port Forward します。（ここでは8081 を使います）

```shell-session
# kubectl port-forward $POD_NAME 8081:80
Forwarding from 127.0.0.1:8081 -> 80
```

ここで、 ターミナルにプロンプトが返ってこない状態になるはずです。

この状態で、もう1枚のターミナルからアクセスします。

まぁ、一応先に 8081 が　Listen されているかを確認します。念のためです。
私のケースではここまで来るまでにだいぶ回り道をしていますので…

```shell-session
# ss -nat | grep 8081
LISTEN  0       4096           127.0.0.1:8081            0.0.0.0:*
```

大丈夫ですね、　次に curl でアクセスします。

```shell-session
# curl --head http://127.0.0.1:8081
HTTP/1.1 200 OK
Server: nginx/1.17.10
Date: Sun, 10 May 2020 13:14:25 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 14 Apr 2020 14:19:26 GMT
Connection: keep-alive
ETag: "5e95c66e-264"
Accept-Ranges: bytes

```

無事に 200 OK が返ってきています。これで確認完了です。
この時、　Port Forward したターミナルに、下記のようなログが出ているかと思います。

```shell-session
Handling connection for 8081
```

確認できれば Port Forward のターミナルで Ctrl + c で Port Forward を終了します。

次に、nginx のログを確認しておきます。

```shell-session
# kubectl logs ${POD_NAME}
127.0.0.1 - - [10/May/2020:13:14:25 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.68.0" "-"
```

きちんとログが出力されていますね。

##Exec

コンテナ内でコマンドを実行させます。
これはよく使いますので自然と覚えているのではないかと思います。

```shell-session
# kubectl exec -ti $POD_NAME -- nginx -v
nginx version: nginx/1.17.10
```

コンテナ内で nginx -v が実行され、応答が返されています。


#Services

サービスですね。外部公開するためのものです。
ここでは NodePort を使って nginx のポート (80) を外部公開します。
オプション等を指定せず簡単に expose だけしているため、 ポートは自動的に 31919 が採用されています。

```shell-session
# kubectl expose deployment nginx --port 80 --type NodePort
service/nginx exposed

# kubectl get svc -o wide
NAME         TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)        AGE   SELECTOR
kubernetes   ClusterIP   10.32.0.1     <none>        443/TCP        31h   <none>
nginx        NodePort    10.32.0.225   <none>        80:31919/TCP   21s   app=nginx
```

一応、kubernetes the hard way に沿って、 json でも確認しておきます。
jsonpath も使えるようになった方が便利なんでしょうかね。

```shell-session
# kubectl get svc nginx -o jsonpath='{range .spec.ports[0]}{.nodePort}'
31919
```

ここがなぜ range となっているのかは、調べる気力が無くわかりません。。。
さてアクセスです。

```shell-session
# curl -I http://192.168.199.210:31919
HTTP/1.1 200 OK
Server: nginx/1.17.10
Date: Sun, 10 May 2020 13:20:08 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 14 Apr 2020 14:19:26 GMT
Connection: keep-alive
ETag: "5e95c66e-264"
Accept-Ranges: bytes

```

無事に応答が返っていますね。 IP アドレスの部分を 他のワーカーノードの IP にしても同じようにアクセスできますので
ためしてみてください。

これで作業は終了です！ お疲れ様でした！！

...が、　トラブル説明のコーナーがまだでしたね。。。

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

さて、今回のこの記事では、 Port Forward に 8081 番を利用しています。
kubernetes the hard way では 8080 を用いています。
なぜか、、、
エラーが出たからです！　　なお、 kubernetes 1.18 では出ませんのでもう無視していただいてもOKです。

```shell-session
# kubectl port-forward $POD_NAME 8080:80
Unable to listen on port 8080: Listeners failed to create with the following errors: [unable to create listener: Error listen tcp4 127.0.0.1:8080: bind: address already in use unable to create listener: Error listen tcp6 [::1]:8080: socket: address family not supported by protocol]
error: unable to listen on any of the requested ports: [{8080 80}]
```

もうほぼ終了、というところでのエラーはつらいですね。
`address already in use`  だそうです。誰だよ。

ポートの利用状況を調べます。

```shell-session
# lsof -n -i:8080
COMMAND   PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
kube-apis 688 root    5u  IPv4  33307      0t0  TCP 127.0.0.1:http-alt (LISTEN)
```

少し短く省略されて `kube-apis`  などと表示されていますが、 kube-apiserver ですね。 6443 じゃないのか！
一応、 PID も確認しておきます。

```shell-session
# ps -ef | grep kube-apiserver
root         688       1  2 01:55 ?        00:15:46 /usr/local/bin/kube-apiserver --advertise-address=192.168.199.200 --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://192.168.199.200:2379,https://192.168.199.201:2379,https://192.168.199.202: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
```

kube-apiserver の設定か何かに 8080 が入っているのか？ということで、確認します。
kubernetes the hard way では kube-apiserver は systemd 配下のサービスですので、
`systemctl` で確認します。

```shell-session
# systemctl cat kube-apiserver
# /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=192.168.199.200 \
  --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://192.168.199.200:2379,https://192.168.199.201:2379,https://192.168.199.202: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
```

8080 は記載がないです。　ただ、たまたまですが NodePort の Range が 30000-32767 であることがわかりました。
kube-apiserver が犯人であることは間違いなさそうですが、
一応 kube-apiserver を停止して 8080 ポートが解放されるか確認します。

```shell-session
# systemctl stop kube-apiserver
# echo $?
0

# lsof -n -i:8080
#

# systemctl start kube-apiserver
# lsof -n -i:8080
COMMAND    PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
kube-apis 2478 root    5u  IPv4 632782      0t0  TCP 127.0.0.1:http-alt (LISTEN)
```

kube-apiserver を止めると 8080 を Listen しなくなるので
犯人は kube-apiserver で確定です。

興味本位ではありますが、 kube-apiserver は 8080 ポートで何をしてみるのか
少しのぞいてみます。

```shell-session
# curl 127.0.0.1:8080

    "/apis/apiregistration.k8s.io",
    "/apis/apiregistration.k8s.io/v1",
... （略）
```

API がずらりとリストされました。　https ではなくてもアクセスできるんですね。（！）

API Server のログを確認してみます。

```shell-session
# journalctl -u kube-apiserver | grep 8080
May 09 05:46:22 k8smaster0 kube-apiserver[2625]: I0509 05:46:22.643275    2625 flags.go:33] FLAG: --insecure-port="8080"
May 09 05:46:22 k8smaster0 kube-apiserver[2625]: I0509 05:46:22.643736    2625 flags.go:33] FLAG: --port="8080"
May 09 05:46:27 k8smaster0 kube-apiserver[2625]: I0509 05:46:27.454254    2625 deprecated_insecure_serving.go:53] Serving insecurely on 127.0.0.1:8080
May 09 15:07:28 k8smaster0 kube-apiserver[2625]: I0509 15:07:28.261738    2625 secure_serving.go:167] Stopped listening on 127.0.0.1:8080
```

`--insecure-port="8080"` となっていますね。
念のため、API Server 再起動の際のログを丁寧に見てみると、確かに指定があります。

```shell-session
May 10 11:51:21 k8smaster0 systemd[1]: Stopped Kubernetes API Server.
May 10 11:51:36 k8smaster0 systemd[1]: Started Kubernetes API Server.
May 10 11:51:36 k8smaster0 kube-apiserver[2478]: I0510 11:51:36.195254    2478 flags.go:33] FLAG: --add-dir-header="false"
May 10 11:51:36 k8smaster0 kube-apiserver[2478]: I0510 11:51:36.195362    2478 flags.go:33] FLAG: --address="127.0.0.1"

...(略)...

May 10 11:51:36 k8smaster0 kube-apiserver[2478]: I0510 11:51:36.196314    2478 flags.go:33] FLAG: --insecure-bind-address="127.0.0.1"
May 10 11:51:36 k8smaster0 kube-apiserver[2478]: I0510 11:51:36.196325    2478 flags.go:33] FLAG: --insecure-port="8080"

...(略)...
```

ネットで調べてみると 「[12 Kubernetes configuration best practices](https://www.stackrox.com/post/2019/09/12-kubernetes-configuration-best-practices/)」というサイトで上記のオプションの記載がありました。

> --insecure-bind-address argument isn’t there. This configuration will prevent the API Server from binding to an insecure address, preventing non-authenticated and unencrypted access to your master node, which minimizes your risk of attackers potentially reading sensitive data in transit.

> --insecure-port argument shows as 0. This setting will prevent the API Server from serving on an insecure port, which would prevent unauthenticated and unencrypted access to the master node and minimize the risk of an attacker taking control of the cluster.

また、 下記の issue を見ると、 default は 8080 であることがわかりました。
[Enabling insecure port for API Server #49933](https://github.com/kubernetes/kubernetes/issues/49933)


> The default value of --insecure-port is 8080, which can be omitted. Also for insecure requests, no authentications and authorizions will be performed.

ちなみに、 kubernetes 1.18 では、 `--insecure-port=0` が指定され、無効化されているようです。
（ kubeadm でクラスター構成をした場合。 デフォルトが 0 に変わったのか、 kubeadm が 0 に設定したのかは不明。
マニュアル上からは `--insecure-port` の記載は消えています（deprecatedのため））

v1.18 では kube-apiserver のログにも出ています。

```shell-session
Flag --insecure-port has been deprecated, This flag will be removed in a future version.
```

ということで、　前にもバグに引っ掛かりましたが、 v1.16 でやったのがすべての間違いでした。。。


---
これで、kubernetes the hard way on PC は終了です。
ここまでお付き合いいただいたみなさま、ありがとうございました。
情報発信自体は続けていきたいので、またよろしくお願いいたします。

---

← [１４．CoreDNS](https://qiita.com/shimauma_Zzzzz/items/d63d9a9819b3183391ec)
↑ [目次](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)

----