Linuxサーバにログインしたらいつもやっているオペレーション

主にアプリケーション開発者向けに、Linuxサーバ上の問題を調査するために、ウェブオペレーションエンジニアとして日常的にやっていることを紹介します。 とりあえず調べたことを羅列しているのではなく、本当に自分が現場で使っているものだけに情報を絞っています。 普段使っているけれども、アプリケーション開発者向きではないものはあえて省いています。

MySQLやNginxなど、個別のミドルウェアに限定したノウハウについては書いていません。

ログインしたらまず確認すること

他にログインしている人がいるか確認(w)

wコマンドにより現在サーバにログインしてるユーザ情報をみれます。

$ w
 11:49:57 up 72 days, 14:22,  1 user,  load average: 0.00, 0.01, 0.05
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
y_uuki   pts/0    x.x.x.x 11:49    1.00s  0.02s  0.00s w

再起動などシステム影響がありうるオペレーションをする場合、同僚が他の作業をしていないかどうかをチェックします。

サーバの稼働時間の確認 (uptime)

サーバが前回再起動してから現在までの稼働している時間をみれます。下記の例の場合、72日間稼働しています。

$ uptime
 11:55:39 up 72 days, 14:28,  1 user,  load average: 0.00, 0.02, 0.05

DOWNアラートがきたときに、このコマンドをよく打ちます。DOWNアラートが来ていても、実際にOSが再起動していないということはよくあります。 これは監視システムと対象ホスト間のネットワーク不調が原因で、DOWNアラートが来ることがあるためです。 AWS EC2のようなVM環境の場合、ハイパーバイザ側のネットワークスタック不調などにより、監視システムと疎通がなくなるということがありえます。

uptimeコマンドにより、前回のシャットダウンから現在までの経過時間を知れます。上記の例の場合、72日間ですね。 OSが再起動している場合、MySQLサーバのようなデータベースのデータが壊れている可能性があるので、チェックする必要があります。

プロセスツリーをみる (ps)

サーバにログインしたときに必ずと言っていいほどプロセスツリーを確認します。 そのサーバで何が動いているのかをひと目で把握できます。

ps auxf

ps コマンドのオプションに何を選ぶかはいくつか流派があるようです。自分はずっと auxf を使っています。 aux でみてる人もみたりしますが、 f をつけるとプロセスの親子関係を把握しやすいのでおすすめです。 親子関係がわからないと、実際には同じプロセスグループなのに、同じプロセスが多重起動していておかしいと勘違いしやすいです。

...
root     25805  0.0  0.1  64264  4072 ?        Ss    2015   0:00 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;
nginx    25806  2.6  0.7  70196 15224 ?        S     2015 2691:21  \_ nginx: worker process
nginx    25807  2.7  0.8  71700 16416 ?        S     2015 2725:39  \_ nginx: worker process
nginx    25808  2.7  0.7  70672 15336 ?        S     2015 2725:30  \_ nginx: worker process
nginx    25809  2.7  0.7  71236 15976 ?        S     2015 2709:11  \_ nginx: worker process
nginx    25810  2.6  0.9  74084 18888 ?        S     2015 2646:32  \_ nginx: worker process
nginx    25811  2.6  0.6  69296 14040 ?        S     2015 2672:49  \_ nginx: worker process
nginx    25812  2.6  0.8  72932 17564 ?        S     2015 2682:30  \_ nginx: worker process
nginx    25813  2.6  0.7  70752 15468 ?        S     2015 2677:45  \_ nginx: worker process
...

pstree でも可。ただし、プロセスごとのCPU利用率やメモリ使用量をみることも多いので、ps をつかっています。

NICやIPアドレスの確認 (ip)

ifconfig コマンドでもよいですが、ifconfig は今では非推奨なので、 ip コマンドを使っています。 ipコマンドはタイプ数が少なくて楽ですね。

$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 00:16:3e:7d:0d:f9 brd ff:ff:ff:ff:ff:ff
    inet 10.0.0.10/22 brd z.z.z.z scope global eth0
       valid_lft forever preferred_lft forever
    inet 10.0.0.20/22 scope global secondary eth0
       valid_lft forever preferred_lft forever

上記の出力例ではよくみているとセカンダリIPがついています。セカンダリIPがついているということは、なんらかの特殊な役割をもつホストである可能性が高いですね。 特にVIPを用いた冗長構成をとっている場合、VIPがついているならばマスタであると判断できます。 社内では、eth0に対するセカンダリIPや、eth1にプライマリIPをVIPとして利用することが多いです。

このような事情もあり、サーバにログインするとIPアドレスのチェックを癖でやってしまっています。

ファイルシステムの確認(df)

ファイルシステムの容量確認は df コマンドを使います。-T をつけると、ファイルシステムの種別を確認できます。-h をつけると、サイズ表記がヒューマンリーダブルになります。

$ df -Th
Filesystem                                             Type      Size  Used Avail Use% Mounted on
rootfs                                                 rootfs     20G  2.4G   17G  13% /
udev                                                   devtmpfs   10M     0   10M   0% /dev
tmpfs                                                  tmpfs     3.0G  176K  3.0G   1% /run
/dev/disk/by-uuid/2dbe52e8-a50b-45d9-a2ee-2c240ab21adb ext4       20G  2.4G   17G  13% /
tmpfs                                                  tmpfs     5.0M     0  5.0M   0% /run/lock
tmpfs                                                  tmpfs     6.0G     0  6.0G   0% /run/shm
/dev/xvdf                                              ext4     100G   31G    69G   4% /data/redis

dfコマンドにより、ディスク容量以外にも、いくつかわかることがあります。

例えば、MySQLのようなデータをもつストレージサーバは、本体とは別の専用のディスクデバイスをマウントして使う(EC2のEBSボリュームやFusion-IOの ioDriveなど)ことがあります。

上記の出力例の場合、/data/redis に着目します。前述の「プロセスツリーをみる」により、Redisが動いていることもわかるので、RedisのRDBやAOFのファイルが配置されていると想像できます。

負荷状況確認

次は、Linuxサーバの負荷を確認する方法です。そもそも負荷とは何かということについて詳しく知りたい場合は、「サーバ/インフラを支える技術」の第4章を読むとよいです。

