4 常见代码部署方式
基础工作:
权限: 服务使用普通用户启动, saltstack或者ansible批量推送用户id, 确保每个服务,在不同的服务器上的id和用户名都是相同的
路径: 同一个服务在不同服务器的部署路径都是一样的
应用程序: /apps
数据: /data
静态页面: /data/nginx
/data/tomcat
/data/tomcat/logs
/data/tomcat/webapps
启动方式:
service文件: 虚拟机和物理机
脚本启动: java -xxx.jar
二进制: Go编译后的二进制文件
回滚:
解决问题:
持续升级
回滚:
静态文件: git reset 实现回滚
通过脚本: 对于java, 容器等, 需要通过脚本实现回滚到指定版本
备份:
更新上线前, 先把旧版本的, 比如, /data/tomcat/webapps目录拷贝一份, 一旦上线出现文件, 直接把新的版本的数据路径删除, 用备份好的上一个版本替换, 重启java服务, 即可完成回滚, 这样就不用重新编译java
/data/tomcat/backups: 备份目录定期清理, 备份到存储设备, 定期删除
清理:
备份文件和服务日志定期清理, 避免磁盘空间占满. 服务无法使用
增量和全量:
增量: 通过rsync等增量复制工具, 只把变化的文件复制到服务器上. 不过难度较大, 因为不好确定哪些文件发生了变化, 而且只适用静态文件, 对于需要编译的服务需要用全量
全量: 整个项目文件全部更新. 容器中必须全量升级, 重新制作镜像
4.1 蓝绿部署
蓝绿部署指的是不停老版本代码(不影响上一个版本访问),而是在另外一套环境部署新版本, 然后进行测试, 测试通过后将用户流量切到新版本, 其特点为业务无中断,升级风险相对较小.
具体过程:
1、当前版本业务正常访问(V1)
2、在另外一套环境部署新代码(V2),代码可能是增加了功能或者是修复了某些 bug
3、测试通过之后将用户请求流量切到新版本环境
4、观察一段时间,如有异常直接切换旧版本
5、下次升级,将旧版本升级到新版本(V3)
蓝绿部署适用的场景:
1、不停止老版本,额外部署一套新版本,等测试发现新版本 OK 后,删除老版本。
2、蓝绿发布是一种用于升级与更新的发布策略,部署的最小维度是容器,而发布的最小维度是应用。
3、蓝绿发布对于增量升级有比较好的支持,但是对于涉及数据表结构变更等不可逆转的升级,并不完全合适用蓝绿发布来实现,需要结合一些业务的逻辑以及数据迁移与回滚的策略才可以完全满足需求。当数据库有变化, 尽量搭建一套相同的数据库架构, 先在测试架构同时测试应用和数据库, 测试没问题, 再切换流量, 修改执行sql语句, 修改生产环境的数据库
- 蓝绿部署对于用户是无感知的, 即使升级时, 有用户正在连接, 那么当前用户会继续连接旧版本的服务器, 而新的用户会被LB调度到新版本的服务器上去. 并且配合session共享, 可以保证用户信息不会丢失. 最终达到无感知更新
图示:
4.2 金丝雀发布
金丝雀发布也叫灰度发布,是指在黑与白之间,能够平滑过渡的一种发布方式,灰度发布是增量发布的一种类型,灰度发布是在原有版本可用的情况下,同时部署一个新版本应用作为“金丝雀”(小白鼠),测试新版本的性能和表现,以保障整体系统稳定的情况下,尽早发现、调整问题。
金丝雀发布、灰度发布步骤组成:
1、准备好部署各个阶段的工件,包括:构建工件,测试脚本,配置文件和部署清单文件。
2、从负载均衡列表中移除掉“金丝雀”服务器。
3、升级“金丝雀”应用(排掉原有流量并进行部署)。
4、对应用进行自动化测试。
5、将“金丝雀”服务器重新添加到负载均衡列表中(连通性和健康检查)。
6、如果“金丝雀”在线使用测试成功,升级剩余的其他服务器。(否则就回滚)
灰度发布可以保证整体系统的稳定,在初始灰度的时候就可以发现、调整问题,以保证其影响度。
灰度发布/金丝雀部署适用的场景:
1、不停止老版本,额外搞一套新版本,不同版本应用共存。
2、灰度发布中,常常按照用户设置路由权重,例如 90%的用户维持使用老版本,10%的用户尝鲜新版本。
3、经常与 A/B 测试一起使用,用于测试选择多种方案。
4.3 滚动发布
滚动发布,一般是取出一个或者多个服务器停止服务,执行更新,并重新将其投入使用。周而复始,直到集群中所有的实例都更新成新版本。使用滚动升级需要确保代码没有问题, 因为滚动升级是分批次轮流升级完所有的服务器, 并不会有升级后的测试时间
升级步骤:
每个批次的服务器都是执行相同的步骤, 整个过程没有停顿, 一次性升级完所有的服务器
1. 将服务器从负载均衡器摘除
2. reload负载均衡器
3. 停止服务
4. 部署新的代码, 重启服务
5. 添加到负载均衡器
6. reload负载均衡器
4.4 A/B 测试
A/B 测试也是同时运行两个 APP 环境,但与蓝绿部署完全是两码事,A/B 测试是用来测试应用功能表现的方法,例如可用性、受欢迎程度、可见性等等,蓝绿部署的目的是安全稳定地发布新版本应用,并在必要时回滚,即蓝绿部署是一套正式环境环境在线,而A/B 测试是两套正式环境在线。
5 部署web架构环境
tomcat-1 10.0.0.199
tomcat-2 10.0.0.209
tomcat-3 10.0.0.219
5.1 tomcat环境部署
5.1.1 用户组和用户创建
id 8080 # 确保8080无占用
groupadd -g 8080 tomcat && useradd -g 8080 -u 8080 -s /bin/bash tomcat # 这里要给tomcat用户登陆服务器的权限, 因为后期服务器的启动, 都是以普通用户连接到服务器上, 不能用root用户
5.1.2 准备代码存放和部署目录
mkdir -pv /data/tomcat/tomcat_zip # 用于存放jenkins或者maven推送过来的jar|war包
mkdir -pv /data/tomcat/tomcat_file # 用于存放jar|war包解压后的目录文件
mkdir -pv /data/tomcat/tomcat_webapps # 真正的tomcat数据目录, 需要在server.xml中定义文件路径
mkdir -pv /data/tomcat/tomcat_file/myapp # 用于存放测试项目的项目代码, 项目就叫myapp. 项目后期会有多个版本, 比如myapp-1, myapp-2, 升级或者回滚时, 只需要通过脚本, 删除软连接, 重新创建软连接即可
vim /data/tomcat/tomcat_file/myapp/index.html
10.0.0.199 # 每个服务器上的文件先暂时存放自己的ip地址, 用于验证负载均衡使用. 生产环境每个服务器的文件都是一样的
10.0.0.209
10.0.0.219
ln -sv /data/tomcat/tomcat_file/myapp /data/tomcat/tomcat_webapps/myapp # 在tomcat_webapps目录下创建一个软链接目录, 链接到/data/tomcat/tomcat_file/myapp里的真实文件
5.1.3 安装jdk和tomcat
#!/bin/bash
DIR=`pwd`
JDK_FILE="jdk-8u271-linux-x64.tar.gz"
TOMCAT_FILE="apache-tomcat-8.5.59.tar.gz"
JDK_DIR="/apps"
TOMCAT_DIR="/apps"
install_jdk(){
if ! [ -f "$DIR/$JDK_FILE" ]; then
echo "$JDK_FILE 文件不存在" false
exit;
elif [ -d $JDK_DIR/jdk ]; then
echo "JDK 已经安装" false
exit;
else
[ -d "$JDK_DIR" ] || mkdir -pv $JDK_DIR
fi
tar xvf $DIR/$JDK_FILE -C $JDK_DIR
cd $JDK_DIR && ln -s jdk1.8* jdk
cat > /etc/profile.d/jdk.sh <<EOF
export JAVA_HOME=$JDK_DIR/jdk
export JRE_HOME=\$JAVA_HOME/jre
export CLASSPATH=\$JAVA_HOME/lib/:\$JRE_HOME/lib
export PATH=\$PATH:\$JAVA_HOME/bin
EOF
. /etc/profile.d/jdk.sh
java -version && echo "JDK 安装完成" || { echo "JDK 安装失败" false ; exit; }
}
install_tomcat(){
if ! [ -f "$DIR/$TOMCAT_FILE" ];then
echo "$TOMCAT_FILE 文件不存在" false
exit;
elif [ -d $TOMCAT_DIR/tomcat ];then
echo "TOMCAT 已经安装" false
exit;
else
[ -d "$TOMCAT_DIR" ] || mkdir -pv $TOMCAT_DIR
fi
tar xf $DIR/$TOMCAT_FILE -C $TOMCAT_DIR
cd $TOMCAT_DIR && ln -s apache-tomcat-* tomcat
echo "PATH=$TOMCAT_DIR/tomcat/bin:"'$PATH' > /etc/profile.d/tomcat.sh
id tomcat &> /dev/null || useradd -r -s /sbin/nologin tomcat
cat > $TOMCAT_DIR/tomcat/conf/tomcat.conf <<EOF
JAVA_HOME=$JDK_DIR/jdk
EOF
chown -R tomcat.tomcat ${TOMCAT_DIR}/tomcat/
cat > /lib/systemd/system/tomcat.service <<EOF
[Unit]
Description=Tomcat
After=syslog.target network.target
[Service]
Type=forking
EnvironmentFile=$TOMCAT_DIR/tomcat/conf/tomcat.conf
ExecStart=$TOMCAT_DIR/tomcat/bin/startup.sh
ExecStop=$TOMCAT_DIR/tomcat/bin/shutdown.sh
RestartSec=3
PrivateTmp=true
User=tomcat
Group=tomcat
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now tomcat.service
systemctl is-active tomcat.service &> /dev/null && echo "TOMCAT 安装完成" || { echo "TOMCAT 安装失败" false; exit; }
}
install_jdk
install_tomcat
5.1.4 修改tomcat配置文件
vim /apps/tomcat/conf/server.xml
<Host name="localhost" appBase="/data/tomcat/tomcat_webapps" # 修改自定义的数据路径
unpackWARs="false" autoDeploy="false"> # 取消自动解压和自动部署
5.1.5 准备tomcat启动脚本
之所以要准备专门的脚本, 是因为无论用catalina.sh还是用Service文件, 都有可能出现java进程无法停止的现象, 因此, 可以在脚本中查看java的进程id, 执行使用pkill去强行杀掉进程
使用了脚本后, 上面部署脚本创建的service文件就不会再用了
#!/bin/bash
JDK_HOME=/apps/jdk
CATALINA_HOME=/apps/tomcat
export JDK_HOME CATALINA_HOME
source /etc/profile.d/tomcat.sh
source /etc/profile.d/jdk.sh
start(){
echo "正在判断服务状态, 请稍等!"
echo "倒计时三秒钟!"
for i in `seq 3 | sort -r`; do echo $i ; sleep 1 ; done
if lsof -i :8080 &> /dev/null; then
echo "Tomcat正在运行!"
else
echo "Tomcat没有运行, 三秒后准备启动!"
for i in `seq 3 | sort -r`; do echo $i ; sleep 1 ; done
$CATALINA_HOME/bin/catalina.sh start
echo "Tomcat启动已经执行, 五秒后判断是否启动成功!"
for i in `seq 5 | sort -r`; do echo $i ; sleep 1 ; done
if lsof -i :8080 &> /dev/null; then
PID=`lsof -i:8080 | tail -1 | awk '{print $2}'`
NUM=`lsof -i:8080 | tail -1 | awk '{print $2}' | wc -l`
echo "Tomcat启动成功! 共${NUM}个java进程, 其PID为${PID}"
else
echo "Tomcat启动失败, 三秒后尝试再次启动!"
for i in `seq 3 | sort -r`; do echo $i ; sleep 1 ; done
$CATALINA_HOME/bin/catalina.sh start
echo "Tomcat启动已经执行, 五秒后判断是否启动成功!"
for i in `seq 5 | sort -r`; do echo $i ; sleep 1 ; done
if lsof -i :8080 &> /dev/null; then
PID=`lsof -i:8080 | tail -1 | awk '{print $2}'`
NUM=`lsof -i:8080 | tail -1 | awk '{print $2}' | wc -l`
echo "Tomcat启动成功! 共${NUM}个java进程, 其PID为${PID}"
else
echo "Tomcat启动失败, 请检查日志!"
fi
fi
fi
}
stop(){
echo "正在判断Tomcat运行状态, 倒计时3秒"
for i in `seq 3 | sort -r`; do echo $i ; sleep 1 ; done
if lsof -i :8080 &> /dev/null; then
PID=`lsof -i:8080 | tail -1 | awk '{print $2}'`
NUM=`lsof -i:8080 | tail -1 | awk '{print $2}' | wc -l`
echo "Tomcat正在运行, 进程ID为$PID, 共$NUM个进程"
echo "三秒后, 准备关闭Tomcat"
for i in `seq 3 | sort -r`; do echo $i ; sleep 1 ; done
$CATALINA_HOME/bin/catalina.sh stop; echo "已尝试关闭Tomcat, 30秒后检查是否关闭成功!"
sleep 27;
for i in `seq 3 | sort -r`; do echo $i ; sleep 1 ; done
if lsof -i :8080 &> /dev/null; then
echo "Tomcat正常关闭失败, 准备pkill强行关闭Tomcat进程"
sleep 3;
pkill java && pkill tomcat
if lsof -i :8080 &> /dev/null; then
echo "Tomcat关闭失败, 请检查日志!"
else
echo "Tomcat关闭成功!"
fi
else
echo "Tomcat关闭成功!"
fi
else
echo "Tomcat并没有运行!"
fi
}
restart(){
stop
start
}
case $1 in
stop)
stop;;
start)
start;;
restart)
restart;;
*)
echo "Usage: /etc/init.d/tomcat.sh start|stop|restart";;
esac
root@tomcat-1:/opt# mv tomcat.sh /etc/init.d/
root@tomcat-1:/opt# chmod +x /etc/init.d/tomcat.sh
- 用systemd停止tomcat服务, 然后用脚本启动
systemctl stop tomcat
/etc/init.d/tomcat.sh restart # 需要切换到tomcat用户下, 执行脚本, 如果是用的root用户启动, 那么之后jenkins远程到tomcat上是tomcat用户, 无法使用lsof -i :8080去查看root用户启动的端口状态. 使用tomcat用户启动, 那么tomcat服务就是tomcat用户启动的
chown -R tomcat.tomcat /data/tomcat/
chown -R tomcat.tomcat /apps/tomcat/
chown -R tomcat.tomcat /apps/jdk/
5.1.6 验证三个服务器都可以正常访问
http://10.0.0.219:8080/myapp/
http://10.0.0.209:8080/myapp/
http://10.0.0.199:8080/myapp/
5.2 部署Keepalived
HA-1 10.0.0.179
HA-2 10.0.0.189
- 10.0.0.179
root@ha-1:~# apt -y install keepalived
root@ha-1:~# cp /usr/share/doc/keepalived/samples/keepalived.conf.vrrp /etc/keepalived/keepalived.conf
root@ha-1:~# vim /etc/keepalived/keepalived.conf
vrrp_instance VI_1 {
state MASTER
interface eth0
garp_master_delay 10
smtp_alert
virtual_router_id 51
priority 100
advert_int 1
authentication {
auth_type PASS
auth_pass 1111
}
virtual_ipaddress {
10.0.0.188 dev eth0 label eth0:0
}
}
root@ha-1:~# systemctl restart keepalived.service
- 验证vip可用
root@ha-1:~# ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 10.0.0.179 netmask 255.255.255.0 broadcast 10.0.0.255
inet6 fe80::20c:29ff:fe49:4cd9 prefixlen 64 scopeid 0x20<link>
ether 00:0c:29:49:4c:d9 txqueuelen 1000 (Ethernet)
RX packets 22499 bytes 13630022 (13.6 MB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 9441 bytes 873838 (873.8 KB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
eth0:0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 10.0.0.188 netmask 255.255.255.255 broadcast 0.0.0.0
ether 00:0c:29:49:4c:d9 txqueuelen 1000 (Ethernet)
root@ha-1:~# ping 10.0.0.188
PING 10.0.0.188 (10.0.0.188) 56(84) bytes of data.
64 bytes from 10.0.0.188: icmp_seq=1 ttl=64 time=0.012 ms
64 bytes from 10.0.0.188: icmp_seq=2 ttl=64 time=0.024 ms
64 bytes from 10.0.0.188: icmp_seq=3 ttl=64 time=0.046 ms
- 10.0.0.189
root@ha-2:~# apt -y install keepalived
root@ha-2:~# cp /usr/share/doc/keepalived/samples/keepalived.conf.vrrp /etc/keepalived/keepalived.conf
root@ha-2:~# vim /etc/keepalived/keepalived.conf
! Configuration File for keepalived
global_defs {
notification_email {
acassen
}
notification_email_from Alexandre.Cassen@firewall.loc
smtp_server 192.168.200.1
smtp_connect_timeout 30
router_id LVS_DEVEL
}
vrrp_instance VI_1 {
state MASTER
interface eth0
garp_master_delay 10
smtp_alert
virtual_router_id 51
priority 80
advert_int 1
authentication {
auth_type PASS
auth_pass 1111
}
virtual_ipaddress {
10.0.0.188 label eth0:0 dev eth0
}
}
root@ha-2:~# systemctl restart keepalived.service
- 停止ha-1上的keepalived服务, 验证vip会迁移到ha-2上即可
5.3 部署HAproxy
HA-1 10.0.0.179
HA-2 10.0.0.189
5.3.1 安装步骤
见: https://www.jianshu.com/p/a4739bbf950c
5.3.2 修改HAproxy配置以及网络和内核参数, 测试web访问
- 修改内核参数
vim /etc/security/limits.conf
# /etc/security/limits.conf
#
#This file sets the resource limits for the users logged in via PAM.
#It does not affect resource limits of the system services.
#
#Also note that configuration files in /etc/security/limits.d directory,
#which are read in alphabetical order, override the settings in this
#file in case the domain is the same or more specific.
#That means for example that setting a limit for wildcard domain here
#can be overriden with a wildcard setting in a config file in the
#subdirectory, but a user specific setting here can be overriden only
#with a user specific setting in the subdirectory.
#
#Each line describes a limit for a user in the form:
#
#<domain> <type> <item> <value>
#
#Where:
#<domain> can be:
# - a user name
# - a group name, with @group syntax
# - the wildcard *, for default entry
# - the wildcard %, can be also used with %group syntax,
# for maxlogin limit
#
#<type> can have the two values:
# - "soft" for enforcing the soft limits
# - "hard" for enforcing hard limits
#
#<item> can be one of the following:
# - core - limits the core file size (KB)
# - data - max data size (KB)
# - fsize - maximum filesize (KB)
# - memlock - max locked-in-memory address space (KB)
# - nofile - max number of open file descriptors
# - rss - max resident set size (KB)
# - stack - max stack size (KB)
# - cpu - max CPU time (MIN)
# - nproc - max number of processes
# - as - address space limit (KB)
# - maxlogins - max number of logins for this user
# - maxsyslogins - max number of logins on the system
# - priority - the priority to run user process with
# - locks - max number of file locks the user can hold
# - sigpending - max number of pending signals
# - msgqueue - max memory used by POSIX message queues (bytes)
# - nice - max nice priority allowed to raise to values: [-20, 19]
# - rtprio - max realtime priority
#
#<domain> <type> <item> <value>
#
#* soft core 0
#* hard rss 10000
#@student hard nproc 20
#@faculty soft nproc 20
#@faculty hard nproc 50
#ftp hard nproc 0
#@student - maxlogins 4
# End of file
* soft core unlimited
* hard core unlimited
* soft nproc 1000000
* hard nproc 1000000
* soft nofile 1000000
* hard nofile 1000000
* soft memlock 32000
* hard memlock 32000
* soft msgqueue 8192000
* hard msgqueue 8192000
- 修改网络参数
vim /etc/sysctl.conf
# Controls source route verification
net.ipv4.conf.default.rp_filter = 1
net.ipv4.ip_nonlocal_bind = 1 # 支持服务器绑定本机不存在的端口
net.ipv4.ip_forward = 1
# Do not accept source routing
net.ipv4.conf.default.accept_source_route = 0
# Controls the System Request debugging functionality of the kernel
kernel.sysrq = 0
# Controls whether core dumps will append the PID to the core filename.
# Useful for debugging multi-threaded applications.
kernel.core_uses_pid = 1
# Controls the use of TCP syncookies
net.ipv4.tcp_syncookies = 1
# Disable netfilter on bridges.
net.bridge.bridge-nf-call-ip6tables = 0
net.bridge.bridge-nf-call-iptables = 0
net.bridge.bridge-nf-call-arptables = 0
# Controls the default maxmimum size of a mesage queue
kernel.msgmnb = 65536
# # Controls the maximum size of a message, in bytes
kernel.msgmax = 65536
# Controls the maximum shared segment size, in bytes
kernel.shmmax = 68719476736
# # Controls the maximum number of shared memory segments, in pages
kernel.shmall = 4294967296
使配置生效
sysctl -p
- 修改haproxy配置文件
listen tomcat
bind 10.0.0.188:80
mode http
log global
server 10.0.0.199 10.0.0.199:8080 check inter 3000 fall 2 rise 5
server 10.0.0.209 10.0.0.209:8080 check inter 3000 fall 2 rise 5
server 10.0.0.219 10.0.0.219:8080 check inter 3000 fall 2 rise 5
systemctl restart haproxy
root@git-client:~# curl http://10.0.0.188/myapp/
10.0.0.199
root@git-client:~# curl http://10.0.0.188/myapp/
10.0.0.209
root@git-client:~# curl http://10.0.0.188/myapp/
10.0.0.219
5.3.3 准备HAproxy配置和文档同步脚本
- 生产环境一般至少有两台HAproxy, 当HAproxy配置更改后, 一般还要手动拷贝配置文件到另一台服务器. 因此, 可以通过免秘钥认证, 配合脚本, 实现当HAproxy配置更改后, 直接执行同步脚本, 即可把当前服务器上的配置文件和文档, 通过scp传给另一个服务器
ha-1上生成root本地公钥, 传给ha-2, 做免密认证. 一般都是通过root登陆负载均衡器, 因此直接生成root账户秘钥即可
root@ha-1:~# ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/root/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /root/.ssh/id_rsa.
Your public key has been saved in /root/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:u6ZmuuwPTyBgKINR/7VMKfUisqsc6vcdoDNXynLvZW4 root@ha-1
The key's randomart image is:
+---[RSA 2048]----+
|... . |
|o. . . o |
|*. o o = . |
|oo + * o |
| . o...S |
| .oo+ . |
| .=o=...o |
| o =*+= *E |
|o.+.*B=*o. |
+----[SHA256]-----+
root@ha-1:~#
root@ha-1:~#
root@ha-1:~# ssh-copy-id 10.0.0.189
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/root/.ssh/id_rsa.pub"
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
root@10.0.0.189's password:
Number of key(s) added: 1
Now try logging into the machine, with: "ssh '10.0.0.189'"
and check to make sure that only the key(s) you wanted were added.
root@ha-1:~# ssh 10.0.0.189
Welcome to Ubuntu 18.04.4 LTS (GNU/Linux 4.15.0-76-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
System information as of Sun Jul 4 00:49:51 CST 2021
System load: 0.09 Processes: 167
Usage of /: 11.4% of 18.21GB Users logged in: 1
Memory usage: 7% IP address for eth0: 10.0.0.189
Swap usage: 0%
* Super-optimized for small spaces - read how we shrank the memory
footprint of MicroK8s to make it the smallest full K8s around.
https://ubuntu.com/blog/microk8s-memory-optimisation
189 packages can be updated.
122 updates are security updates.
New release '20.04.2 LTS' available.
Run 'do-release-upgrade' to upgrade to it.
*** System restart required ***
Last login: Sat Jul 3 23:35:11 2021 from 10.0.0.1
root@ha-2:~# # 验证可以免密要登陆
root@ha-1:~# vim ha_sync.sh
#!/bin/bash
scp -r /etc/haproxy/* 10.0.0.189:/etc/haproxy
systemctl reload haproxy # 修改配置后, 加载本地ha
ssh 10.0.0.189 "systemctl reload haproxy" # 加载另一台ha
- 在ha-1上修改配置文件, 添加ha状态页
listen stats
mode http
bind 0.0.0.0:9999
stats enable
log global
stats uri /haproxy-status
stats auth haadmin:000000
执行脚本, 验证配置同步成功, 配置生效即可
5.3.4 Windows本地添加域名解析
10.0.0.188 web.linux.org
5.3.5 测试访问
- 轮训访问
6 Jenkins部署
Ubuntu-1804 10.0.0.249
4G 2c
jenkins_2.263.1_all.deb
生产环境: 至少8c 16G 500G固态
6.1 安装daemon依赖包
root@jenkins:~# apt -y install daemon
6.2 安装jdk
root@jenkins:~# apt -y install openjdk-8-jdk
6.3 安装Jenkins
root@jenkins:~# dpkg -i jenkins_2.263.1_all.deb
root@jenkins:~# systemctl enable --now jenkins
jenkins.service is not a native service, redirecting to systemd-sysv-install.
Executing: /lib/systemd/systemd-sysv-install enable jenkins
root@jenkins:~# ps aux | grep jenkins
jenkins 8195 0.0 0.1 76640 7508 ? Ss 01:38 0:00 /lib/systemd/systemd --user
jenkins 8196 0.0 0.0 193900 2640 ? S 01:38 0:00 (sd-pam)
jenkins 8210 0.0 0.0 20388 184 ? S 01:38 0:00 /usr/bin/daemon --name=jenkins --inherit --env=JENKINS_HOME=/var/lib/jenkins --output=/var/log/jenkins/jenkins.log --pidfile=/var/run/jenkins/jenkins.pid -- /usr/bin/java -Djava.awt.headless=true -jar /usr/share/jenkins/jenkins.war --webroot=/var/cache/jenkins/war --httpPort=8080
jenkins 8211 115 13.3 3511024 534684 ? Sl 01:38 0:13 /usr/bin/java -Djava.awt.headless=true -jar /usr/share/jenkins/jenkins.war --webroot=/var/cache/jenkins/war --httpPort=8080
root 8304 0.0 0.0 14428 1032 pts/0 S+ 01:38 0:00 grep --color=auto jenkins
root@jenkins:~# ss -ntl
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 128 127.0.0.53%lo:53 0.0.0.0:*
LISTEN 0 128 0.0.0.0:22 0.0.0.0:*
LISTEN 0 50 *:8080 *:*
LISTEN 0 128 [::]:22 [::]:*
6.4 查看日志, 验证启动成功
root@jenkins:~# tail -f /var/log/jenkins/jenkins.log
...
2021-07-03 17:42:33.897+0000 [id=20] INFO hudson.WebAppMain$3#run: Jenkins is fully up and running
6.5 访问页面, 填写密码
6.6 插件安装
- 选择推荐插件即可
- 解决安装插件速度慢的方式
通过 Nginx 进行 rewrite 或者反向代理,如下:
在jenkins上配置DNS解析, 并且安装一个Nginx, 127.0.0.1 updates.jenkins-ci.org
location /download/plugins {
proxy_set_header Host mirrors.tuna.tsinghua.edu.cn;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
rewrite /download/plugins(.*) /jenkins/plugins/$1 break;
proxy_pass http://mirrors.tuna.tsinghua.edu.cn; } # 使用清华源去装
- 如果显示 jenkins 已离线,将以下文件中的更新检查地址改成国内清华大学地址,然后重启 jenkins 即可:https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/update-center.json
vim /var/lib/jenkins/hudson.model.UpdateCenter.xml
<?xml version='1.1' encoding='UTF-8'?>
<sites>
<site>
<id>default</id>
<url>https://updates.jenkins.io/update-center.json</url>
</site>
- 如果jenkins服务器无法上网, 可以找一台能上网的服务器, 或者在虚拟机里部署一个和生产环境版本相同的jenkins, 把插件下载下来, jenkins的插件都会保存在
/var/lib/jenkins/plugins/
目录, 把这个目录内的内容整体打包拷贝到jenkins服务器, 重启jenkins即可
6.7 创建管理员用户
6.8 实例配置
- 保存默认即可
6.9 插件管理
- 安装gitlab插件, 让jenkins通过gitlab拉取代码
- 选择安装完成后, 重启jenkins. 如果jenkins没有正在执行构建任务, 那么可以安装插件后, 直接重启jenkins
6.10 测试项目
- 部署一个自由风格软件项目
- jenkins每个项目的构建有多个自定义的步骤, 这里先构建一个最简单的任务, 显示当前用户和工作目录
workspace: jenkins默认的工作目录, 从gitlab拉取的代码, 以及构建的项目, 默认会存到/var/lib/jenkins/workspace目录下
root@jenkins:~# getent passwd jenkins
jenkins:x:111:115:Jenkins,,,:/var/lib/jenkins:/bin/bash
7 Jenkins权限管理
在公司中, jenkins一般会负责多个环境, 生产, 测试, 开发, 以及多个项目的部署
一般生产环境统一由运维负责部署, 测试和开发环境, 可以交给开发人员去部署, 通过创建对应的用户和权限, 让开发和测试人员只能访问到自己负责的开发和测试环境中的项目
jenkins基于角色的权限管理, 需要先创建角色和用户, 给角色授权, 然后把用户关联到角色. 角色即为一组权限的集合, 实际就是组
7.1 插件安装
7.2 项目, 角色以及用户创建
- 创建两个用户, 分别管理两个项目, 每个项目包含2个job
tom 项目A
包含job: projectA-web1
projectA-web2
jerry 项目B
包含job: projectB-web1
projectB-web2
创建项目
创建用户
用户授权修改
默认情况, 创建的用户和管理员具有相同的权限, 可以对Jenkins进行配置管理, 用户管理和项目管理, 因此, 要对普通用户进行单独的授权
- 修改授权策略为Role-Based Strategy
- 修改后点击保存, 用创建的普通账户登录Jenkins, 验证修改生效
创建角色
- 管理员会有全部的权限
- 普通用户只需要Item roles
项目A的角色创建
Role to add: 角色名称
Pattern: 基于正则表达式, 匹配项目中的job的名称
项目B的角色创建
- 此外, 还需要在Global Roles中创建只有全局读的角色, 并且关联相应的用户, 否则用户登陆Jenkins是看不到界面内容的. Global Roles和Item Roles是独立的权限, 因此要单独创建角色
将用户和角色关联
- tom用户关联到projectA角色
- jerry用户关联到projectB角色
- tom用户关联到projectA-readonly角色
- jerry用户关联到projectB-readonly角色
- 登陆tom和jerry用户, 验证授权成功
- 测试tom和jerry用户, 可以分别对projectA和projectB中的项目进行构建
Jenkins界面的操作权限是针对Jenkins的, 与Linux服务器上的权限无关
至于构建时执行的shell脚本中所需的权限, 还需要单独配置
7.3 Jenkins邮件通知
Jenkins配置邮件通知,当构建任务结束后, 把结果发给用户, 需要通过admin账户配置
启动邮件功能, 需要jenkins可以上外网, 否则无法连接smtp服务器
8 配置Jenkins基于ssh秘钥从gitlab拉取代码
从gitlab拉取代码需要提交用户名和密码, 或者使用ssh的方式, 把服务器的公钥添加到gitlab上
一般的拉取代码可以基于用户名和密码, 而jenkins需要通过ssh key进行非交互式拉取代码
- 以projectA-web1为例, 其对应gitlab上的创建的qq/web-02
git@10.0.0.239:qq/web-02.git
这时因为没有做免密认证, 因此会报错, 需要添加jenkins凭据
在jenkins服务器上, 切换到jenkins用户, 生成公钥
root@jenkins:~# su - jenkins
jenkins@jenkins:~$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/var/lib/jenkins/.ssh/id_rsa):
Created directory '/var/lib/jenkins/.ssh'.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /var/lib/jenkins/.ssh/id_rsa.
Your public key has been saved in /var/lib/jenkins/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:DuVHpIJRWGqGBTD53VIplcaaCE/LgmLWY0toAf2T+GQ jenkins@jenkins
The key's randomart image is:
+---[RSA 2048]----+
|=+..o*+o . |
|ooooo+* o |
|.*=*=O. o . |
|+=BBE .+ . |
|=.o+oo. S . |
| .. o . |
| . |
| |
| |
+----[SHA256]-----+
jenkins@jenkins:~$
jenkins@jenkins:~$ cat .ssh/id_rsa.pub
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDtpGpDfp/S8ePBSVyxwO854eGyl4UUsfKzxfXYEhw0DlduxNsQP6lGuRhPE9ADEby+5VPGCw4jj4gNyaAjKH4EZjN3ZspzrDbPBdyPTnh8mIhahhD0sQbhBORw9WQRezTIVNECpSpHCMx/Bnmhz/EbN8sHQfe4NlMmtHUN+Nc5dSnUCnmgWSFHhh8HmycRDRY+7oB8dqYa01S60Mh8F6UjONmuJdLdpGsPzY/uZKtcISxX80DXldusDDZvUGDYkqyE9rYvexQKUYwP/mehDDh37lalAm4QxhqP5EJGU1EHEXddY71l4kM/if6lY23gJwitoPq8eTKdzHIPezjotO7R jenkins@jenkins
把公钥添加到gitlab, 用root用户登陆gitlab, 将公钥添加到管理员账户下
添加公钥后, 在jenkins服务器上克隆代码时, gitlab会校验其公钥和私钥, 如果是一对, 就不会要求输入用户名和密码. 此时, 在jenkins服务器上, 用jenkins用户, 通过git命令提交代码也是不需要输入用户名和密码的
jenkins@jenkins:~$ git clone git@10.0.0.239:qq/web-02.git
Cloning into 'web-02'...
The authenticity of host '10.0.0.239 (10.0.0.239)' can't be established.
ECDSA key fingerprint is SHA256:J/g/xfIrIB5AUh/fLvUZqO8jkECSfW4Nb3DZ14n8/lo.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '10.0.0.239' (ECDSA) to the list of known hosts.
remote: Enumerating objects: 21, done.
remote: Counting objects: 100% (21/21), done.
remote: Compressing objects: 100% (9/9), done.
remote: Total 21 (delta 6), reused 17 (delta 5), pack-reused 0
Receiving objects: 100% (21/21), 1.66 KiB | 1.66 MiB/s, done.
Resolving deltas: 100% (6/6), done.
这种非交互式拉取和提交代码, 只适合jenkins, 开发人员提交和拉取代码还是建议用账号和密码, 并且普通用户是没有权限添加和修改凭据的, 需要使用管理员用户
下一步需要把jenkins服务器的私钥, 添加到jenkins的凭据里. 因为, 在jenkins服务器上, 以jenkins用户通过git命令去拉取和提交代码, 是通过jenkins的私钥和gitlab上上传的公钥进行校验的. 而jenkins本身是没有这个私钥的, 因此要添加私钥
jenkins@jenkins:~/web-02$ cat ~/.ssh/id_rsa
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEA7aRqQ36f0vHjwUlcscDvOeHhspeFFLHys8X12BIcNA5XbsTb
ED+pRrkYTxPQAxG8vuVTxgsOI4+IDcmgIyh+BGYzd2bKc6w2zwXcj054fJiIWoYQ
9LEG4QTkcPVkEXs0yFTRAqUqRwjMfwZ5oc/xGzfLB0H3uDZTJrR1DfjXOXUp1Ap5
oFkhR4YfB5snEQ0WPu6AfHamGtNUutDIfBelIzjZriXS3aRrD82P7mSrXCEsV/NA
15XbrAw2b1Bg2JKshPa2L3sUClGMD/5noQw4d+5WpQJuEMYaj+RCRlNRBxF3XWO9
ZeJDP4n+pWNt4CcIraD6vHkyncxyD3s46LTu0QIDAQABAoIBAFOG1Z7Rk+V2BeJ7
IayMrsj+fvDh1vHWNgNAElAW1XjqjYqugHzuk1X7WvL4eMWLZ3cFbfOPETIokd6e
vr18zwZG0dIm/AO6RS1w1vw7zZelmU+QDcejrVJMAOs/JSQL1RqQzUILwZdkHVSM
dxYL26gAbUkow6Qo3AMfrIDztNpJiaPuItTtBXnJPw2w2HmqF7yfRr8wO8AcicqZ
/+mcSJGqg2bX5R++aWWFKcGDW/B54Zj0SXK93r9+CS4EC5NJle1gm5iiLXjoxHLm
wOd8PJE7oelRklpvSbFfUxopFTyq5kWeeOI6CCI7nuVEOme6hBlxg3K0huXcUcX/
3TmKjH0CgYEA/zV4V3G4FUn8dFsbZj5zHy62cWn95wSQ4QJHTxWzjpDzzvlAdU0L
bMjX0v9IaRtHBKcsYu8HIuCSmyq/AZGlKiC2FxRYvzUnXr6L16yk2UkDJ8Ae2nfL
xyzokzbwkmogbO3OTOfyicGgT5jBWHkYnh0ZlkQPjeJKDs3GVHQd94sCgYEA7mEB
IH4A5YGi3OKq3zC0VUDt7XWuq4BHhVhoSJUktKxDaEols7iLd3AEpji5nwq+Dyox
fyHJ4oLOXKR/Tl6kac8Urc525Jt7n8H97ZVe2N8CnrgKYKRZ2FSUbxXUWoy23LWG
JEGrMdClBeREd4kTX/OcOGzPeFGKGsVOTQ0onpMCgYA8UeUJtgUucvhKgCYvul73
ZdEVaVnrunaL2EAGfzibX8NgjiUgFH+4zJfGdTQmM55LiT/CeoCTS8UbC7Vtp4EE
PgsL1XFMcEHH8P2YkmO8P7eRM6WXOW1evEyUTw35bUAQvvxdzQzUZoF/jrlUMfsR
lAsyKxAKFwwuB5Dy8ScMFQKBgBmeHSPjxq7jbqA0T86qika7MpaZVgsiPw1qgdKD
DK3J8XiMt16ID5znUeiXIgOggOpaS3VxKSQJkKnOF8xRSXQBiCRhks9gkjmJ/IMC
0W2XEc4C00KthuZDuKvzeZ29j41KiL7uu7ofxQxMCruZ5JYkNP6vWuCASsozuAV5
y4q3AoGAXEFGm7Kh+wWnWq9jVdG+HLTWjSS3S+8acmxXKdm2JidSa2c3C/Mdkmag
Rs2p7wxPT1RITi8eBHvTkDXuF7nV2zU8UEnAV/A/nHDeBCf1Ohrh4RogLg9G5HI1
/1m/WPcE0hnMbWnOFPnWtJeVQcnBERIhK9ZoVBgpkkEpC2tbP9s=
-----END RSA PRIVATE KEY-----
添加后选择新添加的凭据, 然后添加构建shell命令, 点击保存
测试构建
jenkins@jenkins:~$ cd /var/lib/jenkins/workspace/
jenkins@jenkins:~/workspace$ ls
projectA-web1 projectA-web1@tmp projectB-web1 test-job
jenkins@jenkins:~/workspace$ ls -l
total 16
drwxr-xr-x 3 jenkins jenkins 4096 Jul 4 22:40 projectA-web1 # 项目克隆成功
drwxr-xr-x 2 jenkins jenkins 4096 Jul 4 22:40 projectA-web1@tmp
drwxr-xr-x 2 jenkins jenkins 4096 Jul 4 21:24 projectB-web1
drwxr-xr-x 2 jenkins jenkins 4096 Jul 4 12:48 test-job
jenkins@jenkins:~/workspace$ ls projectA-web1/index.html
projectA-web1/index.html
jenkins@jenkins:~/workspace$ cat projectA-web1/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>qq</title>
</head>
<body>
<h1>web-02-v.111111</h1>
<h1>web-02-v.222222</h1>
<h1>web-02-v.333333</h1>
<h1>web-02-v.444444</h1>
<h1>web-02-v.555555</h1>
<h1>web-02-v.666666</h1>
<h1>web-02-v.777777</h1>
</body>
</html>
9 配置jenkins服务器与后端tomcat服务器的免密认证
- 先给后端服务器的tomcat用户设置密码
root@tomcat-1:~# passwd tomcat # 密码000000
root@tomcat-2:~# passwd tomcat # 密码000000
root@tomcat-3:~# passwd tomcat # 密码000000
- 如何tomcat服务器没有tomcat用户的家目录, 还需要手动创建
mkdir /home/tomcat
chown -R tomcat.tomcat /home/tomcat/
- 将jenkins服务器的jenkins用户的公钥拷贝到tomcat服务器
jenkins@jenkins:~$ ssh-copy-id tomcat@10.0.0.209
jenkins@jenkins:~$ ssh-copy-id tomcat@10.0.0.219
jenkins@jenkins:~$ ssh-copy-id tomcat@10.0.0.199
测试从jenkins服务器, 通过ssh远程连接到tomcat服务器, 通过tomcat用户可以正常执行命令
ssh tomcat@10.0.0.199
ssh tomcat@10.0.0.209
ssh tomcat@10.0.0.219
jenkins@jenkins:~$ ssh tomcat@10.0.0.199 "/etc/init.d/tomcat.sh restart"
正在判断Tomcat运行状态, 倒计时3秒
3
2
1
Tomcat正在运行, 进程ID为866
2647, 共2个进程
三秒后, 准备关闭Tomcat
3
2
1
已尝试关闭Tomcat, 30秒后检查是否关闭成功!
3
2
1
Tomcat正常关闭失败, 准备pkill强行关闭Tomcat进程
Tomcat关闭失败, 请检查日志!
正在判断服务状态, 请稍等!
倒计时三秒钟!
3
2
1
Tomcat正在运行!
10 jenkins部署案例
- 在jenkins上将workspace目录下的projectA-web1目录下的文件打包, 拷贝到tomcat服务器的
/data/tomcat/tomcat_webapps/myapp/
目录下
目前, 三个Tomcat服务器myapp/index.html
存放的都是自己本地的ip地址
root@tomcat-1:~# cat /data/tomcat/tomcat_webapps/myapp/index.html
10.0.0.199
- 修改jenkins项目配置
- 立即构建
jenkins@jenkins:~/workspace/projectA-web1$ ls -l
total 16
-rw-r--r-- 1 jenkins jenkins 270 Jul 14 00:04 index.html
-rw-r--r-- 1 jenkins jenkins 10240 Jul 14 00:04 myapp.tar.gz
- 修改配置, 实现拉取代码, 打包, 并且scp到后端tomcat服务器的zip目录, 用于保存tar包
cd /var/lib/jenkins/workspace/projectA-web1
tar czvf myapp.tar.gz index.html
scp myapp.tar.gz tomcat@10.0.0.199:/data/tomcat/tomcat_zip
scp myapp.tar.gz tomcat@10.0.0.209:/data/tomcat/tomcat_zip
scp myapp.tar.gz tomcat@10.0.0.219:/data/tomcat/tomcat_zip
- tomcat验证
root@tomcat-3:~# ll /data/tomcat/tomcat_zip/
total 12
drwxr-xr-x 2 tomcat tomcat 4096 Jul 4 23:13 ./
drwxr-xr-x 5 tomcat tomcat 4096 Jul 3 00:19 ../
-rw-r--r-- 1 tomcat tomcat 257 Jul 4 23:13 myapp.tar.gz
root@tomcat-2:~# ll /data/tomcat/tomcat_zip/
total 12
drwxr-xr-x 2 tomcat tomcat 4096 Jul 4 23:13 ./
drwxr-xr-x 5 tomcat tomcat 4096 Jul 3 00:19 ../
-rw-r--r-- 1 tomcat tomcat 257 Jul 4 23:13 myapp.tar.gz
root@tomcat-1:~# ll /data/tomcat/tomcat_zip/
total 12
drwxr-xr-x 2 tomcat tomcat 4096 Jul 4 23:10 ./
drwxr-xr-x 5 tomcat tomcat 4096 Jul 3 00:19 ../
-rw-r--r-- 1 tomcat tomcat 257 Jul 4 23:13 myapp.tar.gz
- 再次修改构建, 实现拷贝到tomcat服务器后, 通过jenkins去停止tomcat服务
cd /var/lib/jenkins/workspace/projectA-web1
tar czvf myapp.tar.gz index.html
scp myapp.tar.gz tomcat@10.0.0.199:/data/tomcat/tomcat_zip
scp myapp.tar.gz tomcat@10.0.0.209:/data/tomcat/tomcat_zip
scp myapp.tar.gz tomcat@10.0.0.219:/data/tomcat/tomcat_zip
ssh tomcat@10.0.0.199 "/etc/init.d/tomcat.sh stop"
ssh tomcat@10.0.0.209 "/etc/init.d/tomcat.sh stop"
ssh tomcat@10.0.0.219 "/etc/init.d/tomcat.sh stop"
- 修改构建, 停止服务后, 把
/data/tomcat/tomcat_zip
目录下的项目tar包, 解压到/data/tomcat/tomcat_file
的项目目录下, 这里为/data/tomcat/tomcat_file/myapp
. 由于我们在/data/tomcat/tomcat_webapps
目录下配置了项目的软链接, 因此, 解压后再启动tomcat, 就可以完成代码升级
cd /var/lib/jenkins/workspace/projectA-web1
tar czvf myapp.tar.gz index.html
scp myapp.tar.gz tomcat@10.0.0.199:/data/tomcat/tomcat_zip
scp myapp.tar.gz tomcat@10.0.0.209:/data/tomcat/tomcat_zip
scp myapp.tar.gz tomcat@10.0.0.219:/data/tomcat/tomcat_zip
ssh tomcat@10.0.0.199 "/etc/init.d/tomcat.sh stop"
ssh tomcat@10.0.0.209 "/etc/init.d/tomcat.sh stop"
ssh tomcat@10.0.0.219 "/etc/init.d/tomcat.sh stop"
ssh tomcat@10.0.0.199 "tar xvf /data/tomcat/tomcat_zip/myapp.tar.gz -C /data/tomcat/tomcat_file/myapp/"
ssh tomcat@10.0.0.209 "tar xvf /data/tomcat/tomcat_zip/myapp.tar.gz -C /data/tomcat/tomcat_file/myapp/"
ssh tomcat@10.0.0.219 "tar xvf /data/tomcat/tomcat_zip/myapp.tar.gz -C /data/tomcat/tomcat_file/myapp/"
ssh tomcat@10.0.0.199 "/etc/init.d/tomcat.sh start"
ssh tomcat@10.0.0.209 "/etc/init.d/tomcat.sh start"
ssh tomcat@10.0.0.219 "/etc/init.d/tomcat.sh start"
- 当前版本为v76, 客户端提交v7
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>qq</title>
</head>
<body>
<h1>web-02-v.111111</h1>
<h1>web-02-v.222222</h1>
<h1>web-02-v.333333</h1>
<h1>web-02-v.444444</h1>
<h1>web-02-v.555555</h1>
<h1>web-02-v.666666</h1>
<h1>web-02-v.777777</h1>
</body>
</html>
- 测试构建