Ansible实现AWS EC2自动化扩缩容实战指南 解决云资源管理难题

引言:云资源管理的挑战与Ansible的解决方案
在当今云计算时代,企业面临着云资源管理的诸多挑战。手动管理AWS EC2实例不仅耗时耗力,还容易出现人为错误。特别是在业务流量波动较大的场景下,如何根据实际负载动态调整EC2实例数量,成为了运维团队的核心痛点。传统的手动扩缩容方式存在以下问题:
响应延迟
:人工发现负载异常后手动扩容,往往需要数分钟甚至更长时间,期间可能已经影响用户体验
资源浪费
:为了应对峰值流量,通常会过度配置资源,导致在低峰期产生大量闲置成本
操作风险
:手动操作容易出错,比如误删实例、配置错误等
缺乏自动化
:无法实现基于时间的自动化扩缩容策略
Ansible作为一款强大的自动化工具,结合AWS的API,能够完美解决这些问题。通过Ansible的EC2模块和自定义脚本,我们可以实现:
基于指标的自动扩缩容
:根据CPU使用率、内存占用等指标动态调整实例数量
定时扩缩容
:根据业务周期(如工作日/周末、白天/夜晚)自动调整资源
安全的扩缩容流程
:包含健康检查、优雅下线等机制
成本优化
:确保资源使用与业务需求精确匹配
本文将通过完整的实战案例,详细讲解如何使用Ansible实现AWS EC2的自动化扩缩容,帮助您彻底解决云资源管理难题。
Ansible基础与AWS集成
Ansible核心概念回顾
在开始之前,我们先快速回顾Ansible的核心概念:
Inventory(清单)
:定义要管理的主机列表,可以是静态文件或动态清单脚本
Playbook(剧本)
:YAML格式的任务编排文件,定义了要执行的操作
Module(模块)
:Ansible提供的各种功能单元,如ec2_instance、cloudwatch等
Role(角色)
:将Playbook按功能组织的目录结构,便于复用
AWS环境准备
1. 安装必要工具
# 安装Ansible和AWS相关Python库
pip install ansible boto3 botocore

# 验证安装
ansible –version
python -c “import boto3; print(‘boto3 installed successfully’)”
2. 配置AWS凭证
方法一:使用AWS CLI配置
aws configure
# 输入Access Key ID、Secret Access Key、默认区域(如us-east-1)
方法二:环境变量方式
export AWS_ACCESS_KEY_ID=”YOUR_ACCESS_KEY”
export AWS_SECRET_ACCESS_KEY=”YOUR_SECRET_KEY”
export AWS_DEFAULT_REGION=”us-east-1″
方法三:Ansible Vault加密文件
创建加密的变量文件存储凭证:
ansible-vault create group_vars/all/vault.yml
内容格式:
aws_access_key: “YOUR_ACCESS_KEY”
aws_secret_key: “YOUR_SECRET_KEY”
3. IAM权限配置
确保Ansible使用的IAM用户具有以下权限:
{
“Version”: “2012-10-17”,
“Statement”: [
{
“Effect”: “Allow”,
“Action”: [
“ec2:RunInstances”,
“ec2:TerminateInstances”,
“ec2:DescribeInstances”,
“ec2:DescribeInstanceStatus”,
“ec2:CreateTags”,
“cloudwatch:GetMetricStatistics”,
“elasticloadbalancing:DescribeTargetHealth”,
“autoscaling:DescribeAutoScalingGroups”
],
“Resource”: “*”
}
]
}
Ansible与AWS集成测试
创建一个简单的测试Playbook验证连接:
—
– name: Test AWS Connection
hosts: localhost
gather_facts: false
tasks:
– name: List EC2 instances
amazon.aws.ec2_instance_info:
region: “{{ aws_region | default(‘us-east-1’) }}”
register: ec2_info

– name: Display instance count
debug:
msg: “Found {{ ec2_info.instances | length }} EC2 instances”
运行测试:
ansible-playbook -i localhost, test_aws.yml
EC2实例生命周期管理
1. 创建EC2实例
创建自定义VPC和子网(推荐生产环境使用):
—
– name: Create VPC and Subnet
hosts: localhost
gather_facts: false
vars:
vpc_cidr: “10.0.0.0/16”
subnet_cidr: “10.0.1.0/24”
aws_region: “us-east-1”

tasks:
– name: Create VPC
amazon.aws.ec2_vpc_net:
name: “autoscaling-vpc”
cidr_block: “{{ vpc_cidr }}”
region: “{{ aws_region }}”
register: vpc

– name: Create Subnet
amazon.aws.ec2_vpc_subnet:
state: present
vpc_id: “{{ vpc.vpc.id }}”
cidr: “{{ subnet_cidr }}”
az: “{{ aws_region }}a”
region: “{{ aws_region }}”
register: subnet

– name: Create Internet Gateway
amazon.aws.ec2_vpc_igw:
vpc_id: “{{ vpc.vpc.id }}”
region: “{{ aws_region }}”
register: igw

– name: Create Route Table
amazon.aws.ec2_vpc_route_table:
vpc_id: “{{ vpc.vpc.id }}”
region: “{{ aws_region }}”
routes:
– dest: 0.0.0.0/0
gateway_id: “{{ igw.gateway_id }}”
subnets:
– “{{ subnet.subnet.id }}”
创建EC2实例的完整Playbook:
—
– name: Launch EC2 Instances
hosts: localhost
gather_facts: false
vars:
instance_type: “t3.micro”
image_id: “ami-0c02fb55956c7d316” # Amazon Linux 2 AMI
key_name: “your-key-pair”
security_group_ids: [“sg-xxxxxxxx”] # 替换为实际的安全组ID
subnet_id: “subnet-xxxxxxxx” # 替换为实际的子网ID
instance_count: 2
instance_name_prefix: “web-server”
tags:
Environment: “Production”
Application: “WebApp”
ManagedBy: “Ansible”

tasks:
– name: Launch EC2 instances
amazon.aws.ec2_instance:
name: “{{ instance_name_prefix }}-{{ item }}”
region: “{{ aws_region }}”
key_name: “{{ key_name }}”
instance_type: “{{ instance_type }}”
image_id: “{{ image_id }}”
security_group_ids: “{{ security_group_ids }}”
subnet_id: “{{ subnet_id }}”
wait: true
wait_timeout: 600
tags: “{{ tags }}”
network:
assign_public_ip: true
loop: “{{ range(0, instance_count) | list }}”
register: ec2_instances

