新世代 grep の使い方 ripgrep(rg) と silver-searcher(ag)

はやいは正義

ripgrep が現在進行形で大正義らしい。名前からなんとなく「闇を切り裂く grep」みたいなイメージが浮かぶ。こう、アスファルトタイヤを切り裂きながら暗闇走り抜ける素敵なサムシング、を感じる... のはおぢさんだけ、か。 github.com

なぜ、rip という言葉が頭に浮かんだのかは覚えていない。何か "速い" とか "テキストを切り裂く" みたいなイメージが浮かんだんだ。rip が "Rest in Peace"、(魂よ)安らかに眠れ (つまり ripgrep によって grep の役目を終わらせるってこと) の略語たりうるってことに、その時はまだ気づいていなかった。

I don't remember how it popped into my head, but "rip" came up as something that meant "fast," as in, "to rip through your text." The fact that RIP is also an initialism for "Rest in Peace" (as in, "ripgrep kills grep") never really dawned on me. (FAQ.md)

インストールもはやくするためにワンライナを書いておく。バージョン 0.8.0 で鋭意開発中なので今年中にあと何回か叩くことになる気がする。

$ curl -sfSL https://github.com/BurntSushi/ripgrep/releases/download/0.8.0/ripgrep-0.8.0-x86_64-unknown-linux-musl.tar.gz | sudo tar xzf - -C /usr/local/bin/ --wildcards */rg --strip=1

所感

ag を踏襲しつつ速度をさらにはやめた感じ。新世代 grep 共通で ag も rg も結果表示が以下の形式になるのは同じ。ただこれはターミナルの表示上だけで行指向な本質はそのまま持っている。パイプで繋げるとマッチ行だけになる。デフォルトだとドット(.) ではじまるファイルは検索対象外となるが --hidden を使えば含めてくれる。

ファイル名
マッチ行

ag/rg 使い方逆引き一覧

やりたいこと ag (v2.1.0) rg (v0.8.0)
インストール(mac) $ brew install the_silver_searcher $ brew install ripgrep
インストール(ubuntu) $ sudo apt-get install silversearcher-ag 上記参照
サポートしているファイルタイプ一覧を出す $ ag --list-file-types $ rg --type-list
yml のファイルのリストを出す $ ag --yaml -l . $ rg -t yaml --files
yml のファイルで Deployment が含まれるファイルのリストを出す $ ag --yaml Deployment -l $ rg -t yaml Deployment -l
scala のファイルで case class を検索する $ ag --scala '^case class' $ rg -t scala '^case class'
scala のファイルで getOrElse を使っている(良い)箇所を検索する $ ag --scala -w getOrElse もしくは $ ag --scala '\bgetOrElse\b' $ rg -t scala -w getOrElse もしくは $ rg -t scala '\bgetOrElse\b'
scala のファイルで get を使っている(悪い)箇所を検索する $ ag --scala -w get $ rg -t scala -w get
scala のファイルで get を使っている箇所が全ファイル中どれくらいなのかの統計情報を出す $ ag --scala -w get --stats --stats-only なし
scala のファイルで get.wild を使っているワイルドな箇所を検索する(パイプつなぎのファイル名つき/行番号つき) $ ag --scala --filename --numbers -w get | ag wild $ rg -t scala --with-filename --line-number -w get | rg wild
色付き表示のまま less につなげる $ ag --scala get --pager 'less -R' $ rg -p -t scala get | less -R

ripgrep には統計の機能がないようだが、そこまで使わないし、作者がやる気になればすぐ作れるだろう。速度は rg のほうが早く感じたが両者とも (grep に感じる) ストレスを感じさせない速度だったので(てきとう)使いたいほうを使えばいいと思う。

複数行マッチをサポートする ag とサポートしない rg

