袁党生博客

  • 主页
  • linux基础
  • SHELL
  • WEB
  • 负载
  • 企业级应用
  • 数据库
  • KVM
  • Docker
  • K8S
  • 监控
  • 存储
  • 博客搭建问题
  1. 首页
  2. 企业级应用
  3. 正文

Gitlab+Jenkins实现代码自动化部署

2020年8月31日 7700点热度 0人点赞 0条评论


本章概要

  • Gitlab安装
  • 部署web服务器环境
  • 负载均衡配置
  • 配置Jenkins
  • 代码质量管理SonarQube
  • 通过脚本实现代码自动化部署

前言

  • 本章节内容主要是通过Gitlab+Jenkins实现代码的自动化部署
  • 总体环境构建思路:
     1、运维人员从gitlab拉取代码在本地修改
     2、修改完毕后上传gitlab仓库
     3、在jenkins上通过脚本自动化clone代码到本地,然后再复制到后端web服务器
     4、实现代码的自动化部署
     注意:部署负载均衡一是切合实际生产环境,二是方便后期实验时查看后端服务器在线状态

配置环境:

主机1:192.168.40.140 jenkins   
主机2:192.168.40.141 gitlab       注意:要求该主机内存4G以上,实验环境3G内存也可以
主机3:192.168.40.142  web服务(tomcat)
主机4:192.168.40.143  web服务(tomcat)
主机5:192.168.40.144  负载均衡(keepalived+haproxy)
主机6:192.168.40.145  负载均衡(keepalived+haproxy)

1、Gitlab安装

1.1 Gitlab安装即相关配置

  • gitlab网站:gitlab.com
  • gitlab在yum仓库默认没有,需要下载安装才能使用
  • 安装包下载地址:https://packages.gitlab.com/gitlab/gitlab-ce
  • rpm包国内下载地址:https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/yum/
  • 本次实验使用安装包版本:gitlab-ce-11.6.5-ce.0.el7.x86_64.rpm

具体安装步骤如下所示:

1、把gitlab安装包上传到gitlab主机/usr/local/src目录下
2、如果是最小化服务器安装,配置如下:
# yum -y install vim gcc gcc-c++ wget net-tools lrzsz iotop lsof iotop bash-completion curl policycoreutils openssh-server openssh-clients postfix
# wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo
# systemctl disable firewalld
# sed -i '/SELINUX/s/enforcing/disabled/' /etc/sysconfig/selinux
# hostnamectl set-hostname gitlab.example.com
# reboot
gitlab
cd /usr/local/src
yum -y install gitlab-ce-11.6.5-ce.0.el7.x86_64.rpm
注意:gitlab文件集中存放在/opt目录下,为了确保数据安全,可以把该目录挂载到存储上

ab配置文件路径:/etc/gitlab/gitlab.rb
vim  /etc/gitlab/gitlab.rb
#暴露gitlab的url路径
external_url 'http://192.168.40.141'    #gitlab url访问路径
#设置邮件服务,当gitlab发生变动时会自动发送邮件给用户,如不需要则无需配置
gitlab_rails['smtp_enable'] = true    #开启邮件
gitlab_rails['smtp_address'] = "smtp.qq.com"   #邮箱地址
gitlab_rails['smtp_port'] = 25               #邮件服务端口
gitlab_rails['smtp_user_name'] = "XXX@qq.com"   #邮箱用户名
gitlab_rails['smtp_password'] = "XXXX"            #邮箱密码
gitlab_rails['smtp_domain'] = "qq.com"          #邮箱域名
gitlab_rails['smtp_authentication'] = :login   #邮箱认证方式
gitlab_rails['smtp_enable_starttls_auto'] = true   
gitlab_rails['smtp_tls'] = false
gitlab_rails['gitlab_email_from'] = "XXX@qq.com" #向谁发送邮件
user["git_user_email"] = "XXXX@qq.com"     #git用户邮箱
注意:上面的邮箱和密码已XXX代替,可配置为自己的邮箱并设置密码
  • 常用命令:
    gitlab-ctl
     start 第一次启动gitlab
     stop 关闭gitlab
     restart 重启gitlab
     status 查看gitlab状态
     reconfigure 修改配置文件后,使用此命令使配置文件生效
  • 启动gitlab
     gitlab-ctl reconfigure
     由于启动之前更改了配置文件,因此使用reconfigure命令
     注意:启动gitlab时,会启动很多端口,因此gitlab主机尽量不要安装其他服务,避免端口冲突,gitlab默认端口为80端口.
  • 查看gitlab状态

示例:

[root@gitlab ~]# gitlab-ctl status
run: alertmanager: (pid 27629) 957280s; run: log: (pid 12279) 962567s
run: gitaly: (pid 12070) 962578s; run: log: (pid 12088) 962578s
run: gitlab-monitor: (pid 12130) 962576s; run: log: (pid 12136) 962576s
run: gitlab-workhorse: (pid 12022) 962580s; run: log: (pid 12038) 962580s
run: logrotate: (pid 113134) 379s; run: log: (pid 12059) 962579s
run: nginx: (pid 9831) 962968s; run: log: (pid 12045) 962579s
run: node-exporter: (pid 12106) 962577s; run: log: (pid 12114) 962577s
run: postgres-exporter: (pid 12300) 962567s; run: log: (pid 12313) 962567s
run: postgresql: (pid 8926) 963074s; run: log: (pid 11983) 962582s
run: prometheus: (pid 12172) 962574s; run: log: (pid 12248) 962569s
run: redis: (pid 8780) 963085s; run: log: (pid 11976) 962583s
run: redis-exporter: (pid 12147) 962575s; run: log: (pid 12161) 962575s
run: sidekiq: (pid 26870) 957390s; run: log: (pid 12010) 962581s
run: unicorn: (pid 27989) 957221s; run: log: (pid 11997) 962582s
[root@gitlab ~]# lsof -i:80
COMMAND  PID       USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
nginx   9831       root    7u  IPv4  40191      0t0  TCP *:http (LISTEN)
nginx   9832 gitlab-www    7u  IPv4  40191      0t0  TCP *:http (LISTEN)
nginx   9833 gitlab-www    7u  IPv4  40191      0t0  TCP *:http (LISTEN)

1.2 gitlab网站配置

gitlab网站基础配置

  • 登陆gitlab网站web界面
    第一次登陆需要更改密码

  • 更改密码后登陆gitlab
    默认用户名为root

    Gitlab默认首页

  • 关闭注册功能
    gitlab网站默认具有注册功能,该功能对外开放不安全,因此需要关闭
    默认存在注册功能

    关闭步骤:1.使用root登陆,点击扳手图标
    2.在左侧导航栏点击settings
    3.找到Sign-up restrictions
    4.点击右侧Expand
    5.把Sign-up enabled前的对勾去掉
    6.点击下方Save Changes

    关闭后再次登陆,已经关闭注册功能

gitlab用户、组、项目配置

  • 创建用户
    第一次使用新账户登陆需要重新设置密码
    通过邮件重新设置密码,在登录界面点击忘记密码
    创建用户

    点击“忘记密码”,通过邮件重置密码

    点击链接跳转到下一页,更改密码

    更改密码

  • 创建组

  • 创建项目

  • 把用户添加到组
    选择组

    把用户加入到组

  • 创建测试页
    选择项目

    找到项目界面

    创建测试页

    添加测试页内容

  • 在客户端clone项目:

  • 在这里我们先在github上clone项目

示例:

要想通过url下载,可以使用git命令,该命令需要安装才能使用
    yum install git
第一次下载使用 git clone  项目url
    git clone https://github.com/zhangshijle/demo.git
会在当前目录下自动创建以项目名称为名的目录
    [root@centos7 demo]# cd /root/demo/
    [root@centos7 demo]# ls
    index.html  LICENSE  readme.txt
更改文件并上传
    [root@centos7 demo]# pwd
    /root/demo
    [root@centos7 demo]# git add .    #把当前目录下所有文件或某个文件提交到暂存区
    [root@centos7 demo]# git commit -m "v4"   #把暂存区文件提到本地仓库并添加注释信息"v4"
    [root@centos7 demo]# git push   #把本地仓库的文件上传到中央仓库,即github官网仓库
    注意:此时会提示输入账户名和密码进行验证
  • gitlab站点文件的上传下载步骤与github站点基本一致

示例:

gitlab站点url使用ssh协议和http协议,这里用http协议
clone项目
    [root@centos7 ~]# git clone http://192.168.40.141/group1/project1.git
    Cloning into 'project1'...
    Username for 'http://192.168.40.141': test   #输入用户名
    Password for 'http://test@192.168.40.141':   #输入密码
    remote: Enumerating objects: 3, done.
    remote: Counting objects: 100% (3/3), done.
    remote: Total 3 (delta 0), reused 0 (delta 0)
    Unpacking objects: 100% (3/3), done.
查看clone下来的文件
    [root@centos7 ~]# cd project1/
    [root@centos7 project1]# ll
    total 4
    -rw-r--r-- 1 root root 28 May 23 10:47 README.md
添加新内容
    [root@centos7 project1]# vim README.md
    <h1>This is Testpage</h1>
    <h1>This is Testpage02</h1>         #添加新内容
提交到gitlab
    [root@centos7 project1]# git add README.md   #提交到暂存区
    [root@centos7 project1]# git commit -m "v1"     #提交至本地仓库
    *** Please tell me who you are.
    Run
      git config --global user.email "you@example.com"
      git config --global user.name "Your Name"
    to set your account's default identity.
    Omit --global to set the identity only in this repository.
    fatal: unable to auto-detect email address (got 'root@centos7.(none)')
注意:此时会出现以上报错信息,要添加邮箱和用户。但该邮箱和用户并不是做认证用的,而是一个格式验证,因此可以不用填写真是邮箱和用户
方法1:使用命令添加
    git config --global user.email "XXX@qq.com"
    git config --global user.name "XXX"
方法2:更改配置文件
    [root@centos7 project1]# cd .git
    [root@centos7 .git]# pwd
    /root/project1/.git
    [root@centos7 .git]# vim config     #在配置文件中添加以下内容
    [user]
            name = "XXX"
            email = "XXX@qq.com"
    [root@centos7 project1]# git commit -m "v1.1"
    [master d6045e3] v1.1
     1 file changed, 3 insertions(+), 1 deletion(-)
    [root@centos7 project1]# git push
    Username for 'http://192.168.40.141': jack            #输入用户名
    Password for 'http://jack@192.168.40.141':            #输入密码
    Counting objects: 5, done.
    Writing objects: 100% (3/3), 251 bytes | 0 bytes/s, done.
    Total 3 (delta 0), reused 0 (delta 0)
    To http://192.168.40.141/group1/project1.git
       87ae42c..d6045e3  master -> master
在gitlab服务端web界面确认文件是否已经上传成功
此时,就可以上传其他文件到gitlab客户端

在gitlab的web端查看index.html内容进行验证

  • 项目分支branch

下载文件时指定分支

删除原有的/root/project1目录重新下载指定分支的项目文件
    rm -rf /root/project1
    git clone -b develop http://192.168.40.141/group1/project1.git
    使用-b选项制定分支名称
下载后更改文件内容再上传到gitlab上
    [root@centos7 project1]# cd /root/project1
    [root@centos7 project1]# vim index.html
    <h1>This is Testpage</h1>
    <h1>This is Testpage02</h1>
    [root@centos7 project1]git add index.html
    [root@centos7 project1]git commit -m "v2"
    [root@centos7 project1]git config --global user.email "XXX@qq.com"     #使用命令方式设置邮箱
    [root@centos7 project1]git config --global user.name "XX"  #使用命令方式设置用户名
    [root@centos7 project1]git commit -m "v2"
    [root@centos7 project1]git push
在gitlab服务端web界面查看文件内容进行验证
  • git常用命令
     注意:以下命令要在项目所在目录下执行  
     git config --global user.name "name" #设置全局用户名   
     git config --global user.email xxx@xx.com #设置全局邮箱  
     git config --global --list #列出用户全局设置  
     git add index.html / . #添加指定文件、目录或当前目录下所有数据到暂存区  
     git commit -m "11" #提交文件到工作区   
     git status #查看工作区的状态   
     git push #提交代码到服务器   
     git pull #获取代码到本地,用于更新代码  
     git log #查看操作日志  
     vim .gitignore #定义忽略文件  
     git reset --hard HEAD^^ #git版本回滚, HEAD为当前版本,加一个^为上一个,^^为上上一个版本。回滚操作仅限于客户端,服务端并不受影响  
     git reflog # #获取每次提交的ID,可以使用--hard根据提交的ID进行版本回退  
     git reset --hard 5ae4b06 #回退到指定id的版本  
     git branch #查看当前所处的分支  
     git checkout  -b develop #创建并切换到一个新分支  
     git checkout   develop #切换分支

