「車輪の再実装」って言葉が好き(実践はできてない)

起動済みのdockerコンテナのネットワーク名前空間に入る方法

まず、あらかじめ入りたいコンテナのIDか名前を取得しておき、以下の様な手順で入れる。

pid=$(docker inspect <コンテナのIDか名前> --format '{{.State.Pid}}')
sudo mkdir -p /var/run/netns
sudo mount --bind /proc/${pid}/ns/net /var/run/netns/<名前(任意)>
sudo ip netns exec <名前>  bash

基本的にホストのソフトをそのまま使えるので、ipやping等をそのまま使えて便利。

ただし、自分でunmountし無い限り、docker上でコンテナを消しても名前空間が残り続けるので注意。

ちなみに、残っている場合は、

sudo ip netns list

とすると、自分で指定した名前が見えるはず。

オンライン上のmanページまとめ(随時追記)

www.mankier.com デフォルトでついているページ内検索が優秀だったり、デザインが一番モダンな感じで個人的に一番好き。対応しているページも多い気がする。 ただSEOの問題か、google検索してこのサイトに飛ぶことは少ない。

linux.die.net ちょっとマニアックなページをgoogle で検索すると出てくるイメージ

man7.org おそらく一番オーソドックスなページ。

manpages.ubuntu.com Ubuntuのmanページ。 おそらく、各バージョンに対応したページが出てくるはず。
たまに、新しいmanを参照していて実際の挙動と違う...みたいなことがあると、このページで対応したバージョンを見る。

linux namespacesのファイルと永続化について

ip netnsコマンドを使うと、/var/run/netnsディレクトリにファイルを足してネットワーク名前空間を足してくれたり、
/proc/[pid]/ns以下に、そのプロセスの名前空間に対応するファイルを見せてくれるのは知っていたけれど、そのあたりの扱いをうろ覚えなので少し整理.

以下、namespaces(7)より抜粋。

Bind mounting (see mount(2)) one of the files in this directory to somewhere else in the filesystem keeps the corresponding namespace of the process specified by pid alive even if all processes currently in the namespace terminate.

というわけで、/proc/[pid]/ns/ 以下のファイルをバインドマウントすればいいらしい。

ちなみに、network namespace向けのmanも読んだが、/var/run/netns/ についての記述はなかったので、このディレクトリはkernelの規定というよりは、ipコマンドが使っているだけな気がしている。

ところで、

In Linux 3.7 and earlier, these files were visible as hard links. Since Linux 3.8, they appear as symbolic links. If two processes are in the same namespace, then the device IDs and inode numbers of their /proc/[pid]/ns/xxx symbolic links will be the same; an application can check this using the stat.st_dev and stat.st_ino fields returned by stat(2). The content of this symbolic link is a string containing the namespace type and inode number as in the following example:

らしい。 /proc/[pid]/ns/シンボリックリンクを作ればいい...みたいに勘違いていたのはここら辺を混同した気がする。

参考

vagrant-libvirtをネストした時のネットワークアドレス衝突への対処法

vagrant-libvirtを使うと、vagrantlibvirtをproviderとして使うことことができ、自分でvirshコマンド等をいちいち叩かなくてすごい便利なのですが、 表題の通り、ネストして使おうとするとネットワークアドレスが衝突して2段目のvagrant upに失敗します。

これは、vagrant-libvirtが使う管理用のネットワークのアドレスが、 ソースコード中にベタ書きで192.168.121.0/24にデフォルトで設定されていることが原因です。

解決策1: Vagrantfileで libvirtのデフォルトネットワークを利用する様に変更する。

まず、以下の様にlibvirtのデフォルトのネットワークアドレスを調べます。

$ virsh net-dumpxml | grep "ip address"
<ip address='192.168.122.1' netmask='255.255.255.0'>

この値を元に、以下の様にVagrantfileに追記すれば、 仮想マシンがdefaultネットワークを使う様になります

