@ijin

[Michael H. Oshita]

Ruboty-github_pr_releaseを作った

よくプロジェクトでstagingproductionブランチにリリース用のpull requestを送ってmergeする事でCircleCIが走って自動デプロイするような仕組みを入れているんだけど、いちいちGitHub上でポチポチ画面を押すのが面倒くさいのと、久しぶりにやるとbasecompareとで混乱しそうになるので、ChatOpsを導入して効率化してみました。

Slackと連携できるボットにはrubotyを選択。

プラグイン周りではruboty-githubがあるけど、微妙に要求に応えれなかったので少し拡張したruboty-github_pr_releaseを作りました。久々のgem公開。

Install

Gemfile
1
gem 'ruboty-github_pr_release'

Release pull request

create

pull requestを作成。それまでmergeしたfeature branch等のタイトルが集約される

1
ruboty release from <org/repo:from_branch> to <org/repo:to_branch>

update

pull request作成後に新たなコミットを取り込む時やタイトルを変更したい時

1
ruboty update release from <org/repo:from_branch> to <org/repo:to_branch> as "new title"

deploy

pull requestをmergeしてデプロイ

1
ruboty deploy release from <org/repo:from_branch> to <org/repo:to_branch>

alias

ruboty-aliasruboty-scoped_aliasを使うとさらにコマンドを短縮して便利です。scopedにすると部屋毎にプロジェクトを割り当てたりも可能。

1
2
ruboty scoped alias release production -> release from ijin/test:staging to ijin/test:production
ruboty scoped alias deploy production -> deploy release from ijin/test:staging to ijin/test:production

簡単!

注意

  • 開発branchにmergeされたサマリーを作成するのでGit/GitHub flow等が前提(直接branchにコミットしない)
  • mergeをrevertしてしまうと、branch間の差分が取れなくなるので開発branch(develop等)から修正fixを新たにpull requestとして送る必要がある
  • ruboty-githubは個々人で認証をしているので、チャネルではなく、rubotyに対してDMで認証するべき(remember my github token xxx)

参考

おまけ

ほとんど作り終えたところでほぼ同じ機能を持ったincrements/ruboty-qiita-githubに気づいたけど、気にせずリリースしちゃった。一応merge周りはこっちの方が若干楽なので。。

AWS re:Invent 2016に参加してきた

4度目となるAWS re:Inventに参加したので、記録を。

帰国直後にJAWS-UG横浜支部で体験談を発表したのですっかり忘れてた。。

 

今年は例年より開催が1ヶ月遅く、且つ期間が長め(11/28〜12/2)でした。 参加人数は過去最大規模で32,000人。PalazzoとVenetianのホテルに収まらなくて、新たにちょっと離れたMirageも借りることに。また、今年から登録制の運用が厳格になったので、飛び込みは行列に並ぶ必要がありました。

Day 0

Game Day

お題は去年のUnicorn企業のシナリオを拡張したけど、テーマは今年の流行りを反映した内容。 運用コストやパフォーマンスが加味され、チーム感でスコアを競うんだけど今年は新たにボーナスステージが用意されたりして全体的にいろいろ最適化されてた。

80チーム以上で過去最大規模。また、奇しくもGame Day初回参加時に同じチームメンバーで友人となった人とチームに!

8時間の長丁場だったけど、もうこれだけで大変満足して後はもういいやって気分に。

ちなみにオンライン登録が間に合わなくて、当日飛び込み参加だけどなんとかギリギリ最後の一人では入れた。

Day 1

Tuesday Night Live with James Hamilton

AWS Vice PresidentのJames Hamilton氏によるデータセンターの詳細が話された。初公開情報が多く、パッションが強くて聞いてて飽きなかった。

新サービスもちょこっと。

Day 2

Keynote

Andy Jessy (AWS CEO)により発表。テーマは「Superpower

  • new EC2 instances (t2.xlarge, t2.2xlarge, r4, i3, c5, f1 (FPGA))
  • Elastic GPU’s for EC2
  • Amazon Lightsail
  • Amazon Athena
  • Amazon AI (Rekognition, Polly, Lex)
  • PostgreSQL for Amazon Aurora
  • AWS Greengrass
  • AWS Snowball Edge
  • AWS Snowmobile

Day 3

Werner Vogels

Werner Vogels (AWS CTO)による発表。テーマは「Transformation

  • AWS OpsWorks for Chef Automate
  • Amazon EC2 Systems Manager
  • AWS CodeBuild
  • AWS X-ray
  • AWS Personal Health Dashboard
  • AWS Sheild
  • Amazon Pinpoint
  • AWS Glue
  • AWS Batch
  • Blox
  • AWS Lambda for C#
  • AWS Lambda@Edge
  • AWS Step Functions

Lambdaのrubyかgo対応を期待してたのに、ちょっとがっかり。。

SVR 309 Wild Rydes Takes Off - The Dawn of a New Unicorn

サーバーレスをお題にしたワークショップ。コピペだけでちょっと面白みがなかった。。

詳しくはこちら

AWS re:Play

2016年EDMのベストDJに選ばれた若手のMartin Garrix。また金積んだな〜。