2、部署web服务器环境

2.1 部署web服务器环境

  • 配置环境:
    主机1:192.168.40.140 jenkins
    主机2:192.168.40.141 gitlab 注意:要求该主机内存4G以上,实验环境3G内存也可以
    主机3:192.168.40.142 web服务(tomcat)
    主机4:192.168.40.143 web服务(tomcat)
    主机5:192.168.40.144 负载均衡(keepalived+haproxy)
    主机6:192.168.40.145 负载均衡(keepalived+haproxy)
  • 部署tomcat作为后端web应用

路径规划:

web应用路径      /apps
数据代码路径      /data/{nginx,tomcat}/{logs,webapps,html}
另外,为了确保安全,运行web应用时,要以普通用户运行
分别在两台web服务器192.168.40.142和192.168.40.143上进行以下配置
[root@centos7 ~]# mkdir /apps  
[root@centos7 ~]# useradd -u 2001 tomcat
[root@centos7 ~]# passwd tomcat
  • 具体配置如下:

1、环境准备

首先安装jdk
两种安装方式:使用rpm包安装和tar包安装
(1)在oracle官网下载jdk并上传到两台web服务器上
jdk tar包下载地址:https://download.oracle.com/otn/java/jdk/8u202-b08/1961070e4c9b4e26a04e7f5a083f551e/jdk-8u202-linux-x64.tar.gz
在两台服务器192.168.40.142和192.168.40.143上分别安装jdk,进行以下操作
(2)解压tar包
cd /root
mv jdk-8u192-linux-x64.tar.gz /apps
(3)设置环境变量

vim /etc/profile         #在文件最后写入以下内容
export HISTTIMEFORMAT="%F %T `whoami` "
export export LANG="en_US.utf-8"
export JAVA_HOME=/apps/jdk
export CLASSPATH=.:$JAVA_HOME/jre/lib/rt.jar:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
export PATH=$PATH:$JAVA_HOME/bin

(4)便于后期版本升级,对jdk做软连接
ln -sv /apps/jdk1.8.0_192/ /apps/jdk
(5)运行/etc/profile使环境变量生效
source /etc/profile
(6)运行命令查看java版本
java -version

2、tomcat安装
(1)下载tomcat软件包上传web服务器/apps目录下
tomcat下载地址:https://archive.apache.org/dist/tomcat/tomcat-8/v8.5.37/bin/apache-tomcat-8.5.37.tar.gz
(2)解压并对tomcat做软链接,便于版本更新
cd /apps
tar -xvf apache-tomcat-8.5.37.tar.gz
ln -sv /apps/apache-tomcat-8.5.37 /apps/tomcat
(3)手动测试tomcat是否能够启动
/apps/tomcat/bin/catalina.sh start
(4)启用后使用浏览器访问tomcat web页面进行验证,浏览器访问以下页面
192.168.40.142:8080
192.168.40.143:8080

3、tomcat网页文件配置
以上步骤完成后,web应用已经部署成功,下面开始进行代码的相关配置
(1)在两台服务器192.168.40.142进而192.168.40.143上分别创建数据目录
mkdir /data/tomcat_webdir -pv
mkdir /data/tomcat_appdir -pv
(2)配置tomcat网页路径,并关闭自动解压和自动部署
vim /apps/tomcat/conf/server.xml

    <Host name="localhost"  appBase="/data/tomcat_webdir"
        unpackWARs="false" autoDeploy="false">

(3)创建网页文件
注意:为了后期测试便于区分,两台主机上的网页内容要加以区分
cd /data/tomcat_webdir
mkdir myapp
在192.168.40.142上:

vim myapp/index.html
<h1>This is Testpage-01!</h1>

在192.168.40.143上:

vim myapp/index.html
<h1>This is Testpage-02!</h1>

以下内容两台主机保持一致
(4)手动重启tomcat查看新建的网页
/apps/tomcat/bin/catalina.sh stop
/apps/tomcat/bin/catalina.sh start
tomcat排错可以查看错误日志catalina.out或运行/apps/tomcat/bin/catalina.sh run命令
(5)使用浏览器访问web界面
http://192.168.40.142:8080/myapp/
http://192.168.40.143:8080/myapp/

4、启动tomcat服务
如果能够正常访问,那么接下来就以普通用户启动tomcat
/apps/tomcat/bin/catalina.sh stop #关闭tomcat
chown tomcat.tomcat /data/ /apps/ -R #对普通用户授权
(1)使用脚本启动tomcat,脚本内容如下:

#vim tomcat
JDK_HOME=/apps/jdk
CATALINA_HOME=/apps/tomcat
export JDK_HOME CATALINA_HOME
source /etc/profile
#PID=`ps -ef  | grep  -v grep  | grep java | awk  '{print $2}'`
#NUM=`ps -ef  | grep  -v grep  | grep java | awk  '{print $2}' | wc -l`
#case $1 in
start() {
        echo "正在判断服务状态,请稍等!"  
        echo "请稍等3秒钟"
        echo "3";sleep 1;echo "2";sleep 1;echo "1";sleep 1
    if  netstat -an | grep 8080 | grep LISTEN >/dev/null
        then
        echo "Tomcat已经正在运行了!"  
    else 
        echo "Tomcat没有运行,1秒后启动!"
        echo 1;sleep 1  
        $CATALINA_HOME/bin/catalina.sh start 
        echo  "Tomcat 已经成功启动完成,5秒后判断是否启动成功"
        echo "5";sleep 1;echo "4";sleep 1
        echo "3";sleep 1;echo "2";sleep 1;echo "1";sleep 1
    if  netstat -an | grep 8080 | grep LISTEN >/dev/null
        then
        PID=`ps -ef | grep  tomcat | grep jdk | awk '{print $2}'`
        NUM=`ps -ef | grep  tomcat | grep jdk | awk '{print $2}' | wc -l`
        echo "Tomcat 已经成功启动${NUM} 个Tomcat进程!,PID为${PID}"
        else
        echo "Tomcat启动失败,请重新启动!"
            echo 1
    fi
    fi
    }
stop() {
        PID=`ps -ef  | grep  -v grep  | grep java | awk  '{print $2}'`
        NUM=`ps -ef | grep  -v "color"  | grep tomcat | awk '{print $2}' | wc -l`
        echo "正在判断服务状态,请稍等3秒钟!"   
        echo "3";sleep 1;echo "2";sleep 1;echo "1";sleep 1
    if  netstat -an | grep 8080 | grep LISTEN >/dev/null 
       then 
        echo "Tomcat运行中,1秒后关闭!"
        echo  1;sleep 1 
        echo "即将关闭Tomcat服务,请稍等!" 
        $CATALINA_HOME/bin/catalina.sh stop ;echo "已经执行关闭命令,正在检查关闭了多少Tomcat进程,请稍等30秒钟!"
        sleep 5
        echo "3";sleep 1;echo "2";sleep 1;echo "1";sleep 1
        pkill java && pkill tomcat
        if  netstat -an | grep 8080 | grep LISTEN >/dev/null;then
            PID=`ps -ef  | grep  -v grep  | grep java | awk  '{print $2}'`
            NUM=`ps -ef | grep  -v "color"  | grep tomcat | awk '{print $2}' | wc -l`
            kill -9 $PID ;echo "已成功关闭${NUM} 个tomcat进程"
        else
            echo  "Tomcat 已经关闭完成!" 
            echo "3";sleep 1;echo "2";sleep 1;echo "1";sleep 1 
        fi
    else
        echo "Tomcat 没有运行"
        echo 1
    fi
    if  netstat -an | grep 8080 | grep LISTEN >/dev/null;then
            PID=`ps -ef  | grep  -v grep  | grep java | awk  '{print $2}'`
            #NUM=`ps -ef | grep  -v "color"  | grep tomcat | awk '{print $2}' | wc -l`
            echo "关闭失败,即将强制删除tomcat进程!"
            sleep 2
            pkill tomcat ;sleep 2 
            if  netstat -an | grep 8080 | grep LISTEN >/dev/null;then
                echo "强制关闭失败,即将再次强制删除tomcat进程!"
                pkill java; sleep 2
            fi
    fi
    }
restart() {
    stop 
    start 
 }
case "$1" in 
start) 
start 
;; 
stop) 
stop 
;; 
restart) 
restart 
;; 
*) 
echo $"Usage: $0 {start|stop|restart|status}" 
esac

(2)把脚本复制到系统启动目录下,并授予执行权限
cp tomcat /etc/init.d/
chmod a+x /etc/init.d/tomcat
(3)切换到普通用户进行测试
su - tomcat
(4)启动tomcat
[tomcat@centos7 ~]$ /etc/init.d/tomcat start
以上步骤完成后,web服务器配置已经完成,接下来配置负载均衡

3、负载均衡配置

3.1 部署高可用keepalived

  • 在两台服务器上进行以下配置

安装keepalived和haproxy
 yum -y install keepalived haproxy
由于keepalived高可用分为主从两个节点,因此其配置文件要分别配置
(1)在主节点192.168.40.144上:
配置keepalived、

vim /etc/keepalived/keepalived.conf
vrrp_instance VI_1 {
    state MASTER       #配置状态为主
    interface ens33
    virtual_router_id 51
    priority 100
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    virtual_ipaddress {
        192.168.40.100 dev ens33 label ens33:1    #配置vip地址为192.168.40.100
    }
}

启动keepalived服务,查看vip地址是否启用
systemctl start keepalived && ifconfig
systemctl enable keepalived

(2)在从节点192.168.40.145上:
配置keepalived

vim /etc/keepalived/keepalived.conf
vrrp_instance VI_1 {
    state BACKUP           #配置状态为备用
    interface ens33
    virtual_router_id 51
    priority 90      #优先级设置为90
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    virtual_ipaddress {
        192.168.40.100 dev ens33 label ens33:1    #配置vip地址为192.168.40.100
    }
}

(3)启动keepalived服务,查看vip地址是否启用
systemctl start keepalived && ifconfig
systemctl enable keepalived
注意:由于此时主节点为192.168.40.144,并且主节点无故障,因此vip地址绑定在主节点上,从节点192.168.40.145上查看不到vip地址

(4)接下来对keepalived进行测试验证,如果主节点出现故障,vip地址是否会自动漂移
在主节点192.168.40.144上关闭keepalived服务,然后在从节点上查看是否存在vip地址
如果确认keepalived在主节点出现故障的情况下自动转移vip地址,说明配置成功,恢复主节点keepalived服务

3.2 配置负载均衡

  • 配置haproxy

在两台服务器192.168.40.144和192.168.40.145上进行以下配置

(1)配置haproxy配置文件

vim /etc/haproxy/haproxy.cfg
#启动haproxy状态页,便于查看后端web服务器状态
listen stats
mode http
bind 0.0.0.0:9999
stats enable
log global
stats uri /haproxy-status
stats auth admin:admin      #设置状态页认证用户名和密码
删除default配置段以后所有内容,添加一下内容
#门户网站访问入口
listen myapp
bind 192.168.40.100:80
balance     roundrobin
server  192.168.40.142 192.168.40.142:8080 check
server  192.168.40.143 192.168.40.143:8080 check

(2)启动haproxy服务
systemctl start haproxy
注意:此时会提示haproxy无法启动,这是因为需要调整一些内核参数只需把sysctl.conf文件导入即可修复,文件内容如下:

vim 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

# TCP kernel paramater
net.ipv4.tcp_mem = 786432 1048576 1572864
net.ipv4.tcp_rmem = 4096        87380   4194304
net.ipv4.tcp_wmem = 4096        16384   4194304
net.ipv4.tcp_window_scaling = 1
net.ipv4.tcp_sack = 1

# socket buffer
net.core.wmem_default = 8388608
net.core.rmem_default = 8388608
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.core.netdev_max_backlog = 262144
net.core.somaxconn = 20480
net.core.optmem_max = 81920

