サーバーレス初心者が考えるKnativeのメリット

# はじめに

最近、触っているOracle CloudのKubernetesサービスOKEにてサーバーレス機能がリリースされようとしています。関連技術としてKnativeが挙げられています。
触ったことがなかったので、サーバーレスに関するKnativeを調べて、そのメリットを考えます。

# Knativeの第一印象

Knativeのドキュメントをほんの少し触って、何やらリソースをスケーリングするらしいことはわかりました。Kubernetesにはもともと水平スケーリング（HPA）がありますが、それと比べて簡単に使えて、コスト節約やアプリ開発に専念できるようになるらしい。。？でも、ノードをサーバーレス化しないとメリットが少ないのでは？

※参考

・Oracle CloudのOKE製品ページ
https://www.oracle.com/jp/cloud/cloud-native/container-engine-kubernetes/
・サーバレス on Kubernetes/serverless-on-kubernetes
https://speakerdeck.com/oracle4engineer/serverless-on-kubernetes
<iframe width="560" height="315" src="https://www.youtube.com/embed/QV3PsPZG0ZE" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>

# コスト節約になるのか

サーバーレスなKubernetesでKnativeを動かせればコスト節約になるのではないかと考えています。
AWSやOCIといったクラウドでは使ったサービスのリソースに対して課金されます。
Kubernetesでは使うノードが増える程、コストがかかります。なので、クラスタ上で何を作ろうとも、クラウドのサービス（EC2、EBSなど）として使う量が増えなければコストは変わりません。Knativeを使って、動かすコンテナを減らしてもコストは変わらないのでもったいないなと思います。
サーバーレスなKubernetesであれば、コンテナで使ったCPUやメモリなどで課金されます。ノードはクラウドベンダーが管理してくれます。ノードを何台使うかや性能をどうするかなどは管理する必要がありません。Knativeを使えば、不要なコンテナを止めることができ、コスト削減につながると思います。

# クラスタ内リソースの管理が楽になるのか

Knativeでは従来のHPAにはない機能があります。
* Serving
CPUやメモリなどを基にPODを最小数0としてスケーリングします。また、カスタムリソースによって、アプリのコンテナとサービスを同時に管理できます。
* Functions
Node.jsやPythonなどを使って作ったプログラムを、クラスタ上にServingとして作成できます。コンテナ、サービスがどのようになっているかあまり意識することがありません。
* Eventing
特定のイベントが起きた時に、メッセージをServingやコンテナに送信することができます。Kubernetes APIのログを集めたり、pingを送ったりなどできます。

今まで、一から作っていたものについてKnativeでカバーできる部分があり、楽になるのではないかと思いました。

※参考
https://knative.dev/docs/

# Knativeに切り替えるうえで気になること

スケーリングを想定していない構成から、Knativeを使う構成に切り替えるときに気になることがあります。

## コンテナ起動にかかる時間が長いのではないか

大きなアプリで起動に時間がかかる場合、0からスケーリングしたときに時間がかかってしまうのではないかと思います。PODの起動までに、イメージのpull、エントリポイントの実行が行われます。PODの最小数を1にしたりエントリポイントを軽くしたりする必要があると思います。

## アプリ内で定期的に実行する処理があるとスケールダウンできない

定期的に実行したり、頻繁に実行するコンテナでは処理の完了がわかりづらく、ダウンさせづらいです。一つのコンテナで色々な処理を行っているのであれば、別Servingにして分割していくのがよいでしょうか。どうしていくべきかはこれから勉強していきたいです。分割しすぎると管理が大変になったり、コンテナの配置が気になってきてしまうので調整が必要かなと思います。

※参考

https://qiita.com/NewGyu/items/81bcca07b8614905d0a3

## POD数、サービス数の上限に引っかからないか

ノードに配置できるPOD数の上限は決まっています。多くのPODを同時に動かす場合に限ると思いますが、水平スケーリングでどこまで増えるかは気になります。
Kubernetes v1.26のドキュメントには以下のように書かれています。

