アメッシュの画像を表示するslackのボットをbotkitを使ってDockerイメージにしてherokuで動かした
こんにちは、次男が熱を出して、インフルエンザも疑われましたが、扁桃腺の炎症に よるものでした。長男はすっかり熱とは無縁で、ただ、年始から、乾燥による影響が高いとの診断を 受けた肌のかゆみがまだ、すこし残っているようです。 @kjunichiです。
前提
が参考になったのですが、herokuの無料枠でbotkitを動かすには、Webサーバーを動かしておくことが無難な模様。
アメッシュのサイトの画像表示すれば簡単と思いきや
に書いてあるが、単純に一枚のPNGでなく、レイヤーに分かれた個別のPNGファイルとして公開 されているので、レイヤーを合成する必要があり、結構面倒。
幸い、node.jsではnode-canvasがあり、 HTML5のCanvas APIと同様のAPIが利用可能。
というわけで、drawImageで複数のPNGファイルを重ねたイメージを 簡単に生成出来る。
しかも、昨年に自作したライブラリを流用することで、
const ameshUtil = require('amesh-cli') ameshUtil.getImage(770,480,function(pngimg){ // pngimgにレイヤーを重ねたアメッシュの画像がPNG形式で取得可能。 })
slackはDataURLを展開してくれないので、Webサーバー上のURLが必要
PNGのデータが得られたので、これをDataURLにして、botが発言、いい感じにアメッシュ画像が表示されと 期待したが。。。
自前で生成したPNGをDataURLにて、slackに貼りつけようとしたらデータがデカすぎると 怒られ、サイズを小さくすると、今度は単なる文字列扱いとなってしまった。
ということで、herokuの制約で動かしているWebサーバー(express)が意味を持つことになる。
botがアメッシュの画像表示のメッセージを受けたら、このサーバーのURLを返すことで、 TL上にアメッシュの画像を表示できることになる。
expressで/ameshへアクセスしたらアメッシュの画像を返す処理は、 前述のライブラリを利用すれば、以下の様に数行で記述できる。
app.get('/amesh', function(req, res) { ameshUtil.getImage(770,480,function(img){ res.type('png') res.send(img) }) })
botkit側の実装
controller.hears(['雨どう'],'ambient',function(bot,message) { bot.reply(message,"http://xxxx/amesh"); });
という具合にアメッシュ画像のURLを返すだけでOK。
heroku向けのDockerイメージを作るまでの茨の道のり
これまで事、つま以下
- npm install amesh-cli
- web.jsの編集
をDockerに詰め込んだイメージを作れば、herokuでこのイメージを動かすことで、 アメッシュボットの完成となるわけだが、意外とこの先も難関が待っていた。
nodejsのイメージのnode.jsのバージョンが古すぎ
constがエラーとなってしまい、nodeのバージョン調べたら、0.12系だった。 ただこの問題は、heroku/nodejsイメージを自前で作るのでそれほど最終的には問題にはならなかった。
node.jsを新しくするには
以下の様にheroku/nodejsのソースをクローンして、環境変数のNODE_ENGINEに利用したいバージョンを記述すればOKだった。
ENV NODE_ENGINE 4.2.4 RUN curl -s https://s3pository.heroku.com/node/v$NODE_ENGINE/node-v$NODE_ENGINE-linux-x64.tar.gz | tar --strip-components=1 -xz -C /app/heroku/node
node-canvasモジュールのインストール問題
amesh-cliが内部で使っているnode-canvasモジュールは意外と依存ライブラリが多く、 ソースから入れるDockerfile書くには大変。apt-getコマンドでインストールしたいところ。 しかし、herokuでDockerイメージを動かす場合、apt-getコマンドでインストールするだけでは駄目で、 /app配下にコピーする必要がある。
apt-getでインストールされたファイルを取得
単純にnode-canvasモジュールのREADMEに書かれているパッケージをdpkg -Lするだけでは、 不完全かもしれない。apt-getでは依存するパッケージがインストールされていない場合、それらも インストールされるので。仮にnode-canvasではREADME記載のパッケージだけで済んでもこの方法は 汎用性に欠ける。
ということで、汎用的なapt-getでインストールしたファイルを取り出すDockerイメージを作成した。
apt-getを実行する直前の状態はなるべくherokuのイメージ近いものする必要がある。
にイメージのソースが置いてあり、これを元にイメージを作成。
ubuntu-debootstrap:14.04
とベースイメージがubuntu-debootstrap:14.04と分かったので、これに 必要なパッケージを入れ、インストールされたファイルを取り出してそれをheroku向けのイメージに追加 すれば、うごくのでは?ということで、レッツトライ!
FROM ubuntu-debootstrap:14.04 COPY ./cedar.sh /tmp/build.sh RUN LC_ALL=C DEBIAN_FRONTEND=noninteractive /tmp/build.sh RUN curl -L http://cpanmin.us | perl - App::cpanminus RUN cpanm File::NCopy WORKDIR /root ONBUILD ADD aptlist.txt /root/aptlist.txt ADD aptlist.pl /root/aptlist.pl RUN chmod +x /root/aptlist.pl RUN find /usr |sort > /root/before.txt ONBUILD RUN cat aptlist.txt|xargs apt-get install -y --no-install-recommends ONBUILD RUN find /usr |sort > after.txt ONBUILD RUN diff before.txt after.txt > list.txt || exit 0 ONBUILD RUN grep '^>' list.txt| sed -e 's/> //g' > src.txt ONBUILD RUN ./aptlist.pl src.txt && tar jcf bin.tar.bz2 app CMD ["bash"]
cedar.shは以下の通り。
で必要なところを、切り貼りしただけ。
#!/bin/bash exec 2>&1 set -e set -x cat > /etc/apt/sources.list <<EOF deb http://archive.ubuntu.com/ubuntu trusty main deb http://archive.ubuntu.com/ubuntu trusty-security main deb http://archive.ubuntu.com/ubuntu trusty-updates main deb http://archive.ubuntu.com/ubuntu trusty universe deb http://apt.postgresql.org/pub/repos/apt/ trusty-pgdg main EOF apt-key add - <<'PGDG_ACCC4CF8' -----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v1 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 IIP9up4xwgje9LB7fMxsSkCDTHOk =s3DI -----END PGP PUBLIC KEY BLOCK----- PGDG_ACCC4CF8 cd / apt-get update #apt-get upgrade -y --force-yes apt-get install -y --force-yes \ autoconf \ bind9-host \ bison \ build-essential \ coreutils \ curl \ daemontools \ dnsutils \ ed \ git \ imagemagick \ iputils-tracepath \ language-pack-en \ libbz2-dev \ libcurl4-openssl-dev \ libglib2.0-dev \ libjpeg-dev \ libmagickwand-dev \ libmysqlclient-dev \ libncurses5-dev \ libpq-dev \ libpq5 \ libreadline6-dev \ libssl-dev \ libxml2-dev \ libxslt-dev \ netcat-openbsd \ openjdk-7-jdk \ openjdk-7-jre-headless \ openssh-client \ openssh-server \ postgresql-client-9.4 \ postgresql-server-dev-9.4 \ python \ python-dev \ socat \ stunnel \ syslinux \ tar \ telnet \ zip \ zlib1g-dev \ # # locales apt-cache search language-pack \ | cut -d ' ' -f 1 \ | grep -v '^language\-pack\-\(gnome\|kde\)\-' \ | grep -v '\-base$' \ | xargs apt-get install -y --force-yes --no-install-recommends exit 0
差分からエクスポート用のファイルを作成するツール
/usr配下のファイルを対象として、Herokuで動かすDockerイメージに必要なファイルの 抽出を行うツールを作った。
.pcファイルはheroku向けのコンテナイメージ内のパスに合わせて /usrから/app/usr/localへの書き換えも行う必要があった。
#!/usr/bin/env perl use File::Path qw(make_path); use File::NCopy qw(copy); use File::Basename qw(dirname); while(<>) { chomp($_); $destPath = $_; $destPath =~ s/\/usr/\.\/app\/usr\/local/; #print $_; if(-e $_) { if(-d $_) { #print "$_ is directory.\n"; print "$destPath\n"; make_path($destPath); } if(-f $_) { make_path(dirname($destPath)); copy($_,$destPath); if(/\.pc$/) { print ".pc! $destPath\n"; $cmd = "perl -pi.bak -e 's/prefix=\\/usr/prefix=\\/app\\/usr\\/local/' $destPath"; system("$cmd"); } } } else { print "not exists $_\n"; } }
動かし方
Dockerfileの作成
先ほどつくったイメージkjunichi/aptgetを使うだけ。
FROM kjunichi/aptget
node-canvasに必要なパッケージ
node-canvasのREADMEに書いてある依存パッケージは以下。 これをaptlist.txtに書く。
libcairo2-dev libjpeg8-dev libpango1.0-dev libgif-dev build-essential g++
以下のようにコンテナイメージ作成し、動かして、ファイルを取り出す。
docker build -t kjunichi/getapt . docker run -d -t kjunichi/getapt --name getapt docker exec getapt cat bin.tar.bz2 > dest.tar.bz2
依存ライブラリが入ったheroku/nodejsイメージを作る
heroku/nodejsイメージを自前で作ることで、heroku docker:initで生成されたDockerファイルがそのまま使える。
git clone https://github.com/heroku/docker-nodejs.git
以下のように抽出したファイル(dest.tar.bz2)を自前のheroku/nodejsイメージに組み込む。
# Inherit from Heroku's stack FROM heroku/cedar:14 # Internally, we arbitrarily use port 3000 ENV PORT 3000 # Which version of node? ENV NODE_ENGINE 4.2.4 # Locate our binaries ENV PATH /app/heroku/node/bin/:/app/user/node_modules/.bin:$PATH # Create some needed directories RUN mkdir -p /app/heroku/node /app/.profile.d WORKDIR /app/user # Install node RUN curl -s https://s3pository.heroku.com/node/v$NODE_ENGINE/node-v$NODE_ENGINE-linux-x64.tar.gz | tar --strip-components=1 -xz -C /app/heroku/node # Export the node path in .profile.d RUN echo "export PATH=\"/app/usr/local/bin:/app/heroku/node/bin:/app/user/node_modules/.bin:\$PATH\"" > /app/.profile.d/nodejs.sh COPY dest.tar.bz2 /app/user/dest.tar.bz2 RUN cd / && tar xf /app/user/dest.tar.bz2 ENV CXXFLAGS "-I/app/usr/local/include" ENV LDFLAGS "-L/app/usr/local/lib/x86_64-linux-gnu" ENV LD_LIBRARY_PATH /app/usr/local/lib/x86_64-linux-gnu ENV PKG_CONFIG_PATH /app/usr/local/lib/x86_64-linux-gnu/pkgconfig ENV PATH $PATH:/app/usr/local/bin RUN chmod +x /app/usr/local/bin/jq ONBUILD ADD package.json /app/user/ ONBUILD RUN /app/heroku/node/bin/npm install ONBUILD ADD . /app/user/
heroku向けのコンテナイメージ向けの対応
heroku向けのコンテナイメージでは/usr配下は手出しできない仕様のようなので、/app配下に使いたいファイルを 配置する必要がある。このため、配置したファイルをビルドツールやランタイムで参照できるように 環境変数を設定する必要がある。以下の環境変数をDockerfileで設定している。
環境変数名 | 設定値 |
---|---|
CXXFLAGS | -I/app/usr/local/include |
LDFLAGS | -L/app/usr/local/lib/x86_64-linux-gnu |
LD_LIBRARY_PATH | /app/usr/local/lib/x86_64-linux-gnu |
PKG_CONFIG_PATH | /app/usr/local/lib/x86_64-linux-gnu/pkgconfig |
PATH | $PATH:/app/usr/local/bin |
タイムゾーンの設定
今回利用しているamesh-cliモジュールはJSTで前提なので、herokuもJSTで動かす必要があった。
heroku config:add TZ=Asia/Tokyo
で設定出来た。
まとめ
やったこと
今回の作業を通じて以下のことができた。
- コマンドラインツールから一部機能を切出してライブラリ化する(amesh-cli)
- herokuで動かしたDockerイメージに入れたいパッケージを抽出するDockerイメージの作成
- https://github.com/kjunichi/docker-getapt
わかったこと
- slackのトークン等の環境変数はherokuでは無効化されているようだが、LD_LIBRARY_PATHは今のところ、Dockerfileでの設定が効いている模様。
- PerlのFile::Copyだと実行属性がコピーされないので、File::NCopyを使うと良さそう
- herokuのタイムゾーンの設定でDockerコンテナのタイムゾーンも設定される模様
- boot2dockerをv1.10.0-rc1にあげないとSetting up ca-certificates-javaで進めなくなる
参考資料
関連記事
- Node.jsでターミナル.appにアメッシュを表示させてみた
- JavaScriptをDockerを使って黒い画面に表示する
- IJuliaをHerokuの無料枠で動かした
- herokuでbotkitを動かしGoogle Calendar APIを使う
- アメッシュでECONNRESETを食らっててslackbotが死んでた件
- herokuにデプロイしてたSlackボット(Botkitベース)を治した