例えば Kubernetes の yaml ファイルで以下のようなファイルがあるとする。ag は複数行マッチに対応しており --multiline オプションがデフォルトで有効になっているため ag --yaml 'name.*\n.*spec.*\n.*replicas' . でマッチできる。一方 rg は実装の複雑さとパフォーマンスへの影響から複数行マッチには対応していないし、今後もおそらく対応する予定がない。rg の作者の Andrew Gallant はこの機能差分は ag と rg の違いを示すいい例だとコメントしている。(^_^;

(... snip ...)
  name: user-ranking-app
spec:
  replicas: 10

github.com

検索結果からさらに検索したい時はパイプ/プロセス置換

例えば linux カーネルソースから cgroup_selvictim の両方を含んでいる行を検索したい時(例はてきとう)。パイプのほうがプロセス置換よりも若干速い。フィルタする時は最初の検索にファイル名表示 -H と行番号表示 -n をつけておくほうが便利。

$ time rg -nH -t c cgroup_sel | rg victim
mm/memcontrol.c:1285:int mem_cgroup_select_victim_node(struct mem_cgroup *memcg)
mm/memcontrol.c:1305:int mem_cgroup_select_victim_node(struct mem_cgroup *memcg)
mm/vmscan.c:3124:   nid = mem_cgroup_select_victim_node(memcg);
include/linux/memcontrol.h:428:int mem_cgroup_select_victim_node(struct mem_cgroup *memcg);

real  0m42.889s
user  0m1.455s
sys   0m4.252s
$ time rg victim <(rg -nH -t c cgroup_sel)
1:mm/memcontrol.c:1285:int mem_cgroup_select_victim_node(struct mem_cgroup *memcg)
2:mm/memcontrol.c:1305:int mem_cgroup_select_victim_node(struct mem_cgroup *memcg)
3:mm/vmscan.c:3124: nid = mem_cgroup_select_victim_node(memcg);
4:include/linux/memcontrol.h:428:int mem_cgroup_select_victim_node(struct mem_cgroup *memcg);

real  0m48.226s
user  0m1.958s
sys   0m6.691s

percol 連携に都合のいいオプション

alias s='ag --all-types --hidden --noheading --nonumbers --nofilename . ~/memo/*| percol'

grep は滅びぬ、なんでも蘇るさ

新世代 grep は速度や表示の点で使いやすいけれども、それでも grep が滅びることはない。速度だけを見れば完敗だけれど。grep の問題は歴史的なしがらみだと思う。長年使われてきたせいで、さまざまなスクリプトが依存しており、仕様を変えると世界中に様々な影響が出てきてしまう。高速化もできないことはないが、後方互換性を維持する慎重な開発方針にならざるを得ない、といったところだろうか。あれ、人間も同じようなことが起きるような... |ω・)

d.hatena.ne.jp

Docker, Kubernetes 学習とツールとコンピュータサイエンス

f:id:kyagi:20180216023000p:plain

Docker, Kubernetes 学習の進めかた

Udemy の Learning Docker and Kubernetes by Lab を完了した。実際に手を動かしながらだったので 1 週間ほどかかってしまった。内容はかなりよかった。Docker の基礎から Linux カーネルとの関連(Network Namespaces, cgroups)、docker-compose, Docker Swarm, そして Kubernetes の紹介をそれぞれ理論→実践の順番で進めていく形式だったのでよく理解できた。

Kubernetes については 57 アイテムのうち 9 つでアーキテクチャと minikube を使った簡単な実例だけだったので、別に Learn DevOps: The Complete Kubernetes Course で学習を進めている。本家の https://kubernetes.io/ にも katacoda などを使用して理解を促すコンテンツもあるが、本家はどちらかというとリファレンス的な読み方を前提とした構成になっているように感じられる。基礎から手を動かして進めるのであれば Udemy のコースのほうがよいと思った。

Play with DockerPlay with Kubernetes など制限時間はあるが、ブラウザで体験できるプレイグラウンドなサービスもあるので、これらを組織の研修やプレゼンに活かす方法を考えている(特に直接 Docker や Kubernetes に触れることがないアプリケーション開発チームのエンジニアに対して有効な手段となるのではないかと唸っている)

個人的に印象深かったツールは Docker Swarm Visualizer だ。どのノードでどのコンテナが動いているかを可視化できる。これは比較的小規模なクラスタを前提として Docker Swarm の特徴をよく活かしたツールだと思う。Kubernetes にはないし(そもそも Kubernetes Dashboard や kubectl はそういう設計ではない)、同様のことをやろうとするならば kubectl describe pod だろうか。

