Graph Tourism で興味の壁を破る – ユメアト.net による実践

仕事を辞めてぼんやりと路頭に迷っている内に半年が過ぎようとしている。

その間、Cotoami の Patreonプロジェクトを立ち上げたり(ご支援お待ちしてます🙇)、新しく思いついたことを少しずつ形にして、細々と実験を重ねたりしていた。

油断していると一年ぐらいあっという間に過ぎ去りそうなので、せめてこの半年間の試行錯誤をここに記録しておきたいと思う。

前回の記事「アウトライナーの新しい形を考える」では、筆者が個人的に開発中の、なんとも形容のし難いソフトウェアを、アウトライナーという比較的ポピュラーなソフトウェアと対比することで説明を試みた。

そのソフトウェア、Cotoami のゴールは、単に情報を整理して多面的な知識ベースを作るということに留まらず、「発見」を支援する環境を提供することを目標にしている。しかしながら、現時点でその目標に到達できているかと言えば、あと一歩、決定的な何かが欠けているような気がしていた。そして、そのアイデアについての試行錯誤を続ける内に、「発見」にフォーカスするならもっと違うアプローチが必要になるのではないかと考えるようになった。

そこで思いついたアイデアを形にしてみたのが、「ユメアト.net」というサイトである。

ユメアト.net – https://yumeato.net

ユメアト.net は、グラフ構造に従って旅行先を選択し、その足跡を記録して共有するためのサービスである。これを個人的に「Graph Tourism」と呼んでいる。なんのこっちゃと思われるかもしれないが、順を追って説明したい。

どこかに旅行しようと思い立つとき、例えば「温泉に行きたい」とか「三十三観音巡りをしたい」とか、あるいは「そうだ 京都、行こう。」のように、テーマがあってそこから目的地を決めるケースが多いのではないかと思う。それをここでは「縦のつながりによる選択」と呼ぶことにする。つまり、カテゴリー(上位概念)から目的地を選ぶようなケースである。

一方で、とある場所に出かけて行って、現地で見つけた意外な縁で別の目的地に導かれる、といったような「横のつながりによる選択」が行われることもある。ユメアト.net は、この「横のつながり」あるいは「隣接関係」を辿る旅を支援する。

アウトライナーの新しい形を考える」の中で、ツリー構造とグラフ(ネットワーク)構造の比較を行った。縦のつながりだけを表現するツリーに対して、縦横両方を含めることができるのがグラフであり、それがこのコンセプトを Graph Tourism と呼ぶ理由なのだが、実際には縦より横の関係に力点を置く。

縦のつながり、あるいはカテゴリーによる選択は、単純に言えば旅行者自身の「興味」による選択である。一度、興味(テーマ)が設定されてしまうと、そこから逸脱しようという動機付けはなかなか働かない。むしろそのカテゴリーを極めようとするのが人情である。その壁を破るために、横のつながり(隣接関係)が重要なのではないかと考えた。

ユメアト.net では、過去の出来事の痕跡を残す場所を、史跡よりも広い意味で「ユメアト」と呼ぶ(由来にした松尾芭蕉の「夢の跡」はむしろ痕跡が残ってない場所のことだと後々気がついた)。ユメアトはそれぞれに関係する、共通の「固有名(人物や場所などの名前)」があるときに、横のつながりを持つ。

例えば、1862年9月14日に、横浜の生麦で起こった「生麦事件」という出来事がある(生麦事件 - Yumeato.net)。

ユメアト.net で生麦事件のページを見ると、場所や日付などの基本情報と一緒に、沢山の関係する固有名が並んでいる。

例えばこの中から、事件の当事者の一人「大久保利通」を選択すると、彼が関わった他のユメアトの一覧を見ることができる。

こんな感じでユメアトと固有名のつながりを辿って次の目的地を探しながら旅を続けて行く。

以下は、筆者が去年の春頃から始めた旅の足跡をグラフ化したものである(こちらで実物を閲覧できる)。

横のつながりを追う旅は、自分自身の興味の範囲に留まることなく、現地で見つけた痕跡とそこから辿れる縁に従って、カテゴリーを次々に横断して行く。筆者の例で言えば、地図上から適当に選んだ「玉川上水開削」の現場に始まり、巡り巡って、鉄道の父「井上勝」のお墓に訪れたり、その墓地で「賀茂真淵」という人物の墓を見つけた縁で、彼の生誕地である静岡県浜松市の神社を訪れたりと、この仕組みがなければ絶対に訪れなかったであろう場所に幾度も出会ってきた。

隣接関係を辿って行くと、カテゴリーには囚われない自分だけの文脈というものが生まれてくる。それによって目的地が、単に観光地だから、あるいは有名だからという理由で訪れるのとは全く別の意味を帯びてくる。

つながりを辿る旅を続ける内に、自分の中に自分だけの文脈ができて、新たなユメアトをより深く楽しめるようになっていることに気がつきました。なんの変哲も無い路地に、誰にも気付かれずひっそりと立っている石碑でも、自分の足跡につながるものであれば、宝物を発見したような気持ちになれます。- ユメアトを楽しむ – ユメアト

この方法で旅をすれば、興味やカテゴリーによって目的地を選ぶよりも、より未知の世界に足を踏み入れて行く可能性が格段に高くなり、かつ、単にランダムに目的地を選ぶよりも、横のつながりによる文脈が積み重なった、よりリッチな体験として足跡を振り返ることができる。

「発見」という観点から言えば、実際に現地に訪れる、物理的に移動するということも極めて重要である。そこには、先ほどの墓地の例のように、物理的(あるいは距離的な)隣接関係による発見が期待できる。実際に出かけてみると思いもよらなかった発見をするというのは、どんな形であれ、旅行をすれば誰でも普通に経験することなのではないかと思う。

筆者が旅先でどのような体験・発見をしたのか、Twitter (@yumeato_net) で詳細な記録を付けているので、興味のある方は覗いてみて欲しい。旅行記のまとめも随時更新している。

隣接関係のコンセプトは、本屋に出かけてなんとなく書棚を眺めている内に、元々の目的とは違う本を発見することに似ているかもしれない。振り返ってみれば、インターネットも、そもそもはグラフ構造で発見のプラットフォームだったはずなのに、今ではカテゴリー内に閉じる方向に強化されてしまった。そのような環境で自分の興味の壁を破る一つの処方箋として Graph Tourism というものを考えてみた。

アウトライナーの新しい形を考える

アウトライナーというソフトウェアがある。

アウトライナーはその名が表す通り、情報のアウトライン(輪郭)を作るためのソフトウェアである。情報のアウトラインを作るというのは、別の分野の言葉で言い換えれば、知識や考えの「デッサン」をすることに相当するだろうか。考えを文章として精密に表現する前に、その論の構造だけを抜き出して、全体として筋が通るような組み立てを考える。

そもそもどのようなときに考えのデッサンをする必要性に迫られるのかを考えてみると、比較的込み入ったことを考えたり、あるいは、ある程度ボリュームのある論理的な文章を書いたりするときなど、頭の中だけでは容易に解決できないような複雑な問題に取り組むときに限られるだろうし、そう考えると日常的にアウトライナーを利用している人はそれほど多くないのかもしれない。

それでも、日常生活の中でちょっとした考えをまとめたり、あるいは何か記録しておきたい事柄があった場合に、何らかのツール(紙のノートでも、パソコンやスマホでも)を使ってそれらを「箇条書き」にするという経験は誰にでもあるのではないだろうか。

あるいは、単に箇条書きにするだけではなく、字下げなどをして項目に階層を持たせて、もう一段深い掘り下げを行なっている人も少なくないのかもしれない。その階層構造の導入がいわゆるアウトラインを作ることに他ならない。

字下げをして階層を作れるなら、アウトライナーなんて専用のソフトウェアは必要ないのではと思われるかもしれない。アウトライナーの強みは情報の構造を構造として扱えることにある。例えば、同じ階層にある項目を並び替えたり、ある項目を配下の階層ごと別の場所に移動したり、階層の中を上下に移動させたりなど、構造自体の操作を容易にしてくれる。あるいは、その構造を眺めるときに、ある項目にフォーカスしたり、その場では不要な項目を折り畳んで隠したりと、情報の見え方も柔軟にカスマイズできる。

筆者が地道に開発を続けている Cotoami も、ジャンルとしてはアウトライナーに近いソフトウェアである。Cotoami 以外にもそのようなソフトウェアを以前から作っていて、自分でもそれらを日常的に使ってきたので、アウトライナーというソフトウェアの存在は知っていても、実際にそれを利用したことはなかった。

筆者がアウトライナーに抱いていたイメージは、扱う構造がツリー(階層)であるがゆえに、表現できる情報は、ある時点、ある視点からの知識のスナップショットに過ぎないということだった。ツリーというのは一次元の構造である。あるノードは必ず一つのノードに所属し、文脈は一意に固定される。

最終的なアウトプットとしては当然スナップショットにならざるを得ないにしても、そこに至るまでの思考の過程においていくつかの文脈が入り乱れることもあるだろうし、そのようなものを表現できる思考のツールが必要ではないかと考えたのだ。

しかし、ある時『アウトライン・プロセッシング入門』という本にたまたま出会って、それを読んでみたところ、まさに自分が抱いていたような懸念に言及した上で、本来あるべきアウトライナーの利用方法が語られていて、アウトライナーを利用していても結局はそこに辿り着くのだと分かって大きな感銘を受けた。

例えば、アウトライナーでは、最上位の項目(テーマ)からスタートして、下位の項目に向かって徐々に内容を詳細化していく、いわゆるトップダウンのアプローチでアウトラインを作っていくのではないかという先入観があるが(これは同じツリー構造を利用したマインドマップでも同様)、

実践的なアウトライン・プロセッシングは、トップダウンとボトムアップを相互に行き来する形で行われます。トップダウンでの成果とボトムアップでの成果を相互にフィードバックすることで、ランダムに浮かんでくるアイデアや思考の断片を全体の中に位置づけ、統合していきます。 私はこのプロセスを「シェイク」と呼んでいます。行ったり来たりしながら「揺さぶる」からです。 – 『アウトライン・プロセッシング入門

という感じで、実際には「トップダウンとボトムアップを相互に行き来する」ことで、アウトラインを作る過程で新たに発見した知見を構造に反映していくという、より発見的なアプローチが推奨されている。

「トップダウンとボトムアップを相互に行き来する」という考え方については、Cotoami も全くその通りなのだが、アウトライナーとの違いをあえて挙げるとすれば、Cotoami では基本にランダムな入力があって、そこから構造を組み立てていくという、どちらかと言えばボトムアップに焦点を置いたデザインになっているところだろうか。

Cotoami では、全ての情報がフラットに表示されるタイムライン(複数人で利用する場合はチャットして機能する)と、組み立てられた構造を見せるドキュメントビューやグラフビューが分かれていて、片方はフロー、もう片方はストックという感じで、これらを相互に行き来しながら、構造を何重にも組み立てていくという仕組みになっている。

例えば、ある項目(Cotoami では「コト」と呼んでいる)は、複数の文脈に同時に関係しているかもしれないが、ツリー構造ではそれを表現することができない。そこで、関係をもっと自由に作ることのできるネットワーク構造を導入して、Aという文脈におけるX、Bという文脈におけるX、という感じで同じ情報を複数の視点から見れるようになっている。先ほど、アウトライナーはスナップショット的だと書いたが、アウトライナーと異なる構造を採用している Cotoami はデータベース的なソフトウェアだと言えるかもしれない。

興味深いことに『アウトライン・プロセッシング入門』では、ツリー構造とネットワーク構造の対立についても言及されている。

アウトライナーで扱う「生きたアウトライン」は、見た目はツリーでも通常のツリーよりずっと複雑な内容を扱うことができます。なぜならそれは「流動的なツリー」だからです。それは常に変化する可能性をはらんだ「プロセス」であり、完成品ではありません。

確かにアウトライン上では、ある記述は「A」と「B」どちらかにしか分類できません。しかしアウトライナーに入っている限り、それはある記述が「A」と「B」のどちらに含まれるべきかという思考プロセスの、最新のスナップショットにすぎません。今「A」に分類されている内容が、次の瞬間には「B」に再分類される可能性を常にはらんでいます。見た目上「A」に分類されているけれど、同時に「B」でもある可能性が常にある。それはもはや単純なツリーではありません。

そして重要なのは、アウトラインを最終的に完成品(たとえば文章)としてアウトプットするためには、いずれにしても「A」か「B」かを選ばなければならないということです。複雑なものを勇気を持って単純化しなければならない瞬間がくるのです。そのこと自体に思考を強制し、発動する効果があります。

実は発想と呼ばれるものの多くが、この強制的な単純化から生まれてくるのではないでしょうか。「文章にしたり人に説明したりするとその対象がよりよく理解できる」と言いますが、それは複雑で絡み合った情報を、単純なツリーやリニアな語りに強制的に変換しなければならないからです。その過程で情報は咀嚼され、自分の視点が確定します。アウトライナーで編集されるアウトラインは、「流動的なツリー」であることでそのプロセスを促すのです。

一方の網目構造では、複雑なものが複雑なまま存在できてしまいます。それは一見するといいことですが、ともすると素材が素材のままになってしまう可能性があります。

たとえば情報を保管し、必要に応じて引き出すということを第一義に考えれば、複雑な情報を複雑なまま扱える網目構造のほうが優れているかもしれません。しかし「文章を書き、考える」道具、つまり発想ツールとしてみた場合の有効性は、アウトライナー否定派がいうほど単純には決められないのです。

アウトライン・プロセッシング入門

アウトライナーで扱うのは「生きたアウトライン」であって、ある項目がどこかに所属するというのは、その瞬間のスナップショットに過ぎず、常に変わり得るということ、そして発想を具体化するためにはいずれにせよ「A」か「B」かを選ばなければならないということ、その強制的な単純化が発想にとって重要なのではないかということ、いずれも深く納得できる話で、Cotoami が採用するネットワーク構造(網目構造)に対して、説得力のある反論になっている。

実は、この本を通じて主張されている、「考えてから書く」のではなく、書きながら考えて、そこで発見したことを構造にフィードバックさせていくといったような発見的なやり方を踏まえていれば、アウトライナーでも Cotoami でも基本的にはそれほど違いはないと個人的には考えている。これはツールというよりもマインドセットの問題だからだ。

この問題はプログラミング言語における、オブジェクト指向と関数型の対立に似ている。世の中にはオブジェクト指向言語とか、関数型言語というカテゴリーでプログラミング言語が分類されることがあるが、それらのカテゴリーがプログラムデザインのマインドセットを決定するわけではない。オブジェクト指向言語で関数型プログラミングを実践することは可能であるし、同じように関数型言語でオブジェクト指向プログラミングを実践できる。

もちろん個々のツールがどういうアプローチに向いているかという傾向はある。アウトライナーがスナップショット的、Cotoami がデータベース的というのは、その傾向を表している。

ただ、Cotoami としては、現状のアウトライナーではなかなか難しいと思われるアプローチを可能にしていきたいところである。そうすることによって、知的生産の可能性を少しでも広げていきたい。そのためには『アウトライン・プロセッシング入門』で紹介されているアプローチの先にはどのような展開があり得るかということについて考えなければならない。

その一つの可能性が「コトノマ」というコンセプトである。

Cotoami のデータベースでは、アウトライナーの一つの項目に相当する「コト」という単位の情報が相互につながりを持って、単純なツリーではなく、自由なネットワークを構成できるようになっている。ネットワークが大きくなるにつれて、他より多くのつながりを持つコトが現れるだろう。そのようなコトは、参加者にとって重要なコンセプトを表している可能性が高い。そのコトを「コトノマ」に格上げすることによって、そのコンセプトについてさらに深く掘り下げることができるようになる。

そして、そのコンセプトについて掘り下げる過程でさらなるコンセプトの発見があり…という連鎖を Cotoami では期待している。

この機能は、アウトライナーの「フォーカス」という機能の発展形だと言えるかもしれない。

アウトライナーの持つ「文章の集合体」としての性質は、アウトライナーを開発する側でも必ずしも意識していない場合があるように感じます。 この点に自覚的なアウトライナーは、それをサポートする「フォーカス」という機能を持っています(アウトライナーによって「ズーム」「ホイスト」「巻き上げ」などとも呼ばれます)。ひと言でいうと、アウトラインの中の任意の項目を一時的に最上位階層として表示する機能です。他の項目は見えなくなるので、その項目に集中することができます。 – 『アウトライン・プロセッシング入門

コトノマは、そのテーマについての情報を集めるための専用のタイムラインを持つ他は、他のプレインなコトと同様に扱うことができる。ネットワークの中で重要なポイントを表すための特殊なコトだと言えるだろう。

ネットワークを育てる内に、その過程で発見した重要な事柄がコトノマとしてネットワーク状に結節点を作る。そのコトノマのリストは、ネットワークを育てた参加者の足跡であると同時に、外からデータベースを見るときの入り口としても機能する。

Cotoami の基本的な考え方で、アウトライナーと一線を画すのは、そもそもデータベース的なシステムだということもあるが、一人の人間がその全貌を掴めないぐらいの膨大なネットワークを作るというユースケースまで想定しているところだ。一人で利用するケースから、チーム、不特定多数のコラボレーションまで、あらゆる情報を蓄積して巨大なネットワークを作る。

そのときそのときに必要なアウトプットのために、スナップショット的なツリーを作ることはあっても、全体として綺麗に整理された状態を保つということにはこだわらない。かつて、野口悠紀雄氏が「「超」整理法」で書いていたように、探すための分類・整理は結果的に徒労に終わることになるし、整理は情報を既知のカテゴリーに収めることになって、むしろ新しい発見を遠ざける原因になってしまう。

「発想と呼ばれるものの多くが、この強制的な単純化から生まれてくる」という仮説が、確かにそうかもと思わせる一方、拙速な整理が、結局のところ既知のカテゴリーに立ち返ってしまう原因になるのではないかという予感もある。何か新しいことを発見するために、カテゴリーは結果として横断されなければならない。

そのために「コトノマの連鎖」というものが重要になってくるのではないかと考えている。投稿した情報を眺めていく過程で生まれたコンセプトをコトノマに変換し、そこからまた発見される隣接概念をまたコトノマ化する。そのような隣接概念の渡り歩きによって生まれるネットワークを可視化することによって、その足跡が結果として未知のカテゴリーを生むのではないかと期待している。

何気ない会話からどうやってストックに耐え得る知識を引き出すか?

情報のフローとストックを同じシステムの中で循環させるという Cotoami の枠組みが、とりあえず一通り出来上がってきたので、実験しながら改善策を探るというフェーズに入っている。

公式の Cotoami サーバー https://cotoa.me では、公開でその実験を行なっているのだが、参加者がまだ少ないものの、会話の中からストックとなる情報を組み立てる過程が少しずつではあるが現れてきている。

その循環のプロセスは以下の通りである。

例えば、普通のチャットのように、タイムラインにただ投稿したものは構造のないフラットな、時系列の情報になる。これがいわゆるフローの状態だ。情報はどんどん流れていって、常に最新の情報だけを目にすることになる。

この常に流れていく一次元のタイムラインに、自然な形で構造を導入するとしたら、どのような方法があるだろうか? 最も簡単な方法は、既存の情報に対して reply(返信)をすることである。reply によって、時系列の上に、文脈(話題)の構造が生まれる。

この仕組みは全く新しいものではなく、2chの安価やツリー型の掲示板など、Webの黎明期からあちこちで利用されている。ただし、その構造を見せる方法には色んなバリエーションがある。例えば Cotoami では、タイムラインに複数の話題が混在していても個別に遡っていくことができる。

以下のように、PC上のブラウザであれば、reply の連鎖によって生まれたスレッドを同時に複数開いて比較することも出来る。

reply の次の段階では、タイムライン上で重要だと思うコト(Cotoamiでは個々の発言を「コト」と呼ぶ)を pin して、流れていかないように確保する。

普段通りにチャットする流れで、reply によって会話の繋がりを作るのは自然に行えるが、その発言の中で何を pin しておくかという判断は思ったよりも単純ではない。特に、気ままにチャットを楽しんでいるようなときは、そのようなアクションがあることさえ忘れてしまう。自発的な選択という行為は案外ハードルが高いし、会話に参加する人数が多くなるほど、何を選択するか、という問題の重みは大きくなる。

