系统:ubuntu19.04

数据库:mysql5.7

git服务器:gogs

持续集成工具:jenkins

私有仓库:harbor

1.创建一个网络

1
2
3
4
5
# 创建
docker network create -d bridge --subnet=172.20.0.0/24 --gateway=172.20.0.1 deploy

# 删除
docker network rm deploy

2.搭建gogs服务器

2.1 创建数据卷目录

1
2
3
sudo mkdir -p /var/jenkins/docker_mysql/data
sudo mkdir -p /var/jenkins/docker_mysql/log
sudo mkdir -p /var/jenkins/docker_gogs

2.2 编写docker-compose.yaml

文件存放路径~/jenkins/gogs

gogs初始化数据库时填写mysql的IP地址(172.20.0.2)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
version: '3'
services:
# mysql service
mysql:
container_name: mysql
image: mysql:5.7
ports:
- "3306:3306"
environment:
TZ: 'Asia/Shanghai'
MYSQL_ROOT_PASSWORD: 123456 # 数据库密码
MYSQL_DATABASE: gogs # 数据库名称
MYSQL_USER: root
MYSQL_PASSWORD: wyy123456
volumes:
- /var/jenkins/docker_mysql/data:/var/lib/mysql
- /var/jenkins/docker_mysql/log:/var/log/mysql
restart: always
command: mysqld --lower_case_table_names=1 --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci
networks:
deploy:
ipv4_address: 172.20.0.2
# gogs service
gogs:
container_name: gogs
image: gogs/gogs:latest
ports:
- "10022:22"
- "10080:3000"
volumes:
- /var/jenkins/docker_gogs:/data
depends_on:
- "mysql"
restart: always
environment:
TZ: 'Asia/Shanghai'
networks:
deploy:
ipv4_address: 172.20.0.3
networks:
#使用已经存在的网桥
deploy:
external: true
#networks:
# gogs-network:
# driver: "bridge"
# ipam:
# driver: default
# config:
# - subnet: 172.18.0.0/24
# # gateway: 172.18.0.1 version3 不需要

启动容器sudo docker-compose up -d

2.3 gogs初始化

访问 172.20.0.3:10080

配置数据库,填写数据库访问账号密码及ip

root wyy123456 172.20.0.2

设置gogs账号密码

gogs wyy123456

2.4 操作指令

1
2
3
4
5
6
7
8
9
10
11
sudo docker-compose stop
sudo docker rm gogs mysql gitea
# 删除gogs创建的网桥
sudo docker network rm gogs_gogs-network
sudo rm -rf /var/docker_mysql/data /var/docker_mysql/log /var/docker_gogs/ /var/gitea/

# 查看docker网桥
sudo docker network ls

sudo mkdir -p /var/docker_mysql/data /var/docker_mysql/log /var/docker_gogs/ /var/gitea/
sudo docker-compose up -d

3.搭建Registry仓库(改用harbor)

docker仓库改成使用harbor,搭建harbor看文章《Harbor搭建私有docker registry》,需要自定义需要自行修改docker-compose.yaml

3.1 docker使用国内镜像进行加速

常用站点

1
2
3
4
5
6
https://registry.docker-cn.com
http://hub-mirror.c.163.com
https://3laho3y3.mirror.aliyuncs.com
http://f1361db2.m.daocloud.io
https://mirror.ccs.tencentyun.com
http://mirrors.ustc.edu.cn/

编辑文件/etc/docker/daemon.json
insecure-registries指定私有仓库的ip:port

1
2
3
4
5
6
7
8
{
"registry-mirrors": [
"https://registry.docker-cn.com",
"http://hub-mirror.c.163.com",
"https://docker.mirrors.ustc.edu.cn"
],
"insecure-registries": ["127.0.0.1:5000"]
}

重启docker daemon

1
2
$ sudo systemctl daemon-reload
$ sudo systemctl restart docker

3.2 下载registry镜像

1
sudo docker pull registry:2

3.3 创建数据卷

1
2
sudo mkdir -p /var/docker_registry/data
sudo mkdir -p /var/docker_registry/auth

3.4 创建用户授权文件

1
2
3
4
5
6
7
8
9
10
11
12
# username: wyy  passwd: 123456
# root用户
sudo su

sudo docker run --name create_auth --entrypoint htpasswd registry:2 -Bbn wyy 123456 >> /var/docker_registry/auth/htpasswd

exit
# delete tmp container
sudo docker rm create_auth

# show htpassed content
cat /var/docker_registry/auth/htpasswd

3.5 编写docker-compose.yaml

文件存放路径/jenkins/registry

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
version: '3'
services:
# registry service
registry:
container_name: my-registry
image: registry:2
ports:
- "5000:5000"
volumes:
- /var/docker_registry/auth:/auth
- /var/docker_registry/data/var/lib/registry/
restart: always
environment:
- REGISTRY_AUTH="htpasswd"
- REGISTRY_AUTH_HTPASSWD_REALM="Registry Realm"
- REGISTRY_AUTH_HTPASSWD_PATH="/auth/htpasswd"
networks:
deploy:
ipv4_address: 172.18.0.5
networks:
#使用已经存在的网桥
deploy:
external: true
1
2
3
4
5
6
7
8
# 浏览器访问的时候需要填写账号密码
docker run -d -p 5000:5000 --restart=always --name my-registry \
-v /opt/docker-registry/auth:/auth \
-e "REGISTRY_AUTH=htpasswd" \
-e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \
-e "REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd" \
-v /opt/docker-registry/data:/var/lib/registry/ \
registry