github.com

Kubernetes 学習環境の比較

お手軽さ 環境 ツール コメント
最もお手軽 ブラウザ Play with Kubernetes お手軽だが最初は無理。自分で yaml がかけるようになったら研修コースやプレゼンで使えそう
お手軽 Mac/Win/Linux minikube クラスタは一つのノードのみの構成となるが kubectl でのリソース作成の練習にはよい
お手軽 AWS kops 簡単だが Route 53 や IAM の設定があるので AWS に慣れている必要がある(慣れていれば常識的なことをやるだけ)
??? AWS Amazon EKS プレビュー中なのでまだ使用できない
お手軽(だと思う) GCP GCP Kubernetes Engine 少しだけ触ったことがあるが、個人的に GCP ではなく AWS に集中する方針なので ???

Kubernetes は GCP で触るのが王道なのかもだけど、個人的な選択と集中の方針として AWS に集中投資していくので評価は ? としている。

コンピュータサイエンスの理論を学ぶことの大切さ

Swarm mode cluster architecture で紹介されていたものだが、これらの技術やアルゴリズムについては概要だけでも知っておくと、エンジニア人生がより楽しくなると思う。Kubernetes の原型である Borg system の論文は(細かいところはともかくとして)図も豊富でわかりやすいし、基本的な考え方がよくわかるので個人的にはとても気に入っている。

Large-scale cluster management at Google with Borg – Google AI

https://static.googleusercontent.com/media/research.google.com/ja//pubs/archive/43438.pdf

grpc.io

developers.google.com

http://thesecretlivesofdata.com/raft/

Gossip protocol - Wikipedia

Paxos (computer science) - Wikipedia

いわゆる、コンピュータサイエンスを専攻した方であれば、これらはすでに体系的に学んでいるものなのかもしれない。私のような雑草エンジニアはその時その時で自分の知識のスキマを見つけて埋めていくしかない。だが、それもまた楽しい。ԅ( ˘ω˘ԅ)

xargs が Linux と Mac 両方できちんと動くためには -i ではなく -I オプションを使う

LinuxMac で xargs の挙動が違うのは以前から認識していたが、詳細までは調べていなかった。今回、プライベートの git レポジトリをコミットして Mac で xargs を実行した際に illegail option -- i と怒られてしまったので、これを機会に xargs が LinuxMac 両方できちんと動作するように修正した。

Linux では動く、Mac では動かない

# da = dot apply
alias da="ls -A ~/git/$repo/t/dot/ | xargs -i cp {} $HOME && pushd $HOME && source $HOME/.bash_profile && popd"

Mac 版の xargs (BSD) では -i オプションがサポートされていないのが問題だった。

$ da
xargs: illegal option -- i
usage: xargs [-0opt] [-E eofstr] [-I replstr [-R replacements]] [-J replstr]
             [-L number] [-n number [-x]] [-P maxprocs] [-s size]
             [utility [argument ...]]

xargs 調査

Linux 版の xargs の man を読むと、そもそも -i オプションはすでに deprecated となっており -I オプションを使うことが推奨されている。単純に -i を -I に変えればいいのかと思ったら、少し事情は異なっていて -I オプションは -i と違ってデフォルトの replace-str に {} を設定してくれないので、明示的に {} を指定する必要がある。

$ man xargs
(.. snip ...)

       -I replace-str
              Replace  occurrences  of replace-str in the initial-arguments with names read from standard input.  Also, unquoted blanks do not terminate input items;
              instead the separator is the newline character.  Implies -x and -L 1.

       -i[replace-str], --replace[=replace-str]
              This option is a synonym for -Ireplace-str if replace-str is specified.  If the replace-str argument is missing, the effect is the same as -I{}.   This
              option is deprecated; use -I instead.

(.. snip ...)

上記を踏まえて以下のように修正した。(๑•̀ㅂ•́)و✧

Linux でも Mac でも動く(replace-str に伝統的(?) に {} を使用)