config.vm.provider :libvirt do |libvirt|
  libvirt.management_network_name='default'
  libvirt.management_network_address='192.168.122.0/24'
end

これでvagrant upをすればOKです。

解決策2: vagrant-libvirtのデフォルトネットワークのアドレスを変更する。

vagrant-libvirtは、デフォルトでvagrant-libvirtというlibvirtのネットワークを定義します。 このネットワークのアドレスを

virsh net-edit --network vagrant-libvirt

等で修正し、(今回は192.168.200.0/24を使う様にxmlを修正)

VM(L2)向けのVagrantfileを以下の様な内容を追加することです。

  config.vm.provider :libvirt do |libvirt|
    libvirt.management_network_address="192.168.200.0/24"
  end

ただ、一度vagrant upをしないとvagrant-libvirtネットワーク自体が生成されてい無いので、この方法は使えず注意が必要です。

余談

なんの加減かわかりませんが、たまにDHCPでアドレスが降ってくるのが遅い場合があります...

参考

UNIXドメインソケットの通信相手(プロセス)を調べる

UNIX ドメインソケットを使っているプロセスの番号とファイルディスクリプタの番号を知りたくなったので、やり方をメモ。

以下のようにlsofコマンドを使う。

$ sudo lsof -U +E +c 15 | grep containerd
systemd             1            root   48u  unix 0xffff8b1691980400      0t0 106648 /run/systemd/journal/stdout type=STREAM ->INO=104729 10894,containerd,2u 10894,containerd,1u
systemd-journal   530            root   21u  unix 0xffff8b1691980400      0t0 106648 /run/systemd/journal/stdout type=STREAM ->INO=104729 10894,containerd,2u 10894,containerd,1u
containerd      10894            root    1u  unix 0xffff8b1691983400      0t0 104729 type=STREAM ->INO=106648 530,systemd-journal,21u 1,systemd,48u
containerd      10894            root    2u  unix 0xffff8b1691983400      0t0 104729 type=STREAM ->INO=106648 530,systemd-journal,21u 1,systemd,48u
containerd      10894            root    6u  unix 0xffff8b1695092c00      0t0 105834 /run/containerd/containerd.sock type=STREAM
containerd      10894            root    7u  unix 0xffff8b1693019400      0t0 106727 /run/containerd/containerd.sock type=STREAM ->INO=106726 11069,dockerd,7u
containerd      10894            root    8u  unix 0xffff8b1693019c00      0t0 106730 /run/containerd/containerd.sock type=STREAM ->INO=106729 11069,dockerd,8u
dockerd         11069            root    7u  unix 0xffff8b169301b400      0t0 106726 type=STREAM ->INO=106727 10894,containerd,7u
dockerd         11069            root    8u  unix 0xffff8b1693018c00      0t0 106729 type=STREAM ->INO=106730 10894,containerd,8u

-Uオプションで、プロトコルUNIXドメインソケットに限定、
+Eオプションで通信のエンドポイント情報を表示、
+c 15は、コマンド名が省略されるのを防ぐために使用。

調べ不足かも知れないが、lsofのオプションにソケットのパス名は指定できなそうなので、grepで探している。

overlayファイルシステムで複数のlowerdirを使うときの順序

overlay でマウントするときの下位ディレクトリの順番をよく忘れるのでメモ

$ mkdir lower1 lower2 upper work merged
$ echo 'lower1' >> lower1/file
$ echo 'lower2' >> lower2/file
$ sudo mount -t overlay \  
-o lowerdir=$PWD/lower1:$PWD/lower2,upperdir=$PWD/upper,workdir=work \
overlay \
$PWD/merged
$ cat merged/file
lower1

というわけで、一番左側が上位になるらしい。
順番が紛らわしいので

-o upperdir=$PWD/upper,lowerdir=$PWD/lower1:$PWD/lower2

と書いたほうが良い気がする。

Ubuntu18.04 でbcc, bpftraceを動かす

