Pulumi: Deploy Instrastructure as Code using your favorite programming language

6 min read | by Jordi Prats

Unlike Terraform that uses it's own language to deploy Instrastructure as Code (IaC), with Pulumi we can choose between any of the supported programming languages. For this tutorial we are going to use python deploying an object to a Kubernetes cluster

First we will have to install Pulumi as follows:

$ curl -fsSL https://get.pulumi.com | sh
=== Installing Pulumi v3.16.0 ===
+ Downloading https://get.pulumi.com/releases/sdk/pulumi-v3.16.0-linux-x64.tar.gz...
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 61.3M  100 61.3M    0     0  32.2M      0  0:00:01  0:00:01 --:--:-- 32.1M
+ Extracting to /home/pet2cattle/.pulumi/bin
+ Adding $HOME/.pulumi/bin to $PATH in /home/pet2cattle/.bashrc

=== Pulumi is now installed! 🍹 ===
+ Please restart your shell or add /home/pet2cattle/.pulumi/bin to your $PATH
+ Get started with Pulumi: https://www.pulumi.com/docs/quickstart

With Pulumi we can choose between several backends to store it's state, being Pulumi's cloud service the default. We are going to use it for simplicity's sake, so we'll have to register using the following URL:

https://app.pulumi.com/account/tokens

Once registered we'll have to login using the CLI and the access token we will be able to create once registered:

$ pulumi login
Manage your Pulumi stacks by logging in.
Run `pulumi login --help` for alternative login options.
Enter your access token from https://app.pulumi.com/account/tokens
    or hit <ENTER> to log in using your browser                   : 


  Welcome to Pulumi!

  Pulumi helps you create, deploy, and manage infrastructure on any cloud using
  your favorite language. You can get started today with Pulumi at:

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

  Tip of the day: Resources you create with Pulumi are given unique names (a randomly
  generated suffix) by default. To learn more about auto-naming or customizing resource
  names see https://www.pulumi.com/docs/intro/concepts/resources/#autonaming.


