golang jenkins部署及slb平滑上线方案

前言:

      换了份新工作,又是一个新的开始。正题开始,最近在折腾golang的部署及平滑上线,写了几年的golang项目了,按道理来说很简单平常的事情,但在我们这有些小麻烦了   。 该文章后续仍在不断的更新修改中, 请移步到原文地址 http://xiaorui.cc/?p=5476

平滑上线的问题

      先说下平滑上线的问题,阿里云的slb想来大家应该都用过。aliyun slb跟nginx upstream最大的区别在于不支持next_upstream方法。nginx next_upstream的参数控制当某节点不可用时,nginx会调度到下一个可用节点,不停尝试直到访问成功或者next_upstream_timeout超时中断。

      换句话说,nginx upstream默认不加插件是被动检测api可用的过程,而像阿里云的slb是主动探测,这个跟haproxy是一样的,检测完了后,才会刷新可用节点列表。

      如果slb的检测间隔是5s,某节点当前是可用的,距离下次探测之前节点突然不可用,那么这段时间窗口期内,slb还是会把请求转发到该节点,那么请求肯定是异常了。当然你可以把检测间隔调成1s,顶多就失败1s。

      如何在现有的条件下,进行平滑上线启动,优雅上线,尽量减少异常? 因为是个新业务,后面会频繁上下线,如果不解决该问题会导致丢失任务,虽然有多方的重试,不会真的丢失数据,但时不时的微信告警让人厌烦。所以,我们要做的只是尽量减少失败罢了 !!!

以前怎么解决平滑上线?

直接操作阿里云的slb api,删停节点,后面的请求自然就不走被删停的节点,我们就可以升级该节点了(当然,你的服务本身也要做好优雅退出,不能kill就退出)。如果不选择用slb api删除节点,而是把权重配置为0,那么新请求新连接就不会去该节点,但是老请求还是会过来,直到连接的关闭,所以单纯配置权重不是很稳。

那么现在怎么玩?

上海的ops不会给提供slb的api。我想到的方法很简单,我们知道阿里云的slb需要节点提供一个/ping接口,主要http code是2xx就可用。那么我们可以这样,升级一个服务之前,先触发一个信号,把/ping的返回状态重置异常,然后等待slb的探测间隔的时间,这个期间slb会下发一波探测,你的/ping不可用,自然就标记为异常,下次不会再转发,这时候你就可以升级了。升级后,ping为正常的200。

下面是控制服务的shell脚本,这提取了stop部门。逻辑很简单,先发送sigusr2信号,等待5秒,然后再去kill信号,如果20秒内还没有优雅退出,就强制kill -9干掉。

// xiaorui.cc

...

function stop()
{
    check_pid
    running=?
    if [[running -eq 0 ]];then
        echo_info "PROJECT is not running, don't need stop"
        return
    fi

    pid=`catPIDFILE`
    if [[ "pid" -gt 0 ]]; then
        echo_info "PROJECT will close ping api..."
        kill -USR2 pid   # 关闭ping接口
        sleep 5
        killpid
        echo_info "PROJECT will stop srv..."
    else
        echo_panic "not found{PROJECT} pid"
        return
    fi

    c=0
    while true
    do
        if [ c -gt 20 ];then
            echo_info "ensure force kill -9"
            kill -9pid >> STDOUT 2>&1
            break
        fi
    	echo_info "get status sincec seconds"
        check_pid
        running=?
        if [running -gt 0 ];then
            sleep 1
            let c++
            continue
       ....
}

...

下面是 Golang的监听信号及修改 /ping 返回的逻辑。代码很简单,就是注册sigusr2信号及回调方法。回调方法就是修改一个对象,表示是否要关停ping接口。如果关停,返回非200状态。

// xiaorui.cc

// kill -USR2 ${pid}
func ClosePingAPI() {
	c := make(chan os.Signal, 1)
	signal.Notify(c, syscall.SIGUSR2)
	go func() {
		for range c {
			router.ClosePingAPI()
			tool.Log.Info("recv signal sigusr2 for close ping api")
		}
	}()
}

func (r Router) ping(q *gin.Context) {
	if PingStatus {
		q.JSON(http.StatusOK, types.Common{Code: 0, Message: "success"})
	} else {
		q.JSON(http.StatusNotImplemented, types.Common{Code: -1, Message: "active ping api is already closed"})
	}
}

func ClosePingAPI() {
	PingStatus = false
}

jenkins上线部署问题

说下,我以前公司golang上线打包方式的三种方式。

一种:

     基础架构组件,使用rpm打包,推送到yum仓库,然后进行部署。

第二种:

     使用docker容器打包,继而推送到docker私有仓库,然后进行部署。

第三种:

     根据你的项目的不同,如果你是golang的项目,他会帮你构建GOPATH路径,很方便就可以完成打包上线。


由于网络区域问题,暂时不能使用docker的方法上线。 rpm打包适合基础组件,我这类偏业务的系统肯定不能用rpm了。那么就剩下第三种了,问题来了。。。

大家的golang项目中的import路径多是全路径,第三方的包我们可以通过dep扔到vendor目录里。但是jenkins的部署逻辑是这样,你先在页面选择项目及分支,jenkins会异步的把你的git代码pull到jenkins机器的一个目录里,比如 /data/jenkins/。你的项目名是featrue_gateway,那么在jenkins目录就是 /data/jenkins/featrue_gateway 。你怎么指定GOPATH来build代码?

以前公司的运维们针对golang项目在一个路径排列,比如你的项目是http://git.xiaorui.cc/machine/featrue_gateway,  那么在编译机也就是jenkins里路径是这样的 /data/jenkins/go/src/git.xiaorui.cc/machine/featrue_gateway, 对于我们来说,只需要相对路径的指定gopath就可以了。

// xiaorui.cc

import (
    "git.xiaorui.cc/machine/featrue_gateway/libs/pidfile"
    "git.xiaorui.cc/machine/featrue_gateway/libs/primary_ctl"
    "git.xiaorui.cc/machine/featrue_gateway/libs/connect_pool"
    .... 
)

就这个问题跟上海的ops来回扯了好几次。后来想想,为毛非要依赖他的路径?干脆我在当前路径下,设立一组路径,./tmp/go/src/git.xiaorui.cc/machine/feature_gateway/…,我makefile只需要gopath到当前路径的tmp就可以了。

// xiaorui.cc

rm -rf tmp
tmp_path=tmp/src/git.xiaorui.cc/feature-gateway/machine
mkdir -p tmp_path
cp -rf `ls . | grep -E -v "^(tmp)"` tmp_path

cp -rf scripts/deploy/control.sh{PROJECT_PATH}/
cp -rf scripts/deploy/init.sh     {PROJECT_PATH}/
cp -rf scripts/deploy/feature_gateway_logrotate.conf{PROJECT_PATH}/

# real build
export GOPATH=`pwd`/tmp; go build -ldflags "-X main.BuildTime={BUILD_TIME}" -o{PROJECT_PATH}/feature_gateway cmd/feature_gateway.go

总结:

    遇到的这两个问题,看起来是由于运维同学支持不给力导致的,但细想下大家都有难处,运维也有自己的职责规范,所以互相谅解吧。这样也促进我们去思考更多的解题思路。


大家觉得文章对你有些作用! 如果想赏钱,可以用微信扫描下面的二维码,感谢!
另外再次标注博客原地址  xiaorui.cc