第3章 Docker基础概念:镜像、容器、仓库、Docker架构
我记得刚开始接触Docker时,老搞不清镜像和容器的区别。其实很多新人都有这个困惑。今天咱们就把这几个核心概念彻底讲明白。
说白了,Docker就是个轻量级的虚拟机,但又比虚拟机灵活得多。你想想看,传统虚拟机得装个完整的操作系统,而Docker直接共享宿主机的内核。这也是为什么Docker容器启动快、资源占用少的原因。
3.1 镜像:可执行的只读模板
镜像,你可以把它理解成一个压缩包,里面装了你应用运行所需的一切:代码、运行时、系统工具、库、配置文件。但这个压缩包有点特殊,它是分层存储的。
每一层都是从Dockerfile的每一条指令生成的。比如:
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y python3
COPY app.py /app/
CMD ["python3", "/app/app.py"]
Dockerfile里的每一行,都会创建一个新的只读层。这些层叠在一起,构成完整的镜像。我用过很多生物信息工具,Kraken2构建镜像时尤其注意层数优化,太多层会导致镜像体积膨胀。
镜像特点:只读、分层、可共享、版本可追溯
个人经验:我在做Kraken2容器化时,发现Kraken2数据库打包进镜像后体积有90GB。当时只用了ADD和COPY命令,导致最终镜像体积翻倍。后来我改用构建上下文挂载和缓存挂载技巧,成功压缩到50GB以内。所以记住:操作不当会让镜像比实际内容大很多。
3.2 容器:镜像的运行实例
镜像是静态的,容器是动态的。每个容器都是从一个镜像启动的进程实例。你可以启动多个容器,每个之间完全隔离。
容器在镜像层之上,加了一层可写层。所有对文件系统的改动,都写在这一层。容器停止后,这层还可以保留,也可以丢弃。我经常用这个特性做临时分析,用完即弃。
| 对比维度 | 镜像 | 容器 |
|---|---|---|
| 状态 | 静态只读 | 动态运行 |
| 生命周期 | 长期存储 | 可停止/启动/删除 |
| 数据持久性 | 固定不变 | 可写层临时/可持久化 |
| 用途 | 分发和部署 | 运行应用程序 |
举个例子,你拉取一个Kraken2镜像后,可以启动多个容器同时分析不同样本。每个容器使用自己的进程空间和文件系统,互不干扰。
避坑指南:我曾经误以为容器关闭后所有数据都会丢失。其实默认情况下的确是,但如果你使用卷挂载(volume),数据就能持久化。做生产部署时一定要配置数据持久化,否则容器挂了,分析结果也跟着没了。
3.3 仓库:存储和分发镜像的地方
镜像仓库,就是存放镜像的地方。最常用的是Docker Hub,类似GitHub。但Docker Hub有拉取限制,所以我建议在私有机房或者云环境部署私有仓库。
常用的仓库类型:
- 公有仓库:Docker Hub、GitHub Container Registry、Quay.io
- 私有仓库:Harbor(企业级)、Docker Registry(轻量级)、云厂商的镜像服务
在项目中,我习惯把构建好的Kraken2镜像打上标签,然后推送私有Harbor仓库。这样团队成员都能快速拉取,而且版本可控。
docker tag my-kraken2-image:latest harbor.bioinfo.local/library/kraken2:v3.0
docker push harbor.bioinfo.local/library/kraken2:v3.0
标签命名有讲究,我通常用软件名:版本号-数据库版本。比如kraken2:2.1.3-db2023,一看就知道用的是什么版本。
小技巧:别在图方便只用latest标签。一旦版本升级,你就分不清哪个镜像对应哪个版本了。我在项目中养成了每次都写明确版本号的习惯。
3.4 Docker架构:客户端-守护进程-运行时
Docker采用的是C/S架构。当你敲docker run时,实际上是在向守护进程(dockerd)发送请求。守护进程再去调度容器。
我用一张架构图帮你理清它们之间的关系:
这张图展示的是Docker的完整调用链。你敲一个docker run命令,经过客户端、守护进程、containerd、runc,最后才启动容器进程。我以前以为容器的隔离是靠守护进程做的,后来才发现其实是runc通过Linux namespace和cgroup来实现的。
几个核心组件的作用:
- dockerd:常驻后台,处理所有REST API请求
- containerd:管理容器生命周期,负责镜像传输、存储、网络
- runc:真正的容器运行时,创建并运行容器的低级组件
- 镜像仓库:存储和分发镜像
踩坑案例:有一次我部署Kraken2分析集群,遇到容器启动特别慢。排查后发现是镜像拉取占用了大量带宽。后来我搭建了私有镜像缓存,并把Kraken2数据库单独用卷挂载,不再每次启动都拉取。这事让我深刻理解了镜像缓存和持久化存储的重要性。
3.5 三个核心概念的关系总结
最后,我把镜像、容器、仓库的关系整理成一张清单,帮你记忆。
- 镜像:只读的静态模板,相当于代码工程打包好的二进制文件
- 容器:从镜像启动的运行实例,相当于程序启动后的进程
- 仓库:存放镜像的地方,相当于代码托管平台
- Docker架构:客户端向守护进程发命令,守护进程管理containerd和runc,创建容器的隔离环境
你想想看,镜像和容器的关系,有点像程序文件和进程的关系。程序文件(镜像)是静态的,进程(容器)是动态的。仓库则像操作系统里的软件源。理解这些之后再上手Docker,你就不会迷糊了。
核心口诀:镜像管静态,容器管运行,仓库管分发,守护进程管调度。