LXC, or Linux Containers is a userspace interface that provides containment features of the Linux kernel. LXC lets users create Linux containers which are as close as possible to a standard Linux installation but using the same Kernel as the host machine.

Terraform on the other hand is an Infrastructure as code tool used for automation of infrastructure deployment. Terraform is widely used with cloud providers to automate management of their infrastructure as it provides a wide range of mechanisms both for deployment and management of existing infrastructure.

Terraform uses configuration files which describes the components needed to run a single application or your entire datacenter, and desired state will be effected.

This guide we shall discuss how to use Terraform to deploy LXC containers on Ubuntu LTS. The pointers below highlight what we shall achieve at the end of this guide:

  • Install LXC on Ubuntu
  • Install Terraform on Ubuntu
  • Create LXC container using Terraform
  • Attach volume to a container using Terraform
  • Configure container network using Terraform

Let’s dive right in!

Install LXC on Ubuntu / Debian

We need to install and initialize LXC on our Ubuntu host. Use the following steps:

  1. Update and upgrade your system:
sudo apt update
sudo apt upgrade

2. Install LXD

sudo apt install lxd -y

3. Initialize LXC

Run the command below to initialize LXC and setup some defaults

sudo lxd init

You will have to answer some questions according to how you want to setup your LXC environment. e.g.

$ sudo lxd init
Would you like to use LXD clustering? (yes/no) [default=no]:  
Do you want to configure a new storage pool? (yes/no) [default=yes]:  
Name of the new storage pool [default=default]:  
Name of the storage backend to use (btrfs, dir, lvm, zfs, ceph) [default=zfs]:  
Create a new ZFS pool? (yes/no) [default=yes]:  
Would you like to use an existing empty disk or partition? (yes/no) [default=no]:  
Size in GB of the new loop device (1GB minimum) [default=5GB]:  
Would you like to connect to a MAAS server? (yes/no) [default=no]:  
Would you like to create a new local network bridge? (yes/no) [default=yes]:  
What should the new bridge be called? [default=lxdbr0]:  
What IPv4 address should be used? (CIDR subnet notation, “auto” or “none”) [default=auto]:  
What IPv6 address should be used? (CIDR subnet notation, “auto” or “none”) [default=auto]:  
Would you like LXD to be available over the network? (yes/no) [default=no]:  
Would you like stale cached images to be updated automatically? (yes/no) [default=yes]  
Would you like a YAML "lxd init" preseed to be printed? (yes/no) [default=no]:

We now have an LXC/LXD infrastructure ready to start deployment of Linux Containers.

Install Terraform on Ubuntu / Debian

Use the steps below to install Terraform on your host.

  1. Add the HashiCorp GPG key
curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo apt-key add -

2. Add HashiCorp repository

sudo apt-add-repository "deb [arch=amd64] https://apt.releases.hashicorp.com $(lsb_release -cs) main"

3. Install Terraform

sudo apt-get update
sudo apt-get install terraform

4. Enable tab completion

$ terraform -install-autocomplete

We have our terraform installed and ready to be used. You can verify installed version by using the command below:

$ terraform -version

Install LXC container using Terraform

The next step is to configure Terraform so we can use it to install LXC containers. We shall be using LXD Terraform provider to connect provision resources. Create a new terraform main.tf configuration file that will define the provider to be used.

$ vim main.tf
terraform {
  required_providers {
    lxd = {
      source = "terraform-lxd/lxd"
      version = "1.5.0"
    }
  }
}

Initialize to download provider and create necessary files.

$ terraform init

Initializing the backend...

Initializing provider plugins...
- Finding terraform-lxd/lxd versions matching "1.5.0"...
- Installing terraform-lxd/lxd v1.5.0...
- Installed terraform-lxd/lxd v1.5.0 (self-signed, key ID 62BC1162214B5D1E)

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/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!

Deploy container with Terraform

We have to update our main.tf file to add container resource definitions:

$ vim main.tf

terraform {
  required_providers {
    lxd = {
      source = "terraform-lxd/lxd"
      version = "1.5.0"
    }
  }
}

resource "lxd_container" "server1" {
  name      = "server1"
  image     = "ubuntu:18.04"
  ephemeral = false
  profiles = ["default"]

}

Check what will be deployed buy running the command below:

