从Docker到Kubernetes:构建云原生应用交付心智模型

📅 2026/7/4 11:37:33 👁️ 阅读次数 📝 编程学习
从Docker到Kubernetes:构建云原生应用交付心智模型

🚀 30+款热门AI模型一站整合,DeepSeek/GLM/Claude 随心用,限时 5 折。 👉 点击领海量免费额度

上周,一个刚转行做运维的朋友深夜发来消息,说公司让他接手一个老项目,里面一堆docker-compose.ymlk8syaml文件,他看得一头雾水,问我有没有“速成”的办法。我给他发了几篇文档,他看完更困惑了:每个命令都认识,但连起来就不知道为什么要这么做,更别提出问题怎么查了。

这让我想起很多“零基础到实战”的教程,它们往往把 Docker 和 Kubernetes 拆解成一个个孤立的命令和配置文件,却很少讲清楚一个核心问题:这套技术栈真正解决的,不是“怎么启动一个容器”,而是如何把一次性的、依赖环境的手工部署,变成一套可重复、可观测、可自愈的标准化交付流程。如果你只学会了docker runkubectl apply,而没有理解背后从“手工运维”到“声明式运维”的思维转变,那么面对复杂的生产环境,你依然会寸步难行。

所以,这篇文章不会仅仅是命令的罗列。我想和你一起,从“为什么要用容器”这个最根本的问题出发,穿越 Docker 的单机隔离,最终抵达 Kubernetes 的集群编排世界。我们会重点关注那些在教程里一笔带过,但在实战中决定成败的细节:比如容器网络互通背后的原理、持久化存储的选型逻辑、以及如何从“能跑起来”到“能稳定运行”。我们的目标不是“看完视频”,而是建立一套属于自己的、从开发到部署的云原生应用交付心智模型

1. 起点:为什么是容器?重新理解“环境一致”这个老问题

几乎所有 Docker 教程的开篇都会说“它解决了环境一致性问题”。但这句话太轻了,轻到我们常常低估了“环境”二字的复杂性。

在容器出现之前,我们是怎么保证环境一致的?虚拟机能做到硬件隔离,但笨重且资源利用率低;手动写install.sh脚本,但无法保证宿主机库版本差异;更常见的是运维人员拿着一份“标准环境清单”逐台服务器去比对和调整。这种模式下,应用和它的运行环境是深度耦合的——应用的成功部署,高度依赖于某位资深运维的“肌肉记忆”和“祖传文档”。

Docker 带来的核心转变,是将应用及其完整的运行时依赖(包括库、环境变量、配置文件)打包成一个不可变的交付物——镜像。这个镜像可以在任何安装了 Docker 引擎的宿主机上,以几乎相同的方式运行起来。这里的“环境一致”,指的是从内核之上的用户空间(userspace)开始的一致性。

1.1 Docker 的基石:镜像、容器与仓库

让我们用更工程化的视角来看这三个核心概念:

  • 镜像:一个只读的模板。它不仅是你的应用代码,更是一个包含了运行所需一切的文件系统快照。你可以把它理解为一个“应用程序的装机盘”。它的分层存储机制是关键:每一层代表一次修改(如安装一个软件包),这些层是共享和复用的。这带来了两个直接影响:1) 拉取和传输镜像更快(只需拉取你本地没有的层);2) 构建镜像时,合理的分层能显著提升构建和部署效率。
  • 容器:镜像的一个运行实例。容器在镜像的最上层添加了一个可写的“容器层”,所有运行时产生的数据变化都发生在这里。当容器被删除,这个可写层也随之消失。这就引出了容器化应用的第一个重要设计原则:容器应该是无状态的(Stateless)。业务产生的需要持久化的数据,必须通过挂载卷(Volume)或绑定挂载(Bind Mount)的方式存放到容器外部。
  • 仓库:存放镜像的地方。Docker Hub 是公共仓库,而企业内通常会搭建私有仓库(如 Harbor)。仓库管理不仅仅是存储,还涉及镜像的版本控制、安全扫描和访问权限。

1.2 第一个实战命令:从docker run到理解容器生命周期

很多教程会让你直接docker run nginx。我们稍微慢一点,拆解这个过程:

