使用 Jenkins + Ansible 實(shí)現(xiàn) Springboot 自動(dòng)化部署101

首發(fā)于 Jenkins 中文社區(qū)

image.png

本文要點(diǎn):

  1. 設(shè)計(jì)一條 Spring Boot 最基本的流水線:包括構(gòu)建、制品上傳、部署。
  2. 使用 Docker 容器運(yùn)行構(gòu)建邏輯。
  3. 自動(dòng)化整個(gè)實(shí)驗(yàn)環(huán)境:包括 Jenkins 的配置,Jenkins agent 的配置等。

1. 代碼倉庫安排

本次實(shí)驗(yàn)涉及以下多個(gè)代碼倉庫:

% tree -L 1
├── 1-cd-platform # 實(shí)驗(yàn)環(huán)境相關(guān)代碼
├── 1-env-conf # 環(huán)境配置代碼-實(shí)現(xiàn)配置獨(dú)立
└── 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. 實(shí)驗(yàn)環(huán)境準(zhǔn)備

筆者使用 Docker Compose + Vagrant 進(jìn)行實(shí)驗(yàn)。環(huán)境包括以下幾個(gè)系統(tǒng):

  • Jenkins * 1
    Jenkins master,全自動(dòng)安裝插件、默認(rèn)用戶名密碼:admin/admin。
  • Jenkins agent * 2
    Jenkins agent 運(yùn)行在 Docker 容器中,共啟動(dòng)兩個(gè)。
  • Artifactory * 1
    一個(gè)商業(yè)版的制品庫。筆者申請(qǐng)了一個(gè) 30 天的商業(yè)版。

使用 Vagrant 是為了啟動(dòng)虛擬機(jī),用于部署 Spring Boot 應(yīng)用。如果你的開發(fā)機(jī)器無法使用 Vagrant,使用 VirtualBox 也可以達(dá)到同樣的效果。但是有一點(diǎn)需要注意,那就是網(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)對(duì)制品管理問題的。

啟動(dòng) Artifactory 后,需要添加 “Virtual Repository” 及 “Local Repository”。具體請(qǐng)查看 Artifactory 的官方文檔。如果你當(dāng)前使用的是 Nexus,參考本教程,做一些調(diào)整,問題也不大。

如果想使用已有制品庫,可以修改 1-cd-platform 倉庫中的 settings-docker.xml 文件,指向自己的制品庫。

實(shí)驗(yàn)環(huán)境近期的總體結(jié)構(gòu)圖如下:

image.png

之所以說是“近期的”,是因?yàn)樯蠄D與本篇介紹的結(jié)構(gòu)有小差異。本篇文章還沒有介紹 Nginx 與 Springboot 配置共用,但是總體不影響讀者理解。

3. Springboot 應(yīng)用流水線介紹

Springboot 流水線有兩個(gè)階段:

  1. 構(gòu)建并上傳制品
  2. 部署應(yīng)用

