0からはじめる Windows on Kubernetes

# はじめに

Kubernetes の Windows 対応は v.1.14 でGAとなりました。
本記事では、既存の Kubernetes クラスタに0から Windows ワーカーノードを追加する方法をご紹介します。

# 実行環境

今回は実行環境として Azure を使用しています。

- Kubernetes クラスタ（[hard way on azure](https://github.com/ivanfioravanti/kubernetes-the-hard-way-on-azure) で作成）
    - Linux VM（ubuntu 18.04）×6
- Windows ノード
    - Windows VM（Windows Server 2019 Datacenter）×1

```bash
# クラスタには3台の Linux ワーカーノード
$ kubectl get no
NAME       STATUS   ROLES    AGE     VERSION
worker-0   Ready    <none>   12m     v1.15.0
worker-1   Ready    <none>   7m26s   v1.15.0
worker-2   Ready    <none>   5m59s   v1.15.0
```

# クラスタ側の修正

hard way で作成したクラスタですが、kube-controller-manager の起動オプションが足りていません。

```diff:kube-controller-manager.service
[Unit]
Description=Kubernetes Controller Manager
Documentation=https://github.com/kubernetes/kubernetes

[Service]
ExecStart=/usr/local/bin/kube-controller-manager \
  --address=0.0.0.0 \
+ --allocate-node-cidrs=true \
  --cluster-cidr=10.200.0.0/16 \
  --cluster-name=kubernetes \
  --cluster-signing-cert-file=/var/lib/kubernetes/ca.pem \
  --cluster-signing-key-file=/var/lib/kubernetes/ca-key.pem \
  --kubeconfig=/var/lib/kubernetes/kube-controller-manager.kubeconfig \
  --leader-elect=true \
  --root-ca-file=/var/lib/kubernetes/ca.pem \
  --service-account-private-key-file=/var/lib/kubernetes/service-account-key.pem \
  --service-cluster-ip-range=10.32.0.0/24 \
  --use-service-account-credentials=true \
  --v=2
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
```

このオプションがないと`cluster-cidr`が適用されず、ノードの`podCIDR`が未設定になってしまいます。
以降で使用するスクリプトで`podCIDR`が必要になるので、事前に修正しておきましょう。

# Windows ノードの追加

途中まで[MSのドキュメント](https://docs.microsoft.com/en-us/virtualization/windowscontainers/kubernetes/joining-windows-workers)を参考に進めていきます。

## Docker のインストール

手順に従って Docker をインストールし、マシンを再起動します。

```powershell
Install-Module -Name DockerMsftProvider -Repository PSGallery -Force
Install-Package -Name Docker -ProviderName DockerMsftProvider
Restart-Computer -Force
```

再起動後、`docker pull`でコンテナイメージを取得します。
ここで取得するイメージは Pod 内のインフラ用コンテナの作成に使用します。
Dockerfile に記載されているイメージはタグが未指定なので、取得したイメージに latest タグを付加してください。

```powershell
docker pull mcr.microsoft.com/windows/nanoserver:1809
docker tag mcr.microsoft.com/windows/nanoserver:1809 mcr.microsoft.com/windows/nanoserver:latest
```

ちなみにインストールが完了すると、`Get-HNSNetwork`など HNS の操作が可能になります。

## 実行ファイルの用意

Kubernetes のファイルを配置するディレクトリを作成します。

```powershell
mkdir c:\k
```

ディレクトリを作成したら以下のファイルを配置してください。

- クラスタに接続できる kubeconfig（ファイル名は config）
- kubernetes ノード用バイナリ
    - kubectl.exe
    - kubelet.exe
    - kube-proxy.exe

バイナリは Kubernetes の[リリースページ](https://github.com/kubernetes/kubernetes/releases/)にアクセスし、[CHANGE LOG](https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG-1.16.md#v1164)から取得できます。
次のような構成になっていればOKです。

```powershell
ls config,*exe

    Directory: C:\k

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----       12/22/2019  11:49 AM           6254 config
-a----       12/11/2019   1:14 PM       40086016 kube-proxy.exe
-a----       12/11/2019   1:20 PM       47195136 kubectl.exe
-a----       12/11/2019   1:20 PM      119127552 kubelet.exe
```

## スクリプトの実行

ではいよいよ、各種設定とバイナリの起動を行う powershell スクリプトを実行していきます。
github からスクリプトをダウンロードしてください。

```powershell
cd c:\k
Start-BitsTransfer https://github.com/microsoft/SDN/raw/master/Kubernetes/windows/start.ps1
```

ここでは[MSのドキュメント](https://docs.microsoft.com/en-us/virtualization/windowscontainers/kubernetes/joining-windows-workers)の手順とは異なるスクリプトをダウンロードしています。
MSのドキュメントでは CNI プラグインとして flannel を使用し、vxlan overlay ネットワークを構成しています。
flannel はクラスタに事前にインストールする必要がありますが、hard way のクラスタでは別の CNI プラグインを使用しているため、今回は flannel を使用しない方法を取りました。
ダウンロードしたスクリプトでは wincni.exe を使用し、L2 ブリッジネットワークを構成します。（overlay も選択可）

start.ps1 は以下の処理を順番に行います。

1. 必要なバイナリ、スクリプト、Dockerfile をダウンロード
2. コンテナイメージを作成（InstallImages.ps1）
3. HNS ネットワークを初期化
4. kubelet を起動（start-kubelet.ps1）
5. kube-proxy を起動（start-kubeproxy.ps1）
6. ルーティングテーブルに追加（AddRoutes.ps1）

スクリプト1つで必要な処理をすべて行ってくれるようです。
またスクリプトの中を見てみると、2つの param を設定できることがわかります。

```powershell:start.ps1
Param(
    [parameter(Mandatory = $true)] [string] $masterIp,
    [parameter(Mandatory = $false)] $clusterCIDR="192.168.0.0/16"
)
```

`masterIp`は VM のプライベートIP（10.240.0.4）、`clusterCIDR`は上述の kube-controller-manager で設定した値（10.200.0.0/16）を使用します。

```powershell
.\start.ps1 -masterIp 10.240.0.4 -clusterCIDR 10.200.0.0/16
```

これでノードの追加完了！…とはいきませんでした。

## 動かない…

~~案の定~~そのままでは動きませんでした。
powershell ウィンドウには`Waiting for the Network to be created`が延々と出力され続けていると思います。
一度実行すると必要なファイル等はダウンロードされるので、`start.ps1`は捨てて個別のスクリプトを修正＆実行していきます。

## スクリプトの修正＆実行

### helper.psm1

```diff:helper.psm1
function Get-PodCIDR()
{
-   return c:\k\kubectl.exe --kubeconfig=c:\k\config get nodes/$($(hostname).ToLower()) -o custom-columns=podCidr:.spec.podCIDR --no-headers
+   return "10.200.3.0/24"
}
```

まずはモジュールの修正から。
これは本当に疑問なのですが、ノードの追加前なのにクラスタから自身のノード情報を取得しようとしているのはどうしてでしょうか…？（間違っていたらご指摘ください）
当然取得はできないので、自分で決めた podCIDR を返すように修正します。
モジュールをインポート済みの場合は一度削除しておいてください。

```powershell
Remove-Module helper
```

### InstallImages.ps1

問題なく実行できているので、修正・再実行は行いません。
事前に取得した Docker イメージから、pause コンテナを作成しています。

```powershell
docker image ls | Out-String -Stream | Select-String pause
kubeletwin/pause                       latest              14d3bdf2f5cb        5 hours ago         251MB
```

### start-kubelet.ps1

```diff:start-kubelet.ps1
Param(
+   $clusterCIDR="10.200.0.0/16",
-   $clusterCIDR="192.168.0.0/16",
    $NetworkMode = "L2Bridge",
    $NetworkName = "l2bridge",
    [ValidateSet("process", "hyperv")]
    $IsolationType = "process"
)

# Todo : Get these values using kubectl
$KubeDnsSuffix ="svc.cluster.local"
+$KubeDnsServiceIp="10.32.0.10"
-$KubeDnsServiceIp="11.0.0.10"
+$serviceCIDR="10.32.0.0/24"
-$serviceCIDR="11.0.0.0/8"
```

Todo と記載された変数は外から設定できないようです。なぜこんなところに？
`$KubeDnsServiceIp`には kube-dns サービスのクラスタIP、`$serviceCIDR`には kube-controller-manager で設定した`service-cluster-ip-range`を使用します。
ついでに param の`$clusterCIDR`も修正しておきました。これでオプションなしでスクリプトを実行できるはずです。

```powershell
.\start-kubelet.ps1
```

成功するとフォアグラウンドで kubelet.exe が実行中になるので、別の powershell ウィンドウを起動してください。
（`start powershell`で別ウィンドウで起動しても良いです）

kubelet を無事に起動できると、クラスタにノードが追加されていることが確認できます。

```bash
$ kubectl get no
NAME         STATUS   ROLES    AGE    VERSION
win-server   Ready    <none>   173m   v1.16.4
worker-0     Ready    <none>   28h    v1.15.0
worker-1     Ready    <none>   28h    v1.15.0
worker-2     Ready    <none>   28h    v1.15.0
```

### start-kubelet.ps1

```powershell:start-kubelet.ps1
Param(
    [parameter(Mandatory = $false)] $LogDir = "C:\k",    
    $NetworkName = "cbr0"
)
```

特に修正の必要はありません。
初回起動時にはここまで到達していないので、スクリプトを実行します。
他の箇所とは違い`$NetworkName`の初期値がなぜか`cbr0`になっていますが、惑わされずに`l2bridge`を指定しましょう。（私は惑わされました）

```powershell
.\start-Kubeproxy.ps1 -NetworkName l2bridge
```

成功するとフォアグラウンドで kube-proxy.exe が実行中になります。

### AddRoutes.ps1

最後にこのスクリプトを実行します。
こちらも修正は不要です。
初回起動時と同様、`$masterIp`には VM のプライベートIPをしていてください。

```powershell
.\AddRoutes.ps1 -masterIp 10.240.0.4
```

これでノードの追加完了です！

# 動作確認

Windows コンテナのデプロイを試したり、Linux ノード上のコンテナから Windows ノード上のコンテナにアクセスしてみたり、色々と試してみてください。

```bash
$ kubectl get po,svc -o wide
NAME                                 READY   STATUS    RESTARTS   AGE   IP             NODE        NOMINATED NODE   READINESS GATES
pod/busybox                          1/1     Running   13         13h   10.200.2.4     worker-2    <none>           <none>
pod/nginx                            1/1     Running   0          38h   10.200.1.3     worker-1    <none>           <none>
pod/ubuntu                           1/1     Running   0          20h   10.200.1.4     worker-1    <none>           <none>
pod/win-webserver-784d66c84f-bqh9l   1/1     Running   0          10h   10.200.3.159   win-server  <none>           <none>
pod/win-webserver-784d66c84f-ssxfr   1/1     Running   0          10h   10.200.3.39    win-server  <none>           <none>

NAME                    TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)        AGE   SELECTOR
service/kubernetes      ClusterIP   10.32.0.1    <none>        443/TCP        38h   <none>
service/nginx           NodePort    10.32.0.52   <none>        80:32121/TCP   37h   run=nginx
service/win-webserver   NodePort    10.32.0.65   <none>        80:31663/TCP   10h   app=win-webserver
```

# サービスとして起動する

今回は powershell 上で kubelet.exe と kube-proxy.exe を起動しましたが、これらを Windows のサービスとして起動する方法もあります。
詳細は[こちら](https://docs.microsoft.com/en-us/virtualization/windowscontainers/kubernetes/kube-windows-services)をご参照ください。

# 感想

最後までたどり着いて本当に良かった…
色々と悩んで調べまわった結果、ネットワーク周りと powershell の知識が身についてきたのは良かったです。
Issue はあまり確認する時間がなかったので、きちんと調べたら別の方法があったかもしれません。何かご存知の方は教えてくださいm(_ _)m

実際に Windows ノードを使用する場合は、やはりマネージドサービスがおススメです。AKS であれば Windows ノードプールを指定するだけで簡単に始められます。
オンプレの場合は kubeadm + flannel を使用した方法が [kubernetes.io](https://kubernetes.io/docs/setup/production-environment/windows/user-guide-windows-nodes/) で紹介されています。その他の方法についてもサイト下部に記載があるので、興味のある方は読んでみてください。

# 参考

[Joining Windows Server Nodes to a Cluster](https://docs.microsoft.com/en-us/virtualization/windowscontainers/kubernetes/joining-windows-workers)
[github: microsoft/SDN/Kubernetes/Windows](https://github.com/microsoft/SDN/tree/master/Kubernetes/windows)
[KubernetesとFlannelでWindows上にPod間VXLAN Overlayネットワークを構成](https://www.slideshare.net/anikundesu/kubernetesflannelwindowspodvxlan-overlay-152588125)
[Kubernetes Networking: Behind the scenes](https://itnext.io/kubernetes-networking-behind-the-scenes-39a1ab1792bb)
[Troubleshooting Kubernetes Networking on Windows: Part 1](https://techcommunity.microsoft.com/t5/Networking-Blog/Troubleshooting-Kubernetes-Networking-on-Windows-Part-1/ba-p/508648)