Deploy function via GitLab Serverless

この投稿は[FUJITSU その2 Advent Calendar 2018](https://qiita.com/advent-calendar/2018/fujitsu2)の22日目の記事です。
(注意)この記事は個人の見解であり、所属する会社、組織を代表するものではありません。

諸事情により投稿が遅くなりました。ごめんなさい。

# GitLab Serverlessとは

12月22日にリリースされたばかりの[GitLab 11.6](https://about.gitlab.com/product/serverless/)の新機能です。
GitLab Serverlessを利用することで、[Knative](https://cloud.google.com/knative/)をインストールしたKubernetes上にGitLab CI経由でサーバレスアプリケーションをデプロイすることができます。

また、サーバレスアプリケーションの管理ツールである[TriggerMesh](https://triggermesh.com/)を利用することでサーバレスアプリケーションデプロイ後の管理もGitLab上から行えるようになっています。

今回はいち早くGitLab Serverlessを利用して実際にアプリケーションをデプロイするところまでやってみたいと思います。

...が、先に言ってしまうと結果としてはCI実行中にエラーが発生し、アプリケーションを実行するまでには至りませんでした。

時間切れとなってしまったので、今後解決でき次第更新したいと思います。

## 事前準備

### GitLab環境構築

[Cloud Native GitLab Helm Chart](https://docs.gitlab.com/ee/install/kubernetes/gitlab_chart.html)を使ってKubernetesクラスタ上にデプロイしたGitLabで検証しています。

GitLabのk8s連携機能を利用するにあたりGitLab自体をk8sクラスタ上で実行する必要は特にありません。
が、今回はk8s上ですべて完結させてみたかったので上記helmチャートを利用しました。

構築方法は主題ではないので引っかかった部分だけ簡単に説明すると、
今回利用したバージョン`1.4.0`のhelmチャートが依存している`gitlab-runner` helmチャートのバージョン`0.1.38`では
以下の問題が発生したので、公式ドキュメントの方法にいくつか設定を追加しています。

- `gitlab-runner`のPodが`CrashLoopBackOff`を繰り返し立ち上がらない
    - helmチャートから生成されるmanifestに[gitlab-runnerのlock有無を指定する環境変数の値が指定されない](https://gitlab.com/gitlab-org/gitlab-runner/issues/3826)ことが原因です。
    - `helm upgradle --install`時に明示的に`--set gitlab-runner.runners.locked=false`を指定することで解決します。
- CI実行時に`a DNS-1123 subdomain must consist of lower case alphanumeric characters...`というエラーが発生し失敗する
    - CI JobをPodとして実行できるrunnerの実行方式である`kubernetes executor`に問題があり、[CI Job実行時に起動するPod名がk8sの命名規則に準拠していない](https://gitlab.com/gitlab-org/gitlab-runner/issues/3807)ため、GitLabとの通信時のDNS参照に失敗します。
    - `gitlab-runner` helmチャートのバージョン`0.1.38`で起動する`gitlab-runner:alpine-v11.5.0`コンテナイメージに固有の問題のようなので、`helm upgradle --install`時に明示的に`--set gitlab-runner.image=gitlab/gitlab-runner:alpine-v11.5.1`以上を指定することで解決します。

これ以降の内容はk8s上でGitLabを実行し、GitLabの[k8s連携](https://docs.gitlab.com/ee/user/project/clusters/index.html)を有効にしている前提とします。

### Knativeをk8sにデプロイする

[Knative](https://github.com/knative/docs)はKubernetesのオペレーションやコンテナイメージビルド、デプロイの複雑さを低減し、開発者がコードを書くこと自体により集中できるようにしつつ、コンテナの恩恵を受けたサービス構築を実現するツールです。

また、冒頭にサーバレスアプリケーションをデプロイできるといったとおり、通常Kubernetesではできない、普段Podが0の状態で、トリガーとなるイベント発生時に初めてPodを起動するといったワークロードが実現できます。
Knativeは以下のコンポーネントで構成されています。

- Build: ソースコードからコンテナイメージをビルド、レジストリへプッシュの一連の作業を実行
- Eventing: サポートするSubscribe先からのメッセージを受け取る
- Serving: アプリケーションをデプロイ、公開し、デフォルトではPodが0の状態からリクエストを受けPodのスケーリングを行います。また、サービスメッシュである[Istio](https://istio.io/)によりルーティングを制御します。

GitLab Serverlessを利用するため事前にKubernetesへデプロイしておく必要がありますが、GitLabではKnativeのインストールもGitLabのWeb UI経由で実行できてしまいます。

#### helm-tillerのデプロイ

GitLab経由でKnativeをデプロイするにあたり、まずはGitLab経由でhelmをインストールする必要があります。

k8sを有効にしたプロジェクトの`Operations > Kubernetes Clusters > 連携済みKubernetesクラスタ名`の画面からHelm Tillerの`install`を実行します。

しばらくすると表示が`installed`となります。

![スクリーンショット 2018-12-23 17.56.59.png](https://qiita-image-store.s3.amazonaws.com/0/193568/8bffbf53-aa92-1f29-7886-9a81e14ac821.png)

ここでk8sのPodを確認すると、`gitlab-managed-apps`にtiller podがデプロイされていることがわかります。

```
$ kubectl get pod -n gitlab-managed-apps
NAME                             READY     STATUS    RESTARTS   AGE
tiller-deploy-74f5d65d77-k5pzn   1/1       Running   0          5m
```

#### knativeデプロイ

helmインストール時と同じ画面からKnativeをデプロイします。
この際、サーバレスアプリケーションを公開するドメイン名を指定する必要があります。

![スクリーンショット 2018-12-23 17.58.00.png](https://qiita-image-store.s3.amazonaws.com/0/193568/8bcf02f2-f24f-bf83-dd19-006c9a47fb85.png)

しばらくすると、表示が`installed`となり、`Knative IP Address`欄が追加されます。
ここに表示されているIPアドレスを指定したドメインのワイルドカードDNS Aレコードとして登録しておきます。

このIPアドレスはアプリケーションへのルーティングを担う`knative-ingressgateway`コンポーネントのEXTERNAL-IPであり、以下のコマンドでも取得できます。

```
$ kubectl get svc --namespace=istio-system knative-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip} '
35.243.127.45
```

ここでk8sクラスタの状態を確認します。
まず、Istioがデプロイされていることが確認できます。

```
$ kubectl get pod -n istio-system 
NAME                                        READY     STATUS    RESTARTS   AGE
istio-citadel-84fb7985bf-c6vfg              1/1       Running   0          15m
istio-egressgateway-bd9fb967d-mvblb         1/1       Running   0          15m
istio-galley-655c4f9ccd-h5mhx               1/1       Running   0          15m
istio-ingressgateway-688865c5f7-jcqpp       1/1       Running   0          15m
istio-pilot-6cd69dc444-bb77k                2/2       Running   0          15m
istio-policy-6b9f4697d-nbtmz                2/2       Running   0          15m
istio-sidecar-injector-8975849b4-jt8d2      1/1       Running   0          15m
istio-statsd-prom-bridge-7f44bb5ddb-mqqq2   1/1       Running   0          15m
istio-telemetry-6b5579595f-pxrf9            2/2       Running   0          15m
knative-ingressgateway-5f5dc4b4cd-xw2qg     1/1       Running   0          15m
```

さらに、Knativeのコンポーネントである`knative-build`、`knative-serving`がそれぞれ別のネームスペースにデプロイされています。

```
$ kubectl get pod -n knative-build
NAME                                READY     STATUS    RESTARTS   AGE
build-controller-65f855ccd6-dgg87   1/1       Running   0          14m
build-webhook-864d45f66f-hdtcs      1/1       Running   0          14m
```

```
$ kubectl get pod -n knative-serving
NAME                          READY     STATUS    RESTARTS   AGE
activator-847cf57479-6tzmk    2/2       Running   0          15m
controller-5d7f46bfd6-rbqrf   1/1       Running   0          15m
webhook-7f8ddf4499-hgv49      1/1       Running   0          15m
```

ここまでで事前準備は完了です。
実際にアプリケーションをデプロイしていきます。

## サーバレスアプリケーションのデプロイ

### GitLab Serverlessで利用可能なランタイム

現在は`node.js`または`kaniko`がサポートされているようです。
今回はnode.jsアプリケーションを試します。

### `.gitlab-ci.yml`の作成

以下の内容の.gitlab-ci.ymlをプロジェクトのリポジトリに追加します。
TriggerMeshのCLIである`tm`コマンドを実行し、Knativeの機能を利用してコンテナイメージビルド〜デプロイまでを実行するJobを定義しています。

```
stages:
  - deploy

functions:
  stage: deploy
  environment: test
  image: gcr.io/triggermesh/tm:v0.0.7
  script:
    - tm -n "$KUBE_NAMESPACE" set registry-auth gitlab-registry --registry "$CI_REGISTRY" --username "$CI_REGISTRY_USER" --password "$CI_JOB_TOKEN"
    - tm -n "$KUBE_NAMESPACE" --registry-host "$CI_REGISTRY_IMAGE" deploy --wait
```

### `serverless.yaml`の作成

GitLab Serverlessでは下記ファイルでKnativeでアプリケーションをデプロイする際のメタ情報を定義するようです。
このあたりがGitLab CI/CDと統合された、ピュアKnativeとは異なるところでしょうか。

アプリケーションのサービス名やビルドするソースコードを格納するディレクトリ、アプリケーションのランタイムなどを定義します。

```
service: knative-test
description: "Deploying functions from GitLab using Knative"

provider:
  name: triggermesh
  registry-secret: gitlab-registry
  environment:
    FOO: BAR

functions:
  echo:
    handler: echo
    runtime: https://gitlab.com/triggermesh/runtimes/raw/master/nodejs.yaml
    description: "echo function using node.js runtime"
    buildargs:
      - DIRECTORY=echo
  environment:
    FUNCTION: echo
```

### アプリケーションコードをリポジトリに格納

[TriggerMeshのサンプルコード](https://gitlab.com/triggermesh/examples)のうち、node.jsのサンプルコードを`serverless.yaml`の`buildargs`に定義したソース格納先である`echo`ディレクトリ配下に格納します。

### リポジトリにPushしCIを実行

リポジトリにソースコード及び`serverless.yaml`、`.gitlab-ci.yml`をPushすることでCIが実行され、Knativeによりアプリケーションのコンテナイメージビルド〜デプロイ、公開までが実行されます。

...のはずでしたが、どうやらGitLabのコンテナレジストリへのコンテナイメージPush時に認証エラーとなっており、CIに失敗してしまいました。

以下はCI Jobのログです。

```
Running with gitlab-runner 11.5.1 (7f00c780)
  on gitlab-gitlab-runner-5c7d4bfbdf-bvqbj EnNm5sja
Using Kubernetes namespace: default
Using Kubernetes executor with image gcr.io/triggermesh/tm:v0.0.7 ...
Waiting for pod default/runner-ennm5sja-project-1-concurrent-08fk4p to be running, status is Pending
Waiting for pod default/runner-ennm5sja-project-1-concurrent-08fk4p to be running, status is Pending
Waiting for pod default/runner-ennm5sja-project-1-concurrent-08fk4p to be running, status is Pending
Waiting for pod default/runner-ennm5sja-project-1-concurrent-08fk4p to be running, status is Pending
Waiting for pod default/runner-ennm5sja-project-1-concurrent-08fk4p to be running, status is Pending
Waiting for pod default/runner-ennm5sja-project-1-concurrent-08fk4p to be running, status is Pending
Running on runner-ennm5sja-project-1-concurrent-08fk4p via gitlab-gitlab-runner-5c7d4bfbdf-bvqbj...
Cloning repository...
Cloning into '/k-srkw/serverless-test'...
Checking out 6a038601 as master...
Skipping Git submodules setup
$ tm -n "$KUBE_NAMESPACE" set registry-auth gitlab-registry --registry "$CI_REGISTRY" --username "$CI_REGISTRY_USER" --password "$CI_JOB_TOKEN"
Registry credentials set
$ tm -n "$KUBE_NAMESPACE" --registry-host "$CI_REGISTRY_IMAGE" deploy --wait
Creating knative-test-echo function
Deployment started. Run "tm -n serverless-test-1 describe service knative-test-echo" to see the details
Waiting for ready state....2018/12/23 18:32:09 Revision "knative-test-echo-00001" failed with message: "no token in bearer response:\n{\"errors\":[{\"code\":\"DENIED\",\"message\":\"access forbidden\"}],\"http_status\":403}".
ERROR: Job failed: command terminated with exit code 1
```

実際にアプリケーションがデプロイされるとプロジェクトのWeb UI上のOperations > Serverlessからfunction名やURLが確認できるのですが、その登録自体は行われていました。

ただし、アプリケーションはデプロイできていないため、アクセスはできませんでした。

今回はここまで。
上記問題が解決できたら更新したいと思います。