Mackerelのような負荷状況を時系列で可視化してくれるツールを導入していても、以下に紹介するコマンドが役に立つことは多いと思います。 秒単位での負荷の変化やCPUコアごと、プロセスごとの負荷状況など、可視化ツールで取得していない詳細な情報がほしいことがあるからです。

Mackerelである程度あたりをつけて、サーバにログインしてみて様子をみるというフローが一番多いです。

top

top -c をよく打ってます。 -c をつけると、プロセスリスト欄に表示されるプロセス名が引数の情報も入ります。(psコマンドでみれるのでそっちでも十分ですが)

さらに重要なのが、top 画面に遷移してから、 キーの 1 をタイプすることです。1 をタイプすると、各CPUコアの利用率を個別にみることができます。学生時代のころから当たり前に使ってたので、たまにご存知ない人をみつけて、意外に思ったことがあります。(mpstat -P ALL でもみれますが)

かくいう自分も これを書きながら、top の man をみてると知らない機能が結構あることに気づきました。

top - 16:00:24 up 22:11,  1 user,  load average: 1.58, 1.43, 1.38
Tasks: 131 total,   2 running, 129 sleeping,   0 stopped,   0 zombie
%Cpu0  : 39.7 us,  4.1 sy,  0.0 ni, 48.6 id,  0.3 wa,  0.0 hi,  6.9 si,  0.3 st
%Cpu1  : 24.4 us,  1.7 sy,  0.0 ni, 73.9 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu2  : 14.7 us,  1.7 sy,  0.0 ni, 83.7 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu3  :  0.0 us,  2.0 sy, 98.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu4  :  3.3 us,  0.7 sy,  0.0 ni, 96.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu5  :  2.0 us,  0.3 sy,  0.0 ni, 97.7 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu6  :  2.3 us,  0.3 sy,  0.0 ni, 97.3 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu7  :  0.7 us,  0.3 sy,  0.0 ni, 99.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem:   7199160 total,  5764884 used,  1434276 free,   172496 buffers
KiB Swap:        0 total,        0 used,        0 free,  5161520 cached

  PID USER      PR  NI  VIRT  RES  SHR S  %CPU %MEM    TIME+  COMMAND
16659 root      39  19  8612  632  440 R 100.3  0.0   0:12.94 /bin/gzip
 2343 nginx     20   0 60976  14m 1952 S  23.2  0.2 112:48.51 nginx: worker process
 2328 nginx     20   0 61940  15m 1952 S  19.9  0.2 111:49.12 nginx: worker process
 2322 nginx     20   0 61888  15m 1952 S  19.3  0.2 113:44.95 nginx: worker process
 2324 nginx     20   0 61384  14m 1952 S  16.6  0.2 113:30.52 nginx: worker process
 2340 nginx     20   0 61528  14m 1952 S  11.0  0.2 114:02.36 nginx: worker process
 ...

上記の出力例は結構おもしろいタイミングを示していて、下のプロセスリストをみると、 /bin/gzip がCPU 100%使いきっています。 これはlogrotateがアクセスログを圧縮している様子を表しています。 上段のCPU利用率欄をみると、Cpu3 が 0.0 idとなっています。 id は idle の略であり、 id が 0% ということはCpu3を使いきっているということです。これらの状況から gzip プロセスが Cpu3 を使いきっているということが推測できます。

CPU利用率欄には他にも、us, sy、ni、wa、hi、si、stがあります。(カーネルバージョンにより多少異なります) これらは、CPU利用率の内訳を示しています。よくみるのは、us、sy、waの3つですね。

  • us(user): OSのユーザランドにおいて消費されたCPU利用の割合。userが高いということは、アプリケーション(上記の場合nginx)の通常の処理にCPU処理時間を要していることです。
  • sy(system): OSのカーネルランドにおいて消費されたCPU利用の割合。systemが高い場合は、OSのリソース(ファイルディスクリプタやポートなど)を使いきっている可能性があります。カーネルのパラメータチューニングにより、負荷を下げることができるかもしれません。fork 回数が多いなど、負荷の高いシステムコールをアプリケーションが高頻度で発行している可能性があります。straceでより詳細に調査できます。
  • wa(iowait): ディスクI/Oに消費されたCPU利用の割合。iowaitが高い場合は、次の iostat でディスクI/O状況をみましょう。

基本は各CPUコアのidleをざっと眺めて、idle が 0 に近いコアがないかを確認し、次に iowait をみてディスクI/Oが支配的でないかを確認し、user や system をみます。

ちなみに、自分は si (softirq) についてこだわりが強くて、 Linuxでロードバランサやキャッシュサーバをマルチコアスケールさせるためのカーネルチューニング - ゆううきブログ のような記事を以前書いています。

iostat

ディスクI/O状況を確認できます。-d でインターバルを指定できます。だいたい5秒にしています。ファイルシステムのバッファフラッシュによるバーストがあり、ゆらぎが大きいので、小さくしすぎないことが重要かもしれません。 -x で表示する情報を拡張できます。

$ iostat -dx 5
Linux 3.10.23 (blogdb17.host.h)     02/18/16    _x86_64_    (16 CPU)

Device:         rrqm/s   wrqm/s     r/s     w/s    rkB/s    wkB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
xvda              0.00     3.09    0.01    3.25     0.13    36.27    22.37     0.00    1.45    1.59    1.45   0.52   0.17
xvdb              0.00     0.00    0.00    0.00     0.00     0.00     7.99     0.00    0.07    0.07    0.00   0.07   0.00
xvdf              0.01     7.34   49.36   33.05   841.71   676.03    36.83     0.09    8.08    2.68   16.13   0.58   4.80

Device:         rrqm/s   wrqm/s     r/s     w/s    rkB/s    wkB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
xvda              0.00     3.20    0.40    2.80     2.40    30.40    20.50     0.00    0.25    0.00    0.29   0.25   0.08
xvdb              0.00     0.00    0.00    0.00     0.00     0.00     0.00     0.00    0.00    0.00    0.00   0.00   0.00
xvdf              0.00     5.00  519.80    4.00  8316.80    96.00    32.12     0.32    0.61    0.60    1.40   0.52  27.36