Day 4

のんびり

Beta Certification Tests

暇だったのでベータ試験を全部受けた。

  • Advanced Networking
  • Security
  • Big Data

途中、開始トラブルがあって、担当マネージャからテスト問題誕生秘話を聞いたり、クーポンもらったりした。

最後に

  • 今年も結局Sessionは参加せずにGame Dayやワークショップをやった。
  • Available Nowなサービスが多くて、最近のVaporware体質から脱却した模様。
  • ベータ試験受けたのは暇だったで何かチャレンジしたかったから!

ServerlessConf London 2016に参加してきた。

先日、ServerlessConf London 2016に参加したので、そのメモ(例によってtogetter風)。

少し前に話題の「サーバーレス」アーキテクチャに関するイベントであるServerlessConf Tokyo 2016をお手伝いしたことから、ロンドン版開催のお誘いが来たので行ってみました。

開催場所はetc.venuesという施設。

Day 1

Keynote by Patrick Debois

サーバーレスについてのカンファレンスなのに、キーノートでジャブ撃ちまくりが面白かった。

Sessions

iRobotやCloud Custodianを公開したCapitalOneのセッションも面白かったけど、一番は元ParseのエンジニアであるCharity Majorsのセッション(Serverlessness, NoOps and the Tooth Fairy)。サーバーレスを採用しても結局サービスの責任は自分で負わないといけない啓蒙的な話を様々な例を交えつつ、パッションを持って話してくれた。

  • Serverless architecture at iRobot by Ben Kehoe
  • Serverless for the Enterprise by Rafal Gancarz
  • Real-time Data Processing Using AWS Lambda by Ken Payne
  • Cloud Custodian, a serverless rules engine for the cloud by Kapil Thangavelu
  • The Serverless Cloud Integration Pattern by Lars Trieloff
  • Serverlessness, NoOps and the Tooth Fairy by Charity Majors
  • Serverless Operations by Ben Bridts

LT

LTという割にはミニセッションな感じ。日本のLTみたいな笑い所はあまりなし。最後の火星探査機もどきのやつは少し興味深かったけど。

  • Migrating to Serverless: MindMup 2.0 case study by Gojko Adzic
  • Serverless with Ansible by Ryan Brown
  • Cloud Detour - Automation Tool for testing Cloud Applications Resiliency by Sathiya Shunmugasundaram & Gnani Dathathreya
  • Mission to Mars: Exploring new worlds with AWS IoT by Jeroen Resoort

After Party

Day 2

Sessions

興味深かったのはnewrelic apm風なlambda専用の解析ツールIOPipeを作ってる人によるOps for NoOpsRealmのFounderによるServerless in an Offline-First world。どちらも自社プロダクトを紹介しつつも結構サーバーレスアーキテクチャでの設計やオペレーションを深掘りした話だった。他はZappaの作者によるGlobally Available Serverless Architecturesがテンポよく進んで見てて飽きなかった。

  • Ops for NoOps: Operational Challenges for Serverless Apps by Eric Windisch & Pam Selle
  • Serverless in an Offline-First world by Alexander Stigsen
  • The future of serverless by Paul Johnston
  • Getting the most out of the Serverless Framework by Florian Motlik
  • Serverless API in the Enterprise by Scott Patterson & Simon Coward
  • Globally Available Serverless Architectures by Rich Jones
  • Serverless Microservices with Google Cloud Functions by Bret McGowen

LT

MSの人達によるAzureの高速棒読み寸劇が笑えた。

  • Why PubNub moved Serverless Computing into the Network by Girish Dusane
  • DevOps for Serverless Applications by Yochay Kiriaty
  • Writing Serverless Plugins by Anna Doubkova
  • Serverless real-time aggregates by Christian Blunden
  • Big data processing simplified with Serverless Map Reduce by Sunil Mallya

感想

セッションもまあまあ面白かったりしたけど、Serverless Framework等各種ツールの作者と直接話したり、各国のエンジニアとサービス運用について細かく議論できるのが有意義でした。ヨーロッパ圏の人がメインだけど、意外とアメリカからも沢山来てて終始盛り上がりました。日本からはServerlessConf Tokyoの主催者のセクションナイン吉田さんもっぱらさん等が来英した他、クラスメソッドのベルリンリージョンにいる吉田さんがわざわざメソりに来てて、ノルマ大変そうだなぁと思ったり。

後、LTに関しては日本の方が絶対レベルが上だと思う!自社プロダクトの紹介とかやめて。。

Videos

ちなみにセッションの動画はここから見れます。

おまけ

カンファレンスの雰囲気。インタビューされちゃいました。

AWS GameDay Japan 2016を開催してきた

過去に何回も参加・開催両方を経験した事があるAWS GameDayに、またしても運営側として関わりました。

お題は基本的に去年のre:Inventでやったのを若干チューニングしたやつ。 詳細は今後また別のところで開催される可能性があるので、その時の参加者の為に伏せておきます。

当日

競技中はそれぞれのチームのスコアがリアルタイムで見れるダッシュボードがあって白熱した様子が伝わってました。

