bauer's diary

凡人の凡人による凡人のための備忘録

AWS LambdaでサーバーレスにEC2メンテナンスをslackに通知する 〜その2〜

登場人物おさらい

4は少し長くなりそうなので、この記事では1, 2, 3を行います。

  1. Lambdaを実行するIAMにアタッチするポリシー
  2. スケジュール実行に必要なCloudWatch Events設定
  3. SlackのIncoming Webhooks設定/Webhook URL取得
  4. Lambda Functionの作成・実装

尚、設定用のIAM Userは事前に作成済で、ログインした上で操作している前提とします。

1. Lambda実行用の独自ポリシー作成

ポリシー要件は下記の通りです。

  1. CloudWatch Logsへのログ出力
  2. VPC内で実行可能
  3. EC2メンテナンスの情報が取得可能

AWSコンソールにて "IAM" を選択し、ロールの設定画面を開きます。
f:id:kitakitabauer:20170405223658p:plain

ポリシーをアタッチするIAMロールは、Lambda関数を作成すると自動的に作成されるデフォルトの"lambda_basic_execution"にします。
もちろん先にLambda関数を作成せずに、同名・別名で新規作成してもかまいません。
f:id:kitakitabauer:20170405223703p:plain

ポリシーの作成は "ロール" の下の "ポリシー" → "ポリシーの作成" から行います。
f:id:kitakitabauer:20170405224030p:plain

既存のポリシーからコピーしたり、ジェネレータを使うやり方もありますが、
ここでは "独自のポリシーを作成" を選択します。
f:id:kitakitabauer:20170405224146p:plain

選択後表示された入力欄をそれぞれ埋めていきます。
ポリシー名:EC2FullAccess
説明:EC2 フルアクセス

ポリシードキュメントはこちらです。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Stmt14811776XXXXX",
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents",
                "ec2:*"
            ],
            "Resource": "arn:aws:logs:*:*:*"
        }
    ]
}

インスタンスの状態を確認するためには "ec2:DescribeInstanceStatus" が許可されている必要があります。
Lambda作成時にVPCを指定して作成されるロール "AWSLambdaVPCAccessExecutionRole" には上記が付かないため、別のロールにアタッチしています。

ここでは"ec2:*"としてしまっていますが、もちろん今回必要と思われる下記アクションを一つ一つ指定してもかまいません。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Stmt14811776XXXXX",
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents",
                "ec2:CreateNetworkInterface",
                "ec2:DescribeNetworkInterfaces",
                "ec2:DeleteNetworkInterface",
                "ec2:DescribeInstanceStatus"
            ],
            "Resource": "arn:aws:logs:*:*:*"
        }
    ]
}


作成したポリシーを先程のロールにアタッチしたら完了です。
f:id:kitakitabauer:20170405224607p:plain

2. CloudWatch Eventsにルールを作成

CloudWatch Eventとは、AWSのシステムイベントのほぼリアルタイムなストリームを、Lambda関数や、Amazon SNS のトピック、Amazon Kinesis Streamsに振り分けることが可能なサービスです。

f:id:kitakitabauer:20170405220302p:plain:w1200
Cron 式に下記を記述します。

0 1 ? * MON-FRI *

この設定により、1日1回、月〜金の10時ごろに実行されます。
グリニッジ標準時で記述するので、日本標準時(UTC+0900)だと +9時間となります。
f:id:kitakitabauer:20170405220510p:plain:w1200

実際は、次回作成するLambda関数を準備してから、CloudWatch Eventのターゲットに指定します。
関数作成後にこの作業を行うことだけ覚えておいてください。

3. SlackのIncoming Webhooks設定/Webhook URL取得

Incoming Webhooksとは、外部ソースからのメッセージをSlackに投稿するWebhookです。
設定・URL取得方法は下記がわかりやすかったので参考にしてください。
docs.hatenablog.jp

ここで取得したURLを、Lambda関数のslack通知ロジックに指定することになります。


今回はここまでです。
次回の記事はこちら。
kitakitabauer.hatenablog.com

AWS LambdaでサーバーレスにEC2メンテナンスをslackに通知する 〜その1〜