– name: Wait for instances to be running
amazon.aws.ec2_instance_info:
region: “{{ aws_region }}”
filters:
instance-state-name: “running”
“tag:Application”: “WebApp”
register: ec2_info
until: ec2_info.instances | length >= instance_count
retries: 30
delay: 10

– name: Display instance details
debug:
msg: “Instance {{ item.0 + 1 }}: ID={{ item.1.instance_id }}, Private IP={{ item.1.private_ip_address }}, Public IP={{ item.1.public_ip_address }}”
loop: “{{ ec2_info.instances | enumerate }}”

– name: Create inventory file
copy:
content: |
[web_servers]
{% for instance in ec2_info.instances %}
{{ instance.private_ip_address }} ansible_user=ec2-user ansible_ssh_private_key_file={{ key_name }}.pem
{% endfor %}
dest: “./inventory/hosts”
mode: ‘0644’
2. 检查EC2实例状态
—
– name: Check EC2 Instance Status
hosts: localhost
gather_facts: false
vars:
aws_region: “us-east-1”
application_tag: “WebApp”

tasks:
– name: Get EC2 instances by tag
amazon.aws.ec2_instance_info:
region: “{{ aws_region }}”
filters:
“tag:Application”: “{{ application_tag }}”
instance-state-name: “running”
register: ec2_instances

– name: Check instance health
amazon.aws.ec2_instance_info:
region: “{{ aws_region }}”
instance_ids: “{{ ec2_instances.instances | map(attribute=’instance_id’) | list }}”
register: instance_details

– name: Verify instances are healthy
assert:
that:
– item.instance_state.name == “running”
– item.system_status.status == “ok”
fail_msg: “Instance {{ item.instance_id }} is not healthy”
success_msg: “Instance {{ item.instance_id }} is healthy”
loop: “{{ instance_details.instances }}”
ignore_errors: yes
register: health_check

– name: Report unhealthy instances
debug:
msg: “Unhealthy instances: {{ health_check.results | selectattr(‘failed’, ‘equalto’, true) | map(attribute=’item.instance_id’) | list }}”
when: health_check.results | selectattr(‘failed’, ‘equalto’, true) | list | length > 0
3. 终止EC2实例(安全方式)
—
– name: Terminate EC2 Instances Safely
hosts: localhost
gather_facts: false
vars:
aws_region: “us-east-1”
application_tag: “WebApp”
terminate_count: 1 # 要终止的实例数量
drain_time: 300 # 等待时间(秒),让实例完成请求处理

tasks:
– name: Get running instances
amazon.aws.ec2_instance_info:
region: “{{ aws_region }}”
filters:
“tag:Application”: “{{ application_tag }}”
instance-state-name: “running”
register: ec2_instances

– name: Select instances to terminate
set_fact:
instances_to_terminate: “{{ ec2_instances.instances[:terminate_count] | map(attribute=’instance_id’) | list }}”

– name: Drain connections (wait for requests to complete)
pause:
seconds: “{{ drain_time }}”
when: instances_to_terminate | length > 0

– name: Remove from load balancer (if using ELB)
amazon.aws.ec2_instance:
region: “{{ aws_region }}”
instance_ids: “{{ instances_to_terminate }}”
state: absent
wait: true
when: instances_to_terminate | length > 0

– name: Terminate instances
amazon.aws.ec2_instance:
region: “{{ aws_region }}”
instance_ids: “{{ instances_to_terminate }}”
state: absent
wait: true
wait_timeout: 300
register: termination_result

– name: Verify termination
amazon.aws.ec2_instance_info:
region: “{{ aws_region }}”
instance_ids: “{{ instances_to_terminate }}”
register: termination_check
until: termination_check.instances | length == 0
retries: 10
delay: 10

– name: Report termination results
debug:
msg: “Successfully terminated {{ instances_to_terminate | length }} instances”
自动扩缩容策略设计
1. 基于CloudWatch指标的扩缩容
CloudWatch监控配置
首先,我们需要配置CloudWatch告警来监控关键指标:
—
– name: Configure CloudWatch Alarms
hosts: localhost
gather_facts: false
vars:
aws_region: “us-east-1”
application_tag: “WebApp”
alarm_actions: [] # SNS Topic ARN for notifications
scale_up_threshold: 70 # CPU高阈值
scale_down_threshold: 30 # CPU低阈值
evaluation_periods: 2
period: 60

tasks:
– name: Get EC2 instance IDs
amazon.aws.ec2_instance_info:
region: “{{ aws_region }}”
filters:
“tag:Application”: “{{ application_tag }}”
instance-state-name: “running”
register: ec2_instances

– name: Create CloudWatch Alarm for Scale Up
amazon.aws.cloudwatch_metric_alarm:
region: “{{ aws_region }}”
alarm_name: “scale-up-{{ application_tag }}”
metric_name: “CPUUtilization”
namespace: “AWS/EC2”
statistic: “Average”
comparison_operator: “GreaterThanThreshold”
threshold: “{{ scale_up_threshold }}”
period: “{{ period }}”
evaluation_periods: “{{ evaluation_periods }}”
unit: “Percent”
alarm_actions: “{{ alarm_actions }}”
dimensions:
InstanceId: “{{ item.instance_id }}”
loop: “{{ ec2_instances.instances }}”

– name: Create CloudWatch Alarm for Scale Down
amazon.aws.cloudwatch_metric_alarm:
region: “{{ aws_region }}”
alarm_name: “scale-down-{{ application_tag }}”
metric_name: “CPUUtilization”
namespace: “AWS/EC2”
statistic: “Average”
comparison_operator: “LessThanThreshold”
threshold: “{{ scale_down_threshold }}”
period: “{{ period }}”
evaluation_periods: “{{ evaluation_periods }}”
unit: “Percent”
alarm_actions: “{{ alarm_actions }}”
dimensions:
InstanceId: “{{ item.instance_id }}”
loop: “{{ ec2_instances.instances }}”
获取CloudWatch指标数据
—
– name: Get CloudWatch Metrics
hosts: localhost
gather_facts: false
vars:
aws_region: “us-east-1”
application_tag: “WebApp”
metric_name: “CPUUtilization”
start_time: “{{ (ansible_date_time.epoch | int – 3600) | strftime(‘%Y-%m-%dT%H:%M:%SZ’) }}”
end_time: “{{ (ansible_date_time.epoch | int) | strftime(‘%Y-%m-%dT%H:%M:%SZ’) }}”

