Cisco IOxのDockerタイプアプリを試してみる

# Cisco IOxとは

Cisco IOxは、ルータやスイッチ等のIOxプラットフォーム上で、アプリケーションやサービスをホスティングするための（全人類待望の！？）フレームワークです。

https://developer.cisco.com/docs/iox/#introduction-to-iox/introduction-to-iox

ホスティングするアプリケーションのタイプとしては、以下の4つあります。

* PaaS
* Container
* Docker
* VM

今回は、これらの中で最も扱い易いDockerタイプのものを試してみたいと思います。

https://developer.cisco.com/docs/iox/#application-types

ちなみに、IOxプラットフォーム毎にサポートされるアプリケーションタイプは異なります。
詳細は、以下のサイトを参照ください。

https://developer.cisco.com/docs/iox/#supported-platforms

# 前提

IOxアプリケーションをデプロイする環境には、以下のDevNet Sandbox IOx v1.5を用いました。

https://devnetsandbox.cisco.com/RM/Diagram/Index/0837eae5-da95-46d2-a770-4cd513cd3611?diagramType=Topology

IOxアプリケーションをビルドする環境には、以下のものを用いました。

* Ubuntu 14.04.5 LTS
* Docker CE 17.12.0-ce
* ioxclient v1.5.1.0

`ioxclient`は、以下のサイトからバイナリをダウンロードできます。

https://developer.cisco.com/docs/iox/#downloads/downloads

# 事前準備

Linux系のOSにDocker CEをインストールしておいてください。
この記事では、本家サイトに従ってにインストールしてます。

https://docs.docker.com/engine/installation/linux/docker-ce/ubuntu/

# ioxclientのインストール

`ioxclient`はCisco IOxプラットフォームのアプリケーション開発を支援するためのコマンドラインツールです。
IOxアプリケーションをビルドしたり、IOxプラットフォーム上にデプロイするのに使用します。

https://developer.cisco.com/docs/iox/#what-is-ioxclient/what-is-ioxclient

適当なディレクトリに`ioxclient`をダウンロードします。この記事ではHOMEディレクトリ直下にダウンロードすることにします。

```
$ wget https://pubhub.devnetcloud.com/media/iox-docs/docs/artifacts/ioxclient/ioxclient-v1.5.1.0/ioxclient_1.5.1.0_linux_amd64.tar.gz
```

展開します。

```
$ tar zxvf ioxclient_1.5.1.0_linux_amd64.tar.gz
```

PATHを通しておくといいでしょう。

```
$ export PATH=$PATH:$HOME/ioxclient_1.5.1.0_linux_amd64
```

`ioxclient`は初期化されていない状態で、実行されると初期化のための対話ウィザードが走ります。
設定としてHOMEディレクトリ直下に`.ioxclientcfg.yaml`ができます。

とりあえず動かす分には、以下の3つの項目のみ設定すれば、それ以外はデフォルトのままでOKです。

* IOxアプリケーションをデプロイする先にIPアドレス
* 権限を持つユーザー
* そのユーザーのパスワード

Devnet Sandboxの環境にあわせて、以下のようにします。

```
$ ioxclient
Config file not found : /home/vagrant/.ioxclientcfg.yaml
Creating one time configuration..
Your / your organization's name :
Your / your organization's URL :
Your IOx platform's IP address[127.0.0.1] : 10.10.20.51
Your IOx platform's port number[8443] :
Authorized user name[root] : cisco
Password for cisco : cisco
Local repository path on IOx platform[/software/downloads]:
URL Scheme (http/https) [https]:
API Prefix[/iox/api/v2/hosting/]:
Your IOx platform's SSH Port[2222]: 22
Your RSA key, for signing packages, in PEM format[]:
Your x.509 certificate in PEM format[]:
Activating Profile default
Saving current configuration
Active Profile : default
```

# IOxアプリケーションのビルド

IOxアプリケーションとして動かすアプリケーションは、以下のものを使います。

