k8sベースのServeless環境のKnativeをGKEで動かす


はじめに

Knativeをご存知でしょうか? 簡単に言うとCloud Build, Google AppEngine, Cloud FunctionをDocker/k8sベースで実現するための仕組み。
これは中々便利でスケーラブルなビルド環境まで含まれてるので、マネージドなKnative環境があれば自前でGCPっぽい環境が作れる訳ですね。

と言うわけ、Cloud Next 2019でなんか発表あるだろうから予習がてら現状のKnativeをGKEで動かしてみた備忘録です。
最新情報は公式ドキュメント参考で。

GKEでKubenetesクラスタの作成

まずはクラスタを作成するわけですが、KnativeはIstioやFluentd, Prometheus, Grafana, OpenZipkin, Kibanaなども含まれている大きなアセットです。
なので、最小構成といえどそれなりのソースを使います。ただ、たいしたもの動かさないのであまりお金かけたく無いと言うこともあり構成を試行錯誤してみました。

その結果以下の感じになりました。n1-standard-2が恐らく動かすために必要なノードあたりの最低スペックになります。ワーカーとしても用もf1-microだとリソース不足になりがちなのでg1-small以上が良いかと思います。

node type node count description
n1-standard-2 3 Knative環境一式を動かすのに使う
g1-small 1 ワーカー用

ワーカー用と管理用を分ける必要は特に無いのですが、私の環境の場合ワーカーはほとんどリソースがいらないので別でg1-smallを追加のノードプールで作ることでコスト圧縮を狙ってみました。

まずは、以下のコマンドでクラスタの作成。

export CLUSTER_NAME=knative-cluster-01
export CLUSTER_ZONE=us-central1-f

gcloud beta container clusters create $CLUSTER_NAME \
  --zone=$CLUSTER_ZONE \
  --cluster-version=latest \
  --machine-type=n1-standard-2 \
  --preemptible \
  --disk-type "pd-standard" --disk-size "10" \
  --image-type "COS" \
  --num-nodes=3 \
  --enable-autorepair \
  --no-enable-basic-auth \
  --addons HorizontalPodAutoscaling,HttpLoadBalancing,KubernetesDashboard \
  --enable-autoupgrade \
  --enable-stackdriver-kubernetes \
  --scopes "https://www.googleapis.com/auth/userinfo.email","https://www.googleapis.com/auth/compute.readonly","https://www.googleapis.com/auth/devstorage.read_write","https://www.googleapis.com/auth/taskqueue","https://www.googleapis.com/auth/logging.write","https://www.googleapis.com/auth/monitoring","https://www.googleapis.com/auth/pubsub","https://www.googleapis.com/auth/servicecontrol","https://www.googleapis.com/auth/service.management.readonly","https://www.googleapis.com/auth/trace.append","https://www.googleapis.com/auth/source.read_write","https://www.googleapis.com/auth/cloud_debugger"

続いてアカウントの紐づけをします。

kubectl create clusterrolebinding cluster-admin-binding --clusterrole=cluster-admin --user=$(gcloud config get-value core/account)

IstioとKnativeのインストール

次に構築したクラスタにIstioとKnativeのインストールを行います。

kubectl apply --filename https://github.com/knative/serving/releases/download/v0.4.0/istio-crds.yaml
kubectl apply --filename https://github.com/knative/serving/releases/download/v0.4.0/istio.yaml
kubectl label namespace default istio-injection=enabled

以下のコマンドでステータスが全てRunningになってればインストール完了です。

kubectl get pods --namespace istio-system

引き続きKnativeをインストールします。

kubectl apply \
--filename https://github.com/knative/serving/releases/download/v0.4.0/serving.yaml \
--filename https://github.com/knative/build/releases/download/v0.4.0/build.yaml \
--filename https://github.com/knative/eventing/releases/download/v0.4.0/in-memory-channel.yaml \
--filename https://github.com/knative/eventing/releases/download/v0.4.0/release.yaml \
--filename https://github.com/knative/eventing-sources/releases/download/v0.4.0/release.yaml \
--filename https://github.com/knative/serving/releases/download/v0.4.0/monitoring.yaml \
--filename https://raw.githubusercontent.com/knative/serving/v0.4.0/third_party/config/build/clusterrole.yaml

こちらも以下のコマンドでステータスがRunningになれば成功です。

kubectl get pods --namespace knative-serving
kubectl get pods --namespace knative-build
kubectl get pods --namespace knative-eventing
kubectl get pods --namespace knative-sources
kubectl get pods --namespace knative-monitoring

ワーカーノードをクラスタへ追加

Knative関連のインストールが終わったのでワーカー向けのノードをクラスタに追加します。
特に設定を入れてるわけでは無いので常にこのノードにアプリがデプロイされるとは限りませんが、すでにKnative関連のPodは他に入ってるので確率的にはここに追加されるはず。

gcloud container node-pools create small-pool \
   --zone $CLUSTER_ZONE --cluster $CLUSTER_NAME \
  --machine-type=g1-small \
  --preemptible \
  --disk-type "pd-standard" --disk-size "10" \
  --image-type "COS" \
  --num-nodes=1

アプリのDeploy

続いてアプリのデプロイです。
service.yamlを作成し、以下の内容を記述します。

apiVersion: serving.knative.dev/v1alpha1 # Current version of Knative
kind: Service
metadata:
  name: echo-server # The name of the app
  namespace: default # The namespace the app will use
