[AWS EKS] Karpenter (클러스터 노드 오토스케일링)

Alli_Eunbi·2023년 11월 26일
1

현재 요구사항 : Scale In&Out이 빠르면서 비용을 절감할 수 있는 autoscaling 방침이 필요

Karpenter 시작하기

사전 준비

  • 기존 EKS 클러스터를 사용합니다.
  • 기존 VPC와 서브넷을 사용합니다.
  • 기존 보안 그룹을 사용합니다.
  • 노드가 하나 이상의 노드 그룹에 속해 있습니다.
  • 워크로드에 EKS 모범 사례를 준수하는 포드 중단 예산이 있습니다.
  • 클러스터에 서비스 계정에 대한 OIDC 제공업체 가 있습니다.
  1. 클러스터 이름에 대한 변수 지정

    CLUSTER_NAME=<your cluster name>
    
  2. 클러스터 구성에서 다른 변수 설정 (아래 명령어 그냥 복붙하면 알아서 설정됨)

    AWS_PARTITION="aws"
    AWS_REGION="$(aws configure list | grep region | tr -s " " | cut -d" " -f3)"
    OIDC_ENDPOINT="$(aws eks describe-cluster --name ${CLUSTER_NAME} \
        --query "cluster.identity.oidc.issuer" --output text)"
    AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query 'Account' \
        --output text)
  3. 변수 설정 잘됐는지 아래 명령어로 확인

    echo $CLUSTER_NAME $AWS_PARTITION $AWS_REGION

IAM 역할 생성

Karpenter 및 Karpenter 컨트롤러로 프로비저닝된 노드에 대해 두 개의 새로운 IAM 역할을 생성해야 합니다.

노드용 IAM Role 생성

  • Karpenter 노드 역할을 생성하기 위해 다음 정책과 명령을 사용합니다.
    echo '{
        "Version": "2012-10-17",
        "Statement": [
            {
                "Effect": "Allow",
                "Principal": {
                    "Service": "ec2.amazonaws.com"
                },
                "Action": "sts:AssumeRole"
            }
        ]
    }' > node-trust-policy.json
  • IAM Role의 신뢰 관계는 위에서 만든 node-trust-policy.json의 내용을 그대로 적용합니다.
    aws iam create-role --role-name "KarpenterNodeRole-${CLUSTER_NAME}" \
        --assume-role-policy-document file://node-trust-policy.json
  • 이제 필요한 정책을 역할에 연결합니다.
    • KarpenterNodeRole에 연결한 4개의 IAM Policy는 모두 AWS 관리형 정책입니다.
      - AmazonEKSWorkerNodePolicy
      - AmazonEKS_CNI_Policy
      - AmazonEC2ContainerRegistryReadOnly
      - AmazonSSMManagedInstanceCore

      aws iam attach-role-policy --role-name "KarpenterNodeRole-${CLUSTER_NAME}" \
          --policy-arn arn:${AWS_PARTITION}:iam::aws:policy/AmazonEKSWorkerNodePolicy
      
      aws iam attach-role-policy --role-name "KarpenterNodeRole-${CLUSTER_NAME}" \
          --policy-arn arn:${AWS_PARTITION}:iam::aws:policy/AmazonEKS_CNI_Policy
      
      aws iam attach-role-policy --role-name "KarpenterNodeRole-${CLUSTER_NAME}" \
          --policy-arn arn:${AWS_PARTITION}:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly
      
      aws iam attach-role-policy --role-name "KarpenterNodeRole-${CLUSTER_NAME}" \
          --policy-arn arn:${AWS_PARTITION}:iam::aws:policy/AmazonSSMManagedInstanceCore
  • IAM 역할을 EC2 인스턴스 프로필에 연결합니다.
    aws iam create-instance-profile \
        --instance-profile-name "KarpenterNodeInstanceProfile-${CLUSTER_NAME}"
    
    aws iam add-role-to-instance-profile \
        --instance-profile-name "KarpenterNodeInstanceProfile-${CLUSTER_NAME}" \
        --role-name "KarpenterNodeRole-${CLUSTER_NAME}"

