From ec3ccc04079fd583995760c1b118228978911630 Mon Sep 17 00:00:00 2001 From: Luke Hoban Date: Thu, 14 May 2020 17:16:47 -0700 Subject: [PATCH] Add Python Guestbook Component Example (#689) --- aws-ts-stackreference/README.md | 2 +- kubernetes-py-guestbook/.gitignore | 1 + kubernetes-py-guestbook/README.md | 12 ++- .../components/Pulumi.yaml | 3 + kubernetes-py-guestbook/components/README.md | 88 ++++++++++++++++++ .../components/__main__.py | 39 ++++++++ .../components/imgs/guestbook.png | Bin 0 -> 34061 bytes .../components/requirements.txt | 2 + .../components/service_deployment.py | 70 ++++++++++++++ 9 files changed, 214 insertions(+), 3 deletions(-) create mode 100644 kubernetes-py-guestbook/components/Pulumi.yaml create mode 100644 kubernetes-py-guestbook/components/README.md create mode 100644 kubernetes-py-guestbook/components/__main__.py create mode 100644 kubernetes-py-guestbook/components/imgs/guestbook.png create mode 100644 kubernetes-py-guestbook/components/requirements.txt create mode 100644 kubernetes-py-guestbook/components/service_deployment.py diff --git a/aws-ts-stackreference/README.md b/aws-ts-stackreference/README.md index 3ace434f9..cc477534f 100644 --- a/aws-ts-stackreference/README.md +++ b/aws-ts-stackreference/README.md @@ -18,7 +18,7 @@ stacks via [StackReference](https://www.pulumi.com/docs/intro/concepts/organizin ```bash $ cd company $ npm install - ```` + ``` 1. Create a new stack: diff --git a/kubernetes-py-guestbook/.gitignore b/kubernetes-py-guestbook/.gitignore index e64527066..aab42a235 100644 --- a/kubernetes-py-guestbook/.gitignore +++ b/kubernetes-py-guestbook/.gitignore @@ -308,6 +308,7 @@ paket-files/ # Python Tools for Visual Studio (PTVS) __pycache__/ *.pyc +venv # Cake - Uncomment if you are using it # tools/** diff --git a/kubernetes-py-guestbook/README.md b/kubernetes-py-guestbook/README.md index b14cdab9d..f5d742562 100644 --- a/kubernetes-py-guestbook/README.md +++ b/kubernetes-py-guestbook/README.md @@ -1,6 +1,6 @@ -[![Deploy](https://get.pulumi.com/new/button.svg)](https://app.pulumi.com/new?template=https://github.com/pulumi/examples/tree/master/kubernetes-py-guestbook/simple) +[![Deploy](https://get.pulumi.com/new/button.svg)](https://app.pulumi.com/new?template=https://github.com/pulumi/examples/tree/master/kubernetes-ts-guestbook/components) -# Simple Guestbook App +# Simple and Component-based Kubernetes Guestbook Apps A port of the standard [Kubernetes Guestbook](https://kubernetes.io/docs/tutorials/stateless-application/guestbook/) to Pulumi. This example shows you how to build and deploy a simple, multi-tier web application using Kubernetes and @@ -9,3 +9,11 @@ Docker, and consists of three components: * A single-instance Redis master to store guestbook entries * Multiple replicated Redis instances to serve reads * Multiple web frontend instances + +In this directory, you will find two variants of the Guestbook: + +1. [simple/](./simple) is a straight port of the original YAML. +2. [components](./components) demonstrates benefits of using a real language, namely eliminating boilerplate through + the use of real component abstractions. + +Both examples provision the exact same Kubernetes Guestbook application, but showcase different aspects of Pulumi. diff --git a/kubernetes-py-guestbook/components/Pulumi.yaml b/kubernetes-py-guestbook/components/Pulumi.yaml new file mode 100644 index 000000000..1221d61f6 --- /dev/null +++ b/kubernetes-py-guestbook/components/Pulumi.yaml @@ -0,0 +1,3 @@ +name: kubernetes-py-guestbook +runtime: python +description: Kubernetes Guestbook example based on https://kubernetes.io/docs/tutorials/stateless-application/guestbook/ diff --git a/kubernetes-py-guestbook/components/README.md b/kubernetes-py-guestbook/components/README.md new file mode 100644 index 000000000..644085a3c --- /dev/null +++ b/kubernetes-py-guestbook/components/README.md @@ -0,0 +1,88 @@ +[![Deploy](https://get.pulumi.com/new/button.svg)](https://app.pulumi.com/new) + +# Kubernetes Guestbook (with Components) + +A version of the [Kubernetes Guestbook](https://kubernetes.io/docs/tutorials/stateless-application/guestbook/) +application using Pulumi. Unlike [the straight port of the original YAML](../simple), this variant +leverages real code to eliminate boilerplate. A `ServiceDeployment` class is used that combines the common pattern +of deploying a container image using a Kubernetes `Deployment`, and then scaling it using a `Service`. + +## 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 +Previewing update (guestbook): + + Type Name Plan + + pulumi:pulumi:Stack guestbook-easy-guestbook create + + ├─ k8sx:component:ServiceDeployment frontend create + + │ ├─ kubernetes:apps:Deployment frontend create + + │ └─ kubernetes:core:Service frontend create + + ├─ k8sx:component:ServiceDeployment redis-replica create + + │ ├─ kubernetes:apps:Deployment redis-replica create + + │ └─ kubernetes:core:Service redis-replica create + + └─ k8sx:component:ServiceDeployment redis-master create + + ├─ kubernetes:apps:Deployment redis-master create + + └─ kubernetes:core:Service redis-master create + +Resources: + + 10 to create + +Do you want to perform this update? yes +Updating (guestbook): + + Type Name Status + + pulumi:pulumi:Stack guestbook-easy-guestbook created + + ├─ k8sx:component:ServiceDeployment redis-master created + + │ ├─ kubernetes:apps:Deployment redis-master created + + │ └─ kubernetes:core:Service redis-master created + + ├─ k8sx:component:ServiceDeployment frontend created + + │ ├─ kubernetes:apps:Deployment frontend created + + │ └─ kubernetes:core:Service frontend created + + └─ k8sx:component:ServiceDeployment redis-replica created + + ├─ kubernetes:apps:Deployment redis-replica created + + └─ kubernetes:core:Service redis-replica created + +Outputs: + frontend_ip: "10.105.48.30" + +Resources: + + 10 created + +Duration: 21s + +Permalink: https://app.pulumi.com/acmecorp/k8sjs-guestbook/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 frontend_ip) +``` + +> _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-py-guestbook/components/__main__.py b/kubernetes-py-guestbook/components/__main__.py new file mode 100644 index 000000000..cb9082cb8 --- /dev/null +++ b/kubernetes-py-guestbook/components/__main__.py @@ -0,0 +1,39 @@ +# Copyright 2016-2020, Pulumi Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pulumi +from service_deployment import ServiceDeployment + +# 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. +config = pulumi.Config() +isMinikube = config.get_bool("isMinikube") + +ServiceDeployment( + "redis-master", + image="k8s.gcr.io/redis:e2e", + ports=[6379]) +ServiceDeployment( + "redis-slave", + image="gcr.io/google_samples/gb-redisslave:v1", + ports=[6379]) +frontend = ServiceDeployment( + "frontend", + image="gcr.io/google-samples/gb-frontend:v4", + replicas=3, + ports=[80], + allocate_ip_address=True, + is_minikube=config.get_bool("isMinikube")) + +pulumi.export("frontend_ip", frontend.ip_address) diff --git a/kubernetes-py-guestbook/components/imgs/guestbook.png b/kubernetes-py-guestbook/components/imgs/guestbook.png new file mode 100644 index 0000000000000000000000000000000000000000..e32eaffdc0e9c7c0ac6bafbde136f03ec531bee6 GIT binary patch literal 34061 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.0.0,<3.0.0 +pulumi-kubernetes>=2.0.0,<3.0.0 diff --git a/kubernetes-py-guestbook/components/service_deployment.py b/kubernetes-py-guestbook/components/service_deployment.py new file mode 100644 index 000000000..a582d88a9 --- /dev/null +++ b/kubernetes-py-guestbook/components/service_deployment.py @@ -0,0 +1,70 @@ +# Copyright 2016-2020, Pulumi Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import List + +import pulumi +from pulumi import ResourceOptions, ComponentResource, Output +from pulumi_kubernetes.apps.v1 import Deployment +from pulumi_kubernetes.core.v1 import Service, Namespace + + +class ServiceDeployment(ComponentResource): + deployment: Deployment + service: Service + ip_address: Output[str] + + def __init__(self, name: str, image: str, + resources: dict = None, replicas: int = None, + ports: List[int] = None, allocate_ip_address: bool = None, + is_minikube: bool = None, opts: ResourceOptions = None): + super().__init__('k8sx:component:ServiceDeployment', name, {}, opts) + + labels = {"app": name} + container = { + "name": name, + "image": image, + "resources": resources or {"requests": {"cpu": "100m", "memory": "100Mi"}}, + "ports": [{"container_port": p} for p in ports] if ports else None, + } + self.deployment = Deployment( + name, + spec={ + "selector": {"match_labels": labels}, + "replicas": 1, + "template": { + "metadata": {"labels": labels}, + "spec": {"containers": [container]}, + }, + }, + opts=pulumi.ResourceOptions(parent=self)) + self.service = Service( + name, + metadata={ + "name": name, + "labels": self.deployment.metadata['labels'], + }, + spec={ + "ports": [{"port": p, "targetPort": p} for p in ports] if ports else None, + "selector": self.deployment.spec['template']['metadata']['labels'], + "type": ("ClusterIP" if is_minikube else "LoadBalancer") if allocate_ip_address else None, + }, + opts=pulumi.ResourceOptions(parent=self)) + if allocate_ip_address: + if is_minikube: + self.ip_address = self.service.spec['clusterIP'] + else: + ingress=self.service.status['load_balancer']['ingress'][0] + self.ip_address = ingress.apply(lambda i: ingress["ip"] if "ip" in i else ingress['hostname']) + self.register_outputs({}) \ No newline at end of file