tasks:
– name: Get EC2 instance IDs
amazon.aws.ec2_instance_info:
region: “{{ aws_region }}”
filters:
“tag:Application”: “{{ application_tag }}”
instance-state-name: “running”
register: ec2_instances

– name: Get CPU metrics for each instance
amazon.aws.cloudwatch_metric_alarm:
region: “{{ aws_region }}”
metric_name: “{{ metric_name }}”
namespace: “AWS/EC2”
statistic: “Average”
start_time: “{{ start_time }}”
end_time: “{{ end_time }}”
period: 300
unit: “Percent”
dimensions:
InstanceId: “{{ item.instance_id }}”
loop: “{{ ec2_instances.instances }}”
register: metrics

– name: Calculate average CPU across all instances
set_fact:
avg_cpu: “{{ (metrics.results | map(attribute=’Datapoints’) | flatten | map(attribute=’Average’) | list | sum) / (metrics.results | map(attribute=’Datapoints’) | flatten | list | length) }}”

– name: Display average CPU
debug:
msg: “Average CPU Utilization: {{ avg_cpu | round(2) }}%”
2. 定时扩缩容策略
基于时间的扩缩容
—
– name: Time-based Auto Scaling
hosts: localhost
gather_facts: false
vars:
aws_region: “us-east-1”
application_tag: “WebApp”
desired_capacity:
peak: 5
off_peak: 2
weekend: 1
peak_hours: [9, 10, 11, 12, 13, 14, 15, 16, 17]
timezone: “UTC”

tasks:
– name: Get current hour
set_fact:
current_hour: “{{ (ansible_date_time.epoch | int) | strftime(‘%H’) | int }}”
current_day: “{{ (ansible_date_time.epoch | int) | strftime(‘%w’) | int }}”

– name: Determine desired capacity
set_fact:
target_capacity: “{{ peak_hours | contains(current_hour) | ternary(peak_capacity, off_peak_capacity) }}”
vars:
peak_capacity: “{{ desired_capacity.peak }}”
off_peak_capacity: “{{ desired_capacity.off_peak }}”

– name: Adjust for weekend
set_fact:
target_capacity: “{{ desired_capacity.weekend }}”
when: current_day == 0 or current_day == 6

– name: Get current instance count
amazon.aws.ec2_instance_info:
region: “{{ aws_region }}”
filters:
“tag:Application”: “{{ application_tag }}”
instance-state-name: “running”
register: current_instances

– name: Calculate difference
set_fact:
scale_difference: “{{ target_capacity | int – (current_instances.instances | length) | int }}”

– name: Scale up if needed
include_tasks: scale_up.yml
when: scale_difference > 0

– name: Scale down if needed
include_tasks: scale_down.yml
when: scale_difference < 0 3. 基于自定义指标的扩缩容 --- - name: Custom Metric Auto Scaling hosts: localhost gather_facts: false vars: aws_region: "us-east-1" application_tag: "WebApp" metrics_namespace: "Custom/Application" scale_up_threshold: 100 # 自定义指标阈值 scale_down_threshold: 50 min_instances: 1 max_instances: 10 tasks: - name: Get custom metric value amazon.aws.cloudwatch_metric_alarm: region: "{{ aws_region }}" metric_name: "RequestCount" namespace: "{{ metrics_namespace }}" statistic: "Sum" start_time: "{{ (ansible_date_time.epoch | int - 300) | strftime('%Y-%m-%dT%H:%M:%SZ') }}" end_time: "{{ (ansible_date_time.epoch | int) | strftime('%Y-%m-%dT%H:%M:%SZ') }}" period: 60 register: metric_data - name: Calculate metric value set_fact: metric_value: "{{ metric_data.Datapoints | map(attribute='Sum') | list | sum }}" - name: Get current instance count amazon.aws.ec2_instance_info: region: "{{ aws_region }}" filters: "tag:Application": "{{ application_tag }}" instance-state-name: "running" register: current_instances - name: Determine scaling action set_fact: scale_action: "{{ 'scale_up' if metric_value > scale_up_threshold else (‘scale_down’ if metric_value < scale_down_threshold else 'no_change') }}" current_count: "{{ current_instances.instances | length }}" - name: Execute scale up include_tasks: scale_up.yml when: scale_action == 'scale_up' and current_count < max_instances - name: Execute scale down include_tasks: scale_down.yml when: scale_action == 'scale_down' and current_count > min_instances
实现自动扩缩容的完整Playbook
1. 扩容(Scale Up)Playbook
—
# scale_up.yml
– name: Scale Up EC2 Instances
hosts: localhost
gather_facts: false
vars:
aws_region: “us-east-1”
application_tag: “WebApp”
scale_up_count: 1 # 每次扩容数量
instance_type: “t3.micro”
image_id: “ami-0c02fb55956c7d316”
key_name: “your-key-pair”
security_group_ids: [“sg-xxxxxxxx”]
subnet_id: “subnet-xxxxxxxx”
health_check_grace_period: 300 # 健康检查宽限期(秒)

tasks:
– name: Get current instance details
amazon.aws.ec2_instance_info:
region: “{{ aws_region }}”
filters:
“tag:Application”: “{{ application_tag }}”
instance-state-name: “running”
register: current_instances

– name: Calculate new instance number
set_fact:
next_instance_number: “{{ (current_instances.instances | map(attribute=’tags.Name’) | map(‘regex_replace’, ‘.*-(\\d+)$’, ‘\\1’) | map(‘int’) | list | max) + 1 if current_instances.instances | length > 0 else 1 }}”

– name: Launch new instances
amazon.aws.ec2_instance:
name: “{{ application_tag }}-{{ next_instance_number }}”
region: “{{ aws_region }}”
key_name: “{{ key_name }}”
instance_type: “{{ instance_type }}”
image_id: “{{ image_id }}”
security_group_ids: “{{ security_group_ids }}”
subnet_id: “{{ subnet_id }}”
wait: true
wait_timeout: 600
tags:
Application: “{{ application_tag }}”
Environment: “Production”
ManagedBy: “Ansible”
InstanceNumber: “{{ next_instance_number }}”
network:
assign_public_ip: true
loop: “{{ range(0, scale_up_count) | list }}”
register: new_instances

