web-dev-qa-db-fra.com

Service de mise à l'échelle automatique basé sur SQS ApproximateNumberOfMessagesVisible

Je souhaite mettre à l'échelle mes conteneurs aws fargate en fonction de la taille de la file d'attente SQS. Il semble que je ne puisse évoluer qu'en fonction de l'utilisation du processeur ou de la mémoire du conteneur. Existe-t-il un moyen de créer une stratégie qui évoluerait en fonction de la taille de la file d'attente? Quelqu'un a-t-il pu évoluer sur la base d'autres mesures cloudwatch?

10
quasar

Oui, vous pouvez le faire. Vous devez utiliser une stratégie de mise à l'échelle par étapes et vous devez déjà avoir créé une alarme pour la profondeur de votre file d'attente SQS (ApproximateNumberOfMessagesVisible).

Accédez à CloudWatch, créez une nouvelle alarme. Nous appellerons cette alarme sqs-queue-depth-high, et la déclencherons lorsque le nombre approximatif de messages visibles sera de 1000.

Cela fait, accédez à ECS pour le service que vous souhaitez mettre à l'échelle automatique. Cliquez sur Mettre à jour pour le service. Ajoutez une stratégie de mise à l'échelle et choisissez la variété Suivi des étapes. Vous verrez qu'il y a une option pour créer une nouvelle alarme (qui vous permet seulement de choisir entre CPU ou MemoryUtilization), ou utiliser une alarme existante.

Tapez sqs-queue-depth-high dans le champ "Utiliser l'alarme existante" et appuyez sur Entrée, vous devriez voir une coche verte qui vous permet de savoir que le nom est valide (c'est-à-dire que l'alarme existe). Vous verrez de nouvelles listes déroulantes où vous pouvez ajuster la politique d'étape maintenant.

Cela fonctionne pour tous les services d'alarme métrique et ECS. Si vous essayez de faire évoluer cette configuration, pour plusieurs environnements par exemple, ou de la rendre plus sophistiquée que 2 étapes, faites-vous plaisir et utilisez CloudFormation ou Terraform pour aider à la gérer. Rien de pire que d'avoir à régler une alarme en 5 étapes sur 10 services.

16
bluescores

AWS fournit une solution de mise à l'échelle basée sur la file d'attente SQS: https://docs.aws.Amazon.com/autoscaling/ec2/userguide/as-using-sqs-queue.html

Idée principale

  1. Créer une métrique personnalisée CloudWatch sqs-backlog-per-task en utilisant la formule: sqs-backlog-per-task = sqs-messages-number / running-task-number.
  2. Créez une stratégie de mise à l'échelle du suivi des cibles basée sur la métrique backlogPerInstance.

Détails d'implémentation

Métrique personnalisée

Dans mon cas, toute l'infrastructure (Fargate, SQS et autres ressources) est décrite dans la pile CloudFormation. Donc, pour calculer et enregistrer la métrique personnalisée, j'ai décidé d'utiliser la fonction AWS Lambda qui est également décrite dans la pile CloudFormation et déployée avec l'ensemble de l'infrastructure.

Vous trouverez ci-dessous des extraits de code pour la fonction AWS Lambda pour la journalisation des métriques personnalisées suivantes:

  • sqs-backlog-per-task - utilisé pour la mise à l'échelle
  • running-task-number - utilisé pour la mise à l'échelle de l'optimisation et du débogage

Fonction AWS Lambda décrite dans la syntaxe AWS SAM dans la pile CloudFormation (infrastructure.yml):

CustomMetricLoggerFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: custom-metric-logger
      Handler: custom-metric-logger.handler
      Runtime: nodejs8.10
      MemorySize: 128
      Timeout: 3
      Role: !GetAtt CustomMetricLoggerFunctionRole.Arn
      Environment:
        Variables:
          ECS_CLUSTER_NAME: !Ref Cluster
          ECS_SERVICE_NAME: !GetAtt Service.Name
          SQS_URL: !Ref Queue
      Events:
        Schedule:
          Type: Schedule
          Properties:
            Schedule: 'cron(0/1 * * * ? *)' # every one minute

Code Javascript AWS Lambda pour le calcul et la journalisation (custom-metric-logger.js):

var AWS = require('aws-sdk');

