Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Ridge - A framework like GAE/Go on AWS

Ridge - A framework like GAE/Go on AWS

FUJIWARA Shunichiro

March 01, 2017
Tweet

More Decks by FUJIWARA Shunichiro

Other Decks in Technology

Transcript

  1. Ridge A framework like GAE/Go on AWS 2017.03.01 golang.tokyo #4

    @fujiwara https://www.flickr.com/photos/jimgrant/2339167908
  2. Ridge is ͳʹ github.com/fujiwara/ridge API Gateway ɹ Lambda ɹ Go

    Λ࢖ͬͯ GAE/Go Έ͍ͨͳ΋ͷΛ࡞ΔϥΠϒϥϦ
  3. API Gateway Proxy Integration ͱ Lambda Λઃఆ͢Δͱ { "path": "/api/hello",

    "httpMethod": "GET", "headers": { GET /api/hello?name=Bob HTTP/1.1 "User-Agent": "Go-http-client/1.1", User-Agent: Go-http-client/1.1 => ... }, "queryStringParameters": { "name": "Bob" }, "body": null } API Gateway ͕͏͚ͨ೚ҙͷHTTPϦΫΤετ͕ Object ʹͳͬͯ Lambda ʹ
  4. Lambda ͕ฦͨ͠ Object ͕ API Gateway ʹΑͬͯ HTTPϨεϙϯεʹ { "statusCode":

    200, "headers": { HTTP/1.1 200 OK "Content-Type": "text/plain" => Content-Type: text/plain }, "body": "Hello Bob." Hello Bob. }
  5. Example package main type input struct { Name string `json:"name"`

    } type output struct { Message string `json:"message"` } func main() { apex.HandleFunc(func(event json.RawMessage, ctx *apex.Context) (interface{}, error) { var in input if err := json.Unmarshal(event, &in); err != nil { return nil, err } out := output{Message: "Hello " + in.Name} return out, nil }) }
  6. RidgeͰॻ͔ΕͨΞϓϦͷྫ func main() { var mux = http.NewServeMux() mux.HandleFunc("/hello", handleHello)

    ridge.Run(":8080", "/api", mux) } func handleHello(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/plain") fmt.Fprintf(w, "Hello %s\n", r.FormValue("name")) } ridge.Run() Ҏ֎͸ී௨ͷ net/http ΞϓϦέʔγϣϯ
  7. ridge.Run() ͕΍Δ͜ͱ API Gateway Proxy Integration ͱ net/http ͷ૬ޓม׵ apex.HandleFunc(func(event

    json.RawMessage, ctx *apex.Context) (interface{}, error) { r, _ := NewRequest(event) // API GW ͷ Object Λ *http.Request ʹ͢Δ w := NewResponseWriter() // RidgeͷResponseWriter mux.ServeHTTP(w, r) // ී௨ʹHTTP Handlerʹ౉ͯ͠ॲཧ return w.Response(), nil // API GW͕ཁٻ͢ΔObjectʹม׵ͯ͠ฦ͢ })
  8. -> http.Request ϔομͱΫΤϦύϥϝʔλΛ࡞Δ func (r Request) httpRequest() (*http.Request, error) {

    header := make(http.Header) for key, value := range r.Headers { header.Add(key, value) } host := header.Get("Host") header.Del("Host") v := make(url.Values) for key, value := range r.QueryStringParameters { v.Add(key, value) } uri := r.Path if len(r.QueryStringParameters) > 0 { uri = uri + "?" + v.Encode() }
  9. -> http.Request Body ͷ io.ReaderCloser Λจࣈྻ͔Β࡞Δ var contentLength int64 var

    b io.Reader if r.IsBase64Encoded { raw := make([]byte, len(r.Body)) n, err := base64.StdEncoding.Decode(raw, []byte(r.Body)) if err != nil { return nil, err } contentLength = int64(n) b = bytes.NewReader(raw[0:n]) } else { contentLength = int64(len(r.Body)) b = strings.NewReader(r.Body) } body := ioutil.NopCloser(b)
  10. -> http.Request req := http.Request{ Method: r.HTTPMethod, Proto: "HTTP/1.1", ProtoMajor:

    1, ProtoMinor: 1, Header: header, ContentLength: contentLength, Body: body, RemoteAddr: r.RequestContext.Identity["sourceIp"], Host: host, RequestURI: uri, URL: u, } return &req, nil }
  11. ResponseWriter -> http.ResponseWriter interfaceΛ࣮૷ type ResponseWriter struct { bytes.Buffer header

    http.Header statusCode int } func (w *ResponseWriter) Header() http.Header { return w.header } func (w *ResponseWriter) WriteHeader(code int) { w.statusCode = code }
  12. ResponseWriter -> func (w *ResponseWriter) Response() Response { h :=

    make(map[string]string, len(w.header)) for key := range w.header { h[key] = w.header.Get(key) } return Response{ StatusCode: w.statusCode, Headers: h, Body: w.String(), } }
  13. ridge.Run() ͕΍Δ͜ͱ Apex(Lambda)Ͱಈ͔͞ͳ͍৔߹͸ී௨ʹhttp.Serverىಈ if os.Getenv("APEX_FUNCTION_NAME") != "" { apex.HandleFunc(...) }

    else { // ApexͰ͸ͳ͍৔߹ m := http.NewServeMux() m.Handle(prefix+"/", http.StripPrefix(prefix, mux)) log.Println("starting up with local httpd", address) log.Fatal(http.ListenAndServe(address, m)) }
  14. Ridge ͷ͍͍ͱ͜Ζ ͘͝ී௨ͷ Go ͷ net/http ΞϓϦέʔγϣϯͰ͋Δ • ςετ͸ී௨ʹ httptest

    Ͱ • ϩʔΧϧͰಈ͔ͤ͹ curl / ϒϥ΢βͰಈ࡞֬ೝ • API GatewayͰͷੑೳʹ໰୊͕͋ͬͨ৔߹(ޙड़) ී௨ʹEC2Ͱಈ͔ͤΔ
  15. Ridge ͷ͍͍ͱ͜Ζ API Gateway + Lambda Ͱಈ͘ͷͰ • ϦΫΤετ਺ʹԠͯࣗ͡ಈͰεέʔϧ •

    ϦΫΤετ͕ͳ͚Ε͹ ! ͳ͠ • EC2ͷ؅ཧҰ੾ͳ͠ • σϓϩΠ΋ apex deploy Ұൃ
  16. RidgeͷੑೳධՁ RedisʹೖΕͨ༣ศ൪߸DB1 ΛҾ͍ͯJSONΛฦ͢API ڞ௨: ElastiCacheRedis (t2.micro) 1. ALB(https) + EC2(t2.micro)

    2. API Gateway(https) + Lambda (128MB) $ curl -s "https://***/api/postal_code?code=1000001" {"code":"1000001","addresses":[{"address":"౦ژ౎ઍ୅ా۠ઍ୅ా","yomi":"τ΢Ωϣ΢τνϤμΫνϤμ"}]} 1 KEN_ALL.csv
  17. 1. ALB + EC2 (t2.micro) Running 10s test @ https://***/api/postal_code?code=1000001

    4 threads and 30 connections Thread Stats Avg Stdev Max +/- Stdev Latency 10.55ms 4.22ms 222.76ms 96.48% Req/Sec 662.03 68.84 780.00 76.00% Latency Distribution 50% 9.89ms 75% 10.82ms 90% 12.28ms 99% 19.18ms 26402 requests in 10.02s, 7.68MB read Requests/sec: 2634.14 Transfer/sec: 784.46KB
  18. 2. API Gateway + Lambda (128MB) Running 10s test @

    https://***/api/postal_code?code=1000001 4 threads and 30 connections Thread Stats Avg Stdev Max +/- Stdev Latency 33.83ms 14.64ms 355.60ms 86.32% Req/Sec 190.01 27.50 260.00 70.50% Latency Distribution 50% 31.43ms 75% 37.48ms 90% 46.82ms 99% 81.43ms 7603 requests in 10.05s, 4.30MB read Requests/sec: 756.37 Transfer/sec: 438.01KB
  19. ൺֱͷͨΊͲͪΒ΋30ฒྻ ݶքੑೳΛܭଌ͍ͯ͠ͳ͍͜ͱʹ஫ҙ • EC2, Lambdaͱ΋ΞϓϦࣗମͷॲཧ͸ฏۉ 5msఔ౓ • API Gateway, Apex,

    Ridge ͰͷϨΠςϯγ͕߹ܭ20msఔ౓ (େ෦෼͕API Gateway) ฒྻ਺্͕͕Ε͹ req/sec ͸্͕Δ API Gateway͸1000req/sec2, Lambda 100ฒྻͷ੍ݶ͋Γ (্ݶ؇࿨ਃ੥Մೳ) 2 ͳͷͰϕϯνͷฒྻ਺Λ্͛੾Ε͍ͯͳ͍
  20. Ridge͕(ಛʹ)޲͍͍ͯΔ͜ͱ සൟʹΞΫηε͕ͳ͍ / ϨΠςϯγཁٻ͕γϏΞͰͳ͍ • webhookΛड͚Δ Slack bot • S3ͷrepo

    viewer github.com/fujiwara/ridge-s3viewer • αʔϏε͕ऴྃͨ͠ήʔϜͷࠂ஌API POSTΛड͚ͯJSONΛฦ͢ͷΛEC2ͳ͠Ͱ ΧϠοΫͰ͸ຊ൪APIͱͯ͠౤ೖ࣮੷͋Γ
  21. ϩάͷऔΓѻ͍ go-apexͰ͸࢓૊Έ্ STDOUT ʹ͸Կ΋ग़ྗͰ͖ͳ͍ (RidgeͰ͸ os.Stdout = os.Stderr ͯ͠໭͍ͯ͠Δ) STDERR

    ʹग़ྗͨ͠΋ͷ͸ CloudWatch Logs ʹه࿥͞ΕΔ apex logs -f Ͱ΋Ӿཡ(tail)Մೳ
  22. ϩάͷऔΓѻ͍ CloudWatch Logs ͸͍͍͓஋ஈ + औΓѻ͍͕ͭΒ͍ → Lambda ʹྲྀ͢͜ͱ͕Ͱ͖Δ {

    "awslogs": { "data": "H4sIAKmNfFgAA5VQTWvbQBS891cI0aNV7dtv+aYQNRRiWiy1l9iYlfVkBPpwp XXTNOS/98lOwRBy6B7eYWb2zcx7DjucJnfA4umI4TK8TYt0t8ryPL3LwkU4PPY4EpxcPYL b4XA3DqcjMbF7nOLWdWXl4rGpDhh5nPyuc01/EeZ+RNeRkjMwMYMYdPzw8T4tsrzYVhxLU Vd1KS1Ih3W5R+2U2guuoUTNaMV0Kqf92Bx9M/Sfm9bjOIXLh/D+bHlZvrsyJsdwezbOfmH vZ+1z2FTkLwQwrY3QVjHNjUgSKbVRUlhrGZdglALOleCCYCsSQzgwAZTBN3Ql7zoqDNJKp aRKwDJY/Lserc+LdF0Ea/x5IumXahmANgythKgq9zICQB1ZmySRFMrUNdRaKQx+UB0qtgx eL7Lpw5fF28DAjJBAP5nhijHgWnFpaaqEGaNnQicJIzer9fuBxXXgdfbt6/8n3vjb0+j8O TN84izopo2/adoWq+CKYTMRbPwKu2F8CvLmDxLKbbC6IdD9Dl6J7xPOxhd8Lr99+fAXpgd o/ZQCAAA=" } } ͳʹ͜ͷσʔλ !
  23. CloudWatch Logs → Lambda JSONจࣈྻΛgzipͯ͠base64ͨ͠΋ͷ͕ྲྀΕͯ͘Δ ! $ jq -r .awslogs.data

    | base64 --decode | gzip -dc | jq . { "messageType": "DATA_MESSAGE", "owner": "999999999999", "logGroup": "/aws/lambda/ridge-test_main", "logStream": "2017/01/16/[$LATEST]d2eb3fdfb4814aefbce6a55c3261be60", "subscriptionFilters": [ "LambdaStream_ridge-test_log" ], "logEvents": [ { "id": "33106673685062739944675438880241755122532346783978881031", "timestamp": 1484554591801, "message": "START RequestId: 1670e841-dbc4-11e6-8899-4357ff1f655e Version: $LATEST\n" }, { "id": "33106673685107341435072500126524826559077643506990841866", "timestamp": 1484554591803, "message": "REPORT RequestId: 1670e841-dbc4-11e6-8899-4357ff1f655e\t Duration: 1.20 ms\tBilled Duration: 100 ms \tMemory Size: 128 MB\tMax Memory Used: 18 MB\t\n" } ] }
  24. ridge.DecodeLogStream() Ͱόϥͤ·͢ apex.HandleFunc(func(event json.RawMessage, ctx *apex.Context) (interface{}, error) { logStream,

    _ := ridge.DecodeLogStream(event) for _, e := range logStream.LogEvents { /* e => { ID: "33106673685062739944675438880241755122532346783978881031", Timestamp: 1484554591801, Message: "START RequestId: 1670e841-dbc4-11e6-8899-4357ff1f655e Version: $LATEST\n" } */ } }) ల։ͨ͠΋ͷΛ Kinesis Streams, Firehose ͳͲʹ౤͛Δͷ͕Α͍ͷͰ͸
  25. ·ͱΊ API Gateway + Lambda Ͱ GAE/Go Έ͍ͨͳ3 ͜ͱ͕Ͱ͖Δ Ridge

    (github.com/fujiwara/ridge) Λ࡞Γ·ͨ͠ ༻్ʹΑͬͯ͸طʹ 3 ಉ͡ͱ͸͍͑·ͤΜ