アジャイルにとって不変なのは価値であって、方法論は状況に応じて変化していく

以前の記事で少し触れた「Agile Is Dead (Long Live Agility)」について、InfoQ が Dave Thomas氏本人に行ったインタビュー。

2001年、ユタ州のスノーバードに集まった、Kent Beck氏を始めとするソフトウェア開発の思想的リーダー17名。そこで自分たちの思想の共通点は何なのかという事を議論し、その成果を「アジャイルソフトウェア開発宣言」として世に公表したとき、その宣言がここまで大きく世の中を変える事になるとは全く想像していなかったと、Dave Thomas氏は語る。

agile

この宣言が発表される以前から、Kent Beck氏らによるエクストリーム・プログラミング(XP)や、Martin Fowler氏の記事「The New Methodology」など、開発者コミュニティの中では、アジャイルのような思想を受け入れる素地が出来上がっていた。

このように最初は開発者による日々の実践だったものが、成功したスタートアップの秘訣として紹介されるなどして、徐々に開発者以外の注目を集めて行く。

この過程を、Thomas氏は面白い表現で説明している。組織の管理者がアジャイルの評判を聞いて、それを自分の組織にも導入しようと号令をかける。すると、血に引き寄せられるサメのごとく、その周りをコンサルタントやコンサルティング会社が旋回し始める…

当然の事ながら、コンサルタントのアドバイスは管理者受けが良いように最適化されるため、その段階で個人の主体性を大事にするアジャイルの価値は失われてしまった。

Thomas氏曰く、「我々が提案する〜をやればアジャイル開発を実現できます」というコンサルタントは間違っている。理想的なコンサルタントはこのように言う、

「あなた達にとって何が最適か、私には分かりませんし、あなた達自身にも分からないでしょう。でも、とりあえずどこからかスタートすることは出来ます。まずは適当なプラクティスを選んで一ヶ月間実践してみるところから始めてみましょう。その後、何がうまく行って、何がうまく行かなかったか評価をします。そして、その評価に基づいてやり方を変えて行きます。おそらく最初に採用するプラクティスは、XPやスクラムの本を参考にするでしょう。でも、今から半年後、あなた達のやっていることは、どの有名な手法にも当てはまらなくなっています。何故なら、自分たちに最適な方法を自分たちで編み出す事になるからです。やり方はずっと変わって行きます。変化を楽しみましょう。」

アジャイルの導入として、これ以上分かりやすい説明はないのではなかろうかと、個人的には思う。

アジャイルで不変なのは「アジャイルソフトウェア開発宣言」にも謳われているその「価値」であって、方法論はチームによって、あるいはチームが置かれている状況によって変化する。

ここが、アジャイル以前に存在したウォーターフォール手法などとの決定的な違いである。そもそも同列に比較されるものではない。価値原則に照らし合わせれば、アジャイルを方法論として売り込む言説に対しては常に疑ってかからなければならない。逆に言えば「〜を実践してないなら、お前らはアジャイルじゃない」ということも言えないはずだ。

Thomas氏は、アジャイル実践手法の基礎となる、メタプラクティスなるものを提案している。それは、

  • 自分たちの現在地を把握する
  • ゴールに向けて小さなステップを踏み出す
  • そのステップによって起こった事を評価し学習する
  • 繰り返す

によって構成されるプロセスだ。ここに具体的なプラクティスは何も含まれていない。自分たちのアジリティーを向上させたいのなら、このプロセスをあらゆるレベル(関数の命名レベルの細かさから、新しいビジネスの計画に至るまで)に適用出来るように、自分たちでプラクティスを編み出して行かなければならない。

改めて考えてみると、管理者というのは常に「状況をコントロールしなければならない」というプレッシャーを受け続けている。計画出来ずに「今はまだ何も分かりません」では、ステークホルダーを説得出来ないからだ。そのような管理者によって、アジャイルは流行のバズワードとしてステークホルダー向けのプレゼンテーションに利用されてきたのかもしれない。しかし、アジャイルの考え方というのは、そのような(事前に青写真を書かないといけないという)ビジネスのあり方自体に疑問を投げかけるものだ。

TDD再考 (2) – 何故、ほとんどのユニットテストは無駄なのか?

TDD is dead」の最後で紹介されている、TDD否定派として有名な James O Coplien氏の論文。なかなか読み応えがある。

ユニットテストには基本的に価値がないので、よりビジネス価値を反映する粒度の大きなテスト(functional tests, integration tests, system testsなど)に軸足を移すべきである、というのがこの論文の論旨。

何故ユニットテストに価値が無いのかという部分の説明についてはなかなか説得力がある。論点を挙げてみると、

  • TDDの実践者が言及するテストカバレッジ100%には意味が無い
  • バグを取り除く最大の機会はテスト以外のところにある
  • ユニットテストにビジネス価値はほとんどない
  • TDDはプログラムデザインに害を及ぼす
  • ユニットテストの維持コストは馬鹿にならない
  • テストがコードのクオリティを上げる訳ではない、上げるのは開発者自身である

一つ一つの論点を追いかけてみよう。

テストカバレッジ100%には意味が無い

Coplien氏によれば、一般に言われるカバレッジ100%というのは、計算理論の見地から言えばまったくのナンセンスであり、本当の意味で100%を目指すのならば、到達可能なパスの全パターンと、その時にアクセス可能なデータの全パターン、それら全部の組み合わせを検証しなければならないとのこと。そうでなければ、正式にそのプログラムが「正しい」とは言えないのだそうだ。

TDDのメリットとしては、もちろんプログラムの信頼性向上も謳われる事が多いが、いわゆる品質保証としてのテストとは区別される事がほとんどで、TDDとしてのフォーカスはプログラムデザインに当てられる事が多いように思う。なので、この批判はTDDに対する攻撃としてはそれほど有効ではないように思える。

一方、ユニットテストを品質保証の根拠として捉える向きには、計算理論的にコードの正しさを証明する事は非現実的であり、ほとんどのケースでは恣意的な基準によって判定されていることを明らかにした、ということになるだろうか。しかし、果たして未だにそのような勢力(ユニットテストを品質保証の根拠として捉える向き)が存在するんだろうか…?

それとは別にして、プログラマの仕事をメトリクスで計ろうとする事の落とし穴については注意深く考えた方がよいだろう。筆者自身も過去のプロジェクトで、テストプログラムを書いた経験のないプログラマが、カバレッジを基準値まで上げるために、ただプログラムを実行するだけで Assertion が全くないようなテストプログラムを書いているのを目撃した事がある。

バグを取り除く最大の機会はテスト以外のところにある

これはウォーターフォール時代から言われていた事だが、プログラムの品質にもっとも重大な影響を及ぼすのは、要求・ドメインの分析結果を設計に落とし込むタイミングである。

その前提の上で、ユニットテストという高コストな手法でバグを潰すのは、果たして費用対効果的にどうなんだろうかという話である。

TDDも設計手法ではあるが、Coplien氏は他の論点でも述べているように、TDDはプログラムデザインに害を及ぼすと主張している。なので、プログラムの信頼性を向上させる手法として、TDDに従来の設計手法以上の価値があるとは認めていない。「コードのクオリティ向上に寄与するのはテストではなく、結局のところそれは開発者自身である」というのが、彼の基本的な立場である。

ユニットテストにビジネス価値はほとんどない

おそらくこれが一番重要な論点だと思われる。Coplien氏によれば、ビジネス要件から導出されたテストだけがビジネス価値を持つ。ほとんどのユニットテストは、ビジネス価値とはあまり関係のない開発者の独断によって設計されている。

もちろんユニットテストにビジネス価値と直結する部分が全くないとは言い切れない。例えば、キーとなるアルゴリズムを実装している部分などだ。しかし、基本的にビジネス価値はより粒度の高いテストで表現すべきである、というのがCoplien氏の主張である。

ユニットテストの価値を計る方法として、もしそのテストが失敗したら、どのようなビジネス価値を毀損することになるかを考える。もし、よく分からない、あるいは明確でない場合、そのテストのビジネス価値はほとんどゼロだということになる。価値が無いのにも関わらず、そのテストを維持管理するのにはコストがかかる。であれば、そのようなテストは捨てた方が得策である。

TDDはプログラムデザインに害を及ぼす

これはDHH氏も指摘していたが、TDDがプログラムデザインを改善するという客観的な根拠や事例はなく、それを調査した研究によればむしろ逆の結果が出ているようだ。

