在DevOps实战笔记–1中,我们配置了两台实验机器:node1,node2,两台机器都使用docker容器技术来运行基本服务,GitLab作为体量较大的服务之一,运行内存可达4G之多,故使用node1作为GitLab专用服务器来负责代码的版本控制 。node2作为CI/CD过程服务器兼SSH测试服务器,配置了JDK+Maven等服务作为项目持续集成与持续部署的基础。在此之上搭建了Jenkins,SonarQube并进行了CI/CD的实验。本文将从此处开始进行讲解。
SonarQube作为高效的代码分析平台可以对本地代码进行分析并显示出代码不合规范的地方,其分析方法共有两种:
1.SonarQube支持Maven,可通过Maven实现代码检测;
2.下载Sonar Scanner软件到Linux,并在Shell中完成对拉取到代码的检测
在1.SonarQube基本操作中
我们只阐述第一种方法。
进入Windows本地的Maven安装目录,找到/conf
下settings.xml
,在
标签下填入已搭建好的SonarQube配置信息。
<profile>
<id>sonarid>
<activation>
<activeByDefault>trueactiveByDefault> #确认默认开启
activation>
<properties>
<sonar.login>adminsonar.login> #用户名
<sonar.password>*sonar> #设置后的密码
<sonar.host.url>http://*:*sonar.host.url> #SonarQube链接
properties>
profiles>
在pom.xml
中添加sonar-maven-plugin
插件:
<plugin>
<groupId>org.sonarsource.scanner.mavengroupId>
<artifactId>sonar-maven-pluginartifactId>
<version>3.4.1.1168version>
plugin>
在终端中使用
mvn sonar:sonar
可以完成对项目代码的检测,并将报告呈现在SonarQube平台上。
在Jenkins中集成Sonar Scanner和JDK、Maven相似,都可以根据需要选择是否自动下载。本次实验选用本地sonar-scanner-cli-4.6.0.2311-linux.zip
文件,将文件解压缩后重命名,并移动到Jenkins的数据卷下。Jenkins的数据卷在Jenkins容器文件夹的/data
下,与Jenkins容器的docker-compose.yml
同级。至此,可以在Jenkins容器内部查看/var/jenkins_home
下的Sonar Scanner文件夹。
进入刚刚解压的Sonar Scanner文件夹,修改/conf
下的sonar-scanner.properties
的相应部分:
1.配置Sonar Scanner服务器地址;
2.设置编码原则UTF-8为开启
如:
#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.140.120:9000 #将此项修改后设置为开启
#----- Default source code encoding
sonar.sourceEncoding=UTF-8 #将此项开启
至此完成了Sonar Scanner的配置。
回到SonarQube。在SonarQube的Administration(管理) -> Configuration(配置) -> General Settings(全局设置) -> Security(安全)
中打开Force Other Authentication(强制身份验证)
,这样SonarQube在检测代码时会要求用户提交身份验证信息。
为了使Jenkins作为远程用户使用SonarQube,需要在SonarQube中获取可供远程用户使用此SonarQube账号进行工作的密钥。点击右上角个人账户,在security
下填写账户名,并生成一个Token
。Token可以作为身份验证的凭据,将Token记录下来。图中已生成admin对应的Token。
回到Jenkins。在Jenkins中下载插件SonarQube Scanner
,下载后可以在系统管理 -> 系统配置
中找到刚刚下载的插件对应的设置SonarQube Servers
,在此项中添加SonarQube服务器,填入自定义的名称,并指定SonarQube服务器地址。在凭证中选择Jenkins信息提供者
,添加刚刚获取到的SonarQube Token
。
注意:在填写凭据时,需要将type
设置为Secret Text
进行匹配。
进入系统管理 -> 全局工具设置
找到SonarQube Scanner
,由于我们已经提前配置过本地Sonar Scanner,故将自动安装取消勾选。填入自定义名称与映射到Jenkins容器内部的Sonar Scanner路径。至此,我们已将SonarQube集成到Jenkis中。
为了在Jenkins构建项目时同步使用SonarQube检查项目代码,需要更新项目在Jenkins中的构建步骤。目前的项目构建步骤为:使用Git选择出目标构建版本 -> 调用顶层Maven进行构建
,为了使用集成后的SonarQube,需要添加第三步:Execute SonarQube Scanner(执行SonarQube Scanner)
。选择编译的JDK版本为全局JDK配置。
与在命令行中执行Sonar Scanner类似,在Jenkins中也要为Sonar Scanner提供属性,在Analysis Properties
中写入以下信息:
sonar.projectname = ${JOB_NAME} #项目名称使用${JOB_NAME}引用
sonar.projectKey = ${JOB_NAME}
sonar.source = ./ #指定Sonar Scanner工作目录为当前目录
sonar.java.binaries = target/ #指定编译文件的存放目录为target目录
在Jenkins中启动构建项目,完成后可在SonarQube平台中查看本次构建代码分析结果。
在为多台服务器部署项目时为了避免重复操作,需要指定一个所有服务器都可以进行拉取的仓库,将制作好的镜像推送给该仓库以供其他服务器使用。
这时我们可以考虑使用Docker官方提供的镜像仓库Registry,但对比VMware公司推出的一款可以权限控制、分布式发布并提供了强大的安全扫描与审查机制等功能的镜像仓库Harbor,Registry的功能相对简陋。故我们在此选择Harbor作为私有仓库。
在GitHub上下载Harbor的tgz
压缩包。本次实验使用的版本为harbor-offline-installer-v2.3.4.tgz
。将压缩包解压到/usr/local
下。进入Harbor目录后找到harbor.yml.tmpl
,将其复制一份并进入修改。修改的地方如下:
1.Harbor的地址:此处修改hostname
的值,对应node2的IP地址192.168.140.120
;
2.注释掉HTTPS相关内容。
修改后的harbor.yml
(部分)如下:
# Configuration file of Harbor
# The IP address or hostname to access admin UI and registry service.
# DO NOT use localhost or 127.0.0.1, because Harbor needs to be accessed by external clients.
hostname: 192.168.140.120 #修改hostname的值
# http related config
http:
# port for http, default is 80. If https enabled, this port will redirect to https port
port: 80
# https related config 注释掉HTTPS的相关内容
#https:
# https port for harbor, default is 443
# port: 443
# The path of cert and key files for nginx
# certificate: /your/certificate/path
# private_key: /your/private/key/path
# # Uncomment following will enable tls communication between all harbor components
# internal_tls:
# # set enabled to true means internal tls is enabled
# enabled: true
# # put your cert and key files on dir
# dir: /etc/harbor/tls/internal
yml文件提供的其他信息:
# http related config harbor的访问端口
http:
# port for http, default is 80. If https enabled, this port will redirect to https port
port: 80
# The initial password of Harbor admin Harbor的初始密码
# It only works in first time to install harbor
# Remember Change the admin password from UI after launching Harbor.
harbor_admin_password: Harbor12345
修改完成后,可以运行本目录下的install.sh
启动Harbor
./install.sh
首先在Harbor中新建项目,本次新建项目名为repo
。
在向Harbor仓库中推送镜像时需要注意镜像的命名方式,应当以如下方式来命名:HarborURL/projectName/imageName:version
。也就是:Harbor的地址/Harbor中建立的项目名/镜像名称:版本号
。
在使用Docker命令修改镜像名时,重命名的名称需要跟在原有名称后。以本次镜像springtest:v2.0.0
举例,按规范命名后的镜像名应当为:192.168.140.120:80/repo/springtest:v2.0.0
docker tag springtest:v2.0.0 192.168.140.120:80/repo/springtest:v2.0.0
为了保证Docker的正常推送,需要借助daemon.json
。当我们需要对docker服务进行调整配置时,不用去修改主文件 docker.service的参数,通过daemon.json配置文件来管理,更为安全、合理。Docker安装时默认没有此配置文件,需要手动添加到/etc/docker
下
cd /etc/docker
vi daemon.json
#在文件中添加
{
"registry-mirrors": ["https://registry.docker-cn.com"], #注意json文件的格式,不能丢掉逗号,此处指明了镜像加速地址为私有仓库加速,增加后在 docker info中可查看
"insecure-registries": ["192.168.140.120:80"] #指明了私有仓库的地址,也就是Harbor的地址
}
配置完成后,重新启动Docker,根据先前启动的服务来看,本次重启耗时可能较长
注意,在配置daemon.json时偶尔会遇到docker无法启动的问题,可以尝试将daemon文件的后缀由json改为conf,重新加载并重启docker即可,由于Harbor仓库采用Https协议而docker客户端采用Http协议导致的无法解析服务器响应的问题,可以在/usr/lib/systemd/system/下的docker.service中,在ExecStart后添加与daemon.conf相同的私库地址,格式为:–insecure-registry ip:port 重启docker即可解决
systemctl restart docker
在Docker重启完成后,想要本地镜像的推送,需要将Docker服务登入Harbor。登入服务后,根据命名规则命名的镜像可以完成推送。
docker login -u admin -p Harbor12345 192.168.140.120:80 #docker登录命令格式为:docker login -u *HARBOR_USENAME -p *HARBOR_PASSWORD *HARBOR_URL
docker push 192.168.140.120:80/repo/springtest:v2.0.0 #将本地镜像推送至Harbor
为了简化部署服务时的操作,在Jenkisn制作镜像 -> 推送Harbor -> 目标服务器从Harbor拉取镜像的过程中, 我们已于3.2中完成了本地镜像推送Harbor的操作,为了进一步简化推送过程,可以配置Jenkins直接使用Docker向Harbor进行推送。有两种方案可供参考:
1.在Jenkins容器内部安装Docker。此方案较为简单,但由于Jenkins容器内部组件不全,安装成本较高,一般情况下不考虑。
2.通过配置Jenkins,使其可以直接使用宿主机的Docker服务。本篇仅对此方案进行讲解。
为使Jenkins容器内部也可以使用Docker命令,需要对Docker的核心文件进行配置,增加用户权限并修改所属组。
cd /var/run #修改该目录下的docker.sock
chown root:root docker.sock #将原先该文件的所属组docker修改为root
chmod o+rw docker.sock #赋予该文件其他用户的读写权限
权限修改完成后,需要将宿主机Docker的相关文件映射到Jenkins容器内部,包括刚刚修改的docker.sock
,以及Docker的可执行命令
与私服配置
。修改后的docker-compose.yml
文件如下:
version: "3.1"
services:
jenkins:
image: jenkins/jenkins:2.387.1-lts
container_name: jenkins
ports:
- 8080:8080
- 50000:50000
volumes:
- ./data/:/var/jenkins_home/
- /var/run/docker.sock:/var/run/docker.sock #新建的docker.sock映射
- /usr/bin/docker:/usr/bin/docker #新建的可执行命令映射
- /etc/docker/daemon.json:/etc/docker/daemon.json #新建的私服配置映射
更改完成后,使用docker-compose
命令重新启动Jenkins容器
docker-compose up -d
为迎合简化部署服务操作的思路,Jenkins构建项目的步骤也应做出相应改动。以往的构建步骤为:Jenkins构建项目、在构建后操作中发送JAR包与docker-compose文件到目标服务器的服务目录下、通过docker-compose命令进行服务的部署。在配置Jenkins使用宿主机Docker制作镜像后,我们可以在拉取指定版本代码、通过SonarScanner进行代码检查后,直接进行构建,并将构建好的镜像推送到Harbor上。
根据上述步骤,在Jenkins项目配置中应当作出如下修改:
1.将原先属于构建后步骤中,发送JAR与Docker相关文件的SSH发布删除,改为在构建步骤中添加Shell命令。
2.添加的Shell命令为:
mv target/*.jar docker/ #复制target目录下的JAR包到docker目录下
docker build -t springtest:$tag docker/ #在docker目录下进行镜像的制作
docker tag springtest:$tag 192.168.140.120:80/repo/springtest:$tag #镜像重命名
docker login -u admin -p Harbor12345 192.168.140.120:80 #使用docker登录Harbor
docker push 192.168.140.120:80/repo/springtest:$tag #推送镜像至Harbor私库中
此时由于制作自定义镜像只需要用到Dockerfile
而不需要docker-compose
,所以我们可以将项目中的docker-compose
文件安全删除。
此时在Jenkins中使用参数构建项目,随后便可在Harbor仓库中查看到刚刚推送过来的镜像文件。
在之前提到的部署服务中,我们通过Publish Over SSH
插件实现项目的发布与目标服务器命令的执行,为了方便目标服务器由Harbor私库拉取镜像,此时选择采用脚本文件的方式进行。
在部署脚本中,我们要完成的逻辑有以下几条:
1.决定具体由Harbor私库中拉取哪一个镜像;
2.判断容器是否存在,是否需要down掉容器;
3.判断镜像是否已存在,是否需要删除;
4.从Harbor私库中拉取镜像;
5.在容器中运行镜像。
根据以上逻辑,我们可以编写出如下的脚本:
harbor_addr=$1 #参数1
harbor_repo=$2 #参数2
project=$3 #参数3
version=$4 #参数4
container_port=$5 #参数5
host_port=$6 #参数6
imageName=$harbor_addr/$harbor_repo/$project:$version #拼接镜像名
echo $imageName
containerId=`docker ps -a | grep ${project} | awk '{print $1}'` #使用管道命令获取容器ID
if [ "$containerId" != "" ] ; then #判断容器已存在情况下删除容器
docker stop $containerId
docker rm $containerId
echo "Delete Container Success"
fi
tag=`docker images | grep ${project} | awk '{print $3}'` #使用管道命令获取镜像ID
if [[ "$tag" =~ "version" ]] ; then #判断镜像已存在的情况下删除镜像
docker rmi $imageId
echo "Delete Image Success"
fi
docker login -u admin -p Harbor12345 $harbor_addr #登入Harbor服务
docker pull $imageName #拉取镜像
docker run -d -p $host_port:$container_port --name $project $imageName #根据指定端口映射与容器名运行镜像
echo "Start Container Success"
echo $project_name
在完成部署脚本的编写后,为了使外部服务可以访问脚本,需要对脚本赋予可执行权限并将其添加到全局命令:
chmod a+x deploy.sh #对所有用户赋予可执行权限
mv deploy.sh /usr/local/bin #添加至全局命令
鉴于我们已在目标服务器中添加了脚本,并完成了Jenkins推送镜像至Harbor的操作,此时只需使用SSH服务控制目标服务器执行命令完成项目的自动部署即可。
在构建后步骤中添加SSH操作并提前写入命令:
deploy.sh 192.168.140.120:80 repo springtest $tag 8080 8081
此时的宿主机8081端口映射其实是为了规避已经运行在8080端口上的Jenkins服务。