first commit
This commit is contained in:
335
专栏/Jenkins持续交付和持续部署/01.Jenkins的安装配置与日常维护.md
Normal file
335
专栏/Jenkins持续交付和持续部署/01.Jenkins的安装配置与日常维护.md
Normal file
@@ -0,0 +1,335 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
01.Jenkins的安装配置与日常维护
|
||||
Jenkins 安装与系统管理
|
||||
|
||||
本章节作为<<使用Jenkins持续交付与部署>>系列文章的第一篇,主要介绍一下持续集成工具Jenkins的安装配置以及日常维护Jenkins的一些系统管理说明。本次持续交付/持续部署教程主要是针对Jenkins以及与之集成的一些开源工具进行扩展说明的,下面开始本章的内容介绍。
|
||||
|
||||
本小节课程主要涉及到的内容如下:
|
||||
|
||||
|
||||
安装Jenkins
|
||||
Jenkins 系统管理与基础配置
|
||||
|
||||
|
||||
介绍
|
||||
|
||||
在开始正式安装Jenkins之前,首先需要了解一下Jenkins是什么。
|
||||
|
||||
[Jenkins](https://jenkins.io/zh)是一款由Java编写的开源的可扩展持续集成工具。允许持续集成和持续交付项目,并通过各种扩展插件可以处理任何类型的集成构建或部署操作。
|
||||
|
||||
|
||||
Jenkins 提供了软件开发的持续集成服务。当前持续集成(CI)已成为许多开发团队在整个软件生命周期内保证代码质量的必要流程。它是一种实践,旨在缓和和稳固软件的构建过程,而Jenkins使持续集成变成可能。它的主要目标是监控软件开发流程,快速定位并报告问题。丰富而众多的扩展插件,使得Jenkins能够帮助研发、运维、QA团队在软件测试(接口测试、单元测试等)、软件构建(maven、ant、gradle)、软件部署(shell、docker、ansible等等)等CI/CD整个环节进行良好的协作,提高工作效率。
|
||||
|
||||
安装
|
||||
|
||||
简单了解了jenkins是什么,接下来安装一下Jenkins。Jenkins的安装相对简单,可以通过war包、rpm包、Docker等方式安装,这里简单介绍两种安装方式,虽然安装方式不一样,但访问方法和配置都是一样的,所以凭个人喜好及实际情况选择安装方式。
|
||||
|
||||
使用rpm方式安装
|
||||
|
||||
使用rpm安装,首先要确保已经安装好jdk1.8+,使用最新版本时,如果jdk的版本低于1.8,对于jenkins的使用没有问题,但是在后续使用某个插件的时候可能会出现问题。
|
||||
|
||||
配置jdk
|
||||
|
||||
去 jdk官网 下载jdk1.8
|
||||
|
||||
$ tar xf jdk-8u231-linux-x64.tar.gz -C /usr/local/
|
||||
|
||||
# /etc/profile 添加如下内容
|
||||
export JAVA_HOME=/usr/local/jdk1.8.0_231
|
||||
export JRE_HOME=${JAVA_HOME}/jre
|
||||
export CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/lib
|
||||
export PATH=${JAVA_HOME}/bin:$PATH
|
||||
|
||||
$ source /etc/profile
|
||||
|
||||
$ java -version
|
||||
java version "1.8.0_231"
|
||||
Java(TM) SE Runtime Environment (build 1.8.0_231-b11)
|
||||
Java HotSpot(TM) 64-Bit Server VM (build 25.231-b11, mixed mode)
|
||||
|
||||
|
||||
配置好jdk后,安装Jenkins。
|
||||
|
||||
#去官网下载最新的jenkins rpm包
|
||||
$ wget https://pkg.jenkins.io/redhat-stable/jenkins-2.204.2-1.1.noarch.rpm
|
||||
|
||||
# 下载完成后安装
|
||||
$ yum localinstall jenkins-2.204.2-1.1.noarch.rpm
|
||||
|
||||
# 启动
|
||||
$ systemctl start jenkins
|
||||
|
||||
|
||||
使用docker启动
|
||||
|
||||
$ docker run -u root -it -d --name jenkins -p 8080:8080 -p 50000:50000 -v jenkins-data:/var/jenkins_home jenkins/jenkins
|
||||
|
||||
|
||||
获取镜像可以从docker hub搜索关键字获取。
|
||||
|
||||
第一次启动时会拉取镜像。
|
||||
|
||||
$ docker run -u root -it -d --name jenkins -p 8080:8080 -p 50000:50000 -v jenkins-data:/var/jenkins_home jenkins/jenkins
|
||||
Unable to find image 'jenkins/jenkins:latest' locally
|
||||
latest: Pulling from jenkins/jenkins
|
||||
3192219afd04: Pull complete
|
||||
17c160265e75: Pull complete
|
||||
cc4fe40d0e61: Pull complete
|
||||
9d647f502a07: Pull complete
|
||||
d108b8c498aa: Pull complete
|
||||
1bfe918b8aa5: Pull complete
|
||||
dafa1a7c0751: Pull complete
|
||||
78d3391dc013: Pull complete
|
||||
c1c87cf7f0bf: Pull complete
|
||||
4bfacf44cce2: Pull complete
|
||||
9eaefa421a64: Pull complete
|
||||
043144b011c9: Pull complete
|
||||
3d9c8a4a5cb8: Pull complete
|
||||
05fca0659bbd: Pull complete
|
||||
14241c82960f: Pull complete
|
||||
41a13492424a: Pull complete
|
||||
d7aa64814e63: Pull complete
|
||||
19049899ad6a: Pull complete
|
||||
3b23f8338127: Pull complete
|
||||
Digest: sha256:676448a326f96a991d2d32ffbe52f239c0d2c40de3538af2ae6f18d88bf3cb56
|
||||
Status: Downloaded newer image for jenkins/jenkins:latest
|
||||
11f86c1472fc4d89e1af5c9340f68e8318b6b7322d46abe30bff23f092a62fe5
|
||||
|
||||
# 查看启动的容器
|
||||
$ docker ps
|
||||
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
|
||||
11f86c1472fc jenkins/jenkins "/sbin/tini -- /usr/…" 6 minutes ago Up 6 minutes 0.0.0.0:8080->8080/tcp, 0.0.0.0:50000->50000/tcp jenkins
|
||||
|
||||
|
||||
容器启动时映射了8080和50000端口,其中:
|
||||
|
||||
8080端口是web访问的端口。
|
||||
|
||||
50000端口是基于JNLP的Jenkins代理(slave)通过TCP与Jenkins master 进行通信的端口。
|
||||
|
||||
-v jenkins-data:/var/jenkins_home 为挂载的volume卷,将容器内的/var/jenkins_home目录挂载到jenkins-data卷;这个卷如果不存在,启动容器的时候会自动创建,也可以通过docker volume create jenkins-data命令手动创建或者映射到宿主机指定的目录。
|
||||
|
||||
容器启动成功后,通过访问IP:8080,如下Jenkins界面:
|
||||
|
||||
|
||||
|
||||
提示管理员秘密从/var/jenkins_home/secrets/initialAdminPassword路径获取(对于使用war包或者rpm包安装的Jenkins路径可能会有所差异),如果使用rpm方式或者war包安装,直接在jenkins所在服务器上cat一下该文件即可,如果是docker方式安装,可根据上面获取的Container_ID或者Container_name执行docker命令:
|
||||
|
||||
$ docker exec -it jenkins cat /var/jenkins_home/secrets/initialAdminPassword
|
||||
1dc17f65d83c4c56b13cbae230b5e7c6
|
||||
|
||||
|
||||
获取到密码,密码输入成功后:
|
||||
|
||||
|
||||
|
||||
选择安装所需插件(如:gradle,maven,ant……),同样也可忽略这一步,后面有需要再安装所需插件,由于网络原因,安装时间可能有点长,需要耐心等待(或者参考下面的”修改插件更新源”)。然后设置好管理员用户名和密码登录即可,下面就是安装好后的jenkins界面。
|
||||
|
||||
|
||||
|
||||
到这里 jenkins 就安装完成。
|
||||
|
||||
Jenkins基础配置
|
||||
|
||||
系统管理
|
||||
|
||||
安装好Jenkins后,首先来看一下Jenkins中的系统管理(Manage Jenkins)面板。系统管理面板是整个Jenkins管理、配置、维护的入口。该面板下包含多个菜单项,默认包括如下:
|
||||
|
||||
Configure System(系统配置):该菜单用来设置Jenkins的全局配置以及各种工具插件的全局配置,包括jenkins的工作目录、可同时构建的job的数量、jdk环境变量等等。
|
||||
|
||||
configure Global Security(全局工安全配置):Jenkins的全局安全配置入口,可以配置用户权限、API Token、agent代理等信息。
|
||||
|
||||
Configure Credentials(凭据配置):该菜单项用来创建各种类型的凭据,用于对系统或者服务进行认证,比如登录服务器,登录源码仓库的认证等。
|
||||
|
||||
Global Tool Configuration(全局工具配置):该菜单项用来对Jenkins项目里使用到的全局工具进行配置,比如Jdk的配置,代码编译工具 maven/ant/gradle工具的配置,代码拉取命令git的配置等等。
|
||||
|
||||
Manage Plugins(插件管理):Jenkins各种插件得入口,用来对插件进行增删更新等操作,jenkins强大的功能和扩展离不开各种插件的支持,所以在初期这是一个使用比价频繁的菜单。
|
||||
|
||||
System Information(系统信息):该菜鸟用来显示Jenkins系统的各种属性信息,包括jenkins所在系统的配置信息,jenkins环境变量信息,jenkins所安装的插件信息等。
|
||||
|
||||
System Log(系统日志):用来展示jenkins的系统日志。
|
||||
|
||||
Jenkins Cli(Jenkins命令行):该菜单项介绍了如何使用jenkins-cli.jar在命令行操作jenkins,这个菜单提供了大量可以在命令行操作jenkins的命令以及使用说明,比如重启jenkins,可以使用如下方式。
|
||||
|
||||
java -jar jenkins-cli.jar -s ${jenkins_url} restart
|
||||
|
||||
|
||||
Manage Nodes(管理节点):该菜单项用于管理jenkins的节点信息,比如增加或减少jenkins slave节点,修改master或者slave节点的配置等,如何配置将在后面章节介绍。
|
||||
|
||||
Manage Users(用户管理):该菜单项用于管理jenkins的系统用户信息,包括用户信息的增删改查等操作。
|
||||
|
||||
上面列出了jenkins的一些常用的配置菜单,在使用jenkins的时候,对于上面的菜单或多或少的都会接触到,并且在以后的学习中会对某个菜单里的使用进行详细的配置说明,这里先不多做介绍,当然如果你有些迫不及待了,也可以自己试一下。
|
||||
|
||||
配置文件
|
||||
|
||||
了解了Jenkins的面板配置,再来看一下jenkins的配置文件。Jenkins安装好以后,使用的默认端口为8080,通过ip加端口访问jenkins面板,点击”Manage Jenkins”–>”Configure System“,进入系统配置界面,可以看到jenkins的默认配置,比如下图所示:
|
||||
|
||||
|
||||
|
||||
通过该图可以看到jenkins的主目录为/var/lib/jenkins,该目录为Jenkins的实际工作目录,对于jenkins的配置修改以及存放jenkins项目配置的文件都会放到此目录下, 并不是运行Jenkins程序的目录。由上图发现该目录并不能在jenkins ui界面修改,对于要想修改Jenkins的工作目录路径的需求应该怎么实现呢,或者如果想要修改jenkins的启动端口,jvm内存等,又要如何修改呢?本小节就简单介绍一下如何修改这些配置。
|
||||
|
||||
对于使用Tomcat方式启动的Jenkins服务,直接修改tomcat的端口和jvm内存设置即可。有关工作目录的修改,大体就与使用rpm安装的jenkins服务相同了(建议保持默认即可);对于使用容器镜像方式启动的jenkins服务如何配置,可参考docker hub仓库下jenkins镜像的Dockerfile 。
|
||||
|
||||
所以有关使用Tomcat方式或者容器化安装的Jenkins服务配置文件的说明这里不多说,主要介绍一下使用rpm方式安装的jenkins的配置文件的管理。
|
||||
|
||||
我这里使用rpm方式安装的jenkins服务,默认启动命令为systemctl start jenkins(以centos7为例),但是在系统默认存放启动文件的目录有没有找到与jenkins有关的service文件,该如何下手呢?通过systemctl status jenkins命令,可以获取jenkins启动的脚本文件,比如下面示例:
|
||||
|
||||
|
||||
|
||||
找到了该启动文件,首先看一下该文件的内容。
|
||||
|
||||
$ cat /etc/rc.d/init.d/jenkins |grep -v "^#\|^$"|grep "^JENKINS\|^PARAMS"
|
||||
|
||||
JENKINS_WAR="/usr/lib/jenkins/jenkins.war"
|
||||
JENKINS_CONFIG=/etc/sysconfig/jenkins
|
||||
JENKINS_PID_FILE="/var/run/jenkins.pid"
|
||||
JENKINS_LOCKFILE="/var/lock/subsys/jenkins"
|
||||
PARAMS="--logfile=/var/log/jenkins/jenkins.log --webroot=/var/cache/jenkins/war --daemon"
|
||||
|
||||
|
||||
说明
|
||||
|
||||
|
||||
我这里通过grep将所需要的内容过滤了出来,如果想要看全部内容,可以将过滤条件去掉。
|
||||
|
||||
|
||||
然后通过ps命令查看一下Jenkins的进程信息。
|
||||
|
||||
$ ps -ef|grep jenkins
|
||||
|
||||
root 15655 1 2 Apr15 ? 00:31:11 /usr/local/jdk1.8.0_231/bin/java -Djava.awt.headless=true -DJENKINS_HOME=/var/lib/jenkins -jar /usr/lib/jenkins/jenkins.war --logfile=/var/log/jenkins/jenkins.log --webroot=/var/cache/jenkins/war --httpPort=8080 --debug=5 --handlerCountMax=100 --handlerCountMaxIdle=20
|
||||
|
||||
|
||||
由上面信息可以看到,jenkins服务主要配置文件为/etc/sysconfig/jenkins,使用的 java为自定义的/usr/local/jdk1.8.0_231/bin/java,启动方式也是通过war包启动,日志文件存放在/var/log/jenkins/jenkins.log文件里,jenkins服务运行所需要的程序文件放到了/var/cache/jenkins/war目录。当然,这些配置的路径,都是可以修改的。
|
||||
|
||||
了解完基本配置信息,下面看一下jenkins的主配置文件。
|
||||
|
||||
$ cat /etc/sysconfig/jenkins|grep -v "^#\|^$"
|
||||
|
||||
JENKINS_HOME="/var/lib/jenkins"
|
||||
JENKINS_JAVA_CMD=""
|
||||
JENKINS_USER="root"
|
||||
JENKINS_JAVA_OPTIONS="-Djava.awt.headless=true"
|
||||
JENKINS_PORT="8080"
|
||||
JENKINS_LISTEN_ADDRESS=""
|
||||
JENKINS_HTTPS_PORT=""
|
||||
JENKINS_HTTPS_KEYSTORE=""
|
||||
JENKINS_HTTPS_KEYSTORE_PASSWORD=""
|
||||
JENKINS_HTTPS_LISTEN_ADDRESS=""
|
||||
JENKINS_HTTP2_PORT=""
|
||||
JENKINS_HTTP2_LISTEN_ADDRESS=""
|
||||
JENKINS_DEBUG_LEVEL="5"
|
||||
JENKINS_ENABLE_ACCESS_LOG="no"
|
||||
JENKINS_HANDLER_MAX="100"
|
||||
JENKINS_HANDLER_IDLE="20"
|
||||
JENKINS_EXTRA_LIB_FOLDER=""
|
||||
JENKINS_ARGS=""
|
||||
|
||||
|
||||
在这里,就可以看到jenkins的详细配置了,包括Jenkins的主要工作目录,Jenkins的端口,运行Jenkins的用户(默认为jenkins)以及jenkins启动时的java参数等。如果要修改jenkins的jvm参数,只需要将配置放到JENKINS_JAVA_OPTIONS参数里即可,多个参数使用空格隔开,修改完配置文件,不要忘了重启Jenkins服务。
|
||||
|
||||
Jenkins汉化
|
||||
|
||||
大部分版本的Jenkins安装好以后,默认的语言为英文,有些同学可能不太习惯英文版的jenkins,这时就需要对jenkins进行汉化,汉化比较简单,下面进行配置。
|
||||
|
||||
”Manage Jenkins”—> “Manage Plugins“在可选插件中找到Locale plugin插件,点击安装即可。安装完以后在”Config System“菜单中找到Locale选项配置,设置”Default Language“为zh_CN,并勾选Ignore browser preference and force this language to all users
|
||||
|
||||
如下图所示:
|
||||
|
||||
|
||||
|
||||
配置好后重启Jenkins,从新登录界面就发现英文菜单变成了中文菜单。
|
||||
|
||||
Jenkins针对不同的安装方式或者不同的版本汉化方法可能不一样,大多数版本使用上面的方法就能汉化成功。但是有的版本安装该插件后,重启jenkins后发现面板还是英文的,汉化不成功。那么接下来就需要在安装Localization: Chinese (Simplified)插件了,安装好后重启Jenkins即可。如果汉化完发现面板有一部分是中文一部分是英文的话,可能与jenkins的版本或者安装方式有关了。
|
||||
|
||||
修改插件更新源
|
||||
|
||||
Jenkins强大的功能和扩展离不开插件的支持。在安装jenkins时,或者安装完jenkins以后,需要安装很多必须的插件,使用官方默认的jenkins插件源安装插件时,要么速度很慢,要么安装失败,使得无论是工作效率还是学习效率都大大降低,为了解决此问题,需要修改一下jenkins下载插件的镜像源。
|
||||
|
||||
进入”Manage-Jenkins”–>”Manage Plugins” —> “高级“页面,网上大多数方法都是修改该页面下的”升级站点“下URL输入框的值,改为清华源或者Jenkins插件中心国内源。如下图所示:-
|
||||
|
||||
|
||||
然而修改完成后,下载镜像还是很慢,这是因为Jenkins通过解析update-center.json 文件的方式来获取插件版本以及下载插件,但是jenkins使用私钥来给update-center.json文件做了签名,只有通过了公钥验证的update-center.json文件,才会被使用。所以还需要替换Jenkins中使用的秘钥和私钥文件。
|
||||
|
||||
替换证书文件
|
||||
|
||||
到https://github.com/jenkins-zh/mirror-adapter/rootCA下载mirror-adapter.crt文件,放到/var/cache/jenkins/war/WEB-INF/update-center-rootCAs目录下即可。
|
||||
|
||||
对于该配置流程,Jenkins中文社区帮大家把秘钥和地址的问题解决了,可以使用如下方式:
|
||||
|
||||
在jenkins面板的右下角,点击Jenkins中文社区,在跳转的界面会有”更新中心镜像设置“。如下所示:-
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
先点击使用,然后在点击”设置更新中心地址“,在跳转的页面输入上面给出的地址即可。
|
||||
|
||||
如果没有上面的Jenkins中文社区按钮,可能是你没有汉化导致的。
|
||||
|
||||
密码修改
|
||||
|
||||
在使用Jenkins的时候常常遇到了忘记Jenkins管理员登录密码的情况,如果忘记了密码怎么办?可以修改Jenkins的配置文件${JENKINS_HOME}/config.xml,找到:
|
||||
|
||||
<useSecurity>true</useSecurity>
|
||||
|
||||
|
||||
将true 改为false,然后重启jenkins。
|
||||
|
||||
Jenkins重启后,访问Jenkins 就可以直接跳过验证,直接跳转到Jenkins面板界面了。然后点击”Manage Jenkins”–>”Config Global Security“,勾选“启用安全”,在”访问控制“选项的”安全域“属性,选中”Jenkins’ own user database“,授权策略属性选中”Logged-in users can do anything“。如下如所示:-
|
||||
|
||||
|
||||
保存后,到”Manage Users“界面重置管理员用户密码即可。
|
||||
|
||||
备份与恢复
|
||||
|
||||
使用jenkins另一个必不可少的配置就是对jenkins的备份。Jenkins的所有的数据都是以文件的形式存放在${JENKINS_HOME}目录中。所以不管是迁移还是备份,只需要将${JENKINS_HOME}打包后在拷贝,然后将打包的文件解压到新的${JENKINS_HOME}目录就行了。备份jenkins的方法有很多种,本文就简单介绍一下常用的备份方法,使用ThinBackup插件。
|
||||
|
||||
在插件管理页面找到ThinBackup插件,安装即可(如果你不会安装,看一下第二篇的内容吧)。安装完以后,在manage jenkins界面会出现ThinBackup菜单项,点击进去后进入settings设置界面,进行备份设置。如下所示设置:
|
||||
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
Backup directory:备份目录,用于存储备份的文件,如果指定的目录不存在,默认在进行备份之前会自动创建。
|
||||
|
||||
Backup schedule for full backups:进行备份的计划任务,与linux下的crontab一样,其中上面的H,代表哈希,为了允许定期调度的任务在系统上产生均匀负载,应尽可能使用符号H(用于“散列”)。例如,如果多个job都设置使用0 0 * * *将导致负载大幅飙升。相比之下,使用H H *仍然会每天执行一次每个job,但不会同时执行,更好地使用有限的资源。
|
||||
|
||||
Backup schedule for differential backups:进行差异化备份的计划任务,同上。
|
||||
|
||||
Max number of backup sets:备份的最大数量。
|
||||
|
||||
Files excluded from backup (regular expression):不需要进行备份的文件的正则表达式。
|
||||
|
||||
Wait until Jenkins/Hudson is idle to perform a backup:等待jenkins空闲多长时间后进行备份。
|
||||
|
||||
Backup build results:如果启用此选项,还将备份构建结果,一般不建议勾选此选项。
|
||||
|
||||
Backup ‘userContent’ folder:备份${jenkins_home}/userContent目录下的文件。
|
||||
|
||||
Backup next build number file:备份jenkins构建的build id文件。
|
||||
|
||||
Backup plugins archives:备份插件。
|
||||
|
||||
Clean up differential backups:完成备份以后清除所有的差异备份。
|
||||
|
||||
Move old backups to ZIP files:如果选中此选项,则无论何时执行新的完整备份,所有旧备份集都将移至ZIP文件。每个ZIP文件将包含一个备份集,即一个完整备份和任何引用该备份文件的差异备份。文件名将标识包含备份的时间范围(即完整备份的时间戳和最新差异备份的时间戳)。
|
||||
|
||||
备份策略根据自己实际情况定义即可。
|
||||
|
||||
保存后回到ThinBackup界面,除了可以自动进行备份外,也可以通过手动备份,点击Backup Now按钮就会立即进行备份。如下按钮:
|
||||
|
||||
|
||||
|
||||
备份完后会在上面设定的备份目录下生成一个以FULL-开头,以当前时间(精确到分)为结尾的目录,该目录下就是默认所有的Jenkins配置。
|
||||
|
||||
当要使用备份文件进行恢复的时候,点击restore按钮,在跳转的界面中就会显示出该备份文件的备份时间,点击Restore即可。
|
||||
|
||||
总体来说,使用该插件备份相对简单,这里就不在多说,有兴趣的可以自己试一下。
|
||||
|
||||
|
||||
|
||||
|
522
专栏/Jenkins持续交付和持续部署/02.Jenkins强大的插件功能.md
Normal file
522
专栏/Jenkins持续交付和持续部署/02.Jenkins强大的插件功能.md
Normal file
@@ -0,0 +1,522 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
02.Jenkins强大的插件功能
|
||||
在上一节的章节中,简单介绍了一下Jenkins的安装以及一些与jenkins系统管理有关的配置说明。其中有提到过jenkins强大的功能离不开各种插件的支持,所以本节就先来介绍一下本系列教程要用的到一些公共插件以及相关的配置说明。
|
||||
|
||||
安装好Jenkins后开始进行配置,首先安装插件,本次教程用到如下公共插件:
|
||||
|
||||
|
||||
Maven Integration plugin:用于创建maven项目的插件
|
||||
Pipeline:用于创建pipeline项目的插件
|
||||
Ansible Plugin:用于使用ansible插件
|
||||
Git/Git Parameter:用于拉取git代码的插件
|
||||
Docker Pipeline:用于在Pipeline中使用与Docker相关的语法插件
|
||||
SonarQube Scanner:用于执行Sonarqube scan的插件
|
||||
Publish Over SSH:通过ssh协议发送文件到远程服务器
|
||||
Email Extension Template:用于发送邮件给特定的人或组
|
||||
Role-based Authorization Strategy:用户权限管理插件
|
||||
|
||||
|
||||
回到首页,点击”manage Jenkins(管理jenkins) -> Manage Plugins(插件管理)” 菜单,找到插件选中,install即可。
|
||||
|
||||
比如,安装ansible插件。
|
||||
|
||||
|
||||
|
||||
搜索框输入关键字,就会列出包含该关键字的插件,由于我已经安装了ansible的插件,所以这里就不会列出已安装的。
|
||||
|
||||
配置全局工具
|
||||
|
||||
JDK
|
||||
|
||||
回到首页,点击”manage Jenkins -> Global Tool Configuration(全局工具配置)” 菜单,
|
||||
|
||||
首先看到Jdk工具配置,在上一节的文章中已经配置好了jdk,在这里直接配置引入即可,如下所示。
|
||||
|
||||
|
||||
|
||||
Maven
|
||||
|
||||
继续往下浏览,会发现有maven工具的配置,由于还没有安装,这里先配置一下。
|
||||
|
||||
到apache官网下载(根据个人需要选择指定的版本下载)。
|
||||
|
||||
$ wget https://downloads.apache.org/maven/maven-3/3.6.3/source/apache-maven-3.5.4-src.tar.gz
|
||||
|
||||
|
||||
解压后直接放到指定目录,然后在/etc/profile 添加环境变量配置,比如我的配置如下。
|
||||
|
||||
$ tar xf apache-maven-3.5.4-src.tar.gz
|
||||
$ vi /etc/profile
|
||||
export M2_HOME=/opt/apache-maven-3.5.4
|
||||
export PATH=$PATH:$M2_HOME/bin
|
||||
|
||||
$ source /etc/profile
|
||||
|
||||
|
||||
验证。
|
||||
|
||||
$ mvn -version
|
||||
Apache Maven 3.5.4 (1edded0938998edf8bf061f1ceb3cfdeccf443fe; 2018-06-18T02:33:14+08:00)
|
||||
Maven home: /opt/apache-maven-3.5.4
|
||||
Java version: 1.8.0_231, vendor: Oracle Corporation, runtime: /usr/local/jdk1.8.0_231/jre
|
||||
Default locale: en_US, platform encoding: UTF-8
|
||||
OS name: "linux", version: "3.10.0-1062.4.1.el7.x86_64", arch: "amd64", family: "unix"
|
||||
|
||||
|
||||
然后在jenkins中添加maven工具的配置信息。
|
||||
|
||||
|
||||
|
||||
需要注意的是:
|
||||
|
||||
maven编译代码时,默认${Maven_HOME}/conf/路径下的settings.xml文件配置了读取存储公共依赖jar包的仓库地址(默认是阿里云的仓库)以及用户名和密码,所以如果有用其它地址的仓库(比如私有nexus),需要修改该配置文件。
|
||||
|
||||
关于Jenkins配置maven集成的内容到此就完成了,还是比较简单的。
|
||||
|
||||
Email Extension Template 插件
|
||||
|
||||
Email Extension Template插件用来在执行job的时候发送email给要通知的用户或者组。
|
||||
|
||||
配置Jenkins Location和Extended E-mail Notification,其中系统管理员邮件地址一定要和User Name值一致。如下所示。
|
||||
|
||||
-
|
||||
|
||||
|
||||
配置默认的邮件主题以及邮件内容
|
||||
|
||||
该插件下的”Default Subject“和”Default Content“参数分别用来设置发送邮件的主题和内容,默认的配置如下所示。
|
||||
|
||||
|
||||
|
||||
这两个参数的内容均使用了jenkins内置的全局变量,其中$PROJECT_NAME表示项目的名称,$BUILD_NUMBER表示项目构建的ID,$BUILD_STATUS表示项目构建状态。如果想要自定义邮件的主题和内容,直接在这里修改即可。
|
||||
|
||||
配置好以后点击”Test configuration“测试一下。-
|
||||
|
||||
|
||||
用户权限控制
|
||||
|
||||
在使用Jenkins的时候也经常会遇到给不同的用户分配不同的权限的问题,Jenkins的权限管理通过”Role-based Authorization Strategy“插件进行管理。如何安装该插件不在演示,下面看一下如何开启该插件。
|
||||
|
||||
点击”Manage Jenkins”–>”Config Global Security“,在”访问控制“选项的”授权策略“中,选择”Role-Based Strategy“保存即可。保存退出后,在”manage Jenkins“中会出现Manager and Assign Roles选项,该选项用于管理并配置用户权限。
|
||||
|
||||
”Manage and Assign Roles“选项下有三个菜单,如下所示。
|
||||
|
||||
|
||||
|
||||
该插件的使用流程大概如下。
|
||||
|
||||
通过Mnaage Roles选项创建角色,可以是全局角色也可以是项目角色。如果是项目角色,则可以通过正则语法给相应的项目角色创建匹配规则,使该角色对某些项目有管理权限。
|
||||
|
||||
通过Assign Roles选项分配角色,分配角色的主体为Jenkins系统里的用户,可以通过“Manage Users”菜单查看到。
|
||||
|
||||
创建角色
|
||||
|
||||
点击Manage Roles,点击进去以后,会发现有一个默认的admin角色,该角色为具有管理员权限的用户角色。
|
||||
|
||||
创建一个全局角色,一个项目角色,如下所示。
|
||||
|
||||
|
||||
|
||||
在Role to add输入框中,输入角色名称(自定义),然后在Pattern中输入要管理的项目名称或者要匹配的多个jeknkin项目(job)的正则语法,比如我这里写的是test-.*表示匹配以test-开头的所有项目。添加以后,点击”Pattern“框中匹配项就可以显示该正则匹配到的项目名称了。然后在相应的权限配置栏中勾选该role的权限,比如对于凭据有什么权限,对于job有什么权限等,这里就需要根据自己的实际情况勾选了。
|
||||
|
||||
分配角色
|
||||
|
||||
创建好角色以后,就可以分配角色了,分配角色给谁呢?这就需要看jenkins里存在的用户了。点击”Manage-Jenkins”–>”Manage Users“,可以看到jenkins系统里所有的用户。然后回到”Manage and Assign Roles“菜单,点击”Assign Roles“,在跳转的界面中就可以看到刚刚创建的角色。同样的,既可以给用户分配全局角色,也可以给用户分配项目角色。
|
||||
|
||||
如下图所示。
|
||||
|
||||
|
||||
|
||||
然后就可以使用test-li用户登录了。需要注意的是该用户需要在Jenkins中存在。
|
||||
|
||||
此时用“test-li”账户登录发现没有权限,提示如下错误。
|
||||
|
||||
|
||||
|
||||
这是因为在创建角色的时候,没有给用户创建一个对Jenkins面板具有查看权限的全局角色,所以此时还需要去“Manage Roles”菜单里添加一个具有全局权限的角色,如下所示:
|
||||
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
创建一个名称为test-all的全局角色,该角色的权限为Overall/read,对于Jenkins面板有读取权限。该角色的凭据栏和agent栏都没有勾选,表示对Jenkins系统管理的凭据,node节点等都没有权限。
|
||||
|
||||
test-all 角色对Jenkins面板具有读取权限,保存后,再去从新分配权限,如下所示:
|
||||
|
||||
|
||||
|
||||
给test-li用户分配一个具有可以读取jenkins面板的权限,这样就可以使用该用户进行正常工作了。如下所示:
|
||||
|
||||
|
||||
|
||||
有关用户权限的分配说明就这里,所有的角色创建与权限分配大致就是这样一个流程。
|
||||
|
||||
Sonar
|
||||
|
||||
配置完公共插件,下面来介绍一下常用的持续交付过程中的使用代码质量检测插件:Sonar
|
||||
|
||||
介绍
|
||||
|
||||
Sonar是一个用于代码质量管理的开源平台,可以从多个维度管理和检测源代码的质量。通过插件形式,可以支持包括java,C#,C/C++,PL/SQL,Cobol,JavaScrip,Groovy等二十几种编程语言的代码质量管理与检测。sonar也是当下众多公司使用的一款代码质量分析工具。
|
||||
|
||||
sonar从如下几个维度检测代码质量:
|
||||
|
||||
|
||||
sonar通过PMD,CheckStyle,Findbugs等代码规则检测工具规范代码编写;
|
||||
|
||||
sonar也可以通过PMD,CheckStyle,Findbugs等代码规则检测工具检测出潜在的缺陷;
|
||||
|
||||
糟糕的复杂度分布文件、类、方法等,如果复杂度过高将难以改变,这会使得开发人员难以理解它们,且没有自动化的单元测试,对于程序中的任何组件的改变都将可能导致需要全面的回归测试;
|
||||
|
||||
sonar可以展示源码中重复严重的地方;
|
||||
|
||||
注释不足或者过多没有注释将使代码可读性变差,特别是当不可避免出现人员变动时,程序的可读性大幅度下降,而过多的注释又会使得开发人员将精力过多的花费在阅读注释上,亦违背初衷;-
|
||||
sonar可以很方便地统计并展示单元测试覆盖率;-
|
||||
通过sonar可以找出循环,展示包与包、类与类之间相互依赖关系,可以检测自定义的架构规则, 可以管理第三方的jar包,可以利用LCOM4检测单个任务规则的应用情况,检测耦合。
|
||||
|
||||
|
||||
sonar执行流程
|
||||
|
||||
开发人员将他们的代码提交到代码管理平台中(SVN,GIT等)。
|
||||
|
||||
持续集成工具自动触发构建,调用SonarScanner对项目代码进行扫描分析。
|
||||
|
||||
分析报告发送到SonarQube Server中进行加工。
|
||||
|
||||
SonarQube Server 加工并且保存分析报告到SonarQube Database中,通过UI显示分析报告。
|
||||
|
||||
sonar环境需求
|
||||
|
||||
SonarQube服务实例需要至少2GB的RAM才能有效运行,并且1GB的可用RAM用于操作系统。需要的磁盘空间量取决于所使用SonarQube分析的代码量。SonarQube必须安装在具有良好读写性能的硬盘上。sonar 的data文件夹包含Elasticsearch索引,当服务器启动并运行时,将在其上完成大量I/O操作. 因此,良好的读写硬盘性能将对整个SonarQube服务性能产生很大影响。
|
||||
|
||||
安装
|
||||
|
||||
sonar安装的方式也比较多,当然最简便的就是使用docker了,所以本次安装使用docker方式,下面看详细过程。
|
||||
|
||||
安装sonar需要安装数据库,这里使用mysql5.7。
|
||||
|
||||
下载mysql:5.7镜像。
|
||||
|
||||
docker pull mysql:5.7
|
||||
|
||||
|
||||
启动容器
|
||||
|
||||
$ docker run --name mysql5.7 -v /data/mysql5.7-data:/var/lib/mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.7
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
|
||||
/data/mysql5.7-data为挂载的宿主机目录
|
||||
|
||||
|
||||
进入容器中创建数据库与用户。
|
||||
|
||||
$ docker exec -it mysql5.7 bash
|
||||
|
||||
root@60253d27a5f5:/# mysql -uroot -p123456
|
||||
......
|
||||
|
||||
mysql>
|
||||
# 创建数据库db_sonar
|
||||
mysql> create database db_sonar character set utf8 collate utf8_general_ci;
|
||||
Query OK, 1 row affected (0.00 sec)
|
||||
|
||||
mysql> flush privileges;
|
||||
Query OK, 0 rows affected (0.00 sec)
|
||||
|
||||
#创建数据库用户,用户名密码都为sonar
|
||||
mysql> grant all privileges on db_sonar.* to 'sonar'@'%'identified by 'sonar' with grant option;
|
||||
Query OK, 0 rows affected, 1 warning (0.00 sec)
|
||||
|
||||
mysql> flush privileges;
|
||||
Query OK, 0 rows affected (0.00 sec)
|
||||
|
||||
......
|
||||
|
||||
|
||||
有关sonar使用mysql数据库的配置就完成了。
|
||||
|
||||
启动sonar容器
|
||||
|
||||
下载 sonarqube:6.7.5 镜像。
|
||||
|
||||
docker pull sonarqube:6.7.5
|
||||
|
||||
|
||||
启动sonarqube容器。
|
||||
|
||||
$ docker run -d --name sonar --link mysql5.7 -p 9000:9000 -p 9092:9092 -v /data/sonar/conf:/opt/sonarqube/conf -v /data/sonar/data:/opt/sonarqube/data -v /data/sonar/logs:/opt/sonarqube/logs -v /data/sonar/extensions:/opt/sonarqube/extensions -e "SONARQUBE_JDBC_USERNAME=sonar" -e "SONARQUBE_JDBC_PASSWORD=sonar" -e "SONARQUBE_JDBC_URL=jdbc:mysql://mysql5.7:3306/db_sonar?useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&useConfigs=maxPerformance&useSSL=false" sonarqube:6.7.5
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
|
||||
--link 表示sonar容器要连接到名称为mysql5.7容器,在配置数据库连接的时候,数据库地址可以直接写容器的名称
|
||||
-v 挂载相关配置到 /data/sonar/ 目录下
|
||||
-e 指定sonar启动时连接数据库的相关参数配置
|
||||
|
||||
|
||||
待容器全部启动成功后查看
|
||||
|
||||
$ docker ps
|
||||
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
|
||||
120cb90c2248 sonarqube:6.7.5 "./bin/run.sh" 5 weeks ago Up 2 seconds 0.0.0.0:9000->9000/tcp, 0.0.0.0:9092->9092/tcp sonar
|
||||
60253d27a5f5 mysql:5.7 "docker-entrypoint..." 5 weeks ago Up 8 minutes 0.0.0.0:3306->3306/tcp, 33060/tcp mysql5.7
|
||||
|
||||
|
||||
根据 http://ip:9000 访问web界面
|
||||
|
||||
|
||||
|
||||
到此,sonar安装完成。
|
||||
|
||||
配置
|
||||
|
||||
打开web界面,点击login in
|
||||
|
||||
默认账号密码都是admin,首次登录后,会有一个使用教程,根据提示,创建token。
|
||||
|
||||
|
||||
|
||||
随便输入一个,比如我输入的admin,language为java,build technolog为maven,如下所示。
|
||||
|
||||
|
||||
|
||||
点击finish即可。
|
||||
|
||||
点击finish以后跳转到首页,就会看到一个英文版的主页了,如果不习惯,也可以汉化。
|
||||
|
||||
汉化
|
||||
|
||||
点击顶层菜单”Administration”–”marketplace“,在下面箭头处搜索“chinese”,出现如下插件,点击“install”
|
||||
|
||||
|
||||
|
||||
稍等一会,如果安装不成功,报如下错误:
|
||||
|
||||
|
||||
|
||||
可能的原因是插件版本与sonar版本不兼容,可以去sonar官方github下载合适的汉化插件,比如我的版本是6.7.5,在github上发现sonar-l10n-zh-plugin-1.19该版本支持,下载放到宿主机/data/sonar/extensions/plugins 目录后,重启sonar容器即可。
|
||||
|
||||
$ pwd
|
||||
/data/sonar/extensions/plugins
|
||||
|
||||
$ wget https://github.com/SonarQubeCommunity/sonar-l10n-zh/releases/download/sonar-l10n-zh-plugin-1.19/sonar-l10n-zh-plugin-1.19.jar
|
||||
|
||||
$ docker restart sonar
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
|
||||
在部署sonar时挂载了宿主机目录,所以将插件放到宿主机目录即可。
|
||||
|
||||
|
||||
启动完成后,从新打开页面,到应用市场发现新安装的插件。-
|
||||
|
||||
|
||||
到此,sonar的配置基本就告一段落,接下来找一段代码测试一下sonar是否好用。
|
||||
|
||||
测试
|
||||
|
||||
这里以扫描maven项目为例,进入到项目工程根目录下,执行扫描命令为:
|
||||
|
||||
$ mvn sonar:sonar -Dsonar.host.url=http://sonar_ip:9000 -Dsonar.login=7e73499a981bf49e2f724d11479623a00cf97f1a
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
|
||||
sonar.login指定的值为刚登陆时生成的token
|
||||
|
||||
|
||||
token如果忘了怎么办,从新生成一个即可,如下所示,先回收,在重新生成
|
||||
|
||||
|
||||
|
||||
执行成功后,部分日志如下
|
||||
|
||||
66230 [INFO] Analysis report generated in 305ms, dir size=790 KB
|
||||
66816 [INFO] Analysis reports compressed in 584ms, zip size=408 KB
|
||||
67682 [INFO] Analysis report uploaded in 865ms
|
||||
67686 [INFO] ANALYSIS SUCCESSFUL, you can browse http://192.168.176.160:9000/dashboard/index/com.hivescm:fw-base-nop
|
||||
67687 [INFO] Note that you will be able to access the updated dashboard once the server has processed the submitted analysis report
|
||||
67687 [INFO] More about the report processing at http://192.168.176.160:9000/api/ce/task?id=AXBJZsjiw7C6SYqgng-6
|
||||
67816 [INFO] Task total time: 44.679 s
|
||||
68175 [INFO] ------------------------------------------------------------------------
|
||||
68176 [INFO] BUILD SUCCESS
|
||||
|
||||
|
||||
界面如下。
|
||||
|
||||
|
||||
|
||||
到此步骤,sonar基本可以用了。最基本的扫描完成了,还需要对代码进行分析,这就需要其他插件进行了,用的比较多的当然是SonarQube Scanner 了,下面进行安装配置。
|
||||
|
||||
SonarQube Scanner 代码分析
|
||||
|
||||
去官网下载所需要的安装包,解压放到指定的目录即可(需要说明的是,建议将scanner放到与Jenkins同主机,因为需要在jenkins中配置即成scanner),然后添加环境变量,比如我的如下:
|
||||
|
||||
$ cat /etc/profile
|
||||
PATH=$PATH:/opt/sonar-scanner-4.2.0/bin
|
||||
export PATH
|
||||
|
||||
$ source /etc/profile
|
||||
|
||||
|
||||
然后修改全局配置
|
||||
|
||||
$ cat sonar-scanner-4.2.0/conf/sonar-scanner.properties
|
||||
#Configure here general information about the environment, such as SonarQube server connection details for example
|
||||
#No information about specific project should appear here
|
||||
|
||||
#----- Default SonarQube server
|
||||
sonar.host.url=http://192.168.176.160:9000
|
||||
|
||||
#----- Default source code encoding
|
||||
#sonar.sourceEncoding=UTF-8
|
||||
|
||||
|
||||
环境变量生效后,检查配置是否成功
|
||||
|
||||
$ sonar-scanner -h
|
||||
INFO:
|
||||
INFO: usage: sonar-scanner [options]
|
||||
INFO:
|
||||
INFO: Options:
|
||||
INFO: -D,--define <arg> Define property
|
||||
INFO: -h,--help Display help information
|
||||
INFO: -v,--version Display version information
|
||||
INFO: -X,--debug Produce execution debug output
|
||||
|
||||
|
||||
配置成功后,找一个项目测试一下。
|
||||
|
||||
测试
|
||||
|
||||
测试方式有两种,一种是直接在命令行加参数执行,一种是使用在项目目录创建配置文件的方式执行,本次测试使用第二种
|
||||
|
||||
在项目根目录下,创建配置文件sonar-project.properties(部分参数未列出)
|
||||
|
||||
$ cat sonar-project.properties
|
||||
|
||||
# must be unique in a given SonarQube instance
|
||||
|
||||
sonar.projectKey=fw-base-nop
|
||||
|
||||
# this is the name and version displayed in the SonarQube UI. Was mandatory prior to SonarQube 6.1.
|
||||
|
||||
sonar.projectName=fw-base-nop
|
||||
|
||||
sonar.projectVersion=1.0
|
||||
|
||||
# Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows.
|
||||
|
||||
# This property is optional if sonar.modules is set.
|
||||
|
||||
sonar.sources=.
|
||||
|
||||
sonar.java.binaries=./target/classes
|
||||
|
||||
# Encoding of the source code. Default is default system encoding
|
||||
|
||||
sonar.sourceEncoding=UTF-8
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
|
||||
sonar.projectKey :项目名称,每个项目里必须是唯一的
|
||||
sonar.projectName:项目名称,在sonarqube web界面会显示此名称
|
||||
sonar.projectVersion:项目版本
|
||||
sonar.sources :需要扫描的项目目录位置
|
||||
sonar.host.url :sonar服务访问的url地址,此处未指定,配置文件中已经指定
|
||||
sonar.login :令牌名称对应的token,此处也没有指定
|
||||
sonar.java.binaries :项目编译目录,java为例,则为class文件所在的目录
|
||||
sonar.sourceEncoding:指定的字符集
|
||||
|
||||
|
||||
更多与参数有关的内容可参考这里
|
||||
|
||||
执行成功后部分结果如下所示
|
||||
|
||||
......
|
||||
WARN: This may lead to missing/broken features in SonarQube
|
||||
INFO: 37 files had no CPD blocks
|
||||
INFO: Calculating CPD for 80 files
|
||||
INFO: CPD calculation finished
|
||||
INFO: Analysis report generated in 702ms, dir size=541 KB
|
||||
INFO: Analysis reports compressed in 447ms, zip size=259 KB
|
||||
INFO: Analysis report uploaded in 480ms
|
||||
INFO: ANALYSIS SUCCESSFUL, you can browse http://192.168.176.160:9000/dashboard/index/fw-base-nop
|
||||
INFO: Note that you will be able to access the updated dashboard once the server has processed the submitted analysis report
|
||||
INFO: More about the report processing at http://192.168.176.160:9000/api/ce/task?id=AXBMjjIlw7C6SYqgng_B
|
||||
INFO: Task total time: 14.546 s
|
||||
INFO: ------------------------------------------------------------------------
|
||||
INFO: EXECUTION SUCCESS
|
||||
INFO: ------------------------------------------------------------------------
|
||||
INFO: Total time: 17.906s
|
||||
INFO: Final Memory: 14M/60M
|
||||
|
||||
|
||||
然后会在sonar web界面出现一个新的项目,如下-
|
||||
|
||||
|
||||
到此,关于sonar的基本配置已经完成,但是本次虽然测试成功了,但所有的操作还都是在shell bash中,所以接下来要配置与Jenkins的集成。
|
||||
|
||||
jenkins集成sonar-scanner配置
|
||||
|
||||
Jenkins与sonar和scanner集成的插件在文章的开头已经安装,这里直接演示如何配置。
|
||||
|
||||
点击菜单”manage jenkins(管理jenkins)--> configure system (系统配置)“,找到”SonarQube servers“配置参数,配置与sonarqube的连接,如下所示
|
||||
|
||||
|
||||
|
||||
其中红色框内为jenkins连接登陆sonarqube的token凭据ID,共有两种方法创建该凭据,第一种,在此处点击”Add“,选中jenkins,在弹出的框中:
|
||||
|
||||
|
||||
Kind参数处选择Secret text
|
||||
Secret参数处填写前面在安装sonarqube时创建的token
|
||||
ID和Describption为自定义该凭据的名字,其中ID必须为唯一,因为jenkins在调用的时候是根据ID调用该凭据的
|
||||
其它参数默认
|
||||
|
||||
|
||||
如下所示,添加完后点击Add即可添加,然后在上图红色框处的下拉框中选择此凭据即可。
|
||||
|
||||
|
||||
|
||||
第二种,在jenkins首页,点击”凭据-->系统-->全局凭据-->Add Credentials“,如下所示:
|
||||
|
||||
|
||||
|
||||
添加配置与上面一样
|
||||
|
||||
jenkins添加sonarqube配置完成,下面配置jenkins集成sonnarqube-scanner。
|
||||
|
||||
jenkins首页,点击”manage jenkins(管理jenkins)--> Global Tool Configuration(全局工具配置)“,找到SonarQube Scanner参数值,添加scanner在本地的路径,并给定一个名称,如下所示
|
||||
|
||||
|
||||
|
||||
到此即完成jenkins与scanner的集成配置。然后我们创建一个Jenkins项目测试一下
|
||||
|
||||
创建一个maven项目,配置好maven打包配置后,在”Post Steps“步骤点击”Execute SonarQube Scanner“,配置如下
|
||||
|
||||
|
||||
|
||||
配置参数不用多说,上面有过介绍,只不过这里的部分参数值使用了变量的方式,有关变量的详细说明可参考${jenkins_url}/pipeline-syntax/globals。Additional arguments指定了-X参数用于在执行scanner时打印详细的日志信息。
|
||||
|
||||
执行结果如下所示
|
||||
|
||||
|
||||
|
||||
有关jenkins与sonar集成的配置使用就先简单的介绍到这里,在以后的章节中会继续介绍如何在pipeline中使用sonar-scanner
|
||||
|
||||
|
||||
|
||||
|
567
专栏/Jenkins持续交付和持续部署/03.Devops工具链.md
Normal file
567
专栏/Jenkins持续交付和持续部署/03.Devops工具链.md
Normal file
@@ -0,0 +1,567 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
03.Devops工具链
|
||||
基础工具介绍安装配置
|
||||
|
||||
在前面的两个章节中简单介绍了一下Jenkins以及插件的安装配置等内容。本系列文章虽然是基于Jenkins 进行展开的,但是实现Devops中的CI/CD也不是仅凭一个工具就能实现的,需要众多Devops工具链的相互协作。所以在配置好Jenkins服务后,本章节来介绍几款与Jenkins集成进行持续交付和持续部署的devops工具包。并且在以后的章节中会通过与Jenkins配合以实现应用服务的构建和部署。
|
||||
|
||||
本节主要介绍如下工具的安装配置:
|
||||
|
||||
|
||||
Docker
|
||||
Harbor
|
||||
Gitlab
|
||||
Ansible
|
||||
|
||||
|
||||
Docker
|
||||
|
||||
介绍
|
||||
|
||||
Docker 是什么大家都应该都有所了解,一个基于LXC技术构建的的高级容器引擎。Docker以容器为资源分割和调度的基本单位,封装整个软件运行时的环境,为开发者和系统管理员设计的用于构建、发布和运行分布式应用的平台。它是一个跨平台、可移植并且简单易用的容器解决方案。
|
||||
|
||||
Docker 基于Linux 内核的cgroup,namespace以及 AUFS 和Union FS等技术,对进程进行封装隔离,属于操作系统层面的轻量级虚拟化技术。
|
||||
|
||||
说明
|
||||
|
||||
Namespace:是对全局系统资源的一种封装隔离,使得处于不同namespace的进程拥有独立的全局系统资源,改变一个namespace中的系统资源只会影响当前namespace里的进程,对其他namespace中的进程没有影响。Linux内核中提供了7种namesapce隔离系统调用(docker 用到6种):
|
||||
|
||||
UTS:UTS namespace提供主机名和域名的隔离
|
||||
|
||||
IPC:进程间通信,涉及的IPC资源包括常见的信号量,消息队列和共享内存。
|
||||
|
||||
PID:PID namespace隔离进程PID,两个namespace下的进程可以有相同的Pid,每个Pid的namespace都有自己的计数程序。PID namespace中的第一个进程为PID 1(所有进程的父进程),像linux 中 的init进程。
|
||||
|
||||
mount:mount通过隔离文件系统挂载点对隔离文件系统提供支持
|
||||
|
||||
Network :Network namespace主要提供了关于网络资源的隔离(不是真正的网络隔离,只是把网络独立出来)
|
||||
|
||||
User :User namespace主要隔离安全相关的标识符和属性,如用户id,用户组id,root目录 、秘钥以及权限等。
|
||||
|
||||
cgroup:对一组进程进行统一的资源监控和限制,是Linux内核提供的一种将进程按组进行管理的机制。cgroup 可以限制、记录任务组使用的物理资源(cpu、memory 、io等),主要作用如下:
|
||||
|
||||
资源限制:对任务使用的资源总额进行限制
|
||||
|
||||
优先级分配:控制任务运行的优先级
|
||||
|
||||
资源统计:统计系统资源的使用量
|
||||
|
||||
任务控制:对任务执行挂起、恢复等操作
|
||||
|
||||
AUFS 和 Union FS:
|
||||
|
||||
UnionFS 是一种为 Linux 操作系统设计的用于把多个文件系统联合到同一个挂载点的文件系统服务
|
||||
|
||||
AUFS 即 Advanced UnionFS ,是 UnionFS 的升级版,它能够提供更优秀的性能和效率。
|
||||
|
||||
AUFS 作为联合文件系统,它能够将不同文件夹中的层联合(Union)到同一个文件夹中,这些文件夹在 AUFS 中称作分支,整个联合的过程被称为联合挂载(Union Mount)
|
||||
|
||||
有关Docker的介绍就简单的介绍到这里,下面看一下Docker的安装
|
||||
|
||||
安装
|
||||
|
||||
Centos7.x 安装Docker
|
||||
|
||||
安装前首先卸载旧版本的Docker服务(如果曾经安装过)
|
||||
|
||||
$ sudo yum remove docker docker-common docker-selinux docker-engine docker-client*docker-latest* docker-logrotate
|
||||
|
||||
#删除残留文件
|
||||
$ rm -rf /var/lib/docker
|
||||
|
||||
|
||||
安装存储库包
|
||||
|
||||
$ yum install -y yum-utils device-mapper-persistent-data lvm2
|
||||
|
||||
|
||||
设置稳定存储库
|
||||
|
||||
$ sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
|
||||
|
||||
|
||||
注意:使用官方的源安装会很慢,可以换成阿里云的
|
||||
|
||||
https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
|
||||
|
||||
|
||||
安装DOCKER CE
|
||||
|
||||
$ yum-config-manager --enable docker-ce-edge && yum install docker-ce -y
|
||||
|
||||
|
||||
启动
|
||||
|
||||
$ systemctl start docker
|
||||
|
||||
|
||||
使用脚本自动安装
|
||||
|
||||
Docker官方为了简化安装流程,提供了一套安装脚本,CentOS 系统上可以使用这套脚本安装:
|
||||
|
||||
$ curl -fsSL get.docker.com -o get-docker.sh
|
||||
$ sudo sh get-docker.sh --mirror Aliyun
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
|
||||
--mirror 表示指定的yum源
|
||||
执行这个命令后,脚本就会自动的将一切准备工作做好,并且把Docker 安装在系统中。
|
||||
|
||||
|
||||
默认情况下,docker命令会使用Unix socket与Docker引擎通讯。而只有root 用户和 docker组的用户才可以访问Docker引擎的Unix socket。出于安全考虑,一般Linux系统上不会直接使用root用户。因此,更好地做法是将需要使用docker的用户加入docker用户组(如果你不想使用root用户)。
|
||||
|
||||
建立docker组
|
||||
|
||||
$ sudo group add docker
|
||||
|
||||
|
||||
将当前用户加入docker组
|
||||
|
||||
$ sudo usermod -aG docker $USER
|
||||
|
||||
|
||||
其它系统安装docker可以参考官网 ,比如
|
||||
|
||||
Ubuntu安装
|
||||
|
||||
https://docs.docker.com/install/linux/docker-ce/ubuntu/
|
||||
|
||||
|
||||
Mac安装
|
||||
|
||||
https://docs.docker.com/docker-for-mac/install/
|
||||
|
||||
|
||||
安装还是比较简单的。
|
||||
|
||||
测试
|
||||
|
||||
Docker 安装完以后就可以进行下一步的基础测试了。
|
||||
|
||||
这里以根据官方的nginx镜像启动一个容器为例:
|
||||
|
||||
[root@Docker ~]# docker run -itd -p80:80 nginx
|
||||
Unable to find image 'nginx:latest' locally
|
||||
latest: Pulling from library/nginx
|
||||
bc51dd8edc1b: Pull complete
|
||||
.....
|
||||
5c472411317e6f1f13ed522d3963331054c84f747cd88d66f52d67be66709973
|
||||
|
||||
|
||||
启动以后,查看并测试访问一下
|
||||
|
||||
[root@Docker ~]# docker ps
|
||||
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
|
||||
72dd0bd9a12f nginx "nginx -g 'daemon ..." 5 seconds ago Up 2 seconds 0.0.0.0:80->80/tcp gallant_nash
|
||||
|
||||
[root@Docker ~]# curl localhost:80
|
||||
......
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Welcome to nginx!</h1>
|
||||
<p>If you see this page, the nginx web server is successfully installed and
|
||||
......
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
在使用docker run命令启动一个容器的时候,如果该镜像在本地不存在,默认会从docker hub拉取镜像,在这一过程中可能拉取镜像的速度可能会很慢,这是由于国内网络访问Docker hub网络慢造成的。这就需要给docker服务配置镜像加速器了,用于提高镜像拉取速度。
|
||||
|
||||
对于Centos系统,编辑/usr/lib/systemd/system/docker.service文件,找到ExecStart= 这一行,在这行最后添加--registry-mirror="加速器地址",如:
|
||||
|
||||
ExecStart=/usr/bin/dockerd --registry-mirror=https://jxus37ad.mirror.aliyuncs.com
|
||||
|
||||
或者使用docker官方加速器
|
||||
https://registry.docker-cn.com
|
||||
|
||||
|
||||
或者使用官方的方法
|
||||
|
||||
#docker官方镜像加速,如果文件不存在则新建文件,并且保证文件符合json规范
|
||||
cat /etc/docker/daemon.json
|
||||
|
||||
{
|
||||
"registry-mirrors": [
|
||||
"https://registry.docker-cn.com"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
配置完以后需要重启docker服务
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl restart docker
|
||||
|
||||
|
||||
在命令行执行 docker info,如果从结果中看到了上面添加的加速器内容,说明配置成功。比如:
|
||||
|
||||
Registry Mirrors: https://registry.docker-cn.com/
|
||||
|
||||
|
||||
有关docker的安装配置就简单的介绍到这里。那么Jenkins与Docker集成能干什么呢?根据jenkins官方提供的插件来看,主要是两个方面的功能:一个是使用docker作为应用服务的部署环境;另一个是使用docker作为jenkins的动态slave节点。实际工作中常用的功能就是使用docker作为jenkins 的动态slave节点,用于提高jenkins服务的性能以及效率,关于如何集成使用,会在以后的章节中详细介绍。
|
||||
|
||||
Harbor
|
||||
|
||||
在介绍Harbor之前,有必要先了解一下docker的镜像仓库。镜像仓库(Repository)是集中存放镜像的地方。
|
||||
|
||||
一个容易混淆的概念是注册服务器(Registry)。实际上注册服务器是管理仓库的具体服务器,每个服务器上可以有多个仓库,而每个仓库下面有多个镜像。从这方面来说,仓库可以被认为是一个具体的项目或目录。例如,对于仓库地址dl.dockerpool.com/ubuntu来说,dl.dockerpool.com是注册服务器地址,ubuntu 是仓库名。
|
||||
|
||||
大部分时候,并不需要严格区分这两者的概念。
|
||||
|
||||
Harbor正是一个用于存储和分发Docker镜像的企业级Registry服务器, Harbor支持安装在多个Registry节点的镜像资源复制,镜像全部保存在私有Registry中。作为一个企业级私有Registry服务器,Harbor提供了更好的性能和诸如用户管理,访问控制和活动审计等功能。提升用户使用Registry构建和运行环境传输镜像的效率。
|
||||
|
||||
Harbor组件
|
||||
|
||||
Harbor在架构上主要由6个组件构成:
|
||||
|
||||
Proxy:Harbor的registry,UI,token等服务,通过一个前置的反向代理统一接收浏览器、Docker客户端的请求,并将请求转发给后端不同的服务。
|
||||
|
||||
Registry: 负责储存Docker镜像,并处理docker push/pull 命令。由于我们要对用户进行访问控制,即不同用户对Docker image有不同的读写权限,Registry会指向一个token服务,强制用户的每次docker pull/push请求都要携带一个合法的token, Registry会通过公钥对token 进行解密验证。
|
||||
|
||||
Core services: 这是Harbor的核心功能,主要提供以下服务:
|
||||
|
||||
UI:提供图形化界面,帮助用户管理registry上的镜像(image), 并对用户进行授权。
|
||||
|
||||
webhook:为了及时获取registry 上image状态变化的情况, 在Registry上配置webhook,把状态变化传递给UI模块。
|
||||
|
||||
token 服务:负责根据用户权限给每个docker push/pull命令签发token. Docker 客户端向Regiøstry服务发起的请求,如果不包含token,会被重定向到这里,获得token后再重新向Registry进行请求。
|
||||
|
||||
Database:为core services提供数据库服务,负责储存用户权限、审计日志、Docker image分组信息等数据。
|
||||
|
||||
Job Services:提供镜像远程复制功能,可以把本地镜像同步到其他Harbor实例中。
|
||||
|
||||
Log collector:为了帮助监控Harbor运行,负责收集其他组件的log,供日后进行分析。
|
||||
|
||||
Harbor的每个组件都是以Docker容器的形式构建的,官方也是使用Docker Compose来对它进行部署。用于部署Harbor的Docker Compose模板位于 harbor/docker-compose.yml,harbor安装好以后通过docker ps命令或者docker-compose ps命令可以看到Harbor是由7个(新版本是9个)容器组成的,如下所示:
|
||||
|
||||
# docker-compose ps
|
||||
Name Command State Ports
|
||||
------------------------------------------------------------------------------------------------------------------------------
|
||||
harbor-adminserver /harbor/harbor_adminserver Up
|
||||
harbor-db docker-entrypoint.sh mysqld Up 3306/tcp
|
||||
harbor-jobservice /harbor/harbor_jobservice Up
|
||||
harbor-log /bin/sh -c crond && rm -f ... Up 127.0.0.1:1514->514/tcp
|
||||
harbor-ui /harbor/harbor_ui Up
|
||||
nginx nginx -g daemon off; Up 0.0.0.0:443->443/tcp, 0.0.0.0:4443->4443/tcp, 0.0.0.0:80->80/tcp
|
||||
registry /entrypoint.sh serve /etc/ ... Up 5000/tcp
|
||||
|
||||
|
||||
说明
|
||||
|
||||
nginx:nginx负责流量转发和安全验证,对外提供的流量都是从nginx中转,所以开放https的443端口,它将流量分发到后端的ui和正在docker镜像存储的docker registry。
|
||||
|
||||
harbor-jobservice:harbor-jobservice 是harbor的job管理模块,job在harbor里面主要是为了镜像仓库之前同步使用的;
|
||||
|
||||
harbor-ui:harbor-ui是web管理页面,主要是前端的页面和后端CURD的接口;
|
||||
|
||||
registry:registry就是docker原生的仓库,负责保存镜像。
|
||||
|
||||
harbor-adminserver:harbor-adminserver是harbor系统管理接口,可以修改系统配置以及获取系统信息。
|
||||
|
||||
harbor-db:harbor-db是harbor的数据库,这里保存了系统的job以及项目、人员权限管理。由于本harbor的认证也是通过数据,在生产环节大多对接到企业的ldap中;
|
||||
|
||||
harbor-log:harbor-log是harbor的日志服务,统一管理harbor的日志。通过inspect可以看出容器统一将日志输出的syslog。
|
||||
|
||||
这几个容器通过Docker link的形式连接在一起,这样,在容器之间可以通过容器名字互相访问。对终端用户而言,只需要暴露proxy (即Nginx)的服务端口,对于其他的组件用途只需要做个简单的了解即可
|
||||
|
||||
安装与配置
|
||||
|
||||
下载offline 版本的Harbor
|
||||
|
||||
https://github.com/goharbor/harbor/releases
|
||||
|
||||
|
||||
配置Harbor
|
||||
|
||||
$ wget https://storage.googleapis.com/harbor-releases/release-1.7.0/harbor-offline-installer-v1.7.5.tgz
|
||||
$ tar xvf harbor-offline-installer-v1.7.5.tgz
|
||||
|
||||
$ cd harbor
|
||||
|
||||
修改harbor.cfg内容如下:
|
||||
|
||||
# 访问管理UI与注册服务的IP地址或主机名,不要使用localhost或127.0.0.1,因为Harbor需要被外部的客户端访问
|
||||
hostname = 192.168.176.155
|
||||
|
||||
# 修改管理员密码
|
||||
harbor_admin_password = Harbor12345
|
||||
|
||||
|
||||
只需要修改harbor的地址和登录密码即可,前提需要安装docker-compose
|
||||
|
||||
$ sudo curl -L https://github.com/docker/compose/releases/download/1.17.1/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
|
||||
$ sudo chmod +x /usr/local/bin/docker-compose
|
||||
|
||||
|
||||
执行 ./install.sh 即可自动下载镜像并启动
|
||||
|
||||
|
||||
|
||||
启动以后访问Harbor http://192.168.176.155/harbor/sign-in ,用户/密码 admin Harbor2345
|
||||
|
||||
|
||||
|
||||
到这里Harbor就安装完成了
|
||||
|
||||
关闭与启动 Harbor 命令
|
||||
|
||||
# harbor 目录下
|
||||
#前台启动
|
||||
docker-compose up
|
||||
|
||||
#后台启动
|
||||
docker-compose up -d
|
||||
|
||||
#关闭
|
||||
docker-compose down
|
||||
|
||||
|
||||
Docker 默认不允许非 HTTPS 方式推送镜像。但是可以通过Docker的配置选项来取消这个限制
|
||||
|
||||
$ cat /usr/lib/systemd/system/docker.service |grep dockerd
|
||||
ExecStart=/usr/bin/dockerd -H unix:// --insecure-registry 192.168.176.155
|
||||
|
||||
$ docker login 192.168.176.155
|
||||
Username: admin
|
||||
Password:
|
||||
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
|
||||
Configure a credential helper to remove this warning. See
|
||||
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
|
||||
|
||||
Login Succeeded
|
||||
|
||||
|
||||
从上面的登录信息可以看到docker私有仓库认证的凭证存放在/root/.docker/config.json文件。
|
||||
|
||||
测试推送镜像
|
||||
|
||||
$ docker tag nginx 192.168.176.155/library/nginx:test
|
||||
$ docker push 192.168.176.155/library/nginx:test
|
||||
The push refers to repository [192.168.176.155/library/nginx]
|
||||
332fa54c5886: Pushed
|
||||
6ba094226eea: Pushed
|
||||
6270adb5794c: Pushed
|
||||
test: digest: sha256:e770165fef9e36b990882a4083d8ccf5e29e469a8609bb6b2e3b47d9510e2c8d size: 948
|
||||
|
||||
|
||||
登录harbor查看
|
||||
|
||||
|
||||
|
||||
到这里,harbor的基本安装配制就完成了。
|
||||
|
||||
GitLab
|
||||
|
||||
介绍
|
||||
|
||||
GitLab是一个开源的用于代码仓库管理的项目,使用Git作为代码管理工具,并在此基础上搭建起来的web服务。
|
||||
|
||||
Gitlab也是比较受欢迎并且众多公司在用的免费的代码托管服务,除了作为源码仓库服务外,Gitlab还有另一个重要的功能:Gitlab-CI,一套基于Gitlab的持续集成系统。Gitlab 8.0版本开始默认集成CI功能(GitLab CI),通过在项目内创建.gitlab-ci.yaml 配置文件读取CI任务并通过被称为 GitLab Runner 的Agent端进行持续交付与部署操作。每当gitlab仓库有代码更新的时候,就会触发提前定义的脚本(.gitlab-ci.yaml)进行预设定的操作(比如代码编译、测试、部署等)。
|
||||
|
||||
安装
|
||||
|
||||
Gitlab的安装比较简单,GitLab官网针对不同的系统也给出了比较详细的安装步骤,可以参考这里
|
||||
|
||||
为了测试方便,我这里直接使用docker安装,使用如下命令启动容器
|
||||
|
||||
docker run -d -p 4443:443 -p 80:80 -p 2222:22 --name gitlab --restart always -v /srv/gitlab/config:/etc/gitlab -v /srv/gitlab/logs:/var/log/gitlab -v /src/gitlab/data:/var/opt/gitlab gitlab/gitlab-ee
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
gitlab镜像默认使用的端口为80,在使用docker部署gitlab并设置启动参数时,宿主机端口最好也使用80(也就是 -p80:80参数),不要随意改,否则会遇到各种意想不到的坑。比如下面这个:
|
||||
|
||||
在使用docker方式安装gitlab时,遇到一个问题(使用vm默认没有此问题),因为映射的本地端口和gitlab内部端口不同,在使用gitlab-runner容器拉取应用代码的时候出现网络不通的问题,如下所示:
|
||||
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
gitlab-runner默认拉取代码时,会使用gitlab 服务docker内部的ip或者hostname去拉取代码,肯定会导致网络不通,所以就需要做端口映射的时候保持宿主机和容器端口一样,由于gitlab默认内部端口为80,所以宿主机端口最好也用80
|
||||
|
||||
如果非要用别的端口怎么办?比如使用-p8099:80,那么下面将会进入一个填坑的过程,虽然修改gitlab的配置文件、重启容器和服务可以解决上面的问题,但是这个过程坑也比较多,比如修改完地址和端口以后虽然能够拉取代码,有可能会影响Gitlab-CI的pipeline的操作等,或者修改完后重启容器又恢复默认配置等。所以为了保险起见,在做端口映射的时候宿主机端口最好用80
|
||||
|
||||
在Gitlab中创建好项目后,从项目配置中获取项目地址的时候,会发现项目的地址为http://$container_id/$group/$project_name.git,虽然知道gitlab的实际地址,但是每创建一个项目,项目地址中的host都是容器的id,并且复制出来使用时都要手动进行替换,对于严谨的运维来说这是不能接受的,所以还需要统一修改项目的url地址。
|
||||
|
||||
容器启动以后,修改宿主机/srv/gitlab/config/gitlab.rb或者容器/etc/gitlab/gitlab.rb文件,找到external_url关键字,去掉注释,并改成宿主机的ip地址(用于更改新创建的项目里拉取代码的地址)
|
||||
|
||||
external_url 'http://192.168.176.154'
|
||||
|
||||
|
||||
然后重新启动gitlab
|
||||
|
||||
两种方法(任选):
|
||||
|
||||
#进入容器
|
||||
gitlab-ctl restart
|
||||
|
||||
或者直接
|
||||
|
||||
# 重启容器
|
||||
docker restart gitlab
|
||||
|
||||
|
||||
测试
|
||||
|
||||
创建一个项目,比如我们要测试的项目
|
||||
|
||||
|
||||
|
||||
点击create project以后,跳转到如下所示的界面
|
||||
|
||||
|
||||
|
||||
该页面表示我们创建项目成功,并且给出了第一次提交代码的操作步骤。该步骤针对三种情况,第一种是我们要提交到代码仓库的文件不存在时,需要我们自己创建一个目录,并在该目录下创建文件上传到仓库,参考第一至二步骤;如果我们的项目文件存在,且不是git存储库,直接则直接参考第一三步骤即可;如果我要上传一个已经存在的项目,并且该项目为git存储库,则参考第一和第四步骤即可。
|
||||
|
||||
我这里使用的第二种情况,直接将已经存在的项目上传到仓库。如下所示:
|
||||
|
||||
|
||||
|
||||
[root@es-165 helloworldweb]# git config --global user.name "Administrator"
|
||||
[root@es-165 helloworldweb]# git config --global user.email "[email protected]"
|
||||
[root@es-165 helloworldweb]# git init
|
||||
git remote add origin http://192.168.176.154/root/test-helloworld.git
|
||||
Initialized empty Git repository in /opt/helloworldweb/.git/
|
||||
[root@es-165 helloworldweb]# git remote add origin http://192.168.176.154/root/test-helloworld.git
|
||||
[root@es-165 helloworldweb]# git add .
|
||||
[root@es-165 helloworldweb]# git commit -m "Initial commit"
|
||||
[master (root-commit) 8cd1730] Initial commit
|
||||
6 files changed, 76 insertions(+)
|
||||
create mode 100644 README.md
|
||||
create mode 100644 pom.xml
|
||||
create mode 100644 src/main/webapp/.index.jsp.un~
|
||||
create mode 100644 src/main/webapp/WEB-INF/web.xml
|
||||
create mode 100644 src/main/webapp/index.jsp
|
||||
create mode 100644 src/main/webapp/index.jsp~
|
||||
[root@es-165 helloworldweb]# git push -u origin master
|
||||
Username for 'http://192.168.176.154': root
|
||||
Password for 'http://[email protected]':
|
||||
Counting objects: 12, done.
|
||||
Delta compression using up to 4 threads.
|
||||
Compressing objects: 100% (8/8), done.
|
||||
Writing objects: 100% (12/12), 1.88 KiB | 0 bytes/s, done.
|
||||
Total 12 (delta 0), reused 0 (delta 0)
|
||||
To http://192.168.176.154/root/test-helloworld.git
|
||||
* [new branch] master -> master
|
||||
Branch master set up to track remote branch master from origin.
|
||||
|
||||
|
||||
然后在其他机器通过git clone命令直接测试即可。
|
||||
|
||||
Ansible
|
||||
|
||||
上面介绍完docker和gitlab,接下来简单介绍并安装一下比较流行的自动化运维工具Ansible。
|
||||
|
||||
介绍
|
||||
|
||||
Ansible 是一个开源的基于Python paramiko(Python实现的ssh协议库)开发的分布式、无需客户端、轻量级的自动化配置管理工具。Ansible基于模块化工作,本身没有批量部署的能力,它只是提供一种框架,真正具有批量部署功能的是ansible所运行的模块。ansible实现了批量系统配置、批量程序部署、批量运行命令等功能,配置语法使用 YMAL 及 Jinja2模板语言。
|
||||
|
||||
安装
|
||||
|
||||
Ansible是无代理的自动化工具,默认情况下通过SSH协议管理计算机。安装后,Ansible不会添加数据库,并且没有启动或继续运行守护程序。只需将其安装在一台计算机上,它就可以从该中心管理整个远程计算机。
|
||||
|
||||
前提条件:
|
||||
|
||||
当前,Ansible控制节点可以从安装了Python 2(2.7版)或Python 3(3.5版及更高版本)的任何计算机上运行。这包括Red Hat,Debian,CentOS,macOS,任何BSD等。控制节点不支持Windows。
|
||||
|
||||
安装前首先需要解决依赖关系:
|
||||
|
||||
yum -y install python-jinja2 PyYAML python-paramiko python-babel python-crypto
|
||||
|
||||
|
||||
tar包安装
|
||||
|
||||
https://github.com/ansible/ansible/releases ##下载Ansible,解压安装
|
||||
python setup.py build
|
||||
python setup.py install
|
||||
|
||||
|
||||
yum 安装
|
||||
|
||||
$ yum install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
|
||||
$ sudo yum install ansible
|
||||
|
||||
|
||||
更多平台安装可参考官网
|
||||
|
||||
安装完以后执行下面命令查看是否安装成功
|
||||
|
||||
$ ansible --version
|
||||
ansible 2.7.6
|
||||
config file = /etc/ansible/ansible.cfg
|
||||
configured module search path = [u'/root/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
|
||||
ansible python module location = /usr/lib/python2.7/site-packages/ansible
|
||||
executable location = /usr/bin/ansible
|
||||
python version = 2.7.5 (default, Aug 7 2019, 00:51:29) [GCC 4.8.5 20150623 (Red Hat 4.8.5-39)]
|
||||
|
||||
|
||||
出现上面内容说明ansible安装成功。
|
||||
|
||||
修改通过上面命令获取的ansible默认使用的配置文件(config file参数所指定的文件)host_key_checking参数值(去掉注释,true改为false)
|
||||
|
||||
host_key_checking = False
|
||||
|
||||
|
||||
用于解决ansible首次连接_host_服务器时需要验证的问题
|
||||
|
||||
测试
|
||||
|
||||
首先可以通过默认的localhost组进行ping操作,比如
|
||||
|
||||
[root@ansible ~]# ansible localhost -m ping
|
||||
localhost | SUCCESS => {
|
||||
"changed": false,
|
||||
"ping": "pong"
|
||||
}
|
||||
|
||||
|
||||
Ansible通过ssh实现配置管理、应用部署、任务执行等功能。因此要使用ansible,需要先配置ansible端能基于免密钥认证连接被管理的节点(用户名和密码也可以,本节暂时不做演示),如下所示
|
||||
|
||||
在ansible端通过ssh-keygen -t rsa -P ''生成密钥对以后,执行如下命令
|
||||
|
||||
ssh-copy-id -i ~/.ssh/id_rsa.pub <要被管理的主机ip>
|
||||
|
||||
|
||||
执行完成后,创建/etc/ansible/hosts文件,将刚刚做完免密认证的主机ip添加进去-
|
||||
比如
|
||||
|
||||
$ cat /etc/ansible/hosts
|
||||
|
||||
[ansible_ag1] #该名称自定义
|
||||
192.168.177.43 #被远程管理的主机ip
|
||||
|
||||
|
||||
然后执行如下命令
|
||||
|
||||
$ ansible ansible_ag1 -m shell -a "ifconfig"
|
||||
192.168.177.43 | CHANGED | rc=0 >>
|
||||
docker0: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500
|
||||
inet 172.17.0.1 netmask 255.255.0.0 broadcast 172.17.255.255
|
||||
inet6 fe80::42:91ff:fea9:955c prefixlen 64 scopeid 0x20<link>
|
||||
......
|
||||
|
||||
ens192: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
|
||||
inet 192.168.177.43 netmask 255.255.254.0 broadcast 192.168.177.255
|
||||
......
|
||||
|
||||
|
||||
上面的ansible命令表示使用-m参数指定的shell模块,去远程主机执行通过-a参数指定命令”ifconfig”。
|
||||
|
||||
ansible测试没问题以后,我们就可以在jenkins中配置ansible的环境变量了。-
|
||||
点击 “Manage Jenkins”—> “Global Tool Configuration(全局工具配置)“,在跳转的界面中找到Ansible 参数项,点击新增ansible,配置ansible命令的环境变量,如下所示
|
||||
|
||||
|
||||
|
||||
配置好保存即可
|
||||
|
||||
有关基础工具安装配置的内容到这里就介绍到这里,内容都相对比较简单,在以后的章节中会具体介绍使用这些工具的方法。
|
||||
|
||||
|
||||
|
||||
|
251
专栏/Jenkins持续交付和持续部署/04.初探JenkinsCD实践.md
Normal file
251
专栏/Jenkins持续交付和持续部署/04.初探JenkinsCD实践.md
Normal file
@@ -0,0 +1,251 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
04.初探Jenkins CD实践
|
||||
在了解了Jenkins的系统配置与插件以后,本节开始正式学习一下使用Jenkins进行基础的CD实践。
|
||||
|
||||
开始之前要说明的是,本文介绍的部署方法以及脚本等比较基础,旨在介绍使用以及配置jenkins任务(job)的一些流程和方法,在后续的文章中会对部署方法优化和提高,对于不同的场景和需求还需要根据自己的实际情况出发进行项目配置。当然,如果你对jenkins使用或者对持续交付的流程已经很熟了,你也可以略过此章节。
|
||||
|
||||
创建Jenkins项目
|
||||
|
||||
Jenkins包含多种类型的项目配置,比如freestyle、maven、pipeline等,每种类型的项目都有各自的优缺点以及固定的使用场景,重点在于要根据自己的实际情况选择合适的项目类型创建项目。下面简单介绍一下不同类型的项目的使用。
|
||||
|
||||
创建Freestyle类型的项目
|
||||
|
||||
Freestyle,创建一个自由风格类型的项目。为什么叫自由风格的项目,个人理解为项目的配置扩展性较强,对于某项步骤的配置操作不局限于某一个特定的插件,实现想要达到的目标的方法具有多样性。
|
||||
|
||||
在Jenkins主面板点击”新建“菜单,在跳转到的界面中输入任务名称(比如我创建的job名称为test-freestyle-project),选择”Freestyle project(自由风格项目)“,然后在页面最下面点击确定即可。
|
||||
|
||||
点击确定后,跳转到任务(job)配置界面,如下图所示:
|
||||
|
||||
|
||||
|
||||
该任务包含了如下几个配置步骤(图中红线框内的部分):
|
||||
|
||||
general:用于配置任务构建时的全局配置,比如上面列出的选项框:
|
||||
|
||||
Run the build inside Docker containers用来定义构建时是否在docker容器内进行构建;-
|
||||
Discard old builds用于配置该项目构建的历史记录个数和保存的时间;-
|
||||
限制项目的运行节点用于配置该项目在指定的Jenkins节点上构建;-
|
||||
This project is parameterized 用于配置该项目使用参数化构建;
|
||||
|
||||
源码管理:用于配置构建时的源代码仓库。从运维角度去使用Jenkins,几乎所有的Job任务都涉及到代码的拉取操作,该步骤就是配置代码仓库的地址和对仓库认证凭据信息。代码仓库的类型主要分为CVS、Git、svn等。
|
||||
|
||||
构建触发器:该步骤主要用于配置什么时候运行Job,比如通过”身份令牌”进行远程构建配置;通过设置触发器在某个项目构建完成后在构建本项目,通过计划任务设置定时构建等。
|
||||
|
||||
构建环境:构建环境步骤用于设定构建时的工作环境配置,比如配置构建时使用的凭证设置;配置编译代码时使用的特定的配置文件;构建之前发送编译时需要用的文件到服务器或者执行命令等配置。
|
||||
|
||||
构建:构建步骤用于进行job构建操作,该步骤针对不同的插件有不同的使用方法。比如用于代码编译操作、比如执行命令、如果遇到需要构建镜像的需求,也可以构建容器镜像等。
|
||||
|
||||
构建后操作:该步骤作为整个jenkins 任务配置的最后一步,主要进行一些”收尾”工作,比如部署应用服务操作,有测试操作的生成测试报告;对代码进行质量分析操作、或者在上面操作步骤成功后设置构建其他job任务、发送邮件等操作。
|
||||
|
||||
对于jenkins新安装的插件的使用,会在上面列出的一个或者多个步骤中显示插件的使用选项。
|
||||
|
||||
相对于其它类型的Job(比如maven、pipeline类型),每种类型的job都有自身特定的配置步骤,并且各配置步骤会根据插件安装的多少也会有所差异,不过大体上的配置步骤都相同。
|
||||
|
||||
由于jenkins项目配置的参数选项比较多,而且参数选项会根据插件的安装有所不同,下面就先看一个最简单的使用示例。
|
||||
|
||||
在“构建”步骤的”增加构建步骤“选项框里选择Execute shell,该选项表示在目标主机上执行shell命令。需要注意的是,如果在”General“步骤没有对项目的运行节点进行特殊配置,该任务运行的主机为Jenkins master节点,也就是在jenkins master服务器上执行shell命令。
|
||||
|
||||
如下所示:
|
||||
|
||||
|
||||
|
||||
执行结果如下:
|
||||
|
||||
|
||||
|
||||
输出的结果为jenkins服务所在服务器的主机名。
|
||||
|
||||
如果勾选上在”General“步骤中的”限制项目的运行节点“选项框,在”标签表达式“输入框中输入在上一节中添加的slave节点的标签(jenkins-slave1),在次执行该job,如下所示:
|
||||
|
||||
|
||||
|
||||
该结果表示在slave节点上执行jenkins job成功。
|
||||
|
||||
上面虽然成功执行了jenkins job,而我们使用jenkins的目的可不仅仅是在jenkins服务器上执行简单的shell命令,从运维角度出发,用得最多的就是通过jenkins服务拉取应用代码并编译测试,最后部署到指定的服务器上。
|
||||
|
||||
比如,我们使用在上一节中安装Gitlab时创建的test-helloworld仓库时提交的代码作为项目测试用例,通过配置jenkins拉取此项目代码,并使用maven管理工具编译代码。
|
||||
|
||||
代码编译
|
||||
|
||||
使用freestyle类型的job配置如下,在源码管理步骤中使用git服务。
|
||||
|
||||
|
||||
|
||||
其中Credntials为创建的对Gitlab仓库认证的凭证,点击上图中的Jenkins选项,在跳转的界面中,在”类型“选项框选择Username with password,并在下面的输入框中输入用户名和密码以及用于区分该凭证的用户,输入好保存即可。然后就可以在Credntials参数的下拉框中看到刚刚创建的凭证了。有关Jenkins凭证的内容,会在下一章节详细介绍。
|
||||
|
||||
在”增加构建步骤“的下拉列表中选择 “Invoke top level Maven target“,配置maven构建参数。
|
||||
|
||||
|
||||
|
||||
构建步骤这里使用maven插件进行构建,其中:
|
||||
|
||||
Maven 版本 参数为选择框,如果在Jenkins全局配置中配置了Maven工具的环境变量,这里从下拉列表中会有显示,比如我这里显示的为在Jenkins全局配置里设置的全局名称。
|
||||
|
||||
目标 这里应该写maven 编译代码的命令,可用的命令为 clean package/instlall/deploy,这些命令都可以进行代码的编译、测试、打包等操作。
|
||||
|
||||
POM 为编译代码时的pom.xml文件,该文件定义了项目的基本信息,用于描述项目如何构建,声明项目依赖等。这里可以写该文件的绝对路径,也可以写相对路径,上面示例配置的为${WORKSPACE}变量(jenkins自带的环境变量)路径下的pom.xml,也就是本项目工作目录下的pom.xml文件,所以这里也可以直接写pom.xml而不加任何路径。
|
||||
|
||||
Java 虚拟机参数 配置maven编译代码时的参数,上面示例配置参数为跳过执行src/test/java中的JUnit测试用例。
|
||||
|
||||
配置文件 这里设置的为maven编译代码时使用的配置文件。
|
||||
|
||||
如果觉得使用maven插件对话框的方式配置编译参数比较麻烦,也可以通过命令进行代码 编译。在“增加构建步骤”中使用”Exec shell“,输入mvn clean install即可,比较简单,这里不再演示了,有兴趣的可以自己试一下。
|
||||
|
||||
配置好后保存,在项目主菜单点击”build now(立即构建)“就开始进行构建了。如下所示:
|
||||
|
||||
|
||||
|
||||
如图上所示,构建完成会在target目录下生成一个war包。下面我们配置job将该war包部署到指定的服务器上去。
|
||||
|
||||
添加主机
|
||||
|
||||
项目代码编译成功了,就该着手部署该项目了(实际工作中可能会使用sonar对代码进行分析,鉴于该helloworld项目比较简单,代码质量分析的步骤就先略过,至于sonar的使用在以后的章节中会介绍)。将该项目部署到本地服务器上比较容易,通过shell命令将war包拷贝到指定的目录下通过tomcat启动,或者直接通过java -jar命令启动。下面介绍一下如何部署到远程服务器上。
|
||||
|
||||
要部署到远程服务器上,首先需要在jenkins系统中添加一台可远程连接的机器,步骤如下:
|
||||
|
||||
点击”Manage Jenkins(管理jenkins)” –> “Configure System(系统配置)“找到 Publish over SSH参数,点击”add“添加一台机器。
|
||||
|
||||
|
||||
|
||||
说明
|
||||
|
||||
Publish over SSH参数用来配置要远程连接的服务器信息,包括服务器IP地址,用户名、密码、端口等,本次测试的服务器IP为”192.168.176.160”。
|
||||
|
||||
Name 为自定义的主机名称,在Jenkins项目配置中会通过该名称来连接指定的服务器。
|
||||
|
||||
Remote Directory用来定义将文件copy到远程服务器的路径为”/data”目录,所以要确保176.160服务器上存在该目录,否则通过该插件进行文件拷贝的时候会提示路径不存在。
|
||||
|
||||
Timeout选项最好设置大一点,默认30s,也就是如果连接超过30s就会断开连接,如果你的项目部署时间超过30s就需要更改此处(默认情况下一般不会超过30秒)。
|
||||
|
||||
配置好后下面有test configuration按钮,用来测试是否能连接成功。
|
||||
|
||||
如果担心远程服务器主机更改密码频繁,配置连接该主机也可以通过ssh-key的方式,主要配置上面的path to key或者key参数。其中,Path to key参数用来配置key文件(私钥)的路径。Key参数可用于将私钥复制到这个框中。如果你启动Jenkins服务用户已经做了对目标主机的免密认证,也可以不用配置这两个参数
|
||||
|
||||
配置好以后,保存退出。
|
||||
|
||||
代码部署
|
||||
|
||||
添加完主机以后,就可以将编译好的包部署到该服务器上去了。这里以部署服务到tomcat为例:
|
||||
|
||||
修改job配置,在”构建后操作“步骤的下拉列表中点击”Send build artifacts over SSH“,配置如下:
|
||||
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
SSH Server下的 Name为要发送文件到或者执行命令的远程主机,在上面的添加远程连接服务器步骤中有配置远程目标服务器的名称(本例为docker-test)。
|
||||
|
||||
Transfers 选项下的Source files栏这里应该写要从本地复制的jar包或者war包(微服务一般都是jar包),这里写成**/xx.war的方式,类似于linux中的find方式从当前工作空间的所有目录去发现该war包。
|
||||
|
||||
Remote Prefix 相当于将找到的该war包路径的前缀去掉,因为代码编译好后默认会在项目名称目录下生成target目录,该目录存放代码编译后的产物,比如jar包,war包等。需要注意的是这里的路径是相对于当前项目所在的路径。
|
||||
|
||||
Remote directory表示要将文件拷贝到此参数设置的目录下,本示例设置的是/目录。需要注意的是,这里设置的目录不是linux文件系统下的/目录,而是在jenkins”系统配置“里添加该服务器时Remote Directory选项所指定目录下的/目录,从”添加主机“小节的截图可看到,我在添加服务器时设置的Remote Directory目录为/data,所以这里的Remote directory对应到远程主机的目录为/data/,并且这个目录需要在构建前自己创建,否则发送文件时会提示目录不存在。
|
||||
|
||||
exec command表示在远程服务器上执行shell命令,此处的命令是一行一行执行的,并且上一条命令执行出错并不影响下条命令的执行,但是如果这些命令是有依赖关系的(比如下条命令依赖上条命令),会导致部署失败。这里为了方便测试和学习,先将命令一行一行写出来,也可以将上面的命令写成一个脚本,然后通过执行脚本去部署服务。
|
||||
|
||||
这样就完成了一个自动部署tomcat服务的job配置。看一下Jenkins执行结果。
|
||||
|
||||
|
||||
|
||||
然后去浏览器访问tomcat服务ip加端口,以及uri(此示例为test1)即可。如图所示:
|
||||
|
||||
|
||||
|
||||
在配置jenkins项目部署war包的时候,除了使用Publish Over SSH插件以外,也可以通过在”Exec shell“选项框中直接输入shell命令或者shell脚本部署tomcat服务,相比于Publish Over SSH插件,该方法更简单灵活,扩展性更强,管理起来也更加方便。至于如何编写shell脚本,这里我就不演示了,有兴趣的可以自己实现一下。
|
||||
|
||||
创建maven类型的项目
|
||||
|
||||
通过上一小节的实践内容,相信你对jenkins的使用应该有了进一步的了解,下面再来创建一个maven类型的job来加深一下理解。
|
||||
|
||||
Maven类型的项目,顾名思义,就是适合使用maven工具进行代码编译的java项目,配置maven类型的项目时,配置步骤与使用freestyle类型的项目稍有不同。
|
||||
|
||||
比如,我这里创建了一个名称为test-maven-project的job,默认的配置步骤如下:
|
||||
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
相比于freestyle类型的项目,maven类型项目中将”构建“步骤拆分为Pre Steps和build两个步骤,build步骤专门用来配置使用maven工具编译代码时的配置参数,Post Steps与Pre Steps步骤配置参数相同,”构建设置“步骤则增加了邮件通知功能。
|
||||
|
||||
代码编译
|
||||
|
||||
相对于freestyle类型的项目配置,”源码管理“步骤配置不变,只需要在build步骤配置一下maven参数即可,如下所示:
|
||||
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
在maven类型的项目配置中,只需要配上图中列出的这两项即可,对于”Root Pom“的设置,同样以当前(${WORKSPACE})路径(Jenkins工作路径)作为基本路径,只写pom.xml就代表项目的根目录下的pom.xml文件。
|
||||
|
||||
”高级“按钮的下拉框中,包含maven构建时的一些参数配置,使用的配置文件定义等,实际使用过程中使用默认配置就行(当前maven部署程序下的配置文件)。当然如果配置文件有特殊改动也可以在配置中自己定义。
|
||||
|
||||
保存好配置直接构建就行了,是不是很简单。
|
||||
|
||||
代码部署
|
||||
|
||||
代码部署的方式与使用freestyle方式部署项目的方法相同,既可以使用publish over ssh插件也可以使用Exec shell方式。只不过在maven项目中,使用Publish over SSH插件的参数选项名称变为Send files execute commands over ssh,但是配置参数没有变。
|
||||
|
||||
有关上面部署操作这里就不在重复演示了,在以后的章节中会介绍其他部署方式。
|
||||
|
||||
创建一个流水线类型的项目
|
||||
|
||||
创建与使用一个流水线类型(包括多分支流水线类型)的项目需要了解jenkins pipeline的一些基础知识,由于内容较多并且涉及到一些插件的使用,所以有关pipeline的内容将在后续的章节中进行详细的说明。
|
||||
|
||||
部署springboot项目到容器
|
||||
|
||||
本次jenkins系列课程的核心是使用Jenkins持续交付与部署微服务架构类型的项目。按照微服务的理念,如果使用容器作为基础设施,能够实现业务应用的的快速交付、快速部署,所以下面就简单介绍一下如何将服务部署到容器中。当然,这需要你掌握一部分docker相关的知识。
|
||||
|
||||
首先在Jenkins界面创建一个maven类型的job,如下所示:
|
||||
|
||||
|
||||
|
||||
选中”构建一个maven项目”,然后点击最下面的”ok”即可。
|
||||
|
||||
创建项目以后,需要配置项目源码仓库,分支,以及pom.xml路径,如何设置前面已经说过,此处不再演示,直接看构建后操作。
|
||||
|
||||
在”Add Post-build Steps(构建后操作)“下拉框里选中Send files or execute commands over ssh,配置如下:
|
||||
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
有关该插件的各个参数的说明,在上面的示例中有介绍过,这里不再多说。
|
||||
|
||||
上面部署的命令中,除了服务器上指定的目录需要事先创建以外,还需要目标服务器上(/data/base/nop目录)预先放一个Dockerfile文件,用于将maven编译的构建产物jar包文件构建到镜像中去。
|
||||
|
||||
Dockerfile如下:
|
||||
|
||||
FROM fabric8/java-alpine-openjdk8-jre
|
||||
|
||||
COPY fw-base-nop.jar /deployments/
|
||||
CMD java -jar /deployments/fw-base-nop.jar
|
||||
|
||||
|
||||
command中代码如下:
|
||||
|
||||
docker stop base-nop
|
||||
docker rm base-nop
|
||||
|
||||
docker rmi base-nop
|
||||
cd /data/fw-base-nop
|
||||
docker build -t base-nop .
|
||||
docker run --network host -e TZ="Asia/Shanghai" -d -v /etc/localtime:/etc/localtime:ro -m 2G --cpu-shares 512 -v /data/logs/fw-base-nop:/data/logs/fw-base-nop --name base-nop base-nop
|
||||
|
||||
|
||||
使用docker命令 停止/删除容器、停止/删除镜像、最后启动容器。
|
||||
|
||||
执行结果:
|
||||
|
||||
|
||||
|
||||
这样就完成了一个将服务部署到容器内的流程。配置比较简单,介绍此示例的目的主要是为了了解一下流程,在以后的章节中会基于此示例进行一个优化。
|
||||
|
||||
有关使用jenkins的基础实践到这里就结束了。回顾本章节的内容都比较基础,使用两个入门级的示例简单介绍了一下使用不同类型的Jenkins项目进行工作的流程,在以后的进阶章节除了会优化上面示例中的代码,同时也会使用多种不同的方式实现服务的持续交付和部署。
|
||||
|
||||
|
||||
|
||||
|
499
专栏/Jenkins持续交付和持续部署/05.Jenkins常用项目配置参数.md
Normal file
499
专栏/Jenkins持续交付和持续部署/05.Jenkins常用项目配置参数.md
Normal file
@@ -0,0 +1,499 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
05.Jenkins常用项目配置参数
|
||||
在上一章节简单介绍了使用jenkins部署服务到不同的机器上,但是对于在项目中的其它选项参数都没有过多介绍,所以本章的开始先简单的介绍一下项目中一些常用的参数选项。
|
||||
|
||||
项目配置
|
||||
|
||||
本小节以freestyle(自由风格)类型的项目为例,对不同步骤中涉及到的一些常用的选项参数进行简单的说明,需要注意的是,每个步骤中的参数选项会根据在Jenkins中安装的插件会有所不同,如果下面介绍到的某些选项参数在你的项目中不存在,有可能是插件没有安装,所以如果遇到这种情况,直接搜索所用的插件安装即可。
|
||||
|
||||
General
|
||||
|
||||
General 步骤主要是用来配置构建任务的一些通用配置,比如:
|
||||
|
||||
描述:该输入框内容自定义,也可以不写,出发点是用来记录该job任务的一些描述信息,比如该项目属于哪个组,服务的用途是什么等内容。
|
||||
|
||||
Discard old builds(丢弃旧的构建):用于处理Jenkins项目构建的历史记录,”保持构建的天数” 和 “保持构建的最大个数”根据自己情况自定义。
|
||||
|
||||
This project is parameterized(参数化构建过程):在非pipeline类型的job中这可能是用的比较多的一个参数,用于定义job构建过程中用到的一些参数,参数的类型很多,在下面会进行详细的介绍。
|
||||
|
||||
Throttle builds(控制并发的选项),用来设置两个任务之间最小间隔和同一个时间内最大任务数量,该功能用的很少,如果要使用此选项,则下面的”在必要的时候并发构建“参数需要勾选。
|
||||
|
||||
Restrict where this project can be run(限制项目的运行节点):设置项目在指定的机器上运行。
|
||||
|
||||
源码管理
|
||||
|
||||
源码管理步骤这里不需要在过多介绍了,根据不同的源码仓库选择不同的代码拉取方式即可,对代码仓库的认证方式使用jenkins凭证,前面有过介绍,这里不再多说。
|
||||
|
||||
构建触发器
|
||||
|
||||
该步骤中使用介绍了几种使job任务自动构建的方法,比如:
|
||||
|
||||
Trigger builds remotely (e.g., from scripts)(触发远程构建 (例如,使用脚本)):用于在jenkins外部通过url命令触发,需要自己设置身份令牌(TOKEN_NAME),该参数在配置时已经给出了url的连接,比较简单。
|
||||
|
||||
Build after other projects are built:监控其他job的构建状态,触发此job构建。
|
||||
|
||||
Build periodically:周期进行项目构建,输入框内的填写方式参考linux中的crontab,如果”分时日月周”中有0,可以用H代替。
|
||||
|
||||
Poll SCM:用于定时检查源码变更,如果有变更就拉取代码并执行构建动作。如果没有更新就不会构建,同样遵循linux crontab的书写方式。
|
||||
|
||||
构建环境
|
||||
|
||||
构建环境步骤用于在任务构建之前所做的一些准备配置,比如:
|
||||
|
||||
Use secret text(s) or file(s):用于绑定想要使用的jenkins凭证,该参数使用的插件为Credentials Binding Plugin。
|
||||
|
||||
Send files or execute commands over SSH before the build starts:在任务构建之前发送文件或者执行命令,该参数使用的插件为Publish Over SSH。
|
||||
|
||||
With Ant:使用ant,与maven工具类似,使用的插件为Ant Plugin。
|
||||
|
||||
构建
|
||||
|
||||
“构建“步骤可以理解为整个jenkins任务工作的核心了,在这个步骤中,对于中心任务的实现有多种方法,相应的参数选项也会比较多,这里会根据实际情况酌情介绍部分选项参数。比如:
|
||||
|
||||
Build/Pubilsh Docker image:用于根据指定的Dockerfile构建镜像并上传到私有仓库,使用的插件为Docker plugin。
|
||||
|
||||
invoke top-level Maven targets:前面有做简单介绍,这里不重复说明。
|
||||
|
||||
Conditional steps(multiple):根据一个或者多个条件进行任务构建,默认自带的选项参数。
|
||||
|
||||
Deploy to Kubernetes:根据给定的文件部署资源对象到kubernetes集群,使用的插件为Kubernetes Continuous Deploy Plugin,后面会详细介绍。
|
||||
|
||||
Execute Docker command:用于执行docker命令或者,包括重启容器,拉取镜像、打包镜像等,基本上支持在bash shell中的所有docker子命令。
|
||||
|
||||
Execute sonarQube Scanner:执行sonar-scanner命令检测代码质量,用到的插件为Sonarqube scanner。
|
||||
|
||||
Exec shell:使用jenkins启动时设置的用户执行shell命令。
|
||||
|
||||
Invoke Ansible ..:在jenkins中使用ansible系列命令,用到的插件为jenkins ansible plugin。
|
||||
|
||||
Provide Configuration files:使用在jenkins中配置的某些服务或功能的配置文件,使用的插件为Config File Provider Plugin,在以后的章节中会介绍到。
|
||||
|
||||
Send files or execute commands over ssh:通过ssh发送文件或者执行shell命令,使用的插件为Publish Over SSH。
|
||||
|
||||
有关”构建”步骤中的选项参数就简单的介绍这么多,虽然在工作中不一定能全部用到,但是了解这些选项的用法对于灵活使用jenkins还是很有帮助的。
|
||||
|
||||
构建后操作
|
||||
|
||||
构建后操作可以算是Jenkins 任务的收尾工作了,在此步骤中,可以通过Archive the artifacts选项保存在上一步骤中的生成的归档文件;可以使用E-mail Notification选项发送构建详情等信息通知管理人员;也可以使用publish over ssh插件发送部署文件或执行命令或者使用Build other project选项根据条件构建其他任务等。
|
||||
|
||||
关于Jenkins项目配置的部分选项参数就简单的介绍到这里,对于一些选项的使用在以后的章节中会进行详细的介绍。
|
||||
|
||||
参数化构建过程
|
||||
|
||||
实际工作中,在Jenkins UI中配置job或者编写pipeline流水线的时候,为了提高Jenkins Job的灵活性,肯定会有用到变量的情况。变量的值除了可以使用Jenkins内部预设的变量,也可以使用从外部传入的变量。而这些从外部传入的变量,在配置jenkins Job时,大多数都是以参数化构建的方式定义的。
|
||||
|
||||
使用参数化构建,可以根据实际情况定义自己想要设定的参数,jenkins参数化构建选项提供了十多种类型的参数定义,下面针对一些常用类型的参数定义做一个简单的说明。
|
||||
|
||||
Boolean Parmeter
|
||||
|
||||
布尔型参数,该类型的参数的值只能是true或者false。
|
||||
|
||||
示例如下,勾选This project is parameterized,从”添加参数“下拉列表中点击”Boolean Parameter“
|
||||
|
||||
|
||||
|
||||
在”构建”步骤使用Exec shell编写如下脚本:
|
||||
|
||||
#!/bin/bash
|
||||
|
||||
if ${test_boolean} is true:
|
||||
then
|
||||
echo "参数勾选了"
|
||||
else
|
||||
echo "参数没有勾选"
|
||||
fi
|
||||
|
||||
|
||||
传递的参数需要通过${参数名称}引用。
|
||||
|
||||
保存后点击Build with Parameters,如下所示:
|
||||
|
||||
|
||||
|
||||
构建结果这里就不展示了,有兴趣的自己测试一下。
|
||||
|
||||
Choice Parameter
|
||||
|
||||
选择参数,在jenkins中定义一个参数值列表,构建时通过选定的值传给参数,并被其他步骤引用。
|
||||
|
||||
示例如下:
|
||||
|
||||
|
||||
|
||||
在”构建”步骤中在”Exec shell” 输入框中输入命令。
|
||||
|
||||
#!/bin/bash
|
||||
|
||||
echo "current command: git clone $branch http://192.168.176.154/root/test-helloworld.git"
|
||||
|
||||
|
||||
保存后执行job。
|
||||
|
||||
|
||||
|
||||
构建时可以根据下拉列表中的分支拉取代码。构建结果自己测试即可。
|
||||
|
||||
File Parameter
|
||||
|
||||
文件参数用于从浏览器表单提交中接受一个文件,并作为构建参数。上传后的文件将会放在当前工作空间($WORKSPACE)中指定的位置,你可以在构建任务中访问并使用它。
|
||||
|
||||
如下示例:
|
||||
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
表单提交中的文件名称就是文件的路径,并且是在环境变量中可见的。但是引用该文件时,直接使用自定义的文件路径即可。例如:把文件放到当前工作目录的test目录(不存在会自动创建)下,使用时直接使用该文件路径,而不用使用变量。
|
||||
|
||||
在”构建“步骤中的”Exec shell” 输入框中输入命令:
|
||||
|
||||
#!/bin/bash
|
||||
|
||||
cat $WORKSPACE/test/test.xml
|
||||
|
||||
|
||||
点击保存后执行,即可查看该文件的内容。
|
||||
|
||||
Extend choice Parameter
|
||||
|
||||
顾名思义,可扩展的选择参数,相比于choice Parameter只能使用单个参数选项外,该参数可以同时定义多个参数选项,并为每个参数选项设置不同的值,该参数使用插件为Extended Choice Parameter Plug-In。
|
||||
|
||||
首先看一下该参数支持的参数类型:
|
||||
|
||||
|
||||
|
||||
其中:
|
||||
|
||||
“Name 和Description”参数为自定义。-
|
||||
该参数支持的类型主要为单选、多选框和输入框,至于hidden用处不大,这里不做介绍。
|
||||
|
||||
单选类型参数
|
||||
|
||||
Single Select和Radio Butions均为单选框。
|
||||
|
||||
首先看一下Single Select类型,选择该类型后,点击构建时多个参数会组成一个下拉列表。
|
||||
|
||||
|
||||
|
||||
其中:
|
||||
|
||||
Name:为该参数的名称。-
|
||||
Description:该参数的描述。-
|
||||
Parameter Type:为参数类型,这里选择的是single select。-
|
||||
Number of Visible Items:可用的参数的数量。-
|
||||
Delimiter:如果设置了多个参数,每个参数之间的分隔符。
|
||||
|
||||
参数值设置:
|
||||
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
Choose Source for Value:该参数得值,必须项。-
|
||||
Choose Source for default Value:该参数的默认值,非必须。-
|
||||
Choose Source for Value Description:关于该参数值的描述,非必须;如果没有设置该选项,在执行job的时候,在参数后面的下拉列表中会直接显示该参数的值;如果设置了才参数,在执行job的时候,在参数后面的下拉列表中会直接显示该参数的值的描述,也就是此参数设置的值,需要注意的是,如果设置了此参数,此参数的值数量,要与设置的value的值的数量相同,如果数量不相同,最后的一个值会使用设定的Value的值填充,效果可以看下面。
|
||||
|
||||
在”构建“步骤中的”Exec shell” 输入框中输入命令:
|
||||
|
||||
#!/bin/bash
|
||||
|
||||
echo $test_extend_choice
|
||||
|
||||
|
||||
执行时效果如下所示:
|
||||
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
在配置job时,对于最后一个job的描述值没有填写,这里就会直接显示value的值。-
|
||||
此时如果选择para1并执行,就会打印值value1。
|
||||
|
||||
如果设置了基础参数类型为Radio Butions时,构建job时,显示的是这样的:
|
||||
|
||||
|
||||
|
||||
这里可以看到在选择参数值的时候出现了滚动条,这是因为在设置”可用的参数的数量“的时候,设置的值比实际的参数的数量少造成的,所以为了美观,建议有多少个参数就设置”可用的参数的数量”为多少。
|
||||
|
||||
其他设置与使用Single Select时一样,这里不再多说,有兴趣的可以自己试验一下。
|
||||
|
||||
多选类型参数
|
||||
|
||||
如果设置了多选框,在执行jenkins任务的时候可以设置多个选项参数,并使用这些参数指定的值,多选框的类型为Multi Select和Check Boxes,首先看一下基础版本。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
在”Exec Shell“输入框中输入:
|
||||
|
||||
#!/bin/bash
|
||||
|
||||
echo $key
|
||||
|
||||
|
||||
测试一下,执行时会输出选定的key的值。
|
||||
|
||||
|
||||
|
||||
选定哪个参数,就会输出哪个参数对应的值。
|
||||
|
||||
除了使用单个Extend choice Parameter以外,也可以创建多个Extend choice Parameter组合使用。
|
||||
|
||||
示例
|
||||
|
||||
比如下面示例:在部署微服务应用的时候,常常会遇到多个微服务应用构建时依赖同一个公共服务的情况,此时在部署多个微服务应用的同时,可以先构建公共服务,在构建应用服务。
|
||||
|
||||
首先,如果多个项目依赖一个公共服务的情况,第一个Extend choice Parameter参数可以使用单选框类型,为了美观以及更清楚地列出公共服务列表,我使用Radio Buttons类型的参数,如下所示:
|
||||
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
定义一个依赖服务参数,参数选择类型为单选,定义两个参数值,build和not_build,对应的描述分别为”构建依赖服务”和”不构建依赖服务”。
|
||||
|
||||
第二个Extend choice Parameter参数配置如下:
|
||||
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
该参数名称为app_service,描述为应用服务。
|
||||
|
||||
在”Exec Shell“输入框中输入如下脚本。
|
||||
|
||||
#!/bin/bash
|
||||
|
||||
string=$app_service
|
||||
array=(${string//,/ })
|
||||
|
||||
build_job(){
|
||||
for service in ${array[@]}
|
||||
do
|
||||
{
|
||||
echo "并行构建 $service "
|
||||
}&
|
||||
done
|
||||
wait
|
||||
|
||||
}
|
||||
|
||||
if [ "$dependency_service" == "build" ]
|
||||
then
|
||||
echo "构建依赖服务xxx"
|
||||
build_job
|
||||
else
|
||||
echo "不构建依赖服务"
|
||||
build_job
|
||||
|
||||
fi
|
||||
|
||||
|
||||
执行如下所示:
|
||||
|
||||
|
||||
|
||||
结果如下:
|
||||
|
||||
|
||||
|
||||
其次,如果有多个公共服务的时候,可以使用多选框类型的选择参数进行配置,参考上面示例中应用服务的配置即可。
|
||||
|
||||
除了使用Check boxes作为多选的选择参数以外,还可以用Multi Select,只不过该参数在选择多个参数选项的时候需要按住ctrl键,操作起来比Check boxes略显麻烦,其它都没什么特殊变动,这里就不在演示了,有兴趣的可以自己尝试一下。
|
||||
|
||||
输入类型参数
|
||||
|
||||
输入类型参数就比较简单了,定义了参数之后,参数类型直接使用Text Box类型,然后在构建时输入参数的值即可,一般用的不多。
|
||||
|
||||
Git Parameter
|
||||
|
||||
Git Parameter主要用于在构建job之前预先从”源码管理“步骤中设定的git源码库中获取该源码仓库的git标记(支持的git标记包括标签、分支、分支或标签、修订、Pull Request),并根据标记拉取git代码进行构建。
|
||||
|
||||
如下所示,点击”Build with Parameter”后会自动列出设定的源码仓库的git标记(列出的标记根据设定的git标记类型会有所不同,此示例使用的git标记为分支)。
|
||||
|
||||
|
||||
|
||||
job设置如下:
|
||||
|
||||
|
||||
|
||||
其中:
|
||||
|
||||
|
||||
参数类型可以根据自己实际情况选择
|
||||
默认值为必须要填写的参数
|
||||
|
||||
|
||||
此时在设置”源码管理”的时候需要设置分支为上面创建的参数变量。
|
||||
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
|
||||
Branches to build 虽然名称为指定的分支,但是对于上面提到的各种git标记放到这里都是可用的。
|
||||
|
||||
|
||||
Password Parameter
|
||||
|
||||
密码类型的参数:如果在配置Jenkins任务的时候不想在job中或者console log中显示明文密码,可以使用该参数,如下所示,可以给该参数设置一个默认值。
|
||||
|
||||
|
||||
|
||||
比如在登录私有docker hub仓库的时候使用该参数。
|
||||
|
||||
#!/bin/bash
|
||||
|
||||
docker login 192.168.176.155 -u admin -p $test_password_para
|
||||
|
||||
|
||||
虽然整个过程是加密的,但是在exec shell中通过echo $test_password_para命令还是能够获取明文密码的,并且涉及到密码的使用,Jenkins凭据会比使用该参数更有优势,所以此参数还是根据个人情况使用吧。
|
||||
|
||||
String Parameter
|
||||
|
||||
字符串参数,最普通的参数,类似于Extend choice Parameter中的输入类型参数,比较简单,这里就不演示了,有兴趣的可以自己尝试一下。
|
||||
|
||||
有关参数化构建的内容就简单的介绍到这里。
|
||||
|
||||
变量
|
||||
|
||||
在Jenkins job中除了可以使用上面自定义的变量外,还可以使用Jenkins中内置的环境变量,内置的环境变量可以在job的任何配置步骤中使用,可以通过http://Jenkins_URL/env-vars.html/获取所有的内置变量。如下所示:
|
||||
|
||||
|
||||
|
||||
……
|
||||
|
||||
变量很多,但是在工作场景中用的比较频繁也就那么几个,比如WORKSPACE变量,该变量表示当前job的工作空间,也是该job在Jenkins服务器上的绝对路径;BUILD_NUMBER变量表示当前job的构建ID;JOB_NAME表示当前job的名称,也就是jenkins的项目名称。比如在”Exec shell“中输入。
|
||||
|
||||
echo $WORKSPACE $BUILD_NUMBER $BUILD_ID $JOB_NAME
|
||||
|
||||
|
||||
构建job后输出的内容如下:
|
||||
|
||||
|
||||
|
||||
关于其他变量的介绍可以自己去试一下,这里不做过多介绍。
|
||||
|
||||
视图
|
||||
|
||||
使用jenkins的过程中,如果Job数量特别大,想快速定位想要构建或者修改配置的Job、或者想要对同一个项目组中Job任务进行统一管理,可以通过在Jenkins中建立视图来对Job进行管理。jenkins中的视图类似于电脑上的文件夹。默认jenkins中有一个All的视图,所有的项目均会在此视图下列出。
|
||||
|
||||
创建视图
|
||||
|
||||
下面创建一个视图。
|
||||
|
||||
在Jenkins的主面板点击”New View“选项(也可以点击All视图右面的”+“按钮),在跳转的界面中输入视图名称,如下图所示:
|
||||
|
||||
|
||||
|
||||
其中:
|
||||
|
||||
|
||||
List view选项比较简单,用于从全局job列表中自定义选择要放到该视图下的Job,选择Job时可以通过条件过滤或者正则表达式匹配想要放到该视图下的Job;同时对于该视图中的显示Job信息的列的字段名称进行自定义添加或者删除,如下所示
|
||||
|
||||
|
||||
|
||||
根据自己实际需求配置即可。
|
||||
|
||||
My View选项将会将当前用户所能看到的所有job添加到新创建的视图中。
|
||||
|
||||
Pipeline Aggregator View选项用于在jenkins系统中全屏显示pipeline类型job的staeg和Job构建属性信息等,一般用处不大。
|
||||
|
||||
|
||||
配置好点击保存就创建了视图。
|
||||
|
||||
如果想要删除视图,在点击视图名称后,直接在jenkins面板中点击”删除视图”即可。并且视图删除后,job任务不会被删除。
|
||||
|
||||
凭据
|
||||
|
||||
凭据(cridential)是Jenkins访问第三方应用时的认证信息。凭据可以是账号/密码、SSH密钥、加密文件等。Jenkins可以通过设置的凭据与其它第三方应用在可信与可控的范围内进行交互。为了提高安全性,Jenkins会对凭据进行加密存储,使用该凭据时默认使用凭据ID与第三方应用认证。
|
||||
|
||||
Jenkins默认可以存储以下类型的凭据:
|
||||
|
||||
|
||||
Secret text - API token之类的token (如GitHub个人访问token),
|
||||
Username and password - 用户名和密码
|
||||
Secret file - 保存在文件中的加密内容
|
||||
SSH Username with private key - SSH 公钥/私钥对
|
||||
Certificate - PKCS#12 证书文件和可选密码
|
||||
|
||||
|
||||
而如果安装了像docker、kubernetes、openshift这类插件的情况下,也会增加对这类系统的凭据类型:
|
||||
|
||||
|
||||
Docker Host Certificate Authentication - Docker 仓库认证信息
|
||||
Kubernetes configuration(kubeconfig):对kubernetes容器编排系统的认证
|
||||
openshift usernanme password:对openshift系统登录的认证
|
||||
|
||||
|
||||
创建凭据
|
||||
|
||||
在jenkins主面板中点击”凭据(cridential)”—>”系统(system)”—>”全局凭据(Global credentials)(unrestricted)“,如下所示:
|
||||
|
||||
|
||||
|
||||
在跳转的界面中点击”添加凭据(add credentials)“,以创建一个Username with password类型的凭据为例。
|
||||
|
||||
如下所示:
|
||||
|
||||
|
||||
|
||||
这里需要说一下ID参数,ID参数的值可以自定义,如果不填写,jenkins会自动生成一个ID,至于如何使用这个凭据,将在下面的”创建jenkins项目”小节中介绍。
|
||||
|
||||
Jenkins凭据在持续交付和部署过程中使用比较频繁,在以后的章节中会介绍多种类型的凭据的创建,本节有关凭据的内容就简单的介绍到这里。
|
||||
|
||||
配置Master-Slave
|
||||
|
||||
使用jenkins的时候,为了避免Jenkins服务所在的主机资源消耗过大,影响Jenkins服务的性能,往往会增加一个或多个slave节点来专门执行Jenkins任务,这就是我们熟知的Jenkins的Master/slave架构。
|
||||
|
||||
Jenkins 的Master/Slave架构相当于Server和agent的概念。Master提供web接口让用户来管理job和slave,job可以运行在master本机或者被分配到slave上运行。一个Master可以关联多个slave用来为不同的job或相同的job的不同配置来服务。
|
||||
|
||||
配置Jenkins slave节点不需要安装jenkins服务,只需要将jenkins主节点使用的一些基础工具(本系列课程用到jdk、maven、git、ansible、docker、sonar等工具栈)等安装配置好就可以了。jenkins的slave节点可以是虚拟机,也可以是容器,可以是静态的主机资源,也可以是动态的容器资源,鉴于使用动态的容器资源需要相关的jenkins插件方面的知识,这里就先介绍一下如何添加静态的主机节点。
|
||||
|
||||
点击 “Manage Jenkins(系统管理)”–> “Manage Nodes(节点管理)“,可以看到目前只有一个master节点,点击左侧的”New Node“,给该slave节点设置一个名字,比如我这里为”slave1”,点击确定,
|
||||
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
|
||||
of executors(并发构件数量):表示同时进行构建的任务数量,根据自己情况填写。
|
||||
|
||||
Remote root directory(远程工作目录):为必填项,里面用来保存拉取的应用代码,根据自己情况设定。
|
||||
|
||||
Labels(标签):该slave节点的标签,在Jenkins 项目里可以通过该标签匹配到slave节点。
|
||||
|
||||
Usage:用于设置使用该节点的策略,可以设置为尽可能多的使用该节点(如上所示),也可以设置为only build jobs with label expressions matching this node,表示只有jenkins job中设置了指定job任务在该节点运行才会使用该节点。
|
||||
|
||||
Launch metheod:用于配置jenkins slave节点的启动方式,主要分为ssh和jnlp两种,其中ssh为master主动连接slave节点,jnlp为slave节点主动连接agent,图上的配置使用的ssh方式。
|
||||
|
||||
|
||||
Host(主机):这里写Jenkins Slave服务器的地址。
|
||||
|
||||
Credentials(凭据):这里需要添加一下登录Slave服务器的用户名和密码。
|
||||
|
||||
Advanced(高级):用来设置该服务器的一些相关信息。
|
||||
|
||||
port:如果服务器ssh端口不是22,可以设置成别的。
|
||||
|
||||
JavaPath:这里需要设置一下 slave服务器java可执行文件的绝对路径。比如我的配置为(/usr/local/jdk1.8.0_231/bin/java)。
|
||||
|
||||
设置完成后,点击最下方的保存按钮。在跳转到的界面,然后点击launch Agent按钮。日志如下表示添加节点成功。
|
||||
|
||||
|
||||
|
||||
节点添加成功后,可以修改jenkins项目配置,勾选Restrict where this project can be run,在出现的框中输入slave节点的名称或者label,保存后再次构建该项目,就会在新添加的节点构建该项目。如下所示:
|
||||
|
||||
|
||||
|
||||
除了使用vm虚拟机作为jenkins的slave节点以外,也可以使用docker容器作为jenkins的slave节点,同时slave节点也可以通过容器编排工具进行编排,在以后的章节中这些内容都会介绍到,并且对于常用的使用动态slave作为job执行的环境的内容也会进行说明,敬请期待。
|
||||
|
||||
有关jenkins项目配置内容到这里就结束了,在后面的章节开始进入实践阶段。
|
||||
|
||||
|
||||
|
||||
|
983
专栏/Jenkins持续交付和持续部署/06.Jenkins部署之Docker要点.md
Normal file
983
专栏/Jenkins持续交付和持续部署/06.Jenkins部署之Docker要点.md
Normal file
@@ -0,0 +1,983 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
06.Jenkins部署之Docker要点
|
||||
docker 基础操作
|
||||
|
||||
在前面的实践章节介绍了将服务部署到容器中,有些读者对于docker可能并不熟悉,而且在后面的文章中,基本上所有的实践案例都是围绕着容器展开的,所以介绍后面的实践内容之前有必要先学习一下docker的基础操作。当然,如果你对docker的操作比较熟悉,也可以略过此节的内容。
|
||||
|
||||
Docker的三大核心组件:镜像、容器和镜像仓库。使用docker服务的操作基本上也是围绕这三大核心组件开展的,在前面的”基础工具安装“章节对于企业级的镜像仓库已经做了介绍,本节主要来介绍一下镜像操作和容器操作。如果你时一个docker老司机,那么你也可以略过此章节。
|
||||
|
||||
镜像基本操作
|
||||
|
||||
本小节主要涉及到的内容如下:
|
||||
|
||||
|
||||
获取镜像
|
||||
列出镜像
|
||||
删除镜像
|
||||
构建镜像
|
||||
|
||||
|
||||
获取镜像
|
||||
|
||||
从Docker 官方Registry获取镜像的命令是docker pull。其命令格式为:
|
||||
|
||||
docker pull [option] [Docker Registry地址]/<仓库名>:<标签>
|
||||
|
||||
|
||||
具体的参数选项可以通过docker pull --help命令查看,这里说一下镜像地址名称的格式。
|
||||
|
||||
Docker Registry地址的格式一般是
|
||||
|
||||
<域名/IP>[:端口号]
|
||||
|
||||
|
||||
默认地址是Docker Hub的地址。
|
||||
|
||||
<仓库名>:如上面语法所示,这里的仓库名是两段式名称,即
|
||||
|
||||
<用户名>/<镜像名>
|
||||
|
||||
|
||||
对于 Docker Hub,如果不给出用户名,则默认为library,也就是官方镜像。比如:
|
||||
|
||||
$ docker pull ubuntu:14.04
|
||||
14.04:Pulling from library/ubuntu
|
||||
......
|
||||
Digest:sha256:147913621d9cdea08853f6ba9116c2e27a3ceffecf3b492983ae97c3d643fbbe
|
||||
Status:Downloaded newer image for ubuntu:14.04
|
||||
|
||||
|
||||
上面的镜像地址没有给出,默认使用docker hub下library用户下镜像,获取官方library/ubuntu仓库中标签为14.04的镜像。
|
||||
|
||||
如果从其他镜像仓库获取,例如:阿里云
|
||||
|
||||
docker pull registry.cn-beijing.aliyuncs.com/daimler-jenkins/jenkins-slave-java
|
||||
|
||||
|
||||
如果想要查询官方的某个镜像怎么办?使用
|
||||
|
||||
docker search <关键字>
|
||||
|
||||
|
||||
命令可以根据关键字搜索镜像,比如我要搜索jenkins镜像,就会列出存在的jenkins镜像,如下所示
|
||||
|
||||
[root@glusterfs-160 ~]# docker search jenkins
|
||||
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
|
||||
jenkins Official Jenkins Docker image 4642 [OK]
|
||||
jenkins/jenkins The leading open source automation server 1904
|
||||
jenkinsci/blueocean https://jenkins.io/projects/blueocean 490
|
||||
......
|
||||
|
||||
|
||||
也可以通过在浏览器访问hub.docker.com官方界面通过输入关键字搜素想要获取的镜像,这里就不在演示了,有兴趣大家可以试一下
|
||||
|
||||
列出镜像
|
||||
|
||||
要想列出已经下载下来的镜像,可以使用 docker images 命令。
|
||||
|
||||
$ docker images
|
||||
REPOSITORYTAG IMAGE ID CREATED SIZE
|
||||
redis latest 5f515359c7f8 5 days ago 183MB
|
||||
nginx latest 05a60462f8ba 5 days ago 181MB
|
||||
|
||||
|
||||
上面列表包含了仓库名、标签、镜像ID、创建时间以及所占用的空间。镜像ID是镜像的唯一标识,一个镜像可以对应多个标签。
|
||||
|
||||
如果仔细观察,会注意到,这里标识的所占用空间和在Docker Hub上看到的镜像大小不同。
|
||||
|
||||
比如openjdk:latest 镜像大小,拉取下来为495 MB,但是在 Docker Hub显示的却是 244 MB。这是因为Docker Hub中显示的体积是压缩后的体积。在镜像下载和上传过程中镜像是保持着压缩状态的,因此 Docker Hub 所显示的大小是网络传输中更关心的流量大小。
|
||||
|
||||
而 docker images命令显示的是镜像下载到本地后,展开的大小,准确说,是展开后的各层所占空间的总和,因为镜像到本地后,查看空间的时候,更关心的是本地磁盘空间占用的大小。
|
||||
|
||||
另外一个需要注意的问题是,docker images 列表中的镜像体积总和并非是所有镜像实际硬盘消耗。由于 Docker 镜像是多层存储结构,并且可以继承、复用,因此不同镜像可能会因为使用相同的基础镜像,从而拥有共同的层。由于 Docker 使用 Union FS,相同的层只需要保存一份即可,因此实际镜像硬盘占用空间很可能要比这个列表镜像大小的总和要小的多。
|
||||
|
||||
虚悬镜像
|
||||
|
||||
如果在images列出的这中镜像既没有仓库名,也没有标签,并且均为”<none>“的镜像,比如
|
||||
|
||||
<none> <none> 00285df0df87 5 days ago 342 MB
|
||||
|
||||
|
||||
这类镜像为虚悬镜像,虚悬镜像的产生大多是因为镜像仓库里的镜像更新并重新拉取后,旧镜像的标签转移到新镜像上来导致的;另一种情况是通过build操作构建镜像时,构建失败导致的。一般来说,虚悬镜像已经失去了存在的价值,是可以随意删除的。
|
||||
|
||||
可通过如下命令列出所有的虚悬镜像
|
||||
|
||||
$ docker images -f dangling=true
|
||||
|
||||
|
||||
可以用下面的命令删除
|
||||
|
||||
$ docker rmi $(docker images -q -f dangling=true)
|
||||
|
||||
|
||||
中间层镜像
|
||||
|
||||
为了加速镜像构建、重复利用资源,Docker会利用中间层镜像。所以在使用一段时间后,可能会看到一些依赖的中间层镜像。
|
||||
|
||||
与之前的虚悬镜像不同,有些无标签的镜像很多都是中间层镜像,是其它镜像所依赖的镜像。这些无标签镜像不应该删除,否则会导致上层镜像因为依赖丢失而出错。实际上,如果这些镜像在使用中,默认也是不能删除的。这些镜像也没必要删除,因为相同的层只会存一遍,而这些镜像是别的镜像的依赖,因此并不会因为它们被列出来而多存了一份,无论如何你也会需要它们。
|
||||
|
||||
只要删除那些依赖它们的镜像后,这些依赖的中间层镜像也会被连带删除。
|
||||
|
||||
默认的docker images列表中只会显示顶层镜像,如果希望显示包括中间层镜像在内的所有镜像的话,需要加-a参数。
|
||||
|
||||
$ docker images -a
|
||||
|
||||
|
||||
列出部分镜像
|
||||
|
||||
不加任何参数的情况下,docker images命令会列出所有顶级镜像,但是有时候我们只希望列出部分镜像。docker images命令有多个参数可以帮助做到这个事情。
|
||||
|
||||
#根据仓库名列出镜像
|
||||
|
||||
$ docker images Ubuntu
|
||||
|
||||
|
||||
#列出特定的某个镜像,也就是说指定仓库名和标签
|
||||
|
||||
$ docker images ubuntu:16.04
|
||||
|
||||
|
||||
根据指定条件列出镜像
|
||||
|
||||
比如根据REPOSITORY和Tag列出匹配的镜像
|
||||
|
||||
$ docker images --filter=reference='busy*:*libc'
|
||||
|
||||
REPOSITORY TAG IMAGE ID CREATED SIZE
|
||||
busybox uclibc e02e811dd08f 5 weeks ago 1.09 MB
|
||||
busybox glibc 21c16b6787c6 5 weeks ago 4.19 MB
|
||||
|
||||
|
||||
也可以使用多个filter
|
||||
|
||||
$ docker images --filter=reference='busy*:uclibc' --filter=reference='busy*:glibc'
|
||||
|
||||
REPOSITORY TAG IMAGE ID CREATED SIZE
|
||||
busybox uclibc e02e811dd08f 5 weeks ago 1.09 MB
|
||||
busybox glibc 21c16b6787c6 5 weeks ago 4.19 MB
|
||||
|
||||
|
||||
--filter支持的关键字如下
|
||||
|
||||
|
||||
dangling (boolean - true or false),列出虚悬镜像
|
||||
label (label= or label==) 根据指定lable列出镜像
|
||||
before ([:], or) - 过滤出在给定的image id之前创建的镜像
|
||||
since ([:], or) - 过滤出引用此image id创建的镜像
|
||||
reference 根据给出的匹配条件过滤出镜像
|
||||
|
||||
|
||||
以特定格式显示
|
||||
|
||||
默认情况下,docker images 会输出一个完整的表格,但是我们并非所有时候都会需要这些内容。比如,刚才删除虚悬镜像的时候,我们需要利用docker images 把所有的虚悬镜像的ID列出来,然后才可以交给docker rmi命令作为参数来删除指定的这些镜像,这个时候可以使用-q 参数,用于列出镜像的id
|
||||
|
||||
$ docker images -q
|
||||
5f515359c7f8
|
||||
|
||||
|
||||
如果希望自己组织列,获取指定的解析结果,可以用Go的模板语法。
|
||||
|
||||
比如,下面的命令会直接列出镜像结果,并且只包含镜像ID和仓库名:
|
||||
|
||||
$ docker images --format "{{.ID}}:{{.Repository}}“
|
||||
5f515359c7f8: redis
|
||||
|
||||
|
||||
或者打算以表格等距显示,并且有标题行,和默认一样,不过自己定义列:
|
||||
|
||||
$ docker images --format "table{{.ID}}\t{{.Repository}}\t{{.Tag}}"
|
||||
IMAGE ID REPOSITORY TAG
|
||||
5f515359c7f8 redis latest
|
||||
|
||||
|
||||
format支持如下关键字
|
||||
|
||||
.ID
|
||||
|
||||
Image ID
|
||||
|
||||
.Repository
|
||||
|
||||
镜像仓库名称
|
||||
|
||||
.Tag
|
||||
|
||||
镜像标签
|
||||
|
||||
.Digest
|
||||
|
||||
镜像 摘要
|
||||
|
||||
.CreatedSince
|
||||
|
||||
镜像创建后经过多长时间
|
||||
|
||||
.CreatedAt
|
||||
|
||||
镜像创建时间
|
||||
|
||||
.Size
|
||||
|
||||
镜像占用空间大小
|
||||
|
||||
删除镜像
|
||||
|
||||
如果要删除本地的镜像,可以使用docker image rm命令,其格式为:
|
||||
|
||||
$ docker image rm [选项] <镜像1> [<镜像2>...]
|
||||
或者使用
|
||||
$ docker rmi [OPTIONS] IMAGE [IMAGE...]
|
||||
|
||||
|
||||
可以通过 docker rmi --help 查看更多帮助
|
||||
|
||||
#用docker images 命令来配合
|
||||
|
||||
可以使用 docker images -q 来配合使用docker rmi,这样可以成批的删除希望删除的镜像。删除虚悬镜像的指令是:
|
||||
|
||||
$ docker rmi $(docker images -q -f dangling=true)
|
||||
|
||||
|
||||
#比如,我们需要删除所有仓库名为redis的镜像:
|
||||
|
||||
$ docker rmi $(docker images -q redis)
|
||||
|
||||
|
||||
#默认情况下,正在运行使用的镜像是不能被删除的,需要先停止容器在进行删除操作,也可以使用 -f 参数强制删除
|
||||
|
||||
构建镜像
|
||||
|
||||
使用 Dockerfile 定制镜像
|
||||
|
||||
要构建镜像,就要制定构建的指令,而Dockerfile就是包含一些自定义的指令和格式、用来构建镜像的文本文件。
|
||||
|
||||
Dockerfile 提供了一系列统一的资源配置语法,用户可以根据这些语法命令自定义配置,构建自定义镜像。
|
||||
|
||||
在学习构建镜像之前,先学习一下Dockerfile的一些基本介绍以及基本指令
|
||||
|
||||
此次Dockerfile主要介绍如下内容:
|
||||
|
||||
|
||||
Docker build 构建过程
|
||||
Dockerfile 基础指令
|
||||
使用 Dockerfile 定制镜像
|
||||
|
||||
|
||||
构建过程大致如下:
|
||||
|
||||
Docker Client 端收到用户指令,解析命令行参数并发送给Docker Server,Docker Server 端收到http请求后:
|
||||
|
||||
|
||||
首先创建一个临时目录,将context指定的文件系统解压到该目录下
|
||||
读取并解析Dockerfile
|
||||
根据解析出的Dockerfile遍历其中所有指令,并分发到不同的模块去执行
|
||||
解析器指令(parser)为每一个指令创建一个临时容器,并在里面执行当前命令,然后通过commit,使此容器生成一个镜像层。所有层的集合就是build后的结果。最后一次commit的镜像id就会作为此镜像的最终Id
|
||||
|
||||
|
||||
Dockerfile 基础指令
|
||||
|
||||
COPY
|
||||
|
||||
复制文件,格式如下
|
||||
|
||||
COPY <源路径>...<目标路径>COPY ["<源路径1>",..."<目标路径>"]
|
||||
|
||||
|
||||
COPY 指令有两种格式,一种类似于命令行,一种类似于函数调用。
|
||||
|
||||
COPY 指令将从构建上下文目录中<源路径> 的文件/目录复制到新的一层的镜像内的<目标路径> 位置。
|
||||
|
||||
比如:
|
||||
|
||||
COPY package.json /usr/src/app/
|
||||
|
||||
|
||||
<源路径> 可以是多个,甚至可以是通配符,其通配符规则要满足Go的 filepath.Match规则,如:
|
||||
|
||||
COPY hom* /mydir/COPY hom?.txt /mydir/
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
|
||||
<目标路径> 可以是容器内的绝对路径,也可以是相对于工作目录的相对路径(工作目录可以用WORKDIR指令来指定)。
|
||||
<目标路径>不需要事先创建,如果目录不存在会在复制文件前先行创建缺失目录。
|
||||
|
||||
|
||||
此外,还需要注意一点,使用COPY指令,源文件的各种元数据都会保留。比如读、写、执行权限、文件变更时间等。这个特性对于镜像定制很有用。特别是构建相关文件都在使用Git进行管理的时候。
|
||||
|
||||
ADD 高级的复制文件
|
||||
|
||||
ADD 指令和COPY的格式和性质基本一致。但是在COPY基础上增加了一些功能
|
||||
|
||||
比如 <源路径> 可以是一个URL,这种情况下,Docker引擎会试图去下载这个链接的文件放到<目标路径> 下;下载后的文件权限自动设置为600,如果这并不是想要的权限,那么还需要增加额外的一层RUN进行权限调整。
|
||||
|
||||
另外,如果下载的是个压缩包,需要解压缩,也一样还需要额外的一层RUN指令进行解压缩。所以不如直接使用RUN指令,然后使用wget或者curl工具下载,处理权限、解压缩、然后清理无用文件更合理。因此,这个功能其实并不实用,而且不推荐使用。
|
||||
|
||||
如果 <源路径> 为一个tar压缩文件的话,压缩格式为gzip,bzip2以及xz的情况下,ADD指令将会自动解压缩这个压缩文件到<目标路径> 去。
|
||||
|
||||
在某些情况下,这个自动解压缩的功能非常有用,比如官方镜像ubuntu中:
|
||||
|
||||
FROM scratch
|
||||
ADD ubuntu-xenial-core-cloudimg-amd64-root.tar.gz /...
|
||||
|
||||
|
||||
但在某些情况下,如果我们真的是希望复制个压缩文件进去,而不解压缩,这时就不可以使用ADD命令了。最适合使用ADD的场合,就是所提及的需要自动解压缩的场合。
|
||||
|
||||
因此在COPY和ADD指令中选择的时候,可以遵循这样的原则,所有的文件复制均使用COPY指令,仅在需要自动解压缩的场合使用ADD。
|
||||
|
||||
CMD 容器启动命令
|
||||
|
||||
Docker 容器实质就是进程。既然是进程,那么在启动容器的时候,需要指定所运行的程序及参数。CMD 指令就是用于指定默认的容器主进程的启动命令的。
|
||||
|
||||
在指令格式上,一般推荐使用exec格式,这类格式在解析时会被解析为JSON数组,因此一定要使用双引号”,而不要使用单引号。
|
||||
|
||||
提到CMD就不得不提容器中应用在前台执行和后台执行的问题。
|
||||
|
||||
Docker 不是虚拟机,容器中的应用都应该以前台执行,而不是像虚拟机、物理机里面那样,用upstart/systemd去启动后台服务,容器内没有后台服务的概念。
|
||||
|
||||
CMD service nginx start 会被理解为 CMD [ "sh","-c", "service nginx start"],因此主进程实际上是sh。那么当 service nginx start命令结束后,sh也就结束了,sh作为主进程退出了,自然就会令容器退出。
|
||||
|
||||
正确的做法是直接执行nginx可执行文件,并且要求以前台形式运行。比如:
|
||||
|
||||
CMD ["nginx","-g", "daemon off"]
|
||||
|
||||
|
||||
ENTRYPOINT
|
||||
|
||||
ENTRYPOINT 的格式和RUN指令格式一样,分为exec格式和 shell 格式。ENTRYPOINT 的目的和CMD一样,都是在指定容器启动程序及参数。
|
||||
|
||||
当指定了ENTRYPOINT 后,CMD 的含义就发生了改变,不再是直接的运行其命令,而是将 CMD 的内容作为参数传给 ENTRYPOINT指令,即实际执行时,将变为:
|
||||
|
||||
<ENTRYPOINT> "<CMD>"
|
||||
|
||||
|
||||
说通俗了就是相当于在容器内部(或者dockerfile)CMD执行的命令列表里加上了新传入的参数,并且执行
|
||||
|
||||
示例如下
|
||||
|
||||
首先看一下CMD命令
|
||||
|
||||
$ cat Dockerfile
|
||||
FROM nginx
|
||||
CMD ["echo","hello"]
|
||||
|
||||
$ docker build -t nginx:test .
|
||||
|
||||
$ docker run -it nginx:test
|
||||
|
||||
|
||||
效果如下所示:
|
||||
|
||||
|
||||
|
||||
说明
|
||||
|
||||
|
||||
重新构建的镜像加入了CMD命令(此命令会覆盖之前存在镜像里的nginx daeomon off命令),所以运行时会打印出新加入的命令,并且运行后会自动关闭容器(因为命令执行完毕了)
|
||||
|
||||
|
||||
在运行一个容器,并且传入命令
|
||||
|
||||
docker run -it nginx:test world
|
||||
|
||||
|
||||
|
||||
|
||||
说明
|
||||
|
||||
|
||||
新传入的命令 world 会覆盖cmd里的echo hello命令,由于此命令不存在,所以会报错
|
||||
|
||||
|
||||
修改Dockerfile,CMD改成ENTRYPOINT
|
||||
|
||||
$ cat dockerfile
|
||||
FROM nginx
|
||||
ENTRYPOINT ["echo","hello"]
|
||||
|
||||
$ docker build -t nginx:ent .
|
||||
|
||||
$ docker run -it nginx:ent
|
||||
|
||||
$ docker run -it nginx:ent world
|
||||
|
||||
|
||||
|
||||
|
||||
从执行结果可以看到world 被当做参数传去了
|
||||
|
||||
WORKDIR 指定工作目录
|
||||
|
||||
格式:
|
||||
|
||||
WORKDIR <工作目录路径>
|
||||
|
||||
|
||||
使用WORKDIR指令可以来指定工作目录(或者称为当前目录),以后各层的当前目录就被改为指定的目录,如该目录不存在,WORKDIR 会帮你建立目录。
|
||||
|
||||
在 Shell中,连续两行是同一个进程执行环境,因此前一个命令修改的内存状态,会直接影响后一个命令;而在Dockerfile中,这两行RUN命令的执行环境根本不同,是两个完全不同的容器。
|
||||
|
||||
因此如果需要改变以后各层的工作目录的位置,那么应该使用WORKDIR指令。
|
||||
|
||||
示例
|
||||
|
||||
FROM nginx
|
||||
WORKDIR /home
|
||||
RUN pwd
|
||||
ENTRYPOINT ["pwd"]
|
||||
|
||||
|
||||
效果如下所示:
|
||||
|
||||
|
||||
|
||||
ENV 设置环境变量
|
||||
|
||||
格式:
|
||||
|
||||
ENV <key> <value>
|
||||
ENV <key1>=<value1> <key2>=<value2>...
|
||||
|
||||
|
||||
这个指令是设置环境变量,无论是后面的其它指令,如RUN,还是运行时的应用,都可以直接使用这里定义的环境变量。
|
||||
|
||||
ENV NODE_VERSION 7.2.0
|
||||
|
||||
RUN touch $NODE_VERSION-txt
|
||||
CMD ["ls"]
|
||||
|
||||
|
||||
下列指令可以支持环境变量展开:
|
||||
|
||||
ADD、COPY、ENV、EXPOSE、LABEL、USER、WORKDIR、VOLUME、STOPSIGNAL、ONBUILD。
|
||||
|
||||
|
||||
ARG 构建参数
|
||||
|
||||
格式:
|
||||
|
||||
ARG <参数名>[=<默认值>]
|
||||
|
||||
|
||||
构建参数和ENV的效果一样,都是设置环境变量。所不同的是,ARG所设置的构建环境的环境变量,在将来容器运行时是不会存在这些环境变量的。但是不要因此就使用ARG 保存密码之类的信息,因为docker history 还是可以看到所有值的。
|
||||
|
||||
Dockerfile中的 ARG 指令是定义参数名称,以及定义其默认值。该默认值可以在构建命令 docker build 中用 --build-arg <参数名>=<值> 来覆盖。
|
||||
|
||||
在1.13 之前的版本,要求--build-arg中的参数名,必须在Dockerfile中用ARG定义过才能用。换句话说,就是--build-arg指定的参数,必须在Dockerfile中使用了。如果对应参数没有被使用,则会报错退出构建。
|
||||
|
||||
从1.13 开始,这种严格的限制被放开,不再报错退出,而是显示警告信息,并继续构建。这对于使用CI系统,用同样的构建流程构建不同的Dockerfile的时候比较有帮助,避免构建命令必须根据每个Dockerfile的内容修改。
|
||||
|
||||
如下示例:
|
||||
|
||||
$ cat Dockerfile
|
||||
ARG full_name
|
||||
ENV JAVA_APP_JAR $full_name
|
||||
ENV AB_OFF true
|
||||
ADD$JAVA_APP_JAR /deployments/
|
||||
|
||||
#构建
|
||||
$ docker build -t image_name --build-arg full_name=full_name .
|
||||
|
||||
|
||||
EXPOSE 声明端口
|
||||
|
||||
格式:
|
||||
|
||||
EXPOSE <端口1> [<端口2>...]
|
||||
|
||||
|
||||
EXPOSE指令是声明运行时容器提供服务端口,这只是一个声明,在运行时并不会因为这个声明应用就会开启这个端口的服务。
|
||||
|
||||
在Dockerfile中写入这样的声明有两个好处:
|
||||
|
||||
|
||||
帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射;
|
||||
在运行时使用随机端口映射时,也就是docker run -P时,会自动随机映射 EXPOSE的端口。
|
||||
|
||||
|
||||
要将EXPOSE和在运行时使用 -p 宿主端口:容器端口 区分开来。-p,是映射宿主端口和容器端口。换句话说,就是将容器的对应端口服务公开给外界访问;而EXPOSE仅仅是声明容器打算使用什么端口而已,并不会自动在宿主进行端口映射。
|
||||
|
||||
VOLUME 定义匿名卷
|
||||
|
||||
格式:
|
||||
|
||||
VOLUME ["<路径1>","<路径2>"...]
|
||||
VOLUME <路径>
|
||||
|
||||
|
||||
VOLUME /data
|
||||
|
||||
这里的/data目录就会在运行时自动挂载为匿名卷,任何向/data 中写入的信息都不会记录进容器存储层,从而保证了容器存储层的无状态化。当然,运行时可以覆盖这个挂载设置,比如:
|
||||
|
||||
docker run -d -v /mydata:/data xxxx
|
||||
|
||||
|
||||
在这行命令中,就使用了mydata这个命名卷挂载到了/data 这个位置,替代了Dockerfile中定义的匿名卷的挂载配置。
|
||||
|
||||
示例
|
||||
|
||||
$ cat dockerfile
|
||||
FROM nginx
|
||||
WORKDIR /home
|
||||
RUN pwd
|
||||
volume ["/data"]
|
||||
|
||||
$ docker build -t nginx:volume .
|
||||
|
||||
$ docker run -itd nginx:volume
|
||||
|
||||
|
||||
通过docker inspect docker_id 可查看此volume 映射到本地的位置,创建一个文件,写入测试的内容,然后退出容器,可发现文件在本地还在
|
||||
|
||||
USER 指定当前用户
|
||||
|
||||
USER 指令和 WORKDIR 相似,都是改变环境状态并影响以后的层。WORKDIR 是改变工作目录,USER 则是改变之后层的执行RUN,CMD 以及ENTRYPOINT 这类命令的身份。
|
||||
|
||||
当然,和 WORKDIR 一样,USER 只是帮助你切换到指定用户而已,这个用户必须是事先建立好的,否则无法切换
|
||||
|
||||
RUN groupadd -r redis && useradd -r -g redisredis
|
||||
|
||||
USER redis
|
||||
|
||||
RUN ["redis-server"]
|
||||
|
||||
|
||||
如果以root 执行的脚本,在执行期间希望改变身份,比如希望以某个已经建立好的用户来运行某个服务进程,不要使用su或者sudo,这些都需要比较麻烦的配置,而且在TTY 缺失的环境下经常出错。
|
||||
|
||||
# 建立redis用户,并使用gosu换另一个用户执行命令
|
||||
|
||||
RUN groupadd -r redis && useradd -r -g redis redis
|
||||
|
||||
|
||||
# 设置CMD,并以另外的用户执行
|
||||
|
||||
CMD ["exec", "gosu","redis","redis-server"]
|
||||
|
||||
|
||||
到此,常用的dockerfile的指令就介绍的差不多了,熟悉dockerfile的各个指令以后,可以帮助我们更加快速的编写dockerfile文件
|
||||
|
||||
使用 Dockerdile 定制镜像
|
||||
|
||||
了解了Dockerfile的常用指令后,编写好Dockerfile,就可以通过docker build命令构建镜像了。
|
||||
|
||||
首先看一下构建语法
|
||||
|
||||
$ docker build --help
|
||||
$ docker build [OPTIONS] PATH | URL | -
|
||||
|
||||
|
||||
PATH或URL指向的文件为context(上下文路径),此路径包含build过程中的Dockerfile文件以及其它需要构建到镜像中的资源文件。
|
||||
|
||||
使用Dockerfile 定制镜像,那一定是以一个镜像为基础,在其上进行定制。就像下面 redis镜像的容器,对其进行修改一样,基础镜像是必须指定的。而FROM就是指定基础镜像,因此一个Dockerfile中FROM是必备的指令,并且必须是第一条指令。
|
||||
|
||||
先看一个初学者示例(只做演示用,非最终版):
|
||||
|
||||
$ cat Dockerfile
|
||||
FROM debian:jessie
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y gcclibc6-dev make
|
||||
RUN wget -O redis.tar.gz "http://download.redis.io/releases/redis-3.2.5.tar.gz"
|
||||
RUN mkdir -p /usr/src/redis
|
||||
RUN tar -xzf redis.tar.gz -C /usr/src/redis--strip-components=1
|
||||
RUN make -C /usr/src/redis
|
||||
RUN make -C /usr/src/redis install
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
|
||||
FROM 指定一个基础镜像,必须存在
|
||||
RUN 指令用来执行命令行命令,最常用的指令之一
|
||||
|
||||
|
||||
Dockerfile 中每一个指令都会建立一层,RUN也不例外。每一个RUN的行为,就和刚才我们手工建立镜像的过程一样:新建立一层,在其上执行这些命令,执行结束后,commit这一层的修改,构成新的镜像。
|
||||
|
||||
而上面的这种写法,创建了7层镜像。这是完全没有意义的,而且很多运行时不需要的东西,都被装进了镜像里,比如编译环境、更新的软件包等等。结果就是产生非常臃肿、非常多层的镜像,不仅仅增加了构建部署的时间,也很容易出错。 这是很多初学 Docker 的人常犯的一个错误。Union FS 是有最大层数限制的,比如 AUFS,曾经是最大不得超过 42 层,现在是不得超过 127 层。
|
||||
|
||||
所以,修改上面dockerfile 如下所示:
|
||||
|
||||
FROM debian:jessie
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y gcclibc6-dev make \
|
||||
&& wget -O redis.tar.gz "http://download.redis.io/releases/redis-3.2.5.tar.gz" \
|
||||
&& mkdir -p /usr/src/redis \
|
||||
&& tar -xzf redis.tar.gz -C /usr/src/redis--strip-components=1 \
|
||||
&& make -C /usr/src/redis \
|
||||
&& make -C /usr/src/redis install
|
||||
|
||||
|
||||
另一点需要注意的是,Dockerfile的文件名并不要求必须Dockerfile。也可以使用-f参数指定其他Dockerfile名称,此时后面的工作目录需要指定Dockerfile的上下文目录。
|
||||
|
||||
比如,我的Dockerfile在/root/docker/目录下,此目录下还有一个aa.json文件,Dockerfile如下
|
||||
|
||||
FROM debian:jessie
|
||||
RUN build Deps='gcc libc6-dev make wget'\
|
||||
&& mkdir -p /usr/src/redis
|
||||
COPY ./aa.json /usr/src/redis
|
||||
|
||||
|
||||
那么构建的命令为
|
||||
|
||||
docker build -t redis:v1 -f /root/docker/Dockerfile /root/docker/
|
||||
|
||||
|
||||
如果目录下有些东西确实不希望构建时传给Docker引擎,那么可以用.gitignore一样的语法写一个.dockerignore,该文件是用于剔除不需要作为上下文传递给Docker引擎的。
|
||||
|
||||
echo ".git" > .dockerignore
|
||||
|
||||
|
||||
在Dockerfile文件所在目录根据上面的docker build命令进行镜像构建:
|
||||
|
||||
$ docker build -t redis:v3 .
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
|
||||
”.” 表示当前目录(可以理解为Dockerfile目录),而dockerfile就在当前目录也是在指定的上下文的目录
|
||||
|
||||
|
||||
有关镜像操作的内容就介绍到这里,下面看一下容器的操作
|
||||
|
||||
容器操作
|
||||
|
||||
本小节主要介绍以下内容:
|
||||
|
||||
|
||||
Docker命令分类
|
||||
启动容器
|
||||
后台运行容器
|
||||
查看容器
|
||||
进入容器
|
||||
导入导出容器
|
||||
删除容器
|
||||
|
||||
|
||||
Docker 命令分类
|
||||
|
||||
在进行容器实操之前,先看看容器的一些命令分类
|
||||
|
||||
Docker 环境信息 info、version
|
||||
|
||||
容器生命周期管理 Create、exec、kill、pause、restart、rm、run、start、stop、unpause
|
||||
|
||||
镜像仓库命令 login、logout、pull、push、search
|
||||
|
||||
镜像管理 build、images、import、load、rmi、save、tag、commit
|
||||
|
||||
容器运维操作 attach、export、inspect、port、ps、rename、stats、top、wait、cp、diff、update
|
||||
|
||||
容器资源管理 volume、network
|
||||
|
||||
系统信息日志 events、history、logs #events打印容器的实时系统事件, history 打印出指定镜像的历史版本信息,logs打印容器中进程的运行日志
|
||||
|
||||
|
||||
更多命令可参考官网
|
||||
|
||||
启动容器
|
||||
|
||||
启动容器有两种方式,一种是基于镜像新建一个容器并启动,另外一个是将在终止状态(stopped)的容器重新启动。
|
||||
|
||||
命令语法
|
||||
|
||||
$ docker run --help
|
||||
Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
|
||||
|
||||
|
||||
新建并启动,所需要的命令主要为 docker run。例如,下面的命令输出一个 “Hello World”,之后终止容器。
|
||||
|
||||
$ docker run ubuntu:14.04 /bin/echo 'Hello world'
|
||||
Hello world
|
||||
|
||||
|
||||
注意:容器启动后只是输出 hello world,输出后容器关闭退出
|
||||
|
||||
下面的命令则启动一个 bash终端,允许用户进行交互。
|
||||
|
||||
$ sudo docker run -t -i -m 2G --cpu-shares 1536 ubuntu:14.04 /bin/bash
|
||||
root@af8bae53bdd3:/#
|
||||
|
||||
|
||||
其中:
|
||||
|
||||
|
||||
-t 参数让Docker分配一个伪终端(pseudo-tty)并绑定到容器的标准输入上
|
||||
-i 参数则让容器的标准输入保持打开。
|
||||
-c 参数用于给运行的容器分配cpu的shares值
|
||||
-m 参数用于限制为容器的内存信息,以 B、K、M、G 为单位
|
||||
-v 参数用于挂载一个volume,可以用多个-v参数同时挂载多个volume
|
||||
-p 参数用于将容器的端口暴露给宿主机端口 格式:hostport:containerport
|
||||
|
||||
|
||||
需要注意的是,如果没加-d(后台运行)参数,从容器bash终端退出后,此容器会终止运行
|
||||
|
||||
启动和终止容器
|
||||
|
||||
可以使用 docker start/stop/restart 命令,直接将一个已经存在的容器启动/关闭/重启。容器的核心为所执行的应用程序,所需要的资源都是应用程序运行所必需的。除此之外,并没有其它的资源。可以在伪终端中利用ps或top命令来查看进程信息。
|
||||
|
||||
$ docker start/stop/restart <container_Id/containerName>
|
||||
|
||||
|
||||
后台运行容器
|
||||
|
||||
更多的时候,需要让Docker在后台运行而不是直接把执行命令的结果输出在当前宿主机下。可以通过添加 -d 参数来实现。
|
||||
|
||||
注意:
|
||||
|
||||
容器是否会长久运行,是和docker run指定的命令有关,和 -d 参数无关。docker 容器后台运行必须加 -it参数,使用-d参数启动后会返回一个唯一的 id,也可以通过 docker ps 命令来查看容器信息。
|
||||
|
||||
要获取容器的输出信息,可以通过如下命令获取。
|
||||
|
||||
docker logs -f [container ID or container NAMES]
|
||||
|
||||
|
||||
终止后台运行的容器
|
||||
|
||||
可以使用docker container stop来终止一个运行中的容器,当Docker容器中指定的应用结束时,容器也自动终止。 只启动了一个终端的容器,用户通过 exit 命令或 Ctrl+d 来退出终端时,所创建的容器立刻终止。
|
||||
|
||||
终止状态的容器可以用docker container ls -a或者docker ps -a 命令看到。
|
||||
|
||||
例如:
|
||||
|
||||
$ sudo docker ps -a
|
||||
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
|
||||
ba267838cc1b ubuntu:14.04 "/bin/bash" 30 minutes ago Exited (0) About a minute ago trusting_newton
|
||||
98e5efa7d997 training/webapp:latest "python app.py" About an hour ago Exited (0) 34 minutes ago backstabbing_pike
|
||||
|
||||
|
||||
处于终止状态的容器,可以通过docker start命令来重新启动(如果不是意外终止的容器)。docker restart命令会将一个运行态的容器终止,然后再重新启动它。
|
||||
|
||||
查看容器
|
||||
|
||||
容器启动了,那么该如何查看呢?通过docker ps命令,docker ps命令不加参数情况下用来列出正在运行的容器。
|
||||
|
||||
首先看一下ps命令的参数
|
||||
|
||||
$ docker ps -h
|
||||
Flag shorthand -h has been deprecated, please use --help
|
||||
|
||||
Usage: docker ps [OPTIONS]
|
||||
|
||||
List containers
|
||||
|
||||
Options:
|
||||
-a, --all Show all containers (default shows just running)
|
||||
-f, --filter filter Filter output based on conditions provided
|
||||
--format string Pretty-print containers using a Go template
|
||||
-n, --last int Show n last created containers (includes all states) (default -1)
|
||||
-l, --latest Show the latest created container (includes all states)
|
||||
--no-trunc Don't truncate output
|
||||
-q, --quiet 只显示容器id
|
||||
-s, --size Display total file sizes
|
||||
|
||||
|
||||
-a 参数用来显示所有的容器,无论状态是运行的还是退出的
|
||||
|
||||
$ docker ps -a
|
||||
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
|
||||
336dded76f59 gitlab/gitlab-ee "/assets/wrapper" 2 months ago Up 2 months (healthy) 0.0.0.0:80->80/tcp, 0.0.0.0:2222->22/tcp, 0.0.0.0:2443->443/tcp gitlab
|
||||
854e0ae79353 goharbor/harbor-jobservice:v1.9.1 "/harbor/harbor_jobs…" 4 months ago Exited (128) 4 months ago harbor-jobservice
|
||||
.....
|
||||
|
||||
|
||||
-f 参数用来根据关键字过滤出想要列出的容器,比如
|
||||
|
||||
根据name获取指定的容器
|
||||
|
||||
$ docker ps --filter "name=nostalgic"
|
||||
|
||||
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
|
||||
715ebfcee040 busybox "top" 3 seconds ago Up 1 second i_am_nostalgic
|
||||
9b6247364a03 busybox "top" 7 minutes ago Up 7 minutes nostalgic_stallman
|
||||
|
||||
|
||||
根据指定退出码列出容器
|
||||
|
||||
$ docker ps -a --filter 'exited=0'
|
||||
|
||||
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
|
||||
ea09c3c82f6e registry:latest /srv/run.sh 2 weeks ago Exited (0) 2 weeks ago 127.0.0.1:5000->5000/tcp desperate_leakey
|
||||
|
||||
$ docker ps -a --filter 'exited=137'
|
||||
|
||||
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
|
||||
b3e1c0ed5bfe ubuntu:latest "sleep 1000" 12 seconds ago Exited (137) 5 seconds ago grave_kowalevski
|
||||
|
||||
|
||||
根据容器状态列出容器(status)
|
||||
|
||||
可选参数有 created, restarting, running, removing, paused, exited and dead.
|
||||
|
||||
$ docker ps --filter status=running
|
||||
|
||||
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
|
||||
715ebfcee040 busybox "top" 16 minutes ago Up 16 minutes i_am_nostalgic
|
||||
|
||||
$ docker ps --filter status=paused
|
||||
|
||||
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
|
||||
673394ef1d4c busybox "top" About an hour ago Up About an hour (Paused)
|
||||
|
||||
|
||||
使用-f参数还可以使用–format关键字,将获取到的内容格式化或者获取想要的指定的容器信息,
|
||||
|
||||
例如要获取容器的容器id和容器名称
|
||||
|
||||
$ docker ps -a --filter 'exited=0' --format "table {{.ID}}\t{{.Names}}"
|
||||
CONTAINER ID NAMES
|
||||
0f15dba71e5c runner-Zm2_mVvw-project-2-concurrent-0-cache-c33bcaa1fd2c77edfc3893b41966cea8
|
||||
5e3cff545d1f runner-Zm2_mVvw-project-2-concurrent-0-cache-3c3f060a0374fc8bc39395164f415a70
|
||||
|
||||
|
||||
format参数支持的关键字如下
|
||||
|
||||
.ID
|
||||
|
||||
Container ID
|
||||
|
||||
.Image
|
||||
|
||||
Image ID
|
||||
|
||||
.Command
|
||||
|
||||
command 命令
|
||||
|
||||
.CreatedAt
|
||||
|
||||
创建时间
|
||||
|
||||
.RunningFor
|
||||
|
||||
运行时间
|
||||
|
||||
.Ports
|
||||
|
||||
expose的端口
|
||||
|
||||
.Status
|
||||
|
||||
容器状态
|
||||
|
||||
.Size
|
||||
|
||||
容器占用的disk大小
|
||||
|
||||
.Names
|
||||
|
||||
容器名称
|
||||
|
||||
.Label
|
||||
|
||||
特殊的容器标签
|
||||
|
||||
有关容器的查看操作就简单介绍到这里
|
||||
|
||||
进入容器
|
||||
|
||||
在使用-d参数时,容器启动后会进入后台。某些时候需要进入容器进行操作,有很多种方法,包括使用docker attach/exec 命令或 nsenter 工具等。
|
||||
|
||||
exec 命令
|
||||
|
||||
sudo docker exec -it 775c7c9ee1e1 /bin/bash
|
||||
|
||||
|
||||
更简单的,下载 .bashrc_docker,并将内容放到 .bashrc 中
|
||||
|
||||
$ cat .bashrc_docker
|
||||
|
||||
alias docker-pid="sudo docker inspect --format '{{.State.Pid}}'"
|
||||
alias docker-ip="sudo docker inspect --format '{{ .NetworkSettings.IPAddress }}'"
|
||||
|
||||
#the implementation refs from https://github.com/jpetazzo/nsenter/blob/master/docker-enter
|
||||
function docker-enter() {
|
||||
#if [ -e $(dirname "$0")/nsenter ]; then
|
||||
#Change for centos bash running
|
||||
if [ -e $(dirname '$0')/nsenter ]; then
|
||||
# with boot2docker, nsenter is not in the PATH but it is in the same folder
|
||||
NSENTER=$(dirname "$0")/nsenter
|
||||
else
|
||||
# if nsenter has already been installed with path notified, here will be clarified
|
||||
NSENTER=$(which nsenter)
|
||||
#NSENTER=nsenter
|
||||
fi
|
||||
[ -z "$NSENTER" ] && echo "WARN Cannot find nsenter" && return
|
||||
|
||||
if [ -z "$1" ]; then
|
||||
echo "Usage: `basename "$0"` CONTAINER [COMMAND [ARG]...]"
|
||||
echo ""
|
||||
echo "Enters the Docker CONTAINER and executes the specified COMMAND."
|
||||
echo "If COMMAND is not specified, runs an interactive shell in CONTAINER."
|
||||
else
|
||||
PID=$(sudo docker inspect --format "{{.State.Pid}}" "$1")
|
||||
if [ -z "$PID" ]; then
|
||||
echo "WARN Cannot find the given container"
|
||||
return
|
||||
fi
|
||||
shift
|
||||
|
||||
OPTS="--target $PID --mount --uts --ipc --net --pid"
|
||||
|
||||
if [ -z "$1" ]; then
|
||||
# No command given.
|
||||
# Use su to clear all host environment variables except for TERM,
|
||||
# initialize the environment variables HOME, SHELL, USER, LOGNAME, PATH,
|
||||
# and start a login shell.
|
||||
#sudo $NSENTER "$OPTS" su - root
|
||||
sudo $NSENTER --target $PID --mount --uts --ipc --net --pid su - root
|
||||
else
|
||||
# Use env to clear all host environment variables.
|
||||
sudo $NSENTER --target $PID --mount --uts --ipc --net --pid env -i $@
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# 将以上内容放到 .bashrc文件下
|
||||
$ source ~/.bashrc
|
||||
|
||||
|
||||
这个文件中定义了很多方便使用Docker的命令,例如docker-pid可以获取某个容器的PID;而docker-enter可以进入容器或直接在容器内执行命令。
|
||||
|
||||
$ docker-enter c0c00b21f8f8
|
||||
root@c0c00b21f8f8:~# ls
|
||||
root@c0c00b21f8f8:~# pwd
|
||||
/root
|
||||
root@c0c00b21f8f8:~# exit
|
||||
logout
|
||||
|
||||
$ docker-pid c0c00b21f8f8
|
||||
11975
|
||||
|
||||
|
||||
删除容器
|
||||
|
||||
可以使用 docker rm 来删除一个处于终止状态的容器。 例如
|
||||
|
||||
$sudo docker rm trusting_newton(<container_name>)
|
||||
trusting_newton
|
||||
|
||||
|
||||
如果要删除一个运行中的容器,可以添加 -f 参数。Docker 会发送 SIGKILL 信号给容器。
|
||||
|
||||
清理所有处于终止状态的容器
|
||||
|
||||
用 docker ps -a 命令可以查看所有已经创建的包括终止状态的容器,如果数量太多要一个个删除可能会很麻烦,用 docker rm $(docker ps -a -q) 可以全部清理掉。
|
||||
|
||||
注意:这个命令其实会试图删除所有的包括还在运行中的容器,不过就像上面提过的 docker rm 默认并不会删除运行中的容器。
|
||||
|
||||
删除所有终止的容器
|
||||
|
||||
$ docker container prune #删除所有停止的容器,
|
||||
-f 强制删除, --filter "until=24h" 删除停止超过24小时的容器
|
||||
|
||||
|
||||
有关docker容器的内容就简单介绍到这里
|
||||
|
||||
|
||||
|
||||
|
1336
专栏/Jenkins持续交付和持续部署/07.Jenkins集成之Ansible要点.md
Normal file
1336
专栏/Jenkins持续交付和持续部署/07.Jenkins集成之Ansible要点.md
Normal file
File diff suppressed because it is too large
Load Diff
419
专栏/Jenkins持续交付和持续部署/08.Jenkins集成Ansible持续部署服务到Docker容器.md
Normal file
419
专栏/Jenkins持续交付和持续部署/08.Jenkins集成Ansible持续部署服务到Docker容器.md
Normal file
@@ -0,0 +1,419 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
08.Jenkins集成Ansible持续部署服务到Docker容器
|
||||
在前面的jenkins基础实践章节中简单介绍了使用jenkins内置插件实现应用代码的发布,通过上一节Ansible基础知识的介绍,本节来使用Ansible对jenkins基础实践章节的内容继续进行一下重构和提优化。
|
||||
|
||||
使用与配置
|
||||
|
||||
在前面的章节中已经对ansible做过基础的介绍,这里不多说,首先看一下Jenkins与Ansible的集成如何使用。
|
||||
|
||||
Jenkins与Ansible的集成使用主要有三种方式:
|
||||
|
||||
|
||||
一种是在步骤中通过在”Exec shell“中直接输入Ansible命令来进行工作;
|
||||
一种就是使用Jenkins中安装的Ansible插件来工作;
|
||||
然后就是在pipeline中使用ansible插件,本章节暂时不涉及此内容;
|
||||
|
||||
|
||||
下面分别介绍一下前两种的使用方法,以一个freestyle类型的job为例。
|
||||
|
||||
使用Exec shell
|
||||
|
||||
使用”Exec shell“,在输入框中输入:
|
||||
|
||||
ansible localhost -m shell -a "hostname"
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
该命令用来在本机执行hostname命令。-
|
||||
localhost是ansible内置的一个组,表示ansible本机。
|
||||
|
||||
除了可以使用ansible Ad-hoc命令意外,还可以使用ansible-playbook命令执行playbook,有兴趣的可以自己尝试一下。
|
||||
|
||||
使用ansible插件
|
||||
|
||||
在”构建“步骤中,点击”Invoke Anisble Ad-hoc command“,配置如下:
|
||||
|
||||
|
||||
|
||||
其中:
|
||||
|
||||
Ansible installation 用来选择ansible的命令,此处的命令获取方式为一个下拉列表,该列表的值是从Jenkins的全局工具中配置ansible相关命令的环境变量获取到的,在前面的章节中我们已经做过此操作,如果没有的话,需要重新添加ansible的环境变量,比如我这里在Jenkins中配置的ansible命令的环境变量如下:
|
||||
|
||||
|
||||
|
||||
Host pattern:用来填写要操作的主机列表,可以是一个主机ip也可以是一个主机组名称。无论是那种,都要确保该主机ip或者主机组名在ansible的主机清单(inventory)文件中存在,localhost和all除外,这两个是ansible内置的组;
|
||||
|
||||
Inventory:主机清单列表,此处可以自己定义,是使用文件还是手动输入,默认使用的文件为/etc/ansible/hosts;
|
||||
|
||||
module:此处为要使用的ansible的模块名称(在ansible命令中通过-m参数指定的值),ansible实现主机批量管理是通过模块实现的,如果不填写默认为command模块;
|
||||
|
||||
Playbook path:只有使用Invoke Ansible Playbook选项参数才会有此参数配置,用来执行要执行的plyabook文件;
|
||||
|
||||
Module arguments or command to execute:这里需要指定在目标主机上执行的命令,也就是ansible命令中-a参数指定的值;
|
||||
|
||||
Credentials:用来指定ansible连接目标主机时对目标服务器认证的凭据,如果在ansible主机清单文件中配置了可以不填写;
|
||||
|
||||
Vault Credentials:加密后的凭据,只支持file和text类型的加密凭据;
|
||||
|
||||
become:在目标主机执行命令的用户,仅支持目标主机上的用户为sudo用户,并且没有password;
|
||||
|
||||
sudo : 提升用户权限为root;
|
||||
|
||||
除了可以使用Ad-hoc命令外,还以可以使用ansible-playbook命令ansible-valut命令,分别对应的参数选项为Invoke Ansible Playbook(用于执行playbook脚本)和Invoke Ansible vault(用来对playbook内容加密处理)。
|
||||
|
||||
配置保存后执行job,执行结果如下:
|
||||
|
||||
|
||||
|
||||
在实际工作中,通过jenkins与ansible集成,使用ansible ad-hoc命令的情况还是比较少见的,大多数都是通过去执行playbook来进行代码发布和部署工作。
|
||||
|
||||
遇到的问题
|
||||
|
||||
无论是使用ansible ad-hoc命令还是使用ansib-playbook命令,如果执行job后报如下错误(找不到ansible命令)。
|
||||
|
||||
|
||||
|
||||
或者(连接不上远程主机)。
|
||||
|
||||
|
||||
|
||||
导致以上报错的出现的原因有两种,一种是真正意义上的命令找不到(或者到主机网络不可达,主机认证失败);另一种就是Jenkins用户权限问题导致的,这里简单说明一下:第一种情况可能不多见,因为安装好ansible后,默认就配好了ansible的全局环境变量;至于主机不可达与主机认证,相信大家都会测试,在服务器终端执行一下命令就知道了,这里不多说,主要说一下Jenkins的权限问题。
|
||||
|
||||
jenkins安装后,在执行job任务的时候默认使用jenkins用户进行,而jenkins用户对ansible,以及使用root的ssh-key连接远程服务器默认是没有权限的。所以要确保用户jenkins对这些命令和文件具有执行权限。这里给出两种解决此问题的方法。
|
||||
|
||||
方法一
|
||||
|
||||
修改Jenkins配置文件。
|
||||
|
||||
$ vim /etc/sysconfig/jenkins
|
||||
#修改JENKINS_USER,并去掉当前行注释
|
||||
JENKINS_USER="root"
|
||||
|
||||
|
||||
修改Jenkins相关文件夹用户权限(根据实际情况做修改,也可以略过此步骤)。
|
||||
|
||||
chown -R root:root /var/lib/jenkins
|
||||
chown -R root:root /var/cache/jenkins
|
||||
chown -R root:root /var/log/jenkins
|
||||
|
||||
|
||||
重启Jenkins(若是其他方式安装的Jenkins则重启方式略不同)。
|
||||
|
||||
systemctl restart jenkins
|
||||
|
||||
|
||||
方法二
|
||||
|
||||
配置jenkins的用户终端,修改jenkins用户shell为bash。
|
||||
|
||||
cat /etc/passwd
|
||||
jenkins:x:989:985:Jenkins Automation Server:/var/lib/jenkins:/bin/bash
|
||||
|
||||
|
||||
配置jenkins用户连接ssh免秘钥。
|
||||
|
||||
[root@ansible ]# su jenkins
|
||||
bash-4.2$ ssh-keygen -t rsa
|
||||
|
||||
bash-4.2$ ssh-copy-id root@ip
|
||||
|
||||
|
||||
使用哪一种方法都可以,但是建议使用方法一,配置好后重新执行job就可以了。
|
||||
|
||||
基础示例
|
||||
|
||||
了解完jenkins与ansible集成如何使用后,下面演示一下使用ansible-playbook部署服务。
|
||||
|
||||
部署服务到tomcat
|
||||
|
||||
以在”Jenkins基础实践“章节部署的test-helloworld为例,在该章节中使用内置插件虽然实现了项目部署的自动化,但是在向大规模的主机中部署项目的时候,使用publish over ssh插件显然已经很难满足要求,所以下面看一下如何用playbook实现该项目的部署。
|
||||
|
||||
脚本如下:
|
||||
|
||||
$ cat /home/single-playbook-helloworld.yaml
|
||||
|
||||
- hosts: "{{ host }}"
|
||||
gather_facts: False
|
||||
vars:
|
||||
war_file: "{{ workspace }}/target/Helloworldwebapp.war"
|
||||
project_dir: "/usr/share/tomcat/webapps/{{ project_name }}"
|
||||
|
||||
tasks:
|
||||
- name: 判断目录是否存在
|
||||
shell: ls {{ project_dir }}
|
||||
register: dict_exist
|
||||
ignore_errors: true
|
||||
|
||||
- name: 关闭tomcat
|
||||
shell: systemctl stop tomcat
|
||||
|
||||
- name: 备份旧代码
|
||||
shell: chdir={{ project_dir }}/ tar -czf /bak/{{ project_name }}-{{ build_num }}-$(date -d "today" +"%Y%m%d_%H%M%S").tar.gz {{ project_dir }}/
|
||||
when: dict_exist is succeeded
|
||||
ignore_errors: true
|
||||
|
||||
- name: 删除旧版本的配置文件
|
||||
file:
|
||||
state: absent
|
||||
dest: "{{ project_dir }}"
|
||||
when: dict_exist is succeeded
|
||||
|
||||
- name: clean cache|清除缓存
|
||||
shell: chdir={{ project_dir }}/../../ nohup rm -rf work/
|
||||
|
||||
- name: 创建目录
|
||||
file:
|
||||
state: directory
|
||||
dest: "{{ project_dir }}"
|
||||
mode: 0755
|
||||
|
||||
- name: 解压war包
|
||||
unarchive:
|
||||
src: "{{ war_file }}"
|
||||
dest: "{{ project_dir }}/"
|
||||
copy: yes
|
||||
mode: 0755
|
||||
|
||||
- name: 启动tomcat
|
||||
shell: systemctl restart tomcat
|
||||
|
||||
|
||||
流程说明:
|
||||
|
||||
该脚本适用于新建项目和已经在用的项目的服务部署;
|
||||
|
||||
为了提高playbook的灵活性,在playbook中使用了变量,分别指定了目标主机、war包目录、服务部署到远程服务器的路径,服务备份路径等,提高playbook的灵活性,降低维护成本;
|
||||
|
||||
在playbook的开始会判断项目部署的目录是否存在,以区分该项目是新建项目还是已经在运行的项目;
|
||||
|
||||
无论是新项目还是老项目部署应用都要关闭tomcat服务;
|
||||
|
||||
备份旧代码 任务会根据”判断目录是否存在“任务的执行结果进行判断,决定要不要执行当前任务;
|
||||
|
||||
上面的脚本,既可以项目中使用插件配置,也可以在“Exec shell“中执行命令,我这里为了简便直接在”Exec shell”中输入命令。
|
||||
|
||||
/opt/apache-maven-3.5.4/bin/mvn install
|
||||
ansible-playbook -i /etc/ansible/hosts /home/single-playbook-helloworld.yaml -e "workspace=${WORKSPACE} host=192.168.176.160 project_name=hello_world build_num=$BUILD_ID"
|
||||
|
||||
|
||||
执行结果如下:
|
||||
|
||||
|
||||
|
||||
这样就通过ansible将服务部署到vm中去了。
|
||||
|
||||
下面在看一个示例。
|
||||
|
||||
部署服务到容器
|
||||
|
||||
使用ansible将微服务部署到容器中去,大致流程与上面的示例相似,但是对于容器和ansible的使用技术有了稍微提高。
|
||||
|
||||
同样引用”Jenkins实践“章节中”部署服务到容器“的示例,对比上面的ansible示例,来简单说明一下使用内置插件部署服务到容器的局限性:
|
||||
|
||||
将服务通过镜像部署到单独的容器上,如果想要满足负载均衡和高可用的需求,就需要将该镜像部署到多台服务器去了,如果使用了如swarm、kubernetes、mesos这样的容器编排工具,只要通过命令替换一下镜像名称即可,前提是没有其他特殊配置的情况;但是如果要将容器部署到多个虚拟主机上并通过如haproxy/nginx之类的负载均衡软件进行代理,很显然这种方法就不适用了;
|
||||
|
||||
其次,如果项目数量庞大,在jenkins中显然不能每个项目都要使用publish over ssh插件配置,如果中途要修改某些项目的某些配置,还需要一个项目一个项目的去修改,这样就和普通的人肉运维没有什么区别,浪费时间的同时还降低了工作效率;
|
||||
|
||||
再者,如果多个项目同时在一台jenkins主机上构建的话,会加大jenkins负载,严重时可能会导致系统或者jenkins服务崩溃,影响所有项目的构建部署操作。
|
||||
|
||||
首先使用ansible将在Jenkins基础基础实践章节中”部署服务到容器“的部署流程先过一遍,如下:
|
||||
|
||||
[root@ansible ansible]# cat /tmp/dev-test.yaml
|
||||
---
|
||||
- hosts: 192.168.176.160
|
||||
remote_user: root
|
||||
gather_facts: no
|
||||
vars:
|
||||
jar_name: "{{ workspace }}/fw-base-nop/target/fw-base-nop.jar"
|
||||
remote_dest: /data/base/nop
|
||||
|
||||
tasks:
|
||||
- name: copy jar file
|
||||
copy: src={{ jar_name }} dest={{ remote_dest }}
|
||||
|
||||
- name: stop container
|
||||
shell: docker stop base-nop
|
||||
|
||||
- name: delete container
|
||||
shell: docker rm base-nop
|
||||
|
||||
- name: delete image
|
||||
shell: docker rmi base/nop
|
||||
|
||||
- name: build_image
|
||||
shell: "cd {{ remote_dest }} && docker build -t base/nop ."
|
||||
|
||||
- name: run container
|
||||
shell: docker run --network host -e TZ="Asia/Shanghai" -d -v /etc/localtime:/etc/localtime:ro -m 2G --cpu-shares 512 -v /data/logs/fw-base-nop:/data/logs/fw-base-nop --name base-nop base/nop
|
||||
|
||||
|
||||
在jenkins的命令行配置如下,将原来使用Send files or execute commands over ssh变成使用execute shell。
|
||||
|
||||
|
||||
|
||||
然后在执行,执行结果如下:
|
||||
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
上面示例使用ansible-playbook将在jenkins里执行的命令进行了重写,拉取代码和代码编译配置没有变。
|
||||
|
||||
该脚本加入了两个变量,其中workspace是从外部传入的变量,使用了job的路径;remote_dest用于指定远程服务器存放jar包和dockerfile的目录。
|
||||
|
||||
由上图可看到使用这个playbook执行成功了,但是该脚本的成功执行有如下前提:-
|
||||
必须保证远程服务器的remote_dest路径存在(本项目之前已经创建),对于新建项目还是要手动创建的。
|
||||
|
||||
Dockerfile也要事先存在于目标主机上,同样对于新项目还需要手工创建。
|
||||
|
||||
在执行的所有任务列表中,只要有一个任务失败,那么整个playbook部署就会失败并退出。例如,如果jenkins新添加的项目没有容器启动,没有构建的镜像;又或者老项目这些都存在,但是容器运行一段时间后异常退出,stop container任务又会报错,部署还是会失败,所以这个playbook基本是不成立的。
|
||||
|
||||
如果有新的jenkins项目,还需要在次修改playbook的内容,显然这也是频繁并且麻烦的操作。
|
||||
|
||||
所以,基于上面可能会出现的问题,对这个playbook进一步优化如下:
|
||||
|
||||
- hosts: "{{ target_host }}"
|
||||
remote_user: root
|
||||
gather_facts: False
|
||||
|
||||
vars:
|
||||
jar_src: "{{ jar_file }}"
|
||||
dest_dict: "/data/{{ project_name }}/"
|
||||
|
||||
tasks:
|
||||
- name: 判断目录是否存在
|
||||
shell: ls {{ dest_dict }}
|
||||
register: dict_exist
|
||||
ignore_errors: true
|
||||
|
||||
- name: 创建相关目录
|
||||
file: dest="{{ item }}" state=directory mode=755
|
||||
with_items:
|
||||
- "{{ dest_dict }}"
|
||||
- /data/logs/{{ project_name }}
|
||||
when: dict_exist is failure
|
||||
|
||||
- name: 拷贝jar包和dockerfile到目标主机
|
||||
copy: src={{ item }} dest={{ dest_dict }}/
|
||||
with_items:
|
||||
- '{{ jar_file }}'
|
||||
- '/etc/ansible/Dockerfile'
|
||||
|
||||
- name: 查看容器是否存在
|
||||
shell: "docker ps -a --filter name={{ project_name }} |grep -v COMMAND"
|
||||
ignore_errors: true
|
||||
register: container_exists
|
||||
|
||||
- name: 查看容器状态
|
||||
shell: "docker ps -a --filter name={{ project_name }} --format '{{ '{{' }} .Status {{ '}}' }}'"
|
||||
ignore_errors: true
|
||||
register: container_state
|
||||
when: container_exists.rc == 0
|
||||
|
||||
- name: 关闭容器
|
||||
shell: "docker stop {{ project_name }}"
|
||||
when: "('Up' in container_state.stdout)"
|
||||
ignore_errors: true
|
||||
|
||||
- name: 删除容器
|
||||
shell: "docker rm {{ project_name }}"
|
||||
when: container_exists.rc == 0
|
||||
ignore_errors: true
|
||||
|
||||
- name: 查看镜像是否存在
|
||||
command: "docker images --filter reference={{ project_name }}* --format '{{ '{{' }} .Repository {{ '}}' }}:{{ '{{' }}.Tag {{ '}}' }}'"
|
||||
register: image_exists
|
||||
ignore_errors: true
|
||||
|
||||
- name: 删除镜像
|
||||
shell: "docker rmi -f {{ item }}"
|
||||
loop: "{{ image_exists.stdout_lines }}"
|
||||
ignore_errors: true
|
||||
when: image_exists.rc == 0
|
||||
|
||||
- name: 镜像构建
|
||||
shell: "cd {{ dest_dict }} && docker build -t {{ image_name }} --build-arg project_name={{ project_name }} ."
|
||||
|
||||
- name: 启动容器
|
||||
shell: 'docker run {{ run_option }} --name {{ project_name }} {{ image_name }}'
|
||||
|
||||
|
||||
说明
|
||||
|
||||
hosts 指定的主机需要在inventory清单文件中存在,这里可以写一个主机ip,也可以写一个主机组名,同样也可以通过变量的形式传入。
|
||||
|
||||
上面 playbook里vars参数下定义的两个变量,分别代表jar包所在路径、要拷贝jar包和Dockerfile到远程服务器的目录,为了区分每个项目,为每个项目根据项目名称创建目录。
|
||||
|
||||
任务”查看容器是否存在“定义了一个变量container_exists,如果该值为0时,说明通过上面命令执行时有输出(也就是容器存在,无论是处于启动还是关闭状态);为非0时,说明命令执行没有输出结果,放到此示例也就是没有容器存在。
|
||||
|
||||
任务”查看容器状态”和”查看镜像状态”使用的语法大致相同,有些不同的是,在ansible的playbook中执行时,默认会对”{{ }}“进行转义,所以这里需要对特殊符号进行处理,如上例所示,如果不明白可参考前面ansible基础章节的”转义”小节。
|
||||
|
||||
在”镜像构建“任务列表,根据项目名称传入参数进行构建,同时镜像名也是根据项目名称和git short id自定义的(看下面)。
|
||||
|
||||
Jenkins里执行时需要传入参数:
|
||||
|
||||
#!/bin/bash
|
||||
|
||||
#获取git commit的short id
|
||||
git_id=`git rev-parse --short HEAD`
|
||||
|
||||
#定义项目名称
|
||||
project_name=fw-base-nop
|
||||
|
||||
#定义image
|
||||
image_name="${project_name}:${git_id}"
|
||||
|
||||
#查找jar包路径
|
||||
cd ${WORKSPACE}/ && jar_file=`find "$(pwd)" ./ -name ${project_name}.jar |head -1`
|
||||
|
||||
#定义容器运行时的参数
|
||||
container_run_option="--network host -e TZ="Asia/Shanghai" -d -v /etc/localtime:/etc/localtime:ro -m 2G --cpu-shares 512 -v /data/logs/${project_name}:/data/logs/${project_name}"
|
||||
|
||||
#执行playbook,传入参数
|
||||
ansible-playbook -i /etc/ansible/hosts /root/dev-deploy.yaml -e "{'jar_file':${jar_file},'project_name':'${project_name}','image_name':'${image_name}' ,'run_option':'$container_run_option','target_host':'192.168.176.160'}"
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
每次新添加项目时只要修改项目名称即可,如果对容器运行时的参数有要求的话也可以修改一下。
|
||||
|
||||
由于我的项目名称和maven打包生成的jar包名称相同,所以这里没有在定义jar包名称,可根据自己需要自定义。
|
||||
|
||||
上面的”定义image”、”查找jar包路径”、”定义容器运行时参数” 等变量设置可以全部放在playbook中去。
|
||||
|
||||
对于目标主机的定义,此脚本使用变量的方式传入了一个ip,也可以传递主机组名称,这样就可以在多台主机上部署服务了。
|
||||
|
||||
该文目的是为了演示一下实现持续交付和持续部署的流程,所以playbook的实现用了很多shell模块,对于容器操作任务列表也可以通过docker_containers参数去实现,有兴趣的可以去试一下,我这里就不给大家演示了。
|
||||
|
||||
由于构建镜像时使用了变量,所以dockerfile也需要重构一下。
|
||||
|
||||
FROM fabric8/java-alpine-openjdk8-jre
|
||||
ARG project_name
|
||||
ENV JAVA_APP_JAR ${project_name}.jar
|
||||
|
||||
COPY $JAVA_APP_JAR /deployments/
|
||||
CMD java -jar /deployments/$JAVA_APP_JAR
|
||||
|
||||
|
||||
使用优化后的playbook基本上可以满足微服务下服务部署的需求了,但依然存在不足之处。
|
||||
|
||||
对于使用不同镜像的服务没有对dockerfile做处理,可以通过设置变量传参的方式实现。
|
||||
|
||||
对于项目名称与jar包名称不同的服务没有做预处理,同样添加一个变量也能解决此问题。
|
||||
|
||||
如果不想将这些变量的定义放到”Exec shell“输入框中去,也可以通过”参数化构建过程“选项去定义这些变量的值,或者将一些可以自动获取值的参数放到playbook文件中,只保留需要手动输入值的参数。
|
||||
|
||||
解决了最初提到的两个问题,下面来看第三个问题:关于jenkins的构建时的编译与镜像打包等操作。如果在多个项目同时构建的情况下,难免遇到Jenkins卡死或者服务崩溃的情况(笔者曾经遇到过),这是就需要通过添加slave节点的方式来解决此问题。
|
||||
|
||||
jenkins的slave节点可以部署在虚拟机上也可以部署在容器中,也可以通过配置动态生成。在前面的文章中有介绍如何添加与使用slave节点,比较简单,这里就不重复介绍了,同时需要注意的是在slave节点安装相应的工具。
|
||||
|
||||
到这里本节要介绍的内容就结束了。回顾本节的内容,演示了如何将服务部署到vm的tomcat服务以及docker容器中去,至于部署到容器编排系统中,将在课程末尾介绍。
|
||||
|
||||
|
||||
|
||||
|
1058
专栏/Jenkins持续交付和持续部署/09.实现自动化引擎之JenkinsPipeline声明式语法.md
Normal file
1058
专栏/Jenkins持续交付和持续部署/09.实现自动化引擎之JenkinsPipeline声明式语法.md
Normal file
File diff suppressed because it is too large
Load Diff
684
专栏/Jenkins持续交付和持续部署/10.实现自动化引擎之JenkinsPipeline脚本式语法.md
Normal file
684
专栏/Jenkins持续交付和持续部署/10.实现自动化引擎之JenkinsPipeline脚本式语法.md
Normal file
@@ -0,0 +1,684 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
10.实现自动化引擎之Jenkins Pipeline脚本式语法
|
||||
前面章节介绍了pipeline的声明式语法, 本节通过对比来介绍一下脚本式语法的使用。虽然两种类型的语法都是建立在底层流水线的子系统上的语法,但是脚本式流水线在使用一些常用插件的时候显得更加容易,脚本式流水线只有stage指令,在声明式流水线中的stages和steps关键字在脚本式流水线中是不能使用的。下面针对在声明式流水线中介绍到的部分关键字介绍一下在脚本式流水线中的实现方法。
|
||||
|
||||
node
|
||||
|
||||
声明式流水线中,使用agent关键字指定执行Jenkins流水线的代理,在脚本式流水线中通过node关键字来指定代理节点。
|
||||
|
||||
基本示例如下
|
||||
|
||||
node('jenkins-slave1') {
|
||||
stage('test1') {
|
||||
sh 'hostname'
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
其中jenkins-slave为jenkins代理节点的名称或者标签名称,如果”()“为空,则默认会从所有的节点中选择一个slave节点
|
||||
|
||||
除了可以为所有stage提供一个全局的代理节点以外,也可以为不同的stage提供不同的代理节点。
|
||||
|
||||
如下示例
|
||||
|
||||
node() {
|
||||
stage('test-node'){
|
||||
node('jenkins-slave1'){
|
||||
stage('test1'){
|
||||
sh 'hostname'
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('test-node2'){
|
||||
node('jenkins-slave169'){
|
||||
stage('test2'){
|
||||
sh 'hostname'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
该示例为不同的stage步骤使用了不同的slave节点
|
||||
|
||||
使用node也可以用容器作为agent,比如
|
||||
|
||||
node() {
|
||||
docker.image('maven').inside {
|
||||
stage('te') {
|
||||
sh "hostname"
|
||||
}
|
||||
}
|
||||
docker.image('nginx').inside {
|
||||
stage('te1') {
|
||||
sh "hostname"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
该配置针对不同的流水线stage使用不同的镜像启动容器提供脚本执行的环境,docker.image().inside()是docker pipeline插件里的一个指定镜像并启动容器的方法,在以后的章节会介绍,这里只需要知道可以这么操作即可
|
||||
|
||||
基于上面示例的思考,是不是可以在不同的节点上分别启动容器来进行不同的工作呢?答案是可以的,如下示例
|
||||
|
||||
node() {
|
||||
stage('test1'){
|
||||
node('slave1'){
|
||||
docker.image('maven').inside {
|
||||
stage('te') {
|
||||
sh "hostname"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('test2'){
|
||||
node('slave2'){
|
||||
docker.image('nginx').inside {
|
||||
stage('te1') {
|
||||
sh "hostname"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
该示例会在两个slave节点上分别启动容器作为流水线的执行环境。既然使用脚本式语法能实现在不同的slave节点启动不同的容器,在声明式语法中也能做到,在以后的章节中会有说明
|
||||
|
||||
tool
|
||||
|
||||
声明式流水线中使用tools定义在jenkins系统中设置的工具的环境变量,在脚本式流水线中使用def 和tool关键字来使用这些工具,例如
|
||||
|
||||
node{
|
||||
def maven = tool name: 'maven-3.5.4'
|
||||
env.PATH = "${maven}/bin:${env.PATH}"
|
||||
stage('test'){
|
||||
sh 'mvn --version'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
该指令的语法片段也可以通过片段生成器生成。
|
||||
|
||||
withEnv
|
||||
|
||||
在声明式pipeline中通过environment来定义流水线中用到的环境变量,而在脚本式pipeline中使用withEnv来定义全局或者局部环境变量。
|
||||
|
||||
比如定义maven的环境变量
|
||||
|
||||
node() {
|
||||
withEnv(["PATH+MAVEN=${tool 'maven-3.5.4'}/bin"]) {
|
||||
sh 'mvn --version'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
也可以写成这样
|
||||
|
||||
def maven = 'maven-3.5.4'
|
||||
withEnv(["PATH+MAVEN=${tool maven}/bin"]) {
|
||||
sh 'mvn --version'
|
||||
|
||||
}
|
||||
|
||||
|
||||
也可以直接指定实际的值
|
||||
|
||||
withEnv(['MYTOOL_HOME=/usr/local/']) {
|
||||
sh '$MYTOOL_HOME/bin/start'
|
||||
}
|
||||
|
||||
|
||||
parallel(并行)
|
||||
|
||||
parallel除了可以使用声明式语法定义外,也可以使用脚本式语法定义。
|
||||
|
||||
下面的例子可以放到脚本式语法中执行
|
||||
|
||||
node {
|
||||
stage('Test') {
|
||||
parallel slave1: {
|
||||
node('jenkins-slave1') {
|
||||
try {
|
||||
sh 'hostname'
|
||||
}
|
||||
finally {
|
||||
echo "test"
|
||||
}
|
||||
}
|
||||
},
|
||||
slave169: {
|
||||
node('jenkins-slave169') {
|
||||
sh 'hostname'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
同时在两个节点执行shell命令
|
||||
|
||||
properties
|
||||
|
||||
声明式脚本通过parameters来定义流水线中使用到的参数,在脚本式语法中则需要通过properties关键字来定义在stage中使用的参数,可以通过片段生成器生(参考下面)成相应的语法片段。
|
||||
|
||||
如下示例-
|
||||
|
||||
|
||||
生成的语法片段如下
|
||||
|
||||
properties([
|
||||
buildDiscarder(
|
||||
logRotator(artifactDaysToKeepStr: '',
|
||||
artifactNumToKeepStr: '',
|
||||
daysToKeepStr: '2',
|
||||
numToKeepStr: '4')
|
||||
),
|
||||
parameters([string(defaultValue: 'v1',
|
||||
description: '镜像tag',
|
||||
name: 'tag',
|
||||
trim: true)
|
||||
])
|
||||
])
|
||||
|
||||
|
||||
示例如下
|
||||
|
||||
properties([
|
||||
buildDiscarder(
|
||||
logRotator(artifactDaysToKeepStr: '',
|
||||
artifactNumToKeepStr: '',
|
||||
daysToKeepStr: '2',
|
||||
numToKeepStr: '4')
|
||||
),
|
||||
parameters([
|
||||
string(
|
||||
defaultValue: 'v1',
|
||||
description: '镜像tag',
|
||||
name: 'tag',
|
||||
trim: true
|
||||
)
|
||||
])
|
||||
])
|
||||
|
||||
node{
|
||||
stage('test1'){
|
||||
echo "${params.tag}"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
这里需要说明的是,properties语法片段既可以放到node{}里,也可以放到node{}上面
|
||||
|
||||
异常处理
|
||||
|
||||
在使用jenkins的时候难免遇到job执行失败的情况。声明式语法中可以通过post关键字对故障进行处理,而在脚本式流水线中则需要使用 try/catch/finally块来处理故障。当step失败 ,无论什么原因,它们都会抛出一个异常。
|
||||
|
||||
如下示例
|
||||
|
||||
node {
|
||||
stage('Example') {
|
||||
try {
|
||||
sh 'exit 1'
|
||||
currentBuild.result = 'SUCCESS'
|
||||
}
|
||||
catch (exc) {
|
||||
currentBuild.result = 'FAILURE'
|
||||
throw exc
|
||||
}finally{
|
||||
if(currentBuild.currentResult == "ABORTED" || currentBuild.currentResult == "FAILURE" || currentBuild.currentResult == "UNSTABLE"') {
|
||||
echo "---currentBuild.currentResult
|
||||
result is:${currentBuild.currentResult
|
||||
}"
|
||||
}
|
||||
else {
|
||||
echo "---currentBuild.currentResult
|
||||
result is:${currentBuild.currentResult
|
||||
}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
需要注意的是,try语句后面一定要有catch或者finally语句,catch与finally语句可以同时存在,也可以只存在一个。
|
||||
|
||||
Jenkinsfile
|
||||
|
||||
通过学习pipeline的两种语法,对于如何编写脚本式和声明式pipeline脚本应该有了基本的认识,实际工作中使用的jenkinsfile大多数是用声明式脚本编写的(当然脚本式也能用),至于在什么情况下使用哪种类型的语法,取决于个人对两种语法的熟悉程度。有些情况下,比如在后面章节要用到的docker插件,kubernetes插件,使用脚本式语法可以很方便的与这些插件集成,而如果使用声明式语法就会复杂一些,所以在准备使用pipeline之前有必要先做一下调研。
|
||||
|
||||
jenkinsfile脚本文件通常用于放到源码仓库中,在配置jenkins流水线项目时通过checkout script from SCM的方式拉取jenkinsfile和所需的文件,拉取下来以后会自动进行流水线操作。
|
||||
|
||||
比如在定义流水线脚本使用方式时使用Pipeline Script的方式配置
|
||||
|
||||
node {
|
||||
checkout scm
|
||||
}
|
||||
|
||||
|
||||
说明
|
||||
|
||||
|
||||
checkout scm这里应该为实际的拉取代码的指令,用于从代码仓库拉取代码。比如git clone等。
|
||||
此时放到Jenkinsfile中的流水线脚本就不需要使用node{}块包含了
|
||||
|
||||
|
||||
也可以通过Pipeline script from SCM的方式配置,如下所示-
|
||||
|
||||
|
||||
需要注意的是,脚本路径指定的文件名称为从git仓库获取的根目录下的文件名称。文件拉取到本地后就会自动执行流水线操作。
|
||||
|
||||
了解了Jenkinsfile文件如何使用后,下面介绍一下针对两种pipeline语法都能使用的一些指令和关键字。
|
||||
|
||||
变量
|
||||
|
||||
pipeline脚本支持使用变量。在pipeline中的变量分为多种形式,可以自定义变量,可以使用jenkins的环境变量,也可以将通过命令获取的结果动态设置为变量等。下面对这些变量一一进行介绍
|
||||
|
||||
自定义变量
|
||||
|
||||
Jenkins 使用与 Groovy 相同的规则进行变量赋值。Groovy 支持使用单引号或双引号声明一个字符串,例如:
|
||||
|
||||
def singlyQuoted = 'Hello'
|
||||
def doublyQuoted = "World"
|
||||
|
||||
|
||||
对于变量的引用,可以使用单引号和双引号。如果没有特殊字符,用单引号和双引号是一样的;如果要执行多行命令,用三个单引号或三个双引号。如果有特殊字符需要解释,用双引号,需要转义的情况,用\转义符。
|
||||
|
||||
在单引号之间的所有特殊字符都失去了特殊含义;而在双引号之间的绝大多数特殊字符都失去了特殊含义,除了以下特例:
|
||||
|
||||
|
||||
$ 美元号用来提取变量的值
|
||||
` 反冒号用于执行命令
|
||||
\ 反斜杠用来转义字符
|
||||
|
||||
|
||||
比如:
|
||||
|
||||
def username = 'Jenkins'
|
||||
echo 'Hello Mr. ${username}'
|
||||
echo "I said, Hello Mr. ${username}"
|
||||
|
||||
|
||||
其结果是:
|
||||
|
||||
Hello Mr. ${username}
|
||||
I said, Hello Mr. Jenkins
|
||||
|
||||
|
||||
执行命令示例如下:
|
||||
|
||||
sh '''
|
||||
whoami
|
||||
pwd
|
||||
ls -ltra
|
||||
'''
|
||||
|
||||
|
||||
结果
|
||||
|
||||
+ whoami
|
||||
root
|
||||
+ pwd
|
||||
/var/lib/jenkins/workspace/test-mytest
|
||||
+ ls -ltra
|
||||
total 4
|
||||
drwxr-xr-x 2 root root 6 Mar 16 17:02 .
|
||||
drwxr-xr-x 8 root root 4096 Mar 16 17:02 ..
|
||||
|
||||
|
||||
而对于定义多个变量如果每个变量都都使用def指令定义,那酒显得有些重复造轮子了。groovy官方也给出了解决办法,对于上面的示例,可以写成这样:
|
||||
|
||||
def cc=[username='jenkins',version="v2.19.0"]
|
||||
|
||||
|
||||
引用时也简单
|
||||
|
||||
echo "$cc.usernmae $cc.version"
|
||||
|
||||
|
||||
如果要在脚本式语法中使用通过def命令定义的变量,需要使用script{}块将变量定义的步骤包含起来。
|
||||
|
||||
环境变量
|
||||
|
||||
在 Jenkins 流水线中,可以定义整个流水线使用的环境变量,也可以使用jenkins自带的环境变量。
|
||||
|
||||
对于如何自定义环境变量,取决于使用的是声明式还是脚本式流水线语法,它们设置环境变量的方法不同。关于在声明式和脚本式语法中使用环境变量的方法在前面已经介绍过,这里不再多说,下面看一下使用jenkins内置的环境变量。
|
||||
|
||||
Jenkins 流水线通过全局变量 env 提供环境变量,它在 Jenkinsfile 文件的任何地方都可以使用。Jenkins 流水线中可访问的完整的环境变量列表记录在 ${YOUR_JENKINS_URL}/pipeline-syntax/globals#env
|
||||
|
||||
比如:
|
||||
|
||||
|
||||
BUILD_ID-
|
||||
当前构建的 ID,与早期jenkins版本的 BUILD_NUMBER 是完全相同的。
|
||||
|
||||
BUILD_NUMBER-
|
||||
当前构建号,比如 “153”。
|
||||
|
||||
BUILD_TAG-
|
||||
字符串 jenkins-${JOB_NAME}-${BUILD_NUMBER}。可以放到源代码、jar 等文件中便于识别。
|
||||
|
||||
BUILD_URL-
|
||||
可以定位此次构建结果的 URL(比如 http://buildserver/jenkins/job/MyJobName/17/ )
|
||||
|
||||
EXECUTOR_NUMBER-
|
||||
用于识别执行当前构建的执行者的唯一编号(在同一台机器的所有执行者中)。这个就是你在“构建执行状态”中看到的编号,只不过编号从 0 开始,而不是 1。
|
||||
|
||||
JAVA_HOME-
|
||||
如果你的任务配置了特定的 JDK工具,那么这个变量就被设置为此 JDK 的 JAVA_HOME。当设置了此变量时,PATH 也将包括 JAVA_HOME 的 bin 子目录。
|
||||
|
||||
JENKINS_URL-
|
||||
Jenkins 服务器的完整 URL,比如 https://example.com:port/jenkins/ (注意:只有在“系统设置”中设置了 Jenkins URL 才可用)。
|
||||
|
||||
JOB_NAME-
|
||||
本次构建的项目名称
|
||||
|
||||
NODE_NAME-
|
||||
运行本次构建的节点名称。对于 master 节点则为 “master”。
|
||||
|
||||
WORKSPACE-
|
||||
workspace 的绝对路径。也是job的路径
|
||||
|
||||
……
|
||||
|
||||
|
||||
更多变量内容参考pipeline-sytanx即可
|
||||
|
||||
示例如下
|
||||
|
||||
pipeline {
|
||||
agent any
|
||||
stages {
|
||||
stage('Example') {
|
||||
steps {
|
||||
echo "Running ${env.BUILD_ID} on ${env.JENKINS_URL}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
动态设置变量
|
||||
|
||||
环境变量可以在运行时设置,可以通过shell命令(linux sh)、Windows 批处理脚本(bat)和 Powershell 脚本(powershell)获取变量的值并提供给下面的各stage阶段使用。各种脚本都可以返回 returnStatus 或 returnStdout。
|
||||
|
||||
下面是一个使用 sh(shell)的声明式脚本的例子,既有 returnStatus 也有 returnStdout:
|
||||
|
||||
pipeline {
|
||||
agent any
|
||||
environment {
|
||||
// 使用 returnStdout
|
||||
CC = """
|
||||
${sh(
|
||||
returnStdout: true,
|
||||
script: 'echo "clang"'
|
||||
)}
|
||||
"""
|
||||
|
||||
// 使用 returnStatus
|
||||
EXIT_STATUS = """
|
||||
${sh(
|
||||
returnStatus: true,
|
||||
script: 'exit 1'
|
||||
)}
|
||||
"""
|
||||
}
|
||||
stages {
|
||||
stage('Example') {
|
||||
environment {
|
||||
DEBUG_FLAGS = '-g'
|
||||
}
|
||||
steps {
|
||||
sh 'printenv'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
|
||||
使用 returnStdout 时,返回的字符串末尾会追加一个空格。可以使用 .trim() 将其移除。
|
||||
该指令可以通过片段生成器生成语法片段
|
||||
|
||||
|
||||
相对于脚本式语法就简单的多
|
||||
|
||||
node {
|
||||
stage('s'){
|
||||
script {
|
||||
cc=sh(returnStdout: true, script: 'hostname').trim()
|
||||
}
|
||||
echo "$cc"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
条件判断
|
||||
|
||||
Jenkinsfile 从顶部开始向下串行执行,执行过程中难免遇到使用条件判断的情况,声明式语法中可以通过when关键字做一些基础的判断,但是在脚本式语法中无法使用when关键字。而使用if/else语句在两种pipeline语法中都可以使用,并且提高条件判断的灵活性。
|
||||
|
||||
在pipeline中使用的if/else语句同样遵循Groovy语法,如下示例
|
||||
|
||||
node {
|
||||
stage('Example') {
|
||||
if (env.BRANCH_NAME == 'master') {
|
||||
echo 'I only execute on the master branch'
|
||||
} else {
|
||||
echo 'I execute elsewhere'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
或者如下示例
|
||||
|
||||
script{
|
||||
test_result=sh(script:"ls /tmp/uu.txt",returnStatus:true)
|
||||
echo test_result
|
||||
if(test_result == 0){
|
||||
echo "file is exist"
|
||||
}else if(test_resultt == 2){
|
||||
echo "file is not exist"
|
||||
}else{
|
||||
error("command is error,please check")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fileExists
|
||||
|
||||
该关键字用来判断当前工作空间下指定的文件是否存在,文件的路径设定时使用基于当前工作空间的相对路径,返回的结果为布尔值。该关键字的使用语法可以通过片段生成器来生成。
|
||||
|
||||
如下示例
|
||||
|
||||
script {
|
||||
json_file = "${env.WORKSPACE}/testdata/test_json.json"
|
||||
if(fileExists(json_file) == true) {
|
||||
echo("json file is exists")
|
||||
}else {
|
||||
error("here haven't find json file")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
|
||||
该示例用于判断json_file变量指定的文件是否存在
|
||||
error指令用于定义并返回自定义的错误信息
|
||||
|
||||
|
||||
dir
|
||||
|
||||
dir()方法用于改变当前的工作目录,在dir语句块填写要进入的目录路径即可。
|
||||
|
||||
示例如下
|
||||
|
||||
stages{
|
||||
stage("dir") {
|
||||
steps{
|
||||
echo env.WORKSPACE
|
||||
dir("${env.WORKSPACE}/fw-base-nop"){
|
||||
sh "pwd"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
deletedir
|
||||
|
||||
deleteDir()方法默认递归删除WORKSPACE下的文件和文件夹,没有参数,通常它与dir步骤一起使用,用于删除指定目录下的内容。
|
||||
|
||||
示例如下
|
||||
|
||||
stage("deleteDir") {
|
||||
steps{
|
||||
script{
|
||||
sh("ls -al ${env.WORKSPACE}")
|
||||
deleteDir() // clean up current work directory
|
||||
sh("ls -al ${env.WORKSPACE}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
或者删除指定目录
|
||||
|
||||
node {
|
||||
stage('s'){
|
||||
dir('/base'){
|
||||
deleteDir()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
script
|
||||
|
||||
script 步骤需要script-pipeline块并在流水线中执行。 对于大多数用例来说,声明式流水线中的script步骤是不必要的,该关键字大多数情况下用来执行命令。
|
||||
|
||||
示例如下
|
||||
|
||||
pipeline {
|
||||
agent any
|
||||
stages {
|
||||
stage('Example') {
|
||||
steps {
|
||||
echo 'Hello World'
|
||||
|
||||
script {
|
||||
def browsers = ['chrome', 'firefox']
|
||||
for (int i = 0; i < browsers.size(); ++i) {
|
||||
echo "Testing the ${browsers[i]} browser"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
其中,通过def定义的变量可以在下面的stage的中被引用
|
||||
|
||||
stash/unstash
|
||||
|
||||
stash用于将文件保存起来,以便同一次构建的其他stage或step使用。 如果整个流水线在同一台机器上执行,那stash是多余的。stash一般用于跨Jenkins node使用, stash步骤会将文件存储在tar文件中,对于大文件的stash操作将会消耗Jenkins master的计算资源。Jenkins官方文档推荐,当文件大小为5∼100MB时,应该考虑使用其他替代方案。
|
||||
|
||||
stash 指令的参数如下:
|
||||
|
||||
|
||||
name:字符串类型,保存文件的集合的唯一标识
|
||||
allowEmpty:布尔类型,允许stash内容为空
|
||||
excludes:字符串类型,排除文件,如果排除多个使用「逗号」分隔
|
||||
includes:字符串类型,stash文件,留空表示全部
|
||||
useDefaultExcludes:布尔类型,true:使用Ant风格路径默认排除文件 Ant风格路径表达式
|
||||
|
||||
|
||||
除了name参数,其他参数都是可选的。excludes和includes使用的是Ant风格路径表达式。
|
||||
|
||||
与stash对应的是unstash。该指令用于取出之前stash的文件。-
|
||||
unstash步骤只有一个name参数,即stash时的唯一标识。通常情况下,stash与unstash步骤同时使用。示例如下
|
||||
|
||||
pipeline {
|
||||
agent none
|
||||
stages {
|
||||
stage('stash') {
|
||||
agent { label "master" }
|
||||
steps {
|
||||
dir('target') {
|
||||
stash(name: "abc", include: "xx.jar")
|
||||
}
|
||||
}
|
||||
}
|
||||
stage("unstash") {
|
||||
agent { label "jenkins-slave1" }
|
||||
steps {
|
||||
script {
|
||||
unstash("abc")
|
||||
cp xx.jar /data
|
||||
...
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
unstash根据stash指定的name的值获取文件,等流水线执行完毕后,文件会删除
|
||||
|
||||
archiveArtifacts
|
||||
|
||||
jenkins流水线的archiveArtifacts指令也是用来保存文档文件,与stash不同的是,该指令会将文件保存到本地,流水线执行完成后不会被销毁。保存的文件会放到到 Jenkins 的 jobs/JOB_NAME/builds/BUILD_NO目录下
|
||||
|
||||
示例如下
|
||||
|
||||
pipeline {
|
||||
agent any
|
||||
stages {
|
||||
stage('Archive') {
|
||||
steps {
|
||||
archiveArtifacts artifacts: '**/target/*.jar', onlyIfSuccessful: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
archiveArtifacts指令包含多个参数,其中artifacts参数为必须参数,onlyIfSuccessful参数表示只有成功时保存文件。其他参数使用可以参考官方文档
|
||||
|
||||
片段生成器
|
||||
|
||||
jenkins内置的Snippet Generator工具(片段生成器)用于将插件或者关键字的使用语法动态的生成steps语法片段,添加到流水线中。片段生成器界面可以从pipeline项目中最底部的pipeline syntax超链接中进入,也可以直接通过${YOUR_JENKINS_URL}/pipeline-syntax访问。
|
||||
|
||||
Jenkins对于片段生成器分成了两种类别,一种是基于pipeline声明式语法的片段生成器(Declarative Directive Generator菜单),另一种是基于jenkins内置插件以及脚本式语法使用(片段生成器菜单)的片段生成器。下面分别对这两种生成器进行介绍
|
||||
|
||||
基于声明式语法的片段生成器,用于在不知道关键字语法的情况下生成语法片段。同样是访问上面的连接,点击Declarative Directive Generator菜单,如下所示-
|
||||
|
||||
|
||||
可以看到,在下拉框中显示了这些指令的相关语法,选中任意指令配置好后点击下面的Generate Declarative Directive即可生成相应的语法片段
|
||||
|
||||
而对于声明式脚本以及内置插件使用的片段生成器,直接点击”片段生成器“菜单即可,点击”Sample Setp“会列出所有支持的插件列表,选中一个,比如下图所示:
|
||||
|
||||
|
||||
|
||||
根据需要配置好必须项,点击下方的Generate Pipeline Script就会生成相应的代码片段
|
||||
|
||||
|
||||
|
||||
然后就可以复制粘贴放到pipeline项目中的脚本中去了。
|
||||
|
||||
有关Jenkins脚本式语法的内容就介绍到这里,在下一节中将使用实践案例在对本节以及上节学习的内容进行实践,以加深理解
|
||||
|
||||
|
||||
|
||||
|
948
专栏/Jenkins持续交付和持续部署/11.Pipeline语法进行持续交付与基础实践.md
Normal file
948
专栏/Jenkins持续交付和持续部署/11.Pipeline语法进行持续交付与基础实践.md
Normal file
@@ -0,0 +1,948 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
11.Pipeline语法进行持续交付与基础实践
|
||||
在前两节介绍了使用声明式流水线和脚本式流水线的基本语法,本节通过一些基础实践来加深一下对前两节内容的理解。由于每个人的实际情况和理解程度不一样, 笔者会将要学习的内容从最基础开始演示,采用不断优化代码的方式尽量将在之前pipeline章节介绍的语法通过示例都展示出来。
|
||||
|
||||
使用声明式语法进行持续交付与基础实践
|
||||
|
||||
Agent代理
|
||||
|
||||
有些公司在技术上可能没有达到使用容器作为流水线agent代理的条件,但这并不影响使用pipeline脚本在虚拟机上编译代码构建镜像。所以节内容中使用的agent代理均为虚拟机,至于如何使用容器作为agent代理,将在以后的章节介绍。
|
||||
|
||||
配置agent代理没有什么要特别说明的,直接通过agent{}指令配置要使用的代理节点的label或者名称即可.
|
||||
|
||||
以一个基础示例开始本节的内容,比如使用master主机执行shell命令
|
||||
|
||||
pipeline{
|
||||
agent { node { label 'master'} }
|
||||
stages{
|
||||
stage('test'){
|
||||
steps{
|
||||
sh "hostname"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
需要注意的是,在编写声明式脚本时,在stage()中必须加上对该stage的描述,虽然是自定义的内容,但是不添加也会报错
|
||||
|
||||
本次pipeline脚本的全部操作在jenkins-slave1节点进行,所以对于agent定义如下
|
||||
|
||||
agent { node { label 'jenkins-slave1' } }
|
||||
|
||||
|
||||
使用agent指令还是比较简单的
|
||||
|
||||
代码拉取与编译
|
||||
|
||||
下面创建一个流水线类型的Jenkins Job,然后编写pipeline脚本如下
|
||||
|
||||
pipeline {
|
||||
agent { node { label 'jenkins-slave1' } }
|
||||
stages {
|
||||
stage('代码拉取并编译'){
|
||||
steps {
|
||||
sh "git clone http://root:[email protected]/root/base-nop.git"
|
||||
echo "开始编译"
|
||||
sh ' source /etc/profile && cd fw-base-nop && mvn clean install -DskipTests -Denv=beta'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
使用最基础的代码拉取并编译操作只用了两行命令并且拉取代码操作还使用了明文的用户名密码,这显然是不能符合要求的。
|
||||
|
||||
使用在”pipeline语法“章节介绍的片段生成器,可以将代码拉取操作换成语法片段(前面有过介绍)
|
||||
|
||||
如下所示
|
||||
|
||||
checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: 'c33d60bd-67c6-4182-b52c-d7aeebfab772', url: 'http://192.168.176.154/root/base-nop.git']]])
|
||||
|
||||
|
||||
同样对于在使用mvn命令编译代码时,如果遇到maven命令找不到或者jdk环境变量找不到的情况,除了使用shell命令刷新环境变量的方式,也可以用tools指令定义这些工具的环境变量。
|
||||
|
||||
比如:
|
||||
|
||||
tools {
|
||||
maven 'maven-3.5.4'
|
||||
jdk 'jdk-1.8'
|
||||
}
|
||||
|
||||
|
||||
对于代码编译,如果不想使用cd命令进入指定的目录进行编译,pipeline提供了ws指令用于指定目录进行操作,所以上面的编译代码步骤也可以写成如下示例:
|
||||
|
||||
ws('directory'){
|
||||
mvn clean install
|
||||
}
|
||||
|
||||
|
||||
那么上面最初的脚本就可以改成这样
|
||||
|
||||
pipeline {
|
||||
agent { node { label 'jenkins-slave1' } }
|
||||
tools {
|
||||
maven 'maven-3.5.4'
|
||||
jdk 'jdk-1.8'
|
||||
}
|
||||
stages {
|
||||
stage('代码拉取并编译'){
|
||||
steps {
|
||||
checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: 'c33d60bd-67c6-4182-b52c-d7aeebfab772', url: 'http://192.168.176.154/root/base-nop.git']]])
|
||||
|
||||
echo "开始打包 "
|
||||
|
||||
ws("$WORKSPACE/fw-base-nop"){
|
||||
sh ' mvn clean install -DskipTests -Denv=beta'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
|
||||
cd命令的基础路径是当前工作空间(${WORKSPACE})路径
|
||||
ws 指令里使用的目录需要是绝对路径
|
||||
如果想要在pipeline中测试本地主机或者本地路径是哪个,可通过执行hostname命令或者pwd命令获取
|
||||
|
||||
|
||||
使用sonar
|
||||
|
||||
既然是持续交付,那么肯定少不了代码质量分析。上面的示例只是将代码进行了编译,正常流程还需要进行代码质量分析,这就用到了之前搭建的sonarqube平台。在前面《Jenkins插件安装》章节有介绍如何使用代码质量分析工具sonarQube。这里不重复说明,接下来主要看一下在流水线脚本中使用soncarQube以及sonar-scanner。
|
||||
|
||||
本章节我们使用的节点全是基于vm的机器,所以此次介绍的sonar-scanner工具的使用只是基于在Jenkins UI中配置的sonar-scanner命令工具。
|
||||
|
||||
在前面的章节有介绍sonar-scanner的命令以及参数,在pipeline中使用时,我们只需要根据片段生成生成sonar-scanner的语法片段即可。如下所示
|
||||
|
||||
|
||||
|
||||
选择好凭据以后生成语法片段,然后将在之前章节中使用的sonar-scanner命令以及参数粘贴到withSonarQubeEnv(){}代码块里即可,如下所示:
|
||||
|
||||
stage('sonar'){
|
||||
steps{
|
||||
script{
|
||||
def sonarqubeScannerHome = tool name: 'sonar-scanner-4.2.0'
|
||||
withSonarQubeEnv(credentialsId: 'sonarqube') {
|
||||
sh "${sonarqubeScannerHome}/bin/sonar-scanner -X "+
|
||||
"-Dsonar.login=admin " +
|
||||
"-Dsonar.language=java " +
|
||||
"-Dsonar.projectKey=${JOB_NAME} " +
|
||||
"-Dsonar.projectName=${JOB_NAME} " +
|
||||
"-Dsonar.projectVersion=${BUILD_NUMBER} " +
|
||||
"-Dsonar.sources=${WORKSPACE}/fw-base-nop " +
|
||||
"-Dsonar.sourceEncoding=UTF-8 " +
|
||||
"-Dsonar.java.binaries=${WORKSPACE}/fw-base-nop/target/classes " +
|
||||
"-Dsonar.password=admin "
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
这样就配置好了在pipeline中使用sonarqube与sonar-scanner。在执行完代码编译步骤后就会自动执行sonar-scanner命令。
|
||||
|
||||
构建并推送镜像
|
||||
|
||||
基础版
|
||||
|
||||
上面示例使用pipeline脚本就完成了代码的编译工作,下面看一下将代码构建成镜像并推送到私有仓库的操作,还是先看一下基础的代码。
|
||||
|
||||
stage('构建镜像'){
|
||||
steps {
|
||||
sh "cp $WORKSPACE/fw-base-nop/target/fw-base-nop.jar /data/fw-base-nop/"
|
||||
sh "cd /data/fw-base-nop && docker build -t fw-base-nop:${BUILD_ID} ."
|
||||
}
|
||||
}
|
||||
|
||||
stage('上传镜像'){
|
||||
steps {
|
||||
sh "docker tag fw-base-nop:${BUILD_ID} 192.168.176.155/library/fw-base-nop:${BUILD_ID}"
|
||||
sh "docker login 192.168.176.155 -u admin -p da88e43d88722c2c9ca09da644eeb015"
|
||||
sh "docker push 192.168.176.155/library/fw-base-nop:${BUILD_ID}"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
该基础代码虽然可以实现我们的目的,但是对于有想法的年轻人,对于示例中的纯命令式操作还是不能接受的,对于部分步骤还是能够优化的。比如:
|
||||
|
||||
构建镜像阶段:
|
||||
|
||||
1、对于构建产物(jar包)的引用使用了cp命令拷贝到agent代理节点上指定的目录,如果遇到添加其他项目时要使用这个pipeline的模板,还要去频繁修改jar包路径和名称,所以这里还需要简单的优化一下。
|
||||
|
||||
2、对于构建镜像的命令也可以直接通过命令docker build -t fw-base-nop:${BUILD_ID} /data/fw-base-nop实现。
|
||||
|
||||
3、需要在指定的目录(此示例为/data/fw-base-nop/),预先存放Dockerfile文件(可参考第四章节中的Dockerfile)。
|
||||
|
||||
上传镜像阶段:
|
||||
|
||||
1、需要先将上一步骤构建的镜像添加tag以后,才能上传到指定私有仓库。在构建镜像时,我们可以直接将镜像构建成想要通过tag命令标记的镜像名。
|
||||
|
||||
2、上传到私有仓库之前还需要对私有仓库进行认证,简单的几个步骤就使用了如上示例这么多命令。
|
||||
|
||||
针对上面列出的问题,部分步骤可以通过使用插件或指令进行优化:
|
||||
|
||||
比如,使用find命令从当前路径中查找构建的产物,并且将镜像直接构建成名称为Docker Registry地址/仓库名:标签的格式,可以修改成如下:
|
||||
|
||||
stage('构建镜像'){
|
||||
steps {
|
||||
script{
|
||||
jar_file=sh(returnStdout: true, script: "find ${WORKSPACE} ./ -name fw-base-nop.jar |head -1").trim()
|
||||
}
|
||||
sh """
|
||||
cp $jar_file /data/fw-base-nop/
|
||||
docker build -t 192.168.176.155/library/fw-base-nop:${BUILD_ID} /data/fw-base-nop/.
|
||||
"""
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
使用withCredentials方法对私有仓库进行认证
|
||||
|
||||
stage('上传镜像'){
|
||||
steps {
|
||||
withCredentials([usernamePassword(credentialsId: 'auth_harbor', passwordVariable: 'dockerHubPassword', usernameVariable: 'dockerHubUser')]) {
|
||||
sh "“”
|
||||
docker login -u ${env.dockerHubUser} -p ${env.dockerHubPassword} 192.168.176.155
|
||||
docker push 192.168.176.155/library/fw-base-nop:${BUILD_ID}
|
||||
"""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
因为是在虚拟机主机上进行镜像构建操作,为了避免job执行次数过多导致构建的镜像在宿主机保存数量过多的情况,还需要在最后阶段执行删除镜像的操作,比如:
|
||||
|
||||
stage('删除本地镜像'){
|
||||
steps{
|
||||
sh "docker rmi -f 192.168.176.155/library/fw-base-nop:${BUILD_ID}"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
根据上面的更改,完整的pipeline脚本如下:
|
||||
|
||||
pipeline {
|
||||
agent { node { label 'jenkins-slave1'}}
|
||||
tools {
|
||||
maven 'maven-3.5.4'
|
||||
jdk 'jdk-1.8'
|
||||
}
|
||||
stages {
|
||||
stage('代码拉取并编译'){
|
||||
steps {
|
||||
checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: 'c33d60bd-67c6-4182-b52c-d7aeebfab772', url: 'http://192.168.176.154/root/base-nop.git']]])
|
||||
echo "开始编译 "
|
||||
ws("${WORKSPACE}/fw-base-nop"){
|
||||
sh ' mvn clean install -DskipTests -Denv=beta'
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('构建镜像'){
|
||||
steps {
|
||||
script{
|
||||
jar_file=sh(returnStdout: true, script: "find ${WORKSPACE} ./ -name fw-base-nop.jar |head -1").trim()
|
||||
}
|
||||
sh """
|
||||
cp $jar_file /data/fw-base-nop/
|
||||
docker build -t 192.168.176.155/library/fw-base-nop:${BUILD_ID} /data/fw-base-nop/.
|
||||
"""
|
||||
}
|
||||
}
|
||||
|
||||
stage('上传镜像'){
|
||||
steps {
|
||||
withCredentials([usernamePassword(credentialsId: 'auth_harbor', passwordVariable: 'dockerHubPassword', usernameVariable: 'dockerHubUser')]) {
|
||||
sh "docker login -u ${env.dockerHubUser} -p ${env.dockerHubPassword} 192.168.176.155"
|
||||
sh 'docker push 192.168.176.155/library/fw-base-nop:${BUILD_ID}'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('删除本地镜像'){
|
||||
steps{
|
||||
sh "docker rmi -f 192.168.176.155/library/fw-base-nop:${BUILD_ID}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
执行结果如下:-
|
||||
-
|
||||
-
|
||||
|
||||
|
||||
这样,基础版的pipeline脚本算是完成了。
|
||||
|
||||
进阶版
|
||||
|
||||
虽然脚本是完成了,但是回顾前面pipeline章节介绍的相关语法,还是能够发现一些存在的问题并且有些步骤还可以进行优化,对部分步骤的代码还能进行缩减。
|
||||
|
||||
比如:
|
||||
|
||||
1、 在镜像构建步骤中使用的存放dockerfile和jar包的目录,可以通过挂载共享存储的方式挂载到agent代理节点指定的目录下,这样如果Dockerfile发生变更或者要拷贝镜像到指定目录的路径发生变更时只需要修改一次即可,并且在任何slave节点都可以通过挂载共享存储的方式使用,不会限制job只使用固定的agent代理节点。
|
||||
|
||||
2、对于”上传镜像“步骤中对私有仓库认证的操作,除了使用withCredentials方法外,也可以直接在agent指定代理节点上对私有仓库进行认证,这样在上传镜像时就可以直接略过向私有仓库认证操作。
|
||||
|
||||
3、对于”删除本地镜像“步骤中的删除操作,可以直接放到”上传镜像“步骤中。
|
||||
|
||||
下面针对上面列出的问题进行简单的优化。
|
||||
|
||||
1、对于使用共享存储和在代理节点对私有仓库认证的操作在代码中不会体现,共享存储我这里使用nfs模拟代替,目录没有变;
|
||||
|
||||
2、仓库认证直接在agent代理节点上通过docker login命令登录即可,这样就可以将上传镜像和删除镜像两个stage和并为一个,同时为了规范操作,将在代码编译步骤中的构建镜像操作也放到上传镜像操作stage中。
|
||||
|
||||
如下所示:
|
||||
|
||||
stage('上传并删除本地镜像'){
|
||||
steps {
|
||||
sh """
|
||||
docker build 192.168.176.155/library/fw-base-nop:${BUILD_ID}
|
||||
docker push 192.168.176.155/library/fw-base-nop:${BUILD_ID}
|
||||
docker rmi -f 192.168.176.155/library/fw-base-nop:${BUILD_ID}
|
||||
"""
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
对于项目数量较大以及需要对项目分组管理,并且想要使用同一套pipeline脚本作为模板的时候,可以将该pipeline脚本中使用的部分通用配置设置成变量,每次新建项目只更改变量的值即可。
|
||||
|
||||
比如:
|
||||
|
||||
|
||||
对于项目名称,项目构建的产物名称,项目所属的组可以设置成变量
|
||||
对于私有仓库的地址以及项目所属组也可以通过变量的方式配置
|
||||
对于镜像的版本,同样可以通过变量的方式配置
|
||||
|
||||
|
||||
根据上面列出的优化思路,后续的脚本修改如下:
|
||||
|
||||
pipeline {
|
||||
agent { node { label 'jenkins-slave1'}}
|
||||
tools {
|
||||
maven 'maven-3.5.4'
|
||||
jdk 'jdk-1.8'
|
||||
}
|
||||
environment {
|
||||
project_name = 'fw-base-nop'
|
||||
jar_name = 'fw-base-nop.jar'
|
||||
registry_url = '192.168.176.155'
|
||||
project_group = 'base'
|
||||
}
|
||||
stages {
|
||||
stage('代码拉取并编译'){
|
||||
steps {
|
||||
checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: 'c33d60bd-67c6-4182-b52c-d7aeebfab772', url: 'http://192.168.176.154/root/base-nop.git']]])
|
||||
echo "开始打包 "
|
||||
ws("${WORKSPACE}/fw-base-nop"){
|
||||
sh ' mvn clean install -DskipTests -Denv=beta'
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('构建镜像'){
|
||||
steps {
|
||||
script{
|
||||
jar_file=sh(returnStdout: true, script: "find ${WORKSPACE} ./ -name $jar_name |head -1").trim()
|
||||
}
|
||||
sh """
|
||||
cp $jar_file /data/$project_group/$project_name/
|
||||
docker build -t $registry_url/$project_group/$project_name:${BUILD_ID} /data/$project_group/$project_name/.
|
||||
"""
|
||||
}
|
||||
}
|
||||
stage('上传镜像'){
|
||||
steps {
|
||||
sh """
|
||||
docker push $registry_url/$project_group/$project_name:${BUILD_ID}
|
||||
docker rmi -f $registry_url/$project_group/$project_name:${BUILD_ID}
|
||||
"""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
配置比较简单,学会使用environment关键字即可。
|
||||
|
||||
除了使用environment参数以外,也可以在job构建时传入参数用来代替部分变量,这就需要用到前面学过的parameters指令了。
|
||||
|
||||
比如,对于设定的jar包名称和所属项目组,分别添加一个string类型的参数和choice类型的参数。
|
||||
|
||||
如下代码:
|
||||
|
||||
parameters {
|
||||
string defaultValue: 'fw-base-nop.jar', description: 'jar包名称,必须以.jar后缀结尾', name: 'jar_name', trim: false
|
||||
choice choices: ['base', 'open', 'tms'], description: '服务所属项目组', name: 'project_group'
|
||||
}
|
||||
|
||||
|
||||
既然定义了外部参数传入变量,对于通过environment指令定义的这两个变量也就没必要存在了。
|
||||
|
||||
这样在执行pipeline job时就会变成参数化构建类型的job,如下图所示:-
|
||||
|
||||
|
||||
构建时输入想要的参数值即可,使用parameters这种方式个人觉得比较臃肿,可能使用的场景不对,所以对于parameters指令要根据实际情况考虑要不要使用。
|
||||
|
||||
成型版
|
||||
|
||||
本次版本对于进阶版的示例没有什么明显的改动,只是将与docker相关的操作通过docker pipeline插件去执行。有关Docker pipeline 插件相关的内容还没有介绍,此处通过示例先简单演示一下。
|
||||
|
||||
对于stages中的代码变化如下:
|
||||
|
||||
stages {
|
||||
stage('代码拉取并编译'){
|
||||
steps {
|
||||
checkout([$class: 'GitSCM', branches: [[name: "*/master"]], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: 'c33d60bd-67c6-4182-b52c-d7aeebfab772', url: 'http://192.168.176.154/root/base-nop.git']]])
|
||||
|
||||
echo "开始编译 "
|
||||
|
||||
ws("${WORKSPACE}/fw-base-nop"){
|
||||
sh ' mvn clean install -DskipTests -Denv=beta'
|
||||
}
|
||||
|
||||
script{
|
||||
jar_file=sh(returnStdout: true, script: "find ${WORKSPACE} ./ -name ${jar_name} |head -1").trim()
|
||||
}
|
||||
|
||||
sh """
|
||||
cp $jar_file /data/${project_group}/$project_name/
|
||||
"""
|
||||
}
|
||||
}
|
||||
|
||||
stage('构建并上传镜像'){
|
||||
steps {
|
||||
script {
|
||||
def customImage = docker.build("$registry_url/${project_group}/$project_name:${BUILD_ID}", "/data/${project_group}/$project_name/.")
|
||||
customImage.push()
|
||||
}
|
||||
|
||||
sh """
|
||||
docker rmi -f $registry_url/${project_group}/$project_name:${BUILD_ID}
|
||||
"""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
这样使用docker pipeline插件中的build和push方法就简单实现了镜像的构建和推送。从上面的脚本可以看到,在使用插件方法时使用了script{}块包含,是因为Jenkins中的插件基本上都是适配与脚本式语法,而对于声明式语法,需要使用script块来声明使用脚本式语法的部分功能。
|
||||
|
||||
在以后的章节会详细介绍docker pipeline插件的使用语法,这里先简单演示一下如何使用。
|
||||
|
||||
使用多agent
|
||||
|
||||
对于分工比较明确的agent节点,比如某些agent代理节点只能进行代码编译操作,某些agent代理节点只能进行docker镜像构建操作;或者对于同一个项目有不同的脚本语言编写的代码,需要使用使用不同的编译工具构建时时,对于这些特殊情况,可以使用多agent构建方式,在不同的步骤使用不同的agent节点进行项目构建操作。
|
||||
|
||||
以上面的代码为示例,在代码拉取和编译步骤使用master节点,在镜像构建和push步骤使用jenkins-slave1节点,代码如下:
|
||||
|
||||
pipeline {
|
||||
agent none
|
||||
tools {
|
||||
maven 'maven-3.5.4'
|
||||
jdk 'jdk-1.8'
|
||||
}
|
||||
environment {
|
||||
project_name = 'fw-base-nop'
|
||||
jar_name = 'fw-base-nop.jar'
|
||||
registry_url = '192.168.176.155'
|
||||
project_group = 'base'
|
||||
}
|
||||
}
|
||||
stages {
|
||||
stage('代码拉取并打包'){
|
||||
agent { node { label 'master'} }
|
||||
|
||||
steps {
|
||||
checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: 'c33d60bd-67c6-4182-b52c-d7aeebfab772', url: 'http://192.168.176.154/root/base-nop.git']]])
|
||||
echo "开始打包 "
|
||||
ws("${WORKSPACE}/fw-base-nop"){
|
||||
sh ' mvn clean install -DskipTests -Denv=beta'
|
||||
}
|
||||
|
||||
script{
|
||||
jar_file=sh(returnStdout: true, script: "find ${WORKSPACE} ./ -name $jar_name |head -1").trim()
|
||||
}
|
||||
sh "scp $jar_file root@jenkins-slave1:/data/$project_group/$project_name/"
|
||||
}
|
||||
}
|
||||
|
||||
stage('上传镜像'){
|
||||
agent { node { label 'jenkins-slave1'}}
|
||||
steps {
|
||||
script {
|
||||
def customImage = docker.build("$registry_url/${project_group}/$project_name:${BUILD_ID}", "/data/${project_group}/$project_name/.")
|
||||
customImage.push()
|
||||
}
|
||||
|
||||
sh """
|
||||
docker rmi -f $registry_url/${project_group}/$project_name:${BUILD_ID}
|
||||
"""
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
1、 使用多agent代理节点进行构建时,最顶部的agent需要指定为none。
|
||||
|
||||
2、 由于使用了不同的agent节点,对于构建产物需要从master主机拷贝到jenkins-slave1主机上,相对于使用同一个agent,这应该算是比较复杂的操作了吧,但是如果用上共享存储或使用同一个agent,这应该不算是问题。
|
||||
|
||||
3、 对于代码编译和镜像构建分别使用不同的工具进行不同类型的操作,所以使用两个stage即可完成所有操作。
|
||||
|
||||
异常处理
|
||||
|
||||
在使用pipeline脚本执行任务的时候,难免遇到执行出现异常的情况,并且出现异常后流水线会自动终止退出,为了第一时间获取流水线执行的结果或者状态,就需要我们在脚本中加入异常处理的代码。
|
||||
|
||||
前面介绍过声明式语法使用post指令根据任务执行状态进行下一步的处理操作,下面简单看一下post在该示例中的应用。
|
||||
|
||||
post {
|
||||
always {
|
||||
echo "xxx"
|
||||
}
|
||||
success {
|
||||
emailext (
|
||||
subject: "'${env.JOB_NAME} [${env.BUILD_NUMBER}]' 更新正常",
|
||||
body: """
|
||||
详情:<br>
|
||||
Jenkins 构建 ${currentBuild.result} <br>
|
||||
项目名称 :${env.JOB_NAME} <br>
|
||||
项目构建id:${env.BUILD_NUMBER} <br>
|
||||
URL :${env.BUILD_URL} <br>
|
||||
构建日志:${env.BUILD_URL}console
|
||||
|
||||
""",
|
||||
to: "[email protected]",
|
||||
recipientProviders: [[$class: 'CulpritsRecipientProvider'],
|
||||
[$class: 'DevelopersRecipientProvider'],
|
||||
[$class: 'RequesterRecipientProvider']]
|
||||
)
|
||||
}
|
||||
failure {
|
||||
echo "构建日志:${env.BUILD_URL}console"
|
||||
}
|
||||
unstable {
|
||||
echo "构建日志:${env.BUILD_URL}console"
|
||||
}
|
||||
changed {
|
||||
echo "changed"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
将post指令放到全局stages步骤下面,该post指令对于流水线脚本执行的多种状态结果通过使用emailext插件发送邮件通知管理人员。邮件内容可以根据自己实际情况定义,可以参考上面的示例。
|
||||
|
||||
如果觉得将每种状态结果分别单独处理比较麻烦,可以根据job执行状态进行分组处理,比如将状态分为成功和失败两种状态,可以修改脚本如下:
|
||||
|
||||
post {
|
||||
always {
|
||||
script{
|
||||
sh 'docker rmi -f $registry_url/$project_group/$project_name:${BUILD_ID}'
|
||||
if (currentBuild.currentResult == "ABORTED" || currentBuild.currentResult == "FAILURE" || currentBuild.currentResult == "UNSTABLE" ){
|
||||
emailext (
|
||||
subject: "'${env.JOB_NAME} [${env.BUILD_NUMBER}]' 构建结果",
|
||||
body: """
|
||||
详情:\n<br>
|
||||
Jenkins 构建 ${currentBuild.currentresult} '\n'<br>
|
||||
项目名称 :${env.JOB_NAME} "\n"
|
||||
项目构建id:${env.BUILD_NUMBER} "\n"
|
||||
URL :${env.BUILD_URL} \n
|
||||
构建日志:${env.BUILD_URL}console
|
||||
|
||||
""",
|
||||
to: "[email protected]",
|
||||
recipientProviders: [[$class: 'CulpritsRecipientProvider'],
|
||||
[$class: 'DevelopersRecipientProvider'],
|
||||
[$class: 'RequesterRecipientProvider']]
|
||||
)
|
||||
}else{
|
||||
echo "构建成功"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
使用判断语句对执行结果进行判断并分别处理。有关currentBuild变量的介绍,可以参考”全局变量“界面中currentbuild方法。
|
||||
|
||||
Parallel(并行执行)
|
||||
|
||||
在实际工作中可能会遇到并行执行一些job的情况。比如同属一个项目组的多个应用服务依赖同一个或多个基础服务,如果要构建应用服务时需要优先构建基础服务,这种情况先我们就可以使用并行构建的方案。
|
||||
|
||||
例如,我们有基础服务service1和service2,应用服务app1、app2和app3,有些情况下,开发提交了基础服务代码到service1或(和)service2,同时提交了应用服务(app1 app2 app3)的代码需要构建,这种情况下如果先手动构建service1和service2,等着两个都构建完了,再去手动构建三个应用服务,无疑是繁琐的。
|
||||
|
||||
所以本小节就用pipeline中的Parallel关键字来解决类似的问题,代码如下:
|
||||
|
||||
pipeline {
|
||||
agent any
|
||||
parameters {
|
||||
extendedChoice description: '是否构建基础服务', descriptionPropertyValue: 'base_service1,base_service2', multiSelectDelimiter: ',', name: 'base_service', quoteValue: false, saveJSONParameterToFile: false, type: 'PT_CHECKBOX', value: 'service1,service2', visibleItemCount: 2
|
||||
extendedChoice description: '是否构建应用服务', descriptionPropertyValue: 'app_service1,app_service2,app_service3', multiSelectDelimiter: ',', name: 'app_service', quoteValue: false, saveJSONParameterToFile: false, type: 'PT_CHECKBOX', value: 'app1,app2,app3', visibleItemCount: 3
|
||||
}
|
||||
|
||||
stages(){
|
||||
stage('deploy'){
|
||||
parallel{
|
||||
stage('deploy service1'){
|
||||
when {
|
||||
expression {
|
||||
return params.base_service =~ /service1/
|
||||
}
|
||||
}
|
||||
steps{
|
||||
sh "echo build job base_service1"
|
||||
// build job: 'service1'
|
||||
}
|
||||
}
|
||||
stage('deploy service2'){
|
||||
when {
|
||||
expression {
|
||||
return params.base_service =~ /service2/
|
||||
}
|
||||
}
|
||||
steps{
|
||||
sh "echo build job base_service2"
|
||||
// build job: 'service2'
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
stage('build job'){
|
||||
parallel{
|
||||
stage('构建应用服务1') {
|
||||
when {
|
||||
expression {
|
||||
return params.app_service =~ /app1/
|
||||
}
|
||||
}
|
||||
|
||||
steps('build') {
|
||||
sh "echo build job app1"
|
||||
// build job: 'app1'
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
stage('构建应用服务2') {
|
||||
when {
|
||||
expression {
|
||||
return params.app_service =~ /app2/
|
||||
}
|
||||
}
|
||||
|
||||
steps('build') {
|
||||
sh "echo build job app2"
|
||||
// build job: 'app2'
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
stage('构建应用服务3') {
|
||||
when {
|
||||
expression {
|
||||
return params.app_service =~ /app3/
|
||||
}
|
||||
}
|
||||
|
||||
steps('build') {
|
||||
sh "echo build job app3"
|
||||
// build job: 'app3'
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
1 、在该示例中构建job操作使用shell命令代替,在实际工作中,需要使用build job指令来构建一个job,示例中被注释的部分为构建job的代码。
|
||||
|
||||
2、 除了使用parameters指令指定参数外,也可以通过在”General“步骤中勾选This project is parameterized来自定义参数,使用方法在前面章节有介绍,此处不再重复说明。
|
||||
|
||||
有关使用声明式语法进行持续交付与部署的实践到此结束。
|
||||
|
||||
使用脚本式语法进行持续交付与部署实践
|
||||
|
||||
通过在声明式语法章节的学习,对于代码编译,镜像构建整个流程应该都不用重复介绍了,本节与使用声明式语法实践一样,也是只通过使用虚拟机作为agent代理节点来执行流水线脚本,对于部分重复的内容会一笔带过,如果有疑问,可以随时联系笔者。
|
||||
|
||||
node代理
|
||||
|
||||
声明式语法通过agent关键字使用代理,脚本式语法通过node关键字使用代理。
|
||||
|
||||
根据声明式语法中的流程以及示例,使用脚本式语法的基础示例代码如下:
|
||||
|
||||
node('jenkins-slave1'){
|
||||
stage('代码编译'){
|
||||
checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: 'c33d60bd-67c6-4182-b52c-d7aeebfab772', url: 'http://192.168.176.154/root/base-nop.git']]])
|
||||
echo "开始编译 "
|
||||
ws("$WORKSPACE/fw-base-nop"){
|
||||
sh ' mvn clean install -DskipTests -Denv=beta'
|
||||
}
|
||||
}
|
||||
|
||||
stage('镜像构建'){
|
||||
script{
|
||||
jar_file=sh(returnStdout: true, script: "find ${WORKSPACE} ./ -name fw-base-nop.jar |head -1").trim()
|
||||
}
|
||||
sh """
|
||||
cp $jar_file /data/base/fw-base-nop/
|
||||
docker build -t 192.168.176.155/library/fw-base-nop:${BUILD_ID} /data/base/fw-base-nop/.
|
||||
"""
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
代码编译
|
||||
|
||||
在上面基础示例中简单实现了一下使用脚本式语法进行代码拉取、编译、镜像构建的操作,下面根据在声明式脚本中的示例,将部分代码通过使用脚本式语法指令的方式实现,对于一些可能存在的问题以及先关优化方案在前面已经做过介绍,这里就不在重复介绍了。只需要关注一下脚本的变更即可。
|
||||
|
||||
定义工具
|
||||
|
||||
比如,对于代码编译时用到的工具的环境变量的定义,在脚本式语法中可以使用tool和withEnv指令设置jdk和maven工具的环境变量,代替source /etc/profile命令,代码如下所示:
|
||||
|
||||
def jdk = tool name: 'jdk-1.8'
|
||||
env.PATH = "${jdk}/bin:${env.PATH}"
|
||||
stage('代码编译'){
|
||||
checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: 'c33d60bd-67c6-4182-b52c-d7aeebfab772', url: 'http://192.168.176.154/root/base-nop.git']]])
|
||||
echo "开始打包 "
|
||||
withEnv(["PATH+MAVEN=${tool 'maven-3.5.4'}/bin"]) {
|
||||
ws("$WORKSPACE/fw-base-nop"){
|
||||
sh ' mvn clean install -DskipTests -Denv=beta'
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
该脚本使用def指令和env命令定义了jdk的环境变量,使用withenv指令定义了maven的环境变量。
|
||||
|
||||
上面定义变量的方式也可以写成如下:
|
||||
|
||||
node {
|
||||
def jdk=tool name: 'jdk-1.8'
|
||||
def mvn=tool name:'maven-3.5.4'
|
||||
env.PATH = "${jdk}/bin:${mvn}/bin:${env.PATH}"
|
||||
|
||||
stage('代码编译'){
|
||||
......
|
||||
echo "开始打包 "
|
||||
ws("$WORKSPACE/fw-base-nop"){
|
||||
sh ' mvn clean install -DskipTests -Denv=beta'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
构建并推送镜像
|
||||
|
||||
在构建并推送镜像阶段,同样可以使用docker pipeline插件实现镜像的构建与推送操作。
|
||||
|
||||
代码如下:
|
||||
|
||||
stage('镜像构建'){
|
||||
script{
|
||||
jar_file=sh(returnStdout: true, script: "find ${WORKSPACE} ./ -name fw-base-nop.jar |head -1").trim()
|
||||
}
|
||||
sh """
|
||||
cp $jar_file /data/base/fw-base-nop/
|
||||
"""
|
||||
}
|
||||
|
||||
stage('推送镜像到仓库'){
|
||||
def customImage=docker.build("192.168.176.155/base/fw-base-nop:${env.BUILD_ID}",'/data/base/fw-base-nop/')
|
||||
customImage.push()
|
||||
}
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
1、 如果dockerfile的名字不是”Dockerfile或者dockerfile“,还需要通过-f参数指定dockefile的名称。
|
||||
|
||||
2、 推送镜像阶段的默认条件是agent代理节点已经对私有仓库做了认证,如果agent节点没有对私有仓库做认证,这里还需要加上认证的方法,参考下面的“对私有仓库服务认证”。
|
||||
|
||||
3、 由于docker pipeline插件没有删除镜像的属性,所以本地构建的镜像还是要使用shell命令去删除,与在声明式语法中使用相同的命令即可,这里不再演示。
|
||||
|
||||
私有仓库服务认证
|
||||
|
||||
使用脚本式语法对私有仓库服务认证,相对于声明式语法就更简单了,只需要使用docker pipeline插件中的with_registry方法,即可轻松解决认证问题,如下代码:
|
||||
|
||||
stage("构建并推送镜像"){
|
||||
docker.withRegistry('http://192.168.176.155', 'auth_harbor') {
|
||||
def customImage=docker.build("${vars.registry_url}/${vars.project_group}/${vars.project_name}:${env.BUILD_ID}","/data/${vars.project_group}/${vars.project_name}/")
|
||||
customImage.push()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
与在声明式语法中使用docker插件的build方法、push方法一样,在声明式语法中使用该方法同样需要用script{}块包含起来,代码示例如下:
|
||||
|
||||
stage('构建并上传镜像'){
|
||||
steps {
|
||||
script {
|
||||
docker.withRegistry('http://192.168.176.155', 'auth_harbor') {
|
||||
def customImage=docker.build("${registry_url}/${project_group}/${project_name}:${env.BUILD_ID}","/data/${project_group}/${project_name}/")
|
||||
customImage.push()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
使用变量
|
||||
|
||||
在声明式语法中使用environment来定义变量,在脚本式语法中使用def指令来定义变量,如下所示:
|
||||
|
||||
def vars=[project_name:'fw-base-nop',jar_name:'fw-base-nop.jar',registry_url:'192.168.176.155',project_group:'base']
|
||||
|
||||
|
||||
使用时,通过${vars.project_name}来引用该值,在该示例中配置如下:
|
||||
|
||||
node('jenkins-slave1'){
|
||||
def jdk = tool name: 'jdk-1.8'
|
||||
env.PATH = "${jdk}/bin:${env.PATH}"
|
||||
def vars=[project_name:'fw-base-nop',jar_name:'fw-base-nop.jar',registry_url:'192.168.176.155',project_group:'base']
|
||||
|
||||
stage('代码编译'){
|
||||
|
||||
checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: 'c33d60bd-67c6-4182-b52c-d7aeebfab772', url: 'http://192.168.176.154/root/base-nop.git']]])
|
||||
echo "开始打包 "
|
||||
withEnv(["PATH+MAVEN=${tool 'maven-3.5.4'}/bin"]) {
|
||||
ws("$WORKSPACE/fw-base-nop"){
|
||||
sh ' mvn clean install -DskipTests -Denv=beta'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('镜像构建'){
|
||||
script{
|
||||
jar_file=sh(returnStdout: true, script: "find ${WORKSPACE} ./ -name ${vars.jar_name} |head -1").trim()
|
||||
}
|
||||
sh """
|
||||
cp $jar_file /data/${vars.project_group}/${vars.project_name}/
|
||||
"""
|
||||
}
|
||||
|
||||
stage('推送镜像到仓库'){
|
||||
docker.withRegistry('http://192.168.176.155', 'auth_harbor') {
|
||||
def customImage=docker.build("${vars.registry_url}/${vars.project_group}/${vars.project_name}:${env.BUILD_ID}","/data/${vars.project_group}/${vars.project_name}/")
|
||||
customImage.push()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
使用多agent
|
||||
|
||||
使用多agent的方式在语法章节中已经做了简单介绍,首先回顾一下基本语法。
|
||||
|
||||
node() {
|
||||
stage('test-node'){
|
||||
node('jenkins-slave1'){
|
||||
stage('test1'){
|
||||
sh 'hostname'
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('test-node2'){
|
||||
node('jenkins-slave169'){
|
||||
stage('test2'){
|
||||
sh 'hostname'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
对于核心脚本代码的实现,只需要将在声明式语法示例中的代码复制过来放到stage下即可,还是比较简单的。
|
||||
|
||||
异常处理
|
||||
|
||||
在前面的pipeline章节中有介绍,脚本式语法使用try/catch/finally关键字来捕捉异常并对异常进行处理。使用try指令比较灵活,它可以用来包含全局的stage,也可以包含特定的stage中,try{}后边必须包含catch或者finally关键字,也可以同时都包含。
|
||||
|
||||
代码如下:
|
||||
|
||||
node('jenkins-slave1'){
|
||||
def jdk = tool name: 'jdk-1.8'
|
||||
env.PATH = "${jdk}/bin:${env.PATH}"
|
||||
def vars=[project_name:'fw-base-nop',jar_name:'fw-base-nop.jar',registry_url:'192.168.176.155',project_group:'base']
|
||||
|
||||
try{
|
||||
stage('代码编译'){
|
||||
|
||||
checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: 'c33d60bd-67c6-4182-b52c-d7aeebfab772', url: 'http://192.168.176.154/root/base-nop.git']]])
|
||||
|
||||
echo "开始打包 "
|
||||
withEnv(["PATH+MAVEN=${tool 'maven-3.5.4'}/bin"]) {
|
||||
sh "cd ${vars.project_name}/ && mvn clean install -DskipTests -Denv=beta"
|
||||
}
|
||||
}
|
||||
stage('镜像构建'){
|
||||
script{
|
||||
jar_file=sh(returnStdout: true, script: "find ${WORKSPACE} ./ -name ${vars.jar_name} |head -1").trim()
|
||||
}
|
||||
sh """
|
||||
cp $jar_file /data/${vars.project_group}/${vars.project_name}/
|
||||
"""
|
||||
}
|
||||
|
||||
stage('推送镜像到仓库'){
|
||||
def customImage=docker.build("${vars.registry_url}/${vars.project_group}/${vars.project_name}:${env.BUILD_ID}","/data/${vars.project_group}/${vars.project_name}/")
|
||||
customImage.push()
|
||||
}
|
||||
}catch(all){
|
||||
currentBuild.result = 'FAILURE'
|
||||
}
|
||||
if(currentBuild.currentResult == "ABORTED" || currentBuild.currentResult == "FAILURE" || currentBuild.currentResult == "UNSTABLE" ) {
|
||||
echo "---currentBuild.result is:${currentBuild.currentResult}"
|
||||
}
|
||||
else {
|
||||
echo "---currentBuild.result is:${currentBuild.currentResult}"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
对于if和else判断后的步骤,同样可以使用emailext方法将构建结果发送邮件给管理员,参考声明式语法中发送邮件的配置即可。
|
||||
|
||||
删除工作空间
|
||||
|
||||
有些项目为了防止缓存可能会需要在构建完成后删除当前的工作目录。比较简单的方式是使用shell命令直接删除。但是既然是使用pipeline,这里就用pipeline指令删除,此时也许你已经想到方法了,没错,就是使用dir和deleteDir指令。
|
||||
|
||||
针对上面的示例,可以在”推送镜像到仓库“步骤后添加一个新的stage,通过dir命令进入目录,然后通过deleteDir指令删除。具体的代码如下:
|
||||
|
||||
stage('清除工作空间'){
|
||||
dir('$WORKSPACE'){
|
||||
deleteDir()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
这样在执行该stage的时候就会删除当前的工作目录。
|
||||
|
||||
代码部署
|
||||
|
||||
前面的内容介绍的都是对代码的编译以及构建等操作,关于部署的操作没有提及。如何部署可以参考之前ansible章节编写的playbook,只需要做简单的修改即可。当然,如果你不会也没关系,在后面的章节中我会专门用一个章节来写一下如何部署。
|
||||
|
||||
有关使用pipeline语法进行持续交付与部署的基础实践就介绍到这里,在下一节中将介绍一下常用的Docker pipeline的语法。
|
||||
|
||||
|
||||
|
||||
|
610
专栏/Jenkins持续交付和持续部署/12.JenkinsDockerPipeline插件动态生成Slave节点语法剖析.md
Normal file
610
专栏/Jenkins持续交付和持续部署/12.JenkinsDockerPipeline插件动态生成Slave节点语法剖析.md
Normal file
@@ -0,0 +1,610 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
12.Jenkins Docker Pipeline插件动态生成Slave节点语法剖析
|
||||
越来越多的公司和用户在实际工作中引入Jenkins,在使用Jenkins自动化构建部署的过程中,使用动态容器作为构建部署工作的执行环境的方案也越来越多的被采纳,不仅提高了资源利用率,同时还降低配置成本和维护管理成本。
|
||||
|
||||
在pipeline语法章节介绍Jenkins agent代理的时候有提到可以使用Docker容器作为流水线的执行环境。对于可以在Linux上运行的构建步骤,同样可以在容器中进行。每个流水线项目仅需要选择一个包含所需要使用的工具和库的镜像即可。而docker插件设计的目的除了操作容器以外,另一个比较重要的功能是通过指定的镜像启动容器作为单个stage或全局流水线的执行环境。
|
||||
|
||||
为了方便学习,同样将两种类型的语法分开介绍。
|
||||
|
||||
脚本式语法
|
||||
|
||||
在文章的开始首先介绍一下Docker pipeline插件的使用,毕竟无论是声明式语法还是脚本式语法都是围绕该插件来启动代理节点。该插件在脚本式流水线中使用比较简单,下面介绍一下基本语法。
|
||||
|
||||
docker-pipeline插件在安装后,会产生一系列的变量,要获取这些变量的使用方法的详细信息,可以在任何一个pipeline类型的job里,通过pipeline syntax(流水线语法)来查看,在跳转的界面中点击”全局变量参考菜单“
|
||||
|
||||
如下所示-
|
||||
|
||||
|
||||
更多变量内容,可去Jenkins pipeline项目里查看,下面对一些经常用到的变量方法进行简单的介绍
|
||||
|
||||
image
|
||||
|
||||
image方法用于根据镜像名称或者镜像id定义一个镜像对象,然后根据该方法下的多个属性来执行不同的操作
|
||||
|
||||
比如,根据镜像名称定义对象,通过inside属性启动一个容器
|
||||
|
||||
node {
|
||||
docker.image('maven:3.3.3-jdk-8').inside {
|
||||
git '…your-sources…'
|
||||
sh 'mvn -B clean install'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
说明
|
||||
|
||||
|
||||
image方法根据image名称从Docker Hub拉取镜像并定义对象,通过inside属性启动容器。默认使用jenkins所在主机运行该容器,如果要在其他节点可以在node里添加节点名称,如下所示
|
||||
|
||||
node('jenkins-slave1') {
|
||||
docker.image('maven:3.3.3-jdk-8').inside('-v $HOME/:/.m2/') {
|
||||
git '…your-sources…'
|
||||
sh 'mvn -B clean install'
|
||||
}
|
||||
}
|
||||
|
||||
该示例会在jenkins的系统配置里寻找label或者name为jenkins-slave1的节点拉取镜像并启动容器-
|
||||
1、 inside属性用于启动容器并添加容器启动时的参数,该属性可以没有参数,上面示例添加了一个volume挂载的参数-
|
||||
2、 镜像也可以从私有仓库拉取,同时,也可以使用image_id启动一个容器,但该方法用的不多
|
||||
|
||||
|
||||
上面示例使用inside属性启动了一个容器,除了使用这种方式外,还可以通过将image定义的对象赋予变量的方式使用,比如
|
||||
|
||||
node('jenkins-slave1') {
|
||||
def maven = docker.image('maven:latest')
|
||||
maven.pull()
|
||||
maven.inside {
|
||||
sh 'hostname'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
说明
|
||||
|
||||
|
||||
pull方法用于确保该镜像在镜像仓库里的版本是最新的
|
||||
inside属性启动容器
|
||||
|
||||
|
||||
build
|
||||
|
||||
在前面pipeline章节的声明式语法中有介绍可以使用docker和dockrefile来启动一个容器。在脚本式语法中除了根据镜像名称或者镜像id启动一个容器以外,也可以通过dockerfile方式构建一个容器并启动。为了构建 Docker 镜像,Docker pipeline插件提供了一个 build() 方法用于在流水线运行之前根据源码库中提供的Dockerfile 构建一个新的镜像。
|
||||
|
||||
使用语法 docker.build("my-image-name") 可以构建一个镜像,使用定义变量的方式的好处在于在下面能够使用该变量的其他属性方法
|
||||
|
||||
比如:
|
||||
|
||||
node {
|
||||
checkout scm
|
||||
def customImage = docker.build("my-image:${env.BUILD_ID}")
|
||||
customImage.inside {
|
||||
sh 'make test'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
默认情况下, build() 方法是根据拉取的源码当前目录下的Dockerfile文件构建镜像。
|
||||
|
||||
build方法也可以通过提供一个包含 Dockerfile文件的目录路径作为build() 方法的第二个参数 ,用来在指定的目录下构建镜像
|
||||
|
||||
比如:
|
||||
|
||||
node {
|
||||
checkout scm
|
||||
def testImage = docker.build("test-image", "./dockerfiles/test")
|
||||
|
||||
testImage.inside {
|
||||
sh 'make test'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
|
||||
通过在 ./dockerfiles/test/目录下发现的Dockerfile文件构建test-image。
|
||||
checkout scm表示从源码仓库拉取Dockerfile文件,可以单独放置,可以与应用代码放到一起 。需要注意的是,该配置可以通过git clone url(checkout)命令直接代替(包括下面所有示例中的checkout scm)
|
||||
|
||||
|
||||
也可以通过传递 -f参数覆盖默认的 Dockerfile ,当使用这种方法传递参数时, 该字符串的最后一个值必须是Dockerfile文件的路径
|
||||
|
||||
比如
|
||||
|
||||
node {
|
||||
checkout scm
|
||||
def dockerfile = 'Dockerfile.test'
|
||||
def customImage = docker.build("my-image:${env.BUILD_ID}", "-f ${dockerfile} ./dockerfiles")
|
||||
}
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
|
||||
从在./dockerfiles/目录发现的Dockerfile.test文件构建 my-image:${env.BUILD_ID}。
|
||||
|
||||
|
||||
上面的示例中,通过def指令定义了构建镜像操作的变量名称,该变量可以用于通过 push() 方法将Docker 镜像推送到私有仓库
|
||||
|
||||
比如
|
||||
|
||||
node {
|
||||
checkout scm
|
||||
def customImage = docker.build("my-image:${env.BUILD_ID}")
|
||||
customImage.push()
|
||||
}
|
||||
|
||||
|
||||
说明
|
||||
|
||||
|
||||
push方法除了可以将自定义执行流水线的docker镜像推送到镜像仓库以外,也可以将应用代码经过代码编译和镜像构建后推送到私有仓库。
|
||||
|
||||
|
||||
push方法在推送镜像时也可以自定义镜像的tag
|
||||
|
||||
如下所示
|
||||
|
||||
node {
|
||||
checkout scm
|
||||
def customImage = docker.build("my-image:${env.BUILD_ID}")
|
||||
customImage.push()
|
||||
|
||||
customImage.push('latest')
|
||||
}
|
||||
|
||||
|
||||
使用多个容器
|
||||
|
||||
在前面的pipeline章节有介绍在脚本式流水线语法中使用多个agent代理,这里在来回顾一下
|
||||
|
||||
node('jenkins-slave1') {
|
||||
|
||||
stage('Back-end') {
|
||||
docker.image('maven:3-alpine').inside {
|
||||
sh 'mvn --version'
|
||||
}
|
||||
}
|
||||
|
||||
stage('Front-end') {
|
||||
docker.image('node:7-alpine').inside {
|
||||
sh "node --version"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
结合前两节学过的内容,可以在上面语法的基础上对其进行扩展。
|
||||
|
||||
比如,在不同的阶段,使用不同的slave节点启动不同容器作为流水线执行的环境
|
||||
|
||||
如下示例
|
||||
|
||||
node() {
|
||||
|
||||
stage('Back-end') {
|
||||
node('slave1'){
|
||||
stage("test1"){
|
||||
docker.image('maven:3-alpine').inside {
|
||||
sh 'mvn --version'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('Front-end') {
|
||||
node('slave2'){
|
||||
stage('test2'){
|
||||
docker.image('node:7-alpine').inside {
|
||||
sh "node --version"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
这样,针对不同的步骤就会在不同的slave节点上启动容器执行流水线
|
||||
|
||||
使用远程docker服务器
|
||||
|
||||
除了使用jenkins节点宿主机上的docker进程外,还可以使用其他服务器上的docker进程。docker pipeline提供了withServer方法用于连接远程的docker服务器。
|
||||
|
||||
要使用远程Docker服务器上的进程需要Docker服务器开启2375端口,如下所示
|
||||
|
||||
修改/usr/lib/systemd/system/docker.service 配置文件
|
||||
|
||||
#在ExecStart=/usr/bin/docker daemon 后追加 -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock
|
||||
|
||||
#如:
|
||||
ExecStart=/usr/bin/docker daemon -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock
|
||||
|
||||
#修改完后重启docker
|
||||
$ systemctl daemon-reload
|
||||
$ systemctl restart docker
|
||||
|
||||
#查看进程
|
||||
$ netstat -ntlp|grep 2375
|
||||
tcp6 0 0 :::2375 :::* LISTEN 5554/dockerd
|
||||
|
||||
|
||||
配置好后修改pipeline脚本,如下
|
||||
|
||||
node {
|
||||
docker.withServer('tcp://192.168.176.160:2375',) {
|
||||
docker.image('maven:latest').inside {
|
||||
sh "mvn --version"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
这样,就会在远程服务器上启动该容器,可以执行想要执行的命令,比较简单。
|
||||
|
||||
withRegistry(使用私有仓库)
|
||||
|
||||
默认情况下, Docker 流水线 插件集成了 Docker Hub的私有仓库。 在编写流水线脚本时,直接使用image方式拉取的镜像一般都是从Docker Hub拉取的。在上面的声明式流水线语法中介绍了在执行流水线过程中对私有仓库服务的认证的几种方法。对于脚本式流水线,这就简单的多了,那就是使用withRegistry()方法
|
||||
|
||||
例如
|
||||
|
||||
node {
|
||||
checkout scm
|
||||
docker.withRegistry('https://registry.example.com') {
|
||||
|
||||
docker.image('my-custom-image').inside {
|
||||
sh 'make test'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
此时是没有对镜像仓库服务进行认证的。
|
||||
|
||||
而如果想使用自定义的仓库怎么办?使用vm作为agent代理时,在声明式流水线语法可以使用凭据,挂载目录等方式。而使用docker插件就没这么复杂了,通过docker插件的withRegistry() 方法,在使用时通过指定的参数对私有仓库认证,认证方式也是通过jenkins系统里的凭据。
|
||||
|
||||
例如,对于需要身份验证的Docker 私有仓库,直接在Jenkins凭据中添加一个 “Username/Password” 凭据, 然后可以通过片段生成器,使用凭据ID作为 withRegistry()的第二个参数,生成语法片段
|
||||
|
||||
比如
|
||||
|
||||
node {
|
||||
checkout scm
|
||||
|
||||
docker.withRegistry('https://registry.example.com', 'credentials-id') {
|
||||
|
||||
def customImage = docker.build("my-image:${env.BUILD_ID}")
|
||||
|
||||
customImage.push()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
声明式语法
|
||||
|
||||
首先回顾一下在pipeline语法章节介绍声明式语法时使用docker启动agent代理的一些使用方法,然后在做一下补充
|
||||
|
||||
首先来看一下在声明式脚本中使用docker作为agent的范例
|
||||
|
||||
例如,启动一个容器并执行一条命令
|
||||
|
||||
pipeline {
|
||||
agent {
|
||||
docker { image 'node:7-alpine' }
|
||||
}
|
||||
stages {
|
||||
stage('Test') {
|
||||
steps {
|
||||
sh 'node --version'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
当执行上面的流水线脚本时, Jenkins将会自动地启动指定的镜像(如果镜像不存在会自动下载)并在其中执行指定的stage,当stage执行完好,该容器会销毁。
|
||||
|
||||
除了使用该方式生成容器以外,也可以使用docker pipeline插件,例如
|
||||
|
||||
pipeline {
|
||||
agent any
|
||||
stages {
|
||||
stage('Test') {
|
||||
steps {
|
||||
script {
|
||||
docker.image('maven:latest').inside(){
|
||||
sh 'mvn --version'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
需要使用script{}块将pipeline语法块包含起来
|
||||
|
||||
使用Dockerfile
|
||||
|
||||
除了使用上面指定容器镜像的方法启动一个容器外,还可以使用自定义的dockerfile启动容器。这种情况就需要将jenkinsfile文件和dockerfile文件放到源码仓库中了。
|
||||
|
||||
pipeline支持从源代码仓库的Dockerfile中构建和运行容器。使用 agent { dockerfile true } 语法会从 Dockerfile 中构建一个镜像并启动容器,而不是从 Docker Hub或者私有仓库中拉取。
|
||||
|
||||
如下示例
|
||||
|
||||
$ cat Dockerfile
|
||||
FROM maven:latest
|
||||
RUN apt-get update \
|
||||
&& apt-get install lsof
|
||||
|
||||
$ cat Jenkinsfile
|
||||
pipeline {
|
||||
agent { dockerfile true }
|
||||
stages {
|
||||
stage('Test') {
|
||||
steps {
|
||||
sh 'lsof'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
将上面的Dockerfile和Jenkinsfile文件一起放到源码仓库中,然后配置jenkins项目时使用Pipeline script from SCM方式,配置该源码仓库的地址即可-
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
|
||||
Script Path 指定文件名要与jenkinsfile的文件名一样
|
||||
使用dockerfile的其他参数可以参考pipeline章节中关于agent的介绍,这里不再多说
|
||||
|
||||
|
||||
容器的缓存数据
|
||||
|
||||
使用容器作为流水线的执行环境,当流水线执行完以后,容器就会销毁,流水线构建的产物也会随之丢失。对于使用maven构建工具编译代码的时候,默认会下载外部依赖包并将它们缓存到本地.m2仓库中,以便于再次编译或者其他项目编译的的时候使用。 同时对于编译好的服务包有时候也需要做一次备份,所以我们需要将一些需要重复利用依赖包缓存到本地。
|
||||
|
||||
流水线支持向Docker中添加自定义的参数, 允许用户在启动容器时自定义挂载volume用来缓存数据。
|
||||
|
||||
下面的示例将会在流水线运行期间缓存maven仓库目录( ~/.m2), 从而避免了项目在从新构建时重新下载依赖导致部署效率低下的问题。
|
||||
|
||||
pipeline {
|
||||
agent {
|
||||
docker {
|
||||
image 'maven:3-alpine'
|
||||
args '-v $HOME/.m2:/root/.m2'
|
||||
}
|
||||
}
|
||||
stages {
|
||||
stage('Build') {
|
||||
steps {
|
||||
sh 'mvn -version'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
|
||||
上面流水线示例使用args指令传递容器运行时的参数,-v指定的目录默认挂载到宿主机的目录上;如果宿主机节点过多的情况下,可以使用共享存储目录来代替宿主机目录
|
||||
|
||||
|
||||
使用多个容器
|
||||
|
||||
在构建项目的时候如果遇到多种语言写的代码,需要使用不同的编译工具进行代码的编译;又或者对于一套流水线脚本对于不同功能的stage需要使用不同的镜像时,就可以在一个流水线脚本中启动多个容器镜像
|
||||
|
||||
例如,一个应用既有基于Java的后端API实现又有基于JavaScript的前端实现,这种情况就可以通过agent {} 指令在不同的stage使用不同的容器进行编译操作。
|
||||
|
||||
如下示例
|
||||
|
||||
pipeline {
|
||||
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'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
|
||||
示例中在顶层的agent指令里定义代理主机为none,就必须要在每个stage里设置独立的agent代理
|
||||
分别对后端和前端使用不同的镜像启动构建环境
|
||||
|
||||
|
||||
如果要在指定的node节点上启动容器,或者在不同节点上分别启动不同的容器,又该如何操作?这里就要使用到docker pipeline插件了。
|
||||
|
||||
如下所示,在slave1节点启动两个容器
|
||||
|
||||
pipeline {
|
||||
agent { node { label 'slave1' } }
|
||||
|
||||
stages {
|
||||
stage('Build') {
|
||||
steps {
|
||||
echo 'Building..'
|
||||
sh 'npm install'
|
||||
}
|
||||
}
|
||||
stage('Test') {
|
||||
steps {
|
||||
echo 'Testing..'
|
||||
script {
|
||||
docker.image('selenium:latest').inside(){
|
||||
.......
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('build') {
|
||||
steps {
|
||||
script {
|
||||
docker.image('maven:lastet').inside(){
|
||||
docker build ..
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
使用多个node启动多个容器配置如下
|
||||
|
||||
pipeline {
|
||||
agent none
|
||||
stages {
|
||||
stage('Test') {
|
||||
agent { node { label 'jenkins-slave1'} }
|
||||
steps {
|
||||
echo 'Testing..'
|
||||
script {
|
||||
docker.image('selenium:latest').inside(){
|
||||
.......
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('build') {
|
||||
agent { node { label 'jenkins-slave169'} }
|
||||
steps {
|
||||
script {
|
||||
docker.image('maven:lastet').inside(){
|
||||
docker build ..
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
虽然可以实现,但是该方案可能用的比较少。有兴趣的自己可以尝试一下
|
||||
|
||||
使用私有仓库
|
||||
|
||||
将应用代码编译完成并构建成镜像以后,需要将该镜像推送到指定的私有仓库上去,这个流程中涉及到一个向私有仓库服务认证的过程,那么在声明式流水线语法中如何向私有仓库服务认证呢?可以参考如下方法:
|
||||
|
||||
使用docker login命令
|
||||
|
||||
如果你怕麻烦,并且对于Jenkins的安全设置也比较有把握,那么你可以选择用docker login命令加明文密码。如下示例
|
||||
|
||||
stage('Docker Push') {
|
||||
steps {
|
||||
sh "docker login -u xxx -p xxx registry_url"
|
||||
sh 'docker push 192.168.176.154/base/nop:latest'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
该示例在流水线中直接使用docker login命令向私有仓库服务认证。
|
||||
|
||||
如果觉得明文密码太过显眼,也可以使用”参数化构建过程“选项添加一个password类型的参数,然后使用login命令登录时-p参数直接使用变量名称即可。但是此方法每次构建时都要输入一下密码,显得有些笨拙。此时,在之前的章节中介绍到的凭据就派上用场了。
|
||||
|
||||
除了使用”参数化构建过程“选项提供参数以外,还可以使用Jenkins凭据提供私有仓库服务认证的用户名和密码。
|
||||
|
||||
stage('Docker Push') {
|
||||
steps {
|
||||
withCredentials([usernamePassword(credentialsId: 'dockerregistry', passwordVariable: 'dockerHubPassword', usernameVariable: 'dockerHubUser')]) {
|
||||
sh "docker login -u ${env.dockerHubUser} -p ${env.dockerHubPassword}"
|
||||
sh 'docker push 192.168.176.155/library/base-nop:latest'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
|
||||
该语法片段可以通过片段生成器生成
|
||||
|
||||
|
||||
也可以直接使用参数指定仓库的url和凭据id,示例如下
|
||||
|
||||
agent {
|
||||
docker {
|
||||
image '192.168.176.155/library/jenkins-slave'
|
||||
args '-v $HOME/.m2:/root/.m2 -v /data/fw-base-nop:/data -v /var/run/docker.sock:/var/run/docker.sock'
|
||||
registryUrl 'http://192.168.176.155'
|
||||
registryCredentialsId 'auth_harbor'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
这样便实现了与上面withCredentials方式同样的效果。不过需要说明的是,在使用Docker插件时,建议还是使用脚本式语法,该插件与脚本式语法集成使用会更加方便。
|
||||
|
||||
使用目录挂载
|
||||
|
||||
如果不想用Jenkins里的凭据怎么办,可以使用挂载docker配置文件的方法。使用docker命令登录私有仓库的时候,默认会将认证信息保存在docker本地服务器指定的文件里。这是可以使用args参数时,将该文件挂载到容器内指定的文件上。
|
||||
|
||||
示例如下
|
||||
|
||||
agent {
|
||||
docker {
|
||||
image '192.168.176.155/library/jenkins-slave'
|
||||
args '-v $HOME/.m2:/root/.m2 -v /data/fw-base-nop:/data -v /var/run/docker.sock:/var/run/docker.sock -v /root/.docker/config.json:/root/.docker/config.json'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
|
||||
docker向私有仓库认证的配置文件根据docker安装方式的不同会有所差异。yum方式安装的docker使用的认证的文件为/root/.docker/config.json,该文件存储了认证信息,至于如何获取该文件的具体路径,可以通过docker login registry_url命令,认证成功后就会提示存放认证文件的路径。
|
||||
|
||||
|
||||
使用Docker pipeline插件
|
||||
|
||||
Docker pipeline实际使用的时候在脚本式语法中应用的相对简单,但也可以在声明式流水线中使用,只不过需要将插件的方法通过script{}块包起来。
|
||||
|
||||
比如使用私有仓库的方法,在声明式流水线中可以写成如下
|
||||
|
||||
pipeline{
|
||||
agent ...
|
||||
stages(){
|
||||
stage(){
|
||||
steps{
|
||||
....
|
||||
}
|
||||
}
|
||||
stage(){
|
||||
steps{
|
||||
script{
|
||||
docker.withRegistry('https://registry.example.com', 'credentials-id') {
|
||||
def customImage = docker.build("my-image:${env.BUILD_ID}")
|
||||
customImage.push()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
该方式同样适用于docker插件的其它属性方法。
|
||||
|
||||
有关Pipeline与Docker集成的语法说明就简单的介绍到这里。在下面的章节中,会通过一些示例来演示通过jenkins与Docker集成实现持续交付和持续部署的过程。
|
||||
|
||||
|
||||
|
||||
|
792
专栏/Jenkins持续交付和持续部署/13.使用DockerPipeline插件动态生成JenkinsSlave实践.md
Normal file
792
专栏/Jenkins持续交付和持续部署/13.使用DockerPipeline插件动态生成JenkinsSlave实践.md
Normal file
@@ -0,0 +1,792 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
13.使用 Docker Pipeline插件动态生成Jenkins Slave 实践
|
||||
前面有提到过使用docker pipeline插件的主要目的是为了动态生成Jenkins的slave节点作为pipeline执行的环境。本节根据上一节的语法示例,通过一个实践案例来加深一下理解。
|
||||
|
||||
构建镜像
|
||||
|
||||
首先需要说明的是,无论使用哪种类型的语法、是使用一个镜像还是多个镜像作为流水线stage的agent代理,首先要做的是构建好这些提供流水线脚本执行的镜像。
|
||||
|
||||
经过前面一系列实践,可以发现本系列文章用到工具主要包括jdk、git、maven、docker和ansible等。所以我们需要自定义一个包含以上一个多个工具的镜像,dockerfile如下:
|
||||
|
||||
FROM alpine:latest
|
||||
|
||||
RUN apk update && apk add openjdk8 git maven
|
||||
|
||||
COPY settings.xml /usr/share/java/maven-3/conf/settings.xml
|
||||
COPY docker/docker /usr/bin/docker
|
||||
|
||||
|
||||
其中:
|
||||
|
||||
|
||||
settings.xml文件为maven配置文件,里面配置了Maven私有仓库管理器的地址及认证信息,这里需要根据你实际情况考虑要不要添加,在后面的章节中会介绍其它方法来自定义该文件的使用。
|
||||
|
||||
docker/docker:docker的二进制文件,这里需要注意一点,如果想要将docker可执行文件构建到镜像中或者在启动时挂载docker可执行文件,一定要使用二进制可执行文件,不要使用yum安装生成的二进制文件,否则在使用时会提示docker命令找不到。这是因为使用yum安装生成的docker二进制文件,挂载到容器内目录或者构建到其它镜像中时,必须保证宿主机和镜像内的OS相同,否则无法执行另一个系统平台上的docker二进制文件。
|
||||
|
||||
docker/docker从官方下载的docker tar包文件解压后为一个docker目录。
|
||||
|
||||
|
||||
通过build命令构建:
|
||||
|
||||
$ docker build -t alpine-cicd:latest .
|
||||
|
||||
|
||||
构建好以后测试一下,pipeline脚本如下:
|
||||
|
||||
pipeline {
|
||||
agent {
|
||||
docker{
|
||||
image 'alpine-cicd:latest'
|
||||
args '-v /var/run/docker.sock:/var/run/docker.sock'
|
||||
}
|
||||
}
|
||||
|
||||
stages('build') {
|
||||
stage('测试'){
|
||||
steps {
|
||||
sh """
|
||||
docker version
|
||||
mvn --version
|
||||
git --version
|
||||
"""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
流水线正常执行表示镜像构建成功。
|
||||
|
||||
一个统一的镜像构建好了,如果遇到使用多个agent的情况,也可以使用这个统一的镜像,或者分别构建特定工具的镜像,这里不再进行演示,有兴趣的可以自己尝试一下。
|
||||
|
||||
声明式语法
|
||||
|
||||
构建完基础镜像,就可以正式学习如何使用pipeline与docker插件集成了。在前面章节介绍了使用vm作为Jenkins的slave节点的实践示例,使用docker容器作为Jenkins的slave节点与使用vm有很多相同的地方,比如使用共享存储保存构建后的产物,使用pipeline方法实现对私有仓库的认证等,学会了前面章节的内容,对于本章的学习还是相对较简单的。
|
||||
|
||||
Agent代理
|
||||
|
||||
首先来回顾一下使用docker容器作为agent代理的基本语法。
|
||||
|
||||
使用一个全局agent
|
||||
|
||||
pipeline {
|
||||
agent { docker 'maven:3-alpine' }
|
||||
stages {
|
||||
stage('build') {
|
||||
steps {
|
||||
sh 'mvn clean install'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
使用stage级别的agent:
|
||||
|
||||
pipeline {
|
||||
agent none
|
||||
stages {
|
||||
stage('Build') {
|
||||
agent { docker 'maven:3-alpine' }
|
||||
steps {
|
||||
echo 'Hello, Maven'
|
||||
sh 'mvn --version'
|
||||
}
|
||||
}
|
||||
stage('Test') {
|
||||
agent { docker 'openjdk:8-jre' }
|
||||
steps {
|
||||
echo 'Hello, JDK'
|
||||
sh 'java -version'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
而在学习了脚本式语法以及docker pipeline插件语法后,使用多agent也可以这样写:在不同的代理节点启动不同的容器。
|
||||
|
||||
pipeline {
|
||||
agent none
|
||||
stages {
|
||||
stage('Build') {
|
||||
agent { node { label ‘slave1’ } }
|
||||
|
||||
steps {
|
||||
script{
|
||||
docker.image('maven').inside(){
|
||||
sh 'hostname'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('Test') {
|
||||
agent { node { label ‘slave2’ } }
|
||||
steps {
|
||||
script{
|
||||
docker.image('nginx').inside(){
|
||||
sh 'hostname'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
该方式用的情况可能不多,但在这里还是有必要说一下:
|
||||
|
||||
使用全局agent
|
||||
|
||||
使用docker容器作为agent只要修改agent指令的配置即可,配置比较简单,示例如下:
|
||||
|
||||
pipeline {
|
||||
agent {
|
||||
docker{
|
||||
image 'alpine-cicd:latest'
|
||||
args '-v /var/run/docker.sock:/var/run/docker.sock -v /data/:/data/ -v /root/.docker/config.json:/root/.docker/config.json -v /root/.m2:/root/.m2'
|
||||
}
|
||||
}
|
||||
|
||||
environment {
|
||||
project_name = 'fw-base-nop'
|
||||
jar_name = 'fw-base-nop.jar'
|
||||
registry_url = '192.168.176.155'
|
||||
project_group = 'base'
|
||||
}
|
||||
|
||||
stages('build') {
|
||||
stage('代码拉取并打包'){
|
||||
steps {
|
||||
checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: 'c33d60bd-67c6-4182-b52c-d7aeebfab772', url: 'http://192.168.176.154/root/base-nop.git']]])
|
||||
echo "开始打包 "
|
||||
sh "cd $project_name && mvn clean install -DskipTests -Denv=beta"
|
||||
}
|
||||
}
|
||||
|
||||
stage('构建镜像'){
|
||||
steps {
|
||||
script{
|
||||
jar_file=sh(returnStdout: true, script: "find ${WORKSPACE} ./ -name $jar_name |head -1").trim()
|
||||
}
|
||||
sh """
|
||||
cp $jar_file /data/$project_group/$project_name/
|
||||
"""
|
||||
}
|
||||
}
|
||||
stage('上传镜像'){
|
||||
steps {
|
||||
script {
|
||||
docker.withRegistry('http://192.168.176.155', 'auth_harbor') {
|
||||
def customImage=docker.build("${registry_url}/${project_group}/${project_name}:${env.BUILD_ID}","/data/${project_group}/${project_name}/")
|
||||
customImage.push()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
post {
|
||||
always {
|
||||
script{
|
||||
sh 'docker rmi -f $registry_url/$project_group/$project_name:${BUILD_ID}'
|
||||
|
||||
if (currentBuild.currentResult == "FAILURE" || currentBuild.currentResult == "UNSTABLE" ){
|
||||
emailext (
|
||||
subject: "'${env.JOB_NAME} [${env.BUILD_NUMBER}]' 构建结果",
|
||||
body: """
|
||||
详情:\n<br>
|
||||
Jenkins 构建 ${currentBuild.result} '\n'<br>
|
||||
项目名称 :${env.JOB_NAME} "\n"
|
||||
项目构建id:${env.BUILD_NUMBER} "\n"
|
||||
URL :${env.BUILD_URL} \n
|
||||
构建日志:${env.BUILD_URL}console
|
||||
|
||||
""",
|
||||
to: "[email protected]",
|
||||
recipientProviders: [[$class: 'CulpritsRecipientProvider'],
|
||||
[$class: 'DevelopersRecipientProvider'],
|
||||
[$class: 'RequesterRecipientProvider']]
|
||||
)
|
||||
}else{
|
||||
echo "构建成功"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
|
||||
相对于使用vm作为agent代理,流水线核心代码没变,只是修改了agent的配置。
|
||||
|
||||
image 参数指定了在上一个小节中构建的镜像名称。
|
||||
|
||||
args 定义了启动镜像的参数:
|
||||
|
||||
|
||||
-v /var/run/docker.sock:/var/run/docker.sock用于挂载宿主机docker服务的sock文件到容器中,如果不挂载该文件使用docker时会提示docker进程没有启动,并无法进行镜像构建工作;
|
||||
|
||||
-v /data/:/data/用于将共享存储的data目录挂载到容器中,该目录包含了项目组文件夹,项目文件夹以及dockerfile文件(目录结构根据自己实际情况自定义),代码编译的产物会放到指定的项目文件夹中,构建镜像时会使用项目文件夹路径作为上下文路径进行构建;
|
||||
|
||||
-v /root/.docker/config.json:/root/.docker/config.json 用于将宿主机上docker进程向私有仓库认证的文件挂载到容器内指定的文件上,这里需要注意的使用宿主机当前用户(需要对docker命令有操作权限),至于该容器在构建镜像时默认是root用户;
|
||||
|
||||
流水线执行时,默认会在jenkins的所有节点中选择一台节点,并启动容器,虽然是在容器中进行镜像构建与推送到私有仓库操作,但使用的各种配置大多都依赖于宿主机。
|
||||
|
||||
使用多个agent
|
||||
|
||||
使用多个agent与使用全局agent没有明显的区别,只是将最顶层的agent关键字的值变为none,在不同的步骤通过agent关键字指定不同的镜像即可。
|
||||
|
||||
如下示例:
|
||||
|
||||
pipeline {
|
||||
agent none
|
||||
|
||||
environment {
|
||||
project_name = 'fw-base-nop'
|
||||
jar_name = 'fw-base-nop.jar'
|
||||
registry_url = '192.168.176.155'
|
||||
project_group = 'base'
|
||||
}
|
||||
|
||||
stages('build') {
|
||||
stage('代码拉取并打包'){
|
||||
agent {
|
||||
docker {
|
||||
image 'alpine-cicd:latest'
|
||||
args '-v /root/.m2:/root/.m2'
|
||||
}
|
||||
}
|
||||
steps {
|
||||
checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: 'c33d60bd-67c6-4182-b52c-d7aeebfab772', url: 'http://192.168.176.154/root/base-nop.git']]])
|
||||
echo "开始打包 "
|
||||
sh "cd $project_name && mvn clean install -DskipTests -Denv=beta"
|
||||
}
|
||||
}
|
||||
|
||||
stage('构建镜像'){
|
||||
agent {
|
||||
docker {
|
||||
image 'alpine-cicd:latest'
|
||||
args ' -v /var/run/docker.sock:/var/run/docker.sock -v /data/:/data/'
|
||||
}
|
||||
|
||||
}
|
||||
steps {
|
||||
script{
|
||||
jar_file=sh(returnStdout: true, script: "find ${WORKSPACE} ./ -name $jar_name |head -1").trim()
|
||||
}
|
||||
sh """
|
||||
cp $jar_file /data/$project_group/$project_name/
|
||||
"""
|
||||
}
|
||||
}
|
||||
stage('上传镜像'){
|
||||
agent {
|
||||
docker {
|
||||
image 'alpine-cicd:latest'
|
||||
args ' -v /data/:/data/ -v /var/run/docker.sock:/var/run/docker.sock‘
|
||||
}
|
||||
}
|
||||
steps {
|
||||
script {
|
||||
docker.withRegistry('http://192.168.176.155', 'auth_harbor') {
|
||||
def customImage=docker.build("${registry_url}/${project_group}/${project_name}:${env.BUILD_ID}","/data/${project_group}/${project_name}/")
|
||||
customImage.push()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
post {
|
||||
always {
|
||||
node (null){
|
||||
script{
|
||||
sh 'docker rmi -f $registry_url/$project_group/$project_name:${BUILD_ID}'
|
||||
|
||||
if (currentBuild.currentResult == "ABORTED" || currentBuild.currentResult == "FAILURE" || currentBuild.currentResult == "UNSTABLE" ){
|
||||
emailext (
|
||||
subject: "'${env.JOB_NAME} [${env.BUILD_NUMBER}]' 构建结果",
|
||||
body: """
|
||||
详情:<br>
|
||||
Jenkins 构建 ${currentBuild.result} <br>
|
||||
项目名称 :${env.JOB_NAME} <br>
|
||||
项目构建id:${env.BUILD_NUMBER} <br>
|
||||
URL :${env.BUILD_URL} <br>
|
||||
构建日志:${env.BUILD_URL}console
|
||||
|
||||
""",
|
||||
to: "[email protected]",
|
||||
recipientProviders: [[$class: 'CulpritsRecipientProvider'],
|
||||
[$class: 'DevelopersRecipientProvider'],
|
||||
[$class: 'RequesterRecipientProvider']]
|
||||
)
|
||||
}else{
|
||||
echo "构建成功"
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
|
||||
有一点与使用全局agent不同,在最后post指令中,需要单独设定node节点。此处不能用agent指令,但可以通过node('slave-label')的方式指定一个节点,slave的标签也可以设定为null,此时默认会自动选择一个slave节点进行post下的操作。这种情况如果要部署该镜像,可直接通过宿主机的脚本进行部署。
|
||||
|
||||
另一点需要注意的是,代码编译与镜像构建要挂载相同的数据目录来使用同一个构建产物。
|
||||
|
||||
|
||||
私有仓库认证
|
||||
|
||||
前面的章节中,在声明式脚本中对私有仓库的认证介绍了使用withCredentials()与withRegistry<br/>(),对于在声明式语法中使用docker容器作为agent对私有仓库进行认证时,也可以通过在agent指令中添加对私有仓库认证的附加参数,比如:
|
||||
|
||||
docker {
|
||||
image '192.168.176.155/library/maven:v2'
|
||||
args '-v $HOME/.m2:/root/.m2 -v /data/fw-base-nop:/data -v /var/run/docker.sock:/var/run/docker.sock'
|
||||
registryUrl 'http://192.168.176.155'
|
||||
registryCredentialsId 'auth_harbor'
|
||||
}
|
||||
|
||||
|
||||
通过在容器启动参数中加入registryUrl和registryCredentialsId参数,在容器启动后对私有仓库认证。除了可以在拉取工作镜像时使用,也可以在构建应用镜像后推送私到私有仓库时使用。
|
||||
|
||||
完整版的配置如下:
|
||||
|
||||
pipeline{
|
||||
agent {
|
||||
docker {
|
||||
image '192.168.176.155/library/jenkins-slave'
|
||||
args '-v $HOME/.m2:/root/.m2 -v /data/fw-base-nop:/data -v /var/run/docker.sock:/var/run/docker.sock'
|
||||
registryUrl 'http://192.168.176.155'
|
||||
registryCredentialsId 'auth_harbor'
|
||||
}
|
||||
}
|
||||
stages {
|
||||
stage('clone') {
|
||||
steps('clone code'){
|
||||
checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, userRemoteConfigs: [[credentialsId: 'c33d60bd-67c6-4182-b52c-d7aeebfab772', url: 'http://192.168.176.154/root/base-nop.git']]])
|
||||
script {
|
||||
imageTag = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage("mvn") {
|
||||
steps('build'){
|
||||
sh """
|
||||
cd fw-base-nop && mvn clean install -DskipTests -Denv=beta
|
||||
cp ${WORKSPACE}/fw-base-nop/target/fw-base-nop.jar /data/
|
||||
hostname && pwd
|
||||
"""
|
||||
}
|
||||
}
|
||||
stage("build and push to registry") {
|
||||
environment {
|
||||
dockerfile = '/data/Dockerfile'
|
||||
}
|
||||
steps('push'){
|
||||
script {
|
||||
docker.withRegistry('http://192.168.176.155', 'auth_harbor') {
|
||||
def customImage=docker.build("${registry_url}/${project_group}/${project_name}:${env.BUILD_ID}","/data/${project_group}/${project_name}/")
|
||||
customImage.push()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
有关在声明式语法中使用Docker插件的内容就简单的介绍到这里,下面重点介绍一下在脚本式语法中使用Docker pipeline插件。
|
||||
|
||||
脚本式语法
|
||||
|
||||
使用脚本式语法动态生成slave工作节点,开始之前首先来回顾一下使用docker pipeline插件启动容器的语法,如下所示:
|
||||
|
||||
node {
|
||||
docker.image('maven:3.3.3-jdk-8').inside {
|
||||
git '…your-sources…'
|
||||
sh 'mvn -B clean install'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
通过image属性指定一个容器,然后通过inside属性启动一个容器。
|
||||
|
||||
下面分多个步骤介绍该插件的使用:
|
||||
|
||||
|
||||
版本一:在容器内进行代码编译,在宿主机进行镜像构建并推送至私有仓库。
|
||||
版本二:在容器内编译构建并上传到私有仓库
|
||||
|
||||
|
||||
版本一
|
||||
|
||||
使用脚本式语法相对于使用声明式语法在指定的slave节点上启动容器时相对简单的多,比如在jenkins-slave1主机上启动容器。
|
||||
|
||||
node('jenkins-slave1') {
|
||||
docker.image('192.168.176.155/library/maven:v2').inside('-v $HOME/.m2:/root/.m2') {
|
||||
stage('checkout'){
|
||||
checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, userRemoteConfigs: [[credentialsId: 'c33d60bd-67c6-4182-b52c-d7aeebfab772', url: 'http://192.168.176.154/root/base-nop.git']]])
|
||||
}
|
||||
stage('编译'){
|
||||
sh 'cd fw-base-nop && mvn clean install -DskipTests -Denv=beta'
|
||||
sh 'hostname && pwd'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
|
||||
通过在node()中指定slave节点的标签,可以设置在指定的节点上启动容器进行工作。
|
||||
|
||||
该示例会在jenkins-slave1节点上使用inside属性启动一个maven容器用来进行代码编译工作,需要注意的是,即便不传参数,也要保证inside属性存在。
|
||||
|
||||
|
||||
执行日志如下:
|
||||
|
||||
|
||||
|
||||
由上面的jenkins执行结果可以看到,在job执行后,如果本地没有设定的maven镜像,会先拉取maven镜像,并将该镜像启动。启动的方法和参数可以通过上面的截图看到。
|
||||
|
||||
执行结果如下:-
|
||||
|
||||
|
||||
由上图可看到,通过容器拉取代码并编译,编译完成以后容器会自动销毁,所以编译后的产物也会随之消失。此时,在上一章节中使用的共享存储就到了彰显自我价值的时候了,只需要将编译好的包保存到共享存储上即可,一个比较简单的方法,通过在宿主机挂载共享存储目录并将该目录映射到容器中指定的目录下。
|
||||
|
||||
如下示例:
|
||||
|
||||
node('jenkins-slave1') {
|
||||
docker.image('192.168.176.155/library/maven:v2').inside('-v $HOME/.m2:/root/.m2 -v /data/base/fw-base-nop:/data') {
|
||||
stage('checkout'){
|
||||
checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, userRemoteConfigs: [[credentialsId: 'c33d60bd-67c6-4182-b52c-d7aeebfab772', url: 'http://192.168.176.154/root/base-nop.git']]])
|
||||
}
|
||||
stage('编译'){
|
||||
sh 'cd fw-base-nop && mvn clean install -DskipTests -Denv=beta'
|
||||
sh 'hostname && pwd'
|
||||
}
|
||||
stage('拷贝jar包'){
|
||||
sh 'cp ${WORKSPACE}/fw-base-nop/target/*.jar /data/fw-base-nop.jar'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
这里模拟/data/目录为共享存储挂载到宿主机的目录。编译好后将构建产物拷贝到容器的/data目录就相当于拷贝到宿主机/data/base/fw-base-nop目录下,然后在该目录事先创建Dockerfile文件就可以构建镜像了。
|
||||
|
||||
而在宿主机进行镜像构建与推送到私有仓库操作,直接参考上一章节相关的操作即可。
|
||||
|
||||
node('jenkins-slave1') {
|
||||
docker.image('192.168.176.155/library/maven:v2').inside('-v $HOME/.m2:/root/.m2 -v /data/base/fw-base-nop:/data') {
|
||||
stage('checkout'){
|
||||
checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: 'c33d60bd-67c6-4182-b52c-d7aeebfab772', url: 'http://192.168.176.154/root/base-nop.git']]])
|
||||
}
|
||||
stage('maven'){
|
||||
sh 'cd fw-base-nop && mvn clean install -DskipTests -Denv=beta'
|
||||
sh 'hostname && pwd'
|
||||
}
|
||||
stage('拷贝jar包'){
|
||||
sh 'cp ${WORKSPACE}/xxx-management/xxx-admin/target/*.jar /data/admin.jar'
|
||||
}
|
||||
|
||||
}
|
||||
stage('build and push'){
|
||||
docker.withRegistry('http://192.168.176.155','auth_harbor') {
|
||||
def dockerfile = '/data/bsse/fw-base-nop/Dockerfile'
|
||||
def customImage = docker.build("192.168.176.155/library/admin:v1", "-f ${dockerfile} /data/bsse/fw-base-nop/.")
|
||||
customImage.push()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
构建效果图这里不展示了,有兴趣的可以自己进行扩展并测试。如果你的slave节点配置了私有仓库认证,这里你可以不用withRegistry方法。
|
||||
|
||||
版本二
|
||||
|
||||
上面已经完成了在容器内的代码编译并在宿主机构建镜像。下面只需要在此基础上将镜像构建操作放到容器中即可。
|
||||
|
||||
直接编写pipeline script:
|
||||
|
||||
node ('jenkins-slave1') {
|
||||
docker.withRegistry('http://192.168.176.155','auth_harbor') {
|
||||
docker.image('alpine-cicd:latest').inside('-v $HOME/.m2:/root/.m2 -v /data/base/fw-base-nop:/data -v /var/run/docker.sock:/var/run/docker.sock) {
|
||||
stage('checkout'){
|
||||
checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: 'c33d60bd-67c6-4182-b52c-d7aeebfab772', url: 'http://192.168.176.154/root/base-nop.git']]])
|
||||
script {
|
||||
imageTag = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim()
|
||||
}
|
||||
}
|
||||
stage('打包'){
|
||||
sh 'cd fw-base-nop && mvn clean install -DskipTests -Denv=beta'
|
||||
|
||||
sh 'hostname && pwd'
|
||||
}
|
||||
stage('copy jar file'){
|
||||
sh 'cp ${WORKSPACE}/fw-base-nop/target/fw-base-nop.jar /data/'
|
||||
}
|
||||
stage('build and push'){
|
||||
def dockerfile = '/data/Dockerfile'
|
||||
def customImage = docker.build("192.168.176.155/library/fw-base-nop:${imageTag}", "-f ${dockerfile} /data/.")
|
||||
customImage.push()
|
||||
}
|
||||
|
||||
stage('delete image'){
|
||||
sh "docker rmi 192.168.176.155/library/fw-base-nop:${imageTag}"
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
说明
|
||||
|
||||
|
||||
在版本一中我将withRegistry方法放到了指定的stage下,还可以将该方法放到pipeline脚本的最顶层。
|
||||
|
||||
script 包含的命令用来执行命令获取提交的代码的commit 的short id作为镜像的版本号。
|
||||
|
||||
脚本最后不要忘记加上删除刚刚构建的镜像的操作,这里再说说明一下该操作是因为构建的镜像会存在宿主机上,而且每个镜像的tag不一样,如果构建次数多了会导致镜像数量庞大,占用空间;并且该镜像已经上传到私有仓库,这里就没必要在保存在宿主机上了。
|
||||
|
||||
有一点需要注意,就是jenkins slave节点的docker版本问题,本例docker版本为docker-ce-18.06,非ce版本可能会出现如下问题:
|
||||
|
||||
/usr/bin/docker: .: line 2: can't open '/etc/sysconfig/docker'
|
||||
|
||||
|
||||
或者
|
||||
|
||||
|
||||
You don't have either docker-client or docker-client-latest installed. Please install either one and retry.
|
||||
|
||||
|
||||
|
||||
执行结果如下:-
|
||||
|
||||
|
||||
|
||||
|
||||
使用多个agent代理
|
||||
|
||||
在脚本式语法中使用多个agent的配置比较灵活,比如:
|
||||
|
||||
示例一:在同一个node节点上启动多个容器执行流水线。
|
||||
|
||||
node ('jenkins-slave1') {
|
||||
docker.withRegistry('http://192.168.176.155','auth_harbor') {
|
||||
docker.image('xxx').inside('-v $HOME/.m2:/root/.m2 -v /data/base/fw-base-nop:/data ') {
|
||||
stage('checkout'){
|
||||
checkout(xxxx)
|
||||
script {
|
||||
imageTag = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim()
|
||||
}
|
||||
}
|
||||
stage('编译'){
|
||||
sh 'xxx'
|
||||
}
|
||||
stage('拷贝jar包'){
|
||||
sh 'cp ${WORKSPACE}/fw-base-nop/target/fw-base-nop.jar /data/'
|
||||
}
|
||||
}
|
||||
|
||||
docker.image('xxx').inside('-v /data/base/fw-base-nop:/data -v /var/run/docker.sock:/var/run/docker.sock') {
|
||||
stage('构建') {
|
||||
def dockerfile = '/data/Dockerfile'
|
||||
def customImage = docker.build("192.168.176.155/library/fw-base-nop:${imageTag}", "-f ${dockerfile} /data/.")
|
||||
customImage.push()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
示例二:在不同的node节点上进行不同的操作,比如在slave1节点上进行代码编译工作,在slave2节点上进行镜像构建与推送到私有仓库操作(这种一般使用共享存储来共享工作目录)。
|
||||
|
||||
node {
|
||||
|
||||
stage('working in slave1'){
|
||||
node('jenkins-slave1'){
|
||||
docker.image('alpine-cicd:latest').inside('-v $HOME/.m2:/root/.m2 -v /data/base/fw-base-nop:/data -v /var/run/docker.sock:/var/run/docker.sock') {
|
||||
stage('checkout in slave1'){
|
||||
checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: 'c33d60bd-67c6-4182-b52c-d7aeebfab772', url: 'http://192.168.176.154/root/base-nop.git']]])
|
||||
script {
|
||||
imageTag = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim()
|
||||
}
|
||||
}
|
||||
|
||||
stage('build in slave1'){
|
||||
sh 'cd fw-base-nop && mvn clean install -DskipTests -Denv=beta'
|
||||
|
||||
sh 'hostname && pwd'
|
||||
}
|
||||
|
||||
stage('copy jar file in slave1'){
|
||||
sh 'cp ${WORKSPACE}/fw-base-nop/target/fw-base-nop.jar /data/'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('working in slave169'){
|
||||
node('jenkins-slave169'){
|
||||
stage('build and push in slave169'){
|
||||
docker.withRegistry('http://192.168.176.155','auth_harbor') {
|
||||
def dockerfile = '/data/base/fw-base-nop/Dockerfile'
|
||||
def customImage = docker.build("192.168.176.155/library/fw-base-nop:${imageTag}", "-f ${dockerfile} /data/base/fw-base-nop/.")
|
||||
customImage.push()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
此示例分别在两台机器上进行代码编译和镜像构建,需要注意的是,两个slave节点用到的共同的目录(比如maven私有库存放目录,构建产物的存放目录,dockerfile的存放目录等)需要使用共享存储挂载,才能保证数据的一致性。
|
||||
|
||||
构建效果这里就不贴图了,执行结果大致都相同。
|
||||
|
||||
Pipeline script from SCM
|
||||
|
||||
除了上面使用 pipeline script的方式直接在jenkins项目里编写pipeline脚本外,还可以将写好pipeline脚本放到源代码仓库里,即Pipeline script from SCM。
|
||||
|
||||
将上面pipeline脚本写到文件Jenkinsfile中,然后提交到源代码仓库,然后在jenkins项目中通过选择Pipeline script from SCM,然后配置该代码仓库的地址,用户名和密码构建即可。-
|
||||
-
|
||||
上面配置保存即可。使用这种方式构建项目,官方建议使用声明式语法编写脚本。
|
||||
|
||||
异常处理
|
||||
|
||||
有关异常处理的内容,在上一章节以及pipeline实践章节中已经有过介绍,在接下来的内容只要将其拿过来直接使用即可。
|
||||
|
||||
首先回顾一下在脚本式语法中使用try/catch/finally指令的语法:
|
||||
|
||||
node ('jenkins-slave1') {
|
||||
try {
|
||||
stage('exec command'){
|
||||
sh 'hostnamae && pwd'
|
||||
currentBuild.currentresult = 'SUCCESS'
|
||||
}
|
||||
}catch(e) {
|
||||
currentBuild.currentresult = 'FAILURE'
|
||||
def errorMsg = "================== 错误信息 START ===================\n"
|
||||
errorMsg += "${e.toString()}|${e.message}\n"
|
||||
errorMsg += "==================== 错误信息 END ======================"
|
||||
echo "errrormsg is ${errorMsg}"
|
||||
}
|
||||
|
||||
finally{
|
||||
if (currentBuild.result == 'SUCCESS') {
|
||||
echo "---currentBuild.currentresult is:${currentBuild.currentresult}---"
|
||||
}
|
||||
else {
|
||||
echo "---currentBuild.currentresult is:${currentBuild.currentresult}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
说明
|
||||
|
||||
|
||||
try 指令包含要执行的任务,try指令后面必须跟随catch或者finally指令,catch和finally可以不用同时存在,但当有try指令时,这俩指令必须有一个存在。
|
||||
该示例根据job执行的结果分别做出不同的操作。
|
||||
|
||||
|
||||
根据上面的语法,就可以将上面持续交付的代码套进这个模板里,如下所示:
|
||||
|
||||
node ('jenkins-slave1') {
|
||||
|
||||
docker.withRegistry('http://192.168.176.155','auth_harbor') {
|
||||
docker.image('192.168.176.155/library/jenkins-slave').inside('-v $HOME/.m2:/root/.m2 -v /data/fw-base-nop:/data -v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker') {
|
||||
stage('checkout'){
|
||||
checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, userRemoteConfigs: [[credentialsId: 'c33d60bd-67c6-4182-b52c-d7aeebfab772', url: 'http://192.168.176.154/root/base-nop.git']]])
|
||||
script {
|
||||
imageTag = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim()
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
stage('maven'){
|
||||
sh 'cd fw-base-nop && mvn clean install -DskipTests -Denv=beta'
|
||||
sh 'hostname && pwd'
|
||||
currentBuild.currentresult = 'SUCCESS'
|
||||
}
|
||||
}catch(e) {
|
||||
currentBuild.currentresult = 'FAILURE'
|
||||
def errorMsg = "================= 错误信息 START ======================\n"
|
||||
errorMsg += "${e.toString()}|${e.message}\n"
|
||||
errorMsg += "=================== 错误信息 END ======================"
|
||||
echo "${errorMsg}"
|
||||
emailext (
|
||||
subject: "'${env.JOB_NAME} [${env.BUILD_NUMBER}]' 构建异常",
|
||||
body: """
|
||||
详情:\n
|
||||
failure: Job ${env.JOB_NAME} [${env.BUILD_NUMBER}] \n
|
||||
状态:${env.JOB_NAME} jenkins 构建异常 \n
|
||||
URL :${env.BUILD_URL} \n
|
||||
项目名称 :${env.JOB_NAME} \n
|
||||
项目构建id:${env.BUILD_NUMBER} \n
|
||||
错误信息: ${errorMsg} \n
|
||||
|
||||
""",
|
||||
to: "[email protected]",
|
||||
recipientProviders: [[$class: 'DevelopersRecipientProvider']]
|
||||
)
|
||||
}
|
||||
if (currentBuild.currentresult == 'SUCCESS') {
|
||||
echo "---currentBuild.result is:${currentBuild.result}------"
|
||||
stage('拷贝拷贝jar包'){
|
||||
sh 'cp ${WORKSPACE}/fw-base-nop/target/fw-base-nop.jar /data/'
|
||||
}
|
||||
stage('build and push'){
|
||||
def dockerfile = '/data/Dockerfile'
|
||||
def customImage = docker.build("192.168.176.155/library/fw-base-nop:${imageTag}", "-f ${dockerfile} /data/.")
|
||||
customImage.push()
|
||||
}
|
||||
}else {
|
||||
echo "---currentBuild.currentresult is:${currentBuild.currentresult}"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
上面使用了emailext方法用于在指定的步骤发送邮件,可以在多个步骤设置不同的邮件模板,用于快速定位异常问题。
|
||||
|
||||
currentBuild为默认自带的全局变量,该变量下有多个方法,可以通过在${YOUR_JENKINS_URL}/pipeline-syntax/globals#env中获取到。
|
||||
|
||||
对于流水线中的所有核心步骤都可以通过try指令去捕捉流水线各阶段执行的结果,配置比较灵活,此示例仅做演示。
|
||||
|
||||
有关pipeline与docker插件集成的进行持续交付内容到这里就结束了。在以后的章节中会详细介绍如何进行持续部署,此处先略过。在下一节的内容会介绍一下jenkins与kubernetes的集成配置。
|
||||
|
||||
|
||||
|
||||
|
410
专栏/Jenkins持续交付和持续部署/14.搞定不同环境下的Jenkins与Kubernetes集群连接配置.md
Normal file
410
专栏/Jenkins持续交付和持续部署/14.搞定不同环境下的Jenkins与Kubernetes集群连接配置.md
Normal file
@@ -0,0 +1,410 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
14.搞定不同环境下的Jenkins与Kubernetes集群连接配置
|
||||
在前面的章节中,简单的介绍了使用Jenkins与Docker集成的一些基本语法以及实现持续交付和持续部署的配置。在Jenkins中除了可以与docker集成外,还可以与容器编排工具kubernetes集成。
|
||||
|
||||
Jenkins使用kubernetes插件主要用于完成两方面的工作:一是用于在kubernetes集群内动态生成一个pod作为Jenkins 的slave节点,提供流水线执行的工作环境;二是用于将应用代码持续部署到kubernetes集群中。
|
||||
|
||||
基于上面提到的这两方面的用途,在剩下的章节会详细介绍Jenkins与kubernetes的集成配置与使用方法。
|
||||
|
||||
同docker pipeline一样,在Jenkins里集成kubernetes也是依赖于插件的,所以在介绍如何配置与使用jenkins集成kubernetes之前需要先安装插件:
|
||||
|
||||
|
||||
Kubernetes plugin
|
||||
Kubernetes CLI
|
||||
Kubernetes Continous Deploy
|
||||
|
||||
|
||||
上面列出的插件,第一个用于在kubernetes集群中动态生成jenkins slave节点,后两个插件用于通过不同的方式持续部署代码到kubernetes集群。无论哪种插件,使用前都要先保证jenkins能够连接到kubernetes集群,所以本章节就从不同环境下的jenkins连接kubernetes集群的配置说起。
|
||||
|
||||
配置Jenkins连接Kubernetes
|
||||
|
||||
使用docker pipeline做持续交付的时候,默认直接使用的宿主机的docker进程。而Jenkins与kubernetes的集成,主要是通过调用Kubernetes的API去kubernetes集群中进行工作的。大多数公司在安装kubernetes集群配置apiserver服务时使用了证书,所以在配置jenkins连接kubernetes集群时,需要根据kubernetes的配置文件生成一系列证书以及key,并将证书上传到Jenkins用来对apiserver进行认证。
|
||||
|
||||
下面针对部署在kubernetes集群环境下的Jenkins和非kubernetes集群环境下的jenkins连接kubernetes集群的配置进行详细介绍。
|
||||
|
||||
部署在非kubernetes集群内Jenkins连接kubernetes配置
|
||||
|
||||
安装好插件以后,进入Jenkins首页,点击菜单”Manage Jenkins(系统管理)-–> Configure System(系统设置)” 在跳转到的界面中,到最底部,点击” Add a new cloud(新建一个云)–> kubernetes“。
|
||||
|
||||
如下所示:
|
||||
|
||||
|
||||
|
||||
其中:
|
||||
|
||||
名称:这里用于填写要添加的这个云(cloud)的名称,默认为”kubernetes”,如果不想用这个可以自定义。在编写pipeline的时候会用到。
|
||||
|
||||
kubernetes 地址:用于填写kubernetes集群的地址,做了多master集群高可用的环境直接写vip地址加端口;只有单个master的环境直接写master加端口地址即可。
|
||||
|
||||
Kubernetes 服务证书 key:用于填写与kubernetes集群认证的证书内容。
|
||||
|
||||
Kubernetes 命名空间:用于填写调用kubernetes时生成的pod工作的namespace。
|
||||
|
||||
Credentials(凭据):用于连接kubernetes的凭证。
|
||||
|
||||
Jenkins 地址:Jenkins的连接地址。
|
||||
|
||||
了解了基本配置的参数说明,下面主要说一下”Kubernetes 服务证书 key“和Credentials(凭据)配置。
|
||||
|
||||
kubernetes集群安装的时候生成了一系列证书以及key,并且在配置kubernetes中kubectl客户端命令权限的时候,根据这些证书以及key生成了一个kubeconfig文件,用于kubectl与集群通信,这个文件默认为/root/.kube/config文件,对集群有最高操作权限(如果给了cluster-admin权限)。
|
||||
|
||||
Jenkins需要根据这个文件生成的证书与集群通信,所以我们在生产环境配置Jenkins连接kubernetes集群的时候,需要注意一下kubeconfig文件绑定的用户的权限,最好从新生成一个低权限的kubeconfig文件,而不要用kubectl命令使用的文件。
|
||||
|
||||
我这里为了测试方便,就先使用kubectl命令使用的kubeconfig文件,至于如何生成低权限的kubeconfig文件,这里不做介绍,有兴趣的可以私聊我。
|
||||
|
||||
配置证书key
|
||||
|
||||
首先看一下config文件。
|
||||
|
||||
[root@k8s_master1 ~]# cat /root/.kube/config
|
||||
apiVersion: v1
|
||||
clusters:
|
||||
- cluster:
|
||||
certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUR2akNDQXFhZ0F3SUJBZ0lVT0g4cDd6QXZaR3p4cGxUVy9xe......
|
||||
WRnVRNm9IcjZ0Yk0wa1NJVkhvN2JNQjRWOGZoUWk4WjlLS243ZTFsQWdaVWhyWGMzTzRqVS9xVHRWRQpSRXc9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
|
||||
server: https://192.168.176.156:6443
|
||||
name: kubernetes
|
||||
contexts:
|
||||
- context:
|
||||
cluster: kubernetes
|
||||
user: admin
|
||||
name: kubernetes
|
||||
current-context: kubernetes
|
||||
kind: Config
|
||||
preferences: {}
|
||||
users:
|
||||
- name: admin
|
||||
user:
|
||||
client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUQzVENDQXNXZ0F3SUJBZ0lVQmI1ZTJFaUk1WndSY1JVeFVyZ......
|
||||
Qk9DTzRBcEVzWXNOa084UVF2RTlwVEhpNlE0LzhLV0NtU0wyNgotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
|
||||
client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb2dJQkFBS0NBUUVBeDdjNkpRTFdQaC90REtjUDQrcDV5aCtWdWRCUmFacFJ2THM2MU1POGFZWFRCT09pCkJyZzFQb0laRzZEbXNBNnUyUStOVnlGOWg5RTQ3VVpFNjI5ND...... K3dGdUF2S29vRm9lRFZCS3I3NjdiTFA3ZTBibkVqS1ExRmNHVFFFQVU9Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg==
|
||||
|
||||
|
||||
获取文件中certificate-authority-data的内容并转化成base64 encoded文件
|
||||
|
||||
echo LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUR2akNDQXFhZ0F3SUJBZ0lVT0g4cDd6QXZaR3p4cGxUVy9xejRWRmxLQllRd0RRWUpLb1pJaHZjTkFRRUwKQlFBd1pURUxNQWtHQTFVRUJoTUNRMDR4RURBT0JnTlZCQWdUQjBKbG......
|
||||
WRnVRNm9IcjZ0Yk0wa1NJVkhvN2JNQjRWOGZoUWk4WjlLS243ZTFsQWdaVWhyWGMzTzRqVS9xVHRWRQpSRXc9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K |base64 -d > ca.crt
|
||||
|
||||
|
||||
将ca.crt的内容粘贴到kubernetes server certificate key(Kubernetes 服务证书 key)框里,如下图所示:
|
||||
|
||||
|
||||
|
||||
配置凭据
|
||||
|
||||
获取/root/.kube/config文件中client-certificate-data和client-key-data的内容并转化成base64 encoded文件
|
||||
|
||||
echo LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUQzVENDQXNXZ0F3SUJBZ0lVQmI1ZTJFaUk1WndSY1JVeFVyZ......
|
||||
Qk9DTzRBcEVzWXNOa084UVF2RTlwVEhpNlE0LzhLV0NtU0wyNgotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== |base64 -d >client.crt
|
||||
|
||||
//生成key
|
||||
echo LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb2dJQkFBS0NBUUVBeDdjNkpRTFdQaC90REtjUDQrc......
|
||||
K3dGdUF2S29vRm9lRFZCS3I3NjdiTFA3ZTBibkVqS1ExRmNHVFFFQVU9Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg== | base64 -d >client.key
|
||||
|
||||
|
||||
生成Client P12认证文件cert.pfx,并下载至本地
|
||||
|
||||
openssl pkcs12 -export -out cert.pfx -inkey client.key -in client.crt -certfile ca.crt
|
||||
Enter Export Password: //密码自定义,jenkins导入证书的时候需要用到
|
||||
Verifying - Enter Export Password:
|
||||
|
||||
|
||||
在jenkins凭据菜单添加凭据
|
||||
|
||||
|
||||
|
||||
如上图所指示,凭据类型为”Certiticate“,password为生成证书时的设置的密码。
|
||||
|
||||
添加完成后如下图所示,点击连接测试
|
||||
|
||||
连接成功如下所示:
|
||||
|
||||
|
||||
|
||||
到此,部署在非kubernetes集群内的Jenkins连接kubernetes配置就完成了。
|
||||
|
||||
部署在kubernetes集群内jenkins连接kubernetes配置
|
||||
|
||||
在前面介绍jenkins安装配置章节没有介绍如何在kubernetes集群中部署,所以本小节的开头就先介绍一下如何在kubernetes集群中部署jenkins。
|
||||
|
||||
kubernetes系统中使用资源对象来描述某个系统的期望状态以及对象的基本信息。对于部署某个应用或者服务使用最多的资源对象就是deployment,所以这里使用deployment来描述jenkins的配置并部署。
|
||||
|
||||
在kubernetes集群部署jenkins
|
||||
|
||||
首先需要创建一个服务账户(ServiceAccount),用来绑定对某一个命名空间的一系列kubernetes资源对象的操作权限。
|
||||
|
||||
如下jenkins-rbac.yaml文件:
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: jenkins
|
||||
namespace: kube-system
|
||||
|
||||
---
|
||||
kind: Role
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
metadata:
|
||||
name: jenkins
|
||||
namespace: kube-system
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["pods"]
|
||||
verbs: ["create", "delete", "get", "list", "patch", "update", "watch"]
|
||||
- apiGroups: [""]
|
||||
resources: ["pods/exec"]
|
||||
verbs: ["create", "delete", "get", "list", "patch", "update", "watch"]
|
||||
- apiGroups: [""]
|
||||
resources: ["pods/log"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
- apiGroups: [""]
|
||||
resources: ["secrets"]
|
||||
verbs: ["get"]
|
||||
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: jenkins
|
||||
namespace: kube-system
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: jenkins
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: jenkins
|
||||
|
||||
|
||||
然后创建deployment资源对象文件,来描述jenkins的一些基本配置信息,比如开放的端口,启动参数,使用的镜像等。
|
||||
|
||||
如下jenkins-deployment.yaml文件:
|
||||
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: jenkins
|
||||
labels:
|
||||
app-name: jenkins
|
||||
namespace: kube-system
|
||||
spec:
|
||||
replicas: 1
|
||||
strategy:
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
maxSurge: 1
|
||||
maxUnavailable: 0
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app-name: jenkins
|
||||
spec:
|
||||
serviceAccount: "jenkins"
|
||||
containers:
|
||||
- name: jenkins
|
||||
image: docker.io/jenkins:latest
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
name: web
|
||||
protocol: TCP
|
||||
- containerPort: 50000
|
||||
name: agent
|
||||
protocol: TCP
|
||||
volumeMounts:
|
||||
- name: jenkins-home
|
||||
mountPath: /var/jenkins_home
|
||||
env:
|
||||
- name: JAVA_OPTS
|
||||
value: "-Duser.timezone=Asia/Shanghai -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XX:MaxRAMFraction=1 -Dhudson.slaves.NodeProvisioner.MARGIN=50 -Dhudson.slaves.NodeProvisioner.MARGIN0=0.85"
|
||||
volumes:
|
||||
- name: jenkins-home
|
||||
persistentVolumeClaim:
|
||||
claimName: jenkins-public-pvc
|
||||
|
||||
|
||||
在资源对象的定义中使用了pv和pvc做持久化volume,所以还需要创建这两种资源对象定义的文件。
|
||||
|
||||
$ cat jenkins-pv.yaml
|
||||
|
||||
kind: PersistentVolume
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
labels:
|
||||
name: jenkins-public-pv
|
||||
name: jenkins-public-pv
|
||||
namespace: kube-system
|
||||
spec:
|
||||
capacity:
|
||||
storage: 100Gi
|
||||
accessModes:
|
||||
- ReadWriteMany
|
||||
persistentVolumeReclaimPolicy: Retain
|
||||
nfs:
|
||||
path: /data/nfs/jenkins-data
|
||||
server: 192.168.177.43
|
||||
|
||||
$ cat jenkins-pvc.yaml
|
||||
kind: PersistentVolumeClaim
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: jenkins-public-pvc
|
||||
namespace: kube-system
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteMany
|
||||
resources:
|
||||
requests:
|
||||
storage: 100Gi
|
||||
selector:
|
||||
matchLabels:
|
||||
name: jenkins-public-pv
|
||||
|
||||
|
||||
使用nfs作为共享存储,创建pvc使用pv,在实际工作中,一般使用storageclass替代pv作为动态存储。当然如果为了简单,你也可以用挂载nfs目录的方式,或者指定节点通过hostpath将工作目录挂在到宿主机,方法比较多,根据个人实际情况选择合适的存储即可。
|
||||
|
||||
部署service资源对象,相当于给运行jenkins服务的pod加了一个代理。
|
||||
|
||||
如下jenkins-service.yaml:
|
||||
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
labels:
|
||||
app-name: jenkins
|
||||
name: jenkins
|
||||
namespace: kube-system
|
||||
spec:
|
||||
ports:
|
||||
- port: 8080
|
||||
targetPort: 8080
|
||||
name: web
|
||||
- port: 50000
|
||||
targetPort: 50000
|
||||
name: agent
|
||||
selector:
|
||||
app-name: jenkins
|
||||
|
||||
|
||||
部署ingress服务,直接跳过service接管service代理的pod。
|
||||
|
||||
如下jenkins-ingress.yaml:
|
||||
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: jenkins
|
||||
namespace: kube-system
|
||||
spec:
|
||||
rules:
|
||||
- host: k8s-jenkins.com
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
backend:
|
||||
serviceName: jenkins
|
||||
servicePort: 8080
|
||||
|
||||
|
||||
创建好ingress后,需要添加部署ingress controllernode节点的ip和ingress中定义的host到本地hosts文件做解析。如果没有部署ingress服务,可以直接编辑上一步骤中创建service时的类型为nodePort,这样就可以通过访问nodeIP:nodePort的方式访问jenkins了。
|
||||
|
||||
部署完成后如下所示:
|
||||
|
||||
$ kubectl get pods,ingress,svc,deploy -n kube-system |grep jenkins
|
||||
|
||||
pod/jenkins-78f658b5d6-25qgl 1/1 Running 0 163d
|
||||
ingress.extensions/jenkins k8s-jenkins.com 80 10m
|
||||
|
||||
service/jenkins ClusterIP 10.254.179.172 <none> 8080/TCP,50000/TCP 198d
|
||||
|
||||
deployment.extensions/jenkins 1/1 1 1 198d
|
||||
|
||||
|
||||
|
||||
|
||||
部署好后的其他配置与在前面章节安装配置jenkins的方法一样。有关在kubernetes集群中安装jenkins服务的方法就介绍到这里。
|
||||
|
||||
配置jenkins连接kubernetes
|
||||
|
||||
部署在kubernetes集群内的jenkins配置连接kubernetes相对较简单,同样在系统配置页进行配置,不用生成上面的一系列证书,kubernetes的地址直接写kubernetes的FQDN即可。
|
||||
|
||||
如下所示:-
|
||||
|
||||
|
||||
说明
|
||||
|
||||
Name 处默认为 kubernetes,也可以修改为其他名称,这里不做重复介绍。
|
||||
|
||||
Kubernetes URL 处可以填写 https://kubernetes.default ,为Kubernetes Service 对应的 DNS 记录,通过该DNS记录可以解析成该 Service 的 Cluster IP。注意:这里也可以填写 https://kubernetes.default.svc.cluster.local 完整 DNS 记录,因为它要符合 service_name.namespace_name.svc.cluster_domain的命名方式。也可以直接填写外部 Kubernetes 的地址 https://ClusterIP:Ports (不推荐)。
|
||||
|
||||
Jenkins URL 处应该填写Jenkins的service地址和端口。比如我的:http://jenkins.kube-system.svc.cluster.local:8080 也可以写成http://jenkins.kube-system:8080, 表示jenkins的service名称和所在的namespace名称,同kubernetes url设置类似,也是使用Jenkins Service 对应的 DNS 记录以及端口。如果暴露服务的方式为nodeport,也可以用http://NodeIP:NodePort(本示例没配置)的方式,根据自己实际情况修改即可。
|
||||
|
||||
Credentials :同属于一个集群环境,对于集群认证的过程就不需要了,认证凭证也就不用填写了。
|
||||
|
||||
Jenkins虽然能连接kubernetes了,但是通过kubernetes还不能生成动态的slave代理,因为slave agent(jnlp-agent)在启动的时候会通过50000(默认)端口与jenkins master进行通信。Jenkins这个端口默认是关闭的,所以还需要开启这个端口。
|
||||
|
||||
Manage Jenkins(系统管理)-–>(Configure global Security)全局安全配置-–> Agents(代理)
|
||||
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
这里指定的端口是jnlp-agent连接jenkins-master使用的端口。
|
||||
|
||||
如果Jenkins-master只是在Docker容器(没有使用容器编排系统)中启动的一定要记得将这个端口暴露到外部,不然jenkins-master会不知道slave是否已经启动,会反复去创建pod直到到超过重试次数。
|
||||
|
||||
该端口的默认值是50000,如果要修改为其他端口,需要修改在创建jenkins”云”时对应的Jenkins Tunnel(通道)参数的配置,如果使用50000端口,这里可以不用填写,如果换成别的端口,这里需要单独设定,最好是jenkins_url:port的形式。
|
||||
|
||||
如果不开启代理端口,Jenkins通过kubernetes动态生成slave节点的时候,jenkins后台会报如下错误,并且pod会不断的生成和删除。
|
||||
|
||||
|
||||
|
||||
到这里,jenkins与kubernetes的集成就成功了。
|
||||
|
||||
默认情况下,agent代理连Jenkins的接超时时间为100秒。在特殊情况下,如果想设置一个不同的值,可以将system属性设置org.csanchez.jenkins.plugins.kubernetes.PodTemplate.connectionTimeout为一个不同的值。但是100s的话其实够用了,如果超过100s还没生成代理pod,就需要根据jenkins日志去排查问题了。如果jenkins连接kubernetes配置成功了,大多数情况下的错误一般就是配置podtemplate(是什么,后面章节会介绍到)出现了问题。
|
||||
|
||||
示例
|
||||
|
||||
上面配置好了如何连接kubernetes,下面用一个示例测试配置是否成功。
|
||||
|
||||
将下面示例放到pipeline脚本中。
|
||||
|
||||
def label = "mypod-${UUID.randomUUID().toString()}"
|
||||
podTemplate(label: label, cloud: 'kubernetes') {
|
||||
node(label) {
|
||||
stage('Run shell') {
|
||||
sh 'hostname'
|
||||
sh 'echo hello world.'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
PodTemplate中的label参数用于给Pod指定一个唯一的名称。
|
||||
|
||||
PodTemplate中cloud参数的值必须是在系统配置添加的云的名称。
|
||||
|
||||
node中直接使用”label”便可以引用上面定义的label变量,表示在该pod名称中执行命令,如果不指定的话,默认还是在Jenkins服务所在主机上执行流水线脚本。
|
||||
|
||||
执行结果如下:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
根据上面的执行结果可以看到Jenkins通过kubernetes调用的资源对象pod的yaml定义;使用的镜像默认为jenkins/jnlp-slave:3.35-5-alpine;镜像名称以及一些pod的环境变量等信息。
|
||||
|
||||
除了可以在pipeline类型的job中使用kubernetes插件外,也可以在其他类型的job中使用,但是此时需要在jenkins的系统配置中配置PodTemplate,此处先不介绍,在后面的实践章节会进行说明。
|
||||
|
||||
无论哪种类型的job,在job执行的过程中,都可以通过kubectl命令在kubernetes集群中查看pod运行的一些详细信息,而关于该pod的资源定义又是怎么配置的,是否可以自定义呢?下节会通过学习kubernetes的基本语法来解答这个问题。
|
||||
|
||||
|
||||
|
||||
|
505
专栏/Jenkins持续交付和持续部署/15.JenkinsKubernetesPlugin介绍与语法详解.md
Normal file
505
专栏/Jenkins持续交付和持续部署/15.JenkinsKubernetesPlugin介绍与语法详解.md
Normal file
@@ -0,0 +1,505 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
15.Jenkins Kubernetes Plugin介绍与语法详解
|
||||
在上一节中介绍了jenkins使用不同的安装方式配置连接kubernetes集群的方法以及简单的示例,根据kubernetes插件用途的不同,对于插件语法的介绍也会区分开来,本节主要介绍一下使用kubernetes集群动态生成Jenkins slave pod的插件Kubernetes plugin。
|
||||
|
||||
kubernetes plugin
|
||||
|
||||
编写pipeline脚本集成kubernetes插件生成动态slave时,对于启动agent代理部分的代码编写根据pipeline语法类型的不同会有所差异。但无论有什么差异,有一个核心方法不变,那就是PodTemplate,顾名思义,就是pod的模板。
|
||||
|
||||
PodTemplate是使用kubernetes插件生成动态slave节点的核心方法。用于配置启动的agent代理(Pod)的资源对象定义。熟悉kubernetes的应该都知道,kubernetes资源对象pod的定义严格来说主要包含两部分:Pod各项配置定义和容器各项配置定义。而在Jenkins中则通过kubernetes插件的PodTemplate()方法,对这两部分进行定义,并启动代理。
|
||||
|
||||
下面会根据不同的语法类型分别介绍PodTemplate的配置方法。
|
||||
|
||||
基本语法
|
||||
|
||||
脚本式语法
|
||||
|
||||
首先看一个在脚本式pipeline中使用kubernetes插件的基本语法。
|
||||
|
||||
podTemplate(label: 'xxx',cloud:'',<pod_option1>,<pod_option2>,...,containers: [
|
||||
<containerTemplate(name: 'c1',<container_option1>,<container_option2>...)>,
|
||||
<containerTemplate(<name: 'c2',<container_option1>,<container_option2>...)>,
|
||||
....
|
||||
]){
|
||||
node('xxx'){
|
||||
stage('test'){
|
||||
....
|
||||
}
|
||||
}
|
||||
container('c1') {
|
||||
stage('c1'){
|
||||
....
|
||||
}
|
||||
}
|
||||
container('c2') {
|
||||
stage('c2'){
|
||||
....
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
上面PodTemplate包含两部分:
|
||||
|
||||
第一部分为对kubernetes资源对象Pod自身的一些定义,包括pod的标签,pod的名称,使用的云(cloud)名称,挂载的volume等。
|
||||
|
||||
第二部分(container)为对该pod下container的定义。container是一个列表,可以包含一个或多个containerTemplate,用于详细描述container的配置参数,比如镜像地址,容器名称,镜像拉取策略等。
|
||||
|
||||
podTemplate()定义了一个label名称,该lable用于在node()中引用,用于生成一个唯一的pod名称。
|
||||
|
||||
containerTemplate()定义了该container的名称,用于作为流水线执行的环境,通过container('container_name')引用给容器。
|
||||
|
||||
声明式语法
|
||||
|
||||
相对于脚本式语法,声明式的语法就显得相对比较混乱。是因为声明式最基础的配置是将kubernetes中资源对象pod的定义的内容通过yaml方式直接放到流水线脚本中,如下所示(只展示了部分定义)。
|
||||
|
||||
pipeline {
|
||||
agent {
|
||||
kubernetes {
|
||||
label <label_name>
|
||||
yaml """
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: <pod_name>
|
||||
spec:
|
||||
containers:
|
||||
- name: <container_name>
|
||||
image: <image_name>
|
||||
imagePullPolicy: IfNotPresent
|
||||
command:
|
||||
- xxxx
|
||||
tty: true
|
||||
volumeMounts:
|
||||
....
|
||||
restartPolicy: Never
|
||||
volumes:
|
||||
......
|
||||
"""
|
||||
}
|
||||
}
|
||||
stages {
|
||||
stage('one') {
|
||||
steps {
|
||||
container('container_name') {
|
||||
<command1>
|
||||
}
|
||||
container('container_name') {
|
||||
<command2>
|
||||
}
|
||||
}
|
||||
steps {
|
||||
sh 'hostname'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
说明
|
||||
|
||||
agent{}代理配置使用kubernetes关键字通过yaml指令将yaml文件内容直接放到PodTemplate中。
|
||||
|
||||
在不同的stage中引用容器的方式也是通过container('container_name')语法格式。
|
||||
|
||||
以上便是使用脚本式与声明式流水线集成kubernetes插件动态生成jenkins slave节点的基础语法。需要说明的是,对于PodTemplage方法中Pod Template和Container Tempalte的配置,既可以在常规的Jenkins UI中配置,也可以在pipeline script中通过脚本定义配置信息。本节主要介绍在pipeline script中使用脚本的方式定义PodTemplate;在Jenkins UI中配置PodTemplate的方法将在以后介绍,并且如果会使用脚本定义PodTemplate后,在Jenkins UI中配置相对会简单一些。
|
||||
|
||||
参数说明
|
||||
|
||||
PodTemplate()既然是方法,那么肯定会涉及到参数配置,所以接下来先了解一下PodTemplate方法的参数配置,为了区分pod配置和container配置,下面会将PodTemplate()总体配置拆分为Pod Template配置和container Template配置分别进行说明。
|
||||
|
||||
Pod Template 配置部分参数说明-
|
||||
cloud :用来指定在jenkins的系统配置中设置的云名称,默认为kubernetes。在上一节中对于该设置已经做个说明,这里不在重复介绍。
|
||||
|
||||
name :pod名称。
|
||||
|
||||
namespace :pod运行的namespace(命名空间)。
|
||||
|
||||
label :pod的标签. 可以自己定义,也可以使用插件内置的 POD_LABEL 变量。
|
||||
|
||||
yaml :Pod定义的yaml表现形式,可参考kubernetes官网。
|
||||
|
||||
yamlMergeStrategy :包含merge() 和 override()属性。控制yaml定义是覆盖还是与从声明(或者继承)的pod模板的yaml定义合并。默认为override()。
|
||||
|
||||
containers :Pod内容器模板的定义。
|
||||
|
||||
serviceAccount :Pod使用的服务账户。
|
||||
|
||||
nodeSelector :生成的Pod绑定到的node节点,以key:value的形式表现。
|
||||
|
||||
nodeUsageMode :包括NORMAL和EXCLUSIVE两个值,它控制Jenkins在选择到这个代理的方式是:只有指定该节点标签时使用这个节点,还是尽可能多地使用该节点。
|
||||
|
||||
volumes :定义的数据卷,用于pod和所有容器。
|
||||
|
||||
envVars:应用于所有容器的环境变量。
|
||||
|
||||
envVar :可以理解为在pipeline内定义的环境变量。
|
||||
|
||||
secretEnvVar :secret变量,其值是从Kubernetes 的secret获取的。
|
||||
|
||||
imagePullSecrets :一个存放私有仓库认证信息的secret,用于从私有仓库拉取镜像时对私有仓库认证。
|
||||
|
||||
annotations:Pod的注解。
|
||||
|
||||
InheritFrom:继承的一个或多个Pod模板的列表。
|
||||
|
||||
slaveConnectTimeout :代理pod连接jenkins的超时时间(以秒为单位_)_。
|
||||
|
||||
podRetention :控制保留Pod的行为,用户设置在agent执行完毕后是否保留该pod。有多个选项,可以是never(),onFailure(),always()或default()。如果为空,则默认为在经过activeDeadlineSeconds指定的时间之后删除pod 。
|
||||
|
||||
activeDeadlineSeconds :如果 podRetention 参数设置为never() 或者 onFailure(),pod经过该参数设置的时间后会自动删除。
|
||||
|
||||
idleMinutes :允许Pod保持活动状态以供重用,直到时间达到从开始执行到经过该值设置时间为止,默认为分钟。
|
||||
|
||||
showRawYaml :启用或禁用原始yaml文件的输出。默认为true。
|
||||
|
||||
runAsUser :用于设置Pod中所有容器运行的用户ID。
|
||||
|
||||
runAsGroup :用于设置Pod中所有容器运行的组ID。
|
||||
|
||||
hostNetwork :使用宿主机网络,类似docker中的network=host。
|
||||
|
||||
workspaceVolume :用于工作空间的卷的类型。也就是kubernetes中volume的挂载类型,可以是:
|
||||
|
||||
emptyDirWorkspaceVolume (default)-
|
||||
dynamicPVC()-
|
||||
hostPathWorkspaceVolume()-
|
||||
nfsWorkspaceVolume()-
|
||||
persistentVolumeClaimWorkspaceVolume()
|
||||
|
||||
container Template配置部分参数说明
|
||||
|
||||
name :容器名称。-
|
||||
image :容器使用的镜像。-
|
||||
envVars:应用于容器的环境变量(补充和覆盖在pod级别设置的envvar)。-
|
||||
envVar :同上。-
|
||||
secretEnvVar :同上。-
|
||||
command :容器要执行的命令。-
|
||||
args :执行命令要传的参数。-
|
||||
ttyEnabled :标记容器开启tty。-
|
||||
livenessProbe :容器探针。-
|
||||
ports :容器端口。-
|
||||
alwaysPullImage :拉取镜像策略-
|
||||
runAsUser :用于运行容器的用户ID。-
|
||||
runAsGroup :用于运行容器的用户组ID。.
|
||||
|
||||
上面的参数对于脚本式和声明式pipeline都是适用的。对于声明式的语法,也可以通过在前面介绍的片段生成器生成相应的PodTemplate语法片段。
|
||||
|
||||
有关Pod Template和Container Template的更多参数可以参考kubernetes中资源对象pod的yaml定义,默认情况下,Pod中支持的参数,在pipeline中都是可以使用的。
|
||||
|
||||
了解了基本语法,下面通过一些基本的示例来加深一下理解。
|
||||
|
||||
语法示例
|
||||
|
||||
针对脚本式语法和声明式语法的不同,对于示例也会分开介绍。
|
||||
|
||||
脚本式语法
|
||||
|
||||
基础示例如下:
|
||||
|
||||
podTemplate {
|
||||
node(POD_LABEL) {
|
||||
stage('Run shell') {
|
||||
sh 'echo hello world'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
说明
|
||||
|
||||
对于脚本式语法,node()块是被包含在PodTemplate方法里的。
|
||||
|
||||
该示例会启动一个kubernetes Pod,并输出hello world。
|
||||
|
||||
node(POD_LABEL) :POD_LABEL用于给pipeline的agent代理(kubernetes pod)指定一个标签,确保pod名称的唯一性,这个标签可以自定义(通过podTemplate 的label参数指定),也可以像上面示例一样,使用POD_LABEL,这个是在kubernetes plugin插件的1.17及以后版本添加的功能,用于自动生成pod 标签,标签的格式为${JOB_NAME)-${BUILD_NUMBER}-hash_number。
|
||||
|
||||
通过上面的示例会发现,PodTemplate既没有定义Pod参数配置,也没有定义container的参数配置。在没有配置这些参数的情况下,pipeline会使用默认的PodTemplate配置,在执行上面的pipeline后,Jenkins的构建日志中会列出该Pod的yaml定义(可参考上面最初的示例),包括镜像版本,jenkins的环境变量等。由于是默认的配置,对我们编写pipeline 持续交付脚本帮助不大,我们需要自己定义Pod运行参数,所以这里就不在重复介绍默认的pod定义。有兴趣的可以执行上面的示例,结果会通过jenkins的console日志显示出来。
|
||||
|
||||
对于上面的基础示例,可以添加container参数,用来自定义pod内容器的启动参数,比如:
|
||||
|
||||
podTemplate(containers: […]) {
|
||||
node(POD_LABEL) {
|
||||
stage('Run shell') {
|
||||
container('mycontainer') {
|
||||
sh 'echo hello world'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
该示例中,container参数定义了启动容器用到的镜像配置模板,在不填写container参数的情况,默认的jnlp代理(container)的配置参数如下所示:
|
||||
|
||||
containerTemplate(name: 'jnlp', image: 'jenkins/jnlp-slave:3.35-5-alpine', args: '${computer.jnlpmac} ${computer.name}')
|
||||
|
||||
|
||||
需要注意的是:
|
||||
|
||||
对于${computer.jnlpmac} ${computer.name}这两个参数是jnlp-agent(该示例为jnlp-slave:3.35-5-alpine)镜像运行必须的参数,如果不写或少写一个,都会导致slave pod生成失败。
|
||||
|
||||
另一个要说明的点是,该contianer的名称,实际工作中我们可能需要使用自己定义的一些镜像,这些镜像如果是jnlp-agent类型(启动时通过slave agent包启动并连接Jenkins master节点)的镜像,则容器名称就必须为jnlp,否则有可能会导致pod生成失败,至于原因将在下一章节进行说明。
|
||||
|
||||
对于默认的jnlp代理(container)相应的声明式语法如下:
|
||||
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
spec:
|
||||
containers:
|
||||
- name: jnlp
|
||||
image: 'jenkins/jnlp-slave:3.35-5-alpine'
|
||||
args: ['\$(JENKINS_SECRET)', '\$(JENKINS_NAME)']
|
||||
|
||||
|
||||
通过该示例,可以知道上面脚本式语法中的两个参数是做什么用的了吧。如果还不知道,可参考jenkins的docker-jnlp-slave
|
||||
|
||||
默认的container配置就只包含上面的三个参数,name,image,args,除了这些参数外,还有一些经常用到的,比如用于分配伪终端的ttyEnabled参数、设置拉取镜像策略的alwaysPullImage参数、配置是否使用privileged特权模式的参数、设置容器启动内存和cpu等。
|
||||
|
||||
更多与容器相关的参数配置可参考如下示例:
|
||||
|
||||
containerTemplate(
|
||||
name: 'mariadb',
|
||||
image: 'mariadb:10.1',
|
||||
ttyEnabled: true,
|
||||
privileged: false,
|
||||
alwaysPullImage: false,
|
||||
workingDir: '/home/jenkins/agent',
|
||||
resourceRequestCpu: '50m',
|
||||
resourceLimitCpu: '100m',
|
||||
resourceRequestMemory: '100Mi',
|
||||
resourceLimitMemory: '200Mi',
|
||||
envVars: [
|
||||
envVar(key: 'MYSQL_ALLOW_EMPTY_PASSWORD', value: 'true'),
|
||||
secretEnvVar(key: 'MYSQL_PASSWORD', secretName: 'mysql-secret', secretKey: 'password'),
|
||||
...
|
||||
],
|
||||
ports: [portMapping(name: 'mysql', containerPort: 3306, hostPort: 3306)]
|
||||
),
|
||||
|
||||
|
||||
与pod Template相关的参数配置如下:
|
||||
|
||||
podTemplate(cloud: 'kubernetes', label: 'my-defined', containers: [....
|
||||
],
|
||||
volumes: [
|
||||
emptyDirVolume(mountPath: '/etc/mount1', memory: false),
|
||||
secretVolume(mountPath: '/etc/mount2', secretName: 'my-secret'),
|
||||
configMapVolume(mountPath: '/etc/mount3', configMapName: 'my-config'),
|
||||
hostPathVolume(mountPath: '/etc/mount4', hostPath: '/mnt/my-mount'),
|
||||
nfsVolume(mountPath: '/etc/mount5', serverAddress: '127.0.0.1', serverPath: '/', readOnly: true),
|
||||
persistentVolumeClaim(mountPath: '/etc/mount6', claimName: 'myClaim', readOnly: true)
|
||||
],
|
||||
namesapce: default,
|
||||
serviceaccount: default,
|
||||
imagePullSecrets: [ 'pull-secret' ], //用于在启动容器时从私有仓库拉取镜像,而无需在宿主机对私有仓库进行认证登录
|
||||
annotations: [
|
||||
podAnnotation(key: "my-key", value: "my-value")
|
||||
...
|
||||
])
|
||||
|
||||
|
||||
编写pipeline脚本时对于PodTemplate方法中的某个参数如果不知道如何定义,都可以参考上面展示的模版范例,或者通过在前面的介绍的片段生成器通过jenkins ui定义来生成语法片段。
|
||||
|
||||
podTemplate方法的配置除了直接使用key:value方式定义外,也可以通过以yaml(kubernetes中资源对象文件的默认后缀)文件的方式定义,比如下面示例:
|
||||
|
||||
podTemplate(yaml: """
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
labels:
|
||||
some-label: some-label-value
|
||||
spec:
|
||||
containers:
|
||||
- name: busybox
|
||||
image: busybox
|
||||
command:
|
||||
- cat
|
||||
tty: true
|
||||
"""
|
||||
) {
|
||||
node(POD_LABEL) {
|
||||
container('busybox') {
|
||||
sh "hostname"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
除了使用yaml关键字外,也可以使用yamlFile关键字,用于指定一个yaml文件,该文件通常与jenkinsfile文件一起存在于源码仓库中。通过从源码仓库中拉取Jenkinsfile文件和该参数指定的文件,下载后会自动执行。这种方式需要改变pipeline job中脚本定义的类型,默认使用Pipeline script,使用yamlFile的方式就该使用Pipeline script from SCM(前面章节有介绍)的方式了。
|
||||
|
||||
运行多个容器
|
||||
|
||||
对于不同的代码要是使用不同的构建环境怎么办?在container参数中可以定义多个containerTemplate来满足此需求。也就是在一个pod中运行多个容器,熟悉kubernetes的应该都知道,同一个pod内的容器共享主机名、网络、vloume等信息,所以在pod内运行一个或者多个容器,它们之间并没有什么区别。如下示例:
|
||||
|
||||
podTemplate(label:'test', containers: [
|
||||
containerTemplate(name: 'maven', image: 'maven:3.3.9-jdk-8-alpine', ttyEnabled: true, command: 'cat'),
|
||||
containerTemplate(name: 'golang', image: 'golang:1.8.0', ttyEnabled: true, command: 'cat')
|
||||
]) {
|
||||
|
||||
node('test') {
|
||||
stage('Get a Maven project') {
|
||||
git 'https://github.com/jenkinsci/kubernetes-plugin.git'
|
||||
container('maven') {
|
||||
stage('Build a Maven project') {
|
||||
sh 'echo build maven'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('Get a Golang project') {
|
||||
git url: 'https://github.com/hashicorp/terraform.git'
|
||||
container('golang') {
|
||||
stage('Build a Go project') {
|
||||
sh 'echo build go'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
该示例定义多个容器,在不同的stage通过定义的容器的名称来使用该容器作为流水线执行的环境。
|
||||
|
||||
声明式语法
|
||||
|
||||
在声明式语法中使用PodTemplate方法与在脚本式语法中使用该方法的方式基本一致。主要区别在于agent的定义方式上。
|
||||
|
||||
首先看一个在声明式脚本中使用PodTemplate的基础示例:
|
||||
|
||||
pipeline {
|
||||
agent {
|
||||
kubernetes {
|
||||
yaml """
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
labels:
|
||||
some-label: some-label-value
|
||||
spec:
|
||||
containers:
|
||||
- name: maven
|
||||
image: maven:alpine
|
||||
command:
|
||||
- cat
|
||||
tty: true
|
||||
- name: busybox
|
||||
image: busybox
|
||||
command:
|
||||
- cat
|
||||
tty: true
|
||||
"""
|
||||
}
|
||||
}
|
||||
stages {
|
||||
stage('Run maven') {
|
||||
steps {
|
||||
container('maven') {
|
||||
sh 'mvn -version'
|
||||
}
|
||||
container('busybox') {
|
||||
sh '/bin/busybox'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
使用声明式脚本,agent的type必须是”any, docker, dockerfile, kubernetes, label, none”中的一种,本节为kubernetes插件的使用,所以agent的type为kubernetes。
|
||||
|
||||
kubernetes使用yaml方式定义Pod和container模板配置信息,在stage阶段使用container指令指定运行的容器提供流水线执行的环境。
|
||||
|
||||
该pod定义未指定pod工作的namespace(命名空间),这里需要说明一下的是,如果yaml定义没有指定namespace,则默认使用在jenkins系统配置中添加kubernetes云时指定的namespace,如果这里也没设定namespace的名称,则默认使用default namespace;如果都指定了,则默认使用在pipeline脚本中定义的namespace。
|
||||
|
||||
上面示例 stage阶段对容器的引用方式与脚本式流水线引用容器的方式一样。
|
||||
|
||||
有关yaml更全面的定义,可以参考kubernetes中对资源对象pod的定义,参考kubernetes官网
|
||||
|
||||
除了可以在脚本式语法中使用yamlFile指令外,在声明式语法中也可以使用yamlFile指令。如下示例:
|
||||
|
||||
pipeline {
|
||||
agent {
|
||||
kubernetes {
|
||||
yamlFile 'KubernetesPod.yaml'
|
||||
}
|
||||
}
|
||||
stages {
|
||||
...
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
说明
|
||||
|
||||
示例中指定KubernetesPod.yaml文件与jenkinsfile文件放在同一源码仓库的同级目录下,配置job pipeline时使用Pipeline script from SCM。如果指定的kubernetesPod.yaml文件与Jenkinsfile文件没在同一级目录,指定该文件时需要加上相对路径。
|
||||
|
||||
使用多个容器
|
||||
|
||||
默认情况下,声明式脚本的模板不会从父模板继承。所以即便在定义了全局agent代理的情况下,也可以在指定的stage单独定义agent。
|
||||
|
||||
如下示例,定义了全局agent,也可以在stage定义agent,互不影响。
|
||||
|
||||
pipeline {
|
||||
agent {
|
||||
kubernetes {
|
||||
label 'parent-pod'
|
||||
yaml """
|
||||
spec:
|
||||
containers:
|
||||
- name: golang
|
||||
image: golang:1.6.3-alpine
|
||||
command:
|
||||
- cat
|
||||
tty: true
|
||||
"""
|
||||
}
|
||||
}
|
||||
stages {
|
||||
stage('Run maven') {
|
||||
agent {
|
||||
kubernetes {
|
||||
label 'nested-pod'
|
||||
yaml """
|
||||
spec:
|
||||
containers:
|
||||
- name: maven
|
||||
image: maven:3.3.9-jdk-8-alpine
|
||||
command:
|
||||
- cat
|
||||
tty: true
|
||||
"""
|
||||
}
|
||||
}
|
||||
steps {
|
||||
container('maven') {
|
||||
sh 'mvn -version'
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('run go'){
|
||||
steps {
|
||||
container('golang') {
|
||||
sh 'go --version'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
以上就是使用kubernetes插件动态生成jenkins slave节点时用到的一些语法示例。在学会了前面jenkins与docker pipeline插件集成的语法及示例后,对于本章节的内容学习应该相对简单很多。
|
||||
|
||||
有关在jenkins ui中定义PodTemplate以及自定义image镜像等内容会在下一章以实际案例的方式说明。
|
||||
|
||||
|
||||
|
||||
|
594
专栏/Jenkins持续交付和持续部署/16.使用Kubernetes编排JenkinsSlave节点持续交付项目.md
Normal file
594
专栏/Jenkins持续交付和持续部署/16.使用Kubernetes编排JenkinsSlave节点持续交付项目.md
Normal file
@@ -0,0 +1,594 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
16.使用Kubernetes编排Jenkins Slave节点持续交付项目
|
||||
在上一节的内容中,简单介绍了一下使用kubernetes插件动态生成slave节点的基本语法和使用示例,本节会根据上节介绍到的一些语法知识进行一个基础的实践,并继续补充一些使用该插件时的一些配置细节。
|
||||
|
||||
为了方便读者理解,本小节内容会分多个版本,以由浅入深的方式尽量将之前的pipeline语法融会贯通。
|
||||
|
||||
基础版
|
||||
|
||||
本次版本说明:
|
||||
|
||||
1、 PodTemplate 配置以pipeline script的方式放到pipeline脚本里,没有在Jenkins 系统配置里进行定义。
|
||||
|
||||
2、 Jenkins默认的Jenkins slave镜像为jnlp-slave:3.35-5-alpine,这是一个Jnlp方式的agent镜像,该镜像没有拉取/编译代码以及docker命令,需要基于此镜像自定义一个新的镜像。
|
||||
|
||||
3、 构建应用镜像时的Dockerfile文件通过挂载nfs的方式引用。
|
||||
|
||||
4、 使用最基础的shell命令实现一个简单的持续交付脚本,以方便了解该持续交付的流程。
|
||||
|
||||
基于上面条件,用脚本式语法编写的pipeline脚本如下:
|
||||
|
||||
podTemplate(cloud: 'kubernetes',namespace: 'default', label: 'pre-CICD',
|
||||
serviceAccount: 'default', containers: [
|
||||
containerTemplate(
|
||||
name: 'jnlp',
|
||||
image: "192.168.176.155/library/jenkins-slave:latest",
|
||||
args: '${computer.jnlpmac} ${computer.name}',
|
||||
ttyEnabled: true,
|
||||
privileged: true,
|
||||
alwaysPullImage: false,
|
||||
),
|
||||
],
|
||||
volumes: [
|
||||
nfsVolume(mountPath: '/tmp', serverAddress: '192.168.177.43', serverPath: '/data/nfs', readOnly: true),
|
||||
hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock'),
|
||||
],
|
||||
){
|
||||
node('pre-CICD') {
|
||||
stage('build') {
|
||||
|
||||
container('jnlp') {
|
||||
stage('git-clone') {
|
||||
checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: 'c33d60bd-67c6-4182-b52c-d7aeebfab772', url: 'http://192.168.176.154/root/base-nop.git']]])
|
||||
}
|
||||
|
||||
stage('Build a Maven project') {
|
||||
sh 'cd base-nop/fw-base-nop && mvn clean install -DskipTests -Denv=beta'
|
||||
}
|
||||
|
||||
stage('build docker image'){
|
||||
sh 'cp /tmp/Dockerfile base-nop/fw-base-nop/target/'
|
||||
sh '/usr/bin/docker build -t 192.168.176.155/library/fw-base-nop:xxxx --build-arg jar_name="fw-base-nop.jar" base-nop/fw-base-nop/target/'
|
||||
}
|
||||
|
||||
stage('push registry') {
|
||||
sh '''
|
||||
docker login -u admin -p da88e43d88722c2c9ca09da644eeb015 192.168.176.155
|
||||
docker push 192.168.176.155/library/fw-base-nop:xxxx
|
||||
docker rmi 192.168.176.155/library/fw-base-nop:xxxx
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
PodTemplate(….):配置的Pod模板,配置参数以及相关说明在语法章节有过介绍,不再过多赘述;不过有一点需要注意的是:containerTemplate 下的name参数和image参数的设定。
|
||||
|
||||
Jenkins的Agent大概分两种: 一是基于SSH的,由master主动连接slave/Agent节点; 二是基于JNLP的,使用的是HTTP协议,由Agent/slave节点主动连接master节点,每个Agent需要配置一个独特的secret。 可以参考官方说明。
|
||||
|
||||
1、 如果name参数配置的值为jnlp,就表示使用基于Jnlp方式的jnlp-agent自启动连接Jenkins master。这就需要保证image参数指定的镜像里包含启动jnlp-agent的命令(官方提供的镜像启动命令为jenkins-slave(旧版本)或者jenkins-agent(新版本))命令,同时需要指定启动参数${computer.jnlpmac} ${computer.name;如果指定的镜像里没有这些命令,或者没有启动参数,动态pod就会创建失败。
|
||||
|
||||
2、 如果name参数配置的值不是jnlp,而image参数指定的镜像为jnlp agent镜像时,动态pod也会生成失败。这是因为当该参数设置的值不是jnlp时,动态生成的pod默认会启动两个容器,使用的镜像一个是连接jenkins master默认的jnlp-agent镜像,也就是jnlp-slave:3.35-5-alpine;另一个就是通过image参数自定义的镜像。这两个jnlp-agent镜像启动时都会去连接jenkins master,而基于jnlp方式使用agent时,每个客户端只能有一个agent和一个secret,所以同时启动多个jnlp-agent就会导致Pod启动失败。
|
||||
|
||||
3、 如果name参数配置的值不是jnlp,并且image参数指定的镜像为非jnlp agent镜像时(需要注意,此时启动容器时的参数应该去掉),同上面一样,动态生成的pod包含两个容器,一个名称是jnlp(根据默认的jnlp agent镜像默认在后台启动),一个名称是通过name参数设置的值,同时两个容器都能作为pipeline脚本执行的环境,使用时都是通过container('container_name')引用。
|
||||
|
||||
4、 如果Pod内有多个容器,并且只要有一个容器为jnlp容器,其他容器的名称和镜像使用没有限制,也就是说,容器名称可以不是jnlp,但是镜像可以jnlp agent镜像。
|
||||
|
||||
5、上面示例里的image参数配置的镜像为根据默认的jnlp-agent镜像自定义的镜像,dockerfile如下:
|
||||
|
||||
FROM jenkins/jnlp-slave:3.35-5-alpine
|
||||
|
||||
USER root
|
||||
|
||||
RUN apk add maven git
|
||||
|
||||
COPY settings.xml /usr/share/java/maven-3/conf/settings.xml
|
||||
COPY docker/docker /usr/bin/docker
|
||||
|
||||
|
||||
其中settings.xml文件为maven配置文件,这里需要根据实际情况考虑要不要添加。
|
||||
|
||||
执行结果如下:
|
||||
|
||||
|
||||
|
||||
声明式脚本
|
||||
|
||||
对于该pipeline脚本相应的声明式语法如下:
|
||||
|
||||
pipeline {
|
||||
agent {
|
||||
kubernetes {
|
||||
yaml """
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
labels:
|
||||
some-label: some-label-value
|
||||
namespace: 'default'
|
||||
spec:
|
||||
containers:
|
||||
- name: jnlp
|
||||
image: 192.168.176.155/library/jenkins-slave:latest
|
||||
args: ['\$(JENKINS_SECRET)', '\$(JENKINS_NAME)']
|
||||
tty: true
|
||||
privileged: true
|
||||
alwaysPullImage: false
|
||||
volumeMounts:
|
||||
- name: mount-nfs
|
||||
mountPath: /tmp
|
||||
- name: mount-docker
|
||||
mountPath: /var/run/docker.sock
|
||||
volumes:
|
||||
- name: mount-nfs
|
||||
nfs:
|
||||
path: /data/nfs
|
||||
server: 192.168.177.43
|
||||
- name: mount-docker
|
||||
hostPath:
|
||||
path: /var/run/docker.sock
|
||||
"""
|
||||
}
|
||||
}
|
||||
stages {
|
||||
stage('Run maven') {
|
||||
steps {
|
||||
container('jnlp') {
|
||||
checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: 'c33d60bd-67c6-4182-b52c-d7aeebfab772', url: 'http://192.168.176.154/root/base-nop.git']]])"
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('build code') {
|
||||
steps {
|
||||
container('jnlp') {
|
||||
sh 'cd base-nop/fw-base-nop && mvn clean install -DskipTests -Denv=beta'
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
stage('build image'){
|
||||
steps{
|
||||
container('jnlp') {
|
||||
sh 'cp /tmp/Dockerfile base-nop/fw-base-nop/target/'
|
||||
sh '/usr/bin/docker build -t 192.168.176.155/library/fw-base-nop:xxxx --build-arg jar_name="fw-base-nop.jar" base-nop/fw-base-nop/target/'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('push to registry') {
|
||||
steps {
|
||||
container('jnlp') {
|
||||
sh '''
|
||||
docker login -u admin -p da88e43d88722c2c9ca09da644eeb015 192.168.176.155
|
||||
docker push 192.168.176.155/library/fw-base-nop:xxxx
|
||||
docker rmi 192.168.176.155/library/fw-base-nop:xxxx
|
||||
'''
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
声明式pipeline语法对于格式有严格要求,所以如果遇到有语法错误的情况需要有耐心排查。
|
||||
|
||||
使用kubernetes插件时,使用脚本式流水线语法相对于声明式语法要更加简单,所以建议使用脚本式语法。
|
||||
|
||||
Config File Provider Plugin
|
||||
|
||||
关于maven配置文件settings.xml的使用还有另一种方式,就是通过jenkins的Config File Provider Plugin插件。这个插件的作用就是在 Jenkins 中存储以properties、xml、json、Groovy结尾的文件以及Maven settings.xml内容。下面演示一下如何使用该插件。
|
||||
|
||||
点击”系统管理—>Managed files—>Add a new Config—>Global Maven settings.xml“,将上面setting.xml文件的内容放到”Content”对应的输入框里,如下所示:
|
||||
|
||||
|
||||
|
||||
编辑好提交即可。
|
||||
|
||||
然后使用片段生成器configfileProvider:...根据配置的maven settings.xml文件生成语法片段,比如:
|
||||
|
||||
|
||||
|
||||
配置好后就可以编辑pipeline脚本了,为了测试该插件的作用,我直接使用docker hub官方的maven镜像,脚本如下:
|
||||
|
||||
podTemplate(cloud: 'kubernetes', label: 'pre-CICD', containers: [
|
||||
containerTemplate(
|
||||
name: 'maven',
|
||||
image: "maven:latest",
|
||||
command: 'cat',
|
||||
ttyEnabled: true,
|
||||
privileged: true,
|
||||
alwaysPullImage: false)
|
||||
],
|
||||
volumes: [
|
||||
nfsVolume(mountPath: '/tmp', serverAddress: '192.168.177.43', serverPath: '/data/nfs', readOnly: false),
|
||||
hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock'),
|
||||
],
|
||||
){
|
||||
node('pre-CICD') {
|
||||
|
||||
stage('build') {
|
||||
container('maven') {
|
||||
stage('clone code') {
|
||||
sh "git clone http://root:[email protected]/root/base-nop.git"
|
||||
}
|
||||
configFileProvider([configFile(fileId: 'f67fdaf1-4b17-4caa-86ad-e841f387ac7a', targetLocation: 'settings.xml')]) {
|
||||
stage('Build project') {
|
||||
sh 'cd base-nop/fw-base-nop && mvn clean install -DskipTests -Denv=beta --settings ${WORKSPACE}/settings.xml'
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
通过mvn命令编译代码时加上--settings参数,用于指定该文件。上面示例可以看到在指定settings.xml文件时添加了该job的workspace路径,这是因为在使用configFileProvider插件时,该插件默认会将指定的settings.xml文件拷贝到job的workspace目录下,如果不添加路径,有可能会报该文件找不到的错误。
|
||||
|
||||
基础版的持续交付脚本就算是完成了。此版本的脚本只是为了让大家熟悉一下代码编译和镜像构建的一个基本流程,下面的版本将会该该版本进行一个简单的优化。继续往下看。
|
||||
|
||||
进阶版
|
||||
|
||||
说明(相对上一个版本):
|
||||
|
||||
1、 本次版本开始引入变量,以提高灵活性。需要注意的是,引用变量的时候要使用双引号(“”)。-
|
||||
2、 将脚本内部分命令通过kubernetes插件和Docker插件内的相关方法实现。-
|
||||
3、 挂载共享目录,用于挂载maven的.m2仓库,提高代码编译效率。
|
||||
|
||||
首先,用代码片段生成器生成部分命令的相关语法片段:
|
||||
|
||||
checkout:Check out from version control:生成拉取代码的片段,前面有介绍过,不在过多说明。
|
||||
|
||||
使用docker pipeline插件的withDockerRegistry片段生成器生成对docker Registry认证的语法片段,如下所示:
|
||||
|
||||
|
||||
|
||||
pipeline脚本如下
|
||||
|
||||
def project_name = 'fw-base-nop'
|
||||
def registry_url = '192.168.176.155'
|
||||
|
||||
podTemplate(cloud: 'kubernetes',namespace: 'default', label: 'pre-CICD',
|
||||
, containers: [
|
||||
containerTemplate(
|
||||
name: 'jnlp',
|
||||
image: "192.168.176.155/library/jenkins-slave:latest",
|
||||
args: '${computer.jnlpmac} ${computer.name}',
|
||||
ttyEnabled: true,
|
||||
privileged: true,
|
||||
alwaysPullImage: false,
|
||||
),
|
||||
],
|
||||
volumes: [
|
||||
nfsVolume(mountPath: '/tmp', serverAddress: '192.168.177.43', serverPath: '/data/nfs', readOnly: false),
|
||||
nfsVolume(mountPath: '/root/.m2', serverAddress: '192.168.177.43', serverPath: '/data/nfs/.m2', readOnly: false),
|
||||
hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock'),
|
||||
],
|
||||
nodeSelector: 'kubernetes.io/hostname=192.168.176.160',
|
||||
){
|
||||
node('pre-CICD') {
|
||||
stage('build') {
|
||||
container('jnlp') {
|
||||
stage('clone code') {
|
||||
checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, userRemoteConfigs: [[credentialsId: 'c33d60bd-67c6-4182-b52c-d7aeebfab772', url: 'http://192.168.176.154/root/base-nop.git']]])
|
||||
|
||||
script {
|
||||
imageTag = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim()
|
||||
}
|
||||
echo "${imageTag}"
|
||||
}
|
||||
|
||||
stage('Build a Maven project') {
|
||||
sh "cd ${project_name} && mvn clean install -DskipTests -Denv=beta"
|
||||
}
|
||||
|
||||
withDockerRegistry(credentialsId: 'auth_harbor', url: 'http://192.168.176.155') {
|
||||
stage('build and push docker image') {
|
||||
sh "cp /tmp/Dockerfile ${project_name}/target/"
|
||||
def customImage = docker.build("${registry_url}/library/${project_name}-${BUILD_NUMBER}:${imageTag}","--build-arg jar_name=${project_name}.jar ${project_name}/target/")
|
||||
echo "推送镜像"
|
||||
customImage.push()
|
||||
}
|
||||
stage('delete image') {
|
||||
echo "删除本地镜像"
|
||||
sh "docker rmi -f ${registry_url}/library/${project_name}-${BUILD_NUMBER}:${imageTag}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
说明
|
||||
|
||||
volumes配置中挂载maven的.m2仓库到nfs server上,以提高代码编译速度。
|
||||
|
||||
clone code阶段获取应用代码最后提交时的commit short id作为镜像构建时的tag。
|
||||
|
||||
使用docker pipeline语法进行镜像的构建和推送到仓库操作。
|
||||
|
||||
nodeSelector 用来选择一台固定的节点专门用来进行构建工作,以防止在拉取基础镜像时生成pod过慢影响效率,这里我使用了node节点默认自带的标签,如果不想用这个标签也可以自定义一个标签。
|
||||
|
||||
该版本同样使用pipeline script的方式将脚本代码直接放到了job中。实际工作中,我们也可以将上面的代码放到Jenkinsfile文件中,并将该文件放到应用代码仓库的根目录(也可以单独放置),在配置pipeline job时使用pipeline script from SCM的方式,配置该Jenkinsfile的仓库地址,拉取后会自动进行构建。比如:
|
||||
|
||||
|
||||
|
||||
执行后会自动拉取该文件并执行。需要说明的是,如果该文件放到了应用代码仓库的根目录,拉取该文件时并不会拉取代码,所以在Jenkinsfile中拉取代码的操作是不能去掉的。
|
||||
|
||||
对应的声明式脚本,只需要在版本一的基础上,替换做了更改的部分,并将用到的docker pipeline插件的方法加上script{}块即可。
|
||||
|
||||
在Cloud中定义PodTemplate
|
||||
|
||||
版本说明:
|
||||
|
||||
前两个版本的PodTemplate配置均通过在pipeline中使用代码的方式进行配置的,本次版本将PodTemplate配置通过Jenkins UI放到了全局配置里。
|
||||
|
||||
编辑最开始“配置Jenkins连接kubernetes”时创建的cloud,增加PodTemplate和container的配置。如下所示:
|
||||
|
||||
-
|
||||
|
||||
|
||||
上面配置的参数说明应该不用再重复介绍了,有不懂的参数可以参考之前的文章即可。配置保存后修改pipeline,只需要将PodTemplate方法配置的部分去掉即可。
|
||||
|
||||
pipeline如下
|
||||
|
||||
def project_name = 'fw-base-nop' //项目名称
|
||||
def registry_url = '192.168.176.155' //镜像仓库地址
|
||||
|
||||
//node 这里依旧是填写podtemplate设置的标签的名称
|
||||
node('pre-CICD') {
|
||||
stage('build') {
|
||||
|
||||
container('jnlp') {
|
||||
stage('git-clone') {
|
||||
checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, userRemoteConfigs: [[credentialsId: 'c33d60bd-67c6-4182-b52c-d7aeebfab772', url: 'http://192.168.176.154/root/base-nop.git']]])
|
||||
|
||||
script {
|
||||
imageTag = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim()
|
||||
}
|
||||
echo "${imageTag}"
|
||||
}
|
||||
|
||||
stage('Build a Maven project') {
|
||||
sh "cd ${project_name} && mvn clean install -DskipTests -Denv=beta"
|
||||
}
|
||||
|
||||
withDockerRegistry(credentialsId: 'auth_harbor', url: 'http://192.168.176.155') {
|
||||
stage('build and push docker image') {
|
||||
sh "cp /tmp/Dockerfile ${project_name}/target/"
|
||||
def customImage = docker.build("${registry_url}/library/${project_name}-${BUILD_NUMBER}:${imageTag}","--build-arg jar_name=${project_name}.jar ${project_name}/target/")
|
||||
echo "推送镜像"
|
||||
customImage.push()
|
||||
}
|
||||
stage('delete image') {
|
||||
echo "删除本地镜像"
|
||||
sh "docker rmi -f ${registry_url}/library/${project_name}-${BUILD_NUMBER}:${imageTag}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
同版本2一样,在配置pipeline类型的job时该脚本既可以通过Pipeline script的方式使用,也可以通过Pipeline script from SCM的方式使用。
|
||||
|
||||
同时,在使用其他类型(比如 自由风格类型、maven类型)的job时也能够使用该PodTemplate方法,用于动态生成slave。比如创建一个自由风格类型的job,配置job时勾选 Restrict where this project can be run ,在显示的Label Expression 输入框里输入我们上边配置PodTemplate时设定的标签(Labels) 名称 pre-CICD即可。
|
||||
|
||||
|
||||
|
||||
构建job时就会自动启动这个代理,如下所示:
|
||||
|
||||
|
||||
|
||||
需要注意的是,如果使用这种方式的话,pipeline中的代码就不在适合此job,需要通过编写shell脚本或者其他方式(比如ansible)实现持续交付。
|
||||
|
||||
sonarqube
|
||||
|
||||
既然是持续交付,那么肯定少不了代码分析。上面一系列版本只是将代码进行了编译和构建,正常流程还需要进行代码质量分析,这就用到了之前搭建的sonarqube平台。关于sonarqube在前面的文章有详细介绍,这里不重复说明,接下来主要看一下如何在pod中使用sonar-scanner命令。
|
||||
|
||||
使用sonar-scanner命令,既可以使用在Jenkins UI中配置的sonar-scanner命令工具,也可以通过自定义sonar-scanner容器镜像的方式引用此命令。下面对这两种方法分别进行介绍。
|
||||
|
||||
首先看一下使用jenkins系统内配置的sonar-scanner工具(PodTemplate配置同上)实现代码质量分析。
|
||||
|
||||
def project_name = 'fw-base-nop' //项目名称
|
||||
def registry_url = '192.168.176.155' //镜像仓库地址
|
||||
|
||||
node('pre-CICD') {
|
||||
stage('build') {
|
||||
stage('git-clone') {
|
||||
container('jnlp'){
|
||||
stage('clone code'){
|
||||
checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, userRemoteConfigs: [[credentialsId: 'c33d60bd-67c6-4182-b52c-d7aeebfab772', url: 'http://192.168.176.154/root/base-nop.git']]])
|
||||
script {
|
||||
imageTag = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim()
|
||||
}
|
||||
echo "${imageTag}"
|
||||
}
|
||||
stage('Build a Maven project') {
|
||||
echo "${project_name}"
|
||||
sh "cd ${project_name} && mvn clean install -DskipTests -Pproduct -U"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('sonar'){
|
||||
def sonarqubeScannerHome = tool name: 'sonar-scanner-4.2.0'
|
||||
withSonarQubeEnv(credentialsId: 'sonarqube') {
|
||||
sh "${sonarqubeScannerHome}/bin/sonar-scanner -X "+
|
||||
"-Dsonar.login=admin " +
|
||||
"-Dsonar.language=java " +
|
||||
"-Dsonar.projectKey=${JOB_NAME} " +
|
||||
"-Dsonar.projectName=${JOB_NAME} " +
|
||||
"-Dsonar.projectVersion=${BUILD_NUMBER} " +
|
||||
"-Dsonar.sources=${WORKSPACE}/fw-base-nop " +
|
||||
"-Dsonar.sourceEncoding=UTF-8 " +
|
||||
"-Dsonar.java.binaries=${WORKSPACE}/fw-base-nop/target/classes " +
|
||||
"-Dsonar.password=admin "
|
||||
}
|
||||
}
|
||||
withDockerRegistry(credentialsId: 'auth_harbor', url: 'http://192.168.176.155') {
|
||||
stage('build and push docker image') {
|
||||
sh "cp /tmp/Dockerfile ${project_name}/target/"
|
||||
def customImage = docker.build("${registry_url}/library/${project_name}-${BUILD_NUMBER}:${imageTag}","--build-arg jar_name=${project_name}.jar ${project_name}/target/")
|
||||
echo "推送镜像"
|
||||
customImage.push()
|
||||
}
|
||||
stage('delete image') {
|
||||
echo "删除本地镜像"
|
||||
sh "docker rmi -f ${registry_url}/library/${project_name}-${BUILD_NUMBER}:${imageTag}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
该示例通过tool指令指定了在jenkins的global tool configuration里配置的sonar-scanner环境变量,并赋给一个变量,通过withSonarQubeEnv片段生成器对sonar认证并进行代码分析操作。有关sonar-scanner命令各参数的说明在”基础工具配置”章节有做介绍,这里不在过多说明。
|
||||
|
||||
使用sonar-scanner自定义镜像
|
||||
|
||||
除了使用jenkins系统配置sonar-scanner工具,也可以根据sonar-scanner工具自定义一个sonar-scanner镜像,并使用该镜像启动一个容器来进行代码分析工作。
|
||||
|
||||
sonar-scanner镜像默认可以从docker hub上获取,但是为了杜绝不必要的错误(测试中曾经遇到过),我选择了自定义。可以使用之前定义好的jnlp-agent镜像作为基础镜像,这样在持续交付步骤使用一个镜像就可以完成所有工作;有些人觉得这样会使镜像臃肿,也可以使用一个包含jdk的小体积镜像作为基础镜像,当然也可以根据自己的实际情况自定义。
|
||||
|
||||
无论使用哪种镜像,都需要修改${SONAR-SCANER_HOME}/bin/sonar-scanner文件下的use_embedded_jre参数的值,默认为true,需要改成fasle,如下:
|
||||
|
||||
use_embedded_jre=false
|
||||
|
||||
|
||||
为什么要修改此参数呢?是因为use_embedded_jre参数为true时,sonar-scanner命令默认会使用自己提供的jre,路径为$sonar_scanner_home/jre,而不会使用系统环境下的jre,所以需要将此参数改为false,否则会报找不到java环境变量的错误,如下所示
|
||||
|
||||
sonar-scanner:exec: line 73: xxx/sonar-scanner-4.2.0-linux/jre/bin/java: not foun
|
||||
|
||||
|
||||
修改好后编辑dockerfile,比如使用上面构建的jnlp-agent镜像作为基础镜像。
|
||||
|
||||
FROM 192.168.176.155/library/jenkins-slave:latest
|
||||
COPY sonar-scanner-4.2.0/ /opt/sonar-scanner-4.2.0/
|
||||
|
||||
ENV SONAR_SCANNER_HOME /opt/sonar-scanner-4.2.0/
|
||||
ENV SONAR_RUNNER_HOME ${SONAR_SCANNER_HOME}
|
||||
ENV PATH $PATH:${SONAR_SCANNER_HOME}/bin
|
||||
|
||||
|
||||
如果想使用openjdlk镜像作为基础镜像,可以将基础镜像修改为openjdk:8-jre-alpine3.7,编辑好构建即可。
|
||||
|
||||
使用docker命令测试一下。
|
||||
|
||||
docker run -it --rm 192.168.176.155/library/jenkins-slave:sonar sonar-scanner
|
||||
|
||||
|
||||
输出如下信息表示镜像构建成功 。
|
||||
|
||||
$ docker run -it --rm a9592584d82d sonar-scanner
|
||||
INFO: Scanner configuration file: /opt/sonar-scanner-4.2.0/conf/sonar-scanner.properties
|
||||
INFO: Project root configuration file: NONE
|
||||
INFO: SonarQube Scanner 4.2.0.1873
|
||||
INFO: Java 1.8.0_212 IcedTea (64-bit)
|
||||
INFO: Linux 5.4.13-1.el7.elrepo.x86_64 amd64
|
||||
INFO: User cache: /root/.sonar/cache
|
||||
INFO: SonarQube server 6.7.5
|
||||
|
||||
......
|
||||
|
||||
|
||||
构建好镜像,对pipeline脚本进行简单修改,为了区分显示,又新添加一个containerTemplate配置用于定义sonar镜像,同时在引用镜像时新增了一个container指令用于引用新镜像。如下示例:
|
||||
|
||||
def project_name = 'fw-base-nop' //项目名称
|
||||
def registry_url = '192.168.176.155' //镜像仓库地址
|
||||
|
||||
podTemplate(cloud: 'kubernetes',namespace: 'default', label: 'pre-CICD',
|
||||
serviceAccount: 'default', containers: [
|
||||
containerTemplate(
|
||||
name: 'jnlp',
|
||||
image: "192.168.176.155/library/jenkins-slave:latest",
|
||||
args: '${computer.jnlpmac} ${computer.name}',
|
||||
ttyEnabled: true,
|
||||
privileged: true,
|
||||
alwaysPullImage: false,
|
||||
),
|
||||
containerTemplate(
|
||||
name: 'sonar',
|
||||
image: "192.168.176.155/library/jenkins-slave:sonar",
|
||||
ttyEnabled: true,
|
||||
privileged: true,
|
||||
command: 'cat',
|
||||
alwaysPullImage: false,
|
||||
),
|
||||
],
|
||||
volumes: [
|
||||
nfsVolume(mountPath: '/tmp', serverAddress: '192.168.177.43', serverPath: '/data/nfs', readOnly: false),
|
||||
hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock'),
|
||||
nfsVolume(mountPath: '/root/.m2', serverAddress: '192.168.177.43', serverPath: '/data/nfs/.m2', readOnly: false),
|
||||
],
|
||||
){
|
||||
node('pre-CICD') {
|
||||
stage('build') {
|
||||
container('jnlp'){
|
||||
stage('clone code'){
|
||||
checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, userRemoteConfigs: [[credentialsId: 'c33d60bd-67c6-4182-b52c-d7aeebfab772', url: 'http://192.168.176.154/root/base-nop.git']]])
|
||||
script {
|
||||
imageTag = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim()
|
||||
}
|
||||
echo "${imageTag}"
|
||||
}
|
||||
stage('Build a Maven project') {
|
||||
echo "${project_name}"
|
||||
sh "cd ${project_name} && mvn clean install -DskipTests -Pproduct -U"
|
||||
}
|
||||
}
|
||||
|
||||
container('sonar'){
|
||||
stage('sonar test'){
|
||||
withSonarQubeEnv(credentialsId: 'sonarqube') {
|
||||
sh "sonar-scanner -X "+
|
||||
"-Dsonar.login=admin " +
|
||||
"-Dsonar.language=java " +
|
||||
"-Dsonar.projectKey=${JOB_NAME} " +
|
||||
"-Dsonar.projectName=${JOB_NAME} " +
|
||||
"-Dsonar.projectVersion=${BUILD_NUMBER} " +
|
||||
"-Dsonar.sources=${WORKSPACE}/fw-base-nop " +
|
||||
"-Dsonar.sourceEncoding=UTF-8 " +
|
||||
"-Dsonar.java.binaries=${WORKSPACE}/fw-base-nop/target/classes " +
|
||||
"-Dsonar.password=admin "
|
||||
}
|
||||
}
|
||||
}
|
||||
withDockerRegistry(credentialsId: 'auth_harbor', url: 'http://192.168.176.155') {
|
||||
stage('build and push docker image') {
|
||||
sh "cp /tmp/Dockerfile ${project_name}/target/"
|
||||
def customImage = docker.build("${registry_url}/library/${project_name}:${imageTag}-${BUILD_NUMBER}","--build-arg jar_name=${project_name}.jar ${project_name}/target/")
|
||||
echo "推送镜像"
|
||||
customImage.push()
|
||||
}
|
||||
stage('delete image') {
|
||||
echo "删除本地镜像"
|
||||
sh "docker rmi -f ${registry_url}/library/${project_name}:${imageTag}-${BUILD_NUMBER}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
有关jenkins与kubernetes集成持续交付的配置就简单的介绍到这里,下一章节内容介绍一下将代码持续部署到kubernetes集群中。
|
||||
|
||||
|
||||
|
||||
|
804
专栏/Jenkins持续交付和持续部署/17.使用Kubernetes插件持续部署服务到Kubernetes集群.md
Normal file
804
专栏/Jenkins持续交付和持续部署/17.使用Kubernetes插件持续部署服务到Kubernetes集群.md
Normal file
@@ -0,0 +1,804 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
17.使用Kubernetes插件持续部署服务到Kubernetes集群
|
||||
kubernetes系列插件的另一个用途就是将编译好的代码持续部署到kubernetes集群中,用到的插件主要为 :
|
||||
|
||||
Kubernetes CLI。-
|
||||
Kubernetes Continuous Deploy(kubernetes cd)。
|
||||
|
||||
下面分别介绍一下这两款插件。
|
||||
|
||||
Kubernetes CLI
|
||||
|
||||
该插件的主要功能是执行流水线脚本时,通过在Jenkins系统中配置的连接kubernetes集群时创建的凭据,使用kubectl命令与kubernetes集群交互。
|
||||
|
||||
withKubeConfig() 是该插件的核心方法,包含的参数如下(前两个参数为必须):
|
||||
|
||||
credentialsId:凭据id,还记得配置jenkins连接kubernetes时创建的凭据吗,就是这个凭据的id。
|
||||
|
||||
serverUrl:kubernetes集群的ApiServer的地址,也就是配置jenkins集成kubernetes时配置的kubernetes的地址。
|
||||
|
||||
caCertificate:用于验证API服务器的证书。如果未提供参数,则跳过验证,非必须。
|
||||
|
||||
clusterName:生成的集群配置的名称,默认为k8s。
|
||||
|
||||
namespace:默认的上下文所在的命名空间。
|
||||
|
||||
contextName:生成的上下文配置的名称,默认值为k8s。
|
||||
|
||||
了解了上面列出的参数,在来看一下withKubeConfig() 的语法,大致如下:
|
||||
|
||||
node {
|
||||
stage('List pods') {
|
||||
withKubeConfig([credentialsId: '<credential-id>',
|
||||
caCertificate: '<ca-certificate>',
|
||||
serverUrl: '<api-server-address>',
|
||||
contextName: '<context-name>',
|
||||
clusterName: '<cluster-name>',
|
||||
namespace: '<namespace>'
|
||||
]) {
|
||||
sh 'kubectl get pods'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
也可以在片段生成器中通过withKubeConfig:Setup Kubernetes CLI生成相应的语法片段,这里就不在演示了,有兴趣的可以自己试一下。
|
||||
|
||||
该方法除了必须项参数外,其他参数用的都比较少,所以这里就不再通过过多的篇幅去介绍如何使用,主要介绍一下两个必须参数的使用用例。
|
||||
|
||||
下面以一个示例来演示一下:
|
||||
|
||||
node {
|
||||
stage('get nodes') {
|
||||
withKubeConfig([credentialsId: 'afffefbf-9216-41dc-b803-7791cdb87134', serverUrl: 'https://192.168.176.156:6443']) {
|
||||
sh 'kubectl get nodes'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
需要说明的是:要使用该插件,首先要确保执行pipeline的agent上有kubectl二进制可执行文件,并具有执行权限。如果执行该pipeline时报如下错误:
|
||||
|
||||
java.io.IOException: error=2, No such file or directory
|
||||
at java.lang.UNIXProcess.forkAndExec(Native Method)
|
||||
......
|
||||
Caused: java.io.IOException: Cannot run program "kubectl": error=2, No such file or directory
|
||||
......
|
||||
Finished: FAILURE
|
||||
|
||||
|
||||
该错误表示没有kubectl命令或者有该命令但是从全局环境变量中没有找到该命令。
|
||||
|
||||
解决方法是修改/etc/profile文件的export PATH=参数,添加存放常用的可执行文件的路径,或者将kubectl二进制文件放到该参数指定的路径下,比如:
|
||||
|
||||
export PATH=/root/local/bin:/usr/bin:/usr/sbin:$PATH
|
||||
|
||||
|
||||
表示kubectl放到这些路径下是可以被找到的,修改完该文件执行source /etc/profile命令使修改生效。
|
||||
|
||||
在jenkins中使用kubectl与kubernetes集群交互的实际工作场景并不多见,大多数场景是使用该命令根据定义的资源对象文件去部署代码。
|
||||
|
||||
使用kubectl容器
|
||||
|
||||
使用kubectl命令的另一种方式就是使用kubectl容器,可以根据一个小体积的镜像自定义一个可以与kubernetes集群交互的镜像,然后每次部署时就使用该镜像完成部署操作。比如下面的dockerfile。
|
||||
|
||||
FROM alpine
|
||||
USER root
|
||||
COPY kubectl /usr/bin/kubectl
|
||||
RUN chmod +x /usr/bin/kubectl && mkdir -p /root/.kube/
|
||||
COPY config /root/.kube/config
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
|
||||
kubectl 文件为与集群版本对应的二进制可执行文件。
|
||||
config 文件为kubectl客户端命令与 kubernetes集群交互的配置认证文件,默认位置为/root/.kube/config。
|
||||
|
||||
|
||||
根据这个dockerfile构建镜像。
|
||||
|
||||
docker build -t alpine-kubectl:1.14 -f dockerfile .
|
||||
|
||||
|
||||
我的kubernetes集群版本为1.14,所以就打个1.14的tag。
|
||||
|
||||
镜像构建好以后可通过命令查看该镜像是否有效。
|
||||
|
||||
$ docker run -it --rm alpine-kubectl:1.14 kubectl get nodes
|
||||
NAME STATUS ROLES AGE VERSION
|
||||
192.168.176.151 Ready <none> 171d v1.14.1
|
||||
192.168.176.152 Ready <none> 171d v1.14.1
|
||||
......
|
||||
|
||||
|
||||
看到命令成功执行,说明自定义镜像构建成功,接下来就可以使用此镜像操作kubernetes集群了。
|
||||
|
||||
如下示例:
|
||||
|
||||
podTemplate(cloud: 'kubernetes',namespace: 'default', label: 'TEST-CD',
|
||||
containers: [
|
||||
containerTemplate(
|
||||
name: 'test-cd',
|
||||
image: "192.168.176.155/library/alpine-kubectl:1.14",
|
||||
ttyEnabled: true,
|
||||
privileged: true,
|
||||
alwaysPullImage: false)
|
||||
],
|
||||
){
|
||||
node('TEST-CD'){
|
||||
stage('test-cd'){
|
||||
container('test-cd'){
|
||||
sh 'kubectl get nodes'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
该示例仅做参考,在实际工作中可以根据实际情况定义要如何实现持续部署到集群。
|
||||
|
||||
Kubernetes Continuous Deploy
|
||||
|
||||
简称kubernetes cd,该插件用于将kubernetes中的资源对象部署到kubernetes集群中。
|
||||
|
||||
它提供以下功能:
|
||||
|
||||
|
||||
通过SSH从主节点获取cluster credentials ,也可以手动配置它。
|
||||
资源配置的变量替换,使您可以进行动态资源部署。
|
||||
提供登录私有Docker registry的登录凭证管理。
|
||||
无需在Jenkins agent节点上安装kubectl工具。
|
||||
|
||||
|
||||
支持的kubernetes资源对象如下:
|
||||
|
||||
下面列出了支持的资源对象类型以及所支持的扩展组信息。
|
||||
|
||||
|
||||
ConfigMap (v1)
|
||||
Daemon Set (apps/v1、extensions/v1beta1、apps/v1beta2)
|
||||
Deployment (apps/v1、apps/v1beta1、extensions/v1beta1、apps/v1beta2)
|
||||
Ingress (extensions/v1beta1、 networking.k8s.io/v1beta1)
|
||||
Job (batch/v1)
|
||||
Namespace (v1)
|
||||
Pod (v1)
|
||||
Replica Set (apps/v1、extensions/v1beta1、apps/v1beta2)
|
||||
Replication Controller (v1) - 不支持滚动升级,如果需要,请使用 Deployment.
|
||||
Secret (v1) - 同样支持secret配置
|
||||
Service (v1)
|
||||
Stateful Set (apps/v1、apps/v1beta1、apps/v1beta2) apps/v1
|
||||
Cron Job (batch/v1beta1、batch/v2alpha1)
|
||||
Horizontal Pod Autoscaler(autoscaling/v1、autoscaling/v2beta1、autoscaling/v2beta2)
|
||||
Network Policy (networking.k8s.io/v1)
|
||||
Persistent Volume (v1)
|
||||
Persistent Volume Claim (v1)
|
||||
ClusterRole (rbac.authorization.k8s.io/v1)
|
||||
ClusterRoleBinding (rbac.authorization.k8s.io/v1)
|
||||
Role (rbac.authorization.k8s.io/v1)
|
||||
RoleBinding (rbac.authorization.k8s.io/v1)
|
||||
ServiceAccount (v1)
|
||||
|
||||
|
||||
上面列出的资源对象,熟悉kubernetes的同学应该都知道是做什么用的。对于使用该插件持续部署应用代码来说,了解Deployment、service、configmap、ingress这些资源对象就足够了,几乎所有的持续部署使用的资源对象都是Deployment,而对于其他类型的资源对象,一般不会通过jenkins去间接操作。本系列文章不是kubernetes相关的课程,所以有关上面列出的资源对象的内容就不过多介绍。
|
||||
|
||||
要使用cd插件与kubernetes集群交互,就需要添加一个凭证,Jenkins使用该凭证可以通过kubectl命令对kubernetes集群有操作权限,与配置jenkins集成kubernetes时创建的凭证不同的是,该凭证的类型为Kubernetes configuration (kubeconfig),是直接使用kubectl命令与kubernetes集群交互时使用的kubeconfig文件(默认路径/root/.kube/config),而不用根据文件内容去生成相应的认证证书。
|
||||
|
||||
在jenkins中创建使用kubeconfig文件凭证的方式也分多种:
|
||||
|
||||
|
||||
直接输入kubeconfig内容。
|
||||
在Jenkins master上设置kubeconfig的路径。
|
||||
从远程SSH服务器获取kubeconfig文件。
|
||||
|
||||
|
||||
下面以直接使用kubeconfig文件内容为例,演示如何创建该凭证。
|
||||
|
||||
点击”Credentials(凭据)-—-> system(系统)-—> Global Credentials(全局凭据)-—> Add Credentials(添加凭据)” ,如下图所示:
|
||||
|
||||
|
||||
|
||||
直接将kubeconfig文件的内容粘贴到Content框里,然后设置一个ID名称即可,在编写pipeline脚本时,使用创建的这个凭据的ID名称来对其引用。全部配置好保存即可。
|
||||
|
||||
创建好了凭据,就可以使用这个插件了。kubernetes cd插件既可以在普通类型的Jenkins Job中配置使用,也支持在pipeline脚本中使用。
|
||||
|
||||
在普通类型(比如自由风格类型、maven类型)的Job中,配置Job时使用”Add build step(添加构建步骤)”选项,从列出的选项列表中选择”Deploy to kubernetes“。
|
||||
|
||||
比如我在一个自由风格类型的job中配置在kubernetes集群中创建一个namespace,如下所示:
|
||||
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
kubeconfig:这里应该选择上面创建的凭证即可。
|
||||
|
||||
Config Files:为定义的kubernetes资源对象文件,这里的路径默认为jenkins项目的${WORKSPACE}路径,如果有多个资源对象文件,要使用英文逗号”,“分开。需要注意的是,该文件的路径不支持绝对路径的写法,比如/root/ns.yaml,但是支持基于workspace的相对路径,比如job/ns.yaml(此时的文件路径为${WORKSPACE}/job/ns.yaml),更多该文件匹配的相关写法可参考这里
|
||||
|
||||
ns.yaml :是一个namespace类型的资源对象文件,内容比较简单,如下:
|
||||
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: test-deploy-plugin
|
||||
|
||||
|
||||
执行结果这里就不贴图了,想看效果的可以自己去试一下。
|
||||
|
||||
在pipeline类型的job中使用cd插件,就要编写pipeline脚本了,开始之前还是要先了解一下该插件的语法 。
|
||||
|
||||
kubernetes-cd插件提供了kubernetesDeploy方法支持对pipeline脚本的编写。可以用kubernetesDeploy: Deploy to kubernetes片段生成器生成一个该方法的语法片段,如下所示:
|
||||
|
||||
|
||||
|
||||
美化一下语法。
|
||||
|
||||
kubernetesDeploy(kubeconfigId: 'k8s_auth', //必须项
|
||||
|
||||
configs: 'xx-deploy.yaml,xx-service.yaml,xx-ingress.yaml', // 必须项
|
||||
enableConfigSubstitution: false,
|
||||
secretNamespace: '<secret-namespace>',
|
||||
secretName: '<secret-name>',
|
||||
dockerCredentials: [
|
||||
[credentialsId: '<credentials-id-for-docker-hub>'],
|
||||
[credentialsId: 'auth_harbor', url: 'http://192.168.176.154'],
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
kubeconfigId:存储在Jenkins中的kubeconfig的凭据ID,必须。
|
||||
|
||||
configs:相关的资源对象文件,多个文件用逗号”,“隔开,必须。
|
||||
|
||||
enableConfigSubstitution:在配置中启用变量替换,非必须。
|
||||
|
||||
secretNamespace:对docker私有仓库认证的secret所在的命名空间,通常用于从私有仓库拉取镜像,非必须。
|
||||
|
||||
secretName:对docker私有仓库认证的secret名称,该secret的内容包含了私有仓库的地址信息以及登录私有仓库的用户名和密码。
|
||||
|
||||
使用资源对象deployment部署pod的时候通常要从Docker私有仓库拉取应用服务的镜像,对于需要认证的镜像仓库,拉取应用镜像时有两种认证方式:一种是部署pod的宿主机已经做了认证,此时不需要对现有资源对象文件进行额外定义就能直接拉取镜像;另一种是宿主机没有做认证,就需要在资源对象pod中定义imagepullsecret参数,这样拉取镜像时就会使用该secret指定的用户名和密码对仓库地址进行认证并拉取镜像,但是有一点需要注意,无论宿主机有没有做认证,都需要在宿主机上的docker配置文件中使用insecure-registries参数指定私有仓库的地址。
|
||||
|
||||
如果该参数存在,该名称将以KUBERNETES_SECRET_NAME作为环境变量名称显示,在资源对象deployment中引用时就可以写该变量名,如下所示代码:
|
||||
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: sample-k8s-deployment
|
||||
spec:
|
||||
replicas: 1
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: sample-k8s-app
|
||||
spec:
|
||||
containers:
|
||||
- name: sample-k8s-app-container
|
||||
image: <username or registry URL>/<image_name>:<tag(maybe $BUILD_NUMBER)>
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
imagePullSecrets:
|
||||
- name: $KUBERNETES_SECRET_NAME
|
||||
|
||||
|
||||
至于该secret如何创建,将在下面实践小节演示。
|
||||
|
||||
dockerCredentials:docker私有仓库认证的方法,通常用于从私有仓库拉取镜像,非必须。
|
||||
|
||||
有关使用kubernetes插件将代码持续部署到kubernetes集群的使用方法就介绍到这里,下面根据前面章节针对这两种插件进行一个简单实践。
|
||||
|
||||
持续部署示例
|
||||
|
||||
使用kubernetesDeploy插件
|
||||
|
||||
在上一节持续交付的基础下,添加新stage。
|
||||
|
||||
stage('deploy to k8s') {
|
||||
sh """
|
||||
cd /tmp/k8s_yaml/${project_name}/
|
||||
sed -i 's#fw-base-nop:.*-*[0-9]\$#fw-base-nop:${imageTag}-${BUILD_NUMBER}#' deployment.yaml
|
||||
cp * ${WORKSPACE}/
|
||||
"""
|
||||
|
||||
kubernetesDeploy (
|
||||
kubeconfigId:"k8s_auth",
|
||||
configs: "*.yaml",
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
该流水线脚本使用shell命令修改deployment资源对象中image参数的值为新构建镜像的的镜像名称,然后将所有的资源对象拷贝到workspace目录下,然后执行流水线时会自动查找匹配的yaml文件。
|
||||
|
||||
上面是一个基础的示例,旨在演示一下流程。实际工作中,可以将这些资源对象文件放到源代码仓库指定的文件夹下,比如放到项目根路径下kubernetes-deploy目录里,上面pipeline脚本可以修改如下:
|
||||
|
||||
stage('deploy to k8s') {
|
||||
sh """
|
||||
sed -i 's#fw-base-nop:.*-*[0-9]\$#fw-base-nop:${imageTag}-${BUILD_NUMBER}#' kubernetes-deploy/deployment-${project_name}.yaml
|
||||
cp * ${WORKSPACE}/
|
||||
"""
|
||||
|
||||
kubernetesDeploy (
|
||||
kubeconfigId:"k8s_auth",
|
||||
configs: "kubernetes-deploy/*.yaml",
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
需要注意的是:
|
||||
|
||||
kubernetes-cd插件默认会自动解析kubernetes资源对象文件,如果遇到语法错误的设置会报错并退出。
|
||||
|
||||
configs: “*.yaml”:这里既可以指定单个文件,也可以指定多个文件,默认目录为job的workspace目录。
|
||||
|
||||
资源对象文件定义需要注意的是,deployment.yml 文件中,如果做了滚动更新的配置(目前只发现此处需要做改动),maxSurge和maxUnavailable需要写成百分比的形式。
|
||||
|
||||
strategy:
|
||||
rollingUpdate:
|
||||
maxSurge: 100%
|
||||
maxUnavailable: 0%
|
||||
|
||||
|
||||
|
||||
|
||||
如果写成int类型的数字,jenkins插件识别的过程中会报如下错误:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
在前面的章节有提到过异常处理,在整个成熟的流水线脚本中,默认会出现错误的步骤一般发生在代码编译过程,所以还需要在此处加一个异常处理操作。对于脚本式语法依然使用try/catch/finally,如下所示:
|
||||
|
||||
try{
|
||||
container('jnlp'){
|
||||
stage('clone code'){
|
||||
checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, userRemoteConfigs: [[credentialsId: 'c33d60bd-67c6-4182-b52c-d7aeebfab772', url: 'http://192.168.176.154/root/base-nop.git']]])
|
||||
script {
|
||||
imageTag = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim()
|
||||
}
|
||||
echo "${imageTag}"
|
||||
}
|
||||
|
||||
stage('Build a Maven project') {
|
||||
ecoho "${project_name}"
|
||||
sh "cd ${project_name} && mvn clean install -DskipTests -Pproduct -U"
|
||||
currentBuild.result = 'SUCCESS'
|
||||
}
|
||||
}
|
||||
}catch(e){
|
||||
currentBuild.result = 'FAILURE'
|
||||
}
|
||||
|
||||
|
||||
说明
|
||||
|
||||
try指令包含的内容可以是任何一个或者多个stage,无论包含哪个stage,catch指令都要紧跟try指令。
|
||||
|
||||
我这里演示的示例try指令包含的stage为拉取和编译代码的过程,可以将try指令放到任何你想要捕获异常的步骤,根据个人实际情况修改。
|
||||
|
||||
需要注意的是,catch能捕获的异常通常是命令执行失败或者插件的某个方法配置不当这种类型的错误,对于jenkins内部返回的异常(比如没有某个属性或者某个指令不适用于该流水线脚本)是不会捕获到的,此时如果先前做了job执行状态判断,即便job失败了,发送邮件操作也不会执行。
|
||||
|
||||
完整的代码示例如下:
|
||||
|
||||
def project_name = 'fw-base-nop' //项目名称
|
||||
def registry_url = '192.168.176.155' //镜像仓库地址
|
||||
|
||||
podTemplate(cloud: 'kubernetes',namespace: 'default', label: 'pre-CICD',
|
||||
serviceAccount: 'default', containers: [
|
||||
containerTemplate(
|
||||
name: 'jnlp',
|
||||
image: "192.168.176.155/library/jenkins-slave:latest",
|
||||
args: '${computer.jnlpmac} ${computer.name}',
|
||||
ttyEnabled: true,
|
||||
privileged: true,
|
||||
alwaysPullImage: false,
|
||||
),
|
||||
containerTemplate(
|
||||
name: 'sonar',
|
||||
image: "192.168.176.155/library/jenkins-slave:sonar",
|
||||
ttyEnabled: true,
|
||||
privileged: true,
|
||||
command: 'cat',
|
||||
alwaysPullImage: false,
|
||||
),
|
||||
],
|
||||
volumes: [
|
||||
nfsVolume(mountPath: '/tmp', serverAddress: '192.168.177.43', serverPath: '/data/nfs', readOnly: false),
|
||||
hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock'),
|
||||
nfsVolume(mountPath: '/root/.m2', serverAddress: '192.168.177.43', serverPath: '/data/nfs/.m2', readOnly: false),
|
||||
],
|
||||
){
|
||||
node('pre-CICD') {
|
||||
stage('build') {
|
||||
try{
|
||||
container('jnlp'){
|
||||
stage('clone code'){
|
||||
checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, userRemoteConfigs: [[credentialsId: 'c33d60bd-67c6-4182-b52c-d7aeebfab772', url: 'http://192.168.176.154/root/base-nop.git']]])
|
||||
script {
|
||||
imageTag = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim()
|
||||
}
|
||||
echo "${imageTag}"
|
||||
}
|
||||
|
||||
stage('Build a Maven project') {
|
||||
sh "cd ${project_name} && mvna clean install -DskipTests -Pproduct -U"
|
||||
currentBuild.result = 'SUCCESS'
|
||||
}
|
||||
}
|
||||
}catch(e){
|
||||
currentBuild.result = 'FAILURE'
|
||||
}
|
||||
if (currentBuild.result == null || currentBuild.result == 'SUCCESS') {
|
||||
container('sonar'){
|
||||
stage('sonar test'){
|
||||
withSonarQubeEnv(credentialsId: 'sonarqube') {
|
||||
sh "sonar-scanner -X "+
|
||||
"-Dsonar.login=admin " +
|
||||
"-Dsonar.language=java " +
|
||||
"-Dsonar.projectKey=${JOB_NAME} " +
|
||||
"-Dsonar.projectName=${JOB_NAME} " +
|
||||
"-Dsonar.projectVersion=${BUILD_NUMBER} " +
|
||||
"-Dsonar.sources=${WORKSPACE}/fw-base-nop " +
|
||||
"-Dsonar.sourceEncoding=UTF-8 " +
|
||||
"-Dsonar.java.binaries=${WORKSPACE}/fw-base-nop/target/classes " +
|
||||
"-Dsonar.password=admin "
|
||||
}
|
||||
}
|
||||
}
|
||||
withDockerRegistry(credentialsId: 'auth_harbor', url: 'http://192.168.176.155') {
|
||||
stage('build and push docker image') {
|
||||
sh "cp /tmp/Dockerfile ${project_name}/target/"
|
||||
def customImage = docker.build("${registry_url}/base/${project_name}-${imageTag}:${BUILD_NUMBER}","--build-arg jar_name=${project_name}.jar ${project_name}/target/")
|
||||
echo "推送镜像"
|
||||
customImage.push()
|
||||
}
|
||||
stage('delete image') {
|
||||
echo "删除本地镜像"
|
||||
sh "docker rmi -f ${registry_url}/base/${project_name}:${imageTag}-${BUILD_NUMBER}"
|
||||
}
|
||||
}
|
||||
}else {
|
||||
echo "---currentBuild.result is:${currentBuild.result}"
|
||||
emailext (
|
||||
subject: "'${env.JOB_NAME} [${env.BUILD_NUMBER}]' 构建异常",
|
||||
body: """
|
||||
详情:\n
|
||||
failure: Job ${env.JOB_NAME} [${env.BUILD_NUMBER}] \n
|
||||
状态:${env.JOB_NAME} jenkins 构建异常 \n
|
||||
URL :${env.BUILD_URL} \n
|
||||
项目名称 :${env.JOB_NAME} \n
|
||||
项目构建id:${env.BUILD_NUMBER} \n
|
||||
信息: 代码编译失败
|
||||
|
||||
""",
|
||||
to: "[email protected]",
|
||||
recipientProviders: [[$class: 'DevelopersRecipientProvider']]
|
||||
)
|
||||
}
|
||||
stage('deploy'){
|
||||
if (currentBuild.result == null || currentBuild.result == 'SUCCESS') {
|
||||
sh """
|
||||
cd /tmp/k8s_yaml/${project_group}/${project_name}
|
||||
sed -i 's#fw-base-nop:.*-*[0-9]\$#fw-base-nop:${imageTag}-${BUILD_NUMBER}#' deployment.yaml
|
||||
cp * ${WORKSPACE}/
|
||||
"""
|
||||
kubernetesDeploy (
|
||||
kubeconfigId:"k8s_auth",
|
||||
configs: "*.yaml",
|
||||
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
imagePullSecrets
|
||||
|
||||
imagepullsecrets通过指定的secret名称,用于执行创建资源对象操作后,在拉取容器镜像时对docker私有仓库进行认证。
|
||||
|
||||
在kubernetes系列插件中,只有在kubernetes-plugin插件配置PodTemplate方法时有提到过imagepullsecrets,只不过此时的secret只是用于拉取提供pipeline脚本执行环境的容器镜像,不能用于在kubernetes集群部署应用容器,在拉取镜像时对私有仓库进行认证。
|
||||
|
||||
前面有提到过,拉取应用镜像时有两种认证方式:一种宿主机对私有仓库已经做了认证,此时直接部署即可;另一种是宿主机没有做认证,就需要在资源对象pod中定义imagepullsecret参数,这样拉取镜像时就会使用该secret指定的用户名和密码对仓库地址进行认证并拉取镜像。
|
||||
|
||||
那么这个secret是如何创建的呢?比较简单,使用一条命令即可。
|
||||
|
||||
kubectl create secret docker-registry <name> --docker-server=DOCKER_REGISTRY_SERVER --docker-username=DOCKER_USER --docker-password=DOCKER_PASSWORD --docker-email=DOCKER_EMAIL
|
||||
|
||||
|
||||
其中:
|
||||
|
||||
|
||||
DOCKER_REGISTRY_SERVER:docker私有仓库服务的地址。
|
||||
DOCKER_USER:向docker私有仓库认证的用户名。
|
||||
DOCKER_PASSWORD:向docker私有仓库认证的密码。
|
||||
默认如果没有加-n参数,该secret被创建在default命名空间,所以如果要将pod部署在指定的namespace里同时需要在该namespace里创建secret。
|
||||
|
||||
|
||||
创建好secret后,编辑资源对象文件(通常为deployment.yaml),在template.spec下添加。
|
||||
|
||||
imagePullSecrets:
|
||||
- name: <secret_name>
|
||||
|
||||
|
||||
这样部署pod时会自动对私有仓库进行认证,而不在关心宿主机是否对私有仓库进行了认证。只不过这样就需要在每一个要部署应用容器的namespace中创建imagepullsecret。实际工作中虽然可以这样做,但是如果在项目组和namespace数量很大的的情况下,需要在每一个namespace中创建该secret,每次新建namepace都要创建一边该secret,显然不是很好的解决方案。好在kubedeploy提供了更简单的方式来实现此功能,就是使用kubernetesDeploy方法中的secret等参数,下面详细介绍一下。
|
||||
|
||||
kubernetesDeploy方法的dockerCredentials参数用于对docker私有仓库认证,如果secretName参数的值没有指定,则生成一个唯一的secret名称并以KUBERNETES_SECRET_NAME作为环境变量名被资源对象引用;如果设置了secretName,则更新这个secret;然后通过开启变量替换就可以被在secretNamespace指定的命名空间内创建的pod引用。
|
||||
|
||||
具体配置为:
|
||||
|
||||
kubernetesDeploy (
|
||||
kubeconfigId:"k8s_auth",
|
||||
configs: "*.yaml",
|
||||
enableConfigSubstitution: true,
|
||||
secretNamespace: 'base',
|
||||
secretName: 'test-mysecret',
|
||||
dockerCredentials: [
|
||||
[credentialsId: 'auth_harbor', url: 'http://192.168.176.155'],
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
其中:
|
||||
|
||||
secretName最好不要与指定的namespace内存在的secret名称相同。
|
||||
|
||||
dockerCredentials 里credentialsId为创建的对私有仓库认证的凭据ID,url为私有仓库的url。
|
||||
|
||||
此时的资源对象文件imagepullsecret配置应该为:
|
||||
|
||||
imagePullSecrets:
|
||||
- name: $KUBERNETES_SECRET_NAME
|
||||
|
||||
|
||||
配置好后,kubernetesDeploy在解析资源对象文件的时候会自动对KUBERNETES_SECRET_NAME变量进行替换,将在kubernetesDeploy方法中设定的secretName参数定义的值替换该变量,并部署。这样,对于任何namespace下的容器部署都不需要在单独的配置镜像仓库的认证,只需要在配置kubernetesDeploy方法下secretNamespace参数的值即可,而无需在每一个namespace下都创建secret。
|
||||
|
||||
完整的stage如下。
|
||||
|
||||
stage('deploy'){
|
||||
if (currentBuild.result == null || currentBuild.result == 'SUCCESS') {
|
||||
sh """
|
||||
cd /tmp/k8s_yaml/${project_group}/${project_name}
|
||||
sed -i 's#fw-base-nop:.*-*[0-9]\$#fw-base-nop:${imageTag}-${BUILD_NUMBER}#' deployment.yaml
|
||||
cp * ${WORKSPACE}/
|
||||
"""
|
||||
kubernetesDeploy (
|
||||
kubeconfigId:"k8s_auth",
|
||||
configs: "*.yaml",
|
||||
enableConfigSubstitution: true,
|
||||
secretNamespace: 'base',
|
||||
secretName: 'test-mysecret',
|
||||
dockerCredentials: [
|
||||
[credentialsId: 'auth_harbor', url: 'http://192.168.176.155'],
|
||||
]
|
||||
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
执行时在jenkins console中可以看到该值进行了替换,如下所示:-
|
||||
|
||||
|
||||
使用kubernetes cli插件
|
||||
|
||||
该插件如何使用上面已经做了介绍,我这里的示例直接定义一个新的镜像配置(根据自己实际情况配置),使用前面创建的kubectl容器在持续部署步骤使用withKubeConfig方法用apply命令直接更新服务。
|
||||
|
||||
下面直接看一下pipeline脚本。
|
||||
|
||||
def project_name = 'fw-base-nop' //项目名称
|
||||
def registry_url = '192.168.176.155' //镜像仓库地址
|
||||
def project_group = 'base'
|
||||
|
||||
podTemplate(cloud: 'kubernetes',namespace: 'default', label: 'pre-CICD',
|
||||
serviceAccount: 'default', containers: [
|
||||
containerTemplate(
|
||||
name: 'jnlp',
|
||||
image: "192.168.176.155/library/jenkins-slave:sonar",
|
||||
args: '${computer.jnlpmac} ${computer.name}',
|
||||
ttyEnabled: true,
|
||||
privileged: true,
|
||||
alwaysPullImage: false,
|
||||
),
|
||||
containerTemplate(
|
||||
name: 'kubectl',
|
||||
image: "192.168.176.155/library/alpine-kubectl:1.14",
|
||||
ttyEnabled: true,
|
||||
privileged: true,
|
||||
command: 'cat',
|
||||
alwaysPullImage: false,
|
||||
),
|
||||
],
|
||||
volumes: [
|
||||
nfsVolume(mountPath: '/tmp', serverAddress: '192.168.177.43', serverPath: '/data/nfs', readOnly: false),
|
||||
hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock'),
|
||||
nfsVolume(mountPath: '/root/.m2', serverAddress: '192.168.177.43', serverPath: '/data/nfs/.m2', readOnly: false),
|
||||
],
|
||||
){
|
||||
node('pre-CICD') {
|
||||
stage('build') {
|
||||
try{
|
||||
container('jnlp'){
|
||||
stage('clone code'){
|
||||
checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, userRemoteConfigs: [[credentialsId: 'c33d60bd-67c6-4182-b52c-d7aeebfab772', url: 'http://192.168.176.154/root/base-nop.git']]])
|
||||
script {
|
||||
imageTag = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim()
|
||||
}
|
||||
echo "${imageTag}"
|
||||
currentBuild.result == 'SUCCESS'
|
||||
}
|
||||
|
||||
stage('Build a Maven project') {
|
||||
sh "cd ${project_name} && mvn clean install -DskipTests -Pproduct -U"
|
||||
currentBuild.result = 'SUCCESS'
|
||||
}
|
||||
}
|
||||
}catch(e){
|
||||
currentBuild.result = 'FAILURE'
|
||||
}
|
||||
if (currentBuild.result == null || currentBuild.result == 'SUCCESS') {
|
||||
container('jnlp'){
|
||||
stage('sonar test'){
|
||||
withSonarQubeEnv(credentialsId: 'sonarqube') {
|
||||
sh "sonar-scanner -X "+
|
||||
"-Dsonar.login=admin " +
|
||||
"-Dsonar.language=java " +
|
||||
"-Dsonar.projectKey=${JOB_NAME} " +
|
||||
"-Dsonar.projectName=${JOB_NAME} " +
|
||||
"-Dsonar.projectVersion=${BUILD_NUMBER} " +
|
||||
"-Dsonar.sources=${WORKSPACE}/fw-base-nop " +
|
||||
"-Dsonar.sourceEncoding=UTF-8 " +
|
||||
"-Dsonar.java.binaries=${WORKSPACE}/fw-base-nop/target/classes " +
|
||||
"-Dsonar.password=admin "
|
||||
}
|
||||
}
|
||||
}
|
||||
withDockerRegistry(credentialsId: 'auth_harbor', url: 'http://192.168.176.155') {
|
||||
stage('build and push docker image') {
|
||||
sh "cp /tmp/Dockerfile ${project_name}/target/"
|
||||
def customImage = docker.build("${registry_url}/library/${project_name}:${imageTag}-${BUILD_NUMBER}","--build-arg jar_name=${project_name}.jar ${project_name}/target/")
|
||||
echo "推送镜像"
|
||||
customImage.push()
|
||||
}
|
||||
stage('delete image') {
|
||||
echo "删除本地镜像"
|
||||
sh "docker rmi -f ${registry_url}/library/${project_name}:${imageTag}-${BUILD_NUMBER}"
|
||||
}
|
||||
}
|
||||
}else {
|
||||
echo "---currentBuild.result is:${currentBuild.result}"
|
||||
emailext (
|
||||
subject: "'${env.JOB_NAME} [${env.BUILD_NUMBER}]' 构建异常",
|
||||
body: """
|
||||
详情:<br>
|
||||
failure: Job ${env.JOB_NAME} [${env.BUILD_NUMBER}] <br>
|
||||
状态:${env.JOB_NAME} jenkins 构建异常 <br>
|
||||
URL :${env.BUILD_URL} <br>
|
||||
项目名称 :${env.JOB_NAME} <br>
|
||||
项目构建id:${env.BUILD_NUMBER} <br>
|
||||
信息: 代码编译失败
|
||||
|
||||
""",
|
||||
to: "[email protected]",
|
||||
recipientProviders: [[$class: 'DevelopersRecipientProvider']]
|
||||
)
|
||||
}
|
||||
stage('deploy'){
|
||||
if (currentBuild.result == null || currentBuild.result == 'SUCCESS') {
|
||||
container('kubectl'){
|
||||
withKubeConfig(credentialsId: 'afffefbf-9216-41dc-b803-7791cdb87134', serverUrl:'https://192.168.176.156:6443') {
|
||||
sh """
|
||||
cd /tmp/k8s_yaml/${project_group}/${project_name}
|
||||
sed -i 's#fw-base-nop:.*-*[0-9]\$#${project_name}:${imageTag}-${BUILD_NUMBER}#' deployment.yaml
|
||||
cp * ${WORKSPACE}/
|
||||
kubectl apply -f .
|
||||
"""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
执行结果如下:
|
||||
|
||||
|
||||
|
||||
上面的脚本依然可以放到应用代码的源码库中,通过配置Pipeline Script from SCM的方式从代码仓库拉取Jenkinsfile并执行,如何配置在前面的章节有过介绍,就不在重复说明。根据实际情况选择部署方式即可。
|
||||
|
||||
在文章的最后给出一个deployment资源对象的定义做一个参考。
|
||||
|
||||
---
|
||||
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: fw-base-nop
|
||||
namespace: base
|
||||
spec:
|
||||
replicas: 1
|
||||
strategy:
|
||||
rollingUpdate:
|
||||
maxSurge: 100%
|
||||
maxUnavailable: 0%
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
name: fw-base-nop
|
||||
spec:
|
||||
terminationGracePeriodSeconds: 60
|
||||
#imagePullSecrets:
|
||||
#- name: $KUBERNETES_SECRET_NAME
|
||||
containers:
|
||||
- name: fw-base-nop
|
||||
image: 192.168.176.155/base/fw-base-nop:bc29b5e-37
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /monitor/info
|
||||
port: 18082
|
||||
scheme: HTTP
|
||||
initialDelaySeconds: 300
|
||||
timeoutSeconds: 10
|
||||
successThreshold: 1
|
||||
failureThreshold: 5
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /monitor/info
|
||||
port: 18082
|
||||
scheme: HTTP
|
||||
initialDelaySeconds: 300
|
||||
timeoutSeconds: 10
|
||||
successThreshold: 1
|
||||
failureThreshold: 5
|
||||
env:
|
||||
- name: TZ
|
||||
value: "Asia/Shanghai"
|
||||
- name: JAVA_OPTS
|
||||
value: "-Xms1228m -Xmx1228m"
|
||||
- name: _JAVA_OPTIONS
|
||||
value: "-Xms1228m -Xmx1228m"
|
||||
ports:
|
||||
- containerPort: 8082
|
||||
resources:
|
||||
limits:
|
||||
cpu: 1024m
|
||||
memory: 2048Mi
|
||||
requests:
|
||||
cpu: 1024m
|
||||
memory: 2048Mi
|
||||
volumeMounts:
|
||||
- name: fw-base-nop-logs
|
||||
mountPath: /data/logs/fw-base-nop
|
||||
- name: host-resolv
|
||||
mountPath: /etc/resolv.conf
|
||||
- name: tz-config
|
||||
mountPath: /etc/localtime
|
||||
|
||||
volumes:
|
||||
- name: host-resolv
|
||||
hostPath:
|
||||
path: /etc/resolv.conf
|
||||
- name: tz-config
|
||||
hostPath:
|
||||
path: /etc/localtime
|
||||
- name: fw-base-nop-logs
|
||||
emptyDir: {}
|
||||
|
||||
|
||||
有关使用kubernetes插件持续部署的内容到此结束,本节内容只是给大家提供了一个选择的方案。在实际工作中还需要做好调研在考虑要如何去实现持续部署操作。
|
||||
|
||||
|
||||
|
||||
|
@@ -0,0 +1,886 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
18.Ansible Plugin插件语法详解与持续部署服务到kubernetes集群
|
||||
在实际工作中使用容器编排系统对容器编排已经成为使用容器技术的常态。而在前面的Ansible章节中仅仅介绍了使用Ansible部署服务到docker容器中,对于容器的编排系统没有说明;并且只是介绍了在Jenkins UI中使用bash shell命令和插件配置来使用Ansible。基于此,本节就来介绍一下如何在Pipeline中使用Ansible,并通过示例来进行演示。
|
||||
|
||||
本节主要介绍两方面的内容:
|
||||
|
||||
在pipeline脚本中使用ansible插件的语法介绍以及使用示例;-
|
||||
将构建好的镜像持续部署到容器编排系统kubernetes中。
|
||||
|
||||
为什么要将本章节放到课程的最后,是因为该文主要讲的是持续部署。对于持续交付操作,在前面的章节中都已经介绍过,所以将前面的持续交付操作直接拿来用就可以了。
|
||||
|
||||
Ansible plugin 语法详解
|
||||
|
||||
在Pipeline类型的job中,可以通过片段生成器生成使用ansible的语法片段,也可以直接在pipeline中执行shell命令。下面主要介绍一下如何使用ansible语法片段。
|
||||
|
||||
进入流水线语法界面,点击”片段生成器菜单“跳转的页面中,steps下拉框的第一个就是我们要使用的片段生成器参数:ansiblePlaybook Invoke and ansible playbook。
|
||||
|
||||
如下所示
|
||||
|
||||
|
||||
|
||||
其中:
|
||||
|
||||
Ansible tool:该选项参数是一个下拉框,用来选择ansible的命令列表(我这里只配置了ansible和ansible-playbook命令),需要注意的是,该选项下拉框中的工具列表需要事先在Jenkins的”全局工具配置“菜单中定义ansible命令的名称。
|
||||
|
||||
Playbook file path in workspace:该参数用来指定要执行的playbook文件,该文件默认从当前的工作空间($WORKSPACE)路径查找。需要注意的是,该参数只能指定具体的文件名称,不能通过正则匹配,也不能指定多个文件。
|
||||
|
||||
Inventory file path in workspace:该参数用来指定主机清单文件,默认的路径同样是当前工作空间目录。如果该参数指定的是一个目录,则会使用该目录下所有文件中定义的主机。
|
||||
|
||||
SSH connection credentials:用来指定连接服务器的凭证,与在前面章节publish over ssh插件用到的凭证一样。如果执行ansible-playbook命令的机器已经做好了服务器免密认证,此处可以不用填写,并且如果要使用该参数时,要确保ansible-playbook命令所在主机存在sshpass命令。
|
||||
|
||||
Vault credentials:对于使用了ansible valut命令加密过后的playbook文件,可以添加该参数(用于指定一个凭证)在执行playbook时自动解密。
|
||||
|
||||
Use become:用于决定是否启用become username。
|
||||
|
||||
Become username:与Use become参数一起使用,指定运行任务的用户 。
|
||||
|
||||
Host subset:host的子集,如果在playbook中定义的inventory(hosts)清单文件是一个组,并且该组下有多个主机(ip或域名),通过该参数可以设置在该组中指定的主机上执行playbook任务,如果有多个主机,则每个主机之间需要用逗号(”,“)隔开。
|
||||
|
||||
Tags:playbook中任务的标签。如果设置了该参数,则只会执行playbook中与该参数值匹配的任务。
|
||||
|
||||
Tags to skip:跳过要执行的标签。如果设置了该参数,则不会执行playbook中与该参数值匹配的任务。
|
||||
|
||||
Task to start at:执行playbook时用于从指定的任务开始执行,该参数的值用于匹配playbook中定义的任务的名称。
|
||||
|
||||
Number of parallel processes to use:对应ansible中的-f参数,指定并发线程数。
|
||||
|
||||
Disable the host SSH key check:使用ssh连接主机时,对于首次连接的主机,ssh会检测本地known_hosts文件中是否存在该主机的host记录,如果不存在则需要手动确认,对于在控制台通过ssh命令连接可以通过手工确认,而对于使用ansible命令时,则需要在ansible.cfg配置文件中设置host_key_checking = False,在jenkins中可以通过勾选此参数框来关闭检查。
|
||||
|
||||
Colorized output:在控制台中是否切换彩色编码,并无多大用处。
|
||||
|
||||
Extra parameters:用于指定要使用的参数变量,需要加上”-e”参数。
|
||||
|
||||
介绍完ansible插件的基本参数以后,下面来看一下该插件的使用方法。
|
||||
|
||||
通过上面的片段生成器生成的语法示例如下:
|
||||
|
||||
ansiblePlaybook(
|
||||
become: true,
|
||||
colorized: true,
|
||||
credentialsId: '',
|
||||
disableHostKeyChecking: true,
|
||||
extras: '..',
|
||||
installation: 'ansible-playbook',
|
||||
inventory: '',
|
||||
limit: '',
|
||||
playbook: '',
|
||||
skippedTags: '',
|
||||
startAtTask: '',
|
||||
sudo: true,
|
||||
tags: '',
|
||||
vaultCredentialsId: ''
|
||||
)
|
||||
|
||||
|
||||
该语法块在声明式语法和脚本式语法中使用的方式相同。实际工作中可能不会用到所有的参数,根据自己需求选择参数即可。
|
||||
|
||||
下面以一个简单的示例演示一下,例如我要执行test.yaml下的任务,只限制在192.168.176.149和192.168.176.148主机上执行。
|
||||
|
||||
代码如下:
|
||||
|
||||
pipeline{
|
||||
agent { node { label 'master' } }
|
||||
stages{
|
||||
stage('Test'){
|
||||
steps{
|
||||
sh "cp -R /data/nfs/ansible $WORKSPACE/"
|
||||
ansiblePlaybook (
|
||||
become: true,
|
||||
disableHostKeyChecking: true,
|
||||
limit: '192.168.176.149,192.168.176.148',
|
||||
installation: 'ansible-playbook',
|
||||
inventory: './ansible/hosts',
|
||||
playbook: './ansible/test.yaml',
|
||||
extras: ''
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
cp -R /data/nfs/ansible $WORKSPACE/:为什么要执行该命令,因为在使用docker pipeline插件或者kubernetes插件的时候,对于动态生成的容器默认是没有这些文件以及目录的,这时候就需要使用挂载共享存储的方式将所需的目录挂载到容器内。这里模拟的就是在容器内执行playbook,当然如果将使用的文件放到了源码仓库中可以忽略这一步。
|
||||
|
||||
limit 参数就是用来限制执行playbook任务的节点,该示例表示在192.168.176.149,192.168.176.148连个节点上执行任务。
|
||||
|
||||
installation 代表使用ansible-playbook命令。
|
||||
|
||||
inventory和playbook 分别使用相对路径指定主机清单文件和playbook文件。
|
||||
|
||||
hosts和test.yaml文件内容分别如下:
|
||||
|
||||
$ cat hosts
|
||||
[k8s_master]
|
||||
192.168.176.148
|
||||
192.168.176.149
|
||||
192.168.176.150
|
||||
|
||||
$ cat test.yaml
|
||||
---
|
||||
- hosts: k8s_master
|
||||
gather_facts: no
|
||||
tasks:
|
||||
- name: test
|
||||
shell: hostname
|
||||
register: host_name
|
||||
- name: debug
|
||||
debug:
|
||||
msg: "{{ host_name.stdout }}"
|
||||
|
||||
|
||||
执行结果如下:
|
||||
|
||||
|
||||
|
||||
从执行结果可以看到在指定的主机上执行了命令。
|
||||
|
||||
通过上面的示例,对于如何使用ansible插件应该有了初步的了解,下面介绍一下将该插件的语法块集成到前面章节的pipeline的脚本中去使用。
|
||||
|
||||
使用Ansible Plugin
|
||||
|
||||
根据前面章节的示例汇总,针对不同类型agent代理,对于该插件的使用有如下情况:-
|
||||
agent代理为虚拟机。-
|
||||
agent代理为容器。-
|
||||
agent代理通过kubernetes编排。
|
||||
|
||||
当agent为虚拟机时使用ansible插件
|
||||
|
||||
当agent代理为虚拟机时,无论是master节点还是node节点,都需要保证节点上已经安装好ansible工具。
|
||||
|
||||
在最初的在pipeline实践章节中已经完成了代码的编译以及镜像的构建和推送到私有仓库操作,我们只需要在此基础上对在”ansible持续交付与部署“章节”部署服务到容器“小节使用的playbook脚本稍作修改,只保留对容器和镜像的相关操作即可。
|
||||
|
||||
修改完的playbook脚本如下:
|
||||
|
||||
$ cat deploy.yaml
|
||||
|
||||
- hosts: "{{ target }}"
|
||||
remote_user: root
|
||||
gather_facts: False
|
||||
|
||||
vars:
|
||||
dest_dict: "/data/{{ project_name }}/"
|
||||
|
||||
tasks:
|
||||
- name: 判断目录是否存在
|
||||
shell: ls {{ dest_dict }}
|
||||
register: dict_exist
|
||||
ignore_errors: true
|
||||
|
||||
- name: 创建相关目录
|
||||
file: dest="{{ item }}" state=directory mode=755
|
||||
with_items:
|
||||
- "{{ dest_dict }}"
|
||||
- /data/logs/{{ project_name }}
|
||||
when: dict_exist is failure
|
||||
|
||||
- name: 查看容器是否存在
|
||||
shell: "docker ps -a --filter name={{ project_name }} |grep -v COMMAND"
|
||||
ignore_errors: true
|
||||
register: container_exists
|
||||
|
||||
- name: 查看容器状态
|
||||
shell: "docker ps -a --filter name={{ project_name }} --format '{{ '{{' }} .Status {{ '}}' }}'"
|
||||
ignore_errors: true
|
||||
register: container_state
|
||||
when: container_exists.rc == 0
|
||||
|
||||
- name: 关闭容器
|
||||
shell: "docker stop {{ project_name }}"
|
||||
when: "('Up' in container_state.stdout)"
|
||||
ignore_errors: true
|
||||
|
||||
- name: 删除容器
|
||||
shell: "docker rm {{ project_name }}"
|
||||
when: container_exists.rc == 0
|
||||
ignore_errors: true
|
||||
|
||||
- name: 查看镜像是否存在
|
||||
command: "docker images --filter reference={{ registry_url }}/{{ project_group }}/{{ project_name }}* --format '{{ '{{' }} .Repository {{ '}}' }}:{{ '{{' }}.Tag {{ '}}' }}'"
|
||||
register: image_exists
|
||||
ignore_errors: true
|
||||
|
||||
- name: 删除镜像
|
||||
shell: "docker rmi -f {{ item }}"
|
||||
loop: "{{ image_exists.stdout_lines }}"
|
||||
ignore_errors: true
|
||||
when: image_exists.rc == 0
|
||||
|
||||
- name: 启动容器
|
||||
shell: 'docker run {{ run_option }} --name {{ project_name }} {{ image_name }}'
|
||||
|
||||
|
||||
主要修改了”查看镜像是否存在“任务获取镜像列表的命令,添加了两个匹配镜像url的变量(此处需要根据你的实际情况修改),在执行playbook的时候需要添加这两个变量。
|
||||
|
||||
对于执行playbook时用到的变量,我们直接在pipeline中定义即可。
|
||||
|
||||
首先看一下在pipeline中使用ansible-playbook命令部署。
|
||||
|
||||
stage('部署镜像'){
|
||||
environment {
|
||||
container_run_option="--network host -e TZ='Asia/Shanghai' -d -v /etc/localtime:/etc/localtime:ro -m 2G --cpu-shares 512 -v /data/logs/${project_name}:/data/logs/${project_name}"
|
||||
}
|
||||
|
||||
steps{
|
||||
sh """
|
||||
cp -R /data/nfs/ansible $WORKSPACE/
|
||||
echo $container_run_option
|
||||
|
||||
ansible-playbook -i $WORKSPACE/ansible/hosts $WORKSPACE/ansible/deploy.yaml -e "workspace='${WORKSPACE}' registry_url=$registry_url project_group=$project_group target='192.168.176.160' project_name='$project_name' run_option='$container_run_option' image_name='${registry_url}/${project_group}/${project_name}:${BUILD_ID}'"
|
||||
"""
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
注意:我这里执行ansible-playbook命令的主机对目标主机默认做好了免密认证。如果对ansible主机对目标主机没有做免密认证,可以通过添加credentialsId参数指定jenkins凭证的方式向远程服务器认证。
|
||||
|
||||
既然介绍了ansible的插件的语法,为什么还要使用ansible-playbook命令呢?是为了测试playbook脚本是否能够正常执行,传入的变量是否有效,以及整个流水线是否能够执行成功等。毕竟在执行playbook的时候传入了许多变量,如果直接使用ansible插件,可能会遇到意想不到的坑,有时候一个不起眼的双引号或者单引号都能导致playbook执行失败。
|
||||
|
||||
如果换成使用ansible插件,pipeline语法快如下:
|
||||
|
||||
stage('部署镜像'){
|
||||
environment {
|
||||
container_run_option="\'--network host -e TZ=Asia/Shanghai -d -v /etc/localtime:/etc/localtime:ro -m 2G --cpu-shares 512 -v /data/logs/${project_name}:/data/logs/${project_name}\' "
|
||||
}
|
||||
|
||||
steps{
|
||||
sh """
|
||||
cp -R /data/nfs/ansible $WORKSPACE/
|
||||
echo $container_run_option
|
||||
"""
|
||||
ansiblePlaybook (
|
||||
become: true,
|
||||
disableHostKeyChecking: true,
|
||||
installation: 'ansible-playbook',
|
||||
inventory: './ansible/hosts',
|
||||
playbook: './ansible/deploy.yaml',
|
||||
extras: ' -e "workspace=${WORKSPACE} target="192.168.176.160" registry_url=$registry_url project_group=$project_group project_name=$project_name run_option=$container_run_option image_name=${registry_url}/${project_group}/${project_name}:${BUILD_ID}"'
|
||||
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
使用ansible插件的时候,要注意变量的引用。变量通过extras参数传入,使用该参数时请务必加上”-e”选项。对于docker运行时的参数,需要使用单引号(” ‘ “)引起来,并添加转义(可不做),防止在转化成实际变量值时出错。
|
||||
|
||||
比如,对于上面的container_run_option运行参数,如果不加单引号,不做转义,实际传递到extras参数的时候是这样的。
|
||||
|
||||
run_option= --network host -e TZ=Asia/Shanghai -d -v /etc/localtime:/etc/localtime:ro -m 2G --cpu-shares 512 -v /data/logs/fw-base-nop:/data/logs/fw-base-nop
|
||||
|
||||
|
||||
传入到playbook中对应的docker启动命令如下:
|
||||
|
||||
docker run --network --name fw-base-nop 192.168.176.155/base/fw-base-nop:289
|
||||
|
||||
|
||||
此时就会导致容器启动失败。
|
||||
|
||||
而使用了引号与转义后是这样的。
|
||||
|
||||
run_option='--network host -e TZ=Asia/Shanghai -d -v /etc/localtime:/etc/localtime:ro -m 2G --cpu-shares 512 -v /data/logs/fw-base-nop:/data/logs/fw-base-nop'
|
||||
|
||||
|
||||
这才是正确的docker启动时的运行参数。
|
||||
|
||||
该示例只是展示了使用ansible部署容器相关的操作,对于代码的编译和镜像的构建还是通过pipeline语法实现的,如果你不想要将这部分内容使用pipeline脚本实现,也可以将这部分操作通过playbook脚本实现,如何编写这里就不在细说了。
|
||||
|
||||
当agent为容器时使用ansible插件
|
||||
|
||||
当agent为容器时分为两种情况,经过前几章的学习,你应该能够猜到了,没错,这两种情况分别是使用docker pipeline插件在虚拟机上生成容器和使用kubernetes 插件编排容器。使用docker pipeline插件时可以选择在容器内使用ansible插件,也可以选择在虚拟机上使用ansible插件;在使用kubernetes插件时,就只能在容器内使用ansible插件了,无论哪种情况都要保证在agent代理上有ansible-playbook命令。
|
||||
|
||||
在虚拟机上使用ansible插件在上一小节已经做过介绍,下面主要介绍一下如何在容器中使用该插件。
|
||||
|
||||
想要在容器中使用ansible插件,需要使用一个单独的ansible镜像,而不能通过在容器中挂载可执行文件的方式使用。这与使用docker可执行文件的方式相似,可执行文件的使用均依赖于当前的系统环境。并且ansible运行时还依赖python环境以及需要用到的各种模块,单独拷贝二进制文件到镜像的方式同样也不适用与ansible。好在docker hub上已经有了各种版本的ansible镜像,我们只需要直接拿过来用即可。
|
||||
|
||||
首先使用镜像做一个简单的测试,还是使用上一小节的示例,只不过此次使用容器作为agent代理,完成playbook的执行。
|
||||
|
||||
代码如下所示:
|
||||
|
||||
pipeline {
|
||||
agent {
|
||||
docker{
|
||||
image 'gableroux/ansible:2.7.10'
|
||||
args '-v /data/:/data/ -v /root/.ssh:/root/.ssh'
|
||||
}
|
||||
}
|
||||
stages('build') {
|
||||
stage('test-ansilbe'){
|
||||
steps{
|
||||
sh "ansible-playbook -i /data/nfs/ansible/hosts /data/nfs/ansible/test.yaml "
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
该示例会自动在某一个节点上启动容器。
|
||||
|
||||
这里需要注意的是:
|
||||
|
||||
1、 在容器内执行ansible-playbook任务时,需要连接到远程主机,此时的ansible主机(容器)是没有对远程服务器经过免密认证的,除了通过在主机清单文件hosts中添加主机参数以外,也可以通过挂载宿主机ssh目录的方式,我这里使用的是root用户,这样在容器中连接远程服务器时不需要单独认证即可完成连接。不过此种方式需要确保宿主机可以通过免秘钥认证连接目标服务器。
|
||||
|
||||
2、 除此之外,在使用ansible插件的ansiblePlaybook方法时,也可以通过”credentialsId“参数指定jenkins凭据来向远程服务器认证。此时,有两个问题需要我们考虑到:
|
||||
|
||||
如果不使用挂载.ssh目录的方式(或者通过在清单文件中添加ansible用户和密码参数的方式),那么还需要配置ansible的配置文件ansible.cfg,因为每启动一个动态容器都相当于一台新的机器,去连接远程主机时会检查本地known_hosts文件有没有远程主机的fingerprint key串,如果没有需要手动输入yes确认,而使用ansible执行任务时默认是不能手动输入的,所以还需要在ansible配置文件中配置host_key_checking = False来解决此问题,当然你也可以在使用ansible插件时通过disableHostKeyChecking参数来解决此问题。
|
||||
|
||||
如果不使用.ssh目录的方式,使用credentialsId参数时,ansiblePlaybook方法连接远程主机时使用的sshpass命令对远程主机进行认证(通过jenkins执行任务时的控制台日志可以看到),默认使用的镜像是没有该命令的,所以我们还需要安装此命令。
|
||||
|
||||
自定义ansible镜像
|
||||
|
||||
在上面使用的ansible镜像的基础上,在不使用ansible 插件的情况下,定制的Dockerfile内容如下:
|
||||
|
||||
FROM gableroux/ansible:2.7.10
|
||||
|
||||
RUN echo "http://mirrors.aliyun.com/alpine/latest-stable/main/" > /etc/apk/repositories \
|
||||
&& echo "http://mirrors.aliyun.com/alpine/latest-stable/community/" >> /etc/apk/repositories \
|
||||
&& apk add --update --no-cache openssh sshpass \
|
||||
&& echo "[defaults] \n" > ~/.ansible.cfg \
|
||||
&& echo "host_key_checking = False" >> ~/.ansible.cfg
|
||||
|
||||
|
||||
注意:
|
||||
|
||||
如果不想使用ansible.cfg配置文件来定义host_key_checking = False,也可以通过在执行ansible时将该配置通过变量的方式传入,例如:
|
||||
|
||||
ansible-playbook xxx.yaml -e "host_key_checking = False"
|
||||
|
||||
|
||||
构建镜像。
|
||||
|
||||
docker build -t 192.168.176.155/library/ansible:v2.7.10 .
|
||||
|
||||
|
||||
如果想要在指定的节点上执行,代码如下:
|
||||
|
||||
pipeline {
|
||||
agent { node { label 'slave-43' } }
|
||||
stages('build') {
|
||||
stage('test-ansilbe'){
|
||||
steps{
|
||||
script{
|
||||
sh "hostname"
|
||||
docker.image('gableroux/ansible:2.7.10').inside('-v /data/:/data/ -v /root/.ssh:/root/.ssh '){
|
||||
sh "ansible-playbook -i /data/nfs/ansible/hosts /data/nfs/ansible/test.yaml "
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
该示例会在slave-43节点上启动容器执行playbook。
|
||||
|
||||
基础的演示完成了,放到实际脚本中去应该简单多了吧。
|
||||
|
||||
方法比较多,下面给出一种,例如使用多个agent示例。
|
||||
|
||||
pipeline {
|
||||
agent { node { label 'master' } }
|
||||
|
||||
environment {
|
||||
project_name = 'fw-base-nop'
|
||||
jar_name = 'fw-base-nop.jar'
|
||||
registry_url = '192.168.176.155'
|
||||
project_group = 'base'
|
||||
}
|
||||
|
||||
stages('build') {
|
||||
stage('代码拉取并编译'){
|
||||
steps {
|
||||
script {
|
||||
docker.image('alpine-cicd:latest').inside('-v /root/.m2:/root/.m2'){
|
||||
checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: 'c33d60bd-67c6-4182-b52c-d7aeebfab772', url: 'http://192.168.176.154/root/base-nop.git']]])
|
||||
echo "开始编译 "
|
||||
sh "cd $project_name && mvn clean install -DskipTests -Denv=beta"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
stage('构建镜像'){
|
||||
steps {
|
||||
script{
|
||||
jar_file=sh(returnStdout: true, script: "find ${WORKSPACE} ./ -name $jar_name |head -1").trim()
|
||||
|
||||
docker.image('alpine-cicd:latest').inside('-v /root/.m2:/root/.m2 -v /data/:/data/'){
|
||||
sh "cp $jar_file /data/$project_group/$project_name/"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
stage('上传镜像'){
|
||||
steps {
|
||||
script {
|
||||
docker.withRegistry('http://192.168.176.155', 'auth_harbor') {
|
||||
docker.image('alpine-cicd:latest').inside('-v /data/:/data/ -v /var/run/docker.sock:/var/run/docker.sock'){
|
||||
def customImage=docker.build("${registry_url}/${project_group}/${project_name}:${env.BUILD_ID}","/data/${project_group}/${project_name}/")
|
||||
customImage.push()
|
||||
}
|
||||
|
||||
}
|
||||
sh "docker rmi -f ${registry_url}/${project_group}/${project_name}:${env.BUILD_ID}"
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('部署镜像'){
|
||||
environment{
|
||||
container_run_option="--network host -e TZ='Asia/Shanghai' -d -v /etc/localtime:/etc/localtime:ro -m 2G --cpu-shares 512 -v /data/logs/${project_name}:/data/logs/${project_name}"
|
||||
}
|
||||
steps{
|
||||
script{
|
||||
docker.image('gableroux/ansible:2.7.10').inside('-v /data/:/data/ -v /root/.ssh:/root/.ssh'){
|
||||
sh """
|
||||
ansible-playbook -i /data/nfs/ansible/hosts /data/nfs/ansible/deploy.yaml -e "workspace='${WORKSPACE}' registry_url=$registry_url project_group=$project_group target_host='192.168.176.160' project_name='$project_name' run_option='$container_run_option' image_name='${registry_url}/${project_group}/${project_name}:${BUILD_ID}'"
|
||||
"""
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
如果你想要定义一个全局agent代理,需要你使用的镜像中包含ansible命令,这就需要你使用上面自定义的镜像了。
|
||||
|
||||
使用ansible插件的方式与上一小节中的一样,将ansible-playbook命令换成ansiblePlaybook()即可,代码如下所示:
|
||||
|
||||
stage('部署镜像'){
|
||||
environment {
|
||||
container_run_option="\'--network host -e TZ=Asia/Shanghai -d -v /etc/localtime:/etc/localtime:ro -m 2G --cpu-shares 512 -v /data/logs/${project_name}:/data/logs/${project_name}\'"
|
||||
}
|
||||
steps{
|
||||
script{
|
||||
docker.image('gableroux/ansible:2.7.10').inside('-v /data/:/data/ -v /root/.ssh:/root/.ssh'){
|
||||
ansiblePlaybook (
|
||||
become: true,
|
||||
disableHostKeyChecking: true,
|
||||
installation: '/usr/local/bin/ansible-playbook',
|
||||
inventory: '/data/nfs/ansible/hosts',
|
||||
playbook: '/data/nfs/ansible/deploy.yaml',
|
||||
extras: ' -e "workspace=${WORKSPACE} target="192.168.176.160" registry_url=$registry_url project_group=$project_group project_name=$project_name run_option=$container_run_option image_name=${registry_url}/${project_group}/${project_name}:${BUILD_ID}"'
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
注意:
|
||||
|
||||
在使用ansible容器作为agent节点时,一定要注意ansible-playbook命令的位置。比如我在jenkins系统中配置的命令路径为/usr/bin/ansible-playbook,那么通过片段生成器生成的语法片段中installation参数默认指定的ansible-playbook命令的路径为/usr/bin;而我使用的ansible镜像(gableroux/ansible:2.7.10),ansible-playbook命令的默认路径为/usr/local/bin/ansible-playbook,这时候如果installation参数的值还是ansibl-playbook,在运行容器执行ansible-playbook命令时就会提示命令找不到。所以这里应该设置成/usr/local/bin/ansible-playbook。
|
||||
|
||||
如果不想使用宿主机下的/root/.ssh文件,可以通过添加credentialsId参数,用于指定登录远程服务器的jenkins凭证,如下所示:
|
||||
|
||||
docker.image('192.168.176.155/library/ansible:v2.7.10').inside('-v /data/:/data/'){
|
||||
ansiblePlaybook (
|
||||
become: true,
|
||||
disableHostKeyChecking: true,
|
||||
installation: '/usr/local/bin/ansible-playbook',
|
||||
inventory: '/data/nfs/ansible/hosts',
|
||||
credentialsId: '160-ssh',
|
||||
playbook: '/data/nfs/ansible/deploy.yaml',
|
||||
extras: ' -e "workspace=${WORKSPACE} target_host="192.168.176.160" registry_url=$registry_url project_group=$project_group project_name=$project_name run_option=$container_run_option image_name=${registry_url}/${project_group}/${project_name}:35"'
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
有关使用容器作为agent代理的内容就介绍到这里。
|
||||
|
||||
当agent代理通过kubernetes编排
|
||||
|
||||
当agent代理使用kubernetes系统编排时,具体的部署流程与上面使用docker容器作为agent代理流程是一样的。只不过既然使用kubernetes系统,那么应用容器的部署肯定也不是只单独部署了,需要将容器部署到kubernetes集群中。所以我们需要从新编写ansible-playbook脚本。
|
||||
|
||||
我们只需要将在上一节中用的k8s的资源对象文件deployment.ymal拷贝到k8s master主机上指定的目录下apply即可。
|
||||
|
||||
同前两节一样,在开始之前还是使用ansible-playbook命令测试一下流程是成功:
|
||||
|
||||
def project_name = 'fw-base-nop' //项目名称
|
||||
def registry_url = '192.168.176.155' //镜像仓库地址
|
||||
def project_group = 'base'
|
||||
|
||||
podTemplate(cloud: 'kubernetes',namespace: 'default', label: 'pre-CICD',
|
||||
serviceAccount: 'default', containers: [
|
||||
containerTemplate(
|
||||
name: 'jnlp',
|
||||
image: "192.168.176.155/library/jenkins-slave:sonar",
|
||||
args: '${computer.jnlpmac} ${computer.name}',
|
||||
ttyEnabled: true,
|
||||
privileged: true,
|
||||
alwaysPullImage: false,
|
||||
),
|
||||
containerTemplate(
|
||||
name: 'ansible',
|
||||
image: "192.168.176.155/library/ansible:v2.7.10",
|
||||
ttyEnabled: true,
|
||||
privileged: true,
|
||||
command: 'cat',
|
||||
alwaysPullImage: false,
|
||||
),
|
||||
],
|
||||
volumes: [
|
||||
nfsVolume(mountPath: '/tmp', serverAddress: '192.168.177.43', serverPath: '/data/nfs', readOnly: false),
|
||||
hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock'),
|
||||
nfsVolume(mountPath: '/root/.m2', serverAddress: '192.168.177.43', serverPath: '/data/nfs/.m2', readOnly: false),
|
||||
],
|
||||
){
|
||||
node('pre-CICD') {
|
||||
stage('deploy'){
|
||||
container('ansible'){
|
||||
sh """
|
||||
ansible-playbook -i /tmp/ansible/hosts /tmp/ansible/deploy-kubernetes.yaml -e "project_group=$project_group project_name=$project_name"
|
||||
"""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
在stage(‘deploy’)阶段,简单测试一下使用ansible-playbook命令实现部署到kubernetes集群。其中deploy-kubernetes.yaml内容如下:
|
||||
|
||||
---
|
||||
- hosts: k8s_master
|
||||
gather_facts: False
|
||||
tasks:
|
||||
- name: copy deployment
|
||||
copy: src=/tmp/k8s_yaml/{{ project_group }}/{{ project_name }}/deployment.yaml dest=/data/{{ project_group }}/{{ project_name }}/
|
||||
|
||||
- name: exec
|
||||
shell: kubectl apply -f /data/{{ project_group }}/{{ project_name }}/deployment.yaml
|
||||
|
||||
|
||||
执行结果如下:
|
||||
|
||||
|
||||
|
||||
使用ansible命令可以完成部署,那么证明playbook文件和ansible-playbook命令都是没问题的,下面使用ansible插件测试一下。
|
||||
|
||||
因为是使用的脚本式语法,pipeline代码如下:
|
||||
|
||||
stage('deploy'){
|
||||
container('ansible'){
|
||||
ansiblePlaybook (
|
||||
become: true,
|
||||
disableHostKeyChecking: true,
|
||||
installation: '/usr/local/bin/ansible-playbook',
|
||||
inventory: '/tmp/ansible/hosts',
|
||||
credentialsId: '160-ssh',
|
||||
limit: '192.168.176.148',
|
||||
playbook: '/tmp/ansible/deploy-kubernetes.yaml',
|
||||
extras: " -e 'project_group=$project_group project_name=$project_name'"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
基本上与在docker中的配置一样。
|
||||
|
||||
完整的代码如下:
|
||||
|
||||
def project_name = 'fw-base-nop' //项目名称
|
||||
def registry_url = '192.168.176.155' //镜像仓库地址
|
||||
def project_group = 'base'
|
||||
|
||||
podTemplate(cloud: 'kubernetes',namespace: 'default', label: 'pre-CICD',
|
||||
serviceAccount: 'default', containers: [
|
||||
containerTemplate(
|
||||
name: 'jnlp',
|
||||
image: "192.168.176.155/library/jenkins-slave:sonar",
|
||||
args: '${computer.jnlpmac} ${computer.name}',
|
||||
ttyEnabled: true,
|
||||
privileged: true,
|
||||
alwaysPullImage: false,
|
||||
),
|
||||
containerTemplate(
|
||||
name: 'ansible',
|
||||
image: "192.168.176.155/library/ansible:v2.7.10",
|
||||
ttyEnabled: true,
|
||||
privileged: true,
|
||||
command: 'cat',
|
||||
alwaysPullImage: false,
|
||||
),
|
||||
],
|
||||
volumes: [
|
||||
nfsVolume(mountPath: '/tmp', serverAddress: '192.168.177.43', serverPath: '/data/nfs', readOnly: false),
|
||||
hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock'),
|
||||
nfsVolume(mountPath: '/root/.m2', serverAddress: '192.168.177.43', serverPath: '/data/nfs/.m2', readOnly: false),
|
||||
],
|
||||
){
|
||||
node('pre-CICD') {
|
||||
stage('build') {
|
||||
try{
|
||||
container('jnlp'){
|
||||
stage('clone code'){
|
||||
checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, userRemoteConfigs: [[credentialsId: 'c33d60bd-67c6-4182-b52c-d7aeebfab772', url: 'http://192.168.176.154/root/base-nop.git']]])
|
||||
script {
|
||||
imageTag = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim()
|
||||
}
|
||||
echo "${imageTag}"
|
||||
currentBuild.result == 'SUCCESS'
|
||||
}
|
||||
|
||||
stage('Build a Maven project') {
|
||||
sh "cd ${project_name} && mvn clean install -DskipTests -Pproduct -U"
|
||||
currentBuild.result = 'SUCCESS'
|
||||
}
|
||||
}
|
||||
}catch(e){
|
||||
currentBuild.result = 'FAILURE'
|
||||
}
|
||||
if (currentBuild.result == null || currentBuild.result == 'SUCCESS') {
|
||||
container('jnlp'){
|
||||
stage('sonar test'){
|
||||
withSonarQubeEnv(credentialsId: 'sonarqube') {
|
||||
sh "sonar-scanner -X "+
|
||||
"-Dsonar.login=admin " +
|
||||
"-Dsonar.language=java " +
|
||||
"-Dsonar.projectKey=${JOB_NAME} " +
|
||||
"-Dsonar.projectName=${JOB_NAME} " +
|
||||
"-Dsonar.projectVersion=${BUILD_NUMBER} " +
|
||||
"-Dsonar.sources=${WORKSPACE}/fw-base-nop " +
|
||||
"-Dsonar.sourceEncoding=UTF-8 " +
|
||||
"-Dsonar.java.binaries=${WORKSPACE}/fw-base-nop/target/classes " +
|
||||
"-Dsonar.password=admin "
|
||||
}
|
||||
}
|
||||
}
|
||||
withDockerRegistry(credentialsId: 'auth_harbor', url: 'http://192.168.176.155') {
|
||||
stage('build and push docker image') {
|
||||
sh "cp /tmp/Dockerfile ${project_name}/target/"
|
||||
def customImage = docker.build("${registry_url}/library/${project_name}:${imageTag}-${BUILD_NUMBER}","--build-arg jar_name=${project_name}.jar ${project_name}/target/")
|
||||
echo "推送镜像"
|
||||
customImage.push()
|
||||
}
|
||||
stage('delete image') {
|
||||
echo "删除本地镜像"
|
||||
sh "docker rmi -f ${registry_url}/library/${project_name}:${imageTag}-${BUILD_NUMBER}"
|
||||
}
|
||||
}
|
||||
}else {
|
||||
echo "---currentBuild.result is:${currentBuild.result}"
|
||||
emailext (
|
||||
subject: "'${env.JOB_NAME} [${env.BUILD_NUMBER}]' 构建异常",
|
||||
body: """
|
||||
详情:<br>
|
||||
failure: Job ${env.JOB_NAME} [${env.BUILD_NUMBER}] <br>
|
||||
状态:${env.JOB_NAME} jenkins 构建异常 <br>
|
||||
URL :${env.BUILD_URL} <br>
|
||||
项目名称 :${env.JOB_NAME} <br>
|
||||
项目构建id:${env.BUILD_NUMBER} <br>
|
||||
信息: 代码编译失败
|
||||
|
||||
""",
|
||||
to: "[email protected]",
|
||||
recipientProviders: [[$class: 'DevelopersRecipientProvider']]
|
||||
)
|
||||
}
|
||||
stage('deploy'){
|
||||
if (currentBuild.result == null || currentBuild.result == 'SUCCESS') {
|
||||
sh 'sed -i 's#fw-base-nop:.*-*[0-9]\$#${project_name}:${imageTag}-${BUILD_NUMBER}#' /tmp/${project_group}/${project_name}/deployment.yaml'
|
||||
container('ansible'){
|
||||
ansiblePlaybook (
|
||||
become: true,
|
||||
disableHostKeyChecking: true,
|
||||
installation: '/usr/local/bin/ansible-playbook',
|
||||
inventory: '/tmp/ansible/hosts',
|
||||
credentialsId: '160-ssh',
|
||||
limit: '192.168.176.148',
|
||||
playbook: '/tmp/ansible/deploy-kubernetes.yaml',
|
||||
extras: " -e 'project_group=$project_group project_name=$project_name'"
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
有一点需要注意的是对于修改资源对象文件deployment.yaml中image参数的值,可以通过sed命令直接修改共享存储中的源文件该参数的值(前提是在共享存储挂载到pod时权限可写),也可以通过在ansible playbook脚本文件(deployment-kubernetes.yaml)中添加任务去修改(比如使用lineinfile模块),这里就不在演示了。
|
||||
|
||||
这样便完成了使用ansible插件去部署容器到kubernetes集群的操作。而在实际工作中,由于部署的复杂性和多样性,我们在更新应用的时候可能不只要更新服务,我们还需要更新应用的所使用的资源大小,应用的端口,日志路径等任何可能修改信息,此时只是简单的使用单个playbook文件已经无法满足要求了,这时我们在之前介绍到的ansible 相关的知识就派上用场了。
|
||||
|
||||
使用ansible-playbook最常用的的场景就是使用role,定义一系列角色去完成设定的任务。在使用role编写playbook部署容器到kubernetes集群时,根据需求的不同对于所需的kubernetes资源对象的定义也会有所差异。我这里就简单的介绍一种基本部署的流程。
|
||||
|
||||
通过编写一个playbook文件,作为整个部署脚本的执行入口,如下所示:
|
||||
|
||||
$ cat dev-common.yml
|
||||
- hosts: DEV-TEST-k8s
|
||||
remote_user: root
|
||||
gather_facts: False
|
||||
roles:
|
||||
- dev-k8s
|
||||
|
||||
|
||||
通过上面的入口文件,然后看一下该roles目录都包含哪些role以及playbook脚本文件。
|
||||
|
||||
tree roles/
|
||||
roles/
|
||||
└── dev-k8s
|
||||
├── tasks
|
||||
│ └── main.yml
|
||||
└── templates
|
||||
├── deployments.yaml.j2
|
||||
|
||||
|
||||
roles目录包含一个dev-k8 role:
|
||||
|
||||
role下的tasks目录用于存放执行任务的playbook。-
|
||||
templates目录用于存放kubernetes资源对象的模板文件。-
|
||||
资源对象文件只配置了一个做示例,实际工作中可能会涉及到更多(比如configmap,ingress,service等),本例仅作参考。
|
||||
|
||||
然后看一下这个tasks下playbook的内容,命令都比较简单,并且定义了一系列变量,这些变量都可以在pipeline脚本中定义。
|
||||
|
||||
$ cat tasks/main.yml
|
||||
---
|
||||
- name: 判断目录是否存在
|
||||
shell: ls {{ yaml_dir }}/{{ns}}/dev-{{ server_name }}
|
||||
register: dict_exist
|
||||
ignore_errors: true
|
||||
|
||||
- name: Create the k8s-config directory
|
||||
file: path="{{ yaml_dir }}/{{ns}}/dev-{{ server_name }}" state=directory mode=0755
|
||||
when: dict_exist is failure
|
||||
|
||||
- name: Create project file
|
||||
template: "src={{ item }}.j2 dest={{ yaml_dir }}/{{ns}}/dev-{{ server_name }}/dev-{{server_name}}-{{ item }}"
|
||||
with_items:
|
||||
- deployments.yaml
|
||||
|
||||
- name: 部署
|
||||
shell: "cd {{ yaml_dir }}/{{ns}}/dev-{{ server_name }} && {{bin_dir}}/kubectl apply -f . --record"
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
|
||||
变量yaml_dir和bin_dir分别用于存放kubernetes资源对象文件和kubectl命令,这两个变量放到了inventory清单文件中,没有通过外部变量传入,根据自己实际情况修改。
|
||||
其他变量参考下面template模板文件。
|
||||
|
||||
|
||||
然后看一下template下的资源对象模板文件。
|
||||
|
||||
$ cat deployments.yaml.j2
|
||||
---
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dev-{{server_name}}
|
||||
namespace: {{ns}}
|
||||
spec:
|
||||
replicas: 2
|
||||
strategy:
|
||||
rollingUpdate:
|
||||
maxSurge: 1
|
||||
maxUnavailable: 1
|
||||
revisionHistoryLimit: 10
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
name: dev-{{server_name}}
|
||||
spec:
|
||||
hostNetwork: true
|
||||
terminationGracePeriodSeconds: 60
|
||||
imagePullSecrets:
|
||||
- name: mysecret
|
||||
containers:
|
||||
- name: dev-{{server_name}}
|
||||
image: 192.168.176.155/base/{{server_name}}:{{ tag }}
|
||||
imagePullPolicy: Always
|
||||
lifecycle:
|
||||
preStop:
|
||||
exec:
|
||||
command: ["rm","-r","-f","/data/logs/{{server_name}}/"]
|
||||
env:
|
||||
- name: TZ
|
||||
value: "Asia/Shanghai"
|
||||
{% if java_opts %}
|
||||
- name: JAVA_OPTS
|
||||
value: {{java_opts}}
|
||||
- name: _JAVA_OPTIONS
|
||||
value: {{java_opts}}
|
||||
{% else %}
|
||||
- name: JAVA_OPTS
|
||||
value: "-Xms1024m -Xmx1024m"
|
||||
- name: _JAVA_OPTIONS
|
||||
value: "-Xms1024m -Xmx1024m"
|
||||
{% endif %}
|
||||
ports:
|
||||
- containerPort: {{port}}
|
||||
resources:
|
||||
limits:
|
||||
cpu: {{ cpu|int }}m
|
||||
memory: {{ mem|int }}Mi
|
||||
requests:
|
||||
cpu: {{ cpu|int }}m
|
||||
memory: {{ mem|int }}Mi
|
||||
volumeMounts:
|
||||
{% if server_name == "test-enter" %}
|
||||
- name: dev-{{server_name}}-logs
|
||||
mountPath: /data/logs/test-enter
|
||||
{% else %}
|
||||
- name: dev-{{server_name}}-logs
|
||||
mountPath: /data/logs/{{server_name}}
|
||||
{% endif %}
|
||||
- name: host-resolv
|
||||
mountPath: /etc/resolv.conf
|
||||
- name: tz-config
|
||||
mountPath: /etc/localtime
|
||||
volumes:
|
||||
- name: host-resolv
|
||||
hostPath:
|
||||
path: /etc/resolv.conf
|
||||
- name: tz-config
|
||||
hostPath:
|
||||
path: /usr/share/zoneinfo/Asia/Shanghai
|
||||
- name: dev-{{server_name}}-logs
|
||||
emptyDir: {}
|
||||
|
||||
|
||||
说明:
|
||||
|
||||
该资源对象填充了一些变量,其中:-
|
||||
server_name:表示项目名称,对应持续交付步骤时的${project_name}。-
|
||||
ns:表示该资源对象部署到的namespace,对应持续交付步骤的${project_group}。-
|
||||
tag:表示镜像的tag,对应持续交付步骤的${imageTag}-${BUILD_NUMBER}。-
|
||||
java_opts:表示jvm的配置,变量值由外部传入。-
|
||||
cpu/mem:cpu和内存设置,由外部变量传入。-
|
||||
port:服务端口,由外部变量传入。
|
||||
|
||||
执行命令为:
|
||||
|
||||
values="server_name=${project_name} port=8083 cpu=1024 mem=2048 java_opts=${java_opts} ns=$project_group tag=${imageTag}-${BUILD_NUMBER}"
|
||||
|
||||
ansible-playbook /etc/ansible/dev-common.yaml -e "$values"
|
||||
|
||||
|
||||
此示例模板仅做流程参考,在工作中可以根据自己的实际情况进行修改配置。
|
||||
|
||||
持续交付和持续部署的实现方式具有多样性,最重要的是要学会将这些工具链结合起来使用,从中找到适合自己的方式方法。
|
||||
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user