Ollama 를 AWS GPU 서버에서 띄우기 시작하면서 비싼 인스턴스를 계속 새벽에도 둘 수 없게 되었다. 매번 작업을 종료하고 나면 Replicas 수를 0으로 조정하는 것이 귀찮아지기 시작하면서 KEDA 를 통해 특정 시간에 자동 조정되게 하고자 해당 솔루션을 도입한다. KEDA 가 무엇인지 간단히 알아보고 설치 및 사용방법에 대해서 소개한다.
KEDA
KEDA 는 실제 이벤트를 기반으로 쿠버네티스 애플리케이션의 스케일을 도와주는 도구다.
1. KEDA Architecture
2. KEDA Install
Helm을 통해 KEDA를 설치할 수 있다. Values.yaml 파일을 참고해서 배포한다.
helm repo add kedacore https://kedacore.github.io/charts
helm repo update
helm install keda kedacore/keda --namespace keda --create-namespace
나의 경우는 ArgoCD 를 통해 배포하므로 다음과 같이 배포한다. Github Code
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: keda
namespace: argocd
spec:
project: default
sources:
- repoURL: 'https://gitlab.bys.asia/bys/argocd-values.git'
targetRevision: main
ref: values
- repoURL: https://kedacore.github.io/charts
chart: keda
targetRevision: 2.18.2
helm:
releaseName: keda
valueFiles:
- $values/dev-ap2-eks-main/keda/values.yaml
destination:
server: https://kubernetes.default.svc
namespace: keda
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- ServerSideApply=true
- CreateNamespace=false
ArgoCD를 통해 배포시에는 ServerSideApply=true 옵션을 추가하지 않으면 scaledjobs.keda.sh CRD가 정상적으로 배포되지 않으면서 오류가 발생할 수 있다.
Failed sync attempt to : one or more objects failed to apply, reason: CustomResourceDefinition.apiextensions.k8s.io "scaledjobs.keda.sh" is invalid: metadata.annotations: Too long: may not be more than 262144 bytes (retried 5 times).
이는 Default로 ClientSideApply 가 적용되면서 kubectl.kubernetes.io/last-applied-configuration 와 같은 어노테이션이 포함되어 많은 내용이 담길 수도 있기 때문이다.
3. Test - How to work
KEDA를 테스트 해보기 위해 간단히 테스트를 진행해본다.
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: default-cron-scaler # ScaledObject의 이름
namespace: default # Ollama Deployment가 배포된 네임스페이스
spec:
scaleTargetRef:
name: nginx-test # 스케일링할 Ollama Deployment의 이름
minReplicaCount: 0 # 최소 레플리카 수 (저녁 6시 이후에 0개로 줄어들게 됩니다)
maxReplicaCount: 10 # 최대 레플리카 수 (낮 시간 동안 유지될 최대 레플리카 수)
pollingInterval: 30 # KEDA가 크론 스케줄을 확인할 주기 (초 단위)
cooldownPeriod: 300
triggers:
- type: cron
metadata:
timezone: Asia/Seoul # 한국 시간대 (KST) 설정
# 'start' 시간에 'desiredReplicas' (여기서는 2)로 스케일 업 (또는 유지)됩니다.
# 'end' 시간 이후부터 'start' 시간 전까지는 'minReplicas' (여기서는 0)로 스케일 다운됩니다.
start: "40 11 * * *" # 매일 아침 8시 0분에 2개로 스케일 업 (또는 유지)
end: "50 11 * * *" # 매일 저녁 6시 0분에 이 '활성 시간대'가 종료됩니다. # 'end' 시간 이후부터 다음 'start' 시간 전까지는 'minReplicas' (0)로 줄어듭니다.
desiredReplicas: "5" # 'start'와 'end' 사이 활성 시간대에 유지할 레플리카 수
오전 11시 40분이 되면 Scaling 작업이 진행되는데 어떻게 진행되는지 확인해보자.
CloudWatch Logs Insights
region: ap-northeast-2
log-group-names: /aws/eks/bys-dev-eks-main/cluster
data-sources:
facets:
start-time: 2025-12-19T02:37:00.000Z
end-time: 2025-12-19T23:59:59.000Z
query-string:
fields @timestamp, user.username, verb, objectRef.resource, objectRef.name, requestObject.message
| filter @logStream like /^kube-apiserver-audit/
| filter objectRef.namespace == "default"
| filter verb not in ["list", "watch", "get"]
| filter user.username not in ["eks:az-poller"]
| filter user.username not like "system:node"
| filter objectRef.resource not in ["endpoints", "endpointslices"]
| filter objectRef.subresource not in ["status", "token"]
| filter requestObject.message not in ["istio"]
| sort @timestamp desc
| @timestamp | user.username | verb | objectRef.resource | objectRef.name | requestObject.spec.replicas | requestObject.status.desiredReplicas | requestObject.message |
|---|---|---|---|---|---|---|---|
| 2025-12-19 02:54:44.173 | system:serviceaccount:kube-system:horizontal-pod-autoscaler | update | horizontalpodautoscalers | keda-hpa-default-cron-scaler | 0 | ||
| 2025-12-19 02:54:43.421 | system:serviceaccount:kube-system:deployment-controller | update | deployments | nginx-test | 0 | ||
| 2025-12-19 02:54:42.928 | system:serviceaccount:kube-system:replicaset-controller | create | events | nginx-test-5f6f686b54.18827e4f4235bbdf | Deleted pod: nginx-test-5f6f686b54-4zsrp | ||
| 2025-12-19 02:54:42.928 | system:serviceaccount:kube-system:replicaset-controller | create | events | nginx-test-5f6f686b54.18827e4f423ea74b | Deleted pod: nginx-test-5f6f686b54-r5j4p | ||
| 2025-12-19 02:54:42.928 | system:serviceaccount:kube-system:replicaset-controller | update | replicasets | nginx-test-5f6f686b54 | 0 | ||
| 2025-12-19 02:54:42.928 | system:serviceaccount:kube-system:replicaset-controller | create | events | nginx-test-5f6f686b54.18827e4f424b0db5 | Deleted pod: nginx-test-5f6f686b54-ljlns | ||
| 2025-12-19 02:54:42.927 | system:serviceaccount:kube-system:replicaset-controller | update | replicasets | nginx-test-5f6f686b54 | 0 | ||
| 2025-12-19 02:54:42.927 | system:serviceaccount:kube-system:replicaset-controller | create | events | nginx-test-5f6f686b54.18827e4f41ae36c6 | Deleted pod: nginx-test-5f6f686b54-9dt8x | ||
| 2025-12-19 02:54:42.927 | system:serviceaccount:kube-system:replicaset-controller | update | replicasets | nginx-test-5f6f686b54 | 0 | ||
| 2025-12-19 02:54:42.926 | system:serviceaccount:kube-system:replicaset-controller | delete | pods | nginx-test-5f6f686b54-ljlns | |||
| 2025-12-19 02:54:42.926 | system:serviceaccount:kube-system:deployment-controller | patch | events | nginx-test.18827d528919200c | Scaled down replica set nginx-test-5f6f686b54 from 5 to 0 | ||
| 2025-12-19 02:54:42.926 | system:serviceaccount:kube-system:replicaset-controller | create | events | nginx-test-5f6f686b54.18827e4f41a9c98e | Deleted pod: nginx-test-5f6f686b54-p9l9r | ||
| 2025-12-19 02:54:42.925 | system:serviceaccount:kube-system:replicaset-controller | delete | pods | nginx-test-5f6f686b54-r5j4p | |||
| 2025-12-19 02:54:42.924 | system:serviceaccount:kube-system:replicaset-controller | delete | pods | nginx-test-5f6f686b54-p9l9r | |||
| 2025-12-19 02:54:42.924 | system:serviceaccount:kube-system:replicaset-controller | delete | pods | nginx-test-5f6f686b54-4zsrp | |||
| 2025-12-19 02:54:42.923 | system:serviceaccount:kube-system:replicaset-controller | delete | pods | nginx-test-5f6f686b54-9dt8x | |||
| 2025-12-19 02:54:42.922 | system:serviceaccount:keda:keda-operator | create | events | default-cron-scaler.18827e4f3ee250e2 | Deactivated apps/v1.Deployment default/nginx-test from 5 to 0 | ||
| 2025-12-19 02:54:42.922 | system:serviceaccount:kube-system:deployment-controller | update | deployments | nginx-test | 0 | ||
| 2025-12-19 02:54:42.921 | system:serviceaccount:kube-system:deployment-controller | update | replicasets | nginx-test-5f6f686b54 | 0 | ||
| 2025-12-19 02:54:42.920 | system:serviceaccount:keda:keda-operator | update | deployments | nginx-test | |||
| 2025-12-19 02:50:13.793 | system:serviceaccount:kube-system:horizontal-pod-autoscaler | update | horizontalpodautoscalers | keda-hpa-default-cron-scaler | 5 | ||
| 2025-12-19 02:40:43.257 | system:serviceaccount:kube-system:horizontal-pod-autoscaler | update | horizontalpodautoscalers | keda-hpa-default-cron-scaler | 5 | ||
| 2025-12-19 02:40:32.965 | system:serviceaccount:kube-system:deployment-controller | update | deployments | nginx-test | 5 | ||
| 2025-12-19 02:40:32.714 | system:serviceaccount:kube-system:replicaset-controller | update | replicasets | nginx-test-5f6f686b54 | 5 | ||
| 2025-12-19 02:40:28.190 | system:serviceaccount:kube-system:deployment-controller | update | deployments | nginx-test | 5 | ||
| 2025-12-19 02:40:27.946 | system:serviceaccount:kube-system:replicaset-controller | create | pods | ||||
| 2025-12-19 02:40:27.946 | system:serviceaccount:kube-system:replicaset-controller | update | replicasets | nginx-test-5f6f686b54 | 5 | ||
| 2025-12-19 02:40:27.946 | system:serviceaccount:kube-system:replicaset-controller | update | replicasets | nginx-test-5f6f686b54 | 5 | ||
| 2025-12-19 02:40:27.946 | system:serviceaccount:kube-system:replicaset-controller | patch | events | nginx-test-5f6f686b54.18827d84be8c4474 | (combined from similar events): Created pod: nginx-test-5f6f686b54-9dt8x | ||
| 2025-12-19 02:40:27.944 | system:kube-scheduler | create | pods | nginx-test-5f6f686b54-9dt8x | |||
| 2025-12-19 02:40:27.944 | system:kube-scheduler | create | events | nginx-test-5f6f686b54-9dt8x.18827d88385dc064 | |||
| 2025-12-19 02:40:27.944 | system:serviceaccount:kube-system:deployment-controller | update | deployments | nginx-test | 5 | ||
| 2025-12-19 02:40:27.944 | system:serviceaccount:kube-system:deployment-controller | patch | events | nginx-test.18827cfc99445854 | Scaled up replica set nginx-test-5f6f686b54 from 4 to 5 | ||
| 2025-12-19 02:40:27.944 | system:serviceaccount:kube-system:deployment-controller | update | deployments | nginx-test | 5 | ||
| 2025-12-19 02:40:27.939 | system:serviceaccount:kube-system:horizontal-pod-autoscaler | update | deployments | nginx-test | 5 | ||
| 2025-12-19 02:40:27.939 | system:serviceaccount:kube-system:horizontal-pod-autoscaler | update | horizontalpodautoscalers | keda-hpa-default-cron-scaler | 5 | ||
| 2025-12-19 02:40:27.939 | system:serviceaccount:kube-system:horizontal-pod-autoscaler | create | events | keda-hpa-default-cron-scaler.18827d88348250fa | New size: 5; reason: external metric s0-cron-Asia-Seoul-4011xxx-5011xxx(&LabelSelector{MatchLabels:map[string]string{scaledobject.keda.sh/name: default-cron-scaler,},MatchExpressions:[]LabelSelectorRequirement{},}) above target | ||
| 2025-12-19 02:40:27.939 | system:serviceaccount:kube-system:deployment-controller | update | replicasets | nginx-test-5f6f686b54 | 5 | ||
| 2025-12-19 02:40:18.408 | system:serviceaccount:kube-system:deployment-controller | update | deployments | nginx-test | 4 | ||
| 2025-12-19 02:40:18.160 | system:serviceaccount:kube-system:deployment-controller | update | deployments | nginx-test | 4 | ||
| 2025-12-19 02:40:18.160 | system:serviceaccount:kube-system:replicaset-controller | update | replicasets | nginx-test-5f6f686b54 | 4 | ||
| 2025-12-19 02:40:18.159 | system:serviceaccount:kube-system:replicaset-controller | update | replicasets | nginx-test-5f6f686b54 | 4 | ||
| 2025-12-19 02:40:17.906 | system:serviceaccount:kube-system:deployment-controller | update | deployments | nginx-test | 4 | ||
| 2025-12-19 02:40:17.655 | system:serviceaccount:kube-system:replicaset-controller | update | replicasets | nginx-test-5f6f686b54 | 4 | ||
| 2025-12-19 02:40:17.406 | system:serviceaccount:kube-system:deployment-controller | update | deployments | nginx-test | 4 | ||
| 2025-12-19 02:40:17.404 | system:serviceaccount:kube-system:replicaset-controller | update | replicasets | nginx-test-5f6f686b54 | 4 | ||
| 2025-12-19 02:40:13.165 | system:kube-scheduler | create | pods | nginx-test-5f6f686b54-4zsrp | |||
| 2025-12-19 02:40:13.165 | system:kube-scheduler | create | events | nginx-test-5f6f686b54-4zsrp.18827d84b9f2a828 | |||
| 2025-12-19 02:40:13.165 | system:kube-scheduler | create | pods | nginx-test-5f6f686b54-r5j4p | |||
| 2025-12-19 02:40:13.165 | system:kube-scheduler | create | pods | nginx-test-5f6f686b54-ljlns | |||
| 2025-12-19 02:40:13.165 | system:kube-scheduler | create | events | nginx-test-5f6f686b54-r5j4p.18827d84be553247 | |||
| 2025-12-19 02:40:13.165 | system:kube-scheduler | create | events | nginx-test-5f6f686b54-ljlns.18827d84be94dd6d | |||
| 2025-12-19 02:40:13.143 | system:serviceaccount:kube-system:deployment-controller | update | deployments | nginx-test | 4 | ||
| 2025-12-19 02:40:13.143 | system:serviceaccount:kube-system:replicaset-controller | update | replicasets | nginx-test-5f6f686b54 | 4 | ||
| 2025-12-19 02:40:13.143 | system:serviceaccount:kube-system:deployment-controller | update | deployments | nginx-test | 4 | ||
| 2025-12-19 02:40:13.142 | system:serviceaccount:kube-system:replicaset-controller | create | events | nginx-test-5f6f686b54.18827d84be8c4474 | (combined from similar events): Created pod: nginx-test-5f6f686b54-ljlns | ||
| 2025-12-19 02:40:13.142 | system:serviceaccount:kube-system:replicaset-controller | update | replicasets | nginx-test-5f6f686b54 | 4 | ||
| 2025-12-19 02:40:13.142 | system:serviceaccount:kube-system:replicaset-controller | update | replicasets | nginx-test-5f6f686b54 | 4 | ||
| 2025-12-19 02:40:13.141 | system:serviceaccount:kube-system:replicaset-controller | create | events | nginx-test-5f6f686b54.18827d84b9faebe2 | Created pod: nginx-test-5f6f686b54-4zsrp | ||
| 2025-12-19 02:40:13.141 | system:serviceaccount:kube-system:replicaset-controller | create | pods | ||||
| 2025-12-19 02:40:13.141 | system:serviceaccount:kube-system:replicaset-controller | create | pods | ||||
| 2025-12-19 02:40:13.141 | system:serviceaccount:kube-system:replicaset-controller | create | events | nginx-test-5f6f686b54.18827d84bdcbe32f | Created pod: nginx-test-5f6f686b54-r5j4p | ||
| 2025-12-19 02:40:13.141 | system:serviceaccount:kube-system:replicaset-controller | update | replicasets | nginx-test-5f6f686b54 | 4 | ||
| 2025-12-19 02:40:12.918 | system:serviceaccount:kube-system:replicaset-controller | create | pods | ||||
| 2025-12-19 02:40:12.911 | system:serviceaccount:kube-system:deployment-controller | update | deployments | nginx-test | 4 | ||
| 2025-12-19 02:40:12.911 | system:serviceaccount:kube-system:replicaset-controller | update | replicasets | nginx-test-5f6f686b54 | 4 | ||
| 2025-12-19 02:40:12.910 | system:serviceaccount:kube-system:horizontal-pod-autoscaler | update | horizontalpodautoscalers | keda-hpa-default-cron-scaler | 4 | ||
| 2025-12-19 02:40:12.910 | system:serviceaccount:kube-system:replicaset-controller | update | replicasets | nginx-test-5f6f686b54 | 1 | ||
| 2025-12-19 02:40:12.910 | system:serviceaccount:kube-system:deployment-controller | update | deployments | nginx-test | 4 | ||
| 2025-12-19 02:40:12.910 | system:serviceaccount:kube-system:replicaset-controller | create | events | nginx-test-5f6f686b54.18827d84b4a88c56 | Created pod: nginx-test-5f6f686b54-p9l9r | ||
| 2025-12-19 02:40:12.910 | system:serviceaccount:kube-system:deployment-controller | patch | events | nginx-test.18827cf917d9b2af | Scaled up replica set nginx-test-5f6f686b54 from 1 to 4 | ||
| 2025-12-19 02:40:12.905 | system:serviceaccount:kube-system:replicaset-controller | create | pods | ||||
| 2025-12-19 02:40:12.905 | system:serviceaccount:kube-system:horizontal-pod-autoscaler | create | events | keda-hpa-default-cron-scaler.18827d84b38c0441 | New size: 4; reason: external metric s0-cron-Asia-Seoul-4011xxx-5011xxx(&LabelSelector{MatchLabels:map[string]string{scaledobject.keda.sh/name: default-cron-scaler,},MatchExpressions:[]LabelSelectorRequirement{},}) above target | ||
| 2025-12-19 02:40:12.896 | system:serviceaccount:kube-system:deployment-controller | update | replicasets | nginx-test-5f6f686b54 | 4 | ||
| 2025-12-19 02:40:12.895 | system:serviceaccount:kube-system:deployment-controller | update | deployments | nginx-test | 1 | ||
| 2025-12-19 02:40:12.895 | system:serviceaccount:kube-system:deployment-controller | patch | events | nginx-test.18827c0ebcc1d839 | Scaled up replica set nginx-test-5f6f686b54 from 0 to 1 | ||
| 2025-12-19 02:40:12.895 | system:serviceaccount:kube-system:horizontal-pod-autoscaler | update | deployments | nginx-test | 4 | ||
| 2025-12-19 02:40:12.892 | system:kube-scheduler | create | pods | nginx-test-5f6f686b54-p9l9r | |||
| 2025-12-19 02:40:12.892 | system:kube-scheduler | create | events | nginx-test-5f6f686b54-p9l9r.18827d84b44ffbaf | |||
| 2025-12-19 02:40:12.891 | system:serviceaccount:keda:keda-operator | create | events | default-cron-scaler.18827d84ad9b4772 | Scaled apps/v1.Deployment default/nginx-test from 0 to 1, triggered by cronScaler | ||
| 2025-12-19 02:40:12.891 | system:serviceaccount:kube-system:deployment-controller | update | deployments | nginx-test | 1 | ||
| 2025-12-19 02:40:12.890 | system:serviceaccount:kube-system:deployment-controller | update | replicasets | nginx-test-5f6f686b54 | 1 | ||
| 2025-12-19 02:40:12.889 | system:serviceaccount:keda:keda-operator | update | deployments | nginx-test | 1 | ||
| 2025-12-19 02:37:27.937 | system:serviceaccount:kube-system:horizontal-pod-autoscaler | update | horizontalpodautoscalers | keda-hpa-default-cron-scaler | 0 | ||
| 2025-12-19 02:37:12.896 | system:serviceaccount:keda:keda-operator | patch | events | default-cron-scaler.18827d5ac3b3a812 | ScaledObject is ready for scaling | ||
| 2025-12-19 02:37:12.895 | system:serviceaccount:keda:keda-operator | create | horizontalpodautoscalers | keda-hpa-default-cron-scaler | 0 | ||
| 2025-12-19 02:37:12.895 | system:serviceaccount:keda:keda-operator | create | events | default-cron-scaler.18827d5ac3b2fd4e | Started scalers watch | ||
| 2025-12-19 02:37:12.895 | system:serviceaccount:keda:keda-operator | create | events | default-cron-scaler.18827d5ac3b3a812 | ScaledObject is ready for scaling | ||
| 2025-12-19 02:37:12.894 | system:serviceaccount:keda:keda-operator | create | events | default-cron-scaler.18827d5ac1f95e31 | Scaler cron is built | ||
| 2025-12-19 02:37:12.809 | arn:aws:iam::558846430793:user/byoungsoo | create | scaledobjects | default-cron-scaler |
CloudWatch Logs Insights
region: ap-northeast-2
log-group-names: /aws/eks/bys-dev-eks-main/cluster
data-sources:
facets:
start-time: 2025-12-19T02:37:00.000Z
end-time: 2025-12-19T23:59:59.000Z
query-string:
fields @timestamp, @message
| filter @logStream like /^kube-controller-manager/
| FILTER @message like "keda-hpa-default-cron-scaler"
| @timestamp | @message |
|---|---|
| 2025-12-19 02:40:27.000 | I1219 02:40:27.832655 10 horizontal.go:927] "Successfully rescaled" logger="horizontal-pod-autoscaler-controller" HPA="default/keda-hpa-default-cron-scaler" currentReplicas=4 desiredReplicas=5 reason="external metric s0-cron-Asia-Seoul-4011xxx-5011xxx(&LabelSelector{MatchLabels:map[string]string{scaledobject.keda.sh/name: default-cron-scaler,},MatchExpressions:[]LabelSelectorRequirement{},}) above target" |
| 2025-12-19 02:40:12.000 | I1219 02:40:12.784139 10 horizontal.go:927] "Successfully rescaled" logger="horizontal-pod-autoscaler-controller" HPA="default/keda-hpa-default-cron-scaler" currentReplicas=1 desiredReplicas=4 reason="external metric s0-cron-Asia-Seoul-4011xxx-5011xxx(&LabelSelector{MatchLabels:map[string]string{scaledobject.keda.sh/name: default-cron-scaler,},MatchExpressions:[]LabelSelectorRequirement{},}) above target" |
동작방식에 대해 살펴보면 다음과 같다.
- AdmissionWebhook 의 경우 ScaledObject를 만들 때 검증:
- scaleTargetRef가 실제 존재하는 Deployment 인지
- trigger 설정이 올바른지
- 필수 필드가 빠지지 않았는 지 등.
- KEDA Operator 는 ScaledObject 를 Watch 하고 있다가, 새로운 ScaledObject 가 생성되면 HPA 를 생성한다.
- KEDA Operator(Scaler)는 메트릭 수집을 시작한다.
- HPA 가 메트릭을 요청:
- HPA (s0-cron-Asia-Seoul-4011xxx-5011xxx 메트릭 값 알려줘)
- Kubernetes API Server (External Metrics API 요청)
- KEDA Metrics APIServer (Operator 에서 값 조회)
- HPA 스케일 값 계산
- 파드 수 조절 (* 특이점: 사실은 0 -> 1 로 Replicas 수가 조정될 때는 HPA가 아닌 KEDA Operator 가 직접 Deployments 를 수정한다는 점이다.)
log - system:serviceaccount:keda:keda-operator update deployments nginx-test 1
4. Details about KEDA Metrics
KEDA Metrics 를 통해 외부 메트릭이 어떻게 조회되는지 확인해본다. 외부 메트릭을 조회하기 위해 KEDA Operator 내부에는 다양한 Scaler 코드가 존재한다. KEDA Operator 의 Github 코드에는 aws_cloudwatch_scaler.go, aws_dynamodb_scaler.go, aws_sqs_queue_scaler.go, ...... azure_blob_scaler.go, azure_queue_scaler.go, ...... elasticsearch_scaler.go, etcd_scaler.go, mysql_scaler.go 등 다양한 Scaler 가 존재하며 이 Scaler 의 역할은 외부의 메트릭을 수집하는 역할을 한다.
KEDA Metrics 를 조회하는 방법은 아래와 같다.
# kubectl get --raw "/apis/external.metrics.k8s.io/v1beta1/namespaces/{namespace}}/{metrics_name_from_hpa}?labelSelector=scaledobject.keda.sh%2Fname%3D{scaled_object_name}"
kubectl get --raw "/apis/external.metrics.k8s.io/v1beta1/namespaces/default/s0-cron-Asia-Seoul-3622xxx-5022xxx?labelSelector=scaledobject.keda.sh%2Fname%3Ddefault-cron-scaler"
# Cron 일 경우 특정 시간이되지 않을 때는 value 값이 0!
{
"kind": "ExternalMetricValueList",
"apiVersion": "external.metrics.k8s.io/v1beta1",
"metadata": {},
"items": [
{
"metricName": "s0-cron-Asia-Seoul-3622xxx-5022xxx",
"metricLabels": null,
"timestamp": "2025-12-19T12:34:11Z",
"value": "0"
}
]
}
# Cron 일 경우 특정 시간이되면 value 값이 변경!
{
"kind": "ExternalMetricValueList",
"apiVersion": "external.metrics.k8s.io/v1beta1",
"metadata": {},
"items": [
{
"metricName": "s0-cron-Asia-Seoul-3622xxx-5022xxx",
"metricLabels": null,
"timestamp": "2025-12-19T13:37:41Z",
"value": "5"
}
]
}
┌─────────────────────────────────────────────────────────────────────────┐
│ 사용자/HPA │
└─────────────────────────────┬───────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ Kubernetes API Server │
│ │
│ API 라우팅 (APIService 리소스 기반) │
│ │
│ /apis/metrics.k8s.io/... → K8s Metrics Server │
│ /apis/external.metrics.k8s.io/... → KEDA Metrics Server │
│ /apis/custom.metrics.k8s.io/... → (Prometheus Adapter 등) │
└────────┬──────────────────────────────────────┬─────────────────────────┘
│ │
▼ ▼
┌─────────────────────┐ ┌──────────────────────────┐
│ K8s Metrics Server │ │ KEDA Metrics Server │
│ (kube-system) │ │ (keda namespace) │
├─────────────────────┤ ├──────────────────────────┤
│ │ │ │
│ kubelet에서 수집: │ │ ScaledObject 기반 수집: │
│ - Pod CPU │ │ - Prometheus queries │
│ - Pod Memory │ │ - Kafka lag │
│ - Node metrics │ │ - Cron triggers │
│ │ │ - Cloud metrics │
└─────────────────────┘ └──────────────────────────┘
- ScaledObject 생성
- KEDA Operator가 HPA 자동 생성(External Metrics 타입으로 설정)
- HPA가 주기적으로 메트릭 조회 (15초 기본) - GET /apis/external.metrics.k8s.io/v1beta1/namespaces/{ns}/{metric-name}
- API Server가 요청을 KEDA Metrics Server로 라우팅
- KEDA Metrics Server가 실제 외부 소스에서 값 조회 (KEDA)
- HPA가 결과값으로 스케일링 결정
다음 API 서버 Audit 로그를 통해 위 설명과 같이 HPA 로 부터 15초 주기로 API 서버에 메트릭이 요청(/apis/external.metrics.k8s.io/v1beta1/namespaces/default/s0-cron-asia-seoul-3622xxx-5022xxx?labelSelector=scaledobject.keda.sh%2Fname%3Ddefault-cron-scaler) 된 것을 볼 수 있다.
CloudWatch Logs Insights
region: ap-northeast-2
log-group-names: /aws/eks/bys-dev-eks-main/cluster
data-sources:
facets:
start-time: 2025-12-19T02:37:00.000Z
end-time: 2025-12-19T23:59:59.000Z
query-string:
fields @timestamp, userAgent, verb, objectRef.resource
| filter @logStream like "kube-apiserver-audit"
| filter requestURI like "/apis/external.metrics.k8s.io/v1beta1"
| filter objectRef.resource like "s0-cron-"
| sort @timestamp desc
| @timestamp | userAgent | verb | objectRef.resource |
|---|---|---|---|
| 2025-12-19 02:42:13.103 | kube-controller-manager/v1.34.1 (linux/amd64) kubernetes/2011049/system:serviceaccount:kube-system:horizontal-pod-autoscaler | list | s0-cron-asia-seoul-4011xxx-5011xxx |
| 2025-12-19 02:41:58.042 | kube-controller-manager/v1.34.1 (linux/amd64) kubernetes/2011049/system:serviceaccount:kube-system:horizontal-pod-autoscaler | list | s0-cron-asia-seoul-4011xxx-5011xxx |
| 2025-12-19 02:41:43.240 | kube-controller-manager/v1.34.1 (linux/amd64) kubernetes/2011049/system:serviceaccount:kube-system:horizontal-pod-autoscaler | list | s0-cron-asia-seoul-4011xxx-5011xxx |
| 2025-12-19 02:41:28.193 | kube-controller-manager/v1.34.1 (linux/amd64) kubernetes/2011049/system:serviceaccount:kube-system:horizontal-pod-autoscaler | list | s0-cron-asia-seoul-4011xxx-5011xxx |
| 2025-12-19 02:41:13.397 | kube-controller-manager/v1.34.1 (linux/amd64) kubernetes/2011049/system:serviceaccount:kube-system:horizontal-pod-autoscaler | list | s0-cron-asia-seoul-4011xxx-5011xxx |
| 2025-12-19 02:40:58.065 | kube-controller-manager/v1.34.1 (linux/amd64) kubernetes/2011049/system:serviceaccount:kube-system:horizontal-pod-autoscaler | list | s0-cron-asia-seoul-4011xxx-5011xxx |
| 2025-12-19 02:40:43.006 | kube-controller-manager/v1.34.1 (linux/amd64) kubernetes/2011049/system:serviceaccount:kube-system:horizontal-pod-autoscaler | list | s0-cron-asia-seoul-4011xxx-5011xxx |
| 2025-12-19 02:40:27.939 | kube-controller-manager/v1.34.1 (linux/amd64) kubernetes/2011049/system:serviceaccount:kube-system:horizontal-pod-autoscaler | list | s0-cron-asia-seoul-4011xxx-5011xxx |
| 2025-12-19 02:40:12.895 | kube-controller-manager/v1.34.1 (linux/amd64) kubernetes/2011049/system:serviceaccount:kube-system:horizontal-pod-autoscaler | list | s0-cron-asia-seoul-4011xxx-5011xxx |
📚 References
[1] KEDA
- https://keda.sh/docs/2.18/concepts/
[2] KEDA Github
- https://github.com/kedacore/keda/tree/main/pkg/scalers
[3] KEDA Chart
- https://github.com/kedacore/charts/blob/main/keda