「コード化の“つらみ”をいかにうまく防ぐか」が今日のテーマ

チェシャ猫氏:チェシャ猫と言います。Twitterは@y_taka_23の名前でやっているので、よろしくお願いいたします。今日は「Infrastructure as Code の静的テスト戦略」をテーマに選びました。Infrastructure as Codeはここ数年で、非常にメジャーにというか、ある意味で当たり前に使われるものになりました。

みなさんの中で、お仕事とか、趣味でInfrastructure as Codeを使っている方はたくさんいると思います。よく聞く巷の声として、こんなのがあります。「Infrastructure as Codeやろうとしたけど、なんか辛ぇ」。よくあるやつです。

もうちょっと具体的に、こういうことを言う人もいると思います。「『コード化するんだ』ってコードで書いたはいいけど、デプロイしてみたらなんかうまく動かん」と。よくあるやつです。今回の話のテーマはこれです。この状況、この“つらみ”みたいなものを、いかにしてうまく防ぐかが今回話したいところになります。

本日のアジェンダ

もうちょっと具体的なアジェンダに入ります。タイトルで“静的テスト戦略”という言い方を当てさせたのですが、なぜIaC、Infrastructure as Codeをやるときに、静的テスト戦略、静的テストが必要になるのか? というのが1点目ですね。

ここをまず押さえておきましょう。大前提というか、我々の理解、問題意識みたいなところを、最初に合わせておきたいと思います。

2番目として、今日は具体例としてAWS、AmazonのWebサービスの上でIaCを実現することを考えます。1番最初の問題意識を踏まえた上でAWS上で実行するとしたら、どうするんだという、コンクリートな部分を2番目に持ってきます。

最後に、実際に実行しないとしょうがないので、何をテストするか、あるいはどうやってテストするかの戦略。あるいは、もっと具体的なストラテジーの部分についてお話したいと思います。今日はこの3本立てです。

laCにおける“静的テスト”

まず、IaC(Infrastructure as Code)における“静的”テスト。カッコをつけて強調しました。静的テストについて話しましょう。

まず「そもそもInfrastructure as Codeって何でしたっけ?」というところからいきましょう。よく聞く話ですが、『Infrastructure as Code』という本を読んだ方も、たぶんたくさんいると思います。オライリーの有名なやつです。

ここで言っていること、定義しているものを一言でまとめると、ソフトウェア開発のベストプラクティス。今日はDevOpsDaysですが、DevとOpsがいたとしたらDev側のベストプラクティスをOps側に持ってこようと。それを使ってインフラオートメーションしたり、トイルを削減したりしようというところが、Infrastructure as Codeの肝なわけです。

具体的には、Gitにファイルをチェックインしてバージョンを管理したり、あるいは、GitHubでプルリク(プルリクエスト)を出すとコードなどでレビューできたり、その履歴が残るみたいなところです。

CIやCDもその1つです。要するに、今まではオペレーターが手順書をWord、.txtなどに書いて実行していたものを自動化すると。今回のテーマであるテストもそこに含まれます。いつでもインフラストラクチャーは、最新でGitとシンク(シンクロナイゼーション)している状況が作れるのが、IaCの肝、いいところです。

チェシャ猫流、laCのパターン

この原典によると、IaCはいくつかのパターンがあり、4つの分類が書いてあります。ただ、ちょっとここで私流。チェシャ猫流というか、少し再定式化を加えたいと思います。

というのも、新版も出ていますが、本としてはわりと古くて。例えば、AWSのエコシステムみたいなところは、まだ今ほど整っていなかった時代の考え方だろうと思われる部分もあるので、ちょっと分類を変えましょう。

こんなマトリクスを考えます。2×2の4象限です。まず横のラインは、LocalとGlobalに分けます。責任範囲というか、なにか変えたときに、その影響がそのリソースに閉じているようなものをLocalなものと呼びましょう。

逆にGlobalは、なにか変えたときにリソースを跨いで変更が波及する。例えば、具体的にはなにか1ヶ所変えたら、全部をそれに一致させて変えなければいけないようなパターンです。情報がほかのところも連携していて、全体としてconsistentになっていないとうまく動かないようなものをGlobalとします。これが第一の軸。

