Docker 学习
cgroups 是容器技术的基础,提供了对网络、计算、存储等各类资源的隔离,由 Google 贡献给 Linux 内核。
快速开始
基本命令
Docker 可以理解为轻量级的虚拟机,它可以把应用程序放在独立的环境中运行。
最简单的 Docker 命令:
1 | docker run ubuntu echo hello docker |
Dockerfile 中每一条 RUN、COPY、ADD 指令会创建一个新层,Volume 提供了独立于容器之外的持久化存储。
1 | docker run -d --name nginx -v /usr/share/nginx/html nginx |
Docker 持久化卷
在另一个终端执行下面的命令:
1 | screen ~/Library/Containers/com.docker.docker/Data/com.docker.driver.amd64-linux/tty |
进入容器:
1 | docker run -it --volumes-from data_container ubuntu /bin/bash |
退出容器查看:
1 | cat data/a.txt |
仅有数据的容器可以做到被多个容器挂载从而做到数据共享。
Registry 本身是注册中心的意思,提供 SASS 服务,而非镜像的意思,目的是让大家共享镜像。
容器技术的原理
本质上,容器是通过命名空间、cgroups 等技术实现资源隔离和限制,并拥有独立根目录(rootfs)的特殊进程。
文件系统隔离
chroot
命令允许管理员将进程的更目录锁定在特定位置,从而限制进程对 FS 的访问范围,它的隔离功能对安全性至关重要 —— 例如可以创建一个“蜜罐”,用于安全地运行和监控可疑代码和程序。由于它的隔离作用,chroot 环境也被形象地称为“jail”(监狱),从 chroot 逃逸的过程则被称为“越狱”。
资源全方位隔离
Linux 引入了 Mount,IPC,UTS,PID,Network,User,Cgroup,Time 命名空间可以隔离文件系统、进程间通信、主机名、进程号、网络资源、用户和用户组、cgroup 控制组,系统时间。实现了容器所需要的 6 项最基本的资源隔离机制。
资源限制
cgroups 是 Linux 内核用于隔离、分配并限制进程组使用资源配额的机制。例如,它可以控制进程的 CPU 占用时间、内存大小、磁盘 I/O 速度等。groups 通过文件系统向用户暴露其操作接口。这些接口以文件和目录的形式组织在 /sys/fs/cgroup
路径下。
K8S
云原生时代的操作系统。
- 整个集群分为 2 类节点:控制平面的 Master 和数据平面的 Worker
- Master 系统由多个分布式组件组成:包括 API Server,Scheduler,Controller Manager 和 Cloud Controller Manager
- k8s 的最小运行单元是 Pod
- Worker 节点上有 kubeletet 组件
- 基于 Raft 算法实现的分布式一致性 kv 存储 Etcd
超亲密容器组 Pod
容器之间原本通过命名空间和 cgroups 进行隔离,Pod 的设计目标是打破这种隔离,使 Pod 内的容器能够像进程组一样共享资源和数据。为实现这一点,k8s 引入了一个特殊容器 —— Infra Container。
Infra Container 是 Pod 内第一个启动的容器,体积非常小(约 300 KB)。它主要负责为 Pod 内的容器申请共享的 UTS、IPC 和网络等命名空间。Pod 内的其他容器通过 setns(Linux 系统调用,用于将进程加入指定命名空间)来共享 Infra Container 的命名空间。此外,Infra Container 也可以作为 init 进程,管理子进程和回收资源。
Infra Container 启动后,执行一个永远循环的 pause() 方法,因此又被称为“pause 容器”。
Pod 是 k8s 的基本单位
k8s 引入了高层次的抽象来管理多个 Pod 实例。
- Deployment:无状态应用,支持滚动更新和扩缩容
- StatefulSet:有状态应用,确保 Pods 的顺序和持久性
- DaemonSet:确保每个节点上运行一个 Pod,常用于集群管理或监控
- ReplicaSet:确保指定数量的 Pod 副本处于运行状态
- Job/CronJob:管理一次性任务或定期任务
鉴于 Pod 的 IP 地址是动态分配的,k8s 引入了 Service 来提供稳定的网络访问入口并实现负载均衡。此外,Ingress 作为反向代理,根据定义的规则将流量路由至后端的 Service 或 Pod,从而实现基于域名或路径的细粒度路由和更复杂的流量管理。
边车模式
组合多种不同角色的容器,共享资源并统一调度编排,在 k8s 中是一种经典的容器设计模式 —— Sidecar。
在边车模式下,一个主容器(负责业务逻辑处理)与一个或多个边车容器共同运行在同一个 Pod 内。边车容器负责处理非业务逻辑的任务,如日志记录、监控、安全保障或数据同步。边车容器将这些职能从主业务容器中分离,使得开发更加高内聚、低耦合的软件变得更加容易。
资源调度和编排
和调度密切相关的就是 CPU 和内存了。像 CPU 这类的资源被称为可压缩资源,不足的时候,Pod 内的进程会变得卡顿,但是 Pod 不会因此被 kill。k8s 中的 CPU 是逻辑算力,1CPU=1000m。像内存这样的资源是不可压缩资源,不足的时候可能会杀死 Pod 中的进程,甚至驱逐整个 Pod。
服务网格(Service Mesh)
服务网格是一个处理服务通讯的专门的基础设施层。它的职责是在由云原生应用组成服务的复杂拓扑结构下进行可靠的请求传送。在实践中,它是一组和应用服务部署在一起的轻量级的网络代理,对应用服务透明。代表为 Istio,主要解决服务间通信治理问题。
解决了类似 SpringCloud 这类微服务框架的 3 个痛点问题:
- 技术门槛高
- 框架无法跨语言
- 框架升级困难
缺点:
- 网络延迟问题:服务网格通过 iptables 拦截服务间的请求,将原本的 A->B 通信改为 A->(iptables+Sidecar)-> (iptables+Sidecar)->B,调用链的增加导致了额外的性能损耗。尽管边车代理通常只会增加毫秒级(个位数)的延迟,但对性能要求极高的业务来说,额外的延迟是放弃服务网格的主要原因
- 资源占用问题:边车代理作为一个独立的容器必然占用一定的系统资源,对于超大规模集群(如有数万个 Pod)来说,巨大的基数使边车代理占用资源总量变得相当可观。
服务网格之所以备受推崇,关键不在于它提供了多少功能(这些功能传统 SDK 框架也有),而在于其将非业务逻辑从应用程序中剥离的设计思想。
最佳实践
构建足够小的容器镜像
容器镜像的一大挑战是尽量减小镜像体积。较小的镜像在部署、故障转移和存储成本等方面具有显著优势。方法如下:
- 选用精简的基础镜像:基础镜像应只包含运行应用程序所必需的最小系统环境和依赖。选择 Alpine Linux 这样的轻量级发行版作为基础镜像,镜像体积会比 CentOS 这样的大而全的基础镜像要小得多
- 使用多阶段构建镜像:在构建过程中,编译缓存、临时文件和工具等不必要的内容可能被包含在镜像中。通过多阶段构建,可以只打包编译后的可执行文件,从而得到更加精简的镜像
下面的是一个例子:
1 | # 第 1 阶段 |
最终大小仅仅 20MB。
加速容器镜像下载
当容器启动时,如果本地没有镜像文件,它将从远程仓库(Repository)下载。镜像下载效率受限于网络带宽和仓库服务质量,镜像越大,下载时间越长,容器启动也因此变慢。为了解决镜像拉取速度慢和带宽浪费的问题,阿里巴巴技术团队在 2018 年开源了 Dragonfly 项目。
Dragonfly 的工作原理:首先,Dragonfly 在多个节点上启动 Peer 服务(类似 P2P 节点)。当容器系统下载镜像时,下载请求通过 Peer 转发到 Scheduler(类似 P2P 调度器),Scheduler 判断该镜像是否为首次下载:
- 首次下载:Scheduler 启动回源操作,从源服务器获取镜像文件,并将镜像文件切割成多个“块”(Piece)。每个块会缓存到不同节点,相关配置信息上报给 Scheduler,供后续调度决策使用
- 非首次下载:Scheduler 根据配置,生成一个包含所有镜像块的下载调度指令
最终,Peer 根据调度策略从集群中的不同节点下载所有块,并将它们拼接成完整的镜像文件。Dragonfly 的镜像下载加速流程与 P2P 下载加速非常相似,二者都是通过分布式节点和智能调度来加速大文件的传输与重组。
加速容器镜像启动
容器镜像的大小直接影响启动时间,一些大型软件的镜像可能超过数 GB。例如,机器学习框架 TensorFlow 的镜像大小为 1.83 GB,冷启动时至少需要 3 分钟。大型镜像不仅启动缓慢、镜像内的文件往往未被充分利用(业内研究表明,通常镜像中只有 6% 的内容被实际使用)
2020 年,阿里巴巴技术团队发布了 Nydus 项目,它将镜像层的数据(blobs)与元数据(bootstrap)分离,容器第一次启动时,首先拉取元数据,再按需拉取 blobs 数据。相较于拉取整个镜像层,Nydus 下载的数据量大大减少。值得一提的是,Nydus 还使用 FUSE 技术(Filesystem in Userspace,用户态文件系统)重构文件系统,用户几乎无需任何特殊配置(感知不到 Nydus 的存在),即可按需从远程镜像中心拉取数据,加速容器镜像启动。
Nydus 将常见应用镜像的启动时间从几分钟缩短至仅几秒钟。
上述优化措施对于大规模集群,或对扩容延迟有严格要求的场景(如大促扩容、游戏服务器扩容等)来说,不仅能显著降低容器启动时间,还能大幅节省网络和存储成本。值得一提的是,这些技术调整对业务工程师完全透明,不会影响原有的业务流程。