基于资源编排在专有网络环境下快速部署高可用的Dubbox服务(Redis版)-阿里云开发者社区

开发者社区> 开发与运维> 正文

基于资源编排在专有网络环境下快速部署高可用的Dubbox服务(Redis版)

简介: 本文将介绍在专有网络VPC(Virtual Private Cloud)网络环境下,基于资源编排服务,快速部署高可用的Dubbox服务的过程。Dubbox服务采用的注册中心是主备高可用Redis服务器KVStore。做这件事情的意义在于:节约部署Dubbox的时间,降低部署Dubbox过程中出错的风

本文将介绍在专有网络VPC(Virtual Private Cloud)网络环境下,基于资源编排服务,快速部署高可用的Dubbox服务的过程。Dubbox服务采用的注册中心是主备高可用Redis服务器KVStore。做这件事情的意义在于:节约部署Dubbox的时间,降低部署Dubbox过程中出错的风险。

ROS
阿里云资源编排(Resource Orchestration)是一种简单易用的云计算资源管理和自动化运维服务。用户通过模板描述多个云计算资源的依赖关系、配置等,并自动完成所有资源的创建和配置,以达到自动化部署、运维等目的。编排模板同时也是一种标准化的资源和应用交付方式,并且可以随时编辑修改,使基础设施即代码(Infrastructure as Code)成为可能。
Ansible
Ansible是一个简单的自动化IT工具。引用Ansible官网的介绍就是:“Deploy apps.Manage systems.Crush complexity.Ansible helps you build a strong foundation for DevOps.”。
更多Ansible的相关知识可参考Ansible中文权威指南
Ansible的工作机制,可参考基于资源编排和 Ansible 在 VPC 下快速交付应用中“Ansible 及其运行机制”章节。
Dubbox
Dubbox在Dubbo服务的基础上添加了一些新功能,如:REST风格的远程调用、支持基于Jackson的JSON序列化、支持基于Kryo和FST的Java高效序列化实现、支持完全基于Java代码的Dubbo配置、升级Spring、升级Zookeeper等。想了解更多关于Dubbox服务内容可参考Dubbox Github

本文将从两个方面展开介绍

  • 安装Ansible和ROS SDK
  • VPC网络环境下快速部署Dubbox服务

安装Ansible和ROS SDK

首先申请一个VPC,并在这个VPC下面创建一个VSwitch,然后在这个VPC和VSwitch下申请一台ECS作为Ansible主机,给这台机器绑定公网IP,方便访问外网。ECS系统版本信息如下:

CentOS Linux release 7.0.1406 (Core)  

此版本的ECS已经安装了Python 2.7.5。

安装Ansible

Dubbox服务的部署需要通过Ansible来完成,本文采用了yum来安装Ansible:

yum install ansible  

Ansible默认安装目录为/etc/ansible,更多Ansible安装方式可参考Ansible Installation
注意:如果采用的是Ubuntu系统,安装Ansible过程如下:

  apt-get install ansible
  apt-get install sshpass

安装ROS SDK

ROS提供了RESTful API和SDK,本文将介绍用Python的方式来调用ROS SDK创建资源。

使用pip安装aliyun-python-sdk-core:

pip install aliyun-python-sdk-core

使用pip安装ROS SDK:

pip install aliyun-python-sdk-ros

如果Ansible主机未安装pip,可使用以下命令安装pip:

curl "https://bootstrap.pypa.io/get-pip.py" -o "get-pip.py"
python get-pip.py  

关于ROS SDK详细安装和使用过程可以参考阿里云资源编排服务Python SDK使用入门

VPC网络环境下快速部署Dubbox服务

定义ROS资源模板

根据ROS API的调用方式,在python文件中定义ROS资源模板。模板包括以下资源类型:

"Outputs" 资源栈输出,包括:ECS的Private IP,KVStore的Private IP,SLB的公网IP以及KVStore Port

在VPC网络环境下,要给ECS和KVStore分配VPC和VSwtich,并且需要保证和Anisble主机处在同一个VPC和VSwitch下,资源栈输出定义为:ECS的Private IP,KVStore的Private IP,SLB的公网IP以及KVStore Port。

定义ROS资源模板的Python文件generate_vpc_ros_template.py:

from string import Template
# define ros template
create_resources_with_parameters = '''
{
  "ROSTemplateFormatVersion": "2015-09-01",
  "Resources": {
    "SecurityGroup": {
      "Properties": {
        "SecurityGroupIngress": [
          {
            "IpProtocol": "tcp",
            "PortRange": "22/22",
            "NicType": "intranet",
            "Priority": 1,
            "SourceCidrIp": "0.0.0.0/0"
          }
        ],
        "SecurityGroupName": "$security_group_name",
        "VpcId": "$vpc_id"
      },
      "Type": "ALIYUN::ECS::SecurityGroup"
    },
    "InstanceGroup": {
      "Type": "ALIYUN::ECS::InstanceGroup",
      "Properties": {
        "ImageId": "centos7u2_64_40G_cloudinit_20160520.raw",
        "SecurityGroupId": {
          "Ref": "SecurityGroup"
        },
        "Password": "$ecs_password",
        "MinAmount": 2,
        "MaxAmount": 2,
        "InstanceType": "$instance_type",
        "ZoneId": "$zone_id",
        "InternetChargeType": "PayByTraffic",
        "NetworkType": "vpc",
        "InstanceName": "$instance_name",
        "VpcId": "$vpc_id",
        "VSwitchId": "$vswitch_id"
      }
    },
    "KvInstance": {
      "Type": "ALIYUN::REDIS::Instance",
      "Properties": {
        "EvictionPolicy": "noeviction",
        "Password": "$kvstore_password",
        "ZoneId": "$zone_id",
        "Capacity": $kvstore_capacity_g,
        "InstanceName": "$kvstore_instance_name",
        "VpcId": "$vpc_id",
        "VSwitchId": "$vswitch_id"
      }
    },
    "LoadBalance": {
      "Properties": {
        "AddressType": "internet",
        "InternetChargeType": "paybytraffic",
        "LoadBalancerName": "$load_balance_name"
      },
      
    },
    "Attachment": {
      "Properties": {
        "BackendServers": [
          {
            "ServerId": { "Fn::Select": ["0",{ "Fn::GetAtt": [ "InstanceGroup", "InstanceIds" ] }]},
            "Weight": 100
          },
           {
            "ServerId": { "Fn::Select": ["1",{ "Fn::GetAtt": [ "InstanceGroup", "InstanceIds" ] }]},
            "Weight": 100
          }
        ],
        "LoadBalancerId": {
          "Ref": "LoadBalance"
        }
      },
      "Type": "ALIYUN::SLB::BackendServerAttachment"
    },
    "Listener": {
      "Type": "ALIYUN::SLB::Listener",
      "Properties": {
          "LoadBalancerId": {
            "Ref": "LoadBalance"
          },
          "ListenerPort": $listen_port,
          "BackendServerPort": $bachend_server_port,
          "Bandwidth": -1,
          "Protocol": "http",
          "HealthCheck": {
              "HealthyThreshold": 3,
              "UnhealthyThreshold": 3,
              "Interval": 2,
              "Timeout": 5,
              "HttpCode": "http_2xx,http_3xx,http_4xx,http_5xx",
              "URI": "$health_check_path"
          },
          "Scheduler": "wrr"
      }
    }
  },
  "Outputs": {
      "EcsPrivateIps": {
        "Value": { "Fn::GetAtt": [ "InstanceGroup", "PrivateIps"]}
      },
      "LoadBalanceIp": {
        "Value": {"Fn::GetAtt": [ "LoadBalance", "IpAddress"]}
      },
      "KvStoreHost": {
        "Value": { "Fn::GetAtt": ["KvInstance", "ConnectionDomain"] }
      },
      "KvStorePort": {
        "Value": { "Fn::GetAtt": ["KvInstance", "Port"] }
      }
   }
}
'''

# define func to generate ros template
def generate_template(**kwargs):
    template = Template(create_resources_with_parameters)
    return template.substitute(kwargs)

构造请求,创建资源栈

初始化SDK客户端对象:

client = AcsClient(ak_id, ak_secret, region_id)  

构造创建资源栈的请求:

req = CreateStacksRequest.CreateStacksRequest() 

指定请求资源Region:

req.set_headers({'x-acs-region-id': region_id}) 

构造请求体的内容,包括:栈名、过期时间戳、ROS资源模板

create_stack_body = '''
    {
        "Name": "%s",
        "TimeoutMins": %d,
        "Template": %s
    }
    ''' % (stack_name, create_timeout, template)
req.set_content(create_stack_body)

说明:其中template是通过调用generate_vpc_ros_template.generate_template方法生成的ROS资源模板对象,调用方式如下:

 template = generate_classic_ros_template.generate_template(security_group_name=security_group_name, ecs_password = ecs_password, 
            instance_type = instance_type, zone_id = zone_id, instance_name = instance_name, kvstore_password = kvstore_password, 
            kvstore_capacity_g = kvstore_capacity_g, kvstore_instance_name = kvstore_instance_name, load_balance_name = load_balance_name, 
            listen_port = 8080, bachend_server_port =8080, health_check_path = '/dubbo-admin/favicon.ico')    

发送请求,创建资源栈:

response = client.get_response(req)

获取资源栈信息:

 # deal response
 if 201 == response[0]:
      print('Create stack succeccfully!!!')
      return json.loads(response[-1])
 else:
      print('Unexpected errors: status=%d, error=%s' % (response[0], response[-1]))
      return None

