9

Concourse 实战 - 监控 GitHub release 并自动构建镜像

 1 year ago
source link: https://www.boris1993.com/concourse-practise-build-and-push-docker-image.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

距离上一篇 Concourse 相关的文章发布,已过去两年有余,期间因为没什么使用场景,不知道该怎么继续写下去,于是就断了。这次,我终于有机会将 Concourse 用到我自己的 home lab,并成功完成了一条 pipeline。

背景及需求

偶然在网上看到了一个可以多端直播推流的工具,叫 Ant Media Server,但是它的安装程序并不支持我正在用的 Ubuntu 22.04 LTS,同时它也没有提供制作好的 Docker 镜像,只能自己手动构建。可手动构建也太不优雅,根本不能忍,所以萌生了一个需求:监控 Ant Media Server 的 GitHub releases,如果有新的版本发布,那么就自动构建新的 Docker 镜像,并推送到我的 Docker Hub 中。

首先,我要实现在 Concourse 里面监控 GitHub release。github-release这个 resource type 就是干这件事的,所以我们可以在 pipeline 中定义这样一个 resource:

resources:
- name: ant-media-server
type: github-release
source:
owner: ant-media
repository: Ant-Media-Server
# 默认监视的是"release-"开头的tag
# 但Ant Media Server的tag都是以"ams-"开头的
# 所以需要指定一下
tag_filter: "ams-v?([^v].*)"

资源光在 resources 里面定义好还不够,我们需要在 pipeline 里面用 get 这个 task 来让 Concourse 做出从这个资源获取数据的操作。所以,开始写 pipeline 咯。

jobs:
- name: build-image # pipeline的名字
public: true # 公开就意味着用户不需登录也能在dashboard中看到
plan:
- get: ant-media-server # 这里要写上面定义的resource的名字
trigger: true # 这个资源将作为一个触发器

这样就实现了让 Concourse 监控这个 GitHub release,并在发布新 release 的时候触发 pipeline 运行。而这个 task 在运行的时候,会将 release 中的 artifact 下载到 ant-media-server 这个目录中,所以我们也不用担心下载文件的问题。同时它还会把 release 的版本号写在 version 这个文件中,后面我们可以利用这个文件来生成 Docker 镜像的 tag。

有了 Ant Media Server 的成品文件,按照官方文档的说法,接下来只要做两件事:下载 Dockerfile,执行 docker build 命令就行。但是放在 pipeline 里面,就没这么简单了。

先做第一件事,下载 Dockerfile。感谢 jgriff/http-resource这个仓库,它可以实现在 Concourse 里面通过 HTTP 下载一个文件。那么接下来 pipeline 里面可以这么写:

# 因为这不是Concourse官方提供的resource type
# 所以需要在这里定义一个名为http-resource的resource type
# 并声明由jgriff/http-resource这个Docker镜像来实现
resource_types:
- name: http-resource
type: docker-image
source:
repository: jgriff/http-resource

resources:
# 前略
- name: ant-media-server-dockerfile
type: http-resource # 上面定义好这个resource type之后,就可以在这里用了
source:
# 指定要下载的文件
url: https://raw.githubusercontent.com/ant-media/Scripts/master/docker/Dockerfile_Process

jobs:
- name: build-image # pipeline的名字
public: true # 公开就意味着用户不需登录也能在dashboard中看到
plan:
# 让这两个task并行执行,节省时间
- in_parallel:
- get: ant-media-server
trigger: true
# 下载ant-media-server-dockerfile这个resource指定的文件
- get: ant-media-server-dockerfile

现在 Dockerfile 可以下载到了,但是它是被保存在 ant-media-server-dockerfile/body 这个文件里面的,我们需要把它移动到 ant-media-server 这个目录里,才能保证后面成功运行 docker build。所以接下来要用 mv 命令把文件移过去。

jobs:
- name: build-image
public: true
plan:
# 前略
- task: move-dockerfile
config:
# task运行在Linux环境
platform: linux
# task将通过ubuntu这个Docker镜像运行
image_resource:
type: docker-image
source:
repository: ubuntu
# 将这两个资源传给镜像
inputs:
- name: ant-media-server
- name: ant-media-server-dockerfile
# 因为修改了ant-media-server这个资源的内容
# 所以要将其输出,这样后续的task才能取到修改后的内容
outputs:
- name: ant-media-server
run:
path: mv
args: ["ant-media-server-dockerfile/body", "ant-media-server/Dockerfile"]

有了 Dockerfile,接下来就可以开始着手构建了。不用想,对于构建 Docker 镜像这样常见的 task,Concourse 预先制作好了 concourse/oci-build-task这个镜像来给我们用。

但是首先我们需要创建一个包含着 build args 的文件,因为文档的 docker build 命令中提到了 --build-arg AntMediaServer=<Replace_With_Ant_Media_Server_Zip_File> 这个参数,而 Ant Media Server 的 zip 文件名又会随着 release 而变化,同时 oci-build-task 的参数 BUILD_ARGS_* 并不支持 shell 命令,也就是说我不能通过 BUILD_ARGS_AntMediaServer=ant-media-server-community-$(cat version).zip 这样的方法来生成,那么只能用 oci-build-taskBUILD_ARGS_FILE 参数,传进去一个生成好的 build args file。

所以我们需要在 pipeline 中增加这两步来完成镜像的构建操作。

