銀色うつ時間

思い出すたび何か胸につっかえてるだけ

SICP(計算機プログラムの構造と解釈)1.1

はじめに

『計算機プログラムの構造と解釈』を読む。動機は以下。

  • いわゆる情報系の勉強をしていないので、基礎を身につけたい
  • Lispインタープリタを実装してみたい
  • ストリーム、遅延評価、末尾再帰最適化、構文・字句解析器など、なんとなくしか知らないものを理解したい
  • すごいエンジニアがみんな読んでる

年単位でかかるかもしれないが、それでも終わらない可能性・挫折する可能性があるので、練習問題は無理に全部やらない。

資料

mobiをkindleに送ってkindleから読んでいる。

html版

計算機プログラムの構造と解釈 第二版

訳にかなり癖があるので、意味を掴みにくい場合は、原著を確認するとよいかもしれない。また、コード集はこちらにしかないので、適宜見るとよい。

Welcome to the SICP Web Site

HTML版は、スタイルが適用されていないので、読みにくい。epub化を考えたけど、自分がやる前に既にepubおよびmobiで公開してくれている方がいたので、ありがたく使わせていただく。

github.com

環境

環境はOSXLisp/Scheme派生の言語Racketをバイナリからインストールして使っている。DrRacketというIDEが同梱されているので、そちらを利用するか、/Applications/Racket\ v6.2/binにPATHを通せば$ racketで対話型コンソールを起動できる。

racket-lang.org

Emacsの使用経験がないため、エディタは検討中。vimでやるか、これを期にemacsを覚えるか。。。

本文

1.1.7

平方根について。数学的な関数とコンピュータの記述について。 数学では平叙文的(何であるか)記述をするのに対して、コンピュータは命令文的(どうするか)記述をする。どう計算するかというアプローチに対して、通常は次々と近似をとるニュートン法を用いる。

> (define (sqrt-iter guess x)
    (if (good-enough? guess x)
        guess
        (sqrt-iter (improve guess x)
                   x)))
> (define (improve guess x)
    (average guess (/ x guess)))
> (define (average x y) (/ (+ x y) 2))
> (define (good-enough? guess x)
    (< (abs (- (square guess) x)) 0.001))
> (define (sqrt x)
    (sqrt-iter 1.0 x))

> (sqrt 2)
1.4142156862745097
> (sqrt 3)
1.7321428571428572
1.1.8

手続きを抽象化してブロック構造をとる方法、パラメータのスコープについて。外の入れ子にある束縛されたパラメータを内部で利用する(レキシカルスコープ)。

(define (sqrt x)
  (define (good-enough? guess)
    (< (abs (- (square guess) x)) 0.001))
  (define (improve guess)
    (average guess (/ x guess)))
  (define ( sqrt-iter guess)
    (if (good-enough? guess)
      guess
      (sqrt-iter (improve guess))))
  (sqrt-iter 1.0))

問題

EXSERCISE 1.3

三つの数を引数としてとり , 大きい二つの数の二乗の和を返す手続き

> (define (square a) (* a a))
EXERCISE 1.4

schemeの評価モデルは、演算子が合成式である組み合わせでも使える

> (define (a-plus-b a b)
  ((if (> b 0) + -) a b))
> (define (sum a b) (+ a b))
> (define (larger-square-sum a b c)
    (cond ((and (< a b) (< a c)) (sum (square b) (square c)))
          ((and (< b a) (< b c)) (sum (square a) (square c)))
          (else (sum (square a) (square b)))))
> (larger-square-sum 3 4 5)
41
EXERCISE 1.5

作用的順序の評価と正規順序の評価について

EXSERCISE 1.6

特殊形式として定義されているifを通常の手続きとして再実装して、1.1.7における平方根の手続きを行った場合、どうなるか。

> (define (new-if predicate then-clause else-clause)
    (cond (predicate then-clause)
          (else else-clause)))

