LoginSignup
4
4

IstioのGlobal Rate LimitをOpenShift ServiceMeshで試してみる

Posted at

この記事は

Istioの「Enabling Rate Limits using Envoy」をOpenShift Service Meshで試してみた時のメモです。IstioのEnvoyFilterというカスタムリソースを利用して、サイドカー(Envoy)へのカスタイマイズを実装します。

背景(大事)

最初にお断りを入れておくと、EnvoyFilterは明示的に文書化された使い方を除き、OpenShift Service Meshではサポートされていない、とのことです。

このドキュメントを知る前に、「へ~Istioで流量制御できるんだ~じゃあOpenShiftでもできるか試してみよう」という話が持ち上がり、試してしまいました。事前に軽くドキュメントは読みましょう

せっかく試したし、今後もしかしたらサポートされるかもしれない・・ということで軽くメモをまとめておきます。

Rate Limitについてざっくりと

Istio上のサービスへのリクエスト数を制限する機能です。

Istioのサンプルアプリでいえば、「/productpageへのアクセスを1分間に10回までとする」のような制限をかけることができます。

制限をかける個所としては、メッシュの入り口であるIngress Gatewayと、個々のサービスに対して可能です。前者はGlobal Rate Limit、後者はLocal Rate Limitと呼ぶそうです。

Rate Limitを実現する要素としては大きく2つあります。

  • Rate Limitサービス
    • クォータ管理(ざっくり言うとどの通信が何回きたか)を行うサービス。Envoyから問合せが来る。
  • EnvoyFilter

Rate Limitサービスをデプロイし、Rate LimitをかけたいサービスにEnvoyFilterを適用していくのがおおまかな流れとなります。

検証の流れ

前提

  • Red Hat OpenShift on IBM Cloud (ROKS)
  • OpenShift 4.11
  • すでにOpenShift Service Meshは導入済
  • ServiceMeshControlPlaneも作成済
    • ドキュメントに従って作成。こちらも設定値はデフォルトのまま。

作業用Projectの作成

$ oc new-project rate-limit-01
Now using project "rate-limit-01" on server "https://c100-e.jp-tok.containers.cloud.ibm.com:xxxxx".

You can add applications to this project with the 'new-app' command. For example, try:

    oc new-app rails-postgresql-example

to build a new example application in Ruby. Or use kubectl to deploy a simple Kubernetes application:

    kubectl create deployment hello-node --image=k8s.gcr.io/e2e-test-images/agnhost:2.33 -- /agnhost serve-hostname

ServiceMeshMemberRollへの追加

予めdefaultというSMMRをistio-system Projectに作成していたので、.spec.members[]に作業用Projectを追加します。

$ oc edit smmr -n istio-system
servicemeshmemberroll.maistra.io/default edited

Bookinfoアプリケーションのデプロイ

istio恒例のBookinfoアプリケーションをデプロイします。

アプリケーション本体(Deployment, Service, ServiceAccount)は、OpenShiftドキュメントのチュートリアルをそのまま使用しました。

Gateway、VirtualServiceについては同チュートリアルのマニフェストのhostsに、以下のようなホスト名を追加しました。

rate-limit-sample.ixxxxxxxxxxxxxxxxxx0.jp-tok.containers.appdomain.cloud

Destination Ruleもチュートリアルのものをそのまま使用しました。

必要なリソース群を配置したディレクトリでoc applyを実行します。

$ oc apply -f ./

Rate Limit周りのリソース作成

以降のリソースは、Global Rate Limitのチュートリアルを参考に作成します。

ConfigMap

どのDescriptorについてクォータ設定をするか定めます。以下の書き方で、/productpageへのアクセスは1分間に10回まで、メッシュ全体へのアクセスは1分間に1000回までとする設定を表します。

piVersion: v1
kind: ConfigMap
metadata:
  name: ratelimit-config
data:
  config.yaml: |
    domain: productpage-ratelimit
    descriptors:
      - key: PATH
        value: "/productpage"
        rate_limit:
          unit: minute
          requests_per_unit: 10
      - key: PATH
        rate_limit:
          unit: minute
          requests_per_unit: 1000

