DevOps平台
- 2019 年 10 月 3 日
- 筆記
DevOps定義(來自維基百科): DevOps(Development和Operations的組合詞)是一種重視「軟體開發人員(Dev)」和「IT運維技術人員(Ops)」之間溝通合作的文化、運動或慣例。透過自動化「軟體交付」和「架構變更」的流程,來使得構建、測試、發布軟體能夠更加地快捷、頻繁和可靠。
公司技術部目前幾百人左右吧,但是整個技術棧還是比較落後的,尤其是DevOps、容器這一塊,需要將全線打通,當時進來也主要是負責DevOps這一塊的工作,應該說也是沒怎麼做好,其中也走了不少彎路,下面主要是自己踩過的坑吧。
一、自由風格的軟體項目
主要還是基於jenkins裡面構建一個自由風格的軟體項目,當時參考的是阿里的codepipeline,就是對jenkins封裝一層,包括創建job、立即構建、獲取構建進度等都進行封裝,並將需要的東西進行存庫,沒有想到碼程式碼的時候,一堆的坑,比如: 1.連續點擊立即構建,jenkins是不按順序返回的,(分散式鎖解決) 2.跨域調用,csrf,這個還好,不過容易把jenkins搞的無法登錄(注意配置,具體可以點擊這裡) 3.創建job的時候只支援xml格式,還要轉換一下,超級坑(xstream強行轉換) 4.docker構建的時候,需要掛載宿主機的docker(想過用遠程的,但效率不高) 5.資料庫與jenkins的job一致性問題,任務創建失敗,批量刪除太慢(目前沒想好怎麼解決) 6.由於使用了資料庫,需要檢測job是否構建完成,為了自定義參數,我們自寫了個通知插件,將構建狀態返回到kafka,然後管理平台在進行消息處理。
完成了以上的東西,不過由於太過於簡單,導致只能進行單條線的CICD,而且CI僅僅實現了打包,沒有將CD的過程一同串列起來。簡單來說就是,用戶點擊了構建只是能夠打出一個鏡像,但是如果要部署到kubernetes,還是需要在應用裏手動更換一下鏡像版本。總體而言,這個版本的jenkins我們使用的還是單點的,不足以支撐構建量比較大的情況,甚至如果當前服務掛了,斷網了,整一塊的構建功能都不能用。
<project> <actions/> <description>xxx</description> <properties> <hudson.model.ParametersDefinitionProperty> <parameterDefinitions> <hudson.model.TextParameterDefinition> <name>buildParam</name> <defaultValue>v1</defaultValue> </hudson.model.TextParameterDefinition> <hudson.model.TextParameterDefinition> <name>codeBranch</name> <defaultValue>master</defaultValue> </hudson.model.TextParameterDefinition> </parameterDefinitions> </hudson.model.ParametersDefinitionProperty> </properties> <scm class="hudson.plugins.git.GitSCM"> <configVersion>2</configVersion> <userRemoteConfigs> <hudson.plugins.git.UserRemoteConfig> <url>http://xxxxx.git</url> <credentialsId>002367566a4eb4bb016a4eb723550054</credentialsId> </hudson.plugins.git.UserRemoteConfig> </userRemoteConfigs> <branches> <hudson.plugins.git.BranchSpec> <name>${codeBranch}</name> </hudson.plugins.git.BranchSpec> </branches> <doGenerateSubmoduleConfigurations>false</doGenerateSubmoduleConfigurations> <extensions/> </scm> <builders> <hudson.tasks.Shell> <command>ls</command> </hudson.tasks.Shell> <hudson.tasks.Maven> <targets>clean package install -Dmaven.test.skip=true</targets> <mavenName>mvn3.5.4</mavenName> </hudson.tasks.Maven> <com.cloudbees.dockerpublish.DockerBuilder> <server> <uri>unix:///var/run/docker.sock</uri> </server> <registry> <url>http://xxxx</url> </registry> <repoName>xxx/xx</repoName> <forcePull>true</forcePull> <dockerfilePath>Dockerfile</dockerfilePath> <repoTag>${buildParam}</repoTag> <skipTagLatest>true</skipTagLatest> </com.cloudbees.dockerpublish.DockerBuilder> </builders> <publishers> <com.xxxx.notifications.Notifier/> </publishers> </project>
二、優化之後的CICD
上面的過程也仍然沒有沒住DevOps的流程,人工干預的東西依舊很多,由於上級急需產出,所以只能將就著繼續下去。我們將構建、部署每個當做一個小塊,一個CICD的過程可以選擇構建、部署,花了很大的精力,完成了串列化的別樣的CICD。 以下圖為例,整個流程的底層為:paas平台-jenkins-kakfa-管理平台(選擇cicd的下一步)-kafka-cicd組件調用管理平台觸發構建-jenkins-kafka-管理平台(選擇cicd的下一步)-kafka-cicd組件調用管理平台觸發部署。
目前實現了串列化的CICD構建部署,之後考慮實現多個CICD並行,並且一個CICD能夠調用另一個CICD,實際運行中,出現了一大堆問題。由於經過的組件太多,一次cicd的運行報錯,卻很難排查到問題出現的原因,業務方的投訴也開始慢慢多了起來,只能說勸導他們不要用這個功能。
沒有CICD,就無法幫助公司上容器雲,無法合理的利用容器雲的特性,更無法走上雲原生的道路。於是,我們決定另謀出路。
三、調研期
由於之前的CICD問題太多,特別是經過的組件太多了,導致出現問題的時候無法正常排查,為了能夠更加穩定可靠,還是決定了要更換一下底層。 我們重新審視了下pipeline,覺得這才是正確的做法,可惜不知道如果做成一個產品樣子的東西,用戶方Dockerfile都不怎麼會寫,你讓他寫一個Jenkinsfile?不合理!在此之外,我們看到了serverless jenkins、Google的tekton。 GitLab-CICD Gitlab中自帶了cicd的工具,需要配置一下runner,然後配置一下.gitlab-ci.yml寫一下程式的cicd過程即可,構建鏡像的時候我們使用的是kaniko,整個gitlab的cicd在我們公司小項目中大範圍使用,但是學習成本過高,尤其是引入了kaniko之後,還是尋找一個產品化的CICD方案。
分散式構建jenkins x 首先要解決的是多個構建同時運行的問題,很久之前就調研過jenkins x,它必須要使用在kubernetes上,由於當時官方文檔不全,而且我們的DevOps項目處於初始期,所有沒有使用。jenkins的master slave結構就不多說了。jenkins x應該說是個全家桶,包含了helm倉庫、nexus倉庫、docker registry等,程式碼是jenkins-x-image。
serverless jenkins 好像跟Google的tekton相關,用了下,沒調通,只能用於GitHub。感覺還不如直接使用tekton。
阿里云云效 提供了圖形化配置DevOps流程,支援定時觸發,可惜沒有跟gitlab觸髮結合,如果需要個公司級的DevOps,需要將公司的jira、gitlab、jenkins等集合起來,但是圖形化jenkins pipeline是個特別好的參考方向,可以結合阿里云云效來做一個自己的DevOps產品。
微軟Pipeline 微軟也是提供了DevOps解決方案的,也是提供了yaml格式的寫法,即:在右邊填寫完之後會轉化成yaml。如果想把DevOps打造成一款產品,這樣的設計顯然不是最好的。
Googletekton kubernetes的官方cicd,目前已用於kubernetes的release發版過程,目前也僅僅是與GitHub相結合,gitlab無法使用,全過程可使用yaml文件來創建,跑起來就是類似kubernetes的job一樣,用完即銷毀,可惜目前比較新,依舊處於alpha版本,無法用於生產。有興趣可以參考下:Knative 初體驗:CICD 極速入門
四、產品化後的DevOps平台
在調研DockOne以及各個產商的DevOps產品時,發現,真的只有阿里雲的雲效才是真正比較完美的DevOps產品,用戶不需要知道pipeline的語法,也不需要掌握kubernetes的相關知識,甚至不用寫yaml文件,對於開發、測試來說簡直就是神一樣的存在了。雲效對小公司(創業公司)免費,但是有一定的量之後,就要開始收費了。在調研了一番雲效的東西之後,發現雲效也是基於jenkins x改造的,不過阿里畢竟人多,雖然能約莫看出是pipeline的語法,但是阿里徹底改造成了能夠使用yaml來與後台交互。 下面是以阿里雲的雲效介面以及配合jenkins的pipeline語法來講解:
4.1 Java程式碼掃描
PMD是一款可拓展的靜態程式碼分析器它不僅可以對程式碼分析器,它不僅可以對程式碼風格進行檢查,還可以檢查設計、多執行緒、性能等方面的問題。阿里雲的是簡單的集成了一下而已,對於我們來說,底層使用了sonar來接入,所有的程式碼掃描結果都接入了sonar。
stage('Clone') { steps{ git branch: 'master', credentialsId: 'xxxx', url: "xxx" } } stage('check') { steps{ container('maven') { echo "mvn pmd:pmd" } } }
4.2 Java單元測試
Java的單元測試一般用的是Junit,在阿里雲中,使用了surefire插件,用來在maven構建生命周期的test phase執行一個應用的單元測試。它會產生兩種不同形式的測試結果報告。我這裡就簡單的過一下,使用"mvn test"命令來代替。
stage('Clone') { steps{ echo "1.Clone Stage" git branch: 'master', credentialsId: 'xxxxx', url: "xxxxxx" } } stage('test') { steps{ container('maven') { sh "mvn test" } } }
4.3 Java構建並上傳鏡像
鏡像的構建比較想使用kaniko,嘗試找了不少方法,到最後還是只能使用dind(docker in docker),掛載宿主機的docker來進行構建,如果能有其他方案,希望能提醒下。目前jenkins x使用的是dind,掛載的時候需要配置一下config.json,然後掛載到容器的/root/.docker目錄,才能在容器中使用docker。
為什麼不推薦dind:掛載了宿主機的docker,就可以使用docker ps查看正在運行的容器,也就意味著可以使用docker stop、docker rm來控制宿主機的容器,雖然kubernetes會重新調度起來,但是這一段的重啟時間極大的影響業務。
stage('下載程式碼') { steps{ echo "1.Clone Stage" git branch: 'master', credentialsId: 'xxxxx', url: "xxxxxx" script { build_tag = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim() } } } stage('打包並構建鏡像') { steps{ container('maven') { echo "3.Build Docker Image Stage" sh "mvn clean install -Dmaven.test.skip=true" sh "docker build -f xxx/Dockerfile -t xxxxxx:${build_tag} ." sh "docker push xxxxxx:${build_tag}" } } }
4.4 部署到阿里雲k8s
CD過程有點困難,由於我們的kubernetes平台是圖形化的,類似於阿里雲,用戶根本不需要自己寫deployment,只需要在圖形化介面做一下操作即可部署。對於CD過程來說,如果應用存在的話,就可以直接替換掉鏡像版本即可,如果沒有應用,就提供個簡單的介面讓用戶新建應用。當然,在容器最初推行的時候,對於用戶來說,一下子需要接受docker、kubernetes、helm等概念是十分困難的,不能一個一個幫他們寫deployment這些yaml文件,只能用helm創建一個通用的spring boot或者其他的模板,然後讓業務方修改自己的配置,每次構建的時候只需要替換鏡像即可。
def tmp = sh ( returnStdout: true, script: "kubectl get deployment -n ${namespace} | grep ${JOB_NAME} | awk '{print $1}'" ) //如果是第一次,則使用helm模板創建,創建完後需要去epaas修改pod的配置 if(tmp.equals('')){ sh "helm init --client-only" sh """helm repo add mychartmuseum http://xxxxxx --username myuser --password=mypass""" sh """helm install --set name=${JOB_NAME} --set namespace=${namespace} --set deployment.image=${image} --set deployment.imagePullSecrets=${harborProject} --name ${namespace}-${JOB_NAME} mychartmuseum/soa-template""" }else{ println "已經存在,替換鏡像" //epaas中一個pod的容器名稱需要帶上"-0"來區分 sh "kubectl set image deployment/${JOB_NAME} ${JOB_NAME}-0=${image} -n ${namespace}" }
4.5 整體流程
程式碼掃描,單元測試,構建鏡像三個並行運行,等三個完成之後,在進行部署
pipeline:
pipeline { agent { label "jenkins-maven" } stages{ stage('程式碼掃描,單元測試,鏡像構建'){ parallel { stage('並行任務一') { agent { label "jenkins-maven" } stages('Java程式碼掃描') { stage('Clone') { steps{ git branch: 'master', credentialsId: 'xxxxxxx', url: "xxxxxxx" } } stage('check') { steps{ container('maven') { echo "$BUILD_NUMBER" } } } } } stage('並行任務二') { agent { label "jenkins-maven" } stages('Java單元測試') { stage('Clone') { steps{ echo "1.Clone Stage" git branch: 'master', credentialsId: 'xxxxxxx', url: "xxxxxxx" } } stage('test') { steps{ container('maven') { echo "3.Build Docker Image Stage" sh "mvn -v" } } } } } stage('並行任務三') { agent { label "jenkins-maven" } stages('java構建鏡像') { stage('Clone') { steps{ echo "1.Clone Stage" git branch: 'master', credentialsId: 'xxxxxxx', url: "xxxxxxx" script { build_tag = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim() } } } stage('Build') { steps{ container('maven') { echo "3.Build Docker Image Stage" sh "mvn clean install -Dmaven.test.skip=true" sh "docker build -f epaas-portal/Dockerfile -t hub.gcloud.lab/rongqiyun/epaas:${build_tag} ." sh "docker push hub.gcloud.lab/rongqiyun/epaas:${build_tag}" } } } } } } } stage('部署'){ stages('部署到容器雲') { stage('check') { steps{ container('maven') { script{ if (deploy_app == "true"){ def tmp = sh ( returnStdout: true, script: "kubectl get deployment -n ${namespace} | grep ${JOB_NAME} | awk '{print $1}'" ) //如果是第一次,則使用helm模板創建,創建完後需要去epaas修改pod的配置 if(tmp.equals('')){ sh "helm init --client-only" sh """helm repo add mychartmuseum http://xxxxxx --username myuser --password=mypass""" sh """helm install --set name=${JOB_NAME} --set namespace=${namespace} --set deployment.image=${image} --set deployment.imagePullSecrets=${harborProject} --name ${namespace}-${JOB_NAME} mychartmuseum/soa-template""" }else{ println "已經存在,替換鏡像" //epaas中一個pod的容器名稱需要帶上"-0"來區分 sh "kubectl set image deployment/${JOB_NAME} ${JOB_NAME}-0=${image} -n ${namespace}" } }else{ println "用戶選擇不部署程式碼" } } } } } } } } }
在jenkins x中查看:
4.4 日誌
jenkins blue ocean步驟日誌:
雲效中的日誌:
4.5 定時觸發
triggers { cron('H H * * *') //每天 }
五、其他
5.1 Gitlab觸發
pipeline中除了有對於時間的trigger,還支援了gitlab的觸發,需要各種配置,不過如果真的對於gitlab的cicd有要求,直接使用gitlab-ci會更好,我們同時也對gitlab進行了runner的配置來支援gitlab的cicd。gitlab的cicd也提供了構建完後即銷毀的過程。
六、總結
功能最強大的過程莫過於自己使用pipeline腳本實現,選取最適合自己的,但是對於一個公司來說,如果要求業務方來掌握這些,特別是IT流動性大的時候,既需要重新培訓,同個問題又會被問多遍,所以,只能將DevOps實現成一個圖形化的東西,方便,簡單,相對來說功能還算強大。
DevOps最難的可能都不是以上這些,關鍵是讓用戶接受,容器雲最初推行時,公司原本傳統的很多發版方式都需要進行改變,有些業務方不願意改,或者有些程式碼把持久化的東西存到了程式碼中而不是分散式存儲里,甚至有些用戶方都不願意維護老程式碼,看都不想看然後想上容器,一個公司在做技術架構的時候,過於混亂到最後填坑要麼需要耗費太多精力甚至大換血。
最後,DevOps是雲原生的必經之路!!!
文章同步:
部落格園:https://www.cnblogs.com/w1570631036/p/11524673.html
個人網站:http://www.wenzhihuai.com/getblogdetail.html?blogid=663
gitbook:https://gitbook.wenzhihuai.com/devops/devops-ping-tai