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的安装和基本命令操作。到时候我会分享一些我踩过的坑,保证让你少走弯路。