– name: Wait for new instances to be healthy
pause:
seconds: “{{ health_check_grace_period }}”

– name: Add new instances to load balancer (if using ELB)
amazon.aws.ec2_instance_info:
region: “{{ aws_region }}”
instance_ids: “{{ new_instances.results | map(attribute=’instance_id’) | list }}”
register: new_instance_info

– name: Update inventory
lineinfile:
path: “./inventory/hosts”
line: “{{ item.private_ip_address }} ansible_user=ec2-user ansible_ssh_private_key_file={{ key_name }}.pem”
create: yes
loop: “{{ new_instance_info.instances }}”

– name: Verify new instances are running
amazon.aws.ec2_instance_info:
region: “{{ aws_region }}”
instance_ids: “{{ new_instances.results | map(attribute=’instance_id’) | list }}”
register: verification
until: verification.instances | selectattr(‘instance_state.name’, ‘equalto’, ‘running’) | list | length == scale_up_count
retries: 20
delay: 10

– name: Report scale up result
debug:
msg: “Successfully scaled up {{ scale_up_count }} instances. Total instances: {{ current_instances.instances | length + scale_up_count }}”
2. 缩容(Scale Down)Playbook
—
# scale_down.yml
– name: Scale Down EC2 Instances
hosts: localhost
gather_facts: false
vars:
aws_region: “us-east-1”
application_tag: “WebApp”
scale_down_count: 1 # 每次缩容数量
drain_time: 300 # 等待时间,让实例完成现有请求
health_check_grace_period: 60 # 等待时间,确认实例已下线

tasks:
– name: Get running instances
amazon.aws.ec2_instance_info:
region: “{{ aws_region }}”
filters:
“tag:Application”: “{{ application_tag }}”
instance-state-name: “running”
register: current_instances

– name: Select instances to terminate (oldest first)
set_fact:
instances_to_terminate: “{{ current_instances.instances | sort(attribute=’launch_time’) | map(attribute=’instance_id’) | list[:scale_down_count] }}”

– name: Drain connections
pause:
seconds: “{{ drain_time }}”
when: instances_to_terminate | length > 0

– name: Remove instances from load balancer
debug:
msg: “Removing instances from load balancer: {{ instances_to_terminate }}”
when: instances_to_terminate | length > 0

– name: Terminate instances
amazon.aws.ec2_instance:
region: “{{ aws_region }}”
instance_ids: “{{ instances_to_terminate }}”
state: absent
wait: true
wait_timeout: 300
register: termination_result

– name: Wait for termination to complete
pause:
seconds: “{{ health_check_grace_period }}”

– name: Verify termination
amazon.aws.ec2_instance_info:
region: “{{ aws_region }}”
instance_ids: “{{ instances_to_terminate }}”
register: termination_check
until: termination_check.instances | length == 0
retries: 10
delay: 10

– name: Update inventory
lineinfile:
path: “./inventory/hosts”
regexp: “{{ item }} ”
state: absent
loop: “{{ instances_to_terminate }}”
ignore_errors: yes

– name: Report scale down result
debug:
msg: “Successfully scaled down {{ scale_down_count }} instances. Remaining instances: {{ current_instances.instances | length – scale_down_count }}”
3. 主扩缩容控制器
—
# auto_scaling_controller.yml
– name: Auto Scaling Controller
hosts: localhost
gather_facts: false
vars:
aws_region: “us-east-1”
application_tag: “WebApp”
min_instances: 1
max_instances: 10
scale_up_threshold: 70
scale_down_threshold: 30
cooldown_period: 300 # 扩缩容冷却时间(秒)
scaling_strategy: “cpu” # cpu, time, custom

# Time-based settings
peak_hours: [9, 10, 11, 12, 13, 14, 15, 16, 17]
desired_capacity:
peak: 5
off_peak: 2
weekend: 1

# Custom metric settings
custom_metric_name: “RequestCount”
custom_metric_namespace: “Custom/Application”
custom_scale_up_threshold: 100
custom_scale_down_threshold: 50

tasks:
– name: Get current instance count
amazon.aws.ec2_instance_info:
region: “{{ aws_region }}”
filters:
“tag:Application”: “{{ application_tag }}”
instance-state-name: “running”
register: current_instances

– name: Set current count
set_fact:
current_count: “{{ current_instances.instances | length }}”

– name: Determine scaling action based on strategy
block:
– name: CPU-based scaling
block:
– name: Get average CPU metrics
amazon.aws.cloudwatch_metric_alarm:
region: “{{ aws_region }}”
metric_name: “CPUUtilization”
namespace: “AWS/EC2”
statistic: “Average”
start_time: “{{ (ansible_date_time.epoch | int – 300) | strftime(‘%Y-%m-%dT%H:%M:%SZ’) }}”
end_time: “{{ (ansible_date_time.epoch | int) | strftime(‘%Y-%m-%dT%H:%M:%SZ’) }}”
period: 60
unit: “Percent”
register: cpu_metrics

– name: Calculate average CPU
set_fact:
avg_cpu: “{{ (cpu_metrics.Datapoints | map(attribute=’Average’) | list | sum) / (cpu_metrics.Datapoints | list | length) if cpu_metrics.Datapoints | length > 0 else 0 }}”

