14. 代码部署流程与案例

1 代码部署流程

1.1 代码克隆

公司的代码都是放到gitlab仓库, 代码的克隆会由Jenkins的项目构建配置去触发. 运维人员需要在Jenkins中创建项目, 并且配置构建的步骤. 之后点击构建, 触发代码的克隆以及后续阶段

代码克隆阶段需要配置的内容

1. Jenkins服务器上的Jenkins用户和Gitlab服务器的免秘钥认证: Jenkins是以jenkins用户运行, 因此, 需要在Jenkins服务器生成jenkins用的秘钥对, 把公钥添加到Gitlab的root账号下,防止其他用户误删除
2. 在Jenkins上创建项目, 填写项目的gitlab代码地址, 添加jenkins用户的私钥信息

配置好这些信息后, Jenkins就可以从gitlab拉取代码到本地的/var/lib/jenkins/workspace目录下

图片.png

1.2 源代码测试

代码测试可以使用商业的软件, 也可以使用开源的Sonarqube. 需要部署单独Sonarqube服务器并且在Jenkins服务器上安装Sonar-Scanner客户端, 让Jenkins拉取代码后, 在本地进行代码扫描, 之后上传到Sonarqube服务器进行分析

代码扫描一定是在编译前进行

代码测试阶段需要配置的内容

1. 部署Sonarqube Server和PgSQL
2. 在PgSQL创建数据库, 配置Sonarqube和PgSQL的连接
3. 配置SonarScanner和服务器的连接
4. 配置项目构建使用Sonarqube进行扫描. 如果是使用脚本, 那么只需在构建中使用Shell脚本, 同时这样也可以把代码克隆到自定义的Jenkins服务器目录下, 之后在项目目录下, 创建properties文件. 并且在项目目录下执行scanner客户端命令
5. 如果是使用Jenkins的Scanner插件, 那么需要安装插件, 配置Sonarqube服务器地址, 以及本地的Scanner客户端命令的路径, 包括编辑properties文件
6. 执行构建即可完成代码克隆和扫描
图片.png

1.3 代码编译

对于Java这类编译性语言, 一旦源代码扫描没有问题, 就会执行编译, 如果代码有问题, 那么就不会继续构建. 如果是html, php这些语言, 那么编译步骤是不用的

Java代码编译可以在单独的Maven服务器进行执行, 并且可以搭建本地的仓库, 去缓存编译的依赖包, 如Nexus

图片.png

1.4 代码拷贝

代码编译完, 需要把生成的jar|war包拷贝到后端的web服务器, 可以基于scp, rsync或者ansible

这一步需要配置Jenkins和后端服务器的免秘钥认证, 将jenkins用户的公钥拷贝到web服务器的运行用户的账号. 如果web服务是tomcat用户启动, 那么就是ssh-copy-id tomcat@xxxx

图片.png

1.5 将web服务器从负载均衡器下线

做代码替换需要先把服务器从负载均衡器下线, 如果在没有下线前, 直接停止服务, 那么客户端访问会立刻报错. 因此, 要先把web服务器从负载均衡器下线, 然后停止服务, 再做代码替换. web服务都是会做Session共享的, 因此, 即使用户正在访问的服务器下线了, 下次再访问被调度到其他的服务器, Session信息也不会丢失

HAProxy可以通过socket文件, 进行动态的上线下
LVS需要使用lvsadmin命令或者在Keepalived中注释掉被下线的服务器
Nginx需要修改配置文件, 然后reload服务

图片.png

1.6 停止web服务

服务器从负载均衡下线后, 要停止服务, 否则某些文件正在被占用时, 执行代码替换会出现错误

图片.png

1.7 代码替换

代码替换有多种方案, 可以把压缩包存放到统一的路径, 把解压后的目录存放到统一路经, 然后把用户访问的路径软连接到代码目录. 之后代码升级或者降级只需要删除和重新创建软连接即可

