2016年の思い出
しゃかいじんさんねんめ
去年は http://eagletmt.hateblo.jp/entry/2015/12/31/212651
仕事
hako の開発と環境作りがメインだった。 https://github.com/eagletmt/hako
Docker を使ったアプリケーションの動作環境作りは 去年もやってた んだけど、Amazon ECS が出てきたのでそれを利用した環境とそのためのデプロイツールの開発をしていた。 開発自体は去年の9月頃から始めていたんだけど、ツール面でもインフラ面でもちゃんと使える状態にして、実際に使われ始めたのが今年だった。 社内で新規に Web アプリケーションを作ったりバッチジョブを作ったりする上で標準的な選択肢になってきている。 http://techlife.cookpad.com/entry/2016/03/16/100043
発表
Rails Upgrade Casual Talks に誘われて発表してきた。発表は今年だけど内容的には 2年前のもの なのであんまり今年感は無い。 https://speakerdeck.com/eagletmt/activerecord-3-dot-2-4-dot-1
あとは hako の話を JAWS-UG おコンテナ支部 #5 でした。Amazon ECS を使ってこういう環境を用意して、そのために hako というツールを作っているという話。 https://speakerdeck.com/eagletmt/ecs-woli-yong-sitadepuroihuan-jing
趣味開発
envchain の Linux 対応 をしたのが自分にとって地味に一番インパクトあった気がする。個人でも仕事でも利用している。
あとは 録画スケジューラで試しに gRPC 使ってみたり、mitamae を高速化してみたり かな…… こう書いてみるとあんまコード書いてない気はする。
VPS と自宅の間に Site-To-Site VPN はったり IPv6 対応したりして、ちょっとだけネットワークまわりの経験値をためた。 Docker のネットワークまわりの理解の助けにもなった気がする。iptables に対する苦手意識が減った。
アニメ
アイカツ終わってしまいましたね…… アイカツスターズ、最初はちょっとうーんと思っていたけど、劇場版がすばらしくてそこからテレビシリーズのほうの印象も一気によくなった。毎週楽しみです。
ViVid Strike! が魔法少女感はないもののやっぱりリリカルなのはだこれってかんじの話でとてもよかった。これとあわせてついに Reflection の上映日も発表されて本当に楽しみですね。
BD 買ったのはアイカツ、アイカツスターズ、プリズマイリヤドライ、ViVid Strike!、(まだきてないけど) ブレイブウィッチーズ、と結局代わり映えしないかんじになってた。
ゲーム
星のカービィロボボプラネット、スターフォックスゼロ、世界樹の迷宮5、そしてポケモンサン・ムーンが買って面白かった。あと地味にピクロスにはまってピクロスeのシリーズを買ったりしていた。
ペルソナ5とうたわれるもののために PS4 を買った。PlayStation 系のハードを買ったのはこれが初めて。友人にやらせてもらったり、友人から PSP を借りてなのはのゲームをやったことはあったけど。 どちらのゲームもたいへんよかった。
アイカツスターズの筐体デビューしてゲーセンに行く習慣ができた。 その結果、最近スクフェス AC をやって楽しんでる。EXTREME まではついていけるけど CHALLENGE はがんばりが必要そう。 音ゲー勢からはチュウニズムをすすめられているので来年は。
mitamae の高速化
itamae と比較して mitamae のメリットをシングルバイナリ以外にも作りたいなと思って mitamae の高速化を進めている。
高速化の方針は「できるだけ Ruby (mruby) のプロセス内で実行する」というもの。 itamae と mitamae の差の一つに SSH サポートや Docker サポートがあって、itamae は SSH 越しに実行したり Docker コンテナに対して実行したりできるが、mitamae はローカルのみをサポートしている。 mitamae では未実装なだけかと思ったが、既に一部の実装がローカル実行前提のようだったので、だったらその前提で外部コマンドを使わずに Ruby でファイルの存在などをチェックすることで高速化できるだろうと考えた。 実は itamae のときにも同じことをやろうとしていて実際に ちょっとだけ作ってみていた んだけど、うまく itamae 本体に組込む方法が思いつかずにそのまま放置していた。 このアイデアを mitamae に実際に実装して、1.2.x 系でいくつか取り込んでもらって高速化されている。
mitamae に入れた高速化は MItamae::InlineBackends という定数の下にクラスを作ると起動時に読み込まれてそれを使って specinfra のコマンドを一部乗っ取れるようにするという形で実現した。
mitamae 本体だけを考えるとちょっとまわりくどい方法だけど、こうすることによって MItamae::InlineBackends::NantokaBackend というクラスを提供する mrbgem を mitamae と一緒にビルドすることで、mrbgem を使って特定の環境に特化した高速化を入れることができる。
これを実際にやっているのが mitamae-pacman で、Arch Linux でこれと一緒に mitamae をビルドして使うと pacman -Q
のかわりに libalpm*1 の関数を使って高速にインストール済みかどうかの判定ができるようになる。
https://github.com/eagletmt/mitamae-pacman
結果としては、個人で mitamae で管理しているとあるサーバにおいて mitamae v1.1.2 だと dry-run に7.8秒くらいかかっていたものが mitamae v1.2.4 + mitamae-pacman v0.2.0 だと1.2秒くらいになった。 これだけ差があると体感でログが流れる速度が変わっているのがわかる。
mitamae 向けの itamae-secrets を書いた
構成管理ツールを使う上で悩ましい問題として秘匿値の管理があり、それに対する解決策の一つとして itamae 向けには itamae-secrets というものがあった。 自分も秘匿値の管理にこの itamae-secrets を使っていたが、この度 mitamae に移行してみようと思い、mitamae で使える itamae-secrets として mitamae-secrets というものを書いた。
https://github.com/eagletmt/mitamae-secrets
mitamae-secrets の暗号化・復号を実装するためには OpenSSL が必要だが、mruby 向けの OpenSSL バインディングはまだ存在していないようだったので、必要な部分だけ OpenSSL の関数を使いながら C で実装する形にした。 EVP という高レベルの API をそのまま使えたので C 実装部分はわりとすっきり書けた。
mitamae で mitamae-secrets を使うには、まず mitamae を mitamae-secrets と一緒にビルドする必要がある。ここがちょっとめんどくさい点ではある。 自分の場合はこういう build_config.rb を用意してビルドした mitamae バイナリを使っている。 https://aur.archlinux.org/cgit/aur.git/tree/?h=mitamae
node[:secrets] = Itamae::Secrets::Store.new(path)
と書いていたものを node[:secrets] = MitamaeSecrets::Store.new(path)
に変えればだいたいそのまま動くと思う。
少なくとも自分の使い方ではそのまま動いている。
itamae-secrets set
とか itamae-secrets newkey
に使う itamae-secrets コマンドの代わりとなる mitamae-secrets コマンドも用意している。
ただし .itamae-secrets.yml を読む機能は現時点ではまだ実装してないので、とりあえず毎回 --base
を明示するかんじで……
mitamae 移行の感想
既に itamae を使っているところから mitamae への移行を実際にやってみた感想としては、itamae-secrets 以外の点では Ruby と mruby の標準添付ライブラリの差が地味につらかった。 普通に Ruby を使っている itamae では IPAddr とか Digest とかが使えたのに、mitamae では使えない *1 ので、そこを書き直す必要があった。 個人の環境なのでそこまで大変ではなかったとはいえ無視できないコストはかかっていて、mitamae に移行するメリットが本当にあるのか今もちょっと疑問だけど、今後メリットを自分でも作っていきたい気持ちでしばらくは mitamae を使っていこうと思っている。 とりあえず、外部コマンドを実行しすぎで遅い問題を mitamae で解決していきたい……
*1:それぞれ mrbgem としては mruby-ipaddr や mruby-digest はあります
IPv6 と Docker と NAT
IPv6 のアドレスが1つしかない状況で、ネットワークが分離されたコンテナから IPv6 で通信しようとすると IPv6 だろうと NAT が必要になる。 このへん Docker がどう扱うのかよくわかってなくて、結局 Docker の外側で docker0 とか ip6tables を管理することで動いた……
まず docker0 に使うレンジを決める。ここでは fdbb:3f26:ceda::/48
と 10.11.0.0/16
とする。
docker0 を systemd-networkd で作る。
% cat /etc/systemd/network/docker0.netdev [NetDev] Name=docker0 Kind=bridge % cat /etc/systemd/network/docker0.network [Match] Name=docker0 [Network] Address=10.11.0.1/16 Address=fdbb:3f26:ceda::1/48
dockerd を起動する。このときネットワーク系のオプションを切っておく。
% cat /etc/systemd/system/docker.service.d/override.conf [Service] ExecStart= ExecStart=/usr/bin/dockerd --ipv6 --fixed-cidr-v6=fdbb:3f26:ceda::/48 --bridge=docker0 --ip-forward=false --ip-masq=false --iptables=false -H fd:// # systemctl start docker.service
デフォルトだと dockerd が IPv4 でやっていたことを、IPv4 と IPv6 の両方について自分でやる。
% cat /etc/sysctl.d/30-ip-forward.conf net.ipv4.conf.all.forwarding = 1 net.ipv4.conf.default.forwarding = 1 net.ipv6.conf.all.forwarding = 1 net.ipv6.conf.default.forwarding = 1
# iptables -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT # iptables -A FORWARD -s 10.11.0.0/16 -i docker0 ! -o docker0 -j ACCEPT # iptables -A POSTROUTING -s 10.11.0.0/16 ! -o docker0 -j MASQUERADE # ip6tables -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT # ip6tables -A FORWARD -s fdbb:3f26:ceda::/48 -i docker0 ! -o docker0 -j ACCEPT # ip6tables -A POSTROUTING -s fdbb:3f26:ceda::/48 ! -o docker0 -j MASQUERADE
これで Docker コンテナ内から IPv6 で通信できるようになる。
% cat Dockerfile FROM ubuntu:16.04 RUN apt update && apt install -y curl % docker build -t ubuntu-curl . % docker run --rm ubuntu-curl curl -sv -o /dev/null https://ipv6.google.com/ * Trying 2404:6800:4004:816::200e... * Connected to ipv6.google.com (2404:6800:4004:816::200e) port 443 (#0) (snip)
ただ、ULA なアドレスを使っているせいで A も AAAA も持っているようなドメインの場合、デフォルトだと IPv4 が優先されてしまう。
% docker run --rm ubuntu-curl curl -sv -o /dev/null https://google.com/ * Trying 172.217.25.78... * Connected to google.com (172.217.25.78) port 443 (#0) (snip)
この優先度は /etc/gai.conf で決められているので、とりあえずそこを変えれば IPv6 が優先されるようになる *1。
% cat gai.conf label ::1/128 0 label ::/0 1 label 2002::/16 2 label ::/96 3 label ::ffff:0:0/96 4 % docker run -v $PWD/gai.conf:/etc/gai.conf:ro --rm ubuntu-curl curl -sv -o /dev/null https://google.com/ * Trying 2404:6800:4004:819::200e... * Connected to google.com (2404:6800:4004:819::200e) port 443 (#0) (snip)
今回は Docker でやったけど、systemd-nspawn (machinectl) を使うときもやることは同じ。
docker0 と同じ要領で br0 を作って systemd-nspawn --network-bridge=br0
で起動する。
systemd-nspawn の場合は勝手にアドレスを割り当てたりしてくれないので、それはコンテナ内の systemd-netword でやるようにする。
このときデフォルトだと host0 に対して余計な設定があるので、コンテナ内で ln -s /dev/null /etc/systemd/network/80-container-host0.network
で切っておく。
Ruby で実行時に引数のクラスを推測するやつ
https://gist.github.com/eagletmt/3e064fcbe2935a8356bc8658c8e472c1
require_relative '../infer_type' RSpec.configure do |config| config.before(:suite) { @infer_type = InferType.new; @infer_type.start } config.after(:suite) { @infer_type.finish } end
たとえば上記のように RSpec のフックに仕掛けて INFER_TYPE_TARGET=YourAwesomeApp:: bundle exec rspec
とかやるとテストから適当に推測されたクラスが表示される。
とりあえず nullable かどうかと、TrueClass と FalseClass を Boolean と解釈するように調整を入れていて、このへんをがんばるともう少しいいかんじのクラスを表示できそう。
これと同じようなかんじで、YARD で書かれた型と一致するかどうかチェックできると便利そう。
tmux 2.2 以降で East Asian Ambiguous Width Character を正しく表示させる方法
これまで tmux は文字幅を得るために独自のテーブルを持っていて、その独自テーブルでは East Asian Ambiguous Width というものを一切考慮していないので、CJK な環境ではパッチをあてて使うことがよく行われていた (tmux cjk patch とかでググるといろいろ出てくると思う)。
tmux 2.2 からは wcwidth(1) を使うようになり、独自テーブルをやめてロケールの情報から文字幅を得るようになった https://github.com/tmux/tmux/commit/26945d7956bf1f160fba72677082e1a9c6968e0c 。
が、このコミットをよく見ると setlocale(LC_CTYPE, "en_US.UTF-8")
で固定されており、LC_ALL や LC_CTYPE に関係なく en_US.UTF-8 が使われる。
tmux は UTF-8 を前提としており、そこを固定したい気持ちは分からなくもないが……
なので対応としては、en_US.UTF-8 ロケールで文字幅を変更してやれば、パッチなしで East Asian Ambiguous Width の問題を回避できる。
http://eagletmt.hateblo.jp/entry/2016/03/23/020117 に書いたような方法で /usr/share/i18n/charmaps/UTF-8-CJK.gz を生成し、/etc/locale.gen で en_US.UTF-8 UTF-8-CJK
にして locale-gen
したところうまくいった。
普段 en_US.UTF-8 と ja_JP.UTF-8 を使い分けているような人はこの方法だと厳しいけど、基本 ja_JP.UTF-8 しか使ってない人は en_US.UTF-8 で文字幅を変えても悪影響は無いと思う。
fluentd のバッファファイルを直接加工する
たとえば変なレコードが混じってしまったせいで何度リトライしてもバッファのフラッシュに失敗するようなときに、バッファファイル (buffer_type file
で作られるやつ) を使っていれば、そのファイルをいじることで応急処置ができる。
バッファファイルは [tag, time, record]
という三つ組の列を msgpack でシリアライズした形式になっていて、v0.12.x と v0.14.x で time の型が違う *1 けど、どちらのバージョンでも以下のようなコードでバッファファイルを読み書きできそう。
なお v0.12.x において Fluent::Engine.msgpack_factory
が追加されたのは v0.12.17 から なので注意。
require 'fluent/engine' in_path = '/path/to/some-buffer.q0123456789abcdef.log' out_path = '/path/to/modified-buffer.log' def modify(tag, time, record) # do something [tag, time, log] end File.open(in_path) do |fin| unpacker = Fluent::Engine.msgpack_factory.unpacker(fin) File.open(out_path, 'w') do |fout| packer = Fluent::Engine.msgpack_factory.packer(fout) unpacker.each do |triplet| packer.write(modify(*triplet)) end end end
一旦 fluentd を停止してバッファファイルが触られないようにしてから、上のようなスクリプトで変更を加えたバッファファイルを作成し、 mv /path/to/modified-buffer.log /path/to/some-buffer.q0123456789abcdef.log
で上書きしてから fluentd を起動するとよさそう。
*1:http://www.fluentd.org/blog/fluentd-v0.14.0-has-been-released の Sub-second time のところ