Docker 技巧:清除机器上的垃圾

Docker 技巧:清除机器上的垃圾

嘿哈布尔! 我向您展示这篇文章的翻译 “Docker 技巧:清理本地机器” 作者 吕克·贾格里.

今天我们将讨论 Docker 如何使用主机的磁盘空间,我们还将弄清楚如何从未使用的镜像和容器的碎片中释放该空间。


Docker 技巧:清除机器上的垃圾

总消费

Docker 是一个很酷的东西,今天可能很少有人怀疑它。 就在几年前,该产品为我们提供了一种全新的方式来构建、交付和运行任何环境,使我们能够显着节省 CPU 和 RAM 资源。 除此之外(对于某些人来说这将是最重要的事情),Docker 使我们能够极大地简化和统一我们使用的生产环境的生命周期管理。

然而,现代生活的所有这些乐趣都是有代价的。 当我们运行容器、下载或创建自己的镜像以及部署复杂的生态系统时,我们必须付费。 除其他外,我们还用磁盘空间来支付费用。

如果您从未考虑过 Docker 在您的计算机上实际占用了多少空间,您可能会对以下命令的输出感到惊讶:

$ docker system df

Docker 技巧:清除机器上的垃圾

这显示了 Docker 在不同上下文中的磁盘使用情况:

  • 图像 – 从图像存储库下载并在您的系统上构建的图像的总大小;
  • 容器 – 运行容器使用的磁盘空间总量(指所有容器读写层的总体积);
  • 本地卷 – 安装到容器的本地存储卷;
  • 构建缓存 – 映像构建过程生成的临时文件(使用 BuildKit 工具,从 Docker 版本 18.09 开始可用)。

我敢打赌,在这个简单的转移之后,您会渴望清除磁盘上的垃圾并让宝贵的千兆字节重新焕发活力(注意:特别是如果您每月为这些千兆字节支付租金)。

容器的磁盘使用情况

每次在宿主机上创建容器时,都会在 /var/lib/docker 目录下创建几个文件和目录,其中值得注意的是:

  • 目录 /var/lib/docker/containers/container_ID – 使用标准日志记录驱动程序时,这是以 JSON 格式保存事件日志的位置。 过于详细的日志以及无人读取或以其他方式处理的日志通常会导致磁盘变满。
  • /var/lib/docker/overlay2 目录包含容器读写层(overlay2 是大多数 Linux 发行版中的首选驱动程序)。 如果容器将数据存储在其文件系统中,则数据将放置在该目录中。

让我们想象一个安装了原始 Docker 的系统,它从未参与启动容器或构建镜像。 其磁盘空间使用报告如下所示:

$ docker system df
TYPE           TOTAL      ACTIVE     SIZE       RECLAIMABLE
Images         0          0          0B         0B
Containers     0          0          0B         0B
Local Volumes  0          0          0B         0B
Build Cache    0          0          0B         0B

让我们启动一些容器,例如 NGINX:

$ docker container run --name www -d -p 8000:80 nginx:1.16

磁盘会发生什么情况:

  • 图像占用126 MB,这与我们在容器中启动的NGINX相同;
  • 容器占用了荒谬的 2 个字节。

$ docker system df
TYPE           TOTAL      ACTIVE     SIZE       RECLAIMABLE
Images         1          1          126M       0B (0%)
Containers     1          1          2B         0B (0%)
Local Volumes  0          0          0B         0B
Build Cache    0          0          0B         0B

从结论来看,我们还没有任何可以释放的空间。 由于 2 个字节完全是无意义的,让我们想象一下,我们的 NGINX 意外地在某个地方写入了 100 MB 的数据,并在其内部创建了一个与此大小完全相同的文件 test.img。

$ docker exec -ti www 
  dd if=/dev/zero of=test.img bs=1024 count=0 seek=$[1024*100]

让我们再次检查一下主机上的磁盘空间使用情况。 我们将看到该容器(多个容器)占据了 100 MB 的空间。

$ docker system df
TYPE           TOTAL      ACTIVE     SIZE       RECLAIMABLE
Images         1          1          126M       0B (0%)
Containers     1          1          104.9MB    0B (0%)
Local Volumes  0          0          0B         0B
Build Cache    0          0          0B         0B

我想你好奇的大脑已经想知道我们的 test.img 文件位于哪里。 我们来找找看:

$ find /var/lib/docker -type f -name test.img
/var/lib/docker/overlay2/83f177...630078/merged/test.img
/var/lib/docker/overlay2/83f177...630078/diff/test.img

无需详细说明,我们可以注意到 test.img 文件很方便地位于读写级别,由overlay2 驱动程序控制。 如果我们停止容器,主机会告诉我们原则上可以释放该空间:

# Stopping the www container
$ docker stop www

