ChatGPT解决这个技术问题 Extra ChatGPT

如何在镜像更改后升级 docker 容器

假设我已经取消了官方mysql:5.6.21 image

我通过创建几个 docker 容器部署了这个镜像。

这些容器已经运行了一段时间,直到 MySQL 5.6.22 发布。 mysql:5.6 的官方镜像随着新版本的更新而更新,但我的容器仍然运行 5.6.21。

如何将映像中的更改(即升级 MySQL 发行版)传播到所有现有容器?这样做的正确Docker方式是什么?

我制作了一个实用程序来自动更新 docker 图像:github.com/PHPExpertsInc/DockerUpgrader

r
robsch

在评估了答案并研究了我想总结的主题之后。

Docker升级容器的方式似乎如下:

应用程序容器不应存储应用程序数据。这样,您可以随时通过执行以下操作将应用容器替换为其较新版本:

docker pull mysql
docker stop my-mysql-container
docker rm my-mysql-container
docker run --name=my-mysql-container --restart=always \
  -e MYSQL_ROOT_PASSWORD=mypwd -v /my/data/dir:/var/lib/mysql -d mysql

您可以将数据存储在主机上(在作为卷安装的目录中)或特殊的仅数据容器中。阅读更多关于它的信息

关于卷(Docker 文档)

Tiny Docker Pieces,松散连接(Tom Offermann)

如何处理 Docker 中的持久存储(例如数据库)(堆栈溢出问题)

在容器中升级应用程序(例如使用 yum/apt-get upgrade)被认为是一种反模式。应用程序容器应该是不可变的,这应保证可重现的行为。一些官方应用程序映像(尤其是 mysql:5.6)甚至没有设计为自我更新(apt-get upgrade 不起作用)。

我要感谢所有给出答案的人,所以我们可以看到所有不同的方法。


如果需要数据迁移怎么办?新服务器无法挂载数据,因为它是旧格式,它需要知道正在发生迁移并更改数据表示。
我认为,图像设计人员应该考虑到这一点,并允许在容器首次运行期间启动自定义(例如,数据迁移)命令。
@static_rtti 在创建新的 docker rename my-mysql-container trash-container 之前怎么样?
是否有任何一体化命令来更新容器而无需手动停止、删除并重新创建它(基于已拉取的新映像)?
如何恢复用于创建容器的命令?我不记得我通过的所有选项。
k
kMaiSmith

我不喜欢挂载卷作为指向主机目录的链接,所以我想出了一个模式,用完全由 docker 管理的容器来升级 docker 容器。使用 --volumes-from <container> 创建一个新的 docker 容器将使具有更新图像的新容器共享 docker 托管卷的所有权。

docker pull mysql
docker create --volumes-from my_mysql_container [...] --name my_mysql_container_tmp mysql

如果尚未立即删除原始 my_mysql_container,您可以在升级后的容器没有正确的数据或未通过健全性测试时恢复到已知的工作容器。

在这一点上,我通常会为容器运行我拥有的任何备份脚本,以便在出现问题时给自己一个安全网

docker stop my_mysql_container
docker start my_mysql_container_tmp

现在您有机会确保您希望在新容器中的数据存在并运行健全性检查。

docker rm my_mysql_container
docker rename my_mysql_container_tmp my_mysql_container

只要任何容器正在使用 docker 卷,它们就会一直存在,因此您可以安全地删除原始容器。移除原始容器后,新容器可以假定与原始容器同名,以使一切都像开始时一样漂亮。

使用此模式升级 docker 容器有两个主要优点。首先,它允许卷直接传输到升级的容器,从而消除了将卷挂载到主机目录的需要。其次,您永远不会处于没有工作的 docker 容器的位置;因此,如果升级失败,您可以通过再次启动原始 docker 容器轻松恢复到之前的工作方式。