https://github.com/CiscoIOx/docker-nodejs

適当なディレクトリに上記のプロジェクトをGitクローンします。この記事ではHOMEディレクトリ直下にGitクローンすることにします。

```
$ git clone https://github.com/CiscoIOx/docker-nodejs.git
```

アプリケーションそのものは、`Hello World!`と返すだけのいたってオーソドックスなサンプルアプリケーションです。
logの書き出し先を変更している以外は、特別なことはしてません。

```javascript:server.js
// Handle SIGINT/SIGTERM
var sighandler = function() {
  console.log('Exiting..');
  process.exit();
}
process.on( "SIGINT", sighandler );
process.on( "SIGTERM", sighandler );

// Overload console.log and console.error to log to a file and also to stdout

// If running in CAF environment, use the CAF_APP_LOG_DIR location to write the log file
var cafLogDir = process.env.CAF_APP_LOG_DIR
var path = require('path');
var lf = "server.log"
if (cafLogDir) {
	lf = path.join(cafLogDir, lf)
}
var fs = require('fs');
var util = require('util');
var logFile = fs.createWriteStream(lf, { flags: 'a' });
var logStdout = process.stdout;

console.log("Setting up logging to file " + lf)
console.log = function () {
  logFile.write(util.format.apply(null, arguments) + '\n');
  logStdout.write(util.format.apply(null, arguments) + '\n');
}
console.error = console.log;

// Load the http module to create an http server.
var http = require('http');

// Configure our HTTP server to respond with Hello World to all requests.
var server = http.createServer(function (request, response) {
  console.log("Request from " + (request.headers['x-forwarded-for'] || request.connection.remoteAddress))
  response.writeHead(200, {"Content-Type": "text/plain"});
  response.end("Hello World!\n");
  console.log("Response sent..")
});

// Listen on port 8000, IP defaults to 127.0.0.1
server.listen(8000, "0.0.0.0");

// Put a friendly message on the terminal
console.log("Server running at http://0.0.0.0:8000/");
```

Dockerfileも特別なことをしてません。
IOxの実行環境は通常のサーバーと比べ非力なので、Alpine Linux等、軽量なイメージにしておくことは重要です。

```dockerfile:Dockerfile
FROM alpine:3.3

RUN apk add --update nodejs 
COPY server.js /server.js

EXPOSE 8000
CMD ["node", "/server.js"]
```

最後に、ビルドするためのパッケージ情報です。

```package.yaml
descriptor-schema-version: "2.2"

info:
  name: SampleNodeApp
  description: "Simple Docker Style app that runs a nodejs server"
  version: "1.0"
  author-link: "http://www.cisco.com"
  author-name: "Cisco Systems"

app:
  # Indicate app type (vm, paas, lxc etc.,)
  cpuarch: "x86_64"
  type: docker
  resources:
    profile: c1.small
    network:
      -
        interface-name: eth0
        ports:
            tcp: [8000]

# Specify runtime and startup
  startup:
    rootfs: rootfs.tar
    target: ["node", "/server.js"]
```

`info:`の項目でアプリケーションの情報を記載します。
`app:`の項目でアプリケーションのタイプ、リソース、ネットワーク、コンテナ起動に関する情報を記載します。

各項目の詳細は、以下のリンク先を参照ください。

https://developer.cisco.com/docs/iox/#package-descriptor

Dockerイメージを作成します。