はじめに

直近の業務で下記案件に対応する機会がありました。

EC2メンテナンスイベントが、英語のメールで通知されるだけじゃ見落としがちなのでslackに通知したい

色々な方法が考えられますが、せっかくなので興味があったAWS Lambdaを使ってみました。

手順が多かったので、数回に分けて書いていきます。

Lambdaとは?

f:id:kitakitabauer:20161211105038p:plain
AWSのサービスの一つで、サーバーのプロビジョニングや管理なしでコードを実行することができるサービスです。
aws.typepad.com
サーバーレスというのはあくまで我々サービス利用側で、仕組み的には、必要に応じてコンピュートリソース(EC2インスタンス)を起動し管理されていますが、それを意識する必要はありません。

EC2メンテナンスイベントってなに?

docs.aws.amazon.com

AWS は、インスタンスの基盤となるホストコンピュータをメンテナンスする必要があるとき、インスタンスのメンテナンスを予定します。

とあるように、EC2では、まれにインスタンスの再起動が行われます。
これは前述したメールやマネジメントコンソールのEC2のトップページに通知されますが、
見落としがちなのと、普段からコンソールにログインするとは限らないので、今回slackに通知する運びとなりました。

要件

今回の要件をまとめると、下記2点になります。

  • EC2のメンテナンス通知は、AWSから英語メールが届くものの見落としがち
  • EC2ではオーバースペックなこともあり、Lambdaでサーバレスに通知してみたい

尚、今回はAWS内部でEventが走り、slackからOutgoingで呼び出しもないため、API Gatewayの準備は不要です。

具体的にはこの流れです。

  1. 1日1回のスケジュールイベントでLambda Functionを叩く
  2. メンテナンスイベントから対象のEC2インスタンスイベントを抜きだす
  3. slackに通知する

登場人物

  1. Lambdaを実行するIAMにアタッチするポリシー
  2. スケジュール実行に必要なCloudWatch Events設定
  3. SlackのIncoming Webhooks設定/Webhook URL取得
  4. Lambda Functionの作成・実装


今回はここまでです。

次回以降、これら登場人物の詳細を説明していきます。
次回の記事はこちら。
kitakitabauer.hatenablog.com

GoでSet型を実現する場合の選択肢

はじめに

Go言語には標準で Set 型がありません。
ここでは2通りの実現方法を検討してみます。

1. map ✕ structで実現する

mapのフィールドに空のstructを使ってsetを定義します。
structは何もフィールドを持たない場合、サイズは0になるのでコストがかかりません。

A struct{} takes up no space.

golang-nutsにて説明されています。

package main

import . "fmt"

func main() {
	list := []string{
		"test1",
		"test2",
		"test3",
	}

	set := make(map[string]struct{})
	for _, v := range list {
		set[v] = struct{}{}
	}

	Printf("%#v\n", set)
}
map[string]struct {}{
  "test1":struct {}{},
  "test2":struct {}{},
  "test3":struct {}{}
}


2. ライブラリを使う

golang-setというライブラリがいけてそうなので紹介します。
2017/02/03にもコミットがあり、わりと最近も更新されているようです。
github.com

GoDocはこちら


関数はかなり充実していました。

package main

import (
	. "fmt"

	"github.com/deckarep/golang-set"
)

func main() {
	s1 := mapset.NewSet()
	s1.Add("1")
	s1.Add("2")
	s1.Add(3)
	s1.Add(4)
	print(s1) // Set{4, "1", "2", 3}

	// slice → set
	slice := []interface{}{"1", "2", 3, "5"}
	s2 := mapset.NewSetFromSlice(slice)
	print(s2) // Set{"5", "1", "2", 3}

	// set同士の結合
	all := s1.Union(s2)
	print(all)                // Set{3, 4, "1", "2", "5"}
	print(all.Difference(s1)) // Set{"5"}

	// 要素数確認
	Println(s1.Cardinality())  // 4
	Println(s2.Cardinality())  // 4
	Println(all.Cardinality()) // 5

	// 存在チェック
	Println(s1.Contains(1))   // false
	Println(s1.Contains("1")) // true

	// 要素削除
	s1.Remove(4)
	print(s1) // Set{"1", "2", 3}

	// 要素全削除
	s1.Clear()
	print(s1) // Set{}
}

