LoginSignup
7
2

More than 1 year has passed since last update.

Amazon EKS 上でKarpenter + HPA のスケーリングの動作を確認する

Last updated at Posted at 2022-08-17

はじめに

本記事は、Amazon EKS 上でNode のオートスケーラーであるKarpenter とPod のオートスケーラーであるHorizontal Pod Autoscaler(HPA) を組み合わせたスケーリングの動作を確認をした際の作業メモです。

主に、次の2点について記載しています。

  • 動作確認をした環境の構築手順
  • 簡単なスケーリングの動作確認

Karpenter と HPA を導入する際の参考になれば幸いです。

環境の構築手順

環境の全体構成

動作確認をする環境の全体構成は、下図としてます。

各種コード類

tree aws-tf-eks-karpenter-test 
aws-tf-eks-karpenter-test
├── files # マニフェストファイル群
│   ├── test-app.yaml
│   ├── test-hpa.yaml
│   └── test-karpenter.yaml
├── main.tf # VPC,EKSクラスタ,IRSA構築用tfファイル
├── provider.tf
└── versions.tf

構築ツールの環境

$ terraform version
Terraform v1.1.9

$ helm version --short
v3.9.0+g7ceeda6

$ aws --version
aws-cli/2.7.18 Python/3.10.5 Darwin/21.6.0 source/arm64 prompt/off

$ eksctl version
0.100.0

$ kubectl version --short
Client Version: v1.24.3

$ ab -V          
This is ApacheBench, Version 2.3 <$Revision: 1879490 $>

$ k9s version
Version:    0.26.3
Commit:     0893f13b3ca6b563dd0c38fdebaefdb8be594825
Date:       n/a

EKS クラスターの構築

Terraform でVPC, EKS クラスター, IRSA(IAM Roles for Service Accounts) を構築します。(参考: Getting Started with Terraform)

EKS クラスターの、初期のノードの構成は下記としてます。

  • ノード数は、1
  • インスタンスタイプは、m5.large(vCPU:2)
main.tf
# VPC は省略

module "eks" {
  source  = "terraform-aws-modules/eks/aws"
  version = "18.27.1"

  # EKS CONTROL PLANE 
  cluster_name = local.cluster_name

  # 
  # Kubernetes 1.22
  # https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/kubernetes-versions.html#kubernetes-1.22
  #
  cluster_version = "1.22"

  #
  # Amazon EKS クラスターエンドポイントアクセスコントロール
  # https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/cluster-endpoint.html
  #
  cluster_endpoint_private_access = false
  cluster_endpoint_public_access  = true

  #
  # Amazon EKS コントロールプレーンのログ記録
  # https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/control-plane-logs.html 
  #
  cluster_enabled_log_types = ["audit", "api", "authenticator"]

  #
  # Amazon EKS アドオン
  # https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/eks-add-ons.html
  #
  cluster_addons = {

    #
    # CoreDNS アドオンの管理
    # https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/managing-coredns.html
    #
    coredns = {
      resolve_conflicts = "OVERWRITE"
    }

    #
    # kube-proxy アドオンの管理
    # https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/managing-kube-proxy.html
    #
    kube-proxy = {}

    #
    # Amazon VPC CNI Plugin for Kubernetes を使用したAmazon EKS での Pod ネットワーク
    # https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/pod-networking.html
    #
    vpc-cni = {
      resolve_conflicts = "OVERWRITE"
    }
  }

  # EKS CLUSTER VPC AND SUBNETS
  vpc_id     = module.vpc.vpc_id
  subnet_ids = module.vpc.private_subnets

  #
  # マネージド型ノードグループ
  # https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/managed-node-groups.html
  #
  eks_managed_node_groups = {
    karpenter-test = {
      node_group_name = "managed-ondemand"

      instance_types = ["m5.large"]

      min_size     = 1
      max_size     = 1
      desired_size = 1

      create_security_group = false

      iam_role_additional_policies = [
        "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
      ]

      tags = {
        # This will tag the launch template created for use by Karpenter
        "karpenter.sh/discovery" = local.cluster_name
      }
    }
  }

