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) – サインイン