AWSにセルフイントールでKubernetesクラスター作ってみる。'21年10月Kube v1.22

最近のKubernetes事情の確認含めkubeadmでAWSにセルフインストールしてみようかなと。
クラウドが提供するManagedサービスやベンダー製Distroは最近猫も杓子もHAマスター３台構成なのだが、ワーカーノード２台動かしたいだけでマスターノード３台とか、普通に無駄が多くて納得いかないっすよねという事があり、マスターノードに１円も金が掛からないのならやぶさかではないが、ベンダーDistro廃れろとか、ユーザーには思われてたりするのかなと思ったり思わなかったり。
シングルマスタークラスターを２つのAZに１つずつ建てれば高可用性の側面でも大体満足するはずですし？
素のKubernetesならマスターノードのマシンスペックもVCPU 2個で、メモリも2GBで無駄がない。アプリケーションごとに個別にクラスタ建ててもお釣りが来るんじゃないのというくらい。

以下、kubeadmを使ったKubernetesの導入について、Kubernetesの以下のドキュメントを参照しつつ実施。
https://kubernetes.io/ja/docs/setup/production-environment/tools/kubeadm/

クラスターにテスト用のhttpd導入して、Ingress経由Amazon ALBでHTTPSでアクセス出来るようになるまでやっても、慣れれば手動で30分ぐらい。そこらのKubernetesクラスター自動で作りますサービスよりも早くて安いだろう。


# マスターノード（１台）を構成する

## １．仮想マシンの用意
いつもはAmazon Lightsail使うのだが、スペック的に月20$のマシンが必要になるのでさすがに。仕方ないので少し面倒だがEC2のオンデマンドインスタンスを利用。
OSはUbuntu 20.04、マシンはVCPU 2個でメモリ2GBのt3.small、セキュリティグループはkubeという名前で新規作成、他はデフォルト。

ネットワークドライバにCalicoを使うため、インスタンスのネットワーキング設定で送信元/送信先チェックを無効にする必要がある点に注意。
https://docs.projectcalico.org/reference/public-cloud/aws
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146381/1a029806-3943-25fb-acd4-01881e4aaf7a.png)

インスタンスができたらubuntu@＜パブリックIP＞でssh接続。


## ２．コンテナランタイムのインストール
コンテナランタイムは、最近はcri-oなのかと思ったが、どうもKubernetesへの追随が遅いらしくKubernetes 1.22用のcri-oがまだリリースされていない。大丈夫か。
という訳でDockerを利用。

https://docs.docker.com/engine/install/ubuntu/

```bash:
$ sudo apt-get update
$ sudo apt-get install -y apt-transport-https ca-certificates curl gnupg lsb-release
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
$ echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
$ sudo apt-get update
$ sudo apt-get install -y docker-ce=5:19.03.11~3-0~ubuntu-focal docker-ce-cli=5:19.03.11~3-0~ubuntu-focal containerd.io
$ cat <<EOF | sudo tee /etc/docker/daemon.json
{
  "exec-opts": ["native.cgroupdriver=systemd"],
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m"
  },
  "storage-driver": "overlay2"
}
EOF
$ sudo mkdir -p /etc/systemd/system/docker.service.d
$ sudo systemctl restart docker

# Dockerの動作確認
$ docker --version
Docker version 19.03.11, build 42e35e61f3
$ sudo docker run hello-world
...
Hello from Docker!
```

## ３．kubeadmとかのインストール

```bash:
$ sudo apt-get update && sudo apt-get install -y apt-transport-https curl
$ curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
$ cat <<EOF | sudo tee /etc/apt/sources.list.d/kubernetes.list
deb https://apt.kubernetes.io/ kubernetes-xenial main
EOF
$ sudo apt-get update
$ sudo apt-get install -y kubelet kubeadm kubectl
$ sudo apt-mark hold kubelet kubeadm kubectl
```

Auto ScalingグループをAMI作ってやるなら、この時点のイメージを取得する。今回はやらない。とはいえ、5分くらいで出来ることなのでここをAMIにして維持する時間を正当化するのは難しい。自動化って難儀。


## ４．クラスター作成
ネットワークドライバにCalicoを使うため「--pod-network-cidr=192.168.0.0/16」をオプションを指定。

