打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

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

本文将介绍在专有网络VPC(Virtual Private Cloud)下,基于资源编排服务,快速部署高可用的Dubbox服务的过程。Dubbox服务采用的注册中心是ZooKeeper集群。做这件事情的意义在于:节约部署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
ZooKeeper
Apache ZooKeeper是Apache软件基金会的一个软件项目,他为大型分布式计算提供开源的分布式配置服务、同步服务和命名注册。ZooKeeper的架构通过冗余服务实现高可用性。本文将介绍采用ZooKeeper集群作为Dubbox服务的注册中心,来保证Dubbox服务的高可用性。更多ZooKeeper相关的知识可参考ZooKeeper Wiki

本文将从以下三个方面展开介绍

  • 准备Ansible主机
  • VPC网络环境下快速部署高可用的Dubbox服务
  • 总结

准备Ansible主机

本章将从以下两个方面展开:

  • 安装Ansible
  • 安装ROS SDK

首先需要新建一个VPC,并在这个VPC下创建一个VSwitch,在这个VPC和VSwitch下申请一台ECS实例,并给这台机器绑定公网IP,方便访问外网。本文所采用的Ansible主机(ECS)系统版本信息如下:

CentOS Linux release 7.0.1406 (Core)  

这个版本的ECS已经安装了Python 2.7.5。
说明:专有网络VPC的创建链接。创建好VPC以后直接在该VPC下新建VSwitch即可。新建ECS的链接

创建好Ansible主机以后,我们需要给这台主机安装Ansible和ROS SDK。Ansible用来实现对远程主机的控制,ROS SDK用来实现对资源栈的操作。

安装Ansible

Dubbox服务的快速部署需要借助Ansible来完成,本文采用了yum来安装Ansible:

yum install ansible  

Ansible默认安装目录为/etc/ansible,安装好以后,/etc/ansible/目录结构如下:

[root@iZ94jwkjg0sZ ansible]# ls -l总用量 24-rw-r--r-- 1 root root 13819 5月  25 23:49 ansible.cfg-rw-r--r-- 1 root root   441 8月  17 17:52 hostsdrwxr-xr-x 4 root root  4096 8月  17 17:52 roles

关于Ansible的安装方式可参考Ansible Installation
注意:如果采用的是Ubuntu系统,安装Ansible过程如下:

  apt-get install ansible  apt-get install sshpass

安装ROS SDK

ROS提供了RESTful API和SDK,本文将介绍用Python的方式来调用ROS API创建资源。
在调用ROS API之前需要安装相关的依赖。

使用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  

如果不了解pip,可参考维基百科)。
关于ROS SDK详细安装和使用过程可以参考阿里云资源编排服务Python SDK使用入门

VPC网络环境下快速部署高可用的Dubbox服务

本章将详解讲解Dubbox服务的部署过程。您也可以跳过此章节,直接参考如何快速构建高可用Dubbox服务快速部署Dubbox服务。

本章将从以下五个方面展开:

  • 创建资源栈
  • 编辑Inventory文件
  • 构建PlayBook
  • 执行PlayBook
  • 部署Dubbox服务

创建资源栈

创建资源栈的过程主要分为以下三步:

  • 定义ROS资源模板
  • 调用ROS API,创建资源栈
  • 获取资源栈输出信息

定义ROS资源模板

本文通过调用ROS API的方式来创建资源栈。
ROS资源模板包括以下几种资源类型:

在VPC网络环境下,给InstanceGroup指定VPC和VSwtich,并保证和Anisble主机处在同一个VPC和VSwitch下,这样才能保证Ansible主机可通过ECS的私有IP来登录ECS并操控ECS实例。

定义ROS资源模板的Python文件为generate_vpc_ros_template.py,文件内容如下:

