「BotFrameworkで勤務報告Bot作った」は、技術書典7でさとう( @honhotate )さんが頒布していた本です。
私は技術書典7にサークル参加していたのですが、ほとんどサークルを出られなかったため、お隣のサークルさんで唯一普通に購入することができた本とも言えます。
なお本書で紹介されているBotFrameworkとは、「Microsoft Bot Framework」のことです。 SDK v4を使って、Botを開発します。
読み終えたので、さっそく感想など書いていきます。
本書で使っている技術
- Microsoft Bot Framework - SDK v4
- Node.js
- MongoDB
- CentOS7(host OS) + Docker
- Let's Encrypt!
筆者のさとうさんは、さくらVPSのスタンダードプランを使い、ボットを動かしているとのことです。 VPSの場合は定額制になるため、従量課金系のサービスに比べてコストが安定します。 これは大きなメリットです。
なおNode.jsを使ったWEBアプリケーションになるため、稼働環境に大きな制約はありません。
開発言語はC#、またはJavaScript
ボットの開発には、プログラミング言語ではC#またはJavaScriptが使えると、公式リファレンスに書いてありました。 C#では「.NET」、JavaScriptでは主に「Node.js」です。
Azure Bot Service のドキュメント - Bot Service | Microsoft Docs
作成したボットのデバッグとテスト
Microsoftが提供しているBotFrameworkのEmulatorを使うことで、作成したボットの動作確認できます。
https://docs.microsoft.com/ja-jp/azure/bot-service/bot-service-debug-emulator
勤怠を記録するボットの開発
本書では勤怠管理のボットを目的として、一連のボット開発を行ないます。 具体的には「出勤」や「退勤」といった発言を起因に、出勤日時と退勤日時を自動的に記録します。
個人的に興味深かったのは、データベースにMongoDBを採用している点です。 今回はライトに開発できるシステムを目標としているため、NoSQLとしてお手軽なMongoDBを活用していると思われます。
docker-compose
サンプルコードに「docker-compose.yml」があります。
kinchan/docker-compose.yml at master · okg75/kinchan · GitHub
「docker-compose」を使うと、複数のDockerコンテナを組み合わせてサービスを構築することができます。 なお本書では、次の3つのDockerイメージが使われています。
- Nginx: steveltn/https-portal
- データベース: mongo(公式イメージ)
- Node.js: library/node(公式イメージ)
「library/」は、公式イメージ専用の名前空間です。 本書では「library/node」というimageが指定されていますが、公式イメージでは名前空間を省略して表記することも可能です。
大事なのは「library」が公式イメージ専用の名前空間で、セキュリティ上、公式のみ使うべきです。
https://www.slideshare.net/zembutsu/docker-compose-guidebook より
https-portalによる、HTTPS(SSL/TLS)通信
A fully automated HTTPS server powered by Nginx, Let's Encrypt and Docker.
https://github.com/SteveLTN/https-portal より
https-portalのベースイメージは、nginxです。 一番の特徴は、Let's EncryptによるSSL証明書の自動更新が備わっている点にあります。
なお本書のdocker-composeには、次の記述があります。
environment: DOMAINS: 'example.com -> http://app:9443'
これは「example.comへのアクセスを、appという名前で稼働しているnodeの、9443番ポートに転送しますよ」という意味になります。 リバースプロキシのようなイメージで理解すると、良いかもしれません。
ここでは外部との通信をhttps(SSL/TLS)による暗号化をした上で、内部通信ではhttpが使われています。 基本的にhttpsよりhttpのほうが高速な(コストが低い)ため、外部との通信はhttpsを使い、内部アクセスではhttpを使うという理屈です。
MongoDBのデータの永続化
MongoDBはファイルにデータを記録します。 Dockerの再構築などによって消えてしまうと困るため、これを永続化します。
volumes: - ./mongo/db:/data/db - ./mongo/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d
意味合いとしては、MongoDBのDockerコンテナの「/data/db」を、ホストサーバー側の「./mongo/db」にマウント(連携)させますよという意味です。 こうすることでホストサーバー(ホストOS)側にデータが残るため、dockerが死んでもデータは残ります。
The -v /my/own/datadir:/data/db part of the command mounts the /my/own/datadir directory from the underlying host system as /data/db inside the container, where MongoDB by default will write its data files.
https://hub.docker.com/_/mongo より
公式の解説を読むと、「MongoDBはデフォルトで、/data/dbの中にデータファイルを書きます!」と書いてあります。 つまりここだけ、永続化しておけば良いのです。
公式コンテナの使い方は、素直にオフィシャルのdocker hubを確認するのがオススメです。 なお公式コンテナのdocker hubでは、基本的にURLのパスが「/_/コンテナ名」となっています。
docker-entrypoint-initdb.d
「/docker-entrypoint-initdb.d」には、テストデータや初期データをはじめ、予めデータベースに保存しておきたいデータを登録するJSのスクリプトを置きます。
This variable allows you to specify the name of a database to be used for creation scripts in /docker-entrypoint-initdb.d/*.js (see Initializing a fresh instance below). MongoDB is fundamentally designed for "create on first use", so if you do not insert data with your JavaScript files, then no database is created.
https://hub.docker.com/_/mongo より
9443番ポートで稼働するNode.js
本書のサンプルコードでは、Node.jsを使ったWEBアプリケーションサーバーが9443番ポートで稼働しています。
server.listen(process.env.port || process.env.PORT || 9443, () => { console.log(`\n${server.name} listening to ${server.url}`); console.log(`\nGet Bot Framework Emulator: https://aka.ms/botframework-emulator`); console.log(`\nTo test your bot, see: https://aka.ms/debug-with-emulator`); });
「process.env.port || process.env.PORT || 9443」となっているため、docker-compose側のenvironmentで、ポートを差し替えることもできそうです。
たとえば次のようにすると、8080番ポートでlistenするようになります(動作はまだ未確認です)。
app: image: library/node # 〜途中、省略〜 environment: PORT: 8080
Node.jsへの外部からの直アクセスを遮断する
iptablesやfirewallなどを使って、Node.jsがlistenしているポートへの外部からのアクセスは、遮断することが推奨されます。 今回の例ではリバースプロキシ(steveltn/https-portal)を経由してnode(app)にアクセスすることを想定しているため、外部から直接アクセスするルートは閉じておくと、より確実です。
これはホストOS側による設定はもちろんのこと、たとえばAWSのEC2であれば、セキュリティグループを使うこともできます。 docker-composeによる構成では、このように外部に解放する必要があるポートと、内部的な通信でのみ使用するポートを切り分けて考える必要があります。
サーバーはAPIエンドポイントを経由して、Azure側とやりとりする
本書を進めていくと、最終的にAzureに登録し、Skypeから使用できるようになります。 なおAzure側とサーバーとのやりとりは、APIエンドポイントを経由して行われます。
サンプルコードの例では、「/api/messages」がAPIエンドポイントの役割を担っています。
// Listen for incoming requests. server.post('/api/messages', (req, res) => { adapter.processActivity(req, res, async (context) => { // Route to main dialog. await myBot.run(context); }); });
ボットの開発は楽しい
本書を読んでいて感じたことですが、現在は「Microsoft Bot Framework」をはじめとするエコシステムが発達しており、とてもボットが開発しやすくなりました。
「ぜひBotをカスタマイズして、自分だけのかわいい相棒を作ってくださいね。」という一節が本書にありましたが、読み進めていたら楽しくなってきて、私もボットを作ってみたくなりました。
さいごに
ボットの開発は機能要件がシンプルでありながらも、サーバーの構築・プログラミング・セキュリティ・外部との連携をはじめ、エンジニアリングに必要な要素がたくさん詰まってます。
ボットの開発は創意工夫による楽しさに加え、ITエンジニアとしての実力向上にもオススメであると言えそうです。
秋の夜長にBot作成、ぜひチャレンジしてみてはいかがでしょうか?