kakakakakku blog

Weekly Tech Blog: Keep on Learning!

S3 の Static Website Hosting でドメイン間のリダイレクトを実現する

ドメイン間でリダイレクトをする必要があり,nginx で頑張るのもなぁ…と思って,もっとお手軽で運用も楽そうな方法を探していたら,以下の記事を発見して,S3 の機能で実現できそうだったので試してみた.記事では別のドメインにリダイレクトをする方法として紹介されているけど,別のサブドメインにリダイレクトをする方法としても使えた.

構成図

ザッと以下のようになる.

f:id:kakku22:20170829202837j:plain

リダイレクト前のドメイン名と S3 バケット名を一致させる

適当に S3 バケットを作ってしまうとダメで,リダイレクト前のドメイン名と S3 バケット名を一致させる必要がある.例として aaa.example.com から zzz.example.com にリダイレクトさせる場合は,S3 バケット名は aaa.example.com になる.S3 バケット名はユニークである必要があるため,既に取得されていたら無理だという点には注意する必要がある.記事にも注釈が入っていた.

Note: S3 bucket names must be globally unique. If the bucket name you need is already in use, this solution cannot be used.

なお,この制約は公式ドキュメントにも記載されていた.

リソースレコードセット名は、Amazon S3 バケット名と一致する必要があります。たとえば、Amazon S3 バケット名が [acme.example.com] である場合、このリソースレコードセット名も [acme.example.com] である必要があります。

docs.aws.amazon.com

リダイレクト設定をする

管理コンソール上の Static website hosting で「リクエストをリダイレクトする」にチェックを入れて,ドメインとプロトコルを設定すると,すぐにエンドポイントからリダイレクトが可能になる.静的ファイルをホスティングするのは今までも使ったことがあったけど,リダイレクトをできることは知らなくて,勉強になった.また「ログ記録」も設定しておくと,アクセス数を把握できるため,基本的には設定しておくべきだと思う.

f:id:kakku22:20170829202908p:plain

Route 53 で S3 にドメインを設定する

記事に書いてある通り,以下のポイントに注意して設定をする.

  • A レコードを選択する
  • Alias は Yes にする
  • ターゲットに表示される S3 website endpoints から作成済の S3 バケットを選択する
    • ちなみに S3 バケットを作ってから,10分間ほど待たないとターゲットに表示されなかったので,時間差があるかも…?
  • Routing Policy は Simple にする

f:id:kakku22:20170829202930p:plain

まとめ

  • Route 53 + S3 を使ってドメイン間のリダイレクトを実現できた
  • ドメイン名と S3 バケット名を一致させるところは注意する

July Tech Festa 2017 で「急成長するサービスを支える DevOps 戦略と組織変革へのアプローチ」という発表をしてきた

