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 のところ
ここまでの流れ
学生や新卒の人と話したりするときに、これまでエンジニア的にどうやって今の状態になったかみたいな話を何度もする機会があって、 その度に色々思い出しながら話してたんだけど、自分用に整理したかったのでついでにオンラインでアクセスできる場所に置くことにする。 なにか新たに思い出したり思い出補正が発覚したりしたら適宜修正していく。
小学生くらい (- 2002)
学校に自由に使える PC があって、卒業するくらいの時期には古いやつと新しいやつの2つがあって、新しいやつのほうに入っていたタイピングゲームで主に友人の K 君と遊んでいた記憶がある。 もう全然覚えてないけど、時期的には古いほうが Windows 95 で新しいほうが Windows 98 だろうか。 自宅にも PC があって、麻雀ソリティアで遊んでいた記憶がある。 PC に初めて触れたのはこのくらい。タイピングゲームに熱中したおかげで、タッチタイピングはこのへんで習得していた *1。
中学生くらい (2002 - 2005)
自宅にインターネット回線や Windows 98 のラップトップがきた。もちろんラップトップは家族共用。 このへんでインターネットにはまり始めて、ゲームの攻略サイトの掲示板とかによく書き込んでた。 あと K 君が自分でちょっとしたゲームを作っていて、僕も気になったので HSP で何か書いてみていた。 プログラミングというものを初めてやったのがこれで、なんとなく楽しんでいた気がする。 ただ、そんなに長続きはしなかったと思う。 HSP については今は文法すら覚えてない。
高校生くらい (2005 - 2008)
高校進学と同時に自分用の Windows XP のラップトップを買ってもらい、インターネットにがっつりはまった。 というかだいたい 2ch を見ていた。 2ch にいるような人だとプログラマの割合が高めで、そういう人たちと話しているうちに、HSP の経験や PC というのに興味を持ち始めていたこともあって、何か作りたいものがあったわけじゃないけど C 言語に入門した。 猫でもわかるC言語プログラミング のサイトやそこの本を主に読んでた。 最初は Borland C++ Compiler を使っていたけど、cmd.exe にも多少慣れてきてから cygwin をメインで使うようになって gcc でコンパイルしていた。 エディタも TeraPad とか Notepad++ をメインで使いつつも、cygwin の中で Vim を使い始めていた。 cygwin もそうだけど、Emacs や Vim を使ったほうがプロっぽいみたいな風潮があって (?)、最初は Emacs にチャレンジしたんだけど小指を痛めて挫折し、一方 Vim のほうはそういうことがなかったので当時は謎の操作に戸惑いながら無理して Vim を使っていた。
C/C++ の初心者本に載ってるようなことはなんとなく分かるようになって、その後 C に構文が似ているらしいという理由で Perl も勉強し始めた。 CGI とかも書いてみてはいたけど、LWP でインターネットから情報を簡単にとってこれることに感動して、今でいうとスクレイピングみたいなことをよくやっていた。 HTML を DOM としてパースするみたいな発想は全くなくて (というかそもそも HTML/XML というフォーマットや DOM というものについて知らなかったと思う)、ひたすら正規表現だけでがんばっていた。 おかげで正規表現は覚えた。
このへんでプログラムを書くのがすごく楽しくて好きになっていた。 リアルでプログラムを書く仲間みたいなのはいなかった (K 君は別の高校に進学して疎遠になっていた) けど、2ch のプログラム板とかはよく見ていて、大学生の宿題の丸投げスレの問題に解答したり、コードゴルフというものを知って POJ で勝手にコードゴルフにチャレンジしたりしていた。 この頃 Java も少し書いてみたりしていたけど、あまり興味を持てず勉強はしていなかった *2。 Win32 API とか Swing とかで GUI を書いてみようとしていた時期もあったけど、めんどくさすぎるわりに大したものができなくて、ターミナルで満足していた。
大学生くらい (2008 - 2014)
進学と同時に MacBook を買って、cygwin から開放されて普通のターミナルの使いやすさに驚いた。cygwin ではビルドできなかったり動かなかったりした CLI ツールも動いてすごく便利に感じた。 その結果、色々なプログラミング言語を試せるようになって、Ruby とか Python とか Haskell とか Lisp とか色々入門していた。 その中でも Ruby と Haskell は手になじんで、その後もしばらく使い続けて、Haskell は最近はもう全然書く機会がなくなってしまったけど Ruby は仕事で毎日使うようになった。 あと大学の図書館で エキスパートCプログラミング という本に出会って、これが本当に面白くて、C を理解する上で大いに参考になっただけではなく、プログラミング言語そのものやランタイムやツールチェインに興味を持つきっかけの一つになったと思う。 そんな流れでもっとマイナーな言語を使ってみたりしつつ、プログラミング言語への興味が高まってそれ系の研究室に所属することになったりした。
この頃からインターネットの中心 (?) が 2ch から Twitter にシフトしていきつつ、はてなダイアリーに何か書いたり、GitHub に dotfiles や雑コードを上げるようになったり、勉強会的なものに少し参加してみたりするようになった。 リアルのほうでも情報系の学科にいったので、プログラムの話をできる友人ができた。 Twitter がきっかけで B2 の後半くらいから学内の ICPC の練習会にも参加するようにもなった。 技術的な成長は B1 から B3 あたりの時期が際立っていたと思う。 色々な言語の仕様を読んだり処理系の一部を読んでみたり、ちょっと背伸びして公開されている論文を読んでみたり、なにかコードを書くアイデアが出たらあえてマイナーな言語で書いてみたり *3。 まぁ競技プログラミングに関しては全然強くはなれなかったけど……
B3 の頃からデスクトップ環境を Linux にしたり、Linux サーバで PT2 でアニメ録画をし始めたりして、Linux とかサーバ管理について少しずつ学んでいった。 Web アプリケーションに興味を持ち始めたのはたぶん B4 の後半か M1 の前半くらいで、sinatra や Rails に入門しつつ VPS を借りて nginx とか使い始めた。 GUI アプリケーションを書くより HTML を書くほうが圧倒的に楽だと感じて、ここから徐々に sinatra や Rails を使うようになっていた。 といっても不特定多数の人が使う (使える) ようなものではなく、ただ自分だけが使うツールの GUI として使っていた。 研究室に所属してからは同じ研究室の同期を始めとして同じフロアの人たちに恵まれて、似たような興味分野の人同士で色々話したり聞いたりできて本当に楽しかった。
現在
社会人になってからだいたい何してたかは書き残しているのでそっちに http://eagletmt.hateblo.jp/entry/2015/12/31/212651 。 Rails とかインフラにちょっと詳しくなったりした。
プログラミングは手段でしかなくてゲームを作りたいとかサービスを作りたいとかそういう目的があってプログラミングを学んだり仕事にしたりする人もいると思うんだけど、僕は全くそんなことはなくてただ単純にコードを書くこと自体が面白くて楽しくて書き続けてきた。 なので宿題丸投げスレや競技プログラミング、そして仕事みたいに課題を与えてくれるものがあると助かっていた。 面接とかで今まで何作りましたかとか今後何作りたいですがみたいなことを聞かれても困ることが多かった。まぁこれは今もわりとそうな気がする。 コードを書くのは今では仕事になっているけど趣味でもあり続けている。
RTX 810 と Openswan で site-to-site VPN
自宅から 10.0.0.3 とかで VPS にあるサーバにアクセスしたいし、逆に VPS から 192.168.10.8 とかで自宅のサーバにアクセスしたい。
環境
- 自宅 (RTX 810)
- グローバルIP 1.1.1.1
- プライベートIP 192.168.10.1
- サブネット 192.168.10.0/24
- VPS (Openswan)
- グローバルIP 2.2.2.2
- プライベートIP 10.0.0.2
- サブネット 10.0.0.0/16
- pre-shared-key HOGEFUGA
自宅側の設定
Web UI から VPN 接続の設定→IPsecを使用したネットワーク型 LAN間接続VPN からだいたい設定できる。 最終的に以下のような設定になった。
... ip route 10.0.0.0/16 gateway tunnel 1 ... tunnel select 1 tunnel name vps ipsec tunnel 1 ipsec sa policy 1 1 esp aes-cbc sha-hmac ipsec ike group 1 modp1024 ipsec ike hash 1 sha ipsec ike keepalive use 1 off ipsec ike local address 1 192.168.10.1 ipsec ike pre-shared-key 1 * ipsec ike remote address 1 1.1.1.1 ipsec auto refresh 1 off ip tunnel tcp mss limit auto tunnel enable 1 ... ip filter 200085 pass * 192.168.10.1 udp * 500 ip filter 200086 pass * 192.168.10.1 esp * * ...
VPS 側の設定
Openswan
/etc/ipsec.conf
version 2.0 config setup dumpdir=/var/run/pluto/ virtual_private=%v4:10.0.0.0/8,%v4:192.168.0.0/16,%v4:172.16.0.0/12,%v4:25.0.0.0/8,%v6:fd00::/8,%v6:fe80::/10 oe=off protostack=auto include /etc/ipsec.d/*.conf
/etc/ipsec.secrets
include /etc/ipsec.d/*.secrets
/etc/ipsec.d/rtx810.conf
conn rtx810 auto=start type=tunnel authby=secret keyexchange=ike ike=aes128-sha1;modp1024 phase2=esp phase2alg=aes128-sha1;modp1024 pfs=no left=1.1.1.1 leftid=1.1.1.1 leftsourceip=10.0.0.200 leftsubnet=10.0.0.2/16 right=2.2.2.2 rightid=192.168.10.1 rightsubnet=192.168.10.1/24
/etc/ipsec.d/rtx810.secrets
1.1.1.1 192.168.10.1: PSK "HOGEFUGA"
/etc/sysctl.d/50-openswan.conf
net.ipv4.conf.default.send_redirects = 0 net.ipv4.conf.default.accept_redirects = 0 net.ipv4.ip_forward = 1
これで ipsec verify
に FAIL がなくなって起動するはず。
iptables
esp と udp 500 を開けておく。 あとは他のサーバから Openswan がいるサーバを通って通信させるために forward も許可しておく。
iptables -A INPUT -p udp -m multiport --dports 500 -j ACCEPT iptables -A INPUT -p esp -j ACCEPT iptables -A FORWARD -s 10.0.0.0/16 -d 192.168.10.0/24 -j ACCEPT iptables -A FORWARD -s 192.168.10.0/24 -d 10.0.0.0/16 -j ACCEPT
これで VPN 接続を張れるはず。
route
Openswan を立てているサーバ以外から 192.168.10.0/24 にアクセスするときは 10.0.0.2 を通るようにする。
ip route add 192.168.10.0/24 via 10.0.0.2 dev ens4
とすればいいんだけど、この設定を永続化するために systemd-networkd では network ファイルに書いておく。
/etc/systemd/network/ens4.network
... [Route] Gateway=10.0.0.2 Destination=192.168.10.0/24
Docker コンテナ内の net.core.somaxconn を変える
--net=host
でコンテナを起動すれば、network namespace が分離されないので net.core.somaxconn の値はホスト側と一致する。
% cat /proc/sys/net/core/somaxconn 1024 % docker run --net=host ubuntu:16.04 cat /proc/sys/net/core/somaxconn 1024
けど普通に docker run すると、ホスト側の値にかかわらず、コンテナ内ではデフォルトの128になる。
% cat /proc/sys/net/core/somaxconn 1024 % docker run ubuntu:16.04 cat /proc/sys/net/core/somaxconn 128
で、コンテナ内でこの値を変えようとしても、デフォルトでは許可されていない。
% docker run -it ubuntu:16.04 bash root@1704e07731c0:/# cat /proc/sys/net/core/somaxconn 128 root@1704e07731c0:/# echo 512 > /proc/sys/net/core/somaxconn bash: /proc/sys/net/core/somaxconn: Read-only file system
これを回避する方法はいくつかあって、1つは --privileged
で実行する方法。
% docker run --privileged -it ubuntu:16.04 bash root@41d5397d065b:/# echo 512 > /proc/sys/net/core/somaxconn root@41d5397d065b:/# cat /proc/sys/net/core/somaxconn 512 root@41d5397d065b:/# exit % cat /proc/sys/net/core/somaxconn 1024
ホスト側とは別の network namespace で値を変えているだけなので、ホスト側の値には影響は無い。 ただ、これだと不要な権限も色々と渡ってしまうのでできれば避けたい。
そこで、必要なものだけ rw で bind mount して、そこに書き込むという方法がある。
% docker run --volume /proc/sys/net/core/somaxconn:/somaxconn -it ubuntu:16.04 bash root@9bc22f86b0f3:/# cat /somaxconn 128 root@9bc22f86b0f3:/# cat /proc/sys/net/core/somaxconn 128 root@9bc22f86b0f3:/# echo 512 > /somaxconn root@9bc22f86b0f3:/# cat /somaxconn 512 root@9bc22f86b0f3:/# cat /proc/sys/net/core/somaxconn 512 root@9bc22f86b0f3:/# exit
なお Docker 1.12.0 からは --sysctl
というオプションがつくようなので、試してないけど 1.12.0 以降は docker run --sysctl net.core.somaxconn=512
でよさそう。
https://github.com/docker/docker/pull/19265