iostat コマンドは癖があり、1度目の出力はディスクデバイスが有効になってから現在まのの累積値になります。 現在の状況を知る場合は、2度目以降の出力をみます。

自分の場合、IOPS (r/s、w/s)と%utilに着目することが多いです。 上記の場合、r/s が519と高めで、%util が 27%なので、そこそこディスクの読み出し負荷が高いことがわかります。

netstat / ss

netstat はネットワークに関するさまざまな情報をみれます。 TCPの通信状況をみるのによく使っています。

-t でTCPの接続情報を表示し、 -n で名前解決せずIPアドレスで表示します。-n がないと連続して名前解決が走る可能性があり、接続が大量な状況だとつまって表示が遅いということがありえます。(-n なしでも問題ないことも多いので難しい)

-l でLISTENしているポートの一覧をみれます。下記の場合、LISTENしているのは 2812, 5666, 3306, 53549, 111, 49394, 22, 25 ですね。

$ netstat -tnl
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State
tcp        0      0 0.0.0.0:2812            0.0.0.0:*               LISTEN
tcp        0      0 0.0.0.0:5666            0.0.0.0:*               LISTEN
tcp        0      0 0.0.0.0:3306            0.0.0.0:*               LISTEN
tcp        0      0 0.0.0.0:53549           0.0.0.0:*               LISTEN
tcp        0      0 0.0.0.0:111             0.0.0.0:*               LISTEN
tcp        0      0 0.0.0.0:49394           0.0.0.0:*               LISTEN
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN
tcp        0      0 0.0.0.0:25              0.0.0.0:*               LISTEN
tcp6       0      0 :::54282                :::*                    LISTEN
tcp6       0      0 :::111                  :::*                    LISTEN
tcp6       0      0 :::22                   :::*                    LISTEN
tcp6       0      0 :::53302                :::*                    LISTEN
tcp6       0      0 :::25                   :::*                    LISTEN

TCPの全部のステートをみるには -a を指定します。 -o はTCP接続のタイマー情報、 -pはプロセス名の表示 (-p には root権限が必要) ができます。

$ sudo netstat -tanop
...
tcp        0      0 10.0.0.10:3306         10.0.0.11:54321        ESTABLISHED 38830/mysqld keepalive (7190.69/0/0)
tcp        0      0 10.0.0.10:3306         10.0.0.12:39150       ESTABLISHED 38830/mysqld keepalive (7157.92/0/0)
tcp        0      0 10.0.0.10:3306         10.0.0.13:49036         TIME_WAIT  38830/mysqld timewait (46.03/0/0)
tcp        0      0 10.0.0.10:3306         10.0.0.14:41064         ESTABLISHED 38830/mysqld keepalive (7223.46/0/0)
tcp        0      0 10.0.0.10:3306         10.0.0.15:34839        ESTABLISHED 38830/mysqld keepalive (7157.92/0/0)
...

一番、よくみるのは、このホストはどのホストから接続されているかです。 なぜか本番サーバなのに、ステージングサーバから接続されているというようなことがわかったりすることもあります。

他にも、単にやたらと接続が多いなとかざっくりした見方もします。そのときに、TCPのステートでESTABLISHED以外がやたらと多くないかなどをみたりします。

netstat は非推奨なので、ss コマンドをつかったほうがよいようです。 ただし、自分の場合、ss コマンドの出力の余白の取り方があまり好きではないので、netstat をつかっています。

ログ調査

いうまでもなくログ調査は重要です。 ログをみるためには、OSや、各種ミドルウェア、アプリケーションが吐くログがどこに履かれているのかを知る必要があります。

基本的には /var/log 以下を眺めてそれっぽいものをみつけて tail してみます。

/var/log/messages or /var/log/syslog

まずはここを見ましょう。カーネルやOSの標準的なプロセスのログをみることができます。他にもcron実行するコマンドの標準出力や標準エラー出力を明示的に logger にパイプしている場合などはここにログが流れます。

/var/log/secure

ssh 接続の情報がみれます。他の人がssh接続しているのに接続できない場合、ここに吐かれているログをみると原因がわかることがあります。

/var/log/cron

cronが実行されたかどうかがわかります。ただし、cronが実行したコマンドの標準出力または標準エラー出力が /var/log/cron に出力されるわけではなく、あくまでcronのスケジューラが動いたかどうかがわかるだけです。cronが実行したコマンドの標準出力または標準エラー出力はどこに出力されるか決まっているわけではなく、crontab内でloggerコマンドにパイプしたり、任意のログファイルにリダイレクトしたりすることになります。

/var/log/nginx, /var/log/httpd, /var/log/mysql

ミドルウェアのログは /var/log/{ミドルウェア名} 以下にあることが多いです。 特によくみるのはリバースプロキシのアクセスログやDBのスロークエリログですね。

自分が開発しているシステムのログの位置は確認しておいたほうがよいです。

/etc

/var/log 以下にログを吐くというのは強制力があるものではないので、ログがどこにあるのか全くわからんということがあります。

ログファイルのパスは設定ファイルに書かれていることもあります。 設定ファイルは /etc 以下にあることが多いので、 /etc/{ミドルウェア名} あたりをみて、設定ファイルの中身を cat してログファイルのファイルパスがないかみてみましょう。

lsof

/etcをみてもわからんというときは最終手段で、lsofを使います。 ps や top でログをみたいプロセスのプロセスIDを調べて、lsof -p <pid> を打ちます。 そのプロセスが開いたファイルディスクリプタ情報がみえるので、ログを書き込むためにファイルを開いていれば、出力からログのファイルパスがわかります。

他には例えば、daemontools を使っていると、 /service 、もしくは /etc/service 以下に multilog が吐かれているなど、使用しているスーパーバイザによっては、特殊なディレクトリを使っている可能性があります。

関連

あとがき

ここに紹介した内容はあくまでy_uukiが普段やっていることであり、どんな状況でも通用するようなベストプラクティスではありません。 むしろ、自分はこうやってるとか、こっちのほうが便利だということがあれば教えてほしいです。

オペレーションエンジニアは息を吸うように当たり前にやっているのに、意外とアプリケーションエンジニアはそれを知らない、ということはよくあることだと思います。オペレーションエンジニア同士でも、基本的過ぎてノウハウを共有していなかったということもあるのではでしょうか。