为什么不喜欢在 Docker 容器中安装主机卷? (我正是这样做的,所以我对反对这样做的论点感兴趣:-) 我已经安装了例如:./postgres-data/:/var/lib/postgres/data — 即在我的 PostgreSQL 容器中安装了主机目录 ./postgres-data/。)
@KajMagnus 我经常使用 docker swarms,而且我喜欢编写容器以在 swarm 中正常工作。当我在一个集群中启动一个容器时,我不知道容器将在哪个集群节点上运行,所以我不能依赖包含我想要的数据的主机路径。由于 Docker 1.9(我认为)卷可以跨主机共享,这使得使用我描述的方法升级和迁移容器变得轻而易举。另一种方法是确保在所有 swarm 节点上都安装了一些网络卷,但这听起来像是一个巨大的维护痛苦。
谢谢!好的,现在安装主机卷似乎也是我想要避免的事情。如果我的应用程序变得流行并且需要扩展到多台服务器,至少要晚一点
R
Ronan Fauglas

只是为了提供更一般的(不是特定于 mysql 的)答案......

简而言之

与服务映像注册表 (https://docs.docker.com/compose/compose-file/#image) 同步:

docker-compose pull 

如果 docker-compose 文件或图像已更改,则重新创建容器:

docker-compose up -d

背景

容器镜像管理是使用 docker-compose 的原因之一(参见https://docs.docker.com/compose/reference/up/

如果服务存在现有容器,并且在创建容器后更改了服务的配置或映像,则 docker-compose up 通过停止并重新创建容器(保留已安装的卷)来获取更改。要防止 Compose 获取更改,请使用 --no-recreate 标志。

docker-compose 还通过挂载的外部“卷”(参见 https://docs.docker.com/compose/compose-file/#volumes)或数据容器来涵盖数据管理方面。

这使得潜在的向后兼容性和数据迁移问题保持不变,但这些是“应用性”问题,而不是 Docker 特定的问题,必须对照发行说明和测试进行检查......


您如何使用版本控制?例如新图像是 foo/image:2 并且 docker-compose.yml 有 image: foo/image:1?
虽然这肯定是要走的路,但应该知道,一旦重新创建容器,对容器所做的任何更改仍然会丢失。因此,将容器更改保持在已安装的卷内仍然是必要的。
伟大的点彼得博德纳尔!如果确实提取了新图像,则容器将被重新创建,因为基本图像和提取的图像不同(Kubernetes),我相信类似组合的东西。如果您在本地使用 docker,那么只有在本地缓存没有指定的标签时才会进行获取。因为实际上有一个新的最新图像,docker 将只使用您在(由第一个构建缓存)上标记为最新的图像。在再次撰写之前,您需要进行拉取以获得实时的最新版本,并更新适当的标签。
P
Peter Butkovic

我想补充一点,如果您想自动执行此过程(下载、停止和重新启动具有与@Yaroslav 描述的相同设置的新容器),您可以使用 WatchTower。一个在容器更改时自动更新容器的程序https://github.com/v2tec/watchtower


A
Alexis Tyler

考虑这个答案:

数据库名称是 app_schema

容器名称是 app_db

root密码是root123

在容器内存储应用程序数据时如何更新 MySQL

这被认为是一种不好的做法,因为如果您丢失了容器,您将丢失数据。尽管这是一种不好的做法,但这是一种可能的方法:

1) 将数据库转储为 SQL:

docker exec app_db sh -c 'exec mysqldump app_schema -uroot -proot123' > database_dump.sql

2)更新图像:

docker pull mysql:5.6

3)更新容器:

docker rm -f app_db
docker run --name app_db --restart unless-stopped \
-e MYSQL_ROOT_PASSWORD=root123 \
-d mysql:5.6

4)恢复数据库转储:

docker exec app_db sh -c 'exec mysql -uroot -proot123' < database_dump.sql

如何使用外部卷更新 MySQL 容器

使用外部卷是一种更好的数据管理方式,并且可以更轻松地更新 MySQL。丢失容器不会丢失任何数据。您可以使用 docker-compose 来促进在单个主机中管理多容器 Docker 应用程序:

1) 创建 docker-compose.yml 文件以管理您的应用程序:

