Docker与Kubernetes
开篇:从"在我电脑上能跑"到"在哪都能跑"
每个开发者都经历过这样的噩梦:代码在自己电脑上跑得好好的,部署到测试环境就挂了。原因无非是运行时版本不同、依赖缺失、配置差异。Docker 的出现,本质上就是为了解决这个问题 -- 把应用和它的整个运行环境一起打包,像集装箱一样标准化交付。
要理解 Docker,我们先快速回顾虚拟化技术的演进。
虚拟化的演进
最早的隔离手段是 Unix 的 chroot,它可以改变进程看到的根目录,让进程"以为"自己在一个独立的文件系统里。但 chroot 只隔离了文件系统,进程之间仍然共享 CPU、内存和网络。
后来出现了虚拟机技术(VMware、VirtualBox、KVM),它在物理硬件之上加了一层 Hypervisor,模拟出完整的硬件环境,每个虚拟机运行自己的操作系统。好处是隔离性极强,坏处也很明显 -- 每台虚拟机都要跑一个完整的 OS,启动慢、资源开销大。
Docker 走了一条中间路线:它利用 Linux 内核的 Namespace 和 Cgroup 技术,在操作系统层面做隔离,所有容器共享宿主机的内核。这就像大家住在同一栋楼里,但每个房间都有独立的门锁和水电表。
Docker 核心概念
理解 Docker,抓住三个核心概念就够了:镜像、容器、仓库。
用集装箱来类比最直观:
- 镜像(Image) = 集装箱的设计图纸。它是只读的模板,定义了"箱子里装什么"。
- 容器(Container) = 根据图纸造出来的真实集装箱。它是镜像的运行实例,可以启动、停止、删除。
- 仓库(Registry) = 存放图纸的仓库。Docker Hub 是最大的公共仓库,你也可以搭建私有仓库。
Docker 引擎
Docker 采用 C/S 架构:你在终端敲的 docker 命令是客户端,它通过 Unix Socket 或 REST API 与后台的 Docker 守护进程通信。守护进程才是真正干活的 -- 构建镜像、启动容器、管理网络。
镜像与 Dockerfile
镜像基于联合文件系统(UnionFS),由一层层只读层叠加而成。每条 Dockerfile 指令都会创建一个新的层。来看一个 Spring Boot 应用的 Dockerfile:
FROM openjdk:11-jdk
LABEL maintainer="dev@example.com"
ENV JAVA_OPTS="-Xmx512m -Xms256m"
WORKDIR /app
COPY target/my-app.jar my-app.jar
EXPOSE 8080
HEALTHCHECK --interval=1m --timeout=3s \
CMD curl -f http://localhost:8080/actuator/health || exit 1
ENTRYPOINT ["java", "-jar", "my-app.jar"]
CMD ["--server.port=8080"]常用指令速查:
| 指令 | 作用 | 说明 |
|---|---|---|
FROM | 指定基础镜像 | 每个 Dockerfile 必须以此开头 |
LABEL | 添加元数据 | 如维护者信息、版本号 |
ENV | 设置环境变量 | 运行时可用 |
WORKDIR | 设置工作目录 | 后续指令在此目录下执行 |
COPY / ADD | 复制文件到镜像 | COPY 更推荐,ADD 能自动解压 |
RUN | 构建时执行命令 | 常用于安装软件包 |
EXPOSE | 声明监听端口 | 仅声明,不会自动映射 |
ENTRYPOINT | 容器启动命令 | 容器作为可执行文件的入口 |
CMD | 默认参数 | 可被 docker run 后的参数覆盖 |
VOLUME | 创建挂载点 | 用于持久化数据 |
ARG | 构建时变量 | 只在 build 阶段有效 |
USER | 指定运行用户 | 提高安全性 |
常用命令
# ---- 镜像操作 ----
docker pull mysql:5.7 # 拉取镜像
docker images # 查看本地镜像
docker rmi mysql:5.7 # 删除镜像
docker build -t myapp:1.0 . # 根据 Dockerfile 构建镜像
docker tag myapp:1.0 myapp:latest # 给镜像打标签
docker push myapp:1.0 # 推送镜像到仓库
# ---- 容器操作 ----
docker run -d -p 8080:8080 --name myapp myapp:1.0 # 后台启动,映射端口
docker ps # 查看运行中的容器
docker ps -a # 查看所有容器(含已停止)
docker stop myapp # 停止容器
docker start myapp # 启动已停止的容器
docker rm myapp # 删除容器
docker logs -f myapp # 实时查看日志
docker logs --tail 50 myapp # 查看最新 50 行日志
docker exec -it myapp /bin/bash # 进入容器 shell
docker inspect myapp # 查看容器详细信息数据卷(Volume)
容器是临时的,容器删除后数据就没了。数据卷就是为了解决持久化问题 -- 它把宿主机的目录挂载到容器里,数据直接写在宿主机磁盘上。
# 方式一:命名卷(Docker 管理存储位置)
docker volume create my_data
docker run -d -v my_data:/data --name app1 nginx
docker run -d -v my_data:/data --name app2 nginx
# 两个容器共享 /data,一个写入另一个能看到
# 方式二:绑定挂载(指定宿主机路径)
docker run -d -v /host/path:/container/path --name app3 nginx
# 宿主机路径不存在会自动创建多容器共享数据卷是常见场景:比如一个容器负责写日志,另一个容器负责收集和分析日志。数据卷名在同一驱动下必须唯一,如果指定了已存在的卷名,Docker 会复用它。
容器 vs 虚拟机
| 维度 | 容器 | 虚拟机 |
|---|---|---|
| 启动速度 | 秒级 | 分钟级 |
| 性能 | 接近原生 | 有虚拟化开销 |
| 体积 | MB 级 | GB 级 |
| 隔离性 | 进程级(共享内核) | 硬件级(独立内核) |
| 单机密度 | 上千个 | 几十个 |
| 迁移性 | 优秀(镜像即环境) | 一般 |
| 安全性 | 共享内核有风险 | 完全隔离,更安全 |
一句话总结:虚拟机模拟的是硬件,每个 VM 跑一套完整 OS;容器隔离的是进程,所有容器共享宿主机内核。虚拟机像独栋别墅,容器像公寓隔间。
在更新维护方面也有明显差异:虚拟机要逐个更新每个 Guest OS,容器只需更新宿主机 OS 就行。同时容器可以通过 Kubernetes 等编排工具做集中管理,更新和回滚都更简单。
Docker Compose:一键编排多容器
实际项目不会只跑一个容器。前端 Nginx + 后端 Java + 数据库 MySQL,至少三个。Docker Compose 用一个 YAML 文件定义所有服务及其依赖关系,一条命令全部启动。
version: '3'
services:
web:
image: nginx:latest
ports:
- "80:80"
volumes:
- ./webroot:/usr/share/nginx/html
depends_on:
- app
app:
build:
context: ./app
dockerfile: Dockerfile
environment:
- DEBUG=false
depends_on:
- db
db:
image: postgres:latest
environment:
- POSTGRES_USER=admin
- POSTGRES_PASSWORD=secret
volumes:
- db-data:/var/lib/postgresql/data
volumes:
db-data:核心功能:
- 服务定义:在一个文件里定义 Web、App、DB 等一组相互关联的服务
- 一键部署:
docker-compose up启动全部,docker-compose down停止并清理 - 环境隔离:不同项目用不同的 Compose 文件,互不干扰
- 依赖管理:
depends_on声明启动顺序
从 Docker 到 Kubernetes
Docker 解决了单机上的容器管理问题。但到了生产环境,你可能有几十甚至上百台机器,成千上万个容器。这时候有一系列问题需要回答:
- 容器应该跑在哪台机器上?(调度)
- 某个容器挂了怎么办?(自愈)
- 流量增大了怎么扩容?(弹性伸缩)
- 服务之间怎么互相找到对方?(服务发现)
- 新版本怎么平滑上线?(滚动更新)
这就是 Kubernetes(K8s)的工作。如果说 Docker 是单兵装备,K8s 就是指挥系统。
K8s 核心架构
控制面(Master)组件:
- API Server:集群的"前台",所有操作(kubectl 命令、Dashboard、SDK)都通过它进入
- etcd:分布式 KV 存储,保存集群的所有状态数据(Pod 信息、配置、密钥等)
- Scheduler:决定新 Pod 放到哪个节点,考虑资源、亲和性、约束等
- Controller Manager:持续监控集群状态,确保实际状态和期望状态一致
工作节点(Node)组件:
- kubelet:每个节点上的"管家",负责管理本节点的 Pod 生命周期
- kube-proxy:处理网络规则和服务转发
K8s 核心概念
Pod -- K8s 最小调度单位,可以包含一个或多个容器。同一个 Pod 里的容器共享网络和存储空间,就像住在同一个房间的室友,彼此可以通过 localhost 互相访问。
Service -- 为一组 Pod 提供稳定的访问入口。Pod 会重建、IP 会变,但 Service 的 ClusterIP 和 DNS 名不变,起到"虚拟负载均衡器"的作用。外部流量通过 NodePort 或 Ingress 进入 Service,再由 Service 转发到后端 Pod。
Deployment -- 声明式地管理 Pod 的副本数和更新策略。你只需说"我要 3 个副本",Deployment Controller 会确保始终有 3 个健康的 Pod 在跑。支持滚动更新和一键回滚。
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 3 # 始终保持 3 个副本
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: my-app
image: my-app:1.0
ports:
- containerPort: 8080
resources:
limits:
memory: "512Mi"
cpu: "500m"K8s 相比 Docker 的核心优势:
| 能力 | Docker 单机 | K8s 集群 |
|---|---|---|
| 高可用 | 手动管理 | Pod 副本 + 自动重启 |
| 弹性伸缩 | 手动增减容器 | HPA 自动根据 CPU/内存扩缩 |
| 服务发现 | 手动配置 | 内置 DNS + Service |
| 滚动更新 | 手动操作 | Deployment 自动化 |
| 安全管理 | 基础隔离 | RBAC + NetworkPolicy + Secret |
面试高频题
Q1: 为什么要用 Docker?
Docker 是轻量级容器化方案,核心价值是"一次构建,到处运行"。它通过镜像打包应用及其依赖,确保开发、测试、生产环境完全一致,解决了环境差异问题。相比虚拟机,容器启动快(秒级)、资源省(MB 级)、密度高(单机上千容器),非常适合微服务架构下的快速迭代和持续交付。
Q2: 容器和虚拟机的区别?
架构层面,虚拟机通过 Hypervisor 模拟硬件,每个 VM 运行完整 OS 和内核;容器通过 Linux 内核的 Namespace/Cgroup 做进程级隔离,共享宿主机内核。因此容器更轻(MB vs GB)、更快(秒 vs 分钟)、密度更高,但隔离性不如 VM(内核漏洞会影响所有容器)。维护上,VM 需要逐个更新 Guest OS,容器只需更新宿主机 OS 即可。
Q3: 有了 Docker 为什么还需要 K8s?
Docker 解决的是单机容器运行问题,K8s 解决的是集群级容器管理问题。K8s 提供了自动调度(智能选择运行节点)、自愈(容器挂了秒级重启)、弹性伸缩(根据负载自动增减副本)、服务发现与负载均衡(内置 DNS 和 Service)、滚动更新与回滚(零停机发布)。Docker 是单兵装备,K8s 是指挥系统。
Q4: Docker Compose 是什么?
Docker Compose 是定义和运行多容器应用的编排工具。通过 docker-compose.yml 文件声明多个服务的镜像、端口、环境变量、数据卷和依赖关系,用 docker-compose up 一条命令全部拉起。它大大简化了开发和测试环境的搭建,但主要用于单机场景,生产环境的多机集群编排要用 K8s。
小结
| 技术 | 解决的问题 | 核心能力 |
|---|---|---|
| Docker | 环境一致性、标准化交付 | 镜像打包、容器运行 |
| Docker Compose | 多容器编排(单机) | YAML 声明、一键启停 |
| Kubernetes | 大规模容器管理(集群) | 调度、自愈、弹性、服务发现 |
从 chroot 到虚拟机,再到容器和 K8s,本质上是在"隔离性"和"资源效率"之间不断寻找最佳平衡点。Docker 让应用的交付变成了标准化的集装箱,K8s 则建起了管理这些集装箱的智能港口。掌握了这两项技术,你就拥有了现代应用部署和运维的基本功。