これはこれで楽しいんですが、何かが足りない感じ。。。






そう、グラフです!!






参加チームの白熱したバトル(順位の入れ替え等)を時系列で表示し、戦いの軌跡をビジュアライズするアレです。ISUCONでもよく見慣れたあの遷移するグラフがなかったのです!

開始してから気づきました。。

なので作りました

Graphing premiere

ダッシュボードの作りを見てみると jQueryReact.js を使っている模様。ならば、どこかでendpointを叩いてjsonを取得しているはずなので、いろいろ探したところ、それらしいのがありました。

1
2
3
4
5
6
7
8
9
10
11
12
13
  getScores: function() {
    $.ajax({
      url: 'https://xxxxxxxxx.execute-api.us-west-2.amazonaws.com/prod/scores',
      dataType: 'json',
      cache: false,
      success: function(data) {
        this.setState({data: data});
      }.bind(this),
      error: function(xhr, status, err) {
        console.error("scores", status, err.toString());
      }.bind(this)
    });
  },

こうなれば話は簡単で後は返却されるjsonを解析して、定期的にcallしてplotしていけば良いだけです。

データ送信

グラフを描画するサーバを用意するのはしんどいので、カスタムメトリックスが作成できる監視サービスを使いました。 最初はMackerelをと思ったけど、独自グラフを一般公開する設定がなさそうだったのでDatadogを採用。

Datadogでカスタムメトリックスを送るにはAPIを直接叩くよりは、StatsD経由で送信した方が楽なのでrubyでサクっと適当に記述。

gameday.rb
1
2
3
4
5
6
statsd.batch do |s|
  s.gauge('gameday2016', scores[0]['Profit']*100, :tags => ["team:" + scores[0]['Team']])
  s.gauge('gameday2016', scores[1]['Profit']*100, :tags => ["team:" + scores[1]['Team']])
  s.gauge('gameday2016', scores[2]['Profit']*100, :tags => ["team:" + scores[2]['Team']])
  # etc
end

グラフ描画

公開ダッシュボードを作成するには TimeBoard ではなく ScreenBoard を選択し、後は必要そうなグラフを追加していくだけ。

グラフ自体はGUIで作っても良いし、jsonで記述可能なので結構柔軟で素敵です。

profits.json
1
2
3
4
5
6
7
8
9
10
11
{
  "viz": "timeseries",
  "requests": [
    {
      "q": "avg:gameday2016{*} by {team}",
      "aggregator": "avg",
      "conditional_formats": [],
      "type": "line"
    }
  ]
}
ranking.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
  "viz": "toplist",
  "requests": [
    {
      "q": "top(avg:gameday2016{*} by {team}, 20, 'last', 'desc')",
      "style": {
        "palette": "dog_classic"
      },
      "conditional_formats": [
        {
          "palette": "white_on_green",
          "comparator": ">=",
          "value": 0
        },
        {
          "palette": "white_on_red",
          "comparator": "<",
          "value": 0
        }
      ]
    }
  ]
}

できあがったグラフはこんな感じ。 高負荷発生等のイベント時の対応との比較もできて見やすいと思います。

これで各チームのポジション等を伝えやすくなりました。

というわけで優勝したチーム初老丸、おめでとうございます!

終わりに

今回は競技の途中から実装しちゃったので次回は最初から用意しておきたいと思います。

(※)実は去年のISUCONの時も似たような事をやってましたね。。

DockerCon 2016に参加してきた

先日、DockerCon 2016に参加してきたので、そのメモ。

To DockerCon you go!

ちなみに行く事になった経緯は後日のJAWS-UGおコンテナ支部 #5でLTしてきました。

パッション大事!

以下、トゥギャッター風に。

Day 0

Pre-Registration & Welcome Party

場所はシアトルのWashington State Convention Center。何気にこの地に訪れるのは初。

Welcome to DockerCon 2016!

企業ブース

Welcome Party

Bar

Day 1

Highlights

  • Docker 1.12 (orchestration built-in)
    • swarm mode
    • serice api
    • cryptographic node identity
    • build-in routing mesh
  • Experimental Distributed Application Bundles (DAB)
  • Docker for AWS & Azure (beta)
  • Docker for Mac & Windows (public beta)

ぞろぞろ

Sessions

  • The Golden Ticket: Docker and High Security Microservices
  • Microservices + Events + Docker = A Perfect Trio
  • Thinking Inside the Container: A Continuous Delivery Story
  • Docker in Production, Look No Hands!

The Golden Ticket: Docker and High Security Microservices

Microservices + Events + Docker = A Perfect Trio

Thinking Inside the Container: A Continuous Delivery Story

DockerCon party @ Space Needle

最後はシアトルの名所(?)、Space Needleでの貸切パーティー

中でもワイワイ

Day 2

Highlights

  • Docker Datacenter
  • AWS Quickstart
  • Azure Marcketplace Templates
  • Docker Store (private beta)

個人的には、とある企業でのDockerのユースケースを演じる漫才が面白かった。

AzureのCTO

ADPのCTO