컨트롤러용 IAM Role 생성

  • 이제 Karpenter 컨트롤러가 새 인스턴스를 프로비저닝하는 데 사용할 IAM 역할을 생성해야 합니다. Karpenter 컨트롤러는 IRSAIAM Role for Service Accout 방식으로 IAM 권한을 얻어 EC2 생성, 삭제를 수행합니다.
    1. IAM Role의 신뢰관계를 생성합니다.

      cat << EOF > controller-trust-policy.json
      {
          "Version": "2012-10-17",
          "Statement": [
              {
                  "Effect": "Allow",
                  "Principal": {
                      "Federated": "arn:${AWS_PARTITION}:iam::${AWS_ACCOUNT_ID}:oidc-provider/${OIDC_ENDPOINT#*//}"
                  },
                  "Action": "sts:AssumeRoleWithWebIdentity",
                  "Condition": {
                      "StringEquals": {
                          "${OIDC_ENDPOINT#*//}:aud": "sts.amazonaws.com",
                          "${OIDC_ENDPOINT#*//}:sub": "system:serviceaccount:karpenter:karpenter"
                      }
                  }
              }
          ]
      }
      EOF
    2. Karpenter 컨트롤러에서 사용할 IAM Role을 생성합니다.

      aws iam create-role --role-name KarpenterControllerRole-${CLUSTER_NAME} \
          --assume-role-policy-document file://controller-trust-policy.json
  1. Karpenter 컨트롤러용 IAM Policy를 생성합니다.

    
    cat << EOF > controller-policy.json
    {
        "Statement": [
            {
                "Action": [
                    "ssm:GetParameter",
                    "ec2:DescribeImages",
                    "ec2:RunInstances",
                    "ec2:DescribeSubnets",
                    "ec2:DescribeSecurityGroups",
                    "ec2:DescribeLaunchTemplates",
                    "ec2:DescribeInstances",
                    "ec2:DescribeInstanceTypes",
                    "ec2:DescribeInstanceTypeOfferings",
                    "ec2:DescribeAvailabilityZones",
                    "ec2:DeleteLaunchTemplate",
                    "ec2:CreateTags",
                    "ec2:CreateLaunchTemplate",
                    "ec2:CreateFleet",
                    "ec2:DescribeSpotPriceHistory",
                    "pricing:GetProducts"
                ],
                "Effect": "Allow",
                "Resource": "*",
                "Sid": "Karpenter"
            },
            {
                "Action": "ec2:TerminateInstances",
                "Condition": {
                    "StringLike": {
                        "ec2:ResourceTag/karpenter.sh/provisioner-name": "*"
                    }
                },
                "Effect": "Allow",
                "Resource": "*",
                "Sid": "ConditionalEC2Termination"
            },
            {
                "Effect": "Allow",
                "Action": "iam:PassRole",
                "Resource": "arn:${AWS_PARTITION}:iam::${AWS_ACCOUNT_ID}:role/KarpenterNodeRole-${CLUSTER_NAME}",
                "Sid": "PassNodeIAMRole"
            },
            {
                "Effect": "Allow",
                "Action": "eks:DescribeCluster",
                "Resource": "arn:${AWS_PARTITION}:eks:${AWS_REGION}:${AWS_ACCOUNT_ID}:cluster/${CLUSTER_NAME}",
                "Sid": "EKSClusterEndpointLookup"
            }
        ],
        "Version": "2012-10-17"
    }
    EOF
    1. 인라인 정책Inline Policy를 Karpenter Controller용 IAM Role에 연결합니다.
    
    aws iam put-role-policy --role-name KarpenterControllerRole-${CLUSTER_NAME} \
        --policy-name KarpenterControllerPolicy-${CLUSTER_NAME} \
        --policy-document file://controller-policy.json
    

