1. 概述

在我们的项目中,我们通常使用 docker-compose 部署容器化应用。随着 CI 和 CD 的普及,代码更改和部署变得非常频繁。因此,确保 docker-compose 总是使用应用程序的最新镜像至关重要。

本文将介绍几种实现这一目标的方法。

2. 显式拉取镜像

让我们以一个简单的 docker-compose 文件为例:

version: '2.4'
services:
  db:
    image: postgres
  my_app:
    image: "eugen/test-app:latest"
    ports:
      - "8080:8080"

在这里,我们使用了两个服务 - 一个是 PostgreSQL 数据库,另一个是测试应用。我们使用以下 命令 部署我们的应用:

$ docker-compose up -d

这个命令从远程仓库(例如 dockerhub)拉取两个镜像并创建相应的容器。

当我们在相同的命令下重新部署应用时,问题出现了。因为这些镜像已经存在于本地仓库中,它不会检查远程仓库中是否有这两个镜像的更新。 因此,我们的选择是在 docker-compose 文件之前显式拉取所有镜像:

$ docker-compose pull
Pulling db     ... done
Pulling my_app ... done

此命令首先检查 dockerhub 中是否存在两个镜像的更新。然后,它仅下载必要的层,以使镜像与远程版本保持同步。

但是,我们并不总是希望拉取 Postgres 的镜像。如果使用数据库的稳定版本,则镜像不会有变化。因此,每次部署都下载这个镜像并不合理。有时,现有的数据文件与新版本不兼容。这可能会破坏部署。在这种情况下, 我们可能只在 pull 命令中使用特定服务名称:

$ docker-compose pull my_app
Pulling my_app ... done

现在,如果我们执行 up 命令,这将确保使用最新的镜像重新创建容器:

$ docker-compose up -d
Starting docker-test_db_1     ... done
Starting docker-test_my_app_1 ... done

当然,我们可以将这两种命令组合成一行:

$ docker-compose pull && docker-compose up -d

3. 移除本地镜像

让我们再次考虑同一个例子。我们已经知道主要的问题在于存在本地镜像。 因此,我们有第二个选项是停止所有容器并将它们的镜像从本地仓库中删除:

$ docker-compose down --rmi all
Stopping docker-test_my_app_1 ... done
Removing docker-test_db_1     ... done
Removing docker-test_my_app_1 ... done
Removing network docker-test_default
Removing image postgres
Removing image eugen/test-app:latest

[down] 命令停止并移除了容器。–rmi 选项从本地仓库删除镜像。rmi 类型 local 只删除本地构建的镜像。因此,最好使用 rmi 类型 all 确保当前配置使用的所有镜像都被删除。

现在,我们使用 up 命令再次启动容器:

$ docker-compose up -d
Creating network "docker-test_default" with the default driver
Pulling db (postgres:)...
latest: Pulling from library/postgres
7d63c13d9b9b: Pull complete
cad0f9d5f5fe: Pull complete
...
Digest: sha256:eb83331cc518946d8ee1b52e6d9e97d0cdef6195b7bf25323004f2968e91a825
Status: Downloaded newer image for postgres:latest
Pulling my_app (eugen/test-app:latest)...
latest: Pulling from eugen/test-app
df5590a8898b: Already exists
705bb4cb554e: Already exists
...
Digest: sha256:31c05c8245192b32b8b359fc58b5e45d8397674ccf41f5f17a7d3109772ab5c1
Status: Downloaded newer image for eugen/test-app:latest
Creating docker-test_db_1     ... done
Creating docker-test_my_app_1 ... done

我们可以看到,这次它不再在本地仓库中找到镜像。因此,它从远程仓库拉取最新的镜像来重新创建容器。

这种方法的缺点是你不能选择特定的镜像进行删除。因此,它总是删除 postgres 镜像并再次下载同样的镜像,这是不必要的。为了解决这个问题,我们可以使用 docker rmi 删除特定的镜像:

$ docker rmi -f eugen/test-app:latest
Untagged: eugen/test-app:latest
Untagged: eugen/test-app@sha256:31c05c8245192b32b8b359fc58b5e45d8397674ccf41f5f17a7d3109772ab5c1
Deleted: sha256:7bc07b4eb1c23f7a91afeb7133f107e0a8318fb77655d7d5f2f395a035a13eb7

4. 重建镜像

让我们看看 docker-compose 文件的相同示例,但使用不同的风味:

version: '2.4'
services:
  db:
    image: postgres
  my_app:
    build: ./test-app
    ports:
      - "8080:8080"

这里,我们使用相同的 db 服务与 PostgreSQL 相关。但对于服务 my_app,我们给出了构建部分而不是使用现成的镜像。这部分包含了 test-app 的构建上下文。位于 test-app 目录中的 docker 文件如下所示:

FROM openjdk:11
COPY target/test-app-0.0.1-SNAPSHOT.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

在这种情况下,当我们使用 up 命令重新部署时,docker-compose 如果存在本地镜像则会重新使用。因此,我们需要确保每次触发部署时 docker-compose 重建镜像。 我们可以使用 –build 选项实现这一目标:

$ docker-compose up -d --build
Building my_app
[+] Building 2.5s (8/8) FINISHED                                                                                            
 => [internal] load build definition from Dockerfile                                                                    0.0s
 => => transferring dockerfile: 41B                                                                                     0.0s
 => [internal] load .dockerignore                                                                                       0.0s
 => => transferring context: 2B                                                                                         0.0s
 => [internal] load metadata for docker.io/library/openjdk:11                                                           2.3s
 => [auth] library/openjdk:pull token for registry-1.docker.io                                                          0.0s
 => [1/2] FROM docker.io/library/openjdk:11@sha256:1b04b1958a4a61900feec994e3938a2a5d8f88db8ec9515f46a25cbe561b65d9     0.0s
 => [internal] load build context                                                                                       0.0s
 => => transferring context: 84B                                                                                        0.0s
 => CACHED [2/2] COPY target/test-app-0.0.1-SNAPSHOT.jar app.jar                                                        0.0s
 => exporting to image                                                                                                  0.0s
 => => exporting layers                                                                                                 0.0s
 => => writing image sha256:4867a3f0b0a043cd54e16086e2d3c81dbf4c418806399f60fc7d7ffc094c7159                            0.0s
 => => naming to docker.io/library/docker-test_my_app                                                                   0.0s

Starting docker-test_db_1 ... 
Starting docker-test_db_1 ... done

另一种方法是在运行 up 命令之前使用 build 命令:

$ docker-compose build --pull --no-cache
db uses an image, skipping
Building my_app
...                                                                                                           0.0s
 => => naming to docker.io/library/docker-test_my_app 

此命令提供了两个额外的选项。–pull 选项要求在构建镜像时从远程仓库拉取 Dockerfile 的基础镜像。–no-cache 选项表示跳过本地缓存。然而,它跳过了 db 服务的构建,因为我们在那里直接使用了镜像。

现在,如果我们重启 compose 配置,容器将使用最新的镜像:

$ docker-compose up -d
Starting docker-test_db_1       ... done
Recreating docker-test_my_app_1 ... done

5. 结论

在这篇文章中,我们看到了为什么 docker-compose 在部署时可能不会使用最新镜像。我们也学习了几种让 docker-compose 总是使用最新镜像来使用具有该镜像重新创建容器的方法。我们还讨论了方法的陷阱以及如何缓解这些问题。

与本文相关的代码可以在 GitHub 上找到。