搭建高可用的微信小程序服务(Alibaba Cloud Linux 3)

本文介绍如何在阿里云云服务器ECS上基于Alibaba Cloud Linux 3操作系统搭建高可用的微信小程序服务端,并在本地开发一个名为ECS小助手的简单微信小程序。通过远程调用部署在ECS上的服务端,实现在小程序中输入框输入ECS实例ID查询实例详细信息的功能。

步骤一:准备环境和资源

部署高可用的小程序服务需要2ECS实例(Alibaba Cloud Linux3.2104 LTS 64位)、1CLB实例。具体操作,请参见ECS实例创建方式介绍创建和管理CLB实例

本教程配置:

  • 云服务器ECS

    • 地域:华北2(北京)

    • 操作系统:Alibaba Cloud Linux3.2104 LTS 64

    • 实例规格配置:12 GB

    • 实例数量:2(ECS01、ECS02)

    • 其他参数:保持默认值或按需选择

  • 传统型负载均衡CLB

    • 地域与可用区:选择与ECS实例相同的地域,本教程选择华北2(北京)

    • 实例规格配置:按需创建

    • 实例计费方式:按使用量计费

    • 实例类型:公网

    • 实例数量:1

    • IP 版本:IPv4

步骤二:配置资源并搭建服务

一键配置

通过ROS一键部署

准备好资源后,您可以通过一键配置快速完成资源配置或应用搭建。一键配置基于阿里云资源编排服务ROS(Resource Orchestration Service)实现,旨在帮助开发者通过IaC(Infrastructure as Code)的方式体验资源的自动化配置。如需查看软件版本、安装命令等配置的具体信息,可查看教程的手动配置版。模板完成的内容包括:

  • ECS实例配置安全组

  • 创建角色并绑定到ECS实例

  • 安装Nginx服务并写入配置

  • 安装uWSGI Server并写入配置

  • 安装Python环境并写入代码

操作步骤

  1. 打开一键配置模板链接前往ROS控制台,单击上一步:选择模板,在模板内容区域展示了YAML文件的详细信息。

  2. ROS控制台默认处于您上一次访问控制台时的地域,请根据您创建的资源所在地域修改地域,例如选择华北2(北京)地域,保持页面所有选项不变,单击下一步:配置参数

  3. 配置参数页面,修改资源栈名称,选择您申请免费试用时创建的ECS实例,设置要创建的用于ECS实例扮演的角色名称,选择创建的CLB实例(原SLB实例)。填写完所有必选信息并确认后单击下一步:检查并确认

  4. 确认各项配置信息无误后,单击创建

    资源栈信息页签下的基本信息区域中,查看状态显示为创建成功时表示一键配置完成。

    image

通过Terraform一键部署

Terraform是一款IaC工具(Infrastructure as Code),能够帮助开发人员和运维团队自动化基础设施的创建、部署和管理。您可以通过编写简洁的代码来定义和配置云端基础设施,而不必手动操作和配置。