from string import Template# define ros templatecreate_resources_with_parameters = '''{  "ROSTemplateFormatVersion": "2015-09-01",  "Resources": {    "InstanceGroupDubboxAdmin": {      "Type": "ALIYUN::ECS::InstanceGroup",      "Properties": {        "ImageId": "centos7u2_64_40G_cloudinit_20160520.raw",        "Password": "$ecs_password",        "MinAmount": 2,        "MaxAmount": 2,        "InstanceType": "$instance_type",        "ZoneId": "$zone_id",        "InternetChargeType": "PayByTraffic",        "NetworkType": "vpc",        "InstanceName": "ecs",        "VpcId": "$vpc_id",        "VSwitchId": "$vswitch_id"      }    },    "InstanceGroupZK": {      "Type": "ALIYUN::ECS::InstanceGroup",      "Properties": {        "ImageId": "centos7u2_64_40G_cloudinit_20160520.raw",        "Password": "$ecs_password",        "MinAmount": 3,        "MaxAmount": $instance_group_zk_size,        "InstanceType": "$instance_type",        "ZoneId": "$zone_id",        "InternetChargeType": "PayByTraffic",        "NetworkType": "vpc",        "InstanceName": "zk",        "VpcId": "$vpc_id",        "VSwitchId": "$vswitch_id"      }    },    "LoadBalance": {      "Properties": {        "AddressType": "internet",        "InternetChargeType": "paybytraffic",        "LoadBalancerName": "balancer"      },      "Type": "ALIYUN::SLB::LoadBalancer"    },    "Attachment": {      "Properties": {        "BackendServers": [          {            "ServerId": { "Fn::Select": ["0",{ "Fn::GetAtt": [ "InstanceGroupDubboxAdmin", "InstanceIds" ] }]},            "Weight": 100          },           {            "ServerId": { "Fn::Select": ["1",{ "Fn::GetAtt": [ "InstanceGroupDubboxAdmin", "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",              "URI": "$health_check_path"          },          "Scheduler": "wrr"      }    }  },  "Outputs": {    "EcsPrivateIps": {      "Value": { "Fn::GetAtt": [ "InstanceGroupDubboxAdmin", "PrivateIps"]}    },    "ZKPrivateIps": {      "Value": { "Fn::GetAtt": [ "InstanceGroupZK", "PrivateIps"]}    },    "LoadBalanceIp": {      "Value": {"Fn::GetAtt": [ "LoadBalance", "IpAddress"]}    }   }}'''# define func to generate ros templatedef generate_template(**kwargs):    template = Template(create_resources_with_parameters)    return template.substitute(kwargs)

调用ROS API,创建资源栈

创建资源栈的Python文件为create_stack.py,文件内容如下:

from aliyunsdkcore.client import AcsClientfrom aliyunsdkros.request.v20150901 import CreateStacksRequestimport generate_vpc_ros_templateimport jsonfrom config import *# define func to create stackdef create_stack(stack_name, ak_id, ak_secret, region_id, zk_size):    print('invoke CreateStackRequest to create instances...')    client = AcsClient(ak_id, ak_secret, region_id)    req = CreateStacksRequest.CreateStacksRequest()    req.set_headers({'x-acs-region-id': region_id})    #create vpc network resources    template = generate_vpc_ros_template.generate_template(vpc_id = vpc_id, ecs_password = ecs_password,         instance_type = instance_type, zone_id = zone_id, vswitch_id = vswitch_id,         instance_group_zk_size = zk_size, listen_port = 8080,         bachend_server_port =8080, health_check_path = '/dubbo-admin/favicon.ico')     create_stack_body = '''    {        "Name": "%s",        "TimeoutMins": %d,        "Template": %s    }    ''' % (stack_name, create_timeout, template)    req.set_content(create_stack_body)    # get response    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 Noneif __name__ == '__main__':    create_stack(stack_name, ak_id, ak_secret, region_id, zk_size)

下面详细解释该Python文件的执行过程:

  1. 初始化SDK客户端对象:

    client = AcsClient(ak_id, ak_secret, region_id)

  2. 初始化创建资源栈的请求:

    req = CreateStacksRequest.CreateStacksRequest()

  3. 指定请求资源Region:

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

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

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

  5. 发送请求,创建资源栈:

    response = client.get_response(req)