TDDが及ぼす悪影響として、プログラムを過剰に細分化してしまうため、ドメインとプログラムで表現の粒度が釣り合わなくなり、プログラムが理解しづらくなる事、ユニットレベルでのビジネス価値が小さくなってしまうこと、などが挙げられている。

しかし、仮にCoplien氏が言うように「テストがコードのクオリティを上げる訳ではない、上げるのは開発者自身である」のであれば、TDDがデザインに与える影響を客観的に計る事自体が無理筋であるように感じられるのだが、どうだろうか。

ユニットテストの維持コストは馬鹿にならない

この問題はTDD支持者の中でも多くの人が認識するところだと思う。

テストコードもプロダクトコードと同様(あるいはそれ以上に)、デザインとメンテナンスにコストがかかる。

特にテストコードの設計においては、プロダクトコードとは異なる方法論を採用する必要があり、これが想像以上に難しい。ただテストコードがあるというだけで良しとしているプロジェクトも多いが、テストの品質がプロジェクトに与える影響は想像以上に大きい。

プロダクトコードの設計を改善するためには、より良いテストの設計手法を学習しなければならないが、そのためには前提としてプロダクトコードの設計手法に精通する必要がある。プロダクトコードの設計手法に精通しているのなら、何故設計手法としてのTDDにわざわざ手を出さなければならないのかという矛盾がある。

プロダクトコードの coupling(結合度)とcohesion(凝集度)を改善するために、テストを書くというのがTDDであるが、テストコードがプロダクトコードに強く依存してしまう(高結合度)という問題がある。プロダクトコードに大きな修正が入った場合の、テストコードに及ぼす影響は甚大であり、粒度が小さく数の多いユニットテストのメンテナンスコストは想像以上に大きくなる。

テストがコードのクオリティを上げる訳ではない、上げるのは開発者自身である

ユニットテスト・TDD批判のベースにある考え方であり、これはある種の正論として多くの人が賛成出来る部分では無いだろうか。

Dave Thomas氏の「Agile Is Dead」宣言にも見られるように、いわゆる銀の弾丸的な存在としてアジャイルやTDDがあるように喧伝されるために、このような違和感を表明しておきたいというのは多分にあるのではないかと思う。

Coplien氏は、件の記事の締めくくりに、コンピュータ資源が豊富になって何でも簡単に試したりやり直したり出来るようになると、自分の頭で考えなくなってしまう(ツールのアウトプットを盲信してしまう)とう話をしている。

参考資料

James O Coplien氏のTDD批判については、氏に直接話を聞いたという安井力氏のブログとFacebook上のやり取りが大変に興味深かった:

TDD再考 (3) – TDDが解決しようとした問題は何か?

TDD再考 (1) – テストファーストとユニットテストへの死刑宣告

いかにも物議を醸しそうな(そして実際に醸しまくった)この記事を、Ruby on Rails の作者である David Heinemeier Hansson (DHH) 氏が発表したのが去年の4月、ということはあれから既に一年以上が経過している訳で、今更感が若干漂うところではあるが、このシリーズ「TDD再考」では、このDHH氏の記事に端を発して行われた様々な主張や議論を振り返りながら、アジャイル以後のプログラムデザインについて再考してみたいと思う。

件の記事でDHH氏は、「TDDは死んだ」と高らかに宣言しているわけだが、まず始めに気をつけなければならないのは、DHH氏が言うところのTDDというものが、かなり極端な例を念頭に入れて想定されているという事である。それは彼が「fundamentalism(原理主義)」や「fanatical(狂信的)」のような強い言葉を使っている事からも伺われる。

彼の批判を紐解くとポイントは二つある。一つはテストファーストこそがプログラミングのあるべき姿であり、プログラムのクオリティはそれをいかに正しく実践しているかによって査定されるという考え方が蔓延した結果、実際にそれが有効かどうかとは関係なく、それを実践しているかどうかで技術者の値踏みが行われるようなってしまったということ(ドグマ化)。そして、二つ目は、厳密な意味でのユニットテストでTDDを実施してプログラムデザインを導出すると、必要以上の細分化(これは James Coplien氏による指摘)と間接化(大量のMockオブジェクト)によって、過剰な複雑化を招くという事である。

そして、テストファーストという実践は捨てて、ユニットテストよりもシステムテストの方に重心を移すべきだと提案している。ユニットテストの対抗として挙げられているのが、データベースアクセスも含む形で Rails の Active Record をテストする例や、Webブラウザ経由のテストを自動化するフレームワーク「Capybara」などである。

実際にアジャイルやTDDの実践者の話を思い返しても、彼らがDHH氏が言うところの厳密な意味でのユニットテスト(依存関係は全てMockにして、対象となるユニットだけをテスト対象にする)にこだわっている印象はない。むしろ、DHH氏と同様、過剰なMockの導入は無駄が多いし、本来テストすべき箇所をテスト出来ないという問題意識を持っている人が多いように思う。Mockにこだわるのは厳密な Outside-in デザインにこだわる Behavior Driven Development (BDD) の一派ではないだろうか。実際にオリジナルTDD派とBDD派にはそのような対立も散見される。

TDDがドグマ化しているという指摘が、この記事の主題であり、多くの人の興味と共感を得るきっかけとなった部分だと思われるが、仮にそれが正しいとして、何故このようなことが起きたと考えられるだろうか? 

まず、TDDが提案した、プログラムデザインとテストを一体化するという考え方のインパクトが大きかった事が挙げられるだろう。ここにはドグマ化の要因となる二つの要素があったと考えられる。一つは、DHH氏も指摘しているように、これまでのソフトウェア開発の中で主要な不作為の一つだと見られていた、テストを書かない・しないということへの処方箋だったこと。専門家はこういった不作為を特に厳しく糾弾する傾向がある。そして、更に重要だと思われるもう一つの要因は、それが「要求駆動」のデザイン手法だったという事である。

テストをやらないというのは、将来起こりえる問題への不作為であるが、ここを過剰に追求するのはアジャイルとは矛盾すると考える事もできる。以前取り上げた記事の事例では、リーンスタートアップ的な実験フェーズにおいてテストを省略するという話が紹介されていた。

TDDが要求駆動であることの意味は大きく、それはアジャイルにとっても主題になるが故に、TDDがアジャイル実践の基礎だと捉えられている事が多い。そして要求駆動というのは、ユーザー中心設計や顧客中心主義のような、今の時代にとっての最大のドグマと言ってよい領域に属する。そして、これらの逆算的思考というのは本質的に反論がしづらい。

TDD再考 (2) – 何故、ほとんどのユニットテストは無駄なのか?

レガシーなWebアプリにサービス間連携のためのAPIを追加する

運用するWebアプリの数が増えてくると、それらの間で何らかの連携を取らなければならないケースが増えてくる。

例えば、二つのWebアプリ間での連携を考えてみる。最も典型的なのは、下図のように同じデータベースを共有するやり方だろう。

web-api1

異なる立場の人が同じ情報にアクセスする必要がある場合に、このような形でアプリケーションを分ける事はよくある。例えば、エンドユーザーとシステム管理者が使うアプリケーションを分けておきたいケースは多いと思う。

二つのアプリケーションが、同じデータベースに対して等しく関心を持つのならこれで何の問題も無いように思えるが、同じデータベースを扱うので、少なからずデータアクセスの処理やビジネスロジックが重複する可能性が高い。ただ、この段階では、コミュニケーションコストを考えて若干の重複には目をつぶるか、共有ライブラリを作るぐらいの解決策が適当で、Web APIを作って共有するのは大げさに感じられるかもしれない。

Webアプリの横恋慕的依存関係

では、以下のケースだとどうだろうか?

web-api2

二つのWebアプリは、それぞれに関心を持つ別々のデータベースにアクセスしており、一見それぞれ独立しているように見えるが、例外的に「Web app 1」の方は「Web app 2」で管理している情報の一部にアクセスする必要が出てきた。果たして、「Web app 1」は直接「Database 2」にアクセスすべきだろうか?

というのが、たまチームが最近直面した問題であった。

サービス全体が小規模、あるいは二つのWebアプリが両方とも同じ人間によって開発されているのなら、手っ取り早く「Web app 1」から直接「Database 2」にアクセスしてしまうのもアリかもしれない。しかし、Webアプリごとに別々の担当者がいるようなケースでは、自分の担当するアプリケーションのデータベーススキーマを変更する必要が出てきた時に、そこに依存している他のアプリケーションの動作に影響を与えないよう、細心の注意を払わなければならなくなる。

