这属于docker与云原生的第二部分。
DevOps
DevOps是Development和Operation的组合词,这个属性位于开发工程师和运维测试工程师的交集。突出强调的是自动化流程,减少开发人员和运维的冲突,提高效率。
整个DevOps看是一个闭环:

这个闭环从计划开始,然后编码,构建,测试,发布,部署,运维,监控,如果出了bug或者新的需求再继续走这一套流程。
具体到各型企业时,也会具体到各个环节选取的具体开源工具,比如:

以上只是一套工具链的适配,随着场景及越来越丰富,多种多样的工具链组合也不断迭代。

CI/CD
CI是持续集成(Continuous Integration),CD是持续交付(Continuous Delivery)。
持续突出的是不间断的意思,两个异步线程不会太多干扰,又高度配合。
基本概念
属于敏捷开发,不断完善,然后灰度发布。
持续集成(Continuous Integration)
持续集成是指软件个人研发的部分向软件整体部分交付,频繁进行集成以便更快地发现 其中的错 误。“持续集成”源自于极限编程(XP),是 XP 最初的 12 种实践之一。
CI 需要具备这些:
- 全面的自动化测试。这是实践持续集成&持续部署的基础,同时,选择合适的 自动化测试工具也极 其重要;
- 灵活的基础设施。容器,虚拟机的存在让开发人员和 QA 人员不必再大费周 折;
- 版本控制工具。如 Git,CVS,SVN 等;
- 自动化的构建和软件发布流程的工具,如 Jenkins,flow.ci;
- 反馈机制。如构建/测试的失败,可以快速地反馈到相关负责人,以尽快解决达到一个更稳定的版本。
持续交付(Continuous Delivery)
持续交付在持续集成的基础上,将集成后的代码部署到更贴近真实运行环境的「类生产环境」 (production-like environments)中。持续交付优先于整个产品生命周期的软件部署,建立在高水平自 动化持续集成之上。
持续交付和持续集成的优点非常相似:
- 快速发布。能够应对业务需求,并更快地实现软件价值。
- 编码->测试->上线->交付的频繁迭代周期缩短,同时获得迅速反馈;
- 高质量的软件发布标准。整个交付过程标准化、可重复、可靠,
- 整个交付过程进度可视化,方便团队人员了解项目成熟度;
- 更先进的团队协作方式。从需求分析、产品的用户体验到交互 设计、开发、测试、运维等角色密 切协作,相比于传统的瀑布式软件团队,更少浪费。
持续部署(Continuous Deployment)
持续部署是指当交付的代码通过评审之后,自动部署到生产环境中。持续部署是持续交付的最高阶段。 这意味着,所有通过了一系列的自动化测试的改动都将自动部署到生产环境。它也可以被称 为“Continuous Release”。
“开发人员提交代码,持续集成服务器获取代码,执行单元测试,根据测试结果决定是否部署到预 演环境,如果成功部署到预演环境,进行整体验收测试,如果测试通过,自动部署到产品环境, 全程自动化高效运转。
持续部署主要好处是,可以相对独立地部署新的功能,并能快速地收集真实用户的反馈。
持续部署主要好处是,可以相对独立地部署新的功能,并能快速地收集真实用户的反馈。
最佳实践
许多企业是集成jira,k8s等架构的,我这里先从简单的做起,后面会应用整个k8s架构来应对多容器情况下Devops。
当然还有更简单的方法,比如IDEA连接远程docker部署的服务器,maven通过docker插件基于dockerfile直接打包成镜像一键部署。不仅如此IDEA还可以通过集成shell工具直接远程部署jar包,但是这些这个对我目前意义不大,不再详述。
docker官方给的最佳实战参考图:

