蓝鲸实现多环境vsphere虚拟机交付
读完需 5 分钟
速读需 3 分钟
需求
《蓝鲸实现vsphere虚拟机交付 -虚拟机管理(VSPHERE)》一文主要介绍了蓝鲸基于单一的生产vphere环境实现了虚拟机的交付,但是在实际应用中可能存在多个vsphere环境,如生产、测试等,因此我们更希望的是通过蓝鲸能够实现多环境vsphere虚拟机的交付。
如果要实现这一个场景需求,可能存在以下几个问题:
多环境vsphere 会划分不同的vlan、子网掩码、网关、dns等;
vsphere自定义规范不支持更改vlan,因此需要手动调整vlan;
多环境vsphere 需要匹配多个vCenter,并输入相关自定义参数;
我们带着以上问题进入下一阶段。
思路解析
1.多vlan网段
由于vsphere自定义规范的限制,我们不能够在自定义规范中直接指定虚拟机所属vlan。但是退一步思考:
一般情况下,只有在新vlan中第一台虚拟机才需要指定vlan,后续我们可以根据这台已经更改完vlan的虚拟机进行克隆;
修改模板机vlan,后续可直接从模板机直接创建;
手动调整克隆后虚拟机的vlan;
因此我们不必纠结于自定义规范的限制,只需要关注如何快速适配vlan的子网掩码、网关、dns,此时就可以利用自定义规范天生具有的功能了。
在此我的解决方法是将vlan的子网掩码、网关、dns的等可能变动的参数在vsphere前端原子中根据需求自定义,而不是直接写死。
当使用新vlan时,我们只需在vsphere原子中更改以上参数,就可以通过自定义规范中直接定制了。
2.多环境vsphere
多环境vsphere 意味着有多个vCenter,但是最终执行通过自定义规范创建虚拟机的功能是不变的,因此当我们在原子前端输入新虚拟机的配置信息时就已经确定此虚拟机属于哪个环境了。
根据这一特性,我的解决思路是提供三个不同环境的前端原子,而后端原子保持一个即可。
前端原子
1.创建生产虚拟机
通过"vsphere_env"参数来确定使用生产vCenter
# 生产虚拟机参数
# vim static/test_atoms/vsphere_vm_create.js
(function(){
$.atoms.vsphere_vm_create = [
{
tag_code: "vsphere_env",
type: "radio",
attrs: {
name: gettext("环境"),
items: [
{value: "prod", name: gettext("生产")},
],
default: "prod",
hookable: true,
validation: [
{
type: "required"
}
]
}
},
{
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_vm_subnetmask",
type: "input",
attrs: {
name: gettext("虚拟机掩码"),
placeholder: gettext("虚拟机掩码"),
hookable: true,
validation: [
{
type: "required"
}
]
}
},
{
tag_code: "vsphere_vm_gateway",
type: "input",
attrs: {
name: gettext("虚拟机网关"),
placeholder: gettext("虚拟机网关"),
hookable: true,
validation: [
{
type: "required"
}
]
}
},
{
tag_code: "vsphere_vm_dns",
type: "input",
attrs: {
name: gettext("虚拟机DNS"),
placeholder: gettext("虚拟机DNS"),
hookable: true,
validation: [
{
type: "required"
}
]
}
},
{
tag_code: "vsphere_template_name",
type: "input",
attrs: {
name: gettext("模板或虚拟机"),
placeholder: gettext("模板或虚拟机名称"),
hookable: true,
validation: [
{
type: "required"
}
]
}
},
{
tag_code: "vsphere_datacenter_name",
type: "radio",
attrs: {
name: gettext("数据中心"),
items: [
{value: "VSAN-DC", name: "VSAN-DC"},
],
default: "VSAN-DC",
hookable: true,
validation: [
{
type: "required"
}
]
}
},
{
tag_code: "vsphere_cluster_name",
type: "radio",
attrs: {
name: gettext("集群名"),
items: [
{value: "VSAN-Cluster2", name: "VSAN-Cluster2"},
{value: "VSAN-Cluster3", name: "VSAN-Cluster3"},
],
default: "VSAN-Cluster3",
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: "vsanDatastore_Cluster2", value: "vsanDatastore_Cluster2"},
{text: "vsanDatastore_Cluster3", value: "vsanDatastore_Cluster3"},
],
hookable: true,
validation: [
{
type: "required"
}
]
}
},
]
})();
2.创建测试虚拟机
通过"vsphere_env"参数来确定使用测试vCenter
# 生产虚拟机参数
# vim static/test_atoms/vsphere_vm_create.js
(function(){
$.atoms.vsphere_vm_create_test = [
{
tag_code: "vsphere_env",
type: "radio",
attrs: {
name: gettext("环境"),
items: [
{value: "test", name: gettext("测试")},
],
default: "test",
hookable: true,
validation: [
{
type: "required"
}
]
}
},
{
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_vm_subnetmask",
type: "input",
attrs: {
name: gettext("虚拟机掩码"),
placeholder: gettext("虚拟机掩码"),
hookable: true,
validation: [
{
type: "required"
}
]
}
},
{
tag_code: "vsphere_vm_gateway",
type: "input",
attrs: {
name: gettext("虚拟机网关"),
placeholder: gettext("虚拟机网关"),
hookable: true,
validation: [
{
type: "required"
}
]
}
},
{
tag_code: "vsphere_vm_dns",
type: "input",
attrs: {
name: gettext("虚拟机DNS"),
placeholder: gettext("虚拟机DNS"),
hookable: true,
validation: [
{
type: "required"
}
]
}
},
{
tag_code: "vsphere_template_name",
type: "input",
attrs: {
name: gettext("模板或虚拟机"),
placeholder: gettext("模板或虚拟机名称"),
hookable: true,
validation: [
{
type: "required"
}
]
}
},
{
tag_code: "vsphere_datacenter_name",
type: "radio",
attrs: {
name: gettext("数据中心"),
items: [
{value: "Test-Datacenter", name: "Test-Datacenter"},
],
default: "Test-Datacenter",
hookable: true,
validation: [
{
type: "required"
}
]
}
},
{
tag_code: "vsphere_cluster_name",
type: "radio",
attrs: {
name: gettext("集群名"),
items: [
{value: "cluster", name: "cluster"},
{value: "vsan-cluster", name: "vsan-cluster"},
],
default: "VSAN-Cluster3",
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: "vsan-cluster-vsanDatastore", value: "vsanDatastore"},
],
hookable: true,
validation: [
{
type: "required"
}
]
}
},
]
})();
后端原子
# vim test_atoms/components/collections/vsphere.py
# -*- coding: utf-8 -*-
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连接参数
prod_host = "10.10.30.29"
prod_user = "administrator@vsphere.local"
pord_password = "prod@2019"
#测试vsphere vcenter连接参数
test_host = "10.11.30.29"
test_user = "administrator@vsphere.local"
test_password = "test@2019"
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, vm_subnetmask, vm_gateway, vm_dns):
"""设置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 = vm_subnetmask
adaptermap.adapter.gateway = vm_gateway
#adaptermap.adapter.dnsDomain = "localhost"
"""dns设置"""
globalip = vim.vm.customization.GlobalIPSettings()
globalip.dnsServerList = vm_dns
"""设置主机名"""
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_env = data.get_one_of_inputs('vsphere_env')
vsphere_vm_name = data.get_one_of_inputs('vsphere_vm_name')
vsphere_vm_ip = data.get_one_of_inputs('vsphere_vm_ip')
vsphere_vm_subnetmask = data.get_one_of_inputs('vsphere_vm_subnetmask')
vsphere_vm_gateway = data.get_one_of_inputs('vsphere_vm_gateway')
vsphere_vm_dns = data.get_one_of_inputs('vsphere_vm_dns')
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 = {
"env": vsphere_env,
"port": port,
"no_ssl": no_ssl,
"power_on": power_on,
"vm_name": vsphere_vm_name,
"vm_ip": vsphere_vm_ip,
"vm_subnetmask": vsphere_vm_subnetmask,
"vm_gateway": vsphere_vm_gateway,
"vm_dns": vsphere_vm_dns,
"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']:
if args['env'] == 'prod':
si = SmartConnectNoSSL(
host=prod_host,
user=prod_user,
pwd=pord_password,
port=args['port'])
else:
si = SmartConnectNoSSL(
host=test_host,
user=test_user,
pwd=test_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, args['vm_ip'], args['vm_name'], args['vm_subnetmask'], args['vm_gateway'], args['vm_dns'])
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 = '%stest_atoms/vsphere_vm_create.js' % settings.STATIC_URL
class VsphereTestVMCreateComponent(Component):
name = u'创建测试虚拟机'
code = 'vsphere_vm_create_test'
bound_service = VsphereVMCreateService
#form = settings.STATIC_URL + 'custom_atoms/vsphere/vsphere_test_vm_create.js'
form = '%stest_atoms/vsphere_vm_create_test.js' % settings.STATIC_URL
最终效果
蓝鲸实现vsphere虚拟机交付 -虚拟机管理(VSPHERE)
你与世界
只差一个
公众号