3.6 登录docker registry

1
2
3
# 账号密码是上一步创建
登录:sudo docker login -u wyy -p 123456 127.0.0.1:5000
退出:sudo docker logout 127.0.0.1:5000

3.7 验证是否运行成功

打开浏览器,访问下面的链接

1
127.0.0.1:5000/v2/_catalog

返回{"repositories":[]}说明正常

3.8 推送镜像到私有仓库

1
2
3
# 先登陆才能push
docker tag nginx 127.0.0.1:5000/nginx
docker push 127.0.0.1:5000/nginx

3.9 私有仓库拉取镜像到本地

1
docker pull 127.0.0.1:5000/nginx

4.搭建jenkins环境

4.1 构建jenkins容器

由于我们需要在jenkins容器中运行宿主机中的docker,并且需要调用ansible的命令,所以需要重新构建官方的jenkins容器。

1
2
3
4
5
6
7
8
9
10
11
sudo mkdir -p ~/jenkins/jenkins
cd ~/jenkins/jenkins

sudo wget https://repo.jenkins-ci.org/public/org/jenkins-ci/main/jenkins-war/2.199/jenkins-war-2.199.war

# 构建镜像需要使用到
sha256sum jenkins-war-2.199.war
# e5095ae6f8ccf7ef4934d7745f2e086a2e9e2a42a0e6777f009d3e54c8404b96 jenkins-war-2.199.war

# 官方jenkins容器仓库
sudo git clone https://github.com/jenkinsci/docker.git

4.2 修改jenkins容器Dockerfile

1
2
3
4
5
6
7
cd ~/jenkins/jenkins/docker

#jenkins的时区设置
sudo cp /usr/share/zoneinfo/Asia/Shanghai localtime
echo 'Asia/Shanghai' > timezone # 使用root创建

sudo vi Dockerfile

需要修改的内容

第3行