# 1. 拉取镜像(如果本地没有) # Docker 会检查本地是否有 `nginx:latest` 镜像,没有则从默认仓库拉取。 # 你可以指定版本,如 `nginx:1.21-alpine`,这是生产环境的好习惯。 docker pull nginx:latest # 2. 创建并启动容器 # 这个简单的命令背后发生了很多事: # - Docker 引擎以 `nginx:latest` 镜像为模板,创建一个新的容器层。 # - 为容器分配一个唯一的ID和名称(可自定义)。 # - 设置一个隔离的网络命名空间(默认桥接网络)。 # - 启动镜像中定义的进程(对于nginx,是 `nginx -g 'daemon off;'`)。 docker run -d --name my-nginx -p 8080:80 nginx:latest

现在,访问http://localhost:8080就能看到 Nginx 欢迎页。但更重要的是,你需要知道如何与这个运行中的实体互动:

# 查看运行中的容器 docker ps # 查看所有容器(包括已停止的) docker ps -a # 查看容器日志(排障第一步) docker logs my-nginx # 进入容器内部(就像ssh进一台虚拟机,常用于调试) docker exec -it my-nginx /bin/bash # 停止容器 docker stop my-nginx # 启动已停止的容器 docker start my-nginx # 删除容器(必须先停止) docker rm my-nginx # 删除镜像(必须先删除依赖它的容器) docker rmi nginx:latest

关键理解docker run是一个复合命令,包含了createstart。在自动化脚本中,有时需要将它们分开以进行更精细的控制。容器的生命周期管理(创建、启动、停止、重启、删除)是后续 Kubernetes Pod 生命周期管理的基础。

2. 进阶:用 Dockerfile 与 Compose 固化你的部署流程

会运行现成的容器只是第一步。真正的价值在于将你自己的应用容器化。这就需要Dockerfile

2.1 Dockerfile:你的“应用装机说明书”

Dockerfile是一个文本文件,包含了一系列构建镜像的指令。一个典型的 Python Web 应用 Dockerfile 可能长这样:

# 第一阶段:构建阶段 FROM python:3.9-slim as builder WORKDIR /app COPY requirements.txt . # 使用国内源加速,这是实战中的小技巧 RUN pip install --no-cache-dir -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt # 第二阶段:运行阶段 FROM python:3.9-slim WORKDIR /app # 从构建阶段只复制安装好的依赖,减小最终镜像体积 COPY --from=builder /usr/local/lib/python3.9/site-packages /usr/local/lib/python3.9/site-packages COPY . . # 声明容器运行时监听的端口 EXPOSE 8000 # 定义容器启动时执行的命令 CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:8000", "app:app"]

构建并运行它:

docker build -t my-python-app . docker run -d -p 8000:8000 my-python-app

这个简单的文件体现了多个最佳实践:

  1. 使用特定版本的基础镜像(python:3.9-slim),而非latest,保证确定性。
  2. 多阶段构建:将编译/安装依赖的“构建环境”和最终运行的“运行时环境”分离,能极大减小镜像体积,提升安全性(因为构建工具不会留在运行时镜像中)。
  3. 合理使用.dockerignore文件:避免将node_modules__pycache__.git等不必要的文件复制进镜像,加速构建。
  4. 一个容器只运行一个主进程:这是微服务架构和容器编排的基本要求。

2.2 Docker Compose:定义和运行多容器应用的利器

单容器应用很少见。一个典型的 Web 应用至少需要:应用容器 + 数据库容器。手动用docker run管理它们之间的网络、依赖关系非常繁琐。Docker Compose 通过一个docker-compose.yml文件来解决这个问题。

version: '3.8' services: web: build: . ports: - "8000:8000" depends_on: - db environment: - DATABASE_URL=postgresql://user:password@db:5432/mydb volumes: - ./app/logs:/app/logs # 挂载日志目录 db: image: postgres:13 environment: POSTGRES_USER: user POSTGRES_PASSWORD: password POSTGRES_DB: mydb volumes: - postgres_data:/var/lib/postgresql/data # 使用命名卷持久化数据库数据 volumes: postgres_data: # 声明一个命名卷

一键启动整个应用栈:

docker-compose up -d

