使用资源编排 ROS 轻松部署高可用架构网站——以 WordPress 为例

介绍

WordPress是一款免费开源的网站内容管理系统(CMS),它可以帮助用户简单快捷地创建和管理自己的网站,包括博客、新闻网站、电子商务网站、社交网络等等。WordPress 有丰富的主题和插件库,使得用户可以轻松地为网站定制外观和功能。WordPress 的易用性和可扩展性使其成为世界上最受欢迎的网站建设工具之一。

资源编排服务(Resource Orchestration Service, ROS)是阿里云提供基于基础设施即代码(Infrastructure as Code, IaC) 理念的自动化部署服务,我们可以通过定义一个 JSON/YAML/Terraform 模板,轻松部署一套云上单点网站环境。比如部署一套 WordPress 环境,包括创建 VPC、ECS 等云资源,在 ECS 实例中安装 PHP、MySQL 和 WordPress 等。

架构说明

一个经典的高可用架构网站往往包含如下内容:

  • 使用负载均衡 SLB 实例,承接整个网站的访问请求,并根据一定策略路由至后端服务器(ECS 实例)
  • 一个绑定有 EIP 的 NAT 网关,可配置 SNAT 规则,使得后端服务器具备访问公网的能力
  • 基于弹性伸缩 ESS 在多个不同的可用区构建出的服务器集群。使用 ESS 的好处在于:
    • 能够根据一定策略(比如 CPU、内存利用率等)自动弹出或释放服务器
    • 支持多可用区、多种实例规格,最大程度确保可成功创建出服务器
  • 使用 ESS 生命周期挂钩机制可在弹出服务器后进行如程序配置等工作
  • 使用 RDS 的高可用区 MySQL 数据库,确保数据库的高可用

整体的架构图如下:

arch.jpg

在控制台上手动创建这些资源会非常耗时,无法轻易在其他地域部署相同的架构。而使用像 ROS 这样的 IaC 服务则可以高效地部署,且由于使用了标准化的模板,能够轻松在多个地域部署相同架构。

部署步骤

  1. 登录ROS 控制台-高可用架构网站部署页面

  2. 配置模板参数:选择负载均衡实例规格、两个不同的可用区、ECS 实例类型、RDS 实例类型、数据库密码
    1.jpg

  3. 点击【下一步】,然后【创建】。部署完成后,点击资源栈的输出,即可看到 WordPress 服务的地址。点击链接即可体验 WordPress 的功能。

image.png

image.png

部署原理

我们可以看到通过 ROS 可以非常快捷地部署阿里云上的各种云资源(比如 VPC、VSwitch、ECS 实例等)和应用程序(比如 WordPress)。如果想了解是如何做到的,那么可以阅读此章节。

1、编写 ROS 模板。在如下模板中定义了:

  • Resources:定义了 VPC、VSwitch、安全组&规则、ESS 相关资源(在 ScalingConfiguration 中配置了安装 WordPress 的命令)、RDS 相关资源、SLB 相关资源、NAT 网关& EIP。
  • Parameters:定义了常用的参数,比如可用区、ECS 实例类型。
  • Outputs:定义了自定义输出,比如 WordPress 服务的地址。
ROSTemplateFormatVersion: '2015-09-01'
Description:
  en: Build elastic and highly available services by CLB, ESS, NAT, EIP, RDS and OOS. This
    solution takes WordPress as an example. Two ECS instances are deployed in two
    zones through ESS, and are automatically bound to the default server group of
    CLB to provide external services. Among them, the ECS instance uses the RDS high-availability
    instance as the database, and has public network access through the NAT gateway
    bound to the EIP, and the CLB listens to the WordPress 80 port and perform health
    checks. In addition, scaling rules are configured in ESS, and when the average
    CPU value exceeds or falls below a certain threshold, the capacity will be automatically
    scaled.
  zh-cn: 
    基于CLB、ESS、NAT、EIP、RDS、OOS构建弹性高可用服务。本方案以WordPress为例,通过ESS在2个可用区分别部署2台ECS实例,并自动绑定到CLB的默认服务器组,从而对外提供服务。其中,ECS实例使用RDS高可用版实例作为数据库,通过绑定了EIP的NAT网关具备公网访问能力,CLB监听WordPress
    80端口并进行健康检查。此外,ESS中配置了伸缩规则,当CPU平均值超过或低于特定阈值时,会自动扩缩容。
