From b5b7a35f88734aaf89574056eb6c963e0f10da73 Mon Sep 17 00:00:00 2001 From: ebracho Date: Tue, 19 Sep 2017 11:10:32 -0700 Subject: [PATCH] Blue/Green deploy stage --- Jenkinsfile | 195 ++++++++++++++++++-- blue-green/Dockerfile | 7 + blue-green/blue-green-deploy | 66 +++++++ jenkinsfiles/full-demo | 151 --------------- src/main/webapp/resources/css/petclinic.css | 2 +- 5 files changed, 253 insertions(+), 168 deletions(-) create mode 100644 blue-green/Dockerfile create mode 100755 blue-green/blue-green-deploy delete mode 100644 jenkinsfiles/full-demo diff --git a/Jenkinsfile b/Jenkinsfile index 13617b3b9..dfb774536 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,19 +1,182 @@ +#!/bin/env groovy + +@Library('ldop-shared-library@ad8185bec5a1999d144ec6c8fdadf9a62ab0506e') _ + pipeline { - agent none - stages { - stage('Build') { - agent { - docker { - image 'maven:3.5.0' - args '-e INITIAL_ADMIN_USER -e INITIAL_ADMIN_PASSWORD --network=${LDOP_NETWORK_NAME}' - } - } - steps { - configFileProvider( - [configFile(fileId: 'nexus', variable: 'MAVEN_SETTINGS')]) { - sh 'mvn -s $MAVEN_SETTINGS clean deploy -DskipTests=true -B' - } - } - } + agent none + + environment { + IMAGE = "liatrio/petclinic-tomcat" + } + + stages { + + stage('Build') { + agent { + docker { + image 'maven:3.5.0' + args '-e INITIAL_ADMIN_USER -e INITIAL_ADMIN_PASSWORD --network=${LDOP_NETWORK_NAME}' + } + } + steps { + configFileProvider( + [configFile(fileId: 'nexus', variable: 'MAVEN_SETTINGS')]) { + sh 'mvn -s $MAVEN_SETTINGS clean deploy -DskipTests=true -B' + } + } } + + stage('Sonar') { + agent { + docker { + image 'sebp/sonar-runner' + args '-e SONAR_ACCOUNT_LOGIN -e SONAR_ACCOUNT_PASSWORD -e SONAR_DB_URL -e SONAR_DB_LOGIN -e SONAR_DB_PASSWORD --network=${LDOP_NETWORK_NAME}' + } + } + steps { + sh '/opt/sonar-runner-2.4/bin/sonar-runner -e -D sonar.login=${SONAR_ACCOUNT_LOGIN} -D sonar.password=${SONAR_ACCOUNT_PASSWORD} -D sonar.jdbc.url=${SONAR_DB_URL} -D sonar.jdbc.username=${SONAR_DB_LOGIN} -D sonar.jdbc.password=${SONAR_DB_PASSWORD}' + } + } + + stage('Get Artifact') { + agent { + docker { + image 'maven:3.5.0' + args '-e INITIAL_ADMIN_USER -e INITIAL_ADMIN_PASSWORD --network=${LDOP_NETWORK_NAME}' + } + } + steps { + sh 'mvn clean' + script { + pom = readMavenPom file: 'pom.xml' + getArtifact(pom.groupId, pom.artifactId, pom.version, 'petclinic') + } + } + } + + stage('Build container') { + agent any + steps { + script { + if ( env.BRANCH_NAME == 'master' ) { + pom = readMavenPom file: 'pom.xml' + TAG = pom.version + } else { + TAG = env.BRANCH_NAME + } + sh "docker build -t ${env.IMAGE}:${TAG} ." + } + } + } + + stage('Run local container') { + agent any + steps { + sh 'docker rm -f petclinic-tomcat-temp || true' + sh "docker run -d --network=${LDOP_NETWORK_NAME} --name petclinic-tomcat-temp ${env.IMAGE}:${TAG}" + } + } + + stage('Smoke-Test & OWASP Security Scan') { + agent { + docker { + image 'maven:3.5.0' + args '--network=${LDOP_NETWORK_NAME}' + } + } + steps { + sh "cd regression-suite && mvn clean -B test -DPETCLINIC_URL=http://petclinic-tomcat-temp:8080/petclinic/" + } + } + stage('Stop local container') { + agent any + steps { + sh 'docker rm -f petclinic-tomcat-temp || true' + } + } + + stage('Push to dockerhub') { + agent any + steps { + withCredentials([usernamePassword(credentialsId: 'dockerhub', passwordVariable: 'dockerPassword', usernameVariable: 'dockerUsername')]){ + script { + sh "docker login -u ${env.dockerUsername} -p ${env.dockerPassword}" + sh "docker push ${env.IMAGE}:${TAG}" + } + } + } + } + + stage('Deploy to dev') { + agent any + steps { + script { + deployToEnvironment("ec2-user", "dev.petclinic.liatr.io", "petclinic-deploy-key", env.IMAGE, TAG, "spring-petclinic", "dev.petclinic.liatr.io") + } + } + } + + stage('Smoke test dev') { + agent { + docker { + image 'maven:3.5.0' + args '--network=${LDOP_NETWORK_NAME}' + } + } + steps { + sh "cd regression-suite && mvn clean -B test -DPETCLINIC_URL=https://dev.petclinic.liatr.io/petclinic" + echo "Should be accessible at https://dev.petclinic.liatr.io/petclinic" + } + } + + stage('Deploy to qa') { + when { + branch 'master' + } + agent any + steps { + deployToEnvironment("ec2-user", "qa.petclinic.liatr.io", "petclinic-deploy-key", env.IMAGE, TAG, "spring-petclinic", "qa.petclinic.liatr.io") + } + } + + stage('Smoke test qa') { + when { + branch 'master' + } + agent { + docker { + image 'maven:3.5.0' + args '--network=${LDOP_NETWORK_NAME}' + } + } + steps { + sh "cd regression-suite && mvn clean -B test -DPETCLINIC_URL=https://qa.petclinic.liatr.io/petclinic" + echo "Should be accessible at https://qa.petclinic.liatr.io/petclinic" + input 'Deploy to Prod?' + } + } + + stage('Blue/Green deploy to prod') { + /* + when { + branch 'master' + } + */ + agent { + dockerfile { + filename "blue-green/Dockerfile" + } + } + steps { + withCredentials([ + usernamePassword(credentialsId: 'aws', usernameVariable: 'AWS_ACCESS_KEY_ID', passwordVariable: 'AWS_SECRET_ACCESS_KEY'), + file(credentialsId: 'petclinic-deploy-key', variable: 'DEPLOY_KEY_PATH') + ]) { + script { + sh "TAG=${TAG} blue-green/blue-green-deploy" + } + } + } + } + } } diff --git a/blue-green/Dockerfile b/blue-green/Dockerfile new file mode 100644 index 000000000..0d4d6e886 --- /dev/null +++ b/blue-green/Dockerfile @@ -0,0 +1,7 @@ +FROM maven:3.5.0 + +RUN apt-get update -y && apt-get install -y awscli jq + +# Jenkins workspace runs with UID 1000 +RUN useradd -ms /bin/bash -u 1000 jenkins +USER jenkins diff --git a/blue-green/blue-green-deploy b/blue-green/blue-green-deploy new file mode 100755 index 000000000..7487d9968 --- /dev/null +++ b/blue-green/blue-green-deploy @@ -0,0 +1,66 @@ +#!/bin/bash -xe + +# This script executes a blue/green deployment on the petclinic production infrastructure. +# +# Production infrastructure consists of: +# - An application load balancer (prod-petclinic) +# - Two target groups (proda-petclinic, prodb-petclinic) +# - A single t2.micro ec2 instance in each target group (proda.petclinic.liatr.io, prodb.petclinic.liatr.io) +# +# One of the two target groups should be attached to the ALB at any given time via a Listener +# +# To perform a blue/green deployment, we: +# - Figure out which target group is detached +# - Deploy the application to each instance in the detached target group (only 1 instance in this case) +# - Run smoke tests on the newly deployed applications +# - Delete the current attached listener +# - Attach a new listener that forwards to the target group we deployed to. +# +# Expected environment variables: +# - DEPLOY_KEY_PATH Private key used to shell deploy to petclinic prod instances +# - AWS_ACCESS_ID_ID +# - AWS_SECRET_ACCESS_KEY For modifying load balancer resources +# - IMAGE Dockerhub repo to deploy from +# - TAG Version of image to deploy +# + +export AWS_DEFAULT_REGION='us-west-2' + +deploy() { + FQDN="$1" + ssh -i ${DEPLOY_KEY_PATH} -o StrictHostKeyChecking=no ec2-user@${FQDN} "docker rm -f petclinic || true" + ssh -i ${DEPLOY_KEY_PATH} -o StrictHostKeyChecking=no ec2-user@${FQDN} "docker run -d -p 80:8080 --name petclinic ${IMAGE}:${TAG}" +} + +smoke_test() { + FQDN="$1" + cd ./regression-suite && mvn clean -B test -DPETCLINIC_URL="${FQDN}" && cd - +} + +LOAD_BALANCER_ARN=$(aws elbv2 describe-load-balancers --names prod-petclinic | jq -r '.LoadBalancers|.[0]|.LoadBalancerArn') +ATTACHED_LISTENER_ARN=$(aws elbv2 describe-listeners --load-balancer-arn "${LOAD_BALANCER_ARN}" | jq -r '.Listeners|.[0]|.ListenerArn') +ATTACHED_LISTENER_TARGET_GROUP_ARN=$(aws elbv2 describe-listeners --load-balancer-arn "${LOAD_BALANCER_ARN}" | jq -r '.Listeners|.[0]|.DefaultActions|.[0]|.TargetGroupArn') +PRODA_TARGET_GROUP_ARN=$(aws elbv2 describe-target-groups --names proda-petclinic | jq -r '.TargetGroups|.[0]|.TargetGroupArn') +PRODB_TARGET_GROUP_ARN=$(aws elbv2 describe-target-groups --names prodb-petclinic | jq -r '.TargetGroups|.[0]|.TargetGroupArn') + +if [[ "${ATTACHED_LISTENER_TARGET_GROUP_ARN}" == "${PRODA_TARGET_GROUP_ARN}" ]]; then + NEW_TARGET_GROUP_ARN=${PRODB_TARGET_GROUP_ARN} + deploy "prodb.petclinic.liatr.io" + smoke_test "http://prodb.petclinic.liatr.io/petclinic" +else + NEW_TARGET_GROUP_ARN=${PRODA_TARGET_GROUP_ARN} + deploy "proda.petclinic.liatr.io" + smoke_test "http://proda.petclinic.liatr.io/petclinic" +fi + +if [[ "${ATTACHED_LISTENER_ARN}" != "null" ]]; then + echo "Deleting previous listener: ${ATTACHED_LISTENER_ARN}" + aws elbv2 delete-listener --listener-arn "${ATTACHED_LISTENER_ARN}" +fi + +echo "Attaching listener with target group: ${NEW_TARGET_GROUP_ARN}" +aws elbv2 create-listener \ + --load-balancer-arn "${LOAD_BALANCER_ARN}" \ + --protocol "HTTP" \ + --port "80" \ + --default-actions "Type=forward,TargetGroupArn=${NEW_TARGET_GROUP_ARN}" diff --git a/jenkinsfiles/full-demo b/jenkinsfiles/full-demo deleted file mode 100644 index fd86271c4..000000000 --- a/jenkinsfiles/full-demo +++ /dev/null @@ -1,151 +0,0 @@ -#!/bin/env groovy -pipeline { - agent none - environment { - IMAGE = "liatrio/petclinic-tomcat" - } - stages { - stage('Build') { - agent { - docker { - image 'maven:3.5.0' - args '-e INITIAL_ADMIN_USER -e INITIAL_ADMIN_PASSWORD --network=${LDOP_NETWORK_NAME}' - } - } - steps { - configFileProvider( - [configFile(fileId: 'nexus', variable: 'MAVEN_SETTINGS')]) { - sh 'mvn -s $MAVEN_SETTINGS clean deploy -DskipTests=true -B' - } - } - } - stage('Sonar') { - agent { - docker { - image 'sebp/sonar-runner' - args '-e SONAR_ACCOUNT_LOGIN -e SONAR_ACCOUNT_PASSWORD -e SONAR_DB_URL -e SONAR_DB_LOGIN -e SONAR_DB_PASSWORD --network=${LDOP_NETWORK_NAME}' - } - } - steps { - sh '/opt/sonar-runner-2.4/bin/sonar-runner -e -D sonar.login=${SONAR_ACCOUNT_LOGIN} -D sonar.password=${SONAR_ACCOUNT_PASSWORD} -D sonar.jdbc.url=${SONAR_DB_URL} -D sonar.jdbc.username=${SONAR_DB_LOGIN} -D sonar.jdbc.password=${SONAR_DB_PASSWORD}' - } - } - stage('Get Artifact') { - agent { - docker { - image 'maven:3.5.0' - args '-e INITIAL_ADMIN_USER -e INITIAL_ADMIN_PASSWORD --network=${LDOP_NETWORK_NAME}' - } - } - steps { - sh 'mvn clean' - script { - pom = readMavenPom file: 'pom.xml' - getArtifact(pom.groupId, pom.artifactId, pom.version) - } - } - } - stage('Build container') { - agent any - steps { - script { - sh "docker build -t liatrio/petclinic-tomcat:${env.BRANCH_NAME} ." - if ( env.BRANCH_NAME == 'master' ) { - pom = readMavenPom file: 'pom.xml' - containerVersion = pom.version - sh "docker build -t liatrio/petclinic-tomcat:${containerVersion} ." - } - } - } - } - stage('Run local container') { - agent any - steps { - sh 'docker rm -f petclinic-tomcat-temp || true' - sh "docker run -d --network=${LDOP_NETWORK_NAME} --name petclinic-tomcat-temp liatrio/petclinic-tomcat:${env.BRANCH_NAME}" - } - } - stage('Smoke-Test & OWASP Security Scan') { - agent { - docker { - image 'maven:3.5.0' - args '--network=${LDOP_NETWORK_NAME}' - } - } - steps { - sh "cd regression-suite && mvn clean -B test -DPETCLINIC_URL=http://petclinic-tomcat-temp:8080/petclinic/" - } - } - stage('Stop local container') { - agent any - steps { - sh 'docker rm -f petclinic-tomcat-temp || true' - } - } - stage('Push to dockerhub') { - agent any - steps { - withCredentials([usernamePassword(credentialsId: 'dockerhub', passwordVariable: 'dockerPassword', usernameVariable: 'dockerUsername')]) { - script { - sh "docker login -u ${env.dockerUsername} -p ${env.dockerPassword}" - if ( env.BRANCH_NAME == 'master' ) { - sh "docker push liatrio/petclinic-tomcat:${containerVersion}" - } - else { - sh "docker push liatrio/petclinic-tomcat:${env.BRANCH_NAME}" - } - } - } - } - } - stage('Deploy to dev') { - agent any - steps { - script { - if ( env.BRANCH_NAME == 'master' ) { - deployToEnvironment("ec2-user", "dev.petclinic.liatr.io", "petclinic-deploy-key", "${env.IMAGE}", "${containerVersion}", "spring-petclinic", "dev.petclinic.liatr.io") - } - else{ - deployToEnvironment("ec2-user", "dev.petclinic.liatr.io", "petclinic-deploy-key", "${env.IMAGE}", "${env.BRANCH_NAME}", "spring-petclinic", "dev.petclinic.liatr.io") - } - } - } - } - stage('Smoke test dev') { - agent { - docker { - image 'maven:3.5.0' - args '--network=${LDOP_NETWORK_NAME}' - } - } - steps { - sh "cd regression-suite && mvn clean -B test -DPETCLINIC_URL=https://dev.petclinic.liatr.io/petclinic" - echo "Should be accessible at https://dev.petclinic.liatr.io/petclinic" - } - } - stage('Deploy to qa') { - when { - branch 'master' - } - agent any - steps { - deployToEnvironment("ec2-user", "qa.petclinic.liatr.io", "petclinic-deploy-key", "${env.IMAGE}", "${containerVersion}", "spring-petclinic", "qa.petclinic.liatr.io") - } - } - stage('Smoke test qa') { - when { - branch 'master' - } - agent { - docker { - image 'maven:3.5.0' - args '--network=${LDOP_NETWORK_NAME}' - } - } - steps { - sh "cd regression-suite && mvn clean -B test -DPETCLINIC_URL=https://qa.petclinic.liatr.io/petclinic" - echo "Should be accessible at https://qa.petclinic.liatr.io/petclinic" - } - } - } -} diff --git a/src/main/webapp/resources/css/petclinic.css b/src/main/webapp/resources/css/petclinic.css index 76a8ef83d..dbf904c07 100644 --- a/src/main/webapp/resources/css/petclinic.css +++ b/src/main/webapp/resources/css/petclinic.css @@ -12,7 +12,7 @@ input[type="text"] { } .navbar .nav > li > a { - color: #000000; + color: #000000 } .form-horizontal .control-label {