서브넷 및 보안 그룹에 태그 추가

  • Karpenter가 사용할 서브넷을 알 수 있도록 노드 그룹 서브넷에 태그를 추가해야 합니다.
  1. for 반복문 형태로 여러 서브넷에 한 번에 태그가 추가됩니다.

    for NODEGROUP in $(aws eks list-nodegroups --cluster-name ${CLUSTER_NAME} \
        --query 'nodegroups' --output text); do aws ec2 create-tags \
        --tags "Key=karpenter.sh/discovery,Value=${CLUSTER_NAME}" \
        --resources $(aws eks describe-nodegroup --cluster-name ${CLUSTER_NAME} \
        --nodegroup-name $NODEGROUP --query 'nodegroup.subnets' --output text )
    done

    추가되는 태그 정보는 다음과 같습니다.

    • Key : karpenter.sh/discovery
    • Value : 현재 사용중인 자신의 EKS 클러스터 이름을 찾아 자동 입력됨
  2. 보안 그룹에도 태그를 추가해야합니다. 

    이 명령은 클러스터의 첫 번째 노드 그룹에 대한 보안 그룹에만 태그를 지정합니다. 

    만약, 여러 보안 그룹을 사용하고 있는 경우 Karpenter가 노드별로 사용해야 할 보안 그룹을 결정해야 합니다.

    NODEGROUP=$(aws eks list-nodegroups --cluster-name ${CLUSTER_NAME} \
        --query 'nodegroups[0]' --output text)LAUNCH_TEMPLATE=$(aws eks describe-nodegroup --cluster-name ${CLUSTER_NAME} \
        --nodegroup-name ${NODEGROUP} --query 'nodegroup.launchTemplate.{id:id,version:version}' \
        --output text | tr -s "\t" ",")
    
    # If your EKS setup is configured to use only Cluster security group, then please execute -
    # 클러스터에서 하나의 보안그룹만 사용한다면 아래 명령어 사용
    SECURITY_GROUPS=$(aws eks describe-cluster \
        --name ${CLUSTER_NAME} --query "cluster.resourcesVpcConfig.clusterSecurityGroupId" --output text)
    
    # If your setup uses the security groups in the Launch template of a managed node group, then :
    # 노드그룹의 시작 템플릿에 있는 보안그룹을 사용한다면 아래 명령어 사용
    SECURITY_GROUPS=$(aws ec2 describe-launch-template-versions \
        --launch-template-id ${LAUNCH_TEMPLATE%,*} --versions ${LAUNCH_TEMPLATE#*,} \
        --query 'LaunchTemplateVersions[0].LaunchTemplateData.[NetworkInterfaces[0].Groups||SecurityGroupIds]' \
        --output text)
    
    aws ec2 create-tags \
        --tags "Key=karpenter.sh/discovery,Value=${CLUSTER_NAME}" \
        --resources ${SECURITY_GROUPS}
    

aws-auth ConfigMap 업데이트

  • 방금 생성한 노드 IAM 역할을 사용하는 노드가 클러스터에 조인하도록 허용해야 합니다. aws-auth이를 위해서는 클러스터에서 ConfigMap을 수정해야 합니다 .
    kubectl edit configmap aws-auth -n kube-system
  • 변경전 aws-auth configmap의 내용은 아래와 같습니다.
    apiVersion: v1
    data:
      mapRoles: |
        - groups:
          - system:bootstrappers
          - system:nodes
          rolearn: arn:aws:iam::111122223333:role/dev-global-eks-node-iam-role
          username: system:node:{{EC2PrivateDNSName}}    
    kind: ConfigMap
    metadata:
      ...
    mapRoles를 보면 기존 ASG로 관리되는 노드그룹이 등록되어 있습니다.
  • mapRoles에 Karpenter의 노드용 Role을 추가해줘야 합니다.
    apiVersion: v1
    data:
      mapRoles: |
        - groups:
          - system:bootstrappers
          - system:nodes
          rolearn: arn:aws:iam::111122223333:role/dev-global-eks-node-iam-role
          username: system:node:{{EC2PrivateDNSName}}
    +   - groups:
    +     - system:bootstrappers
    +     - system:nodes
    +     rolearn: arn:aws:iam::111122223333:role/KarpenterNodeRole-YOUR_CLUSTER_NAME_HERE
    +     username: system:node:{{EC2PrivateDNSName}}
    kind: ConfigMap
    metadata:
      ...
    위와 같이 추가되면 전체 aws-auth configmap에는 두개의 그룹이 있게 됩니다. 하나는 Karpenter 노드 역할용이고 다른 하나는 기존 노드 그룹용입니다.

Karpenter 배포

카펜터의 버전을 지정해줍니다.

  • 2023.10 기준 v0.31.0 이 제일 최신 (v0.31.1 로 갱신)
export KARPENTER_VERSION=v0.31.0

helm 차트 배포 명령어를 수행해줍니다.

helm template karpenter oci://public.ecr.aws/karpenter/karpenter --version ${KARPENTER_VERSION} --namespace karpenter \
    --set settings.aws.defaultInstanceProfile=KarpenterNodeInstanceProfile-${CLUSTER_NAME} \
    --set settings.aws.clusterName=${CLUSTER_NAME} \
    --set serviceAccount.annotations."eks\.amazonaws\.com/role-arn"="arn:${AWS_PARTITION}:iam::${AWS_ACCOUNT_ID}:role/KarpenterControllerRole-${CLUSTER_NAME}" \
    --set controller.resources.requests.cpu=1 \
    --set controller.resources.requests.memory=1Gi \
    --set controller.resources.limits.cpu=1 \
    --set controller.resources.limits.memory=1Gi > karpenter.yaml

Karpenter Controller Pod의 배치 설정

  • Karpenter Controller 파드들은 Karpenter가 스스로 생성한 워커노드에 배치되면 안됩니다. 운 나쁘게 자기 자신이 위치한 EC2 노드를 스스로 Terminate 하게 될 경우, 클러스터 전체의 노드 프로비저닝이 멈출 수 있기 때문입니다. 이러한 이유로 Karpenter Controller Pod는 기존 Auto Scaling Group 기반에서 운영되는 노드그룹에 배치되어야 합니다.
  • 위와 같은 이유로 클러스터에 Karpenter를 설치해서 사용하더라도 최소 1개의 노드그룹은 반드시 필요합니다. 저도 처음엔 “Karpenter를 쓰면 노드그룹(ASG)은 하나도 필요 없겠네?“라고 생각했지만 현재로서는 불가능합니다. 처음 Karpenter를 쓸 때 쉽게 혼동할 수 있는 부분입니다.
  • 적절한 파드 분배 (선택사항): Karpenter 공식문서에서는 클러스터 운영 및 유지에 필요한 핵심 파드들은 nodeAffinity를 사용해 기존 ASG로 운영되는 노드그룹에 배치하는 걸 권장하고 있습니다. 파드 배치의 예시는 다음과 같습니다.
    • 기존 노드그룹에 배치 ASG
      • karpenter
      • coredns
      • metrics-server
      • prometheus
      • grafana
    • Karpenter 노드에 배치
      • Backend Application Pod
      • Frontend Application Pod
      • Data Application Pod

위와 같은 배포 원칙으로 인해 karpenter.yaml 파일을 편집하여 karpenter node affinity를 변경할 겁니다. 변경되면 karpenter는 기존 노드 그룹 노드 중 하나에서 실행될겁니다.

규칙은 다음과 같아야 합니다. $NODEGROUP 은 기존에 클러스터에서 사용하던 노드그룹 중 하나의 이름을 넣으면 됩니다.

affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: karpenter.sh/provisioner-name
                operator: DoesNotExist
+           - matchExpressions:
+             - key: eks.amazonaws.com/nodegroup
+               operator: In
+               values:
+               - YOUR_NODE_GROUP_NAME  # 노드그룹 이름은 현재 사용중인 노드 그룹으로 수정하기

이제 Karpenter 배포가 준비되었습니다.

  1. Karpenter 네임스페이스를 생성합니다.

    kubectl create namespace karpenter

Karpenter가 새 노드를 프로비저닝할 때 사용하는 CRD(Custom Resource Definition)인 provisioners와 awsnodetemplates을 생성합니다.

kubectl create -f \
    https://raw.githubusercontent.com/aws/karpenter/${KARPENTER_VERSION}/pkg/apis/crds/karpenter.sh_provisioners.yaml
kubectl create -f \
    https://raw.githubusercontent.com/aws/karpenter/${KARPENTER_VERSION}/pkg/apis/crds/karpenter.k8s.aws_awsnodetemplates.yaml
kubectl create -f \
    https://raw.githubusercontent.com/aws/karpenter/${KARPENTER_VERSION}/pkg/apis/crds/karpenter.sh_machines.yaml
kubectl apply -f karpenter.yaml

생성 후에는 kubectl api-resources 명령어로 CRD 목록을 확인합니다.

kubectl api-resources \
    --categories karpenter \
    -o wide

awsnodetemplates과 provisioners 리소스가 새로 추가된 걸 확인할 수 있습니다.

NAME               SHORTNAMES   APIVERSION                   NAMESPACED   KIND              VERBS                                                        CATEGORIES
awsnodetemplates                karpenter.k8s.aws/v1alpha1   false        AWSNodeTemplate   delete,deletecollection,get,list,patch,create,update,watch   karpenter
provisioners                    karpenter.sh/v1alpha5        false        Provisioner       delete,deletecollection,get,list,patch,create,update,watch   karpenter

Karpenter Node 설정

Worker Node를 생성하려면 Provisioner와 AWS Node template, 두개가 필요합니다.

1. Provisioner

apiVersion: karpenter.sh/v1alpha5
kind: Provisioner
metadata:
  name: <<<provisioner name>>>
spec:
	providerRef:
	  name: <<node template name>>
# taint 설정 및 labels 설정을 추가할 수 있다.
#	taints:
#    - key: purpose
#      value: monitoring
#      effect: NoExecute
#  labels:
#    purpose: monitoring
  requirements:
      # instance type을 정의 여러개를 한번에 정의할 수 있습니다.
    - key: node.kubernetes.io/instance-type
      operator: In
      values: [ c6i.8xlarge, c6i.4xlarge ]
      # WorkerNode를 생성할 Zone
    - key: topology.kubernetes.io/zone
      operator: In
      values: [ ap-northeast-2a, ap-northeast-2c ]
      # on-demand, spot 중 원하는 인스턴스를 선언, 둘다 정의할 수 있습니다.
    - key: karpenter.sh/capacity-type
      operator: In
      values: [ on-demand ]
    - key: kubernetes.io/os
      operator: In
      values:
        - linux
    - key: kubernetes.io/arch
      operator: In
      values:
        - amd64 # or arm64 
# pod를 적극적으로 이동하고 노드를 삭제하거나 더 저렴한 인스턴스 타입으로 교체하는 노드 통합 기능
  consolidation:
    enabled: true

# launch Template 사용시(권장하지 않음)
 # providerRef:
 #   name: <<<AWSNodeTemplasteName>>>

# consolidation.enabled 가 false인 경우 사용
#  ttlSecondsAfterEmpty: 30
  ttlSecondsUntilExpired: 2592000
  • 예시
    apiVersion: karpenter.sh/v1alpha5
    kind: Provisioner
    metadata:
      name: prometheus-provisioner
    spec:
      labels:
        purpose: prometheus
        nodegroup: prometheus
    
      requirements:
          # instance type을 정의 여러개를 한번에 정의할 수 있습니다.
        - key: node.kubernetes.io/instance-type
          operator: In
          values: [ t4g.medium ]
          # WorkerNode를 생성할 Zone
        - key: topology.kubernetes.io/zone
          operator: In
          values: [ ap-northeast-2a, ap-northeast-2c ]
          # on-demand, spot 중 원하는 인스턴스를 선언, 둘다 정의할 수 있습니다.
        - key: karpenter.sh/capacity-type
          operator: In
          values: [ on-demand ]
        - key: kubernetes.io/os
          operator: In
          values:
            - linux
        - key: kubernetes.io/arch
          operator: In
          values:
            - arm64
      consolidation:
        enabled: true
      providerRef:
        name: prometheus-node-template
      ttlSecondsUntilExpired: 2592000

설정 방식)