今日は July Tech Festa 2017 ( #JTF2017 ) で発表をしてきたので,さっそく参加レポートをまとめた.運営の皆さま,お疲れさまでした!

発表資料

感想 / 補足など

今回は「急成長するサービスを支える DevOps 戦略と組織変革へのアプローチ」というタイトルで発表をした.タイトルが良かったのかもしれないけど,会場はほぼ満席で嬉しかった(空席が目立つ会場もチラホラあったため).

発表内容としては大きく3点で,DevOps の話と組織変革の話をバランス良く混ぜることを意識した.特に「組織変革」の部分が重要で,組織課題が残っている状態でどんなに頑張ってもサービスは伸びないと思っている.「DevOps は文化である」ことはよく知られているのに,文化として根付かせるために変化を実践できている人は案外少ないのでは?

  • 急成長を支える ( ≒ 守る ) ための DevOps 戦略
  • 急成長を支える ( ≒ 攻める ) ための DevOps 戦略
  • DevOps 戦略に必須だった組織変革へのアプローチ

また,今回は発表が45分間と長く,参加者とシンクロしながら発表したいなと思っていたため,最初にアイスブレイクとして「ノンバーバルなコミュニケーション」の例として「うなずく練習」をしてもらったりもした.ミーティングの場でも重要なスキルなので,是非意識してもらえると良いのでは!

関連記事

モニタリング + AWS Well-Architected Framework の話は,以下に記事を公開している.

developers.cyberagent.co.jp

「Aurora 事例祭り」で話した Aurora 移行の詳細は,以下の記事を公開している.

kakakakakku.hatenablog.com

経営層を巻き込んだ話のインタビュー記事も公開している.

developers.cyberagent.co.jp

コミュニケーションに悩んでいる人には以下の記事が参考になるはず.

kakakakakku.hatenablog.com

関連書籍

発表資料の中で大量の本を紹介した.特に厳選すると以下かな!

「構成ドリフト」と「スノーフレークサーバ」の話は Infrastructure as Code 本に載っている.

Infrastructure as Code ―クラウドにおけるサーバ管理の原則とプラクティス

Infrastructure as Code ―クラウドにおけるサーバ管理の原則とプラクティス

組織変革なら Fearless Change をオススメする!質疑応答のときに Fearless Change を監訳された川口さんとお話できてすごく嬉しかった!(ただ質疑応答はテンパって変な回答になってしまった...あああw)

Fearless Change アジャイルに効く アイデアを組織に広めるための48のパターン

Fearless Change アジャイルに効く アイデアを組織に広めるための48のパターン

  • 作者: Mary Lynn Manns,Linda Rising,川口恭伸,木村卓央,高江洲睦,高橋一貴,中込大祐,安井力,山口鉄平,角征典
  • 出版社/メーカー: 丸善出版
  • 発売日: 2014/01/30
  • メディア: 単行本(ソフトカバー)
  • この商品を含むブログ (16件) を見る

第1章にまとまってる「ラストマイル」の話は必読だと思う.

ThoughtWorksアンソロジー ―アジャイルとオブジェクト指向によるソフトウェアイノベーション

ThoughtWorksアンソロジー ―アジャイルとオブジェクト指向によるソフトウェアイノベーション

  • 作者: ThoughtWorks Inc.,株式会社オージス総研オブジェクトの広場編集部
  • 出版社/メーカー: オライリージャパン
  • 発売日: 2008/12/27
  • メディア: 単行本(ソフトカバー)
  • 購入: 14人 クリック: 323回
  • この商品を含むブログ (81件) を見る

写真

f:id:kakku22:20170827140819j:plain

A00 : 「ITエンジニアリングの本質」を考える / @enakai00

ネットワークの話は難しい部分も多かったが,直面する課題に対して考え続けるという思想こそが,エンジニアリングの本質なんだろうなと感じることができ,素晴らしかった.特に資料の最後にある「技術的制約に対する洞察力」があるからこそ,現在の常識を壊すことができているんだと思う.

www.slideshare.net

B10 : Web サービスの信頼性を守るための取り組み / @rrreeeyyy

  • SRE : サイトの信頼性の全てに責任範囲を持つ
    • Software Engineering
      • 自動化スクリプト / ツール実装 / プロダクションコードも直す
    • Systems Engineering
      • 1回の作業で永続的な改善を生む作業
      • アーキテクチャ設計
    • Toil
      • 繰り返し行われる手動オペレーション
      • トイルを無くすことが重要
    • Overhead
      • 管理的な作業
      • 採用/ 評価 / トレーニング
  • プロジェクトごとに,1人 SRE がアサインされて,設計相談やレビューをする
  • アプリケーションの負荷テストを実施する
    • そもそも,エンジニアが負荷テストを実施している時点で,Toil なのでは?
  • ロードバランサの改善

日本語訳の SRE 本は買ってあるけど,まだちゃんと読めていなかったため,理論的な部分と実践的な部分の話が聞けて,個人的には1番面白かった.エラーバジェットも実践しているという点も興味深かった.また,定義上は Overhead にあたり新卒研修も,改めて理解を整理できるだろうし,担当する SRE 側にも大きなメリットがあるだろうなと感じた.

C20 : 若手エンジニアの私が MBA(経営学修士)を取りに行った話 / @nari_ex

  • 上司の薦めで,経営大学院に入学することになった
  • 組織行動 (OB) と 人的資源管理 (HRM)
  • 理論と実践は違う − フィードバックをもらったり,ストレングスファインダーを試して,自分を知る工夫をした

自己過信によって失敗が続き,自分を見つめ直す中で,マネジメントに興味が出て,上司の薦めで経営大学院に入学したという話だった.個人的には,話を聞いていて全体的に甘いなという印象を受けた.マネジメントに限った話ではないけど,理論武装しているだけではダメで,実践してこそ価値があると思っている.そもそもメンバーからの信頼が無ければマネジメントなんてできないため,まずはメンバーから信頼されるためにどうしたら良いか?を考えてみるのが良いのではないかなと感じた.あと結局のところ,ビジネスの成功や,メンバーの成長など,目に見える成果が出ていなければ,マネジメントは成功したとは言えない点も重要だと思う.MBA は非常に興味あるなー!

A40 : Kubernetes でクラスタリングして解決したことと新たに出てきた課題 / @koudaiii

  • 新規サービスの立ち上げを早くするために Kubernetes を導入した
  • rails c を実行するコンテナ,rake db:migrate を実行するコンテナなどもある
  • 80個以上もあったチェック項目が,簡単になった
  • Kubernetes によって,スケールも容易になった

僕のところだと ECS を使っているため,Kubernetes は全然詳しくないが,大規模な Kubernetes 導入事例として非常に面白かった.特にサービス数が 9 → 60 に増えているという伸びが,まさに Kubernetes 導入が成功だったことを示していると思う.Kubernetes のマスターも3台構成にして,SPOF を回避しているとのことだった.

まとめ

  • July Tech Festa 初参加だった(なぜ7月開催じゃないのに July なんだろう...w)
  • 発表できて良い経験になった
    • LT と違って45分間もあると幅広く話せるなーと感じられた

「みんなのGo言語」には現場で使える実践テクニックが本当に書いてあった

発売されたのは約1年前だけど,最近仕事で Golang を書いているので,実践的なテクニックを学ぶために今さらながら「みんなのGo言語」を読んだ.WEB+DB PRESS のようなカジュアルさのある本で,1日でサクッと読むことができた.読み終わった後に,学んだことを実際に試してみたりもして,とにかく即戦力のある1冊だと感じた.

みんなのGo言語【現場で使える実践テクニック】

みんなのGo言語【現場で使える実践テクニック】

golint -min_confidence

今までも golint は使っていたけど,各項目に Confidence というレベル設定があって -min_confidence オプションで閾値を決められるというのは知らなかった.golint の README にも書いてなかったけど,実際に golint.go を読んだら実装があり,デフォルトは 0.8 になっていた.もっと厳しくても良いのではないかなと思って,さっそく 0.6 に変更した.0.8 → 0.6 に変更したことによって追加で検知できたのは以下で,エラーメッセージの1文字目も小文字にするのがお作法なのかぁー,という気持ちだった.

error strings should not be capitalized or end with punctuation or a newline

golint -set_exit_status

デフォルトのまま golint を実行すると,コマンドの終了ステータスが 0 で返ってきてしまうため,今までは CircleCI で以下のようなワークアラウンド実装をしていた.

test:
  override:
    - test -z "$(golint $(glide novendor) | tee /dev/stderr)"

しかし -set_exit_status オプションを使うと,指摘が出た場合に 1 が返ると本書に書いてあった.よって,以下のように書き換えることができて非常に助かった.

test:
  override:
    - golint -set_exit_status $(glide novendor)

Makefile

最初は golint と go vet を circle.yml に直接書いていたけど,僕のチームに Golang のアドバイザーとして入ってもらっているメンバーに「Makefile で書きましょう」と教えてもらって,今は Makefile を使っている.まさに同じことが本書にも書かれていて,参考になった.なお make help の部分は本書で紹介されていた make2help は使ってなくて,以下の記事で紹介されていた方法を使っている.

postd.cc

ザッとこのような感じになり,CircleCI でも make lint を実行するだけで良いという状態になっている.

help:
    @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

setup: ## Setup some tools
    go get -u github.com/Masterminds/glide
    go get -u github.com/golang/lint/golint

lint: ## Run the Golint in all directories without /vendor
    go vet $$(glide novendor)
    golint -min_confidence 0.6 -set_exit_status $$(glide novendor)

goroutine の停止

goroutine をどのように停止するかという話があり,まさに似たような実装を書く予定があって,チャネルをクローズするイメージで考えていたので,Go 1.7 以降だと context を使うのが推奨であると知れたのは非常に良かった.

実際に本書に載っているコードを写経して動作確認をしてみて,これだけだとあまりチャネルをクローズする実装と変わらないと思ったけど,最後に context.WithTimeout を使えば,タイムアウトの考慮もできるようになるメリットがあるという紹介があった.

とは言え,本書だけだと記載が少なく概要レベルの紹介だったため,以下の記事を読んで,より理解を深められたような気がする.

ベンチマーク

テストの章では,ベンチマークの機能を知った.実際に写経をして試したりもした.

cat.go

package cat

import "bytes"

func cat(ss ...string) string {
    var r string
    for _, s := range ss {
        r += s
    }
    return r
}

func buf(ss ...string) string {
    var b bytes.Buffer
    for _, s := range ss {
        b.WriteString(s)
    }
    return b.String()
}

cat_test.go

package cat

import "testing"

func seed(n int) []string {
    s := make([]string, 0, n)
    for i := 0; i < n; i++ {
        s = append(s, "a")
    }
    return s
}

func bench(b *testing.B, n int, f func(...string) string) {
    b.ReportAllocs()
    for i := 0; i < b.N; i++ {
        f(seed(n)...)
    }
}

func BenchmarkConcatenate(b *testing.B) {
    benchCases := []struct {
        name string
        n    int
        f    func(...string) string
    }{
        {"Cat", 3, cat},
        {"Buf", 3, buf},
        {"Cat", 100, cat},
        {"Buf", 100, buf},
        {"Cat", 10000, cat},
        {"Buf", 10000, buf},
    }
    for _, c := range benchCases {
        b.Run(fmt.Sprintf("%s%d", c.name, c.n),
            func(b *testing.B) { bench(b, c.n, c.f) })
    }
}

サブベンチマークという機能を使って書くことで,テーブルドリブンに書けるのは良かった.以下のような結果となり,文字列結合をする要素数が多くなればなるほど bytes.Buffer を使った方がパフォーマンスが良いということを確認することができた.すぐに仕事に活かせる知見だった.

$ go test -bench .
BenchmarkConcatenate/Cat3-8            10000000          164 ns/op         54 B/op         3 allocs/op
BenchmarkConcatenate/Buf3-8            10000000          181 ns/op        163 B/op         3 allocs/op
BenchmarkConcatenate/Cat100-8            300000         5794 ns/op       7520 B/op       100 allocs/op
BenchmarkConcatenate/Buf100-8           1000000         1938 ns/op       2160 B/op         4 allocs/op
BenchmarkConcatenate/Cat10000-8             200      7414202 ns/op   53327801 B/op     10000 allocs/op
BenchmarkConcatenate/Buf10000-8           10000       188879 ns/op     211424 B/op        11 allocs/op
PASS

その他

Windows の考慮,リフレクションの話などは,一通り読んだけど,実際に使う場面がありそうなときに改めて読み直そうかなと思っている.CLI の話は前に Togoo を実装したときに学んだ内容が多く含まれていて,復習の意味も兼ねて知識を整理することができた.

読んでいて少し気になったこと

初版だからある程度はしょうがないとは思うけど,少し誤植が多いなとは思った.また,誤植ではないものの,文字間隔が広くなっているのが気になる行もあった.これは組版の問題だとは思うけども.誤植一覧は以下に公開されていた.

まとめ

  • 仕事でさっそく使えるような,実践的なテクニックが学べる1冊だった
  • 実装のテクニックだけではなく,OSS として公開されているパッケージが多く紹介されているのも良かった

関連記事

紹介されていた「プログラミング言語Go」も購入したので,さっそく読むぞ!

suzuken.hatenablog.jp

Golang でジョブのスケジューリング実行ができる JobRunner を試した

最近 Golang でジョブを非同期にスケジューリング実行するような仕組みを実装していて,要件に合っていた JobRunner を使った.特徴としては,様々なスケジューリングを定義できる点と,API を起動してプロセスを常駐させる点にある.

github.com

基本実装

最初に動く実装を載せておく.ザッと書いてみると,このようになる.5秒間隔で MyJob を実行していて,標準出力にログを吐いている.また Gin を起動してプロセスを常駐させている.さらに JobRunner のステータスを API から取得できるようにもしている.

package main

import (
    "fmt"
    "net/http"

    "github.com/bamzi/jobrunner"
    "github.com/gin-gonic/gin"
)

// MyJob ...
type MyJob struct {
}

func main() {
    jobrunner.Start()
    jobrunner.Schedule("@every 5s", MyJob{})

    gin.SetMode(gin.ReleaseMode)
    r := gin.Default()
    r.GET("/jobrunner/status", JobJSON)
    r.Run(":8080")
}

// JobJSON ...
func JobJSON(c *gin.Context) {
    c.JSON(http.StatusOK, jobrunner.StatusJson())
}

// Run ...
func (e MyJob) Run() {
    fmt.Println("Run MyJob!")
}

実行すると,以下のように5秒間隔でログが出力できる.

$ go run main.go
[JobRunner] 2017/08/13 - 11:10:00 Started...
Run MyJob!
Run MyJob!
Run MyJob!
Run MyJob!
Run MyJob!
Run MyJob!

ステータスは以下のように取得できる.

$ curl -s http://localhost:8080/jobrunner/status | jq .
{
  "jobrunner": [
    {
      "Id": 1,
      "JobRunner": {
        "Name": "MyJob",
        "Status": "IDLE",
        "Latency": "77.204µs"
      },
      "Next": "2017-08-13T11:15:14+09:00",
      "Prev": "2017-08-13T11:15:09+09:00"
    }
  ]
}

スケジューリング

JobRunner では,様々なスケジューリングを定義できる.内部的には gopkg.in/robfig/cron.v2 を利用しているため,cron 形式でスケジューリングを定義することができたり,シンプルに一定間隔で実行することもできる.例えば,以下のように定義することができる.

// Schedule : 5秒間隔で実行する
jobrunner.Schedule("@every 5s", MyJob{})

// Schedule : 1時間間隔で実行する
jobrunner.Schedule("@hourly", MyJob{})

// Schedule : 2分間隔で実行する (cron 形式)
jobrunner.Schedule("* */2 * * * *", MyJob{})

// Every : 10分間隔で実行する
jobrunner.Every(10*time.Minute, MyJob{})

// In : 10秒後に実行する
jobrunner.In(10*time.Second, MyJob{})

// Now : 即時実行する
jobrunner.Now(MyJob{})

詳しくは cron.v2 のドキュメントに載っている.

godoc.org

API

今回は Gin で実装をしたが,JobRunner では README に書かれている通り,特定の API に依存することはなく,好きなものを選択することができる.

  • Gin
  • Echo
  • Martini
  • Beego

などなど.

並列実行

README にあまり詳しく書かれていないが,JobRunner は並列実行をサポートしている.init.go を読んで,実際に動作確認をして,やっと理解できた.

func Start(v ...int) {
    MainCron = cron.New()

    if len(v) > 0 {
        if v[0] > 0 {
            workPermits = make(chan struct{}, v[0])
        } else {
            workPermits = make(chan struct{}, DEFAULT_JOB_POOL_SIZE)
        }
    }

    if len(v) > 1 {
        if v[1] > 0 {
            selfConcurrent = true
        } else {
            selfConcurrent = false
        }
    }

    MainCron.Start()

    fmt.Printf("%s[JobRunner] %v Started... %s \n",
        magenta, time.Now().Format("2006/01/02 - 15:04:05"), reset)

}

まず jobrunner.Start() の引数は int の可変長引数になっている.結局のところ,第二引数を指定しない限りは selfConcurrent = false となるため,並列実行はされないようになっていた.

  • 第一引数 : プールサイズ(デフォルト : 10)
  • 第二引数 : 並列実行数(デフォルト : 並列実行なし)

検証 : jobrunner.Start(10)

まず,ログ出力を拡張して,30秒待機するようにしてみた.

package main

import (
    "fmt"
    "net/http"
    "time"

    "github.com/bamzi/jobrunner"
    "github.com/gin-gonic/gin"
)

// MyJob ...
type MyJob struct {
}

func main() {
    jobrunner.Start(10)
    jobrunner.Schedule("@every 5s", MyJob{})

    gin.SetMode(gin.ReleaseMode)
    r := gin.Default()
    r.GET("/jobrunner/status", JobJSON)
    r.Run(":8080")
}

// JobJSON ...
func JobJSON(c *gin.Context) {
    c.JSON(http.StatusOK, jobrunner.StatusJson())
}

// Run ...
func (e MyJob) Run() {
    fmt.Println("[Start] Run MyJob!")
    time.Sleep(30 * time.Second)
    fmt.Println("[End] Run MyJob!")
}

この状態で jobrunner.Start(10) を実行すると,以下のようになった.30秒の待機が優先されて,並列実行はされなかった.

$ go run main.go
[Start] Run MyJob!
[End] Run MyJob!
[Start] Run MyJob!
[End] Run MyJob!
[Start] Run MyJob!
[End] Run MyJob!
[Start] Run MyJob!
[End] Run MyJob!

検証 : jobrunner.Start(10, 5)

次に jobrunner.Start(10, 5) と書いて,並列実行数を指定してみた.

func main() {
    jobrunner.Start(10, 5)
}

すると,並列実行となった.JobRunner を使うときには,理解しておくべきポイントだと思う.

$ go run main.go
[Start] Run MyJob!
[Start] Run MyJob!
[Start] Run MyJob!
[Start] Run MyJob!
[Start] Run MyJob!
[Start] Run MyJob!
[End] Run MyJob!
[Start] Run MyJob!
[Start] Run MyJob!
[End] Run MyJob!
[Start] Run MyJob!
[End] Run MyJob!

まとめ

  • JobRunner を使うと柔軟なスケジューリング定義でジョブを実行することができる
  • Gin や Echo など,任意の API を起動してプロセスを常駐させる仕組みになっている
  • 並列実行をする場合は jobrunner.Start() の引数を意識する

Lambda (Python) をローカル環境で実行できる python-lambda-local を試した

Lambda (Python) のローカル環境を整えるため,python-lambda-local を試した.python-lambda-local を使うと,Python コードを Lambda にデプロイすることなく動作確認ができるようになる.Apex を使っていれば apex invoke でお手軽に実行することもできるけど,やはり Lambda に依存せず,単独で実行したいという要件はある.Lambda のローカル環境は誰に聞いても困っている印象がある.

github.com

前提

インストールは pip を実行するだけで良い.また,今回紹介するディレクトリ構成は Apex で Lambda をデプロイする前提にしている.

$ pip install python-lambda-local

BluePrint : hello-world-python

まずは AWS に依存せず,Python ライブラリにも依存せず,単体で実行できるコードとして,BluePrint の hello-world-python を試した.ディレクトリ構成は以下のようになっている.

.
├── event.json
├── functions
│   └── myfunc
│       └── main.py
└── project.json

myfunc/main.py は以下のようなコードにした.シンプルにイベントを受けて,表示するだけの実装にしている.

import json

print('Loading function')

def lambda_handler(event, context):
    print("function = myfunc, value1 = " + event['key1'])
    print("function = myfunc, value2 = " + event['key2'])
    print("function = myfunc, value3 = " + event['key3'])

ここで python-lambda-local で実行すると以下のようになる.オプションとしては --function でハンドラを指定して,-t でタイムアウトを指定する.あとは Python コードとイベントを指定する.

$ python-lambda-local --function lambda_handler --timeout 5 functions/myfunc/main.py event.json
Loading function
[root - INFO - 2017-07-30 16:16:36,272] Event: {u'key3': u'value3', u'key2': u'value2', u'key1': u'value1'}
[root - INFO - 2017-07-30 16:16:36,273] START RequestId: 9e683e91-821f-4680-b8e2-bcf37c7eb44f
function = myfunc, value1 = value1
function = myfunc, value2 = value2
function = myfunc, value3 = value3
[root - INFO - 2017-07-30 16:16:36,273] END RequestId: 9e683e91-821f-4680-b8e2-bcf37c7eb44f
[root - INFO - 2017-07-30 16:16:36,274] RESULT:
None
[root - INFO - 2017-07-30 16:16:36,274] REPORT RequestId: 9e683e91-821f-4680-b8e2-bcf37c7eb44f    Duration: 0.38 ms

簡単に動いた!

BluePrint : s3-get-object-python

次は AWS に依存していて,Python ライブラリにも依存するコードとして,BluePrint の s3-get-object-python を試した.ディレクトリ構成は以下のようになっている.

.
├── event.json
├── functions
│   └── myfunc
│       ├── main.py
│       ├── requirements.txt
│       └── (ライブラリいろいろ)
└── project.json

myfunc/main.py は以下のようなコードにした.S3 Put のイベントを受けて,Put されたファイルの Content-Type を表示している.なお,S3 Put のイベントは量が多いため,割愛するが,公式ドキュメントにちゃんと載っている.

docs.aws.amazon.com

from __future__ import print_function

import json
import urllib
import boto3

print('Loading function')

s3 = boto3.client('s3')

def lambda_handler(event, context):

    # Get the object from the event and show its content type
    bucket = event['Records'][0]['s3']['bucket']['name']
    key = urllib.unquote_plus(event['Records'][0]['s3']['object']['key'].encode('utf8'))
    try:
        response = s3.get_object(Bucket=bucket, Key=key)
        print("CONTENT TYPE: " + response['ContentType'])
        return response['ContentType']
    except Exception as e:
        print(e)
        print('Error getting object {} from bucket {}. Make sure they exist and your bucket is in the same region as this function.'.format(key, bucket))
        raise e

ここで python-lambda-local で実行すると以下のようになる.

$ python-lambda-local --function lambda_handler --library functions/myfunc --timeout 5 functions/myfunc/main.py event.json

[root - INFO - 2017-07-30 23:14:18,203] START RequestId: 07669e19-37af-4640-ae65-859dadd758ed
[botocore.vendored.requests.packages.urllib3.connectionpool - INFO - 2017-07-30 23:14:18,214] Starting new HTTPS connection (1): s3-ap-northeast-1.amazonaws.com
CONTENT TYPE: text/plain
[root - INFO - 2017-07-30 23:14:18,721] END RequestId: 07669e19-37af-4640-ae65-859dadd758ed
[root - INFO - 2017-07-30 23:14:18,722] RESULT:
text/plain
[root - INFO - 2017-07-30 23:14:18,722] REPORT RequestId: 07669e19-37af-4640-ae65-859dadd758ed    Duration: 517.71 ms

問題なく動いた!

気を付けるポイント

python-lambda-local を使ってみて,数点気を付けるポイントがあった.

1. /lib ではなくルートディレクトリにライブラリをインストールする

公式ドキュメントにも書いてある通り,pip で Python ライブラリをインストールする場合,Lambda の場合はルートディレクトリに配置する必要がある.よって,python-lambda-local のドキュメントにあるような /lib ディレクトリを用意するのではなく,main.py と同じルートディレクトリにそのままインストールする必要がある.具体的には以下のようなコマンドになる.

$ pip install --requirement functions/myfunc/requirements.txt --target functions/myfunc

docs.aws.amazon.com

2. requirements.txtboto3 を明示する必要がある

次も公式ドキュメントに書いてある内容だが,Lambda にはデフォルトで Boto 3 がインストールされているため,わざわざ requirements.txt に書く必要がないようになっている.ただし,ローカル環境で実行する場合には必要なので,結果的に書く必要があった.今回は以下のファイルを用意した.

urllib
boto3

まとめ

  • Lambda (Python) のローカル環境を整えるために python-lambda-local を試してみた
  • 簡単に使えたし AWS サービスとの接続もできたため一般的なユースケースは網羅できそう
  • ライブラリのインストールディレクトリなど少し気を付けるポイントはある

関連記事

Apex を使って複数環境にデプロイする方法は前にまとめてある.Apex ユーザーには参考になる内容だと思う.

kakakakakku.hatenablog.com