«Деплой» виртуальных машин путем «тикета» в Jira
Всем привет, меня зовут Денис, я хотел бы поделиться опытом использования AWX в рамках одной из наших потребностей. Статья может быть полезна ребятам с «инфры», если в компании используется vmware и подобное cloud решение для частого деплоя, а для всяческой бюрократии и запросов вы используйте Jira.
Недавно @kuksepa выкладывала отличную статью про AWX поэтому я не стану много описывать конкретно его, а постараюсь кратко описать процесс. Прошу не обращать внимание на замазанные элементы, в них нет повествовательной нагрузки.

Jira
Для реализации в Jira создан новый проект (Deploy VM), пользовательские поля, 2 типа запроса (cloud и ЦОД) и 2 скрипта в ScriptRunner. Заранее говорю, что слабо знаком с динамикой полей и форм в jira и ввиду этого логика заполнения полей «тикета» пока что громоздкая
-
Проект Deploy VM имеет очередь заданий, созданы ассоциированные с ним поля, а также привязаны 2 типа запроса (Deploy и DeployCloud).
-
Скрипты (ScriptRunner) внутри jira запускаются при переводе «тикета» в статус «в работе» и посылают api запрос в AWX. Содержание скриптов в основном — это обработка полей и отправка POST запроса в требуемом AWX формате.
import com.atlassian.jira.component.ComponentAccessor import com.atlassian.jira.issue.IssueInputParametersImpl import com.atlassian.jira.user.ApplicationUser //Далее идет мапинг пользовательских полей .... def apiUrl switch (field2Value) { case "Ubuntu": apiUrl = "https://Ваш url/v2/workflow_job_templates/121/launch/" break case "WindowsServer2019": //тут ендпоинт в зависимости от выбора ОС apiUrl = "https://Ваш url/v2/workflow_job_templates/122/launch/" break default: log.error("Неверное значение для customfield_21402: ${field2Value}") throw new IllegalArgumentException("Не удалось определить URL для значения: ${field2Value}") } def requestBody = """{ //преобразование полей в тело запроса "extra_vars":{ "computer_name": "${field3Value}", "VDC": "${field4Value}", "vapp_name": "${field5Value}", "network": "${field6Value}", "ip": "${field7Value}", "gw": "${field8Value}", "ip_network": "${field9Value}", "mask": "${field10Value}", "cpu": "${field11Value}", "memory": "${field12Value}", "disk": "${field13Value}", "group_access": "${field14Value}", "disk2": "${field15Value}", "size": "${field16Value}", "mount_folder": "${field17Value}", "description": "${field18Value}", "JiraTicket": "${ticketNumber}" } }""" //Далее формируем POST .....

