云原生与 DevOps
1. Docker 是什么?
是实现容器技术的一种工具
是一个开源的应用容器引擎
使用 C/S 架构模式,通过远程 API 来管理
可以打包一个应用及依赖包到一个轻量级、可移植的容器中
2. 虚拟化是什么?
可以理解成虚拟机技术
一个主机可以部署多个虚拟机,每个虚拟机又可以部署多个应用
对于主机来说,虚拟机就是一个普通文件
3. 虚拟化的缺点是什么?
资源占用多:每个虚拟机都是完整的操作系统,需要给它分配大量系统资源
冗余步骤多:一个完整的操作系统,一些系统级别的步骤无法避免,比如用户登录
启动慢:启动操作系统需要多久,启动虚拟机就要多久
4. Docker 有什么优势?
资源占用少:每个容器都共享主机的资源,容器需要多少就用多少
启动快:一条命令即可将容器启动,而容器启动时一般会将服务或应用一并启动
5. Docker 与虚拟化的不同
启动速度不同
docker 启动快速属于秒级别。虚拟机通常需要几分钟去启动。
性能损耗不同
docker 需要的资源更少,docker 在操作系统级别进行虚拟化,docker 容器和内核交互,几乎没有性能损耗,性能优于通过 Hypervisor 层与内核层的虚拟化。
系统利用率不同
docker 更轻量,docker 的架构可以共用一个内核与共享应用程序库,所占内存极小。同样的硬件环境,Docker 运行的镜像数远多于虚拟机数量,对系统的利用率非常高。
隔离性不同
与虚拟机相比,docker 隔离性更弱,docker 属于进程之间的隔离,虚拟机可实现系统级别隔离。
安全性不同
docker 的安全性也更弱。Docker 的租户 root 和宿主机 root 等同,一旦容器内的用户从普通用户权限提升为 root 权限,它就直接具备了宿主机的 root 权限,进而可进行无限制的操作。虚拟机租户 root 权限和宿主机的 root 虚拟机权限是分离的。
可管理性不同
docker 的集中化管理工具还不算成熟。各种虚拟化技术都有成熟的管理工具,例如 VMware vCenter 提供完备的虚拟机管理能力。
可用和可恢复性不同
docker 对业务的高可用支持是通过快速重新部署实现的。虚拟化具备负载均衡,高可用,容错,迁移和数据保护等经过生产实践检验的成熟保障机制,VMware 可承诺虚拟机 99.999% 高可用,保证业务连续性。
创建、删除速度不同
虚拟化创建是分钟级别的,Docker 容器创建是秒级别的,Docker 的快速迭代性,决定了无论是开发、测试、部署都可以节约大量时间。
交付、部署速度不同
虚拟机可以通过镜像实现环境交付的一致性,但镜像分发无法体系化;Docker 在 Dockerfile 中记录了容器构建过程,可在集群中实现快速分发和快速部署;
虚拟化
虚拟化帮助我们在单个物理服务器上运行和托管多个操作系统。在虚拟化中,管理程序为客户操作系统提供了一个虚拟机。VM 形成了硬件层的抽象,因此主机上的每个 VM 都可以充当物理机。
容器化
容器化为我们提供了一个独立的环境来运行我们的应用程序。我们可以在单个服务器或 VM 上使用相同的操作系统部署多个应用程序。容器构成了应用层的抽象,所以每个容器代表一个不同的应用。
6. 什么是镜像?
创建容器的模板
同一个镜像可以创建多个不同的容器
7. 什么是容器?
通过镜像生成的运行实例
不同容器之间是相互隔离,独立运行的
通常一个容器就是一个应用或一个服务,也是我们常说的微服务
8. Docker 如何做到资源隔离
Namespace 对内核资源进行隔离,使得容器中的进程到可以在单独的命名空间中运行,并且只可访问当前容器命名空间的资源。
9. OpenTelemetry 是什么
OpenTelemetry 是一组 API、SDK、工具和集成,旨在创建和管理遥测数据,例如 Trace、Metrics 和 Logs。该项目提供了一个与供应商无关的实现,可以将其配置为将遥测数据发送到您选择的后端。
10. Jaeger 是什么
Jaeger 是用于追踪分布式服务之间事务的开源软件,它为微服务场景而生。它主要用于分析多个服务的调用过程,图形化服务调用轨迹,是诊断性能问题、分析系统故障的利器。
11. Prometheus 是什么
Prometheus 是一个开源的系统监控和报警系统
样本:在时间序列中的每一个点称为一个样本(sample),样本由以下三部分组成:
指标(metric):指标名称和描述当前样本特征的 labelsets;
时间戳(timestamp):一个精确到毫秒的时间戳;
样本值(value): 一个 folat64 的浮点型数据表示当前样本的值。
12. Dockerfile 常用指令
FROM:指定基础镜像。
MAINTAINER:指定镜像创建者信息。
RUN:在新的镜像内部运行命令。
CMD:指定容器启动时要运行的命令。
EXPOSE:声明容器运行时需要监听的端口。
ENV:设置环境变量。
ADD:将文件或目录复制到容器中。
COPY:将文件或目录复制到容器中。
ENTRYPOINT:配置容器启动后执行的命令和参数。
VOLUME:创建一个可以从本地主机或其他容器挂载的挂载点。
13. Docker 数据卷
Docker 数据卷是一个可供一个或多个容器使用的特殊目录,它绕过了 UFS,可以提供很多有用的特性:
数据卷可以在容器之间共享和重用。
对数据卷的修改会立马生效。
对数据卷的更新,不会影响镜像。
数据卷默认会一直存在,即使容器被删除。
Docker 数据卷有两种创建方式:在创建容器时创建数据卷和先创建好数据卷,然后在创建容器时挂载这个数据卷。
14. K8s 组件
Master
apiserver 是 Master 节点——同时也是整个 Kubernetes 系统的唯一入口,它对外公开了一系列的 RESTful API,并且加上了验证、授权等功能,所有其他组件都只能和它直接通信,可以说是 Kubernetes 里的联络员。
etcd 是一个高可用的分布式 Key-Value 数据库,用来持久化存储系统里的各种资源对象和状态,相当于 Kubernetes 里的配置管理员。注意它只与 apiserver 有直接联系,也就是说任何其他组件想要读写 etcd 里的数据都必须经过 apiserver。
scheduler 负责容器的编排工作,检查节点的资源状态,把 Pod 调度到最适合的节点上运行,相当于部署人员。因为节点状态和 Pod 信息都存储在 etcd 里,所以 scheduler 必须通过 apiserver 才能获得。
controller-manager 负责维护容器和节点等资源的状态,实现故障检测、服务迁移、应用伸缩等功能,相当于监控运维人员。同样地,它也必须通过 apiserver 获得存储在 etcd 里的信息,才能够实现对资源的各种操作。
Node
kubelet 是 Node 的代理,负责管理 Node 相关的绝大部分操作,Node 上只有它能够与 apiserver 通信,实现状态报告、命令下发、启停容器等功能,相当于是 Node 上的一个“小管家”。
kube-proxy 的作用有点特别,它是 Node 的网络代理,只负责管理容器的网络通信,简单来说就是为 Pod 转发 TCP/UDP 数据包,相当于是专职的“小邮差”。
container-runtime 我们就比较熟悉了,它是容器和镜像的实际使用者,在 kubelet 的指挥下创建容器,管理 Pod 的生命周期,是真正干活的“苦力”。
15. K8s 不同功能会放在同一个容器中吗
K8s 中不同的功能可以放在同一个容器中,但是这并不是最佳实践。通常情况下,每个容器应该只负责一个进程或服务,这样可以更好地管理和维护容器。如果将多个服务放在同一个容器中,那么当其中一个服务出现问题时,可能会影响到其他服务的正常运行。
16. K8s 声明式 api 和普通 api 有什么差异
K8s 的 API 有两种类型:声明式 API 和命令式 API。声明式 API 是一种更加高级的 API,它允许用户通过 YAML 文件来描述所需的状态,而不是通过命令来创建或更新对象。声明式 API 会自动检测对象的状态,并根据所需的状态进行调整。命令式 API 则是通过命令行工具或客户端库来创建或更新对象,它需要用户手动指定所需的状态。
17. Dockerfile 合并多个 RUN 指令的原因
当我们编写 Dockerfile 时,可以合并多个 RUN 指令,减少不必要的镜像层的产生,并且在之后将多余的命令清理干净,只保留运行时需要的依赖。
比如,下面这个 Dockerfile:
虽然这个操作创建的镜像中没有安装 Vim,但是镜像的大小和有 Vim 是一样的。原因就是,每条指令都会新加一个镜像层,执行 install vim 后添加了一层,执行 remove vim 后也会添加一层,而这一删除命令并不会减少整个镜像的大小。
18. docker stats 命令
docker stats 命令用于监视容器的实时资源使用情况,包括 CPU、内存、网络和磁盘等方面的信息。通过该命令,可以查看每个容器的 CPU 利用率、内存使用量、网络传输速度以及磁盘读写速度等指标,在容器出现性能问题时,可以帮助开发人员快速地定位问题所在。
使用 docker stats 命令可以列出正在运行的所有容器的实时资源使用情况,也可以通过指定容器名称或 ID 来获取特定容器的实时资源使用情况。默认情况下,docker stats 命令会每秒钟更新一次容器的资源使用情况,并按照容器 ID 或名称进行排序显示。
19. 持续交付的价值
持续交付的价值不仅仅局限于简单地提高产品交付的效率,它还通过统一标准、规范流程、工具化、自动化等等方式,影响着整个研发生命周期。
持续交付最终的使命是打破一切影响研发的“阻碍墙”,为软件研发工作本身赋能。无论你是持续交付的老朋友还是新朋友,无论你在公司担任管理工作还是普通的研发人员,持续交付都会对你的工作产生积极的作用。
20. DevOps 与持续交付
DevOps 的本质其实是一种鼓励协作的研发文化;
持续交付与 DevOps 所追求的最终目标是一致的,即快速向用户交付高质量的软件产品;
DevOps 的概念比持续交付更宽泛,是持续交付的继续延伸;
持续交付更专注于技术与实践,是 DevOps 的工具及技术实现。
21. 代码分支策略的选择
“主干开发”集成效率高,冲突少,但对团队个人的开发能力有较高要求;
“特性分支开发”有利于并行开发,需要一定的流程保证,能保证主干代码质量。
相信在没有绝对自信能力的情况下,面对绝大多数的场景,企业还是会选择“特性分支开发”的策略。
22. 如何做到分钟级搭建环境
可以使用虚拟机资源池,提升获取机器资源的速度;
合理打造并行的应用部署流水线,是进一步提升环境创建速度的方法;
利用配置等方式快速达到环境变更需求,可以再次有效地提升整个环境部署的效率。
23. 容器与环境管理
容器是一种轻量级、可移植、自包含的软件打包技术,使应用程序几乎可以在任何地方以相同的方式运行。
容器技术统一了软件环境和软件代码,交付产物中既包括了软件环境,又包括了软件代码。也就是说,容器帮我们重新定义了交付标准。
第一 交付结果一致
第二 交付自动化
第三 交付个性化
第四 交付版本控制
但它并不是银弹,它同样会带来问题,而这些问题,则需要改造和重新设计既有的持续交付模式来解决。
24. 如何做到构建的提速
升级硬件资源,最直接和粗暴的提速方式;
搭建私有仓库,避免从外网下载依赖;
使用本地缓存,减少每次构建时依赖下载的消耗;
规范构建流程,通过异步方式解决旁支流程的执行;
善用构建工具,根据实际情况合理发挥的工具特性。
25. 构建资源的弹性伸缩
通常建议使用成熟的 CI 产品(比如,Travis CI、Circle CI、Jenkins CI)来作为平台的基础;
虽然这些 CI 工具是成熟产品,但面对日新月异的技术需求,高可用和伸缩问题还是要自己解决;
通过请求分发等设计,可以实现 Master 节点的横向伸缩及高可用问题;
利用容器技术,可以解决 Salve 节点的弹性伸缩和资源利用率问题。
26. 容器镜像构建
容器镜像是一个独立的文件系统,它包含了容器运行初始化时所需要的数据或软件。Docker 容器的文件系统是分层的、只读的,每次创建容器时只要在最上层添加一个叫作 Container layer 的可写层就可以了。这种创建方式不同于虚拟机,可以极大的减少对磁盘空间的占用。
Docker 提供了 Dockerfile 这个可以描述镜像的文本格式的配置文件。你可以在 Dockerfile 中运行功能丰富的指令,并可以通过 docker build 将这些指令转化为镜像。
基于 Dockerfile 的特性,我分享了 Dockerfile 镜像构建优化的三个建议,包括:选择合适的 Base 镜像、减少不必要的镜像层产生,以及善用构建缓存。
用容器来构建容器镜像,主要有 DooD 和 DinD 两种方案。这两种方案,各有优劣,可以根据自身情况去选择。
27. 容器镜像的个性化及合规检查
用户自定义环境脚本,通过 build-env.sh 和 image-env.sh 两个文件可以在构建的两个阶段改变镜像的内容;
平台环境选项与服务集市,利用这两个自建系统,可以将个性化的内容进行抽象,以达到快速复用,和高度封装的作用;
自定义镜像,是彻底解决镜像个性化的方法,但也要注意符合安全和合规的基本原则。
关于对镜像的安全合规检查,在实践过程中总结有 5 条合规检查的基本建议
基础镜像来自于 Docker 官方认证的,并做好签名检查;
不使用 root 启动应用进程;
不在镜像保存密码,Token 之类的敏感信息;
不使用 --privileged 参数标记使用特权容器;
安全的 Linux 内核、内核补丁。如 SELinux,AppArmor,GRSEC 等。
28. 几种常见的灰度方式
蓝绿发布,是先增加一套新的集群,发布新版本到这批新机器,并进行验证,新版本服务器并不接入外部流量。此时旧版本集群保持原有状态,发布和验证过程中老版本所在的服务器仍照常服务。验证通过后,流控处理把流量引入新服务器,待全部流量切换完成,等待一段时间没有异常的话,老版本服务器下线。
这种发布方法需要额外的服务器集群支持,对于负载高的核心应用机器需求可观,实现难度巨大且成本较高。
蓝绿发布的好处是所有服务都使用这种方式时,实际上创造了蓝绿两套环境,隔离性最好、最可控,回滚切换几乎没有成本。
滚动发布,是不添加新机器,从同样的集群服务器中挑选一批,停止上面的服务,并更新为新版本,进行验证,验证完毕后接入流量。重复此步骤,一批一批地更新集群内的所有机器,直到遍历完所有机器。这种滚动更新的方法比蓝绿发布节省资源,但发布过程中同时会有两个版本对外提供服务,无论是对自身或是调用者都有较高的兼容性要求,需要团队间的合作妥协。但这类问题相对容易解决,实际中往往会通过功能开关等方式来解决。
金丝雀发布,从集群中挑选特定服务器或一小批符合要求的特征用户,对其进行版本更新及验证,随后逐步更新剩余服务器。这种方式,比较符合携程对灰度发布的预期,但可能需要精细的流控和数据的支持,同样有版本兼容的需求。
29. 不可变模型
每一次变更都是一次发布,而每一次发布都是系统重新构建,更形象点说,每一次发布都是一个独立镜像的启动。
首先,任何的变更,包括代码的、配置的、环境的,甚至是 CPU、内存、磁盘的大小变化,都需要制作成独立版本的镜像。
其次,变更的镜像可以提前制作,但必须通过发布才能生效。 这有 2 个好处:
重新生成新的实例进行生效,完全遵循不可变模型的做法;
发布内容既包含代码也包含基础设施,更有利于 DevOps 的实施。
再次,一组运行中的同一个镜像的实例,因为“不可变”的原因,其表现和实质都是完全一样的,所以不再需要关心顺序的问题。因为任何一个都等价,所以也就没有发布或替换的先后问题了。
最后,根据“一致”模型的要求,我们需要记录系统从第一天发展到今天的所有有序变更。 对 Docker 而言,不仅要能向上追溯层层 Base 镜像的情况,更建议将系统和软件的配置以 Dockerfile 的方式进行处理,以明确整个过程的顺序。
30. 发布系统的附加问题思考
一款出色的发布系统,除了要考虑架构、核心模型,以及发布流程的问题外,还必须同时考虑一些附加问题,比如:
为了降低 Quick and Dirty 方式对业务功能的影响,你需要提供发布刹车机制;
利用分布式存储、尾单加速、symlink 回滚等方式,可以提升发布速度;
必要的降级机制,可以保证发布系统的高可用。
31. 如何利用监控保障发布质量
监控的几种分类,以及分别可以采用什么方式去采集数据:
用户侧监控,可以通过打点收集,或者定期采集日志的方式进行数据收集;
网络监控,通过模拟手段或定期采样进行收集;
业务监控,需要定义正确的指标以及相匹配的采集技术,务必注意实时性;
应用监控,可以通过中间件打点采集,也可以通过日志联合分析进行数据采集;
系统监控,通常采用定期采样的方式收集数据。
三个对发布来说特别重要的监控问题:
测试环境的监控需要视作用而定,如果不能帮助分析和定位问题,则不需要很全面的监控;
一般发布后,我建议继续坚持监控 30 分钟,把这个流程纳入发布流程中;
完整的运维事件记录体系,可以帮你定位某次故障是否是由发布引起的。
32. 破坏性测试
破坏性测试就是通过有效的测试手段,使软件应用程序出现奔溃或失败的情况,然后测试在这样的情况下,软件运行会产生什么结果,而这些结果又是否符合预期。
第一,破坏性测试的手段和过程,并不是无的放矢,它们是被严格设计和执行的。不要把破坏性测试和探索性测试混为一谈。也就是说,破坏性测试不应该出现,“试试这样会不会出问题”的假设,而且检验破坏性测试的结果也都应该是有预期的。
第二,破坏性测试,会产生切实的破坏作用,你需要权衡破坏的量和度。因为破坏不仅仅会破坏软件,还可能会破坏硬件。通常情况下,软件被破坏后的修复成本不会太大,而硬件部分被破坏后,修复成本就不好说了。所以,你必须要事先考虑好破坏的量和度。
破坏性测试能够很好地测试分布式系统的健壮性,但也因为其破坏特点,使得它在持续交付中无法显示真正的威力;而混沌工程的提出,很好地解决了这个问题,使破坏性测试的威力能够在持续交付过程中被真正发挥出来。
33. 自动化回归测试
自动化回归测试会遇到三个难题:测试数据的准备和清理、分布式系统的依赖,以及测试用例的高度仿真。
我们可以利用 Mock 技术(即通过代理的方式模拟被依赖的对象、方法或服务的技术),通过不同的框架,解决自动化回归测试的前两个问题:
基于对象和类的 Mock,解决一个应用内部依赖的问题;
基于微服务的 Mock,解决应用与应用之间外部依赖的问题。
“回放技术”,即先通过虚拟交换机,复制和记录生产用户的实际请求,在测试时“回放”这些真实操作,以达到更逼真地模拟用户行为的目的,从而解决了自动化回归测试遇到的第三个问题。
所以,利用 Mock 和“回放”技术,我们能够提高自动化回归测试的效率和准确度,从而使整个持续交付过程更顺滑,自动化程度更高。
34. 持续交付为什么要实现平台化
随着软件技术的发展,任何企业最终都将面临多技术栈的现实。不同的技术栈,就意味着不同的标准、不同的工具、不同的方式,所以我们就必须要通过合理的持续交付平台,去解决不同技术栈的适配工作。
随着持续交付业务的发展,团队会越来越庞大,分工也会越来越明细。这就要求持续交付体系能够支持更大规模的并发处理操作,同时还要不断地提升效率。更重要的是,当持续交付成为企业研发的生命线时,它必须做到高可用,否则一旦停产,整个研发就停产了。
随着持续交付技术本身的发展,还会不断引入新的工具,或新的流程方法。如果我们的持续交付体系不能做到快速适应、局部改造、高可扩展的话,那它自身的发展与优化将会面临严峻的挑战。
35. 持续交付中有哪些宝贵数据
首先,你可以利用与业务量相关的数据模型来衡量持续交付系统的稳定性;然后,在日常的数据分析中,除了要抓住主要数据的大趋势外,你还要关注那些异常的个性数据,它能帮你及早地发现问题;最后,通过日常的数据分析,你也能发现持续交付流程上的一些问题,并协助团队一起改进。
当然,这只是三个比较突出的例子而已。在实践中,实施持续交付的过程中还有很多数据需要我们关注。包括:
稳定性相关指标;
性能相关指标;
持续交付能力成熟度指标。
36. Prometheus 中的 Pushgateway
用于接收短生命周期任务的指标上报,是 PUSH 的接收方式。因为 Prometheus 主要是 PULL 的方式拉取监控数据,这就要求在拉取的时刻,监控对象得活着,但是很多短周期任务,比如 cronjob,可能半秒就运行结束了,就没法拉取了。为了应对这种情况,才单独做了 Pushgateway 组件作为整个生态的补充。
37. Prometheus 中的服务发现
我们演示抓取数据时,是直接在 prometheus.yml 中配置的多个 Targets。这种方式虽然简单直观,但是也有弊端,典型的问题就是如果 Targets 是动态变化的,而且变化得比较频繁,那就会造成管理上的灾难。所以 Prometheus 提供了多种服务发现机制,可以动态获取要监控的目标,比如 Kubernetes 的服务发现,可以通过调用 kube-apiserver 动态获取到需要监控的目标对象,大幅降低了抓取目标的管理成本。
38. 为什么 Prometheus 默认为拉模式
拉模式有个最重要的优势,就是解耦。对于各类中间件,特别是非常基础的那些,很大概率是先于监控系统部署的。如果是拉模式,部署好监控系统之后,再来调用中间件的接口获取数据即可。如果是推模式,就需要在中间件里重新配置监控数据上报地址,然后重启中间件,这个代价就太高了。
39. 时序数据有哪些部分组成
每一个点称为一个样本(sample),样本由三部分组成。
指标(metric):metric name 和描述当前样本特征的 labelsets。
时间戳(timestamp):一个精确到毫秒的时间戳。
值(value):表示该时间样本的值。
PromQL 就是对这样一批样本数据做查询和计算操作。
40. Prometheus 联邦机制
原本一个 Prometheus 解决不了的问题,拆成了多个,然后又把多个 Prometheus 的数据聚拢到中心的 Prometheus 中。但是,中心的 Prometheus 仍然是个瓶颈。所以在联邦机制中,中心端的 Prometheus 去抓取边缘 Prometheus 数据时,不应该把所有数据都抓取到中心,而是应该 只抓取那些需要做聚合计算或其他团队也关注的指标,大部分数据还是应该下沉在各个边缘 Prometheus 内部消化掉。
41. Prometheus 的远端存储方案
默认情况下,Prometheus 收集到监控数据之后是存储在本地,在本地查询计算。由于单机容量有限,对于海量数据场景,需要有其他解决方案。最直观的想法就是:既然本地搞不定,那就在远端做一个集群,分治处理。
VictoriaMetrics
VM 虽然可以作为 Prometheus 的远程存储,但它志不在此。VM 是希望成为一个更好的 Prometheus,所以它不只是时序库,它还有抓取器、告警等各个组件,体系非常完备。
Thanos
Thanos 的做法和 VM 不同,Thanos 完全拥抱 Prometheus,对 Prometheus 做了一个增强,核心特点是使用 对象存储 做海量时序存储。
42. RED 方法
(Request)Rate:请求速率,每秒请求数。
(Request)Errors:错误,每秒错误请求数。
(Request)Duration:延迟,每个请求的延迟分布情况。
43. USE 方法
使用率:这个我们最熟悉,比如内存使用率、CPU 使用率等,是一个百分比。
饱和度:资源排队工作的指标,无法再处理额外的工作。通常用队列长度表示,比如在 iostat 里看到的 aqu-sz 就是队列长度。
错误:资源错误事件的计数。比如 malloc() 失败次数、通过 ifconfig 看到的 errors、dropped 包量。有很多错误是以系统错误日志的方式暴露的,没法直接拿到某个统计指标,此时可以进行日志关键字监控。
44. OpenTelemetry 架构
OpenTelemetry 主要包括了下面三个部分:
跨语言规范 (Specification);
API / SDK;
接收、转换和导出遥测数据的工具,又称为 OpenTelemetry Collector。
45. 容器技术原理
chroot
chroot 就是可以改变某进程的根目录,使这个程序不能访问目录之外的其他目录,这个跟我们在一个容器中是很相似的。
Namespace
Namespace 是 Linux 内核的一项功能,该功能对内核资源进行隔离,使得容器中的进程都可以在单独的命名空间中运行,并且只可以访问当前容器命名空间的资源。Namespace 可以隔离进程 ID、主机名、用户 ID、文件名、网络访问和进程间通信等相关资源。
Docker 主要用到以下五种命名空间。
pid namespace:用于隔离进程 ID。
net namespace:隔离网络接口,在虚拟的 net namespace 内用户可以拥有自己独立的 IP、路由、端口等。
mnt namespace:文件系统挂载点隔离。
ipc namespace:信号量,消息队列和共享内存的隔离。
uts namespace:主机名和域名的隔离。
Cgroups
Cgroups 是一种 Linux 内核功能,可以限制和隔离进程的资源使用情况(CPU、内存、磁盘 I/O、网络等)。在容器的实现中,Cgroups 通常用来限制容器的 CPU 和内存等资源的使用。
联合文件系统
联合文件系统,又叫 UnionFS,是一种通过创建文件层进程操作的文件系统,因此,联合文件系统非常轻快。Docker 使用联合文件系统为容器提供构建层,使得容器可以实现写时复制以及镜像的分层构建和存储。常用的联合文件系统有 AUFS、Overlay 和 Devicemapper 等。
46. Docker 核心概念
镜像
通俗地讲,它是一个只读的文件和文件夹组合。它包含了容器运行时所需要的所有基础文件和配置信息,是容器启动的基础。
容器
容器是镜像的运行实体。镜像是静态的只读文件,而容器带有运行时需要的可写文件层,并且容器中的进程属于运行状态。即容器运行着真正的应用进程。容器有初建、运行、停止、暂停和删除五种状态。
虽然容器的本质是主机上运行的一个进程,但是容器有自己独立的命名空间隔离和资源限制。也就是说,在容器内部,无法看到主机上的进程、环境变量、网络等信息,这是容器与直接运行在主机上进程的本质区别。
47. Docker 核心组件
runC 是 Docker 官方按照 OCI 容器运行时标准的一个实现。通俗地讲,runC 是一个用来运行容器的轻量级工具,是真正用来运行容器的。
containerd 是 Docker 服务端的一个核心组件,它是从 dockerd 中剥离出来的,它的诞生完全遵循 OCI 标准,是容器标准化后的产物。containerd 通过 containerd-shim 启动并管理 runC,可以说 containerd 真正管理了容器的生命周期。
dockerd
通过 gRPC 与 containerd
通信,由于 dockerd
与真正的容器运行时,runC
中间有了 containerd
这一 OCI 标准层,使得 dockerd
可以确保接口向下兼容。
48. Dockerfile 构建镜像特性
Dockerfile 的每一行命令都会生成一个独立的镜像层,并且拥有唯一的 ID;
Dockerfile 的命令是完全透明的,通过查看 Dockerfile 的内容,就可以知道镜像是如何一步步构建的;
Dockerfile 是纯文本的,方便跟随代码一起存放在代码仓库并做版本管理。
分层的结构使得 Docker 镜像非常轻量,每一层根据镜像的内容都有一个唯一的 ID 值,当不同的镜像之间有相同的镜像层时,便可以实现不同的镜像之间共享镜像层的效果。
49. 使用 Dockerfile 进行构建的好处
易于版本化管理,Dockerfile 本身是一个文本文件,方便存放在代码仓库做版本管理,可以很方便地找到各个版本之间的变更历史;
过程可追溯,Dockerfile 的每一行指令代表一个镜像层,根据 Dockerfile 的内容即可很明确地查看镜像的完整构建过程;
屏蔽构建环境异构,使用 Dockerfile 构建镜像无须考虑构建环境,基于相同 Dockerfile 无论在哪里运行,构建结果都一致。
50. CMD 和 ENTRYPOINT
Dockerfile 中如果使用了
ENTRYPOINT
指令,启动 Docker 容器时需要使用 --entrypoint 参数才能覆盖 Dockerfile 中的ENTRYPOINT
指令,而使用CMD
设置的命令则可以被docker run
后面的参数直接覆盖。ENTRYPOINT
指令可以结合CMD
指令使用,也可以单独使用,而CMD
指令只能单独使用。
如果你希望你的镜像足够灵活,推荐使用 CMD
指令。如果你的镜像只执行单一的具体程序,并且不希望用户在执行 docker run
时覆盖默认程序,建议使用 ENTRYPOINT
。
最后再强调一下,无论使用 CMD
还是 ENTRYPOINT
,都尽量使用 exec
模式。
51. Docker 的安全问题
镜像安全
Linux 内核隔离性不够
尽管目前 Namespace 已经提供了非常多的资源隔离类型,但是仍有部分关键内容没有被完全隔离,其中包括一些系统的关键性目录(如 /sys、/proc 等),这些关键性的目录可能会泄露主机上一些关键性的信息,让攻击者利用这些信息对整个主机甚至云计算中心发起攻击。
所有容器共享主机内核
52. 为什么 Docker 需要 Namespace
当 Docker 新建一个容器时,它会创建六种 Namespace,然后将容器中的进程加入这些 Namespace 之中,使得 Docker 容器中的进程只能看到当前 Namespace 中的系统资源。
正是由于 Docker 使用了 Linux 的这些 Namespace 技术,才实现了 Docker 容器的隔离,可以说没有 Namespace,就没有 Docker 容器。
53. Cgroups 功能及核心概念
功能
资源限制: 限制资源的使用量,例如我们可以通过限制某个业务的内存上限,从而保护主机其他业务的安全运行。
优先级控制:不同的组可以有不同的资源( CPU 、磁盘 IO 等)使用优先级。
审计:计算控制组的资源使用情况。
控制:控制进程的挂起或恢复。
概念
子系统(subsystem):是一个内核的组件,一个子系统代表一类资源调度控制器。例如内存子系统可以限制内存的使用量,CPU 子系统可以限制 CPU 的使用时间。
控制组(cgroup):表示一组进程和一组带有参数的子系统的关联关系。例如,一个进程使用了 CPU 子系统来限制 CPU 的使用时间,则这个进程和 CPU 子系统的关联关系称为控制组。
层级树(hierarchy):是由一系列的控制组按照树状结构排列组成的。这种排列方式可以使得控制组拥有父子关系,子控制组默认拥有父控制组的属性,也就是子控制组会继承于父控制组。比如,系统中定义了一个控制组 c1,限制了 CPU 可以使用 1 核,然后另外一个控制组 c2 想实现既限制 CPU 使用 1 核,同时限制内存使用 2G,那么 c2 就可以直接继承 c1,无须重复定义 CPU 限制。
cgroups 的三个核心概念中,子系统是最核心的概念,因为子系统是真正实现某类资源的限制的基础。
54. Docker 相关的组件
docker
docker 是 Docker 客户端的一个完整实现,它是一个二进制文件,对用户可见的操作形式为 docker 命令,通过 docker 命令可以完成所有的 Docker 客户端与服务端的通信
dockerd
dockerd 是 Docker 服务端的后台常驻进程,用来接收客户端发送的请求,执行具体的处理任务,处理完成后将结果返回给客户端。
docker-init
在容器内部,当我们自己的业务进程没有回收子进程的能力时,在执行 docker run 启动容器时可以添加 --init 参数,此时 Docker 会使用 docker-init 作为 1 号进程,帮你管理容器内子进程,例如回收僵尸进程等。
docker-proxy
docker-proxy 主要是用来做端口映射的。当我们使用 docker run 命令启动容器时,如果使用了 -p 参数,docker-proxy 组件就会把容器内相应的端口映射到主机上来,底层是依赖于 iptables 实现的。
55. Containerd 相关组件
containerd
containerd 组件是从 Docker 1.11 版本正式从 dockerd 中剥离出来的,它的诞生完全遵循 OCI 标准,是容器标准化后的产物。containerd 完全遵循了 OCI 标准,并且是完全社区化运营的,因此被容器界广泛采用。
containerd 不仅负责容器生命周期的管理,同时还负责一些其他的功能:
镜像的管理,例如容器运行前从镜像仓库拉取镜像到本地;
接收 dockerd 的请求,通过适当的参数调用 runc 启动容器;
管理存储相关资源;
管理网络相关资源。
containerd 包含一个后台常驻进程,默认的 socket 路径为 /run/containerd/containerd.sock,dockerd 通过 UNIX 套接字向 containerd 发送请求,containerd 接收到请求后负责执行相关的动作并把执行结果返回给 dockerd。
如果你不想使用 dockerd,也可以直接使用 containerd 来管理容器,由于 containerd 更加简单和轻量,生产环境中越来越多的人开始直接使用 containerd 来管理容器。
containerd-shim
containerd-shim 的主要作用是将 containerd 和真正的容器进程解耦,使用 containerd-shim 作为容器进程的父进程,从而实现重启 containerd 不影响已经启动的容器进程。
ctr
ctr 实际上是 containerd-ctr,它是 containerd 的客户端,主要用来开发和调试,在没有 dockerd 的环境中,ctr 可以充当 docker 客户端的部分角色,直接向 containerd 守护进程发送操作容器的请求。
56. 容器运行时组件 runc
runc 是一个标准的 OCI 容器运行时的实现,它是一个命令行工具,可以直接用来创建和运行容器。负责真正意义上创建和启动容器。
57. Libnetwork 常见网络模式
null 空网络模式:可以帮助我们构建一个没有网络接入的容器环境,以保障数据安全。
bridge 桥接模式:可以打通容器与容器间网络通信的需求。
host 主机网络模式:可以让容器内的进程共享主机网络,从而监听或修改主机网络。
container 网络模式:可以将两个容器放在同一个网络命名空间内,让两个业务通过 localhost 即可实现访问。
bridge 桥接模式是 Docker 的默认网络模式,当我们创建容器时不指定任何网络模式,Docker 启动容器默认的网络模式为 bridge。
58. Docker 卷的实现原理
Docker 卷的实现原理是在主机的 /var/lib/docker/volumes 目录下,根据卷的名称创建相应的目录,然后在每个卷的目录下创建 data 目录,在容器启动时如果使用 --mount 参数,Docker 会把主机上的目录直接映射到容器的指定目录下,实现数据持久化。
59. Kubernetes 生成对象的 YAML 模版
使用参数 --dry-run=client -o yaml
可以生成对象的 YAML 模板,简化编写工作。
60. 进入/拷贝文件到 Pod
61. 快速暴露 Kubernetes 服务
因为 Pod 都是运行在 Kubernetes 内部的私有网段里的,外界无法直接访问,想要对外暴露服务,需要使用一个专门的 kubectl port-forward
命令,它专门负责把本机的端口映射到在目标对象的端口号,有点类似 Docker 的参数 -p
,经常用于 Kubernetes 的临时调试和测试。
下面我就把本地的“8080”映射到 WordPress Pod 的“80”,kubectl 会把这个端口的所有数据都转发给集群内部的 Pod:
注意在命令的末尾使用了一个 &
符号,让端口转发工作在后台进行,这样就不会阻碍我们后续的操作。
如果想关闭端口转发,需要敲命令 fg
,它会把后台的任务带回到前台,然后就可以简单地用“Ctrl + C”来停止转发了。
62. Kubernetes 应用伸缩
kubectl scale
是专门用于实现“扩容”和“缩容”的命令,你只要用参数 --replicas
指定需要的副本数量,Kubernetes 就会自动增加或者删除 Pod,让最终的 Pod 数量达到“期望状态”。
63. Kubernetes labels
我们通过 labels
为对象“贴”了各种“标签”,在使用 kubectl get
命令的时候,加上参数 -l
,使用 ==
、!=
、in
、notin
的表达式,就能够很容易地用“标签”筛选、过滤出所要查找的对象(有点类似社交媒体的 #tag
功能),效果和 Deployment 里的 selector
字段是一样的。
64. 什么是污点和容忍度
Master 节点默认有一个 taint
,名字是 node-role.kubernetes.io/master
,它的效果是 NoSchedule
,也就是说这个污点会拒绝 Pod 调度到本节点上运行,而 Worker 节点的 taint
字段则是空的。
tolerations
是一个数组,里面可以列出多个被“容忍”的“污点”,需要写清楚“污点”的名字、效果。比较特别是要用 operator
字段指定如何匹配“污点”,一般我们都使用 Exists
,也就是说存在这个名字和效果的“污点”。
如果我们想让 DaemonSet 里的 Pod 能够在 Master 节点上运行,就要写出这样的一个 tolerations
,容忍节点的 node-role.kubernetes.io/master:NoSchedule
这个污点:
“容忍度”并不是 DaemonSet 独有的概念,而是从属于 Pod,你可以在 Job/CronJob、Deployment 里为它们管理的 Pod 也加上 tolerations
,从而能够更灵活地调度应用。
65. 什么是静态 Pod
DaemonSet 是在 Kubernetes 里运行节点专属 Pod 最常用的方式,但它不是唯一的方式,Kubernetes 还支持另外一种叫“静态 Pod”的应用部署手段。
“静态 Pod”非常特殊,它不受 Kubernetes 系统的管控,不与 ApiServer、scheduler 发生关系,所以是“静态”的。
但既然它是 Pod,也必然会“跑”在容器运行时上,也会有 YAML 文件来描述它,而唯一能够管理它的 Kubernetes 组件也就只有在每个节点上运行的 kubelet 了。
“静态 Pod”的 YAML 文件默认都存放在节点的 /etc/kubernetes/manifests
目录下,它是 Kubernetes 的专用目录。
Kubernetes 的 4 个核心组件 apiserver、etcd、scheduler、controller-manager 原来都以静态 Pod 的形式存在的,这也是为什么它们能够先于 Kubernetes 集群启动的原因。
66. 动态存储卷
Kubernetes 里有“动态存储卷”的概念,它可以用 StorageClass 绑定一个 Provisioner 对象,而这个 Provisioner 就是一个能够自动管理存储、创建 PV 的应用,代替了原来系统管理员的手工劳动。
Last updated