6. 获取资源栈信息:

 # 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 控制台查看资源栈详情。

获取资源栈输出信息

资源栈创建好以后,我们再次调用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'EcsPrivateIps', u'Description': u'No description given', u'OutputValue': [u'192.168.x.x', u'192.168.x.x']}, {u'OutputKey': u'ZKPrivateIps', u'Description': u'No description given', u'OutputValue': [u'192.168.x.x', u'192.168.x.x', u'192.168.x.x']}, {u'OutputKey': u'LoadBalanceIp', u'Description': u'No description given', u'OutputValue': u'112.74.x.x'}]

我们将以上输出定义为Outputs

编辑Inventory文件

根据上面获取的资源栈输出信息Outputs,获取Dubbox控制台服务器组的私有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

根据上面获取的资源栈输出信息Outputs,获取ZooKeeper集群服务器组的私有IP。方法如下:

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

因为在创建资源栈的时候,已经在配置文件中配置好了ALIYUN::ECS::InstanceGroup的登录密码,所以可以直接使用配置文件中的密码信息,用户名默认为root。编辑/etc/ansible/hosts文件,即通常我们所说的Ansible Inventory文件。编辑该文件的方法如下:

# define func to create inventorydef edit_hosts(host_parameters, zk_parameters):    print 'Start edit hosts'    host_str = ' ansible_ssh_port=%s ansible_ssh_user=%s ansible_ssh_pass=%s\n'    with open(hosts_file, 'wb') as file:        file.write( '[%s]\n' % host_dubbo_admin )        for index in range(len(host_parameters)):            file.write( ('%s'+host_str) % (host_parameters[index][0], host_parameters[index][1], host_parameters[index][2], host_parameters[index][3]) )        file.write( '[%s]\n' % host_zookeeper )        for index in range(len(zk_parameters)):            file.write( ('%s'+host_str) % (zk_parameters[index][0], zk_parameters[index][1], zk_parameters[index][2], zk_parameters[index][3]) )    print 'Edit hosts end'

执行该方法以后,生成的Inventory文件,即/etc/Ansible/hosts文件,内容如下:

[dubbo_admin]10.169.***.*** ansible_ssh_port=22 ansible_ssh_user=root ansible_ssh_pass=***10.44.***.*** ansible_ssh_port=22 ansible_ssh_user=root ansible_ssh_pass=***[zookeeper]10.45.***.*** ansible_ssh_port=22 ansible_ssh_user=root ansible_ssh_pass=***10.170.***.*** ansible_ssh_port=22 ansible_ssh_user=root ansible_ssh_pass=***10.170.***.*** ansible_ssh_port=22 ansible_ssh_user=root ansible_ssh_pass=***

Inventory文件中定义了两个远程主机组,dubbo_adminzookeeper,并指定了不同主机的IP、登录协议(默认是 SSH )、登录端口、登录用户名和密码。Ansible在执行PlayBook的时候通过以上信息连接远程主机。

构建PlayBook

本文所描述的高可用Dubbox服务,注册中心采用的是ZooKeeper集群,因此需要构建两个PlayBook。一个用来部署Dubbox控制台集群,一个用来部署ZooKeeper集群。

部署Dubbox控制台集群的PlayBook为vpc_dubbox_admin_playbook,结构如下:

[root@iZ94jwkjg0sZ roles]# ls -l vpc_dubbox_admin_playbook总用量 12drwxr-xr-x 2 501 games 4096 8月  17 17:52 filesdrwxr-xr-x 2 501 games 4096 8月  17 17:52 tasksdrwxr-xr-x 2 501 games 4096 8月  17 14:20 templates

关于vpc_dubbox_admin_playbook结构的说明如下:

  • files

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

    • 存放要执行的yml文件 install_dubbo_admin.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}}#注册中心连接地址字符串REGISTRY_ADDRESS {{registry_address}}#ZooKeeper集群的IP地址ZK_IPS_STR {{zk_ips_str}}
    • 配置文件config.j2中的参数值从vpc_dubbox_zk.yml文件中获取,通过Ansible执行PlayBook的过程中会在每一台远程主机的/root目录下生成config文件,提供给install_Jdk.shinstall_Jetty.shdeploy_Admin.sh这三个脚本使用。关于vpc_dubbox_zk.yml文件,后面会提到。