ttlSecondsAfterEmpty는 현재 Karpenter에 의해 생성된 WorkerNode에 DaemonSet 이외에 모든 POD들이 삭제되어 있는 상태라면 해당 WorkerNode를 해당 시간만큼 기다렸다가 삭제하라는 의미 입니다. 즉 불필요해진 WorkerNode로 판단되었기 때문에 해당 시간만큼 기다렸다가 삭제하는 것입니다.

ttlSecondsUntilExpired는 Karpenter에 의해 WorkerNode가 생성되었을 때 해당 WorkerNode의 수명을 의미합니다. 즉, 위에서 설정한 시간만큼 WorkerNode가 살아 있었다면 신규 WorkerNode로 교체하라는 것을 의미합니다. 시간은 “초”로 설정할 수 있으며 위의 값이 의미하는 것은 다음과 같습니다. 60sec 60min 24hour 30days = 2592000*

마지막으로 consolidation인데 이 옵션이 아주 중요합니다. consolidation은 WorkerNode의 상태를 감시하며 Node에 현재 배포되어 있는 POD들의 spec을 확인하여 해당 POD를 이동했을 때 WorkerNode를 삭제할 수 있다면 POD를 이동시킵니다. 결국 자원효율성을 최대한으로 사용하기 위한 옵션입니다. 주의 사항으로는 원하지 않는 POD의 이동이 자주 발생할 수 있으며 위에서 설명한 ttlSecondsAfterEmpty 옵션과 동시에 사용이 불가능하다는 점이 있습니다. 2개의 옵션 중 1개만 사용해야 합니다. 제가 위에서 사용한 Sample에도 consolidation을 false로 설정해 놓은 것을 보실 수 있을 겁니다.

