如何在 Docker 镜像变更后升级容器

655

假设我已经拉取了官方的mysql:5.6.21镜像

我通过创建多个Docker容器来部署此镜像。

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

我如何将镜像中的更改(即升级MySQL发行版)传播到所有现有的容器中?这是使用Docker的正确方法吗?


我已经制作了一个实用程序来自动更新Docker镜像:https://github.com/PHPExpertsInc/DockerUpgrader - Theodore R. Smith
@TheodoreR.Smith,您提到了“docker image update”,但问题是关于“docker container update”的。 - Roman Podlinov
14个回答

681

在评估答案并研究该主题后,我想做个总结。

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

您可以将数据存储在主机上(作为挂载的目录)或特殊的仅数据容器中。了解更多信息请参见:

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

感谢所有提供答案的人,这样我们就可以看到各种不同的方法。


40
如果需要数据迁移该怎么办?由于数据处于旧格式,新服务器无法挂载数据,因此需要知道正在进行迁移并更改数据表示方式。为此,数据迁移过程需要对新服务器进行适当的配置和更新。 - Dor Rotman
20
我认为图像设计师应该考虑这一点,并允许在容器首次运行期间启动自定义命令(例如数据迁移)。 - Yaroslav Stavnichiy
5
在创建新的容器之前,尝试使用docker rename my-mysql-container trash-container来重命名你的MySQL容器,这样做会更好吗? - Franklin Yu
10
有没有一条命令可以更新容器而无需手动停止、删除和重新创建它(基于已拉取的新镜像)? - Michaël Perrin
6
我该如何恢复创建容器所用的命令?我记不清传递的所有选项了。 - Sergei G
显示剩余8条评论

98

我不喜欢将卷挂载为主机目录的链接,因此我想出了一种方法来使用完全由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容器来恢复之前的工作状态。


4
为什么您不喜欢在Docker容器内挂载主机卷?(我正在做这件事,因此我对反对这样做的论点很感兴趣:-) 我已经挂载了例如:./postgres-data/:/var/lib/postgres/data - 也就是在我的PostgreSQL容器内挂载了主机目录./postgres-data/。) - KajMagnus
6
我经常使用Docker Swarm,并且喜欢编写能够在Swarm中运行良好的容器。当我在Swarm中启动一个容器时,我不知道这个容器将在哪个Swarm节点上运行,因此我不能依赖于主机路径来包含我想要的数据。自从Docker 1.9(我想)开始,卷可以在主机之间共享,使用我描述的方法使得升级和迁移容器变得轻而易举。另一种选择是确保某些网络卷在所有Swarm节点上都被挂载,但这听起来像是一个很大的维护负担。 - kMaiSmith
谢谢!好的,现在看来我也想避免挂载主机卷。至少等我的应用程序变得流行并需要扩展到多个服务器时再考虑。 - KajMagnus

63

仅为提供一个更加通用的(不针对MySQL特定的)答案...

  1. 简而言之

