之前的文章曾写了Git怎么用和Git服务器怎么搭建,一个公司仅仅只有这些还是远远不够的,这些仅仅是对源码的管理,程序猿开发好的源码怎么编译、打包、部署上线呢?下面就需要运维来解决这个问题了,不过这一段时间公司老大让老夫负责公司的源码由SVN迁Git,有幸接触到一点这块的知识,今天记录一下,万一老夫哪天失业了转行去做运维了呢!

在开始正式文章之前,首先感谢一下我在小马金融的同事:张学军,此脚本原始版本是由学军提供的,然后加上老夫的优化,可以说没有学军的无私帮助,老夫不可能完成这个脚本的,所以,谢谢,学军!

#!/bin/bash

# 1. 环境变量配置
export JAVA_HOME=/usr/local/jdk1.7.0_67
export JRE_HOME=${JAVA_HOME}/jre
export CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/lib
export PATH=${JAVA_HOME}/bin:$PATH

# 2. 基础路径与变量定义
dir_path="/apps/project/project_name"
work="project_name"
DATE=$(date +%Y%m%d%H%M)
tomcat="apache-tomcat-6.0.41_project_name"

cd "$dir_path"
if [ ! -d wars ]; then
    mkdir wars
fi

# 备份旧的 ROOT 目录
if [ -d ROOT ]; then
    tar cfz "ROOT-$DATE.tar.gz" ROOT
    mv "ROOT-$DATE.tar.gz" wars/
fi

# --- 函数定义 ---

# Git 更新与版本切换函数
git_update() {
    tag_version=$1
    cd "$dir_path/$work"
    
    git checkout master
    git pull
    
    echo "$tag_version"
    
    # 检查 tag 是否存在
    git_tag=$(git tag | grep -x "$tag_version")
    if [ "$git_tag" = "$tag_version" ]; then
        git checkout -b "$(date +%Y%m%d%H%M)" "$git_tag"
    else
        printf "tag number input error\n"
        exit 1
    fi
    
    # 校验当前分支 commit 是否与 tag 一致
    git_id1=$(git log -1 --format=%H)
    git_id2=$(git show "$git_tag" | grep commit | awk '{print $2}' | head -1)
    
    if [ "$git_id1" != "$git_id2" ]; then
        printf "The current git branch where inconsistent tag number is correct, please check the tag number\n"
        exit 1
    fi
}

# Maven 构建函数
build() {
    local build_dir=$1
    local build_work=$2
    
    cd "$build_dir/$build_work"
    mvn package -DskipTests
    
    if [ -d "$build_dir/$build_work/target" ]; then
        cd "$build_dir/$build_work/target"
        return 0
    else
        echo "build $build_dir/$build_work failed"
        exit 1
    fi
}

# --- 主流程 ---

# 1. 版本控制
tag_version=$1
if [ -z "$tag_version" ]; then
    printf "Please enter the version number\n"
    exit 1
else
    git_update "$1"
    echo "$1" >> "${dir_path}/tag.txt"
fi

# 2. 执行构建
build "$dir_path" "$work"
type=$?

if [ $type -eq 0 ]; then
    cd "$dir_path/$work/target"
    # 查找构建出的 war 包或 jar 包 (修正了原脚本 grep 的逻辑)
    # 假设生成的包名包含项目名且以 .war 或 .jar 结尾
    work_name=$(ls | grep "$work" | grep -E "\.war$|\.jar$")
else
    echo "build failure"
    exit 1
fi

# 3. 清理旧部署
if [ -d "$dir_path/ROOT" ]; then
    rm -rf "$dir_path/ROOT"
fi

# 4. 部署新包
cd "$dir_path/$work/target/"
# 将构建好的包重命名为 ROOT (模拟 Tomcat 默认部署)
mv "$work_name" "$dir_path/ROOT"

# 5. 覆盖配置文件
if [ -d "$dir_path/ROOT" ]; then
    # 使用 cp 替代 scp,因为是在本地操作 (原脚本使用 scp 可能是为了远程,若需远程请保留 scp 并补全路径)
    cp "$dir_path/config.properties" "$dir_path/ROOT/WEB-INF/classes/"
    cp "$dir_path/redis.properties" "$dir_path/ROOT/WEB-INF/classes/"
    cp "$dir_path/jdbc.properties" "$dir_path/ROOT/WEB-INF/classes/"