```bash:
$ sudo kubeadm init --pod-network-cidr=192.168.0.0/16
→ 出力のうち、「kubeadm join ～」はワーカーノード追加に後で必要になるため控えておく。
こんなの。
kubeadm join 172.31.59.187:6443 --token si14yv.1b31nu4ay5eaaz1x --discovery-token-ca-cert-hash sha256:d658729e7ec5902712ed3dc03fe54b77dc5915629aa508dd6d64543cbeb42b1d

$ mkdir -p $HOME/.kube
$ sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
$ sudo chown $(id -u):$(id -g) $HOME/.kube/config

# ネットワークドライバ導入。Calico
$ kubectl create -f https://docs.projectcalico.org/manifests/tigera-operator.yaml
$ kubectl create -f https://docs.projectcalico.org/manifests/custom-resources.yaml

# Calicoが起動してくるのを待つ
$ watch kubectl get pods -n calico-system
NAME                                       READY   STATUS    RESTARTS   AGE
calico-kube-controllers-767ddd5576-mc6sd   1/1     Running   0          48s
calico-node-dk87j                          1/1     Running   0          48s
calico-typha-7cc6899b8c-kdsvl              1/1     Running   0          48s

# corednsが起動してくるのを待つ
$ watch kubectl get pod -A
NAMESPACE          NAME                                       READY   STATUS    RESTARTS   AGE
calico-apiserver   calico-apiserver-6dd4bc68c6-lmwqb          1/1     Running   0          88s
calico-system      calico-kube-controllers-767ddd5576-mc6sd   1/1     Running   0          2m40s
calico-system      calico-node-dk87j                          1/1     Running   0          2m40s
calico-system      calico-typha-7cc6899b8c-kdsvl              1/1     Running   0          2m40s
kube-system        coredns-78fcd69978-hzmfs                   1/1     Running   0          7m4s
kube-system        coredns-78fcd69978-kwlmv                   1/1     Running   0          7m4s
kube-system        etcd-ip-172-31-59-187                      1/1     Running   1          7m18s
kube-system        kube-apiserver-ip-172-31-59-187            1/1     Running   1          7m18s
kube-system        kube-controller-manager-ip-172-31-59-187   1/1     Running   1          7m18s
kube-system        kube-proxy-z6655                           1/1     Running   0          7m4s
kube-system        kube-scheduler-ip-172-31-59-187            1/1     Running   1          7m18s
tigera-operator    tigera-operator-59f4845b57-hpvlm           1/1     Running   0          3m27s
```

クラスター外のPC等からkubectlを実行したい場合はkubeセキュリティグループに6443ポートの穴も空けておく。


# ワーカーノード（２台）を構成する

## ０．セキュリティグループの調整
マスターノード作成時に作ったkubeセキュリティグループを調整。
インバウンドルールに、kubeセキュリティグループ自身からのすべてのトラフィックを許可するルールを追加する。


## １．仮想マシンの用意
マスターと同じスペック。
OSはUbuntu 20.04、マシンはVCPU 2個でメモリ2GBのt3.small、セキュリティグループは先のkube、サブネットはマスターノードに合わせる、他はデフォルト。
User Dataを駆使してAuto Scalingグループでワーカーノードを追加できるようにしても良いかもだが、Ingressコントローラーを使う場合はASG活用はちょっと難しい。
Calicoのため送信元/送信先チェックを無効にするのも忘れずに。これも、ASGでは設定できないっぽい。

インスタンスができたらubuntu@＜パブリックIP＞でssh接続。


### ２．コンテナランタイムのインストール
マスターノードと手順一緒。

## ３．kubeadmとかのインストール
マスターノードと手順一緒。


## ４．ワーカーノードをクラスターに追加
先のマスターノードのkubeadm initの出力に、sudoを付けて以下実行。

```bash:
$ sudo kubeadm join ～
(ex)sudo kubeadm join 172.31.59.187:6443 --token si14yv.1b31nu4ay5eaaz1x --discovery-token-ca-cert-hash sha256:d658729e7ec5902712ed3dc03fe54b77dc5915629aa508dd6d64543cbeb42b1d
```

## ５．ノードを確認
マスターノードのssh画面に戻って、以下のコマンドを実行すればノードが２つ追加されているはず。

