Memento memo.

Today I Learned.

Elixir ApplicationをGAEにデプロイ

GAEの説明を見る限り、メジャー言語しか対応してなさそうに見えたのですが、 Docker (Custom Runtimes) を選択すれば何でも動かせるそうです。

EBSもGAEもDockerさえ使えばどんなRuntimeでも使える => Elixirも動かせる!!!

ということでGAEにElixir製のbotをデプロイしてみました。

deploy

https://cloud.google.com/appengine/docs/flexible/custom-runtimes/quickstart を参考に。

Dockerfile

Dockerfileの設定はこんな感じです。

Elixir製Hubot風BotフレームワークのHedwigでSlack Botを作る - Memento memo. で作成したbotをデプロイしました。

FROM msaraiva/elixir-dev
MAINTAINER shotat

RUN mkdir /app
COPY . /app/
WORKDIR /app
RUN mix do deps.get, compile

CMD ["mix", "run", "--no-halt"]

app.yaml

runtime: custom
vm: true
env_variables:
  ELIXIR_BOT_TOKEN: xxxxxxxxxxxxxx

token系については、実行時に環境変数で渡すのが良いみたいです。 ここに設定していいのかは若干謎ですが、Dockerfile内にべた書きするよりマシなので一旦これで。

deploy

事前にprojectの作成 & SDKのインストールの設定が済んでいれば、以下のコマンドを実行するだけでdeployできます。

$ gcloud app deploy

以上です。

Elixir製Hubot風BotフレームワークのHedwigでSlack Botを作る

Hubotに飽きたので、HedwigというElixir製のFWでbotを作ってみようと思い立ちました。

Hedwig

github.com

Adapter baseのHubotっぽいBotフレームワークです。Elixirです。

公式のAdapterでSlack対応してます。

Setup

mixでprojectを適当に作ります。supervisor (--sup)つきで。

$ mix new hoge --sup
$ cd hoge
$ vi mix.exs # 以下の設定を記述
$ mix deps.get

mix.exs ファイルの設定

...
def applications do
  [applications: [:hedwig]]
end
...
defp deps do
  [{:hedwig, github: "hedwig-im/hedwig"}]
end
...

Bot作成

以下のコマンドでインタラクティブBotの設定ができます。AdapterとBot名を決めるだけです。

$ mix hedwig.gen.robot

lib/hoge.ex 内でsupervision treeにbotを登録します。

  worker(Xxxx.Robot, [])

以上です。

Botの起動

mix run --no-halt で起動できます。今回のbot名は Neko にしてます。

❯ mix run --no-halt
Compiling 2 files (.ex)
Generated foobot app

Hedwig Console - press Ctrl+C to exit.

The console adapter is useful for quickly verifying how your
bot will respond based on the current installed responders

### ここからコンソール
shotat> Neko ping
Neko> shotat: pong
shotat> Neko help
Neko> Neko help - Displays all of the help commands that Neko knows about.
Neko help <query> - Displays all help commands that match <query>.
Neko: ping - Responds with 'pong'

Responderの登録

ここからが本番です。pingとhelpだけできても意味がないので、諸々追加していきましょう。

以下のようなファイルを <app>/lib/responders/hello.ex に作ります。

defmodule Foobot.Responders.Hello do # Foobotはアプリ名
  @moduledoc false
  use Hedwig.Responder

  @greet [
    "Hello",
    "ねむい",
    "にゃー"
  ]

  @usage """
  hello
  """
  hear ~r/hello/i, msg do
    send msg, random(@greet)
  end

  respond ~r/yo/i, msg do
    reply msg, random(@greet)
  end
end

hear, respondでワードを拾って、send, replyで返事をします。それぞれメンションの有無で2パターンあります。9割くらいHubotと同じですね。

randomも最初から使えます。

あとは作ったresponderをconfigファイルに登録します。

config :foobot, Foobot.Robot,
  adapter: Hedwig.Adapters.Console,
  name: "Neko",
  aka: "cat", <= alias
  responders: [
    {Hedwig.Responders.Help, []},
    {Hedwig.Responders.Ping, []},
    {Foobot.Responders.Hello, []} # <= new!
  ]
...

akaにbotの別名も登録できます。Neko a.k.a. cat。

もう一度動かす

こんな感じになります。