Rate Limitサービス

Rate Limitサービスをデプロイします。あわせてデプロイするRedisは、OpenShiftの場合defaultのServiceAccountでは権限不足で正常に稼働しないので、Redis用のServiceAccountを作成し権限を付与します。

SevriceAccount

apiVersion: v1
kind: ServiceAccount
metadata:
  creationTimestamp: null
  name: redis

上記の内容でServiceAccountを作成し、anyuidを付与します。

$ oc adm policy add-scc-to-user anyuid -z redis
clusterrole.rbac.authorization.k8s.io/system:openshift:scc:anyuid added: "redis"

Rate Limitサービス+Redis

以下の内容で作成します(長いので折り畳み)。ほぼほぼ参考にしたチュートリアルどおりですが、前述のServiceAccountをRedis Deploymentで使うようにしています。

マニフェスト
apiVersion: v1
kind: Service
metadata:
  name: redis
  labels:
    app: redis
spec:
  ports:
  - name: redis
    port: 6379
  selector:
    app: redis
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis
spec:
  replicas: 1
  selector:
    matchLabels:
      app: redis
  template:
    metadata:
      labels:
        app: redis
    spec:
      containers:
      - image: redis:alpine
        imagePullPolicy: Always
        name: redis
        ports:
        - name: redis
          containerPort: 6379
      restartPolicy: Always
      serviceAccountName: redis
---
apiVersion: v1
kind: Service
metadata:
  name: ratelimit
  labels:
    app: ratelimit
spec:
  ports:
  - name: http-port
    port: 8080
    targetPort: 8080
    protocol: TCP
  - name: grpc-port
    port: 8081
    targetPort: 8081
    protocol: TCP
  - name: http-debug
    port: 6070
    targetPort: 6070
    protocol: TCP
  selector:
    app: ratelimit
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: ratelimit
spec:
  replicas: 1
  selector:
    matchLabels:
      app: ratelimit
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: ratelimit
    spec:
      containers:
      - image: envoyproxy/ratelimit:9d8d70a8 # 2022/08/16
        imagePullPolicy: Always
        name: ratelimit
        command: ["/bin/ratelimit"]
        env:
        - name: LOG_LEVEL
          value: debug
        - name: REDIS_SOCKET_TYPE
          value: tcp
        - name: REDIS_URL
          value: redis:6379
        - name: USE_STATSD
          value: "false"
        - name: RUNTIME_ROOT
          value: /data
        - name: RUNTIME_SUBDIRECTORY
          value: ratelimit
        - name: RUNTIME_WATCH_ROOT
          value: "false"
        - name: RUNTIME_IGNOREDOTFILES
          value: "true"
        - name: HOST
          value: "::"
        - name: GRPC_HOST
          value: "::"
        ports:
        - containerPort: 8080
        - containerPort: 8081
        - containerPort: 6070
        volumeMounts:
        - name: config-volume
          mountPath: /data/ratelimit/config
      volumes:
      - name: config-volume
        configMap:
          name: ratelimit-config

EnvoyFilter

2つ作成します。1つ目のEnvoyFilterではどのRate Limitサービスと連携するか指定する箇所があるので、デプロイしたRate Limitサービスを指定します。

apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: filter-ratelimit
  namespace: istio-system
spec:
  workloadSelector:
    labels:
      istio: ingressgateway
  configPatches:
    - applyTo: HTTP_FILTER
      match:
        context: GATEWAY
        listener:
          filterChain:
            filter:
              name: "envoy.filters.network.http_connection_manager"
              subFilter:
                name: "envoy.filters.http.router"
      patch:
        operation: INSERT_BEFORE
        value:
          name: envoy.filters.http.ratelimit
          typed_config:
            "@type": type.googleapis.com/envoy.extensions.filters.http.ratelimit.v3.RateLimit
            # 前述のConfigMap名に一致させる必要あり
            domain: productpage-ratelimit
            failure_mode_deny: false
            timeout: 10s
            rate_limit_service:
              grpc_service:
                envoy_grpc:
                  # namespace名に注意(ratelimitをデプロイした名前空間にする)
                  cluster_name: outbound|8081||ratelimit.rate-limit-01.svc.cluster.local
                  authority: ratelimit.rate-limit01.svc.cluster.local
              transport_api_version: V3

