Knative Servingを試してみた
この記事はCyberAgent Developers Advent Calendar 2018 8日目の記事です
この記事では、最近興味があるknativeの中のServingについて紹介していきます。
knative とは
サーバレスワークロードのビルド、デプロイ、管理するためのKubernetesをベースにしたプラットフォームを提供するフレームワークです cloud.google.com
knativeは、Serving, Build, Eventingの3つのコンポーネントがあります。概要としてはそれぞれこのような認識です。
- Serving: リクエスト駆動でポッドの起動やポッド数の管理
- Build: ソースからコンテナを生成するビルドに関するオーケストレーション
- Eventing: イベント管理と配信
今回は、このServing部分で実際に動作させながらスケールリングやリクエストの振り分けなどをまとまめていきます。 環境としては、docker desktop for mac上のk8s環境で行いました。
インストール
knative servingのセットアップには、kubernetes環境にIstioとKnative Servingの2つをインストールすることでできます。
今回は、docker desktop for macでのkubernetesクラスタを使っていますが、他環境の場合も記載されているので、手持ちのkubernetesで試すことができると思います。
docs/README.md at master · knative/docs · GitHub
istioのインストール
まずistioのインストールから行います。 minikubeの場合を参考にNodePortにしてapplyします。
curl -L https://github.com/knative/serving/releases/download/v0.2.2/istio.yaml \ | sed 's/LoadBalancer/NodePort/' \ | kubectl apply --filename - kubectl label namespace default istio-injection=enabled
関連するのpodがRunningやCompletedになるとインストールは完了です。apply後にはistio-injection=enabled
ラベルを設定も行います。
この istio-injection
の設定ができていないと動作させられないです。後続のkubectl applyなどはエラーも出ず、この設定漏れにも気づかずになぜ動作しないのかと時間を使ったことがあるので忘れずやりましょう。
それぞれ行うと自分の環境だと次のようになりました。
$ kubectl get pods --namespace istio-system NAME READY STATUS RESTARTS AGE istio-citadel-84fb7985bf-tvjf2 1/1 Running 0 1m istio-cleanup-secrets-4lk9z 0/1 Completed 0 1m istio-egressgateway-bd9fb967d-g57cw 1/1 Running 0 1m istio-galley-655c4f9ccd-vr2dz 1/1 Running 0 1m istio-ingressgateway-688865c5f7-ppqwl 1/1 Running 0 1m istio-pilot-6cd69dc444-9mqgp 2/2 Running 0 1m istio-policy-6b9f4697d-b8n4r 2/2 Running 0 1m istio-sidecar-injector-8975849b4-2sf9h 1/1 Running 0 1m istio-statsd-prom-bridge-7f44bb5ddb-wm2pw 1/1 Running 0 1m istio-telemetry-6b5579595f-vmlfj 2/2 Running 0 1m $ kubectl get namespace -L istio-injection NAME STATUS AGE ISTIO-INJECTION default Active 3d enabled docker Active 3d istio-system Active 6m disabled kube-public Active 3d kube-system Active 3d
Knative Servingのインストール
Knative Servingのインストール時にも、先程と同様にNodePortに変更してapplyします。
curl -L https://github.com/knative/serving/releases/download/v0.2.2/release-lite.yaml \ | sed 's/LoadBalancer/NodePort/' \ | kubectl apply --filename -
完了時には次のようになりました。
$ kubectl get pods --namespace knative-serving NAME READY STATUS RESTARTS AGE activator-df78cb6f9-cdjqz 2/2 Running 0 1m activator-df78cb6f9-gbn7f 2/2 Running 0 1m activator-df78cb6f9-whhbk 2/2 Running 0 1m autoscaler-6fccb66768-cnwzk 2/2 Running 0 1m controller-56cf5965f5-dnfr6 1/1 Running 0 1m webhook-5dcbf967cd-8bjzs 1/1 Running 0 1m
これで一連のインストールは完了です。
Knative Serving
今回扱うKnative Servingは、次の4つの要素があります。
- Service
- Route
- Configuration
- Revision
参考: https://github.com/knative/docs/tree/master/serving
Service
他の3つの要素を含んだ全体を自動的に管理するもの。
更新に対する新しいRevionを作成し、最新のRevisionや特定のRevisionに常にルーティングされるように管理している。
kubectl get kservice
で定義されているものを取得できる
Route
どのRevionに何%トラフィックを流すかなどをマッピング。
kubectl get route
で定義されているものを取得できる
Configuration
実行する構成の定義、変更されると新しいRevisionが作成される
他のものと同様に kubectl get configuration
定義されているものを取得できる
Revision
Configurationの履歴にあたるポイントインタイムスナップショット。
kubectl get revision
で取得できる
デプロイ
環境も整ったので、実際にデプロイしていきます。 今回は、単純なjsonを返すサーバアプリで、このアプリのdockerイメージはDocker Hubにある状態です。
これをknative上で動かすためにknative servingのServiceリソースを追加します。
# echo-server-service.yaml apiVersion: serving.knative.dev/v1alpha1 kind: Service metadata: name: echo-server namespace: default spec: runLatest: configuration: revisionTemplate: spec: container: image: terachanple/echo-server-go:latest
kubectl apply -f echo-server-service.yaml
このapplyが成功すると、
curl -H "Host: echo-server.default.example.com" "http://localhost:32380"
上記のようなcurlコマンドでデプロイしたecho-serverへリクエストできます。
ポートの32380
は knative-ingressgateway
へリクエストするためのもので次のようにして確認できます。
# knative-ingressgateway のNodePort kubectl get svc knative-ingressgateway --namespace istio-system --output 'jsonpath={.spec.ports[?(@.port==80)].nodePort}' # HostヘッダのURL kubectl get services.serving.knative.dev echo-server -o jsonpath='{.status.domain}'
podの動きとしては次のようになっていて、初めてリクエストしたときにpodの初期化生成が行われています。そしてしばらくリクエストをしないとそのpodは削除されています。なのでリクエストがない状態であればpod数が0になります。
$ kubectl get pod --watch NAME READY STATUS RESTARTS AGE echo-server-00001-deployment-dbb4b64b9-xsnjt 0/3 Pending 0 0s echo-server-00001-deployment-dbb4b64b9-xsnjt 0/3 Pending 0 0s echo-server-00001-deployment-dbb4b64b9-xsnjt 0/3 Init:0/1 0 0s echo-server-00001-deployment-dbb4b64b9-xsnjt 0/3 PodInitializing 0 3s echo-server-00001-deployment-dbb4b64b9-xsnjt 2/3 Running 0 5s echo-server-00001-deployment-dbb4b64b9-xsnjt 3/3 Running 0 6s echo-server-00001-deployment-dbb4b64b9-xsnjt 3/3 Terminating 0 5m echo-server-00001-deployment-dbb4b64b9-xsnjt 0/3 Terminating 0 5m echo-server-00001-deployment-dbb4b64b9-xsnjt 0/3 Terminating 0 6m echo-server-00001-deployment-dbb4b64b9-xsnjt 0/3 Terminating 0 6m
上のように1podだけでなく、リクエストが増えれば、podの数も増加させてくれます。
https://github.com/knative/docs/blob/master/serving/samples/autoscale-go/README.md#algorithm ここで記載されているように、スケールはpodごとのリクエスト中でまだ完了していない数の平均をもとに算出しており、例としては次のような計算になるようです。
条件 - リクエスト数: 350req/s - 1リクエストの処理時間: 0.5s/req 式 350[req/s] * 0.5[s/req] = 175 175 / 100 (デフォルト:100) = 1.75 ceil(1.75) = 2 [pods]
ちなみに、podがない状態からの最初のアクセスではpodの作成処理から行われているので時間はかかってしまいます。
# echo-server podが0個の場合 $ curl -H "Host: echo-server.default.example.com" -s -o /dev/null -w "%{time_starttransfer}\n" "http://localhost:32380" 9.562675 # echo-server podが1個の場合 $ curl -H "Host: echo-server.default.example.com" -s -o /dev/null -w "%{time_starttransfer}\n" "http://localhost:32380" 0.023103
一連のserviceを削除する場合は次のようにkubectlから削除できます。
kubectl delete -f echo-server-service.yaml
リクエストをrevisionに振り分ける
knative servingではRouteを設定することでバージョンAに何%のトラフィック、バージョンBに何%のトラフィックを流すといったような制御ができます。 なので機能追加などで変更した新しいものへのリクエストは数パーセントから初めて徐々に増やしていくような形をとることができます。
先程で使ったecho-serverを使って試していきます。
Serviceだと自動で最新のものになるようなので、 Configuration
と Route
を追加していきます。
今あるecho-serverに100%リクエストが行く形です。
# echo-server-split.yaml (latestのみ) apiVersion: serving.knative.dev/v1alpha1 kind: Configuration metadata: name: echo-server namespace: default spec: revisionTemplate: metadata: labels: knative.dev/type: container spec: container: image: terachanple/echo-server-go:latest imagePullPolicy: Always --- apiVersion: serving.knative.dev/v1alpha1 kind: Route metadata: name: echo-server namespace: default spec: traffic: - revisionName: echo-server-00001 percent: 100 name: "v1"
上記のyamlをapplyすると先程と同じように
curl -H "Host: echo-server.default.example.com" "http://localhost:32380"
次にイメージに変更を加えたバージョンを指定し、今までのバージョンには90%、新しいバージョンには10%で振り分けるように設定します。(今回のアプリの変更はレスポンスjsonに含まれるmeta:"v1"
を meta:"v2"
へ変更しただけです)
# echo-server-split.yaml (新しくfeature_aを追加) apiVersion: serving.knative.dev/v1alpha1 kind: Configuration metadata: name: echo-server namespace: default spec: revisionTemplate: metadata: labels: knative.dev/type: container spec: # container: # image: terachanple/echo-server-go:latest # imagePullPolicy: Always container: image: terachanple/echo-server-go:feature_a imagePullPolicy: Always --- apiVersion: serving.knative.dev/v1alpha1 kind: Route metadata: name: echo-server namespace: default spec: traffic: - revisionName: echo-server-00001 percent: 90 name: "v1" - revisionName: echo-server-00002 percent: 10 name: "v2"
再度applyし、 先程までと同様にリクエストすると、 "meta":"v2"
となっているレスポンスがたまに返ってくるようになります。
リクエストとレスポンスの例としては、次のようなものです。
# リクエストとレスポンスの例 $ curl -H "Host: echo-server.default.example.com" "http://localhost:32380" {"meta":"v2","msg":""}% $ curl -H "Host: echo-server.default.example.com" "http://localhost:32380" {"meta":"v1","msg":""}% $ curl -H "Host: echo-server.default.example.com" "http://localhost:32380" {"meta":"v1","msg":""}% $ curl -H "Host: echo-server.default.example.com" "http://localhost:32380" {"meta":"v1","msg":""}%
このように同じリクエストでもリクエストごとに2つのものに振り分けられます。この振り分ける割合を traffic.percent
の値を調整することで簡単に制御できます。
特定のものにリクエストする場合はtraffic.name
を設定し、その設定したname
をリクエスト時のhostの先頭につけることで指定したrevisionへアクセスできます。
今回であれば、今までの方にv1
、新しく追加した方に v2
とnameを設定しているので、次のようにして特定のものへアクセスできます。
# name v1へのアクセス curl -H "Host: v1.echo-server.default.example.com" "http://localhost:32380" # name v2へのアクセス curl -H "Host: v2.echo-server.default.example.com" "http://localhost:32380" # 指定なし (v1, v2は設定したpercentで振り分けられる) curl -H "Host: echo-server.default.example.com" "http://localhost:32380"
リクエストを振り分けて簡単に適用できるのは、カナリアリリースなどもできるようになるのですごく嬉しいですね。
まとめ
- knative servingをローカルのKubernetes環境でデプロイやリクエストの振り分けの動作までの方法をまとめました
- リクエストに応じて自動でスケールし、リクエストが無いとpod数が0にまでなるので、必要分だけが起き上がるサーバレスな感じは体感でよかった
- pod数が0の状態からのアクセス時には時間がかかってしまうので、apiサーバとして使うときは注意が必要そうに感じました。batchサーバだとすぐに開始しないといけない用途以外にはあまり気にならないようには感じました。
- リクエストの振り分けは特定の%を設定するだけで簡単に行えるので便利
- servingだけだとリクエストに応じてだけになるってしまうので今回できなかったですが、BuildとEventingを連携して、特定のイベントでソースがビルドされり、特定のサービスが動くというのも試していきたいと思います
今回使用したyamlやecho-serverのコードは次のリポジトリに置いてあります github.com