この記事はaratana Advent Calendar 2017 21日目の記事です
こんにちは、インデントは全角スペース派1の岡元です。
前日は@aratana_tamutomoさんの「Vim一年生による、Vimプラグイン作成指南」というエントリーでした。
Vimプラグインってあんな感じで作るんですね!
今回は、bashスクリプトやPHPコードを実行するslack botを作ったのでそれについて書こうと思います。
(botのソースはこちらです)
botについて
botに対してメンションしてメッセージを送ると、メッセージをbashスクリプトとして実行してくれます。
また、メッセージに-php
のプレフィックスをつけるとPHPのコードとして実行してくれます。
スクリプト自体はdockerコンテナ内で実行するのでrm -rf /
などファイルを削除する系のコマンドは実行してもおそらく大丈夫ですが、
:(){ :|:& };:
などのリソースを食いつぶす系のスクリプトはコンテナ内だけでなくホストにも影響があるかも知れないので実行するときは気をつけたほうがいいかもしれません。
botを作る
botのフレームワークはpythonのslackbotを使います。
プログラムの内容は、受け取ったメッセージをsubprocessでrunするだけです。
メッセージはクオートが全角になっていたりurlエンコードされていたりするのでrunする前に整形します。
from slackbot.bot import default_reply
import subprocess
from plugins.utils.Modifier import Modifier
@default_reply
def shell(message):
modifier = Modifier()
text = modifier.modify(message.body['text'])
print(text)
res = subprocess.run(text, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, executable="/bin/bash")
message.send(res.stdout.decode())
また、これをdockerで実行させるためにDockerfileを書きます。
ファイルはdocker hubのpythonのページにあるサンプルとほぼ同じものです。
(サンプルのDockerfileについて、はじめは何でCOPY
を二回に分けてるんだろうと思いましたが、
COPY
を一回で纏めてしてその後にRUN pip install
するとファイルを編集するたびにキャッシュが無効になるからだったんですね。)
PHPコードを評価するサービスを作る
先ほど作成したpythonのイメージとは別にPHPコードを実行するイメージを作成します。
プログラムの内容は単純にhttpリクエストで渡ってきたコードを直接evalする感じです。危ないですね。
if (isset($_REQUEST['eval'])) {
var_dump(eval($_REQUEST['eval']));
}
実際には、エラー時の出力をいい感じにしたり、入力のreturn
や行末の;
を省略しても実行されるようにしたいのでpsyshを参考に実装します。
以下はDockerfileです。
multi-stage buildsを用いることで、PHPの依存ファイルのインストールが簡単に行えます。
FROM composer:1.5.5 AS build
COPY composer.json composer.lock /app/
RUN composer install --no-dev
FROM php:7.1.12-apache-jessie
COPY --from=build /app/vendor /var/www/vendor/
COPY . /var/www/
RUN ln -s /var/www/public/* /var/www/html/
また、slackからメッセージを受け取りPHPコードを評価するサービスを呼び出す処理を先程のpythonイメージに追加します。
import re
from slackbot.bot import respond_to
from plugins.php.Client import Client as PhpClient
from plugins.utils.Modifier import Modifier
@respond_to('^-php (.*)', re.IGNORECASE)
def main(message, php_message):
modifier = Modifier()
client = PhpClient()
php_code = modifier.modify(php_message)
message.send(client.eval(php_code))
docker-composeする
これまででbotを実行するpythonイメージとPHPコードを評価するphpのイメージを作成したので、docker-composeを使いこの2つを動かしてみます。
version: '3'
services:
shellbot:
build:
context: .
networks:
- bot
environment:
- SLACKBOT_API_TOKEN=${SLACKBOT_API_TOKEN}
php-evaluate-server:
build:
context: ./php-evaluate-server
networks:
- bot
networks:
bot:
docker-composeファイルで環境変数の値を指定する際は${...}
のように書くことで現在のシェルの変数を入れることができるみたいです。
また、デフォルトの値は.env
ファイルを見てくれるみたいです。(詳しくはこちら)
SLACKBOT_API_TOKEN=xoxb-12345XXX
ここではslackbotの実行に必要なSLACKBOT_API_TOKEN
という変数を設定しています。
SLACKBOT_API_TOKEN
にはslackのcustom integrationsでBotsを追加することで取得できるAPI Tokenを設定します。
今作成したdocker-composeファイルを使いbotを動かすには以下のコマンドを実行します。
$ docker-compose up --build
rm -rf /されたら、、swarmのhealthcheckを使う
rm -rf /
などを実行されると/bin/bashなどのファイルが削除されてbotが動かなくなってしまうのでhealthcheckでbotが動ける状態にあるか監視します。
dockerのswarmモードを用いるとstatusがunhealthyになった際にコンテナを立ち上げなおしてくれます。
swarmにサービスをデプロイする際にdocker stack deployコマンドを用いるのですが、
その際に使用するdocker-stackファイルを以下のように作成します。
version: '3'
services:
shellbot:
image: mokamoto12/slack-shellbot:0.2
networks:
- bot
env_file: .env
depends_on:
- php-evaluate-server
healthcheck:
test: type ls
php-evaluate-server:
image: mokamoto12/php-evaluate-server:latest
networks:
- bot
healthcheck:
test: type ls
networks:
bot:
先程のdocker-composeファイルと違うのはbuildが消えimageになっているのと、environmentが消えenv_fileになり、healthcheckの項目が増えています。
buildではなくimageを使う
swarmを用いる際には複数のマシンでコンテナを立ち上げることになるため、それぞれのマシンからアクセスできるdocker registryが必要になるみたいです。
そのため、buildの項目は使うことはできず、imageでregistryにあるイメージを指定します。
environmentで.envファイルによる値の指定ができない
docker-composeファイルでは${...}
のように記述することで.envで指定した変数の値を用いることができていましたが、試しにdocker stack deployしてみると値がうまく設定されていませんでした。(よくリファレンスを見ると.envは使えないことが書いてあるんですね)
代わりにenv_fileで環境変数の設定がされているファイルの指定を行います。
healthcheck
healthcheckのtestで記述した内容はコンテナ内で定期的に実行され、その終了ステータスからコンテナがhealthyな状態かunhealthyな状態かを判断してくれます。
また、先程も書きましたがdocker stack deployした時にはコンテナがunhealthyになると勝手に新しくコンテナを立ち上げてくれます。
今回はtype ls
を実行し/bin/lsのファイルが削除されていればbotが動かない状態としています。
botを実行する
上で作成したdocker-stackファイルを用いbotを動かします。
以下のコマンドを実行します
$ docker stack deploy -c docker-stack.yml bot
まとめ
docker swarmを用いることでrm -rf /を実行されても復活するbotを作成することができました。
また、今回始めてdocker swarmを使ったのですがdocker-composeで使っていたdocker-compose.ymlをそのまま使うことができず一部試行錯誤する感じでした、
リファレンスを読むだけではわからないことも多く実際に触ってみることで色々理解できることもあったので良かったです。
明日は@okazono_erikaさんの「初対面の人に会う時のドキドキのついて」です。
ドキドキしますね!
-
冗談です ↩