您的位置:首页 > 运维架构 > Docker

使用jenkins、docker、consul、nginx搭建支持自动化构建部署以及弹性伸缩的集群系统详细教程

2017-07-25 22:12 1481 查看

一、前言

这是我第一篇博文,中间可能存在许多纰漏,所以请大家不吝指教!另外在文中API及原理性的东西仅做简单提及,因为网上有很多,讲的很详细,我尽量将篇幅放在记录过程上,如有疑惑,欢迎评论。

1.1 搭建思路

使用jenkins+maven做自动化部署及构建工具,采用参数化构建或自动构建方式,减少集成工作量。

在docker容器中装载应用后直接利用docker 的swarm mode 集群模式来搭建一个应用集群,方便简单快捷。

在外部使用nginx进行负载均衡,性能高,可靠。

使用consul进行服务发现,在这里为了简化搭建复杂度,在本文中暂时先用consul的主机发现功能,如果还需要更细粒度的服务发现,可通过应用调HTTP API的方式或者使用registrator进行注册服务。

使用consul-template对nginx的配置文件进行实时更改及重载。

使用keepalived维持主节点的高可用。

1.2 系统结构图



1.3 应用场景

任何搭建及部署方式都是有其应用场景,这一套系统主要解决的问题是减少开发-集成-部署工作的繁琐程度、后期运维手动切换故障节点的麻烦和滞后性、以及当业务量上涨时扩充节点的繁琐步骤,使开发人员专心于业务逻辑、运维人员专心于解决服务器故障。

1.3.1 开发及部署

部署完这一套系统之后,开发人员将代码上传到git上,可以自动或根据预设的策略时机触发构建,此时构建服务器会自动检出代码,并进行打包、集成、构建镜像、推送镜像到私有仓库,再自动通知应用容器集群滚动升级容器,整个过程无需人为干预。

1.3.2 后期运维

对于应用容器来说,当某个节点服务器不可用时会通知其它节点拉取镜像;当节点可用时则自动加入集群提供服务,而主服务器使用keepalived做容灾,在某台主服务器挂掉时实现自动切换,大大降低运维成本。而docker swarm支持弹性伸缩,当系统的业务量和访问量上去,只需要一句命令便可水平扩展节点,甚至可编写脚本实时监控节点服务器负载情况,当节点负载过高时则自动扩展节点;当节点负载低时则关闭部分节点容器,通过弹性伸缩可以更加灵活地分配计算资源。

二、实验环境

本次实验使用了两台主机(win10系统,虚拟机软件使用的是系统自带的hyper-V),第一台主机建立三台虚拟机作为节点服务器,第二台主机建立两台虚拟机做构建服务器/负载均衡服务器及双机热备。建议一开始在两台主机上各创建一个虚拟机,然后配置好环境,接着直接复制虚拟机的文件夹,就不用一台一台配置环境。

主服务器配置如下

主机名地址操作系统内存容量
master192.168.0.200centos73GB
backup192.168.0.199centos73GB
节点服务器配置如下

主机名地址操作系统内存容量
node1192.168.0.201centos71GB
node2192.168.0.202centos71GB
node3192.168.0.203centos71GB
测试应用

使用spring boot写了一个简单的restAPI,主要用来显示容器的hostname

三、开始搭建

需先准备好环境再进行部署。

3.1 准备环境

说明:主服务器需先配好java环境,至于怎么配自行上网搜索配置。另外建议在配置前更换yum源为阿里源,节约后面的时间,若系统与博主一样为centos7,也可以直接使用复制下面指令进行更换。

wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo && \
yum makecache


3.1.1 docker环境

在主服务器及节点服务器中安装docker环境

yum install -y docker


安装完毕后需要修改docker启动服务文件,增加监听远程指令选项。

vi /lib/systemd/system/docker.service


将配置文件中的ExecStart=/usr/bin/dockerd-current后面加上-H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock \

如下配置:

[Unit]
Description=Docker Application Container Engine
Documentation=http://docs.docker.com
After=network.target
Wants=docker-storage-setup.service
Requires=docker-cleanup.timer