あるいは、このような例外を際限なく認めると、どのWebアプリがどのデータベースにアクセスしているかを把握するのが難しくなってしまうという問題もある。

web-api-dep-hell

共有ライブラリ

例えば、最初のケースで挙げたように「Database 2」についての共有ライブラリを作って、それを「Web app 1」から利用するという手がある。これによってデータベースの変更をライブラリの背後に隠蔽出来るようになり、より安全に変更を行えるようになる。

web-api3

しかし、ライブラリ方式にも問題はある。まず一つは配布の問題である。一度作って配布したら終わりというわけにはいかないので、変更がある度に利用者に知らせて、利用者はライブラリを更新し、アプリケーションをビルドし直す必要がある。あるいは、ライブラリ方式は言語やフレームワークに依存するというのが一番大きな問題かもしれない。全てのWebアプリが同じ言語・フレームワークで開発されていれば問題ないが、このMicroservicesの時代ではそうでないことも多々あると思われる。実際に、たまチームでは「Web app 1」と「Web app 2」で異なるフレームワークを利用していたために、ライブラリ方式を選択肢に入れることは出来なかった。

Web API

そこで選択肢として残ったのが、「Database 2」を利用するための Web API を立ち上げて、「Web app 1」からはそのAPIにアクセスするという方法である。

もし仮に Web APIサーバーを立ち上げる場合、データアクセスやビジネスロジック部分は共有したいので、「Web app 2」からその部分を切り離して単独のAPIサーバーとし、残った「Web app 2」のフロントエンド部分と「Web app 1」でそのAPIを共有するというのが、一番美しい、いわゆるマイクロサービス的な解法のように思える。

web-api4

もし、今から「Web app 2」を開発するなら、今風に始めからAPIサーバーとフロントエンドを分けた形で実装するだろうと思う。しかし、「Web app 2」は数年前にフルスタックなフレームワークの上に構築された、ガッチガチな一枚岩のWebアプリであり、ロジック部分だけを切り出してAPI化するというのは結構な大手術になってしまう。「Web app 1」側から利用する機能もそれほど多くなく、独立したAPIサーバーを置くほどのメリットも感じられなかった。

Webアプリに追加したAPIへのアクセス制御

そこで、「Web app 2」に Web API を追加することにしたが、この場合に問題になるのは API へのアクセスコントロールである。「Web app 2」は、エンドユーザーが利用するWebアプリなので、そこにそのまま API を追加すると、エンドユーザーもそのAPIにアクセス出来てしまう。

web-api5

APIに認証を追加するという方法も考えられるが、セキュアな認証の仕組みを導入すること自体が大仕事である上に、本来の目的がアプリ間連携であることを考えると、もっとより簡単で安全な方法があるような気もする。

そこで思い付いたのが、元から設定されているエンドユーザー向けのTCPポート(80番)の他に、新たにAPI専用のポート(8091番)でもリクエストを受け付けるようにして、そのポートに対してファイヤーウォール(たまチームではAWSのセキュリティグループ)でアクセス制御するという方法である。

web-api6

たまチームの「Web app 2」はTomcat上で動くWebアプリだったので、API用のHTTPポートを追加するためには、設定ファイル conf/server.xml に以下の一行を追加するだけでよかった。

<Connector port="8091" protocol="HTTP/1.1" connectionTimeout="20000" />

ポートを追加するだけだと、依然として既存のポートでもAPIにアクセス出来てしまうので、API内部ではアクセス元のポートを調べて、専用ポート(8091)経由の場合にのみアクセスを許可するようにする。

たまチームのケースでは、Webアプリはクラスタリング(冗長化)構成になっていてロードバランサーが間に入るため、実際の構成は以下のようになった。

web-api-in-aws

開発者を叱責の無間地獄から解放するためのメトリクス

DevOps色の強いインフラチーム(infrastructure team)に所属していた著者が、DevOpsの原則が通用しないプロダクトチーム(product team)に移って受けたカルチャーショックと、そこから、従来の「運用チーム(operations team)」という考え方は止めて、プロダクト開発者のためのサービスを開発・運用する「プラットフォームチーム(platform team)」へ移行するべきだという発想に至るまでの話。

この記事では、運用というものが、障害を起こさないという方向に強く動機付けられていて、それがプロダクトを常に更新していかなければならない開発者の利害と衝突してしまうという問題を指摘している。障害が起きたら、たとえ深夜であろうが、まず対応しなければならないのは運用チームであるため、運用としては出来るだけ障害が起こらないよう、システムの変更に対しては保守的にならざるを得ない。

つまり、問題の本質は「維持」と「変更」の対立であって、これは運用だけに限ったことではなく、障害が起きた時に顧客対応しなければならない営業やカスタマーサポートでも同様の問題が起こる可能性がある。

DevOps界隈でよく話題になる、開発者から嫌われる運用チームの人物像というものがある。何故こんな人物像が生まれるかと言えば、「維持」に動機づけられた側の人間が、システムを変更し障害をもたらした人物に対して責任の追及を行う(finger-pointing)ということが頻繁に発生するからである。これは上に書いたように運用に限らず、「維持」と「変更」のトレードオフを理解しない人間によっても度々行われるため、組織で働く開発者というのは常に、完璧に防ぐことが原理的に不可能な問題で、叱責され続ける運命にある。

この問題を解決するためのヒントとなりそうなのが、この記事で紹介されている MTTRMTBF という二つのメトリクスである。

MTTRというのは Mean Time to Recovery の略で、障害が起きてから回復するまでにかかった時間の平均である。一方、MTBFは Mean Time between Failures の略で、障害から回復後、次に障害が起こるまでの時間の平均、つまり平穏無事にシステムが動き続ける時間の平均である。

「維持」に動機づけられた人間は、これら二つの指標の内、MTBF、つまりシステムが平穏無事に動きづける時間の最大化を目指す。これを MTTR の方に重きを置くように考え方をシフトさせる事が出来れば、開発者の精神的な負担は大分軽くなる。

「維持」と「変更」がトレードオフの関係であること、システムを変更し続けなければ組織のミッションは果たせないというところから、「変更」に対する理解を得ることはそれほど難しくない。しかし、どこまで障害に対して許容するかとなれば、利害が異なる二つのグループの間でそう簡単に折り合いがつく訳でもない。そのような時に「障害をいかに起こさないか」という考え方から「障害が起こる事自体はそれほど大きな問題ではない、問題はいかに早く障害を解決するか」という方向に考え方を変える事が出来れば、変更に対する抵抗も低減するし、変更は MTTR を最小化するための学びの場となる。

Nagiosから早朝のアラート「CHECK_NRPE: Error – Could not complete SSL handshake」

早朝4時にNagiosから何通かのCRITICALアラートが届いた。その後、10分程度でRECOVERYしたとの通知。

アラートの Additional Info の欄には以下のメッセージ:

CHECK_NRPE: Error - Could not complete SSL handshake.

ネット上で調べると、/etc/nagios/nrpe.cfg ファイルの allowed_hosts= を設定せよという情報がすぐに出てくるが、

今まで接続出来ていたものが突然出来なくなったという現象なのでこれには当たらず。

「繋がる時もあるし繋がらない時もある」という現象では以下のサイトと同様なのだが、

xinetdは使ってないので、これでもない。

Nagiosサーバー側には以下のようなログが出ている。

NRPE: Call to fork() failed
CHECK_NRPE: Error - Could not complete SSL handshake.
...

そこで辿り着いたのが、NagiosのTrackerにあった以下のIssue:

NRPE will fail to fork or run the check command(s) on a host with multiple simultaneous tests when the soft limit on open files is reached. (Even if the hard limit is higher.) I suggest, upon a failed fork, you use setrlimit to increase the soft limit as needed until the hard limit is reached.

何故この時だけファイルディスクリプタ数の上限に達したのかについては、毎朝4時に行われるウィルススキャンと関係しているんじゃないかと推測することも出来るが、この一度だけのアラートだけでは何とも言えない。今回はとりあえず様子を見て、再度発生したら具体的な対策を考えよう。

我々は何のためにソフトウェアを開発するのか?

達人プログラマー』の著者であり、アジャイルソフトウェア開発宣言の発起人の一人でもある Dave Thomas氏によって書かれたブログ記事「Agile Is Dead (Long Live Agility)」が、アジャイル界隈に少なからぬ波紋を投げかけたのは記憶に新しい。