$ git diff -U0 HEAD~2 HEAD
(... snip ...)
-alias da="ls -A ~/git/$repo/t/dot/ | xargs -i cp {} $HOME && pushd $HOME && source $HOME/.bash_profile && popd"
+alias da="ls -A ~/git/$repo/t/dot/ | xargs -I {} cp {} $HOME && pushd $HOME && source $HOME/.bash_profile && popd"
(.. snip ...)

Linux でも Mac でも動く(replace-str に独自のトークンを使ってみた例)

$ git diff -U0 HEAD~2 HEAD~1
(.. snip ...)
-alias da="ls -A ~/git/$repo/t/dot/ | xargs -i cp {} $HOME && pushd $HOME && source $HOME/.bash_profile && popd"
+alias da="ls -A ~/git/$repo/t/dot/ | xargs -I _file_ cp _file_ $HOME && pushd $HOME && source $HOME/.bash_profile && popd"
(... snip ...)

Docker イメージの LABEL 情報は Label-Schema が標準になる

f:id:kyagi:20180216163134p:plain

Docker イメージのメタ情報(メンテナ、ライセンス種別、ビルド日時、Github の URL など) は LABEL に集約しようという動きが広まってきている。そのために「どういった情報を載せるべきか」という議論がコミュニティベースで展開された結果、標準として Label-Schema という規格で定まってきているようだ。

label-schema.org

Docker 社もこの動きには賛同している。現在は 1.0.0-rc.1 (Release Candidate) だが、近いうちに正式に標準となりそうだ。

Docker Inc. express a preference that container labels should be namespaced. Label Schema is a community project to provide a shared namespace for use by multiple tools, specifically org.label-schema.

個人的にもイメージの透明性を高めるために当然の動きだと思う。正直 Dockerhub でよさそうなイメージを見つけても Dockerfile が公開されていない(≒ Github 連携していない) イメージは怖くて使えない。

Dockerfile の中で ARG で指定した変数は docker build 時に --build-arg var=value とオプションで渡すことができる。動的な情報のビルド日時や VCS_REF(=git のコミット値(SHA)) が対象となるようだ。

$ docker build --build-arg BUILD_DATE=`date -u +"%Y-%m-%dT%H:%M:%SZ"` .
(... snip ...)
Successfully built d187459cd07e

$ docker image inspect d187459cd07e | jq '.[].ContainerConfig.Labels' | jq '.["org.label-schema.build-date"]'
"2018-02-05T16:39:17Z"

$ date
Tue Feb  6 01:57:38 JST 2018

Github と Dockerhub を連携して Automated Build を設定している場合は hooks/build というスクリプトを置くことで同様に ARG で指定した変数を渡すことができる。サンプルを公開してくださっている方がいるので、こちらを参考に GitHub - kyagi/rod: REPLs On Docker を修正した。

github.com

Automated Build されたイメージを pull して LABEL 情報を確認すると vcs-ref がきちんと入っていることを確認できた。

$ docker image inspect kyagi/rod:latest | jq '.[].ContainerConfig.Labels' | jq -r '.["org.label-schema.vcs-ref"]'
02714fd

git rev-parse --short HEAD
commit 02714fd

