はじめに
本記事は、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)
# 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
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=1
〜maxReplicas=10
- Pod のスケール条件は、
averageUtilization=50%
- Pod のスケールダウンの挙動は、60秒あたり30%(
behavior scaleDown policies
)
$ kubectl apply -f ./files/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
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
の状態となります。
Karpenter は、Pending
状態のPod を検知し、Node を追加で作成しようとします。
Karpenter がPending
の状態だったPod を追加作成されたNode にバインドします。
以上で、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 の代替となるのか、もう少し深く動作確認を進めたいと思います。