白桦林雪 發表於 2019-3-4 00:33:00

Asp.net Core 使用Jenkins + Docker 实现持续集成、自动化部署(四):发布与回滚

<p><img src="https://img2018.cnblogs.com/blog/641760/201903/641760-20190303144200882-234185842.png" alt="“deploy”的图片搜索ç&quot;“æžœ" loading="lazy"></p>
<h1 id="写在前面">写在前面</h1>
<p>我们以前windows跑.net Framework程序的时候,发布,自己乖乖的替换程序;备份,也是自己一个一个的重命名备份;回滚,发布遇到问题的回滚更是不用说了;运维很是怕我们 这些用windows的啊;</p>
<p>那全面拥抱linux的一一.net core 时代 ,是如何处理这些个问题的呢?</p>
<p>噔噔蹬蹬~请往下看。</p>
<h1 id="运行环境">运行环境</h1>
<p>centos:7.2<br>
cpu:1核 2G内存 1M带宽</p>
<p>Jenkins ver. 2.150.1</p>
<p>一台安装jenkins的机器。</p>
<blockquote>
<p>(本文例子不一定要安装jenkins,但实际项目是要用jenkins的)</p>
</blockquote>
<h1 id="背景">背景</h1>
<p>我们目前的应用部署环境是这样划分的(暂定):</p>
<h2 id="开发环境">开发环境</h2>
<p>环境变量:Development</p>
<p>开发环境就是我们平时的开发用的机器,错误、异常尽可能多的报出来这种。css、js、页面文件等各种静态资源也不做压缩处理,连接测试库;</p>
<p>开发环境的部署:开发人员按自己习惯自己部署;</p>
<h2 id="测试环境">测试环境</h2>
<p>环境变量:Staging</p>
<p>测试环境也就是测试同学测试用的环境,为了贴合生产环境的多机器部署,我们测试机器也有多台,目前我们搭建了jenkins可由测试同学自己部署;错误信息已做捕捉处理,静态文件同样不压缩,连接测试库;</p>
<p>测试环境的部署:docker+docker-compose部署,我们在项目里面编写好了Staging.Dockerfile、docker-compose.yml还有对应的测试环境发布的shell脚本,借助jenkins来进行参数化的构建。参数包括程序运行的端口、绑定的ip,consul配置等等。哦对了,我们目前的构建步骤大概是:</p>
<ol>
<li>
<p>去gitlab拉取最新程序代码;</p>
</li>
<li>
<p>执行单元测试和集成测试,<strong>只有通过单元测试和集成测试才能继续步骤3,否则部署失败终止</strong>:</p>
</li>
<li>
<p>dotnet restore-&gt;build-&gt;publish,将生成产品打包成一个镜像;</p>
</li>
<li>
<p>使用docker-compose down 停止、移除上次的构建;</p>
</li>
<li>
<p>使用docker-compose up 这个强大的命令,构建新的镜像、启动容器;</p>
</li>
<li>
<p>清除临时镜像,构建完成;</p>
</li>
</ol>
<blockquote>
<p>单元测试用dotnet test 命令;</p>
<p>这里我们还可以看到,配置文件也一并被打包到镜像里面了,修改配置文件也需要重新构建的;</p>
</blockquote>
<h2 id="预生产环境">预生产环境</h2>
<p>环境变量:Staging</p>
<p>预生产环境是相对于测试环境来说,无论数据、配置还是架构都是更加接近生产环境的存在了。一般还是连接的数据库是预生产环境的数据库(同步了生产环境的数据的),甚至有的使用会直接连接生产环境的库(一般不练、只读账号等控制);不过我们公司还是连接的测试库<img src="https://img2018.cnblogs.com/blog/641760/201903/641760-20190303144200314-851119181.png" alt="img" loading="lazy"></p>
<p>然后静态文件压缩啊、什么的这些,生产环境怎么处理,这里也怎么处理;</p>
<p>通过测试环境测试的程序才可以部署到这里,这里测试通过后,才可以部署到生产环境;</p>
<p>预生产环境的部署:由项目负责人或者运维部署,需要比较大权限才可以;</p>
<h2 id="生产环境"><strong>生产环境</strong></h2>
<p>环境变量:Production</p>
<p>生产环境一般应配置为最大限度地提高安全性、性能和应用可靠性,包括但不限于以下举措:</p>
<ol>
<li>全面启用分布式缓存</li>
<li>客户端资源被捆绑和缩小,并可能从 CDN (网络分发)提供。</li>
<li>必须禁用诊断错误页。</li>
<li>启用友好错误页、一致的错误响应。</li>
<li>启用生产记录和监视。</li>
</ol>
<p>生产环境的部署:运维部署,我们开发没有权限了;</p>
<p>部署的背景我们的条件等等大概讲完了,下面我们说说生产环境我们怎么设计容器的。</p>
<h1 id="生产环境的容器设计">生产环境的容器设计</h1>
<p>由于生产环境经常需要修改配置、保留日志信息、需考虑程序的备份与回滚等等,我们不能像上面的测试环境一样,把整个发布的产品打包成一个镜像了,我们需要做特殊的处理;</p>
<p>熟悉docker的同学,肯定会想到:挂载</p>
<p>对的,我们就这么处理,我们用docker -v 处理这头痛的问题;</p>
<h2 id="程序的目录结构">程序的目录结构</h2>
<p><img src="https://img2018.cnblogs.com/blog/641760/201903/641760-20190303144159861-986860629.png" alt="1551588236442" loading="lazy"></p>
<p>我们程序的目录结构是这样的:</p>
<p>backs:放历史版本的程序文件,按备份日期压缩命名;</p>
<p>logs:程序的运行日志文件;</p>
<p>program:当前运行的程序;</p>
<blockquote>
<p>logs 和 program 目录,使用 docker -v 挂载;</p>
</blockquote>
<p>backs目录截图:</p>
<p><img src="https://img2018.cnblogs.com/blog/641760/201903/641760-20190303144159395-1250758849.png" alt="1551591640526" loading="lazy"></p>
<h1 id="发布">发布</h1>
<h2 id="发布步骤">发布步骤</h2>
<ol>
<li>
<p>同步通过测试的预生产环境的程序文件;</p>
</li>
<li>
<p>压缩、备份上一版本的程序文件;</p>
</li>
<li>
<p>通过更改文件夹名称的方式,当前运行程序替换为最新的;</p>
</li>
<li>
<p>重启程序;</p>
</li>
<li>
<p>心跳检测:通过输出部署成功,未通过执行回滚操作。</p>
</li>
</ol>
<h2 id="发布脚本productionpublishsh">发布脚本(Production.Publish.sh)</h2>
<pre><code class="language-shell">#!/bin/bash

