ボット作成で学ぶ、Dockerとdocker-compose入門 - 技術書典7「BotFrameworkで勤務報告Bot作った」

「BotFrameworkで勤務報告Bot作った」は、技術書典7でさとう( @honhotate )さんが頒布していた本です。

f:id:konosumi:20191004024951j:plain

私は技術書典7にサークル参加していたのですが、ほとんどサークルを出られなかったため、お隣のサークルさんで唯一普通に購入することができた本とも言えます。

なお本書で紹介されているBotFrameworkとは、「Microsoft Bot Framework」のことです。 SDK v4を使って、Botを開発します。

Microsoft Bot Framework

読み終えたので、さっそく感想など書いていきます。

本書で使っている技術

  • 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 のドキュメント - チュートリアル、API リファレンス - 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証明書の自動更新が備わっている点にあります。

github.com

なお本書の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作成、ぜひチャレンジしてみてはいかがでしょうか?

techbookfest.org