share file based scatch with docker

·

2 min read

不是特别想用英文,但确实是想不到一个比较好的中文题目了……

背景

加入新公司快两个月了,随着对团队项目的了解,也逐步发现了一些问题,比如我们有A、B两个后端项目:

  • A 是使用 java 开发的,部署在 K8s 中

  • B 是 go 开发,使用supervisor部署在宿主机上

Action

"熟谙" DevOps 的笔者在 A 的构建脚本中发现 B 项目的可执行文件,竟然直接被打到了 jar 包中,具体做法就是

  • B 项目在构建后产生的二进制文件 b-bin 被拷贝到 /opt/ 目录

  • A 构建过程中将 /opt/b-bin 拷贝到自己的目录中来,然后完成构建

先不论合理不合理,显而易见,A、B是相互依赖的,当时我就想到几种解决办法

  1. B 使用 Dockerfile 构建(镜像 tag 必须是 commit-id 啊), 在 A 的 Dockerfile 中使用 multi-stage 技术将 b-bin 拷贝过来即可

  2. A、B 本身存在依赖关系,它俩应该保持开发版本的一致,不如将其合并为一个 repo

这两种办法都可以, 方法1对当前现状的影响最小,最容易实施。但为何 b-bin 会被打到 A的 jar 包中呢?原因为 A 需要将 b-bin 安装到不同的宿主机上……A 与 B 如此耦合的缺点就是:当B有重大变化时,A需要重新打包,当然也有办法可以解决这个问题:

  • 搭一个 ftp server,将 b-bin 扔上去,在 A 中记录下对应的 b-bin 的版本号即可

为了这个需求单独再维护一个 ftp server 太不值当了,还有没有其它办法?

既然 A 部署在 K8s 中,就必然会有一个镜像中心 harbor,何不将 b-bin 存到 harbor 中, harbor 不支持直接存储二进制文件,但我可以把 b-bin 打入镜像中啊,在需要的时候将期取出即可,so easy!

## Build the manager binary
FROM golang:1.17 as builder
​
WORKDIR /opt
# Copy the Go Modules manifests
COPY go.mod go.mod
COPY go.sum go.sum
​
RUN go env -w GO111MODULE=on && \
    go env -w GOPROXY=https://goproxy.cn,direct && \
    go mod download
​
# Copy the go source
COPY cmd/ cmd/
COPY pkg/ pkg/
COPY internal/ internal/
​
# Build
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o b-bin cmd/b.go
​
FROM alpine
COPY --from=builder /opt/b-bin /opt/b-bin
​

执行 docker build -t my-b-bin 需要的时候直接通过命令取出即可

$ docker run -v $(PWD):/itmp --rm my-b-bin sh -c "cp /opt/b-bin /itmp/"

换成瑞士军刀 busybox,最终镜像还能小一点,但依然有多余的东西,此时我脑海中想到一个镜像: scatch

an explicitly empty image, especially for building images "FROM scratch"

一个空镜像,真的是空的,里面啥也没有,这很有趣

➜  ~ dive scratch
Fetching image... (this can take a while with large images)
Image not available locally. Trying to pull 'scratch'...
Using default tag: latest
Error response from daemon: 'scratch' is a reserved name
cannot fetch image: Error response from daemon: No such image: scratch

将前面 Dockerfile 改一下,将 FROM alpine 替换为 FROM scratch , 但发现取出 b-bin 的命令不好使了,因为 scratch 镜像中既没有 sh 也没有 cp,因为 scratch 真是空的,啥也没有,所以这个法子不管用了。本来到这里我要放弃了,就为了几M存储空间值得吗?不是值不值得的问题,我只是想验证这方法到底行不行!go on

在 Dockerfile 最后一行加上:CMD [ "" ] ,最后三行即视感:

FROM scratch
COPY --from=builder /opt/b-bin /opt/b-bin
CMD [ "" ]

一个空命令,得以让我们可以创建容器,然后再利用以下命令即可取出 b-bin:

CID=$(docker create my-b-bin) 
docker cp ${CID}:/opt/b-bin /tmp/b-bin
docker rm ${CID}

如果没有 CMD [ "" ] 这一行,创建容器时会报错:

docker create my-b-bin
Error response from daemon: No command specified

为了骗过docker daemon,索性加一个空命令好了。

总结

虽然费了点时间,但最终达到了目的。大胆设想一下,在容器化环境,镜像中心可以用于文件分发啊,理论上 mvn、npm 等仓库是不是也可以使用这种方式呢?当然,前提是得有足够的收益(比如容器的分层缓存),最好能兼容现状,改动还要尽可能小……嗯,可不可行是一回事,适不适用是另一回事。

虽然最终A、B的依赖问题我通过其它方式解决了,是的,B直接容器化部署不就行了。那之前为啥不这么干呢?因为之前我还没来嘛^-^