SVX日記
2021-12-21(Tue) dockerのコンテナ内でアレしたい
というわけで、私はdockerが好きなのだが、どうにも使い勝手の悪い部分がある。コンテナの開発をしていると、docker execでコンテナ内に入ったり、docker logsでコンテナのログを見たりする機会が非常に多いのだが、その都度、対象となるコンテナ名をフルで入力しなければならない点だ。マウスでコピペとか、キーボードから手が離れるのは、かなりのストレスなんじゃ!
コンテナ名の部分一致でマッチしてくれりゃいいのに……などと、ブツブツ文句をいうヒマがあるならば、ラッパースクリプトを作ってしまえばいいのである。で、作った。コンテナ名の一部を入力すればマッチしてくれる。便利。
# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b41353b0f15e docker.io/furutanian/maverick "bash startup.sh" 11 days ago Up 11 days 0.0.0.0:8081->80/tcp maverick-gamma
d474d17534d1 docker.io/furutanian/hyperestraier "bash startup.sh" 4 months ago Up 2 weeks 0.0.0.0:8091->80/tcp hyperestraier-gamma
54eef26064e9 docker.io/furutanian/pinchmail "bash -c 'bash sta..." 4 months ago Up 2 weeks 0.0.0.0:8025->25/tcp, 0.0.0.0:8110->110/tcp pinchmail-alpha
# docker_exec pin
# INTO [pinchmail-alpha].
[root@54eef26064e9 /]# ls
Dockerfile bin boot crontab.index dev etc extract.sh home lib lib64 lost+found media mnt opt proc root run sbin srv startup.sh sys tmp usr var
[root@54eef26064e9 /]# exit
exit
# docker_logs pin
# INTO [pinchmail-alpha].
startup.sh start.
TZ='Asia/Tokyo'
'/etc/localtime' -> '/usr/share/zoneinfo/Asia/Tokyo'
:
MAILTO=""
* * * * * cd /root/pinchmail; ./pinchmail
#__END1__crontab.index__
startup.sh done.
で、しばらくは心穏やかに幸せに暮らしていたのだが、そのうち既製のコンテナを使う状況で、コンテナ内でnetstat等が実行できないことにイラつき始めた。「docker netstat」とググると、同じようにイラついている人は多いようだ。しかし、あまりロクな解法は見当たらない。コンテナ内で「cat /proc/net/tcp」して結果をデコードすりゃいいだけじゃん。そうなりゃ、docker_execに乗っけるしかないでしょ。コマンド名はdocker_netstatだな。
で、作り始めたら、容易には「PID/Program name」欄が出力できないことに気づいた。自分は「netstat」といえば「-anp」な人なので、それは出したい。しかし、それを実現するには大きくふたつの障害がある。(1)コンテナ内で使えるコマンドは非常に限定的、(2)「docker exec」を介するので、基本ワンライナーで情報を得る必要がある。というわけで、それをクリアする解法は「ls -lR /proc」なのであった。どうよ、この再現度。
# docker_netstat pin
# INTO [pinchmail-alpha].
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 127.0.0.11:39751 0.0.0.0:0 LISTEN -
tcp 0 0 0.0.0.0:110 0.0.0.0:0 LISTEN 55/dovecot
tcp 0 0 0.0.0.0:143 0.0.0.0:0 LISTEN 55/dovecot
tcp 0 0 0.0.0.0:25 0.0.0.0:0 LISTEN 130/master
tcp 0 0 172.18.0.3:110 172.18.0.2:60954 TIME_WAIT -
tcp6 0 0 :::110 :::0 LISTEN 55/dovecot
tcp6 0 0 :::143 :::0 LISTEN 55/dovecot
tcp6 0 0 :::25 :::0 LISTEN 130/master
tcp6 0 0 ::1:55838 ::1:110 TIME_WAIT -
udp 0 0 127.0.0.11:47826 0.0.0.0:0 CLOSE -
# docker exec pinchmail-alpha netstat -anp
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 127.0.0.11:39751 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:110 0.0.0.0:* LISTEN 55/dovecot
tcp 0 0 0.0.0.0:143 0.0.0.0:* LISTEN 55/dovecot
tcp 0 0 0.0.0.0:25 0.0.0.0:* LISTEN 130/master
tcp 0 0 172.18.0.3:110 172.18.0.2:60954 TIME_WAIT -
tcp6 0 0 :::110 :::* LISTEN 55/dovecot
tcp6 0 0 :::143 :::* LISTEN 55/dovecot
tcp6 0 0 :::25 :::* LISTEN 130/master
tcp6 0 0 ::1:55838 ::1:110 TIME_WAIT -
udp 0 0 127.0.0.11:47826 0.0.0.0:* -
とかなんとかやっていると、普通にコンテナ内でpsが実行できてもいいじゃねぇか、ということになって、やっぱり、docker_execに乗っけて、docker_psの誕生である。例によって、基本ワンライナーで情報を得る必要がある、というところで、かなり首をひねったが、内部でシェル芸をかませば、なんでもできることに気がついたのであった。どうよ、この再現度。
# docker_ps pin
# INTO [pinchmail-alpha].
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 - - - /sbin/init
root 43 1 0 - - - /usr/lib/systemd/systemd-journald
root 50 1 0 - - - /usr/lib/systemd/systemd-homed
root 53 1 0 - - - /usr/sbin/crond -n
root 55 1 0 - - - /usr/sbin/dovecot -F
dbus 56 1 0 - - - /usr/bin/dbus-broker-launch --scope system --audit
dbus 59 56 0 - - - dbus-broker --log 4 --controller 9 --machine-id a2b06096cef94996b35ec906189d7873 --max-bytes 536870912 --max-fds 4096 --max-matches 16384 --audit
dovecot 62 55 0 - - - dovecot/anvil
root 63 55 0 - - - dovecot/log
root 64 55 0 - - - dovecot/config
root 130 1 0 - - - /usr/libexec/postfix/master -w
postfix 132 130 0 - - - qmgr -l -t unix -u
dovecot 135 55 0 - - - dovecot/stats
root 146 1 0 - - - /usr/lib/systemd/systemd-userdbd
postfix 59937 130 0 - - - tlsmgr -l -t unix -u
dovecot 147675 55 0 - - - dovecot/auth
root 159689 146 0 - - - systemd-userwork
root 159697 146 0 - - - systemd-userwork
root 159706 146 0 - - - systemd-userwork
postfix 159723 130 0 - - - pickup -l -t unix -u
root 159730 55 0 - - - dovecot/auth -w
root 159738 55 0 - - - dovecot/auth -w
root 159740 0 0 - - -
# docker exec pinchmail-alpha ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 Dec02 ? 00:00:35 /sbin/init
root 43 1 0 Dec02 ? 00:01:15 /usr/lib/systemd/systemd-journald
root 50 1 0 Dec02 ? 00:00:29 /usr/lib/systemd/systemd-homed
root 53 1 0 Dec02 ? 00:00:08 /usr/sbin/crond -n
root 55 1 0 Dec02 ? 00:00:57 /usr/sbin/dovecot -F
dbus 56 1 0 Dec02 ? 00:00:04 /usr/bin/dbus-broker-launch --scope system --audit
dbus 59 56 0 Dec02 ? 00:00:11 dbus-broker --log 4 --controller 9 --machine-id a2b06096cef94996b35ec906189d7873 --max-bytes 536870912 --max-fds 4096 --max-matches 16384 --audit
dovecot 62 55 0 Dec02 ? 00:00:21 dovecot/anvil
root 63 55 0 Dec02 ? 00:00:23 dovecot/log
root 64 55 0 Dec02 ? 00:01:02 dovecot/config
root 130 1 0 Dec02 ? 00:00:04 /usr/libexec/postfix/master -w
postfix 132 130 0 Dec02 ? 00:00:00 qmgr -l -t unix -u
dovecot 135 55 0 Dec02 ? 00:00:35 dovecot/stats
root 146 1 0 Dec02 ? 00:00:05 /usr/lib/systemd/systemd-userdbd
postfix 59937 130 0 Dec10 ? 00:00:00 tlsmgr -l -t unix -u
dovecot 147675 55 0 08:29 ? 00:00:02 dovecot/auth
root 159689 146 0 23:26 ? 00:00:00 systemd-userwork
root 159697 146 0 23:27 ? 00:00:00 systemd-userwork
root 159706 146 0 23:28 ? 00:00:00 systemd-userwork
postfix 159723 130 0 23:30 ? 00:00:00 pickup -l -t unix -u
root 159730 55 0 23:31 ? 00:00:00 dovecot/auth -w
root 159738 55 0 23:32 ? 00:00:00 dovecot/auth -w
root 159820 0 0 23:32 ? 00:00:00 ps -ef
#!/usr/bin/env ruby
# coding: utf-8
class String
def to_ipaddr
r = self.scan(/.{8}/).map {|h8|
h8.scan(/../).map {|hh|
hh.to_i(16)
}.reverse
}.join('.')
self.size < 9 ? r : r.gsub(/(0\.){3,}0?/, '::')
end
end
states = [
'', 'ESTABLISHED', 'SYN_SENT', 'SYN_RECV', 'FIN_WAIT1', 'FIN_WAIT2',
'TIME_WAIT', 'CLOSE', 'CLOSE_WAIT', 'LAST_ACK', 'LISTEN', 'CLOSING',
]
$0 =~ /docker_(.+)/ and subcmd = $1
ps = `docker ps`
targets = []; ps.split(/\n/)[1..-1].each {|l|
(it = l.split(/\s+/).last) =~ /#{ARGV.last}/ and targets << it
}
if(targets.size == 0)
puts('# NOT MATCH...')
elsif(targets.size == 1)
def get_ps(target)
uids = {}; progs = {}; sockets = {}
IO.popen(['docker', 'exec', target, 'ls', '-lR', '/proc'], :err => [:child, :out]) {|io|
pid = nil; io.each {|line|
if(!pid and line =~ /^d.*?(\d+)$/)
uids[$1] = line.split(/\s+/)[2]
end
if(line =~ /^\/proc\/(\d+):/)
pid = $1
end
if(line =~ / exe -> (.+)/)
progs[pid] ||= []
progs[pid] << $1
end
if(line =~ / -> socket:\[(\d+)\]/)
sockets[$1] ||= []
sockets[$1] << pid
end
}
}
[uids, progs, sockets]
end
def get_ps2(target)
ppids = {}; cmdlines = {}
IO.popen(['docker', 'exec', target, '/bin/bash', '-c',
'ls /proc | grep -e "^[1-9]" | sed "s/\(.*\)/cat \/proc\/\\1\/status/" | sh 2>/dev/null'], :err => [:child, :out]) {|io|
pid = nil; io.each {|line|
line =~ /^Pid:\s*(\d+)/ and pid = $1
line =~ /^PPid:\s*(\d+)/ and ppids[pid] = $1
}
}
IO.popen(['docker', 'exec', target, '/bin/bash', '-c',
'ls /proc | grep -e "^[1-9]" | sed "s/\(.*\)/echo -n \\1:; cat \/proc\/\\1\/cmdline; echo/" | sh 2>/dev/null'], :err => [:child, :out]) {|io|
io.each {|line|
line =~ /^(\d+):(.*)/ and cmdlines[$1] = $2.gsub(/\u0000/, ' ')
}
}
[ppids, cmdlines]
end
puts('# INTO [%s].' % targets[0])
if(subcmd == 'exec')
# TODO
system('docker exec -it %s %s /bin/bash' % [ARGV[0..-2].join(' '), targets[0]])
elsif(subcmd == 'ps')
uids, progs, sockets = get_ps(targets[0])
ppids, cmdlines = get_ps2(targets[0])
puts('UID PID PPID C STIME TTY TIME CMD')
progs.keys.sort {|a, b|
a.to_i <=> b.to_i
}.each {|pid|
puts('%-8s %7d %7d %2d %5s %-8s %8s %s' % [uids[pid], pid, ppids[pid] || 0, 0, '-', '-', '-', cmdlines[pid]])
}
elsif(subcmd == 'netstat')
uids, progs, sockets = get_ps(targets[0])
puts('Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name')
['tcp', 'tcp6', 'udp', 'udp6'].each {|prot|
IO.popen(['docker', 'exec', targets[0], 'cat', '/proc/net/' + prot]) {|io|
io.each {|line|
ls = line.split(/[:\s]+/)
ls[1] =~ /^\d/ or next
laddr = ls[2].to_ipaddr + ':' + ls[3].to_i(16).to_s
faddr = ls[4].to_ipaddr + ':' + ls[5].to_i(16).to_s
prog = '-'; (it = sockets[ls[14]]) and prog = '%s/%s' % [it[0], progs[it[0]][0].split('/').last]
puts('%-5s %6d %6d %-23s %-23s %-11s %-s' % [prot, ls[8].to_i(16), ls[7].to_i(16), laddr, faddr, states[ls[6].to_i(16)], prog])
}
}
(it = $?.exitstatus and it == 0) or raise 'failed.'
}
else
system('docker %s %s %s' % [subcmd, ARGV[0..-2].join(' '), targets[0]])
end
else
puts('# MULTIPLE MATCH...')
puts(targets.join("\n"))
end
__END__