可以看出有内循环和外循环。
对此图简单解析:
- 内循环(开发要做的事情):编码、测试、运行、debug、提交
- 代码推送到代码仓库(svn,git)【代码回滚】
- 进行CI过程(持续集成),万物皆可容器化。打包成一个Docker镜像
- 镜像推送到镜像仓库
- 测试
- 持续部署流程(CD),拿到之前的镜像,进行CD。怎么放到各种环境。uat、test、prod
- 外循环()
- 运行时监控
- 生产环境的管理
- 监控
- 线上反馈到开发
- 再次来到内循环
以上是官方推荐的最佳实践,相当于是一种规范,具体落实看公司。
实践流程

当遇到新功能需求或者bug时,走上面一套流程。
- 创建相应分支来做这个事情(开发功能)
- 提交响应了需求的分支代码
- 进入持续集成流程
- 当前分支代码功能性自动化构建和测试
- 自动工具推送这次提交
- 自动化集成测试
- 管理员确认此次功能是否发布到生产环境
- 代码合并。
- 进入持续部署流程
- 构建、测试、发布......
CICD LandSpace
整个工具链工具一览:
第一个阶段collaborate一般是收集需求进行开发,常用的jira或者禅道,沟通交流用钉钉微信,知识分享用confluence wiki等。
build阶段,版本控制工具就是git,开发集成工具等。
test阶段一般是gatling或者jmeter,selenium,junit等。
deploy阶段 vagrant dockerhub nexus等都很常用
run阶段选择熟悉的就好。
为了让这些工具配合起来,就需要Jenkins来统一起来。
Jenkins

Jenkins本质就是java编写的程序。
我们安装Jenkins基于docker,所以确保docker的安装步骤不错。
Jenkins中午安装文档参考:
https://www.jenkins.io/zh/doc/book/installing/
文件已经十分详细了
docker run \
-u root \
-d \
-p 9090:8080 \
-p 50000:50000 \
-v jenkins-data:/var/jenkins_home \
-v /etc/localtime:/etc/localtime:ro \
-v /var/run/docker.sock:/var/run/docker.sock \
--restart=always \
jenkinsci/blueocean
--rm:
关闭时自动删除Docker容器。如果您需要退出Jenkins,这可以保持整洁。
-p 50000:50000:
集群端口号。
/var/jenkins_home:
jenkins的核心文件,所有差异化文件都在这里以文件的方式存储。
-v /var/run/docker.sock:/var/run/docker.sock:
是为了Jenkins容器内可以操作docker进程及相关命令。
表示Docker守护程序通过其监听的基于Unix的套接字。 该映射允许 jenkinsci/blueocean 容器与Docker守护进程通信, 如果 jenkinsci/blueocean 容器需要实例化其他Docker容器,则该守护进程是必需的。 如果运行声明式管道,其语法包含agent部分用 docker;例 如, agent { docker } 此选项是必需的。
jenkinsci/blueocean:
安装的版本是jenkinsci,带blueocean插件,jenkinsci/jenkins是没有插件的。
-v /etc/localtime:/etc/localtime:ro:
容器的运行时间很可能是UTC 0时区,需要run的时候我们设置好。
**启动

管理员密码在这个目录也可以通过docker命令查看日志:
docker logs 容器id

然后选择安装推荐的插件,jenkins最大的特色就是集成了上千插件形成了庞大的生态。
安装完成登录进入工作台:

如图,构建新项目就从新建项目开始,可以在系统管理中配置用户相关信息,插件和查看日志等,主要就是blue ocean查看我们构建的流程。
jenkins应用
目标:
代码在本地修改----提交到远程git----触发jenkins整个自动化构建流程(打包,测试,发布,部署)。
步骤:
- idea创建Spring Boot项目
- VCS - 创建git 仓库
- gitee创建一个空仓库(gitlap是内网,Jenkins是外网,这种情况gitlab要暴露一个公网端口)
- idea提交内容到gitee
- 开发项目基本功能,并在项目中创建一个Jenkinsfile文件
- 创建一个名为 cicdtest的流水线项目,使用项目自己的流水线
Jenkins的工作流程
-
先定义一个流水线项目,指定项目的git位置

