阅读感悟
暂略
阅读摘录
《凤凰架构:构建可靠的大型分布式系统》
周志明
41个笔记
自序
- 软件工程小说《凤凰项目》(见图1)讲述了徘徊在死亡边缘的凤凰项目在精益方法下浴火重生的故事
5.2.2 OAuth 2
- [插图]
第10章 可观测性
- 日志收集和分析大多被统一到Elastic Stack(ELK)技术栈上,如果说未来还能出现什么变化的话,也就是其中的Logstash有被Fluentd取代的趋势,让ELK变成EFK,但整套Elastic Stack技术栈的地位已是相当稳固
- 度量方面,跟随Kubernetes统一容器编排的步伐,Prometheus也击败了度量领域里以Zabbix为代表的众多前辈,即将成为云原生时代度量监控的事实标准,虽然从市场角度来说Prometheus还没有达到Kubernetes那种“拔剑四顾,举世无敌”的程度,但是从社区活跃度上看,Prometheus已占有绝对的优势,在Google和CNCF的推动下,未来可期。
- Kubernetes是CNCF第一个孵化成功的项目,Prometheus是CNCF第二个孵化成功的项目。Kubernetes起源于Google的编排系统Borg,Prometheus起源于Google为Borg做的度量监控系统BorgMon。
11.1.4 封装系统:LXC
- LXC的出现肯定受到了OpenVZ和Linux-VServer的启发,站在巨人的肩膀上过河并没有什么不对。可惜的是,LXC在设定自己的发展目标时,也被前辈们的影响所局限住了。LXC眼中的容器与OpenVZ和Linux-VServer定义的并无差别,是一种封装系统的轻量级虚拟机,而Docker眼中的容器则是一种封装应用的技术手段。这两种封装理念在技术层面并没有什么本质区别,但应用效果差异巨大
- 以封装系统为出发点,仍是按照先装系统再装软件的思路,就永远无法在一两分钟甚至十几秒钟就构造出一个合乎要求的软件运行环境,也决定了LXC不可能形成今天的容器生态,所以,接下来舞台的聚光灯落到了Docker身上
11.1.5 封装应用:Docker
- 至少对早期的Docker而言,确实没有什么能构成壁垒的技术,它的容器化能力直接来源于LXC,它的镜像分层组合的文件系统直接来源于AUFS。在Docker开源后不久,有人仅用一百多行Shell脚本便实现了Docker的核心功能(名为Bocker[插图],提供了docker build/pull/images/ps/run/exec/logs/commit/rm/rmi等功能)。
- 那为何历史选择了Docker,而不是LXC或者其他容器技术呢?对于这个问题,笔者将引用(转述非直译,有所精简)DotCloud公司(当年创造Docker的公司,已于2016年倒闭)创始人Solomon Hykes在Stackoverflow上的一段问答来回应。
- 为了符合OCI标准,Docker推动自身的架构继续向前演进,首先将libcontainer独立出来,封装重构成runC项目,并捐献给Linux基金会管理。runC是OCI运行时的首个参考实现,提出了“让标准容器无所不在”的口号。
- 为了能够兼容所有符合标准的OCI运行时实现,Docker进一步重构了Docker Daemon子系统,将其中与运行时交互的部分抽象为containerd项目,这是一个负责管理容器执行、分发、监控、网络、构建、日志等功能的核心模块,内部会为每个容器运行时创建一个containerd-shim适配进程,默认与runC搭配工作,但也可以切换到其他OCI运行时实现上(然而实际并没做到
- 2016年,Docker把containerd项目捐献给CNCF管理。runC与containerd两个项目的捐赠托管,既是Docker对开源信念执着的追求,也是Docker在众多云计算大厂夹击下无奈的自救,这两个项目将成为未来Docker消亡和存续的伏笔。(看到本节末尾你就能理解这句矛盾的话了。)
11.1.6 封装集群:Kubernetes
- Kubernetes Master→kubelet→DockerManager→Docker Engine→containerd→runC
- ,现在连LXC都还没有被淘汰,反倒发展出了更加专注于与OpenVZ等系统级虚拟化竞争的LXD,相信Docker本身也很难彻底消亡,如已经习惯使用的CLI界面,已经形成成熟生态的镜像仓库等都应该会长期存在,只是在容器编排领域,未来的Docker很可能只会以runC和containerd的形式存续下去,毕竟它们最初都源于Docker
11.2.1 隔离与协作
- Docker设计的Dockerfile只允许有一个ENTRYPOINT,这并非无故添加的人为限制,而是因为Docker只能通过监视PID为1的进程(即由ENTRYPOINT启动的进程)的运行状态来判断容器的工作状态是否正常,然后根据状态决定是否执行清理自动重启等操作。
11.2.2 韧性与弹性
- 故障恢复、滚动更新、自动扩缩这些特性,在云原生时代里常被概括成服务的韧性(Resilience)与弹性(Elasticity),ReplicaSet、Deployment、Autoscaling的用法,也属于所有Kubernetes教材资料都会讲到的“基础必修课”
- 如果你准备学习Kubernetes或者其他与云原生相关的技术,建议最好不要死记硬背地学习每个资源的元数据文件如何编写、有哪些指令、有哪些功能,而是站在解决问题的角度去理解为什么Kubernetes要设计这些资源和控制器,为什么这些资源和控制器会被设计成现在这种样子
11.3.1 Kustomize
- 最初,由Kubernetes官方给出的“如何封装应用”的解决方案是“用配置文件来配置配置文件”,这不是绕口令,你可以理解为一种针对YAML的模板引擎的变体
- Kubernetes官方认为应用就是一组具有相同目标的Kubernetes资源的集合
- 如果逐一管理、部署每项资源元数据过于烦琐的话,那就提供一种便捷的方式,把应用中不变的信息与易变的信息分离开以解决管理问题,把应用所有涉及的资源自动生成一个多合一(All-in-One)的整合包以解决部署问题
11.3.3 Operator与CRD
- 有状态应用(Stateful Application)与无状态应用(Stateless Application)是指应用程序是否要自己持有运行所需的数据,如果程序每次运行都跟首次运行一样,不会依赖之前任何操作遗留下来的痕迹,那它就是无状态的;反之,如果程序推倒重来之后,用户能察觉到该应用已经发生变化,那它就是有状态的
- Operator提供了一种kind:Elasticsearch的自定义资源,在它的帮助下,仅需十行代码,将用户的意图是“部署三个版本为7.9.1的ES集群
12.1.1 网络通信模型
- 从整体上看,Linux系统的通信过程无论按理论上的OSI七层模型,还是以实际上的TCP/IP四层模型来解构,都明显呈现出“逐层调用,逐层封装”的特点,这种逐层处理的方式与栈结构,譬如程序执行时的方法栈很类似,因此它通常被称为“Linux网络协议栈”,简称“网络栈”,有时也称“协议栈”
- [插图]图12-1 Linux系统下的网络通信模型
12.1.2 干预网络通信
- 这套名为Netfilter的框架是Linux防火墙和网络的主要维护者Rusty Russell提出并主导设计的,它围绕网络层(IP协议)的周围,埋下了五个钩子(Hook),每当有数据包流到网络层,经过这些钩子时,就会自动触发由内核模块注册在这里的回调函数,这样程序代码就能够通过回调函数来干预Linux的网络通信
- [插图]
12.1.3 虚拟化网络设备
- 普通交换机只会单纯地做二层转发,Linux Bridge却还支持把发给它自身的数据包接入主机的三层协议栈中
- 对于通过brctl命令显式接入网桥的设备,Linux Bridge与物理交换机的转发行为是完全一致的,都不允许给接入的设备设置IP地址,因为网桥是根据MAC地址做二层转发的,就算设置了三层的IP地址也毫无意义。然而Linux Bridge与普通交换机的区别是除了显式接入的设备外,它自己也无可分割地连接着一台有着完整网络协议栈的Linux主机,因为Linux Bridge本身肯定是在某台Linux主机上创建的,可以看作Linux Bridge有一个与自己名字相同的隐藏端口,隐式地连接了创建它的那台Linux主机
- Linux Bridge允许给自己设置IP地址,比普通交换机多出一种特殊的转发情况:如果数据包的目的MAC地址为网桥本身,并且网桥设置了IP地址的话,那该数据包即被认为是收到发往创建网桥那台主机的数据包,此数据包将不会转发到任何设备,而是直接交给上层(三层)协议栈去处理
- 此时,网桥就取代了eth0设备来对接协议栈,进行三层协议的处理。设置这条特殊转发规则的好处是:只要通过简单的NAT转换,就可以实现一个最原始的单IP容器网络。这种组网是最基本的容器间通信形式,
- 单臂路由不属于任何VLAN,它与交换机之间的链路允许任何VLAN ID的数据包通过,而这个通信的接口也被称为TRUNK。这样,A1要和B2通信,就要将数据包先发送给路由(只需把路由设置为网关即可),然后路由会根据数据包上的IP地址得知B2的位置,去掉VLAN-A的VLAN Tag,改用VLAN-B的VLAN Tag重新封装数据包后再发回给交换机,交换机收到后就可以顺利转发给B2了
14.2 服务质量与优先级
- Kubernetes选择哪个节点运行Pod,只会根据requests的值来进行决策;limits才是供cgroups使用的,Kubernetes在向cgroups传递资源配额时,会按照limits的值来进行设置。
- 用户提交工作负载时设置的资源配额,并不是容器调度必须严格遵守的值,因为根据实际经验,大多数的工作负载在运行过程中真正使用到的资源,其实都远小于它所请求的资源配额。
- 要进行驱逐,首先Kubernetes就必须制定资源不足时该先牺牲哪些Pod、保留哪些Pod的明确准则,由此就形成了Kubernetes的服务质量等级(Quality of Service Level,QoS Level)和优先级(Priority)的概念
- Kubernetes目前提供的服务质量等级一共分为三级,由高到低分别为Guaranteed、Burstable和BestEffort
- 如果Pod中所有的容器都设置了limits和requests,且两者的值相等,那此Pod的服务质量等级便为最高的Guaranteed;如果Pod中有部分容器的requests值小于limits值,或者只设置了requests而未设置limits,那此Pod的服务质量等级为第二级Burstable;如果是上文说的那种情况,limits和requests两个都没设置则属于最低的BestEffort
14.3 驱逐机制
- Pod的驱逐机制是通过kubelet来执行的,kubelet是部署在每个节点的集群管理程序,由于本身就运行在节点中,所以最容易感知到节点的资源实时消耗情况。kubelet一旦发现某种不可压缩资源将要耗尽时,就会主动终止节点上较低服务质量等级的Pod,以保证其他更重要的Pod的安全。被驱逐的Pod中的所有容器都会被终止,Pod的状态也会被更改为Failed
- ·软驱逐:通常配置一个较低的警戒线(譬如可用内存仅剩20%),触及此线时,系统将进入一段观察期。如果只是暂时的资源抖动,在观察期内能够恢复到正常水平的话,那就不会真正启动驱逐操作。否则,若资源持续超过警戒线一段时间,就会触发Pod的优雅退出(Grace Shutdown),系统会通知Pod进行必要的清理工作(譬如将缓存的数据落盘),然后自行结束。在优雅退出期结束后,系统会强制杀掉还未曾自行了断的Pod
- 硬驱逐:通常配置一个较高的终止线(譬如可用内存仅剩10%),一旦触及此红线,立即强制杀掉Pod,而不会优雅退出
- 软驱逐是为了减少资源抖动对服务的影响,硬驱逐是为了保障核心系统的稳定
- 总而言之,在Kubernetes还没有成熟到变为“傻瓜式”容器编排系统之前,因地制宜地配置和运维是非常必要的
来自微信读书