对于旧版本的代码, 也可以暂时保留上一个版本, 一旦升级失败, 可以直接把上一个版本的代码拷贝到目标目录下, 修改软连接, 即可完成回滚

图片.png

1.8 启动服务

代码替换后, 进行服务启动

图片.png

1.9 服务测试

在把服务器重新添加到负载均衡前, 要先进行服务测试, 因为, 代码扫描只能扫描语法和bug等信息. 但是具体的功能, 还需要部署后才能进行测试. 因此, 在上线前, 要进行功能测试, 或者至少访问以下监控的url. 确保服务启动, 且运行正常. 如果发现服务启动失败, 或者功能缺失, 可以在这一步进行回滚

图片.png

1.10 将服务器添加到负载均衡器

添加到负载均衡器后, 用户就可以正常访问了

图片.png
图片.png

2 代码部署策略

2.1 灰度/金丝雀部署

对于灰度部署, 可以先只升级一部分服务器, 让剩余的服务器正常运行在旧版本下. 新版本服务器升级测试没问题后, 上线到负载均衡器, 让用户访问一段时间, 确定没问题, 再继续升级后续的服务器

2.2 蓝绿部署

蓝绿部署要求有两套相同的环境, 每次升级只升级一套, 当运行稳定后, 再把另一套升级到相同的版本

2.3 滚动部署

每次都升级一个或者几个服务器, 并且连续的把所有的服务器都升级完.

具体的部署流程和策略, 还需要根据具体情况和业务, 进行修改

3 Jenkins参数化构建

3.1 创建一个项目

图片.png
图片.png

3.2 配置构建

  • 丢弃旧的构建

只保留7天内的5次构建

  • 参数化构建过程

字符参数: 用于指定部署的是哪个分支, 也就是代码部署到哪个环境
选项参数: 用于选择此次部署的内容, 比如可以定义某个选项为执行灰度部署, 部署后, 测试没问题, 再选择部署剩余的服务器

选项参数案例:

图片.png
图片.png

Jenkins服务器创建脚本, 测试$1返回值

jenkins@jenkins:/data/jenkins$ vim tomcat-1.sh

#!/bin/bash
echo $1

jenkins@jenkins:/data/jenkins$ bash tomcat-1.sh GROUP
GROUP
jenkins@jenkins:/data/jenkins$ bash tomcat-1.sh GROUP1
GROUP1
jenkins@jenkins:/data/jenkins$ bash tomcat-1.sh GROUP2
GROUP2

图片.png

在执行Shell构建时, 脚本后要接$GROUP. 之后执行构建时, 可以在Jenkins页面选择GROUP1或者GROUP2, 如果执行GROUP1, 那么GROUP1就会作为参数, 传给GROUP变量, 最终传给脚本中的$1. 只需要在脚本中判断$1的值, 就可以判断此次是灰度发布, 还是发布剩余的服务器

  • 此时, 执行构建时, 是没有立即构建选项的, 而是要使用Build with Parameters
图片.png
  • 构建时, 可以根据不同的选项进行构建
图片.png

Group1

图片.png
图片.png

Group2

图片.png
图片.png

字符参数案例:

一般用于指定部署到不同的环境, 比如生产, 开发或者测试环境

  • 字符参数创建
图片.png
jenkins@jenkins:/data/jenkins$ vim tomcat-1.sh 

#!/bin/bash
BRANCH=$1
GROUP=$2

echo "部署的分支是:${BRANCH}"
echo "部署的服务器是:${GROUP}"
~                             
jenkins@jenkins:/data/jenkins$ bash tomcat-1.sh master GROUP1
部署的分支是:master # master就表示部署的是master分支的代码, 而master分支的代码是部署到生产环境的
部署的服务器是:GROUP1
图片.png
图片.png
图片.png
  • 在构建脚本中, 定义克隆的分支
#!/bin/bash
BRANCH=$1
GROUP=$2


cd /data/jenkins/qq && rm -rf web-02