然后配置项目来源为SCM Git,添加URL后,Jenkins会自动拉取远程代码进行流水线构建。(实际我使用的是Generic Webhook,这个会监听所有分支提交然后正则匹配拉取分支部署,所谓的Jenkins拉取代码实际是一次post请求,里面包含了我们所熟悉的各种json格式信息,git信息)

流水线启动后流程: -
先去git位置自动拉取代码
-
解析拉取代码里面的Jenkinsfile文件
-
按照Jenkinsfile指定的流水线开始加工项目
目前还没有Jenkinsfile,需要在项目根目录新建一个Jenkinsfile。Jenkinsfile有声明式和脚本式,主流还是声明式,用的是groovy的语法。
参考文档:https://www.jenkins.io/zh/doc/book/pipeline/
文档里给了示例:

一个pipeline中分了很多stage,每个stage中分step去做相应的事。
至于每个阶段可以做什么事情,Jenkins可以自动帮我们生成相应片段:

所有可以简单应用编写下我们的Jenkinsfile:
pipeline{
//任何一个代理可用即可执行
agent any
//定义一些环境信息
environment {
hello = "mella"
}
//定义流水线的加工流程
stages {
//
stage('代码编译'){
steps {
echo "编译...."
echo "$hello"
sh 'pwd && ls -alh'
sh 'printenv'
sh 'echo ${GIT_BRANCH}'
}
}
stage('测试'){
steps {
echo "测试...."
}
}
}
}
打开blue ocean查看:

可以看到我们打印出的环境信息:

WORKSPACE=/var/jenkins_home/workspace/cicdtest
- 每个新建的项目都有一个workspace
BUILD_NUMBER=5
- 第五次构建
WORKSPACE_TMP=/var/jenkins_home/workspace/cicdtest@tmp
- 临时工作目录
还有其他很多环境信息。
Jenkins环境和自动触发
现在流水线问题解决了,还有的问题是,如果我们的springboot项目启动没有相应的环境要怎么办?如何自动触发Jenkins拉取git项目?
gitee为例子,远程构建触发:
从Jenkins主页面选择构建历史进入我们之前构建的项目,然后配置我们的构建触发器。

身份验证令牌随便写。
需要在gitee上配置钩子,这样当你提交代码后会触发gitee的钩子,这个钩子配置的就是Jenkins对应的地址,等于说提交代码到gitee后gitee会通知Jenkins,jenkins收到通知就会去gitee拉取代码。(gitee如果是私库,需要配置Jenkins的credential,一般也就是gitee的账号密码)

钩子填写遵循官方要求:
Use the following URL to trigger build remotely:
JENKINS_URL/job/cicdtest/build?token=TOKEN_NAME或者 /buildWithParameters?token=TOKEN_NAME
Optionally append&cause=Cause+Textto provide text that will be included in the recorded build cause.
Generic Webhook和这个也不一样,注意参照提示要求。

效果如上,选择的是push才会触发钩子,也可自行适配。
可以看到URL有点奇怪,其实是 用户名:API Token
关于用户名和token,在jenkins的系统设置-->管理用户-->设置中生成:

token只显示一次,及时保存。

显示表明远程触发成功。
这时候,当我们代码一提交就会显示触发成功。
环境问题
解决环境问题,要么是直接打包一个大docker里面包含了所有的运行环境,要么在jenkins中集成配置环境,还有一种方式是使用docker临时环境。
官方文档给了示例:
pipeline {
//如果为none,下面每个stage都必须指定具体的
agent none
stages {
stage('Back-end') {
agent {
docker { image 'maven:3-alpine' }
}
steps {
sh 'mvn --version'
}
}
stage('Front-end') {
agent {
docker { image 'node:7-alpine' }
}
steps {
sh 'node --version'
}
}
}
}
当使用自定义执行环境时,jenkins需要安装docker pinpline插件:

