34、实现高可用性:可用区、自动扩展和 CloudWatch

实现高可用性:可用区、自动扩展和 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: {}
  1. 在每个可用区创建 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
  1. 调整用户数据以挂载 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 上构建出既满足业务需求又具有高可用性的系统。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值