前言
我们要做的事情很简单,用Concourse这个工具拉取私有代码库的Java源代码,构建然后推送到阿里云仓库.
我希望看这篇文章的人能够重点看前面几段内容,而不是学会了皮毛,但没办法理解这个工具的设计理念.
概念介绍
Pipeline是一等公民:Concourse以pipeline机制运行集成任务。pipeline将Task、Resource、Job三者有机地结合起来。
任务(Task)是执行的基本单元,表现为在一个全新启动的容器中所运行的脚本。容器已经经过了预处理,因此其中包含了一个输入与输出目录,任务脚本可在这些目录中运行。
资源(Resource)则表现为版本化资源的抽象位置,例如某个repository。资源也可用于对进入或退出某个管道的外部依赖进行建模,或是表示更抽象的概念,例如时间触发器等等。资源的变更可被检测(检查)、获取(获得)以及发布(推送)。不同的资源类型(例如Git、AWS S3或触发器)可用于封装用于管道的各种样板代码,为Concourse的扩展提供了一个可适配的接口。
作业(Job)是由资源与任务构成的,通过构建计划实现。作业可由资源的变更所触发,也可以选择手动触发,以实现人工批准流程。
前期心理准备
Concourse-CI 是一个很坑爹的开源项目. 文档写的简直狗屎,如果不是2个资深同事帮助,估计我早已摔锅走人.
我花了整整3天学习之后,蓦然回首,发现最好用的其实还是
fly -h
这个命令.它解释了Concourse-CI能够做的所有事情.初学这玩意的时候,一定要弄清概念.
一开始的时候,他这个所谓的管道对我来说简直就是我们家乡逢雨必浸的破水沟,简直狗屁不通.我们用
Jenkins
也好,直接本地构建
也罢,其实是基于同一个上下文
.我们在同一个环境下cd来cd去,然后gradle build/mvn install云云.但是Concourse的上下文一开始对我来说是极其割裂的.每一个子任务都是在各自不同的镜像上面跑.再加上配置job的文件是在本地,搞得当时我根本分不清环境变量到底哪个环境的变量(本地环境,Concourse所在的服务器环境,job/task里面的docker环境)…
但随着学习的深入,这上面的一段话其实是一段误解.
task
可以通过inputs
接收resources
的结果.task
与task
之间通过inputs
和outputs
传递结果.理解这个上下文,后面的工作就好办多了.
而
fly
命令是基于本地的,那么-v
的内容其实也是基于本地上下文.而我们的构建工作也变得相当的清晰.
前期准备
本地环境
- 安装
docker
,docker-compose
. - 下载配置fly工具fly
通过安装fly
工具实现与服务器的通讯
服务器环境 docker-compose.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
concourse-db:
image: postgres:11-alpine
hostname: concourse-db
volumes:
- /root/docker/concourse/database:/database
environment:
- 'POSTGRES_DB=concourse'
- 'POSTGRES_PASSWORD=root'
- 'POSTGRES_USER=root'
- 'PGDATA=/database'
concourse-web:
image: concourse/concourse:4.0.0
hostname: concourse-web
ports:
- '9003:8080'
command: web
volumes:
- /root/docker/concourse/keys/web:/concourse-keys
environment:
- 'CONCOURSE_POSTGRES_HOST=concourse-db'
- 'CONCOURSE_POSTGRES_DATABASE=concourse'
- 'CONCOURSE_POSTGRES_USER=root'
- 'CONCOURSE_POSTGRES_PASSWORD=root'
- 'CONCOURSE_EXTERNAL_URL=https://ci.zeusro.io'
- 'CONCOURSE_MAIN_TEAM_ALLOW_ALL_USERS=true'
- 'CONCOURSE_ADD_LOCAL_USER=zeusro:pwd'
labels:
aliyun.depends: concourse-db
concourse-worker:
image: concourse/concourse:4.0.0
privileged: true
command: worker --work-dir /opt/concourse/worker
volumes:
- /root/docker/concourse/keys/worker:/concourse-keys
- /root/docker/concourse/worker:/opt/concourse/worker
environment:
- 'CONCOURSE_TSA_HOST=concourse-web:2222'
- 'CONCOURSE_GARDEN_DNS_SERVER=114.114.114.114'
labels:
aliyun.depends: concourse-web
- 数据卷要按需指定目录
- 如果不是在阿里云主机上面跑,
aliyun.depends
改为docker-compose
的depends_on - CONCOURSE_EXTERNAL_URL按需绑定自己的域名
-
CONCOURSE_ADD_LOCAL_USER按需配置
Concourse-CI
的用户名和密码docker-compose up fly -t zeusro login -b -c https://ci.zeusro.io 输入用户名,密码
构建用Java镜像
1
2
3
4
5
6
7
FROM openjdk:8-alpine3.8
LABEL maintainer="zeusro"
# 中国特色社会主义
RUN echo https://mirrors.ustc.edu.cn/alpine/v3.8/main > /etc/apk/repositories; \
echo https://mirrors.ustc.edu.cn/alpine/v3.8/community >> /etc/apk/repositories;\
echo "Asia/Shanghai" > /etc/timezone ;\
apk add --no-cache bash maven gradle
我把这个镜像命名为registry.cn-shenzhen.aliyuncs.com/amiba/openjdk:8-tools
对构建的建模
我们先抛开一些,想一个问题:我现在有一台崭新的服务器,我要怎么部署Java的代码?
问题的答案基本可以归纳为以下的步骤.
- 安装
git
,maven
/gradle
,java JDK
,docker
- git clone xx
- mvn install 私有依赖
- 执行主体项目的构建命令
- 将生成的jar包打包到docker镜像里面
- docker push
那么切换到 Concourse-CI
这个语境.这个步骤就变成了
- 获取私有依赖,主体项目这N个
resources
(假设私有依赖可能有多个) - 在一个有
git
,maven
/gradle
,java JDK
的环境里面执行构建任务(task) - 将生成的jar包打包到docker镜像里面(task)
- docker push(put
resources
)
Concourse-CI的构建上下文
每次构建任务开始时,我们通过pwd
发现其每一次都是一个按照特定规则命名的临时目录.inputs
,outputs
,cache
都是指定这个临时目录下的目录.
假设某次构建的临时目录是/temp/xxx
1
2
3
4
5
6
7
inputs:
- name: dependency1
- name: dev-repo
outputs:
- name: dev-resource
caches:
- path: dependency-cache-1
那么这个配置产生的目录(ls /temp/xxx
)就是
1
2
3
4
dependency1---------|私有依赖git仓库
dev-repo------------|主题项目git仓库
dev-resource--------|交付给下一个task的目录,下一个task用inputs接收
dependency-cache-1--|缓存的目录
caches机制
首先问一个问题,为什么需要缓存这种机制?
这个问题得从依赖的还原机制入手.依赖一般是从本地缓存抓取文件,如果本地缓存没有这个文件,则从中心仓库里面拉取.但是Concourse-CI
这个坑爹玩意是没有全局上下文这种东西的,每一次构建都是重新拉取镜像,那么如果不走缓存,这个下载依赖的动作就会在每一次的构建任务中重复进行.caches
是为了解决这个问题而生.
开干
一开始的思路
一开始我的思路有点有问题,我分成了2个task
:
mvn install
私有依赖mvn install
主体项目
见maven分支的concourse-ci-pipeline-1.yaml
文件
效果不是很理想.虽然用caches缓存了依赖包,但是2个task
之间是通过cp操作连贯起来的.其实应该把2个task合为一个,这样就不存在上下文切换的问题.
改进版的思路
见master分支的concourse-ci-pipeline-2.yaml
文件.
这个项目master用的gradle,maven版本的在maven分支.
Concourse-CI的一些用法
以我这个项目为例
1
2
3
4
5
6
7
8
fly -t zeusro set-pipeline -p spring-boot-template -c concourse-ci-pipeline.yaml \
-v "name=spring-boot-template" \
-v "repouri=ssh://[email protected]:zeusro/spring-boot-template.git" \
-v "repo-key=$(cat ~/.ssh/id_rsa)" \
-v "dependency1uri=ssh://<基础依赖的仓库>" \
-v "image-name=<打包的阿里云镜像名称>" \
-v "image-username=<登录镜像的用户名>" \
-v 'image-password=<登录镜像的密码>' ;
尖括号中间的内容自行按需替换即可
其实配置还可用文件方式指定
1
fly -t amiba sp -p demo -c demo.yml -l demo/gradle.yml
执行sq之后,
- web UI相应的
Pipelines
上点播放键; fly -t zeusro trigger-job --job spring-boot-demo/dev-build
手动触发.resources
的特定事件
这些都能够触发相应Pipelines
的执行.
1
fly -t zeusro watch --job spring-boot-demo/dev-build --build 2
这个是实时查看命令的输出信息
1
fly -t zeusro abort-build --job spring-boot-demo/dev-build --build 1
这个命令是废止特定构建
好用的命令集锦
1
2
3
4
5
fly -h
fly -t zeusro builds
fly -t zeusro watch --job spring-boot-demo/dev-build --build 2
fly -t zeusro abort-build --job spring-boot-demo/dev-build --build 1
fly -t zeusro trigger-job --job spring-boot-demo/dev-build
备注
- 要留意dockerfile里面alpine,gradle,mvn的版本,避免误事
默认的gradle
cache在
1
/root/.gradle/caches/modules-2/files-2.1/
默认的maven
repository在
1
/root/.m2/repository/
结论
这玩意真他妈坑爹,有问题各位自己解决,谁爱搞谁去~