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を巡る対立 | ゆびてく