こんにちは、Red Hatでソリューションアーキテクトをしている石川です。 前回の記事からやや間が空いてしまいましたが今回もKnativeについて改めて入門していきたいと思います。
前回の記事はこちら
rheb.hatenablog.com
本編に入る前に、Knative関連の最近のトピックをご紹介させて下さい。 この度なんとKnativeがCNCFのincubatingプロジェクトに移管されることが発表されました! knative.dev
現在はまだ申請中のステータスとのことなので正式にCNCF配下となるのは少し時間がかかると思いますが、これからKnativeがますます盛り上がっていくことが期待されますね。
TLDR;
- Knative ServingではKPA(Knative Pod Autoscaler)という仕組みでスケーリングを実施。HPAと挙動が違うので注意。
- Knative/Serviceの設定で複数のRevisionにトラフィックをルーティングできる。
- Knativeのルーティングを実現するためKourier(中身はenvoy)が使われている。
Knative Servingの特徴
前回の記事ではKnative Servingの基本的な内容に触れましたが、今回はやや発展編としてKnative Servingの特徴についていくつか焦点を当て見ていきたいと思います。
具体的には、
* Knativeのオートスケールの仕組み
* 複数のRevisionへのトラフィックルーティング
* アプリケーションPodにトラフィックが到達するまでの仕組み
という3つのトピックについてご紹介しようと思います。 それでは早速見ていきましょう。
Knativeのオートスケールの仕組み
Knativeはアクセスがない時はPodを0までスケールインし、アクセスが増えるとPodをスケールアウトするという特徴を持っています。 このオートスケールの仕組みはKPA(Knative Pod Autoscaler)というカスタムリソースにより実現されています。 Kubernetesを普段から使われている方であれば、Podのスケーリングの仕組みとしてHPA(Horizontal Pod Autoscaler)を思い浮かべるかと思いますが、Knativeではデフォルトの設定としてHPAではなくKPAを使用しています。
KPAを使うことで多くの場合で適切にスケールを行い、必要な処理を実行することができますが、スケーリングの挙動について注意すべきこともあります。 それは何をトリガーにスケールを実施するか、という点です。
HPAの場合、一般的にはPodのCPUやメモリの使用量に閾値を設定しておき、その値を超えた場合にPodがスケールアウトする挙動となります。またCPU、メモリ以外にもPrometheus等で取得したカスタムメトリクスを利用するパターンもあります。
一方でKPAはデフォルトでPodへの実行中のリクエスト数(concurrency、in-flight requestsととも)の閾値が設定され、その値を超えた場合にスケールするような挙動となります。
concurrencyを使うのではなくrps(requests per second=1秒ごとのリクエスト数)をスケールのトリガーにすることもできますが、どちらもリクエスト数に基づく設定となるため、実行したいサーバーレスのアプリケーションによってはKPAでは上手くスケールできないこともあります。そのため、アプリケーションの性質に応じてKPAとHPAと使い分けることが重要となります。
試しにKPAでのオートスケールを実践してみましょう。 以下のコマンドはheyというツールを使い並列リクエストを実行する例です。
# hey -z 30s -c 50 \ "https://blog-django-py-knative-serving.apps.mycluster.gzqc.p1.openshiftapps.com/" \ && oc get pods -l app.kubernetes.io/name=blog-django-py
ここでhey
コマンドの-c 50
はリクエストを実行するworkerの数を、-z 30s
でリクエストの実行時間を指定しています。
コマンド実行の結果は以下となります。
Summary: Total: 30.0245 secs Slowest: 6.9209 secs Fastest: 0.0048 secs Average: 0.0580 secs Requests/sec: 861.0630 Total data: 51654294 bytes Size/request: 1998 bytes [中略] NAME READY STATUS RESTARTS AGE blog-django-py-00003-deployment-65ccd7dcf7-4vbz8 2/2 Running 0 28s blog-django-py-00003-deployment-65ccd7dcf7-6bhj4 2/2 Running 0 30s blog-django-py-00003-deployment-65ccd7dcf7-h6vpd 2/2 Running 0 26s blog-django-py-00003-deployment-65ccd7dcf7-jv26l 2/2 Running 0 24s blog-django-py-00003-deployment-65ccd7dcf7-rzgn5 2/2 Running 0 28s blog-django-py-00003-deployment-65ccd7dcf7-shvwb 2/2 Running 0 28s blog-django-py-00003-deployment-65ccd7dcf7-v4sf8 2/2 Running 0 28s blog-django-py-00003-deployment-65ccd7dcf7-wd5n7 2/2 Running 0 28s
実行結果の後半部分からアプリケーションのPodがスケーリングし複数実行されているのが確認できました。
Knativeのスケーリングに関する設定は、Revision単位ではKnative/Serviceのspec.template.metadata.annotations
に記載を行うことで実施可能です。
全てのKnative/Serviceに設定を反映させる場合は、Namespaceknative-serving
にあるConfigMapconfig-autoscaler
に設定を追記することで反映されます。
詳細についてはKnativeの公式ドキュメントをご参照下さい。
是非色々と試行錯誤をして最適な設定を見つけてみて下さい。
複数のRevisionへのトラフィックルーティング
前回の基礎編でデプロイされるアプリケーションのバージョンはRevisionという単位で管理されるということをお伝えしました。 Knative ServingではKnative/Serviceの設定で各Revisionにどの程度トラフィックを割り当てるのか決めることができます。これを利用することでサーバーレスアプリケーションにおいてもカナリアリリースなどの高度なリリース手法を実現することができます。
具体的に設定の例を見てみましょう。
以下ではKnative/Serviceのspec.traffic
以下に各Revisionの情報とそれぞれのトラフィックの割合を定義しています。
ここではカナリアリリースを想定し、最新のRevisionに全体のトラフィックの5%、一つ前のRevisionに95%が振り分けられるようにしています。
spec: traffic: - latestRevision: true percent: 5 tag: latest - revisionName: "blog-django-py-00002" percent: 95 tag: current
このyamlを適用すると、OpenShiftコンソールでは以下のような形でトポロジーを見ることができます。
意図した通りにアクセス先が振り分けられていることを確認できました。
この時、それぞれのRevisionのPodは、エンドポイントへのアクセスではなくPodへのアクセスがあって初めて起動するような形となります。
アプリケーションPodにトラフィックが到達するまでの仕組み
これまでお伝えしてきた通り、Knative Servingではサーバーレスアプリケーションのエンドポイントにアクセスした時の動きとして、Podの数が0であればスケールしたり、Revisionごとにトラフィックを割り振ったりなど、様々な機能を提供しています。
こうした機能を提供するため、どのようなオブジェクトが関連しているか詳細を以下の図で表しました。
やや複雑な絵になりますが、ポイントとして以下を押さえれば大丈夫です。
1) 外部向けエンドポイント公開や、Revision間のトラフィックルーティングはNamespaceknative-serving-ingress
内のオブジェクトで実施されている
2) クラスタ内部向けのエンドポイントはアプリケーションのNamespace(上記の場合app-ns
)で公開されている
3) サーバーレスのアプリケーションPodはアプリケーションのNamespaceで実行される
4) アプリケーションPodの数が0の場合、Namespaceknative-serving
のactivator
/autoscaler
を介してPodをスケールする
外部/内部向けに公開されたエンドポイントにアクセスすると、まずはNamespaceknative-serving-ingress
の3scale-kourier-ingress
という名前のPodにアクセスすることとなります。
これはKourierというKnativeでトラフィックルーティングを行うコンポーネントで、実態としてはIstioでも利用されているenvoyです。
ルーティングの設定は同じNamespaceにあるnet-kourier-controler
というPodに管理されており、このPodはControl planeとしてアプリケーションのNamspaceにあるKnative/Ingressというカスタムリソースを見てenvoyに動的に設定を反映させています。
このKnative/Ingressは、
Knative/Service -> Knative/Route -> Knative/Ingress
といった親子関係を持っており、大元のKnative/Serviceが作成されるとその情報を元に自動で作成されるカスタムリソースです。
エンドポイントからKourier Podに到達したトラフィックはアプリケーションNamspaceのDefault/Serviceを経由し、Namspaceknative-serving
にあるactivator
というPodにアクセスします。このactivator
はその名の通り、アプリケーションPodを起動するトリガーとなる役割を持っており、autoscaler
を経由してアプリケーションPodをスケールさせます。
アプリケーションPodが起動すると同じNamspaceにあるEndpontsオブジェクトが更新され、Default/Serviceを介してPodへのアクセスが可能となります。
まとめ
Knative Serving発展編いかがだったでしょうか。前回に比べるとやや深掘りした内容となりましたが、あくまでもKnative/Serviceカスタムリソースさえ作成してしまえばサーバーレスのアプリケーションを実現できてしまうというのがKnativeの良いところです。
次回はKnativeのもう一つのコンポーネントであるKnative Eventingについてご紹介し、イベント駆動でのサーバーレスアプリ実行方法をご紹介したいと思います。