部署ZooKeeper集群的PlayBook为vpc_dubbox_zookeeper_playbook,结构如下:

[root@iZ94jwkjg0sZ roles]# ls -l vpc_dubbox_admin_playbook总用量 12drwxr-xr-x 2 501 games 4096 8月  17 17:52 filesdrwxr-xr-x 2 501 games 4096 8月  17 17:52 tasksdrwxr-xr-x 2 501 games 4096 8月  17 14:20 templates

关于vpc_dubbox_zookeeper_playbook结构的说明如下:

  • files

    • 存放install_Jdk.shinstall_ZK.sh两个文件,这两个文件分别用来安装JDK、部署ZooKeeper集群。
  • tasks

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

      ---- name: add config  template:    dest: /root/config    src: config.j2    mode: 0600- name: run install jdk  script: install_Jdk.sh- name: run install zookeeper  script: install_ZK.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}}#注册中心连接地址字符串REGISTRY_ADDRESS {{registry_address}}#ZooKeeper集群的IP地址ZK_IPS_STR {{zk_ips_str}}
    • 配置文件config.j2中的参数值从vpc_dubbox_zk.yml文件中获取,通过Ansible执行PlayBook的过程中会在每一台远程主机的/root目录下生成config文件,提供给install_Jdk.shinstall_ZK.sh使用。关于vpc_dubbox_zk.yml文件,后面会说明。

这两个PlayBook构建好了以后,可上传到阿里云OSS。在执行脚本的时候需要用到这两个PlayBook,可以通过wget命令从阿里云OSS上下载,然后直接使用。

执行PlayBook

执行PlayBook,需要以下三个步骤:

  • 生成Ansible可执行文件
  • 下载PlayBook
  • 执行PlayBook

生成Ansible可执行文件

我们需要通过Ansible执行vpc_dubbox_zk.yml文件来运行我们构建好的两个PlayBook。
vpc_dubbox_zk.yml文件的生成,需要分两步进行:

  • 定义文件模板
  • 生成文件

定义vpc_dubbox_zk.yml文件模板的Python文件为deploy_vpc_dubbox.py,文件内容如下:

from string import Templatecreate_hosts_with_parameters = '''- name: deploy dubbox service  hosts: $zookeeper  vars:    - jetty_home: $jetty_home    - enforce: $jetty_home_enforce    - dubbo_root: $dubbo_root_password    - dubbo_guest: $dubbo_guest_password    - registry_address: $registry_address     - zk_ips_str: $zk_ips_str  roles:    - $zookeeper_pb_name- name: deploy dubbox service  hosts: $dubbo_admin  vars:    - jetty_home: $jetty_home    - enforce: $jetty_home_enforce    - dubbo_root: $dubbo_root_password    - dubbo_guest: $dubbo_guest_password    - registry_address: $registry_address     - zk_ips_str: $zk_ips_str  roles:    - $dubbo_admin_pb_name'''#define func to define redis playbook templatedef create_playbook(**kwargs):    template = Template(create_hosts_with_parameters)    return template.substitute(kwargs)

生成vpc_dubbox_zk.yml文件,方法如下:

# define func to create playbook init configdef create_pb_init(registry_address, zk_ips_str):    print('Start to edit playbook init config...')    with open(ansible_dir + pb_file_name, 'wb') as file_pb:        playbook = generate_playbook_template.create_playbook(dubbo_admin=host_dubbo_admin, zookeeper=host_zookeeper, jetty_home=jetty_home,             jetty_home_enforce=jetty_home_enforce, dubbo_root_password=dubbo_root_password, dubbo_guest_password=dubbo_guest_password,            registry_address=registry_address, zk_ips_str=zk_ips_str, dubbo_admin_pb_name=dubbo_admin_pb_name, zookeeper_pb_name=zookeeper_pb_name)        file_pb.write(playbook)    print('Editting pb_init is finished.')

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