func print(s mapset.Set) {
	Printf("%#v\n", s)
}


まとめ

簡易的でよければ 1 で、
凝った使い方をしたければ 2 という選択がよさそうです。


おしまい。

GitHubの2段階認証におけるプロトコル最適解を考える

なに?

cloneしたときに選択したプロトコルが、ローカルGit設定の remote url にそのまま設定されるのですが、
そもそもプロトコルは何を選べばいいのか、ベストプラクティスは何かを検討します。

前提

現場では2段階認証が必須となっているので、前提項目とします。
2段階認証にしておけば、パスワードが盗まれても安全なので推奨します。

プロトコルの種類と速さ

まずプロトコルの種類は下記の3つです。

  1. git
  2. https
  3. ssh

そしてcloneの速さは下記の順番です。
git > https >> ssh

これだけ見れば "gitプロトコルでいいのでは?" となるのですが、そうもいかない事情があります。

git プロトコル

gitプロトコルは read-only のため、そのままではpushできません。
また、社内proxyを経由していて gitプロトコルで使う9418ポートがFWなどで制限されている場合は使えないので、その場合は他のプロトコルに変更する必要が出てきます。

尚、gitプロトコルは現在GitHubのヘルプ上では言及されていませんが、利用可能です。

https プロトコル

f:id:kitakitabauer:20170219015533p:plain
httpsプロトコルの場合、通常はpushのたびにユーザ名/PWによる認証が必要になりますが、2段階認証の場合、PWの代わりにGiHubの設定画面から発行するPersonal Access Tokenが必要となります。

Personal Access Tokenの発行手順は公式ヘルプにあるとおりです。
尚、credential helperを導入すればキャッシュしてくれますが、キャッシュが切れたら再度入力する必要があります。

ssh プロトコル

f:id:kitakitabauer:20170219015440p:plain
ssh は鍵認証です。
clone 後のメリットとして、push のときに ssh-agent や pageant が、パスフレーズの入力を代行してくれるので便利ですが、前述したとおり最も遅いプロトコルとなります。


開発上、sshプロトコルが楽ですが、速さも求めたい。
そんなときどうすればよいのでしょうか。

結論

下記手順を踏むことによって、
cloneは最速のgitプロトコルで clone し、
pushではsshプロトコルによってパスフレーズを求められることなくpushできます。


指定したurlに対して他のプロトコルを使うようにgitconfigに定義します。

[url "git@github.com:"]
  pushInsteadOf = git://github.com/
  pushInsteadOf = https://github.com/
[url "git://github.com/"]
  insteadOf = https://github.com/

この設定によって、"https:" や "git:" を使っていても git push のときには ssh 経由になります。

git fetch や git pull の時は "https:" の代わりに "git:" を使用します。
尚、git configでは"pushInsteadOf"の部分が一つずつしか設定できないため、直接ファイルに追記しました。

また、ssh/configに下記設定を追加すれば、sshのcloneが高速化するので、
間違ってsshでcloneしたときのために設定しておくとよいかと思います。

Host github.com
  Compression yes
  Ciphers arcfour128,arcfour,aes128-cbc,3des-cbc,blowfish-cbc,cast128-cbc,aes192-cbc,aes256-cbc

Ciphersで利用する暗号方式を指定します。左から順に利用を試みます。

検証

それでは設定を検証してみます。

まずgitプロトコルでcloneします。

% git clone git://github.com/kitakitabauer/clone-sample.git
Cloning into 'clone-sample'...
remote: Counting objects: 51, done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 51 (delta 0), reused 0 (delta 0), pack-reused 48
Receiving objects: 100% (51/51), 10.82 KiB | 0 bytes/s, done.
Resolving deltas: 100% (6/6), done.

リモートURLを確認すると、"fetch" ではgitプロトコルが、
"push" ではsshプロトコルが設定されていることがわかります。

% git remote -v
origin	git://github.com/kitakitabauer/clone-sample.git (fetch)
origin	git@github.com:kitakitabauer/clone-sample.git (push)

