Nate Vick
Posted on September 7, 2021
CDK or Cloud Development Kit came out of AWS in 2018 as a way to write Infrastructure as Code in software languages used day-to-day by developers (JavaScript/TypeScript, Python, Java, C#, and soon Go). Since its release, a community has built up around it, and new flavors have arrived. AWS CDK, CDKTF, and CDK8s are all based on the same core, but compile to different formats. AWS CDK compiles to CloudFormation, while CDKTF compiles to Terraform compatible JSON, and CDK8s compiles to Kubernetes config.
In this post, we will walk through how to use CDKTF with DigitalOcean's Terraform provider. A Terraform provider is a "plugin" for Terraform to interact with remote systems. In this case, the provider is created and maintained by DigitalOcean. We will share a few examples of creating a Digital Ocean Project, a VPC, a Postgres Database, and a Droplet.
Prereqs and Install
To use cdktf you will need the following packages installed:
- Terraform β₯ 0.12
- Node.js β₯ 12.16
- Yarn β₯ 1.21
To install cdktf, we will use npm install
$ npm install --global cdktf-cli
You will also need to create a personal access token on Digital Ocean if you would like to deploy this config.
Create and Initialize the example project
First let's make a directory and change into it.
$ mkdir cdk-do-example && cd cdk-do-example
Initialize the project with the init
command. In general, I use the --local
flag, so all state is stored locally.
$ cdktf init --template=typescript --local
You will be prompted for Project Name and Description but defaults are fine.
The last step of setting up the project is to add Terraform providers to the cdktf.json
. Open the project in your editor and let's add the DigitalOcean provider to it as follows.
{
"language": "typescript",
"app": "npm run --silent compile && node main.js",
"terraformProviders": [
"digitalocean/digitalocean@~> 2.9"
],
"terraformModules": [],
"context": {
"excludeStackIdFromLogicalIds": "true",
"allowSepCharsInLogicalIds": "true"
}
}
Install Dependencies
Run cdktf get
to download the dependencies for using DigitalOcean with Typescript.
$ cdktf get
Generated typescript constructs in the output directory: .gen
Let's Write Some TypeScript
Open main.ts
in your editor and let's start by creating a DO Project.
import { Construct } from 'constructs'
import { App, TerraformStack, Token } from 'cdktf'
import { DigitaloceanProvider } from './.gen/providers/digitalocean'
import { Project } from './.gen/providers/digitalocean'
class MyStack extends TerraformStack {
constructor(scope: Construct, name: string) {
super(scope, name)
new DigitaloceanProvider(this, 'digitalocean', {
token: Token.asString(process.env.DO_TOKEN)
})
new Project(this, 'example-project', {
name: 'Example Rails Project'
})
}
}
const app = new App()
new MyStack(app, 'cdk-do-example')
app.synth()
You will create a new DigitaloceanProvider
and pass in the environment variable assigned to your DO personal access token. Next, we create the Project
, which has a required key of name
.
Optionally, as a test of the code above, run cdktf deploy
to deploy your CDKTF project. The cli will ask if you want to make the changes listed under Resources. Type 'yes', then once it finishes, you will see a green check next to the Resources it successfully created.
Now we will add a VPC, a Postgres Database, and a Droplet. Once those examples are in, we will tie it all together before deploying it again.
Create a VPC
import { Construct } from 'constructs'
import { App, TerraformStack, Token } from 'cdktf'
import { DigitaloceanProvider, Droplet, Vpc } from './.gen/providers/digitalocean'
import { Project } from './.gen/providers/digitalocean'
class MyStack extends TerraformStack {
constructor(scope: Construct, name: string) {
super(scope, name)
new DigitaloceanProvider(this, 'digitalocean', {
token: Token.asString(process.env.DO_TOKEN)
})
...more code above
new Vpc(this, 'example-vpc', {
name: 'example-vpc',
region: 'sfo3'
})
}
}
const app = new App()
new MyStack(app, 'cdk-do-example')
app.synth()
*Note: If you do not have VPC in your account already this will become the default VPC which will not be deleted when cleaning up the CDKTF Project.
Create a Postgres Database
import { Construct } from 'constructs'
import { App, TerraformStack, Token } from 'cdktf'
import { DigitaloceanProvider, Droplet, Vpc, DatabaseCluster, DatabaseUser, DatabaseDb } from './.gen/providers/digitalocean'
import { Project } from './.gen/providers/digitalocean'
class MyStack extends TerraformStack {
constructor(scope: Construct, name: string) {
super(scope, name)
new DigitaloceanProvider(this, 'digitalocean', {
token: Token.asString(process.env.DO_TOKEN)
})
...more code above
const postgres = new DatabaseCluster(this, 'example-postgres', {
name: 'example-postgres',
engine: 'pg',
version: '13',
size: 'db-s-1vcpu-1gb',
region: 'sfo3',
nodeCount: 1
})
new DatabaseUser(this, 'example-postgres-user', {
clusterId: `${postgres.id}`,
name: 'example'
})
new DatabaseDb(this, 'example-postgres-db', {
clusterId: `${postgres.id}`,
name: 'example-db'
})
}
}
const app = new App()
new MyStack(app, 'cdk-do-example')
app.synth()
Notice we assigned the DatabaseCluster
to a variable const postgres
. We then use the variable to create the DatabaseUser
and DatabaseDb
on that cluster.
Create a Droplet
import { Construct } from 'constructs'
import { App, TerraformStack, Token } from 'cdktf'
import { DigitaloceanProvider, Droplet, Vpc, DatabaseCluster, DatabaseUser, DatabaseDb } from './.gen/providers/digitalocean'
import { Project } from './.gen/providers/digitalocean'
class MyStack extends TerraformStack {
constructor(scope: Construct, name: string) {
super(scope, name)
new DigitaloceanProvider(this, 'digitalocean', {
token: Token.asString(process.env.DO_TOKEN)
})
...more code above
new Droplet(this, 'example-droplet', {
name: 'example-droplet',
size: 's-1vcpu-1gb',
region: 'sfo3',
image: 'ubuntu-20-04-x64'
})
}
}
const app = new App()
new MyStack(app, 'cdk-do-example')
app.synth()
Let's Put It All Together
If you run cdktf deploy
now, it would create everything, but nothing created would be put into the Digital Ocean project or the VPC we create. Let's do that now.
import { Construct } from 'constructs'
import { App, TerraformStack, Token } from 'cdktf'
import { DigitaloceanProvider, Droplet, Vpc} from './.gen/providers/digitalocean'
import { DatabaseCluster, DatabaseUser, DatabaseDb } from './.gen/providers/digitalocean'
import { Project, ProjectResources } from './.gen/providers/digitalocean'
class MyStack extends TerraformStack {
constructor(scope: Construct, name: string) {
super(scope, name)
new DigitaloceanProvider(this, 'digitalocean', {
token: Token.asString(process.env.DO_TOKEN)
})
const project = new Project(this, 'example-project', {
name: 'Example Rails Project'
})
const vpc = new Vpc(this, 'example-vpc', {
name: 'example-vpc',
region: 'sfo3'
})
const postgres = new DatabaseCluster(this, 'example-postgres', {
name: 'example-postgres',
engine: 'pg',
version: '13',
size: 'db-s-1vcpu-1gb',
region: 'sfo3',
nodeCount: 1,
privateNetworkUuid: vpc.id
})
new DatabaseUser(this, 'example-postgres-user', {
clusterId: `${postgres.id}`,
name: 'example'
})
new DatabaseDb(this, 'example-postgres-db', {
clusterId: `${postgres.id}`,
name: 'example-db'
})
const droplet = new Droplet(this, 'example-droplet', {
name: 'example-droplet',
size: 's-1vcpu-1gb',
region: 'sfo3',
image: 'ubuntu-20-04-x64',
vpcUuid: vpc.id
})
new ProjectResources(this, 'example-project-resources', {
project: project.id,
resources: [
postgres.urn,
droplet.urn
],
dependsOn: [ postgres, droplet ]
})
}
}
const app = new App()
new MyStack(app, 'cdk-do-example')
app.synth()
We start by assigning the project, VPC, and droplet to variables. In the DatabaseCluster
definition, we add privateNetworkUuid: [vpc.id](http://vpc.id)
to place the database in our newly created VPC. Similarly, on the Droplet
definition, we place it in the VPC, by adding vpcUuid: vpc.id
.
Lastly, create a new ProjectResource
to assign other resources to the Digital Ocean project. In this small example, we will assign the database and droplet to the project using the urn
for each resource. We will wait to assign those until both are created using a Terraform helper dependsOn
.
With all of that in place you can deploy again. The database and droplet creation take a bit, so be patient. π Once it has finished, check your Digital Ocean Dashboard to see everything created and ready for use.
*Make sure you run cdktf destroy
to remove these resources from your account or you will be charged by Digital Ocean.*
Things To Know
CDKTF is still a young project, so it is changing fast, has limited documentation, and you can run into unclear errors.
There are few things that help with the limited documentation. You can read the provider documentation on the Terraform registry site and use it as a guide. Also, using a language like Typescript with VSCode there are a lot of code hints, hover info, and signature information. The gif below is an example of what is shown in VSCode when you hover on the problem. Note the missing properties for DropletConfig
.
When you run into unclear errors, prepend CDKTF_LOG_LEVEL=debug
to the deploy and/or destroy commands to get very verbose output.
Wrapping Up
In this post, we used CDKTF to create some basic example resources on Digital Ocean which gives you a good primer to build more complex infrastructure in a language of your choice. You can find the code from this post in this repo. If you would like to chat more about CDK or infrastructure as code you can ping me on Twitter @natron99.
Posted on September 7, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
October 18, 2024