  node_security_group_additional_rules = {
    # Extend node-to-node security group rules. Recommended and required for the Add-ons
    ingress_self_all = {
      description = "Node to node all ports/protocols"
      protocol    = "-1"
      from_port   = 0
      to_port     = 0
      type        = "ingress"
      self        = true
    }
    # Recommended outbound traffic for Node groups
    egress_all = {
      description      = "Node all egress"
      protocol         = "-1"
      from_port        = 0
      to_port          = 0
      type             = "egress"
      cidr_blocks      = ["0.0.0.0/0"]
      ipv6_cidr_blocks = ["::/0"]
    }

    ingress_cluster_to_node_all_traffic = {
      description                   = "Cluster API to Nodegroup all traffic"
      protocol                      = "-1"
      from_port                     = 0
      to_port                       = 0
      type                          = "ingress"
      source_cluster_security_group = true
    }
  }

  node_security_group_tags = {
    # NOTE - if creating multiple security groups with this module, only tag the
    # security group that Karpenter should utilize with the following tag
    # (i.e. - at most, only one security group should have this tag in your account)
    "karpenter.sh/discovery" = local.cluster_name
  }
}

data "aws_eks_cluster" "cluster" {
  name = module.eks.cluster_id
}

data "aws_eks_cluster_auth" "cluster" {
  name = module.eks.cluster_id
}

#
# AWS Load Balancer Controller IRSA
#
module "load_balancer_controller_irsa" {
  source  = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks"
  version = "5.3.0"

  role_name = "load-balancer-controller-${local.cluster_name}"

  attach_load_balancer_controller_policy = true

  oidc_providers = {
    ex = {
      provider_arn               = module.eks.oidc_provider_arn
      namespace_service_accounts = ["kube-system:aws-load-balancer-controller"]
    }
  }
}

resource "kubernetes_service_account" "aws_loadbalancer_controller" {
  metadata {
    name      = "aws-load-balancer-controller"
    namespace = "kube-system"
    annotations = {
      "eks.amazonaws.com/role-arn" = module.load_balancer_controller_irsa.iam_role_arn
    }
  }
}

#
# Karpenter IRSA
#
module "karpenter_irsa" {
  source  = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks"
  version = "5.3.0"

  role_name = "karpenter-controller-${local.cluster_name}"

  attach_karpenter_controller_policy = true
  karpenter_tag_key                  = "karpenter.sh/discovery"

  karpenter_controller_cluster_id = module.eks.cluster_id

  karpenter_controller_node_iam_role_arns = [
    module.eks.eks_managed_node_groups[local.cluster_name].iam_role_arn
  ]

  oidc_providers = {
    ex = {
      provider_arn               = module.eks.oidc_provider_arn
      namespace_service_accounts = ["karpenter:karpenter"]
    }
  }
}

resource "kubernetes_namespace" "karpenter" {
  metadata {
    name = "karpenter"
  }
}

resource "kubernetes_service_account" "karpenter" {
  metadata {
    name      = "karpenter"
    namespace = "karpenter"

    annotations = {
      "eks.amazonaws.com/role-arn" = module.karpenter_irsa.iam_role_arn
    }
  }
}

resource "aws_iam_instance_profile" "karpenter" {
  name = "KarpenterNodeInstanceProfile-${local.cluster_name}"
  role = module.eks.eks_managed_node_groups[local.cluster_name].iam_role_name
}

Terraform を実行します。

$ export AWS_PROFILE= [ YOUR AWS ACCOUNT PROFILE NAME ]

$ terraform init
$ terraform plan
$ terraform apply

結果:
Apply complete! Resources: 60 added, 0 changed, 0 destroyed.

Kubernetes 上の構築

現在のコンテキストを確認します。

$ kubectl config current-context

環境変数の設定

# AWS アカウント情報
$ export AWS_PROFILE=[ YOUR AWS ACCOUNT PROFILE NAME ]

# EKS クラスター名
$ export CLUSTER_NAME=karpenter-test

Metrics Server のインストール

参考: メトリック サーバー

# リポジトリを追加
$ helm repo add metrics-server https://kubernetes-sigs.github.io/metrics-server/

# ローカルリポジトリを更新
$ helm repo update

# インストール
$ helm upgrade -n kube-system --install metrics-server metrics-server/metrics-server

AWS Load Balancer Controller のインストール

参考: AWS Load Balancer Controller アドオンのインストール

# リポジトリを追加
$ helm repo add eks https://aws.github.io/eks-charts

# ローカルリポジトリを更新
$ helm repo update

# インストール
$ 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 \
  --set image.repository=602401143452.dkr.ecr.ap-northeast-1.amazonaws.com/amazon/aws-load-balancer-controller

Karpenter(v0.14.0) のインストール

参考: Install Karpenter Helm Chart