> (define (sqrt-iter guess x)
    (new-if (good-enough? guess x)
            guess
            (sqrt-iter (improve guess x)
                       x)))

結果、無限ループする。これは、Schemeにおける通常の手続きが作用的順序で行われることに起因する。作用的順序での評価は、以下の通り。

  1. 組み合わせの部分式を評価する
  2. 最左部分式の値である手続き(演算子)を残りの部分式の値である引数に作用させる

つまり、一般的なSchemeの評価規則で定義されたnew-ifの場合だと、先に部分式が評価されるため、

(good-enough? guess x)

が真であったとしても

(sqrt-iter (improve guess x)
           x

が評価されるため、無限ループする

EXERCISE 1.7

曖昧。 平方根の手続きにおいて、入力が非常に小さい値もしくは大きい値にテストすっとが失敗する。大きい値の場合は、浮動小数点の比較における誤差によるところ。桁数の増大によって仮数が計算機に無視されるため、無限ループする。値が小さい場合、予測値が基準値より下回ると真を返すため、値にかなりのずれがあっても再帰が終了してしまう。改良版未着手。

EXERCISE 1.8

未着手。立方根の問題。ニュートン法の実装を改良する。

linuxにおけるメモリと関連コマンド(free, vmstat, top, sar)

linuxにおけるメモリの扱いを中心として、関連する統計情報の閲覧、監視ツールの見方についてまとめる。下に挙げた技評の2冊および学びの多かった記事を基にしています。

ページキャッシュ

freeとかtopとかの説明に入る前に、まず、OSのキャッシュの話から始める。大規模なデータを扱う際、Linuxはキャッシュの仕組みによって効率的にメモリ管理を行うことで、ディスクへのアクセスを減らし、データアクセスを高速化している。これは「ページキャッシュ」と呼ばれる仕組みで実現している。

Linuxには仮想アドレスと呼ばれる機能を提供している。論理的なアドレス(リニアアドレス)をOSが提供することによって、各プロセスはメモリ(あるいはスワップ)の物理的なアドレスを気にすることなく扱うことができるようになる。この際、Linuxはメモリ領域をアドレス単位ではなく1つのまとまったブロックとして提供しており、その単位が「ページ」と呼ばれる。サイズは4KB。このページをOSはメモリ上にずっと確保しており、別のプロセスが再び同じデータにアクセスしようとしたときに、ディスクへのアクセスを行うことなく高速に処理できる、という仕組みが、ページキャッシュの大まかな仕組み。Linuxはメモリが空いている限り全てページキャッシュに回そうとする。

基本的にはメモリに空きがあれば全てキャッシュに乗せるが、開いていない場合は古いキャッシュから破棄して新しいキャッシュと入れ替える。sar -rで確認すると、memusedの比率におけるkbcachedの値が確認できる。

06:00:01 AM kbmemfree kbmemused  %memused kbbuffers  kbcached kbswpfree kbswpused  %swpused  kbswpcad
06:10:01 AM    161280   3824360     95.95      6672   2008004   4174336     18620      0.44       380
06:20:01 AM    171652   3813988     95.69     18132   1995240   4174336     18620      0.44       380

メモリとディスク

メモリへのアクセスとディスクへのアクセスには、105から106(10万〜100万)倍程度の速度差が生じる。これは、メモリが電気的な部品であって、探索速度に物理的制約を受けることがないのに対して、ディスクの場合はヘッドの移動、ディスク回転という物理的な動作を伴う必要があることに起因する。を必要なデータがメモリ内に乗っていれば、多少アプリケーションが非効率であっても、ある程度パフォーマンスが担保できる。アプリケーションを構成する上で、iowaitを避けて極力全てのアクセスをメモリで行いたいのは、こうした理由からである。また、OSのキャッシュで捌ききれなくなったときに、分散の考え方が必要になる。

メモリの使用状況を確認する

メモリ使用量を計測する各種コマンドについて。環境はvagrantのubuntu14.04 LTS。

free

現在のメモリ使用量を確認できる。この出力は、/proc/meminfoファイルを元にしている。 オプションの-tTotal行の出力、-mはMB単位での出力を行う。

vagrant@vagrant-ubuntu-trusty-64:~$ free -tm
             total       used       free     shared    buffers     cached
Mem:           489        336        153          0         12        211
-/+ buffers/cache:        112        377
Swap:            0          0          0
Total:         489        336        153

Mem行は、物理的なメモリの総量。489MBのメモリが存在し、336MBが既に使用され、空きメモリは153MBということが分かる。bufferscachedはOSによって予約されている領域。buffersおよびcacheは、メモリが不足する場合に開放して利用できるようにする。よって、実質的なメモリの使用量および利用可能量を見るためには、2行目の-/+ buffers/cache行を見るのが正しい。2行目のusedMem行のusedからbuffers/cachedを引いた使用量、freeは足した量を表示している。ここではSwap行に出力はないが、メモリに載せるデータが増えてくると、スワップ領域がディスクに作成され、一部のデータがそちらに移される。

スワップ

ディスクを利用して使用可能なメモリ領域を増やす。メモリ容量が不足している場合に、現在使われていないデータを一時的にスワップ領域に退避させてメモリを開放する。 スワップ領域に退避させたデータの参照をプロセスが要求したときに、再度メモリ上に読み込まれる。ディスクへのアクセスがあるので、もちろん遅い。スワップ領域への退避・アクセスが頻発する条件は、要改善項目。メモリが不足しているため、以下のようなことを検討する必要がある。

  • 極端に大きなプロセス(メモリリークなどの異常含む)がないか
  • プログラム内に非効率にメモリを使う箇所がないか
  • メモリを増設できないか
  • 分散を検討できないか

ボトルネックの特定には、下で挙げるvmstatsarを使うとよい。

vmstat

メモリやCPUの情報だけでなく、スワップやプロセスの情報を出力できる。

vagrant@vagrant-ubuntu-trusty-64:~$ vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 3  0      0 144368  23220 200692    0    0    54    20   21  327  0  1 99  0  0
 0  0      0 144360  23220 200692    0    0     0     0   32   66  0  0 100  0  0
 0  0      0 144360  23220 200692    0    0     0     0   24   52  0  0 100  0  0

siスワップイン), soスワップアウト)が頻繁に動いている場合はスワップが発生している。他の項目としては、rは実行待ち状態のプロセス数、bは割り込み不可能なスリープ状態にあるプロセス数。memoryの項目はfreeと同様、ioはデバイスとのブロックの転送量、systemはコンテキストの毎秒の切り替えレート(割り込み回数・コンテキストスイッチの回数)、cpuCPU使用率である。