– name: Determine CPU-based action
set_fact:
scale_action: “{{ ‘scale_up’ if avg_cpu > scale_up_threshold else (‘scale_down’ if avg_cpu < scale_down_threshold else 'no_change') }}" when: scaling_strategy == "cpu" - name: Time-based scaling block: - name: Get current hour and day set_fact: current_hour: "{{ (ansible_date_time.epoch | int) | strftime('%H') | int }}" current_day: "{{ (ansible_date_time.epoch | int) | strftime('%w') | int }}" - name: Determine target capacity set_fact: target_capacity: "{{ desired_capacity.weekend if current_day == 0 or current_day == 6 else (desired_capacity.peak if peak_hours | contains(current_hour) else desired_capacity.off_peak) }}" - name: Determine time-based action set_fact: scale_action: "{{ 'scale_up' if target_capacity > current_count else (‘scale_down’ if target_capacity < current_count else 'no_change') }}" when: scaling_strategy == "time" - name: Custom metric scaling block: - name: Get custom metric value amazon.aws.cloudwatch_metric_alarm: region: "{{ aws_region }}" metric_name: "{{ custom_metric_name }}" namespace: "{{ custom_metric_namespace }}" statistic: "Sum" start_time: "{{ (ansible_date_time.epoch | int - 300) | strftime('%Y-%m-%dT%H:%M:%SZ') }}" end_time: "{{ (ansible_date_time.epoch | int) | strftime('%Y-%m-%dT%H:%M:%SZ') }}" period: 60 register: custom_metric - name: Calculate metric value set_fact: metric_value: "{{ custom_metric.Datapoints | map(attribute='Sum') | list | sum }}" - name: Determine custom metric action set_fact: scale_action: "{{ 'scale_up' if metric_value > custom_scale_up_threshold else (‘scale_down’ if metric_value < custom_scale_down_threshold else 'no_change') }}" when: scaling_strategy == "custom" when: scaling_strategy in ["cpu", "time", "custom"] - name: Check cooldown period block: - name: Get last scaling action timestamp set_fact: last_scaling_time: "{{ lookup('file', 'last_scaling.txt') if lookup('file', 'last_scaling.txt') is defined else 0 }}" - name: Check if cooldown has passed set_fact: cooldown_ok: "{{ (ansible_date_time.epoch | int - last_scaling_time | int) > cooldown_period }}”

– name: Skip scaling due to cooldown
debug:
msg: “Skipping scaling action due to cooldown period. Last action: {{ last_scaling_time }}, Current: {{ ansible_date_time.epoch }}”
when: not cooldown_ok
when: scale_action != “no_change”

– name: Execute scale up
include_tasks: scale_up.yml
when:
– scale_action == “scale_up”
– current_count < max_instances - cooldown_ok | default(true) - name: Execute scale down include_tasks: scale_down.yml when: - scale_action == "scale_down" - current_count > min_instances
– cooldown_ok | default(true)

– name: Update last scaling timestamp
copy:
content: “{{ ansible_date_time.epoch }}”
dest: “last_scaling.txt”
when:
– scale_action != “no_change”
– cooldown_ok | default(true)

– name: Report scaling decision
debug:
msg: |
Scaling Decision:
– Strategy: {{ scaling_strategy }}
– Current Count: {{ current_count }}
– Action: {{ scale_action }}
– Min/Max: {{ min_instances }}/{{ max_instances }}
– Cooldown OK: {{ cooldown_ok | default(‘N/A’) }}
高级主题
1. 与负载均衡器集成
—
– name: Integrate with Load Balancer
hosts: localhost
gather_facts: false
vars:
aws_region: “us-east-1”
load_balancer_name: “my-app-lb”
target_group_arn: “arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/my-app-tg/1234567890123456”

tasks:
– name: Register instance with target group
amazon.aws.ec2_instance_info:
region: “{{ aws_region }}”
instance_ids: “{{ instance_ids }}”
register: instance_info

– name: Add instances to target group
amazon.aws.elasticloadbalancingv2_target_group:
region: “{{ aws_region }}”
target_group_arn: “{{ target_group_arn }}”
targets: “{{ instance_info.instances | map(attribute=’private_ip_address’) | list }}”
state: present

– name: Wait for health checks
pause:
seconds: 60

– name: Verify target health
amazon.aws.elb_target_group_info:
region: “{{ aws_region }}”
target_group_arn: “{{ target_group_arn }}”
register: tg_info

– name: Check if all targets are healthy
assert:
that:
– item.health_state == “healthy”
fail_msg: “Target {{ item.target.id }} is not healthy”
loop: “{{ tg_info.target_health_descriptions }}”
2. 安全组和网络配置
—
– name: Configure Security Groups
hosts: localhost
gather_facts: false
vars:
aws_region: “us-east-1”
vpc_id: “vpc-xxxxxxxx”
security_group_name: “autoscaling-sg”

tasks:
– name: Create security group
amazon.aws.ec2_security_group:
name: “{{ security_group_name }}”
description: “Security group for auto-scaling instances”
vpc_id: “{{ vpc_id }}”
region: “{{ aws_region }}”
rules:
– proto: tcp
from_port: 80
to_port: 80
cidr_ip: 0.0.0.0/0
rule_desc: “HTTP access”
– proto: tcp
from_port: 443
to_port: 443
cidr_ip: 0.0.0.0/0
rule_desc: “HTTPS access”
– proto: tcp
from_port: 22
to_port: 22
cidr_ip: “{{ your_ip }}/32” # 限制SSH访问
rule_desc: “SSH access”
rules_egress:
– proto: all
cidr_ip: 0.0.0.0/0
register: sg

– name: Create security group rules for internal communication
amazon.aws.ec2_security_group:
name: “{{ security_group_name }}”
vpc_id: “{{ vpc_id }}”
region: “{{ aws_region }}”
rules:
– proto: tcp
from_port: 0
to_port: 65535
group_id: “{{ sg.group_id }}”
rule_desc: “Internal communication”
3. 成本优化策略
—
– name: Cost Optimization
hosts: localhost
gather_facts: false
vars:
aws_region: “us-east-1”
application_tag: “WebApp”
spot_instance: true
spot_price_limit: 0.05
instance_types: [“t3.micro”, “t3.small”, “t3.medium”]

tasks:
– name: Get current spot prices
amazon.aws.ec2_instance_info:
region: “{{ aws_region }}”
filters:
instance-type: “{{ item }}”
loop: “{{ instance_types }}”
register: spot_prices

– name: Find cheapest instance type
set_fact:
cheapest_instance: “{{ instance_types[0] }}”
min_price: 999999