version: '2'
services:
  app_db:
    image: mysql:5.6
    restart: unless-stopped
    volumes_from: app_db_data
  app_db_data:
    volumes: /my/data/dir:/var/lib/mysql

2) 更新 MySQL(来自与 docker-compose.yml 文件相同的文件夹):

docker-compose pull
docker-compose up -d

注意:上面的最后一条命令将更新 MySQL 镜像,重新创建并使用新镜像启动容器。


假设我有一个巨大的数据库(几 GB),在导入整个数据库之前我的数据是否无法访问?这可能是一个巨大的“停机时间”
既然您提到了 docker-compose,这会起作用吗? stackoverflow.com/a/31485685/65313
现在不推荐使用 volumes_from 键(甚至在 compose 文件的版本 3 中也将其删除),取而代之的是新的 volumes 键。
docker pull image_uri:tag && docker restart container_running_that_image 为我工作。不需要 docker-compose pull && docker-compose up -d
E
Eddie Jaoude

与上面类似的答案

docker images | awk '{print $1}' | grep -v 'none' | grep -iv 'repo' | xargs -n1 docker pull

杰出的!很惊讶它没有得到更多的选票。现在唯一缺少的是触发所有已更新容器的重新启动。
不幸的是,这不会更新现有的容器。这只会更新拉取的镜像,但现有容器是不可变的,并且仍然使用用于创建它的原始镜像。这仅在您从图像创建新容器时才有效,但任何现有容器仍基于原始图像。
惊人。如果需要拉取特定版本的容器,可以这样操作:docker images | awk '{打印 $1":"$2}' | grep -v '无' | grep -iv 'repo' | xargs -n1 搬运工拉
g
gdbj

下面是在构建自定义 Dockerfile 时使用 docker-compose 的样子。

首先构建您的自定义 Dockerfile,附加下一个版本号以区分。例如: docker build -t imagename:version 。这将在本地存储您的新版本。运行 docker-compose down 编辑您的 docker-compose.yml 文件以反映您在步骤 1 中设置的新映像名称。运行 docker-compose up -d。它将在本地查找图像并使用您升级的图像。

-编辑-

我上面的步骤比他们需要的更冗长。通过将 build: . 参数包含到我的 docker-compose 文件中,我优化了我的工作流程。现在的步骤如下:

验证我的 Dockerfile 是我想要的样子。在我的 docker-compose 文件中设置我的图像名称的版本号。如果我的图像尚未构建:运行 docker-compose build 运行 docker-compose up -d

当时我没有意识到,但 docker-compose 足够聪明,只需使用一个命令将我的容器更新为新图像,而不必先将其关闭。


在实际情况下,您不能用自己的双手进行这些更改。您的解决方案不支持自动解决问题的方法。
所以你是说因为我的解决方案不是自动化的,所以它是无效的?这是OP的要求吗?其他答案是否暗示自动化?真的很迷茫。而且,我认为反对票对来到这里的其他人不利。我的回答对于所提出的问题是 100% 有效的。
感谢您的回答,我也没有意识到您可以简单地运行 docker-compose up -d 而无需先停止一切。
a
afe

如果您不想使用 Docker Compose,我可以推荐 portainer。它具有重新创建功能,可让您在拉取最新图像时重新创建容器。


s
seanmcl

您需要重建所有映像并重新启动所有容器,或者以某种方式 yum 更新软件并重新启动数据库。没有升级路径,只能由您自己设计。


重新启动容器到底是什么意思?有 docker restart 命令,但我不确定它是否会获取图像更改。我在容器中的数据会发生什么?
对不起,我不是说 docker 重启。我的意思是 docker rm -f 容器;泊坞窗运行 NEW_IMAGE。您的 sql 容器中的数据将消失。这就是人们通常使用卷来存储数据的原因。
如果您将所有数据安装在单独的容器或主机中的卷中,那么 nas @seanmcl 说只需创建新容器,新的 mysql 连接到相同的数据。如果你没有这样做(你应该这样做),但你可以使用 docker 1.3 中可用的 docker exec 命令来更新 mysql 并在容器内重新启动它。
g
gvlx

