クラウド同上

コンテナを超簡単にデプロイするKnative Servingの実力

Author
闇の戦士
Lv:12 Exp:23510

闇の戦士です。
https://jp.finalfantasyxiv.com/lodestone/character/492168/

大好きなもの: FFXIV/ガルパ/スタァライト
好きなもの: Angular/flutter/Kubernetes

嫌いなもの:Stackdriver Monitoring/ピーマン/辛いもの

GCP力: CA/DE/CD/Authorized Trainer

この記事は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!
このドメイン名を独自のものに設定し、SSL対応する方法は公式ドキュメントに記載があります。
ここまでが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
このリソースは、Serviceをデプロイした際に自動的に作成されています。
定義を覗いてみると、どのコンテナイメージをデプロイするのか、などの情報が書かれています。

Revision
Revisionは、Configurationの変更を追跡します。
現在デプロイされているRevisionは、以下のコマンドで確認できます。

コード
$ kubectl get revisions
NAME                  AGE
helloworld-go-00001   1h
helloworld-go-00002   48m
Serviceを2つデプロイしていたので(= Configurationを1回更新したので)、Revisionが2つ作成されています。
定義を覗いてみると、環境変数を変更した履歴が確認できます。

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
環境変数を “Go Sample v2” に変更しただけのファイルも用意して2つのファイルを使ってリソースをデプロイしてください。

すると、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を紹介します。

次の記事を読み込んでいます
次の記事を読み込んでいます
次の記事を読み込んでいます
次の記事を読み込んでいます
次の記事を読み込んでいます