Опыт построения Infrastructure-as-Code в VMware. Часть 1: Обозначение проблемы
У нас есть система управления конфигурациями Puppet для Linux, есть (на данный момент) DSC для Windows Server.
Что касается Linux — практически все автоматизированно. Мы заносим конфигурации машин в nodes.yaml, мы заносим роли в Hiera, строим модули (или берем готовые), у нас есть PXE, IP адреса раздаются из DHCP по MAC адресу.
То есть с момента как Linux виртуалка стартует, до момента, когда виртуалка готова к эксплуатации — никаких действий не нужно. Попробуйте угадать, что в этой цепочке делается вручную? Верно, создание самой виртуальной машины в VSphere.
Когда я впервые поднял этот вопрос, мне сказали, что искали решение, пробовали варианты, но ничего не получилось. «К черту!» — подумал я и поспорил на ящик пива, что найду решение, которое будет работать по следующему сценарию: разработчик или инженер делает Pull Request, в котором у нас находится конфигурация виртуальной машины (ядра, память, сеть, шаблон и т.д.) — далее некая магия обращается в VSphere и создает машину, согласно настройкам в файле.
Позволь рассказать немного о нашем окружении, чтобы ты понял с чем мне приходится иметь дело.
У нас в качестве On-Premise виртуализации используется VMware VSphere — парочка датацентров, datastore-кластер и несколько Resource Pool«ов (RP) под каждую команду. Члены команды имеют права на создание виртуальных машин в пределах RP, ребята-инфраструктурщики им в этом не мешают и просто занимаются обслуживанием всей платформы, периодически напоминая разработчикам и инженерам убирать за собой неиспользуемые машины (ресурсы-то не резиновые).
У нас есть Windows виртуалки, Linux виртуалки, масштаб задач огромен — веб серверы, реверс прокси, балансировщики, контроллеры домена, серверы приложений и баз данных и нет им конца и края.
Теперь я поведаю тебе, какие инструменты я пытался применять, и почему они мне не подошли.
Эмпирическим путем…
Ansible vsphere_guest
Как я уже писал в предыдущей статье, я очень люблю Ansible и в вопросах автоматизации я первым делом смотрю, можно ли его для этого использовать.
Согласно документации, есть хороший модуль vsphere_guest который может создавать и удалять виртуалки. То, что нужно. Вот так выглядит мой плейбук createvm.yaml
---
- name: Create a VM in resource pool
hosts: localhost
connection: local
gather_facts: False
vars_prompt:
- name: "user"
prompt: "Enter your username to virtualcenter"
private: no
- name: "password"
prompt: "Enter your password to virtualcenter"
private: yes
- name: "guest"
prompt: "Enter you guest VM name: "
private: no
tasks:
- name: create VM
vsphere_guest:
vcenter_hostname: vcenter.example.com
validate_certs: no
username: '{{ user }}'
password: '{{ password }}'
guest: '{{ guest }}'
state: powered_off
vm_extra_config:
vcpu.hotadd: yes
mem.hotadd: yes
notes: This is a test VM
vm_disk:
disk1:
size_gb: 10
type: thick
datastore: mydatastore
vm_nic:
nic1:
type: vmxnet3
network: mynetwork
network_type: standard
vm_hardware:
memory_mb: 1024
num_cpus: 1
osid: centos64Guest
scsi: paravirtual
resource_pool: "/Resources/MyResourcePool"
esxi:
datacenter: mysite
#hostname: myesxhost01
Я сознательно комментирую hostname esxi потому, что я создаю виртуалку непосредственно в RP, а не на хосте. DRS сам решит, куда виртуалку положить.
Если я запускаю плейбук, он ругается что необходимый параметр hostname не указан. Если я его раскоментирую, то он поругается на отсутствие прав на создание виртуалки на esx хосте (что очевидно, т.к. права у меня есть только на RP). Я создал соответствующий issue, так что надеюсь, ребята из Ansible это исправят, поскольку инструмент реально хороший.
Terraform
Иная тулза, которая умеет создавать виртуалки в VMware это Terraform, продукт от HashiCorp. Изначально он заточен под взаимодействие с Packer и деплоит в AWS, но наши задачи он тоже решает. Вот собственно файл с конфигурацией:
provider "vsphere" {
user = "mylogin@example.com"
password = "${var.vsphere_password}"
vsphere_server = "virtualcenter.example.com"
allow_unverified_ssl = "true"
}
resource "vsphere_virtual_machine" "test" {
name = "${var.machine_name}"
vcpu = 1
memory = 1024
domain = "test.example.com”
datacenter = "mysite"
resource_pool = "Production Cluster #1/Resources/myresourcepool"
network_interface {
label = "test"
ipv4_address = "192.168.1.2"
ipv4_prefix_length = "24"
ipv4_gateway = "192.168.1.1"
}
disk {
datastore = "${var.datastore}"
size = "10"
name = "${var.datastore}/${var.machine_name}/${var.machine_name}.vmdk"
template = "mytemplate"
}
}
variables.tf
variable "vsphere_password" {}
variable "machine_name" {
type = "string"
default = "test"
}
variable "datastore" {
type = "string"
default = "mysite/mydatastore"
}
terraform plan работает прекрасно.
$ terraform plan
var.vsphere_password
Enter a value: supersecurepassword
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but
will not be persisted to local or remote state storage.
The Terraform execution plan has been generated and is shown below.
Resources are shown in alphabetical order for quick scanning. Green resources
will be created (or destroyed and then created if an existing resource
exists), yellow resources are being changed in-place, and red resources
will be destroyed. Cyan entries are data sources to be read.
Note: You didn't specify an "-out" parameter to save this plan, so when
"apply" is called, Terraform can't guarantee this is what will execute.
+ vsphere_virtual_machine.test
datacenter: "mysite"
detach_unknown_disks_on_delete: "false"
disk.#: "1"
disk.1370406802.bootable: ""
disk.1370406802.controller_type: "scsi"
disk.1370406802.datastore: ""
disk.1370406802.iops: ""
disk.1370406802.keep_on_remove: ""
disk.1370406802.key: ""
disk.1370406802.name: ""
disk.1370406802.size: ""
disk.1370406802.template: "mytemplate"
disk.1370406802.type: "eager_zeroed"
disk.1370406802.uuid: ""
disk.1370406802.vmdk: ""
domain: "test.example.com”
enable_disk_uuid: "false"
linked_clone: "false"
memory: "1024"
memory_reservation: "0"
name: "test"
network_interface.#: "1"
network_interface.0.ip_address: ""
network_interface.0.ipv4_address: "192.168.1.2"
network_interface.0.ipv4_gateway: "192.168.1.1"
network_interface.0.ipv4_prefix_length: "24"
network_interface.0.ipv6_address: ""
network_interface.0.ipv6_gateway: ""
network_interface.0.ipv6_prefix_length: ""
network_interface.0.label: "test"
network_interface.0.mac_address: ""
network_interface.0.subnet_mask: ""
resource_pool: "Production Cluster #1/Resources/myresourcepool"
skip_customization: "false"
time_zone: "Etc/UTC"
uuid: ""
vcpu: "1"
Plan: 1 to add, 0 to change, 0 to destroy.
Что так же здорово, можно задать IP адрес, доменное имя — то есть задать полноценную кастомизацию машинки из шаблона. Пробуем запустить…
Error applying plan:
1 error(s) occurred:
* vsphere_virtual_machine.test: Datastore 'mysite/mydatastore not found.
Terraform does not automatically rollback in the face of errors.
Instead, your Terraform state file has been partially updated with
any resources that successfully completed. Please address the error
above and apply again to incrementally change your infrastructure.
Хм, не найден datastore. Как я уже говорил, у нас кластер, так что я попробую сделать по-грязному указав одну из нод кластера.
Error applying plan:
1 error(s) occurred:
* vsphere_virtual_machine.test: Datastore 'mysite/mydatastore/mydatastore-vol01' not found.
Terraform does not automatically rollback in the face of errors.
Instead, your Terraform state file has been partially updated with
any resources that successfully completed. Please address the error
above and apply again to incrementally change your infrastructure.
Что ж… снова неудача. Позже выяснилось, что Terraform не умеет работать с datastore-кластерами. Соответственный issue был создан на GitHub моим коллегой, но успехов на этот поприще тоже, увы, нет.
PowerCLI
Претерпев неудачу в поисках рабочих инструментов от третьих лиц, я решил обратиться к вендорскому решению.
Вендор предлагает два решения — PowerCLI (надстройка над Powershell) и vmware-cli (командный интерфейс для *nix).
Заставить работать vmware-cli на CentOS 7 и OS X не удалось (один страдалец даже написал блог об этом), посему я решил сразу начать пользоваться инструментом, который работает.
Внимательный читатель может поинтересоваться, почему я потратил столько времени на Ansible и Terraform, в то время как PowerCLI уже давно используется. Причины просты — я не знаю Powershell на должном уровне, чтобы с наскоку начать пользоваться им, плюс это вынуждает меня использовать windows машину, которая будет заниматься чистым provision«ом. Впрочем, иных вариантов у меня и нет.
Беглое изучение документации дало мне достаточно навыков, чтобы написать простенький скрипт.
Param(
[string]$Name,
[string]$ResourcePool,
[string]$Location,
[int]$NumCPU,
[int]$MemoryGB,
[int]$DiskGB)
$ErrorActionPreference = "Stop"
Try
{
$credential = Get-Credential
Add-PSSnapin VMware.VimAutomation.Core
[string] $username = $credential.GetNetworkCredential().UserName
$username = 'example\' + $username
Connect-VIServer -Server virtualcenter.example.com -User $username -Password $credential.GetNetworkCredential().Password -Force
$params = @{
name = $Name
ResourcePool = $ResourcePool
Location = $Location
NumCPU = $NumCPU
MemoryGB = $MemoryGB
DiskGB = $DiskGB
}
new-vm @params
}
Catch [VMware.VimAutomation.ViCore.Types.V1.ErrorHandling.DuplicateName] {"VM exists"}
Этот скрипт рабочий и делает все необходимое. Запуск скрипта выглядит следующим образом:
.\createvm.ps1 -Name mytestvm -ResourcePool myresourcepool -Location myteam -NumCPI 1 - MemoryGB 1 -DisckGB 10
Скрипт попросит меня предоставить логин и пароль, переиграет переменные и создаст машинку с помощью cmdlet«а new-vm. Читатель может поинтересоваться, почему присутствует вот эта строка:
[string] $username = $credential.GetNetworkCredential().UserName
$username = 'example\' + $username
Пусть меня поправят опытные powershell ребята, если я ошибаюсь. Get-Credential создает объект состоящий из логина, пароля и домена (если он есть). Пароль находится в состоянии SecureString. К сожалению, PowerCLI не умеет работать ни с объектом Get-Credential, ни с SecureString, потому приходится идти на подобные ухищрения, чтобы передать ему логин и пароль простой строковой переменной.
Выводы
Дорогой читатель, если у тебя однажды встанет задача автоматизировать создание виртуальных машин в VMware, то учти следующее:
- используешь ли ты Resource Pool«ы
- используешь ли ты Datastore кластеры
Если у тебя single node ESX, то я рекомендую пользоваться Ansible, у него низкий порог вхождения, и он довольно легкий и шустрый.
Если же у тебя такая же сложная инфраструктура, как и у нас, то лучше не изобретай велосипед, а осваивай PowerCLI.
В следующей части я расскажу, как мы сделали наш скрипт умнее, и научили его делать проверки на кастомизацию, количество ядер и других ресурсов и naming convention.
Комментарии (3)
9 декабря 2016 в 00:33
0↑
↓
с PowerCLI не работал, но раз говорите, что с Get-Credential оно не работает, то мб попробовать как-то так:$Username = ReadHost
$UserPaswd = ReadHost9 декабря 2016 в 00:54 (комментарий был изменён)
0↑
↓
Хотя это тоже изврат, вобщем на мой взгляд — так как все равно приводите результат Get-Credential к строке, то самое простое будет добавить в params еще и $username $password.
Тогда вместо:$credential = Get-Credential
Add-PSSnapin VMware.VimAutomation.Core
[string] $username = $credential.GetNetworkCredential().UserName
$username = 'example\' + $username
Connect-VIServer -Server virtualcenter.example.com -User $username -Password $credential.GetNetworkCredential().Password -Forceвыйдет как-то так:
Add-PSSnapin VMware.VimAutomation.Core
Connect-VIServer -Server virtualcenter.example.com -User $username -Password $password -Force
9 декабря 2016 в 02:14
0↑
↓
https://www.theforeman.org/?