Compose 的价值在于它**将本地开发环境“代码化”**了。新成员克隆代码后,一个docker-compose up就能获得一个完整、一致的应用运行环境,无需在本地安装和配置 PostgreSQL、Redis 等中间件。它也是通往 Kubernetes 的很好过渡,因为docker-compose.yml的语法和思维与 K8s 的 YAML 有相似之处。

3. 飞跃:从单机 Docker 到集群 Kubernetes 的核心思维转变

当你能够在单机上熟练使用 Docker 和 Compose 后,自然会遇到它的天花板:

  • 如何管理成百上千个容器?
  • 容器挂了如何自动重启?
  • 如何实现滚动更新而不中断服务?
  • 如何根据 CPU/内存使用情况自动扩容缩容?
  • 如何统一管理配置和密钥?

这就是 Kubernetes 出场的时候。但请先忘掉那些复杂的 API 对象,理解一个根本性的思维转变:从“命令式”到“声明式”

  • 命令式:你告诉系统具体的执行步骤。“启动 3 个 Nginx 容器,把它们放在这 3 台机器上,把端口映射出来。”
  • 声明式:你告诉系统你期望的最终状态。“我需要一个名为my-web的服务,它始终有 3 个副本在运行,使用nginx:1.21镜像,暴露 80 端口。” Kubernetes 的控制器会持续观察当前状态,并驱动集群向你所声明的目标状态无限逼近。

3.1 Kubernetes 核心对象模型:Pod, Deployment, Service

这是初学者必须跨越的三个核心概念。

  1. Pod:Kubernetes 中最小的可部署和管理单元。一个 Pod 可以包含一个或多个紧密关联的容器,它们共享网络命名空间、IPC、UTS 以及有时可以共享存储卷。你可以把 Pod 想象成一个“逻辑主机”,里面的容器就像这个主机上运行的多个进程。但在 90% 的情况下,我们遵循“一个 Pod 一个容器”的最佳实践。

    # pod.yaml apiVersion: v1 kind: Pod metadata: name: my-nginx-pod spec: containers: - name: nginx image: nginx:1.21 ports: - containerPort: 80

    但你不会直接创建 Pod,因为 Pod 本身是脆弱的(节点故障,Pod 就没了)。我们使用Deployment

  2. Deployment:定义 Pod 的期望状态。它是管理无状态应用的核心对象。你声明“我需要 3 个这样的 Pod”,Deployment 控制器就会确保任何时候都有 3 个健康的 Pod 在运行。它还负责滚动更新和回滚。

    # deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment spec: replicas: 3 # 期望的副本数 selector: matchLabels: app: nginx template: # Pod 模板 metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.21 ports: - containerPort: 80

    应用它:kubectl apply -f deployment.yaml。Kubernetes 会创建 3 个 Pod。

  3. Service:Pod 是动态的(可以被销毁、重建、调度到不同节点)。Service 提供了一个稳定的网络端点(IP 地址和 DNS 名称)来访问一组具有相同标签的 Pod。它是服务的抽象,负责负载均衡。

    # service.yaml apiVersion: v1 kind: Service metadata: name: nginx-service spec: selector: app: nginx # 选择所有标签为 app: nginx 的 Pod ports: - protocol: TCP port: 80 # Service 对外暴露的端口 targetPort: 80 # Pod 内容器监听的端口 type: ClusterIP # 默认类型,仅在集群内部可访问

    应用后,在集群内部,其他 Pod 就可以通过nginx-service这个 DNS 名称来访问这组 Nginx Pod。

一个最简单的应用部署流程就是:编写 Deployment 和 Service 的 YAML 文件,然后kubectl apply

3.2 实战入门:在本地搭建 Kubernetes 实验环境

在生产环境,Kubernetes 集群由多台物理机或虚拟机组成。但对于学习和开发,我们可以在本地快速启动一个单节点集群。推荐以下工具:

  • Minikube:最经典的本地 K8s 工具,会在本地虚拟机中启动一个单节点集群。
  • Docker Desktop:内置了 Kubernetes 功能,一键启用,最为方便。
  • Kind:使用 Docker 容器作为“节点”来运行 K8s 集群,轻量快速。

Docker Desktop为例:

  1. 打开 Docker Desktop 设置,进入 “Kubernetes” 选项卡。
  2. 勾选 “Enable Kubernetes”,点击 “Apply & Restart”。等待几分钟,集群就绪。
  3. 在终端验证:kubectl cluster-infokubectl get nodes

