KVMでおうちKubernetes

ぷりぷりあぷりけーしょんずインフラ担当による [ぷりぷりあぷりけーしょんず Advent Calendar 2019](https://qiita.com/advent-calendar/2019/ppap) の3日目
# 1.目標
これを作ります。

- Master1台、Worker2台
- CNIにはCalicoを利用してPod間通信
- NginxPodをデプロイしてPod間通信を検証
![kvm_k8s.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/537267/33f3b14d-32ba-e33e-9e9f-190513c0a2fa.png)

# 2.環境
## ホストOS
- メモリ：16G
- OS：Ubuntu18.04.2
- KVM:

```
$ virsh version
Compiled against library: libvirt 4.0.0
Using library: libvirt 4.0.0
Using API: QEMU 4.0.0
Running hypervisor: QEMU 2.11.1
```

## ゲストOS(Master,Worker01,Worker02)
- メモリ：4G
- OS：Ubuntu18.04.2
- Kubernetes：1.15.3

# 3.仮想マシンの作成

## 3.1.必要パッケージのインストール

`$ sudo apt install qemu-kvm libvirt-bin virtinst bridge-utils libosinfo-bin libguestfs-tools virt-top`

## 3.2.仮想マシンの作成

### 3.2.1.インスタンスイメージの作成

```
sudo virt-install \
    --name master \
    --ram 4096 \
    --vcpus 2 \
    --disk path=/var/lib/libvirt/images/master.img,size=20,format=qcow2 \
    --os-type linux \
    --os-variant ubuntu18.04 \
    --network bridge=virbr0 \
    --graphics none \
    --console pty,target_type=serial \
    --location 'http://jp.archive.ubuntu.com/ubuntu/dists/bionic/main/installer-amd64/' \
    --extra-args 'console=ttyS0,115200n8 serial'
```

### 3.2.2.仮想マシンのコンソールへアクセスする設定

最終的には仮想マシンへSSHで接続しますが、SSHやNWの基本設定の操作をするために下記の手順を踏みます。

`$ sudo virsh shutdown master`

`$ sudo guestmount -d master -i /mnt`

`$ sudo ln -s /mnt/lib/systemd/getty@.service /mnt/etc/systemd/system/getty.target.wants/getty@ttyS0.service`

`$ sudo umount /mnt`

### 3.2.3.仮想マシンの諸設定

- 仮想マシンのIPアドレスを手動で設定します

    ホストOSで以下を実行

    `$ sudo virsh start master --console`

    ゲストOSへログイン後以下を実行

    `$ sudo su -`

    `# vi /etc/netplan/01-netcfg.yaml`

    ```
    # This file describes the network interfaces available on your system
    # For more information, see netplan(5).
    network:
    version: 2
    renderer: networkd
    ethernets:
        ens2:
        dhcp4: false
        addresses:
        - 192.168.122.2/24
        gateway4: 192.168.122.1
        nameservers:
            addresses:
            - 192.168.122.1
    ```

    `# netplan apply`

- Kubernetesを利用可能にする

    `# swapoff -a`

    `# vi /etc/fstab`

    ```
    ## 以下の行をコメントアウト
    /swapfile                                 none            swap    sw              0       0
    ```

    `# vi /etc/hosts`

    ```
    # 作成した仮想マシンの情報を追記
    # IPアドレスは各自読み替えてください
    192.168.122.2 master
    192.168.122.3 node01
    192.168.122.4 node02
    ```

    `# echo net.bridge.bridge-nf-call-iptables = 1 >> /etc/sysctl.conf`

    `# echo net.bridge.bridge-nf-call-ip6tables = 1 >> /etc/sysctl.conf`

    `# echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf`

またこの際にホストOSから仮想マシンへSSHするユーザを作成したり、SSHのための公開鍵のコピーを行なってください。

### 3.2.4.仮想マシンへDocker,Kubernetesのインストール

上記に引き続き仮想マシンへログインした状態で作業してください。

公式サイトにある手順を実施していますが、バージョンを明示するため冗長ですが手順を示します。

- Docker

    `# apt-get install apt-transport-https ca-certificates curl gnupg-agent software-properties-common`

    `# curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -`

    `# apt-key fingerprint 0EBFCD88`

    `# add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"`

    `# apt-get update`

    `# apt-get install docker-ce=5:18.09.9~3-0~ubuntu-bionic docker-ce-cli=5:18.09.9~3-0~ubuntu-bionic containerd.io`

- Kubernetes

    `# 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 -`

    `# echo "deb https://apt.kubernetes.io/ kubernetes-xenial main" | tee /etc/apt/sources.list.d/kubernetes.list`

    `# apt-get update`

    `# apt-get install -y kubelet kubeadm kubectl`

    `# apt-mark hold kubelet kubeadm kubectl`

## 3.3.仮想マシンを複製

ここまでの手順で作成したMaster用仮想マシンを雛形として、Worker用の仮想マシンを作成します。

`$ sudo virsh shutdown master`

`$ sudo virt-clone --original master --name node01 --file /var/lib/libvirt/images/node01.img`

これでWorker用仮想マシンが作成されたので3.2.3.の`IPアドレスの手動設定`だけして、仮想マシンの作成は終了です。

# 4.Kubernetesクラスタ作成

## Masterノード

Master用仮想マシンへログインし、以下のコマンドを実行します。

`--pod-network-cidr`オプションはPodの所属するネットワークの指定に利用します。Calicoのデフォルトが`192.168.0.0/16`でKVMのネットワークとバッティングするため`10.x.x.x/16`で設定すると良いでしょう。

`$ sudo kubeadm init --apiserver-advertise-address=192.168.122.2 --pod-network-cidr=10.64.0.0/16`

```
(前略)
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

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 192.168.122.2:6443 --token 0bno9n.2h84n6b0nhl71iyy \
    --discovery-token-ca-cert-hash sha256:5fc061867c95d29841675ec575af7787a84316167a5b92e73b7f7a483579c100 
```

コマンドの結果にある、`mkdir~chown`までのコマンドを実行することで`kubectl`によるKubernetesの操作が可能になります。

## Workerノード

Masterノードで実行したコマンドの結果の最終行にあるコマンドを各Workerノードで実行します。

`$ sudo kubeadm join 192.168.122.2:6443 --token 0bno9n.2h84n6b0nhl71iyy --discovery-token-ca-cert-hash sha256:5fc061867c95d29841675ec575af7787a84316167a5b92e73b7f7a483579c100`

## クラスタの確認

Masterノードで下記のコマンドを実行してNodeの状態を確認します。

`$ kubectl get nodes`

```
NAME     STATUS     ROLES    AGE    VERSION
master   NotReady   master   101s   v1.15.3
node01   NotReady   <none>   21s    v1.15.3
node02   NotReady   <none>   5s     v1.15.3
```

CNIを導入していないのでNodeのStatusが`NotReady`ですが、kubectlでNodeの情報を取得できていることがわかります。

# 5.Calicoのインストール
## Calicoとは

DockerでデプロイしたコンテナはそのDockerのあるホスト上でのみ通信可能です。(緑矢印)

複数のホスト上でDockerを構築した場合、コンテナが外方向で通信可能な範囲は他のホストのインターフェイスまでになります。(青矢印)

ネットワークの異なる他のホストのコンテナへはアクセスできません。(紫矢印)

![kvm_k8s-ページ2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/537267/6ab857a6-a3d6-80af-91f5-8ad73e1bff49.png)

Calicoを利用することで、Kubernetesクラスタ作成時に指定したpod-network-cidrのネットワーク内でコンテナ(Pod)間の通信が可能になります。

コンテナのIPアドレスの共有はetcdとBGPを利用します。

## etcdのインストール

Calicoの設定情報やIPや各コンテナのIPを保存するためのetcdPodをデプロイします。

`$ wget https://docs.projectcalico.org/v3.5/getting-started/kubernetes/installation/hosted/etcd.yaml`

`$ kubectl apply -f etcd.yaml`

## Calicoのインストール

`$ wget https://docs.projectcalico.org/v3.5/getting-started/kubernetes/installation/hosted/calico.yaml`

`$ vi calico.yaml`

IPIPモードをOFFに、Calicoの利用するネットワークの設定にpod-network-cidrで指定した値を書き込みます。

```
217             - name: CALICO_IPV4POOL_IPIP
218               value: "off"

225             # The default IPv4 pool to create on startup if none exists. Pod IPs will be           
226             # chosen from this range. Changing this value after installation will have
227             # no effect. This should fall within `--cluster-cidr`.
228             - name: CALICO_IPV4POOL_CIDR
229               value: "10.64.0.0/16"
```

`$ kubectl apply -f calico.yaml`

```
configmap/calico-config created
secret/calico-etcd-secrets created
daemonset.extensions/calico-node created
serviceaccount/calico-node created
deployment.extensions/calico-kube-controllers created
serviceaccount/calico-kube-controllers created
clusterrole.rbac.authorization.k8s.io/calico-kube-controllers created
clusterrolebinding.rbac.authorization.k8s.io/calico-kube-controllers created
clusterrole.rbac.authorization.k8s.io/calico-node created
clusterrolebinding.rbac.authorization.k8s.io/calico-node created
```

`$ kubectl get pods -A`

Calicoのコンポーネントがデプロイされていることがわかります。

またCalicoをデプロイするまではステータスがPendingであった、`coredns`PodもRunningになっています。

```
NAMESPACE     NAME                                       READY   STATUS    RESTARTS   AGE
kube-system   calico-etcd-4m6k2                          1/1     Running   0          73s
kube-system   calico-kube-controllers-5d57bd4b96-4bj8s   1/1     Running   1          100s
kube-system   calico-node-jmsmd                          1/1     Running   2          100s
kube-system   calico-node-mhlnk                          1/1     Running   1          100s
kube-system   calico-node-v9ll2                          1/1     Running   1          100s
kube-system   coredns-5c98db65d4-j2h6b                   1/1     Running   0          39m
kube-system   coredns-5c98db65d4-x2shs                   1/1     Running   0          39m
kube-system   etcd-master                                1/1     Running   0          38m
kube-system   kube-apiserver-master                      1/1     Running   0          38m
kube-system   kube-controller-manager-master             1/1     Running   0          38m
kube-system   kube-proxy-2xggm                           1/1     Running   0          38m
kube-system   kube-proxy-j9j9m                           1/1     Running   0          38m
kube-system   kube-proxy-p6xnw                           1/1     Running   0          39m
kube-system   kube-scheduler-master                      1/1     Running   0          38m
```

# 6.Pod間通信の確認

冒頭にある通り、NginxPodを利用してPod間通信の確認をしていきます。

## Nginxのデプロイ

`$ kubectl run my-nginx --image=nginx --replicas=2 --port=80`

```
kubectl run --generator=deployment/apps.v1 is DEPRECATED and will be removed in a future version. Use kubectl run --generator=run-pod/v1 or kubectl create instead.
deployment.apps/my-nginx created
```

`$ kubectl get pods -o wide`

Worker01,Worker02のそれぞれにNginxがデプロイされています。

Podの持つIPアドレスがCalicoで指定したネットワークになっていることも確認できます。

```
NAME                        READY   STATUS    RESTARTS   AGE   IP              NODE     NOMINATED NODE   READINESS GATES
my-nginx-756fb87568-f9bvd   1/1     Running   0          17h   10.64.196.130   node01   <none>           <none>
my-nginx-756fb87568-gpg99   1/1     Running   0          17h   10.64.140.64    node02   <none>           <none>
```

## 疎通確認

- curl用Podのデプロイ

    `$ kubectl run curl --image=radial/busyboxplus:curl -i --tty --rm`

    別ターミナルでMasterノードへログイン後`$ kubectl get pods`を実行するとcurl用Podがデプロイされたことがわかります。

    今回はWorker02にデプロイされました。

    ```
    NAME                        READY   STATUS    RESTARTS     AGE   IP              NODE     NOMINATED NODE   READINESS GATES
    curl-6bf6db5c4f-vfwfd       1/1     Running   0          47s   10.64.140.65    node02   <none>           <none>
    my-nginx-756fb87568-f9bvd   1/1     Running   0          17h   10.64.196.130   node01   <none>           <none>
    my-nginx-756fb87568-gpg99   1/1     Running   0          17h   10.64.140.64    node02   <none>           <none>
    ```

- 疎通確認

    `[ root@curl-6bf6db5c4f-vfwfd:/ ]$ curl -I 10.64.196.130`

    ```
    HTTP/1.1 200 OK
    Server: nginx/1.17.6
    Date: Sun, 24 Nov 2019 07:38:15 GMT
    Content-Type: text/html
    Content-Length: 612
    Last-Modified: Tue, 19 Nov 2019 12:50:08 GMT
    Connection: keep-alive
    ETag: "5dd3e500-264"
    Accept-Ranges: bytes
    ```

    `[ root@curl-6bf6db5c4f-vfwfd:/ ]$ curl -I 10.64.140.64`

    ```
    HTTP/1.1 200 OK
    Server: nginx/1.17.6
    Date: Sun, 24 Nov 2019 07:42:30 GMT
    Content-Type: text/html
    Content-Length: 612
    Last-Modified: Tue, 19 Nov 2019 12:50:08 GMT
    Connection: keep-alive
    ETag: "5dd3e500-264"
    Accept-Ranges: bytes
    ```

    同一ホスト(Worker02)のPodはもちろん、異なるホスト(Worker01)への疎通についても確認できました。

## Calicoの確認

Calicoの説明箇所で`etcd`と`BGP`を利用していると書きましたが、それの確認をしてみます。

### etcdctlのインストール

- calico-etcdのバージョン確認

    `$ curl http://10.96.232.136:6666/version | jq .`

    ```
    % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                    Dload  Upload   Total   Spent    Left  Speed
    100    44  100    44    0     0  11000      0 --:--:-- --:--:-- --:--:-- 14666
    {
    "etcdserver": "3.3.9",
    "etcdcluster": "3.3.0"
    }
    ```
- etcdctlのインストール

    `$ ETCD_VER=v3.3.9`

    `$ GOOGLE_URL=https://storage.googleapis.com/etcd`

    `$ DOWNLOAD_URL=${GOOGLE_URL}`

    `$ curl -L ${DOWNLOAD_URL}/${ETCD_VER}/etcd-${ETCD_VER}-linux-amd64.tar.gz -o etcd-${ETCD_VER}-linux-amd64.tar.gz`

    `$ tar zxvf etcd-v3.3.9-linux-amd64.tar.gz`

    `$ sudo mv etcd-v3.3.9-linux-amd64/etcdctl /usr/local/bin/`

- etcdctlのセットアップ

    calico-etcdがVersion3を利用しているため、etcdctlでもVersion3を利用するように設定します。

    --endpointsオプションには先ほどデプロイしたetcd.yamlにある`ClusterIP`を指定します。

    `$ alias etcd='ETCDCTL_API=3 etcdctl --endpoints=10.96.232.136:6666'`

    `$ etcd version`

    ```
    etcdctl version: 3.3.9
    API version: 3.3
    ```

### calico-etcdの確認

下記のコマンドでetcdに格納されているキーの一覧を取得可能です。

`$ etcd get / --prefix --keys-only`

先ほどデプロイしたNginxPodの情報についても格納されていることがわかります(JSONは整形してあります)。

`$ etcd get /calico/resources/v3/projectcalico.org/workloadendpoints/default/node01-k8s-my--nginx--756fb87568--f9bvd-eth0`

```
{
  "kind": "WorkloadEndpoint",
  "apiVersion": "projectcalico.org/v3",
  "metadata": {
    "name": "node01-k8s-my--nginx--756fb87568--f9bvd-eth0",
    "generateName": "my-nginx-756fb87568-",
    "namespace": "default",
    "uid": "e2b350ab-0df7-11ea-a8fd-525400f19390",
    "creationTimestamp": "2019-11-23T13:48:09Z",
    "labels": {
      "pod-template-hash": "756fb87568",
      "projectcalico.org/namespace": "default",
      "projectcalico.org/orchestrator": "k8s",
      "projectcalico.org/serviceaccount": "default",
      "run": "my-nginx"
    }
  },
  "spec": {
    "orchestrator": "k8s",
    "node": "node01",
    "containerID": "0baf28fb5c448b6850091691ad79c713f665e4915862c4d7713304414f02c210",
    "pod": "my-nginx-756fb87568-f9bvd",
    "endpoint": "eth0",
    "ipNetworks": [
      "10.64.196.130/32"
    ],
    "profiles": [
      "kns.default",
      "ksa.default.default"
    ],
    "interfaceName": "cali4ecc9d1d445",
    "mac": "ae:61:8a:0a:7b:4d"
  }
}
```

Calicoについてはまだまだ理解が浅いので断言はできませんが、これらの情報を利用してPod間の通信を可能にしているものと思われます。（たぶんね）

# 7.まとめ

ここまでで構築した環境があれば、Kubernetes上に適当なアプリケーションをデプロイするもサービスメッシュを実現するも、なんでも可能になります！

さらにこの記事で構築した環境については`12/11or19`のIstio構築に続きます！

# 参考URL

https://tech.uzabase.com/entry/2017/08/07/172411

https://thinkit.co.jp/article/14638

https://docs.projectcalico.org/v3.5/getting-started/kubernetes/

https://www.slideshare.net/okinawaopenlabs/20180119-calicopj-calicobgp

https://developers.cyberagent.co.jp/blog/archives/3132/

https://www.slideshare.net/motonorishindo/cni-124981353