2. Node Template

apiVersion: karpenter.k8s.aws/v1alpha1
kind: AWSNodeTemplate
metadata:
  name: <<<AWSNodeTemplateName>>>
spec:
  # subnet, securityGroup tag
  subnetSelector:
    karpenter.sh/discovery: <<<subnet_tag_value>>>
  securityGroupSelector:
    karpenter.sh/discovery: <<<securityGroup_tag_value>>>
  # WorkerNode Role Name, 미설정시 karpenter에서 생성한 instance role로 자동 할당
  # eksctl로 자동생성한 IAM은 설정시 에러 발생할 가능성이 있음, 추가 생성한 IAM 으로는 가능
  instanceProfile: <<<Instance_Role_Name>>>
  # WorkerNode AMI
  # amiFamily 사용 시 자동으로 최신버전의 AmazonLinux2 WorkerNode image 사용
  # AL2, Bottlerocket, Ubuntu, Windows2019, Windows2022 등을 사용할 수 있습니다.
  amiFamily: AL2
  # 주석 처리 되어 있지만 amiSelector를 사용하여 특정 AMI 를 지정해줄 수도 있습니다.
#  amiSelector:
#    aws-ids: <<<AMI ID>>>

# 노드그룹 사용시, instance에 연결된 볼륨을 확인 후 값을 맞춰줘야 합니다.
# 아래 blockDeviceMapping 찾는법 토글 참조
  blockDeviceMappings:
    - deviceName: /dev/xvda
      ebs:
        volumeSize: 10G
        volumeType: gp3
        iops: 3000
        throughput: 125
        deleteOnTermination: true
  # WorkerNode tag
  tags:
    Name : <<<instance_name_tag>>>

