CAPで作ったサービスをKyma runtimeにデプロイ(2) 基本的なCAPサービスをデプロイ


## はじめに
chillSAP [夏の自由研究2022](https://note.com/chillsap/n/n62740563aee0)、8/30の記事として執筆しています。

この記事は[CAPで作ったサービスをKyma runtimeにデプロイ](https://qiita.com/tami/items/8a2921217eba458eeb8a)シリーズの2回目です。以下のチュートリアルを参考にしつつ、

https://developers.sap.com/mission.btp-deploy-cap-kyma.html

- CAPで作ったアプリをKymaにデプロイするにはどのような仕組みが使われているか
- 一からアプリを作成してKymaにデプロイするには何が必要か

について確認することを目的としています。
今回は、基本的なCAPサービス（認証なし）を作成してKyma runtimeにデプロイします。



## 基本的なCAPアプリを作成してKyma runtimeにデプロイ
**ステップ**
1. CAPプロジェクトを生成
1. Dockerイメージを作成
1. Helmの設定
1. Kyma runtimeにデプロイ
1. デプロイ結果の確認

### 1. CAPプロジェクトを生成
#### 1.1. プロジェクトの生成
以下のコマンドでCAPプロジェクトを生成します。

```
cds init <プロジェクト名>

例
cds init capkyma
```
#### 1.2. サンプルスキーマとサービスの生成
時間短縮のため、以下のコマンドでサンプルのスキーマとサービスを生成します。

```
cds add samples
```

#### 1.3. HANAにデプロイ用の設定を追加
以下のコマンドで、HANAにデプロイするための設定を追加します。

```
cds add hana --for production
```

### 2. Dockerイメージを作成
#### 2.1 Buildpackを使用したイメージの作成
Dockerイメージの作成方法については、自分でDockerfileを作成する方法もありますが、[チュートリアル](https://developers.sap.com/tutorials/btp-app-kyma-deploy-application.html#b8ffa897-336a-4ee5-980e-825fc8fae6d8)では[Cloud Native Buildpacks](https://buildpacks.io/)というものを使ってイメージを作成しています。
Buildpacksを使うと、Dockerfileを使わずにソースコードをコンテナイメージに変換することができます。Buildpackはアプリケーションを実行するのに必要なdependency（モジュール）を自動的に検知してインストールし、クラウド環境で実行できる状態にしてくれます。

**参考**
- https://blogs.sap.com/2022/07/12/cap-on-kyma-now-officially-supported/
- https://buildpacks.io/docs/concepts/

#### 2.2 イメージの作成対象
Kyma runtimeにデプロイするために、2つのDockerイメージを作成する必要があります。一つはCAPで作成したサービスのため、もう一つはHANA Cloudにテーブルやビューをデプロイするためのものです。

MTAを使ってCloud Foundryにデプロイするときも、mta.yamlに以下のように2つのモジュールが設定されます（今回mta.yamlは使いません）。以下の`capkyma-srv`と`capkyma-db-deployer`に対応するDockerイメージを作成するということになります。

```yaml:mta.yamlの場合
modules:
  - name: capkyma-srv
    type: nodejs
    path: gen/srv
    parameters:
      buildpack: nodejs_buildpack
    build-parameters:
      builder: npm-ci
    provides:
      - name: srv-api # required by consumers of CAP services (e.g. approuter)
        properties:
          srv-url: ${default-url}
    requires:
      - name: capkyma-db

  - name: capkyma-db-deployer
    type: hdb
    path: gen/db
    parameters:
      buildpack: nodejs_buildpack
    requires:
      - name: capkyma-db
```

以下ではプロジェクトをビルドし、各モジュールのためのDockerイメージを作成していきます。

#### 2.3. ビルド
以下のコマンドでビルドします。

```
cds build --production
```
オプションとして、以下のコマンドでサンプルデータがデプロイされないように消すこともできます。今回はサンプルデータで動作確認したいので、そのまま残しておきます。

```
rm -rf gen/db/data
```
/genフォルダにdbモジュールとsrvモジュールのデプロイ用のソースが格納されました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/f1d9c21f-c90c-eda6-52b8-37f55fe75518.png)

#### 2.4. CAPサービスのDockerイメージを作成
Buildpackを使ってイメージを作成します。以下のコマンドを実行します。`pack`というコマンドは、[事前準備編](https://qiita.com/tami/items/8a2921217eba458eeb8a#1-%E7%92%B0%E5%A2%83%E8%A8%AD%E5%AE%9A)でインストールしたものです。

```
pack build <コンテナレジストリ※>/<CAPのサービス名> --path gen/srv \
--buildpack gcr.io/paketo-buildpacks/nodejs \
--builder paketobuildpacks/builder:base

# 例
pack build miyasuta/capkyma-srv --path gen/srv \
--buildpack gcr.io/paketo-buildpacks/nodejs \
--builder paketobuildpacks/builder:base

# Powershellの場合、改行はバックスクオート
pack build miyasuta/capkyma-srv --path gen/srv `
--buildpack gcr.io/paketo-buildpacks/nodejs `
--builder paketobuildpacks/builder:base
```

※コンテナレジストリ：Dokcer Hubを使用する場合、Docker HubのユーザIDを指定する

イメージの作成が終わると、`docker image ls`コマンドでイメージができていることを確認できます。（作成がなぜか42年前になっていますが…）

```
REPOSITORY                                                      TAG                 IMAGE ID            CREATED             SIZE
miyasuta/capkyma-srv                                            latest              589516e87e29        42 years ago        261MB
```
#### 2.5. DBデプロイヤーのDockerイメージを作成
以下のコマンドを実行します。

```
pack build <コンテナレジストリ>/<deployer名> --path gen/db \
    --buildpack gcr.io/paketo-buildpacks/nodejs \
    --builder paketobuildpacks/builder:base

# 例
pack build miyasuta/capkyma-db-deployer --path gen/db \
    --buildpack gcr.io/paketo-buildpacks/nodejs \
    --builder paketobuildpacks/builder:base
```

#### 2.6. Dockerイメージをプッシュ
コンテナレジストリにログインします。

```
docker login
```

以下のコマンドでDockerイメージをコンテナレジストリにプッシュします。

```
docker push <コンテナレジストリ>/<CAPのサービス名>
docker push <コンテナレジストリ>/<deployer名>

例
docker push miyasuta/capkyma-srv
docker push miyasuta/capkyma-db-deployer
```
Docker Hubでみると、以下のように登録されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/1f19a71a-9c68-c628-c9f9-7ddd201812c0.png)

### 3. Helmの設定
[前回](https://qiita.com/tami/items/8a2921217eba458eeb8a#cap%E3%81%AE%E5%A0%B4%E5%90%88)の記事で書いたように、CAPのデプロイには[Helm](https://helm.sh/)というツールを使います。前提としてhelmのインストールが必要です。
まずは以下のコマンドを実行してみましょう。

```
cds add helm
```
すると、chartフォルダが追加されました。この中にデプロイ用のyamlファイルを作成するためのテンプレートと、テンプレートに値を設定するvalue.yamlファイルが入っています。手を入れるのはvalues.yamlファイルだけです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/b2dbe0b6-f4f4-8020-f68c-25492fc383ef.png)

values.yamlの初期状態は以下のようになっています。

```yaml
global:
  domain: null
  imagePullSecret:
    name: null
srv:
  bindings:
    db:
      fromSecret: null
  image:
    repository: <your-container-registry>/capkyma-srv
    tag: latest
  resources:
    limits:
      cpu: 100m
      ephemeral-storage: 1G
      memory: 500M
    requests:
      cpu: 100m
      ephemeral-storage: 1G
      memory: 500M
  health_check:
    liveness:
      path: /
    readiness:
      path: /
hana_deployer:
  image:
    repository: <your-container-registry>/capkyma-hana-deployer
    tag: latest
  bindings:
    hana:
      fromSecret: null
```

更新箇所は4つあります。

**①globalセクション**
globalセクションでは`domain`と`imagePullSecret.name`を指定します。
```yaml
global:
  domain: <Kyma runtimeのドメイン>
  imagePullSecret:
    name: <Dockerレジストリ用のシークレット>
```

`domain`にはデプロイ先のKyma runtimeのドメインを指定します。以下のコマンドで確認できます。
```
kubectl get gateway -n kyma-system kyma-gateway -o jsonpath='{.spec.servers[0].hosts[0]}'
```
以下のような結果が返ってくるので、先頭の`*.`を除いた部分を設定します。

```
# 結果（例）
*.a2985eb.kyma.shoot.live.k8s-hana.ondemand.com

# 設定値（例）
a2985eb.kyma.shoot.live.k8s-hana.ondemand.com
```
`imagePullSecret.name`には[事前準備](https://qiita.com/tami/items/8a2921217eba458eeb8a#1-%E7%92%B0%E5%A2%83%E8%A8%AD%E5%AE%9A)で作成したDockerレジストリアクセス用のシークレットを指定します。

**②srvセクション**
srvセクションでは`bindings.db.fromSecret`と`image.repository`を指定します。
```yaml
srv:
  bindings:
    db:
      fromSecret: capkyma-db
  image:
    repository: docker.io/miyasuta/capkyma-srv # Docker Hubの場合、docker.io/をつける
    tag: latest
```
`bindings.db.fromSecret`に[事前準備](https://qiita.com/tami/items/8a2921217eba458eeb8a#233-kyma-runtime%E3%81%AB%E3%82%B7%E3%83%BC%E3%82%AF%E3%83%AC%E3%83%83%E3%83%88%E3%82%92%E4%BD%9C%E6%88%90)で作成したHDIコンテナにアクセスするためのシークレットを指定します。
`image.repository`に2.6.でプッシュしたCAPサービスのイメージを指定します。

**③hana_deployerセクション**
hana_deployerセクションでは`image.repository`と`bindings.hana.fromSecret`を指定します。

```yaml
hana_deployer:
  image:
    repository: docker.io/miyasuta/capkyma-hana-deployer
    tag: latest
  bindings:
    hana:
      fromSecret: capkyma-db
```
`image.repository`に2.6.でプッシュしたDBデプロイヤーのイメージを指定します。
`bindings.hana.fromSecret`にHDIコンテナにアクセスするためのシークレットを指定します。

#### どんなyamlファイルが生成されるか
実際に生成されるデプロイ用のyamlファイルは内部的なもので、ローカル環境には作成されません。ですが、Helmの[template](https://helm.sh/docs/helm/helm_template/)コマンドで生成されるyamlをターミナル上で見ることができます。
以下のコマンドを実行します。
```
helm template <name> <chartフォルダのパス>

# 例 
helm template capkyma ./chart
```
すると、以下のように長い結果が返ってきます。
```
---
# Source: capkyma/charts/srv/templates/network-policy.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: capkyma-srv
  labels:
    helm.sh/revision: "1"
    helm.sh/chart: srv-0.1.0
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/name: srv
    app.kubernetes.io/instance: capkyma
spec:
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: istio-ingressgateway
      namespaceSelector: {}
  podSelector:
    matchLabels:
      app.kubernetes.io/name: srv
      app.kubernetes.io/instance: capkyma
  policyTypes:
  - Ingress
---
...省略
```

kindを見るとどのような種類のリソースが作られるかわかるので、それをまとめたものが以下です。6種類のリソースができることがわかります。
|リソース  |何をするものか  |
|---|---|
|NetworkPolicy  |CAPのサービスのPodへの通信を許可するPodを指定（istio-ingressgatewayにマッチするものを許可している）  |
|PodDisruptionBudget  |Podを停止する際に停止できる数を制限する  |
|Service |CAPのサービスを公開する |
|Deployment |CAPのサービスをデプロイし、Podを作成する |
|APIRule |CAPのサービスへのアクセスポリシーなどを設定 |
|Job (hana-deployer) |CAPのdbモジュールをHANA Cloudにデプロイ |

JobはHANA Cloudにデプロイするために一時的に作成されるリソースで、デプロイが完了すると消えます。

### 4. Kyma runtimeにデプロイ
以下のコマンドを実行します。Helmはchartとvalueの組み合わせをリリースとして管理するので、任意のリリース名を指定します。
```
helm upgrade <リリース名> <chartフォルダのパス> --install

# 例
helm upgrade capkyma ./chart --install
```

正常に終了した場合は、ターミナルに以下のメッセージが出力されます。

```
The release "capkyma" is installed in namespace "capkyma".

Your services are available at:
  - https://capkyma-srv-capkyma.a2985eb.kyma.shoot.live.k8s-hana.ondemand.com
```

表示されたURLを叩くと、以下の画面が表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/a02dd8af-2472-47e0-5fc2-e873c8c59f6e.png)

Booksをクリックするとデータが表示されます。HANA Cloudへの接続もOKです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/7a3aa627-e427-a24f-78ea-291fcaea8cb3.png)


### 5. デプロイ結果の確認
**Podの状態**
以下のコマンドでPodの状態を確認できます。

```
kubectl get pods
```

デプロイ中は以下の状態ですが、完了するとデプロイヤーのPodは消えます。
```
NAME                           READY   STATUS      RESTARTS   AGE
capkyma-hana-deployer-6qf2w    0/1     Completed   0          13s
capkyma-srv-588798958f-gzppj   2/2     Running     0          24m
```

**Helmのリリース**
以下のコマンドでhelmのリリース一覧を照会できます。RIVISIONが2となっているのは、初回のデプロイに失敗して2度デプロイしたためです。
```
helm list
```
```
NAME    NAMESPACE       REVISION        UPDATED                                 STATUS      CHART           APP VERSION
capkyma capkyma         2               2022-08-26 06:28:33.6311764 +0900 JST   deployed    capkyma-1.0.0   1.0.0 
```

リリースはKubernatesのシークレットとして保存されています。以下のコマンドで確認することができます。

```
kubectl get secrets
```
```
NAME                                 TYPE                                  DATA   AGE
...
sh.helm.release.v1.capkyma.v1        helm.sh/release.v1                    1      24h
sh.helm.release.v1.capkyma.v2        helm.sh/release.v1                    1      23h
```

### トラブルシューティング
最初にデプロイしたときに、以下のエラーになりました。トラブルシューティングの手順を載せておきます。

```
Error: failed post-install: timed out waiting for the condition
```

**ジョブの状況確認**
以下のコマンドでDBにデプロイするためのジョブの状況を確認できます。

```
kubectl get jobs
```
ジョブが正常に終了すると、結果に出てこなくなります。ジョブがずっと残っている場合は何らかの問題が発生したと考えられるので、DBデプロイヤーのログを確認します。

**DBデプロイヤーのログ確認**
以下のコマンドでエラーになっているPodを確認します。

```
kubectl get pods
```

```
NAME                           READY   STATUS    RESTARTS   AGE
capkyma-hana-deployer-6s7fl      0/1     Error     0          6m16s
capkyma-hana-deployer-n5fnq      0/1     Error     0          7m46s
capkyma-hana-deployer-plfmh      0/1     Error     0          7m16s
capkyma-hana-deployer-z2nxh      0/1     Error     0          8m8s
capkyma-hana-deployer-zc9c2      0/1     Error     0          6m56s
```
エラーになっているPodの名前を取得して、以下のコマンドを実行します。複数ある場合は一番上のものでよいと思います。

```
kubectl logs <Pod名>

# 例
kubectl logs capkyma-hana-deployer-6s7fl
```

私の場合、以下のログが表示されました。ログの内容からHANA Cloudに接続する際に認証エラーが起きていることがわかりました。HDIコンテナのシークレットを作成したときに、値の一部が間違っていたことが原因でした。
シークレットを修正して再度デプロイした結果、正常にデプロイできました。

```
@sap/hdi-deploy, version 4.4.1 (mode default), server version unknown (0.0.0.0), node version 16.16.0, HDI version -1, container API version -1
Using hana-client for connection
Detection of server version failed; root cause: authentication failed
Detection of container API version failed; root cause: authentication failed
Detection of HDI version failed; root cause: authentication failed
The server does not support live updating of make messages. The messages will be displayed, when the make is done.                                                      No ignore file at /workspace/.hdiignore.
Collecting files...
Collecting files... ok (0s 2ms)
3 directories collected
7 files collected
0 reusable modules collected
Target service: hana
Could not check if session variable APPLICATION is set correctly: authentication failed
Could not determine status of last build: authentication failed
Processing revoke files...
Processing revoke files... ok (0s 0ms)
Processing grants files...
Processing grants files... ok (0s 0ms)
Preprocessing files...
Preprocessing files... ok (0s 0ms)
Connecting to the container "CAC6988D1FD049B6A3A2AE628CD6EB1C"...
Deployment to container CAC6988D1FD049B6A3A2AE628CD6EB1C failed - error: authentication failed [Deployment ID: none].                                                      Deployment ended at 2022-08-25 21:04:50
Error: authentication failed
(2s 117ms)
```

`kubectl logs <Pod名>`はDBデプロイヤーに限らず、例えばサービスがエラーになったときにログを確認することもできます。Cloud Foundryでいうところの`cf logs <サービス名>`と同じです。