その記事で彼は、アジャイルという言葉が、その人気ゆえに、マーケティングの道具にされて有名無実なものになっていることを嘆いていた。

同じくアジャイルソフトウェア開発宣言の発起人である Ron Jeffries氏が年初に出した「The Nature of Software Development」という本は、もしかしたらこの流れに何らかの影響を受けているのかもしれない。

この本でJeffries氏は、いわゆるアジャイルと呼ばれるものの本質について、アジャイルという語やその周辺の専門用語を極力使わずに、おそらく技術者でなくても理解出来るような平易な言葉で解説をしている。150ページほどのコンパクトな内容にたくさんの手書きイラストと専門用語を排した説明。2015年の今、アジャイルの(再)入門として読むには最適な書籍だ。

Dave Thomas氏の記事もそうだが、アジャイルが生まれてから十年以上が経過した今、アジャイルってそもそも何だ? というところに一度立ち返ろうという流れが出てきているように見える。

本来、アジャイルというものは、自分が置かれた状況に合わせてどのように行動したら良いか、ということを考える上での原則を与えるものであって、複雑な手順やツールを利用したり、専門用語に精通したり、ましてやスクラムの資格などとは本質的に関係のないもののはずである。

Jeffries氏は、我々は何のためにソフトウェアを開発するのか? それは「価値」を提供するためである、という当たり前(だけど忘れがちな)事実からスタートして、その効率を最大化するための基礎となる要素「Guiding」「Organizing」「Building」「Slicing」「Quarity」について説明する。

この本の中で個人的に面白いと思ったのは、「価値とは何か」という問いに踏み込んでいるところである。

これまでのアジャイルでは、価値の決定は顧客、あるいはそれに準ずる利害関係者に託されていた。開発の透明性を高めて判断材料を増やし、イテレーション開発で判断機会を頻繁に設けることで、より高い価値を目指す。

アジャイルの考え方を起業に応用したリーンスタートアップでは、さらに踏み込んで、計測可能な価値というものを考える。価値仮説を立てて、実験と計測を繰り返す事で価値の最大化を目指す。

そして、「The Nature of Software Development」では、基本的に価値は計測可能なものではなく「Value is what you want(価値とはあなたが欲しいもの)」だと定義する。一見、何も説明していないようにも読めるが、Jeffries氏は価値というものの多様性を説く。我々は色んなものに価値を認める。例えば「情報」だったり「人命」「資本」「スピード」「幸福」「創造性」などなど、あらゆるものに。

つまり、「価値とはあなたが欲しいもの」の意味は、価値、あるいは何が役に立つかという事は、その価値を受ける人とその人が置かれた状況・文脈に依存するのであって、その状況ごとに自分の頭で考える他はない、ということになる。

DevOpsの起源とOpsを巡る対立

たまチームとして技術ブログを書くようになって、ある日ふと気付いたのだが、そのメインのトピックの一つであるDevOpsという言葉の起源については深く追求しないまま今日に至ってしまっていた。開発と運用が一緒になるとか、あるいはインフラの自動化ぐらいのぼんやりとしたイメージはあったのだが、その運動を起こした人たちとその意図、歴史などについては無頓着なままであった。そこで今回はこの辺の事情について調査を行い、今の知識と照らし合わせながら現状の考えをまとめておきたい。

DevOpsの起源

まず、DevOpsの起源と歴史については以下のサイトに簡単にまとまっている。

Agile 2008 conference

これらの記事によれば、DevOpsという言葉が生まれる一年ほど前、2008年にカナダのトロントで開催された Agile 2008 conference にて、ベルギーのITコンサルタントであるPatrick Debois氏が「Agile Infrastructure & OperationsPDFのプレゼン資料)」というプレゼンテーションを行い、これが後のDevOpsムーブメントの萌芽となったらしい。

このプレゼンテーションで提示されている文脈としては、まず「開発と運用の分離」がある。「What is DevOps?」という資料にも歴史的経緯が解説されているが、元々は分かれていなかった開発(Development)と運用(Operations)という役割が、コンピュータの進歩と共に、次第にそれらを担う別々の専門家が生まれて、最終的には職能として、組織の中では別々の部門に配属される事が多くなったという流れである。

そして、発表が行われたカンファレンスのテーマである「アジャイル開発」も重要な文脈である。このプレゼンテーションのために実施されたアンケートでは、(科学的なサーベイではないと断り書きがあるものの)開発者はアジャイルという方法論に慣れ親しんでいるが、それ以外の部署、運用やインフラ、営業などになると、いわゆるアジリティーはかなり低くなるという結果が出ている。

そして、発表者のPatrick Debois氏は、開発と運用の両方で豊富な経験を持ち、両者の問題をよく理解している、いわば開発と運用のブリッジとなるような人である。その彼がプレゼンテーションで紹介しているのが、データセンターの移行というインフラの仕事をどうやってアジャイル(スクラム)でやるかという事例で、アプリケーションや運用チームがProduct Ownerになるという大変に興味深いものだ。

Velocity 2009 – O’Reilly Conferences

Patrick Debois氏の発表から約一年後、カリフォルニア州サンノゼで開催されたVelocity 2009というイベントで、Flickrのエンジニア(John Allspaw, Paul Hammond)によって発表された「10+ Deploys per Day: Dev and Ops Cooperation at Flickr」というプレゼンテーションがDevOpsという言葉を生み出すきっかけとなった。

このO’Reilly主催のVelocityというカンファレンスのことは知らなかったのだが、インフラ・運用(Web Performance & Operations)がテーマになっていて、参加者もどちらかと言えば開発者より運用系の人が多いようである。このカンファレンスの存在自体が「開発と運用の分離」という事情を象徴するようであるが、そこからDevOpsという言葉が生まれたというのはなかなか興味深い事実である。

さて、件のプレゼンテーションの内容はと言えば、開発者と運用担当者が共通の目的に向かって手を携える事で、一日に10回以上デプロイするような高速な開発を実現できるというものである。まさに Wikipedia などに書かれているようなDevOpsの定義につながる。そして、そこで指摘されていたのは、職能によって分けられてしまった開発者と運用担当者の利害が衝突するようになり、いわゆるセクショナリズムが生じて、いつまでも対立を続けるようになった結果、組織全体が目指すべき本来の目的を見失っているということであった。

この問題は、開発・運用だけにかぎらず、例えば、営業・開発の関係でも、職能で部署が分けられている場合は不可避的に生じる問題である。何か問題が発生した場合に、セクショナリズムが存在すると、その問題を「我々の問題」として認識出来なくなる。そして責任転嫁(finger-pointing)や責任を回避するような保守的な言動が横行するようになる。これは普通の会社の中で働いているたまチーム自身もまさに経験していることでもある。

そして、このプレゼンテーションをベルギーからストリーミングで視聴していたPatrick Debois氏が、「Devopsdays」というイベントを開く事を思い立ち、それが本格的なDevOpsムーブメントの始まりとなった。

devops

NoOps炎上事件

さて、以上の流れでDevOpsというムーブメントが生まれるまでの経緯は大体把握する事が出来たが、その後の2011年から2012年にかけて「NoOps炎上事件」とでも呼ぶべき興味深い事件が起こっているのでここで紹介しておきたい。

事の発端となったのは、2011年にマサチューセッツ州ケンブリッジに本拠地を置くフォレスター・リサーチという調査会社の出した「Augment DevOps With NoOps」というレポートである。

このレポートの主張は、クラウド環境の進歩により自動化が促進されて、将来的に DevOps は NoOps となる、つまり運用というものは必要なくなるというものである。開発者の立場で考えると、昨今のPaaSの隆盛などを見るに、普通に納得出来そうな将来予測ではある。しかしながら、この NoOps という言葉が、運用だけでなく、運用を担ってきた人間さえも否定するように響き、DevOpsという言葉を生み出すきっかけを作ったコミュニティに少なからぬ波紋を引き起こす事となった。

象徴的なのが、DevOpsでは頻繁にその名前が言及される Netflix という会社に当時勤めていたAdrian Cockcroft氏の NoOps に関する記事「Ops, DevOps and PaaS (NoOps) at Netflix」と、それに対するJohn Allspaw氏の反論である。

John Allspaw氏の反論を全部読み通すのはなかなかに骨が折れるが、その長さからして運用のコミュニティを代表する彼の怒りが伝わってくるようだった。しかし、そもそもここで問題にされている事は何なのか?