spec:
  runLatest:
    configuration:
      revisionTemplate:
        spec:
          container:
            image: koduki/echo-server

見た目通りecho-serverをKnativeのServiceとしてデプロイするシンプルな設定になっています。
echoサーバは8080で動くシンプルな構成。コンテナの詳細はこちらのDockerfileを確認してください。
echoサーバの実装はこちらのとおりです。Istioを経由するということでどういうI/Fを書けば良いのかと思ったのですがとりあえず普通にHTTPのrequest/responseを返しておけば後は良しなにしてくれるようです。便利!

kubectl apply --filename service.yaml

とりあえずデプロイできたら動作確認をしてみます。

export IP_ADDRESS=$(kubectl get svc istio-ingressgateway --namespace istio-system --output 'jsonpath={.status.loadBalancer.ingress[0].ip}')
curl -H "Host: echo-server.default.example.com" http://${IP_ADDRESS}/hello
hello%                                

引数で渡しているhelloが返ってるのがわかりますね。

クラスタのIPアドレスを確認してcurlでアクセスします。
ドメイン名としてecho-server.default.example.comが振られているらしく、同一IPでもバーチャルホストとして動作しているようです。

ただ、default.example.comがDNSに登録されているわけじゃ無いので-Hオプションでホストを直接指定します。この辺の解消は後ほど。

もう一つ重要な点としてkoduki/echo-serverは8080で動作してるのに、curlで接続するポートは80です。これはリクエストをまず受け取ってるのはIstioでecho-serverはそこと通信をしているためかと思います。たぶん。

ドメインの設定

example.comはDNS登録されてないので、DNSに適切に登録されたドメインを設定します。そうしないとcurlはさておきブラウザでのアクセスは不便ですし。
とはいえ毎回ドメインを作るのも面倒なのでNIP.IOを使います。

これは、任意のIPアドレスをサブドメイン名に指定すると指定したIPで名前解決をしてくれるワイルドカードDNSと呼ばれるものです。
例えばecho-server.default.127.0.0.1.nip.io127.0.0.1を返します。これを使うことで簡単にドメインが利用できます。

まずEXTERNAL-IPを確認します。

$ kubectl get svc istio-ingressgateway -n istio-system  
NAME                   TYPE           CLUSTER-IP     EXTERNAL-IP     PORT(S)                                                                                                                   AGE
istio-ingressgateway   LoadBalancer   10.xxx.xxx.xxx   35.xxx.xxx.xxx   80:31380/TCP,443:31390/TCP,31400:31400/TCP,15011:31406/TCP,8060:32449/TCP,853:30097/TCP,15030:32188/TCP,15031:32016/TCP   7d3h

kubectl edit cm config-domain --namespace knative-serving を実行して、ドメインの内容を下記のようにExternal IP + nio.ioに変更します。

apiVersion: v1
data:
  35.xxx.xxx.xxx.nip.io: ""
kind: ConfigMap
metadata:
  annotations:

これで準備は完了。今度は-Hオプションは不要でURLをホスト名で指定して接続します。

$ curl http://echo-server.default.35.xxx.xxx.xxx.nip.io/hello
hello

無事接続できましたね。

Knativeをモニタリングする

構築/デプロイと来たら次はモニタリングですね?
KnativeにはIstioと連携する形でOpenZipkin, Prometheus/Grafana, そしてKibanaが含まれています。

Grafana

Grafanaにアクセスするためにport-forwardのため以下のコマンドを実行します。

kubectl port-forward --namespace knative-monitoring \
$(kubectl get pods --namespace knative-monitoring \                       
--selector=app=grafana --output=jsonpath="{.items..metadata.name}") \
3000

3000portにフォーワードしてるのでhttp://localhost:3000/にアクセスします。
ノードやPodの状態、あるいはリクエスト数などの基本的なダッシュボードは既に用意されているのですぐに利用できるかと思います。

OpenZipkinを開く

また、Istioと連携する形でOpenZipkinも入っています。kubectl proxyを実行して以下のURLにアクセスします。

http://localhost:8001/api/v1/namespaces/istio-system/services/zipkin:9411/proxy/zipkin/

Kibanaを開く

Kibanaを開くにはkubectl proxyを実行して以下のURLにアクセスします。

http://localhost:8001/api/v1/namespaces/knative-monitoring/services/kibana-logging/proxy/app/kibana

現時点ではデフォルトにはあまり仕込みはいれて無いようなのでカスタマイズは自分でする必要がありそうです。

まとめ

Knativeを使って環境をGKE上にサーバレス環境を構築してみました。
Knativeの場合は単にFaaSのことをサーバレスと呼んでるのではなくPaaS/FaaS/Build含めてスケーラブルな環境を実サーバ台数を気にすることなく構築できるので、k8sよりかなり使いやすい抽象度になります。

とはいえ、これを使ってオレオレGCPやオレオレHerokuを運用したい人は実際はさほど多く無い気がするので、Knative自体のマネージドサービスが出て、それが今後のクラウドのベースラインになってくるのかな、と思います。
現在、α版やってるServerless add-on for Google Kubernetes Engineとかがそんな感じでは無いかと期待。
いずれにしても常時稼働しておくと結構な費用なのでどこかマネージドknative早く頼むw

それではHappy Hacking!

参考