ElixirとRabbitMQでJenkinsっぽいサービスつくる その1
Elixirで何か作ってみたい
ということで、いろいろ考えた結果、俺の考えた最強のYet Another Jenkinsをつくってみようとおもいます。 Queueの管理はRabbitMQに任せる予定なので、いわゆるPub/Sub Applicationになる予定。 ブログなので、進捗等を雑にまとめていく。
コンセプト
UIはIFTTTくらいシンプルにする
シンプル イズ ベスト
サービスは実行スクリプトを持たない、Jobのコントロールにのみ専念する
Jenkinsの場合、Jobに直接スクリプトを登録できるのですが、これをやり過ぎると責任範囲が曖昧になってしまう上に、スクリプトをどこで定義しているのかわからなくなってしまうので禁止にする。 代わりに、実行ファイルもしくはPluginの機能を実行する。 最悪、サービスが止まっても、Jobをローカルで実行できることが望ましい。
JobはDocker container上で実行する
Slaveサーバ同士の環境差分や、Localだと動いた/動かない、Jenkinsだと動いた/動かない 問題が厄介なので、差分を無くすために積極的にDockerの恩恵にあずかる。
悩み中
JenkinsのBuild FlowやWork Flow Pluginに代わるJobの定義方法を模索中。 今のプロジェクトでは、これらをヘビーに使っていて、便利さは重々感じているのですが、Jenkinsに依存しちゃうのがいまいちなのと、Jenkins職人じゃないと管理できないのが辛い。
ひとまず、Bitbucketと連携を試みる
なぜGitHubじゃないのかというと、弊社がAtlassianのサービス使っているので、需要はこっちの方が高いからです。もちろん、将来的にはGitHubも対応予定。
Webhookのリクエスト確認
Git pushに対するWebhook
フローとしては
- Git push
- Bitbucket -- webhook --> Publisherアプリ
- Publisherアプリがwebhookの情報を加工してRabbitMQにメッセージとして送る
- Exchangeが適切なQueueにメッセージを追加
- ConsumerアプリがQueueからメッセージを取り出し処理する
Netcatで生データ見るのが、一番手っ取り早い。
$ nc -l 3000 POST / HTTP/1.1 Host: mysidia.takitake.tech:3000 Content-Length: 3404 Accept-Encoding: gzip, deflate Accept: */* X-Event-Key: repo:push User-Agent: Bitbucket-Webhooks/2.0 X-Hook-UUID: 50ac74f5-e8d8-4b6a-82ce-02f433d4816c Content-Type: application/json Via: 1.1 util-102.ash1.bb-inf.net (squid/3.3.8) Cache-Control: max-age=259200 Connection: keep-alive {"repository": {"name": "mysidia", "scm": "git", "links": {"avatar": {"href": "https://bitbucket.org/takitake/mysidia/avatar/16/"}, "html": {"href": "https://bitbucket.org/takitake/mysidia"}, "self": {"href": "https://api.bitbucket.org/2.0/repositories/takitake/mysidia"}}, "full_name": "takitake/mysidia", "uuid": "{65abe83f-810a-469f-bfe0-e2d4c409618b}", "type": "repository", "is_private": true, "owner": {"links": {"avatar": {"href": "https://bitbucket.org/account/takitake/avatar/32/"}, "html": {"href": "https://bitbucket.org/takitake/"}, "self": {"href": "https://api.bitbucket.org/2.0/users/takitake"}}, "uuid": "{22c717bd-843a-47f8-8a52-38eef8145ddb}", "display_name": "Takeshi Takizawa", "type": "user", "username": "takitake"}}, "push": {"changes": [{"old": null, "closed": false, "created": true, "truncated": false, "commits": [{"author": {"raw": "TakiTake <TakiTake.create@gmail.com>", "user": {"links": {"avatar": {"href": "https://bitbucket.org/account/takizawatake01/avatar/32/"}, "html": {"href": "https://bitbucket.org/takizawatake01/"}, "self": {"href": "https://api.bitbucket.org/2.0/users/takizawatake01"}}, "uuid": "{608bf02a-5eb8-4822-b16e-699e6fd8fbe3}", "display_name": "Takeshi Takizawa", "type": "user", "username": "takizawatake01"}}, "links": {"self": {"href": "https://api.bitbucket.org/2.0/repositories/takitake/mysidia/commit/8fb0eefc6a340d57598ca553a07fade1a10f2c30"}, "html": {"href": "https://bitbucket.org/takitake/mysidia/commits/8fb0eefc6a340d57598ca553a07fade1a10f2c30"}}, "type": "commit", "message": "Initial commit with contributors\n", "hash": "8fb0eefc6a340d57598ca553a07fade1a10f2c30"}], "forced": false, "links": {"commits": {"href": "https://api.bitbucket.org/2.0/repositories/takitake/mysidia/commits?include=8fb0eefc6a340d57598ca553a07fade1a10f2c30"}, "html": {"href": "https://bitbucket.org/takitake/mysidia/branch/master"}}, "new": {"name": "master", "links": {"commits": {"href": "https://api.bitbucket.org/2.0/repositories/takitake/mysidia/commits/master"}, "html": {"href": "https://bitbucket.org/takitake/mysidia/branch/master"}, "self": {"href": "https://api.bitbucket.org/2.0/repositories/takitake/mysidia/refs/branches/master"}}, "type": "branch", "target": {"links": {"self": {"href": "https://api.bitbucket.org/2.0/repositories/takitake/mysidia/commit/8fb0eefc6a340d57598ca553a07fade1a10f2c30"}, "html": {"href": "https://bitbucket.org/takitake/mysidia/commits/8fb0eefc6a340d57598ca553a07fade1a10f2c30"}}, "type": "commit", "message": "Initial commit with contributors\n", "hash": "8fb0eefc6a340d57598ca553a07fade1a10f2c30", "author": {"raw": "TakiTake <TakiTake.create@gmail.com>", "user": {"links": {"avatar": {"href": "https://bitbucket.org/account/takizawatake01/avatar/32/"}, "html": {"href": "https://bitbucket.org/takizawatake01/"}, "self": {"href": "https://api.bitbucket.org/2.0/users/takizawatake01"}}, "uuid": "{608bf02a-5eb8-4822-b16e-699e6fd8fbe3}", "display_name": "Takeshi Takizawa", "type": "user", "username": "takizawatake01"}}, "date": "2015-09-25T12:44:44+00:00", "parents": []}}}]}, "actor": {"links": {"avatar": {"href": "https://bitbucket.org/account/takitake/avatar/32/"}, "html": {"href": "https://bitbucket.org/takitake/"}, "self": {"href": "https://api.bitbucket.org/2.0/users/takitake"}}, "uuid": "{22c717bd-843a-47f8-8a52-38eef8145ddb}", "display_name": "Takeshi Takizawa", "type": "user", "username": "takitake"}}
bitbucket.repo.pushをRabbitMQのrouting keyにして、messageは上記のJSONをmessagepackでエンコードしようかな。
Google様Pub/Subサービスしてた
対抗するわけではないので、積極的に参考にしていく方向で
What is Google Cloud Pub/Sub? - Cloud Pub/Sub — Google Cloud Platform