基于资源编排和 Ansible 在 VPC 下快速交付应用-阿里云开发者社区

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

基于资源编排和 Ansible 在 VPC 下快速交付应用

简介: 阿里云资源编排服务(ROS)为我们快速搭建和整合云计算资源环境提供一个低成本、标准化的方案。基于ROS提供的能力,我们所要做的就是将所需的资源以资源模板的形式进行定义,进而实现对所定义云资源的快速生产。除此之外,ROS 将应用交付和资源释放的过程也进行了简化。 用户基于 ROS 交付应用可以有两种

阿里云资源编排服务(ROS)为我们快速搭建和整合云计算资源环境提供一个低成本、标准化的方案。基于ROS提供的能力,我们所要做的就是将所需的资源以资源模板的形式进行定义,进而实现对所定义云资源的快速生产。除此之外,ROS 将应用交付和资源释放的过程也进行了简化。

用户基于 ROS 交付应用可以有两种途径:一种是通过 ROS 搭建资源环境,然后登陆云主机 ECS,手动实现对应用的部署和维护操作;另一种是利用文章基于资源编排一键交付应用中提到的 Cloud-Init 机制,实现对应用的一键交付。但是,不管利用哪种方式,它们都将面临同一个问题:随着 ECS 数量的增多和应用部署复杂度的增大,应用部署和维护的成本会相应的增大。那么如何在降低构建应用操作成本的同时,实现对应用的快速交付呢?这里就要使用自动化运维管理工具 Ansible

可以说,ROS 帮助我们快速搭建了资源环境,Ansible 可帮助我们在已搭建好的资源环境中实现了应用的快速交付。

本文将介绍利用 ROS 和 Ansible 实现对应用的快速交付。为了更好的理解应用交付的整个实现过程,本文首先对 Ansible 及其运行机制进行简单的介绍,然后讲述基于 ROS 和 Ansible 快速交付应用的过程,最后以搭建 Redis 集群作为示例,展示整个应用交付过程,本文大致流程如下:

  • Ansible 及其运行机制
  • 基于 ROS 和 Ansible 快速交付应用
  • 基于 ROS 和 Ansible 快速搭建 Redis 集群

Ansible 及其运行机制

Ansible 是一个基于 Python paramiko 开发的、分布式的、需客户端的、轻量级的自动化运维管理工具,它可以实现自动化部署应用、配置、执行 task 等工作。面对多台服务器的运维工作时,它大大简化了运维人员的工作量,降低了应用的部署和交付成本。

Ansible 使用 YMAL 语法及 Jinja2 模板语言,采用 paramiko 协议库,通过 SSH 或者 ZeroMQ 等方式连接工作主机,进而实现对远程主机的控制和维护。Ansible 一般每两个月出一个发行版本。

Ansible的运行机制:管理节点 Master 利用存放节点信息的 Inventory 文件将 Ansible 模块通过 SSH 协议(或者 Kerberos、LDAP)推送到被管理节点,然后执行 Playbooks,执行结束后自动删除,如下图所示:
ansible_work
由上图可知,Ansible主要有五部分组成:

  • Master:Ansible 的控制节点核心
  • Inventory:定义 Ansible 管理主机的清单
  • Playbooks:定义 Ansible 执行任务文件及其配置文件
  • Modules:包括 Ansible 自带的核心模块及自定义模块
  • Plugins:完成模块功能的补充

基于 ROS 和 Ansible 快速交付应用

在了解了 Ansible 的运行机制后,接下来将介绍利用 ROS 快速生产应用运行环境,并结合 Ansible 实现对应用的快速交付,大致流程如下:
ros_and_ansible

  • 安装 ROS SDK 和 Ansible
  • 基于 ROS 创建资源栈,搭建资源环境
  • 获取主机信息,并创建 Inventory 文件,获取 Playbook
  • 执行 Playbook,完成应用的快速交付基于 Ansible 快速部署应用