それは「運用」とは一体何なのか? ということである。Allspaw氏によれば、Cockcroft氏が言うところの「運用」とは、90年代に存在した古い存在であり、相互不理解が原因で開発者にとっての障害かつフラストレーションの原因となっていたが、その反省によってまさに DevOps が生み出されたのだと、そして現代の我々運用コミュニティの人間はとっくにそちらにシフトしているのだと、今更お前は何を言っているんだと、Opsを無くして悦に浸っているようだけど、「運用」という専門領域あるいは責任は変わらずそこにあるし、全然 NoOps になってないじゃないかと、そのような言葉の誤解を指摘している訳である。

この議論は果たして有益だろうか? Allspaw氏の反論は、Opsの誤用を諭しているようで、その実は、彼の所属するコミュニティが信奉しているOpsの定義を主張しているだけのようにも見える。つまり、彼はOpsコミュニティの利害を代表して抗議する事によって、DevOpsが避けようとしていた落とし穴に再び陥っているかもしれないのである。

専門用語の定義問題というのは、その専門性によって縄張り意識を生み出し、セクショナリズムにつながる危険性を孕んでいる。

物理から仮想へ

この言葉を巡る炎上問題から学べる事は何だろうか? それはおそらくDevとOpsという区別自体がナンセンスな時代になったということではないだろうか。その区別が引き起こしていた障害を解消するために DevOps が発明された訳だが、依然として言葉上では区別していたために「NoOps炎上事件」のような対立が起きた。

DevOpsの背景にあった重大な変化は、あらゆるものがソフトウェア化しているということである。分かりやすい例で言えば、仮想化技術によって実現されたクラウドやVPSのような、物理リソースから論理リソースへの移行である。その他、元々はOpsの領域だった、OSのインストールやコンフィグレーション、アプリケーションのデプロイなどの操作もコード化されるようになった。それぞれ、Infrastructure as Code, Operations as Code と呼ばれるものである。

そのようなソフトウェア化した世界にとって、Infrastructure や Operations の区別というのはそれほど重要ではなくなる。あるいは別のレイヤーの話になる。例えば、Microservices概念の出現によって、一つのサーバー(Service)と、プログラム上の一つのモジュールとの概念的な違いが無くなっていくように。

物理から仮想へと言っても、物理的なハードウェアが無くなる事はあり得ない。もしOpsという言葉が有効に機能する領域があるとすれば、それは物理的装置に関連する領域ではないだろうか。言葉というのは視点の問題である。Infrastructure も Operations も、コード化してソフトウェアとして扱えるようなれば、ソフトウェア開発の技術がそこに応用出来る。そこで重要なのはDevとOpsの区別ではなくて、物理なのか仮想なのかという問題である。

Microservices時代のプロジェクト管理を考える (2) – サービス開発者の責任 / 優先順位

水平型から垂直型への移行」によって、今までアプリケーションのビルドまで面倒を見ればよかったアプリケーション開発者は、(マイクロ)サービス開発者となって、サーバーの構築 (Provisioning) から運用の一部まで面倒を見ることになった。一つ一つのサービスの独立性が高くなり、開発者あるいは開発チームの自律性は高まるが、やることが増えた分、外部に対する責任をより明確にしておかないと運用やメンテナンスの局面で様々な問題に直面することが予想される。

前回も少し触れたが、開発者やチームの責任を考えるときは、ソフトウェアのモジュール、あるいはオブジェクト指向でいうところの「責務 (responsibility)」の考え方が参考になる。まずシステムが果たすべき役割を検討して、そこから個々のサービスの責務を割り出し、その責務を実現することを第一優先とする。責任を明確にする理由の第一は、優先度に沿ってリソースを効率よく分配することである。「そもそも我々は何のためにこのシステムを作っているのか」という組織やチームのゴールが共有されてないとバランスの悪いリソース配分をしてしまう危険性が高くなる。

よくある問題の一例として、技術者が技術者の責任を考えると、この優先度の考え方が欠落して必要以上に内部の問題に耽溺してしまうということがある。例えば、技術者の間でよく行われる成果物のチェック方法としてコードレビューがある。スケーラビリティやセキュリティといった比較的目的が明確で判定しやすい指標はよいのだが、可読性や保守性といった、人によって判断が分かれることが多い指標は、無駄に議論が長引いたり、最悪なケースだと技術者同士の衝突に発展してしまうケースもある。

お前さえいなければあああああ!!!
お前さえいなければあああああ!!!

プログラムデザインの良し悪しを判断する時に厄介なのは、重要性が高いものほど多くの人間の同意を取るのが難しくなることである。関数やメソッドレベルの設計はまだ良くても、クラス・パッケージレベルやフレームワーク、アーキテクチャレベルになると、必要になる前提知識も増え、考え方のスペクトラムも大きくなるため、宗教論争的になって同意に至るのは容易ではない。結果的に、声が大きい人の意見が採用されるか、衝突を避けるために同意しづらい事項についてはスルーして、反論されづらい些末な指摘ばかりを繰り返すことになる。

このようなことを避けるため、レビューをするとしても出来るだけ価値の高い外部視点のもの、例えばテストコードをレビューすることを過去の現場では奨めてきたのだが、これも現実にはなかなか難しかった。技術者同士でレビューするとすれば、単体から結合テストレベルのコードになるが、テストの価値が相対的に低いので些末な感じは否めない。受け入れテストなど、もっと上位のテストになれば、ユーザー視点からのレビューも可能になるというのが、Acceptance Test Driven Development (ATDD) などの考え方であるが、以前の記事でFitの失敗を紹介したように、この分野もなかなか前途多難である。

さて話を戻すと、まずサービス開発者の責任を明確にして、責任を果たす限りは自由な裁量に任せるという形にした方が、プロジェクトの参加者全員にゴールへの意識を持たせつつも、無駄なコミュニケーションコストを省くことが出来るし、ゴールと関係ない内部の詳細に耽溺することも無くなる。というわけで、次回以降では、まずサービスの受益者は誰かということを考えて、そこからサービス開発者の果たすべき責任を一つ一つ定義してみたいと思う。

運用中のサービスに与える影響を最小限に抑えつつ、EC2-Classic上のRDSをVPC上に移行する

たまチームで開発しているWebサービスの大半は AWS EC2-Classic 上で動いているのだが、去年から標準でサポートされるようになった VPC (Virtual Private Cloud) に移行しないといろいろと不便なことが多くなってきた。というわけで、移行にあたって一番厄介だと思われる RDS の移行手順を以下にまとめた。

ちなみにこの手順は、EC2-Classic から VPC への移行だけでなく、VPC から VPC の移行にも適用出来るはず。

rds-migration1

大まかな流れとしては、

  1. リードレプリカ経由でClassic側のdumpを取り
  2. それをVPC側で復元
  3. 復元したデータベースをClassic側のslaveとして動かしてデータを同期
  4. 読み込み専用のアプリはここでVPC側に移行して動作確認
  5. VPC側をmasterに昇格した後(ここで一瞬だけ Read Only になる)
  6. 書き込みのあるアプリケーションもVPC側に切り替えて
  7. Classic側を潰せば

めでたく移行は完了する。

1. リードレプリカ経由でClassic側のdumpを取る

rds-migration2

まずはClassic側で移行用のdumpを取得する。リードレプリカを作ってすぐにリプリケーションを停止し、そのときのdumpとバイナリログの位置を取得しておく。リードレプリカ経由なので、この間、サービスの運用には一切影響を与えない。

バイナリログの保持期間を変更する

デフォルトのバイナリログの保持期間が短いので、VPC-slaveを作る間にログが消失してしまわないよう、Classic-masterで以下のように変更しておく。

CALL mysql.rds_set_configuration('binlog retention hours', 24);

現在の設定を確認する:

CALL mysql.rds_show_configuration;

レプリケーション用のユーザーを作る

Classic-masterでレプリケーション用のユーザーを作っておく。

GRANT REPLICATION CLIENT, REPLICATION SLAVE ON *.* TO 'repl_user' IDENTIFIED BY 'some-password';

Classic-masterのリードレプリカを作る

AWS管理コンソールで Classic-master となるRDSインスタンスを選び、”Instance Actions” から “Create Read Replica” を選択する。

レプリケーションをストップする

Classic-slave(今作ったレプリカ)のレプリケーションをストップする。

CALL mysql.rds_stop_replication;

バイナリログの位置を記録する

