「ls: 引数行が長すぎます」を回避する

ls: 引数行が長すぎます

ファイルが大量にあるディレクトリをワイルドカードを使用してlsすると以下のように怒られる。

  • ダメな例
$ rm /hoge/fuga/??/*/*/hige/
ls: 引数が多すぎます 

エラーの文字列でググると、回避方法としてfindとxargsの組み合わせが一般的らしい。 http://moviesearch1.blog15.fc2.com/blog-entry-8.html

【例】
find /var/www/html/movie-search/cache/ -name "*.cash" -print0 | xargs -0 rm -rf

エラーはシェルがワイルドカードを展開してコマンドに渡す際に上限値を超える場合に発生する。

上記の例だとfindの-nameで渡しているワイルドカードはシェルでは解釈されず、findコマンドが展開を担当しているので、問題を回避できる。

「ダメな例」の場合はこの方法を使えない。なぜなら、ディレクトリ階層がネストしていて、それを-nameできれいに吸収できないからだ。 やるとして以下の様な汚い感じになるだろう。

  • 回避例1
find  /hoge/fuga/ -name "??/*/*/hige/" -print0 | xargs -0 rm -rf

直感的じゃないし、余計なディレクトリを探しに行くだろう。

更に調べると、どうやらechoとxargsの組み合わせで回避できるらしい。 http://x68000.q-e-d.net/~68user/unix/pickup?xargs

echo はシェルの内部コマンドのため ARG_MAX の制限を受けない。一方、xargs は ARG_MAX の値を把握していて、引数が ARG_MAX を越えそうになると

バッドノウハウ感があるが採用する。

echoとxargsの組み合わせで「ダメな例」を回避するには以下のようにする。

  • 回避例2
$ echo /hoge/fuga/??/*/*/hige/ | xargs rm -rf

回避例1にくらべてだいぶマシなのがわかる。

xargs -I{}を使うとき

上記の例のrmをmvに書き換えるともう一つの問題に直面する。

  • ダメな例2
$ echo /hoge/fuga/??/*/*/hige/ | xargs -I mv {} {}.bk
xargs: 引数行が長すぎます

上記ではうまくいっていたxargsだが、-I{}を使用した途端が引数が長すぎると文句を行ってきた。 これはいかに??

困ったらmanを読む。以下のように書いてある。 http://linuxjm.osdn.jp/html/GNU_findutils/man1/xargs.1.html

-I replace-str
    xargs が実行するコマンドに対してユーザが引き数 (すなわち initial-arguments) を指定したとき、その initial-arguments 中にある replace-str の部分すべてを、標準入力から読み込んだ名前で 置き換える。 なお、空白は、クォートされていない場合も、入力される項目の区切りには ならない。区切り記号は改行文字だけになるのだ。 -x と -L 1 が自動的に設定される。 

「区切り記号は改行文字だけになるのだ」 サラッと書いてあるが、驚きである。 echoはファイル名を空白区切りで返すので、すべてのファイル名を一つの文字列として扱うためエラーとなる。 手っ取り早く解決するには、空白を改行に書き換えればよい。

$ echo /hoge/fuga/??/*/*/hige/ | tr ' ' '\n' | xargs -I mv {} {}.bk

この一文だけ見ると、なぜtrがいるのかわからない謎シェルになってしまうこと請け合いである。

Postfixがきりの良い時間にアクセスすると遅い

Postfixがきりの良い時間にアクセスすると遅い

背景

ユーザから「cronがキックするスクリプトがメールを送るとPostfixからの応答が遅くタイムアウトが起きている」と報告された。

結論

nslcdのレスポンスが遅いことが原因だった。 きりの良い時間はLDAPのアクセスが多く、負荷が高まるためレスポンスが遅くなっている。 対象のPostfixは動作上LDAPが必要なく、nslcdがLDAPの応答がない場合にさっさとタイムアウトするようにすることで改善した。

調査

最初はmilter、saslauthdを疑っていたのだが、両者を外した状態で検証しても改善しない。 master.cf, main.cfのパラメタも変えてみたが改善しない。 回りくどいことはやめて、straceでプロセスを追ってみた。

