[EKS] Lambda로 EKS Automode Karpenter Nodepool 스케줄링

2025. 11. 20. 00:41·Kubernetes & EKS/k8s 운영 특이사항

1. 들어가기 앞서

개발 / 품질 환경의 경우 Instance Scheduler 를 사용하여 EKS 의 노드를 종료시킬 수 있다. 자세한 내용은 아래의 포스팅을 참고 부탁하며 그 중 핵심은 ASG(Auto Scaling Group) 의 desired, min, max 를 각각 0 으로 조정하여 내리는 방법이다. 

https://hyukops.tistory.com/39

 

AWS: Instance Scheduling

들어가기 앞서현재 운영중인 프로젝트의 비용최적화 방안으로 개발 및 품질과 같은 운영을 제외한 환경에 있는, EC2를 포함한 RDS와 같은 인스턴스 등을 업무시간 이외에 자원을 중지시키고 업무

hyukops.tistory.com

문제는 EKS Automode 의 경우 ASG 를 사용하지 않는다는 것이다. EKS automode 는 Karpenter 를 기반으로 Nodepool 을 생성하여 노드를 관리하기 때문에 외부에서 이를 조정할 수 없다. 해당 해결 방법에 대해 아래에 공유하고자 한다. 우선, 앞서 말한대로 노드 사이즈를 수정하는 것이 아니라, 아래 예시의 Nodepool 의 limits.cpu 를 "4" 에서 "0" 으로 수정하여 적용해야 한다. 

그렇게 되면 노드는 추가 노드를 생성하지 않게 된다. 이때 노드를 cordon -> drain 시킨 뒤 delete 하면 추가 노드는 생성되지 않게 된다. 해당 솔루션을 구축하기 위해 간략히 방법을 설명하면 아래와 같다. 

  • IAM Role 생성 (Lambda 부착)
  • EKS 액세스 허용
  • Lambda Layer 생성
  • Lambda 생성 및 세팅
  • Lambda 코드 작성 및 테스트

2. IAM Role 생성 (Lambda 부착)

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "eks:DescribeCluster",
                "eks:ListClusters",
                "sts:GetCallerIdentity",
                "ec2:DescribeInstances",
                "ec2:DescribeTags"
            ],
            "Resource": "*"
        }
    ]
}
  • 위 내용으로 Policy 생성 후 Lambda 용 Role 을 생성하여 부착 (신뢰 관계는 lambda 가 되도록 사용자는 lambda 지정)

3. EKS 액세스 허용

  • EKS 콘솔 -> 클러스터 선택 -> 중간의 액세스 탭 이동

  • 우측 상단 생성 버튼 클릭 -> 'IAM 보안 주체'에 위 2번에서 생성한 Role 선택 후 다음
  • 정책 연결 'AmazonEKSClusterAdminPolicy' 선택 -> 액세스 범위 '클러스터' 선택 후 최종 생성

4. Lambda Layer 생성

Lambda에서는 kubernetes 모듈과 eks token 모듈이 없다. 즉, Python 패키지가 Lambda 환경에 설치되어 있지 않은 상태이므로 Layer 를 생성하여 Lambda 에 연결해야 한다. 

mkdir python
pip install kubernetes eks-token -t python/
zip -r lambda-layer.zip python

 

  • 개인 PC 등의 로컬 환경에서 위의 명령어를 통해 zip 파일을 생성한다.

  • 이후 람다 콘솔로 이동하여 좌측 계층 탭으로 이동한다.
  • 우측 상단의 계층 생성 선택

  • zip 파일 업로드를 선택한 뒤 위에서 생성한 lambda-layer.zip 을 업로드한다.
  • 호환 아키텍처와 호환 런타임은 람다 설정값과 일치시켜야 한다. 현재 람다를 아직 생성하지 않았으므로 우선 위 같이 선택한 뒤 생성

