Docker 高级特性:从 Scratch 镜像到 Harbor 高可用部署
大家好,我是 leo-zhang。在今天的分享中,我们将深入探讨 Docker 的一些高级概念和实践,这些技能对于在实际生产环境中高效、安全地使用容器至关重要。我们将涵盖以下内容:
- 分析 Docker 镜像历史:理解镜像是如何构建的。
- 使用
scratch
构建极简镜像:创建不依赖任何基础镜像的自定义镜像。 - Docker Registry 与 Harbor 对比:了解企业级镜像仓库的需求。
- 镜像迁移实战:将镜像从标准 Docker Registry 迁移到 Harbor。
- Harbor 高可用 (HA) 部署:探讨生产环境下的 Harbor 部署策略。
- Containerd 简介:了解 Docker 背后的核心容器运行时。
- 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-builder
或custom-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 的过程通常很简单,主要涉及三个步骤:
- 拉取 (Pull):从旧仓库拉取需要迁移的镜像。
- 重新打标签 (Tag):为镜像打上指向新 Harbor 仓库地址和项目的新标签。
- 推送 (Push):将带有新标签的镜像推送到 Harbor 仓库。
示例(假设要迁移 my-app:latest
从 old-registry.com
到 harbor.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 版本起) 将容器运行的核心逻辑剥离出来交给了
containerd
。dockerd
依然负责镜像构建、卷管理、网络管理、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 之间传输的镜像数据和用户凭证是加密的,防止中间人攻击和数据泄露。
配置步骤概述:
-
获取 SSL/TLS 证书:
- 选项 A: 使用 Let's Encrypt (推荐用于公共访问):可以通过
certbot
等工具自动获取和续订免费证书。 - 选项 B: 使用内部 CA 或购买商业证书:对于内部使用的 Harbor,可以使用公司内部 CA 签发的证书。对于需要广泛信任的场景,可以购买商业证书。
- 你需要获取到证书文件 (
.crt
或.pem
) 和私钥文件 (.key
)。
- 选项 A: 使用 Let's Encrypt (推荐用于公共访问):可以通过
-
配置 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.type
为ingress
或loadBalancer
。 - 在
expose.tls
部分配置enabled: true
,并指定包含证书和私钥的 Kubernetes Secret 名称。或者,如果使用ingress
,可以通过 Ingress Controller 的注解(如cert-manager.io/cluster-issuer
)来自动管理证书。
- 修改
- 对于使用
-
配置 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
)。
- 在 Linux 上,通常是
- 如果使用的是公共 CA(如 Let's Encrypt 或商业 CA)签发的证书,并且客户端操作系统信任该 CA,则无需额外配置。
- 如果使用的是自签名证书或内部 CA 签发的证书,Docker 客户端默认不信任。你需要将 CA 的根证书(或者 Harbor 服务器证书本身,如果不关心链验证)放到 Docker 守护进程信任的目录中。
-
验证:
- 使用
docker login harbor.oldboyedu.com
尝试登录,不应出现证书错误。 - 尝试
docker pull/push
操作。 - 在浏览器中访问
https://harbor.oldboyedu.com
,应看到安全的锁标志。
- 使用
总结
今天我们一起探索了 Docker 和 Harbor 的多个高级主题。从使用 scratch
构建极致精简的镜像,到理解镜像分层结构,再到企业级镜像仓库 Harbor 的选型、迁移、高可用部署和安全配置,以及底层容器运行时 containerd
的介绍。掌握这些知识,将使你能够更专业、更安全、更高效地在生产环境中使用容器技术。
希望这篇分享对你有帮助!如果你有任何问题或想法,欢迎在评论区留言交流。