Después de ver nuestra factura mensual de AWS subir constantemente a medida que crecían nuestras cargas de trabajo de Kubernetes, sabía que necesitábamos un enfoque más inteligente para el aprovisionamiento de nodos. La autoscalación tradicional del clúster estaba quemando a través de nuestro presupuesto con los nodos demasiado provisionados que se sentaban inactivos, mientras que aún tomaba una eternidad para escalar cuando necesitaba capacidad.
Había oído hablar de Karpenter, pero no lo había intentado. Ese incidente me empujó a finalmente darle una oportunidad. En esta guía, compartiré cómo construí un clúster EKS de la arquitectura múltiple listo para la producción que redujo nuestros costos de cómputo en un 70% mientras hace que la escala sea casi instantánea.
El código fuente completo está disponible en GitHub.
- Cambiaron de AutoScaler de clúster a Karpenter
- Cortar los costos de cálculo en un 70% usando Spot + Graviton
- Reducción de la latencia de programación de POD de 3 minutos a 20 segundos
- Cúmulo de EKS de múltiples arcos construidos con AMD64 + ARM64
- Terraform + Tereline CI/CD en Github
Abandono de autoscalado tradicional
Esto es lo que no me gustaba del clúster estándar de Kubernetes Autoscaler: tendría vainas en estado “pendiente” durante 2-3 minutos esperando nuevos nodos, al tiempo que pagaría un montón de M5.Caltas de mayor provisión que apenas alcanzaron la utilización de CPU del 20% la mayor parte del tiempo.
Recuerdo un incidente particularmente frustrante en el que una pico de tráfico llegó a nuestra aplicación a las 2 am. El AutoScaler de clúster tardó más de 4 minutos en aprovisionar nuevos nodos, y para entonces nuestros usuarios recibían tiempos de espera. Fue entonces cuando comencé a mirar seriamente a Karpenter.
Lo que hace que Karpenter sea diferente es que no piensa en términos de grupos de nodos fijos. En su lugar, mira sus cápsulas pendientes y cube: “Okay, necesitas 2 VCPU y 4GB RAM? Déjame encontrar la instancia de punto más barata que se ajuste a esos requisitos”. Es como tener un asistente de aprovisionamiento realmente inteligente que realmente comprenda sus patrones de carga de trabajo.
Después de implementarlo, vi que nuestros tiempos de aprovisionamiento de nodos cayeron de más de 3 minutos a menos de 20 segundos. Y los ahorros de costos: una reducción de aproximadamente el 70% en nuestra factura de cómputo, que liberó el presupuesto para otras mejoras en la infraestructura.
Nuestra configuración de múltiples arquitecturas
Una cosa que me impresionó de Karpenter fue la facilidad con que manejó nuestros requisitos de carga de trabajo mixtos. Tenía algunas aplicaciones PHP heredadas que necesitaban instancias X86, pero también quería experimentar con instancias de Graviton ARM64 para nuestros microservicios más nuevos.
La arquitectura con la que terminé se ve así:
┌─────────────────────────────────────────────────────────────┐
│ EKS Cluster │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ AMD64 │ │ ARM64 │ │ Karpenter │ │
│ │ NodePool │ │ NodePool │ │ Controller │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Spot Occasion Dealing with │ │
│ │ SQS Queue + EventBridge Guidelines │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
La arquitectura consta de varios componentes clave:
- Infraestructura de VPC: Obtener la crimson correcta fue essential. Pasé algún tiempo haciendo que el etiquetado de la subred sea perfecto, esos
karpenter.sh/discovery
Las etiquetas no son solo agradables, son esenciales para que Karpenter encuentre sus subredes. Olvidé elkarpenter.sh/discovery
Etiqueta una vez: tardó una hora en depurar por qué no lanzaría nodos.
- EKS Cluster: La integración del proveedor OIDC fue probablemente la parte más complicada para acertar inicialmente. Es lo que permite a Karpenter hablar de forma segura con las AWS API sin credenciales codificadas.
- Controlador Karpenter: Aquí es donde sucede la magia. Una vez desplegado, observa constantemente las cápsulas no programadas y las disposiciones de manera inteligente las instancias correctas.
- Soporte de la arquitectura múltiple: Tener grupos de nodos separados para AMD64 y ARM64 nos permite ejecutar diferentes cargas de trabajo en el {hardware} más rentable.
- Gestión de instancias de spot: El manejo de la interrupción period algo por lo que inicialmente estaba preocupado, pero la integración SQS de AWS lo hace sorprendentemente robusto.
La estructura de terraform que realmente funciona
Mi primer intento de estructurar esto fue un desastre. Tenía todo en un gigante fundamental.tf
archivo, y hacer cambios fue aterrador. Después de un poco de refactorización (y algunas noches), aterricé en este enfoque modular:
├── eks-module/ # EKS cluster creation
├── karpenter/ # Karpenter autoscaler setup
├── vpc/ # VPC infrastructure
├── root/ # Major Terraform execution
└── .github/workflows/ # CI/CD pipeline
VPC: Obtener la base de redes correctas
La configuración de VPC me llevó algunas iteraciones para acertar. La concept clave period que la estrategia de etiquetado no es solo documentación, es funcional.
Esto es lo que aprendí obras:
useful resource "aws_vpc" "fundamental" {
cidr_block = var.vpc_cidr_block
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Title = "${var.project_name}-vpc"
"kubernetes.io/cluster/${var.project_name}" = "shared"
}
}
useful resource "aws_subnet" "private_subnets" {
# ... configuration
tags = {
Title = "private-subnet-${rely.index}"
"karpenter.sh/discovery" = var.project_name
"kubernetes.io/function/internal-elb" = "1"
}
}
Eks y karpenter: el corazón del sistema
La configuración del EKS fue directa, pero obtener el proveedor OIDC correcto fue crítico. Esto es lo que le permite a Karpenter asumir los roles de IAM de forma segura:
useful resource "aws_eks_cluster" "undertaking" {
identify = var.cluster_name
role_arn = aws_iam_role.cluster_role.arn
vpc_config {
subnet_ids = var.private_subnet_ids
endpoint_private_access = true
endpoint_public_access = true
}
access_config {
authentication_mode = "API"
}
}
useful resource "aws_iam_openid_connect_provider" "eks" {
client_id_list = ["sts.amazonaws.com"]
thumbprint_list = [data.tls_certificate.eks.certificates[0].sha1_fingerprint]
url = aws_eks_cluster.undertaking.identification[0].oidc[0].issuer
}
Para el despliegue de Karpenter, fui con el enfoque de la tabla de timón. Una cosa que aprendí es que primero debe instalar los CRD, luego el gráfico principal. La configuración de la cola de interrupción fue essential: es lo que evita que sus cargas de trabajo terminen abruptamente cuando se recuperan las instancias spot:
# Set up CRDs first
useful resource "helm_release" "karpenter_crd" {
identify = "karpenter-crd"
chart = "oci://public.ecr.aws/karpenter/karpenter-crd"
model = var.karpenter_version
namespace = var.namespace
}
# Then set up the primary Karpenter chart
useful resource "helm_release" "karpenter" {
depends_on = [helm_release.karpenter_crd]
identify = "karpenter"
chart = "oci://public.ecr.aws/karpenter/karpenter"
model = var.karpenter_version
namespace = var.namespace
set {
identify = "settings.clusterName"
worth = var.cluster_name
}
set {
identify = "settings.aws.interruptionQueueName"
worth = aws_sqs_queue.karpenter_interruption_queue.identify
}
set {
identify = "serviceAccount.annotations.eks.amazonaws.com/role-arn"
worth = var.controller_role_arn
}
}
Nodepools de la arquitectura múltiple
Inicialmente, period escéptico sobre ARM64. “¿Funcionarán nuestras aplicaciones?” Pero después de algunas pruebas, descubrí que nuestros servicios Node.js y Python funcionaban perfectamente en las instancias de Graviton, a menudo con un mejor rendimiento por dólar.
Así es como configuré los grupos de nodos:
AMD64 Nodepool
apiVersion: karpenter.sh/v1
form: NodePool
metadata:
identify: amd64-nodepool
spec:
template:
spec:
necessities:
- key: kubernetes.io/arch
operator: In
values: ["amd64"]
- key: karpenter.sh/capacity-type
operator: In
values: ["spot", "on-demand"]
- key: node.kubernetes.io/instance-type
operator: In
values: ["m5.large", "m5.xlarge", "c5.large", "c5.xlarge"]
limits:
cpu: 1000
disruption:
consolidationPolicy: WhenEmpty
consolidateAfter: 1m
ARM64 Nodepool: el experimento de Graviton
La configuración ARM64 requiere un poco más de pensamiento. Agregué manchas para evitar que las cargas de trabajo incompatibles aterricen accidentalmente en los nodos ARM64:
apiVersion: karpenter.sh/v1
form: NodePool
metadata:
identify: arm64-nodepool
spec:
template:
spec:
necessities:
- key: kubernetes.io/arch
operator: In
values: ["arm64"]
- key: node.kubernetes.io/instance-type
operator: In
values: ["t4g.medium", "c6g.large", "m6g.large"]
taints:
- key: arm64
worth: "true"
impact: NoSchedule
Aprendí que especificar ID de AMI exactos le brinda más management sobre lo que se implementa, especialmente cuando se prueba diferentes configuraciones ARM64. También puede usar los requisitos de NodesElector para dirigir arquitecturas específicas sin necesidad de manchas si sus aplicaciones son conscientes de la arquitectura.
Ahorros de costos con este enfoque
Permítanme compartir el impacto actual de nuestro entorno de producción. Antes de Karpenter, estábamos ejecutando una combinación de instancias a pedido en múltiples grupos de nodos: alrededor de 15-20 M5.Giant y C5.xlarge instancias ejecutadas las 24 horas del día, las 24 horas, los 7 días de la semana.
Después de cambiar a Karpenter con aproximadamente el 70% de uso de instancias spot, nuestros costos de cálculo mensuales cayeron en un 70%. Esa es una reducción significativa que liberó un presupuesto sustancial para nuevas características y mejoras en la infraestructura.
Los beneficios del ARM64
Las instancias ARM64 proporcionaron una bonificación adicional. Más allá de los ahorros de costos de ~ 20% en comparación con las instancias X86 equivalentes, vi un mejor rendimiento en algunas de nuestras cargas de trabajo intensivas en CPU. Nuestro servicio de procesamiento de imágenes, por ejemplo, funcionó aproximadamente un 15% más rápido en las instancias de Graviton.
Esto es lo que normalmente veo en nuestros grupos:
- 70% de reducción en costos de cómputo
- Escala de menos de 20 segundos en lugar de minutos
- Mejor utilización de recursos – No más pagar por los ciclos de CPU inactivos
El monitoreo del rendimiento mostró que pasamos de la utilización promedio del 25% de la CPU en nuestros nodos fijos hasta el 70% de la utilización con el tamaño correcto de Karpenter.
Lecciones de producción aprendidas
Seguridad: Irsa es tu amiga
Obtener el modelo de seguridad correcto fue essential. Utilizo roles IAM para cuentas de servicio (IRSA) en todas partes, lo que significa que no hay más credenciales de AWS codificadas que floten en nuestro clúster:
useful resource "aws_iam_role" "karpenter_controller" {
identify = "KarpenterController-${var.cluster_name}"
assume_role_policy = jsonencode({
Model = "2012-10-17"
Assertion = [
{
Action = "sts:AssumeRoleWithWebIdentity"
Effect = "Allow"
Principal = {
Federated = var.oidc_provider_arn
}
Condition = {
StringEquals = {
"${replace(var.oidc_provider_url, " "")}:sub" = "system:serviceaccount:karpenter:karpenter"
}
}
}
]
})
}
Escucha
Después de alguna prueba y error, me decidí por monitorear estas métricas clave. Demasiados paneles se vuelven ruido, pero estos cuatro me dicen todo lo que necesito saber sobre el rendimiento de nuestro clúster:
- karpenter_nodes_created_total: ¿Los nodos están aumentando cuando deberían estar?
- karpenter_nodes_mined_total: ¿Los nodos se reducen apropiadamente?
- karpenter_pods_state: ¿Se están programando las vainas rápidamente?
- karpenter_node_utilization: ¿Las instancias son del tamaño correcto?
La métrica de utilización del nodo fue particularmente reveladora. Pasamos de una utilización promedio de ~ 25% hasta un 70% después de que Karpenter comenzó a tamaño correctamente nuestras instancias.
CI/CD
Mi tubería de implementación es bastante sencilla. Utilizo acciones de GitHub con autenticación OIDC a AWS (¡no más claves de acceso en secretos!). El flujo de trabajo admite ramas de características para el desarrollo, puesta en escena para pruebas y principal para la producción:
identify: Terraform Deploy
on:
push:
branches:
- 'function/**'
- 'staging'
- 'fundamental'
workflow_dispatch:
permissions:
id-token: write # Required for OIDC JWT
contents: learn # Required for checkout
jobs:
deploy:
atmosphere: ${ 'dev' }
runs-on: ubuntu-22.04
defaults:
run:
working-directory: root/
steps:
- identify: Clone repo
makes use of: actions/checkout@v3
- identify: Configure AWS Credentials
makes use of: aws-actions/configure-aws-credentials@v4
with:
aws-region: us-east-1
role-to-assume: ${{ vars.IAM_Role }}
role-session-name: gha-assignment-session
- identify: Initialize Terraform
run: terraform init -backend-config="bucket=${{ vars.TF_STATE_BUCKET }}"
- identify: Terraform Plan
run: terraform plan -input=false -var-file=${{ vars.STAGE }}.tfvars
- identify: Terraform Apply
run: terraform apply -auto-approve -input=false -var-file=${{ vars.STAGE }}.tfvars
Lo que me gusta de esta configuración es la selección dinámica del entorno y el uso de variables GitHub para diferentes etapas. El terraform fmt -check
Step asegura la consistencia del código en todo nuestro equipo.
Resumen
Mira, no voy a fingir que todo esto fue una navegación suave. Tenía algunos problemas iniciales con interrupciones de instancia Spot (Protip: asegúrese de que sus aplicaciones manejen Sigter con gracia), y obtener las cargas de trabajo ARM64 correctamente tomó cierta iteración.
Pero los resultados hablan por sí mismos. Estamos ejecutando una infraestructura más resistente y rentable que escala de manera inteligente. Los ahorros de costos sustanciales por sí solos pagados por el tiempo de ingeniería que pasé en esta migración dentro del primer mes.
Si se trata de cargas de trabajo impredecibles, asciende las facturas de AWS, o simplemente quiere probar algo que se sienta como “el futuro de Kubernetes”, definitivamente recomiendo que dispare a Karpenter. Comience con un clúster de desarrollo, se sienta cómodo con los conceptos y luego migre gradualmente sus cargas de trabajo de producción.
La implementación completa está disponible en GitHub. He incluido todos los módulos de Terraform, scripts de prueba y una guía de solución de problemas basada en los problemas que me encontré. Siéntase libre de usarlo como punto de partida para su propia implementación, ¡y hágame saber cómo va!