Docker 高级特性:从 Scratch 镜像到 Harbor 高可用部署

大家好,我是 leo-zhang。在今天的分享中,我们将深入探讨 Docker 的一些高级概念和实践,这些技能对于在实际生产环境中高效、安全地使用容器至关重要。我们将涵盖以下内容:

  1. 分析 Docker 镜像历史:理解镜像是如何构建的。
  2. 使用 scratch 构建极简镜像:创建不依赖任何基础镜像的自定义镜像。
  3. Docker Registry 与 Harbor 对比:了解企业级镜像仓库的需求。
  4. 镜像迁移实战:将镜像从标准 Docker Registry 迁移到 Harbor。
  5. Harbor 高可用 (HA) 部署:探讨生产环境下的 Harbor 部署策略。
  6. Containerd 简介:了解 Docker 背后的核心容器运行时。
  7. Harbor HTTPS 配置:为镜像仓库启用安全传输。

让我们开始吧!

1. 分析 Docker 镜像历史 (docker history)

在优化或理解现有镜像时,docker history 命令是一个非常有用的工具。它能展示构成镜像的每一层以及创建该层的 Dockerfile 指令。

示例分析:

让我们看一个实际的 docker history 输出:

root@docker102:~# docker history registry.cn-hangzhou.aliyuncs.com/yinzhengjie-k8s/apps:v1
IMAGE          CREATED        CREATED BY                                      SIZE      COMMENT
f28fd43be4ad   6 months ago   COPY 1.jpg index.html /usr/share/nginx/html/…   234kB     buildkit.dockerfile.v0
<missing>      6 months ago   MAINTAINER 尹正杰 y1053419035@qq.com               0B        buildkit.dockerfile.v0
<missing>      2 years ago    /bin/sh -c #(nop)  CMD ["nginx" "-g" "daemon…   0B
<missing>      2 years ago    /bin/sh -c #(nop)  STOPSIGNAL SIGQUIT           0B
<missing>      2 years ago    /bin/sh -c #(nop)  EXPOSE 80                    0B
<missing>      2 years ago    /bin/sh -c #(nop)  ENTRYPOINT ["/docker-entr…   0B
<missing>      2 years ago    /bin/sh -c #(nop) COPY file:09a214a3e07c919a…   4.61kB
<missing>      2 years ago    /bin/sh -c #(nop) COPY file:0fd5fca330dcd6a7…   1.04kB
<missing>      2 years ago    /bin/sh -c #(nop) COPY file:0b866ff3fc1ef5b0…   1.96kB
<missing>      2 years ago    /bin/sh -c #(nop) COPY file:65504f71f5855ca0…   1.2kB
<missing>      2 years ago    /bin/sh -c set -x     && addgroup -g 101 -S …   17.1MB
<missing>      2 years ago    /bin/sh -c #(nop)  ENV PKG_RELEASE=1            0B
<missing>      2 years ago    /bin/sh -c #(nop)  ENV NJS_VERSION=0.5.3        0B
<missing>      2 years ago    /bin/sh -c #(nop)  ENV NGINX_VERSION=1.20.1     0B
<missing>      2 years ago    /bin/sh -c #(nop)  LABEL maintainer=NGINX Do…   0B
<missing>      2 years ago    /bin/sh -c #(nop)  CMD ["/bin/sh"]              0B        # Base image's CMD
<missing>      2 years ago    /bin/sh -c #(nop) ADD file:5a707b9d6cb5fff53…   5.62MB    # Base image layer
root@docker102:~#

解读:

  • 层级结构:镜像是分层的,每一行(除了 <missing>)代表一个层。最底下的层是最先构建的(通常是基础镜像)。
  • CREATED BY:显示用于创建该层的 Dockerfile 指令。#(nop) 通常表示元数据指令(如 LABEL, ENV, EXPOSE, CMD 等),它们不创建新的文件系统层,大小为 0B。
  • SIZE:表示该层添加的文件系统内容的大小。注意,这并不完全等于最终镜像大小的增量,因为上层文件可以覆盖或删除下层文件。
  • <missing>:这通常意味着该层是在另一台机器上构建的,或者使用了 BuildKit 等现代构建器,本地没有存储这些中间层的完整信息。但镜像本身是完整的。
  • COMMENT: BuildKit 构建器可能会添加注释,如 buildkit.dockerfile.v0

通过分析历史,我们可以了解镜像的构建过程、基础镜像的选择、以及哪些步骤贡献了主要的体积,为镜像优化提供依据。

2. 使用 scratch 构建极简镜像

