Docker高级之——Docker Compose、Docker Swarm、Jakins持续集成、Rancher持续部署

1 容器编排概述

Docker只是一个对项目做打包和运行的小工具,如果止步于此,那么充其量就是一个开发者手里的小玩具

因为真实的项目都是要集群部署的,还要考虑负载均衡、水平扩展、动态伸缩、集群容错等问题,而Docker并不具备这样的功能。

而要想让Docker在集群中的部署如同单机部署一样的方便,那就需要用到容器编排技术了。

“编排”(Orchestration)在云计算行业里不算是新词汇,它主要是指用户如何通过某些工具或者配置来完成一组虚拟机以及关联资源的定义、配置、创建、删除等工作,然后由云计算平台按照这些指定的逻辑来完成的过程。

而容器时代,“编排”显然就是对 Docker 容器的一系列定义、配置和创建动作的管理。目前容器编排技术比较知名的包括:

  • Docker公司自己的:docker-compose + swarm组合
  • Google牵头的Kubernetes技术,简称为k8s

2 Docker Compose,配合swarm集群使用时坑,BUG多,运行的服务不稳定,写脚本一个个加服务,别用它

Docker Compose 是 Docker 官方编排(Orchestration)项目之一,负责快速的部署分布式应用,官网地址: https://github.com/docker/compose ,其前身是开源项目 Fig。

本节将介绍 Compose 项目情况以及安装和使用。

网址:https://docs.docker.com/compose/compose-file/

2.1 为什么要用Docker Compose

通过Dockerfile我们可以将一个项目很方便的打包为一个Docker镜像。但是在日常工作中,经常会碰到需要多个容器相互配合来完成某项任务的情况。例如要实现一个 Web 项目,除了 Web 服务容器本身,往往还需要再加上后端的数据库服务容器,甚至还包括负载均衡容器等。

那么如何定义各个容器的依赖关系,这就需要用到docker-compose了。

Compose 恰好满足了Docker集群化的需求。它允许用户通过一个单独的 ==docker-compose.yml== 模板文件(YAML 格式)来定义一组相关联的应用容器为一个项目(project)。

2.2 Docker Compose安装

MAC下或者Windows下的Docker自带Compose功能,无需安装。

Linux下需要通过命令安装:

1
2
3
4
5
# 安装
curl -L https://get.daocloud.io/docker/compose/releases/download/1.24.1/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
# 修改权限
chmod +x /usr/local/bin/docker-compose
ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose

2.3 Docker Compose快速入门

假设我们要部署一个SpringBoot项目,并且依赖于Redis。

2.3.1 导入微服务工程

工程的基本功能就是统计用户的访问量,代码在资料中docker-demo,直接解压到没有中文的目录下,使用Idea导入即可。

打包项目,获得app.jar

2.3.2 编写Dockerfile

(1)在任意位置创建一个新目录,docker-compose,将app.jar复制到该目录

(2)在该目录中,新建一个==Dockerfile==文件,并编写下面的内容:

1
2
3
4
FROM java:8-alpine
COPY ./app.jar /tmp/app.jar
EXPOSE 9090
ENTRYPOINT ["java","-jar","/tmp/app.jar"]

2.3.3 编写docker-compose

在刚才的目录中,创建一个==docker-compose.yml==文件并填写内容:

1
2
3
4
5
6
7
8
version: '3'
services:
web:
build: .
ports:
- "9090:9090"
redis:
image: "redis"

此时的结构如下:

image-20210626122026530

命令解读:

  • version:compose的版本
  • services:服务列表,包括两个服务:
    • web:自己写的Java项目
      • build:这个服务镜像是临时构建的,构建目录是当前目录,会利用当前目录的Dockerfile来完成构建。
      • ports:端口映射,对外开放8080端口
    • redis:redis服务

2.3.4 启动测试

将刚刚准备好的文件夹docker-compose上传到Linux的/opt目录:

image-20210626122243656

进入docker-compose目录然后执行命令:

1
docker-compose up

构建完成后,可以看到项目运行的日志信息:

image-20201109001711441

image-20201109001832104

此时,访问浏览器 http://192.168.80.151:9090/hello,可以看到下面的结果:

image-20210626123534275

如果多次访问,这个次数会累加。

CTRL+C后可以停止运行程序,并且Docker运行的容器中也会关闭。

通过docker-compose up -d命令,可以后台启动,这样就不会显示日志:

image-20201109002000959

通过docker-compose stop 关闭容器

image-20201109002527208

通过docker-compose down关闭容器并删除

image-20201109002122299

2.4 Docker Compose 相关命令

docker-compose的相关命令参数:

通过:docker-compose --help 查看

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
[root@localhost docker-demo]# docker-compose --help
利用Docker来定义和构建一个多容器的应用

使用方式:
docker-compose [-f <arg>...] [options] [COMMAND] [ARGS...]
docker-compose -h|--help

Options:
-f, --file FILE 指定一个 compose 文件,
(默认: docker-compose.yml)
-p, --project-name NAME 指定project名字
(默认: 目录名称)
--verbose 显示更多日志
--log-level LEVEL 日志级别 (DEBUG, INFO, WARNING, ERROR, CRITICAL)
-v, --version 打印版本并退出
-H, --host HOST Daemon socket to connect to
Commands:
build 构建多个service
config 校验 Compose 文件,格式是否正确,若正确则显示配置,
若格式错误显示错误原因
down 停止并删除 容器, 网络, 镜像, 和 数据卷
exec 进入一个指定的容器
help Get help on a command
images 列出该Compose中包含的各个镜像
kill 通过发送 SIGKILL 信号来强制停止服务容器
格式为 docker-compose kill [options] [SERVICE...]
logs 查看服务容器的输出日志
格式为 docker-compose logs [options] [SERVICE...]。
pause 暂停一个容器
port 打印某个容器端口所映射的公共端口
ps 列出项目中目前的所有容器
pull 拉取服务依赖的镜像
push 推送服务依赖的镜像到 Docker 镜像仓库
restart 重启项目中的服务
rm 删除停止的容器(要先停止容器)
run 在某个服务上运行指令
scale 设定某个容器的运行个数
start 启动多个 services
stop 停止多个 services
top 查看各个服务容器内运行的进程。
unpause 恢复处于暂停状态中的服务。
up 创建并启动多个service的容器
version Show the Docker-Compose version information

2.5 Docker Compose常用语法

Compose模板文件是Compose的核心,包括有多种版本的Compose文件格式–:1,2,2.x和3.x。对应的Docker版本也不一样,对照表:

Compose file formatDocker Engine release
3.819.03.0+
3.718.06.0+
3.618.02.0+
3.517.12.0+
3.417.09.0+
3.317.06.0+
3.217.04.0+
3.11.13.1+
3.01.13.0+
2.417.12.0+
2.317.06.0+
2.21.13.0+
2.11.12.0+
2.01.10.0+
1.01.9.1.+

编写时,需要根据自己的docker版本来选择指定的Compose版本。

详细语法参考文档:https://docs.docker.com/compose/compose-file/

概念

Compose 中有两个重要的概念:

  • 项目 (project):由一组关联的应用容器组成的一个完整业务单元,在 docker-compose.yml 文件中定义。
  • 服务 (service):一个应用的容器,实际上可以包括若干运行相同镜像的容器实例。

Compose 的默认管理对象是项目,通过子命令对项目中的一组容器进行便捷地生命周期管理。

Compose文件是一个YAML文件,定义 一个或多个服务(service),网络(network)和 卷(volume)。撰写文件的默认路径为./docker-compose.yml

提示:您可以为此文件使用 .yml.yaml扩展名。他们俩都工作。

简单来说,一个project包含多个service,每个service都是一个组件。例如入门案例中的Java项目和Redis都是service。部署时,可能每个service都会有多个容器去运行,形成负载均衡的集群。

因此,我们定义service,就是在定义这个service在容器运行时的规则参数,就像是给docker run命令设置参数一样。

我们定义network和volume类似于 docker network createdocker volume create这两个命令的效果。

只不过,我们定义规则,执行命令则由docker compose来完成。

参考我们之前的Demo,我们来学习下Compose的模板文件语法:

1
2
3
4
5
6
7
8
version: '3'
services:
web:
build: .
ports:
- "9090:9090"
redis:
image: "redis"