Sessions

  • Making Friendly Microservices
  • Sharding Containers: Make Go Apps Computer-Friendly Again
  • Closing General Session: Moby Dock’s Cool Hacks

Making Friendly Microservices

Sharding Containers: Make Go Apps Computer-Friendly Again

Closing General Session: Moby Dock’s Cool Hacks

Serverless Dockerが面白かった。

Docker on Drones

終わりに

世界各国のエンジニアと話せて、Dockerに対する熱量や動向を肌感覚で感じとれて非常に有意義な旅でした。以前のChefConfHashiConfもそうだけど、気になる技術やトレンドがあればTechカンファレンスに直接乗り込んで見るのが一番効率的なので、今後も継続してどこかに参加するようにしたいです。

Thank you!

TerraformでAPI Gatewway

つい先日、Terraformでずっと気になっていたAmazon API Gatewayのselection_patternpull requestがmergeされました。

今まではAPI GWをInfrastructure As Codeで構築するにあたって複数のintegration responseパターンを返却できないのがネックだったのが、これでようやく解決。途中までTerraformで作って、その後に以下のようにawscliで追加するというちょっと煩わしい手順でした。

1
2
3
REST_ID=$(aws apigateway get-rest-apis --query 'items[?name==`my_api`].id' --output text)
RESOURCE_ID=$(aws apigateway get-resources --rest-api-id $REST_ID --query 'items[?path==`/my_path`].id' --output text)
aws apigateway put-integration-response --rest-api-id $REST_ID --resource-id $RESOURCE_ID --http-method GET --status-code 400 --response-templates '{"application/json": "$input.path('$').errorMessage"}' --selection-pattern "[^0-9](.|\n)*" 

というわけで、早速実験。お題は以前紹介したElastic Beanstalk ssh用のAPI GWで。

Terraform version

まずは、masterにmergeされた開発版Terraformのビルド。やり方はこちら

1
2
$ terraform version
Terraform v0.6.16-dev - 5cd27c2

Terraform file

API GWのterraform化はこんな感じで。

(*) permissionはlambda作成後に許可

Terraform plan/apply

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
$ terraform plan
Refreshing Terraform state prior to plan...


The Terraform execution plan has been generated and is shown below.
Resources are shown in alphabetical order for quick scanning. Green resources
will be created (or destroyed and then created if an existing resource
exists), yellow resources are being changed in-place, and red resources
will be destroyed.

Note: You didn't specify an "-out" parameter to save this plan, so when
"apply" is called, Terraform can't guarantee this is what will execute.

+ aws_api_gateway_deployment.eb_deployment
    rest_api_id: "" => "${aws_api_gateway_rest_api.EB.id}"
    stage_name:  "" => "prod"

+ aws_api_gateway_integration.ip_get
    http_method:                        "" => "GET"
    integration_http_method:            "" => "POST"
    request_templates.#:                "" => "1"
    request_templates.application/json: "" => "{ \"env_name\": \"$input.params('env_name')\" }"
    resource_id:                        "" => "${aws_api_gateway_resource.ip.id}"
    rest_api_id:                        "" => "${aws_api_gateway_rest_api.EB.id}"
    type:                               "" => "AWS"
    uri:                                "" => "arn:aws:apigateway:ap-northeast-1:lambda:path/2015-03-31/functions/arn:aws:lambda:ap-northeast-1:123456789012:function:eb_ip/invocations"

+ aws_api_gateway_integration.server_num_get
    http_method:                        "" => "GET"
    integration_http_method:            "" => "POST"
    request_templates.#:                "" => "1"
    request_templates.application/json: "" => "{\n \"env_name\": \"$input.params('env_name')\",\n \"server_num\": \"$input.params('server_num')\" \n}"
    resource_id:                        "" => "${aws_api_gateway_resource.server_num.id}"
    rest_api_id:                        "" => "${aws_api_gateway_rest_api.EB.id}"
    type:                               "" => "AWS"
    uri:                                "" => "arn:aws:apigateway:ap-northeast-1:lambda:path/2015-03-31/functions/arn:aws:lambda:ap-northeast-1:123456789012:function:eb_ip/invocations"

+ aws_api_gateway_integration_response.ip_get_200
    http_method:                         "" => "GET"
    resource_id:                         "" => "${aws_api_gateway_resource.ip.id}"
    response_templates.#:                "" => "1"
    response_templates.application/json: "" => "$input.path('$')"
    rest_api_id:                         "" => "${aws_api_gateway_rest_api.EB.id}"
    status_code:                         "" => "200"

+ aws_api_gateway_integration_response.ip_get_400
    http_method:                         "" => "GET"
    resource_id:                         "" => "${aws_api_gateway_resource.ip.id}"
    response_templates.#:                "" => "1"
    response_templates.application/json: "" => "$input.path('$').errorMessage"
    rest_api_id:                         "" => "${aws_api_gateway_rest_api.EB.id}"
    selection_pattern:                   "" => "[^0-9](.|\n)*"
    status_code:                         "" => "400"

