发现 docker cp 的实现跟屎一样.
一开始发现的问题是 docker-ce/components/client 里的 CopyToContainer 这个 interface, 签名是这样的:
CopyToContainer(ctx context.Context, container, path string, content io.Reader, options types.CopyToContainerOptions) error
我注意到的问题是里面 path 必须是 dirname, 比如说你想 docker cp ./a.txt $CONTAINER_ID:/etc/b.txt, 那么 path 必须是 /etc, 否则直接报错.
那么问题来了, destination filename 都没传过去, dockerd 是怎么知道文件名的? 就比如上面的复制到 /etc/b.txt, 我都没把 b.txt 告诉 dockerd 它怎么可能做到的?
懒, 不想看代码, 先抓包, docker -H tcp://10.143.201.210:2376 cp /tmp/a.txt 4ea389f2119e:/etc/b.txt, 发现是这样的:
PUT /v1.39/containers/4ea389f2119e/archive?noOverwriteDirNonDir=true&path=%2Fetc HTTP/1.1
Host: 10.143.201.210:2376
User-Agent: Docker-Client/19.03.5 (darwin)
Transfer-Encoding: chunked
Content-Type: text/plain
PaxHeaders.0/b.txt..................................................................................0000000.0000000.0000000.00000000021.00000000000.011174. x....................................................................................................ustar.00.......................................................................................................................................................................................................................................................
那么一看到 PaxHeaders 就了然了, body 是一个 tarball, 所以 dest filename 也蕴藏其中.
那么问题来了, CopyToContainer interface 里的类型签名只说是 container io.Reader, 我直接 bytes.NewBuffer() 生成一个 reader 它岂不是要仆街?
看了一下源码, 结果在接口实现里注释写着:
// CopyToContainer copies content into the container filesystem.
// Note that `content` must be a Reader for a TAR archive
func (cli *Client) CopyToContainer(ctx context.Context, containerID, dstPath string, content io.Reader, options types.CopyToContainerOptions) error {
我服了... 垃圾 API, 居然会在客户端创建一个临时文件.tar 再 open, 匪夷所思, 给我三个大脑我都想不出这种实现, 内存里不能压缩 bytes buffer 吗?
更垃圾在后面.
我发现 docker cp 连 process substitution 都不认, 具体的命令是这样的:
docker cp /tmp/a.txt 4ea389f2119e:/etc/b.txt // ok
docker cp <(cat /tmp/a.txt) 4ea389f2119e:/etc/b.txt // not ok
后者的命令不会出错, 然而如果你去看 cp 进去是啥文件的时候就:
# docker exec -it f21996c9dab3 ls -l /etc/b.txt
lrwxrwxrwx 1 root root 15 Mar 2 15:12 /etc/b.txt -> pipe:[38941509]
这垃圾居然直接把 process substitution 的 pipe 打到 tarball 里了, 就不能认认真真去 read 到内存里吗?
然后它还有一个 followLink 的 option, 这次直接报错了:
# docker -H tcp://localhost:2376 cp --follow-link <(cat /tmp/a.txt) f21996c9dab3:/etc/b.txt
lstat /proc/300038/fd/pipe:[38956641]: no such file or directory
这次直接报错了, 看了一下代码, 因为在 follow 的情况下它非要去 os.Lstat 一下 link 的 dest, 然而 pipe 在文件系统上是不存在的, 就挂了.
我 真 的 服 了.
你的重定向的写法是不是要用
-?