知ってる人は今さら話題にするのもなとかみんな知ってるでしょとか思いがちです。しかし、これだけ毎日のように新しい話題がでてくると、基本ではあっても、意外と見落としているノウハウは結構あると思います。見落としていた話題にキャッチアップできるように、新卒研修の時期にあわせて定期的に話題になるとよいですね。

昨今は、No SSHと呼ばれるように、サーバにSSHしないという前提で、インフラを構築する発想があります。 少なくとも現段階では、No SSHは一切サーバにSSH(ログイン)しないという意味ではなく、あくまでサーバ投入や退役など、通常のオペレーションをするときにSSHしないという意味だと捉えています。 もしくは、Herokuのユーザになり、サーバ側の問題はなるべくPaaS事業者に任せるということかもしれません。

したがって、サーバにログインして問題調査するという形はまだしばらくは続くでしょう。

異常を発見した場合、より深く問題に踏み込むために、straceやgdbなどを駆使するという先のオペレーションの話もあります。そのうち書きたいと思います。

Keepalivedのシンタックスチェッカ「gokc」を作った

Keepalivedのシンタックスチェッカ「gokc」をGo言語で書きました。

github.com

執筆時点でのKeepalived最新版であるバージョン1.2.19まで対応していることと、include文に対応していることがポイントです。

使い方

https://github.com/yuuki/gokc/releases からバイナリをダウンロードします。OSXでHomebrewを使っていれば、

$ brew tap yuuki/gokc
$ brew install gokc

でインストールできます。

gokcコマンドを提供しており、-f オプションで設定ファイルのパスを指定するだけです。

gokc -f /path/to/keepalived.conf
gokc: the configuration file /etc/keepalived/keepalived.conf syntax is ok

シンタックスエラーのある設定ファイルを読み込むと、以下のようなエラーで怒られて、exit code 1で終了します。

gokc -f /path/to/invalid_keepalived.conf
./keepalived/doc/samples/keepalived.conf.track_interface:7:14: syntax error: unexpected STRING

なぜ作ったか

KeepalivedはIPVSによるロードバランシングと、VRRPによる冗長化を実現するためのソフトウェアです。 KeepalivedはWeb業界で10年前から使われており、はてなでは定番のソフトウェアです。 社内の多くのシステムで導入されており、今なお現役で活躍しています。

KeepalivedはNginxやHAProxy同様に、独自の構文を用いた設定ファイルをもちます。

  • しかし、Keepalived本体は構文チェックをうまくやってくれず、誤った構文で設定をreloadさせると、正常に動作しなくなることがありました。

そのため、これまでHaskellで書かれた kc というツールを使って、シンタックスチェックしていました。 initスクリプトのreloadで、kcによるシンタックスチェックに失敗するとreloadは即中断されるようになっています。

ところが、Haskellを書けるメンバーがいないので、メンテナンスができず、Keepalivedの新機能に対応できていないという問題がありました。 (Haskell自体がこのようなものを書くのに向いているとは理解しているつもりです。) さらに、kcについてはビルドを成功させるのが難しいというのもありました。Re: keepalived.confのシンタックスチェックツール「keepalived-check」「haskell-keepalived 」が凄い! - maoeのブログ

さすがに、Keepalivedの新しい機能を使うためだけに、Haskellを学ぶモチベーションがわかなかったので、Go言語とyaccで新規にgokcを作りました。 Go言語はインフラエンジニアにとって馴染みやすい言語だと思っています。 yaccは構文解析の伝統的なツールなので、情報系の大学で習っていたりすることもあります(僕は習わなかったけど、概念は習った)。

ちなみに、C言語+flex+yacc版のシンタックスチェッカである ftp://ftp.artech.se/pub/keepalived/ というものがあります。 新しい構文には対応しているのですが、include未対応だったり、動いてない部分が結構あるので、参考にしつつも一から作りました。

実装

シンタックスのチェックだけであれば、コンパイラのフェーズのうち、字句解析と簡単な構文解析だけで済みました。 「簡単な」と言ったのは、構文解析フェーズで、抽象構文木を作らなくて済んだということです。

一般に字句解析器は、自分で書くか、Flexのような字句解析器の自動生成ツールを使います。 後者の実装として、自分の知る限り、Go言語にはgolexnex があります。

ただし、include文のような字句解析をそこそこ複雑にする構文があるため、柔軟に書けたほうがよかろうということで自分で書くことにしました。 といっても、スキャナ部分はGo言語自体のスキャナであるtext/scannerを流用しました。 Go言語用のスキャナですが、多少カスタマイズできる柔軟性があるので、ユーザ定義の言語の字句解析器として利用できます。 Rational Number Calculator in Go を参照。

構文解析にはパーサジェネレータであるyaccを使いました。 yaccのGo版は標準でgo tool yaccがあります。 goyaccについて詳しくは、goyaccで構文解析を行う - Qiitaを参照してください。

多少面倒だったのはinclude文の対応です。 include対応とはつまり、字句解析器において、別の設定ファイルを開いて、また元の設定ファイルに戻るというコンテキストの切り替えをしつつ、トークンを呼び出し元の構文解析器に返すことが求められます。

字句解析器から構文解析器へトークンを渡す構造をどうするかが問題でした。 逐次的にトークンを構文解析器へ返すのを諦めて、一旦末尾まで字句解析した結果をメモリにすべてのせて、構文解析器から順に読ませるみたいなこともできました。

それでもよかったんですが、Rob Pikeの Lexical Scanning in Go の資料に、goroutineとchannelを利用して、字句解析器を作る方法が書かれており、この手法を部分的に真似てみました。

具体的には、字句解析を行うgoroutineと、構文解析を行うgoroutine(メインのgo routine)が2つがあり、字句解析goroutineが構文解析goroutineにemitetr channelを通じて、トークンを受け渡すという構造にして解決しました。 channelをキューとして扱うようなイメージです。

include文のもつ複雑さに対して、そこそこシンプルに書けたような気はしています。

参考

2015年の心に残った技術エントリ

