Kubernetes

AEWS [3기] 5주차 - EKS Autoscaling

yu3papa 2025. 3. 8. 10:37

0. AutoScailing 개요

쿠버네티스에서 운영중인 시스템에 대해 리소스가 부족해지면 정상운영이 힘들어 집니다.

쿠버네티스는 리소스가 부족할 경우 다양한 방법으로 AutoScailing을 지원합니다.

kubernetes patterns ‘2nd 발췌

 

AWS 장표를 보면 아래와 같습니다.

위의 HPA, VPA 는 POD 단위입니다. 

POD를 이용하여 AutoScail이 안되어지는 상황이 오면 워커노드를 스케일 아웃해야 합니다.

CAS는 워커노드를 자동으로 Scale In/Out 이 가능합니다.

하지만 기능이 추가된 Karpenter (고성능 노드 오토스케일러)를 실무에서는 많이 사용합니다.

AutoScaling을 이해하기 전에 POD안의 Container가 CPU, Memory 리소스를 어떻게 사용하는지가 중요합니다.

  • requests : 쿠버네티스 스케쥴러가 POD를 어떤 워커노드에 스케쥴링할때 기준값
  • limits : 워커노드에 스케쥴링된 후에 해당 워커노드의 자원 사용을 제한 --> Control Group이 동작

클러스터에 자원이 부족해지게 되면 POD에서 설정된 request, limit 값을 확인하여 Qos 클래스로 구분한 후 BestEffort 로 분류된 POD를 제거해 버립니다.

 

이러한 문제를 해결하기 위해 AutoScailing은 클러스터를 운영하는데 필수적으로 적용되어야 합니다.

  • 수평 확장 Horizontal Scaling
    • 운영 환경에 더 많은 워크로드(VM, Task, Pod)를 추가하는 방식
    • 트래픽을 여러 워크로드에 분산시키는 방법
    • 확장성이 높고 유연한 방법
    • 비용 효율적
    • Stateless 워크로드에 적합 (데이터 일관성 유지 필요)
  • 수직 확장 Vertical Scaling
    • 운영 환경의 기존 워크로드의 성능(CPU, Memory)을 향상하는 방법
    • 하드웨어는 확장에 한계가 존재함
    • 확장 과정에서의 장애 위험이 존재함
    • 수평적 확장과 비교하면 유연성이 부족함
  • AWS Auto Scaling 정책
    • Simple/Step scaling : Manual Reactive, Dynamic scaling
      • 고객이 정의한 단계에 따라 메트릭을 모니터링하고 인스턴스를 추가 또는 제거합니다.
    • Target tracking : Automated Reactive, Dynamic scaling
      • 고객이 정의한 목표 메트릭을 유지하기 위해 자동으로 인스턴스를 추가 또는 제거합니다.
    • Scheduled scaling : Manual Proactive
      • 고객이 정의한 일정에 따라 인스턴스를 시작하거나 종료합니다.
    • Predictive scaling : Automated Proactive
      • 과거 트렌드를 기반으로 용량을 선제적으로 시작합니다.
  • K8S Auto Scaling 정책
    • 확장 방법 : 컨테이너(파드) vs 노드(서버)
      • 컨테이너 수평적 확장
      • 컨테이너 수직적 확장
      • 노드 수평적 확장
      • 노드 수직적 확장
    • 확장 기준
      • 컨테이너 메트릭 기반
      • 애플리케이션 메트릭 기반
      • 이벤트(일정, 대기열 등) 기반
    • 확장 정책
      • 단순 확장 정책
      • 단계 확장 정책
      • 목표 추적 확장 정책
  • EKS Auto Scaling - Link
    • HPA : 서비스를 처리할 파드 자원이 부족한 경우 신규 파드 Provisioning → 파드 Scale Out
    • VPA : 서비스를 처리할 파드 자원이 부족한 경우 파드 교체(자동 or 수동) → 파드 Scale Up
    • CAS : 파드를 배포할 노드가 부족한 경우 신규 노드 Provisioning → 노드 Scale Out
    • Karpenter : Unscheduled 파드가 있는 경우 새로운 노드 및 파드 Provisioning → 노드 Scale Up/Out

AWS Auto Scaling 한계 관리 복잡성 증가, 최적화 설정 어려움, 비용 관리 어려움

https://www.youtube.com/watch?v=jLuVZX6WQsw

  • 오토스케일링 전략은 EC2의 오토스케일링 그룹 사용을 중심으로 동작합니다
  • 노드 그룹에서 인스턴스 타입이 동일하다고 가정합니다.
  • 혼합 인스턴스 타입은 가능한 CPU와 메모리가 균등하게 되어야 합니다.
  • 다양한 인스턴스 타입을 지원하기 위해서는 여러 노드 그룹이 필요합니다.
  • 모범사례는 AZ당 노드 그룹을 가지는 것입니다.

카펜터

  • 유연한 노드 프로비저닝, 빠른 스케일링 및 비용 최적화, 똑똑한 통합 관리 및 간소화된 설정, 향상된 리소스 활용 및 확장성

https://www.youtube.com/watch?v=jLuVZX6WQsw

 

  • EC2와 긴밀히 통합 : EC2 Fleet API, ASGs 가 없음
  • 쿠버네티스 네이티브 : Watch API, Labels, Finalizers
  • 자동화된 인스턴스 선택 : 워크로드 요구사항을 인스턴스 타입과 일치
  • 기본 동작 흐름 : 카펜터가 ‘파드 unschedulable’ 감지 후 Pod 스펙 평가 후 ‘EC2 Fleet’를 통해 노드 프로비저닝
  • Over Provisioning : 스파이크 트래픽 대비하기 - 여유 노드를 항상 유지하기, 더미파드 사용(낮은 우선순위 적용)
  • Custom Metric 활용하기 : Resource Metric API, Custom Metric API, External Metric API
    • Resource Metric API : Node 또는 Pod의 CPU, 메모리 사용량 등의 metric 기반으로한 파드 스케일링, metrics.k8s.io
    • Custom Metric API : 사용자가 정의한 클러스터 내부의 metric을 기반으로 파드 스케일링, custom.metrics.k8s.io
    • External Metric API : 클러스터 외부에서 수집된 metric 을 기반으로 파드 스케일링, external.metrics.k8s.io

 

  • KEDA 활용하기 : 다양한 Event로 Scaling 수행
    • 이벤트 기반 파드 오토스케일링 (HPA 이용)
    • 트리거 : cron, ..
    • ScaledObject 오브젝트에 명세 정의
    • 카펜터와 통합되어 클러스터 수준 오토스케일링 지원 : 예) Cron으로 특정 시간에 노드 증설

1. 실습을 위한 EKS 클러스터 구성

AutoScaling를 실습을 위한 EKS 클러스터를 CloudFormation을 이용하여 구성하겠습니다.

myeks-5week.yaml
0.02MB

# 변수 지정
CLUSTER_NAME=myeks
SSHKEYNAME=<SSH페이 름>
MYACCESSKEY=<IAM Uesr 액세스 키>
MYSECRETKEY=<IAM Uesr 시크릿 키>

# CloudFormation 스택 배포
[yu3papa@iworks ~]$ aws cloudformation deploy --template-file myeks-5week.yaml --stack-name $CLUSTER_NAME --parameter-overrides KeyName=$SSHKEYNAME SgIngressSshCidr=$(curl -s ipinfo.io/ip)/32  MyIamUserAccessKeyID=$MYACCESSKEY MyIamUserSecretAccessKey=$MYSECRETKEY ClusterBaseName=$CLUSTER_NAME --region ap-northeast-2

Waiting for changeset to be created..
Waiting for stack create/update to complete
Successfully created/updated stack - myeks


# CloudFormation 스택 배포 완료 후 작업용 EC2 IP 출력
[yu3papa@iworks ~]$ aws cloudformation describe-stacks --stack-name myeks --query 'Stacks[*].Outputs[0].OutputValue' --output text

13.124.218.189

 

운영서버 EC2-IP와 SSH Private-Key를 이용하여 SSH 접속을 합니다.

 

kubeconfig 작업을 수행하여 kubectl  명령을 수행할 수 있도록 설정합니다.

# 변수 지정
CLUSTER_NAME=myeks
SSHKEYNAME=yu3papakey

#
eksctl get cluster

# kubeconfig 생성
aws sts get-caller-identity --query Arn
# aws eks update-kubeconfig --name myeks --user-alias <위 출력된 자격증명 사용자>
aws eks update-kubeconfig --name myeks --user-alias admin

#
kubectl ns default
kubectl get node --label-columns=node.kubernetes.io/instance-type,eks.amazonaws.com/capacityType,topology.kubernetes.io/zone
kubectl get pod -A
kubectl get pdb -n kube-system

 

운영서버 EC2에서 EKS 설치확인

# default 네임스페이스 적용
kubectl ns default

# 환경변수 정보 확인
export | egrep 'ACCOUNT|AWS_|CLUSTER|KUBERNETES|VPC|Subnet'
export | egrep 'ACCOUNT|AWS_|CLUSTER|KUBERNETES|VPC|Subnet' | egrep -v 'KEY'

# krew 플러그인 확인
kubectl krew list

# 인스턴스 정보 확인
aws ec2 describe-instances --query "Reservations[*].Instances[*].{InstanceID:InstanceId, PublicIPAdd:PublicIpAddress, PrivateIPAdd:PrivateIpAddress, InstanceName:Tags[?Key=='Name']|[0].Value, Status:State.Name}" --filters Name=instance-state-name,Values=running --output table

# 노드 IP 확인 및 PrivateIP 변수 지정
aws ec2 describe-instances --query "Reservations[*].Instances[*].{PublicIPAdd:PublicIpAddress,PrivateIPAdd:PrivateIpAddress,InstanceName:Tags[?Key=='Name']|[0].Value,Status:State.Name}" --filters Name=instance-state-name,Values=running --output table
N1=$(kubectl get node --label-columns=topology.kubernetes.io/zone --selector=topology.kubernetes.io/zone=ap-northeast-2a -o jsonpath={.items[0].status.addresses[0].address})
N2=$(kubectl get node --label-columns=topology.kubernetes.io/zone --selector=topology.kubernetes.io/zone=ap-northeast-2b -o jsonpath={.items[0].status.addresses[0].address})
N3=$(kubectl get node --label-columns=topology.kubernetes.io/zone --selector=topology.kubernetes.io/zone=ap-northeast-2c -o jsonpath={.items[0].status.addresses[0].address})
echo "export N1=$N1" >> /etc/profile
echo "export N2=$N2" >> /etc/profile
echo "export N3=$N3" >> /etc/profile
echo $N1, $N2, $N3

# 노드 IP 로 ping 테스트
for i in $N1 $N2 $N3; do echo ">> node $i <<"; ping -c 1 $i ; echo; done

 

EKS 배포후 실습편의를 위해 각종 정보를 환경변수 설정파일에 등록하겠습니다.

# 변수 지정
export CLUSTER_NAME=myeks
export VPCID=$(aws ec2 describe-vpcs --filters "Name=tag:Name,Values=$CLUSTER_NAME-VPC" --query 'Vpcs[*].VpcId' --output text)
export PubSubnet1=$(aws ec2 describe-subnets --filters Name=tag:Name,Values="$CLUSTER_NAME-Vpc1PublicSubnet1" --query "Subnets[0].[SubnetId]" --output text)
export PubSubnet2=$(aws ec2 describe-subnets --filters Name=tag:Name,Values="$CLUSTER_NAME-Vpc1PublicSubnet2" --query "Subnets[0].[SubnetId]" --output text)
export PubSubnet3=$(aws ec2 describe-subnets --filters Name=tag:Name,Values="$CLUSTER_NAME-Vpc1PublicSubnet3" --query "Subnets[0].[SubnetId]" --output text)
export N1=$(aws ec2 describe-instances --filters "Name=tag:Name,Values=$CLUSTER_NAME-ng1-Node" "Name=availability-zone,Values=ap-northeast-2a" --query 'Reservations[*].Instances[*].PublicIpAddress' --output text)
export N2=$(aws ec2 describe-instances --filters "Name=tag:Name,Values=$CLUSTER_NAME-ng1-Node" "Name=availability-zone,Values=ap-northeast-2b" --query 'Reservations[*].Instances[*].PublicIpAddress' --output text)
export N3=$(aws ec2 describe-instances --filters "Name=tag:Name,Values=$CLUSTER_NAME-ng1-Node" "Name=availability-zone,Values=ap-northeast-2c" --query 'Reservations[*].Instances[*].PublicIpAddress' --output text)
export CERT_ARN=$(aws acm list-certificates --query 'CertificateSummaryList[].CertificateArn[]' --output text) #사용 리전의 인증서 ARN 확인
MyDomain=yu3papa.shop # 각자 자신의 도메인 이름 입력
MyDnzHostedZoneId=$(aws route53 list-hosted-zones-by-name --dns-name "$MyDomain." --query "HostedZones[0].Id" --output text)


# 실습 완료 후 삭제 할 것!
cat << EOF >> ~/.bash_profile

# eksworkshop
export CLUSTER_NAME=myeks
export VPCID=$(aws ec2 describe-vpcs --filters "Name=tag:Name,Values=$CLUSTER_NAME-VPC" --query 'Vpcs[*].VpcId' --output text)
export PubSubnet1=$(aws ec2 describe-subnets --filters Name=tag:Name,Values="$CLUSTER_NAME-Vpc1PublicSubnet1" --query "Subnets[0].[SubnetId]" --output text)
export PubSubnet2=$(aws ec2 describe-subnets --filters Name=tag:Name,Values="$CLUSTER_NAME-Vpc1PublicSubnet2" --query "Subnets[0].[SubnetId]" --output text)
export PubSubnet3=$(aws ec2 describe-subnets --filters Name=tag:Name,Values="$CLUSTER_NAME-Vpc1PublicSubnet3" --query "Subnets[0].[SubnetId]" --output text)
export N1=$(aws ec2 describe-instances --filters "Name=tag:Name,Values=$CLUSTER_NAME-ng1-Node" "Name=availability-zone,Values=ap-northeast-2a" --query 'Reservations[*].Instances[*].PublicIpAddress' --output text)
export N2=$(aws ec2 describe-instances --filters "Name=tag:Name,Values=$CLUSTER_NAME-ng1-Node" "Name=availability-zone,Values=ap-northeast-2b" --query 'Reservations[*].Instances[*].PublicIpAddress' --output text)
export N3=$(aws ec2 describe-instances --filters "Name=tag:Name,Values=$CLUSTER_NAME-ng1-Node" "Name=availability-zone,Values=ap-northeast-2c" --query 'Reservations[*].Instances[*].PublicIpAddress' --output text)
export CERT_ARN=$(aws acm list-certificates --query 'CertificateSummaryList[].CertificateArn[]' --output text)
MyDomain=yu3papa.shop # 각자 자신의 도메인 이름 입력
MyDnzHostedZoneId=$(aws route53 list-hosted-zones-by-name --dns-name "$MyDomain." --query "HostedZones[0].Id" --output text)
EOF