```bash:
$ kubectl get node
NAME               STATUS   ROLES                  AGE     VERSION
ip-172-31-53-240   Ready    <none>                 4m36s   v1.22.2
ip-172-31-59-187   Ready    control-plane,master   46m     v1.22.2
ip-172-31-63-183   Ready    <none>                 4m53s   v1.22.2
```

## ６．CoreDNSを再配置
マスターノード上でCoreDNSのPodが２つ動作しているので、それをワーカーノードに再配置する。

```bash:
$ kubectl scale --replicas=0 -n kube-system deployment/coredns
$ kubectl scale --replicas=2 -n kube-system deployment/coredns
```

## ７．Ingressコントローラーを導入
NodePortタイプのKubernetesコミュニティ提供のingress-nginxを使って、hostPortで動かす。
まあ、この構成なら１アプリ１クラスターも全然現実的なので、アプリをDaemonSetで動かしてそれにhostPort設定するでもOKだと思うし、その場合はIngressコントローラー要らない。

https://kubernetes.github.io/ingress-nginx/deploy/

```bash:
$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.0.4/deploy/static/provider/baremetal/deploy.yaml
$ kubectl edit deploy -n ingress-nginx ingress-nginx-controller
（変更前）
        ports:
        - containerPort: 80
          name: http
          protocol: TCP

（変更後）
        ports:
        - containerPort: 80
          hostPort: 80
          name: http
          protocol: TCP
```

443をいじる必要はない。HTTPS接続したかったら、証明書とかの関係でAWS ALB経由で公開したほうが良いので。

ついでに、レプリカ２つに。

```bash:
kubectl scale --replicas=2 -n ingress-nginx deploy/ingress-nginx-controller
```

また、ingress-nginxをクラスターのデフォルトIngressコントローラーにする。

```bash:
$ kubectl edit IngressClass nginx
（変更前）
  annotations:
（変更後）
  annotations:
    ingressclass.kubernetes.io/is-default-class: "true"
```

# アプリケーションを導入して動作確認
アプリケーションといっても、httpd。

```bash:
$ kubectl run httpd --image=httpd
$ kubectl expose pod/httpd --port=80
$ cat httpd-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: httpd
spec:
  rules:
    - http:
        paths:
        - pathType: Prefix
          path: /
          backend:
            service:
              name: httpd
              port:
                number: 80
$ kubectl create -f httpd-ingress.yaml
```

ワーカーノードのHTTP(80)ポートに直接アクセスしに行くため、先のkubeセキュリティグループでHTTP(80)のアクセスを許可する。後で述べる、ALB経由でHTTPSアクセスを構成する場合は不要。

手元のWebブラウザから、ワーカノードのパブリックIPアドレスにアクセスすれば「It works!」とメッセージが表示されるはず。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146381/6421d28a-efef-3476-b512-13e0fb878910.png)


# （ついでに）ALB経由でHTTPSアクセス

AWSのALB経由でHTTPSアクセスを構成することもできる。大体以下の手順だが、詳細割愛。
・TLS証明書を作るため、Route 53でドメイン名を取っておく
・ALB用に、Ingressコントローラーをデプロイしたワーカーノード２台のHTTP(80)がターゲットのターゲットグループを作る
・ALB用の、HTTPS(443)を受け付けるセキュリティグループを作る
・ALB用のTLS証明書を作る
・HTTPS(443)で受け取ったリクエストをターゲットグループに転送するALBを作る
・ワーカーノードの、kubeセキュリティグループのInboundルールを編集し、ALBのセキュリティグループを許可する
・ALBのDNS名を、Route 53にTLS証明書を取得した名前でCNAME登録する

ALBを作るときにAZを複数指定するが、指定した２個目のAZにクラスターもう一つ作れば、マルチAZ、SLA 99.99%位のコンテナアプリ実行環境出来上がり。
ホストOSにアクセス出来るのでCloudWatch Agent入れることが出来るなど、運用監視は組み立てやすいだろう。hostPathも気軽に使えて嬉しい。嬉しいですよね？
また、一般的に非推奨だろうが何だろうがKubernetes v1.22等でバージョン固定が可能というのも場合によっては大きい。将来もDocker使えるとか、そんなところ。
その他運用的には、障害回復やバージョンアップどうすんのという課題はすべて、クラスター作り直せば？であり、シンプル。