コピペでつくるPhotonOSのkubernetesクラスタとIngressとDashboard（2021年10月）

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334782/8c3f1d58-d0cc-9fa0-1e94-2978ff452fef.png" width=80%>
やっとリソースのグラフ込みでダッシュボード表示できた。

# はじめに
kubernetesクラスタのうえで動くモノの勉強をするためにPhotonOSでkubernetesクラスタをつくってみました。
2021年10月アタマ時点でできるだけ最新のものを利用しています。サンプルとしてnginxのHTTPコンテナを独自ドメインの証明書で終端してHTTPS化するingressのテストもしています。

自身が再利用できるように構築手順を残す目的で書きます。
やった！できた！おわり！な内容なので解説は少なめですが、各コマンドの根拠はできるだけ書くようにしました。


検証環境では、ESXi(v7.0.0)のうえに、PhotonOS(v4 Rev1)をnode用のOSとして準備してkubernetesクラスタを構築しました。

|対象|バージョン|用途|URL|GitHub|
|:-|:-:|:-|:-|:-|
|kubernetes<br>kubeadm/kubectl/kubelet|v1.22.2|コンテナ管理|https://kubernetes.io/|https://github.com/kubernetes/kubernetes|
|canal|flannel : v0.14.0<br>calico : v3.20.1|コンテナネットワーク<br>ネットワークポリシー|https://docs.projectcalico.org/getting-started/kubernetes/flannel/flannel|~~https://github.com/projectcalico/canal~~|
|MetalLB|v0.10.2|ServiceとしてのLoadBalancer|https://metallb.universe.tf/|https://github.com/metallb/metallb|
|NGINX Ingress|v1.0.2|Ingressコントローラー|https://www.nginx.co.jp/products/products-nginx/kubernetes-ingress-controller/|https://github.com/kubernetes/ingress-nginx|
|Kubernetes Dashboard|v2.3.1|リソース情報の収集/WebUI|https://kubernetes.io/ja/docs/tasks/access-application-cluster/web-ui-dashboard/|https://github.com/kubernetes/dashboard|
canal は latest をインストールした2021年10月の時点でこのバージョンのイメージだった、というだけです。


# 作業内容
## PhotonOSのデプロイと初期設定
今回、ESXiを利用するにあたり、PhotonOSを利用してみました。

https://github.com/vmware/photon

**[PhotonOSのGitHub](https://github.com/vmware/photon)**のREADMEをdeeplで翻訳すると、

> Photon OSは、クラウド・ネイティブ・アプリケーション、クラウド・プラットフォーム、VMwareインフラストラクチャ向けに最適化された、オープンソースのLinuxコンテナ・ホストです。

だそうです。

インストール直後からDockerが稼働できる状態ですし、iptablesもLegacyモードなので最初のセットアップの手間があまりかかりませんでした。
今回は１台の control-plane node と ３台の worker node としてPhotonOSをESXiにデプロイして、クラスタ構築します。

すでにPhotonOS（でなくてもいいんです）が構築済みであれば**[kubernetesのインストール](#kubernetesのインストール)**までスキップします。


### PhotonOSをESXiにデプロイ
OVAファイルは**[GitHubのDownloadページ](https://github.com/vmware/photon/wiki/Downloading-Photon-OS)**にダウンロードリンクがあります。
**Photon OS 4.0 Rev1** というのが一番新しいので、このセクションの **OVA with virtual hardware v11** のOVAをダウンロードします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334782/f3538912-0052-b8ef-011b-872fd62bb3a1.png)

OVAをデプロイするので、これを選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334782/732c0c18-4130-f0c4-d68e-dfe49952fe02.png)

なんやかんやしてデプロイします。ゲストOSのリソースを変更するので、デプロイ後は電源OFFにしておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334782/c562a695-822c-d41a-2441-982fcb4c1867.png)

CPUは2コア、メモリは4GBにしておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334782/dd86c7ba-c9b9-2eb6-1937-b44d57fa69b6.png)

仮想マシンをパワーオンするとアドレスがDHCPで割り当てられるので、IPアドレスを確認します。
この例では`10.254.10.44`が割り当てられています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334782/5701246f-c762-1a62-26e4-e0500e7f07cf.png)



### デプロイしたPhotonOSの初期設定
割り当てられたIPアドレスにrootでSSHログインします。
初期アカウントはrootのみで、パスワード`changeme`でログインできます。
ログイン直後にパスワード変更を求められるので、適当なパスワードに変更しておきます。

```shell-session
$ ssh root@10.254.10.44
Password: <-- 初期パスワードは changeme
You are required to change your password immediately (administrator enforced).
Changing password for root.
Current password: <-- changeme
New password: <-- 新しいパスワードを入力
Retype new password: <-- 新しいパスワードを再度入力
 03:09:48 up 1 min,  0 users,  load average: 0.02, 0.01, 0.00
tdnf update info not available yet!
root@photon-machine [ ~ ]#
```


基本的な設定として以下を実施します。
ネットワーク設定は１台ごとに異なるため、修正して流し込みます。

* ネットワーク設定
    * アドレス設定をDHCPからスタティックに変更
    * ホスト名の変更
    * ネットワークサービスの再起動


```console:初期設定（コピペ）
#######################
# ノードごとの設定項目
#######################
Hostname=pos01
Domain=example.com
Address=10.254.10.121/24
Gateway=10.254.10.254
DNS=10.254.10.241

#######################
# ここから先は共通
#######################
# ネットワークの設定ファイルを新規作成
cat <<EOF > /etc/systemd/network/10-static-en.network
[Match]
Name=eth0
[Network]
Address=${Address}
Gateway=${Gateway}
Domains=${Domain}
DNS=${DNS}
EOF
chmod 644 /etc/systemd/network/10-static-en.network

# DHCPの設定ファイルを削除
rm -f /etc/systemd/network/99-dhcp-en.network

# ホスト名の設定
hostnamectl set-hostname ${Hostname}.${Domain}

# アドレス変更が反映されて疎通できなくなるので、裏側でセッションを切る
systemctl restart systemd-networkd & exit

```

これでネットワーク設定が更新されました。
ホスト名が引けない環境であれば、hostsファイルに対象ノード分のアドレスを追記しておきます。

```console:/etc/hostsに追記する（コピペ）
cat <<EOF >> /etc/hosts
10.254.10.121  pos01 pos01.example.com
10.254.10.122  pos02 pos02.example.com
10.254.10.123  pos03 pos03.example.com
10.254.10.124  pos04 pos04.example.com
EOF

```


ここまでの作業を必要なノードの台数分実施します。（今回は４台にしました）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334782/da3b0193-2c63-5860-0ef3-0c306b41165f.png)