操作步骤

  1. 点击一键运行进入Terraform Explorer,自动加载如下的Terraform代码。

    provider "alicloud" {
      region = var.region
    }
    
    variable "region" {
      description = "Region where resources will be created"
      default     = "cn-beijing"
    }
    
    variable "common_name" {
      default     = "deploy-java-web-by-terraform"
      description = "Common name for resources"
    }
    
    
    variable "system_disk_category" {
      default     = "cloud_essd"
      description = "The category of the system disk."
    }
    
    variable "instance_password" {
      default     = "Test@123456"
      description = "Server login password"
    }
    
    variable "image_id" {
      default     = "aliyun_3_x64_20G_alibase_20240528.vhd"
      description = "Please choose the Alibaba Cloud Linux 3 Image ID for the instance"
    }
    
    variable "instance_type" {
      description = "Instance type"
      default     = "ecs.e-c1m2.large"
    }
    
    variable "instance_count" {
      description = "The number of ECS instances to create."
      default     = "2"
    }
    
    variable "ecs_ram_role_name" {
      description = "English letters, numbers, or ' - ' are allowed. The number of characters should be less than or equal to 64."
      default     = "EcsRoleForMiniProgramServer"
    }
    
    data "alicloud_zones" "default" {
      available_disk_category     = var.system_disk_category
      available_resource_creation = "VSwitch"
      available_instance_type     = var.instance_type
    }
    
    resource "alicloud_ram_role" "role" {
      name        = var.ecs_ram_role_name
      document    = <<EOF
      {
        "Statement": [
          {
            "Action": "sts:AssumeRole",
            "Effect": "Allow",
            "Principal": {
              "Service": [
                "ecs.aliyuncs.com"
              ]
            }
          }
        ],
        "Version": "1"
      }
      EOF
      description = "Ecs ram role."
      force       = true
    }
    
    resource "alicloud_ram_policy" "policy" {
      policy_name     = "${var.ecs_ram_role_name}Policy"
      policy_document = <<EOF
      {
        "Statement": [
          {
            "Action":  [
              "ecs:DescribeInstances",
              "ecs:DescribeInstanceStatus"
            ],
            "Effect":  "Allow",
            "Resource": ["*"]
          }
        ],
          "Version": "1"
      }
      EOF
      description     = "this is a policy test"
      force           = true
    }
    
    resource "alicloud_ram_role_policy_attachment" "attach" {
      policy_name = alicloud_ram_policy.policy.policy_name
      policy_type = "Custom"
      role_name   = alicloud_ram_role.role.name
    }
    
    resource "alicloud_vpc" "vpc" {
      vpc_name   = "${var.common_name}-vpc"
      cidr_block = "192.168.0.0/16"
    }
    
    resource "alicloud_vswitch" "vswitch" {
      vpc_id       = alicloud_vpc.vpc.id
      vswitch_name = "${var.common_name}-vsw"
      cidr_block   = "192.168.0.0/24"
      zone_id      = data.alicloud_zones.default.zones.0.id
    }
    
    resource "alicloud_security_group" "sg" {
      security_group_name = "${var.common_name}-sg"
      vpc_id              = alicloud_vpc.vpc.id
      description         = "Security group for ECS instance"
    }
    
    resource "alicloud_security_group_rule" "allow_ssh" {
      type              = "ingress"
      ip_protocol       = "tcp"
      nic_type          = "intranet"
      policy            = "accept"
      port_range        = "22/22"
      priority          = 1
      security_group_id = alicloud_security_group.sg.id
      cidr_ip           = "0.0.0.0/0"
    }
    
    resource "alicloud_security_group_rule" "allow_80" {
      type              = "ingress"
      ip_protocol       = "tcp"
      nic_type          = "intranet"
      policy            = "accept"
      port_range        = "80/80"
      priority          = 1
      security_group_id = alicloud_security_group.sg.id
      cidr_ip           = "0.0.0.0/0"
    }
    
    resource "alicloud_ram_role_attachment" "attach" {
      role_name    = alicloud_ram_role.role.name
      instance_ids = alicloud_instance.ecs_instance.*.id
    }
    
    resource "alicloud_instance" "ecs_instance" {
      count                      = var.instance_count
      instance_name              = "${var.common_name}-ecs"
      image_id                   = var.image_id
      instance_type              = var.instance_type
      security_groups            = alicloud_security_group.sg.*.id
      vswitch_id                 = alicloud_vswitch.vswitch.id
      password                   = var.instance_password
      internet_max_bandwidth_out = 100
      system_disk_category       = var.system_disk_category
    }
    
    resource "alicloud_eip_address" "eip" {
      description  = "${var.common_name}-eip"
      address_name = "${var.common_name}-eip"
      netmode      = "public"
      bandwidth    = "5"
    }
    
    resource "alicloud_eip_association" "clb_ip" {
      allocation_id = alicloud_eip_address.eip.id
      instance_id   = alicloud_slb_load_balancer.instance.id
    }
    
    resource "alicloud_slb_load_balancer" "instance" {
      load_balancer_name   = "${var.common_name}-clb"
      load_balancer_spec   = "slb.s2.small"
      internet_charge_type = "PayByTraffic"
      address_type         = "intranet"
      vswitch_id           = alicloud_vswitch.vswitch.id
    }
    
    resource "alicloud_slb_listener" "listener" {
      load_balancer_id          = alicloud_slb_load_balancer.instance.id
      backend_port              = 80
      frontend_port             = 80
      protocol                  = "http"
      bandwidth                 = -1
      health_check              = "on"
      health_check_uri          = "/"
      health_check_connect_port = 80
      healthy_threshold         = 3
      unhealthy_threshold       = 3
      health_check_timeout      = 5
      health_check_interval     = 2
      health_check_http_code    = "http_2xx,http_3xx"
    }
    
    resource "alicloud_slb_attachment" "default" {
      load_balancer_id = alicloud_slb_load_balancer.instance.id
      instance_ids     = alicloud_instance.ecs_instance.*.id
      weight           = 80
    }
    
    resource "alicloud_ecs_command" "deploy" {
      name            = "DeployMiniProgramServer"
      type            = "RunShellScript"
      command_content = base64encode(local.command_content)
      timeout         = 3600
      working_dir     = "/root"
    }
    
    resource "alicloud_ecs_invocation" "invocation" {
      instance_id = alicloud_instance.ecs_instance.*.id
      command_id  = alicloud_ecs_command.deploy.id
      timeouts {
        create = "10m"
      }
    }
    
    locals {
      command_content = <<EOF
    #!/bin/bash
    if [ ! -f .tf.provision ]; then
      echo "Name: 搭建小程序" > .tf.provision
    fi
    
    name=$(grep "^Name:" .tf.provision | awk -F':' '{print $2}' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
    
    if [ "$name" != "搭建小程序" ]; then
      echo "当前实例已使用过\"$name\"教程的一键配置,不能再使用本教程的一键配置"
      exit 0
    fi
    
    if ! grep -q "^Step1: Install Python$" .tf.provision; then
      echo "#########################"
      echo "# Install Python"
      echo "#########################"
      mkdir /data && touch /data/GetServerInfo.py
      cat > /data/GetServerInfo.py << EOF2
    # -*- coding: utf-8 -*-
    from flask import Flask, jsonify, request
    from alibabacloud_credentials.client import Client as CredClient
    from alibabacloud_credentials.models import Config as CredConfig
    from alibabacloud_ecs20140526 import models as ecs_20140526_models
    from alibabacloud_ecs20140526.client import Client as EcsClient
    from alibabacloud_tea_openapi.models import Config
    
    app = Flask(__name__)
    
    ecs_ram_role = '${var.ecs_ram_role_name}'
    region = '${var.region}'
    
    
    # 用于健康检查
    @app.route('/', methods=['HEAD', 'GET'])
    def index():
        return "ok"
    
    
    # 在app.route装饰器中声明响应的URL和请求方法 Declare the URL and request method of the response in the app.route decorator.
    @app.route('/ecs/getServerInfo', methods=['GET'])
    def get_server_info():
        cred_config = CredConfig(
            type='ecs_ram_role',
            role_name=ecs_ram_role,
        )
        cred_client = CredClient(cred_config)
        ecs_config = Config(credential=cred_client, region_id=region)
        ecs_client = EcsClient(ecs_config)
        # GET方式获取请求参数 Get request parameters.
        instance_id = request.args.get("instanceId")
        if instance_id is None:
            return "Invalid Parameter"
        instance_ids = [instance_id]
        # 查询实例信息 Query instance information.
        describe_instances_request = ecs_20140526_models.DescribeInstancesRequest()
        describe_instances_request.region_id = region
        describe_instances_request.instance_ids = str(instance_ids)
    
        describe_instances_response = ecs_client.describe_instances(describe_instances_request)
        # 返回数据为bytes类型,需要将bytes类型转换为str然后反序列化为json对象 The returned data is of type bytes, which needs to be converted to str and then deserialized into a json object.
        if len(describe_instances_response.to_map()['body']['Instances']['Instance']) == 0:
            return jsonify({})
    
        instance_info = describe_instances_response.to_map()['body']['Instances']['Instance'][0]
    
        # 查询实例状态 Query instance status.
        describe_instance_status_request = ecs_20140526_models.DescribeInstanceStatusRequest()
        describe_instance_status_request.region_id = region
        describe_instance_status_request.instance_id = instance_ids
        describe_instance_status_response = ecs_client.describe_instance_status(describe_instance_status_request)
        instance_status = describe_instance_status_response.to_map()['body']['InstanceStatuses']['InstanceStatus'][0][
            'Status']
    
        # 封装结果 result
        result = {
            # cpu数
            'Cpu': instance_info['Cpu'],
            # 内存大小
            'Memory': instance_info['Memory'],
            # 操作系统名称
            'OSName': instance_info['OSName'],
            # 实例规格
            'InstanceType': instance_info['InstanceType'],
            # 实例公网IP地址
            'IpAddress': instance_info['PublicIpAddress']['IpAddress'][0],
            # 公网出带宽最大值
            'InternetMaxBandwidthOut': instance_info['InternetMaxBandwidthOut'],
            # 实例状态
            'instance_status': instance_status
        }
        return jsonify(result)
    
    
    if __name__ == "__main__":
        app.run()
    
    EOF2
      touch /data/requirements.txt
      cat > /data/requirements.txt << EOF2
    wheel
    alibabacloud_ecs20140526
    Flask==2.0.3
    
    EOF2
      pip3 install --upgrade pip && pip3 install -r /data/requirements.txt
      echo "Step1: Install Python" >> .tf.provision
    else
      echo "#########################"
      echo "# Python has been installed"
      echo "#########################"
    fi
    
    if ! grep -q "^Step2: Install uWSGI Server$" .tf.provision; then
      echo "#########################"
      echo "# Install uWSGI Server"
      echo "#########################"
      pip3 install uwsgi
      touch /data/uwsgi.ini
      cat > /data/uwsgi.ini << EOF2
    [uwsgi]
    
    #uwsgi启动时所使用的地址和端口 The address and port used when uwsgi starts.
    
    socket=127.0.0.1:5000
    
    #指向网站目录 site directory.
    
    chdir=/data
    
    
    #python启动程序文件 python start file.
    
    wsgi-file=GetServerInfo.py
    
    
    #python程序内用以启动的application变量名 The application variable name used to start in the python program.
    
    callable=app
    
    
    #处理器数 Processors.
    
    processes=1
    
    
    #线程数 Threads.
    
    threads=2
    
    
    #状态检测地址 status detection address.
    
    stats=127.0.0.1:9191
    
    
    #保存启动之后主进程的pid Save the pid file of the main process after startup.
    
    pidfile=/data/uwsgi.pid
    
    
    #设置uwsgi后台运行,uwsgi.log保存日志信息 自动生成 save log info file.
    
    daemonize=/data/uwsgi.log
    
    EOF2
      uwsgi /data/uwsgi.ini
      echo "Step2: Install uWSGI Server" >> .tf.provision
    else
      echo "#########################"
      echo "# uWSGI Server has been installed"
      echo "#########################"
    fi
    
    if ! grep -q "^Step3: Install Nginx$" .tf.provision; then
      echo "#########################"
      echo "# Install Nginx"
      echo "#########################"
      yum install -y nginx
      touch /etc/nginx/conf.d/app.conf
      cat > /etc/nginx/conf.d/app.conf << EOF2
    server {
        listen 80 default_server;
        
        server_name app.example.com;
    
        root /var/www/html;
    
        # Add index.php to the list if you are using PHP
        index index.html index.htm index.nginx-debian.html;
    
        location / {
            # 转发端口 forwarding address.
            uwsgi_pass  127.0.0.1:5000;
            include uwsgi_params;
        }
    }
    
    EOF2
      systemctl start nginx
      echo "Step3: Install Nginx" >> .tf.provision
    else
      echo "#########################"
      echo "# Nginx has been installed"
      echo "#########################"
    fi
    EOF
    }
    
    output "public_ip" {
      value = alicloud_eip_address.eip.ip_address
    }
  2. 根据实际情况修改参数,默认情况下,将会在cn-beijing创建1VPC实例、2台操作系统为Alibaba Cloud Linux 3的按量付费ECS实例、1个按量付费CLB实例、1个按量付费EIP实例和1RAM角色。关于各个实例计费说明,请分别查阅以下文档:

    1. 关于ECS的计费说明,请参见ECS计费概述

    2. 关于CLB的计费说明,请参见CLB计费概述

    3. 关于EIP的计费说明,请参见计费概述

  3. 单击发起调试,预览待创建资源详细信息,若资源信息无误,单击预览并执行开始创建资源。当任务的执行详情状态为执行成功时,说明已部署完成。

  4. 验证服务部署情况。在浏览器中输入以下内容,其中<clb_public_ip>应替换为Terraform输出的IP地址。

    http://<clb_public_ip>/ecs/getServerInfo

    若命令执行结果是Invalid Parameter,则表示服务部署成功。

手动配置

您可参考以下步骤,手动配置ECSCLB实例:

  1. 步骤一:创建RAM角色并绑定到ECS实例

  2. 步骤二:登录云服务器

  3. 步骤三:安装Nginx服务

  4. 步骤四:开发后端服务

  5. 步骤五:安装uWSGI Server

  6. 步骤六:配置Nginx并重启

  7. 步骤七:配置监听

步骤一:创建RAM角色并绑定到ECS实例

创建好实例后,您需要创建RAM角色并关联到ECS实例,用于后端服务调用SDK。

在实例内部基于STS(Security Token Service)临时凭证访问云产品的API,临时凭证将周期性更新。既可以保证云账号AccessKey的安全,还可以借助访问控制RAM实现精细化控制和权限管理。

  1. 创建实例RAM角色。

    1. 使用RAM管理员登录RAM控制台

    2. 在左侧导航栏,选择身份管理 > 角色

    3. 角色页面,单击创建角色

      image

    4. 创建角色面板,选择可信实体类型选择为阿里云服务,然后单击下一步

      阿里云服务用于授权ECS实例访问或管理您的云资源。RAM角色选择阿里云服务类型后,支持授予给ECS实例。创建RAM角色

    5. 选择角色类型为普通服务角色

    6. 输入角色名称备注

    7. 选择受信服务为云服务器

    8. 单击完成

    9. 单击关闭

  2. 创建自定义策略。在下面为RAM角色授予权限时,选择这里创建的策略。

    1. 使用阿里云账号登录RAM控制台

    2. 在左侧导航栏,选择权限管理 > 权限策略

    3. 权限策略页面,单击创建权限策略

    4. 创建权限策略页面,单击脚本编辑页签。

    5. 输入权限策略内容,然后单击确定

      {
          "Version": "1",
          "Statement": [
              {
                  "Effect": "Allow",
                  "Action": [
                      "ecs:DescribeInstances",
                      "ecs:DescribeInstanceStatus"
                  ],
                  "Resource": "*"
              }
          ]
      }
    6. 输入权限策略名称(例如EcsRamRolePolicyTest)和备注

    7. 单击确定

  3. RAM角色授予权限。

    1. 使用RAM管理员登录RAM控制台

    2. (可选)如果您不使用系统权限,可以创建一个自定义策略。

      具体操作,请参见创建自定义权限策略

    3. 在左侧导航栏,选择身份管理 > 角色

    4. 权限管理页签,单击精确授权

      image

    5. 添加权限面板,选择权限策略类型为自定义策略,然后输入刚刚创建的权限策略名称。

    6. 单击确定

    7. 单击关闭

  4. 为实例授予RAM角色。

    1. 登录ECS管理控制台

    2. 在左侧导航栏,选择实例与镜像 > 实例

    3. 在顶部菜单栏左上角处,选择ECS实例所在地域。例如华北2(北京)

    4. 找到待操作的ECS实例,选择image.png > 实例设置 > 授予/收回RAM角色

    5. 在弹出的对话框中,选择已创建好的实例RAM角色,单击确定完成授予。

步骤二:登录云服务器

开通免费试用ECS服务器后,系统会创建一个ECS实例(对应一台云服务器),使用ECS实例部署应用或搭建环境前,需设置该ECS实例密码后才能登录实例。

  1. 登录ECS控制台

  2. 在左侧导航栏,选择实例与镜像 > 实例

  3. 在顶部菜单栏左上角处,选择和试用实例相同的地域。

  4. 设置ECS实例的登录密码。

    1. ECS实例对应操作列,选择image > 实例属性 > 重置实例密码,按照界面提示设置ECS实例的登录密码,然后单击确认修改

      重置密码

      重要

      实例创建完成大约3~5分钟后,才支持重置实例密码,如不可重置请您耐心等待后重试。

    2. (可选)在弹出的对话框中,单击立即重启实例,使密码生效。

      如果重置密码的方式选择在线重置密码,请跳过该步骤。

  5. 单击试用实例的ID,选择安全组页签,单击安全组操作列的管理规则,在入方向添加需要放行的端口。本教程中,在安全组入方向放行SSH默认22端口和Apache默认80端口。

    image

  6. 远程连接ECS实例。

    1. 返回实例页面,单击该实例对应操作列下的远程连接

    2. 在弹出的连接与命令对话框中,单击通过Workbench远程连接对应的立即登录。

    3. 在弹出的登录实例对话框中,输入登录信息。

  7. 重复执行以上操作,登录ECS02实例。

步骤三:安装Nginx服务

  1. 执行命令安装Nginx。

    sudo yum update && sudo yum -y install nginx
  2. 启动Nginx。

    sudo systemctl start nginx
  3. 执行以下命令,查看Nginx服务的运行状态。

    如果回显信息显示Active:active(running)时,表示Nginx已启动。

    systemctl status nginx
  4. 重复执行以上操作,在ECS02实例中安装Nginx服务。

步骤四:开发后端服务

  1. 创建服务目录。

    sudo mkdir /data && cd /data
  2. 创建并编辑Python服务依赖文件。

    sudo vim requirements.txt
  3. 进入Vim编辑器后,按i键进入编辑模式,粘贴以下内容。

    aliyun_python_sdk_core==2.13.36
    aliyun_python_sdk_ecs==4.24.62
    Flask==2.0.3
  4. 粘贴后,按Esc键,输入:x保存并退出编辑。

  5. 执行如下命令安装依赖。

    sudo pip3 install --upgrade pip && sudo pip3 install -r requirements.txt
  6. 创建并编辑Python服务代码文件。

    sudo vim get_server_info.py
  7. 进入Vim编辑器后,按i键进入编辑模式,粘贴以下内容。

    说明

    代码中metaUrlEcsRamRoleTest需要和前面创建的角色名称保持一致。

    代码中region需要与您真实创建实例地域保持一致。

    # -*- coding: utf-8 -*-
    from flask import Flask, jsonify, request
    from aliyunsdkcore.client import AcsClient
    from aliyunsdkcore.auth import credentials
    import requests
    import json
    from aliyunsdkecs.request.v20140526 import DescribeInstancesRequest, DescribeInstanceStatusRequest
    
    app = Flask(__name__)
    
    metaUrl = 'http://100.100.100.200/latest/meta-data/ram/security-credentials/EcsRamRoleTest'
    region = 'cn-beijing'
    
    # 获取临时身份凭证
    def getStsToken():
    	tokenResponse = requests.get(metaUrl)
    	return tokenResponse.json()
    
    # 用于健康检查
    @app.route('/', methods=['HEAD', 'GET'])
    def index():
      return "ok"
    
    # 在app.route装饰器中声明响应的URL和请求方法
    @app.route('/ecs/getServerInfo', methods=['GET'])
    def getServerInfo():
        tokenResult = getStsToken()
        accessKeyId = tokenResult['AccessKeyId']
        accessSecret = tokenResult['AccessKeySecret']
        securityToken = tokenResult['SecurityToken']
        credential = credentials.StsTokenCredential(accessKeyId, accessSecret, securityToken)
        client = AcsClient(credential=credential, region_id=region)
    
        # GET方式获取请求参数
        instanceId = request.args.get("instanceId")
        if instanceId is None:    
            return "Invalid Parameter"
        # 查询实例信息
        describeInstancesRequest = DescribeInstancesRequest.DescribeInstancesRequest()
        describeInstancesRequest.set_InstanceIds([instanceId])
        describeInstancesResponse = client.do_action_with_exception(describeInstancesRequest)
        # 返回数据为bytes类型,需要将bytes类型转换为str然后反序列化为json对象
        describeInstancesResponse = json.loads(str(describeInstancesResponse, 'utf-8'))
        print(describeInstancesResponse)
        if len(describeInstancesResponse['Instances']['Instance']) == 0:
            return jsonify({})
    
        instanceInfo = describeInstancesResponse['Instances']['Instance'][0]
    
        # 查询实例状态
        describeInstanceStatusRequest = DescribeInstanceStatusRequest.DescribeInstanceStatusRequest()
        describeInstanceStatusRequest.set_InstanceIds([instanceId])
        describeInstanceStatusResponse = client.do_action_with_exception(describeInstanceStatusRequest)
        describeInstanceStatusResponse = json.loads(str(describeInstanceStatusResponse, 'utf-8'))
        instanceStatus = describeInstanceStatusResponse['InstanceStatuses']['InstanceStatus'][0]['Status']
    
        # 封装结果
        result = {
            # cpu数
            'Cpu': instanceInfo['Cpu'],
            # 内存大小
            'Memory': instanceInfo['Memory'],
            # 操作系统名称
            'OSName': instanceInfo['OSName'],
            # 实例规格
            'InstanceType': instanceInfo['InstanceType'],
            # 实例公网IP地址
            'IpAddress': instanceInfo['PublicIpAddress']['IpAddress'][0],
            # 公网出带宽最大值
            'InternetMaxBandwidthOut': instanceInfo['InternetMaxBandwidthOut'],
            # 实例状态
            'instanceStatus': instanceStatus
        }
        return jsonify(result)
    
    
    if __name__ == "__main__":
        app.run()
  8. 粘贴后,按Esc键,输入:x保存并退出编辑。

  9. 重复执行以上操作,在ECS02实例中开发后端服务。

步骤五:安装uWSGI Server

写完服务端代码后,您需要安装并使用uWSGI来启动Flask服务。

  1. 执行命令安装uWSGI。

    sudo pip3 install uwsgi
  2. 新建uwsgi配置文件。

    cd /data && sudo vim uwsgi.ini
  3. 进入Vim编辑器后,按i键进入编辑模式。

    [uwsgi]
    #uwsgi启动时所使用的地址和端口
    socket=127.0.0.1:5000
    #指向网站目录
    chdir=/data
    
    #python启动程序文件
    wsgi-file=get_server_info.py
    #python程序内用以启动的application变量名
    callable=app
    
    #处理器数
    processes=1
    
    #线程数
    threads=2
    
    #状态检测地址
    stats=127.0.0.1:9191
    
    #保存启动之后主进程的pid
    pidfile=uwsgi.pid
    
    #设置uwsgi后台运行,uwsgi.log保存日志信息 自动生成
    daemonize=uwsgi.log
  4. 粘贴后,按Esc键,输入:x保存并退出编辑。

  5. 运行which uwsgi命令,查找并确认uwsgi可执行文件的完整路径。

    系统返回信息如下:

    /usr/local/bin/uwsgi
  6. 运行uwsgi server。

    sudo /usr/local/bin/uwsgi uwsgi.ini
  7. 执行以下命令查看uwsgi服务启动情况,看到如下图效果是服务启动成。

    ps aux | grep uwsgi

    uwsgi..png

  8. 重复执行以上操作,在ECS02实例中安装uWSGI Server。

步骤六:配置Nginx并重启

启动好后端服务后,我们需要添加Nginx配置来代理后端服务。

  1. 创建配置文件。

    sudo vim /etc/nginx/conf.d/app.conf
  2. 进入Vim编辑器后,按i键进入编辑模式。

    server {
        listen 80 default_server;
        
        server_name app.example.com;
    
        root /var/www/html;
    
        # Add index.php to the list if you are using PHP
        index index.html index.htm index.nginx-debian.html;
    
        location / {
            # 转发端口
            uwsgi_pass  127.0.0.1:5000;
            include uwsgi_params;
        }
    }
  3. 粘贴后,按Esc键,输入:x保存并退出编辑。

  4. 重启Nginx。

    sudo nginx -s reload
  5. 验证配置是否成功。

    说明

    如果未运行成功,可以在/data/uwsgi.log日志文件查看原因。

    curl http://127.0.0.1/ecs/getServerInfo

    命令执行结果是Invalid Parameter表示服务配置成功。

  6. 重复执行以上操作,在ECS02实例中配置Nginx并重启。

步骤七:配置监听

负载均衡实例监听负责检查连接请求,然后根据调度算法定义的转发策略将请求流量分发至后端服务器。

  1. 登录传统型负载均衡CLB控制台

  2. 实例管理页面,单击已创建的CLB实例ID。

  3. 监听页签单击添加监听

  4. 协议&监听配置向导,完成以下主要参数的配置,其余参数保持默认配置,然后单击下一步

    监听配置

    说明

    示例值

    选择负载均衡协议

    选择监听的协议类型。

    HTTP

    监听端口

    设置前端协议端口,即用来接收请求并向后端服务器进行请求转发的监听端口。

    监听端口范围:1~65535。

    80

    监听名称

    自定义监听的名称。

    HTTP_80

  5. 后端服务器配置向导,选择默认服务器组,可以看到已添加的ECS01ECS02,如果没默认添加点击继续添加,选择刚刚创建的ECS01ECS02实例。

  6. 输入ECS01ECS02的端口,本教程都设置为80,然后单击下一步

  7. 健康检查配置向导,保持默认配置,单击下一步,然后单击提交。等待配置成功后,单击知道了

步骤三:注册微信小程序

在开发小程序之前,您需要先注册微信小程序。

  1. 进入小程序页面,单击前往注册,根据指引填写信息并提交相应的资料,完成账号申请。

    mp注册

  2. 使用申请的微信公众平台账号登录小程序后台,单击开发管理> 开发设置,可以看到小程序的AppID,请记录AppID,后续操作中需要使用。

    setting

步骤四:安装小程序开发环境并创建项目

启动好后端服务后,我们接下来要开发小程序。先安装小程序开发环境。

  1. 安装Node.js开发环境,请到Node.js页面下载并安装Node.js环境。

  2. 下载并安装微信小程序开发工具。详细信息请参见开发工具下载

  3. 打开小程序开发工具,使用微信扫码登录。

  4. 单击加号创建微信小程序示例项目。

    mp创建

  5. 参考以下填写项目信息,最后单击新建。

    • 项目名称:例如ECSAssistant。

    • 目录:例如D:\workspace\wechat\ECSAssistant。

    • AppID:小程序的唯一标识,从小程序控制台获取。

    • 开发模式:小程序。

    • 后端服务:不使用云服务。

    mpcreate

  6. 配置项目允许访问非HTTPS域名。在顶部配置栏,选择设置>项目设置,在本地设置页签,选中不校验合法域名、web-view(业务域名)、TLS版本一级HTTPS证书

步骤五:开发小程序

安装好开发环境后,我们来编写小程序代码。

  1. 生成的小程序示例项目结构如下。

    可以看到小程序的项目结构中有三种前缀为app的文件,它们定义了小程序的一些全局配置。

    • app.json应用配置。用于配置小程序的页面列表、默认窗口标题、导航栏背景色等。更多信息,请参见全局配置

    • app.acss应用样式。定义了全局样式,作用于当前小程序的所有页面。

    • app.js应用逻辑。可配置小程序的生命周期,声明全局数据,调用丰富的API。

    小程序所有的页面文件都在pages/路径下,页面文件有四种文件类型,分别是.js、.wxml、.wxss、和.json后缀的文件。相比全局配置文件,页面配置文件只对当前页面生效。其中.wxml文件定义了当前页面的页面结构。小程序中的所有页面都需要在app.json文件中声明。更多信息,请参见代码构成

    此外,项目顶层还有开发工具配置文件project.config.json和爬虫索引文件sitemap.json

    ECSAssistant
    ├── app.js
    ├── app.json
    ├── app.wxss
    ├── pages
    │ ├── index
    │ │ ├── index.js
    │ │ ├── index.json
    │ │ ├── index.wxml
    │ │ └── index.wxss
    │ └── logs
    │     ├── logs.js
    │     ├── logs.json
    │     ├── logs.wxml
    │     └── logs.wxss
    ├── project.config.json
    └── sitemap.json
  2. 编辑app.json文件,将小程序页面Title修改为ECS小助手,修改后的app.json文件内容如下。

    {
      "pages":[
        "pages/index/index",
        "pages/logs/logs"
      ],
      "window":{
        "backgroundTextStyle":"light",
        "navigationBarBackgroundColor": "#fff",
        "navigationBarTitleText": "ECS小助手",
        "navigationBarTextStyle":"black"
      },
      "style": "v2",
      "sitemapLocation": "sitemap.json"
    }
  3. 编辑pages/index/index.wxss文件,定义index的页面样式,修改后的index.wxss文件内容如下。

    .search-input {
      position: relative;
      margin-bottom: 50rpx;
      padding-left:80rpx;
      line-height: 70rpx;
      height: 80rpx;
      box-sizing: border-box;
      border: 2px solid #ff8f0e;
      border-radius: 100rpx;
      overflow: hidden;
      text-overflow: ellipsis;
      transition: border 0.2s;
    }
    
    .resultView {
      margin-top: 70rpx;
    }
    .result {
      position: relative;
      left: 30rpx;
      display: list-item;
      font-size: small;
    }
  4. 编辑pages/index/index.js文件,定义搜索框的失去焦点事件,修改后的index.js文件内容如下。

    说明

    将代码中<CLB_PUBLIC_IP>换成您刚刚创建的CLB实例的公网IP。

    Page({
      data: {
        queryResult: null,
        showView: 'false',
      },
    
    
      bindblur: function(e) {
        let that = this;
        wx.request({
          url: 'http://<CLB_PUBLIC_IP>/ecs/getServerInfo',
          method: 'GET',
          data: {
            instanceId: e.detail.value
          },
          success(res) {
            if(res.statusCode == 200){
              that.setData({
                queryResult: res.data,
                showView: !that.data.showView,
              });
            }else{
              that.setData({
                showView: 'false',
              });
              wx.showToast({
                title: '请输入你的ECS实例ID',
                duration: 1500,
                icon: 'none',
                mask: true
              })
            }
          }
    
        })
      }
    })
  5. 编辑pages/index/index.wxml文件,编写展示界面,修改后的index.wxml文件内容如下。

    <view class='container'>
      <input placeholder='请输入你的ECS实例ID' class='search-input' value='{{ inputValue }}' bindblur='bindblur'></input>
      <view class='resultView' hidden='{{ showView }}'>
        <text class='result'>CPU数:{{queryResult.Cpu}} 核</text>
        <text class='result'>内存大小:{{queryResult.Memory}} MB</text>
        <text class='result'>操作系统:{{queryResult.OSName}}</text>
        <text class='result'>实例规格:{{queryResult.InstanceType}}</text>
        <text class='result'>公网IP地址:{{queryResult.IpAddress}}</text>
        <text class='result'>网络带宽:{{queryResult.InternetMaxBandwidthOut}} MB/s</text>
        <text class='result'>在线状态:{{queryResult.instanceStatus == 'Running' ? '运行中':'已关机'}}</text>
      </view>
    </view>
  6. 保存后,编辑器会自动刷新。看到如下界面,表示小程序运行成功了。

    image..png

步骤六:测试微信小程序

完成以上操作后,您已经成功部署了服务端程序,并且在本地开发好了小程序。

您可以登录ECS控制台,复制刚刚创建的ECS实例ID,输入到小程序输入框中,就可以看到结果了。

您可以通过停机一台ECS模拟故障从而验证服务的可用性。操作如下:

  1. 登录ECS控制台,找到目标实例,在操作列单击image.png,在面板单击停止

  2. ECS状态为已停止后,再次在小程序中查询ECS实例ID,看到返回正常内容,则表明服务可用。

小程序界面的示意图如下:

image.png

后续步骤

如果您期望上线您的小程序,您需要做下面几件事:

  1. 申请域名,可以参考通用域名注册基本流程如何注册阿里云域名

  2. 申请SSL证书,并配置到服务器上,可以参考申请个人测试证书

  3. 上传小程序并提交审核,可以参考小程序发布上线

待审核通过后,手动上线小程序,就可以在微信客户端中查看和使用小程序了。

相关文档