SVX日記

2004|04|05|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|04|05|06|07|08|09|10|11|12|
2008|01|02|03|04|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|09|10|11|12|
2011|01|02|03|04|05|06|07|08|09|10|11|12|
2012|01|02|03|04|05|06|07|08|09|10|11|12|
2013|01|02|03|04|05|06|07|08|09|10|11|12|
2014|01|02|03|04|05|06|07|08|09|10|11|12|
2015|01|02|03|04|05|06|07|08|09|10|11|12|
2016|01|02|03|04|05|06|07|08|09|10|11|12|
2017|01|02|03|04|05|06|07|08|09|10|11|12|
2018|01|02|03|04|05|06|07|08|09|10|11|12|
2019|01|02|03|04|05|06|07|08|09|10|11|12|
2020|01|02|03|04|05|06|07|08|09|10|11|12|
2021|01|02|03|04|05|06|07|08|09|10|11|12|
2022|01|02|03|04|05|06|07|08|09|10|11|12|
2023|01|02|03|04|05|06|07|08|09|10|11|12|
2024|01|02|03|04|

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.

  ちなみに、docker_logsはdocker_execのシンボリックリンクである。スクリプト内で自らのスクリプト名を見て処理を替えている。

  で、しばらくは心穏やかに幸せに暮らしていたのだが、そのうち既製のコンテナを使う状況で、コンテナ内で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

  で、以下がdocker_execのコードである。さっき作ったトコなんで、考慮不足も多いだろうが。まずは、バージョン0.1ということで。

#!/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__

  というわけで、自画自賛するようではあるが、ちょっと今回のこれは、え!? いったい、どうやってんの!? と思わせるような、魔術的なコードになったのではないかと思う。ふはははは。