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

【CNDT2020】Amebaアフィリエイト基盤の GKEアーキテクチャと マイクロサービス

youta ogino
September 08, 2020

【CNDT2020】Amebaアフィリエイト基盤の GKEアーキテクチャと マイクロサービス

Cloud Native Days 2020での登壇資料
https://event.cloudnativedays.jp/cndt2020/talks/59

youta ogino

September 08, 2020
Tweet

More Decks by youta ogino

Other Decks in Technology

Transcript

  1. Amebaアフィリエイト基盤の
    GKEアーキテクチャと
    マイクロサービス

    View Slide

  2. 小沢周平
    CyberAgent, Inc.
    技術本部サービスリライアビリティグループ
    インフラエンジニア
    現在はAmebaの新認証基盤のEKS周りの運用を主に
    しています。今後はコスト可視化/負債可視化業を
    やっていき。

    View Slide

  3. 1. アーキテクチャの紹介
    2. GKEのオートプロビジョニングとスケールアップ
    3. gRPCのバランシングのEnvoyの設定
    4. リクエストを取りこぼさないためにやったこと
    5. DatadogAPMとOpenCensusでのモニタリング
    後半:アプリやマイクロサービスについて

    View Slide

  4. View Slide

  5. 1. アーキテクチャの紹介
    2. GKEのオートプロビジョニングとスケールアップ
    3. gRPCのバランシングのEnvoyの設定
    4. リクエストを取りこぼさないためにやったこと
    5. DatadogAPMとOpencensusでのモニタリング
    後半:アプリやマイクロサービスについて

    View Slide

  6. Backend Services:簡易版 Overview
    Services(Pods):gRPC
    GKE Ingress:HTTP
    MySQL
    Storage
    外部ASP
    ユーザ
    Kubernetes

    View Slide

  7. Kubernatesを選んだ理由
    即応性: マイクロサービス毎に応答時間に信頼性のあるサービス提供
    弾力性:負荷に応じたマイクロサービス毎のスケーリング
    耐障害性: マイクロサービス毎にデータストアを持ちサービス提供
    メッセージ駆動:Pub/Subでの非同期メッセージングパターン通信
    即応性
    Responcive
    メッセージ駆動
    Message Driven
    弾力性
    Elastic
    耐障害性
    Responcive
    社内にk8sを運用している人
    も多く知見もありました

    View Slide

  8. GKEを選んだ理由
    Kubernatesの運用が初めてだったので、一番運用がしやすいGKEを選択
    KubernatesクラスターはGoogleが管理
    node groupもマネージド管理
    クラスタアップグレードが容易
    Podをdrainしてサージアップグレードをしてくれる
    ノードの自動プロビジョニング
    ノードプールのスペックを最適なものにしてくれる
    ノードの自動修復
    修復対象のノードが検出されるとdrainして、再作成をしてくれる
    その他にもKubernatesの運用を楽にする機能の有効化が簡単
    EKSを構築した時にGKE
    がいい感じにしてくれて
    いた事に気が付きました

    View Slide

  9. 1. アーキテクチャの紹介
    2. GKEのオートプロビジョニングとスケールアップ
    3. gRPCのバランシングのEnvoyの設定
    4. リクエストを取りこぼさないためにやったこと
    5. DatadogAPMとOpencensusでのモニタリング
    後半:アプリやマイクロサービスについて

    View Slide

  10. Ⅰ GKEのオートプロビジョニングとスケールアップ
    負荷に応じてノードとPodのオートスケールをさせています
    ノードの自動プロビジョニング
    必要なリソースに合わせてノードプール単位でスケーリングしてくれます
    HPA
    人気ブログなどでアクセスのスパ
    イクが予想されるときは予めス
    ケールさせます
    spec:
    scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: grpcserver
    minReplicas: 3
    maxReplicas: 40
    metrics:
    - type: Resource
    resource:
    name: cpu
    targetAverageUtilization: 80
    ここではCPU使用率が閾値を
    超えたらPodがスケールする
    ように設定しています。

    View Slide

  11. Ⅰ HPAでスケールしたPodにリクエストが送られない
    負荷試験時にIngress→NodePort→Podだと負荷が偏ってしまった
    NEGsを有効にしてIngressから直接Podを見れるようにすることでHPAで
    スケールしたPodに対しても偏りなく負荷分散させることができた。
    annotations:
    cloud.google.com/neg: '{ "ingress": true }'
    有効化もannotationを追加するだけ

    View Slide

  12. 1. アーキテクチャの紹介
    2. GKEのオートプロビジョニングとスケールアップ
    3. gRPCのバランシングのEnvoyの設定
    4. リクエストを取りこぼさないためにやったこと
    5. DatadogAPMとOpencensusでのモニタリング
    後半:アプリやマイクロサービスについて

    View Slide

  13. gRPCのバランシングのEnvoyの設定
    gRPCのバランシングのためにEnvoyをSidecarで導入しました。
    サービスディスカバリはHeadless Services
    名前解決のレイテンシを改善する為にNodeLocalDNSCacheを有効化
    control planeを導
    入してCanary
    Releaseなど出来る
    ようにしたいお気持
    ちはあります
    Envoy
      アプリA
    Envoy
    アプリB
    Inbound Inbound
    Outbound Outbound

    View Slide

  14. 可用性を高めるEnvoyの機能
    Circuit Breaking
    過剰なリクエストが来た時に応答不可を防ぐ
    Outlier Detection
    Podへの500系や200系の回数をみてクラスタから取り除くか制御
    Health Check
    アプリ側でHTTPのエンドポイントをはやしてLiveness/Readiness Prove
    gRPCのHealthCheckはSidecarのEnvoyからのみ叩くようにした

    View Slide

  15. アクセスログの設定
    access_log:
    - name: envoy.access_loggers.file
    typed_config:
    "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
    path: "/dev/stdout"
    typed_json_format:
    start_time: "%START_TIME%"
    method: "%REQ(:METHOD)%"
    path: "%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%"
    protocol: "%PROTOCOL%"
    response_code: "%RESPONSE_CODE%"
    response_flags: "%RESPONSE_FLAGS%"
    bytes_rcvd: "%BYTES_RECEIVED%"
    bytes_snt: "%BYTES_SENT%"
    duration: "%DURATION%"
    x-envoy-upstream-svc-time: "%RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)%"
    x-forwarded-for: "%REQ(X-FORWARDED-FOR)%"
    useragent: "%REQ(USER-AGENT)%"
    x-request-id: "%REQ(X-REQUEST-ID)%"
    backend_address: "%UPSTREAM_HOST%"
    client: "%DOWNSTREAM_REMOTE_ADDRESS%"
    referer: "%REQ(REFERER)%"
    response_duration: "%RESPONSE_DURATION%"
    upstream_transport_failure_reason: "%UPSTREAM_TRANSPORT_FAILURE_REASON%"
    アプリのログの他にも、
    Envoyでアクセスログを取得
    しています
    %RESPONSE_FRAGS%で
    RESPONSEの情報をみるぐ
    らいに使っています

    View Slide

  16. Envoyのメトリクス
    EnvoyのメトリクスでgRPC等
    のstatusなどもとれます。
    実際はDatadogAPMで取得し
    たメトリクスを主に使用して
    いますが、Envoyでのメトリ
    クスで監視設定している部分
    もあるので、取得していま
    す。
    template:
    metadata:
    annotations:
    ad.datadoghq.com/envoy.check_names: '["envoy"]'
    ad.datadoghq.com/envoy.init_configs: '[{}]'
    ad.datadoghq.com/envoy.instances: |
    [
    {
    "stats_url": "http://%%host%%:8001/stats"
    }
    ]

    View Slide

  17. 共通のEnvoyイメージの用意
    共通で使える設定ファイルを内包したEnvoyイメージを使用
    YAMLのアンカーとエイリアスで記述量を減らすようにしています。
    ゆくゆくはサービス
    の規模感や体制に合
    わせたcontrol
    planeを導入したい
    です
    type: STRICT_DNS
    lb_policy: ROUND_ROBIN
    connect_timeout: 0.25s
    ignore_health_on_host_removal: true
    http2_protocol_options: {}
    health_checks: *egress_health_checks
    outlier_detection: *outlier_detection
    circuit_breakers: *circuit_breakers
    マイクロサービスが増え
    た際はclustersに書き足
    していく運用です。
    Envoyをv1.15.0にアッ
    プグレードして、data
    plane apiの設定をv3に
    対応してあります。

    View Slide

  18. 実際に運用して困ったこと
    Pod数が増えるとヘルスチェックのgRPCアクセスが劇的に増えて、
    API監視でUNKNOWNのアラートを発報してしまった。
    http_filters:
    - name: envoy.filters.http.health_check
    typed_config:
    "@type":
    type.googleapis.com/envoy.extensions.filters.http.health_che
    ck.v3.HealthCheck
    pass_through_mode: false
    cluster_min_healthy_percentages:
    self-grpc:
    value: 100
    headers:
    - name: ":path"
    exact_match: /healthz
    pass_through_mode: falseにし
    てヘルスチェックの状態を保持
    し返すようにしました。
    no_traffic_intervalを60sにし大
    量のヘルスチェックを飛ばさな
    いようにしています。

    View Slide

  19. 1. アーキテクチャの紹介
    2. GKEクラスタを本番運用にするためにやった工夫
    3. GKEのオートプロビジョニングとスケールアップ
    4. gRPCのバランシングのEnvoyの設定
    5. リクエストを取りこぼさないためにやったこと
    後半:DatadogAPMとOpencensusでのモニタリング

    View Slide

  20. Ⅰ リクエストを取りこぼさないためにやったこと①
    アプリのGraceful Shutdown
    // Stop datadog exporter cleanly
    defer a.datadog.Stop()
    go func() {
    _ = a.server.Serve(listenPort)
    }()
    // Wait for signal
    sigCh := make(chan os.Signal, 1)
    signal.Notify(
    sigCh,
    syscall.SIGHUP,
    syscall.SIGINT,
    syscall.SIGTERM,
    syscall.SIGQUIT)
    log.Printf("SIGNAL %d received, then shutting down...\n", <-sigCh)
    a.server.GracefulStop()
    gRPCのGraceful Shutdownを実
    装しました。
    GracefulStop()を使っています。
    またDatadog Exporterにメトリ
    クスを投げているためdeferで止
    めています。
    terminationGracePeriodSecon
    dsも指定しています。

    View Slide

  21. リクエストを取りこぼさないためにやったこと②
    SidecarのEnvoyの起動順番
    1. ignore_health_on_host_removalをtrueにして、
    HealthCheckの失敗を待たずにサービスディスカバリから除外
    2. アプリの起動前にEnvoyを起動
    3. Envoyが終了する前にアプリを終了
    #!/bin/sh
    wget -qO- --post-data='' http://127.0.0.1:8001/healthcheck/fail
    sleep 30
    curl -s -o /dev/null -w '%{http_code}' http://localhost:8001/ready
    シェル芸でいい感じにしています

    View Slide

  22. 定期的なクラスタアップグレードや機能追加
    定期的・継続的にアップグレードや機能追加をすることで
    システムの価値を維持・向上し続けることができます。
    そのためのリクエストを取りこぼさない工夫でした。
    GKEは3つのマイナーバージョンをサポート
    EnvoyのDataplane APIのLifecycleは最大3つのメジャーバージョン
    開発スピードが早い

    View Slide

  23. アーキテクチャの紹介
    GKEのオートプロビジョニングとスケールアップ
    gRPCのバランシングのEnvoyの設定
    リクエストを取りこぼさないためにやったこと
    DatadogAPMとOpencensusでのモニタリング
    後半:アプリやマイクロサービスについて

    View Slide

  24. Kubernatesの監視体制
    外形監視: Datadog Synthetic
    内形監視: Datadog
    DeploymentのDesired/Available, PodのRestart回数
    GSLBと各APIのレイテンシやStatusの監視,
    NodeのStatus,ノードのDisk使用量etc
    ログ監視: Stackdriver Logging
    トレース/APM: DatadogAPM/OpenCensus
    通知先: Slack/メーリングリスト
    ドキュメント管理: esa
    Datadogでの監視の知見が多いの
    で使用していますDashboardを
    なるべく統一したいのもあります

    View Slide

  25. DatadogAPMとOpenCensusでのモニタリング
    APM(Application Performance Monitoring)でアプリの稼働状況を監視
    - DatadogのDashboardに出力したいので設定はAPMの有効化ぐらい
    - エンドポイント毎にLATENCYとERROR RATEが表示できる
    # Enable APM and Distributed Tracing
    apmEnabled: true
    ◆Datadogはhelmfileで管理しています
    エンドポイント名

    View Slide

  26. DatadogAPMとOpenCensusでのモニタリング
    OpenCensusでTrace,Stats情報を取得
    Datadog Exporterへ送信
    - StatsはPrometheus Exporterへ変更を対応中
    - Agentのリソース負担を減らすため
    - DogStatsD経由でメトリクスを送らないため
    - OpenTelemetryに移行させたい
    当初DatadogAPMを有効化するか、自前
    でJaegerをたてるか悩み、DatadogAPM
    のライブラリを使わずにOpencensusで取
    得している背景があります

    View Slide

  27. OpenCensusでのStats/Tracingでとっているもの
    gRPC Server/Client
    - StatsHandlerにocgrpcのhandlerを設定するだけ
    HTTP Server
    MySQLドライバー
    - driverNameの設定時にocsqlでwrapするだけ
    他にどんなものをとっ
    ているか教えて下さい
    # Server
    grpc.StatsHandler(&ocgrpc.ServerHandler{})
    # Client
    grpc.WithStatsHandler(&ocgrpc.ClientHandler{})
    return &http.Server{
    Addr: host + port,
    Handler: &ochttp.Handler{
    Handler: mux,
    },
    }, listenPort
    ochttp.Handlerでhttp.Handlerを
    wrapするだけ
    ochttpにはHTTPヘッダやStatusを取
    得してくれる機能があるので、
    stats/viewなどがなくても表示可能
    // OpenCensus
    driverName, err := ocsql.Register("mysql")

    View Slide

  28. 次はアプリ側
    のおはなし

    View Slide

  29. 荻野陽太
    CyberAgent, Inc.
    Ameba事業本部 AmebaPickDiv
    サーバサイドエンジニア
    新卒2年目
    Ameba Pickの開発をしています

    View Slide

  30. 1. マイクロサービスを採用した理由
    2. マイクロサービスの設計
    3. システム構成について
    4. マイクロサービス間でのエラーハンドリング
    5. マイクロサービスのデプロイ

    View Slide

  31. 1. マイクロサービスを採用した理由
    2. マイクロサービスの設計
    3. システム構成について
    4. マイクロサービス間でのエラーハンドリング
    5. マイクロサービスのデプロイ

    View Slide

  32. Ⅰ 開発初期のチーム状況
    サーバーチームのメンバー: 8人
    クラウドネイティブを使った開発経験があった人:2人
    ほとんどのメンバーがクラウドネイティブを使った開発経験がなかった!
    手探りで開発を進めて
    いきました

    View Slide

  33. Ⅰ マイクロサービス採用した理由
    システム要件を洗い出したらモノリスだと辛そうだったから
    必要なシステム要件
    ● アフィリエイト広告の計測
    ● 成果報酬の支払い
    ● 成果通知の受け取り
    ● アフィリエイト案件の管理
    ● 報酬レポート作成
    ● アフェリエイターの管理
    社内に採用事例があ
    り知見もあったので
    採用しやすかったで

    外部のシステム
    との連携が必要

    View Slide

  34. 1. マイクロサービスを採用した理由
    2. マイクロサービスの設計
    3. システム構成について
    4. マイクロサービス間でのエラーハンドリング
    5. マイクロサービスのデプロイ

    View Slide

  35. マイクロサービスの設計 ①
    コンテナオーケストレーションにはKubernatesを採用
    ● デファクトスタンダードなので採用しない理由がなかった
    マイクロサービス 間の通信にはgRPCを採用
    ● HTTP/2を使うので低レイテンシでマイクロサービス間の通信が行える
    ● Protoocol Buffersを使ったIDLでクライアント・サーバー間でスキーマを共有
    できる
    ● IDLからクライアント・サーバーコードの自動生成ができる
    ● エコシステムが充実している

    View Slide

  36. マイクロサービスの設計 ②
    アーキテクチャにはレイヤードアーキテクチャを採用
    開発手法にはDDDを採用(しようとした)
    ● 人員追加や要件追加があり途中からおざなりになった
    DDDの境界付けられたコンテキストによってマイクロサービスを分割
    開発言語にはGolangを採用
    ● Kotlinという案もあったが、社内での導入事例や人員採用のしやすさを踏まえ
    て採用
    ● Golangを採用したことで結果的にかなりインフラコストを削減できた

    View Slide

  37. 1. マイクロサービスを採用した理由
    2. マイクロサービスの設計
    3. システム構成について
    4. マイクロサービス間でのエラーハンドリング
    5. マイクロサービスのデプロイ

    View Slide

  38. Ⅰ バックエンドアーキテクチャ
    ユーザ
    Private Cloud
    ブログシステム
    Services(Pods):gRPC
    GKE Ingress:HTTP
    MySQL
    Storage
    外部
    ASP
    NAT
    Rest API
    gRPC喋れない

    View Slide

  39. Ⅰ システム構成
    ブロガー
    Private Cloud
    ブログシステム
    API Gateway
    Micro Service A
    Micro Service B
    Micro Service C
    Routing
    SSL終端・API認証
    Micro Service内でREST to
    gRPCの変換をしてます
    REST(JSON) REST(JSON)

    View Slide

  40. REST to RPCの仕組み
    grpc-gatewayを利用している
    ● .protoからREST to gRPCのリバースプロキシをするコードを自動生成してく
    れるもの
    ● https://github.com/grpc-ecosystem/grpc-gateway
    ● 自動生成されたコードが各マイクロサービスに取り込まれている
    rpc SearchItems (SearchItemsRequest) returns (SearchItemsResponse) {
    option (google.api.http) = {
    post: "/api/affiliate_item/search_items"
    body: "*"
    };
    }
    RESTとgRPCのマッピングをちゃんと考
    えると面倒なので以下のルールで統一
    ● POSTのみ
    ● パス名は/api/マイクロサービス
    名/mathod名のスネークケース

    View Slide

  41. Ⅰ 再掲: システム構成
    ブロガー
    Private Cloud
    ブログシステム
    REST(JSON)
    API Gateway
    Micro Service A
    Micro Service B
    Micro Service C
    Routing
    SSL終端・API認証
    当初はここにREST to
    gRPCの変換を持たせよう
    としていました
    REST(JSON)

    View Slide

  42. REST to RPCの仕組み
    当初はAPI-GatewayにREST to gRPCの変換機能を持たせようとしていた
    この方法だとマイクロサービスのデプロイの度にAPI-Gatewayのデプロイ
    が必要になり辛いのでやめた

    View Slide

  43. 1. マイクロサービスを採用した理由
    2. マイクロサービスの設計
    3. システム構成について
    4. マイクロサービス間でのエラーハンドリング
    5. マイクロサービスのデプロイ

    View Slide

  44. Ⅰ マイクロサービス間でのエラーハンドリング
    共通のエラーコードを定義して、エラーコードを使ってエラーハンドリン
    グをしている
    type ApplicationError struct {
    code string
    message string
    gRPCStatusCode codes.Code
    }
    var (
    UnknownError = newApplicationError("CM500003", "unknown error", codes.Internal)
    UnavailableError = newApplicationError("CM503001", "unavailable", codes.Unavailable)
    )
    共通のエラーコードの定義

    View Slide

  45. Ⅰ マイクロサービス間でのエラーの伝播①
    1. カスタムエラーを定義
    2. カスタムエラーをProtocol Buffersで定義
    3. エラー時アプリでカスタムエラーを返す
    message ApplicationError {
    string code = 1;
    string message = 2;
    }
    type ApplicationError struct {
    code string
    message string
    gRPCStatusCode codes.Code
    }
    var (
    UnknownError = newApplicationError("CM500003", "unknown error", codes.Internal)
    UnavailableError = newApplicationError("CM503001", "unavailable", codes.Unavailable)
    )
    - エラーメッセージ
    - 共通のエラーコード
    - エラーに対応するgRPC
    のステータスコード
    を定義

    View Slide

  46. Ⅰ マイクロサービス間でのエラーの伝播②
    4. Server InterceptorでError Detailsにカスタムエラーを突っ込む
    5.
    func ApplicationErrorUnaryServerInterceptor() grpc.UnaryServerInterceptor {
    return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler)
    (interface{}, error) {
    res, err := handler(ctx, req)
    if err == nil {
    return res, nil
    }
    ae, ok := err.(*ApplicationError)
    if !ok {
    ae = UnknownError.Wrap(err).(*ApplicationError)
    }
    st := status.New(ae.gRPCStatusCode, ae.Message())
    if st, _ := st.WithDetails(&commonpb.ApplicationError{
    Message: ae.message,
    Code: ae.code,
    }); st != nil {
    return nil, st.Err()
    }
    return nil, st.Err()
    }
    }
    エラーが起きたらエラーをProtoで
    シリアライズして詰めている
    エラーに対応するgRPCステータス
    コードを設定している

    View Slide

  47. Ⅰ マイクロサービス間でのエラーの伝播③
    5. Client InterceptorでError Detailsからカスタムエラーを取り出す
    func ApplicationErrorUnaryClientInterceptor() grpc.UnaryClientInterceptor {
    return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker
    grpc.UnaryInvoker, opts ...grpc.CallOption) error {
    err := invoker(ctx, method, req, reply, cc, opts...)
    if err == nil {
    return nil
    }
    st := status.Convert(err)
    for _, d := range st.Details() {
    switch t := d.(type) {
    case *commonpb.ApplicationError:
    return newApplicationError(t.GetCode(), t.GetMessage(), st.Code())
    }
    }
    //不明なエラー
    return UnknownError.Wrap(err)
    }
    }
    Error DetailからProtoでシリアラ
    イズされたエラーを取り出してデシ
    リアライズしている

    View Slide

  48. 1. マイクロサービスを採用した理由
    2. マイクロサービスの設計
    3. システム構成について
    4. マイクロサービス間でのエラーハンドリング
    5. マイクロサービスのデプロイ

    View Slide

  49. マイクロサービスのデプロイ
    ブランチモデルにはGithub-Flowを採用
    CIOpsを採用
    ● CI/CDに時間をかけたくなかった
    ● Manifestとアプリケーションコードを同一リポジトリで管理している
    ● CIにK8sの強い権限を渡たす必要があったり、Manifestの軽微な変更でもイ
    メージのビルドが走るなどの課題もある...
    将来的にはGitOps
    に移行していきたい
    と考えています

    View Slide

  50. CI/CDパイプライン
    GCR
    本番クラスタ
    ステージング
    クラスタ
    ステージング
    本番
    PRマージ 反映
    反映
    イメージ作成
    タグ追加
    gitタグ作成
    パラメータに作成したイメージのタ
    グ名が渡される

    View Slide

  51. Manifestの管理
    最初はKustomizeを使っていた...
    ● yaml差分を書くのが面倒臭い
    ● 使い回しがしづらい
    ● Command Lineからパラメータを渡せないのが辛い
    結局Helmを採用した
    ● Kustomizeに比べて学習コストが高いが一度Chartを作れば使いまわせる!
    ● Command Lineからパラメータを渡せる!

    View Slide

  52. Secretsの管理
    最初はkubesecを利用
    ● Secret の構造を保ったまま値だけ暗号化してくれるもの
    ● https://github.com/shyiko/kubesec
    現在はhelm-secretsを採用
    ● HelmのPlugin
    ● HelmのValuesファイルを暗号化してくれるもの
    ● デプロイも簡単! helm install が helm secrets installになるだけ
    ● https://github.com/zendesk/helm-secrets
    External Secretsを採用するのもアリだったかも...

    View Slide

  53. ご清聴ありがとう
    ございました

    View Slide

  54. CAではSRE・
    バックエンドエンジニ
    アを募集しています
    https://www.cyberagent.co.jp/careers/

    View Slide