kind + Knativeでローカルにサーバーレス環境をつくる

#はじめに

ここには、kindセットアップ済みの環境にKnativeをインストールする手順を記録しておきます。
kindのセットアップは別記事「[Linux環境でKubernetesクラスターを動かす](https://qiita.com/miekoma/items/cb1dd4528f19ff8eb37a)」にまとめてます。

今回の環境はこちら：
Ubuntu 20.04.3
Docker version 20.10.12
kubectl version 1.23.0
kind version 0.11.1
Knative version 1.1.0

#インストール手順
公式の[Installing Knative](https://knative.dev/docs/install/)や、[Blog: How to set up a local Knative environment with KinD and without DNS headaches](https://knative.dev/blog/articles/set-up-a-local-knative-environment-with-kind/)あたりの情報を参考にKnativeをセットアップしていきます。

###Kubernetesクラスターの作成
Knative用のクラスターを作成します。まずは設定ファイルを作成して、Ingressへアクセスするポート番号をextraPortMappingsで設定します。

```yaml
$ cat > clusterconfig.yaml <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  extraPortMappings:
    ## expose port 31080 of the node to port 80 on the host
  - containerPort: 31080
    hostPort: 80
    ## expose port 31443 of the node to port 443 on the host
  - containerPort: 31443
    hostPort: 443
EOF
```
ここでは、ホスト上の80番ポートをコンテナーの31080に、443ポートを31443にマッピングしています。
次にこの設定ファイルを使って、クラスターを作成します。

```none
$ sudo kind create cluster --name knative --config clusterconfig.yaml
Creating cluster "knative" ...
 ✓ Ensuring node image (kindest/node:v1.21.1) 🖼
 ✓ Preparing nodes 📦  
 ✓ Writing configuration 📜 
 ✓ Starting control-plane 🕹️ 
 ✓ Installing CNI 🔌 
 ✓ Installing StorageClass 💾 
Set kubectl context to "kind-knative"
You can now use your cluster with:

kubectl cluster-info --context kind-knative

Thanks for using kind! 😊
```
###Knative Servingのインストール
KnativeにはKnative EventingというEvent-Driven用のコンポーネントもありますが、今回はRequest-Replyモデルのアプリケーションが動けばいいのでKnative Servingだけインストールします。

* CRD(Custom Resource Definition)のApply

```console
$ sudo kubectl apply -f https://github.com/knative/serving/releases/download/knative-v1.1.0/serving-crds.yaml
```
ログ

```shell-session
ustomresourcedefinition.apiextensions.k8s.io/certificates.networking.internal.knative.dev created
customresourcedefinition.apiextensions.k8s.io/configurations.serving.knative.dev created
customresourcedefinition.apiextensions.k8s.io/clusterdomainclaims.networking.internal.knative.dev created
customresourcedefinition.apiextensions.k8s.io/domainmappings.serving.knative.dev created
customresourcedefinition.apiextensions.k8s.io/ingresses.networking.internal.knative.dev created
customresourcedefinition.apiextensions.k8s.io/metrics.autoscaling.internal.knative.dev created
customresourcedefinition.apiextensions.k8s.io/podautoscalers.autoscaling.internal.knative.dev created
customresourcedefinition.apiextensions.k8s.io/revisions.serving.knative.dev created
customresourcedefinition.apiextensions.k8s.io/routes.serving.knative.dev created
customresourcedefinition.apiextensions.k8s.io/serverlessservices.networking.internal.knative.dev created
customresourcedefinition.apiextensions.k8s.io/services.serving.knative.dev created
customresourcedefinition.apiextensions.k8s.io/images.caching.internal.knative.dev created
```

* Knative Serving Core Componentのインストール

```console
$ sudo kubectl apply -f https://github.com/knative/serving/releases/download/knative-v1.1.0/serving-core.yaml
```
ログ

```shell-session
namespace/knative-serving created
clusterrole.rbac.authorization.k8s.io/knative-serving-aggregated-addressable-resolver created
clusterrole.rbac.authorization.k8s.io/knative-serving-addressable-resolver created
clusterrole.rbac.authorization.k8s.io/knative-serving-namespaced-admin created
clusterrole.rbac.authorization.k8s.io/knative-serving-namespaced-edit created
clusterrole.rbac.authorization.k8s.io/knative-serving-namespaced-view created
clusterrole.rbac.authorization.k8s.io/knative-serving-core created
clusterrole.rbac.authorization.k8s.io/knative-serving-podspecable-binding created
serviceaccount/controller created
clusterrole.rbac.authorization.k8s.io/knative-serving-admin created
clusterrolebinding.rbac.authorization.k8s.io/knative-serving-controller-admin created
clusterrolebinding.rbac.authorization.k8s.io/knative-serving-controller-addressable-resolver created
customresourcedefinition.apiextensions.k8s.io/images.caching.internal.knative.dev unchanged
customresourcedefinition.apiextensions.k8s.io/certificates.networking.internal.knative.dev unchanged
customresourcedefinition.apiextensions.k8s.io/configurations.serving.knative.dev unchanged
customresourcedefinition.apiextensions.k8s.io/clusterdomainclaims.networking.internal.knative.dev unchanged
customresourcedefinition.apiextensions.k8s.io/domainmappings.serving.knative.dev unchanged
customresourcedefinition.apiextensions.k8s.io/ingresses.networking.internal.knative.dev unchanged
customresourcedefinition.apiextensions.k8s.io/metrics.autoscaling.internal.knative.dev unchanged
customresourcedefinition.apiextensions.k8s.io/podautoscalers.autoscaling.internal.knative.dev unchanged
customresourcedefinition.apiextensions.k8s.io/revisions.serving.knative.dev unchanged
customresourcedefinition.apiextensions.k8s.io/routes.serving.knative.dev unchanged
customresourcedefinition.apiextensions.k8s.io/serverlessservices.networking.internal.knative.dev unchanged
customresourcedefinition.apiextensions.k8s.io/services.serving.knative.dev unchanged
image.caching.internal.knative.dev/queue-proxy created
configmap/config-autoscaler created
configmap/config-defaults created
configmap/config-deployment created
configmap/config-domain created
configmap/config-features created
configmap/config-gc created
configmap/config-leader-election created
configmap/config-logging created
configmap/config-network created
configmap/config-observability created
configmap/config-tracing created
horizontalpodautoscaler.autoscaling/activator created
Warning: policy/v1beta1 PodDisruptionBudget is deprecated in v1.21+, unavailable in v1.25+; use policy/v1 PodDisruptionBudget
poddisruptionbudget.policy/activator-pdb created
deployment.apps/activator created
service/activator-service created
deployment.apps/autoscaler created
service/autoscaler created
deployment.apps/controller created
service/controller created
deployment.apps/domain-mapping created
deployment.apps/domainmapping-webhook created
service/domainmapping-webhook created
horizontalpodautoscaler.autoscaling/webhook created
poddisruptionbudget.policy/webhook-pdb created
deployment.apps/webhook created
service/webhook created
validatingwebhookconfiguration.admissionregistration.k8s.io/config.webhook.serving.knative.dev created
mutatingwebhookconfiguration.admissionregistration.k8s.io/webhook.serving.knative.dev created
mutatingwebhookconfiguration.admissionregistration.k8s.io/webhook.domainmapping.serving.knative.dev created
secret/domainmapping-webhook-certs created
validatingwebhookconfiguration.admissionregistration.k8s.io/validation.webhook.domainmapping.serving.knative.dev created
validatingwebhookconfiguration.admissionregistration.k8s.io/validation.webhook.serving.knative.dev created
secret/webhook-certs created
```
###ネットワーク・レイヤーのインストール
以前はKnativeのネットワークといえばIstioだったようですが、今では選択肢が増えています。今回はKourierという軽量なIngressを使って外部からクラスターへのアクセスを制御します。

* yamlファイルをダウンロード
ローカル構成用にカスタマイズするため、kourier.yamlファイルをダウンロードします。

```console
$ curl -Lo kourier.yaml https://github.com/knative/net-kourier/releases/download/knative-v1.1.0/kourier.yaml
```
* yamlファイルの編集

type: LoadBalancerをnodePortに変更します。

```(抜粋)kourier.yaml 
apiVersion: v1
kind: Service
metadata:
  name: kourier
  namespace: kourier-system
  labels:
    networking.knative.dev/ingress-provider: kourier
    app.kubernetes.io/component: net-kourier
    app.kubernetes.io/version: "1.1.0"
    app.kubernetes.io/name: knative-serving
    serving.knative.dev/release: "v1.1.0"
spec:
  ports:
    - name: http2
      port: 80
      protocol: TCP
      targetPort: 8080
      nodePort: 31080                # 追加
    - name: https
      port: 443
      protocol: TCP
      targetPort: 8443
      nodePort: 31443                # 追加
  selector:
    app: 3scale-kourier-gateway
  type: NodePort                     # LoadBalancerから変更
```
* Kourierコントローラーをインストールします。

```console
$ sudo kubectl apply --filename kourier.yaml
```
ログ

```shell-session
namespace/kourier-system created
configmap/kourier-bootstrap created
configmap/config-kourier created
serviceaccount/net-kourier created
clusterrole.rbac.authorization.k8s.io/net-kourier created
clusterrolebinding.rbac.authorization.k8s.io/net-kourier created
deployment.apps/net-kourier-controller created
service/net-kourier-controller created
deployment.apps/3scale-kourier-gateway created
service/kourier created
service/kourier-internal created
```
* Knative ServingがKourierを使用するように構成します。

```console
$ sudo kubectl patch configmap/config-network \
  --namespace knative-serving \
  --type merge \
  --patch '{"data":{"ingress-class":"kourier.ingress.networking.knative.dev"}}'
```

設定が正しく反映されているかは以下のコマンドで確認できます。

```console
$ sudo kubectl describe configmap/config-network --namespace knative-serving

(省略)
ingress-class:
----
kourier.ingress.networking.knative.dev
(省略)
```

###DNSの構成
ここではワイルドカードDNSサービスであるsslip.ioを使用するよう構成します。

```console
$ sudo kubectl apply -f https://github.com/knative/serving/releases/download/knative-v1.1.0/serving-default-domain.yaml

job.batch/default-domain created
service/default-domain-service created
```

config-domainに以下を追加して、カスタム・ドメイン"*.127.0.0.1.sslip.io"を127.0.0.1に解決します。

```shell-session
$ kubectl patch configmap/config-domain \
  --namespace knative-serving \
  --type merge \
  --patch '{"data":{"127.0.0.1.sslip.io":""}}'

configmap/config-domain patched
```

設定が正しく反映されているかは以下のコマンドで確認できます。

```console
$ sudo kubectl describe configmap/config-domain --namespace knative-serving

(抜粋)
Data
====
127.0.0.1.sslip.io:
----
```

###Knativeコンポーネントの稼働確認
pod一覧を表示して、すべてのpodがRunningまたはCompleted STATUSになっていることを確認します。以下は出力例です (pod名など環境によって異なります)。

```shell-session
$ sudo kubectl get pods -n knative-serving
NAME                                      READY   STATUS    RESTARTS   AGE
activator-6fb68fff7b-cqgbx                1/1     Running   0          18m
autoscaler-54f48f5bb7-bwfls               1/1     Running   0          18m
controller-66cb4b556b-649sl               1/1     Running   0          18m
domain-mapping-66f8d5bc4c-vh7c2           1/1     Running   0          18m
domainmapping-webhook-55bdf595dd-gwdjr    1/1     Running   0          18m
net-kourier-controller-7df5866d8d-cvhh9   1/1     Running   0          3m8s
webhook-6d5c77f989-qq4gk                  1/1     Running   0          18m

$ sudo kubectl get pods -n kourier-system
NAME                                      READY   STATUS    RESTARTS   AGE
3scale-kourier-gateway-58856c6cc7-qqp69   1/1     Running   0          3m30s
```

設定通りにポート番号が使用されていることを確認します。

```shell-session
$ sudo kubectl --namespace kourier-system get service kourier
NAME      TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
kourier   NodePort   10.96.152.240   <none>        80:31080/TCP,443:31443/TCP   4m36s
```

```shell-session
goma@gomagame-ub:~$ sudo docker ps -a
CONTAINER ID   IMAGE                  COMMAND                  CREATED          STATUS          PORTS                                                                      NAMES
c7f672d87425   kindest/node:v1.21.1   "/usr/local/bin/entr…"   22 minutes ago   Up 22 minutes   127.0.0.1:38523->6443/tcp, 0.0.0.0:80->31080/tcp, 0.0.0.0:443->31443/tcp   knative-control-plane
```

#サンプル・アプリケーションの実行
GitHub [knative/docs](https://github.com/knative/docs)にはKnativeのサンプル・アプリなどが色々公開されています。その中のKnative Serving用サンプル、[helloworld-go](https://github.com/knative/docs/tree/main/code-samples/serving/hello-world/helloworld-go)アプリを動かしてみます。

* GitHubリポジトリーをローカルにclone

```console
$ git clone https://github.com/knative/docs.git knative-docs
Cloning into 'knative-docs'...
remote: Enumerating objects: 36892, done.
remote: Counting objects: 100% (141/141), done.
remote: Compressing objects: 100% (119/119), done.
remote: Total 36892 (delta 47), reused 31 (delta 11), pack-reused 36751
Receiving objects: 100% (36892/36892), 54.71 MiB | 13.18 MiB/s, done.
Resolving deltas: 100% (23693/23693), done.
goma@gomagame-ub:~$ cd knative-docs/code-samples/serving/hello-world/helloworld-go
```
これ以降は、knative-docs/code-samples/serving/hello-world/helloworld-goディレクトリーで作業します。

### Docker build & push

ローカルでコンテナーをBuildして、Docker HubへPushします。
ここで{username}はDocker Hubのユーザー名を指定します。Docker Hubのアカウントを持っていない場合は、[Docker Hub](https://hub.docker.com/)サイトでSign Upしてアカウントを作成しておきましょう。

```cnone
$ sudo docker build -t {username}/helloworld-go .
$ sudo docker push {username}/helloworld-go
```
pushする前に`docker login`しておくこともお忘れなく。

### Deploy

Buid & Pushしたアプリをクラスターにデプロイします。
まずはservice.yamlファイルを編集して、{username}の部分を自分のDocker Hubユーザー名に書き換えておきます。

```console
$ vi service.yaml
```

```service.yaml
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  name: helloworld-go
  namespace: default
spec:
  template:
    spec:
      containers:
      - image: docker.io/{username}/helloworld-go
        env:
        - name: TARGET
          value: "Go Sample v1"
```

service.yamlをapplyすると、アプリがデプロイされます。

```console
$ sudo kubectl apply --filename service.yaml
service.serving.knative.dev/helloworld-go configured
```

###実行

以下のコマンドでURLを調べます。

```console
$ sudo kubectl get ksvc helloworld-go

(出力例)
NAME            URL                                               LATESTCREATED         LATESTREADY           READY   REASON
helloworld-go   http://helloworld-go.default.127.0.0.1.sslip.io   helloworld-go-00001   helloworld-go-00001   True
```

リクエストを実行します。
以下のように、Helloメッセージが表示されれば成功です。

```console
$ curl http://helloworld-go.default.127.0.0.1.sslip.io
Hello Go Sample v1!
```

###クラスターの削除
要らなくなったknativeクラスターは以下のコマンドで削除できます。

```none
$ sudo kind delete cluster --name knative
```


#まとめ
kindとKnativeで簡単にローカルサーバーレス環境を構築することができました。お次は、サーバーレス・アプリケーションを作ってこの環境で動かしてみたいと思います。