$ terraform plan

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # lxd_container.server1 will be created
  + resource "lxd_container" "server1" {
      + ephemeral        = false
      + id               = (known after apply)
      + image            = "ubuntu:18.04"
      + ip_address       = (known after apply)
      + ipv4_address     = (known after apply)
      + ipv6_address     = (known after apply)
      + mac_address      = (known after apply)
      + name             = "server1"
      + privileged       = false
      + profiles         = [
          + "default",
        ]
      + start_container  = true
      + status           = (known after apply)
      + target           = (known after apply)
      + type             = (known after apply)
      + wait_for_network = true
    }

Plan: 1 to add, 0 to change, 0 to destroy.

Run the terraform apply command to apply the changes. You will be prompted to confirm if the script should proceed making changes.

$ terraform apply

......
Plan: 1 to add, 0 to change, 0 to destroy.


Warning: provider set empty string as default value for bool generate_client_certificates



Warning: provider set empty string as default value for bool accept_remote_certificate


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

lxd_container.server1: Creating...
lxd_container.server1: Still creating... [10s elapsed]
lxd_container.server1: Still creating... [20s elapsed]
lxd_container.server1: Still creating... [30s elapsed]
lxd_container.server1: Still creating... [40s elapsed]
lxd_container.server1: Still creating... [50s elapsed]
lxd_container.server1: Still creating... [1m0s elapsed]
lxd_container.server1: Still creating... [1m10s elapsed]
lxd_container.server1: Still creating... [1m20s elapsed]
lxd_container.server1: Creation complete after 1m21s [id=server1]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

You can now check and see if your instance is created:

$ lxc list

Sample output:

how to create LXC containers using Terraform

You can access the created server using the command below:

$ lxc exec server1 /bin/bash

Remember to use a name of your choice for server1 during container creation and accessing the container.

Attach Volume to a Container

You can also create a volume and attach it to your container. We will first create a storage pool of type=dir and assign a path on the server where the for the pool.

We then will create a volume then attach the volume to our container. We then will define where the new volume shall be mounted on the container.

Update your main.tf file to have all the components discussed above as shown below:

terraform {
  required_providers {
    lxd = {
      source = "terraform-lxd/lxd"
      version = "1.5.0"
    }
  }
}


resource "lxd_storage_pool" "pool1" {
  name = "mypool"
  driver = "dir"
  config = {
    source = "/var/lib/lxd/storage-pools/mypool"
  }
}

resource "lxd_volume" "volume1" {
  name = "myvolume"
  pool = "${lxd_storage_pool.pool1.name}"
}

resource "lxd_container" "server1" {
  name      = "server1"
  image     = "ubuntu:18.04"
  ephemeral = false
  profiles = ["default"]

device {
    name = "volume1"
    type = "disk"
    properties = {
      path = "/opt/"
      source = "${lxd_volume.volume1.name}"
      pool = "${lxd_storage_pool.pool1.name}"
    }
   }                                                                                                                            
  }

Run the terraform plan command to see what changes will be effected when you apply.

$ terraform apply

lxd_container.server1: Refreshing state... [id=server1]

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create
  ~ update in-place

Terraform will perform the following actions:

  # lxd_container.server1 will be updated in-place
  ~ resource "lxd_container" "server1" {
        id               = "server1"
        name             = "server1"
        # (14 unchanged attributes hidden)

      + device {
          + name       = "volume1"
          + properties = {
              + "path"   = "/opt/"
              + "pool"   = "mypool"
              + "source" = "myvolume"
            }
          + type       = "disk"
        }
    }

  # lxd_storage_pool.pool1 will be created
  + resource "lxd_storage_pool" "pool1" {
      + config = {
          + "source" = "/var/lib/lxd/storage-pools/mypool"
        }
      + driver = "dir"
      + id     = (known after apply)
      + name   = "mypool"
    }

  # lxd_volume.volume1 will be created
  + resource "lxd_volume" "volume1" {
      + expanded_config = (known after apply)
      + id              = (known after apply)
      + name            = "myvolume"
      + pool            = "mypool"
      + type            = "custom"
    }

Plan: 2 to add, 1 to change, 0 to destroy.

Then apply the changes.

$ terraform apply

....
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

lxd_storage_pool.pool1: Creating...
lxd_storage_pool.pool1: Creation complete after 0s [id=mypool]
lxd_volume.volume1: Creating...
lxd_volume.volume1: Creation complete after 0s [id=mypool/myvolume/custom]
lxd_container.server1: Modifying... [id=server1]
lxd_container.server1: Modifications complete after 0s [id=server1]

