Bei einem internen Projekt, das mit AWS Fargate läuft, haben wir uns mit automatischer Skalierung beschäftigt.
In diesem Blogbeitrag möchte ich zeigen, wie wir eine Rails-Webanwendung und deren Resque-Worker anhand ihrer Auslastung automatisch skalieren lassen.
Skalierung der Webanwendung
Um den Fargate-Service für die Webanwendung zu skalieren ist ein ApplicationLoadBalancer nötig.
Wir nutzen CloudFormation, um die AWS-Ressourcen aufzusetzen. Das Template um einen ApplicationLoadBalancer anzulegen und mit dem Web-Service zu verknüpfen sieht wie folgt aus:
Wird der ECS-Service dann skaliert, kümmert sich der ApplicationLoadBalancer darum, eingehende Anfragen an die neuen ECS-Tasks zu verteilen.
Für unsere Webanwendung wollen wir den Service je nach CPU-Last skalieren lassen. Bei AWS passiert das über CloudWatch-Alarme. Die Alarme bekommen dann als Alarm-Aktion eine ScalingPolicy zugewiesen, in der dann beschrieben ist, wie skaliert werden soll.
Hier eine Beispielkonfiguration, die den Web-Service jeweils um einen Task bis auf maximal fünf Tasks hoch skaliert, wenn die letzten drei Minuten die CPU-Last über 90 % war. Runter skaliert wird, wenn die letzten drei Minuten die CPU-Last unter 30 % war:
Über die Eigenschaft Cooldown
bei der ScalingPolicy kann man außerdem
angeben, wie lange gewartet werden soll, bis die nächste Skalierung ausgeführt
wird.
Skalierung der Resque-Worker
Zur Skalierung unserer Resque-Worker haben wir die Anzahl der wartenden Jobs genommen. Anders als die CPU-Auslastung wird das aber natürlich nicht automatisch in CloudWatch erfasst. Also müssen wir das selbst als eine Custom-Metric an CloudWatch senden.
Wir haben das durch einen eigenen Resque-Job gelöst, der mit resque-scheduler jede Minute ausgeführt wird und die aktuelle Anzahl der Jobs in allen Queues an CloudWatch schickt.
Wir haben immer mindestens einen Worker laufen, daher wird die Anzahl der Jobs auch zuverlässig erfasst.
require 'aws-sdk-cloudwatch'
class CloudwatchJob
@queue = :critical
def self.perform
queue_size = Resque.queues.sum { |q| Resque.size(q) }
if Rails.env.production?
Aws::CloudWatch::Client.new.put_metric_data(
namespace: 'NWW/Resque',
metric_data: [
{
metric_name: 'QueueSize',
dimensions: [{ name: 'Stack', value: ENV['STACK'] }],
timestamp: Time.zone.now.iso8601,
value: queue_size,
unit: 'Count'
}
]
)
else
Rails.logger.info "Resque-Queue-Size is #{queue_size}"
end
end
end
Vielen Dank außerdem an XDEV (@XDEVSoftware), für die umfangreiche Beratung zu AWS ECS.