1、Docker初识:什么是容器化、Docker与虚拟机的区别、Docker的核心概念

大家好,我是你们的老朋友。今天咱们来聊聊Docker,这个在DevOps圈子里几乎人手必备的工具。说实话,我第一次接触Docker的时候,心里也犯嘀咕:这不就是个轻量级的虚拟机吗?后来踩了不少坑,才真正理解它的价值。嗯,咱们一步步来。

1.1 什么是容器化?

容器化,说白了就是一种操作系统级别的虚拟化技术。它让你能把应用和它所有的依赖打包在一起,形成一个标准化的单元。这个单元可以在任何支持容器引擎的环境中运行,完全不用担心环境差异。

我打个比方你就明白了。以前我们部署应用,就像搬家——你得把家具、电器、锅碗瓢盆全搬过去,还得确保新家的水电煤气都匹配。容器化呢?就像用集装箱运货。你把所有东西装进集装箱,贴上标签,不管是用轮船、火车还是卡车,到了目的地直接卸货就行。

我在项目中遇到过最典型的场景:开发同学在Mac上写代码,一切正常。部署到Linux服务器上,就报各种依赖缺失的错误。为什么?因为开发环境和生产环境不一样。容器化就是解决这个问题的——你想想看,如果连操作系统层面的依赖都打包在一起,还会出这种问题吗?

核心要点:容器化不是虚拟化硬件,而是虚拟化操作系统。多个容器共享同一个操作系统内核,但彼此隔离。

1.2 Docker与虚拟机的区别

很多新手会问:Docker和虚拟机到底有啥区别?我刚开始也搞混过。咱们直接看对比:

对比维度 Docker容器 传统虚拟机
启动速度 秒级(毫秒级启动) 分钟级(需要引导操作系统)
资源占用 极低(共享宿主机内核) 较高(每个VM有完整OS)
隔离级别 进程级隔离(Namespace+Cgroups) 硬件级虚拟化(Hypervisor)
镜像大小 通常MB级别(Alpine Linux仅5MB) GB级别(完整操作系统)
性能损耗 几乎无损耗(直接调用宿主机内核) 有一定损耗(需要虚拟化硬件)

为什么会这样?我简单解释一下。虚拟机是在硬件层面做虚拟化,每个VM都包含一个完整的操作系统。你想想看,跑三个虚拟机,就等于同时跑三个操作系统,光内存就得吃掉好几个G。而Docker容器呢?它们共享宿主机的操作系统内核,只是通过Namespace做了进程、网络、文件系统的隔离。

我的经验:曾经有个项目需要部署20个微服务。如果用虚拟机,至少需要4台物理服务器。换成Docker后,一台高配服务器就搞定了。资源利用率提升了不止一个量级。

但要注意,Docker不是万能的。如果你需要运行不同操作系统的应用(比如在Linux上跑Windows程序),那还是得用虚拟机。Docker只能运行与宿主机同类型的操作系统。

避坑指南:我曾经在CentOS上直接跑了一个Ubuntu的容器,结果发现容器里的apt-get命令根本用不了。后来才明白,Docker容器共享的是宿主机的内核,不是完整的操作系统。容器里的Ubuntu只是文件系统层面的模拟,内核还是CentOS的。

1.3 Docker的核心概念

Docker有三个核心概念:镜像、容器、仓库。这三个东西搞明白了,Docker就算入门了。我习惯把它们类比成面向对象编程中的概念,你听听看:

1.3.1 镜像(Image)

镜像就是一个只读的模板,包含了运行应用所需的一切:代码、运行时、库、环境变量、配置文件。你可以把它理解成Java里的类——定义了对象的结构和行为,但本身不是运行中的实例。

镜像是由一层一层的文件系统叠加而成的。每一层代表一个Dockerfile指令。这样做的好处是什么?复用。比如你有一个基础镜像ubuntu:20.04,然后基于它安装Python,再安装你的应用代码。如果下次只需要换代码,那只需要重新构建最后一层,前面的层都可以缓存。

# 一个简单的Dockerfile示例
FROM ubuntu:20.04          # 基础镜像层
RUN apt-get update && \    # 安装依赖层
    apt-get install -y python3
COPY app.py /app/          # 复制代码层
CMD ["python3", "/app/app.py"]  # 启动命令层

关键点:镜像是分层的、只读的。每次构建都会生成新的层,但已有的层会被缓存。这也是为什么Docker构建速度很快的原因之一。

1.3.2 容器(Container)

容器是镜像的运行实例。你可以把它理解成Java里new出来的对象——类(镜像)定义好了,new一下(docker run)就得到一个运行中的实例(容器)。

容器在镜像的基础上加了一层可写层。所有对容器的修改(比如创建文件、修改配置)都写在这一层。当你删除容器时,这一层也会被删除。所以容器是无状态的——除非你挂载了数据卷。

我记得有一次在生产环境排查问题,一个同事直接在运行中的容器里改了配置文件。结果容器重启后,所有修改都丢了。嗯,这就是典型的「容器无状态」陷阱。

我的建议:永远不要在运行中的容器里做持久化修改。如果需要持久化数据,使用数据卷(Volume)或者绑定挂载(Bind Mount)。

1.3.3 仓库(Registry)

仓库就是存放镜像的地方。你可以把它理解成Maven的中央仓库,或者npm的包管理器。Docker Hub是官方的公共仓库,里面有几百万个镜像。你也可以搭建私有的仓库,比如Harbor、Nexus。

仓库的命名规则是:[仓库地址/]用户名/镜像名:标签。比如:

# 从Docker Hub拉取官方镜像
docker pull nginx:latest

# 从私有仓库拉取
docker pull registry.example.com/myapp:v1.0

# 推送镜像到仓库
docker push myusername/myapp:latest

标签(Tag)是用来区分不同版本的。我习惯用语义化版本号,比如v1.2.3。千万别用latest作为生产环境的标签——你永远不知道latest指向的是哪个版本。

避坑指南:我曾经在一个项目里用了latest标签,结果某天重新部署时,latest指向了一个不兼容的新版本,导致整个服务挂了半小时。从那以后,我所有生产环境都用具体的版本号标签。

1.4 三者之间的关系

镜像、容器、仓库的关系,我用一句话总结:从仓库拉取镜像,基于镜像创建容器,在容器中运行应用。

你想想看,这个流程是不是很清晰?开发人员把应用打包成镜像,推送到仓库。运维人员从仓库拉取镜像,在任何机器上启动容器。环境不一致的问题,就这么解决了。

最后分享一个我个人的习惯:每个项目我都会维护一个Dockerfile和一个docker-compose.yml。Dockerfile定义镜像怎么构建,docker-compose.yml定义多个容器怎么协同工作。这样不管是本地开发、测试环境还是生产环境,都能保证一致性。

好了,这一章的内容就到这里。下一章咱们会深入Docker的安装和基本命令操作。到时候我会分享一些我踩过的坑,保证让你少走弯路。