使用 Jenkins + Ansible 实现 Springboot 自动化部署101

首发于 Jenkins 中文社区

image.png

本文要点:

  1. 设计一条 Spring Boot 最基本的流水线:包括构建、制品上传、部署。
  2. 使用 Docker 容器运行构建逻辑。
  3. 自动化整个实验环境:包括 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 文件,指向自己的制品库。

实验环境近期的总体结构图如下:

image.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: https://github.com/geerlingguy/ansible-role-java

重点在 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。

网络结构可以总结为下图:

image.png

后记

目前遗留问题:

  1. 部署时制品版本、配置版本、部署代码版本没有同步。
  2. Springboot 的配置是写死在制品中的,没有实现制品与配置项的分离。

这些遗留问题在后期会逐个解决。就像现实一样,经常需要面对各种遗留项目的遗留问题。

附录

  1. 使用 Jenkins + Ansible 实现自动化部署 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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容