もう1つの軸はMutableとImmutableです。まず、従来型のインフラやサーバーなどの管理がMutable。つまり、既存のリソースがあったときに重ねがけして、リソースそのものは生き残っていた上でアップデートしていくようなパターンがMutableのインフラです。逆にImmutableは更新はしません。常に消して作り直すものをImmutableと呼びましょう。

有名どころのツール類をマッピングするとこんな感じです。コンテナベースになっているようなDockerやKubernetesは、Immutableなインフラを実現する上で非常によく使われる手段です。サーバーを立てておく代わりにコンテナを立てて、終わったら消す。次は新しいPodが立つ。そのため、そいつは記憶を持ちません。

左側はどちらかというと古典的というか、初期型のIaCの実現で、GlobalなものとLocalなものが両方あります。1つのサーバーに対して変更を加えるようなものがLocalで、スタック丸ごとに対して変更を加えるようなものがGlobalなものです。

Global+Mutableから起こる“オートメーション恐怖症”

今日のテーマは、「IaCのなんかつらい」というやつでした。それを考えるために、今回はGlobalであって、かつ、Mutableなものに注目します。4象限のうちの一番肝になる、左下の象限です。

ではなぜGlobal+Mutableがつらみにつながるのか? たぶんみなさん感覚的には「いや、これつらいよね」というのがわかると思いますが、もうちょっと言語化していきましょう。

まず、Globalは局所的な改善が難しいです。1ヶ所変えようとすると、全部一気にconsistentに変えないといけない。そのため、「今回はここの部分だけちょっと変える」のは、一般的には厳しいわけです。

さらに、Mutableなので1回なにか変えてしまうと、次もそれが残り続けます。だからワークアラウンドみたいなものを入れてしまうと、そのあとずっとifみたいなものをずっと書いて残しておかなければいけなくなる。

この2つがあるので、どうしても左下に囲んだ象限(Global+Mutable)は、インフラの現物が「神のみぞ知る」みたいな、カオス状態になりやすいわけです。そうなると、次の、例えば「コードで書いたぜ、アプライしよう」みたいなときにやばいわけです。何がやばいかって、何が起きるかがよくわからないからやばいわけです。

実際に組織でよくあるのは、IaCを推進しようとして「これでいけます」とアプライしてみたらメチャクチャ事故ると。事故るとどうしても「このツール大丈夫か?」みたいな話になるし、組織的には「Opsしっかりしろよ」みたいなことをDevから言われるわけです。というのがあって、アプライするのが怖くなってくる。

そうすると人間の自然な力学として、「とりあえず今回だけだから手で書いちゃおう」と、例外的なハンドオペができるわけです。なんかどこかで聞いたような話です。

それが起こるとどうなるかというと、スクリプトから外れたことをやるので構成が不均一になったり、意図していなくても、例えばなにかのバージョンが古くなったりなどでうまく管理されていない部分、時間が経つにつれて古くなってきてしまう部分、ほつれてきてしまう部分があるわけです。

これは、元の本だと“システム疲労”、“エロージョン”と呼ばれるものです。エロージョンは、崖が水で侵食されるとか、削られる、みたいな意味です。

もっとソフトなことをいうと、担当者が嫌になって辞めるとかがあるわけです。そうすると、ノウハウごと消えてしまう。そうすると、一番上のところに戻って、余計インフラの現状がカオスになっていくわけです。

このサイクル、この回転を回ることで、めでたくインフラが塩漬けになる負のサイクルが完成したというのが、すごくよくある、よく共通して見られる現象です。元の本では“オートメーション恐怖症”と呼ばれています。

負を断ち切るにはなにが起こるか予測ができればいい

そもそも、どこかで切らなければネガティブなポジティブフィードバックがかかるので、どこかで切らなければいけません。どこで切るかを考えたときに、その肝になるのは「何かが壊れそうで心配」の部分です。「なにかが壊れそうで不安だ」というところが、仕組みによっては、このサイクルを回している中で、一番コントローラブルな部分なわけです。

