Настройка автоскейлинга серверов веб-приложения
Автоскейлинг автоматически добавляет серверы при росте нагрузки и убирает их в периоды затишья. Главная цель — не переплачивать за простаивающие мощности и не терять запросы при пиках трафика.
Метрики для скейлинга
Правильный выбор метрики определяет качество автоскейлинга:
| Метрика | Когда использовать | Порог |
|---|---|---|
| CPU Utilization | CPU-intensive приложения | 60–70% |
| Request Count (RPS) | Stateless HTTP-сервисы | по бизнес-тесту |
| Memory Utilization | Memory-intensive | 70–80% |
| Queue Depth (SQS/RabbitMQ) | Worker-процессы | 100–500 сообщений |
| Custom metric (p95 latency) | Latency-sensitive API | 200–500 мс |
CPU-метрика запаздывает — сервер сначала тормозит, потом скейлится. RPS-метрика реагирует быстрее. Для типичного веб-приложения комбинируют CPU + Request Count.
AWS Auto Scaling Group
Самый распространённый сценарий — EC2 ASG с Application Load Balancer.
# Terraform: Launch Template + ASG
resource "aws_launch_template" "app" {
name_prefix = "myapp-"
image_id = data.aws_ami.ubuntu.id
instance_type = "t3.medium"
user_data = base64encode(<<-EOF
#!/bin/bash
cd /var/www/myapp
git pull origin main
systemctl restart php8.3-fpm
systemctl reload nginx
EOF
)
network_interfaces {
associate_public_ip_address = false
security_groups = [aws_security_group.app.id]
}
iam_instance_profile {
name = aws_iam_instance_profile.app.name
}
lifecycle {
create_before_destroy = true
}
}
resource "aws_autoscaling_group" "app" {
name = "myapp-asg"
vpc_zone_identifier = aws_subnet.private[*].id
target_group_arns = [aws_lb_target_group.app.arn]
health_check_type = "ELB"
health_check_grace_period = 300
min_size = 2
max_size = 20
desired_capacity = 2
launch_template {
id = aws_launch_template.app.id
version = "$Latest"
}
instance_refresh {
strategy = "Rolling"
preferences {
min_healthy_percentage = 50
}
}
tag {
key = "Name"
value = "myapp-app"
propagate_at_launch = true
}
}
# Target Tracking Policy: CPU
resource "aws_autoscaling_policy" "cpu" {
name = "myapp-cpu-tracking"
autoscaling_group_name = aws_autoscaling_group.app.name
policy_type = "TargetTrackingScaling"
target_tracking_configuration {
predefined_metric_specification {
predefined_metric_type = "ASGAverageCPUUtilization"
}
target_value = 65.0
scale_in_cooldown = 300
scale_out_cooldown = 60
}
}
# Target Tracking Policy: ALB Request Count per Target
resource "aws_autoscaling_policy" "rps" {
name = "myapp-rps-tracking"
autoscaling_group_name = aws_autoscaling_group.app.name
policy_type = "TargetTrackingScaling"
target_tracking_configuration {
predefined_metric_specification {
predefined_metric_type = "ALBRequestCountPerTarget"
resource_label = "${aws_lb.main.arn_suffix}/${aws_lb_target_group.app.arn_suffix}"
}
target_value = 1000.0 # запросов на инстанс
}
}
ECS Fargate Auto Scaling
Для контейнерных приложений ECS Fargate проще — нет EC2, только задачи.
resource "aws_appautoscaling_target" "ecs" {
max_capacity = 50
min_capacity = 2
resource_id = "service/${aws_ecs_cluster.main.name}/${aws_ecs_service.app.name}"
scalable_dimension = "ecs:service:DesiredCount"
service_namespace = "ecs"
}
resource "aws_appautoscaling_policy" "ecs_cpu" {
name = "myapp-ecs-cpu"
policy_type = "TargetTrackingScaling"
resource_id = aws_appautoscaling_target.ecs.resource_id
scalable_dimension = aws_appautoscaling_target.ecs.scalable_dimension
service_namespace = aws_appautoscaling_target.ecs.service_namespace
target_tracking_scaling_policy_configuration {
predefined_metric_specification {
predefined_metric_type = "ECSServiceAverageCPUUtilization"
}
target_value = 60.0
scale_in_cooldown = 300
scale_out_cooldown = 30
}
}
# Scale by SQS queue depth (worker service)
resource "aws_appautoscaling_policy" "ecs_sqs" {
name = "myapp-worker-sqs"
policy_type = "TargetTrackingScaling"
resource_id = aws_appautoscaling_target.worker.resource_id
scalable_dimension = aws_appautoscaling_target.worker.scalable_dimension
service_namespace = aws_appautoscaling_target.worker.service_namespace
target_tracking_scaling_policy_configuration {
customized_metric_specification {
metric_name = "ApproximateNumberOfMessagesNotVisible"
namespace = "AWS/SQS"
statistic = "Sum"
dimensions {
name = "QueueName"
value = aws_sqs_queue.jobs.name
}
}
target_value = 100.0 # сообщений на задачу
}
}
Kubernetes HPA и KEDA
Kubernetes Horizontal Pod Autoscaler работает с CPU и Memory из коробки. KEDA (Kubernetes Event-Driven Autoscaling) добавляет внешние метрики: SQS, RabbitMQ, Kafka, Prometheus.
# HPA по CPU + Memory
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: myapp-hpa
namespace: myapp
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: myapp-web
minReplicas: 2
maxReplicas: 50
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 65
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 75
behavior:
scaleUp:
stabilizationWindowSeconds: 30
policies:
- type: Pods
value: 4
periodSeconds: 60
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 25
periodSeconds: 60
# KEDA ScaledObject — RabbitMQ queue
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: myapp-worker-scaler
namespace: myapp
spec:
scaleTargetRef:
name: myapp-worker
minReplicaCount: 1
maxReplicaCount: 30
pollingInterval: 10
cooldownPeriod: 60
triggers:
- type: rabbitmq
metadata:
host: amqp://rabbitmq.myapp.svc.cluster.local
queueName: email-queue
mode: QueueLength
value: "50" # 50 сообщений на под
Scheduled Scaling
Для предсказуемых пиков (ночная рассылка, распродажи по расписанию) добавляют scheduled actions.
import boto3
from datetime import datetime, timezone
client = boto3.client('application-autoscaling', region_name='eu-west-1')
# Масштабирование вверх перед пиком (пятница 18:00 UTC)
client.put_scheduled_action(
ServiceNamespace='ecs',
ResourceId='service/myapp-cluster/myapp-web',
ScalableDimension='ecs:service:DesiredCount',
ScheduledActionName='scale-up-friday-evening',
Schedule='cron(0 18 ? * FRI *)',
ScalableTargetAction={
'MinCapacity': 10,
'MaxCapacity': 50,
}
)
# Возврат к нормальному режиму (воскресенье 22:00 UTC)
client.put_scheduled_action(
ServiceNamespace='ecs',
ResourceId='service/myapp-cluster/myapp-web',
ScalableDimension='ecs:service:DesiredCount',
ScheduledActionName='scale-down-sunday-night',
Schedule='cron(0 22 ? * SUN *)',
ScalableTargetAction={
'MinCapacity': 2,
'MaxCapacity': 20,
}
)
Warm Pool для EC2
Warm Pool держит инстансы в состоянии Stopped или Running (без трафика), чтобы скейлинг занимал секунды, а не минуты.
resource "aws_autoscaling_group" "app" {
# ... основная конфигурация
warm_pool {
pool_state = "Stopped" # экономия: инстансы остановлены
min_size = 2
max_group_prepared_capacity = 5
instance_reuse_policy {
reuse_on_scale_in = true
}
}
}
Graceful Shutdown
При scale-in инстанс получает сигнал завершения. Приложение должно успеть завершить текущие запросы.
// Node.js Express
const server = app.listen(3000);
process.on('SIGTERM', () => {
console.log('SIGTERM received, shutting down gracefully');
server.close(() => {
console.log('HTTP server closed');
process.exit(0);
});
// Принудительное завершение через 30 секунд
setTimeout(() => {
console.error('Forced shutdown');
process.exit(1);
}, 30000);
});
// PHP-FPM: pm.process_idle_timeout = 10s
// nginx upstream: proxy_read_timeout 60s
// ASG: lifecycle hook + heartbeat timeout
Lifecycle hook в AWS позволяет выполнить команды до реального завершения инстанса:
# Lambda-функция на lifecycle hook (TERMINATING:WAIT)
# 1. Снять инстанс с балансировщика (ALB делает автоматически)
# 2. Дождаться завершения активных соединений
# 3. aws autoscaling complete-lifecycle-action --lifecycle-action-result CONTINUE
Мониторинг автоскейлинга
# CloudWatch алерты
- Metric: GroupDesiredCapacity > 15 → Slack alert (неожиданный рост)
- Metric: GroupInServiceInstances < 2 → PagerDuty (мало инстансов)
- Metric: WarmPoolMinSize не выполняется → проверить бюджет
Grafana дашборд: aws_autoscaling_group_desired_capacity, aws_autoscaling_group_in_service_instances, aws_alb_request_count в разрезе target group.
Типичные проблемы
Thrashing — постоянное добавление/удаление инстансов из-за короткого cooldown. Решение: увеличить scale_in_cooldown до 300–600 секунд, добавить stabilizationWindowSeconds в HPA.
Медленный старт приложения — инстанс создан, но трафик идёт до готовности. Решение: health check grace period + readiness probe + Warm Pool.
Дорогой scale-in — удаление инстанса с незавершёнными фоновыми задачами. Решение: lifecycle hook + drain очереди перед CONTINUE.
Неправильная метрика — CPU 20%, но приложение тормозит из-за I/O wait. Решение: custom metric (p95 latency через CloudWatch Contributor Insights или Prometheus).
Срок реализации
| Конфигурация | Срок |
|---|---|
| EC2 ASG + ALB + CPU scaling | 2–3 дня |
| ECS Fargate + target tracking | 1–2 дня |
| Kubernetes HPA | 1 день |
| KEDA + внешние метрики | 2–3 дня |
| Scheduled scaling + Warm Pool | +1–2 дня |