На данный момент поля заполнения cloud (слева) и vcenter (справа) выглядит следующим образом. Где существует выбор из предоставляемых ресурсов, выполнен каскадный список с единичным выбором.
Terraform-Git
Из внутреннего гита берутся все необходимые шаблоны, скрипты и прочие файлы для выполнения заданий.
Начну с примеров шаблона, которые выполняет непосредственно terraform.
1) Шаблон по деплою ubuntu в cloud, сеть и имя хоста передаем через скрипт, т.к. опции кастомизации не работают.
variable "vcd_url" {} variable "org_name" {} variable "vcd_max_retry_timeout" { default = "1800" } variable "vcd_allow_unverified_ssl" { default = "true" } terraform { backend "local" { path = "/home/dsoldatov/terraformstate/terraform.tfstate" } required_providers { vcd = { source = "Ваше/vmware/vcd" version = ">=3.10.0" } } } provider "vcd" { user = "none" password = "none" auth_type = "api_token_file" api_token_file = "token.json" allow_api_token_file = true org = var.org_name vdc = "Ваш тенант-{{VDC}}" url = var.vcd_url max_retry_timeout = var.vcd_max_retry_timeout allow_unverified_ssl = var.vcd_allow_unverified_ssl } variable "attach_additional_disk" { type = bool default = "{{disk2}}" } resource "vcd_vapp_vm" "{{computer_name}}" { vapp_name = "{{vapp_name}}" name = "{{computer_name}}" catalog_name = "Ubuntu_new" template_name = "Ваш шаблон" memory = "{{memory}}" cpus = "{{cpu}}" accept_all_eulas = true description = "{{description}}" storage_profile = "{{disk}}" computer_name = "{{computer_name}}" network { type = "org" name = "{{network}}" ip = "{{ip_network}}" ip_allocation_mode = "MANUAL" is_primary = true } customization { allow_local_admin_password = "true" auto_generate_password = "false" admin_password = "Ваше" enabled = true initscript = <<-SCRIPT #!/bin/bash sudo awk '{gsub("Ваш IP шаблона", "{{ip}}")}1' /etc/netplan/00-installer-config.yaml > tmpfile && sudo mv tmpfile /etc/netplan/00-installer-config.yaml sudo awk '{gsub("Ваш шлюз шаблона", "{{gw}}")}1' /etc/netplan/00-installer-config.yaml > tmpfile && sudo mv tmpfile /etc/netplan/00-installer-config.yaml sudo awk '{gsub("Ваш шаблон шаблона", "{{computer_name}}")}1' /etc/hostname > tmpfile && sudo mv tmpfile /etc/hostname netplan apply reboot SCRIPT } } resource "time_sleep" "wait_for_vm" { create_duration = "30s" } resource "vcd_vm_internal_disk" "additional_disk" { count = var.attach_additional_disk ? 1 : 0 depends_on = [time_sleep.wait_for_vm] vapp_name = "{{vapp_name}}" vm_name = "{{computer_name}}" allow_vm_reboot = "true" bus_number = 0 unit_number = 1 bus_type = "parallel" size_in_mb = "{{size}}" }
2) Шаблон по «деплою» windows server в vsphere использует 2 файла. В этом случае опции кастомизации работают и позволяют сменить сеть и авторизовать в домен. Также тут имеет место быть выбор между datastore и datacluster
provider "vsphere" { user = "${var.vsphere_user}" password = "${var.vsphere_password}" vsphere_server = "${var.vsphere_server}" allow_unverified_ssl = true } data "vsphere_datacenter" "dc" { name = "{{datacenter}}" } data "vsphere_datastore" "datastore" { count = var.use_datastore_cluster ? 0 : 1 name = "{{datastore}}" datacenter_id = data.vsphere_datacenter.dc.id } data "vsphere_datastore_cluster" "datastore_cluster" { count = var.use_datastore_cluster ? 1 : 0 name = "{{datastore}}" datacenter_id = data.vsphere_datacenter.dc.id } data "vsphere_compute_cluster" "cluster" { name = "{{cluster}}" datacenter_id = "${data.vsphere_datacenter.dc.id}" } data "vsphere_network" "network" { name = "{{env}}_vlan{{vlan}}" datacenter_id = "${data.vsphere_datacenter.dc.id}" } data "vsphere_virtual_machine" "template" { name = "Ваш шаблон" datacenter_id = "${data.vsphere_datacenter.dc.id}" } #data "vsphere_custom_attribute" "attribute" { # name = "Описание" #} terraform { backend "local" { path = "/home/dsoldatov/terraformstate/terraform.tfstate" } required_providers { Ваше = { source = "Ваше" } } }
variable "use_datastore_cluster" { description = "use datastore cluster" type = bool default = "{{datacluster}}" } locals { add_extra_disk = "{{disk2}}" selected_datastore = var.use_datastore_cluster ? data.vsphere_datastore_cluster.datastore_cluster[0].id : data.vsphere_datastore.datastore[0].id } resource "vsphere_virtual_machine" "vm1" { name = "{{computer_name}}" resource_pool_id = "${data.vsphere_compute_cluster.cluster.resource_pool_id}" datastore_id = local.selected_datastore folder = "{{folder}}" # custom_attributes = tomap({"${data.vsphere_custom_attribute.attribute.id}" = "{{description}}"}) num_cpus = "{{cpu}}" memory = "{{memory}}" firmware = "efi" guest_id = "${data.vsphere_virtual_machine.template.guest_id}" scsi_type = "${data.vsphere_virtual_machine.template.scsi_type}" network_interface { network_id = "${data.vsphere_network.network.id}" adapter_type = "${data.vsphere_virtual_machine.template.network_interface_types[0]}" } disk { label = "disk0" size = "${data.vsphere_virtual_machine.template.disks.0.size}" eagerly_scrub = "${data.vsphere_virtual_machine.template.disks.0.eagerly_scrub}" thin_provisioned = "${data.vsphere_virtual_machine.template.disks.0.thin_provisioned}" } dynamic "disk" { for_each = local.add_extra_disk ? [1] : [] content { label = "disk1" size = "{{size}}" eagerly_scrub = false thin_provisioned = false unit_number = 1 controller_type = "scsi" } } clone { template_uuid = "${data.vsphere_virtual_machine.template.id}" customize { windows_options { computer_name = "{{computer_name}}" join_domain = "Ваш домен" domain_admin_user = "{{login}}" domain_admin_password = "{{pass}}" # admin_password = var.local_adminpass run_once_command_list = [ "C:\\Distr\\installer.exe /s", "msiexec /i C:\\Distr\\Ваше /quiet" ] } network_interface { ipv4_address = "{{ip_network}}" ipv4_netmask = "{{mask}}" dns_server_list = ["Ваши dns"] } ipv4_gateway = "{{gw}}" } } }
Запуск задания и передача данных в terraform инициируется ansible шаблоном. Пример шаблона.
gather_facts: false tasks: - name: Wait #Ожидание в случае параллельно работающего процесса деплоя ansible.builtin.wait_for: path: /home/dsoldatov/terraformstate/.terraform.tfstate.lock.info state: absent - name: Run state.sh script #Запуск скрипта на сброс состояния command: /home/dsoldatov/templates_cloud_windows/state.sh ignore_errors: yes args: chdir: /home/dsoldatov/templates_cloud_windows/ - name: Copy #Замена шаблона на деплой template: src: ../Playbooks/main_windows.j2 dest: /home/dsoldatov/templates_cloud_windows/main.tf - name: Init command: "terraform init" args: chdir: /home/dsoldatov/templates_cloud_windows - name: Plan #Инит и запуск деплоя command: terraform plan -out=plan.out args: chdir: /home/dsoldatov/templates_cloud_windows - name: Apply command: terraform apply -input=false plan.out args: chdir: /home/dsoldatov/templates_cloud_windows - name: Pause #Пауза для последеплойного состояния ansible.builtin.pause: seconds: 60
AWX
Для реализации получения на основании заполненного «тикета» в Jira готовой ВМ, авторизованной в домене, возможностью автоматической разметки второго диска с «мапингом» необходимого каталога, а также для автоматической смены статуса «тикета» в Jira, потребовалось сделать несколько дополнительных решений.

Для каждого из деплоев в AWX созданы workflow с цепочкой из ряда job_template, т.к. после деплоя ВМ нам необходимо, например, добавить ВМ в домен, a для этого после деплоя (terraform) потребуется выполнение ansible шаблона (кроме windows server в vsphere) с AWX (job_template) на авторизацию в домен, но мы не сможем его выполнить, если данного хоста нет в «инвентори». Поэтому первым делом во всех workflow мы выполняем именно этот ansible шаблон.
- hosts: localhost tasks: - name: Add new host uri: url: "ваш url" method: POST headers: Content-Type: "application/json" Authorization: "Bearer Ваш токен" body: > { "name": "{{ computer_name }}", "variables": "{\"ansible_host\": \"{{ ip_network }}\"}" } body_format: json status_code: 201 register: result - name: Debug result debug: var: result
Добавление хоста в «инвентори» на основании переданных Jira переменных из «тикета». Токен заранее прописан от технической jira УЗ с определенными правами в AWX.
Авторизация в домен реализована в рамках job_template ansible шаблонов идущих после основного деплоя.
- name: enter domain hosts: "{{computer_name}}" gather_facts: false vars_files: - ../Playbooks/credentials_win.yml tasks: - name: Pause ansible.builtin.pause: seconds: 90 - name: Join domain win_domain_membership: dns_domain_name: "Ваше домен" hostname: "{{computer_name}}" domain_admin_user: "{{login}}" domain_admin_password: "{{pass}}" state: domain
«Креды» для авторизации в домен подставляются из файла credentials_win.yml записанные в ansible vault формате. Без таймаута winrm не способен выполнить шаблон.
Для удобства использования «последеплойной» Ubuntu c доп. диском, в workflow добавлен шаблон ansible с процедурой разметки
- name: add_seconddisk hosts: "{{ computer_name }}" become: yes vars: disk2: "{{ lookup('env', 'DISK2') | default(false) }}" #Переводится в true если потребовался второй диск и при составлении заявки вы выбрали true tasks: - name: reboot #Проводим ребут и ждем когда сервер перезапустится shell: | #!/bin/bash /sbin/shutdown -r now sleep 45 async: 1 poll: 0 - name: Wait wait_for_connection: delay: 30 timeout: 45 - name: Resize block: - name: Copy #Проводими создание LVM, разметку и мапинг папки к диску copy: dest: /home/ansible_usr/disk2.sh content: | #!/bin/bash sudo echo -e "nn\np\n1\n\n\nw\nq" | sudo fdisk /dev/sdb sudo pvcreate /dev/sdb1 sudo vgcreate datavolume /dev/sdb1 sudo lvcreate -l +100%FREE -n data datavolume sudo mkfs.ext4 /dev/mapper/datavolume-data sudo mkdir -p "{{ mount_folder }}" sudo echo "/dev/datavolume/data {{ mount_folder }} ext4 rw 1 1" >> /etc/fstab sudo mount -a mode: '0755' - name: Execute shell: /home/ansible_usr/disk2.sh - name: Remove file: path: /home/ansible_usr/disk2.sh state: absent when: disk2 | bool - name: Skip disk2 #Пропускаем процедуру, если false debug: msg: "Disk skipped." when: not disk2 | bool

В данном случае mount_folder был заполнен как /var/postgres1
Так же реализована отправка webhook после окончания выполнения workflow

Уведомление отработает в случае успеха/провала выполнения workflow и отправит статус на локально стоящий flask сервер, который забирает значение статуса и смотрит на заполненную переменную JiraTicket. Данная переменная при создании «тикета» в jira заполняется автоматический и передается в AWX, чтобы отправился issue_key и сделал новый статус заявки.

По итогу, данный способ предоставления инфраструктурных мощностей вполне оправдан, особенно, если ваши рабочие процессы постоянно связаны с деплоем ВМ, а второе по трате времени — это jira с ее insight и прочими вещами. Остается сделать динамику полей и провести логические ассоциации с проектами.
В целом, даже если jira не входит в ваш рабочий стек, можно использовать web-интерфейс AWX с его Survey.
ссылка на оригинал статьи https://habr.com/ru/articles/876934/
Добавить комментарий