version

版本信息,详见上面提到的Compose版本与Docker的对应关系。

build

指定 Dockerfile 所在文件夹的路径(可以是绝对路径,或者相对 docker-compose.yml 文件的路径)。 Compose 将会利用它自动构建这个镜像,然后使用这个镜像。

在入门案例中,因为Dockerfiledocker-compose.yml是在一个目录,因此build值指定为.

1
2
3
4
5
6
version: '3'
services:
web:
build: .
ports:
- "8080:8080"

另外,你也可以先制定目录,然后在指定Dockerfile文件,例如:

1
2
3
4
5
6
7
8
9
10
version: '3'
services:
web:
build:
context: .
dockerfile: Dockerfile
args:
buildno: 1
ports:
- "8080:8080"

说明:

  • build:Dockerfile配置
    • context:用来指定Compose的工作环境目录,如果不指定或使用了相对路径则默认为docker-compose.yml所在目录。
    • dockerfile:指定Dockerfile的文件名称

command

覆盖容器运行时的默认命令。

1
2
3
4
5
6
7
version: '3'
services:
web:
build: .
ports:
- "8080:8080"
command: ["java", "-jar", "-Xmx256m", "/tmp/app.jar"]

depends_on

解决容器的依赖、启动先后的问题。以下例子中会先启动 redis db 再启动 web

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
version: '3'
services:
web:
build: .
ports:
- "8080:8080"
depends_on:
- db
- redis
# redis服务
redis:
image: redis
# mysql服务
db:
image: mysql:5.7

depends_on踩坑,巨坑!!!

1、就算配置了也不一定按照既定顺序走。。。BUG没办法

2、depends_on可以解决容器的运行顺序先后的问题,但它只是检测服务是否在running阶段,而这时启动慢的服务其实还没真正启动,比如Nacos依赖的Mysql,而要解决这个问题,需要使用一个脚本:wait-for

Github地址:https://github.com/Eficode/wait-for

首先下载wait-for脚本:

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
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
#!/bin/sh

# The MIT License (MIT)
#
# Copyright (c) 2017 Eficode Oy
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

VERSION="2.2.2"

set -- "$@" -- "$TIMEOUT" "$QUIET" "$PROTOCOL" "$HOST" "$PORT" "$result"
# 设置超时时间,可以设置高一些,单位:秒
TIMEOUT=120
QUIET=0
# The protocol to make the request with, either "tcp" or "http"
PROTOCOL="tcp"

echoerr() {
if [ "$QUIET" -ne 1 ]; then printf "%s\n" "$*" 1>&2; fi
}

usage() {
exitcode="$1"
cat << USAGE >&2
Usage:
$0 host:port|url [-t timeout] [-- command args]
-q | --quiet Do not output any status messages
-t TIMEOUT | --timeout=timeout Timeout in seconds, zero for no timeout
-v | --version Show the version of this tool
-- COMMAND ARGS Execute command with args after the test finishes
USAGE
exit "$exitcode"
}

wait_for() {
case "$PROTOCOL" in
tcp)
if ! command -v nc >/dev/null; then
echoerr 'nc command is missing!'
exit 1
fi
;;
wget)
if ! command -v wget >/dev/null; then
echoerr 'wget command is missing!'
exit 1
fi
;;
esac

TIMEOUT_END=$(($(date +%s) + TIMEOUT))

while :; do
case "$PROTOCOL" in
tcp)
nc -w 1 -z "$HOST" "$PORT" > /dev/null 2>&1
;;
http)
wget --timeout=1 -q "$HOST" -O /dev/null > /dev/null 2>&1
;;
*)
echoerr "Unknown protocol '$PROTOCOL'"
exit 1
;;
esac

result=$?

if [ $result -eq 0 ] ; then
if [ $# -gt 7 ] ; then
for result in $(seq $(($# - 7))); do
result=$1
shift
set -- "$@" "$result"
done

TIMEOUT=$2 QUIET=$3 PROTOCOL=$4 HOST=$5 PORT=$6 result=$7
shift 7
exec "$@"
fi
exit 0
fi

if [ $TIMEOUT -ne 0 -a $(date +%s) -ge $TIMEOUT_END ]; then
echo "Operation timed out" >&2
exit 1
fi

sleep 1
done
}

while :; do
case "$1" in
http://*|https://*)
HOST="$1"
PROTOCOL="http"
shift 1
;;
*:* )
HOST=$(printf "%s\n" "$1"| cut -d : -f 1)
PORT=$(printf "%s\n" "$1"| cut -d : -f 2)
shift 1
;;
-v | --version)
echo $VERSION
exit
;;
-q | --quiet)
QUIET=1
shift 1
;;
-q-*)
QUIET=0
echoerr "Unknown option: $1"
usage 1
;;
-q*)
QUIET=1
result=$1
shift 1
set -- -"${result#-q}" "$@"
;;
-t | --timeout)
TIMEOUT="$2"
shift 2
;;
-t*)
TIMEOUT="${1#-t}"
shift 1
;;
--timeout=*)
TIMEOUT="${1#*=}"
shift 1
;;
--)
shift
break
;;
--help)
usage 0
;;
-*)
QUIET=0
echoerr "Unknown option: $1"
usage 1
;;
*)
QUIET=0
echoerr "Unknown argument: $1"
usage 1
;;
esac
done

if ! [ "$TIMEOUT" -ge 0 ] 2>/dev/null; then
echoerr "Error: invalid timeout '$TIMEOUT'"
usage 3
fi

case "$PROTOCOL" in
tcp)
if [ "$HOST" = "" ] || [ "$PORT" = "" ]; then
echoerr "Error: you need to provide a host and port to test."
usage 2
fi
;;
http)
if [ "$HOST" = "" ]; then
echoerr "Error: you need to provide a host to test."
usage 2
fi
;;
esac

wait_for "$@"


docker-conpose.xml文件:

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
version: '3'
services:
mysql1:
image: "mysql:5.7.37"
networks:
- demo
ports:
- "3306:3306"
volumes:
- /opt/mysql_docker/conf:/etc/mysql/conf.d
- /opt/mysql_docker/logs:/logs
- /opt/mysql_docker/data:/var/lib/mysql
environment:
- MYSQL_ROOT_PASSWORD=123456
deploy:
placement:
constraints: [node.role == manager]

nacos:
image: "nacos/nacos-server:1.4.2"
networks:
- demo
ports:
- "8848:8848"
environment:
- SPRING_DATASOURCE_PLATFORM=mysql
- MYSQL_SERVICE_HOST=mysql1
- MYSQL_SERVICE_PORT=3306
- MYSQL_SERVICE_DB_NAME=nacos
- MYSQL_SERVICE_USER=nacos
- MYSQL_SERVICE_PASSWORD=123456
- MYSQL_DATABASE_NUM=1
- JVM_XMS=256m
- JVM_XMX=256m
- JVM_XMN=256m
volumes:
- nacos:/home/nacos
# 把当前目录下的wait-for脚本放到容器内部
- ./wait-for:/home/nacos/wait-for
deploy:
placement:
constraints: [node.role == manager]
# 先设置好依赖
depends_on:
- mysql1
# 执行wait-for脚本,检查mysql是否启动成功,成功后执行Nacos启动脚本
command: sh -c '/home/nacos/wait-for mysql1:3306 -- ./bin/docker-startup.sh'
networks:
demo:
volumes:
nacos:

ENTRYPOINT

指定服务容器启动后执行的入口文件或者启动命令,例如:

1
entrypoint: /code/entrypoint.sh

或者:

1
entrypoint: ["php", "-d", "memory_limit=-1", "vendor/bin/phpunit"]

environment

添加环境变量。您可以使用数组或字典。任何布尔值(true,false,yes,no)都需要用引号引起来,以确保YML解析器不会将其转换为True或False。

仅具有键的环境变量在运行Compose的计算机上解析为它们的值,这对于秘密或特定于主机的值很有用。

1
2
3
4
environment:
RACK_ENV: development
SHOW: 'true'
SESSION_SECRET:

或:

1
2
3
4
environment:
- RACK_ENV=development
- SHOW=true
- SESSION_SECRET

expose

指定内部端口,不将其发布到宿主机上,只有链接的其它服务才能访问它们。

1
2
3
expose:
- "3000"
- "8000"

