Introducing kube2pulumi: No More YAML
Posted on
Kubernetes users often joke about being “YAML engineers,” and the pile of YAML seems to get deeper every day. Today, we’re pleased to announce kube2pulumi, a tool to automatically convert Kubernetes manifests into modern code! Instead of manipulating YAML directly, you can take advantage of the rich ecosystem of programming language tools to supercharge your productivity.
Using programming languages to manage Kubernetes resources has many benefits: strong typing, linting, auto-completion,
refactoring, testing, and the ability to use standard language features like variables, loops, conditions, and
abstraction. Pulumi also supports Helm, Kustomize, and YAML, but these tools don’t take full advantage of the
programming language. With kube2pulumi
, you can realize these benefits without manual conversion work upfront.
If you want to try it out right now in your browser, head over to our Kubernetes to Pulumi Converter!
Otherwise, keep reading as we check out an example of kube2pulumi
in action, and then take a closer look at the
generated code.
Getting Started with kube2pulumi
Let’s start with a simple example: converting a Namespace. Here’s the YAML manifest:
apiVersion: v1
kind: Namespace
metadata:
name: foo
kube2pulumi
will generate any Pulumi-supported language, so choose whichever one you like!
Here, we choose TypeScript and select our YAML manifest.
$ kube2pulumi typescript -f namespace.yaml
Conversion successful! Generated File: namespace.ts
import * as pulumi from "@pulumi/pulumi";
import * as kubernetes from "@pulumi/kubernetes";
const fooNamespace = new kubernetes.core.v1.Namespace("fooNamespace", {
apiVersion: "v1",
kind: "Namespace",
metadata: {
name: "foo",
},
});
Here, we choose Python and select our YAML manifest.
$ kube2pulumi python -f namespace.yaml
Conversion successful! Generated File: `__main__.py`
import pulumi
import pulumi_kubernetes as kubernetes
foo_namespace = kubernetes.core.v1.Namespace("fooNamespace",
api_version="v1",
kind="Namespace",
metadata={
"name": "foo",
})
Here, we choose C# and select our YAML manifest.
$ kube2pulumi csharp -f namespace.yaml
Conversion successful! Generated File: Program.cs
using Pulumi;
using Kubernetes = Pulumi.Kubernetes;
class MyStack : Stack
{
public MyStack()
{
var fooNamespace = new Kubernetes.Core.V1.Namespace("fooNamespace", new Kubernetes.Types.Inputs.Core.V1.NamespaceArgs
{
ApiVersion = "v1",
Kind = "Namespace",
Metadata = new Kubernetes.Types.Inputs.Meta.V1.ObjectMetaArgs
{
Name = "foo",
},
});
}
}
Here, we choose Go and select our YAML manifest.
$ kube2pulumi go -f namespace.yaml
Conversion successful! Generated File: main.go
package main
import (
corev1 "github.com/pulumi/pulumi-kubernetes/sdk/v2/go/kubernetes/core/v1"
metav1 "github.com/pulumi/pulumi-kubernetes/sdk/v2/go/kubernetes/meta/v1"
"github.com/pulumi/pulumi/sdk/v2/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
_, err := corev1.NewNamespace(ctx, "fooNamespace", &corev1.NamespaceArgs{
ApiVersion: pulumi.String("v1"),
Kind: pulumi.String("Namespace"),
Metadata: &metav1.ObjectMetaArgs{
Name: pulumi.String("foo"),
},
})
if err != nil {
return err
}
return nil
})
}
Multi-resource Example
That’s pretty cool but doesn’t show off the full power of kube2pulumi
. Let’s try a more realistic conversion
scenario. Our newly announced Pulumi Kubernetes Operator requires a few different resources to install: a Service
Account, Role, RoleBinding, and Deployment, totaling about 130 lines of YAML. Let’s try converting that directory
to some real code!
$ ls operator
operator.yaml role.yaml role_binding.yaml service_account.yaml
$ kube2pulumi typescript -d operator
Conversion successful! Generated File: operator/main.ts
import * as pulumi from "@pulumi/pulumi";
import * as kubernetes from "@pulumi/kubernetes";
const pulumi_kubernetes_operatorDeployment = new kubernetes.apps.v1.Deployment("pulumi_kubernetes_operatorDeployment", {
apiVersion: "apps/v1",
kind: "Deployment",
metadata: {
name: "pulumi-kubernetes-operator",
},
spec: {
replicas: 1,
selector: {
matchLabels: {
name: "pulumi-kubernetes-operator",
},
},
template: {
metadata: {
labels: {
name: "pulumi-kubernetes-operator",
},
},
spec: {
serviceAccountName: "pulumi-kubernetes-operator",
imagePullSecrets: [{
name: "pulumi-kubernetes-operator",
}],
containers: [{
name: "pulumi-kubernetes-operator",
image: "pulumi/pulumi-kubernetes-operator:v0.0.2",
command: ["pulumi-kubernetes-operator"],
args: ["--zap-level=debug"],
imagePullPolicy: "Always",
env: [
{
name: "WATCH_NAMESPACE",
valueFrom: {
fieldRef: {
fieldPath: "metadata.namespace",
},
},
},
{
name: "POD_NAME",
valueFrom: {
fieldRef: {
fieldPath: "metadata.name",
},
},
},
{
name: "OPERATOR_NAME",
value: "pulumi-kubernetes-operator",
},
],
}],
},
},
},
});
const pulumi_kubernetes_operatorRole = new kubernetes.rbac.v1.Role("pulumi_kubernetes_operatorRole", {
apiVersion: "rbac.authorization.k8s.io/v1",
kind: "Role",
metadata: {
name: "pulumi-kubernetes-operator",
},
rules: [
{
apiGroups: [""],
resources: [
"pods",
"services",
"services/finalizers",
"endpoints",
"persistentvolumeclaims",
"events",
"configmaps",
"secrets",
],
verbs: [
"create",
"delete",
"get",
"list",
"patch",
"update",
"watch",
],
},
{
apiGroups: ["apps"],
resources: [
"deployments",
"daemonsets",
"replicasets",
"statefulsets",
],
verbs: [
"create",
"delete",
"get",
"list",
"patch",
"update",
"watch",
],
},
{
apiGroups: ["monitoring.coreos.com"],
resources: ["servicemonitors"],
verbs: [
"get",
"create",
],
},
{
apiGroups: ["apps"],
resourceNames: ["pulumi-kubernetes-operator"],
resources: ["deployments/finalizers"],
verbs: ["update"],
},
{
apiGroups: [""],
resources: ["pods"],
verbs: ["get"],
},
{
apiGroups: ["apps"],
resources: [
"replicasets",
"deployments",
],
verbs: ["get"],
},
{
apiGroups: ["pulumi.com"],
resources: ["*"],
verbs: [
"create",
"delete",
"get",
"list",
"patch",
"update",
"watch",
],
},
],
});
const pulumi_kubernetes_operatorRoleBinding = new kubernetes.rbac.v1.RoleBinding("pulumi_kubernetes_operatorRoleBinding", {
kind: "RoleBinding",
apiVersion: "rbac.authorization.k8s.io/v1",
metadata: {
name: "pulumi-kubernetes-operator",
},
subjects: [{
kind: "ServiceAccount",
name: "pulumi-kubernetes-operator",
}],
roleRef: {
kind: "Role",
name: "pulumi-kubernetes-operator",
apiGroup: "rbac.authorization.k8s.io",
},
});
const pulumi_kubernetes_operatorServiceAccount = new kubernetes.core.v1.ServiceAccount("pulumi_kubernetes_operatorServiceAccount", {
apiVersion: "v1",
kind: "ServiceAccount",
metadata: {
name: "pulumi-kubernetes-operator",
},
});
Superpowers
Now that we’ve got some Kubernetes resources defined in real code, we can start working on them like any other software project. First, we define a variable for the operator name and then swap out hardcoded string references to the other resource names. With these changes, it doesn’t require any thought to keep these resources in sync! You can later decide to change the name in one place, and every other reference will update automatically as well.
const operatorName = "pulumi-kubernetes-operator";
const pulumi_kubernetes_operatorServiceAccount = new kubernetes.core.v1.ServiceAccount("pulumi_kubernetes_operatorServiceAccount", {
apiVersion: "v1",
kind: "ServiceAccount",
metadata: {
name: operatorName,
},
});
const pulumi_kubernetes_operatorRoleBinding = new kubernetes.rbac.v1.RoleBinding("pulumi_kubernetes_operatorRoleBinding", {
kind: "RoleBinding",
apiVersion: "rbac.authorization.k8s.io/v1",
metadata: {
name: operatorName,
},
subjects: [{
kind: "ServiceAccount",
name: pulumi_kubernetes_operatorServiceAccount.metadata.name,
}],
roleRef: {
kind: "Role",
name: pulumi_kubernetes_operatorRole.metadata.name,
apiGroup: "rbac.authorization.k8s.io",
},
});
This simple refactoring is just the tip of the iceberg. At this point, you could:
- wrap these resources in a class to abstract away the low-level details
- add configuration values to the stack to make it easy to tweak certain values
- or even add custom logic for Pulumi to run after a particular resource is created
Pulumi gives you Kubernetes superpowers, and you decide how to use them!
Learn More
If you’d like to learn about Pulumi and how to manage your infrastructure and Kubernetes through code, get started today. Pulumi is open source and free to use.
For further examples on how to use Pulumi to create Kubernetes clusters, or deploy workloads to a cluster, check out the rest of the Kubernetes tutorials.
As always, you can check out our code on GitHub, follow us on Twitter, subscribe to our YouTube channel, or join our Community Slack channel if you have any questions, need support, or just want to say hello.