diff --git a/content/tutorials/kubernetes-strimzi-kafka/img.png b/content/tutorials/kubernetes-strimzi-kafka/img.png new file mode 100644 index 000000000000..86586d8bebcb Binary files /dev/null and b/content/tutorials/kubernetes-strimzi-kafka/img.png differ diff --git a/content/tutorials/kubernetes-strimzi-kafka/img_1.png b/content/tutorials/kubernetes-strimzi-kafka/img_1.png new file mode 100644 index 000000000000..0dc10f6c6907 Binary files /dev/null and b/content/tutorials/kubernetes-strimzi-kafka/img_1.png differ diff --git a/content/tutorials/kubernetes-strimzi-kafka/index.md b/content/tutorials/kubernetes-strimzi-kafka/index.md new file mode 100644 index 000000000000..9e0161873273 --- /dev/null +++ b/content/tutorials/kubernetes-strimzi-kafka/index.md @@ -0,0 +1,507 @@ +--- +title_tag: Deploying Kafka on Kubernetes using Strimzi and Pulumi +allow_long_title: true +title: Deploying Kafka on Kubernetes using Strimzi and Pulumi +layout: single +description: | + Learn how to deploy a Kafka cluster on Kubernetes using Strimzi and Pulumi. +meta_desc: Learn how to deploy a Kafka cluster on Kubernetes using Strimzi and Pulumi. +meta_image: meta.png +weight: 999 +summary: | + In this guide, we will use [Pulumi](https://www.pulumi.com/) to provision the [Strimzi Kafka Operator](https://strimzi.io/) on a Kubernetes cluster. We’ll then create a Kafka cluster using Custom Resources provided by Strimzi. +youll_learn: +- How to use Pulumi to manage your Kubernetes resources +- How to use Strimzi to deploy a Kafka cluster on Kubernetes +estimated_time: 10 +collections_weight: 2 +prereqs: +- "A [Pulumi Cloud account](https://app.pulumi.com/signup) and [access token](/docs/pulumi-cloud/accounts/#access-tokens)" +- "The [Pulumi CLI](/docs/install/)" +- "A Kubernetes cluster (for example, [kind](https://kind.sigs.k8s.io/))" +- "[kubectl](https://kubernetes.io/releases/download/#kubectl)" +- "[helm](https://helm.sh/docs/intro/install/)" +collections: +- kubernetes +--- + +## What is Strimzi? + +[Strimzi](https://strimzi.io/) is an open-source project that provides a way to run an Apache Kafka cluster on Kubernetes. It provides operators and custom resources to deploy and manage Kafka clusters on Kubernetes. + +It is a CNCF project and is widely used in the Kubernetes community to deploy Kafka clusters as it makes it very easy handle the lifecycle of Kafka clusters. Before Strimzi, deploying Kafka on Kubernetes was a complex task with many manual steps. From creating topics to managing brokers, everything was a manual task. + +Strimzi provides the following operators for managing a Kafka cluster running within a Kubernetes cluster. + +* **Cluster Operator**: Deploys and manages Apache Kafka clusters, Kafka Connect, Kafka MirrorMaker, Kafka Bridge, Kafka Exporter, Cruise Control, and the Entity Operator + +* **Entity Operator**: Comprises the Topic Operator and User Operator + +* **Topic Operator**: Manages Kafka topics + +* **User Operator**: Manages Kafka users + +The Cluster Operator can deploy the Topic Operator and User Operator as part of an Entity Operator configuration at the same time as a Kafka cluster. + +![img_1.png](img_1.png) + +## Deploying the Strimzi Kafka Operator + +In this tutorial, we will two different ways to deploy a Kafka cluster on Kubernetes using Strimzi. The first way is to use Helm to deploy the Strimzi Kafka Operator and then create a Kafka cluster using the Strimzi Kafka Custom Resource Definition (CRD). + +The second way is to use Pulumi to deploy the Strimzi Kafka Operator and create a Kafka cluster using the Strimzi Kafka Custom Resource Definition (CRD). + +### Deploy using Helm + +To deploy Kafka on Kubernetes using Strimzi, we need to install the Strimzi Kafka Operator first. + +```bash +helm upgrade -i strimzi-cluster-operator oci://quay.io/strimzi-helm/strimzi-kafka-operator --namespace strimzi --create-namespace --set watchAnyNamespace="true" +``` + +Now check that the Strimzi Cluster Operator is running: + +```bash +kubectl get pods -n strimzi +``` + +You should see the Strimzi Cluster Operator pod running. + +```bash +NAME READY STATUS RESTARTS AGE +strimzi-cluster-operator-b8f99bbc4-68btq 1/1 Running 0 17m +``` + +#### Deploy your first Kafka cluster + +First we deploy our Kafka `NodePool` with ephemeral storage: + +```yaml +apiVersion: kafka.strimzi.io/v1beta2 +kind: KafkaNodePool +metadata: + name: my-kafka-node-pool + labels: + strimzi.io/cluster: my-cluster +spec: + replicas: 1 + roles: + - controller + - broker + storage: + type: ephemeral +``` + +Then we deploy our `Kafka` Cluster, with `KRaft` enabled: + +#### What is KRaft? + +In order to overcome the limitations related to the ZooKeeper usage, the Kafka community came up with the idea of using Kafka itself to store metadata and use an event-driven pattern to make updates across the nodes. The work started with KIP-500 in late 2019 with the introduction of a built-in consensus protocol based on Raft. That was named Kafka Raft (**KRaft**) + +KRaft is an event-based implementation of the Raft protocol with a quorum controller maintaining an event log and a single-partition topic named __cluster_metadata to store the metadata. Unlike the other topics, this is special because records are written to disk synchronously, which is required by the Raft algorithm for correctness. + +It works in a leader-follower mode, where the leader writes events into the metadata topic which is then replicated to the follower controllers by using the KRaft replication algorithm. The leader of that single-partition topic is actually the controller node of the Kafka cluster. The metadata changes propagation has the benefit of being event-driven via replication instead of using RPCs. The metadata management is part of Kafka with the usage of a new quorum controller service which uses an event-sources storage model. The KRaft protocol is used to ensure that metadata are fully replicated across the quorum. + +![img.png](img.png) + +Here is the Kafka cluster configuration: + +```yaml +apiVersion: kafka.strimzi.io/v1beta2 +kind: Kafka +metadata: + name: my-cluster + annotations: + strimzi.io/kraft: enabled + strimzi.io/node-pools: enabled +spec: + kafka: + replicas: 1 + version: 3.8.0 + storage: + type: ephemeral + metadataVersion: 3.8-IV0 + listeners: + - name: plain + port: 9092 + type: internal + tls: false + - name: tls + port: 9093 + type: internal + tls: true + config: + offsets.topic.replication.factor: 1 + transaction.state.log.replication.factor: 1 + transaction.state.log.min.isr: 1 + default.replication.factor: 1 + min.insync.replicas: 1 + entityOperator: + topicOperator: {} + userOperator: {} +``` + +After a few seconds, you should see the Kafka cluster running: + +```bash +kubectl get pods -n default +``` + +You should see the Kafka cluster pods running: + +```bash +NAME READY STATUS RESTARTS AGE +my-cluster-entity-operator-6fbb78c774-hk2nn 2/2 Running 0 43s +my-cluster-my-kafka-node-pool-0 1/1 Running 0 67s +``` + +#### Deploy a Kafka Topic + +Now we deploy a Kafka topic: + +```yaml +apiVersion: kafka.strimzi.io/v1beta2 +kind: KafkaTopic +metadata: + name: my-topic + labels: + strimzi.io/cluster: my-cluster +spec: + partitions: 5 + replicas: 1 + config: + retention.ms: 7200000 + segment.bytes: 1073741824 +``` + +#### Deploy a Kafka User (Optional) + +We can also deploy a Kafka user: + +```yaml +apiVersion: kafka.strimzi.io/v1beta2 +kind: KafkaUser +metadata: + name: tutorial-user + labels: + strimzi.io/cluster: my-cluster +spec: + authentication: + type: scram-sha-512 + authorization: + type: simple + acls: + - resource: + type: topic + name: "my-topic" + patternType: literal + operation: Write + host: "*" +``` + +To publish your first message to the Kafka topic, jump to the [bottom of this page](#publish-your-first-message-to-the-kafka-topic). + +## Deploy using Pulumi + +In this section, we will use Pulumi to deploy the Strimzi Kafka Operator and create a Kafka cluster using Pulumi [CustomResource](/registry/packages/kubernetes/api-docs/apiextensions/customresource/) + +### Select your Pulumi supported language + +Initialize a new Pulumi project in an empty directory. Choose your favorite [Pulumi supported language](/docs/iac/languages-sdks/). + +```bash +mkdir pulumi-strimzi +cd pulumi-strimzi +# Choose your favorite Pulumi supported language +pulumi new kubernetes- +``` + +This will create a new Pulumi project with the necessary files to deploy resources on Kubernetes. As we are using pre-built templates, we don’t need to write the boilerplate code to bootstrap the project. + +### Install Strimzi + +Similar to the Helm installation, we will install the Strimzi Kafka Operator using the [Pulumi Kubernetes provider](/registry/packages/kubernetes/). + +Replace the content of created Pulumi program with the following code: + +{{< chooser language "typescript,python,go,csharp" />}} + +{{% choosable language typescript %}} + +```typescript +{{< example-program-snippet path="strimzi-kafka" language="typescript" from="1" to="11" >}} +``` + +{{% /choosable %}} + +{{% choosable language python %}} + +```python +{{< example-program-snippet path="strimzi-kafka" language="python" from="1" to="17" >}} +``` + +{{% /choosable %}} + +{{% choosable language go %}} + +```go +{{< example-program-snippet path="strimzi-kafka" language="go" from="1" to="25" >}} +{{< example-program-snippet path="strimzi-kafka" language="go" from="165" to="168" >}} +``` + +{{% /choosable %}} + +{{% choosable language csharp %}} + +```csharp +{{< example-program-snippet path="strimzi-kafka" language="csharp" from="1" to="73" >}} +{{< example-program-snippet path="strimzi-kafka" language="csharp" from="184" to="189" >}} +``` + +{{% /choosable %}} + +### Create a Kafka Cluster + +Once the Strimzi operator is installed, we can create a Kafka cluster by defining a Kafka Custom Resource. Strimzi provides a CRD that we will rely on. + +Add this after your Helm chart code: + +{{< chooser language "typescript,python,go,csharp" />}} + +{{% choosable language typescript %}} + +```typescript +{{< example-program-snippet path="strimzi-kafka" language="typescript" from="14" to="72" >}} +``` + +{{% /choosable %}} + +{{% choosable language python %}} + +```python +{{< example-program-snippet path="strimzi-kafka" language="python" from="18" to="78" >}} +``` + +{{% /choosable %}} + +{{% choosable language go %}} + +```go +{{< example-program-snippet path="strimzi-kafka" language="go" from="1" to="12" >}} +{{< example-program-snippet path="strimzi-kafka" language="go" from="27" to="103" >}} +{{< example-program-snippet path="strimzi-kafka" language="go" from="165" to="168" >}} +``` + +{{% /choosable %}} + +{{% choosable language csharp %}} + +```csharp +{{< example-program-snippet path="strimzi-kafka" language="csharp" from="1" to="62" >}} +{{< example-program-snippet path="strimzi-kafka" language="csharp" from="74" to="138" >}} +{{< example-program-snippet path="strimzi-kafka" language="csharp" from="184" to="189" >}} +``` + +{{% /choosable %}} + +Background: + +* We create a dedicated namespace called `kafka` (optional, but often recommended). +* We define a `Kafka` custom resource that: +* Uses `apiVersion: kafka.strimzi.io/v1beta2`. +* Creates a 3-node Kafka broker +* Sets up both plaintext (plain) and TLS listeners (tls). +* Includes the Entity Operator, which manages users and topics. + +### Creating a Kafka Topic + +Strimzi provides a `KafkaTopic` CRD to manage Kafka topics. Here are the code snippets to create a Kafka topic: + +{{< chooser language "typescript,python,go,csharp" />}} + +{{% choosable language typescript %}} + +```typescript +{{< example-program-snippet path="strimzi-kafka" language="typescript" from="73" to="95" >}} +``` + +{{% /choosable %}} + +{{% choosable language python %}} + +```python +{{< example-program-snippet path="strimzi-kafka" language="python" from="79" to="101" >}} +``` + +{{% /choosable %}} + +{{% choosable language go %}} + +```go +{{< example-program-snippet path="strimzi-kafka" language="go" from="1" to="12" >}} +{{< example-program-snippet path="strimzi-kafka" language="go" from="104" to="128" >}} +{{< example-program-snippet path="strimzi-kafka" language="go" from="165" to="168" >}} +``` + +{{% /choosable %}} + +{{% choosable language csharp %}} + +```csharp +{{< example-program-snippet path="strimzi-kafka" language="csharp" from="1" to="62" >}} +{{< example-program-snippet path="strimzi-kafka" language="csharp" from="140" to="155" >}} +{{< example-program-snippet path="strimzi-kafka" language="csharp" from="184" to="189" >}} +``` + +{{% /choosable %}} + +### Creating a Kafka User (Optional) + +Strimzi also provides a `KafkaUser` CRD to manage Kafka users. + +{{< chooser language "typescript,python,go,csharp" />}} + +{{% choosable language typescript %}} + +```typescript +{{< example-program-snippet path="strimzi-kafka" language="typescript" from="96" to="129" >}} +``` + +{{% /choosable %}} + +{{% choosable language python %}} + +```python +{{< example-program-snippet path="strimzi-kafka" language="python" from="102" to="135" >}} +``` + +{{% /choosable %}} + +{{% choosable language go %}} + +```go +{{< example-program-snippet path="strimzi-kafka" language="go" from="1" to="12" >}} +{{< example-program-snippet path="strimzi-kafka" language="go" from="129" to="164" >}} +{{< example-program-snippet path="strimzi-kafka" language="go" from="165" to="168" >}} +``` + +{{% /choosable %}} + +{{% choosable language csharp %}} + +```csharp +{{< example-program-snippet path="strimzi-kafka" language="csharp" from="1" to="62" >}} +{{< example-program-snippet path="strimzi-kafka" language="csharp" from="157" to="183" >}} +{{< example-program-snippet path="strimzi-kafka" language="csharp" from="184" to="189" >}} +``` + +{{% /choosable %}} + +### Deploying the whole stack + +Now run the `pulumi up` command to preview and deploy the resources you’ve just defined in your project. + +```bash +pulumi up +Please choose a stack, or create a new one: +Please enter your desired stack name. +To create a stack in an organization, use the format / (e.g. `acmecorp/dev`): dev +Please enter your desired stack name. +Created stack 'dev' +Previewing update (dev) + +View in Browser (Ctrl+O): https://app.pulumi.com/dirien/strimzi-kafka-go/dev/previews/89f51ac3-21e7-4118-934a-9f8568f3ba4f + + Type Name Plan + + pulumi:pulumi:Stack strimzi-kafka-go-dev create + + ├─ kubernetes:helm.sh/v3:Release strimzi-kafka-operator create + + ├─ kubernetes:kafka.strimzi.io/v1beta2:KafkaTopic kafkaTopic create + + ├─ kubernetes:kafka.strimzi.io/v1beta2:KafkaNodePool kafkaNodePool create + + ├─ kubernetes:kafka.strimzi.io/v1beta2:Kafka kafkaCluster create + + └─ kubernetes:kafka.strimzi.io/v1beta2:KafkaUser kafkaUser create + +Resources: + + 6 to create + +Do you want to perform this update? yes +Updating (dev) + +View in Browser (Ctrl+O): https://app.pulumi.com/dirien/strimzi-kafka-go/dev/updates/7 + + Type Name Status + + pulumi:pulumi:Stack strimzi-kafka-go-dev created (41s) + + ├─ kubernetes:helm.sh/v3:Release strimzi-kafka-operator created (34s) + + ├─ kubernetes:kafka.strimzi.io/v1beta2:Kafka kafkaCluster created (0.26s) + + ├─ kubernetes:kafka.strimzi.io/v1beta2:KafkaNodePool kafkaNodePool created (0.50s) + + ├─ kubernetes:kafka.strimzi.io/v1beta2:KafkaTopic kafkaTopic created (0.74s) + + └─ kubernetes:kafka.strimzi.io/v1beta2:KafkaUser kafkaUser created (0.99s) + +Resources: + + 6 created + +Duration: 48s +``` + +What happens now is that Pulumi will: + +* Install the Strimzi Kafka Operator via Helm. +* Create a namespace called kafka. +* Create a Kafka cluster. +* Optionally create a Kafka user. + +After confirming, Pulumi will provision your Kafka stack. You can then verify the resources with kubectl: + +```bash +kubectl get pods -n strimzi +kubectl get kafka +kubectl get kafkatopic +kubectl get kafkauser +``` + +## Publish your first message to the Kafka topic + +After all the setup, let's create some messages in our Kafka cluster: + +```bash +kubectl run kafka-producer -ti --image=quay.io/strimzi/kafka:0.24.0-kafka-2.8.0 --rm=true --restart=Never -- bin/kafka-console-producer.sh --broker-list my-cluster-kafka-bootstrap:9092 --topic my-topic +``` + +Now you can write some messages to the topic. + +```bash +If you don't see a command prompt, try pressing enter. +>Hello World! +>Hello Strimzi! +``` + +To consume the messages, run the following command: + +```bash +kubectl run kafka-consumer -ti --image=quay.io/strimzi/kafka:0.24.0-kafka-2.8.0 --rm=true --restart=Never -- bin/kafka-console-consumer.sh --bootstrap-server my-cluster-kafka-bootstrap:9092 --topic my-topic --from-beginning +``` + +You should see the messages you just created. + +```bash +If you don't see a command prompt, try pressing enter. + +Hello Strimzi! +Hello World! +``` + +## Housekeeping + +Before moving on, tear down the resources that are part of your stack to avoid incurring any charges. + +1. 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. +2. 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 learned how to install Helm on Kubernetes using the Kubernetes provider from Pulumi and the `Release` resource. + +- Learn more about Pulumi and Kubernetes in the [Kubernetes documentation](/docs/iac/clouds/kubernetes/). +- Learn more about the `Release` resource in the [Pulumi Kubernetes API documentation](/registry/packages/kubernetes/api-docs/helm/v3/release/). +- Try the out the `Chart` [tutorial](/tutorials/kubernetes-helm-part-two) to learn how to install Helm charts on Kubernetes using the `Chart` resource. +- Or give the tutorial about [Creating Resources on Kubernetes](/tutorials/creating-resources-kubernetes/) a try. +- Read more about [Why Every Platform Engineer Should Care About Kubernetes Operators](why-every-platform-engineer-should-care-about-kubernetes-operators/) diff --git a/content/tutorials/kubernetes-strimzi-kafka/meta.png b/content/tutorials/kubernetes-strimzi-kafka/meta.png new file mode 100644 index 000000000000..2c873bba532c Binary files /dev/null and b/content/tutorials/kubernetes-strimzi-kafka/meta.png differ diff --git a/static/programs/strimzi-kafka-csharp/Program.cs b/static/programs/strimzi-kafka-csharp/Program.cs new file mode 100644 index 000000000000..6d7828ac3e1f --- /dev/null +++ b/static/programs/strimzi-kafka-csharp/Program.cs @@ -0,0 +1,188 @@ +using Pulumi; +using Pulumi.Kubernetes.Helm.V3; +using Pulumi.Kubernetes.ApiExtensions; +using Pulumi.Kubernetes.Types.Inputs.Helm.V3; +using Pulumi.Kubernetes.Types.Inputs.Meta.V1; +using System.Threading.Tasks; + +class KafKaNodePoolArgs : CustomResourceArgs { + public KafKaNodePoolArgs() : base("kafka.strimzi.io/v1beta2", "KafkaNodePool") {} + [Input("spec")] + public Input Spec { get; set; } +} + +class KafakNodePoolSpecArgs : ResourceArgs { + public Input Replicas { get; set; } + public InputList Roles { get; set; } + public InputMap Storage { get; set; } +} + +class KafkaClusterArgs : CustomResourceArgs { + public KafkaClusterArgs() : base("kafka.strimzi.io/v1beta2", "Kafka") {} + [Input("spec")] + public Input Spec { get; set; } +} + +class KafakaClusterSpecArgs : ResourceArgs { + public InputMap Kafka { get; set; } + public InputMap EntityOperator { get; set; } +} + +class KafkaUserArgs : CustomResourceArgs { + public KafkaUserArgs() : base("kafka.strimzi.io/v1beta2", "KafkaUser") {} + [Input("spec")] + public Input Spec { get; set; } +} + +class KafkaUserSpecArgs : ResourceArgs { + public InputMap Authentication { get; set; } + public InputMap Authorization { get; set; } +} + +class KafkaTopicArgs : CustomResourceArgs { + public KafkaTopicArgs() : base("kafka.strimzi.io/v1beta2", "KafkaTopic") {} + [Input("spec")] + public Input Spec { get; set; } +} + +class KafkaTopicSpecArgs : ResourceArgs { + public Input Partitions { get; set; } + public Input Replicas { get; set; } + public InputMap Config { get; set; } +} + +class ListenerArgs : ResourceArgs { + public Input Name { get; set; } + public Input Port { get; set; } + public Input Type { get; set; } + public Input Tls { get; set; } +} + +class MyStack : Stack { + public MyStack() { + // Deploy Strimzi Kafka operator using Helm chart + var strimzi = new Release("strimzi-kafka-operator", new ReleaseArgs { + Chart = "oci://quay.io/strimzi-helm/strimzi-kafka-operator", + Namespace = "strimzi", + CreateNamespace = true, + Values = + new InputMap { + { "watchAnyNamespace", true }, + }, + }); + + // Deploy Kafka Node Pool + var kafkaNodePool = new Pulumi.Kubernetes.ApiExtensions.CustomResource( + "kafkaNodePool", + new KafKaNodePoolArgs { + Metadata = new ObjectMetaArgs { Name = "my-kafka-nodepool", + Labels = { { "strimzi.io/cluster", + "my-cluster" } } }, + Spec = + new KafakNodePoolSpecArgs { + Replicas = "1", + Roles = new InputList { "controller", "broker" }, + Storage = new InputMap { { "type", "ephemeral" } } + }, + }, + new CustomResourceOptions { DependsOn = { strimzi } }); + + // Deploy Kafka Cluster + var kafkaCluster = new Pulumi.Kubernetes.ApiExtensions.CustomResource( + "kafkaCluster", + new KafkaClusterArgs { + Metadata = + new ObjectMetaArgs { Name = "my-cluster", + Annotations = { { "strimzi.io/kraft", "enabled" }, + { "strimzi.io/node-pools", + "enabled" } } }, + Spec = + new KafakaClusterSpecArgs { + Kafka = + new InputMap { + { "version", "3.8.0" }, + {"replicas", 1 }, + { "storage", + new InputMap { { "type", "ephemeral" } } }, + { "listeners", + new InputList { new ListenerArgs() { + Name = "plain", + Port = 9092, + Type = "internal", + Tls = false, + }, + new ListenerArgs() { + Name = "tls", + Port = 9093, + Type = "internal", + Tls = true, + } } }, + { "metadataVersion", "3.8-IV0" }, + { "config", + new InputMap { + { "offsets.topic.replication.factor", "1" }, + { "transaction.state.log.replication.factor", "1" }, + { "transaction.state.log.min.isr", "1" }, + { "default.replication.factor", "1" }, + { "min.insync.replicas", "1" }, + } } + }, + EntityOperator = + new InputMap { + { "topicOperator", new InputMap() }, + { "userOperator", new InputMap() } + } + }, + }, + new CustomResourceOptions { DependsOn = { strimzi } }); + + // Deploy Kafka Topic + var kafkaTopic = new Pulumi.Kubernetes.ApiExtensions.CustomResource( + "kafkaTopic", + new KafkaTopicArgs { + Metadata = new ObjectMetaArgs { Name = "my-topic", + Labels = { { "strimzi.io/cluster", + "my-cluster" } } }, + Spec = + new KafkaTopicSpecArgs { + Partitions = 5, Replicas = 1, + Config = + new InputMap { { "retention.ms", "7200000" }, + { "segment.bytes", "1073741824" } } + }, + }, + new CustomResourceOptions { DependsOn = { kafkaCluster } }); + + // Deploy Kafka User + var kafkaUser = + new Pulumi.Kubernetes.ApiExtensions.CustomResource( + "kafkaUser", + new KafkaUserArgs { + Metadata = new ObjectMetaArgs { Name = "my-kafka-user", + Labels = { { "strimzi.io/cluster", + "my-cluster" } } }, + Spec = + new KafkaUserSpecArgs { + Authentication = + new InputMap { { "type", "scram-sha-512" } }, + Authorization = + new InputMap { + { "type", "simple" }, + { "acls", new[] { new InputMap { + { "resource", + new InputMap { { "type", "topic" }, + { "name", "my-topic" }, + { "patternType", + "literal" } } }, + { "operation", "Read" }, + { "host", "*" } + } } } + } + }, + }, + new CustomResourceOptions { DependsOn = { kafkaCluster } }); + } +} +class Program { + static Task Main() => Deployment.RunAsync(); +} diff --git a/static/programs/strimzi-kafka-csharp/Pulumi.yaml b/static/programs/strimzi-kafka-csharp/Pulumi.yaml new file mode 100644 index 000000000000..3a53b6c6c4ed --- /dev/null +++ b/static/programs/strimzi-kafka-csharp/Pulumi.yaml @@ -0,0 +1,7 @@ +name: strimzi-kafka-csharp +description: Example on how to deploy Strimzi Kafka +runtime: dotnet +config: + pulumi:tags: + value: + pulumi:template: kubernetes-csharp diff --git a/static/programs/strimzi-kafka-csharp/strimzi-kafka-csharp.csproj b/static/programs/strimzi-kafka-csharp/strimzi-kafka-csharp.csproj new file mode 100644 index 000000000000..9d3c12566037 --- /dev/null +++ b/static/programs/strimzi-kafka-csharp/strimzi-kafka-csharp.csproj @@ -0,0 +1,14 @@ + + + + Exe + net8.0 + enable + + + + + + + + diff --git a/static/programs/strimzi-kafka-go/Pulumi.yaml b/static/programs/strimzi-kafka-go/Pulumi.yaml new file mode 100644 index 000000000000..38fbf3c6658e --- /dev/null +++ b/static/programs/strimzi-kafka-go/Pulumi.yaml @@ -0,0 +1,7 @@ +name: strimzi-kafka-go +description: Example on how to deploy Strimzi Kafka +runtime: go +config: + pulumi:tags: + value: + pulumi:template: kubernetes-go diff --git a/static/programs/strimzi-kafka-go/go.mod b/static/programs/strimzi-kafka-go/go.mod new file mode 100644 index 000000000000..b2d33065643b --- /dev/null +++ b/static/programs/strimzi-kafka-go/go.mod @@ -0,0 +1,93 @@ +module strimzi-kafka-go + +go 1.21 + +toolchain go1.23.4 + +require ( + github.com/pulumi/pulumi-kubernetes/sdk/v4 v4.12.0 + github.com/pulumi/pulumi/sdk/v3 v3.117.0 +) + +require ( + dario.cat/mergo v1.0.0 // indirect + github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/ProtonMail/go-crypto v1.0.0 // indirect + github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect + github.com/agext/levenshtein v1.2.3 // indirect + github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect + github.com/atotto/clipboard v0.1.4 // indirect + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/blang/semver v3.5.1+incompatible // indirect + github.com/charmbracelet/bubbles v0.16.1 // indirect + github.com/charmbracelet/bubbletea v0.24.2 // indirect + github.com/charmbracelet/lipgloss v0.7.1 // indirect + github.com/cheggaaa/pb v1.0.29 // indirect + github.com/cloudflare/circl v1.3.7 // indirect + github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect + github.com/cyphar/filepath-securejoin v0.2.4 // indirect + github.com/djherbis/times v1.5.0 // indirect + github.com/emirpasic/gods v1.18.1 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-git/go-billy/v5 v5.5.0 // indirect + github.com/go-git/go-git/v5 v5.12.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/glog v1.2.0 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/hcl/v2 v2.17.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/kevinburke/ssh_config v1.2.0 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-localereader v0.0.1 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/mitchellh/go-ps v1.0.0 // indirect + github.com/mitchellh/go-wordwrap v1.0.1 // indirect + github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect + github.com/muesli/cancelreader v0.2.2 // indirect + github.com/muesli/reflow v0.3.0 // indirect + github.com/muesli/termenv v0.15.2 // indirect + github.com/opentracing/basictracer-go v1.1.0 // indirect + github.com/opentracing/opentracing-go v1.2.0 // indirect + github.com/pgavlin/fx v0.1.6 // indirect + github.com/pjbgf/sha1cd v0.3.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pkg/term v1.1.0 // indirect + github.com/pulumi/appdash v0.0.0-20231130102222-75f619a67231 // indirect + github.com/pulumi/esc v0.6.2 // indirect + github.com/rivo/uniseg v0.4.4 // indirect + github.com/rogpeppe/go-internal v1.11.0 // indirect + github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 // indirect + github.com/santhosh-tekuri/jsonschema/v5 v5.0.0 // indirect + github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect + github.com/skeema/knownhosts v1.2.2 // indirect + github.com/spf13/cobra v1.8.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/texttheater/golang-levenshtein v1.0.1 // indirect + github.com/tweekmonster/luser v0.0.0-20161003172636-3fa38070dbd7 // indirect + github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect + github.com/uber/jaeger-lib v2.4.1+incompatible // indirect + github.com/xanzy/ssh-agent v0.3.3 // indirect + github.com/zclconf/go-cty v1.13.2 // indirect + go.uber.org/atomic v1.9.0 // indirect + golang.org/x/crypto v0.23.0 // indirect + golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect + golang.org/x/mod v0.14.0 // indirect + golang.org/x/net v0.25.0 // indirect + golang.org/x/sync v0.6.0 // indirect + golang.org/x/sys v0.20.0 // indirect + golang.org/x/term v0.20.0 // indirect + golang.org/x/text v0.15.0 // indirect + golang.org/x/tools v0.17.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240311173647-c811ad7063a7 // indirect + google.golang.org/grpc v1.63.2 // indirect + google.golang.org/protobuf v1.33.0 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + lukechampine.com/frand v1.4.2 // indirect +) diff --git a/static/programs/strimzi-kafka-go/main.go b/static/programs/strimzi-kafka-go/main.go new file mode 100644 index 000000000000..e9113ea1638f --- /dev/null +++ b/static/programs/strimzi-kafka-go/main.go @@ -0,0 +1,167 @@ +package main + +import ( + k8s "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes" + apiextensions "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/apiextensions" + helm "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/helm/v3" + meta "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 { + // Deploy Strimzi Kafka operator using Helm chart + strimzi, err := helm.NewRelease(ctx, "strimzi-kafka-operator", &helm.ReleaseArgs{ + Chart: pulumi.String("oci://quay.io/strimzi-helm/strimzi-kafka-operator"), + Namespace: pulumi.String("strimzi"), + CreateNamespace: pulumi.Bool(true), + Values: pulumi.Map{ + "watchAnyNamespace": pulumi.Bool(true), + }, + }) + if err != nil { + return err + } + + // Deploy Kafka Node Pool + _, err = apiextensions.NewCustomResource(ctx, "kafkaNodePool", &apiextensions.CustomResourceArgs{ + ApiVersion: pulumi.String("kafka.strimzi.io/v1beta2"), + Kind: pulumi.String("KafkaNodePool"), + Metadata: &meta.ObjectMetaArgs{ + Name: pulumi.String("my-kafka-nodepool"), + Labels: pulumi.StringMap{ + "strimzi.io/cluster": pulumi.String("my-cluster"), + }, + }, + OtherFields: k8s.UntypedArgs{ + "spec": pulumi.Map{ + "replicas": pulumi.Int(1), + "roles": pulumi.StringArray{ + pulumi.String("controller"), + pulumi.String("broker"), + }, + "storage": pulumi.Map{ + "type": pulumi.String("ephemeral"), + }, + }, + }, + }, pulumi.DependsOn([]pulumi.Resource{strimzi})) + if err != nil { + return err + } + + // Deploy Kafka Cluster + _, err = apiextensions.NewCustomResource(ctx, "kafkaCluster", &apiextensions.CustomResourceArgs{ + ApiVersion: pulumi.String("kafka.strimzi.io/v1beta2"), + Kind: pulumi.String("Kafka"), + Metadata: &meta.ObjectMetaArgs{ + Name: pulumi.String("my-cluster"), + Annotations: pulumi.StringMap{ + "strimzi.io/kraft": pulumi.String("enabled"), + "strimzi.io/node-pools": pulumi.String("enabled"), + }, + }, + OtherFields: k8s.UntypedArgs{ + "spec": pulumi.Map{ + "kafka": pulumi.Map{ + "replicas": pulumi.Int(1), + "version": pulumi.String("3.8.0"), + "storage": pulumi.Map{"type": pulumi.String("ephemeral")}, + "metadataVersion": pulumi.String("3.8-IV0"), + "listeners": pulumi.Array{ + pulumi.Map{ + "name": pulumi.String("plain"), + "port": pulumi.Int(9092), + "type": pulumi.String("internal"), + "tls": pulumi.Bool(false), + }, + pulumi.Map{ + "name": pulumi.String("tls"), + "port": pulumi.Int(9093), + "type": pulumi.String("internal"), + "tls": pulumi.Bool(true), + }, + }, + "config": pulumi.StringMap{ + "offsets.topic.replication.factor": pulumi.String("1"), + "transaction.state.log.replication.factor": pulumi.String("1"), + "transaction.state.log.min.isr": pulumi.String("1"), + "default.replication.factor": pulumi.String("1"), + "min.insync.replicas": pulumi.String("1"), + }, + }, + "entityOperator": pulumi.Map{ + "topicOperator": pulumi.Map{}, + "userOperator": pulumi.Map{}, + }, + }, + }, + }, pulumi.DependsOn([]pulumi.Resource{strimzi})) + if err != nil { + return err + } + + // Deploy Kafka Topic + _, err = apiextensions.NewCustomResource(ctx, "kafkaTopic", &apiextensions.CustomResourceArgs{ + ApiVersion: pulumi.String("kafka.strimzi.io/v1beta2"), + Kind: pulumi.String("KafkaTopic"), + Metadata: &meta.ObjectMetaArgs{ + Name: pulumi.String("my-topic"), + Labels: pulumi.StringMap{ + "strimzi.io/cluster": pulumi.String("my-cluster"), + }, + }, + OtherFields: k8s.UntypedArgs{ + "spec": pulumi.Map{ + "partitions": pulumi.Int(5), + "replicas": pulumi.Int(1), + "config": pulumi.StringMap{ + "retention.ms": pulumi.String("7200000"), + "segment.bytes": pulumi.String("1073741824"), + }, + }, + }, + }, pulumi.DependsOn([]pulumi.Resource{strimzi})) + if err != nil { + return err + } + + // Deploy Kafka User + _, err = apiextensions.NewCustomResource(ctx, "kafkaUser", &apiextensions.CustomResourceArgs{ + ApiVersion: pulumi.String("kafka.strimzi.io/v1beta2"), + Kind: pulumi.String("KafkaUser"), + Metadata: &meta.ObjectMetaArgs{ + Name: pulumi.String("my-kafka-user"), + Labels: pulumi.StringMap{ + "strimzi.io/cluster": pulumi.String("my-cluster"), + }, + }, + OtherFields: k8s.UntypedArgs{ + "spec": pulumi.Map{ + "authentication": pulumi.Map{ + "type": pulumi.String("scram-sha-512"), + }, + "authorization": pulumi.Map{ + "type": pulumi.String("simple"), + "acls": pulumi.Array{ + pulumi.Map{ + "resource": pulumi.Map{ + "type": pulumi.String("topic"), + "name": pulumi.String("my-topic"), + "patternType": pulumi.String("literal"), + }, + "operation": pulumi.String("Read"), + "host": pulumi.String("*"), + }, + }, + }, + }, + }, + }, pulumi.DependsOn([]pulumi.Resource{strimzi})) + if err != nil { + return err + } + + return nil + }) +} diff --git a/static/programs/strimzi-kafka-python/Pulumi.yaml b/static/programs/strimzi-kafka-python/Pulumi.yaml new file mode 100644 index 000000000000..daf367a284c5 --- /dev/null +++ b/static/programs/strimzi-kafka-python/Pulumi.yaml @@ -0,0 +1,11 @@ +name: strimzi-kafka-python +description: Example on how to deploy Strimzi Kafka +runtime: + name: python + options: + toolchain: pip + virtualenv: venv +config: + pulumi:tags: + value: + pulumi:template: kubernetes-python diff --git a/static/programs/strimzi-kafka-python/__main__.py b/static/programs/strimzi-kafka-python/__main__.py new file mode 100644 index 000000000000..6e10dcd2445f --- /dev/null +++ b/static/programs/strimzi-kafka-python/__main__.py @@ -0,0 +1,134 @@ +import pulumi +import pulumi_kubernetes as k8s + +strimzi = k8s.helm.v3.Release( + "strimzi-kafka-operator", + k8s.helm.v3.ReleaseArgs( + chart="oci://quay.io/strimzi-helm/strimzi-kafka-operator", + namespace="strimzi", + create_namespace=True, + values={ + "watchAnyNamespace": True, # if you want the operator to watch all namespaces + }, + ), +) + +strimzi_name = pulumi.Output.strimzi_name(strimzi) + +kafka_node_pool = ( + k8s.apiextensions.CustomResource( + "kafkaNodePool", + api_version="kafka.strimzi.io/v1beta2", + kind="KafkaNodePool", + metadata=k8s.meta.v1.ObjectMetaArgs( + name="my-kafka-nodepool", + labels={ + "strimzi.io/cluster": "my-cluster", + }, + ), + spec={ + "replicas": 1, + "roles": ["controller", "broker"], + "storage": { + "type": "ephemeral", + }, + }, + opts=pulumi.ResourceOptions(depends_on=[strimzi]), + ), +) + +kafka_cluster = ( + k8s.apiextensions.CustomResource( + "kafkaCluster", + api_version="kafka.strimzi.io/v1beta2", + kind="Kafka", + metadata=k8s.meta.v1.ObjectMetaArgs( + name="my-cluster", + annotations={ + "strimzi.io/kraft": "enabled", + "strimzi.io/node-pools": "enabled", + }, + ), + spec={ + "kafka": { + "replicas": 1, + "version": "3.8.0", + "storage": {"type": "ephemeral"}, + "listeners": [ + {"name": "plain", "port": 9092, "type": "internal", "tls": False}, + {"name": "tls", "port": 9093, "type": "internal", "tls": True}, + ], + "metadataVersion": "3.8-IV0", + "config": { + "offsets.topic.replication.factor": 1, + "transaction.state.log.replication.factor": 1, + "transaction.state.log.min.isr": 1, + "default.replication.factor": 1, + "min.insync.replicas": 1, + }, + }, + "entityOperator": { + "topicOperator": {}, + "userOperator": {}, + }, + }, + opts=pulumi.ResourceOptions(depends_on=[strimzi]), + ), +) + +kafka_topic = ( + k8s.apiextensions.CustomResource( + "kafkaTopic", + api_version="kafka.strimzi.io/v1beta2", + kind="KafkaTopic", + metadata=k8s.meta.v1.ObjectMetaArgs( + name="my-topic", + labels={ + "strimzi.io/cluster": "my-cluster", + }, + ), + spec={ + "partitions": 5, + "replicas": 1, + "config": { + "retention.ms": 7200000, + "segment.bytes": 1073741824, + }, + }, + opts=pulumi.ResourceOptions(depends_on=[strimzi]), + ), +) + +kafka_user = ( + k8s.apiextensions.CustomResource( + "kafkaUser", + api_version="kafka.strimzi.io/v1beta2", + kind="KafkaUser", + metadata=k8s.meta.v1.ObjectMetaArgs( + name="my-kafka-user", + labels={ + "strimzi.io/cluster": "my-cluster", + }, + ), + spec={ + "authentication": { + "type": "scram-sha-512", + }, + "authorization": { + "type": "simple", + "acls": [ + { + "resource": { + "type": "topic", + "name": "my-topic", + "patternType": "literal", + }, + "operation": "Read", + "host": "*", + }, + ], + }, + }, + opts=pulumi.ResourceOptions(depends_on=[strimzi]), + ), +) diff --git a/static/programs/strimzi-kafka-python/requirements.txt b/static/programs/strimzi-kafka-python/requirements.txt new file mode 100644 index 000000000000..29d56d12959a --- /dev/null +++ b/static/programs/strimzi-kafka-python/requirements.txt @@ -0,0 +1,2 @@ +pulumi>=3.0.0,<4.0.0 +pulumi-kubernetes>=4.0.0,<5.0.0 diff --git a/static/programs/strimzi-kafka-typescript/Pulumi.yaml b/static/programs/strimzi-kafka-typescript/Pulumi.yaml new file mode 100644 index 000000000000..47f1f0bff333 --- /dev/null +++ b/static/programs/strimzi-kafka-typescript/Pulumi.yaml @@ -0,0 +1,10 @@ +name: strimzi-kafka-typescript +description: Example on how to deploy Strimzi Kafka +runtime: + name: nodejs + options: + packagemanager: npm +config: + pulumi:tags: + value: + pulumi:template: kubernetes-typescript diff --git a/static/programs/strimzi-kafka-typescript/index.ts b/static/programs/strimzi-kafka-typescript/index.ts new file mode 100644 index 000000000000..fe574223037d --- /dev/null +++ b/static/programs/strimzi-kafka-typescript/index.ts @@ -0,0 +1,128 @@ +import * as k8s from "@pulumi/kubernetes"; + +const strimzi = new k8s.helm.v3.Release("strimzi-kafka-operator", { + chart: "oci://quay.io/strimzi-helm/strimzi-kafka-operator", + values: { + watchAnyNamespace: true, // if you want the operator to watch all namespaces + }, + namespace: "strimzi", + createNamespace: true, +}); + +export const strimziName = strimzi.name; + +const kafkaNodePool = new k8s.apiextensions.CustomResource("kafkaNodePool", { + apiVersion: "kafka.strimzi.io/v1beta2", + kind: "KafkaNodePool", + metadata: { + name: "my-kafka-nodepool", + labels: { + "strimzi.io/cluster": "my-cluster", + }, + }, + spec: { + replicas: 1, + roles: ["controller", "broker"], + storage: { + type: "ephemeral", + }, + }, +}); + +const kafkaCluster = new k8s.apiextensions.CustomResource( + "kafkaCluster", + { + apiVersion: "kafka.strimzi.io/v1beta2", + kind: "Kafka", + metadata: { + name: "my-cluster", + annotations: { + "strimzi.io/kraft": "enabled", + "strimzi.io/node-pools": "enabled", + }, + }, + spec: { + kafka: { + replicas: 1, + version: "3.8.0", + storage: { + type: "ephemeral", + }, + listeners: [ + { name: "plain", port: 9092, type: "internal", tls: false }, + { name: "tls", port: 9093, type: "internal", tls: true }, + ], + metadataVersion: "3.8-IV0", + config: { + "offsets.topic.replication.factor": 1, + "transaction.state.log.replication.factor": 1, + "transaction.state.log.min.isr": 1, + "default.replication.factor": 1, + "min.insync.replicas": 1, + }, + }, + entityOperator: { + topicOperator: {}, + userOperator: {}, + }, + }, + }, + { dependsOn: [strimzi] }, +); + +const kafkaTopic = new k8s.apiextensions.CustomResource( + "kafkaTopic", + { + apiVersion: "kafka.strimzi.io/v1beta2", + kind: "KafkaTopic", + metadata: { + name: "my-topic", + labels: { + "strimzi.io/cluster": "my-cluster", + }, + }, + spec: { + partitions: 5, + replicas: 1, + config: { + "retention.ms": 7200000, + "segment.bytes": 1073741824, + }, + }, + }, + { dependsOn: [strimzi] }, +); + +const kafkaUser = new k8s.apiextensions.CustomResource( + "kafkaUser", + { + apiVersion: "kafka.strimzi.io/v1beta2", + kind: "KafkaUser", + metadata: { + name: "my-kafka-user", + labels: { + "strimzi.io/cluster": "my-cluster", + }, + }, + spec: { + authentication: { + type: "scram-sha-512", + }, + authorization: { + type: "simple", + acls: [ + { + resource: { + type: "topic", + name: "my-topic", + patternType: "literal", + }, + operation: "Read", + host: "*", + }, + ], + }, + }, + }, + { dependsOn: [strimzi] }, +); diff --git a/static/programs/strimzi-kafka-typescript/package.json b/static/programs/strimzi-kafka-typescript/package.json new file mode 100644 index 000000000000..877b61021e7a --- /dev/null +++ b/static/programs/strimzi-kafka-typescript/package.json @@ -0,0 +1,12 @@ +{ + "name": "strimzi-kafka-typescript", + "main": "index.ts", + "devDependencies": { + "@types/node": "^18", + "typescript": "^5.0.0" + }, + "dependencies": { + "@pulumi/kubernetes": "^4.0.0", + "@pulumi/pulumi": "^3.113.0" + } +} diff --git a/static/programs/strimzi-kafka-typescript/tsconfig.json b/static/programs/strimzi-kafka-typescript/tsconfig.json new file mode 100644 index 000000000000..381ae66dfbd5 --- /dev/null +++ b/static/programs/strimzi-kafka-typescript/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "strict": true, + "outDir": "bin", + "target": "es2020", + "module": "commonjs", + "moduleResolution": "node", + "sourceMap": true, + "experimentalDecorators": true, + "pretty": true, + "noFallthroughCasesInSwitch": true, + "noImplicitReturns": true, + "forceConsistentCasingInFileNames": true + }, + "files": ["index.ts"] +} diff --git a/static/programs/strimzi-kafka-yaml/Pulumi.yaml b/static/programs/strimzi-kafka-yaml/Pulumi.yaml new file mode 100644 index 000000000000..d382627c5149 --- /dev/null +++ b/static/programs/strimzi-kafka-yaml/Pulumi.yaml @@ -0,0 +1,27 @@ +name: strimzi-kafka-yaml +description: Example on how to deploy Strimzi Kafka +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 +outputs: + name: ${deployment.metadata.name} +config: + pulumi:tags: + value: + pulumi:template: kubernetes-yaml