Skip to main content

Karpenter


Prerequisites

진행 전 Spot Role에 대한 설정을 먼저 해주세요.

Karpenter Role

name = "eks-karpenter-controller-policy"
karpenter_controller_policy = aws.iam.Policy(
name,
name_prefix=name,
policy=json.dumps(
{
"Statement": [
{
"Action": [
"ec2:CreateLaunchTemplate",
"ec2:CreateFleet",
"ec2:RunInstances",
"ec2:CreateTags",
"iam:PassRole",
"ec2:TerminateInstances",
"ec2:DescribeLaunchTemplates",
"ec2:DeleteLaunchTemplate",
"ec2:DescribeInstances",
"ec2:DescribeSecurityGroups",
"ec2:DescribeSubnets",
"ec2:DescribeInstanceTypes",
"ec2:DescribeInstanceTypeOfferings",
"ec2:DescribeAvailabilityZones",
"ssm:GetParameter",
],
"Effect": "Allow",
"Resource": "*",
}
],
"Version": "2012-10-17",
}
),
opts=pulumi.ResourceOptions(parent=eks.cluster),
tags={
"Name": name,
"Stack": variable.stack_name,
"Github": variable.github,
},
)

name = "eks-karpenter-controller-role"
karpenter_controller_role = aws.iam.Role(
name,
name_prefix=name,
assume_role_policy=pulumi.Output.all(oidc_url=eks.oidc_provider.url,).apply(
lambda args: json.dumps(
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": f"arn:aws:iam::{variable.account_id}:oidc-provider/{args['oidc_url']}"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
f"{args['oidc_url']}:sub": "system:serviceaccount:karpenter:karpenter"
}
},
}
],
}
)
),
opts=pulumi.ResourceOptions(parent=eks.cluster),
tags={
"Name": name,
"Stack": variable.stack_name,
"Github": variable.github,
},
)

aws.iam.RolePolicyAttachment(
"eks-karpenter-rpa-0",
policy_arn=karpenter_controller_policy.arn,
role=karpenter_controller_role.name,
opts=pulumi.ResourceOptions(parent=karpenter_controller_role),
)

NodeGroup Role

name = "eks-karpenter-ng-role"
ng_role = aws.iam.Role(
name,
name_prefix=name,
assume_role_policy=json.dumps(
{
"Statement": [
{
"Action": "sts:AssumeRole",
"Effect": "Allow",
"Principal": {"Service": "ec2.amazonaws.com"},
}
],
"Version": "2012-10-17",
}
),
opts=pulumi.ResourceOptions(parent=eks.cluster),
tags={
"Name": name,
"Stack": variable.stack_name,
"Github": variable.github,
},
)

node_policy_arns = {
"0": "arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy",
"1": "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly",
"2": "arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy",
"3": "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore", # Karpenter
}

ng_rpas = []
for i, arn in node_policy_arns.items():
ng_rpas.append(
aws.iam.RolePolicyAttachment(
f"eks-karpenter-ng-rpa-{i}",
policy_arn=arn,
role=ng_role.name,
opts=pulumi.ResourceOptions(parent=ng_role),
)
)

name = "eks-karpenter-ng-profile"
default_ng_profile = aws.iam.InstanceProfile(
name,
name_prefix=name,
role=ng_role.name,
opts=pulumi.ResourceOptions(parent=ng_role),
tags={
"Name": name,
"Stack": variable.stack_name,
"Github": variable.github,
},
)

Installation

helm repo add karpenter https://charts.karpenter.sh \
&& helm repo update karpenter
mkdir -p node/karpenter/helm
helm search repo karpenter/karpenter -l | head -n 10
helm show values karpenter/karpenter \
--version 0.9.0 \
> node/karpenter/helm/values.yaml
serviceAccount:
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::<account_id>:role/<role_name>

clusterName: <cluster_name>

clusterEndpoint: <cluster_endpoint>

aws:
defaultInstanceProfile: <instance_profile>
kubectl create namespace karpenter
helm upgrade karpenter karpenter/karpenter \
--install \
--version 0.9.0 \
-n karpenter \
-f node/karpenter/helm/values.yaml

