首發(fā)于 Jenkins 中文社區(qū)
本文要點:
- 設(shè)計一條 Spring Boot 最基本的流水線:包括構(gòu)建狞谱、制品上傳壳炎、部署哲虾。
- 使用 Docker 容器運(yùn)行構(gòu)建邏輯锦亦。
- 自動化整個實驗環(huán)境:包括 Jenkins 的配置,Jenkins agent 的配置等嫂拴。
1. 代碼倉庫安排
本次實驗涉及以下多個代碼倉庫:
% tree -L 1
├── 1-cd-platform # 實驗環(huán)境相關(guān)代碼
├── 1-env-conf # 環(huán)境配置代碼-實現(xiàn)配置獨立
└── 1-springboot # Spring Boot 應(yīng)用的代碼及其部署代碼
1-springboot 的目錄結(jié)構(gòu)如下:
% cd 1-springboot
% tree -L 1
├── Jenkinsfile # 流水線代碼
├── README.md
├── deploy # 部署代碼
├── pom.xml
└── src # 業(yè)務(wù)代碼
所有代碼播揪,均放在 GitHub:https://github.com/cd-in-practice
2. 實驗環(huán)境準(zhǔn)備
筆者使用 Docker Compose + Vagrant 進(jìn)行實驗。環(huán)境包括以下幾個系統(tǒng):
- Jenkins * 1
Jenkins master筒狠,全自動安裝插件猪狈、默認(rèn)用戶名密碼:admin/admin。 - Jenkins agent * 2
Jenkins agent 運(yùn)行在 Docker 容器中辩恼,共啟動兩個雇庙。 - Artifactory * 1
一個商業(yè)版的制品庫谓形。筆者申請了一個 30 天的商業(yè)版。
使用 Vagrant 是為了啟動虛擬機(jī)疆前,用于部署 Spring Boot 應(yīng)用寒跳。如果你的開發(fā)機(jī)器無法使用 Vagrant,使用 VirtualBox 也可以達(dá)到同樣的效果竹椒。但是有一點需要注意童太,那就是網(wǎng)絡(luò)。如果在虛擬機(jī)中要訪問 Docker 容器內(nèi)提供的服務(wù)胸完,需要在 DNS 上或者 hosts 上做相應(yīng)的調(diào)整书释。所有的虛擬機(jī)的鏡像使用 Centos7。
另舶吗,接下來筆者的所有教程都將使用 Artifactory 作為制品庫征冷。在此申明,筆者沒有收 JFrog——研發(fā) Artifactory 產(chǎn)品的公司——任何廣告費(fèi)誓琼。 筆者只是想試用商業(yè)產(chǎn)品,以便了解商業(yè)產(chǎn)品是如何應(yīng)對制品管理問題的肴捉。
啟動 Artifactory 后腹侣,需要添加 “Virtual Repository” 及 “Local Repository”。具體請查看 Artifactory 的官方文檔齿穗。如果你當(dāng)前使用的是 Nexus傲隶,參考本教程,做一些調(diào)整窃页,問題也不大跺株。
如果想使用已有制品庫,可以修改 1-cd-platform 倉庫中的 settings-docker.xml 文件脖卖,指向自己的制品庫乒省。
實驗環(huán)境近期的總體結(jié)構(gòu)圖如下:
之所以說是“近期的”,是因為上圖與本篇介紹的結(jié)構(gòu)有小差異畦木。本篇文章還沒有介紹 Nginx 與 Springboot 配置共用袖扛,但是總體不影響讀者理解。
3. Springboot 應(yīng)用流水線介紹
Springboot 流水線有兩個階段:
- 構(gòu)建并上傳制品
- 部署應(yīng)用
流水線的所有邏輯都寫在 Jenkinsfile 文件十籍。接下來蛆封,分別介紹這兩個階段。
3.1 構(gòu)建并上傳制品
此階段核心代碼:
docker.image('jenkins-docker-maven:3.6.1-jdk8')
.inside("--network 1-cd-platform_cd-in-practice -v $HOME/.m2:/root/.m2") {
sh """
mvn versions:set -DnewVersion=${APP_VERSION}
mvn clean test package
mvn deploy
"""
}
它首先啟動一個裝有 Maven 的容器勾栗,然后在容器內(nèi)執(zhí)行編譯惨篱、單元測試、發(fā)布制品的操作围俘。
而 mvn versions:set -DnewVersion=${APP_VERSION}
的作用是更改 pom.xml
文件中的版本砸讳。這樣就可以實現(xiàn)每次提交對應(yīng)一個版本的效果机断。
3.2 部署應(yīng)用
注意: 這部分需要一些 Ansible 的知識。
首先看部署腳本的入口 1-springboot/deploy/playbook.yaml:
---
- hosts: "springboot"
become: yes
roles:
- {"role": "ansible-role-java", "java_home": "{{JAVA_HOME}}"}
- springboot
先安裝 JDK绣夺,再安裝 Spring Boot吏奸。JDK 的安裝,使用了現(xiàn)成 Ansible role: https://github.com/geerlingguy/ansible-role-java陶耍。
重點在 Spring Boot 部署的核心邏輯奋蔚。它主要包含以下幾部分:
- 創(chuàng)建應(yīng)用目錄。
- 從制品庫下載指定版本的制品烈钞。
- 生成 Systemd service 文件(實現(xiàn)服務(wù)化)泊碑。
- 啟動服務(wù)。
以上步驟實現(xiàn)在 1-springboot/deploy/roles/springboot 中毯欣。
流水線的部署階段的核心代碼如下:
docker.image('williamyeh/ansible:centos7').inside("--network 1-cd-platform_cd-in-practice") {
checkout([$class: 'GitSCM', branches: [[name: "master"]], doGenerateSubmoduleConfigurations: false,
extensions: [[$class: 'RelativeTargetDirectory', relativeTargetDir: "env-conf"]], submoduleCfg: [],
userRemoteConfigs: [[url: "https://github.com/cd-in-practice/1-env-conf.git"]]])
sh "ls -al"
sh """
ansible-playbook --syntax-check deploy/playbook.yaml -i env-conf/dev
ansible-playbook deploy/playbook.yaml -i env-conf/dev --extra-vars '{"app_version": "${APP_VERSION}"}'
"""
}
它首先將配置變量倉庫的代碼 clone 下來馒过,然后對 playbook 進(jìn)行語法上的檢查,最后執(zhí)行 ansible-playbook
命令進(jìn)行部署酗钞。--extra-vars
參數(shù)的 app_version
用于指定將要部署的應(yīng)用的版本腹忽。
3.3 實現(xiàn)簡易指定版本部署
在 1-springboot/Jenkinsfile 中實現(xiàn)了簡易的指定版本部署。核心代碼如下:
- 流水線接受參數(shù)
parameters { string(name: 'SPECIFIC_APP_VERSION',
defaultValue: '', description: '') }
- 如果指定了版本砚作,則跳過構(gòu)建階段窘奏,直接執(zhí)行部署階段
stage("build and upload"){
// 如果不指定部署版本,則執(zhí)行構(gòu)建
when {
expression{ return params.SPECIFIC_APP_VERSION == "" }
}
// 構(gòu)建并上傳制品的邏輯
steps{...}
}
之所以說是“簡易”葫录,是因為部署時只指定了制品的版本着裹,并沒有指定的部署邏輯和配置的版本。這三者的版本要同步米同,部署才真正做到準(zhǔn)確骇扇。
4. 配置管理
所有的配置項都放在 1-env-conf 倉庫中。Ansible 執(zhí)行部署時會讀取此倉庫的配置面粮。
將配置放在 Git 倉庫中有兩個好處:
- 配置版本化少孝。
- 任何配置的更改都可以被審查。
有好處并不代表沒有成本但金。那就是開發(fā)人員必須開始關(guān)心軟件的配置(筆者發(fā)現(xiàn)不少開發(fā)者忽視配置項管理的重要性韭山。)。
本文重點不在配置管理冷溃,后面會有文章重點介紹钱磅。
5. 實驗環(huán)境詳細(xì)介紹
事實上,整個實驗似枕,工作量大的地方有兩處:一是 Spring Boot 流水線本身的設(shè)計盖淡;二是整個實驗環(huán)境的自動化。讀者朋友之所以能一兩條簡單的命令就能啟動整個實驗環(huán)境凿歼,是因為筆者做了很多自動化的工作褪迟。筆者認(rèn)為有必要在本篇介紹這些工作冗恨。接下來的文章將不再詳細(xì)介紹。
5.1 解決流水線中啟動的 Docker 容器無法訪問 http://artifactory
流水線中味赃,我們需要將制品上傳到 artifactory(settings.xml 配置的倉庫地址是 http://artifactory:8081)掀抹,但是發(fā)現(xiàn)無法解析 host。這是因為流水線中的 Docker 容器所在網(wǎng)絡(luò)與 Docker compose 創(chuàng)建的網(wǎng)絡(luò)不同心俗。所以傲武,解決辦法就是讓流水線中的 Docker 容器加入到 Docker compose 的網(wǎng)絡(luò)。
具體解決辦法就是在啟動容器時城榛,加入?yún)?shù):--network 1-cd-platform_cd-in-practice
5.2 Jenkins 初次啟動初始化
在沒有做任何設(shè)置的情況啟動 Jenkins揪利,會出現(xiàn)一個配置向?qū)А_@個過程必須是手工的狠持。筆者希望這一步也是自動化的疟位。Jenkins 啟動時會執(zhí)行 init.groovy.d/
目錄下的 Groovy 腳本。
5.3 虛擬機(jī)中如何能訪問到 http://artifactory 喘垂?
http://artifactory 部署在 Docker 容器中甜刻。Spring Boot 應(yīng)用的制品要部署到虛擬機(jī)中,需要從 http://artifactory 中拉取制品王污,也就是要在虛擬機(jī)里訪問容器里提供的服務(wù)罢吃。虛擬機(jī)與容器之間的網(wǎng)絡(luò)是不通的。那怎么辦呢昭齐?筆者的解決方案是使用宿主機(jī)的 IP 做中轉(zhuǎn)。具體做法就是在虛擬機(jī)中加一條 host 記錄:
machine.vm.provision "shell" do |s|
s.inline = "echo '192.168.52.1 artifactory' >> /etc/hosts"
end
以上是使用了 Vagrant 的 provision
技術(shù)矾柜,在執(zhí)行命令 vagrant up
啟動虛擬機(jī)時阱驾,就自動執(zhí)行那段內(nèi)聯(lián) shell。192.168.52.1
是虛擬宿主機(jī)的 IP怪蔑。所以里覆,虛擬機(jī)里訪問 http://artifactory:8081 時,實際上訪問的是 http://192.168.52.1:8081缆瓣。
網(wǎng)絡(luò)結(jié)構(gòu)可以總結(jié)為下圖:
后記
目前遺留問題:
- 部署時制品版本喧枷、配置版本、部署代碼版本沒有同步弓坞。
- Springboot 的配置是寫死在制品中的隧甚,沒有實現(xiàn)制品與配置項的分離。
這些遺留問題在后期會逐個解決渡冻。就像現(xiàn)實一樣戚扳,經(jīng)常需要面對各種遺留項目的遺留問題。
附錄
- 使用 Jenkins + Ansible 實現(xiàn)自動化部署 Nginx:https://jenkins-zh.cn/wechat/articles/2019/04/2019-04-25-jenkins-ansible-nginx/
- 簡單易懂 Ansible 系列 —— 解決了什么:https://showme.codes/2017-06-12/ansible-introduce/
本文作者:翟志軍