请求成功会返回资源栈的IdName信息,请求发出以后,可到ROS 控制台查看资源栈详情。

创建Inventory,构建Playbook

获取资源栈输出信息

资源栈创建好以后,我们再次调用ROS API获取资源栈的输出信息。方法如下:

def get_stack_outputs(stack, ak_id, ak_secret, region_id):
    print('Start to get stack output...')
    if stack is None:
        return None
    req = DescribeStackDetailRequest.DescribeStackDetailRequest()
    req.set_headers({'x-acs-region-id': region_id})
    req.set_StackName(stack['Name'])
    req.set_StackId(stack['Id'])
    client = AcsClient(ak_id, ak_secret, region_id)
    attempt = attempt_times
    wait = wait_time
    while attempt >= 0 and wait >= 0:
        response = client.get_response(req)
        if 200 == response[0]:
            resources = json.loads(response[-1])
            if (resources is None) or (not resources.has_key('Outputs')):
                    time.sleep(wait)
                    attempt = attempt - 1
                    wait = wait - interval
                    continue
            outputs = resources['Outputs']
            print('Getting stack outputs finished. outputs: ', outputs)
            return outputs
        else:
            print('Unexpected errors: status=%d, error=%s' % (response[0], response[-1]))
            return None
    print('Getting stack outputs timeout.')
    return None

调用时需要传入创建好的资源栈IdName信息,资源栈信息可从创建资源栈获取。由于创建资源栈所需时间未定,方法中定义了获取超时重试的机制。每次重试以后的等待时间要比上次等待时间少interval秒,在attempt次尝试后仍未获取到资源栈信息视为资源创建失败(一般不会出现这种情况)。

资源栈的输出格式如下:

[{u'OutputKey': u'KvStorePort', u'Description': u'No description given', u'OutputValue': 6379}, 
{u'OutputKey': u'EcsPrivateIps', u'Description': u'No description given', u'OutputValue': [u'192.168.x.x', u'192.168.x.x']}, 
{u'OutputKey': u'KvStoreHost', u'Description': u'No description given', u'OutputValue': u'xxxx.m.cnsza.kvstore.aliyuncs.com'}, 
{u'OutputKey': u'LoadBalanceIp', u'Description': u'No description given', u'OutputValue': u'112.74.x.x'}]

我们将以上输出定义为Outputs

编辑Inventory文件

根据Outputs,获取ECS内网IP。方法如下:

def get_ecs_ips(outputs):
    for i in range(len(outputs)):
        if outputs[i]['OutputKey'] == ecs_ip_output_key:
            return outputs[i]['OutputValue']
    return None

由于ECS登录密码在配置文件中已经给出,可以直接使用,用户默认为root用户,根据这些信息编辑/etc/ansible/hosts文件,即通常我们所说的Ansible Inventory文件。方法如下:

def edit_hosts(remote_ips, remote_ports, remote_users, remote_pass):
    print 'Start edit hosts'
    if len(remote_ips) <= 0:
        print('Error! Remote ips is empty!')
        return 
    if len(remote_ports) <= 0:
        print 'Error! Remote ports is empty!'
        return
    if len(remote_users) <= 0:
        print('Error! Remote users is empty!')
        return
    if len(remote_pass) <= 0:
        print('Error! Remote pass is empty!')        
    host_str = ' ansible_ssh_port=%s ansible_ssh_user=%s ansible_ssh_pass=%s\n'
    with open(hosts_dir + '/' + hosts_file, 'wb') as file:
        file.write( '[%s]\n' % service_name )
        for index in range(len(remote_ips)):
            file.write( ('%s'+host_str) % (remote_ips[index], remote_ports[index], remote_users[index], remote_pass[index]) )
    print 'Edit hosts end'

生成Inventory文件,即/etc/Ansible/hosts文件。文件内容如下:

[vpc_dubbox]
192.168.x.x ansible_ssh_port=22 ansible_ssh_user=xxxx ansible_ssh_pass=xxxx
192.168.x.x ansible_ssh_port=22 ansible_ssh_user=xxxx ansible_ssh_pass=xxxx

Inventory文件中定义了远程主机组vpc_dubbox,并指明了该主机组下云主机的登录地址、登录协议(默认是 SSH )、登录端口、登录用户名和密码。

生成和执行playbook

生成Ansible可执行文件

根据Outputs,获取KVStore Host。方法如下:

def get_kvstore_host(outputs):
    for i in range(len(outputs)):
        if outputs[i]['OutputKey'] == kvstore_host_output_key:
            return outputs[i]['OutputValue']
    return None

根据Outputs,获取KVStore Port。方法如下:

def get_kvstore_port(outputs):
    for i in range(len(outputs)):
        if outputs[i]['OutputKey'] == kvstore_port_output_key:
            return outputs[i]['OutputValue']
    return None