流水線的所有邏輯都寫在 Jenkinsfile 文件。接下來,分別介紹這兩個(gè)階段。

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
    """
}

它首先啟動(dòng)一個(gè)裝有 Maven 的容器,然后在容器內(nèi)執(zhí)行編譯、單元測試、發(fā)布制品的操作。

mvn versions:set -DnewVersion=${APP_VERSION} 的作用是更改 pom.xml 文件中的版本。這樣就可以實(shí)現(xiàn)每次提交對(duì)應(yīng)一個(gè)版本的效果。

3.2 部署應(yīng)用

注意: 這部分需要一些 Ansible 的知識(shí)。

首先看部署腳本的入口 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

重點(diǎn)在 Spring Boot 部署的核心邏輯。它主要包含以下幾部分:

  1. 創(chuàng)建應(yīng)用目錄。
  2. 從制品庫下載指定版本的制品。
  3. 生成 Systemd service 文件(實(shí)現(xiàn)服務(wù)化)。
  4. 啟動(dòng)服務(wù)。

以上步驟實(shí)現(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 下來,然后對(duì) playbook 進(jìn)行語法上的檢查,最后執(zhí)行 ansible-playbook 命令進(jìn)行部署。--extra-vars 參數(shù)的 app_version 用于指定將要部署的應(yīng)用的版本。

3.3 實(shí)現(xiàn)簡易指定版本部署

1-springboot/Jenkinsfile 中實(shí)現(xiàn)了簡易的指定版本部署。核心代碼如下:

  1. 流水線接受參數(shù)
parameters { string(name: 'SPECIFIC_APP_VERSION', 
defaultValue: '', description: '') }
  1. 如果指定了版本,則跳過構(gòu)建階段,直接執(zhí)行部署階段
stage("build and upload"){
      // 如果不指定部署版本,則執(zhí)行構(gòu)建
      when {
        expression{ return params.SPECIFIC_APP_VERSION == "" }
      }
      // 構(gòu)建并上傳制品的邏輯
      steps{...}
}

之所以說是“簡易”,是因?yàn)椴渴饡r(shí)只指定了制品的版本,并沒有指定的部署邏輯和配置的版本。這三者的版本要同步,部署才真正做到準(zhǔn)確。

4. 配置管理

所有的配置項(xiàng)都放在 1-env-conf 倉庫中。Ansible 執(zhí)行部署時(shí)會(huì)讀取此倉庫的配置。

將配置放在 Git 倉庫中有兩個(gè)好處:

  1. 配置版本化。
  2. 任何配置的更改都可以被審查。

有好處并不代表沒有成本。那就是開發(fā)人員必須開始關(guān)心軟件的配置(筆者發(fā)現(xiàn)不少開發(fā)者忽視配置項(xiàng)管理的重要性。)。

本文重點(diǎn)不在配置管理,后面會(huì)有文章重點(diǎn)介紹。

5. 實(shí)驗(yàn)環(huán)境詳細(xì)介紹

事實(shí)上,整個(gè)實(shí)驗(yàn),工作量大的地方有兩處:一是 Spring Boot 流水線本身的設(shè)計(jì);二是整個(gè)實(shí)驗(yàn)環(huán)境的自動(dòng)化。讀者朋友之所以能一兩條簡單的命令就能啟動(dòng)整個(gè)實(shí)驗(yàn)環(huán)境,是因?yàn)楣P者做了很多自動(dòng)化的工作。筆者認(rèn)為有必要在本篇介紹這些工作。接下來的文章將不再詳細(xì)介紹。

5.1 解決流水線中啟動(dòng)的 Docker 容器無法訪問 http://artifactory

流水線中,我們需要將制品上傳到 artifactory(settings.xml 配置的倉庫地址是 http://artifactory:8081),但是發(fā)現(xiàn)無法解析 host。這是因?yàn)榱魉€中的 Docker 容器所在網(wǎng)絡(luò)與 Docker compose 創(chuàng)建的網(wǎng)絡(luò)不同。所以,解決辦法就是讓流水線中的 Docker 容器加入到 Docker compose 的網(wǎng)絡(luò)。

具體解決辦法就是在啟動(dòng)容器時(shí),加入?yún)?shù):--network 1-cd-platform_cd-in-practice

5.2 Jenkins 初次啟動(dòng)初始化

在沒有做任何設(shè)置的情況啟動(dòng) Jenkins,會(huì)出現(xiàn)一個(gè)配置向?qū)?。這個(gè)過程必須是手工的。筆者希望這一步也是自動(dòng)化的。Jenkins 啟動(dòng)時(shí)會(huì)執(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 啟動(dòng)虛擬機(jī)時(shí),就自動(dòng)執(zhí)行那段內(nèi)聯(lián) shell。192.168.52.1 是虛擬宿主機(jī)的 IP。所以,虛擬機(jī)里訪問 http://artifactory:8081 時(shí),實(shí)際上訪問的是 http://192.168.52.1:8081。

網(wǎng)絡(luò)結(jié)構(gòu)可以總結(jié)為下圖:

image.png

后記

目前遺留問題:

  1. 部署時(shí)制品版本、配置版本、部署代碼版本沒有同步。
  2. Springboot 的配置是寫死在制品中的,沒有實(shí)現(xiàn)制品與配置項(xiàng)的分離。

這些遺留問題在后期會(huì)逐個(gè)解決。就像現(xiàn)實(shí)一樣,經(jīng)常需要面對(duì)各種遺留項(xiàng)目的遺留問題。

附錄

  1. 使用 Jenkins + Ansible 實(shí)現(xiàn)自動(dòng)化部署 Nginx:https://jenkins-zh.cn/wechat/articles/2019/04/2019-04-25-jenkins-ansible-nginx/
  2. 簡單易懂 Ansible 系列 —— 解決了什么:https://showme.codes/2017-06-12/ansible-introduce/

本文作者:翟志軍

image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容