この記事は
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と呼ぶそうです。
(2024年9月10日)微妙に勘違いしていたので修正します。。あるサービス全体のリクエスト数制限をGlobal Rate Limit、あるサービスの個々のインスタンス(すなわちPod)のリクエスト数制限をLocal Rate Limitと呼ぶそうです。
Rate Limitを実現する要素としては大きく2つあります。
- Rate Limitサービス
- クォータ管理(ざっくり言うとどの通信が何回きたか)を行うサービス。Envoyから問合せが来る。
- EnvoyFilter
- Envoy(サイドカー)のカスタマイズをするリソース。Envoyが通信を受け取ったときの処理を色々いじれると思われる。
- https://istio.io/latest/docs/reference/config/networking/envoy-filter/
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のドキュメントではサポートしていないとしつつも、とりあえず動くことや必要なリソースは確認できました。