# 아래는 필요시 사용
  # WorkerNode userdata
  # userData: <<<instance_userdata>>>
  • 예시
    apiVersion: karpenter.k8s.aws/v1alpha1
    kind: AWSNodeTemplate
    metadata:
      name: prometheus-node-template
    spec:
      # subnet, securityGroup tag
      subnetSelector:
        Name: "subnet-private-a,subnet-private-c"
      securityGroupSelector:
        karpenter.sh/discovery: "devops"
      amiFamily: AL2
      blockDeviceMappings:
        - deviceName: /dev/xvda
          ebs:
            volumeSize: 50G
            volumeType: gp3
            iops: 3000
            throughput: 125
            deleteOnTermination: true
      tags:
        Name: prometheus
        nodegroup-role: worker
        Team: devops

Karpenter의 Statefulset 배포 처리

// 앱의 yaml 파일이나 helm에서 annotation을 수정하면 됨.
spec:
  template:
    metadata:
      labels:
	       annotations:
           # 아래가 추가되어야 함
           karpenter.sh/do-not-evict: "true"

주의할 점.

1. PDB 설정(혹시라도 운영에서 consolidation 혹은 spot 인스턴스 사용하는 경우 참조)

Pod Disruption Budget(PDB)은 애플리케이션의 복제본 수가 선언된 임계값 미만으로 떨어지면 퇴거 프로세스를 일시적으로 중단할 수 있습니다. 사용 가능한 복제본 수가 임계값을 초과하면 제거 프로세스가 계속됩니다. minAvailable, maxUnavailable 를 사용하여 PDB의 복제본 수 를 선언할 수 있습니다. 예를 들어 앱 복사본을 3개 이상 사용하려면 PDB를 만들면 됩니다.

apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
  name: my-svc-pdb
spec:
  minAvailable: 3
  selector:
    matchLabels:
      app: my-svc