>クラスターはKubernetesのエージェントが動作する(物理もしくは仮想の)ノードの集合で、コントロールプレーンによって管理されます。 Kubernetes v1.26 では、最大5000ノードから構成されるクラスターをサポートします。 具体的には、Kubernetesは次の基準を 全て 満たす構成に対して適用できるように設計されています。
1ノードにつきPodが110個以上存在しない
5000ノード以上存在しない
Podの総数が150000個以上存在しない
コンテナの総数が300000個以上存在しない

また、クラウドや、仮想ネットワークプラグインによっても少なくなります。
Oracle Cloudの場合は、VNIC数によってPOD数上限が決まります。以下の式で求められます。

> Maximum number of Pods per node = MIN( (Number of VNICs - 1) * 31 ), 110)

POD数が上限に到達してしまったら、新たにServingが0からスケーリングできなくなってしまいます。

また、サービスにてノードポートを使っている場合、上限はノードポートの数（範囲：30000-32767）になります。

※参考

https://kubernetes.io/ja/docs/setup/best-practices/cluster-large/

https://docs.oracle.com/ja-jp/iaas/Content/ContEng/Concepts/contengpodnetworking_topic-OCI_CNI_plugin.htm

## Kubernetes APIアップデートはどのように行うか

APIのバージョン管理はクラウドベンダーの領分になります。しかし、アップデートするときは、コンテナは再起動してしまうのではないかと思います。そのため、アップデートする時間を決められたらいいなと思います。

## アプリのテストがしやすいか

私はインフラ面の作業が主ですが、アプリ開発をしている方々への影響が気になります。書いたコードをすぐにテストする場合、テスト環境を作れるかが気になります。スケーリングを再現したいですが、ローカルにクラスタを作るのは大変だと思います。
テスト内容にもよると思いますが、minikube、Kustmoizeなど簡単に環境を作れる工夫が必要ではないかと思います。

## ノードに関するリソース設定はできるのか

細かいことかもしれませんが、ノード管理を自分でしなくなるので、ノードに関わる設定がどうなるか気になります。
例えば、Fluentdによるログ収集はノードにあるコンテナごとのログファイルを使います。関係する記述がKnativeドキュメントにありました。

https://knative.dev/docs/serving/observability/logging/collecting-logs/

DaemonSetで収集するとあります。しかし、ベンダーが提供するクラスタの種類によっては、サポートされていないかもしれません。AWS Fargateではサポートされておらず、サイドカーやマネージドサービスが挙げられていました。

https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/fargate.html

https://note.com/shift_tech/n/n503b32e5cd35

PODがどこのノードにあるかわからないため、ノードに関する設定はうまくいかないと思った方がよいのでしょうか。どんな方法がよいかはこれから調べたいと思います。

また、冗長化のためPODのスケジューリングで異なるノードに配置したいということがあると思います。FargateのようにPOD同士でノードが異なることがわかれば、設定が不要なので楽だと思います。もし異なるノードか不明の場合は以下のPODのaffinityを使えないでしょうか。ノードに関係なくPODに付けられたラベルを見て、同じノードに配置されないようにできます。

https://kubernetes.io/ja/docs/concepts/scheduling-eviction/assign-pod-node

# KnativeのServingを使ってみる

実際に使ってみます。
minikubeを使うとします。公式ページに従ってKnativeをインストールします。
https://knative.dev/docs/install/
今回はServing、Eventing、Functionsをインストールしますが、Servingのみ使います。

初めに、Operatorをインストールします。

```bash
$ VERSION="v1.8.1"
$ kubectl apply -f https://github.com/knative/operator/releases/download/knative-${VERSION}/operator.yaml
```

必要に応じて、ServingとEventingのCRDをインストールします。