git clone -b ${BRANCH} git@10.0.0.239:qq/web-02.git

echo "部署的分支是:${BRANCH}"
#echo "部署的服务器是:${GROUP}"

  • 测试部署克隆
图片.png
jenkins@jenkins:/data/jenkins/qq/web-02$ git status
On branch develop
Your branch is up to date with 'origin/develop'.

nothing to commit, working tree clean
jenkins@jenkins:/data/jenkins/qq/web-02$ git branch
* develop

3.3 通过选项参数, 控制部署的服务器组

jenkins@jenkins:/data/jenkins$ vim tomcat-1.sh 

#!/bin/bash
BRANCH=$1
GROUP=$2


if [ ${GROUP} == GROUP1 ]; then
    IP_LIST="10.0.0.199"
elif [ ${GROUP} == GROUP2 ]; then
    IP_LIST="10.0.0.209 10.0.0.219"
fi

echo "${GROUP} ----> ${IP_LIST}"

#cd /data/jenkins/qq && rm -rf web-02

#git clone -b ${BRANCH} git@10.0.0.239:qq/web-02.git

#echo "部署的分支是:${BRANCH}"
#echo "部署的服务器是:${GROUP}"

jenkins@jenkins:/data/jenkins$ bash tomcat-1.sh master GROUP1
GROUP1 ----> 10.0.0.199
图片.png
#测试关闭指定服务器组的tomcat服务

jenkins@jenkins:/data/jenkins$ vim tomcat-1.sh 

#!/bin/bash
BRANCH=$1
GROUP=$2


if [ ${GROUP} == GROUP1 ]; then
    IP_LIST="10.0.0.199"
elif [ ${GROUP} == GROUP2 ]; then
    IP_LIST="10.0.0.209 10.0.0.219"
fi

for node in ${IP_LIST}; do
    ssh tomcat@${node} "/etc/init.d/tomcat.sh stop"
done

#echo "${GROUP} ----> ${IP_LIST}"

#cd /data/jenkins/qq && rm -rf web-02

#git clone -b ${BRANCH} git@10.0.0.239:qq/web-02.git

#echo "部署的分支是:${BRANCH}"
#echo "部署的服务器是:${GROUP}"

jenkins@jenkins:/data/jenkins$ bash tomcat-1.sh master GROUP1
正在判断Tomcat运行状态, 倒计时3秒
3
2
1
Tomcat正在运行, 进程ID为5167, 共1个进程
三秒后, 准备关闭Tomcat
3
2
1
已尝试关闭Tomcat, 30秒后检查是否关闭成功!
3
2
1
Tomcat关闭成功!

验证10.0.0.199的tomcat服务关闭成功

jenkins@jenkins:/data/jenkins$ curl 10.0.0.199:8080/myapp/
curl: (7) Failed to connect to 10.0.0.199 port 8080: Connection refused
验证10.0.0.199的tomcat服务启动成功

jenkins@jenkins:/data/jenkins$ vim tomcat-1.sh 

#!/bin/bash
BRANCH=$1
GROUP=$2


if [ ${GROUP} == GROUP1 ]; then
    IP_LIST="10.0.0.199"
elif [ ${GROUP} == GROUP2 ]; then
    IP_LIST="10.0.0.209 10.0.0.219"
fi

for node in ${IP_LIST}; do
    ssh tomcat@${node} "/etc/init.d/tomcat.sh start"
done

#echo "${GROUP} ----> ${IP_LIST}"

#cd /data/jenkins/qq && rm -rf web-02

#git clone -b ${BRANCH} git@10.0.0.239:qq/web-02.git

#echo "部署的分支是:${BRANCH}"
#echo "部署的服务器是:${GROUP}"