function success()
{
echo -e "\033[32m $1 \033[0m"
}
function error()
{
   echo -e "\033[31m\033[01m $1 \033[0m"
}

echo "publish beging。。。。。。"
remotePath=$1
healthCheckUrl=$2
defaulPaht= $3;
bashPath=${defaulPaht:=`pwd`}

if [ ! $remotePath ]; then
    echo "warn:remotePath should't be empty!"
    exit
fi

if [ ! $bashPath ]; then
    error "error:bashPath should'tbe empty!"
    exit
fi
echo "bashpath is ${bashPath}"
programPath="${bashPath}/program"
logPath="${bashPath}/logs"
backPath="${bashPath}/backs"
publisTemp="${bashPath}/publisTemp"
mkdir -p $programPath
mkdir -p $logPath
mkdir -p $backPath
mkdir -p $publisTemp

#remote git or scp
#这里同步预生产环境的程序文件,这里写死了ip只是示例,scp也只是示例
#大家可以采用更安全,更有效率的同步文件方式
scp -rroot@139.199.196.67:${remotePath}"/.*"${publisTemp}

if [ $? ]; then
    echo "info:copy successful!"
    #压缩、备份当前运行程序到backs文件夹
    backFileName=`date +%Y%m%d%H%M%S`".tar.gz"
    `cd ${programPath} &amp;&amp; tar -zcPf ${backPath}/${backFileName} *`

    #replace
    #替换程序
    if [ $? ]; then
      mv $programPath ${programPath}"Old"
      mv $publisTemp ${programPath}
      rm -r ${programPath}"Old"

      #publis fail ,then Production.Rollback
      if [ $healthCheckUrl ]; then
            curl $healthCheckUrl
            if [ $? -ne 0 ]; then
                error "error:public failed!"
                #心跳检测失败,执行回滚
                if [ -f "Production.Rollback.sh" ];then
                  echo "************************************ exec Rollbacking...... ************************************"
                  ./Production.Rollback.sh ${backPath}
                else
                  error "error:Production.Rollback.sh is not existing!";
                fi
                exit
            fi
      fi
      success "publish Successful!"
    else
      error "error:file tar failed!"
    fi