```
$ docker build -t tetsusat/docker-nodejs .
Sending build context to Docker daemon 65.02kB
Step 1/5 : FROM alpine:3.3
3.3: Pulling from library/alpine
53969ec691ff: Pull complete
Digest: sha256:36b22b4ceb2128310ed800fdb28c52bf8a31c953a0e91d77e99e6ce66289c3bb
Status: Downloaded newer image for alpine:3.3
 ---> fc8815064a1b
Step 2/5 : RUN apk add --update nodejs
 ---> Running in 0191cfc85b9c
fetch http://dl-cdn.alpinelinux.org/alpine/v3.3/main/x86_64/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.3/community/x86_64/APKINDEX.tar.gz
(1/4) Installing libgcc (5.3.0-r0)
(2/4) Installing libstdc++ (5.3.0-r0)
(3/4) Installing libuv (1.7.5-r0)
(4/4) Installing nodejs (4.3.2-r1)
Executing busybox-1.24.2-r2.trigger
OK: 29 MiB in 15 packages
Removing intermediate container 0191cfc85b9c
 ---> da6550944b0a
Step 3/5 : COPY server.js /server.js
 ---> 275566899bf9
Step 4/5 : EXPOSE 8000
 ---> Running in d7aeb4bc3101
Removing intermediate container d7aeb4bc3101
 ---> 632953687b6d
Step 5/5 : CMD ["node", "/server.js"]
 ---> Running in f28c1ef6e6da
Removing intermediate container f28c1ef6e6da
 ---> 2d90edf50d6e
Successfully built 2d90edf50d6e
Successfully tagged tetsusat/docker-nodejs:latest
```

作成したDockerイメージを`rootfs.tar`として出力します。ちなみに、`package.yaml`において`rootfs:`の項目で指定しているがこれです。

```
$ docker save tetsusat/docker-nodejs -o rootfs.tar
```

`package.yaml`に基づいて、IOxアプリケーションをビルドします。


```
$ ioxclient package .
Currently active profile : default
Command Name: package
No rsa key and/or certificate files to sign the package
Checking if package descriptor file is present..
Validating descriptor file /home/vagrant/docker-nodejs/package.yaml with package schema definitions
Parsing descriptor file..
Found schema version 2.2
Loading schema file for version 2.2
Validating package descriptor file..
File /home/vagrant/docker-nodejs/package.yaml is valid under schema version 2.2
Created Staging directory at : /tmp/283132817
Copying contents to staging directory
Creating an inner envelope for application artifacts
Excluding .git/HEAD
Excluding .git/config
Excluding .git/description
Excluding .git/hooks/applypatch-msg.sample
Excluding .git/hooks/commit-msg.sample
Excluding .git/hooks/post-update.sample
Excluding .git/hooks/pre-applypatch.sample
Excluding .git/hooks/pre-commit.sample
Excluding .git/hooks/pre-push.sample
Excluding .git/hooks/pre-rebase.sample
Excluding .git/hooks/prepare-commit-msg.sample
Excluding .git/hooks/update.sample
Excluding .git/index
Excluding .git/info/exclude
Excluding .git/logs/HEAD
Excluding .git/logs/refs/heads/master
Excluding .git/logs/refs/remotes/origin/HEAD
Excluding .git/objects/11/6dd4fa9290ce24674833be7e5b52841c010b2b
Excluding .git/objects/1f/31940e91ff6d82ace07a841ff33b18de2ce213
Excluding .git/objects/4c/920cd9b5913f8af1563afd1ea836614bcd9756
Excluding .git/objects/50/0739fb160f9f051961323457e86571cf24de15
Excluding .git/objects/6f/00a34d2e82f0bf1c5db6abd994e8b6c6227e34
Excluding .git/objects/7a/f6594554ddeabe1a73273936345caf6e5f94b4
Excluding .git/objects/a3/26e0d239703bc53985bfbd4420657de93446fd
Excluding .git/objects/a8/ac28363229271de9b5fe11ea5a874369ab5469
Excluding .git/objects/c9/7aa9c61fa6f256933416b3ed60d7507688dd8a
Excluding .git/objects/e7/2a7793896a6fe96b4ede46a61694b0de60d291
Excluding .git/packed-refs
Excluding .git/refs/heads/master
Excluding .git/refs/remotes/origin/HEAD
Generated /tmp/283132817/artifacts.tar.gz
Calculating SHA1 checksum for package contents..
Updated package metadata file : /tmp/283132817/.package.metadata
Root Directory : /tmp/283132817
Output file: /tmp/408232892
Path: .package.metadata
SHA1 : 2cd065316d0522584df4fad3059eb595f78c4c1a
Path: artifacts.tar.gz
SHA1 : 2e8c238c54f892785764db0fd1a3a371a7294841
Path: package.yaml
SHA1 : 8528e35786058253da12646a45723e95e80ef9d0
Generated package manifest at package.mf
Generating IOx Package..
Package generated at /home/vagrant/docker-nodejs/package.tar
```

