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:2Unable to find image 'registry:2' locally2: Pulling from library/registry530afca65e2e: Already existsd450d4da0343: Pull complete96277bea17b6: Pull complete470ad04e03fb: Pull completebd3d4dc6e66f: Pull completeDigest: sha256:c631a581c6152f5a4a141a974b74cf308ab2ee660287a3c749d88e0b536c0c20Status: Downloaded newer image for registry:22d9c6166d2ea3b1f6ef9d933afa6069eef5e2dbaece27ce1b235b89b7c5d374b
Use kind to create a Kubernetes cluster running in Docker on your local machine.
$ kind create cluster --name=cdktf-app --config kind-config.yamlCreating 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 clusterscdktf-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-appKubernetes control plane is running at https://127.0.0.1:56821CoreDNS 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.yamlconfigmap/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? Seehttps://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.12Installing 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 synthGenerated 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 deployapp 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 deploymentsNAME READY UP-TO-DATE AVAILABLE AGEmyapp 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 deployapp Initializing the backend...app Initializing provider plugins...app - Reusing previous version of hashicorp/kubernetes from the dependency lock fileapp - Using previously-installed hashicorp/kubernetes v2.14.0app 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 deploymentsNAME READY UP-TO-DATE AVAILABLE AGEmyapp 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 deployapp Initializing the backend...app Initializing provider plugins...app - Reusing previous version of hashicorp/kubernetes from the dependency lock fileapp - Using previously-installed hashicorp/kubernetes v2.14.0app 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 0sapp 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 deploymentsNAME READY UP-TO-DATE AVAILABLE AGEmyapp-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 totalTests: 1 todo, 1 passed, 2 totalSnapshots: 0 totalTime: 5.109 sRan 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 totalTests: 1 todo, 2 passed, 3 totalSnapshots: 0 totalTime: 5.094 sRan 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 deployapp Initializing the backend...app Initializing provider plugins...app - Reusing previous version of hashicorp/kubernetes from the dependency lock fileapp - Using previously-installed hashicorp/kubernetes v2.14.0app 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 servicesNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGEkubernetes ClusterIP 10.96.0.1 <none> 443/TCP 101mmyapp-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 andworking. 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 destroyapp Initializing the backend...app Initializing provider plugins...app - Reusing previous version of hashicorp/kubernetes from the dependency lock fileapp - Using previously-installed hashicorp/kubernetes v2.14.0app 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 Stopapp 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 0sapp kubernetes_service.service_E7C408F2 (service/service): Destruction complete after 0sapp 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 totalTests: 1 todo, 4 passed, 5 totalSnapshots: 0 totalTime: 8.556 sRan 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 deployapp Initializing the backend...app Initializing provider plugins...app - Reusing previous version of hashicorp/kubernetes from the dependency lock fileapp - Using previously-installed hashicorp/kubernetes v2.14.0app 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 Stopapp 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 servicesNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGEkubernetes ClusterIP 10.96.0.1 <none> 443/TCP 142mmyapp-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 andworking. 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:latestThe push refers to repository [localhost:5000/nocorp-frontend]72d21ab0ed02: Pusheda831dca05db6: Pushed66018b4c7669: Pushed65f4bcfe9daa: Pushedadd6fa0cc975: Pushed8628cf05347a: Pushed85bfe2c7cf32: Pushed28e2f0d3695c: Pushedb892edc3d92e: Pushedf1486e967e48: Pushed5750262417ad: Pushed9ed3c35b4335: Pushed6f7f3f280040: Pushedd6e0d602719c: Pushed73c3e7ef7bc6: Pushedlatest: 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 deployapp Initializing the backend...app Initializing provider plugins...app - Reusing previous version of hashicorp/kubernetes from the dependency lock fileapp - 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 deploymentsNAME READY UP-TO-DATE AVAILABLE AGEmyapp-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: Pushede64b93b2c9bd: Pushed8628cf05347a: Mounted from nocorp-frontend85bfe2c7cf32: Mounted from nocorp-frontend28e2f0d3695c: Mounted from nocorp-frontendb892edc3d92e: Mounted from nocorp-frontendf1486e967e48: Mounted from nocorp-frontend5750262417ad: Mounted from nocorp-frontend9ed3c35b4335: Mounted from nocorp-frontend6f7f3f280040: Mounted from nocorp-frontendd6e0d602719c: Mounted from nocorp-frontend73c3e7ef7bc6: Mounted from nocorp-frontendlatest: 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 deployapp Initializing the backend...app Initializing provider plugins... - Reusing previous version of hashicorp/kubernetes from the dependency lock fileapp - 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 deploymentsNAME READY UP-TO-DATE AVAILABLE AGEmyapp-backend-dev 1/1 1 1 57smyapp-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 deployapp Initializing the backend...app Initializing provider plugins... - Reusing previous version of hashicorp/kubernetes from the dependency lock fileapp - Using previously-installed hashicorp/kubernetes v2.14.0app 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-testapp-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 directoryapp-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 deploymentsNAME READY UP-TO-DATE AVAILABLE AGEmyapp-backend-dev 1/1 1 1 46mmyapp-backend-test 2/2 2 2 71smyapp-frontend-dev 3/3 3 3 60mmyapp-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-testapp-test Initializing the backend...app-test Initializing provider plugins... - Reusing previous version of hashicorp/kubernetes from the dependency lock fileapp-test - Using previously-installed hashicorp/kubernetes v2.14.0app-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 0sapp-test kubernetes_service.app_frontend_service_C2863249 (app_frontend/service/service): Destruction complete after 0sapp-test Destroy complete! Resources: 4 destroyed.
Next, destroy your app
stack. Respond to the confirmation prompt by choosing
Approve
.
$ cdktf destroy appapp Initializing the backend...app Initializing provider plugins... - Reusing previous version of hashicorp/kubernetes from the dependency lock fileapp - Using previously-installed hashicorp/kubernetes v2.12.1app 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 0sapp kubernetes_service.app_frontend_service_C2863249 (app_frontend/service/service): Destruction complete after 0sapp Destroy complete! Resources: 4 destroyed.
Delete your kind cluster.
$ kind delete cluster --name=cdktf-appDeleting cluster "cdktf-app" ...
Stop your local registry Docker container.
$ docker stop local-registrylocal-registry
Remove the container.
$ docker rm local-registrylocal-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.