第 2 章
容器技术基础
第 1 章推过一遍"为什么会有容器",这一章往下钻一层:容器具体是怎么实现的、Docker 的架构长什么样、编排为什么会从 Docker 进化到 Kubernetes。
容器凭什么能又轻又快:Namespace 和 Cgroups
第 1 章说容器"共享宿主机内核,所以启动快、开销小",具体是靠 Linux 内核的两个机制实现的:
- Namespace(命名空间):负责"隔离"。让每个容器都觉得自己独占了一套资源——有自己的进程树(PID Namespace)、自己的网络栈(Network Namespace)、自己的文件系统挂载点(Mount Namespace)、自己的主机名(UTS Namespace)等,实际上大家都跑在同一个内核上,只是互相看不见。
- Cgroups(控制组):负责"限制"。规定每个容器最多能用多少 CPU、内存、磁盘 IO,防止一个容器把整台机器的资源吃光。
一句话总结:Namespace 让容器觉得自己独享世界,Cgroups 防止它真的把资源都占了。这两个是操作系统层面的原语,Docker 只是把它们封装成了好用的命令行工具。
Docker 的三大核心概念
Docker 围绕三个概念展开:
| 概念 | 是什么 |
|---|---|
| 镜像(Image) | 只读模板,包含应用运行所需的一切(代码、依赖、配置)。多层叠加而成,每一层对应 Dockerfile 里的一条指令,利用联合文件系统(OverlayFS)把多层叠成一个统一视图 |
| 容器(Container) | 镜像的运行实例,在只读镜像层之上加一层可写层,容器里的所有修改都发生在这层可写层,不影响原镜像 |
| 仓库(Registry) | 存放和分发镜像的地方,公共的有 Docker Hub,私有的阿里云对应产品是 ACR(容器镜像服务,第 3 章展开) |
镜像分层的好处是复用:如果两个镜像都基于同一个 ubuntu:22.04 基础层构建,本地只需要存一份基础层,上层各自的差异部分才会重复占用空间。docker pull 拉镜像的时候也是按层拉取,已有的层会跳过。
Docker 架构:谁在真正干活
Docker 是 C/S(客户端-服务端)架构:
- Docker Client:接收命令(
docker run、docker build……),把请求发给 Daemon - Docker Daemon(dockerd):真正干活的后台进程,负责构建镜像、运行容器、管理网络和存储,对外暴露 REST API
- 客户端和 Daemon 既可以在同一台机器上(最常见),也可以通过网络远程调用
底层的容器运行时早年是 Docker 自己基于 LXC(Linux Container)实现,后来演化出自己的 libcontainer。现在整个行业遵循 OCI(开放容器标准,Open Container Initiative),runc 是 OCI 标准的参考实现。不同厂商做的容器工具之所以能互相兼容,就是因为大家底层都对齐了同一份标准。
顺带一个常考的冷知识:Docker 是用 Go 语言写的。不止 Docker,云原生生态的核心项目(Kubernetes、etcd、Prometheus、containerd)几乎全是 Go。
Docker 基础操作
日常最常用的一批命令:
docker pull nginx:1.25 # 从仓库拉镜像
docker images # 查看本地已有的镜像
docker run -d -p 80:80 nginx # 后台启动一个容器,把容器 80 端口映射到宿主机 80
docker ps # 查看正在运行的容器
docker exec -it <容器ID> sh # 进到容器里执行命令,排查问题常用
docker logs <容器ID> # 看容器的输出日志
docker stop <容器ID> # 停止容器
docker rm <容器ID> # 删除已停止的容器
docker rmi <镜像ID> # 删除镜像
自己构建镜像要写 Dockerfile,一个最基础的例子:
FROM node:20-alpine # 基础镜像
WORKDIR /app # 设置工作目录
COPY . . # 把当前目录内容复制进镜像
RUN npm install # 构建时执行的命令
EXPOSE 3000 # 声明容器对外监听的端口
CMD ["node", "index.js"] # 容器启动时执行的命令
docker build -t myapp:1.0 . 会按 Dockerfile 里的指令顺序逐层构建出镜像。
容器编排技术为什么会崛起
单机上用 Docker 管几个容器没问题,但生产环境的容器数量往往是几百上千个,分布在几十上百台机器上,这时候单机 Docker 就不够用了,会遇到一串新问题:
- 这么多容器,该起在哪台机器上才能让资源分布均匀?
- 某台机器上的容器挂了,谁负责发现并重新拉起?
- 一个服务的多个容器实例分布在不同机器上,IP 还会变,别的服务怎么找到它?
- 流量涨了要扩容,跌了要缩容,谁来做这个决策和执行?
- 发布新版本的时候,怎么平滑替换旧容器而不中断服务?
这些问题的本质是"跨机器的容器生命周期管理",超出了单机工具能覆盖的范围,于是催生了一批容器编排系统:Docker 官方自带的 Swarm、Apache Mesos + Marathon,以及源自 Google 内部 Borg 系统经验、2014 年开源的 Kubernetes。2017 年前后,包括 Docker 官方在内的主流平台陆续把 Kubernetes 作为默认的编排引擎选项,Kubernetes 也成为 CNCF 首个毕业的项目——编排这条赛道基本尘埃落定。
Kubernetes 概念与架构
Kubernetes(简称 K8s)的核心理念延续了第 1 章提到的声明式 API:你描述"我要什么样的最终状态"(比如"这个服务要保持 3 个副本在运行"),K8s 自己想办法达成,并且持续监控、一旦偏离就自动纠正。
整个集群分成两部分:
控制平面(Master)——负责做决策:
- API Server:整个集群唯一的入口,所有组件之间的交互都要经过它,不允许绕过
- etcd:分布式键值存储,保存集群里所有对象的状态数据(可以理解成整个集群的"数据库")
- Scheduler:决定新创建的 Pod 该调度到哪个 Node 上运行,会综合考虑资源余量、亲和性规则等
- Controller Manager:跑着一堆控制器,每个控制器都在持续对比"期望状态"和"实际状态",有差异就想办法纠正——这就是声明式 API 能自愈的根本原因
工作节点(Node)——负责真正跑容器:
- kubelet:常驻在每个 Node 上,和 Master 通信,负责本节点上 Pod 的整个生命周期
- kube-proxy:维护网络规则,让 Service 能把流量正确转发、负载均衡到后端的 Pod 上
- 容器运行时:真正拉镜像、起容器的组件(如 containerd),遵循前面提到的 OCI 标准
有一个常考的细节:Kubernetes 调度的最小单位不是容器,是 Pod——一个 Pod 里可以放一个或多个容器,这些容器共享同一个网络命名空间(同一个 IP)和存储卷。围绕 Pod,还有几个核心对象概念上要认识:Deployment(管理一组 Pod 副本,负责滚动升级和版本回滚)、Service(给一组变化中的 Pod 提供一个稳定不变的访问入口,并做负载均衡)、Namespace(在同一个集群里做逻辑隔离,划分不同项目或环境)。这几个对象具体怎么用 kubectl 操作、YAML 怎么写,留到 CKA 那个合集里深挖。
Kubernetes 的工作流程和典型场景
一次典型的部署流程:
- 用户写一份 YAML,声明"我要什么"(比如:镜像是什么、副本数是几个),提交给 API Server
- API Server 校验请求,把最终状态写入 etcd
- Scheduler 发现有新的 Pod 还没被分配 Node,根据资源情况挑一个合适的 Node
- 该 Node 上的 kubelet 收到指令,拉镜像、起容器
- Controller Manager 持续监控,一旦实际运行的 Pod 数量跟期望不符(比如某个 Pod 崩了),自动重建
- kube-proxy 配置好网络规则,让 Service 能把请求正确转发到健康的 Pod 上
这套机制适合几类场景:Web 应用的弹性伸缩(流量涨跌自动加减 Pod)、批处理/定时任务的资源调度、作为微服务架构的运行底座(每个微服务是一组 Pod)、CI/CD 流水线里隔离出干净的构建环境。
Kubernetes 的价值和优势
和传统的手工运维相比,K8s 的核心价值可以归纳成几点:
- 自动装箱:根据资源需求和限制自动决定 Pod 放在哪台机器,尽量提高整体资源利用率
- 自我修复:容器挂了自动重启,Node 挂了上面的 Pod 自动迁移到健康节点
- 水平扩展:一条命令或一个策略就能让副本数随负载自动增减
- 服务发现和负载均衡:Service 屏蔽了后端 Pod IP 变化的细节,调用方只认一个固定入口
- 自动发布和回滚:新版本可以逐步替换旧版本,出问题一键回滚到上一个稳定版本
- 跨云可移植:Kubernetes 的 API 是云厂商无关的标准接口,同一套 YAML 理论上可以在不同云、甚至自建机房之间迁移,不完全被某一家云厂商锁定
这些能力靠自己从零维护一套高可用的 Kubernetes 集群(尤其是 etcd 的高可用和版本升级),运维成本很高。下一章看阿里云怎么把这件事做成托管产品。
留言区待配置
部署 Twikoo 后端后,设置环境变量 NEXT_PUBLIC_TWIKOO_ENV_ID