社内galaxyを作ろう

これは何

自分でansible galaxy を作る。特に今回は公開のものではなくて、社内に限る共通roleを作る話。 実際に作ったroleは社内事情や秘匿情報を含むのでここには書きません。

背景

社内クラウドのリソースを管理するplaybookをある人が作った。その後それをコピーして他のサービスで同じコードを使った(以下繰り返し)。 何かミドルウェア仕様が変更になったり、設定を変えたい場合にすべてのサービスでメンテナンスコストが発生する。 この状態は最低だと思ったので、社内galaxyを作り共通roleにしようと考えた。 その時に考えたこと、ハマったことをここに記録しておきたい。

参考

作り方

  • meta/main.yml を配置する
    • 内容は ansible-galaxy init hoge で適当なものを作ってコピーしてくるのが楽
  • 使う
    • 上のmetaファイルだけ置いてしまえばもうansible-galaxy installで読み込める
    • ansible-galaxy install git+http://github.fuga.co.jp/dw-ansible/piyo.git --roles-path ./ex_roles/
  • 整理する
    • あくまでgalaxyはroleを共有するという感じなので、普通ansibleレポジトリにはinventoryやplaybookファイルが含まれていると思うが、それらを削除する
    • defaults/* ファイルへデフォルト設定を配置する

defaults/ について

  • どのようなサービスでも共通の設定はmain.ymlにおいておくと良い
  • 必ず変えて使って欲しいという変数はあえてデフォルトを配置せず実行時にエラーで落ちるようにしたほうが安全かなと思った
  • main.ymlは読み込まれるが他は特に指定しなければば読み込まれない。変えて欲しい値だけどテンプレートは欲しいよねというような値はtemplate-*.ymlみたいなのをおいて読み込み先のplaybookでコピーして書き換えてもらうのがよさそう

その他

  • 社内共通galaxyは、作るのはそこまで難しくないことがわかった
  • むしろ広報や運用が難しい。使ってもらわなければroleは育っていかないし、破壊的変更をすると困るユーザがいるはずなので合意を取って前にすすめていくのは大変。後方互換を保ったまま育てていくのもカオスになりがちだし、これからそこの悩みが出てきそう
  • とはいえ、作ればみんな喜ぶので積極的に共通パーツを作って展開していきたい

netscaler LBにおいてサーバを使わずにリダイレクトする

これは何

netscaler LBにおいて特定FQDNへのアクセスをパスそのままで他のFQDNにリダイレクトする方法についてまとめる。 この資料は NetScaler version 11.0 で実験した動きを元に書いています。

目次

参考資料

https://support.citrix.com/article/CTX120664 大体これを読めばできるが以下の問題があった(この資料を作るモティベーション

  • LoadBalancing Virtual Server の設定のところがServiceと書いてあり誤植と思われる
  • 設定箇所の階層が書いておらず多少読みづらい
  • LoadBalancing Virtual ServerにGlobal IPを設定するパターンしか書かれていない

手順

System設定でresponderの有効化

NetScaler -> System -> Settings -> Configure Acvanced Features
responder にチェックを入れる。

Responder Actionの設定をする

  • Addする(既存のActionを選択してAddするとそれをテンプレートとして使うことになる。最初から作るなら選択を外してAddする)
  • TypeはRedirectを選択
  • Expression
    例1) http->httpsにリダイレクトするなら、スキーマのところだけhttpsと書いて後は同じ
"https://" + HTTP.REQ.HOSTNAME.HTTP_URL_SAFE + HTTP.REQ.URL.PATH_AND_QUERY.HTTP_URL_SAFE

例2) 特定の別のドメインにリダイレクトする

"http://piyo.jp" + HTTP.REQ.URL.PATH_AND_QUERY.HTTP_URL_SAFE

補足: L7で http://hoge.jp/fuga などで入ってきたLoadBalancing Virtual Server経由でこのactionで http://piyo.jp/fuga に飛ばすイメージ
* Response Status Code を設定する デフォルト 302

Responder Policy の設定をする

  • さっきのActionを設定する 
  • Undefined-Result ActionをRESETにする
    • tcp handshake の rst に対応すると思われる。dropとするとクライアント側はそのまま待ち状態になってしまう。
  • Expressionには HTTP.REQ.IS_VALID を設定(frequently Userd Expressionから選択すると良い)
    • 要するにこのpolicyでは特に何もせずに(validかチェックだけして)actionへ流すという意味か

monitor 設定をする

localhostpingをうつmonitor設定を作る

service を作る

上で作成したmonitorを使って常にserviceがUP状態になるようにする。あくまでダミーで、LoadBalancing Virtual Serverが常にUP状態になるようにするためのものの模様

  • 上記serverを紐付ける
  • ProtocolとPortはHTTPと80とでも適当に設定しておく
  • 作成後、Monitoring設定で先程作成したmonitorを設定し、UP状態になることを確認する

LoadBalancing Virtual Serverを作る

  • IP Address: WebサイトのIPアドレスもしくはPrivate IP
    • Global IPで直接受ける場合にはここに設定
    • Content Switching Virtual Server通すならここはPrivate IPを指定する
  • 作成後Responder Policyを設定

Content Switching Virtual Server 設定

上記でLoadBalancing Virtual ServerにPrivate IPを設定した場合にはContent Switching Virtual Serverのpolicyに紐付ける この手順はredirector特有というよりもContent Switching Virtual ServerからLoadBalancing Virtual Serverに流す手順と同じなので省略する 

キャッシュサーバが遠くなるとレイテンシは上がるのか、pingとtcpdumpで考えた

これは何

私の持っているシステムではフロント系サーバからmemcached サーバへ接続回数が結構ある。
データセンタ内で今まではフロント系サーバもキャッシュサーバも同一セグメント、物理的にも近くにいた。
とある事情でサーバをリプレイスしなくてはならず一時的にそれが違うセグメントで、物理的にも遠く、間にいくつかスイッチとルータが挟まるようになる。
これにより新しいフロント系サーバから古いmemcachedサーバに接続するとレイテンシが問題になるかもしれない。
ネットワーク的に遠くなると実際にアプリケーションの応答性能の視点で問題になるのか、いまと比べて実際は大した事ないのか判断するために考えたことのメモ。

目次

測定

測定方法

ping のtimeの100回の平均を取る。 pingのtimeは何か -> https://linuxjm.osdn.jp/html/netkit/man8/ping.8.html

往復時間 (round-trip time) と消失パケットの統計が計算される。

なので、単なる往復時間。

TCPにおける遅延は? TCPの場合はパケットごとにACKするので、送りたいもののデータサイズが大きく、パケットの個数が増えると全体としてはそれだけ1パケットあたりの遅延が蓄積されていくことになる。 なので、pingのRTTの増加はそのままレスポンスタイムの遅れに繋がると考えられる。

測定実施

2台のサーバが同一セグメントにある 旧frontサーバ(hoge-old-web) => 旧cacheサーバ(hoge-cache)

[myuser@hoge-old-web ~]$  ping -c 100 xx.xx.xx.xx > ./tmp_to_hoge-cache_ping
[myuser@hoge-old-web ~]$ grep "time="  ./tmp_to_hoge-cache_ping > ./tmp_to_hoge-cache_latency
[myuser@hoge-old-web ~]$ awk '{print $(NF-1)}' tmp_to_hoge-cache_latency | awk -F'=' '{sum+=$2} END{print sum/NR}'
0.14284

0.14 ms くらい

新frontサーバ(hoge-new-web) => 旧cacheサーバ(hoge-cache)

[root@hoge-new-web ~]# ping -c 100 xx.xx.xx.xx > ./tmp_to_hoge-cache_ping
[root@hoge-new-web ~]# grep "time="  ./tmp_to_hoge-cache_ping > ./tmp_to_hoge-cache_latency
[root@hoge-new-web check_latency]# awk '{print $(NF-1)}' tmp_to_hoge-cache_latency | awk -F'=' '{sum+=$2} END{print sum/NR}'
0.16793

0.16 ms くらい

差分

0.16793 - 0.14284 = 0.02509 ms 割合ていうと 0.02509 / 0.14284 * 100 = 17%。

frontへアクセスした時にmemcachedはどれくらい叩かれている?

コードで呼ばれているcacheメソッドの回数も重要だが、遅延全体を考えるならパケットがどれくらい投げられているかが重要。なのでtcpdump でパケット数がどれくらい投げられているのか確認したい。

myuser@hoge-dev-web [~ ] $ sudo tcpdump -i eth0 > ./tmp_tcpdump_to_memcache
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes
^C7136 packets captured
7136 packets received by filter   // 全部でこれだけのパケットが1リクエストで通った
0 packets dropped by kernel

その中で memcachedにつなぎにいっているのは?

myuser@hoge-dev-web [~ ] $ grep "xx.xx.xx.xx.11211 >" tmp_tcpdump_to_memcache -c
1579

memcachedから来ているパケットは1リクエスト1579回。(行きも同数。pingのtimeはRTTなので片方の道を通っている回数がわかれば良い。)

最大で増えるレイテンシはどれくらい?

毎回のパケット 0.02509 ms * 1579 = 39.6 ms。ほとんど誤差。気にしなくてもよさそう。

結論

今回の件ではレイテンシはあまり気にしなくても良い。

ansibleのget_url moduleでSNIのhttpsサイトからダウンロードが出来ない【問題と解決】

これは何

ansible version 2.2。get_url moduleを使ったときの話。 ターゲットサーバのOSはSL6.8。python 2.6。

結論から言うと

証明書期限切れしていただけでした。ドハマリした。 期限切れしていない正常なダウンロードサイトからのものはpython2.6にpipでライブラリいれたら治っていた。

問題

"msg": "Failed to validate the SSL certificate for hogehoge.org:443. Make sure your managed systems have a valid CA certificate installed. If the website serving the url uses SNI you need python >= 2.7.9 on your managed machine or you can install the `urllib3`, `pyopenssl`, `ndg-httpsclient`, and `pyasn1` python modules to perform SNI verification in python >= 2.6. You can use validate_certs=False if you do not need to confirm the servers identity but this is unsafe and not recommended. Paths checked for this platform: /etc/ssl/certs, /etc/pki/ca-trust/extracted/pem, /etc/pki/tls/certs, /usr/share/ca-certificates/cacert.org, /etc/ansible"}

解決策

提示されている解決策とやってみた内容は以下

  • python 2.6だとurllib3, pyopenssl, ndg-httpsclient, and pyasn1 を入れると治るかも => pipで入れてみたが、この部分のエラーメッセージだけが消えて他は同じ。解決せず
  • python2.7を入れてみる => ソースからビルドして ansible_python_interpreter=/usr/local/bin/python に設定してみた。同じくこの部分のエラーメッセージだけが消えて、解決せず
  • 後の問題は、そもそも正しい証明書が系にインストールされていない? 根が深そうなきがするが・・・

じゃあSNI対応しているwget使うとどうか? => 同じように検証でエラーがでる。SNI対応していて治るなら1個め2個めで治っているはずだしね。 結局、ブラウザでアクセスしてよくよく見てみると、

この証明書の有効期限は 2017年8月2日 18:39 に切れています だった。

※これが起こった日は 2017/8/4 最初にダウンロードリンクを取る時にアクセスしてスルーしていたのだった。ちゃんと真っ赤になっていたのに。「ドメインあっているよなあ」などと思って。

結論

思い込みで時間を無駄にした。 やはり違和感は大切にしよう。後で調べる、と思っていたことがトラブルの元になるので、違和感があったらできるだけ説明がつけられるようにしておこう。

swapが出るDBサーバの vm.swappiness と innodb_flush_metho=O_DIRECT設定 について

これはなに

MySQLサーバでswapが出てレプリケーション遅延が起こっていた。その原因を考えた時のメモ(結構昔だけど、掘り起こして少し整理)。

トラブル発生時の状況

swap 全食いつぶしされていた(swapは8Gの設定)。swap が単に発生したというよりも、swap in/out が沢山あったのが問題。3列目がページイン(swapin)、4列目が ページアウト(swapout) 。単位は kbyte/s。

$ sar -f /var/log/sa/sa16 -B  | grep '11:20' -A 10
11:20:01 AM     19.96   1528.86    227.95      0.23    306.29      0.00      0.00      0.00      0.00
11:30:01 AM     32.78   1060.79    284.00      0.41    418.23     57.04      2.05     53.95     91.29
11:40:01 AM     35.41    918.87    288.44      0.32    613.35    156.52      4.00    154.15     96.03
11:50:01 AM     30.93    931.70    337.14      0.23    635.15    129.01      3.78    125.92     94.82
12:00:08 PM   1090.31  66591.60   1161.96     38.31  18141.11 264214.86 998791.30  17389.23      1.38
12:10:04 PM  16094.34  73548.50   1853.48     65.37  23471.29 248716.54 1527936.86  22704.65      1.28
12:20:05 PM  25803.10  25574.29   2219.17    183.17  13001.47  45104.96  88174.19  12253.92      9.19
12:30:07 PM   6338.98  27665.28   1886.63    201.32   8700.89  42764.03  85559.13   7997.71      6.23
12:40:05 PM   6489.96  22360.17   1541.27    204.05   7111.80  38805.93  31339.39   6642.17      9.47
12:50:01 PM   7012.49  16050.09    730.82     80.87  14578.10  23819.42  35602.05   3570.47      6.01
01:00:01 PM      0.03      1.32     43.08      0.01     28.28      0.00      0.00      0.00      0.00

cpu 使用率 system 30% と I/o wait 20 % くらいでていた。User は数%程度でそこまで大きく変わらず。

対策

swappiness 設定

https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Performance_Tuning_Guide/s-memory-tunables.html  anonymous memory か page cache かどちらを優先するのかの度合。値が低ければ スワップアウトを避ける(anonymous memoryをスワップに出さずに、page cacheをディスクに書き戻すのを優先する)。 DBだと低めの値を設定するのが推奨のよう。 RHEL 6.4 からは swappiness = 0 はさらに強くswapout しないような仕様になっているそうなので注意が必要。Oracle Databaseで10が推奨とのこと。

anonyamous memory って何? page cacheとの関係は?

http://nopipi.hatenablog.com/entry/2015/09/13/181026   無名ページ —>メモリが足りない時スワップアウト(スワップへ退避)され、物理メモリから解放されるページ ファイルページ —> メモリが足りない時ディスク同期し解放されるページ スワップする/しない、の差。 どういうこっちゃ? もうちょっと調べる。

http://enakai00.hatenablog.com/entry/20110906/1315315488
http://mkosaki.blog46.fc2.com/blog-entry-884.html
MemTotal = MemFree + File-backedなメモリ + Anonymousなメモリ + カーネル空間が使うメモリ File-backed(file pageやpage cacheとも)というのは、ディスクからメモリに読み込んだファイルなど、メモリを開放したくなったら、その内容をディスクに書き戻せば開放できるタイプのメモリ。ファイルキャッシュ、バッファ、プロセスのテキスト領域(プログラム本体) 

Anonymousというのはそれ以外のメモリで、メモリを開放したくなったら、Swap領域に書き出さないと開放できないタイプのメモリ。プロセスのメモリ(データ/スタック領域)、共有メモリ、tmpfsとか。

maria DBでのswappiness 推奨設定

https://mariadb.com/kb/en/mariadb/configuring-swappiness/   swappiness = 1 がよい。 そもそも メモリがswapであることを想定したアルゴリズムではない、とのこと。mysql でも同様か? 値の参考には出来そう。

結局どのような設定にすべきか?

innodb buffer pool 内に書かれているものは、ディスクにそのまま書き戻せるものではなくて、検索結果や更新途中の処理のデータが乗っている。つまり、mysqldによって作られたanonymous memoryとして持たれていると思われる。anonymous memory は以下のように確認する。18G ちょっとある(データはトラブルの起こっていない他のDB)。

$ grep anon /proc/meminfo -A 2
Active(anon):   19021848 kB
Inactive(anon):  1724228 kB
Active(file):    1416160 kB
Inactive(file):  1705648 kB

バイナリログ/その他ログなどはfile cacheへ置かれていると考えられ、これらがメモリ内に共存することになる。 ここで swappiness が高い(デフォルト60)だと、file cache とanonymous memory のどちらも物理メモリ上に残そうとするため、物理メモリがあふれるとfile cacheの一部がディスクに戻ると共に、anonymous memoryの一部がswapに書きだされる。kernel の mm/vmscan.c を読むと、fille_prio = 200 - anon_prio (デフォルトなら file:anon = 140 :60)となっており、LRU(使ってないものから追い出す)に基づいて追い出す模様(深くは追えていない)。 一方 swappiness が低い(例えば設定値1の)場合には、anonymous memoryを物理メモリ上に置いておく優先順位が高くなり、file cacheが優先的にディスクに戻される。すなわり、swapは起こりにくくなる。
DBにおいてswap が発生するのと、file cacheが揮発するのとどちらがサーバ全体に有利かという点を考える。innodb buffer pool が swap に書きだされるか、主にその他ログファイルがディスクに戻るかとの違い。

file cache に乗る可能性のあるもの。ログファイルの大きさは innodb_log_file_size = 128M であり、そこまで大きくない。slow query log は(肥大化しているとはいえ)300M 以下。DBサーバにおいてメモリに必ず乗っていて欲しい大きなファイルなどは他にない。他に数G単位のがあるならbuffer pool sizeチューニングの話のところで出てくるはずだし、Active(file)あたりにも現れるはず。テーブルスペースから buffer pool にのせる前にfile cache にのせることがある。この buffer pool にのせる前のfile cache については innodb_flush_method 設定で抑制できそう。
結論: 単に各種ログファイル等が開かれることにより乗った古い file cache がディスクに書き戻されても差支えないと考えれば、swap が出るよりは file cacheに乗っているデータをファイルに戻してもらった方がよい。  

innodb_flush_method = O_DIRECT設定

設定の意味は? https://dev.mysql.com/doc/refman/5.5/en/innodb-parameters.html#sysvar_innodb_flush_method   データファイルを開くときには O_DIRECT を用いて、flushするときにはfsync() を用いるというオプション。ただし、fsyncするときもOSはデータキャッシュを行わない。 デフォルトでは innodb データファイルから、一回ファイルシステムキャッシュを経由して innodb buffer pool にデータを読みだす。 O_DIRECT設定をいれると、データファイルから直接buffer pool に読み込むことで、システムによるキャッシュとRDBMSによるキャッシュの重複を無くすという感じ。buffer pool にキャッシュされているデータは、まったく内容が同じというわけではないが役割がかぶるので、それだけを使う。

デメリットはあるのか?

http://d.hatena.ne.jp/sh2/20101205 性能を上げるというよりも、メモリ使用量を制限するというためのものだと考えた方がよさそう。 どういうときに入れてもいいのかという話は 「High Performance MySQL」に書かれていた。https://books.google.co.jp/books?id=JXFuCQAAQBAJ&pg=PA363&lpg=PA363&dq=O_DIRECT+raid+write+back&source=bl&ots=8oVbZYwCdj&sig=4vpbTujup-BfTM0bRACIsnOLcA0&hl=ja&sa=X&ved=0ahUKEwi6qdrEseDMAhVBpJQKHVK9D-4Q6AEIHjAA#v=onepage&q=O_DIRECT%20raid%20write%20back&f=false この設定はOSによるキャッシュは無効化するが、RAIDカードによる read-ahead は無効化しない。O_DIRECT を有効化してかつパフォーマンスを落ちないようにする方法は、RAIDカードによる write-back設定をいれたwrite cache が必要。InnoDBと実際のストレージとの間にバッファがなければ、パフォーマンスは劇的に悪くなる。 弊社で使用しているサーバは write-back 設定を入れてあるはずなので大丈夫(構築時に一応確認するが、大体デフォルトで入っている)。 また、設定を入れるとMysqlを起動した直後のパフォーマンスが悪くなりそう。OSがキャッシュしていれば起動後にdiskから直接読みださなくてよいので早くなる。これは、OS再起動後とかだとどちらにせよOSによるファイルキャッシュも消えていると思うので気にしてもしょうがなさそう。

設定手順

vm.swappiness = 1の設定を入れる
sudo su -
sysctl vm.swappiness # 確認
sudo cp /etc/sysctl.conf /var/tmp/sysctl.conf
sudo vim /etc/sysctl.conf 
 # 末尾に vm.swappiness=1 を追記
diff /var/tmp/sysctl.conf  /etc/sysctl.conf
sudo sysctl -p # 設定を反映
sysctl vm.swappiness # 反映を確認
innodb_flush_method = O_DIRECT の設定をいれる

[mysqld]ブロックの最後にでも以下の記述を追加する

innodb_flush_method=O_DIRECT
mysql restart する

$ sudo service mysql restart

設定確認する
mysql -uroot -p -e "show global variables ;" | grep innodb_flush_method

納期と障害とElasticBeansTalkのお話

さて、納期がありますね。
急いで開発しますね。
リリースしますね。
喜びますね。
お客さん沢山きますね。

・・・(しばしリリースを喜ぶ)

負荷が上がりますね。
詰まりますね。
障害ですね。

こんにちは。ElasticBeansTalkを作ってとあるキャンペーンの特設サイトを構築した話です。

要件的なやつ
  • リリースまで残り10日くらい
  • キャンペーン期間は一ヶ月以下
  • SSLじゃないとダメ(oauthのコールバックの関係)
  • Nodejs, Nginx, 適当なNoSQL

期限的にオンプレでssl証明書買うのは無理だし、ちゃんとしたの買うと短い期間しか使わないのに高い。そしたらクラウドに金払った方が良い。 どうやらAWSACMというやつで高速で証明書発行してhttpsサイト作れるらしいじゃないの。最高。
そしてElasticBeansTalkというやつで高速にELB+EC2(Nginx+Nodejs)+DynamoDB立てられるらしいじゃないですか。

こりゃ楽勝だな。

やったみた
  • ElasticBeansTalk+ACMを使って、サンプルアプリデプロイして動かすまで1日かからず。最高。
  • インフラとしては監視を入れたい。ebextensionsを使ってCloudFormation的なものを書く。ほぼサンプルなのでデプロイすれば動く。楽勝。
  • ↑のものを何が書いてあるのに理解するのに3日くらい(まだ理解仕切れていない)。AWS普段触らない人には辛い。
  • AWSでどれくらいかかるか詳細見積もり(ざっくり見積もりはEC2使うと決めた時点でざっくりとっていたが、インスタンスタイプとかアクセス見積もりとか詳細のやつ)。
  • DynamoDBもサンプルデプロイして半日たたずに動く。なるほど。オートスケールはデフォルトじゃないのか。気をつけないとね。楽勝(嫌な予感。伝えておかないと)。サンプル突っ込んだら動いたことを開発側に伝えて、ドキュメント渡してその後ここを丸投げ。
  • Slack通知もしたいねということで、LamdaのBlueprintとかいうのを使ってSNSからLamda通してSlack通知へ。
  • その他eb設定もebextensionsで管理

slack通知のタスクは残念ながらリリース時間には間に合わなかったけど、アプリ共々動いてるのでリリースはできた。

で、問題は?

リリース後、しばらくしたら5xx返すかクソ重いかになる。
EC2はCPUまあまあ食ってるけど詰まるほどではない。メモリはスカスカ。サーバ内書き込みないからDiskIOなんて起こるはずもない。確かにNodejsなので1プロセスだけ立てててそれが詰まると死ぬのだが、それだったらCPUはりつくのではという感じだし。実際にオートスケールの下限を増やしたが関係なし。
となるとDBだな。 はい。DynamoDB詰まってました。
オートスケールするんじゃないの?

前述した通り、オートスケールはデフォルトではありませんでした。
自分で手作業で検証した時には気がついていたけど伝えてなかった。開発側がebextensions書いてTable作るとのことでそのままお任せしてしまった。
高速Backend開発。忙しすぎる開発者。
そしてリリースされるアプリ。
オートスケールしないクソ弱いDB。
当然詰まる。
死。

はい。雑にまとめておきましょう。

教訓

  • なんとなく嫌な予感は大切にしましょう
  • 気になることはなんでも伝えましょう
  • 忙しい人は細かいこと忘れるので、少しでも余裕がある人が地雷を気にしてあげましょう
  • 後で絶対にやる・確認するタスクは分かりやすいところに置きましょう。今回はリリース前に気になりリストを最終確認する時間を取れば問題起こらなかったですね。github issueとかだとめっちゃ急いでいると見るの忘れるので、slackのリマインダ機能使いましょう。

最後の行がこの話で書きたかったこと。忘れそうなものは分かりやすいところへ。PULL型ではなくてPUSHで教えてくれるように。

以上

はじめてnodejsを使った。はじめてbotを作った話。

これは何

nodejsを使ってslack botを作った話。 インフラエンジニアだけどコードもかけるようになりたい、といって少しだけ時間をもらってbotを作る仕事をやった。 その時のメモ的なもの。

何を使う

botkitというツールキットを使う。 hubotと何が違うの? そういうのを調べたりはしてません。お手軽にモダンな感じで作れるよ、とおすすめされたので使った見た感じ。

コード

コードは長くないのでここに貼ってしまう。
npm install --save botkit とかして、package.json作らせた(と思う)。
channel id はチャンネルがprivateの場合はhttps://api.slack.com/methods/groups.list/test で確認する。publicの場合は /channels.list/test で確認する。slackの場合。facebook messanger とかだとまた別の取得方法だと思う。
token はbot のものを使用する。https://app.slack.com/apps/new/bot から作成したもの。custom botについてはこちらを参照のこと:https://api.slack.com/bot-users

const Botkit = require('botkit');
const os = require('os');

// 色々変数チェック。他dest,originについてはここの説明では略
if (!process.env.token) {
  console.warn('Error: Specify token in environment');
  process.exit(1);
}

const controller = Botkit.slackbot({
  debug: true,
});

controller.spawn({
  token: process.env.token,
}).startRTM();

const destChannel = process.env.destChannel;
const originChannel = process.env.originChannel;

controller.hears('.*https?://www.example.com[sS]*', 'ambient', (bot, message) => {  // [sS]* は改行含む全一致。.*だと改行入らない。ambient はbotが全発言を拾うということ(direct_messageとかもある)。 
  // 指定のチャンネルのときのみ発言を拾う
  if (message.channel === originChannel) {
    const matchMessage = message.text;
    const userId = message.user;
    bot.api.users.info({ user: userId }, (userApiError, userApiRes) => {
      const userName = userApiRes.user.profile.real_name;
      bot.api.channels.info({ channel: message.channel }, (channelApiError, channelApiRes) => {
        // private channel はchannels apiからは情報取得できない。groups apiなら取れるが、privateチャンネル名は知られなくても良い
        const channelName = (channelApiError !== 'channel_not_found') ? channelApiRes.channel.name : 'どこかprivate channel';
        bot.say(
          {
            text: `${userName}さんが作品リンクを投稿しました(in #${channelName}) \n -------------------------- \n ${matchMessage} \n --------------------------`,
            channel: destChannel,
          });
      });
    });
  }
});

起動は

originChannel='<channel_id1>' destChannel='<channel_id2>' token=<bot_token> node urlcopy_bot.js

その他

eslint 使ってみた。 明らかな表記ブレは適当に直してくれるし、コードはきれいになるし、紛らわしい表記とか教えてくれるし、割と良かった。 他のところにもlint入れてみようかなと思った。

追記

slack接続が切れてしまって(close RTMになる) botが死んだように見える現象があった。 最初は自分で close_rtmイベントをフックしてconnectするように書いていたが、botkitはデフォルトで予期せぬ接続断があったらreconnectするようになっているため、両方で接続しに行ってしまい、多重接続(このbotの場合は2回投稿してしまう動き)。参考:https://github.com/howdyai/botkit/blob/bee9c5c70dcf3c024e71f2a10bdb2e425c421988/lib/Slackbot_worker.js#L230-L233
また、その他のエラーでcloseRTMしたときにはretryEnabledがtrueならreconnectしてくれるっぽい。こちらの動作に任せることにした。参考: https://github.com/howdyai/botkit/blob/bee9c5c70dcf3c024e71f2a10bdb2e425c421988/lib/Slackbot_worker.js#L70-L86
そこで、ドキュメントの通り、

const controller = Botkit.slackbot({ retry: Infinity });

みたいにしたらちゃんと接続が切れてもreconnectしてくれるようになった。