– name: Compare prices and select cheapest
set_fact:
cheapest_instance: “{{ item.0 }}”
min_price: “{{ item.1 }}”
loop: “{{ instance_types | zip(spot_prices.results | map(attribute=’spot_price’) | map(‘float’) | list) }}”
when: item.1 | float < min_price - name: Launch spot instance request amazon.aws.ec2_spot_instance: region: "{{ aws_region }}" spot_price: "{{ spot_price_limit }}" instance_count: 1 launch_specification: image_id: "ami-0c02fb55956c7d316" instance_type: "{{ cheapest_instance }}" key_name: "your-key-pair" security_group_ids: ["sg-xxxxxxxx"] subnet_id: "subnet-xxxxxxxx" tags: Application: "{{ application_tag }}" SpotInstance: "true" when: spot_instance - name: Request spot instances amazon.aws.ec2_spot_instance: region: "{{ aws_region }}" spot_price: "{{ spot_price_limit }}" instance_count: "{{ scale_up_count }}" launch_specification: image_id: "{{ image_id }}" instance_type: "{{ cheapest_instance }}" key_name: "{{ key_name }}" security_group_ids: "{{ security_group_ids }}" subnet_id: "{{ subnet_id }}" tags: Application: "{{ application_tag }}" ManagedBy: "Ansible" SpotInstance: "true" register: spot_request - name: Wait for spot instances to be fulfilled amazon.aws.ec2_spot_instance_info: region: "{{ aws_region }}" spot_instance_request_ids: "{{ spot_request.results | map(attribute='spot_instance_request_id') | list }}" register: spot_status until: spot_status.instances | length == scale_up_count retries: 30 delay: 10 4. 监控和告警 --- - name: Monitoring and Alerting hosts: localhost gather_facts: false vars: aws_region: "us-east-1" application_tag: "WebApp" sns_topic_arn: "arn:aws:sns:us-east-1:123456789012:auto-scaling-alerts" cloudwatch_dashboard_name: "AutoScaling-Dashboard" tasks: - name: Create SNS Topic amazon.aws.sns_topic: region: "{{ aws_region }}" name: "auto-scaling-alerts" state: present register: sns_topic - name: Subscribe email to SNS amazon.aws.sns_topic: region: "{{ aws_region }}" name: "auto-scaling-alerts" email: "ops-team@example.com" state: present - name: Create CloudWatch Dashboard amazon.aws.cloudwatch_dashboard: region: "{{ aws_region }}" dashboard_name: "{{ cloudwatch_dashboard_name }}" dashboard_body: | { "widgets": [ { "type": "metric", "properties": { "metrics": [ ["AWS/EC2", "CPUUtilization", "InstanceId", "{{ item.instance_id }}"] for item in current_instances.instances ], "period": 300, "stat": "Average", "region": "{{ aws_region }}", "title": "CPU Utilization" } }, { "type": "metric", "properties": { "metrics": [ ["AWS/EC2", "NetworkIn", "InstanceId", "{{ item.instance_id }}"], [".", "NetworkOut", ".", "."] ], "period": 300, "stat": "Sum", "region": "{{ aws_region }}", "title": "Network Traffic" } } ] } loop: "{{ current_instances.instances }}" - name: Create scaling activity log amazon.aws.cloudwatch_log_group: region: "{{ aws_region }}" log_group_name: "/aws/auto-scaling/{{ application_tag }}" retention_in_days: 30 state: present - name: Put log events for scaling actions amazon.aws.cloudwatch_log_stream: region: "{{ aws_region }}" log_group_name: "/aws/auto-scaling/{{ application_tag }}" log_stream_name: "{{ ansible_date_time.date }}" log_events: - timestamp: "{{ (ansible_date_time.epoch | int) * 1000 }}" message: "Scaling action: {{ scale_action }}, Current count: {{ current_count }}, Target: {{ target_capacity }}" when: scale_action != "no_change" 实战案例:Web应用自动扩缩容 场景描述 假设我们有一个Web应用,需要根据流量自动调整服务器数量: 业务时间 (9:00-18:00):至少2台服务器,最多5台 非业务时间 :1台服务器 周末 :1台服务器 紧急情况 :CPU > 70% 时扩容,CPU < 30% 时缩容 完整解决方案 1. 环境准备 --- # setup_environment.yml - name: Setup Auto Scaling Environment hosts: localhost gather_facts: false vars: aws_region: "us-east-1" vpc_cidr: "10.0.0.0/16" subnet_cidr: "10.0.1.0/24" security_group_name: "web-app-sg" application_name: "web-app" tasks: # 创建VPC和子网 - name: Create VPC amazon.aws.ec2_vpc_net: name: "{{ application_name }}-vpc" cidr_block: "{{ vpc_cidr }}" region: "{{ aws_region }}" register: vpc - name: Create Subnet amazon.aws.ec2_vpc_subnet: state: present vpc_id: "{{ vpc.vpc.id }}" cidr: "{{ subnet_cidr }}" az: "{{ aws_region }}a" region: "{{ aws_region }}" register: subnet - name: Create Internet Gateway amazon.aws.ec2_vpc_igw: vpc_id: "{{ vpc.vpc.id }}" region: "{{ aws_region }}" register: igw - name: Create Route Table amazon.aws.ec2_vpc_route_table: vpc_id: "{{ vpc.vpc.id }}" region: "{{ aws_region }}" routes: - dest: 0.0.0.0/0 gateway_id: "{{ igw.gateway_id }}" subnets: - "{{ subnet.subnet.id }}" # 创建安全组 - name: Create Security Group amazon.aws.ec2_security_group: name: "{{ security_group_name }}" description: "Security group for web application" vpc_id: "{{ vpc.vpc.id }}" region: "{{ aws_region }}" rules: - proto: tcp from_port: 80 to_port: 80 cidr_ip: 0.0.0.0/0 rule_desc: "HTTP" - proto: tcp from_port: 443 to_port: 443 cidr_ip: 0.0.0.0/0 rule_desc: "HTTPS" - proto: tcp from_port: 22 to_port: 22 cidr_ip: "{{ your_ip }}/32" rule_desc: "SSH" rules_egress: - proto: all cidr_ip: 0.0.0.0/0 register: sg # 创建SNS Topic用于告警 - name: Create SNS Topic amazon.aws.sns_topic: region: "{{ aws_region }}" name: "{{ application_name }}-alerts" state: present - name: Subscribe email amazon.aws.sns_topic: region: "{{ aws_region }}" name: "{{ application_name }}-alerts" email: "ops@example.com" state: present # 保存环境变量 - name: Save environment variables copy: content: | vpc_id: {{ vpc.vpc.id }} subnet_id: {{ subnet.subnet.id }} security_group_id: {{ sg.group_id }} aws_region: {{ aws_region }} application_name: {{ application_name }} dest: "./environment.yml" mode: '0644' 2. 应用部署脚本 --- # deploy_app.yml - name: Deploy Web Application hosts: localhost gather_facts: false vars: aws_region: "us-east-1" instance_name: "web-app-server" instance_type: "t3.micro" image_id: "ami-0c02fb55956c7d316" key_name: "your-key-pair" user_data: | #!/bin/bash yum update -y yum install -y httpd systemctl start httpd systemctl enable httpd echo "