2つ目のEnvoyFilterでは、リクエストヘッダーにpathがあった場合、PATHというDescriptorを作成し、その値にpathの中身を入れる設定が記載されています。

apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: filter-ratelimit-svc
  namespace: istio-system
spec:
  workloadSelector:
    labels:
      istio: ingressgateway
  configPatches:
    - applyTo: VIRTUAL_HOST
      match:
        context: GATEWAY
        routeConfiguration:
          vhost:
            name: ""
            route:
              action: ANY
      patch:
        operation: MERGE
        value:
          rate_limits:
            - actions:
              - request_headers:
                  header_name: ":path"
                  descriptor_key: "PATH"

リクエストを繰り返し送信

Bookinfoの/prodcutpageに向かって、毎秒リクエスト送信してみます。

どうもx分00秒~(x+1)分00秒の間でリクエスト数をカウントしているらしく、その時間の中で10回送信されるとステータスコード429(Too Many Requests)が返ってきました。

リクエスト送信時の出力
$ while true;do date; curl -I http:///rate-limit-sample.ixxxxxxxxxxxxxxxx0.jp-tok.containers.appdomain.cloud/productpage -o /dev/null -w '%{http_code}\n' -s; echo ""; sleep 1; done
Tue Jun 27 22:48:13 JST 2023
200

Tue Jun 27 22:48:14 JST 2023
200

Tue Jun 27 22:48:15 JST 2023
200

Tue Jun 27 22:48:16 JST 2023
200

Tue Jun 27 22:48:18 JST 2023
200

Tue Jun 27 22:48:19 JST 2023
200

Tue Jun 27 22:48:20 JST 2023
200

Tue Jun 27 22:48:21 JST 2023
200

Tue Jun 27 22:48:22 JST 2023
200

Tue Jun 27 22:48:23 JST 2023
200

Tue Jun 27 22:48:24 JST 2023
429

Tue Jun 27 22:48:25 JST 2023
429

Tue Jun 27 22:48:26 JST 2023
429

Tue Jun 27 22:48:27 JST 2023
429

Tue Jun 27 22:48:29 JST 2023
429

Tue Jun 27 22:48:30 JST 2023
429

Tue Jun 27 22:48:31 JST 2023
429

Tue Jun 27 22:48:32 JST 2023
429

Tue Jun 27 22:48:33 JST 2023
429

Tue Jun 27 22:48:34 JST 2023
429

Tue Jun 27 22:48:35 JST 2023
429

Tue Jun 27 22:48:36 JST 2023
429

Tue Jun 27 22:48:37 JST 2023
429

Tue Jun 27 22:48:38 JST 2023
429

Tue Jun 27 22:48:39 JST 2023
429

Tue Jun 27 22:48:40 JST 2023
429

Tue Jun 27 22:48:41 JST 2023
429

Tue Jun 27 22:48:43 JST 2023
429

Tue Jun 27 22:48:44 JST 2023
429

Tue Jun 27 22:48:45 JST 2023
429

Tue Jun 27 22:48:46 JST 2023
429

Tue Jun 27 22:48:47 JST 2023
429

Tue Jun 27 22:48:48 JST 2023
429

Tue Jun 27 22:48:49 JST 2023
429

Tue Jun 27 22:48:50 JST 2023
429

Tue Jun 27 22:48:51 JST 2023
429

Tue Jun 27 22:48:52 JST 2023
429

Tue Jun 27 22:48:53 JST 2023
429

Tue Jun 27 22:48:54 JST 2023
429

Tue Jun 27 22:48:55 JST 2023
429

Tue Jun 27 22:48:56 JST 2023
429

Tue Jun 27 22:48:58 JST 2023
429

Tue Jun 27 22:48:59 JST 2023
429