# Visualizing the impact on the disk usage
$ docker system df
TYPE           TOTAL      ACTIVE     SIZE       RECLAIMABLE
Images         1          1          126M       0B (0%)
Containers     1          0          104.9MB    104.9MB (100%)
Local Volumes  0          0          0B         0B
Build Cache    0          0          0B         0B

我们应该怎么做? 通过删除容器,需要清除读写级别的相应空间。

使用以下命令,您可以一举删除所有已安装的容器,并清除磁盘上由它们创建的所有读写文件:

$ docker container prune
WARNING! This will remove all stopped containers.
Are you sure you want to continue? [y/N] y
Deleted Containers:
5e7f8e5097ace9ef5518ebf0c6fc2062ff024efb495f11ccc89df21ec9b4dcc2

Total reclaimed space: 104.9MB

因此,我们通过删除容器释放了 104,9 MB。 但由于我们不再使用之前下载的图像,它也成为删除和释放资源的候选者:

$ docker system df
TYPE           TOTAL      ACTIVE     SIZE       RECLAIMABLE
Images         1          0          126M       126M (100%)
Containers     0          0          0B         0B
Local Volumes  0          0          0B         0B
Build Cache    0          0          0B         0B

注意:只要该镜像至少被一个容器使用,您就无法使用这一技巧。

我们上面使用的 prune 子命令仅对停止的容器有效。 如果我们不仅想删除已停止的容器,还想删除正在运行的容器,我们应该使用以下命令之一:

# Historical command
$ docker rm -f $(docker ps –aq)

# More recent command
$ docker container rm -f $(docker container ls -aq)

附注:如果在启动容器时使用 -rm 参数,那么当容器停止时,它占用的所有磁盘空间将被释放。

使用磁盘映像

几年前,数百兆字节的映像大小是完全正常的:Ubuntu 映像重达 600 兆字节,Microsoft .Net 映像重达数千兆字节。 在那些杂乱的日子里,即使您在图像之间共享关卡,仅下载一张图像也可能会严重占用您的可用磁盘空间。 今天——赞美伟大的人——图像的重量要轻得多,但即便如此,如果您不采取一些预防措施,您也可以很快填满可用资源。

有几种类型的图像对最终用户来说不直接可见:

  • 中间图像,在此基础上收集其他图像 - 如果您使用基于这些“其他”图像的容器,则无法删除它们;
  • 悬空图像是任何正在运行的容器都没有引用的中间图像 - 它们可以被删除。
  • 使用以下命令,您可以检查系统上是否存在悬空图像:

$ docker image ls -f dangling=true
REPOSITORY  TAG      IMAGE ID         CREATED             SIZE
none      none   21e658fe5351     12 minutes ago      71.3MB

您可以通过以下方式删除它们:

$ docker image rm $(docker image ls -f dangling=true -q)

我们还可以使用 prune 子命令:

$ docker image prune
WARNING! This will remove all dangling images.
Are you sure you want to continue? [y/N] y
Deleted Images:
deleted: sha256:143407a3cb7efa6e95761b8cd6cea25e3f41455be6d5e7cda
deleted: sha256:738010bda9dd34896bac9bbc77b2d60addd7738ad1a95e5cc
deleted: sha256:fa4f0194a1eb829523ecf3bad04b4a7bdce089c8361e2c347
deleted: sha256:c5041938bcb46f78bf2f2a7f0a0df0eea74c4555097cc9197
deleted: sha256:5945bb6e12888cf320828e0fd00728947104da82e3eb4452f

Total reclaimed space: 12.9kB

如果我们突然想用一个命令完全删除所有图像(而不仅仅是悬空的图像),那么我们可以这样做:

$ docker image rm $(docker image ls -q)

磁盘使用量(按卷)

卷用于在容器文件系统外部存储数据。 例如,如果我们想要保存应用程序的结果以便以其他方式使用它们。 一个常见的例子是数据库。

让我们启动一个 MongoDB 容器,安装容器外部的卷,并从中恢复数据库备份(我们在 bck.json 文件中提供了它):

# Running a mongo container
$ docker run --name db -v $PWD:/tmp -p 27017:27017 -d mongo:4.0

# Importing an existing backup (from a huge bck.json file)
$ docker exec -ti db mongoimport 
  --db 'test' 
  --collection 'demo' 
  --file /tmp/bck.json 
  --jsonArray

数据将位于主机上的 /var/lib/docker/volumes 目录中。 但为什么不在容器的读写级别呢? 因为在 MongoDB 镜像的 Dockerfile 中,/data/db 目录(MongoDB 默认存储数据的位置)被定义为卷。

Docker 技巧:清除机器上的垃圾

旁注:许多必须生成数据的图像使用卷来存储该数据。