# TCP conn
net.ipv4.tcp_max_syn_backlog = 262144
net.ipv4.tcp_syn_retries = 3
net.ipv4.tcp_retries1 = 3
net.ipv4.tcp_retries2 = 15

# tcp conn reuse
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_fin_timeout = 1

net.ipv4.tcp_max_tw_buckets = 20000
net.ipv4.tcp_max_orphans = 3276800
net.ipv4.tcp_timestamps = 1 #?
net.ipv4.tcp_synack_retries = 1
net.ipv4.tcp_syncookies = 1

# keepalive conn
net.ipv4.tcp_keepalive_time = 300
net.ipv4.tcp_keepalive_intvl = 30
net.ipv4.tcp_keepalive_probes = 3
net.ipv4.ip_local_port_range = 10001    65000

# swap
vm.overcommit_memory = 0
vm.swappiness = 10

#net.ipv4.conf.eth1.rp_filter = 0
#net.ipv4.conf.lo.arp_ignore = 1
#net.ipv4.conf.lo.arp_announce = 2
#net.ipv4.conf.all.arp_ignore = 1
#net.ipv4.conf.all.arp_announce = 2

把sysctl.conf文件移动到/etc/下覆盖原有的sysctl.conf文件或把原有文件进行备份后进行再进行移动
读取该文件配置参数
sysctl -p
重启haproxy服务
systemctl restart haproxy

  • 访问haproxy状态页,查看后端服务器状态
     192.168.40.144:9999/haproxy-status
     192.168.40.145:9999/haproxy-status
    haproxy状态页验证界面

    haproxy状态页

  • 访问vip地址192.168.40.100:/myapp,查看是否实现负载均衡

4、配置jenkins

4.1 jenkins配置

  • jenkins具体配置步骤如下:

配置步骤如下:
(1)下载jenkins的rpm包进行安装
下载地址:https://pkg.jenkins.io/redhat-stable/jenkins-2.138.4-1.1.noarch.rpm
jenkins依赖于java,因此要先安装jdk
这次试用jdk的rpm包方式进行安装,该包可以去oracle官网进行下载
yum install jdk-8u192-linux-x64.rpm
(2)安装jenkins
yum install jenkins-2.138.4-1.1.noarch.rpm
(3)配置jenkins配置文件

vim /etc/sysconfig/jenkins
JENKINS_USER="root"                      #更改用户
JENKINS_JAVA_OPTIONS="-Djava.awt.headless=true \      #配置监控参数,便于后期添加监控
-Dcom.sun.management.jmxremote \
-Dcom.sun.management.jmxremote.port=12345 \
-Dcom.sun.management.jmxremote.authenticate=false \
-Dcom.sun.management.jmxremote.ssl=false \
-Djava.rmi.server.hostname="192.168.4.140"
"
JENKINS_DEBUG_LEVEL="5"      #日志级别默认为5,该级别越高,日志记录越详细,日志文件很快就会变大,会占用大量磁盘空间,在jenkins配置完成后,要把日志级别设置为0(关闭)或1
配置文件配置完成后具体配置情况如下:
[root@centos7 sysconfig]# grep "^[a-Z]" jenkins
JENKINS_HOME="/var/lib/jenkins"
JENKINS_JAVA_CMD=""
JENKINS_USER="root"
JENKINS_JAVA_OPTIONS="-Djava.awt.headless=true \
JENKINS_PORT="8080"
JENKINS_LISTEN_ADDRESS=""
JENKINS_HTTPS_PORT=""
JENKINS_HTTPS_KEYSTORE=""
JENKINS_HTTPS_KEYSTORE_PASSWORD=""
JENKINS_HTTPS_LISTEN_ADDRESS=""
JENKINS_DEBUG_LEVEL="5"
JENKINS_ENABLE_ACCESS_LOG="no"
JENKINS_HANDLER_MAX="100"
JENKINS_HANDLER_IDLE="20"
JENKINS_ARGS=""

启动jenkins服务
systemctl start jenkins

4.2 插件安装、邮箱配置、用户权限管理

  • jenkins插件安装

  • 需要在jenkins安装必要的插件才能实现后续配置所需的功能

  • 插件分别是:gitlab插件、Blue Ocean插件、Role-based Authorization Strategy插件
    以rpm包方式安装插件
     把插件存放到jenkins存放插件目录下,重启jenkins即可。
     jenkins插件存放路径:/var/lib/jenkins/plugins/
    在web界面安装插件
    注意:安装插件需要连接互联网进行下载
    在jenkins--系统管理--插件管理

    根据名称搜索插件

    搜索gitlab插件

    搜索blue ocean插件

    插件安装

  • 配置jenkins权限管理
    基于角色的权限管理,先创建角色和用户,给角色授权,然后把用户管理到角色
    由于jenkins默认所有用户只要登录都具有管理员权限,因此要关闭新建用户的管理员权限,这里需要用到Role-based Authorization Strategy插件
    安装role-base插件

    创建新用户
     系统管理--管理用户--创建新用户

    更改认证方式
     系统管理—全局安全配置
     默认创建的用户登录后可以做任何操作,取决于默认的认证授权方式

    创建角色
     系统管理--Manage and Assign Roles
     创建角色

     添加角色

    角色授权
     系统管理--Manage and Assign Roles--Manage Roles
     给角色授权1

     给角色授权2

    将用户关联到角色
     系统管理--Manage and Assign Roles--Assign Roles

    登录测试
     使用dev1用户登录,发现已经没有"系统管理"的选项

    jenkins邮箱配置
     生成QQ邮箱授权码,登录qq邮箱--设置--账户--POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务

    配置管理员邮箱

    发件设置
     发件设置配置-1

     发件设置配置-2

    此时如果检测失败,则需要重启jenkins服务后再次测试,如果能够收到邮件,说明配置成功

4.3 jenkins拉取代码

jenkins拉取代码

  • 创建一个project:demo-project1

  • jenkins拉取代码时要注意:如果代码存放目录存在同名的目录,拉取代码时会报错,需要勾选选项Delete workspace before build starts,在拉取代码时自动删除,或者可以手动删除同名目录

  • 如果使用shell命令部署代码,需要对jenkins服务器和gitlab服务器进行免秘钥认证,把jenkins的公钥上传到gitlab之上
    gitlab右上角--settings--SSH keys

    添加jenkins公钥

    测试:完成免秘钥认证后,不使用用户名密码即可完成代码的拉取

  • 配置免秘钥认证完成以后,再从gitlab上拉取代码就无需使用http协议,而使用ssh协议

  • 在jenkins主机上执行以下命令即可拉取代码:
     git clone git@192.168.40.141:group1/project1.git

  • 但是这只能用于clone拉取代码,不能用于提交代码

在jenkins的web界面项目的配置--构建--执行shell命令内容框里面提交该命令,可以直接拉取代码
git clone -b develop git@192.168.40.141:group1/project1.git #使用-b选项可以指定分支
代码拉取成功,接下来就是如何把代码放到后端服务器中去
我们可以使用scp命令把diamante远程复制到后端web服务器

在jenkins---demo-project1---配置---构建
增加构建步骤

添加shell命令内容

注意:使用scp复制时需要对后端web服务器做免秘钥认证,否则将不能自动把代码复制过去
在jenkins主机上做免秘钥认证
 ssh-copy-id tomcat@192.168.40.142
 ssh-copy-id tomcat@192.168.40.143
注意:复制时要复制到tomcat用户家目录下,因为后端web服务是以tomcat用户启动

免秘钥认证成功就可以构建项目,拉取并推送代码,这时需要勾选选项Delete workspace before buildstarts,即构建时自动删除之前构建的工作空间,防止出现同名目录而报错

完整步骤测试:

从gitlab拉取代码,在本地做修改,然后上传,再自动推送到后端web服务器
注意:由于需要上传,所以要以http协议clone代码,自动推送时则可以用ssh协议进行拉取代码
具体步骤:
先从Gitlab拉取代码
    git clone -b develop http://192.168.40.141/group1/project1.git
在本地更改文件内容
    cd /root/project1
    vim /root/project1/index.html
    <h1>This is Testpage</h1>
    <h1>This is Testpage02</h1>
    <h1>This is Testpage03</h1>
提交代码
    git add index.html
    git commit -m "v3"
    git push

由于之前在jenkins上配置好了自动推送代码,此时直接点击立即构建即可
在构建过程中可以查看控制台输出信息
查看构建历史

查看控制台输出信息

然后访问web服务器查看网页内容是否发生改变

  • 对jenkins上的代码进一步的完善

具体内容如下:

在复制代码之前先关闭tomcat,复制完毕以后再启动tomcat,这是因为对于java程序来说如果不重启应用程序,可能会识别不到程序代码
####
git clone -b develop git@192.168.40.141:group1/project1.git
cd /var/lib/jenkins/workspace/demo-project1/project1
ssh tomcat@192.168.40.142 "/etc/init.d/tomcat stop"
ssh tomcat@192.168.40.143 "/etc/init.d/tomcat stop"
scp index.html tomcat@192.168.40.142:/data/tomcat_webdir/myapp/
scp index.html tomcat@192.168.40.143:/data/tomcat_webdir/myapp/
ssh tomcat@192.168.40.142 "/etc/init.d/tomcat start"
ssh tomcat@192.168.40.143 "/etc/init.d/tomcat start"

注意:由于会中断tomcat,而且java应用程序启动相对较慢,因此这种方式只用于实验环境,生产环境不推荐这种做法

4.4 jenkins分布式构建

  • 在maste节点上把任务分配给slave节点运行,减轻主节点的压力
  • 在本次实验中,把负载均衡两台主机作为从节点来配置

在两台负载均衡主机上需要做以下配置:

1、配置java环境
yum -y install jdk-8u192-linux-x64.rpm
2、创建数据目录
mkdir -pv /data/jenkins/slave
3、时间必须要和master主机保持一致

在jenkins web端配置slave节点
在jenkins--系统管理--节点管理
配置slave节点

添加凭据,增加认证方式

  • slave节点创建成功后,master节点会自动连接从节点
  • 连接成功以后,按照以上步骤再次添加第二个节点192.168.40.145,即slave-node2
  • slave节点创建以后,就可以在上面运行任务,而运行任务则需要用到另外一个组件pipline

4.5 pipline

pipline配置

添加pipline

配置pipline

添加pipline脚本内容

进行保存,然后选择立即构建进行测试

pipline实现代码克隆

  • 由于使用Pipeline Syntax功能需要新的凭据,因此在配置之前要创建新的凭据
  • 该凭据需要在demo-project1配置中创建新的凭据

注意:把jenkins的私钥存放在这里的原因是,jenkins的公钥在之前已经存放到gitlab之上,这样jenkins就有权限从gitlab上拉取代码

配置完成后返回demo-project1项目中根据新建的凭据重新配置构建信息

到这里,已经配置完毕,保存即可
然后点击立即构建,进行测试能否把代码拉取下来

在pipline-test流水线配置中,Pipeline Syntax选项可以把具体操作转换为pipline语句,然后把pipline语句写入流水线pipline-test完成代码的自动部署。
在新的窗口打开进入pipline-test流水线配置页面

配置相关操作并转换为代码

然后在浏览器新的窗口中,打开流水线pipline-test配置,重新配置pipline脚本

这样一来,代码克隆就完成了,剩下的就是如何把代码推送到生产或测试环境中

  • 在pipline-test流水线脚本中添加打包命令

配置pipline-test脚本,对脚本内容进行更改,内容如下:

