上周有个朋友在群里问了一句:“CentOS 7 上到底能不能顺利把 k8s 装起来?我按网上的教程折腾了两天,不是这里报错就是那里起不来。”这个问题其实特别典型,我这些年帮别人排查 Kubernetes 安装问题,遇到最多的就是 CentOS 7 环境。你说它老吧,生产环境里还有一大堆跑着;你说它新吧,很多新版本的 kubeadm 和容器运行时在 7 上确实会遇到各种兼容性坑。
所以今天这篇实操记录,就是专门针对 CentOS 7 安装 Kubernetes 集群的完整过程。我会把每一步的关键原理讲清楚,为什么这么做、不这么做会踩什么坑,全部摊开来说。无论你是刚开始学 k8s 编排的新手,还是要给公司搭内部测试环境的老手,这篇文章都能让你少走很多弯路。毕竟 k8s 集群搭建不是照抄命令就行,底层很多概念搞清楚了,后面上了生产才不慌。
1. 装之前必须想清楚的事
1.1 为什么 CentOS 7 仍然值得用
我知道很多人会说 CentOS 7 已经停止维护了,为什么还要写它。但现实是,目前大量服务器、虚拟机模板、物理机还是 CentOS 7.9 的系统,尤其是企业内部的老资产,短期内根本换不掉。另外 CentOS 7 的 glibc 版本是 2.17,很多软件在这上面编译和运行都没问题,社区里大量教程也都是基于 7 的。
关键点在于:Kubernetes 官方在 v1.27 版本之前,依然为 CentOS 7 / RHEL 7 提供完善的 RPM 包支持。这意味你完全可以用 yum 安装 kubeadm、kubelet、kubectl,不需要从源码编译。哪怕是已经发布的 v1.28、v1.29,也仍然可以使用,只要容器运行时和内核参数满足要求。
不过要提前说清楚,CentOS 7 默认的内核是 3.10.0,这个版本跑高版本 Kubernetes 确实有些老。如果你的场景对网络性能、存储性能要求比较高,装好集群后最好考虑升级内核到 5.4 或 5.15 LTS 版本,或者干脆用 Rocky Linux 9 / Ubuntu 22.04。但如果只是学习、测试、跑些轻量应用,默认内核完全够用。
1.2 k8s 和 Docker 到底是什么关系
很多刚接触的人会把 Kubernetes 和 Docker 搞混,觉得装了 Docker 就等于有了容器平台。这里先说清楚:Docker 只是容器运行时的一种实现,负责把容器跑起来;Kubernetes 是容器编排平台,负责管理一大堆容器——它们之间是管理和被管理的关系。
具体到部署架构上:kubelet 需要调用容器运行时来创建、停止容器,早期的 kubelet 直接通过 Docker 的 API 来工作。后来 Kubernetes 推出了 CRI(Container Runtime Interface),kubelet 只认 CRI 接口。Docker 自己实现了 CRI 的适配层 dockershim,但在 v1.24 版本之后,dockershim 被正式移除了。所以你如果现在装新版本的 k8s,继续用 Docker 做运行时就需要额外装一个 cri-dockerd 来兼容,或者干脆用 containerd。
这里我建议直接用 containerd,理由后面会详细讲。简单说就是:更少的中间层、更稳定的兼容性、更少的资源占用。
1.3 三种部署方式怎么选
Kubernetes 的部署方式主要就三种:Minikube、kubeadm、二进制部署。
- Minikube 适合单机学习,它会在你的电脑上启动一个虚拟机或者容器来模拟集群。优点是快、简单,但生产环境用不上。
- kubeadm 是官方推荐的集群部署工具,也是本文采用的方式。它在每个节点上通过 RPM 包安装必要的组件,然后用一条命令初始化控制平面。优点是标准化、可重复性强、证书和配置文件都是自动生成的。
- 二进制部署 是完全手动下载各个组件二进制文件,自己用 systemd 管理进程。这种方式最透明,最可控,但步骤繁琐,配置文件要自己写,适合对 Kubernetes 架构有深度理解的人。
如果你是第一次装 k8s,我建议直接学 kubeadm 方式。因为这套流程是官方维护的,出错概率比二进制手动部署低很多,而且装完的集群结构清晰,后面想升级、扩容都有现成的指令。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备与前置检查
2.1 最小化安装后的主机规划
这里假设你有至少两台 CentOS 7.9 的机器(虚拟机也行),一台作为控制平面节点,一台作为工作节点。如果你只有一台机器,也可以把控制平面和工作节点合在一起,但至少需要 2 核 CPU 和 2GB 内存,否则后面启动系统组件会非常吃力。
我先说下这次安装时用的主机规划,方便你对照操作。
| 主机名 | IP 地址 | 角色 | 配置 |
|---|---|---|---|
| k8s-master | 192.168.56.101 | 控制平面节点 | 4 核 / 4GB |
| k8s-node01 | 192.168.56.102 | 工作节点 | 4 核 / 4GB |
注意一点:规划时尽量不要让控制平面和工作节点使用同一个 IP,否则后面安装网络插件、查看节点状态的时候会出现很多无关干扰。所有节点的时区、时间要同步,这个千万别忽略。k8s 集群对时间偏移很敏感,如果节点间时间差超过 5 秒,证书校验就会失败,kubelet 会一直报错。
2.2 节点初始化必做项
拿到一台新装的 CentOS 7,不要急着一上来就装 k8s 组件,先完成下面这些环境初始化,每一步都写好理由,免得你踩坑以后才知道为什么。
第一,关闭防火墙。CentOS 7 默认开的是 firewalld,如果你不关,kube-proxy 的 NodePort 模式、Calico 的 BGP 通信都会受到干扰。虽然可以在防火墙里放行端口,但我实际操作下来的经验是:测试环境直接关掉最省心。生产环境另说,需要按最小化原则放行端口。
bash复制systemctl stop firewalld
systemctl disable firewalld
第二,关闭 SELinux。这个不关,系统会连挂载目录都搞不定,容器创建时总会出现权限相关的诡异报错。
bash复制setenforce 0
sed -i 's/^SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
第三,关闭 swap。Kubernetes 的设计假设是节点上不启用 swap,因为 kubelet 的资源管理模型压根没考虑 swap 的存在。如果开着 swap,kubelet 会直接拒绝启动。
bash复制swapoff -a
sed -i '/ swap / s/^#?/#/' /etc/fstab
注意检查 /etc/fstab,把 swap 那一行注释掉,否则重启以后系统又自动挂载 swap,集群直接崩。这种问题我碰到过不止一次,都是当时装完没看 fstab,结果重启直接 NotReady。
第四,加载内核模块。Kubernetes 网络要用到 overlay 和 br_netfilter 这两个模块,让 iptables 能看见网桥流量。
bash复制cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
modprobe overlay
modprobe br_netfilter
第五,调整内核参数。这一步决定 iptables 是否正确处理桥接流量,最好直接把配置写死在 sysctl 里。
bash复制cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF
sysctl --system
2.3 换源与基础软件安装
CentOS 7 默认的 yum 源现在很多都失效了,或者速度特别慢。装 k8s 之前建议先把系统源换到国内可用的镜像站,比如阿里云镜像源。这里提醒一句:不同镜像站的仓库结构可能有点差异,配置前看一眼官方说明。
bash复制mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.bak
curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
yum clean all && yum makecache
基础软件方面,建议先装 wget、vim、net-tools、lsof 这些日常排查工具,后面用得上。
bash复制yum install -y wget vim net-tools lsof
2.4 确认主机名与解析
如果网络环境有内部 DNS,最好把三台机器的主机名和 IP 都配置到 /etc/hosts,避免后面 kubeadm init 或者 join 的时候解析到 127.0.0.1,导致各种连接超时。
bash复制hostnamectl set-hostname k8s-master
echo "192.168.56.101 k8s-master
192.168.56.102 k8s-node01" >> /etc/hosts
这个操作虽然简单,但特别重要。之前遇到过用户用了默认的 localhost 主机名,结果 kubeadm init 初始化完以后,kubelet 一直无法找到 API Server,因为节点名解析乱了。
3. 容器运行时安装:containerd 是首选
3.1 为什么弃用 Docker、选 containerd
从 Kubernetes v1.24 开始,kubelet 就不再直接支持 Docker 的 dockershim 了。如果你想继续用 Docker Engine 来跑容器,就得额外装 cri-dockerd,通过它把 Docker API 翻译成 CRI。这个过程不是不行,但多了一个中间组件,就多了一个故障点,而且资源开销也大。
containerd 就是 Docker 背后的那个核心容器运行时,它本身就原生支持 CRI,kubelet 可以直接和它通信。用 containerd 的好处有三点:
- 少了一层翻译,稳定性更好,排查问题时链路短。
- 资源占用更少,没有 Docker Daemon 那种经常僵死的问题。
- 配置简洁,默认配置稍微改一改就能用。
所以我的建议是:别再纠结 Docker 和 k8s 的关系了,新集群统一用 containerd。你在 CentOS 7 上运行 docker 命令的经验完全不影响你对 k8s 的理解,kubectl 才是主要操作入口。
3.2 安装并配置 containerd
CentOS 7 的默认 yum 源里并没有 containerd 包,需要先添加 Docker 官方的 yum 源才拉得到。这个源里既包含 docker-ce,也包含 containerd.io。
bash复制yum install -y yum-utils
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
yum install -y containerd.io
装完以后生成默认配置文件。注意这里有个很关键的参数:SystemdCgroup。Kubernetes 要求容器运行时使用 systemd 作为 cgroup 驱动,这样 kubelet 和容器运行时在 cgroup 资源管理上保持一致。如果这里不改成 true,后面初始化集群时 kubelet 会和 containerd 的 cgroup 驱动不一致,节点状态会直接 NotReady。
bash复制mkdir -p /etc/containerd
containerd config default | tee /etc/containerd/config.toml
找一个支持流编辑器或者直接用 vim 编辑 /etc/containerd/config.toml,修改两个地方:
第一处是 SystemdCgroup。默认配置里它是 false,改成 true。
bash复制sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
第二处是 pause 镜像仓库地址。默认的 sandbox_image 指向的是 registry.k8s.io/pause:3.8,这个地址在国内网络环境下经常拉不动。需要把它改成能访问的镜像仓库地址。具体地址这里不做推荐,你可以根据自己的网络情况选择可用仓库。我在内网环境通常直接配一个本地私有仓库,里面提前放好 pause 镜像,这样最稳。
bash复制# 修改前
sandbox_image = "registry.k8s.io/pause:3.8"
# 修改后示例(按实际情况调整)
sandbox_image = "your-registry.example.com/pause:3.8"
改完以后启动 containerd,并设置开机自启。
bash复制systemctl daemon-reload
systemctl enable --now containerd
systemctl status containerd
3.3 容器运行时选择的坑
这里有个坑必须专门拿出来说。有人为了省事,直接去网上找那种一键脚本装 Docker,装完以后再用 cri-dockerd 让 kubelet 连 Docker。这种方案在 CentOS 7 上我也试过,确实能跑起来,但遇到高版本 Kubernetes 时,cri-dockerd 偶尔会有兼容性问题,而且创建容器时日志排查路径比 containerd 多一层,增加不少心智负担。
另外还有人会碰到这种情况:containerd 装了,配置也改了,但 kubelet 却报找不到 runc 或者 runc 版本不对。这时候直接把 runc 重装一下或者指定版本就好,原因是 Docker 源里的 runc 和系统自带的可能冲突。遇到这个报错不用慌,按常规思路检查和重装即可。
4. kubeadm 部署 Kubernetes 集群
4.1 安装 kubeadm、kubelet、kubectl
现在环境已经干净了,开始装核心组件。kubeadm 是集群初始化工具,kubelet 是每个节点上运行的守护进程,kubectl 是你和集群交互的命令行工具。
先配置 Kubernetes 的 yum 源。这里同样建议用国内可访问的镜像源,配置方式就是在 /etc/yum.repos.d/kubernetes.repo 写入仓库地址。
bash复制cat <<EOF | sudo tee /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
EOF
安装时我建议指定版本,不要直接安装最新版,因为 Kubernetes 小版本更新很快,很多插件和网络组件需要适配时间。以 v1.28.2 为例:
bash复制yum install -y kubelet-1.28.2 kubeadm-1.28.2 kubectl-1.28.2
systemctl enable --now kubelet
这里有个细节:刚装完 kubelet 后你可能发现它的状态是 activating(自动重启),这其实是正常的,因为此时还没有初始化集群,kubelet 没有配置文件。等 kubeadm init 完成后,它会自动稳定下来。
4.2 初始化控制平面节点
一切准备就绪,现在执行最关键的一步。在 master 节点上运行 kubeadm init。这里有几个参数要重点解释:
--apiserver-advertise-address:指定 API Server 监听的地址,就用 master 节点 IP。--pod-network-cidr:指定 Pod 网络的网段。这个网段不能和物理网络冲突。如果你后续用 Calico,可以设成 192.168.0.0/16;如果用 Flannel,一般是 10.244.0.0/16。--image-repository:指定拉取控制平面镜像的仓库地址。国内网络环境通常需要改成一个可访问的仓库地址。--kubernetes-version:指定安装版本,要和之前 yum 装的版本一致。
这次我以 Calico 网络插件为例,用 192.168.0.0/16 作为 Pod 网段:
bash复制kubeadm init \
--apiserver-advertise-address=192.168.56.101 \
--pod-network-cidr=192.168.0.0/16 \
--image-repository=registry.aliyuncs.com/google_containers \
--kubernetes-version=v1.28.2
这个命令执行过程大概会持续几分钟,它会依次拉取 kube-apiserver、kube-controller-manager、kube-scheduler、kube-proxy、pause 这几个控制平面组件的镜像,然后启动静态 Pod。如果网络顺利,最后看到类似下面这段输出,就表示初始化成功:
bash复制Your Kubernetes control-plane has initialized successfully!
To start using your cluster, you need to run the following as a regular user:
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
按提示执行那三条命令,把 kubeconfig 配置拷贝到当前用户下,这样 kubectl 才能连上集群。
bash复制mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
4.3 安装网络插件:Calico
kubeadm init 完以后,集群核心组件已经跑起来了,但此时节点状态是 NotReady,因为还没有安装 Pod 网络插件。网络插件负责给每个 Pod 分配 IP,并打通跨节点的容器通信。
我推荐用 Calico,因为它功能全面,支持 NetworkPolicy(网络策略)。在安全性和灵活性上比 Flannel 强不少。而且 Calico 的安装非常简单,就是应用一个 YAML 文件。
先下载 Calico 的部署清单:
bash复制curl -OL https://raw.githubusercontent.com/projectcalico/calico/v3.26.1/manifests/calico.yaml
kubectl apply -f calico.yaml
如果你下载不了 GitHub 上的文件,或者网速很慢,可以先在能访问的地方下载好再传到服务器上,或者直接配置镜像加速。这里不展开讲怎么加速,重点说一下安装完后验证:
bash复制kubectl get pods -n kube-system
等 calico-kube-controllers 和 calico-node 的 Pod 都变成 Running,再执行:
bash复制kubectl get nodes
这时候 master 节点的状态应该已经变成 Ready。如果还是 NotReady,大概率是 Calico 镜像拉不下来,或者网卡选择有问题。排查方法后面我单独列一个章节讲。
4.4 工作节点加入集群
控制平面准备好了,现在把 node01 加进来。在 kubeadm init 成功后的输出末尾,有一行以 kubeadm join 开头的命令,里面带着 token 和证书哈希。把它复制到 node01 上执行即可。
bash复制kubeadm join 192.168.56.101:6443 --token xxxx \
--discovery-token-ca-cert-hash sha256:xxxx
如果你的 token 已经过期了,或者输出没有保留,可以在 master 上重新生成:
bash复制kubeadm token create --print-join-command
node01 执行完 join 命令后,回到 master 上验证:
bash复制kubectl get nodes
正常情况下 node01 的状态过一会儿会变成 Ready。这个过程取决于节点拉取镜像的速度,可能等待一两分钟。
4.5 验证集群部署结果
集群初步起来了,最后做一个完整的健康检查。
bash复制kubectl get pods -n kube-system -o wide
kubectl get nodes -o wide
确认所有节点都是 Ready,所有核心组件都是 Running,再创建一个测试 deployment 验证调度:
bash复制kubectl create deployment nginx-test --image=nginx
kubectl expose deployment nginx-test --port=80 --type=NodePort
kubectl get svc nginx-test
访问 node IP 加 NodePort 端口,能看到 Nginx 默认页面,就说明整个集群从网络到调度、从 Service 到 Pod 都是正常的。
5. 常见问题与排查技巧实录
5.1 coredns CrashLoopBackOff 怎么办
集群装好后,kube-system 命名空间里的 coredns 经常进入 CrashLoopBackOff。很多人第一时间怀疑 DNS 坏了,但真正原因往往是网络插件没装好,或者 kubelet 的 cgroup 驱动和运行时不一致。
排查思路是先看 coredns 的日志:
bash复制kubectl logs -n kube-system <coredns-pod-name> --previous
如果日志里出现 Failed to create pod sandbox 或者和网络相关的错误,先去把 Calico 的状态查一遍:
bash复制kubectl get pods -n kube-system -l k8s-app=calico-node
kubectl logs -n kube-system <calico-pod-name> | tail -50
最让我印象深刻的是一个 client 的案例:Calico Pod 一直 CrashLoopBackOff,日志里报隧道创建失败,排查了半天发现是节点的网卡名是 ens33,而 Calico 默认找的是 eth0,导致 IP 识别错误。解决办法很简单,在 calico.yaml 里加一个环境变量指定网卡名:
yaml复制- name: IP_AUTODETECTION_METHOD
value: "interface=ens33"
这种问题如果你不仔细看日志,可能折腾一整天都找不到症结。
5.2 kubelet 无法启动:cgroup 驱动不一致
另一个高频问题就是 cgroup 驱动不一致。CentOS 7 本身使用 systemd 管理进程,kubelet 默认也是 systemd 驱动,但如果你装 containerd 时没有修改 SystemdCgroup = true,那么 containerd 会使用 cgroupfs。两个驱动不一致时,kubelet 会反复启动失败,日志里大概率会出现:
bash复制failed to run Kubelet: failed to validate kubelet flags:
the cgroup driver is not set to systemd
解决办法就是在 /etc/containerd/config.toml 里确认 SystemdCgroup = true,然后重启 containerd:
bash复制systemctl restart containerd
systemctl restart kubelet
这问题完全可以通过初始化前检查避免。我一般在做环境准备时就会把 cgroup 驱动这个点写进检查清单,因为每次踩坑都是在这个细节上。
5.3 Node NotReady 排查三步法
节点状态一直是 NotReady,这是最让人头疼的。我现在给了一套固定排查流程,你们可以直接照做。
第一步,查看节点状态:
bash复制kubectl describe node <node-name> | tail -30
重点看 Conditions 部分,KubeletReady 是否为 False,以及下面写的 Reason 是什么。
第二步,查看 kubelet 日志:
bash复制journalctl -u kubelet -f
如果 kubelet 日志出现连接 API Server 失败,优先检查 hosts 解析、防火墙、API Server 的 6443 端口是否连通。
第三步,查看容器运行时状态:
bash复制ctr version
crictl ps -a
如果 crictl 连不上 containerd,看看 /etc/crictl.yaml 里的 endpoint 路径对不对。CentOS 7 上 containerd 的 socket 一般是 /run/containerd/containerd.sock。
这套流程我用了很久,大部分 NotReady 都逃不出这三步。
5.4 证书过期问题提前预防
集群跑久了会有人来问,kubeadm 证书过期了怎么办?其实 kubeadm 生成的证书有效期默认是一年,控制平面的证书可以通过 kubeadm certs renew all 续期,再重启相关组件。不过更好的方式是给集群配置定期自动续期,或者干脆遵循官方推荐:一年升级一次集群版本,升级本身就会刷新证书。
当然,对于测试环境,如果证书过期导致 API Server 都起不来,可以用:
bash复制kubeadm certs renew all
systemctl restart kubelet
然后更新本地 kubeconfig:
bash复制cp /etc/kubernetes/admin.conf ~/.kube/config
这个问题在生产环境特别重要,建议所有用 kubeadm 搭集群的人都提前把 90 天内到期提醒加上。
6. 常用命令与日常运维心得
6.1 高频 kubectl 命令速查
集群搭好后,日常维护主要靠 kubectl。下面这些命令是我几乎每天都用的,建议收藏。
| 目的 | 命令 |
|---|---|
| 查看节点及状态 | kubectl get nodes -o wide |
| 查看所有命名空间的 Pod | kubectl get pods -A |
| 查看某个 Pod 的详细事件 | kubectl describe pod <pod-name> -n <namespace> |
| 查看某个 Pod 的日志 | kubectl logs -f <pod-name> -n <namespace> |
| 进入 Pod 内部执行命令 | kubectl exec -it <pod-name> -n <namespace> -- /bin/bash |
| 查看 Service 列表 | kubectl get svc -A |
| 查看配置映射 | kubectl get configmap -n <namespace> |
| 删除资源 | kubectl delete deployment <name> -n <namespace> |
有些初学者习惯用 docker ps 去看容器,但 k8s 环境里最标准的排查路径是 kubectl。如果确实需要看容器运行时层面的状态,用 crictl ps 更贴近实际。
6.2 日常健康检查的正确姿势
有了集群不代表什么都不用管。我建议每隔一段时间就做一次基础巡检,不用额外装工具,纯命令行就能完成。
先看节点是否全部 Ready:
bash复制kubectl get nodes
再看核心组件是否稳定:
bash复制kubectl get pods -n kube-system
看事件里有没有异常:
bash复制kubectl get events -A --sort-by=.lastTimestamp | tail -30
这三个命令组合起来,基本能覆盖 90% 的集群异常。如果某个 Pod 反复重启,再用 describe 和 logs 深入排查。
6.3 集群扩容、重置与升级建议
扩容节点时,只要你保留了 kubeadm join 命令,新节点只要完成第 2 章的环境准备和 containerd 安装,就可以直接 join。这也体现了 kubeadm 部署方式的可复制性优势。
如果装错了想重来,重置命令是:
bash复制kubeadm reset -f
rm -rf /etc/cni/net.d ~/.kube/config
然后在干净的环境里重新走一遍 init/join 流程。有几次我看到有人不执行 reset 直接再 init,结果残留配置互相干扰,出各种奇怪问题。重置命令虽然不起眼,但它存在的意义就是给你一次重新来过的机会。
升级方面,建议小版本升级为主。每次 kubeadm 升级都要先更新节点上的 kubeadm 和 kubelet 包,然后执行 kubeadm upgrade plan 看可升级版本和注意事项,再执行 kubeadm upgrade apply。这里建议在测试环境完整演练一遍再上生产,不要直接挑战跨多个大版本的升级。
我自己在实际操作中体会最深的一点是:k8s 安装卡住的时刻,90% 都不是 k8s 本身的问题,而是系统环境、镜像拉取、cgroup 驱动、网络配置这些外围环节。所以做环境初始化时千万别图快,每一步都确认清楚了再往下走。按照这套流程,CentOS 7 装 k8s 完全可以做到一次通过,尤其是把 containerd 的 SystemdCgroup 和镜像地址提前处理好,后面省下的时间绝对超乎你的想象。