+ aws_api_gateway_integration_response.server_num_get_200
    http_method:                         "" => "GET"
    resource_id:                         "" => "${aws_api_gateway_resource.server_num.id}"
    response_templates.#:                "" => "1"
    response_templates.application/json: "" => "$input.path('$')"
    rest_api_id:                         "" => "${aws_api_gateway_rest_api.EB.id}"
    status_code:                         "" => "200"

+ aws_api_gateway_integration_response.server_num_get_400
    http_method:                         "" => "GET"
    resource_id:                         "" => "${aws_api_gateway_resource.server_num.id}"
    response_templates.#:                "" => "1"
    response_templates.application/json: "" => "$input.path('$').errorMessage"
    rest_api_id:                         "" => "${aws_api_gateway_rest_api.EB.id}"
    selection_pattern:                   "" => "[^0-9](.|\n)*"
    status_code:                         "" => "400"

+ aws_api_gateway_method.ip_get
    api_key_required: "" => "0"
    authorization:    "" => "NONE"
    http_method:      "" => "GET"
    resource_id:      "" => "${aws_api_gateway_resource.ip.id}"
    rest_api_id:      "" => "${aws_api_gateway_rest_api.EB.id}"

+ aws_api_gateway_method.server_num_get
    api_key_required: "" => "0"
    authorization:    "" => "NONE"
    http_method:      "" => "GET"
    api_key_required: "" => "0"
    authorization:    "" => "NONE"
    http_method:      "" => "GET"
    resource_id:      "" => "${aws_api_gateway_resource.server_num.id}"
    rest_api_id:      "" => "${aws_api_gateway_rest_api.EB.id}"

+ aws_api_gateway_method_response.ip_200
    http_method: "" => "GET"
    resource_id: "" => "${aws_api_gateway_resource.ip.id}"
    rest_api_id: "" => "${aws_api_gateway_rest_api.EB.id}"
    status_code: "" => "200"

+ aws_api_gateway_method_response.ip_400
    http_method: "" => "GET"
    resource_id: "" => "${aws_api_gateway_resource.ip.id}"
    rest_api_id: "" => "${aws_api_gateway_rest_api.EB.id}"
    status_code: "" => "400"

+ aws_api_gateway_method_response.server_num_200
    http_method: "" => "GET"
    resource_id: "" => "${aws_api_gateway_resource.server_num.id}"
    rest_api_id: "" => "${aws_api_gateway_rest_api.EB.id}"
    status_code: "" => "200"

+ aws_api_gateway_method_response.server_num_400
    http_method: "" => "GET"
    resource_id: "" => "${aws_api_gateway_resource.server_num.id}"
    rest_api_id: "" => "${aws_api_gateway_rest_api.EB.id}"
    status_code: "" => "400"

+ aws_api_gateway_resource.eb
    parent_id:   "" => "${aws_api_gateway_rest_api.EB.root_resource_id}"
    path:        "" => "<computed>"
    path_part:   "" => "eb"
    rest_api_id: "" => "${aws_api_gateway_rest_api.EB.id}"

+ aws_api_gateway_resource.env_name
    parent_id:   "" => "${aws_api_gateway_resource.eb.id}"
    path:        "" => "<computed>"
    path_part:   "" => "{env_name}"
    rest_api_id: "" => "${aws_api_gateway_rest_api.EB.id}"

+ aws_api_gateway_resource.ip
    parent_id:   "" => "${aws_api_gateway_resource.env_name.id}"
    path:        "" => "<computed>"
    path_part:   "" => "ip"
    rest_api_id: "" => "${aws_api_gateway_rest_api.EB.id}"

+ aws_api_gateway_resource.server_num
    parent_id:   "" => "${aws_api_gateway_resource.ip.id}"
    path:        "" => "<computed>"
    path_part:   "" => "{server_num}"
    rest_api_id: "" => "${aws_api_gateway_rest_api.EB.id}"

+ aws_api_gateway_rest_api.EB
    description:      "" => "get EB info"
    name:             "" => "EB"
    root_resource_id: "" => "<computed>"


Plan: 18 to add, 0 to change, 0 to destroy.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
$ terraform apply
aws_api_gateway_rest_api.EB: Creating...
  description:      "" => "get EB info"
  name:             "" => "EB"
  root_resource_id: "" => "<computed>"
aws_api_gateway_rest_api.EB: Creation complete
aws_api_gateway_resource.eb: Creating...
  parent_id:   "" => "k9x3d7qlhd"
  path:        "" => "<computed>"
  path_part:   "" => "eb"
  rest_api_id: "" => "mdsyn3w42a"
aws_api_gateway_resource.eb: Creation complete
aws_api_gateway_resource.env_name: Creating...
  parent_id:   "" => "nr2lkm"
  path:        "" => "<computed>"
  path_part:   "" => "{env_name}"
  rest_api_id: "" => "mdsyn3w42a"
aws_api_gateway_resource.env_name: Creation complete
aws_api_gateway_resource.ip: Creating...
  parent_id:   "" => "g29h7n"
  path:        "" => "<computed>"
  path_part:   "" => "ip"
  rest_api_id: "" => "mdsyn3w42a"