ただ、問題がふたつ。ひとつめは、LABEL に BUILD_DATE など動的な値を渡すとキャッシュが効かなくなるようで、ビルドに時間がかかる。--no-cache を指定した時と同じ動きをしているように見える。ふたつめは org.label-schema.abc とキー名にドットが入っているせいで jq の処理に手間がかかる。どうしたものかなあ。(´・_・`)

Tectonic CoreOS を使って AWS 上に Kubernetes クラスタを構築する

CoreOS 社が提供している Kubernetes にセキュリティ、監視、マルチクラウド統合、そしてカッコイイ管理インターフェイスを備えた統合環境 Tectonic を使用してみた。

同社の推しは「Kubernetes の管理コストをさげる」「クラウドベンダーにロックインされない」だが、逆に柔軟が設計/設定ができる Kubernetes の自由度を下げ、同社にロックインされるとも受け取れる。試していないが Openshift も似たような統合環境のようだ。

coreos.com

課金体系

kubernetes クラスタのノードが 10 台までは無料、それ以上は応相談。

インストール

インストールの前に CoreOS の URL からアカウントを作成してライセンス情報と設定ファイルを入手する必要がある。CoreOS 社が提供する圧縮ファイルの中のインストーラを起動し、ブラウザ上で各種情報(AWS の場合、認証情報や Manage, Worker, Etcd それぞれのノード台数とインスタンスタイプなど)を入力して進めていく。内部では Terraform が使われている。インスタンスタイプはデフォルトで t2.medium なのでそこまでお金はかからない。

ハマりどころ

  • デフォルトでは Manage : Worker : Etcd = 3 : 3 : 3 で kubernetes クラスタを構築しようとするが、個人の AWS だと EIP の上限はだいたい 5 になっているのでそこでエラーになる。
  • 使用するドメインTTL を短くする必要がある。retry = 90, ttl = 89 にして成功した。

DNS caching

An issue arises when a domain's Address record (A record) resolution is attempted before Route 53 publishes the cluster's A record and the NXDOMAIN response is cached in the NCACHE (RFC2308). This negative response may be cached for up to the number of seconds set in the domain's SOA record's TTL. Resolution fails until the negative caching TTL expires. These TTLs are typically large enough to disrupt the installation. The current workaround is to ensure your TTLs are set to a low interval, or to wait for them to expire, then proceed with the installation.

AWS Troubleshooting | Tectonic Installer on AWS | CoreOS

変更前 SOA) ns-123.awsdns-45.com. awsdns-hostmaster.amazon.com.  1 7200 900 1209600 86400
変更後 SOA) ns-123.awsdns-45.com. awsdns-hostmaster.amazon.com.  1 7200 90 1209600 89
  • 上記エラーの場合 Destroy & Start Over でやり直すことができるのだが、IAM ロールが削除されていないようで Already Exists なエラーになるので削除する。

感想

tectonic-system というネームスペースが追加されて、そこで Tectonic 固有の監視やコンソールの Pods が動いているようだ。Prometheus, Grafana も同時にインストールされる。ノードが 10 台まで無料(EC2 や AWS リソースはもちろん自分で払う) なので、開発環境にはよいかもしれない。個人的には kops のほうがコマンド一発で使いやすい。

  • Tectonic Console 1 f:id:kyagi:20180206022634p:plain

  • Tectonic Console 2 f:id:kyagi:20180206022645p:plain

  • Tectonic Console 3 f:id:kyagi:20180206022731p:plain

  • Prometheus f:id:kyagi:20180206022743p:plain

  • Grafana f:id:kyagi:20180206022748p:plain

クラスタ削除は terraform destroy で可能。構築した時のインストーラの情報を削除しないように注意されたし。(´・ω・`) https://coreos.com/tectonic/docs/latest/install/aws/uninstall.html

Scala, Ruby, Go の REPL 環境を Docker コンテナですぐに試せる Rod (REPLs on Docker) を作った

Scala の学習用に Ammonite 環境AWS EC2 上に構築していて、Scala Exercises の例を試せるように紹介されている cats や shapeless のライブラリを追加していた。ただ、個人的に Ruby を使う機会が圧倒的に多いので pry は必須だし、Go もやってみたいと考えているうちに、「REPL 環境をまとめた Docker コンテナを作成してしまえば楽なのでは?」と思って Rod (REPLs on Docker) というツールを作成した。

github.com

Github と Dockerhub を連携させて Automated Build 設定しているので、Github が更新されると自動的に Dockerhub 内部で docker builddocker push してくれる。

https://hub.docker.com/r/kyagi/rod/

簡単な使い方は以下の通り。例えば Scala Exercises の Cats | Semigroup の紹介されているコード例をそのまま打ち込めば試せるようになっている。自分自身が sbt console の使い勝手にあまり満足していないのと、Scala の開発環境構築に手間取ったところがあるので、こういったツールが誰かの役に立ってくれれば嬉しい。(^.^)

docker コンテナをたちあげる。

