k3sをVagrantで構築する（3台構成）

## はじめに

[k3s](https://k3s.io/)という軽量なKubernetes環境があるということで、Vagrantで構築してみました。
Vagrantで構築する場合、ネットワーク設定(flannel)で注意点があるため記録を残します。

## 構成

以下のように、3台のUbuntu 16.04マシンをそれぞれ、master, node1, node2として構成し、Node間はプライベートネットワークで接続します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/115363/9a5ebf57-d11b-1f15-cb51-242049bc7e76.png)

## flannelのVXLANインターフェースが適切に設定されない問題

[こちらのissue](https://github.com/rancher/k3s/issues/72)の通りですが、そのままインストールするとNode間の通信ができません。

k3sで使われているCNIのflannelはVXLANトンネルを利用してNode間通信を行います。その時に利用するVXLANの出入口となるNICがデフォルトではNodeの一番最初のNICとなります。

Vagrantの場合、必ずNAT用のNICが最初に作成されてしまうため、デフォルトではNAT用のインターフェースが選択されてしまい都合が良くありません。
kubeadm等で構築する場合は、flanneldの実行引数`--iface`で明示的に指定可能ですが、k3sではflannelが組み込まれているため指定ができません。

そのため、今回はk3sでは`--no-flannel`フラグを付けてflannelインストールをスキップし、別途flannelをインストールしました。

なお、k3sの実行オプションとして`--flannel-iface`を付ける[PR](https://github.com/rancher/k3s/pull/250)もあるため、取り込まれたらk3sのflannelを利用しても設定できるようになります。

--2019/4/26追記

マージされていました。

## Master Nodeへk3sをインストール

公式のインストーラーを用いて以下のように設定します。`INSTALL_K3S_EXEC`の環境変数を用いてオプションを渡してあげます。

```bash
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="--no-flannel --node-ip=192.168.33.11" sh -
```

`/var/lib/rancher/k3s/server/node-token`にNode追加用のトークンが払い出されていますので、メモしておきます。

## Node1,2を追加

先ほどメモしたトークンを用いてNode1, 2をagentとして追加します。以下はNode1用です。

```bash
export K3S_TOKEN=<トークン>
export K3S_URL=https://192.168.33.11:6443
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="--no-flannel --node-ip=192.168.33.12" sh -
```

## CNIプラグインのインストール

すべてのNodeにおいて、CNIプラグインを配置しておく必要があるため、あらかじめ[公式のリリースページ](https://github.com/containernetworking/plugins/releases)より`cni-plugins-xxxx.tgz`をインストールし、`/opt/cni/bin`に展開しておきます。

## flannelをインストール

flannel自身のデプロイは`kubectl`を使って行います。

[公式のマニフェストファイル](https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml)をダウンロードして、以下のように修正します。

ポイントは以下の2点です。

* flanneldのDaemonSetで起動オプションに`--iface`を追加
    *  以下の例は`amd64`用のDaemonSetに設定しましたが、環境にあわせて適切に設定してください。
* CNI設定のConfigMapを環境にあわせて設定
    *  k3sのデフォルトにあわせて`10.42.0.0/16`で設定しました。

```diff
--- kube-flannel.yaml.org       2019-03-21 09:28:45.427370300 +0000
+++ kube-flannel.yaml   2019-03-20 14:37:30.155241900 +0000
@@ -124,7 +124,7 @@
     }
   net-conf.json: |
     {
-      "Network": "10.244.0.0/16",
+      "Network": "10.42.0.0/16",
       "Backend": {
         "Type": "vxlan"
       }
@@ -174,6 +174,7 @@
         args:
         - --ip-masq
         - --kube-subnet-mgr
+        - --iface=enp0s8
         resources:
           requests:
             cpu: "100m"
```

修正したファイルを`kubectl`で適用します。

```console
vagrant@master:~$ kubectl apply -f kube-flannel.yaml
```

## 確認

以上で構築は完了しました。VXLANインターフェースの設定やNode間通信が問題ないかを確認します。

### VXLANインターフェース

```console
vagrant@master:~$ ip -d a show flannel.1
4: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN group default
    link/ether 6a:02:aa:4b:49:0d brd ff:ff:ff:ff:ff:ff promiscuity 0
    vxlan id 1 local 192.168.33.11 dev enp0s8 srcport 0 0 dstport 8472 nolearning ageing 300
    inet 10.42.0.0/32 scope global flannel.1
       valid_lft forever preferred_lft forever
    inet6 fe80::6802:aaff:fe4b:490d/64 scope link
       valid_lft forever preferred_lft forever
```

プライベートのインターフェースが設定されています。


### Node間通信

NginxのDeploymentを適当に作成しておきます。

```console
vagrant@master:~$ kubectl run --image=nginx --replicas=5 nginx
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/nginx created
vagrant@master:~$ kubectl get pod -o wide
NAME                     READY   STATUS    RESTARTS   AGE   IP           NODE     NOMINATED NODE   READINESS GATES
nginx-7cdbd8cdc9-824qd   1/1     Running   0          25s   10.42.1.12   node1    <none>           <none>
nginx-7cdbd8cdc9-9sct7   1/1     Running   0          25s   10.42.2.11   node2    <none>           <none>
nginx-7cdbd8cdc9-bxfhb   1/1     Running   0          25s   10.42.2.12   node2    <none>           <none>
nginx-7cdbd8cdc9-csd4p   1/1     Running   0          25s   10.42.1.13   node1    <none>           <none>
nginx-7cdbd8cdc9-wcn9s   1/1     Running   0          25s   10.42.0.13   master   <none>           <none>
```

* Master -> Pods の通信

```console
vagrant@master:~$ ping 10.42.1.12 -c 1
PING 10.42.1.12 (10.42.1.12) 56(84) bytes of data.
64 bytes from 10.42.1.12: icmp_seq=1 ttl=63 time=0.425 ms

--- 10.42.1.12 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.425/0.425/0.425/0.000 ms

vagrant@master:~$ ping 10.42.2.11 -c 1
PING 10.42.2.11 (10.42.2.11) 56(84) bytes of data.
64 bytes from 10.42.2.11: icmp_seq=1 ttl=63 time=0.802 ms

--- 10.42.2.11 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.802/0.802/0.802/0.000 ms
```

* Pod -> Pods の通信

```console
vagrant@master:~$ kubectl run --generator=run-pod/v1 -it test --image=busybox /bin/sh
If you don't see a command prompt, try pressing enter.
/ # ping 10.42.0.13 -c 1
PING 10.42.0.13 (10.42.0.13): 56 data bytes
64 bytes from 10.42.0.13: seq=0 ttl=64 time=0.075 ms

--- 10.42.0.13 ping statistics ---
1 packets transmitted, 1 packets received, 0% packet loss
round-trip min/avg/max = 0.075/0.075/0.075 ms

/ # ping 10.42.1.12 -c 1
PING 10.42.1.12 (10.42.1.12): 56 data bytes
64 bytes from 10.42.1.12: seq=0 ttl=62 time=0.484 ms

--- 10.42.1.12 ping statistics ---
1 packets transmitted, 1 packets received, 0% packet loss
round-trip min/avg/max = 0.484/0.484/0.484 ms

/ # ping 10.42.2.11 -c 1
PING 10.42.2.11 (10.42.2.11): 56 data bytes
64 bytes from 10.42.2.11: seq=0 ttl=62 time=0.433 ms

--- 10.42.2.11 ping statistics ---
1 packets transmitted, 1 packets received, 0% packet loss
round-trip min/avg/max = 0.433/0.433/0.433 ms
```

すべて通信できることが確認できました。

## Vagrantfile

最後に、すべてをまとめたVagrantファイルを以下に置いておきます。
`kube-flannel.yaml`は修正済みのものがすでにVagrantfileと同じフォルダに置かれているものとします。

```ruby:Vagrantfile
# -*- mode: ruby -*-
# vi: set ft=ruby :

$configureCommon = <<-SHELL

  # apt-get update
  # apt-get upgrade -y

  ## cni pluginsを展開
  mkdir -p /opt/cni/bin
  curl -sSL https://github.com/containernetworking/plugins/releases/download/v0.7.5/cni-plugins-amd64-v0.7.5.tgz | tar xzf - -C /opt/cni/bin

SHELL

$configureMaster = <<-SHELL

  # プライベートネットワークのNICのIPアドレスを変数に格納
  IPADDR=$(ip a show enp0s8 | grep "inet " | awk '{print $2}' | cut -d / -f1)

  ## k3sのデプロイ flannelをOFF プライベートIPをkubeletのIPとするように指定
  ## ここで、flannelをOFFにしているのは、現在ではVxLANの送出IFの指定ができず、Vagrant環境の場合、NATインターフェースが選択されてしまうため都合が悪い
  ## k3sのFlannelはあきらめて、flannelは別途構築する
  curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="--no-flannel --node-ip=${IPADDR}" sh -

  ## flannelのデプロイ
  kubectl apply -f /vagrant/kube-flannel.yaml

  ## agent登録用のトークンを共有フォルダに配置
  cp /var/lib/rancher/k3s/server/node-token /vagrant/token

SHELL

$configureNode = <<-SHELL

  export K3S_TOKEN=$(cat /vagrant/token)
  export K3S_URL=https://192.168.33.11:6443
  # プライベートネットワークのNICのIPアドレスを変数に格納
  IPADDR=$(ip a show enp0s8 | grep "inet " | awk '{print $2}' | cut -d / -f1)

  ## k3sのデプロイ flannelをOFF プライベートIPをkubeletのIPとするように指定
  curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="--no-flannel --node-ip=${IPADDR}" sh -

SHELL

Vagrant.configure(2) do |config|

  node_num = 3

  (1..node_num).each do |i|

    if i == 1 then
      vm_name = "master"
    else
      vm_name = "node#{i-1}"
    end

    config.vm.define vm_name do |s|

      # ホスト名
      s.vm.hostname = vm_name
      # ノードのベースOSを指定
      s.vm.box = "ubuntu/xenial64"
      # ネットワークを指定
      private_ip = "192.168.33.#{i+10}"
      s.vm.network "private_network", ip: private_ip

      # 共通
      s.vm.provision "shell", inline: $configureCommon

      if i == 1 then
        # For Master
        s.vm.provision "shell", inline: $configureMaster
      else
        # For Nodes
        s.vm.provision "shell", inline: $configureNode
      end

    end
  end
end
```