jenkins@jenkins:/data/jenkins$ bash tomcat-1.sh master GROUP1
正在判断服务状态, 请稍等!
倒计时三秒钟!
3
2
1
Tomcat没有运行, 三秒后准备启动!
3
2
1
Tomcat started.
Tomcat启动已经执行, 五秒后判断是否启动成功!
5
4
3
2
1
Tomcat启动成功! 共1个java进程, 其PID为5660

  • 到此, 确保了可以通过构建脚本, 对于指定的服务器, 通过选项和字符参数进行服务的停止和启动, 以及根据分支, 进行代码克隆

4 灰度部署案例-静态页面

  • 实现qq/web-02项目的灰度发布, 先升级tomcat-1
三台tomcat服务器
10.0.0.199
10.0.0.209
10.0.0.219
灰度服务器: 10.0.0.199
其余服务器: 10.0.0.209, 10.0.0.219

4.1 准备工作

    1. 把jenkins上的项目以及workspace目录清空
    1. 准备sonar-project.properties文件, 放到web-02项目, 和代码一起提交到Gitlab
sonar.projectKey=sonarqube-qq-web-02-projectKey
sonar.projectName=sonarqube-qq-web-02-projectName
sonar.projectVersion=1.0

# Jenkins克隆代码到/var/lib/jenkins/workspace/目录, 该文件会在web-02目录下, sonar-scanner会扫描web-02中的所有文件

sonar.sources=./  
sonar.language=html
sonar.sourceEncoding=UTF-8

    1. Jenkins服务器以及tomcat服务器安装zip和unzip命令
root@jenkins:~# apt -y install zip unzip
root@tomcat-1:~# apt -y install zip unzip
root@tomcat-2:~# apt -y install zip unzip
root@tomcat-3:~# apt -y install zip unzip
    1. haproxy安装socat
root@ha-1:~# apt -y install socat
root@ha-2:~# apt -y install socat
    1. 将Jenkins服务器jenkins账号的公钥, 传给haproxy服务器的root账号, 做免秘钥认证.
      jenkins是以jenkins用户连接到haproxy, 而haproxy用root执行服务器上下线
jenkins@jenkins:~$ ssh-copy-id root@10.0.0.179
jenkins@jenkins:~$ ssh-copy-id root@10.0.0.189

4.2 修改构建配置

  • 选项参数GROUP, 用来控制部署/回滚的服务器
  • 选项参数METHOD, 表示此次部署是升级还是回滚
图片.png
  • 选项参数BRANCH, 表示此次部署的是哪个分支
图片.png
  • 构建脚本
图片.png
#!/bin/bash
DATE=`date +%Y-%m-%d_%H-%M-%S`
# METHOD变量用来指定本次执行是升级代码还是回滚代码
METHOD=$1
# BRANCH变量用来指定本次执行部署或者回滚的分支
BRANCH=$2
# 服务器分组
# GROUP1灰度环境服务器,10.0.0.199
# GROUP2灰度以外的服务器, 10.0.0.209,10.0.0.219
# GROUP3一次新部署所有服务器, 用于紧急的服务升级, 比如需要执行紧急的bug修复
GROUP_LIST=$3

ip_list(){
  if [[ ${GROUP_LIST} == "GROUP1" ]];then
     Server_IP="10.0.0.199"
     
     echo "灰度服务器地址为: ${Server_IP}"
     

  elif [[ ${GROUP_LIST} == "GROUP2" ]];then
     Server_IP="10.0.0.209 10.0.0.219"
     
     echo "剩余服务器地址为: ${Server_IP}"
     

  elif [[ ${GROUP_LIST} == "GROUP3" ]];then
     Server_IP="10.0.0.199 10.0.0.209 10.0.0.219"
     
     echo "所有服务器地址: ${Server_IP}"
     
  fi
}

clone_code(){
  
  echo "即将开始克隆 ${BRANCH} 分支的代码"
  
  cd /var/lib/jenkins/workspace && rm -rf web-02 && git clone -b  ${BRANCH} git@10.0.0.239:qq/web-02.git
  
  echo "${BRANCH}分支代码clone完成,准备开始代码扫描"
  
}