1年分の自分のはてなブックマークを見直した。 およそ 2,000 URLのエントリの中から、特に感銘を受けたり、記憶に残ったエントリを紹介したい。 2015年にブクマしたというだけで、必ずしも2016年に公開されたエントリばかりではないことに注意。

エントリ

Scalable Deployments

一昨年のRubyKaigiの講演スライド。 スクリプト言語のtar ballデプロイをみたのはこれが初めてだった。 大量のアプリケーションサーバに対するsshが遅いもしくは失敗する問題を、Serfを利用したpull型デプロイにより解決しているところがすごいアイデアだと思う。

Advanced Techinic for OS upgradeing in 3 minutes

昨年のYAPC::Asiaで直接拝聴したトーク。 前述のtar ballデプロイにも触れられている。 No sshという思想を軸に、目の前のアーキテクチャを変えずに確実に自動化していくという方法論が蒙を啓くようだった。

最近は、色濃い思想をもつ飛び道具系のツールがそこら中に飛び交っている。 いきなりそれらのツールを導入しようとした結果、自分も含めて、振り回されてしまう人たちにとって、一つの道標になる。

MySQLやSSDとかの話

前編と後編でセットになっている。 2000年代のMySQL運用から10年経った今というような視点で読める。 全く他人事ではないので、わかるわかると頷きながらスライドをめくっていた。

単に10年経ってつらいという話ではない。 ハードウェアの進化にあわせた最適な構成は何かということを考えさせるスライドだった。

モバイルアプリのスレッドプールサイズの最適化

コンピュータサイエンスの知識、ここでは待ち行列理論をモバイルアプリ開発に応用している例。 システムの振る舞いをモデル化して、検証するというサイクルを回せているのはすごい。 モバイルの人ってやっぱり計算機やネットワークのことはあまり考えないだろうという思い込みがあり、いい意味で裏切られた。

性能測定道

性能測定は技芸であり、技あるところに道ありとして、性能測定道というタイトルに行き着くまでの流れに魂みたいなものを感じた。 適当にベンチーマーク回して、最終結果の数値だけみて速いとか遅いとか言ってたりするんだけど、はたして本当にそれでいいのか。 性能測定のためにはシステムのブラックボックスを理解する必要があり、性能測定が本質的に難しいということを知れるだけでも、十分な発見だと思う。

情報科学における18のメタテクニック

キャッシング、パイプライニング、投機的実行、先読みなど、コンピュータサイエンスのあらゆるレイヤで利用される普遍的なテクニックを紹介したスライド。 切り口がよい。以前、Cache Nightとかやったらおもしろいんじゃないかとか言ってたことがある。CPUのキャッシュメモリからCDNまで幅広い話題がでてきそう。こういうレイヤを横断したトピックで、利用例を集めてみるのもおもしろいかもしれない。

Webオペレーションエンジニアのアウトプットと開発力

社外へアウトプットするということの大切さを再確認したエントリ。

自分は社内のインフラチームの中で最もアウトプットしているという自負がある一方で、自分一人ではなく、チーム全体のアウトプットを増やすにはどうしたらいいか悩むことがある。 悩みの中心はたぶんアウトプットに対する過小評価を感じている。 アウトプットの目的が単にプレゼンス向上のためだけだと社内で捉えられてしまうことがあり、揶揄も込めてプレゼンスばかりあっても意味ないという話で終わってしまう。 自分も気分が乗る人だけやればよくて無理することはないというスタンスだったんだけど、これで本当にいいのかと思うようになってきている。

改めてスライドを拝見して、アウトプットすることはより質の高い仕事をするためのステップ、もしくは成長のためのステップと捉えるのがよいのかなと思うようになってきた。

はてなに入った技術者の皆さんへ

10年前のエントリなんだけど、今でも全く色あせない。 matsumotoryさんのエントリとあわせて読むとよい。

僕たちが作るものは、会社の外から評価されることによって初めて価値が生まれるのです。

アウトプットに関しては、この一言に尽きると思う。 やはり社外へのアウトプットを単にプレゼンス向上とみなすのはもったないという気持ちが強くなってきた。

シンプルでかつ最高のJavaScriptプロファイラ sjsp を作りました!

後輩のブログなんだけど、まさにお手本のようなアウトプット。 他人が利用できる形であり、社外でも評価され、なおかつ、裏では社内のサービスのユーザ体験まで向上させている。

ペパボのインターネット基盤技術研究・開発の活動

2年間の自分の仕事を振り返ってみても、基盤技術がボトルネックでサービス開発を妥協していることは少なからずある。 開発チームからの要求があったときに、それはちょっと厳しいから、この辺で妥協するかとかよく言ってる。

一方で、もし圧倒的な基盤技術が開発できれば、例えば、コストを10倍下げて、その結果ユーザが保持できるデータ量を10倍にできるかもしれない。 基盤技術の開発により、競合サービスに対してリードできないかという視点を持ち始めるきっかけになった。

これからのWebサービスは新規性のある技術を自ら研究・開発し、ググっても解決できない問題を自分たちで解決しなければ一歩リードする事ができない時代へと突入

インフラチーム改め Site Reliability Engineering (SRE) チームになりました

昨年、Site Reliability Engineeringという言葉を初めて知った。 通常、自分たちのことをインフラ部とかサーバ管理者とか運用チームとか呼んだりするんだけど、どうにも保守的というか価値を生み出す感がなかった。 はっとしたのは次の一文だ。

...SREがソースコードを追って原因を特定し、パッチを充ててリリースをすることもあるようです。 GoogleのSREの特徴として、ソフトウェアエンジニアとしての業務の比重が大きい事が挙げれます。業務時間の20-80%は開発の業務に関わっているようです。

別の記事にもSREの役割として近い内容が書かれている。

現に、コア原則の一つは SRE が運営の作業に費やせる時間を 50% のみにすることを義務付けています。彼らは、可能な限り最大限の時間をコード書きとシステム構築に費やして、業績と運営効率を改善しなくてはならないのです。

https://www.atlassian.com/ja/help-desk/site-reliability-engineering-sre

自分の仕事を振り返ってみると、運用に手間を割くことが多く、正直そんなにコードを書いているわけではない。 自分たちのことを運用チームと呼んでいたりすると、運用チームだから運用に時間を割くのは当然だと思ってしまう。 @rrreeeyyyくんがよく「運用を消したい」って言っているのとつながる。