Web Application Server

Instance: $(hostname)

” > /var/www/html/index.html

tasks:
– name: Include environment variables
include_vars:
file: “./environment.yml”

– name: Launch EC2 instance
amazon.aws.ec2_instance:
name: “{{ instance_name }}”
region: “{{ aws_region }}”
key_name: “{{ key_name }}”
instance_type: “{{ instance_type }}”
image_id: “{{ image_id }}”
security_group_ids: [“{{ security_group_id }}”]
subnet_id: “{{ subnet_id }}”
user_data: “{{ user_data }}”
wait: true
wait_timeout: 600
tags:
Application: “{{ application_name }}”
Environment: “Production”
ManagedBy: “Ansible”
network:
assign_public_ip: true
register: ec2_instance

– name: Wait for instance to be ready
pause:
seconds: 30

– name: Get instance details
amazon.aws.ec2_instance_info:
region: “{{ aws_region }}”
instance_ids: “{{ ec2_instance.instance_id }}”
register: instance_info

– name: Test application
uri:
url: “http://{{ instance_info.instances[0].public_ip_address }}”
method: GET
register: web_result
until: web_result.status == 200
retries: 10
delay: 5

– name: Display application URL
debug:
msg: “Application deployed at: http://{{ instance_info.instances[0].public_ip_address }}”
3. 完整的自动扩缩容系统
—
# web_app_auto_scaling.yml
– name: Web App Auto Scaling System
hosts: localhost
gather_facts: false
vars:
aws_region: “us-east-1”
application_name: “web-app”

# Scaling parameters
min_instances: 1
max_instances: 5
scale_up_cpu_threshold: 70
scale_down_cpu_threshold: 30

# Time-based parameters
business_hours: [9, 10, 11, 12, 13, 14, 15, 16, 17, 18]
business_hours_min: 2
off_hours_min: 1
weekend_min: 1

# Instance configuration
instance_type: “t3.micro”
image_id: “ami-0c02fb55956c7d316”
key_name: “your-key-pair”

# Health check
health_check_grace_period: 300

# Cooldown
cooldown_period: 300

tasks:
– name: Include environment variables
include_vars:
file: “./environment.yml”

– name: Get current instance count
amazon.aws.ec2_instance_info:
region: “{{ aws_region }}”
filters:
“tag:Application”: “{{ application_name }}”
instance-state-name: “running”
register: current_instances

– name: Set current count
set_fact:
current_count: “{{ current_instances.instances | length }}”

– name: Get average CPU utilization
amazon.aws.cloudwatch_metric_alarm:
region: “{{ aws_region }}”
metric_name: “CPUUtilization”
namespace: “AWS/EC2”
statistic: “Average”
start_time: “{{ (ansible_date_time.epoch | int – 300) | strftime(‘%Y-%m-%dT%H:%M:%SZ’) }}”
end_time: “{{ (ansible_date_time.epoch | int) | strftime(‘%Y-%m-%dT%H:%M:%SZ’) }}”
period: 60
unit: “Percent”
register: cpu_metrics

– name: Calculate average CPU
set_fact:
avg_cpu: “{{ (cpu_metrics.Datapoints | map(attribute=’Average’) | list | sum) / (cpu_metrics.Datapoints | list | length) if cpu_metrics.Datapoints | length > 0 else 0 }}”

– name: Determine time-based minimum
set_fact:
current_hour: “{{ (ansible_date_time.epoch | int) | strftime(‘%H’) | int }}”
current_day: “{{ (ansible_date_time.epoch | int) | strftime(‘%w’) | int }}”

– name: Set time-based minimum
set_fact:
time_based_min: “{{ weekend_min if current_day == 0 or current_day == 6 else (business_hours_min if business_hours | contains(current_hour) else off_hours_min) }}”

– name: Determine scaling action
set_fact:
scale_action: “no_change”
target_count: “{{ current_count }}”

– name: Check if scaling is needed
block:
– name: Check CPU for scale up
set_fact:
scale_action: “scale_up”
target_count: “{{ current_count + 1 }}”
when: avg_cpu > scale_up_cpu_threshold and current_count < max_instances - name: Check CPU for scale down set_fact: scale_action: "scale_down" target_count: "{{ current_count - 1 }}" when: avg_cpu < scale_down_cpu_threshold and current_count > time_based_min

– name: Check time-based minimum
set_fact:
scale_action: “scale_up”
target_count: “{{ time_based_min }}”
when: current_count < time_based_min - name: Check time-based maximum set_fact: scale_action: "scale_down" target_count: "{{ business_hours_min if current_hour in business_hours else off_hours_min }}" when: current_count > (business_hours_min if current_hour in business_hours else off_hours_min) and avg_cpu < scale_down_cpu_threshold - name: Check cooldown block: - name: Get last scaling time set_fact: last_scaling_time: "{{ lookup('file', 'last_scaling_{{ application_name }}.txt') if lookup('file', 'last_scaling_{{ application_name }}.txt') is defined else 0 }}" - name: Check cooldown set_fact: cooldown_ok: "{{ (ansible_date_time.epoch | int - last_scaling_time | int) > cooldown_period }}”

– name: Skip due to cooldown
debug:
msg: “Cooldown active. Last scaling: {{ last_scaling_time }}, Current: {{ ansible_date_time.epoch }}, Diff: {{ ansible_date_time.epoch | int – last_scaling_time | int }}s”
when: not cooldown_ok
when: scale_action != “no_change”

– name: Execute scaling
block:
– name: Scale up
include_tasks: scale_up.yml
vars:
scale_up_count: “{{ target_count – current_count }}”
when: scale_action == “scale_up” and cooldown_ok | default(true)

– name: Scale down
include_tasks: scale_down.yml
vars:
scale_down_count: “{{ current_count – target_count }}”
when: scale_action == “scale_down” and cooldown_ok | default(true)

– name: Update last scaling time
copy:
content: “{{ ansible_date_time.epoch }}”
dest: “last_scaling_{{ application_name }}.txt”
when: cooldown_ok | default(true)
when: scale_action != “no_change”