Parameters:
  LoadBalancerSpec:
    Type: String
    Label:
      en: LoadBalancer Specifications
      zh-cn: 负载均衡实例规格
    AssociationProperty: ALIYUN::SLB::Instance::InstanceType
    Default: slb.s1.small
  ZoneId1:
    Type: String
    Label:
      en: VSwitch Availability Zone1
      zh-cn: 可用区1
    AssociationProperty: 'ALIYUN::ECS::Instance::ZoneId'
  ZoneId2:
    Type: String
    Label:
      en: VSwitch Availability Zone2
      zh-cn: 可用区2
    AssociationProperty: 'ALIYUN::ECS::Instance::ZoneId'
    AssociationPropertyMetadata:
      ExclusiveTo:
        - ZoneId1
  InstanceType1:
    Type: String
    Label:
      en: Instance Type
      zh-cn: 实例类型1
    AssociationProperty: 'ALIYUN::ECS::Instance::InstanceType'
    AssociationPropertyMetadata:
      InstanceChargeType: PostPaid
      SystemDiskCategory: cloud_essd
      ZoneId: ${ZoneId1}
  InstanceType2:
    Type: String
    Label:
      en: Instance Type
      zh-cn: 实例类型2
    AssociationProperty: 'ALIYUN::ECS::Instance::InstanceType'
    AssociationPropertyMetadata:
      InstanceChargeType: PostPaid
      SystemDiskCategory: cloud_essd
      ZoneId: ${ZoneId2}
  RdsInstanceClass:
    Type: String
    Label:
      en: RDS Instance Class
      zh-cn: RDS实例规格
    AssociationProperty: ALIYUN::RDS::Instance::InstanceType
    AssociationPropertyMetadata:
      ZoneId: ${ZoneId1}
      EngineVersion: "8.0"
      Engine: MySQL
      Category: HighAvailability
      DBInstanceStorageType: cloud_essd
  RdsDBPassword:
    Type: String
    Label:
      en: RDS Database Account Password
      zh-cn: RDS数据库账号密码
    Description:
      en: 'The password must be 8 to 32 characters in length and must contain at least
        three of the following types: uppercase letters, lowercase letter, digits,
        and special characters. Special characters include <span style="background:#E7E9EB;"><b>!@#$%^&*()_+-=</b></span>'
      zh-cn: 必须包含三种及以上类型:大写字母、小写字母、数字、特殊符号。长度为8~32位。特殊字符包括<span style="background:#E7E9EB;"><b>!@#$%^&*()_+-=</b></span>
    AllowedPattern: 
      ^(?=.*[a-zA-Z])(?=.*[a-z0-9])(?=.*[a-z!@#$%^&*()_+=-])(?=.*[A-Z0-9])(?=.*[A-Z!@#$%^&*()_+=-])(?=.*[0-9!@#$%^&*()_+=-])[a-zA-Z0-9!@#$%^&*()_+=-]{8,32}$
    NoEcho: true
  CommonName:
    Type: String
    Default: ha
Resources:
  Vpc:
    Type: ALIYUN::ECS::VPC
    Properties:
      VpcName:
        Fn::Sub: ${CommonName}-vpc
      CidrBlock: 192.168.0.0/16
  VSwitch1:
    Type: ALIYUN::ECS::VSwitch
    Properties:
      ZoneId:
        Ref: ZoneId1
      VpcId:
        Ref: Vpc
      VSwitchName:
        Fn::Sub: ${CommonName}-vsw-001
      CidrBlock: 192.168.1.0/24
  VSwitch2:
    Type: ALIYUN::ECS::VSwitch
    Properties:
      ZoneId:
        Ref: ZoneId2
      VpcId:
        Ref: Vpc
      VSwitchName:
        Fn::Sub: ${CommonName}-vsw-002
      CidrBlock: 192.168.2.0/24
  SecurityGroup:
    Type: ALIYUN::ECS::SecurityGroup
    Properties:
      VpcId:
        Ref: Vpc
      SecurityGroupName:
        Fn::Sub: ${CommonName}-sg
      SecurityGroupIngress:
      - PortRange: 80/80
        Priority: 1
        SourceCidrIp: 0.0.0.0/0
        IpProtocol: tcp
        NicType: internet
      SecurityGroupEgress:
      - PortRange: '-1/-1'
        Priority: 1
        IpProtocol: all
        DestCidrIp: 0.0.0.0/0
        NicType: internet
      - PortRange: '-1/-1'
        Priority: 1
        IpProtocol: all
        DestCidrIp: 0.0.0.0/0
        NicType: intranet
  ClbLoadBalancer:
    Type: ALIYUN::SLB::LoadBalancer
    Properties:
      LoadBalancerName:
        Fn::Sub: ${CommonName}-clb
      PayType: PayOnDemand
      AddressType: internet
      LoadBalancerSpec:
        Ref: LoadBalancerSpec
  ClbListener:
    Type: ALIYUN::SLB::Listener
    Properties:
      ListenerPort: 80
      Bandwidth: 10
      HealthCheck:
        HttpCode: http_2xx,http_3xx,http_4xx,http_5xx
        HealthCheckType: http
        UnhealthyThreshold: 3
        Timeout: 5
        HealthyThreshold: 3
        Port: 80
        URI: /
        Interval: 5
      LoadBalancerId:
        Ref: ClbLoadBalancer
      BackendServerPort: 80
      Protocol: http
  RdsInstance:
    Type: ALIYUN::RDS::DBInstance
    Properties:
      ZoneId:
        Ref: ZoneId1
      VpcId:
        Ref: Vpc
      VSwitchId:
        Ref: VSwitch1
      DBInstanceDescription:
        Fn::Sub: ${CommonName}-rds-instance
      Engine: MySQL
      DBInstanceStorage: 100
      EngineVersion: '8.0'
      Category: HighAvailability
      DBInstanceStorageType: cloud_essd
      DBInstanceClass:
        Ref: RdsInstanceClass
      SecurityIPList:
        Fn::Sub: ${VSwitch1.CidrBlock},${VSwitch2.CidrBlock}
      PayType: Postpaid
  RdsDatabase:
    Type: ALIYUN::RDS::Database
    Properties:
      CharacterSetName: utf8mb4
      DBInstanceId:
        Ref: RdsInstance
      DBDescription: wordpress
      DBName: wordpress
  RdsAccount:
    Type: ALIYUN::RDS::Account
    Properties:
      AccountName: wp_admin
      AccountType: Normal
      AccountDescription: wordpress admin
      AccountPassword:
        Ref: RdsDBPassword
      DBInstanceId:
        Ref: RdsInstance
  RdsAccountPrivilege:
    Type: ALIYUN::RDS::AccountPrivilege
    Properties:
      AccountPrivilege: ReadWrite
      DBInstanceId:
        Ref: RdsInstance
      DBName:
        Ref: RdsDatabase
      AccountName:
        Ref: RdsAccount
  NatGateway:
    Type: ALIYUN::VPC::NatGateway
    Properties:
      VpcId:
        Ref: Vpc
      VSwitchId:
        Ref: VSwitch1
      NatGatewayName:
        Fn::Sub: ${CommonName}-nat
      InternetChargeType: PayByLcu
      EipBindMode: NAT
  NatEip:
    Type: ALIYUN::VPC::EIP
    Properties:
      Name:
        Fn::Sub: ${CommonName}-nat-eip
      DeletionProtection: false
      Isp: BGP
      Bandwidth: 100
      InternetChargeType: PayByTraffic
  NatEipAssociation:
    Type: ALIYUN::VPC::EIPAssociation
    Properties:
      InstanceId:
        Ref: NatGateway
      AllocationId:
        Ref: NatEip
  SnatEntry:
    Type: ALIYUN::VPC::SnatEntry
    Properties:
      SnatEntryName: public-network-access-in-vpc
      SnatTableId:
        Fn::GetAtt:
        - NatGateway
        - SNatTableId
      SnatIp:
        Fn::GetAtt:
        - NatEipAssociation
        - EipAddress
      SourceCIDR: 0.0.0.0/0
  EssScalingGroup:
    Type: ALIYUN::ESS::ScalingGroup
    Properties:
      VSwitchIds:
      - Ref: VSwitch1
      - Ref: VSwitch2
      ScalingGroupName:
        Fn::Sub: ${CommonName}-asg
      RemovalPolicys:
      - NewestInstance
      MinSize: 2
      MaxSize: 10
      DefaultCooldown: 300
      MultiAZPolicy: COMPOSABLE
      AzBalance: true
      LoadBalancerIds:
      - Ref: ClbLoadBalancer
    DependsOn: SecurityGroup
  EssScalingConfiguration:
    Type: ALIYUN::ESS::ScalingConfiguration
    Properties:
      SecurityGroupId:
        Ref: SecurityGroup
      ImageId: centos_7_9_x64_20G_alibase_20220727.vhd
      ScalingConfigurationName:
        Fn::Sub: ${CommonName}-asc
      ScalingGroupId:
        Ref: EssScalingGroup
      InstanceTypes:
      - Ref: InstanceType1
      - Ref: InstanceType2
      SystemDiskCategory: cloud_essd
      SystemDiskSize: 200
      InstanceName:
        Fn::Sub: ${CommonName}-wordpress
      UserData:
        Fn::Sub: |-
          #!/bin/bash
          script=/root/setup-wordpress.sh
          cat<<\EOF>$script
          #!/bin/bash
          if [ ! -f .ros.provision ]; then
            echo "Name: ha-service" > .ros.provision
          fi

          name=$(grep "^Name:" .ros.provision | awk -F':' '{print $2}' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
          if [[ "$name" != "ha-service" ]]; then
            echo "ha-service installed, skip"
            exit 0
          fi

          if ! grep -q "^Step1: Install Environment$" .ros.provision; then
            echo "#########################"
            echo "# Install Environment"
            echo "#########################"
            yum -y install httpd httpd-manual mod_ssl mod_perl mod_auth_mysql sysbench
            systemctl start httpd
            systemctl enable httpd
            systemctl status httpd

            yum install -y yum-utils epel-release http://rpms.remirepo.net/enterprise/remi-release-7.rpm 
            yum-config-manager --enable remi-php82
            yum -y install php php-opcache php-mysqlnd php-pdo php-gd php-ldap php-odbc php-pear php-xml php-xmlrpc php-mbstring php-soap
            echo "<?php phpinfo(); ?>" > /var/www/html/phpinfo.php
            systemctl restart httpd
            echo "Step1: Install Environment" >> .ros.provision
          else
            echo "#########################"
            echo "# Environment has been installed"
            echo "#########################"
          fi

          if ! grep -q "^Step2: Install and Config WordPress$" .ros.provision; then
            echo "################################"
            echo "# Install and Config WordPress"
            echo "################################"
            wget https://ros-template-resources.oss-cn-beijing.aliyuncs.com/WordPress/wordpress-6.3.1-zh_CN.tar.gz
            tar -xvf wordpress-6.3.1-zh_CN.tar.gz -C /var/www/html
            mv /var/www/html/wordpress/* /var/www/html
            chown -R apache:apache /var/www/html/wordpress
            chmod -R 755 /var/www/html/wordpress
            mv /var/www/html/wp-config-sample.php /var/www/html/wp-config.php
            sed -i 's/localhost/${RdsInstance.InnerConnectionString}/' /var/www/html/wp-config.php
            sed -i 's/username_here/${RdsAccount}/' /var/www/html/wp-config.php
            sed -i 's/password_here/${RdsDBPassword}/' /var/www/html/wp-config.php
            sed -i 's/database_name_here/${RdsDatabase}/' /var/www/html/wp-config.php
            systemctl restart httpd
            echo "Step2: Install and Config WordPress" >> .ros.provision
          else
            echo "#########################"
            echo "# WordPress has been installed and configed"
            echo "#########################"
          fi
          EOF
  EssScalingGroupEnable:
    Type: ALIYUN::ESS::ScalingGroupEnable
    Properties:
      ScalingGroupId:
        Ref: EssScalingGroup
      ScalingConfigurationId:
        Ref: EssScalingConfiguration
  ESSLifecycleOOSRunCommandRole:
    Type: ALIYUN::RAM::Role
    Properties:
      RoleName: ESSLifecycleOOSRunCommandRole
      IgnoreExisting: true
      AssumeRolePolicyDocument:
        Statement:
        - Action: sts:AssumeRole
          Effect: Allow
          Principal:
            Service:
            - oos.aliyuncs.com
        Version: '1'
      Policies:
      - PolicyName: ESSLifecycleOOSRunCommandRolePolicy
        PolicyDocument:
          Statement:
          - Action:
            - ecs:DescribeInvocationResults
            - ecs:DescribeInvocations
            - ecs:RunCommand
            Resource:
            - '*'
            Effect: Allow
          - Action:
            - ess:CompleteLifecycleAction
            Resource:
            - '*'
            Effect: Allow
          Version: '1'
  ESSLifecycleHook:
    Type: ALIYUN::ESS::LifecycleHook
    Properties:
      LifecycleHookName:
        Fn::Sub: ${CommonName}-ash-scaleout
      ScalingGroupId:
        Ref: EssScalingGroup
      LifecycleTransition: SCALE_OUT
      NotificationArn:
        Fn::Sub: acs:ess:${ALIYUN::Region}:${ALIYUN::TenantId}:oos/ACS-ESS-LifeCycleRunCommand
      NotificationMetadata:
        Fn::Sub: |-
          {
            "commandContent": "bash -x /root/setup-wordpress.sh",
            "commandType": "RunShellScript",
            "timeout": 1200,
            "OOSAssumeRole": "${ESSLifecycleOOSRunCommandRole.RoleName}",
            "regionId": "${!regionId}",
            "instanceIds": "${!instanceIds}",
            "lifecycleHookId": "${!lifecycleHookId}",
            "rateControl": "{\"Mode\":\"Concurrency\",\"MaxErrors\":0,\"Concurrency\":10}",
            "lifecycleActionToken": "${!lifecycleActionToken}"
          }
    DependsOn: SnatEntry
  EssScalingRule:
    Type: ALIYUN::ESS::ScalingRule
    Properties:
      ScalingRuleName:
        Fn::Sub: ${CommonName}-asr-scaleout
      ScalingGroupId:
        Ref: EssScalingGroup
      ScalingRuleType: TargetTrackingScalingRule
      AdjustmentType: QuantityChangeInCapacity
      AdjustmentValue: 1
      MetricName: CpuUtilization
      TargetValue: 80
      ScaleOutEvaluationCount: 3
      ScaleInEvaluationCount: 3
      EstimatedInstanceWarmup: 0
Outputs:
  Endpoint:
    Description:
      zh-cn: 公网IP地址
      en: Public IP Addresses
    Value:
      Fn::Sub:
      - http://${ServerAddress}
      - ServerAddress:
          Fn::GetAtt:
          - ClbLoadBalancer
          - IpAddress
Metadata:
  ALIYUN::ROS::Interface:
    ParameterGroups:
    - Parameters:
      - LoadBalancerSpec
      Label:
        default:
          en: CLB Configuration
          zh-cn: 负载均衡配置
    - Parameters:
      - ZoneId1
      - ZoneId2
      Label:
        default:
          en: Availability Zone
          zh-cn: 可用区配置
    - Parameters:
      - InstanceType1
      - InstanceType2
      Label:
        default:
          en: Instance Configuration
          zh-cn: 实例配置
    - Parameters:
      - RdsInstanceClass
      - RdsDBPassword
      Label:
        default:
          en: RDS Configuration
          zh-cn: RDS配置
    TemplateTags:
    - 'acs:technical-solution:high-availability-architecture:弹性高可用服务'
    Hidden:
    - CommonName

2、在 ROS 控制台中使用此模板创建资源栈。ROS 会自动解析出模板中资源的依赖关系,按照资源依赖顺序创建云资源。如果资源间没有依赖,则会并发创建,从而提升部署效率。ROS 会把这次创建的所有资源存放到一个“资源栈”中,后续可以方便地管理这组资源集合。比如:

  • 将新模板应用到这个“资源栈”中,从而更新里面的资源。
  • 删除这个“资源栈”,从而把所有的资源删掉。

总结

基于 IaC 的理念,通过定义一个模板,使用 ROS 进行自动化部署,可以非常高效快捷地部署任意云资源和应用(比如 高可用架构的 WordPress 服务)。相比于手动部署或者通过 API、SDK 的部署方式,有着高效、稳定等诸多优势,也是服务上云的最佳实践。

posted @ 2024-07-11 15:18  阿里云CloudOps  阅读(8)  评论(0编辑  收藏  举报