Creating Kubernetes Guestbook App With Pulumi
Tags: typescript, pulumi, iac, kubernetes
Published: 2021-11-02 18:41:25

[Pulumi](https://www.pulumi.com/) is an open-source [Infrastructure as code (IaC)](https://docs.microsoft.com/en-us/devops/deliver/what-is-infrastructure-as-code) SDK.
It's possible to use common programming languages to create, deploy and manage infrastructure in the most popular clouds.

This article shows how to use Pulumi to create a Kubernetes infrastructure.
We'll create, build and deploy the Kubernetes Guestbook example using typescript whit Pulumi.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wt9vi6e8sm0nlvlccu3f.png)
 

## Installation

We'll use NodeJs and npm, so install both:

- Install [NodeJS](https://nodejs.org/en/download/) (10.x or later)
- Install [npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)

You have to install Pulumi CLI:

https://www.pulumi.com/docs/get-started/install/

install Pulumi on Windows:

``` shell
$ choco install pulumi
```

or on Linux:

``` shell
$ curl -fsSL https://get.pulumi.com | sh
```

or on MacOS:

``` shell
$ brew install pulumi
```

## Create new project

Let's start creating a folder and Pulumi project:

``` shell
$ mkdir quickstart && cd quickstart
$ pulumi new kubernetes-typescript
```
After, finish project setup on prompt:
1. Set the project name
2. Set the project description
3. Create a stack (in this case, we will use `dev`)

![Setup prompt in VSCode](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zzsjoietnijbpp4rjhco.png)

Change `index.ts` file with the below code:

``` typescript
import * as pulumi from "@pulumi/pulumi";
import * as k8sjs from "./config/k8sconfig";

let stack = pulumi.getStack();
let project = pulumi.getProject();

console.log(`Creating Pulumi Project ${project} on Stack ${stack}`)

const config = new pulumi.Config();
const isMinikube = stack == "dev";

const redisImageLeader = "docker.io/redis:6.0.5";
const redisImageReplica = "gcr.io/google_samples/gb-redis-follower:v2";
const frontEndImage = "gcr.io/google_samples/gb-frontend:v5";

const redisLeader = new k8sjs.ServiceDeployment("redis-leader", {
    image: redisImageLeader,
    ports: [6379],
});

const redisReplica = new k8sjs.ServiceDeployment("redis-replica", {
    image: redisImageReplica,
    ports: [6379],
});

const frontend = new k8sjs.ServiceDeployment("frontend", {
    replicas: 3,
    image: frontEndImage,
    ports: [80],
    allocateIpAddress: true,
    isMinikube: isMinikube,
});

export let frontendIp = frontend.ipAddress;

```

And we will create a folder to components of the configuration of the project:

- `serviceDeployment.ts`
```typescript
import * as k8s from "@pulumi/kubernetes";
import * as k8stypes from "@pulumi/kubernetes/types/input";
import * as pulumi from "@pulumi/pulumi";
import * as serviceDeployArgs  from "./serviceDeploymentArgs"

export class ServiceDeployment extends pulumi.ComponentResource {
    public readonly deployment: k8s.apps.v1.Deployment;
    public readonly service: k8s.core.v1.Service;
    public readonly ipAddress?: pulumi.Output<string>;

    constructor(name: string, args: serviceDeployArgs.ServiceDeploymentArgs,
                opts?: pulumi.ComponentResourceOptions) {
        super("k8sjs:service:ServiceDeployment", name, {}, opts);

        const labels = { app: name };
        const container: k8stypes.core.v1.Container = {
            name,
            image: args.image,
            resources: args.resources || { requests: { cpu: "100m", memory: "100Mi" } },
            env: [{ name: "GET_HOSTS_FROM", value: "dns" }],
            ports: args.ports && args.ports.map(p => ({ containerPort: p })),
        };
        this.deployment = new k8s.apps.v1.Deployment(name, {
            spec: {
                selector: { matchLabels: labels },
                replicas: args.replicas || 1,
                template: {
                    metadata: { labels: labels },
                    spec: { containers: [ container ] },
                },
            },
        }, { parent: this });

        this.service = new k8s.core.v1.Service(name, {
            metadata: {
                name: name,
                labels: this.deployment.metadata.labels,
            },
            spec: {
                ports: args.ports && args.ports.map(p => ({ port: p, targetPort: p })),
                selector: this.deployment.spec.template.metadata.labels,
                type: args.allocateIpAddress ? (args.isMinikube ? "ClusterIP" : "LoadBalancer") : undefined,
            },
        }, { parent: this });

        if (args.allocateIpAddress) {
            this.ipAddress = args.isMinikube ?
                this.service.spec.clusterIP :
                this.service.status.loadBalancer.ingress[0].ip;
        }
    }
}

```
- `serviceDeploymentArgs.ts`

```typescript
import * as k8stypes from "@pulumi/kubernetes/types/input";

export interface ServiceDeploymentArgs {
    image: string;
    resources?: k8stypes.core.v1.ResourceRequirements;
    replicas?: number;
    ports?: number[];
    allocateIpAddress?: boolean;
    isMinikube?: boolean;
}

```
Basically, in `index.ts` we define the images of deployments and call `serviceDeployment.ts` to create container, deployment, and service in K8s.
 
Moreover, in the project, we have files with metadata of the project.
The `Pulumi.yaml` and `Pulumi.dev.yaml` define metadata of configuration of the project, like name, description, and custom properties:

`Pulumi.yaml`
```yaml
name: k8s-guestbook
runtime: nodejs
description: Creating Kubernets Gustbook with Pulumi
```

`Pulumi.dev.yaml`
```yaml
config:
  k8s-guestbook:isMinikube: "true"
  k8s-guestbook:useLoadBalancer: "false"
```
## Deploy to Kubernets

The command to create and update a stack is:

```shell
$ pulumi up
```
Afther this performe updates using prompt, selecting yes:
```sh
Previewing update (dev)

View Live: https://app.pulumi.com/acme/k8s-guestbook/dev/previews/9fb88818e4-756d-48ed-baf8-f44774d9bbd0

     Type                                 Name               Plan       Info
 +   pulumi:pulumi:Stack                  k8s-guestbook-dev  create     1 message
 +   ├─ k8sjs:service:ServiceDeployment   redis-leader       create
 +   │  ├─ kubernetes:apps/v1:Deployment  redis-leader       create
 +   │  └─ kubernetes:core/v1:Service     redis-leader       create
 +   ├─ k8sjs:service:ServiceDeployment   frontend           create
 +   │  ├─ kubernetes:apps/v1:Deployment  frontend           create
 +   │  └─ kubernetes:core/v1:Service     frontend           create
 +   └─ k8sjs:service:ServiceDeployment   redis-replica      create
 +      ├─ kubernetes:apps/v1:Deployment  redis-replica      create
 +      └─ kubernetes:core/v1:Service     redis-replica      create

Diagnostics:
  pulumi:pulumi:Stack (k8s-guestbook-dev):
    Creating Pulumi Project k8s-guestbook on Stack dev


Do you want to perform this update? yes
Updating (dev)

View Live: https://app.pulumi.com/acme/k8s-guestbook/dev/updates/23

     Type                                 Name               Status      Info
 +   pulumi:pulumi:Stack                  k8s-guestbook-dev  created     1 message
 +   ├─ k8sjs:service:ServiceDeployment   frontend           created
 +   │  ├─ kubernetes:apps/v1:Deployment  frontend           created
 +   │  └─ kubernetes:core/v1:Service     frontend           created
 +   ├─ k8sjs:service:ServiceDeployment   redis-replica      created
 +   │  ├─ kubernetes:apps/v1:Deployment  redis-replica      created
 +   │  └─ kubernetes:core/v1:Service     redis-replica      created
 +   └─ k8sjs:service:ServiceDeployment   redis-leader       created
 +      ├─ kubernetes:apps/v1:Deployment  redis-leader       created
 +      └─ kubernetes:core/v1:Service     redis-leader       created

Diagnostics:
  pulumi:pulumi:Stack (k8s-guestbook-dev):
    Creating Pulumi Project k8s-guestbook on Stack dev

Outputs:
    frontendIp: "10.108.227.133"

Resources:
    + 10 created

Duration: 44s
```
## Checking the results

Inspect your cluster to validate the Guestbook App services:

```sh
$ kubectl get services
``` 
Minikube does not support type `LoadBalancer`; if you are using dev stack, run:

```sh
$ kubectl port-forward svc/frontend 8080:80
 ```
and open in browser [http://localhost:8080](http://localhost:8080)

![Result Guestbook in browser](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p95u5v8q5clv0sh8qc6o.png)
 
 

## Destroying the Project

To clean up and destroy resources, run commands to destroy and remove your stack/resources:
 ```sh
$ pulumi destroy --yes
$ pulumi stack rm --yes
 ```
 
## Links

Pulumi example projects https://github.com/pulumi/examples

## Conclusion

Pulumi is a good alternative to use the most common program languages (javascript, typescript, python, C# and Go) to create IaC projects in popular clouds (AWS, Azure, and Google Cloud).

The code of this post is available on [Github](https://github.com/viniciusabreusouza/pulumi-k8s-guestbook)