Logged in to pulumi.com as jordiprats (https://app.pulumi.com/jordiprats)

To be able to create the first python project using Pulumi we'll need to have installed the following dependencies:

  • python 3
  • python 3 venv

On Debian/Ubuntu we can just run:

sudo apt install python3.8 python3.8-venv

At this point we are all set up to be able to init a new project using pulumi new. Since we want to have installed the dependencies to deploy to Kubernetes using Python we are going to use kubernetes-python. Using the -n flag we telling Pulumi how we want to name our new project:

$ pulumi new kubernetes-python -n simple-example
This command will walk you through creating a new Pulumi project.

Enter a value or leave blank to accept the (default), and press <ENTER>.
Press ^C at any time to quit.

project description: (A minimal Python Pulumi program) 
Created project 'simple-example'

Please enter your desired stack name.
To create a stack in an organization, use the format <org-name>/<stack-name> (e.g. `acmecorp/dev`).
stack name: (dev) 
Created stack 'dev'

Creating virtual environment...
Finished creating virtual environment
(...)

To perform an initial deployment, run 'pulumi up'

By default we will have an example on how to push a Deployment, but as a first step we are going to create a Namespace using the following code:

import pulumi
from pulumi_kubernetes.core.v1 import Namespace

config = pulumi.Config()
demoNS = Namespace("test1")

pulumi.export("demoNS", demoNS.metadata.apply(lambda m: m.name))

This code tells pulumi to deploy a namespace tha it's resource name is going to be test1, we can check what is going to do using pulumi preview (which is equivalent to terraform plan)

$ pulumi preview
Previewing update (dev)

View Live: https://app.pulumi.com/jordiprats/simple-example/dev/previews/f08d0489-26fb-49aa-9054-1de352d2ea36

     Type                             Name                Plan       
 +   pulumi:pulumi:Stack              simple-example-dev  create     
 +   └─ kubernetes:core/v1:Namespace  test1               create     

Resources:
    + 2 to create

Using pulumi up it's going to apply it (equivalent to terraform apply)

$ pulumi up
Previewing update (dev)

View Live: https://app.pulumi.com/jordiprats/simple-example/dev/previews/3d81b12e-4933-4534-9823-6fe748ca7a88

     Type                             Name                Plan       
 +   pulumi:pulumi:Stack              simple-example-dev  create     
 +   └─ kubernetes:core/v1:Namespace  test1               create     

Resources:
    + 2 to create

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

View Live: https://app.pulumi.com/jordiprats/simple-example/dev/updates/1

     Type                             Name                Status      
 +   pulumi:pulumi:Stack              simple-example-dev  created     
 +   └─ kubernetes:core/v1:Namespace  test1               created     

Outputs:
    demoNS: "test1-78gjwsog"

Resources:
    + 2 created

Duration: 6s

If we check the list of namespaces we will see that it have created a namespace named after pulumi's resource but not exactly what we have called it:

$ kubectl get ns
NAME                  STATUS   AGE
(...)
test1-78gjwsog        Active   23s
$ kubectl describe ns test1-78gjwsog
Name:         test1-78gjwsog
Labels:       app.kubernetes.io/managed-by=pulumi
              kubernetes.io/metadata.name=test1-78gjwsog
Annotations:  pulumi.com/autonamed: true
Status:       Active

No resource quota.

No LimitRange resource.

We can destroy what we have just created by using pulumi destroy (this command is the same we would use with terraform)

$ pulumi destroy
Previewing destroy (dev)

View Live: https://app.pulumi.com/jordiprats/simple-example/dev/previews/62fdf19d-32af-493e-8e88-1d30bb7b5a34

     Type                             Name                Plan       
 -   pulumi:pulumi:Stack              simple-example-dev  delete     
 -   └─ kubernetes:core/v1:Namespace  test1               delete     

Outputs:
  - demoNS: "test1-78gjwsog"

Resources:
    - 2 to delete

Do you want to perform this destroy? yes
Destroying (dev)

View Live: https://app.pulumi.com/jordiprats/simple-example/dev/updates/2

     Type                             Name                Status      
 -   pulumi:pulumi:Stack              simple-example-dev  deleted     
 -   └─ kubernetes:core/v1:Namespace  test1               deleted     

Outputs:
  - demoNS: "test1-78gjwsog"

Resources:
    - 2 deleted

Duration: 11s

The resources in the stack have been deleted, but the history and configuration associated with the stack are still maintained. 
If you want to remove the stack completely, run 'pulumi stack rm dev'.

To be able to specify the exact name of the resource we will have to set it's metadata, just like we do with Kubernetes when we create the YAML definition of the object. The code that does it looks like this:

import pulumi
from pulumi_kubernetes.core.v1 import Namespace

config = pulumi.Config()
demoNS = Namespace("test1",
                    metadata={
                        "name": "test1"
                        })

pulumi.export("demoNS", demoNS.metadata.apply(lambda m: m.name))

If we apply this code:

$ pulumi up
Previewing update (dev)

View Live: https://app.pulumi.com/jordiprats/simple-example/dev/previews/4402bdd0-45d6-4642-94d2-8d06e8a83982

     Type                             Name                Plan       
 +   pulumi:pulumi:Stack              simple-example-dev  create     
 +   └─ kubernetes:core/v1:Namespace  test1               create     

Resources:
    + 2 to create

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

View Live: https://app.pulumi.com/jordiprats/simple-example/dev/updates/5

     Type                             Name                Status      
 +   pulumi:pulumi:Stack              simple-example-dev  created     
 +   └─ kubernetes:core/v1:Namespace  test1               created     

Outputs:
    demoNS: "test1"

Resources:
    + 2 created

Duration: 6s

We will be able to see that now the namespace doesn't have an auto-generated name but the same exact name we have definied:

$ kubectl get ns
NAME                  STATUS   AGE
(...)
test1                 Active   19s
$ kubectl describe ns test1
Name:         test1
Labels:       app.kubernetes.io/managed-by=pulumi
              kubernetes.io/metadata.name=test1
Annotations:  <none>
Status:       Active

No resource quota.

No LimitRange resource.

You can also find this example's code on this GitHub repo


Posted on 02/11/2021