top

topコマンドは、現在のシステム全体の負荷情報を閲覧するのに利用する。プロセス・CPU・メモリ・スワップが確認できる。主なオプションは、以下の通り。

vagrant@vagrant-ubuntu-trusty-64:~$ top          # CPU使用率順にソート
vagrant@vagrant-ubuntu-trusty-64:~$ top -o %MEM  # メモリ使用率順にソート. -o fieldnameでソート順を切り替えられる。centosでは-a?
vagrant@vagrant-ubuntu-trusty-64:~$ top -p [PID] # 特定のプロセスを監視
vagrant@vagrant-ubuntu-trusty-64:~$ top -d3      # 3秒ごとに更新

topではshiftでソート順の切り替えができる。

shift + m // メモリ使用率順
shift + p // CPU使用率順
ヘッダについて
vagrant@vagrant-ubuntu-trusty-64:~$ top
top - 19:56:06 up  2:12,  2 users,  load average: 0.00, 0.01, 0.05
Tasks:  75 total,   1 running,  74 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0.0 us,  0.0 sy,  0.0 ni,100.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem:    501748 total,   140316 used,   361432 free,     5252 buffers
KiB Swap:        0 total,        0 used,        0 free.    24128 cached Mem

1行目は、uptimeコマンドと同じ出力。現在時刻、システム稼働時間、ログインユーザ数、LoadAverageと並ぶ。LAは左から、1分、5分、15分を単位時間としている。2行目は、タスクの統計。上の例では、75のタスクがあり、稼働中のものは1、待機中のものが74となっている。3行目はCPU。usがユーザ、syがシステム、niがnice、idがidle、waがiowait、hiはhardware interrupt、siはsoftware interrupt、stがsteal。 3行目、4行目はメモリ統計。これはfreeで解説したものと同じ。