Provisioner

Provisioner는 Provisioner에 명시된 제약사항Workload에 명시된 제약사항 중 처리 가능한 제약사항을 고려하여 프로비저닝을 수행합니다. 옵션이 다양한 만큼 여러 제약사항의 조합을 만들 수 있도록 하여 유언성을 제공합니다.

또한 kube-scheduler나 Node가 준비 상태가 되기 전에 Pod가 배치되기 위한 작업을 미리 수행해서 노드 시작 대기 시간을 줄여 줍니다.

apiVersion: karpenter.sh/v1alpha5
kind: Provisioner
metadata:
name: <name>

kubeletConfiguration

spec:
kubeletConfiguration:
clusterDNS:
- "<ip|url>"

labels

spec:
labels: # 모든 Node에 적용될 label
<key>: <value>

limits

spec:
limits:
resources:
# 관리하는 리소스의 총 합이 아래 설정 값을 넘어가면 더이상 노드를 생성하지 않음
# cpu: "10" 으로 설정되더라도 72 코어 인스턴스가 생성될 수 있음
cpu: <DecimalSI> # m | "" | k | M | G | T | P | E
memory: <BinarySI> # Ki | Mi | Gi | Ti | Pi | Ei

AWS provider

spec:
provider:
# 설치했을 때 설정한 defaultInstanceProfile 이 기본값으로 사용 됨
InstanceProfile: <instance_profile>
# 설정하지 않으면 자동으로 생성함
launchTemplate: <launch_template>
subnetSelector:
# AWS 태그
<key>: <value>
# Name: subnet-xxxx
# kubernetes.io/cluster/<cluster_name>: '*'
securityGroupSelector:
# AWS 태그
<key>: <value>
# Name: sg-xxxx
# kubernetes.io/cluster/<cluster_name>: '*'
tags:
<key>: <value>
# 아래 태그가 생성된 EC2 인스턴스에 기본으로 적용됨
# Name: karpenter.sh/cluster/<cluster_name>/provisioner/<provisioner_name>
# karpenter.sh/provisioner-name: <name>
# kubernetes.io/cluster/<cluster_name>: owned
metadataOptions:
# 설정하지 않으면 아래 값이 설정 됨
# httpEndpoint: enabled
# httpProtocolIPv6: disabled
# httpPutResponseHopLimit: 2
# httpTokens: required

requirements

https://kubernetes.io/docs/reference/labels-annotations-taints/, 잘 알려진 label을 key로 하는 조건을 설정 할 수 있습니다.

spec:
requirements:
- key: <string>
operator: <In|NotIn>
values:
- <string>
# - key: "node.kubernetes.io/instance-type"
# operator: In
# values: ["m5.large", "m5.2xlarge"]
# - key: "topology.kubernetes.io/zone"
# operator: In
# values: ["us-west-2a", "us-west-2b"]
# - key: "kubernetes.io/arch"
# operator: In
# values: ["arm64", "amd64"]
# - key: "karpenter.sh/capacity-type"
# operator: In
# values: ["spot", "on-demand"]

taints

spec:
taints:
- key: <string>
value: <string>
effect: <NoSchedule|PreferNoSchedule|NoExecute>

ttlSeconds*

Karpenter는 각 Node에 Finalizer를 설정하여 Node 삭제 프로세스를 개선해줍니다.

spec:
# ttlSecondsAfterEmpty를 설정하면 Daemonset을 제외한 Pod가 없을 때,
# 설정 된 시간내에 새로운 Pod이 스케줄 되지 않으면 Node를 정리합니다.
# In seconds
ttlSecondsAfterEmpty: <int>
# ttlSecondsUntilExpired를 설정하면 Node가 시작된 후, 설정 된 시간이 지났을 때,
# Node를 정리합니다.
# In seconds
ttlSecondsUntilExpired: <int>

kubectl delete node <node> 또는 kubectl delete node -l <key>[=<value>]로 Node를 제거할 때에도 Karpenter가 관리하는 Node는 미리 Finalizer를 설정해놨기 때문에 Node 삭제에 의해 생길 수 있는 문제를 최소화 해줍니다.

Reference