エンジニアとしての落としどころを作る

本当にすごい人には勝てないんじゃないか、勝てないとだめなのかとか誰しも考える悩みに正面から応えたエントリ。 落としどころを作るというのは、つまるところ、自分はこれでいいのだなと思えるところを発見することなんだなと思えた。

イメージできることを実践する

技術エントリではないけれど、ことあるごとに思い出すエントリ。 他の人がやってるわけではないんだけど、なぜかイメージできるものというのは確かにある。 例えば、会社でいま存在しないんだけど、こういうポジションがあったらうまくやれそうだなとかおもしろくやれそうだとか、ポジションをイメージできたりすることがある。 このエントリを読むと、他の人がやっているかやっていないかより、自分がイメージできるものをやってみようと思える。

今明かされる! シンラ・テクノロジーのインフラへの挑戦と舞台裏

資料はおそらく公開されていないが、JTF 2015の招待講演は圧巻だった。 中身は、シンラ・テクノロジー(「オンラインゲームを支える技術」の著者である中嶋謙互さんが所属されている会社)のプロジェクトの紹介だ。 クラウドにレンダリングサーバを配備することにより、クライアントではハイスペックなマシンを不要にするアーキテクチャの話。(リモートレンダリングアーキテクチャ)

あまり話題にならなかったのが不思議だ。 GPGPUとカーネルのネットワークスタックにある程度明るくないとピンとこない内容だったかもしれない。 ネットワークレイテンシをできるだけさげるために、RDMAを使って、CPUをバイパスして、NICからGPUのメモリに直接パケットをコピーするとかそういう話。 実はオンラインゲームの基盤技術には昔から興味があったので、こういうのやってみたいなと思ったりする。

こちらのエントリにメモが書かれている。 July Tech Festa 2015 - したためなければいきのこれない

オンラインゲームを支える技術  ??壮大なプレイ空間の舞台裏 (WEB+DB PRESS plus)

オンラインゲームを支える技術  ??壮大なプレイ空間の舞台裏 (WEB+DB PRESS plus)

あとがき

id:toya さんが書かれた 2015年にブックマークしたURLでよかったものの中からブクマコメントに代えて - Really Saying Something にインスパイアされて書いてみた。

こうして振り返ると、ここに挙げたエントリの内容は、おもしろいように昨年の自分のブログの文章にも反映されているなと感じる。 何を考えて、この一年を過ごしたのかということが意外とはっきりと現れる。

他人の心に残るということは、それだけ月日がたっても色あせない何かがあるということだと思う。 今年も他人の心に残るようなアウトプットを心がけたい。

2015年も技術しかしてない

今年も技術しかしてない。

やけにブログを書くことに傾倒していた。

26歳になった。

YAPCで発表した。

ISUCONに負けた。

入社2年過ぎた。

振り返ってみると、今年は技術でも仕事でもプライベートでも、ものごとやものの考え方は5年後、10年後はどうなるのか、変わらないものは何か、色褪せないものは何かを考え始めた年だった。 具体的には、例えば、単にブログを書いて、1カ月にN回書いたとかNブクマついたとか、対外発表の数をこなしてたくさん発表したねだけで終わるのではなく、その先に何があるのかを考えるといったことだ。 他には、5年後でも使っている技術はなにかとか、それを見極めるためには何を理解している必要があるのかといったこともある。 去年は若者らしくあまりそういったことは考えなかった。 たぶん、DockerとかHashicorpを使ってモダンなインフラを作るのがかっこいいと思っていた。 結局、いきなりそれらのツールを導入して、仮にメリットが1つ増えたとしてもデメリットが2つ以上増えることがわかり、技術というのはそういうものではないと悟った。 最終的にはそれらのツールの良い思想だけをいただいて、Dockerやめてchrootでいいとか言い始めることになった。 Dockerとchrootを組み合わせたシンプルなコンテナデプロイツール - ゆううきブログ

昨年の目標

2014年、技術しかしてない - ゆううきブログ

今年の目標はどうやら下記の3つだったらしい。

  • ツールを作る
  • 計算機システムの理解
  • インフラ系の若者と交流

ツールを作るというのはあんまりうまくいってない。目標にないはずのブログのアウトプットに大半を費やしていた。

ふたつ目の計算機システムの理解というのもそんなにうまくいかなかった。あまり書籍とか論文とかまとまったものを読めなかった。

インフラ系の若者とは交流成功した。大成功と言ってもよい。

wakateinfra Slackとかあって、毎日なんか会話してる。平和。

f:id:y_uuki:20151231210951p:plain

ブログの振り返り

昨年は28本のエントリを書いた。合計ブクマ数は 5,055 users、年間PVは 172,878 views だった。 今年は13本のエントリを書いた。合計ブクマ数は 5,409 users、年間PVは 198,381 views だった。 1エントリあたりの平均ブクマ数は 416 users / entry 。 去年は東京はもう古いとか書いてたけど、今年は技術しか書いていない。 昔はもっと量を書かないと、とか思ってたけど、量だけあって一ヶ月で廃れる内容を書いても自分で満足できないので、あとで振り返っても色あせないものを目指した。 自分が満足できるアウトプットかどうかが重要だと思う。 採用活動にも奇跡的にうまくつながったので、会社でそんな賞ないはずないのになぜかブログで求人賞みたいなのをいただいた。

順位 エントリ
1位 2015年Webサーバアーキテクチャ序論 - ゆううきブログ
2位 Webシステムにおけるデータベース接続アーキテクチャ概論 - ゆううきブログ
3位 はてなで大規模サービスのインフラを学んだ - ゆううきブログ
4位 Linuxでロードバランサやキャッシュサーバをマルチコアスケールさせるためのカーネルチューニング - ゆううきブログ
5位 Dockerとchrootを組み合わせたシンプルなコンテナデプロイツール - ゆううきブログ
6位 Mackerelを支える時系列データベース技術 - ゆううきブログ
7位 Ansible + Mackerel APIによる1000台規模のサーバオペレーション - ゆううきブログ
8位 パフォーマンスの観点からみるDockerの仕組みと性能検証 #dockerjp - ゆううきブログ
9位 YAPC::Asia 2015で技術ブログを書くことについて発表しました - ゆううきブログ
10位 Serverspec + Mackerel APIによるインフラテストの実運用化 - ゆううきブログ
11位 ISUCON 5予選で5位通過した話 - ゆううきブログ