aws_api_gateway_resource.ip: Creation complete
aws_api_gateway_resource.server_num: Creating...
  parent_id:   "" => "sthj28"
  path:        "" => "<computed>"
  path_part:   "" => "{server_num}"
  rest_api_id: "" => "mdsyn3w42a"
aws_api_gateway_method.ip_get: Creating...
  api_key_required: "" => "0"
  authorization:    "" => "NONE"
  http_method:      "" => "GET"
  resource_id:      "" => "sthj28"
  rest_api_id:      "" => "mdsyn3w42a"
aws_api_gateway_method.ip_get: Creation complete
aws_api_gateway_method_response.ip_200: Creating...
  http_method: "" => "GET"
  resource_id: "" => "sthj28"
  rest_api_id: "" => "mdsyn3w42a"
  status_code: "" => "200"
aws_api_gateway_integration.ip_get: Creating...
  http_method:                        "" => "GET"
  integration_http_method:            "" => "POST"
  request_templates.#:                "" => "1"
  request_templates.application/json: "" => "{ \"env_name\": \"$input.params('env_name')\" }"
  resource_id:                        "" => "sthj28"
  rest_api_id:                        "" => "mdsyn3w42a"
  type:                               "" => "AWS"
  uri:                                "" => "arn:aws:apigateway:ap-northeast-1:lambda:path/2015-03-31/functions/arn:aws:lambda:ap-northeast-1:123456789012:function:eb_ip/invocations"
aws_api_gateway_resource.server_num: Creation complete
aws_api_gateway_method.server_num_get: Creating...
  api_key_required: "" => "0"
  authorization:    "" => "NONE"
  http_method:      "" => "GET"
  resource_id:      "" => "9w68fs"
  rest_api_id:      "" => "mdsyn3w42a"
aws_api_gateway_method_response.ip_200: Creation complete
aws_api_gateway_method_response.ip_400: Creating...
  http_method: "" => "GET"
  resource_id: "" => "sthj28"
  rest_api_id: "" => "mdsyn3w42a"
  status_code: "" => "400"
aws_api_gateway_integration_response.ip_get_200: Creating...
  http_method:                         "" => "GET"
  resource_id:                         "" => "sthj28"
  response_templates.#:                "" => "1"
  response_templates.application/json: "" => "$input.path('$')"
  rest_api_id:                         "" => "mdsyn3w42a"
  status_code:                         "" => "200"
aws_api_gateway_integration.ip_get: Creation complete
aws_api_gateway_method.server_num_get: Creation complete
aws_api_gateway_method_response.server_num_200: Creating...
  http_method: "" => "GET"
  resource_id: "" => "9w68fs"
  rest_api_id: "" => "mdsyn3w42a"
  status_code: "" => "200"
aws_api_gateway_integration.server_num_get: Creating...
  http_method:                        "" => "GET"
  integration_http_method:            "" => "POST"
  request_templates.#:                "" => "1"
  request_templates.application/json: "" => "{\n \"env_name\": \"$input.params('env_name')\",\n \"server_num\": \"$input.params('server_num')\" \n}"
  resource_id:                        "" => "9w68fs"
  rest_api_id:                        "" => "mdsyn3w42a"
  type:                               "" => "AWS"
  uri:                                "" => "arn:aws:apigateway:ap-northeast-1:lambda:path/2015-03-31/functions/arn:aws:lambda:ap-northeast-1:123456789012:function:eb_ip/invocations"
aws_api_gateway_integration_response.ip_get_200: Creation complete
aws_api_gateway_method_response.ip_400: Creation complete
aws_api_gateway_integration_response.ip_get_400: Creating...
  http_method:                         "" => "GET"
  resource_id:                         "" => "sthj28"
  response_templates.#:                "" => "1"
  response_templates.application/json: "" => "$input.path('$').errorMessage"
  rest_api_id:                         "" => "mdsyn3w42a"
  selection_pattern:                   "" => "[^0-9](.|\n)*"
  status_code:                         "" => "400"
aws_api_gateway_integration.server_num_get: Creation complete
aws_api_gateway_method_response.server_num_200: Creation complete
aws_api_gateway_method_response.server_num_400: Creating...
  http_method: "" => "GET"
  resource_id: "" => "9w68fs"
  rest_api_id: "" => "mdsyn3w42a"
  status_code: "" => "400"
aws_api_gateway_method_response.server_num_400: Creation complete
aws_api_gateway_integration_response.ip_get_400: Creation complete
aws_api_gateway_integration_response.server_num_get_200: Creating...
  http_method:                         "" => "GET"
  resource_id:                         "" => "9w68fs"
  response_templates.#:                "" => "1"
  response_templates.application/json: "" => "$input.path('$')"
  rest_api_id:                         "" => "mdsyn3w42a"
  status_code:                         "" => "200"
aws_api_gateway_integration_response.server_num_get_200: Creation complete
aws_api_gateway_integration_response.server_num_get_400: Creating...
  http_method:                         "" => "GET"
  resource_id:                         "" => "9w68fs"
  response_templates.#:                "" => "1"
  response_templates.application/json: "" => "$input.path('$').errorMessage"
  rest_api_id:                         "" => "mdsyn3w42a"
  selection_pattern:                   "" => "[^0-9](.|\n)*"
  status_code:                         "" => "400"