Classic-slaveで、レプリケーションの状態をメモる(どの位置からレプリケーションを再開すれば良いか)。

show slave statusG
...
    Master_Log_File: mysql-bin-changelog.050354
Read_Master_Log_Pos: 422
...
   Slave_IO_Running: No
  Slave_SQL_Running: No
...

Slave_IO_RunningSlave_SQL_RunningNoになっているので、ちゃんとレプリケーションが止まっていることが分かる。

Classic-slaveのdumpを取る

  • Classic-slaveのエンドポイントを classic-slave.rds.amazonaws.com、管理ユーザーを dbuser とする。
mysqldump -u dbuser -h classic-slave.rds.amazonaws.com --single-transaction --compress --order-by-primary --max_allowed_packet=1G -p --databases database1 databases2  > classic-slave.dump

2. VPCにClassic-masterのレプリカを作る

rds-migration3

VPC内に立てたRDSインスタンスに、Classic-slaveのdumpをインポートしてClassic-masterへのレプリケーションを再開させる(VPC-slave)。

VPC-slaveとなるRDSインスタンスを立ち上げる

AWS管理コンソールでサクッと立ち上げる。

VPC-slaveにClassic-slaveのdumpをインポート

  • VPC-slaveのエンドポイントを vpc.rds.amazonaws.com、管理ユーザーを dbuser とする。
mysql -u dbuser -h vpc.rds.amazonaws.com -p --default-character-set=utf8 < classic-slave.dump

Classic-masterのセキュリティグループ設定を変更する

レプリケーションを可能にするために、VPC-slave から Classic-master にアクセス出来るようにセキュリティグループ(DB Security Groups)を設定する。

以下のような感じで VPC-slave のIPを調べて、そのIPからのアクセスを許可する:

$ ping vpc.rds.amazonaws.com

以下を Classic-master のセキュリティグループに追加。

CIDR/IP to Authorize:  xxx.xxx.xxx.xxx/32

レプリケーションをスタートさせる

VPC-slave にログインして、レプリケーションをスタートさせる(さっき作った専用ユーザーがここで活躍)。
パラメータは先ほどメモしたレプリケーションの状態を参考に。

CALL mysql.rds_set_external_master('classic-master.rds.amazonaws.com',3306,'repl_user','some-password','mysql-bin-changelog.050354',422,0);
CALL mysql.rds_start_replication;

レプリケーションの状態を確認:

show slave statusG

以下のような項目があれば、正しくレプリケーションが動作している。

...
 Slave_IO_Running: Yes
Slave_SQL_Running: Yes
...

レプリケーションがうまく行ったら、Classic-slaveは用無しなので削除する。
この段階で読み込み専用のアプリはVPC内に移行出来る。

3. VPC-slaveをmasterに昇格する

rds-migration4

Classic-masterをReadOnlyにする

FLUSH TABLES WITH READ LOCK;

Closes all open tables and locks all tables for all databases with a global read lock. This is a very convenient way to get backups if you have a file system such as Veritas or ZFS that can take snapshots in time. Use UNLOCK TABLES to release the lock.

として、全てのテーブルをロックしてから切り替えたいところなのだが、RDSではこの命令を実行するための権限がないことが判明。

簡単な代替策もないようなので、とりあえず編集を行う可能性があるサービスを全て「メンテナンス中」として操作出来ないようにした。

VPC-slaveのレプリケーションを停止する

CALL mysql.rds_stop_replication;
CALL mysql.rds_reset_external_master;

以後、こちらを master として扱う。

アプリケーションをVPC内に移動する

VPC内にアプリケーションサーバーを立てて、そちらを指すようにDNSの設定を書き換える。

後は古いClassic環境を潰して移行は完了。

ドキュメンテーションを成功させるには

ドキュメンテーションをうまく機能させるのは本当に難しい。

たまチームの開発は、同じフロアにいるマーケティングチームからの要求に従って行われている。ミーティングなどで要件のヒアリングを行い、それを簡単なタスクの箇条書きに落とし込む。PDCAサイクルを一週間(場合によっては二週間)ごとに行うイテレーション開発を採用しているので、全ての要件を一度に明らかにせず、次のイテレーションのタスクが埋まる程度にヒアリングを行う。ミーティングではイテレーションの成果を踏まえて次の要件やタスクを考えるので、想定外の事象が発生しても早い段階で軌道修正を行える(ここに落とし穴があったのだが、これはまた後日)。

いわゆるアジャイル的な開発である。進捗は可視化されていて、軌道修正もしやすく、見当違いなものを作ってしまうリスクも抑えられるので、なかなか効果的な開発方法に思えるが一つ大きな問題があった。それは、きちんとしたドキュメントを残すタイミングがなく、ほとんど全てが現物主義になってしまうことだ。

そもそもアジャイルという手法に現物主義の傾向があるとも言える。動くソフトウェアとクリーンなコードが何よりも現状を雄弁に語るという考え方だ。

しかし、それはある程度小規模な開発であれば当てはまるかもしれないが、一年以上に渡って開発し続ける複雑なシステムではなかなか難しい状況になる。たとえ動くソフトウェアがあったとしても全貌を把握することは難しくなるし、全てのコードをクリーンに保つことも現実的には難しい。

ある日、こういうことがあった。開発がスタートしてから一年半が経過し、システムの機能も大分増えてきた頃だ。

マーケティング担当者「そう言えば、この機能って XXX のような仕様だったと思うんですけど。」

開発者「え、そうでしたっけ。私は YYY と聞いたように記憶してるんですが。」

マーケティング担当者「うーん、それは困るな。お客さんになんと説明すればよいのか。。。」

もの凄くありがちな光景である。イテレーションごとに現物を確認していても、同じ機能を何度も修正している間に、マーケティング担当者の想定と現物がズレてきてしまったのだ。

その後もシステムはどんどん複雑化することが予想されたので、たまチームとしても何か対策を打たざるを得なくなった。仕様書を書こうということになったのだが、常に変化し続けるシステムの仕様書を維持管理することの難しさは重々承知している。そこで、どのようにドキュメントを管理したよいかを入念に検討して以下の指針を作った。

  • 分かりやすさ: XXXを知らない人でもシステムの目的・機能要件が理解できること
  • 網羅性: 要件が一通りカバーできていること
  • 探しやすさ: 必要な情報をすぐに見つけられること
  • ステータス管理: 要件ごとに「実装待ち」「実装済み」「検証済み」のようなステータスを管理すること
  • 変更容易性: 文書の内容が常にup-to-dateになるように、変更のコストを最小化すること
  • テスト仕様書: この文書を見ながらシステムを操作して要件をテストできること

今回のお題である「Lean Documentation」に書かれている指針と大体同じである。Lean Documentationで紹介されている「良いドキュメンテーションのためのルール」は以下の通り:

  • 素早く書けて更新出来るようにする。現状を反映してない情報は、まったく情報が存在しないことより有害である。
  • 簡単に正しい回答を得られること。必要な情報が見つけづらければ誰もそれを利用しなくなる。
  • ドキュメンテーションが対話を置き換えるわけではない。「プロセスやツールよりも、個人や対話に重きを置くべきである(Manifesto for Agile Software Development より)」。

変更容易性はかなり重要な要件で、これを考えるとWordやExcelなどで仕様書を管理することの問題がよく分かると思う。プロジェクトメンバー全員で情報を共有しつつ、かつ変更も容易に行えるとなると、データベース的な仕組みがどうしても必要になる。

昨今の開発プロジェクトだとWikiで仕様を管理することも多いのではないかと思う。しかしWikiでも、まだ変更容易性に若干の不満があり、ステータス管理やフィードバックなどの扱いに難がある。そこで、たまチームではチケットシステム(具体的には GitHub の Issues)を試してみることにした。

仕様を独立した小さな単位に分割してチケットとして管理すれば、その単位でステータスを管理したり、フィードバックをチケットのコメントとして行えるようになる。変更についても、そのチケット自体を修正したり、破棄したり、また新しく作ったりと、より柔軟に行える。どのような機能が修正されたかも把握しやすい。

全体の構成について、Lean Documentation では「Structure it like Google Earth(グーグルアースのように構造化せよ)」というプラクティスが紹介されている。全体像から詳細に向かって徐々にズームインしていく仕組みがあれば、情報が格段に見つけやすくなるというわけだ。たまチームでは、細かい単位であるチケットだけだと全体像が見えないので、Wikiページに概要とチケットへのインデックスを作った。

