2

容器技术基础

·13 分钟

第 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 rundocker 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 的工作流程和典型场景

一次典型的部署流程:

  1. 用户写一份 YAML,声明"我要什么"(比如:镜像是什么、副本数是几个),提交给 API Server
  2. API Server 校验请求,把最终状态写入 etcd
  3. Scheduler 发现有新的 Pod 还没被分配 Node,根据资源情况挑一个合适的 Node
  4. 该 Node 上的 kubelet 收到指令,拉镜像、起容器
  5. Controller Manager 持续监控,一旦实际运行的 Pod 数量跟期望不符(比如某个 Pod 崩了),自动重建
  6. 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