$ ps auxww | grep master
root     12984  0.0  0.0 107452   948 pts/1    S+   16:07   0:00 grep master
$ sudo strace -tt -s 2048 -f -p 12984 -t > strace.20151105 2>&1 
1:35:01 clone(Process 9798 attached
[pid 12984] 21:35:01 clone(Process 9799 attached

9799と9788はmasterがcloneを実行して作成したプロセス。

[pid  9799] 21:35:01 open("/lib64/libnss_ldap.so.2", O_RDONLY) = 1

ldapライブラリを読み込んでいる

[pid  9799] 21:35:01 execve("/usr/libexec/postfix/smtpd", ["smtpd", "-n", "465", "-t", "inet", "-u", "-o", "stress=", "-s", "2", "-o", "smtpd_sasl_auth_enable=yes", "-o", "smtpd_sasl_security_options=noanonymous"], [/* 4 vars */]
 <unfinished ...>

9799はmasterからsmtpdに転身する。

9799だけに関して見ると以下のように1秒から14秒まで飛んでいる。

[pid  9799] 21:35:01 munmap(0x7fc9a8b3b000, 34862) = 0
[pid  9799] 21:35:01 socket(PF_FILE, SOCK_STREAM, 0) = 10
[pid  9799] 21:35:01 connect(10, {sa_family=AF_FILE, path="/var/run/nslcd/socket"}, 110) = 0
[pid  9799] 21:35:01 select(1024, NULL, [10], NULL, {9, 999999}) = 1 (out [10], left {9, 999995})
[pid  9799] 21:35:01 sendto(10, "\1\0\0\0\213\23\0\0\7\0\0\0postfix", 19, MSG_NOSIGNAL, NULL, 0) = 19
[pid  9799] 21:35:01 select(1024, [10], NULL, NULL, {59, 999999} <unfinished ...>
[pid  9799] 21:35:14 <... select resumed> ) = 1 (in [10], left {46, 803711})
[pid  9799] 21:35:14 read(10, "\1\0\0\0\213\23\0\0\3\0\0\0", 1024) = 12
[pid  9799] 21:35:14 close(10)          = 0

/var/run/nslcd/socketにデータを送りつけるところで止まっているように見える。 nslcdはLDAPでユーザ情報を引くときに通信するデーモンらしい。 http://arthurdejong.org/nss-pam-ldapd/nslcd.conf.5

Postfixという名前のユーザはローカルに存在するので、LDAPに聞きに行く必要は無いはずだがよくわからない。 /etc/nsswitch.conf上もfilesが優先されていた。

とりあえず、以下のようにnslcdのタイムアウト値を減らしたところ、レスポンスが改善した。

bind_timelimit 2
timelimit 3

ちょっと釈然としないが、よいとしよう。

キーボードってそろそろ進化するべきなんじゃないだろうか

スマホのフリックは好きだ。でもキーボードの入力速度にはかなわない。
いつでもキーボードを持ち運べて、どこでも使えるならキーボードを使うだろうけど、そうはいかない。
キーボードは、まずどこかに置かないと行けないし、幅を取る。

キーボードが現在の形に落ち着いてからしばらく立つんだけど、そろそろ進化するべきじゃないかな?

なんとなく検索していたらkeygloveというものを見つけた。makezine.jp

手袋型のキーボードと呼んだらいいのだろうか。
これならスキー場でも使うこともできるだろう。
ちょっとごてごてしているのがネックだ。

今後に期待。

手のひらキーボードなるものもあるらしいgigazine.net

発送がキーボードから抜けていない気もするけど、応用の幅は広そうだ。
脳に限らず、人体の信号をセンサーにできればいいのだろうけど。
神経から信号を取り、逆にフィードバックを神経に与えるようなもの。失敗すると痛そうだけど。


prosheet.jp
指輪デバイスもある。これはキーボドの代わりにはならないだろうなぁ。

IPの偽装

検証目的で接続元のIPを偽装したい場合にループバックデバイスIPアドレスを変更するという方法がある。
参照:http://thesimplesynthesis.com/post/how-to-spoof-your-ip-address-to-test-geolocation


$ sudo ifconfig lo 218.xxx.xxx.xxx
$ telnet 218.xxx.xxx.xxx 25
$ sudo less /var/log/maillog
Aug 24 17:59:03 dev001 postfix/smtpd[27156]: disconnect from unknown[218.xxx.xxx.xxx]
Aug 24 17:59:21 dev001 postfix/smtpd[27160]: connect from unknown[218.xxx.xxx.xxx]

dockerのAmbassadorパターンを理解する

前回前々回で「単一のコンテナの起動」と、「2台のコンテナの起動と接続」について、ある程度理解した。 今回は2台のホスト(VM)にまたがるコンテナの接続について試す。 2台のホストにまたがる場合は、--linkによる接続はできない。これはひとつのホストにのっているDockerの中でのみ使える機能だからである。

参照: Dockerコンテナ接続パターン (2014年冬)

まず定石はAmbassadorパターンというものらしい。 他のホストとの接続だけを担うプロキシ機能しか持たないコンテナで軽量なものらしい。 ほかにも、いろいろあるらしいが、ひとまず定石であるAmbassadorパターンを理解しよう。

ちゃんとした例を考えるのが面倒くさいので、 今回も前回に引き続きhttpdとclientを例に使う。

構成は以下

ホスト コンテナ名 役割 expose/publishするポート リンク
VM1 httpd apacheサーバ 80 なし
httpd-amb ambassador 8080(publish) httpdコンテナにリンク
VM2 httpdclient クライアント なし httpdclient-ambコンテナにリンク
httpd-amb ambassador 80(expose) なし
ホスト IP
VM1 192.168.11.11
VM2 192.168.11.12

構成図

VM1[ httpdclient --- 80/tcp ---> httpd-amb ] --- 8080/tcp --> VM2[ httpd-amb -- 80/tcp --> httpd]

VM1: apacheサーバ+ambassadorを構築する

apacheサーバコンテナ作成する

# docker run --privileged -d --expose 80 --name httpd centos:centos7 /sbin/init
# docker exec -it httpd /bin/bash
[root@a4522c59ed0c /]#
[root@a4522c59ed0c /]# yum install httpd -y
[root@a4522c59ed0c /]# systemctl enable httpd.service
[root@a4522c59ed0c /]# systemctl start httpd.service
[root@a4522c59ed0c /]# exit

Ambassadorコンテナを作成する

Ambassadorコンテナの作成はイメージを持ってきて、あとはhttpdコンテナにlinkするだけで他に設定は不要。超簡単。

# docker run -d --link httpd:httd-alias --name httpd-amb -p 8080:80 svendowideit/ambassador

これでAmbassadorが8080ポートで受けた接続はコンテナ内では80ポートで受けて、それをhttpdの80ポートへそのまま流すことができるようになる。

現状確認。2つコンテナが実行されている。

# docker ps
CONTAINER ID        IMAGE                            COMMAND                CREATED             STATUS              PORTS                NAMES
c06aaa97b55a        svendowideit/ambassador:latest   "\"/bin/sh -c 'env |   30 seconds ago      Up 29 seconds       0.0.0.0:80->80/tcp   httpd-amb           
9c4495ffddfd        centos:centos7                   "/sbin/init"           4 minutes ago       Up 4 minutes        80/tcp               httpd 
# telnet localhost 8080
GET /

VM2: httpクライアント+ambassadorを構築する

Ambassadorコンテナを作成する

docker run -d --name httpd_ambassador --expose 80 -e HTTPD_PORT_80_TCP=tcp://192.168.11.11:8080 svendowideit/ambassador

環境変数HTTPD_PORT_80_TCP=tcp://192.168.11.11:8080を渡しているのが肝だと思われる。 多分Ambassadorは渡された環境変数をパースして自分がプロキシすべきポートを判断する。(かなり適当な予想なので間違えっているかも。)

httpdクライアントを作成する

# docker run --privileged -d --link httpd_ambassador:httpd_ambassador-alias --name httpdclient centos:centos7 /sbin/init

現状確認。2つのコンテナが実行されている。

# docker ps
CONTAINER ID        IMAGE                            COMMAND                CREATED             STATUS              PORTS               NAMES
bcabc10a7506        centos:centos7                   "/sbin/init"           14 minutes ago      Up 14 minutes                           httpdclient         
e526511520fa        svendowideit/ambassador:latest   "\"/bin/sh -c 'env |   17 minutes ago      Up 17 minutes       80/tcp              httpd_ambassador

VM2: httpdクライアントからAmbassadorの80番ポートにアクセスしてWebページをゲットする。

準備が整ったので、ホストを隔てたhttpdへのアクセスができるか試す。

流れ - VM2のhttpdclientコンテナの中でbashを実行する - 環境変数からAmbassadorのIPアドレスを取得する - Ambassadorの80ポートへcurlでアクセスする

# docker exec -it httpdclient /bin/bash

[root@bcabc10a7506 /]# env
HTTPD_AMBASSADOR_ALIAS_PORT_80_TCP=tcp://172.17.0.26:80
HOSTNAME=bcabc10a7506
HTTPD_AMBASSADOR_ALIAS_PORT=tcp://172.17.0.26:80
HTTPD_AMBASSADOR_ALIAS_ENV_HTTPD_PORT_80_TCP=tcp://192.168.11.11:8080
HTTPD_AMBASSADOR_ALIAS_PORT_80_TCP_PORT=80
LS_COLORS=
HTTPD_AMBASSADOR_ALIAS_PORT_80_TCP_PROTO=tcp
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PWD=/
container_uuid=bcabc10a-7506-1b55-74a8-7c9f0d2ec7dc
SHLVL=1
HOME=/root
HTTPD_AMBASSADOR_ALIAS_NAME=/httpdclient/httpd_ambassador-alias
LESSOPEN=||/usr/bin/lesspipe.sh %s
HTTPD_AMBASSADOR_ALIAS_PORT_80_TCP_ADDR=172.17.0.26
_=/usr/bin/env

[root@bcabc10a7506 /]# curl -s http://172.17.0.26 | head -1

いけた! まぁあんまりAmbassadorのありがたみが無い感もある. どちらかというと、Ambassadorの使い方がわかっただけ。

docker --link --exposeの動きを理解する

dockerの--linkとかEXPOSEとか接続関係のコマンドがよくわからないので、動かしながら理解する。

参照: Dockerのネットワークの基礎

  • --link 他のコンテナのポートにつなぐ
  • --expose コンテナのポートを他のコンテナに晒す
  • --publish コンテナのポートをホストのポートにマッピングする

試しに2台のcentosのコンテナを作って、 一台にはapacheをインストールし、もう一台からそのapacheの80番ポートを叩いてみる。 --publishについては言及しない。

コンテナの名前は以下

コンテナ名 役割 exposeするポート リンク
httpd apacheサーバ 80 なし
httpdclient クライアント なし httpdコンテナにリンク

apacheコンテナを作成する。(1台目)

# docker run --privileged -d --expose 80 --name httpd centos:centos7 /sbin/init
# docker exec -it httpd /bin/bash
[root@a4522c59ed0c /]#
[root@a4522c59ed0c /]# yum install httpd -y
[root@a4522c59ed0c /]# systemctl enable httpd.service
[root@a4522c59ed0c /]# systemctl start httpd.service
[root@a4522c59ed0c /]# exit

一応確認

[root@centos001 ~]# docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
25fcc3bcc9b8        centos:centos7      "/sbin/init"        44 seconds ago      Up 43 seconds       80/tcp              httpd

ただのcentosを作成する。(2台目)

# docker run --privileged -d --link httpd:httpd-alias --name httpdclient centos:centos7 /sbin/init

--linkはコンテナ名:エイリアス名というフォーマットで指定する。

一応起動していることを確認する。2台動いている。

# docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
44e11aeaea7c        centos:centos7      "/sbin/init"        9 minutes ago       Up 9 minutes                            httpdclient         
25fcc3bcc9b8        centos:centos7      "/sbin/init"        14 minutes ago      Up 14 minutes       80/tcp              httpd     

作成されたコンテナの環境変数は以下のように調べられる。 この環境変数を使えば、このコンテナからapacheのポートを晒しているホストにアクセスする方法がわかる。

# docker exec -it httpdclient env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=44e11aeaea7c
HTTPD_ALIAS_PORT=tcp://172.17.0.20:80
HTTPD_ALIAS_PORT_80_TCP=tcp://172.17.0.20:80
HTTPD_ALIAS_PORT_80_TCP_ADDR=172.17.0.20
HTTPD_ALIAS_PORT_80_TCP_PORT=80
HTTPD_ALIAS_PORT_80_TCP_PROTO=tcp
HTTPD_ALIAS_NAME=/httpdclient/httpd-alias
container_uuid=44e11aea-ea7c-97ee-6c8f-e903929a981d
HOME=/root

以下のように環境変数経由でアクセスする。

[root@44e11aeaea7c /]# curl -s http://$HTTPD_ALIAS_PORT_80_TCP_ADDR:$HTTPD_ALIAS_PORT_80_TCP_PORT/ | head -1

これでコンテナ同士をつなぐことができた。

dockerを触ってみる

centosのイメージを持ってくる

公式に解説があるので、それを参考にする。 https://hub.docker.com/_/centos/

途中で公式が解決方法を提供している問題をひとつずつ踏みながら、理解しつつ進むので少しまどろっこしい方法で理解する。

以下のようなDockerfileを作成する。

# cat Dockerfile
FROM centos:7
MAINTAINER "you" 
ENV container docker
RUN yum -y swap -- remove fakesystemd -- install systemd systemd-libs
RUN yum -y update; yum clean all; \
(cd /lib/systemd/system/sysinit.target.wants/; for i in *; do [ $i == systemd-tmpfiles-setup.service ] || rm -f $i; done); \
rm -f /lib/systemd/system/multi-user.target.wants/*;\
rm -f /etc/systemd/system/*.wants/*;\
rm -f /lib/systemd/system/local-fs.target.wants/*; \
rm -f /lib/systemd/system/sockets.target.wants/*udev*; \
rm -f /lib/systemd/system/sockets.target.wants/*initctl*; \
rm -f /lib/systemd/system/basic.target.wants/*;\
rm -f /lib/systemd/system/anaconda.target.wants/*;
VOLUME [ "/sys/fs/cgroup" ]
CMD ["/usr/sbin/init"]

イメージをビルドする

Dockerfileからイメージをビルドする。

# docker build --rm -t local/c7-systemd .

作成されたことを確認する。

# docker images
REPOSITORY                          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
local/c7-systemd                    latest              55328d2ac5c8        6 minutes ago       229.4 MB

コンテナを起動する

次に、このイメージからコンテナを作成して、シェルを実行させる。 dockerにおけるコンテナは起動後に必ず一つのプロセスを動かす必要があるようだ。 シェルからapacheをインストールして、起動してみる。

# docker run -it local/c7-systemd  /bin/bash
[root@4747003b4111 /]#
[root@4747003b4111 /]# yum install httpd
[root@4747003b4111 /]# systemctl start httpd.service
Failed to get D-Bus connection: No connection to service manager.
[root@42ebe1f0dcb4 /]# ps auxww
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.2  0.1  11744  1872 ?        Ss   05:09   0:00 /bin/bash
root        18  0.0  0.1  19764  1228 ?        R+   05:09   0:00 ps auxww

コンテナのシェルのプロンプトが現れる。 「-it」はstdinを有効にするために必要らしい。 httpdをインストールして起動してみたが、エラーが出る。 psコマンドを見てもシェルから起動したプロセスしか現れない。これがコンテナか。

Dockerfileを以下のように書き換えて再実行。 apacheをインストールして、systemctlでhttpを起動してinitを実行する。

# cat Dockerfile
FROM centos:7
MAINTAINER "you" 
ENV container docker
RUN yum -y swap -- remove fakesystemd -- install systemd systemd-libs
RUN yum -y update; yum clean all; \
(cd /lib/systemd/system/sysinit.target.wants/; for i in *; do [ $i == systemd-tmpfiles-setup.service ] || rm -f $i; done); \
rm -f /lib/systemd/system/multi-user.target.wants/*;\
rm -f /etc/systemd/system/*.wants/*;\
rm -f /lib/systemd/system/local-fs.target.wants/*; \
rm -f /lib/systemd/system/sockets.target.wants/*udev*; \
rm -f /lib/systemd/system/sockets.target.wants/*initctl*; \
rm -f /lib/systemd/system/basic.target.wants/*;\
rm -f /lib/systemd/system/anaconda.target.wants/*;
VOLUME [ "/sys/fs/cgroup" ]
FROM local/c7-systemd
RUN yum -y install httpd; yum clean all; systemctl enable httpd.service
EXPOSE 80
CMD ["/usr/sbin/init"]

先ほどと同様の方法で起動するが、失敗する。

# docker run -it local/c7-systemd
Usage of loopback devices is strongly discouraged for production use. Either use `--storage-opt dm.thinpooldev` or use `--storage-opt dm.no_warn_on_loop_devices=true` to suppress this warning.
                                                                                                                                                                                                systemd 208 running in system mode. (+PAM -LIBWRAP -AUDIT +SELINUX -IMA +SYSVINIT -LIBCRYPTSETUP -GCRYPT -ACL -XZ)
Detected virtualization 'docker'.

Welcome to CentOS Linux 7 (Core)!

Initializing machine ID from container UUID.
No control group support available, not creating root group.
Please start this container with '-v /sys/fs/cgroup:/sys/fs/cgroup'
Failed to allocate manager object: Operation not permitted

エラーが出て停止する。cgroupに関するエラーのようだ。

これは以下のようにコンテナを実行すると回避できる。

# docker run --privileged -ti -v /sys/fs/cgroup:/sys/fs/cgroup:ro -p 80:80 local/c7-systemd

今度はエラーが出ない。起動時の表示が出たまま 以下のようにすると起動していることを確認できる。

# docker ps
CONTAINER ID        IMAGE                     COMMAND             CREATED             STATUS              PORTS                NAMES
0c3958d6a846        local/c7-systemd:latest   "/usr/sbin/init"    4 minutes ago       Up 4 minutes        0.0.0.0:80->80/tcp   adoring_almeida

変な名前「adoring_almeida」が付いている。なんだろこれ。

試しにホストのlocalhostの80版ポートにアクセスしてみる。

# telnet localhost 80
Trying ::1...
Connected to localhost.
Escape character is '^]'.
GET /

index.htmlが返ってくる。

GETできたので成功だ。

あとは終了して、コンテナを終了しておく。

# docker stop adoring_almeida 
adoring_almeida

他の方法

以下のようにすれば、もっと簡単に検証できらしい。 参照: CentOS 7のDockerコンテナ内でsystemdを使ってサービスを起動する

$ sudo docker run --privileged -d -p 80:80 --name httpd centos:centos7 /sbin/init
$ sudo docker exec -it httpd /bin/bash
# yum install httpd -y
# systemctl enable httpd.service
# systemctl start httpd.service
# exit
$ curl -s http://localhost/ | head -n 1

docker runとdocker execの違いを理解していないため、よくわからなくなってしまった。 以下がそれぞれのコマンドの役割である。

  • docker run コンテナを起動する
  • docker exec 実行中のコンテナで新しいコマンドを実行する

docker runで起動中のコンテナ上で新しいコマンドを起動したければdocker execを使う。

うえのほうで実行していたapacheのインストールと起動が失敗した理由は単純にDockerfileから起動したものに対してシェルを起動したつもりになっていて実はそうなっていなかったのかも。