## kubernetesのインストール
**このセクションの作業は全ノードで必要です**

「kubernetesのインストール」も「kubeadmのインストール」も、なんかしっくりこないですが、公式ドキュメントにはそう書いてあるので、それに倣います。

大体この公式ドキュメントに沿って作業していきます。

https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/

### システム設定

パッケージのアップグレードと必要なものをインストールします。
これ以外にも使うものがありますが、最初からインストール済みなので指定していません。

```console:パッケージの更新と導入（コピペ）
tdnf upgrade -y 
tdnf install -y tar conntrack ebtables ethtool socat

```


システムの設定をします。
このあたりは公式ドキュメントそのままです。

```console:システムのネットワーク設定（コピペ）
echo -e "export br_netfilter" > /etc/modules-load.d/k8s.conf
echo -e "net.bridge.bridge-nf-call-ip6tables = 1\nnet.ipv4.ip_forward = 1" > /etc/sysctl.d/k8s.conf
sysctl --system

```

kubernetesに必要なポートは以下のドキュメントを参照します。
英語と日本語のドキュメントで記載が異なりますが、よくわからないので両方採用します。
たぶんバージョンが違うんだと思います。
https://kubernetes.io/docs/reference/ports-and-protocols/
https://kubernetes.io/ja/docs/setup/production-environment/tools/kubeadm/install-kubeadm/

CNIとして利用するcanalに必要なポートは以下のドキュメントを参照します。
使わなさそうなものもありますが、記載されてるものはひとまず網羅しておきます。
https://docs.projectcalico.org/getting-started/kubernetes/requirements

オフィシャルじゃないですが、flannelのポート情報が記載されていたので、これも含めちゃいます。
https://github.com/coreos/coreos-kubernetes/blob/master/Documentation/kubernetes-networking.md

LoadBalancerとして利用するMetalLBに必要なポートは以下のドキュメントを参照します。
https://metallb.universe.tf/#requirements

必要なポートをiptablesで設定して設定を保存します。
control-plane nodeのみでしか使わないポートが多いですが面倒なので全ノードで共通にします。
それでも面倒なら、検証用だしノーガードでもいいんじゃなかろうか。

```console:iptablesで必要なポートを開放（コピペ）
# kubernetes用
iptables -A INPUT -p tcp --dport 443 -m state --state NEW -j ACCEPT
iptables -A INPUT -p tcp --dport 6443 -m state --state NEW -j ACCEPT
iptables -A INPUT -p tcp --dport 2379:2380 -m state --state NEW -j ACCEPT
iptables -A INPUT -p tcp --dport 10250:10259 -m state --state NEW -j ACCEPT
iptables -A INPUT -p tcp --dport 30000:32767 -m state --state NEW -j ACCEPT

# canal用
iptables -A INPUT -p tcp --dport 179 -m state --state NEW -j ACCEPT
iptables -A INPUT -p udp --dport 4789 -j ACCEPT
iptables -A INPUT -p tcp --dport 5473 -m state --state NEW -j ACCEPT
iptables -A INPUT -p udp --dport 8285 -j ACCEPT
iptables -A INPUT -p udp --dport 8472 -j ACCEPT

# flannel用（canalのドキュメントにはないが入れておく）
iptables -A INPUT -p tcp --dport 7946 -m state --state NEW -j ACCEPT
iptables -A INPUT -p udp --dport 7946 -j ACCEPT

# 再起動しても設定が残るようにする
iptables-save > /etc/systemd/scripts/ip4save

```

### kubeadm / kubectl / kubelet インストール
以下ドキュメントにしたがってkubeadmをインストールします。
日本語ドキュメントと英語ドキュメントでバイナリを保管するパスが違ってたり、指定されているバージョンがちょっと古かったりしていたので、ドキュメントの内容からちょっとだけ変更しています。

https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/


**CNI ( Container Network Interface )**
CNIプラグインの最新バージョンを以下で確認しておきます。
将来的にコマンドラインを使いまわせるように、最新バージョンを一時変数にいれてコマンド発行します。
https://github.com/containernetworking/plugins/releases/

```console:CNIのプラグイン（コピペ）
VERSION_CNI=v1.0.1
mkdir -p /opt/cni/bin
curl -sSL https://github.com/containernetworking/plugins/releases/download/${VERSION_CNI}/cni-plugins-linux-amd64-${VERSION_CNI}.tgz | tar -C /opt/cni/bin -xz

```


**CRI ( Container Runtime Interface )**
CLI群を提供するCRIの最新バージョンを以下で確認しておきます。
将来的にコマンドラインを使いまわせるように、最新バージョンを一時変数にいれてコマンド発行します。
https://github.com/kubernetes-sigs/cri-tools/releases

```console:CRICTL（コピペ）
VERSION_CRICTL=v1.22.0
curl -sSL https://github.com/kubernetes-sigs/cri-tools/releases/download/${VERSION_CRICTL}/crictl-${VERSION_CRICTL}-linux-amd64.tar.gz | tar -C /usr/local/bin -xz

```


**kubeadm / kubelet / kubectl**
kubernetesの最新バージョンを以下で確認しておきます。
将来的にコマンドラインを使いまわせるように、最新バージョンを一時変数にいれてコマンド発行します。
https://dl.k8s.io/release/stable.txt

```console:kubernetes（コピペ）
VERSION_K8S=v1.22.2
mkdir -p /usr/local/bin
cd /usr/local/bin
curl -sSL --remote-name-all https://storage.googleapis.com/kubernetes-release/release/${VERSION_K8S}/bin/linux/amd64/{kubeadm,kubelet,kubectl}
chmod +x {kubeadm,kubelet,kubectl}

```


### kubeletのサービス設定
kubeletをサービス化するためのファイルを取得し、サービス登録と開始の設定をします。
問答無用で最新バージョンを取得してしまいます。

```console:kubeletのサービス設定と開始（コピペ）
curl -sSL "https://raw.githubusercontent.com/kubernetes/release/master/cmd/kubepkg/templates/latest/deb/kubelet/lib/systemd/system/kubelet.service" | sed "s:/usr/bin:/usr/local/bin:g" > /etc/systemd/system/kubelet.service
mkdir -p /etc/systemd/system/kubelet.service.d
curl -sSL "https://raw.githubusercontent.com/kubernetes/release/master/cmd/kubepkg/templates/latest/deb/kubeadm/10-kubeadm.conf" | sed "s:/usr/bin:/usr/local/bin:g" > /etc/systemd/system/kubelet.service.d/10-kubeadm.conf
echo 'KUBELET_EXTRA_ARGS="--cgroup-driver=systemd --image-pull-progress-deadline=10m"' > /etc/default/kubelet
systemctl enable kubelet
systemctl start kubelet

```


