一直认为不懂部署的开发工程师不是好的开发工程师,以下以一些实例讲解自己在项目中的 Golang 后端部署的情况。

一般部署脚本应该具有构建、启动、停止、回滚已经查看记录日志等功能,以下分别将这些功能以单个脚本的形式给出,当然也可以写成Makefile 的形式。

单个部署脚本的形式,在一个目录下建立如下文件:

bin # 目录,用于存放每次 build 之后存放的二进制文件
app.log # 用来记录的日志文件
log.sh # 实时查看日志
build.sh # 构建
run.sh # 启动某一次编译版本
start.sh # 启动最新版本,并且备份之前前一次运行的版本
shutdown.sh # 停止
rollback.sh # 回滚到上一版本


本例中的 GOPATH=”/go”

log.sh 具体内容:

tail -f -n 200 app.log # 实时查看最后 200 条日志的情况

build.sh 具体内容:

#!/bin/bash

# 项目地址,/go 在 GOPATH 里面
baseProjectDir="/go/src/monitor"

# targetDir 编译后的二进制文件目录
targetDir="bin"

# branch 编译的分支
branch="master"

pwd=`pwd`
# targetFile 编译后的输出文件名称
targetFile=`basename $pwd`

# buildPkg 编译的包名,main.go 所在的包
buildPkg="monitor"

# buildResult 编译结果
buildResult=""

if [ -n "$1" ]; then
  branch="$1"
  echo "Switch branch to ${branch}"
else
  echo "Building Branch: ${branch}"
fi

gitPull() {
  pushd .

  cd "$baseProjectDir"
  git checkout "$branch"
  git pull

  popd
}

goBuild() {
    buildResult=`go build -o "${targetDir}/${targetFile}" "$buildPkg" 2>&1`

    if [ -z "$buildResult" ]; then
      buildResult="success"
    fi
}

gitPull
goBuild

if [ "$buildResult" = "success" ]; then
  cp ${targetDir}/${targetFile} ${targetFile}-new
  chmod +x ${targetFile}-new
else
  echo "build error $buildResult"
  exit
fi

echo "All Complete"


run.sh 具体内容:

#!/bin/bash

# 以后台方式启动程序,并且将日志记录到 app.log
nohup ./$1 >> app.log &


start.sh 具体内容:

#!/bin/bash

pwd=`pwd`
target=`basename $pwd`
# kill
pid=`ps -C ${target} -o pid=`
if [ -n "$pid" ]; then
  echo "Stopping old version, PID: ${pid}"
  if [ "$1" = "-f" ]; then
    # force shutdown
    echo "Force shutdown..."
    kill -9 $(ps -C ${target} -o pid=)
  else
    kill -s 2 $(ps -C ${target} -o pid=)
  fi
  # wait for program to stop
  pid=`ps -C ${target} -o pid=`
  while [ -n "$pid" ]; do
    sleep 1
  done
fi

# upgrade
if [ -f "${target}-new" ]; then
  echo "Upgrading..."
  if [ -f "${target}-backup" ]; then
    backupdt=`date +%Y%m%d-%H`
    mv "${target}-backup" "${target}-backup-${backupdt}"
  fi

  mv ${target} ${target}-backup
  mv ${target}-new ${target}

  echo "Upgrade Complete"
fi

# run
echo "Starting..."
./run.sh ${target}
echo "Done"


shutdown.sh 具体内容:

#!/bin/bash

pwd=`pwd`
target=`basename $pwd`

# kill
pid=`ps -C ${target} -o pid=`
if [ -n "$pid" ]; then

  echo "Stopping old version, PID: ${pid}"
  if [ "$1" = "-f" ]; then
    # force shutdown
    echo "Force shutdown..."
    kill -9 $(ps -C ${target} -o pid=)
  else
    kill -s 2 $(ps -C ${target} -o pid=)
  fi

  # wait for program to stop
  pid=`ps -C ${target} -o pid=`
  while [ -n "$pid" ]; do
    sleep 1
  done

fi

echo "Done"


需要注意的是 kill -s 2 发送的是 Interrupt 中断信号,在项目中要有监听该信号的处理程序,eg:

package main

import (
    "log"
    "os"
    "os/signal"

    "github.com/robfig/cron"
)

func main() {
    i := 0
    c := cron.New()
    spec := "*/3 * * * * ?"
    c.AddFunc(spec, func() {
        i++
        log.Println("cron running:", i)
    })
    c.Start()

    go signalListen()

    select {}
}

func signalListen() {
    c := make(chan os.Signal)
    // 监听 os.Interrupt 并且退出程序
    signal.Notify(c, os.Interrupt, os.Kill)
    for {
        <-c
        os.Exit(0)
    }
}


rollback.sh 具体内容:

#!/bin/bash

pwd=`pwd`
target=`basename $pwd`

# Kill running program
pid=`ps -C ${target} -o pid=`
if [ -n "$pid" ]; then

  echo "Stopping old version, PID: ${pid}"
  if [ "$1" = "-f" ]; then
    # force shutdown
    echo "Force shutdown..."
    kill $(ps -C ${target} -o pid=)
  else
    kill -s 2 $(ps -C ${target} -o pid=)
  fi

  # wait for program to stop
  pid=`ps -C ${target} -o pid=`
  while [ -n "$pid" ]; do
    sleep 1
  done
fi

# Rollback
if [ -f "${target}-backup" ]; then
  echo "Rolling back..."
  if [ -f "${target}" ]; then
    rm "${target}"
  fi
  mv ${target}-backup ${target}
  echo "Rollback Complete"
fi

# run
echo "Starting..."
./run.sh ${target}
echo "Done"