【问题标题】:How to upgrade docker container after its image changed如何在镜像更改后升级 docker 容器
【发布时间】:2014-12-31 07:10:58
【问题描述】:

假设我拉了官方mysql:5.6.21 image

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

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

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

【问题讨论】:

标签: docker


【解决方案1】:

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

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 不起作用)。

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

【讨论】:

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

我不喜欢将挂载卷作为指向主机目录的链接,因此我想出了一个模式,用于使用完全由 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 节点上都安装了一些网络卷,但这听起来很难维护。
  • 谢谢!好的,现在安装主机卷似乎也是我想要避免的事情。如果我的应用程序变得流行并且需要扩展到多台服务器,至少要晚一点
【解决方案3】:

只是为了提供更一般的(不是特定于 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 标志。

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

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

【讨论】:

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

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

【讨论】:

    【解决方案5】:

    考虑这个答案:

    • 数据库名称为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 &amp;&amp; docker restart container_running_that_image 为我工作。不需要docker-compose pull &amp;&amp; docker-compose up -d
    【解决方案6】:

    与上述类似的答案

    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 docker pull
    【解决方案7】:

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

    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 足够聪明,只需使用一个命令将我的容器更新为新图像,而不必先将其关闭。

    【讨论】:

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

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

    【讨论】:

      【解决方案9】:

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

      【讨论】:

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

      取自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
      

      【讨论】:

      • 这将更新图像,但不会更新容器。容器是不可变的,如果不从更新的镜像创建新容器,则无法更改其基础镜像。
      【解决方案11】:

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

      【讨论】:

        【解决方案12】:

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

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

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

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

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

        【讨论】:

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

        更新

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

        我遇到了同样的问题,所以我创建了 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 做事方式。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-02-26
        • 2017-04-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-04-21
        相关资源
        最近更新 更多