值得注意的是,本文介绍的快速交付应用是在 VPC 环境下进行的,并且过是以 python 的形式执行的,所以在开始之前,需要有一台可以访问公网的 ECS 作为 Master,以执行所有的 python 程序。下面详细介绍基于 ROS 和 Ansible 快速交付应用的流程。

安装 ROS SDK 和 Ansible

本文中介绍的创建资源栈是以 python 调用 ROS 开放 API 实现的,所以首先需要在 Master 上安装 ROS SDK,具体的安装过程可参考阿里云资源编排服务Python SDK使用入门

对应用的部署是通过 Ansible 完成的,所以在 Master 上要安装 Ansible。为了避免不必要的麻烦,建议安装 Ansible 的最新版本,具体安装过程可参考 Ansible 安装文档

基于 ROS 创建资源栈,搭建资源环境

在安装完开发环境后,将开始基于 ROS 搭建资源环境。首先根据需求,定义所要创建资源的模板,并将模板添加到 python 文件中,然后 python 以该模板作为参数调用 ROS API,进而实现对资源的创建,具体的调用方式可参考阿里云资源编排服务Python SDK使用入门或者本文第三部分的示例应用。

创建 Inventory,获取 Playbook

根据 Ansible 的运行机制,在搭建好资源环境后,首先需要根据资源栈的创建结果,再次调用 ROS API 以获取云主机资源的登录信息,如:主机 IP、主机登录密码等。然后利用主机信息生成 Inventory 文件,以作为 Ansible 管理主机的清单。在 Inventory 文件中,你可以定义登录云主机的方式,如下是一个示例 Inventory 文件:

[webservers]
192.168.xx.xx ansible_ssh_port=22 ansible_ssh_user=root ansible_ssh_pass=abc123
192.168.xx.xx ansible_ssh_port=22 ansible_ssh_user=root ansible_ssh_pass=abc123

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

最后,根据所要部署的应用,可在 Ansible 社区中查询并获取应用相关的 playbook。当然,除了这种获取方式,您还可以根据应用部署的过程,依据 YMAL 语法和 Jinja2 模板语言编写自己的playbook。

执行 Playbook,快速部署应用

Ansible 在运行 Playbook 时需要指定执行文件,执行文件可根据 Inventory 文件和 Playbook 进行编写。执行文件中需要指定所要运行的 Playbook 名称和远程主机或者远程主机组名,如下是一个执行文件 example.yml,文件中显示了对远程组webservers 执行名称为 example 的 Playbook:

---
- hosts: webservers
    roles: 
      - Example

执行文件编写成功后,运行以下命令即可完成对 Playbook 的执行:

ansible-playbook example.yml  

基于 ROS 和 Ansible 快速搭建 Redis 集群

为了更好的说明基于 ROS 和 Ansible 快速交付应用的过程,本文将以搭建 Redis 集群作为展示示例。

目前我已经构建好了我的 VPC 环境,我的 Master 上已经安装了 ROS SDK 和 Ansible,下面我将展示基于 ROS SDK 和 Ansible 快速搭建一主(Master)两备(Slave) Redis 集群的详细过程。

为了提高 Redis 集群的高可用性,在搭建 Redis 集群时为其部署了高可用性解决方案 Sentinel,以帮助 Redis 集群实现自动化的主备切换。

构建资源环境

首先定义资源栈模板。由于我已经构建好了我的 VPC 环境和控制节点 Master,所以模板中只需要定义构建三台云主机 ECS 资源并与已有的 VPC 环境相关联即可。为了方便起见,我在模板中使用资源 "Type": "ALIYUN::ECS::InstanceGroup" 定义 ECS 资源。定义资源栈的模板可详见附件中的 python 文件 create_instancegroup_template.py。

模板定义成功后,编辑 python 代码,以模板作为参数,调用 ROS API 即可实现对资源环境的搭建,如下是调用 ROS API 的 python 文件 create_stack_instancegroup.py:

# invoke CreateStackRequest to create InstanceGroup stack

