From f2ac0a6249ea2806cd8dbe5a9d5f42e429d383c1 Mon Sep 17 00:00:00 2001 From: Lee Briggs Date: Tue, 21 Jul 2020 16:31:42 -0700 Subject: [PATCH] add a gke + service account example --- gcp-ts-gke-serviceaccount/Pulumi.yaml | 12 ++ gcp-ts-gke-serviceaccount/README.md | 119 ++++++++++++++++++ gcp-ts-gke-serviceaccount/index.ts | 153 ++++++++++++++++++++++++ gcp-ts-gke-serviceaccount/package.json | 11 ++ gcp-ts-gke-serviceaccount/tsconfig.json | 18 +++ 5 files changed, 313 insertions(+) create mode 100644 gcp-ts-gke-serviceaccount/Pulumi.yaml create mode 100644 gcp-ts-gke-serviceaccount/README.md create mode 100644 gcp-ts-gke-serviceaccount/index.ts create mode 100644 gcp-ts-gke-serviceaccount/package.json create mode 100644 gcp-ts-gke-serviceaccount/tsconfig.json diff --git a/gcp-ts-gke-serviceaccount/Pulumi.yaml b/gcp-ts-gke-serviceaccount/Pulumi.yaml new file mode 100644 index 000000000..d1f4f8591 --- /dev/null +++ b/gcp-ts-gke-serviceaccount/Pulumi.yaml @@ -0,0 +1,12 @@ +name: gcp-ts-gke-serviceaccount +description: A Google Kubernetes Engine (GKE) + Service account example +runtime: nodejs +template: + config: + gcp:project: + description: The Google Cloud project to deploy into + gcp:zone: + description: The Google Cloud zone + gcp:credentials: + description: Your GCP Service Account key contents for GKE Administration + secret: true diff --git a/gcp-ts-gke-serviceaccount/README.md b/gcp-ts-gke-serviceaccount/README.md new file mode 100644 index 000000000..2513d57c9 --- /dev/null +++ b/gcp-ts-gke-serviceaccount/README.md @@ -0,0 +1,119 @@ +[![Deploy](https://get.pulumi.com/new/button.svg)](https://app.pulumi.com/new) + +# Google Kubernetes Engine (GKE) Cluster with Service Account + +This example deploys an Google Cloud Platform (GCP) [Google Kubernetes Engine (GKE)](https://cloud.google.com/kubernetes-engine/) cluster, and deploys an example application that consumes a PubSub topic. The cluster has a secret which contains [Google Cloud Service Account Credentials](https://cloud.google.com/iam/docs/service-accounts) + +## Deploying the App + +To deploy your infrastructure, follow the below steps. + +### Prerequisites + +1. [Install Pulumi](https://www.pulumi.com/docs/get-started/install/) +1. [Install Node.js](https://nodejs.org/en/download/) +1. Install a package manager for Node.js, such as [npm](https://www.npmjs.com/get-npm) or [Yarn](https://yarnpkg.com/en/docs/install). +1. [Install Google Cloud SDK (`gcloud`)](https://cloud.google.com/sdk/docs/downloads-interactive) +1. Configure GCP Auth + + * Login using `gcloud` + + ```bash + $ gcloud auth login + $ gcloud config set project + $ gcloud auth application-default login + ``` + > Note: This auth mechanism is meant for inner loop developer + > workflows. If you want to run this example in an unattended service + > account setting, such as in CI/CD, please [follow instructions to + > configure your service account](https://www.pulumi.com/docs/intro/cloud-providers/gcp/setup/). The + > service account must have the role `Kubernetes Engine Admin` / `container.admin`. + +### Steps + +After cloning this repo, from this working directory, run these commands: + +1. Install the required Node.js packages: + + This installs the dependent packages [needed](https://www.pulumi.com/docs/intro/concepts/how-pulumi-works/) for our Pulumi program. + + ```bash + $ npm install + ``` + +1. Create a new Pulumi stack, which is an isolated deployment target for this example: + + This will initialize the Pulumi program in TypeScript. + + ```bash + $ pulumi stack init + ``` + +1. Set the required GCP configuration variables: + + This sets configuration options and default values for our cluster. + + ```bash + $ pulumi config set gcp:project + $ pulumi config set gcp:zone us-west1-a // any valid GCP Zone here + ``` + +1. Set some optional configuration variables (note, these values are optional and have defaults set): + + ```bash + $ pulumi config set name + $ pulumi config set machineType n1-standard-1 + ``` + +1. Stand up the GKE cluster: + + To preview and deploy changes, run `pulumi update` and select "yes." + + The `update` sub-command shows a preview of the resources that will be created + and prompts on whether to proceed with the deployment. Note that the stack + itself is counted as a resource, though it does not correspond + to a physical cloud resource. + + You can also run `pulumi up --diff` to see and inspect the diffs of the + overall changes expected to take place. + + Running `pulumi up` will deploy the GKE cluster. Note, provisioning a + new GKE cluster takes between 3-5 minutes. + + ```bash + + ``` + +1. After 3-5 minutes, your cluster will be ready, and the kubeconfig YAML you'll use to connect to the cluster will + be available as an output. + +1. Access the Kubernetes Cluster using `kubectl` + + To access your new Kubernetes cluster using `kubectl`, we need to setup the + `kubeconfig` file and download `kubectl`. We can leverage the Pulumi + stack output in the CLI, as Pulumi facilitates exporting these objects for us. + + ```bash + $ pulumi stack output kubeconfig > kubeconfig + $ export KUBECONFIG=$PWD/kubeconfig + $ kubectl version + $ kubectl cluster-info + $ kubectl get nodes + ``` + +1. Verify the pubsub example is working + + The pubsub deployment should be running, you can check it by examining the logs: + + ```bash + k logs -n pubsub -l appClass=pubsub + Pulling messages from Pub/Sub subscription... + ``` + +1. Once you've finished, tear down your stack's resources by destroying and removing it: + + ```bash + $ pulumi destroy --yes + $ pulumi stack rm --yes + ``` + diff --git a/gcp-ts-gke-serviceaccount/index.ts b/gcp-ts-gke-serviceaccount/index.ts new file mode 100644 index 000000000..170703a1f --- /dev/null +++ b/gcp-ts-gke-serviceaccount/index.ts @@ -0,0 +1,153 @@ +// Copyright 2016-2019, Pulumi Corporation. All rights reserved. +import * as gcp from "@pulumi/gcp"; +import * as k8s from "@pulumi/kubernetes"; +import * as pulumi from "@pulumi/pulumi"; + +const config = new pulumi.Config(); + +const name = config.get("name") || + "gke-serviceaccount-example"; +export const masterVersion = config.get("masterVersion") || + gcp.container.getEngineVersions().then(it => it.latestMasterVersion); +const machineType = "n1-standard-1" || config.get("machineType") + +// Create a service account +const serviceAccount = new gcp.serviceAccount.Account("serviceAccount", { + accountId: name, + displayName: "A service account for a GKE application", +}); + +const serviceAccountIAM = new gcp.projects.IAMBinding("serviceAccount-pub", { + role: "roles/pubsub.subscriber", + members: [pulumi.interpolate`serviceAccount:${serviceAccount.email}`] +}, {parent: serviceAccount}) + +const serviceAccountKey = new gcp.serviceAccount.Key("serviceAccount-key", { + serviceAccountId: serviceAccount.name, + publicKeyType: "TYPE_X509_PEM_FILE", +}, {parent: serviceAccount, additionalSecretOutputs: ['privateKey']}) + +// Create a GKE cluster +const cluster = new gcp.container.Cluster(name, { + initialNodeCount: 2, + minMasterVersion: masterVersion, + nodeVersion: masterVersion, + nodeConfig: { + machineType: machineType, + oauthScopes: [ + "https://www.googleapis.com/auth/compute", + "https://www.googleapis.com/auth/devstorage.read_only", + "https://www.googleapis.com/auth/logging.write", + "https://www.googleapis.com/auth/monitoring", + ], + }, +}); + +// Export the Cluster name +export const clusterName = cluster.name; + +// Manufacture a GKE-style kubeconfig. Note that this is slightly "different" +// because of the way GKE requires gcloud to be in the picture for cluster +// authentication (rather than using the client cert/key directly). +export const kubeconfig = pulumi.all([cluster.name, cluster.endpoint, cluster.masterAuth]).apply(([name, endpoint, masterAuth]) => { + const context = `${gcp.config.project}_${gcp.config.zone}_${name}`; + return `apiVersion: v1 +clusters: +- cluster: + certificate-authority-data: ${masterAuth.clusterCaCertificate} + server: https://${endpoint} + name: ${context} +contexts: +- context: + cluster: ${context} + user: ${context} + name: ${context} +current-context: ${context} +kind: Config +preferences: {} +users: +- name: ${context} + user: + auth-provider: + config: + cmd-args: config config-helper --format=json + cmd-path: gcloud + expiry-key: '{.credential.token_expiry}' + token-key: '{.credential.access_token}' + name: gcp +`; +}); + +// Create a Kubernetes provider instance that uses our cluster from above. +const clusterProvider = new k8s.Provider(name, { + kubeconfig: kubeconfig, +}); + +const appLabels = {appClass: "pubsub"}; + +// Create a Kubernetes Namespace +const ns = new k8s.core.v1.Namespace("pubsub-ns", { + metadata: { + name: "pubsub", + labels: appLabels, + } +}, {provider: clusterProvider}); + + +const gcpCredentials = new k8s.core.v1.Secret('gcp-credentials', { + metadata: { + namespace: ns.metadata.name, + labels: appLabels, + }, + type: 'Opaque', + stringData: { + 'gcp-credentials.json': serviceAccountKey.privateKey.apply((x) => Buffer.from(x, 'base64').toString('utf8')), + }, +}, {provider: clusterProvider, parent: ns}); + +const deployment = new k8s.apps.v1.Deployment("pubsub", + { + metadata: { + namespace: ns.metadata.name, + labels: appLabels, + }, + spec: { + replicas: 1, + selector: {matchLabels: appLabels}, + template: { + metadata: { + labels: appLabels, + }, + spec: { + volumes: [ + { + name: 'google-cloud-key', + secret: { + secretName: gcpCredentials.metadata.name, + } + } + ], + containers: [ + { + name: "pubsub-example", + image: "gcr.io/google-samples/pubsub-sample:v1", + volumeMounts: [ + { + name: "google-cloud-key", + mountPath: "/var/secrets/google" + } + ], + env: [{ + name: "GOOGLE_APPLICATION_CREDENTIALS", + value: "/var/secrets/google/gcp-credentials.json" + }] + }, + ], + }, + }, + }, + }, + { + provider: clusterProvider, parent: ns + }, +); diff --git a/gcp-ts-gke-serviceaccount/package.json b/gcp-ts-gke-serviceaccount/package.json new file mode 100644 index 000000000..90e7c1263 --- /dev/null +++ b/gcp-ts-gke-serviceaccount/package.json @@ -0,0 +1,11 @@ +{ + "name": "gke.typescript", + "devDependencies": { + "@types/node": "^10.0.0" + }, + "dependencies": { + "@pulumi/gcp": "^3.0.0", + "@pulumi/kubernetes": "^2.4.0", + "@pulumi/pulumi": "^2.0.0" + } +} diff --git a/gcp-ts-gke-serviceaccount/tsconfig.json b/gcp-ts-gke-serviceaccount/tsconfig.json new file mode 100644 index 000000000..ab65afa61 --- /dev/null +++ b/gcp-ts-gke-serviceaccount/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "strict": true, + "outDir": "bin", + "target": "es2016", + "module": "commonjs", + "moduleResolution": "node", + "sourceMap": true, + "experimentalDecorators": true, + "pretty": true, + "noFallthroughCasesInSwitch": true, + "noImplicitReturns": true, + "forceConsistentCasingInFileNames": true + }, + "files": [ + "index.ts" + ] +}