(AWS EC2) kubeadmでKubernetesのシングルノードクラスタを構築する


# はじめに

この記事で扱うこと

- kubeadmを使ってKubernetesクラスタを構築する
- シングルノードクラスタとする（マスターノードにデプロイ）
- AWSのEC2インスタンスを利用する

# 1. ec2インスタンスの起動

マシンの要件としては、下記の通り[kubeadmのインストール](https://kubernetes.io/ja/docs/setup/production-environment/tools/kubeadm/install-kubeadm/)に記載があります。
今回はCentOSを選択します。

> **始める前に**
>
- 次のいずれかが動作しているマシンが必要です
  - Ubuntu 16.04+
  - Debian 9+
  - CentOS 7
  - Red Hat Enterprise Linux (RHEL) 7
  - Fedora 25+
  - HypriotOS v1.0.1+
  - Container Linux (tested with 1800.6.0)
- 1台あたり2GB以上のメモリ(2GBの場合、アプリ用のスペースはほとんどありません)
- 2コア以上のCPU
- クラスター内のすべてのマシン間で通信可能なネットワーク(パブリックネットワークでもプライベートネットワークでも構いません)
- ユニークなhostname、MACアドレス、とproduct_uuidが各ノードに必要です。詳細はここを参照してください。
- マシン内の特定のポートが開いていること。詳細はここを参照してください。
- Swapがオフであること。kubeletが正常に動作するためにはswapは必ずオフでなければなりません。

EC2インスタンスの構成は下記の通りとします。
2GB以上のメモリ、2コア以上のCPUを満たすよう`t3.small`のインスタンスを選択しています。
(※ t2.microでも動いたよという記事もちらほら見かけるのちょっと触るくらいならt2.microでも問題なさそうな気がします。)

| 項目               |  設定                                       | 
| ------------------ | ------------------------------------------ | 
| AMI                | CentOS 7 (x86_64) - with Updates HVM       | 
| アーキテクチャ      | x86_64                                     | 
| インスタンスタイプ  | t3.small (2 vCPU / 2 GiBメモリ)             | 
| EBS ボリューム     | 8 GB                                        | 

公開するポートについても、[kubeadmのインストール](https://kubernetes.io/ja/docs/setup/production-environment/tools/kubeadm/install-kubeadm/)に下記のように記載があります。
今回はMasterノード1つだけのシングルノードクラスタなので、NodePortで使用する`30000-32767`の通信のみ許可します。

![ports.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/705474/092d7f13-b4a6-5c48-731b-1b60bd1b379b.png)

セキュリティグループのインバウンドルールはこのように設定しています。
（複数ノードでクラスタを構築したい方は上記の画像の通りにポートを解放します。）

![セキュリティグループ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/705474/d1383285-bc97-3042-e132-7822f7c7b89d.png)


以上の設定でEC2インスタンスを起動し、sshでログインします。
CentOSのユーザは`centos`です。（ec2-userではありません。）

```shell:ec2へssh
[centos@ip-172-31-7-118 ~]$ 
```


# 2. クラスタセットアップの前準備

はじめにrootユーザに昇格しておきます。

```
[centos@ip-172-31-7-118 ~]$ sudo su -
[root@ip-172-31-7-118 ~]# 
```

次に[kubeadmのインストール](https://kubernetes.io/ja/docs/setup/production-environment/tools/kubeadm/install-kubeadm/)に記載のネットワーク要件を満たすように設定を行います。

> Linuxノードのiptablesがブリッジを通過するトラフィックを正確に処理する要件として、net.bridge.bridge-nf-call-iptablesをsysctlの設定ファイルで1に設定してください。


```shell:br_netfilterの有効化
[root@ip-172-31-7-118 ~]# modprobe br_netfilter
[root@ip-172-31-7-118 ~]# lsmod | grep br_netfilter
br_netfilter           22256  0 
bridge                151336  1 br_netfilter
```

```shell:iptablesの設定
[root@ip-172-31-7-118 ~]# cat <<EOF > /etc/sysctl.d/k8s.conf
> net.bridge.bridge-nf-call-ip6tables = 1
> net.bridge.bridge-nf-call-iptables = 1
> EOF
```

下記コマンドで設定が反映されていることを確認します（下の方が優先される）。

```
[root@ip-172-31-7-118 ~]# sysctl --system | grep net.bridge.bridge-nf-call
net.bridge.bridge-nf-call-ip6tables = 0
net.bridge.bridge-nf-call-iptables = 0
net.bridge.bridge-nf-call-arptables = 0
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
```

次にSwap領域が無効になっていることを確認します。

```shell:Swap領域の確認
[root@ip-172-31-7-118 ~]# swapon -s
[root@ip-172-31-7-118 ~]# 
```

何も表示されていないのでSwap領域は無効になっているようです。
Swap領域が有効になっている場合は下記コマンドで無効化します。

```shell:Swap領域の無効化
[root@ip-172-31-7-118 ~]# swapoff -a
```

以上で前準備が完了となります。

# 3. コンテナランタイムのインストール

[CRIのインストール](https://kubernetes.io/ja/docs/setup/production-environment/container-runtimes/)に従ってコンテナランタイムのインストールを行います。
ほとんどコマンドのコピペ作業になります。

```shell:必要パッケージのインストール
[root@ip-172-31-7-118 ~]# yum install -y yum-utils device-mapper-persistent-data lvm2
```

```shell:Dockerレポジトリの追加
[root@ip-172-31-7-118 ~]# yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
```

ランタイムとしては`containerd`を利用しています。
注意点としては下手に最新バージョンを利用したりするとkubernetesが起動しなくなるため、バージョンも公式サイトの手順にあるものをそのまま利用します。

```shell:Dockerのインストール
[root@ip-172-31-7-118 ~]# yum update -y && yum install -y \
>   containerd.io-1.2.13 \
>   docker-ce-19.03.11 \
>   docker-ce-cli-19.03.11
```

インストールに成功するとdockerデーモンが読み込まれていることが確認できます。

```shell:dockerデーモンの確認
[root@ip-172-31-7-118 ~]# systemctl status docker
● docker.service - Docker Application Container Engine
   Loaded: loaded (/usr/lib/systemd/system/docker.service; disabled; vendor preset: disabled)
   Active: inactive (dead)
     Docs: https://docs.docker.com
```

`/usr/lib/systemd/system/docker.service`の中身を確認すると
`--containerd=/run/containerd/containerd.sock`とランタイムにcontainerdのソケットファイルが設定されていることが分かります。


```shell:serviceファイルの確認
[root@ip-172-31-7-118 ~]# cat /usr/lib/systemd/system/docker.service
[Unit]
Description=Docker Application Container Engine
Documentation=https://docs.docker.com
BindsTo=containerd.service
After=network-online.target firewalld.service containerd.service
Wants=network-online.target
Requires=docker.socket

[Service]
Type=notify
# the default is not to use systemd for cgroups because the delegate issues still
# exists and systemd currently does not support the cgroup feature set required
# for containers run by docker
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
ExecReload=/bin/kill -s HUP $MAINPID
TimeoutSec=0
RestartSec=2
Restart=always

～～～ 以下略 ～～～
```

```shell:ディレクトリ作成
[root ~]# mkdir /etc/docker
```

```shell:デーモンのセットアップ
[root@ip-172-31-7-118 ~]# cat > /etc/docker/daemon.json <<EOF
> {
>   "exec-opts": ["native.cgroupdriver=systemd"],
>   "log-driver": "json-file",
>   "log-opts": {
>     "max-size": "100m"
>   },
>   "storage-driver": "overlay2",
>   "storage-opts": [
>     "overlay2.override_kernel_check=true"
>   ]
> }
> EOF
```

```shell:ディレクトリ作成
[root@ip-172-31-7-118 ~]# mkdir -p /etc/systemd/system/docker.service.d
```

dockerデーモンの設定を再読み込みし、サービスを起動します。

```shell:dockerデーモンの起動
[root@ip-172-31-7-118 ~]# systemctl daemon-reload
[root@ip-172-31-7-118 ~]# systemctl enable --now docker
Created symlink from /etc/systemd/system/multi-user.target.wants/docker.service to /usr/lib/systemd/system/docker.service.
```

dockerのサービスが起動していることを確認します。
`Active: active (running)`とあれば問題なく起動されています。

```shell:dockerのサービスが起動していることを確認
[root@ip-172-31-7-118 ~]# systemctl status docker
● docker.service - Docker Application Container Engine
   Loaded: loaded (/usr/lib/systemd/system/docker.service; enabled; vendor preset: disabled)
   Active: active (running) since Sun 2021-08-08 13:39:09 UTC; 5s ago
     Docs: https://docs.docker.com
 Main PID: 28157 (dockerd)
    Tasks: 10
   Memory: 37.8M
   CGroup: /system.slice/docker.service
           └─28157 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
```

# 4. kubeadm、kubelet、kubectlのインストール

[kubeadmのインストール](https://kubernetes.io/ja/docs/setup/production-environment/tools/kubeadm/install-kubeadm/)に従ってkubeadm、kubelet、kubectlをインストールします。
こちらもほとんどコマンドのコピペ作業になります。

初めにyumレポジトリの登録をします。

```shell:yumレポジトリの登録
[root@ip-172-31-7-118 ~]# cat <<EOF > /etc/yum.repos.d/kubernetes.repo
> [kubernetes]
> name=Kubernetes
> baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64
> enabled=1
> gpgcheck=1
> repo_gpgcheck=1
> gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
> EOF 
```

```shell:SELinuxをpermissiveモードに設定
[root@ip-172-31-7-118 ~]# setenforce 0
[root@ip-172-31-7-118 ~]# sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config
```

kubeadm、kubelet、kubectlのパッケージをインストールします。

```shell:パッケージインストール
[root@ip-172-31-7-118 ~]# yum install -y kubelet kubeadm kubectl --disableexcludes=kubernetes
```

インストールが完了したらkubeletのサービスが読み込まれていることを確認します。

```shell:kubeletのサービスの確認
[root@ip-172-31-7-118 ~]# systemctl status kubelet
● kubelet.service - kubelet: The Kubernetes Node Agent
   Loaded: loaded (/usr/lib/systemd/system/kubelet.service; disabled; vendor preset: disabled)
  Drop-In: /usr/lib/systemd/system/kubelet.service.d
           └─10-kubeadm.conf
   Active: inactive (dead)
     Docs: https://kubernetes.io/docs/
```

kubeletのサービスを起動します。

```shell:kubeletのサービス起動
[root@ip-172-31-7-118 ~]# systemctl enable --now kubelet
Created symlink from /etc/systemd/system/multi-user.target.wants/kubelet.service to /usr/lib/systemd/system/kubelet.service.
```

ステータスを確認すると失敗しているようですが、気にせず次の章（kubeadmでクラスタの初期化）へ移ります。

```
[root@ip-172-31-7-118 ~]# systemctl status kubelet
● kubelet.service - kubelet: The Kubernetes Node Agent
   Loaded: loaded (/usr/lib/systemd/system/kubelet.service; enabled; vendor preset: disabled)
  Drop-In: /usr/lib/systemd/system/kubelet.service.d
           └─10-kubeadm.conf
   Active: activating (auto-restart) (Result: exit-code) since Sun 2021-08-08 14:00:15 UTC; 1s ago
     Docs: https://kubernetes.io/docs/
  Process: 4988 ExecStart=/usr/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_CONFIG_ARGS $KUBELET_KUBEADM_ARGS $KUBELET_EXTRA_ARGS (code=exited, status=1/FAILURE)
 Main PID: 4988 (code=exited, status=1/FAILURE)

Aug 08 14:00:14 ip-172-31-7-118.us-west-2.compute.internal systemd[1]: Unit kubelet.service entered failed state.
Aug 08 14:00:15 ip-172-31-7-118.us-west-2.compute.internal systemd[1]: kubelet.service failed.
```

# 5. kubeadmでクラスタの初期化

クラスタの初期化を行う前に一般ユーザとして利用したいので、`root`から`centos`へ切り替えます。

```shell:一般ユーザへ変更
[root@ip-172-31-7-118 ~]# exit
logout
[centos@ip-172-31-7-118 ~]$ 
```

kubeadmコマンドでクラスタの初期化を行います。
[kubeadmを使用したクラスターの作成](https://kubernetes.io/ja/docs/setup/production-environment/tools/kubeadm/create-cluster-kubeadm/)に説明があります。

ネットワークアドオンとしてflannelを利用するため（後述）ここでは`--pod-network-cidr=10.244.0.0/16`をオプションに設定しています。

```shell:クラスタの初期化
[centos@ip-172-31-7-118 ~]$ sudo kubeadm init --apiserver-advertise-address $(hostname -i) --pod-network-cidr=10.244.0.0/16
～～～中略～～～

Your Kubernetes control-plane has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

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

Alternatively, if you are the root user, you can run:

  export KUBECONFIG=/etc/kubernetes/admin.conf

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
  https://kubernetes.io/docs/concepts/cluster-administration/addons/

Then you can join any number of worker nodes by running the following on each as root:

kubeadm join 172.31.7.118:6443 --token 539hwl.b1...........ak \
        --discovery-token-ca-cert-hash sha256:db513........................................2deb 
```

クラスタの初期化が完了しました。
今回は一般ユーザで利用しているため、
`To start using your cluster, you need to run the following as a regular user:`
と記載されている手順を実施します。

```shell:セットアップ
[centos@ip-172-31-7-118 ~]$ mkdir -p $HOME/.kube
[centos@ip-172-31-7-118 ~]$ sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
[centos@ip-172-31-7-118 ~]$ sudo chown $(id -u):$(id -g) $HOME/.kube/config
```

複数ノードでクラスタを構築している方は、
`Then you can join any number of worker nodes by running the following on each as root:`
に記載の通り、下記のコマンドをクラスタへ参加させたいノード上で実行することによりKubernetesクラスタへ参加させることができます。
今回はシングルノードクラスタなのでこの手順は実施しません。

```shell:クラスタへの参加コマンド
kubeadm join 172.31.7.118:6443 --token 539hwl.b1...........ak \
        --discovery-token-ca-cert-hash sha256:db513........................................2deb 
```

この時点で先ほど失敗していた`kubelet`のサービスが起動していることが確認できます。

```shell:kueletのさーびが起動していることを確認
[centos@ip-172-31-7-118 ~]$ systemctl status kubelet
● kubelet.service - kubelet: The Kubernetes Node Agent
   Loaded: loaded (/usr/lib/systemd/system/kubelet.service; enabled; vendor preset: disabled)
  Drop-In: /usr/lib/systemd/system/kubelet.service.d
           └─10-kubeadm.conf
   Active: active (running) since Sun 2021-08-08 14:04:31 UTC; 7min ago
     Docs: https://kubernetes.io/docs/
 Main PID: 6595 (kubelet)
    Tasks: 13
   Memory: 98.4M
   CGroup: /system.slice/kubelet.service
           └─6595 /usr/bin/kubelet --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf --config=/var/lib/kubelet/config.yaml --network-plugin=cni -...
```

また、`kubectl`コマンドでノード一覧を取得することもできます。
この時点ではまだ`NotReady`になっています。

```
[centos@ip-172-31-7-118 ~]$ kubectl get nodes
NAME                                         STATUS     ROLES                  AGE     VERSION
ip-172-31-7-118.us-west-2.compute.internal   NotReady   control-plane,master   8m54s   v1.22.0
```

`kubeadm init`で何か問題が発生したり、オプションの設定を修正したいときは`kubeadm reset`コマンドでリセットすることができます。

```shell:クラスタのリセット
[centos@ip-172-31-7-118 ~]$ kubeadm reset
```

[kubeadmを使用したクラスターの作成](https://kubernetes.io/ja/docs/setup/production-environment/tools/kubeadm/create-cluster-kubeadm/)にも記載がありますが、デフォルトではMasterノードにPodをスケジュールすることはできません。
そのため、下記のコマンドでMasterノードにもPodをスケジュールできるようにします。

```shell:
[centos@ip-172-31-7-118 ~]$ kubectl taint nodes --all node-role.kubernetes.io/master-
node/ip-172-31-7-118.us-west-2.compute.internal untainted
```

# 6. ネットワークアドオンの設定

ここではネットワークアドオンとして`flannel`を利用します。
[https://github.com/flannel-io/flannel](https://github.com/flannel-io/flannel)に記載の通り、下記コマンドでインストールを行います。

```shell:flannelのインストール
[centos@ip-172-31-7-118 ~]$ kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
Warning: policy/v1beta1 PodSecurityPolicy is deprecated in v1.21+, unavailable in v1.25+
podsecuritypolicy.policy/psp.flannel.unprivileged created
clusterrole.rbac.authorization.k8s.io/flannel created
clusterrolebinding.rbac.authorization.k8s.io/flannel created
serviceaccount/flannel created
configmap/kube-flannel-cfg created
daemonset.apps/kube-flannel-ds created
```

この時点でMasterノードが`Ready`になり、セットアップがすべて完了しました。

```shell:
[centos@ip-172-31-7-118 ~]$ kubectl get nodes
NAME                                         STATUS   ROLES                  AGE   VERSION
ip-172-31-7-118.us-west-2.compute.internal   Ready    control-plane,master   24m   v1.22.0
```

# 7. Nginxで動作確認

最後にNginxで動作確認を行います。
下記のファイルを作成します。
外部からアクセスできるようにServiceにNodePort 30000番を設定して公開しています。

```yaml:Pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: mypod
  labels:
    app: mynginx
spec:
  containers:
    - name: mynginx
      image: nginx:1.19.8-alpine

---

apiVersion: v1
kind: Service
metadata:
  name: myservice
spec:
  ports:
    - name: http
      port: 80
      protocol: TCP
      targetPort: 80
      nodePort: 30000
  selector:
    app: mynginx
  type: NodePort
```

`kubectl apply`してブラウザからアクセスしてみます。

```shell:PodとServiceの起動
[centos@ip-172-31-7-118 ~]$ kubectl apply -f Pod.yaml
pod/mypod created
service/myservice created
```

```shell:PodとServiceの起動確認
[centos@ip-172-31-7-118 ~]$ kubectl get pods,service
NAME        READY   STATUS    RESTARTS   AGE
pod/mypod   1/1     Running   0          76s

NAME                 TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
service/kubernetes   ClusterIP   10.96.0.1        <none>        443/TCP        31m
service/myservice    NodePort    10.108.194.192   <none>        80:30000/TCP   76s
```

![nginx_welcome.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/705474/f0f37519-2eb1-5439-c3fb-343d55508b47.png)

# 参考資料

[kubeadmのインストール](https://kubernetes.io/ja/docs/setup/production-environment/tools/kubeadm/install-kubeadm/)
[CRIのインストール](https://kubernetes.io/ja/docs/setup/production-environment/container-runtimes/)
[kubeadmを使用したクラスターの作成](https://kubernetes.io/ja/docs/setup/production-environment/tools/kubeadm/create-cluster-kubeadm/)
[https://github.com/flannel-io/flannel](https://github.com/flannel-io/flannel