$ docker run -it kyagi/rod
 ____  _____ ____  _                         ____             _
|  _ \| ____|  _ \| |    ___    ___  _ __   |  _ \  ___   ___| | _____ _ __
| |_) |  _| | |_) | |   / __|  / _ \| '_ \  | | | |/ _ \ / __| |/ / _ \ '__|
|  _ <| |___|  __/| |___\__ \ | (_) | | | | | |_| | (_) | (__|   <  __/ |
|_| \_\_____|_|   |_____|___/  \___/|_| |_| |____/ \___/ \___|_|\_\___|_|
root@e8dcf015f1b5:~# 

Scala の REPL を使いたい時は rod scala で amm が起動する。

root@e8dcf015f1b5:~# rod scala
Loading...
Compiling (synthetic)/ammonite/predef/interpBridge.sc
Compiling (synthetic)/ammonite/predef/replBridge.sc
Compiling (synthetic)/ammonite/predef/DefaultPredef.sc
Compiling /root/.ammonite/predef.sc
Welcome to the Ammonite Repl 1.0.3
(Scala 2.12.4 Java 1.8.0_151)
If you like Ammonite, please support our development at www.patreon.com/lihaoyi
@ import cats.Semigroup
import cats.Semigroup

@ import cats.implicits._
import cats.implicits._

@ Semigroup[Int => Int].combine({ (x: Int) => x + 1 }, { (x: Int) => x * 10 }).apply(6)
res2: Int = 67

@ exit
Bye!
root@e8dcf015f1b5:~#

Ruby の場合は pry が立ち上げる。

root@e8dcf015f1b5:~# rod ruby

[1] pry(main)> ri open
(... snip ...) いっぱいあるけど Tempfile.open が知りたい
[2] pry(main)> ri Tempfile.open
(... snip ...)
[3] pry(main)> ri Array
(... snip ...) Array クラスのドキュメント
[4] pry(main)> ri Array#
(... snip ...) Array クラスのインスタンスメソッド一覧

Go の場合は gore が立ち上げる。

root@e8dcf015f1b5:~# rod go
gore version 0.2.6  :help for help
gore> :help
    :import <package>     import a package
    :print                print current source
    :write [<file>]       write out current source
    :doc <expr or pkg>    show documentation
    :help                 show this help
    :quit                 quit the session
gore> :quit
root@e8dcf015f1b5:~#

いろいろいれたらイメージサイズがふくらんでしまった(1.43GB)。特定の言語の REPL があればいい場合は、Dockerfile を削ることで軽量化できるけど、その逆にこの REPL も入れたい(例えば gore じゃなくて go-pry を使いたいなど) とかもあるかもしれない。

ちなみに .rod-prompt を読み込むことでプロンプトが変わって、Docker コンテナ内の環境にいることがわかりやすくなる(おまけ)。(๑˃̵ᴗ˂̵)و

https://camo.githubusercontent.com/4982b60d784c7eea80e15e3a47de26ab4e4fd01f/687474703a2f2f63646e2d616b2e662e73742d686174656e612e636f6d2f696d616765732f666f746f6c6966652f6b2f6b796167692f32303138303230342f32303138303230343132323935322e706e67

プロンプト新世代を agnoster-bash と docker-prompt に感じた

agnoster-bash

会社の同僚に教えていただいて Mac のプロンプトを変えてみた。今までも環境ごとにプロンプトは変えていたけれど、vt100 で xterm な枠の中でせいぜい背景色と文字書を変更するぐらいだった。大げさかかもしれないけど、ブランチの記号がフォントに含まれるのにプロンプト新世代を感じた。打ったコマンドの終了ステータスが 0 以外だと赤いバツが出るのも心憎い。

GitHub - speedenator/agnoster-bash: Agnoster Theme for Bash

いままで)

f:id:kyagi:20180204111353p:plain

これから)

f:id:kyagi:20180204111407p:plain

コマンドの終了ステータスが 0 以外の場合、赤いバツがでる)

f:id:kyagi:20180204183523p:plain

