前面我们已经通过jenkins+docker搭建了基本的持续集成环境,实现了服务的自动构建和部署,但是,我们遇到一个问题,jenkins构建出来的镜像部署后,需要通过ip:port去访问,有什么更好的方法吗?肯定是通过域名啊!前提是你注册一个域名,或者修改机器hosts文件。
本文介绍通过引入etcd+confd实现部署服务的自动注册,自动生成nginx配置文件,实现每个服务独立域名访问。
配置域名
假设你的域名是: example.com,那么我们可以规划
- dev.$servicename.example.com作为开发环境,
- test.$servicename.example.comz作为服务的测试环境。
配置步骤:
- 首先将*.example.com 指向一台nginx服务器
- 增加vhost配置文件,假设86.6,86.8,86.11 是docker swarm集群中的机器,服务的名称为allinoneservice,那么我们可以增加一个配置文件nginx_vhosts/service.conf:
upstream test_service_allinoneservice {
server 192.168.86.11:10091;
server 192.168.86.6:10091;
server 192.168.86.8:10091;
}
server {
listen 80;
server_name test.allinoneservice.example.com;
location / {
proxy_redirect off;
proxy_set_header Host $host;
proxy_read_timeout 300;
proxy_set_header X-Real-IP $http_x_forwarded_for;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://test_service_allinoneservice;
}
}
-
修改nginx配置文件nginx.conf,在最后一个大括号前,将刚新建的配置文件包含进去:
include nginx_vhosts/*.conf;
-
重启nginx,就可以通过test.allinoneservice.example.com访问服务了
通过服务注册自动生成配置文件
第一步里,我们需要手动编写配置文件,有更好的方式吗?答案是通过服务注册+confd,自动生成配置文件。
docker 安装etcd集群
首先,docker安装etcd作为注册中心,我们安装一个包含3个实例的集群,编写docker-compose.yml:
version: \'3\'
services:
etcd0:
image: 192.168.86.8:5000/etcd
ports:
- "2379:2379"
volumes:
- etcd0:/etcd_data
command:
- /usr/local/bin/etcd
- -name
- etcd0
- --data-dir
- /etcd_data
- -advertise-client-urls
- http://etcd0:2379
- -listen-client-urls
- http://0.0.0.0:2379
- -initial-advertise-peer-urls
- http://etcd0:2380
- -listen-peer-urls
- http://0.0.0.0:2380
- -initial-cluster
- etcd0=http://etcd0:2380,etcd1=http://etcd1:2380,etcd2=http://etcd2:2380
etcd1:
image: 192.168.86.8:5000/etcd
ports:
- "2380:2379"
volumes:
- etcd1:/etcd_data
command:
- /usr/local/bin/etcd
- -name
- etcd1
- --data-dir
- /etcd_data
- -advertise-client-urls
- http://etcd1:2379
- -listen-client-urls
- http://0.0.0.0:2379
- -initial-advertise-peer-urls
- http://etcd1:2380
- -listen-peer-urls
- http://0.0.0.0:2380
- -initial-cluster
- etcd0=http://etcd0:2380,etcd1=http://etcd1:2380,etcd2=http://etcd2:2380
etcd2:
image: 192.168.86.8:5000/etcd
ports:
- "2381:2379"
volumes:
- etcd2:/etcd_data
command:
- /usr/local/bin/etcd
- -name
- etcd2
- --data-dir
- /etcd_data
- -advertise-client-urls
- http://etcd2:2379
- -listen-client-urls
- http://0.0.0.0:2379
- -initial-advertise-peer-urls
- http://etcd2:2380
- -listen-peer-urls
- http://0.0.0.0:2380
- -initial-cluster
- etcd0=http://etcd0:2380,etcd1=http://etcd1:2380,etcd2=http://etcd2:2380
volumes:
etcd0:
etcd1:
etcd2:
注意,上面的image: 192.168.86.8:5000/etcd 是用的私有仓库,大家可以使用官方版本quay.io/coreos/etcd
然后启动etcd:
docker stack deploy -c docker-compose.yml etcd
服务注册
etcd注册就简单了,可以通过etcd的rest api,例如:
curl http://192.168.86.11:2379/v2/keys/services/test/allinoneservice/service1 -XPUT -d value="192.168.86.8:10091"
所以,我们修改一下jenkins里的docker部署脚本,服务部署后自动向etcd注册,由于是swarm集群,因此我们可以注册多个ip。
echo "start remove old service"
docker service rm ${service_name}-${env}
echo "start create new service with latest builded image"
docker service create --name ${service_name}-${env} --replicas ${replicas} --publish ${service_port}:${docker_expose_port} 192.168.86.8:5000/${service_name}-${env}
echo "publish service to nginx"
curl http://192.168.86.11:2379/v2/keys/services/${env}/${service_name}/service1 -XPUT -d value="192.168.86.8:${service_port}"
curl http://192.168.86.11:2379/v2/keys/services/${env}/${service_name}/service2 -XPUT -d value="192.168.86.11:${service_port}"
curl http://192.168.86.11:2379/v2/keys/services/${env}/${service_name}/service3 -XPUT -d value="192.168.86.6:${service_port}"
注意,上面的service_name是jenkins参数化构建里定义的参数:
通过confd生成nginx配置文件
confd 是一个配置文件生成工具,可以从etcd、consul等注册中心读取数据根据模板生成配置文件,并在配置发生变化后自动更新配置文件,还能自动重启服务,是服务自动发现的居家必备良药。
首先讲下怎么安装,根据官方文档:
wget https://github.com/kelseyhightower/confd/releases/download/v0.14.0/confd-0.14.0-linux-amd64
mkdir -p /opt/confd/bin
mv confd-0.14.0-linux-amd64 /opt/confd/bin/confd
chmod +x /opt/confd/bin/confd
export PATH="$PATH:/opt/confd/bin"
为了方便使用,最好修改下/etc/profile,加入export PATH="$PATH:/opt/confd/bin",然后source /etc/profile让配置生效。
然后编写confd配置文件/etc/confd/conf.d/myapp-nginx.toml:
[template]
src = "nginx.conf.tmpl"
dest = "/opt/third_party/nginx_vhosts/service.conf"
keys = [
"/services/dev",
"/services/test",
]
reload_cmd = "/opt/third_party/sbin/nginx -s reload"
上面配置了,会读取的keys,以及dest目标配置文件地址,和配置文件更新后的reload_cmd,用于重启nginx
接着编写模板文件
{{range $dir := lsdir "/services/test"}}
upstream test_service_{{base $dir}} {
{{$custdir := printf "/services/test/%s/*" $dir}}{{range getvs $custdir}}
server {{.}};
{{end}}
}
server {
listen 80;
server_name test.{{base $dir}}.iflyresearch.com;
location / {
proxy_redirect off;
proxy_set_header Host $host;
proxy_read_timeout 300;
proxy_set_header X-Real-IP $http_x_forwarded_for;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://test_service_{{base $dir}};
}
}
{{end}}
上面的模板比较简单,通过lsdir指令读取服务列表,然后通过range getvs获取服务对应的负载地址。
然后启动confd,需要指定etcd的地址:
nohup confd -backend etcd -node http://192.168.86.11:2379 &
搞定!
集成PMD、FindBugs、Checkstyle静态代码检查工具并邮件发送检查结果
为了规范代码,我们一般会集成静态代码检测工具,比如PMD、FindBugs、Checkstyle,那么Jenkins如何集成这些检查工具,并把检查结果放到构建邮件里呢?
今天做了调研和实现,过程如下
首先看,最终效果:
1.pom.xml
build。plugins 增加:
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<finalName>${project.artifactId}-${project.version}</finalName>
<appendAssemblyId>false</appendAssemblyId>
<descriptors>
<descriptor>src/assembly/assembly-descriptor.xml</descriptor>
</descriptors>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>findbugs-maven-plugin</artifactId>
<version>3.0.5</version>
<configuration>
<threshold>High</threshold>
<effort>Default</effort>
<findbugsXmlOutput>true</findbugsXmlOutput>
<findbugsXmlWithMessages>true</findbugsXmlWithMessages>
<xmlOutput>true</xmlOutput>
<formats><format>html</format></formats>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-pmd-plugin</artifactId>
<version>3.8</version>
</plugin>
reporting 增加:
<reporting>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>3.0.0</version>
<reportSets>
<reportSet>
<reports>
<report>checkstyle</report>
</reports>
</reportSet>
</reportSets>
</plugin>
<!--pmd-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-pmd-plugin</artifactId>
<configuration>
<linkXref>true</linkXref>
<sourceEncoding>utf-8</sourceEncoding>
<minimumTokens>100</minimumTokens>
<targetJdk>1.8</targetJdk>
<excludes>
<exclude>**/*Bean.java</exclude>
<exclude>**/generated/*.java</exclude>
</excludes>
<excludeRoots>
<excludeRoot>target/generated-sources/stubs</excludeRoot>
</excludeRoots>
</configuration>
<version>3.8</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jxr-plugin</artifactId>
<version>2.5</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-report-plugin</artifactId>
<version>2.14.1</version>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>cobertura-maven-plugin</artifactId>
</plugin>
</plugins>
</reporting>
2 jenkins设置
2.1 安装相关插件
直接搜索安装:
- Checkstyle Plug-in
- PMD Plug-in
- FindBugs Plug-in
- Static Analysis Collector Plug-in
邮件插件安装:
- Email Extension Plugin
- Email Extension Template Plugin
2.2 项目配置
maven构建Goals设置为:
pmd:pmd checkstyle:checkstyle findbugs:findbugs package -DskipTests
在构建设置里,勾上
- Publish Checkstyle analysis results
- Publish FindBugs analysis results
- Publish PMD analysis results
构建后操作,添加Publish combined static analysis results,默认设置即可。
2.3 邮件配置
首先在系统设置里,配置Extended E-mail Notification部分
设置:
- Default Subject : 自动构建通知:$PROJECT_NAME - Build # $BUILD_NUMBER - $BUILD_STATUS!
- Default Content: ${JELLY_SCRIPT, template="analysis.jelly"}
剩下的自己配置下SMTP和收件人。
这里邮件内容使用analysis.jelly,使用jelly script,系统没有这个模板,我们需要配置一下:
打开系统管理-Managed files,增加一个Extended Email Publisher Jelly Template
模板文件如下,该模板修改自官方的模板,做了一定的本地化和样式调整:
<?jelly escape-by-default=\'true\'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define">
<html>
<head>
<title>${project.name}</title>
<style>
body table, td, th, p, h1, h2 {
margin:0;
font:normal normal
100% Georgia, Serif;
}
h1, h2 {
border-bottom:dotted 1px #999999;
padding:5px;
margin-bottom:10px;
color: #000000;
font: normal bold 130%
Georgia,Serif;
background-color:#f0f0f0;
}
tr.gray {
background-color:#f0f0f0;
}
h2 {
padding:5px;
margin-top:5px;
margin-bottom:5px;
font: italic bold 110% Georgia,Serif;
}
.bg2 {
color:black;
background-color:#E0E0E0;
font-size:110%
}
th {
font-weight: bold;
}
tr, td, th {
padding:2px;
}
td.test_passed {
color:blue;
}
td.test_failed {
color:red;
}
td.center {
text-align: center;
}
td.test_skipped {
color:grey;
}
.console {
font: normal normal 90% Courier New,
monotype;
padding:0px;
margin:0px;
}
div.content, div.header {
background: #ffffff;
border: dotted
1px #666;
margin: 2px;
content:
2px;
padding: 2px;
}
table.border, th.border, td.border {
border:
1px solid black;
border-collapse:collapse;
}
.round_border{margin-bottom:5px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;margin-top:0;font-size:14px;padding:6px;border:1px solid #ccc}
.status{background-color:<j:choose><j:when test="${build.result==\'SUCCESS\'}">green</j:when><j:otherwise>red</j:otherwise></j:choose>;font-size:28px;font-weight:bold;color:white;height:52px;margin-bottom:18px;text-align:center;vertical-align:middle;border-collapse:collapse;background-repeat:no-repeat}
.status .info{color:white!important;text-shadow:0 -1px 0 rgba(0,0,0,0.3);font-size:32px;line-height:36px;padding:8px 0}
</style>
</head>
<body>
<div class="status">
<p class="info">${project.name}构建<j:choose><j:when test="${build.result==\'SUCCESS\'}">成功</j:when><j:otherwise>失败</j:otherwise></j:choose></p>
</div>
<div class="header round_border">
<j:set var="spc" value=" " />
<h1>基本信息</h1>
<table>
<tr>
<td>构建地址</td>
<td>
<a href="${rooturl}${build.url}">${rooturl}${build.url}</a>
</td>
</tr>
<tr>
<td>项目:</td>
<td>${project.name}</td>