fi

# 6. 重启 Tomcat
# 修正了原脚本中 grep 拼写错误 (gerp -> grep) 和逻辑连写问题
tomcat_status=$(ps aux | grep "$tomcat" | grep -v "grep" > /dev/null; echo $?)

if [ $tomcat_status -eq 0 ]; then
    echo "Stopping Tomcat..."
    kill -9 $(ps aux | grep "$tomcat" | grep -v "grep" | awk '{print $2}')
fi

echo "Cleaning work and temp directories..."
rm -rf "/usr/local/$tomcat/work/*"
rm -rf "/usr/local/$tomcat/temp/*"

echo "Starting Tomcat..."
/usr/local/$tomcat/bin/startup.sh

注:里面的project_name是项目名,老夫隐藏了公司的实际项目名,就以project_name代替了,实际用的时候请根据实际情况修改

首先说这个脚本的语法,如果看不太明白的话,可以参考老夫的这篇文章,一定可以看明白的,但当你看明白之后,一定会有一些迷惑,这是毫无疑问的,因为在第一次执行这个脚本之前还有两项准备工作要做:

  1. 在git_update方法里面一开始就是git checkout,并没有git clone,说明最起码第一次我们要手动先 git clone 一下,当然从第二次开始就不用了;
  2. 我们可以猜到 dir_path 是一个很随意建的目录,但这个脚本里面并没有把编译、打包之后的代码拷贝或剪切到tomcat的webapps目录下并且重命名为ROOT,而是放在了 dir_path 下并且被重命名为了 ROOT ,这和tomcat并没有关系啊,嗯,是的,并没有什么关系,因为这里做了一个软连接,也就是 dir_path 写的 ROOT 和tomcat中webapps下的ROOT做了一个软连接,至于软连接的命令,老夫就不多说了,不知道的自己用Google百度去;

最后要在解释三个问题:

  1. scp,这个大家肯定都看懂了,就是用运维自己的这几个配置文件覆盖源码中的配置文件,为什么要这么做呢?老夫刚工作的时候很不理解,现在终于想明白了这件事,实际上线上的数据库、缓存等等之类的并不能真正的对程序猿开放,这些东西知道的人越少越好,因为程序猿可能会盲目自信(这句话并没有黑程序猿的意思啊,因为老夫本身就是程序猿,仅仅是从安全性上考虑的),去改线上库里面的东西,所以,最多给他一个读的权限而已,线上写的权限,还是运维自己保留的好;
  2. 此脚本是根据系统的tag号进行的上线,即每次上线必须打tag,然后根据此tag进行上线,这样做的好处,老夫在之前Git开发最佳实践中已经说过了,就不多赘述了,不知道的可以看这篇文章
  3. 别忘了给这个脚本加x权限;

综合以上,其实已经没有问题了,但老夫实际操作中还是出现了问题(汗,可能老夫人品有问题),报了一个:/bin/sh^M: bad interpreter: No such file or directory异常,那叫一个郁闷啊,最后查资料得知:这是不同系统编码格式引起的:在windows系统中编辑的.sh文件可能有不可见字符,所以在Linux系统下执行会报以上异常信息,那么这个问题怎么解决呢?其实很简单,有以下两种方法:

1)在Windows下转换:

利用一些编辑器如UltraEdit或EditPlus等工具先将脚本编码转换,再放到Linux中执行。转换方式如下(UltraEdit):File–>Conversions–>DOS->UNIX即可。

2)也可在Linux中转换:

首先要确保文件有可执行权限

#chmod u+x filename

然后修改文件格式

#vim filename

利用如下命令查看文件格式

:set ff

:set fileformat

可以看到如下信息

fileformat=dos

fileformat=unix

利用如下命令修改文件格式

:set ff=unix

:set fileformat=unix

然后存盘退出

:wq

最后再执行文件

#./filename

好了,这就不会再报这个错了

参考资料:http://www.linuxidc.com/Linux/2011-09/42618.htm