使用 Jenkins + Ansible 實現 Spring Boot 自動化部署101
本文首發於:Jenkins 中文社群
本文要點:
- 設計一條 Spring Boot 最基本的流水線:包括構建、製品上傳、部署。
- 使用 Docker 容器執行構建邏輯。
- 自動化整個實驗環境:包括 Jenkins 的配置,Jenkins agent 的配置等。
1. 程式碼倉庫安排
本次實驗涉及以下多個程式碼倉庫:
% tree -L 1
├── 1-cd-platform # 實驗環境相關程式碼
├── 1-env-conf # 環境配置程式碼-實現配置獨立
└── 1-springboot # Spring Boot 應用的程式碼及其部署程式碼
1-springboot 的目錄結構如下:
% cd 1-springboot
% tree -L 1
├── Jenkinsfile # 流水線程式碼
├── README.md
├── deploy # 部署程式碼
├── pom.xml
└── src # 業務程式碼
所有程式碼,均放在 GitHub:https://github.com/cd-in-practice
2. 實驗環境準備
筆者使用 Docker Compose + Vagrant 進行實驗。環境包括以下幾個系統:
- Jenkins * 1 Jenkins master,全自動安裝外掛、預設使用者名稱密碼:admin/admin。
- Jenkins agent * 2 Jenkins agent 執行在 Docker 容器中,共啟動兩個。
- Artifactory * 1 一個商業版的製品庫。筆者申請了一個 30 天的商業版。
使用 Vagrant 是為了啟動虛擬機器,用於部署 Spring Boot 應用。如果你的開發機器無法使用 Vagrant,使用 VirtualBox 也可以達到同樣的效果。但是有一點需要注意,那就是網路。如果在虛擬機器中要訪問 Docker 容器內提供的服務,需要在 DNS 上或者 hosts 上做相應的調整。所有的虛擬機器的映象使用 Centos7。
另,接下來筆者的所有教程都將使用 Artifactory 作為製品庫。在此申明,筆者沒有收 JFrog——研發 Artifactory 產品的公司——任何廣告費。
啟動 Artifactory 後,需要新增 “Virtual Repository” 及 “Local Repository”。具體請檢視 Artifactory 的官方文件。如果你當前使用的是 Nexus,參考本教程,做一些調整,問題也不大。
如果想使用已有製品庫,可以修改 1-cd-platform 倉庫中的 settings-docker.xml 檔案,指向自己的製品庫。
實驗環境近期的總體結構圖如下:
之所以說是“近期的”,是因為上圖與本篇介紹的結構有小差異。本篇文章還沒有介紹 Nginx 與 Springboot 配置共用,但是總體不影響讀者理解。
3. Springboot 應用流水線介紹
Springboot 流水線有兩個階段:
- 構建並上傳製品
- 部署應用
流水線的所有邏輯都寫在 Jenkinsfile 檔案。接下來,分別介紹這兩個階段。
3.1 構建並上傳製品
此階段核心程式碼:
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 的容器,然後在容器內執行編譯、單元測試、釋出製品的操作。
而 mvn versions:set -DnewVersion=${APP_VERSION}
的作用是更改 pom.xml
檔案中的版本。這樣就可以實現每次提交對應一個版本的效果。
3.2 部署應用
注意: 這部分需要一些 Ansible 的知識。
首先看部署指令碼的入口 1-springboot/deploy/playbook.yaml:
---
- hosts: "springboot"
become: yes
roles:
- {"role": "ansible-role-java", "java_home": "{{JAVA_HOME}}"}
- springboot
先安裝 JDK,再安裝 Spring Boot。JDK 的安裝,使用了現成 Ansible role: https://github.com/geerlingguy/ansible-role-java。
重點在 Spring Boot 部署的核心邏輯。它主要包含以下幾部分:
- 建立應用目錄。
- 從製品庫下載指定版本的製品。
- 生成 Systemd service 檔案(實現服務化)。
- 啟動服務。
以上步驟實現在 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 進行語法上的檢查,最後執行 ansible-playbook
命令進行部署。--extra-vars
引數的 app_version
用於指定將要部署的應用的版本。
3.3 實現簡易指定版本部署
在 1-springboot/Jenkinsfile 中實現了簡易的指定版本部署。核心程式碼如下:
- 流水線接受引數
parameters { string(name: 'SPECIFIC_APP_VERSION',
defaultValue: '', description: '') }
- 如果指定了版本,則跳過構建階段,直接執行部署階段
stage("build and upload"){
// 如果不指定部署版本,則執行構建
when {
expression{ return params.SPECIFIC_APP_VERSION == "" }
}
// 構建並上傳製品的邏輯
steps{...}
}
之所以說是“簡易”,是因為部署時只指定了製品的版本,並沒有指定的部署邏輯和配置的版本。這三者的版本要同步,部署才真正做到準確。
4. 配置管理
所有的配置項都放在 1-env-conf 倉庫中。Ansible 執行部署時會讀取此倉庫的配置。
將配置放在 Git 倉庫中有兩個好處:
- 配置版本化。
- 任何配置的更改都可以被審查。
有好處並不代表沒有成本。那就是開發人員必須開始關心軟體的配置(筆者發現不少開發者忽視配置項管理的重要性。)。
本文重點不在配置管理,後面會有文章重點介紹。
5. 實驗環境詳細介紹
事實上,整個實驗,工作量大的地方有兩處:一是 Spring Boot 流水線本身的設計;二是整個實驗環境的自動化。讀者朋友之所以能一兩條簡單的命令就能啟動整個實驗環境,是因為筆者做了很多自動化的工作。筆者認為有必要在本篇介紹這些工作。接下來的文章將不再詳細介紹。
5.1 解決流水線中啟動的 Docker 容器無法訪問 http://artifactory
流水線中,我們需要將製品上傳到 artifactory(settings.xml 配置的倉庫地址是 http://artifactory:8081),但是發現無法解析 host。這是因為流水線中的 Docker 容器所在網路與 Docker compose 建立的網路不同。所以,解決辦法就是讓流水線中的 Docker 容器加入到 Docker compose 的網路。
具體解決辦法就是在啟動容器時,加入引數:--network 1-cd-platform_cd-in-practice
5.2 Jenkins 初次啟動初始化
在沒有做任何設定的情況啟動 Jenkins,會出現一個配置嚮導。這個過程必須是手工的。筆者希望這一步也是自動化的。Jenkins 啟動時會執行 init.groovy.d/
目錄下的 Groovy 指令碼。
5.3 虛擬機器中如何能訪問到 http://artifactory ?
http://artifactory 部署在 Docker 容器中。Spring Boot 應用的製品要部署到虛擬機器中,需要從 http://artifactory 中拉取製品,也就是要在虛擬機器裡訪問容器裡提供的服務。虛擬機器與容器之間的網路是不通的。那怎麼辦呢?筆者的解決方案是使用宿主機的 IP 做中轉。具體做法就是在虛擬機器中加一條 host 記錄:
machine.vm.provision "shell" do |s|
s.inline = "echo '192.168.52.1 artifactory' >> /etc/hosts"
end
以上是使用了 Vagrant 的 provision
技術,在執行命令 vagrant up
啟動虛擬機器時,就自動執行那段內聯 shell。192.168.52.1
是虛擬宿主機的 IP。所以,虛擬機器裡訪問 http://artifactory:8081 時,實際上訪問的是 http://192.168.52.1:8081。
網路結構可以總結為下圖:
後記
目前遺留問題:
- 部署時製品版本、配置版本、部署程式碼版本沒有同步。
- Springboot 的配置是寫死在製品中的,沒有實現製品與配置項的分離。
這些遺留問題在後期會逐個解決。就像現實一樣,經常需要面對各種遺留專案的遺留問題。
附錄
- 使用 Jenkins + Ansible 實現自動化部署 Nginx:https://jenkins-zh.cn/wechat/articles/2019/04/2019-04-25-jenkins-ansible-nginx/
- 簡單易懂 Ansible 系列 —— 解決了什麼:https://showme.codes/2017-06-12/ansible-introduce/