I recently posted about provisioning a Virtual Machine on the MAAS system, using a web User Interface. You can read about it in the following post.
The same effect can be achieved using the terminal as MAAS provides a command line interface tool. Nevertheless, it is alright when provisioning a single machine, but gets troublesome when a whole infrastructure with dozens of virtual machines is required. So why not automate such a process?
Infrastructure as Code
Infrastructure as Code (IaC) is the automated process of managing and provisioning an infrastructure through code instead of through manual processes. The IT infrastructure managed by this process comprises physical equipment, such as bare-metal servers, virtual machines, and associated configuration resources. Infrastructure specification is described by configuration files that can be stored in the version control system, easily edited, and distributed.
There are generally two approaches to IaC: declarative (functional) vs. imperative (procedural). The difference between the declarative and the imperative approach is essentially what versus how. The declarative approach defines the desired state and the system executes what needs to happen to achieve that desired state. Imperative defines specific commands that need to be executed in the appropriate order to end with the desired conclusion.
One of the tools that provide capabilities for managing infrastructure as a code is Terraform.
Terraform by HashiCorp
What is Terraform?
Terraform is an infrastructure as a code tool that let you define both cloud and on-prem resources in human-readable configuration files. They can be versioned, reused, and shared. It enables consistent workflow to provision and manage all of the infrastructure throughout its lifecycle. Terraform can manage low-level as well as high-level components, like computing, storage, networking resources, DNS entries, etc.
Terraform workflow consists of three stages:
Write: You define resources. For instance, you might create a configuration to deploy a virtual machine and a load balancer.
Plan: Terraform creates an execution plan describing the infrastructure it will create, update, or destroy based on the existing infrastructure and your configuration.
Apply: On approval, Terraform performs the proposed operations maintaining correct order and dependencies.
Terraform creates and manages resources on cloud platforms and other services through its application programming interfaces. Terraform Providers enable Terraform to work with any platform or service with an accessible API.
Canonical provides its own Terraform Provider for MAAS that can be easily employed for provisioning resources on bare-metal servers.
GitHub - maas/terraform-provider-maas: Terraform MAAS provider
Initializing Terraform
First of all, we have to install Terraform itself. It can be downloaded directly from the website or installed through the package manager. In my case I used Brew.
brew install terraform
Once installation is finished, we create a working directory to describe our infrastructure and start with defining the provider. Let's create providers.tf
file and add the following content.
terraform {
required_providers {
maas = {
source = "maas/maas"
version = "~>1.0"
}
}
}
Now, we can issue the first Terraform command.
❯ terraform plan
╷
│ Error: Inconsistent dependency lock file
│
│ The following dependency selections recorded in the lock file are inconsistent with the current configuration:
│ - provider registry.terraform.io/maas/maas: required by this configuration but no version is selected
│
│ To make the initial dependency selections that will initialize the dependency lock file, run:
│ terraform init
The output informs that we need properly initialize dependencies. Let's run it.
❯ terraform init
Initializing the backend...
Initializing provider plugins...
- Finding maas/maas versions matching "~> 1.0"...
╷
│ Error: Failed to query available provider packages
│
│ Could not retrieve the list of available versions for provider maas/maas: provider registry registry.terraform.io does not have a provider named
│ registry.terraform.io/maas/maas
│
│ All modules should specify their required_providers so that external consumers will get the correct providers when using a module. To see which modules are
│ currently depending on maas/maas, run the following command:
│ terraform providers
╵
It turns out that MAAS Terraform Provider is not in the repository. Therefore, we have to handle this situation on our own. Let's clone the MAAS Terraform Provider repository.
❯ git clone git@github.com:maas/terraform-provider-maas.git
Cloning into 'terraform-provider-maas'...
remote: Enumerating objects: 888, done.
remote: Counting objects: 100% (292/292), done.
remote: Compressing objects: 100% (192/192), done.
remote: Total 888 (delta 140), reused 150 (delta 89), pack-reused 596
Receiving objects: 100% (888/888), 298.28 KiB | 1.20 MiB/s, done.
Resolving deltas: 100% (491/491), done.
Then we have to navigate into the directory and run a build.
❯ cd terraform-provider-maas
❯ make install
mkdir -p /Users/slysj/terraform-provider-maas/bin
go build -o /Users/slysj/terraform-provider-maas/bin/terraform-provider-maas
mkdir -p ~/.terraform.d/plugins/registry.terraform.io/maas/maas/1.0.1/$(go env GOOS)_$(go env GOARCH)
mv /Users/slysj/terraform-provider-maas/bin/terraform-provider-maas ~/.terraform.d/plugins/registry.terraform.io/maas/maas/1.0.1/$(go env GOOS)_$(go env GOARCH)
❗ make and golang must be present in the system to execute a build.
Now, we can rerun the initialization.
❯ terraform init
Initializing the backend...
Initializing provider plugins...
- Finding maas/maas versions matching "~> 1.0"...
- Installing maas/maas v1.0.1...
- Installed maas/maas v1.0.1 (unauthenticated)
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.
╷
│ Warning: Incomplete lock file information for providers
│
│ Due to your customized provider installation methods, Terraform was forced to calculate lock file checksums locally for the following providers:
│ - maas/maas
│
│ The current .terraform.lock.hcl file only includes checksums for darwin_amd64, so Terraform running on another platform will fail to install these
│ providers.
│
│ To calculate additional checksums for another platform, run:
│ terraform providers lock -platform=linux_amd64
│ (where linux_amd64 is the platform to generate)
╵
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.
We have successfully initialized the working directory!
Now, we have to update the providers.tf
file to include the MAAS API key and MAAS URL. API key has to be obtained from MAAS UI.
Click your username at the top right (in my case it is kuba), and pick API keys. Copy the API key and paste it into providers.tf
file.
terraform {
required_providers {
maas = {
source = "maas/maas"
version = "~>1.0"
}
}
}
provider "maas" {
api_version = "2.0"
api_key = "<YOUR API KEY>"
api_url = "http://127.0.0.1:5240/MAAS"
}
The updated file should include your API key in place of <YOUR API KEY>, and your MAAS address instead of http://127.0.0.1:5240/MAAS. Now, we are ready to write a configuration file to deploy the Virtual Machine instances.
Provisioning with Terraform
Let's define main.tf
file with maas_vm_host_machine
and maas_instance
to provision a virtual machine with an operating system.
resource "maas_vm_host_machine" "test-vm" {
vm_host = "macmini01"
cores = 2
memory = 2048
hostname = "test-vm"
storage_disks {
size_gigabytes = 50
}
pool = "default"
}
resource "maas_instance" "test-vm" {
allocate_params {
min_cpu_count = 2
min_memory = 2048
}
deploy_params {
distro_series = "focal"
}
depends_on = [maas_vm_host_machine.test-vm]
}
The maas_vm_host_machine
test-vm
resource will provision a virtual machine on bare-metal macmini01
host. This machine will have 2 vCores, 2048 GB of RAM, and 50 GB of storage. The maas_instance
test-vm
will match the characteristics of maas_vm_host_machine
and deploy Ubuntu "focal" on the machine. depends_on
section means we have to create a virtual machine first before we can install an operating system on it. Now we are ready to apply the configuration!
❯ terraform 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:
# maas_instance.test-vm will be created
+ resource "maas_instance" "test-vm" {
+ cpu_count = (known after apply)
+ fqdn = (known after apply)
+ hostname = (known after apply)
+ id = (known after apply)
+ ip_addresses = (known after apply)
+ memory = (known after apply)
+ pool = (known after apply)
+ tags = (known after apply)
+ zone = (known after apply)
+ allocate_params {
+ min_cpu_count = 2
+ min_memory = 2048
+ tags = []
}
+ deploy_params {
+ distro_series = "focal"
}
}
# maas_vm_host_machine.test-vm will be created
+ resource "maas_vm_host_machine" "test-vm" {
+ cores = 2
+ domain = (known after apply)
+ hostname = "test-vm"
+ id = (known after apply)
+ memory = 2048
+ pool = "default"
+ vm_host = "macmini01"
+ zone = (known after apply)
+ storage_disks {
+ size_gigabytes = 50
}
}
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:
We can review the changes, type yes
and hit Enter.
maas_vm_host_machine.test-vm: Creating...
maas_vm_host_machine.test-vm: Still creating... [10s elapsed]
...
maas_vm_host_machine.test-vm: Still creating... [2m0s elapsed]
...
maas_vm_host_machine.test-vm: Still creating... [2m10s elapsed]
...
maas_vm_host_machine.test-vm: Still creating... [3m0s elapsed]
maas_vm_host_machine.test-vm: Creation complete after 3m4s [id=aqn8qt]
maas_instance.test-vm: Creating...
...
maas_instance.test-vm: Still creating... [3m0s elapsed]
...
maas_instance.test-vm: Still creating... [5m30s elapsed]
maas_instance.test-vm: Creation complete after 5m34s [id=aqn8qt]
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
test-vm
has been created, it is visible on MAAS UI and we can now ssh
to it.
❯ ssh test-vm.maas
The authenticity of host 'test-vm.maas (192.168.111.114)' can't be established.
ED25519 key fingerprint is SHA256:nUNPVw+kSN2Gro8a2aggy2Qbolzq5C8PORUMz7i+IiE.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'test-vm.maas' (ED25519) to the list of known hosts.
Welcome to Ubuntu 20.04.5 LTS (GNU/Linux 5.4.0-137-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
System information as of Sat Jan 28 11:50:21 UTC 2023
System load: 0.0 Processes: 111
Usage of /: 9.7% of 45.53GB Users logged in: 0
Memory usage: 11% IPv4 address for ens4: 192.168.111.114
Swap usage: 0%
43 updates can be applied immediately.
36 of these updates are standard security updates.
To see these additional updates run: apt list --upgradable
The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.
ubuntu@test-vm:~$
Once the provisioned infrastructure is not needed anymore, it can be easily disposed of by running destroy.
❯ terraform destroy
maas_vm_host_machine.test-vm: Refreshing state... [id=aqn8qt]
maas_instance.test-vm: Refreshing state... [id=aqn8qt]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
- destroy
Terraform will perform the following actions:
# maas_instance.test-vm will be destroyed
- resource "maas_instance" "test-vm" {
- cpu_count = 2 -> null
- fqdn = "test-vm.maas" -> null
- hostname = "test-vm" -> null
- id = "aqn8qt" -> null
- ip_addresses = [
- "192.168.111.114",
] -> null
- memory = 2048 -> null
- pool = "default" -> null
- tags = [
- "pod-console-logging",
- "virtual",
] -> null
- zone = "default" -> null
- allocate_params {
- min_cpu_count = 2 -> null
- min_memory = 2048 -> null
- tags = [] -> null
}
- deploy_params {
- distro_series = "focal" -> null
}
}
# maas_vm_host_machine.test-vm will be destroyed
- resource "maas_vm_host_machine" "test-vm" {
- cores = 2 -> null
- domain = "maas" -> null
- hostname = "test-vm" -> null
- id = "aqn8qt" -> null
- memory = 2048 -> null
- pool = "default" -> null
- vm_host = "macmini01" -> null
- zone = "default" -> null
- storage_disks {
- size_gigabytes = 50 -> null
}
}
Plan: 0 to add, 0 to change, 2 to destroy.
Do you really want to destroy all resources?
Terraform will destroy all your managed infrastructure, as shown above.
There is no undo. Only 'yes' will be accepted to confirm.
Enter a value:
Review the changes, type yes
, and hit Enter to run it.
maas_instance.test-vm: Destroying... [id=aqn8qt]
maas_instance.test-vm: Still destroying... [id=aqn8qt, 10s elapsed]
maas_instance.test-vm: Destruction complete after 13s
maas_vm_host_machine.test-vm: Destroying... [id=aqn8qt]
maas_vm_host_machine.test-vm: Destruction complete after 3s
Conclusion
Infrastructure as a code eases maintaining IT infrastructure throughout its full lifecycle. Terraform is the tool that implements this approach. Thanks to the notion of providers, Terraform is easily extendable and can automate the provisioning process on different clouds. MAAS Terraform provider enables provisioning on Canonical MAAS. The provider is pretty useful and usable but has plenty of glitches, therefore still requires developers' attention.