最近流行りのeBPFを研究でつかっている?のですが、せっかくなので使い方のメモを公開しておこうと思います。

OSのセットアップ

タイトルにもあるようにUbuntu18.04を使います。

bpfは最近流行っていることもあり、新しいバージョンのカーネルでないと使えない機能もあります。
せっかくなのでカーネルのバージョンを新しいものに上げておきましょう。
また、ヘッダーも必要なので、合わせてインストールしておきます。

sudo sed -i.bak -e "s%http://archive.ubuntu.com/ubuntu/%http://ftp.iij.ad.jp/pub/linux/ubuntu/archive/%g" /etc/apt/sources.list
sudo apt update
LATEST_KERNEL_IMAGE=$(apt search linux-image- 2>/dev/null |egrep  '^linux-image-[45].*-generic' |cut -d'/' -f1 | tail -n -1)
LATEST_KERNEL_HEADER=$(apt search linux-headers- 2>/dev/null |egrep  '^linux-headers-[45].*-generic' |cut -d'/' -f1 | tail -n -1)
sudo apt install -y $LATEST_KERNEL_HEADER $LATEST_KERNEL_IMAGE
sudo reboot

bcc

bccはbpf用のツールチェーンです。
特に、pythonモジュールを使うことで、かんたんにBPFのプログラムを動かすことができます。

Ubuntu 18.04では、aptを使えばbcc自体は入るのですが、
aptで入るbccはライブラリの依存の関係でbpftraceには使えないので、bcc自体も自分でビルドします。

基本的には公式のリポジトリの説明のとおりですが、python3で使いたいので cmakeの際に-DPYTHON_CMD=python3を足します。

$ cd ~
$ sudo apt-get -y install bison build-essential cmake flex git libedit-dev \
  libllvm6.0 llvm-6.0-dev libclang-6.0-dev python zlib1g-dev libelf-dev
$ git clone https://github.com/iovisor/bcc.git
$ mkdir bcc/build; cd bcc/build
$ cmake .. -DCMAKE_INSTALL_PREFIX=/usr -DPYTHON_CMD=python3
$ make -j4
$ sudo make install

これでインストールは完了です。

早速動かしてみましょう。

 cd ..
$ sudo python3 examples/hello_world.py

多分これだけだと何も表示されないはずです。

それもそのはず、このpythonのプログラムは以下のようにcloneが呼ばれたらHello, World!を出力する仕様になっています。

code = '''
int kprobe__sys_clone(void *ctx)
{
  bpf_trace_printk("Hello, World!\\n");
  return 0;
}
'''
BPF(text=code).trace_print()

というわけで、別端末等で適当にコマンドを実行してみましょう。
(シェルのbuiltinだとcloneが実行されない場合があるので、注意)

こんな感じで出力されるはずです。

b'            bash-1048  [000] ....  2985.061252: 0: Hello, World!'
b'            bash-1048  [000] ....  2987.416850: 0: Hello, World!'

余談ですが、python3ではなく2系で実行したらb' 'とは表示されませんでした。
python3対応が遅れてるみたいですね(涙)

なお、このブログを書いている時は Vagrantで試しているせいか引っかからなかったですが、
実機で試したときは

#  echo 1 > /proc/sys/kernel/sysrq
#  echo x > /proc/sysrq-trigger

としなければ動きませんでした。

このあたりの問題は FAQにまとめてあるので、 動かなかったら見てみると良いでしょう。

また、もしかすると、ライブラリを認識させるために、sudo ldconfig を実行する必要があるかもしれません。

bccの使い方についての説明は今回は省略します。

bpftrace

Dtrace や SystemTapのようなツールです。

カーネル内部にさくっとフックを仕込んでトレースしたい場合に便利なツールです。
カーネル内部だけでなく、ユーザー空間にフックを仕込むとう、幅広い使い方ができます。)