### dockerのサービス設定
dockerのcgroup driverがデフォルトのcgroupfsだとうまく動作しなかったので、systemdに変更しています。
これでdockerのサービス登録と開始の設定をします。

```console:dockerのcgroup設定と開始（コピペ）
mkdir /etc/docker
echo '{ "exec-opts": ["native.cgroupdriver=systemd"]  }' > /etc/docker/daemon.json
systemctl enable docker
systemctl start docker

```


### 再起動
ここまで設定したら一度システムを再起動しておきます。

```console:再起動
shutdown -r now

```



## master node ( control-plane node ) の準備
**このセクションはcontrol-plane nodeでの作業です**

kubernetesクラスタのマスターノードに初期設定を行います。
ドキュメント的にはコントロールプレーンノードって言うっぽいです。


### kubeadm init によるクラスタの構築

podが利用するネットワークを明示的に指定します。今回は `10.244.0.0/16` を指定します。

```console:kubernetesクラスタの構築（コピペ）
# kubeadm initすると生成されるadmin.confをあらかじめ読み込むようにしておく
echo -e "export KUBECONFIG=/etc/kubernetes/admin.conf" > .bash_profile
source .bash_profile

# podが利用するネットワーク情報は一時変数に格納しておく
POD_NETWORK_CIDR=10.244.0.0/16

# join用のトークンはあとで表示させるので、最初は表示させなくてOK（表示してもよいですが）
kubeadm init --pod-network-cidr=${POD_NETWORK_CIDR} --skip-token-print

# control-plane用のpodが起動するまで待つ
kubectl wait -n kube-system pod  -l tier=control-plane  --for=condition=ready     --timeout=60s

```


kubeadmでの初期化が完了すると `kube-system` というネームスペースにポッドが生成されます。
corednsのポッド以外がRunningになるのを確認してから次のステップに進みます。

```shell-session:kubeadm_init後のpodの状態
root@pos01 [ ~ ]# kubectl get pods -A
NAMESPACE     NAME                                         READY   STATUS    RESTARTS   AGE
kube-system   coredns-78fcd69978-5pq2m                     0/1     Pending   0          87s
kube-system   coredns-78fcd69978-vcnfl                     0/1     Pending   0          87s
kube-system   etcd-pos01.example.com                       1/1     Running   0          102s
kube-system   kube-apiserver-pos01.example.com             1/1     Running   0          102s
kube-system   kube-controller-manager-pos01.example.com    1/1     Running   0          103s
kube-system   kube-proxy-fv684                             1/1     Running   0          87s
kube-system   kube-scheduler-pos01.example.com             1/1     Running   0          104s
```



### canal のインストール
コンテナ間のネットワークを動作させるためのCNI( Container Network Interface ) をインストールします。
以下のドキュメントに従って canal をインストールしました。

https://docs.projectcalico.org/getting-started/kubernetes/flannel/flannel

```console:canalのインストール（コピペ）
# podネットワークが 10.244.0.0/16 なのでそのままインストールできるはずなのですが、動作が怪しかったので修正しておきます
curl -sSL https://docs.projectcalico.org/manifests/canal.yaml | sed 's:#\ \(-\ name.\ CALICO_IPV4POOL_CIDR\):\1:g' |  sed 's:#\ \(\ \ value. \)\(\"192.168.0.0\/16\"\):\1${POD_NETWORK_CIDR}:g' | kubectl apply -f - ;

# calicoのcontrollerとcorednsのpodが起動するまで待つ
kubectl wait -n kube-system pod  -l k8s-app=calico-kube-controllers --for=condition=ready     --timeout=60s
kubectl wait -n kube-system pod  -l k8s-app=kube-dns --for=condition=ready     --timeout=60s

```


全部のポッドがRunningになるのを確認してから次のステップに進みます。

```shell-session:canalインストール後のpodの状態
root@pos01 [ ~ ]# kubectl get pod -A
NAMESPACE     NAME                                         READY   STATUS    RESTARTS   AGE
kube-system   calico-kube-controllers-74b8fbdb46-nhnxw     1/1     Running   0          61s
kube-system   canal-svdqf                                  2/2     Running   0          61s
kube-system   coredns-78fcd69978-5pq2m                     1/1     Running   0          30m
kube-system   coredns-78fcd69978-vcnfl                     1/1     Running   0          30m
kube-system   etcd-pos01.example.com                       1/1     Running   0          30m
kube-system   kube-apiserver-pos01.example.com             1/1     Running   0          30m
kube-system   kube-controller-manager-pos01.example.com    1/1     Running   0          30m
kube-system   kube-proxy-fv684                             1/1     Running   0          30m
kube-system   kube-scheduler-pos01.example.com             1/1     Running   0          30m
```


次は他ノードをこのクラスタにjoinする作業を実施します。



## worker node ( non control-plane node ) をクラスタにjoinする
じょいんじょいん

### control-plane nodeでトークン付きコマンドを取得
まず、control-plane nodeでトークン付きコマンドを取得します。
トークンには有効期限があるようです。（24時間？）

```shell-session:control-plane_nodeでjoin用のトークン付きコマンドを表示させる
root@pos01 [ ~ ]# kubeadm token create --print-join-command
kubeadm join 10.254.10.121:6443 --token qho156.cgetkgpr92ga1dlv \
         --discovery-token-ca-cert-hash sha256:dcc69c052df1acc5436c2e07009fd07a2c20d3dbeca3f0b7c74c224824da8075
```


### worker nodeからJOINする
次に、取得したトークン付きコマンドを各worker node上でコマンド発行します。

```shell-session:worker_nodeでjoin用のトークン付きコマンドを発行する
root@pos02 [ ~ ]# kubeadm join 10.254.10.121:6443 --token qho156.cgetkgpr92ga1dlv \
         --discovery-token-ca-cert-hash sha256:dcc69c052df1acc5436c2e07009fd07a2c20d3dbeca3f0b7c74c224824da8075
[preflight] Running pre-flight checks
( ... たくさんログがでる ... )
This node has joined the cluster:
* Certificate signing request was sent to apiserver and a response was received.
* The Kubelet was informed of the new secure connection details.

Run 'kubectl get nodes' on the control-plane to see this node join the cluster.
```


### control-plane nodeでちゃんとJOINしたか確認
control-plane 以外のすべてのノードでjoinコマンドが発行されたら、ノードの数と同じだけ `kube-proxy` と `canal` のpodが生成されます。

```console:calicoのcontrollerとcorednsのpodが起動するまで待つ（コピペ）
kubectl wait -n kube-system pod  -l k8s-app=kube-proxy --for=condition=ready     --timeout=60s
kubectl wait -n kube-system pod  -l k8s-app=canal --for=condition=ready     --timeout=60s

```