`package.tar`というファイルが出来上がりました。

# IOxアプリケーションのデプロイ

**ioxclient application install _applistion\_name_** でアプリケーションをインストールします。

```
$ ioxclient application install hello_iox package.tar
Currently active profile :  default
Command Name:  application-install
Saving current configuration
Installation Successful. App is available at : https://10.10.20.51:8443/iox/api/v2/hosting/apps/hello_iox
Successfully deployed
```

アクティベートするタイミングで、ネットワークの設定を指定します。
以下のような`activate.json`ファイルを用意します。
Sandbox環境では、`iox-bridge0`接続ではなく`iox-nat0`接続に変更してあげる必要があります。

```activate.json
{
    "resources": {
        "profile": "c1.small",
        "network": [{"interface-name": "eth0", "network-name": "iox-nat0"}]
    }
}
```

**ioxclient application activate _applistion\_name_** でアプリケーションをアクティベートします。

```
$ ioxclient application activate hello_iox --payload activate.json
Currently active profile :  default
Command Name:  application-activate
Payload file : activate.json. Will pass it as application/json in request body..
App hello_iox is Activated
```

**ioxclient application start _applistion\_name_** でアプリケーションを起動します。

```
$ ioxclient application start hello_iox
Currently active profile :  default
Command Name:  application-start
App hello_iox is Started
```

**ioxclient application list**でアプリケーションの状態を確認できます。

```
$ ioxclient application list
Currently active profile :  default
Command Name:  application-list
List of installed App :
 1. hello_iox  --->    RUNNING
```

**ioxclient application info _applistion\_name_**でアプリケーションの詳細を確認できます。
`networkInfo` -> `eth0` -> `port_mappings` -> `tcp`の情報を参照すると、ゲスト側（Docker）のTCP 8000番ポートがホスト側（IOxプラットフォーム）のTCP 40000番ポートにマッピングされていることが分かります。

