前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >解决国产系统 Docker 拉取大镜像卡顿之谜

解决国产系统 Docker 拉取大镜像卡顿之谜

作者头像
米开朗基杨
发布2023-09-09 18:44:43
7400
发布2023-09-09 18:44:43
举报
文章被收录于专栏:云原生实验室云原生实验室

今天解决了客户 arm64 机器上 docker pull 大镜像卡住的问题。

由来

同事让我帮忙解决客户现场 Docker 镜像无法拉取的问题,故障如下会一直卡住:

代码语言:javascript
复制
$ docker pull xxx:5000/xxxx
xxx: Pulling from xxx/xxxxxx
7c0b344a74c2: Extracting [>                                                  ]  294.9kB/26.66MB
7c0b344a74c2: Download complete
e53ed7fd3110: Download complete
d2cae797bc79: Download complete
ec3ddc176f08: Download complete
2969517e196e: Download complete
097fa64722e8: Download complete
1dde4ca01a5a: Download complete

离线文件 load -i 后,打上 tag 推送到镜像仓库,然后本地删除这个镜像,然后拉取还是像上面这样卡住,部分小镜像拉取没问题,所以不可能是 docker data-root 的挂载 option 影响。环境信息如下:

代码语言:javascript
复制
$ docker info
...
 Server Version: 19.03.15
 Storage Driver: overlay2
  Backing Filesystem: extfs
  Supports d_type: true
  Native Overlay Diff: true
 Logging Driver: json-file
 Cgroup Driver: cgroupfs
 Plugins:
  Volume: local
  Network: bridge host ipvlan macvlan null overlay
  Log: awslogs fluentd gcplogs gelf journald json-file local logentries splunk syslog
 Swarm: inactive
 Runtimes: runc
 Default Runtime: runc
 Init Binary: docker-init
 containerd version: ea765aba0d05254012b0b9e595e995c09186427f
 runc version: v1.0.0-0-g84113eef
 init version: fec3683
 Security Options:
  seccomp
   Profile: default
 Kernel Version: 4.19.90-2211.5.0.0178.22.uel20.aarch64
 Operating System: UnionTech OS Server 20
 OSType: linux
 Architecture: aarch64
 CPUs: 24
 Total Memory: 94.56GiB
 Name: host-xxxx
 ID: RTQS:5TXE:5T3S:YW7X:OHPK:FZ7D:7EHD:DH5Z:JNBV:FVXS:24FA:EIVS
 Docker Root Dir: /data/kube/docker
 Debug Mode: true
  File Descriptors: 29
  Goroutines: 46
  System Time: 2023-04-12T16:10:25.33362426+08:00
$ uname -a
Linux host-x 4.19.90-2211.5.0.0178.22.uel20.aarch64 #1 SMP Thu Nov 24 10:33:07 CST 2022 aarch64 aarch64 aarch64 GNU/Linux
$ cat /etc/os-release
PRETTY_NAME="UnionTech OS Server 20"
NAME="UnionTech OS Server 20"
VERSION_ID="20"
VERSION="20"
ID=uos
HOME_URL="https://www.chinauos.com/"
BUG_REPORT_URL="https://bbs.chinauos.com/"
VERSION_CODENAME=fuyu
PLATFORM_ID="platform:uel20"

排查

卡住的过程中,另外开一个 ssh top 发现进程 unpigz 占用较高,利用其 pid 查看了一些信息:

代码语言:javascript
复制
$ pstree -sp 1170083
systemd(1)───dockerd(1169795)───unpigz(1170083)─┬─{unpigz}(1170084)
                                                ├─{unpigz}(1170086)
                                                └─{unpigz}(1170087)

发现这个进程是 Docker 调用的,strace 只能看到卡住,kill 了 unpigz 后,卡住的 pull 开始报错了:

代码语言:javascript
复制
failed to register layer: Error processing tar file(exit status 1): unexpected EOF

Docker 的镜像每层 layer 实际是 tar,pull 时会下载 tar 包然后解压,这个看着是解压相关的逻辑出现了问题。在 Docker 源码里搜索 Error processing tar file 后找到:

代码语言:javascript
复制
// https://github.com/moby/moby/blob/v19.03.15/pkg/chrootarchive/archive_unix.go#L90-L116
 cmd := reexec.Command("docker-untar", dest, root)
  ...
 if err := cmd.Wait(); err != nil {
  // when `xz -d -c -q | docker-untar ...` failed on docker-untar side,
  // we need to exhaust `xz`'s output, otherwise the `xz` side will be
  // pending on write pipe forever
  io.Copy(ioutil.Discard, decompressedArchive)

  return fmt.Errorf("Error processing tar file(%v): %s", err, output)
 }
 return nil

看注释里的xz -d -c -q | docker-untar ...,与 unpigz 的 cmdline 和一个卡住的 docker-untar 进程相符:

代码语言:javascript
复制
$ xargs -0 < /proc/1170083/cmdline
/usr/bin/unpigz -d -c
$ ps aux | grep docker-unta[r]
root     1164788  0.0  0.0 1491008 39488 pts/2   Sl+  15:21   0:00 docker-untar / /data/kube/docker/overlay2/546b7b992b53b243450807b8150c4a1905e93afae604da69a21bbaaf443f178e/diff

看来是 exec 调用 unpigz 解压管道给 reexec 注册的 docker-untar,而上面的 unpigz 进程树显示是 docker 默认调用的而非 xz。搜索后发现,unpigz 是一个在 gz 格式处理上比 gzip 更快的实现。既然 Docker 是 exec 调用的 unpigz,那就在源码里搜索它看看:

代码语言:javascript
复制
// https://github.com/moby/moby/blob/v19.03.15/pkg/archive/archive.go#L32-L39
func init() {
 if path, err := exec.LookPath("unpigz"); err != nil {
  logrus.Debug("unpigz binary not found in PATH, falling back to go gzip library")
 } else {
  logrus.Debugf("Using unpigz binary found at path %s", path)
  unpigzPath = path
 }
}

往下翻看,发现 unpigzPath 的 exec 调用逻辑:

代码语言:javascript
复制
// https://github.com/moby/moby/blob/v19.03.15/pkg/archive/archive.go#L160-L174
func gzDecompress(ctx context.Context, buf io.Reader) (io.ReadCloser, error) {
 if unpigzPath == "" {
  return gzip.NewReader(buf)
 }

 disablePigzEnv := os.Getenv("MOBY_DISABLE_PIGZ")
 if disablePigzEnv != "" {
  if disablePigz, err := strconv.ParseBool(disablePigzEnv); err != nil {
   return nil, err
  } else if disablePigz {
   return gzip.NewReader(buf)
  }
 }

 return cmdStream(exec.CommandContext(ctx, unpigzPath, "-d", "-c"), buf)
}

现场 unpigz 版本:

代码语言:javascript
复制
$ rpm -qf /bin/unpigz
pigz-2.4-7.uel20.01.aarch64
$ rpm -V pigz
# -V 查看包也没被修改

注意看其中有个 env 设置不使用 PIGZ 而是使用 gzip,然后启动 Docker Daemon 时设置这个 env 就可以拉取镜像了:

代码语言:javascript
复制
$ systemctl stop docker
# 临时命令行前台 debug 启动下看看是没问题的
$ MOBY_DISABLE_PIGZ=true dockerd --debug

后续

UOS 这个系统需要授权才能使用 yum 安装升级,访问 repo 里的 url 会报错 401,让客户联系 UOS 厂商升级 pigz 包发现是最新的版本,只能使用 MOBY_DISABLE_PIGZ 环境变量回退到 gz 了。UOS官方仓库的Docker版本无此问题

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2023-04-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 云原生实验室 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 由来
  • 排查
  • 后续
相关产品与服务
容器镜像服务
容器镜像服务(Tencent Container Registry,TCR)为您提供安全独享、高性能的容器镜像托管分发服务。您可同时在全球多个地域创建独享实例,以实现容器镜像的就近拉取,降低拉取时间,节约带宽成本。TCR 提供细颗粒度的权限管理及访问控制,保障您的数据安全。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档