取自http://blog.stefanxo.com/2014/08/update-all-docker-images-at-once/

您可以使用以下命令管道更新所有现有图像:

docker images | awk '/^REPOSITORY|\<none\>/ {next} {print $1}' | xargs -n 1 docker pull

这将更新图像,但不会更新容器。容器是不可变的,如果不从更新的镜像创建新容器,就无法更改它的基础镜像。
D
Daniel Dinnyes

确保您将卷用于存储在与容器内进程状态相关的容器上的所有持久数据(配置、日志或应用程序数据)。更新您的 Dockerfile 并使用您想要的更改重建映像,然后重新启动容器并将您的卷安装在适当的位置。


c
clopez

这也是我一直在为自己的图像而苦苦挣扎的事情。我有一个服务器环境,我从中创建一个 Docker 映像。当我更新服务器时,我希望所有基于我的 Docker 映像运行容器的用户都能够升级到最新的服务器。

理想情况下,我更愿意生成新版本的 Docker 映像,并让所有基于该映像先前版本的容器自动更新为“就地”新映像。但这种机制似乎并不存在。

因此,到目前为止,我能够提出的下一个最佳设计是提供一种让容器自行更新的方法——类似于桌面应用程序检查更新然后自行升级的方式。就我而言,这可能意味着制作一个涉及 Git 从知名标签中提取的脚本。

图像/容器实际上并没有改变,但该容器的“内部”发生了变化。您可以想象使用 apt-get、yum 或任何适合您的环境的东西来做同样的事情。除此之外,我还会更新注册表中的 myserver:latest 映像,以便任何新容器都基于最新映像。

我很想知道是否有任何现有技术可以解决这种情况。


它违背了不可变基础设施的概念及其一些好处。您可以测试您的应用程序/环境以查看它是否正常工作,如果您更新内部组件,则无法保证。从配置数据中拆分容器代码可以让您更新、测试我们当前工作并部署到生产环境中,因为从测试镜像到生产镜像没有不同的代码行。无论如何,系统也让您按您说的管理它,它是您的选择。
非常好的点 gmuslera。同意更新现有 docker 容器的“内部”是一种反模式。
那么,基于 docker 镜像更新自动更新 docker 容器以毫不费力地将更新交付给所有容器的最佳解决方案是什么?
i
iTech

更新

这主要是查询容器不要更新,因为构建图像是要完成的方式

我遇到了同样的问题,因此我创建了 docker-run,这是一个非常简单的命令行工具,在 docker container 内运行,用于更新其他正在运行的容器中的包。

它使用 docker-py 与正在运行的 docker 容器通信并更新包或运行任意单个命令

例子:

docker run --rm -v /var/run/docker.sock:/tmp/docker.sock itech/docker-run exec

默认情况下,这将在所有正在运行的容器中运行 date 命令并返回结果,但您可以发出任何命令,例如 docker-run exec "uname -a"

要更新软件包(目前仅使用 apt-get):

docker run --rm -v /var/run/docker.sock:/tmp/docker.sock itech/docker-run update

您可以创建和别名并将其用作常规命令行,例如

alias docker-run='docker run --rm -v /var/run/docker.sock:/tmp/docker.sock itech/docker-run'


这是一个好主意吗? (如果您执行 apt update; apt upgrade,图像会变大。)
@yaroslav 的图片是解决此问题的更好方法。以上并不是真正的 Docker 做事方式。
Z
Zedverse07

从这里尝试了很多东西,但这最终对我有用。如果您在容器上有 AutoRemove: On,您无法停止和编辑容器,或者正在运行的服务甚至无法暂时停止,您必须:

拉取最新图片 --> docker pull [image:latest] 验证是否拉取了正确的图像,您可以在 Portainer Images 部分看到 UNUSED 标签

使用 Portainer 或 CLI 更新服务并确保使用最新版本的图像,Portainer 将为您提供执行相同操作的选项。

这不仅会使用最新图像更新容器,还会保持服务运行。