1. 简介

在本教程中,我们将探讨在 Docker 基础镜像更新时,如何自动更新 Docker 容器的策略。我们会介绍自定义 Bash 脚本和第三方工具如 Watchtower 提供的多种解决方案。


2. 理解 Docker 镜像更新

在深入解决方案之前,我们先理解为何自动更新 Docker 容器至关重要。

Docker 容器基于基础镜像构建,这些镜像本质上是文件系统的快照,包括操作系统和额外安装的软件包。这些基础镜像会定期更新以包含安全补丁、错误修复和新功能。

⚠️ 但是一旦容器构建完成,它不会自动继承基础镜像的更新。 这意味着,如果未手动更新,容器会随着时间推移变得过时,存在安全风险。

因此,自动化更新过程可以确保容器保持安全和高效,无需持续人工干预。这对于运行在生产环境中的关键服务尤其重要,能显著降低容器化应用的安全漏洞风险。


3. 使用 Watchtower 实现容器自动更新

Watchtower 是一个开源工具,可自动更新 Docker 容器。它通过轮询 Docker Registry,检查容器所依赖的基础镜像是否有更新。

如果 Watchtower 检测到镜像更新,它会优雅地关闭旧容器、拉取新镜像,并以相同配置启动新容器。

此外,Watchtower 可以监控所有容器,或仅监控指定容器,提供灵活的更新管理方式。它还支持通知机制,可通过邮件、Slack、HTTP 接口等通知更新状态。

3.1. 运行 Watchtower 容器

要使用 Watchtower,首先需要运行其容器:

$ docker run -d \
  --name watchtower \
  -v /var/run/docker.sock:/var/run/docker.sock \
  containrrr/watchtower

e9d83c116cde23a5983d0e34f0dec7b658f000123f8a2edef1b2c2b3a9256c6e

⚠️ 必须挂载 Docker 套接字以使 Watchtower 能与 Docker 守护进程通信。

输出为 Watchtower 容器的唯一 ID,表示容器已后台运行。

3.2. 配置监控的容器

默认情况下,Watchtower 会监控所有容器。若只监控特定容器,可在运行 Watchtower 时添加 --label-enable 参数,并在目标容器上添加标签:

$ docker run -d \
  --name watchtower \
  -v /var/run/docker.sock:/var/run/docker.sock \
  --label-enable \
  containrrr/watchtower

然后运行需监控的容器并添加标签:

$ docker run -d \
  --name my-nginx \
  --label com.centurylinklabs.watchtower.enable=true \
  nginx

0f415dcfaf3b25a1a7ec2d3d3125c0f0d3e9730c4e9a5f0b7555b6e7e1d8a5f7

该容器已被 Watchtower 标记并开始监控。

3.3. 自定义更新轮询间隔

可通过 --interval 参数设置 Watchtower 检查更新的频率(单位:秒):

--interval 86400

该配置表示每天检查一次更新,平衡及时性与资源消耗。

3.4. 启用通知功能

Watchtower 支持多种通知方式,如邮件、Slack 和 HTTP 接口。

邮件通知示例:

$ docker run -d \
  --name watchtower \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -e WATCHTOWER_NOTIFICATIONS=email \
  -e [email protected] \
  -e [email protected] \
  -e WATCHTOWER_NOTIFICATION_EMAIL_SERVER=smtp.example.com \
  -e WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PORT=587 \
  -e [email protected] \
  -e WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD=your-email-password \
  containrrr/watchtower

收到通知后邮件内容类似:

主题:Watchtower Updates

Watchtower 成功更新了以下容器:
- 'nginx' 从 'nginx:1.17' 升级到 'nginx:1.18'

Slack 通知:

$ docker run -d \
  --name watchtower \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -e WATCHTOWER_NOTIFICATIONS=slack \
  -e WATCHTOWER_NOTIFICATION_SLACK_HOOK_URL=<SLACK_WEBHOOK_URL> \
  containrrr/watchtower

自定义 HTTP 接口通知:

$ docker run -d \
  --name watchtower \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -e WATCHTOWER_NOTIFICATIONS=shoutrrr \
  -e WATCHTOWER_NOTIFICATION_URL=shoutrrr://http://your-webhook-endpoint \
  containrrr/watchtower

通知内容类似:

{
  "text": "Watchtower 成功更新了以下容器:nginx 从 nginx:1.17 升级到 nginx:1.18"
}

4. 自定义脚本应对高级场景

虽然 Watchtower 提供了便捷的解决方案,但在某些复杂场景下仍需自定义脚本控制更新逻辑。

4.1. 使用 Bash 脚本自动更新容器

以下脚本可自动检查并更新容器:

#!/usr/bin/env bash
set -e