else
    error "error:remote files copy failed,Maybe you should checkout your ssh auth!"
fi


</code></pre>
<h2 id="dockerfile">Dockerfile</h2>
<p>Dockerfile比较简单</p>
<pre><code class="language-dockerfile">FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base
WORKDIR /app
ARG RUN_PORT=${RUN_PORT:-""}
ARG CONSUL_TO_NETCOREHOST=${CONSUL_TO_NETCOREHOST:-""}
ARG ASPNETCORE_ENVIRONMENT=${ASPNETCORE_ENVIRONMENT:-""}
ENV RUN_PORT=${RUN_PORT} CONSUL_TO_NETCOREHOST=${CONSUL_TO_NETCOREHOST} ASPNETCORE_ENVIRONMENT=${ASPNETCORE_ENVIRONMENT}
ENTRYPOINT ["dotnet", "Member.WebApi.dll"]
</code></pre>
<h2 id="docker-compose">docker-compose</h2>
<pre><code class="language-dockerfile">version: '3.4'

services:
member.webapi:
    image: memberwebapi${RUN_PORT}
    build:
      context: .
      dockerfile: ${ASPNETCORE_ENVIRONMENT}.Dockerfile
    network_mode: "host"
    restart: always

    environment:
      - RUN_PORT=${RUN_PORT}
      - ASPNETCORE_ENVIRONMENT=${ASPNETCORE_ENVIRONMENT}
      - CONSUL_TO_NETCOREHOST=${CONSUL_TO_NETCOREHOST}

    command:
      - "--port"
      - "${RUN_PORT}"

        #就是这里挂载
    volumes:
      - ../program/:/app/
      - ../logs/:/app/logs
</code></pre>
<h1 id="回滚">回滚</h1>
<p>回滚其实就是发布的逆操作;</p>
<p>发布是:同步最新程序-&gt;备份当前运行程序-&gt;替换;</p>
<p>回滚是-&gt;找到上一次的备份-&gt;删掉的当前运行程序-&gt;替换;</p>
<h2 id="productionrollbacksh">Production.Rollback.sh</h2>
<pre><code>#!/bin/bash

echo "rollback beging。。。。。。"
defaulPaht= $1;
bashPath=${defaulPaht:=`pwd`}
programPath="${bashPath}/program"
backPath="${bashPath}/backs"
lastFile=`cd ${backPath} &amp;&amp;ls -t |head -n1|awk '{print $0}'`

if [ ! $lastFile ];then
    echo "error:none backup program!"
fi

lastFilePath="${backPath}/${lastFile}"
echo $lastFilePath
if [ -f $lastFilePath ];then
    echo "rollback program:${lastFilePath}"   
    programOldPath="${programPath}Old"
    mkdir -p ${programOldPath}
    tar zxvf ${lastFilePath} -C ${programOldPath}

    #replace
    if [ $? ]; then
      rm -r ${programPath}
      mv ${programOldPath} ${programPath}
      echo"rollback Successful!"
    else
      echo "error:backup program is not existing!"
    fi   
else
    echo "error:backup program is not existing!"
fi



</code></pre>
<p>最后贴一个运行截图:</p>
<p><img src="https://img2018.cnblogs.com/blog/641760/201903/641760-20190303144158557-1257562874.png" alt="1551590930566" loading="lazy"></p>
<h1 id="总结">总结</h1>
<p>毫不夸张地说,Jenkins + Dockor 让.net 完全从一个刀耕火种的原始人一下子穿越到了全自动化的现代;</p>
<p>文章的思路可以借鉴,脚本改改也可以用,但需理解思路;</p>
<p>有的同学可能会问,为什么生产环境的部署,不能像测试环境一样直接拉取master的代码构建,我这里的回答是涉及到配置的权限问题、devops的学习到位问题。历史原因等,我们暂定这样,后面实践,我乐于分享;</p>
<p>本文的实践都有很大的局限性,比如有现成的工具、有更强大的插件等等可以更简单的去解决这个问题之类的,我可能还不知道;比如我的shell写的一塌糊涂等等。。欢迎沟通,不理赐教。</p>
<p>晚安~</p><br><br>
来源:https://www.cnblogs.com/xiaxiaolu/p/10468612.html
頁: [1]
查看完整版本: Asp.net Core 使用Jenkins + Docker 实现持续集成、自动化部署(四):发布与回滚