```
$ ioxclient application info hello_iox
Currently active profile :  default
Command Name:  application-info
Details of App : hello_iox
-----------------------------
{
 "appCustomOptions": "",
 "appType": "docker",
 "app_checksums": {
  "app_resource": "d8da0353159510deeae7f5435f804e9b5311d9ca5f8271853dcaba0ec18ae21b",
  "app_state": "RUNNING"
 },
 "app_health": {
  "health_status": 0,
  "last_health_probe_err": "",
  "last_health_probe_output": ""
 },
 "author": "Cisco Systems",
 "authorLink": "http://www.cisco.com",
 "auto_start": false,
 "container_type": "LXC Container : Name: hello_iox, UUID: bc74d343-0165-4f33-af7a-ed5567f750b6",
 "debugMode": false,
 "dependsOn": {},
 "description": "Simple Docker Style app that runs a nodejs server",
 "dynamic_app_resources": {
  "disk": "10",
  "network": [
   {
    "interface-name": "eth0",
    "network-name": "iox-nat0"
   }
  ],
  "profile": "c1.small",
  "vcpu": 1
 },
 "env": {
  "CAF_APP_APPDATA_DIR": "/data/appdata",
  "CAF_APP_CONFIG_DIR": "/data",
  "CAF_APP_CORE_DIR": "/tmp/cores",
  "CAF_APP_CPU_SHARES": 1147,
  "CAF_APP_ID": "hello_iox",
  "CAF_APP_LOG_DIR": "/data/logs",
  "CAF_APP_MEMORY_SIZE_KB": 65536,
  "CAF_APP_PERSISTENT_DIR": "/data",
  "CAF_APP_PERSISTENT_DISK_SIZE_KB": 10240,
  "CAF_APP_USERNAME": "root",
  "CAF_SYSTEM_UUID": "72e4c20b-b4d9-41d4-8fe6-f75b9ffff71c",
  "null": "/dev/net/tun"
 },
 "host_mode": false,
 "id": "hello_iox",
 "ioxv_metadata": {},
 "is_autoinstalled": false,
 "is_service": false,
 "last_state_change_time": "2018-04-03 03:27:59",
 "name": "SampleNodeApp",
 "networkInfo": {
  "eth0": {
   "ipv4": "192.168.10.2",
   "ipv6": [
    "fe80::5054:99ff:fe99:0/64"
   ],
   "ipv6_required": null,
   "libvirt_network": "dpbr_n_0",
   "mac": "52:54:99:99:00:00",
   "mac_address": "52:54:99:99:00:00",
   "network_name": "iox-nat0",
   "port_mappings": {
    "tcp": [
     [
      8000,
      40000,
      null
     ]
    ]
   }
  }
 },
 "packageManifest": [
  "SHA1(.package.metadata)= 76b2d1d7833f450b8dc54168b66b25cf5019d615\n",
  "SHA1(artifacts.tar.gz)= 8140e1ddfb25f556e865e9194217384954846c1a\n",
  "SHA1(package.yaml)= 8528e35786058253da12646a45723e95e80ef9d0\n"
 ],
 "packageMetaData": {
  "packageInfo": {
   "buildDirectory": "/vagrant/webinar/docker-nodejs",
   "buildHost": "vagrant-ubuntu-trusty-64",
   "builtBy": "vagrant",
   "builtOn": "Tuesday, 27-Mar-18 09:22:20 UTC",
   "compressedArtifactsSizeInBytes": "9543320",
   "compressedArtifactsSizeInMB": "9.10",
   "ioxclientVersion": "1.5.0.0",
   "uncompressedArtifactsSizeInBytes": "27035156",
   "uncompressedArtifactsSizeInMB": "25.78"
  }
 },
 "package_config": {
  "contents": "",
  "metadata": {
   "contents_parsed": true,
   "last_modified": "",
   "size": 0
  }
 },
 "platform_service": false,
 "provides": null,
 "reconcile_attempted": false,
 "reconcile_failure": false,
 "resources": {
  "cpu": 200,
  "disk": 10,
  "memory": 64,
  "network": [
   {
    "interface-name": "eth0",
    "ipv4": {},
    "ipv6": {},
    "network-name": "iox-nat0",
    "port_map": null,
    "ports": {
     "tcp": [
      8000
     ]
    }
   }
  ],
  "profile": "c1.small",
  "vcpu": 1
 },
 "state": "RUNNING",
 "toolkitServicesUsed": null,
 "version": "1.0"
}
```

最後に、ホスト側のTCP 40000番ポートにHTTPアクセスすると、`Hello World!`が返ってきます。

```
$ curl 10.10.20.51:40000
Hello World!
```

# 結びに代えて

IOxプラットフォームのうち、Dockerタイプのアプリケーションをサポートするのは、今のところIR829/IR809など一部に限られますが、人々が慣れ親しんだDockerの仕組みでネットワークデバイスに自由にアプリケーションを載せられることは、ユーザーにとって素晴らしいことだと思います。今後、通常のルータやスイッチにもこの仕組みが広がっていくことを期待したいです。

主にフォグコンピューティング的な文脈においてフォグノードをネットワークデバイス上で動作させるようなユースケースを想定してるようですが、ネットワークデバイスを支援するようなサイドカーコンテナとして活用してみてもおもしろいと思います。