問題なくpull/pushできます。

% git pull -v
Looking up github.com ... done.
Connecting to github.com (port 9418) ... 192.XX.XXX.XXX done.
From git://github.com/kitakitabauer/clone-sample
 = [up to date]      master     -> origin/master
Already up-to-date.

% git push -v
Pushing to git@github.com:kitakitabauer/clone-sample.git
To github.com:kitakitabauer/clone-sample.git
 = [up to date]      master -> master
updating local tracking ref 'refs/remotes/origin/master'
Everything up-to-date

リポジトリ内の .git/config にも反映されています。

[remote "origin"]
	url = https://kitahara-yuki@github.com/kitakitabauer/go-sample.git
	fetch = +refs/heads/*:refs/remotes/origin/*



おしまい

golang製のREPL内でUnixタイムスタンプから日付フォーマットを取得する

忘れがちだけど、よくある動作なので書き留めておきます。

準備

goreをインストールしておきます。
github.com

$ go get -u github.com/motemen/gore

"gore" とは、コード補完もしてくれるREPL(Read-eval-print loop)で、文字通り読んで評価して表示して繰り返すツールのことです。
仕組み的には、裏ではそれまでの入力を順番に実行するソースコードを生成して "go run" しています。

goreをより使いこなしたい場合は下記もインストールしておくとよいです。

# 入力補完
$ go get -u github.com/nsf/gocode
# プリティプリント
$ go get -u github.com/k0kubun/pp
# ドキュメント
$ go get -u golang.org/x/tools/cmd/godoc


実践

$ gore
gore > :import time

# 第一引数に日付フォーマット変換したいUnixタイムスタンプ
gore > t := time.Unix(1488697427, 0)
time.Time{sec:63624294227, nsec:0, loc:(*time.Location)(0x1070a0)}

gore > t.Format(time.RFC3339)
"2017-03-05T16:03:47+09:00"

フォーマットの種類は公式のformat.goのdocを参照してください。
https://golang.org/src/time/format.go

逆に、指定した日付のタイムスタンプを取得する場合

UTCでの、2016年1月1日0時00分の定点がほしい場合

$ gore
gore > :import time

gore > day := time.Date(2016, 1, 1, 0, 0, 0, 0, time.UTC)
time.Time{sec:63587203200, nsec:0, loc:(*time.Location)(nil)}

gore> day.String()
"2016-01-01 00:00:00 +0000 UTC"

gore> day.Unix()
1451606400

東京での、2016年1月1日0時00分の定点がほしい場合

# ロケーション名とUTCとの時差で、ロケーション構造体を作成
gore> jst := time.FixedZone("Asia/Tokyo", 9 * 60 * 60)
&time.Location{name:"Asia/Tokyo", zone:[]time.zone{time.zone{name:"Asia/Tokyo", offset:32400, isDST:false}}, tx:[]time.zoneTrans{time.zoneTrans{when:-9223372036854775808, index:0x0, isstd:false, isutc:false}}, cacheStart:-9223372036854775808, cacheEnd:9223372036854775807, cacheZone:(*time.zone)(0xc42000a3e0)}

gore> day := time.Date(2016, 1, 1, 0, 0, 0, 0, jst)
time.Time{sec:63587170800, nsec:0, loc:(*time.Location)(0xc42006e120)}

gore> day.String()
"2016-01-01 00:00:00 +0900 Asia/Tokyo"

gore> day.Unix()
1451574000


おしまい

「学力の経済学」は、今の日本の教育を考えさせられる良書だった

この本に出会った経緯

最近、テレビでも話題になっている慶應義塾大学の教育経済学者である、中室牧子さん著書の本を読みました。
投稿時点のAmazonの"経済学・経済事情"カテゴリで、ベストセラー1位になっている人気図書です。

「学力」の経済学

「学力」の経済学

以前、"今でしょ!"の林先生のTV番組に出演されていた方なのですが、
そこでは

  • 勉強をしたか確認している
  • 勉強を横について見ている
  • 勉強をする時間を決めて守らせている
  • 勉強するように言っている

のうち、どの方法が効果が一番あったかという中室さん自身が行った実験について説明していました。

母親・父親で結果が異なるのですが、
母親の場合「勉強をする時間を決めて守らせている」が一番効果的で、
父親の場合「勉強をしたか確認している」が一番効果的との結果が出ていました。

データは個人の経験に勝る

この本では、そういった子育てに携わる親なら非常に気になる話題についてメスを入れており、他にも

  • ご褒美で釣っては「いけない」
  • ほめ育てはしたほうが「よい」
  • ゲームをすると「暴力的になる」

といった世論が正しいかどうか、中室さんは様々なデータ・前例を元に解説していました。
結論と詳細は本に書かれているのですが、一貫して

経済学がデータを用いて明らかにしている教育や子育てに関する発見は、教育評論家や子育て専門家の指南やノウハウよりも、よっぽど価値がある

ことを定説しています。

因果関係と相関関係の違い

また、本書の中で何度も「因果関係」と「相対関係」を見極める事例をいくつも解説しており、非常に納得感が強い内容になっています。

  • 因果関係:Aという原因によってBという結果が生じた
  • 相関関係:AとBが同時に起こっている

例えば、読書をしている子どもは学力が高い、というメディアの報道に対して、
読書をしているから子どもの学力が高い(因果関係)のではなく、
学力の高い子どもが読書をしているのにすぎない(相関関係)可能性について言及していました。
他の要因も考えられる中で因果関係がはっきりしたデータがない限り、報道側の主観にすぎないということです。


子育てにおいて少しでも戸惑いを感じている方にとって、考え直すきっかけを与えてくれる良書になっているので、是非読んでみてください。

Jenkinsの全ジョブに対して条件でフィルタリングするワンライナー

掲題の通りです。
業務で棚卸しするときに必要だったので手順をまとめておきます。

コマンド中の "/var/lib/jenkins/jobs" は、
それぞれの環境に合わせてJenkinsのジョブ直下を指定してください。

disabled一覧

不要なジョブを整理するためにdisabledのフラグと共に出力しました。
そのままExcelスプレッドシートに貼り付けるために、ジョブ名とdisabledの間はタブで区切っています。

find /var/lib/jenkins/jobs/*/config.xml -print0 | xargs -0 grep -o "\<disabled\>.*" | sed -e 's/\/var\/lib\/jenkins\/jobs\///g' -e 's/\/config.xml:\<disabled>/\t/g' -e 's/<\/disabled>//g'