全部のノードがReadyになっていて、全部のポッドがRunningになるのを確認してから次のステップに進みます。

```shell-session:worker_nodeがjoinした後のpodの状態
root@pos01 [ ~ ]# kubectl get node,pods -A
NAME                      STATUS   ROLES                  AGE     VERSION
node/pos01.example.com    Ready    control-plane,master   37m     v1.22.2
node/pos02.example.com    Ready    <none>                 4m41s   v1.22.2
node/pos03.example.com    Ready    <none>                 114s    v1.22.2
node/pos04.example.com    Ready    <none>                 81s     v1.22.2

NAMESPACE     NAME                                             READY   STATUS    RESTARTS   AGE
kube-system   pod/calico-kube-controllers-74b8fbdb46-nhnxw     1/1     Running   0          8m3s
kube-system   pod/canal-h9hs5                                  2/2     Running   0          114s
kube-system   pod/canal-l8hn5                                  2/2     Running   0          4m41s
kube-system   pod/canal-lwpd8                                  2/2     Running   0          81s
kube-system   pod/canal-svdqf                                  2/2     Running   0          8m3s
kube-system   pod/coredns-78fcd69978-5pq2m                     1/1     Running   0          37m
kube-system   pod/coredns-78fcd69978-vcnfl                     1/1     Running   0          37m
kube-system   pod/etcd-pos01.example.com                       1/1     Running   0          37m
kube-system   pod/kube-apiserver-pos01.example.com             1/1     Running   0          37m
kube-system   pod/kube-controller-manager-pos01.example.com    1/1     Running   0          37m
kube-system   pod/kube-proxy-8vsx7                             1/1     Running   0          81s
kube-system   pod/kube-proxy-fv684                             1/1     Running   0          37m
kube-system   pod/kube-proxy-g4bk7                             1/1     Running   0          4m41s
kube-system   pod/kube-proxy-rbz9j                             1/1     Running   0          114s
kube-system   pod/kube-scheduler-pos01.example.com             1/1     Running   0          37m
```


