Memento memo.

Today I Learned.

Docker と node_modules と Volume Trick

Rails & Node.jsの構成で Docker環境を作ろうとしたらハマったのでメモ

現象

npm installを実行する Dockerfileを記述

...
RUN mkdir /app
WORKDIR /app
# npm install
ADD package.json /app/package.json
RUN npm install
...

カレントディレクトリを /app にマウントするdocker-compose.ymlを記述

version: "2"

# これは失敗
services:
  web:
    build: .
    command: bundle exec rails server -p 3000 -b '0.0.0.0'
    ports:
      - "3000:3000"
    working_dir: /app
    volumes:
      - .:/app

するとnode_modulesが消える

原因

考えてみれば当たり前なんですが、
「node_modulesが出力されたコンテナ内のワーキングディレクトリに対して、ローカルのカレントディレクトリをマウントしてしまっている 」ため、コンテナ内のnode_modulesが隠れてしまいます。

解決策

こちらに書いてありました。 volume trick と紹介されています。

jdlm.info

version: "2"

services:
  web:
    build: .
    command: bundle exec rails server -p 3000 -b '0.0.0.0'
    ports:
      - "3000:3000"
    working_dir: /app
    volumes:
      - .:/app
      - /app/node_modules # コンテナ内のnode_moduleをvolumesに登録

複数マウントの詳細な挙動は謎なんですが、マウントで隠されたくないものをvolumesに登録しておくとコンテナ内のものが読まれるようになるそうです。

ちなみにbundle installなどでも同様の問題が起き得ますが、bundlerの場合はinstall先を環境変数でワーキングディレクトリ以外のパスに逃がせるのでそっちの方が楽です。

Redisの有効期限系コマンド

redis-cliから叩いた場合のコマンドです

EXPIRE(key, seconds)

有効期限の設定(相対時間)

# key-valueを適当に設定
> SET ice "I'm melting..."
OK
# 有効期限を10秒にする
> EXPIRE ice 10
(Integer) 1
> EXISTS ice
(Integer) 1 # 10秒以内
> EXISTS ice
(Integer) 0 # 10秒以上経過

上記例はMULTIブロックを使わないとatomic性が保証されないので注意。

EXPIREAT(key, timestamp)

有効期限の設定(絶対時間)

timestampにはUNIX時間を指定します。redis-cli上ではTIMEコマンドで現在のUNIX時間が取得できます。

SETEX(key, seconds, value)

SET(key, value) + EXPIRE(key, seconds)のショートカット。

公式ドキュメント(SETEX – Redis)を見る限り、atomic性が保証されるので、 SET + EXPIREを使うよりこっちを使った方が良いみたいです。

> SETEX ice 10 "i'm melting..."
OK

TTL(key)

有効期限の残り秒数を表示

PERSIST(key)

タイムアウトを無効化

参考

7つのデータベース 7つの世界

7つのデータベース 7つの世界

VimConf 2016 に参加してきました

vimconf.vim-jp.org

VimConf 2016 に参加してきました。会場はmixiさんで、コーヒーが無限に飲めて最高でした。

vim-jpの中の人や普段お世話になっているプラグインの開発者の方と話せて楽しかったです。

印象に残った発表と感想書きます。全部は書けないので3つほど紹介します。ちなみに全部面白かったです。

Introduction to Vim 8.0

www.slideshare.net

日本一のVimコミッターのK.Takata さんの、Vim8.0についての発表でした。

Vimの歴史的な話や、Vimのpatchを書いてる開発者の多くが日本人、という話が印象的でした。

Vim scriptもいつの間にかLambdaやClosure等のモダンな機能が使えるようになっているそうです。Vim script書きましょう。 あと表記ゆれに注意しましょう。

発表の中で紹介されていた breakindent 機能は知らなかったのですが、便利そうなので今後使って行きたいと思いました。

