10. Jenkins及代码部署简单示例

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语句, 修改生产环境的数据库

  1. 蓝绿部署对于用户是无感知的, 即使升级时, 有用户正在连接, 那么当前用户会继续连接旧版本的服务器, 而新的用户会被LB调度到新版本的服务器上去. 并且配合session共享, 可以保证用户信息不会丢失. 最终达到无感知更新

图示:

图片.png

4.2 金丝雀发布

金丝雀发布也叫灰度发布,是指在黑与白之间,能够平滑过渡的一种发布方式,灰度发布是增量发布的一种类型,灰度发布是在原有版本可用的情况下,同时部署一个新版本应用作为“金丝雀”(小白鼠),测试新版本的性能和表现,以保障整体系统稳定的情况下,尽早发现、调整问题。

金丝雀发布、灰度发布步骤组成:

1、准备好部署各个阶段的工件,包括:构建工件,测试脚本,配置文件和部署清单文件。
2、从负载均衡列表中移除掉“金丝雀”服务器。
3、升级“金丝雀”应用(排掉原有流量并进行部署)。
4、对应用进行自动化测试。
5、将“金丝雀”服务器重新添加到负载均衡列表中(连通性和健康检查)。
6、如果“金丝雀”在线使用测试成功,升级剩余的其他服务器。(否则就回滚)

灰度发布可以保证整体系统的稳定,在初始灰度的时候就可以发现、调整问题,以保证其影响度。

灰度发布/金丝雀部署适用的场景:

1、不停止老版本,额外搞一套新版本,不同版本应用共存。
2、灰度发布中,常常按照用户设置路由权重,例如 90%的用户维持使用老版本,10%的用户尝鲜新版本。
3、经常与 A/B 测试一起使用,用于测试选择多种方案。

图片.png

4.3 滚动发布

滚动发布,一般是取出一个或者多个服务器停止服务,执行更新,并重新将其投入使用。周而复始,直到集群中所有的实例都更新成新版本。使用滚动升级需要确保代码没有问题, 因为滚动升级是分批次轮流升级完所有的服务器, 并不会有升级后的测试时间

升级步骤:

每个批次的服务器都是执行相同的步骤, 整个过程没有停顿, 一次性升级完所有的服务器

1. 将服务器从负载均衡器摘除
2. reload负载均衡器
3. 停止服务
4. 部署新的代码, 重启服务
5. 添加到负载均衡器
6. reload负载均衡器

4.4 A/B 测试

A/B 测试也是同时运行两个 APP 环境,但与蓝绿部署完全是两码事,A/B 测试是用来测试应用功能表现的方法,例如可用性、受欢迎程度、可见性等等,蓝绿部署的目的是安全稳定地发布新版本应用,并在必要时回滚,即蓝绿部署是一套正式环境环境在线,而A/B 测试是两套正式环境在线。

图片.png

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 测试访问

  • 轮训访问
图片.png
图片.png
图片.png

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 访问页面, 填写密码

图片.png

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; } # 使用清华源去装
图片.png
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 创建管理员用户

图片.png

6.8 实例配置

  • 保存默认即可
图片.png

6.9 插件管理

图片.png
图片.png

图片.png
  • 安装gitlab插件, 让jenkins通过gitlab拉取代码
图片.png
  • 选择安装完成后, 重启jenkins. 如果jenkins没有正在执行构建任务, 那么可以安装插件后, 直接重启jenkins
图片.png

6.10 测试项目

  • 部署一个自由风格软件项目
图片.png
图片.png
  • jenkins每个项目的构建有多个自定义的步骤, 这里先构建一个最简单的任务, 显示当前用户和工作目录
图片.png
图片.png
图片.png
图片.png
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 插件安装

图片.png

7.2 项目, 角色以及用户创建

  • 创建两个用户, 分别管理两个项目, 每个项目包含2个job
tom 项目A
    包含job: projectA-web1
             projectA-web2

jerry 项目B
      包含job: projectB-web1
             projectB-web2

创建项目

图片.png
图片.png
图片.png
图片.png
图片.png
图片.png
图片.png
图片.png
图片.png

创建用户

图片.png
图片.png
图片.png

图片.png

图片.png
图片.png

用户授权修改

默认情况, 创建的用户和管理员具有相同的权限, 可以对Jenkins进行配置管理, 用户管理和项目管理, 因此, 要对普通用户进行单独的授权

图片.png
图片.png
图片.png
  • 修改授权策略为Role-Based Strategy
图片.png
  • 修改后点击保存, 用创建的普通账户登录Jenkins, 验证修改生效
图片.png

创建角色

图片.png
图片.png
  • 管理员会有全部的权限
图片.png
  • 普通用户只需要Item roles

项目A的角色创建

图片.png

Role to add: 角色名称
Pattern: 基于正则表达式, 匹配项目中的job的名称

图片.png

项目B的角色创建

图片.png
  • 此外, 还需要在Global Roles中创建只有全局读的角色, 并且关联相应的用户, 否则用户登陆Jenkins是看不到界面内容的. Global Roles和Item Roles是独立的权限, 因此要单独创建角色
图片.png

将用户和角色关联

图片.png
图片.png
  • tom用户关联到projectA角色
  • jerry用户关联到projectB角色
图片.png
  • tom用户关联到projectA-readonly角色
  • jerry用户关联到projectB-readonly角色
图片.png
  • 登陆tom和jerry用户, 验证授权成功
图片.png
图片.png
  • 测试tom和jerry用户, 可以分别对projectA和projectB中的项目进行构建
Jenkins界面的操作权限是针对Jenkins的, 与Linux服务器上的权限无关
至于构建时执行的shell脚本中所需的权限, 还需要单独配置

7.3 Jenkins邮件通知

Jenkins配置邮件通知,当构建任务结束后, 把结果发给用户, 需要通过admin账户配置

启动邮件功能, 需要jenkins可以上外网, 否则无法连接smtp服务器

图片.png
图片.png
图片.png
图片.png
图片.png

8 配置Jenkins基于ssh秘钥从gitlab拉取代码

从gitlab拉取代码需要提交用户名和密码, 或者使用ssh的方式, 把服务器的公钥添加到gitlab上
一般的拉取代码可以基于用户名和密码, 而jenkins需要通过ssh key进行非交互式拉取代码

  • 以projectA-web1为例, 其对应gitlab上的创建的qq/web-02
图片.png
图片.png

git@10.0.0.239:qq/web-02.git

这时因为没有做免密认证, 因此会报错, 需要添加jenkins凭据

图片.png
图片.png

在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, 将公钥添加到管理员账户下

图片.png

添加公钥后, 在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-----
图片.png

添加后选择新添加的凭据, 然后添加构建shell命令, 点击保存

图片.png
图片.png

测试构建

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项目配置
图片.png
  • 立即构建
图片.png
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"
图片.png
  • 修改构建, 停止服务后, 把/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>

  • 测试构建
图片.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容