[Service]
Type=notify
NotifyAccess=all
KillMode=process
EnvironmentFile=-/etc/sysconfig/docker
EnvironmentFile=-/etc/sysconfig/docker-storage
EnvironmentFile=-/etc/sysconfig/docker-network
Environment=GOTRACEBACK=crash
Environment=DOCKER_HTTP_HOST_COMPAT=1
Environment=PATH=/usr/libexec/docker:/usr/bin:/usr/sbin
ExecStart=/usr/bin/dockerd-current -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock \
--add-runtime docker-runc=/usr/libexec/docker/docker-runc-current \
--default-runtime=docker-runc \
--exec-opt native.cgroupdriver=systemd \
--userland-proxy-path=/usr/libexec/docker/docker-proxy-current \
$OPTIONS \
$DOCKER_STORAGE_OPTIONS \
$DOCKER_NETWORK_OPTIONS \
$ADD_REGISTRY \
$BLOCK_REGISTRY \
$INSECURE_REGISTRY
ExecReload=/bin/kill -s HUP $MAINPID
LimitNOFILE=1048576
LimitNPROC=1048576
LimitCORE=infinity
TimeoutStartSec=0
Restart=on-abnormal
MountFlags=slave

[Install]
WantedBy=multi-user.target


保存完毕后执行下面指令使其生效

firewall-cmd --zone=public --add-port=2375/tcp --permanent && \
systemctl reload firewalld && \
systemctl daemon-reload && \
systemctl start docker || \
systemctl restart docker


3.1.2 jenkins环境

在主服务器中配置jenkins环境,不建议使用jenkins的docker容器,配置docker比较麻烦,且并不比war版方便多少。

wget -P /home/jenkins http://mirrors.jenkins.io/war-stable/latest/jenkins.war && \
firewall-cmd --zone=public --add-port=8080/tcp --permanent && \
systemctl reload firewalld && \
nohup java -jar /home/jenkins/jenkins.war &


当控制台输出带“nohup.out”字样时可按ctrl+c退出,然后执行如下执行

vi /home/jenkins/nohup.out


找到文件中如下字样

信息: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@24f2b2ff: defining beans [filter,legacy]; root of factory hierarchy
七月 15, 2017 9:11:15 上午 jenkins.install.SetupWizard init
信息:

*************************************************************
*************************************************************
*************************************************************

Jenkins initial setup is required. An admin user has been created and a password generated.
Please use the following password to proceed to installation:

9a8e9681d8f74a5894be57d618c6878b

This may also be found at: /root/.jenkins/secrets/initialAdminPassword

*************************************************************
*************************************************************
*************************************************************

七月 15, 2017 9:11:24 上午 hudson.model.UpdateSite updateData
信息: Obtained the latest update center data file for UpdateSource default
七月 15, 2017 9:11:26 上午 hudson.model.UpdateSite updateData
信息: Obtained the latest update center data file for UpdateSource default
七月 15, 2017 9:11:27 上午 hudson.WebAppMain$3 run
信息: Jenkins is fully up and running
七月 15, 2017 9:11:27 上午 hudson.model.DownloadService$Do


将其中的密钥(9a8e9681d8f74a5894be57d618c6878b)复制出来,在浏览器运行http://192.168.0.200:8080/ ,第一次登陆要求输入密钥,将刚刚复制的密钥复制进去,然后根据资料填写相关信息,插件安装则选择默认安装。

等待一段时间安装完毕后再登陆进去。

3.1.3 Maven环境

在主服务器中配置maven环境,这里有一个注意的地方,截止目前jenkins的这个版本只兼容到maven3.3.9,所以最好使用这个版本进行部署。

wget -P /home/maven https://archive.apache.org/dist/maven/maven-3/3.3.9/binaries/apache-maven-3.3.9-bin.tar.gz && \
tar zvxf /home/maven/apache-maven-3.3.9-bin.tar.gz -C /home/maven/


将maven加入环境变量

vi /etc/profile

#加入maven环境变量
MAVEN_HOME=/home/maven/apache-maven-3.3.9
export MAVEN_HOME
export PATH=${PATH}:${MAVEN_HOME}/bin


3.1.4 Git环境

yum install -y git


3.2 部署docker镜像私有仓库

也可以在阿里云上建立私有仓库,不过建议自己搭建,在内网中访问速度还是比较快的,而且也安全。

3.2.1 运行registry容器

在主服务器执行下面命令

docker run -d  --restart=always \
--name registry \
-p 5000:5000 \
-v /opt/data/registry:/tmp/registry registry


3.2.2 取消docker的https限制