1
2
3
4
5
6
7
RUN apt-get update && apt-get upgrade -y && apt-get install -y git curl &&     rm -rf /var/lib/apt/lists/*
#修改为
RUN echo "deb http://ppa.launchpad.net/ansible/ansible/ubuntu trusty main" >> /etc/apt/sources.list \
&& apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 93C4A3FD7BB9C367 \
&& apt-get update \
&& apt-get install -y git curl ansible libltdl7 \
&& rm -rf /var/lib/apt/lists/*

第36行

1
2
3
ARG TINI_VERSION=v0.16.1
#修改为
ARG TINI_VERSION=v0.18.0

第47行

1
2
3
ENV JENKINS_VERSION ${JENKINS_VERSION:-2.176.2}
#修改为
ENV JENKINS_VERSION ${JENKINS_VERSION:-2.199}

第50行

1
2
3
JENKINS_SHA=33a6c3161cf8de9c8729fd83914d781319fd1569acf487c7b1121681dba190a5
#修改为
JENKINS_SHA=e5095ae6f8ccf7ef4934d7745f2e086a2e9e2a42a0e6777f009d3e54c8404b96

第73行

1
2
3
4
5
6
7
#注释
USER ${user}

#添加
USER root
COPY localtime /etc/localtime
COPY timezone /etc/timezone

完整的Dockerfile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
FROM openjdk:8-jdk-stretch

# RUN apt-get update && apt-get upgrade -y && apt-get install -y git curl && rm -rf /var/lib/apt/lists/*
RUN echo "deb http://ppa.launchpad.net/ansible/ansible/ubuntu trusty main" >> /etc/apt/sources.list \
&& apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 93C4A3FD7BB9C367 \
&& apt-get update \
&& apt-get install -y git curl ansible libltdl7 \
&& rm -rf /var/lib/apt/lists/*

ARG user=jenkins
ARG group=jenkins
ARG uid=1000
ARG gid=1000
ARG http_port=8080
ARG agent_port=50000
ARG JENKINS_HOME=/var/jenkins_home
ARG REF=/usr/share/jenkins/ref

ENV JENKINS_HOME $JENKINS_HOME
ENV JENKINS_SLAVE_AGENT_PORT ${agent_port}
ENV REF $REF

# Jenkins is run with user `jenkins`, uid = 1000
# If you bind mount a volume from the host or a data container,
# ensure you use the same uid
RUN mkdir -p $JENKINS_HOME \
&& chown ${uid}:${gid} $JENKINS_HOME \
&& groupadd -g ${gid} ${group} \
&& useradd -d "$JENKINS_HOME" -u ${uid} -g ${gid} -m -s /bin/bash ${user}

# Jenkins home directory is a volume, so configuration and build history
# can be persisted and survive image upgrades
VOLUME $JENKINS_HOME

# $REF (defaults to `/usr/share/jenkins/ref/`) contains all reference configuration we want
# to set on a fresh new installation. Use it to bundle additional plugins
# or config file with your custom jenkins Docker image.
RUN mkdir -p ${REF}/init.groovy.d

# Use tini as subreaper in Docker container to adopt zombie processes
ARG TINI_VERSION=v0.18.0
COPY tini_pub.gpg ${JENKINS_HOME}/tini_pub.gpg
RUN curl -fsSL https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini-static-$(dpkg --print-architecture) -o /sbin/tini \
&& curl -fsSL https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini-static-$(dpkg --print-architecture).asc -o /sbin/tini.asc \
&& gpg --no-tty --import ${JENKINS_HOME}/tini_pub.gpg \
&& gpg --verify /sbin/tini.asc \
&& rm -rf /sbin/tini.asc /root/.gnupg \
&& chmod +x /sbin/tini

# jenkins version being bundled in this docker image
ARG JENKINS_VERSION
ENV JENKINS_VERSION ${JENKINS_VERSION:-2.199}

# jenkins.war checksum, download will be validated using it
# ARG JENKINS_SHA=33a6c3161cf8de9c8729fd83914d781319fd1569acf487c7b1121681dba190a5
ARG JENKINS_SHA=e5095ae6f8ccf7ef4934d7745f2e086a2e9e2a42a0e6777f009d3e54c8404b96

# Can be used to customize where jenkins.war get downloaded from
ARG JENKINS_URL=https://repo.jenkins-ci.org/public/org/jenkins-ci/main/jenkins-war/${JENKINS_VERSION}/jenkins-war-${JENKINS_VERSION}.war

# could use ADD but this one does not check Last-Modified header neither does it allow to control checksum
# see https://github.com/docker/docker/issues/8331
RUN curl -fsSL ${JENKINS_URL} -o /usr/share/jenkins/jenkins.war \
&& echo "${JENKINS_SHA} /usr/share/jenkins/jenkins.war" | sha256sum -c -

ENV JENKINS_UC https://updates.jenkins.io
ENV JENKINS_UC_EXPERIMENTAL=https://updates.jenkins.io/experimental
ENV JENKINS_INCREMENTALS_REPO_MIRROR=https://repo.jenkins-ci.org/incrementals
RUN chown -R ${user} "$JENKINS_HOME" "$REF"

# for main web interface:
EXPOSE ${http_port}

# will be used by attached slave agents:
EXPOSE ${agent_port}

ENV COPY_REFERENCE_FILE_LOG $JENKINS_HOME/copy_reference_file.log

# USER ${user}
USER root
COPY localtime /etc/localtime
COPY timezone /etc/timezone

COPY jenkins-support /usr/local/bin/jenkins-support
COPY jenkins.sh /usr/local/bin/jenkins.sh
COPY tini-shim.sh /bin/tini
ENTRYPOINT ["/sbin/tini", "--", "/usr/local/bin/jenkins.sh"]

# from a derived Dockerfile, can use `RUN plugins.sh active.txt` to setup ${REF}/plugins from a support bundle
COPY plugins.sh /usr/local/bin/plugins.sh
COPY install-plugins.sh /usr/local/bin/install-plugins.sh

构建jenkins docker镜像

1
sudo docker build -t jenkins:2.199 .

4.3 编写docker-compose.yml

jenkins需要访问域名,需要设置dsn

文件存放路径/jenkins/jenkins

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
version: '3'
services:
# jenkins service
jenkins:
container_name: jenkins_inte
image: jenkins:2.199
ports:
- "8080:8080"
volumes:
- /var/jenkins_home:/var/jenkins_home
- /usr/bin/make:/usr/bin/make
- /usr/bin/docker:/usr/bin/docker
- /var/run/docker.sock:/var/run/docker.sock
restart: always
dns:
- 114.114.114.114
environment:
- JAVA_OPTS="-Dorg.apache.commons.jelly.tags.fmt.timeZone=Asia/Shanghai"
- JAVA_OPTS="-Duser.timezone=Asia/Shanghai"
- JAVA_OPTS="-Dfile.encoding=UTF-8"
networks:
deploy:
ipv4_address: 172.20.0.4
networks:
#使用已经存在的网桥
deploy:
external: true

启动jenkins docker容器

1
sudo docker-compose up -d

4.4 jenkins初始化

初始化需要时间比较长

密码:sudo cat /var/jenkins_home/secrets/initialAdminPassword

创建管理员账号

1
2
3
4
5
6
7
8
# 用户名
jenkins
# 密码
wyy123456
# 全名
jenkins
# 电子邮件地址
1368299513@qq.com

4.4.1 安装插件

安装go,ansiColor和ansible插件

重启jenkins:127.0.0.1:8080/restart

4.4.2 创建凭证

gogs访问账号

私有容器仓库访问账号

ansible凭证

4.4.2 全局工具配置

新增ansible配置

新增docker配置

sudo docker -v

新增go配置

1
2
3
4
5
6
7
8
9
10
11
12
# 手动下载golang
sudo mkdir -p /var/jenkins/mytools
cd /var/jenkins/mytools
sudo wget https://studygolang.com/dl/golang/go1.13.1.linux-amd64.tar.gz
sudo tar xvf go1.13.1.linux-amd64.tar.gz
sudo mv go go1.13.1

# gopath
sudo mkdir -p /var/jenkins/mytools/gopath
sudo mkdir -p /var/jenkins/mytools/gopath/src
sudo mkdir -p /var/jenkins/mytools/gopath/pkg
sudo mkdir -p /var/jenkins/mytools/gopath/bin

4.4.3 SSH远程登录

如果要使用root用户远程登陆需要修改ssh配置文件(非root用户无需修改)

设置root用户密码sudo passwd root

编辑/etc/ssh/sshd_config文件(没有这文件需要安装ssh:sudo apt install ssh)

1
2
3
# 找到PermitRootLogin,注释掉这一行
# 添加PermitRootLogin yes,保存,退出。
sudo service sshd restart

生成密钥

1
2
3
4
5
6
7
8
9
10
11
12
# 进入 jenkins 容器交互模式
sudo docker exec -it jenkins_inte bash

# 生成 ssh 免密登入 密钥
mkdir -p /var/jenkins_home/ssh
ssh-keygen -t rsa -C "1368299513@qq.com" -f /var/jenkins_home/ssh/jenkins_ansible

# 复制公钥到远程免密登入的服务器上
ssh-copy-id -i /var/jenkins_home/ssh/jenkins_ansible.pub root@192.168.221.132

# 退出容器
exit

4.5 设置邮箱

注意需要设置dns,否则无法访问smtp.qq.com域名

4.5.1 jenkins自带邮箱设置

4.5.1 邮箱插件配置

首先要先安装Email Extension Plugin

设置发件人等信息

配置邮件内容模版

邮件内容模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>${ENV, var="JOB_NAME"}-第${BUILD_NUMBER}次构建日志</title>
</head>

<body leftmargin="8" marginwidth="0" topmargin="8" marginheight="4"
offset="0">
<table class="table" width="1000" border="1" cellspacing="0" cellpadding="5">
<caption align="center"><h3>项目构建信息</h3></caption>
<thead>
<tr>
<th>构建项目-<b>${PROJECT_NAME}</b></th>
</tr>
</thead>
<tbody>
<tr>
<td><font color="#CC0000"><b>构建结果 - ${BUILD_STATUS}</b></font></td>
</tr>
<tr>
<td>测试数量-${TEST_COUNTS, var="total"}</td>
</tr>
</tbody>
<tr>
本邮件由系统自动发出,无需回复!<br/>
各位同事,大家好,以下为${PROJECT_NAME}项目构建信息</br>
</tr>

<tr>
<td><br/>
<b><font color="#0B610B">构建报告</font></b>
<hr size="2" width="100%" align="center"/>
<ul>
<li>构建报告:<a href="${BUILD_URL}allure/">${PROJECT_URL}allure/</li>
</ul>
</td>
</tr>
<tr>
<td><br/>
<b><font color="#0B610B">构建信息</font></b>
<hr size="2" width="100%" align="center"/>
<ul>
<li>项目名称: ${PROJECT_NAME}</li>
<li>构建编号: 第${BUILD_NUMBER}次构建</li>
<li>触发原因:${CAUSE}</li>
<li>构建状态:${BUILD_STATUS}</li>
<li>构建日志:<a href="${BUILD_URL}console">${BUILD_URL}console</a></li>
<li>构建 Url: <a href="${BUILD_URL}">${BUILD_URL}</a></li>
<li>工作目录: <a href="${PROJECT_URL}ws">${PROJECT_URL}ws</a></li>
<li>项目 Url: <a href="${PROJECT_URL}">${PROJECT_URL}</a></li>
</ul>
</td>
</tr>
<tr>
<td>
<b><font color="#0B610B">构建详细信息</font></b>
<hr size="2" width="100%" align="center"/>
<ul>
<li>BRANCH_NAME: ${BRANCH_NAME}</li>
<li>CHANGE_ID: ${CHANGE_ID}</li>
<li>CHANGE_URL: ${CHANGE_URL}</li>
<li>CHANGE_TITLE: ${CHANGE_TITLE}</li>
<li>CHANGE_AUTHOR: ${CHANGE_AUTHOR}</li>
<li>CHANGE_AUTHOR_DISPLAY_NAME: ${CHANGE_AUTHOR_DISPLAY_NAME}</li>
<li>CHANGE_AUTHOR_EMAIL: ${CHANGE_AUTHOR_EMAIL}</li>
<li>CHANGE_TARGET: ${CHANGE_TARGET}</li>
<li>BUILD_NUMBER: ${BUILD_NUMBER}</li>
<li>BUILD_DISPLAY_NAME: ${BUILD_DISPLAY_NAME}</li>
<li>BUILD_ID: ${BUILD_ID}</li>
<li>JOB_NAME: ${JOB_NAME}</li>
<li>JOB_BASE_NAME: ${JOB_BASE_NAME}</li>
<li>BUILD_TAG: ${BUILD_TAG}</li>
<li>EXECUTOR_NUMBER: ${EXECUTOR_NUMBER}</li>
<li>NODE_NAME: ${NODE_NAME}</li>
<li>NODE_LABELS: ${NODE_LABELS}</li>
<li>WORKSPACE: ${WORKSPACE}</li>
<li>JENKINS_HOME: ${JENKINS_HOME}</li>
<li>JENKINS_URL: ${JENKINS_URL}</li>
<li>BUILD_URL: ${BUILD_URL}</li>
<li>JOB_URL: ${JOB_URL}</li>
<li>GIT_COMMIT: ${GIT_COMMIT}</li>
<li>GIT_PREVIOUS_COMMIT: ${GIT_PREVIOUS_COMMIT}</li>
<li>GIT_PREVIOUS_SUCCESSFUL_COMMIT: ${GIT_PREVIOUS_SUCCESSFUL_COMMIT}</li>
<li>GIT_BRANCH: ${GIT_BRANCH}</li>
<li>GIT_LOCAL_BRANCH: ${GIT_LOCAL_BRANCH}</li>
<li>GIT_URL: ${GIT_URL}</li>
<li>GIT_COMMITTER_NAME: ${GIT_COMMITTER_NAME}</li>
<li>GIT_AUTHOR_NAME: ${GIT_AUTHOR_NAME}</li>
<li>GIT_COMMITTER_EMAIL: ${GIT_COMMITTER_EMAIL}</li>
<li>GIT_AUTHOR_EMAIL: ${GIT_AUTHOR_EMAIL}</li>
<li>MERCURIAL_REVISION: ${MERCURIAL_REVISION}</li>
<li>MERCURIAL_REVISION_SHORT: ${MERCURIAL_REVISION_SHORT}</li>
<li>MERCURIAL_REVISION_NUMBER: ${MERCURIAL_REVISION_NUMBER}</li>
<li>MERCURIAL_REVISION_BRANCH: ${MERCURIAL_REVISION_BRANCH}</li>
<li>MERCURIAL_REPOSITORY_URL: ${MERCURIAL_REPOSITORY_URL}</li>
<li>SVN_REVISION: ${SVN_REVISION}</li>
<li>SVN_URL: ${SVN_URL}</li>
</ul>
</td>
</tr>
<tr>
<td>
<h4><font color="#0B610B">失败用例</font></h4>
${FAILED_TESTS}<br/>
</td>
</tr>

<tr>
<td>
<h4><font color="#0B610B">最近提交(#$GIT_REVISION)</font></h4>
<!--
<ul>
${CHANGES_SINCE_LAST_SUCCESS, reverse=true, format="%c", changesFormat="
<li>%d [%a] %m</li>
"}
</ul>
<-->
</td>
</tr>
<tr>
<td>
<b><font color="#0B610B">变更信息:</font></b>
<hr size="2" width="100%" align="center"/>
</td>
</tr>
<tr>
<td>
<ul>
<li>
上次构建成功后变化 : ${CHANGES_SINCE_LAST_SUCCESS, reverse=true, format="%c", changesFormat="<li>%d [%a] %m</li>"}
</li>
</ul>
</td>
</tr>
<tr>
<td>
<ul>
<li>上次构建不稳定后变化 : ${CHANGES_SINCE_LAST_UNSTABLE, reverse=true, format="%c", changesFormat="<li>%d [%a] %m</li>"}
</li>
</ul>
</td>
<tr>
<td>
<ul>
<li>历史变更记录 : <a href="${PROJECT_URL}changes">${PROJECT_URL}changes</a></li>
</ul>
</td>
</tr>
<tr>
<td>
<ul>
<li>变更集:${JELLY_SCRIPT,template="html"}</li>
</ul>
</td>
</tr>
<tr>
<td><b><font color="#0B610B">构建日志 (最后 200行):</font></b>
<hr size="2" width="100%" align="center"/>
</td>
</tr>
<tr>
<td><textarea cols="120" rows="30" readonly="readonly"
style="font-family: Courier New">${BUILD_LOG, maxLines=200}</textarea>
</td>
</tr>
</table>
</body>
</html>

设置邮件触发机制

4.6 项目构建邮箱提醒

在项目配置中设置

4.6.1 使用自带邮箱提醒

4.6.2 使用邮箱插件(推荐)

使用插件可以自定义邮件内容,添加附件等功能,推荐使用

5.构建go运行alpine容器

编写Dockerfile文件,文件存放路径~/jenkins/alpine

1
2
3
4
5
6
7
8
9
10
11
12
13
FROM alpine:latest

WORKDIR /

RUN apk update \
# Update and updgrage alpine packages
&& apk upgrade \
# Install required packages
&& apk --no-cache add bash ca-certificates libc6-compat tzdata \
&& cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& echo "Asia/Shanghai" > /etc/timezone \
&& echo "hosts: files mdns4_minimal[NOTFOUND=return] dns mdns4" >> /etc/nsswitch.conf \
&& rm -rf /var/cache/apk/* /tmp/*

构建镜像:sudo docker build -t 172.19.0.1/library/alpine-ca .

测试alpine-ca镜像

1
2
sudo docker run -it --rm  172.19.0.1/library/alpine-ca date
# Mon Oct 14 21:15:37 CST 2019

推送镜像到私有仓库

1
2
3
# 先登陆仓库
sudo docker login -u admin -p 123456 172.19.0.1
sudo docker push 172.19.0.1/library/alpine-ca

6.测试环境编译部署

为微服务创建一个网络

1
2
3
4
5
# 创建
docker network create -d bridge --subnet=172.21.0.0/24 --gateway=172.21.0.1 microx_dev

# 删除
docker network create microx_dev

任务命名规则:框架名-类型-服务-环境-本服务ip-配置中心ip

microx-srv-gconfig-dev-172.21.0.3-172.21.0.2

pipeline脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
#!groovy

timestamps {
node {
def GOROOT = tool 'GOROOT_1.13.1'
def DOCKERROOT = tool name: 'DOCKER_19.03.2-ce', type: 'org.jenkinsci.plugins.docker.commons.tools.DockerTool'
env.PATH = "${GOROOT}/bin:${DOCKERROOT}:${env.PATH}"

env.GOPATH = "/var/jenkins_home/mytools/gopath"
env.GOPROXY = "https://goproxy.io"

def String FIRST_NAME = "${JOB_NAME}".split('-')[0]
def String MIDDLE_NAME = "${JOB_NAME}".split('-')[1]
def String LAST_NAME = "${JOB_NAME}".split('-')[2]
def String DEPLOY = "${JOB_NAME}".split('-')[3]
def String SERVER_IP = "${JOB_NAME}".split('-')[4]
def String GCONFIG_IP = "${JOB_NAME}".split('-')[5]


env.DEPLOY_MOD = "${DEPLOY}"
env.GCONFIG_ADDR = "${GCONFIG_IP}:9600"

env.WORKPATH = "/var/jenkins_home/workspace/${JOB_NAME}"
env.BUILDPATH = "${env.WORKPATH}/${MIDDLE_NAME}/${LAST_NAME}"
def String BUILDPATH = "${env.BUILDPATH}"

env.BINARYNAME = "${MIDDLE_NAME}-${LAST_NAME}"
env.CONTAINERNAME = "${env.BINARYNAME}"
env.DOCKERFILE = "${env.BUILDPATH}/Dockerfile"
env.IMAGENAME = "${MIDDLE_NAME}-${LAST_NAME}-${DEPLOY}"

// gogs git 凭证秘钥
def String MYCREDENTIALSID = "68f960c0-cf08-44af-a27b-3c658f73794c"
def String MYREPOSITORYURL = "http://172.20.0.3:3000/jenkins/microx.git"

// ansible私钥 凭证秘钥
def String ANSIBLECREID = "092d8e04-81fd-4081-b7a3-5de9b07a3166"
// 远程服务器
def String PLAYBOOKPATH = "/var/jenkins_home/ansible"

// harbor docker私服 凭证秘钥
def String REGISTRYCREID = "0104b6b6-ae88-4fb3-9d65-42262bbce892"
def String REGISTRYURL = "172.22.0.1"
def String REGISTRYUSER="admin"
def String REGISTRYPASS="123456"
def String NAMESPACES = "library"
def String IMGBRANCH = "latest"
//env.IMAGETAG = "${REGISTRYURL}/${NAMESPACES}/${env.IMAGENAME}:${IMGBRANCH}"
env.IMAGETAG = "${REGISTRYURL}/${NAMESPACES}/${env.IMAGENAME}:${IMGBRANCH}"

// 打印环境变量
sh "echo $PATH"
sh "echo ${env.WORKPATH}"
sh "echo ${BUILDPATH}"
sh "echo ${env.BINARYNAME}"
sh "echo ${FIRST_NAME}"
sh "echo ${MIDDLE_NAME}"
sh "echo ${LAST_NAME}"

// 拉取gogs代码
stage('拉取代码') {
// deleteDir()
git credentialsId: "${MYCREDENTIALSID}", url: "${MYREPOSITORYURL}"
}

// 生成编译依赖
stage('安装依赖') {
echo "microx 不需要这个步骤!"
// sh 'cd ./common/proto && sh build.sh'
}

// 停止 dmicro 所有容器,并删除
stage('删除容器') {
echo "drop microx container"

// 停止旧容器
try {
sh "docker rm -f ${env.CONTAINERNAME}"
}catch(error){
echo error.getMessage()
}

// 删除旧镜像
try {
sh "docker rmi ${env.IMAGETAG}"
}catch(error){
echo error.getMessage()
}
}

// 编译二进制文件、构建docker镜像、推送镜像至阿里云
stage('编译构建') {
echo "compile binary"

// 编译二进制文件
try {
sh'''#!/bin/bash -xe
echo $SHELL
echo $PATH
go version
go env
cd ${BUILDPATH}
env CGO_ENABLED=0 GOOS=linux go build -v -o ${BINARYNAME}
'''
}catch(error){
echo error.getMessage()
}

// 构建docker镜像
def customImage = docker.build("${env.IMAGETAG}", "-f ${env.DOCKERFILE} ${BUILDPATH}")

// 推送镜像私服
docker.withRegistry("http://${REGISTRYURL}", "${REGISTRYCREID}") {
customImage.push()
}

}

// ansible剧本文件
stage('部署容器') {
echo "deploment microx service"

sh "echo ${env.CONTAINERNAME}"
sh "echo ${env.IMAGETAG}"
sh "echo ${env.DEPLOY_MOD}"

ansiColor('xterm') {
ansiblePlaybook (
colorized: true,
// become: true,
// sudo: true,
// tags: "alldown, allstatus",
disableHostKeyChecking: true,
credentialsId: "${ANSIBLECREID}",
installation: "ansible-playbook",
inventory: "${PLAYBOOKPATH}/hosts",
playbook: "${PLAYBOOKPATH}/deployment_microx_dev.yaml",
extras: "-v",
extraVars: [
CONTAINER_NAME: "${env.CONTAINERNAME}",
IMAGE_NAME: "${env.IMAGETAG}",
DEPLOY_MOD: "${env.DEPLOY_MOD}",
GCONFIG_ADDR: "${env.GCONFIG_ADDR}",
REGISTRY_USER: "${REGISTRYUSER}",
REGISTRY_PASS: "${REGISTRYPASS}",
REGISTRY_LINK: "${REGISTRYURL}",
SERVER_ADDR: "${SERVER_IP}",
]
)
}

}

}

}

ansible hosts

mkdir /var/jenkins_home/ansible

vi /var/jenkins_home/ansible/hosts

1
2
[dev]
dev1 ansible_ssh_host=192.168.221.132 ansible_ssh_port=22 ansible_ssh_private_key_file=/var/jenkins_home/ssh/jenkins_ansible

vi /var/jenkins_home/ansible/deployment_microx_dev.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
---
- hosts: dev
vars:
- BASE_PATH: /root/microx_dev
tasks:
- name: docker stop
command: docker stop {{ CONTAINER_NAME }}
ignore_errors: True
tags: alldown

- name: docker rm
command: docker rm -f {{ CONTAINER_NAME }}
ignore_errors: True
tags: alldown

- name: login to private docker registry
command: docker login -u {{ REGISTRY_USER }} -p {{ REGISTRY_PASS }} {{ REGISTRY_LINK }}

- name: docker pull image
command: docker pull {{ IMAGE_NAME }}

- name: check cid file
shell: if [[ -f "{{ BASE_PATH }}/{{ CONTAINER_NAME }}/cid" ]]; then /bin/true; else /bin/false; fi
register: result
ignore_errors: True
- command: mkdir -p {{ BASE_PATH }}/{{ CONTAINER_NAME }}
when: result is failed
- command: touch {{ BASE_PATH }}/{{ CONTAINER_NAME }}/cid
when: result is failed
- command: cat {{ BASE_PATH }}/{{ CONTAINER_NAME }}/cid
when: result is success

- name: docker up in the background
# command: docker run -d --net didong_default --name {{ CONTAINER_NAME }} --link consul:consul -e MICRO_REGISTRY_ADDRESS=consul:8500 -v {{ BASE_PATH }}/{{ CONTAINER_NAME }}/cid:/cid -v {{ BASE_PATH }}/{{ CONTAINER_NAME }}/logs:/logs {{ IMAGE_NAME }} --config_address={{ GCONFIG_ADDR }}
# command: docker run -d --net didong_default --name {{ CONTAINER_NAME }} -e MICRO_REGISTRY_ADDRESS=consul:8500 -v {{ BASE_PATH }}/{{ CONTAINER_NAME }}/cid:/cid -v {{ BASE_PATH }}/{{ CONTAINER_NAME }}/logs:/logs {{ IMAGE_NAME }} --config_address={{ GCONFIG_ADDR }}
command: docker run -d --net microx_dev --ip {{ SERVER_ADDR }} --name {{ CONTAINER_NAME }} -e MICRO_REGISTRY_ADDRESS=consul:8500 -e MICROX_DEPLOY_ENV={{ DEPLOY_MOD }} -v {{ BASE_PATH }}/{{ CONTAINER_NAME }}/cid:/cid -v {{ BASE_PATH }}/{{ CONTAINER_NAME }}/logs:/logs {{ IMAGE_NAME }} --config_address={{ GCONFIG_ADDR }}

- name: delete all unlabeled images
shell: docker rmi -f `docker images -f dangling=true -q`
ignore_errors: True

- name: container status
command: docker logs {{ CONTAINER_NAME }}
register: psinfo
tags: allstatus

- name: display variable
debug:
var: psinfo.stdout
verbosity: 0
tags: allstatus

7.正式环境编译部署

microx_srv-gconfig_prod

pipeline脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
#!groovy

timestamps {
node {
def GOROOT = tool 'GOROOT_1.13.1'
def DOCKERROOT = tool name: 'DOCKER_19.03.2-ce', type: 'org.jenkinsci.plugins.docker.commons.tools.DockerTool'
env.PATH = "${GOROOT}/bin:${DOCKERROOT}:${env.PATH}"

env.GOPATH = "/var/jenkins_home/mytools/gopath"
env.GOPROXY = "https://goproxy.io"

def String FIRST_NAME = "${JOB_NAME}".split('_')[0]
def String MIDDLE_NAME = "${JOB_NAME}".split('_')[1].split('-')[0]
def String LAST_NAME = "${JOB_NAME}".split('-')[1].split('_')[0]
def String DEPLOY = "${JOB_NAME}".split('-')[1].split('_')[1]

env.DEPLOY_MOD = "${DEPLOY}"
env.GCONFIG_ADDR = "gconfig:9600"

env.WORKPATH = "/var/jenkins_home/workspace/${JOB_NAME}"
env.BUILDPATH = "${env.WORKPATH}/${MIDDLE_NAME}/${LAST_NAME}"
def String BUILDPATH = "${env.BUILDPATH}"

env.BINARYNAME = "${MIDDLE_NAME}-${LAST_NAME}"
env.CONTAINERNAME = "${env.BINARYNAME}"
env.DOCKERFILE = "${env.BUILDPATH}/Dockerfile"
env.IMAGENAME = "${JOB_NAME}"

// gogs git 凭证秘钥
def String MYCREDENTIALSID = "b8ecc066-0f0b-4a05-b686-d42dff34cbb6"
def String MYREPOSITORYURL = "http://172.18.0.3:3000/jenkins/microx.git"

// ansible私钥 凭证秘钥
def String ANSIBLECREID = "092d8e04-81fd-4081-b7a3-5de9b07a3166"
// 远程服务器
def String PLAYBOOKPATH = "/var/jenkins_home/ansible"

// ocker私服 凭证秘钥
def String REGISTRYCREID = "3fd1a3be-5378-4ee9-973a-9895433b4709"
def String REGISTRYURL = "127.0.0.1:5000"
//def String NAMESPACES = "didong"
def String IMGBRANCH = "latest"
//env.IMAGETAG = "${REGISTRYURL}/${NAMESPACES}/${env.IMAGENAME}:${IMGBRANCH}"
env.IMAGETAG = "${REGISTRYURL}/${env.IMAGENAME}:${IMGBRANCH}"

def String BASE_PATH = "/root/microx_prod"

// 打印环境变量
sh "echo $PATH"
sh "echo ${env.WORKPATH}"
sh "echo ${BUILDPATH}"
sh "echo ${env.BINARYNAME}"
sh "echo ${FIRST_NAME}"
sh "echo ${MIDDLE_NAME}"
sh "echo ${LAST_NAME}"

// 拉取gogs代码
stage('拉取代码') {
// deleteDir()
git credentialsId: "${MYCREDENTIALSID}", url: "${MYREPOSITORYURL}"
}

// 生成编译依赖
stage('安装依赖') {
echo "microx 不需要这个步骤!"
// sh 'cd ./common/proto && sh build.sh'
}

// 停止 dmicro 所有容器,并删除
stage('删除容器') {
echo "drop microx container"

// 停止旧容器
try {
// sh "docker rm -f ${env.CONTAINERNAME}"
}catch(error){
echo error.getMessage()
}

// 删除旧镜像
try {
// sh "docker rmi ${env.IMAGETAG}"
}catch(error){
echo error.getMessage()
}
}

// 编译二进制文件、构建docker镜像、推送镜像至阿里云
stage('编译构建') {
echo "compile binary"

// 编译二进制文件
try {
sh'''#!/bin/bash -xe
echo $SHELL
echo $PATH
go version
go env
cd ${BUILDPATH}
env CGO_ENABLED=0 GOOS=linux go build -v -o ${BINARYNAME}
'''
}catch(error){
echo error.getMessage()
}

// 构建docker镜像
def customImage = docker.build("${env.IMAGETAG}", "-f ${env.DOCKERFILE} ${BUILDPATH}")

// 推送镜像私服
docker.withRegistry("http://${REGISTRYURL}", "${REGISTRYCREID}") {
customImage.push()
}

}

// ansible剧本文件
stage('部署容器') {
echo "deploment microx service"

sh "echo ${env.CONTAINERNAME}"
sh "echo ${env.IMAGETAG}"
sh "echo ${env.DEPLOY_MOD}"

ansiColor('xterm') {
ansiblePlaybook (
colorized: true,
// become: true,
// sudo: true,
// tags: "alldown, allstatus",
disableHostKeyChecking: true,
credentialsId: "${ANSIBLECREID}",
installation: "ansible-playbook",
inventory: "${PLAYBOOKPATH}/hosts",
playbook: "${PLAYBOOKPATH}/deployment_microx_prod.yaml",
extras: "-v",
extraVars: [
CONTAINER_NAME: "${env.CONTAINERNAME}",
IMAGE_NAME: "${env.IMAGETAG}",
DEPLOY_MOD: "${env.DEPLOY_MOD}",
GCONFIG_ADDR: "${env.GCONFIG_ADDR}",
DOCKER_COMPOSE_FILE: "${BASE_PATH}/${env.CONTAINERNAME}/docker-compose.yaml"
]
)
}

}

}

}

ansible hosts

vi /var/jenkins_home/ansible/hosts

1
2
[prod]
prod1 ansible_ssh_host=192.168.23.144 ansible_ssh_port=22 ansible_ssh_private_key_file=/var/jenkins_home/ssh/jenkins_ansible

ansible deployment_microx_prod.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
---
- hosts: prod
vars:
- REGISTRY_USER: wyy
- REGISTRY_PASS: 123456
- REGISTRY_LINK: 127.0.0.1:5000
- BASE_PATH: /root/microx_prod
tasks:
- name: docker-compose down all container
command: docker-compose -f {{ DOCKER_COMPOSE_FILE }} down
tags: alldown

- name: Log in to private docker registry
command: docker login -u {{ REGISTRY_USER }} -p {{ REGISTRY_PASS }} {{ REGISTRY_LINK }}

- name: docker-compose pull all image
command: docker-compose -f {{ DOCKER_COMPOSE_FILE }} pull

- name: docker-compose up in the background
command: docker-compose -f {{ DOCKER_COMPOSE_FILE }} up -d

- name: docker-compose show container status
command: docker-compose -f {{ DOCKER_COMPOSE_FILE }} ps
register: psinfo
tags: allstatus

- name: display variable
debug:
var: psinfo.stdout
verbosity: 0
tags: allstatus

8.远程触发项目构建

8.1 新建一个自由风格的软件项目

使用playbook

8.2 生成jenkins用户token

复制保存生成的token

8.2 gogs服务器上的microx项目配置webhook

jenkins用户名:wyy token:11d57202b53d688c12789e39c85e8883b5

http://wyy:11d57202b53d688c12789e39c85e8883b5@127.0.0.1:8080/job/microx/build?token=microx-token

测试推送是否触发

9.资料

客户端Jenkins自动构建指南

微信小程序集成 Jenkins