株式会社日立製作所 研究開発グループ サービスコンピューティング研究部の長沼です。
今回はCloud Native Application Bundles(CNAB)という観点で、実装の1つであるdocker applicationを動かしてみましたので、その内容について紹介します。
CNABの概要については、こちらの記事を参照ください。
docker applicationとは
公式のREADME.mdによれば、
docker CLI Pluginとして動作する、アプリケーションをインストール・共有する仕組みです。
CNAB仕様と互換があり、Composeファイル(docker-compose.yml)から
CNABのinvocation image(アプリケーションをインストールするためのコンテナイメージ、すなわちインストーラ)を作成し、このinvocation imageをコンテナレジストリを用いて共有(配布・再利用)するための機能群を提供するものです。
以降では、この一連の流れを具体的な手順と合わせて説明していきます。
docker applicationの準備
動作環境
今回使用するマシンのOSとdocker、およびdocker-app(docker applicationを実現するコマンド)のversionは以下の通りとなります。
- ubuntu 18.04 LTS
- docker
- client: Docker Engine - Community 19.03.2
- Server: Docker Engine - Community 19.03.2
- docker-app: v0.8.0
またアプリケーションを展開するためのプライベートレジストリ(Harbor)と、インストール先として上記マシン上で動作するdocker swarmクラスタ、も準備しておきます。
インストール
まずdocker application(docker-appコマンド)のインストールを以下手順に従い実施していきます。
https://github.com/docker/app#installation
上記で紹介されているようにdocker-appは、スタンドアローンとして利用する方法と、
docker CLI Pluginによりdockerのサブコマンドとして利用する方法があります。
今回は、dockerのサブコマンドとして利用する方法を使う方法で紹介していきます。
具体的には、以下コマンドを実行します。
# docker-appのdownload
$ export OSTYPE="$(uname | tr A-Z a-z)"
$ curl -fsSL --output "/tmp/docker-app-${OSTYPE}.tar.gz" "https://github.com/docker/app/releases/download/v0.8.0/docker-app-${OSTYPE}.tar.gz"
$ tar xf "/tmp/docker-app-${OSTYPE}.tar.gz" -C /tmp/
# docker CLI plugin利用の設定
$ mkdir -p ~/.docker/cli-plugins && cp "/tmp/docker-app-plugin-${OSTYPE}" ~/.docker/cli-plugins/docker-app
appがサブコマンドとして認識されない('app' is not a docker command.
などとなる)場合、dockerのexperimentalを有効にすることで解決する場合があります。
例えば、以下を実行します。
$ export DOCKER_CLI_EXPERIMENTAL=enabled
docker applicationの利用
docker applicationを利用する上で必要となる一連の流れとして、
- invocation imageの元となるコンテンツ開発
- CNAB invocation imageへのビルド、
- レジストリへのpush/pull、
- アプリケーションのインストール
を以下で紹介していきます。
コンテンツの開発
docker applicationは主に以下の3つのファイルからなります。
- metadata file (metadata.yml): アプリケーションのバージョンや説明などのメタ情報を記述
- compose file (docker-compose.yml): アプリケーションで使うイメージやポートなどの構成を記述
- parameter file (parameters.yml): compose fileに与える変数の定義を記述
以降では、docker applicationの例でも用いられている、指定メッセージを表示する簡単なWebサーバを動作させるアプリケーションの作成を例に挙げ、説明を行っていきます。
まず上記ファイルの雛形をinitコマンドを使って生成します。
$ docker app init hello-message
するとhello-message.dockerappというディレクトリが作成され、3つのファイルの雛形が作成されているのが確認できます。
なおinitコマンドに--single-file
オプションを付けると、この3つのファイルを一つにまとめた1ファイル(今回の場合はhello-message.dockerappというYAMLファイル)
として雛形を出力することもできます。
$ tree hello-message.dockerap
hello-message.dockerapp/
├── metadata.yml
├── parameters.yml
└── docker-compose.yml
この雛形を編集して、実際に動かすアプリケーションに必要な情報へ書き換えを行います。
ここでアプリケーション動作時に変更したい(変数化しておきたい)項目は、parameters.ymlにkey:valueの形式で記述し、docker-compose.yml側で変数${key}
として埋め込むことができます。
今回の例では、表示するメッセージ(docker-compose.ymlの${message}
)と公開ポート(同じく${ports.port1}
)を変数としています。
# アプリケーションのバージョン
version: 0.1.2
# アプリケーションの名前
name: hello-message
# アプリケーションの簡単な説明
description: This application returns a hello message for HTTP request.
# アプリケーションやパッケージのメンテナの名前・E-Mail
maintainers:
- name: maintainer.name
email: maintainer@email.address
# docker-compose.ymlの${message}に対応するkeyとvalue(デフォルト値)
message: world
# keyを階層構造にもできる
ports:
port1: 5678
version: "3.3"
services:
hello-server:
image: hashicorp/http-echo
# ${message}は変数として扱われる
command: ["-text", "hello ${message}"]
ports:
- ${ports.port1}:5678
これら用いて、docker app render
により変数化部分に値が入ったdocker-composeファイルを生成することもできます。
以下ではメッセージ部分はデフォルト値(parameters.yamlで指定した値)、公開ポート部分は--set
オプションで変更した値、で生成したdocker-composeファイルを生成する例を示しています。
$ docker app render --set ports.port1=8080
version: "3.3"
services:
hello-server:
command:
- -text
- hello world
image: hashicorp/http-echo
ports:
- mode: ingress
target: 5678
published: 8080
protocol: tcp
invocation imageのビルド・レジストリへのpush/pull
invocation imageのビルド
docker app bundle
コマンドにより、hello-message.dockerappに対する
# invocation imageのbuild
$ docker app bundle
Invocation image "hello-message:0.1.2-invoc" successfully built
# invocation imageの確認
$ docker images | grep hello-message
hello-message 0.1.2-invoc fbce87b60497 23 seconds ago 49.1MB
# bundle.jsonの確認
$ls .
bundle.json docker-compose.yml metadata.yml parameters.yml
ちなみに現在のdocker-appの実装では、実はdocker app render
でもCNAB invocation imageのビルドが行われるようです。
変数に値を入れたdocker-composeファイルを生成するにしては処理時間がかかると感じたら、実はイメージのビルドも行っていたんですね。。
作成したinvocation imageの中身
作成したinvocation image(hello-message:0.1.2-invoc)とは、いったいどんなものか気になり、中身をのぞいてみました。
$ docker inspect hello-message:0.1.2-invoc
[
{
...
"Config": {
"Hostname": "",
"Domainname": "",
"User": "cnab",
"Env": ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],
"Cmd": [
"/bin/sh",
"-c",
"/cnab/app/run"
], ...,
}, ...
}
]
から分かるように、invocation image実行時は、イメージ内の/cnab/app/runが起動されるようです。
invocation image内の/cnab内にどのようなものが格納されているかを見てみると、以下のように今回作成したhello-message.dockerappとrunコマンド(バイナリ)が格納されたものであることが確認できました。
# tree /cnab の結果
cnab
└── app
├── hello-message.dockerapp
│ ├── bundle.json
│ ├── docker-compose.yml
│ ├── metadata.yml
│ └── parameters.yml
└── run
レジストリへのpush・pull
さらに作成したinvocation imageは、レジストリに格納することができます。
これにより、dockerイメージと同じようにレジストリを介し、invocation image(すなわちアプリのインストーラ)を配布・再利用することができるようになります。
以降ではローカルに立てたレジストリ(registry:5000)を介してinvocation imageをpush/pullする例を示しています。
今回はinsecure registryを使ったため、--insecure-registries
オプションを付ける必要がありました。
# レジストリへpush
$ docker app push --insecure-registries registry:5000 --tag registry:5000/hello-message:0.1.2
# レジストリからのpull
$ docker app pull --insecure-registries registry:5000 registry:5000/hello-message:0.1.2
さらにdocker app inspect
をpush前後で比較してみると単純なpushではなく、以下のようにアプリケーションのイメージ(hello-serverに対応するimage)
も参照場所が書き換えられていました。
すなわちinvocation imageだけでなく、invocation imageに含まれるアプリのイメージも、今回invocation imageを格納したレジストリへ集約しているようです。
# hello-message.dockerappディレクトリ直下で実行した結果
$ docker app inspect .
hello-message 0.1.2
Maintained by: maintainer.name <maintainer@email.address>
This application returns a hello message for HTTP request.
Service (1) Replicas Ports Image
----------- -------- ----- -----
hello-server 1 5678 hashicorp/http-echo
Parameters (2) Value
-------------- -----
message world
ports.port1 5678
Attachments (7) Size
--------------- ----
bundle.json 2.345kB
...(略)
# pushしたregistry:5000/hello-message:0.1.2に対して実行するとhello-serverのImage部分が変更されている
$ docker app inspect --insecure-registries registry:5000 registry:5000/hello-message:0.1.2
hello-message 0.1.2
Maintained by: maintainer.name <maintainer@email.address>
This application returns a hello message for HTTP request.
Service (1) Replicas Ports Image
----------- -------- ----- -----
hello-server 1 5678 registry:5000/hello-message@sha256:ba27d460cd1f22a1a4331bdf74f4fccbc025552357e8a3249c40ae216275de96
Parameters (2) Value
-------------- -----
message world
ports.port1 5678
Attachments (7) Size
--------------- ----
bundle.json 2.345kB
...
アプリの起動
このようにビルドしたinvocation imageは、現状ではdocker swarmとkubernetesへインストール(デプロイ)することができます。
swarmの場合は、以下のようにしてアプリをインストール・確認できます。
# hello-messageのswarmへのインストール
$ docker app install --insecure-registries registry:5000 registry:5000/hello-message:0.1.2
# インストール結果の確認
$ docker app list
INSTALLATION APPLICATION LAST ACTION RESULT CREATED MODIFIED REFERENCE
hello-message hello-message (0.1.2) install success 12 seconds 11 seconds registry:5000/hello-message:0.1.2
# アンインストール
$ docker app uninstall hello-message
kubernetesも、compose-on-kubernetesでAPIを拡張したkubernetesクラスタに対して、--orchestrator kubernetes
オプションを付けることでアプリケーションをインストールできるとのことです。
ただ今回準備した環境ではうまく動かすことができず、こちらは引き続き調査などを行っていきたいと思います。
まとめ
今回はdocker-appを使って、コンテンツの準備、CNABのinvocation imageのビルド/push/pull、およびアプリのインストールまでの流れを紹介しました。
これまでの資産として既にdocker-composeファイルで動作するアプリケーションがあり、これをCNAB仕様に従って共有したいようなユースケースには、うまくフィットする気がしました。
特にdocker app pull/pushという、慣れ親しんだdokcerコマンドの使い勝手でinvocation imageを配布/再利用(push/pull)できる仕組みはステキに感じました。
またCNAB向けのレジストリという点ではcnab-to-ociというプロジェクトでも議論されており、こういった周辺動向含め引き続き見ていきたいと思います。
参考資料
- CNAB: https://github.com/deislabs/cnab-spec
- docker-app: https://github.com/docker/app
- cnab-to-oci: https://github.com/docker/cnab-to-oci