発表の振り返り

対外発表は確か3件だけだったと思う。

東京へ行く度に結構疲れてしまった反省をこめて、来年はピンポイントで興味のあるカンファレンスや勉強会で発表したい。

去年の感想によると、東京に疲れてた。今年はピンポイント感あってよかった。 セッションオーナーの話などをいただいたこともあったけど、申し訳ないと思いながらお断りしたりもした。

執筆の振り返り

去年まで雑誌に寄稿とかしたことなかったけど、気づけば今年はSoftware DesignとWeb+DBで執筆することになった。

雑誌の次はいつかは単著で書籍を書いてみたい気もするけどそれは来年とかではない。

OSSの振り返り

ツール書くとか言ってあまり書けなかった。

技術以外のことは何もしてない割にはアウトプットが少ない気がする。 発表やブログのアウトプットは、去年と今年でそれなりに満足したので、来年はブログも発表もできるかぎり抑えて、もっとコード書きたい。 みんなが飲み会とか結婚とかいってのんびりしている間にコード書くというのが半分冗談の来年のテーマだ。

今年もお世話になりました。来年もよろしくお願いします。

Serverspec + Mackerel APIによるインフラテストの実運用化

この記事は Mackerel Advent Calendar 2015 の24日目の記事です。 前回は、id:hitode909 による 三度の飯より監視と通知!Mackerelで自分の心拍数を監視しよう - hitode909の日記 でした。

今回は、Mackerel APIを用いてServerspecによるサーバ構成テストを実運用化した話を紹介します。 Serverspec単体では手の届かないかゆいところをMackerelでサポートするところがポイントです。 Mackerelはもちろんですが、他のサーバ管理ツールにも通用する汎用的な話になるように心がけています。

続きを読む

Dockerとchrootを組み合わせたシンプルなコンテナデプロイツール

この記事ははてなエンジニアアドベントカレンダー2015の1日目です。今回は、既存の運用フローに乗せやすいDockerイメージへのchrootによるデプロイの考え方と自作のコンセプトツール droot を紹介します。

github.com

続きを読む

ISUCON 5予選で5位通過した話

ISUCON 5の予選で2日目3位、全体で5位のスコアで通過した。

メンバーは id:ntakanashi さん, id:astj さんと自分の3人で、「はむちゃん」というかわいいチーム名で参加した。 言語は当然Perl。 役割分担は id:astj さんの記事にも書いてあるけど、だいたい以下のようなものだった。

id:y_uuki : ミドルウェアより下をお任せ / ログ解析して改善ポイントの洗い出し id:ntakanashi : オンメモリにしたりモジュールを入れ替えたり諸々チューニング id:astj : クソクエリやN+1をちまちま潰していく

ISUCON 5の予選に参加して全体5位で通過しました - 平常運転

昨年のISUCON 4に参加したときに、少なくともISUCON予選においてはアプリケーションロジックの改善/改変がスコアに対して支配的だと感じていた。 そこで、インフラ担当の最初の仕事はいかにしてアプリケーションエンジニアにロジックの改善に集中できる環境を作るかということだと考えた。 さらに、インフラエンジニアは普段からシステム全体を俯瞰することを求められるので、インスタンスサイズなど与えられた条件とシステム全体をみてシステムの性質を捉えることが重要だと思う。 今回の指定インスタンスはCPU4コア、メモリ4GB弱と去年よりメモリ搭載量がかなり少なかったので、これはいつものようなオンメモリ勝負は厳しいのではないかとあたりをつけていた。実際、普通に考えるとデータサイズがOSのメモリに乗らない量(一部のテーブルは150万行ぐらい)だったので、初期段階で明らかにMySQLのCPU利用率とディスクI/Oがボトルネックだった。 したがって、今回の出題意図はメモリに乗らないデータをいかに捌くかということにあると判断した。

インフラ担当である自分が具体的にやったことは、インスタンスの構築、デプロイの仕組みの整備、OS/Nginx/MySQL/memcached/アプリサーバのチューニング、topやiostatによるハードウェアリソース利用状況の把握、アクセスログとスロークエリログの解析などだった。 今回はベンチマークをインスタンスのローカルで実行する術がなかったので一回一回のベンチマークを無駄にしないために、どうせ仕込むであろうsysctlやmy.cnf、nginx.confのチューニング、UNIX domain socket化、PlackサーバをGazelleに変更、静的ファイルのnginx配信などは初期の段階で一気にやった。 さすがにやったことのないチューニングをするのは不安なので、過去問で訓練を重ねた。 ちなみにどうせスコアに影響しない(より高req/sな環境では影響するかもしれない)ワーカー数やスレッド数の調整に時間をかけるのも無駄なので、コア数分のワーカーしか立てないと決めていた。 今年はAWSではなくGCPだったので、去年の問題をGCEで構築し一通り癖を把握しておいた。Web UIからの公開鍵の設定やスナップショットの取り方など。 さらにディストリがCentOSからUbuntuに変更されるようだったので、ISUCONに必要なUbuntuのオペレーションも練習しておいた。(といっても普段からDebianをいじってるのであまり差はない)systemdだったのは面くらったけど、やることは大して変わらなかった。

あらかじめチェックリストやオペレーションメモを用意しておいて、それに従ってすばやく足場を組むことを意識していた。 13時すぎまでにはだいたい整えたので、のんびりコードでも眺めるかと思っていたけれど、後述する大きめのトラブルの解決やちょっとインデックス張ったりスコアが変化したときのボトルネックの変化を解析するということをずっとやっていた。 プロファイラとしてDevel::NYTProfの準備を一応していたけど、ディスクI/Oがネックだったので今回はいれなかった。ボトルネックがアプリケーションのCPU利用に移行した段階でいれてたと思う。

しかし、事前にいろいろ準備していたとはいってもやはりトラブルはいろいろある。

