Terraform: Crear máquina virtual en Hyper-V
Hoy os vamos a explicar como crear máquinas virtuales y recursos en Hyper-V utilizando el software de automatización Terraform.
Os dejo algunos enlaces de referencia:
- https://github.com/flynnhandley/terraform-provider-hyperv
- https://github.com/Captn138/hyperv-terraform
- https://registry.terraform.io/providers/taliesins/hyperv/latest/docs
Para los que no tenéis instalado Hyper-V, instalarlo con Powershell:
1 |
Enable-WindowsOptionalFeature -Online -FeatureName:Microsoft-Hyper-V -All |
Habilitar WinRM en Windows 10
Habilitamos WinRM:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 |
############## # Enable WinRM ############## Enable-PSRemoting -SkipNetworkProfileCheck -Force Set-WSManInstance WinRM/Config/WinRS -ValueSet @{MaxMemoryPerShellMB = 1024} Set-WSManInstance WinRM/Config -ValueSet @{MaxTimeoutms=1800000} Set-WSManInstance WinRM/Config/Client -ValueSet @{TrustedHosts="*"} Set-WSManInstance WinRM/Config/Service/Auth -ValueSet @{Negotiate = $true} # WINRM ALLOW HTTPS #Create CA certificate $rootCaName = "DevRootCA" $rootCaPassword = ConvertTo-SecureString "P@ssw0rd" -asplaintext -force $rootCaCertificate = Get-ChildItem cert:\LocalMachine\Root |?{$_.subject -eq "CN=$rootCaName"} if (!$rootCaCertificate){ Get-ChildItem cert:\LocalMachine\My |?{$_.subject -eq "CN=$rootCaName"} | remove-item -force if (Test-Path .\$rootCaName.cer) { remove-item .\$rootCaName.cer -force } if (Test-Path .\$rootCaName.pfx) { remove-item .\$rootCaName.pfx -force } $params = @{ Type = 'Custom' DnsName = $rootCaName Subject = "CN=$rootCaName" KeyExportPolicy = 'Exportable' CertStoreLocation = 'Cert:\LocalMachine\My' KeyUsageProperty = 'All' KeyUsage = 'None' Provider = 'Microsoft Strong Cryptographic Provider' KeySpec = 'KeyExchange' KeyLength = 4096 HashAlgorithm = 'SHA256' KeyAlgorithm = 'RSA' NotAfter = (Get-Date).AddYears(5) } $rootCaCertificate = New-SelfSignedCertificate @params Export-Certificate -Cert $rootCaCertificate -FilePath .\$rootCaName.cer -Verbose Export-PfxCertificate -Cert $rootCaCertificate -FilePath .\$rootCaName.pfx -Password $rootCaPassword -Verbose Get-ChildItem cert:\LocalMachine\My |?{$_.subject -eq "CN=$rootCaName"} | remove-item -force Import-PfxCertificate -FilePath .\$rootCaName.pfx -CertStoreLocation Cert:\LocalMachine\Root -password $rootCaPassword -Exportable -Verbose Import-PfxCertificate -FilePath .\$rootCaName.pfx -CertStoreLocation Cert:\LocalMachine\My -password $rootCaPassword -Exportable -Verbose $rootCaCertificate = Get-ChildItem cert:\LocalMachine\My |?{$_.subject -eq "CN=$rootCaName"} } #Create host certificate using CA $hostName = [System.Net.Dns]::GetHostName() $hostPassword = ConvertTo-SecureString "P@ssw0rd" -asplaintext -force $hostCertificate = Get-ChildItem cert:\LocalMachine\My |?{$_.subject -eq "CN=$hostName"} if (!$hostCertificate){ if (Test-Path .\$hostName.cer) { remove-item .\$hostName.cer -force } if (Test-Path .\$hostName.pfx) { remove-item .\$hostName.pfx -force } $dnsNames = @($hostName, "localhost", "127.0.0.1") + [System.Net.Dns]::GetHostByName($env:computerName).AddressList.IpAddressToString $params = @{ Type = 'Custom' DnsName = $dnsNames Subject = "CN=$hostName" KeyExportPolicy = 'Exportable' CertStoreLocation = 'Cert:\LocalMachine\My' KeyUsageProperty = 'All' KeyUsage = @('KeyEncipherment','DigitalSignature','NonRepudiation') TextExtension = @("2.5.29.37={text}1.3.6.1.5.5.7.3.1,1.3.6.1.5.5.7.3.2") Signer = $rootCaCertificate Provider = 'Microsoft Strong Cryptographic Provider' KeySpec = 'KeyExchange' KeyLength = 2048 HashAlgorithm = 'SHA256' KeyAlgorithm = 'RSA' NotAfter = (Get-date).AddYears(2) } $hostCertificate = New-SelfSignedCertificate @params Export-Certificate -Cert $hostCertificate -FilePath .\$hostName.cer -Verbose Export-PfxCertificate -Cert $hostCertificate -FilePath .\$hostName.pfx -Password $hostPassword -Verbose Get-ChildItem cert:\LocalMachine\My |?{$_.subject -eq "CN=$hostName"} | remove-item -force Import-PfxCertificate -FilePath .\$hostName.pfx -CertStoreLocation Cert:\LocalMachine\My -password $hostPassword -Exportable -Verbose $hostCertificate = Get-ChildItem cert:\LocalMachine\My |?{$_.subject -eq "CN=$hostName"} } Get-ChildItem wsman:\localhost\Listener\ | Where-Object -Property Keys -eq 'Transport=HTTPS' | Remove-Item -Recurse New-Item -Path WSMan:\localhost\Listener -Transport HTTPS -Address * -CertificateThumbPrint $($hostCertificate.Thumbprint) -Force -Verbose Restart-Service WinRM -Verbose New-NetFirewallRule -DisplayName "Windows Remote Management (HTTPS-In)" -Name "WinRMHTTPSIn" -Profile Any -LocalPort 5986 -Protocol TCP -Verbose # WINRM ALLOW HTTP # Get the public networks $PubNets = Get-NetConnectionProfile -NetworkCategory Public -ErrorAction SilentlyContinue # Set the profile to private foreach ($PubNet in $PubNets) { Set-NetConnectionProfile -InterfaceIndex $PubNet.InterfaceIndex -NetworkCategory Private } # Configure winrm Set-WSManInstance WinRM/Config/Service -ValueSet @{AllowUnencrypted = $true} # Restore network categories foreach ($PubNet in $PubNets) { Set-NetConnectionProfile -InterfaceIndex $PubNet.InterfaceIndex -NetworkCategory Public } Get-ChildItem wsman:\localhost\Listener\ | Where-Object -Property Keys -eq 'Transport=HTTP' | Remove-Item -Recurse New-Item -Path WSMan:\localhost\Listener -Transport HTTP -Address * -Force -Verbose Restart-Service WinRM -Verbose New-NetFirewallRule -DisplayName "Windows Remote Management (HTTP-In)" -Name "WinRMHTTPIn" -Profile Any -LocalPort 5985 -Protocol TCP -Verbose # ENABLE SSL WINRM $hostName=[System.Net.Dns]::GetHostName() $winrmPort = "5986" # Get the credentials of the machine $cred = Get-Credential # Connect to the machine $soptions = New-PSSessionOption -SkipCACheck -SkipCNCheck Enter-PSSession -ComputerName $hostName -Port $winrmPort -Credential $cred -SessionOption $soptions -UseSSL |
Aseguraros que se ha creado una regla en el cortafuegos y que el servicio “Administración remota de Windows” se queda arrancado (lo mejor es reiniciar el sistema):
Realizamos un test de WinRM:
1 2 3 4 5 6 7 |
PS C:\Users\administrator> Test-WSMan -ComputerName minis.negu.local wsmid : http://schemas.dmtf.org/wbem/wsman/identity/1/wsmanidentity.xsd ProtocolVersion : http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd ProductVendor : Microsoft Corporation ProductVersion : OS: 0.0.0 SP: 0.0 Stack: 3.0 |
Validamos el puerto de escucha, en mi caso HTTP:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
PS D:\Terraform> winrm enumerate winrm/config/listener Listener Address = * Transport = HTTP Port = 5985 Hostname Enabled = true URLPrefix = wsman CertificateThumbprint ListeningOn = 127.0.0.1, 169.254.21.72, 169.254.110.11, 169.254.154.129, 169.254.186.84, 169.254.198.117, 169.254.255.233, 172.19.80.1, 172.29.96.1, 192.168.2.171, ::1, fe80::3909:3ffc:f827:92ae%23, fe80::3b5b:aa62:7c6:74d8%50, fe80::6618:78:2e12:1aaa%13, fe80::6e83:12ab:22bd:51ce%26, fe80::94b6:6bae:325a:eee9%9, fe80::9a05:acd3:9fa8:21ca%60, fe80::ba55:58e0:de0a:667d%5, fe80::c17c:bd42:6bbc:1331%18 Listener Address = * Transport = HTTPS Port = 5986 Hostname Enabled = true URLPrefix = wsman CertificateThumbprint = B7C1E60A80B79945833EDBDDD4B27637094143F0 ListeningOn = 127.0.0.1, 169.254.21.72, 169.254.110.11, 169.254.154.129, 169.254.186.84, 169.254.198.117, 169.254.255.233, 172.19.80.1, 172.29.96.1, 192.168.2.171, ::1, fe80::3909:3ffc:f827:92ae%23, fe80::3b5b:aa62:7c6:74d8%50, fe80::6618:78:2e12:1aaa%13, fe80::6e83:12ab:22bd:51ce%26, fe80::94b6:6bae:325a:eee9%9, fe80::9a05:acd3:9fa8:21ca%60, fe80::ba55:58e0:de0a:667d%5, fe80::c17c:bd42:6bbc:1331%18 |
Forzaré WinRM con HTTPs.
Averiguamos los VMSwitch que disponemos actualmente en Hyper-V para recopilar datos:
1 2 3 4 5 6 |
PS D:\Terraform> Get-VMSwitch Name SwitchType NetAdapterInterfaceDescription ---- ---------- ------------------------------ Default Switch Internal WSL Internal |
Inicializamos Terraform:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
PS D:\Terraform> .\terraform.exe init Initializing the backend... Initializing provider plugins... - Finding taliesins/hyperv versions matching ">= 1.0.3"... - Installing taliesins/hyperv v1.0.4... - Installed taliesins/hyperv v1.0.4 (self-signed, key ID 3470D7F4380DF50A) Partner and community providers are signed by their developers. If you'd like to know more about provider signing, you can read about it here: https://www.terraform.io/docs/cli/plugins/signing.html Terraform has created a lock file .terraform.lock.hcl to record the provider selections it made above. Include this file in your version control repository so that Terraform can guarantee to make the same selections by default when you run "terraform init" in the future. Terraform has been successfully initialized! You may now begin working with Terraform. Try running "terraform plan" to see any changes that are required for your infrastructure. All Terraform commands should now work. If you ever set or change modules or backend configuration for Terraform, rerun this command to reinitialize your working directory. If you forget, other commands will detect it and remind you to do so if necessary. |
Lo que vamos a hacer es generar una máquina virtual mediante terraform en Hyper-V y varios recursos:
Fichero Terraform para crear VMSwitch o disco VHX en Hyper-V de Windows 10
Creamos un fichero .TF, usaré un usuario de directorio activo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
# Configurar HyperV //https://github.com/flynnhandley/terraform-provider-hyperv //https://registry.terraform.io/providers/taliesins/hyperv/latest/docs/resources/machine_instance terraform { required_providers { hyperv = { source = "taliesins/hyperv" version = ">= 1.0.3" } } } provider "hyperv" { user = "negu\\administrator" password = "password" host = "minis" port = 5986 https = true insecure = false use_ntlm = true tls_server_name = "" cacert_path = "" cert_path = "" key_path = "" timeout = "30s" } # Create a switch resource "hyperv_network_switch" "dmz" { name = "DMZ" notes = "" allow_management_os = true enable_embedded_teaming = false enable_iov = false enable_packet_direct = false minimum_bandwidth_mode = "None" switch_type = "Internal" net_adapter_names = [] default_flow_minimum_bandwidth_absolute = 0 default_flow_minimum_bandwidth_weight = 0 default_queue_vmmq_enabled = false default_queue_vmmq_queue_pairs = 16 default_queue_vrss_enabled = false } # Create a vhd resource "hyperv_vhd" "webserver" { path = "D:\\Hyper-V\\webserver.vhdx" vhd_type = "Dynamic" size = 10737418240 #10GB } |
Y ejecutamos con:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
PS D:\Terraform> .\terraform.exe apply Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # hyperv_network_switch.dmz will be created + resource "hyperv_network_switch" "dmz" { + allow_management_os = true + default_flow_minimum_bandwidth_absolute = 0 + default_flow_minimum_bandwidth_weight = 0 + default_queue_vmmq_enabled = false + default_queue_vmmq_queue_pairs = 16 + default_queue_vrss_enabled = false + enable_embedded_teaming = false + enable_iov = false + enable_packet_direct = false + id = (known after apply) + minimum_bandwidth_mode = "None" + name = "DMZ" + net_adapter_names = [] + switch_type = "Internal" } # hyperv_vhd.webserver will be created + resource "hyperv_vhd" "webserver" { + block_size = (known after apply) + exists = false + id = (known after apply) + logical_sector_size = (known after apply) + path = "D:\\Hyper-V\\webserver.vhdx" + physical_sector_size = (known after apply) + size = 10737418240 + vhd_type = "Dynamic" } Plan: 2 to add, 0 to change, 0 to destroy. Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value: yes hyperv_network_switch.dmz: Creating... hyperv_vhd.webserver: Creating... hyperv_vhd.webserver: Still creating... [10s elapsed] hyperv_network_switch.dmz: Still creating... [10s elapsed] hyperv_network_switch.dmz: Creation complete after 13s [id=DMZ] hyperv_vhd.webserver: Creation complete after 16s [id=D:\Hyper-V\webserver.vhdx] Apply complete! Resources: 2 added, 0 changed, 0 destroyed. |
Me ha dado el error (quiero enseñaros que estas entradas tienen un montón de errores antes, y por eso sirven para aprender, a mí el primero y por eso las hago):
1 |
Error: timeout - last error: http response error: 401 - invalid content type |
Esto se debe a que no estaba configurando bien WinRM. Con el script de Powershell deberíais poder trabajar sin problemas.
Fichero Terraform para crear máquina virtual en Hyper-V de Windows 10
Podríamos completar el script de terraform de la siguiente forma:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 |
terraform { required_providers { hyperv = { source = "taliesins/hyperv" version = ">= 1.0.3" } } } provider "hyperv" { user = "negu\\administrator" password = "password" host = "MINIS" port = 5986 https = true insecure = false use_ntlm = true tls_server_name = "" cacert_path = "" cert_path = "" key_path = "" timeout = "30s" } resource "hyperv_network_switch" "dmz" { name = "DMZ" notes = "" allow_management_os = true enable_embedded_teaming = false enable_iov = false enable_packet_direct = false minimum_bandwidth_mode = "None" switch_type = "Internal" net_adapter_names = [] default_flow_minimum_bandwidth_absolute = 0 default_flow_minimum_bandwidth_weight = 0 default_queue_vmmq_enabled = false default_queue_vmmq_queue_pairs = 16 default_queue_vrss_enabled = false } resource "hyperv_vhd" "webserver" { path = "D:\\Hyper-V\\webserver.vhdx" #Needs to be absolute path size = 10737418240 #10GB } resource "hyperv_machine_instance" "default" { name = "WebServer" generation = 2 automatic_critical_error_action = "Pause" automatic_critical_error_action_timeout = 30 automatic_start_action = "StartIfRunning" automatic_start_delay = 0 automatic_stop_action = "Save" checkpoint_type = "Production" guest_controlled_cache_types = false high_memory_mapped_io_space = 536870912 lock_on_disconnect = "Off" low_memory_mapped_io_space = 134217728 memory_maximum_bytes = 1099511627776 memory_minimum_bytes = 536870912 memory_startup_bytes = 536870912 notes = "" processor_count = 1 smart_paging_file_path = "C:\\ProgramData\\Microsoft\\Windows\\Hyper-V" snapshot_file_location = "C:\\ProgramData\\Microsoft\\Windows\\Hyper-V" #dynamic_memory = false static_memory = true state = "Running" # Configure firmware vm_firmware { enable_secure_boot = "Off" #secure_boot_template = "" preferred_network_boot_protocol = "IPv4" console_mode = "None" pause_after_boot_failure = "Off" boot_order { boot_type = "HardDiskDrive" controller_number = "0" controller_location = "0" } boot_order { boot_type = "NetworkAdapter" network_adapter_name = "wan" } } # Configure processor vm_processor { compatibility_for_migration_enabled = false compatibility_for_older_operating_systems_enabled = false hw_thread_count_per_core = 0 maximum = 100 reserve = 0 relative_weight = 100 maximum_count_per_numa_node = 0 maximum_count_per_numa_socket = 0 enable_host_resource_protection = false expose_virtualization_extensions = false } # Configure integration services integration_services = { "Guest Service Interface" = false "Heartbeat" = true "Key-Value Pair Exchange" = true "Shutdown" = true "Time Synchronization" = true "VSS" = true } # Create a network adaptor network_adaptors { name = "wan" switch_name = hyperv_network_switch.dmz.name management_os = false is_legacy = false dynamic_mac_address = true static_mac_address = "" mac_address_spoofing = "Off" dhcp_guard = "Off" router_guard = "Off" port_mirroring = "None" ieee_priority_tag = "Off" vmq_weight = 100 iov_queue_pairs_requested = 1 iov_interrupt_moderation = "Off" iov_weight = 100 ipsec_offload_maximum_security_association = 512 maximum_bandwidth = 0 minimum_bandwidth_absolute = 0 minimum_bandwidth_weight = 0 mandatory_feature_id = [] resource_pool_name = "" test_replica_pool_name = "" test_replica_switch_name = "" virtual_subnet_id = 0 allow_teaming = "On" not_monitored_in_cluster = false storm_limit = 0 dynamic_ip_address_limit = 0 device_naming = "Off" fix_speed_10g = "Off" packet_direct_num_procs = 0 packet_direct_moderation_count = 0 packet_direct_moderation_interval = 0 vrss_enabled = true vmmq_enabled = false vmmq_queue_pairs = 16 } # Create dvd drive dvd_drives { controller_number = "0" controller_location = "1" path = "" #path = "D:\\ISOS\\w2019.iso" resource_pool_name = "" } # Create a hard disk drive hard_disk_drives { controller_type = "Scsi" controller_number = "0" controller_location = "0" path = hyperv_vhd.webserver.path disk_number = 4294967295 resource_pool_name = "Primordial" support_persistent_reservations = false maximum_iops = 0 minimum_iops = 0 qos_policy_id = "00000000-0000-0000-0000-000000000000" override_cache_attributes = "Default" } } |
Te ha gustado la entrada SGUENOS EN TWITTER O INVITANOS A UN CAFE?