Use APISIX, Prometheus, and KEDA to Scale Applications Elastically in Kubernetes
February 17, 2023
Introduction
The amount of traffic that enters an application varies from time to time. For example, an online shopping application is much busier during the holiday season than on regular days. To be able to adjust the application's capacity based on traffic will provide much better user experiences and services.
Apache APISIX is a high-performance cloud-native API gateway that can provide meaningful metrics to determine whether the applications need to be scaled. It is a middleware that processes all traffic sent to the upstream applications and can therefore collect traffic data along the process.
To perform elastic scaling, KEDA will be used as the controller, and Prometheus will be used to fetch metrics provided by APISIX.
How to Use the Prometheus Scaler in KEDA
KEDA is an event-based autoscaler in Kubernetes, which can configure various scalers. In this article, the Prometheus scaler will be used to obtain the metrics exposed by APISIX.
Deploy KEDA
The deployment of KEDA is relatively simple, just add the corresponding Helm repo and install it.
(MoeLove) ➜ helm repo add kedacore https://kedacore.github.io/charts
"kedacore" has been added to your repositories
(MoeLove) ➜ helm repo update kedacore
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "kedacore" chart repository
Update Complete. ⎈Happy Helming!⎈
(MoeLove) ➜ helm install keda kedacore/keda --namespace keda --create-namespace
NAME: keda
LAST DEPLOYED: Thu Jan 19 00:01:00 2023
NAMESPACE: keda
STATUS: deployed
REVISION: 1
TEST SUITE: None
After the installation, the Pod has a status Running
, indicating it has been installed.
(MoeLove) ➜ kubectl -n keda get pods
NAME READY STATUS RESTARTS AGE
keda-operator-metrics-apiserver-6d4db7dcff-ck9qg 1/1 Running 0 36s
keda-operator-5dd4748dcd-k8jjz 1/1 Running 0 36s
Deploy Prometheus
Here we use Prometheus Operator to deploy Prometheus. Prometheus Operator can help us quickly deploy the Prometheus instance in Kubernetes and add the monitoring rules through declarative configuration.
Complete the installation of Prometheus Operator through the following steps.
(MoeLove) ➜ https://github.com/prometheus-operator/prometheus-operator/releases/download/v0.62.0/bundle.yaml
(MoeLove) ➜ kubectl apply --server-side -f bundle.yaml
customresourcedefinition.apiextensions.k8s.io/alertmanagerconfigs.monitoring.coreos.com serverside-applied
customresourcedefinition.apiextensions.k8s.io/alertmanagers.monitoring.coreos.com serverside-applied
customresourcedefinition.apiextensions.k8s.io/podmonitors.monitoring.coreos.com serverside-applied
customresourcedefinition.apiextensions.k8s.io/probes.monitoring.coreos.com serverside-applied
customresourcedefinition.apiextensions.k8s.io/prometheuses.monitoring.coreos.com serverside-applied
customresourcedefinition.apiextensions.k8s.io/prometheusrules.monitoring.coreos.com serverside-applied
customresourcedefinition.apiextensions.k8s.io/servicemonitors.monitoring.coreos.com serverside-applied
customresourcedefinition.apiextensions.k8s.io/thanosrulers.monitoring.coreos.com serverside-applied
clusterrolebinding.rbac.authorization.k8s.io/prometheus-operator serverside-applied
clusterrole.rbac.authorization.k8s.io/prometheus-operator serverside-applied
deployment.apps/prometheus-operator serverside-applied
serviceaccount/prometheus-operator serverside-applied
service/prometheus-operator serverside-applied
Then use the following as the configuration for Prometheus and apply it to the Kubernetes cluster.
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: prometheus
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: prometheus
rules:
- apiGroups: [""]
resources:
- nodes
- nodes/metrics
- services
- endpoints
- pods
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources:
- configmaps
verbs: ["get"]
- apiGroups:
- networking.k8s.io
resources:
- ingresses
verbs: ["get", "list", "watch"]
- nonResourceURLs: ["/metrics"]
verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: prometheus
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: prometheus
subjects:
- kind: ServiceAccount
name: prometheus
namespace: default
---
apiVersion: monitoring.coreos.com/v1
kind: Prometheus
metadata:
name: prometheus
spec:
serviceAccountName: prometheus
serviceMonitorSelector:
matchLabels:
app: apisix
serviceMonitorNamespaceSelector:
matchLabels:
team: apisix
resources:
requests:
memory: 400Mi
enableAdminAPI: false
---
apiVersion: v1
kind: Service
metadata:
name: prometheus
spec:
type: LoadBalancer
ports:
- name: web
port: 9090
protocol: TCP
targetPort: web
selector:
prometheus: prometheus
After applying to the Kubernetes cluster, you can see that a Prometheus instance is created under the default
namespace. Since we configured Prometheus with TYPE LoadBalancer
, users can directly access Prometheus through the public IP of the LoadBalancer.
(MoeLove) ➜ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.43.0.1 <none> 443/TCP 96m
prometheus-operator ClusterIP None <none> 8080/TCP 92m
prometheus-operated ClusterIP None <none> 9090/TCP 41m
prometheus LoadBalancer 10.43.125.194 216.6.66.66 9090:30099/TCP 41m
How to Deploy the API Gateway and Enable Monitoring
Next, deploy APISIX Ingress controller and use Prometheus for metrics collection.
The method is similar for users who only use APISIX instead of the APISIX Ingress Controller. We will not explain it separately here.
Here, Helm is used for deployment, and the APISIX Ingress controller and APISIX can simultaneously be deployed to the cluster.
(MoeLove) ➜ helm repo add apisix https://charts.apiseven.com
"apisix" already exists with the same configuration, skipping
(MoeLove) ➜ helm repo update apisix
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "apisix" chart repository
Update Complete. ⎈Happy Helming!⎈
(MoeLove) ➜ helm upgrade --install apisix apisix/apisix --create-namespace --namespace apisix --set gateway.type=LoadBalancer --set ingress-controller.enabled=true --set ingress-controller.config.apisix.serviceNamespace=apisix
Release "apisix" has been upgraded. Happy Helming!
NAME: apisix
LAST DEPLOYED: Thu Jan 19 02:11:23 2023
NAMESPACE: apisix
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
1. Get the application URL by running these commands:
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
You can watch the status of by running 'kubectl get --namespace apisix svc -w apisix-gateway'
export SERVICE_IP=$(kubectl get svc --namespace apisix apisix-gateway --template "{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}")
echo http://$SERVICE_IP:80
Next, enable the Prometheus plugin of APISIX. Please refer to the following two documents for specific configuration methods and related parameters.
- prometheus plugins | Apache APISIX®
- How to access Apache APISIX Prometheus metrics on Kubernetes | Apache APISIX®
Once enabled, Prometheus can capture the metrics exposed by APISIX by creating a ServiceMonitor resource.
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: example-app
labels:
app: apisix
spec:
selector:
matchLabels:
app: apisix
endpoints:
- port: web
Verify Application’s Elastic Scaling Capability
Create an application first.
(MoeLove) ➜ kubectl create deploy httpbin --image=kennethreitz/httpbin --port=80
deployment.apps/httpbin created
(MoeLove) ➜ kubectl expose deploy httpbin --port 80
Create the following routing rules and apply them to the Kubernetes cluster to proxy requests through APISIX.
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
name: httpserver-route
spec:
http:
- name: rule1
match:
hosts:
- local.httpbin.org
paths:
- /*
backends:
- serviceName: httpbin
servicePort: 80
Next, create a KEDA ScaledObject and configure Prometheus-related configurations.
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: prometheus-scaledobject
namespace: default
spec:
scaleTargetRef:
name: httpbin
triggers:
- type: prometheus
metadata:
serverAddress: http://prometheus.default.svc:9090
metricName: apisix_http_status
threshold: '10'
query: sum(rate(apisix_http_status{route="httpserver-route"}[1m]))
The above configuration means that sum(rate(apisix_http_status{route="httpserver-route"}[1m]))
is used as the query expression, and if the result can reach 10, the expansion will start. (The configuration here is only for demonstrating purposes, please modify it according to your own situation). Then, we make continuous requests to the httpbin service through curl.
Later, if we check the application pods, you can see that it has been autoscaled to two by KEDA.
(MoeLove) ➜ kubectl get pods
NAME READY STATUS RESTARTS AGE
httpbin-d46d778d7-chtdw 1/1 Running 0 12m
httpbin-d46d778d7-xanbj 1/1 Running 0 10s
When the application sits idle after some time, we will find that the number of pods has automatically shrunk back to one instance.
(MoeLove) ➜ kubectl get pods
NAME READY STATUS RESTARTS AGE
httpbin-d46d778d7-chtdw 1/1 Running 0 32m
Summary
KEDA uses Prometheus as a scaler to collect the metrics exposed by APISIX. Since all traffic passes through APISIX first, getting statistics on the APISIX side will be much simpler and more convenient.
When the volume of business requests increases, the application will be automatically expanded, and when the volume of business requests drops, the application will be automatically contracted.
This method can alleviate manual expansion/reduction operations in many production environments to ensure the best user experience.