- name: deploy dubbox service  hosts: zookeeper  vars:    - jetty_home: /opt/jetty    - enforce: f    - dubbo_root: ***    - dubbo_guest: ***    - registry_address: 192.168.***.***:2181?backup=192.168.***.***:2181,192.168.***.***:2181    - zk_ips_str: 192.168.***.***,192.168.***.***,192.168.***.***  roles:    - vpc_dubbox_zookeeper_playbook- name: deploy dubbox service  hosts: dubbo_admin  vars:    - jetty_home: /opt/jetty    - enforce: f    - dubbo_root: root    - dubbo_guest: guest    - registry_address: 192.168.***.***:2181?backup=192.168.***.***:2181,192.168.***.***:2181    - zk_ips_str: 192.168.***.***,192.168.***.***,192.168.***.***  roles:    - vpc_dubbox_admin_playbook

vpc_dubbox_zk.yml文件中定义了一些参数,下面详细介绍文件中参数的作用:

  • hosts

    • 远程主机组名称,和Inventory文件中的远程主机组相对应。此文件说明要连接的主机组有两个。
  • vars

    • 配置文件config中的参数,提供给install_Jdk.shinstall_Jetty.shdeploy_Admin.shinstall_ZK.sh使用。
  • roles

    • 指定要执行的PlayBook名称。此文件说明要执行的PlayBook有两个。

最终生成的vpc_dubbox_zk.yml文件在目录/etc/ansible/下。

下载PlayBook

前面的章节构建PlayBook中提出,构建好PlayBook后会传到阿里云OSS上,然后通过wget命令下载到/etc/ansible/roles目录下。下载PlayBook的方法如下:

def download_playbook():    for url in playbook_url:        file_name = url.split('/')[-1]        command_wget = 'wget -P ' + playbook_dir + ' ' + url        subprocess.call(command_wget, shell = True)        command_tar = 'tar zxf ' + playbook_dir + file_name + ' -C ' + playbook_dir        subprocess.call(command_tar, shell = True)        command_rm = 'rm -rf ' + playbook_dir + file_name        subprocess.call(command_rm, shell = True)

执行playbook