需要在所有的服务器(包括主服务器及节点服务器)上取消docker对私有仓库https访问方式,否则会导致节点服务器拉取镜像或主服务器推送镜像时报错。

echo '{ "insecure-registries":["192.168.0.200:5000"] }' > /etc/docker/daemon.json && \
systemctl restart docker


其中ip地址为主服务器的地址。

3.2 部署consul

consul的作用在于服务发现,在这个系统中我仅仅用其节点发现功能,即当有新的服务器节点加入应用服务器集群时能够自动注册到consul中。在这套系统中需要在节点服务器上部署consul-agent,而在节点中某个服务器部署consul-server。节点服务器上的consul-agent负责收集各自服务器上应用的状态,再通过raft协议扩散到所有节点,这样所有的agent节点与agent-server都能知道整个集群的状态。consul-server则代表集群与外部应用进行通信。值得注意的是,consul-agent与consul-server是一模一样的容器,区别仅在于是谁加入谁。

3.2.1 关闭SELinux

centos下若不关闭SELinux则容器访问共享存储时会有权限问题,所以需要临时关闭。在主服务器与节点服务器执行以下命令

setenforce 0


3.2.2 运行consul-server容器

在节点1服务器上运行以下命令

docker run -d -h node1 -v /mnt:/data \
-p 8300:8300 \
-p 8301:8301 \
-p 8301:8301/udp \
-p 8302:8302 \
-p 8302:8302/udp \
-p 8400:8400 \
-p 8500:8500 \
-p 53:53/udp \
progrium/consul -server -advertise 192.168.0.201 -bootstrap-expect 1


参数说明如下

-advertise 为当前容器IP地址,也可使用overlay的网络地址。

-bootstrap-expect 参数表明该服务运行时最低开始进行选举的节点数,当设置为1时,则意味允许节点为一个时也进行选举;当设置为3时,则等到3台节点同时运行consul并加入到server才能参与选举,选举完集群才能够正常工作。

在这里有个小技巧,一开始我在找资料时发现网上大部分中文教程都直接给出运行容器的命令而不解释其中的参数,后来发现在https://hub.docker.com/ 官方镜像站上面可以找到大部分镜像参数的说明。

3.2.3 运行consul-agent容器

在其它节点服务器运行如下命令创建consul-agent容器并加入到consul-server集群中。值得注意的是其中的node2与advertise后面的IP要根据那台节点服务器的实际情况填写。

docker run -d -h node2 \
-p 8300:8300 \
-p 8301:8301 \
-p 8301:8301/udp \
-p 8302:8302 \
-p 8302:8302/udp \
-p 8400:8400 \
-p 8500:8500 \
-p 53:53/udp \
progrium/consul -server -advertise 192.168.0.202 -join 192.168.0.201


参数说明如下

-join 为加入的consul集群的consul-server容器地址。

3.2.4 测试是否集群成功

在浏览器中打开以下地址

http://192.168.0.201:8500/


该地址为consul-server所在服务器的IP地址,集群成功则在网页中找到节点服务器,如下图



3.3 部署docker集群

本次实验使用docker集群为docker swarm mode,这个模式是1.12版本之后才有的,比起原来直接run一个swarm容器相比,这种方式更加简单,仅需数句命令即可组建一个docker集群。值得注意的是,在这个集群里面,master节点也会参与运行容器。

3.3.1 开放端口

这一步尤为重要,如果端口不开放可能后面服务发现或者routing mesh无法工作,最显著的表现就是节点无法加入master,或者访问集群的任意一个未运行容器的节点时,未自动将请求转发到有运行容器的节点上。

在所有的节点服务器中运行以下命令

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/udp --permanent && \
systemctl reload firewalld


3.3.2 创建Master节点

在节点服务器一(node1)运行如下命令

docker swarm init


执行完此命令后会返回一个加入集群命令,如下

Swarm initialized: current node (4i0mnc9q3ue9tqsd0unknro77) is now a manager.

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

docker swarm join \
--token SWMTKN-1-3e3ty26af0g45vxk0hubw3j4oke204umuhw4k5ytjsbrez9lss-8fk0o9yqnaed93j1ed0jn4y5z \
192.168.0.201:2377

To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.


复制其中的docker swarm join命令,后面创建node节点会用到。

3.3.3 创建Node节点

在其它节点服务器(node2,node3)运行上一步的命令,以加入集群。