scanner_code(){
  cd /var/lib/jenkins/workspace/web-02 && /apps/sonar-scanner/bin/sonar-scanner 
  
  echo "代码扫描完成,请打开sonarqube查看扫描结果"
  
}

code_maven(){
  echo  "cd /var/lib/jenkins/workspace/web-02 && mvn clean package -Dmaven.test.skip=true"
  
  echo "代码编译完成"
  
}


make_zip(){
  cd /var/lib/jenkins/workspace/web-02 && zip -r web-02.zip ./
  
  echo "代码打包完成"
  
}



down_node(){
  for node in ${Server_IP};do
    ssh root@10.0.0.179 "echo "disable server  tomcat/${node}" | socat stdio /var/lib/haproxy/admin.sock"
    
    echo "${node} 从负载均衡10.0.0.179下线成功"
    
    ssh root@10.0.0.189 "echo "disable server  tomcat/${node}" | socat stdio /var/lib/haproxy/admin.sock"
    
    echo "${node} 从负载均衡10.0.0.189下线成功"
    
  done
}

scp_zipfile(){
  for node in ${Server_IP};do
    scp /var/lib/jenkins/workspace/web-02/web-02.zip  tomcat@${node}:/data/tomcat/tomcat_zip/web-02-${DATE}.zip
    ssh tomcat@${node} "unzip /data/tomcat/tomcat_zip/web-02-${DATE}.zip  -d /data/tomcat/tomcat_file/web-02-${DATE} && rm -rf  /data/tomcat/tomcat_webapps/myapp && ln -sv  /data/tomcat/tomcat_file/web-02-${DATE} /data/tomcat/tomcat_webapps/myapp"
  done
}

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

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

  done
}

web_test(){

  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
}

add_node(){
  node=$1
  
  echo "准备添加${node}到负载均衡"
  

  if [ ${node} == "10.0.0.199" ];then
    ssh root@10.0.0.179 ""echo enable  server tomcat/${node}" | socat stdio /var/lib/haproxy/admin.sock"
    ssh root@10.0.0.189 ""echo enable  server tomcat/${node}" | socat stdio /var/lib/haproxy/admin.sock"
    
    echo "灰度部署环境服务器-->10.0.0.199 部署完毕,请进行代码测试!"
    
  else

      ssh root@10.0.0.179 ""echo enable  server tomcat/${node}" | socat stdio /var/lib/haproxy/admin.sock"
      ssh root@10.0.0.189 ""echo enable  server tomcat/${node}" | socat stdio /var/lib/haproxy/admin.sock"


  fi
}

rollback_last_version(){
  for node in ${Server_IP};do
    
    echo $node
    
    CURRENT_VERSION=`ssh tomcat@${node} ""/bin/ls -l  -rt /data/tomcat/tomcat_webapps/ | awk -F"->" '{print $2}'  | tail -n1""`
    CURRENT_VERSION=`basename ${CURRENT_VERSION}`
    
    echo "当前版本为: $CURRENT_VERSION"
    
    LAST_VERSION=`ssh  tomcat@${node}  ""ls  -l  -rt  /data/tomcat/tomcat_file/ | grep -B 1 ${CURRENT_VERSION} | head -n1 | awk '{print $9}'""`
    
    echo "上一个版本为: $LAST_VERSION"
    
    ssh tomcat@${node} "rm -rf /data/tomcat/tomcat_webapps/myapp && ln -sv  /data/tomcat/tomcat_file/${LAST_VERSION} /data/tomcat/tomcat_webapps/myapp"
  done 
}