5. Lambda 생성 및 세팅

  • 이름은 편하신대로 하시고.. 
  • 아키텍처와 런타임은 layer 를 생성할 때 지정한 값과 일치시킨다.
  • '기존 실행 역할 변경'에서 '기존 역할 사용'을 선택한 뒤 2번에서 생성한 Role 을 선택한다.

  • 생성한 람다 - 함수 콘솔로 들어와서 아래의 '구성' - '일반 구성' 탭에서 제한시간은 10초 이상(넉넉히 30초 추천)으로 설정하고 메모리도 512MB로 변경한다.

  • '환경 변수' 탭에서 위와 같이 CLUSTER_NAME(본인의 EKS 클러스터 이름) 과 REGION(클러스터가 생성된 리전) 지정한다.

  • 람다 함수 메인 페이지에서 아래로 쭉 내리면 '계층' 위젯을 확인할 수 있다.

  • 'Add a layer' 선택 후 '사용자 지정 계층'에서 4번에서 생성한 Layer 추가 (주인장은 이미 생성한 이력이 있어서 버전이 3입니다. 아마 버저닝이 없거나 1로 되어 있습니다!)

6. Lambda 코드 작성 및 테스트 

자! 다 왔습니다. 코드 샘플은 아래와 같습니다. 주석처리를 하였으니 참고 부탁드리며 필요한 부분은 수정해서 사용하시면 됩니다!

import os
import boto3
from kubernetes import client, config
import base64
from eks_token import get_token  # EKS 토큰 발급 라이브러리

# 클러스터 정보 관련 상수
CERT_INFO = ''
ENDPOINT = ''
CA_CERT_FILEPATH = '/tmp/certification_file_name.pem'

# Lambda 환경변수로부터 EKS 클러스터 이름과 리전 가져오기
CLUSTER_NAME = os.environ.get('CLUSTER_NAME')
REGION = os.environ.get('REGION')  

def build_k8s_api():
    """
    EKS 클러스터에 접속할 수 있는 Kubernetes API client 생성
    1. eks_token 라이브러리로 인증 토큰 발급
    2. boto3로 클러스터 endpoint 및 CA cert 조회
    3. kubeconfig 구성 후 ApiClient 반환
    """
    token = get_token(cluster_name=CLUSTER_NAME)['status']['token']

    eks_api = boto3.client('eks', region_name=REGION)
    cluster_info = eks_api.describe_cluster(name=CLUSTER_NAME)
    endpoint = cluster_info['cluster']['endpoint']
    cert_info = cluster_info['cluster']['certificateAuthority']['data']

    # CA 인증서를 /tmp에 파일로 저장
    with open(CA_CERT_FILEPATH, "w") as f:
        f.write(base64.b64decode(cert_info).decode())

    config = client.Configuration()
    config.host = endpoint
    config.verify_ssl = True
    config.ssl_ca_cert = CA_CERT_FILEPATH
    config.api_key['authorization'] = token
    config.api_key_prefix['authorization'] = 'Bearer'

    return client.ApiClient(config)

def patch_nodepool(api):
    """
    NodePool의 CPU limit을 0으로 패치하여 노드 축소를 유도
    """
    body = {
        "spec": {
            "limits": {
                "cpu": "0"
            }
        }
    }

    # NodePool은 cluster-scoped CRD라서 cluster 호출 사용
    custom_api = client.CustomObjectsApi(api)
    response = custom_api.patch_cluster_custom_object(
        group="karpenter.sh",
        version="v1",
        plural="nodepools",
        name="system",
        body=body
    )
    print("NodePool patched:", response)