有时,我们希望构建一个尽可能小的镜像,只包含运行应用程序所需的二进制文件和依赖项,没有任何操作系统工具或库。这对于静态编译的 Go 应用或需要极致安全、最小攻击面的场景特别有用。Docker 提供了一个特殊的、空的基础镜像名:scratch

scratch 镜像特性:

  • 它是一个空镜像,没有任何文件系统内容、库或 shell。
  • FROM scratch 指令告诉 Docker 构建器,接下来的指令将是镜像的第一层。
  • 不能 docker run scratch,因为它什么都没有。
  • 镜像名称不能是 scratch

构建步骤:

1. 获取根文件系统 (Root Filesystem)

既然 scratch 是空的,我们需要自己提供一个最小化的文件系统。通常可以使用 BusyBox 这类工具集,它将许多标准 Unix 工具集成到一个小型可执行文件中。

# 下载 BusyBox 的 rootfs 压缩包 (示例,请根据需要选择版本)
wget https://mirrors.tuna.tsinghua.edu.cn/lxc-images/images/busybox/1.36.1/amd64/default/20240726_06%3A00/rootfs.tar.xz

# 为了方便后续操作,可以解压并重命名 (根据你的笔记,你似乎重命名了)
xz -d rootfs.tar.xz
mv rootfs.tar oldboyedu-linux-rootfs.tar.gz # 假设你转换并重命名了
# 注意:原始下载的是 .tar.xz,Dockerfile 中使用的是 .tar.gz,请确保格式一致或修改 Dockerfile

2. (可选) 修改根文件系统

在添加到镜像之前,你可以解压 rootfs.tar.gz,进行必要的修改(例如,添加配置文件、删除不需要的工具),然后再重新打包。在我们的示例中,这一步被省略了。

3. 编写 Dockerfile

创建一个 Dockerfile,使用 scratch 作为基础,并添加我们准备好的根文件系统。

# /oldboyedu/dockerfile/alpine/13-scratch/Dockerfile

# "scratch"是保留名称,表示不引用任何父镜像,从零开始构建
FROM scratch

# 设置镜像维护者信息
MAINTAINER JasonYin <y1053419035@qq.com>

# 添加元数据标签
LABEL auther="JasonYin" \
      school="oldboyedu" \
      class="linux92"

# ONBUILD 指令:当其他镜像以此镜像为基础时触发
ONBUILD LABEL email="y1053419035@qq.com" office="www.oldboyedu.com"

# 将准备好的根文件系统压缩包添加到镜像的根目录
# ADD 指令会自动解压常见的压缩格式 (.tar.gz, .tar.xz 等)
ADD oldboyedu-linux-rootfs.tar.gz /

# 设置容器启动时默认执行的命令
CMD ["tail", "-f", "/etc/hosts"]

4. 使用 docker-compose 进行构建和推送 (可选但方便)

docker-compose 不仅用于编排多容器应用,也可以方便地管理单个镜像的构建和推送流程。

创建 docker-compose.yaml 文件:

# /oldboyedu/dockerfile/alpine/13-scratch/docker-compose.yaml
version: '3.8'

services:
   # 服务名,可以任意取,这里叫 mysql-server 似乎不太合适,改为 mylinux-builder 更好
   mylinux-builder:
     build:
       context: . # 构建上下文目录,即 Dockerfile 所在目录
       dockerfile: Dockerfile # 指定 Dockerfile 文件名
     image: harbor.oldboyedu.com/oldboyedu-linux/linux92:v0.1 # 构建后镜像的名称和标签
     container_name: mylinux # 这个 container_name 在 build 场景下通常不会生效
  • 注意: container_name 主要用于 docker-compose up 启动容器时指定容器名,在 docker-compose build 中作用不大。服务名 mysql-server 建议修改为与镜像内容更相关的名称,例如 mylinux-buildercustom-linux

执行构建:

root@docker101:/oldboyedu/dockerfile/alpine/13-scratch# docker-compose build
[+] Building 2.5s (8/8) FINISHED
 => [internal] load build definition from Dockerfile                                                                      0.0s
 => => transferring dockerfile: 377B                                                                                      0.0s
 => [internal] load .dockerignore                                                                                         0.0s
 => => transferring context: 2B                                                                                           0.0s
 => [internal] load metadata for docker.io/library/scratch:latest                                                         0.0s
 => [internal] load build context                                                                                         0.1s
 => => transferring context: 2.32MB                                                                                       0.1s
 => [1/3] FROM docker.io/library/scratch                                                                                  0.0s
 => [2/3] ADD oldboyedu-linux-rootfs.tar.gz /                                                                             1.9s
 => [3/3] ONBUILD LABEL email=y1053419035@qq.com office=www.oldboyedu.com                                                 0.0s
 => exporting to image                                                                                                    0.4s
 => => exporting layers                                                                                                   0.4s
 => => writing image sha256:80ccf58557fef008e45933f38a0b5bd8f23b42125a9b10b2612d10a43676777c                              0.0s
 => => naming to harbor.oldboyedu.com/oldboyedu-linux/linux92:v0.1                                                        0.0s

