用野路子实现JVM项目CI/CD
用野路子实现JVM项目CI/CD
2019-04-26
28 次浏览
概述
这一篇是一个野路子CI/CD,并不是正规化的,因为实现的方式并不是想象中的使用jenkins/teamcity/gitlab/gocd/etc...,当然docker是必不可少的。方案也具备一定的针对性,但是思路才是最重要的不是?
为什么没有使用流行工具呢?
- jenkins老是安装插件失败,我真的很郁闷,这是我前几年的傍身工具,居然现在用不上。
- teamcity很好,我的新宠,但是构建的docker文件必须放到hub.docker.com中
- gocd太难玩,还没入门
- 自建SSL支撑的Docker registry这一路子还不熟
- Git代码在云端(Github、Gitlab、Coding.net、Gitee、etc...)
但是我们要实现“每60秒检查一次云端的Git源码的对应分支是否有更新,如果有,则更新当前分支源码,并清理之前的构建,重新构建,对产出的Jar包构建docker镜像,并使用这个镜像启动新的docker container”的需求,其实简单来说就是我这更新了代码,本地测试服务器就自动打包部署了。
既然知道需求了,接下来就是思考方案了。
方案
CI/CD tools
使用CI/CD工具是我最初的方案,毕竟jenkins用得老6了,所以方案就是
Jenkins -> clean -> build -> build image -> upload image
这样一来,每次都会Push新的docker image到hub中,这个方案简直好得很
- 由于JVM项目一般都有profile的说法,所以一个image,通过传递不同的profile就能在不同环境正确运行。
- docker image在hub中,这样无论在什么地方,都可以去hub中下载到这个image
- ci/cd tools还能根据版本来给image tag不同的版本,对于运维来说可以随时切换不同的版本进行运行。
- 每一步错误都能通过邮件短信等发送到相关责任人,个别Tools还有贴心的钉钉插件通知你哦。
整个都是一条龙服务,完美无缺。但是,有一个问题就是,我们有好几个JVM项目,但是免费的hub只能存储一个image,老板不给钱,这是硬伤。所以有了下面的野路子解决方案。
Shell it!
回到原始时代,一切都是我们自己来吧。命令行也有春天。方案就是
crontab(per 60s) -> deploy.sh
而这个deploy.sh就包含了
git pull -> clean -> build -> docker build -> docker run
这个野路子方案就没有上一个方案好了,他只解决了基本需求
- 能够定时更新代码,并打包部署
他没有解决的是
- 每个步骤中的错误不能够通知到相关责任人
- image不在公共hub中,只有本机docker能够使用
- docker仅仅作为JVM容器,而没有 tag 版本的功能
当然,这些缺点都是可以技术实现的,但我这里只要做到基本需求就好。
实施
我们采用"Shell it"的方案进行实施。接下来我们创建如下的目录结构。
workdir
| --- Dockerfile
| --- app.jar
| --- deploy.sh
| --- logs<dir>
| --- source_root<dir>
上述目录结构中的app.jar文件会在处理完之后删除或者移动。
获取源码
第一步当然是要获取源码了,我们以源码在Coding.net中为例子,在获取源码时,既然是能够自动获取,我们当然不希望还需要我们手动去输入用户密码的情况,所以我们需要使用ssh-key的方式。
打开命令行终端输入 ssh-keygen -t rsa -C "your_email@example.com"( 你的邮箱),连续点击 Enter 键即可。
ssh-keygen -t rsa -b 4096 -C "your_email@example.com"
# Creates a new ssh key, using the provided email as a label
# Generating public/private rsa key pair.
Enter file in which to save the key (/Users/you/.ssh/id_rsa): [Press enter] // 推荐使用默认地址
Enter passphrase (empty for no passphrase): //此处点击 Enter 键即可
成功之后,你的key放置在"/Users/you/.ssh/id_rsa"下,将id_rsa.pub文件中的内容放到coding.net的部署公钥中即可。这样一来,使用git更新代码便畅通无阻。
比对更新
更新源码不是总是能够有新的代码出现,毕竟我们每60秒就更新一次,但是怎么判断是否有更新呢?这里的思路就是比对本地head的sha和远程最新head的sha值是否相同,如果不同,则肯定有更新。相关脚本如下:
l1=`git rev-parse HEAD`
l2=`git ls-remote git@e.coding.net:/you/app.git HEAD | awk '{ print $1 }'`
if [ $l1 = $l2 ];then
echo "no change"
exit 1
else
# clean build package deploy
fi
定时检查
在/etc/crontab中加入如下内容:
*/1 * * * * root /workdir/deploy.sh >> /workdir/logs/cron.log 2>&1
这里的意思是以root用户的角度来执行deploy.sh脚本,并将脚本的输出信息追加在cron.log文件中。
值得注意的是为什么要用root用户呢?原因是docker需要用到root用户,当然也可以不用,但是又给我们找事儿了不是?
编译源码
我们假定我们的项目使用的是Gradle,当然如果你的是Maven,可以类推。
echo "buiding..."
if ./gradlew clean build bootJar -x test;then
echo "build done"
else
exit 1
fi
注意命令中的 "-x test"表示我们skip了测试。
创建镜像
创建镜像就是标准的Docker命令了,
docker build -t group/app-name -f Dockerfile .
Dockerfile:
FROM openjdk:8-jre-alpine
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
VOLUME /tmp
COPY app-1.0.0.jar app.jar
ENTRYPOINT ["java", "-Dspring.profiles.active=test","-jar", "/app.jar"]
简单说一下Dockerfile,使用openjdk:8-jre-alpine的原因是它够小,够用,够快。同时ENV和RUN解决了时区问题。
启动镜像
docker run -d -p 8800:8800 -v /workdir/logs/app/:/logs --restart=always --name test-app group/app-name
好了基本上就结束了。野路子可以玩儿了。
附件
完整的deploy.sh
#!/bin/sh
cd /workdir/source_dir
l1=`git rev-parse HEAD`
l2=`git ls-remote git@e.coding.net:/you/app.git HEAD | awk '{ print $1 }'`
if [ $l1 = $l2 ];then
echo "no change"
exit 1
else
echo "pulling source code"
if git pull;then
echo "source pulled."
else
exit 1
fi
echo "buiding..."
if ./gradlew clean build bootJar -x test;then
echo "build done"
else
exit 1
fi
echo "copy jar..."
cp ./build/libs/*.jar /workdir
echo "copy done"
cd /workdir
docker stop test-app
docker rm test-app
docker rmi group/app-name
if docker build -t group/app-name -f Dockerfile .;then
if docker run -d -p 8800:8800 -v /workdir/logs/:/logs --restart=always --name test-app group/app-name;then
rm app-1.0.0.jar
echo "It's done!"
else
echo "docker container run fail"
exit 1
fi
else
echo "docker image build fail"
exit 1
fi
fi