Опыт построения Infrastructure-as-Code в VMware. Часть 1: Обозначение проблемы

Приветствую, дорогой читатель. Я начинаю цикл статей о том, как мы искали решение для применения подхода Infrastructure-as-Code в нашем виртуальном окружении VMware VSphere.

У нас есть система управления конфигурациями 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 = ReadHost

    • 9 декабря 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/?

© Habrahabr.ru