根据Outputs,获取SLB 公网IP。方法如下:

def get_loadbalance_ip(outputs):
    for i in range(len(outputs)):
        if outputs[i]['OutputKey'] == loadbalance_ip_output_key:
            return outputs[i]['OutputValue']
    return None

/etc/ansible目录下创建文件夹vpc_dubbox,并在此文件夹下生成playbook的执行文件vpc_dubbox.yml。生成vpc_dubbox.yml的方法如下:

def create_pb_init(kvstore_port, kvstore_host):
    print('Start to edit playbook init config...')
    #if service folder not exist, create it
    if not os.path.exists(pb_file_dir):
        os.makedirs(pb_file_dir)
    with open(pb_file_dir + '/' + pb_file_name, 'wb') as file_pb:
        playbook = generate_playbook_template.create_playbook(service_name=service_name, jetty_home=jetty_home, 
            jetty_home_enforce=jetty_home_enforce, dubbo_root_password=dubbo_root_password, dubbo_guest_password=dubbo_guest_password,
            kvstore_host=kvstore_host, kvstore_port=kvstore_port, kvstore_password=kvstore_password, pb_name=pb_name)
        file_pb.write(playbook)
    print('Editting pb_init is finished.')

生成的vpc_dubbox.yml文件内容如下:

- name: deploy dubbox service
  hosts: vpc_dubbox
  vars:
    - jetty_home: /opt/jetty
    - enforce: f
    - dubbo_root: [root用户密码]
    - dubbo_guest: [guest用户密码]
    - redis_host: [kvstore域名]
    - redis_port: [kvstore端口号]
    - redis_pwd: [kvstore登录密码]
  roles:
    - vpc_dubbox_playbook

文件中的属性解释如下:

  • hosts

    • 远程主机组名称,和Inventory文件中的远程主机组相对应。
  • vars

    • 配置参数,提供给install_Jdk.sh install_Jetty.sh deploy_Admin.sh这三个脚本使用。
  • roles

    • 指定要执行的playbook为 vpc_dubbox_playbook
下载playbook文件

vpc_dubbox_playbook定义好后会被传到阿里云oss,运行Python脚本会将vpc_dubbox_playbook下载至/etc/ansible/vpc_dubbox目录下。下载PlayBook到Ansible主机的方法如下:

# define func to download playbook
def download_playbook():
    file_name = playbook_url.split('/')[-1]
    command_wget = 'wget -P ' + ansible_dir + service_name + ' ' + playbook_url
    print 'Execute linux command:' + command_wget
    subprocess.call(command_wget, shell = True)
    command_tar = 'tar zxf ' + ansible_dir + service_name + '/' + file_name + ' -C ' + ansible_dir + service_name 
    print 'Execute linux command:' + command_tar
    subprocess.call(command_tar, shell = True)
    command_rm = 'rm -rf ' + ansible_dir + service_name + '/' + file_name
    print 'Execute linux command:' + command_rm
    subprocess.call(command_rm, shell = True)

vpc_dubbox_playbook目录结构如下:

[root@iZ94jwkjg0sZ vpc_dubbox]# ls -l vpc_dubbox_playbook/
总用量 12
drwxr-xr-x 2 501 games 4096 8月   8 11:08 files
drwxr-xr-x 2 501 games 4096 8月   8 11:08 tasks
drwxr-xr-x 2 501 games 4096 8月   8 10:25 templates 

关于vpc_dubbox_playbook结构的说明:

  • files

    • 存放install_Jdk.sh install_Jetty.sh deploy_Admin.sh三个文件,这三个文件分别用来安装JDK、Jetty以及部署Dubbox服务。
  • tasks

    • 存放要执行的yml文件 install.yml,文件内容如下:


      • name: add config
        template:

        dest: /root/config
        src: config.j2
        mode: 0600
        
      • name: run install jdk
        script: install_Jdk.sh
      • name: run install jetty
        script: install_Jetty.sh
      • name: run deploy admin
        script: deploy_Admin.sh
  • templates

    • 存放配置文件模板config.j2,文件内容如下:

            #第一列 变量名 第二列 变量值 第三列 配置项 不同列之间用tab或者空格分开
            #jetty安装路径,当目录已经存在的时候加上f参数会强制覆盖,否则会退出安装
            JETTY_HOME {{jetty_home}} {{enforce}}
            #设置admin控制台root用户的密码
            DUBBO_ADMIN_ROOT_PASSWD {{dubbo_root}}
            #设置admin控制台guest用户的密码
            DUBBO_ADMIN_GUEST_PASSWD {{dubbo_guest}}
            #KvStore域名,端口,密码设置,端口号可省略
            REDIS_HOST {{redis_host}}
            REDIS_PORT {{redis_port}}
            REDIS_PASSWD {{redis_pwd}}
      
    • 配置文件config.j2中的参数值从vpc_dubbox.yml文件中获取,然后在通过Ansible执行PlayBook的过程中在每一台远程主机/root目录下生成config文件,提供给install_Jdk.sh install_Jetty.sh deploy_Admin.sh这三个脚本读取配置信息。