プロセス一覧
 PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
 1789 vagrant   20   0  107696   1352    348 S  0.3  0.3   0:01.28 sshd
 8523 vagrant   20   0   23536   1560   1116 R  0.3  0.3   0:00.02 top
    1 root      20   0   33608   1716    276 S  0.0  0.3   0:00.86 init

ここでのCPUは1コアを100%とした値が出力されるため、仮に何かのバッチスクリプトのプロセスが100%になっていたからといって焦る必要はない(効率はよくないだろうけど)。

PID // プロセスid
USER // 実行ユーザ
PR // 優先度
NI // ナイス値
VIRT // 割り当てられた仮想メモリのサイズ
RES // スワップのない物理メモリのサイズ
SHR // 割り当てられた共有メモリのサイズ
S // プロセスのステータス。R: 実行可能,S: 停止,D: 割り込み不可の停止,T: 停止またはトレース中,Z: ゾンビ・プロセス,W: スワップ・アウトしたプロセス, N: ナイス値が正
%CPU // CPU使用率
%MEM // メモリ使用率
TIME // プロセスの経過時間
COMMAND // タスクのコマンド名
LoadAverage

1CPUにおける単位時間あたりの実行待ち、ディスクI/Oの完了待ちをしているタスク。これが高ければ高いほどCPUかIO起因で高負荷といえる。雑な例えをすると、手元のPCでchromefirefoxxcodeを立ち上げて更にでかいExcelファイル読み込もうとすると、マルチタスクとはいえちょっと重いよねって話。

sar

sarコマンドは、CPU使用率およびディスクI/Oを出力できる。他のコマンドと異なる点としては、過去に遡れることが挙げられる。また、topやfreeと異なり定期的に1行ずつ出力されるので、負荷試験時などに計測を行いやすい。sysstatパッケージをインストールする必要があるので、sarコマンドがない場合はインストールする。

vagrant@vagrant-ubuntu-trusty-64:~$ sudo apt-get install sysstat

デフォルトでは10分間隔でログ出力されている。必要に応じて変更する場合は、以下のファイルを編集する。また、ログの保存期間も必要に応じて伸ばしておく。centosであれば/etc/sysconfig/sysstatあたり。ubuntuであれば/etc/default/sysstat?(未検証)

vagrant@vagrant-ubuntu-trusty-64:~$ cat /etc/cron.d/sysstat
~
# Activity reports every 10 minutes everyday
5-55/10 * * * * root command -v debian-sa1 > /dev/null && debian-sa1 1 1
オプション
vagrant@vagrant-ubuntu-trusty-64:~$ sar -q   loadaverage
vagrant@vagrant-ubuntu-trusty-64:~$ sar -u  CPU使用率
vagrant@vagrant-ubuntu-trusty-64:~$ sar -b  I/O
vagrant@vagrant-ubuntu-trusty-64:~$ sar -r  メモリとスワップ使用率
vagrant@vagrant-ubuntu-trusty-64:~$ sar -s time 時間以降のデータ
vagrant@vagrant-ubuntu-trusty-64:~$ sar -e time 時間までのデータ

オプションなしでsar 1 5等の指定も可能。この場合は、1秒を単位時間として5秒間CPUの使用率を出力する。

vagrant@vagrant-ubuntu-trusty-64:~$ sar 1 5
Linux 3.13.0-55-generic (vagrant-ubuntu-trusty-64)    07/06/2015   _x86_64_    (1 CPU)

