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"

동작방식에 대해 살펴보면 다음과 같다.

  1. AdmissionWebhook 의 경우 ScaledObject를 만들 때 검증:
    1. scaleTargetRef가 실제 존재하는 Deployment 인지
    2. trigger 설정이 올바른지
    3. 필수 필드가 빠지지 않았는 지 등.
  2. KEDA Operator 는 ScaledObject 를 Watch 하고 있다가, 새로운 ScaledObject 가 생성되면 HPA 를 생성한다.
  3. KEDA Operator(Scaler)는 메트릭 수집을 시작한다.
  4. HPA 가 메트릭을 요청:
    1. HPA (s0-cron-Asia-Seoul-4011xxx-5011xxx 메트릭 값 알려줘)
    2. Kubernetes API Server (External Metrics API 요청)
    3. KEDA Metrics APIServer (Operator 에서 값 조회)
  5. HPA 스케일 값 계산
  6. 파드 수 조절 (* 특이점: 사실은 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         │
└─────────────────────┘              └──────────────────────────┘
  1. ScaledObject 생성
  2. KEDA Operator가 HPA 자동 생성(External Metrics 타입으로 설정)
  3. HPA가 주기적으로 메트릭 조회 (15초 기본) - GET /apis/external.metrics.k8s.io/v1beta1/namespaces/{ns}/{metric-name}
  4. API Server가 요청을 KEDA Metrics Server로 라우팅
  5. KEDA Metrics Server가 실제 외부 소스에서 값 조회 (KEDA)
  6. 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

[2] KEDA Github

[3] KEDA Chart