Kubespray での Kubernetes Cluster 構築 (Ubuntu)

## はじめに
自宅サーバ環境で以前、[Kubespray で CentOS での構築](https://qiita.com/suzuyui/items/e7531fe5e1e84c061b23)を実施した

今回、Ubuntu (20.4 LTS) で再度構築してみたのでその内容を記載する
結論、kubespray 実施前の設定方法が OS の違いで若干違うだけでほとんど変わらずできた

## 環境
- ESXi
    - Version: 7.0u2a
    - license: Free
- Network (Server Gateway Router)
    - Edgerouter-X (ER-X)
    - Version: v2.0.9-hotfix.2 [記載時(2021.11.28)最新版]
        - 注意： 本稿には記載しないが、v1.x.xだと MetaLB との組み合わせで本環境では不具合を確認しているため、v2系が必要

上記以外の環境と、Kubernetes のノードスペック設定は過去に [CentOS べースで作成した際](https://qiita.com/suzuyui/items/e7531fe5e1e84c061b23#%E6%A7%8B%E6%88%90%E5%AE%9F%E8%A1%8C%E7%92%B0%E5%A2%83%E3%82%B9%E3%83%9A%E3%83%83%E3%82%AF)と同じ

Kubespray は [`v2.17.1`](https://github.com/kubernetes-sigs/kubespray/releases/tag/v2.17.1) を使用する

## VM 構築

ESXi での Ubuntu サーバ構築。VM構築のみ記載なので、環境が整っている場合は、この項目はスキップ可能

### Ubuntu Image 作成
ESXi で Ubuntu 20.04 LTS の VM を立てる

![スクリーンショット 2021-11-27 9.48.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/2c42d8c7-0548-2684-91f6-5f069c72f61a.png)

新規で VM を作成する

![スクリーンショット 2021-11-27 9.51.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/c8542f10-90e6-56de-40bc-a3f1551b7444.png)

VM 名称と、OS の種類を選択する

![スクリーンショット 2021-11-27 9.52.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/3faf3f63-6ded-f047-c94f-7b1c05f0c81c.png)

VM のストレージを選択する (ここではローカルの datastore1)

![スクリーンショット 2021-11-27 9.53.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/e0802d7d-715b-28aa-9210-068e185469a3.png)

VM のスペックを設定する
Disk はサイズ節約のため `Thin provisioned` にしている

![スクリーンショット 2021-11-27 9.54.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/81661f7a-ad94-2290-4938-8219b6cbf90e.png)

CD/DVD Media の `Browese...` をクリックして事前にアップロードした Ubuntu の ISO を選択する

![スクリーンショット 2021-11-27 9.55.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/a33de5ea-aec2-9406-ee7a-5531ffb67764.png)

![スクリーンショット 2021-11-27 9.56.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/8fb8e7df-6c6a-f76f-b3a0-2222d3629967.png)

入力した内容を確認して`Finish`を押して完了する

![スクリーンショット 2021-11-27 9.56.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/8ae4bcc5-c96c-6beb-f196-4909dacbe849.png)

起動すると `Network adapter` に `MAC Address` がアサインされる

![スクリーンショット 2021-11-27 10.13.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/c4535e84-a462-d17d-ce19-00d741aaf431.png)

Gateway の DHCP でアドレス管理をしている場合は、上記 MAC Address を参照して、登録する

下記は、ER-X の際の MAC アドレスとアサイン IP アドレスの設定組み合わせの抜粋例

```
set service dhcp-server shared-network-name TRUST_POOL subnet 192.168.129.0/24 static-mapping nuc02_k8master01 ip-address 192.168.129.21
set service dhcp-server shared-network-name TRUST_POOL subnet 192.168.129.0/24 static-mapping nuc02_k8master01 mac-address 00:0c:29:75:57:eb
```

起動後は VM のコンソール画面で設定していく

表示言語は English としている

![スクリーンショット 2021-11-27 10.15.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/1a2dd387-7832-3fa4-dae2-eba0a7ddf815.png)

keyboard は Japanese を選択する

![スクリーンショット 2021-11-27 10.16.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/59059d6c-a418-a2ff-24e2-768713c7ab17.png)

DHCP 設定に従って、下記アドレスが表示される (VM 自体で固定IPを設定する場合はここで設定可能)

![スクリーンショット 2021-11-27 10.16.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/1ac8b308-0d51-94fa-301c-72a02886e479.png)

Proxy 環境でなければそのまま進む

![スクリーンショット 2021-11-27 10.17.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/9c2b21d0-9daf-9927-3386-53597e1bb306.png)

Mirror を変更しないならそのまま進む

![スクリーンショット 2021-11-27 10.17.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/5d5851e6-b3e7-be9a-b9f6-d346a4287a70.png)

Install するストレージを選択して再構築する

![スクリーンショット 2021-11-27 10.18.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/0cdc1acb-67d7-a9f8-45b8-5f188edad651.png)

![スクリーンショット 2021-11-27 10.18.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/7189daaf-ab2e-2811-534c-9186066752c6.png)

![スクリーンショット 2021-11-27 10.18.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/fb6c14bd-81dd-d11b-07ad-7eb27b3f6f39.png)

ユーザ名やホスト名を登録する (ホスト名は後ほど変更もする)

![スクリーンショット 2021-11-27 10.19.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/3bb87f90-8bd5-66c6-48d3-bcb8226ad1d6.png)

SSH でログインできるように OpenSSH はインストールするようにスペースで `X` をつけて進む

![スクリーンショット 2021-11-27 10.19.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/ba251a2d-c7fa-90ed-9a44-312f3b6bd792.png)

初期インストールが終われば、`Reboot Now` が出てくるので選択して進める

![スクリーンショット 2021-11-27 10.23.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/03b442ff-5cf9-8e6f-64e1-ee1bde787dcf.png)

`Enter`を押して完了させる

![スクリーンショット 2021-11-27 10.23.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/2c40788d-826b-72d5-5fd5-a7fada79c488.png)

以上で、ベースのイメージを作成完了

### 台数分のコピー

フリーの ESXi を使っているので、VM イメージのクローンができないので、
単純に一旦 Export して Import する方法でコピーする

作成した VM (Ubuntu) で `Export`を選択する

![スクリーンショット 2021-11-27 10.29.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/600abf8d-1ec2-f8cb-7d66-eda7377343ef.png)

ファイルのダウンロードになるのでダウンロードする

![スクリーンショット 2021-11-27 10.29.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/e4038927-939f-72b3-5032-8a94a34ea40c.png)

3ファイルダウンロードすることになる

![スクリーンショット 2021-11-27 10.31.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/e5cff7b0-de37-135d-a284-4555afcf227c.png)

ダウンロードしたファイルを使って VM を作成する (下記、台数分繰り返す。ホスト名は後で修正するのでここでの修正は不要)

![スクリーンショット 2021-11-27 10.31.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/e00fa6a1-e0fe-8cca-de38-a56975f557ef.png)

ホスト名を入力して、ダウンロードしたファイルをアップロードする

![スクリーンショット 2021-11-27 10.50.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/ee8cfead-90a9-6e52-a68c-2a0dd8d5b413.png)

VM で使用するストレージを選択する

![スクリーンショット 2021-11-27 10.34.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/d7bef9f4-e82e-7cf9-507e-a0a959b581e9.png)

VMのスペックを変える場合は、`Power on automatically`のチェックを外して、作成してから Edit で編集して修正してから VM を立ち上げる

![スクリーンショット 2021-11-27 10.35.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/b29acce4-fbf0-d6e0-06a1-75c1de547a21.png)

Finish を押して完了する

![スクリーンショット 2021-11-27 10.36.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/2e8c3f8b-0b26-d060-4529-58c6d735fc6d.png)

上記ノード分をコピーして作成する

## Kubespray 構築

### kubespray git pull

git clone で [kubespray](https://github.com/kubernetes-sigs/kubespray) をダウンロードする

```
git clone https://github.com/kubernetes-sigs/kubespray.git
```

利用するバージョンに checkout して、必要な python モジュールをインストールして、inventory のサンプルをコピーしておく

```
cd kubespray
git checkout v2.17.1
sudo pip3 install -r requirements.txt
cp -r inventory/sample inventory/mycluster
```

次に、環境用の inventory を用意する

### inventory

今回は Calico で [Route Reflector (RR)](https://kubespray.io/#/docs/calico?id=optional-bgp-peering-with-route-reflectors) を使う構成で実施した
本環境で使用する inventory ファイルの記載例は下記の通り

```yaml:~/kubespray/inventory/mycluster/hosts.yaml
all:
  hosts:
    k8smaster01:
      ansible_host: 192.168.129.21
      ip: 192.168.129.21
      access_ip: 192.168.129.21
    k8smaster02:
      ansible_host: 192.168.129.31
      ip: 192.168.129.31
      access_ip: 192.168.129.31
    k8smaster03:
      ansible_host: 192.168.129.22
      ip: 192.168.129.22
      access_ip: 192.168.129.22
    k8sworker01:
      ansible_host: 192.168.129.23
      ip: 192.168.129.23
      access_ip: 192.168.129.23
    k8sworker02:
      ansible_host: 192.168.129.32
      ip: 192.168.129.32
      access_ip: 192.168.129.32
    k8sworker03:
      ansible_host: 192.168.129.33
      ip: 192.168.129.33
      access_ip: 192.168.129.33
    k8scalicorr01:
      ansible_host: 192.168.129.24
      ip: 192.168.129.24
      access_ip: 192.168.129.24
    k8scalicorr02:
      ansible_host: 192.168.129.34
      ip: 192.168.129.34
      access_ip: 192.168.129.34
  children:
    kube-master:
      hosts:
        k8smaster01:
        k8smaster02:
        k8smaster03:
    kube-node:
      hosts:
        k8sworker01:
        k8sworker02:
        k8sworker03:
    etcd:
      hosts:
        k8smaster01:
        k8smaster02:
        k8smaster03:
    k8s-cluster:
      children:
        kube-master:
        kube-node:
        calico-rr:
    calico-rr:
      hosts:
        k8scalicorr01:
        k8scalicorr02:
    rack0:
      hosts:
        k8scalicorr01:
        k8scalicorr02:
        k8smaster01:
        k8smaster02:
        k8smaster03:
        k8sworker01:
        k8sworker02:
        k8sworker03:
      vars:
        cluster_id: "1.0.0.1"
```

### kubespray 事前設定

Kubespray の [Requirements](https://github.com/kubernetes-sigs/kubespray/blob/master/README.md#requirements) の下記抜粋から、OSのファイアウォールを落としておけば良さそう

- The target servers are configured to allow IPv4 forwarding.
    - roles/kubernetes/preinstall で sysctl 設定してそうなので事前設定はしない
- If using IPv6 for pods and services, the target servers are configured to allow IPv6 forwarding.
    - roles/kubernetes/preinstall で sysctl 設定してそうなので事前設定はしない
- The firewalls are not managed, you'll need to implement your own rules the way you used to. in order to avoid any issue during deployment you should disable your firewall.
    - firewall を落とす設定を実施する

上記を踏まえ下記を実施する

- packages の事前アップデート
- 必要になる `sshpass`の事前インストール
- 上記にある`ufw`(Ubuntu搭載のファイアウォール)の無効化
- Ansible 実行などのために事前に実行サーバのローカルユーザの ssh key をノードへ登録 (事前に `ssh-keygen -t rsa` などで作成しておく必要あり(以後実行は no pass前提で記載))
- ホスト名を inventory 名に修正 (VMコピーしたのでVM内のホスト名が同じになっているため)
- リブート

下記事前セットアップ用の Ansible Role 例

```yaml:/etc/ansible/roles/kubespray-presetup/tasks/main.yml

  - name: kubespray-presetup / Update apt packages
    apt:
      name: "*"
      state: latest
    become: yes
    when:
     - ansible_distribution == "Ubuntu"

  - name: kubespray-presetup / install sshpass when Ubuntu
    apt:
      name: "{{ packages }}"
    vars:
      packages:
        - sshpass
      state: present
    become: yes
    when:
     - ansible_distribution == "Ubuntu"

  - name: kubespray-presetup / stop and disable ufw Ubuntu
    systemd:
      name: ufw
      state: stopped
      enabled: false
    become: yes
    when:
     - ansible_distribution == "Ubuntu"

  - name: kubespray-presetup / Username Ubuntu
    set_fact:
      LOCAL_USER: "{{lookup('env', 'USER')}}"
    when:
     - ansible_distribution == "Ubuntu"

  - name: kubespray-presetup / copy users ssh key to local user authorized key Ubuntu
    authorized_key:
      user: "{{ LOCAL_USER }}"
      key: "{{ lookup('file', '/home/{{ LOCAL_USER }}/.ssh/id_rsa.pub') }}"
    become: yes
    when:
     - ansible_distribution == "Ubuntu"

  - name: kubespray-presetup / change hostname
    hostname:
      name: "{{ inventory_hostname }}"
    become: yes

  - name: kubespray-presetup / reboot Ubuntu
    reboot:
    become: yes
    when:
     - ansible_distribution == "Ubuntu"
```

上記ロールを使用する playbook などを用意する (他にユーザ作成などもあれば合わせておく)

```yaml:~/playbook/playbook_preset_kubespray_nodes.yml
--- 
- hosts: all
  become: true
  gather_facts: yes

  roles:
    - kubespray-presetup
```

下記のように playbook を実施して、事前設定を適用する

```
ansible-playbook -i ~/kubespray/inventory/mycluster/hosts.yaml playbook_preset_kubespray_nodes.yml -Kk
```

### kubespray 実行

あとは kubespray のディレクトリへ移動して実行するだけ

```
cd ~/kubespray
```

構築は`cluster.yml`を使用して [Usage](https://github.com/kubernetes-sigs/kubespray#usage) にある通り、下記コマンドで実行するのみ

```
ansible-playbook -i inventory/mycluster/hosts.yaml -K --become --become-user=root cluster.yml
```

20~30分くらい待つと構築完了する (Timeout などで Error 時は時間がかかっている場合もあるので、再実施でうまくいく時もある)

マスターノードへログインして、`root`ユーザで構築されたことを確認する

```
root@k8master01:~# kubectl get node
NAME           STATUS   ROLES                  AGE   VERSION
k8calicorr01   Ready    <none>                 29m   v1.21.6
k8calicorr02   Ready    <none>                 29m   v1.21.6
k8master01     Ready    control-plane,master   31m   v1.21.6
k8master02     Ready    control-plane,master   30m   v1.21.6
k8master03     Ready    control-plane,master   30m   v1.21.6
k8worker01     Ready    <none>                 29m   v1.21.6
k8worker02     Ready    <none>                 29m   v1.21.6
k8worker03     Ready    <none>                 29m   v1.21.6
```

### .kube/config 設定

[ドキュメント](https://github.com/kubernetes-sigs/kubespray/blob/master/docs/setting-up-your-first-cluster.md#access-the-kubernetes-cluster)にある通り、kubeconfig を master ノードの`/etc/kubernetes/admin.conf`から取得する
(もしくは`kubespray/inventory/mycluster/group_vars/k8s_cluster/k8s-cluster.yml`に`kubeconfig_localhost: yes`にしておくと`inventory/mycluster/artifacts/`に`admin.conf`が入った状態になる[ドキュメント](https://github.com/kubernetes-sigs/kubespray/blob/master/docs/getting-started.md#accessing-kubernetes-api))

下記、Ansible コード例

```yaml:/etc/ansible/roles/kubespray-get-adminconf/tasks/main.yml
---
# tasks file for kubespray-get-adminconf

## https://github.com/kubernetes-sigs/kubespray/blob/master/docs/setting-up-your-first-cluster.md#access-the-kubernetes-cluster

  - name: kubespray-get-adminconf / Username Ubuntu
    set_fact:
      USER: "{{lookup('env', 'USER')}}"
    when:
     - ansible_distribution == "Ubuntu"

  - name: kubespray-get-adminconf / cp admin.conf USER
    copy:
      src:   "/etc/kubernetes/admin.conf"
      dest:  "/home/{{ USER }}/admin.conf"
      owner: "{{ USER }}"
      group: "{{ USER }}"
      remote_src: yes
    become: yes
    when:
     - ansible_distribution == "Ubuntu"

  - name: kubespray-get-adminconf / mkdir .kube
    file:
      path: "/home/{{ USER }}/.kube"
      state: directory
      mode: '0755'
    when:
     - ansible_distribution == "Ubuntu"

  - name: kubespray-get-adminconf / cp admin.conf to .kube/config
    copy:
      src:   "/home/{{ USER }}/admin.conf"
      dest:  "/home/{{ USER }}/.kube/config"
      backup: yes
      remote_src: yes
    when:
     - ansible_distribution == "Ubuntu"

  - name: kubespray-get-adminconf / mkdir .kube localhost
    file:
      path: "/home/{{ USER }}/.kube"
      state: directory
      mode: '0755'
    delegate_to: localhost
    when:
     - ansible_distribution == "Ubuntu"

  - name: kubespray-get-adminconf / get .kube/config get remote form admin
    fetch:
      src:   "/home/{{ USER }}/.kube/config"
      dest:  "/tmp/.kube/config"
      flat: yes
    when:
     - ansible_distribution == "Ubuntu"

  - name: kubespray-get-adminconf / cp to .kube/config localhost
    copy:
      src:   "/tmp/.kube/config"
      dest:  "/home/{{ USER }}/.kube/config"
      backup: yes
    delegate_to: localhost
    when:
     - ansible_distribution == "Ubuntu"

  - name: kubespray-get-adminconf / replace 127.0.0.1 to kube-master node 1 ip
    replace:
      path: "/home/{{ USER }}/.kube/config"
      regexp: "server: https://127.0.0.1:6443"
      replace: "server: https://{{ hostvars['k8master01']['access_ip'] }}:6443"
    delegate_to: localhost
    when:
     - ansible_distribution == "Ubuntu"
```

```yaml:playbook.yml例
--- 
- hosts: k8master01
  become: true
  gather_facts: yes

  roles:
    - kubespray-get-adminconf
```

```
ansible-playbook -i ../kubespray/inventory/mycluster/hosts.yaml playbook_kubespray-get-adminconf.yml -K
```

実行サーバから kubectl でアクセスできるようになったことを確認する

```
% kubectl get node
NAME           STATUS   ROLES                  AGE   VERSION
k8calicorr01   Ready    <none>                 16h   v1.21.6
k8calicorr02   Ready    <none>                 16h   v1.21.6
k8master01     Ready    control-plane,master   16h   v1.21.6
k8master02     Ready    control-plane,master   16h   v1.21.6
k8master03     Ready    control-plane,master   16h   v1.21.6
k8worker01     Ready    <none>                 16h   v1.21.6
k8worker02     Ready    <none>                 16h   v1.21.6
k8worker03     Ready    <none>                 16h   v1.21.6
```

以上で、構築からアクセス設定まで完了

## まとめ
ESXi / Ubuntu 20.04 LTS 環境上で、 Kubespray を使用して Kubernetes 環境構築を実施した
今後、Ubuntu 22.04 LTS リリースされて Kuberspray がサポートされたら、載せ替えを実施して、CentOS のサポート切れ対応を実施したい