読者です 読者をやめる 読者になる 読者になる

ghrls: GitHub Tags / Releases を手元からシュッと確認するツールを作った

GitHub に push されたタグの一覧、またタグに紐付いた Releases の情報をワンコマンドでシュッと表示するツール ghrls を作りました。

github.com

背景

自分は何かアプリケーション・ライブラリを作るときに依存ライブラリのバージョンを細かく気にする質で、たいてい Semantic Versioning でいう patch version レベルで指定しています (e.g. ~> 1.6.10, ~1.6.10)。 そういうわけで新しい依存を加えるときはまず最新バージョンがいくつか確認しているのですが、そのために毎回

  1. 該当リポジトリをブラウザで開く
  2. コミット数と並んでいる 30 Releases をクリックして開く
  3. 先頭に記載されたバージョン番号を確認する

というステップを踏んでいて、さすがに面倒になっていました。

f:id:dtan4:20170213223606p:plain

Releases に上がっている tarball の URL を確認するには、さらに「タグをクリックして開く」「tarball を右クリックで “Copy Link Address"」が加わりやってられない。

というわけで面倒くさいが高じた結果、手元でサクッと確認したいということになり ghrls を作りました。

使い方

ghrls list <user/name> で、そのリポジトリの Tag / Release 一覧を表示します。

$ ghrls list kubernetes/kubernetes | head
TAG               TYPE           CREATEDAT                        NAME
v1.6.0-alpha.0    TAG
v1.5.3-beta.0     TAG
v1.5.2            TAG+RELEASE    2017-01-12 13:51:15 +0900 JST    v1.5.2
v1.5.2-beta.0     TAG
v1.5.1            TAG+RELEASE    2016-12-14 09:50:36 +0900 JST    v1.5.1
v1.5.1-beta.0     TAG
v1.5.0            TAG+RELEASE    2016-12-13 08:29:43 +0900 JST    v1.5.0
v1.5.0-beta.3     TAG+RELEASE    2016-12-09 06:52:35 +0900 JST    v1.5.0-beta.3
v1.5.0-beta.2     TAG+RELEASE    2016-11-25 07:29:04 +0900 JST    v1.5.0-beta.2

ghrls get <user/name> <tag> で、指定したタグの Release 情報を表示します。 対応するコミットハッシュや作成日、Release にアップロードされた成果物 URL などが表示されます。

$ ghrls get kubernetes/kubernetes v1.5.2
Tag:         v1.5.2
Commit:      08e099554f3c31f6e6f07b448ab3ed78d0520507
Name:        v1.5.2
Author:      saad-ali
CreatedAt:   2017-01-12 13:51:15 +0900 JST
PublishedAt: 2017-01-12 16:25:50 +0900 JST
URL:         https://github.com/kubernetes/kubernetes/releases/tag/v1.5.2
Assets:      https://github.com/kubernetes/kubernetes/releases/download/v1.5.2/kubernetes.tar.gz

