第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客户端 docker CLI Docker守护进程 dockerd 镜像仓库 Registry 镜像缓存 /var/lib/docker containerd 容器生命周期管理 runc OCI运行时 / namespace 交互流程说明 1. 客户端通过REST API调用守护进程 2. 守护进程从仓库拉取镜像,存入本地缓存 3. 守护进程通过containerd启动容器,runc负责创建隔离环境

这张图展示的是Docker的完整调用链。你敲一个docker run命令,经过客户端、守护进程、containerd、runc,最后才启动容器进程。我以前以为容器的隔离是靠守护进程做的,后来才发现其实是runc通过Linux namespace和cgroup来实现的。

几个核心组件的作用:

  • dockerd:常驻后台,处理所有REST API请求
  • containerd:管理容器生命周期,负责镜像传输、存储、网络
  • runc:真正的容器运行时,创建并运行容器的低级组件
  • 镜像仓库:存储和分发镜像

踩坑案例:有一次我部署Kraken2分析集群,遇到容器启动特别慢。排查后发现是镜像拉取占用了大量带宽。后来我搭建了私有镜像缓存,并把Kraken2数据库单独用卷挂载,不再每次启动都拉取。这事让我深刻理解了镜像缓存和持久化存储的重要性。

3.5 三个核心概念的关系总结

最后,我把镜像、容器、仓库的关系整理成一张清单,帮你记忆。

  1. 镜像:只读的静态模板,相当于代码工程打包好的二进制文件
  2. 容器:从镜像启动的运行实例,相当于程序启动后的进程
  3. 仓库:存放镜像的地方,相当于代码托管平台
  4. Docker架构:客户端向守护进程发命令,守护进程管理containerd和runc,创建容器的隔离环境

你想想看,镜像和容器的关系,有点像程序文件和进程的关系。程序文件(镜像)是静态的,进程(容器)是动态的。仓库则像操作系统里的软件源。理解这些之后再上手Docker,你就不会迷糊了。

核心口诀:镜像管静态,容器管运行,仓库管分发,守护进程管调度。