Apply complete! Resources: 2 added, 1 changed, 0 destroyed.

In the screenshot below, we can see that the volume has been attached to /opt of the container.

how to create LXC containers using Terraform 2

Configure container network using Terraform

The last part of this article is about how to setup container network and use it to provision conainers.

This part involved creating a profile, LXC uses namespaces(profiles) do define the containers. We will create a new profile configure the attributes including the creation of a new network bridge and DHCP server. We shall then provision our containers in the new namespace/profile to make use of the new changes we shall have made.

Modify your main.tf file and add the following:

resource "lxd_network" "new_default" {
  name = "new_default"

  config = {
    "ipv4.address" = "10.150.19.1/24"
    "ipv4.nat"     = "true"
    "ipv6.address" = "fd42:474b:622d:259d::1/64"
    "ipv6.nat"     = "true"
  }
}

resource "lxd_profile" "profile1" {
  name = "profile1"

  device {
    name = "eth0"
    type = "nic"

    properties = {
      nictype = "bridged"
      parent  = "${lxd_network.new_default.name}"
    }
  }
 device {
    type = "disk"
    name = "root"

    properties = {
      pool = "default"
      path = "/"
    }
  }
}

We have named our profile as profile1. A network bridge has also been created.

We will need to modify the existing container state to use the new profile if we wish to use the new network subnet. The new main.tf configuration file should be as below:

terraform {
  required_providers {
    lxd = {
      source = "terraform-lxd/lxd"
      version = "1.5.0"
    }
  }
}


resource "lxd_storage_pool" "pool1" {
  name = "mypool"
  driver = "dir"
  config = {
    source = "/var/lib/lxd/storage-pools/mypool"
  }
}

resource "lxd_volume" "volume1" {
  name = "myvolume"
  pool = "${lxd_storage_pool.pool1.name}"
}

resource "lxd_network" "new_default" {
  name = "new_default"

  config = {
    "ipv4.address" = "10.150.19.1/24"
    "ipv4.nat"     = "true"
    "ipv6.address" = "fd42:474b:622d:259d::1/64"
    "ipv6.nat"     = "true"
  }
}

resource "lxd_profile" "profile1" {
  name = "profile1"
  device {
    name = "eth0"
    type = "nic"

    properties = {
      nictype = "bridged"
      parent  = "${lxd_network.new_default.name}"
    }
  }
device {
    type = "disk"
    name = "root"

    properties = {
      pool = "default"
      path = "/"
    }
  }
}

resource "lxd_container" "server1" {
  name      = "server1"
  image     = "ubuntu:18.04"
  ephemeral = false
  profiles = ["profile1"]

device {
    name = "volume1"
    type = "disk"
    properties = {
      path = "/opt/"
      source = "${lxd_volume.volume1.name}"
      pool = "${lxd_storage_pool.pool1.name}"
    }
  }
}

Apply the changes then check if your container network has changed.

[email protected]:~/terraform# lxc list
+---------+---------+---------------------+-----------------------------------------------+------------+-----------+
|  NAME   |  STATE  |        IPV4         |                     IPV6                      |    TYPE    | SNAPSHOTS |
+---------+---------+---------------------+-----------------------------------------------+------------+-----------+
| server1 | RUNNING | 10.150.19.13 (eth0) | fd42:474b:622d:259d:216:3eff:fe8c:e44e (eth0) | PERSISTENT | 0         |
+---------+---------+---------------------+-----------------------------------------------+------------+-----------+

We have achieved all the objectives for this article as stated in the beginning. I hope you were able to follow keenly and deploy your first container using Terraform. Check out these interesting articles about Terraform:

How To Store Terraform State in Consul KV Store

How To Provision VMs on oVirt / RHEV with Terraform

Your support is our everlasting motivation,
that cup of coffee is what keeps us going!


As we continue to grow, we would wish to reach and impact more people who visit and take advantage of the guides we have on our blog. This is a big task for us and we are so far extremely grateful for the kind people who have shown amazing support for our work over the time we have been online.

Thank You for your support as we work to give you the best of guides and articles. Click below to buy us a coffee.

LEAVE A REPLY

Please enter your comment!
Please enter your name here