mirror of
https://github.com/luxfi/lux-gpu-ci.git
synced 2026-10-30 16:20:42 +00:00
EKS cluster with Karpenter v1 NodePool that scales g4dn.xlarge spot instances 0..1 on demand for CUDA workflows. ARC RunnerScaleSet registers as [self-hosted, linux, amd64, cuda, gpu, t4]. Components: - terraform/ — VPC, EKS 1.31, system node group, Karpenter IAM/SQS - helm/karpenter-nodepool.yaml — v1 NodePool + EC2NodeClass for GPU spot - helm/nvidia-device-plugin-values.yaml — DS with GPU taint toleration - helm/arc-values.yaml + helm/runner-scaleset.yaml — ARC v0.9.x - bootstrap.sh — one-shot deploy - Makefile — init/apply/destroy/verify/cost-estimate Cost: ~$138/mo idle (EKS control plane + tiny system node + NAT), ~$0.027/build for a 10-minute g4dn.xlarge spot run.
171 lines
4.9 KiB
Terraform
171 lines
4.9 KiB
Terraform
###############################################################################
|
|
# Lux GPU CI — EKS cluster with Karpenter-managed GPU spot nodes.
|
|
#
|
|
# Layout:
|
|
# - 1 small system node group (t3.medium x 1) runs Karpenter, ARC controller,
|
|
# kube-system, NVIDIA device plugin scheduler. Always-on (~$30/mo).
|
|
# - Karpenter NodePool spins g4dn.xlarge spot instances 0..1 on demand.
|
|
# - GPU node lifecycle: queued workflow → ARC creates pod → unschedulable →
|
|
# Karpenter provisions node (~90s) → workflow runs → consolidation tears
|
|
# it down 30s after pod completes.
|
|
###############################################################################
|
|
|
|
provider "aws" {
|
|
region = var.region
|
|
default_tags {
|
|
tags = var.tags
|
|
}
|
|
}
|
|
|
|
data "aws_caller_identity" "current" {}
|
|
data "aws_partition" "current" {}
|
|
data "aws_availability_zones" "available" {
|
|
filter {
|
|
name = "opt-in-status"
|
|
values = ["opt-in-not-required"]
|
|
}
|
|
}
|
|
|
|
locals {
|
|
account_id = data.aws_caller_identity.current.account_id
|
|
partition = data.aws_partition.current.partition
|
|
azs = slice(data.aws_availability_zones.available.names, 0, 3)
|
|
|
|
# Karpenter discovery tag — Karpenter finds subnets and security groups via this.
|
|
karpenter_discovery_tag = {
|
|
"karpenter.sh/discovery" = var.cluster_name
|
|
}
|
|
}
|
|
|
|
###############################################################################
|
|
# VPC — 3 AZs, public + private subnets. Private subnets host worker nodes.
|
|
###############################################################################
|
|
|
|
module "vpc" {
|
|
source = "terraform-aws-modules/vpc/aws"
|
|
version = "~> 5.13"
|
|
|
|
name = var.cluster_name
|
|
cidr = var.vpc_cidr
|
|
azs = local.azs
|
|
|
|
private_subnets = [for i, _ in local.azs : cidrsubnet(var.vpc_cidr, 4, i)]
|
|
public_subnets = [for i, _ in local.azs : cidrsubnet(var.vpc_cidr, 8, i + 48)]
|
|
|
|
enable_nat_gateway = true
|
|
single_nat_gateway = true
|
|
enable_dns_hostnames = true
|
|
map_public_ip_on_launch = false
|
|
|
|
public_subnet_tags = {
|
|
"kubernetes.io/role/elb" = 1
|
|
}
|
|
private_subnet_tags = merge(local.karpenter_discovery_tag, {
|
|
"kubernetes.io/role/internal-elb" = 1
|
|
})
|
|
}
|
|
|
|
###############################################################################
|
|
# EKS cluster + tiny managed system node group.
|
|
###############################################################################
|
|
|
|
module "eks" {
|
|
source = "terraform-aws-modules/eks/aws"
|
|
version = "~> 20.24"
|
|
|
|
cluster_name = var.cluster_name
|
|
cluster_version = var.cluster_version
|
|
|
|
cluster_endpoint_public_access = true
|
|
enable_cluster_creator_admin_permissions = true
|
|
|
|
vpc_id = module.vpc.vpc_id
|
|
subnet_ids = module.vpc.private_subnets
|
|
|
|
cluster_addons = {
|
|
coredns = {}
|
|
kube-proxy = {}
|
|
vpc-cni = {}
|
|
eks-pod-identity-agent = {}
|
|
}
|
|
|
|
# System node group — always on, runs the controllers. Single small instance.
|
|
eks_managed_node_groups = {
|
|
system = {
|
|
ami_type = "AL2023_x86_64_STANDARD"
|
|
instance_types = [var.system_instance_type]
|
|
min_size = 1
|
|
max_size = 2
|
|
desired_size = 1
|
|
capacity_type = "ON_DEMAND"
|
|
labels = {
|
|
role = "system"
|
|
}
|
|
}
|
|
}
|
|
|
|
# Discovery tag on the cluster security group so Karpenter can find it.
|
|
node_security_group_tags = local.karpenter_discovery_tag
|
|
|
|
tags = var.tags
|
|
}
|
|
|
|
###############################################################################
|
|
# Karpenter — IAM, SQS interruption queue, controller IRSA.
|
|
###############################################################################
|
|
|
|
module "karpenter" {
|
|
source = "terraform-aws-modules/eks/aws//modules/karpenter"
|
|
version = "~> 20.24"
|
|
|
|
cluster_name = module.eks.cluster_name
|
|
|
|
# Pod Identity is the modern replacement for IRSA; v20 module supports both.
|
|
enable_v1_permissions = true
|
|
enable_pod_identity = true
|
|
create_pod_identity_association = true
|
|
|
|
# Allow node IAM role to be used by EC2 instances launched by Karpenter.
|
|
node_iam_role_use_name_prefix = false
|
|
node_iam_role_name = "${var.cluster_name}-karpenter-node"
|
|
|
|
tags = var.tags
|
|
}
|
|
|
|
###############################################################################
|
|
# Outputs.
|
|
###############################################################################
|
|
|
|
output "cluster_name" {
|
|
value = module.eks.cluster_name
|
|
}
|
|
|
|
output "cluster_endpoint" {
|
|
value = module.eks.cluster_endpoint
|
|
}
|
|
|
|
output "cluster_certificate_authority_data" {
|
|
value = module.eks.cluster_certificate_authority_data
|
|
sensitive = true
|
|
}
|
|
|
|
output "region" {
|
|
value = var.region
|
|
}
|
|
|
|
output "karpenter_node_iam_role_name" {
|
|
value = module.karpenter.node_iam_role_name
|
|
}
|
|
|
|
output "karpenter_queue_name" {
|
|
value = module.karpenter.queue_name
|
|
}
|
|
|
|
output "karpenter_iam_role_arn" {
|
|
value = module.karpenter.iam_role_arn
|
|
}
|
|
|
|
output "kubeconfig_command" {
|
|
value = "aws eks update-kubeconfig --region ${var.region} --name ${module.eks.cluster_name}"
|
|
}
|