Published
- 7 min read
Graviton Migration 전환 여정(1)

EKS 환경을 운영하면서 항상 고민되는 것 중 하나는 비용이다.
현재 Prod 환경에서 on-demand로 amd64 기반의 노드를 사용중인데 슬슬 RI/SP에 대한 이야기가 나오고 있어 계약 전에 최대한 EC2 비용을 줄이기 위한 방법을 찾다가 Graviton 인스턴스에 대해 알게되었다.
이제 Dev 환경에서 Graviton으로 전환한 방법에 대해 이야기해보려 한다.
Graviton instance
Graviton instance는 AWS에서 발표한 ARM64 기반의 프로세스이다.
x86 인스턴스 대비 높은 에너지 효율, 비용과 성능을 제공한다고 한다.
인스턴스 비용 비교
현재 사용중인 EC2 Instance spec 기준으로 비교해보았다.
ISA | Instance type | 비용 |
---|---|---|
x86_64 | m7i-flex.4xlarge | 0.94168 USD per Hour |
arm | m7g.4xlarge | 0.8024 USD per Hour |
단순 계산으로 약 15%의 EC2 비용 절감이 가능하다. (한달 기준 약 100 달러 / 인스턴스 하나당)
전환 시 고려사항
전환을 위해 처음에 고려했던 사항들이다.
- 기존 워크로드에 중단 없이 무중단으로의 전환을 위해 어떻게 할건지?
- 사용중인 라이브러리들이 ARM 기반에서 호환되는지?
- 어플리케이션이 현재 x86 기반으로 빌드되어 있는데 어떻게 처리할건지?
Migration 과정
ARM Node group provisioning
현재 terraform-aws-module/eks를 이용하여 x86 기반의 Managed node group을 사용중인데 여기에 arm 기반의 Managed node group 하나를 더 추가한다.
변경필요한 변수: ami_type
, instance_types
- AS-IS
eks_managed_node_groups = {
"${var.name}" = {
ami_type = "AL2023_x86_64_STANDARD"
capacity_type = local.environment == "p" ? "ON_DEMAND" : "SPOT"
instance_types = ["m7i-flex.4xlarge"]
iam_role_use_name_prefix = false
min_size = local.min_size
max_size = local.max_size
desired_size = local.desired_size
subnet_ids = local.is_multi_az ? var.subnet_ids : [data.aws_subnet.single_az_subnet[0].id]
ebs_optimized = true
block_device_mappings = local.environment == "p" ? {
xvda = {
device_name = "/dev/xvda"
ebs = {
volume_size = 200
volume_type = "gp3"
iops = 3000
encrypted = true
throughput = 125
kms_key_id = local.kms_key_id
}
}
} : {}
iam_role_additional_policies = local.merged_iam_role_additional_policies
node_repair_config = {
enabled = true
}
}
}
- TO-BE
eks_managed_node_groups = {
"${var.name}" = {
ami_type = "AL2023_ARM_64_STANDARD"
capacity_type = local.environment == "p" ? "ON_DEMAND" : "SPOT"
instance_types = ["m7g.4xlarge"]
iam_role_use_name_prefix = false
min_size = local.min_size
max_size = local.max_size
desired_size = local.desired_size
subnet_ids = local.is_multi_az ? var.subnet_ids : [data.aws_subnet.single_az_subnet[0].id]
ebs_optimized = true
block_device_mappings = local.environment == "p" ? {
xvda = {
device_name = "/dev/xvda"
ebs = {
volume_size = 200
volume_type = "gp3"
iops = 3000
encrypted = true
throughput = 125
kms_key_id = local.kms_key_id
}
}
} : {}
iam_role_additional_policies = local.merged_iam_role_additional_policies
node_repair_config = {
enabled = true
}
}
}
Appplication parallel build
사실 가장 깔끔한 방법은 docker buildx를 활용하여 ARM, AMD 모두 대응 가능한 Multi platform build를 활용하는 방법이다.
하지만 현재 사용중인 Bitbucket pipeline의 경우 보안상의 이슈로 미지원한다고 한다…
물론 Self-hosted runner를 활용하는 방법도 있으나 EC2를 띄우면 챙겨야할 보안 요소가 많이지므로 패스했다. (참고: Bitbucket 공식문서)
결국 parallel 빌드를 활용하여 ARM, AMD 두 버전의 image를 생성하는 방향으로 정했다.
develop:
- parallel:
- step: *docker-build-push
- step: *docker-build-push-arm
- step: *helm-chart-release-create
- step: *helm-chart-main-merge
물론 이 외에도 자잘하게 변경할 사항이 있었다.
runtime/cloud/arch
에 arm 명시- docker build 명령어에 옵션 추가
--platform linux/arm64
- Tag 수정
- curl로 설치하는 aws-cli archtecture에 맞게 명령어 수정
arm 이미지로 application 배포
현재 ArgoCD와 helm chart를 이용하여 어플리케이션을 배포중이다.
각 이미지가 맞는 아키텍처의 노드를 찾아가도록 affintiy를 먼저 추가한 후 이미지 태그만 변경하여 배포를 진행했다.
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/arch
operator: In
values:
- amd64
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/arch
operator: In
values:
- arm64
alb ingress routing action을 통해 traffic 비율 조정
이제 기존 서비스와 arm 서비스 두개가 생겼으니 각각의 서비스로 라우팅되는 트래픽을 조정하면서 테스트를 진행해보려한다.
찾아보다보니 service mesh(ex. istio, linkerd)를 통해 간편하게 트래픽 제어가 가능한걸로 확인했는데 현재는 서비스 당 클러스터인 셈이라 안쓰고 있는 상태라 도전하지는 못했다. (물론 요건이 생겨 PoC 준비 단계인 상황이라 나중에 정리해서 포스팅 할 예정이다.)
현 상황에서 가장 최적의 방법으로 각각 서비스가 따로 존재하기 때문에 alb ingress annotation의 actions를 이용하여 트래픽에 대한 가중치를 부여하는 방식을 택했다.
alb.ingress.kubernetes.io/actions.traffic-split: >
{
"type": "forward",
"forwardConfig": {
"targetGroups": [
{
"serviceName": "test-arm-service",
"servicePort": 80,
"weight": 50
},
{
"serviceName": "test-service",
"servicePort": 80,
"weight": 50
}
]
}
}
hosts:
- host: dev.ddochi.kr
paths:
- path: /v1
pathType: Prefix
backend:
service:
name: traffic-split
port:
name: use-annotation
마치며
이렇게 기본적인 세팅을 마쳤으며 트래픽을 조절해가며 기본 기능 테스트 수행 후 성능테스트를 진행해보려 한다.