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

はじめに
---------

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

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

GKEでKubenetesクラスタの作成
-----------

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

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

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

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

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

```bash
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"
```

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

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

IstioとKnativeのインストール
------

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

```bash
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`になってればインストール完了です。

```bash
kubectl get pods --namespace istio-system
```

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

```bash
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になれば成功です。

```bash
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は他に入ってるので確率的にはここに追加されるはず。

```bash
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を作成し、以下の内容を記述します。

```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](https://github.com/koduki/echo-server/blob/master/Dockerfile)を確認してください。
echoサーバの実装は[こちらのとおり](https://github.com/koduki/echo-server/blob/master/app.rb)です。Istioを経由するということでどういうI/Fを書けば良いのかと思ったのですがとりあえず普通にHTTPのrequest/responseを返しておけば後は良しなにしてくれるようです。便利!

```bash
kubectl apply --filename service.yaml
```

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

```bash
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](http://nip.io/)を使います。

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

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

```bash
$ 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をホスト名で指定して接続します。

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

無事接続できましたね。

Knativeをモニタリングする
-----

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

### Grafana

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

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

`3000`portにフォーワードしてるので`http://localhost:3000/`にアクセスします。
ノードやPodの状態、あるいはリクエスト数などの基本的なダッシュボードは既に用意されているのですぐに利用できるかと思います。
![001.png](https://qiita-image-store.s3.amazonaws.com/0/54904/e8bd9766-b110-95ea-4130-e4b83296640b.png)

![002.png](https://qiita-image-store.s3.amazonaws.com/0/54904/760ac78b-59ad-2970-dc9d-18a2382d33e4.png)

### OpenZipkinを開く

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

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

![003.png](https://qiita-image-store.s3.amazonaws.com/0/54904/91a57827-65e4-5b28-cf29-7cf15cb4f791.png)


### 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!

参考
--------

- [Knative Doc](https://www.knative.dev/docs/)
- [Knative-Tutorial](https://github.com/meteatamel/knative-tutorial)
- [最強のServerlessプラットフォーム？ Knativeを動かしてみるぞい](https://qiita.com/jacopen/items/d7f12703dcfa19242bcb)