aws_api_gateway_integration_response.server_num_get_400: Creation complete
aws_api_gateway_deployment.eb_deployment: Creating...
  rest_api_id: "" => "mdsyn3w42a"
  stage_name:  "" => "prod"
aws_api_gateway_deployment.eb_deployment: Creation complete

Apply complete! Resources: 18 added, 0 changed, 0 destroyed.

The state of your infrastructure has been saved to the path
below. This state is required to modify and destroy your
infrastructure, so keep it safe. To inspect the complete state
use the `terraform show` command.

State path: terraform.tfstate

できた!

考察

  • 依存関係

リソースを作成するのに並列処理が出来なかったり、依存関係がまだうまく対応できてないので、depends_onを駆使する必要があるのが若干まだ面倒。ない場合は、BadRequestException: Unable to complete operation due to concurrent modification. Please try again laterBadRequestException: No integration defined for method status code: 400等のエラーが発生する。

  • Integration/Method Response Headers

CORS等の設定するする際にはAccess-Control-Allow-Origin等のヘッダーをMethodやIntegrationのResponse Headerに設定をする必要があるけど、.の扱い問題で未対応(#2143)。それまではawscliで以下のようにすると事で回避。

1
aws apigateway update-integration-response --rest-api-id $rest_id --resource-id $appo_resource_id --http-method OPTIONS --status-code 200 --patch-operations op=add,path="/responseParameters/method.response.header.Access-Control-Allow-Headers",value="\"'Content-Type,X-Amz-Date,Authorization,X-Api-Key, Access-Control-Allow-Origin, x-amz-security-token'\""

Issueはこの前上げたので(#6092)、ウォッチしておくと良い。

  • Infrastructure as Code

API Gatewayはresource, method, integration, method responseintegration response等を記述しないといけないので、どうしてもコードが多くになってしまう事からSwaggerでやった方が楽だったりするかも。ただ、その場合はInfrastructure as YAMLになってしまうけど。。また、YAMLは整形してからimportする必要があったりするので、その辺は諸々トレードオフかなぁ。

Terraformで特定のブランチを使う

HashiCorpのTerraformの開発は非常に活発であり日々進化していますが、リリースまでに待てない実装中や実験的な機能を使いたい場合は、自分で特定のブランチを変更・ビルドして使えます。

リポジトリ取得

1
2
3
$ go get github.com/hashicorp/terraform
$ cd $GOPATH/src/github.com/hashicorp/terraform
$ git checkout new-aws-feature

commit hash付与

どのコミットリビジョンを使っているか簡単に分かるように、versionにgitのcommit hashを指定。

1
sed -i -e "s/dev/dev - `git rev-parse --short HEAD`/" terraform/version.go

ビルド

make devすると全pluginやproviderがコンパイルされ、非常に時間がかかる(特に最近は対応範囲の広がりが著しく、生成されるバイナリの総容量が増加傾向)。 よって、特定のproviderを指定して時間短縮する。以下はaws providerで作業している場合。

1
2
3
4
5
6
7
$ make core-dev plugin-dev PLUGIN=provider-aws
==> Checking that code complies with gofmt requirements...
/Users/ijin/golang/bin/stringer
go generate $(go list ./... | grep -v /vendor/)
go install github.com/hashicorp/terraform
go install github.com/hashicorp/terraform/builtin/bins/provider-aws
mv /Users/ijin/golang/bin/provider-aws /Users/ijin/golang/bin/terraform-provider-aws

確認

1
2
$ terraform version
Terraform v0.6.15-dev - 123abcd

Happy Terraforming!

LambdaでSSHやGitを使ってみよう

AWS Lambda上でsshgitを使えたら便利!思ったけど、そもそもバイナリ自体が入ってない上にパッケージのインストールが出来ないので回避方法を悩んでいたところ、幸いそれぞれPython nativeの実装があったので先人の肩に乗っかる事で事無きを得ました。

SSH

SSHはParamikoというライブラリを利用。s3上にSSE-KMSで暗号化されたキーファイルをダウンロードし、それを使ってサーバと認証しログイン。

注意点

  • Lambdaの実行環境にはデフォルトでParamikoが入ってないので、deployment packageに含める必要がある
  • Mac上ではpythonライブラリの互換性が衝突する為、Linux(EC2等)上でpackageを作成する必要がある
  • 最近発表されたVPC対応を使ってprivate networkで通信をしたい場合、外部への経路はそのままでは不可なので、s3の場合はVPC endpointを作成するかNATが必要になってくる。詳しくはこの記事を。

Git

Gitはdulwichというライブラリを利用。sshプロトコルの場合、デフォルトではシステム上のsshが利用されるのでPython版のParamikoSSHVendorクラスを使えばすんなりいくと思いきや、キーファイル指定が出来なかったのでそこを少し改変。また、Lambda上での特殊な環境の為かsys.stderr のencode周りがうまく検出されなかったので、dulwich.porcelain.push methodも若干修正。

Paramikoを使っているので、上記同様パッケージ作成はLinux上で。

以下はprivate repositoryをclone後、別branchに新規ファイルを追加後にcommitし、githubへpushする例。

それでは、良いLambdaライフを!

Lambdaの容量を監視しよう

2016/1/14現在、AWS Lambdaにはなんとリージョン毎!にアップロードできるパッケージの合計サイズがたったの1.5GBという悲しい制限があります。特にlibraryを同包したり、versioningを使ったりしてCIをガンガン回してると、結構すぐこの上限に達してしまいがちです。そこで、Lambdaの総容量はAWSコンソール上には表示されるものの、トラッキングし辛いので監視する仕組みを作ってみました。

仕組み

LambdaのScheduled Eventsを使って、ListFunctionsListVersionsByFunction APIを叩いて、個別functionのCodeSizeをサマって、PutMetricDataでCloudWatchに投げて、Alarm設定してるだけ。

(*) 2016/1/26 追記:@marcy_teruiさんからのご指摘でVersionsの容量計算が抜けてました。ありがとうございます。

といっても、今後別アカウントでいちいち設定(IAM role&policy、Lambda、SNS、CloudWatch)するのも非常に面倒くさいので、今回はCloudFormation Designerを使って、ほぼ一発で環境を再現できるようにしました。

CloudFormation

ボタン作ってみた。

Stack Creation

Designerではこんな感じ。心なしか、jsonの苦痛が多少楽になったような。。後、Propertyの補完機能は良いけどショートカットがCmd+SpaceなのでSpotlightさんがぁ。

s3からtemplateを指定。 template urlはhttps://s3-ap-northeast-1.amazonaws.com/ijin/aws/lambda/check_lambda_capacity/check_lambda_capacity.template

Parameterとしては以下が指定可能

  • アラート閾値(Byte単位)
  • SNS topic(空の場合は、自動作成される)

Stackを作成すると、諸々のリソースが数分で出来上がり。

Manual Labor

本当は以上で終了!にしたいところですが、LambdaのScheduled Eventsの設定はAWSコンソールからのみしか出来ないという情けない残念な状態2016/1/14現在)なので、ここからポチポチ設定作業。。(API重視の開発姿勢はどこ行ったんだろう)

(*) 2016/1/26 追記:この記事の翌日に発表された CloudWatch Eventsscheduling 機能 によって出来るようになりました。なんというタイミング。

最小頻度が5分毎

Graph

これで、グラフが取れて閾値を超えたらアラートが飛ぶようになる

Code

出来上がったCloudFormation templateコードはこちら。AWS::Lambda::Functionがlambdaのresource担当だけど、templateにfunctionをインラインで埋め込めるのは現時点ではnodejsのみなので仕方なくzipしたpythonコードをs3にアップして参照するようにしてる。

元気があれば、そのうちnode版も書こうかな。。

GitHubのリポジトリはこちらから

バリ島のHubudでコワークしてきた

東京で借りているマンションのエレベータが総入れ替えで1週間完全停止する事になったので、ふらっとインドネシアのバリ島に来てみました。 仕事柄リモートワークがしやすいで、ついでにバリ島のウブドというエリアにある噂のコワーキングスペース「Hubud」で作業してみることに。

場所

インドネシアのバリ島は日本からやや西にほぼ南下しただけなので、時差は1時間程度。


山岳エリアにあるウブドという地域。



そこにあるMonkey Forestというジャングルの近く。猿がそこら中に。

Hubud

HUB in UBUDなのでHubudという名称。

$60〜の月額プランを支払う事によって、館内で使える時間数が決まる。平日は24時間アクセス可能で、土日は22時ぐらいに閉店。(2015年12月現在)

今回は$60(+$3の処理手数料)を事前でPaypalで払って、25時間プランに加入。

ちなみに、bitcoinでも支払い可能。

中にはbitcoin自販機も!

館内には24時間いつでも入れるので時間のトラッキングはどうなってるかというと、恐らく登録時にノートのwifiを設定したので、そのmac addressで識別していると推定。

内装

リラックスした感じでアットホームな雰囲気。

フリーデスクで館内でも中庭でも作業可能。

お気に入りの作業場所。但し、夜になると蚊が出てくるので蚊よけスプレー必須。

2階にはゆったりソファも。

free coffee!

通信環境

宿のwifiや現地のキャリアで買えるSIMカードによる通信品質はそれほど良くなく、時間と場所によってはパケットロスが大量に発生。

しかし、Hubudに引いている回線は帯域が太く、レイテンシーもほとんど感じられずに超快適!ストレスなく通信ができるので日本にいるのとさほど変わらないぐらいの感触。

まとめ

たまには作業場所を替えて仕事して見るのも一興です。異なる環境に身を置くことによって、いつもとは違った感じの集中力が発揮でき、短時間で没頭してproductivityが向上する場面がいくつかありました。(昼間の熱い時間はプールに入って涼んで、夕方から夜まで作業するスタイルが結構効率的でした)また、いろんな国やバックグラウンドの人がいるので会話してみると良い刺激にもなるので是非一度体験してみてはいかがでしょうか。