使用 Jenkins + Ansible 實現 Springboot 自動化部署101

首發於 Jenkins 中文社區java

本文要點:nginx

  1. 設計一條 Spring Boot 最基本的流水線:包括構建、製品上傳、部署。
  2. 使用 Docker 容器運行構建邏輯。
  3. 自動化整個實驗環境:包括 Jenkins 的配置,Jenkins agent 的配置等。

1. 代碼倉庫安排

本次實驗涉及如下多個代碼倉庫:git

% tree -L 1
├── 1-cd-platform # 實驗環境相關代碼
├── 1-env-conf # 環境配置代碼-實現配置獨立
└── 1-springboot # Spring Boot 應用的代碼及其部署代碼
複製代碼

1-springboot 的目錄結構以下:github

% cd 1-springboot
% tree -L 1 
├── Jenkinsfile # 流水線代碼
├── README.md
├── deploy # 部署代碼
├── pom.xml 
└── src # 業務代碼
複製代碼

全部代碼,均放在 GitHub:github.com/cd-in-pract…spring

2. 實驗環境準備

筆者使用 Docker Compose + Vagrant 進行實驗。環境包括如下幾個系統:docker

  • Jenkins * 1 Jenkins master,全自動安裝插件、默認用戶名密碼:admin/admin。
  • Jenkins agent * 2 Jenkins agent 運行在 Docker 容器中,共啓動兩個。
  • Artifactory * 1 一個商業版的製品庫。筆者申請了一個 30 天的商業版。

使用 Vagrant 是爲了啓動虛擬機,用於部署 Spring Boot 應用。若是你的開發機器沒法使用 Vagrant,使用 VirtualBox 也能夠達到一樣的效果。可是有一點須要注意,那就是網絡。若是在虛擬機中要訪問 Docker 容器內提供的服務,須要在 DNS 上或者 hosts 上作相應的調整。全部的虛擬機的鏡像使用 Centos7。shell

另,接下來筆者的全部教程都將使用 Artifactory 做爲製品庫。在此申明,筆者沒有收 JFrog——研發 Artifactory 產品的公司——任何廣告費。 筆者只是想試用商業產品,以便了解商業產品是如何應對製品管理問題的。express

啓動 Artifactory 後,須要添加 「Virtual Repository」 及 「Local Repository」。具體請查看 Artifactory 的官方文檔。若是你當前使用的是 Nexus,參考本教程,作一些調整,問題也不大。centos

若是想使用已有製品庫,能夠修改 1-cd-platform 倉庫中的 settings-docker.xml 文件,指向本身的製品庫。springboot

實驗環境近期的整體結構圖以下:

architecture.png

之因此說是「近期的」,是由於上圖與本篇介紹的結構有小差別。本篇文章尚未介紹 Nginx 與 Springboot 配置共用,可是整體不影響讀者理解。

3. Springboot 應用流水線介紹

Springboot 流水線有兩個階段:

  1. 構建並上傳製品
  2. 部署應用

流水線的全部邏輯都寫在 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: github.com/geerlingguy…

重點在 Spring Boot 部署的核心邏輯。它主要包含如下幾部分:

  1. 建立應用目錄。
  2. 從製品庫下載指定版本的製品。
  3. 生成 Systemd service 文件(實現服務化)。
  4. 啓動服務。

以上步驟實如今 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 中實現了簡易的指定版本部署。核心代碼以下:

  1. 流水線接受參數
parameters { string(name: 'SPECIFIC_APP_VERSION', 
defaultValue: '', description: '') }
複製代碼
  1. 若是指定了版本,則跳過構建階段,直接執行部署階段
stage("build and upload"){
      // 若是不指定部署版本,則執行構建
      when {
        expression{ return params.SPECIFIC_APP_VERSION == "" }
      }
      // 構建並上傳製品的邏輯
      steps{...}
}
複製代碼

之因此說是「簡易」,是由於部署時只指定了製品的版本,並無指定的部署邏輯和配置的版本。這三者的版本要同步,部署才真正作到準確。

4. 配置管理

全部的配置項都放在 1-env-conf 倉庫中。Ansible 執行部署時會讀取此倉庫的配置。

將配置放在 Git 倉庫中有兩個好處:

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

有好處並不表明沒有成本。那就是開發人員必須開始關心軟件的配置(筆者發現很多開發者忽視配置項管理的重要性。)。

本文重點不在配置管理,後面會有文章重點介紹。

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

網絡結構能夠總結爲下圖:

network.png

後記

目前遺留問題:

  1. 部署時製品版本、配置版本、部署代碼版本沒有同步。
  2. Springboot 的配置是寫死在製品中的,沒有實現製品與配置項的分離。

這些遺留問題在後期會逐個解決。就像現實同樣,常常須要面對各類遺留項目的遺留問題。

附錄

  1. 使用 Jenkins + Ansible 實現自動化部署 Nginx:jenkins-zh.cn/wechat/arti…
  2. 簡單易懂 Ansible 系列 —— 解決了什麼:showme.codes/2017-06-12/…

本文做者:翟志軍

相關文章
相關標籤/搜索