蓝鲸实现vsphere虚拟机交付 -虚拟机管理(VSPHERE)
点击上方蓝色字体,关注我们
腾讯蓝鲸实现vsphere虚拟机交付介绍了虚拟机上架的交付流程,但是蓝鲸标准运维自带的原子不满足我们我们的环境需求,因此我们需要针对VSPHERE单独开发。
环境
名称 | 版本 | 备注 |
蓝鲸 | 5.1.26 | |
标准运维 | 3.3.27 | https://github.com/Tencent/bk-sops |
python开发框架 | 2.0.0 | 蓝鲸开发框架 |
vcenter | 5.5.0-218311 | vCenter Server 5.5 Update 2b |
pyvmomi | 6.7.3 |
注意:
1.由于需要单独开发标准运维原子,需要将蓝鲸自带的标准运维下架,然后部署从源码开发的定制版的标准运维;
2.其中标准运维需要redis支持,否则无法运行,可参见Tencent/bk-sops
“https://github.com/Tencent/bk-sops/blob/master/docs/install/source_code_deploy.md”
3.标准运维的原子开发需要学习蓝鲸社区"早起鸟儿有虫吃"
思路
1.使用vcenter自定义规范管理器直接从模板克隆虚拟机,定制过程中我们需要输入以下参数:虚拟机名、虚拟机ip、模板名称、数据中心、集群名、存放位置、存储器名等;
2.新虚拟机启动后,需要开机启动修改内核参数、安装蓝鲸agent、修改zabbix-agent地址等,此步已经在模板中提前设置;
3.后续的步骤均以输入的虚拟机IP为准,进行资产创建、cmdb注册等;
虚拟机管理(VSPHERE)原子开发
1.原子前端开发
定义虚拟机管理需要输入的参数 ,以web的形式展示
vim vsphere_vm_create.js
/**
* Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community
* Edition) available.
* Copyright (C) 2017-2019 THL A29 Limited, a Tencent company. All rights reserved.
* Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
(function(){
$.atoms.vsphere_vm_create = [
{
tag_code: "vsphere_vm_name",
type: "input",
attrs: {
name: gettext("虚拟机名"),
placeholder: gettext("新建虚拟机名"),
hookable: true,
validation: [
{
type: "required"
}
]
}
},
{
tag_code: "vsphere_vm_ip",
type: "input",
attrs: {
name: gettext("虚拟机IP"),
placeholder: gettext("虚拟机IP"),
hookable: true,
validation: [
{
type: "required"
}
]
}
},
{
tag_code: "vsphere_template_name",
type: "radio",
attrs: {
name: gettext("模板名称"),
items: [
{value: "template_root", name: "root"},
{value: "template_app", name: "app"},
],
default: "app",
hookable: true,
validation: [
{
type: "required"
}
]
}
},
{
tag_code: "vsphere_datacenter_name",
type: "radio",
attrs: {
name: gettext("数据中心"),
items: [
{value: "unicom-idc", name: "unicom-idc"},
],
default: "unicom-idc",
hookable: true,
validation: [
{
type: "required"
}
]
}
},
{
tag_code: "vsphere_cluster_name",
type: "radio",
attrs: {
name: gettext("集群名"),
items: [
{value: "unicom-ha", name: "unicom-ha"},
{value: "unicom-offline", name: "unicom-offline"},
],
default: "unicom-offline",
hookable: true,
validation: [
{
type: "required"
}
]
}
},
{
tag_code: "vsphere_folder_name",
type: "input",
attrs: {
name: gettext("存放位置"),
placeholder: gettext("虚拟机存放位置"),
hookable: true,
validation: [
{
type: "required"
}
]
}
},
{
tag_code: "vsphere_datastore_name",
type: "select",
attrs: {
name: gettext("存储器名"),
placeholder: gettext("存储器名"),
items: [
{text: "test1.datastore1", value: "uvm50.datastore1"},
{text: "test2.datastore1", value: "uvm51.datastore1"},
{text: "test3.datastore1", value: "uvm52.datastore1"},
],
hookable: true,
validation: [
{
type: "required"
}
]
}
},
]
})();
具体的web展示如下:
2.原子后端开发
后端实现了虚拟机创建,主要由三部分组成:
(1)虚拟机克隆,根据pyvmomi的clone_vm.py 进行修改;
(2)自定义规范修改ip、主机名,也需要通过pyvmomi进行修改;
(3)启动虚拟机,自定义规范订制后的虚拟机是关机状态的,我们需要实现开机的操作;
具体实现如下:
#自行修改vcenter链接参数
vim vsphere.py
# -*- coding: utf-8 -*-
"""
vsphere虚拟机管理
Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community
Edition) available.
Copyright (C) 2017-2019 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
"""
import logging
from pipeline.conf import settings
from pipeline.core.flow.activity import Service
from pipeline.component_framework.component import Component
#vsphere 原子所需扩展
from pyVmomi import vim
from pyVim.connect import SmartConnect, SmartConnectNoSSL, Disconnect
import atexit
import argparse
import getpass
import json
import time
logger = logging.getLogger('celery')
__group_name__ = u"虚拟机管理(VSPHERE)"
#vsphere vcenter连接参数
host = "10.10.5.88"
user = "admin@vsphere.local"
password = "xxxxxxxxxx"
port = 443
no_ssl = True
power_on = False
resource_pool = False
vsphere_datastorecluster_name = False
'''
vsphere 基础函数
'''
def wait_for_task(task):
""" wait for a vCenter task to finish """
task_done = False
while not task_done:
if task.info.state == 'success':
return task.info.result
if task.info.state == 'error':
print("go to vCenter")
return task.info.result
task_done = True
def get_obj(content, vimtype, name):
"""
Return an object by name, if name is None the
first found object is returned
"""
obj = None
container = content.viewManager.CreateContainerView(
content.rootFolder, vimtype, True)
for c in container.view:
if name:
if c.name == name:
obj = c
break
else:
obj = c
break
return obj
def ip_assign(vm, vm_ip, vm_name):
"""设置IP地址"""
adaptermap = vim.vm.customization.AdapterMapping()
adaptermap.adapter = vim.vm.customization.IPSettings()
adaptermap.adapter.ip = vim.vm.customization.FixedIp()
adaptermap.adapter.ip.ipAddress = vm_ip
adaptermap.adapter.subnetMask = "255.255.255.0"
adaptermap.adapter.gateway = "192.168.3.1"
#adaptermap.adapter.dnsDomain = "localhost"
"""dns设置"""
globalip = vim.vm.customization.GlobalIPSettings()
globalip.dnsServerList = "114.114.114.114"
"""设置主机名"""
ident = vim.vm.customization.LinuxPrep()
#ident.domain = "localhost"
ident.hostName = vim.vm.customization.FixedName()
ident.hostName.name = vm_name
customspec = vim.vm.customization.Specification()
customspec.nicSettingMap = [adaptermap]
customspec.globalIPSettings = globalip
customspec.identity = ident
print "Reconfiguring VM Networks . . ."
#task = get_obj([vim.VirtualMachine],vm).Customize(spec=customspec)
task = vm.Customize(spec=customspec)
wait_for_task(task)
return True
def clone_vm(
content, template, vm_name, si,
datacenter_name, vm_folder, datastore_name,
cluster_name, resource_pool, power_on, datastorecluster_name):
"""
Clone a VM from a template/VM, datacenter_name, vm_folder, datastore_name
cluster_name, resource_pool, and power_on are all optional.
"""
# if none git the first one
datacenter = get_obj(content, [vim.Datacenter], datacenter_name)
if vm_folder:
destfolder = get_obj(content, [vim.Folder], vm_folder)
else:
destfolder = datacenter.vmFolder
if datastore_name:
datastore = get_obj(content, [vim.Datastore], datastore_name)
else:
datastore = get_obj(
content, [vim.Datastore], template.datastore[0].info.name)
# if None, get the first one
cluster = get_obj(content, [vim.ClusterComputeResource], cluster_name)
if resource_pool:
resource_pool = get_obj(content, [vim.ResourcePool], resource_pool)
else:
resource_pool = cluster.resourcePool
vmconf = vim.vm.ConfigSpec()
if datastorecluster_name:
podsel = vim.storageDrs.PodSelectionSpec()
pod = get_obj(content, [vim.StoragePod], datastorecluster_name)
podsel.storagePod = pod
storagespec = vim.storageDrs.StoragePlacementSpec()
storagespec.podSelectionSpec = podsel
storagespec.type = 'create'
storagespec.folder = destfolder
storagespec.resourcePool = resource_pool
storagespec.configSpec = vmconf
try:
rec = content.storageResourceManager.RecommendDatastores(
storageSpec=storagespec)
rec_action = rec.recommendations[0].action[0]
real_datastore_name = rec_action.destination.name
except:
real_datastore_name = template.datastore[0].info.name
datastore = get_obj(content, [vim.Datastore], real_datastore_name)
# set relospec
relospec = vim.vm.RelocateSpec()
relospec.datastore = datastore
relospec.pool = resource_pool
clonespec = vim.vm.CloneSpec()
clonespec.location = relospec
clonespec.powerOn = power_on
print("cloning VM...")
task = template.Clone(folder=destfolder, name=vm_name, spec=clonespec)
#返回结果函数,用于判断任务是否完成
return wait_for_task(task)
class VsphereVMCreateService(Service):
__need_schedule__ = False
def execute(self, data, parent_data):
vsphere_vm_name = data.get_one_of_inputs('vsphere_vm_name')
vsphere_vm_ip = data.get_one_of_inputs('vsphere_vm_ip')
vsphere_template_name = data.get_one_of_inputs('vsphere_template_name')
vsphere_folder_name = data.get_one_of_inputs('vsphere_folder_name')
vsphere_datacenter_name = data.get_one_of_inputs('vsphere_datacenter_name')
vsphere_cluster_name = data.get_one_of_inputs('vsphere_cluster_name')
vsphere_datastore_name = data.get_one_of_inputs('vsphere_datastore_name')
args = {
"host": host,
"user": user,
"password": password,
"port": port,
"no_ssl": no_ssl,
"power_on": power_on,
"vm_name": vsphere_vm_name,
"template": vsphere_template_name,
"datacenter_name": vsphere_datacenter_name,
"cluster_name": vsphere_cluster_name,
"vm_folder": vsphere_folder_name,
"datastore_name": vsphere_datastore_name,
"datastorecluster_name": vsphere_datastorecluster_name,
"resource_pool": resource_pool
}
try:
si = None
if args['no_ssl']:
si = SmartConnectNoSSL(
host=args['host'],
user=args['user'],
pwd=args['password'],
port=args['port'])
else:
si = SmartConnect(
host=args.host,
user=args.user,
pwd=args.password,
port=args.port)
# disconnect this thing
atexit.register(Disconnect, si)
content = si.RetrieveContent()
template = None
template = get_obj(content, [vim.VirtualMachine], args['template'])
if template:
task_info_result = clone_vm(
content, template, args['vm_name'], si,
args['datacenter_name'], args['vm_folder'],
args['datastore_name'], args['cluster_name'],
args['resource_pool'], args['power_on'], args['datastorecluster_name'])
#if args.opaque_network:
#此功能关闭
# vm = get_obj(content, [vim.VirtualMachine], args.vm_name)
# add_nic(si, vm, args.opaque_network)
if task_info_result:
print task_info_result
#自定义规范定制虚拟机
vm = get_obj(content, [vim.VirtualMachine], args['vm_name'])
task_info_result = ip_assign(vm, vsphere_vm_ip, args['vm_name'])
if task_info_result:
#启动自定义后的虚拟机
print "PowerOn vm..."
vm.PowerOn()
#启动完毕,等待50s完全启动
time.sleep(50)
data.set_outputs('vm_ip', vsphere_vm_ip)
return True
else:
print task_info_result
data.set_outputs('ex_data', u"自定义虚拟机错误")
return False
else:
print task_info_result
data.set_outputs('ex_data', u"克隆虚拟机指定的参数错误或虚拟机名已重复")
return False
else:
data.set_outputs('ex_data', u"虚拟机模板没有找到")
return False
except Exception as e:
data.set_outputs('ex_data', e)
logger.exception(e)
return False
def outputs_format(self):
return [
self.OutputItem(name=u'虚拟机IP', key='vm_ip', type='string'),
self.OutputItem(name=u'报错信息', key='ex_data', type='string')
]
class VsphereVMCreateComponent(Component):
name = u'创建虚拟机'
code = 'vsphere_vm_create'
bound_service = VsphereVMCreateService
#form = settings.STATIC_URL + 'custom_atoms/vsphere/vsphere_vm_create.js'
form = '%scityre_atoms/vsphere_vm_create.js' % settings.STATIC_URL
以上过程要注意:
日志打印,帮助我们排查开发过程中的问题;
ex_data,用于前段展示错误提示;
最后启动虚拟机的时间,根据实际情况调整下,我设置在50秒;
3.最终效果
(1)填写参数
(2)创建完成
总结
经过以上自动创建虚拟机,我们完成了初步的虚拟机交付,后续还需要添加跳板机、注册cmdb等操作,涉及到跳板机管理(JUMP)、配置平台自定义(CMDB)原子的开发。
注意在开发过程中要遵循标准插件开发规范:
分组命名规则是“系统名(系统英文缩写)”,如“作业平台(JOB)”;
标准插件编码(code)使用下划线方式,规则是“系统名_接口名”,如 job_execute_task;
后台类名使用驼峰式,规则是“标准插件编码+继承类名”,如 JobExecuteTaskService;
前端 JS 文件目录保持和系统名缩写一致,JS 文件名保持和标准插件编码一致;
参数 tagcode 命名规则是“系统名参数名”,这样可以保证全局唯一;长度不要超过 20 个字符;
- End -