Native improvement environments with Terraform + LXD | Digital Noch

Native improvement environments with Terraform + LXD | Digital Noch

As a Huge Knowledge Options Architect and InfraOps, I would like improvement environments to put in and take a look at software program. They should be configurable, versatile, and performant. Working with distributed programs, the best-fitting setups for this use case are native virtualized clusters of a number of Linux cases.

For just a few years, I’ve been utilizing HashiCorp’s Vagrant to handle libvirt/KVM cases. That is working properly, however I just lately skilled one other setup that works higher for me: LXD to handle cases and Terraform (one other HashiCorp software) to function LXD. On this article, I clarify what the benefits of the latter are and the best way to setup such an atmosphere.


Vagrant and Terraform

Vagrant allows customers to create and configure light-weight, reproducible, and transportable improvement environments. It’s largely used to provision digital machines regionally.

Terraform is a extensively used Infrastructure as Code software that enables provisioning sources on nearly any cloud. It helps many suppliers from public cloud (AWS, Azure, GCP) to non-public self-hosted infrastructure (OpenStack, Kubernetes, and LXD in fact). With Terraform, InfraOps groups apply GitOps greatest practices to handle their infrastructure.

Linux virtualization/containerization

Here’s a fast evaluate of the assorted instruments (and acronyms) used on this article and composing the crowded Linux virtualization/containerization ecosystem:

Having Vagrant working KVM hosts is achieved with the vagrant-libvirt supplier. See KVM machines for Vagrant on Archlinux for the best way to setup libvirt/KVM with Vagrant.

Why Terraform?

LXD is utilized in CLI with the lxc command to handle it’s sources (containers and VMs, networks, storage swimming pools, occasion profiles). Being a command-based software, it’s by nature not Git pleasant.

Happily, there’s a Terraform supplier to handle LXD: terraform-provider-lxd. This allows versioning LXD infrastructure configuration alongside software code.

Observe: One other software to function LXD might be Canonical’s Juju, however it appears a bit extra advanced to study.

Why Terraform + LXD? Benefits over Vagrant + libvirt/KVM

Reside resizing of cases

Linux containers are extra versatile than VMs, which permits resizing cases with out reboot. It is a very handy function.

Unified tooling from improvement to manufacturing

LXD will be put in on a number of hosts to make a cluster that can be utilized as the bottom layer of a self-hosted cloud. The Terraform + LXD couple can thus be used to handle native, integration, and manufacturing environments. This considerably eases testing and deploying infrastructure configurations.

LXD help in Ansible

To put in and configure software program on the native cases, I typically use Ansible. There are a number of connection plugins out there to Ansible to connect with the goal hosts, the principle one being ssh.

When provisioning LXC cases we are able to use the usual ssh plugin but additionally a native LXC plugin: lxc (which makes use of the LXC Python library) or lxd (which makes use of the LXC CLI). That is helpful for 2 causes:

  • For safety as we don’t have to begin an OpenSSH server and open the SSH port on our cases
  • For simplicity as we don’t should handle SSH keys for Ansible

Configuration modifications preview

One of many fundamental options of Terraform is the flexibility to preview the modifications {that a} command would apply. This avoids undesirable configuration deployments and command errors.

Instance with an LXD occasion profile’s resizing:

$ terraform plan
Terraform will carry out the next actions:

  ~ useful resource "lxd_profile" "tdp_profiles" 
      ~ config = 
          ~ "limits.cpu"    = "1" -> "2"
          ~ "limits.reminiscence" = "1GiB" -> "2GiB"
        id     = "tdp_edge"
        identify   = "tdp_edge"

Plan: 0 so as to add, 1 to vary, 0 to destroy.

Configuration readability and modularity

The Terraform language is declarative. It describes an meant aim somewhat than the steps to succeed in that aim. As such, it’s extra readable than the Ruby language utilized in Vagrant information. Additionally as a result of Terraform parses all information within the present listing and permits defining modules with inputs and outputs, we are able to very simply cut up the configuration to extend maintainability.

$ ls -1 | grep -P '.tf(vars)?$'

Efficiency acquire

Utilizing Terraform + LXD hastens day by day operations in native improvement environments which is all the time pleasurable.

Here’s a efficiency benchmark when working an area improvement cluster with the next specs:

  • Host OS: Ubuntu 20.04
  • Variety of visitor cases: 7
  • Sources allotted: 24GiB of RAM and 24 vCPUs
MetricVagrant + libvirt/KVMTerraform + LXDEfficiency acquire
Cluster creation (sec)56.5511.1x quicker
Cluster startup (sec)36.566x quicker
Cluster shutdown (sec)4613.53.4x quicker
Cluster destroy (sec)9172x slower

Setup of a minimal Terraform + LXD atmosphere

Now let’s attempt to setup a minimal Terraform + LXD atmosphere.


Your laptop wants:

  • LXD (see Set up)
  • Terraform >= 0.13 (see Set up Terraform)
  • Linux cgroup v2 (to run current Linux containers like Rocky 8)
  • 5 GB of RAM out there

Additionally create a listing to work from:

mkdir terraform-lxd-xs
cd terraform-lxd-xs

Linux cgroup v2

