Skip to content
Permalink
Browse files

Add the Pulumi infrastructure definitions

This adds the Pulumi infrastructure definitions for our Ruby on Rails
application, including

* A hosted Kubernetes cluster, using GKE
* A managed PostgreSQL database, hosted in Google CloudSQL
* A Docker build and publish to the Docker Hub of the Rails App
  • Loading branch information...
joeduffy committed Oct 17, 2018
1 parent 8da9f33 commit 7c9ed844bed34cd7a7cf97e730fd607de4b1aebf
Showing with 1,534 additions and 0 deletions.
  1. +2 −0 infra/.gitignore
  2. +3 −0 infra/Pulumi.yaml
  3. +61 −0 infra/cluster.ts
  4. +19 −0 infra/config.ts
  5. +22 −0 infra/db.ts
  6. +56 −0 infra/index.ts
  7. +1,334 −0 infra/package-lock.json
  8. +12 −0 infra/package.json
  9. +25 −0 infra/tsconfig.json
@@ -0,0 +1,2 @@
/node_modules/
kubeconfig.yml
@@ -0,0 +1,3 @@
name: project-cloudysky
runtime: nodejs
description: An awesome Ruby on Rails app in Google Cloud
@@ -0,0 +1,61 @@
// Copyright 2016-2018, Pulumi Corporation. All rights reserved.

import * as gcp from "@pulumi/gcp";
import * as k8s from "@pulumi/kubernetes";
import * as pulumi from "@pulumi/pulumi";
import { clusterNodeCount, clusterNodeMachineType, clusterPassword, clusterUsername } from "./config";

// Create the GKE cluster and export it.
export const cluster = new gcp.container.Cluster("gke-cluster", {
initialNodeCount: clusterNodeCount,
nodeVersion: "latest",
minMasterVersion: "latest",
masterAuth: { username: clusterUsername, password: clusterPassword },
nodeConfig: {
machineType: clusterNodeMachineType,
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"
],
},
});

// 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 config = pulumi.
all([ cluster.name, cluster.endpoint, cluster.masterAuth ]).
apply(([ name, endpoint, auth ]) => {
const context = `${gcp.config.project}_${gcp.config.zone}_${name}`;
return `apiVersion: v1
clusters:
- cluster:
certificate-authority-data: ${auth.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
`;
});

// Export a Kubernetes provider instance that uses our cluster from above.
export const provider = new k8s.Provider("gke-k8s", {
kubeconfig: config,
});
@@ -0,0 +1,19 @@
// Copyright 2016-2018, Pulumi Corporation. All rights reserved.

import { Config } from "@pulumi/pulumi";

const config = new Config();

/// Docker config
export const dockerUsername = config.require("dockerUsername");
export const dockerPassword = config.require("dockerPassword");

/// PostgreSQL config
export const dbUsername = config.require("dbUsername");
export const dbPassword = config.require("dbPassword");

/// Kubernetes config
export const clusterNodeCount = config.getNumber("clusterNodeCount") || 3;
export const clusterNodeMachineType = config.get("clusterNodeMachineType") || "n1-standard-1";
export const clusterUsername = config.get("clusterUsername") || "admin";
export const clusterPassword = config.require("clusterPassword");
@@ -0,0 +1,22 @@
// Copyright 2016-2018, Pulumi Corporation. All rights reserved.

import * as gcp from "@pulumi/gcp";
import * as config from "./config";

// Provision a database for our Rails app.
export const instance = new gcp.sql.DatabaseInstance("web-db", {
databaseVersion: "POSTGRES_9_6",
settings: {
tier: "db-f1-micro",
ipConfiguration: {
authorizedNetworks: [{ value: "0.0.0.0/0" }],
},
},
});

// Create a user with the configured credentials for the Rails app to use.
const user = new gcp.sql.User("web-db-user", {
instance: instance.name,
name: config.dbUsername,
password: config.dbPassword,
});
@@ -0,0 +1,56 @@
// Copyright 2016-2018, Pulumi Corporation. All rights reserved.

import * as docker from "@pulumi/docker";
import * as k8s from "@pulumi/kubernetes";
import * as pulumi from "@pulumi/pulumi";
import * as cluster from "./cluster";
import * as config from "./config";
import * as db from "./db";

// Get the GCR repository for our app container, and build and publish the app image.
const appImage = new docker.Image("rails-app", {
imageName: `${config.dockerUsername}/${pulumi.getProject()}_${pulumi.getStack()}`,
build: "../app",
registry: {
server: "docker.io",
username: config.dockerUsername,
password: config.dockerPassword,
},
});

// Deploy the app container as a Kubernetes load balanced service.
const appPort = 3000;
const appLabels = { app: "rails-app" };
const appDeployment = new k8s.apps.v1.Deployment("rails-deployment", {
spec: {
selector: { matchLabels: appLabels },
replicas: 1,
template: {
metadata: { labels: appLabels },
spec: {
containers: [{
name: "rails-app",
image: appImage.imageName,
env: [
{ name: "DB_HOST", value: db.instance.firstIpAddress },
{ name: "DB_USERNAME", value: config.dbUsername },
{ name: "DB_PASSWORD", value: config.dbPassword },
],
ports: [{ containerPort: appPort }],
}],
},
},
},
}, { provider: cluster.provider });
const appService = new k8s.core.v1.Service("rails-service", {
metadata: { labels: appDeployment.metadata.apply(m => m.labels) },
spec: {
type: "LoadBalancer",
ports: [{ port: appPort, targetPort: appPort }],
selector: appDeployment.spec.apply(spec => spec.template.metadata.labels),
},
}, { provider: cluster.provider });

// Export the service's IP address.
export let appAddress =
appService.status.apply(s => `http://${s.loadBalancer.ingress[0].ip}:${appPort}/todo_lists`);
Oops, something went wrong.

0 comments on commit 7c9ed84

Please sign in to comment.
You can’t perform that action at this time.