def drain_nodes(api):

    """PDB를 무시하고 해당 라벨 노드의 모든 파드 강제 삭제"""

    core = client.CoreV1Api(api)
    nodes = core.list_node(label_selector="workload-type=system").items

    for node in nodes:
        node_name = node.metadata.name
        print(f"Draining node: {node_name}")
        
        pods = core.list_pod_for_all_namespaces(field_selector=f"spec.nodeName={node_name}").items
        for pod in pods:

            # PDB 무시(force)로 강제 삭제
            try:
                core.delete_namespaced_pod(
                    name=pod.metadata.name,
                    namespace=pod.metadata.namespace,
                    grace_period_seconds=0,
                    propagation_policy="Foreground"
                )
                print(f"Deleted pod: {pod.metadata.name}")
            except client.exceptions.ApiException as e:
                print(f"Failed to delete {pod.metadata.name}: {e.reason}")

        print(f"Node drained: {node_name}")

def delete_nodes(api):
    """
    특정 라벨(workload-type=system)을 가진 노드를 실제로 삭제
    """
    core = client.CoreV1Api(api)

    nodes = core.list_node(label_selector="workload-type=system").items

    for node in nodes:
        node_name = node.metadata.name
        core.delete_node(node_name)
        print(f"Deleted node: {node_name}")
                    
def lambda_handler(event, context):
    """
    Lambda 엔트리 포인트
    1. Kubernetes API client 생성
    2. NodePool CPU limit 패치 → 노드 축소
    3. 파드 drain
    4. 노드 삭제
    """
    api = build_k8s_api()  # kubeconfig 구성

    print("Starting scale-down")

    patch_nodepool(api)
    drain_nodes(api)
    delete_nodes(api)

    print("Scale-down completed")

    return {"status": "success"}

'Kubernetes & EKS > k8s 운영 특이사항' 카테고리의 다른 글

[kubernetes] failed to create pod sandbox 에러 발생  (2) 2025.08.24
[kubernetes] HPA loop 현상 해결 및 Best Practice  (3) 2025.07.30
[kubernetes] 데몬셋 파드 Pending 현상 (PriorityClass)  (0) 2025.06.26
[kubernetes] EKS의 Burstable 인스턴스 타입 변경 문제  (0) 2025.05.08
[kubernetes] EKS 업그레이드 장애 (2) - net.ipv4.ip_forward  (0) 2025.03.08
'Kubernetes & EKS/k8s 운영 특이사항' 카테고리의 다른 글
  • [kubernetes] failed to create pod sandbox 에러 발생
  • [kubernetes] HPA loop 현상 해결 및 Best Practice
  • [kubernetes] 데몬셋 파드 Pending 현상 (PriorityClass)
  • [kubernetes] EKS의 Burstable 인스턴스 타입 변경 문제
Hyukops
Hyukops
안녕하세요
  • Hyukops
    Hyukops 님의 Tech Blog
    Hyukops
    • 분류 전체보기 (141)
      • Introduction (1)
      • Kubernetes & EKS (43)
        • k8s in action (9)
        • k8s 공부 기록 (17)
        • k8s 운영 가이드 (10)
        • k8s 운영 특이사항 (7)
      • Service Mesh (29)
        • Istio 공부 기록 (20)
        • Istio 운영 특이사항 (9)
      • CICD (10)
        • argoCD 공부 기록 (6)
        • argoCD 운영 특이사항 (4)
      • Logging & Monitoring (5)
        • Prometheus 운영 특이사항 (0)
        • fluent bit 운영 특이사항 (5)
      • Infrastructure as Code (8)
        • terraform 공부 기록 (3)
        • terraform 운영 특이사항 (5)
      • AWS (40)
        • aws 공부 기록 (29)
        • 솔루션 사례 & 문제 해결 (11)
      • Database (5)
        • postgreSQL (5)
  • 태그

    MSK
    aws saa
    Database
    Istio
    argocd
    k8s in action
    Terraform
    kubernetes
    eks
    Logging
    AWS
    PostgreSQL
    prometheus
    canary
    fluent bit
    fluentbit
  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
Hyukops
[EKS] Lambda로 EKS Automode Karpenter Nodepool 스케줄링
상단으로

티스토리툴바