Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

libvirt example #994

Merged
merged 2 commits into from
May 7, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
libvirt example
  • Loading branch information
MitchellGerdisch committed May 6, 2021
commit 710337a0e99546c60e52b0b245ad44f308d40de6
3 changes: 3 additions & 0 deletions libvirt-py-vm/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*.pyc
venv/
*.priv
6 changes: 6 additions & 0 deletions libvirt-py-vm/Pulumi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
name: libvirt-py-vm
runtime:
name: python
options:
virtualenv: venv
description: Uses libvirt to deploy a KVM VM on an Azure-hosted KVM server.
58 changes: 58 additions & 0 deletions libvirt-py-vm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
[![Deploy](https://get.pulumi.com/new/button.svg)](https://app.pulumi.com/new)

# Using the Pulumi Libvirt Provider to Deploy a VM on a KVM Server

Deploys a KVM server in Azure and then deploys a small Linux VM on that KVM server.
It uses the Pulumi Libvirt provider (https://www.pulumi.com/docs/reference/pkg/libvirt/) and nested virtualization that is supported by certain Azure instance types to accomplish this.

## Running the App

1. Create a new stack:

```
$ pulumi stack init dev
```

1. Login to Azure CLI (you will be prompted to do this during deployment if you forget this step):

```
$ az login
```

1. Create a Python virtualenv, activate it, and install dependencies:

This installs the dependent packages [needed](https://www.pulumi.com/docs/intro/concepts/how-pulumi-works/) for our Pulumi program.

```bash
$ python3 -m venv venv
$ source venv/bin/activate
$ pip3 install -r requirements.txt
```

1. Set the Azure region location to use:

```
$ pulumi config set azure-native:location westus
```

1. Run `pulumi up` to preview and deploy changes:

```
$ pulumi up
Previewing changes:
...

Performing changes:
...
Resources:
+ 12 created
Duration: 3m36s
```

1. Check the VM on the KVM host:
The stack generates an output that provides a string you can execute to run `virsh` remotely on the KVM host.
It will look something like
```
echo virsh list | ssh -i libvirt-ex-dev-kvm_server.priv [email protected]
```
Additionally, you can ssh to the KVM host and use virsh locally to explore more details.
67 changes: 67 additions & 0 deletions libvirt-py-vm/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Copyright 2016-2020, Pulumi Corporation. All rights reserved.
#
# There are two main parts to this project:
# - Standing up a KVM host
# - Using that KVM host to create a VM (aka Domain) and related storage resources.
#
# The KVM host is created via a component resource.
# Any KVM host can be used as long as the connection URI is returned to the main so that
# the libvirt provider can be instantiated.
#
# In this case, the KVM host component creates a VM in Azure and uses an ssh connection URI.
#

import pulumi as pulumi
from pulumi import Config, Output, ResourceOptions, export
import pulumi_libvirt as libvirt
import libvirt_host

# Get some stack-related config data
stackname = pulumi.get_stack()
config = Config()
basename = config.get("basename") or "libvirt-ex"
basename = f"{basename}-{stackname}"

### Create a KVM host
libvirt_server = libvirt_host.Server(basename)

### Use the KVM host
# Create a provider using the connection URI returned by the KVM host component
libvirt_provider = libvirt.Provider(f"{basename}-libvirt",
uri=libvirt_server.libvirt_remote_uri
)

# Create a storage pool for the KVM VM that is going to be launched.
vm_pool = libvirt.Pool(f"{basename}-vm_pool",
args=libvirt.PoolArgs(type="dir", path=libvirt_server.vm_pool_dir),
opts=ResourceOptions(provider=libvirt_provider)
)
export("libvirt pool name", vm_pool.name)

# Create a small linux volume that contains a tiny (and thus fast to download) linux.
vm_vol = libvirt.Volume(f"{basename}-linux",
pool=vm_pool.name,
source="http:https://download.cirros-cloud.net/0.5.2/cirros-0.5.2-x86_64-disk.img",
format="qcow2",
opts=ResourceOptions(provider=libvirt_provider)
)
export("libvirt volume name", vm_vol.name)

# Create a VM using the volume created above.
vm = libvirt.Domain(f"{basename}-vm",
memory=512,
vcpu=1,
disks=[libvirt.DomainDiskArgs(
volume_id=vm_vol.id
)],
network_interfaces=[libvirt.DomainNetworkInterfaceArgs(
network_name="default",
wait_for_lease=True,
)],
opts=ResourceOptions(provider=libvirt_provider)
)
export("libvirt VM name", vm.name)

# Export a command that can be used to see that there is indeed a VM running under KVM on the KVM host.
test_cmd = Output.concat('echo virsh list | ssh -i ', libvirt_server.ssh_priv_key_file, ' ',libvirt_server.username,'@',libvirt_server.ip)
export("Check the libvirt VM on the KVM host", test_cmd)
150 changes: 150 additions & 0 deletions libvirt-py-vm/libvirt_host.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
# Builds a KVM host in Azure using an instance type that supports nested virtualization.
# It uses SSH remote connection URI.
# See the following for more details: https://libvirt.org/uri.html#URI_remote

import pulumi as pulumi
from pulumi import ComponentResource, ResourceOptions
from pulumi import Config, Output, ResourceOptions, export
import pulumi_azure_native.compute as compute
import pulumi_azure_native.network as network
import pulumi_azure_native.resources as resources
import pulumi_tls as tls
import os as os
import time as time
import base64


class Server(ComponentResource):

def __init__(self,
name: str,
opts: ResourceOptions = None):

super().__init__('custom:resource:LibvirtHost', name, {}, opts)

basename = f"{name}-kvm"
username = "kvmuser"
computername = "kvmhost"

# Resource group, etc for the KVM host
resource_group = resources.ResourceGroup(f"{basename}-rg", opts=ResourceOptions(parent=self))

net = network.VirtualNetwork(
f"{basename}-net",
resource_group_name=resource_group.name,
address_space=network.AddressSpaceArgs(
address_prefixes=["10.0.0.0/16"],
),
subnets=[network.SubnetArgs(
name="default",
address_prefix="10.0.1.0/24",
)],
opts=ResourceOptions(parent=self))

public_ip = network.PublicIPAddress(
f"{basename}-ip",
resource_group_name=resource_group.name,
public_ip_allocation_method=network.IPAllocationMethod.DYNAMIC,
opts=ResourceOptions(parent=self))

network_iface = network.NetworkInterface(
f"{basename}-nic",
resource_group_name=resource_group.name,
ip_configurations=[network.NetworkInterfaceIPConfigurationArgs(
name="serveripcfg",
subnet=network.SubnetArgs(id=net.subnets[0].id),
private_ip_allocation_method=network.IPAllocationMethod.DYNAMIC,
public_ip_address=network.PublicIPAddressArgs(id=public_ip.id),
)],
opts=ResourceOptions(parent=self))

# SSH key for accessing the Azure VM that is going to be the KVM host.
ssh_key = tls.PrivateKey(f"{basename}-sshkey", algorithm="RSA", rsa_bits=4096, opts=ResourceOptions(parent=self))

# Script to configure the kvm service on the kvm host
init_script = f"""#!/bin/bash

# Install KVM
sudo apt update
sudo apt-get -y install qemu-kvm libvirt-bin
# hack to account for this bug: https://bugs.launchpad.net/ubuntu/+source/libvirt/+bug/1677398
# Work around: https://bugs.launchpad.net/ubuntu/+source/libvirt/+bug/1677398/comments/42
sudo sed -i '$ a security_driver = "none"' /etc/libvirt/qemu.conf
sudo systemctl restart libvirt-bin

"""

vm = compute.VirtualMachine(
f"{basename}-vm",
resource_group_name=resource_group.name,
network_profile=compute.NetworkProfileArgs(
network_interfaces=[
compute.NetworkInterfaceReferenceArgs(id=network_iface.id),
],
),
hardware_profile=compute.HardwareProfileArgs(
vm_size=compute.VirtualMachineSizeTypes.STANDARD_D4S_V3
),
os_profile=compute.OSProfileArgs(
computer_name=computername,
admin_username=username,
custom_data=base64.b64encode(init_script.encode("ascii")).decode("ascii"),
linux_configuration=compute.LinuxConfigurationArgs(
ssh=compute.SshConfigurationArgs(
public_keys=[compute.SshPublicKeyArgs(
key_data=ssh_key.public_key_openssh,
path=f"/home/{username}/.ssh/authorized_keys"
)]
)
)
),
storage_profile=compute.StorageProfileArgs(
os_disk=compute.OSDiskArgs(
create_option=compute.DiskCreateOptionTypes.FROM_IMAGE,
),
image_reference=compute.ImageReferenceArgs(
publisher="canonical",
offer="UbuntuServer",
sku="18.04-LTS",
version="latest",
),
),
opts=ResourceOptions(parent=self))

# There's some delay between when Azure says the VM is ready and
# when the KVM/qemu service can start accepting connections.
# So, wait a bit to allow the KVM server to become fully ready.
# But only do the wait if the VM has been provisioned (i.e. not during a preview).
vm.provisioning_state.apply(lambda state: time.sleep(90))

combined_output = Output.all(public_ip.name, resource_group.name, vm.id)
public_ip_addr = combined_output.apply(
lambda lst: network.get_public_ip_address(
public_ip_address_name=lst[0],
resource_group_name=lst[1]))

# Create/update the private key file for the SSH remote connection URI.
def write_key_file(priv_key, key_file):
if (os.path.exists(key_file)):
os.chmod(key_file, 0o666)
f = open(key_file, "w")
f.write(priv_key)
f.close()
os.chmod(key_file, 0o400)
key_file=f"{basename}_server.priv"
ssh_key.private_key_pem.apply(lambda priv_key: write_key_file(priv_key, key_file))

# Build the connection URI that is returned for use by the libvirt provider.
# See https://libvirt.org/uri.html#URI_remote for details on the remote URI options
self.libvirt_remote_uri = Output.concat("qemu+ssh:https://",username,"@",public_ip_addr.ip_address,"/system?keyfile=./",key_file,"&socket=/var/run/libvirt/libvirt-sock&no_verify=1")

# Return where the VM pool should be created.
# In this case, the "vm pool" is simply placed under the KVM host user's home folder
self.vm_pool_dir = f"/home/{username}/vms"

# Other values for convenience to output useful information
self.ip = public_ip_addr.ip_address
self.username = username
self.ssh_priv_key_file = key_file

self.register_outputs({})
4 changes: 4 additions & 0 deletions libvirt-py-vm/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
pulumi>=2.0.0,<4.0.0
pulumi-azure-native>=0.0.0,<2.0.0
pulumi-tls>=4.0.0,<5.0.0
pulumi-libvirt>=0.1.0,<1.0.0