node {
    stage("代码克隆"){
        echo '代码克隆'
        sh 'rm -rf /var/lib/jenkins/workspace/pipline-test/*'     #为了防止目录冲突,使用shell命令删除之前拉取代码时所创建目录下的文件
        git branch: 'develop', credentialsId: 'cbee689f-71f7-492f-adaf-c21e2dd1e51e', url: 'git@192.168.40.141:group1/project1.git'   #此处为代码克隆脚本,即刚才生成的代码
        sh 'cd /var/lib/jenkins/workspace/pipline-test && zip -r code.zip ./*'   #切换到代码目录下,把代码进行打包
    }
    stage("代码复制"){              #把代码复制到后端web服务器专门用于存放代码压缩包的目录下
        echo '代码复制'
        sh 'cd /var/lib/jenkins/workspace/pipline-test && scp code.zip tomcat@192.168.40.142:/data/tomcat_appdir'
        sh 'cd /var/lib/jenkins/workspace/pipline-test && scp code.zip tomcat@192.168.40.143:/data/tomcat_appdir'
    }
    stage("停止tomcat服务"){            #关闭应用程序
        echo '停止tomcat服务'
        sh "ssh tomcat@192.168.40.142 '/etc/init.d/tomcat stop'"
        sh "ssh tomcat@192.168.40.143 '/etc/init.d/tomcat stop'"
    }
    stage("代码解压"){                #删除之前的文件并把压缩包解压到web服务器网页文件存放目录
        echo '代码复制'
        sh "ssh tomcat@192.168.40.142 'rm -rf /data/tomcat_webdir/myapp/* && unzip /data/tomcat_appdir/code.zip -d /data/tomcat_webdir/myapp'"
        sh "ssh tomcat@192.168.40.143 'rm -rf /data/tomcat_webdir/myapp/* && unzip /data/tomcat_appdir/code.zip -d /data/tomcat_webdir/myapp'"

    }
    stage("启动tomcat服务"){        #启动web服务
        echo '启动tomcat服务'
        sh "ssh tomcat@192.168.40.142 '/etc/init.d/tomcat start'"
        sh "ssh tomcat@192.168.40.143 '/etc/init.d/tomcat start'"
    }
}

访问网页进行检测是否成功

  • 在pipline-test流水线脚本中指定job任务在slave节点上运行

更改pipline脚本内容如下:

node('slave-node1'){        #指定任务运行在哪个上,就把哪个slave节点的标签填写到括号引号内,这里slave1节点的标签为slave-node1
    stage("代码克隆"){
        echo '代码克隆'
        sh 'rm -rf /var/lib/jenkins/workspace/pipline-test/*'
        git branch: 'develop', credentialsId: 'cbee689f-71f7-492f-adaf-c21e2dd1e51e', url: 'git@192.168.40.141:group1/project1.git'
        sh 'cd /var/lib/jenkins/workspace/pipline-test && zip -r code.zip ./*'
    }
    stage("代码复制"){
        echo '代码复制'
        sh 'cd /var/lib/jenkins/workspace/pipline-test && scp code.zip tomcat@192.168.40.142:/data/tomcat_appdir'
        sh 'cd /var/lib/jenkins/workspace/pipline-test && scp code.zip tomcat@192.168.40.143:/data/tomcat_appdir'
    }
    stage("停止tomcat服务"){
        echo '停止tomcat服务'
        sh "ssh tomcat@192.168.40.142 '/etc/init.d/tomcat stop'"
        sh "ssh tomcat@192.168.40.143 '/etc/init.d/tomcat stop'"
    }
    stage("代码解压"){
        echo '代码复制'
        sh "ssh tomcat@192.168.40.142 'rm -rf /data/tomcat_webdir/myapp/* && unzip /data/tomcat_appdir/code.zip -d /data/tomcat_webdir/myapp'"
        sh "ssh tomcat@192.168.40.143 'rm -rf /data/tomcat_webdir/myapp/* && unzip /data/tomcat_appdir/code.zip -d /data/tomcat_webdir/myapp'"

    }
    stage("启动tomcat服务"){
        echo '启动tomcat服务'
        sh "ssh tomcat@192.168.40.142 '/etc/init.d/tomcat start'"
        sh "ssh tomcat@192.168.40.143 '/etc/init.d/tomcat start'"
    }
}

注意:由于jenkins的master节点的配置环境与slave节点配置环境不一致,因此执行流水线构建时会报错
除此之外,由于jenkins的master节点上配置和后端web服务器配置了免秘钥认证,如果把job任务运行在slave节点上,则也要配置和web服务器的免秘钥认证,否则将会报错

  • slave具体配置如下:

配置如下:

在主机192.168.40.144上
与配置环境相关的配置:
(1)安装git
yum -y install git
(2)更改pipline脚本内容
注意:由于slave上之前配置的工作空间目录为/data/jenkins/slave/workspace/pipline-test,
而master节点的工作目录为/var/lib/jenkins/workspace/pipline-test,所以,要对脚本中的相关工作空间目录进行修改。

node('slave-node1'){
    stage("代码克隆"){
        echo '代码克隆'
        sh 'rm -rf  /data/jenkins/slave/workspace/pipline-test/*'              #注意:要更改此处的工作空间路径
        git branch: 'develop', credentialsId: 'cbee689f-71f7-492f-adaf-c21e2dd1e51e', url: 'git@192.168.40.141:group1/project1.git'
        sh 'cd  /data/jenkins/slave/workspace/pipline-test && zip -r code.zip ./*'      #注意:要更改此处的工作空间路径
    }
    stage("代码复制"){
        echo '代码复制'
        sh 'cd  /data/jenkins/slave/workspace/pipline-test && scp code.zip tomcat@192.168.40.142:/data/tomcat_appdir'    #注意:要更改此处的工作空间路径
        sh 'cd  /data/jenkins/slave/workspace/pipline-test && scp code.zip tomcat@192.168.40.143:/data/tomcat_appdir'     #注意:要更改此处的工作空间路径
    }
    stage("停止tomcat服务"){
        echo '停止tomcat服务'
        sh "ssh tomcat@192.168.40.142 '/etc/init.d/tomcat stop'"
        sh "ssh tomcat@192.168.40.143 '/etc/init.d/tomcat stop'"
    }
    stage("代码解压"){
        echo '代码复制'
        sh "ssh tomcat@192.168.40.142 'rm -rf /data/tomcat_webdir/myapp/* && unzip /data/tomcat_appdir/code.zip -d /data/tomcat_webdir/myapp'"
        sh "ssh tomcat@192.168.40.143 'rm -rf /data/tomcat_webdir/myapp/* && unzip /data/tomcat_appdir/code.zip -d /data/tomcat_webdir/myapp'"

    }
    stage("启动tomcat服务"){
        echo '启动tomcat服务'
        sh "ssh tomcat@192.168.40.142 '/etc/init.d/tomcat start'"
        sh "ssh tomcat@192.168.40.143 '/etc/init.d/tomcat start'"
    }
}

(3)免秘钥认证相关配置:
此时,代码clone以及压缩完成,还不能远程复制到后端web服务器之上,因此需要做免秘钥认证,把slave节点的公钥复制到后端所有web服务器之上
生成公钥:
ssh-keygen
(4)复制公钥到后端web服务器
ssh-copy-id tomcat@192.168.40.142
ssh-copy-id tomcat@192.168.40.143

  • 从节点配置完毕,点击立即构建,查看pipline执行结果
  • 测试:

在pipline脚本执行过程中,在slave节点上执行以下命令查看任务是否在slave节点上运行:

[root@centos7 ~]# ps aux|grep ssh
root      74126  0.8  0.3 178640  4624 ?        S    15:01   0:00 ssh tomcat@192.168.40.142 /etc/init.d/tomcat stop
可以看到在从节点上看到远程连接到后端web服务器停止tomcat的进程,说明job任务在slave节点上运行

注意:在节点管理中,slave节点的标签和名称最好保持一致,在pipline脚本中指定slave节点时,要填写slave节点的标签名称
在以上配置中master节点的工作目录为/var/lib/jenkins/workspace/pipline-test,
而slave节点的远程工作目录为/data/jenkins/workspace/pipline-test,这样在配置时极为不便,不推荐这种做法,在这里特别指出。

5、代码质量管理SonarQube

5.1 SonarQube安装和配置

  代码测试工具SonarQube
  两个版本:最新版和LTS长期支持版
  官网:https://www.sonarqube.org
  安装环境依赖:https://docs.sonarqube.org/display/SONARQUBE67/Requirements
注意:

sonar可以安装在jenkins主机上,也可以和jenkins分开安装,它是通过网络访问jenkins  
在本实验中把sonar和jenkins安装到一块  
sonar只支持oracle jre8版本和openjdk8版本,安装前查看本机java版本是否符合要求  

sonarqube在linux平台安装需要注意(详见sonarqube环境依赖):

 要以非root用户安装  
 要设置单个进程能够打开的虚拟内存最大数量为262144  
 要设置能打开的最大文件数为65536  

SonarQube具体配置如下:

为sonar准备数据库,把数据库安装到192.168.40.145主机上,这里我们使用mysql作为sonar数据库。  
另外,sonar要求mysql数据库版本为5.6或5.7版本  

1、mysql安装

数据库安装需要以下三个文件
注意:这三个文件必须放在/usr/local/src目录下
my.cnf #mysql配置文件
mysql-5.6.42-linux-glibc2.12-x86_64.tar.gz #mysql安装tar包
mysql-install.sh #mysql启动脚本

(1)mysql的tar包需要从mysql官网下载
(2)使用脚本mysql-install.sh安装mysql
脚本内容如下:

[root@mysql src]# vim mysql-install.sh 
#!/bin/bash
DIR=`pwd`
NAME="mysql-5.6.42-linux-glibc2.12-x86_64.tar.gz"
FULL_NAME=${DIR}/${NAME}
DATA_DIR="/data/mysql"

if [ -f ${FULL_NAME} ];then
    echo "安装文件存在"
else
    echo "安装文件不存在"
    exit 3
fi
if [ -h /usr/local/mysql ];then
    echo "Mysql 已经安装"
    exit 3
else
    tar xvf ${FULL_NAME}   -C /usr/local/src
    ln -sv /usr/local/src/mysql-5.6.42-linux-glibc2.12-x86_64  /usr/loc