# リポジトリを追加
$ helm repo add karpenter https://charts.karpenter.sh/

# ローカルリポジトリを更新
$ helm repo update

# インストール
$ helm upgrade --install karpenter karpenter/karpenter --namespace karpenter \
  --version v0.14.0 \
  --set serviceAccount.create=false  \
  --set serviceAccount.name=karpenter \
  --set clusterName=${CLUSTER_NAME} \
  --set clusterEndpoint=$(aws eks describe-cluster --name ${CLUSTER_NAME} --query "cluster.endpoint" --output json) \
  --set aws.defaultInstanceProfile=KarpenterNodeInstanceProfile-${CLUSTER_NAME}

リリースの一覧

$ helm list -A                     
NAME                            NAMESPACE       REVISION        UPDATED                                 STATUS          CHART                               APP VERSION
aws-load-balancer-controller    kube-system     1               2022-08-16 07:41:03.655152 +0900 JST    deployed        aws-load-balancer-controller-1.4.4  v2.4.3     
karpenter                       karpenter       1               2022-08-16 07:45:10.041976 +0900 JST    deployed        karpenter-0.14.0                    0.14.0     
metrics-server                  kube-system     1               2022-08-16 07:34:30.364095 +0900 JST    deployed        metrics-server-3.8.2                0.6.1 

ALB とPod のデプロイ

Pod のDeployment のパラメータは下記としています。
image=kennethreitz/httpbin,replicas=1,resources request=cpu: 100m,resources limits=cpu: 200m

$ kubectl apply -f ./files/test-app.yaml 
test-app.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: test-app
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: test-app
  namespace: test-app
spec:
  selector:
    matchLabels:
      app: test-app
  replicas: 1
  template:
    metadata:
      labels:
        app: test-app
    spec:
      containers:
        - name: test-app
          image: kennethreitz/httpbin:latest
          imagePullPolicy: Always
          ports:
            - containerPort: 80
          resources:
            limits:
              cpu: 200m
            requests:
              cpu: 100m
---
apiVersion: v1
kind: Service
metadata:
  name: test-app
  namespace: test-app
spec:
  selector:
    app: test-app
  ports:
    - port: 80
      targetPort: 80
      protocol: TCP
  type: NodePort
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: test-app
  namespace: test-app
  annotations:
    kubernetes.io/ingress.class: alb
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/tags: Name=karpenter-test
    alb.ingress.kubernetes.io/target-type: ip
spec:
  rules:
    - host: "*.amazonaws.com"
      http:
        paths:
          - path: /
            pathType: Exact
            backend:
              service:
                name: test-app
                port:
                  number: 80

Horizontal Pod Autoscaler の設定

Pod のスケールの定義は、下記としてます。

  • Pod のスケール範囲は、minReplicas=1maxReplicas=10
  • Pod のスケール条件は、averageUtilization=50%
  • Pod のスケールダウンの挙動は、60秒あたり30%(behavior scaleDown policies)
$ kubectl apply -f ./files/test-hpa.yaml 
test-hpa.yaml
apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
  name: test-hpa
  namespace: test-app
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: test-app
  minReplicas: 1
  maxReplicas: 10
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 50
  behavior:
    scaleDown:
      policies:
        - type: Percent
          value: 30
          periodSeconds: 60
      stabilizationWindowSeconds: 60

Provisioner の設定

Node のスケールの定義は、下記としてます。(参考: Karpenter Provisioner)

  • Node のインスタンスタイプ は、m5.large
  • Node は、on-demand
  • Node に、Pod が 30 秒以上ない場合は、終了(ttlSecondsAfterEmpty=30)
  • Node の有効期限は、900秒(ttlSecondsUntilExpired=900)
$ kubectl apply -f ./files/test-karpenter.yaml
test-karpenter.yaml
apiVersion: karpenter.sh/v1alpha5
kind: Provisioner
metadata:
  name: test-karpenter
spec:
  ttlSecondsUntilExpired: 900
  ttlSecondsAfterEmpty: 30
  requirements:
    - key: node.kubernetes.io/instance-type
      operator: In
      values: ["m5.large"]
    - key: "topology.kubernetes.io/zone"
      operator: In
      values: ["ap-northeast-1a", "ap-northeast-1c"]
    - key: "kubernetes.io/arch"
      operator: In
      values: ["amd64"]
    - key: karpenter.sh/capacity-type
      operator: In
      values: ["on-demand"]
  limits:
    resources:
      cpu: "1000"
      memory: 1000Gi
  provider:
    tags:
      karpenter.sh/discovery: karpenter-test
    subnetSelector:
      karpenter.sh/discovery: karpenter-test
    securityGroupSelector:
      karpenter.sh/discovery: karpenter-test
    instanceProfile: KarpenterNodeInstanceProfile-karpenter-test

