实现高可用性:可用区、自动扩展和 CloudWatch
在云计算环境中,实现高可用性对于确保服务的稳定运行至关重要。本文将围绕如何在 AWS 上构建高可用的 Jenkins 服务器展开,深入探讨在实现过程中遇到的问题及相应的解决方案。
1. 当前架构的问题
借助自动扩展,我们已经构建了一个由 EC2 实例组成的高可用架构。然而,当前的设置存在两个问题:
-
数据丢失
:Jenkins 服务器将数据存储在磁盘上。当启动新的虚拟机来从故障中恢复时,由于创建了新磁盘,这些数据会丢失。
-
IP 地址变更
:在启动新的虚拟机进行恢复后,Jenkins 服务器的公共和私有 IP 地址会发生变化,导致无法通过相同的端点访问 Jenkins 服务器。
2. 网络附加存储恢复的陷阱
EBS 服务为虚拟机提供网络附加存储。需要注意的是,EC2 实例与子网关联,而子网又与可用区关联。EBS 卷仅位于单个可用区中。如果由于故障在另一个可用区启动虚拟机,则无法从该可用区访问 EBS 卷。例如,若 Jenkins 数据存储在 us - east - 1a 可用区的 EBS 卷上,只要在同一可用区中有 EC2 实例运行,就可以挂载该 EBS 卷。但如果该可用区不可用,而在 us - east - 1b 可用区启动新的 EC2 实例,则无法访问 us - east - 1a 中的 EBS 卷,也就无法恢复 Jenkins,因为无法访问数据。
为避免不必要的成本,需要清理资源。执行以下命令删除与 Jenkins 设置对应的所有资源:
$ aws cloudformation delete-stack --stack-name jenkins-multiaz
$ aws cloudformation wait stack-delete-complete \
--stack-name jenkins-multiaz
同时,要注意区分可用性和耐久性保证:
-
可用性
:EBS 卷保证 99.999% 的时间可用。在可用区中断的情况下,该卷将不可用,但这并不意味着会丢失任何数据。一旦可用区恢复在线,就可以再次访问包含所有数据的 EBS 卷。
-
耐久性
:EBS 卷保证 99.9% 的时间不会丢失任何数据。如果使用 1000 个卷,预计每年会丢失一个卷及其数据。
3. 从数据中心故障中恢复的解决方案
针对数据中心故障,有以下几种解决方案:
1.
外包虚拟机状态
:将虚拟机的状态外包给默认使用多个可用区的托管服务,如 RDS、DynamoDB(NoSQL 数据库)、EFS(NFSv4.1 共享)或 S3(对象存储)。
2.
定期创建快照
:定期为 EBS 卷创建快照,并在 EC2 实例需要在另一个可用区恢复时使用这些快照。EBS 快照存储在 S3 上,因此在多个可用区可用。如果 EBS 卷是 ECS 实例的根卷,则创建 AMI 来备份 EBS 卷,而不是快照。
3.
使用分布式第三方存储解决方案
:使用分布式第三方存储解决方案将数据存储在多个可用区,如 GlusterFS、DRBD、MongoDB 等。
由于 Jenkins 服务器直接将数据存储在磁盘上,要外包虚拟机的状态,需要块级存储解决方案。EBS 卷仅在单个可用区可用,不太适合该问题。而 EFS 提供块级存储(通过 NFSv4.1),并在一个区域内的可用区之间自动复制数据。
4. 将 EFS 嵌入 Jenkins 设置
要将 EFS 嵌入 Jenkins 设置,需要对之前的多可用区模板进行三项修改:
1.
创建 EFS 文件系统
:
FileSystem:
Type: 'AWS::EFS::FileSystem'
Properties: {}
- 在每个可用区创建 EFS 挂载目标 :
MountTargetSecurityGroup:
Type: 'AWS::EC2::SecurityGroup'
Properties:
GroupDescription: 'EFS Mount target'
SecurityGroupIngress:
- FromPort: 2049
IpProtocol: tcp
SourceSecurityGroupId: !Ref SecurityGroup
ToPort: 2049
VpcId: !Ref VPC
MountTargetA:
Type: 'AWS::EFS::MountTarget'
Properties:
FileSystemId: !Ref FileSystem
SecurityGroups:
- !Ref MountTargetSecurityGroup
SubnetId: !Ref SubnetA
MountTargetB:
Type: 'AWS::EFS::MountTarget'
Properties:
FileSystemId: !Ref FileSystem
SecurityGroups:
- !Ref MountTargetSecurityGroup
SubnetId: !Ref SubnetB
- 调整用户数据以挂载 EFS 文件系统 :
LaunchConfiguration:
Type: 'AWS::AutoScaling::LaunchConfiguration'
Properties:
# [...]
UserData:
'Fn::Base64': !Sub |
#!/bin/bash -x
bash -ex << "TRY"
wget -q -T 60 https://.../jenkins-1.616-1.1.noarch.rpm
rpm --install jenkins-1.616-1.1.noarch.rpm
while ! nc -z \
${FileSystem}.efs.${AWS::Region}.amazonaws.com 2049; \
do sleep 10; done
sleep 10
echo -n "${FileSystem}.efs.${AWS::Region}.amazonaws.com:/ \
/var/lib/jenkins" >> /etc/fstab
echo " nfs4 nfsvers=4.1,rsize=1048576,wsize=1048576,hard, \
timeo=600,retrans=2,_netdev 0 0" >> /etc/fstab
mount -a
chown jenkins:jenkins /var/lib/jenkins/
# [...]
service jenkins start
TRY
/opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} \
--resource AutoScalingGroup --region ${AWS::Region}
执行以下命令创建将状态存储在 EFS 上的新 Jenkins 设置,将
$Password
替换为包含 8 - 40 个字符的密码:
$ aws cloudformation create-stack --stack-name jenkins-multiaz-efs \
--template-url https://s3.amazonaws.com/ \
awsinaction-code2/chapter14/multiaz-efs.yaml \
--parameters ParameterKey=JenkinsAdminPassword,ParameterValue=$Password
创建 CloudFormation 堆栈需要几分钟时间。运行以下命令获取虚拟机的公共 IP 地址:
$ aws ec2 describe-instances --filters "Name=tag:Name, \
Values=jenkins-multiaz-efs" "Name=instance-state-code,Values=16" \
--query "Reservations[0].Instances[0]. \
[InstanceId, PublicIpAddress, PrivateIpAddress, SubnetId]"
在浏览器中打开
http://$PublicIP:8080
,将
$PublicIP
替换为上一个命令输出中的公共 IP 地址,即可看到 Jenkins 服务器的 Web 界面。
创建新的 Jenkins 作业的步骤如下:
1. 在浏览器中打开
http://$PublicIP:8080/newJob
,将
$PublicIP
替换为上一个描述命令输出中的公共 IP 地址。
2. 使用用户
admin
和启动 CloudFormation 模板时选择的密码登录。
3. 输入
AWS in Action
作为新作业的名称。
4. 选择
Freestyle Project
作为作业类型,然后点击
OK
保存作业。
现在,执行以下命令终止 EC2 实例:
$ aws ec2 terminate-instances --instance-ids $InstanceId
几分钟后,自动扩展组会检测到虚拟机已终止,并启动新的虚拟机。重新运行
describe - instances
命令,直到输出包含新的运行中的虚拟机。再次在浏览器中打开
http://$PublicIP:8080
,会发现 Jenkins 服务器的 Web 界面仍然包含之前创建的
AWS in Action
作业。
此时,借助自动扩展构建了由 EC2 实例组成的高可用架构,并且状态现在存储在 EFS 上,在替换 EC2 实例时不会再丢失数据。但仍然存在一个问题:在启动新的虚拟机进行恢复后,Jenkins 服务器的公共和私有 IP 地址会发生变化,无法通过相同的端点访问 Jenkins 服务器。
4. 网络接口恢复的陷阱及解决方案
使用 CloudWatch 警报在同一可用区内恢复虚拟机很容易,因为私有 IP 地址和公共 IP 地址会自动保持不变,即使在故障转移后也可以使用这些 IP 地址作为端点访问 EC2 实例。但使用自动扩展从 EC2 实例或可用区故障中恢复时,情况则不同。如果需要在另一个可用区启动虚拟机,则必须在另一个子网中启动,因此无法为新虚拟机使用相同的私有 IP 地址。
默认情况下,也不能将弹性 IP 用作自动扩展启动的虚拟机的公共 IP 地址。但对于接收请求的静态端点的需求很常见。对于 Jenkins 服务器的用例,开发人员希望将 IP 地址或主机名添加到书签以访问 Web 界面。在使用自动扩展为单个虚拟机构建高可用性时,提供静态端点有以下几种可能性:
-
分配弹性 IP
:分配一个弹性 IP,并在虚拟机的引导过程中关联此公共 IP 地址。
-
创建或更新 DNS 条目
:创建或更新指向虚拟机当前公共或私有 IP 地址的 DNS 条目。
-
使用弹性负载均衡器(ELB)
:使用弹性负载均衡器(ELB)作为静态端点,将请求转发到当前虚拟机。
由于使用第二个解决方案需要将域名与 Route 53(DNS)服务关联,且需要注册域名才能实现,这里选择跳过。ELB 解决方案将在后续章节介绍,因此本文也跳过。下面将重点介绍第一个解决方案:分配弹性 IP 并在虚拟机的引导过程中关联此公共 IP 地址。
为避免不必要的成本,执行以下命令删除与 Jenkins 设置对应的所有资源:
$ aws cloudformation delete-stack --stack-name jenkins-multiaz-efs
$ aws cloudformation wait stack-delete-complete \
--stack-name jenkins-multiaz-efs
执行以下命令创建基于自动扩展的 Jenkins 设置,使用弹性 IP 地址作为静态端点:
$ aws cloudformation create-stack --stack-name jenkins-multiaz-efs-eip \
--template-url https://s3.amazonaws.com/ \
awsinaction-code2/chapter14/multiaz-efs-eip.yaml \
--parameters ParameterKey=JenkinsAdminPassword,ParameterValue=$Password \
--capabilities CAPABILITY_IAM
该命令基于以下模板创建堆栈,与原始自动扩展启动 Jenkins 服务器的模板相比,有以下不同:
IamRole:
Type: 'AWS::IAM::Role'
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: 'ec2.amazonaws.com'
Action: 'sts:AssumeRole'
Policies:
- PolicyName: root
PolicyDocument:
Version: '2012-10-17'
Statement:
- Action: 'ec2:AssociateAddress'
Resource: '*'
Effect: Allow
IamInstanceProfile:
Type: 'AWS::IAM::InstanceProfile'
Properties:
Roles:
- !Ref IamRole
ElasticIP:
Type: 'AWS::EC2::EIP'
Properties:
Domain: vpc
LaunchConfiguration:
Type: 'AWS::AutoScaling::LaunchConfiguration'
Properties:
InstanceMonitoring: false
IamInstanceProfile: !Ref IamInstanceProfile
ImageId: 'ami-6057e21a'
KeyName: mykey
SecurityGroups:
- !Ref SecurityGroup
AssociatePublicIpAddress: true
InstanceType: 't2.micro'
UserData:
'Fn::Base64': !Sub |
#!/bin/bash -x
bash -ex << "TRY"
INSTANCE_ID="$(curl -s http://169.254.169.254/ \
latest/meta-data/instance-id)"
aws --region ${AWS::Region} ec2 associate-address \
--instance-id $INSTANCE_ID \
--allocation-id ${ElasticIP.AllocationId}
# [...]
service jenkins start
TRY
/opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} \
--resource AutoScalingGroup --region ${AWS::Region}
如果查询返回包含 URL、用户和密码的输出,则表示堆栈已创建,Jenkins 服务器可以使用。在浏览器中打开该 URL,使用用户
admin
和选择的密码登录 Jenkins 服务器。如果输出为
null
,请等待几分钟后再试:
$ aws cloudformation describe-stacks --stack-name jenkins-multiaz-efs-eip \
--query "Stacks[0].Outputs"
要测试虚拟机的恢复是否按预期工作,需要知道运行中的虚拟机的实例 ID。运行以下命令获取此信息:
$ aws ec2 describe-instances --filters "Name=tag:Name, \
Values=jenkins-multiaz-efs-eip" "Name=instance-state-code,Values=16" \
--query "Reservations[0].Instances[0].InstanceId" --output text
执行以下命令终止虚拟机并测试自动扩展触发的恢复过程,将
$InstanceId
替换为上一个命令输出中的实例 ID:
$ aws ec2 terminate-instances --instance-ids $InstanceId
几分钟后,虚拟机将恢复。由于在引导时为新虚拟机分配了弹性 IP,因此可以在浏览器中打开与终止旧实例之前相同的 URL。
清理资源,执行以下命令删除与 Jenkins 设置对应的所有资源:
$ aws cloudformation delete-stack --stack-name jenkins-multiaz-efs-eip
$ aws cloudformation wait stack-delete-complete \
--stack-name jenkins-multiaz-efs-eip
5. 分析灾难恢复要求
在开始在 AWS 上实施高可用性甚至容错架构之前,需要分析灾难恢复要求。在云中进行灾难恢复比在传统数据中心更容易、更便宜,但构建高可用性会增加系统的复杂性,从而增加初始成本和运营成本。恢复时间目标(RTO)和恢复点目标(RPO)是从业务角度定义灾难恢复重要性的标准。
-
恢复时间目标(RTO)
:系统从故障中恢复所需的时间,即系统在中断后达到工作状态(定义为系统服务级别)所需的时间。在 Jenkins 服务器的示例中,RTO 是在虚拟机或整个可用区出现故障后,启动新虚拟机并安装和运行 Jenkins 所需的时间。
-
恢复点目标(RPO)
:故障导致的可接受数据丢失时间,数据丢失量以时间衡量。如果在上午 10:00 发生中断,系统使用上午 09:00 的数据快照恢复,则数据丢失的时间跨度为一小时。在使用自动扩展的 Jenkins 服务器示例中,RPO 为零,因为数据存储在 EFS 上,在可用区中断期间不会丢失。
以下是单个 EC2 实例高可用性的比较:
| RTO | RPO | 可用性 |
| — | — | — |
| 约 10 分钟 | 无数据丢失 | 从虚拟机故障中恢复,但不能从整个可用区的中断中恢复 |
| 约 10 分钟 | 所有数据丢失 | 从虚拟机故障和整个可用区的中断中恢复 |
| 约 10 分钟 | 快照的实际时间跨度在 30 分钟到 24 小时之间 | 从虚拟机故障和整个可用区的中断中恢复 |
| 约 10 分钟 | 无数据丢失 | 从虚拟机故障和整个可用区的中断中恢复 |
如果希望能够从可用区的中断中恢复并降低 RPO,应尝试实现无状态服务器。使用 RDS、EFS、S3 和 DynamoDB 等存储服务可以帮助实现这一目标。
综上所述,通过合理选择解决方案,可以在 AWS 上构建高可用的 Jenkins 服务器,满足不同的业务需求。在实际应用中,需要根据具体情况权衡各种方案的优缺点,以达到最佳的性能和成本效益。
实现高可用性:可用区、自动扩展和 CloudWatch
6. RTO 和 RPO 对单一 EC2 实例的影响分析
在为单一 EC2 实例选择高可用性解决方案时,必须充分考虑应用程序的业务需求。以下是不同解决方案下 RTO 和 RPO 的对比分析:
| 解决方案 | RTO | RPO | 可用性 |
| — | — | — | — |
| EC2 实例,数据存储在 EBS 根卷,由 CloudWatch 警报触发恢复 | 约 10 分钟 | 无数据丢失 | 可从虚拟机故障恢复,但无法应对整个可用区的故障 |
| EC2 实例,数据存储在 EBS 根卷,由自动扩展触发恢复 | 约 10 分钟 | 所有数据丢失 | 可从虚拟机故障和整个可用区的故障中恢复 |
| EC2 实例,数据存储在 EBS 根卷且定期创建快照,由自动扩展触发恢复 | 约 10 分钟 | 快照的实际时间跨度在 30 分钟到 24 小时之间 | 可从虚拟机故障和整个可用区的故障中恢复 |
| EC2 实例,数据存储在 EFS 文件系统,由自动扩展触发恢复 | 约 10 分钟 | 无数据丢失 | 可从虚拟机故障和整个可用区的故障中恢复 |
从这个表格可以看出,不同的解决方案在 RTO、RPO 和可用性方面各有优劣。如果应用程序能够容忍可用区故障带来的不可用风险,那么 EC2 实例恢复方案是最简单的选择,且不会丢失数据。但如果应用程序必须在罕见的可用区故障中存活,那么使用 EFS 存储数据的自动扩展方案则更为安全,但可能会对性能产生一定影响。
7. 决策流程
为了帮助大家更清晰地选择适合的解决方案,下面是一个 mermaid 格式的流程图:
graph LR
classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px
A([开始]):::startend --> B{能否容忍可用区故障?}:::decision
B -->|是| C(EC2 实例恢复):::process
B -->|否| D{是否需要高性能?}:::decision
D -->|是| E(需权衡 EFS 性能影响):::process
D -->|否| F(自动扩展 + EFS 存储数据):::process
C --> G([结束]):::startend
E --> G
F --> G
这个流程图展示了在选择解决方案时的决策过程。首先判断是否能够容忍可用区故障,如果可以,那么选择 EC2 实例恢复方案;如果不能,则进一步考虑是否需要高性能,根据这个判断再选择合适的方案。
8. 总结
通过上述内容,我们对在 AWS 上构建高可用性架构有了更深入的了解。以下是关键要点总结:
-
常见问题
:
- 虚拟机可能因底层硬件或软件故障而失败。
- EBS 卷与单个可用区绑定,在可用区故障时可能导致数据无法访问。
- 使用自动扩展恢复时,默认情况下 IP 地址会发生变化。
-
解决方案
:
- 可以利用 CloudWatch 警报恢复故障的虚拟机,默认情况下 EBS 存储的数据和 IP 地址保持不变。
- 利用多个可用区可以实现从可用区故障中恢复。
- 部分 AWS 服务默认使用多个可用区,但虚拟机通常运行在单个可用区。
- 采用自动扩展可确保即使在可用区故障时,单个虚拟机也能始终运行。
- 对于数据恢复,可选择将数据存储在 RDS、EFS、S3 和 DynamoDB 等托管存储服务上。
-
灾难恢复指标
:
- 恢复时间目标(RTO)衡量系统从故障中恢复所需的时间。
- 恢复点目标(RPO)衡量故障导致的可接受数据丢失时间。
在实际应用中,需要根据业务需求和成本效益来选择合适的解决方案。例如,如果业务对数据丢失非常敏感,那么应该优先考虑 RPO 较低的方案;如果对系统恢复时间要求较高,则应关注 RTO 指标。同时,要注意清理不再使用的资源,以避免不必要的成本。通过合理规划和实施,我们可以在 AWS 上构建出既满足业务需求又具有高可用性的系统。
超级会员免费看
1035

被折叠的 条评论
为什么被折叠?