shotat> cat yo
Neko> shotat: にゃー
shotat> Hello
Neko> Hello

Slackで動かす場合

Adapterを設定すればいけます。deps

mix.exs に以下を設定

def application do
  [applications: [:hedwig_slack]]
end

def deps do
  [{:hedwig_slack, github: "hedwig-im/hedwig_slack"}]
end

github.com

設定例

config :foobot, Foobot.Robot,
  adapter: Hedwig.Adapters.Slack, # <= adapterはSlack
  name: "shotat-machine",
  aka: "<@UXXXXXXX>", # <= debug logからuser id確認
  token: "xxxxxxxxxxx", # <= tokenは適当に
  rooms: [],
  responders: [
    {Hedwig.Responders.Help, []},
    {Foobot.Responders.Hello, []},
    {Hedwig.Responders.Ping, []}
  ]

これでslackで動きました。

f:id:shotat_jp:20161016005002p:plain

注意点として、aka欄にuser idを設定しないとメンションに反応してくれません。 user idはdebugコンソールに出てくるので、それを設定しておきましょう(他に調べる方法もある気もしますが。。。)。

まとめ

Hubotに飽きたらHedwigに乗り換えましょう。楽しいElixir。

プログラミングElixir

プログラミングElixir

Concurrent Rubyで並行処理プログラミング

Rubyで並行処理を書きたかったのですが、自前でスレッドセーフなプログラムを書ける気がしないのでgemを探して来ました。

Concurrent Ruby

github.com

Be an 'unopinionated' toolbox that provides useful utilities without debating which is better or why

と書いてあるので、諸々の並行処理の実装があるみたいです。スレッドセーフらしいです。 READMEを読むとActorやらChannelやら書いてあります。

とりあえず今回は一番basicっぽいAsyncを使って並行でHTTPリクエストを投げる処理を実装してみます。

Async

Async: A mixin module that provides simple asynchronous behavior to a class. Loosely based on Erlang's gen_server.

らしいです。

とりあえずDocumentを読みつつコードを書いてみました。

1秒のレイテンシがあるmock serverに5回リクエストを投げる処理です。

通常の直列処理では5秒かかってる処理が、 Async使った並行処理では1秒で終わっています。

実装ポイント
  • Concurrent::Async をincludeする
  • initializerメソッドで super() をコールする
  • .async をメソッドチェインに挟んで非同期処理をdispatchする
  • .wait で処理の終了を待つ(ブロッキングなメソッド)
  • .value で結果を取り出す

まとめ

結構手軽に並行処理が書けて良さげです。

HTTPリクエストの並列化自体は Faraday + Typhoeus なんかでも簡単に書けるんですが、 Concurrent Rubyの方が汎用的ですね。

ActorとかChannelあたりはまだ触れてないので後日触ってみようと思います。

パーフェクトRuby (PERFECT SERIES 6)

パーフェクトRuby (PERFECT SERIES 6)

メタプログラミングRuby 第2版

メタプログラミングRuby 第2版

NeovimでGolangを書く環境を整える

Dockerの勉強を始めたのでGolangもついでにやります。

スターティングGo言語

スターティングGo言語

GoのInstall & 環境設定

$ brew install go

GOPATHを設定しないといけないらしいので、 使ってるshellの設定に以下を記述します。

$ export GOPATH=$HOME/go
$ export PATH=$PATH:$GOPATH/bin

開発に必須なバイナリを入れる

補完エンジンのgocodeとLinterのgolintを入れます。

$ go get -u github.com/nsf/gocode
$ go get -u github.com/golang/lint/golint

Neovimの設定

plugin managerはdein.vim

call dein#add('Shougo/deoplete.nvim')
call dein#add('zchee/deoplete-go')
call dein#add('benekastah/neomake')
call dein#add('fatih/vim-go')

だいたいこんな感じでぬるぬる開発できるようになります。

npm installで依存関係エラーが出る場合の対処

eslint周りで色々installしていたら依存関係でエラーを吐いてどうにもならなくなりました。

結論としては、npmのversionを最新化したら直りました。

npm installが失敗した時に試したい3つのコマンド - Qiita にもありましたが、以下のコマンドで最新化できます。大抵の場合これでいける気がします。

$ npm update -g npm

ついでですが、以下のようにversion指定してあげることもできます。