Kubernetes 上のリソースの環境

これで、環境の構築は完了です。

$ kubectl get node,pod,ing,hpa -A

# 結果
NAME                                                 STATUS   ROLES    AGE   VERSION
node/ip-10-0-3-252.ap-northeast-1.compute.internal   Ready    <none>   35h   v1.22.9-eks-810597c

NAMESPACE     NAME                                               READY   STATUS    RESTARTS   AGE
karpenter     pod/karpenter-5b486c8b59-f876z                     2/2     Running   0          35h
kube-system   pod/aws-load-balancer-controller-79f8b7cdb-ktgjh   1/1     Running   0          35h
kube-system   pod/aws-load-balancer-controller-79f8b7cdb-m4gwd   1/1     Running   0          35h
kube-system   pod/aws-node-jkbr5                                 1/1     Running   0          35h
kube-system   pod/coredns-5b6d4bd6f7-cgl2n                       1/1     Running   0          35h
kube-system   pod/coredns-5b6d4bd6f7-r2542                       1/1     Running   0          35h
kube-system   pod/kube-proxy-b2bbd                               1/1     Running   0          35h
kube-system   pod/metrics-server-694d47d564-g5btk                1/1     Running   0          35h
test-app      pod/test-app-c55bc5b88-5h6qt                       1/1     Running   0          9m12s

NAMESPACE   NAME                                 CLASS    HOSTS             ADDRESS                                                                     PORTS   AGE
test-app    ingress.networking.k8s.io/test-app   <none>   *.amazonaws.com   k8s-testapp-testapp-8e64fd9961-700841967.ap-northeast-1.elb.amazonaws.com   80      29h

NAMESPACE   NAME                                           REFERENCE             TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
test-app    horizontalpodautoscaler.autoscaling/test-hpa   Deployment/test-app   1%/50%    1         10        1          20h

スケーリングの動作確認

ALB のDNS名を確認します。(ADDRESS=k8s-testapp-testapp-xxxxxxxxxx)

$ kubectl get ingress -n test-app
NAME       CLASS    HOSTS             ADDRESS                                                                     PORTS   AGE
test-app   <none>   *.amazonaws.com   k8s-testapp-testapp-8e64fd9961-700841967.ap-northeast-1.elb.amazonaws.com   80      32h

URL に対して、疎通確認をします。

$ curl -I http://k8s-testapp-testapp-8e64fd9961-700841967.ap-northeast-1.elb.amazonaws.com/
HTTP/1.1 200 OK

Apache Bench でURL に対して、負荷をかけます。

$ ab -c 50 -t 300 http://k8s-testapp-testapp-8e64fd9961-700841967.ap-northeast-1.elb.amazonaws.com/

負荷によって、Horizontal Pod Autoscaler(HPA)によるPod のスケーリングが実行されますが、一部のPod は容量不足により、スケジュールできず、Pending の状態となります。
01_Karpenter_Test.png

Karpenter は、Pending状態のPod を検知し、Node を追加で作成しようとします。
02_Karpenter_Test.png

Node が追加で作成されます。
03_Karpenter_Test.png

Karpenter がPending の状態だったPod を追加作成されたNode にバインドします。
04_Karpenter_Test.png

以上で、Karpenter + HPA のスケーリングの動作を確認することができました。

クリーンアップ

[重要]ALB を含むテスト用のリソースを削除します

$ kubectl delete -f ./files/test-karpenter.yaml
$ kubectl delete -f ./files/test-hpp.yaml
$ kubectl delete -f ./files/test-app.yaml

# 結果
namespace "test-app" deleted
deployment.apps "test-app" deleted
service "test-app" deleted
ingress.networking.k8s.io "test-app" deleted

EKS クラスターを削除します。

$ terraform destroy --target=module.eks

VPC を削除します。(完全削除)

$ terraform destroy

さいごに

実際に計測したわけではないのですが、スケーリングは、Cluster Autoscaler と比べるとKarpenter の方が速いと感じています。

Karpenter は、Cluster Autoscaler の代替となるのか、もう少し深く動作確認を進めたいと思います。

7
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
2