findに "-print0" オプション、xargsに "-0" オプションをつけることで、
ジョブ名にスペースが含まれていると別々なものとして認識してしまう問題を回避するため、
区切り文字をスペースから"\0"に変更しています。

※ disabled=trueだけ出力したい場合はこちら。

find /var/lib/jenkins/jobs/*/config.xml -print0 | xargs -0 grep "\<disabled>true" | sed -e 's/\/var\/lib\/jenkins\/jobs\///g' -e 's/\/config.xml:.*\<disabled>/\t/g'

displayName一覧

ジョブの表示名称も一緒に出力したいときに使いました。

find /var/lib/jenkins/jobs/*/config.xml -print0 | xargs -0 grep -o \<displayName\>.* | sed -e 's/\/var\/lib\/jenkins\/jobs\///g' -e 's/\/config.xml:<displayName>/\t/g' -e 's/<\/displayName>//g'

Linuxでpbcopy的な動きを実現する

ここからは少し余談になりますが、上記のような結果をクリップボードに入れたくなるのが常で、
Mac OS ではクリップボードにコピーするためにpbcopyができたのですが、Linuxではどのようにやるのでしょうか。

$ cat hoge.txt | pbcopy

Ubuntuの場合、"xsel" というものを使ってこの動きを模倣できます。
標準インストールされてないので、インストールします。

sudo apt-get install xsel

下記の通り実行すれば、クリップボードにcatの結果が保存されます。

cat hoge.txt | xsel --clipboard --input

毎回オプション付きで実行するのは辛いので、
.bashrcにエイリアスを定義しておきます。

alias pbcopy='xsel --clipboard --input'


以上です。素敵なJenkinsライフを!