Creating Resources on Kubernetes

In Pulumi, resources represent the fundamental units that make up your infrastructure, such as virtual machines, networks, storage, and databases. A resource is used to define and manage an infrastructure object in your Pulumi configuration.
In this tutorial, you will create a simple Nginx server hosted on Kubernetes. You will then refer to documentation in the Pulumi Registry to create a service to make the Nginx deployment accessible.
In this tutorial, you'll learn:
- How to create a new resource
- How to reference resource definitions in the Pulumi documentation
Prerequisites:
- The Pulumi CLI
- A Pulumi Cloud account and access token
- A running Minikube cluster
- The Kubernetes command line tool kubectl installed
- Your desired language runtime installed
Create a new project
To start, login to the Pulumi CLI and create a new project and stack.
# Login to Pulumi by following the prompts
$ 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 :
-------------
# Create new project (example using Python)
$ mkdir pulumi-tutorial-k8s
$ cd pulumi-tutorial-k8s
$ pulumi new python
Then replace the default code with the following code snippet to scaffold your project with the required imports and overall program structure:
"use strict";
const pulumi = require("@pulumi/pulumi");
const k8s = require("@pulumi/kubernetes");
const appName = "nginx";
const appLabels = { app: appName };
// Create a deployment
// Create a service
"use strict";
const pulumi = require("@pulumi/pulumi");
const k8s = require("@pulumi/kubernetes");
const appName = "nginx";
const appLabels = { app: appName };
// Create a deployment
// Create a service
import pulumi
from pulumi_kubernetes.apps.v1 import Deployment
from pulumi_kubernetes.core.v1 import Service
app_labels = { "app": "nginx" }
app_name = "nginx"
# Create a deployment
# Create a service
package main
import (
appsv1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/apps/v1"
corev1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/core/v1"
metav1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/meta/v1"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
appName := "nginx"
appLabels := pulumi.StringMap{
"app": pulumi.String(appName),
}
deployment, err := appsv1.NewDeployment(ctx, appName, &appsv1.DeploymentArgs{
// Create a deployment
// Create a service
using Pulumi;
using Pulumi.Kubernetes.Core.V1;
using Pulumi.Kubernetes.Types.Inputs.Core.V1;
using Pulumi.Kubernetes.Types.Inputs.Apps.V1;
using Pulumi.Kubernetes.Types.Inputs.Meta.V1;
using System.Collections.Generic;
return await Deployment.RunAsync(() =>
{
var appName = "nginx";
var appLabels = new InputMap<string>
{
{ "app", appName },
};
// Create a deployment
// Create a service
name: k8s-deployment-service-for-minikube-yaml
description: An example that deploys a Deployment and Service for Minikube on Kubernetes.
runtime: yaml
variables:
appLabels:
app: nginx
resources:
# Create a deployment
# Create a service
This tutorial will define resources using the Kubernetes provider, so you will also need to make sure to install the Kubernetes dependency into your project.
Configure Kubernetes access
Now that you have a Pulumi project created, you will need to configure the Pulumi CLI to access your Kubernetes environment, in this case the Minikube cluster. Start by verifying that Minikube is running in your environment:
$ minikube status
minikube
type: Control Plane
host: Running
kubelet: Running
apiserver: Running
kubeconfig: Configured
Then configure the Pulumi CLI to connect to your Minikube cluster by running the following command:
$ pulumi config set kubernetes:context minikube
For the purposes of this tutorial, the default kubeconfig
is used, but you are also able to specify a custom kubeconfig file
if desired as shown below:
pulumi config set kubernetes:kubeconfig ./minikube-config.yaml
Create a deployment
The first resource you will create will be a Kubernetes deployment. The Pulumi Registry provides the documentation for all of the Pulumi providers and their associated resources. Open the kubernetes.apps/v1.Deployment documentation page to view a description of this resource, example usage, the resource definition, and supported properties. You will now define your deployment resource as shown below:
"use strict";
const pulumi = require("@pulumi/pulumi");
const k8s = require("@pulumi/kubernetes");
const appName = "nginx";
const appLabels = { app: appName };
const deployment = new k8s.apps.v1.Deployment(appName, {
spec: {
selector: { matchLabels: appLabels },
replicas: 1,
template: {
metadata: { labels: appLabels },
spec: { containers: [{ name: appName, image: "nginx" }] },
},
},
});
// Create a service
"use strict";
const pulumi = require("@pulumi/pulumi");
const k8s = require("@pulumi/kubernetes");
const appName = "nginx";
const appLabels = { app: appName };
const deployment = new k8s.apps.v1.Deployment(appName, {
spec: {
selector: { matchLabels: appLabels },
replicas: 1,
template: {
metadata: { labels: appLabels },
spec: { containers: [{ name: appName, image: "nginx" }] },
},
},
});
// Create a service
import pulumi
from pulumi_kubernetes.apps.v1 import Deployment
from pulumi_kubernetes.core.v1 import Service
app_labels = { "app": "nginx" }
app_name = "nginx"
deployment = Deployment(
app_name,
spec={
"selector": { "match_labels": app_labels },
"replicas": 1,
"template": {
"metadata": { "labels": app_labels },
"spec": { "containers": [{ "name": "nginx", "image": "nginx" }] }
},
})
# Create a service
package main
import (
appsv1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/apps/v1"
corev1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/core/v1"
metav1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/meta/v1"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
appName := "nginx"
appLabels := pulumi.StringMap{
"app": pulumi.String(appName),
}
deployment, err := appsv1.NewDeployment(ctx, appName, &appsv1.DeploymentArgs{
Spec: appsv1.DeploymentSpecArgs{
Selector: &metav1.LabelSelectorArgs{
MatchLabels: appLabels,
},
Replicas: pulumi.Int(1),
Template: &corev1.PodTemplateSpecArgs{
Metadata: &metav1.ObjectMetaArgs{
Labels: appLabels,
},
Spec: &corev1.PodSpecArgs{
Containers: corev1.ContainerArray{
corev1.ContainerArgs{
Name: pulumi.String("nginx"),
Image: pulumi.String("nginx"),
}},
},
},
},
})
if err != nil {
return err
}
// Create a service
using Pulumi;
using Pulumi.Kubernetes.Core.V1;
using Pulumi.Kubernetes.Types.Inputs.Core.V1;
using Pulumi.Kubernetes.Types.Inputs.Apps.V1;
using Pulumi.Kubernetes.Types.Inputs.Meta.V1;
using System.Collections.Generic;
return await Deployment.RunAsync(() =>
{
var appName = "nginx";
var appLabels = new InputMap<string>
{
{ "app", appName },
};
var deployment = new Pulumi.Kubernetes.Apps.V1.Deployment(appName, new DeploymentArgs
{
Spec = new DeploymentSpecArgs
{
Selector = new LabelSelectorArgs
{
MatchLabels = appLabels,
},
Replicas = 1,
Template = new PodTemplateSpecArgs
{
Metadata = new ObjectMetaArgs
{
Labels = appLabels,
},
Spec = new PodSpecArgs
{
Containers =
{
new ContainerArgs
{
Name = appName,
Image = "nginx",
Ports =
{
new ContainerPortArgs
{
ContainerPortValue = 80
},
},
},
},
},
},
},
});
// Create a service
name: k8s-deployment-service-for-minikube-yaml
description: An example that deploys a Deployment and Service for Minikube on Kubernetes.
runtime: yaml
variables:
appLabels:
app: nginx
resources:
deployment:
type: kubernetes:apps/v1:Deployment
properties:
spec:
selector:
matchLabels: ${appLabels}
replicas: 1
template:
metadata:
labels: ${appLabels}
spec:
containers:
- name: nginx
image: nginx
# Create a service
All resources have a required name
argument. Each resource has both a logical name and a physical name. The logical name is how the resource is known inside Pulumi. This is the value provided to the required name
argument. The physical name is the name used for the resource in the cloud provider that a Pulumi program is deploying to. It is a combination of the logical name plus a random suffix which helps to prevent resource naming collisions.
In the above example, the logical name for our deployment
resource is “nginx”, and the physical name might typically look something like “nginx-d7c2fa0”.
In addition to names, resources have properties and options.
Properties are used to specify what type of resource to create. Properties are often resource-specific, and they can be required or optional depending on the specifications of the provider.
The property defined in your kubernetes.apps/v1.Deployment
resource is:
Property | Description |
---|---|
spec | tells the Kubernetes provider the specification of the desired behavior of the deployment |
Options let you control certain aspects of a resource (such as showing explicit dependencies or importing existing infrastructure). You do not have any options defined for this resource, but you can learn more about how it works in the Resource options documentation.
Deploy your resources
Now run the pulumi up
command to preview and deploy the resouces you’ve just defined in your project.
$ pulumi up -y
Previewing update (k8s-tutorial)
Type Name Plan
+ pulumi:pulumi:Stack k8s-tutorial create
+ └─ kubernetes:apps/v1:Deployment nginx create
Resources:
+ 2 to create
Updating (k8s-tutorial)
Type Name Status
+ pulumi:pulumi:Stack k8s-tutorial created (14s)
+ └─ kubernetes:apps/v1:Deployment nginx created (12s)
Resources:
+ 2 created
Duration: 16s
Create a service
In this section, you will use Pulumi documentation to configure a Kubernetes Service. Use the following steps as a guide for adding the Service resource:
- Navigate to the Kubernetes Registry documentation page
- Search for the
kubernetes.core/v1.Service
resource - Define the Service resource in your project code
- Preview and deploy your updated project code
Once you have completed these steps, forward the Nginx service to make it accessible from localhost. First, retrieve the name and port of your service using the following command:
$ kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 44h
nginx-9e5d5cd4 ClusterIP 10.103.199.118 <none> 80/TCP 6m47s
In the example above, the name of the service is nginx-9e5d5cd4
. Then run the following command to perform the forwarding, making sure to provide the name and port of your own Nginx service in your own environment:
$ kubectl port-forward service/nginx-9e5d5cd4 8080:80
Forwarding from 127.0.0.1:8080 -> 80
Forwarding from [::1]:8080 -> 80
In a new terminal, verify that Nginx is running and accessible by running curl against the localhost as shown in the below example:
curl "http://localhost:8080"
<!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>
You should be greated with the HTML code of the Nginx landing page as shown above.
View complete solution
You can view the complete project code below:
"use strict";
const pulumi = require("@pulumi/pulumi");
const k8s = require("@pulumi/kubernetes");
const appName = "nginx";
const appLabels = { app: appName };
const deployment = new k8s.apps.v1.Deployment(appName, {
spec: {
selector: { matchLabels: appLabels },
replicas: 1,
template: {
metadata: { labels: appLabels },
spec: { containers: [{ name: appName, image: "nginx" }] },
},
},
});
const frontend = new k8s.core.v1.Service(appName, {
metadata: { labels: deployment.spec.template.metadata.labels },
spec: {
type: "ClusterIP",
ports: [{ port: 80, targetPort: 80, protocol: "TCP" }],
selector: appLabels,
},
});
"use strict";
const pulumi = require("@pulumi/pulumi");
const k8s = require("@pulumi/kubernetes");
const appName = "nginx";
const appLabels = { app: appName };
const deployment = new k8s.apps.v1.Deployment(appName, {
spec: {
selector: { matchLabels: appLabels },
replicas: 1,
template: {
metadata: { labels: appLabels },
spec: { containers: [{ name: appName, image: "nginx" }] },
},
},
});
const frontend = new k8s.core.v1.Service(appName, {
metadata: { labels: deployment.spec.template.metadata.labels },
spec: {
type: "ClusterIP",
ports: [{ port: 80, targetPort: 80, protocol: "TCP" }],
selector: appLabels,
},
});
import pulumi
from pulumi_kubernetes.apps.v1 import Deployment
from pulumi_kubernetes.core.v1 import Service
app_labels = { "app": "nginx" }
app_name = "nginx"
deployment = Deployment(
app_name,
spec={
"selector": { "match_labels": app_labels },
"replicas": 1,
"template": {
"metadata": { "labels": app_labels },
"spec": { "containers": [{ "name": "nginx", "image": "nginx" }] }
},
})
frontend = Service(
app_name,
metadata={
"labels": deployment.spec["template"]["metadata"]["labels"],
},
spec={
"type": "ClusterIP",
"ports": [{ "port": 80, "target_port": 80, "protocol": "TCP" }],
"selector": app_labels,
})
package main
import (
appsv1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/apps/v1"
corev1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/core/v1"
metav1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/meta/v1"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
appName := "nginx"
appLabels := pulumi.StringMap{
"app": pulumi.String(appName),
}
deployment, err := appsv1.NewDeployment(ctx, appName, &appsv1.DeploymentArgs{
Spec: appsv1.DeploymentSpecArgs{
Selector: &metav1.LabelSelectorArgs{
MatchLabels: appLabels,
},
Replicas: pulumi.Int(1),
Template: &corev1.PodTemplateSpecArgs{
Metadata: &metav1.ObjectMetaArgs{
Labels: appLabels,
},
Spec: &corev1.PodSpecArgs{
Containers: corev1.ContainerArray{
corev1.ContainerArgs{
Name: pulumi.String("nginx"),
Image: pulumi.String("nginx"),
}},
},
},
},
})
if err != nil {
return err
}
feType := "ClusterIP"
template := deployment.Spec.ApplyT(func(v appsv1.DeploymentSpec) *corev1.PodTemplateSpec {
return &v.Template
}).(corev1.PodTemplateSpecPtrOutput)
meta := template.ApplyT(func(v *corev1.PodTemplateSpec) *metav1.ObjectMeta { return v.Metadata }).(metav1.ObjectMetaPtrOutput)
_, _ = corev1.NewService(ctx, appName, &corev1.ServiceArgs{
Metadata: meta,
Spec: &corev1.ServiceSpecArgs{
Type: pulumi.String(feType),
Ports: &corev1.ServicePortArray{
&corev1.ServicePortArgs{
Port: pulumi.Int(80),
TargetPort: pulumi.Int(80),
Protocol: pulumi.String("TCP"),
},
},
Selector: appLabels,
},
})
return nil
})
}
using Pulumi;
using Pulumi.Kubernetes.Core.V1;
using Pulumi.Kubernetes.Types.Inputs.Core.V1;
using Pulumi.Kubernetes.Types.Inputs.Apps.V1;
using Pulumi.Kubernetes.Types.Inputs.Meta.V1;
using System.Collections.Generic;
return await Deployment.RunAsync(() =>
{
var appName = "nginx";
var appLabels = new InputMap<string>
{
{ "app", appName },
};
var deployment = new Pulumi.Kubernetes.Apps.V1.Deployment(appName, new DeploymentArgs
{
Spec = new DeploymentSpecArgs
{
Selector = new LabelSelectorArgs
{
MatchLabels = appLabels,
},
Replicas = 1,
Template = new PodTemplateSpecArgs
{
Metadata = new ObjectMetaArgs
{
Labels = appLabels,
},
Spec = new PodSpecArgs
{
Containers =
{
new ContainerArgs
{
Name = appName,
Image = "nginx",
Ports =
{
new ContainerPortArgs
{
ContainerPortValue = 80
},
},
},
},
},
},
},
});
var frontend = new Service(appName, new ServiceArgs
{
Metadata = new ObjectMetaArgs
{
Labels = deployment.Spec.Apply(spec =>
spec.Template.Metadata.Labels
),
},
Spec = new ServiceSpecArgs
{
Type = "ClusterIP",
Selector = appLabels,
Ports = new ServicePortArgs
{
Port = 80,
TargetPort = 80,
Protocol = "TCP",
},
}
});
});
name: k8s-deployment-service-for-minikube-yaml
description: An example that deploys a Deployment and Service for Minikube on Kubernetes.
runtime: yaml
variables:
appLabels:
app: nginx
resources:
deployment:
type: kubernetes:apps/v1:Deployment
properties:
spec:
selector:
matchLabels: ${appLabels}
replicas: 1
template:
metadata:
labels: ${appLabels}
spec:
containers:
- name: nginx
image: nginx
service:
type: kubernetes:core/v1:Service
properties:
metadata:
labels: ${appLabels}
spec:
type: ClusterIP
selector: ${appLabels}
ports:
- port: 80
targetPort: 80
protocol: TCP
Clean up
Before moving on, tear down the resources that are part of your stack to avoid incurring any charges.
- Run
pulumi destroy
to tear down all resources. You'll be prompted to make sure you really want to delete these resources. A destroy operation may take some time, since Pulumi waits for the resources to finish shutting down before it considers the destroy operation to be complete. - To delete the stack itself, run
pulumi stack rm
. Note that this command deletes all deployment history from the Pulumi Service.
Next steps
In this tutorial, you created a Kubernetes deployment resource, and you created a Service resource by referencing the Pulumi registry. You also reviewed resource properties and example usage of various resources.
To learn more about creating resources in Pulumi, take a look at the following resources:
- Learn more about stack outputs and references in the Stack Outputs and References tutorial.
- Learn more about inputs and outputs in the Inputs and Outputs documentation.
- Learn more about resource names, options, and providers in the Pulumi documentation.