From 76871f6a03df2324af618e025fa6aac4d0d15254 Mon Sep 17 00:00:00 2001 From: Sean Gillespie Date: Thu, 31 May 2018 22:02:12 -0700 Subject: [PATCH] Add a Jenkins on Kubernetes example --- kubernetes-ts-jenkins/.gitignore | 3 + kubernetes-ts-jenkins/Pulumi.yaml | 3 + kubernetes-ts-jenkins/index.ts | 15 ++ kubernetes-ts-jenkins/jenkins.ts | 254 ++++++++++++++++++++++++++++ kubernetes-ts-jenkins/package.json | 16 ++ kubernetes-ts-jenkins/tsconfig.json | 24 +++ 6 files changed, 315 insertions(+) create mode 100644 kubernetes-ts-jenkins/.gitignore create mode 100644 kubernetes-ts-jenkins/Pulumi.yaml create mode 100644 kubernetes-ts-jenkins/index.ts create mode 100644 kubernetes-ts-jenkins/jenkins.ts create mode 100644 kubernetes-ts-jenkins/package.json create mode 100644 kubernetes-ts-jenkins/tsconfig.json diff --git a/kubernetes-ts-jenkins/.gitignore b/kubernetes-ts-jenkins/.gitignore new file mode 100644 index 000000000..391c812cd --- /dev/null +++ b/kubernetes-ts-jenkins/.gitignore @@ -0,0 +1,3 @@ +/bin/ +/node_modules/ +/.pulumi/ diff --git a/kubernetes-ts-jenkins/Pulumi.yaml b/kubernetes-ts-jenkins/Pulumi.yaml new file mode 100644 index 000000000..9f7d75d09 --- /dev/null +++ b/kubernetes-ts-jenkins/Pulumi.yaml @@ -0,0 +1,3 @@ +name: kubernetes-ts-jenkins +description: Jenkins deployment on Kubernetes +runtime: nodejs diff --git a/kubernetes-ts-jenkins/index.ts b/kubernetes-ts-jenkins/index.ts new file mode 100644 index 000000000..facee0732 --- /dev/null +++ b/kubernetes-ts-jenkins/index.ts @@ -0,0 +1,15 @@ +import * as pulumi from "@pulumi/pulumi"; +import * as jenkins from "./jenkins"; + +const config = new pulumi.Config("jenkins"); +const instance = new jenkins.Instance("jenkins", { + name: "jenkins", + credentials: { + username: config.require("username"), + password: config.require("password"), + }, + resources: { + memory: "512Mi", + cpu: "100m", + } +}); diff --git a/kubernetes-ts-jenkins/jenkins.ts b/kubernetes-ts-jenkins/jenkins.ts new file mode 100644 index 000000000..a9380fcf8 --- /dev/null +++ b/kubernetes-ts-jenkins/jenkins.ts @@ -0,0 +1,254 @@ +import * as pulumi from "@pulumi/pulumi"; +import * as k8s from "@pulumi/kubernetes"; +import * as input from "@pulumi/kubernetes/types/input"; +import { extensions } from "@pulumi/kubernetes"; + +function createDeploymentArgs(args: JenkinsArgs): input.extensions.v1beta1.Deployment { + const image = args.image || { + registry: "docker.io", + repository: "bitnami/jenkins", + tag: "2.107.3", + pullPolicy: "IfNotPresent", + }; + + // This object is a projection of the Kubernetes object model into the Pulumi object model. + // Its structure is derived from the Deployment object in the Kubernetes API. + return { + metadata: { + name: args.name, + }, + spec: { + replicas: 1, + template: { + metadata: { + labels: { + app: args.name, + }, + }, + spec: { + volumes: [ + { + name: "jenkins-data", + persistentVolumeClaim: { + claimName: args.name, + } + }, + ], + containers: [ + { + name: args.name, + image: `${image.registry}/${image.repository}:${image.tag}`, + imagePullPolicy: image.pullPolicy, + env: [ + { + name: "JENKINS_USERNAME", + value: args.credentials.username, + }, + { + name: "JENKINS_PASSWORD", + valueFrom: { + secretKeyRef: { + name: args.name, + key: "jenkins-password", + }, + }, + }, + ], + ports: [ + { + name: "http", + containerPort: 8080, + }, + { + name: "https", + containerPort: 8443, + }, + ], + livenessProbe: { + httpGet: { + path: "/", + port: "http", + }, + initialDelaySeconds: 180, + timeoutSeconds: 5, + failureThreshold: 6, + }, + readinessProbe: { + httpGet: { + path: "/", + port: "http", + }, + initialDelaySeconds: 90, + timeoutSeconds: 5, + periodSeconds: 6, + }, + volumeMounts: [ + { + name: "jenkins-data", + mountPath: "/bitnami/jenkins", + } + ], + resources: { + requests: { + memory: args.resources.memory, + cpu: args.resources.cpu, + }, + }, + } // container + ] // containers + } // spec + } // template + } // spec + } // deployment +} + +/** + * ComponentResource for a Jenkins instance running in a Kubernetes cluster. + */ +export class Instance extends pulumi.ComponentResource { + constructor(name: string, args: JenkinsArgs, opts?: pulumi.ResourceOptions) { + super("jenkins:jenkins:Instance", name, args, opts); + + // The Secret will contain the root password for this instance. + const secret = new k8s.core.v1.Secret(`${args.name}-secret`, { + metadata: { + name: args.name, + }, + type: "Opaque", + data: { + "jenkins-password": Buffer.from(args.credentials.password).toString("base64"), + }, + }, { parent: this }); + + // The PVC provides persistant storage for Jenkins state. + const pvc = new k8s.core.v1.PersistentVolumeClaim(`${args.name}-pvc`, { + metadata: { + name: args.name, + annotations: { + "volume.beta.kubernetes.io/storage-class": "standard" + }, + }, + spec: { + accessModes: ["ReadWriteOnce"], + resources: { + requests: { + storage: "8Gi", + }, + }, + }, + }, { parent: this }); + + // The Deployment describes the desired state for our Jenkins setup. + const deploymentArgs = createDeploymentArgs(args); + const deployment = new k8s.extensions.v1beta1.Deployment(`${args.name}-deploy`, deploymentArgs, { parent: this }); + + // The Service exposes Jenkins to the external internet by providing load-balanced ingress for HTTP and HTTPS. + const service = new k8s.core.v1.Service(`${args.name}-service`, { + metadata: { + name: args.name, + }, + spec: { + type: "LoadBalancer", + ports: [ + { + name: "http", + port: 80, + targetPort: "http", + }, + { + name: "https", + port: 443, + targetPort: "https", + } + ], + selector: { + app: args.name, + } + } + }, { parent: this }); + + // This component resource has no outputs. + this.registerOutputs({}); + } +} + +/** + * Arguments for Jenkins instances. + */ +export interface JenkinsArgs { + /** + * The name of the instance. All Kubernetes objects will be tagged with this name + * in their metadata. + */ + readonly name: string, + + /** + * Credentials for accessing the created Jenkins instance. + */ + readonly credentials: JenkinsCredentials, + + /** + * The Docker image to use to launch this instance of Jenkins. + */ + readonly image?: JenkinsImage, + + /** + * Resource requests for this instance. + */ + readonly resources: JenkinsResources, +} + +/** + * Credentials to access the newly-created Jenkins instance. + */ +export interface JenkinsCredentials { + /** + * Username for the root user. + */ + readonly username: string, + + /** + * Password for the root user. + */ + readonly password: string, +} + +/** + * The image to use when launching Jenkins. + */ +export interface JenkinsImage { + /** + * The registry from which to draw Docker images. + */ + readonly registry: string, + + /** + * The Docker repository name for the target image. + */ + readonly repository: string, + + /** + * The Docker image tag for the target image. + */ + readonly tag: string, + + /** + * Pull policy for this image. + */ + readonly pullPolicy: string, +} + +/** + * Resource requests for this Jenkins instance. + */ +export interface JenkinsResources { + /** + * Requested memory. + */ + readonly memory: string; + + /** + * Requested CPU. + */ + readonly cpu: string; +} diff --git a/kubernetes-ts-jenkins/package.json b/kubernetes-ts-jenkins/package.json new file mode 100644 index 000000000..422780da0 --- /dev/null +++ b/kubernetes-ts-jenkins/package.json @@ -0,0 +1,16 @@ +{ + "name": "kubernetes-ts-jenkins", + "main": "bin/index.js", + "typings": "bin/index.d.ts", + "scripts": { + "build": "tsc" + }, + "devDependencies": { + "typescript": "^2.7.2", + "@types/node": "latest" + }, + "dependencies": { + "@pulumi/pulumi": "^0.12.2", + "@pulumi/kubernetes": "^0.13.0" + } +} diff --git a/kubernetes-ts-jenkins/tsconfig.json b/kubernetes-ts-jenkins/tsconfig.json new file mode 100644 index 000000000..2ea71672d --- /dev/null +++ b/kubernetes-ts-jenkins/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "outDir": "bin", + "target": "es6", + "lib": [ + "es6" + ], + "module": "commonjs", + "moduleResolution": "node", + "declaration": true, + "sourceMap": true, + "stripInternal": true, + "experimentalDecorators": true, + "pretty": true, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "forceConsistentCasingInFileNames": true, + "strictNullChecks": true + }, + "files": [ + "index.ts" + ] +}