Skip to main content

Deploy a Stateless Application on Kubernetes

15 min

In this tutorial, you'll run a stateless application on Kubernetes using a Deployment object, authored with Pulumi and TypeScript instead of YAML and the kubectl CLI. This gives you the full power of a familiar programming language combined with immutable infrastructure for a robust, repeatable update experience.

You'll deploy an Nginx container, inspect the resulting Kubernetes resources with kubectl, update the container image to see Pulumi's structured diff, and scale the application by changing a single configuration value.

What you'll learn
  • How to define a Kubernetes Deployment with Pulumi and TypeScript
  • How Pulumi waits for a deployment to converge, unlike kubectl
  • How to update a container image and review a structured diff before applying it
  • How to scale an application by changing a single configuration value
Prerequisites

In this tutorial, you’ll run an application using a Kubernetes Deployment object. This results in an automatically scaled-out container running inside of a cluster.

This example is authored using Pulumi’s programming model in TypeScript instead of YAML, and using the pulumi CLI for deployment rather than kubectl. This gives you the full power of familiar languages, combined with immutable infrastructure, delivering a robust and repeatable update experience.

This example is based on this Kubernetes tutorial, and its full code is available on GitHub.

By the end, you’ll have:

  • Created a Deployment using Pulumi that runs Nginx
  • Used pulumi to deploy this application
  • Used kubectl to list information about the resulting Kubernetes resources
  • Updated the deployment’s number of replicas using pulumi
  • Cleaned up

Create and configure a project#

Normally you would write YAML files to configure your services, and then run kubectl commands to create and manage them. Instead of doing that, you’ll author your program in code and deploy it with pulumi.

To start, create a new Pulumi project from a template:

Terminal window
mkdir k8s-nginx && cd k8s-nginx
pulumi new kubernetes-typescript

This command initializes a fresh project in the newly created k8s-nginx directory.

Next, replace the minimal contents of the template’s index.ts file with the code for the deployment:

import * as pulumi from "@pulumi/pulumi";
import * as k8s from "@pulumi/kubernetes";
let config = new pulumi.Config();
let nginxLabels = { app: "nginx" };
let nginxDeployment = new k8s.apps.v1.Deployment("nginx-deployment", {
spec: {
selector: { matchLabels: nginxLabels },
replicas: config.getNumber("replicas") || 2,
template: {
metadata: { labels: nginxLabels },
spec: {
containers: [{
name: "nginx",
image: "nginx:1.7.9",
ports: [{ containerPort: 80 }]
}],
},
},
},
});
export let nginx = nginxDeployment.metadata.apply(md => md.name);

This code simply creates a Kubernetes Deployment object. The entire Kubernetes object model is available to you, giving you the full power of Kubernetes right away.

Deploy the application#

Now you’re ready to deploy your code. To do so, run pulumi up:

Terminal window
pulumi up

The command first shows a complete preview of what will take place, with a confirmation prompt. No changes are made yet. It should look something like this:

Previewing update of stack 'k8s-nginx-dev'
Previewing changes:
Type Name Plan Info
+ pulumi:pulumi:Stack k8s-nginx-k8s-nginx-dev create
+ └─ kubernetes:apps:Deployment nginx-deployment create
info: 2 changes previewed:
+ 2 resources to create
Do you want to perform this update?
> yes
no
details

Select “yes” and hit enter. The deployment proceeds, and the output looks like this:

Updating stack 'k8s-nginx-dev'
Performing changes:
Type Name Status Info
+ pulumi:pulumi:Stack k8s-nginx-k8s-nginx-dev created
+ └─ kubernetes:apps:Deployment nginx-deployment created
---outputs:---
nginx: "nginx-deployment-rlefbi4w"
info: 2 changes performed:
+ 2 resources created
Update duration: 8.127334048s
Permalink: https://app.pulumi.com/joeduffy/k8s-nginx-dev/updates/1

Inspect the Kubernetes resources#

Now that you’ve done the deployment, check some state with kubectl:

Terminal window
kubectl describe deployment $(pulumi stack output nginx)