```bash
$ echo "install knative serving"
$ kubectl apply -f https://github.com/knative/serving/releases/download/knative-${VERSION}/serving-crds.yaml
$ kubectl apply -f https://github.com/knative/serving/releases/download/knative-${VERSION}/serving-core.yaml
$ echo "install network layer of kourier"
$ kubectl apply -f https://github.com/knative/net-kourier/releases/download/knative-${VERSION}/kourier.yaml
$ kubectl patch configmap/config-network \
--namespace knative-serving \
--type merge \
--patch '{"data":{"ingress-class":"kourier.ingress.networking.knative.dev"}}'
$ echo "install knative eventing"
$ kubectl apply -f https://github.com/knative/eventing/releases/download/knative-${VERSION}/eventing-crds.yaml
$ kubectl apply -f https://github.com/knative/eventing/releases/download/knative-${VERSION}/eventing-core.yaml
```

クライアントツールをインストールします。

```bash
$ curl -LO https://github.com/knative/client/releases/download/knative-${VERSION}/kn-linux-amd64
$ mv kn-linux-amd64 kn
$ chmod +x kn
$ sudo mv kn /usr/local/bin
```

Functions用のクライアントツールのプラグインをインストールします。

```bash
$ curl -LO https://github.com/knative/func/releases/download/knative-${VERSION}/func_linux_amd64
$ mv func_linux_amd64 kn-func
$ chmod +x kn-func
$ sudo mv kn-func /usr/local/bin
```

Servingをデプロイするためのyamlファイルを以下ページから持ってきます。
https://knative.dev/docs/serving/convert-deployment-to-knative-service/#knative-service

デプロイします。

```bash
$ kubectl apply -f <yamlファイル>
```

状態を確認します。

```bash
$ kubectl get ksvc
NAME       URL                                         LATESTCREATED    LATESTREADY      READY   REASON
my-nginx   http://my-nginx.default.svc.cluster.local   my-nginx-00001   my-nginx-00001   True
$ kubectl get po -w
NAME                                         READY   STATUS        RESTARTS   AGE
knative-operator-695f5f9556-5jctv            1/1     Running       0          7m37s
my-nginx-00001-deployment-6448b7fd7f-w7hmr   2/2     Terminating   0          89s
operator-webhook-5c99f5fc94-lhm9f            1/1     Running       0          7m37s
my-nginx-00001-deployment-6448b7fd7f-w7hmr   1/2     Terminating   0          101s
my-nginx-00001-deployment-6448b7fd7f-w7hmr   0/2     Terminating   0          108s
my-nginx-00001-deployment-6448b7fd7f-w7hmr   0/2     Terminating   0          108s
my-nginx-00001-deployment-6448b7fd7f-w7hmr   0/2     Terminating   0          108s
```

一度PODが作られ、アクセスがないために削除されました。
アクセスしてPODが作成されるか確認します。

```bash
$ kubectl run busybox --image=busybox --rm --restart=Never -it -- wget -O - http://my-nginx.default.svc.cluster.local
...
writing to stdout
<!DOCTYPE html>
...
written to stdout
pod "busybox" deleted
$ kubectl get po -w
NAME                                         READY   STATUS    RESTARTS   AGE
knative-operator-695f5f9556-5jctv            1/1     Running   0          10m
my-nginx-00001-deployment-6448b7fd7f-6xj4h   2/2     Running   0          11s
operator-webhook-5c99f5fc94-lhm9f            1/1     Running   0          10m
my-nginx-00001-deployment-6448b7fd7f-6xj4h   2/2     Terminating   0          63s
my-nginx-00001-deployment-6448b7fd7f-6xj4h   1/2     Terminating   0          91s
my-nginx-00001-deployment-6448b7fd7f-6xj4h   0/2     Terminating   0          93s
my-nginx-00001-deployment-6448b7fd7f-6xj4h   0/2     Terminating   0          94s
my-nginx-00001-deployment-6448b7fd7f-6xj4h   0/2     Terminating   0          94s
```

Servingへのアクセスに対して、応答が返ってきてスケーリングされることが確認できました。

# おわりに

今後、サーバーレス化が進みそうだなと思いましたが、使い始めるための準備が必要だと思いました。その間に勉強して使えるようになりたいです。