Sansan Tech Blog

Sansanのものづくりを支えるメンバーの技術やデザイン、プロダクトマネジメントの情報を発信

KEDAを導入してAWS SQSキューとHTTPリクエストベースのスケーリングを実現する

こんにちは、年始に研究開発部ArchitectグループからSPEU(Strategic Products Engineering Unit) SREグループに異動したジャン(a.k.a jc)です。 本記事では、異動前に研究開発部Architectグループが開発・運用している、Circuitと呼ばれるKubernetes基盤の管理にイベント駆動のスケーリングを実現するために、KEDAを導入した際の概要を紹介します。

KEDA導入の背景

利用者から、契約書の自動データ化を行うR&Dサービスをプラットフォーム「Circuit」上に構築したいという要望がありました。 開発者の運用負荷・インフラ構築の負担を軽減したい背景です。R&Dサービスの要件は既存APIと異なり、常時待ち受けが不要のため、Amazon SQSキューのメッセージ数に応じてスケールする仕組みを構築することが求められました。具体的な要件は以下の通りです。

  • ゼロスケーリングが可能である
  • external metricを考慮したスケール可能であること

Knativeでの課題

一方、CircuitではKnativeを使ったHTTPリクエストベースのスケーリングとステージング環境のゼロスケーリングを実現していました。しかし、Knativeはactivator, autoscaler, controller, webhook, servingなど複数のコンポーネントを組み合わせて動作します。この複雑な構成により、いくつかの課題が浮かび上がりました。

  • ネットワーク構造の複雑化: debugとmetricsの収集が難しくなっており、Datadogのカスタムメトリクスを利用せざるを得ない状況だった
  • ルーティング設定の調整困難: KnativeがIstioのGatewayとVirtualServiceが自動作成するため、細かなネットワークの調整が難しくする
  • ゼロスケーリングの活用不足: 0→1だとスケールが間に合わず本番環境では少なくともmin scale: 1が設定されていたため、ゼロスケーリングのメリットを生かせていない

これらの課題を解決するため、Amazon SQSキューのメッセージ数に応じたスケーリングだけでなく、Knativeの代替となるシンプルなイベント駆動スケーリングの仕組みが必要とされました。その候補として選ばれたのがKEDAです。

KEDAとは

KEDA(Kubernetes-based Event Driven Autoscaler)は、Kubernetesのcustom metricを使って、イベント駆動のスケーリングを実現するためのOSSプロジェクトです。 基本的なCPUやメモリの利用率に加え、時間帯(cron)、メッセージキュー、prometheusのmetricなど、さまざまな外部のイベントソースからのmetricに基づいて、スケーリングを行うことが可能です。 検証の結果、KEDAは我々が実現したいこととマッチしていると判断し、導入を決定しました。

続いて、具体的なKEDAの導入方法について詳しく説明します。

導入方法

以下の手順で実施しました。

  1. KEDA本体のインストール
  2. Amazon SQSキューのメッセージ数に応じてスケールするaws-sqs-queue scalerの導入
  3. Knativeを置き換えるため、HTTPリクエストベースのスケーリングを実現するprometheus scalerの導入

KEDA本体のインストール

CircuitではArgoCDでGitOpsを行っており、KEADのインストールもArgoCDを通じて行いました。以下はArgoCDのアプリケーションリソースに以下のマニフェストを追加しました。

後ほどkeda-operatorにAWSのリソースにアクセスする権限を付与するため、serviceAccountにeks.amazonaws.com/role-arnのアノテーションを追加しています。 また、client side applyではエラーが発生するため、ServerSideApply=trueを指定しています(参考: scaledjobs.keda.sh CRD is too long)。

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: keda
spec:
  project: default
  source:
    chart: keda
    repoURL: https://kedacore.github.io/charts
    targetRevision: 2.16.0
    helm:
      valuesObject:
        serviceAccount:
          operator:
            create: true
            name: keda-operator
            automountServiceAccountToken: true
            annotations:
              eks.amazonaws.com/role-arn: arn:aws:iam::000000000000:role/keda-operator-role

  destination:
    server: "https://kubernetes.default.svc"
    namespace: keda
  syncPolicy:
    automated:
      selfHeal: true
      prune: true
    syncOptions:
      - CreateNamespace=true
      - ServerSideApply=true

KEDAが正常に動作していることを確認するため、以下の簡易マニフェストを作成しました。この例では、メモリ使用率に応じてスケールするデモ用deploymentを設定しています。