docker swarm join \
--token SWMTKN-1-3e3ty26af0g45vxk0hubw3j4oke204umuhw4k5ytjsbrez9lss-8fk0o9yqnaed93j1ed0jn4y5z \
192.168.0.201:2377


若加入成功会返回如下信息:

This node joined a swarm as a worker.


若加入失败,请认真检查是否有执行3.3.1的步骤。

3.3.4 检查集群状态

在集群的master节点中(node1),输入如下命令

docker node ls


若集群创建成功,则会返回如下内容

ID                           HOSTNAME  STATUS  AVAILABILITY  MANAGER STATUS
0i1m30us3hjrxicrc8efe6x97 *  node1     Ready   Active        Leader
51efmacx6292f1d4zjbgzzx1t    node3     Ready   Active
9f0dkpagh97yveohahka947du    node2     Ready   Active


3.4 搭建服务发现及负载均衡

在本次实验中,使用consul做服务发现,而使用consul-template监视consul,当有新的主机加入集群时,自动修改nginx的配置文件并进行reload。其中nginx与consul-template运行在同一个容器中,并且该容器应该运行在主服务器里。

3.4.1 下载consul-template

在主服务器上运行此命令

mkdir /home/consul && \
cd /home/consul && \
wget https://releases.hashicorp.com/consul-template/0.14.0/consul-template_0.14.0_linux_amd64.zip && \
yum install -y unzip && \
unzip consul-template_0.14.0_linux_amd64.zip


3.4.2 创建nginx配置文件模板

在主服务器上运行此命令

vi /home/consul/nginx.tmpl


在打开的空白文本中写入以下内容

upstream webapp {
{{ range nodes }}
server {{ .Address }}:8080 max_fails=3 fail_timeout=60 weight=1;{{ end }}
}