from aliyunsdkcore.client import AcsClient
from aliyunsdkros.request.v20150901 import CreateStacksRequest
import create_instancegroup_template
import json

# define stack creation timeout(minutes)
create_timeout = 60

# define func to create stack
def create_stack(stack_name, ak_id, ak_secret, region_id):
    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})

    template = create_instancegroup_template.generate_template(io_optimized='optimized', network_type='vpc', image_id='centos6u5_64_40G_cloudinit_20160427.raw', security_group_id='sg-94qyqvpvj', instance_password='********', instance_min_amount=3, system_disk_category='cloud_ssd', instance_max_amount=3, vswitch_id='vsw-sm9i9vhin', vpc_id='vpc-a6al8acn4', instance_type='ecs.n1.small')

    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]:
        parameters = json.loads(response[-1])
        print('Create stack succeccfully!!!')
        return parameters
    else:
        print('Unexpected errors: status=%d, error=%s' % (response[0], response[-1]))
        return None

调用成功后,登录 ROS 控制台即可看到正在创建的资源栈。

创建 Inventory,获取 Playbook

接下来创建 Inventory 文件。为了省去手动编写 Inventory 文件的麻烦,本文使用 python 程序自动生成。首先在栈资源创建成功后,再次调用 ROS API 获取资源 ECS 的登录信息,由于资源栈的创建时间一般需要2~5分钟(视具体情况而定),所以在程序中需设置一定的等待时间,如下所示:

# define func to get host ip
def get_host_ips(stack, ak_id, ak_secret, region_id):
    # get host ip
    print('Start to get host ips...')

    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
            private_ips = resources['Outputs'][0]['OutputValue']
            print('Getting host ips finished. ips: ', private_ips)
            return private_ips
        else:
            print('Unexpected errors: status=%d, error=%s' % (response[0], response[-1]))
            return None
    print('Getting host ips timeout.')
    return None

获取云主机 IP 后,即可开始生成 Inventory 文件,由于远程主机的登录密码在创建资源栈时指定的,所以此处可直接引用,如下所示:

# define func to create inventory
def edit_hosts(remote_ips, remote_ports, remote_users, remote_pass, is_sentinel):
if remote_ips is None or 0>=len(remote_ips):
    print('Error! Remote ips is empty!')
    return
else:
    if remote_ports is None or len(remote_ips)!=len(remote_ports):
        print('Error! Remote ports is empty!')

    if remote_users is None or len(remote_ips)!=len(remote_users):
        print('Error! Remote users is empty!')

    if remote_pass is None or len(remote_ips)!=len(remote_pass):
        print('Error! Remote pass is empty!')

    host_str = ' ansible_ssh_port=%s ansible_ssh_user=%s ansible_ssh_pass=%s\n'
    file = open(hosts_dir + '/' + hosts_file, 'wb')
    file.write( '[%s]\n' % host_master )
    file.write( ('%s'+host_str) % (remote_ips[0], remote_ports[0], remote_users[0], remote_pass[0]) )
    if len(remote_ips)>1:
        file.write( '\n[%s]\n' % host_slave )
        for index in range(1,len(remote_ips)):
            file.write( ('%s'+host_str) % (remote_ips[index], remote_ports[index], remote_users[index], remote_pass[index]) )
    if is_sentinel:
        file.write( '\n[%s]\n' % host_sentinel )
        for index2 in range(0,len(remote_ips)):
            file.write( ('%s redis_sentinel=True'+host_str) % (remote_ips[index2], remote_ports[index2], remote_users[index2], remote_pass[index2]) )
    file.close()
print 'End'

文件生成后,开始获取搭建 Redis 集群的 playbook,本文使用的是 DavidWittman 编写的 playbook:DavidWittman.redis,直接调用 ansible 的命令行工具 ansible-galaxy 即可将 playbook 直接下载到 Ansible 的 roles 目录下(当然也可以使用其他下载方式,但下载后要将其放在 roles 目录下)。由于命令 ansible-galaxy 使用的是 https 通信协议,所以在调用该命令前应该确认当前主机以安装 openssl,如下所示:

