云上编译器,随时随地编代码

2,445 阅读5分钟

  在《Spring Cloud配置服务》中提到本人在小云上瞎折腾,奈何学艺不精,折腾得挺费劲的,为了让折腾变得轻松点,上线了配置服务一事。其实除了折腾配置参数之外,代码的修改也是不可避免的事儿(虽然常常只是增删一句代码)。比如跨域访问资源的时候会被CSRF拦截,为了验证功能,就不得不加上.csrf().disable()试试效果。为了能进一步让我的折腾之路走得更从容,我开始寻找轻量化、免费的CI环境。

  大约是几年前的时候吧,那时候用Github做代码仓库,就顺手白嫖了Travis CI的资源,他的用法很简单,在代码中加入.travis.yml配置文件(可参考这个),这个文件作用是告诉Travis CI需要做什么,然后在Travis CI控制面板中添加Github账户公钥即可。当代码提交到Github代码仓库的master分支以后,会被自动编译成jar包,然后打包进容器,并进一步自动发布到 Docker hub上,而云端只需要更新容器即可实现部署(注1:其实Travis CI可以再调用云端接口,实现真正全自动部署,只不过当时购买的菊厂弹性服务器并未开放相关接口。注2:其实也有办法,Travis CI 推送完DockerHub之后,向Github指定仓库再提交一个issue,利用Github的WebHook向云端发起POST请求,从而触发Docker更新,只不过这条路也太折腾了。) 。

travis ci

对标

  现在小站长的代码仓库早已切换到Gitee, Travis CI又不支持小小的Gitee,没办法只好寻找替代品,不过好在可以以它作为标定。

  能提供类似功能的产品很多,不过要么收费,要么太重载,比如Gitee Go再比如Jenkins。并不是说他们不够好,只不过不太适合我的应用场景罢了。

需求

  1. 能随时快速启动,结束后不常驻系统(毕竟小云资源有限)。
  2. 能编译Spring Cloud工程,并打包为docker (不用发布到DockerHub,因为已经在云上了,直接运行即可)。
  3. 不影响主机环境配置,保持主机稳定运行。

选型

  好消息是Spring Boot现在支持直接编译为Docker了,可参考《SpringBoot 构建 Docker 镜像的 3 种方式》,但是他们都无法满足第三点需求,因为它们至少需要在主机搭好maven环境。那么maven环境会影响主机运行吗?我想可能性很低,但如果主机运行的不是常见系统,那么可能得费很大的劲来搭建了。不幸的是在下的小云就是这样的系统—CoreOS,一个已停止更新并被收购的系统。🤣

  要保持主机稳定,不能更改主机运行环境,我的主机上现在有什么呢?有docker、有git、有nginx,其他的啥都没有了。看来得利用已有的工具完成代码编译和容器打包呢。既然最终输出物为docker image,那么得在docker上找找突破口。

Docker 多阶段构建

  Docker 17.05之后原生支持多阶段构建,什么是多阶段构建呢?请点这里。简单来说就是可以将docker build按需要划分为多个阶段,并控制各阶段的输入输出。那么我们可以将编译作为构建第一阶段,最终打包成镜像则作为第二阶段。因此我的Dockerfile可以这样写:

FROM registry.cn-hangzhou.aliyuncs.com/acs/maven:3-jdk-8 AS build-env
LABEL stage=tmp-build

ENV MY_HOME=/app
RUN mkdir -p $MY_HOME
WORKDIR $MY_HOME
ADD pom.xml $MY_HOME

RUN ["/usr/local/bin/mvn-entrypoint.sh", "mvn", "validate", "clean", "--fail-never"]
ADD . $MY_HOME

RUN ["/usr/local/bin/mvn-entrypoint.sh", "mvn", "verify", "-Dmaven.test.skip=true"]

FROM openjdk:8-jre-alpine
COPY --from=build-env /app/target/*.jar /app.jar
ADD run.sh run.sh
RUN chmod a+x run.sh

  第一阶段为名为build-env的编译阶段,它的目标是编译输出最终的jar包,因此这个阶段不用考虑docker镜像大小以及层级(反正都是中间产物)。即便如此,这个阶段也挺有意思的,首先选取带JDK的maven镜像,然后先加pom文件,并依据pom拉取需要的库文件。然后再加源码工程进行编译。这一阶段应该有可进一步提速的空间,不过就这样吧。

又不是不能用

  第二阶段则是为了输出最终的image,因此选取了相对较小的alpine镜像。同时为了减少层级并提高可阅读性,我将容器执行入口封装进run.sh脚本(为了调试方便,并未添加自动执行,实际应用的时候用docker-compose指定run.sh即可)。

  准备好Dockerfile之后,将其与Spring Cloud工程放在一起随代码更新即可。使用的时候执行docker build -t <imagename> .就行了。正常情况下可以看到这样的效果:

编译

附加效果

  想想看在大家聚在一起喜气洋洋过年的时候,你默默的拿出手机,看似随意的点几下然后就轻轻的放在桌上,转身和大家云淡风轻的聊着家长里短。而屏幕上哗哗不停的显示,一定会引起所有人的好奇,最终会有人问你这是在干嘛,这个时候你一定要控制好情绪和声调,慢慢的说:“哦,这不美国佬的小飞机掉水里了嘛,今天刚好有空,我帮国家找一下”。是不是完成了一次完美装逼?正所谓无形装逼最为致命。

装逼

遗留问题

  多阶段构建确实很方便,合理利用能很好满足我的小需求,不过多阶段构建会产生的中间产物并不会自动删除,通过docker images -a可以看到有很多标记为none的image,不过也不用惊慌,执行一条shell命令即可批量删除:

docker rmi $(docker images -a | grep none | awk '{print $3}')

最后

  因为有云,很多以前做不到的事现在能做了。因为有云,资源利用率可以更高了。因为有云,便携设备的作用以及边界被扩展了。因为有云,各种想象正在逐渐变为现实。

云