项目介绍:
Dockerfile
FROM golang:1.13
COPY ./ /go/src/code构建命令及输入如下:
time DOCKER_BUILDKIT=1 docker build --no-cache -t test:v3 -f Dockerfile . --progress=plain
#1 [internal] load build definition from Dockerfile
#1 sha256:2a154d4ad813d1ef3355d055345ad0e7c5e14923755cea703d980ecc1c576ce7
#1 transferring dockerfile: 37B done
#1 DONE 0.1s
#2 [internal] load .dockerignore
#2 sha256:9598c0ddacf682f2cac2be6caedf6786888ec68f009c197523f8b1c2b5257b34
#2 transferring context: 2B done
#2 DONE 0.2s
#3 [internal] load metadata for golang:1.13
#3 sha256:0c7952f0b4e5d57d371191fa036da65d51f4c4195e1f4e1b080eb561c3930497
#3 DONE 0.0s
#4 [1/2] FROM golang:1.13
#4 sha256:692ef5b58e708635d7cbe3bf133ba934336d80cde9e2fdf24f6d1af56d5469ed
#4 CACHED
#5 [internal] load build context
#5 sha256:f87f36fa1dc9c0557ebc53645f7ffe404ed3cfa3332535260e5a4a1d7285be3c
#5 transferring context: 18.73MB 4.8s
#5 transferring context: 38.21MB 9.8s done
#5 DONE 10.5s
#6 [2/2] COPY ./ /go/src/code
#6 sha256:2c63806741b84767def3d7cebea3872b91d7ef00bd3d524f48976077cce3849a
#6 DONE 26.8s
#7 exporting to image
#7 sha256:e8c613e07b0b7ff33893b694f7759a10d42e180f2b4dc349fb57dc6b71dcab00
#7 exporting layers
#7 exporting layers 67.5s done
#7 writing image sha256:03b278543ab0f920f5af0540d93c5e5340f5e1f0de2d389ec21a2dc82af96754 done
#7 naming to docker.io/library/test:v3 done
#7 DONE 67.6s
real 1m45.411s
user 0m18.374s
sys 0m7.344s其中比较花时间的是:
以下也是按照这个思路进行逐一排查,测试验证,寻找构建时的 IO 瓶颈。
工程 https://github.com/shaowenchen/demo/tree/master/buidl-cli 实现的功能就是将本地的 Dockerfile 及上下文提交给 Dockerd 进行构建,从而测试 Docker CLI 是否有提交文件上的瓶颈。
GOOS=linux GOARCH=amd64 go build -o build main.gotime ./build ./ test:v3
real 5m12.758s
user 0m2.182s
sys 0m14.169s使用 Go 写的 cli 工具,将构建上下文提交给 Dockerd 进行构建,时长急剧增加;与此同时,构建机的负载飙升。
也可能还有其他优化点,需要慢慢调试。而 Docker CLI 其实也有相关的参数可以用于减少 IO 占用时间。
compress 会将上下文压缩为 gzip 格式进行传输,而 stream 会以流的形式传输上下文。
time DOCKER_BUILDKIT=1 docker build --no-cache -t test:v3 -f Dockerfile . --compress
real 1m46.117s
user 0m18.551s
sys 0m7.803stime DOCKER_BUILDKIT=1 docker build --no-cache -t test:v3 -f Dockerfile . --stream
real 1m51.825s
user 0m19.399s
sys 0m7.657s这两个参数对缩短构建时间,并没有什么效果。但需要注意的是测试项目的文件大而且数量多,如果测试用例发生变化,可能产生不同的效果。接着,我们一起看看文件数量、文件大小对 Dockerd 构建镜像的影响。
du -h --max-depth=1
119M ./data
119M .在 data 目录下放置了一个 119MB 的文件,通过复制该文件不断增加 build context 的大小。
FROM golang:1.13
COPY ./ /go/src/codeDOCKER_BUILDKIT=1 docker build --no-cache -t test:v3 -f Dockerfile .文件大小 | 构建时长 | 文件个数 |
119M | 0.3s | 1个 |
237M | 0.4s | 2个 |
355M | 0.5s | 3个 |
473M | 0.6s | 4个 |
1.3G | 3.7s | 11个 |
2.6G | 9.0s | 22个 |
文件大小对 COPY 影响明显,接近线性增长。
文件大小 | 构建时长 | 文件个数 |
2.9G | 13.8s | 264724个 |
5.6G | 37.1s | 529341个 |
文件数量对 COPY 影响不大。这是由于在 Docker CLI 将 build context 发送给 Dockerd 时,会对 context 进行 tar 打包,并不是一个一个文件传输。
5.6G 529341个
并发量 | 构建时长 |
1 | 37.1s |
2 | 46s |
3 | 81s |
通过 iotop 可以实时观测到磁盘写速度,最快能达到 200MB/s,与文件系统 4K 随机写速度最接近。
Rand_Write_Testing: (groupid=0, jobs=1): err= 0: pid=30436
write: IOPS=37.9k, BW=148MiB/s (155MB/s)(3072MiB/20752msec); 0 zone resets由于公用一个 Dockerd,并发时 Dockerd 吞吐会有瓶颈,系统磁盘 IO 也会成为瓶颈。
如果提示找不到 docker build,则需要开启EXPERIMENTAL 或者没有 buildx,需要下载 docker-buildx 到 /usr/libexec/docker/cli-plugins/ 目录。
docker system df -vDOCKER_BUILDKIT=1 docker builder prune -f仅当开启 BuildKit 时,才会产生 Build cache。生产环境的缓存大小达到 1.408TB,但比较清理前后,对于新项目的构建并没有发现明显构建速度变化;对于老项目,如果没有变动,命中缓存后速度很快。可能的原因是缓存虽大但条目不多,查询是否有缓存的时间开销很小。
但定期定理缓存,有利于预防磁盘被占满的风险。
清理掉 72h 之前的缓存
DOCKER_CLI_EXPERIMENTAL=enabled docker buildx prune --filter "until=72h" -fDockerfile 文件
FROM ubuntu
RUN apt-get update -y
RUN apt-get install -y stress
RUN stress -c 40DOCKER_BUILDKIT=1 docker build --no-cache -t test:v3 -f Dockerfile .构建机有 40C,构建时机器 CPU 负载能达到 95%,说明构建时,Dockerd 默认不会对 CPU 消耗进行限制。在生产环境下,出现过 npm run build 占用 十几个 GB 内存的场景,因此我判断 Dockerd 默认也不会对内存消耗进行限制。
Dockerfile 文件
FROM ubuntu
RUN apt-get update -y
RUN apt-get install -y fio
RUN fio -direct=1 -iodepth=128 -rw=randwrite -ioengine=libaio -bs=4k -size=3G -numjobs=1 -runtime=1000 -group_reporting -filename=/tmp/test.file --allow_mounted_write=1 -name=Rand_Write_TestingDOCKER_BUILDKIT=1 docker build --no-cache -t test:v3 -f Dockerfile .
Rand_Write_Testing: (groupid=0, jobs=1): err= 0
write: IOPS=17.4k, BW=67.9MiB/s (71.2MB/s)(3072MiB/45241msec); 0 zone resetsdocker run -it shaowenchen/demo-fio bashRand_Write_Testing: (groupid=0, jobs=1): err= 0
write: IOPS=17.4k, BW=68.1MiB/s (71.4MB/s)(3072MiB/45091msec); 0 zone resetsdocker run -v /tmp:/tmp -it shaowenchen/demo-fio bashRand_Write_Testing: (groupid=0, jobs=1): err= 0
write: IOPS=39.0k, BW=152MiB/s (160MB/s)(3072MiB/20162msec); 0 zone resetsRand_Write_Testing: (groupid=0, jobs=1): err= 0
write: IOPS=38.6k, BW=151MiB/s (158MB/s)(3072MiB/20366msec); 0 zone resetsDockerd 在构建 Dockerfile 时,遇到 Run 命令会启动一个容器运行,然后提交镜像。从测试结果,可以看到 Dockerfile 中的 IO 速度远达不到主机的,与容器中的 IO 速度一致;主机存储卷的 IO 速度与主机的 IO 速度一致。
虽然可以通过 DOCKER_BUILDKIT=1 开启 Buildkit 构建,但如果直接使用 buildkitd 效果不错,用于替换 Dockerd 构建也是一个不错的选择。
wget https://github.com/moby/buildkit/releases/download/v0.11.2/buildkit-v0.11.2.linux-amd64.tar.gz
tar xvf buildkit-v0.11.2.linux-amd64.tar.gz
mv bin/* /usr/local/bin/cat > /usr/lib/systemd/system/buildkitd.service <<EOF
[Unit]
Description=/usr/local/bin/buildkitd
ConditionPathExists=/usr/local/bin/buildkitd
After=containerd.service
[Service]
Type=simple
ExecStart=/usr/local/bin/buildkitd
User=root
Restart=on-failure
RestartSec=1500ms
[Install]
WantedBy=multi-user.target
EOFsystemctl daemon-reload
systemctl restart buildkitd
systemctl enable buildkitd
systemctl status buildkitd查看到 buildkitd 正常运行即可。
buildctl build --frontend=dockerfile.v0 --local context=. --local dockerfile=. --no-cache --output type=docker,name=test:v4 | docker load
[+] Building 240.8s (7/7) FINISHED使用 buildctl 提交给 buildkitd 进行构建,需要的时间更多,达到 4min,较之前增加一倍。
在代码 https://github.com/moby/moby/blob/8d193d81af9cbbe800475d4bb8c529d67a6d8f14/builder/dockerfile/dispatchers.go 可以找到处理 Dockerfile 的逻辑。
1,Add 和 Copy 都是调用 performCopy 函数 2,performCopy 中调用 NewRWLayer() 新建层,调用 exportImage 写入数据
因此,怀疑的是 Dockerd 写镜像层速度慢。
准备一个镜像,大小 16GB,一共 18 层。
time docker load < /tmp/16GB.tar
real 2m43.288stime docker save 0d08de176b9f > /tmp/16GB.tar
real 2m48.497sdocker load 和 docker save 速度差不多,对镜像层的处理速度大约为 100 MB/s。这个速度比磁盘 4K 随机写速度少了近 30%。在我看来,如果是个人使用勉强接受;如果用于对外提供构建服务的平台产品,这块磁盘显然是不合适的。
下面是从 https://docs.docker.com/storage/storagedriver/select-storage-driver/ 整理得出的一个比较表格:
存储驱动 | 文件系统要求 | 高频写入性能 | 稳定性 | 其他 |
overlay2 | xfs、ext4 | 差 | 好 | 当前首选 |
fuse-overlayfs | 无限制 | - | - | 适用 rootless 场景 |
btrfs | btrfs | 好 | - | - |
zfs | zfs | 好 | - | - |
vfs | 无限制 | - | - | 不建议生产 |
aufs | xfs、ext4 | - | 好 | Docker 18.06 及之前版本首选,不维护 |
devicemapper | direct-lvm | 好 | 好 | 不维护 |
overlay | xfs、ext4 | 差,但好于 overlay2 | - | 不维护 |
排除不维护和非生产适用的,可选项其实没几个。正好有一台机器,前段时间初始化时,将磁盘格式化成 Btrfs 文件格式,可以用于测试。zfs 存储驱动推荐用于高密度 PaaS 系统。
Rand_Write_Testing: (groupid=0, jobs=1): err= 0
write: IOPS=40.0k, BW=160MiB/s (168MB/s)(3072MiB/19191msec); 0 zone resets运行容器
docker run -it shaowenchen/demo-fio bash执行测试
fio -direct=1 -iodepth=128 -rw=randwrite -ioengine=libaio -bs=4k -size=3G -numjobs=1 -runtime=1000 -group_reporting -filename=/data/test.file --allow_mounted_write=1 -name=Rand_Write_Testingdocker info
Server Version: 20.10.12
Storage Driver: overlay2
Backing Filesystem: btrfsRand_Write_Testing: (groupid=0, jobs=1): err= 0: pid=78: Thu Feb 2 02:41:48 2023
write: IOPS=21.5k, BW=84.1MiB/s (88.2MB/s)(3072MiB/36512msec); 0 zone resetsdocker info
Server Version: 20.10.12
Storage Driver: btrfs
Build Version: Btrfs v5.4.1Rand_Write_Testing: (groupid=0, jobs=1): err= 0
write: IOPS=39.8k, BW=156MiB/s (163MB/s)(3072MiB/19750msec); 0 zone resets可以明显看到 btrfs 存储驱动在速度上优于 overlay2。
本篇主要是记录在生产环境下碰到的 Dockerfile 构建 IO 慢问题排查过程。
通过设计各种测试案例排查问题,对各个要素进行一一验证,需要极大耐心,也特别容易走错方向,得出错误结论。
本篇主要观点如下:
但最简单的还是使用 4K 随机读写快的磁盘,在拿到新的环境用于生产之前,务必先进行测试,仅当满足需求时,再执行后续计划。
我想为Heroku构建一个Rails3应用程序。他们使用Postgres作为他们的数据库,所以我通过MacPorts安装了postgres9.0。现在我需要一个postgresgem并且共识是出于性能原因你想要pggem。但是我对我得到的错误感到非常困惑当我尝试在rvm下通过geminstall安装pg时。我已经非常明确地指定了所有postgres目录的位置可以找到但仍然无法完成安装:$envARCHFLAGS='-archx86_64'geminstallpg--\--with-pg-config=/opt/local/var/db/postgresql90/defaultdb/po
尝试通过RVM将RubyGems升级到版本1.8.10并出现此错误:$rvmrubygemslatestRemovingoldRubygemsfiles...Installingrubygems-1.8.10forruby-1.9.2-p180...ERROR:Errorrunning'GEM_PATH="/Users/foo/.rvm/gems/ruby-1.9.2-p180:/Users/foo/.rvm/gems/ruby-1.9.2-p180@global:/Users/foo/.rvm/gems/ruby-1.9.2-p180:/Users/foo/.rvm/gems/rub
我的最终目标是安装当前版本的RubyonRails。我在OSXMountainLion上运行。到目前为止,这是我的过程:已安装的RVM$\curl-Lhttps://get.rvm.io|bash-sstable检查已知(我假设已批准)安装$rvmlistknown我看到当前的稳定版本可用[ruby-]2.0.0[-p247]输入命令安装$rvminstall2.0.0-p247注意:我也试过这些安装命令$rvminstallruby-2.0.0-p247$rvminstallruby=2.0.0-p247我很快就无处可去了。结果:$rvminstall2.0.0-p247Search
由于fast-stemmer的问题,我很难安装我想要的任何rubygem。我把我得到的错误放在下面。Buildingnativeextensions.Thiscouldtakeawhile...ERROR:Errorinstallingfast-stemmer:ERROR:Failedtobuildgemnativeextension./System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/bin/rubyextconf.rbcreatingMakefilemake"DESTDIR="cleanmake"DESTDIR=
这里有一个很好的答案解释了如何在Ruby中下载文件而不将其加载到内存中:https://stackoverflow.com/a/29743394/4852737require'open-uri'download=open('http://example.com/image.png')IO.copy_stream(download,'~/image.png')我如何验证下载文件的IO.copy_stream调用是否真的成功——这意味着下载的文件与我打算下载的文件完全相同,而不是下载一半的损坏文件?documentation说IO.copy_stream返回它复制的字节数,但是当我还没有下
当我尝试安装Ruby时遇到此错误。我试过查看this和this但无济于事➜~brewinstallrubyWarning:YouareusingOSX10.12.Wedonotprovidesupportforthispre-releaseversion.Youmayencounterbuildfailuresorotherbreakages.Pleasecreatepull-requestsinsteadoffilingissues.==>Installingdependenciesforruby:readline,libyaml,makedepend==>Installingrub
我正在尝试解析一个文本文件,该文件每行包含可变数量的单词和数字,如下所示:foo4.500bar3.001.33foobar如何读取由空格而不是换行符分隔的文件?有什么方法可以设置File("file.txt").foreach方法以使用空格而不是换行符作为分隔符? 最佳答案 接受的答案将slurp文件,这可能是大文本文件的问题。更好的解决方案是IO.foreach.它是惯用的,将按字符流式传输文件:File.foreach(filename,""){|string|putsstring}包含“thisisanexample”结果的
我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www
我意识到这可能是一个非常基本的问题,但我现在已经花了几天时间回过头来解决这个问题,但出于某种原因,Google就是没有帮助我。(我认为部分问题在于我是一个初学者,我不知道该问什么......)我也看过O'Reilly的RubyCookbook和RailsAPI,但我仍然停留在这个问题上.我找到了一些关于多态关系的信息,但它似乎不是我需要的(尽管如果我错了请告诉我)。我正在尝试调整MichaelHartl'stutorial创建一个包含用户、文章和评论的博客应用程序(不使用脚手架)。我希望评论既属于用户又属于文章。我的主要问题是:我不知道如何将当前文章的ID放入评论Controller。
在编写Ruby(客户端脚本)时,我看到了三种构建更长字符串的方法,包括行尾,所有这些对我来说“闻起来”有点难看。有没有更干净、更好的方法?变量递增。ifrender_quote?quote="NowthatthereistheTec-9,acrappyspraygunfromSouthMiami."quote+="ThisgunisadvertisedasthemostpopularguninAmericancrime.Doyoubelievethatshit?"quote+="Itactuallysaysthatinthelittlebookthatcomeswithit:themo