Tue Jun 27 22:49:00 JST 2023
200

Tue Jun 27 22:49:01 JST 2023
200

Tue Jun 27 22:49:02 JST 2023
200

Tue Jun 27 22:49:03 JST 2023
200

Tue Jun 27 22:49:04 JST 2023
200

Tue Jun 27 22:49:05 JST 2023
200

Tue Jun 27 22:49:06 JST 2023
200

Tue Jun 27 22:49:07 JST 2023
200

Tue Jun 27 22:49:09 JST 2023
200

Tue Jun 27 22:49:10 JST 2023
200

Tue Jun 27 22:49:11 JST 2023
429

Tue Jun 27 22:49:12 JST 2023
429

Tue Jun 27 22:49:13 JST 2023
429

Tue Jun 27 22:49:14 JST 2023
429

Tue Jun 27 22:49:15 JST 2023
429

Tue Jun 27 22:49:16 JST 2023
429

Tue Jun 27 22:49:17 JST 2023
429

Tue Jun 27 22:49:18 JST 2023
429

Tue Jun 27 22:49:19 JST 2023
429

Tue Jun 27 22:49:20 JST 2023
429

Tue Jun 27 22:49:22 JST 2023
429

Tue Jun 27 22:49:23 JST 2023
429

Tue Jun 27 22:49:24 JST 2023
429

Tue Jun 27 22:49:26 JST 2023
429

Tue Jun 27 22:49:27 JST 2023
429

Tue Jun 27 22:49:28 JST 2023
429

Tue Jun 27 22:49:29 JST 2023
429

Tue Jun 27 22:49:30 JST 2023
429

Tue Jun 27 22:49:31 JST 2023
429

Tue Jun 27 22:49:32 JST 2023
429

Tue Jun 27 22:49:33 JST 2023
429

Tue Jun 27 22:49:34 JST 2023
429

Tue Jun 27 22:49:35 JST 2023
429

Tue Jun 27 22:49:36 JST 2023
429

Tue Jun 27 22:49:38 JST 2023
429

Tue Jun 27 22:49:39 JST 2023
429

Tue Jun 27 22:49:40 JST 2023
429

Tue Jun 27 22:49:41 JST 2023
429

Tue Jun 27 22:49:42 JST 2023
429

Tue Jun 27 22:49:43 JST 2023
429

Tue Jun 27 22:49:44 JST 2023
429

Tue Jun 27 22:49:45 JST 2023
429

Tue Jun 27 22:49:46 JST 2023
429

Tue Jun 27 22:49:47 JST 2023
429

Tue Jun 27 22:49:48 JST 2023
429

Tue Jun 27 22:49:50 JST 2023
429

Tue Jun 27 22:49:51 JST 2023
429

Tue Jun 27 22:49:52 JST 2023
429

Tue Jun 27 22:49:53 JST 2023
429

Tue Jun 27 22:49:54 JST 2023
429

Tue Jun 27 22:49:55 JST 2023
429

Tue Jun 27 22:49:56 JST 2023
429

Tue Jun 27 22:49:57 JST 2023
429

Tue Jun 27 22:49:58 JST 2023
429

Tue Jun 27 22:49:59 JST 2023
429

Tue Jun 27 22:50:00 JST 2023
200

Tue Jun 27 22:50:01 JST 2023
200

Tue Jun 27 22:50:03 JST 2023
200

Tue Jun 27 22:50:05 JST 2023
200

Tue Jun 27 22:50:06 JST 2023
200

Tue Jun 27 22:50:07 JST 2023
200

Tue Jun 27 22:50:08 JST 2023
200

Tue Jun 27 22:50:09 JST 2023
200

Tue Jun 27 22:50:10 JST 2023
200

Tue Jun 27 22:50:11 JST 2023
200

Tue Jun 27 22:50:13 JST 2023
429

Tue Jun 27 22:50:14 JST 2023
429

Tue Jun 27 22:50:15 JST 2023
429

まとめ

OpenShiftのドキュメントではサポートしていないとしつつも、とりあえず動くことや必要なリソースは確認できました。

4
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
4