トラブル1: MySQL has gone away

PerlのDBIではMySQLとの接続が切断された状態でSQLを投げると、MySQL has gone away というエラーメッセージがでる。 あるタイミングからこのメッセージが頻発するようになって、最初は接続できてるのにベンチ中に接続に失敗することがあった。 スコアが立ち上がる前だったのでとにかく最悪。 これは結局原因がよくわからなくて(原因らしきものは後述)、要は再接続するようにすればよいということで、DBIを Scope::Container::DBI Scope::Container::DBIを書いた - blog.nomadscafe.jp に差し替えて、dbhオブジェクトのキャッシュをやめて、Scope::Container::DBI->connectを毎回呼ぶようにした。

以前にこういうことを書いてた。

少なくとも、PerlのDBIの場合、DBI->connectの返り値であるデータベースハンドラオブジェクトをキャッシュしても、うまくいかない。 キャッシュしている間に、データベースとの接続が切れると、再接続せずにエラーを吐く。 データベース接続まわりのオブジェクトをキャッシュするときは、キャッシュして意図どおりに動作するのかをよく調査したほうがよい。

Webシステムにおけるデータベース接続アーキテクチャ概論 - ゆううきブログ

トラブル2: ALTER TABLEが30分たっても終わらない

entriesテーブルに対するインデックスの作成が2000秒以上かかっていてめちゃくちゃだった。どうみてもディスクI/Oを使いきっていたのでどうしようもなかった。 過去に人数分のインスタンスをたてたりしていたチームがあったようで、それにならってメインのインスタンスのスナップショットからディスクをSSDに変更したインスタンスをたててそちらでALTERを回して、メインのインスタンスに/var/lib/mysqlごとncでとばすということをやった(SSDのインスタンスはもちろんベンチにはかけていない)。今ではSSDが当たり前になってるけど、改めてSSDの速さを実感した。これも最初はスナップショットではなくてVMインスタンスの複製機能みたいなのを使った。これは便利とか言ってたら、数時間前の状態のインスタンスの複製が作られることに気づいて、あわててスナップショットによる複製に切り替えた。

その他、手作り感のある最小限の/etc/my.cnfは読まれずに、debパッケージ付属の /etc/mysql/my.cnf が実は読まれているという罠があった。 これを適当に/etc/mysql/my.cnf/etc/my.cnfに差し替えるとbase dir か data dirあたりがたぶん間違っていてmysqldが起動しなくなる。事前に過去問であれこれ壊したのでハマることはなかった。

立ち上がりはそれほどスコアが伸びなかったことや、途中でトラブルがあって、トラブル解析中に複数の改善をいれていたりしたので、はっきりこの変更がスコアに効いたみたいなのがわからずに進んでいった(failしつつもログの解析はやってた)。 とはいえ、基本はアクセスログ解析とpt-query-digestを丁寧にまわして実行時間の割合が大きい順にクエリを改善していくことを意識していた。 その他は変更の手間が少ないものをやるぐらいで、ボトルネック無視で見当違いのところをチューニングし始めるということはたぶんなかったと思う。 打つ手がなくなってくる終盤はともかく中盤まで1つずつボトルネックをつぶせていった感があった。

最終的には、Nginx - Perl - MySQLの普通の構成でセッションだけmemcachedにいれた。usersのようなメモリ内にキャッシュできるところはアプリケーション起動時にMySQLから引いて親のアプリケーションプロセスのメモリにキャッシュした。あとは普通に各テーブルにインデックスを張り、N+1クエリをなくしていくという感じ。 親プロセスにキャッシュするのは、最初 /initialize でやってたけど、/initialize でキャッシュするとpreforkされた子プロセスのうちの1つだけしかにしかキャッシュされなくて確率的にエラーになるので親プロセスでロードすることにした。

書いてる途中に思い当たったけど、親プロセスでMySQLに接続しつつDBIのattributeでAutoInactiveDestroyが指定されているので子プロセスでdbhオブジェクトが勝手に破棄されて、dbhオブジェクトを使いまわしてる場合うまく動かないかも?と思った。 第30回 データベースプログラミング入門―汎用インタフェースDBIと,O/RマッパTengの使い方(2):Perl Hackers Hub|gihyo.jp … 技術評論社 いずれにしても親プロセスのソケットディスクリプタをforkで引き継いでるところが問題になっていそう。 あとでみてみる。

リポジトリはこちら。https://github.com/yuuki1/isucon5-qualifier

ちなみにMackerelの外形監視でトップページを監視させた。アプリケーションが停止してるときに誤ってベンチしないようにとかいろいろ使いみちがありそう。

blog-ja.mackerel.io

参考

ISUCON予選の準備をするときに特に下記のエントリを参考にさせていただくことが多かった。

あとがき

去年は本戦出場枠に結構ギリギリで滑りこんだので、去年より参加チームの多い今年はかなり厳しいのではと思っていたけど、終わってみれば意外と上位通過でチームメンバーの優秀さをみせつけられた一日だった。 ISUCON、結構メンバーのバランスが重要だと思っていて、全員ある程度サーバをいじれて、ある程度コードを書ける+@みたいなイメージ。 例えば、方針決めたりボトルネック見極めたりはインフラ担当まかせたみたいな感じになりがちのような空気があるけど、ツールさえ置いておけば2人とも勝手にみてくれる。コードだけみて局所最適に走ることが少ない。 同じ会社にいるとその優秀さがだんだん当たり前にみえてくるけど、ISUCONのようなイベントで対外的に評価されることになって、その優秀さは普通じゃなかったんだなということが改めてわかる。

基本はアプリケーションロジックの改善に2人とも集中してもらえたと思うので、スコアにはたいして貢献してないけどとりあえず役目は果たした感がある。 まあまあトラブル多かったわりに意外となんとかなったのは事前準備してやるべきことをわりと早めに終わらせて時間的余裕を稼げたからかなと思う。

インフラエンジニアにとっては複数サーバ使える本戦が本当の力を試されると思う。複数サーバ使うISUCON楽しみすぎる。

ISUCON運営の皆様、すばらしいイベントをありがとうございました。 やりがいのある問題で楽しかった。今回の問題を5台構成で解いたりしてみるとたのしそう。