あるいは、UIのデザインでその障壁を下げることは可能かもしれない。例えば、”pin” ではなくて、”いいね” のような、会話の中で自然に行えるアクションにするとしたら、どのようなものがあるだろうか?(例えば「メモメモφ(・ω・`)」ボタン?)

pin で起点となる情報が出来たら、そこに has-a 関係となるようなコトを追加して構造を作っていく。

現状の Cotoami では、has-a 関係と reply の区別はなく、双方ともポストとポストの間にコネクションを作って辿れるようにする、という同じアクションである。将来的にはコネクションのタイプを指定できるようにしても面白いかもしれない。

そして、最後はコトのコトノマ(カテゴリー)化。Cotoami で最も重要なステップである。

「コトノマ」とはチャットの文脈で言えば、いわゆるチャットルームに相当するものである。コトをコトノマに変換することによって、その話題についての専用のチャット・タイムラインを用意でき、議論をさらに掘り下げることが出来る。と同時に、コトノマはコトと同じように扱えるので、相互に繋げたり、タイムラインにポストしたりすることも出来る。

Cotoami の最終的な目的は、このコトノマのネットワークを作ることである。そしてコトノマは、タイムラインでやり取りされている内容から自然に生まれてくるのが望ましい。そうなれば、出来上がったコトノマのリストは、参加者にとって(先入観からではない)自然に重要なコンセプトになるはずだというのが Cotoami の仮説である。

ここまでの流れを追うと、コトノマが出来上がっていく過程というのは、基本的には「連想」によって駆動されることになる。なので、あるテーマについて話しているときは、その隣接概念がコトノマの候補として上がってくるケースが多い。ただ、個人的にはこの連想だけだとつまらないと思っている。それとは少し違う方法でコンセプトの発見に誘導できないか、というのが今後の大きなテーマである。

チャットでウィキペディアをつくる

例えば、何か新しいことを始めようと思うと、どうしても「新しいことを始める」ためのフォーマットに乗っかってしまう。

これは、「やるぞ」っていう意気込みが強ければ強いほど、むしろそうなってしまったりするし、その何かをやろうという構えが、本当はもっと柔軟に考えられたかもしれない可能性を狭めることになったりしないだろうか?

例えば、新しいことを始めるぞということで、いついつどこに集まってブレストでもやろうということになって、立派な会議室なんかを予約して、場合によっては上司や先輩、あるいはよく知らない人などが集まった空間で、本当に価値のあるアイデアが出たりするものなのだろうか?

それよりも、日頃たわいもなくしている雑談で、ふざけたりしながらいろんな話題を縦横無尽に移動する中で、「これってそもそもこういうことじゃない?」とか「これとこれを組み合わせたら面白そう」みたいな気づきから、自然にコンセプトが浮き上がってきて、それが結果として「ワタシたちの百科事典」になるような仕組みあったとしたら、それが本当に自分たちがやりたかったこと(必要だと思ったこと)の種になるんじゃないだろうか?

例えば、

みたいな会話が、

という感じで「煽り運転」というテーマに辿り着く。

さらに、そのテーマにつなげる形で、あちこちで見つけてきた情報を投稿していく。

このテーマについて、もっと深く掘り下げる場所を作っておきたいということであれば、

みたいな感じで、発言内容から新しいチャットルーム(ここではコトノマと呼んでいる)を作る。このコトノマが、会話に参加している人たちにとっての百科事典の項目になる。

さらに何日か経って、

ここで キラーン✧ とひらめくTakeo氏、

その勢いで新しいコトノマを作る。

これはあくまでも例なので内容については深く考えないで頂きたい。

「チャットでウィキペディアをつくる」ということの雰囲気は感じて頂けただろうか? こんな感じで、発想にリミットがかからない普段の会話からコンセプトをすくい上げて辞書化するというのが、Cotoami の一つの活用例である。

予めこういうことをやろうとか、こういう情報が必要だろうという予断を持つよりも、興味のまま、話を面白そうな方向へ転がす時に出てくるキーワードを拾い上げたり、それらの間にネットワークを作ったりすることによって、本当に自分たちが必要とするオリジナルなアイデアを生み出す。それが「チャットでウィキペディアをつくる」という実践である。

Elixir から Elm の流れで、いよいよオブジェクト指向に対する懐疑心が無視できないレベルに達した2017年冬。

このエントリは Elm2 Advent Calendar 2017、2日目の記事になります。


Disclaimer: 勢いで書いてしまった後に改めて読み返してみると、Elmの中身には全く触れてないような気もしなくはない感じになってました… その辺を期待している方はブラウザ(のタブ)をそっ閉じして明日の記事にご期待下さい。


東京都港区の会社でインフラの仕事をしているフリをしながら、Elixir や Elm での関数型プログラミングに四苦八苦しつつ、Cotoami というよく分からないアプリケーションを作ったりしています。

今回は、まだ駆け出しの関数型プログラマーである筆者が、関数型プログラミングの洗礼を受けたことによって、長年慣れ親しんできたオブジェクト指向に対する見方が変わってきたという話について書いてみたいと思います。たとえて言えば、外国語を勉強することで、初めて日本語というものを客観的に見る機会を得たという体験に似ているかもしれません。

90年代からゼロ年代の中盤ぐらいまでにオブジェクト指向でプログラミングを始めた人間にとって、その考え方はプログラムデザインの共通言語のようになっていて、それ自体を疑うということには、なかなかなりづらい状況が長く続きました。Paul GrahamJeff AtwoodLinus Torvalds のような著名な人たちがオブジェクト指向に対する批判や懐疑を表明しても、「使い方の問題だよね」という感じで、オブジェクト指向そのものの問題ではないというのが多くの支持者の反応だったように思います。

あの TDD(Test-Driven Development)に対しても「Faith-based TDD」として同じ構造の批判がなされています(参考:「TDD再考 (8) – 凝集性(cohesion)とは何なのか?」)。このような議論の際によく見られる「◯◯が機能しないのは、◯◯のやり方を間違えているからだ」のような論法は、「No True Scotsman fallacy(本当のスコットランド人なら◯◯などしない論法)」だとの指摘もあります。

そもそもオブジェクト指向への批判が、関数型プログラミング界隈から行われることが多かったということもあり、その筋の人たちにとっては自明のことでも、オブジェクト指向しか知らない人たちにとっては、その指摘自体をうまく理解できないという非対称な構造がありました。

Information hiding vs. Explicitness

そのような状況の中で、オブジェクト指向への敬虔な信仰を残したまま、関数型プログラミングの門を叩いたわけですが、そこでいきなりオブジェクト指向の中心的な価値を全否定されるという事件が起こります。

オブジェクト指向では、状態というものがインタフェースの向こう側にあって、どのように実現されているか見えないようになっており、それがカプセル化、あるいは情報隠蔽と呼ばれる、複雑さを扱う技術の核心になっています。

ところが、関数型プログラミングでは状態の遷移を隠さずに、関数の入出力として表現しようとします(状態の遷移が入出力で完結している時、この関数を「純粋な関数」と呼ぶ)。

状態遷移が関数の入出力に限定されている時、プログラムの動作を把握するのは飛躍的に楽になります。一方で、オブジェクト指向ではプログラム上は簡潔に見えても、水面下に沢山の状態が隠されているので、何か問題が起きた時に状況を把握するのは容易ではありません。


The problem with object-oriented languages is they’ve got all this implicit environment that they carry around with them. You wanted a banana but what you got was a gorilla holding the banana and the entire jungle. — Joe Armstrong(関数型プログラミング言語 Erlang の作者)

「欲しかったのはバナナだけなのに、それを持ってたゴリラどころか、ゴリラがいたジャングルごとついてきた」って、分かりやすくてなかなか面白い表現ですが、それを言ったら、関数型だってバナナだけじゃ済まないだろっていう話もあるような・・・


ところが、なぜオブジェクト指向が状態遷移を隠蔽していたかと言えば、インターフェースに対してプログラミングすることによって、プログラムが実現すべき要求だけを簡潔に表現できるということがあったと思います。そうせずに、単純に状態遷移を全て入出力で表現しようとすると、プログラムはとても読み辛いものになってしまいます。この関数型特有の問題に対応するため、いくつかの関数型言語ではモナドという「本来の計算とおまけを切り離す」ための仕組みが導入されており、これはオブジェクト指向でやっていた情報隠蔽が形を変えて現れたと言えるのかもしれません。

興味深いのは、オブジェクト指向と関数型で、状態の扱いに関して言えば、全く逆の考え方になっているところです。オブジェクト指向では複雑性を扱うための核心となっていた考え方が、関数型プログラミングでは悪として扱われている。

ここでぼんやり、オブジェクト指向と関数型のハイブリッド言語ってどうなのよ? という疑念が立ち上がってくることになります。

A paradigm is a mindset

そんな思いを抱いたまま、8月に開催された Elm Tokyo Meetup #4 に参加して、そこで教えて頂いた、Elmアプリケーションのプログラムデザインについての動画を見ている内に、オブジェクト指向と関数型プログラミングはそもそも両立しないのではないかという印象はさらに強くなっていきました。

この動画を見る限り、Elmのコミュニティには、オブジェクト指向からの編入組が結構いるのではないかという印象を受けます。というのも、そこで語られていたのは、コンポーネントという、オブジェクト指向的なデザインを導入したみたけれど、どうもうまく行かなかったので、一度その辺の考え方をリセットして、もっと原理的なところからプログラムデザインを考え直してみようという話だったからです。

動画の中で、オブジェクト指向と関数型というのは、プログラミング言語の問題というよりもマインドセットの問題なのだという話が紹介されています。つまり、言語のパラダイムとは関係なく、プログラマがオブジェクト指向のマインドセットでコーディングしていれば、たとえElmのような純粋関数型の言語であっても、オブジェクト指向的にデザインされてしまうということです。

そのように考えると、マルチパラダイムの言語では、良く言えば、プログラマのマインドセットによって多彩なプログラムデザインが実現できるということになるけれど、悪く言えば、互いに相容れない複数のマインドセットを想定している場合は、単に混乱の元になるだけではないかという感じがして来ます。

Prefer duplication over the wrong abstraction

そのモヤモヤ感が強まった中で、さらに追い討ちをかけて来たのが、Cindy Sridharan 氏による「小さな関数は有害だと考えられる」というタイトルの記事です。

この記事で彼女は、一般的には名著とされている「Clean Code」に書かれているような、オブジェクト指向時代に生まれた設計指針は、むしろ過剰な構造化を誘発して、可読性や柔軟性を欠いたコードになることが多いのではないかという、いかにもその筋で炎上しそうな指摘をしています。

この指摘の背景には「そもそもオブジェクト指向が想定する抽象化が容易ではない」という問題意識があります。抽象化が容易でないのに、オブジェクト指向の設計指針には、その分割が本当に必要だと確信できるより前に、プログラムの分割を進めさせてしまうような圧力があります。

SRP(Single Responsibility Principle)

SRPでは、「1つのクラスは1つの責務を持つ」を原則とします。複数の責務を 1つのクラスに持たせないこと。1つの分かりやすい役割をクラス分割の境界 とすること。1つのクラス内に入る要素(属性や操作)が、1つの目的に向かっ て凝集していること。これが原則です。

クラスに変更が起こる理由は、一つであるべき。
A class should have only one reason to change.

– ソフトウェア原則[3]

 

ISP(Interface Segregation Principle)

クライアントは自分が使わないメソッドに依存することを強制されない。
Clients should not forced to depend on methods that they don’t use.

クライアントが本当に必要としているインターフェイスのみ が、クライアントから見えるべきで、他のメソッドには依存したくない。依存 を最小にして、変更の伝播を最小限に食い止めたい。Segregationとは分割、分 離、という意味です。つまり、ISPは「インターフェイスをクライアント毎に分離しよう」という原則なのです。

– ソフトウェア原則[4]

この問題について、Elmの作者である Evan Czaplicki 氏も同じような話をしています。上の動画と同じ「Elm Europe 2017」にて行われた発表によれば、

JavaScript での開発では、モジュールを細かく分けて、小さなファイルを沢山作る傾向があるが(”Prefer shorter files.”)、何故そのようなことになるかと言えば、

1) 一つのモジュールが大きくなると、その内部で何か想定外のことが起こる可能性が高くなる(想定外の状態共有や変更)
2) Static type のない JavaScript では、リファクタリングのコストが高くなるので、早い段階で分割を進めてしまう

という理由があるからではないかと指摘しています。Elmでは、1) に対しては、副作用がない純粋関数型であること、2) に対しては、強力な型システムがあることによって、これらの懸念を払拭できるため、先走りのモジュール分割を避けることが出来るというわけです。実際に、Elmでは一つのファイルやモジュールが大きくなることについて、他の言語(特にオブジェクト指向言語)よりも寛容であるということがよく言われています。

そもそも適切な抽象化が難しいのに、その抽象化が有用であるという証拠が揃わない内に分割を進めてしまうと、より不適切な分割をしてしまう可能性が高くなってしまいます。先走って分割したモジュールが、後々の状況変化に対応できなくなって、例外条件に対応するコードが増えて行き、そしてスパゲッティ化していく過程というのはシステム開発の現場で働く多くの人が目撃しているのではないでしょうか。

duplication is far cheaper than the wrong abstraction, and thus to prefer duplication over the wrong abstraction

「間違った抽象化よりも、コードの重複の方を好む」ということで、長らく信奉されてきたDRY原則への挑戦がここでは行われています。

Leaky abstraction

そして、偶然なのか何なのか、これも同じ8月に、オブジェクト指向における抽象化がなぜ難しいかというのを良く表している大変興味深い記事を見つけることになります。

カプセル化することが自己目的化していて、何のためにカプセル化するのかという視点が極めて希薄なことです。その結果、「うまいカプセル化」「まずいカプセル化」の区別がない、という状況に陥っています。

本来は、値引き判定のロジックをどのオブジェクトに配するかを決めるにあたって、どのような知識を隠蔽すべきか、あるいは裏返して言えば、どのような知識は開示して構わないかという点に思いをめぐらすべきでした。

解決策は、「データとロジックを一体に」という、どちらかというとゲームのルールのような具体的で単純なルールから視点を引き上げ、「情報隠蔽(=知識隠蔽)」のような、より本質的な、目的志向的な設計原則に立ち帰って考えることです。

この記事の要旨は、増田亨氏の「現場で役立つシステム設計の原則」という書籍で紹介されているオブジェクト指向のコード例について、「データとロジックを一体に」というオブジェクト指向の表面的なルールに囚われ過ぎて「何を隠蔽して何を表に出すのかという設計判断」を蔑ろにしているという指摘です。

しかし、この記事を読んで個人的に思ったことは、設計判断の根拠となる「スコープが適切でない」ということを、後から文脈をズラすことでいくらでも言えてしまうというのが問題の本質じゃないか、そこに「データ・ロジック一体型設計」の限界があるということなのではないか、ということでした。

うまく抽象化したつもりでも、どこかに必ず漏れが出てきてしまうという話は、「Joel on Software」の「The Law of Leaky Abstractions」という、2002年に書かれた記事に出てきます。

TCPプロトコルが、下位のネットワークをうまく抽象化しているように見えて、実際はいくつかの例外ケースで、その隠蔽しているはずのネットワークの存在が漏れて出してしまう(Abstraction Leak)。そうなった時にかかるコストというのは、抽象化がなかった時よりも高くついてしまう可能性があります。隠蔽された部分の知識も結局のところは必要なのだとなれば、抽象化された部分と隠蔽された部分の両方の知識が必要になるからです。

Objects bind functions and data structures together

オブジェクト指向の問題点を指摘する場合、一番厄介なのは、オブジェクト指向に定まった定義がないという事実です。このブログでは以前、オブジェクト指向の歴史を遡って、あれってそもそも何だったのかということについて検討したことがあります。

歴史的な経緯から言えば、オブジェクト指向を発案したアラン・ケイ氏が言うところの「メッセージング」が、オブジェクト指向の本質だということなりそうですが、一般に普及した「オブジェクト指向言語」と呼ばれるもので、メッセージングをサポートしているものはほとんどありません。メッセージング、あるいはそれが実現する動的結合(late binding)だけを考えると、それは今、オブジェクト指向と呼ばれるものよりも遥かに広い範囲で利用されていますし、実際にはオブジェクト指向言語じゃなくても実現できることを考えると、C++ をきっかけに流行した「抽象データ型」を起源とする流れが、一般的に認識されているオブジェクト指向だと考えて差し支えなさそうです。

ちなみに、オブジェクト指向信者の反論を「No True Scotsman fallacy」だと指摘した Lawrence Krubner 氏によれば、一般的にオブジェクト指向の強みだと思われているほとんどの要素はオブジェクト指向固有ではなく、オブジェクト指向固有の強みなど、実際には一つもないそうです。

オブジェクト指向固有でないものを除外していくと、最後に残るのが「データとロジックを一体に」という先ほどのルールです。そして、どうもこのルールがオワコンになりつつあるのではないかというのが、この約1年間、Elmでプログラミングをしていて実感するようになったことです。

関数型プログラミングをしていると、データとロジックが分かれていることのメリットを実感する機会が度々あります。アプリケーションにはアプリケーション固有の複雑さというものがあって、それらは多くの場合、必要な複雑さである場合が多いような気がしています。オブジェクト指向では、データとロジックを一緒にしなければならないという制約のために、それらの例外的だと思われるケースを捨象して、現実に即さないモデルに(強引にでも)落とし込むことになります。必要な複雑性を無理に捨象しようとするから、Abstraction Leak が起こります。

オブジェクトに関数が結びついているからこそ「このメソッドはこのオブジェクト構造を処理するためのものだ(他の用途には使えない)」という風に専門化できていたんであって、データと関数を個別のものと扱う以上は、「この関数はこのオブジェクトだけを扱う」という前提を置けないのです(当たり前です)。あるオブジェクトと別のオブジェクトが、型であったりクラスであったりが異なったとしても、関数は、そのオブジェクトが、関数の処理できる構造であれば、処理できるべきなんです。関数とオブジェクトが独立しているというのはそういう意味であるべきです。であれば、すべての関数にまたがるような、共通の汎用データ構造があって、すべてのデータはその汎用性を担保してたほうがいい。 Clojureの世界観 – 紙箱

Oscar Nierstrasz 氏が、彼のオブジェクト指向批判の中で、「オブジェクト指向とは、つまりモデリングなのだ」と喝破していますが、複雑な事象を分かりやすい用語(ターム)の集合に落とし込めるという先入観が、アプリケーションレベルの複雑性を扱う時に明らかな障害となって現れるケースが多くなっているような気がします。

メッセージングが今のオブジェクト指向と関係ないのだとすれば、「データとロジックを一体に」がオブジェクト指向の核心になりますが、そうだとすれば、オブジェクト指向自体がオワコンだという結論になってしまいます。そして、それはどうもそうっぽいという感じがしているのです。

Solve problems of its own making

ここからは少し余談になりますが、以上のような気づきを得た上で、過去のオブジェクト指向批判の文章を読むといちいち首肯できることが多くて困ってしまいます。

オブジェクト指向というのは、オブジェクト指向にしかない問題を作り上げて、それを解決するためのツールを作るというマッチポンプ的なことをしてお金を稼いでいるという話があります。

If a language technology is so bad that it creates a new industry to solve problems of its own making then it must be a good idea for the guys who want to make money. Why OO Sucks by Joe Armstrong(さっきのバナナの人)

あるいは、オブジェクト指向ではそもそも過剰な複雑性を作り込んでしまう傾向があるという批判があります。何故かといえば、オブジェクト指向には、インターフェースに対してプログラミングするという考え方があるので、プログラムは自然にレイヤー構造になっていくからです。もう古典と言っても良いかもしれない、Martin Fowler 氏の「リファクタリング」にも、

「コンピュータサイエンスは、間接層(indirection)を設けることであらゆる問題が解決できるという信念に基づいた学問である。― Dennis DeBruler」

とあったりしますが、

しかし、間接層はもろ刃の剣であることに注意しなければなりません。1つのものを2つに分割するということは、それだけ管理しなければならない部分が増えるということなのです。また、オブジェクトが、他のオブジェクトに委譲を行って、その先もさらに委譲を繰り返すような場合、プログラムが読みにくくなるのも事実です。つまり間接層は、最小限に絞り込むべきなのです。

とは言え、実際には過剰なレイヤー構造になっていることが多いような気がします。アジャイルという考え方が出てきて、「Just enough」や「YAGNI」なんてことが言われるようになりましたが、今を思えば、これはオブジェクト指向の側にそもそも過剰な複雑性を生む性質があったために、わざわざ言わなければならなくなったことのようにも思えてきます。

オブジェクト指向特有の問題として指摘されている中で、ああこれはと思ったのは、オブジェクトをどう作るかという問題、すなわち Dependency Injection の問題です。

OOP was once seen as the silver bullet that was going to save the software industry. Nowadays we need a silver bullet to save us from OOP, and so, we are told (in the next paragraph), the Inversion of Control Container was invented. This is the new silver bullet that will save the old silver bullet. Object Oriented Programming is an expensive disaster which must end | Smash Company

これはまさにオブジェクト指向にしか存在し得ない問題を、比較的大掛かりに解決しようとした例の代表だと言えそうです。今改めて考えると、この枠組みには二つの問題があって、一つはインタフェースベースのポリモーフィズムの問題(あらかじめ想定したインタフェースの範囲の柔軟性しか持てない)、そしてもう一つは、本当に複数の実装を必要とするケースがどれだけあるのか? という問題です。後者は仮に統計が取れれば面白い数字が出てきそうですが、少なくともテスト時にモックオブジェクトに置き換えられるという主張は、テスト容易性はすなわち良いデザインではないと、Ruby on Rails の作者である David Heinemeier Hansson 氏に批判されています。

最後に

なんか、「もうやめて!オブジェクト指向のライフはゼロよ!」みたいな感じになってしまいましたが、これは完全に Elm のせいです。

オブジェクト指向と関数型プログラミングは両立するのか?

オブジェクト指向と関数型プログラミングは両立すると言われる。実際に両方のパラダイムを持つプログラミング言語も存在し普及もしている。確かに言語というレベルでは両立しているように見えるが、果たしてプログラムデザインのレベルではどうなのだろうか? 関数型プログラミングに入門してから一年余り、どうもこの二つの考え方は両立しないのではないかという思いが強くなってきた。あるいは、これらが両立するという前提が混乱の元になっているのではないかと思うようになった。

例えば、「関数型つまみ食い: 関数型プログラミングの何が嬉しいのか?」では、オブジェクト指向と関数型での、状態に対するアプローチの違いについて触れた。オブジェクト指向では状態とその遷移を隠蔽することによってシステムを単純化する一方、関数型では(多少冗長になっても)状態遷移を明確にすることによってシステムの動作を予測しやすくするという対比だ。つまり両者は、状態に関しては真逆のアプローチを取っている。

オブジェクト指向でも、不可変なデータを扱うことによって意図しない状態遷移を防ぐことができるし、それによってオブジェクト指向でなくなってしまうわけではないと考えることもできる。あるいは、副作用を避ける(不可変なデータを扱う)場所と、副作用を扱う場所を分けることによって、両者の考え方をうまく共存させることができるのではないかと。

しかし、先月開催された Elm Tokyo Meetup にて教えて頂いた、Elm Europe 2017 のアーカイブ動画を見ていたら、オブジェクト指向と関数型はそもそも根本的に異なる思想なのだという思いを新たにした。ちなみに Elm は Web のフロントエンド開発に特化した、純粋関数型のプログラミング言語である。

この発表では、プログラムデザインの流れをもう一度基礎から再考すべきであるということが語られている。まずは一枚岩で構造化されてない状態から初めて、「Build -> Discover -> Refactor」というサイクルを回しながら、構造化すること自体を目的化せず、今行おうとしているそのリファクタリングによって「そもそも何を保障したいのか?」を問う。

ここでの狙いは、過去の経験によって培われた「構造化の先走り」を防ぐことにある。「構造化の先走り」とは何か? Elm で有名な問題に「コンポーネント・アンチパターン」というものがある。

以前の Elm のチュートリアルでは、UI をコンポーネントの集まりとして表現する、というそれまで当たり前に考えられてきた文脈に沿って、状態とその変更ロジック、そしてその状態を表示するためのビューをひとまとめにしたコンポーネントというものを定義し、それらを組み合わせることによってアプリケーションを構築するという手法を紹介していた。しかし、このデザインでは、アプリケーションが大きくなるにつれてコンポーネント同士を連携させるためのボイラープレートが膨大になったり、柔軟性に欠けるケースや無駄な重複が現れるとして、その後、コンポーネント化はできるだけ避けるべきだと明言されるようになった。

Elm Europe の動画では、コンポーネントとは結局のところ、状態と操作をひとまとめにする、オブジェクト指向の構造化手法であることが指摘されている。長らくオブジェクト指向に親しんできたプログラマーが Elm に同様の手法を適用した結果、思ったような柔軟性を得られなかったという流れだ。

このブログでは過去に「オブジェクト指向とは何だったのか?」という記事で、オブジェクト指向をその起源に遡って解釈しようと試みたことがある。

ケイ氏は、オブジェクトを、ネットワークを形成してメッセージを送り合うコンピュータのメタファーとして捉えており、インターネット上のサーバーのように、リクエストをメッセージとして受け取り、そのメッセージをサーバー側で解釈して何らかの処理を行うというモデルを想定していた。

この全てがオブジェクトであるという原則と、大きなシステム(オブジェクト)は小さなシステム(オブジェクト)の組み合わせで作られるという「再帰的デザイン」によって、どんな複雑なシステムをデザインするときでも、覚えなくてはならない原則は少なくて済むようになる。

オブジェクト指向では上のような再帰的構造を持つものとしてシステムを捉える。これがアプリケーションに応用されると「アプリケーションはより小さなアプリケーションの集合によって表現される」ことになる。凝集性の観点から言えば、これはとても魅力的なアプローチに思える。アプリケーションを構成する、より小さなサブアプリケーションは各々が独立していて、変更の影響も局所化される。

しかし、関数型の世界(少なくとも Elm の世界)では、このモデルに疑いの目が向けられることになった。

件の動画では、アプリケーションはアプリケーション固有の(そもそも必要とされる)複雑さを持つのだから、その複雑さに対応するために、データから操作を切り離し、両者をより柔軟に組み合わせることによってその複雑性に対応すべきだということが示唆されている。その結果として、Elm ではグローバルな状態やロジックを集めたコードが長くなることについて、他の言語よりも寛容である、ということが説明される。これはあるいは Elm の持つ強力な型システムの支えもあるかもしれないが。

アプリケーションの再帰的構造というオブジェクト指向のモデルは、昨今のフロントエンドのような複雑なシステムを表現するには単純に過ぎるという問題は、オブジェクトとリレーショナルモデルをマッピングするときに問題視されたインピーダンスミスマッチの問題に似ているかもしれない。

最近、この問題に関係していると思われる大変興味深い記事に遭遇した。Cindy Sridharan 氏による「小さな関数は有害だと考えられる」というタイトルの記事だ。

タイトルが若干ミスリーディングであることは本人も認めているが、彼女が言わんとしていることはこういうことである。名著「Clean Code」などに書かれていたり、あるいはソフトウェア開発における著名人が喧伝するような、今では当たり前とされる、オブジェクト指向時代に生まれた設計指針が果たして今も本当に有効なのかどうか今一度確認してみるべきではないのか、と。

彼女の問題意識は、適切な抽象化というものがそもそも難しいのだというところから出発して、DRY原則などによって無条件に構造化を推し進めようとする既存の設計手法は、多くの場合に失敗に終わる抽象化によって、可読性や柔軟性を欠いたコードになることが多いという発見に辿り着く。オブジェクト指向時代の多くのプラクティスは抽象化が適切に行われることを前提にしているため、物事が複雑に揺れ動く現実の世界では、そもそもそのメリットを享受することが難しい。

duplication is far cheaper than the wrong abstraction, and thus to prefer duplication over the wrong abstraction

そもそも完璧な抽象化というものは存在しない。抽象化された概念というのは、ある文脈を前提にした主観的なものに過ぎないからだ。そしてのその文脈はあらゆる要因によって常に揺れ動いている。Sridharan 氏は、既存のコードが持つ影響についても指摘している。すでに書き上がったコードはある種の成果であるため、たとえ文脈が変わってしまっても、そもそもの文脈を廃棄することには心理的な抵抗が働くのだと。その結果、いびつな形で増改築が行われた建築物のように、構造化はされているが、全体像を捉えるのは難しいプログラムが出来上がってしまう。

より速くそして複雑に変化するシステムを捉えるためには、無条件な構造化については慎重にならなければならない。下手に構造化するぐらいであれば、多少関数が長くなっても実直に書かれている方が遥かに可読性が高く修正もしやすい。そして、過剰な抽象化を避けるためにコードのライフサイクルについても思いを馳せるべきだと Sridharan 氏は指摘する。これまでの設計指針では、コードを追加したり、削除したりする際は Open-Closed Principle(開放/閉鎖原則)に則るべきだと言われてきた。しかし、その原則がうまく働くのは抽象化がうまく行われた時だけである。であれば、「修正しやすい」こととは何かということを今一度考え直すべきなのかもしれない。

さて、この議論を呼びそうな記事に対しては、当然のことながら、既存の設計指針を支持する陣営から反論が行われている。以下はその一例である。

反論の要旨は以下のような感じだ。

  • 大きな関数は変更が局所化されないので脆弱である
  • ユニットテストが書きづらくなるため、結果としてテストされないコードが増える
  • 適切に抽象化されれば、やたらと長い名前問題はそもそも起こらない
  • コードは Open-Closed Principle に従って修正されるべきである
  • 大きな関数は副作用を持つ可能性が高くなる

こういう反論はいかにもありそうだという印象を個人的には受ける。「あなたはそもそも適切に抽象化できていないから」小さな関数が有害だと感じるのだという、オブジェクト指向界では比較的馴染みのあるものだ。「そもそも抽象化が難しい」というところには応答しておらず、議論はすれ違っている印象を受ける。

「そもそもやり方が悪いんだ」問題は、以前このブログで紹介した「Is TDD Dead? 会談」にも現れていた。

DHH氏の想定するようなTDDは、Beck氏やFowler氏の想定するTDDとは異なり、単にやり方が悪いからうまく行かないだけで、DHH氏の発言は「わら人形論法」に過ぎないというのが、TDD肯定派の反論である。それに対して「うまく行かないのはやり方が間違えているからであって、正しく実践すればいつかは正解に辿り着ける」というのは「faith-based TDD」という信仰であって、そのようなものは認められないというのが DHH氏の立場であり、ここがすれ違いの中心になっている。

そして、「ユニットテストが書きづらくなる」という主張も、関数型(特に Elm のような静的型付けの)ではユニットテスト の役割が相対的に小さくなること、あるいは「TDD再考」でも取り上げたように、ユニットテストそのものの価値の問題を考えると、それほどクリティカルな反論であるとは言えないように思える。

Cotoami コンセプト考: 発想を支援するツール

仕事の合間に Cotoami というシステムを開発している。

Cotoami は、それ以前に開発していた Oinker というシステムの後継に当たる。

これはこれで便利に使っていたのだけど(例えば、この「ゆびてく」の記事の多くは、一度 Oinker で素材を作ってから書いている)、どうしても身内に閉じてしまいがちなので、さらに色々な展開を望むならオープンに開発した方が良いのでは? という考えに至った。

そのような経緯で、Oinker をオープンソースとして一から作り直そう、ということで始めたのが Cotoami プロジェクトである。

2017年5月現在、完成にはまだ程遠い状態であるが、中途半端な状態でもとりあえずサービスとして公開して、興味を持って頂いた方々からのフィードバックを募っている。

断続的に開発して5ヶ月程経ったが、すでに Oinker とは大分異なるシステムになってきている。最終的にどのようなシステムにするかもぼんやりとしか決めておらず、思いついたアイデアを Twitter に投げて、それに対していろんな意見や提案を頂いて考え直したりと、そのようなプロセスを経て、最終的には当初の想像とは全く違うシステムが出来上がれば良いなと期待している。

現段階でぼんやりとしながらも頭の中にあるアイデアを、全て俎上に載せて、色々な方々からフィードバックを頂く機会を作れないかということでこの記事を書いている。とりあえず今回は第一回ということで、中心となりそうなコンセプトについて紹介してみたい。


Cotoami で何を作ろうとしているのか? 大きな括りで言えば「発想を支援するツール」ということになると思う。

ここで注意しないといけないのは、そもそもパーソナルコンピュータやインターネット、そしてWeb自体が「発想を支援するツール」になっているという事実である。そういう基盤がすでにある上に「発想を支援するツール」をわざわざ作る意味については常に考えておかないといけない。

パーソナルコンピューティングの主題は、コンピュータを使っていかに人間の能力を高めるか(「Amplify human reach」)ということであったが、これは言い換えれば「クリエイティビティ(創造性)」の追求である。 – 「オブジェクト指向とは何だったのか?」 

もっと具体的に言えば、パソコンやスマートフォンを買えば普通に付いてくるテキストエディタやその他の基本的なツール、こういったものを押しのけてまで必要なものになり得るのかという問いである。結局テキストエディタでいいんじゃない? とならないかどうか。テキストエディタは思っている以上に柔軟で強力である。

というわけで、まずは、最も単純なツールであるテキストエディタを出発点に考えてみたい。

テキストエディタで簡単に実現できないことは何か? それはコンテンツに構造を持たせることである。インデントなどを駆使して擬似的な構造を表現することはできる。しかしそれはあくまでも見た目上の構造である。構造を構造として扱うことはできない。

ここで一つ目の要件が出てくる。

要件 1) コンテンツに構造を持たせる

複雑な発想を表現するためには、コンテンツに構造を導入する必要がある。そのような構造をサポートするツールで普及しているものと言えば、最も基本的なところでファイルシステム(ファイルシステムを駆使すればテキストエディタでもある程度構造を扱えるようになる)、そしてアウトライナーやマインドマップ、さらには Web 上での共同作業を支援するあらゆるツールの基礎となっている WikiWikiWeb がある。

これらのツールが採用する構造のタイプに目を向けると、大体以下の二種類に分かれる。

  1. ツリー型: ファイルシステム、アウトライナー、マインドマップ
  2. ネットワーク型: WikiWikiWeb

ツリーとネットワークを比較した時に最も分かりやすい違いは、ある一つのコンテンツがたった一つの場所に所属するか、あるいは複数の場所に所属するかという違いである。ツリー型だと一つのコンテンツは一つの場所にしか所属させることができない。つまり、コンテンツの文脈は常に一つになるが、ネットワーク型だと一つのコンテンツを複数の文脈の中に配置することができる。

アウトライナーやマインドマップのようなツールは、一つのテーマ(文脈)を出発点に、そこから連想したり知識を細分化したりするのが基本になる。一つのテーマを俯瞰したり、まとめたりするのには便利なのだが、発想を広げようとするとツリー構造が制限になる。

ツリー型だと知識の俯瞰はしやすいが、本来の知識が持つ多様な文脈を表現できない。ネットワーク型だと知識の全体像を掴むのは難しくなるが、複数のテーマを横断するような知識の表現が可能になる。

Cotoami としては、「発想を広げる」という観点から、一つのコンテンツをいろんな文脈に置くことのできるネットワーク型を選択したい。

要件 2) コンテンツはネットワークを構成する

そして、ここまでに挙げたツールの多くが個人向けのツールである。より発想を広げたいと考えた時、やはり他の人と何らかの形で連携できた方が良さそうである。それも少人数で流動性の低いチームだけでなく、不特定多数の人とコラボレーションできればもっと発想は広がるだろう。

要件 3) 個人から、複数人のチーム、さらには不特定多数の人とのコラボレーションをサポートする

ここまでに挙げたツールの中で、これら三つの要件を満たすのは WikiWikiWeb だけである。この WikiWikiWeb が、Cotoami から見たときの重要な参照点になる。

WikiWikiWeb は、プロダクトそのものよりも、コンセプト自体が永遠に生き残るような「発想を支援するツール」の傑作だと思う。Cotoami としても、結果としてそのようなコンセプトを残せればと思うのだけど、それはちょっと高望みし過ぎかもしれない。

さて、「発想を支援する」という観点から考えた場合、この WikiWikiWeb に足りないものは何だろうか?

それは、どんな小さなアイデアでも気軽に投稿できるような仕組みがない、ということではないだろうか。

思いついたことを気軽に書き込める Inbox 的な Wiki ページを用意すれば良いのではないかと思われるかもしれない。しかし、Wiki の場合、一つのページ内に書き込まれた内容はテキストエディタと同様に見た目上の構造しか持たせることが出来ない。ページとして登録するにしても、思いつきの断片でわざわざページを作りたくないというケースも多いだろう。ユーザーインタフェース的な問題もあって、投稿障壁が最小限になっているとは言い難い部分もある。

発想の可能性を広げるためには、投稿障壁を最小限にして材料となる情報を出来るだけたくさん集めること、そして、どんな小さなアイデアでも構造の単位としてネットワークを構成できるように、そしてそれを柔軟に操作できるようにしたい。というわけで、以下の二つの要件が導き出される。

要件 4) どんな小さなアイデアでも気軽に投稿できる(投稿障壁を最小限にする)

要件 5) どんな小さなアイデアでも構造の単位となれる

Cotoami では、投稿障壁を最小限にするために、コンテンツの入力部分はチャットアプリと同じ形になっている。

Twitter が情報発信の障壁を劇的に下げて、それ以前までは考えられなかったような発言を利用者から引き出したように、情報の入力を Twitter や、さらに障壁が低いと考えられるチャットの形式にすることによって、どんな些細な情報でも発想の材料として利用できるようにしたい。

投稿障壁をチャットのレベルまで下げると、発想の材料としては使えないようなノイズも多く含まれるようになる。そこで、以下のような要件が出てくるだろう。

要件 6) ネットワークに参加させるコンテンツを取捨選択できる

さて、この辺からいよいよ Cotoami の核心に近づく。

チャット形式で入力されたアイデアは、思いつきで投稿されるため、その内容は比較的ランダムになりやすい。特に複数人で会話をしているようなケースだとそのような傾向が強くなるだろう。ある程度テーマを決めて話していても、その周辺となるような話題に足を踏み入れたりすることも少なくないはずである。あるいは個人で利用しているケースでも、思いついたことは何でも記録するという形で利用していれば、そこに様々なテーマが現れてくるはずだ。それらのテーマを事後的に発見して、構造を立ち上げていけるような仕組みが欲しい。

要件 7) コンテンツの中から事後的にテーマを発見して構造を立ち上げていける

ここが既存の発想支援ツールと一線を画すところだと言えるかもしれない。アウトライナー、マインドマップ、そして WikiWikiWeb も同様、基本的には「連想」をベースにした発想を支援するツールである。ツリーの場合、その連想はどちらかと言えば、上から下というように細分化する方向に向かう。ネットワークの場合は、より自由な連想を可能にするが、隣接する領域にしか発想が伸ばせないという意味では同じである。

ここで Cotoami として考えていることは、一見相互に関係ないと思われるコンテンツの中につながりを発見して、それを新しい発想の拠点とすることである。

具体的にどのように実現するか? 以下の図を見て欲しい。

まず、チャット形式で投稿される小さなコンテンツ、これを Cotoami では Coto(コト) と呼んでいる。その Coto は、Cotonoma(コトノマ) という、チャットルーム的な場所で投稿される。「Stage 1 – Posting to timeline」の部分は、まずは Cotonoma を作って、そこに Coto を投稿して行く(チャットする)という段階を表している。

そうして集めたランダムな Coto の中につながりを探して、そのつながりの中心となるような Coto を選び(あるいはつながりを表す名前を新たに Coto として投稿して)、そこから関係する Coto に向かってリンクを作成する。これが二段階目の「Stage 2 – Connecting」である。

そのようにつながりを作って行くと、Cotonoma の中にいくつものグループが出来上がってくる。そして、その中からこれは重要だと思えるものが出てくるはずである。その重要だと思われるグループの中心となっている Coto を Cotonoma に変換することによって、その新しく生まれたテーマを、専用の場所で掘り下げて行くことができる。これが「Stage 3 – Convert a category coto into a cotonoma」である。

その後はまた最初のステージに戻って同じ流れを繰り返して行く。このようにして生まれた Cotonoma(テーマ)のリストは、発想のプロセスを開始する前に想定していたものとは異なるものになっている可能性が高い。これを「発想」の肝として考えるのが Cotoami のコンセプトである。


というわけで、今回は Cotoami の中心となるコンセプトについて書いてみた。第一回ということで、比較的長い間維持されそうな核となるコンセプトに絞ったのだが、ツッコミどころは色々とありそうな予感もある。あるいは、これらのコンセプトからどのような展開があり得るのかということも開発を進める上で明らかになってくると思う。このような話題、あるいはツールに興味のある方は、是非是非 Cotoami プロジェクトをウォッチして頂いて、思いついたことがあったら何でも、Twitter やコメント欄などで気軽に話しかけて頂けたら幸いである。

関数型つまみ食い: 関数型プログラミングの何が嬉しいのか?

 

「モナド会」とは、モナドをまともに使ったことがない人間が、モナドどころか関数型プログラミングの経験もない人間に、モナドについて解説するという恐ろしい会である。

実は以前、モナドについての記事を書いたことがある。

モナドについての知識が全くない頃に(今でもかなり怪しいが)、Philip Wadler 氏の論文を読んで、少し分かった気になったので軽い気持ちで書いた記事だ。しかしその後、何の間違いなのか、「モナド」でググるとこの記事が1ページ目に表示されるようになってしまった。本当に申し訳ない気持ちでいっぱいである。

この責任ある立場としては、「モナド会」なるものを開催し、分かったつもりの勢いで初心者に解説を試みて、そしてその成果をここで紹介することでより混乱を深めていくしかない、そのように決意した次第である。

というわけで、今回は「モナド会」で説明を試みた話題の中から、最も根本的な話である「そもそもなんで関数型プログラミングが必要なのか?」というお題について紹介してみたい。

 

オブジェクト指向と不確定性

関数型プログラミングのメリットは、これまでの主流を占めていたオブジェクト指向プログラミングとの比較で考えると分かりやすい。

一言で言えば、オブジェクト指向と比較して関数型は「原因と結果を局所化するので、システムの動きが分かりやすくなる」。

どういうことだろうか?

以下の図を見て欲しい。

オブジェクト指向では、システムに何か動きがあったとき、その動きの原因となる箇所と、結果となる箇所が分散しているため、システムの動作(状態遷移)が把握しづらくなる。上図で言うと、青い部分が原因になる箇所で、赤い部分が結果として状態変更の行われる可能性のある箇所だ。

まず、青丸に Arg と書かれている method の引数が動作の入力になるというのは、比較的すんなりと理解できる。ところが、図をよく見ると、Devices と書かれた箱も青い線で囲まれていて、入力の一部になっていることが分かる。Devices は、プログラムの外部にあるサービスを表している。単にオブジェクトを利用するときには意識に上らないことが多いが、実は Devices の状態も事前条件として、動作に影響を与える「原因」の一部になっている。

さらに、結果の方を見てみると、動作に関係しているオブジェクトそれぞれの状態が変更される可能性がある上に、Devices にも状態変更が起こる可能性があることが分かる。

Devices を操作するのがプログラムのそもそもの目的なのだから、当たり前と言えば当たり前の事態なのだが、オブジェクト指向言語でユニットテストを書いたことがある人なら、なんとなくこれらの厄介さが分かるのではないだろうか。

とあるメソッドのテストを書く場合、単純に引数を渡して実行すればOKというわけには行かず、依存オブジェクトやシステムについて、何らかの準備が必要になることが多い。そして、結果を検証する際にも、オブジェクトの境界だけを確認するか(Mockist Testing)、あるいは分散したシステムの状態を確認するか(Classical Testing)といった選択に悩むことになる。

このように、プログラムから直接接続された Devices(外部サービス)は、プログラムの挙動を予測しづらくする諸悪の根源なのである。

 

純粋関数 – 原因と結果の局所化

関数型では、この原因と結果を、関数の入出力として局所化するため、システムの動きが格段に分かりやすくなる。

この原則が徹底されているとき、つまり、システムで発生し得る状態遷移の全てが関数の入出力として表現されてるとき、これらの関数を純粋な関数と呼ぶ。

純粋な関数だけで構築されたシステムでは、その入出力として表現されている以外の出来事は起こらない。つまり、関数の入出力を見ればシステムがどのように動くかを完全に把握できるということだ。

そのようなシステムを図にしたのが以下である。

システムが動くときの原因と結果が、関数の入出力として局所化されていることが分かる。

これでシステムの状態遷移がシンプルになったし、めでたしめでたしと言いたいところだが、オブジェクト指向で Devices にアクセスしてた部分はどうなったのだろうか? Devices を操作できなければ、まともなプログラムは作れないはずである。

実はそこに純粋関数型プログラミングのトリックがある。

図をよく見ると、関数は入力を得て出力を返すのみで、Devices へ直接アクセスすることはないものの、入出力を受け渡しするレイヤーとして Runtime というものが Devices との間に挟まっている。

純粋関数型は、Devices を直接操作できない代わりに、Devices への命令をデータとして出力し(図中の出力に Command が含まれていることに注目)、それを Runtime に渡すことによって、間接的に Devices を操作する。

このようなややこしい迂回をすることによって、外部サービスをプログラムから切り離して、関数の純粋性を保つわけである。

Devices への命令をデータ化して、プログラム全体を純粋関数にしようとするのは、関数型プログラミングの中でも最もハードコアな部類になるとは言え、基本的に関数型プログラミングは、純粋関数を出来るだけ多く導入することによってシステムから不確定性を取り除こうとする考え方だと言って良いのではないだろうか。

純粋関数にはメリットがある。しかし、それを徹底しようとすると「命令をデータとして表現する」というややこしい方法を取らざるを得ない。その結果、命令型の言語のような簡潔さは失われることになる。その失われた簡潔さを取り戻すために「モナド」のような仕組みが活躍することになるのだが、これはまた続きの記事で紹介したいと思う。

Kubernetes on AWS: LoadBalancer型 Service との決別

LoadBalancer型 Service (type: LoadBalancer) は、Pod群にアクセスするための ELB を自動的に作ってくれて便利なのだが、ELB に関する全ての設定をサポートしているわけではなく、Service を作り直す度に、k8s の外側でカスタマイズした内容もやり直さなければならないのはつらい。というわけで、type: LoadBalancer を利用するのは止めて、ELB は Terraform で管理し、そこから NodePort型 Service に接続する方法を試してみた。

Kubernetes がサポートする ELB 設定

「ELB に関する全ての設定をサポートしているわけではなく」と書いたが、今現在どれぐらいサポートされているのだろうか? 改めて調べてみた。

Service 定義の metadata.annotations に、以下の値を書くことで ELB の設定を行うことが出来る (v1.5現在)。

  • Backend protocol
    • TCP – default
    • HTTP – service.beta.kubernetes.io/aws-load-balancer-backend-protocol: http
    • HTTPS – service.beta.kubernetes.io/aws-load-balancer-backend-protocol: https
  • SSL Certificate
    • service.beta.kubernetes.io/aws-load-balancer-ssl-cert: "[cert-arn]"
  • SSL Port
    • service.beta.kubernetes.io/aws-load-balancer-ssl-ports: "443"
    • The SSL certificate will only be used for 443 and not 80.
  • Internal ELB
    • service.beta.kubernetes.io/aws-load-balancer-internal: 0.0.0.0/0
  • Security group
    • service.beta.kubernetes.io/load-balancer-source-ranges: [a comma separated list of CIDRs]
  • Idle timeout
    • service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout: [seconds]
  • Access logs
    • Enabled – service.beta.kubernetes.io/aws-load-balancer-access-log-enabled: [true|false]
    • Emit interval – service.beta.kubernetes.io/aws-load-balancer-access-log-emit-interval: [minutes]
    • s3://bucket/prefix
      • S3 bucket – service.beta.kubernetes.io/aws-load-balancer-access-log-s3-bucket-name: [bucket-name]
      • S3 prefix – service.beta.kubernetes.io/aws-load-balancer-access-log-s3-bucket-prefix: [prefix]
  • Cross-Zone Load Balancing
    • service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: [true|false]
  • Connection draining
    • service.beta.kubernetes.io/aws-load-balancer-connection-draining-enabled: [true|false]
    • service.beta.kubernetes.io/aws-load-balancer-connection-draining-timeout: [seconds]
  • Proxy protocal
    • service.beta.kubernetes.io/aws-load-balancer-proxy-protocol: '*'
  • Route53

こうやってまとめてみると、v1.5 においては、ELB の設定項目はほとんど網羅されてるような印象を受ける。Route53 についても Third party の拡張を使えばなんとかなるようなので、ほとんどのケースではわざわざ LoadBalancer を別管理にする必要はないのかもしれない。

それでもあえて、LoadBalancer を k8s の外側で管理する理由があるとすれば、

  • k8s に問題が起きた時に、通常の EC2方式に戻せるようにしておきたい。
  • Application Load Balancer (ALB) を使いたい。
  • ELB に分かりやすい名前を付けたい。
    • k8s側から作ると a5902e609eed711e69a1986001d7b1fb みたいなランダムな名前になる。
    • Tag kubernetes.io/service-name みれば、どの Service のものかは分かるのだけど。
  • Cloudwatch Alarm を Terraform で管理したい。
    • ELB に Cloudwatch Alarm を設定する場合は、ELB も Terraform で管理しておいた方がやりやすい。
  • k8s の LoadBalancer 管理に一抹の不安がある。

ぐらいだろうか。

移行手順

AWS 上の Kubernetes クラスタが kops で構築されていることを前提に、ELB を Terraform で 管理する運用に移行してみる。

  • クラスタの名前を仮に k8s.example.com とする。

1. Terraform ELB から k8sノードへアクセス出来るように追加のセキュリティグループを作る

k8sノード用とELB用の二つのセキュリティグループを作る。

# For nodes
resource "aws_security_group" "nodes" {
  vpc_id = "${module.environment.vpc_id}"
  name = "additional.nodes.k8s.example.com"
  description = "Additional security group for nodes"
  tags {
    Name = "additional.nodes.k8s.example.com"
  }
  # ELB から NodePort経由のアクセスを受け付ける
  ingress {
    from_port = 0
    to_port = 0
    protocol = "-1"
    security_groups = ["${aws_security_group.service_elb.id}"]
  }
  egress {
    from_port = 0
    to_port = 0
    protocol = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

# For ELB
resource "aws_security_group" "service_elb" {
  vpc_id = "${module.environment.vpc_id}"
  name = "k8s-service-elb"
  description = "Security group for k8s service ELBs"
  tags {
    Name = "k8s-service-elb"
  }
  ingress {
    from_port = 80
    to_port = 80
    protocol = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  ingress {
    from_port = 443
    to_port = 443
    protocol = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  egress {
    from_port = 0
    to_port = 0
    protocol = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

kops 1.5.x 以降では、マスターあるいはノードに対して、デフォルトのセキュリティグループの他に、独自のセキュリティグループを追加することができる。

上で作ったセキュリティグループ(additional.nodes.k8s.example.com)が、ノード起動時に適用されるようにクラスタの設定を更新する。

$ kops edit ig nodes
...
spec:
  additionalSecurityGroups:
  - sg-xxxxxxxx  # additional.nodes.k8s.example.com
...

$ kops update cluster k8s.example.com --yes

Sticky session を必要としない場合

2. Service を NodePort 型で立ち上げる

通常 NodePort の番号は自動で決定されるが、Terraform ELB から接続できるように固定の番号を設定しておく。NodePort に設定出来る番号の範囲は 30000-32767

apiVersion: v1
kind: Service
metadata:
  name: example-app
spec:
  selector:
    app: example-app
  type: NodePort
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
    nodePort: 30000

3. Terraform で ELB を立ち上げる

  • instance_porthealth_checktarget に NodePort を指定する。
  • instances は設定しない(後ほど Auto Scaling group と紐付けるため)。
  • k8sノードへアクセス出来るように、先ほど作ったセキュリティグループ(k8s-service-elb)を設定する。
resource "aws_elb" "example_app" {
  name = "k8s-example-app"
  cross_zone_load_balancing = true
  subnets = ["${split(",", module.environment.subnets)}"]
  security_groups = ["sg-xxxxxxxx"]   # k8s-service-elb
  listener {
    instance_port = 30000
    instance_protocol = "http"
    lb_port = 80
    lb_protocol = "http"
  }
  listener {
    instance_port = 30000
    instance_protocol = "http"
    lb_port = 443
    lb_protocol = "https"
    ssl_certificate_id = "${module.environment.ssl_certificate_id}"
  }
  health_check {
    healthy_threshold = 3
    unhealthy_threshold = 2
    timeout = 5
    target = "HTTP:30000/healthcheck"
    interval = 30
  }
}

4. ELB にドメイン名を設定する

サービスのドメイン名が、たった今作った ELB を参照するように Route53 を設定する。

resource "aws_route53_record" "example_app" {
  zone_id = "${module.environment.zone_id}"
  name = "app.example.com"
  type = "CNAME"
  ttl = "300"
  records = ["<elb-dns-name>"]
}

5. ELB をノードの Auto Scaling Group に紐付ける

3 で立ち上げた ELB を、k8sノードの Auto Scaling Group (nodes.k8s.example.com) の Load Balancers に追加する。これによりk8sノードがELBに追加される。

6. 全ての k8sノードが ELB に追加されて、InService になることを確認する

サービスのURLにアクセスして、サービスが問題なく稼動していることを確認する。

Sticky session を必要とする場合

アプリケーションが sticky session を必要とする場合は、予め ingress controller をクラスタにインストールしておき、そこ経由でサービスにアクセスさせる。なので、このケースではアプリケーション用の ELB は立てない。

参考: Kubernetes on AWS で sticky session を実現する | ゆびてく

今回の移行では、ingress controller の入り口となる Service を NodePort で作っておいて、そこにアクセスする ELB を Terraform で作った。

2. Service を ClusterIP 型で立ち上げる

apiVersion: v1
kind: Service
metadata:
  name: example-app
spec:
  selector:
    app: example-app
  type: ClusterIP
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80

3. Ingress controller にドメイン名を追加する

サービスのドメイン名が ingress controllerの ELB を参照するように Route53 を設定する。

resource "aws_route53_record" "example_app" {
  zone_id = "${module.environment.zone_id}"
  name = "app.example.com"
  type = "CNAME"
  ttl = "300"
  records = ["<ingress-controller-elb-dns-name>"]
}

4. Ingress を追加する

Ingress controller から対象のサービスにアクセス出来るように ingress rule を追加する。

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: sticky-session-ingress
spec:
  rules:
  - host: app.example.com
    http:
      paths:
      - path: /
        backend:
          serviceName: example-app
          servicePort: 80

5. サービスが問題なく稼動していることを確認する

サービスのURLにアクセスして、サービスが問題なく稼動していることを確認する。

Cotoami成長記録 (5) – リアルタイムチャット

ようやくチャットが出来るようになりましたー(ウハウハ)。

他のメンバーが同じコトノマに接続していれば、投稿の内容をリアルタイムに共有出来ます。

上の動画にあるように、チャット中に新しい話題に移りたくなった場合は、新しいコトノマをタイムラインに投稿してメンバーをそちらに誘導する、なんてことも出来るようになりました。

メンバーがコトノマに接続しているかどうかは、以下のように、表示の濃淡で分かるようになっています。

チャットが出来て、入れ子にすることが可能なコトノマ(部屋)による情報の整理だけでも、結構色々なことが可能になるような感じもありますが、コトやコトノマの編集がまだ出来ないし、さらには Oinker 最大の機能である「つながりを作る機能」もないので Cotoami の全貌はまだ見えていません。


Cotoami の最新バージョンは https://cotoa.me/ で試験運用中。

Cotoami のロードマップ: https://github.com/cotoami/cotoami/issues/2

細かな更新情報は Twitter でつぶやいています: https://twitter.com/cotoami

ソースコードはこちら: https://github.com/cotoami/cotoami

@__tai2__さんによるCotoami開発日誌: https://cotoami-dev.tumblr.com/

Cotoami成長記録 (4) – コトノマの共有

次の大きなヤマはチャット機能。今月中に実現できるよう頑張りたいと思います。

出来ませんでした _| ̄|○

チャットに必要になるコトノマの共有機能と、自分が参加しているコトノマが分かるように、コトノマ一覧機能を作ったところで力尽きました…

というわけで、現状の Cotoami は、コトノマの中身はシェアできるけど、内容はリアルタイムに更新されない、言わば掲示板レベルのハリボテです。

コトノマには以下のような感じでメンバーを設定できるようになりました(サインアップしてない人への招待機能は未実装)。

cotonoma-members

コトノマ一覧は、以下のような感じで端末によってレスポンシブに表示されます。

PC(左側に表示):

cotonomas-pc

スマホ(ヘッダーからプルダウン):

cotonomas-mobile

一覧の内容は現在地のコトノマ内で作られたコトノマのみに絞り込まれます(ホームではアクセス可能な全てのコトノマを表示)。

来月の今頃にはチャットでウハウハになっていることを願って…

Cotoami成長記録 (5) – リアルタイムチャット


Cotoami の最新バージョンは https://cotoa.me/ で試験運用中。

Cotoami のロードマップ: https://github.com/cotoami/cotoami/issues/2

細かな更新情報は Twitter でつぶやいています: https://twitter.com/cotoami

ソースコードはこちら: https://github.com/cotoami/cotoami

@__tai2__さんによるCotoami開発日誌: https://cotoami-dev.tumblr.com/

Kubernetes Secrets の紹介 – データベースのパスワードやその他秘密情報をどこに保存するか?

なんてこったい(棒)

GitHub の Public リポジトリには、太っ腹な開発者によって大量の Credentials(外部サービスに接続するための秘密キーなど)が公開されており、賢い人たちが日夜クローラーを走らせてそれらを回収し、5万件もの Uber ドライバーの個人情報を頂戴するために利用したり高価な AWS のインスタンスを沢山立ち上げて、もの凄い勢いでビットコインを発掘したりしているらしい。

ウチのリポジトリはプライベートだから問題ないよねって思われる方もおられるかもしれないが、ほんの5分間違って公開しただけで流出したケースもあるらしいので、そもそもコードリポジトリに秘密情報を入れること自体が太っ腹行為の可能性を高めていることを理解しておきたい。

というわけで、Kubernetes でサービスを運用する場合、そういった秘密情報をどこに保存すれば良いかという要求に応えるのが Secrets という仕組みである。

kube-secrets

秘密情報を Secrets というデータベースで集中管理し、それぞれの情報はそれらを必要とする Pod/Container のみに送られる。Docker Image や Container を作るプロセスから秘密情報を切り離せるので、その過程で情報を漏洩させるリスクは少なくなる。Container に送られた秘密情報は tmpfs 上に置かれるので、ノード上のディスクに書き込まれることもない。

Kubernetes 自体がまだ若いプロジェクトなので、この Secrets にも注意しなければならない点がいくつかある。

  • Secrets のデータは etcd の中に平文で保存されているので、etcd には管理者ユーザーだけがアクセス出来るようにセットアップする必要がある。
    • etcdは kubernetes のあらゆるデータを保管しているデータストア。
  • 現在のところ、Secrets に対するユーザーごとのアクセスコントロールは出来ない(将来的にはサポート予定)。

以下は、社内向けに書いた Secrets の簡単なチュートリアル。


Secret のデータ構造

Secret の中身は単純な Key-Value ペアのリスト:

secret-structure

kubectl コマンドで登録されている Secret のリストを見る:

$ kubectl get secrets
NAME                  TYPE                                  DATA      AGE
default-token-pb7ls   kubernetes.io/service-account-token   3         21m
mysecret              Opaque                                2         38s

その中から一つの Secret を選んで中身を見てみる:

$ kubectl describe secret mysecret
Name:       mysecret
Namespace:  sandbox
Labels:     <none>
Annotations:    <none>

Type:   Opaque

Data
====
password:   12 bytes
username:   5 bytes

mysecret の内容を図に書くと以下のような感じ:

mysecret

Secret を登録する

Secret は、以下の二種類のファイルのいずれかを経由して登録できる。

  1. 中身が Value になっているファイル(便宜的に「Secret Value ファイル」と呼ぶ)
  2. YAML あるいは JSON 形式の Kubernetes Manifest ファイル

以下のような Secret を、

[Secret: test-secret] => [Key: password] => [Value: this-is-a-password]

それぞれのファイル形式で登録してみよう。

1. Secret Value ファイル経由

1) ファイルを作る

$ echo -n "this-is-a-password" > ./password

2) --from-file オプションを使って登録

$ kubectl create secret generic test-secret --from-file=./password
secret "test-secret" created

3) 中身を見てみる

$ kubectl describe secrets/test-secret
Name:       test-secret
Namespace:  sandbox
Labels:     <none>
Annotations:    <none>

Type:   Opaque

Data
====
password:   18 bytes

--from-file に指定したファイルの名前が Key になっていることが分かる。

2. Kubernetes Manifest ファイル経由

1) Value を base64 でエンコードする

$ echo -n "this-is-a-password" | base64
dGhpcy1pcy1hLXBhc3N3b3Jk

2) ファイルを作る

以下の内容を secret.yaml に保存:

apiVersion: v1
kind: Secret
metadata:
  name: test-secret
type: Opaque
data:
  password: dGhpcy1pcy1hLXBhc3N3b3Jk

3) -f オプションを使って登録

$ kubectl create -f ./secret.yaml

4) 中身を見てみる

$ kubectl describe secrets/test-secret
Name:       test-secret
Namespace:  sandbox
Labels:     <none>
Annotations:    <none>

Type:   Opaque

Data
====
password:   18 bytes

Secret Value ファイル経由のときと全く同じ Secret が出来ていることが分かる。

Secret の中身を取得する

$ kubectl get secret test-secret -o yaml
apiVersion: v1
data:
  password: dGhpcy1pcy1hLXBhc3N3b3Jk
kind: Secret
metadata:
  creationTimestamp: 2017-03-01T08:49:49Z
  name: test-secret
  namespace: sandbox
  resourceVersion: "12535581"
  selfLink: /api/v1/namespaces/sandbox/secrets/test-secret
  uid: 0747637f-fe5c-11e6-8f7a-0674330dcd09
type: Opaque

Value をデコードする:

$ echo "dGhpcy1pcy1hLXBhc3N3b3Jk" | base64 --decode
this-is-a-password

Secret を使う

Container から Secret を使うには、

  1. 環境変数
  2. Volume としてマウント

の二種類の経路がある。

1. 環境変数

Container の Manifest から以下のような感じで参照:

containers:
- name: http-debug-server
  image: cotoami/http-debug-server:latest
  ports:
  - containerPort: 3000
  env:
    - name: PASSWORD
      valueFrom:
        secretKeyRef:
          name: test-secret
          key: password

Container にログインして、環境変数を見てみる:

$ kubectl get pods
NAME                                 READY     STATUS    RESTARTS   AGE
http-debug-server-4073343574-ro8s8   1/1       Running   0          2m

$ kubectl exec -it http-debug-server-4073343574-ro8s8 /bin/sh

# echo $PASSWORD
this-is-a-password

2. Volume としてマウント

Container 内のディレクトリに、Key をファイル名、Value をファイルの中身としてマウントできる。

以下のように、volumes を定義しておいて、それを volumeMounts でマウントする:

containers:
- name: http-debug-server
  image: cotoami/http-debug-server:latest
  ports:
  - containerPort: 3000
  volumeMounts:
  - mountPath: /tmp
    name: test-secret
    readOnly: true
volumes:
- name: test-secret
  secret:
    secretName: test-secret

Container にログインして、マウントされたファイルを見てみる:

$ kubectl get pods
NAME                                 READY     STATUS    RESTARTS   AGE
http-debug-server-2197190929-q6lke   1/1       Running   0          1m

$ kubectl exec -it http-debug-server-2197190929-q6lke /bin/sh

# cat /tmp/password
this-is-a-password

マウントされた Secret を後から更新した場合は、Kubernetes が自動的に検出してリフレッシュしてくれる。

参考

Kubernetes の登場でインフラ担当の仕事はますます曖昧になっていくような気がする

アプリケーション開発者は Dockerfile を作るところまで、インフラ担当はサービス構成とデプロイメントパイプラインを整備する、という分担を考えていたけれど…

マニフェストファイルを作ってサービスの構成をデザインしたり、ビルドスクリプトを書いて自動デプロイを実現するのも、もうアプリケーション開発者のレベルで出来る。ここを無理に分業すると、むしろ k8s が提供するサイクルの短さを享受出来なくなる可能性がある。サービスが出来上がってからインフラを移行するというのは k8s の考え方に合わないので、開発の初期から k8s 上で育てるのが標準的なモデルになるが、そのモデルに則って開発して行けば、開発者は無理なくそのプラットフォームを使いこなせるようになる。

つまり、DevOps の問題が、Kubernetes によるマイクロサービス化によって、ますます深刻になる可能性がある。アプリケーション更新のサイクルとインフラ更新のサイクルが限りなく近づいたとき、そこにどこまでのコミュニケーションコストを許容出来るだろうか。

チームとして分かれていることのコミュニケーションコストは決して過小評価することは出来ない。

では、インフラチームは Kubernetes クラスタの管理やモニタリングを担当するようにしたらどうか?

筋的には悪くない気もするが、GKE のような環境が充実してきた時に、アプリケーション開発者が自身で出来る領域はさらに広がって行くのではないかと思う。

さらには、Kubernetes のような環境に適応出来る・出来ない、という形でも大きな分断が起きて行きそうな予感もある。

Kubernetes に限らず、インフラの自動化を進めるためには、アプリケーションパッケージのポータビリティが重要になってくる。しかし、そのようなことを意識して開発しているプロジェクトは案外少ないのではないだろうか。例えば、Twelve-Factor App みたいな指針を全く知らないというのも珍しくないのではないか。そうなると、インフラ担当はアーキテクチャやソフトウェアデザインを指導する立場になるが、これは結構広範な指導を必要とするし、そのような規律を快く思わないエンジニアも多い。

一方で、Twelve-Factor App みたいな指針に慣れているエンジニアは、上に書いたように、自分でアーキテクチャや自動化の基盤を作って行けるので、自律してサービスを開発出来る。

アプリ開発もインフラも、一つのチームに閉じることに越したことはないけれど、多くの組織ではそんな贅沢は許されないだろう。組織全体でインフラを刷新していくためには、どうしても独立したインフラチームが必要になる。でも、マイクロサービス開発のサイクルの短さに合わせて、チーム間のコミュニケーションコストを下げて行くのは至難の業のように思える。

DevOpsの起源とOpsを巡る対立 | ゆびてく

Docker(コンテナ型仮想化)と Kubernetes についての簡単な紹介

社内向けに書いた文書です。

コンテナ型仮想化とは何か?

OS上に、コンテナと呼ばれる、隔離されたアプリケーションの実行環境を作り、一台のホスト上であたかも複数のホストが動いているかのような環境を実現するのが、コンテナ型仮想化技術です。

コンテナ型仮想化と従来の VM 型仮想化を比較したものが以下の図です(Xen は AWS EC2 などの仮想化を実現する実装で、Docker はコンテナ型仮想化の代表的な実装)。

container

これらの仮想化技術に共通するのは、

  1. アプリケーションの実行環境(ホスト)を仮想化すること
  2. それらのスナップショットをイメージとして保存することで、アプリケーションを環境ごとパッケージング出来るようにすること

という二つの目的です。

VM 型仮想化ではハードウェアのレベルで仮想化が実現されているので、ホスト上で動く一つ一つの VM の中でそれぞれ別々の OS を動作させることができます。しかしその一方で、実行環境(VM)のサイズが大きくなり、アプリケーションの起動にかかる時間も長くなります。

コンテナ型仮想化では OS 上の実行環境を仮想化するので、OS 自体は限定されますが、その分実行環境(コンテナ)のサイズはコンパクトになり、起動にかかる時間も短くなります。

基本的に VM 型仮想化からコンテナ型仮想化への移行は、一つ一つのアプリケーションの粒度が小さくなる、いわゆるマイクロサービス化を促進します。インフラ上の更新の単位はより小さくなり、変更のサイクルは短くなります。

パッケージング技術の変遷

packaging

Kubernetes とは何か?

1. Docker 実行環境をクラスタ化する

通常、Docker の実行環境は一台のホストに閉じています。

docker-network

同一ホスト内で動くコンテナ同士は、プライベートネットワーク経由でやり取りができますが、ホストの外側とやり取りする場合は NAT (IP Masquerade) を経由する必要があります。

このように、標準の Docker 実行環境では、ホスト間の連携が煩雑になるため、コンテナの数が増えて要求されるリソースが大きくなった時に、容易にスケールアウトすることが出来ません。

この問題を解決するのが Kubernetes です。

Kubernetes によって、複数台のホストから構成される実行環境を あたかも一台の実行環境のように 扱うことができるようになります。

kube1

コンテナを起動する際は、イメージと台数を指定するだけでよく、クラスタのどこにどのように配置するかは Kubernetes 側で面倒を見てくれます(スケジューリング)。

そして、クラスタのリソース(CPU、メモリ、記憶領域など)が足りなくなった場合は、単純にノードを増やすだけで、既存のサービスに影響を与えることなく、いくらでも拡張することができます。

2. Self-healing – 耐障害性

kube2

上図はデプロイプロセスの詳細です。

  1. オペレーターが、どのようなコンテナを何台起動するかといった情報を Spec として Kubernetes 側に渡すと、
  2. Scheduler が、空きリソースを見ながらそれらをどのように配置するかを決定し、
  3. 各ノードに常駐している Kubelet というプログラムがその決定に従ってコンテナを起動します

オペレーターが直接コンテナを起動するのではなく、必要とする状態を Spec として渡すと、Kubernetes 側がクラスタの状態を Spec に合わせようする、というこの挙動が重要です。

仮に、運用中のコンテナに不具合があってサービスがダウンしたとします。Kubernetes はこの状態変化を察知し、Spec の状態に合わせようとして、そのコンテナを自動的に再起動します(Self-healing)。

多少の不具合であれば問題なく運用出来てしまう反面、問題の発覚するタイミングが遅れてしまう可能性もあるのでモニタリングが重要になります。

コンテナではなく、ノードとなるホストマシンに障害があった場合はどうなるか? Kubernetes 環境のセットアップによりますが、AWS の場合は Auto Scaling グループでクラスタが組まれているので、自動的にインスタンスが作り直され、その中に元の状態が復元されます。

3. Pod – 管理上の基本単位

Kubernetes 上で動作するプログラムの最小単位はコンテナですが、管理上の基本単位は Pod というものになります。

pod

Pod は、Volume という記憶領域を共有するコンテナの集まりで、Volume の他には一つのIPアドレスを共有しています。つまり、Pod は Kubernetes 上でホストに相当する単位です。

VM イメージによるパッケージングでは、一つのホストがデプロイの単位になっていました。あるいは、Java EE の WAR パッケージだと、必要最低限のプログラムだけをデプロイの単位に出来て軽量ですが、上図で言うと、デプロイの対象は webapp の部分だけになります。Docker/Kubernetes 環境でマイクロサービス化を推進すると、ホストを構成する様々な部品(webapp, nginx, log collectorなど)全てを、独立してデプロイ出来るようになります。

kubectl というコマンドがセットアップされていれば、以下のようにして Pod の一覧を見ることができます。

$ kubectl get pods
NAME                                 READY     STATUS    RESTARTS   AGE
cotoami-2660026290-81y2g             1/1       Running   0          9d
cotoami-2660026290-xzua4             1/1       Running   0          9d
default-http-backend-t4jid           1/1       Running   0          6d
grafana-807516790-8pszk              1/1       Running   0          63d
http-debug-server-2355350955-38jht   1/1       Running   0          6d
http-debug-server-2355350955-c64y1   1/1       Running   0          6d
http-debug-server-2355350955-g3rjq   1/1       Running   0          6d
nginx-ingress-controller-ka0od       1/1       Running   0          6d
node-exporter-556yr                  1/1       Running   0          63d
node-exporter-euprj                  1/1       Running   0          63d
node-exporter-hzdqk                  1/1       Running   0          63d
prometheus-1314804115-9v0yk          1/1       Running   0          54d
redis-master-517881005-ceams         1/1       Running   0          70d

コンテナのデバッグ

Pod 内のコンテナにログインしたい場合は、リストにある Pod の名前をパラメータにして以下のコマンドを実行します。

$ kubectl exec -it http-debug-server-2355350955-38jht /bin/sh
/app # ls
Dockerfile    README.md     circle.yml    index.js      node_modules  package.json

Pod に複数のコンテナが含まれる場合は、-c オプションでコンテナの名前を指定します。

$ kubectl exec -it POD -c CONTAINER /bin/sh

あるいは、以下のコマンドでコンテナが出力するログを見ることができます。

$ kubectl logs redis-master-517881005-ceams
1:M 16 Feb 03:27:35.080 * 1 changes in 3600 seconds. Saving...
1:M 16 Feb 03:27:35.081 * Background saving started by pid 226
226:C 16 Feb 03:27:35.084 * DB saved on disk
226:C 16 Feb 03:27:35.084 * RDB: 0 MB of memory used by copy-on-write
1:M 16 Feb 03:27:35.181 * Background saving terminated with success

# 複数のコンテナがある場合
$ kubectl logs POD CONTAINER 

4. Deployment – Pod の配備と冗長化

Pod の配備と冗長化を担当するのが Deployment という仕組みです。

deployment

ある Pod について、Spec で定義されたレプリカの数を維持する責任を負うのが Replica Set、Replica Set の配備・更新ポリシーを定義するのが Deployment です。

以下は、Deployment 定義の例です。

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: http-debug-server
spec:
  replicas: 3
  template:
    metadata:
      labels:
        app: http-debug-server
    spec:
      containers:
      - name: http-debug-server
        image: cotoami/http-debug-server:latest
        ports:
        - containerPort: 3000

この内容を、例えば http-debug-server.yaml というファイルに保存し、以下のコマンドを実行すると Deployment として定義された Replica Set がクラスタ内に出来上がります。

$ kubectl create -f http-debug-server.yaml

$ kubectl get deployments
NAME                DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
http-debug-server   3         3         3            3           6s

Rolling Update

Deployment には標準で Rolling Update(無停止更新)の機能が備わっています。

例えば、以下のようなコマンドで Docker イメージのバージョンを更新すると、Replica Set の中のコンテナ全てを一斉に更新するのではなく、稼働中の Pod を何台か維持したまま、一つずつ順番に更新を行います。

$ kubectl set image deployment/DEPLOYMENT CONTAINER=IMAGE_NAME:TAG

5. Service – Pod へのアクセス

Pod への安定的なアクセス手段を提供するのが Service です。

Deployment で配備した Pod にアクセスしようと思っても、実際に Pod がどのノードに配備されているかは分かりません。仮に分かったとしても、Pod は頻繁に作り直されるので、いつまでも同じ Pod にアクセスできる保証はありません。Replica Set に対するロードバランシング機能も必要です。

ClusterIP

そこで Service は、Pod の集合(一般的には Replica Set)に対して安定的にアクセスできる仮想の IP アドレスを割り当てます。これを cluster IP と呼び、Kubernetes クラスタ内だけで通用するアドレスです。cluster IP にアクセスすると、Service の対象となる Pod 群の中のいずれかの Pod にアクセスできます。

NodePort

さらに Service は、クラスタの外部から Pod にアクセスするための経路も開いてくれます。具体的には、各ノードの特定のポートを経由して Pod にアクセス出来るようになります。

service

上図のように、Service の実体は、cluster IP のルーティングとロードバランシング機能を実現する kube-proxy というプログラムです。

クラスタの外部から Service が用意してくれたポートを経由してノードにアクセスすると、kube-proxy 経由で適切な Pod に接続することができます。

Kubernetes on AWS で sticky session を実現する

  • 対象の環境
    • Kubernetes 1.4.x(筆者の環境は 1.4.6)
    • AWS

AWS 上で稼動する Kubernetes (k8s) で LoadBalancer 型の Service を作ると、自動的に入り口となる ELB (Elastic Load Balancer) を立ち上げてくれてとても便利なのだが、AWS では当たり前に出来ていた sticky session が、この構成だとどうも実現できないらしい。

k8s で新規に開発しようという場合なら、sticky session が必要な実装はなるべく避けるのかもしれないが、稼働中の Web アプリを k8s に移行したいとなった場合に sticky session が必要になるケースもあるのではないだろうか。

というわけで、k8s on AWS における sticky session の実現方法について調べてみた。

諸悪の根源 kube-proxy

まず、単に ELB の sticky session を有効にしたらどうなるだろうか?

[ELB] = sticky => [Node] => [kube-proxy] => [Pod]

ELB から kube-proxy までが、Service の内部構造である。まず ELB が選択するのは k8s クラスタの各ノードである。ELB が sticky に設定されている場合、ノードの選択が sticky になるだけで、その先の Pod の選択は kube-proxy に委ねられている。

もう少し詳しく見てみると、

k8s-sticky-session

こんな感じで、ELB が sticky になってても意味はなく、Pod の選択を行う kube-proxy が sticky にならないといけない。

Service の sessionAffinity 機能

実は k8s の Service (kube-proxy) には sessionAffinity という機能がある。これを有効にすれば IP アドレスベースの stickiness を実現できるらしいのだが、なんと type が LoadBalancer の場合はこの機能を利用出来ないという。

ClientIP value for sessionAffinity is not supported when service type is LoadBalancer.

IPアドレスが隠れちゃう問題

では、LoadBalancer 型を利用するのはやめて、NodePort 型の Service にして、sessionAffinity を有効にし、自前で ELB を立てたらどうなるか?

その場合も、ELB がクライアントの IP アドレスを隠してしまうので sessionAffinity は正しく動作しない。現状の kube-proxy は、ELB がサポートしている X-Forwarded-For ヘッダや、proxy protocol を解釈しないので、[ELB] → [kube-proxy] の構成だとどう頑張っても sticky session を実現できないことになる。LoadBalancer 型の Service が sessionAffinity をサポートしない所以だ。

Proxy protocol のサポートについては多くのリクエストがあるようなので、将来的にはサポートされる可能性が高い。もしサポートされれば IP アドレスベースの stickiness については標準構成で利用出来るようになる。

解決策 1) ELB と kube-proxy の間に nginx を立てる

kube-proxy は X-Real-IP ヘッダを解釈するらしい。これを利用して、ELB と kube-proxy の間に nginx を立てて、そこで X-Real-IP ヘッダを追加するという手段が紹介されていた(ちなみに、ELB は X-Real-IP を追加してくれない)。

以下のような構成になる。

[ELB] = proxy protocol => [nginx] = X-Real-IP => [kube-proxy]

解決策 2) Nginx Ingress Controller

解決策 1) も悪くないように思えるが、IP アドレスベースの stickiness だと Web アプリとしてはあんまり嬉しくないので、なんとか諸悪の根源である kube-proxy をバイパスする方法がないだろうかということで見つけたのが、最近サポートされ始めたばかりの Ingress を使う方法だ。

Ingress とは、Service に対する柔軟なルーティングやロードバランシング機能を提供する k8s では比較的新しいリソースで、機能的には LoadBalancer 型の Service を切り出して、独立したリソースにしたような感じである。

Ingress を利用するためには、まず Ingress Controller というものを k8s 内に立ち上げておく必要がある。Ingress Controller の実体は proxy server であり、その proxy server に適用するルールのセットを Ingress リソースと呼ぶ。

ingress-controller

Ingress Controller には色々な実装が提供されていて、その中でも k8s の公式実装と思われる Nginx Ingress Controller が sticky session をサポートするために kube-proxy をバイパスするらしい。まさに今回のニーズにぴったしな実装だと言える。

The NGINX ingress controller does not uses Services to route traffic to the pods. Instead it uses the Endpoints API in order to bypass kube-proxy to allow NGINX features like session affinity and custom load balancing algorithms. It also removes some overhead, such as conntrack entries for iptables DNAT.

Nginx Ingress Controller のセットアップ

実際に Nginx Ingress Controller をセットアップする手順は以下の通り。必要なマニフェストファイル一式は、cotoami-infraのリポジトリ に置いてある。

1) まず、ルーティング先が見つからなかった場合にリクエストを送るための、デフォルトのバックエンドサービスを立てる。このサービスは単に 404 を返すだけである。

$ kubectl create -f https://raw.githubusercontent.com/cotoami/cotoami-infra/master/kubernetes/ingress/default-backend.yaml

2) Ingress Controller を作る前に、ConfigMap による設定を先に作っておく。

$ kubectl create -f https://raw.githubusercontent.com/cotoami/cotoami-infra/master/kubernetes/ingress/nginx-load-balancer-conf.yaml

この ConfigMap の中に、sticky session を有効にする設定 enable-sticky-sessions: "true" が含まれている。

3) 設定が登録出来たら、主役の Ingress Controller を作る。

$ kubectl create -f https://raw.githubusercontent.com/cotoami/cotoami-infra/master/kubernetes/ingress/ingress-controller.yaml

上のマニフェストファイル ingress-controller.yaml では、Ingress Controller に外部からアクセス出来るように、LoadBalancer 型の Service をくっつけてある。これで、ELB 経由で Ingress Controller にアクセス出来るようになる。

(※) Controller本体は、公式の例に倣って ReplicationController で作成しているが、Daemonset で作ることも出来る。

以下のように Service の一覧を見ると、nginx-ingress-controller へアクセスするための Service が登録されているのが分かる。

$ kubectl get svc
...
nginx-ingress-controller   100.66.116.210   a5902e609eed6...   80/TCP     20s
...

a5902e609eed6... と省略表示されているのが ELB の DNS name である。試しにこのアドレスにアクセスしてみると、

default backend - 404

という感じで、先ほど立ち上げておいたデフォルトサービスの応答が得られるはずだ。

4) Ingress をテストするための、テスト用 Web アプリを立ち上げる。

Ingress 経由でアクセスする Web アプリを立ち上げる。ここでは、HTTPやサーバーに関する情報を表示するだけの http-debug-server を立ち上げてみる。

$ kubectl create -f https://raw.githubusercontent.com/cotoami/cotoami-infra/master/kubernetes/ingress/http-debug-server.yaml

マニフェストファイルにあるように、Nginx Ingress Controller 経由でアクセスする場合、Service の type は ClusterIP にしておく。

5) Ingress リソースを登録する

最後にルーティングルールを登録する。

$ kubectl create -f https://raw.githubusercontent.com/cotoami/cotoami-infra/master/kubernetes/ingress/ingress.yaml

このルールでは、ホスト debug.example.com に対するアクセスを、http-debug-server Service にルーティングするように設定している。以下のように、curl コマンドでアクセスすると、デバッグ用の情報がレスポンスとして返されるはずだ。

$ curl <ELB-DNS-name> -i -H 'Host: debug.example.com'

sticky session をテストするためには cookie が必要になるので、以下のようにしてレスポンスヘッダーを表示させる。

$ curl <ELB-DNS-name> -I -H 'Host: debug.example.com'

以下のような感じで、ヘッダーに cookie の情報が含まれているので、

...
Set-Cookie: route=acfc2f2f4d692d7a90b9797428615e29d3936e95; Path=/; HttpOnly
...

この cookie をリクエストに含めて送信する。

$ curl -b route=acfc2f2f4d692d7a90b9797428615e29d3936e95 <ELB-DNS-name> -H 'Host: debug.example.com' -s | python -mjson.tool

レスポンスの JSON データが出力されると思うが、hostname のところに注目して欲しい。何度リクエストを送っても、同じホスト名が返ってくるはずだ。試しに-b オプションを外して送信すると、送信するたびにホスト名が変わる。

大分ややこしい感じになったが、以上が Ingress で sticky session を実現するための手順である。

解決策 3) service-loadbalancer

その他、k8s が提供している service-loadbalancer を使うことで sticky session を実現する事例が紹介されていた。

Cotoami成長記録 (3) – コトとコトノマ

今回は重要な機能を追加しました。その名も「コトノマ」機能です。

Cotoami では、投稿される情報の単位を「コト」と呼んでいます。その「コト」をグルーピングするものが「コトノマ」です。ファイルシステムのフォルダやディレクトリ、チャットのルーム(チャット機能はまだありませんが)などに相当します。

cotonoma

画面左上の(+)ボタンをクリック(タップ)して、コトノマを作ると、コトと同じようにタイムライン上に投稿されます。投稿されたリンクをクリックすると、そのコトノマに移動します。コトノマの中にまた別のコトノマを作ることも出来ます。

コトと同じように、コトノマも扱うことが出来る。ここが重要な部分です。現状の機能だとまだ有り難みが少ないですが、将来的にはアイデアを生み出す際の力となるはずです。

コトノマからホーム画面に戻ってくると、他のコトノマに投稿したコトやコトノマも含めて、自分が投稿したもの全てが表示されるようになっています。

さて、次の大きなヤマはチャット機能。今月中に実現できるよう頑張りたいと思います。お楽しみに〜。

Cotoami成長記録 (4) – コトノマの共有


Cotoami は誰でもダウンロードして試せるオープンソースプロジェクトですが、 https://cotoa.me/ で最新バージョンの運用もしています。興味のある方は是非是非お試し下さい。

細かな更新情報は Twitter でつぶやいています: https://twitter.com/cotoami

ソースコードはこちら: https://github.com/cotoami/cotoami

Elm沼とKubernetes沼で交互に溺れているうちに1月が終わっていた

ElmCotoami のユーザーインタフェースを作るために選択したプログラミング言語で、そのデザインは JavaScript 界で一世を風靡している Redux の原型になったと言われている。その原型を体験してみたいという単純な好奇心だけで Elm を選択し、分かりやすいチュートリアルに感心したのも束の間、実際にアプリケーションを作り始めると、入り口ではあんなに優しかった Elm の顔がみるみる般若のようになり、気がついたら底なしの泥沼に足を取られていた。

Elm のような、いわゆる純粋な関数型と呼ばれる環境では、言語からコンピューターを直接操作することが出来ない。出来るのはデータの変換だけだ。この変換がいわゆる「関数」で、関数型でプログラマーに許されているのは関数を書くことだけである。しかし、コンピューターを操作出来ないと何も出来ないのでどうするのかと言うと、コンピューターへの命令をデータとして表現して、それを返す関数を作って環境に渡すみたいなことをする。

関数型にはこのような制限があるので、従来の命令型言語でプログラムを書く場合に比べて、かなり回りくどい書き方をしなければならない。しかしその代償として、プログラムは必ず「データの変換」という形に落とし込まれるので、実行時に意図しない動作をすることが格段に少なくなり、デバッグやテストも容易になるというわけだ。

関数型の回りくどさに加えて、Elm には厳密な型システムがある。実は今、開発スピードを減退させている最大の原因はこの型システムである。代数的データ型とか、今まで遭遇したことのないコンセプトに純粋な感動を覚える一方、曖昧さを許さない型システムのおかげで、型を合わせるためにどうすれば良いかというのを考えてるだけで膨大な時間が過ぎて行く。

動的型が、書く時にいかに楽をするか(少ない記述でいかに多くを実現するか)を主眼に置いているとすれば、静的型は、書いた後にいかに楽をするか(起こりうることを明示的に示しておく)ことに主眼を置く。しかし、動的型でも、曖昧に書いて楽をしつつも、危ないと思うところは慎重に書いたり、テストでフォローしたりするわけなので、単純に上のような図式が当てはまるとも思えず、どちらが良いと判断するのはなかなか難しい。動的型の言語であれば、場所によって手綱を締めたり緩めたり出来るのが、静的型だと一律で同じような書き方をしなければならない、というのがなかなか辛いところである。

まだ言語自体に慣れてないということもあるし、これから開発が進んで、規模が大きくなり、複数人で連携するようになったら関数型や型システムの恩恵を実感出来るのかもしれないなと思いつつ、最初の敷居はやっぱり高かったということだけここに記録しておく。

一方の、Kubernetes 沼の方はと言えば、こちらは Cotoami で試行錯誤した成果を仕事の方にフィードバックしたいということで、プロダクションへの導入を目指して色々と準備しているため、Elm よりもより深い深い泥沼となっている。Elm の場合はとりあえずユーザーインタフェースが動けばなんとかなるのに対して、インフラの場合はその辺のごまかしが効かないので、あらゆる側面から検証を行わなければならず、人材の不足も手伝ってかなり余裕のない状況に陥ってる今日この頃(もし、同じように Kubernetes で試行錯誤している方がいたら、情報交換したいなあと思うのですが… @marubinotto までご連絡お待ちしております(切実))。

Kubernetes は未来のインフラだとの意を強くする一方(やっと10年ぐらい前のGoogleに追いついただけとも言える)、アジャイルでない組織に導入してもメリットはあまりないだろうなとも思う。Kubernetes は単なるインフラというよりも、サービスがどのように開発・運用されるべきかという思想が強烈に埋め込まれた環境だと言った方が良いかもしれない。

Kubernetes によって、いわゆるデリバリーサイクルは極限まで短縮され、システムの柔軟性もかつてないレベルで実現出来るようになる。しかしその一方、運用やモニタリングにかかるコストは従来より高いように感じられるし、Kubernetes 自体も高速で進化していくので、そこにキャッチアップ出来る人材も確保する必要がある。例えば、運用において、MTTR (Mean-Time-To-Recovery) よりも、MTBF (Mean-Time-Between-Failures) の方を重視するような組織だと、なかなかこの機動性をメリットだと感じるのは難しいだろう。

既に開発が落ち着いていて安定運用されているサービスを Kubernetes に移行するメリットは、おそらくほとんどない。基本的には開発の初期から導入するのが望ましい。Cotoami はリスクのないオープンソース開発なので、その辺の事情で迷う必要がなく、一番始めのハリボテアプリの段階から Kubernetes で本番 ( https://cotoa.me/ ) の運用を始めて、自動デプロイなどの仕組みも整備出来た。

去年まで、ウチのチームでは、AMI (Amazon Machine Image) のように VM 上で動くホストがパッケージングの単位になっていた。パッケージを作るのも、パッケージをデプロイするのもうんざりするほど時間がかかる。それが Kubernetes/Docker によって、ホストの中で動く一つ一つのプロセスがパッケージングの単位となり、それらの小さな部品を組み合わせてホスト(Kubernetes では Pod と呼ばれている)を作り、それらをまた組み合わせて大きなWebサービスを作るという形に変わり、一度の更新も最小限で高速、しかも無停止ということで、新しい時代の到来を感じずにはいられない。

Cotoami成長記録 (2) – サインイン

ひたすら匿名で投稿するだけだったハリボテメモ帳に、サインイン機能を追加しました。

認証はメールアドレスのみで行うシンプルなもので、ユーザー登録を行う必要もありません。

signin

メールアドレスを送信すると、以下のようなサインイン用のメールが送られて来ます。

signin-mail

メールに書かれたURLを開くとサインイン完了です。

signed-in

アカウントのアイコンやユーザー名は Gravatar のものを利用しています。

サインインせずに匿名(Anonymous)のままでも書き込みを行うことが出来ますが、サインインの際に「Save the anonymous cotos (posts) into your account」のチェックボックスを ON にしておくと、匿名で投稿した内容を自分のアカウントに移すことが出来ます。

まだまだハリボテの段階を脱していませんが、これで「PCで書いたメモをスマホで見る」といったような当たり前の使い方が出来るようになりました。

Cotoami https://cotoa.me/

ソースコードはこちら: https://github.com/cotoami/cotoami/tree/signin

Cotoami成長記録 (3) – コトとコトノマ


(舞台裏)ローカル Cotoami を一瞬で作る魔法のコマンド

現段階でのシステム構成は以下のような感じになっています。

cotoami-2

アカウントごとの投稿内容(Cotoamiでは「コト(Coto)」と呼んでます)を保存するためのデータベース(PostgreSQL)や、サインインメールを送信するためのメールサーバーなどが新しい登場人物です。

既にそこそこ複雑なシステムになってきていますが、minikube というサーバーの箱庭環境があれば、驚くほど簡単にローカル Cotoami システムを構築することができます。

minikube のインストールについては以下の記事を参考にしてみて下さい。

kubectl cluster-info で準備が出来ていることを完了したら、以下のコマンドを実行するだけでシステムが出来上がります。

# 魔法のコマンド
$ kubectl create -f https://raw.githubusercontent.com/cotoami/cotoami-infra/signin/kubernetes/all-in-one.yaml

kubectl get pods コマンドで全てのサーバーが「Running」になったら準備完了。

$ kubectl get pods
NAME                           READY     STATUS    RESTARTS   AGE
cotoami-3593364574-ji2wy       1/1       Running   1          15m
maildev-2318592657-ag3ib       1/1       Running   0          15m
postgres-4226949952-8dk6q      1/1       Running   0          15m
redis-master-517881005-pqvud   1/1       Running   0          15m

WebアプリのURLを知るためには、以下のコマンドを実行します。

$ minikube service cotoami --url
http://192.168.99.101:31923

出力されたURLをブラウザで開くと、Cotoami の画面が表示されるはずです。

サインインメールは maildev というダミーのメールサーバーが受け取るようになっています。以下のコマンドで maildev の URL を取得して、

$ minikube service maildev --url
http://192.168.99.101:32261
http://192.168.99.101:30914

出力された二つの URL の内、上の URL をブラウザで開くと、

maildev

こんな感じで送信されたサインインメールを見ることができます。サインイン URL の先頭が http://cotoami となっているのを、minikube service cotoami --url で出力された URL に置き換えてブラウザで開くと、サインイン完了です。

Cotoami成長記録 (1) – ハリボテメモ帳的な何か

今月から始まった Cotoami プロジェクト。開発途上のバージョンからどんどん公開して、開発が進むに連れて変わって行く様を、逐一こちらに記録して行きたいと思います。

まずは第一歩ということで、ハリボテメモ帳的な何かから出発。

cotoami-1

Cotoami http://cotoa.me/

上のサイトにアクセスするとすぐに試せるので適当に何か書き込んでみて下さい(書き込んだ内容は本人にしか見えません)。同じブラウザでアクセスする限り、書き込んだ情報は維持されます。ハリボテなので、データが突然消えることがあるかもしれませんが…

このハリボテアプリのシステム構成は以下の通り、

cotoami-1

ハリボテとは言え、データは Redis に保存したりしてます。

この段階のコードは以下から見ることができます。

Cotoami成長記録 (2) – サインイン

Kubernetes で実現する Phoenix/Elm アプリのホットデプロイ自動化完全詳解(2016年12月版)

今年の初頭に「Phoenixアプリのホットデプロイ完全自動化」の記事を書いてから一年が過ぎようとしている。この自動化は Elixir/Erlang の Hot swapping 機能を利用していて、git push から10分以内でデプロイが完了するという、当時としてはそこそこ満足のいく達成だったのだが、こんな不具合や、exrm (Elixir Release Manager) 作者の「hot upgrades はあんまりオススメ出来ない発言」などを見るにつけ、これを本番で使うのはちょっと辛いかもしれないと思うようになった。

今回、Cotoami プロジェクト を始めるに当たって、前々から気になっていた Google の Kubernetes(クバネテス)を試してみようと思い立った。そして実際に自動化の仕組みを構築してみて、その簡単さと仕組みの先進さに驚いた。言語に依存しないマイクロサービスのパッケージングと、それらを組み合わせて簡単にスケーラブルなWebサービスを構築できる仮想環境。これで本格的にコンテナの時代が来るんだなという新しい時代の訪れを感じずにはいられない。

というわけで、以下では Kubernetes を使った自動化の詳細について紹介したいと思う。この仕組みの全貌は Cotoami プロジェクトの一部として公開しているので、興味のある方は以下の GitHub プロジェクトを覗いて頂ければと思う。



 

Kubernetes とは何か?

Kubernetes が提供する仕組みは Container Orchestration と呼ばれている。Container Orchestration とは、Docker のようなコンテナ(アプリケーションを実行環境ごとパッケージングする仕組み)で実現されている小さなサービス(マイクロサービス)を組み合わせて、より大きなサービスを作るための仕組みである。

今では、Webサービスを複数のサービス(プロセス)の連携として実現することが当たり前になって来ている。次第に細かくなりつつあるこれらのサービスを扱う時の最大の障害が従来型の重い仮想化だ。例えば、Amazon Machine Images (AMI) のような従来型の仮想化技術を使ってサービスを更新する場合、イメージをビルドするのに20分から30分程度、更にそれを環境にデプロイするのに10分以上かかってしまう。自動化も容易ではない。サービスの数が多くなるほどに時間的なペナルティが積み重なってしまい、マイクロサービスのメリットを享受するのは難しくなる。なので、実際はマシンイメージをデプロイの単位にすることはせずに、言語やフレームワーク固有のパッケージに頼ったデプロイを行っている現場が多いのではないだろうか。

これらの問題を一挙に解決しようとするのが、Docker のような軽い仮想化と、それらをまるでソフトウェアモジュールのように組み合わせることを可能にする Container Orchestration 技術である。

 

Kubernetes を最短で試す

複数サービスの連携を、ローカルマシンで簡単に試せるというのも Kubernetes のようなツールの魅力だ。Kubernetes には Minikube というスグレモノのツールが用意されていて、ローカルマシン上に、お手軽に Kubernetes 環境を立ち上げることが出来る。

以下では、Mac OS X での手順を紹介する。

1. VirtualBox をインストールする

筆者の環境:

$ vboxmanage --version
5.1.8r111374

2. Minikube をインストールする

$ curl -Lo minikube https://storage.googleapis.com/minikube/releases/v0.12.2/minikube-darwin-amd64 && chmod +x minikube && sudo mv minikube /usr/local/bin/

$ minikube version
minikube version: v0.12.2

3. Kubernetes を操作するためのコマンドツール kubectl をインストールする

$ curl -Lo kubectl https://storage.googleapis.com/kubernetes-release/release/v1.3.0/bin/darwin/amd64/kubectl && chmod +x kubectl && sudo mv kubectl /usr/local/bin/

4. Minikube を起動する

$ minikube start
Starting local Kubernetes cluster...
Kubectl is now configured to use the cluster.

以下のような情報を見れれば、準備は完了。

$ kubectl cluster-info
Kubernetes master is running at https://192.168.99.101:8443
KubeDNS is running at https://192.168.99.101:8443/api/v1/proxy/namespaces/kube-system/services/kube-dns
kubernetes-dashboard is running at https://192.168.99.101:8443/api/v1/proxy/namespaces/kube-system/services/kubernetes-dashboard

$ kubectl get nodes
NAME       STATUS    AGE
minikube   Ready     5d

5. サンプルプロジェクトをデプロイしてみる

Kubernetes には色んなサンプルプロジェクトが用意されているが、ここでは Guestbook という簡単なアプリを試してみる。

以下のファイル(guestbook-all-in-one.yaml)を適当な場所に保存して、

https://github.com/kubernetes/kubernetes/blob/master/examples/guestbook/all-in-one/guestbook-all-in-one.yaml

以下のコマンドを実行してデプロイする。

$ kubectl create -f guestbook-all-in-one.yaml 
service "redis-master" created
deployment "redis-master" created
service "redis-slave" created
deployment "redis-slave" created
service "frontend" created
deployment "frontend" created

これによって、以下の3つの Deployments と、

$ kubectl get deployments
NAME           DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
frontend       3         3         3            3           5m
redis-master   1         1         1            1           5m
redis-slave    2         2         2            2           5m

それぞれの Deployments に対応する3つの Serviceskubernetesはシステムのサービスなので除く)が出来上がっていることが分かる。

$ kubectl get services
NAME           CLUSTER-IP   EXTERNAL-IP   PORT(S)    AGE
frontend       10.0.0.118   <none>        80/TCP     7m
kubernetes     10.0.0.1     <none>        443/TCP    6d
redis-master   10.0.0.215   <none>        6379/TCP   7m
redis-slave    10.0.0.202   <none>        6379/TCP   7m

簡単に説明すると、Deployment は一つのマイクロサービスのクラスタに対応し、Service はそのクラスタへのアクセス手段を提供する。

たったこれだけの手順で、冗長化された Redis をバックエンドにした、アプリケーションの環境が出来上がってしまった。構成の全ては guestbook-all-in-one.yaml というテキストファイルに定義されている。

早速ブラウザでアクセスして試してみたいところだが、デフォルトの設定だとサービスが Kubernetes の外部には公開されていないので、frontendサービスの設定をちょっと書き換えて(guestbook-all-in-one.yaml に以下のような感じで type: NodePort の行を追加する)、

apiVersion: v1
kind: Service
metadata:
  name: frontend
  labels:
    app: guestbook
    tier: frontend
spec:
  ports:
  - port: 80
  type: NodePort
  selector:
    app: guestbook
    tier: frontend

以下のコマンドを実行して設定ファイルの更新を環境に適用する。

$ kubectl apply -f guestbook-all-in-one.yaml 

更新が完了したら、以下のコマンドでアプリケーションのURLを知ることが出来る。

$ minikube service frontend --url
http://192.168.99.101:31749

以下のようなページが表示されただろうか?

guestbook

6. お片づけ

先ほどのサンプルプロジェクトで作ったリソースは、以下のコマンドで全部削除出来る。

$ kubectl delete -f guestbook-all-in-one.yaml

Minikube の停止は以下。

$ minikube stop

 

Phoenix/Elm アプリの Docker イメージを作る

さて、Cotoami の話に戻ろう。Cotoami では、以下のような構成で自動化を実現しようとしている。

cotoami-auto-deploy

CircleCI 上のビルドで Docker イメージをビルドして Docker Hub にリリース。その後、AWS上に構築した Kubernetes に更新の命令を出して、新しいイメージでアプリケーションの Rolling Update(無停止デプロイ)を行う。

この仕組みを構築するためには、まず Phoenix/Elm アプリケーションを Docker でパッケージングするための Dockerfile を用意する必要がある。しかし、ここで気をつけなければならないのは、パッケージングそのものよりも、CircleCI 上でどうやって Phoenix/Elm アプリケーションをビルドするかという問題である。

Elixirアプリケーションは、クロスコンパイル・ビルドが出来るという説明もあるが、実行環境とビルド環境は合わせておいた方が良いというアドバイスもよく見かけるので、Cotoami ではよりトラブルが少なそうな、環境を合わせるアプローチを取ることにした。

今回の例では、実行環境も Docker 上になるので、まずビルド用の Docker イメージを用意しておき、それを使ってアプリケーションのコンパイルとテストを行い、その後、そのイメージをベースにしてアプリケーションをパッケージングするという、docker build の二段構え方式でビルドを実施する。

まずは、以下の Dockerfile で Phoenix/Elm アプリのビルド環境を作る。

一度作ったイメージは、CircleCI のキャッシュディレクトリに入れておき、後々のビルドで使い回せるようにしておく。この辺の設定は全て circle.yml に書く。

アプリケーションのコンパイルとテストが終わったら、ビルド用のイメージをベースにして、アプリケーションのパッケージングを行う。そのための Dockerfile が以下である。

これらの組み合わせで、git push する度に、Docker Hub にアプリケーションのイメージがリリースされるようになる(Docker Hub に docker push するために、CircleCI に 認証用の環境変数を設定しておくこと: DOCKER_EMAIL, DOCKER_USER, DOCKER_PASS)。

参考: Continuous Integration and Delivery with Docker – CircleCI

 

AWS上に Kubernetes 環境を作る

アプリケーションの Docker イメージが用意出来たら、それを動かすための Kubernetes 環境を作る。今回は AWS 上に Kubernetes 環境を構築することにした。

Kubernetes から kops という、これまた便利なツールが提供されていて、これを使うと簡単に環境を構築出来る。

1. kops のインストール

Mac OS の場合:

$ wget https://github.com/kubernetes/kops/releases/download/v1.4.1/kops-darwin-amd64
$ chmod +x kops-darwin-amd64
$ mv kops-darwin-amd64 /usr/local/bin/kops

2. Kubernetes 用のドメイン名を用意する

ここが比較的厄介なステップなのだが、kops による Kubernetes 環境はドメイン名を名前空間として利用する仕組みになっている。具体的には、Route 53 内に Kubernetes 環境用の Hosted zone を作る必要がある。

例えば、立ち上げようとしているWebサービスのドメインが example.com だとすれば、k8s.example.com のような専用の Hosted zone を用意する(k8s は Kubernetes の略称)。

Cotoami の場合、AWS のリソースは出来るだけ Terraform を利用して管理することにしているので、Terraform で Hosted zone を設定する際の例を以下に置いておく。

resource "aws_route53_zone" "main" {
  name = "example.com"
}

resource "aws_route53_zone" "k8s" {
  name = "k8s.example.com"
}

resource "aws_route53_record" "main_k8s_ns" {
  zone_id = "${aws_route53_zone.main.zone_id}"
  name = "k8s.example.com"
  type = "NS"
  ttl = "30"
  records = [
    "${aws_route53_zone.k8s.name_servers.0}",
    "${aws_route53_zone.k8s.name_servers.1}",
    "${aws_route53_zone.k8s.name_servers.2}",
    "${aws_route53_zone.k8s.name_servers.3}"
  ]
}

主ドメインとなる example.com の Hosted zone について、サブドメイン k8s の問い合わせを委譲するような NS レコードを登録しておくのが味噌。

以下のコマンドを叩いて、DNSの設定がうまく行っているかを確認する。

$ dig NS k8s.example.com

上で設定した4つの NS レコードが見えれば OK。

3. kops の設定を保存するための S3 bucket を作る

kops は、Amazon S3 上に保存された構成情報に基づいて環境の構築・更新などを行う。というわけで、予めそのための S3 bucket を作っておき、その場所を環境変数 KOPS_STATE_STORE に設定する。

$ aws s3 mb s3://kops-state.example.com
$ export KOPS_STATE_STORE=s3://kops-state.example.com

これで、準備は完了。いよいよ Kubernetes の環境を立ち上げる。

4. Kubernetes の設定を生成する

新しい環境の名前を staging.k8s.example.com として、以下のコマンドで新規環境の設定を生成する。生成された設定は先ほどの S3 bucket に保存される。

$ kops create cluster --ssh-public-key=/path/to/your-ssh-key.pub --zones=ap-northeast-1a,ap-northeast-1c staging.k8s.example.com

Kubernetes ノードにログインするための ssh キーや、ノードを展開する Availability Zone などを指定する。細かいオプションについては、以下を参照のこと。

デフォルトでは、以下のような構成の環境が立ち上がるようになっている。

  • master (m3.medium)
  • node (t2.medium * 2)

5. Kubernetes 環境を立ち上げる

Kubernetes 環境を AWS 上に立ち上げる。単純に以下のコマンドを実行すれば良いのだが、

$ kops update cluster staging.k8s.example.com --yes

Terraform の設定ファイルを生成するオプションもあるので、Cotoami ではその方法を取ることにした。

$ kops update cluster staging.k8s.example.com --target=terraform

$ cd out/terraform
$ terraform plan
$ terraform apply

生成されたデフォルトの構成から、セキュリティグループなどをより安全な設定にカスタマイズすることもあると思われるが、これらのファイルは自動生成によって更新される可能性があることに注意する必要がある。ファイルを直接編集すると、新しく生成したファイルに同じ変更を施すのを忘れてしまう可能性が高い。なので、AWS のコンソール上で直接カスタマイズした方が良いかもしれない(新しい設定ファイルとの齟齬は terraform plan の時に気づける)。

どのようなファイルが生成されるか興味のある方は、Cotoami のリポジトリを覗いてみて欲しい。

環境を立ち上げる過程で、kop によって kubectl の設定も自動的に追加されている。以下のコマンドを実行すれば、AWS上の環境に接続していることが確認できるはずだ。

$ kubectl cluster-info
Kubernetes master is running at https://api.staging.k8s.example.com
KubeDNS is running at https://api.staging.k8s.example.com/api/v1/proxy/namespaces/kube-system/services/kube-dns

 

Kubernetes 上にアプリケーションをデプロイする

Kubernetes の準備は整ったので、後はアプリケーションをデプロイするだけである。Minikube のところでサンプルアプリをデプロイしたのと同じように、サービスの構成情報を YAML ファイルに定義しておき、kubectl create コマンドでデプロイを行う。

Cotoami の構成ファイルは以下に置いてある。

$ kubectl create -f deployment.yaml
$ kubectl create -f service.yaml

設定ファイルの仕様については Kubernetes のサイトを参照して頂くとして、内容自体は単純だということはお分かり頂けると思う。deployment.yaml では、アプリケーションの Docker イメージ名やクラスタを構成するレプリカの数、ポート番号などが指定されている。service.yaml では、そのサービスを外部にどのように公開するかという設定がされており、面白いのは type: LoadBalancer と書いておくと、AWS の ELB が自動的に作成されてアプリケーションのエンドポイントになるところだろうか。

 

デプロイ自動化をビルド設定に組み込む

最初のデプロイが無事に成功すれば、無停止更新の仕組みは Kubernetes 上に用意されている。後はそれを利用するだけである。

CircleCI から Kubernetes にアクセスするためには、以下のような準備が必要になる。

  1. kubectl のインストール
  2. kubectl の設定
    • ensure-kubectl.sh では、環境変数 S3_KUBE_CONF に設定された Amazon S3 のパスから kubectl の設定ファイルをビルド環境にコピーする。
    • Kubernetes on AWS を構築する過程でローカルに出来上がった設定ファイル ~/.kube/config を S3 にコピーして、その場所を CircleCI の環境変数 S3_KUBE_CONF に設定する。
      • この設定ファイルには、Kubernetes にアクセスするための credential など、重要な情報が含まれているので、取り扱いには注意すること!
    • CircleCI 側から S3 にアクセスするためのユーザーを IAM で作成して最低限の権限を与え、その credential を CircleCI の AWS Permissions に設定する。

これらの設定が完了すれば、ビルド中に kubectl コマンドを呼び出せるようになる。Cotoami の場合は、circle.ymldeployment セクションに、以下の二行を追加するだけで自動デプロイが行われるようになった。

https://github.com/cotoami/cotoami/blob/auto-deployment/circle.yml

- ~/.kube/kubectl config use-context tokyo.k8s.cotoa.me
- ~/.kube/kubectl set image deployment/cotoami cotoami=cotoami/cotoami:$CIRCLE_SHA1

長くなってしまったが、以上が自動化の全貌である。

Elixir/Phoenix と Elm による関数型 Web 開発環境の構築

前回は、Cotoami のアーキテクチャについて、コレオグラフィ型を採用するという話を書いた。しかし、開発の最初からコレオグラフィを前提にした構成にするのはスモールスタートとは言い難いので、まずは核となるWebアプリケーションを作るところから初めて、徐々にイベント駆動の箇所を増やしてく感じで進めたい。

このWebアプリケーションを実装する環境として選んだのが、Phoenix FrameworkElm である。両方とも関数型の言語なので、Webアプリケーション全体を関数型の枠組みで実装することになる。

Elixirの強みについてはゆびてくで何度か触れているのでここでは割愛するが、Elm を選択したのは何故だろうか?

大きな要因としては、Elmアプリのアーキテクチャを参考にデザインされたという JavaScript のライブラリ Redux での開発経験が挙げられる。その過程で、複雑化するフロントエンドを実装する技術として、全てのビジネスロジックを「変換の連鎖」へと落とし込む関数型の有効性を実感した(参考: 関数型つまみ食い: 関数型とはプログラミング言語ではなく、プログラムデザインの問題であることに気づく | ゆびてく)。Elm の場合は、Redux では冗長になりがちだったこの仕組みを簡潔に表現出来る上に、Static Typing があるというのも大きなアドバンテージだと考えた。

エディタ上で即座にフィードバックを受けることが出来る
プログラムの誤りについて、エディタ上で即座にフィードバックを受けることが出来る

Phoenix と Elm の相性については、最近 Elm 側で Phoenix のサポートが入ったというのが明るい材料ではあるが… こればかりは試してみないと分からない。


[2016/12/09追記]

素晴らしいツッコミを頂く。


 
以下に Phoenix/Elmアプリケーションのひな形を作るまでの手順をまとめてみた。

 

関連ツールのインストール

Node.js

以下を参考に nvm をインストールする。

creationix/nvm: Node Version Manager – Simple bash script to manage multiple active node.js versions

  • Phoenixのサイトに「Phoenix requires version 5.0.0 or greater.」とある。

筆者の環境:

$ node -v
v5.4.1

 

Elixir

Installing Elixir - Elixir

Mac OS X で Homebrew を利用している場合。

$ brew update
$ brew install elixir

筆者の環境:

$ elixir -v
Erlang/OTP 19 [erts-8.0.2]  [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]

Elixir 1.3.4

 

PostgreSQL

標準構成の Phoenix が利用するデータベース。環境によってパッケージも様々なのでインストール方法については割愛。データベースを使わないのであれば省略可。

以下のコマンドでデータベース一覧が取得出来ればデータベースのスタンバイは出来ている。

$ psql -l

筆者の環境:

# SELECT version();
                                                              version                                                              
-----------------------------------------------------------------------------------------------------------------------------------
 PostgreSQL 9.4.0 on x86_64-apple-darwin13.4.0, compiled by Apple LLVM version 6.0 (clang-600.0.56) (based on LLVM 3.5svn), 64-bit
(1 row)

 

Phoenix

Installation · Phoenix

$ mix archive.install https://github.com/phoenixframework/archives/raw/master/phoenix_new.ez

筆者の環境:

$ mix phoenix.new -v
Phoenix v1.2.1

 

Elm

インストーラが用意されているので簡単。

Install · An Introduction to Elm

筆者の環境:

$ elm -v
0.18.0

 

Phoenix/Elm アプリケーションを作る

Phoenixアプリのひな形を作る

$ mix phoenix.new cotoami

依存関係の取得とデータベースの作成。

$ cd cotoami
$ mix deps.get
$ mix ecto.create   # PostgreSQLを使わなければ省略可
$ npm install

アプリを起動してブラウザでチェックしてみる。

$ mix phoenix.server

http://localhost:4000 にアクセスすると「Welcome to Phoenix!」のページが表示される。

 

elm-brunch をセットアップする

Phoenix に標準で付いてくる Brunch というJavaScriptのビルドツールがあるのだが、elm-brunch という Elm をビルドするための拡張があるのでそれをインストールする。

$ npm install --save-dev elm-brunch

brunch-config.js に elm-brunch の設定を追加。以下の二カ所を修正。

  1)
    ...
    watched: [
      "web/static",
      "test/static",
      "web/elm"
    ],
    ...

  2)
  ...
  plugins: {
    elmBrunch: {
      elmFolder: "web/elm",
      mainModules: ["App.elm"],
      outputFolder: "../static/vendor"
    },
    babel: {
      // Do not use ES6 compiler in vendor code
      ignore: [/web\/static\/vendor/]
    }
  },
  ...

 

Elmアプリのひな形を作る

$ mkdir web/elm && touch web/elm/App.elm
$ cd web/elm
$ elm package install elm-lang/html

App.elm の内容を以下のように編集。

module App exposing (..)

import Html exposing (Html, text)

main : Html msg
main =
  text "Hello Cotoami!"

 

ElmアプリをPhoenixアプリに配置する

Phoenixアプリのファイルをそれぞれ以下のように編集。

web/templates/layout/app.html.eex


<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="">
<title>Hello Cotoami!</title>
<link rel="stylesheet" href="<%= static_path(@conn, "/css/app.css") %>">
</head>
<body>
<div class="container">
<main role="main">
<%= render @view_module, @view_template, assigns %>
</main>
</div>
<script src="<%= static_path(@conn, "/js/app.js") %>"></script>
</body>
</html>

view raw

app.html.eex

hosted with ❤ by GitHub

web/templates/page/index.html.eex

<div id="elm-container"></div>

web/static/js/app.js に以下の二行を追記:

const elmDiv = document.querySelector("#elm-container")
const elmApp = Elm.App.embed(elmDiv)

これで準備は完了。ブラウザをリロードすると「Hello Cotoami!」と表示される。さらには、App.elm の内容を編集して保存すると、ブラウザが自動的にリロードされて即座に変更を確認出来るようになっているはずだ。

参考: Setting up Elm with Phoenix – Medium

コレオグラフィ: 開発者が参加・離脱しやすいアーキテクチャを考える

Cotoami のアーキテクチャを、どのような体制、あるいは場を作って開発したいのかという観点から考えてみたい。いわゆるコンウェイの法則に沿ったシステムデザインである。

現状のシステム、つまり Oinker.me のアーキテクチャは以下のようになっている。

oinker

Grailsフレームワークを利用して実装されたアプリケーションサーバーが中心となり各種サービスの連携を計る、いわゆるオーケストレーション (orchestration) モデルのアーキテクチャである。

マイクロサービス本でも指摘されているように、オーケストレーションは素直なアーキテクチャではあるが、システムを拡張する度に指揮者となるサービスに手を入れる必要がある。例えば、現状 Oinker の検索エンジンは壊れているのだが、この修正を誰かに頼みたいと思ったら、まず指揮者となるサービス(Grailsアプリケーション)について理解してもらう必要がある(「え、今更Groovyを勉強しないといけないの?」)。これは指揮者が肥大化すればするほど骨の折れる作業になる。

多くの組織で開発言語を統一しようとするのは、おそらくこのオーケストレーションの仕組みに起因している。メンバーが同じ言語や環境に習熟していないと共同開発が難しくなるのである。

しかし、新しいものを生み出そうとする場合、様々なレベルにおいて多様性を損なう事は長期視点で考えるとマイナスになると筆者は考える。システムデザインにおいて「一貫性」は抗い難い誘惑だ。しかし、システムや組織が進化するためには、多少のいびつさを許容しなければならない。そのいびつさを許容する仕組みが、最近耳にする事が多くなって来た「コレオグラフィ (choreography)」モデルのアーキテクチャにあるのではないかと考えている。

ThoughtWorksのサイトに、オーケストレーションとコレオグラフィの違いを表す図が紹介されている。

オーケストレーション:

Dependency graph in a real world orchestrated microservice project
Dependency graph in a real world orchestrated microservice project

コレオグラフィ:

Dependency graph illustrating the concept of a fully choreographed set of microservices
Dependency graph illustrating the concept of a fully choreographed set of microservices

コレオグラフィアーキテクチャによって、システムを構成する各サービスの連携が疎結合に保たれ、かつ、非同期の連携なので全てのサービスが同じ負荷に耐えなくても良いというおまけも付いてくる。システムに新たな機能を追加する場合に参照するのは、指揮者となるサービスではなく、システムの拡張点となるイベントである。追加機能の起点となるイベントだけに着目すれば良いので、イベントを受け取れる限り、どのような言語・環境でサービスを開発しても良い。既存システムを変更せずに機能を追加出来るのはもちろん、いらなくなった機能を捨てたり、新しい言語で実装し直したり、ということも容易になる。

と、ここまではあくまでWebや書籍でかじった知識で書いている机上の空論に過ぎないので、この考えがどれぐらい有効なのかはまだ分からない。Cotoamiプロジェクト上で検証して行きたいと思う。

今のところの感覚では、イベント駆動という連携が、RESTのような従来型の連携に比べて複雑なのは明らかで、だからこそ、コレオグラフィは同じ言語で実装された統一的なプラットフォームの上で実現するという流れが強いように感じる。だけども、それではサービスの多様性を実現するための疎結合という理想からは離れてしまう。おそらくこの辺にコレオグラフィの辛みがあるのかもしれない。

あまり意識されないかもしれないが、言語やプラットフォームを統一するのは、それはそれである種の密結合である。今は目眩を覚えるほどの多様性が言語やフレームワークにある時代である。その多様性の中で独自性を得ようとするエンジニアの力を借りるために、この密結合はデメリットとして働くのではないかと今のところは考えている。

Cotoami: Oinker.me オープンソース化計画

この「ゆびてく」を運営する動機の大部分は、Oinker.me というサービスを出来るだけ多くの人に知ってもらいたい、というところから来ている。夏に簡単な紹介記事を一つ書いて、これから Oinker を使った知的生産について色んな側面から紹介してみようと意気込んでいたのだが、一方で現状のやり方ではイマイチ広がりがないなという感覚もあった。

個人的には Oinker.me というサービス自体をどうこうしたいと言うよりも、それ以前から育てて来たコンセプトを展開させるための場を如何に作るか、フィードバックを貰ったり、実験したりする場を如何に継続するかというのが大きな問題だった。広がりがないとプロジェクトも短命で終わってしまう。

そこでまず、Oinker.me をオープンソース化しようと思い立った。でも、単にそのままオープンソース化するだけでは面白くないので、全く新しい形にゼロから作り直そうと考えた。そして―――これが最大の事件なのだが―――全てを一人で回して独善ループに陥っているプロジェクトの救世主として、フリーランスプログラマの武藤大樹氏(@__tai2__)をゲスト開発者としてお迎えして新たなスタートを切る事になった。

tai2

Oinker.me では、知的生産の「予測の出来なさ」をそのまま力にするようなツールを作れないかという事で取り組んで来たのだが、それを更に進めるために、チームに色んな個性を呼び込んでプロジェクト自体の「予測の出来なさ」を増大させて行きたい。

新たなスタートを切るに当たってプロジェクトの名前も変える事にした。新しいプロジェクトの名前は「Cotoami (言編み・言網)」である。

「言を編んで、網(ネットワーク)を作る」ということで、名前がそのままコンセプトを表している。

元々は実験的な社内プロジェクトに、社外の個性を呼び込んで組織の枠を越えた新しい恊働のあり方を探る、Cotoamiプロジェクトにはそのような狙いも込められている。

現段階ではまだ「Hello world」的なプログラムに過ぎないが、これを徐々に発展させて行きたい。その過程はこの「ゆびてく」で詳細に報告して行く予定なので、是非是非ご注目頂ければと思う。

チーミング試行錯誤録 (3) – チケットシステムを知識ベースの中心に据える

前回は「情報共有の拠点を作る」ということで、GitHubというWebサービスを我々「クマ組」の拠点として選択したという話をしましたが、今回はそのGitHubをどのように使って行くのかということについて書いてみたいと思います。

GitHubのメニューを見ると、以下のような機能があることが分かります。

github-menu

この中で情報共有の中心的な役割を担うのが「Issues」です。

知識ベースとしてのチケットシステム

Issues のようなシステムは一般的に「BTS(Bug Tracking System/バグ管理システム)」と呼ばれたりします。ソフトウェア開発には欠かせないツールで、プログラムの不具合を報告したり、それらの対応状況を共有したりするために利用します。「Bug Tracking System」とあるように、最初はバグ管理のためのシステムでしたが、次第にプログラムの変更に関する事であれば何でも登録して管理するようになり、登録の単位はバグではなく「Ticket(チケット)」とか「Issue」などと呼ばれるようになりました。プログラムの変更をチケットの発行によって進めるという事で「チケット駆動開発」などと呼ばれたりすることもあります。

チケットは「アクションを要求するもの」として機能します。つまり、チケットシステムというのは簡単に言えば組織のためのタスク管理システムです。なので、プログラムの変更に関わることだけでなく、そのプロジェクトに関係するありとあらゆるタスクを管理しても別に問題は無いわけです。

クマ組では、このチケットシステムをタスク管理のためだけでなく、更に拡張して共有知識を生み出す場として使えないか実験してみることにしました。

具体的には、タスクだけでなく議論したい話題や気になっている事などもどんどん Issues に登録して行きます。これらのチケットは「完了」ステータスがないという意味でタスクではありません。でも、チケットとして(議論という)アクションを要求します。

このままだと「未完(Open)」チケットの中にタスク以外のものが紛れてしまいますが、これを解決するのが GitHub Issues に用意されている「Labels」という機能です。これはいわゆるタグ機能で、チケットにラベルを付けて分類する事が出来ます。クマ組では、現時点で以下のようなラベルを作ってチケットを分類しています。

  • task – 日々のタスク。
  • milestone – チームのミッションを達成するために必要なプロジェクト。タスクをグルーピングするもの。
  • topic – 議論したい話題。
  • book – チームメンバーが読むべき課題図書。
  • nayami – 困っているけどすぐに解決できなそうな事。

GitHub Issues を汎用的な知識ベースとして理由できる最大の理由はこのタグ機能の存在だと言っても過言ではありません。タグによって色々な情報を一カ所で管理することが出来るようになりますし、一つのチケットに複数のタグを付けられるのも重要です。例えば、上の例で言うと、milestoneタグの付いたチケットには同時にtaskタグも付けていずれの文脈でも見れるようにしたり、topicチケットの一部には課題図書を表すbookタグによって分類されているものがあったりします。

issues

知識管理のグラデーション – フローからストックまで

taskでも、topicでも、チケット上で議論を行うことによってチケットシステムに知識やノウハウが蓄積されて行きます。全文検索やタグなどを利用していつでも欲しい情報を探し出す事が出来るので、これだけでもかなり有用な知識ベースではありますが、仕事の経緯を記録するというチケットの性格上、多くのノイズを含んでいたりしてそのままだと読み辛かったりします。

というわけで、ある程度汎用的な(文脈にあまり依存しない)知識を獲得出来たと感じるチケットについては、それをマークダウンの文書に清書して Gitリポジトリの中に保存することにしました。リポジトリ内にある文書は、GitHubのトップページから辿れるようにしておきます。このように、理解するための文脈をそれほど必要とせず、かつ価値の高い情報(多くは技術的なノウハウになると思いますが)を容易に参照出来るようにしておけば、後からチームに合流した人や、外部の人たちの助けになります。

Gitリポジトリに保存された文書はいわゆるストックと呼ばれる情報です。チケットシステム上で知識を生み出し、より価値の高いもの(耐用年数の長いもの)をストックとして保存する。これがクマ組の学習プロセスの中心です。

ストックと言えば、もう片方にあるのがフローです。フローは短時間で流れて行く情報で、その目的は「アクションの喚起」にあります。今やどこの組織でも使われるようになったチャットがフローの基本だというのはクマ組も同じです。チャットによるフローと、Gitリポジトリに保存されたストック文書の間に、チケットシステムを配置する、というのがクマ組の試みです。

このように考えるとチケットシステムがフローとストックの間に入って両者を補完することが分かります。チャットはアクションを喚起するものですが、議論の記録は流れて行ってしまいストックにはならないのが基本的な性質です。検索して過去の議論を探す事は出来ますがノイズが多いので役に立つ状況は限られます。逆に言えば、ノイズが許容されるのがフローのメリットです。チケットシステムは「アクションを要求する」というフローの性質を備えつつも、より構造化された、比較的ストックに近い情報を蓄積することの出来る仕組みです。

flow-and-stock

チーミング試行錯誤録 (2) – 情報共有の拠点を作る

さてチーミングを始めようということで、新しいチームに集まった人間が自分も含めてたったの二人だと知ったとき、私は目の前が真っ暗になるのを堪えながら、心の中で何かがこみ上げてくるのを感じました。

前回紹介したように、多くの人が複数の仕事に従事するようになった結果、新しいチームに専念できる人間がその二人しかいなかったのです。

チームに緩やかな境界を設定する

しかし、無い袖は振れないという事では仕方がありません。段階的にチームビルディングしていくことを考え、チームに緩やかな境界を設定する事にしました。

まず、チームに専念出来る人を「構成員」、たまに作業に関わる人を「準構成員」としました。その他は、ミーティングに参加自由の「オブザーバー」というものを設定して、これらを緩やかなチームの境界とします。

  • 構成員(専任)
  • 準構成員(作業に関わる人)
  • オブザーバー(ミーティングに参加する人)

チームの名前を決める

チームの構成を決めたら、次はチームの名前を考えます。元々、手薄だったインフラ周りの仕事を担当するために結成されたチームだったので「インフラチーム」で良いんじゃないかという声もありましたが、チーミングの考え方からいって「機能」を名前にするのは出来るだけ避けたいと断固主張し、何か良い名前はないかと皆で思案した結果、ある構成員のアイコンが「リ○ックマ」だったという安直な理由で「クマ組」という名前に決まりました。

kuma

チーミングの本を購入する

そもそも私以外のメンバーは「チーミング」という言葉を聞くのも初めてです。まずは方向性を共有するために、教科書となる本を買って皆で少しずつ読んで行く事にしました。

チームが機能するとはどういうことか――「学習力」と「実行力」を高める実践アプローチ

この本の内容を簡単に説明すると、

「成功している組織やプロジェクトを調べるとどうも共通点があって、それは「チーミング」と名付けた方が良いのでは(と勝手に著者が思ってる)、という本」

という感じになります。沢山の事例が出てくるという事もあるのでしょうけど、内容が若干散漫なので、もうちょっとまとめてくれないかな? という気もしないではありません。というわけで、下のようなマインドマップを作って随時更新するようにします。

teaming

今回のプロジェクトで最初の課題だと考えているのは「学習の場」の部分です。前回説明したように、組織がピラミッド化していくにつれて、エンジニア間の横のつながりが薄くなってしまい、知識の交換が行われる機会がかなり少なくなっていました。こうなってしまうと、学習や成長は個人の責任になってしまい、組織で働く意味がなくなってしまいます。そこで横のつながりを復活させて再び知識の共有が行われるようにしよう、というのが我々の最初の目標になります。

情報共有の拠点を作る

というわけで、まずは情報共有の拠点を作るというのが、我々がチーミングを始めるに当たっての第一歩になりました。

情報共有の拠点としては、GitHubというサービスを選択してみました。ここに一つのリポジトリを作って情報共有の拠点とします。GitHubは本来プログラマーが協働するためのプラットフォームですが、この環境を利用してプログラマーが協働するのと同じような方法でチームの知識を開発して行く事にします。

(続く)

チーミング試行錯誤録 (1) – ピラミッド型組織にチーミングを根付かせる事は可能なのか?

というわけで始まりました。

限りなくゼロに近いスタート地点からチーミングの花を咲かせる事が出来るのかどうか、貴重な挑戦の機会を得られたので、ここに試行錯誤の記録を残して行きたいと思います。

「チーミング」とは、『チームが機能するとはどういうことか』という本で紹介されている新しい時代の協働のあり方で、今のように複雑で変化の速いマーケット(この本の中では「知識ベースの経済」と紹介されています)で生き残るために、今後ますます重要になっていくと考えられています。

チームが機能するとはどういうことか――「学習力」と「実行力」を高める実践アプローチ
チームが機能するとはどういうことか――「学習力」と「実行力」を高める実践アプローチ

この試みについて少し背景を説明しておきましょう。

ウチの会社は、自社開発のWebサービスを中心にした事業を展開しています。会社としての歴史はそこそこ長いものの、Webサービス企業としてはまだ新米であり、これから大きく展開して行こうと日々頑張っているところです。元々は規模の小さな会社であり、ベンチャー的なフラットな雰囲気のある会社でしたが、合併などによる社員の増加に伴い、部署ごとに様々な顔を持つ組織に変化しつつあります。

会社の規模が大きくなると必然的に起こる変化があります。ウチはまだ表題の「ピラミッド型組織」だと断言してしまえるほど固定的な組織になっているわけではありませんが、部署によってはそのような傾向が見えつつあるのは否定出来なくなってきています。この傾向はどのように現れるのでしょうか?

  1. 意思決定が上層部に集中するようになり「指示 => 実行」の構造が現れてくる。
    • チーミングの本では「実行するための組織づくり」として紹介されています。
  2. 効率を重視するあまり、小分けにしたタスクを少しでも余裕がある人に割り当てるようになる。
  3. 仕事が個別になった結果、横のつながりが弱くなり、チームが形骸化する。
  4. 一人の人間が同時並行で別々の仕事をするようになる。

このような変化は、従来型の「効率を重視した組織運営」の結果、必然的に起こるものです。というかむしろ、組織運営とは本来そのようなものであり、変化が今ほど激しくない時代の協働としては当たり前の形だったわけです。

これが時代の変化によって時代遅れになりつつあるというのがチーミング本の主張であり、「効率を重視した組織運営」に変わって新しい時代の協働のあり方として有望なのが「チーミング」だというわけです。

チーミング本では、チーミングにとってピラミッド型組織がいかに不利な環境なのかという事が繰り返し解説されています。マイクロマネジメントをやっている方は自身では案外気づけないものです。であれば、下位の人間が上位の人間に対して指摘すれば良いのでしょうが、それが出来るんだったらそもそもピラミッドになんかならないという話もあります。

ウチの場合は、部署によってはチームがうまく機能しているところもあり、あくまでも局所的な問題ではあるのですが、チーミングを立ち上げる場としてあえて難しい場所を選んで試行錯誤してみようというのが、このプロジェクトの趣旨です。カッチカチのピラミッドの中で挑戦するより遥かに障害は少ないかもしれませんが、「指示 => 実行」構造から脱却したいと考えている方々の参考になればと思い、うまく行った事・行かなかった事も含めて、その試行錯誤の記録を赤裸々に記録して行ければと思っています。

チーミング試行錯誤録 (2) – 情報共有の拠点を作る

組織やチームの話を追いかけると、プログラミングは基礎教養なんだって話に辿り着いてしまう件

まさにそんな感じの話がInfoQで紹介されていた。

自律的なチームを「管理」するという矛盾にどう立ち向かうか

シュレーディンガーの猫
シュレーディンガーの猫

組織とはそもそもピラミッド(階層構造)なのだということを、筆者の中で今流行りのハーバート・サイモン先生は主張して目出度くノーベル賞を獲得したのだが、時代は変わってチーミングの本では、そのピラミッド型組織が親の仇のように扱われていた。

21世紀になって、それまではどちらかと言えば個人の才能に頼っていたイノベーションという難題に、組織として取り組む企業が出現し始め、それに成功した企業が高い競争力を持つようになったため、チーミングやらボトムアップ組織のようなものが脚光を浴びるようになったというのがその背景だ。

しかし、組織とはそもそもピラミッドなのである。その中では、上位の人間の役目は下位の人間を「コントロール」して成果を出す事だと信じる人間が圧倒的なマジョリティを占めるだろう。

このような環境の中で、自律的に考えて動くチームを組織の中心に据えるというのは、相当な難題である事は想像に難くない。

この「自律的なチームを組織の中でどう育てるか?」という、一見矛盾するかのように思える問題を、量子力学の「シュレーディンガーの猫」に喩えて解説したのが、Instagram で技術責任者を務める James Everingham 氏だ。

観測という行為自体が観測対象に影響を与えてしまうという「観察者効果」のメタファーは、自律的なチームを管理するという矛盾(難題)をうまく表現していると思う。管理者は常に、自分が介入する事でチームの自律性(シュレーディンガーの猫)を殺してしまう可能性があることに注意を払う必要がある。

Everingham氏が提案する、量子力学的チームマネジメントの5つの法則はこちら。

  1. 成功のパターンを出来るだけ沢山想定すること。
  2. 観察者効果を常に意識すること。
  3. 箱を開ける(観測する)タイミングを心得ること。
  4. 自律性を壊さずにチームに良い影響を与える方法を心得ること。
    • 背中を見せる
    • 共感による動機付け
    • インセンティブ
    • 連帯感
  5. チームが正しい方向に向かっているか、外部からのフィードバックに常に気を配ること。

この新しいマネジメント手法のエッセンスは、当該記事の最後の言葉に集約出来るかもしれない。

By not suggesting a destination, we all ended up somewhere extraordinary — a place I didn’t even know we were going.

「あえて行き先を押し付けない事で、想像もしなかったような、すごいところに到達できる。」

適応 2.0: 職能分割型組織は何故生き残れないのか?

色んな役割の人たちが、力を合わせてより大きな意思決定を行う。

経済学から心理学、そして認知科学からコンピュータ科学まで、あらゆる分野の知見を総動員して組織とその経営について論じ、1978年にノーベル経済学賞を受賞したハーバート・サイモン氏は、組織の存在意義をそのように説明した。

古典的な経済学において人間は完璧な存在だった。あらゆる場面で常に最良の意思決定を行う。しかしサイモン氏は、現実の人間はそのような合理性を想定するにはあまりに非力過ぎるため、その非力さを前提に経済活動を分析した方が良いのではないかと考えた。

これが彼の代名詞的な発見とされている「限定合理性」と呼ばれる考え方だ。

マーケットが要求する難しくて大きな意思決定を、小さな意思決定に分割して組織の各部署に分担させる。分割された意思決定に必要となる知識は少なくて済むために、「非力な」個人でも比較的合理的に判断が行えるようになる。

人間はそのようにして、マーケットと呼ばれる環境に適応して行く。

組織は、すべての重要な意思決定が中央においてなされる、高度に集権化した構造のものではない。高度に集権化された方法で機能している組織は、再び、手続的合理性の枠をこえ、階層的な権限の使用から得られる多くの利点を失うことになる。現実世界の組織は、それとはまったく違った動きを示すのである。

1つの決定は、多くの事実前提と選択基準によって影響されるから、これらの決定前提のうちの一部が上司によって規定されるからといって、それが完全な集権化を意味するものではない。組織は、決定を分散させることによって、市場と同様、情報の需要を局所化し最少化することができる。実際の事柄は、組織内のそのための技能と情報がもっともよく集まったところでまず決められ、次にそれを「集合点」に伝達し、もって特定問題に関するすべての事実を統合する、そしてこれらを踏まえて1つの決定が下される。われわれは1つの決定を、それぞれが特定のタスクをもちかつローカルな情報源に依存しているサブルーチンを備えた、大規模なコンピュータ・プログラムを実行することによって造り出された生産物、と考えることができる。いかなる1個人あるいは1集団も、その決定にかかわるすべての面で専門家である必要は、まったくないのである。

このようにして企業組織は、市場と同様、巨大かつ分散化されたコンピュータなのであって、その意思決定過程は実質上分権化しているのである。

システムの科学

意思決定に関わる「情報の需要を局所化し最少化する」ための仕組みが、役割の分担であり、仕事の専門分化である。それが20世紀のマーケットを生き延びるための「適応」の技術だった。そして、職能分割型組織は、21世紀になった今でも主流を占めている(少なくとも日本では)。

職能という考え方は、役割、あるいは専門というものが安定しているという前提に立っている。

例として、ソフトウェア開発会社について考えてみよう。そこにはプログラマーや営業、マーケターといったような、役割ごとの部署があるはずだ。このように組織が職能で分割されている場合、例えば、プログラマーという一つの役割に期待されるものが、かなり限定されていることに気がつく。

プログラマーはプログラムを書くが、どのようなプログラムを書くのかは別の人が決定出来るはずだと、職能分割型組織は考える。設計をする人と、それをプログラムという形に落とし込む人。そのように分担すれば、人はそれぞれの役割に集中出来るはずであると。そして、プログラマーはプログラマーである限り(その部署に居続ける限り)、その役割が変化することはない。

しかし、多くの人が認識する通り、21世紀になってこの「固定化された役割」という前提はとっくに崩れている。

それまで当たり前だと思われていた役割の境界はいとも簡単に融解して、それまでには存在しなかった専門領域が次から次へと出現するようになった。

ソフトウェア開発における DevOps ムーブメントも、その流れを象徴的に示すものだ。DevOpsは、Dev と Ops という異なる役割の間にあった分断を乗り越えて手を携えなければ、変化の速い環境に「適応」出来ないという問題意識である。

あるいは、先端的な機能横断型組織において、役割がオーバーラップしてしまうという現象も起きている。

これらの現象は全て、今まで境界だと思っていたものが境界で無くなることが原因で起きている。専門分野が増えるというよりも、個人に要求される役割が変化しているのである。

このような状況にあって、多くの組織が職能分割の構造から脱却出来ないのは何故なのだろうか?

それは、人間の考え方や行動様式が、所属する組織の構造に支配されてしまうからだと筆者は考える。人間は本質的には不自由な存在だ。職能分割型組織に長く所属していれば、その構造がその人にとっては現実になる。役割の境界が変わって行くことに気づく事は難しくなるだろう。だから、現在の変化に対して役割を変化させるというよりも、役割を超えた連携をしようという発想にならざるを得ない。

この問題に対処するのに、今のところ最も有効なのはおそらく「チーミング」という考え方だろう。

チームが機能するとはどういうことか――「学習力」と「実行力」を高める実践アプローチ
チームが機能するとはどういうことか――「学習力」と「実行力」を高める実践アプローチ

この本で、チームは以下のような機能を提供するものとして紹介されている。

  • 目標を共有する場
  • 学習の場
  • 自由に発言できる場(心理的安全)
  • 境界を乗り越える場

Googleで行われたチームワークに関する調査、「Project Aristotle」で指摘されていたように、上の項目の中で「心理的安全」は、チーミングにとって最も重要な考え方になっている。

職能分割型組織、チーミングの本では「ピラミッド型組織」として紹介されているが、そういった組織が何故駄目なのかと言えば、構造的にも、あるいは心理的安全という見地からも、今の状況に適応するためにはあまりにも不自由過ぎるからだ。

従来的な「プログラマー」の部署で働いている彼は、ひょっとしたらプログラマーという役割に留まらず、それまでは想像もしなかったような役割で企業に貢献出来たかもしれない。組織の変革者となる可能性は誰にでもあるのに、職能分割型組織はその可能性をことごとく潰してしまう。

チームというのは各人の役割をダイナミックに変えて行く仕組みだ。

チームがその仕組みを遺憾なく発揮するためには、チームに与える裁量を出来るだけ大きくする必要がある。その裁量によってチームは試行錯誤し、各メンバーの役割は状況に応じて柔軟に変わり、仕事のやり方それ自体も改善され、より状況に合ったアウトプットを生み出せるようになる。このような環境において、「指示 => 実行」というモデルで管理していた従来型のマネジャーの役割は大きく変わる事になる。そのような役割の変遷を表したのが、John Cutler氏の以下の表だ。

The Evolving Product Manager Role
The Evolving Product Manager Role

果たして、ピラミッド型組織のマネジャーがこのような変遷を遂げる事が出来るのだろうか? おそらくこれがチーミングの中で最も難しいチャレンジになるのではないかと筆者は予想する。

『システムの科学』を読み解く (2) – 経済学ってそもそも何なのか問題

第2章は経済学についての話。

経済学に登場する、消費者や企業、市場といったものを、外部環境に適応する人工物と捉えて、その適応のメカニズム(前回の言葉で言えば「インターフェース」)を経済学的に考える。

ところで、筆者は経済学のことを何も知らない。いや、「何も」知らないというのは語弊があるかもしれない。この「ゆびてく」でも以前、「取引コスト」や「フランチャイズ」、あるいは「規模の経済」という話題に触れてきた。

入門的な本も過去に何冊かは読んだ事がある(内容を覚えているかどうかは別にして)。しかし、この第2章の内容を検討している間に分かった事は、自分がいかに経済学というものを理解していなかったのかという事実だった。なので今回の話は、大学などで経済学を勉強した人にとっては何を今更という話になるかもしれない。でも、筆者にとっては目から鱗の体験だったのである。

今回はその筆者の試行錯誤の記録を通して『システムの科学』の議論を紹介してみたいと思う。何故『システムの科学』が重要なのかと言えば、それが「分野横断的」だからである。そして分野を横断するときに、おそらくこのような試行錯誤を避けては通れない。

そもそも経済学とは何なのか?

『システムの科学』第2章では、「適応」を実現する最も重要な概念として「合理性」というものが登場する。経済学によれば、人間が合理的な生き物であるが故にマーケットの中で需要と供給の均衡が実現できるのだと言う。いわゆる「神の見えざる手」というやつだ。

supply_demand

このとき、古典的な経済学では合理性というものを以下のように考える。マーケットの中で合理的に行動するというのは、客観的に存在し得るあらゆる選択肢の内、その人にとっても最も得になるものを選択することである。これを『システムの科学』では「実質的合理性 (Substantive rationality)」と呼んでいる。「実質的」という言葉がどうもぴんと来ないので、ここでは代わりに「完璧な合理性」と呼ぶ事にしよう。

さて、経済学の最重要概念だったこの「完璧な合理性」に異議を申し立てたのが他でもないサイモン氏だ。冷静に考えると「客観的に存在し得るあらゆる選択肢」を想定する事も無理筋なのに、損か得かを判断するためには、それらの選択肢が将来的に及ぼす影響も完全に想定していないと「最も得になる」かどうかなんて分からないぞ、と。そもそも人間は、経済学が想定するような完璧な合理性を実現するには程遠い能力しか持ち合わせていないんだから、合理性は限定的に成らざるを得ない。つまり、「限定的な合理性 (Bounded rationality)」というものを想定しなければならないのではないかと。

このくだりに遭遇した時の筆者の反応は「え… ん? 当たり前だよね?」というものであった。しかも、調べてみれば、サイモン氏がノーベル経済学賞を受賞するきっかけとなったのは、この「限定的な合理性」の発見だと言うではないか。

「何が凄い発見なのか全然分からない…」ということで頭を抱えてしまった筆者は、そもそも経済学の議論を理解していないと話にならないのかもと思い、何冊かの入門書に当たってみる事にした。そこで出会ったのが以下の本である。

ミクロ経済学の力
ミクロ経済学の力

最終的に辿り着いたこの本で、ようやく理解したのは「経済学というのは計算可能性を問題にしている」ということだった。つまり、有名な需要と供給のモデルも、ある前提条件の下に数学的に計算可能であるから「経済学的に」意味があるというわけだ。今までそんなことも知らなかったの? と思われるかもしれないが、経済学の多くの入門書では案外この事が説明されていないのである。一般の人が手に取る多くの入門書では、元々は計算から導かれたと思われる法則を応用した政策の提言的な内容が多く、そこから計算という雰囲気は極力排除されている。おそらく数学的に説明しようとすると多くの読者が逃げてしまうからだろうと想像するが、そのために経済学とはそもそも計算モデルを開発する事なのだと言う理解にはなかなか辿り着けない。

ミクロ経済学が(合理的行動の原理や数学モデルを使って)導き出す結論の多くは、例えば「価格が上がると、供給が増える」というような、常識でも十分理解できるものが多い。物理学のように「光速に近いロケットに乗ると時間の進み方が遅くなる」などというアッと驚く結論がつぎつぎに出てくるわけではない。ではなぜ、数理モデルなどわざわざ使って持って回った分析をするのか、はじめから常識をなぜ使わないのかというと、それはわれわれの議論に「大きな見落としや、論理の穴」がないかをチェックする有効な方法だからである – ミクロ経済学の力

この経済学的なモデルの役割に気づけなかったのは、自分がソフトウェア開発者だからという事もあるかもしれない。ソフトウェア開発というのは日常的にモデルを扱う仕事だ。しかし、ここでのモデルは他人とコミュニケーションするための言語としてのモデルである。意図が通じれば良いのでモデルの厳密性などは問題にならない。重要なのは、相手の文脈を踏まえて「納得のできる」モデルを提示する事だけである。

限定的な合理性が開く可能性

さて、これで「限定的な合理性」の重要性が少しずつ見えて来た。計算可能性を重視するからこそ、「完璧な合理性」とそこから生まれる完全競争のモデルから脱却するのはなかなか難しいだろうと想像出来る。つまり、そこでは計算可能性を実現するために、人間あるいは社会をかなり単純化して見ているわけだ。そこには当時の環境から得られる経済学者自身の計算能力の問題もあったのかもしれない。

サイモン氏が「限定的な合理性」という考え方を発見したのは、彼が1950年代に、経済学の知見を経営学に応用しようとしたのがきっかけになっている。経済学のモデルを別分野の現実的な問題に応用しようとして初めて色々な問題が見えて来た。現実の人間は、経済学の合理性を実現するには圧倒的に能力が足りていない。もっと現実的なモデルを作るためには、人間そのものの限界、つまり「内部環境」の条件について我々は知らなければならない。

人間の限界を明らかにしてそこから計算可能なモデルを開発しようとすれば、経済学から離れてあらゆる分野の知見が必要になってくる。その必要性が彼を心理学やコンピューター科学に向かわせ、さらには人間の脳の中で行われる情報処理、つまり認知科学や人工知能と言った新しい分野を開拓させる原動力になった。

サイモン氏は、1956年に開催されて「人工知能 (Artificial Intelligence)」という研究分野の起源となったダートマス会議において、世界初の人工知能プログラムと呼ばれる「Logic Theorist」のデモンストレーションを行っている。

企業組織の研究と人工知能の研究はかけ離れているように見えるが、どちらも人間の問題解決能力と判断力の性質への洞察を必要とする。サイモンは1950年代初めにランド研究所でコンサルタントとして働いており、普通の文字や記号を使ってプリンターで地図を描いたのを見ている。そこから彼は記号を処理できる機械なら意思決定をシミュレートできるだろうし、人間の思考過程すらシミュレートできるのではないかと考えた。 – Logic Theorist – Wikipedia

 

手続的合理性

前回の人工物のモデルに経済学の考え方を導入すると以下のような形になる。

outer-inner-economy

外部環境はその経済主体(経済活動を行う人や組織)と関わりを持つ他の経済主体の集合によって定義される。そして、内部環境は主体の「目標」と「能力」によって定義される。

古典的な経済学のモデルだと、主体の「能力」は考慮されず(あるいは全知全能と言っても良いかもしれない)、「目標」はそのまま「完璧な合理性」を意味する。外部環境と目標だけ分かれば、その主体についての行動を予測する事が可能な、完全競争のモデルである。それとは対照的に、「限定的な合理性」を考慮する場合は、経済主体の「能力」が問題になってくる。

つまり、合理性というのは人間がどのように振る舞うのかということを計算可能にするルールのようなものだ。完璧な合理性の下で、そのルールはかなり単純なものであった。では、限定的な合理性の下でそのルールはどのようなものになるだろうか?

完璧な合理性、つまり全知全能モデルでは、人間は常に最適な選択肢を知っているので単にそれを選ぶという話だったが、全知全能でない、限定的な合理性しか持ち合わせない人間の場合、自分にとって出来るだけ得になるような選択肢を探すそのプロセスが大事になってくる。つまり、ここで合理性の問題は「選択肢の優劣」から「良さそうな選択肢をどう探すか」という問題にシフトしている。この「良さそうな選択肢をどう探すか」問題を『システムの科学』では「手続的合理性」と呼んでいる。

満足出来る選択肢を探す

サイモン氏は、「良さそうな選択肢をどう探すか」問題を、以下の二つに分けて考える。

  • 探索: 限られた能力で選択肢をどう探すか?
  • 判断基準: 見つけた選択肢を選択するかどうかの判断はどのように行われるのか?

完璧な合理性の下では、客観的に考え得るすべての選択肢を知っているという前提なので「探索」をする必要はなく、選択肢の中から最適なものを選ぶ際も「効用関数」という統一的な判断基準を持っている。しかし限定的な合理性の下では、主体は自分の持つ能力を駆使して選択肢を探さなければならない。例えば、旅行に行く計画を立てるとして、あなたは自分の目的に沿ったプランを出来るだけ安い予算で実現させたいと思う。この場合、交通手段や宿泊場所といった選択肢をどうやって見つけ出せば良いのだろうか?

この選択肢を見つけ出すコストを、経済学では「取引コスト(transaction costs)」と呼んでいる。このコストを肩代わりするためにマネジメントや会社組織の存在が必要になる、という話を「UberやAirbnbが経済にもたらす革命的なインパクト」で書いた。旅行の場合は、取引コストを肩代わりする旅行代理店という組織が存在する。ところが、インターネットの出現によって、そういった探索が劇的に効率的になり、マネジメントや組織の存在意義を揺るがす事態になった。

限定的な合理性の下で、どの選択肢を選ぶのかという判断基準について、サイモン氏は「満足化」というモデルを提案している。これは簡単に言えば、人は見つけ出した選択肢の中から「満足」出来そうなものを選ぶ、という至って当たり前的な行動パターンである。重要なのはこの「満足化」をいかに計算可能にするかということで、氏は心理学の「要求水準」というモデルを援用している。

要求水準は、人が何かに満足するかどうかについて、その人の過去の実績(経験)を元に基準を設定するという考え方である。過去の実績を上回れば満足し、下回れば不満となる。つまり、同じものに対しても人によっては満足したりしなかったりすることになる。これは「主観」というものをなかなかにうまくモデル化しているのではないかと思う。

心理学には要求水準の程度を測る「内田クレペリン検査」というものがあり、今でも職業適性検査として活用されている。

簡単な一桁の足し算を1分毎に行を変えながら、休憩をはさみ前半と後半で各15分間ずつ合計30分間行う検査です。全体の計算量(作業量)、1分毎の計算量の変化の仕方(作業曲線)と誤答から、受検者の能力面と性格や行動面の特徴を総合的に測定します。

 

まだまだ先は長い

というわけで、今回は経済学ってそもそも何なのかという話から、サイモン氏がそれまでの古典的な経済学を支配していた「完璧な合理性」に代わる「限定的な合理性」を提案し、能力が限定的である人間の振る舞いとはどのようなものであるかを、あらゆる分野を駆け巡って考え抜き、最終的に「要求水準」による「満足化」の理論に辿り着くところまで紹介した。この成果はノーベル経済学賞として評価されているわけなので『システムの科学』の中でも相当に重要な議論である事は間違いない。しかし、まだ第2章に入ったところで先はまだまだ長い(全8章)。こんなところで息切れしている場合ではないのだが。。それにしても疲れた (;´д`)

(まだまだ続きたい所存)