现在,你可以将上一节的deployment.yamlservice.yaml应用到你的本地集群中,并通过端口转发来访问服务:

kubectl apply -f deployment.yaml kubectl apply -f service.yaml # 查看 Pod 状态 kubectl get pods # 将集群内的 Service 端口映射到本地 kubectl port-forward service/nginx-service 8080:80

访问http://localhost:8080,你就通过本地 Kubernetes 集群访问到了 Nginx。

4. 深入:生产就绪必须考虑的三大支柱——网络、存储与配置

让应用在 K8s 里“跑起来”只是第一步。要让它“跑得稳”、“跑得好”,必须理解并处理好网络、存储和配置管理。

4.1 网络:Pod 之间如何通信?

Kubernetes 网络模型要求:

  1. 每个 Pod 都拥有一个唯一的 IP 地址(Pod IP)。
  2. 所有 Pod 可以在不经过 NAT 的情况下直接与其他所有 Pod 通信。
  3. 所有节点可以在不经过 NAT 的情况下与所有 Pod 通信。

这是通过CNI实现的。当你部署 Minikube 或 Kind 时,它们已经集成了 CNI 插件(如 Flannel、Calico)。作为应用开发者,你主要和Service打交道。Service 有几种类型:

  • ClusterIP:默认,仅在集群内部访问。
  • NodePort:在每个节点上开放一个静态端口,将流量转发到 Service。适合本地测试或简单暴露。
  • LoadBalancer:在云平台上,会自动创建一个外部负载均衡器,将流量导入 Service。这是暴露服务到公网的常用方式。
  • Ingress它不是一种 Service 类型,而是一个 API 对象,用于管理外部 HTTP/HTTPS 访问。Ingress 需要配合 Ingress Controller(如 Nginx Ingress Controller)使用,可以提供基于域名和路径的路由、SSL 终止等功能,是生产环境暴露 Web 服务的标准方式。

4.2 存储:容器重启后,数据如何不丢失?

重申容器的重要原则:容器本身是无状态的。Pod 的生命周期也可能很短暂。因此,任何需要持久化的数据(数据库文件、上传的图片、日志)都必须存储在容器之外。

Kubernetes 通过VolumePersistentVolume (PV) / PersistentVolumeClaim (PVC)抽象来管理存储。

  • Volume:与 Pod 生命周期绑定。Pod 消失,Volume 也可能消失(取决于类型)。适合临时数据共享。
  • PV & PVC:解耦存储细节与应用需求。
    • PersistentVolume:集群中的一块存储资源,由管理员预先配置(如 NFS 服务器、云硬盘)。
    • PersistentVolumeClaim:用户对存储的“申请单”。Pod 通过 PVC 来使用 PV。

示例:为之前的 Nginx Deployment 添加一个持久化存储,用于存放日志。

# pvc.yaml apiVersion: v1 kind: PersistentVolumeClaim metadata: name: nginx-logs-pvc spec: accessModes: - ReadWriteOnce # 访问模式:单节点读写 resources: requests: storage: 1Gi # 申请1G空间
# deployment-with-pvc.yaml (部分) apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment spec: ... template: ... spec: containers: - name: nginx image: nginx:1.21 volumeMounts: - name: log-volume mountPath: /var/log/nginx # 将卷挂载到容器内的日志目录 volumes: - name: log-volume persistentVolumeClaim: claimName: nginx-logs-pvc # 使用上面创建的PVC

这样,即使 Pod 被重建,新的 Pod 挂载同一个 PVC,就能访问到之前的日志文件。

4.3 配置与密钥:如何管理环境变量和敏感信息?

将配置硬编码在镜像或 YAML 文件中是糟糕的做法。Kubernetes 提供了ConfigMapSecret

  • ConfigMap:用于存储非敏感的配置数据,如配置文件、环境变量。
    # configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: app-config data: app.properties: | server.port=8080 logging.level=INFO database.url: jdbc:mysql://db-host:3306/mydb
  • Secret:用于存储敏感信息,如密码、令牌、密钥。数据默认以 Base64 编码存储(仅防君子不防小人,真正的安全需要配合 RBAC 和加密等)。
    # secret.yaml apiVersion: v1 kind: Secret metadata: name: db-secret type: Opaque data: username: YWRtaW4= # admin password: cGFzc3dvcmQ= # password

在 Deployment 中引用它们:

env: - name: DB_USERNAME valueFrom: secretKeyRef: name: db-secret key: username - name: LOG_LEVEL valueFrom: configMapKeyRef: name: app-config key: logging.level volumeMounts: - name: config-volume mountPath: /etc/app/config volumes: - name: config-volume configMap: name: app-config

5. 走向实战:从部署到观测与维护

当你掌握了以上核心概念,就可以开始规划一个真实应用的部署了。这里提供一个从开发到部署的简化流程框架:

5.1 应用容器化与部署清单设计

  1. 编写 Dockerfile:遵循多阶段构建、使用非 root 用户等最佳实践。
  2. 设计 Kubernetes 清单:这是核心设计环节。
    • Deployment:定义副本数、资源请求与限制、健康检查、滚动更新策略。
    • Service:定义内部访问方式。
    • Ingress:定义外部访问路由和 TLS。
    • ConfigMap/Secret:定义配置和密钥。
    • PersistentVolumeClaim:定义存储需求。
  3. 使用 Kustomize 或 Helm 进行管理:当环境(开发、测试、生产)增多,配置差异化时,需要工具来管理这些 YAML 文件的覆盖和组合。Helm 是事实上的包管理标准。

5.2 不可或缺的“观测性”三板斧

应用上线后,你必须知道它是否健康。Kubernetes 提供了基础能力,但需要你主动定义。

  1. 存活探针:告诉 K8s 容器是否还在“活着”。如果失败,K8s 会重启容器。
    livenessProbe: httpGet: path: /healthz port: 8080 initialDelaySeconds: 30 # 容器启动后30秒开始探测 periodSeconds: 10 # 每10秒探测一次
  2. 就绪探针:告诉 K8s 容器是否已准备好接收流量。如果失败,Service 会将该 Pod 从负载均衡端点中移除。
    readinessProbe: httpGet: path: /ready port: 8080 initialDelaySeconds: 5 periodSeconds: 5
  3. 日志与监控
    • 日志:确保应用日志输出到标准输出和标准错误,这样kubectl logs和日志收集系统才能抓取。不要将日志只写入容器内的文件
    • 监控:部署 Prometheus 来收集集群和应用的指标,使用 Grafana 进行可视化。这是洞察系统性能、发现问题瓶颈的眼睛。

5.3 日常运维与排障命令速查

最后,附上一组最常用、也最能体现你理解深度的kubectl命令,它们构成了你与集群交互的主要界面:

# 查看资源状态 kubectl get pods,svc,deploy,ing -n <namespace> # 获取指定命名空间的各种资源 kubectl describe pod <pod-name> # 查看Pod的详细事件和状态,排障第一选择 kubectl get pods -w # 实时观察Pod状态变化 # 与Pod交互 kubectl logs -f <pod-name> [-c <container-name>] # 查看并跟随日志输出 kubectl exec -it <pod-name> -- /bin/sh # 进入Pod内的容器 # 应用与更新 kubectl apply -f <file.yaml> # 声明式应用配置 kubectl rollout status deployment/<deploy-name> # 查看部署状态 kubectl rollout undo deployment/<deploy-name> # 回滚到上一版本 # 诊断与调试 kubectl top pod/node # 查看资源使用情况 kubectl port-forward <resource-name> [local-port]:<pod-port> # 端口转发,用于本地访问集群内服务

学习 Docker 和 Kubernetes 的过程,很像学习一门新的编程语言。一开始你会被语法(各种命令和 YAML 字段)困扰,但真正的突破发生在你理解了它的“编程范式”——声明式、控制器模式、最终一致性。当你不再机械地复制 YAML,而是能根据应用的需求,在脑海中勾勒出 Deployment、Service、Ingress、ConfigMap 如何协同工作时,你就真正从“入门”走向了“实战”。这条路没有捷径,但每一步的深入,都会让你对现代软件交付的掌控力,提升一个维度。

🚀 30+款热门AI模型一站整合,DeepSeek/GLM/Claude 随心用,限时 5 折。 👉 点击领海量免费额度