Please note that this guide is a work in progress. If you find any issues at all, please don't hesitate to report them either in the #KUBE100 Slack for beta testers, or on the GitHub repository.
Terraform is an industry-leading Infrastructure-as-code tool that allows you to use simple declarative statements to provision resources on cloud servers. This guide will guide you through the basic functionality of the Terraform Provider for Civo, allowing you to get started with provisioning Civo instances and services through Terraform files.
Install the provider
This guide will assume that you have Terraform installed for your particular operating system. Follow these installation instructions if you need to acquire Terraform before returning to this guide.
We will need to install the provider for your platform. Linux, Mac OS and Windows binaries can be downloaded from here. After you have downloaded and decompressed the binary, you'll need to place it in the Plugins folder for your specific operating system, like this:
├── .terraform
│ ├── plugins
│ ├── terraform-provider-civo_v0.9.0
In my case, as I use a Mac, the folder where plugins resides is in a hidden directory called .terraform
in my home directory. Depending on your platform, your Plugins directory will reside in a different place. For example, for Linux, the directory name is .terraform.d/
.
Once you have located the correct directory, copy the downloaded Civo Terraform provider file into it. Once you have copied the provider plugin to its folder, we need to declare the provider to make it usable. Start by creating a file called provider.tf
in your ~/.terraform
directory (i.e. one level above plugins
) with this inside:
provider "civo" {
token = "your_token"
}
You can find your API token in your Civo account under Security, or alternatively on the Civo API documentation page provided you are logged in to your account.
Save this file as provider.tf
and you will be able to run terraform init
. You should see a message like this:
Initializing the backend...
Initializing provider plugins...
The following providers do not have any version constraints in configuration,
so the latest version was installed.
To prevent automatic upgrades to new major versions that may contain breaking
changes, it is recommended to add version = "..." constraints to the
corresponding provider blocks in configuration, with the constraint strings
suggested below.
* provider.civo: version = "~> 0.9.0"
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.
Quick Start: Working with the Civo Terraform provider
Instances
The provider is under active development. If you find any issues or discrepancies below, please report them in the github repository.
We will be starting with the civo_instances
resource. In order to create a new instance you can create a file like instances.tf
with the content based on the snippet below. For all documentation, refer to the help here
resource "civo_instance" "my-test-instance" {
hostname = "foo.com"
tags = ["python", "nginx"]
notes = "this is a note for the server"
size = element(data.civo_instances_size.small.sizes, 0).name
template = element(data.civo_template.debian.templates, 0).id
}
The file above will need references for what the instance_size
and template
will be. For that, we'll need to create a file called data-source.tf
with this content:
data "civo_instances_size" "small" {
filter {
key = "name"
values = ["g2.small"]
}
}
data "civo_template" "debian" {
filter {
key = "code"
values = ["buster"]
}
}
To see all options for this data source you can read the doc here for instance_size
and here for the template
.
Deploying a configuration
By running terraform plan
, you will be able to see the configuration plan to be executed. For the above example, it would look something like this:
$ terraform plan
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.
data.civo_template.debian: Refreshing state...
data.civo_instances_size.medium: Refreshing state...
------------------------------------------------------------------------
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:
# civo_instance.my-test-instance will be created
+ resource "civo_instance" "my-test-instance" {
+ created_at = (known after apply)
+ hostname = "test-terraform"
+ id = (known after apply)
+ initial_password = (known after apply)
+ initial_user = "civo"
+ notes = "Test note to the server"
+ private_ip = (known after apply)
+ pseudo_ip = (known after apply)
+ public_ip = (known after apply)
+ size = "g2.medium"
+ status = (known after apply)
+ tags = [
+ "nginx",
]
+ template = "fffbe2e5-0dd8-476b-b480-cb7c9fccbe39"
}
Plan: 1 to add, 0 to change, 0 to destroy.
------------------------------------------------------------------------
You will be able to execute the plan and watch your instance get deployed by running terraform apply
:
$ terraform apply
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:
# civo_instance.my-test-instance will be created
+ resource "civo_instance" "my-test-instance" {
+ created_at = (known after apply)
+ hostname = "test-terraform"
+ id = (known after apply)
+ initial_password = (known after apply)
+ initial_user = "civo"
+ notes = "Test note to the server"
+ private_ip = (known after apply)
+ pseudo_ip = (known after apply)
+ public_ip = (known after apply)
+ size = "g2.medium"
+ status = (known after apply)
+ tags = [
+ "nginx",
]
+ template = "fffbe2e5-0dd8-476b-b480-cb7c9fccbe39"
}
Plan: 1 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:
DNS
Now, to manage the dns_domain_name
and dns_domain_record
resources, we need to create a file called dns.tf
with this content altered for your personal domain details:
resource "civo_dns_domain_name" "main" {
name = "mydomain.com"
}
resource "civo_dns_domain_record" "www" {
domain_id = civo_dns_domain_name.main.id
type = "a"
name = "www"
value = civo_instance.my-test-instance.public_ip
ttl = 600
depends_on = [civo_dns_domain_name.main, civo_instance.my-test-instance]
}
In this case we use depends_on
because in order to create a domain record the domain name must exist and the instance must exist too. The depends_on
option is the way to tell Terraform it needs to wait for the resource in the list to be created before creating any dependent resources. Details on DNS documentation can be found here and here
Firewall
Now, we will see how to create a firewall using the provider. For this, we'll need to create another file, in this case the name will be firewall.tf
. To extend the functionality of our service, suppose you have more than one instance declared in Terraform, with the names vps01 and vps02. We want to make a firewall rule to allow access to a MySQL server:
resource "civo_firewall" "mysql" {
name = "mysql"
}
resource "civo_firewall_rule" "mysql-inbound" {
firewall_id = civo_firewall.mysql.id
protocol = "tcp"
start_port = "3306"
end_port = "3306"
cidr = [format("%s/%s",civo_instance.vps01.public_ip,"32"), format("%s/%s",civo_instance.vps02.public_ip,"32")]
direction = "inbound"
label = "server mysql"
depends_on = [civo_firewall.mysql]
}
First we create a firewall with the name mysql
, followed by a rule for that firewall with the name mysql-inbound
. If you take a look at the cidr
attribute, it is a list of two sets of civo_instance
: vps01 and vps02 and for both we get the public_ip
attribute.
Here the depends_on
option works in the same way as above. Now, to add this rule to our MySQL instance simply add the line firewall_id = civo_firewall.mysql.id
to the civo_instance
that runs MySQL. But be careful - if you include this line and the firewall doesn't exist, an error will be raised. If you are creating all from scratch then you can use the depends_on
option inside the instance resource like this depends_on = [civo_firewall.mysql]
.
You can read all documentation about firewalls and firewall rules here and here.
Load balancer
We will still be using the same two instances vps01 and vps02. These instances will stay connected to the MySQL instance, but we need a way to handle all traffic in front of vps01 and vps02. The way to do that is by creating a load balancer. Just like before, we need to create a file to define this service. Call a new file lb.tf
and inside this file put the following code snippet. To see all options about load balancers you can read the doc here.
resource "civo_loadbalancer" "www-lb" {
hostname = "www.mydomain.com"
protocol = "http"
port = 80
max_request_size = 30
policy = "round_robin"
max_conns = 10
fail_timeout = 40
backend {
instance_id = civo_instance.vps01.id
protocol = "http"
port = 80
}
backend {
instance_id = civo_instance.vps02.id
protocol = "http"
port = 80
}
}
Conclusion
So far we have a functional service using the Terraform provider, with instances having traffic routed to them in turn through a load balancer, and a firewall rule allowing traffic between the instances and a mysql database. If you would like to know more and explore how all possible resources work, you can visit the GitHub repository of the project here. Let us know what you think by raising an issue, feature request or best of all, submit a pull request!