diff --git a/assets/images/declareative-config.png b/assets/images/declareative-config.png new file mode 100644 index 0000000..bfc1ccf Binary files /dev/null and b/assets/images/declareative-config.png differ diff --git a/assets/images/scaleable-jenkins.png b/assets/images/scaleable-jenkins.png new file mode 100644 index 0000000..765626d Binary files /dev/null and b/assets/images/scaleable-jenkins.png differ diff --git a/categories/devops.md b/categories/devops.md index 46dcbf7..3fb7ee6 100644 --- a/categories/devops.md +++ b/categories/devops.md @@ -6,12 +6,139 @@ - Travis - Buildkite -## Everything is code -- Infrastructure as code -- CI config as code -- Dockerfile +### Scaleable Jenkins architecture + +![declarative jobs](https://raw.githubusercontent.com/wahyd4/knowledge-mind-mapping/master/assets/images/declareative-config.png) +![scaleable jenkins](https://raw.githubusercontent.com/wahyd4/knowledge-mind-mapping/master/assets/images/scaleable-jenkins.png) + + +- Use external storage plugin to host artifacts +- Store job/pipeline configurations in code repository +- Use Docker container for each stage, which means to need to install various dependencies on agents +- Scale up/down(On-demand) agents when needed to save costs. + +### A sample jenkins job configuration file + +From + +``` +pipeline { + + environment { + PROJECT = "REPLACE_WITH_YOUR_PROJECT_ID" + APP_NAME = "gceme" + FE_SVC_NAME = "${APP_NAME}-frontend" + CLUSTER = "jenkins-cd" + CLUSTER_ZONE = "us-east1-d" + IMAGE_TAG = "gcr.io/${PROJECT}/${APP_NAME}:${env.BRANCH_NAME}.${env.BUILD_NUMBER}" + JENKINS_CRED = "${PROJECT}" + } + + agent { + kubernetes { + label 'sample-app' + defaultContainer 'jnlp' + yaml """ +apiVersion: v1 +kind: Pod +metadata: +labels: + component: ci +spec: + # Use service account that can deploy to all namespaces + serviceAccountName: cd-jenkins + containers: + - name: golang + image: golang:1.10 + command: + - cat + tty: true + - name: gcloud + image: gcr.io/cloud-builders/gcloud + command: + - cat + tty: true + - name: kubectl + image: gcr.io/cloud-builders/kubectl + command: + - cat + tty: true +""" +} + } + stages { + stage('Test') { + steps { + container('golang') { + sh """ + ln -s `pwd` /go/src/sample-app + cd /go/src/sample-app + go test + """ + } + } + } + stage('Build and push image with Container Builder') { + steps { + container('gcloud') { + sh "PYTHONUNBUFFERED=1 gcloud builds submit -t ${IMAGE_TAG} ." + } + } + } + stage('Deploy Canary') { + // Canary branch + when { branch 'canary' } + steps { + container('kubectl') { + // Change deployed image in canary to the one we just built + sh("sed -i.bak 's#gcr.io/cloud-solutions-images/gceme:1.0.0#${IMAGE_TAG}#' ./k8s/canary/*.yaml") + step([$class: 'KubernetesEngineBuilder',namespace:'production', projectId: env.PROJECT, clusterName: env.CLUSTER, zone: env.CLUSTER_ZONE, manifestPattern: 'k8s/services', credentialsId: env.JENKINS_CRED, verifyDeployments: false]) + step([$class: 'KubernetesEngineBuilder',namespace:'production', projectId: env.PROJECT, clusterName: env.CLUSTER, zone: env.CLUSTER_ZONE, manifestPattern: 'k8s/canary', credentialsId: env.JENKINS_CRED, verifyDeployments: true]) + sh("echo http://`kubectl --namespace=production get service/${FE_SVC_NAME} -o jsonpath='{.status.loadBalancer.ingress[0].ip}'` > ${FE_SVC_NAME}") + } + } + } + stage('Deploy Production') { + // Production branch + when { branch 'master' } + steps{ + container('kubectl') { + // Change deployed image in canary to the one we just built + sh("sed -i.bak 's#gcr.io/cloud-solutions-images/gceme:1.0.0#${IMAGE_TAG}#' ./k8s/production/*.yaml") + step([$class: 'KubernetesEngineBuilder',namespace:'production', projectId: env.PROJECT, clusterName: env.CLUSTER, zone: env.CLUSTER_ZONE, manifestPattern: 'k8s/services', credentialsId: env.JENKINS_CRED, verifyDeployments: false]) + step([$class: 'KubernetesEngineBuilder',namespace:'production', projectId: env.PROJECT, clusterName: env.CLUSTER, zone: env.CLUSTER_ZONE, manifestPattern: 'k8s/production', credentialsId: env.JENKINS_CRED, verifyDeployments: true]) + sh("echo http://`kubectl --namespace=production get service/${FE_SVC_NAME} -o jsonpath='{.status.loadBalancer.ingress[0].ip}'` > ${FE_SVC_NAME}") + } + } + } + stage('Deploy Dev') { + // Developer Branches + when { + not { branch 'master' } + not { branch 'canary' } + } + steps { + container('kubectl') { + // Create namespace if it doesn't exist + sh("kubectl get ns ${env.BRANCH_NAME} || kubectl create ns ${env.BRANCH_NAME}") + // Don't use public load balancing for development branches + sh("sed -i.bak 's#LoadBalancer#ClusterIP#' ./k8s/services/frontend.yaml") + sh("sed -i.bak 's#gcr.io/cloud-solutions-images/gceme:1.0.0#${IMAGE_TAG}#' ./k8s/dev/*.yaml") + step([$class: 'KubernetesEngineBuilder',namespace: "${env.BRANCH_NAME}", projectId: env.PROJECT, clusterName: env.CLUSTER, zone: env.CLUSTER_ZONE, manifestPattern: 'k8s/services', credentialsId: env.JENKINS_CRED, verifyDeployments: false]) + step([$class: 'KubernetesEngineBuilder',namespace: "${env.BRANCH_NAME}", projectId: env.PROJECT, clusterName: env.CLUSTER, zone: env.CLUSTER_ZONE, manifestPattern: 'k8s/dev', credentialsId: env.JENKINS_CRED, verifyDeployments: true]) + echo 'To access your environment run `kubectl proxy`' + echo "Then access your service via http://localhost:8001/api/v1/proxy/namespaces/${env.BRANCH_NAME}/services/${FE_SVC_NAME}:80/" + } + } + } + } +} +``` ## CD + +### Tools Comparision + ![OSI Models](https://raw.githubusercontent.com/wahyd4/knowledge-mind-mapping/master/assets/images/iac.png) ### An example CI/CD flow @@ -28,6 +155,20 @@ F --Manual/Auto trigger--> G[Update manifest] G --> H((Deploy to Next env)) ``` +### Spinnaker + +Spinnaker is an open-source, multi-cloud continuous delivery platform that helps you release software changes with high velocity and confidence. Spinnaker provides two core sets of features: + + application management + + application deployment + +## Everything is code + +- Infrastructure as code +- CI config as code +- Dockerfile + ## Automation tools ### Capistrano