Terraform
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.
$ git clone https://github.com/hashicorp-education/learn-terraform-cdktf-applications
Change to the repository directory.
$ cd learn-terraform-cdktf-applications
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.
$ docker run -d --restart always -p "127.0.0.1:5000:5000" --name local-registry registry:2
Unable to find image 'registry:2' locally
2: Pulling from library/registry
530afca65e2e: Already exists
d450d4da0343: Pull complete
96277bea17b6: Pull complete
470ad04e03fb: Pull complete
bd3d4dc6e66f: Pull complete
Digest: sha256:c631a581c6152f5a4a141a974b74cf308ab2ee660287a3c749d88e0b536c0c20
Status: Downloaded newer image for registry:2
2d9c6166d2ea3b1f6ef9d933afa6069eef5e2dbaece27ce1b235b89b7c5d374b
Use kind to create a Kubernetes cluster running in Docker on your local machine.
$ kind create cluster --name=cdktf-app --config kind-config.yaml
Creating cluster "cdktf-app" ...
✓ Ensuring node image (kindest/node:v1.25.0) 🖼
✓ Preparing nodes 📦
✓ Writing configuration 📜
✓ Starting control-plane 🕹️
✓ Installing CNI 🔌
✓ Installing StorageClass 💾
Set kubectl context to "kind-cdktf-app"
You can now use your cluster with:
kubectl cluster-info --context kind-cdktf-app
Not sure what to do next? 😅 Check out https://kind.sigs.k8s.io/docs/user/quick-start/
Verify that your cluster exists by listing your kind clusters.
$ kind get clusters
cdktf-app
Then, use kubectl
to print out information about your cluster. The context is
kind-
followed by the name of your cluster.
$ kubectl cluster-info --context=kind-cdktf-app
Kubernetes control plane is running at https://127.0.0.1:56821
CoreDNS is running at https://127.0.0.1:56821/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
Create a kubeconfig file to allow access to your Kubernetes cluster.
$ kubectl config view --raw --context kind-cdktf-app > kubeconfig.yaml
Now, attach your local Docker registry to your kind cluster.
$ docker network connect kind local-registry
Configure your Kubernetes cluster to use the local registry.
$ kubectl apply -f local-registry-configmap.yaml --kubeconfig kubeconfig.yaml
configmap/local-registry-hosting created
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.
$ mkdir app
Now change into it.
$ cd app
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.
$ cdktf init --template=typescript \
--project-name=learn-terraform-cdktf-applications \
--project-description="Learn how to develop CDKTF applications" \
--providers="kubernetes@~>2.14" \
--local
When CDKTF prompts you, accept the defaults. CDKTF will initialize your application in the current directory, and print out a help message.
Note: By supplying '--local' option you have chosen local storage mode for storing the state of your stack.
This means that your Terraform state file will be stored locally on disk in a file 'terraform.<STACK NAME>.tfstate' in the root of your project.
? Do you want to start from an existing Terraform project? No
? Do you want to send crash reports to the CDKTF team? See
https://www.terraform.io/cdktf/create-and-deploy/configuration-file#enable-crash
-reporting-for-the-cli for more information Yes
##...
Use Providers:
You can add prebuilt providers (if available) or locally generated ones using the add command:
cdktf provider add "aws@~>3.0" null kreuzwerker/docker
You can find all prebuilt providers on npm: https://www.npmjs.com/search?q=keywords:cdktf
You can also install these providers directly through npm:
npm install @cdktf/provider-aws
npm install @cdktf/provider-google
npm install @cdktf/provider-azurerm
npm install @cdktf/provider-docker
npm install @cdktf/provider-github
npm install @cdktf/provider-null
You can also build any module or provider locally. Learn more https://cdk.tf/modules-and-providers
========================================================================================================
Checking whether pre-built provider exists for the following constraints:
provider: kubernetes
version : ~>2.14
language: typescript
cdktf : 0.15.0
Found pre-built provider.
Adding package @cdktf/provider-kubernetes @ 3.0.12
Installing package @cdktf/provider-kubernetes @ 3.0.12 using npm.
Package installed.
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.
$ cat ../k8s_deployment.tf | cdktf convert --provider=kubernetes
/*Provider bindings are generated by running cdktf get.
See https://cdk.tf/provider-generation for more details.*/
import * as kubernetes from "./.gen/providers/kubernetes";
new kubernetes.deployment.Deployment(this, "myapp", {
metadata: {
labels: {
app: "myapp",
component: "frontend",
environment: "dev",
},
name: "myapp-frontend-dev",
},
spec: {
replicas: "1",
selector: {
matchLabels: {
app: "myapp",
component: "frontend",
environment: "dev",
},
},
template: {
metadata: {
labels: {
app: "myapp",
component: "frontend",
environment: "dev",
},
},
spec: {
container: [
{
image: "nginx:latest",
name: "myapp-frontend-dev",
},
],
},
},
},
});
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.
main.ts
import { Construct } from "constructs"
import { App, TerraformStack } from "cdktf"
import * as kubernetes from "@cdktf/provider-kubernetes"
import * as path from "path"
class MyStack extends TerraformStack {
constructor(scope: Construct, name: string) {
super(scope, name)
new kubernetes.provider.KubernetesProvider(this, 'kind', {
configPath: path.join(__dirname, '../kubeconfig.yaml'),
})
new kubernetes.deployment.Deployment(this, "myapp", {
metadata: {
labels: {
app: 'myapp',
component: 'frontend',
environment: 'dev',
},
name: 'myapp',
},
spec: {
replicas: '1',
selector: {
matchLabels: {
app: 'myapp',
component: 'frontend',
environment: 'dev',
},
},
template: {
metadata: {
labels: {
app: 'myapp',
component: 'frontend',
environment: 'dev',
},
},
spec: {
container: [
{
image: 'nginx:latest',
name: 'frontend',
},
],
},
},
},
})
}
}
const app = new App()
new MyStack(app, 'app')
app.synth()
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.
$ npm install path
added 299 packages, and audited 357 packages in 3s
33 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
Synthesize your application
Use the cdktf synth
command to generate Terraform configuration from your
code.
$ cdktf synth
Generated Terraform code for the stacks: app
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
.
$ cdktf deploy
app Initializing the backend...
app
Successfully configured the backend "local"! Terraform will automatically
use this backend unless the backend configuration changes.
app Initializing provider plugins...
app - Finding hashicorp/kubernetes versions matching "2.14.0"...
app - Using hashicorp/kubernetes v2.14.0 from the shared cache directory
##...
Plan: 1 to add, 0 to change, 0 to destroy.
─────────────────────────────────────────────────────────────────────────────
Saved the plan to: plan
To perform exactly these actions, run the following command to apply:
terraform apply "plan"
Please review the diff output above for app
❯ Approve Applies the changes outlined in the plan.
Dismiss
Stop
##...
app kubernetes_deployment.myapp (myapp): Creating...
app kubernetes_deployment.myapp (myapp): Creation complete after 8s [id=default/myapp]
app
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
No outputs found.
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
.
$ kubectl get deployments
NAME READY UP-TO-DATE AVAILABLE AGE
myapp 1/1 1 1 117s
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
.
main.ts
spec: {
- replicas: '1',
+ replicas: '4',
selector: {
Deploy your application again. Respond to the confirmation prompt by choosing
Approve
.
$ cdktf deploy
app Initializing the backend...
app Initializing provider plugins...
app - Reusing previous version of hashicorp/kubernetes from the dependency lock file
app - Using previously-installed hashicorp/kubernetes v2.14.0
app Terraform has been successfully initialized!
##...
Terraform will perform the following actions:
app # kubernetes_deployment.myapp (myapp) will be updated in-place
~ resource "kubernetes_deployment" "myapp" {
id = "default/myapp"
# (1 unchanged attribute hidden)
~ spec {
~ replicas = "1" -> "4"
# (4 unchanged attributes hidden)
# (3 unchanged blocks hidden)
}
# (1 unchanged block hidden)
}
Plan: 0 to add, 1 to change, 0 to destroy.
─────────────────────────────────────────────────────────────────────────────
Saved the plan to: plan
To perform exactly these actions, run the following command to apply:
terraform apply "plan"
Please review the diff output above for app
❯ Approve Applies the changes outlined in the plan.
Dismiss
Stop
##...
app kubernetes_deployment.myapp (myapp): Modifying... [id=default/myapp]
app kubernetes_deployment.myapp (myapp): Modifications complete after 4s [id=default/myapp]
app
Apply complete! Resources: 0 added, 1 changed, 0 destroyed.
No outputs found.
After Kubernetes finishes applying this change, kubectl
will report four
instances of your applicaton.
$ kubectl get deployments
NAME READY UP-TO-DATE AVAILABLE AGE
myapp 4/4 4 4 8m31s
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.
$ mkdir constructs
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.
constructs/kubernetes-web-app.ts
import { Construct } from "constructs"
import * as kubernetes from "@cdktf/provider-kubernetes"
export interface KubernetesWebAppDeploymentConfig {
readonly image: string
readonly replicas: number
readonly app: string
readonly component: string
readonly environment: string
readonly env?: Record<string, string>
}
export class KubernetesWebAppDeployment extends Construct {
public readonly resource: kubernetes.deployment.Deployment
constructor(
scope: Construct,
name: string,
config: KubernetesWebAppDeploymentConfig
) {
super(scope, name)
this.resource = new kubernetes.deployment.Deployment(this, name, {
metadata: {
labels: {
app: config.app,
component: config.component,
environment: config.environment,
},
name: `${config.app}-${config.component}-${config.environment}`,
},
spec: {
replicas: config.replicas.toString(),
selector: {
matchLabels: {
app: config.app,
component: config.component,
environment: config.environment,
},
},
template: {
metadata: {
labels: {
app: config.app,
component: config.component,
environment: config.environment,
},
},
spec: {
container: [
{
image: config.image,
name: `${config.app}-${config.component}-${config.environment}`,
env: Object.entries(config.env || {}).map(([name, value]) => ({
name,
value,
})),
},
],
},
},
},
})
}
}
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
.
constructs/index.ts
export * from "./kubernetes-web-app"
Add Deployment construct to CDKTF application
Open app/main.ts
and import your new Construct near top of the file.
main.ts
import { Construct } from "constructs"
import { App, TerraformStack } from "cdktf"
import * as kubernetes from "@cdktf/provider-kubernetes"
import * as path from "path"
import { KubernetesWebAppDeployment } from "./constructs"
Replace the entire new kubernetes.deployment.Deployment(this, "myapp", { ... });
block with a new instance of your construct.
main.ts
class MyStack extends TerraformStack {
constructor(scope: Construct, name: string) {
super(scope, name)
new kubernetes.provider.KubernetesProvider(this, 'kind', {
configPath: path.join(__dirname, '../kubeconfig.yaml'),
})
new KubernetesWebAppDeployment(this, 'deployment', {
image: 'nginx:latest',
replicas: 2,
app: 'myapp',
component: 'frontend',
environment: 'dev',
})
}
}
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:
main.ts
import { Construct } from "constructs"
import { App, TerraformStack } from "cdktf"
import * as kubernetes from "@cdktf/provider-kubernetes"
import * as path from "path"
import { KubernetesWebAppDeployment } from "./constructs"
class MyStack extends TerraformStack {
constructor(scope: Construct, name: string) {
super(scope, name)
new kubernetes.provider.KubernetesProvider(this, 'kind', {
configPath: path.join(__dirname, '../kubeconfig.yaml'),
})
new KubernetesWebAppDeployment(this, 'deployment', {
image: 'nginx:latest',
replicas: 2,
app: 'myapp',
component: 'frontend',
environment: 'dev',
})
}
}
const app = new App()
new MyStack(app, 'app')
app.synth()
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
.
$ cdktf deploy
app Initializing the backend...
app Initializing provider plugins...
app - Reusing previous version of hashicorp/kubernetes from the dependency lock file
app - Using previously-installed hashicorp/kubernetes v2.14.0
app Terraform has been successfully initialized!
##...
Plan: 1 to add, 0 to change, 1 to destroy.
─────────────────────────────────────────────────────────────────────────────
Saved the plan to: plan
To perform exactly these actions, run the following command to apply:
terraform apply "plan"
Please review the diff output above for app
❯ Approve Applies the changes outlined in the plan.
Dismiss
Stop
##...
app kubernetes_deployment.myapp: Destroying... [id=default/myapp]
app kubernetes_deployment.deployment_7B0B4E40 (deployment/deployment): Creating...
app kubernetes_deployment.myapp: Destruction complete after 0s
app kubernetes_deployment.deployment_7B0B4E40 (deployment/deployment): Creation complete after 8s [id=default/myapp-frontend-dev]
app
Apply complete! Resources: 1 added, 0 changed, 1 destroyed.
No outputs found.
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.
$ kubectl get deployments
NAME READY UP-TO-DATE AVAILABLE AGE
myapp-frontend-dev 2/2 2 2 5m14s
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:
jest.setup.js
const cdktf = require('cdktf')
cdktf.Testing.setupJest()
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.
__tests__/kubernetes-web-app-test.ts
import "cdktf/lib/testing/adapters/jest"
import { Testing } from "cdktf"
import * as kubernetes from "@cdktf/provider-kubernetes"
import { KubernetesWebAppDeployment } from "../constructs"
describe('Our CDKTF Constructs', () => {
describe('KubernetesWebAppDeployment', () => {
it('should contain a deployment resource', () => {
expect(
Testing.synthScope((scope) => {
new KubernetesWebAppDeployment(scope, 'myapp-frontend-dev', {
image: 'nginx:latest',
replicas: 4,
app: 'myapp',
component: 'frontend',
environment: 'dev',
})
})
).toHaveResource(kubernetes.deployment.Deployment)
})
})
})
In your terminal, run your new test from the app/
directory.
$ npm run test
> app@1.0.0 test
> jest
PASS __tests__/main-test.ts
PASS __tests__/kubernetes-web-app-test.ts
Test Suites: 2 passed, 2 total
Tests: 1 todo, 1 passed, 2 total
Snapshots: 0 total
Time: 5.109 s
Ran all test suites.
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.
constructs/kubernetes-web-app.ts
export interface KubernetesNodePortServiceConfig {
readonly port: number
readonly app: string
readonly component: string
readonly environment: string
}
Next, add a new KubernetesNodePortService
construct after your
KubernetesWebAppDeployment
class, at the end of the file.
constructs/kubernetes-web-app.ts
export class KubernetesNodePortService extends Construct {
public readonly resource: kubernetes.service.Service
constructor(
scope: Construct,
name: string,
config: KubernetesNodePortServiceConfig
) {
super(scope, name)
this.resource = new kubernetes.service.Service(this, name, {
metadata: {
name: `${config.app}-${config.component}-${config.environment}`,
},
spec: {
type: 'NodePort',
port: [
{
port: 80,
targetPort: '80',
nodePort: config.port,
protocol: 'TCP',
},
],
selector: {
app: config.app,
component: config.component,
environment: config.environment,
},
},
})
}
}
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
.
__tests__/kubernetes-web-app-test.ts
import {
KubernetesWebAppDeployment,
KubernetesNodePortService,
} from '../constructs'
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.
__tests__/kubernetes-web-app-test.ts
describe('KubernetesNodePortService', () => {
it('should contain a Service resource', () => {
expect(
Testing.synthScope((scope) => {
new KubernetesNodePortService(scope, 'myapp-frontend-dev', {
app: 'myapp',
component: 'frontend',
environment: 'dev',
port: 30001,
})
})
).toHaveResource(kubernetes.service.Service)
})
})
Check to make sure your tests still pass. Run the tests in the app/
directory.
$ npm run test
> app@1.0.0 test
> jest
PASS __tests__/main-test.ts
PASS __tests__/kubernetes-web-app-test.ts
Test Suites: 2 passed, 2 total
Tests: 1 todo, 2 passed, 3 total
Snapshots: 0 total
Time: 5.094 s
Ran all test suites.
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
.
main.ts
import {
KubernetesWebAppDeployment,
KubernetesNodePortService,
} from './constructs'
Add a new KubernetesNodePortService
after your KubernetesWebAppDeployment
, inside
the constructor( ... ) { ... }
function block.
main.ts
new KubernetesNodePortService(this, 'service', {
port: 30001,
app: 'myapp',
component: 'frontend',
environment: 'dev',
})
Deploy your application. Respond to the confirmation prompt by choosing
Approve
.
$ cdktf deploy
app Initializing the backend...
app Initializing provider plugins...
app - Reusing previous version of hashicorp/kubernetes from the dependency lock file
app - Using previously-installed hashicorp/kubernetes v2.14.0
app Terraform has been successfully initialized!
##...
Plan: 1 to add, 0 to change, 0 to destroy.
─────────────────────────────────────────────────────────────────────────────
Saved the plan to: plan
To perform exactly these actions, run the following command to apply:
terraform apply "plan"
Please review the diff output above for app
❯ Approve Applies the changes outlined in the plan.
Dismiss
Stop
##...
app kubernetes_service.service_E7C408F2 (service/service): Creating...
app kubernetes_service.service_E7C408F2 (service/service): Creation complete after 0s [id=default/myapp-frontend-dev]
app
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
No outputs found.
Use kubectl
to list your running services.
$ kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 101m
myapp-frontend-dev NodePort 10.96.186.212 <none> 80:30001/TCP 22s
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.
$ curl http://localhost:30001
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
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
.
$ cdktf destroy
app Initializing the backend...
app Initializing provider plugins...
app - Reusing previous version of hashicorp/kubernetes from the dependency lock file
app - Using previously-installed hashicorp/kubernetes v2.14.0
app Terraform has been successfully initialized!
##...
Plan: 0 to add, 0 to change, 2 to destroy.
─────────────────────────────────────────────────────────────────────────────
Saved the plan to: plan
To perform exactly these actions, run the following command to apply:
terraform apply "plan"
Please review the diff output above for app
❯ Approve Applies the changes outlined in the plan.
Dismiss
Stop
app kubernetes_service.service_E7C408F2 (service/service): Destroying... [id=default/myapp-frontend-dev]
app kubernetes_deployment.deployment_7B0B4E40 (deployment/deployment): Destroying... [id=default/myapp-frontend-dev]
app kubernetes_deployment.deployment_7B0B4E40 (deployment/deployment): Destruction complete after 0s
app kubernetes_service.service_E7C408F2 (service/service): Destruction complete after 0s
app
Destroy complete! Resources: 2 destroyed.
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.
constructs/kubernetes-web-app.ts
import { TerraformOutput } from "cdktf"
At the end of the file, add a new construct to represent an application, including a Deployment and a NodePortService.
constructs/kubernetes-web-app.ts
export type SimpleKubernetesWebAppConfig = KubernetesWebAppDeploymentConfig &
KubernetesNodePortServiceConfig
export class SimpleKubernetesWebApp extends Construct {
public readonly deployment: KubernetesWebAppDeployment
public readonly service: KubernetesNodePortService
public readonly config: SimpleKubernetesWebAppConfig
constructor(
scope: Construct,
name: string,
config: SimpleKubernetesWebAppConfig
) {
super(scope, name)
this.config = config
this.deployment = new KubernetesWebAppDeployment(this, 'deployment', {
image: config.image,
replicas: config.replicas,
app: config.app,
component: config.component,
environment: config.environment,
env: config.env,
})
this.service = new KubernetesNodePortService(this, 'service', {
port: config.port,
app: config.app,
component: config.component,
environment: config.environment,
})
new TerraformOutput(this, 'url', {
value: `http://localhost:${config.port}`,
})
}
}
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.
__tests__/kubernetes-web-app-test.ts
import {
KubernetesWebAppDeployment,
KubernetesNodePortService,
SimpleKubernetesWebApp,
} from "../constructs"
Add two new tests inside the describe('Our CDKTF Constructs', () => { ... });
block, on the same level as the describe('KubernetesWebAppDeployment', () => { ... });
block.
__tests__/kubernetes-web-app-test.ts
describe('SimpleKubernetesWebApp', () => {
it('should contain a Service resource', () => {
expect(
Testing.synthScope((scope) => {
new SimpleKubernetesWebApp(scope, 'myapp-frontend-dev', {
image: 'nginx:latest',
replicas: 4,
app: 'myapp',
component: 'frontent',
environment: 'dev',
port: 30001,
})
})
).toHaveResource(kubernetes.service.Service)
})
it('should contain a Deployment resource', () => {
expect(
Testing.synthScope((scope) => {
new SimpleKubernetesWebApp(scope, 'myapp-frontend-dev', {
image: 'nginx:latest',
replicas: 4,
app: 'myapp',
component: 'frontent',
environment: 'dev',
port: 30001,
})
})
).toHaveResource(kubernetes.deployment.Deployment)
})
})
Run the tests
Run the tests from the app/
directory.
$ npm run test
> app@1.0.0 test
> jest
PASS __tests__/main-test.ts (6.019 s)
PASS __tests__/kubernetes-web-app-test.ts (8.059 s)
Test Suites: 2 passed, 2 total
Tests: 1 todo, 4 passed, 5 total
Snapshots: 0 total
Time: 8.556 s
Ran all test suites.
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.
main.ts
import { SimpleKubernetesWebApp } from "./constructs"
Remove the new KubernetesWebAppDeployment( ... );
and new KubernetesNodePortService( ... );
blocks from your constructor()
function.
Replace the old constructs with the new one:
main.ts
new SimpleKubernetesWebApp(this, 'app_frontend', {
image: 'nginx:latest',
replicas: 3,
port: 30001,
app: 'myapp',
component: 'frontend',
environment: 'dev',
})
Deploy your application. Respond to the confirmation prompt by choosing Approve
.
$ cdktf deploy
app Initializing the backend...
app Initializing provider plugins...
app - Reusing previous version of hashicorp/kubernetes from the dependency lock file
app - Using previously-installed hashicorp/kubernetes v2.14.0
app Terraform has been successfully initialized!
##...
Plan: 2 to add, 0 to change, 0 to destroy.
Changes to Outputs:
+ app_frontend_url_5DD99814 = "http://localhost:30001"
─────────────────────────────────────────────────────────────────────────────
Saved the plan to: plan
To perform exactly these actions, run the following command to apply:
terraform apply "plan"
Please review the diff output above for app
❯ Approve Applies the changes outlined in the plan.
Dismiss
Stop
app kubernetes_service.app_frontend_service_C2863249 (app_frontend/service/service): Creating...
app kubernetes_deployment.app_frontend_deployment_0EE98C72 (app_frontend/deployment/deployment): Creating...
app kubernetes_service.app_frontend_service_C2863249 (app_frontend/service/service): Creation complete after 0s [id=default/myapp-frontend-dev]
app kubernetes_deployment.app_frontend_deployment_0EE98C72 (app_frontend/deployment/deployment): Creation complete after 4s [id=default/myapp-frontend-dev]
app
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
app
Outputs:
app_frontend_url_5DD99814 = "http://localhost:30001"
app
app_frontend
url = http://localhost:30001
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.
$ kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 142m
myapp-frontend-dev NodePort 10.96.216.88 <none> 80:30001/TCP 44s
After Kubernetes is finished deploying your application, curl will once again respond with the NGINX welcome page:
$ curl http://localhost:30001
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
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.
$ cd ../frontend
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:
$ docker build . -t nocorp-frontend
[+] Building 33.0s (12/12) FINISHED
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 295B 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 67B 0.0s
=> [internal] load metadata for docker.io/library/node:14 1.3s
=> [1/7] FROM docker.io/library/node:14@sha256:109b118e0d49dd12ca6f5b84a7a9a9c8a147f75567b3ad50620bdacaf5e6320d 26.2s
## ...
=> [internal] load build context 0.0s
=> => transferring context: 718.44kB 0.0s
=> [2/7] WORKDIR /usr/share/app 0.2s
=> [3/7] COPY package*.json ./ 0.0s
=> [4/7] COPY frontend.js ./ 0.0s
=> [5/7] COPY public/ ./public/ 0.0s
=> [6/7] COPY views/ ./views/ 0.0s
=> [7/7] RUN npm install 4.8s
=> exporting to image 0.2s
=> => exporting layers 0.2s
=> => writing image sha256:9dc1ce3668a79770f694ddeae6a5c2236527c381cd429d850eb4a37a8c565ce1 0.0s
=> => naming to docker.io/library/nocorp-frontend 0.0s
Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them
Tag your image in the local Docker registry.
$ docker tag nocorp-frontend:latest localhost:5000/nocorp-frontend:latest
Push your image to the registry.
$ docker push localhost:5000/nocorp-frontend:latest
The push refers to repository [localhost:5000/nocorp-frontend]
72d21ab0ed02: Pushed
a831dca05db6: Pushed
66018b4c7669: Pushed
65f4bcfe9daa: Pushed
add6fa0cc975: Pushed
8628cf05347a: Pushed
85bfe2c7cf32: Pushed
28e2f0d3695c: Pushed
b892edc3d92e: Pushed
f1486e967e48: Pushed
5750262417ad: Pushed
9ed3c35b4335: Pushed
6f7f3f280040: Pushed
d6e0d602719c: Pushed
73c3e7ef7bc6: Pushed
latest: digest: sha256:f56babe32077523e891b24af0e38fe00026c7f8ed38b89d90a102aaeeb3d40b8 size: 3465
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.
app/main.ts
new SimpleKubernetesWebApp(this, 'app_frontend', {
image: 'localhost:5000/nocorp-frontend:latest',
replicas: 3,
port: 30001,
app: 'myapp',
component: 'frontend',
environment: 'dev',
});
In your terminal, return to the app
directory.
$ cd ../app
Deploy your new frontend with the new image. Respond to the confirmation prompt with a yes
.
$ cdktf deploy
app Initializing the backend...
app Initializing provider plugins...
app - Reusing previous version of hashicorp/kubernetes from the dependency lock file
app - Using previously-installed hashicorp/kubernetes v2.14.0
Terraform has been successfully initialized!
##...
app Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
~ update in-place
Terraform will perform the following actions:
app # kubernetes_deployment.app_frontend_deployment_0EE98C72 (app_frontend/deployment/deployment) will be updated in-place
~ resource "kubernetes_deployment" "app_frontend_deployment_0EE98C72" {
id = "default/myapp-frontend-dev"
# (1 unchanged attribute hidden)
~ spec {
# (5 unchanged attributes hidden)
~ template {
~ spec {
# (11 unchanged attributes hidden)
~ container {
~ image = "nginx:latest" -> "localhost:5000/nocorp-frontend:latest"
name = "myapp-frontend-dev"
# (8 unchanged attributes hidden)
##...
Plan: 0 to add, 1 to change, 0 to destroy.
─────────────────────────────────────────────────────────────────────────────
Saved the plan to: plan
To perform exactly these actions, run the following command to apply:
terraform apply "plan"
Please review the diff output above for app
❯ Approve Applies the changes outlined in the plan.
Dismiss
Stop
##...
app kubernetes_deployment.app_frontend_deployment_0EE98C72 (app_frontend/deployment/deployment): Modifying... [id=default/myapp-frontend-dev]
app kubernetes_deployment.app_frontend_deployment_0EE98C72 (app_frontend/deployment/deployment): Still modifying... [id=default/myapp-frontend-dev, 10s elapsed]
app kubernetes_deployment.app_frontend_deployment_0EE98C72 (app_frontend/deployment/deployment): Still modifying... [id=default/myapp-frontend-dev, 20s elapsed]
app kubernetes_deployment.app_frontend_deployment_0EE98C72 (app_frontend/deployment/deployment): Modifications complete after 25s [id=default/myapp-frontend-dev]
app
Apply complete! Resources: 0 added, 1 changed, 0 destroyed.
Outputs:
app app_frontend_url_5DD99814 = "http://localhost:30001"
app
app_frontend
url = http://localhost:30001
Kubernetes will take a few minutes to deploy the new container image. Use
kubectl
to check your deployment's status.
$ kubectl get deployments
NAME READY UP-TO-DATE AVAILABLE AGE
myapp-frontend-dev 3/3 3 3 43s
Once all three replicas are up-to-date, curl will respond with a Terramino page instead of the NGINX welcome page.
$ curl http://localhost:30001
<!DOCTYPE html><html><head><title>Terranimo</title></head><link rel="stylesheet" href="/styles/terramino.css"></html><body><div class="score" id="score"></div><div class="container"><div class="content"><h1>Terramino</h1><p class="error" id="errorMessage">Could not connect to server! <br/> Reload to try again.</p><p>Move: ← → Rotate: ↑ Drop: ↓</p></div><div class="content"><canvas width="320" height="640" id="game"></canvas></div></div><script src="/scripts/terramino.js"></script><script>start('http://localhost:30002')</script></body>
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.
$ cd ../backend
Use NPM to build and push the Docker image for your application backend.
$ npm run deploy
> nocorp-backend-app@1.0.0 deploy /Users/<YOU>/code/learn-terraform-cdktf-applications/backend
> npm run build && npm run tag && npm run push
> nocorp-backend-app@1.0.0 build /Users/<YOU>/code/learn-terraform-cdktf-applications/backend
> docker build . -t nocorp-backend
## ...
The push refers to repository [localhost:5000/nocorp-backend]
676744aedcf3: Pushed
e64b93b2c9bd: Pushed
8628cf05347a: Mounted from nocorp-frontend
85bfe2c7cf32: Mounted from nocorp-frontend
28e2f0d3695c: Mounted from nocorp-frontend
b892edc3d92e: Mounted from nocorp-frontend
f1486e967e48: Mounted from nocorp-frontend
5750262417ad: Mounted from nocorp-frontend
9ed3c35b4335: Mounted from nocorp-frontend
6f7f3f280040: Mounted from nocorp-frontend
d6e0d602719c: Mounted from nocorp-frontend
73c3e7ef7bc6: Mounted from nocorp-frontend
latest: digest: sha256:845e60dd8350066a89757f1bdb200584f317e5b15d68cc9609a3c359f3736676 size: 2841
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.
main.ts
const app_backend = new SimpleKubernetesWebApp(this, 'app_backend', {
image: 'localhost:5000/nocorp-backend:latest',
replicas: 1,
port: 30002,
app: 'myapp',
component: 'backend',
environment: 'dev',
})
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.
main.ts
new SimpleKubernetesWebApp(this, 'app_frontend', {
image: 'localhost:5000/nocorp-frontend:latest',
replicas: 3,
port: 30001,
app: 'myapp',
component: 'frontend',
environment: 'dev',
env: { BACKEND_APP_URL: `http://localhost:${app_backend.config.port}` },
})
In your terminal, return to the app
directory.
$ cd ../app
Deploy your new backend. Respond to the confirmation prompt with a yes
.
$ cdktf deploy
app Initializing the backend...
app Initializing provider plugins...
- Reusing previous version of hashicorp/kubernetes from the dependency lock file
app - Using previously-installed hashicorp/kubernetes v2.14.0
Terraform has been successfully initialized!
##...
app Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
+ create
~ update in-place
Terraform will perform the following actions:
app # kubernetes_deployment.app_backend_deployment_1A8B5520 (app_backend/deployment/deployment) will be created
+ resource "kubernetes_deployment" "app_backend_deployment_1A8B5520" {
##...
# kubernetes_deployment.app_frontend_deployment_0EE98C72 (app_frontend/deployment/deployment) will be updated in-place
~ resource "kubernetes_deployment" "app_frontend_deployment_0EE98C72" {
id = "default/myapp-frontend-dev"
##...
+ env {
+ name = "BACKEND_APP_URL"
+ value = "http://localhost:30002"
}
##...
# kubernetes_service.app_backend_service_EAD583EF (app_backend/service/service) will be created
+ resource "kubernetes_service" "app_backend_service_EAD583EF" {
##...
Plan: 2 to add, 1 to change, 0 to destroy.
Changes to Outputs:
+ app_backend_url_CAA2B50B = "http://localhost:30002"
─────────────────────────────────────────────────────────────────────────────
Saved the plan to: plan
To perform exactly these actions, run the following command to apply:
terraform apply "plan"
Please review the diff output above for app
❯ Approve Applies the changes outlined in the plan.
Dismiss
Stop
##...
app kubernetes_service.app_backend_service_EAD583EF (app_backend/service/service): Creating...
app kubernetes_deployment.app_backend_deployment_1A8B5520 (app_backend/deployment/deployment): Creating...
app kubernetes_deployment.app_frontend_deployment_0EE98C72 (app_frontend/deployment/deployment): Modifying... [id=default/myapp-frontend-dev]
app kubernetes_service.app_backend_service_EAD583EF (app_backend/service/service): Creation complete after 0s [id=default/myapp-backend-dev]
app kubernetes_deployment.app_backend_deployment_1A8B5520 (app_backend/deployment/deployment): Creation complete after 1s [id=default/myapp-backend-dev]
app kubernetes_deployment.app_frontend_deployment_0EE98C72 (app_frontend/deployment/deployment): Modifications complete after 3s [id=default/myapp-frontend-dev]
app
Apply complete! Resources: 2 added, 1 changed, 0 destroyed.
Outputs:
app_backend_url_CAA2B50B = "http://localhost:30002"
app_frontend_url_5DD99814 = "http://localhost:30001"
app
app_backend
url = http://localhost:30002
app_frontend
url = http://localhost:30001
It may take Kubernetes a few minutes to deploy your new resources. Use kubectl
to check the status.
$ kubectl get deployments
NAME READY UP-TO-DATE AVAILABLE AGE
myapp-backend-dev 1/1 1 1 57s
myapp-frontend-dev 3/3 3 3 15m
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:
$ curl http://localhost:30002/new
{"game":{"id":"pdSW6Cymw","state":"running","score":0,"tetrominoSequence":["I","J","Z","O","T","S"]},"tetromino":"L"}
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.
main.ts
import {
SimpleKubernetesWebApp,
SimpleKubernetesWebAppConfig,
} from "./constructs"
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.
main.ts
class MyStack extends TerraformStack {
constructor(
scope: Construct,
name: string,
config: {
frontend: SimpleKubernetesWebAppConfig
backend: SimpleKubernetesWebAppConfig
}
) {
super(scope, name)
new kubernetes.provider.KubernetesProvider(this, 'kind', {
configPath: path.join(__dirname, '../kubeconfig.yaml'),
})
const app_backend = new SimpleKubernetesWebApp(
this,
'app_backend',
config.backend
)
new SimpleKubernetesWebApp(this, 'app_frontend', {
...config.frontend,
env: { BACKEND_APP_URL: `http://localhost:${app_backend.config.port}` },
})
}
}
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();
.
main.ts
const app = new App()
new MyStack(app, 'app', {
frontend: {
image: 'localhost:5000/nocorp-frontend:latest',
replicas: 3,
port: 30001,
app: 'myapp',
component: 'frontend',
environment: 'dev',
},
backend: {
image: 'localhost:5000/nocorp-backend:latest',
replicas: 1,
port: 30002,
app: 'myapp',
component: 'backend',
environment: 'dev',
},
})
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.
$ cdktf deploy
app Initializing the backend...
app Initializing provider plugins...
- Reusing previous version of hashicorp/kubernetes from the dependency lock file
app - Using previously-installed hashicorp/kubernetes v2.14.0
app Terraform has been successfully initialized!
##...
app No changes. Your infrastructure matches the configuration.
app Terraform has compared your real infrastructure against your configuration
and found no differences, so no changes are needed.
app
app_frontend
url = http://localhost:30001
app_backend
url = http://localhost:30002
Add a second stack
Now, add a second stack to main.ts
to represent your test environment,
immediately before the last line, app.synth();
.
main.ts
new MyStack(app, 'app-test', {
frontend: {
image: 'localhost:5000/nocorp-frontend:latest',
replicas: 4,
port: 30003,
app: 'myapp',
component: 'frontend',
environment: 'test',
},
backend: {
image: 'localhost:5000/nocorp-backend:latest',
replicas: 2,
port: 30004,
app: 'myapp',
component: 'backend',
environment: 'test',
},
})
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
.
$ cdktf deploy app-test
app-test Initializing the backend...
app-test
Successfully configured the backend "local"! Terraform will automatically
use this backend unless the backend configuration changes.
app-test Initializing provider plugins...
- Finding hashicorp/kubernetes versions matching "2.14.0"...
app-test - Using hashicorp/kubernetes v2.14.0 from the shared cache directory
app-test 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.
##...
Please review the diff output above for app
❯ Approve Applies the changes outlined in the plan.
Dismiss
Stop
##...
app-test kubernetes_service.app_frontend_service_C2863249 (app_frontend/service/service): Creating...
kubernetes_service.app_backend_service_EAD583EF (app_backend/service/service): Creating...
app-test kubernetes_deployment.app_backend_deployment_1A8B5520 (app_backend/deployment/deployment): Creating...
app-test kubernetes_deployment.app_frontend_deployment_0EE98C72 (app_frontend/deployment/deployment): Creating...
app-test kubernetes_service.app_frontend_service_C2863249 (app_frontend/service/service): Creation complete after 0s [id=default/myapp-frontend-test]
app-test kubernetes_service.app_backend_service_EAD583EF (app_backend/service/service): Creation complete after 0s [id=default/myapp-backend-test]
app-test kubernetes_deployment.app_frontend_deployment_0EE98C72 (app_frontend/deployment/deployment): Creation complete after 8s [id=default/myapp-frontend-test]
app-test kubernetes_deployment.app_backend_deployment_1A8B5520 (app_backend/deployment/deployment): Creation complete after 8s [id=default/myapp-backend-test]
app-test
Apply complete! Resources: 4 added, 0 changed, 0 destroyed.
app-test
Outputs:
app_backend_url_CAA2B50B = "http://localhost:30004"
app_frontend_url_5DD99814 = "http://localhost:30003"
app-test
app_backend
url = http://localhost:30004
app_frontend
url = http://localhost:30003
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.
$ kubectl get deployments
NAME READY UP-TO-DATE AVAILABLE AGE
myapp-backend-dev 1/1 1 1 46m
myapp-backend-test 2/2 2 2 71s
myapp-frontend-dev 3/3 3 3 60m
myapp-frontend-test 4/4 4 4 71s
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
.
$ cdktf destroy app-test
app-test Initializing the backend...
app-test Initializing provider plugins...
- Reusing previous version of hashicorp/kubernetes from the dependency lock file
app-test - Using previously-installed hashicorp/kubernetes v2.14.0
app-test Terraform has been successfully initialized!
##...
Plan: 0 to add, 0 to change, 4 to destroy.
app-test
Changes to Outputs:
- app_backend_url_CAA2B50B = "http://localhost:30004" -> null
- app_frontend_url_5DD99814 = "http://localhost:30003" -> null
─────────────────────────────────────────────────────────────────────────────
Saved the plan to: plan
To perform exactly these actions, run the following command to apply:
terraform apply "plan"
Please review the diff output above for app-test
❯ Approve Applies the changes outlined in the plan.
Dismiss
Stop
##...
app-test kubernetes_service.app_backend_service_EAD583EF (app_backend/service/service): Destruction complete after 0s
app-test kubernetes_service.app_frontend_service_C2863249 (app_frontend/service/service): Destruction complete after 0s
app-test
Destroy complete! Resources: 4 destroyed.
Next, destroy your app
stack. Respond to the confirmation prompt by choosing
Approve
.
$ cdktf destroy app
app Initializing the backend...
app Initializing provider plugins...
- Reusing previous version of hashicorp/kubernetes from the dependency lock file
app - Using previously-installed hashicorp/kubernetes v2.12.1
app Terraform has been successfully initialized!
##...
Plan: 0 to add, 0 to change, 4 to destroy.
Changes to Outputs:
- app_frontend_url_FE3D723A = "http://localhost:30001" -> null
- app_backend_url_91B41C22 = "http://localhost:30002" -> null
─────────────────────────────────────────────────────────────────────────────
Saved the plan to: plan
To perform exactly these actions, run the following command to apply:
terraform apply "plan"
Please review the diff output above for app
❯ Approve Applies the changes outlined in the plan.
Dismiss
Stop
##...
app kubernetes_service.app_backend_service_EAD583EF (app_backend/service/service): Destruction complete after 0s
app kubernetes_service.app_frontend_service_C2863249 (app_frontend/service/service): Destruction complete after 0s
app
Destroy complete! Resources: 4 destroyed.
Delete your kind cluster.
$ kind delete cluster --name=cdktf-app
Deleting cluster "cdktf-app" ...
Stop your local registry Docker container.
$ docker stop local-registry
local-registry
Remove the container.
$ docker rm local-registry
local-registry
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.