server {
listen 80 default_server;

location / {
proxy_pass http://webapp; }
}


执行:wq!保存该文本

当consul发现新主机时,则consul-template会根据上面模板内容修改配置,若想了解具体模板语法可到github上查看。

https://github.com/hashicorp/consul-template

3.4.3 创建nginx+consul-template镜像的dockerfile文件

在主服务器上运行此命令

vi /home/consul/nginx


在打开的空白文本中写入以下内容

FROM nginx
MAINTAINER caizh <x@codercai.cn>
WORKDIR /home
EXPOSE 80
COPY nginx.tmpl nginx.tmpl
COPY consul-template /usr/bin/consul-template
CMD nginx && consul-template -template "/home/nginx.tmpl:/etc/nginx/conf.d/default.conf:nginx -s reload" -consul $CONSUL_ADDR


执行:wq!保存该文本

该dockerfile主要是以官方Nginx镜像为基础镜像,然后加入了上面两步准备的consul-template文件以及nginx配置文件模板,其中

consul-template -template "/home/nginx.tmpl:/etc/nginx/conf.d/default.conf:nginx -s reload" -consul $CONSUL_ADDR


命令的意思连接consul,当consul的主机有发生变化时,根据nginx.tmpl模板生成default.conf配置文件并reload nginx,其中CONSUL_ADDR为运行容器时传入的环境变量。

3.4.4 构建consul-template-nginx镜像并运行容器

在主服务器运行以下命令

docker build -t nginx:consul --rm=true -f /home/dockerfiles/nginx/nginx ./ && \
docker run -d --name nginx -p 80:80 -e CONSUL_ADDR=192.168.0.201:8500 nginx:consul


成功运行后,进入该容器查看配置文件是否成功生成

docker exec -it nginx bash
//进入容器里后,运行下面命令
cat /etc/nginx/conf.d/default.conf
如果内容如下:
upstream webapp {
server 192.168.0.201:8080 max_fails=3 fail_timeout=60 weight=1;
server 192.168.0.202:8080 max_fails=3 fail_timeout=60 weight=1;
server 192.168.0.203:8080 max_fails=3 fail_timeout=60 weight=1;
}

server {
listen 80 default_server;

location / {
proxy_pass http://webapp; }
}
可以发现upstream webapp里面已经自动加入所有节点服务器的地址,证明服务发现及负载均衡搭建成功。


3.5 搭建keepalived实现主服务器的高可用

在本次实验中,两台主服务器通过keepalived实现双机热备,也可根据实际情况,对节点服务器的Master节点进行双机热备。

3.5.1 下载安装keepalived

在两台主服务器上执行以下指令

下载并解压keepalived

wget -P /home/keepalived http://www.keepalived.org/software/keepalived-1.3.5.tar.gz && \
cd /home/keepalived && \
tar -zxvf keepalived-1.3.5.tar.gz && \
cd keepalived-1.3.5

编译安装

yum install -y openssl && \
yum install -y openssl-devel  && \
./configure --prefix=/home/keepalived_install && \
make && \
make install

执行上述命令时会检查环境,若提示缺失什么依赖库,则使用yum install -y xxx进行安装。


3.5.2 创建配置文件夹

在两台主服务器上执行以下指令

mkdir -p /etc/keepalived/ && \
cp /home/keepalived_install/etc/keepalived/keepalived.conf /etc/keepalived/keepalived.conf


3.5.3 配置keepalived

在两台主服务器上执行以下指令

vi /etc/keepalived/keepalived.conf


将配置改为如下配置(只修改注释部分)

global_defs {
notification_email {
root@localhost #直接使用本地地址进行邮件通知
}
notification_email_from root@localhost #同上
smtp_server localhost  #同上
smtp_connect_timeout 30
router_id LVS_DEVEL
vrrp_skip_check_adv_addr
vrrp_strict
vrrp_garp_interval 0
vrrp_gna_interval 0
}

vrrp_instance VI_1 {
state MASTER #设为MASTER的机器为主机,备机需设为BACKUP
interface eth0 #这里必须与你的网卡名对应,可通过ip address 查看网卡名命令,一般默认都是eth0
virtual_router_id 51
priority 125 #优先级,应高一点
advert_int 1
authentication {
auth_type PASS
auth_pass 1111
}
virtual_ipaddress {
192.168.0.209 #虚拟IP,外部会通过虚拟IP访问这两台服务器,所以要设置一个还未被使用的IP地址,或者设置为主备两台机中任意一台机的IP地址。
}
}

virtual_server 192.168.200.100 443 {
delay_loop 6
lb_algo rr
lb_kind NAT
persistence_timeout 50
protocol TCP

real_server 192.168.201.100 443 {
weight 1
SSL_GET {
url {
path /
digest ff20ad2481f97b1754ef3e12ecd3a9cc
}
url {
path /mrtg/
digest 9b3a0c85a887a256d6939da88aabd8cd
}
connect_timeout 3
nb_get_retry 3
delay_before_retry 3
}
}
}

virtual_server 10.10.10.2 1358 {
delay_loop 6
lb_algo rr
lb_kind NAT
persistence_timeout 50
protocol TCP

sorry_server 192.168.200.200 1358

real_server 192.168.200.2 1358 {
weight 1
HTTP_GET {
url {
path /testurl/test.jsp
digest 640205b7b0fc66c1ea91c463fac6334d
}
url {
path /testurl2/test.jsp
digest 640205b7b0fc66c1ea91c463fac6334d
}
url {
path /testurl3/test.jsp
digest 640205b7b0fc66c1ea91c463fac6334d
}
connect_timeout 3
nb_get_retry 3
delay_before_retry 3
}
}

real_server 192.168.200.3 1358 {
weight 1
HTTP_GET {
url {
path /testurl/test.jsp
digest 640205b7b0fc66c1ea91c463fac6334c
}
url {
path /testurl2/test.jsp
digest 640205b7b0fc66c1ea91c463fac6334c
}
connect_timeout 3
nb_get_retry 3
delay_before_retry 3
}
}
}

virtual_server 10.10.10.3 1358 {
delay_loop 3
lb_algo rr
lb_kind NAT
persistence_timeout 50
protocol TCP

real_server 192.168.200.4 1358 {
weight 1
HTTP_GET {
url {
path /testurl/test.jsp
digest 640205b7b0fc66c1ea91c463fac6334d
}
url {
path /testurl2/test.jsp
digest 640205b7b0fc66c1ea91c463fac6334d
}
url {
path /testurl3/test.jsp
digest 640205b7b0fc66c1ea91c463fac6334d
}
connect_timeout 3
nb_get_retry 3
delay_before_retry 3
}
}

real_server 192.168.200.5 1358 {
weight 1
HTTP_GET {
url {
path /testurl/test.jsp
digest 640205b7b0fc66c1ea91c463fac6334d
}
url {
path /testurl2/test.jsp
digest 640205b7b0fc66c1ea91c463fac6334d
}
url {
path /testurl3/test.jsp
digest 640205b7b0fc66c1ea91c463fac6334d
}
connect_timeout 3
nb_get_retry 3
delay_before_retry 3
}
}
}


执行wq!保存

3.5.4 启动并检查

在两台主服务器上执行如下命令启动keepalived

/home/keepalived_install/sbin/keepalived -D


执行上面命令后,再执行下述命令检查是否已经启动三个keepalived进程

ps aux | grep keepalived




此时访问192.168.0.209相当于访问主服务器,当主服务器挂了,则虚拟IP自动漂移到备份服务器,实现高可用。

3.6 jenkins自动构建及部署应用

到这一步就已经完成大半了,剩下的内容就与实际的项目有关了,为了演示我创建了一个简单的spring boot项目在git(https://git.oschina.net/codercai/docker.git)上,这个应用打包出来为一个可运行的jar包,提供了一个简单http api,访问该接口时服务会先进行小段耗时操作(为了模拟实际应用消耗cpu行为)再返回应用所处环境的hostname。后面jenkins时自动部署脚本也是根据这个项目来做,大家在实际使用时需要根据自己项目再做调整。

3.6.1 创建dockerfile文件

在主服务器中运行如下命令

vi /home/Dockerfile
在打开的新文本文档中写入下面脚本内容

FROM registry.cn-hangzhou.aliyuncs.com/codercai/java
MAINTAINER caizh <x@codercai.cn>
WORKDIR /home
COPY target/*.jar main.jar
CMD java -jar main.jar


执行wq!保存

这是一个构建镜像脚本,基础镜像是一个我制作的只包含jdk环境的镜像,然后将打包出来jar包自动复制到镜像中,运行容器时则自动运行该jar包(spring-boot 运行环境只需要jdk,并且可以直接通过java -jar的方式运行)。具体dockerfile语法可上网搜索。

3.6.2 jenkins安装构建插件

在3.1.2章节中已经在主服务器上运行好了jenkins,所以在浏览器可以通过192.168.0.200:8080访问jenkins 的管理页面。



进入系统管理中的管理插件,勾选并下载下面几个插件

Git Parameter Plug-In(可选)
Maven Integration plugin
CloudBees Docker Build and Publish plugin


安装成功后可在“已安装”中看到



3.6.3 创建自动构建工程

在主页选择新建,如下图



输入工程名,该工程名要与3.6.1中的项目名一致,并且选择Maven项目,点击OK,如下图



在“源码管理页”中,选择Git,其中Repository URL填写项目的git地址,这里我填写了测试项目的git地址,Credentials 则填写相关账户密码,可点击旁边的Add按钮进行添加,Branches to build则是填写需要检出代码的分支,这里我选择了Master分支,如下图



在“构建触发器”中,选择Poll SCM,该选项的意思是定期到git上检查代码,若有新代码提交则触发构建,而旁边的日程表则填写时间间隔或定时,格式如下

每5分钟检查一次:*/5 * * * *
每天早上9点检查一次:0 9 * * *
每天8点~17点,每两小时检查一次:0 8-17/2 * * *
周一到周五,8点~17点,两小时检查一次:0 8-17/2 * * 1-5
每月1号、15号各检查一次,除12月:H H 1,15 1-11 *


在这里我方便演示,定为每分钟检查一次,如下图



在“Post Steps”中,选中“Run only if build succeeds ”,代表当maven打包成功后才进行下面的步骤,然后点击“Add post-build step”,选择“Execute shell,在Command中填入

cp /home/Dockerfile /root/.jenkins/jobs/docker/workspace/Dockerfile


上述指令为将放在/home下的dockerfile移动到工作空间,好让jenkins可以进行构建,路径中docker为项目名。如下图



然后再点击“Add post-build step”,选择“Docker Build and Publish”,其中”Repository Name”填写你想要构建的镜像名称(一般为项目名),“Tag”是镜像的标签号,在这里我直接使用了jenkins提供的v${BUILD_NUMBER},即将构建次数作为标签,Docker Host URI,Docker registry URL为你私有仓库所在的机器IP地址,通过Docker Host URI jenkins可以远程执行docker 命令,而通过Docker registry URL jenkins可以推送构建完成的镜像,如下图



再点击“Add post-build step”,选择“Execute shell”,代表当上述步骤完成后便执行脚本,在Command中写下以下脚本

#构建完成后的镜像地址
REGISTRY=192.168.0.200:5000/docker:v$BUILD_NUMBER && \
#创建服务
docker -H 192.168.0.201:2375 service create --replicas 1 --name docker -p 8080:80 $REGISTRY|| \
#创建服务失败则尝试升级服务
docker -H 192.168.0.201:2375 service update --image $REGISTRY  --update-parallelism 1 docker


参数说明:

service create 运行容器,在swarm mode中创建服务需要用此命令

replicas 同时运行容器数

name 容器名

update 更新容器

update-parallelism 同时更新容器的数量

如下图



点击保存,过了一分钟,jenkins会自动检出代码,进行第一次构建,如下图



通过jenkins的构建任务控制台可以看到构建已经成功,进入节点服务器也可以看到该服务已经在node2节点运行,如下图





从上图可以看出由于我的启动容器命令里面启动容器参数设为了1,所以它只在node2节点启动了容器,即在192.168.0.202:8080 机器上启动了该容器,所以访问该网址可以访问到我的服务



其中红框处为该容器的hostname,默认为该容器的ID。访问容器所在机器没问题,但访问集群中其它未运行容器的节点呢?



从上图可以看到节点服务器均能够正常访问,并且另外两个节点访问的服务来自于node2的服务,routing mesh及负载均衡工作正常。

四、测试

在这里我将简单通过以下两个场景来进行测试

- 修改代码并上传到git,观察系统是否自动完成检出代码、打包、构建镜像、启动容器。

- 增加机器,然后进行弹性扩容,观察是否扩容成功。

4.1 开发人员上传代码

在这个场景中我简单地修改一下代码,把返回的json数据中的version字段从8.0修改为9.0,然后上传代码,之后啥事都不做,只监控jenkins,看能不能成功完成。

首先修改代码并上传,如下图



观察jenkins,此时发现自动产生一次构建,如下图



查看该构建任务的控制台,发现其已经构建成功,并且重新生成且运行一个容器,如下图



此时再刷新一下网页,可以看到该服务已经成功发布,如下图



全程无运维人员参与,开发人员一旦提交代码便实现全自动构建及部署,由于私有仓库还保留之前构建的版本,所以若出现问题也可以及时回退而无需重新部署,另外根据业务需要,也可以设定部署日程,如每天凌晨2点构建等,十分灵活。

4.2 弹性扩容

在这个测试里我先将容器扩容到三个,以便三个节点都能够同时运行容器,然后再使用jmeter对该服务进行压测,粗略模拟现实中的高并发场景,最后再在主服务器上再建一台虚拟机临时充当扩容服务器进行扩容。

4.2.1 准备测试环境

在docker集群的master节点中运行以下命令以将三个节点均跑上容器。

docker service scale docker=3

其中scale后面的参数为运行时设定的name。




从上图可以看到运行后三个节点容器状态均处于Running,重新刷新一下网页验证是否成功运行。



从上图可以看到三个地址对应的容器ID均不一样,则证明此时访问的是三个不同的容器。

再进入主服务器的nginx配置看看



此时负载均衡配置是三个节点服务器,测试环境准备完成。

4.2.2 启动压测

在这里使用jmeter进行压测。



在测试中启动了50个线程,并同时请求负载均衡服务器,同时带了一个count参数,在项目的代码里,count参数的值越大,处理请求的速度则越久,用以模拟实际服务。

跑了大概三四分钟,吞吐量稳定在360左右,而从节点服务器也可以看出,三个节点的CPU使用率在12左右





4.2.3 扩容主机

在这次实验中由于就两台物理机,现在作为主服务器的物理机负载较小,所以在主服务器上再建一台虚拟机来模拟,该虚拟机的IP为192.168.0.204

新主机开机后,只需执行以下四步即可完成新节点加入。

第一步,安装docker服务并取消http限制

执行以下命令安装docker服务并取消docker拉取私有仓库时的http限制,其中insecure-registries的值对应私有仓库所在的地址。

yum install -y docker && \
echo '{ "insecure-registries":["192.168.0.200:5000"] }' > /etc/docker/daemon.json && \
systemctl start docker


第二步,开放相关端口

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/udp --permanent && \
firewall-cmd --zone=public --add-port=8080/udp --permanent && \
systemctl reload firewalld


以上命令在3.3.1节中用过,

其中7946及4789为docker集群所需端口,8080则为项目使用的端口。

第三步,运行consul-agent容器

执行3.2.3节中的启动consul-agent容器命令,以加入服务发现中,记得节点别名及advertise需要根据实际情况修改

docker run -d -h node4 \
-p 8300:8300 \
-p 8301:8301 \
-p 8301:8301/udp \
-p 8302:8302 \
-p 8302:8302/udp \
-p 8400:8400 \
-p 8500:8500 \
-p 53:53/udp \
progrium/consul -server -advertise 192.168.0.204 -join 192.168.0.201


第四步,加入docker swarm 集群

运行3.3.3节中的加入docker集群命令

docker swarm join \
--token SWMTKN-1-3e3ty26af0g45vxk0hubw3j4oke204umuhw4k5ytjsbrez9lss-8fk0o9yqnaed93j1ed0jn4y5z \
192.168.0.201:2377


若已经忘记此命令,可以在docker集群的master节点中(即node1),运行以下命令

docker swarm join-token worker




返回的命令就是加入集群的命令,将该命令放到新的虚拟机上运行。

到此新主机已经加入集群中了,我们可以查看consul的控制页及docker node ls命令。





接下来就是见证奇迹的时刻,我将docker 集群的容器数量扩容为4,即使用四个容器运行。

docker service scale docker=4


查看节点服务运行情况



可以看到新节点开始运行容器了。



从主服务器上的虚拟机管理器也可以看出新主机在开始负载了。

再查看jmeter



也可以看到吞吐量从之前的360提升到500,看到这里可能就有疑惑,为何nginx会知道有新机器?难道配置文件变了?我们可以查看一下nginx的配置



可以看到nginx的配置文件的负载服务器那自动加上了新增加的服务器,原因在于我使用了consul-template,它会自动监听consul上的服务变化而自动根据模板重写并加载nginx。

至此,测试也成功完成。

五、总结

5.1 踩过的坑

在整个使用中踩过不少的坑,并且由于中文资料较少,基本都得靠自己去官方文档中摸索,其中遇到印象深刻的坑如下:

routing mesh不工作

主要表现为当集群中只有一个节点启动了容器,而其它节点未启动容器时,访问其它节点的服务无法自动将请求转发到有容器的节点中,后来发现是端口未开放完全,所以大家千万要记住所有服务器务必开放7946的tcp、udp及4789的udp端口。

服务无法访问

在以前使用docker run命令运行容器时,只要命令中带参数p,则会自动映射端口且防火墙也会自动开放端口,但docker swarm mode下运行服务,宿主机防火墙的端口不会自动开放,需要自己去开放。

consul无法正常工作

这个问题和routing mesh一样坑了我两天,具体表现为进入consul控制台无法找到其它节点,并且consul容器的日志一直显示找不到master节点,最后发现是启动参数中的bootstrap的锅,看到这个单词我第一印象以为它和前端有关,没想到它真实的作用是限制选举条件,当设为3时,则当有三个节点才能进行选举,才有所谓的master,而一开始我只有一台虚拟机测试,并且官方指导中的命令及推荐都是3,结果死活都无法运行,后面改为1,即一台机器运行时也能参与选举,最后才解决这问题。

5.2 可能存在的坑

系统资源使用不充分

不知道是不是虚拟机的问题,在hyper v 的控制台中,我每个节点服务器cpu最高占用基本都是维持在12%,不清楚是docker容器的限制还是虚拟机本身的限制,有时间我再去研究研究。

弹性伸缩期间服务不可用

当存在大量请求时,此时进行弹性伸缩,nginx会短时间地报503错误,大概持续半分钟所有容器服务均不可用,而没有请求时,却没有此问题,具体问题原因后续再研究一下。

5.3 优化空间

整个系统还存在着许多优化的空间,比如在节点服务器中可以选择使用 registrator做服务注册,这样可以更细粒度地分配资源,但是似乎 registrator会与swarm mode冲突,会导致一些奇怪的问题。另外这个系统的弹性伸缩还需要运维人员手动执行指令进行,其实完全可以使用脚本定时检测系统资源,当节点系统资源占用较高时则触发扩容、反之当负载较小时则触发缩容,这样可以更加智能化。

5.4 最后说一下

从开始研究docker到集群成功,花了我两个星期的下班空闲时间,但写这篇博客却花了三个星期(无奈~),其中是有一些其它事情打扰,但最后总算写出来了,不过可能会存在许多问题,还希望大家指明。谢谢您的浏览!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息