$ sudo apt update
$ sudo apt install -y bison cmake flex g++ git libelf-dev zlib1g-dev libfl-dev systemtap-sdt-dev \
                             llvm-7-dev llvm-7-runtime libclang-7-dev clang-7
$ git clone https://github.com/iovisor/bpftrace
$ mkdir bpftrace/build; cd bpftrace/build;
$ cmake -DCMAKE_BUILD_TYPE=Release ..
$ make -j4
$ sudo make install

さて、実行してみます。

(このプログラムは自分で終了する必要があります。数秒立ったらCtrl+Cで止めましょう。 実際の出力が出るまで少し時間がかかるかもしれません。)

sudo  bpftrace -e 'tracepoint:syscalls:sys_enter_* { @[probe] = count(); }'
Attaching 332 probes...
^C

@[tracepoint:syscalls:sys_enter_getsockname]: 1
@[tracepoint:syscalls:sys_enter_prctl]: 1
@[tracepoint:syscalls:sys_enter_adjtimex]: 1
@[tracepoint:syscalls:sys_enter_rename]: 1
@[tracepoint:syscalls:sys_enter_bind]: 1
@[tracepoint:syscalls:sys_enter_clone]: 1
@[tracepoint:syscalls:sys_enter_fchmod]: 1
@[tracepoint:syscalls:sys_enter_kill]: 1
@[tracepoint:syscalls:sys_enter_epoll_create1]: 1
@[tracepoint:syscalls:sys_enter_lseek]: 1
@[tracepoint:syscalls:sys_enter_exit_group]: 1
@[tracepoint:syscalls:sys_enter_rt_sigreturn]: 2
@[tracepoint:syscalls:sys_enter_getdents]: 2
@[tracepoint:syscalls:sys_enter_epoll_ctl]: 2
@[tracepoint:syscalls:sys_enter_socket]: 2
@[tracepoint:syscalls:sys_enter_write]: 3
@[tracepoint:syscalls:sys_enter_newlstat]: 3
@[tracepoint:syscalls:sys_enter_getpid]: 3
@[tracepoint:syscalls:sys_enter_getrandom]: 3
@[tracepoint:syscalls:sys_enter_readlinkat]: 3
@[tracepoint:syscalls:sys_enter_poll]: 4
@[tracepoint:syscalls:sys_enter_select]: 4
@[tracepoint:syscalls:sys_enter_futex]: 5
@[tracepoint:syscalls:sys_enter_munmap]: 5
@[tracepoint:syscalls:sys_enter_sendmsg]: 5
@[tracepoint:syscalls:sys_enter_inotify_add_watch]: 8
@[tracepoint:syscalls:sys_enter_access]: 9
@[tracepoint:syscalls:sys_enter_rt_sigprocmask]: 10
@[tracepoint:syscalls:sys_enter_recvmsg]: 11
@[tracepoint:syscalls:sys_enter_fcntl]: 12
@[tracepoint:syscalls:sys_enter_alarm]: 15
@[tracepoint:syscalls:sys_enter_rt_sigaction]: 20
@[tracepoint:syscalls:sys_enter_newfstat]: 30
@[tracepoint:syscalls:sys_enter_newstat]: 31
@[tracepoint:syscalls:sys_enter_epoll_wait]: 39
@[tracepoint:syscalls:sys_enter_read]: 148
@[tracepoint:syscalls:sys_enter_perf_event_open]: 150
@[tracepoint:syscalls:sys_enter_dup]: 302
@[tracepoint:syscalls:sys_enter_bpf]: 322
@[tracepoint:syscalls:sys_enter_openat]: 347
@[tracepoint:syscalls:sys_enter_ioctl]: 565
@[tracepoint:syscalls:sys_enter_dup2]: 602
@[tracepoint:syscalls:sys_enter_close]: 1029

ちゃんと動きました。

本記事はこれで終わり。
probeにも色々な種類があり、使い方に癖があったりするので、
その気になったらまた記事にするかもしれません。