Deploy an application with CDK for Terraform
The Cloud Development Kit for Terraform (CDKTF) allows you to define infrastructure using familiar programming languages such as TypeScript, Python, or Go. This lets you take advantage of the existing library of providers and modules in the Terraform ecosystem.
Terraform configuration language is a declarative configuration language with functions and constructs that let you manage infrastructure. CDKTF allows you to generate Terraform configuration by writing code in your preferred programming language, with the same tools and workflows as your application code. Using CDKTF allows you to integrate your infrastructure management process with your testing and application deployment pipelines, which makes it easer for developers to work with their own infrastructure.
In this tutorial, you will use the CDK for Terraform to deploy an application on Kubernetes. First, you will use CDKTF to convert Terraform configuration into TypeScript code. Then, you will refactor your code to dynamically generate Terraform configuration to manage an example web application consisting of frontend and backend services. Next, you will create multiple stacks representing the application in different environments. Finally, you will use CDKTF to remove your application.
Prerequisites
This tutorial assumes that you are familiar with the standard Terraform workflow. If you are new to Terraform, complete the Get Started tutorials first.
For this tutorial, you will need:
- Terraform v1.2+
- CDK for Terraform v0.15+
- Docker Desktop installed and running.
- NPM (v8.19+) and NodeJS (v18+) installed.
- kubectl.
- kind.
Launch Terminal
This tutorial includes a free interactive command-line lab that lets you follow along on actual cloud infrastructure.
Clone example repository
Clone the CDKTF Applications GitHub repository for this tutorial.
Change to the repository directory.
This repository contains an example application with frontend and a backend services that you will deploy on Kubernetes as Docker containers. It also contains configuration files that you will use to create a local Kubernetes cluster with kind.
Configure local Kubernetes cluster
Start a local Docker registry to store the container images you will use in this tutorial.
Use kind to create a Kubernetes cluster running in Docker on your local machine.
Verify that your cluster exists by listing your kind clusters.
Then, use kubectl
to print out information about your cluster. The context is
kind-
followed by the name of your cluster.
Create a kubeconfig file to allow access to your Kubernetes cluster.
Now, attach your local Docker registry to your kind cluster.
Configure your Kubernetes cluster to use the local registry.
Your Kubernetes cluster now uses the local Docker registry to store and retrieve Docker images. Next you will use CDKTF to generate images for your application and deploy them to your cluster.
Initialize your CDKTF application
Create a directory for your CDKTF application.
Now change into it.
Initialize your CDKTF application with the typescript
template and a pre-built
Kubernetes provider. For this tutorial, use the --local
flag so that CDKTF
stores your Terraform state locally, rather than in a remote backend such as
HCP Terraform.
When CDKTF prompts you, accept the defaults. CDKTF will initialize your application in the current directory, and print out a help message.
CDKTF created your application from a template.
Create a Kubernetes Deployment
Now that you have initialized your CDKTF application, create a Kubernetes Deployment for your application's frontend.
Add Kubernetes Deployment
Convert example Terraform configuration that defines a Kubernetes Deployment into TypeScript.
The cdktf convert
command converts Terraform configuration into code in the
project's configured programming language. This command is still experimental,
so this output requires some adjustment before you can use it in your project.
Open main.ts
and add code to import the Kubernetes provider and path, and
define a deployment.
This code adds a Kubernetes provider configured with the kubeconfig.yml
file
you created earlier, and a new kubernetes.Deployment
object that represents a
Kubernetes Deployment consisting of a single pod running the
nginx:latest
image.
Install dependencies
Install the path
package with NPM.
Synthesize your application
Use the cdktf synth
command to generate Terraform configuration from your
code.
In your text editor, review the contents of
app/cdktf.out/stacks/app/cdk.tf.json
. This is Terraform configuration in JSON
format that CDKTF generated based on your TypeScript code in main.ts
.
Deploy your app
The cdktf deploy
command will use Terraform to apply the configuration
generated by cdktf synth
.
Create your Kubernetes Deployment with cdktf deploy
. Respond to the
confirmation prompt with Approve
.
The deploy
command synthesizes your Terraform configuration if needed, so you
will skip cdktf synth
for the rest of the tutorial.
List your deployments with kubectl
.
Update replica count
As with Terraform, you can use CDKTF to update your existing resources.
In your editor, update main.ts
to change the number of replicas for your
deployment from 1
to 4
.
Deploy your application again. Respond to the confirmation prompt by choosing
Approve
.
After Kubernetes finishes applying this change, kubectl
will report four
instances of your applicaton.
Refactor your deployment
CDKTF applications are made up of constructs which represent the infrastruture
CDKTF will manage for you. You can extend the Construct
class to define your
own constructs. This way, you can create reusable components that will make up
your application infrastructure.
Create construct
Refactor your Deployment using a CDKTF construct.
Create a new directory for your constructs inside the
learn-terraform-cdktf-applications/app
directory.
Inside the constructs
directory, create a new file named
kubernetes-web-app.ts
for the Kubernetes web application constructs you will
create in this tutorial.
This code imports the Construct
base class, as well as the Kubernetes provider
library you installed earlier. Then, it defines an interface you will use to
configure your web application deployments. Finally, it defines a class named
KubernetesWebAppDeployment
which extends the Construct
base class to define
and configure the Kubernetes Deployments you will use to manage the web
application for this tutorial.
Create a file in the constructs directory named index.ts
, with the following
contents to export the classes and interfaces you define in
kubernetes-web-app.ts
.
Add Deployment construct to CDKTF application
Open app/main.ts
and import your new Construct near top of the file.
Replace the entire new kubernetes.deployment.Deployment(this, "myapp", { ... });
block with a new instance of your construct.
Tip
Be sure to replace the entire kubernetes.deployment.Deployment
block. If you
get stuck editing the file, replace the entire file with the code below.
Now main.ts
will contain:
Deploy your application. This will reduce the number of replicas for your
deployment from four to two, as specified in the arguments to
KubernetesWebAppDeployment
. Respond to the confirmation prompt by choosing
Approve
.
The name of your deployment resource now includes a unique suffix. CDKTF adds this suffix to resources defined inside of constructs to ensure that each resource in the Terraform configuration it generates has a unique name.
Use kubectl
to report your deployment's status.
Once Kubernetes deploys your app, kubectl
will report two running replicas.
Add a test
Since your CDKTF application is written in TypeScript, you can use Jest to unit test it.
First, configure Jest to work with CDKTF. Create a new file called
jest.setup.js
in the app
directory with the following contents:
Note
This filename ends in .js
, not .ts
, as required by Jest.
Next, create a new file in the app/__tests__
directory named
kubernetes-web-app-test.ts
with the following contents.
In your terminal, run your new test from the app/
directory.
All of your tests should pass.
There are more example tests in main-test.ts
. CDKTF generates this file when
you run cdktf init
.
Add a NodePort Service
The nginx:latest
container is running in your deployment, but it is not
accessible. Next you will add a NodePort Service to make it available on port
30001
.
Add NodePort Service construct
In your editor, open app/constructs/kubernetes-web-app.ts
.
Add a new interface for your NodePort Service right after the export interface
KubernetesDeploymentConfig { ... }
block.
Next, add a new KubernetesNodePortService
construct after your
KubernetesWebAppDeployment
class, at the end of the file.
Test your construct
Now open app/__tests__/kubernetes-web-app-test.ts
and add a test.
Near the top of the file, add your new KubernetesNodePortService
to the
import { ... } from '../constructs';
block, and include a comma after the
KubernetesWebAppDeployment
.
Add the test, inside the describe("Our CDKTF Constructs", () => { ... })
block. This test must be on the same level as the
describe("KubernetesWebAppDeployment", () => { ... })
block. There will be a
final })
at the end of the file after the new test.
Check to make sure your tests still pass. Run the tests in the app/
directory.
Jest should report that all of your tests pass.
Add a NodePortService to your application
In your editor, open app/main.ts
, and a new import to the import { ... } from
'./constructs';
block near the top of the file with the following code, and
include a comma after the KubernetesWebAppDeployment
.
Add a new KubernetesNodePortService
after your KubernetesWebAppDeployment
, inside
the constructor( ... ) { ... }
function block.
Deploy your application. Respond to the confirmation prompt by choosing
Approve
.
Use kubectl
to list your running services.
Kubectl reports two services, a ClusterIP
service, and your new NodePort
service.
In may take a minute or two for the frontend to become available. When it is, curl will respond with the NGINX welcome page.
Open http://localhost:30001
in a new browser tab to review this page.
Refactor your application
Now you will refactor your application with a new CDKTF construct. Before you do so, remove the existing application to prevent the new application from attempting to use the same ports as the current one.
Destroy app
Like Terraform, you can use CDKTF to destroy the resources it manages.
Before you refactor your code, destroy your application. Respond to the
confirmation prompt by choosing Approve
.
Refactor constructs
In your editor, open app/constructs/kubernetes-web-app.ts
.
CDKTF provides classes and interfaces for built-in Terraform features, such as output values and functions. Add an import for Terraform outputs to the imports near the top of the file.
At the end of the file, add a new construct to represent an application, including a Deployment and a NodePortService.
This new class also includes a Terraform output for the URL of your NodePort Service.
Add a test for the SimpleKubernetesWebApp construct
In your editor, add a test to app/__tests__/kubernetes-web-app-test.ts
.
Add your SimpleKubernetesWebApp
to the import { ... } from '../constructs';
block.
Add two new tests inside the describe('Our CDKTF Constructs', () => { ... });
block, on the same level as the describe('KubernetesWebAppDeployment', () => { ... });
block.
Run the tests
Run the tests from the app/
directory.
Use the SimpleKubernetesWebApp construct
In your editor, update app/main.ts
to import the SimpleKubernetesWebApp
construct instead of the seperate Deployment and Service constructs.
Replace the entire import { ... } from "./constructs"
block with the following.
Remove the new KubernetesWebAppDeployment( ... );
and new KubernetesNodePortService( ... );
blocks from your constructor()
function.
Replace the old constructs with the new one:
Deploy your application. Respond to the confirmation prompt by choosing Approve
.
Notice that after you refactored your applications resources using constructs,
CDKTF assigned the resources new names. Because the names have changed,
Terraform will treat them as new resources. If you had not destroyed the old
application first, then Terraform would attempt to provision the new resources
before destroying the old ones. Since both services use the port 30001
, this
would result in a port conflict error from Kubernetes.
List your running services with the kubectl command.
After Kubernetes is finished deploying your application, curl will once again respond with the NGINX welcome page:
Visit this URL in your web browser to confirm that your application still works as expected after refactoring your code.
Deploy a custom image
Now you will deploy a custom image to your application's frontend.
Build frontend docker image
Navigate to the frontend
directory.
This directory contains Terramino, a Terraform-skinned Tetris game, which you will deploy as a Docker container.
Build a Docker image for your web app:
Tag your image in the local Docker registry.
Push your image to the registry.
Now your application's frontend is available as a Docker container that your Kubernetes instance can deploy.
Use new frontend image
In your editor, open app/main.ts
, and replace the image
parameter with the
location of your new image.
In your terminal, return to the app
directory.
Deploy your new frontend with the new image. Respond to the confirmation prompt with a yes
.
Kubernetes will take a few minutes to deploy the new container image. Use
kubectl
to check your deployment's status.
Once all three replicas are up-to-date, curl will respond with a Terramino page instead of the NGINX welcome page.
Visit http://localhost:30001
to review the application. The app will display
an error, because you have not yet deployed the backend server.
Add a backend service
Now you will deploy a backend component to your application, using the
SimpleKubernetesWebApp
construct.
Build backend image
In your terminal, navigate to the backend
directory.
Use NPM to build and push the Docker image for your application backend.
This command automatically ran the same build, tag, and push steps that you ran manually for your frontend image.
In your editor, open app/main.ts
, and add a new SimpleKubernetesWebApp
immediately before the new SimpleKubernetesWebApp(this, 'app_frontend', { ... });
block.
Your application's frontend needs access to the backend's URL. Update the
frontend block in app/main.ts
to pass this as an environment variable.
In your terminal, return to the app
directory.
Deploy your new backend. Respond to the confirmation prompt with a yes
.
It may take Kubernetes a few minutes to deploy your new resources. Use kubectl
to check the status.
Once your kubectl reports all four replicas as ready (three myapp-frontend-dev
and one myapp-backend-dev
), your application will be available.
Visit your application's frontend URL (http://localhost:30001
) in your web
browser. You may need to reload the page for the application to work.
You can also use curl
to interact directly with the backend server:
Deploy another application stack
CDKTF projects can support multiple application stacks. Each CDKTF Stack is a separate Terraform project that you can manage independently. So far in this tutorial, you have deployed your resources as a single stack. In this section, you will deploy a new copy of your application as a new stack representing a test environment.
Refactor application stack
Currently, your stack's frontend and backend services are configured using
hard-coded values that you pass to SimpleKubernetesWebApp
in main.ts
.
Refactor your MyApp
stack to accept your application's configuration as an
argument instead of hard-coding it in the constructor()
function.
In your editor, open app/main.ts
, and add the SimpleKubernetesWebAppConfig
interface to your list of imports near the top of the file.
Next, update the class's constructor to accept two
SimpleKubernetesWebAppConfig
objects, and pass them to your frontend and
backend.
Replace the entire class MyStack extends TerraformStack { ... }
block with the
following.
Finally, pass these configuration objects when you create your application
object near the end of the file. Replace the current new MyStack...
line with
the following, immediately before the last line, app.synth();
.
In your terminal, run cdktf deploy
. Since the Terraform configuration
generated by CDKTF has not changed, there will be no changes to deploy.
Add a second stack
Now, add a second stack to main.ts
to represent your test environment,
immediately before the last line, app.synth();
.
Now that your project contains more than one stack, you must specify a stack when you run CDKTF commands.
Deploy your new stack. Respond to the confirmation prompt by choosing Approve
.
Since each stack is a separate Terraform project with separate state, you can deploy, update, and destroy them independently.
Confirm that kubernetes has deployed your test stack's frontend and backend with kubectl.
This command will list both your "-dev" and "-test" deployments, since they are running on the same Kubernetes cluster.
Clean up resources
In this section, you will delete your CDKTF application stacks, your kind cluster, and your local Docker image registry.
Destroy your app-test
stack. Respond to the confirmation prompt by choosing
Approve
.
Next, destroy your app
stack. Respond to the confirmation prompt by choosing
Approve
.
Delete your kind cluster.
Stop your local registry Docker container.
Remove the container.
Next steps
In this tutorial, you used the CDK for Terraform to deploy an application to a local Kubernetes cluster. You used CDK Constructs to refactor your application, and deployed multiple copies of your entire application as separate stacks.
Learn more about how to use the CDK for Terraform to manage Terraform projects by reviewing the following resources:
Follow the other CDKTF tutorials.
Find more examples and documentation in the CDKTF documentation.
Learn how the Mozilla Pocket team uses the CDK for Terraform to deploy applications on Amazon ECS.