Notice that you used the pulumi stack output command to fetch the auto-generated deployment name.

The output will look similar to this:

Name: nginx-deployment-rlefbi4w
Namespace: default
CreationTimestamp: Tue, 30 Aug 2018 18:11:37 -0700
Labels: app=nginx
Annotations: deployment.kubernetes.io/revision=1
Selector: app=nginx
Replicas: 2 desired | 2 updated | 2 total | 2 available | 0 unavailable
StrategyType: RollingUpdate
MinReadySeconds: 0
RollingUpdateStrategy: 1 max unavailable, 1 max surge
Pod Template:
Labels: app=nginx
Containers:
nginx:
Image: nginx:1.7.9
Port: 80/TCP
Environment: <none>
Mounts: <none>
Volumes: <none>
Conditions:
Type Status Reason
---- ------ ------
Available True MinimumReplicasAvailable
Progressing True NewReplicaSetAvailable
OldReplicaSets: <none>
NewReplicaSet: nginx-deployment-rlefbi4w-1771418926 (2/2 replicas created)
No events.

Notice the StrategyType is RollingUpdate with a RollingUpdateStrategy of 1 max unavailable, 1 max surge. This is how Kubernetes rolls out changes to the deployment without downtime, replacing pods gradually.

Now list the pods created by this deployment:

Terminal window
kubectl get pods -l app=nginx

The output will look something like this:

NAME READY STATUS RESTARTS AGE
nginx-deployment-rlefbi4w-1771418926-7o5ns 1/1 Running 0 1m
nginx-deployment-rlefbi4w-1771418926-r18az 1/1 Running 0 1m

Update the deployment#

You can update your program by changing the source code and re-running pulumi up. Do two quick updates to see what this looks like. After that, you’ll clean up and you’re done!

First, update your version of Nginx from 1.7 to 1.8. Simply replace the line:

image: "nginx:1.7.9",

with:

image: "nginx:1.8",

and re-run pulumi up. You’ll see a preview that indicates just the spec changed:

Previewing update of stack 'k8s-nginx-dev'
Previewing changes:
Type Name Plan Info
* pulumi:pulumi:Stack k8s-nginx-k8s-nginx-dev no change
~ └─ kubernetes:apps:Deployment nginx-deployment update changes: ~ spec
---outputs:---
nginx: "nginx-deployment-rlefbi4w"
info: 1 change previewed:
~ 1 resource to update
1 resource unchanged
Do you want to perform this update?
yes
no
> details

If you choose details and hit enter, you’ll see a full diff of the changes:

* pulumi:pulumi:Stack: (same)
[urn=urn:pulumi:k8s-nginx-dev::k8s-nginx::pulumi:pulumi:Stack::k8s-nginx-k8s-nginx-dev]
---outputs:---
nginx: "nginx-deployment-rlefbi4w"
~ kubernetes:apps/v1:Deployment: (update)
[id=default/nginx-deployment-rlefbi4w]
[urn=urn:pulumi:k8s-nginx-dev::k8s-nginx::kubernetes:apps/v1:Deployment::nginx-deployment]
~ spec : {
~ template: {
~ spec : {
~ containers: [
~ [0]: {
~ image: "nginx:1.7.9" => "nginx:1.8"
}
]
}
}
}

If you select yes and hit enter to proceed with the update, the deployment is updated in place.

Scale the application#

Next, scale your application by increasing the replica count. Notice this example used the Pulumi configuration system so that the replica count can be easily changed. Simply run:

Terminal window
pulumi config set replicas 4

and re-run pulumi up. Pulumi figures out the minimal set of changes to make:

Terminal window
pulumi up

The output from running this command will look like the usual update, with a preview diff, prompt, and details.

Clean up#

From here, feel free to experiment. As soon as you’re done, clean up your stack:

Terminal window
pulumi destroy --yes
pulumi stack rm --yes

Afterwards, query the list of pods to verify that none are remaining:

Terminal window
kubectl get pods -l app=nginx

This should print out something along these lines:

No resources found.

</content> </invoke>

Related

The infrastructure as code platform for any cloud.