위의 PDB 정책은 3개 이상의 복제본을 사용할 수 있을 때까지 제거 프로세스를 중단하도록 Kubernetes에 지시합니다. PodDisruptionBudgets 은 노드의 draining을 존중합니다. EKS 관리형 노드 그룹 업그레이드 중에 노드는 15분의 시간 초과로 드레이닝됩니다 . 15분 후 업데이트가 강제 실행되지 않으면(EKS 콘솔에서는 롤링 업데이트 옵션이라고 함) 업데이트가 실패합니다. 업데이트가 강제 실행되면 pod 삭제됩니다.

자체 관리형 노드의 경우 AWS 노드 종료 핸들러 와 같은 도구를 사용할 수도 있습니다 . 이 도구는 EC2 유지 관리 이벤트 및 EC2 스팟 중단 과 같이 EC2 인스턴스를 사용할 수 없게 만드는 이벤트에 Kubernetes 제어 플레인이 적절하게 응답하도록 보장합니다 . Kubernetes API를 사용하여 노드를 차단하여 새 포드가 예약되지 않았는지 확인한 다음 노드를 비워 실행 중인 모든 포드를 종료합니다.

pod anti-affinity를 사용하여 여러 노드에 배포 포드를 예약하고 노드 업그레이드 중에 PDB 관련 지연을 방지할 수 있습니다.

2. anti-affinity 활용

  • 노드 당 포드 하나 예약.

3. Readiness probe / liveness probe 활용

  • Readiness probe 비가용성 Liveness 프로브는 Pod를 종료하여(따라서 앱을 다시 시작하여) 해결되는 앱의 오류를 감지하는 반면, Readiness Probe는 앱을 일시적으로 사용할 수 없는 조건을 감지합니다. 이러한 상황에서는 앱이 일시적으로 응답하지 않을 수 있습니다. 그러나 이 작업이 완료되면 다시 정상 상태가 될 것으로 예상됩니다. 예를 들어, 집중적인 디스크 I/O 작업 중에는 요청을 처리하기 위해 애플리케이션을 일시적으로 사용하지 못할 수 있습니다. 여기서 애플리케이션의 Pod를 종료하는 것은 해결 방법이 아닙니다. 동시에 Pod로 전송된 추가 요청이 실패할 수 있습니다. 준비 프로브를 사용하여 앱의 일시적인 사용 불가를 감지하고 앱이 다시 작동할 때까지 Pod에 대한 요청 전송을 중지할 수 있습니다. 실패로 인해 Pod가 재생성되는 Liveness Probe와 달리, Readiness Probe가 실패하면 Pod가 Kubernetes Service로부터 트래픽을 수신하지 못하게 됩니다 . 준비 프로브가 성공하면 포드는 서비스에서 트래픽 수신을 재개합니다. Liveness Probe와 마찬가지로 Pod 외부 리소스(예: 데이터베이스)에 의존하는 Readiness Probe를 구성하지 마세요. 다음은 잘못 구성된 준비로 인해 애플리케이션이 작동하지 않을 수 있는 시나리오입니다. 앱의 데이터베이스에 연결할 수 없을 때 포드의 준비 프로브가 실패하면 동일한 상태 확인 기준을 공유하기 때문에 다른 포드 복제본도 동시에 실패합니다. 이러한 방식으로 프로브를 설정하면 데이터베이스를 사용할 수 없을 때마다 포드의 준비 프로브가 실패하고 Kubernetes가 모든 포드에 트래픽 전송을 중지합니다. 준비 프로브 사용의 부작용은 배포를 업데이트하는 데 걸리는 시간이 늘어날 수 있다는 것입니다. 준비 프로브가 성공하지 않으면 새 복제본은 트래픽을 수신하지 않습니다. 그때까지는 이전 복제본이 계속해서 트래픽을 수신합니다.
  • Liveness Probe를 사용하여 비정상 포드 제거 활동성 프로브는 프로세스가 계속 실행되지만 애플리케이션이 응답하지 않는 교착 상태 조건을 감지할 수 있습니다. 예를 들어 포트 80에서 수신 대기하는 웹 서비스를 실행하는 경우 Pod의 포트 80에서 HTTP GET 요청을 보내도록 Liveness 프로브를 구성할 수 있습니다. Kubelet은 정기적으로 Pod에 GET 요청을 보내고 응답을 기다립니다. 포드가 200-399 사이에서 응답하면 kubelet은 포드가 정상이라고 간주합니다. 그렇지 않으면 포드가 비정상으로 표시됩니다. Pod가 상태 확인에 지속적으로 실패하면 kubelet은 Pod를 종료합니다. initialDelaySeconds첫 번째 프로브를 지연하는 데 사용할 수 있습니다 . Liveness Probe를 사용할 때 Kubernetes가 모든 Pod를 교체하려고 시도하여 애플리케이션을 오프라인으로 렌더링하므로 모든 Pod가 동시에 Liveness Probe에 실패하는 상황이 발생하지 않도록 해야 합니다. 또한 Kubernetes는 Liveness Probe에도 실패하는 새로운 Pod를 계속 생성하여 제어 플레인에 불필요한 부담을 줄 것입니다. Pod 외부 요소(예: 외부 데이터베이스)에 의존하도록 Liveness Probe를 구성하지 마세요. 즉, 포드 외부의 응답하지 않는 데이터베이스로 인해 포드가 활성 프로브에 실패하게 해서는 안 됩니다. Sandor Szücs의 게시물 LIVENESS PROBES ARE DANGEROUS에서는 잘못 구성된 프로브로 인해 발생할 수 있는 문제에 대해 설명합니다.

