perlワンライナー集計処理
以下の様なテキストを入力として受け、出力を合計値にしたい。
a 10000 b c 33 b 100 a 3993
出力
c 33 a 13993 b 100
シェルスクリプトで実装する方法も考えたが、なんだか面倒なので、
なれないPerlワンライナーにチャレンジ
以下を参考にした。
「Perlワンライナー覚書」tossh
http://qiita.com/tossh/items/f8d448c0c039f68c0ea3
結論としては以下とすればよい。
$ cat << EOF | perl -anle '$count{$F[0]}+=$F[1]; END{foreach(keys(%count)){print "$_ $count{$_}"}}' > a 10000 > b > c 33 > b 100 > a 3993 > EOF c 33 a 13993 b 100
「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の中でのみ使える機能だからである。
まず定石は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
これでコンテナ同士をつなぐことができた。