al/mysql
    if id  mysql;then
        echo "mysql 用户已经存在,跳过创建用户过程"
    fi
        useradd  mysql  -s /sbin/nologin
    if  id  mysql;then
        chown  -R mysql.mysql  /usr/local/mysql/* -R
        if [ ! -d  /data/mysql ];then
            mkdir -pv /data/mysql /var/lib/mysql && chown  -R mysql.mys
ql  /data /var/lib/mysql  -R
            /usr/local/mysql/scripts/mysql_install_db  --user=mysql --d
atadir=/data/mysql  --basedir=/usr/local/mysql/
            cp  /usr/local/src/mysql-5.6.42-linux-glibc2.12-x86_64/supp
ort-files/mysql.server /etc/init.d/mysqld
            chmod a+x /etc/init.d/mysqld
            cp ${DIR}/my.cnf   /etc/my.cnf
            ln -sv /usr/local/mysql/bin/mysql  /usr/bin/mysql
            ln -sv /data/mysql/mysql.sock  /var/lib/mysql/mysql.sock
            /etc/init.d/mysqld start
        else
            echo "MySQL数据目录已经存在,"
                        exit 3
        fi
    fi
fi

运行脚本安装mysql
cd /usr/local/src
bash mysql-install.sh

(3)配置文件my.cnf文件内容如下:

vim my.cnf
[mysqld]
socket=/data/mysql/mysql.sock
user=mysql
symbolic-links=0
datadir=/data/mysql
innodb_file_per_table=1
max_connections=10000

[client]
port=3306
socket=/var/lib/mysql/mysql.sock

[mysqld_safe]
log-error=/var/log/mysqld.log
pid-file=/tmp/mysql.sock

(4)创建数据库并授权

[root@centos7 src]# mysql
mysql> create database sonar character set utf8 collate utf8_general_ci;
Query OK, 1 row affected (0.14 sec)
mysql> grant all on sonar.* to sonar@'192.168.40.%' identified by "centos123456";
Query OK, 0 rows affected (0.09 sec)
mysql> flush privileges;
Query OK, 0 rows affected (0.00 sec)

(5)连接测试mysql
[root@centos7 src]# mysql -usonar -pcentos123456 -h192.168.40.145

2、sonarqube安装
在jenkins主机192.168.40.140上:
(1)把软件包从官网下载并上传到服务器上并解压
cd /usr/local/src
unzip sonarqube-6.7.6.zip
(2)为了便于后期升级和配置,创建软连接
ln -sv /usr/local/src/sonarqube-6.7.6 /usr/local/sonarqube
(3)配置sonar的配置文件
cd /usr/local/sonarqube/conf

#vim sonar.properties 
sonar.jdbc.username=sonar        #mysql用户名
sonar.jdbc.password=centos123456  #mysql用户密码
sonar.jdbc.url=jdbc:mysql://192.168.40.145:3306/sonar?useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&useConfigs=maxPerformance&useSSL=false   #mysql主机ip地址
sonar.web.host=0.0.0.0          #sonar监听地址
sonar.web.port=9000         #sonar监听端口

(4)查看更改内容

[root@centos7 conf]# grep '^[a-Z]' sonar.properties
sonar.jdbc.username=sonar
sonar.jdbc.password=centos123456
sonar.jdbc.url=jdbc:mysql://192.168.40.145:3306/sonar?useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&useConfigs=maxPerformance&useSSL=false
sonar.web.host=0.0.0.0
sonar.web.port=9000

(5)启动sonar服务
注意:sonar服务不能以root用户运行
因此需要创建普通用户运行sonar
创建普通用户
useradd www
passwd www
更改配置文件的属主和属组
cd /usr/local/sonarqube
chown -R www.www ./
切换到www用户,运行sonar
[root@centos7 sonarqube]# su - www
[www@centos7 ~]$ cd /usr/local/sonarqube/
[www@centos7 sonarqube]$ ./bin/linux-x86-64/sonar.sh start
(6)启动sonar命令后追踪日志信息查看sonar是否成功启动

[www@centos7 sonarqube]$ tail -f logs/sonar.log
2019.06.05 09:59:07 INFO  app[][o.s.a.SchedulerImpl] Waiting for Elasticsearch to be up and running
       2019.06.05 09:59:08 INFO  app[][o.e.p.PluginsService] no modules loaded
       2019.06.05 09:59:09 INFO  app[][o.e.p.PluginsService] loaded plugin [org.elasticsearch.transport.Netty4Plugin]
       2019.06.05 09:59:32 INFO  app[][o.s.a.SchedulerImpl] Process[es] is up
       2019.06.05 09:59:32 INFO  app[][o.s.a.p.ProcessLauncherImpl] Launch process[[key='web', ipcIndex=2, logFilenamePrefix=web]] from [/usr/local/src/sonarqube-6.7.6]: /usr/java/jdk1.8.0_192-amd64/jre/bin/java -Djava.awt.headless=true -Dfile.encoding=UTF-8 -Djava.io.tmpdir=/usr/local/src/sonarqube-6.7.6/temp -Xmx512m -Xms128m -XX:+HeapDumpOnOutOfMemoryError -cp ./lib/common/*:./lib/server/*:/usr/local/src/sonarqube-6.7.6/lib/jdbc/mysql/mysql-connector-java-5.1.42.jar org.sonar.server.app.WebServer /usr/local/src/sonarqube-6.7.6/temp/sq-process1719798068856583672properties
        2019.06.05 10:00:32 INFO  app[][o.s.a.SchedulerImpl] Process[web] is up
        2019.06.05 10:00:32 INFO  app[][o.s.a.p.ProcessLauncherImpl] Launch process[[key='ce', ipcIndex=3, logFilenamePrefix=ce]] from [/usr/local/src/sonarqube-6.7.6]: /usr/java/jdk1.8.0_192-amd64/jre/bin/java -Djava.awt.headless=true -Dfile.encoding=UTF-8 -Djava.io.tmpdir=/usr/local/src/sonarqube-6.7.6/temp -Xmx512m -Xms128m -XX:+HeapDumpOnOutOfMemoryError -cp ./lib/common/*:./lib/server/*:./lib/ce/*:/usr/local/src/sonarqube-6.7.6/lib/jdbc/mysql/mysql-connector-java-5.1.42.jar org.sonar.ce.app.CeServer /usr/local/src/sonarqube-6.7.6/temp/sq-process5693374724551028794properties
        2019.06.05 10:00:51 INFO  app[][o.s.a.SchedulerImpl] Process[ce] is up
        2019.06.05 10:00:51 INFO  app[][o.s.a.SchedulerImpl] SonarQube is up

(7)查看9000端口是否启用
在浏览器访问sonar
192.168.40.140:9000
如果不能访问web页面,可以查看数据库sonar数据库中的表是否自动创建

  • 登录sonarqube
    默认用户名和密码都是admin

  • 创建token
    启动成功以后,第一次页登录成功后会设置token,这个token是以后被扫描代码工程中需要配置的,用于通过token的方式连接Sonarqube平台传数据结果。
    这里我们可以点击右上角跳过教程,后续用到时可以手动创建

  • 安装中文支持
    方法1:通过安装插件实现
    在配置---应用市场---搜索,查找Chinese Pack并安装,安装完成后sonarqube会自动重启

    方法2:通过安装包实现
    sonar插件所在路径:/usr/local/sonarqube/extensions/plugins/
    在插件目录/usr/local/sonar/extensions/plugins执行以下命令:
    wget https://github.com/SonarQubeCommunity/sonar-l10n-zh/releases/download/sonar-l10n-zh-plugin-1.11/sonar-l10n-zh-plugin-1.11.jar
    然后重启服务:
    /usr/local/sonarqube-6.7.6/bin/linux-x86-64/sonar.sh restart

注意:启动sonar时可能会出现以下报错:

2019.05.28 18:16:33 INFO  app[][o.s.a.AppFileSystem] Cleaning or creating temp directory /usr/local/src/sonarqube-6.7.6/temp

WrapperSimpleApp: Encountered an error running main: java.nio.file.AccessDeniedException: /usr/local/src/sonarqube-6.7.6/temp/conf/es/elasticsearch.yml
java.nio.file.AccessDeniedException: /usr/local/src/sonarqube-6.7.6/temp/conf/es/elasticsearch.yml

解决方法:删除/usr/local/sonarqube/temp目录下所有文件后重启sonar服务即可

5.2 SonarQube-Scanner实现代码扫描

1、总体架构思路:

 运维在jenkins进行拉取代码操作  
 jenkins向gitlab拉取代码到本地  
 然后调用sonar-scanner扫描代码,把扫描结果传递给sonarqube  
 sonarqube把扫描结果存储到MySQL  

2、下载地址:http://docs.sonarqube.org/display/SCAN/Analyzing+with+SonarQube+Scanner
目前sonarqube-scanner最新版本为3.3,要求sonarqube版本为5.6以上
注意:在老版本中需要为sonar-scanner创建专用数据库,而在目前新版本下,sonar-sanner无需创建数据库,只需在配置文件中添加sonarqube的地址即可
3、sonarqube-scanner具体安装步骤如下:
(1)下载sonar-scanner并上传到sonarqube服务器192.168.40.140上并解压
cd /usr/local/src
unzip sonar-scanner-cli-3.3.0.1492-linux.zip
(2)创建软连接
ln -sv /usr/local/src/sonar-scanner-3.3.0.1492-linux /usr/local/sonar-scanner
(3)更改配置文件
cd /usr/local/sonar-scanner/conf

#vim sonar-scanner.properties
sonar.host.url=http://192.168.40.140:9000   #指定sonarqube服务器的地址
sonar.sourceEncoding=UTF-8    #指定字符编码

4、在代码所在目录执行命令进行扫描
注意:

在代码所在目录创建名为sonar-project.properties的配置文件,该文件定义了代码所在的路径。
另外,扫描命令/usr/local/sonar-scanner/bin/sonar-scanner必须在代码所在目录执行,因为该命令会在当前目录下查找名为sonar-project.properties的文件,根据文件中定义的代码路径扫描代码。
从官网下载示例代码进行扫描,这些代码已经写好配置文件sonar-project.properties,并且为了方便查看演示效果,这些代码存在bug。
示例代码已存入个人百度云盘,请按照下方链接下载
链接:https://pan.baidu.com/s/1DkTe1KSQBRffUzlNANnpcA 提取码:d6qs

(1)把代码上传到/usr/local/src并解压
unzip sonar-examples-master.zip
(2)在代码所在目录运行扫描命令
cd /usr/local/src/sonar-examples-master/projects/languages/python/python-sonar-runner

[root@centos7 python-sonar-runner]# ll
total 12
-rw-r--r-- 1 root root 461 Jul 25  2016 README.md
-rw-r--r-- 1 root root 338 Jul 25  2016 sonar-project.properties
drwxr-xr-x 5 root root  93 Jul 25  2016 src
-rw-r--r-- 1 root root 290 Jul 25  2016 validation.txt

(3)执行扫描命令
/usr/local/sonar-scanner/bin/sonar-scanner

5、在sonarqube的web界面刷新并查看扫描结果
查看扫描结果

查看报错详情

5.3 配置代码自动化扫描

  • 流水线总体流程:
    运维在jenkins上执行代码上线操作,进行下面一系列流程
    clone--->代码扫描--->压缩打包(tar/zip)--->从负载均衡摘除后端web服务器--->停止web服务--->同步代码--->启动web服务--->访问测试--->从负载均衡上线服务器
    这里同步代码包括:代码的解压,代码的复制,代码的存放位置等

测试:
在gitlab上新建一个项目python-app,属于group1组
在python-app项目中添加readme.md的内容为"测试app"
由于之前做实验做过授权,因此可以使用jack用户进行clone该测试代码
在jenkins主机上clone代码
git clone git@192.168.40.141:group1/python-app.git
使用一个python脚本作为测试代码进行扫描是否存在bug
脚本内容如下:

vim app.py
!/bin/env  python
coding:utf-8
mport subprocess
mport sys
ef mongodb_faults_num():
   obj = subprocess.Popen(("/usr/local/mongodb/bin/mongostat -u user -p pswd--authenticationDatabase admin -rowcount 10 2 |tail -n10 \
|awk '{print $11}' |tail -n10"),shell=True, stdout=subprocess.PIPE)
   restful = obj.stdout.read()
   data = restful.split()
   print max(data)  #取出统计结果中最大的一个值传递给zabbix
def mongodb+locked_num(dbname):
   obj = subprocess.Popen(("/usr/local/mongodb/bin/mongostat -u user-p pswd --authenticationDatabase admin -row count 10 2 |tail -n10 \
grep %s |awk '{print $12}' |awk -F':' '{PRINT $2}' |awk -F'%%' '{print $1}'" % dbname),shell=True, stdout=subprocess.PIPE)
   restful = obj.stdout.read()
   data = restful.split()
   print max(data)  #取出统计结果中最大的一个值传递给zabbix

def main():
   if sys.argv[1] == "faults":
       mongodb_faults_num()
   elif sys.argv[1] == "locked": #如果第一个参数是locaket
       dname=sys.argv[2]   #将第二个参数赋值给dbname
       mongodb_locked_num(dbname)  #执行函数mongodb_locked_num并传递参数dbname
   else:
       pass
if __name__ == "__main__":
   main()

创建存放脚本的目录
cd /root/python-app
mkdir src #该目录要与sonar-project.properties文件指定的目录保持一致
mv /root/app.py /root/python-app/src/
把之前示例代码中的配置文件sonar-project.properties复制到当前目录下
cp /usr/local/src/sonar-examples-master/projects/languages/python/python-sonar-runner/sonar-project.properties ./
该配置文件内容如下:

vim sonar-project.properties
# Required metadata
sonar.projectKey=python-app         #指定项目key
sonar.projectName=python-app        #指定项目名称
sonar.projectVersion=1.0

# Comma-separated paths to directories with sources (required)
sonar.sources=src       #注意:此路径必须是代码存放路径,脚本代码放在哪个目录下,路径就是哪个目录,二者要保持一致

# Language
sonar.language=py

# Encoding of the source files
sonar.sourceEncoding=UTF-8

把代码上传gitlab
[root@centos7 python-app]# git add .
[root@centos7 python-app]# git commit -m "python app"
[root@centos7 python-app]# git push
在gitlab的web界面上python-app项目中查看是否有对应的文件

  • 在jenkins上创建新的任务,用于测试代码的自动部署
    新建任务python-app,选择自由风格的软件项目,使用shell命令进行测试

    然后点击立即构建,进行代码clone

  • 配置jenkins调用sonarqube进行代码扫描
    在jenkins上需要做两个配置:
    1、指明sonarqube服务器的地址,让jenkins和sonarqube进行通信
    2、当执行扫描时,jenkins在哪找到sonar-scanner的可执行命令
    在配置之前的配置界面是通过安装插件才会出现,因此需要先安装一个插件SonarQube Scanner
    在jenkins---系统管理---插件管理中,搜索SonarQube Scanner插件并安装

  • 配置sonarqube
    在jenkins---系统管理---系统设置

  • 配置sonar-scanner
    在jenkins---系统管理---全局工具配置
    在SonarQube Scanner项,点击新增SonarQube Scanner
    有两种安装方式
    1、手动指定url
    2、系统自动安装
    这里,我们先使用手动安装方
    注意:要去掉下方自动安装的对勾后,才会显示指定url的内容框

    注意:#该url路径为sonar-scanner软件包解压后的软连接路径,系统会自动在该路径下找到bin目录下的sonar-scanner扫描器
    然后点击save保存即可

  • sonarqube服务器地址和sonar-scanner扫描器已经配置完成,接下来要在项目中添加代码扫描功能

  • 返回到python-app项目,配置代码扫描
    在python-app项目----配置----构建项
    点击增加构建步骤,选择Execute SonarQube Scanner

在Analysis properties项,添加以下内容

sonar.projectKey=python-app
sonar.projectName=python-app
sonar.projectVersion=1.0
sonar.sources=./src
sonar.language=py
sonar.sourceEncoding=UTF-8

然后点击保存,点击立即构建

在构建历史右上角有蓝色信号图标,点击图标可直接跳转到sonarqube页面查看扫描到的bug,查看bug在jenkins本机进行修复

注意:修复完成后需要再次提交gitlab再次执行代码clone和扫描,依次循环

  • 使用shell命令进行代码扫描
    在python-app项目----构建项删除创建的Execute SonarQube Scanner
    选择执行shell
    在控制台输出信息中找到python-app项目的工作目录为/var/lib/jenkins/workspace/pyhton-app
    shell命令内容如下:
     cd /var/lib/jenkins/workspace/pyhton-app #切换目录
     /usr/local/sonar-scanner/bin/sonar-scanner #执行扫描命令,该命令会在当前目录下查找sonar-project.properties文件,根据文件定义进行扫描,如果该文件缺失将无法进行代码扫描
    另外,在构建环境项,勾选第一项Delete workspace before build starts,即在构建之前删除之前的工作目录,防止冲突
    然后点击保存,并且立即构建
    注意:每次更改/root/python-app目录下的文件都要提交到gitlab

6、通过脚本实现代码自动化部署

6.1 准备工作

  • 在jenkins创建的项目中可以使用多种参数,而在使用脚本过程中需要用到字符参数和选项参数,因此需要先添加选项参数和选项参数

  • 在jenkins构建新的项目,名为myapp

  • 在myapp项目配置参数中,勾选参数化构建过程,添加以下三种参数:

  • 1、添加参数下拉列表中选择选项参数,该参数用于定义服务器对象,即为哪一台服务器进行代码部署
    选项参数如下:
     名称:GROUP
     选项:
      online-group1
      online-group2
      online-all
     描述:
      online-group1->192.168.40.142
      online-group2->192.168.40.143
      online-all->192.168.40.142 192.168.40.143

  • 2、再次添加一个字符参数,用于gitlab分支的代码clone
    字符参数
     名称:branch
     默认值:develop
     描述:develop-默认gitlab分支
    在构建项shell命令中添加该字符参数
     bash /root/script/deploy.sh $GROUP $branch

  • 3、再次添加一个选项参数,用于代码的部署或回滚
    选项参数
     名称:METHOD
     选项:
      deploy
      rollback_last_version
     描述:
      deploy-代码部署
      rollback_last_version-回滚到上一个版本

4、在构建项
执行shell
 命令:bash /root/script/deploy.sh $METHOD $branch $GROUP

在jenkins主机上,创建以下脚本用于调用参数

vim /root/script/deploy.sh
#!/bin/bash
DATE=`date +%Y-%m-%d_%H-%M-%S`
METHOD=$1
BRANCH=$2
SERVER_LIST=$3
echo $METHOD
echo $BRANCH
echo $SERVER_LIST
echo $DATE

注意:选项参数可以通过手动拖拽的方式,把该选项参数拉到最上方,而脚本在读取参数时是从上到下进行读取的。
因此构建项中,三个参数从上到下的位置是:选项参数METHOD,选项参数GROUP,字符参数branch,由于执行shell命令中参数的位置进行调整,
脚本deploy.sh中的位置变量也要进行调整,二者的位置要相对应,即$1为$METHOD,$2为$GROUP,$3为$branch

  • 在jenkins的web界面执行Build with Parameters进行测试

执行结果如下:

rollback_last_version          #指定回滚操作
develop                      #指定对开发分支进行回滚操作
online-all                    #指定对所有服务器进行回滚
2019-05-30_15-37-5           #回滚时间

6.2 构建自动化部署脚本

脚本自动化部署代码升级回滚总图流程图

1、代码clone

  • 可以通过jenkins进行代码clone,也可以通过脚本进行代码clone,这里我们以脚本clone代码为例
    在jenkins主机上:
    通过jenkins clone的代码默认在/var/lib/jenkins/workspace目录下,而我们通过脚本clone代码,这里为了防止出现混淆,我们手动创建存放代码的工作目录
     mkdir /data/gitdir -p
     注意:要提前在/data/gitdir目录下clone gitlab的代码生成project1目录

更改脚本内容进行代码clone:

cd /root/script
vim deploy.sh
#!/bin/bash

DATE=`date +%Y-%m-%d_%H-%M-%S`
METHOD=$1
BRANCH=$2
SERVER_LIST=$3

function clone_code(){       #定义函数
  rm -rf /data/gitdir/project1/*    #clone代码前先删除之前的目录
  cd /data/gitdir/project1/
  git clone  git@192.168.40.141:group1/project1.git
}

clone_code

执行脚本进行测试
 bash deploy.sh deploy group3 develop

2、代码扫描

  • 由于代码扫描需要调用sonar-scanner,但之前我们只对python-app项目做了soanr-scanner,对当前项目并没有做代码扫描的配置。
  • 可以把pyhton-app项目的sonar-scanner配置文件复制过来进行更改

在jenkins主机上:

删除之前存在project1目录并重新clone master分支的项目
    rm -rf /root/project1/*
    cd /root
    git clone git@192.168.40.141:group1/project1.git
配置sonar-scanner文件
    cd /root/project1
    cp /root/python-app/sonar-project.properties .
    vim sonar-project.properties
    # Required metadata
    sonar.projectKey=project1
    sonar.projectName=project1
    sonar.projectVersion=1.0

    # Comma-separated paths to directories with sources (required)
    sonar.sources=./

    # Language
    sonar.language=py

    # Encoding of the source files
    sonar.sourceEncoding=UTF-8
更改完成以后,/root/project1目录下的所有文件提交上传到gitlab master分支
    [root@centos7 project1]# git add .
    [root@centos7 project1]# git commit -m "v1"
    [root@centos7 project1]# git push
  • 以上完成的是对master分支的clone,这里为了便于实验,对develop分支也进行代码clone

配置如下:

clone develop分支的项目
    cd /opt
    git clone -b develop git@192.168.40.141:group1/project1.git
配置sonar-scanner文件
    cd /opt/project1
    cp /root/python-app/sonar-project.properties .
    vim sonar-project.properties
    # Required metadata
    sonar.projectKey=project1-develop    #命名与master分支进行区分
    sonar.projectName=project1-develop   #命名与master分支进行区分
    sonar.projectVersion=1.0

    # Comma-separated paths to directories with sources (required)
    sonar.sources=./

    # Language
    sonar.language=py

    # Encoding of the source files
    sonar.sourceEncoding=UTF-8
更改完成以后,/root/project1目录下的所有文件提交上传到gitlab develop分支
    [root@centos7 project1]# git add .
    [root@centos7 project1]# git commit -m "sonar"
    [root@centos7 project1]# git push   
  • 编写代码扫描脚本

编写脚本:

vim /root/script/deploy.sh
#!/bin/bash

DATE=`date +%Y-%m-%d_%H-%M-%S`
METHOD=$1
BRANCH=$2
SERVER_LIST=$3

function clone_code(){
  rm -rf /data/gitdir/project1   #这里删除原有项目工作目录,写为/data/gitdir/project1/*会报错,因此要写为/data/gitdir/project1
  cd /data/gitdir/         #由于执行此代码时会自动创建project1目录,这样一来在原来/data/gitdir/project1的基础上多了一层project1目录,即/data/gitdir/project1/project1,因此要删除原有的project1目录,即/data/gitdir/即可。
  git clone -b ${BRANCH} git@192.168.40.141:group1/project1.git      #通过调用参数,指定分支
}

function scanner_code(){
  cd /data/gitdir/project1/ && /usr/local/sonar-scanner/bin/sonar-scanner   #切换目录并运行代码扫描

}

clone_code           #调用函数
scanner_code         #调用函数
  • 在jenkins的web界面,myapp项目中执行立即构建
    选择参数1:deploy
    选项参数2:online-group1
    字符参数3:develop
    点击Build with Parameters按钮,在输出信息中访问sonarqube的链接,查看扫描结果

3、代码打包

  • 切换到代码所在目录,并对目录下所有文件进行压缩打包
     cd /data/gitdir/project1 && zip -r code.zip ./*
  • 代码打包脚本内容如下:

脚本内容如下:

vim /root/script/deploy.sh
#!/bin/bash

DATE=`date +%Y-%m-%d_%H-%M-%S`
METHOD=$1
BRANCH=$2
SERVER_LIST=$3

function clone_code(){
  rm -rf /data/gitdir/project1
  cd /data/gitdir/
  git clone -b ${BRANCH} git@192.168.40.141:group1/project1.git
}

function scanner_code(){
  cd /data/gitdir/project1/ && /usr/local/sonar-scanner/bin/sonar-scanner

}

function make_zip(){                   #定义函数压缩打包代码
  cd /data/gitdir/project1 && zip -r code.zip ./*
}

clone_code
scanner_code

4、从负载摘除后端web服务器

注意:以下操作要在两台负载均衡服务器上进行操作

  • 在负载均衡服务器上安装socat
     yum install socat -y
     注意:该命令可以通过参数对haproxy中配置的后端服务器进行摘除或上线操作
  • 为了便于摘除服务器操作,需要对haproxy的配置文件做以下修改
     vim /etc/haproxy/haproxy.cfg
     stats socket /var/lib/haproxy/stats mode 600 level admin #要授予该命令以600和admin级别的权限,否则访问将会被拒绝

另外,如果没有开启状态页,则需要开启状态页查看服务器状态上线状态,需要配置以下信息

vim /etc/haproxy/haproxy.cfg
listen stats
 mode http
 bind 0.0.0.0:9999
 stats enable
 log global
 stats uri /haproxy-status
 stats auth admin:admin
  • 重启haproxy,使配置生效
     systemctl restart haproxy
  • 进行命令测试

测试:

echo "help" |socat stdio /var/lib/haproxy/stats
  clear counters : clear max statistics counters (add 'all' for all counters)
  clear table    : remove an entry from a table
  help           : this message
  prompt         : toggle interactive mode with prompt
  quit           : disconnect
  show info      : report information about the running process
  show pools     : report information about the memory pools usage
  show stat      : report counters for each proxy and server
  show errors    : report last request and response errors for each proxy
  show sess [id] : report the list of current sessions or dump this session
  show table [id]: report table usage stats or dump this table's contents
  get weight     : report a server's current weight
  set weight     : change a server's weight
  set server     : change a server's state or weight
  set table [id] : update or create a table entry's data
  set timeout    : change a timeout setting
  set maxconn    : change a maxconn setting
  set rate-limit : change a rate limiting value
  disable        : put a server or frontend in maintenance mode
  enable         : re-enable a server or frontend which is in maintenance mode
  shutdown       : kill a session or a frontend (eg:to release listening ports)
  show acl [id]  : report available acls or dump an acl's contents
  get acl        : reports the patterns matching a sample for an ACL
  add acl        : add acl entry
  del acl        : delete acl entry
  clear acl <id> : clear the content of this acl
  show map [id]  : report available maps or dump a map's contents
  get map        : reports the keys and values matching a sample for a map
  set map        : modify map entry
  add map        : add map entry
  del map        : delete map entry
  clear map <id> : clear the content of this map
  set ssl <stmt> : set statement for ssl
  • 使用命令行摘除后端服务器
     echo "disable server myapp/192.168.40.142" |socat stdio /var/lib/haproxy/stats
     该命令意为把haproxy配置文件中定义的服务器组myapp中的192.168.40.142的服务器下线
    注意:由于需要在jenkins主机上通过脚本远程负载均衡服务器摘除后端服务器,因此jenkins主机需要对后端服务器做免秘钥认证
     ssh-copy-id 192.168.40.144
     ssh-copy-id 192.168.40.145
  • 在jenkins主机上:
  • 摘除后端服务器的脚本内容总结如下:

脚本:

vim /root/script/deploy.sh
#!/bin/bash

DATE=`date +%Y-%m-%d_%H-%M-%S`
METHOD=$1
BRANCH=$2
SERVER_LIST=$3

function IP_list(){         #由于不确定vip在哪个负载均衡服务器上,因此不能确定摘除哪台服务器,定义函数判断输入的参数是哪台服务器
  if [[ ${SERVER_LIST} == "online-group1" ]];then
     Server_IP="192.168.40.142"
     echo ${Server_IP}
  elif [[ ${SERVER_LIST} == "online-group2" ]];then
     Server_IP="192.168.40.143"
     echo ${Server_IP}
  elif [[ ${SERVER_LIST} == "online-all" ]];then
     Server_IP="192.168.40.142 192.168.40.143"
     echo ${Server_IP}
  fi
}

function clone_code(){
  rm -rf /data/gitdir/project1
  cd /data/gitdir/
  git clone -b ${BRANCH} git@192.168.40.141:group1/project1.git
}

function scanner_code(){
  cd /data/gitdir/project1/ && /usr/local/sonar-scanner/bin/sonar-scanner
}

function make_zip(){                   #定义函数压缩打包代码
  cd /data/gitdir/project1 && zip -r code.zip ./*
}

function down_node(){       #定义函数远程到负载均衡主机摘除后端web服务器,在函数内调用参数Server_IP,并通过for循环摘除所有需要下线的服务器
  for node in ${Server_IP};do
    ssh root@192.168.40.144 "echo "disable server myapp/${node}" |socat stdio /var/lib/haproxy/stats"
    ssh root@192.168.40.145 "echo "disable server myapp/${node}" |socat stdio /var/lib/haproxy/stats"
  done
}

clone_code
scanner_code
IP_list
down_node
  • 执行脚本进行测试
     bash deploy.sh deploy develop online-group2
  • 在浏览器访问haproxy状态页查看后端服务器在线状态
     192.168.40.144:9999/haproy-status
     192.168.40.144:9999/haproy-status

5、关闭服务

  • 这里由于代码过长,只贴出本部分脚本中定义的函数

部分函数内容:

function stop_tomcat(){
  for node in ${Server_IP};do
    ssh tomcat@${node} "/etc/init.d/tomcat stop"
  done
}

6、同步代码

  • 该步骤包括:
     把代码压缩包复制到后端web服务器专门用于存放代码压缩包的目录
    把代码压缩包解压到web服务器网页文件存放目录

配置如下:

mkdir /data/tomcat_webapps/
chown -R tomcat.tomcat /data
ln -sv  /data/tomcat_webapps/code-${DATE} /data/tomcat_webdir/myapp
注意:新创建的存放解压后网页文件目录为/data/tomcat_webapps/,而网页文件的原始路径为/data/tomcat_webdir/myapp,
    因此需要把新建的网页文件路径下的存放网页文件的目录软链接到存放网页文件的原始目录即可
  • 代码同步脚本内定义函数如下:

定义函数:

function scp_zipfile(){
  for node in ${Server_IP};do
    scp /data/gitdir/project1/code.zip tomcat@${node}:/data/tomcat_appdir/code-${DATE}.zip
    ssh tomcat@${node} "unzip /data/tomcat_appdir/code-${DATE}.zip  -d /data/tomcat_webapps/code-${DATE} && rm -rf  /data/tomcat_webdir/myapp && ln -sv  /data/tomcat_webapps/code-${DATE} /data/tomcat_webdir/myapp"
  done
}

7、启动服务

  • 这里由于代码过长,只贴出本部分脚本中定义的函数

函数内容:

function start_tomcat(){
  for node in ${Server_IP};do
    ssh tomcat@${node} "/etc/init.d/tomcat start"
  done
}
  • 以上5,6,7三个步骤配置总结内容如下:

配置总结:

创建存放代码目录并授权
    mkdir /data/tomcat_webapps/
    chown -R tomcat.tomcat /data
脚本内容:
vim /root/script/deploy.sh
#!/bin/bash

DATE=`date +%Y-%m-%d_%H-%M-%S`
METHOD=$1
BRANCH=$2
SERVER_LIST=$3

function IP_list(){         #由于不确定vip在哪个负载均衡服务器上,因此不能确定摘除哪台服务器,定义函数判断输入的参数是哪台服务器
  if [[ ${SERVER_LIST} == "online-group1" ]];then
     Server_IP="192.168.40.142"
     echo ${Server_IP}
  elif [[ ${SERVER_LIST} == "online-group2" ]];then
     Server_IP="192.168.40.143"
     echo ${Server_IP}
  elif [[ ${SERVER_LIST} == "online-all" ]];then
     Server_IP="192.168.40.142 192.168.40.143"
     echo ${Server_IP}
  fi
}

function clone_code(){
  rm -rf /data/gitdir/project1
  cd /data/gitdir/
  git clone -b ${BRANCH} git@192.168.40.141:group1/project1.git
}

function scanner_code(){
  cd /data/gitdir/project1/ && /usr/local/sonar-scanner/bin/sonar-scanner
}

function make_zip(){                   #定义函数压缩打包代码
  cd /data/gitdir/project1 && zip -r code.zip ./*
}

function down_node(){       #定义函数远程到负载均衡主机摘除后端web服务器,在函数内调用参数Server_IP,并通过for循环摘除所有需要下线的服务器
  for node in ${Server_IP};do
    ssh root@192.168.40.144 "echo "disable server myapp/${node}" |socat stdio /var/lib/haproxy/stats"
    ssh root@192.168.40.145 "echo "disable server myapp/${node}" |socat stdio /var/lib/haproxy/stats"
  done
}

function stop_tomcat(){
  for node in ${Server_IP};do
    ssh tomcat@${node} "/etc/init.d/tomcat stop"
  done
}

function scp_zipfile(){
  for node in ${Server_IP};do
    scp /data/gitdir/project1/code.zip tomcat@${node}:/data/tomcat_appdir/code-${DATE}.zip
    ssh tomcat@${node} "unzip /data/tomcat_appdir/code-${DATE}.zip  -d /data/tomcat_webapps/code-${DATE} && rm -rf  /data/tomcat_webdir/myapp && ln -sv  /data/tomcat_webapps/code-${DATE} /data/tomcat_webdir/myapp"
  done
}

function start_tomcat(){
  for node in ${Server_IP};do
    ssh tomcat@${node} "/etc/init.d/tomcat start"
  done
}

clone_code
scanner_code
make_zip
IP_list
down_node
stop_tomcat
scp_zipfile
start_tomcat
  • 执行脚本进行测试,查看服务状态
     bash deploy.sh deploy develop online-group1

8、服务测试

脚本内函数定义如下:

function web_test(){
  #sleep 30
  for node in ${Server_IP};do
    NUM=`curl -s  -I -m 10 -o /dev/null  -w %{http_code}  http://${node}:8080/myapp/index.html`
    if [[ ${NUM} -eq 200 ]];then
       echo "${node} 测试通过,即将添加到负载"
       add_node ${node}
    else
       echo "${node} 测试失败,请检查该服务器是否成功启动tomcat"
    fi
  done
}

9、把web服务器添加到负载均衡

脚本内函数定义如下:

function add_node(){
   node=$1
    echo ${node},"----->"
    if [ ${node} == "192.168.40.142" ];then
       echo "192.168.40.142 部署完毕,请进行代码测试!"
    else
      ssh root@192.168.40.144 ""echo enable  server myapp/${node}" | socat stdio /var/lib/haproxy/stats"
      ssh root@192.168.40.145 ""echo enable  server myapp/${node}" | socat stdio /var/lib/haproxy/stats"
    fi
}
  • 以上8,9两步骤脚本内容总结如下:

内容总结如下:

vim /root/script/deploy.sh
#!/bin/bash

DATE=`date +%Y-%m-%d_%H-%M-%S`
METHOD=$1
BRANCH=$2
SERVER_LIST=$3

function IP_list(){         #由于不确定vip在哪个负载均衡服务器上,因此不能确定摘除哪台服务器,定义函数判断输入的参数是哪台服务器
  if [[ ${SERVER_LIST} == "online-group1" ]];then
     Server_IP="192.168.40.142"
     echo ${Server_IP}
  elif [[ ${SERVER_LIST} == "online-group2" ]];then
     Server_IP="192.168.40.143"
     echo ${Server_IP}
  elif [[ ${SERVER_LIST} == "online-all" ]];then
     Server_IP="192.168.40.142 192.168.40.143"
     echo ${Server_IP}
  fi
}

function clone_code(){
  rm -rf /data/gitdir/project1
  cd /data/gitdir/
  git clone -b ${BRANCH} git@192.168.40.141:group1/project1.git
}

function scanner_code(){
  cd /data/gitdir/project1/ && /usr/local/sonar-scanner/bin/sonar-scanner
}

function make_zip(){                   #定义函数压缩打包代码
  cd /data/gitdir/project1 && zip -r code.zip ./*
}

function down_node(){       #定义函数远程到负载均衡主机摘除后端web服务器,在函数内调用参数Server_IP,并通过for循环摘除所有需要下线的服务器
  for node in ${Server_IP};do
    ssh root@192.168.40.144 "echo "disable server myapp/${node}" |socat stdio /var/lib/haproxy/stats"
    ssh root@192.168.40.145 "echo "disable server myapp/${node}" |socat stdio /var/lib/haproxy/stats"
  done
}

function stop_tomcat(){              #定义函数停止tomcat服务
  for node in ${Server_IP};do
    ssh tomcat@${node} "/etc/init.d/tomcat stop"
  done
}

function scp_zipfile(){             #定义函数远程复制代码压缩包并解压到指定目录,然后删除之前的目录并创建软链接
  for node in ${Server_IP};do
    scp /data/gitdir/project1/code.zip tomcat@${node}:/data/tomcat_appdir/code-${DATE}.zip
    ssh tomcat@${node} "unzip /data/tomcat_appdir/code-${DATE}.zip  -d /data/tomcat_webapps/code-${DATE} && rm -rf  /data/tomcat_webdir/myapp && ln -sv  /data/tomcat_webapps/code-${DATE} /data/tomcat_webdir/myapp"
  done
}

function start_tomcat(){           #定义函数启动tomcat服务
  for node in ${Server_IP};do
    ssh tomcat@${node} "/etc/init.d/tomcat start"
  done
}

function web_test(){           #定义函数进行测试
  #sleep 30
  for node in ${Server_IP};do
    NUM=`curl -s  -I -m 10 -o /dev/null  -w %{http_code}  http://${node}:8080/myapp/index.html`
    if [[ ${NUM} -eq 200 ]];then
       echo "${node} 测试通过,即将添加到负载"
       add_node ${node}
    else
       echo "${node} 测试失败,请检查该服务器是否成功启动tomcat"
    fi
  done
}

function add_node(){             #定义函数上线web服务器
   node=$1 
    echo ${node},"----->"
    if [ ${node} == "192.168.40.142" ];then
       echo "192.168.40.142 部署完毕,请进行代码测试!"
    else
      ssh root@192.168.40.144 ""echo enable  server myapp/${node}" | socat stdio /var/lib/haproxy/stats"
      ssh root@192.168.40.145 ""echo enable  server myapp/${node}" | socat stdio /var/lib/haproxy/stats"
    fi
}

main(){                #定义主函数
   case $1  in
      deploy)
        IP_list;
        clone_code;
        scanner_code;
        make_zip;
        down_node;
        stop_tomcat;
        scp_zipfile;
        start_tomcat;
        web_test;
         ;;
    esac
}

main $1 $2 $3
  • 对脚本进行测试:

脚本测试:

更改/opt/目录develop分支的代码并提交,然后使用脚本完成自动部署
更改网页文件内容
    cd /opt/project1
    vim index.html
    <h1>This is Testpage</h1>
    <h1>This is Testpage02</h1>
    <h1>This is Testpage03</h1>
    <h1>This is Testpage04</h1>
    <h1>This is Testpage05</h1>
提交代码
    git add .
    git commit -m "v5"
    git push
  • 模拟真实环境:
  • 192.168.40.142作为测试环境,192.168.40.143作为生产环境
  • 先在192.168.40.142进行测试,如果测试没问题,再对192.168.40.143进行升级
  • 在jenkins的web界面myapp项目中点击Build with Paremetes构建项目对192.168.40.142进行测试

示例:

选项参数如下:
METHOD :deploy
GROUP :online-group1        #online-group1对应主机192.168.40.142
branch:develop
  • 如果执行成功,访问192.168.40.144:9999/haproxy-status(或192.168.40.144:9999/haproxy-status)查看haproxy的状态页,查看服务器状态,此时192.168.40.142处于下线状态
  • 只有当我们对192.168.40.143进行代码部署时,192.168.40.142主机才会被上线,这样相对来说比较安全
  • 在脚本中添加以下代码,由于脚本内容过长,这里只列出更改部分的函数

示例:

function IP_list(){
  if [[ ${SERVER_LIST} == "online-group1" ]];then
     Server_IP="192.168.40.142"
     echo ${Server_IP}
  elif [[ ${SERVER_LIST} == "online-group2" ]];then
     Server_IP="192.168.40.143"
     echo ${Server_IP}
     ssh root@192.168.40.144 ""echo enable  server myapp/192.168.40.142" | socat stdio /var/lib/haproxy/stats"     #当检测到服务器ip地址为192.168.40.143时,说明192.168.40.142主机测试成功可以恢复上线,远程执行命令使其上线
     ssh root@192.168.40.145 ""echo enable  server myapp/192.168.40.142" | socat stdio /var/lib/haproxy/stats"  #当检测到服务器ip地址为192.168.40.143时,说明192.168.40.142主机测试成功可以恢复上线,远程执行命令使其上线
  elif [[ ${SERVER_LIST} == "online-all" ]];then
     Server_IP="192.168.40.142 192.168.40.143"
     echo ${Server_IP}
  fi
}
  • 在jenkins的web界面myapp项目中点击Build with Paremetes构建项目再次对192.168.40.143进行测试

示例:

选项参数如下:
METHOD :deploy
GROUP :online-group2          #online-group1对应主机192.168.40.143
branch:develop
  • 此时,再次查看haproxy的状态页,访问192.168.40.144:9999/haproxy-status(或192.168.40.144:9999/haproxy-status),查看后端web服务器的状态,可以发现,192.168.40.142已经恢复上线,而192.168.40.143处于下线状态。
  • 当192.168.40.143代码部署完成后,会自动进行检测,如果检测没有问题,会自动将其上线加入到负载均衡上,这是再次查看haproxy状态页,可以发现,192.168.40.143已经自动恢复上线状态
  • 此时访问vip地址查看web网页内容,可以发现正是我们修改过得内容
  • 至此,代码升级和部署完成

10、代码回滚

  • 为了便于测试,我们需要在jenkins上myapp项目多次进行代码的升级,生成多个版本便于回滚
  • 同时可以测试对后端所有服务器进行升级,即选择online-all,对所有web服务器进行升级。
    注意:对所有web服务器升级时,可能会导致web服务无法访问,因此要谨慎操作
  • 可通过两种方式进行代码回滚:
     1、git常用命令
      使用git命令指定版本进行回滚,但如果代码量较大且为编译安装时,花费时间较长
      git reset --hard HEAD^^ #git版本回滚, HEAD为当前版本,加一个^为上一个,^^为上上一个版本
     2、在web服务器更改web文件软链接指向的版本为上一个版本即可
      在之前的实验中,我们把tomcat网页文件/data/tomcat_wendir/myapp软链接到从jenkins复制过来的目录,因此只需更改软链接即可实现版本的回滚

代码回滚脚本内容如下:

function rollback_last_version(){
  for node in ${Server_IP};do
    NOW_VERSION=`ssh tomcat@${node} ""/bin/ls -l  -rt /data/tomcat_webdir/ | awk -F"->" '{print $2}'  | tail -n1""`  #取出当前版本网页文件的绝对路径
   NOW_VERSION=`basename ${NOW_VERSION}`      #根据上一步骤中版本号所在的绝对路径取出当前版本的版本号
   echo $NOW_VERSIONG
    NAME=`ssh tomcat@${node}  "" ls  -l  -rt  /data/tomcat_webapps/ | grep -B 1 ${NOW_VERSION} | head -n1 | awk '{print $9}'""`    #取出上一个版本的版本号
    ssh tomcat@${node} "rm -rf /data/tomcat_webdir/myapp && ln -sv  /data/tomcat_webapps/${NAME} /data/tomcat_webdir/myapp"    #删除最新版本的软链接,创建新的软链接,把软链接指向上一个版本的版本号
  done 
}
在主函数需要添加以下内容
main(){                #定义主函数
    case $1  in
      deploy)
        IP_list;
        clone_code;
        scanner_code;
        make_zip;
        down_node;
        stop_tomcat;
        scp_zipfile;
        start_tomcat;
        web_test;
         ;;
      rollback_last_version)
        IP_list;
        echo ${Server_IP}
        down_node;
        stop_tomcat;
        rollback_last_version;
        start_tomcat;
        web_test;
         ;;
    esac

以上就是代码回滚操作的整个过程,在下面会对整个脚本代码进行整合,便于查看。
全部脚本内容如下所示:

vim /root/script/deploy.sh
#!/bin/bash

DATE=`date +%Y-%m-%d_%H-%M-%S`
METHOD=$1
BRANCH=$2
SERVER_LIST=$3

function IP_list(){         #由于不确定vip在哪个负载均衡服务器上,因此不能确定摘除哪台服务器,定义函数判断输入的参数是哪台服务器
  if [[ ${SERVER_LIST} == "online-group1" ]];then
     Server_IP="192.168.40.142"
     echo ${Server_IP}
  elif [[ ${SERVER_LIST} == "online-group2" ]];then
     Server_IP="192.168.40.143"
     echo ${Server_IP}
  elif [[ ${SERVER_LIST} == "online-all" ]];then
     Server_IP="192.168.40.142 192.168.40.143"
     echo ${Server_IP}
  fi
}

function clone_code(){
  rm -rf /data/gitdir/project1
  cd /data/gitdir/
  git clone -b ${BRANCH} git@192.168.40.141:group1/project1.git
}

function scanner_code(){
  cd /data/gitdir/project1/ && /usr/local/sonar-scanner/bin/sonar-scanner
}

function make_zip(){                   #定义函数压缩打包代码
  cd /data/gitdir/project1 && zip -r code.zip ./*
}

function down_node(){       #定义函数远程到负载均衡主机摘除后端web服务器,在函数内调用参数Server_IP,并通过for循环摘除所有需要下线的服务器
  for node in ${Server_IP};do
    ssh root@192.168.40.144 "echo "disable server myapp/${node}" |socat stdio /var/lib/haproxy/stats"
    ssh root@192.168.40.145 "echo "disable server myapp/${node}" |socat stdio /var/lib/haproxy/stats"
  done
}

function stop_tomcat(){              #定义函数停止tomcat服务
  for node in ${Server_IP};do
    ssh tomcat@${node} "/etc/init.d/tomcat stop"
  done
}

function scp_zipfile(){             #定义函数远程复制代码压缩包并解压到指定目录,然后删除之前的目录并创建软链接
  for node in ${Server_IP};do
    scp /data/gitdir/project1/code.zip tomcat@${node}:/data/tomcat_appdir/code-${DATE}.zip
    ssh tomcat@${node} "unzip /data/tomcat_appdir/code-${DATE}.zip  -d /data/tomcat_webapps/code-${DATE} && rm -rf  /data/tomcat_webdir/myapp && ln -sv  /data/tomcat_webapps/code-${DATE} /data/tomcat_webdir/myapp"
  done
}

function start_tomcat(){           #定义函数启动tomcat服务
  for node in ${Server_IP};do
    ssh tomcat@${node} "/etc/init.d/tomcat start"
  done
}

function web_test(){           #定义函数进行测试
  #sleep 30
  for node in ${Server_IP};do
    NUM=`curl -s  -I -m 10 -o /dev/null  -w %{http_code}  http://${node}:8080/myapp/index.html`
    if [[ ${NUM} -eq 200 ]];then
       echo "${node} 测试通过,即将添加到负载"
       add_node ${node}
    else
       echo "${node} 测试失败,请检查该服务器是否成功启动tomcat"
    fi
  done
}

function add_node(){             #定义函数上线web服务器
   node=$1 
    echo ${node},"----->"
    if [ ${node} == "192.168.40.142" ];then
       echo "192.168.40.142 部署完毕,请进行代码测试!"
    else
      ssh root@192.168.40.144 ""echo enable  server myapp/${node}" | socat stdio /var/lib/haproxy/stats"
      ssh root@192.168.40.145 ""echo enable  server myapp/${node}" | socat stdio /var/lib/haproxy/stats"
    fi
}

function rollback_last_version(){
  for node in ${Server_IP};do
    NOW_VERSION=`ssh tomcat@${node} ""/bin/ls -l  -rt /data/tomcat_webdir/ | awk -F"->" '{print $2}'  | tail -n1""`  #取出当前版本网页文件的绝对路径
   NOW_VERSION=`basename ${NOW_VERSION}`      #根据上一步骤中版本号所在的绝对路径取出当前版本的版本号
   echo $NOW_VERSIONG
    NAME=`ssh tomcat@${node}  "" ls  -l  -rt  /data/tomcat_webapps/ | grep -B 1 ${NOW_VERSION} | head -n1 | awk '{print $9}'""`    #取出上一个版本的版本号
    ssh tomcat@${node} "rm -rf /data/tomcat_webdir/myapp && ln -sv  /data/tomcat_webapps/${NAME} /data/tomcat_webdir/myapp"    #删除最新版本的软链接,创建新的软链接,把软链接指向上一个版本的版本号
  done 
}

main(){                #定义主函数
    case $1  in
      deploy)
        IP_list;
        clone_code;
        scanner_code;
        make_zip;
        down_node;
        stop_tomcat;
        scp_zipfile;
        start_tomcat;
        web_test;
         ;;
      rollback_last_version)
        IP_list;
        echo ${Server_IP}
        down_node;
        stop_tomcat;
        rollback_last_version;
        start_tomcat;
        web_test;
         ;;
    esac
}

main $1 $2 $3
  • 代码回滚测试:

示例:

在后端web服务器查看当前版本
    [root@centos7 ~]# ll /data/tomcat_webdir/
    total 0
    lrwxrwxrwx 1 tomcat tomcat 45 Jun  3 11:28 myapp -> /data/tomcat_webapps/code-2019-06-03_11-27-35
查看存放往期代码的目录
    [root@centos7 ~]# ll /data/tomcat_webapps/
    total 0
    drwxrwxr-x 2 tomcat tomcat 56 May 31 17:08 code-2019-05-31_17-07-54
    drwxrwxr-x 2 tomcat tomcat 56 May 31 17:15 code-2019-05-31_17-14-48
    drwxrwxr-x 2 tomcat tomcat 56 May 31 17:16 code-2019-05-31_17-16-07
    drwxrwxr-x 2 tomcat tomcat 56 May 31 17:38 code-2019-05-31_17-38-15
    drwxrwxr-x 2 tomcat tomcat 56 Jun  3 11:28 code-2019-06-03_11-27-35
在jenkins上myapp项目进行回滚
    METHOD: rollback_last_version
    GROUP:online-group1
    branch:develop
回滚完毕查看web服务器myapp软链接指向
    [root@centos7 ~]# ll /data/tomcat_webdir/
    total 0
    lrwxrwxrwx 1 tomcat tomcat 45 Jun  3 15:22 myapp -> /data/tomcat_webapps/code-2019-05-31_17-38-15
可以看到,已经回滚到上一个版本,登录浏览器查看网页内容是否已经回滚到上一个版本。
  • 最后,附上各服务器安装服务以及服务启动方式,便于服务的重启。
主机 服务 服务启动方式
192.168.40.145 keepalived systemctl start keepalived
haproxy systemctl start haproxy
mysql /etc/init.d/mysqld start
192.168.40.144 keepalived systemctl start keepalived
haproxy systemctl start haproxy
192.168.40.143 tomcat systemctl start tomcat
192.168.40.142 tomcat systemctl start tomcat
192.168.40.141 gitlab gitlab-ctl start
192.168.40.140 jenkins systemctl start jenkins
sonarqube su - www(以普通用户启动)
usr/local/sonarqube/bin/linux-x86-64/sonar.sh start
标签: gitlab+jenkins
最后更新:2023年6月20日

袁党生

这个人很懒,什么都没留下

点赞
< 上一篇

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复

COPYRIGHT © 2023 linux学习. ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang

豫ICP备18039507号-1