Cuando nos planteamos el diseño de una nueva arquitectura para nuestra plataforma uno de los aspectos que suele influenciar en el diseño es el coste. Pero ya no solo es el coste, sino también el impacto medioambiental que supone el hecho de optimizar los recursos de los que disponemos. En muchas ocasiones tenemos distintos entornos que no tienen porque ser usados las 24 horas (desarrollo, qa, staging, ...). En este caso, os voy a mostrar cómo poder ahorrar costes haciendo un scale down de los nodos de vuestros clusters de kubernetes en AWS a través de una Lambda, de esta manera podréis optimizar costes con aquellos entornos que no necesitáis tener encendidos durante todo el día.
Para realizar este proyecto usaremos Serverless Framework para crear y desplegar nuestro pequeño sistema de apagado automático, el cual realizaremos en NodeJS con Typescript.
Instalar serverless framework
Lo primero de todo es instalar serverless framework con el siguiente comando desde un terminal
npm install -g serverless
Una vez instalado creamos nuestro proyecto en un nuevo directorio ejecutamos el siguiente comando:
sls create -t aws-nodejs-typescript
Esto nos creará un proyecto serverless con typescript. Al abrirlo veréis como ha creado el serverless.ts y una función de ejemplo “hello”. En el fichero de serverless.ts debéis poner los datos referentes a vuestra cuenta de aws.
Ahora ya podéis probar que el proyecto se ha creado correctamente invocando la función hello desde local:
sls invoke local -f start
Lambda
Una vez que comprobamos que el ejemplo funciona, ya podemos empezar a modificar nuestra lambda, o bien podemos crear una nueva. Lo primero que necesitamos es la sdk de aws:
npm i aws-sdk
y luego añadimos este código a nuestra lambda:
import type { ValidatedEventAPIGatewayProxyEvent } from '@libs/api-gateway';
import { formatJSONResponse } from '@libs/api-gateway';
import { middyfy } from '@libs/lambda';
import { NodegroupScalingConfig, UpdateNodegroupConfigRequest, UpdateNodegroupConfigResponse } from 'aws-sdk/clients/eks';
import { AWSError, EKS } from 'aws-sdk';
import schema from './schema';
import { PromiseResult } from 'aws-sdk/lib/request';
const stop: ValidatedEventAPIGatewayProxyEvent<typeof schema> = async (event) => {
const CLUSTER_NAME:string = '[CLUSTER_NAME]';
const NODE_GROUP_NAME:string = '[NODE_GROUP_NAME]';
let nodeGroupConfig:NodegroupScalingConfig = {
desiredSize: 0,
maxSize: 1,
minSize: 0
};
let nodeGroupRequest:UpdateNodegroupConfigRequest = {
clusterName: CLUSTER_NAME,
nodegroupName: NODE_GROUP_NAME,
scalingConfig: nodeGroupConfig
};
let response:PromiseResult<UpdateNodegroupConfigResponse, AWSError> = await new EKS().updateNodegroupConfig(nodeGroupRequest).promise();
console.log(response);
return formatJSONResponse({
message: 'NodeGroupScaling updated to 0',
event,
});
};
export const main = middyfy(stop);
En el código podemos ver cómo modificamos la configuración de escalado de los nodos, poniéndola a 0. Antes debemos modificar los datos para indicar cual es nuestro cluster y nuestro node group name.
Lambda scheduler
La lambda podemos ejecutarla manualmente, a través de una llamada a un endpoint de API Gateway o a través de una tarea periódica con CloudWatch Rules.
Para ello vamos al archivo index.ts y vemos que está configurado para llamar a la lambda a través de un endpoint de API Gateway, sustituimos ese código por el siguiente:
import { handlerPath } from '@libs/handler-resolver';
export default {
handler: `${handlerPath(__dirname)}/handler.main`,
events: [
{
schedule: 'cron(0 19 ? * MON-FRI *)',
}
],
};
De esta manera nuestra lambda hará un scale down cada día, de lunes a viernes a las 7 de la tarde.
Lambda role (I)
Para que la lambda tenga acceso a nuestro cluster de eks debemos hacer que su rol tenga acceso a la policy que permite modificar la configuración de escalado de los nodos:
provider: {
name: 'aws',
iamRoleStatements: [
{
'Effect': 'Allow',
'Action': ['eks:UpdateNodegroupConfig'],
'Resource': '*'
}
],
Deploy
Ahora tenemos todo nuestro código preparado para ralizar la función que queremos, solo queda desplegarlo, y es tan simple como hacer lo siguiente:
sls deploy
Lambda role (II)
Una vez desplegados podemos ver que la lambda tiene asignado un rol
Debemos copiar el arn de ese rol, ya que debemos configurar nuestro cluster para permitir a ese rol realizar cambios, tal y como se explica en la documentación oficial https://docs.aws.amazon.com/eks/latest/userguide/add-user-role.html
eksctl create iamidentitymapping \
--cluster cluster-name \
--region=xx-xxxx-x \
--arn arn:aws:iam::0000000000000:role/eks-management-dev-xx-xxxx-x-lambdaRole \
--group group:example
Esto creará un registro nuevo en el mapa de identidades de nuestro cluster
eksctl get iamidentitymapping --cluster cluster-name --region=xx-xxxx-x
y con esa acción podremos comprobar que realmente se ha añadido. Con esto ya hemos conseguido dar acceso a nuestra lambda.
Os dejo la documentación oficial donde se detalla como funciona la API de la SDK de AWS: https://docs.aws.amazon.com/eks/latest/APIReference/API_UpdateNodegroupConfig.html
Os dejo también el repositorio GitHub donde está subido este ejemplo: https://github.com/fjaviermoradiaz/aws-eks-manager
Top comments (2)
Hola, me gusto mucho el tema sobre el que trata esta entrada, ahora tengo una duda, ¿cuál es la diferencia entre usar esta opción que propones usando Lambda, la cual veo que está genial, pero un poco compleja, a usar el ASG del clúster y programarle una tarea en "Scheduled actions" para que haga ese mismo proceso todos los días?
Slds y gracias por la entrada, todo lo que sea optimización de costos siempre es bienvenido :)
Hola Israel, realmente no hay ninguna diferencia (que yo sepa), es mas bien de que forma se aplica, mi solución está mas enfocada para que la aplique un equipo de desarrolladores con menos conocimiento en infraestructura. Pero por supuesto, con auto scaling group podrías llegar a obtener la misma funcionalidad.
Esto tb es aplicable a otros servicios, tengo pendiente de hacer lo mismo pero con bases de datos.