Ansible是通过ssh命令连接远程主机,并执行playbook的。首次执行playbook前,由于当前Ansible主机并没有记录远端主机的RSA Key,会弹出RSA Key的确认对话框,对话框内容如下:

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 ansibledef confirm_ssh_login(all_ips):    print('Start to confirm ssh login to all nodes...')    if len(all_ips) == 0:        print('Host_ips is empty')        return    for ip in all_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无法下载安装包,但是在安装JDK、Jetty以及部署Dubbox Admin和Zookeeper集群的时候需要这些安装包。因为Ansible主机配有公网IP,所以可以将安装包先下载到Ansible主机,又因为Ansible主机和每台ECS在同一个VSwitch下面,可以通过scp命令将安装包从Ansible主机传到每台ECS上。具体操作过程如下:

  1. 首先将JDK、Jetty、Dubbox Admin以及ZooKeeper安装包上传至阿里云OSS,获取安装包的下载地址。
  2. 在Python文件中填入安装包的下载地址,运行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)    subprocess.call('wget -P ' + wget_file_path + ' ' + zookeeper_path, shell = True)
  3. 通过scp命令将安装包拷贝到每台ECS。方法如下:

    def scp_files_from_ansible_host_to_ecs_zk(host_ips, zk_ips):    scp_password_info = 'root@%s\'s password:'     scp_command_ecs = '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'    scp_command_zk = 'scp ' + wget_file_path + 'jdk-8u101-linux-x64.rpm ' + wget_file_path + 'zookeeper-3.4.6.tar.gz root@%s:/root'    if host_ips is None or len(host_ips) == 0:        print 'Host ips is None,exit!'        return None    if zk_ips is None or len(zk_ips) == 0:        print 'ZK ips is None,exit!'        return None    for ip in host_ips:        scp = pexpect.spawn(scp_command_ecs % 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!'    for ip in zk_ips:        scp = pexpect.spawn(scp_command_zk % 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!'
  4. 通过Ansible,执行PlayBook。方法如下:

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

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

部署Dubbox服务

Dubbox服务的部署,需要以下两个步骤:

  • 搭建ZooKeeper集群
  • 搭建Dubbox控制台集群

搭建ZooKeeper集群

ZooKeeper集群主要用来作为Dubbox服务的注册中心。

搭建ZooKeeper集群,需要以下两个步骤:

  • 安装JDK
  • 搭建ZooKeeper集群
安装JDK

因为ZooKeeper的运行需要Java环境,所以需要先安装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.logelse        #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.rpmfi#检查java环境是否安装成功if which java 2>/dev/null; then        echo $(eval $DATE) " install jdk8 success" >> ~/install_dubbox.logelse        echo $(eval $DATE) " install jdk8 failed" >> ~/install_dubbox.log

安装JDK过程中,首先需要检查当前ECS是否存在Java环境,存在就跳过安装过程,反之则安装。
声明:JDK安装包请从Oracle官网下载,并下载本文指定的JDK版本(jdk-8u101-linux-x64.rpm)。从本文链接中下载JDK带来的任何法律问题,和本文作者无关。

搭建ZooKeeper集群

搭建ZooKeeper集群的脚本为install_ZK.sh,内容如下:

#!/bin/sh#日志时间格式DATE="date +'%Y-%m-%d %H:%M:%S'"#download zk#wget 'http://dubbo.oss-cn-shenzhen.aliyuncs.com/zookeeper-3.4.6.tar.gz'#解压zk到/opt目录tar -zxvf zookeeper-3.4.6.tar.gz -C /opt#设置软链接ln -s /opt/zookeeper-3.4.6 /opt/zookeeper#复制zoo.cfg文件cp /opt/zookeeper/conf/zoo_sample.cfg /opt/zookeeper/conf/zoo.cfg#删除压缩包rm -rf zookeeper-3.4.6.tar.gz#读取配置文件内容,给变量赋初值while read linedo    #过滤掉注释行    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    fidone < ~/configmkdir -p '/opt/zookeeper/data'#修改配置文件sed -i -e 's/^dataDir=.*/dataDir=\/opt\/zookeeper\/data/' /opt/zookeeper/conf/zoo.cfgZK_IPS_STR=${ZK_IPS_STR//,/ }init_id=1#获取本机ipSELF_IP=`ifconfig -a|grep inet|grep -v 127.0.0.1|grep -v inet6|awk '{print $2}'|tr -d "addr:"`for ip in $ZK_IPS_STRdo    echo "server.$init_id=$ip:2888:3888" >> /opt/zookeeper/conf/zoo.cfg    #创建myid文件,标识本机id号    if [[ $SELF_IP == *$ip* ]]; then        echo $init_id >  "/opt/zookeeper/data/myid"    fi    init_id=$((init_id+1))  done#启动zknohup /opt/zookeeper/bin/zkServer.sh start &sleep 10#centos 7 关闭防火墙systemctl stop firewalld.service#查看当前是否存在zookeeper进程ZK_ID=`ps -ef |grep zookeeper |grep -v grep |awk '{print $2}'`if [ ! "$ZK_ID" ]; then    echo $(eval $DATE) " start zookeeper failed" >> ~/install_dubbox.error.logelse    echo $(eval $DATE) " start zookeeper success" >> ~/install_dubbox.logfi#删除配置文件rm -rf ~/config

这个脚本的主要功能是安装ZooKeeper,并通过修改ZooKeeper配置文件conf/zoo.cfg来配置ZooKeeper集群。修改后的配置文件内容如下:

tickTime=2000  dataDir=/opt/zookeeper/data  clientPort=2181  initLimit=5  syncLimit=2  server.1=slave-01:2888:3888  server.2=slave-02:2888:3888  server.3=slave-03:2888:3888  

上述文件,配置了三台机器的ZooKeeper集群。在dataDir目录下,创建一个myid文件,里面内容为一个数字,用来标识当前主机号。
更多关于ZooKeeper集群配置的知识,可参考Running Replicated ZooKeeper

搭建Dubbox控制台集群

由于Dubbox控制台需要运行在Jetty容器上,Jetty容器的运行又需要有Java环境,因此部署Dubbox控制台之前,需要先安装JDK和Jetty。

搭建Dubbox控制台集群,需要以下三个步骤:

  • 安装JDK
  • 安装Jetty
  • 部署Dubbox控制台
安装JDK

安装JDK的文件为install_Jdk.sh,和上述过程安装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.gzecho $(eval $DATE) " tar zxf jetty success" >> ~/install_dubbox.log#删除压缩包rm -f jetty-distribution-8.1.19.v20160209.tar.gzecho $(eval $DATE) " rm jetty.tgz success" >> ~/install_dubbox.log#读取配置文件内容,给变量赋初值while read linedo        #过滤掉注释行        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        fidone < ~/config#JETTY_HOME未配置,选择默认配置JETTY_HOME_DEFAULTif [ ! -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.logfi#移动jetty到JETTY_HOMEmv jetty-distribution-8.1.19.v20160209/* $JETTY_HOMErm -rf jetty-distribution-8.1.19.v20160209echo $(eval $DATE) " mv jetty  success" >> ~/install_dubbox.log#将jetty配置文件/etc/webdefault.xml中的属性dirAllowed值设置为falsecd $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.xmlecho $(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.logelse        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.logfi#查看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.logelse     echo $(eval $DATE) " install jetty success" >> ~/install_dubbox.log     kill -9 $JETTY_PROCESS_ID     echo $(eval $DATE) " stop jetty" >> ~/install_dubbox.logfi

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

  • 未指定Jetty安装目录,则选择默认目录进行安装
  • 指定了Jetty安装目录但是目录不存在,则创建目录并安装Jetty
  • 指定了Jetty安装目录并且目录已经存在
    • 如果配置了强制执行选项,则覆盖目录并安装Jetty
    • 如果没有配置强制执行选项,程序强制退出
部署Dubbox控制台

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

#!/bin/sh#默认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 linedo    #过滤掉注释行    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    fidone < ~/config#JETTY_HOME未配置,选择默认配置JETTY_HOME_DEFAULTif [ ! -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    fifi#检测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_DEFAULTfi#检测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_DEFAULTfi#从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.warecho $(eval $DATE) " mv dubbo-admin.war to webapps" >> ~/install_dubbox.log#修改配置文件mkdir $JETTY_HOME/webapps/dubbo-admincd $JETTY_HOME/webapps/dubbo-adminjar xf ../dubbo-admin.warcd $JETTY_HOME/webapps/dubbo-admin/WEB-INF#配置admin注册监听文件sed -i -e "s/^dubbo.registry.address.*/dubbo.registry.address=zookeeper:\/\/$REGISTRY_ADDRESS/" dubbo.propertiesecho $(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.propertiesecho $(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.propertiesecho $(eval $DATE) " set user guest passwd" >> ~/install_dubbox.logcd $JETTY_HOME/webapps/dubbo-adminjar cf dubbo-admin.war *mv dubbo-admin.war $JETTY_HOME/webapps/rm -rf $JETTY_HOME/webapps/dubbo-admin#启动jettynohup $JETTY_HOME/bin/jetty.sh start &echo $(eval $DATE) " start jetty" >> ~/install_dubbox.log#关闭centos7的防火墙systemctl stop firewalld.servicesleep 30CODE=`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.logif [ $CODE = 200 ]; then    echo $(eval $DATE) " admin控制台启动成功" >> ~/install_dubbox.logelse    echo $(eval $DATE) " admin控制台启动失败" >> ~/install_dubbox.error.logfirm -rf ~/config

部署Dubbox服务控制台的过程,主要包括:先将Dubbox服务部署到Jetty上,然后修改dubbo.properties文件的方式来设置Dubbox服务注册中心为ZooKeeper集群的方式,并设置Dubbox服务控制台的登录密码。Dubbox服务注册中心的选择,可参考Dubbo用户指南

在部署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 &

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

http://ip:8080/dubbo-admin

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

Dubbox服务部署好以后,可通过以下操作登录控制台:

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

登录进去以后的Dubbox控制台界面如下:

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

总结

本章将从以下两个方面进行总结:

  • Dubbox服务系统结构图
  • 如何快速构建高可用Dubbox服务

Dubbox服务系统结构图

最终,采用ZooKeeper集群作为注册中心,基于资源编排快速部署出来的高可用Dubbox服务的系统结构图,如下图所示:

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

  • 注册中心的高可用

    • 注册中心采用了ZooKeeper集群的方式,ZooKeeper集群中只要有超过半数的服务可用,Dubbox服务的注册中心就可以正常工作。
  • Dubbox服务控制台的高可用

    • 创建两台ECS实例并分别部署Dubbox控制台服务,这两台ECS挂载到一个SLB上,我们可通过SLB来访问Dubbox控制台服务。

注意:生产环境中应该将SLB放在VPC网络环境内,本文中为了测试方便,把SLB放在VPC网络环境外。若需要修改SLB的网络环境,只需修改ROS资源模板

如何快速构建高可用Dubbox服务

前面章节描述的部署过程看起来可能比较繁琐,本文的核心是快速部署,因此你可以根据下面的指导,快速部署属于你的高可用Dubbox服务。四个步骤快速部署高可用Dubbox服务:

  • 准备Ansible主机
  • 下载源码
  • 修改配置文件
  • 运行main函数
准备Ansible主机

这个过程和前面章节的准备Ansible主机相同,这里不再重复。

下载源码

可从本文的附件中下载源码,然后将vpc_python文件拷贝到Ansible主机。

修改配置文件

修改vpc_python/config.py文件,文件内容如下:

#define stack namestack_name = 'vpc_dubbox_zookeeper'# define stack creation timeout(minutes)create_timeout = 60#vpc parametervpc_id = '******'vswitch_id = '******'#zookeeper cluster sizezk_size = 3#ecsgroup parametersecs_password = '******'instance_type =  'ecs.s2.large'#ros ak idak_id = '******'#ros ak secretak_secret = '******'#ros region idregion_id = 'cn-shenzhen'#zone idzone_id = 'cn-shenzhen-a'#set jetty pathjetty_home = '/opt/jetty'#if jetty_home exists, choose f to overlapjetty_home_enforce = 'f'#dubbo admin root passworddubbo_root_password = '******'#dubbo admin guest passworddubbo_guest_password = '******'

下面详细讲解配置文件中一些参数所代表的意义:

  • vpc_id

    • Ansible主机所在的VPC ID
  • vswitch_id

    • Ansible主机所在的VSwitch ID
  • zk_size

    • ZooKeeper集群的大小,ZooKeeper集群的大小必须为奇数,且必须大于1
  • ecs_password

    • 申请的ECS服务器的登录密码,用户名默认为root
  • ak_id

    • 用户的ak id
  • ak_secret

    • 用户的ak secret
  • region_id

    • 资源栈创建的区域
  • jetty_home

    • jetty默认安装目录
  • jetty_home_enforce

    • 是否强制安装jetty,f代表强制安装,其它代表非强制
  • dubbo_root_password

    • Dubbox控制台root用户的登录密码
  • dubbo_guest_password

    • Dubbox控制台guest用户的登录密码

用户可根据自己的需求更改配置文件。

运行main函数

运行vpc_python/main.py。函数运行完以后,高可用Dubbox服务就部署好了。

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
Dubbox:来自当当网的SOA服务框架
dubbox
dubbo与zookeeper的关系
Dubbo安装部署
dubbo各种管理和监管
zookeeper在dubbo中扮演什么角色
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服