exports.handler = async () => {
  try {
    var sqsMessagesNumber = await getSqsMessagesNumber();
    var runningContainersNumber = await getRunningContainersNumber();

    var backlogPerInstance = sqsMessagesNumber;
    if (runningContainersNumber > 0) {
      backlogPerInstance = parseInt(sqsMessagesNumber / runningContainersNumber);
    }

    await putRunningTaskNumberMetricData(runningContainersNumber);
    await putSqsBacklogPerTaskMetricData(backlogPerInstance);

    return {
      statusCode: 200
    };
  } catch (err) {
    console.log(err);

    return {
      statusCode: 500
    };
  }
};

function getSqsMessagesNumber() {
  return new Promise((resolve, reject) => {
    var data = {
      QueueUrl: process.env.SQS_URL,
      AttributeNames: ['ApproximateNumberOfMessages']
    };

    var sqs = new AWS.SQS();
    sqs.getQueueAttributes(data, (err, data) => {
      if (err) {
        reject(err);
      } else {
        resolve(parseInt(data.Attributes.ApproximateNumberOfMessages));
      }
    });
  });
}

function getRunningContainersNumber() {
  return new Promise((resolve, reject) => {
    var data = {
      services: [
        process.env.ECS_SERVICE_NAME
      ],
      cluster: process.env.ECS_CLUSTER_NAME
    };

    var ecs = new AWS.ECS();
    ecs.describeServices(data, (err, data) => {
      if (err) {
        reject(err);
      } else {
        resolve(data.services[0].runningCount);
      }
    });
  });
}

function putRunningTaskNumberMetricData(value) {
  return new Promise((resolve, reject) => {
    var data = {
      MetricData: [{
        MetricName: 'running-task-number',
        Value: value,
        Unit: 'Count',
        Timestamp: new Date()
      }],
      Namespace: 'fargate-sqs-service'
    };

    var cloudwatch = new AWS.CloudWatch();
    cloudwatch.putMetricData(data, (err, data) => {
      if (err) {
        reject(err);
      } else {
        resolve(data);
      }
    });
  });
}

function putSqsBacklogPerTaskMetricData(value) {
  return new Promise((resolve, reject) => {
    var data = {
      MetricData: [{
        MetricName: 'sqs-backlog-per-task',
        Value: value,
        Unit: 'Count',
        Timestamp: new Date()
      }],
      Namespace: 'fargate-sqs-service'
    };

    var cloudwatch = new AWS.CloudWatch();
    cloudwatch.putMetricData(data, (err, data) => {
      if (err) {
        reject(err);
      } else {
        resolve(data);
      }
    });
  });
}

Stratégie de mise à l'échelle du suivi des cibles

Ensuite, sur la base du sqs-backlog-per-task métrique J'ai créé la stratégie de mise à l'échelle du suivi des cibles dans mon modèle Cloud Formation.

Stratégie de mise à l'échelle du suivi des cibles basée sur sqs-backlog-per-task métrique (infrastructure.yml):

ServiceScalingPolicy:
    Type: AWS::ApplicationAutoScaling::ScalingPolicy
    Properties:
      PolicyName: service-scaling-policy
      PolicyType: TargetTrackingScaling
      ScalingTargetId: !Ref ServiceScalableTarget
      TargetTrackingScalingPolicyConfiguration:
        ScaleInCooldown: 60
        ScaleOutCooldown: 60
        CustomizedMetricSpecification:
          Namespace: fargate-sqs-service
          MetricName: sqs-backlog-per-task
          Statistic: Average
          Unit: Count
        TargetValue: 2000

En conséquence, AWS Application Auto Scaling crée et gère les alarmes CloudWatch qui déclenchent la stratégie de mise à l'échelle et calcule l'ajustement de la mise à l'échelle en fonction de la métrique et de la valeur cible. La stratégie de mise à l'échelle ajoute ou supprime la capacité requise pour maintenir la métrique à la valeur cible spécifiée ou proche de celle-ci. En plus de maintenir la métrique proche de la valeur cible, une stratégie de mise à l'échelle du suivi cible s'adapte également aux changements de la métrique en raison d'un modèle de charge changeant.

9

J'ai écrit un article de blog sur exactement ce sujet, y compris un conteneur Docker pour l'exécuter. L'article peut être consulté à l'adresse: https://allaboutaws.com/how-to-auto-scale-aws-ecs-containers-sqs-queue-metrics

Le conteneur de pré-construction est disponible sur DockerHub: https://hub.docker.com/r/sh39sxn/ecs-autoscaling-sqs-metrics

Les fichiers sont disponibles sur GitHub: https://github.com/sh39sxn/ecs-autoscaling-sqs-metrics

J'espère que ça t'aide.

1
bsj4sla