本文将介绍在专有网络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 hosts
drwxr-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资源模板包括以下几种资源类型:
- "Type": "ALIYUN::ECS::InstanceGroup"
- 在资源模板中使用两组该类型的资源,一组用来定义Dubbox控制台集群服务器,一组用来定义ZooKeeper集群服务器。
- "Type": "ALIYUN::SLB::LoadBalancer"
- 创建一台SLB。
- "Type": "ALIYUN::SLB::BackendServerAttachment"
- 给SLB添加后端服务器。
- "Type": "ALIYUN::SLB::Listener"
- 配置SLB监听,SLB监听Dubbox控制台集群服务器的8080端口,并配置了健康检查
HealthCheck
。
- 配置SLB监听,SLB监听Dubbox控制台集群服务器的8080端口,并配置了健康检查
- 资源栈的输出为
"Outputs"
,包括:EcsPrivateIps(Dubbox控制台集群服务器的私有IP),ZKPrivateIps(ZooKeeper集群服务器的私有IP),LoadBalanceIp(负载均衡SLB的公网IP)。
在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 template
create_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 template
def generate_template(**kwargs):
template = Template(create_resources_with_parameters)
return template.substitute(kwargs)
调用ROS API,创建资源栈
创建资源栈的Python文件为create_stack.py
,文件内容如下:
from aliyunsdkcore.client import AcsClient
from aliyunsdkros.request.v20150901 import CreateStacksRequest
import generate_vpc_ros_template
import json
from config import *
# define func to create stack
def 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 None
if name == '__main__':
create_stack(stack_name, ak_id, ak_secret, region_id, zk_size)
下面详细解释该Python文件的执行过程:
- 初始化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) - 发送请求,创建资源栈:
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
请求成功会返回资源栈的Id
和Name
信息,请求发出以后,可到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
调用时需要传入创建好的资源栈Id
和Name
信息。由于创建资源栈需要的时间不确定,所以以上方法定义了超时重试的机制。每次重试以后的等待时间要比上次等待时间少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 inventory
def 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_admin
和zookeeper
,并指定了不同主机的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
总用量 12
drwxr-xr-x 2 501 games 4096 8月 17 17:52 files
drwxr-xr-x 2 501 games 4096 8月 17 17:52 tasks
drwxr-xr-x 2 501 games 4096 8月 17 14:20 templates
关于vpc_dubbox_admin_playbook
结构的说明如下:
- files
- 存放
install_Jdk.sh
、install_Jetty.sh
、deploy_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
- 存放要执行的yml文件
- 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.sh
、install_Jetty.sh
、deploy_Admin.sh
这三个脚本使用。关于vpc_dubbox_zk.yml
文件,后面会提到。
- 存放配置文件模板config.j2,文件内容如下:
部署ZooKeeper集群的PlayBook为vpc_dubbox_zookeeper_playbook
,结构如下:
[root@iZ94jwkjg0sZ roles]# ls -l vpc_dubbox_admin_playbook
总用量 12
drwxr-xr-x 2 501 games 4096 8月 17 17:52 files
drwxr-xr-x 2 501 games 4096 8月 17 17:52 tasks
drwxr-xr-x 2 501 games 4096 8月 17 14:20 templates
关于vpc_dubbox_zookeeper_playbook
结构的说明如下:
- files
- 存放
install_Jdk.sh
、install_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
- 存放要执行的yml文件
- 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.sh
、install_ZK.sh
使用。关于vpc_dubbox_zk.yml
文件,后面会说明。
- 存放配置文件模板config.j2,文件内容如下:
这两个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 Template
create_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 template
def create_playbook(**kwargs):
template = Template(create_hosts_with_parameters)
return template.substitute(kwargs)
生成vpc_dubbox_zk.yml
文件,方法如下:
# define func to create playbook init config
def 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.sh
、install_Jetty.sh
、deploy_Admin.sh
、install_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 ansible
def 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上。具体操作过程如下:
- 首先将JDK、Jetty、Dubbox Admin以及ZooKeeper安装包上传至阿里云OSS,获取安装包的下载地址。
- 在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)
- 通过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!'
- 通过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.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安装包请从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 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
mkdir -p '/opt/zookeeper/data'
#修改配置文件
sed -i -e 's/^dataDir=.*/dataDir=\/opt\/zookeeper\/data/' /opt/zookeeper/conf/zoo.cfg
ZK_IPS_STR=${ZK_IPS_STR//,/ }
init_id=1
#获取本机ip
SELF_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_STR
do
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
#启动zk
nohup /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.log
else
echo $(eval $DATE) " start zookeeper success" >> ~/install_dubbox.log
fi
#删除配置文件
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.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的配置文件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 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
#检测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=zookeeper:\/\/$REGISTRY_ADDRESS/" 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上,然后修改dubbo.properties
文件的方式来设置Dubbox服务注册中心为ZooKeeper集群的方式,并设置Dubbox服务控制台的登录密码。Dubbox服务注册中心的选择,可参考Dubbo用户指南。
在部署Dubbox服务的过程中,有几个需要注意的问题:
- 当访问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 name
stack_name = 'vpc_dubbox_zookeeper'
# define stack creation timeout(minutes)
create_timeout = 60
#vpc parameter
vpc_id = ''
vswitch_id = ''
#zookeeper cluster size
zk_size = 3
#ecsgroup parameters
ecs_password = ''
instance_type = 'ecs.s2.large'
#ros ak id
ak_id = ''
#ros ak secret
ak_secret = ''
#ros region id
region_id = 'cn-shenzhen'
#zone id
zone_id = 'cn-shenzhen-a'
#set jetty path
jetty_home = '/opt/jetty'
#if jetty_home exists, choose f to overlap
jetty_home_enforce = 'f'
#dubbo admin root password
dubbo_root_password = ''
#dubbo admin guest password
dubbo_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服务就部署好了。