補足: KEDAのmemory scalerはHPAと似ていますが、特定のcontainerNameを指定できる点が異なります。sidecarパターンを使用している場合、全podではなくアプリケーションコンテナのメモリ使用率に基づいてスケーリングが可能です。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: memory-scaler
spec:
  replicas: 1
  selector:
    matchLabels:
      app: memory-scaler
  template:
    metadata:
      labels:
        app: memory-scaler
    spec:
      containers:
        - name: stress
          image: ghcr.io/colinianking/stress-ng
          args:
            - "--vm"
            - "1"
            - "--vm-bytes"
            - "500M"
            - "--timeout"
            - "600s"
          resources:
            limits:
              memory: "700Mi"
            requests:
              memory: "500Mi"
---
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: memory-scaler
spec:
  scaleTargetRef:
    name: memory-scaler
  maxReplicaCount: 3
  minReplicaCount: 1
  triggers:
    - type: memory
      metadata:
        type: Utilization
        value: "80" # Scale when memory utilization exceeds 80%

aws-sqs-queue scalerの導入

KEDAの基本動作を確認した後、AWS SQS Queue scalerの導入をします。

KEDAがSQSキューへアクセスするためには、適切な権限設定が必要です。KEDAでのアクセスモデルは以下の2種類があります。

  • Operator Identity Model
    • keda-operatorworkloadにそれぞれSQSキューへのアクセス権限を与える
    • IAMロールの2重管理になってしまうが、最小権限の設定が可能
  • Pod Identity Model
    • workloadのみSQSキューへのアクセス権限を付与し、keda-operatorにはworkloadのアクセス権限を委譲する
    • workload用のIAMロールだけを管理すればよいが、keda-operatorにSQSキューへのアクセス以外の権限も与えることになる
    • 追加のTriggerAuthenticationの設定が必要

Operator Identity Model

Pod Identity Model

今回はOperator Identity Modelを採用しました。理由としては、過剰な権限付与を避けるためと、シンプルな構成を保つためです。設定例は以下の通りです。

1. SQSキューを作成

resource "aws_sqs_queue" "keda_sqs_queue" {
  name                      = "keda-sqs-queue"
  max_message_size          = 2048
  message_retention_seconds = 86400
}

2. pod(workload) 用のロールを作成

Podが SQSキューにアクセスできるよう、以下のロールを作成します。その後、KubernetesのServiceAccountにこのロールをアノテーションで指定します。

resource "aws_iam_role" "pod_role" {
  name               = "pod-role"
  assume_role_policy = "{K8S Service Account assume role policy}"
}

resource "aws_iam_role_policy" "pod" {
  name   = "pod-role-policy"
  role   = "pod-role"
  policy = data.aws_iam_policy_document.pod.json
}

data "aws_iam_policy_document" "pod" {
  statement {
    actions = [
      "sqs:GetQueueAttributes"
    ]

    resources = [
      aws_sqs_queue.keda_sqs_queue.arn
    ]
  }
}
apiVersion: v1
kind: ServiceAccount
metadata:
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::000000000000:role/pod-role
  name: test-aws-sqs-queue-scaler

3. keda-operator 用のロールを作成

KEDAインストール時すでにannotationで指定しているため、ここではロールだけ作成して権限を付与します。

resource "aws_iam_role" "keda_operator_role" {
  name               = "keda-operator-role"
  assume_role_policy = "{K8S Service Account assume role policy}"
}

resource "aws_iam_role_policy" "keda_operator" {
  name   = "keda-operator-policy"
  role   = "keda-operator-role"
  policy = data.aws_iam_policy_document.keda_operator.json
}

data "aws_iam_policy_document" "keda_operator" {
  statement {
    actions = [
      "sqs:GetQueueAttributes"
    ]

    resources = [
      aws_sqs_queue.keda_sqs_queue.arn
    ]
  }
}

4. DeploymentとScaledObjectの作成

以下のマニフェストを使って、SQSキューのメッセージ数に応じてスケールするdeploymentとscaledobjectを作成します。Operator Identity Modelを採用したため、identityOwneroperatorを指定しなければなりません。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: test-aws-sqs-queue-scaler
spec:
  selector:
    matchLabels:
      app: test-aws-sqs-queue-scaler
  template:
    metadata:
      labels:
        app: test-aws-sqs-queue-scaler
    spec:
      serviceAccountName: test-aws-sqs-queue-scaler
      containers:
        - name: test-aws-sqs-queue-scaler
          image: ghcr.io/knative/helloworld-go:latest
          ports:
            - containerPort: 8080
          env:
            - name: TARGET
              value: "SQS"
          resources:
            requests:
              memory: 16Mi
              cpu: "10m"
            limits:
              memory: 16Mi

apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: scaledobject
spec:
  scaleTargetRef:
    name: test-aws-sqs-queue-scaler
  pollingInterval: 10
  minReplicaCount: 0
  maxReplicaCount: 5
  cooldownPeriod: 30
  triggers:
    - type: aws-sqs-queue
      metadata:
        queueURL: https://sqs.ap-northeast-1.amazonaws.com/000000000000/keda-sqs-queue
        queueLength: "2"
        awsRegion: "ap-northeast-1"
        identityOwner: operator

prometheus scalerの導入

HTTPリクエストベースのスケーリングを実現するために、KEDA公式がhttp-add-onを提供していますが、注意書きの通りまだまだbeta版です。

The HTTP Add-on currently is in beta. We can't yet recommend it for production usage because we are still developing and testing it. It may have "rough edges" including missing documentation, bugs and other issues. It is currently provided as-is without support.

そのため、本番環境では使わない方が無難だと考え、prometheus scalerを使う方法を選択しました。

以下の設定はIstio Ingress Gatewayを使用する前提の話ですが、リクエスト数のmetricsを取得できれば他のIngress Gatewayの場合でも同様の設定が可能です。

全体的なフローは以下の通りです。

Istio Ingress Gatewayのmetricによってスケーリング

  • リクエストの収集: Istio Ingress Gatewayが受け取ったHTTPリクエストをprometheusでmetricとして収集する
  • ScaledObjectの作成: ScaledObjectを作成し、prometheus scalerを指定してクエリや閾値を設定する
  • スケーリングの実行: KEDAがそのmetric(今回使用しているのはistio_requests_total)をexternal metricsとしてMetric Serverに登録し、HPAがmetricsに基づいてPodのスケーリングを実行する

設定例

1. prometheusにIstioのリクエスト数を収集するための設定

scrape_configs:
  - job_name: "istio-ingressgateway-monitor"
    scrape_interval: 30s
    scrape_timeout: 10s
    kubernetes_sd_configs:
      - role: pod
        namespaces:
          names:
            - istio-system

2. ScaledObjectの設定

apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: scaledobject
spec:
  scaleTargetRef:
    name: test-prometheus-scaler
  pollingInterval: 10
  minReplicaCount: 1
  maxReplicaCount: 5
  cooldownPeriod: 30
  triggers:
    - type: prometheus
      metadata:
        serverAddress: http://prometheus-server.prometheus
        metricName: istio_requests_total_hello_istio
        threshold: "12"
        query: sum(increase(istio_requests_total{destination_app="hello-istio"}[2m]))

ポイント

  • serverAddress: Prometheusのアドレス
  • query: 2分間のリクエスト数を取得

3. External Metricsの確認

上記マニフェストを適用した後、external metricsが正しく登録しているか確認するには、以下のコマンドを実行します。

$kubectl get apiservices | grep metrics.k8s.io
v1beta1.external.metrics.k8s.io             keda/keda-operator-metrics-apiserver    True   8d
v1beta1.metrics.k8s.io                      kube-system/metrics-server              True   218d

v1beta1.external.metrics.k8s.ioが登録されていることを確認できます。

まとめ

本記事では、イベント駆動のスケーリングを実現するために、KEDAを導入する際の背景と手順について解説しました。

  • aws-sqs-queue scaler を用いて、AWS SQSキューに応じたスケーリングを実現した
  • prometheus scalerとistioのmetricを利用して、HTTPリクエストベースのスケーリングを実現した

現時点ではまだ運用時間が短いため、今後新しい知見や課題が出てくるかもしれません。その際には詳細を共有したいと思います。

なお、研究開発部ではMLOps/DevOpsエンジニア・プラットフォームエンジニアを募集中です。興味のある方はぜひご連絡ください!

open.talentio.com

open.talentio.com

Sansan技術本部ではカジュアル面談を実施しています

Sansan技術本部では中途・新卒採用向けにカジュアル面談を実施しています。Sansan技術本部での働き方、仕事の魅力について、現役エンジニアの視点からお話します。「実際に働く人の話を直接聞きたい」「どんな人が働いているのかを事前に知っておきたい」とお考えの方は、ぜひエントリーをご検討ください。

© Sansan, Inc.