# 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是相互依赖的，当时我就想到几种解决办法

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!

```plaintext
## 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` 需要的时候直接通过命令取出即可

```plaintext
$ 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"*

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

```plaintext
➜  ~ 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 [ "" ]` ，最后三行即视感：

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

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

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

如果没有 `CMD [ "" ]` 这一行，创建容器时会报错：

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

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

## **总结**

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

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