jobs:
- name: build-image
public: true
plan:
# 前略
# 生成build args file
- task: generate-build-args
config:
platform: linux
image_resource:
type: docker-image
source:
repository: ubuntu
inputs:
- name: ant-media-server
outputs:
- name: ant-media-server
run:
# 这里我曾经试过
# path: echo
# args: ["AntMediaServer=ant-media-server-community-$(cat ant-media-server/version).zip", ">", "ant-media-server/build_args.txt"]
# 但是没成功,因为Concourse会把args做字符串拼接处理,最后当成一整个字符串传给命令
# 所以其实变成了 echo "AntMediaServer=ant-media-server-community-$(cat ant-media-server/version).zip > ant-media-server/build_args.txt"
# 很明显这只能把这串字符串打在屏幕上,并不能生成文件
# 所以只能通过调用sh来执行命令,把命令当成参数传给sh
path: sh
args:
- -exc
- 'echo "AntMediaServer=ant-media-server-community-$(cat ant-media-server/version).zip" > ant-media-server/build_args.txt'
# 开始构建镜像
- task: build-image
privileged: true
config:
platform: linux
image_resource:
type: registry-image
source:
repository: concourse/oci-build-task
# 构建所需的文件都在ant-media-server这个资源中
# 所以将它传给这个task
inputs:
- name: ant-media-server
# 将task输出的资源命名为image,并将其输出
outputs:
- name: image
params:
CONTEXT: ant-media-server
BUILD_ARGS_FILE: ant-media-server/build_args.txt
# 缓存构建结果,加速将来的新的构建
caches:
- path: cache
run:
path: build

oci-build-task 成功后,会把镜像保存到 image/image.tar 文件中。要将它上传到 Docker Hub,我们还需要定义一个 registry-image 类型的 resource,来指定要将镜像上传到哪里。

因为上传 Docker Hub 需要登陆,而把 token 写在 pipeline 里面是非常蠢的行为,所以我把登陆信息放到了 Vault 中。向 Vault 放登陆信息很简单,在 /concourse 这个 path 中新建两个 secret 就可以了:

  • /shared/dockerhub_username,key 是 value,value 填写 Docker Hub 的用户名
  • /shared/dockerhub_token,key 是 value,value 填写 Docker Hub 的 access token

之所以我把登陆信息放到 /shared 这个 path 下,是因为我在 Vault 中配置了这个 path 作为一个公共的 path,在构建的时候要根据实际情况来修改,比如改成 team 的名字,或者放在 /{team}/{pipeline}/ 下面。具体请参考 Concourse 与 Vault 集成相关的文档,这里不再赘述。

放好登陆信息后,就可以添加这样一个 resource:

resources:
# 前略
- name: ant-media-server-docker
type: registry-image
icon: docker
source:
repository: "((dockerhub_username))/ant-media-server"
username: "((dockerhub_username))"
password: "((dockerhub_token))"

然后在 pipeline 最后增加两个 task,一个是读取 ant-media-server/version 的值,将其写在名为 tag 的变量中,后面我们会用这个变量来指定镜像的 tag;另一个就是对 ant-media-server-docker 这个资源执行 put 的操作,将 image/image.tar 这个镜像上传到 Docker Hub。

jobs:
- name: build-image
public: true
plan:
# 前略
# load_var用来从文件读入数据,并将其放在一个变量中
- load_var: tag
# 指定要读的文件
file: ant-media-server/version
# 为避免自动识别给我识别错,干脆直接指定文件内容的格式
# trim就是纯文本,读取之后会去掉头尾的空白
format: trim
- put: ant-media-server-docker
params:
image: image/image.tar
# 上传时,同时更新latest和相关semver的镜像
# 比如上传2.5.3时,会同时更新2.5,2,latest这三个tag
bump_aliases: true
# 这里注意要指明从local var source中寻找变量,也就是开头的.:
# 否则会找不到这个变量
version: "((.:tag))"

完整的 pipeline

至此这个 pipeline 就完成了,下面我附上已经部署过的版本,供参考。

---
resource_types:
- name: http-resource
type: docker-image
source:
repository: jgriff/http-resource

resources:
- name: ant-media-server
type: github-release
source:
owner: ant-media
repository: Ant-Media-Server
tag_filter: "ams-v?([^v].*)"
- name: ant-media-server-dockerfile
type: http-resource
source:
url: https://raw.githubusercontent.com/ant-media/Scripts/master/docker/Dockerfile_Process
- name: ant-media-server-docker
type: registry-image
icon: docker
source:
repository: "((dockerhub_username))/ant-media-server"
username: "((dockerhub_username))"
password: "((dockerhub_token))"

jobs:
- name: build-image
public: true
build_log_retention:
# 只保留最近5次的构建记录,以节省空间
builds: 5
plan:
- in_parallel:
- get: ant-media-server
trigger: true
- get: ant-media-server-dockerfile
- load_var: tag
file: ant-media-server/version
format: trim
reveal: true
- task: move-dockerfile
config:
platform: linux
image_resource:
type: docker-image
source:
repository: ubuntu
inputs:
- name: ant-media-server
- name: ant-media-server-dockerfile
outputs:
- name: ant-media-server
run:
path: mv
args: ["ant-media-server-dockerfile/body", "ant-media-server/Dockerfile"]
- task: generate-build-args
config:
platform: linux
image_resource:
type: docker-image
source:
repository: ubuntu
inputs:
- name: ant-media-server
outputs:
- name: ant-media-server
run:
path: sh
args:
- -exc
- 'echo "AntMediaServer=ant-media-server-community-$(cat ant-media-server/version).zip" > ant-media-server/build_args.txt'
- task: build-image
privileged: true
config:
platform: linux
image_resource:
type: registry-image
source:
repository: concourse/oci-build-task
inputs:
- name: ant-media-server
outputs:
- name: image
params:
CONTEXT: ant-media-server
BUILD_ARGS_FILE: ant-media-server/build_args.txt
caches:
- path: cache
run:
path: build
- put: ant-media-server-docker
params:
image: image/image.tar
bump_aliases: true
version: "((.:tag))"

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK