diff --git a/kubernetes-cs-guestbook/Guestbook.csproj b/kubernetes-cs-guestbook/Guestbook.csproj new file mode 100644 index 000000000..2db3916df --- /dev/null +++ b/kubernetes-cs-guestbook/Guestbook.csproj @@ -0,0 +1,12 @@ + + + + Exe + netcoreapp3.0 + + + + + + + diff --git a/kubernetes-cs-guestbook/Program.cs b/kubernetes-cs-guestbook/Program.cs new file mode 100644 index 000000000..2b7a8d030 --- /dev/null +++ b/kubernetes-cs-guestbook/Program.cs @@ -0,0 +1,270 @@ +// Copyright 2016-2019, Pulumi Corporation. All rights reserved. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +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 Pulumi.Kubernetes.Types.Inputs.ApiExtensions.V1Beta1; + +class Program +{ + static Task Main(string[] args) + { + return Deployment.RunAsync(() => + { + // Minikube does not implement services of type `LoadBalancer`; require the user to + // specify if we're running on minikube, and if so, create only services of type + // ClusterIP. + var config = new Config(); + var isMiniKube = config.GetBoolean("isMiniKube") ?? false; + + // + // REDIS MASTER. + // + + var redisMasterLabels = new InputMap{ + { "app", "redis-master" }, + }; + + var redisMasterDeployment = new Pulumi.Kubernetes.Apps.V1.Deployment("redis-master", new DeploymentArgs + { + Spec = new DeploymentSpecArgs + { + Selector = new LabelSelectorArgs + { + MatchLabels = redisMasterLabels, + }, + Template = new PodTemplateSpecArgs + { + Metadata = new ObjectMetaArgs + { + Labels = redisMasterLabels, + }, + Spec = new PodSpecArgs + { + Containers = + { + new ContainerArgs + { + Name = "master", + Image = "k8s.gcr.io/redis:e2e", + Resources = new ResourceRequirementsArgs + { + Requests = + { + { "cpu", "100m" }, + { "memory", "100Mi" }, + }, + }, + Ports = + { + new ContainerPortArgs { ContainerPortValue = 6379 } + }, + }, + }, + }, + }, + }, + }); + + var redisMasterService = new Pulumi.Kubernetes.Core.V1.Service("redis-master", new ServiceArgs + { + Metadata = new ObjectMetaArgs + { + Labels = redisMasterDeployment.Metadata.Apply(metadata => metadata.Labels), + }, + Spec = new ServiceSpecArgs + { + Ports = + { + new ServicePortArgs + { + Port = 6379, + TargetPort = 6379, + }, + }, + Selector = redisMasterDeployment.Spec.Apply(spec => spec.Template.Metadata.Labels), + } + }); + + // + // REDIS REPLICA. + // + + var redisReplicaLabels = new InputMap{ + { "app", "redis-replica" }, + }; + + var redisReplicaDeployment = new Pulumi.Kubernetes.Apps.V1.Deployment("redis-replica", new DeploymentArgs + { + Spec = new DeploymentSpecArgs + { + Selector = new LabelSelectorArgs + { + MatchLabels = redisReplicaLabels, + }, + Template = new PodTemplateSpecArgs + { + Metadata = new ObjectMetaArgs + { + Labels = redisReplicaLabels, + }, + Spec = new PodSpecArgs + { + Containers = + { + new ContainerArgs + { + Name = "replica", + Image = "gcr.io/google_samples/gb-redisslave:v1", + Resources = new ResourceRequirementsArgs + { + Requests = + { + { "cpu", "100m" }, + { "memory", "100Mi" }, + }, + }, + // If your cluster config does not include a dns service, then to instead access an environment + // variable to find the master service's host, change `value: "dns"` to read `value: "env"`. + Env = + { + new EnvVarArgs + { + Name = "GET_HOSTS_FROM", + Value = "dns" + }, + }, + Ports = + { + new ContainerPortArgs { ContainerPortValue = 6379 } + }, + }, + }, + }, + }, + }, + }); + + var redisReplicaService = new Pulumi.Kubernetes.Core.V1.Service("redis-replica", new ServiceArgs + { + Metadata = new ObjectMetaArgs + { + Labels = redisReplicaDeployment.Metadata.Apply(metadata => metadata.Labels), + }, + Spec = new ServiceSpecArgs + { + Ports = + { + new ServicePortArgs + { + Port = 6379, + TargetPort = 6379, + }, + }, + Selector = redisReplicaDeployment.Spec.Apply(spec => spec.Template.Metadata.Labels), + } + }); + + // + // FRONTEND + // + + var frontendLabels = new InputMap{ + { "app", "frontend" }, + }; + + var frontendDeployment = new Pulumi.Kubernetes.Apps.V1.Deployment("frontend", new DeploymentArgs + { + Spec = new DeploymentSpecArgs + { + Selector = new LabelSelectorArgs + { + MatchLabels = frontendLabels, + }, + Replicas = 3, + Template = new PodTemplateSpecArgs + { + Metadata = new ObjectMetaArgs + { + Labels = frontendLabels, + }, + Spec = new PodSpecArgs + { + Containers = + { + new ContainerArgs + { + Name = "php-redis", + Image = "gcr.io/google-samples/gb-frontend:v4", + Resources = new ResourceRequirementsArgs + { + Requests = { + { "cpu", "100m" }, + { "memory", "100Mi" }, + }, + }, + // If your cluster config does not include a dns service, then to instead access an environment + // variable to find the master service's host, change `value: "dns"` to read `value: "env"`. + Env = + { + new EnvVarArgs + { + Name = "GET_HOSTS_FROM", + Value = "dns", /* Value = "env"*/ + }, + }, + Ports = + { + new ContainerPortArgs { ContainerPortValue = 80 } + }, + }, + }, + }, + }, + }, + }); + + var frontendService = new Pulumi.Kubernetes.Core.V1.Service("frontend", new ServiceArgs + { + Metadata = new ObjectMetaArgs + { + Labels = frontendDeployment.Metadata.Apply(metadata => metadata.Labels), + }, + Spec = new ServiceSpecArgs + { + Type = isMiniKube ? "ClusterIP" : "LoadBalancer", + Ports = + { + new ServicePortArgs + { + Port = 80, + TargetPort = 80, + }, + }, + Selector = frontendDeployment.Spec.Apply(spec => spec.Template.Metadata.Labels), + } + }); + + Output frontendIP; + if (isMiniKube) + { + frontendIP = frontendService.Spec.Apply(spec => spec.ClusterIP); + } + else + { + frontendIP = frontendService.Status.Apply(status => status.LoadBalancer.Ingress[0].Hostname); + } + + return new Dictionary{ + { "frontendIp", frontendIP }, + }; + + }); + } +} diff --git a/kubernetes-cs-guestbook/Pulumi.yaml b/kubernetes-cs-guestbook/Pulumi.yaml new file mode 100644 index 000000000..255bc7929 --- /dev/null +++ b/kubernetes-cs-guestbook/Pulumi.yaml @@ -0,0 +1,8 @@ +name: guestbook-csharp +runtime: dotnet +description: Kubernetes Guestbook example based on https://kubernetes.io/docs/tutorials/stateless-application/guestbook/ +template: + config: + isMinikube: + description: Whether you are deploying to minikube + default: true diff --git a/kubernetes-cs-guestbook/README.md b/kubernetes-cs-guestbook/README.md new file mode 100644 index 000000000..b7867720b --- /dev/null +++ b/kubernetes-cs-guestbook/README.md @@ -0,0 +1,66 @@ +[![Deploy](https://get.pulumi.com/new/button.svg)](https://app.pulumi.com/new) + +# Kubernetes Guestbook (Simple Variant) + +A version of the [Kubernetes Guestbook](https://kubernetes.io/docs/tutorials/stateless-application/guestbook/) +application using Pulumi. + +## Running the App + +Follow the steps in [Pulumi Installation](https://www.pulumi.com/docs/get-started/install/) and [Kubernetes Setup](https://www.pulumi.com/docs/intro/cloud-providers/kubernetes/setup/) to get Pulumi working with Kubernetes. + +Create a new stack: + +```sh +$ pulumi stack init +Enter a stack name: testbook +``` + +This example will attempt to expose the Guestbook application to the Internet with a `Service` of +type `LoadBalancer`. Since minikube does not support `LoadBalancer`, the Guestbook application +already knows to use type `ClusterIP` instead; all you need to do is to tell it whether you're +deploying to minikube: + +```sh +pulumi config set isMinikube +``` + +Perform the deployment: + +```sh +$ pulumi up +Updating stack 'testbook' +Performing changes: + + Type Name Status + + pulumi:pulumi:Stack guestbook-csharp-testbook created + + ├─ kubernetes:apps:Deployment redis-replica created + + ├─ kubernetes:apps:Deployment frontend created + + ├─ kubernetes:apps:Deployment redis-master created + + ├─ kubernetes:core:Service redis-master created + + ├─ kubernetes:core:Service redis-replica created + + └─ kubernetes:core:Service frontend created + +Outputs: + + frontendIp: "35.232.147.18" + +Resources: + + 7 created + +Duration: 17s + +Permalink: https://app.pulumi.com/lukehoban/guestbook-csharp/testbook/updates/1 +``` + +And finally - open the application in your browser to see the running application. If you're running +macOS you can simply run: + +```sh +open $(pulumi stack output frontendIp) +``` + +> _Note_: minikube does not support type `LoadBalancer`; if you are deploying to minikube, make sure +> to run `kubectl port-forward svc/frontend 8080:80` to forward the cluster port to the local +> machine and access the service via `localhost:8080`. + +![Guestbook in browser](./imgs/guestbook.png) diff --git a/kubernetes-cs-guestbook/imgs/guestbook.png b/kubernetes-cs-guestbook/imgs/guestbook.png new file mode 100644 index 000000000..e32eaffdc Binary files /dev/null and b/kubernetes-cs-guestbook/imgs/guestbook.png differ