extra_hosts

类似 Docker 中的 --add-host 参数,指定额外的 host 名称映射信息。

1
2
3
extra_hosts:
- "googledns:8.8.8.8"
- "dockerhub:52.1.157.61"

会在启动后的服务容器中 /etc/hosts 文件中添加如下两条条目。

1
2
8.8.8.8 googledns
52.1.157.61 dockerhub

image

指定用于启动容器的图像。可以是镜像名称(仓库:tag)或镜像ID,例如:

1
image: redis

如果镜像在本地不存在,而且你没有指定build参数,那么Compose会尝试docker pull来拉取镜像

logging

配置日志选项。

1
2
3
4
logging:
driver: syslog
options:
syslog-address: "tcp://192.168.0.42:123"

目前支持三种日志驱动类型。

1
2
3
driver: "json-file" # 记录为json文件
driver: "syslog" # 发送到syslog服务
driver: "none" # 没有日志记录

默认采用json-file的日志方式,可以通过options 配置日志文件的限制参数。

1
2
3
options:
max-size: "200k"
max-file: "10"

network_mode

网络模式。使用与docker --network参数相同的值:

1
2
3
network_mode: "bridge"
network_mode: "host"
network_mode: "none"

networks

要加入的网络,引用Compose文件中的顶级项目networks下的定义的网络名称 。

要通过容器名称互相访问,则,各个容器必须处于同一个网络中。

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.8"

services:
web:
image: "nginx:alpine"
networks:
- new # 加入名称为new的网络

worker:
image: "my-worker-image:latest"
networks:
- legacy # 加入名称为legacy的网络

db:
image: mysql
networks:
new: # 加入名称为new的网络
aliases: # 在new网络中的别名
- database
legacy: # 加入名称为legacy的网络
aliases: # 在legacy网络中的别名
- mysql

networks:
new: # 定义一个网络,名称为new
external: true # 这样可以不创建新的网络,而是加入一个已经创建好的网络
legacy: # 定义一个网络,名称为legacy

另外,在定义网络时可以指定ip网段,而加入网络的容器则需要在网段中选择一个固定ip地址:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
version: "3.8"

services:
app:
image: nginx:alpine
networks:
app_net:
ipv4_address: 172.16.238.10 # 指定一个IPv4子网地址
ipv6_address: 2001:3984:3989::10 # 指定一个IPv6子网地址

networks:
app_net:
ipam:
driver: default
config:
- subnet: "172.16.238.0/24" # 定义IPv4的地址网段
- subnet: "2001:3984:3989::/64" # 定义IPv6的地址网段

ports

暴露的端口信息,会映射到宿主机端口,另外为了避免语法出错,所有端口配置都必须使用字符串格式:

1
2
3
4
5
6
7
8
9
10
ports:
- "3000"
- "3000-3005"
- "8000:8000"
- "9090-9091:8080-8081"
- "49100:22"
- "127.0.0.1:8001:8001"
- "127.0.0.1:5000-5010:5000-5010"
- "6060:6060/udp"
- "12400-12500:1240"

如果仅指定了容器端口,则会随机选择一个宿主机端口。

restart

指定容器退出后的重启策略。包括下面的几种选项:

1
2
3
4
restart: "no" # 在任何情况下都不会重新启动容器
restart: always # 容器总是重新启动。
restart: on-failure # 遇到故障后重启
restart: unless-stopped # 总是重新启动容器,除非容器停止

生产环境建议配置为:always或者unless-stopped

volumes

指定要挂载的数据卷或目录。数据卷可以是某个service的局部数据卷,也可以是提前定义的全局数据卷(通过顶级参数volumes来指定)。

例如:

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
version: "3.8"
services:
web:
image: nginx:alpine
volumes: # 完整的数据卷配置语法
- type: volume
source: mydata # 数据卷
target: /data # 容器内目录
volume:
nocopy: true
- type: bind
source: ./static # 宿主机目录
target: /opt/app/static # 容器内目录

db:
image: postgres:latest
volumes: # 简化的数据卷语法
# 将一个宿主机目录映射到容器内的某个目录
- "/var/run/postgres.sock:/var/run/postgres/postgres.sock"
# 将一个全局卷映射到容器内某个目录
- "dbdata:/var/lib/postgresql/data"

volumes:
mydata: # 定义全局的数据卷mydata
dbdata: # 定义全局的数据卷dbdata

其他swarm配置,上面只是讲解了Compose的部分模板语法。有关swarm下的一些配置并未说明,在swarm部分继续讲解。

3 Docker Swarm

Docker-Compose负责定义Project和Service(服务)。但是服务具体运行在哪个服务节点?需要多少个Docker容器来部署?这就要靠Docker Swarm来管理了。

Swarm 是使用 SwarmKit 构建的 Docker 引擎内置(原生)的集群管理和编排工具。

网址:https://docs.docker.com/engine/swarm/

3.1 Docker Swarm相关概念

Docker Swarm 是 Docker 的集群管理工具。它将 Docker 主机池转变为单个虚拟 Docker 主机。 Docker Swarm 提供了标准的 Docker API,所有任何已经与 Docker 守护程序通信的工具都可以使用 Swarm 轻松地扩展到多个主机。

使用 Swarm 集群之前需要了解以下几个概念:

3.1.1 Node节点

什么是节点?

运行 Docker 的主机可以主动初始化一个 Swarm 集群或者加入一个已存在的 Swarm 集群,这样这个运行 Docker 的主机就成为一个 Swarm 集群的节点 (node) 。

节点的分类:

节点分为管理 (manager) 节点工作 (worker) 节点

管理节点:用于 Swarm 集群的管理,docker swarm 命令基本只能在管理节点执行(节点退出集群命令 docker swarm leave 可以在工作节点执行)。一个 Swarm 集群可以有多个管理节点(高可用),但只有一个管理节点可以成为 leaderleader 通过 raft 协议实现。

工作节点:是任务执行节点,管理节点将服务 (service) 下发至工作节点执行。管理节点默认也作为工作节点。你也可以通过配置让服务只运行在管理节点。

Docker-Swarm的官方架构图:

image-20201109004124428

3.1.2 Service服务和Task任务

任务 (Task):是 Swarm 中的最小的调度单位,可以理解为一个单一的容器。

服务 (Services): 是指一组任务的集合,服务定义了任务的属性。服务有两种模式:

  • replicated services 按照一定规则在各个工作节点上运行指定个数的任务。
  • global services 每个工作节点上运行一个任务

两种模式通过 docker service create--mode 参数指定。

容器、任务、服务的关系图:

image-20201109004545055

常用命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
docker service ls 发布服务列表
docker service inspect nginx 查看服务详情
docker service ps nginx 查看服务详情

docker service scale nginx=5 伸缩副本数量
docker service rm helloworld 删除服务(同时容器停止)
docker service update --image redis:3.2.5-alpine redis 更新指定服务的镜像
docker service create --replicas 3 --name redis --update-delay 10s redis:3.0.7-alpine 指定镜像进行滚动更新

docker node update --availability drain worker02 下线指定节点(容器迁移到active节点)
docker node update --availability active worker2 指定节点上线

docker service update --publish-add published=8080,target=2368 ghost 指定已存在的服务暴露端口

3.2 创建Swarm集群

我们知道 Swarm 集群由 管理节点工作节点 组成。本节我们来创建一个包含一个管理节点和两个工作节点的最小 Swarm 集群。

我会启动3台虚拟机,计划如下:

虚拟机IP节点角色
192.168.80.151管理节点
192.168.80.152工作节点
192.168.80.153工作节点

注意:节点的IP请定义为自己的虚拟机IP。

虚拟机克隆

image-20210626132820171

image-20210626132859269

image-20210626132927652

image-20210626133831847

结果:

image-20210626134615100

分别先后开启f1,f2,修改ip分别为100,101,切记,此时不能同时开任何两个虚拟机

image-20210626134831109

image-20210626134946452

image-20210626135021759

image-20210626135229567

分别在三台机器执行命令:

99机器

1
2
echo '192.168.80.151 master' >> /etc/hosts
systemctl restart network

100机器

1
2
echo '192.168.80.152 slaver1' >> /etc/hosts
systemctl restart network

101机器

1
2
echo '192.168.80.153 slaver2' >> /etc/hosts
systemctl restart network

