share file based scatch with docker
不是特别想用英文,但确实是想不到一个比较好的中文题目了……
背景
加入新公司快两个月了,随着对团队项目的了解,也逐步发现了一些问题,比如我们有A、B两个后端项目:
A 是使用 java 开发的,部署在 K8s 中
B 是 go 开发,使用supervisor部署在宿主机上
Action
"熟谙" DevOps 的笔者在 A 的构建脚本中发现 B 项目的可执行文件,竟然直接被打到了 jar 包中,具体做法就是
B 项目在构建后产生的二进制文件 b-bin 被拷贝到 /opt/ 目录
A 构建过程中将 /opt/b-bin 拷贝到自己的目录中来,然后完成构建
先不论合理不合理,显而易见,A、B是相互依赖的,当时我就想到几种解决办法
B 使用 Dockerfile 构建(镜像 tag 必须是 commit-id 啊), 在 A 的 Dockerfile 中使用 multi-stage 技术将 b-bin 拷贝过来即可
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直接容器化部署不就行了。那之前为啥不这么干呢?因为之前我还没来嘛^-^