update_container() {
    local image=$1
    docker pull $image
    local updated_containers=0

    for container in $(docker ps --format "{{.Names}}"); do
        local container_image=$(docker inspect --format '{{.Config.Image}}' "$container")
        if [[ "$container_image" == "$image" ]]; then
            local latest=$(docker inspect --format "{{.Id}}" $image)
            local running=$(docker inspect --format "{{.Image}}" $container)
            if [[ "$running" != "$latest" ]]; then
                echo "升级 $container"
                docker rm -f $container
                docker run --name $container $image
                ((updated_containers++))
            fi
        fi
    done

    if [[ $updated_containers -eq 0 ]]; then
        echo "未更新 $image"
    else
        echo "$updated_containers 个容器已更新 $image"
    fi
}

for image in $(docker ps --format '{{.Image}}' | sort | uniq); do
    echo "检查 $image 更新"
    update_container $image
done

echo "容器更新检查完成。"

运行结果示例:

Checking updates for nginx:latest
No containers updated for nginx:latest
Checking updates for ubuntu:latest
Upgrading ubuntu_container_1
1 container(s) updated for ubuntu:latest
Checking updates for redis:alpine
No containers updated for redis:alpine
Container update check complete.

4.2. 保留配置更新容器

以下脚本保留容器的环境变量、卷和网络配置:

#!/usr/bin/env bash
set -e

preserve_and_update_container() {
    local container=$1
    local image=$(docker inspect --format '{{.Config.Image}}' "$container")
    docker pull $image

    local latest_image_id=$(docker inspect --format '{{.Id}}' $image)
    local container_image_id=$(docker inspect --format '{{.Image}}' "$container")

    if [[ "$latest_image_id" != "$container_image_id" ]]; then
        echo "更新 $container..."
        
        local env_vars=$(docker inspect $container --format '{{range .Config.Env}}{{println .}}{{end}}')
        local volumes=$(docker inspect $container --format '{{range .Mounts}}{{println .Source ":" .Destination}}{{end}}')
        local network=$(docker network ls --filter id=$(docker inspect $container --format '{{.HostConfig.NetworkMode}}') --format '{{.Name}}')
        
        docker rm -f $container
        
        docker run -d --name $container $(echo "$env_vars" | xargs -I {} echo --env '{}') $(echo "$volumes" | xargs -I {} echo -v '{}') --network="$network" $image
        echo "$container 更新成功。"
    else
        echo "$container 已为最新版本。"
    fi
}

for container in $(docker ps --format "{{.Names}}"); do
    preserve_and_update_container $container
done

echo "容器更新检查完成,保留原有配置。"

⚠️ 注意:该脚本对网络和卷的处理较为简单,复杂场景需进一步调整。

4.3. 使用 Cron 自动运行脚本

可通过 crontab 自动运行脚本:

$ crontab -e

添加每日凌晨 3 点执行脚本的任务:

0 3 * * * /path/to/update_all_docker_containers.sh

保存后,脚本将自动定时执行。

4.4. 将镜像更新集成到 CI/CD 流水线

将脚本集成到 CI/CD 流程中,可实现镜像更新后自动构建和部署。

示例配置文件 project_images.conf

my-node-app=node:14
my-python-app=python:3.9

对应脚本:

#!/bin/bash

declare -A projects

while IFS='=' read -r key value; do
  projects["$key"]="$value"
done < "project_images.conf"

CI_TRIGGER_URL="https://ci.example.com/build"

for project in "${!projects[@]}"; do
  BASE_IMAGE="${projects[$project]}"
  docker pull $BASE_IMAGE

  LOCAL_IMAGE_ID=$(docker images -q $BASE_IMAGE)
  REMOTE_IMAGE_ID=$(docker inspect --format='{{.Id}}' $BASE_IMAGE)

  if [ "$LOCAL_IMAGE_ID" != "$REMOTE_IMAGE_ID" ]; then
    echo "基础镜像 $BASE_IMAGE 已更新。触发 $project 重建。"
    curl -X POST "$CI_TRIGGER_URL" -d "project=$project&trigger=rebuild"
  else
    echo "$project 已为最新版本。"
  fi
done

可与以下 CI 工具集成:

  • Jenkins
  • GitLab CI/CD
  • GitHub Actions
  • CircleCI

集成后,可实现自动化构建、测试和部署流程。


5. 小结

在本文中,我们探讨了 Docker 容器更新的重要性,特别是在基础镜像更新时。我们介绍了多种自动化更新策略,包括使用 Watchtower、自定义 Bash 脚本、Cron 定时任务和 CI/CD 流水线集成。

通过这些方法,可以显著提升容器化应用的安全性和稳定性,同时减少人工干预,提高运维效率。


原始标题:Auto-Update Docker Containers for Latest Base Images