(参考: Vim 8.0 で追加された機能 'breakindent' - Secret Garden(Instrumental)

Denite.nvim ~The next generation of unite~

暗黒美夢王こと、Shougoさんの Denite.nvim についての発表でした。

発表資料がVimのただのmarkdownだったのが前衛的でした。

とりあえずUniteは開発がつらいのでDenite使いましょう、ってことでした。

実際Deniteは超速くて感動するので使っていない方は導入しましょう。

Dark powered系プラグインは基本的にNeovimで前提ですが、Vim8もサポートするそうです。

今後のDark powered系のプラグインの構想?みたいなお話も聞けました。ちなみにNeovimもDark powered系プラグインもユーザは海外の開発者が多いそうです。

ShougoさんもzcheeさんもPython3書いてプラグインやdeopleteソース書いてるそうなので、VimmerはPython3書きましょう。

ちなみに他の発表ではGolang書きましょう、という話が多かったのでGolangも書きましょう。

vim-mode-plus for Atom editor

t9mdさんの、Atomプラグインvim-mode-plusの発表でした。

Atom上での単なるVimのエミュレートというわけではなく、 Atom上のvim-mode-plusならではの機能を実装しているそうです。

vim-mode-plusのdemoでVim Golf的なことをやっていたのですが、鮮やかなtext-editingでした。使いこなせればまさに思考のスピードで編集できそうです。

VimAtomの双方の特性を深く理解した上で、便利な拡張機能を実装しているようで、t9mdさんのこだわりを感じました。

詳細は上記のリンクの資料とチュートリアルをこなすのが良いと思います。自分も試してみようと思います。

その他

Vim8 vs Neovim だと圧倒的にVim8の方が多いみたいです。ちなみに私はNeovim派です。

gemを作ってrubygems.orgでリリースする

作ったもの

github.com

slash-force というかっこいい名前のgemを書きました。

Railsで特定のURLへとアクセスしたときに末尾にスラッシュを付けて強制的にリダイレクトさせるだけのプラグインです。

こんな感じでgemをリリースできました。

slash_force | RubyGems.org | your community gem host

gemの作り方

Gemの作り方まとめ 普通のgem編 - masarakki's blog を参考に。

  • bundle gem xxxxx -t でプロジェクトの雛形を作成
  • 適当に実装
  • gemspecのTODO部分を埋める

くらいです。

gemのリリース

curl -u shotat https://rubygems.org/api/v1/api_key.yaml > ~/.gem/credentials; chmod 0600 ~/.gem/credentials
  • bundle exec rake release を実行。

以上の手順でリリースが完了します。当初の想定の2兆倍くらい簡単なプロセスでした。

gem化のメリデメ

汎用的な機能のgem化のメリデメについて(私見)

メリット

  • プロダクトコード内の libconcern の肥大化を防げる
  • 車輪の再発明を防げる(gem自体が再発明でない場合に限る)
  • UTをプロダクトコードと完全に分離できる
  • 見知らぬ強い人がenhance, bug fixしてくれる可能性がある
  • 楽しい

デメリット

  • インタフェースの変更に大いなる責任が伴う
  • やりすぎるとプロダクトコード側が苦しくなる(謎gemに大量に依存する状況になるとつらい)

Eslintでunderscoreから始まる変数をチェックさせない設定にする

defined but never used って怒られるやつです。

error  '_jqXHR' is defined but never used   no-unused-vars

_xxx みたいにアンダースコアから始まる変数は未使用でもokな設定にしたい、と思い調べました。

以下のリンクが該当箇所でした。

eslint.org

.eslintrc.yml の設定に以下を追記すればokです。

rules:
  no-unused-vars:
    - 2
    - argsIgnorePattern: '^_'

Rubyのobject_idからobjectを取得する

備忘録です

[1] pry(main)> hoge = "foo"
=> "foo"
[2] pry(main)> hoge.object_id
=> 70163083352460
[3] pry(main)> ObjectSpace._id2ref(70163083352460)
=> "foo"

yeah!

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')

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