How to create an LXC container using Terraform

You can download this article in PDF format via the link below to support us.Download the guide in PDF formatClose

LXC or Linux container is a user space interface that provides the containment function of the Linux kernel. LXC allows users to create Linux containers that are as close as possible to standard Linux installations but use the same kernel as the host.

On the other hand, Terraform is an infrastructure code tool used to automate infrastructure deployment. Terraform is widely used with cloud providers to automate the management of its infrastructure because it provides a wide range of mechanisms to deploy and manage existing infrastructure.

Terraform uses configuration files to describe the components required to run a single application or an entire data center, and will achieve the desired state.

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

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

Let’s dive in!

Install LXC on Ubuntu/Debian

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

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

2. Install LXD

sudo apt install lxd -y

3. Initialize LXC

Run the following command to initialize LXC and set some default values

sudo lxd init

You will have to answer some questions based on how you want to set up the 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]:

Now, we are ready to start deploying the LXC/LXD infrastructure for Linux containers.

Install Terraform on Ubuntu/Debian

Use the following steps to install Terraform on the host.

  1. Add 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 installed Terraform and are ready to use. You can use the following command to verify the installed version:

$ terraform -version

Install LXC container using Terraform

The next step is to configure Terraform so that we can use it to install the LXC container. We will use the LXD Terraform provider to connect to supply 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 the provider and create the 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 the container using Terraform

We must update the main.tf file to add the container resource definition:

$ 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"]

}

Run the following command to check what will be deployed:

$ 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 over terraform apply Command to apply the changes. You will be prompted to confirm whether the script should continue to make 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.

Now you can check and see if an instance was created:

$ lxc list

Sample output:

You can access the created server with the following command:

$ lxc exec server1 /bin/bash

Remember to use the name of your choice server1 During container creation and access.

Attach the volume to the container

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

Then, we will create a volume and then attach the volume to our container. Then, we will define where to install the new volume on the container.

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

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 over terraform plan Order to see what changes will be made 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 Container.

How to create an LXC container using Terraform 2

Configure container network using Terraform

The last part of this article is about how to set up a container network and use it to configure containers.

This part involves creating configuration files. LXC uses namespaces (configuration files) to define containers. We will create a new configuration file and configure properties, including creating a new bridge and DHCP server. We will then configure the container in the new namespace/configuration file to take advantage of the new changes we should make.

Modify the main.tf file and add the following content:

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 the profile profile1. A network bridge was also created.

If we want to use a new network subnet, we need to modify the existing container state to use the new configuration file. The new main.tf configuration file should look like this:

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

As mentioned at the beginning, we have achieved all the goals of this article. I hope you will be keenly aware of and deploy your first container using Terraform. Check out these interesting articles about Terraform:

How to store Terraform status in Consul KV Store

How to configure a VM on oVirt/RHEV with Terraform

You can download this article in PDF format via the link below to support us.Download the guide in PDF formatClose

Sidebar