裏を返せば、なにが起こるかわからないので不安なのであれば、なにが起こるかわからない状況を減らすべきだというのが、考え方として自然な方針になります。そこで、今回テーマに取り上げたい言葉が“予測可能性”です。

つまり、アプライする前になにが起こるか予測できればいいわけです。もともと「Infrastructure as Codeって、アプリの管理をインフラに輸入しようとしたら、なんか微妙につれぇじゃん」みたいになるという話でした。

アプリとインフラとの違い

ではアプリと比較したときに何が違うのかを調べると、なぜインフラは特に予測可能でないように感じられるのかがわかるはずです。開発のプロセスからちょっと掘ってみましょう。

こんな感じで、よく出てくるVモデルです。開発の場合、要件定義があり、外部設計して、詳細設計して、実装があり、登っていくときには単体テスト、結合テスト、受け入れテストと、だんだんでかくなっていく仕組みになっています。横に対応しているものがそれぞれensureしている。

TDD (Test-Driven Development)などは前後しますが、コーディングしたものを単体テストして、それを設計にフィードバックしてという、いわゆる三角測量というものです。それが、一番小さい囲みの部分、小さいサイクルで回せるので、アプリの開発はテストがうまくワークするというのが、モダンなアプリ開発の考え方です。

これに対してインフラはどうなっているかというと、こんな感じです。重要な点が2点あります。まず絵で見てわかるとおり、単体テストが欠けている。

インフラのテストで有名なテストだとServerspecなどがありますけど、インフラが構築されたことをテストするのは、単体ではなくて、全体が揃わないとテストできません。だから単体テストがまず不在である。これでサイクルの1回がどうしてもでかくなりがちです。

もう1つ質的に違いがあるのが、単体テストとServerspecみたいなインテグレーションテストの間で壁がある。つまり、ローカルで実行できるようなアプリのソースコードと違い、インフラはインフラの現物がないと一般的にはテストができません。そのため、デプロイしないといけない壁があるわけです。デプロイしたものに対して、Serverspecが実行する。

この壁はもちろん単純に実行しなきゃいけないのもありますが、もっとソフトな部分で、例えばデプロイすると、モノがいるので、AWSにお金がかかりますよ。

あるいは、会社で働いていると、統制上デプロイできる手段が限られているとか、部長のハンコがいるとか、いろいろあるわけです。一般に、そこにはすごく高いハードルがある。組織的に切られていて、特定の部署しかデプロイの権限を持っていない、みたいな。

いろいろなところに壁があり、アプリのように簡単にテストすることができない状態になっている。この壁の存在が、アプリとインフラの開発プロセスを分ける、一番大きな要因です。ということは、ここに差がある。そのため、その差を吸収する方法を考えればよいことになります。

ここまでの議論を踏まえ、デプロイの壁の手前側でテストができれば、いわゆる「インフラつれぇじゃん」みたいなものがなんとかなるのではないかというのが今回の考え方で、タイトルの静的テスト戦略に戻ってくるわけです。インフラだからこそ、静的にテストする。ユニットテスト相当のところをやる必要があるだろう、というのが、今回私の主張したいことです。

セクション1のまとめ

というところで、セクション1を1回まとめておきましょう。IaCはアプリ開発のプラクティスをインフラに輸入してきました、という話でした。

特に今回は、つらみの原因を知りたい。そこを掘っていくと、どうもGlobal×Mutableの領域はやばそうだというのがわかる。なぜやばいかを極論すると、予測の可能性を担保しなければならないところがつらいわけです。

つまり、実行したときになにができるかわからん。どういう状況になるかわからんのが肝で、そこの恐怖をどうにかしてconquerするのが、よいInfrastructure as Codeのための第一歩になります。

具体的にどうすればいいかというと、アプリのV字の開発プロセスをモデルにして考えると、デプロイ前にテストすることによって、デプロイ前に何が起こるかを実際に影響を及ぼす前に知れるのではないかというのが、セクション1で話したところです。

(次回につづく)