– name: Report status
debug:
msg: |
=== Auto Scaling Status ===
Application: {{ application_name }}
Current Instances: {{ current_count }}
Time-based Minimum: {{ time_based_min }}
Average CPU: {{ avg_cpu | round(2) }}%
Scaling Action: {{ scale_action }}
Target Count: {{ target_count }}
Cooldown OK: {{ cooldown_ok | default(‘N/A’) }}
Status: {{ ‘SCALED’ if scale_action != ‘no_change’ else ‘NO CHANGE’ }}
4. 定时执行配置
创建cron作业或使用Ansible Tower/AWX定期执行:
# 每5分钟检查一次
*/5 * * * * cd /path/to/ansible/playbooks && ansible-playbook -i inventory/hosts web_app_auto_scaling.yml >> /var/log/auto_scaling.log 2>&1
或者使用systemd timer:
# /etc/systemd/system/auto-scaling.service
[Unit]
Description=Auto Scaling Service
After=network.target

[Service]
Type=oneshot
ExecStart=/usr/bin/ansible-playbook /opt/ansible/playbooks/web_app_auto_scaling.yml
User=ansible

# /etc/systemd/system/auto-scaling.timer
[Unit]
Description=Run auto-scaling every 5 minutes
Requires=auto-scaling.service

[Timer]
OnCalendar=*:0/5
Persistent=true

[Install]
WantedBy=timers.target
故障排除与最佳实践
常见问题及解决方案
1. 权限问题
问题
:
AccessDenied
错误
解决方案
:
# 检查IAM策略
– name: Verify IAM permissions
command: aws iam simulate-principal-policy –policy-source-arn arn:aws:iam::123456789012:user/ansible-user –action-names ec2:RunInstances ec2:TerminateInstances cloudwatch:GetMetricStatistics
register: permission_check

– name: Display permission results
debug:
var: permission_check.stdout
2. 网络连接问题
问题
:实例无法访问或SSH连接失败
解决方案
:
– name: Check security group rules
amazon.aws.ec2_security_group_info:
region: “{{ aws_region }}”
group_ids: “{{ security_group_ids }}”
register: sg_info

– name: Display security group rules
debug:
var: sg_info.security_groups[0].rules
3. 实例启动失败
问题
:实例启动后立即终止或状态异常
解决方案
:
– name: Get instance termination reasons
amazon.aws.ec2_instance_info:
region: “{{ aws_region }}”
filters:
instance-state-name: “terminated”
“tag:Application”: “{{ application_name }}”
register: terminated_instances

– name: Check termination reason
debug:
msg: “Instance {{ item.instance_id }} terminated at {{ item.launch_time }}. State reason: {{ item.state_reason }}”
loop: “{{ terminated_instances.instances }}”
最佳实践
1. 安全最佳实践
# 使用最小权限原则
– name: Create minimal IAM policy
amazon.aws.iam_policy:
policy_name: “ansible-auto-scaling-policy”
policy: |
{
“Version”: “2012-10-17”,
“Statement”: [
{
“Effect”: “Allow”,
“Action”: [
“ec2:RunInstances”,
“ec2:TerminateInstances”,
“ec2:DescribeInstances”,
“ec2:DescribeInstanceStatus”,
“ec2:CreateTags”,
“cloudwatch:GetMetricStatistics”,
“elasticloadbalancing:DescribeTargetHealth”
],
“Resource”: “*”,
“Condition”: {
“StringEquals”: {
“aws:RequestedRegion”: “{{ aws_region }}”
}
}
}
]
}
state: present
2. 成本优化最佳实践
# 使用Spot实例降低成本
– name: Launch mixed on-demand and spot instances
block:
– name: Launch on-demand instances (minimum)
amazon.aws.ec2_instance:
name: “{{ application_name }}-on-demand-{{ item }}”
# … on-demand config
loop: “{{ range(0, min_instances) | list }}”

– name: Launch spot instances (additional capacity)
amazon.aws.ec2_spot_instance:
spot_price: “0.05”
instance_count: “{{ target_capacity – min_instances }}”
# … spot config
when: target_capacity > min_instances
3. 监控和日志最佳实践
# 详细日志记录
– name: Log scaling activity
amazon.aws.cloudwatch_log_stream:
region: “{{ aws_region }}”
log_group_name: “/aws/auto-scaling/{{ application_name }}”
log_stream_name: “{{ ansible_date_time.date }}”
log_events:
– timestamp: “{{ (ansible_date_time.epoch | int) * 1000 }}”
message: “{{ lookup(‘template’, ‘scaling_log.json.j2’) }}”
when: scale_action != “no_change”

# scaling_log.json.j2 模板
{
“timestamp”: “{{ ansible_date_time.iso8601 }}”,
“action”: “{{ scale_action }}”,
“current_count”: {{ current_count }},
“target_count”: {{ target_count }},
“cpu_avg”: {{ avg_cpu | round(2) }},
“strategy”: “{{ scaling_strategy }}”,
“region”: “{{ aws_region }}”
}
4. 测试和验证
# 测试Playbook
– name: Test Auto Scaling Logic
hosts: localhost
gather_facts: false
vars:
test_cases:
– {cpu: 80, current: 2, expected: “scale_up”}
– {cpu: 20, current: 3, expected: “scale_down”}
– {cpu: 50, current: 2, expected: “no_change”}

tasks:
– name: Run test cases
set_fact:
test_result: “{{ ‘PASS’ if (item.cpu > 70 and item.current < 5) == (item.expected == 'scale_up') else 'FAIL' }}" loop: "{{ test_cases }}" register: test_results - name: Display test results debug: msg: "Test {{ item.0 + 1 }}: {{ item.1.test_result }}" loop: "{{ test_results.results | enumerate }}" 总结 通过本文的详细指导,您已经掌握了使用Ansible实现AWS EC2自动扩缩容的完整方案。从基础的AWS集成到复杂的扩缩容策略,再到生产环境的最佳实践,这套解决方案能够帮助您: 显著降低运营成本 :通过精确匹配资源与需求,避免过度配置 提高系统可靠性 :自动响应负载变化,减少人工干预 增强灵活性 :支持多种扩缩容策略,适应不同业务场景 确保安全性 :包含完整的健康检查和优雅下线机制 建议您根据实际业务需求,逐步实施这些方案,并持续优化扩缩容策略。记住,成功的自动扩缩容系统需要持续的监控、调优和改进。