$ npm install -g npm@3.10.3

Node.js周りは、nvm, node, npmそれぞれにversionがあってややこしいですね。

パーフェクトJavaScript (PERFECT SERIES 4)

パーフェクトJavaScript (PERFECT SERIES 4)

Gemfileとpackage.jsonでみるバージョン指定の読み方

Semantic Versioning 2.0.0

セマンティック・バージョニング(SemVer)によると

バージョンナンバーは、メジャー.マイナー.パッチとし、バージョンを上げるには、

  • APIの変更に互換性のない場合はメジャーバージョンを、
  • 後方互換性があり機能性を追加した場合はマイナーバージョンを、
  • 後方互換性を伴うバグ修正をした場合はパッチバージョンを上げます。
  • プレリリースやビルドナンバーなどのラベルに関しては、メジャー.マイナー.パッチの形式を拡張する形で利用することができます。

SemVerはバージョニングのルールみたいなものですね。

Gemfile(Ruby)やpackage.json(Node.js)等で依存関係を記述する時、 チルダ(~)やキャレット(^)を使って指定するかと思いますが、自分の中での解釈が曖昧だったのでまとめます。

Gemfile

Rubyの場合は ~> の記法が使われます。

これはpessimistic operator(悲観的バージョン演算子)というそうです。

解釈は以下のようになります。

  • 指定バージョンの一番右の数字を取り除く
  • 次に、一番右の数字をインクリメントしたものを上限とする
# e.g.
gem 'hoge', '~> 5.1.1'
# 次と等価
gem 'hoge', '>= 5.1.1', '< 5.2.0'

参考: Ruby's Pessimistic Operator

package.json

package.jsonではチルダ(~)とキャレット(^)を使います。

参考: semver | npm Documentation

Tilde Ranges

Allows patch-level changes if a minor version is specified on the comparator. Allows minor-level changes if not.

とのことで

  • 基本的にはマイナーバージョンまで固定
  • マイナーバージョンが指定されてない場合はメジャーバージョン固定

です。

e.g.
~1.2.3 := >=1.2.3 <1.(2+1).0 := >=1.2.3 <1.3.0
~1.2 := >=1.2.0 <1.(2+1).0 := >=1.2.0 <1.3.0
~1 := >=1.0.0 <(1+1).0.0 := >=1.0.0 <2.0.0

Caret Ranges

Allows changes that do not modify the left-most non-zero digit in the [major, minor, patch] tuple. In other words, this allows patch and minor updates for versions 1.0.0 and above, patch updates for versions 0.X >=0.1.0, and no updates for versions 0.0.X.

  • 基本的にはメジャーバージョン固定(メジャーバージョンが0でない場合)
  • メジャーバージョンが0の場合マイナーバージョン固定
  • メジャー・パッチバージョンが0の場合はアップデートしない
^1.2.3 := >=1.2.3 <2.0.0
^0.2.3 := >=0.2.3 <0.3.0
^0.0.3 := >=0.0.3 <0.0.4

大抵の場合はメジャーバージョン固定のCaret(^)指定で問題ないかと思います。

プログラマのためのDocker教科書を読み始めた

Dockerを使いこなせないと会社で人権がないので プログラマのためのDocker教科書を読み始めました。

プログラマのための、というタイトルから、 "Dockerの使い方だけ紹介" みたいな雰囲気があるんですが、 想定以上に諸々しっかり書いてありました。

序盤にインフラ・仮想化・コンテナ技術の歴史と概要についてしっかり書かれていて、 非常に参考になりました。定期的に読み直したい内容です。

以下、適当にDocker周りの備忘録を適当にまとめていきます。