「さて、これで完璧なドキュメントが出来上がったぞ(ドヤァ)」

数ヶ月後、苦労して作ったドキュメントは誰にも読まれなくなっていた。読まれないので更新もされない。ドキュメントは徐々に現実からズレていく。

つまり、大失敗である。

原因は一体何だったのだろうか? Lean Documentation にあるように、ドキュメントがマーケティング担当者のニーズに沿った構成や内容になっていなかったのだろうか(「Practice 1 – ドキュメントの利用者とその目的を特定せよ」)?

マーケティング担当者曰く「GitHubを見ることが習慣化していないというのと、同じフロアにいるので、つい直接尋ねてしまう。」

ドキュメンテーションを成功させるために、最も重要でかつ最低限必要なことは「(継続的に)読まれること」である。

このことは文書そのものをどう書くかということよりも遥かに重要である。文書が適切に書かれるから読まれるのではなくて、読まれるから適切な形に変わっていく。読まれなければその文書は死んだも同然である。

ドキュメンテーションというのは固定化されたものではない。コミュニケーションの記録である。口頭の対話は記録されないが、ドキュメンテーションという対話は記録され、かつ構造化される。かなり効率は落ちるが、その記録が残ることの価値はかなり大きい。

ここで重要なのは、「読まれること」は「読ませること」ではないということだ。「GitHubを見ることが習慣化していない」という人に対して「じゃあ、習慣化して下さい」と言うのは間違っている。ドキュメンテーションを行うものは、その文書を読んでもらうためのあらゆる努力をしなければならない。それには、Web上にブログを開設して読者を集めることと同様の難しさがある。

仕様を表現するための一手法である「Specification by Example」も、手法としては素晴らしいと思う。Living document のコンセプトも良いし、Given-When-Then 形式の Acceptance Test Scenario(あるいは、Acceptance Criteria)も分かりやすく書きやすい。実際に、たまチームの一つのプロジェクトでは英語話者と日本語話者が効率的にコミュニケーションするためにこのフォーマットを採用している。しかしこれでも「読まれること」とは、全く別次元の話なのだ。

ビジネスパーソンとエンジニアは、そもそも異なる言語で会話をしている。アジャイルコミュニティではドキュメントの限界を理解しつつも、テストプログラムに通じる形式的な文書を、いかにビジネスパーソンに読ませる(書かせる)かということに苦心してきた。しかしこの試みも Fit の失敗 に代表されるように、うまく行っているとは言えないように見える。ビジネスパーソンとエンジニア、両者の想定があまりに違うのである。

書いたものを読んでもらうというのは、人を動かすということである。Stackoverflow の共同創業者で、Joel on Software で有名な Joel Spolsky 氏は、編著「The Best Software Writing I」の中で「ストーリー」の重要性を説いている(「”Show, don’t tell” rule」)。いくら内容が正しくとも、ストーリーなき文章を長々と読めるわけがないというわけだ。

ストーリーなんて仕様書のような形式的な文書とは関係がないと思うだろうか? アジャイル開発で要件の単位となる「ユーザーストーリー」は、形式的表現に慣れているエンジニアからビジネスパーソンに歩み寄った結果として生み出されたものだ。ドキュメンテーションとはコミュニケーションであり、その質にはコミュニケーション能力がそのまま反映される。だからこそプログラミングそのものよりも難しいと言っても過言ではないように思える。

Microservicesアーキテクチャの思想性

Microservices and the Goal of Software Development:
http://www.infoq.com/news/2015/03/microservices-software

  • ソフトウェア開発の目的は business impact を生み出すこと
    • Business impactとは、組織内で観測可能な効果のこと(新規顧客やオペレーションコストの削減など)
    • Lead time(機会の発見からその解答を生み出すまでの時間)を最小限にすること
    • これを持続的に行うのが難しい
  • 三種類のコード
    1. 最近書いて理解しているコード
    2. よくテストされ、文書化されているコード
    3. 誰も知らないコード(不明瞭な依存関係、変更の影響範囲が局所化されない)
  • ソフトウェア開発最大の問題は、大部分のコードが 3 に分類されるということ
  • コードはきちんと運用出来るものだけを維持するようにするべき(3のコードを排除すべき)
  • North氏の提案する二つのパターン
    1. コードの目的(意図)を明確にすること
      • ソフトウェアの半減期は短い(「半減期」とは放射性崩壊の速さを示す物理学用語)
      • なので、それぞれのコードの目的を明確にして安定化に努めるべし
    2. 理解しやすいようにコードを分割統治すること
  • 上の二つのパターンを実現するために有効なのが「置換可能なコンポーネントによるアーキテクチャ (replaceable component architecture) 」
    • APIによって意図が明確にされたコンポーネント
    • コンポーネントは Alan Kay の発明したオブジェクト指向のオブジェクトのごとく、メッセージのやり取りをする小さなコンピュータのようなもの
  • Microservicesは、置換可能なコンポーネントによるアーキテクチャになり得る
    • 置換可能性と一貫性を追求すること
    • コンポーネントの「小ささ」よりも「置換可能性」の方が重要

現場のエンジニアがMicroservicesアーキテクチャを評価するときは、どうしてもその実用性や現実性、例えばプロセス間通信の多用によるオーバーヘッドなどが問題にされがちである。しかし、そのコンセプトをつぶさに見ていくと、これは単にアーキテクチャだけの問題ではなく、ソフトウェア開発はどうあるべきかという、ある種の思想を提示していることに気がつく。

開発の現場には「エンジニアとは外から持ち込まれた問題に解を与える者だ」という強い思い込みがあることが多い。しかし、これはプログラミングに対する誤解を含んでいるだけでなく、一つのシステムを長く育てるというケースにおいては、持続可能なモデルではない。

Martin Fowler氏が「The New Methodology」の中で「ソフトウエア開発は、全てが「デザイン」である」と指摘したのはもう大分昔のことだが、この考え方が開発の現場においてどれだけ浸透しているだろうか。

ソフトウエア開発は、本質的に、問題に対する解法の実装だけでなく、問題自体の検証と再設定を含むサイクルを繰り返す作業で、プログラミングというのはそのための強力な武器である。既に設定された問題を解決するためだけにプログラミングが存在し、そしてその問題設定を開発から切り離せると考えるのは、ソフトウエア開発に対する大きな誤解である。

Microservicesで提案されているのは、一つのサービスだけでなく、それを開発する開発者(あるいはチーム)も、できるだけ完結した存在になることが望ましいということである。中央集権的な開発体制ではなく、いくつものサービスとその開発を担当する cross-functional なチームが自律的に連携して、全体として大きなシステムを実現するという、オブジェクト指向が理想としていたようなモデルである。

あまり理想主義的になり過ぎるのも良くないが、このような考え方のルーツの一つにオブジェクト指向があり、それが考え方としてはデザインパターンやアジャイル開発などにつながり、プロダクトとしてはインターネットやiPhoneなどにつながっていることを考えると、実用性や現実性はともかく、その思想の重要性に注目しておく必要はあるだろう。

Microservices時代のプロジェクト管理を考える (1) – 水平型から垂直型へ

たまチームで管理しているGitHubリポジトリの数もどんどん増えてきて、今ではデプロイ可能なサービスだけでも20個ぐらい、リポジトリの総数は30を超えた。既に書いた通り、これらのサービスに共通するサーバーの構築 (Provisioning) と管理は、これまで一人のインフラ担当者が頑張って面倒を見てきた。しかしこのままサービスの数が増えていくと、インフラ部分がボトルネックになって効率が大きく損なわれることが目に見えている。そこで Deployment や Provisioning の管理方法を変えよう、というのが前回の話であった。

この方針変更をたまチームでは「水平型から垂直型への移行」と呼んでいる。

従来の管理方法だと、アプリケーション開発者はアプリケーションのビルドにだけに注力すればよい一方、インフラ担当者は、すべてのアプリの Deployment、サーバーの Provisioning や Monitoring まで面倒を見なければならなかった。

horizontal

これを垂直型に移行すると以下のようになる。

vertical

アプリケーション開発者はサービス開発者となり、アプリケーションのビルドに加えて、そのアプリケーションが動作する環境の Provisioning も担当し、最終的には Server Image をアウトプットするようにビルドプロセスを変更する。インフラの共通部分は最小限に抑えて、チームメンバー全員で共同管理出来るようにする。