## metrics-server
**[Metris Server](https://github.com/kubernetes-sigs/metrics-server)**は、kubernetesからのリソース情報を収集して提供するものです。リソース状況を調査する手段である `kubectl top pods` と `kubectl top nodes` がよく利用されているようです。

https://github.com/kubernetes-sigs/metrics-server


### metrics-serverのインストール
**[公式ドキュメント](https://github.com/kubernetes-sigs/metrics-server#requirements)**にあるとおり、 `--kubelet-insecure-tls` と `hostNetwork: true` を適用したものをデプロイします。

```console:metrics-serverのインストール（コピペ）
# --kubelet-insecure-tls と hostNetwork: true を追加
curl -sSL https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml | sed 's/\(^ *image:\)/        - --kubelet-insecure-tls\n\1/' |  sed 's/\(^ *containers:\)/      hostNetwork: true\n\1/'  | kubectl apply -f -

# metrics serverのpodが起動するまで待つ
kubectl wait -n kube-system pod -l k8s-app=metrics-server --for=condition=ready --timeout=60s

```


### metrics-serverでCPUとメモリの情報を見る
メトリクスが取得できているか確認します。デプロイしてから１分くらいかかりますので、ちょっと待ってください。

```shell-session:metrics-serverでnode/podのリソースを見る
root@pos01 [ ~ ]# kubectl top node
NAME                 CPU(cores)   CPU%   MEMORY(bytes)   MEMORY%
pos01.example.com    157m         7%     1574Mi          40%
pos02.example.com    50m          2%     825Mi           21%
pos03.example.com    48m          2%     907Mi           23%
pos04.example.com    48m          2%     827Mi           21%

root@pos01 [ ~ ]# kubectl top pods -A
NAMESPACE              NAME                                         CPU(cores)   MEMORY(bytes)
ingress-nginx          ingress-nginx-controller-74cb6699df-s2glc    1m           77Mi
kube-system            calico-kube-controllers-74b8fbdb46-nhnxw     1m           21Mi
kube-system            canal-h9hs5                                  13m          112Mi
kube-system            canal-l8hn5                                  13m          111Mi
kube-system            canal-lwpd8                                  15m          116Mi
kube-system            canal-svdqf                                  18m          119Mi
kube-system            coredns-78fcd69978-5pq2m                     1m           19Mi
kube-system            coredns-78fcd69978-vcnfl                     1m           19Mi
kube-system            etcd-pos01.example.com                       10m          64Mi
kube-system            kube-apiserver-pos01.example.com             46m          491Mi
kube-system            kube-controller-manager-pos01.example.com    14m          65Mi
kube-system            kube-proxy-8vsx7                             1m           18Mi
kube-system            kube-proxy-fv684                             1m           20Mi
kube-system            kube-proxy-g4bk7                             1m           19Mi
kube-system            kube-proxy-rbz9j                             1m           19Mi
kube-system            kube-scheduler-pos01.example.com             2m           26Mi
kube-system            metrics-server-7f55f7bd5-rtx6b               2m           18Mi
kubernetes-dashboard   dashboard-metrics-scraper-856586f554-dxhxh   1m           14Mi
kubernetes-dashboard   kubernetes-dashboard-67484c44f6-dnxvj        1m           19Mi
metallb-system         controller-6b78bff7d9-p4cbg                  1m           16Mi
metallb-system         speaker-5qnx5                                2m           17Mi
metallb-system         speaker-gvkgn                                2m           19Mi
metallb-system         speaker-lg5hd                                2m           18Mi
metallb-system         speaker-lp8dj                                2m           20Mi
```



## NGINX Ingress 導入
**[Kubernetes Ingress](https://kubernetes.io/ja/docs/concepts/services-networking/ingress/)**は、負荷分散、SSL終端、名前ベースの仮想ホスティングの機能を提供する、クラスター内のServiceに対する外部からのアクセス（主にHTTP）を管理するAPIオブジェクト。
だそうです。この実装の一つが**[NGINX Ingress Controller](https://github.com/kubernetes/ingress-nginx)**で、NGINXをリバースプロキシとロードバランサとして使う、kubernetes用のIngressコントローラー。とのことで、L7でルーティングしたり、SSLの肩代わりしたり、などのいろいろ機能がある、ってことなんでしょうか。

https://github.com/kubernetes/ingress-nginx



NGINX Ingressは **[Bare-metal用の実装](https://kubernetes.github.io/ingress-nginx/deploy/#bare-metal)**もあるみたいなのですが、NodePortが前提のようでClusterIPのままでは外に出れません。
なので、 `Type=LoadBalancer` の実装である**MetalLB**といっしょに使います。


### MetalLBのインストール
**[MetalLB](https://metallb.universe.tf/)**は、標準的なルーティングプロトコルを利用したベアメタルkubernetesクラスタ用のロードバランサ実装。
らしいです。

https://metallb.universe.tf/


どうやらkubernetesはベアメタルクラスター用のType=LoadBalancerのServiceの実装を提供していないそうな。
外部からアクセス可能にする手段としてはNodePortとexternalIPsが準備されてはいるけれども、ぜんぜんイケてないから俺たちが作ってやるぜ！みたいな感じですか。

ここでは、Layer2モード、という、IP ⇔ worker nodeという固定的な割り当ての代替となる機能を利用します。
MetalLBがプールしたIPに対するARP要求は、MetalLBによってnodeごとに用意されたspeakerが応答することによって、適切なnodeを紐づける動作のようです。


以下ドキュメントにしたがってMetalLB本体をインストールします。
https://metallb.universe.tf/installation/

```console:MetalLBのインストール（コピペ）
# 公式ドキュメントのとおり
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.10.2/manifests/namespace.yaml
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.10.2/manifests/metallb.yaml

# MetalLBのpodが起動するまで待つ
kubectl wait -n metallb-system pod -l app=metallb --for=condition=ready     --timeout=60s

```


MetalLBのネームスペースにあるpodがすべてRunningになっていることを確認してから次のステップに進みます。

```shell-session:MetalLBインストール後のpodの状態
root@pos01 [ ~ ]# kubectl get pods -n metallb-system
NAME                          READY   STATUS    RESTARTS   AGE
controller-6b78bff7d9-p4cbg   1/1     Running   0          80s
speaker-5qnx5                 1/1     Running   0          80s
speaker-gvkgn                 1/1     Running   0          80s
speaker-lg5hd                 1/1     Running   0          80s
speaker-lp8dj                 1/1     Running   0          80s
```


次に、Layer2モードで動作させるために、外部に露出するアドレス範囲を指定します。
構築パラメータの通り `10.254.10.181-10.254.10.199` を指定します。
以下の公式ドキュメントのとおりに設定します。
https://metallb.universe.tf/configuration/#layer-2-configuration

```console:MetalLBのLayer2モード設定（コピペ）
INGRESS_ADDRESS=10.254.10.181-10.254.10.199
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ConfigMap
metadata:
  namespace: metallb-system
  name: config
data:
  config: |
    address-pools:
    - name: default
      protocol: layer2
      addresses:
      - ${INGRESS_ADDRESS}
EOF

```


なお、MetalLBのVer.0.10以前では memberlist という secret の作成が必要でしたが、現在のリリースでは自動的に作成してくれるようになっています。
https://metallb.universe.tf/release-notes/#version-0-10-0


### NGINX Ingress Controller のインストール
以下ドキュメントにしたがってNGINX Ingress Controllerをインストールします。
https://kubernetes.github.io/ingress-nginx/deploy/

後述する**[dashboardのIngress設定](#dashboard用のingress設定)**でSSLパススルー機能を利用したいので、以下ページのようにapply後にフラグを追加します。
https://github.com/kubernetes/ingress-nginx/issues/6722

```console:NGINX_Ingressのインストール（コピペ）
# 公式ドキュメントのとおり
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.0.2/deploy/static/provider/cloud/deploy.yaml

# --enable-ssl-passthroughを引数に追加
kubectl patch deployment -n ingress-nginx ingress-nginx-controller --type='json' -p='[{"op": "add", "path": "/spec/template/spec/containers/0/args/-", "value": "--enable-ssl-passthrough"}]'

# NGINX Ingressのpodが起動するまで待つ
kubectl wait -n ingress-nginx   --for=condition=ready pod   --selector=app.kubernetes.io/component=controller   --timeout=60s

```


ingress-nginx-controllerのpodがRunningになっていることを確認してから次のステップに進みます。
また、EXTERNAL-IPにMetalLBのLayer2モードに設定したアドレスが割り当てられていることが確認できます。

```shell-session:NGINX_Ingressインストール後のpod/serviceの状態
root@pos01 [ ~ ]# kubectl get pods,service -n ingress-nginx
NAME                                            READY   STATUS      RESTARTS   AGE
pod/ingress-nginx-admission-create--1-vmz5x     0/1     Completed   0          3m32s
pod/ingress-nginx-admission-patch--1-vfpwq      0/1     Completed   0          3m32s
pod/ingress-nginx-controller-74cb6699df-j7lc7   1/1     Running     0          3m32s

NAME                                         TYPE           CLUSTER-IP      EXTERNAL-IP     PORT(S)                      AGE
service/ingress-nginx-controller             LoadBalancer   10.108.66.241   10.254.10.181   80:30874/TCP,443:31509/TCP   3m32s
service/ingress-nginx-controller-admission   ClusterIP      10.103.25.101   <none>          443/TCP                      3m32s
```


このままではWebhookのValidationによりIngressがうまく動作できませんでした。手っ取り早く回避するために、下記URLの通りWebhookのvaridatingを削除します。
https://stackoverflow.com/questions/61616203/nginx-ingress-controller-failed-calling-webhook

```console:ValidatingWebhookの削除（コピペ）
kubectl delete -A ValidatingWebhookConfiguration ingress-nginx-admission

```

### サンプル用のIngress

動作を理解するためのIngressサンプルです。
HTTP(80/tcp)で稼働するnginxのPod(ここではDeploymentで定義されるcontainers)を生成します。それをServiceとしてClusterIPの80/tcpでアクセスできるようにしていますが、ClusterIPなので、外とは直接通信できません。

なので、これをIngressで外部からアクセスできるようにして、なおかつHTTPSでも待ち受けられるようにしています。バックエンドはHTTPですがHTTPSでもアクセスできるようになります。

まず、サーバー証明書と秘密鍵を作成してsecretに登録をします。
ルートまでたどれるサーバー証明書があらかじめ準備できるならば、`kubectl create secret ～`の登録の部分だけで大丈夫です

```console:サンプルwebページ用のサーバー証明書作成（コピペ）
# CNはwww.example.comとしています
CN=www.example.com
openssl req -new -newkey rsa:2048 -sha256 -nodes -outform PEM -keyform PEM -out ${CN}.csr -keyout ${CN}.key -subj "/C=JP/ST=Tokyo/O=${CN}/CN=${CN}"
openssl x509 -req -days 30 -signkey ${CN}.key < ${CN}.csr > ${CN}.crt

# これをtlsのsecretとして登録する
kubectl create secret tls ${CN} --cert=${CN}.crt --key=${CN}.key

```

次に、secretに登録したサーバー証明書と秘密鍵を利用してIngressを作成します。
ついでにこのIngressのバックエンドとなるDeployment / Service もまとめてファイルとして作成します。


```yaml:~/sample-ingress.yaml
cat <<EOF > sample-ingress_${CN}.yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx:1.17
          ports:
            - containerPort: 80
              protocol: TCP


---
apiVersion: v1
kind: Service
metadata:
  name: nginx-service
spec:
  type: ClusterIP
  ports:
    - port: 80
  selector:
    app: nginx

---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nginx-ingress
  annotations:
    kubernetes.io/ingress.class: "nginx"
    nginx.ingress.kubernetes.io/ssl-redirect: "false"
spec:
  tls:
  - hosts:
      - ${CN}
    secretName: ${CN}
  rules:
  - host: ${CN}
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: nginx-service
            port:
              number: 80
EOF

```


これを適用します。

```console:サンプルwebページ用のdeployment/service/ingressを作成（コピペ）
kubectl apply -f sample-ingress_${CN}.yaml

```


状態を確認します。

```shell-session:Ingressとそれを構成するpod/deploy/svcの状態
root@pos01 [ ~ ]# kubectl get pod,deployment,service,ingress  -o wide
NAME                                   READY   STATUS    RESTARTS   AGE     IP           NODE                 NOMINATED NODE   READINESS GATES
pod/nginx-deployment-db749865c-67n8w   1/1     Running   0          4h23m   10.244.2.5   pos03.example.com    <none>           <none>
pod/nginx-deployment-db749865c-k865l   1/1     Running   0          4h23m   10.244.1.7   pos02.example.com    <none>           <none>

NAME                               READY   UP-TO-DATE   AVAILABLE   AGE     CONTAINERS   IMAGES       SELECTOR
deployment.apps/nginx-deployment   2/2     2            2           4h23m   nginx        nginx:1.17   app=nginx

NAME                    TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE     SELECTOR
service/kubernetes      ClusterIP   10.96.0.1       <none>        443/TCP   5h15m   <none>
service/nginx-service   ClusterIP   10.103.225.66   <none>        80/TCP    4h23m   app=nginx

NAME                                      CLASS    HOSTS             ADDRESS         PORTS     AGE
ingress.networking.k8s.io/nginx-ingress   <none>   www.example.com   10.254.10.181   80, 443   4h23m
```


では、アクセスできるか curl で確認してみます。これは適当なクラスタ外のホストからテストします。

なおCNとして設定したFQDNの `www.example.com` は、アドレスを調べてからhostsファイルに追記して動作確認します。先ほどの結果からアドレスが `10.254.10.181` ということがわかるので、これをhostsファイルに追記します。ADDRESS欄にアドレスが表示されるまで、１分程度かかる場合があります。表示されない場合は、少し時間をおいてから再試行します。

```:/etc/hosts
# CN=www.example.com としたので、これに対応する行を追加します
10.254.10.181 www.example.com
```


curlでページ情報を取得してみましょう。HTTPでもHTTPSでもアクセスできます。
アクセスできないときは一度podを削除してみます。（→**[サンプル用のIngressにアクセスできない](#サンプル用のingressにアクセスできない)**)

```shell-session:テスト用のIngressにアクセスしてみる
~$  curl -I http://www.example.com/
HTTP/1.1 200 OK
Date: Fri, 01 Oct 2021 11:31:26 GMT
Content-Type: text/html
Content-Length: 612
Connection: keep-alive
Last-Modified: Tue, 14 Apr 2020 14:19:26 GMT
ETag: "5e95c66e-264"
Accept-Ranges: bytes

~$ curl -Ik https://www.example.com/
HTTP/2 200
date: Fri, 01 Oct 2021 11:31:36 GMT
content-type: text/html
content-length: 612
last-modified: Tue, 14 Apr 2020 14:19:26 GMT
etag: "5e95c66e-264"
accept-ranges: bytes
strict-transport-security: max-age=15724800; includeSubDomains
```


`nginx.ingress.kubernetes.io/ssl-redirect: "false"` の部分を
`nginx.ingress.kubernetes.io/ssl-redirect: "true"` に変更すると
HTTPアクセスはHTTPSにリダイレクトされます。

```shell-session:ssl-redirect設定をtrueにして適用
root@pos01 [ ~ ]# sed ./sample-ingress_${CN}.yaml -e 's/\(ssl-redirect: \).*/\1"true"/' | kubectl apply -f -
deployment.apps/nginx-deployment unchanged
service/nginx-service unchanged
ingress.networking.k8s.io/nginx-ingress configured
```


これで先ほどと同様にcurlでアクセスしてみると、HTTPがHTTPSへリダイレクトされていることがわかります。

```shell-session:HTTPはHTTPSにリダイレクトされる
~$  curl -IkL http://www.example.com/
HTTP/1.1 308 Permanent Redirect
Date: Fri, 01 Oct 2021 11:32:44 GMT
Content-Type: text/html
Content-Length: 164
Connection: keep-alive
Location: https://www.example.com

HTTP/2 200
date: Fri, 01 Oct 2021 11:32:44 GMT
content-type: text/html
content-length: 612
last-modified: Tue, 14 Apr 2020 14:19:26 GMT
etag: "5e95c66e-264"
accept-ranges: bytes
strict-transport-security: max-age=15724800; includeSubDomains
```


証明書が自己署名のサーバー証明書かどうか確認するために openssl で接続してみます。
先ほど生成した証明書が取得できていることがわかります。

```shell-session:サーバー証明書の確認
$ openssl s_client -connect www.example.com:443 -quiet
depth=0 C = JP, ST = Tokyo, O = example, CN = www.example.com
verify error:num=18:self signed certificate
verify return:1
depth=0 C = JP, ST = Tokyo, O = example, CN = www.example.com
verify return:1
^C
```

### サンプル用のIngressにアクセスできない
理由はよくわからないのですが、504になってしまい、うまくアクセスできない場合があります。podが特定のnodeにデプロイされるとこの症状が出るようなので、別のnodeにデプロイされるまで削除しましょう。が、なぜそうなるのか理由がよくわかりません。。。

```shell-session:サンプル用のIngressが504になる
$ curl -I http://www.example.com/
HTTP/1.1 504 Gateway Time-out
Date: Sat, 02 Oct 2021 10:16:21 GMT
Content-Type: text/html
Content-Length: 160
Connection: keep-alive
```


このときのpodはpos04とpos02に配置されています。

```shell-session:サンプル用のIngressが504になったときのpodの配置
root@pos01 [ ~ ]# kubectl get pod -n default -o wide
NAME                               READY   STATUS    RESTARTS   AGE   IP           NODE                NOMINATED NODE   READINESS GATES
nginx-deployment-db749865c-htsb5   1/1     Running   0          28s   10.244.3.4   pos04.example.com   <none>           <none>
nginx-deployment-db749865c-kvw8g   1/1     Running   0          27s   10.244.1.4   pos02.example.com   <none>           <none>
```


podを削除すると、deploymentによってpodが自動的に生成されるので、 `kubectl delete pod` で削除して配置を見てみましょう。

```shell-session:podを削除してpodの配置が変わったか見てみる
root@pos01 [ ~ ]# kubectl delete pod -n default --all
pod "nginx-deployment-db749865c-htsb5" deleted
pod "nginx-deployment-db749865c-kvw8g" deleted

root@pos01 [ ~ ]# kubectl get pod -o wide
NAME                               READY   STATUS    RESTARTS   AGE   IP           NODE                NOMINATED NODE   READINESS GATES
nginx-deployment-db749865c-sd4bh   1/1     Running   0          1s    10.244.2.7   pos03.example.com   <none>           <none>
nginx-deployment-db749865c-z7g74   1/1     Running   0          1s    10.244.2.8   pos03.example.com   <none>           <none>
```

nodeがpos04とpos02からpos03に変わりましたので、再度アクセスしてみます。

```shell-session:サンプル用のIngressにアクセスできるようになった
$ curl -I http://www.example.com/
HTTP/1.1 200 OK
Date: Sat, 02 Oct 2021 10:20:28 GMT
Content-Type: text/html
Content-Length: 612
Connection: keep-alive
Last-Modified: Tue, 14 Apr 2020 14:19:26 GMT
ETag: "5e95c66e-264"
Accept-Ranges: bytes
```

どっかのiptablesのフィルタリングかフォワードルールかが、おかしくなってしまっているんでしょうか？でもそこまで詳しくないので調べ切れていません。。。



## Kubernetes Dashboard の導入
**[Kubernetes Dashboard](https://github.com/kubernetes/dashboard)**は、kubernetesクラスタのための一般的はWebベースのUIで、クラスタ内のアプリケーション管理やトラブルシュート、クラスタ自体の管理もすることができます。
だそうです。

https://github.com/kubernetes/dashboard



### dashboardのインストール
以下ドキュメントにしたがってKubernetes Dashboardをインストールします。
https://github.com/kubernetes/dashboard#install

```console:KubernetesDashboardのインストール（コピペ）
# 公式ドキュメントの通りにインストール
kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.3.1/aio/deploy/recommended.yaml

# dashboardのpodが起動するまで待つ
kubectl wait -n kubernetes-dashboard pod --all --for=condition=ready --timeout=60s

```


全部のポッドがRunningになるのを確認してから次のステップに進みます。

```shell-session:Dashboardインストール後のpodの状態
root@pos01 [ ~ ]# kubectl get pods -n kubernetes-dashboard
NAME                                         READY   STATUS    RESTARTS   AGE
dashboard-metrics-scraper-856586f554-dxhxh   1/1     Running   0          12s
kubernetes-dashboard-67484c44f6-dnxvj        1/1     Running   0          12s
```


サービスアカウントとして権限を付与した管理者ユーザを作成し、そのユーザーに紐づいたログイン用のトークンを利用してdashboardにログインさせます。
ということが以下のドキュメントに書いてあります。
https://github.com/kubernetes/dashboard/blob/master/docs/user/access-control/creating-sample-user.md

```console:Dashboard用アカウント設定（コピペ）
# サービスアカウントとして管理者ユーザを作成
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
  name: admin-user
  namespace: kubernetes-dashboard
EOF

# そのユーザーにクラスタ管理者という役割を与えています。
cat <<EOF | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: admin-user
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: admin-user
  namespace: kubernetes-dashboard
EOF

```


### dashboard用のIngress設定
ただDashboardをデプロイするだけならこれで終了です。が、公式のyamlをそのまま適用させるとServiceがClulsterIPになるため、外部からアクセスすることができません。

**[Accessing Dashboard](https://github.com/kubernetes/dashboard/blob/master/docs/user/accessing-dashboard/README.md)**では、`kubectl proxy` や `kubectl port-forward` で外部からアクセスさせたり、ServiceをNodePortにする解説があります。Ingressの解説は「Ingressのドキュメントみてね」で終わってるので、これを実際にやってみようと思います。

もともとDashboardのServieはHTTPSで動作していますので、ssl-passthroughでSSL終端はDashboard側にしてもらうことにしましょう。tlsのセクションにsecretを指定せず、annotetionsのセクションにssl-passthrouhを追加すればOKです。

以下の内容でIngressを設定します。

```console:Dashboard用のIngressを作成（コピペ）
# Dashboard用のCNは検証用のものを利用
CN=dashboard.example.com

cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: dashboard-ingress
  namespace: kubernetes-dashboard
  labels:
    k8s-app: kubernetes-dashboard
  annotations:
    kubernetes.io/ingress.class: "nginx"
    nginx.ingress.kubernetes.io/ssl-passthrough: "true"
    nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
spec:
  tls:
  - hosts:
      - ${CN}
  rules:
  - host: ${CN}
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: kubernetes-dashboard
            port:
              number: 443
EOF

```


ingressの情報を取得してホスト名が設定されている通りかどうか確認します。

```shell-session:dashboardのingress状態
root@pos01 [ ~ ]# kubectl get  ingress -n kubernetes-dashboard
NAME                CLASS    HOSTS                    ADDRESS         PORTS     AGE
dashboard-ingress   <none>   dashboard.example.com    10.254.10.181   80, 443   4h43m
```


サンプル用のIngressと同様に、CNとして設定したFQDNの`dashboard.example.com`に対応する`10.254.10.181`をhostsファイルに追記します。

```:/etc/hosts
# CN=dashboard.example.com としたので、これに対応する行を追加します
10.254.10.181 dashboard.example.com
```


curlでページ情報を取得してみましょう。
アクセスできないときは先ほどと同様にpodが配置されているnodeが変わるまでpodを削除してみます。（→**[dashboardにアクセスできない](#dashboardにアクセスできない)**)

```shell-session:dashboardのIngressにアクセスしてみる
~$ curl -Ik https://dashboard.example.com/
HTTP/2 200
accept-ranges: bytes
cache-control: no-cache, no-store, must-revalidate
content-type: text/html; charset=utf-8
last-modified: Wed, 16 Jun 2021 10:53:38 GMT
content-length: 1338
date: Fri, 01 Oct 2021 13:26:22 GMT
```


サーバー証明書の発行元を確認するために openssl で接続してみます。
Dashboardが発行した証明書が取得できているように見えます。が、中身がカラでよくわかんないですね。

```shell-session:dashboardのingressのサーバー証明書
~$  openssl s_client -connect dashboard.example.com:443 -quiet
depth=0
verify error:num=18:self signed certificate
verify return:1
depth=0
verify return:1
^C
```


ブラウザからアクセスすると認証画面が表示されます。証明書の発行者やサブジェクトは空欄になっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334782/ec7e3114-4d28-ea5e-9daa-725ba3f550de.png)



ログインするためのトークンは、control-plane node から以下のように取得します。

```shell-session:dashboardのログイントークンを取得
root@pos01 [ ~ ]# kubectl -n kubernetes-dashboard get secret $(kubectl -n kubernetes-dashboard get sa/admin-user -o jsonpath="{.secrets[0].name}") -o go-template="{{.data.token | base64decode}}"
eyJhbGci( ... トークン文字列をコピーする ... )tNTZ
```


ログイン直後はdefaultのネームスペースが対象となっているので、先ほどのサンプル用のIngressの情報が表示されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334782/a29453fd-5815-c40c-7855-e4acd25f85de.png)


上部のメニュで「すべてのネームスペース」を選択すればたくさん表示されます。metrics-serverを導入しているため、CPUとメモリのグラフも見れるようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334782/42efd271-de18-faa2-0f05-ccf9d3c74188.png)

ダッシュボードまで見えるのに、CPU/Memoryのグラフが表示されない、というときは、metrics-serverのpodが配置されているnodeを疑ってみます。（→**[dashboardにアクセスできない](#dashboardにアクセスできない)**)


ちなみに、先ほどのSSL-passthroughをしないと、どうなるでしょうか。ingressのannotationsの部分を直接書き換えてみましょう。

```console:dashboardのingressをssl-passthroughしないに変更する（コピペ）
kubectl patch ingress -n kubernetes-dashboard dashboard-ingress -p '{"metadata":{"annotations":{"nginx.ingress.kubernetes.io/ssl-passthrough":"false"}}}'
```


なるほど。Ingressが代わりに証明書を作ってくれるんですね。

```shell-session:dashboardのingressをssl-passthroughしないときのサーバー証明書
~$  openssl s_client -connect dashboard.example.com:443 -quiet
depth=0 O = Acme Co, CN = Kubernetes Ingress Controller Fake Certificate
verify error:num=20:unable to get local issuer certificate
verify return:1
depth=0 O = Acme Co, CN = Kubernetes Ingress Controller Fake Certificate
verify error:num=21:unable to verify the first certificate
verify return:1
^C
```


### dashboardにアクセスできない
サンプル用Ingressと同様に、うまくアクセスできない時はpodが配置される場所が変更されるまで削除すると、うまくいく場合があります。

```shell-session:dashboard用のIngressにアクセスできない
$ curl -Ik https://dashboard.example.com/
curl: (35) OpenSSL SSL_connect: SSL_ERROR_SYSCALL in connection to dashboard.example.com:443
```


このときのdashboardのpodはpos04に配置されています。

```shell-session:dashboard用のIngressにアクセスできないときのpodの配置
root@pos01 [ ~ ]# kubectl get pod -n kubernetes-dashboard -o wide
NAME                                         READY   STATUS    RESTARTS   AGE   IP           NODE                NOMINATED NODE   READINESS GATES
dashboard-metrics-scraper-856586f554-flq4p   1/1     Running   0          15m   10.244.1.3   pos02.example.com   <none>           <none>
kubernetes-dashboard-67484c44f6-9c2qj        1/1     Running   0          15m   10.244.3.3   pos04.example.com   <none>           <none>
```


podを削除すると、deploymentによってpodが自動的に生成されるので、 `kubectl delete pod` で削除して配置を見てみましょう。

```shell-session:podを削除してpodの配置が変わったか見てみる
root@pos01 [ ~ ]# kubectl delete pod -n kubernetes-dashboard --all
pod "dashboard-metrics-scraper-856586f554-flq4p" deleted
pod "kubernetes-dashboard-67484c44f6-9c2qj" deleted

root@pos01 [ ~ ]# kubectl get pod -n kubernetes-dashboard -o wide
NAME                                         READY   STATUS    RESTARTS   AGE   IP            NODE                NOMINATED NODE   READINESS GATES
dashboard-metrics-scraper-856586f554-8lxzh   1/1     Running   0          34s   10.244.2.10   pos03.example.com   <none>           <none>
kubernetes-dashboard-67484c44f6-9ncmk        1/1     Running   0          34s   10.244.2.11   pos03.example.com   <none>           <none>
```

nodeがpos04とpos02からpos03に変わりましたので、再度アクセスしてみます。

```shell-session:dashboard用のIngressにアクセスできるようになった
$ curl -Ik https://dashboard.example.com/
HTTP/2 200
accept-ranges: bytes
cache-control: no-cache, no-store, must-revalidate
content-type: text/html; charset=utf-8
last-modified: Wed, 16 Jun 2021 10:53:38 GMT
content-length: 1338
date: Sat, 02 Oct 2021 10:42:28 GMT
```

何度削除しても変わってくれない時は `kubectl drain [NODE]` でpodが配置されないようにしてから、削除すれば変更できます。

でも、そのnodeがちゃんと使えないって、結局そのnodeが存在する意味はないし、そもそも正しく構築できてないってことなんですよね。
pod間通信の仕組みが理解できておらず、ホントになんでこうなるのかわかりませんし、どうやって調査したらよいかすら見当もつかずでして。。。


# おわりに
でもこれ、コピペで作れるんだったら、全自動でも作れるよな。
…… おもしろそう！


## DockerHubのRateLimitにひっかかった

検証中にImageのPullがうまくいかなくなった。タイムアウトを伸ばしてみたが、意味がなかった。
以下を参考にさせていただきました。

https://qiita.com/zembutsu/items/cd8e41cfd02e4a9a75ca

nodeが多いとjoinしたときにCNIのcontainerをpullしてくるので、繰り返し検証しているとすぐに引っかかってしまった。たまにこれで確認しながら進めた。

```
TOKEN=$(curl -sSL "https://auth.docker.io/token?service=registry.docker.io&scope=repository:ratelimitpreview/test:pull" | jq -r .token)
curl -sSL -v -H "Authorization: Bearer $TOKEN" https://registry-1.docker.io/v2/ratelimitpreview/test/manifests/latest 2>&1 | egrep "ratelimit-|rate limit"

```



## ubuntu 20.04

構築時のホストとして利用したubuntuは server edition をインストールしたけど、日本語がなかった。
ので、初期設定として、以下を投入してから利用した。

```
sudo apt-get install -y language-pack-ja-base language-pack-ja
sudo update-locale LANG=ja_JP.UTF-8 LANGUAGE=ja_JP.UTF-8
source /etc/default/locale
sudo timedatectl set-timezone Asia/Tokyo
timedatectl timesync-status

```