Docker の Install(Mac

Docker for Macがリリースされているのでこちらを使います。 (数ヶ月前にちょっと触った時はまだプライベートベータでした)

$ brew update && brew upgrade
$ brew cask install docker

あとは適当にDocker.appでsetupします。

いろいろ試す

docker pull

超軽量Linux Distributionの"Alpine Linux"を使います。

とりあえず docker search してみます。

$ docker search alpine
NAME                           DESCRIPTION                                     STARS     OFFICIAL   AUTOMATED
alpine                         A minimal Docker image based on Alpine Lin...   1475      [OK]
anapsix/alpine-java            Oracle Java 8 (and 7) with GLIBC 2.23 over...   151                  [OK]
...

docker pull でイメージを落とします

$ docker pull alpine
Using default tag: latest
latest: Pulling from library/alpine
c0cb142e4345: Pull complete
Digest: sha256:ca7b185775966003d38ccbd9bba822fb570766e4bb69292ac23490f36f8a742e
Status: Downloaded newer image for alpine:latest

Imageの確認

docker images で一覧確認します。

$ docker images
REPOSITORY            TAG                 IMAGE ID            CREATED             SIZE
rails                 latest              9df3ff98cd15        9 days ago          840.6 MB
nginx                 latest              ba6bed934df2        2 weeks ago         181.4 MB
alpine                latest              ee4603260daa        2 weeks ago         4.803 MB
msaraiva/elixir-dev   latest              6287638223f3        3 months ago        52.54 MB

いろいろ試しに入れてたので余計なものも出てきてますが、alpineのdocker imageだけ圧倒的に軽いですね。

個別に中身を見たい場合は inspect を使います

$ docker inspect alpine
...
     "Architecture": "amd64",
        "Os": "linux",
        "Size": 4802964,
        "VirtualSize": 4802964,
...

hello world

基本型は

docker run <dockerイメージ名> <コマンド>

です。

$ docker run alpine /bin/echo 'hello world'
hello world

dockerイメージがローカルに見つからなければ勝手にpullしてくれます。

tag

イメージにtagを付けて管理することができます。

$ docker tag alpine:latest shotat/alpine:foo
$ docker images
REPOSITORY            TAG                 IMAGE ID            CREATED             SIZE
alpine                latest              ee4603260daa        2 weeks ago         4.803 MB
shotat/alpine         foo                 ee4603260daa        2 weeks ago         4.803 MB

コンテナのshellを起動する

単体でshellを起動したい場合は以下のようなコマンドを使います。

$ docker run -it --rm alpine /bin/sh
オプションについて

-it --rm のオプションを見ていきます。

$ docker run --help | grep -E '(?:--tty|--interactive|--rm)'
  -i, --interactive                 Keep STDIN open even if not attached
      --rm                          Automatically remove the container when it exits
  -t, --tty                         Allocate a pseudo-TTY
  • -i : 標準入力を開く
  • -t : 疑似ttyを割り当てる
  • --rm : exit後にコンテナを破棄する

-it を付けないと対話shellをうまく扱えないので必ずつけましょう。 /bin/echo 'hoge' みたいなことしかしない場合は不要です。

マスコットキャラクターの名前

Dockerのクジラのキャラクターの本名は "Moby Dock" というそうです。

白鯨 - Moby-Dick; or The White Whale - を文字ってるみたいですね。( 白鯨 - Wikipedia

blog.docker.com

npm scriptsを並列実行する

npm-run-all を使う

github.com

使い方

npm i --save-dev npm-run-all でinstallします。 直列・並列実行コマンドの run-s, run-p が使えるようになります。

package.jsonのscripts部を以下のように設定してみます。

  "scripts": {
    "dev": "run-p stub watch",
    "stub": "stubcell",
    "build": "webpack",
    "watch": "run-p watch:*",
    "watch:js": "watch 'npm run build' ./src/scripts"
  },

この状態で npm run dev とすると、stubサーバとjavascriptのwatch & buildが同時に走るようになります。 楽ちんですね。

Node.jsのversionをプロジェクト毎に設定する

Rubyの場合は .ruby-version にバージョン指定すればプロジェクト毎にversion指定できるのですが、 Node.jsの場合どうすればいいのか分からなかったので備忘録。

大きく分けて2パターンありました。

avn を使う

.node-version でversion指定すればディレクトリ移動したときに自動でversionが切り替わるみたいです。 nodeのversion指定するために別コマンドいれるの微妙な気がしたので今回は使ってません。

github.com

nvm useを使う

.nvmrc にversionを記述して、nvm use を実行します。 rbenvみたいに自動で切り替わらないのかな?と思ったのですが、以下のissueを見るに切り替わらないっぽいです。

github.com

zshのhook使って自動的にnvm use実行する、みたいなのも見ました。個人的にはavn使うよりこっちの方がスマートな気がします。

現場で通用する力を身につける Node.jsの教科書

現場で通用する力を身につける Node.jsの教科書