执行playbook

Ansible是通过ssh命令连接远程主机,并执行playbook的。首次执行playbook前,由于当前Ansible主机并没有记录远端主机的RSA key,会弹出确认对话框,输入yes,回车:

OSX10111-0c4de9cb8aea:dubbox wujin.lhr$ ssh root@112.74.205.137
    The authenticity of host '112.74.205.137 (112.74.205.137)' can't be established.
    ECDSA key fingerprint is SHA256:bbDuVh6dQYDQo/X+Qzh52VGAxBFpGSqVG0jVNCB/9cE.
    Are you sure you want to continue connecting (yes/no)?

可通过Python脚本实现上述过程:

# define func to confirm ssh login before execute ansible
def confirm_ssh_login(host_ips):
    print('Start to confirm ssh login to all nodes...')
    if len(host_ips) == 0:
        print('Host_ips is empty')
        return
    for ip in host_ips:
        child = pexpect.spawn('ssh root@' + ip)
        ret_1 = child.expect(['Are you sure you want *', 'Password*', 'root@*', pexpect.EOF])
        if 0 == ret_1:
            child.sendline('yes')
            ret_2 = child.expect(['Password*', 'root@*', pexpect.EOF])
            if 0 == ret_2 or 1 == ret_2:
                print('Confirm ' + ip + ' ok!')
                child.sendintr()
                continue
            else:
                print('Confirm ' + ip + ' failed!')
        elif 1 == ret_1 or 2 == ret_1:
            print('Confirm ' + ip + ' ok!')
            child.sendintr()
        else:
            print('Confirm ' + ip + ' failed!')
    print('Confirm ssh login finished!')

注意:由于用到了pexpect这个模块,在执行脚本前,使用pip安装pexpect模块:

pip install pexpect

VPC网络环境下未给ECS分配公网IP,因此每台ECS无法下载安装包。因为Ansible主机和每台ECS在同一个VSwitch下面,所以可以通过scp命令将安装包从Ansible主机传到每台ECS上,具体过程如下:

  1. 首先将JDK,Jetty以及Dubbox控制台的安装包上传至阿里云oss,获取object地址。
  2. 传入object地址,运行Python脚本,下载安装包至Ansible主机。方法如下:

    def wget_files():

    if not os.path.exists(wget_file_path):
        os.makedirs(wget_file_path)
    subprocess.call('wget -P ' + wget_file_path + ' ' + jdk_path, shell = True)
    subprocess.call('wget -P ' + wget_file_path + ' ' + jetty_path, shell = True)
    subprocess.call('wget -P ' + wget_file_path + ' ' + dubbo_admin_path, shell = True)    
            
  3. 将安装包拷贝到每台ECS。方法如下:

    def scp_files_from_ansible_host_to_ecs(host_ips):

    scp_password_info = 'root@%s\'s password:'
    scp_command = 'scp ' + wget_file_path + 'dubbo-admin-2.8.4.war ' + wget_file_path + 'jdk-8u101-linux-x64.rpm ' + wget_file_path + 'jetty-distribution-8.1.19.v20160209.tar.gz root@%s:/root'
    if host_ips is None or len(host_ips) == 0:
        print 'Host_ips is None,exit!'
        return None
    for ip in host_ips:
        scp = pexpect.spawn(scp_command % ip)
        i = scp.expect([scp_password_info % ip, pexpect.EOF])
        if i == 0:
            scp.sendline(ecs_password)
            scp.expect(pexpect.EOF, timeout=None)
        else:
            print 'Scp files to' + ip + 'failed!' 
    

最后执行Ansible。命令如下

subprocess.call('ansible-playbook ' + pb_file_dir + '/' + pb_file_name, shell=True)

运行Ansible以后会进行Dubbox服务的部署。

快速部署Dubbox服务

Dubbox服务的部署,主要分为以下三个步骤:

安装JDK

安装JDK的文件为install_Jdk.sh,内容如下:

#!/bin/bash

#日志时间格式
DATE="date +'%Y-%m-%d %H:%M:%S'"

#检查java环境是否存在
if which java 2>/dev/null; then
        echo $(eval $DATE) " java already exits" >> ~/install_dubbox.log
else
        #wget jdk安装包
        #wget http://dubbo.oss-cn-shenzhen.aliyuncs.com/jdk-8u101-linux-x64.rpm
        #echo $(eval $DATE) " wget jdk success" >> ~/install_dubbox.log
        rpm -ivh jdk-8u101-linux-x64.rpm
        rm -rf jdk-8u101-linux-x64.rpm
fi

