Cloud Native Application Bundles（CNAB）をporterで動かしてみた

株式会社日立製作所　研究開発グループ　サービスコンピューティング研究部の長沼です。
今回は[Cloud Native Application Bundles（CNAB）](https://github.com/deislabs/cnab-spec)を、開発ツールの１つである[porter](https://github.com/deislabs/porter)を使って開発・利用してみましたので、その内容について紹介します。

# CNABとは

CNABとは、分散型アプリケーションのインストールや管理のためのパッケージ仕様を定めたものです。KubeCon NA 2018で紹介され、以降、仕様のアップデートや関連ツールの開発が行われています。  
CNABの基本的な構成要素は
- インストール処理を行うコンテナイメージであるinvocation image
- パッケージの説明やバージョン、対となるinvocation image名や処理実行時に入力するパラメータの定義などを記述したbundle.json、

となります。  
要は、最近のCloud Nativeなアプリケーションを立ち上げようとすると、構築のために色々なクライアントツール入れ、構成定義を取得し、、、準備だけでも大変です。これを1コマンド（例えばdocker run）で、クライアントツール準備からアプリケーションのインストールまでやってくれる、そんなパッケージを作りましょう、という仕様です。

# CNABを実現する開発ツール

CNABはパッケージの仕様であって、bundle.jsonやInvocation Imageの具体的な開発や利用方法までは定めていません。
全部手書き開発して、Invocation Imageをdocker runすれば利用することもできますが大変です。
そこでCNABの実装として、いくつかのツール（以下）も開発が進められています。

- [duffle](https://github.com/deislabs/duffle):  
bundle.json（とinvocation image）を用いてアプリケーションのインストール、アンインストールなどCNABの利用が可能。
- [porter](https://github.com/deislabs/porter):  
porter.yamlというYAMLで記述された独自の書式をもとに、bundle.jsonとinvocation imageを生成。
[helm](https://github.com/deislabs/porter-helm)や[terraform](https://github.com/deislabs/porter-terraform)のラインタイム(Mixinと呼ばれる)が用意されており、様々なプラットフォーム（Kubernetes, パブリッククラウドサービス etc.）に対してアプリケーションのインストールが可能。
- [docker-app](https://github.com/docker/app):  
docker-composeで用いるdocker-compose.yamlやそのメタ情報からbundle.jsonとinvocation imageを生成。
生成したinvocation imageを用いて、kubernetesまたはswarmへアプリケーションのインストールが可能。

今回は数あるツールの中からporterを用いて、CNABの開発・利用を行ってみることとしました。

# porterによるCNABの開発・利用

https://github.com/deislabs/porter/blob/master/docs/content/quickstart.md
を参考に、
- porterをセットアップ
- [helm](https://helm.sh/)によりkubernetesにアプリケーションをインストールするためのbundle.json、invocation imageを作成
- 作成したbundle.jsonとinvocation imageを用いて、事前に構築済みのkubernetesクラスタ（helm tillerも立ち上げ済）に実際にインストール（デプロイ）

を動かしてみた流れを紹介します。

なお今回使っているporterのバージョンはv0.10.0-beta.2であり、執筆時点で頻繁にアップデートされています。
このためバージョンによっては一部動作が異なる場合があります。

## porterの準備

https://github.com/deislabs/porter/blob/master/docs/content/install.md
の記述を参考に、利用するクライアントのOSに対応したスクリプトを実行します。例えば、MacOSの場合は、以下となります。

```console
curl https://cdn.deislabs.io/porter/canary/install-mac.sh | bash
```
なおプロキシ環境下で動作させる場合、mixinのインストール失敗する場合がありました。注意ください。

## porterによる開発

まず今回開発するパッケージ用のディレクトリを作成・移動し、`porter create`により雛型を生成します。

```console
mkdir -p my-bundle/ && cd my-bundle
porter create
```

すると作成したディレクトリ（上記my-bundle）下に以下のような雛型が生成されます。

```console
my-bundle
├── Dockerfile.tmpl
├── README.md
└── porter.yaml
```

次に、インストールに使うhelm chart(下記my-bundle/chart/my-noderedの部分)の追加と、生成されたporter.yamlの編集を行います。
今回の例は、サンプルとして[Node-REDのhelm chart](https://github.com/helm/charts/tree/master/stable/node-red)を用い、
フローベースプログラミングツールNode REDをインストールする例を試しました。

- helm chartを追加したディレクトリ構成：

```console
my-bundle
├── Dockerfile.tmpl
├── README.md
├── chart
│   └── my-nodered
│       ├── Chart.yaml
│       ├── templates
│       │   └── ...
│       └── values.yaml
└── porter.yaml
```

- 編集後のporter.yamlの内容:

```yaml:porter.yaml
name: my-bundle
version: 0.1.0
description: "An example Porter configuration"
invocationImage: path-to-registry/my-bundle:0.1.0

# インストールほか処理で利用するmixins（ランタイム）を定義すると、invocation imageビルド時に読み込まれる
mixins:
  - helm 

# デフォルトで読み込まれるkubeconfigの場所
credentials:
 - name: kubeconfig
   path: /root/.kube/config

# 変数の定義
parameters:
- name: release-name
  type: string
  default: default-name

install:
- helm:
    description: "Install my-bundle"
    name: "{{ bundle.parameters.release-name}}" #{{ bundle.parameters.xxx}}でparametersで指定の変数を読み込む
    chart: chart/my-nodered #相対パスでchartの格納場所を指定
    version: 0.1.0
    replace: true
...
```

このように準備したディレクトリ下で

```console
porter build
```

を実行すると、bundle.json（bundle.jsonはmy-bundle/.cnab/配下に格納）とinvocation imageが作成されます。
invocation imageの作成では、porter.yamlのmixins部分に指定のmixinを含むコンテナイメージが自動的に作成されるため、必要なツールを都度準備して格納するといった作業は不要となります。

## porterによるインストール

同じく上記で準備したディレクトリ（my-bundle）下で

```console
porter install
```

を実行します。
すると、対応するinvocation image（今回の場合は上記`porter build`で作成したもの）内で定義されたinstall処理が実行されます。
具体的には、porter.yamlのinstall部分に記述された処理（今回の場合は`helm install chart/my-nodered`）が実行されることになります。
結果、対象としたkubernetesクラスタ上でNode REDがインストールされる（動作している）ことが確認できました。

なお作成済みのbundle.json、invocation imageを利用してインストールする場合は、オプションで次のように`--cnab-file=`を指定することができます(bundle.jsonのkeyの一つとしてinvocation imageが含まれています)。

```console
porter install --cnab-file=path_to/bundle.json
```

同様に、認証情報（今回の場合はkubeconfig）やporter.yamlに指定したパラメータ（今回の場合はrelease-name）をデフォルトから変更することもできます。
例えば、kubeconfigとしてクライアントのpath_to_kubeconfigディレクトリにあるmy-kubeconfigを用い、パラメータrelease-nameをchanged-nameに変更する場合、
- $HOME/credset.yaml:
- 
```yaml:$HOME/credeset.yaml
name: my-credentials
credentials:
- name: kubeconfig
  source:
    path: /path_to_kubeconfig/my-kubeconfig
```
というファイルを用意しておき、オプションとして認証情報の変更：`-c $HOME/cred.yaml`、変数の変更：`-param release-name=changed-name`を付加した

```console
poter install -c $HOME/credset.yaml -param release-name=changed-name
```
を実行することになります。

なおporterで認証情報を扱う仕組みとして、`porter credentials`コマンドが用意されているように見えますが、今回はうまく動かすことができず利用を断念しました。こちらは引き続き調査したいと思います。

## porterによるインストール内容の確認

porterでインストールしたbundleの内容は、

```console
# 一覧の出力
porter bundles list

# 個々のインストールbundle(名称$NAME)の出力
porter bundles show $NAME
```

のコマンドにより確認することができます。
なお現仕様では、porterが動作するクライアントの`$HOME/.porter`下に状態を管理するファイルが置かれており、その内容を整形・出力しているようです。

# まとめ

porterを使ってCNABのinvocation image, bundle.jsonを作成し、サンプルのアプリケーションをkubernetesにインストール（デプロイ）することができました。
作成済みのCNABを利用しアプリケーション環境を構築する立場から見ると、環境構築のために色々なツールを準備せずコンテナが実行可能な環境（要はdocker runが可能）のみ用意すればよいため、便利そうな印象を受けました。
一方で、porterを使ったとしても、CNABのコンテンツ（bundle.jsonやinvocation image）を準備するのはまだまだ大変そうな印象です。また利用の立場でもinvocation imageの中の処理次第では、インストール先環境との不整合で結局うまく動かない場合も考えられます。
このあたりはCNABの周辺ツール含めて、動向を引き続きウォッチしていきたいと思います。

# 参考

- duffleとporterの比較: https://github.com/deislabs/porter/blob/master/docs/content/porter-or-duffle.md