与服务镜像库同步(https://docs.docker.com/compose/compose-file/#image):

docker-compose pull 

如果 Docker Compose 文件或镜像已更改,则重新创建容器:

docker-compose up -d
  1. 背景

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

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

数据管理方面也通过挂载外部“卷”(请参见https://docs.docker.com/compose/compose-file/#volumes)或数据容器来使用docker-compose进行处理。

这仍然存在潜在的向后兼容性和数据迁移问题,但这些是“应用程序”问题,而不是Docker特定的问题,必须根据发布说明和测试进行检查...


你如何使用版本控制来处理这个问题?例如,新的镜像是foo/image:2,而docker-compose.yml文件中的镜像是foo/image:1。 - dman
虽然这肯定是正确的方法,但人们应该意识到,在容器中进行的任何更改仍将在容器重新创建时丢失。因此,仅在挂载的卷中保留容器更改仍然是必要的。 - Petr Bodnár
太好了,Petr Bodnar提出了很好的观点!如果确实获取了新的镜像,容器将会被重新创建,因为基础镜像和获取的镜像是不同的(Kubernetes),我认为类似于compose up。如果您在本地使用docker,则只有当本地缓存中没有指定标签时,才会进行该获取。如果实际上有一个新的最新镜像,docker将只使用您在第一次构建时标记为latest的那个(由缓存保留)。您需要执行pull以获取最新版本,并更新适当的标签,然后再次compose up。 - Anthony Moon Beam Toorie

30

我想补充一点,如果你想自动完成这个过程(下载、停止并重新启动一个新的容器,其设置与 @Yaroslav 所描述的相同),你可以使用 WatchTower。它是一个可以在容器发生改变时自动更新容器的程序。https://github.com/v2tec/watchtower


19

考虑以下答案:

  • 数据库名称为app_schema
  • 容器名称为app_db
  • 根密码为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),在整个数据库被导入之前,我的数据会不可访问吗?这可能会导致巨大的“停机时间”。 - hellimac
既然您提到了 docker-compose,这个方案是否可行?https://dev59.com/wlwZ5IYBdhLWcg3wbv3q#31485685 - sivabudh
1
volumes_from键现已被弃用(甚至在Compose文件的版本3中已被删除),取而代之的是新的volumes键。请参考volumes键。 - Franklin Yu
docker pull image_uri:tag && docker restart container_running_that_image 对我来说有效。不需要 docker-compose pull && docker-compose up -d - Yuri Pozniak

16

与上面类似的回答

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

1
太棒了!很惊讶它没有得到更多的投票。现在唯一缺少的就是触发更新的所有容器的重启。 - sorin
8
不幸的是,这不会更新现有的容器。这只会更新拉取的镜像,但现有的容器是不可变的,并且仍在使用创建它时使用的原始镜像。这只在从镜像创建新容器时有效,但任何现有的容器仍基于原始镜像。 - Eric B.
太棒了。如果你需要拉取特定版本的容器,可以这样做: docker images | awk '{print $1":"$2}' | grep -v 'none' | grep -iv 'repo' | xargs -n1 docker pull - rogervila

12
以下是使用docker-compose构建自定义Dockerfile的示例:
  1. 首先构建您的自定义Dockerfile,并附加下一个版本号以进行区分。例如:docker build -t imagename:version .,这将在本地保存您的新版本。
  2. 运行docker-compose down命令。
  3. 编辑docker-compose.yml文件,以反映您在第1步设置的新映像名称。
  4. 运行docker-compose up -d 命令。它会在本地查找镜像并使用您升级的镜像。
- 编辑 -
我的步骤比必要的还要详细。我通过在docker-compose文件中包含build: .参数来优化了我的工作流程。现在步骤如下:
  1. 验证我的Dockerfile是否符合预期。
  2. 设置我的docker-compose文件中的映像名称版本号。
  3. 如果尚未构建映像:运行docker-compose build命令。
  4. 运行docker-compose up -d命令。
我当时没有意识到,但docker-compose足够智能,可以使用一个命令简单地将容器更新为新的映像,而无需首先关闭它。

在实际情况下,您不能使用自己的手来进行这些更改。您的解决方案不支持自动解决问题的方式。 - Carlos Vázquez Losada
8
所以你的意思是因为我的解决方案不是自动化的,所以它就无效了?这是提问者的要求吗?其他答案暗示需要自动化吗?我真的很困惑。而且,我认为负评对其他来这里寻求帮助的人是不公平的。我的回答完全符合所问的问题。 - gdbj
1
谢谢你的回答,我也没有意识到可以直接运行 docker-compose up -d 而不需要先停止一切。 - radicand

11

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


2

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


重新启动容器是什么意思?有docker restart命令,但我不确定它是否会捕捉到镜像的更改。而且我的容器内的数据会发生什么? - Yaroslav Stavnichiy
1
抱歉,我不是指docker重启。我的意思是docker rm -f CONTAINER; docker run NEW_IMAGE。你的SQL容器中的数据将会消失。这就是为什么人们通常使用卷来存储数据的原因。 - seanmcl
如果您将所有数据都挂载到不同的容器或主机上的卷中,@seanmcl建议使用相同数据创建新的mysql连接的新容器。如果您没有这样做(您应该这样做),但可以使用docker 1.3中可用的docker exec命令来更新mysql并在容器内重新启动它。 - Usman Ismail

2

确保您在容器中存储与进程状态相关的所有持久数据(配置、日志或应用数据)都使用卷。更新Dockerfile并使用所需的更改重新构建映像,然后将卷挂载到适当的位置并重新启动容器。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接