プロンプト中のブランチマークが ? になってしまって表示できない現象は iTerm2 のフォントを Meslo LG ファミリに変更すれば表示できるようになる。

My theme error, shows question mark symbol · Issue #23 · agnoster/agnoster-zsh-theme · GitHub

個人的にフォントは Menlo 派なので Meslo LG フォントが Menlo の流れを汲んでいるのはありがたい(Menlo と Meslo で名前も似ている理由があるのかな、と探してみたけど、見つからなかった)

GitHub - andreberg/Meslo-Font: Customized version of Apple's Menlo font. Great monospaced font for development work. Should also work with the Windows Console (see Wiki for Windows infos).

日付と時間を表示しておきたいのと、視点はいつも左端に固定しておくのが楽なので、ちょっとだけ修正を加えておく。

Added datetime and newline for my preference by kyagi · Pull Request #1 · kyagi/agnoster-bash · GitHub

docker-prompt

最近では Docker コンテナかそうでないかによってプロンプトを変更するということもあるようだ。環境ごとに違った emoji をプロンプトに出すというのもなかなかいいかもしれない。

How to get a fancier bash prompt PS1 inside a docker container #docker #ps1 #emoji · GitHub

歴史的なプロンプト変更方法

環境ごとにプロンプトを変更するのは昔からよく行われてきた。よくある例だと、開発環境か本番環境によって色を変える *1 など。

