本文由 CNCF + Alibaba 云原生技术公开课 整理而来
云原生
- 云原生的定义:
实际上,云原生是一条最佳路径或者最佳实践。云原生为用户指定了一条低心智负担的、敏捷高效的、能够以可扩展、可复制的方式最大化地利用云的能力、发挥云的价值的最佳路径。
因此,云原生其实是一套指导进行软件架构设计的思想。按照这样的思想设计出来的软件:
- 云原生的愿景:
云原生的最大价值和愿景,就是认为未来的软件,从诞生起就生在云上、长在云上,遵循全新的软件开发、发布和运维模式,从而使得软件能够最大化地发挥云的能力。
容器技术和集装箱技术的革命性非常类似,容器技术使得应用具有了一种“自包含”的定义方式,这样应用才能以敏捷高效的、可扩展、可复制的方式发布在云上。容器技术正是云原生技术的核心底盘。
- 云原生的技术范畴:
云应用定义与开发流程:
1. 应用定义与镜像制作
2. CI/CD
3. 消息和Streaming
4. 数据库
云应用编排与管理:
1. 应用编排与调度
2. 服务发现与治理
3. 远程调用
4. API网关
5. Service Mesh
监控与可观测性:
1. 监控
2. 日志
3. Tracing
4. 混沌工程
云原生底层技术:
1. 容器运行时
2. 云原生存储技术
3. 云原生网络技术
云原生工具集:
1. 流程自动化与配置管理
2. 容器镜像仓库
3. 云原生安全技术
4. 云端密码管理
Serverless:
1. FaaS
2. BaaS
3. Serverless 计费
- 云原生思想的两个理论基础:
1. 不可变基础设施:容器镜像
2. 云应用编排理论:容器设计模式
- 基础设施向云演进的意义:
基础设施的一致性和可靠性:容器镜像、自包含、可漂移
简单可预测的部署与运维:自描述和自运维、流程自动化、容易水平扩展、可快速复制的管控系统与支撑组件
容器与镜像
- 什么是容器?
容器,是一个视图隔离、资源可限制、独立文件系统的进程集合。
视图隔离:能看见部分进程、独立主机名等
控制资源使用率:对于内存大小以及 CPU 使用个数等可以进行限制
资源视图隔离 - namespace
控制资源使用率 - cgroup
独立文件系统 - chroot
容器具有一个独立的文件系统,因为使用的是系统的资源,所以在独立的文件系统内不需要具备内核相关的代码或工具,只需要提供容器所需的二进制文件、配置文件以及依赖即可。只要容器运行时所需的文件集合都具备,那这个容器就能够运行起来。
- 什么是镜像?
结合上述,容器运行时所需要的所有文件集合就是容器镜像。
通常情况下,会使用Dockerfile来构建镜像。Dockerfile有非常便利的语法糖,能够帮助我们描述构建的每个步骤。每个构建步骤都会对已有的文件系统进行操作,这样会带来文件系统内容的变化,我们将这些变化称之为
changeset
。当把构建步骤所产生的变化依次作用到一个空文件夹上,就能够得到一个完整的镜像。
Dockerfile 常用语法糖:
FROM 指定基于哪个基础镜像
MAINTAINER 指定作者信息
ENV 为后续的RUN指令提供一个环境变量
ADD 将本地的一个文件或目录拷贝到容器的某个目录里,支持url和压缩包解压
COPY 将本地的一个文件或目录拷贝到容器的某个目录里,不支持url和压缩包解压
RUN 镜像操作指令
WORKDIR 为后续的RUN、CMD或者ENTERPOINT指定工作目录
USER 指定运行容器的用户
ENTRYPOINT 指定容器启动时执行的命令,只能有一条,写多条也只有最后一条生效
CMD 指定容器启动时执行的命令,只能有一条
EXPOSE 指定要映射出去的端口
VOLUME 创建一个可以从本机或者其他容器挂载的挂载点
changeset
的分层以及复用特点能够带来几点优势:
1. 能够提高分发效率,减少磁盘压力
2. 由于镜像数据相互共享,只需要下载本地没有的数据即可
3. 由于镜像数据相互共享,可以节省大量磁盘空间
- 如何构建镜像:
运行一个容器一般分为两步:
1. 编写Dockerfile
2. docker build 指定Dockerfile构建镜像
- 如何运行容器?
运行一个容器一般分为两步:
1. docker pull 从镜像仓库中下载镜像
2. docker run 运行镜像得到一个容器
一个镜像就相当于一个模板,一个容器就像是一个具体的运行实例。镜像具有一次构建、到处运行的特点。
容器生命周期
容器是一组具有隔离特性的进程集合,在使用
docker run
时会选择一个镜像来提供独立的文件系统并指定相应的运行程序。这里指定的运行程序称之为
initial
进程,这个
initial
进程启动的时候,容器会随之启动;当
initial
进程退出的时候,容器也会随之退出。
因此,可以认为容器的生命周期和
initial
进程的生命周期是一致的。容器内不只有这样的一个
initial
进程,
initial
进程本身也可以产生其他的子进程或者通过
docker exec
产生出来的运维操作,也属于
initial
进程管理的范围内。当
initial
进程退出的时候,所有的子进程也会随之退出,这也是为了防止资源泄露。
但这样做也会存在一些问题,应用里面的程序往往是有状态的,其可能会产生一些重要的数据,当一个容器退出被删除之后,数据也就丢失了,这对于应用来说是不能接受的,因此需要将容器产生出来的重要数据持久化。容器能够直接将数据持久化到指定的目录,这个目录称之为数据卷。
数据卷的生命周期独立于容器的生命周期,容器的创建、运行、停止、删除等操作都和数据卷没有关系,它是一个特殊的目录——用于帮助容器进行持久化的。将数据卷挂载到容器内,容器就能将数据写入到相应的目录,这样容器的退出就不会导致数据的丢失。
通常情况下,数据卷管理主要有两种方式:
1. 通过 bind 的方式,直接将宿主机的目录挂载到容器内
2. 将目录管理交给运行引擎
容器项目架构
当前最流行的容器管理引擎是
moby
,
moby daemon
会对上提供有关于容器、镜像、网络以及Volume的管理。
moby daemon
所依赖的最重要的组件就是
containerd
,
containerd
是一个容器运行时管理引擎,其独立于
moby daemon
,可以对上提供容器、镜像的相关管理。
containerd
底层有
containerd shim
模块,其类似于一个守护进程,这样设计的原因有几点:
1. 首先,containerd 需要管理容器生命周期,而容器可能是由不同的容器运行时所创建出来的,因此需要提供一个灵活的插件化管理。
而 shim 就是针对于不同的容器运行时所开发的,这样就能够从 containerd 中脱离出来,通过插件的形式进行管理。
2. 其次,因为 shim 插件化的实现,使其能够被 containerd 动态接管。如果不具备这样的能力,当 moby daemon 或者 containerd daemon 意外退出的时候,
容器就没人管理了,那么它也会随之消失、退出,这样就会影响到应用的运行。
3. 最后,因为随时可能会对 moby 或者 containerd 进行升级,如果不提供 shim 机制,那么就无法做到原地升级,也无法做到不影响业务的升级,
因此 containerd shim 非常重要,它实现了动态接管的能力。
容器 vs VM
VM 利用
Hypervisor
虚拟化技术来模拟 CPU、内存等硬件资源,这样就可以在宿主机上建立一个 Guest OS,也是常说的安装一个虚拟机。
每一个 Guest OS 都有一个独立的内核,比如 Ubuntu、CentOS 甚至是 Windows 等,在这样的 Guest OS 之下,每个应用都是相互独立的,VM 可以提供一个更好的隔离效果。但这样的隔离效果需要付出一定的代价,因为需要把一部分的计算资源交给虚拟化,这样就很难充分利用现有的计算资源,并且每个 Guest OS 都需要占用大量的磁盘空间,比如 Windows 操作系统的安装需要 10~30G 的磁盘空间,Ubuntu 也需要 5~6G,同时这样的方式启动很慢。正是因为虚拟机技术的缺点,催生出了容器技术。
容器是针对于进程而言的,因此无需 Guest OS,只需要一个独立的文件系统提供其所需要文件集合即可。所有的文件隔离都是进程级别的,因此启动时间快于 VM,并且所需的磁盘空间也小于 VM。当然了,进程级别的隔离并没有想象中的那么好,隔离效果相比 VM 要差很多。
总体而言,容器和 VM 相比,各有优劣,因此容器技术也在向着强隔离方向发展。