一定要重启机器

一定要重启机器

一定要重启机器

3.2.1 创建管理节点

我们在节点192.168.200.99上运行一个命令:

1
docker swarm init --advertise-addr 192.168.80.151

因为我们的虚拟机可能有多个IP地址,这里通过--advertise-addr指定一个IP地址,这里我选择的是我的NAT网卡的地址。

执行命令效果如下:

image-20210626165656364

3.2.2 创建工作节点

通过上面执行的结果可以看到这样的提示:

1
2
3
To add a worker to this swarm, run the following command:

docker swarm join --token SWMTKN-1-61z291f29zm8w3rh0wbptpzipavgq9lso77a6s46os5mijf5xr-553q8z2bs91r67ptxd1nq1mge 192.168.80.151:2377

所以,我们需要在另外两台机器192.168.80.152和192.168.80.153上执行命令,不要复制笔记,要复制自己的内容

1
docker swarm join --token SWMTKN-1-40jgt6v1n59mb7aaw41yg10coxo2524tdgw2t6g2sorbiuflhj-5rymf91h0w2l9ic3adbkik39y 192.168.80.151:2377

效果:

image-20210626165810134

image-20210626165825582

踩坑:manage节点需要防火墙开放端口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
firewall-cmd --zone=public --add-port=2377/tcp --permanent     # 开放端口
firewall-cmd --zone=public --remove-port=2377/tcp --permanent # 关闭端口
firewall-cmd --reload # 重新载入配置,让开放或关闭的端口配置生效

# 以下端口必须是开放的:
# TCP port 2377为集群管理通信
# TCP and UDP port 7946 为节点间通信
# UDP port 4789 为网络间流量

# 需要配置如下端口
firewall-cmd --list-all
firewall-cmd --zone=public --add-port=2377/tcp --permanent
firewall-cmd --zone=public --add-port=7946/tcp --permanent
firewall-cmd --zone=public --add-port=7946/udp --permanent
firewall-cmd --zone=public --add-port=4789/tcp --permanent
firewall-cmd --zone=public --add-port=4789/udp --permanent
firewall-cmd --reload

3.2.3.查看swarm集群

在管理节点:192.168.80.151上执行命令,查看swarm集群信息:

1
docker node ls

结果:

image-20210626165907655

此时,我们已经创建了一个最小的 Swarm 集群,包含一个管理节点和两个工作节点。

3.2.4.删除swarm集群中的某个节点

管理节点上对节点进行“离开”操作:

1
2
3
docker node update --availability drain 6whtoqrhkzv3ax4xy9ab20gmy

docker node update --availability drain (通过docker node ls 命令查询到的节点id

在节点中自己退出集群

1
docker swarm leave

删除节点

1
docker node rm 6whtoqrhkzv3ax4xy9ab20gmy

3.2.5.查看加入集群的Token等命令

1
2
3
4
docker swarm join-token worker:查看加入woker的命令。
docker swarm join-token manager:查看加入manager的命令
docker swarm join-token --rotate worker:重置woker的Token。
docker swarm join-token -q worker:仅打印Token。

3.3 部署单个服务

通过docker service create命令,可以创建一个service,并在swarm集群中运行。

3.3.1.创建服务

在管理节点:192.168.80.151上运行代码:

1
docker service create --replicas 3 -p 80:80 --name nginx nginx

解读:

  • --replicas 3:代表这个服务要创建3个副本,也就是启动3个容器来运行nginx

如图:

image-20201109010318388

3.3.2 查看服务

通过docker service ls可以查看服务状态:

image-20201109010353253

通过docker service ps nginx命令可以查看nginx服务的运行节点信息:

image-20210626170156585

此时,我们通过浏览器访问:http://192.168.80.151或者http://192.168.80.152或者http://192.168.80.153都可以看到一样的效果:

image-20210626170235800

其它命令:

我们可以使用 docker service scale 对一个服务运行的容器数量进行伸缩。

当业务处于高峰期时,我们需要扩展服务运行的容器数量。

1
docker service scale nginx=5

当业务平稳时,我们需要减少服务运行的容器数量。

1
docker service scale nginx=2

使用 docker service rm 来从 Swarm 集群移除某个服务。

1
docker service rm nginx

3.4 部署多个服务

使用 docker service create 一次只能部署一个服务,使用 docker-compose.yml 我们可以一次启动多个关联的服务并部署到swarm集群中。

接下来,我们就来搭建一个多服务的集群,包括下面的服务:

  • web:就是之前在docker-compose案例中的Java项目,依赖于redis进行计数。部署3个
  • redis:redis数据库,记录某个IP的访问次数,部署1个,在管理节点。
  • nginx:nginx服务,对3个web服务反向代理,部署1个,在管理节点

部署计划表:

服务名称部署数量节点IP
web3192.168.80.151, 192.168.80.152, 192.168.80.153
redis1192.168.80.151
nginx1192.168.80.151

结构如下:

image-20201102105907797

3.4.1 准备镜像

首先,我们需要在3个docker节点上都准备java项目的镜像。

1)上传

找到之前准备的docker-compose这个文件夹:

分别上传到3个docker节点的 `/opt 目录:

image-20210626170803800

2)构建镜像

然后分别在3个docker节点中运行下面的命令:

进入: /opt/docker-compose ,执行如下命令

1
docker build -t web:latest .

通过docker images查看镜像:

image-20210626171123064

3.4.2 编写nginx配置

我们需要用nginx反向代理3个web节点,因此需要编写一个nginx的配置文件。

nginx部署在管理节点:192.168.80.151,所以进入这个节点的/opt/docker-compose/swarm目录下,创建一个nginx.conf文件,内容如下:

/opt/docker-compose/swarm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
worker_processes  1;
events {
worker_connections 1024;
}
http {
default_type text/html; # 默认响应类型是html

server {
listen 80;
location /hello {
# 代理/hello路径,会代理到web服务的9090端口
proxy_pass http://web:9090;
}
location / {
root /usr/share/nginx/html;
}
}
}

注意:

  • proxy_pass http://web:9090:会把请求代理到web服务的9090端口。Docker-Swarm会自动对3个docker节点的web服务负载均衡

3.4.3 编写docker-compose

swarm下的docke-swarm会有一些变化,我们修改管理节点(192.168.80.151)下的docker-compose.yml文件,内容如下:

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
version: '3'
services:
web:
image: "web:latest"
networks:
- overlay
deploy:
mode: replicated
replicas: 3
redis:
image: "redis:latest"
networks:
- overlay
deploy:
placement:
constraints: [node.role == manager]
nginx:
image: "nginx:latest"
networks:
- overlay
ports:
- "80:80"
volumes:
- /opt/docker-compose/swarm/nginx.conf:/etc/nginx/nginx.conf
deploy:
placement:
constraints: [node.role == manager]
networks:
overlay:

解读:

  • service:服务,包括3个
    • web:java项目
      • image:指定web服务的镜像,就是刚刚自己打包的web:latest
      • networks: 网络配置,这里是用了默认的overlay格式,是swarm模式的固定格式
      • deploy:swarm下的部署配置
        • mode:replicated代表在多个节点上做备份
        • replicas: 3 ,备份数量为3,即web服务会部署到swarm集群的随机3个节点
    • redis:redis数据库
      • image: “redis:latest”,指定用到的镜像是redis最新镜像
      • deploy:swarm下的部署配置
        • placement: 指定部署位置
          • constraints: [node.role == manager] 部署到manager节点
    • nginx:nginx服务
      • image: “nginx:latest”,指定镜像名称
      • networks: 指定网络,这里是用了默认的overlay格式,是swarm模式的固定格式
      • ports: 对外暴露的端口为80
      • volumes: 数据卷,指定目录下的nginx.conf文件挂载到容器中
      • deploy: 部署,指定部署位置到manager节点

3.4.4 部署运行

多服务运行与单个服务命令不同,在管理节点(192.168.80.151)运行下面的命令:

1
docker stack deploy -c docker-compose.yml counter

说明:

  • docker stack:就是通过docker-compose部署的命令
  • -c docker-compose.yml:指定docker-compose文件位置
  • counter:给部署的集群起个名字

运行过程如图:

image-20201109193630473

通过docker stack ls可以查看到当前集群信息:

image-20201109193723407

通过docker stack ps [集群名] 可以查看集群中的服务信息:

1
2
docker stack ps counter
# 或者 docker stack services counter

image-20210626171747386

此时,访问浏览器:http://192.168.80.151/hello即可:

image-20210626171903854

此时,通过命令:

1
docker service logs -f counter_web

可以查看运行日志:

image-20201109195354830

多次访问99主节点,可以看到,Docker-Swarm会自己对3个web服务做负载均衡:

image-20201109195456557

4 持续集成&持续部署

4.1 理解什么是持续集成&持续部署

随着软件开发复杂度的不断提高,团队开发成员间如何更好地协同工作以确保软件
开发的质量已经慢慢成为开发过程中不可回避的问题。互联网软件的开发和发布,已经形成了一套标准流程。

如: 在互联网企业中,每时每刻都有需求的变更,bug的修复, 为了将改动及时更新到生产服务器上,下面的图片我们需要每天执行N多次,开发人员完成代码自测后提交到git,然后需要将git中最新的代码生成镜像并部署到测试服务器,如果测试通过了还需要将最新代码部署到生产服务器。如果采用手动方式操作,那将会浪费大量的时间浪费在运维部署方面。

1605489947242

现在的互联网企业,基本都会采用以下方案解决:

持续集成(Continuous integration,简称 CI)。

持续部署(continuous deployment, 简称 CD)

4.1.1 持续集成

持续集成 (Continuous integration,简称 CI) 指的是,频繁地(一天多次)将代码集成到主干。

它的好处主要有两个。

  1. 快速发现错误。每完成一点更新,就集成到主干,可以快速发现错误,定位错误也比较容易。

  2. 防止分支大幅偏离主干。如果不是经常集成,主干又在不断更新,会导致以后集成的难度变大,甚至难以集成。

持续集成的目的,就是让产品可以快速迭代,同时还能保持高质量。它的核心措施是,代码集成到主干之前,必须通过自动化测试。只要有一个测试用例失败,就不能集成。

Martin Fowler 说过,”持续集成并不能消除 Bug,而是让它们非常容易发现和改正。”

与持续集成相关的,还有两个概念,分别是持续交付和持续部署。

4.1.2 持续交付

持续交付(Continuous delivery)指的是,频繁地将软件的新版本,交付给质量团队或者用户,以供评审。如果评审通过,代码就进入生产阶段。

持续交付可以看作持续集成的下一步。它强调的是,不管怎么更新,软件是随时随地可以交付的。

4.1.3 持续部署

持续部署(continuous deployment)是持续交付的下一步,指的是代码通过评审以后,自动部署到生产环境。

持续部署的目标是,代码在任何时刻都是可部署的,可以进入生产阶段。

持续部署的前提是能自动化完成测试、构建、部署等步骤。

4.1.4 演示流程说明

为了保证团队开发人员提交代码的质量,减轻软件发布时的压力;
持续集成中的任何一个环节都是自动完成的,无需太多的人工干预,有利于减少重复
过程以节省时间、费用和工作量;接下来我们会演示一套基本的自动化持续集成和持续部署方案,来帮助大家理解互联网企业的软件部署方案。

流程如下:

1605837774557

实现流程:

  1. 开发人员将代码提交到 git 指定分支 如: dev
  2. git仓库触发push事件,发送webhooks通知到持续集成软件
  3. 持续集成软件触发构建任务,对dev分支的代码进行构建、编译、单元测试
  4. 如果构建失败,发送邮件提醒代码提交人员或管理员
  5. 如果构建成功,最新代码将会被构建Docker镜像并上传到注册中心
  6. 构建成功触发webhooks通知容器编排软件,进行服务升级
  7. 容器编排软件,触发对应的服务升级任务, 将创建对应服务的新容器替换之前的容器
  8. 完成最新代码的自动构建与自动部署,全程无工作人员干预

要实现上面流程,我们需要了解两款新的软件 jenkinsrancher

4.2 CI&CD jenkins

4.2.1 jenkins介绍

Jenkins,原名Hudson,2011年改为现在的名字,它 是一个开源的实现持续集成的
软件工具。官方网站:http://jenkins-ci.org/

Jenkins 能实施监控集成中存在的错误,提供详细的日志文件和提醒功能,还能用图
表的形式形象地展示项目构建的趋势和稳定性。

1605852309418

特点:

  • 易配置:提供友好的GUI配置界面;
  • 变更支持:Jenkins能从代码仓库(Subversion/CVS)中获取并产生代码更新列表并
    输出到编译输出信息中;
    支持永久链接:用户是通过web来访问Jenkins的,而这些web页面的链接地址都是
    永久链接地址,因此,你可以在各种文档中直接使用该链接;
  • 集成E-Mail/RSS/IM:当完成一次集成时,可通过这些工具实时告诉你集成结果(据
    我所知,构建一次集成需要花费一定时间,有了这个功能,你就可以在等待结果过程
    中,干别的事情);
  • JUnit/TestNG测试报告:也就是用以图表等形式提供详细的测试报表功能;
  • 支持分布式构建:Jenkins可以把集成构建等工作分发到多台计算机中完成;
    文件指纹信息:Jenkins会保存哪次集成构建产生了哪些jars文件,哪一次集成构建使
    用了哪个版本的jars文件等构建记录;
  • 支持第三方插件:使得 Jenkins 变得越来越强大

4.2.2 安装配置jenkins

jenkins的官方文档中提供了多种安装方式,本文选择docker的安装方式来学习jenkins

4.2.2.1 安装jenkins

强烈建议虚拟机内存升级到4G,CPU给4核心

强烈建议虚拟机内存升级到4G,CPU给4核心

强烈建议虚拟机内存升级到4G,CPU给4核心

下载jenkins

1
docker pull jenkins/jenkins:lts-centos7 

创建jenkins容器

1
2
3
docker run -d --name myjenkins -p 8888:8080 --restart=always jenkins/jenkins:lts-centos7

docker run -d --name jenkins -p 8888:8080 --restart=always jenkins/jenkins:lts-centos7

查看jenkins启动日志

1
docker logs -f myjenkins

image-20210519212902939

启动成功后 访问:

http://192.168.80.151:8888

4.2.2.2 解锁jenkins

第一次运行时,需要先解锁jenkins

具体步骤:

  1. 去容器中 指定文件查看管理员密码
  2. 将密码拷贝到文本框
  3. 点击继续即可

1605853229487

密码在日志文件中已经打印,也可以根据提示在容器中获取

具体解锁的管理员密码,在jenkins的安装目录中,因为我们是采用的容器安装,所以需要进入到容器中查看,命令如下:

1
2
3
4
5
6
7
# 进入到jenkins容器
docker exec -it myjenkins bash

# 查看密码
cat /var/jenkins_home/secrets/initialAdminPassword

# 将密码复制到上图管理员密码文本框,然后点击继续 完成解锁

image-20210626172752249

4.2.2.3 安装推荐插件

jenkins的各项功能,依赖各种插件,可以手工选择安装也可以按照推荐安装

image-20210626174213291

我们课程主要使用git 和 chinese中文插件,所以搜索安装

image-20210626174254482

image-20210626174400786

创建管理员用户

插件安装完毕后,会进入到设置管理员用户页面,按自己需求设置就好,后续登录可以使用

设置完毕后点击保存并完成则进入到jenkins欢迎页

1605855220823

接下来,jenkins会让我们确认jenkins服务端的地址,直接下一步就好,

image-20210626174604987

然后点击开始使用jenkins进入到jenkins页面

1605855290646

进入到jenkins页面, 如果这个时候你的页面都是英文的话,重启下就好

(因为上面安装默认插件中已经安装了 中文插件)

1605855341537

4.2.2.4 jenkins插件下载镜像加速

image-20210626175433767

更新地址:https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/update-center.json

image-20210626175748534

4.2.2.4 配置maven环境

对于git中项目的构建我们要使用到maven命令,那么在jenkins中需要下载对应的maven插件,以及jenkins所在的容器也要有maven环境

(1) 下载maven插件:

点击系统管理 –> 点击插件管理 –> 进入到插件管理页面

1605855531632

1605855558517

点击可选插件 –> 输入maven –> 勾选Maven Integration –> 下载待重启安装

1605856223602

等待下载完成后,重启jenkins容器即可

1605856272958

(2) 安装maven环境

将资源中的maven安装包,拷贝到容器中解压即可,再配置好华为云镜像

1605511349048

将maven压缩包拷贝容器解压

1
2
3
4
5
6
7
8
9
# 目录根据自己实际情况来
docker cp ./apache-maven-3.6.3-bin.tar.gz myjenkins:/var/jenkins_home/
# 进入到容器
docker exec -it -u root myjenkins bash
# 将maven解压
cd /var/jenkins_home
tar -zxvf apache-maven-3.6.3-bin.tar.gz
# 设置mvn权限
chmod 777 /var/jenkins_home/apache-maven-3.8.4/bin/mvn

配置maven镜像

上传资料中的settings.xml文件到宿主机中并复制配置文件到容器中

image-20210626181013661

1
docker cp settings.xml myjenkins:/var/jenkins_home/apache-maven-3.6.3/conf/settings.xml

(3) jenkins中配置maven环境

系统管理中点击全局工具配置

1605856856869

  1. 新增maven
  2. name随意,MAVEN_HOME: /var/jenkins_home/apache-maven-3.6.3
  3. 取消勾选自动安装
  4. 保存即可

1605857052762

完成后,我们就可以通过jenkins创建构建任务啦

Docker私有Registry

(1)拉取私有仓库镜像(此步可省略)

1
docker pull registry

(2)启动私有仓库容器

1
docker run -id -p 5000:5000 --name myregistry --restart=always  registry

​ 修改开机自启动

(3)查看检验是否安装启动成功。

打开浏览器 输入地址

1
http://192.168.80.151:5000/v2/_catalog

看到 {“repositories”:[]} ,表示私有仓库搭建成功并且内容为空

设置当前docker信任私有注册中心(所有容器都要添加,否则Docker会拉取不到本地镜像仓库的镜像

(1)修改daemon.json,让 docker信任私有仓库地址

修改文件:

1
vim /etc/docker/daemon.json

添加如下内容,保存退出。

1
2
3
4
{
"insecure-registries":["192.168.80.151:5000"]
}

注意:该文件中如有多个内容,比如有之前配置的私服镜像地址,用英文逗号隔开,参考如下:

1
2
3
4
{
"registry-mirrors": ["https://r2fftmt2.mirror.aliyuncs.com"],
"insecure-registries":["192.168.80.151:5000"]
}

(2)重启docker 服务和私服

1
systemctl restart docker

4.2.3 jenkins快速入门

4.2.3.1 准备要部署的工程

准备工作,导入资源中的docker-demo工程, 在pom中添加docker-maven插件配置,

注意将IP部分变成自己虚拟机的IP

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
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.itcast</groupId>
<artifactId>docker_demo</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
<build>
<finalName>app</finalName>
<plugins>
<!-- 打jar包时如果不配置该插件,打出来的jar包没有清单文件 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!-- 插件网址:https://github.com/spotify/docker-maven-plugin -->
<plugin>
<groupId>com.spotify</groupId>
<artifactId>docker-maven-plugin</artifactId>
<version>1.2.2</version>
<configuration>
<imageName>192.168.80.151:5000/${project.artifactId}:${project.version}</imageName>
<baseImage>java:8-alpine </baseImage>
<entryPoint>["java","-jar","/${project.build.finalName}.jar"]</entryPoint>
<resources>
<resource>
<targetPath>/</targetPath>
<directory>${project.build.directory}</directory>
<include>${project.build.finalName}.jar</include>
</resource>
</resources>
<dockerHost>http://192.168.80.151:2375</dockerHost>
</configuration>
</plugin>
</plugins>
</build>
</project>

将项目上传到码云(gitee.com)中

Git中的地址: git@gitee.com:taft31/docker-demo.git

image-20210626182453657

4.2.3.2 创建maven构建任务

(1) 新建jenkins任务

在jenkins的首页的第一个页签就是用于构建任务,点击新建任务:

1605857248023

定义任务名名称,勾选构建模板 保存任务

image-20210626182544621

(2) 设置任务的构建信息:

描述信息设置

1605857417849

**源码设置 ***

jenkins可以根据配置的源码地址获取源码,来用于构建,配置如下:

  1. 选择git仓库

  2. 设置git仓库地址

  3. 如果是私有仓库需要添加凭证

  4. 选择仓库分支

1605857556647

构建触发器:

什么情况可以触发此任务,或者定时触发此任务,暂不设置

构建设置

  1. Pre Steps 构建的前置任务,可以在构建执行前触发一些通知或脚本的执行
  2. build 要执行的构建任务
  3. PostSteps 构建的后置任务,可以在完成构建后触发的一些通知或脚本的执行

构建任务配置如下:

Root POM: 本次构建要使用的git仓库中的pom文件

Goals and options: 要执行的mvn命令 不用写前面的mvn

1
2
3
4
5
# maven命令 
clean package -DskipTests docker:build -DpushImage

# 依次:
# 清除 打包 跳过单元测试 远程构建镜像 上传镜像到注册中心

1605857730695

构建结果通知

可以将构建结果,通知给配置的管理员或触发此任务的代码上传人员,本文不配置

1605857813161

保存任务

点击保存即可

构建前要确认docker的2375端口是开放

1
2
3
4
5
6
7
# 编辑此文件
vi /lib/systemd/system/docker.service
# 修改此行配置,原来设置删除
ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock -H tcp://0.0.0.0:7654
# 重新加配置文件并重启docker
systemctl daemon-reload
systemctl restart docker

4.2.3.3 执行maven构建任务

(1)执行构建任务

当我们保存完毕任务之后,会进入到任务的详情页面, 点击立即构建即可执行该构建任务

1605858494552

或者返回首页面板,也能看到任务列表,列表后面的图标也可以用于构建

1605858525007

(2) 查看任务执行日志

点击构建后,在页面左下会出现任务的执行状态,点击进度条进入到任务构建详情中

1605858565132

可以通过控制台输出页面,查看控制台信息,和我们在idea控制中看到的信息类似

第一次执行会下载很多maven依赖

1605858604063

实际上,jenkins是从我们配置的git中拉取了源码信息,在使用maven的命令进行构建

(3) 查看任务构建结果

控制台出现 BUILD SUCCESS 代表构建成功啦

1605858832629

对应的虚拟机中已经有了这个镜像

image-20210626200126181

对应的注册中心中也上传了此镜像

image-20210626200220451

OK 那么接下来基于这个镜像构建出容器,我们就完成了部署。

4.3 容器编排平台 Rancher

4.3.1 Rancher介绍

前面我们了解了容器编排的概念,如: docker 的Swarm以及 google的k8s, 但是这些软件的入门门槛很高,需要我们记住很多命令,那么下面我们介绍一款软件 Rancher,它可以基于上面的容器编排软件,提供可视化的操作页面 实现容器的编排和管理。

Rancher是一个开源的企业级全栈化容器部署及管理平台。Rancher为容器提供一揽
子基础架构服务:CNI兼容的网络服务、存储服务、主机管理、负载均衡、防护墙……
Rancher让上述服务跨越公有云、私有云、虚拟机、物理机环境运行,真正实现一键式应
用部署和管理。
https://www.cnrancher.com/

1605858952789

4.3.1 Rancher快速入门

4.3.1.1 安装Rancher

下载rancher

1
docker pull rancher/server

创建rancher容器

1
2
3
docker run -d --name=myrancher -p 9099:8080 rancher/server

docker run -d --restart=always --name=rancher -p 9090:8080 rancher/server

查看rancher启动日志

1
docker logs -f myrancher

开启2049端口

1
2
3
4
5
6
7
8
9
# 开启2049端口
firewall-cmd --zone=public --add-port=2049/tcp --permanent

# 重启防火墙
firewall-cmd --reload

# 查看开放端口
firewall-cmd --zone=public --list-ports

访问Rancher: http://192.168.80.151:9099/

1605859055814

页面右下角 点击下拉框 选择简体中文

1605859091466

4.3.1.2 配置环境

在互联网项目中,可能会有多套部署环境 如: 测试环境 、 生产环境,不同的环境下会有不同的服务器 Rancher支持多环境多服务器管理

默认 我们处于default的默认环境中,点击环境管理可以创建环境

1605859140946

点击添加环境可以定义一个环境

1605859175441

构建环境时,需要设置环境的名称、环境描述、及环境模板

可以看到 环境模板支持多套,所谓的环境模板就是底层使用哪种编排工具

rancher支持 cattle、swarm、k8s、mesos等,默认使用cattle

1605859229797

入门案例我们使用内置的Cattle模板即可,添加后列表出现刚创建的环境

1605859274224

切换到prod环境中

1605859327642

4.3.1.3 配置主机

在不同的环境中可以会有不同的服务器,要想让我们的rancher能够管理这些服务器,需要在基础架构中添加主机

(1) 添加主机

点击基础架构下拉框中的主机 –> 在点击添加主机

1605859380135

(2) 复制脚本

确认站点地址是否正确,然后点击保存(端口9099)

image-20210626203241022

复制脚本:

  1. 要管理的主机IP 如: 要管理 192.168.80.151的虚拟机
  2. 复制脚本,将脚本复制到192.168.80.151的机器上执行
  3. 执行完毕后关闭此页面,等待主机连接

image-20210626220858903

(3) 到主机中执行脚本

如: 到我的192.168.80.151的虚拟机中 执行如下命令:

1605859702912

运行完毕后,在rancher的页面上,关闭窗口 可以在主机列表中看到对应服务器信息

(需要等待主机中下载镜像及启动相关容器)

image-20210626221247380

显示active 代表服务器当前状态可用, 如果报红 或显示reconnecting则为重连状态,等待一会即可

4.3.1.4 管理容器

连接成功后,我们可以点击基础架构下的容器 进行容器的管理

image-20210626204226263

说明:

(1)页面提供了对应主机上的容器管理功能,额外创建的容器都是系统容器,用于rancher的管理,可以通过 取消勾选显示系统容器进行过滤

(2)点击添加容器,可以通过简单配置构建一个容器

​ 如: 构建一个redis容器

  1. 点击添加容器
  2. 配置容器名称、描述、镜像、端口映射即可

1605860222158

1605860305990

(3)容器列表结尾提供了容器的 重启、删除、查看日志等功能

1605860374959

4.3.3 Rancher中的应用与服务

4.3.3.1 应用与服务的概念

上面的容器管理,仅仅是提供了容器的管理页面,但对于企业级的项目部署 会涉及到集群扩容缩容、服务升级、负载均衡等等高可用的管理。需要在Rancher中通过定义应用与服务的设置来管理。

应用(Project): 代表一个项目 如: 电商项目

服务(Service):代表一个服务 如: 电商项目下的订单微服务

1605841694459

4.3.3.2 创建应用与服务

和我们学习的swarm类似,我们可以创建一个应用project 一个应用下可以包含多个服务

service , 一个服务下可以运行多个相同的容器container

创建应用

1605860470039

点击到环境首页,创建应用 : 应用名称、描述 点击创建

1605860527144

添加服务

在刚创建好的myPro应用中添加服务

1605860574808

设置服务信息:

  1. 容器名称(rancher中显示的名称)
  2. 描述
  3. 构建创建前拉取最新镜像
  4. 镜像的名称

image-20210626224317219

点击创建,可以看到容器已经运行

image-20210626223532122

image-20210626223551798

在docker中也有对应的服务

1605861203059

4.3.3.3 演示服务扩容

点击左侧的 数量加减 会自动对服务进行扩容 缩容

image-20210626224416672

4.3.3.4 演示服务负载均衡

不过我们当前服务集群 并没有配置端口映射,因此外部无法访问,需要配置负载均衡

回到服务列表,添加负载均衡

1605861417894

配置负载均衡

​ 1.负载均衡名称 : lbdockerDemo

​ 2.负载均衡描述 : dockerDemo的负载均衡

​ 3.访问端口: 9001

​ 4.目标服务: myPro/dockerDemo

​ 5.映射服务容器端口: 9090

1605861694062

image-20210626224727637

访问测试: http://192.168.80.151:9001/hello 多次点击

image-20210626224903555

依次查看3个容器的日志

1605862093699

1605862150892

1605862172571

1605862198473

已经实现了负载均衡效果

4.3.3.5 演示服务升级

访问当前服务 http://192.168.206.66:9001/hello

image-20210626225151134

变更当前代码

1605862350110

push提交到git

1605862398258

执行jenkins构建任务,将最新的代码打包成新镜像,并上传到注册中心

1605862470028

构建成功后,在rancher中进行服务升级 在详情页面或列表页面都有向上的箭头代表服务升级

1605862595138

填写升级信息, 启动行为勾选:

这样会先根据最新镜像创建容器,创建完毕后,在将之前的容器删除,来完成服务的更新

点击升级

image-20210626230551086

最后,点击完成升级 旧的容器将被删除掉

image-20210626230746572

刷新页面,可以看到服务已经升级完毕

image-20210626231005676

也就意味着完成服务的一键部署

搭建NFS服务挂载数据卷

1、安装NFS服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#安装nfs服务
yum install -y nfs-utils rpcbind

#新建文件
vi /etc/exports

#输入以下内容
/nfsdata *(rw,no_root_squash,no_all_squash,sync)

NFS共享的常用参数,具体如下:
ro 只读访问
rw 读写访问
sync 所有数据在请求时写入共享
async NFS在写入数据前可以相应请求
secure NFS通过1024以下的安全TCP/IP端口发送
insecure NFS通过1024以上的端口发送
subtree_check 如果共享/usr/bin之类的子目录时,强制NFS检查父目录的权限(默认)
no_subtree_check 和上面相对,不检查父目录权限
all_squash 共享文件的UID和GID映射匿名用户anonymous,适合公用目录。
no_all_squash 保留共享文件的UID和GID(默认)
root_squash root用户的所有请求映射成如anonymous用户一样的权限(默认)
no_root_squash root用户具有根目录的完全管理访问权限
anonuid=xxx 指定NFS服务器/etc/passwd文件中匿名用户的UID

2、创建目录和赋予权限:

1
2
3
4
5
#创建文件夹
mkdir /nfs_data

#赋予权限
chmod 777 /nfs_data

3、启动rpcbind和nfs服务:

1
2
systemctl start rpcbind && systemctl enable rpcbind
systemctl start nfs && systemctl enable nfs

注意:需要设置固定端口,并且放行

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
vim /etc/sysconfig/nfs

# 此处需要放行的端口为:
# tcp/udp:111、2049、30003、30004
# tcp:30001
# udp:30002

#
# Note: For new values to take effect the nfs-config service
# has to be restarted with the following command:
# systemctl restart nfs-config
#
# Optional arguments passed to in-kernel lockd
#LOCKDARG=
# TCP port rpc.lockd should listen on.
LOCKD_TCPPORT=30001
# UDP port rpc.lockd should listen on.
LOCKD_UDPPORT=30002
#
# Optional arguments passed to rpc.nfsd. See rpc.nfsd(8)
RPCNFSDARGS=""
# Number of nfs server processes to be started.
# The default is 8.
#RPCNFSDCOUNT=16
#
# Set V4 grace period in seconds
#NFSD_V4_GRACE=90
#
# Set V4 lease period in seconds
#NFSD_V4_LEASE=90
#
# Optional arguments passed to rpc.mountd. See rpc.mountd(8)
RPCMOUNTDOPTS=""
# Port rpc.mountd should listen on.
MOUNTD_PORT=30003
#
# Optional arguments passed to rpc.statd. See rpc.statd(8)
STATDARG=""
# Port rpc.statd should listen on.
STATD_PORT=30004
# Outgoing port statd should used. The default is port
# is random
#STATD_OUTGOING_PORT=2020
# Specify callout program
#STATD_HA_CALLOUT="/usr/local/bin/foo"
#
#
# Optional arguments passed to sm-notify. See sm-notify(8)
SMNOTIFYARGS=""
#
# Optional arguments passed to rpc.idmapd. See rpc.idmapd(8)
RPCIDMAPDARGS=""
#
# Optional arguments passed to rpc.gssd. See rpc.gssd(8)
# Note: The rpc-gssd service will not start unless the
# file /etc/krb5.keytab exists. If an alternate
# keytab is needed, that separate keytab file
# location may be defined in the rpc-gssd.service's
# systemd unit file under the ConditionPathExists
# parameter
RPCGSSDARGS=""
#
# Enable usage of gssproxy. See gssproxy-mech(8).
GSS_USE_PROXY="yes"
#
# Optional arguments passed to blkmapd. See blkmapd(8)
BLKMAPDARGS=""

放行端口:

1
2
3
4
5
6
7
8
9
10
# tcp/udp:111、2049、30003、30004
# tcp:30001
# udp:30002

firewall-cmd --zone=public --add-port=111/tcp --add-port=111/udp --permanent
firewall-cmd --zone=public --add-port=2049/tcp --add-port=2049/udp --permanent
firewall-cmd --zone=public --add-port=30003/tcp --add-port=30003/udp --permanent
firewall-cmd --zone=public --add-port=30004/tcp --add-port=30004/udp --permanent
firewall-cmd --zone=public --add-port=30001/tcp --add-port=30002/udp --permanent
firewall-cmd --reload

5、每个node查询NFS服务器

1
showmount -e 192.168.3.99

6、在Rancher中的应用商店添加NFS应用

1

7、设置NFS服务器的IP地址以及挂载目录

因为以及设置过了,所以这里只展示参数

注意:ON_REMOVE 参数设置为retain,如果设置成purge那么在删除卷的时候会清除底层数据,这样会造成数据丢失

2

8、进入“存储”页面,添加卷

3

4

5

使用时直接添加卷就行了卷名称:容器内路径

6

4.4 自动集成及自动部署

上面的演示中,当我们把idea上的代码提交到git中之后, 手动的点击了jenkins中的构建任务,完成镜像的构建和上传注册中心。 然后,在到rancher软件中,根据最新的镜像完成一键升级。 那么自动化的流程就是让这两部也变成自动的,我们只需要将代码上传到指定分支将会自动化的完成构建与升级部署。

4.4.1 自动通知jenkins触发任务

主流的git软件都提供了webhooks功能(web钩子), 通俗点说就是git在发生某些事件的时候可以通过POST请求调用我们指定的URL路径,那在这个案例中,我们可以在push事件上指定jenkins的任务通知路径。

4.4.1.1 jenkins配置Gitee插件

jenkins下载webhooks插件

gitee插件介绍: https://gitee.com/help/articles/4193#article-header0

jenkins也支持通过url路径来启动任务,具体设置方法:

jenkins的默认下载中仅下载了github的通知触发,我们需要先下载一个插件

(1) 下载gitee插件

系统管理–>插件管理–>可选插件–>搜索 Gitee 下载–>重启jenkins

1605863059692

(2) gitee生成访问令牌

首先,去下面网址生成gitee访问令牌

https://gitee.com/profile/personal_access_tokens

1605863332642

添加令牌描述,提交,弹出框输入密码

1605863372394

复制令牌

1605863446752

(3) jenkins中配置Gitee

系统管理 –> 系统配置 –> Gitee配置

  1. 链接名: gitee
  2. 域名: https://gitee.com
  3. 令牌: Gitee Api 令牌 (需要点击添加按下图配置)
  4. 配置好后测试连接
  5. 测试成功后保存配置

1605864020886

令牌配置:

  1. 类型选择Gitee API令牌
  2. 私人令牌: 将码云中生成的令牌复制过来
  3. 点击添加

1605863756159

4.4.1.2 修改jenkins构建任务

修改配置接收webhooks通知

任务详情中点击配置来修改任务

1605864126335

点击构建触发器页签,勾选Gitee webhook

image-20210626235155347

生成Gitee Webhook密码

1605864316485

保存好触发路径和webhook密码,到gitee中配置webhook通知

如:

触发路径: http://192.168.80.151:8888/gitee-project/dockerDemo

触发密码: a591baa17f90e094500e0a11b831af9c

4.4.1.3 Gitee添加webhooks通知

gitee仓库配置webhooks通知

点击仓库页面的管理

image-20210626235434055

添加webhook

  1. 点击webhooks菜单,然后点击添加
  2. 配置jenkins通知地址
  3. 填写密码
  4. 点击添加

image-20210626235610214

但在点击添加时,提示失败 gitee中需要配置一个公有IP或域名,这里我们可以通过内网穿透来解决

1605877091111

4.4.1.4 配置内网穿透

内网穿透的小工具很多,这里面我们使用 natapp提供的内网穿透功能

在资料中找到natapp,上传整个目录到linux,

并进入natapp,执行命令

授权

1
chmod a+x natapp 

运行

1
./natapp

image-20210627000411799

在gitee中将上面的外网地址替换之前的ip和端口部分,再次添加,

如:http://hgyyd3.natappfree.cc/gitee-project/dockerDemo

image-20210626235755243

添加成功

image-20210626235815645

4.4.1.5 测试自动构建

添加完毕后测试一下:

点击webhooks,发送测试请求

image-20210627000626447

点击查看更多结果,200代表请求成功

image-20210627000651626

不过这个时候jenkins中的任务是没被触发的,我们尝试从idea中上传代码,看看任务是否自动构建

image-20210627002215324

上传代码

1605865274308

代码上传到git后,自动触发了jenkins中的构建任务

image-20210627000820697

4.4.2 Jenkiens自动通知Rancher触发升级

4.4.2.1 Rancher配置接收器

在rancher中,配置接收器来接收webhooks通知

在api下拉菜单下,点击webhooks添加接收器

1605865468861

  1. 名称:自定义即可

  2. 类型:支持扩容,缩容,和服务升级 我们演示服务升级

  3. 参数格式: Docker Hub即可

  4. 镜像标签: 对应镜像的标签

  5. 服务选择器: 我们的服务也可以设置标签, 如: 当前标签service=demo

​ 当这个接收器被触发时,所有服务包含此标签的 service=demo 则会触发服务升级

  1. 后面参数的概念:

先启动一个新容器, 启动成功后停止老容器,最后删除老容器完成升级

image-20210627001042333

保存,复制触发url路径

1605865748613

触发路径:

http://192.168.80.151:9099/v1-webhooks/endpoint?key=ajTY9M3GYC4geiy255UtQ0XuxluQtidIvp8mav96&projectId=1a7

4.4.2.2 服务添加标签

最后,给我们的服务设置标签,删除之前的服务,重新添加服务 ,以及负载均衡器

注意在下面标签下的内容,一致要和接收器设置的标签和值一致此服务才会触发升级

标签: service docker

image-20210627001326171

4.4.2.3 测试服务升级

通过POSTMAN进行测试

在触发请求时还需要携带一些必要的参数:

  1. 镜像的标签 tag: 这个标签的值要和上面接收器中的标签值一致才可以触发
  2. 仓库的名称 repo_name: 镜像的仓库名称
1
2
3
4
5
6
7
8
{
"push_data": {
"tag": "1.0.0-SNAPSHOT"
},
"repository": {
"repo_name": "192.168.80.151:5000/docker-demo"
}
}

image-20210627001556812

点击完毕后观察rancher中服务列表变化,会发现服务将自动完成升级

image-20210627002142463

4.4.2.4 配置jenkins的后置处理

最后,让jenkins来触发rancher,修改jenkins中的配置

  1. 在构建完毕的后置处理步骤中添加 执行Shell脚本
  2. 选择Run only if build succeeds 仅在构建成功时运行下面脚本
  3. 执行脚本

注意: 调用的路径是我们接收器所生成的路径

tag: 是镜像的tag标签

repo_name: 是对应镜像的仓库名称

要根据自己的实际情况修改哦~~~~~

1
2
3
curl "http://192.168.80.151:9099/v1-webhooks/endpoint?key=KoA2vrxiAycydPNDwlb3DLkZ5Kghdshs58lN2lia&projectId=1a13" \
-H "Content-Type:application/json" \
-d "{\"push_data\": {\"tag\": \"1.0.0-SNAPSHOT\"},\"repository\": {\"repo_name\": \"192.168.80.151:5000/docker-demo\"}}"

image-20210627002516069

image-20210627002549319

image-20210627002730322

image-20210627002831259

4.4.3 自动集成&自动部署演示

操作步骤:

  1. 变更代码并上传到git
  2. 注意jenkins任务是否被触发
  3. 注意rancher自动升级是否被触发
  4. 访问项目查看变更是否生效

sqmxvOw23L

OK,如果成功了,说明你只需要提交代码就可以了, 和部署相关 编译,测试,构建,上传镜像,服务升级,扩容缩容全部交给工具吧~~~