See [kubernetes-announce@](https://groups.google.com/forum/#!forum/kubernetes-announce) and [CHANGELOG](https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG.md#v152) for details.

SHA256 for `kubernetes.tar.gz`: `67344958325a70348db5c4e35e59f9c3552232cdc34defb8a0a799ed91c671a3`

Additional binary downloads are linked in the [CHANGELOG](https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG.md#downloads-for-v152).

各プラットフォーム対応バイナリを上げてある例も:

$ ghrls get dtan4/s3url v1.0.0
Tag:         v1.0.0
Commit:      0ebc096229e46f560827b9e041479abf5cf3823b
Name:
Author:      dtan4
CreatedAt:   2017-02-06 00:59:16 +0900 JST
PublishedAt: 2017-02-06 01:04:06 +0900 JST
URL:         https://github.com/dtan4/s3url/releases/tag/v1.0.0
Artifacts:   https://github.com/dtan4/s3url/releases/download/v1.0.0/s3url-v1.0.0-darwin-386.tar.gz
             https://github.com/dtan4/s3url/releases/download/v1.0.0/s3url-v1.0.0-darwin-386.zip
             https://github.com/dtan4/s3url/releases/download/v1.0.0/s3url-v1.0.0-darwin-amd64.tar.gz
             https://github.com/dtan4/s3url/releases/download/v1.0.0/s3url-v1.0.0-darwin-amd64.zip
             https://github.com/dtan4/s3url/releases/download/v1.0.0/s3url-v1.0.0-linux-386.tar.gz
             https://github.com/dtan4/s3url/releases/download/v1.0.0/s3url-v1.0.0-linux-386.zip
             https://github.com/dtan4/s3url/releases/download/v1.0.0/s3url-v1.0.0-linux-amd64.tar.gz
             https://github.com/dtan4/s3url/releases/download/v1.0.0/s3url-v1.0.0-linux-amd64.zip
             https://github.com/dtan4/s3url/releases/download/v1.0.0/s3url-v1.0.0-windows-386.tar.gz
             https://github.com/dtan4/s3url/releases/download/v1.0.0/s3url-v1.0.0-windows-386.zip
             https://github.com/dtan4/s3url/releases/download/v1.0.0/s3url-v1.0.0-windows-amd64.tar.gz
             https://github.com/dtan4/s3url/releases/download/v1.0.0/s3url-v1.0.0-windows-amd64.zip

インストール

Mac をお使いであれば Homebrew でインストールできます。 Go で書いてワンバイナリ吐くようにしておくと、Homebrew で簡単に配布できて最高。

$ brew tap dtan4/tools
$ brew install ghrls

GitHub Releases で各プラットフォーム対応のバイナリを配布しています。

このまま即使うこともできますが、GitHub API の制限で未ログインユーザは1時間に60回しか API call できないようになっています (参照)。 IP ベースの制限なので、オフィス / 集合住宅からの通信や Homebrew を併用する場合は速攻で上限に引っかかる可能性があります。

なので、心配な方はここから Personal Access Token を発行して、GITHUB_TOKEN 環境変数にセットしておいてください1。これなら1時間5000回まで上限緩和されます。

おわりに

ghrls を作ったおかげで、新しくツールを作るときの初速がだいぶ上がりました。 バグとか Feature Request、Pull Request をぜひいただけると幸いです :pray:


  1. Homebrew の場合も、HOMEBREW_GITHUB_API_TOKEN を設定することで同様の上限緩和が可能です (参照)。

Basic 認証かけるだけのプロキシサーバ Docker image 作った

Web アプリがあってとりあえず Basic 認証かけたいときは前段に Nginx 置けば楽なんだけど、毎回設定ファイル書いたり htpasswd 生成するのが面倒なので、サクッと用意できるよう Docker image を用意しました。

github.com

https://quay.io/repository/dtan4/nginx-basic-auth-proxy

以下のように、環境変数で username, password そしてプロキシ先の URL を指定すれば Basic 認証設定済みの Nginx が起動します。

$ docker run \
    --rm \
    --name nginx-basic-auth-proxy \
    -p 8080:80 \
    -e BASIC_AUTH_USERNAME=username \
    -e BASIC_AUTH_PASSWORD=password \
    -e PROXY_PASS=https://www.google.com \
    -e SERVER_NAME=proxy.dtan4.net \
    quay.io/dtan4/nginx-basic-auth-proxy

Docker Compose や Kubernetes を使って、メインの Web アプリコンテナは外部にポートを公開しない設定で立てた上でその前段に立ててあげるとセキュアで便利かもしれません。

version: '2'
services:
  web:
    image: tutum/hello-world:latest
  nginx:
    image: quay.io/dtan4/nginx-basic-auth-proxy:latest
    ports:
      - 8080:80
    environment:
      - BASIC_AUTH_USERNAME=username
      - BASIC_AUTH_PASSWORD=password
      - PROXY_PASS=http://web/

設定はサクッと書いただけなので、不備があればご指摘いただけると幸いです。

本当はなぜ作ったのか

ちょっと Amazon Elasticsearch Service を使い始めて Kibana も触ってたんですが、アクセス制限は IAM Policy 記述なので IAM リソース単位 or グローバル IP でしかいじれないのが悩みでした。ブラウザレベルで制限かけたかったので前段に何か噛ますか〜というので、とりあえず作ってみた形です。

毎回 Signed URL を発行するようにして Es クラスタドメインを隠すプロキシサーバを CoreOS が書いてたので、一緒に見てます。

github.com

余談でした。

k8stail: Kubernetes の複数 Pod のログをまとめて流し読みできるツールを作った

Kubernetes の特定の namespace にある、全 Pod のログを一括で流し読みできるコマンドラインツール k8stail を作りました。

github.com

取り急ぎこちらスクリーンショットです。

f:id:dtan4:20161118221101p:plain

インストール

Mac をお使いなら Homebrew でインストールできます。

$ brew tap dtan4/dtan4
$ brew install k8stail

その他の OS 用バイナリは GitHub Releases で配布しています。

また、対応している Kubernetes のバージョンは 1.3 以上 です。

使い方

-namespace で namespace を指定すると、その namespace に所属する全ての Pod のログが tail -f の如くリアルタイムで流れます。 -namespace 指定しなかったら default namespace を使います。

1 Pod に複数のコンテナがぶら下がっている場合は、それらもまとめて表示します。 コマンド実行後に Pod が追加された / 作り直されても、それに追従して新しい Pod のログが流れます。

Ctrl-C で止まります。

$ k8stail -namespace awesome-app
Namespace: awesome-app
Labels:
----------
Pod awesome-app-web-4212725599-67vd4 has detected
Pod awesome-app-web-4212725599-6pduy has detected
Pod awesome-app-web-4212725599-lbuny has detected
Pod awesome-app-web-4212725599-mh3g1 has detected
Pod awesome-app-web-4212725599-pvjsm has detected
[awesome-app-web-4212725599-mh3g1][web]  | creating base compositions...
[awesome-app-web-4212725599-zei9h][web]  |    (47.1ms)  CREATE TABLE "schema_migrations" ("version" character varying NOT NULL)
[awesome-app-web-4212725599-zei9h][web]  |    (45.1ms)  CREATE UNIQUE INDEX  "unique_schema_migrations" ON "schema_migrations"  ("version")
[awesome-app-web-4212725599-zei9h][web]  |   ActiveRecord::SchemaMigration Load (1.8ms)  SELECT "schema_migrations".* FROM "schema_migrations"
[awesome-app-web-4212725599-zei9h][web]  | Migrating to CreatePosts (20160218082522)

-timestamps オプションをつけるとタイムスタンプがつきます。

$ k8stail -namespace awesome-app -timestamps
Namespace: awesome-app
Labels:
----------
Pod awesome-app-web-4212725599-67vd4 has detected
Pod awesome-app-web-4212725599-6pduy has detected
Pod awesome-app-web-4212725599-lbuny has detected
Pod awesome-app-web-4212725599-mh3g1 has detected
Pod awesome-app-web-4212725599-pvjsm has detected
[awesome-app-web-4212725599-mh3g1][web] 2016-11-15T10:57:22.178667425Z  | creating base compositions...
[awesome-app-web-4212725599-zei9h][web] 2016-11-15T10:57:22.309011520Z  |    (47.1ms)  CREATE TABLE "schema_migrations" ("version" character varying NOT NULL)
[awesome-app-web-4212725599-zei9h][web] 2016-11-15T10:57:22.309053601Z  |    (45.1ms)  CREATE UNIQUE INDEX  "unique_schema_migrations" ON "schema_migrations"  ("version")
[awesome-app-web-4212725599-zei9h][web] 2016-11-15T10:57:22.463700110Z  |   ActiveRecord::SchemaMigration Load (1.8ms)  SELECT "schema_migrations".* FROM "schema_migrations"
[awesome-app-web-4212725599-zei9h][web] 2016-11-15T10:57:22.463743373Z  | Migrating to CreatePosts (20160218082522)

また、-labels オプションで Pod についた Label を用いたフィルタリングができます。 下の例では、name: awesome-app-web ラベルが付いた Pod のログのみ表示しています。

$ k8stail -namespace awesome-app -labels name=awesome-app-web
Namespace: awesome-app
Labels:    name=awesome-app-web
----------
Pod awesome-app-web-4212725599-67vd4 has detected
Pod awesome-app-web-4212725599-6pduy has detected
Pod awesome-app-web-4212725599-lbuny has detected
Pod awesome-app-web-4212725599-mh3g1 has detected
Pod awesome-app-web-4212725599-pvjsm has detected
[awesome-app-web-4212725599-mh3g1][web]  | creating base compositions...
[awesome-app-web-4212725599-zei9h][web]  |    (47.1ms)  CREATE TABLE "schema_migrations" ("version" character varying NOT NULL)
[awesome-app-web-4212725599-zei9h][web]  |    (45.1ms)  CREATE UNIQUE INDEX  "unique_schema_migrations" ON "schema_migrations"  ("version")
[awesome-app-web-4212725599-zei9h][web]  |   ActiveRecord::SchemaMigration Load (1.8ms)  SELECT "schema_migrations".* FROM "schema_migrations"
[awesome-app-web-4212725599-zei9h][web]  | Migrating to CreatePosts (20160218082522)

Why

Kubernetes のコマンドラインツール kubectl には、Pod のログを見るためのコマンド kubectl logs があります。 ですが、このコマンドは一つの Pod のログしか見られないため、予め Pod の名前を把握しておく必要があります。 ReplicationController を使って動的にポコポコ Pod を作っている場合は Pod 名にランダムな Suffix がつくので、いちいち kubectl get po で確認しないといけません。 また、大抵は1サービスに対して冗長性や負荷分散の目的で複数 Pod を立てることになります。 その場合は kubectl logs を使っていると「サービス全体の」ログを一度に見ることが困難です。 Pod の数だけターミナルを開くとかになります…

Fluentd で Elasticsearch に飛ばして云々…とか Logentries などの SaaS に飛ばしてフィルタリングする方法もありますが(所属先はもともと後者で全ログを取ってる)、確か Pod 名とかそういうフィールドがなくてフィルタリングが面倒くさそうでした。 あとターミナルからサクッと確認するのが難しいですね。

あと、Deployments / ReplicationController 使ってると Pod spec の設定をミスったときに Pod 作り直しループに突入するのですが、そうなると Pod が速攻で削除されて別名の Pod が立つのでログが全然負えなくて厳しかったです。

所属先では現在積極的に Kubernetes の導入を進めていて、先日リリースしたサービスもバックエンドはマイクロサービス化されて Kubernetes 上で稼働しています。 そういうわけで「サービス全体の」ログを手元で見たい、という需要が高まってきたので今回 k8stail を作りました。

既に johanhaleby/kubetail というのはあって、これは同じ機能をシェルスクリプトで提供しています。 が、Known issues にかかれているように終了時の挙動が微妙なのと途中から Pod が追加された場合も追従できなさそうだったので今回 Go で一から作りました。

おわりに

k8stail の紹介をしました。 さくっと作ったという感じなので詰めが甘い部分ありますが、ちょくちょく改善していく所存です。

最近公式で出た Go の Kubernetes クライアントライブラリ kubernetes/client-go を使ってみたかったので、良い題材になりました。 ただ、Kubernetes でかいので仕方ないんですがビルドにかなり時間食うようになるのが厳しいですね…

s3url: S3 の署名付き URL を一発で発行するコマンドを作った

S3 オブジェクトの署名付き URL を一発で発行できるコマンドラインツール s3url を作ったのでご紹介です。

github.com

下のような感じで、S3 オブジェクトのパスを与えると5分間誰でもアクセスできる URL が即座に発行されるコマンドです。

$ s3url s3://my-bucket/foo.key
https://my-bucket.s3-ap-northeast-1.amazonaws.com/foo.key?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIA***************************%2Fap-northeast-1%2Fs3%2Faws4_request&X-Amz-Date=20160923T010227Z&X-Amz-Expires=300&X-Amz-SignedHeaders=host&X-Amz-Signature=****************************************************************

S3 署名付き URL とは、指定したオブジェクトに対する「一定期間有効な」「誰でもダウンロード可能となる」URL のことです。詳しくはドキュメントを御覧ください。

docs.aws.amazon.com

インストール

Mac ユーザであれば Homebrew 経由でインストール可能です。dtan4/dtan4 tap にレシピがあります。

$ brew tap dtan4/dtan4
$ brew install s3url

その他の OS をお使いの方は、GitHub Releases からバイナリをダウンロードしてください。

使い方

引数に S3 オブジェクトの URL を与えることで、一時的に誰でもそのオブジェクトをダウンロード可能になる署名付き URL が表示されます。デフォルトだと5分間有効となっています。5分過ぎるとアクセスできなくなります。

$ s3url s3://my-bucket/foo.key
https://my-bucket.s3-ap-northeast-1.amazonaws.com/foo.key?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIA***************************%2Fap-northeast-1%2Fs3%2Faws4_request&X-Amz-Date=20160923T010227Z&X-Amz-Expires=300&X-Amz-SignedHeaders=host&X-Amz-Signature=****************************************************************

オブジェクトのは https://s3:// で始まる URL、または -b, -k オプションでそれぞれ指定することができます。

-d オプションを与えることで、秒単位で有効期間を指定することができます。

# 10分間有効
$ s3url s3://my-bucket/foo.key -d 600

--upload オプションでローカルファイルのパスを指定すれば、そのファイルを S3 へアップロードした後に URL を発行します。自分のマシンにあるファイルを他人へ渡すような場合に、アップロードを他の手段でしなくてよくなるので便利です。

# カレントディレクトリの foo.key を s3://my-bucket/foo.key にアップロードして URL 発行
$ s3url s3://my-bucket/foo.key --upload foo.key

なお、ここで発行された URL は期限付きとは言え誰でもアクセスできる URL です。なので、ファイルの機密性が高い場合は URL をパブリックな場所で公開してはいけません。チャットツールの private channel とかで必要な人だけと共有しましょう。

Why

チームで開発していると、他の人とファイルのやり取りをすることがそれなりにあるでしょう。で、他人に見られても問題ないファイルであればどこで共有しようといいでしょう。しかし、クレデンシャルファイル (e.g. AWS Management Console から落とせる IAM user の Access Key ID, Secret Access Key を書いた CSV) のような他人に見られちゃマズいファイルを共有するには、何らかのセキュアな方法を取らないといけません。

  • AirDrop
    • Mac に限定される、更にいうと Mac のバージョンもある程度限定される
    • Bluetooth で検知できる範囲にいないといけないので、リモート勤務とかだと使えない
  • Dropbox やチャットツールで直接共有
    • 他人にファイルを見られる可能性がある
    • ファイルを消し忘れて、相手が引き続きアクセスできる状態が保たれてセキュリティ上よくない
  • USB メモリ
    • 手間がかかって面倒、使いたいときに限って見つからない

そんな中で S3 と署名付き URL 使うのは便利だと思っています。

  • バケットやオブジェクトに対して細かくアクセスルールを決められる
  • URL なのでどんな環境でもだいたい送れる
  • 署名付き URL は指定した時間経つと無効化される

先述した IAM クレデンシャルの場合は、Ops チームのみ読み書きできるバケットを作成した上でそこに CSV を置き、相手には s3url で発行した URL を private chat で渡す運用にできます。

あとは機能的な why があります。公式ドキュメントや s3url のコードをみたらわかるんですけど、署名付き URL って AWS CLI で生成できないし API も1コールで生成できないんですよね…。Management Console でもいいけど。なので、この辺のやり取りを全部ひっくるめてワンコマンドでできたら便利かなー、というので作られたのが s3url なのです。

おわりに

最近作った s3url の紹介をしました。s3url、自分のチームを中心にそれなりに使われています。

同じ機能を持ちかつ s3url と名のつくツールは既にいくつかあったので例によって他の名前を考えたのですが、いいのが思いつかず…。そこで既存の見てみると、どれも LL 上の実装で言語ランタイムをインストールしないといけないやつでした。これはバイナリ置くだけで実行できるコマンドラインツール作れば、名前が被っていようと他を駆逐できるのでは…という目論見で、あえて s3url と名付けて Go で実装 & プラットフォームごとのバイナリを一括配布する形にしました。

Issue, Pull Request お待ちしています :octocat:

terraform import と Terraforming

先日 Terraform 0.7 がリリースされました。 Terraform 0.7 の目玉機能は、なんと言っても既存リソースの import terraform import ではないでしょうか。全世界の Terraform ユーザが長年待ちわびていた機能がついに搭載されたことになります。 あ、あと terraform.tfstate のバージョンが 1 から 3 に上がったので後方互換性が地味に失われているのも大きいですね…

さて、自分は1年以上前から既存リソースをコード化する手段として Terraforming を開発し今に至るまでメンテナンスしてきました。

github.com

現在ではそれなりの認知をいただき、リソース追加などで Pull Request も多くもらうようになりました。 そんなことをしていたので、既存リソース import の公式対応には注目している、むしろしなければならないような立ち位置となっています。

本記事では、terraform import を試しつつ Terraforming がこの先生きのこれるのかどうかを見ていきたいと思います。

terraform import

概要

terraform import は、既存のリソースを Terraform の管理下に置くための機能です。Terraform 0.7 時点では、tfstate(Terraform がリソース管理状態を把握する JSON)のみ生成できます。人間が書く tf ファイルの生成はできません。

公式ドキュメントはこれ => Import - Terraform by HashiCorp

This is a great way to slowly transition infrastructure to Terraform

とのことです。楽しみですね。

A future version of Terraform will fully generate configuration significantly simplifying this process.

とも言ってるので、いずれは tf の生成にも対応するのでしょう。

対応リソース

公式が提供しているだけあって、リリース時点で数多くのリソースが import 機能に対応しています。逆に言うとすべてのリソースが対応しているわけではありません。

対応リソース一覧 => Import: Resource Importability - Terraform by HashiCorp

上にあるリソースを数えてみたところ *1107種類のリソースが import に対応しています。AWS だけでなく、Azure や DigitalOcean、OpenStack など複数 provider に対応しているのも公式の強みですね。 意外と S3 系のリソースは対応していなかったりします。

実際に使ってみる

適当に EC2 インスタンスを1台立てました(以降、EC2 インスタンスの情報は一部マスキングした状態でお届けします)。

f:id:dtan4:20160818010212p:plain

おもむろに terraform import します。引数に Terraform 上でのリソース名 (tf ファイルの resource につける名前) と import するリソースの ID (ここでは EC2 instance ID) を指定してあげます。 実行すると、カレントディレクトリに terraform.tfstate が生成されます。

$ terraform import aws_instance.great-instance i-96163a09
aws_instance.great-instance: Importing from ID "i-96163a09"...
aws_instance.great-instance: Import complete!
  Imported aws_instance (ID: i-96163a09)
aws_instance.great-instance: Refreshing state... (ID: i-96163a09)

Import success! The resources imported are shown above. These are
now in your Terraform state. Import does not currently generate
configuration, so you must do this next. If you do not create configuration
for the above resources, then the next `terraform plan` will mark
them for destruction.

$ ls
terraform.tfstate

中身はこんな感じ。当たり前ですがちゃんと tfstate が生成されています。

{
    "version": 3,
    "terraform_version": "0.7.0",
    "serial": 0,
    "lineage": "c1f9c929-52e9-4b4a-897f-6c5be268e505",
    "modules": [
        {
            "path": [
                "root"
            ],
            "outputs": {},
            "resources": {
                "aws_instance.great-instance": {
                    "type": "aws_instance",
                    "primary": {
                        "id": "i-96163a09",
                        "attributes": {
                            "ami": "ami-374db956",
                            "availability_zone": "ap-northeast-1a",
                            "disable_api_termination": "false",
                            "ebs_block_device.#": "0",
                            "ebs_optimized": "false",
                            "ephemeral_block_device.#": "0",
                            "iam_instance_profile": "",
                            "id": "i-96163a09",
                            "instance_state": "running",
                            "instance_type": "t2.micro",
                            "key_name": "****",
                            "monitoring": "false",
                            "network_interface_id": "eni-********",
                            "private_dns": "ip-172-31-7-232.ap-northeast-1.compute.internal",
                            "private_ip": "172.31.7.232",
                            "public_dns": "ec2-52-196-13-225.ap-northeast-1.compute.amazonaws.com",
                            "public_ip": "52.196.13.225",
                            "root_block_device.#": "1",
                            "root_block_device.0.delete_on_termination": "true",
                            "root_block_device.0.iops": "100",
                            "root_block_device.0.volume_size": "8",
                            "root_block_device.0.volume_type": "gp2",
                            "security_groups.#": "0",
                            "source_dest_check": "true",
                            "subnet_id": "subnet-********",
                            "tags.%": "1",
                            "tags.Name": "great-instance",
                            "tenancy": "default",
                            "vpc_security_group_ids.#": "1",
                            "vpc_security_group_ids.**********": "sg-********"
                        },
                        "meta": {
                            "schema_version": "1"
                        }
                    },
                    "provider": "aws"
                }
            }
        }
    ]
}

ところで、terraform.tfstate がすでにある状態で terraform import するとどうなるのでしょうか。 もう一台インスタンスを立てて試してみました。

f:id:dtan4:20160818010244p:plain

import はちゃんとできます。

$ envchain dtan4 terraform import aws_instance.awesome-instance i-60103cff
aws_instance.awesome-instance: Importing from ID "i-60103cff"...
aws_instance.awesome-instance: Import complete!
  Imported aws_instance (ID: i-60103cff)
aws_instance.awesome-instance: Refreshing state... (ID: i-60103cff)

Import success! The resources imported are shown above. These are
now in your Terraform state. Import does not currently generate
configuration, so you must do this next. If you do not create configuration
for the above resources, then the next `terraform plan` will mark
them for destruction.

tfstate もきっちりマージされた状態になっています。serial もインクリメントされているのでこのまま terraform plan を実行しても問題ありません。

{
    "version": 3,
    "terraform_version": "0.7.0",
    "serial": 1,
    "lineage": "c1f9c929-52e9-4b4a-897f-6c5be268e505",
    "modules": [
        {
            "path": [
                "root"
            ],
            "outputs": {},
            "resources": {
                "aws_instance.awesome-instance": {
                    "type": "aws_instance",
                    "primary": {
                        "id": "i-60103cff",
                        "attributes": {
                            "ami": "ami-374db956",
                            "availability_zone": "ap-northeast-1a",
                            "disable_api_termination": "false",
                            "ebs_block_device.#": "0",
                            "ebs_optimized": "false",
                            "ephemeral_block_device.#": "0",
                            "iam_instance_profile": "",
                            "id": "i-60103cff",
                            "instance_state": "running",
                            "instance_type": "t2.micro",
                            "key_name": "****",
                            "monitoring": "false",
                            "network_interface_id": "eni-********",
                            "private_dns": "ip-172-31-15-63.ap-northeast-1.compute.internal",
                            "private_ip": "172.31.15.63",
                            "public_dns": "ec2-52-198-18-169.ap-northeast-1.compute.amazonaws.com",
                            "public_ip": "52.198.18.169",
                            "root_block_device.#": "1",
                            "root_block_device.0.delete_on_termination": "true",
                            "root_block_device.0.iops": "100",
                            "root_block_device.0.volume_size": "8",
                            "root_block_device.0.volume_type": "gp2",
                            "security_groups.#": "0",
                            "source_dest_check": "true",
                            "subnet_id": "subnet-********",
                            "tags.%": "1",
                            "tags.Name": "awesome-instance",
                            "tenancy": "default",
                            "vpc_security_group_ids.#": "1",
                            "vpc_security_group_ids.**********": "sg-********"
                        },
                        "meta": {
                            "schema_version": "1"
                        }
                    },
                    "provider": "aws"
                },
                "aws_instance.great-instance": {
                    "type": "aws_instance",
                    "primary": {
                        "id": "i-96163a09",
                        "attributes": {
                            "ami": "ami-374db956",
                            "availability_zone": "ap-northeast-1a",
                            "disable_api_termination": "false",
                            "ebs_block_device.#": "0",
                            "ebs_optimized": "false",
                            "ephemeral_block_device.#": "0",
                            "iam_instance_profile": "",
                            "id": "i-96163a09",
                            "instance_state": "running",
                            "instance_type": "t2.micro",
                            "key_name": "****",
                            "monitoring": "false",
                            "network_interface_id": "eni-********",
                            "private_dns": "ip-172-31-7-232.ap-northeast-1.compute.internal",
                            "private_ip": "172.31.7.232",
                            "public_dns": "ec2-52-196-13-225.ap-northeast-1.compute.amazonaws.com",
                            "public_ip": "52.196.13.225",
                            "root_block_device.#": "1",
                            "root_block_device.0.delete_on_termination": "true",
                            "root_block_device.0.iops": "100",
                            "root_block_device.0.volume_size": "8",
                            "root_block_device.0.volume_type": "gp2",
                            "security_groups.#": "0",
                            "source_dest_check": "true",
                            "subnet_id": "subnet-********",
                            "tags.%": "1",
                            "tags.Name": "great-instance",
                            "tenancy": "default",
                            "vpc_security_group_ids.#": "1",
                            "vpc_security_group_ids.**********": "sg-********"
                        },
                        "meta": {
                            "schema_version": "1"
                        }
                    },
                    "provider": "aws"
                }
            }
        }
    ]
}

あとは、この tfstate や Management Console で得られる情報を元に tf ファイルを書き、terraform plan で差分が出なければ既存リソースの import は完了です(今回はそこまでしません…)。

Terraforming との比較

それぞれ機能の比較をしてみました。

terraform import Terraforming
メンテナ HashiCorp @dtan4
対応リソース数 107 37
(↑うち AWS 67 35
AWS 以外の provider Azure, DigitalOcean, Fastly, OpenStack, Triton DNSimple, Datadog
全リソース一括 import x o (resource type 単位)
リソースを指定した import o x
tfstate の import o o
tf の import x o

メンテナ

まず、terraform import は言わずもがな HashiCorp 本家がやっているので安心感があります。Terraform でのリソースパラメータ追加にもリアルタイムで追従できるものと思われます。 たまにあるんですよね。Terraform のバージョンが上がってパラメータ足されたから、Terraforming の生成結果が仕様を満たさなくなることが…。 一方で Terraforming は @dtan4 が(ほぼ)プライベートの時間でメンテナンスをしています。最近 Issue, PR の消化が追いつかなくなってきて危機感を感じています。

リソース数

対応リソース数は歴然とした差があります。特に Terraforming は AWS 以外がめっぽう弱いです。AWS も基本自分が触るリソースを中心に対応しているので、ある程度偏りが出てしまうのは否めません。

AWS 以外の provider

AWS 以外の provider は、terraform import と Terraforming できっかり分かれました。ちなみに DNSimple と Datadog は、会社で使っているので特別に開発したという経緯があります。

全リソース / リソースを指定した import 機能

terraform import は全リソース一括 import ができないのが痛いですね。数台程度ならまだしも、多くの場合だと数十から数百のリソースを一気にコードに落とし込みたいケースになるのではないでしょうか。 awscli と連携すればできなくもなさそうですが…。この辺、Terraforming は普通に全リソースを攫ってくるようになっています。

逆に Terraforming リソース個別の import には対応していません。前々から要望はあったりするのですが、なかなか導入する気になれず…

tf の import

terraform import は tf の生成ができないのも現時点だと難しいですね。tfstate に比べるとまだ書きやすいですが、terraform plan で差分が出ないようきっちり書こうと思うとかなり神経を使います。 Terraforming は既存リソースの import をすべてツールに任せる思想で初めから開発していたので、tf 生成は対応しています。もちろん terraform plan で差分が出ないようチェックも行っています。

というわけで

自分の結論としては、現時点ではまだ Terraforming が生き残れるということです。

安心感とリソース対応数という点では完敗です。しかし、いま実際既存 AWS インフラのリソースを Terraform コードに落とし込みたい場合に使うとなると圧倒的に Terraforming の方がお手軽だといえます(リソースが対応している場合)。 コマンド一発で自分のアカウントが管理するリソースを一括 import する機能と tf の import、この2つが terraform import に実装される日までは Terraforming が使われて続けていくでしょう。使われ続けてほしい。雑なまとめだ!

今後も多くの場面で使ってもらえるよう、Terraforming のメンテナンスは精力的に続けていきます。みなさん引き続きご支援をよろしくお願いいたします。Issue, Pull Request 大歓迎です!

とりあえず Terraforming v0.10 をいい加減今週中に出さないとですね…

*1:Chrome DevTool Console で $('#main-content > div > ul > li').length

YAPC::Asia Hachioji 2016 で自作 PaaS について喋ってきた #yapc8oji

というわけで、7/2-3 に開催された YAPC::Asia Hachioji 2016 に参加してきました。ついでに喋ってきました。

yapcasia8oji-2016mid.hachiojipm.org

行ってきた

YAPC は2013年から数えて4回目なのですが、毎回ワイワイとお祭り感があるのが参加していて楽しいです。 ワイワイしつつ、内容も面白い & 興味深い発表が多いですね。

SideCI の方の発表で、Terraforming 紹介してもらえてありがたかったです。 近いうちに Terraforming リポジトリへ SideCI 導入します。 あとマイクロソフトの DC の話が面白かったです。Cognitive Services とかも含めて、Azure 色々できるんだなーとか。

喋ってきた

仕事でやったネタをどっかで喋っておきたかった & 隣の席の先輩が応募していたので、自分も締め切り1分前に応募しました。そしたら無事採択され、今回登壇したという形です。非 LT でのちゃんとした?登壇は初めてですね。

仕事で PaaS を作ったので、それについて話しました。

speakerdeck.com

応募時にヒヨって15分枠で出してそのまま通ったわけですが、いざ資料を作ってみると15分に収まらなかったですね…発表ではデモ含めてギリギリだったのですが。 発表時間、仮に余ったらデモするなり余談するなりで埋める術はありますが短い場合だと削るしかなくて厳しいです。今後の教訓になりました。 あと、先輩が「Kubernetes 導入しました」と発表した1時間後に「自作 PaaS 導入した。ECS 入れたい」って発表するのはクレイジーでしたね…

Hosting Paus できたらいいですね〜

Paus

一応発表した Paus について簡単に紹介しときます。

github.com

Paus は、docker-compose.yml 書いて git push したらそれだけで Web アプリがデプロイできるというシンプルな PaaS です。とりあえす下のデモ動画で雰囲気を掴んでください:


Paus: Docker Compose PaaS / Demo

Heroku とか OSS PaaS は便利だけど Buildpack + Addons 構成に縛られちゃう、もっと気軽にデプロイしたい。そういった流れで作りました。 全体的に荒削りですが、なんとか一通りのことはできるようになってます。

本番では AWS 上で稼働していますが、ローカルでも簡単に試せるよう Vagrant 設定一式を用意しました。ここGitHub OAuth application 作ったあと、上のリポジトリを clone して Client ID, Secret 書いて vagrant up したらお手元に Paus が立ちます。

さいごに

主催の方々スタッフの方々、本当にありがとうございました!2日間楽しめました。 来年もネタを準備して発表したいです。

Kubernetes Meetup Tokyo #2 で LT してきた #k8sjp

最近ちょくちょく人前出るようになったので、ちゃんと記録はしておこうと思いました。


六本木ヒルズGoogle で開催された Kubernetes Meetup Tokyo #2 で LT してきました。

k8sjp.connpass.com

最近作った Kubernetes Secret を手軽に扱えるツール k8sec と Go Kubernetes API Client Library を使ったコードの書き方を紹介しました。準備不足感が否めなくて申し訳なかったです…。スライドはこれです。

speakerdeck.com

(R, G, B) = (50, 109, 230) が Kubernetes の色っぽいです。

伝えたかったこととしては、

です。

で、こいつが発表中に紹介した k8sec です。Heroku 使ってた人にとっては馴染みやすいと思うのですがどうなんでしょう…。

github.com

Kubernetes の運用は自分たちも検証段階ですが、各社それぞれ違った知見を持ちつつ同じような悩みを抱えてたりしているようでした。今回みたいな Meetup や Slack などで情報交換していけるとよさそうです。ありがとうございました。