08:47:12 PM     CPU     %user     %nice   %system   %iowait    %steal     %idle
08:47:13 PM     all      0.99      0.00      0.00      0.00      0.00     99.01
08:47:14 PM     all      0.00      0.00      0.00      0.00      0.00    100.00
08:47:15 PM     all      0.00      0.00      1.00      0.00      0.00     99.00
08:47:16 PM     all      0.00      0.00      0.00      0.00      0.00    100.00
08:47:17 PM     all      0.00      0.00      0.00      0.00      0.00    100.00
Average:        all      0.20      0.00      0.20      0.00      0.00     99.60

sarは過去に遡って出力できるため、ボトルネックの特定に重宝する。-qでLoadAverageが高かった場合は、-u -eで該当時間帯のCPU使用率をチェック、userが高ければCPU負荷、iowaitが高ければディスクI/O負荷と切り分けることができる。

vagrant@vagrant-ubuntu-trusty-64:~$ sar -u -s 04:00:00

04:00:01 AM       CPU     %user     %nice   %system   %iowait    %steal     %idle
04:10:01 AM       all      4.78      0.01      1.38      2.84      0.00     90.99
04:20:01 AM       all      3.03      0.00      1.10      0.02      0.00     95.85
04:30:01 AM       all      2.99      0.00      1.06      0.02      0.00     95.93

監視ツールの見方

まず、監視ツールを導入する。ここではXymon(旧hobbit)を利用して、メモリに関する部分のみ見ていくことにする。Xymonオープンソースのネットワーク・サーバ監視ツール。読み方はサイモンorシモン。恐らくサイモンだろう。シモンとガーファンクルとは言わないし。。。GPL2ライセンス。

これが監視ツールで満たときのメモリ使用率。

f:id:sisidovski:20150707060345p:plain

freeコマンドを元にグラフを出力している。

actualは、バッファとキャッシュ分を差し引いた値。freeでいうところの-/+ buffers/cache行のusedのことである。realは、Mem行、つまりバッファとキャッシュを含めた使用率の値である。そのため、realよりもactualの方が低い値となっている。ページキャッシュがあまり乗っていない起動直後は、actualrealに大きな変化が見られない。swapはその名の通りスワップです。

言い回しが違うだけで確信が掴めなかったけど、以下のMLに書いてあった。

Re: [hobbit] Memory check

インストール手順は、こちらの記事が参考になります。

karia.hatenablog.jp

おわり

Linuxにおけるメモリの扱いを中心としてざっと見てきたが(CPUとかディスクとかが中途半端に入ったけど)、メモリ管理の仕組みそのものや、メモリ管理を意識したスケールアウト時の考え方、各コマンドの詳細、実践に則した使い方、監視ツールの多項目等、もっと掘り下げたいところ。間違い等あれば指摘ください。

参考資料

マネジメントは難しい

マネジメントは難しい。

得体の知れないプロジェクトという怪物に対して、有用なアプローチを見出し、いかなる手段を用いても遂行しなければならない。考えるべきことは山ほどある。プロジェクトのスケジュール管理、非エンジニアとの企画検討、要件のヒアリング、仕様策定。差し込まれる割り込み案件、実装すべき機能のタスク化、ブレイクダウン。設計・コードレビュー、チームの技術力底上げ、メンバのモチベーション管理、その他雑用、ドサ回りに至るまでもがマネジメントに含まれる。

『Team Geek』を読んで共感する部分もあったが(HRT原則は本当に心がけるべき)、違和感を拭えないのは、メンバを「信じて、任せるべき」であるとする部分だ。チームとしての総合力が突出して高くない且つスケジュールがタイトな現場に於いては、これは当てはまらないと感じている。