水平型も垂直型も、それぞれ一長一短でありトレードオフがある。インフラの共通部分を厚くすれば、同じような構成のサーバーを一括で変更したり、多くのProvisioningコードを再利用出来る一方、共通部分と各アプリケーション開発者間で生じるコミュニケーションコストは馬鹿にならない。

あるいは、高度に訓練されたチームであれば、メンバーの誰もが共通部分(インフラ)とアプリケーションを行ったり来たりしながら自在に開発出来るのかもしれない。しかし、残念ながら我々たまチームはそこまでのレベルには達していないので、共通管理する部分を出来るだけ薄くして、後はアプリケーション開発者の裁量に任せてみることにした。

この考え方はソフトウェアのモジュール(あるいはオブジェクト)の考え方に似ている。モジュールは外部との責任を明確にする一方、内部構造については隠蔽しておくのが望ましいとされる(責任を果たす限り内部の詳細を問題にしない)。これはモジュール間のコミュニケーションコストを最小限にして、大量のモジュールが柔軟に連携出来るようにするための考え方であり、これをそのままサービス開発に応用するとすれば、アプリケーション開発者は、そのアプリケーションが外部に果たす役割を明確にする限り、他の開発者はその内部のことを(とりあえずは)気にしなくて済む。

サービスの数が増えていくのと同時に、技術の進歩に伴って、サービスの粒度が小さくなってきている。サービスが Microservice化すると、一つのリポジトリを複数の人間が管理するよりも、一人の人間が複数のリポジトリを管理するケースの方が増えてくる。その意味でも、コミュニケーションコストを抑える方法を考えておかなければならない。

個々のサービス開発者に課せられる責任は、オープンソースプロジェクトの開発に要求されることに似ている。そのプロジェクトを誰かに使って欲しければ、それが外部に提供する価値について、そしてその利用方法について、第三者が分かる形で説明する必要がある。ここで何を説明するべきかという最小限のラインを考えることが垂直型の勘所ではないかと思う。

ソフトウェアアーキテクチャと組織の構造

Martin Fowler氏が記事 Microservices の中で言及しているように、コンウェイの法則によれば、ある組織が開発するソフトウェアのアーキテクチャはその組織の構造をそっくりそのまま反映した形になる。今回の話で言えば、水平型のアーキテクチャを生み出す組織は職能で分けられた水平分業組織であり、垂直型の場合は cross-functional な垂直統合チーム型組織になる。

既に述べたように、いずれも一長一短があるため、どちらが良いとは一概には言えない。たまチームではたまチーム独自の事情があって、今回は垂直型への移行を試みることになった。この移行によって開発のやり方やアウトプットがどう変わったかはこのブログで逐一レポートする予定である。

最後に一つだけ垂直型への期待を述べるとすれば、現代は専門領域の境界がめまぐるしく変化する時代である。一つの専門領域に留まっていたら、あっという間に時代遅れになることも珍しく無い。このような時代においては、多様な領域に横断的に関わり、それらの融合をもって競争社会のアドバンテージとするべきであり、そういう意味ではチームだけでなく、個人にも cross-functional な能力が求められるのではないだろうか。

Immutable Infrastructureを導入する

現在たまチームでは、以下のような自動化を実現している。

gyron-infra-old

自動化の柱は大きく分けて二つあって、まずは Deployment Pipeline。アプリケーションコードを変更して git push すると、Jenkins上でビルドされて、Amazon S3上へのパッケージのリリースから、アプリケーションサーバー上へのデプロイまで自動で行われる。

もう一つはサーバー群の構成管理 (Provisioning)で、Chef による Codification(コード化) が実現されている。

この環境でかれこれ二年ぐらい運用してきたが、自動デプロイは便利だと思いつつ、いくつかの問題点が明らかになってきた。

  • デプロイの際に更新されるのはアプリケーションだけなので、サーバー上のミドルウェアの更新をいつやるか、どうやって安全にやるかを考えるのはなかなか悩ましかった。
  • Chefを経由するより、まずはサーバーを直接直してしまおうという誘惑が常にある(突然のセキュリティアップデートが必要になった場合など)。
  • EC2のインスタンスがリタイアするときにあたふたしてしまう。
  • Chefのコードを書く人がインフラ担当者に限られてしまい、アプリケーション開発者側から手出し出来なくなる。
  • 簡単とは言えないChefにも問題があると思われ。
  • ProvisioningコードがCI上に乗っかってない。

というわけで、たまチームへの新たな要求や DevOps の新たなトレンドなどを鑑みて、インフラの大幅刷新を行うことにした。

以下が、目下移行中、新インフラの概要図である。

gyron-infra-new

大きく変わるのはアプリケーション開発者が担当するビルドの部分。以下、重要なポイントごとに検討してみる。

DeploymentとProvisioningの統合

今までは別々に管理されていた Deployment と Provisioning のプロセスを統合して、アプリケーション側で Server Image の生成まで面倒を見るようにする。

現状のビルドプロセス:

deploy-and-provision-old

新ビルドプロセス:

deploy-and-provision

この統合によって、Provisioningの大部分がアプリケーション側に移動することになる。今まではインフラ担当者の責任だった Provisioning を各アプリケーションの開発者が担当することによって、誰もがアプリケーションコードを扱う感覚でProvisioningコードを扱えるようになることを狙いたい。

また、今回は Chef から Ansible への移行も行う。プロジェクト全体で Provisioning を共有する場合、あるいはインフラが大規模になるようなケースであれば Chef のメリットもあると思われるが、アプリケーションに Provisioning を含めてしまうという今回のケースでは、より習得が容易でコンパクトに書ける Ansible が向いているのではないかという判断である。

アプリだけでなくサーバー全体を含めたContinuous Integration

今までCI上で実施されていたのはアプリのビルドまでだった。新しい仕組みでは、サーバーのビルド(AMIの作成)までをCI上で回す。これによって、以下のようなメリットが得られる。

  • Provisioningも自動テストの対象にできる(serverspec
  • アプリケーションの変更を行う感覚でミドルウェアの変更を行える
  • サーバーのPortabilityが改善し、テスト用の環境などが構築しやすくなる

Immutable Infrastructure

新インフラのアプリケーションサーバーは、原則として、一度起動した後にsshなどでログインしてその状態を変更しないようにする。アプリケーションやミドルウェアを更新する場合は、コードを変更してServer Imageを作り直し、古いサーバーを破棄して新しいサーバーに入れ替える。

このような方針で管理されたインフラを「Immutable Infrastructure」と呼ぶ。インフラがコードに表現された以外の状態を取り得ないということは、コードさえあれば環境を容易に再現できるということであり、テストが簡単になる、理解しやすくなる、不測の事態が起こりづらくなる、Blue-Green Deployment がやりやすくなるなど、数多くのメリットがある。

DevOps Split – BuildとOrchestration

新しいインフラでは、Server Image (AMI) という Artifact を生成するプロセスと、そのArtifactを利用してOrchestration(サーバーの構成管理)を行うプロセスを分離する。前者は個々のアプリケーション開発者が担当し、後者はチーム全体で共同管理することになる。

以下は HashiCorp で紹介されているワークフロー:

Atlas-DevOps-Split

このワークフローを採用した場合、Artifactの作成で流れが一旦切れるため、git pushしたらアプリケーションの更新まで行われるという、現状実現できている自動化を再現するのが難しくなる。新インフラで得られるメリットに比べたら些細なことだと言えるかもしれないが、今後の課題ということでここに記録しておく。

CIのCodification – JenkinsからCircleCIへ

現状のインフラで自動化の要を担っているJenkinsであるが、ジョブの数が多くなってくると管理が大変になってくる上に、設定が Codification されていないという問題があるため、CircleCIに移行することにした。

Blue-Green Deployment

現状のインフラで Blue-Green Deployment を実現しているのは、状態(セッション)を持たないアプリケーションに限られ、かつデプロイ作業の多くは手動で行っている(ELBの操作など)。新インフラでは、以下のような手法を採用して、全てのアプリでより安全な Blue-Green Deployment の導入を目指す。

  • JEEアプリについては、 tomcat-redis-session-manager を利用してセッションをRedis (Elasticache) に保存するようにし、アプリケーションサーバーをステートレスにする。
  • Terraform を利用して新しい Server Image を立ち上げた後、Smoke Testで動作確認し、ELB配下で新旧の切り替えを行う。
  • Terraformによって、サーバーの設定・構成がコード化されると共に、管理コンソールで操作する必要がなくなるため、ヒューマンエラーの予防になる。