delete_last_version(){
  for node in ${Server_IP};do
    ssh tomcat@${node} "rm -rf /data/tomcat/tomcat_zip/*"
    NUM=`ssh tomcat@${node}  ""/bin/ls -l -d -rt /data/tomcat/tomcat_file/web-02-* | wc -l""`
    
    echo "当前历史构建数量为: $NUM"
    
    if [ ${NUM} -gt 5 ];then
      # 只保留最近5个版本, 超过5个, 那么下次部署完, 就删除当前最旧的一个版本
      NAME=`ssh tomcat@${node} ""/bin/ls -l -d   -rt /data/tomcat/tomcat_file/web-02-* | head -n1 | awk '{print $9}'""`
      ssh tomcat@${node} "rm -rf ${NAME}"
      
      echo "${node} 删除历史版本 /data/tomcat/tomcat_file/${NAME}成功!"
      
    fi
  done 
}

main(){
   case $1  in
      deploy)
        ip_list;        
        clone_code;
        scanner_code;
        #code_maven;
        make_zip;
        down_node;
        stop_tomcat;
        scp_zipfile;
        start_tomcat;
        web_test;
        delete_last_version;
         ;;
      rollback)
        ip_list;
        #echo ${Server_IP}
        down_node;
        stop_tomcat;
        rollback_last_version;
        start_tomcat;
        web_test;
         ;;
    esac
}

main $1 $2 $3

4.3 部署测试

图片.png
  • 先部署灰度服务器, GROUP1, 测试没问题后, 部署GROUP2
  • 如果发现全部升级后, 出现bug, 可以直接rollback GROUP3
  • 如果是部署开发/测试环境, 那么还需要做额外的判断, 添加开发/测试环境的服务器, 在Jenkins上创建单独的目录存放测试环境的代码, 然后传给测试环境服务器.
  • 整个灰度升级过程是分多个阶段的, 首先代码克隆后, 要进行代码质量检测, 检测通过后, 会进行灰度服务器的代码拷贝, 并且把灰度服务器在负载均衡器上线, 此时, 可以给灰度服务器专门配置一个vip, 在haproxy添加一个灰度服务器的监听组, 让开发测试人员, 修改本地hosts文件, 把网站的域名, 解析到灰度服务器的vip, 之后进行测试, 测试没问题, 再把灰度服务器上线到生成环境的vip组上. 之后再升级剩余的服务器
  • 如果想基于指定版本进行升级或者回滚, 可以基于tag号标签, 或者再给构建脚本传一个参数, 先确定要回滚到的指定的目录名, 如,web-02-2021_xx_xx-xx-xx-xx, 之后把这个目录名, 作为参数, 传给构建脚本, 修改软链接, 直接指向这个指定的版本
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


listen canary                                                                                                                                                                
    bind 10.0.0.189:80
    mode http
    log global
    server 10.0.0.199 10.0.0.199:8080 check inter 3000 fall 2 rise 5
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 label eth0:0 dev eth0
        10.0.0.189 label eth0:1 dev eth0          # 189这个vip专门给内网用户使用, 并不会在防火墙做解析, 因此, 公网用户是访问不到的                                                                                                                           
    }
}


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

推荐阅读更多精彩内容

  • 4 常见代码部署方式 基础工作: 4.1 蓝绿部署 蓝绿部署指的是不停老版本代码(不影响上一个版本访问),而是在另...
    随便写写咯阅读 1,334评论 0 6
  • 【 ①Java代码自动部署-总结简介】 代码部署是每一个软件开发项目组都会有的一个流程,也是从开发环节到发布功能必...
    程序员日常填坑阅读 600评论 0 1
  • jenkins要想在远程服务器执行命令,发送文件等操作,必然要处理jenkins与远程机的免交互问题。下面如何让j...
    阿当运维阅读 1,220评论 0 9
  • 我是黑夜里大雨纷飞的人啊 1 “又到一年六月,有人笑有人哭,有人欢乐有人忧愁,有人惊喜有人失落,有的觉得收获满满有...
    陌忘宇阅读 8,503评论 28 53
  • 信任包括信任自己和信任他人 很多时候,很多事情,失败、遗憾、错过,源于不自信,不信任他人 觉得自己做不成,别人做不...
    吴氵晃阅读 6,161评论 4 8