To test in case your host makes use of cgroup v2, run:

stat -fc %T /sys/fs/cgroup

Latest distributions use cgroup v2 by default (test the record right here) however the function is on the market on all hosts that run a Linux kernel >= 5.2 (e.g. Ubuntu 20.04). To allow it, see Enabling cgroup v2.

Terraform supplier

We are going to use the terraform-lxd/lxd Terraform supplier to handle our LXD sources.


    lxd = 
      supply  = "terraform-lxd/lxd"
      model = "1.7.1"

supplier "lxd" 
  generate_client_certificates = true
  accept_remote_certificate    = true

Variables definition

It’s a good follow to permit customers to configure the Terraform atmosphere via enter variables. We implement the variable correctness by declaring their anticipated varieties.


variable "xs_storage_pool" 
  sort = object(
    identify   = string
    supply = string

variable "xs_network" 
  sort = object(
    ipv4 = object(
      handle = string

variable "xs_profiles" 
  sort = record(object(
    identify = string
    limits = object(
      cpu    = quantity
      reminiscence = string

variable "xs_image" 
  sort    = string
  default = "photos:rocky/8"

variable "xs_containers" 
  sort = record(object(
    identify    = string
    profile = string
    ip      = string

The next variables are outlined:

  • xs_storage_pool: the LXD storage pool storing the disks of our containers
  • xs_network: the LXD IPv4 community utilized by containers to speak inside a shared community
  • xs_profiles: the LXD profiles created for our containers. Profiles enable the definition of a set of properties that may be utilized to any container.
  • xs_image: the LXD picture. This primarily specifies which OS the containers run.
  • xs_containers: The LXD cases to create.


The principle Terraform file defines all of the sources configured via the variables. This file will not be modified fairly often by builders after its first implementation for the undertaking.


useful resource "lxd_storage_pool" "xs_storage_pool" 
  identify = var.xs_storage_pool.identify
  driver = "dir"
  config = 
    supply = "$path.cwd/$path.module/$"

useful resource "lxd_network" "xs_network" 
  identify = "xsbr0"

  config = 
    "ipv4.handle" = var.xs_network.ipv4.handle
    "ipv4.nat"     = "true"
    "ipv6.handle" = "none"

useful resource "lxd_profile" "xs_profiles" 
  depends_on = [

  for_each = 
    for index, profile in var.xs_profiles :
    profile.identify => profile.limits

  identify = every.key

  config = 
    "boot.autostart" = false
    "limits.cpu"    = every.worth.cpu
    "limits.reminiscence" = every.worth.reminiscence

    sort = "disk"
    identify = "root"

    properties = 
      pool = var.xs_storage_pool.identify
      path = "/"

useful resource "lxd_container" "xs_containers" 
  depends_on = [

  for_each = 
    for index, container in var.xs_containers :
    container.identify => container

  identify  = every.key
  picture = var.xs_image
  profiles = [

    identify = "eth0"
    sort = "nic"
    properties = 
      community        = lxd_network.xs_network.identify
      "ipv4.handle" = "$every.worth.ip"

The next sources are created by Terraform:

  • lxd_network.xs_network: the community for all our cases
  • lxd_profile.xs_profiles: a number of profiles that may be outlined by the person
  • lxd_container.xs_containers: the cases’ definitions (together with the applying of the profile and the community system attachment)

Variables file

Lastly, we offer Terraform with the variables particular to the environment. We use the auto.tfvars extension to robotically load the variables when terraform is run.


xs_storage_pool = 
  identify = "xs_storage_pool"
  supply = "lxd-xs-pool"

xs_network = 
  ipv4 =  handle = "" 

xs_profiles = [
    name = "xs_master"
    limits = 
      cpu    = 1
      memory = "1GiB"
    name = "xs_worker"
    limits = 
      cpu    = 2
      memory = "2GiB"

xs_image = "photos:rockylinux/8"

xs_containers = [
    name    = "xs-master-01"
    profile = "xs_master"
    ip      = ""
    name    = "xs-master-02"
    profile = "xs_master"
    ip      = ""
    name    = "xs-worker-01"
    profile = "xs_worker"
    ip      = ""
    name    = "xs-worker-02"
    profile = "xs_worker"
    ip      = ""
    name    = "xs-worker-03"
    profile = "xs_worker"
    ip      = ""

Atmosphere provisioning

Now now we have all of the information wanted to provision the environment:

terraform init

mkdir lxd-xs-pool

terraform apply

As soon as the sources are created, we are able to test that the whole lot is working high-quality:

lxc community record
lxc profile record
lxc record

lxc shell xs-master-01

Et voilà!

Observe: To destroy the atmosphere: terraform destroy

Extra superior instance

You’ll be able to check out tdp-lxd for a extra superior setup with:

  • Extra profiles
  • File templating (for an Ansible stock)
  • Outputs definition


The mix of Terraform and LXD brings a brand new means of managing native improvement environments that has a number of benefits over rivals (specifically Vagrant). If you’re typically bootstrapping this sort of atmosphere, I counsel you give it a strive!

#Native #improvement #environments #Terraform #LXD

Related articles


Leave a reply

Please enter your comment!
Please enter your name here

Skip to toolbar