Configuring bare-metal Packet servers with cloud-init
Tags: devops, cloud, linux
Published: 2020-06-27 16:31:19

#Introduction#

Oftentimes there will be cases where you will want to automate the provisioning and configuration of your Packer bare-metal infrastructure. There are a plethora of tools out there, however, `cloud-init` is an industry-standard that is used to initialize and configure instances with `user-data`.

#What is Terraform?#

Terraform is an Infrastructure-as-code tool that allows users to build, change, and version your infrastructure safely and efficiently. It uses a high-level syntax to declaratively provision and manage infrastructure, allowing the ability to break down the configuration into smaller chunks for better organization, re-use, and maintainability. Information on installing and running Terraform can be found [here](https://www.terraform.io/intro/index.html). By passing the `user_data` parameter into a Terraform.yaml file, you can use automation to configure your Packet instance at boot time. More on that below.

#Using Terraform to configure Packet devices with cloud-init#

If Terraform is your preferred infrastructure provisioning method then you can find the Packet Terraform Provider at the Github Repo [here](https://www.terraform.io/docs/providers/packet/index.html).

For any infrastructure provider, when using Terraform as a provisioning tool you will always need to specify the `provider` block as seen here:

```
provider "packet" {
  auth_token = "${var.auth_token}"
}
```

Here's an example module that utilizes `user-data` to configure a Packet instance at boot time:

```
# Create a server
resource "packet_device" "my-dream-server-1" {
    hostname         = "tf.coreos2"
    plan             = "t1.small.x86"
    facilities       = ["ewr1"]
    operating_system = "coreos_stable"
    billing_cycle    = "hourly"
    project_id       = local.project_id
    user_data = "I2Nsb3VkLWNvbmZpZwpwYWNrYWdlczoKICAtIGlmdG9wCiAgLSBubW9uCg=="
    project_ssh_keys_ids = ["95"]
    tags          = {
        Name        = "Application Server 2"
        Environment = "development"
    }
}

```

With this module, you have a `resource` that is designating `packet_device` as the type of resource you want to provision, and using variables such as `project_id` and `user_data` to handle the provisioning. When you provide the string for `user_data`, you are designating a startup script that the bare-metal server will run on boot-up.

###Using cloud-init to configure Packet devices###

You can provision new servers via the API to fetch user data for your Packet device via the `cloud-init` service. This allows you to automate various server configuration tasks by fetching user data directives upon server deployment. Your provided tasks will be executed when your server boots for the first time. There are two ways of doing this - **shell scripts** or **cloud-init** directives. We're going to talk about cloud-init directives.

Cloud-Init directives are executed when your server boots for the first time, but the syntax is slightly different. Your scenario must start with `#cloud-config line`, otherwise user data directives will be rejected. For further reference, I recommend checking the cloud-init official documentation: https://cloudinit.readthedocs.io/en/latest/index.html

*A simple example of a `cloud-init` script that would be passed is*:

```
#cloud-config

packages:

 - httpd

 - mariadb-server

runcmd:

 - systemctl start httpd

 - sudo systemctl enable httpd

 - [ sh, -c, "chmod 755 /var/tmp" ]
```

In order to pass this data scenario to the Packet API, it must first be converted into **base64** format. On a Linux system you would do the following for your `test.yaml` file:

```
# base64 test.yaml

I2Nsb3VkLWNvbmZpZwpwYWNrYWdlczogCiAtIGh0dHBkIAogLSBtYXJpYWRiLXNlcnZlcgpydW5j

bWQ6CiAtIHN5c3RlbWN0bCBzdGFydCBodHRwZAogLSBzdWRvIHN5c3RlbWN0bCBlbmFibGUgaHR0

cGQKIC0gWyBzaCwgLWMsICJjaG1vZCA3NTUgL3Zhci90bXAiIF0K
```

This output text then has to be fetched via Packet API `user_data` parameter when ordering a new server.

###Putting it all together###

To see this in action, specify the resources `provider` so that you can designate Packet as the platform you're provisioning to. Your final script should look like this:

```
# Set provider

provider "packet" {
  auth_token = "${var.auth_token}"
}

# Create a server
resource "packet_device" "my-dream-server-1" {
    hostname         = "tf.coreos2"
    plan             = "t1.small.x86"
    facilities       = ["ewr1"]
    operating_system = "coreos_stable"
    billing_cycle    = "hourly"
    project_id       = local.project_id
    user_data = "I2Nsb3VkLWNvbmZpZwpwYWNrYWdlczoKICAtIGlmdG9wCiAgLSBubW9uCg=="
    project_ssh_keys_ids = ["95"]
    tags          = {
        Name        = "Application Server 2"
        Environment = "development"
    }
}
```

#Finishing up#

That's all there is to it! Terraform is a really great tool for automating infrastructure once you understand the syntax and how it works.