# [신규 터미널] 확인
echo $CLUSTER_NAME $VPCID $PubSubnet1 $PubSubnet2 $PubSubnet3
echo $N1 $N2 $N3 $MyDomain $MyDnzHostedZoneId
tail -n 15 ~/.bash_profile

 

AWS LoadBalancer Controller, ExternalDNS, gp3 storageclass, kube-ops-view(Ingress) 설치합니다.

# AWS LoadBalancerController
helm install aws-load-balancer-controller eks/aws-load-balancer-controller -n kube-system --set clusterName=$CLUSTER_NAME \
  --set serviceAccount.create=false --set serviceAccount.name=aws-load-balancer-controller

# ExternalDNS
echo $MyDomain
curl -s https://raw.githubusercontent.com/gasida/PKOS/main/aews/externaldns.yaml | MyDomain=$MyDomain MyDnzHostedZoneId=$MyDnzHostedZoneId envsubst | kubectl apply -f -

# gp3 스토리지 클래스 생성
cat <<EOF | kubectl apply -f -
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: gp3
  annotations:
    storageclass.kubernetes.io/is-default-class: "true"
allowVolumeExpansion: true
provisioner: ebs.csi.aws.com
volumeBindingMode: WaitForFirstConsumer
parameters:
  type: gp3
  allowAutoIOPSPerGBIncrease: 'true'
  encrypted: 'true'
  fsType: xfs # 기본값이 ext4
EOF
kubectl get sc


# kube-ops-view
helm repo add geek-cookbook https://geek-cookbook.github.io/charts/
helm install kube-ops-view geek-cookbook/kube-ops-view --version 1.2.2 --set service.main.type=ClusterIP  --set env.TZ="Asia/Seoul" --namespace kube-system

# kubeopsview 용 Ingress 설정 : group 설정으로 1대의 ALB를 여러개의 ingress 에서 공용 사용
echo $CERT_ARN
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    alb.ingress.kubernetes.io/certificate-arn: $CERT_ARN
    alb.ingress.kubernetes.io/group.name: study
    alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}, {"HTTP":80}]'
    alb.ingress.kubernetes.io/load-balancer-name: $CLUSTER_NAME-ingress-alb
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/ssl-redirect: "443"
    alb.ingress.kubernetes.io/success-codes: 200-399
    alb.ingress.kubernetes.io/target-type: ip
  labels:
    app.kubernetes.io/name: kubeopsview
  name: kubeopsview
  namespace: kube-system
spec:
  ingressClassName: alb
  rules:
  - host: kubeopsview.$MyDomain
    http:
      paths:
      - backend:
          service:
            name: kube-ops-view
            port:
              number: 8080  # name: http
        path: /
        pathType: Prefix
EOF

 

잘 설치되었는지 확인합니다.

# 설치된 파드 정보 확인
[root@operator-host ~]# kubectl get pods -n kube-system
NAME                                          READY   STATUS    RESTARTS   AGE
aws-load-balancer-controller-554fbd9d-tt6hc   1/1     Running   0          6m28s
aws-load-balancer-controller-554fbd9d-vqpls   1/1     Running   0          6m28s
aws-node-9m9jq                                2/2     Running   0          20m
aws-node-dzj26                                2/2     Running   0          20m
aws-node-glzc4                                2/2     Running   0          20m
coredns-86f5954566-q2dqk                      1/1     Running   0          26m
coredns-86f5954566-v7twp                      1/1     Running   0          26m
ebs-csi-controller-549bf6879f-dvg9x           6/6     Running   0          17m
ebs-csi-controller-549bf6879f-g6zr9           6/6     Running   0          17m
ebs-csi-node-jvzmz                            3/3     Running   0          17m
ebs-csi-node-wb7mn                            3/3     Running   0          17m
ebs-csi-node-zh9fz                            3/3     Running   0          17m
external-dns-57c44fb644-8lr98                 1/1     Running   0          5m34s
kube-ops-view-657dbc6cd8-9zpwt                1/1     Running   0          5m5s
kube-proxy-5lhtp                              1/1     Running   0          20m
kube-proxy-ltfvh                              1/1     Running   0          20m
kube-proxy-x5c4c                              1/1     Running   0          20m
metrics-server-6bf5998d9c-5rnzh               1/1     Running   0          26m
metrics-server-6bf5998d9c-rvxwz               1/1     Running   0          26m

# service, ep, ingress 확인
[root@operator-host ~]# kubectl get ingress,svc,ep -n kube-system
NAME                                    CLASS   HOSTS                      ADDRESS                                                         PORTS   AGE
ingress.networking.k8s.io/kubeopsview   alb     kubeopsview.yu3papa.shop   myeks-ingress-alb-1265817985.ap-northeast-2.elb.amazonaws.com   80      5m26s

NAME                                        TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                  AGE
service/aws-load-balancer-webhook-service   ClusterIP   10.100.170.40    <none>        443/TCP                  7m15s
service/eks-extension-metrics-api           ClusterIP   10.100.128.212   <none>        443/TCP                  31m
service/kube-dns                            ClusterIP   10.100.0.10      <none>        53/UDP,53/TCP,9153/TCP   27m
service/kube-ops-view                       ClusterIP   10.100.132.200   <none>        8080/TCP                 5m52s
service/metrics-server                      ClusterIP   10.100.53.163    <none>        443/TCP                  27m

NAME                                          ENDPOINTS                                                       AGE
endpoints/aws-load-balancer-webhook-service   192.168.2.33:9443,192.168.3.79:9443                             7m15s
endpoints/eks-extension-metrics-api           172.0.32.0:10443                                                31m
endpoints/kube-dns                            192.168.1.228:53,192.168.1.96:53,192.168.1.228:53 + 3 more...   27m
endpoints/kube-ops-view                       192.168.2.150:8080                                              5m52s
endpoints/metrics-server                      192.168.1.28:10251,192.168.1.51:10251                           27m

# Kube Ops View 접속 정보 확인 : 조금 오래 기다리면 접속됨...
[root@operator-host ~]# echo -e "Kube Ops View URL = https://kubeopsview.$MyDomain/#scale=1.5"
Kube Ops View URL = https://kubeopsview.yu3papa.shop/#scale=1.5

 

프로메테우스 & 그라파나(admin / prom-operator) 설치

  • 대시보드 Import 17900
# repo 추가
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts

# 파라미터 파일 생성 : PV/PVC(AWS EBS) 삭제에 불편하니, 4주차 실습과 다르게 PV/PVC 미사용
cat <<EOT > monitor-values.yaml
prometheus:
  prometheusSpec:
    scrapeInterval: "15s"
    evaluationInterval: "15s"
    podMonitorSelectorNilUsesHelmValues: false
    serviceMonitorSelectorNilUsesHelmValues: false
    retention: 5d
    retentionSize: "10GiB"
 
  # Enable vertical pod autoscaler support for prometheus-operator
  verticalPodAutoscaler:
    enabled: true

  ingress:
    enabled: true
    ingressClassName: alb
    hosts:
      - prometheus.$MyDomain
    paths:
      - /*
    annotations:
      alb.ingress.kubernetes.io/scheme: internet-facing
      alb.ingress.kubernetes.io/target-type: ip
      alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}, {"HTTP":80}]'
      alb.ingress.kubernetes.io/certificate-arn: $CERT_ARN
      alb.ingress.kubernetes.io/success-codes: 200-399
      alb.ingress.kubernetes.io/load-balancer-name: myeks-ingress-alb
      alb.ingress.kubernetes.io/group.name: study
      alb.ingress.kubernetes.io/ssl-redirect: '443'

grafana:
  defaultDashboardsTimezone: Asia/Seoul
  adminPassword: prom-operator
  defaultDashboardsEnabled: false

  ingress:
    enabled: true
    ingressClassName: alb
    hosts:
      - grafana.$MyDomain
    paths:
      - /*
    annotations:
      alb.ingress.kubernetes.io/scheme: internet-facing
      alb.ingress.kubernetes.io/target-type: ip
      alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}, {"HTTP":80}]'
      alb.ingress.kubernetes.io/certificate-arn: $CERT_ARN
      alb.ingress.kubernetes.io/success-codes: 200-399
      alb.ingress.kubernetes.io/load-balancer-name: myeks-ingress-alb
      alb.ingress.kubernetes.io/group.name: study
      alb.ingress.kubernetes.io/ssl-redirect: '443'

kube-state-metrics:
  rbac:
    extraRules:
      - apiGroups: ["autoscaling.k8s.io"]
        resources: ["verticalpodautoscalers"]
        verbs: ["list", "watch"]
  customResourceState:
    enabled: true
    config:
      kind: CustomResourceStateMetrics
      spec:
        resources:
          - groupVersionKind:
              group: autoscaling.k8s.io
              kind: "VerticalPodAutoscaler"
              version: "v1"
            labelsFromPath:
              verticalpodautoscaler: [metadata, name]
              namespace: [metadata, namespace]
              target_api_version: [apiVersion]
              target_kind: [spec, targetRef, kind]
              target_name: [spec, targetRef, name]
            metrics:
              - name: "vpa_containerrecommendations_target"
                help: "VPA container recommendations for memory."
                each:
                  type: Gauge
                  gauge:
                    path: [status, recommendation, containerRecommendations]
                    valueFrom: [target, memory]
                    labelsFromPath:
                      container: [containerName]
                commonLabels:
                  resource: "memory"
                  unit: "byte"
              - name: "vpa_containerrecommendations_target"
                help: "VPA container recommendations for cpu."
                each:
                  type: Gauge
                  gauge:
                    path: [status, recommendation, containerRecommendations]
                    valueFrom: [target, cpu]
                    labelsFromPath:
                      container: [containerName]
                commonLabels:
                  resource: "cpu"
                  unit: "core"
  selfMonitor:
    enabled: true

alertmanager:
  enabled: false
defaultRules:
  create: false
kubeControllerManager:
  enabled: false
kubeEtcd:
  enabled: false
kubeScheduler:
  enabled: false
prometheus-windows-exporter:
  prometheus:
    monitor:
      enabled: false
EOT
cat monitor-values.yaml

# helm 배포
helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack --version 69.3.1 \
-f monitor-values.yaml --create-namespace --namespace monitoring

# helm 확인
helm get values -n monitoring kube-prometheus-stack

# PV 사용하지 않음
[root@operator-host ~]# kubectl get pv,pvc -A
No resources found

[root@operator-host ~]# kubectl df-pv
INFO[2025-03-08T17:15:31+09:00] Either no volumes found in namespace/s: 'all' or the storage provisioner used for the volumes does not publish metrics to kubelet

# 프로메테우스 웹 접속
echo -e "https://prometheus.$MyDomain"


# 그라파나 웹 접속 : admin / prom-operator
echo -e "https://grafana.$MyDomain"
 

#
[root@operator-host ~]# kubectl get targetgroupbindings.elbv2.k8s.aws -A
NAMESPACE     NAME                               SERVICE-NAME                       SERVICE-PORT   TARGET-TYPE   AGE
kube-system   k8s-kubesyst-kubeopsv-f59a7f60c9   kube-ops-view                      8080           ip            17m
monitoring    k8s-monitori-kubeprom-5c3d6ee03d   kube-prometheus-stack-grafana      80             ip            9m11s
monitoring    k8s-monitori-kubeprom-ad3e3e2518   kube-prometheus-stack-prometheus   9090           ip            9m9s


# 상세 확인
[root@operator-host ~]# kubectl get pod -n monitoring -l app.kubernetes.io/name=kube-state-metrics
NAME                                                        READY   STATUS    RESTARTS   AGE
kube-prometheus-stack-kube-state-metrics-5674c7ddd8-g8qwz   1/1     Running   0          9m50s

[root@operator-host ~]# kubectl describe pod -n monitoring -l app.kubernetes.io/name=kube-state-metrics
...
Service Account:  kube-prometheus-stack-kube-state-metrics
...
    Args:
      --port=8080
      --resources=certificatesigningrequests,configmaps,cronjobs,daemonsets,deployments,endpoints,horizontalpodautoscalers,ingresses,jobs,leases,limitranges,mutatingwebhookconfigurations,namespaces,networkpolicies,nodes,persistentvolumeclaims,persistentvolumes,poddisruptionbudgets,pods,replicasets,replicationcontrollers,resourcequotas,secrets,services,statefulsets,storageclasses,validatingwebhookconfigurations,volumeattachments
      --custom-resource-state-config-file=/etc/customresourcestate/config.yaml
    ...
Volumes:
  customresourcestate-config:
    Type:      ConfigMap (a volume populated by a ConfigMap)
    Name:      kube-prometheus-stack-kube-state-metrics-customresourcestate-config
    Optional:  false
...

[root@operator-host ~]# kubectl describe cm -n monitoring kube-prometheus-stack-kube-state-metrics-customresourcestate-config
Name:         kube-prometheus-stack-kube-state-metrics-customresourcestate-config
Namespace:    monitoring
Labels:       app.kubernetes.io/component=metrics
              app.kubernetes.io/instance=kube-prometheus-stack
              app.kubernetes.io/managed-by=Helm
              app.kubernetes.io/name=kube-state-metrics
              app.kubernetes.io/part-of=kube-state-metrics
              app.kubernetes.io/version=2.14.0
              helm.sh/chart=kube-state-metrics-5.29.0
              release=kube-prometheus-stack
Annotations:  meta.helm.sh/release-name: kube-prometheus-stack
              meta.helm.sh/release-namespace: monitoring

Data
====
config.yaml:
----
kind: CustomResourceStateMetrics
spec:
  resources:
  - groupVersionKind:
      group: autoscaling.k8s.io
      kind: VerticalPodAutoscaler
      version: v1
    labelsFromPath:
      namespace:
      - metadata
      - namespace
      target_api_version:
      - apiVersion
      target_kind:
      - spec
      - targetRef
      - kind
      target_name:
      - spec
      - targetRef
      - name
      verticalpodautoscaler:
      - metadata
      - name
    metrics:
    - commonLabels:
        resource: memory
        unit: byte
      each:
        gauge:
          labelsFromPath:
            container:
            - containerName
          path:
          - status
          - recommendation
          - containerRecommendations
          valueFrom:
          - target
          - memory
        type: Gauge
      help: VPA container recommendations for memory.
      name: vpa_containerrecommendations_target
    - commonLabels:
        resource: cpu
        unit: core
      each:
        gauge:
          labelsFromPath:
            container:
            - containerName
          path:
          - status
          - recommendation
          - containerRecommendations
          valueFrom:
          - target
          - cpu
        type: Gauge
      help: VPA container recommendations for cpu.
      name: vpa_containerrecommendations_target



BinaryData
====

Events:  <none>

...

#
[root@operator-host ~]# kubectl get clusterrole kube-prometheus-stack-kube-state-metrics
NAME                                       CREATED AT
kube-prometheus-stack-kube-state-metrics   2025-03-08T08:08:16Z

[root@operator-host ~]# kubectl describe clusterrole kube-prometheus-stack-kube-state-metrics
Name:         kube-prometheus-stack-kube-state-metrics
Labels:       app.kubernetes.io/component=metrics
              app.kubernetes.io/instance=kube-prometheus-stack
              app.kubernetes.io/managed-by=Helm
              app.kubernetes.io/name=kube-state-metrics
              app.kubernetes.io/part-of=kube-state-metrics
              app.kubernetes.io/version=2.14.0
              helm.sh/chart=kube-state-metrics-5.29.0
              release=kube-prometheus-stack
Annotations:  meta.helm.sh/release-name: kube-prometheus-stack
              meta.helm.sh/release-namespace: monitoring
PolicyRule:
  Resources                                                     Non-Resource URLs  Resource Names  Verbs
  ---------                                                     -----------------  --------------  -----
  configmaps                                                    []                 []              [list watch]
  endpoints                                                     []                 []              [list watch]
  limitranges                                                   []                 []              [list watch]
  namespaces                                                    []                 []              [list watch]
  nodes                                                         []                 []              [list watch]
  persistentvolumeclaims                                        []                 []              [list watch]
  persistentvolumes                                             []                 []              [list watch]
  pods                                                          []                 []              [list watch]
  replicationcontrollers                                        []                 []              [list watch]
  resourcequotas                                                []                 []              [list watch]
  secrets                                                       []                 []              [list watch]
  services                                                      []                 []              [list watch]
  mutatingwebhookconfigurations.admissionregistration.k8s.io    []                 []              [list watch]
  validatingwebhookconfigurations.admissionregistration.k8s.io  []                 []              [list watch]
  customresourcedefinitions.apiextensions.k8s.io                []                 []              [list watch]
  daemonsets.apps                                               []                 []              [list watch]
  deployments.apps                                              []                 []              [list watch]
  replicasets.apps                                              []                 []              [list watch]
  statefulsets.apps                                             []                 []              [list watch]
  verticalpodautoscalers.autoscaling.k8s.io                     []                 []              [list watch]
  horizontalpodautoscalers.autoscaling                          []                 []              [list watch]
  cronjobs.batch                                                []                 []              [list watch]
  jobs.batch                                                    []                 []              [list watch]
  certificatesigningrequests.certificates.k8s.io                []                 []              [list watch]
  leases.coordination.k8s.io                                    []                 []              [list watch]
  daemonsets.extensions                                         []                 []              [list watch]
  deployments.extensions                                        []                 []              [list watch]
  ingresses.extensions                                          []                 []              [list watch]
  replicasets.extensions                                        []                 []              [list watch]
  ingresses.networking.k8s.io                                   []                 []              [list watch]
  networkpolicies.networking.k8s.io                             []                 []              [list watch]
  poddisruptionbudgets.policy                                   []                 []              [list watch]
  storageclasses.storage.k8s.io                                 []                 []              [list watch]
  volumeattachments.storage.k8s.io                              []                 []              [list watch]

[root@operator-host ~]# kubectl describe clusterrole kube-prometheus-stack-kube-state-metrics | grep verticalpodautoscalers
  verticalpodautoscalers.autoscaling.k8s.io                     []                 []              [list watch]

 

EKS Node Viewer

노드 할당 가능 용량요청 request 리소스 표시, 실제 파드 리소스 사용량 X - 링크

  • 동작
    • It displays the scheduled pod resource requests vs the allocatable capacity on the node.
    • It does not look at the actual pod resource usage.
    • Node마다 할당 가능한 용량과 스케줄링된 POD(컨테이너)의 Resource 중 request 값을 표시한다.
    • 실제 POD(컨테이너) 리소스 사용량은 아니다. /pkg/model/pod.go 파일을 보면 컨테이너의 request 합을 반환하며, init containers는 미포함
    • https://github.com/awslabs/eks-node-viewer/blob/main/pkg/model/pod.go#L82
// Requested returns the sum of the resources requested by the pod. This doesn't include any init containers as we
// are interested in the steady state usage of the pod
func (p *Pod) Requested() v1.ResourceList {
  p.mu.RLock()
  defer p.mu.RUnlock()
  requested := v1.ResourceList{}
  for _, c := range p.pod.Spec.Containers {
    for rn, q := range c.Resources.Requests {
      existing := requested[rn]
      existing.Add(q)
      requested[rn] = existing
    }
  }
  requested[v1.ResourcePods] = resource.MustParse("1")
  return requested
}
  • 설치
# 운영서버 EC2에 설치 : userdata 통해 이미 설치 되어 있음
yum install golang -y
go install github.com/awslabs/eks-node-viewer/cmd/eks-node-viewer@latest  # 설치 시 2~3분 정도 소요

 

  • 사용
# Standard usage
[root@operator-host ~]# eks-node-viewer


# Display both CPU and Memory Usage
[root@operator-host ~]# eks-node-viewer --resources cpu,memory


[root@operator-host ~]# eks-node-viewer --resources cpu,memory --extra-labels eks-node-viewer/node-age


# Display extra labels, i.e. AZ : node 에 labels 사용 가능
[root@operator-host ~]# eks-node-viewer --extra-labels topology.kubernetes.io/zone

[root@operator-host ~]# eks-node-viewer --extra-labels kubernetes.io/arch


# Sort by CPU usage in descending order
[root@operator-host ~]# eks-node-viewer --node-sort=eks-node-viewer/node-cpu-usage=dsc


 # Karenter nodes only
[root@operator-host ~]# eks-node-viewer --node-selector "karpenter.sh/provisioner-name"


# Specify a particular AWS profile and region
AWS_PROFILE=myprofile AWS_REGION=us-west-2


Computed Labels : --extra-labels
# eks-node-viewer/node-age - Age of the node
eks-node-viewer --extra-labels eks-node-viewer/node-age
eks-node-viewer --extra-labels topology.kubernetes.io/zone,eks-node-viewer/node-age

# eks-node-viewer/node-ephemeral-storage-usage - Ephemeral Storage usage (requests)
eks-node-viewer --extra-labels eks-node-viewer/node-ephemeral-storage-usage

# eks-node-viewer/node-cpu-usage - CPU usage (requests)
eks-node-viewer --extra-labels eks-node-viewer/node-cpu-usage

# eks-node-viewer/node-memory-usage - Memory usage (requests)
eks-node-viewer --extra-labels eks-node-viewer/node-memory-usage

# eks-node-viewer/node-pods-usage - Pod usage (requests)
eks-node-viewer --extra-labels eks-node-viewer/node-pods-usage

 

HPA - Horizontal Pod Autoscaler

  • CPU 부하량을 기준으로 자동으로 POD 개수를 Scale In/Out 실습을 진행하겠습니다.
  • 그라파나(22128 , 22251) 대시보드 Import

 

  • 샘플 애플리케이션 배포
    • (참고) hpa-example : Dockerfile , index.php (CPU 과부하 연산 수행 , 100만번 덧셈 수행)
    • Dockerfile
FROM php:5-apache
COPY index.php /var/www/html/index.php
RUN chmod a+rx index.php
  • index.php
<?php
$x = 0.0001;
for ($i = 0; $i <= 1000000; $i++) {
            $x += sqrt($x);
}
echo "OK!";
?>

 

# Run and expose php-apache server
cat << EOF > php-apache.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: php-apache
spec:
  selector:
    matchLabels:
      run: php-apache
  template:
    metadata:
      labels:
        run: php-apache
    spec:
      containers:
      - name: php-apache
        image: registry.k8s.io/hpa-example
        ports:
        - containerPort: 80
        resources:
          limits:
            cpu: 500m
          requests:
            cpu: 200m
---
apiVersion: v1
kind: Service
metadata:
  name: php-apache
  labels:
    run: php-apache
spec:
  ports:
  - port: 80
  selector:
    run: php-apache
EOF
kubectl apply -f php-apache.yaml

# 확인
[root@operator-host ~]# kubectl exec -it deploy/php-apache -- cat /var/www/html/index.php
<?php
$x = 0.0001;
for ($i = 0; $i <= 1000000; $i++) {
        $x += sqrt($x);
}
echo "OK!";
?>

# 모니터링 : 터미널2개 사용
[root@operator-host ~]# watch -d 'kubectl get hpa,pod;echo;kubectl top pod;echo;kubectl top node'


--> 현재 1개의 POD가 사용중입니다.


 

  • HPA 정책 생성 및 부하 발생 후 오토 스케일링 테스트 : 증가 시 기본 대기 시간(30초), 감소 시 기본 대기 시간(5분) → 조정 가능


# Create the HorizontalPodAutoscaler : requests.cpu=200m - 알고리즘
# Since each pod requests 200 milli-cores by kubectl run, this means an average CPU usage of 100 milli-cores.
cat <<EOF | kubectl apply -f -
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: php-apache
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: php-apache
  minReplicas: 1
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        averageUtilization: 50
        type: Utilization

EOF
혹은
kubectl autoscale deployment php-apache --cpu-percent=50 --min=1 --max=10

# 확인
[root@operator-host ~]# kubectl get hpa
NAME         REFERENCE               TARGETS       MINPODS   MAXPODS   REPLICAS   AGE
php-apache   Deployment/php-apache   cpu: 0%/50%   1         10        1          82s

[root@operator-host ~]# kubectl describe hpa
Name:                                                  php-apache
Namespace:                                             default
Labels:                                                <none>
Annotations:                                           <none>
CreationTimestamp:                                     Sat, 08 Mar 2025 22:08:04 +0900
Reference:                                             Deployment/php-apache
Metrics:                                               ( current / target )
  resource cpu on pods  (as a percentage of request):  0% (1m) / 50%
Min replicas:                                          1
Max replicas:                                          10
Deployment pods:                                       1 current / 1 desired
Conditions:
  Type            Status  Reason               Message
  ----            ------  ------               -------
  AbleToScale     True    ScaleDownStabilized  recent recommendations were higher than current one, applying the highest recent recommendation
  ScalingActive   True    ValidMetricFound     the HPA was able to successfully calculate a replica count from cpu resource utilization (percentage of request)
  ScalingLimited  False   DesiredWithinRange   the desired count is within the acceptable range
Events:           <none>

# HPA 리소스가 생성된 후에 그라파나에서 모니터링이 가능합니다.



# HPA 설정 확인
kubectl get hpa php-apache -o yaml | kubectl neat
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
spec:
  minReplicas: 1               # [4] 또는 최소 1개까지 줄어들 수도 있습니다
  maxReplicas: 10              # [3] 포드를 최대 10개까지 늘립니다
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: php-apache           # [1] php-apache 의 자원 사용량에서
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 50  # [2] CPU 활용률이 50% 이상인 경우

# --> 이제 부하를 발생시키겠습니다.
# 반복 접속 1 (파드1 IP로 접속) >> 증가 확인 후 중지
while true;do curl -s $PODIP; sleep 0.5; done

# 반복 접속 2 (서비스명 도메인으로 파드들 분산 접속) >> 증가 확인(몇개까지 증가되는가? 그 이유는?) 후 중지
## >> [scale back down] 중지 5분 후 파드 갯수 감소 확인
# Run this in a separate terminal
# so that the load generation continues and you can carry on with the rest of the steps
kubectl run -i --tty load-generator --rm --image=busybox:1.28 --restart=Never -- /bin/sh -c "while sleep 0.01; do wget -q -O- http://php-apache; done"

# --> CPU 사용률이 증가하면서 POD 가 7개까지 Scale Out이 되었습니다.






# Horizontal Pod Autoscaler Status Conditions
kubectl describe hpa
...
Events:
  Type    Reason             Age    From                       Message
  ----    ------             ----   ----                       -------
  Normal  SuccessfulRescale  13m    horizontal-pod-autoscaler  New size: 2; reason: cpu resource utilization (percentage of request) above target
  Normal  SuccessfulRescale  11m    horizontal-pod-autoscaler  New size: 3; reason: cpu resource utilization (percentage of request) above target
  Normal  SuccessfulRescale  11m    horizontal-pod-autoscaler  New size: 6; reason: cpu resource utilization (percentage of request) above target
  Normal  SuccessfulRescale  10m    horizontal-pod-autoscaler  New size: 8; reason: cpu resource utilization (percentage of request) above target
  Normal  SuccessfulRescale  5m35s  horizontal-pod-autoscaler  New size: 7; reason: All metrics below target
  Normal  SuccessfulRescale  4m35s  horizontal-pod-autoscaler  New size: 5; reason: All metrics below target
  Normal  SuccessfulRescale  4m5s   horizontal-pod-autoscaler  New size: 2; reason: All metrics below target
  Normal  SuccessfulRescale  3m50s  horizontal-pod-autoscaler  New size: 1; reason: All metrics below target

 

  • (참고) HPA 프로메테우스 메트릭
kube_horizontalpodautoscaler_status_current_replicas
kube_horizontalpodautoscaler_status_desired_replicas
kube_horizontalpodautoscaler_status_target_metric
kube_horizontalpodautoscaler_status_condition

kube_horizontalpodautoscaler_spec_target_metric
kube_horizontalpodautoscaler_spec_min_replicas
kube_horizontalpodautoscaler_spec_max_replicas

# [운영서버 EC2]
kubectl get pod -n monitoring -l app.kubernetes.io/name=kube-state-metrics -owide
kubectl get pod -n monitoring -l app.kubernetes.io/name=kube-state-metrics -o jsonpath="{.items[*].status.podIP}"
PODIP=$(kubectl get pod -n monitoring -l app.kubernetes.io/name=kube-state-metrics -o jsonpath="{.items[*].status.podIP}")
curl -s http://$PODIP:8080/metrics | grep -i horizontalpodautoscaler | grep HELP
# HELP kube_horizontalpodautoscaler_info Information about this autoscaler.
# HELP kube_horizontalpodautoscaler_metadata_generation [STABLE] The generation observed by the HorizontalPodAutoscaler controller.
# HELP kube_horizontalpodautoscaler_spec_max_replicas [STABLE] Upper limit for the number of pods that can be set by the autoscaler; cannot be smaller than MinReplicas.
# HELP kube_horizontalpodautoscaler_spec_min_replicas [STABLE] Lower limit for the number of pods that can be set by the autoscaler, default 1.
# HELP kube_horizontalpodautoscaler_spec_target_metric The metric specifications used by this autoscaler when calculating the desired replica count.
# HELP kube_horizontalpodautoscaler_status_target_metric The current metric status used by this autoscaler when calculating the desired replica count.
# HELP kube_horizontalpodautoscaler_status_current_replicas [STABLE] Current number of replicas of pods managed by this autoscaler.
# HELP kube_horizontalpodautoscaler_status_desired_replicas [STABLE] Desired number of replicas of pods managed by this autoscaler.
# HELP kube_horizontalpodautoscaler_annotations Kubernetes annotations converted to Prometheus labels.
# HELP kube_horizontalpodautoscaler_labels [STABLE] Kubernetes labels converted to Prometheus labels.
# HELP kube_horizontalpodautoscaler_status_condition [STABLE] The condition of this autoscaler.

curl -s http://$PODIP:8080/metrics | grep -i horizontalpodautoscaler
...

 

  • 실습 리소스 삭제
[root@operator-host ~]# kubectl delete deploy,svc,hpa,pod --all
deployment.apps "php-apache" deleted
service "kubernetes" deleted
service "php-apache" deleted
horizontalpodautoscaler.autoscaling "php-apache" deleted
pod "php-apache-d87b7ff46-62z46" deleted
pod "php-apache-d87b7ff46-dqb22" deleted
pod "php-apache-d87b7ff46-gtvsd" deleted
pod "php-apache-d87b7ff46-jd6ls" deleted
pod "php-apache-d87b7ff46-tt858" deleted
pod "php-apache-d87b7ff46-wtg6n" deleted
pod "php-apache-d87b7ff46-zwrtz" deleted

 

KEDA - Kubernetes based Event Driven Autoscaler

  • KEDA AutoScaler 소개 - Docs , DevOcean반면에 KEDA는 특정 이벤트를 기반으로 스케일 여부를 결정할 수 있습니다.이러한 이벤트를 활용하여 worker의 scale을 결정한다면 queue에 task가 많이 추가되는 시점에 더 빠르게 확장할 수 있습니다.
  • 예를 들어 airflow는 metadb를 통해 현재 실행 중이거나 대기 중인 task가 얼마나 존재하는지 알 수 있습니다.
  • 기존의 HPA(Horizontal Pod Autoscaler)는 리소스(CPU, Memory) 메트릭을 기반으로 스케일 여부를 결정하게 됩니다.

https://keda.sh/docs/2.10/concepts/

  1. Agent — KEDA activates and deactivates Kubernetes Deployments to scale to and from zero on no events. This is one of the primary roles of the keda-operator container that runs when you install KEDA.
  2. Metrics — KEDA acts as a Kubernetes metrics server that exposes rich event data like queue length or stream lag to the Horizontal Pod Autoscaler to drive scale out. It is up to the Deployment to consume the events directly from the source. This preserves rich event integration and enables gestures like completing or abandoning queue messages to work out of the box. The metric serving is the primary role of the keda-operator-metrics-apiserver container that runs when you install KEDA.
  3. Admission Webhooks - Automatically validate resource changes to prevent misconfiguration and enforce best practices by using an admission controller. As an example, it will prevent multiple ScaledObjects to target the same scale target. keda-admission-webhooks
kubectl get pod -n keda
NAME                                               READY   STATUS    RESTARTS      AGE
keda-operator-6bdffdc78-5rqnp                      1/1     Running   1 (11m ago)   11m
keda-operator-metrics-apiserver-74d844d769-2vrcq   1/1     Running   0             11m
keda-admission-webhooks-86cffccbf5-kmb7v           1/1     Running   0             11m

예) KEDA Scalers : kafka trigger for an Apache Kafka topic - Link

 

KEDA | Apache Kafka

Scale applications based on an Apache Kafka topic or other services that support Kafka protocol.

keda.sh

triggers:
- type: kafka
  metadata:
    bootstrapServers: kafka.svc:9092
    consumerGroup: my-group
    topic: test-topic
    lagThreshold: '5' # Average target value to trigger scaling actions. (Default: 5, Optional)
    activationLagThreshold: '3' #  Target value for activating the scaler. Learn more about activation here.
    offsetResetPolicy: latest
    allowIdleConsumers: false
    scaleToZeroOnInvalidOffset: false
    excludePersistentLag: false
    limitToPartitionsWithLag: false
    version: 1.0.0
    partitionLimitation: '1,2,10-20,31'
    sasl: plaintext
    tls: enable
    unsafeSsl: 'false'

 

{
  "annotations": {
    "list": [
      {
        "builtIn": 1,
        "datasource": {
          "type": "grafana",
          "uid": "-- Grafana --"
        },
        "enable": true,
        "hide": true,
        "iconColor": "rgba(0, 211, 255, 1)",
        "name": "Annotations & Alerts",
        "target": {
          "limit": 100,
          "matchAny": false,
          "tags": [],
          "type": "dashboard"
        },
        "type": "dashboard"
      }
    ]
  },
  "description": "Visualize metrics provided by KEDA",
  "editable": true,
  "fiscalYearStartMonth": 0,
  "graphTooltip": 0,
  "id": 1653,
  "links": [],
  "liveNow": false,
  "panels": [
    {
      "collapsed": false,
      "gridPos": {
        "h": 1,
        "w": 24,
        "x": 0,
        "y": 0
      },
      "id": 8,
      "panels": [],
      "title": "Metric Server",
      "type": "row"
    },
    {
      "datasource": {
        "type": "prometheus",
        "uid": "${datasource}"
      },
      "description": "The total number of errors encountered for all scalers.",
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "palette-classic"
          },
          "custom": {
            "axisCenteredZero": false,
            "axisColorMode": "text",
            "axisLabel": "",
            "axisPlacement": "auto",
            "barAlignment": 0,
            "drawStyle": "line",
            "fillOpacity": 25,
            "gradientMode": "opacity",
            "hideFrom": {
              "legend": false,
              "tooltip": false,
              "viz": false
            },
            "lineInterpolation": "linear",
            "lineWidth": 2,
            "pointSize": 5,
            "scaleDistribution": {
              "type": "linear"
            },
            "showPoints": "never",
            "spanNulls": true,
            "stacking": {
              "group": "A",
              "mode": "none"
            },
            "thresholdsStyle": {
              "mode": "off"
            }
          },
          "mappings": [],
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "green",
                "value": null
              },
              {
                "color": "red",
                "value": 80
              }
            ]
          },
          "unit": "Errors/sec"
        },
        "overrides": [
          {
            "matcher": {
              "id": "byName",
              "options": "http-demo"
            },
            "properties": [
              {
                "id": "color",
                "value": {
                  "fixedColor": "red",
                  "mode": "fixed"
                }
              }
            ]
          },
          {
            "matcher": {
              "id": "byName",
              "options": "scaledObject"
            },
            "properties": [
              {
                "id": "color",
                "value": {
                  "fixedColor": "red",
                  "mode": "fixed"
                }
              }
            ]
          },
          {
            "matcher": {
              "id": "byName",
              "options": "keda-system/keda-operator-metrics-apiserver"
            },
            "properties": [
              {
                "id": "color",
                "value": {
                  "fixedColor": "red",
                  "mode": "fixed"
                }
              }
            ]
          }
        ]
      },
      "gridPos": {
        "h": 9,
        "w": 8,
        "x": 0,
        "y": 1
      },
      "id": 4,
      "options": {
        "legend": {
          "calcs": [],
          "displayMode": "list",
          "placement": "bottom",
          "showLegend": true
        },
        "tooltip": {
          "mode": "single",
          "sort": "none"
        }
      },
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "editorMode": "code",
          "expr": "sum by(job) (rate(keda_scaler_detail_errors_total{}[5m]))",
          "legendFormat": "{{ job }}",
          "range": true,
          "refId": "A"
        }
      ],
      "title": "Scaler Total Errors",
      "type": "timeseries"
    },
    {
      "datasource": {
        "type": "prometheus",
        "uid": "${datasource}"
      },
      "description": "The number of errors that have occurred for each scaler.",
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "palette-classic"
          },
          "custom": {
            "axisCenteredZero": false,
            "axisColorMode": "text",
            "axisLabel": "",
            "axisPlacement": "auto",
            "barAlignment": 0,
            "drawStyle": "line",
            "fillOpacity": 25,
            "gradientMode": "opacity",
            "hideFrom": {
              "legend": false,
              "tooltip": false,
              "viz": false
            },
            "lineInterpolation": "linear",
            "lineWidth": 2,
            "pointSize": 5,
            "scaleDistribution": {
              "type": "linear"
            },
            "showPoints": "never",
            "spanNulls": true,
            "stacking": {
              "group": "A",
              "mode": "none"
            },
            "thresholdsStyle": {
              "mode": "off"
            }
          },
          "mappings": [],
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "green",
                "value": null
              },
              {
                "color": "red",
                "value": 80
              }
            ]
          },
          "unit": "Errors/sec"
        },
        "overrides": [
          {
            "matcher": {
              "id": "byName",
              "options": "http-demo"
            },
            "properties": [
              {
                "id": "color",
                "value": {
                  "fixedColor": "red",
                  "mode": "fixed"
                }
              }
            ]
          },
          {
            "matcher": {
              "id": "byName",
              "options": "scaler"
            },
            "properties": [
              {
                "id": "color",
                "value": {
                  "fixedColor": "red",
                  "mode": "fixed"
                }
              }
            ]
          },
          {
            "matcher": {
              "id": "byName",
              "options": "prometheusScaler"
            },
            "properties": [
              {
                "id": "color",
                "value": {
                  "fixedColor": "red",
                  "mode": "fixed"
                }
              }
            ]
          }
        ]
      },
      "gridPos": {
        "h": 9,
        "w": 8,
        "x": 8,
        "y": 1
      },
      "id": 3,
      "options": {
        "legend": {
          "calcs": [],
          "displayMode": "list",
          "placement": "bottom",
          "showLegend": true
        },
        "tooltip": {
          "mode": "single",
          "sort": "none"
        }
      },
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "editorMode": "code",
          "expr": "sum by(scaler) (rate(keda_scaler_detail_errors_total{exported_namespace=~\"$namespace\", scaledObject=~\"$scaledObject\", scaler=~\"$scaler\"}[5m]))",
          "legendFormat": "{{ scaler }}",
          "range": true,
          "refId": "A"
        }
      ],
      "title": "Scaler Errors",
      "type": "timeseries"
    },
    {
      "datasource": {
        "type": "prometheus",
        "uid": "${datasource}"
      },
      "description": "The number of errors that have occurred for each scaled object.",
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "palette-classic"
          },
          "custom": {
            "axisCenteredZero": false,
            "axisColorMode": "text",
            "axisLabel": "",
            "axisPlacement": "auto",
            "barAlignment": 0,
            "drawStyle": "line",
            "fillOpacity": 25,
            "gradientMode": "opacity",
            "hideFrom": {
              "legend": false,
              "tooltip": false,
              "viz": false
            },
            "lineInterpolation": "linear",
            "lineWidth": 2,
            "pointSize": 5,
            "scaleDistribution": {
              "type": "linear"
            },
            "showPoints": "never",
            "spanNulls": true,
            "stacking": {
              "group": "A",
              "mode": "none"
            },
            "thresholdsStyle": {
              "mode": "off"
            }
          },
          "mappings": [],
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "green",
                "value": null
              },
              {
                "color": "red",
                "value": 80
              }
            ]
          },
          "unit": "Errors/sec"
        },
        "overrides": [
          {
            "matcher": {
              "id": "byName",
              "options": "http-demo"
            },
            "properties": [
              {
                "id": "color",
                "value": {
                  "fixedColor": "red",
                  "mode": "fixed"
                }
              }
            ]
          }
        ]
      },
      "gridPos": {
        "h": 9,
        "w": 8,
        "x": 16,
        "y": 1
      },
      "id": 2,
      "options": {
        "legend": {
          "calcs": [],
          "displayMode": "list",
          "placement": "bottom",
          "showLegend": true
        },
        "tooltip": {
          "mode": "single",
          "sort": "none"
        }
      },
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "editorMode": "code",
          "expr": "sum by(scaledObject) (rate(keda_scaled_object_errors_total{exported_namespace=~\"$namespace\", scaledObject=~\"$scaledObject\"}[5m]))",
          "legendFormat": "{{ scaledObject }}",
          "range": true,
          "refId": "A"
        }
      ],
      "title": "Scaled Object Errors",
      "type": "timeseries"
    },
    {
      "collapsed": false,
      "gridPos": {
        "h": 1,
        "w": 24,
        "x": 0,
        "y": 10
      },
      "id": 10,
      "panels": [],
      "title": "Scale Target",
      "type": "row"
    },
    {
      "datasource": {
        "type": "prometheus",
        "uid": "${datasource}"
      },
      "description": "The current value for each scaler’s metric that would be used by the HPA in computing the target average.",
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "palette-classic"
          },
          "custom": {
            "axisCenteredZero": false,
            "axisColorMode": "text",
            "axisLabel": "",
            "axisPlacement": "auto",
            "barAlignment": 0,
            "drawStyle": "line",
            "fillOpacity": 25,
            "gradientMode": "opacity",
            "hideFrom": {
              "legend": false,
              "tooltip": false,
              "viz": false
            },
            "lineInterpolation": "linear",
            "lineWidth": 2,
            "pointSize": 5,
            "scaleDistribution": {
              "type": "linear"
            },
            "showPoints": "never",
            "spanNulls": true,
            "stacking": {
              "group": "A",
              "mode": "none"
            },
            "thresholdsStyle": {
              "mode": "off"
            }
          },
          "mappings": [],
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "green",
                "value": null
              },
              {
                "color": "red",
                "value": 80
              }
            ]
          },
          "unit": "none"
        },
        "overrides": [
          {
            "matcher": {
              "id": "byName",
              "options": "http-demo"
            },
            "properties": [
              {
                "id": "color",
                "value": {
                  "fixedColor": "blue",
                  "mode": "fixed"
                }
              }
            ]
          }
        ]
      },
      "gridPos": {
        "h": 9,
        "w": 24,
        "x": 0,
        "y": 11
      },
      "id": 5,
      "options": {
        "legend": {
          "calcs": [],
          "displayMode": "list",
          "placement": "bottom",
          "showLegend": true
        },
        "tooltip": {
          "mode": "single",
          "sort": "none"
        }
      },
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "editorMode": "code",
          "expr": "sum by(metric) (keda_scaler_metrics_value{exported_namespace=~\"$namespace\", metric=~\"$metric\", scaledObject=~\"$scaledObject\"})",
          "legendFormat": "{{ metric }}",
          "range": true,
          "refId": "A"
        }
      ],
      "title": "Scaler Metric Value",
      "type": "timeseries"
    },
    {
      "datasource": {
        "type": "prometheus",
        "uid": "${datasource}"
      },
      "description": "shows current replicas against max ones based on time difference",
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "palette-classic"
          },
          "custom": {
            "axisCenteredZero": false,
            "axisColorMode": "text",
            "axisLabel": "",
            "axisPlacement": "auto",
            "barAlignment": 0,
            "drawStyle": "line",
            "fillOpacity": 21,
            "gradientMode": "opacity",
            "hideFrom": {
              "legend": false,
              "tooltip": false,
              "viz": false
            },
            "lineInterpolation": "linear",
            "lineStyle": {
              "fill": "solid"
            },
            "lineWidth": 1,
            "pointSize": 5,
            "scaleDistribution": {
              "type": "linear"
            },
            "showPoints": "auto",
            "spanNulls": false,
            "stacking": {
              "group": "A",
              "mode": "none"
            },
            "thresholdsStyle": {
              "mode": "off"
            }
          },
          "mappings": [],
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "green",
                "value": null
              }
            ]
          },
          "unit": "short"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 8,
        "w": 24,
        "x": 0,
        "y": 20
      },
      "id": 13,
      "options": {
        "legend": {
          "calcs": [],
          "displayMode": "list",
          "placement": "bottom",
          "showLegend": true
        },
        "tooltip": {
          "mode": "single",
          "sort": "none"
        }
      },
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "editorMode": "code",
          "exemplar": false,
          "expr": "kube_horizontalpodautoscaler_status_current_replicas{namespace=\"$namespace\",horizontalpodautoscaler=~\"keda-hpa-$scaledObject\"}",
          "format": "time_series",
          "instant": false,
          "interval": "",
          "legendFormat": "current_replicas",
          "range": true,
          "refId": "A"
        },
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "editorMode": "code",
          "exemplar": false,
          "expr": "kube_horizontalpodautoscaler_spec_max_replicas{namespace=\"$namespace\",horizontalpodautoscaler=~\"keda-hpa-$scaledObject\"}",
          "format": "time_series",
          "hide": false,
          "instant": false,
          "legendFormat": "max_replicas",
          "range": true,
          "refId": "B"
        }
      ],
      "title": "Current/max replicas (time based)",
      "type": "timeseries"
    },
    {
      "datasource": {
        "type": "prometheus",
        "uid": "${datasource}"
      },
      "description": "shows current replicas against max ones based on time difference",
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "continuous-GrYlRd"
          },
          "custom": {
            "fillOpacity": 70,
            "lineWidth": 0,
            "spanNulls": false
          },
          "mappings": [
            {
              "options": {
                "0": {
                  "color": "green",
                  "index": 0,
                  "text": "No scaling"
                }
              },
              "type": "value"
            },
            {
              "options": {
                "from": -200,
                "result": {
                  "color": "light-red",
                  "index": 1,
                  "text": "Scaling down"
                },
                "to": 0
              },
              "type": "range"
            },
            {
              "options": {
                "from": 0,
                "result": {
                  "color": "semi-dark-red",
                  "index": 2,
                  "text": "Scaling up"
                },
                "to": 200
              },
              "type": "range"
            }
          ],
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "green",
                "value": null
              }
            ]
          },
          "unit": "none"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 8,
        "w": 24,
        "x": 0,
        "y": 28
      },
      "id": 16,
      "options": {
        "alignValue": "left",
        "legend": {
          "displayMode": "list",
          "placement": "bottom",
          "showLegend": false,
          "width": 0
        },
        "mergeValues": true,
        "rowHeight": 1,
        "showValue": "never",
        "tooltip": {
          "mode": "single",
          "sort": "none"
        }
      },
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "editorMode": "code",
          "exemplar": false,
          "expr": "delta(kube_horizontalpodautoscaler_status_current_replicas{namespace=\"$namespace\",horizontalpodautoscaler=~\"keda-hpa-$scaledObject\"}[1m])",
          "format": "time_series",
          "instant": false,
          "interval": "",
          "legendFormat": ".",
          "range": true,
          "refId": "A"
        }
      ],
      "title": "Changes in replicas",
      "type": "state-timeline"
    },
    {
      "datasource": {
        "type": "prometheus",
        "uid": "${datasource}"
      },
      "description": "shows current replicas against max ones",
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "thresholds"
          },
          "mappings": [],
          "min": 0,
          "thresholds": {
            "mode": "percentage",
            "steps": [
              {
                "color": "green"
              },
              {
                "color": "red",
                "value": 80
              }
            ]
          },
          "unit": "short"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 8,
        "w": 12,
        "x": 0,
        "y": 36
      },
      "id": 15,
      "options": {
        "orientation": "auto",
        "reduceOptions": {
          "calcs": [
            "lastNotNull"
          ],
          "fields": "/^current_replicas$/",
          "values": false
        },
        "showThresholdLabels": false,
        "showThresholdMarkers": true
      },
      "pluginVersion": "9.5.2",
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "editorMode": "code",
          "exemplar": false,
          "expr": "kube_horizontalpodautoscaler_status_current_replicas{namespace=\"$namespace\",horizontalpodautoscaler=~\"keda-hpa-$scaledObject\"}",
          "instant": true,
          "legendFormat": "current_replicas",
          "range": false,
          "refId": "A"
        },
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "editorMode": "code",
          "exemplar": false,
          "expr": "kube_horizontalpodautoscaler_spec_max_replicas{namespace=\"$namespace\",horizontalpodautoscaler=~\"keda-hpa-$scaledObject\"}",
          "hide": false,
          "instant": true,
          "legendFormat": "max_replicas",
          "range": false,
          "refId": "B"
        }
      ],
      "title": "Current/max replicas",
      "type": "gauge"
    }
  ],
  "refresh": "1m",
  "schemaVersion": 38,
  "style": "dark",
  "tags": [],
  "templating": {
    "list": [
      {
        "current": {
          "selected": false,
          "text": "Prometheus",
          "value": "Prometheus"
        },
        "hide": 0,
        "includeAll": false,
        "multi": false,
        "name": "datasource",
        "options": [],
        "query": "prometheus",
        "queryValue": "",
        "refresh": 1,
        "regex": "",
        "skipUrlSync": false,
        "type": "datasource"
      },
      {
        "current": {
          "selected": false,
          "text": "bhe-test",
          "value": "bhe-test"
        },
        "datasource": {
          "type": "prometheus",
          "uid": "${datasource}"
        },
        "definition": "label_values(keda_scaler_active,exported_namespace)",
        "hide": 0,
        "includeAll": false,
        "multi": false,
        "name": "namespace",
        "options": [],
        "query": {
          "query": "label_values(keda_scaler_active,exported_namespace)",
          "refId": "PrometheusVariableQueryEditor-VariableQuery"
        },
        "refresh": 1,
        "regex": "",
        "skipUrlSync": false,
        "sort": 1,
        "type": "query"
      },
      {
        "current": {
          "selected": false,
          "text": "All",
          "value": "$__all"
        },
        "datasource": {
          "type": "prometheus",
          "uid": "${datasource}"
        },
        "definition": "label_values(keda_scaler_active{exported_namespace=\"$namespace\"},scaledObject)",
        "hide": 0,
        "includeAll": true,
        "multi": true,
        "name": "scaledObject",
        "options": [],
        "query": {
          "query": "label_values(keda_scaler_active{exported_namespace=\"$namespace\"},scaledObject)",
          "refId": "PrometheusVariableQueryEditor-VariableQuery"
        },
        "refresh": 2,
        "regex": "",
        "skipUrlSync": false,
        "sort": 0,
        "type": "query"
      },
      {
        "current": {
          "selected": false,
          "text": "cronScaler",
          "value": "cronScaler"
        },
        "datasource": {
          "type": "prometheus",
          "uid": "${datasource}"
        },
        "definition": "label_values(keda_scaler_active{exported_namespace=\"$namespace\"},scaler)",
        "hide": 0,
        "includeAll": false,
        "multi": false,
        "name": "scaler",
        "options": [],
        "query": {
          "query": "label_values(keda_scaler_active{exported_namespace=\"$namespace\"},scaler)",
          "refId": "PrometheusVariableQueryEditor-VariableQuery"
        },
        "refresh": 2,
        "regex": "",
        "skipUrlSync": false,
        "sort": 0,
        "type": "query"
      },
      {
        "current": {
          "selected": false,
          "text": "s0-cron-Etc-UTC-40xxxx-55xxxx",
          "value": "s0-cron-Etc-UTC-40xxxx-55xxxx"
        },
        "datasource": {
          "type": "prometheus",
          "uid": "${datasource}"
        },
        "definition": "label_values(keda_scaler_active{exported_namespace=\"$namespace\"},metric)",
        "hide": 0,
        "includeAll": false,
        "multi": false,
        "name": "metric",
        "options": [],
        "query": {
          "query": "label_values(keda_scaler_active{exported_namespace=\"$namespace\"},metric)",
          "refId": "PrometheusVariableQueryEditor-VariableQuery"
        },
        "refresh": 2,
        "regex": "",
        "skipUrlSync": false,
        "sort": 0,
        "type": "query"
      }
    ]
  },
  "time": {
    "from": "now-24h",
    "to": "now"
  },
  "timepicker": {},
  "timezone": "",
  "title": "KEDA",
  "uid": "asdasd8rvmMxdVk",
  "version": 8,
  "weekStart": ""
}

 

 

# 설치 전 기존 metrics-server 제공 Metris API 확인
kubectl get --raw "/apis/metrics.k8s.io" -v=6 | jq
kubectl get --raw "/apis/metrics.k8s.io" | jq
{
  "kind": "APIGroup",
  "apiVersion": "v1",
  "name": "metrics.k8s.io",
  ...


# KEDA 설치 : serviceMonitor 만으로도 충분할듯..
cat <<EOT > keda-values.yaml
metricsServer:
  useHostNetwork: true

prometheus:
  metricServer:
    enabled: true
    port: 9022
    portName: metrics
    path: /metrics
    serviceMonitor:
      # Enables ServiceMonitor creation for the Prometheus Operator
      enabled: true
    podMonitor:
      # Enables PodMonitor creation for the Prometheus Operator
      enabled: true
  operator:
    enabled: true
    port: 8080
    serviceMonitor:
      # Enables ServiceMonitor creation for the Prometheus Operator
      enabled: true
    podMonitor:
      # Enables PodMonitor creation for the Prometheus Operator
      enabled: true
  webhooks:
    enabled: true
    port: 8020
    serviceMonitor:
      # Enables ServiceMonitor creation for the Prometheus webhooks
      enabled: true

EOT

helm repo add kedacore https://kedacore.github.io/charts
helm repo update
helm install keda kedacore/keda --version 2.16.0 --namespace keda --create-namespace -f keda-values.yaml

# KEDA 설치 확인
[root@operator-host ~]# kubectl get crd | grep keda
cloudeventsources.eventing.keda.sh           2025-03-08T13:40:25Z
clustercloudeventsources.eventing.keda.sh    2025-03-08T13:40:25Z
clustertriggerauthentications.keda.sh        2025-03-08T13:40:25Z
scaledjobs.keda.sh                           2025-03-08T13:40:25Z
scaledobjects.keda.sh                        2025-03-08T13:40:25Z
triggerauthentications.keda.sh               2025-03-08T13:40:25Z

[root@operator-host ~]# kubectl get all -n keda
NAME                                                   READY   STATUS    RESTARTS       AGE
pod/keda-admission-webhooks-86cffccbf5-84c5m           1/1     Running   0              3m17s
pod/keda-operator-6bdffdc78-9v76f                      1/1     Running   1 (3m4s ago)   3m17s
pod/keda-operator-metrics-apiserver-74d844d769-vdvcr   1/1     Running   0              3m17s

NAME                                      TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)             AGE
service/keda-admission-webhooks           ClusterIP   10.100.214.210   <none>        443/TCP,8020/TCP    3m18s
service/keda-operator                     ClusterIP   10.100.2.93      <none>        9666/TCP,8080/TCP   3m18s
service/keda-operator-metrics-apiserver   ClusterIP   10.100.7.86      <none>        443/TCP,9022/TCP    3m18s

NAME                                              READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/keda-admission-webhooks           1/1     1            1           3m18s
deployment.apps/keda-operator                     1/1     1            1           3m18s
deployment.apps/keda-operator-metrics-apiserver   1/1     1            1           3m18s

NAME                                                         DESIRED   CURRENT   READY   AGE
replicaset.apps/keda-admission-webhooks-86cffccbf5           1         1         1       3m17s
replicaset.apps/keda-operator-6bdffdc78                      1         1         1       3m17s
replicaset.apps/keda-operator-metrics-apiserver-74d844d769   1         1         1       3m17s

[root@operator-host ~]# kubectl get validatingwebhookconfigurations keda-admission -o yaml
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
  annotations:
    meta.helm.sh/release-name: keda
    meta.helm.sh/release-namespace: keda
  creationTimestamp: "2025-03-08T13:40:26Z"
  generation: 2
  labels:
    app.kubernetes.io/component: operator
    app.kubernetes.io/instance: keda
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/name: keda-admission-webhooks
    app.kubernetes.io/part-of: keda-operator
    app.kubernetes.io/version: 2.16.0
    helm.sh/chart: keda-2.16.0
  name: keda-admission
  resourceVersion: "102153"
  uid: 662907eb-01fa-4544-9778-3d16f160adb1
webhooks:
- admissionReviewVersions:
  - v1
  clientConfig:
    caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURFRENDQWZpZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFoTVJBd0RnWURWUVFLRXdkTFJVUkIKVDFKSE1RMHdDd1lEVlFRREV3UkxSVVJCTUI0WERUSTFNRE13T0RFeU5EQXpPRm9YRFRNMU1ETXdOakV6TkRBegpPRm93SVRFUU1BNEdBMVVFQ2hNSFMwVkVRVTlTUnpFTk1Bc0dBMVVFQXhNRVMwVkVRVENDQVNJd0RRWUpLb1pJCmh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTVdkcnlGWW5FMndaTDU2bG95dmdDc0c5ZmoxcUhtRld2QXQKM25KeVVibnhPYnQ4Wi9yR29pU2tkTGpQRHVlUGNoMUdGeE11OXNUeEljUlhCRDR1cGZKYW5za1VncHZYRDE1dgo0R2Fob24wekJCcmF4bkRFREVydjZ1ZXhpbmMyaXhCY0UwcEVHMjUvRnZHYi9yRVh6eUlPOGlnYVg1eXUyNWtOCk5VRERFV0RONzZ4QVZMbytIaUlranJWSERHMHhYZitSTXVuaVAzYktpb1lxZWVuLzF5ZURBNWwweTBGVERIYzcKWm1qdFRQam5zdFVHSnQxaVlPZm9oM2Nqbm04SnRQWlBydVQyeTk0MmxJMElxZHZGRkkwK0ExVFg5WE1BOExuNQpXRVNIQTF5VllVVjF5WjJaZUovc3RCRGJJVGRsbGxRbEFXS2ZCOHgyZ0ZjaEVDL01aeFVDQXdFQUFhTlRNRkV3CkRnWURWUjBQQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCL3dRRk1BTUJBZjh3SFFZRFZSME9CQllFRkZrWHhwSWcKdkhwTlYrTVNpbm9RcUNxUzlISVVNQThHQTFVZEVRUUlNQWFDQkV0RlJFRXdEUVlKS29aSWh2Y05BUUVMQlFBRApnZ0VCQUx5cjVFVlExWEdkMXYzdDROT25mMWE1NnQ5QWhvM3NlcG15UlMrODQrZ2VsN1FUT3ZtdnYxcDNXNURVCkx5eHRGSTNYR0p6S21tdWV2NFU3K2hsSENBU0FQNkRoRktDTFdrWmhJUVJLV0d0SGIzWXFlU09hNE4rTkdhdHkKQVNJTGM1K3htb2NvQzdWaU90aDVtVjYyQWcwdmhIdXJQRWV1TUpDeitoZlFJZmFHQXU0MUdMSDhDVXlnRVNKWQpGZVRTUUdrcCsrazJwQmszcVlzY1czSWNobDV0VWE1MTU2TDAyZlhEQUJLeDJHSDdKa0RETzRwcU4zL2RCV2VhCmo0SVdzcGtJUloyOERWc1EzakZ2SEM1R0dZa3duMVFRRU9jeVlnSjhGS2Q4VTFGbU1VckNMSGhZOUtNRWwvODkKUEk5VUIzbmVmMTAwV3UyQUc5WTdWWkNJRGdnPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
    service:
      name: keda-admission-webhooks
      namespace: keda
      path: /validate-keda-sh-v1alpha1-scaledobject
      port: 443
  failurePolicy: Ignore
  matchPolicy: Equivalent
  name: vscaledobject.kb.io
  namespaceSelector: {}
  objectSelector: {}
  rules:
  - apiGroups:
    - keda.sh
    apiVersions:
    - v1alpha1
    operations:
    - CREATE
    - UPDATE
    resources:
    - scaledobjects
    scope: '*'
  sideEffects: None
  timeoutSeconds: 10
- admissionReviewVersions:
  - v1
  clientConfig:
    caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURFRENDQWZpZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFoTVJBd0RnWURWUVFLRXdkTFJVUkIKVDFKSE1RMHdDd1lEVlFRREV3UkxSVVJCTUI0WERUSTFNRE13T0RFeU5EQXpPRm9YRFRNMU1ETXdOakV6TkRBegpPRm93SVRFUU1BNEdBMVVFQ2hNSFMwVkVRVTlTUnpFTk1Bc0dBMVVFQXhNRVMwVkVRVENDQVNJd0RRWUpLb1pJCmh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTVdkcnlGWW5FMndaTDU2bG95dmdDc0c5ZmoxcUhtRld2QXQKM25KeVVibnhPYnQ4Wi9yR29pU2tkTGpQRHVlUGNoMUdGeE11OXNUeEljUlhCRDR1cGZKYW5za1VncHZYRDE1dgo0R2Fob24wekJCcmF4bkRFREVydjZ1ZXhpbmMyaXhCY0UwcEVHMjUvRnZHYi9yRVh6eUlPOGlnYVg1eXUyNWtOCk5VRERFV0RONzZ4QVZMbytIaUlranJWSERHMHhYZitSTXVuaVAzYktpb1lxZWVuLzF5ZURBNWwweTBGVERIYzcKWm1qdFRQam5zdFVHSnQxaVlPZm9oM2Nqbm04SnRQWlBydVQyeTk0MmxJMElxZHZGRkkwK0ExVFg5WE1BOExuNQpXRVNIQTF5VllVVjF5WjJaZUovc3RCRGJJVGRsbGxRbEFXS2ZCOHgyZ0ZjaEVDL01aeFVDQXdFQUFhTlRNRkV3CkRnWURWUjBQQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCL3dRRk1BTUJBZjh3SFFZRFZSME9CQllFRkZrWHhwSWcKdkhwTlYrTVNpbm9RcUNxUzlISVVNQThHQTFVZEVRUUlNQWFDQkV0RlJFRXdEUVlKS29aSWh2Y05BUUVMQlFBRApnZ0VCQUx5cjVFVlExWEdkMXYzdDROT25mMWE1NnQ5QWhvM3NlcG15UlMrODQrZ2VsN1FUT3ZtdnYxcDNXNURVCkx5eHRGSTNYR0p6S21tdWV2NFU3K2hsSENBU0FQNkRoRktDTFdrWmhJUVJLV0d0SGIzWXFlU09hNE4rTkdhdHkKQVNJTGM1K3htb2NvQzdWaU90aDVtVjYyQWcwdmhIdXJQRWV1TUpDeitoZlFJZmFHQXU0MUdMSDhDVXlnRVNKWQpGZVRTUUdrcCsrazJwQmszcVlzY1czSWNobDV0VWE1MTU2TDAyZlhEQUJLeDJHSDdKa0RETzRwcU4zL2RCV2VhCmo0SVdzcGtJUloyOERWc1EzakZ2SEM1R0dZa3duMVFRRU9jeVlnSjhGS2Q4VTFGbU1VckNMSGhZOUtNRWwvODkKUEk5VUIzbmVmMTAwV3UyQUc5WTdWWkNJRGdnPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
    service:
      name: keda-admission-webhooks
      namespace: keda
      path: /validate-keda-sh-v1alpha1-triggerauthentication
      port: 443
  failurePolicy: Ignore
  matchPolicy: Equivalent
  name: vstriggerauthentication.kb.io
  namespaceSelector: {}
  objectSelector: {}
  rules:
  - apiGroups:
    - keda.sh
    apiVersions:
    - v1alpha1
    operations:
    - CREATE
    - UPDATE
    resources:
    - triggerauthentications
    scope: '*'
  sideEffects: None
  timeoutSeconds: 10
- admissionReviewVersions:
  - v1
  clientConfig:
    caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURFRENDQWZpZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFoTVJBd0RnWURWUVFLRXdkTFJVUkIKVDFKSE1RMHdDd1lEVlFRREV3UkxSVVJCTUI0WERUSTFNRE13T0RFeU5EQXpPRm9YRFRNMU1ETXdOakV6TkRBegpPRm93SVRFUU1BNEdBMVVFQ2hNSFMwVkVRVTlTUnpFTk1Bc0dBMVVFQXhNRVMwVkVRVENDQVNJd0RRWUpLb1pJCmh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTVdkcnlGWW5FMndaTDU2bG95dmdDc0c5ZmoxcUhtRld2QXQKM25KeVVibnhPYnQ4Wi9yR29pU2tkTGpQRHVlUGNoMUdGeE11OXNUeEljUlhCRDR1cGZKYW5za1VncHZYRDE1dgo0R2Fob24wekJCcmF4bkRFREVydjZ1ZXhpbmMyaXhCY0UwcEVHMjUvRnZHYi9yRVh6eUlPOGlnYVg1eXUyNWtOCk5VRERFV0RONzZ4QVZMbytIaUlranJWSERHMHhYZitSTXVuaVAzYktpb1lxZWVuLzF5ZURBNWwweTBGVERIYzcKWm1qdFRQam5zdFVHSnQxaVlPZm9oM2Nqbm04SnRQWlBydVQyeTk0MmxJMElxZHZGRkkwK0ExVFg5WE1BOExuNQpXRVNIQTF5VllVVjF5WjJaZUovc3RCRGJJVGRsbGxRbEFXS2ZCOHgyZ0ZjaEVDL01aeFVDQXdFQUFhTlRNRkV3CkRnWURWUjBQQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCL3dRRk1BTUJBZjh3SFFZRFZSME9CQllFRkZrWHhwSWcKdkhwTlYrTVNpbm9RcUNxUzlISVVNQThHQTFVZEVRUUlNQWFDQkV0RlJFRXdEUVlKS29aSWh2Y05BUUVMQlFBRApnZ0VCQUx5cjVFVlExWEdkMXYzdDROT25mMWE1NnQ5QWhvM3NlcG15UlMrODQrZ2VsN1FUT3ZtdnYxcDNXNURVCkx5eHRGSTNYR0p6S21tdWV2NFU3K2hsSENBU0FQNkRoRktDTFdrWmhJUVJLV0d0SGIzWXFlU09hNE4rTkdhdHkKQVNJTGM1K3htb2NvQzdWaU90aDVtVjYyQWcwdmhIdXJQRWV1TUpDeitoZlFJZmFHQXU0MUdMSDhDVXlnRVNKWQpGZVRTUUdrcCsrazJwQmszcVlzY1czSWNobDV0VWE1MTU2TDAyZlhEQUJLeDJHSDdKa0RETzRwcU4zL2RCV2VhCmo0SVdzcGtJUloyOERWc1EzakZ2SEM1R0dZa3duMVFRRU9jeVlnSjhGS2Q4VTFGbU1VckNMSGhZOUtNRWwvODkKUEk5VUIzbmVmMTAwV3UyQUc5WTdWWkNJRGdnPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
    service:
      name: keda-admission-webhooks
      namespace: keda
      path: /validate-keda-sh-v1alpha1-clustertriggerauthentication
      port: 443
  failurePolicy: Ignore
  matchPolicy: Equivalent
  name: vsclustertriggerauthentication.kb.io
  namespaceSelector: {}
  objectSelector: {}
  rules:
  - apiGroups:
    - keda.sh
    apiVersions:
    - v1alpha1
    operations:
    - CREATE
    - UPDATE
    resources:
    - clustertriggerauthentications
    scope: '*'
  sideEffects: None
  timeoutSeconds: 10


[root@operator-host ~]# kubectl get podmonitor,servicemonitors -n keda
NAME                                                               AGE
podmonitor.monitoring.coreos.com/keda-operator                     4m16s
podmonitor.monitoring.coreos.com/keda-operator-metrics-apiserver   4m16s

NAME                                                                   AGE
servicemonitor.monitoring.coreos.com/keda-admission-webhooks           4m16s
servicemonitor.monitoring.coreos.com/keda-operator                     4m16s
servicemonitor.monitoring.coreos.com/keda-operator-metrics-apiserver   4m16s

[root@operator-host ~]# kubectl get apiservice v1beta1.external.metrics.k8s.io -o yaml
apiVersion: apiregistration.k8s.io/v1
kind: APIService
metadata:
  annotations:
    meta.helm.sh/release-name: keda
    meta.helm.sh/release-namespace: keda
  creationTimestamp: "2025-03-08T13:40:25Z"
  labels:
    app.kubernetes.io/component: operator
    app.kubernetes.io/instance: keda
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/name: v1beta1.external.metrics.k8s.io
    app.kubernetes.io/part-of: keda-operator
    app.kubernetes.io/version: 2.16.0
    helm.sh/chart: keda-2.16.0
  name: v1beta1.external.metrics.k8s.io
  resourceVersion: "102154"
  uid: 76882ec1-33a8-4fce-a758-76738614b9ae
spec:
  caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURFRENDQWZpZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFoTVJBd0RnWURWUVFLRXdkTFJVUkIKVDFKSE1RMHdDd1lEVlFRREV3UkxSVVJCTUI0WERUSTFNRE13T0RFeU5EQXpPRm9YRFRNMU1ETXdOakV6TkRBegpPRm93SVRFUU1BNEdBMVVFQ2hNSFMwVkVRVTlTUnpFTk1Bc0dBMVVFQXhNRVMwVkVRVENDQVNJd0RRWUpLb1pJCmh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTVdkcnlGWW5FMndaTDU2bG95dmdDc0c5ZmoxcUhtRld2QXQKM25KeVVibnhPYnQ4Wi9yR29pU2tkTGpQRHVlUGNoMUdGeE11OXNUeEljUlhCRDR1cGZKYW5za1VncHZYRDE1dgo0R2Fob24wekJCcmF4bkRFREVydjZ1ZXhpbmMyaXhCY0UwcEVHMjUvRnZHYi9yRVh6eUlPOGlnYVg1eXUyNWtOCk5VRERFV0RONzZ4QVZMbytIaUlranJWSERHMHhYZitSTXVuaVAzYktpb1lxZWVuLzF5ZURBNWwweTBGVERIYzcKWm1qdFRQam5zdFVHSnQxaVlPZm9oM2Nqbm04SnRQWlBydVQyeTk0MmxJMElxZHZGRkkwK0ExVFg5WE1BOExuNQpXRVNIQTF5VllVVjF5WjJaZUovc3RCRGJJVGRsbGxRbEFXS2ZCOHgyZ0ZjaEVDL01aeFVDQXdFQUFhTlRNRkV3CkRnWURWUjBQQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCL3dRRk1BTUJBZjh3SFFZRFZSME9CQllFRkZrWHhwSWcKdkhwTlYrTVNpbm9RcUNxUzlISVVNQThHQTFVZEVRUUlNQWFDQkV0RlJFRXdEUVlKS29aSWh2Y05BUUVMQlFBRApnZ0VCQUx5cjVFVlExWEdkMXYzdDROT25mMWE1NnQ5QWhvM3NlcG15UlMrODQrZ2VsN1FUT3ZtdnYxcDNXNURVCkx5eHRGSTNYR0p6S21tdWV2NFU3K2hsSENBU0FQNkRoRktDTFdrWmhJUVJLV0d0SGIzWXFlU09hNE4rTkdhdHkKQVNJTGM1K3htb2NvQzdWaU90aDVtVjYyQWcwdmhIdXJQRWV1TUpDeitoZlFJZmFHQXU0MUdMSDhDVXlnRVNKWQpGZVRTUUdrcCsrazJwQmszcVlzY1czSWNobDV0VWE1MTU2TDAyZlhEQUJLeDJHSDdKa0RETzRwcU4zL2RCV2VhCmo0SVdzcGtJUloyOERWc1EzakZ2SEM1R0dZa3duMVFRRU9jeVlnSjhGS2Q4VTFGbU1VckNMSGhZOUtNRWwvODkKUEk5VUIzbmVmMTAwV3UyQUc5WTdWWkNJRGdnPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
  group: external.metrics.k8s.io
  groupPriorityMinimum: 100
  service:
    name: keda-operator-metrics-apiserver
    namespace: keda
    port: 443
  version: v1beta1
  versionPriority: 100
status:
  conditions:
  - lastTransitionTime: "2025-03-08T13:40:54Z"
    message: all checks passed
    reason: Passed
    status: "True"
    type: Available


# CPU/Mem은 기존 metrics-server 의존하여, KEDA metrics-server는 외부 이벤트 소스(Scaler) 메트릭을 노출
## https://keda.sh/docs/2.16/operate/metrics-server/
[root@operator-host ~]# kubectl get pod -n keda -l app=keda-operator-metrics-apiserver
NAME                                               READY   STATUS    RESTARTS   AGE
keda-operator-metrics-apiserver-74d844d769-vdvcr   1/1     Running   0          8m9s

# Querying metrics exposed by KEDA Metrics Server
[root@operator-host ~]# kubectl get --raw "/apis/external.metrics.k8s.io/v1beta1" | jq
{
  "kind": "APIResourceList",
  "apiVersion": "v1",
  "groupVersion": "external.metrics.k8s.io/v1beta1",
  "resources": [
    {
      "name": "externalmetrics",
      "singularName": "",
      "namespaced": true,
      "kind": "ExternalMetricValueList",
      "verbs": [
        "get"
      ]
    }
  ]
}

# keda 네임스페이스에 디플로이먼트 생성
[root@operator-host ~]# kubectl apply -f php-apache.yaml -n keda
deployment.apps/php-apache created
service/php-apache created
[root@operator-host ~]# kubectl get pod -n keda
NAME                                               READY   STATUS    RESTARTS      AGE
keda-admission-webhooks-86cffccbf5-84c5m           1/1     Running   0             13m
keda-operator-6bdffdc78-9v76f                      1/1     Running   1 (13m ago)   13m
keda-operator-metrics-apiserver-74d844d769-vdvcr   1/1     Running   0             13m
php-apache-d87b7ff46-tv2r6                         1/1     Running   0             61s

# ScaledObject 정책 생성 : cron
cat <<EOT > keda-cron.yaml
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: php-apache-cron-scaled
spec:
  minReplicaCount: 0
  maxReplicaCount: 2  # Specifies the maximum number of replicas to scale up to (defaults to 100).
  pollingInterval: 30  # Specifies how often KEDA should check for scaling events
  cooldownPeriod: 300  # Specifies the cool-down period in seconds after a scaling event
  scaleTargetRef:  # Identifies the Kubernetes deployment or other resource that should be scaled.
    apiVersion: apps/v1
    kind: Deployment
    name: php-apache
  triggers:  # Defines the specific configuration for your chosen scaler, including any required parameters or settings
  - type: cron
    metadata:
      timezone: Asia/Seoul
      start: 00,15,30,45 * * * *
      end: 05,20,35,50 * * * *
      desiredReplicas: "1"

EOT

[root@operator-host ~]# kubectl apply -f keda-cron.yaml -n keda
scaledobject.keda.sh/php-apache-cron-scaled created

# 그라파나 대시보드 추가 : 대시보드 상단에 namespace : keda 로 변경하기!

# 모니터링
watch -d 'kubectl get ScaledObject,hpa,pod -n keda'


# 확인
[root@operator-host ~]# kubectl get ScaledObject,hpa,pod -n keda
NAME                                          SCALETARGETKIND      SCALETARGETNAME   MIN   MAX   READY   ACTIVE   FALLBACK   PAUSED    TRIGGERS   AUTHENTICATIONS   AGE
scaledobject.keda.sh/php-apache-cron-scaled   apps/v1.Deployment   php-apache        0     2     True    True     False      Unknown                                6m26s

NAME                                                                  REFERENCE               TARGETS             MINPODS   MAXPODS   REPLICAS   AGE
horizontalpodautoscaler.autoscaling/keda-hpa-php-apache-cron-scaled   Deployment/php-apache   <unknown>/1 (avg)   1         2         1          6m26s

NAME                                                   READY   STATUS    RESTARTS      AGE
pod/keda-admission-webhooks-86cffccbf5-84c5m           1/1     Running   0             21m
pod/keda-operator-6bdffdc78-9v76f                      1/1     Running   1 (21m ago)   21m
pod/keda-operator-metrics-apiserver-74d844d769-vdvcr   1/1     Running   0             21m
pod/php-apache-d87b7ff46-bbbnb                         1/1     Running   0             115s


kubectl get hpa -o jsonpath="{.items[0].spec}" -n keda | jq
...
"metrics": [
    {
      "external": {
        "metric": {
          "name": "s0-cron-Asia-Seoul-00,15,30,45xxxx-05,20,35,50xxxx",
          "selector": {
            "matchLabels": {
              "scaledobject.keda.sh/name": "php-apache-cron-scaled"
            }
          }
        },
        "target": {
          "averageValue": "1",
          "type": "AverageValue"
        }
      },
      "type": "External"
    }

# KEDA 및 deployment 등 삭제
[root@operator-host ~]# kubectl delete ScaledObject -n keda php-apache-cron-scaled && kubectl delete deploy php-apache -n keda && helm uninstall keda -n keda
scaledobject.keda.sh "php-apache-cron-scaled" deleted
deployment.apps "php-apache" deleted
release "keda" uninstalled
[root@operator-host ~]# kubectl delete namespace keda
namespace "keda" deleted

 

VPA - Vertical Pod Autoscaler

VPA 소개 : pod resources.request을 최대한 최적값으로 수정

  • VPA는 HPA와 같이 사용할 수 없습니다.
  • VPA는 pod자원을 최적값으로 수정하기 위해 pod를 재실행(기존 pod를 종료하고 새로운 pod실행)합니다.
  • 계산 방식 : ‘기준값(파드가 동작하는데 필요한 최소한의 값)’ 결정 → ‘마진(약간의 적절한 버퍼)’ 추가 → 상세정리 Link
 

Pod CPU/Memory 리소스 최적화하기 (VPA 및 Kubecost 추천로직 분석)

 

devocean.sk.com

 

https://malwareanalysis.tistory.com/603
https://devocean.sk.com/blog/techBoardDetail.do?ID=164786

# [운영서버 EC2] 코드 다운로드
# git clone https://github.com/kubernetes/autoscaler.git # userdata 로 설치 되어 있음
[root@operator-host ~]# cd ~/autoscaler/vertical-pod-autoscaler/
[root@operator-host vertical-pod-autoscaler]# tree hack
hack
├── api-docs
│   └── config.yaml
├── boilerplate.go.txt
├── convert-alpha-objects.sh
├── deploy-for-e2e-locally.sh
├── deploy-for-e2e.sh
├── dev-deploy-locally.sh
├── e2e
│   ├── Dockerfile.externalmetrics-writer
│   ├── k8s-metrics-server.yaml
│   ├── metrics-pump.yaml
│   ├── prometheus-adapter.yaml
│   ├── prometheus.yaml
│   ├── recommender-externalmetrics-deployment.yaml
│   └── vpa-rbac.diff
├── emit-metrics.py
├── generate-api-docs.sh
├── generate-crd-yaml.sh
├── generate-flags.sh
├── lib
│   └── util.sh
├── local-cluster.md
├── run-e2e-locally.sh
├── run-e2e.sh
├── run-e2e-tests.sh
├── tools.go
├── update-codegen.sh
├── update-kubernetes-deps-in-e2e.sh
├── update-kubernetes-deps.sh
├── verify-codegen.sh
├── verify-vpa-flags.sh
├── vpa-apply-upgrade.sh
├── vpa-down.sh
├── vpa-process-yaml.sh
├── vpa-process-yamls.sh
├── vpa-up.sh
└── warn-obsolete-vpa-objects.sh

3 directories, 34 files

# openssl 버전 확인
[root@operator-host vertical-pod-autoscaler]# openssl version
OpenSSL 1.0.2k-fips  26 Jan 2017

# 1.0 제거
[root@operator-host vertical-pod-autoscaler]# yum remove openssl -y

# openssl 1.1.1 이상 버전 확인
[root@operator-host vertical-pod-autoscaler]# yum install openssl11 -y
[root@operator-host vertical-pod-autoscaler]# openssl11 version
OpenSSL 1.1.1zb  11 Feb 2025

# 스크립트파일내에 openssl11 수정
[root@operator-host vertical-pod-autoscaler]# sed -i 's/openssl/openssl11/g' ~/autoscaler/vertical-pod-autoscaler/pkg/admission-controller/gencerts.sh

# git commit
git status
git config --global user.email "yu3papa.j@gmail.com"
git config --global user.name "yu3papa"
git add .
git commit -m "openssl version modify"

# Deploy the Vertical Pod Autoscaler to your cluster with the following command.
watch -d kubectl get pod -n kube-system


[root@operator-host vertical-pod-autoscaler]# cat hack/vpa-up.sh
#!/bin/bash

# Copyright 2018 The Kubernetes Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

set -o errexit
set -o nounset
set -o pipefail

SCRIPT_ROOT=$(dirname ${BASH_SOURCE})/..
DEFAULT_TAG="1.3.0"
TAG_TO_APPLY=${TAG-$DEFAULT_TAG}

if [ "${TAG_TO_APPLY}" == "${DEFAULT_TAG}" ]; then
  git switch --detach vertical-pod-autoscaler-${DEFAULT_TAG}
fi

$SCRIPT_ROOT/hack/vpa-process-yamls.sh apply $*


[root@operator-host vertical-pod-autoscaler]# ./hack/vpa-up.sh
HEAD is now at 200a292f8 Merge pull request #7774 from jm-franc/vpa-finalize-branch
customresourcedefinition.apiextensions.k8s.io/verticalpodautoscalercheckpoints.autoscaling.k8s.io created
customresourcedefinition.apiextensions.k8s.io/verticalpodautoscalers.autoscaling.k8s.io created
clusterrole.rbac.authorization.k8s.io/system:metrics-reader created
clusterrole.rbac.authorization.k8s.io/system:vpa-actor created
clusterrole.rbac.authorization.k8s.io/system:vpa-status-actor created
clusterrole.rbac.authorization.k8s.io/system:vpa-checkpoint-actor created
clusterrole.rbac.authorization.k8s.io/system:evictioner created
clusterrolebinding.rbac.authorization.k8s.io/system:metrics-reader created
clusterrolebinding.rbac.authorization.k8s.io/system:vpa-actor created
clusterrolebinding.rbac.authorization.k8s.io/system:vpa-status-actor created
clusterrolebinding.rbac.authorization.k8s.io/system:vpa-checkpoint-actor created
clusterrole.rbac.authorization.k8s.io/system:vpa-target-reader created
clusterrolebinding.rbac.authorization.k8s.io/system:vpa-target-reader-binding created
clusterrolebinding.rbac.authorization.k8s.io/system:vpa-evictioner-binding created
serviceaccount/vpa-admission-controller created
serviceaccount/vpa-recommender created
serviceaccount/vpa-updater created
clusterrole.rbac.authorization.k8s.io/system:vpa-admission-controller created
clusterrolebinding.rbac.authorization.k8s.io/system:vpa-admission-controller created
clusterrole.rbac.authorization.k8s.io/system:vpa-status-reader created
clusterrolebinding.rbac.authorization.k8s.io/system:vpa-status-reader-binding created
role.rbac.authorization.k8s.io/system:leader-locking-vpa-updater created
rolebinding.rbac.authorization.k8s.io/system:leader-locking-vpa-updater created
role.rbac.authorization.k8s.io/system:leader-locking-vpa-recommender created
rolebinding.rbac.authorization.k8s.io/system:leader-locking-vpa-recommender created
deployment.apps/vpa-updater created
deployment.apps/vpa-recommender created
Generating certs for the VPA Admission Controller in /tmp/vpa-certs.
./hack/../hack/../pkg/admission-controller/gencerts.sh: line 42: openssl: command not found
deployment.apps/vpa-admission-controller created
service/vpa-webhook created



# 재실행!
[root@operator-host vertical-pod-autoscaler]# sed -i 's/openssl/openssl11/g' ~/autoscaler/vertical-pod-autoscaler/pkg/admission-controller/gencerts.sh
[root@operator-host vertical-pod-autoscaler]# ./hack/vpa-up.sh
M       vertical-pod-autoscaler/pkg/admission-controller/gencerts.sh
HEAD is now at 200a292f8 Merge pull request #7774 from jm-franc/vpa-finalize-branch
customresourcedefinition.apiextensions.k8s.io/verticalpodautoscalercheckpoints.autoscaling.k8s.io unchanged
customresourcedefinition.apiextensions.k8s.io/verticalpodautoscalers.autoscaling.k8s.io unchanged
clusterrole.rbac.authorization.k8s.io/system:metrics-reader unchanged
clusterrole.rbac.authorization.k8s.io/system:vpa-actor unchanged
clusterrole.rbac.authorization.k8s.io/system:vpa-status-actor unchanged
clusterrole.rbac.authorization.k8s.io/system:vpa-checkpoint-actor unchanged
clusterrole.rbac.authorization.k8s.io/system:evictioner unchanged
clusterrolebinding.rbac.authorization.k8s.io/system:metrics-reader unchanged
clusterrolebinding.rbac.authorization.k8s.io/system:vpa-actor unchanged
clusterrolebinding.rbac.authorization.k8s.io/system:vpa-status-actor unchanged
clusterrolebinding.rbac.authorization.k8s.io/system:vpa-checkpoint-actor unchanged
clusterrole.rbac.authorization.k8s.io/system:vpa-target-reader unchanged
clusterrolebinding.rbac.authorization.k8s.io/system:vpa-target-reader-binding unchanged
clusterrolebinding.rbac.authorization.k8s.io/system:vpa-evictioner-binding unchanged
serviceaccount/vpa-admission-controller unchanged
serviceaccount/vpa-recommender unchanged
serviceaccount/vpa-updater unchanged
clusterrole.rbac.authorization.k8s.io/system:vpa-admission-controller unchanged
clusterrolebinding.rbac.authorization.k8s.io/system:vpa-admission-controller unchanged
clusterrole.rbac.authorization.k8s.io/system:vpa-status-reader unchanged
clusterrolebinding.rbac.authorization.k8s.io/system:vpa-status-reader-binding unchanged
role.rbac.authorization.k8s.io/system:leader-locking-vpa-updater unchanged
rolebinding.rbac.authorization.k8s.io/system:leader-locking-vpa-updater unchanged
role.rbac.authorization.k8s.io/system:leader-locking-vpa-recommender unchanged
rolebinding.rbac.authorization.k8s.io/system:leader-locking-vpa-recommender unchanged
deployment.apps/vpa-updater unchanged
deployment.apps/vpa-recommender unchanged
Generating certs for the VPA Admission Controller in /tmp/vpa-certs.
Generating RSA private key, 2048 bit long modulus (2 primes)
............................................................................+++++
.....+++++
e is 65537 (0x010001)
Can't load /root/.rnd into RNG
140406327777088:error:2406F079:random number generator:RAND_load_file:Cannot open file:crypto/rand/randfile.c:98:Filename=/root/.rnd
Generating RSA private key, 2048 bit long modulus (2 primes)
...........................................................+++++
..........................................................+++++
e is 65537 (0x010001)
Signature ok
subject=CN = vpa-webhook.kube-system.svc
Getting CA Private Key
Uploading certs to the cluster.
secret/vpa-tls-certs created
Deleting /tmp/vpa-certs.
deployment.apps/vpa-admission-controller unchanged
service/vpa-webhook unchanged

[root@operator-host vertical-pod-autoscaler]# kubectl get crd | grep autoscaling
verticalpodautoscalercheckpoints.autoscaling.k8s.io   2025-03-08T14:25:55Z
verticalpodautoscalers.autoscaling.k8s.io             2025-03-08T14:25:55Z

[root@operator-host vertical-pod-autoscaler]# kubectl get mutatingwebhookconfigurations vpa-webhook-config
NAME                 WEBHOOKS   AGE
vpa-webhook-config   1          14s

[root@operator-host vertical-pod-autoscaler]# kubectl get mutatingwebhookconfigurations vpa-webhook-config -o json | jq
{
  "apiVersion": "admissionregistration.k8s.io/v1",
  "kind": "MutatingWebhookConfiguration",
  "metadata": {
    "creationTimestamp": "2025-03-08T14:28:34Z",
    "generation": 1,
    "name": "vpa-webhook-config",
    "resourceVersion": "116465",
    "uid": "77d4a3aa-53ac-461c-8ff8-acaff602b3f6"
  },
  "webhooks": [
    {
      "admissionReviewVersions": [
        "v1"
      ],
      "clientConfig": {
        "caBundle": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURMVENDQWhXZ0F3SUJBZ0lVVTlCNjR4ZEV2T1piVWNmQnZyTkhsZTVxWjdJd0RRWUpLb1pJaHZjTkFRRUwKQlFBd0dURVhNQlVHQTFVRUF3d09kbkJoWDNkbFltaHZiMnRmWTJFd0lCY05NalV3TXpBNE1UUXlOekkzV2hnUApNakk1T0RFeU1qSXhOREkzTWpkYU1Ca3hGekFWQmdOVkJBTU1Eblp3WVY5M1pXSm9iMjlyWDJOaE1JSUJJakFOCkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXQySXZWbldSUlNaUkYxZHcrMlpNemJ4U0RUT0YKVjNDN25IZkRZcDI1UFpmUmY2WS81UXBLTDFJdWIzRytaUGNjVFJONVkwVlJWTUQ2VG5WRG1MMGJhYlF3U2xiRwptWGZyY3FzUnl4bmdmWHBobFJaYnNiWEtoZGdKaU5qRmVSYkpVZlBPZ3VLMk13YmtiaWxSOFF3S055ZCtzdWpaClZyUU5GSmNsRjFDR1BCSERaQVNTbHdVcEZZbERuTW9DZG9BTmpCT2pCRmdMeUNuaUxIZThHNjg1OHpVMVJBUWkKU3hvcGZjOXpFNCsvenhzWHE4YS84NUJuaWsxeTBsb1VuRk9EMDQySm80Z3JzdFFtNkp2SVFpZVY1cHZMeDN1bQpjMGJSM1BwOUhCTTF4Ty8rdGdYY29rVkUzb1hNSzlKQ3prWnYwa08rSHVGNnIzdFhjclNXWDd2dHV3SURBUUFCCm8yc3dhVEFkQmdOVkhRNEVGZ1FVK2FJYU9PLzljcVhMNWRzL0dCUEFJU3NnQ0d3d0h3WURWUjBqQkJnd0ZvQVUKK2FJYU9PLzljcVhMNWRzL0dCUEFJU3NnQ0d3d0RBWURWUjBUQkFVd0F3RUIvekFaQmdOVkhSRUVFakFRZ2c1MgpjR0ZmZDJWaWFHOXZhMTlqWVRBTkJna3Foa2lHOXcwQkFRc0ZBQU9DQVFFQUNNenppREZQUGNyV0hzMnVBejNHCmdJL3czaXdKdmxPODhzWTNGYXZ0eXJLdGZMbGtTVVJ1MVhXQTVIYk9TUnB3Y1IyNEZlRFFFU2YwcS9NMlp5SnAKZDhRSWtSeHY4TzJBLzdpU0IrQ2krNTlVNXVEaUluY09EeUd4NTZXUktQUjJqVUJESTkzbVp1aUhFZHJ4ZHF4Ygp5SWJjZ2FKUHZKNDE4RndTamlVaHc2aytFQzlRVHI0S1U1dmdZVyt4NDhZN2VxaDZqMUQvMUxzODlrUkxMZGtMCnlycE1uMlNYNzhLMVRtWklZdnlvd3dtZ0VHaVllbk91N3M0K2RPYnZQTm5XQ3gyS253SFd3UjA1MDFZNDI0VkIKMlRYV0hwQ002SXk2OEJIcmNmeEwzK2MwOS9XWVVLTWlXNXg5d2hobERQNnAyRm51MUdreGxLYUNNbU1kR2tvUgp0dz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K",
        "service": {
          "name": "vpa-webhook",
          "namespace": "kube-system",
          "port": 443
        }
      },
      "failurePolicy": "Ignore",
      "matchPolicy": "Equivalent",
      "name": "vpa.k8s.io",
      "namespaceSelector": {
        "matchExpressions": [
          {
            "key": "kubernetes.io/metadata.name",
            "operator": "NotIn",
            "values": [
              ""
            ]
          }
        ]
      },
      "objectSelector": {},
      "reinvocationPolicy": "Never",
      "rules": [
        {
          "apiGroups": [
            ""
          ],
          "apiVersions": [
            "v1"
          ],
          "operations": [
            "CREATE"
          ],
          "resources": [
            "pods"
          ],
          "scope": "*"
        },
        {
          "apiGroups": [
            "autoscaling.k8s.io"
          ],
          "apiVersions": [
            "*"
          ],
          "operations": [
            "CREATE",
            "UPDATE"
          ],
          "resources": [
            "verticalpodautoscalers"
          ],
          "scope": "*"
        }
      ],
      "sideEffects": "None",
      "timeoutSeconds": 30
    }
  ]
}

 

  • 공식 예제 : pod가 실행되면 약 2~3분 뒤에 pod resource.reqeust가 VPA에 의해 수정 - 링크
    • vpa에 spec.updatePolicy.updateModeOff 로 변경 시 파드에 Spec을 자동으로 변경 재실행 하지 않습니다. 기본값(Auto)

 

# 모니터링
watch -d "kubectl top pod;echo "----------------------";kubectl describe pod | grep Requests: -A2"

# 공식 예제 배포
[root@operator-host vertical-pod-autoscaler]# cat examples/hamster.yaml
# This config creates a deployment with two pods, each requesting 100 millicores
# and trying to utilize slightly above 500 millicores (repeatedly using CPU for
# 0.5s and sleeping 0.5s).
# It also creates a corresponding Vertical Pod Autoscaler that adjusts the
# requests.
# Note that the update mode is left unset, so it defaults to "Auto" mode.
---
apiVersion: "autoscaling.k8s.io/v1"
kind: VerticalPodAutoscaler
metadata:
  name: hamster-vpa
spec:
  # recommenders field can be unset when using the default recommender.
  # When using an alternative recommender, the alternative recommender's name
  # can be specified as the following in a list.
  # recommenders:
  #   - name: 'alternative'
  targetRef:
    apiVersion: "apps/v1"
    kind: Deployment
    name: hamster
  resourcePolicy:
    containerPolicies:
      - containerName: '*'
        minAllowed:
          cpu: 100m
          memory: 50Mi
        maxAllowed:
          cpu: 1
          memory: 500Mi
        controlledResources: ["cpu", "memory"]
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hamster
spec:
  selector:
    matchLabels:
      app: hamster
  replicas: 2
  template:
    metadata:
      labels:
        app: hamster
    spec:
      securityContext:
        runAsNonRoot: true
        runAsUser: 65534 # nobody
      containers:
        - name: hamster
          image: registry.k8s.io/ubuntu-slim:0.14
          resources:
            requests:
              cpu: 100m
              memory: 50Mi
          command: ["/bin/sh"]
          args:
            - "-c"
            - "while true; do timeout 0.5s yes >/dev/null; sleep 0.5s; done"


[root@operator-host vertical-pod-autoscaler]# kubectl apply -f examples/hamster.yaml && kubectl get vpa -w
verticalpodautoscaler.autoscaling.k8s.io/hamster-vpa created
deployment.apps/hamster created
NAME          MODE   CPU   MEM   PROVIDED   AGE
hamster-vpa   Auto                          2s

# 파드 리소스 Requestes 확인
kubectl describe pod | grep Requests: -A2
    Requests:
      cpu:        100m
      memory:     50Mi
--
    Requests:
      cpu:        587m
      memory:     262144k
--
    Requests:
      cpu:        587m
      memory:     262144k


# VPA에 의해 기존 파드 삭제되고 신규 파드가 생성됨
kubectl get events --sort-by=".metadata.creationTimestamp" | grep VPA
2m16s       Normal    EvictedByVPA             pod/hamster-5bccbb88c6-s6jkp         Pod was evicted by VPA Updater to apply resource recommendation.
76s         Normal    EvictedByVPA             pod/hamster-5bccbb88c6-jc6gq         Pod was evicted by VPA Updater to apply resource recommendation.

# 삭제
[root@operator-host vertical-pod-autoscaler]# kubectl delete -f examples/hamster.yaml && cd ~/autoscaler/vertical-pod-autoscaler/ && ./hack/vpa-down.sh
verticalpodautoscaler.autoscaling.k8s.io "hamster-vpa" deleted
deployment.apps "hamster" deleted
customresourcedefinition.apiextensions.k8s.io "verticalpodautoscalercheckpoints.autoscaling.k8s.io" deleted
customresourcedefinition.apiextensions.k8s.io "verticalpodautoscalers.autoscaling.k8s.io" deleted
clusterrole.rbac.authorization.k8s.io "system:metrics-reader" deleted
clusterrole.rbac.authorization.k8s.io "system:vpa-actor" deleted
clusterrole.rbac.authorization.k8s.io "system:vpa-status-actor" deleted
clusterrole.rbac.authorization.k8s.io "system:vpa-checkpoint-actor" deleted
clusterrole.rbac.authorization.k8s.io "system:evictioner" deleted
clusterrolebinding.rbac.authorization.k8s.io "system:metrics-reader" deleted
clusterrolebinding.rbac.authorization.k8s.io "system:vpa-actor" deleted
clusterrolebinding.rbac.authorization.k8s.io "system:vpa-status-actor" deleted
clusterrolebinding.rbac.authorization.k8s.io "system:vpa-checkpoint-actor" deleted
clusterrole.rbac.authorization.k8s.io "system:vpa-target-reader" deleted
clusterrolebinding.rbac.authorization.k8s.io "system:vpa-target-reader-binding" deleted
clusterrolebinding.rbac.authorization.k8s.io "system:vpa-evictioner-binding" deleted
serviceaccount "vpa-admission-controller" deleted
serviceaccount "vpa-recommender" deleted
serviceaccount "vpa-updater" deleted
clusterrole.rbac.authorization.k8s.io "system:vpa-admission-controller" deleted
clusterrolebinding.rbac.authorization.k8s.io "system:vpa-admission-controller" deleted
clusterrole.rbac.authorization.k8s.io "system:vpa-status-reader" deleted
clusterrolebinding.rbac.authorization.k8s.io "system:vpa-status-reader-binding" deleted
role.rbac.authorization.k8s.io "system:leader-locking-vpa-updater" deleted
rolebinding.rbac.authorization.k8s.io "system:leader-locking-vpa-updater" deleted
role.rbac.authorization.k8s.io "system:leader-locking-vpa-recommender" deleted
rolebinding.rbac.authorization.k8s.io "system:leader-locking-vpa-recommender" deleted
deployment.apps "vpa-updater" deleted
deployment.apps "vpa-recommender" deleted
Deleting VPA Admission Controller certs.
secret "vpa-tls-certs" deleted
Unregistering VPA admission controller webhook
Warning: deleting cluster-scoped resources, not scoped to the provided namespace
mutatingwebhookconfiguration.admissionregistration.k8s.io "vpa-webhook-config" deleted
deployment.apps "vpa-admission-controller" deleted
service "vpa-webhook" deleted
resource mapping not found for name: "verticalpodautoscalers.autoscaling.k8s.io" namespace: "" from "STDIN": no matches for kind "CustomResourceDefinition" in version "apiextensions.k8s.io/v1beta1"
ensure CRDs are installed first
resource mapping not found for name: "verticalpodautoscalercheckpoints.autoscaling.k8s.io" namespace: "" from "STDIN": no matches for kind "CustomResourceDefinition" in version "apiextensions.k8s.io/v1beta1"
ensure CRDs are installed first


 

EKS 삭제 (실습 리소스 정리)

# 클러스터 삭제
[root@operator-host ~]# eksctl delete cluster --name "${CLUSTER_NAME}"

# CloudFormation에서 myeks 스택 삭제