Virtual Kubelet CRI Provider

# はじめに
最近Edge Computingに漠然とした興味があります。
Edgeっていう響きが、なんかカッコいい。
[k3s](https://k3s.io/)や[kubeedge](https://kubeedge.io/)の登場により、KubernetesのEdge Computingでの利用・応用というアプローチが登場してきています。
KubeCon + CloudNativeCon North America 2019では[コネクテッドカーへの応用](https://kccncna19.sched.com/event/UaaU/kubernetes-in-your-4x4-continuous-deployment-directly-to-the-car-rafal-kowalski-grape-up)という新しいアプローチも発表されていました。
世間的にはローカル5Gなどの動きが出てきている中、クラウドとの連携、Edge Computingに対するアプリケーションのデプロイ、ワークロードの管理といった領域は様々な技術が複合する必要があり、これから色々な業界で盛り上がることは間違いないでしょう。
そんな状況の中、本記事ではVirtual KubeletのCRI Providerに触れてみました。

## Virtual Kubeletとは
[Virtual Kubelet](https://virtual-kubelet.io/)をご存知でしょうか？
これは、Kubernetesの各Nodeで稼働するコンポーネントであるkubelet[^1]を装う、仮のkubeletです。
Virtual Kubeletで検索すると、すでに運用しているKubernetesクラスタに対し、Serverlessプラットフォームで提供されるNodeをKubernetesクラスタに追加する事例がよくヒットします。
Kubernetesクラスタをインターネットサービスで使っている時、トラフィックがバーストするタイミングが事前にわかる場合、事前にKubernetesクラスタのWorkerをスケールアウトし、Podを増やすことをよくします。
Virtual KubeletはServerlessプラットフォームと連携し、プラットフォームが払い出した一時的なVM/コンテナで、KubernetesのNodeとして振る舞うことができます。また、KubernetesのNodeとして機能するため、APIやCLIによる操作を受付け、Podのデプロイができます。
バーストへの対策や、WorkerのVM自体を運用したくないケースでの利用が考えられます。
またバッチ処理だけ実行したい場合、かつVMは運用したくない場合などの利用も考えられます。

さて、[Virtual KubeletのレポジトリのREADME](https://github.com/virtual-kubelet/virtual-kubelet/blob/master/README.md)には冒頭に以下が記載されています。

> Virtual Kubelet is an open source Kubernetes kubelet implementation that masquerades as a kubelet for the purposes of connecting Kubernetes to other APIs. This allows the nodes to be backed by other services like ACI, AWS Fargate, **IoT Edge** etc. The primary scenario for VK is enabling **the extension of the Kubernetes API into serverless container platforms like ACI and Fargate**, though we are open to others. However, it should be noted that VK is explicitly **not intended to be an alternative to Kubernetes federation**.

IoT Edgeとあります。が、Azure IoT Edgeを意味しているようです。
Kubernetesクラスタを他のServerlessプラットフォームに拡張するのが主なシナリオであり、複数のKubernetesクラスタを一元管理するFederationの代替ではないとあります。
ServerlessなContainerプラットフォームでの利用が主な用途なのでしょう。

## Virtual Kubelet Provider
Virtual Kubeletを実行するには提供されているProviderを使うもしくは自作する必要があります。

- [公式Provider](https://virtual-kubelet.io/docs/) [^2]
    - Alibaba Cloud Elastic Container Instance (ECI)
    - AWS Fargate
    - Azure Batch
    - Azure Container Instances (ACI)
    - **Kubernetes Container Runtime Interface (CRI)**
    - Huawei Cloud Container Instance (CCI)
    - HashiCorp Nomad
    - OpenStack Zun
- 自作例
    - [Netflix社の事例](https://kccncna19.sched.com/event/Uai8/introduction-to-virtual-kubelet-featuring-titus-by-netflix-ria-bhatia-microsoft-sargun-dhillon-netflix)

[^1]: kubeletについては公式ドキュメントをご覧ください。https://kubernetes.io/ja/docs/concepts/overview/components/
[^2]: GitHub上には、Azure IoT Edge Connector for Kubernetesもありますが、2019/12現在サイトのDocsには含まれていないようです。

さて、私が興味を持ったのは、**Virtual Kubelet**ではありません。興味の矛先は**Edge Computing**です。またAzure IoT Edge Connectorなど特定のサービスに依存したくはありませんでした。
そこで、**Kubernetes Container Runtime Interface (CRI) Provider**が提供されていることに着目しました。
これがあれば、Raspberry PIのようなシングルボードコンピュータとの連携もでき、IoTやEdge Computingでの活用もあり得るのではないか、そういう淡い期待を抱きました。

## CRI Providerを試す
さっそく触ってみましょう。
今回はお試しということで、すべてローカル環境で完結させました。

- 環境（Mac）
    - K8s
        - minikube v1.5.2
            - K8s 1.16.2 on VirtualBox 6.0
    - Virtual Kubelet
        - ubuntu-16.04 on VirtualBox 6.0
            - Virtual Kubelet 1.2.1
            - [CRI Provider](https://github.com/virtual-kubelet/cri)
            - CRI 1.3.0
    - その他
        - Vagrant 2.2.6
        
### Kubernetesクラスタのセットアップ
```zsh:Mac
# VirtualBox間で相互接続できるよう、HyperKitではなく、VirtualBoxを使います
$ minikube start --vm-driver=virtualbox
$ kubectl get no
NAME       STATUS   ROLES    AGE    VERSION
minikube   Ready    master   9m9s   v1.16.2
```

### Virtual Kubeletのセットアップ
```zsh:Mac
# Vagrant BoxでサクッとVM作ります
$ vagrant init ubuntu/xenial64
$ vim Vagrantfile
# 以下を変更。minikubeとVirtual Boxで作るVMが相互アクセスできるNWになるよう、ホストオンリーアダプターを設定
35c35
<   # config.vm.network "private_network", ip: "192.168.33.10"
---
>   config.vm.network "private_network", ip: "192.168.33.10"

$ vagrant up
```

Virtual Kubelet用証明書、設定ファイルのコピー。
今回はお試しなのでminikubeのクライアント証明書をそのままVirtual Kubeletにコピーして利用します。

```zsh:Mac
$ minikube ssh sudo cat /etc/kubernetes/admin.conf > admin.conf
$ vagrant upload admin.conf
$ vagrant upload `kubectl config view -o jsonpath='{.users[?(@.name=="minikube")].user.client-key}'` client.key
$ vagrant upload `kubectl config view -o jsonpath='{.users[?(@.name=="minikube")].user.client-certificate}'` client.crt
```

Virtual Kubelet CRI Providerを実行するには、CRI, CNIのセットアップおよびgoでのビルドが必要です。

```zsh:ubuntu

### CRIのセットアップ
$ VERSION=1.3.0
$ wget https://storage.googleapis.com/cri-containerd-release/cri-containerd-${VERSION}.linux-amd64.tar.gz
#### checksum確認
$ sha256sum cri-containerd-${VERSION}.linux-amd64.tar.gz
$ curl https://storage.googleapis.com/cri-containerd-release/cri-containerd-${VERSION}.linux-amd64.tar.gz.sha256
$ sudo tar --no-overwrite-dir -C / -xzf cri-containerd-${VERSION}.linux-amd64.tar.gz

### CNIのセットアップ
$ wget https://github.com/containernetworking/plugins/releases/download/v0.8.2/cni-plugins-linux-amd64-v0.8.2.tgz

$ sudo mkdir -p \
  /etc/cni/net.d \
  /opt/cni/bin
$ sudo tar -xvf cni-plugins-linux-amd64-v0.8.2.tgz -C /opt/cni/bin/

#### bridge
$ POD_CIDR=10.128.0.0/16
$ cat <<EOF | sudo tee /etc/cni/net.d/10-bridge.conf
{
    "cniVersion": "0.3.1",
    "name": "bridge",
    "type": "bridge",
    "bridge": "cnio0",
    "isGateway": true,
    "ipMasq": true,
    "ipam": {
        "type": "host-local",
        "ranges": [
          [{"subnet": "${POD_CIDR}"}]
        ],
        "routes": [{"dst": "0.0.0.0/0"}]
    }
}
EOF

#### loopback
$ cat <<EOF | sudo tee /etc/cni/net.d/99-loopback.conf
{
    "cniVersion": "0.3.1",
    "name": "lo",
    "type": "loopback"
}
EOF

### Containerd起動
$ sudo systemctl start containerd

### Golangセットアップ
$ sudo add-apt-repository ppa:longsleep/golang-backports
$ sudo apt-get update
$ sudo apt-get install -y golang-go

### Virtual Kubelet CRI Providerのビルド
$ git clone https://github.com/virtual-kubelet/cri.git
$ cd cri
$ make build && mv bin ../ && cd ..

### kube-apiserverアクセス用client証明書のコピー
$ sudo mkdir /etc/virtual-kubelet
$ sudo mv client.key /etc/virtual-kubelet/client.key
$ sudo mv client.crt /etc/virtual-kubelet/client.crt

### 起動スクリプトの作成
# admin.confのIPをminikubeのホストIPに置換
$ sed -i -e 's/localhost/192.168.99.105/g' ./admin.conf

# 以下、起動スクリプトの準備。VirtualBoxのIPアドレスに合わせて変更
cat << EOF | tee startup.sh
#!/bin/bash
# VM Host IP
export VKUBELET_POD_IP=192.168.33.10
export APISERVER_CERT_LOCATION="/etc/virtual-kubelet/client.crt"
export APISERVER_KEY_LOCATION="/etc/virtual-kubelet/client.key"
export KUBELET_PORT="10250"
export KUBERNETES_SERVICE_PORT="8443"

# minikube Host IP
export KUBERNETES_SERVICE_HOST=192.168.99.105
cd bin
./virtual-kubelet --provider cri --kubeconfig ../admin.conf
EOF

$ chmod +x startup.sh
$ sudo ./startup.sh
```

### 動作確認
```zsh:Mac
# Virtual Kubeletの追加を確認
$ kubectl get no
NAME              STATUS   ROLES    AGE   VERSION
minikube          Ready    master   17h   v1.16.2
virtual-kubelet   Ready    agent    19s   v1.15.2-vk-cri-5dec3cb
```

# Podをデプロイ
Virtual Kubeletで起動したNodeはデフォルトで以下のtaintsが設定されています。
そのため、デプロイするときは、`.spec.tolerations`をセット、もしくはNodeの`taints`を削除する必要があります。

```yaml
spec:
  taints:
  - effect: NoSchedule
    key: virtual-kubelet.io/provider
    value: cri
```

今回は`.spec.tolerations`をセットし、nginx Podをデプロイします。

```zsh:Mac
$ cat << EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: edge-nginx
  labels:
    app: edge-nginx
spec:
  containers:
  - name: nginx
    image: nginx:alpine
    ports:
    - containerPort: 80
  tolerations:
  - effect: NoSchedule
    key: virtual-kubelet.io/provider
    value: cri
    operator: Equal
EOF

$ kubectl get po
NAME         READY   STATUS    RESTARTS   AGE
edge-nginx   1/1     Running   0          8s

$ kubectl exec -it edge-nginx sh
# レスポンスは空

$ kubectl logs edge-nginx
# レスポンスは空

```
CRI Providerには現在execやlogsは実装されていません。必要なら自分で実装する[^3]ことが可能です。
今回のセットアップだけではminikubeのPodからのアクセスや、Service Type Loadbalancerなどが実現できないので、Podが稼働しているか確認するために、直接VM上でport forwardをして確認します。

[^3]: https://godoc.org/github.com/virtual-kubelet/virtual-kubelet/node/api


```zsh:ubuntu
# port-forwardにはsocatが必要
$ sudo apt-get install -y socat
$ sudo crictl pods 
POD ID              CREATED             STATE               NAME                NAMESPACE           ATTEMPT
f9ba814efc837       4 minutes ago       Ready               edge-nginx          default             0
676b7b074c729       15 minutes ago      Ready               kube-proxy-qcqf8    kube-system         0

$ POD_ID=`sudo crictl pods --name='^edge-nginx$' | grep edge-nginx | awk '{print $1}'`

$ sudo crictl port-forward $POD_ID 80

$ curl localhost
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

```
Podがデプロイされ、nginxが稼働していることが確認できました。

ちなみに、複数のVirtual Kubelet CRI Providerも稼働できますが、デフォルトだとNodeの名前が衝突してしまい、おかしな挙動になります。
スクリプトで`virtual kubelet`を起動する際に、`—node-name=virtual-kubelet-foo` などセットしましょう。

## 感想
今回、Edge Computingに興味を持った私は、Virtual Kubelet CRI Providerが活用できないかと思い触ってみましたが、CRI Provider自体がほとんどメンテされていないこともあり、セットアップに手間がかかるものになっていました。
ここまで手間がかかるのであれば、Edge側でk3sを構築した上でクラウドからアクセスもしくはEdge側にsshし、K3sを操作する。
Edge側でセンサーなどのIoT機器との通信が必要であれば、Kubernetesだけで完結することはできないため、Azure IoT Hubなど特定サービスの利用がよいかもしれません。このあたりはKubeEdgeが強く意識しているようなので、いつか触れてみたいと思います。

Edge Computingに必要な要件は様々でしょう。
たとえば、NW帯域制限、通信できるタイミング・時間の制限、Edge内で閉じた閉域NW、省電力、クラウドからの操作、アプリケーションの更新方法など考えられます。
Virtual KubeletをEdge Computingの一部として利用する、ことはありえるかもしれませんが、それだけで何か完結することはなく、周辺コンポーネントの作り込みも重要になりそうです。

個人的には、Virtual KubeletはやはりServerlessなコンテナプラットフォームなどで稼働させるのがベストなのではないか、と考えます。
とはいえ、Amazon EKS on Fargateなど、Virtual Kubeletを使っていないプロダクトもでており、今後どういうプロダクトがMajorityになっていくのか、まだわかりません。


## 参考
- CRI/CNI関連
    - https://github.com/containerd/cri/blob/master/docs/installation.md
    - https://github.com/containerd/cri/blob/master/docs/config.md
    - https://github.com/kelseyhightower/kubernetes-the-hard-way/blob/master/docs/09-bootstrapping-kubernetes-workers.md

## 後記
実際にEdge Computingという側面でどういったプロダクト・サービスが登場していくのでしょうか？
今後も興味の赴くままに追ってみたいと思います。
また、公式で紹介しているCRI Providerの[セットアップ手順](https://github.com/virtual-kubelet/cri)は、ほぼ無いも同然なので、本記事が誰かの助けになれば幸いです。

本記事は、あくまでも個人的な興味であり、現在Z LabにはEdge Computingの業務はありませんので、誤解なさらず。