マネジメントには頭と時間を使う。仕様策定やアプリケーションの設計など、考えるべきことは山積みであり、これらに考慮漏れや曖昧さが含まれると、プロジェクトは滞り、メンバは曖昧な要求に対して不満を持ち、最終的な成果物の価値も落とす結果になる。マネジメントを行う者は「考える事」と「論理的思考に基づき決定すること」に頭と時間を可能な限り費やすべきだ。また、睡眠不足や疲労が蓄積している場面では、適切な判断が下せないので、自分は手を動かさず、極力休息を取る必要がある。マネジメントと実際に手を動かす作業をそれぞれ充分に遂行するのは、非凡な人材にしかできない。ので、「信じて、任せるべき」なのである。

しかし、それではうまく回らない。プロジェクトに対して、決められた納期(あるいは、求められるスピード感)で曖昧な要件の中で仕様を固め、品質のよいコードを書くことができる人間ばかりではない。むしろ、自分も含め大抵の人が充分にそれをこなすことができない。原因は無数に存在する。曖昧な仕様を積極的に解決しない、スケジュールを意識せず永遠に設計について思案する、そもそも要求を満たしていない、エラー処理など、考慮漏れがあまりにも多い、等。

「ただ単にお前が無能なだけではないか」といった意見はごもっともである。これらは、冒頭で挙げたマネジメントが全て解決する。メンバ個々人の性質に応じて、仕様を細かく固めてから依頼することであったり、当事者意識を持たせるためのモチベーション向上であったり、経験不足や細部の曖昧さをテストコードを書かせることで補ったり、等のアプローチが考えられる。だが、同時に複数ラインで様々なことが進行する中で、全てに対して適切な処置を施し、判断を下すことは中々に難しい。毎日、気がつけば深夜だ。

マネジメントする者自身が充分な実力をつければ、様々なことが解決する。必要なのは、自分の頭が充分に冴えたコンディションを維持できる時間を確保することだ。仕様決定や設計は、論理的思考とソフトウェア設計の知識および経験を培えば時間は削減できるし、数時間かかるようなコードレビューも、テスト環境が充分に整備されていればコードレベルのバグ等は担保され、人間が気にするべき設計や要件を満たしているか、といった部分に注力できる。しかしながら、中〜大現場なソフトウェア開発の現場では、古いライブラリや複雑なコードに縛られてそういった改善を行うのが大きなコストになっていたり、次々に来る要望と既存システムの保守運用に対応するのに精いっぱいだったりして、中々うまくいかないのも現実である。そういった事情の上で、「信じて、任せる」ことができるのなら喜んでお願いするのだが、実際には厳しかったりする。

故に、結局は自分の手を動かすことで解決する。

これは純粋に人が足りない、ということの証左でもあると思うが、「人が足りない」という言葉の裏には一部甘えだったり、自分の未熟さを認めるような響きを感じていて、敗北感を感じる。簡単には音を上げたくない、といった個人的心情もあるのかもしれない。また、コードを書く時間を確保したい、という心情もそれに交錯する。弊害として、作業内容が曖昧なままだったり、仕様の考慮漏れなどが発生して、メンバの作業を停滞させるし、今ひとつチームの開発効率が高まらない結果を産む。

結局のところ、問題はマネジメントする者自身にあるのだろうと思う。適切なマネジメントと、コードを存分に書くことは両立しない。両立させるためには、頭と時間、確かな技術力が全て必要なのだ。それを持たざる者は、コードを書くことを優先してはならない。プロジェクトを成功に導くことが至上命題だからだ。しかしながら、自分がマネジメント専業となるには、まだ早過ぎる。

Team Geek ―Googleのギークたちはいかにしてチームを作るのか

Team Geek ―Googleのギークたちはいかにしてチームを作るのか

いまレンタルサーバ・クラウド契約するなら

ほぼ放置状態だったさくらのVPSを解約した。年間契約だったので、解約するタイミングがなかなかこなかった。ドメインだけ手元に置いてあるので、これを機にクラウドサービスを色々使ってみようと思っているんだけど、どこがよいのだろう。EC2かdigitaloceanが取り急ぎ無難な気もするけど。

