2026 最强容器技术指南:Kubernetes 和 Docker 关系深度解析,一文讲透核心原理与企业落地(建议收藏)

前言
话说回来,2026 年的今天,如果你还分不清 Docker 和 Kubernetes 的区别,那面试官看你的眼神,大概就像你看三年前自己写的代码一样——充满了复杂的情感。
别担心,今天这篇文章,就是来帮你彻底理清这对"世纪冤家"的关系。我会从痛点场景出发,用大白话解释原理,配上 Python 实战代码,最后再来点面试题压压惊。
准备好了吗?发车!
一、痛点场景:你中招了几个?
在正式开始之前,让我们先来聊聊那些年,我们一起踩过的容器坑。
场景一:开发环境正常,上线就翻车
"在我的机器上能跑啊!"
这句话大概是程序员说过的最著名谎言之一。你在本地 Windows 上开发得好好的 Python 服务,丢到服务器上就报错:"ModuleNotFoundError: No module named 'xxx'"。同事跑过来一看:"哦,你用的是 macOS 啊,那个包在 Linux 上编译不过的。"
然后你们开始了漫长的"依赖对齐"大战:改环境变量、装编译工具链、处理版本冲突……一通操作猛如虎,上线时间已经过了两天。
这就是"环境一致性"问题,而 Docker 正是来解决这个问题的。
场景二:手动扩缩容之痛
"双十一马上到了,老板说流量要扩容到 10 倍!"
你看着手里的 50 台服务器,陷入了沉思。手动登录每一台机器,执行 docker run 命令,然后手动配置负载均衡……
等你扩完容,双十一都结束了。
更可怕的是,峰值过后你要再缩回去。如果手动操作,你可能会在凌晨三点一边缩容一边怀疑人生:"我为什么要学计算机?"
这就是"弹性扩缩容"问题,而 Kubernetes 正是来解决这个问题的。
场景三:集群规模膨胀之痛
随着业务发展,你的 Docker 容器从 10 个变成了 100 个,又从 100 个变成了 1000 个。
然后你发现:
- 某个容器挂了,没人知道
- 某个服务响应慢,不知道是谁的问题
- 要更新某个服务,得一个个手动操作
- 资源分配不均,有的机器打满了,有的还空着
你开始怀念当初只有 10 个容器的日子,那时候一切都很简单,简单到你可以记住每一个容器的名字和用途。
这就是"容器编排"问题,而 Kubernetes 就是来解决这个问题的。
场景四:面试翻车之痛
面试官:"Docker 和 Kubernetes 有什么区别?"
你(信心满满):"Docker 是容器,Kubernetes 是编排平台!"
面试官(微笑):"那 Kubernetes 为什么在 1.24 版本移除了对 Docker 的直接支持?"
你:"???"
面试官:"你知道 CRI 是什么吗?"
你:"?????"
这就是"知识深度不够"的问题,今天这篇文章就是来帮你避免这种社死场面的。
二、痛点解决方案:为什么是 Docker+Kubernetes?
2.1 黄金组合:各司其职
其实啊,Docker 和 Kubernetes 从来就不是竞争关系,而是最佳拍档。
- Docker:负责"打包"——把你的应用和依赖打包成一个标准化的容器镜像
- Kubernetes:负责"调度"——管理这些容器的运行、扩缩容、故障恢复等
这就好比:
- Docker = 快递公司(打包、封装货物)
- Kubernetes = 物流调度中心(分配路线、处理异常、批量管理)
2.2 数据说话
根据 CNCF(云原生计算基金会)2025 年的调查数据:
- 82% 的组织已经在生产环境中使用 Kubernetes
- 77% 的组织使用 Docker 作为容器运行时
- 65% 的组织同时使用两者
换句话说,不懂 Docker 和 Kubernetes,你可能连跟同行聊天都费劲。
2.3 核心公式
容器化应用 = Dockerfile(构建) -> Docker Image(打包) -> Docker Registry(分发)
容器编排系统 = Kubernetes(调度) -> Pod(调度单元) -> Deployment(管理)
记住这个公式,你就理解了容器技术的半壁江山。
三、是什么:极简概念与原理
3.1 Docker 核心定义
官方定义:Docker 是一个开源的容器化平台,让开发者能够打包应用及其依赖到一个可移植的容器中。
大白话版:Docker 就是一个"应用打包工厂"。你把代码和运行环境一股脑丢给 Docker,它给你吐出一个"集装箱"——这个集装箱里包含了代码、运行时、系统工具、系统库,你把这个集装箱往哪一放,它就能在哪跑起来。
核心组件:
- Dockerfile:定义镜像构建规则的"配方"
- Docker Image:只读的容器模板,可以理解为"模具"
- Docker Container:基于镜像运行的实例,可以理解为"用模具造出来的产品"
- Docker Daemon (dockerd):后台服务,负责管理容器
- Docker CLI:命令行工具,你和 Docker 交互的"翻译官"
3.2 Kubernetes 核心定义
官方定义:Kubernetes(简称 K8s)是一个开源的容器编排平台,用于自动化容器化应用的部署、扩缩容和管理。
大白话版:Kubernetes 就是一个"机房管理员",只不过这个管理员从不休息、从不犯错、记忆力还特别好。它能自动监控你所有的容器,发现哪个挂了立刻重启,发现流量大了自动扩容,发现资源不够自动调度。
核心组件:
- Master Node:集群的"大脑",负责整体调度决策
- kube-apiserver:API 入口,所有操作的"前台接待"
- kube-scheduler:负责分配 Pod 到哪个节点
- kube-controller-manager:各种控制器,管理各种资源状态
- etcd:分布式存储,保存集群的所有状态
- Worker Node:干活的"工人",实际运行容器
- kubelet:节点上的"监工",汇报节点状态
- kube-proxy:网络代理,负责服务间通信
- container-runtime:实际运行容器的引擎
- Pod:K8s 的最小调度单位,一个 Pod 里可以有一个或多个容器
- Deployment:管理 Pod 副本数的控制器
- Service:为 Pod 提供稳定的访问入口
- Ingress:管理外部访问的"大门"
3.3 二者关系的形象比喻
让我用一个更接地气的比喻来解释 Docker 和 Kubernetes 的关系:
把 Docker 想象成"货车":
- 你有一批货物(应用代码)
- 货车负责把货物装进集装箱(构建镜像)
- 货车负责把集装箱运到目的地(分发镜像)
把 Kubernetes 想象成"物流调度中心":
- 调度中心决定用多少辆车、每辆车装什么货
- 调度中心监控每辆车的状态,发现抛锚立刻换车
- 调度中心根据订单量动态调整运力
关键点:货车本身不重要,重要的是能运货。同样,Docker 只是容器运行时的一种选择,Kubernetes 还支持 containerd、CRI-O 等其他运行时。
3.4 Kubernetes 为什么不用 Docker 了?
等等,既然 Docker 和 Kubernetes 是"最佳拍档",为什么 Kubernetes 在 1.24 版本移除了对 Docker 的直接支持?
答案:为了简化架构、提高性能。
让我们来看看架构演变:
旧架构(K8s 1.24 之前):
kubelet -> dockershim -> dockerd -> containerd -> runc
新架构(K8s 1.24 之后):
kubelet -> CRI -> containerd -> runc
变化在哪里?
- 移除了 dockershim:dockershim 是 K8s 为了兼容 Docker 临时写的一段"适配器代码",维护起来很麻烦
- 直接对接 containerd:containerd 才是 Docker 的核心,K8s 直接对接它就够了
- 更少层级,更高性能:少了一层转发,资源消耗更少
但是!你之前的 Dockerfile 没有白写。
因为 Docker 镜像遵循 OCI 标准(Open Container Initiative),这个标准包括:
- Image-spec:镜像格式规范
- Distribution-spec:镜像分发规范
- Runtime-spec:运行时规范
所以 containerd 和 CRI-O 都能 100% 兼容你的 Docker 镜像。你的 docker build 命令白打了吗?当然没有!
3.5 Dockerfile 没有白写
即使你用的是 containerd 而不是 Docker 作为运行时,你的 Dockerfile 依然有效,因为:
- 工具链通用:
docker build生成的镜像,ctr或nerdctl都能使用 - 镜像格式兼容:OCI 格式是行业标准,所有符合 OCI 的运行时都能运行
- 构建工具替代:可以用
buildah、kaniko、buildkit等工具替代docker build
所以,Dockerfile 是你编写过最值钱的配置之一,它让你的应用可以被任何 OCI 兼容的容器运行时使用。
四、为什么用:核心优势与对比
4.1 Docker vs 虚拟机
| 维度 | Docker 容器 | 虚拟机 |
|---|---|---|
| 启动速度 | 秒级(通常 1-2 秒) | 分钟级(通常 1-5 分钟) |
| 资源占用 | 轻量(MB 级别) | 重量(GB 级别,需要独立 OS) |
| 密度 | 高(单机能跑上百个容器) | 低(单机能跑几个 VM) |
| 隔离性 | 进程级隔离 | 硬件级隔离(更强) |
| 性能损耗 | 几乎无损耗 | 有虚拟化开销(约 5-10%) |
| 镜像大小 | 小(通常几十到几百 MB) | 大(通常几 GB) |
比喻版:
- 虚拟机 = 独立房子,每个房子有自己的水电系统,安全但贵
- Docker 容器 = 酒店房间,共享基础设施,便宜但依赖酒店服务
选谁?
- 需要强隔离、安全性优先 -> 虚拟机
- 需要快速部署、高密度、资源效率 -> Docker 容器
4.2 Kubernetes vs 手动 docker run
手动管理 100 个容器:
# 你需要做的事情
docker run -d --name app1 -p 8001:80 myapp:v1
docker run -d --name app2 -p 8002:80 myapp:v1
docker run -d --name app3 -p 8003:80 myapp:v1
# ... 重复97次
# 监控:手动登录每台机器
docker ps
docker logs app1
docker stats
# 扩缩容:逐个操作
docker scale app1=10 # 不支持!
# 更新:逐个替换
docker stop app1
docker rm app1
docker run -d --name app1 -p 8001:80 myapp:v2
Kubernetes 管理 100 个容器:
# 一个Deployment搞定一切
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 100
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: myapp:v1
ports:
- containerPort: 80
一条命令:kubectl apply -f deployment.yaml,然后:
- 自动创建 100 个 Pod
- 自动分配到合适的节点
- 自动监控健康状态
- 自动故障恢复
- 自动扩缩容
结论:容器少的时候 Docker 够用,容器多了必须上 K8s。
4.3 Kubernetes vs Docker Compose vs Docker Swarm
| 特性 | Docker Compose | Docker Swarm | Kubernetes |
|---|---|---|---|
| 定位 | 单机容器编排 | 集群容器编排 | 企业级容器编排 |
| 复杂度 | 低 | 中 | 高 |
| 功能 | 启动多个关联容器 | 基本的集群管理 | 全面的编排能力 |
| 扩缩容 | 手动 | 半自动 | 自动 |
| 服务发现 | 基础 | 内置 | 内置 |
| 负载均衡 | 基础 | 内置 | 需配合 Service |
| 滚动更新 | 支持 | 支持 | 支持 |
| 回滚 | 支持 | 支持 | 支持 |
| 适用场景 | 开发测试 | 小规模生产 | 大规模生产 |
选择建议:
- 本地开发:Docker Compose(简单、快速)
- 小规模生产(<100 容器):Docker Swarm(简单、功能够用)
- 大规模生产:Kubernetes(功能全面、生态成熟)
4.4 2026 年运行时选型建议
截至 2026 年 4 月,主要的容器运行时选择:
| 运行时 | 版本 | 适用场景 | 特点 |
|---|---|---|---|
| containerd | 2.1.x (LTS) | 大规模生产集群 | 性能最优,K8s 默认 |
| CRI-O | 最新稳定版 | 边缘计算、安全敏感场景 | 更轻量,与 K8s 原生集成 |
| Docker | 27.x/29.x | 开发环境、CI/CD | 生态最完善 |
2026 年新变化:Kubernetes 1.35 要求使用 cgroups v2,如果你还在用 cgroups v1,需要升级内核。
选型建议:
- 生产环境首选 containerd
- 安全要求极高选 CRI-O
- 开发环境用 Docker 即可
五、怎么用:保姆级基础教学
5.1 环境搭建
Docker 安装(Ubuntu 为例):
# 安装Docker
curl -fsSL https://get.docker.com | sh
# 验证安装
docker --version
# Docker version 27.x.x
# 配置当前用户
sudo usermod -aG docker $USER
Kubernetes 安装(MiniKube 单节点):
# 安装kubectl
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
chmod +x kubectl
sudo mv kubectl /usr/local/bin/
# 安装MiniKube
curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
sudo install minikube-linux-amd64 /usr/local/bin/minikube
# 启动MiniKube
minikube start --driver=docker
阿里云 ACK(托管 K8s):
# 安装阿里云CLI和ACK插件
curl -sSL https://aliyuncli.alibabacloud.com/install.sh | sh
# 配置凭证
aliyun configure
# 创建ACK集群(需要阿里云账号)
aliyun cs POST /clusters --body "$(cat cluster-config.json)"
5.2 Python + Docker SDK 实战
安装 Docker SDK for Python:
pip install docker
实战 1:构建镜像并运行容器:
import docker
# 创建Docker客户端
client = docker.from_env()
# 构建自定义镜像
print("开始构建镜像...")
image, build_logs = client.images.build(
path='.',
tag='my-python-app:latest',
rm=True # 构建成功后删除中间容器
)
# 打印构建日志
for log in build_logs:
if 'stream' in log:
print(log['stream'], end='')
print(f"镜像构建成功: {image.tags}")
# 运行容器
print("启动容器...")
container = client.containers.run(
"my-python-app:latest",
ports={"5000/tcp": 5000},
detach=True, # 后台运行
environment={
"FLASK_ENV": "production",
"LOG_LEVEL": "info"
},
name="my-flask-app",
remove=False # 停止后保留容器
)
print(f"容器已启动: {container.short_id}")
print(f"容器状态: {container.status}")
实战 2:管理容器生命周期:
import docker
import time
client = docker.from_env()
# 获取容器
container = client.containers.get("my-flask-app")
# 查看容器状态
print(f"容器ID: {container.id}")
print(f"容器名: {container.name}")
print(f"容器状态: {container.status}")
print(f"容器镜像: {container.image.tags}")
# 查看日志
print("\n--- 容器日志 ---")
for log_line in container.logs(stream=True, follow=True):
print(log_line.decode().strip())
# 停止容器
# container.stop()
# 重启容器
# container.restart()
# 查看资源使用
stats = container.stats(stream=False)
print(f"\nCPU使用: {stats['cpu_stats']['cpu_usage']['total_usage']}")
print(f"内存使用: {stats['memory_stats']['usage']}")
实战 3:列出和清理资源:
import docker
client = docker.from_env()
# 列出所有容器
print("=== 所有容器 ===")
all_containers = client.containers.list(all=True)
for c in all_containers:
print(f" {c.name}: {c.status}")
# 列出所有镜像
print("\n=== 所有镜像 ===")
all_images = client.images.list()
for img in all_images:
tags = img.tags if img.tags else ['<none>']
print(f" {', '.join(tags)}: {img.attrs['Size']} bytes")
# 清理未使用的资源
print("\n清理未使用的资源...")
client.containers.prune()
client.images.prune()
print("清理完成")
5.3 Python + Kubernetes API 实战
安装 Kubernetes SDK:
pip install kubernetes
实战 1:创建 Deployment:
from kubernetes import client, config
from kubernetes.client.rest import ApiException
import yaml
# 加载kubeconfig(本地开发用)
# 生产环境可用 config.load_incluster_config()
config.load_kube_config()
# 创建API实例
api_instance = client.AppsV1Api()
# 定义Deployment
deployment = client.V1Deployment(
api_version="apps/v1",
kind="Deployment",
metadata=client.V1ObjectMeta(
name="nginx-deployment",
labels={"app": "nginx", "environment": "production"}
),
spec=client.V1DeploymentSpec(
replicas=3,
selector=client.V1LabelSelector(
match_labels={"app": "nginx"}
),
template=client.V1PodTemplateSpec(
metadata=client.V1ObjectMeta(
labels={"app": "nginx"}
),
spec=client.V1PodSpec(
containers=[
client.V1Container(
name="nginx",
image="nginx:latest",
ports=[client.V1ContainerPort(
container_port=80,
protocol="TCP"
)],
resources=client.V1ResourceRequirements(
requests={"cpu": "100m", "memory": "128Mi"},
limits={"cpu": "500m", "memory": "512Mi"}
),
liveness_probe=client.V1Probe(
http_get=client.V1HTTPGetAction(
path="/",
port=80
),
initial_delay_seconds=3,
period_seconds=10
)
)
]
)
)
)
)
# 创建Deployment
try:
api_instance.create_namespaced_deployment(
namespace="default",
body=deployment
)
print("Deployment创建成功!")
except ApiException as e:
if e.status == 409:
print("Deployment已存在,更新中...")
api_instance.replace_namespaced_deployment(
name="nginx-deployment",
namespace="default",
body=deployment
)
print("Deployment更新成功!")
else:
print(f"创建失败: {e}")
实战 2:扩缩容与滚动更新:
from kubernetes import client, config
config.load_kube_config()
api_instance = client.AppsV1Api()
# 扩缩容
def scale_deployment(name, replicas, namespace="default"):
"""扩缩容Deployment"""
body = {"spec": {"replicas": replicas}}
api_instance.patch_namespaced_deployment_scale(
name=name,
namespace=namespace,
body=body
)
print(f"已将 {name} 扩缩容到 {replicas} 副本")
# 滚动更新镜像
def update_image(name, new_image, namespace="default"):
"""更新容器镜像"""
body = {
"spec": {
"template": {
"spec": {
"containers": [{
"name": name,
"image": new_image
}]
}
}
}
}
api_instance.patch_namespaced_deployment(
name=name,
namespace=namespace,
body=body
)
print(f"已将 {name} 更新到镜像 {new_image}")
# 示例调用
scale_deployment("nginx-deployment", replicas=10)
update_image("nginx-deployment", "nginx:1.25-alpine")
实战 3:监控 Pod 状态:
from kubernetes import client, config
import time
config.load_kube_config()
v1 = client.CoreV1Api()
def watch_pods(namespace="default"):
"""实时监控Pod状态"""
print(f"监控 {namespace} 命名空间下的Pod...\n")
count = 0
for event in v1.watch.namespaced_pod(namespace).watch():
pod = event['object']
pod_name = pod.metadata.name
phase = pod.status.phase
reason = pod.status.reason or ""
event_type = event['type']
timestamp = pod.metadata.creation_timestamp.strftime("%H:%M:%S")
print(f"[{timestamp}] {event_type:10} | {pod_name:30} | {phase:10} | {reason}")
count += 1
if count > 20: # 监控20条事件后退出
break
def get_pod_stats(namespace="default"):
"""获取Pod统计信息"""
pods = v1.list_namespaced_pod(namespace)
stats = {"Running": 0, "Pending": 0, "Failed": 0, "Succeeded": 0}
for pod in pods.items:
stats[pod.status.phase] = stats.get(pod.status.phase, 0) + 1
print(f"\n=== {namespace} Pod统计 ===")
for state, count in stats.items():
if count > 0:
print(f" {state}: {count}")
return stats
# 获取统计
get_pod_stats()
5.4 Python 自动化部署脚本
一个完整的自动化部署脚本:
#!/usr/bin/env python3
"""
企业级自动化部署脚本
功能:构建镜像 -> 推送仓库 -> 部署到K8s -> 验证
"""
import docker
from kubernetes import client, config
from kubernetes.client.rest import ApiException
import time
import sys
class K8sDeployer:
def __init__(self, image_name, namespace="default"):
self.docker_client = docker.from_env()
self.image_name = image_name
self.namespace = namespace
# 加载K8s配置
try:
config.load_kube_config()
self.k8s_available = True
except Exception:
self.k8s_available = False
print("警告: 无法连接Kubernetes集群,仅执行Docker操作")
if self.k8s_available:
self.apps_v1 = client.AppsV1Api()
self.core_v1 = client.CoreV1Api()
def build_image(self, dockerfile_path=".", tag=None):
"""构建Docker镜像"""
if tag is None:
tag = self.image_name
print(f"[1/4] 构建镜像: {tag}")
try:
image, logs = self.docker_client.images.build(
path=dockerfile_path,
tag=tag,
rm=True
)
for log in logs:
if 'stream' in log:
print(f" {log['stream'].strip()}")
print(f" 镜像构建成功: {image.short_id}")
return tag
except Exception as e:
print(f" 镜像构建失败: {e}")
return None
def push_to_registry(self, image_tag):
"""推送到镜像仓库(需要先登录)"""
print(f"[2/4] 推送镜像到仓库: {image_tag}")
try:
for line in self.docker_client.images.push(
image_tag.split(':')[0],
tag=image_tag.split(':')[1] if ':' in image_tag else 'latest',
stream=True,
decode=True
):
if 'status' in line:
print(f" {line['status']}", end='\r')
print("\n 镜像推送成功!")
return True
except Exception as e:
print(f"\n 镜像推送失败: {e}")
return False
def deploy_to_k8s(self, image_tag, replicas=3):
"""部署到Kubernetes"""
if not self.k8s_available:
print("[3/4] 跳过K8s部署(集群不可用)")
return False
print(f"[3/4] 部署到Kubernetes: {image_tag}")
deployment_name = self.image_name.split('/')[-1].replace(':', '-')
deployment = client.V1Deployment(
api_version="apps/v1",
kind="Deployment",
metadata=client.V1ObjectMeta(
name=deployment_name,
namespace=self.namespace
),
spec=client.V1DeploymentSpec(
replicas=replicas,
selector=client.V1LabelSelector(
match_labels={"app": deployment_name}
),
template=client.V1PodTemplateSpec(
metadata=client.V1ObjectMeta(
labels={"app": deployment_name}
),
spec=client.V1PodSpec(
containers=[client.V1Container(
name="app",
image=image_tag,
ports=[client.V1ContainerPort(container_port=8080)]
)]
)
)
)
)
try:
self.apps_v1.create_namespaced_deployment(
namespace=self.namespace,
body=deployment
)
print(f" Deployment创建成功")
except ApiException as e:
if e.status == 409:
self.apps_v1.replace_namespaced_deployment(
name=deployment_name,
namespace=self.namespace,
body=deployment
)
print(f" Deployment更新成功")
else:
print(f" Deployment操作失败: {e}")
return False
# 等待Pod就绪
print(" 等待Pod就绪...", end='')
for _ in range(30):
time.sleep(2)
pods = self.core_v1.list_namespaced_pod(
self.namespace,
label_selector=f"app={deployment_name}"
)
ready = sum(1 for p in pods.items if p.status.phase == "Running")
print(f"\r 等待Pod就绪... ({ready}/{replicas})", end='')
if ready >= replicas:
print("\n 所有Pod已就绪!")
return True
print("\n 等待超时,部分Pod可能未就绪")
return True
def verify_deployment(self):
"""验证部署"""
if not self.k8s_available:
print("[4/4] 跳过验证(集群不可用)")
return
print("[4/4] 验证部署状态")
deployment_name = self.image_name.split('/')[-1].replace(':', '-')
deployment = self.apps_v1.read_namespaced_deployment(
name=deployment_name,
namespace=self.namespace
)
replicas = deployment.status.ready_replicas or 0
desired = deployment.spec.replicas
print(f" 期望副本: {desired}")
print(f" 就绪副本: {replicas}")
print(f" 状态: {'成功' if replicas == desired else '部分就绪'}")
def main():
if len(sys.argv) < 2:
print("用法: python deploy.py <image_name> [--namespace NS] [--replicas N]")
sys.exit(1)
image_name = sys.argv[1]
namespace = "default"
replicas = 3
# 简单参数解析
if "--namespace" in sys.argv:
idx = sys.argv.index("--namespace")
namespace = sys.argv[idx + 1]
if "--replicas" in sys.argv:
idx = sys.argv.index("--replicas")
replicas = int(sys.argv[idx + 1])
# 执行部署
deployer = K8sDeployer(image_name, namespace)
image_tag = deployer.build_image()
if image_tag:
deployer.push_to_registry(image_tag)
deployer.deploy_to_k8s(image_tag, replicas)
deployer.verify_deployment()
print("\n部署流程完成!")
if __name__ == "__main__":
main()
5.5 调试与常见避坑指南
坑 1:Docker 构建慢
# 优化:使用构建缓存
# 正确:把不变的内容放前面,变化的放后面
FROM python:3.11-slim
COPY requirements.txt .
RUN pip install -r requirements.txt # 变化少,放前面
COPY . . # 变化多,放后面
# 优化:使用.dockerignore
echo "node_modules\n.git\n__pycache__" > .dockerignore
坑 2:K8s Pod 一直 Pending
# 查看原因
kubectl describe pod <pod-name>
# 常见原因:资源不足、调度失败、镜像拉取失败
# 检查节点资源
kubectl describe nodes
坑 3:Docker 镜像拉取失败
# 配置私有仓库认证
import docker
client = docker.from_env()
client.login(
registry="registry.example.com",
username="user",
password="password"
)
坑 4:K8s 访问被拒绝
# 检查kubeconfig
kubectl config view
kubectl config current-context
# 切换上下文
kubectl config use-context <context-name>
六、常用场景列举
6.1 Python 微服务容器化与部署
项目结构:
my-microservice/
├── Dockerfile
├── docker-compose.yml
├── requirements.txt
├── app/
│ ├── __init__.py
│ ├── main.py
│ ├── config.py
│ └── models/
└── k8s/
├── deployment.yaml
├── service.yaml
└── ingress.yaml
Dockerfile:
FROM python:3.11-slim
WORKDIR /app
# 先复制依赖文件,利用构建缓存
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 再复制代码
COPY . .
# 非root用户运行
RUN useradd -m appuser && chown -R appuser:appuser /app
USER appuser
EXPOSE 8080
CMD ["python", "app/main.py"]
Kubernetes Deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-microservice
labels:
app: my-microservice
spec:
replicas: 3
selector:
matchLabels:
app: my-microservice
template:
metadata:
labels:
app: my-microservice
spec:
containers:
- name: my-microservice
image: my-registry.com/my-microservice:v1.0.0
ports:
- containerPort: 8080
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: my-secrets
key: database-url
resources:
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
6.2 CI/CD 自动化流水线
GitLab CI 示例:
stages:
- build
- test
- deploy
variables:
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
build:
stage: build
image: docker:latest
services:
- docker:dind
script:
- docker build -t $IMAGE_TAG .
- docker push $IMAGE_TAG
test:
stage: test
image: $IMAGE_TAG
script:
- pytest tests/
deploy:
stage: deploy
image: bitnami/kubectl:latest
script:
- kubectl set image deployment/my-app app=$IMAGE_TAG
- kubectl rollout status deployment/my-app
only:
- main
6.3 微服务架构多服务编排
docker-compose.yml:
version: '3.8'
services:
web:
build: .
ports:
- "8000:8000"
depends_on:
- api
- redis
environment:
- API_URL=http://api:8001
- REDIS_URL=redis://redis:6379
api:
build: ./api
ports:
- "8001:8001"
depends_on:
- db
environment:
- DATABASE_URL=postgresql://postgres:password@db:5432/mydb
worker:
build: ./worker
depends_on:
- db
- redis
redis:
image: redis:7-alpine
ports:
- "6379:6379"
db:
image: postgres:15-alpine
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
- POSTGRES_PASSWORD=password
- POSTGRES_DB=mydb
volumes:
postgres_data:
6.4 企业级多环境管理
开发环境:
apiVersion: v1
kind: Namespace
metadata:
name: dev
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
namespace: dev
spec:
replicas: 1
# 开发者需要快速迭代,单副本即可
生产环境:
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
namespace: prod
spec:
replicas: 10
# 生产环境高可用,多副本
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
使用 Kustomize 管理差异:
# base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
# overlays/dev/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: dev
bases:
- ../../base
patches:
- patch.yaml
6.5 容器化监控与可观测性
部署 Prometheus + Grafana:
apiVersion: v1
kind: ConfigMap
metadata:
name: prometheus-config
data:
prometheus.yml: |
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'kubernetes-pods'
kubernetes_sd_configs:
- role: pod
查看资源使用:
# kubectl top查看资源使用
kubectl top nodes
kubectl top pods
# 查看Pod日志
kubectl logs -f deployment/myapp --previous
6.6 蓝绿部署与金丝雀发布
蓝绿部署:
# 新版本Service
apiVersion: v1
kind: Service
metadata:
name: myapp-bluegreen
spec:
selector:
version: v2 # 切换这个标签即可切换流量
---
# v1 Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-v1
spec:
replicas: 3
template:
metadata:
labels:
app: myapp
version: v1
---
# v2 Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-v2
spec:
replicas: 3
template:
metadata:
labels:
app: myapp
version: v2
金丝雀发布(Istio):
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: myapp
spec:
hosts:
- myapp
http:
- route:
- destination:
host: myapp
subset: v1
weight: 90
- destination:
host: myapp
subset: v2
weight: 10 # 只有10%流量到新版本
七、专业解释 + 大白话 + 生活案例
7.1 Docker 分层镜像与联合文件系统
专业解释:
Docker 镜像采用分层(Layer)结构,每一层代表 Dockerfile 中的一条指令。当容器启动时,所有层通过联合文件系统(如 OverlayFS)叠加挂载,形成统一的文件系统视图。
关键特性:
- 写时复制(Copy-on-Write):容器修改某文件时,只在该容器层复制一份,其他容器不受影响
- 层共享:多个镜像可以共享相同的底层层,节省存储空间
- 增量更新:只需传输变更的层
大白话版:
想象你是一个美食博主,你的"红烧肉菜谱"被拆成了多个层级:
- 第 1 层:基础调料(盐、酱油)
- 第 2 层:辅料(葱姜蒜)
- 第 3 层:主料(猪肉)
- 第 4 层:烹饪方法
现在有两个人要学做红烧肉:
- A 学了 1-3 层,直接上手
- B 学了全部 4 层,做得更精细
Docker 的做法是:你们各自只需要保存"自己独有的那层",公共层大家共享。就像 A 只需要保存"红烧肉"三个字,而 B 需要保存完整的菜谱。
生活案例:
- 你的手机系统更新:更新只下载变化的部分,不是整个系统重装
- 程序员写代码:每个人只修改自己负责的模块,然后合并到主分支
性能影响:
# 优化:把变化频繁的放在最后
FROM python:3.11-slim
# 变化少,放前面(利用缓存)
COPY requirements.txt .
RUN pip install -r requirements.txt
# 变化多,放后面
COPY app/ .
# 变化最频繁的放最后
CMD ["python", "app/main.py"]
7.2 CRI 与容器运行时架构演变
专业解释:
CRI(Container Runtime Interface)是 Kubernetes 定义的容器运行时接口标准,让 Kubelet 能够支持多种容器运行时。CRI 通过 gRPC 与运行时通信,主要包括 RuntimeService 和 ImageService。
架构演变:
| 阶段 | 架构 | K8s 版本 |
|---|---|---|
| 早期 | kubelet -> dockershim -> dockerd -> containerd -> runc | 1.24 之前 |
| 过渡 | kubelet -> dockershim -> containerd -> runc | 1.24-1.26 |
| 现在 | kubelet -> CRI -> containerd -> runc | 1.27+ |
为什么弃用 dockershim?
dockershim 是一段"临时补丁"代码,因为 Docker 比 K8s 早出现,K8s 不得不写代码去适配 Docker。后来 containerd 原生支持 CRI 后,这段"适配代码"就成了技术债务,维护成本高、额外开销大,所以被移除了。
大白话版:
想象你要给手机充电:
- 旧方案:苹果充电线 -> 安卓转换头 -> 手机(多了一层转换,效率低)
- 新方案:Type-C 充电线 -> 手机(直接对接,效率高)
dockershim 就是那个"安卓转换头",虽然能用,但没必要。
生活案例:
- 早期 USB 接口混乱:各种转接头满天飞
- 现在 USB-C 一统天下:统一接口,直接插就能用
7.3 Kubernetes 控制循环与声明式状态管理
专业解释:
Kubernetes 采用"控制循环"(Control Loop)模式实现声明式状态管理。每个控制器持续监控当前状态(Status)与期望状态(Spec)的差异,并通过调谐(Reconcile)使当前状态趋近期望状态。
期望状态 (Spec) -> 控制器 -> 当前状态 (Status) -> 比较 -> 调谐 -> 期望状态 (Spec)
核心控制器:
- Deployment Controller:管理 Pod 副本数,确保期望的 Pod 数量运行
- ReplicaSet Controller:确保 Pod 副本数与期望一致
- Node Controller:监控节点状态,标记不可用节点
- Service Controller:管理 Service 的 Endpoints
大白话版:
想象你是外卖店长,你告诉店员:
"我要保持店里始终有 3 个厨师在岗。"
这就是"声明式"——你只说期望,不说具体怎么做。
店员(Controller)会:
- 实时检查当前有几个厨师在岗
- 发现少了一个,立刻招聘新人
- 发现多了一个,考虑是否裁员
你不需要亲自盯着每一个厨师,店员会帮你维护"3 个厨师"的期望状态。
生活案例:
- 空调自动控温:你设定 26 度,空调自动开关,这就是控制循环
- 电饭煲保温:低于温度就加热,高于就停止,典型的控制循环
Python 模拟 K8s 控制循环:
import time
from dataclasses import dataclass
@dataclass
class DeploymentSpec:
replicas: int
@dataclass
class DeploymentStatus:
current_replicas: int = 0
class DeploymentController:
"""模拟K8s的Deployment控制器"""
def __init__(self, spec: DeploymentSpec):
self.spec = spec
self.status = DeploymentStatus()
def reconcile(self):
"""调谐:使当前状态匹配期望状态"""
diff = self.spec.replicas - self.status.current_replicas
if diff > 0:
print(f"需要创建 {diff} 个Pod")
self.status.current_replicas += diff
elif diff < 0:
print(f"需要删除 {-diff} 个Pod")
self.status.current_replicas += diff
else:
print("Pod数量已达标,无需调整")
def get_status(self):
return f"期望: {self.spec.replicas}, 当前: {self.status.current_replicas}"
# 模拟使用
controller = DeploymentController(spec=DeploymentSpec(replicas=5))
while True:
print(controller.get_status())
controller.reconcile()
time.sleep(2)
# 模拟外部修改(如人工扩容)
if controller.status.current_replicas > 0 and input("模拟Pod崩溃? (y/n): ").lower() == 'y':
controller.status.current_replicas -= 1
print("一个Pod崩溃了!")
八、面试官高频面试题
面试题 1:Docker 和虚拟机的核心区别是什么?
参考答案:
| 维度 | Docker 容器 | 虚拟机 |
|---|---|---|
| 架构 | 共享宿主机内核,进程级隔离 | 独立操作系统,硬件级隔离 |
| 启动速度 | 秒级 | 分钟级 |
| 资源占用 | MB 级别 | GB 级别 |
| 性能 | 几乎无损耗 | 有虚拟化开销(5-10%) |
| 密度 | 单机可运行上百个 | 通常几个到十几个 |
加分回答:容器不是"更轻量的虚拟机",它们解决的是不同问题。容器提供应用级封装,虚拟机提供系统级隔离。在安全要求高的场景(如多租户),虚拟机仍是首选。
面试题 2:Kubernetes 和 Docker 是什么关系?
参考答案:
Docker 和 Kubernetes 不是竞争关系,而是协作关系:
- Docker:负责"打包"——将应用和依赖封装成容器镜像
- Kubernetes:负责"编排"——管理这些容器的部署、扩缩容、故障恢复等
Docker 是容器运行时之一,Kubernetes 通过 CRI 接口来调用容器运行时。
常见误区:很多人认为"Kubernetes 要取代 Docker",实际上 K8s 取代的是"用 Docker 命令手动管理容器"的方式,而不是 Docker 镜像格式本身。
面试题 3:Kubernetes 弃用 Docker 是哪个版本?具体表现是什么?
参考答案:
- Kubernetes 1.20(2020 年 12 月):开始弃用 dockershim 警告
- Kubernetes 1.24(2022 年 5 月):正式移除 dockershim
具体表现:kubelet 不再直接调用 dockershim,而是通过 CRI 接口直接调用 containerd。
架构变化:
- 旧:
kubelet -> dockershim -> dockerd -> containerd -> runc - 新:
kubelet -> CRI -> containerd -> runc
面试题 4:为什么要弃用 Docker?
参考答案:
- 架构简化:移除 dockershim 这一层,减少维护负担和性能开销
- 统一接口:通过 CRI 标准化容器运行时接口,支持更多选择(containerd、CRI-O)
- 更少依赖:减少对 Docker 特定 API 的依赖,降低耦合度
- 性能提升:直接调用 containerd,减少一层转发
注意:Docker 镜像仍然可用,因为它们遵循 OCI 标准。
面试题 5:Docker Compose 和 Kubernetes 的区别是什么?
参考答案:
| 维度 | Docker Compose | Kubernetes |
|---|---|---|
| 范围 | 单机 | 集群 |
| 复杂度 | 简单 | 复杂 |
| 功能 | 定义多容器应用 | 全面的容器编排平台 |
| 扩缩容 | 手动 | 自动 |
| 适用场景 | 开发、测试 | 生产环境 |
进阶回答:Docker Compose 适合本地开发和测试,Kubernetes 适合大规模生产环境。两者解决的问题维度不同,不是替代关系。
面试题 6:Pod 和 Container 是什么关系?
参考答案:
核心概念:
- Container(容器):镜像的运行实例,是操作系统级虚拟化的基本单位
- Pod(容器组):K8s 的最小调度单位,一个 Pod 包含一个或多个共享网络的容器
关系解释:
- 一个 Pod 里的容器共享同一个 Linux 命名空间(网络、进程间通信、文件系统)
- Pod 像是一个"逻辑主机",Container 是里面的"进程"
- 通常一个 Pod 只包含一个容器(单容器 Pod),但也支持多容器 Pod(Sidecar 模式)
大白话:Pod 是"盒子",Container 是盒子里的"物品"。一个盒子可以装多个物品,这些物品共享盒子的空间。
面试题 7:Deployment 和 StatefulSet 的区别是什么?
参考答案:
| 维度 | Deployment | StatefulSet |
|---|---|---|
| 用途 | 无状态应用 | 有状态应用 |
| Pod 标识 | 随机后缀 | 固定序号 |
| 存储 | 共享存储 | 独立持久存储 |
| 扩缩容 | 随意 | 按序号顺序 |
| 场景 | Web 服务、API | 数据库、消息队列 |
典型使用:
- Deployment:Nginx、Python Flask API、前端应用
- StatefulSet:MySQL、MongoDB、Kafka、Zookeeper
核心区别:Deployment 创建的 Pod 可以互换(谁挂了换谁),StatefulSet 创建的 Pod 有固定身份(有持久存储和网络标识)。
面试题 8:如何用 Python 代码管理 Kubernetes?
参考答案:
使用官方的 Kubernetes Python 客户端:
from kubernetes import client, config
# 加载配置
config.load_kube_config() # 本地开发
# config.load_incluster_config() # Pod内运行
# 创建API实例
api = client.CoreV1Api()
apps_api = client.AppsV1Api()
# 列出Pod
pods = api.list_namespaced_pod(namespace="default")
for pod in pods.items:
print(f"{pod.metadata.name}: {pod.status.phase}")
# 创建Deployment
deployment = client.V1Deployment(...)
apps_api.create_namespaced_deployment(namespace="default", body=deployment)
# 扩缩容
apps_api.patch_namespaced_deployment_scale(
name="myapp",
namespace="default",
body={"spec": {"replicas": 10}}
)
加分回答:生产环境中,通常配合 Kubernetes 的 Watch 机制实现事件驱动,或使用 Operator 模式封装复杂逻辑。
九、企业级实战指导
9.1 技术选型决策树
应用场景评估
|
v
是否有强烈的隔离需求?
|
是 -> 选择虚拟机(安全优先场景)
|
否 -> 是否需要大规模编排?
|
是 -> 容器数量 > 100?
|
是 -> 选择Kubernetes
|
否 -> 选择Docker Swarm / Docker Compose
|
否 -> 是否需要快速部署?
|
是 -> 选择Docker
|
否 -> 选择传统部署
2026 年选型建议:
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 本地开发 | Docker Compose | 简单、快速 |
| 小团队(<10 人) | Docker Swarm + Portainer | 简单、可视化 |
| 中型团队 | Kubernetes(托管版) | 省运维、自动扩缩容 |
| 大型企业 | Kubernetes(自建/多集群) | 完全可控、安全合规 |
| 边缘计算 | K3s / CRI-O | 轻量、低资源占用 |
| 安全敏感 | Kata Containers + K8s | 硬件级隔离 |
9.2 Kubernetes 部署架构最佳实践
高可用集群架构:
┌─────────────────┐
│ 负载均衡器 │
└────────┬────────┘
│
┌────────────────────┼────────────────────┐
│ │ │
┌────▼────┐ ┌────▼────┐ ┌────▼────┐
│ Master │ │ Master │ │ Master │
│ Node 1 │◄────────►│ Node 2 │◄────────►│ Node 3 │
└────┬────┘ └────┬────┘ └────┬────┘
│ │ │
└────────────────────┼────────────────────┘
│ etcd集群
┌────────────────────┼────────────────────┐
│ │ │
┌────▼────┐ ┌────▼────┐ ┌────▼────┐
│ Worker │ │ Worker │ │ Worker │
│ Node 1 │ │ Node 2 │ │ Node 3 │
└─────────┘ └─────────┘ └─────────┘
命名空间隔离策略:
apiVersion: v1
kind: Namespace
metadata:
name: prod
labels:
env: production
---
apiVersion: v1
kind: ResourceQuota
metadata:
name: prod-quota
namespace: prod
spec:
hard:
requests.cpu: "20"
requests.memory: 40Gi
pods: "100"
services: "20"
---
apiVersion: v1
kind: LimitRange
metadata:
name: prod-limits
namespace: prod
spec:
limits:
- max:
cpu: "4"
memory: 8Gi
min:
cpu: "50m"
memory: 64Mi
default:
cpu: "500m"
memory: 512Mi
defaultRequest:
cpu: "100m"
memory: 128Mi
type: Container
9.3 企业级容器化四步落地法
第一步:评估与规划(1-2 周)
1. 应用分类
- 无状态应用:优先迁移(Web服务、API)
- 有状态应用:评估复杂度(数据库、缓存)
- 遗留系统:考虑重构或虚拟化
2. 依赖梳理
- 列出所有依赖(系统包、环境变量、配置文件)
- 识别外部服务(数据库、消息队列、第三方API)
- 评估网络需求(服务间通信、端口暴露)
3. 团队能力评估
- Docker基础
- K8s基础
- CI/CD经验
第二步:试点迁移(2-4 周)
# 试点项目:选择一个低风险应用
FROM python:3.11-slim
# 最小化依赖
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 非root运行
RUN useradd -m appuser
COPY --chown=appuser:appuser . .
USER appuser
# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8000/health || exit 1
EXPOSE 8000
CMD ["python", "main.py"]
第三步:生产就绪(2-4 周)
# 生产配置清单
apiVersion: apps/v1
kind: Deployment
metadata:
name: production-app
spec:
replicas: 5 # 高可用
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 25%
maxUnavailable: 0 # 零停机更新
template:
spec:
affinity: # 反亲和,避免单点
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- production-app
topologyKey: kubernetes.io/hostname
containers:
- name: app
resources:
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
readinessProbe:
httpGet:
path: /ready
port: 8000
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 15
periodSeconds: 20
第四步:规模化运营(持续)
# 监控指标
kubectl get --raw /apis/metrics.k8s.io/v1beta1/nodes | jq '.items[].usage'
# 成本优化
kubectl top pods --sort-by=memory
# 资源审计
kubectl describe resourcequota
9.4 生产环境避坑指南
坑 1:镜像拉取超时
# 错误配置
image: myregistry.com/myapp:latest # 网络不稳定时会卡死
# 正确配置
imagePullSecrets + 超时配置
imagePullPolicy: IfNotPresent # 本地已有就不拉取
坑 2:内存泄漏未限制
# 危险:无资源限制
containers:
- name: app
image: app:v1
# 安全:设置资源限制
containers:
- name: app
image: app:v1
resources:
limits:
memory: "512Mi" # 防止内存泄漏拖垮节点
requests:
memory: "128Mi" # 调度依据
坑 3:健康检查缺失
# 没有健康检查:Pod挂了不知道
containers:
- name: app
image: app:v1
# 有健康检查:自动发现、自动重启
containers:
- name: app
image: app:v1
livenessProbe:
httpGet:
path: /health
port: 8080
failureThreshold: 3
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
坑 4:滚动更新配置不当
# 危险配置
strategy:
type: Recreate # 会导致服务中断
# 安全配置
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # 最多超出1个Pod
maxUnavailable: 0 # 确保服务始终可用
9.5 2026 年前沿趋势与未来展望
趋势 1:Serverless 容器
- Knative:在 K8s 上运行 Serverless 工作负载
- AWS Fargate:无需管理服务器
- 阿里云 ASK:Serverless Kubernetes
趋势 2:智能扩缩容
- KEDA:基于事件的自动扩缩容
- Horizontal Pod Autoscaler V2:支持自定义指标
- Vertical Pod Autoscaler:自动调整资源请求
趋势 3:安全加固
- Sigstore:软件签名和透明日志
- Kyverno:K8s 原生策略引擎
- Falco:运行时安全检测
趋势 4:多集群管理
- Fleet:大规模 K8s 管理
- Cluster API:声明式集群管理
- GitOps:以 Git 为中心的运维模式(ArgoCD、Flux)
趋势 5:AI/ML 工作负载支持
- Kubeflow:ML 工作流平台
- GPU 调度:NVIDIA GPU Operator
- 分布式训练:PyTorch Elastic
总结
好啦,今天这篇文章就到这里。我们来回顾一下核心知识点:
- Docker:负责打包,是"集装箱工厂"
- Kubernetes:负责编排,是"物流调度中心"
- 关系:Docker 打包,K8s 调度,是最佳拍档
- 弃用真相:K8s 移除了 dockershim,但 Docker 镜像仍可用(OCI 标准)
- 选择建议:小规模用 Docker/Swarm,大规模用 K8s
记住,学习容器技术不是为了应付面试,而是为了真正理解现代云原生应用是如何构建和运行的。希望这篇文章能帮你在容器化的道路上走得更稳、更远
参考链接
- Kubernetes 官方文档:kubernetes.io/zh-cn/docs/
- Docker 官方文档:docs.docker.com/
- CNCF 云原生定义:glossary.cncf.io/
- OCI 开放容器标准:opencontainers.org/
- 阿里云 ACK:help.aliyun.com/document_de…
转载声明:
本文系原创文章,遵循 CC BY-SA 4.0 协议,转载需注明出处。
欢迎各位开发者朋友转发分享,如需在其他平台转载,请私信联系作者获取授权。