# Install openssl before execute ansible-galaxy
print('Install openssl ...')
subprocess.call('yum install openssl -y', shell=True)

# install playbook
print('Download playbook %s...' % pb_name)
subprocess.call('ansible-galaxy install ' + pb_name + ' -vvv', shell=True)

搭建 Redis 集群的 playbook DavidWittman.redis 获取成功后,由于 DavidWittman.redis 编写完成时默认只部署了 Sentinel,没有实现 Redis 集群的主备配置,所以在运行 DavidWittman.redis 前,需要对 DavidWittman.redis 的 /tasks/main.yml 进行修改,使其完成分布式 Redis 集群的搭建,如下所示:


# define func to mod playbook
def mod_playbook():
    file = open(ansible_dir + '/roles/' + pb_name + '/tasks/main.yml', 'rb+')
    lines=file.readlines()
    for row in range(0, len(lines)):
        if lines[row].find('when: not redis_sentinel')>=0:
           lines[row] = '#' + lines[row]
           break
    file = open(ansible_dir + '/roles/' + pb_name + '/tasks/main.yml', 'wb+')
    file.writelines(lines)

执行 Playbook,快速部署应用

在执行 playbook 前需要编写 playbook 的执行文件。和 Inventory 文件一样,执行文件可根据下载的 playbook 利用程序自动生成,如下所示:

# define func to create playbook init config
def create_pb_init(host_ip, has_slaves, is_sentinel):
# config master
file_pb = open(pb_file_dir + '/' + pb_file_name, 'wb')
file_pb.write(redis_playbook_template.create_playbook(template='master', host_master_name=host_master, host_slave_name=host_slave, host_sentinel_name=host_sentinel, playbook_name=pb_name, master_ip=host_ip, master_port='6379', master_name='master01'))
file_pb.close()

# config slaves
if has_slaves:
    file_pb = open(pb_file_dir + '/' + pb_file_name, 'ab')
    file_pb.write(redis_playbook_template.create_playbook(template='slaves', host_master_name=host_master, host_slave_name=host_slave, host_sentinel_name=host_sentinel, playbook_name=pb_name, master_ip=host_ip, master_port='6379', master_name='master01'))
    file_pb.close()

# config sentinel
if is_sentinel:
    file_pb = open(pb_file_dir + '/' + pb_file_name, 'ab')
    file_pb.write(redis_playbook_template.create_playbook(template='sentinel', host_master_name=host_master, host_slave_name=host_slave, host_sentinel_name=host_sentinel, playbook_name=pb_name, master_ip=host_ip, master_port='6379', master_name='master01'))
    file_pb.close()

程序中使用到的生成执行文件的模板文件redis_playbook_template.py 可详见附件。

接下来即可通过运行 ansible 的命令 ansible-playbook 执行 playbook ,从而完成 Redis 集群的搭建,如下所示:

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

创建 Inventory、获取 Playbook、生成执行文件以及执行 Playbook 的代码可详见附件中的 execute_redis_playbook.py 文件。

python 代码执行的 main.py 如下所示:

import execute_redis_playbook
import time
# invoke ros api params
ak_id=''
ak_secret=''
instance_password=''
region_id='cn-shenzhen'
is_sentinel = True

# define func main
def main():
    start_time = time.time()
    execute_redis_playbook.execute(ak_id, ak_secret, region_id, instance_password, is_sentinel)
    end_time = time.time()
    print end_time-start_time
if __name__=='__main__':
    main()

在运行 main.py 前,需要指定账户的 AccessKeys 信息、云主机密码以及可用区。

以上便是基于 ROS 和 Ansible 搭建 Redis 集群的全部过程,示例中 Redis 集群中节点数量是三个,当然也可以通过修改云主机 ECS 的数量,实现对单个节点或更多节点的 Redis 集群的搭建。本示例 Redis 集群搭建成功后的资源拓扑图如下所示:
redis

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

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

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

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

其他文章