昔読んだドキュメントにプロンプトに焦点を当てて解説していたものがあったので探してみた。バージョン管理が RCS ぽいところも懐かしい。ただし、内容はまだ現在でも褪せていない。(๑´ㅂ`๑)

http://linuxdocs.org/HOWTOs/Bash-Prompt-HOWTO/index.html

*1:具体的には ip a show dev eth0 | grep -o 'inet [0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}' | cut -d ' ' -f2 して切り出した IP アドレスを、さらに cut -d'.' -f1-2 でネットワーク部分を抽出して case ごとに PS1 を変える

bash: /dev/tty: No such device or address を script コマンドで解決する

GitHub - kyagi/rod: REPLs On Docker を作っている中で Ammonite の起動が早くなるようにあらかじめライブラリをキャッシュさせておくため .ammonite/predef.sc にライブラリを追加したあとに echo exit | amm -s を実行するようにしたところ、docker build 中に bash: /dev/tty: No such device or address が出てビルドに失敗するようになってしまった。原因は amm がメッセージを吐き出す端末(tty)が見つからないことのようだ。REPL はそもそも人間とのインタラクティブなやりとりが想定されているのだから、至極当然な気もするが、このままでは Docker イメージが作れない。

Dockerfile

RUN echo exit | amm -s
$ docker build .
(... snip ...)
bash: /dev/tty: No such device or address
bash: /dev/tty: No such device or address
java.lang.RuntimeException: Nonzero exit value: 1
  scala.sys.package$.error(package.scala:27)
  scala.sys.process.ProcessBuilderImpl$AbstractBuilder.slurp(ProcessBuilderImpl.scala:134)
  scala.sys.process.ProcessBuilderImpl$AbstractBuilder.$bang$bang(ProcessBuilderImpl.scala:104)
  ammonite.terminal.TTY$.stty(Utils.scala:118)
  ammonite.terminal.TTY$.init(Utils.scala:97)
  ammonite.terminal.Terminal$.x$1$lzycompute$1(Terminal.scala:41)
  ammonite.terminal.Terminal$.x$1$1(Terminal.scala:41)
(... snip ...)
ビルドが終わらない...

いろいろ悩んで、ぐぐって、試行錯誤したところ、script -c を使うことで回避することができた。これが今年の今まで一番のハック。╭( ・ㅂ・)و ̑̑ グッ !

$ man script
(... snip ...)
     -c, --command command
             Run the command rather than an interactive shell.  This makes it easy for a script to capture the output
             of a program that behaves differently when its stdout is not a tty.
(... snip ...)

Dockerfile

RUN export TERM=vt100 && script -qfc 'echo exit | amm -s' && rm typescript

https://github.com/kyagi/rod/blob/master/Dockerfile#L63

$ docker build .
(... snip ...)
Successfully built 38b3611dc9b5

Docker を支える Linux カーネル機能 Network Namespace から docker exec の実装が ip netns exec である雰囲気をつかむ

f:id:kyagi:20180216163134p:plain

Linux の Network Namespace を ip netns exec で操作しているうちに「docker exec の正体」がなんとなく見えてきた。

Bridge Networking Deep Dive — Docker Kubernetes Lab 0.1 documentation

デフォルトでは docker はコンテナの Network Namespace を見せないようにしている。これは docker exec からのみコンテナの Network Namespace にアクセスを許可するように意図しているものに感じられる。OOP でクラスのメンバを private や protected で保護することと似ているかもしれない。

$ docker run --rm -d --name b1 busybox sh -c "while true; do sleep 3600; done"
$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
0e3030605661        busybox             "sh -c 'while true..."   About an hour ago   Up About an hour                        b1

いつも操作を忘れてしまうので、シェル関数を作っておく。docker-netns show [CONTAINER-NAME] で ip netns 経由で exec できるようになる。隠蔽された状態に戻したい時は docker-netns stash [CONTAINER-NAME] を実行する。

function docker-netns () {
  local action=$1
  local cname=$2

  case ${action} in
    show)
      docker inspect ${cname} | jq ".[].State.Pid" | xargs -i sudo ln -s /proc/{}/ns/net /var/run/netns/${cname}
      echo "Network namespace for container ${cname} is visible now."
      ;;
    stash)
      sudo rm /var/run/netns/${cname}
      echo "Network namespace for container ${cname} is invisible now."
      ;;
    *) :
      ;;
  esac

  ls -l /var/run/netns/${cname}
}
$ sudo ip netns list
$

$ docker-netns show b1
Network namespace for container b1 is visible now.
lrwxrwxrwx 1 root root 18 Jan 31 00:30 /var/run/netns/b1 -> /proc/11385/ns/net

$ sudo ip netns list
b1

$ sudo ip netns exec b1 ip a # 以下 docker exec と内容が同じ
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
91: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.2/16 scope global eth0
       valid_lft forever preferred_lft forever

$ docker exec -it b1 ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
91: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.2/16 scope global eth0
       valid_lft forever preferred_lft forever

$ docker-netns stash b1
Network namespace for container b1 is invisible now.
ls: cannot access /var/run/netns/b1: No such file or directory

$ sudo ip netns list
$ 

$ sudo ip netns exec b1 ip route
Cannot open network namespace "b1": No such file or directory

ただ ip netns exec b1 ip a でなく ip netns exec b1 hostname だとコンテナのホスト名 0e3030605661 は返ってこずに docker host のホスト名が返ってくるので、あくまでネットワークに限定した名前空間である感覚がしている。その意味では docker exec は Network Namespace だけでなく Process Namespace やその他の名前空間をまとめて隔離した空間に対するインターフェイスなのではないか。

ip netns exec が docker exec の正体というのはただの個人的感覚でしかない。今度、知り合いのカーネルハッカーに聞いてみよう...。自分でソースを読めと言われるに決まっているのだけれど。ただこういった局所的な興味からカーネルソースに飛び込んでみるのも目的がはっきりしていていいかもしれない。(´▽`)

Disjunction と Either の違い

Safari Books Online で検索したところ、以下の記述が見つかった。

Disjunction is conceptually similar to Either, which can be used to represent one of two possible types. Disjunction is different from Either because its operations are right-biased. (6. Concurrency in Scala, Scala High Performance Programming)

scala の API ドキュメントでも Either の「Left を例外処理、Right を正常処理」は慣習的な取り決めのように読み取れる。

Convention dictates that Left is used for failure and Right is used for success. Scala Standard Library 2.12.0 - scala.util.Either

上記から、個人的に以下のような理解をしている。

  • Disjunction はもともと Right を正常値とするように設計されている。
  • Either は convention として慣習的に Right を正常処理とするように使われている(ことが多い)。

※会社の Scala エキスパートによると「2.12からEitherもright-biasedなので古い本は窓から投げ捨てて下さい」だそうだ。(^_^;