当我们使用 MongoDB 足够多并停止(甚至删除)容器时,卷将不会被删除。 它将继续占用我们宝贵的磁盘空间,直到我们使用如下命令明确删除它:

$ docker volume rm $(docker volume ls -q)

好吧,或者我们可以使用我们已经熟悉的 prune 子命令:

$ docker volume prune
WARNING! This will remove all local volumes not used by at least one container.
Are you sure you want to continue? [y/N] y
Deleted Volumes:
d50b6402eb75d09ec17a5f57df4ed7b520c448429f70725fc5707334e5ded4d5
8f7a16e1cf117cdfddb6a38d1f4f02b18d21a485b49037e2670753fa34d115fc
599c3dd48d529b2e105eec38537cd16dac1ae6f899a123e2a62ffac6168b2f5f
...
732e610e435c24f6acae827cd340a60ce4132387cfc512452994bc0728dd66df
9a3f39cc8bd0f9ce54dea3421193f752bda4b8846841b6d36f8ee24358a85bae
045a9b534259ec6c0318cb162b7b4fca75b553d4e86fc93faafd0e7c77c79799
c6283fe9f8d2ca105d30ecaad31868410e809aba0909b3e60d68a26e92a094da

Total reclaimed space: 25.82GB
luc@saturn:~$

使用磁盘作为镜像构建缓存

在 Docker 18.09 中,由于 BuildKit 工具,镜像创建过程发生了一些变化。 这个东西提高了处理速度并优化了数据存储和安全管理。 在这里,我们不会考虑这个精彩工具的所有细节;我们只会关注它如何解决磁盘空间使用问题。

假设我们有一个完全简单的 Node.Js 应用程序:

  • index.js 文件启动一个简单的 HTTP 服务器,该服务器对收到的每个请求响应一行:
  • package.json文件定义了依赖项,其中仅使用expressjs来运行HTTP服务器:

$ cat index.js
var express = require('express');
var util    = require('util');
var app = express();
app.get('/', function(req, res) {
  res.setHeader('Content-Type', 'text/plain');
  res.end(util.format("%s - %s", new Date(), 'Got Request'));
});
app.listen(process.env.PORT || 80);

$ cat package.json
    {
      "name": "testnode",
      "version": "0.0.1",
      "main": "index.js",
      "scripts": {
        "start": "node index.js"
      },
      "dependencies": {
        "express": "^4.14.0"
      }
    }

用于构建镜像的 Dockerfile 如下所示:

FROM node:13-alpine
COPY package.json /app/package.json
RUN cd /app && npm install
COPY . /app/
WORKDIR /app
EXPOSE 80
CMD ["npm", "start"]

让我们以通常的方式构建镜像,而不使用 BuildKit:

$ docker build -t app:1.0 .

如果我们检查磁盘空间使用情况,我们可以看到只有基础镜像 (node:13-alpine) 和目标镜像 (app:1.0) 占用了空间:

TYPE           TOTAL      ACTIVE     SIZE       RECLAIMABLE
Images         2          0          109.3MB    109.3MB (100%)
Containers     0          0          0B         0B
Local Volumes  0          0          0B         0B
Build Cache    0          0          0B         0B

让我们使用 BuildKit 构建应用程序的第二个版本。 为此,我们只需将 DOCKER_BUILDKIT 变量设置为 1:

$ DOCKER_BUILDKIT=1 docker build -t app:2.0 .

如果我们现在检查磁盘使用情况,我们将看到构建缓存(buid-cache)现在参与其中:

$ docker system df
TYPE           TOTAL      ACTIVE     SIZE       RECLAIMABLE
Images         2          0          109.3MB    109.3MB (100%)
Containers     0          0          0B         0B
Local Volumes  0          0          0B         0B
Build Cache    11         0          8.949kB    8.949kB

要清除它,请使用以下命令:

$ docker builder prune
WARNING! This will remove all dangling build cache.
Are you sure you want to continue? [y/N] y
Deleted build cache objects:
rffq7b06h9t09xe584rn4f91e
ztexgsz949ci8mx8p5tzgdzhe
3z9jeoqbbmj3eftltawvkiayi

Total reclaimed space: 8.949kB

全部清除!

因此,我们研究了清理容器、映像和卷占用的磁盘空间。 prune 子命令可以帮助我们完成此任务。 但它也可以在 docker 系统级别使用,并且它将清理它可以清理的所有内容:

$ docker system prune
WARNING! This will remove:
  - all stopped containers
  - all networks not used by at least one container
  - all dangling images
  - all dangling build cache

Are you sure you want to continue? [y/N]

如果出于某种原因您要在运行 Docker 的计算机上节省磁盘空间,那么定期运行此命令应该成为一种习惯。

来源: habr.com

添加评论