至于插件安装,我们挂载的目录/var/lib/docker/volumes/jenkins-data/_data 下有plugins目录就是我们所有的插件,也可以下载插件包手动导入jenkins插件管理。
推荐插件安装
上面提到了插件安装docker pipeline,下面列举推荐的插件:
Docker Pipeline && Docker
安装Docker Pipeline会自动安装docker相关的 这个允许我们自定义agent使用docker环境
Git Parameter
解析git参数,允许我们选择分支进行构建
Active Choices
可以做到参数的级联选择
Generic Webhook Trigger
通用的webhook触发器,构建更强大的webhook功能
Role-based Authorization Strategy
RBAC权限指定,给一个用户精确指定权限
List Git Branches Parameter
列出分支参数
Build With Parameters
基于自定义参数构建
自定义环境原理

这里自定义maven仓库和配置文件通过-v挂载的方式实现,实现该方式的手段有两种,一是挂载linux的文件,二是直接使用jenkins的home目录的文件。要选择直接使用jenkins的home目录,这样方便jenkins整体移植。
所以可以在jenkins的挂载目录jenkins-data/_data外创建配置文件进行相应配置。

以root用户进入到挂载的文件夹

然后复制粘贴如下配置:
<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
http://maven.apache.org/xsd/settings-1.0.0.xsd">
<!-- localRepository
| The path to the local repository maven will use to store artifacts.
|
| Default: ${user.home}/.m2/repository
<localRepository>H:\Devsoft\apache-maven-3.6.1\repository</localRepository>
用户目录下的.m2是所有jar包的地方; maven容器内jar包的位置
-->
<localRepository>/root/.m2</localRepository>
<pluginGroups>
</pluginGroups>
<proxies>
</proxies>
<servers>
</servers>
<mirrors>
<mirror>
<id>nexus-aliyun</id>
<mirrorOf>central</mirrorOf>
<name>Nexus aliyun</name>
<url>http://maven.aliyun.com/nexus/content/groups/public</url>
</mirror>
</mirrors>
<profiles>
<profile>
<id>jdk-1.8</id>
<activation>
<activeByDefault>true</activeByDefault>
<jdk>1.8</jdk>
</activation>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
</properties>
</profile>
</profiles>
</settings>
流水线可以这样写:
stage('maven编译'){
agent {
docker {
image 'maven:3-alpine'
args '-v /var/jenkins_home/appconfig/maven/.m2:/root/.m2'
// args '-v /a/settings.xml:/app/settings.xml'
// docker run -v /a/settings.xml:/app/settings.xml
}
}
steps {
//git下载来的代码目录下
sh 'pwd && ls -alh'
sh 'mvn -v'
//打包,jar.。默认是从maven中央仓库下载。 jenkins目录+容器目录;-s指定容器内位置
//只要jenkins迁移,不会对我们产生任何影响
//workdir
//每一行指令都是基于当前环境信息。和上下指令无关
sh 'mvn clean package -s "/var/jenkins_home/appconfig/maven/settings.xml" -Dmaven.test.skip=true '
//jar包推送给maven repo ,nexus
}
//原理: jenkins在解析流水线期间,可以任意访问jenkins家目录的位置和相关环境信息
args '-v /var/jenkins_home/appconfig/maven/.m2:/root/.m2':
maven依赖的jar包挂载到对应位置,这样多次构建同一个项目无需再次重复下载jar包。
但是这样依然会报错,这涉及到了临时容器的问题
临时容器问题
临时容器导致的问题
- 第一次检出代码,默认在 /var/jenkins_home/workspace/cicdtest (这个才是实际的工作空间,也就是我们项目存储在jenkins的地址)
- 但是当使用docker临时agent的时候,每一个临时容器运行又分配临时目录 /var/jenkins_home/workspace/cicdtest@1;这里面放到就是上面workspace/cicdtest的内容
- 在临时容器里面运行的mvn package命令时,实际所有maven命令操作都会在这个/var/jenkins_home/workspace/cicdtest@1临时目录进行操作
- package包实际是打到了 /var/jenkins_home/workspace/cicdtest@1 位置 ,最大的问题是临时目录用完就删除了,我们打的包实际也消失了。
- 当我们走到下一步进行打包镜像时,实际又是在 /var/jenkins_home/workspace/cicdtest这个实际工作目录
- 但是这个位置没有运行过 mvn clean package ,没有生成target。
总结就是我们执行maven命令的工作地址是个临时目录,执行完命令相当于什么都没有做所以会报错。
为了解决这个问题,就不要在临时空间进行操作,直接cd到真实的工作目录执行命令。
pipeline{
//任何一个代理可用即可执行
agent any
//定义一些环境信息
environment {
hello = "mella"
WS = "${WORKSPACE}"
}
//定义流水线的加工流程
stages {
//
stage('环境检查'){
steps {
echo "$hello"
sh 'pwd && ls -alh'
sh 'printenv'
sh 'echo ${GIT_BRANCH}'
}
}
stage('maven编译'){
agent {
docker {
image 'maven:3-alpine'
args '-v /var/jenkins_home/appconfig/maven/.m2:/root/.m2'
// args '-v /a/settings.xml:/app/settings.xml'
//docker run -v /a/settings.xml:/app/settings.xml
}
}
steps {
//git下载来的代码目录下
sh 'pwd && ls -alh'
sh 'mvn -v'
//打包,jar.。默认是从maven中央仓库下载。 jenkins目录+容器目录;-s指定容器内位置
//只要jenkins迁移,不会对我们产生任何影响
sh "echo 默认的工作目录:${WS}"
// sh 'cd ${WS}'
//workdir
//每一行指令都是基于当前环境信息。和上下指令无关
sh 'cd ${WS} && mvn clean package -s "/var/jenkins_home/appconfig/maven/settings.xml" -Dmaven.test.skip=true '
//jar包推送给maven repo ,nexus
//如何让他适用阿里云镜像源
}
}
stage('生成镜像'){
steps {
echo "打包..."
//检查Jenkins的docker命令是否能运行
sh 'docker version'
sh 'pwd && ls -alh'
sh 'docker build -t mella-test .'
//镜像就可以进行保存
}
}
stage('部署'){
steps {
echo "部署..."
sh 'docker rm -f java-devops-demo-dev'
sh 'docker run -d -p 8888:8888 --name mellaservertest mella-test'
}
}
}
post {
failure {
echo "这个阶段 完蛋了.... $currentBuild.result"
}
success {
echo "这个阶段 成了.... $currentBuild.result"
}
}
}
要注意 开始定义了一个全局变量WS = "$",这个是取出实际环境变量的地址赋值给WS,然后在下面 操作,如果直接在下面使用 cd "$" 进入的仍然是临时目录,而且进入目录和相关操作必须写在一行,不然进入的还是临时目录。
打包镜像就需要用的我们之前编写的Dockerfile,直接copy就可以用了,然后就是部署,首先强制删除以前的镜像再部署新镜像。
post是后置处理,当任何一个阶段出错都会报错,也可以单独配置到某一个阶段。
目前为止简单的CICD已经完成了:

其他功能植入
发送邮件通知
1、首先要在系统配置中加上系统管理员地址,由于现在访问不了gmail,就先用qq代替。

2、然后qq邮箱开启pop3服务:

会获得一串授权码,记得复制,当账户密码用。
3、账号密码各种空能填的都填上

4、邮件模板
emailext body: '''<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>${ENV, var="JOB_NAME"}-第${BUILD_NUMBER}次构建日志</title>
</head>
<body leftmargin="8" marginwidth="0" topmargin="8" marginheight="4"
offset="0">
<table width="95%" cellpadding="0" cellspacing="0" style="font-size: 11pt;
font-family: Tahoma, Arial, Helvetica, sans-serif">
<h3>本邮件由系统自动发出,请勿回复!</h3>
<tr>
<br/>
以下为${PROJECT_NAME }项目构建信息</br>
<td><font color="#CC0000">构建结果 - ${BUILD_STATUS}</font></td>
</tr>
<tr>
<td><br/>
<b><font color="#0B610B">构建信息</font></b>
<hr size="2" width="100%" align="center"/>
</td>
</tr>
<tr>
<td>
<ul>
<li>项目名称 : ${PROJECT_NAME}</li>
<li>构建编号 : 第${BUILD_NUMBER}次构建</li>
<li>触发原因: ${CAUSE}</li>
<li>构建状态: ${BUILD_STATUS}</li>
<li>构建日志: <a
href="${BUILD_URL}console">${BUILD_URL}console</a></li>
<li>构建 Url : <a href="${BUILD_URL}">${BUILD_URL}</a></li>
<li>工作目录 : <a
href="${PROJECT_URL}ws">${PROJECT_URL}ws</a></li>
<li>项目 Url : <a href="${PROJECT_URL}">${PROJECT_URL}</a>
</li>
</ul>
<h4><font color="#0B610B">最近提交</font></h4>
<ul>
<hr size="2" width="100%"/>
${CHANGES_SINCE_LAST_SUCCESS, reverse=true, format="%c", changesFormat="
<li>%d [%a] %m
</li>
"}
</ul>
详细提交: <a href="${PROJECT_URL}changes">${PROJECT_URL}changes</a><br/>
</td>
</tr>
</table>
</body>
</html>''', subject: '${ENV, var="JOB_NAME"}-第${BUILD_NUMBER}次构建日志', to: 'flitsneak@gmail.com'
一般来说通知要通知到jira,禅道或者钉钉等。
发布版本到镜像仓库
发布镜像到镜像仓库,发布版本。
阿里云举例,实际应用的是aws的ECR,但是ECR操作太简单了,所以以阿里云的ACR为例。
首先是开通个人镜像仓库:

然后设置登录镜像仓库的密码,接着创建一个镜像仓库。

可以看到支持很多仓库自动构建镜像,我们不选择这种方式而是选择本地仓库手动确认生成镜像。
然后会看到阿里云推送镜像的指令:
#登录阿里云
docker login --username=flits**** registry.cn-beijing.aliyuncs.com
#推送镜像
docker tag [ImageId] registry.cn-beijing.aliyuncs.com/flitsneak/mellaserver:[镜像版本号]
docker push registry.cn-beijing.aliyuncs.com/flitsneak/mellaserver:[镜像版本号]
实际写法:
docker login -u ${ALIYUN_USR} -p ${ALIYUN_PSW} registry.cn-beijing.aliyuncs.com
docker tag cicdtset registry.cn-beijing.aliyuncs.com/flitsneak/mellaserver:${APP_VER}
docker push registry.cn-beijing.aliyuncs.com/flitsneak/mellaserver:${APP_VER}
注意到我们引用了变量代替了账户密码,这个其实是系统的credentials,除了配置系统变量还可以配置阶段变量,先看系统变量如何配置:
首先进入jenkins,选择系统管理———》Manage Credentials

可以看到我们添加的全局凭据,我们之前设置过gitee私有仓库。
现在添加阿里云镜像仓库:

添加账户密码就可以了,还要命名一个ID方便取引用变量,这样在Jenkinsfile中声明变量:
environment {
//引用Jenkins配置的全局秘钥信息
ALITUN=credentials("flistsneakrepo")
}
取用相应的账户密码就是加对应下划线即可。
至于stage里定义局部credentials使用片段生成器即可。

然后选择separated,给账户密码分别起个引用别名,用的时候直接引用即可。
这样推送镜像就OK了。
还有参数化构建,手动选择input等等太简单就不搞了。
通过脚本及Generic Webhook,可以实现选择部署,这个是个大工程以后待完善。
监听拉取固定分支
Generic Webhook 动态部署。
Q.E.D.






Comments | 0 条评论