5. 推送镜像到私有仓库 (Harbor)

首先,登录到你的 Harbor 仓库:

root@docker101:/oldboyedu/dockerfile/alpine/13-scratch# docker login -u admin -p YourHarborPassword harbor.oldboyedu.com
WARNING! Using --password via the CLI is insecure. Use --password-stdin.
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Login Succeeded
  • 安全提示: 生产环境中强烈建议使用 docker login --password-stdin 或配置凭证助手 (credential helper) 来避免密码明文存储和命令行警告。

然后,使用 docker-compose 推送镜像:

root@docker101:/oldboyedu/dockerfile/alpine/13-scratch# docker-compose push
[+] Pushing 1/1
 ✔ Pushing mylinux-builder: harbor.oldboyedu.com/oldboyedu-linux/linux92:v0.1 Pushed  0.2s

6. 拉取并测试镜像

在另一台配置了 Docker 的机器上(或本机清理后),拉取并运行镜像进行验证:

# 拉取镜像
root@docker102:~# docker pull harbor.oldboyedu.com/oldboyedu-linux/linux92:v0.1
v0.1: Pulling from oldboyedu-linux/linux92
be97840ca723: Pull complete
Digest: sha256:6bdfad8687c45c613fadfffe0f0fba44272e1d7a436f47afea9dd381fc6d0425
Status: Downloaded newer image for harbor.oldboyedu.com/oldboyedu-linux/linux92:v0.1
harbor.oldboyedu.com/oldboyedu-linux/linux92:v0.1

# 查看本地镜像信息
root@docker102:~# docker image ls harbor.oldboyedu.com/oldboyedu-linux/linux92:v0.1
REPOSITORY                                     TAG       IMAGE ID       CREATED         SIZE
harbor.oldboyedu.com/oldboyedu-linux/linux92   v0.1      80ccf58557fe   About an hour ago   2.31MB

# 启动容器并进入交互式 shell (覆盖默认 CMD)
root@docker102:~# docker run --rm -it harbor.oldboyedu.com/oldboyedu-linux/linux92:v0.1 sh
~ #
~ # cat /etc/os-release  # 验证自定义内容 (如果 rootfs 包含的话)
NAME="Linux92"
VERSION="24.07 LTS (Jason Yin)"
ID=oldboyedu_linux
PRETTY_NAME="Ubuntu 24.07.1 LTS"
VERSION_ID="24.07"
HOME_URL="https://www.oldboyedu.com/"
~ #
~ # ifconfig  # 验证网络工具是否可用 (BusyBox 提供了)
eth0      Link encap:Ethernet  HWaddr 02:42:AC:11:00:02
          inet addr:172.17.0.2  Bcast:172.17.255.255  Mask:255.255.0.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:12 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:1016 (1016.0 B)  TX bytes:0 (0.0 B)

lo        Link encap:Local Loopback
          inet addr:127.0.0.1  Mask:255.0.0.0
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

~ # exit # 退出容器,--rm 会自动删除

可以看到,我们成功构建了一个仅 2.31MB 的超小型 Linux 环境镜像,并能在其中执行基本命令。

3. Docker Registry 与 Harbor 对比

  • Docker Registry: Docker 官方提供的开源镜像仓库实现。功能相对基础,主要提供镜像的存储和分发 (push/pull)。权限控制简单,没有图形化界面,缺少镜像安全扫描、复制策略等高级功能。适合小型团队或简单场景。
  • VMware Harbor: 一个企业级的开源云原生镜像仓库。基于 Docker Registry 构建,并增加了大量企业级特性:
    • 图形用户界面 (UI):方便管理项目、用户、镜像、策略等。
    • 基于角色的访问控制 (RBAC):精细控制用户对项目的权限(推送、拉取、管理等)。
    • 镜像安全漏洞扫描:集成 Clair、Trivy 等扫描器,自动扫描镜像中的已知漏洞。
    • 镜像内容信任 (Notary):支持 Docker Content Trust,确保镜像来源可靠且未被篡改。
    • 镜像复制 (Replication):支持在多个 Harbor 实例或公有云仓库之间同步镜像。
    • 垃圾回收 (GC):清理不再被引用的镜像层,回收存储空间。
    • 审计日志:记录所有操作,便于追踪和审计。
    • Helm Chart 仓库:除了 Docker 镜像,还可以托管 Helm Chart。

