この記事はKnativeをより深く知るための記事の第1弾です。
Kubernetesでコンテナをデプロイするためには、Deployment Service といったリソースを定義してサービスへの疎通を試みますが、Knativeを使うことでどれだけ簡素化するか、どのようなことができるようになるのかを解説していきます。
以降の手順では、KnativeがインストールされているKubernetesクラスタがGKEを用いて構築されているという環境を前提にしています。
みなさんも是非ご自分のKnativeクラスタを用いながらハンズオンのように実施してみてください。
目次
Knative Servingを知る
Knative Serving(Serving)は、コンテナをサービスとして提供する機能です。
Servingでコンテナをホストするために必要なものは Service リソースのみです。
ややこしいのですが、ここでの”Service” はKuberentesにおけるServiceと明確に異なります。以降この記事におけるServiceは、ServingにおけるServiceのことです。
Servingを利用することにより、Kubernetesの世界では実現できなかった異なるバージョンを持つコンテナへのトラフィック分割が可能になり、いわゆるカナリアリリースが実現できます。
早速Knativeから提供されているサンプルを利用して挙動を見ていきましょう。
Knativeにコンテナをデプロイする
今回はサンプルのうち、 helloworld-go のサンプルを使用します。ゴールは、異なる環境変数を指定した2つのバージョンのコンテナをデプロイして、トラフィックを1:1に分割することです。
サンプルのDockerfileを使用して、 helloworld-go:latest という名前でGoogleContainerRegistryへのイメージ保存が完了したという前提です。
Servingの設定ファイルを作成する
このコンテナをServingへデプロイするための設定ファイル helloworld-go.svc.yamlがこちらになります
apiVersion: serving.knative.dev/v1alpha1
kind: Service
metadata:
name: helloworld-go
namespace: default
spec:
runLatest:
configuration:
revisionTemplate:
spec:
container:
image: gcr.io/ca-tominaga-test/helloworld-go
env:
- name: TARGET
value: "Go Sample v1"
KubernetesのServiceを見たことがある人は、なんとなく似ていると思っていただけるはずです。
これをKubernetesでやるようにいつもの方法でデプロイできます。
コンテナをKnativeへデプロイする
kubectl apply -f helloworld-go.svc.yaml と実行し、kubectl get svc で以下の結果を受け取れるはずです。
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
helloworld-go ExternalName knative-ingressgateway.istio-system.svc.cluster.local 1m
helloworld-go-00001-service ClusterIP 10.19.245.71 80/TCP 1m
kubernetes ClusterIP 10.19.240.1 443/TCP 2h
コンテナへリクエストを送る
コンテナへのエンドポイントは以下のコマンドで調査できます
$ kubectl get svc knative-ingressgateway -n istio-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
AGE
knative-ingressgateway LoadBalancer 10.19.245.254 35.189.159.86 80:32380/TCP,443:32390/TCP,31400:32400/TCP,15011:32318/TCP,8060:31219/TCP,853:32626/TCP,15
030:31611/TCP,15031:32548/TCP 2h
ここでは 35.189.159.86 という外部IPが付与されています。
Knativeでは、リクエスト時のホストヘッダーを見てルーティングを決定します。
今回作成した helloworld-go という名前のServiceへリクエストを送るためのホストヘッダーは以下のコマンドで調査できます。
$ kubectl get ksvc helloworld-go --output=custom-columns=NAME:.metadata.name,DOMAIN:.status.domain
NAME DOMAIN
helloworld-go helloworld-go.default.example.com
以下のようにリクエストを送信すれば結果を得られる状態になりました。
$ curl -H "Host: helloworld-go.default.example.com" http://35.189.159.86
Hello Go Sample v1!
ここまでがServingの基本です。コンテナをデプロイするために必要なものは、コンテナイメージとService定義ファイルの2つだけです。
トラフィックの分割
ここからは少し発展的な内容を扱います。
Servingでは、異なるバージョンのコンテナをデプロイし、トラフィックの分割を実現することができます。
先程のyamlファイルの TARGET 環境変数を Go Sample v2 に変更してデプロイしてみてください。
すると今度は以下のような結果に変わります。
$ curl -H "Host: helloworld-go.default.example.com" http://35.189.159.86
Hello Go Sample v2!
実はServingでは、新しいバージョンのコンテナをデプロイすると100%のトラフィックが新しいバージョンへ流れます。そのことは以下のコマンドで確認できます。
出力は最後の3行のみ掲載します。
$ kubectl get route helloworld-go -o=yaml
traffic:
- percent: 100
revisionName: helloworld-go-00002
この3行がトラフィックの分割を担っていてとても重要です。
ここに書いてある revisionName が何を指しているのかは、以下のコマンドで確認できます。
$ kubectl get revisions
NAME AGE
helloworld-go-00001 41m
helloworld-go-00002 22m
実はServiceとしてリソースを定義すると、このRevisionと呼ばれるものが作成されています。
さきほどのトラフィックの定義は、revisionNameが helloworld-go-00002 のrevisionにトラフィックを全部流してくれ、という指定をしていたことになります。
Service = Configuration + Revision + Route
ここで一旦KnativeにおけるServiceを構成する要素について確認します。
Serviceとは、単一のリソースではなく、いくつかの構成要素の集合で成り立っています。
その構成要素たちが、Configuration, Revision, Route です。
Configuration
Configurationは、どのコンテナイメージを使用するのかを管理しています。
現在デプロイされているConfigurationは、以下のコマンドで確認できます。
$ kubectl get configuration
NAME AGE
helloworld-go 1h
定義を覗いてみると、どのコンテナイメージをデプロイするのか、などの情報が書かれています。
Revision
Revisionは、Configurationの変更を追跡します。
現在デプロイされているRevisionは、以下のコマンドで確認できます。
$ kubectl get revisions
NAME AGE
helloworld-go-00001 1h
helloworld-go-00002 48m
定義を覗いてみると、環境変数を変更した履歴が確認できます。
Route
Routeは、どのRevisionに対してどの割合でトラフィックを流すのかを管理しています。
現在デプロイされているRouteは以下のコマンドで確認できます。
$ kubectl get route
NAME AGE
helloworld-go 9m
今の所、Serviceで定義したRouteを変更することはできず、常に新しいバージョンのRevisionにトラフィックが割り当てられてしまうので、トラフィックの分割をやろうと思ったら、Configurationを作成、更新しなければなりません。
続いてはServiceを使わず、ConfigurationとRouteを独自に定義してみます。
ここまでの作業を一旦消したいときは、Serviceリソースを削除すると、これらの関連リソースが全て削除されます。
$ kubectl delete -f helloworld-go.svc.yaml
ConfigurationとRouteの定義
helloworld-goアプリケーションをConfigurationとしてデプロイするための定義ファイルは以下のようなものになります。
apiVersion: serving.knative.dev/v1alpha1
kind: Configuration
metadata:
name: helloworld-go
namespace: default
spec:
revisionTemplate:
metadata:
labels:
knative.dev/type: container
app: prod
spec:
container:
image: gcr.io/ca-tominaga-test/helloworld-go
env:
- name: TARGET
value: "Go Sample v1"
readinessProbe:
httpGet:
path: /
initialDelaySeconds: 3
periodSeconds: 3
すると、2つのRevisionが作成されているはずです。
$ kubectl get revisions
NAME AGE
helloworld-go-00001 17s
helloworld-go-00002 14s
一方で、今回はConfigurationを単体で定義したので、Routeは作成されていません。
$ kubectl get route
No resources found.
この2つのRevisionへ半々にトラフィックを分割するRoute定義ファイルは以下のようになります。
apiVersion: serving.knative.dev/v1alpha1
kind: Route
metadata:
name: helloworld-go
namespace: default
labels:
app: prod
spec:
traffic:
- revisionName: helloworld-go-00001
percent: 50
- revisionName: helloworld-go-00002
percent: 50
このファイルをデプロイして、再度リクエストを送ってください。
すると、Hello Go Sample v1! というレスポンスと、Hello Go Sample v2! という2つのレスポンスが半々くらいの割合で返ってくることが確認できたはずです。
まとめ
今回はKnative Servingにおけるコンテナのデプロイ方法及びKnativeにおけるServiceの構成要素などについて解説しました。
次回はコンテナをKnativeの中でビルドする、Knative Buildを紹介します。