mysqlコンソールでプロンプトを変更する

mysqlコマンドの出力結果を貼ったりするときに、見る人に「このサーバ内での作業なんですよー」みたいなのを示しておきたかったりする。もちろんデフォルトは以下

mysql>

そういうときはPROMPTを使うとよい。作業日時やログインユーザ、ホスト名などの表示変更が可能だ。例えば作業ユーザとホスト名を指定したい場合は以下。

mysql> PROMPT \u@\h >

>とスペースを最後につけておいた方が個人的には見栄えがよい。 プロンプトは次のような表示となる。

user@database01 >

以下はよく使うシーケンス。

\c
カウンタ。複数のSQLを実行する場合にナンバリングに使える

\D
日時

\u
ログインユーザ名

\h
ホスト名

特定のプロンプトを毎回打つのが面倒なので、その場合は$HOME/.my.cnfにPROMPTを設定しておけば、デフォルトで設定できる。

Ansibleで冪等性を保つためにはfailed_whenとかstatを使うと便利

構成管理ツールとしてAnsibleを使って開発環境を作っているんだけど(本番でも使えるようにとかはまだできてない)、特定のコマンドの実行結果によって次の処理をスキップするかどうかみたいなのを制御したい場面がある。

例えばcentos6.xでnginxの設定を行いたいとすると、以下のようになる。

- name: get nginx rpm
  get_url: url="http://nginx.org/packages/centos/6/noarch/RPMS/nginx-release-centos-6-0.el6.ngx.noarch.rpm" dest="/var/tmp/nginx-release-centos-6-0.el6.ngx.noarch.rpm"
- name: add nginx rpm
  yum: name=/var/tmp/nginx-release-centos-6-0.el6.ngx.noarch.rpm state=present
- name: install nginx
  yum: name=nginx state=present
- name: register service
  service: name=nginx enabled=yes
- name: add conf file
  copy: src=../files/extra.conf dest=/etc/nginx/conf.d
- name: move default conf
  shell: mv /etc/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf.backup

確かにこれでもnginxのインストールと必要な設定は行えるが、このままでは冪等性が担保されない。Ansibleのコアモジュールの多くは冪等性をチェックしているが、shellモジュールなどの冪等性は担保されない。対象のサーバに対してこのスクリプトを実行した場合、2回目移行もdefault.confをmvしようとしてエラーになるだろう。

幸いAnsibleにはregisterという出力を変数に格納できる仕組みがあるので、それを利用するとよい。

- name: is nginx already installed
   shell: which nginx
   register: which_nginx

# (中略)

- name: move default conf
  shell: mv /etc/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf.backup

しかし、これではエラーとなってしまう。Ansibleは終了ステータスが0以外のものを原則エラーとして解釈するからだ。which nginxでnginxが存在しなかった場合、no nginx foundのようなものがエラー出力に出てしまうから、ハンドリングする必要がある。

結果、以下のように書くのがよい。

- name: is nginx already installed
   shell: which nginx
   register: which_nginx
   failed_when: which_nginx not in [0, 1]

# (中略)

- name: move default conf
  shell: mv /etc/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf.backup
  when: which_nginx.rc == 1

failed_whenは値に記述した結果のBooleanでタスクの成功可否を判定する。failed_whenを使うことで終了ステータスの正常・エラーに関わらずタスク自体は通過させ、以後はそこで宣言した変数のステータスや出力内容を見ながらタスクを実行するかスキップするか決めるようにするとよいと思う。

余談だが、特定ファイルの存在確認をするためには以下のようなやり方がある

- name: check default conf
  stat: path=/etc/nginx/conf.d/default.conf
  register: is_exists
- name: move default conf
  shell: mv /etc/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf.backup
  when: is_exists.stat.md5 is defined

statモジュールを利用して変数にset、md5ハッシュ値が存在するかどうかで以降のタスクを制御する。