对于需要严格安全策略、权限管理、多团队协作和高可用性的生产环境,Harbor 是比基础 Docker Registry 更优的选择。

4. Docker Registry 迁移镜像到 Harbor 仓库

将现有镜像从一个 Docker Registry(可能是官方的,也可能是自建的基础版)迁移到 Harbor 的过程通常很简单,主要涉及三个步骤:

  1. 拉取 (Pull):从旧仓库拉取需要迁移的镜像。
  2. 重新打标签 (Tag):为镜像打上指向新 Harbor 仓库地址和项目的新标签。
  3. 推送 (Push):将带有新标签的镜像推送到 Harbor 仓库。

示例(假设要迁移 my-app:latestold-registry.comharbor.oldboyedu.com/my-project):

# 1. 登录旧仓库 (如果需要认证)
docker login old-registry.com

# 2. 拉取镜像
docker pull old-registry.com/my-app:latest

# 3. 登录新仓库 (Harbor)
docker login harbor.oldboyedu.com # 使用你的 Harbor 用户名密码

# 4. 给镜像打上新标签,指向 Harbor 的项目
# 格式: docker tag SOURCE_IMAGE[:TAG] TARGET_IMAGE[:TAG]
docker tag old-registry.com/my-app:latest harbor.oldboyedu.com/my-project/my-app:latest

# (可选) 如果需要迁移其他 tag,重复步骤 2 和 4
docker pull old-registry.com/my-app:v1.0
docker tag old-registry.com/my-app:v1.0 harbor.oldboyedu.com/my-project/my-app:v1.0

# 5. 推送新标签的镜像到 Harbor
docker push harbor.oldboyedu.com/my-project/my-app:latest
docker push harbor.oldboyedu.com/my-project/my-app:v1.0

# 6. (可选) 清理旧标签的本地镜像
docker rmi old-registry.com/my-app:latest
docker rmi old-registry.com/my-app:v1.0

对于大量镜像的迁移,可以编写脚本自动化这个 pull -> tag -> push 的过程。

5. Harbor 高可用 (HA) 项目部署

在生产环境中,单个 Harbor 实例存在单点故障风险。为了保证镜像仓库服务的持续可用和高性能,需要部署 Harbor 高可用 (HA) 集群。典型的 Harbor HA 架构包含以下关键组件:

  • 负载均衡器 (Load Balancer):如 Nginx, HAProxy, F5 或云服务商 LB。将客户端请求(Docker client, Web UI)分发到多个 Harbor 后端实例。需要处理 HTTPS 终结和会话保持(如果需要)。
  • 多个 Harbor 核心服务实例:运行 Harbor Core, Registry, Jobservice 等核心组件的多个无状态或准无状态节点。通常部署为容器。
  • 共享存储 (Shared Storage):存储镜像层数据(Registry 数据)。必须是所有 Harbor Registry 实例都能访问的共享文件系统,如 NFS、CephFS、GlusterFS 或对象存储(S3, MinIO 等)。
  • 高可用数据库 (HA Database):存储 Harbor 的元数据(用户信息、项目、镜像元数据、扫描结果等)。通常使用 PostgreSQL,需要配置主从复制和故障转移机制(如 Patroni, Repmgr)。
  • 高可用缓存/队列 (HA Cache/Queue):Harbor 使用 Redis 进行缓存和内部任务队列。需要部署 Redis Sentinel 或 Redis Cluster 来实现高可用。

部署方式:

  • 可以使用官方提供的 harbor-helm Chart 在 Kubernetes 集群上部署,Helm Chart 本身支持配置副本数和外部依赖(数据库、Redis、存储),简化了 HA 部署。
  • 也可以手动在多台虚拟机或物理机上部署,需要自行配置负载均衡器、共享存储、数据库和 Redis 的高可用。

Harbor HA 部署相对复杂,需要仔细规划网络、存储和依赖服务。

6. Containerd 容器管理工具

虽然我们经常直接与 Docker CLI 交互,但 Docker 引擎本身是分层的。containerd 是一个行业标准的容器运行时 (Container Runtime),它位于 Docker Engine (dockerd) 的下层。