4. 시작하는 데 시간이 오래 걸리는 응용 프로그램에는 시작 프로브를 사용하세요. (자바 관련 고려사항)

• 컨테이너가 보통 초기 지연 시간 + 실패 임계 값 * 대기 초(initialDelaySeconds + failureThreshold * periodSeconds) 이후에 기동된다면, 스타트업 프로브가 활성화 프로브와 같은 엔드포인트를 체크하도록 명시해야 한다.

앱을 시작하는 데 추가 시간이 필요한 경우 시작 프로브를 사용하여 활성 및 준비 프로브를 지연할 수 있습니다. 예를 들어, 데이터베이스에서 캐시를 하이드레이션해야 하는 Java 앱이 완전히 작동하려면 최대 2분이 걸릴 수 있습니다. 완전히 작동할 때까지 모든 활성 또는 준비 프로브는 실패할 수 있습니다. 시작 프로브를 구성하면 활성 또는 준비 프로브가 실행되기 전에 Java 앱이 정상 상태가 될 수 있습니다 .

시작 프로브가 성공할 때까지 다른 모든 프로브는 비활성화됩니다. Kubernetes가 애플리케이션 시작을 기다려야 하는 최대 시간을 정의할 수 있습니다. 구성된 최대 시간 후에도 포드가 여전히 시작 프로브에 실패하면 포드가 종료되고 새 포드가 생성됩니다.

Startup Probe는 Liveness Probe와 유사합니다. 실패하면 Pod가 다시 생성됩니다. Ricardo A.가 자신의 게시물 인 환상적인 프로브 및 구성 방법 에서 설명했듯이 , 시작 프로브는 애플리케이션의 시작 시간을 예측할 수 없을 때 사용해야 합니다. 애플리케이션을 시작하는 데 10초가 걸린다는 것을 알고 있다면 대신 Liveness/Readiness Probe를 사용해야 합니다 initialDelaySeconds.

5. Request/Limit 걸어두기

Limit을 걸지 않으면 노드에 다중 포드가 예약 되어 OOM 에러가 발생할 수 있습니다.

설정 귀찮을땐 limit range를 namespace 별로 걸수도 있으니 확인

https://kubernetes.io/docs/tasks/administer-cluster/manage-resources/memory-default-namespace/

주의사항)

동일한 태그를 사용하는 리소스들은 두개 이상 정의 금지

Karpenter provisioner yaml 수정 후 다시 apply 해도 적용이 되지 않기 때문에, 바로 적용하려면 별도의 롤링 업데이트를 해야함.

https://stackoverflow.com/questions/74231427/can-karpenter-target-only-specific-node-groups-in-a-cluster

https://younsl.github.io/blog/k8s/karpenter/

무신사 유튜브 참조: https://www.youtube.com/watch?v=FPlCVVrCD64

카카오 스타일 블로그 참조 : https://devblog.kakaostyle.com/ko/2022-10-13-1-karpenter-on-eks/

https://techblog.gccompany.co.kr/karpenter-7170ae9fb677

profile
BACKEND

1개의 댓글

comment-user-thumbnail
2024년 3월 14일

감사합니다!

답글 달기