From d0d4204eae731eb2f083c9da3007139c85c4ad0e Mon Sep 17 00:00:00 2001 From: malacourse Date: Wed, 23 Aug 2017 13:10:57 -0400 Subject: [PATCH 1/6] Change tag to use Openshift plugin --- basic-tomcat/pipeline.groovy | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/basic-tomcat/pipeline.groovy b/basic-tomcat/pipeline.groovy index 42bc591e..7aa265ad 100644 --- a/basic-tomcat/pipeline.groovy +++ b/basic-tomcat/pipeline.groovy @@ -66,26 +66,28 @@ node('maven') { openshiftVerifyDeployment(deploymentConfig: "${env.APP_NAME}", namespace: "${STAGE1}", verifyReplicaCount: true) - input "Promote Application to Stage?" + //input "Promote Application to ${env.STAGE2}?" } stage("Promote To ${env.STAGE2}") { - sh """ - ${env.OC_CMD} tag ${env.STAGE1}/${env.APP_NAME}:latest ${env.STAGE2}/${env.APP_NAME}:latest - """ + openshiftTag (alias: 'true', apiURL: "${ocpApiServer}", + authToken: "${env.TOKEN}", destStream: "${env.APP_NAME}", + destTag: 'latest', destinationAuthToken: "${env.TOKEN}", destinationNamespace: "${env.STAGE2}", + namespace: "${env.STAGE1}", srcStream: "${env.APP_NAME}", srcTag: 'latest', verbose: 'false') } stage("Verify Deployment to ${env.STAGE2}") { openshiftVerifyDeployment(deploymentConfig: "${env.APP_NAME}", namespace: "${STAGE2}", verifyReplicaCount: true) - input "Promote Application to Prod?" + //input "Promote Application to ${env.STAGE3}?" } stage("Promote To ${env.STAGE3}") { - sh """ - ${env.OC_CMD} tag ${env.STAGE2}/${env.APP_NAME}:latest ${env.STAGE3}/${env.APP_NAME}:latest - """ + openshiftTag (alias: 'true', apiURL: "${ocpApiServer}", + authToken: "${env.TOKEN}", destStream: "${env.APP_NAME}", + destTag: 'latest', destinationAuthToken: "${env.TOKEN}", destinationNamespace: "${env.STAGE3}", + namespace: "${env.STAGE2}", srcStream: "${env.APP_NAME}", srcTag: 'latest', verbose: 'false') } stage("Verify Deployment to ${env.STAGE3}") { From af5dfd57e2d44b3222874588003cd403b63ec7f2 Mon Sep 17 00:00:00 2001 From: malacourse Date: Tue, 29 Aug 2017 17:06:02 -0400 Subject: [PATCH 2/6] Initial blue/green README.ml --- blue-green-spring/README.md | 152 ++++++++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 blue-green-spring/README.md diff --git a/blue-green-spring/README.md b/blue-green-spring/README.md new file mode 100644 index 00000000..24ac3af4 --- /dev/null +++ b/blue-green-spring/README.md @@ -0,0 +1,152 @@ +# A Sample OpenShift Pipeline for Blue Green deployments + +This example demonstrates how to implement a full end-to-end Jenkins Pipeline for a Java application in OpenShift Container Platform. This sample demonstrates the following capabilities: + +* Deploying an integrated Jenkins server inside of OpenShift +* Running both custom and oob Jenkins slaves as pods in OpenShift +* "One Click" instantiation of a Jenkins Pipeline using OpenShift's Jenkins Pipeline Strategy feature +* Promotion of an application's container image within an OpenShift Cluster (using `oc tag`) +* Promotion of an application's container image to a blue/green production configuration + +## Quickstart + +Run the following commands to instantiate this example. + +``` +cd ./simple-spring-boot +oc create -f projects/projects.yml +oc process openshift//jenkins-ephemeral | oc apply -f- -n simple-spring-boot-dev +oc process -f deploy/simple-spring-boot-template.yml --param-file=deploy/dev/params | oc apply -f- +oc process -f deploy/simple-spring-boot-template.yml --param-file=deploy/stage/params | oc apply -f- +oc process -f deploy/simple-spring-boot-template-prod.yml --param-file=deploy/prod/params | oc apply -f- +oc process -f build/basic-java-template.yml --param-file build/dev/params | oc apply -f- +``` + +## Architecture + +### OpenShift Templates + +The components of this pipeline are divided into two templates. + +The first template, `build/simple-spring-boot-template.yml` is what we are calling the "Build" template. It contains: + +* A `jenkinsPipelineStrategy` BuildConfig +* An `s2i` BuildConfig +* An ImageStream for the s2i build config to push to + +The build template contains a default source code repo for a java application compatible with this pipelines architecture (https://github.com/etsauer/ticket-monster). + +The second template, `deploy/simple-spring-boot-template.yml` is the "Deploy" template. It contains: + +* A tomcat8 DeploymentConfig +* A Service definition +* A Route + +The idea behind the split between the templates is that I can deploy the build template only once (to my dev project) and that the pipeline will promote my image through all of the various stages of my application's lifecycle. The deployment template gets deployed once to each of the stages of the application lifecycle (once per OpenShift project). + +### Pipeline Script + +This project includes a sample `pipeline.groovy` Jenkins Pipeline script that could be included with a Java project in order to implement a basic CI/CD pipeline for that project, under the following assumptions: + +* The project is built with Maven +* The `pipeline.groovy` script is placed in the same directory as the `pom.xml` file in the git source. +* The OpenShift projects that represent the Application's lifecycle stages are of the naming format: `-dev`, `-stage`, `-prod`. + +For convenience, this pipeline script is already included in the following git repository, based on the [JBoss Developers Ticket Monster](https://github.com/jboss-developer/ticket-monster) app. + +https://github.com/etsauer/ticket-monster + +## Bill of Materials + +* One or Two OpenShift Container Platform Clusters + * OpenShift 3.5+ is required. +* Access to GitHub + +## Implementation Instructions + +### 1. Create Lifecycle Stages + +For the purposes of this demo, we are going to create three stages for our application to be promoted through. + +- `simple-spring-boot-dev` +- `simple-spring-boot-stage` +- `simple-spring-boot-prod` + +In the spirit of _Infrastructure as Code_ we have a YAML file that defines the `ProjectRequests` for us. This is as an alternative to running `oc new-project`, but will yeild the same result. + +``` +$ oc create -f projects/projects.yml +projectrequest "simple-spring-boot-dev" created +projectrequest "simple-spring-boot-stage" created +projectrequest "simple-spring-boot-prod" created +``` + +### 2. Stand up Jenkins master in dev + +For this step, the OpenShift default template set provides exactly what we need to get jenkins up and running. + +``` +$ oc process openshift//jenkins-ephemeral | oc apply -f- -n simple-spring-boot-dev +route "jenkins" created +deploymentconfig "jenkins" created +serviceaccount "jenkins" created +rolebinding "jenkins_edit" created +service "jenkins-jnlp" created +service "jenkins" created +``` + +### 4. Instantiate Pipeline + +A _deploy template_ is provided at `deploy/simple-spring-boot-template.yml` that defines all of the resources required to run our Tomcat application. It includes: + +* A `Service` +* A `Route` +* An `ImageStream` +* A `DeploymentConfig` +* A `RoleBinding` to allow Jenkins to deploy in each namespace. + +This template should be instantiated once in each of the namespaces that our app will be deployed to. For this purpose, we have created a param file to be fed to `oc process` to customize the template for each environment. + +Deploy the deployment template to all three projects. +``` +$ oc process -f deploy/simple-spring-boot-template.yml --param-file=deploy/dev/params | oc apply -f- +service "simple-spring-boot" created +route "simple-spring-boot" created +imagestream "simple-spring-boot" created +deploymentconfig "simple-spring-boot" created +rolebinding "jenkins_edit" configured +$ oc process -f deploy/simple-spring-boot-template.yml --param-file=deploy/stage/params | oc apply -f- +service "simple-spring-boot" created +route "simple-spring-boot" created +imagestream "simple-spring-boot" created +deploymentconfig "simple-spring-boot" created +rolebinding "jenkins_edit" created +$ oc process -f deploy/simple-spring-boot-template.yml --param-file=deploy/prod/params | oc apply -f- +service "simple-spring-boot" created +route "simple-spring-boot" created +imagestream "simple-spring-boot" created +deploymentconfig "simple-spring-boot" created +rolebinding "jenkins_edit" created +``` + +A _build template_ is provided at `build/basic-java-template.yml` that defines all the resources required to build our java app. It includes: + +* A `BuildConfig` that defines a `JenkinsPipelineStrategy` build, which will be used to define out pipeline. +* A `BuildConfig` that defines a `Source` build with `Binary` input. This will build our image. + +Deploy the pipeline template in dev only. +``` +$ oc process -f build/basic-java-template.yml --param-file build/dev/params | oc apply -f- +buildconfig "simple-spring-boot-pipeline" created +buildconfig "simple-spring-boot" created +``` + +At this point you should be able to go to the Web Console and follow the pipeline by clicking in your `myapp-dev` project, and going to *Builds* -> *Pipelines*. At several points you will be prompted for input on the pipeline. You can interact with it by clicking on the _input required_ link, which takes you to Jenkins, where you can click the *Proceed* button. By the time you get through the end of the pipeline you should be able to visit the Route for your app deployed to the `myapp-prod` project to confirm that your image has been promoted through all stages. + +## Cleanup + +Cleaning up this example is as simple as deleting the projects we created at the beginning. + +``` +oc delete project simple-spring-boot-dev simple-spring-boot-prod simple-spring-boot-stage +``` From 011be57e5030f45e07087bccf983712bb0480f6b Mon Sep 17 00:00:00 2001 From: malacourse Date: Thu, 31 Aug 2017 10:29:59 -0400 Subject: [PATCH 3/6] Add blue/green spring boot app --- blue-green-spring/README.md | 40 +++- .../build/basic-java-template.yml | 102 +++++++++ blue-green-spring/build/dev/params | 3 + blue-green-spring/deploy/dev/params | 3 + blue-green-spring/deploy/prod/params | 4 + .../simple-spring-boot-template-prod.yml | 205 ++++++++++++++++++ .../deploy/simple-spring-boot-template.yml | 128 +++++++++++ blue-green-spring/deploy/stage/params | 4 + blue-green-spring/pipeline.groovy | 145 +++++++++++++ blue-green-spring/projects/projects.yml | 21 ++ 10 files changed, 644 insertions(+), 11 deletions(-) create mode 100644 blue-green-spring/build/basic-java-template.yml create mode 100644 blue-green-spring/build/dev/params create mode 100644 blue-green-spring/deploy/dev/params create mode 100644 blue-green-spring/deploy/prod/params create mode 100644 blue-green-spring/deploy/simple-spring-boot-template-prod.yml create mode 100644 blue-green-spring/deploy/simple-spring-boot-template.yml create mode 100644 blue-green-spring/deploy/stage/params create mode 100644 blue-green-spring/pipeline.groovy create mode 100644 blue-green-spring/projects/projects.yml diff --git a/blue-green-spring/README.md b/blue-green-spring/README.md index 24ac3af4..d6b20819 100644 --- a/blue-green-spring/README.md +++ b/blue-green-spring/README.md @@ -1,12 +1,14 @@ # A Sample OpenShift Pipeline for Blue Green deployments -This example demonstrates how to implement a full end-to-end Jenkins Pipeline for a Java application in OpenShift Container Platform. This sample demonstrates the following capabilities: +This example demonstrates how to implement a full end-to-end Jenkins Pipeline for a Java application in a Blue/Green deployment in the OpenShift Container Platform. This sample demonstrates the following capabilities: * Deploying an integrated Jenkins server inside of OpenShift * Running both custom and oob Jenkins slaves as pods in OpenShift * "One Click" instantiation of a Jenkins Pipeline using OpenShift's Jenkins Pipeline Strategy feature * Promotion of an application's container image within an OpenShift Cluster (using `oc tag`) +* Tagging images with the current version of the artifact defined in the pom.xml file * Promotion of an application's container image to a blue/green production configuration +* Switching production routes between blue and green deployments after confirmation ## Quickstart @@ -26,7 +28,7 @@ oc process -f build/basic-java-template.yml --param-file build/dev/params | oc a ### OpenShift Templates -The components of this pipeline are divided into two templates. +The components of this pipeline are divided into three templates. The first template, `build/simple-spring-boot-template.yml` is what we are calling the "Build" template. It contains: @@ -34,14 +36,20 @@ The first template, `build/simple-spring-boot-template.yml` is what we are calli * An `s2i` BuildConfig * An ImageStream for the s2i build config to push to -The build template contains a default source code repo for a java application compatible with this pipelines architecture (https://github.com/etsauer/ticket-monster). +The build template contains a default source code repo for a java application compatible with this pipelines architecture (https://github.com/malacourse/simple-spring-boot-web). The second template, `deploy/simple-spring-boot-template.yml` is the "Deploy" template. It contains: -* A tomcat8 DeploymentConfig +* A openjdk8 DeploymentConfig * A Service definition * A Route +The third template, `deploy/simple-spring-boot-template-prod.yml` is the "Deploy" template for a blue/green project. It contains: + +* Two openjdk8 DeploymentConfig's +* Two Service definition's +* A Route + The idea behind the split between the templates is that I can deploy the build template only once (to my dev project) and that the pipeline will promote my image through all of the various stages of my application's lifecycle. The deployment template gets deployed once to each of the stages of the application lifecycle (once per OpenShift project). ### Pipeline Script @@ -52,13 +60,13 @@ This project includes a sample `pipeline.groovy` Jenkins Pipeline script that co * The `pipeline.groovy` script is placed in the same directory as the `pom.xml` file in the git source. * The OpenShift projects that represent the Application's lifecycle stages are of the naming format: `-dev`, `-stage`, `-prod`. -For convenience, this pipeline script is already included in the following git repository, based on the [JBoss Developers Ticket Monster](https://github.com/jboss-developer/ticket-monster) app. +For convenience, this pipeline script is already included in the following git repository, based on a [Simple Spring Boot Web app](https://github.com/malacourse/simple-spring-boot-web) app. The app displays a message that will change color based on which deployment is live in the production project. -https://github.com/etsauer/ticket-monster +https://github.com/malacourse/simple-spring-boot-web ## Bill of Materials -* One or Two OpenShift Container Platform Clusters +* One OpenShift Container Platform Clusters * OpenShift 3.5+ is required. * Access to GitHub @@ -97,7 +105,7 @@ service "jenkins" created ### 4. Instantiate Pipeline -A _deploy template_ is provided at `deploy/simple-spring-boot-template.yml` that defines all of the resources required to run our Tomcat application. It includes: +A _deploy template_ is provided at `deploy/simple-spring-boot-template.yml` that defines all of the resources required to run the openjdk8 application. It includes: * A `Service` * A `Route` @@ -105,7 +113,17 @@ A _deploy template_ is provided at `deploy/simple-spring-boot-template.yml` that * A `DeploymentConfig` * A `RoleBinding` to allow Jenkins to deploy in each namespace. -This template should be instantiated once in each of the namespaces that our app will be deployed to. For this purpose, we have created a param file to be fed to `oc process` to customize the template for each environment. +This template should be instantiated once in each of the lower level namespaces (dev, stage,qa) that our app will be deployed to. For this purpose, we have created a param file to be fed to `oc process` to customize the template for each environment. + +A production blue/green_deploy template_ is provided at `deploy/simple-spring-boot-template-prod.yml` that defines all of the resources required to run the openjdk8 application. It includes: + +* Two `Service's` +* A `Route` +* Two `ImageStream's` +* Two `DeploymentConfig's` +* A `RoleBinding` to allow Jenkins to deploy in each namespace. + +This template should be instantiated in the production blue/green namespace that our app will be deployed to. For this purpose, we have created a param file to be fed to `oc process` to customize the template for each environment. Deploy the deployment template to all three projects. ``` @@ -121,7 +139,7 @@ route "simple-spring-boot" created imagestream "simple-spring-boot" created deploymentconfig "simple-spring-boot" created rolebinding "jenkins_edit" created -$ oc process -f deploy/simple-spring-boot-template.yml --param-file=deploy/prod/params | oc apply -f- +$ oc process -f deploy/simple-spring-boot-template-prod.yml --param-file=deploy/prod/params | oc apply -f- service "simple-spring-boot" created route "simple-spring-boot" created imagestream "simple-spring-boot" created @@ -141,7 +159,7 @@ buildconfig "simple-spring-boot-pipeline" created buildconfig "simple-spring-boot" created ``` -At this point you should be able to go to the Web Console and follow the pipeline by clicking in your `myapp-dev` project, and going to *Builds* -> *Pipelines*. At several points you will be prompted for input on the pipeline. You can interact with it by clicking on the _input required_ link, which takes you to Jenkins, where you can click the *Proceed* button. By the time you get through the end of the pipeline you should be able to visit the Route for your app deployed to the `myapp-prod` project to confirm that your image has been promoted through all stages. +At this point you should be able to go to the Web Console and follow the pipeline by clicking in your `myapp-dev` project, and going to *Builds* -> *Pipelines*. There is a prompt for input on the pipeline before the production route is switched to the new deployment. You can interact with it by clicking on the _input required_ link, which takes you to Jenkins, where you can click the *Proceed* button. By the time you get through the end of the pipeline you should be able to visit the Route for your app deployed to the `myapp-prod` project to confirm that your image has been promoted through all stages. ## Cleanup diff --git a/blue-green-spring/build/basic-java-template.yml b/blue-green-spring/build/basic-java-template.yml new file mode 100644 index 00000000..4eb63261 --- /dev/null +++ b/blue-green-spring/build/basic-java-template.yml @@ -0,0 +1,102 @@ +apiVersion: v1 +kind: Template +labels: + template: generic-java-jenkins-pipeline +metadata: + annotations: + description: Application template for JWS applications built using a Jenkins Pipeline + iconClass: icon-tomcat + tags: tomcat,tomcat8,java,jboss,xpaas,jenkins-ci + version: 1.2.0 + name: generic-java-jenkins-pipeline +objects: +- kind: "BuildConfig" + apiVersion: "v1" + metadata: + labels: + application: ${APPLICATION_NAME} + name: "${APPLICATION_NAME}-pipeline" + namespace: "${NAMESPACE}" + spec: + source: + type: Git + git: + uri: ${SOURCE_REPOSITORY_URL} + ref: ${SOURCE_REPOSITORY_REF} + contextDir: ${CONTEXT_DIR} + triggers: + - type: "GitHub" + github: + secret: ${GITHUB_WEBHOOK_SECRET} + - type: "ConfigChange" + strategy: + type: "JenkinsPipeline" + jenkinsPipelineStrategy: + jenkinsfilePath: ${PIPELINE_SCRIPT} + env: + - name: "BUILD_CONTEXT_DIR" + value: "demo" +- apiVersion: v1 + kind: BuildConfig + metadata: + labels: + application: ${APPLICATION_NAME} + name: ${APPLICATION_NAME} + namespace: "${NAMESPACE}" + spec: + output: + to: + kind: ImageStreamTag + name: ${APPLICATION_NAME}:latest + source: + binary: {} + type: Binary + strategy: + sourceStrategy: + from: + kind: ImageStreamTag + name: ${IMAGE_STREAM_TAG_NAME} + namespace: ${IMAGE_STREAM_NAMESPACE} + type: Source +parameters: +- description: The name for the application. + name: APPLICATION_NAME + required: true + value: simple-spring-boot +- description: The namespace to deploy into + name: NAMESPACE + required: true +- description: Git source URI for application + name: SOURCE_REPOSITORY_URL + required: true + value: https://github.com/malacourse/simple-spring-boot-web.git +- description: Git branch/tag reference + name: SOURCE_REPOSITORY_REF + value: "master" +- description: Path within Git project to build; empty for root project directory. + name: CONTEXT_DIR + value: +- description: Path within Git project pointing to the pipeline run script + name: PIPELINE_SCRIPT + value: pipeline.groovy +- description: GitHub trigger secret + from: '[a-zA-Z0-9]{8}' + generate: expression + name: GITHUB_WEBHOOK_SECRET + required: true +- description: Generic build trigger secret + from: '[a-zA-Z0-9]{8}' + generate: expression + name: GENERIC_WEBHOOK_SECRET + required: true +- description: Namespace in which the ImageStreams for Red Hat Middleware images are + installed. These ImageStreams are normally installed in the openshift namespace. + You should only need to modify this if you've installed the ImageStreams in a + different namespace/project. + name: IMAGE_STREAM_NAMESPACE + required: true + value: openshift +- description: Image stream tag for the image you'd like to use to build the application + name: IMAGE_STREAM_TAG_NAME + required: true + value: redhat-openjdk18-openshift:1.1 diff --git a/blue-green-spring/build/dev/params b/blue-green-spring/build/dev/params new file mode 100644 index 00000000..ae507548 --- /dev/null +++ b/blue-green-spring/build/dev/params @@ -0,0 +1,3 @@ +APPLICATION_NAME=spring-boot-web +NAMESPACE=simple-spring-boot-dev +IMAGE_STREAM_TAG_NAME=redhat-openjdk18-openshift:1.1 diff --git a/blue-green-spring/deploy/dev/params b/blue-green-spring/deploy/dev/params new file mode 100644 index 00000000..56d804f7 --- /dev/null +++ b/blue-green-spring/deploy/dev/params @@ -0,0 +1,3 @@ +APPLICATION_NAME=spring-boot-web +NAMESPACE=simple-spring-boot-dev +SA_NAMESPACE=simple-spring-boot-dev diff --git a/blue-green-spring/deploy/prod/params b/blue-green-spring/deploy/prod/params new file mode 100644 index 00000000..db6a9d17 --- /dev/null +++ b/blue-green-spring/deploy/prod/params @@ -0,0 +1,4 @@ +APPLICATION_NAME=spring-boot-web +NAMESPACE=simple-spring-boot-prod +SA_NAME=jenkins +SA_NAMESPACE=simple-spring-boot-dev diff --git a/blue-green-spring/deploy/simple-spring-boot-template-prod.yml b/blue-green-spring/deploy/simple-spring-boot-template-prod.yml new file mode 100644 index 00000000..62067ab4 --- /dev/null +++ b/blue-green-spring/deploy/simple-spring-boot-template-prod.yml @@ -0,0 +1,205 @@ +apiVersion: v1 +kind: Template +labels: + template: openjdk-deployment +metadata: + annotations: + description: Application template for spring boot built using a Jenkins Pipeline + iconClass: icon-java + tags: openjdk8,java,xpaas + version: 1.2.0 + name: openjdk8-deployment +objects: +- apiVersion: v1 + kind: Service + metadata: + annotations: + description: The web server's http port. + labels: + application: ${APPLICATION_NAME} + name: ${APPLICATION_NAME}-green + namespace: ${NAMESPACE} + spec: + ports: + - port: 8080 + targetPort: 8080 + selector: + deploymentConfig: ${APPLICATION_NAME}-green +- apiVersion: v1 + kind: Service + metadata: + annotations: + description: The web server's http port. + labels: + application: ${APPLICATION_NAME} + name: ${APPLICATION_NAME}-blue + namespace: ${NAMESPACE} + spec: + ports: + - port: 8080 + targetPort: 8080 + selector: + deploymentConfig: ${APPLICATION_NAME}-blue +- apiVersion: v1 + id: ${APPLICATION_NAME}-http + kind: Route + metadata: + annotations: + description: Route for application's http service. + labels: + application: ${APPLICATION_NAME} + name: ${APPLICATION_NAME} + namespace: ${NAMESPACE} + spec: + host: ${HOSTNAME_HTTP} + to: + name: ${APPLICATION_NAME}-blue +- apiVersion: v1 + kind: ImageStream + metadata: + labels: + application: ${APPLICATION_NAME} + name: ${APPLICATION_NAME}-green + namespace: ${NAMESPACE} +- apiVersion: v1 + kind: ImageStream + metadata: + labels: + application: ${APPLICATION_NAME} + name: ${APPLICATION_NAME}-green + namespace: ${NAMESPACE} +- apiVersion: v1 + kind: DeploymentConfig + metadata: + labels: + application: ${APPLICATION_NAME} + name: ${APPLICATION_NAME}-blue + namespace: ${NAMESPACE} + spec: + replicas: 1 + selector: + deploymentConfig: ${APPLICATION_NAME}-blue + strategy: + type: Recreate + template: + metadata: + labels: + application: ${APPLICATION_NAME} + deploymentConfig: ${APPLICATION_NAME}-blue + name: ${APPLICATION_NAME}-blue + spec: + containers: + - env: + - name: MY_DEPLOYMENT_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + image: ${APPLICATION_NAME}-blue + imagePullPolicy: Always + name: ${APPLICATION_NAME}-blue + ports: + - containerPort: 8080 + name: http + protocol: TCP + readinessProbe: + exec: + command: + - /bin/bash + - -c + - curl -s 'http://localhost:8080/' |grep -iq 'Openshift' + terminationGracePeriodSeconds: 60 + triggers: + - imageChangeParams: + automatic: true + containerNames: + - ${APPLICATION_NAME}-blue + from: + kind: ImageStreamTag + name: ${APPLICATION_NAME}-blue:latest + type: ImageChange + - type: ConfigChange +- apiVersion: v1 + kind: DeploymentConfig + metadata: + labels: + application: ${APPLICATION_NAME} + name: ${APPLICATION_NAME}-green + namespace: ${NAMESPACE} + spec: + replicas: 1 + selector: + deploymentConfig: ${APPLICATION_NAME}-green + strategy: + type: Recreate + template: + metadata: + labels: + application: ${APPLICATION_NAME} + deploymentConfig: ${APPLICATION_NAME}-green + name: ${APPLICATION_NAME}-green + spec: + containers: + - env: + - name: MY_DEPLOYMENT_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + image: ${APPLICATION_NAME}-green + imagePullPolicy: Always + name: ${APPLICATION_NAME}-green + ports: + - containerPort: 8080 + name: http + protocol: TCP + readinessProbe: + exec: + command: + - /bin/bash + - -c + - curl -s 'http://localhost:8080/' |grep -iq 'Openshift' + terminationGracePeriodSeconds: 60 + triggers: + - imageChangeParams: + automatic: true + containerNames: + - ${APPLICATION_NAME}-green + from: + kind: ImageStreamTag + name: ${APPLICATION_NAME}-green:latest + type: ImageChange + - type: ConfigChange +- apiVersion: v1 + groupNames: null + kind: RoleBinding + metadata: + creationTimestamp: null + labels: + template: simple-spring-boot-template + name: jenkins_edit + namespace: ${NAMESPACE} + roleRef: + name: edit + subjects: + - kind: ServiceAccount + name: ${SA_NAME} + namespace: ${SA_NAMESPACE} + userNames: + - system:serviceaccount:${SA_NAMESPACE}:${SA_NAME} +parameters: +- description: The name for the application. + name: APPLICATION_NAME + required: true + value: openjdk8-app +- description: The namespace to deploy into + name: NAMESPACE + required: true +- descriptiondd: Name of a service account that can deploy to this project + name: SA_NAME + required: true + value: jenkins +- description: Namespace of service account that can deploy to this project + name: SA_NAMESPACE + required: true +- description: 'Custom hostname for http service route. Leave blank for default hostname, + e.g.: -.' + name: HOSTNAME_HTTP diff --git a/blue-green-spring/deploy/simple-spring-boot-template.yml b/blue-green-spring/deploy/simple-spring-boot-template.yml new file mode 100644 index 00000000..d6f5d480 --- /dev/null +++ b/blue-green-spring/deploy/simple-spring-boot-template.yml @@ -0,0 +1,128 @@ +apiVersion: v1 +kind: Template +labels: + template: openjdk-deployment +metadata: + annotations: + description: Application template for spring boot built using a Jenkins Pipeline + iconClass: icon-java + tags: openjdk8,java,xpaas + version: 1.2.0 + name: openjdk8-deployment +objects: +- apiVersion: v1 + kind: Service + metadata: + annotations: + description: The web server's http port. + labels: + application: ${APPLICATION_NAME} + name: ${APPLICATION_NAME} + namespace: ${NAMESPACE} + spec: + ports: + - port: 8080 + targetPort: 8080 + selector: + deploymentConfig: ${APPLICATION_NAME} +- apiVersion: v1 + id: ${APPLICATION_NAME}-http + kind: Route + metadata: + annotations: + description: Route for application's http service. + labels: + application: ${APPLICATION_NAME} + name: ${APPLICATION_NAME} + namespace: ${NAMESPACE} + spec: + host: ${HOSTNAME_HTTP} + to: + name: ${APPLICATION_NAME} +- apiVersion: v1 + kind: ImageStream + metadata: + labels: + application: ${APPLICATION_NAME} + name: ${APPLICATION_NAME} + namespace: ${NAMESPACE} +- apiVersion: v1 + kind: DeploymentConfig + metadata: + labels: + application: ${APPLICATION_NAME} + name: ${APPLICATION_NAME} + namespace: ${NAMESPACE} + spec: + replicas: 1 + selector: + deploymentConfig: ${APPLICATION_NAME} + strategy: + type: Recreate + template: + metadata: + labels: + application: ${APPLICATION_NAME} + deploymentConfig: ${APPLICATION_NAME} + name: ${APPLICATION_NAME} + spec: + containers: + - image: ${APPLICATION_NAME} + imagePullPolicy: Always + name: ${APPLICATION_NAME} + ports: + - containerPort: 8080 + name: http + protocol: TCP + readinessProbe: + exec: + command: + - /bin/bash + - -c + - curl -s 'http://localhost:8080/' |grep -iq 'Openshift' + terminationGracePeriodSeconds: 60 + triggers: + - imageChangeParams: + automatic: true + containerNames: + - ${APPLICATION_NAME} + from: + kind: ImageStreamTag + name: ${APPLICATION_NAME}:latest + type: ImageChange + - type: ConfigChange +- apiVersion: v1 + groupNames: null + kind: RoleBinding + metadata: + creationTimestamp: null + labels: + template: simple-spring-boot-template + name: jenkins_edit + namespace: ${NAMESPACE} + roleRef: + name: edit + subjects: + - kind: ServiceAccount + name: ${SA_NAME} + namespace: ${SA_NAMESPACE} + userNames: + - system:serviceaccount:${SA_NAMESPACE}:${SA_NAME} +parameters: +- description: The name for the application. + name: APPLICATION_NAME + required: true + value: openjdk8-app +- description: The namespace to deploy into + name: NAMESPACE + required: true +- descriptiondd: Name of a service account that can deploy to this project + name: SA_NAME + required: true + value: jenkins +- description: Namespace of service account that can deploy to this project + name: SA_NAMESPACE + required: true +- description: 'Custom hostname for http service route. Leave blank for default hostname, + e.g.: -.' + name: HOSTNAME_HTTP diff --git a/blue-green-spring/deploy/stage/params b/blue-green-spring/deploy/stage/params new file mode 100644 index 00000000..75ef207f --- /dev/null +++ b/blue-green-spring/deploy/stage/params @@ -0,0 +1,4 @@ +APPLICATION_NAME=spring-boot-web +NAMESPACE=simple-spring-boot-stage +SA_NAME=jenkins +SA_NAMESPACE=simple-spring-boot-dev diff --git a/blue-green-spring/pipeline.groovy b/blue-green-spring/pipeline.groovy new file mode 100644 index 00000000..6d9f1e51 --- /dev/null +++ b/blue-green-spring/pipeline.groovy @@ -0,0 +1,145 @@ +#!/usr/bin/groovy + +//// +// This pipeline requires the following plugins: +// Kubernetes Plugin 0.10 +//// + +String ocpApiServer = env.OCP_API_SERVER ? "${env.OCP_API_SERVER}" : "https://openshift.default.svc.cluster.local" + +node('master') { + + env.NAMESPACE = readFile('/var/run/secrets/kubernetes.io/serviceaccount/namespace').trim() + env.TOKEN = readFile('/var/run/secrets/kubernetes.io/serviceaccount/token').trim() + env.OC_CMD = "oc --request-timeout='0' --token=${env.TOKEN} --server=${ocpApiServer} --certificate-authority=/run/secrets/kubernetes.io/serviceaccount/ca.crt --namespace=${env.NAMESPACE}" + + env.APP_NAME = "${env.JOB_NAME}".replaceAll(/-?pipeline-?/, '').replaceAll(/-?${env.NAMESPACE}-?/, '') + def projectBase = "${env.NAMESPACE}".replaceAll(/-dev/, '') + env.STAGE1 = "${projectBase}-dev" + env.STAGE2 = "${projectBase}-stage" + env.STAGE3 = "${projectBase}-prod" + +} + +node('maven') { + def mvnHome = env.MAVEN_HOME ? "${env.MAVEN_HOME}" : "/usr/share/maven/" + def mvnCmd = "mvn" + String pomFileLocation = env.BUILD_CONTEXT_DIR ? "${env.BUILD_CONTEXT_DIR}/pom.xml" : "pom.xml" + + stage('SCM Checkout') { + + checkout scm + sh "orig=\$(pwd); cd \$(dirname ${pomFileLocation}); git describe --tags; cd \$orig" + } + + stage('Build') { + + sh "${mvnCmd} clean install -DskipTests=true -f ${pomFileLocation}" + + } + + stage('Unit Test') { + + sh "${mvnCmd} test -f ${pomFileLocation}" + + } + + // The following variables need to be defined at the top level and not inside + // the scope of a stage - otherwise they would not be accessible from other stages. + // Extract version and other properties from the pom.xml + def groupId = getGroupIdFromPom("./pom.xml") + def artifactId = getArtifactIdFromPom("./pom.xml") + def version = getVersionFromPom("./pom.xml") + println("Artifact ID:" + artifactId + ", Group ID:" + groupId) + println("New version tag:" + version) + + stage('Build Image') { + + sh """ + rm -rf oc-build && mkdir -p oc-build/deployments + + for t in \$(echo "jar;war;ear" | tr ";" "\\n"); do + cp -rfv ./target/*.\$t oc-build/deployments/ 2> /dev/null || echo "No \$t files" + done + + ${env.OC_CMD} start-build ${env.APP_NAME} --from-dir=oc-build --wait=true --follow=true || exit 1 + """ + } + + stage("Verify Deployment to ${env.STAGE1}") { + + openshiftVerifyDeployment(deploymentConfig: "${env.APP_NAME}", namespace: "${STAGE1}", verifyReplicaCount: true) + + //input "Promote Application to Stage?" + } + + stage('Integration Test') { + + //TODO: Add application integration testing, verify db connectivity, rest calls ... + } + + stage("Promote To ${env.STAGE2}") { + sh """ + ${env.OC_CMD} tag ${env.STAGE1}/${env.APP_NAME}:latest ${env.STAGE2}/${env.APP_NAME}:${version} + """ + sh "${env.OC_CMD} patch dc ${env.APP_NAME} --patch '{\"spec\": { \"triggers\": [ { \"type\": \"ImageChange\", \"imageChangeParams\": { \"containerNames\": [ \"${env.APP_NAME}\" ], \"from\": { \"kind\": \"ImageStreamTag\", \"namespace\": \"${env.STAGE2}\", \"name\": \"${env.APP_NAME}:${version}\"}}}]}}' -n ${env.STAGE2}" + openshiftDeploy (apiURL: "${ocpApiServer}", authToken: "${env.TOKEN}", depCfg: "${env.APP_NAME}", namespace: "${env.STAGE2}", waitTime: '300', waitUnit: 'sec') + } + + stage("Verify Deployment to ${env.STAGE2}") { + + openshiftVerifyDeployment(deploymentConfig: "${env.APP_NAME}", namespace: "${STAGE2}", verifyReplicaCount: true) + + } + + def newState = "blue" + def currentState = "green" + + stage("Promote To ${env.STAGE3}") { + + sh "oc get route ${env.APP_NAME} -n ${env.STAGE3} -o jsonpath='{ .spec.to.name }' --loglevel=4 > activeservice" + activeService = readFile('activeservice').trim() + println("Current active service:" + activeService) + if (activeService == "${env.APP_NAME}-blue") { + newState = "green" + currentState = "blue" + } + + sh """ + ${env.OC_CMD} tag ${env.STAGE1}/${env.APP_NAME}:'latest' ${env.STAGE3}/${env.APP_NAME}-${newState}:${version} + """ + sh "${env.OC_CMD} patch dc ${env.APP_NAME}-${newState} --patch '{\"spec\": { \"triggers\": [ { \"type\": \"ImageChange\", \"imageChangeParams\": { \"containerNames\": [ \"${env.APP_NAME}-${newState}\" ], \"from\": { \"kind\": \"ImageStreamTag\", \"namespace\": \"${env.STAGE3}\", \"name\": \"${env.APP_NAME}-${newState}:${version}\"}}}]}}' -n ${env.STAGE3}" + + openshiftDeploy (apiURL: "${ocpApiServer}", authToken: "${env.TOKEN}", depCfg: "${env.APP_NAME}-${newState}", namespace: "${env.STAGE3}", waitTime: '300', waitUnit: 'sec') + + } + + stage("Verify Deployment to ${env.STAGE3}") { + + openshiftVerifyDeployment(deploymentConfig: "${env.APP_NAME}-${newState}", namespace: "${STAGE3}", verifyReplicaCount: true) + println "Application ${env.APP_NAME}-${newState} is now in Production!" + + input "Switch ${env.STAGE3} form ${currentState} to ${newState} deployment?" + + // Switch Route to new active c + sh "oc patch route ${env.APP_NAME} --patch '{\"spec\": { \"to\": { \"name\": \"${env.APP_NAME}-${newState}\"}}}' -n ${env.STAGE3}" + println("Route switched to: " + newState) + + } +} + + +// Convenience Functions to read variables from the pom.xml +// Do not change anything below this line. +def getVersionFromPom(pom) { + def matcher = readFile(pom) =~ '(.+)' + matcher ? matcher[0][1] : null +} +def getGroupIdFromPom(pom) { + def matcher = readFile(pom) =~ '(.+)' + matcher ? matcher[0][1] : null +} +def getArtifactIdFromPom(pom) { + def matcher = readFile(pom) =~ '(.+)' + matcher ? matcher[0][1] : null +} diff --git a/blue-green-spring/projects/projects.yml b/blue-green-spring/projects/projects.yml new file mode 100644 index 00000000..a0026032 --- /dev/null +++ b/blue-green-spring/projects/projects.yml @@ -0,0 +1,21 @@ +apiVersion: v1 +kind: List +items: +- kind: ProjectRequest + apiVersion: v1 + metadata: + name: simple-spring-boot-dev + creationTimestam: null + displayName: Dev - Spring Boot Blue Green +- kind: ProjectRequest + apiVersion: v1 + metadata: + name: simple-spring-boot-stage + creationTimestam: null + displayName: Staging - Spring Boot Blue Green +- kind: ProjectRequest + apiVersion: v1 + metadata: + name: simple-spring-boot-prod + creationTimestam: null + displayName: Prod - Spring Boot Blue Green From baa0c2af4855dda7d7d45774f3831a6c17b3e0a6 Mon Sep 17 00:00:00 2001 From: malacourse Date: Fri, 1 Sep 2017 11:21:44 -0400 Subject: [PATCH 4/6] Revert due to issue with tag plugin --- basic-tomcat/pipeline.groovy | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/basic-tomcat/pipeline.groovy b/basic-tomcat/pipeline.groovy index 7aa265ad..42bc591e 100644 --- a/basic-tomcat/pipeline.groovy +++ b/basic-tomcat/pipeline.groovy @@ -66,28 +66,26 @@ node('maven') { openshiftVerifyDeployment(deploymentConfig: "${env.APP_NAME}", namespace: "${STAGE1}", verifyReplicaCount: true) - //input "Promote Application to ${env.STAGE2}?" + input "Promote Application to Stage?" } stage("Promote To ${env.STAGE2}") { - openshiftTag (alias: 'true', apiURL: "${ocpApiServer}", - authToken: "${env.TOKEN}", destStream: "${env.APP_NAME}", - destTag: 'latest', destinationAuthToken: "${env.TOKEN}", destinationNamespace: "${env.STAGE2}", - namespace: "${env.STAGE1}", srcStream: "${env.APP_NAME}", srcTag: 'latest', verbose: 'false') + sh """ + ${env.OC_CMD} tag ${env.STAGE1}/${env.APP_NAME}:latest ${env.STAGE2}/${env.APP_NAME}:latest + """ } stage("Verify Deployment to ${env.STAGE2}") { openshiftVerifyDeployment(deploymentConfig: "${env.APP_NAME}", namespace: "${STAGE2}", verifyReplicaCount: true) - //input "Promote Application to ${env.STAGE3}?" + input "Promote Application to Prod?" } stage("Promote To ${env.STAGE3}") { - openshiftTag (alias: 'true', apiURL: "${ocpApiServer}", - authToken: "${env.TOKEN}", destStream: "${env.APP_NAME}", - destTag: 'latest', destinationAuthToken: "${env.TOKEN}", destinationNamespace: "${env.STAGE3}", - namespace: "${env.STAGE2}", srcStream: "${env.APP_NAME}", srcTag: 'latest', verbose: 'false') + sh """ + ${env.OC_CMD} tag ${env.STAGE2}/${env.APP_NAME}:latest ${env.STAGE3}/${env.APP_NAME}:latest + """ } stage("Verify Deployment to ${env.STAGE3}") { From 072b34f74ea8a03ba6a0b4584513ab56f5a773b0 Mon Sep 17 00:00:00 2001 From: malacourse Date: Wed, 25 Oct 2017 14:24:55 -0400 Subject: [PATCH 5/6] Updated readme created new routes --- blue-green-spring/README.md | 6 ++-- .../simple-spring-boot-template-prod.yml | 30 ++++++++++++++++++- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/blue-green-spring/README.md b/blue-green-spring/README.md index d6b20819..2cf321c2 100644 --- a/blue-green-spring/README.md +++ b/blue-green-spring/README.md @@ -1,6 +1,8 @@ # A Sample OpenShift Pipeline for Blue Green deployments -This example demonstrates how to implement a full end-to-end Jenkins Pipeline for a Java application in a Blue/Green deployment in the OpenShift Container Platform. This sample demonstrates the following capabilities: +This example demonstrates how to implement a full end-to-end Jenkins Pipeline for a Java application in a Blue/Green deployment in the OpenShift Container Platform. The pipleine will create two instances of the applicaiton in the Production namespace. There will be three routes in the namespace; a blue, green and blue-green route. The blue-green route will switch to the latest deployment when the pipeline completes. This allows for tesing of the new deployment prior to switching live traffic. Also, the previous deployment can be used to compmare the previous deployment. + +This sample demonstrates the following capabilities: * Deploying an integrated Jenkins server inside of OpenShift * Running both custom and oob Jenkins slaves as pods in OpenShift @@ -118,7 +120,7 @@ This template should be instantiated once in each of the lower level namespaces A production blue/green_deploy template_ is provided at `deploy/simple-spring-boot-template-prod.yml` that defines all of the resources required to run the openjdk8 application. It includes: * Two `Service's` -* A `Route` +* Three `Route's` a blue route, green route and main route that switches between the two deployments/services. * Two `ImageStream's` * Two `DeploymentConfig's` * A `RoleBinding` to allow Jenkins to deploy in each namespace. diff --git a/blue-green-spring/deploy/simple-spring-boot-template-prod.yml b/blue-green-spring/deploy/simple-spring-boot-template-prod.yml index 62067ab4..3c1d44d4 100644 --- a/blue-green-spring/deploy/simple-spring-boot-template-prod.yml +++ b/blue-green-spring/deploy/simple-spring-boot-template-prod.yml @@ -45,7 +45,7 @@ objects: kind: Route metadata: annotations: - description: Route for application's http service. + description: Current live route for application's http service. labels: application: ${APPLICATION_NAME} name: ${APPLICATION_NAME} @@ -54,6 +54,34 @@ objects: host: ${HOSTNAME_HTTP} to: name: ${APPLICATION_NAME}-blue +- apiVersion: v1 + id: ${APPLICATION_NAME}-http + kind: Route + metadata: + annotations: + description: Route for the blue deployment. + labels: + application: ${APPLICATION_NAME} + name: ${APPLICATION_NAME}-blue + namespace: ${NAMESPACE} + spec: + host: ${HOSTNAME_HTTP} + to: + name: ${APPLICATION_NAME}-blue +- apiVersion: v1 + id: ${APPLICATION_NAME}-http + kind: Route + metadata: + annotations: + description: Route for green deployment. + labels: + application: ${APPLICATION_NAME} + name: ${APPLICATION_NAME}-green + namespace: ${NAMESPACE} + spec: + host: ${HOSTNAME_HTTP} + to: + name: ${APPLICATION_NAME}-green - apiVersion: v1 kind: ImageStream metadata: From 7c7536e2b4469b25fc37142bdc1f3a294c81986c Mon Sep 17 00:00:00 2001 From: Michael LaCourse Date: Tue, 14 Nov 2017 17:59:09 -0500 Subject: [PATCH 6/6] Fix dir path --- blue-green-spring/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blue-green-spring/README.md b/blue-green-spring/README.md index 2cf321c2..2c4258e5 100644 --- a/blue-green-spring/README.md +++ b/blue-green-spring/README.md @@ -17,7 +17,7 @@ This sample demonstrates the following capabilities: Run the following commands to instantiate this example. ``` -cd ./simple-spring-boot +cd ./blue-green-spring oc create -f projects/projects.yml oc process openshift//jenkins-ephemeral | oc apply -f- -n simple-spring-boot-dev oc process -f deploy/simple-spring-boot-template.yml --param-file=deploy/dev/params | oc apply -f-