关键点:

  • 核心功能containerd 负责管理容器的整个生命周期:镜像的拉取和存储、容器的执行和监控、底层网络和存储的配置。
  • Docker & Containerd: Docker Engine (自 1.11 版本起) 将容器运行的核心逻辑剥离出来交给了 containerddockerd 依然负责镜像构建、卷管理、网络管理、API 等上层功能,但最终创建和运行容器是通过调用 containerd 实现的。
  • OCI 标准: containerd 遵循开放容器标准 (Open Container Initiative, OCI),确保了容器格式和运行时的兼容性。
  • Kubernetes CRI: Kubernetes 通过容器运行时接口 (Container Runtime Interface, CRI) 与容器运行时交互。containerd 有一个名为 cri-containerd 的插件(现在已内置),可以直接被 Kubernetes 使用,绕过 Docker Engine。这使得 Kubernetes 集群可以使用 containerd 作为运行时,更加轻量和高效。
  • 轻量级: 相比完整的 Docker Engine,containerd 更专注于运行容器的核心功能,资源占用更少。

理解 containerd 有助于我们更深入地了解容器生态系统的底层工作原理,尤其是在 Kubernetes 环境中选择和排查容器运行时问题时。

7. Harbor 部署 HTTPS 案例

在生产环境中,必须为 Harbor 配置 HTTPS,以确保 Docker 客户端与 Harbor 之间传输的镜像数据和用户凭证是加密的,防止中间人攻击和数据泄露。

配置步骤概述:

  1. 获取 SSL/TLS 证书

    • 选项 A: 使用 Let's Encrypt (推荐用于公共访问):可以通过 certbot 等工具自动获取和续订免费证书。
    • 选项 B: 使用内部 CA 或购买商业证书:对于内部使用的 Harbor,可以使用公司内部 CA 签发的证书。对于需要广泛信任的场景,可以购买商业证书。
    • 你需要获取到证书文件 (.crt.pem) 和私钥文件 (.key)。
  2. 配置 Harbor 使用证书

    • 对于使用 docker-compose 安装的 Harbor (官方离线/在线安装包)
      • 修改 harbor.yml 文件。
      • 找到 https: 相关配置段。
      • 设置 port: 443 (或其他 HTTPS 端口)。
      • 提供证书文件和私钥文件的绝对路径
        https:
          # https port for harbor, default is 443
          port: 443
          # The path of cert and key files for nginx
          certificate: /path/to/your/domain.crt
          private_key: /path/to/your/domain.key
        
      • 确保 Harbor 运行的主机上有这些证书文件,并且 Nginx 容器(Harbor 的一部分)能够访问到它们(通常安装脚本会处理挂载)。
      • 重新运行 ./prepare 脚本(如果需要更新配置)并重启 Harbor (docker-compose down && docker-compose up -d)。
    • 对于使用 Helm Chart 在 Kubernetes 上部署的 Harbor
      • 修改 values.yaml 文件或通过 --set 参数传递。
      • 配置 expose.typeingressloadBalancer
      • expose.tls 部分配置 enabled: true,并指定包含证书和私钥的 Kubernetes Secret 名称。或者,如果使用 ingress,可以通过 Ingress Controller 的注解(如 cert-manager.io/cluster-issuer)来自动管理证书。
  3. 配置 Docker 客户端信任证书

    • 如果使用的是自签名证书内部 CA 签发的证书,Docker 客户端默认不信任。你需要将 CA 的根证书(或者 Harbor 服务器证书本身,如果不关心链验证)放到 Docker 守护进程信任的目录中。
      • 在 Linux 上,通常是 /etc/docker/certs.d/<harbor_domain_name>:<port>/ca.crt
      • 例如,对于 harbor.oldboyedu.com,路径是 /etc/docker/certs.d/harbor.oldboyedu.com/ca.crt (如果使用默认 443 端口)。
      • 放置证书后,需要重启 Docker 服务 (systemctl restart docker)。
    • 如果使用的是公共 CA(如 Let's Encrypt 或商业 CA)签发的证书,并且客户端操作系统信任该 CA,则无需额外配置。
  4. 验证

    • 使用 docker login harbor.oldboyedu.com 尝试登录,不应出现证书错误。
    • 尝试 docker pull/push 操作。
    • 在浏览器中访问 https://harbor.oldboyedu.com,应看到安全的锁标志。

总结

今天我们一起探索了 Docker 和 Harbor 的多个高级主题。从使用 scratch 构建极致精简的镜像,到理解镜像分层结构,再到企业级镜像仓库 Harbor 的选型、迁移、高可用部署和安全配置,以及底层容器运行时 containerd 的介绍。掌握这些知识,将使你能够更专业、更安全、更高效地在生产环境中使用容器技术。

希望这篇分享对你有帮助!如果你有任何问题或想法,欢迎在评论区留言交流。


posted on 2025-04-13 17:13  Leo-Yide  阅读(67)  评论(0)    收藏  举报