#检查java环境是否安装成功
if which java 2>/dev/null; then
        echo $(eval $DATE) " install jdk8 success" >> ~/install_dubbox.log
else
        echo $(eval $DATE) " install jdk8 failed" >> ~/install_dubbox.log

安装JDK过程中,首先需要检查当前ECS是否存在java环境,存在就跳过安装过程,反之则安装JDK。
声明:JDK安装包请从Oracle官网下载,并下载本文指定的JDK版本。由于从本文链接中下载JDK带来的任何问题,和本文作者无关。

安装Jetty

安装Jetty的文件为install_Jetty.sh,内容如下:

#!/bin/bash

#jetty默认安装目录
JETTY_HOME_DEFAULT=/opt/jetty
#日志时间格式
DATE="date +'%Y-%m-%d %H:%M:%S'"

#安装jetty
#wget http://dubbo.oss-cn-shenzhen.aliyuncs.com/jetty-distribution-8.1.19.v20160209.tar.gz
#echo $(eval $DATE) " wget jetty success" >> ~/install_dubbox.log

#解压
tar zxf jetty-distribution-8.1.19.v20160209.tar.gz
echo $(eval $DATE) " tar zxf jetty success" >> ~/install_dubbox.log

#删除压缩包
rm -f jetty-distribution-8.1.19.v20160209.tar.gz
echo $(eval $DATE) " rm jetty.tgz success" >> ~/install_dubbox.log

#读取配置文件内容,给变量赋初值
while read line
do
        #过滤掉注释行
        if [ ! "`echo $line|grep '#'`" ]; then
                varname=`echo $line|awk '{print $1}'`
                varvalue=`echo $line|awk '{print $2}'`
                varconfig=`echo $line|awk '{print $3}'`
                eval $varname=$varvalue
                eval $varname"_CONFIG"=$varconfig
        fi
done < ~/config

#JETTY_HOME未配置,选择默认配置JETTY_HOME_DEFAULT
if [ ! -n "$JETTY_HOME" ]; then
        JETTY_HOME=$JETTY_HOME_DEFAULT
        rm -rf $JETTY_HOME
        mkdir -p $JETTY_HOME
        echo $(eval $DATE) " JETTY_HOME采用默认设置 $JETTY_HOME" >> ~/install_dubbox.log

#如果目录不存在,创建目录
elif [ ! -d "$JETTY_HOME" ]; then
        mkdir -p $JETTY_HOME
        echo $(eval $DATE) " 创建JETTY_HOME新目录 $JETTY_HOME" >> ~/install_dubbox.log
        
#如果目录已经存在,并且配置了强制执行,则覆盖目录
elif [ "$JETTY_HOME_CONFIG" = "f" ]; then
        rm -rf $JETTY_HOME
        mkdir -p $JETTY_HOME
        echo $(eval $DATE) " 强制覆盖已存在的JETTY_HOME $JETTY_HOME" >> ~/install_dubbox.log

#如果目录已经存在,并且没有配置强制执行,退出程序
else
        echo $(eval $DATE) " $JETTY_HOME 已经存在,未选择强制覆盖,请重新设置JETTY_HOME或在配置文件中配
置强制执行选项:f" >> ~/install_dubbox.log
        #退出程序
        exit
        echo $(eval $DATE) " 程序退出" >> ~/install_dubbox.log
fi

#移动jetty到JETTY_HOME
mv jetty-distribution-8.1.19.v20160209/* $JETTY_HOME
rm -rf jetty-distribution-8.1.19.v20160209
echo $(eval $DATE) " mv jetty  success" >> ~/install_dubbox.log

#将jetty配置文件/etc/webdefault.xml中的属性dirAllowed值设置为false
cd $JETTY_HOME/etc
#确认行数
NUMBER=`grep -n "<param-name>dirAllowed</param-name>" webdefault.xml | cut  -d  ":"  -f  1`
sed -i -e "$[++NUMBER]s/.*/<param-value>false<\/param-value>/" webdefault.xml
echo $(eval $DATE) " set dirAllowed to false success" >> ~/install_dubbox.log

#查看jetty服务是否开启,即系统已经安装了jetty并已经开启
JETTY_PROCESS_ID=`ps -fe|grep jetty |grep -v grep |awk '{print $2}'`
#未开启jetty服务
if [ ! "$JETTY_PROCESS_ID" ]; then
        $JETTY_HOME/bin/jetty.sh start
        echo $(eval $DATE) " start jetty" >> ~/install_dubbox.log
else
        kill -9 $JETTY_PROCESS_ID
        echo $(eval $DATE) " stop jetty" >> ~/install_dubbox.log
        $JETTY_HOME/bin/jetty.sh start
        echo $(eval $DATE) " start jetty" >> ~/install_dubbox.log
fi

