最近在调整公司项目的 CI,目前主要使用 GitLab CI,在尝试多阶段构建中踩了点坑,然后发现了一些有意思的玩意
本文参考:
一、起因
公司目前主要使用 GitLab CI 作为主力 CI 构建工具,而且由于机器有限,我们对一些包管理器的本地 cache 直接持久化到了本机;比如 maven 的 .m2
目录,nodejs 的 .npm
目录等;虽然我们创建了对应的私服,但是在 build 时毕竟会下载,所以当时索性调整 GitLab Runner 在每个由 GitLab Runner 启动的容器中挂载这些缓存目录(GitLab CI 在 build 时会新启动容器运行 build 任务);今天调整 nodejs 项目浪了一下,直接采用 Dockerfile 的 multi-stage build 功能进行 “Build => Package(docker image)” 的实现,基本 Dockerfile 如下
FROM gozap/build as builder
COPY . /xxxx
WORKDIR /xxxx
RUN source ~/.bashrc \
&& cnpm install \
&& cnpm run build
FROM gozap/nginx-react:v1.0.0
LABEL maintainer="mritd <mritd@linux.com>"
COPY --from=builder /xxxx/public /usr/share/nginx/html
EXPOSE 80
STOPSIGNAL SIGTERM
CMD ["nginx", "-g", "daemon off;"]
本来这个 cnpm
命令是带有 cache 的(见这里),不过运行完 build 以后发现很慢,检查宿主机 cache 目录发现根本没有 cache…然后突然感觉
仔细想想,情况应该是这样事儿的…
+------------+ +-------------+ +----------------+
| | | | | |
| | | build | | Multi-stage |
| Runner +--------------->+ conatiner +----------->+ Build |
| | | | | |
| | | | | |
+------------+ +------+------+ +----------------+
^
|
|
|
|
+------+------+
| |
| Cache |
| |
+-------------+
后来经过查阅文档,发现 Dockerfile 是有扩展语法的(当然最终我还是没用),具体请见下篇文章(我怕被打死)下面,先说好,下面的内容无法完美的解决上面的问题,目前只是支持了一部分功能,当然未来很可能支持类似 IF ELSE
语法、直接挂载宿主机目录等功能
二、开启 Dockerfile 扩展语法
2.1、开启实验性功能
目前这个扩展语法还处于实验性功能,所以需要配置 dockerd 守护进程,修改如下
ExecStart=/usr/bin/dockerd -H unix:// \
--init \
--live-restore \
--data-root=/data/docker \
--experimental \
--log-driver json-file \
--log-opt max-size=30m \
--log-opt max-file=3
主要是 --experimental
参数,参考官方文档;同时在 build 前声明 export DOCKER_BUILDKIT=1
变量
2.2、修改 Dockerfile
开启实验性功能后,只需要在 Dockerfile 头部增加 # syntax=docker/dockerfile:experimental
既可;为了保证稳定性,你也可以指定具体的版本号,类似这样
# syntax=docker/dockerfile:1.1.1-experimental
FROM tomcat
2.3、可用的扩展语法
RUN --mount=type=bind
这个是默认的挂载模式,这个允许将上下文或者镜像以可都可写/只读模式挂载到 build 容器中,可选参数如下(不翻译了)
Option | Description |
---|---|
target (required) |
Mount path. |
source |
Source path in the from . Defaults to the root of the from . |
from |
Build stage or image name for the root of the source. Defaults to the build context. |
rw ,readwrite |
Allow writes on the mount. Written data will be discarded. |
RUN --mount=type=cache
专用于作为 cache 的挂载位置,一般用于 cache 包管理器的下载等
Option | Description |
---|---|
id |
Optional ID to identify separate/different caches |
target (required) |
Mount path. |
ro ,readonly |
Read-only if set. |
sharing |
One of shared , private , or locked . Defaults to shared . A shared cache mount can be used concurrently by multiple writers. private creates a new mount if there are multiple writers. locked pauses the second writer until the first one releases the mount. |
from |
Build stage to use as a base of the cache mount. Defaults to empty directory. |
source |
Subpath in the from to mount. Defaults to the root of the from . |
Example: cache Go packages
# syntax = docker/dockerfile:experimental
FROM golang
...
RUN --mount=type=cache,target=/root/.cache/go-build go build ...
Example: cache apt packages
# syntax = docker/dockerfile:experimental
FROM ubuntu
RUN rm -f /etc/apt/apt.conf.d/docker-clean; echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache
RUN --mount=type=cache,target=/var/cache/apt --mount=type=cache,target=/var/lib/apt \
apt update && apt install -y gcc
RUN --mount=type=tmpfs
专用于挂载 tmpfs 的选项
Option | Description |
---|---|
target (required) |
Mount path. |
RUN --mount=type=secret
这个类似 k8s 的 secret,用来挂载一些不想打入镜像,但是构建时想使用的密钥等,例如 docker 的 config.json
,S3 的 credentials
Option | Description |
---|---|
id |
ID of the secret. Defaults to basename of the target path. |
target |
Mount path. Defaults to /run/secrets/ + id . |
required |
If set to true , the instruction errors out when the secret is unavailable. Defaults to false . |
mode |
File mode for secret file in octal. Default 0400. |
uid |
User ID for secret file. Default 0. |
gid |
Group ID for secret file. Default 0. |
Example: access to S3
# syntax = docker/dockerfile:experimental
FROM python:3
RUN pip install awscli
RUN --mount=type=secret,id=aws,target=/root/.aws/credentials aws s3 cp s3://... ...
注意: buildctl
是 BuildKit 的命令,你要测试的话自己换成 docker build
相关参数
$ buildctl build --frontend=dockerfile.v0 --local context=. --local dockerfile=. \
--secret id=aws,src=$HOME/.aws/credentials
RUN --mount=type=ssh
允许 build 容器通过 SSH agent 访问 SSH key,并且支持 passphrases
Option | Description |
---|---|
id |
ID of SSH agent socket or key. Defaults to “default”. |
target |
SSH agent socket path. Defaults to /run/buildkit/ssh_agent.${N} . |
required |
If set to true , the instruction errors out when the key is unavailable. Defaults to false . |
mode |
File mode for socket in octal. Default 0600. |
uid |
User ID for socket. Default 0. |
gid |
Group ID for socket. Default 0. |
Example: access to Gitlab
# syntax = docker/dockerfile:experimental
FROM alpine
RUN apk add --no-cache openssh-client
RUN mkdir -p -m 0700 ~/.ssh && ssh-keyscan gitlab.com >> ~/.ssh/known_hosts
RUN --mount=type=ssh ssh -q -T git@gitlab.com 2>&1 | tee /hello
# "Welcome to GitLab, @GITLAB_USERNAME_ASSOCIATED_WITH_SSHKEY" should be printed here
# with the type of build progress is defined as `plain`.
$ eval $(ssh-agent)
$ ssh-add ~/.ssh/id_rsa
(Input your passphrase here)
$ buildctl build --frontend=dockerfile.v0 --local context=. --local dockerfile=. \
--ssh default=$SSH_AUTH_SOCK
你也可以直接使用宿主机目录的 pem 文件,但是带有密码的 pem 目前不支持
目前根据文档测试,当前的挂载类型比如 cache
类型,仅用于 multi-stage 内的挂载,比如你有 2+ 个构建步骤,cache
挂载类型能帮你在各个阶段内共享文件;但是它目前无法解决直接将宿主机目录挂载到 multi-stage 的问题(可以采取些曲线救国方案,但是很不优雅);但是未来还是很有展望的,可以关注一下
转载请注明出处,本文采用 CC4.0 协议授权