#查看jetty服务是否开启
JETTY_PROCESS_ID=`ps -fe|grep jetty |grep -v grep |awk '{print $2}'`
if [ ! "$JETTY_PROCESS_ID" ]; then
        echo $(eval $DATE) " install jetty failed" >> ~/install_dubbox.error.log
else
     echo $(eval $DATE) " install jetty success" >> ~/install_dubbox.log
     kill -9 $JETTY_PROCESS_ID
     echo $(eval $DATE) " stop jetty" >> ~/install_dubbox.log
fi

安装Jetty过程,主要包括:读取配置文件,设置Jetty安装目录,修改Jetty的配置文件。Jetty安装目录的选择包括以下几种情形:

  • 未指定Jetty安装目录,则选择默认目录进行安装
  • 指定了Jetty安装目录并且目录不存在,则创建目录并安装Jetty
  • 指定了Jetty安装目录并且目录已经存在

    • 如果配置了强制执行选项,则覆盖目录并安装Jetty
    • 如果没有配置强制执行选项,程序强制退出

部署Dubbox服务

部署Dubbox服务的文件为deploy_Admin.sh,内容如下:

#!/bin/sh

#redis默认端口号
REDIS_PORT_DEFAULT=6379
#默认jetty安装目录
JETTY_HOME_DEFAULT=/opt/jetty
#默认root用户密码
DUBBO_ADMIN_ROOT_PASSWD_DEFAULT=root
#默认guest用户密码
DUBBO_ADMIN_GUEST_PASSWD_DEFAULT=guest
#日志时间格式
DATE="date +'%m-%d-%Y %H:%M:%S'"

#读取配置文件内容,给变量赋初值
while read line
do
        #过滤掉注释行
        if [ ! "`echo $line|grep '#'`" ]; then
                varname=`echo $line|awk '{print $1}'`
                varvalue=`echo $line|awk '{print $2}'`
                varconfig=`echo $line|awk '{print $3}'`
                eval $varname=$varvalue
                eval $varname"_CONFIG"=$varconfig
        fi
done < ~/config
#JETTY_HOME未配置,选择默认配置JETTY_HOME_DEFAULT
if [ ! -n "$JETTY_HOME" ]; then
        JETTY_HOME=$JETTY_HOME_DEFAULT
#如果目录已经存在,并且没有要求强制覆盖
elif [ -d "$JETTY_HOME" ]; then
        if [ "$JETTY_HOME_CONFIG" != "f" ]; then
                echo $(eval $DATE) " $JETTY_HOME 已经存在,未选择强制覆盖,请重新设置JETTY_HOME或在配置文件中配置强制执行选项:f" >> ~/install_dubbox.log
                #退出程序
                echo $(eval $DATE) " 程序退出" >> ~/install_dubbox.log
                exit
        fi
fi

#检测REDIS_HOST是否设置
if [ ! $REDIS_HOST ]; then
        echo $(eval $DATE) " 未设置KvStore主机域名" >> ~/install_dubbox.log
        echo $(eval $DATE) " 程序退出" >> ~/install_dubbox.log
        #退出程序
        exit
fi

#检测REDIS_PASSWD是否设置
if [ ! $REDIS_PASSWD ]; then
        echo $(eval $DATE) " 未设置KvStore登录密码" >> ~/install_dubbox.log
        echo $(eval $DATE) " 程序退出" >> ~/install_dubbox.log
        exit
fi

#检测redis端口号是否设置,未设置的采用默认端口号
if [ ! $REDIS_PORT ]; then
        echo $(eval $DATE) " 未设置KvStore端口号,选择默认端口号 $REDIS_PORT_DEFAULT" >> ~/install_dubbox.log
        REDIS_PORT=$REDIS_PORT_DEFAULT
fi

#检测admin root用户密码是否设置
if [ ! $DUBBO_ADMIN_ROOT_PASSWD ]; then
        echo $(eval $DATE) " 未设置admin root用户的密码,采用默认密码 $DUBBO_ADMIN_ROOT_PASSWD_DEFAULT" >> ~/install_dubbox.log
        DUBBO_ADMIN_ROOT_PASSWD=$DUBBO_ADMIN_ROOT_PASSWD_DEFAULT
fi

#检测admin guest用户密码是否设置
if [ ! $DUBBO_ADMIN_GUEST_PASSWD ]; then
        echo $(eval $DATE) " 未设置admin guest用户的密码,采用默认密码 $DUBBO_ADMIN_GUEST_PASSWD_DEFAULT" >> ~/install_dubbox.log
        DUBBO_ADMIN_GUEST_PASSWD=$DUBBO_ADMIN_GUEST_PASSWD_DEFAULT
fi

#从oss上下载dubbo-admin的war包
#wget http://dubbo.oss-cn-shenzhen.aliyuncs.com/dubbo-admin-2.8.4.war
#echo $(eval $DATE) " wget dubbo-admin success" >> ~/install_dubbox.log

#将war包部署到jetty上
mv dubbo-admin-2.8.4.war $JETTY_HOME/webapps/dubbo-admin.war
echo $(eval $DATE) " mv dubbo-admin.war to webapps" >> ~/install_dubbox.log

#修改配置文件
mkdir $JETTY_HOME/webapps/dubbo-admin
cd $JETTY_HOME/webapps/dubbo-admin
jar xf ../dubbo-admin.war
cd $JETTY_HOME/webapps/dubbo-admin/WEB-INF

#配置admin注册监听文件
sed -i -e "s/^dubbo.registry.address.*/dubbo.registry.address=redis:\/\/a:$REDIS_PASSWD@$REDIS_HOST:$REDIS_PORT/" dubbo.properties
echo $(eval $DATE) " set registry to redis" >> ~/install_dubbox.log
#设置root用户密码
sed -i -e "s/^dubbo.admin.root.password.*/dubbo.admin.root.password=$DUBBO_ADMIN_ROOT_PASSWD/" dubbo.properties
echo $(eval $DATE) " set user root passwd" >> ~/install_dubbox.log
#设置guest用户密码
sed -i -e "s/^dubbo.admin.guest.password.*/dubbo.admin.guest.password=$DUBBO_ADMIN_GUEST_PASSWD/" dubbo.properties
echo $(eval $DATE) " set user guest passwd" >> ~/install_dubbox.log

cd $JETTY_HOME/webapps/dubbo-admin
jar cf dubbo-admin.war *
mv dubbo-admin.war $JETTY_HOME/webapps/
rm -rf $JETTY_HOME/webapps/dubbo-admin

#启动jetty
nohup $JETTY_HOME/bin/jetty.sh start &
echo $(eval $DATE) " start jetty" >> ~/install_dubbox.log

#关闭centos7的防火墙
systemctl stop firewalld.service
sleep 30

CODE=`curl -I -m 10 -o /dev/null -s -w %{http_code}  -u root:$DUBBO_ADMIN_ROOT_PASSWD http://localhost:8080/dubbo-admin/`
echo $(eval $DATE) " return http status code: $CODE" >> ~/install_dubbox.log
if [ $CODE = 200 ]; then
        echo $(eval $DATE) " admin控制台启动成功" >> ~/install_dubbox.log
else
        echo $(eval $DATE) " admin控制台启动失败" >> ~/install_dubbox.error.log
fi
rm -rf ~/config

部署Dubbox服务过程,主要包括:检查设置是否合理,将Dubbox服务部署到Jetty,设置Dubbox服务注册中心为redis,修改dubbo.properties配置文件,设置控制台的登录密码。

在部署Dubbox服务的过程中,有几个需要注意的问题:

  1. 当访问Dubbox服务时,需要访问服务器的8080端口,由于防火墙的原因,外部可能无法访问到服务器的Dubbox服务,因此需要修改防火墙的设置。我创建的ECS系统为Centos 7,为了简单起见,我直接关闭了系统的防火墙:

    systemctl stop firewalld.service

  2. 通过Ansible控制远程服务器组启动Jetty服务时,Ansible命令执行结束以后,Jetty服务也自动退出,这是我们不想看到的结果。可通过nohup命令以守护进程的方式启动Jetty服务,可以解决Jetty服务自动退出的问题,启动Jetty命令如下:

    nohup $JETTY_HOME/bin/jetty.sh start &

  3. Dubbox服务支持redis注册中心加密的方式,配置文件dubbo.properties中的格式为:

    dubbo.registry.address=redis://user:password@ip:port
    user可随便填写,但是不能为空,password为redis服务的登录密码,redis服务默认port为6379

Dubbox服务部署好了以后,可通过以下地址访问Dubbox服务控制台:

http://ip:8080/dubbo-admin

注意:在VPC网络下,ip指的是SLB的公网IP

输入用户名密码,点击登录:

图片

Dubbox服务控制台如下:

图片

现在,我们可以使用Dubbox服务了。

系统结构图

最终,基于资源编排快速部署出来的高可用Dubbox服务框架如下图所示:

图片

Dubbox服务的高可用,主要体现在两个方面:

  • 注册中心的高可用

    • 注册中心采用了KVStore,KVStore其实是一个主备双机的高可用Redis服务器。
  • Dubbox服务控制台的高可用

    • 创建两台ECS实例并分别部署Dubbox服务,然后创建一个SLB,将之前创建的两台ECS实例作为SLB的后端服务器,最后给这个SLB添加监听,并设置健康检查。外部通过SLB来访问Dubbox服务。

附件下载:https://developer.aliyun.com/topic/download?id=290

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

分享:
开发与运维
使用钉钉扫一扫加入圈子
+ 订阅

集结各类场景实战经验,助你开发运维畅行无忧

其他文章