阅读视图

发现新文章,点击刷新页面。

Navigating the Cloud: KubeConf 2023 Journey

I had the honor of attending KubeCon + CloudNativeCon + Open Source Summit China 2023 in Shanghai, an enriching experience that united the brightest minds in the tech industry. The conference delved into cutting-edge advancements and sparked insightful discussions on Cloud Native technologies, Artificial Intelligence (AI), Edge Computing, WebAssembly, Service Mesh, and pioneering strategies for reducing our carbon footprint. Key Highlights Cloud Native Ecosystem Witnessed the evolution of Cloud Native technologies, focusing on Kubernetes, containerization, multicluster orchestration, multicloud deployment, and serverless computing.

Linux与Git联手创造多重宇宙

自Linux诞生以来,一直以其简洁灵活的特性受到开发者的热烈追捧。Linux坚守Unix的KISS(Keep It Simple, Stupid)设计原则,但随着新的计算场景不断涌现,一些问题也逐渐浮现,其中之一便是操作系统生命周期的管理。 回顾历史,开发者们为解决这个问题尝试了各种各样的方法,其中包括包管理工具和漏洞自动修复工具等等。但是,是否还存在更出色的解决方案呢?当然存在,其中之一就是建立一个与Git类似的操作系统。想象一下,将操作系统的生命周期管理类比为Git仓库的管理,我们可以随意切换到不同的版本,创建新的分支,然后将它们合并到主分支,或者回滚到任何特定的版本。这样一来,我们就可以随心所欲地管理操作系统的生命周期,就像在创建多重宇宙一样。 为什么要重新造轮子 我们常说“less is more”。如果你的智能手机已经提供了GPS地图应用程序,你可能就不会再购买物理GPS导航器了。同样地,如果你的Linux系统能够提供高效且易于使用的即插即用功能,那么为什么还需要配置和维护额外的工具呢? 让我们深思熟虑一下那些声称支持操作系统回滚、增量更新、一致性或变更历史跟踪的解决方案所涉及的复杂性(以及成本)。如果我们能够仅仅通过操作系统本身的功能就能够实现这些目标,那岂不是更理想?当然,前提是即插即用的功能至少要与额外工具提供的功能一样出色。接下来,我们将详细了解这种全新的操作系统生命周期管理方法是如何实现这一目标的。 这个创新的轮子被称为libostree,当然,许多人也习惯称其为OSTree。OSTree的模型类似于Git,因为它对文件进行了校验和,并采用内容寻址的对象存储方式。但与Git不同的是,OSTree通过硬链接来“checkout”文件,因此需要将它们保持不变,以防止数据损坏。 在我们深入研究OSTree之前,让我们先了解一些相关的基础知识。 chroot 监狱 多年前,一些Linux开发者认识到在操作系统核心功能的开发中需要极高的谨慎,以防意外破坏环境。为了确保他们可以在一个安全的沙盒中进行开发,就像拥有时光机一样,可以随时回到原始环境,他们开发了一种在同一系统中快速创建新的操作系统“实例”的方式。这种方法允许他们在一个相对独立的环境中进行开发,从而安全地进行实验,而无需担心对主系统的影响。此外,这种方法还具有其他优点,如共享数据、使用系统安装的应用程序和特定硬件等。 或许你会提到虚拟化和容器技术也可以实现类似的隔离和环境复制功能。但问题是,虚拟机和容器的创建过程相对较慢,且在多个“版本”之间共享数据、应用程序和硬件有时会变得复杂。更重要的是,回滚操作通常不容易。因此,开发者们决定采用chroot技术来实现这种特殊的超能力。 “chroot”是早期Unix引入的一项操作,它改变了进程的“根目录”,从而可以创建一个隔离的“监狱”环境,以限制进程只能访问该环境内的资源。这种技术可用于创建进程的沙盒,防止它们对chroot目录之外的数据进行恶意更改,或者作为轻量级虚拟机的替代方案。尽管容器技术也使用了“namespace”等更复杂的机制来实现隔离,但chroot技术同样可以提供类似的隔离效果,而且更加轻量。 回到操作系统的生命周期管理问题,核心思想是允许从不同的chroot目录引导,这样开发者可以在一个chroot监狱环境中开发新功能,可以访问一些共享数据和其他应用程序。如果新环境出现问题,只需回到“原始”的chroot监狱,而不会影响其他环境。然而,仅仅使用chroot还不足够,因为要实现这些功能,需要与不同的引导加载程序组件(如GRUB、内核初始化文件、文件系统挂载等)协同工作。此外,还需要考虑如何执行诸如平台更新等操作,这就是libostree的核心功能。 类Git的可引导文件系统 首先,让我们来看libostree的官方定义: Libostree is both a shared library and suite of command line tools that combines a “git-like” model for committing and downloading bootable filesystem trees, along with a layer for deploying them and managing the bootloader configuration. 简而言之,libostree是一个共享库和一组命令行工具,它结合了一种“类似于Git”的模型,用于提交和下载可引导的文件系统树,同时还提供了用于部署它们和管理引导加载程序配置的层次。 换句话说,libostree用于管理可引导的操作系统生命周期,它采用了类似于Git版本控制系统的工作方式。与Git一样,libostree使用校验和来管理文件,将操作系统的不同版本存储为一组不可变的对象。这些对象可以像源代码一样进行版本控制和回滚。这使得libostree能够轻松地管理不同版本的操作系统,并实现类似Git的分支、合并和回滚操作,为操作系统的生命周期管理提供了强大的工具。 然而,正如你所指出的,使用chroot创建不同“版本”的根文件系统可能会导致存储空间的浪费,因为相同的文件会重复出现。为了解决这个问题,需要一种方式来在不复制或共享文件的情况下,在不同的根文件夹中存储相同的文件,并能够跟踪版本更改。 这就是类似Git的思想发挥作用的地方。Git是一个由哈希标识的对象组成的数据库,它使用键值数据存储概念来存储数据。Git具有四种不同类型的对象,用于表示文件的内容(blob)、目录结构(tree)、版本信息(commit)和标签(tag)。 虽然“blobs”和“trees”足以表示完整的文件系统,但“commits”包含对描述存储库根目录的“tree”对象的引用,从而提供了完整的版本控制系统。如果两个不同版本之间的文件内容没有更改,它们可以指向相同的“blob”,而不需要复制文件内容。 libostree不仅仅是Git的翻版,它借鉴了Git的概念,并以非常相似的方式应用这些概念,但在具体实现上有所不同。Git主要设计用于源代码仓库版本控制,因此它的功能更侧重于“文本文件”。相反,libostree需要处理混合版本控制,包括对“文本文件”和“二进制文件”的优化,因此它的功能更广泛,不仅局限于文本文件。 “有效地”复制文件系统 我们已经理解了使用chroot技术创建可引导的根文件系统环境,并希望实现类似Git的版本控制系统,以便有效管理不同版本的文件系统。现在让我们来谈谈如何实现“有效地”复制文件系统,这涉及到Linux中的硬链接。 在Linux中,有两种类型的文件链接:软链接(符号链接)和硬链接。软链接是一种特殊的文件,它指向另一个常规文件,而硬链接则是不同文件名直接指向相同数据和属性(inode)的文件。这两种链接类型之间有一个关键区别,使得硬链接更适合类似Git的版本控制用例。使用硬链接,即使你删除了“目标”文件,数据仍然可以访问,而软链接在目标文件被删除后将不再有效。在我们的情况下,我们需要在同一磁盘分区上拥有多个“文件副本”,并且这些副本必须是独立的,以防止删除一个文件影响到其他版本。 libostree使用硬链接来“checkout”文件,这意味着它可以在不复制文件内容的情况下创建多个文件系统版本,这是非常高效的。但是,硬链接也带来了一个问题。假设你有两个操作系统的“快照”(我们将它们称为“部署A”和“部署B”),它们是相同的。然后,你在“部署B”上进行了一些二进制文件的更改,但后来发现这些更改导致问题。你决定回到“部署A”,但问题是,“部署B”中的更改已经影响了“部署A”,因为它们实际上共享了相同的硬链接文件。这使得文件必须是不可变的,否则一个版本的更改将影响到其他版本。 为了解决这个问题,操作系统基于只读文件系统构建,并在启动时使用符号链接来选择可用的操作系统根文件系统“快照/镜像/部署”。每当你需要进行更改时,会创建一个全新的根文件系统的副本,但不需要复制所有文件内容,因此速度非常快。只有发生更改的文件将成为新的“常规”文件,而其他文件将保持为硬链接或在新版本中被删除。 虽然文件是不可变的,但仍然可以更新文件系统。假设你需要更新多个应用程序的二进制文件,你只需创建一个新的chroot文件系统副本,其中包含新版本的二进制文件。但是,如何在运行中的操作系统上应用这些更改呢?在启动时,通过修改符号链接中的路径来选择不同的操作系统根文件系统“快照/镜像”。这是通过修改内核参数中的软链接来实现的。 在启动时选择哪个操作系统版本(部署)是一项决策,如果要切换到新的部署,必须重新启动系统以使更改生效。这确保了文件系统镜像的一致性,因为每个部署都是一个相对独立的镜像,更改在重新启动后生效。 这种方法的好处在于它提供了一种有效管理多个文件系统版本的方法,并允许在需要时轻松切换版本。此外,这种一致性也允许系统回滚,即在需要时返回到先前的部署,因为它们都共享相同的文件系统镜像。这种方式也适用于大规模管理,可以在中央位置生成更新并将它们应用于多个系统,以确保一致性。这种方法大大简化了管理,减少了网络带宽和计算资源的浪费。

关于 WebAssembly,我们需要知道的事

WebAssembly 是什么? WebAssembly 简称 WASM,从字面上看,它是由 Web 和 Assembly 组成,可以理解为网络/浏览器汇编,这也表明这项技术的由来,即运行在浏览器内的汇编代码。但是,随着技术的迭代,WebAssembly 早已不局限于它最初的设计愿景。 WebAssembly 不完全是汇编语言,而是一种类似汇编字节码的指令格式标准,由 W3C 的 WASM 工作组和 ByteCode Aliance(字节码联盟)共同维护。它更像 LLVM-IR 那种比汇编语言更高一些抽象的中间语言,开发者不需要手写 WASM,而是选择使用其他高级语言(如 C、C++、Rust、Go、Python等)编写并编译为 WASM。 WebAssembly 已经不只局限于运行在浏览器上面,如今随着 WASM 生态圈的不断扩展,涌现出了各种与 WASM 兼容的运行时,这些运行时的出现使得 WASM 可以运行在浏览器之外的客户端与服务器端这样的沙盒环境中。 为什么需要 WebAssembly ? 不管是在浏览器还是 Node.js 这样的服务端中,JavaScript 都可以胜任,为什么还需要 WASM ? 主要是因为 JavaScript 的性能问题,本质上,JavaScript 没有静态的变量类型,导致执行引擎所做的编译优化(JIT Compiler)很可能失效。举个例子,JavaScript 代码中定义了一个函数并且包含一个局部变量,开始是它被赋予 Array 类型的值,执行到下一行代码时它又被赋予 Object 类型的值,JIT 编译器所做的优化也就失效了。也正是因为 JavaScript 本身的设计缺陷,导致这门编程语言的发展史变成了填坑史,即使是围绕 JavaScript 的开发生态越来越庞大,它在面对大型复杂项目是还是有点儿捉襟见肘。 当然,开发者为了解决 JavaScript 的性能问题也做了各种尝试,除了 WASM 还有一种优化 JavaScript 的优化子集:asm.js。与 WASM 一样,asm.js 也是一种编译目标,可读性比 WebAssembly 好,但是开发者必须强制使用静态类型,这导致并不是所有的开发者都能够接受;其次 acm.js 的代码还是需要经过“获取-解析-编译-优化”这些耗时的过程,而 WASM 则不用这些步骤,WASM 已经是原生的字节码,而且 WASM 也是静态类型的,这使得大多数优化在其初始编译时就已完成,所以 WebAssembly 比 asm.

写在「2022」年年末

岁月不居,时节如流,转眼间就到了圣诞节了。一般来说每年这时候是比较轻松自在的阶段,然而,面对国内疫情放开后的第一波的冲击,即使再加倍小心,还是中招了。 目前,经过大约一周的休整,身体基本已经恢复到感染前的水平。作为一个没有接种过疫苗的人,希望我与奥密克戎的抗争周期可以供大家参考,希望大家能更多得了解新冠。 第一天,也就是12月20号晚上,开始发烧,但其实早在发烧前几天都伴有轻微头痛眩晕,以为自己没休息好,也就没太在意,直到发烧开始,头痛加剧,到晚上10点钟体温超过39度,心跳急促,辗转一晚几乎没睡着,看着窗外日光变成灯光,灯光变成日光。整晚亲身经历着嗓子如何变得干涩,味觉逐渐退化的过程,这应该是最难熬的一天。 第二天,体温降到38度以下,但由于前一晚基本睡睡醒醒,醒醒睡睡,导致第二天起来之后全身酸痛,头昏脑胀,喉咙痛痒,恶心厌食,主要靠维C泡腾片冲水喝,这一天我足足喝了大约3升左右的水。到了下午左右所有症状开始好转一点儿,于是开始睡觉,醒来时已经到了第二天凌晨5点。 第三天,体温基本恢复正常,头痛也有所缓解,但是腰酸背痛,特别是竖脊肌附近的肌群,同时,喉咙干涩有痰,偶尔咳嗽。稍微有了些胃口,于是自己煮了碗菠菜挂面补充了一下。 第四天,全部症状都有所缓解,但开始干咳不停,味觉慢慢开始恢复,眼睛重新闪烁出光芒。 第五天,偶尔咳嗽,鼻子干涩,但抗原测试还是阳性。 总的来说,如果要我对这次奥密克戎感染的剧烈程度做一个评价的话,我认为它不能简单用一个“大号流感”来形容。这次西安地区主要的流行株是奥密克戎BF.7,它的整个发病过程猛烈但病程短,需要有至少正常的身体来度过真个发病周期,所以这个冬天对于老年人真的是个巨大的挑战。 关于药物的使用,需要按照个人的体质酌情处理。我之前感冒有对药物过敏的先例,于是这次没有使用任何药物,发烧的前两天自己熬制了生姜葱白汤来热饮,效果还可以。最后需要说的是,一旦转阳之后最好身边有人照顾,尤其是发烧阶段,神志不清,万一遇到紧急状况很难处理。 现在奥密克戎的传播能力进化得非常强,基本不靠飞沫,面对面讲话就可能被感染,真是防不胜防,一般的医用外科口罩有些难以抵挡。希望大家尽量不感染,也尽可能晚感染,也许,当我们积累了更多的对付奥密克戎的经验之后,真个发病周期就不需要这么难受了。 总之,不管是2022年以及即将到来的2023年,健康都变得无比重要,也希望这场疫情能够随着政策的变动而淡出我们的生活。最后愿凛冬散尽,星河长明。

关于 eBPF,我们需要知道的事

什么是 eBPF eBPF 全称为 Extended Berkeley Packet Filter,源于 BPF(Berkeley Packet Filter), 从其命名可以看出来,它适用于网络报文过滤的功能模块。但是,eBPF已经进化为一个通用的执行引擎,其本质为内核中一个类似于虚拟机的功能模块。eBPF 允许开发者编写在内核中运行的自定义代码,并动态加载到内核,附加到某个处罚 eBPF 程序执行的内核事件上,而不再需要重新编译新的内核模块,可以根据需要动态加载和卸载 eBPF 程序。由此开发者可以基于 eBPF 开发各种网络、可观察性以及安全方面的工具,正因为如此,才让 eBPF 在云原生盛行的当下如鱼得水。 Note: 最初的 BPF 广泛使用在类 unix 的内核中,而重新设计开发的 eBPF 最早集成在 3.18 的 linux 内核中,此后 BPF 就被被称为经典 BPF,也就是 cBPF(classic BPF),如今的 linux 内核不在运行 cBPF ,内核会将加载的 cBPF 字节码透明地转换成 eBPF 再执行。 eBPF 是如何工作的 一般来说,eBPF 程序包括两部分: eBPF 程序本身 使用 eBPF 的应用程序 先说使用 eBPF 的应用程序,它运行在用户空间,通过系统调用来加载 eBPF 程序,将其 attach 到某个出发此 eBPF 的内核事件上。如今的内核版本已经支持将 eBPF 程序可以加载到很多类型的内核事件上,最典型的是内核收到网络数据包的事件,这也是 BPF 最初的设计初衷,网络报文的过滤。除此之外,还可以将 eBPF 程序附加到和黑函数的入口(kprobe)以及跟踪点(trace point)等上面。eBPF 的应用程序有时候也需要读取从 eBPF 程序中传回的统计信息,事件报告等。

为什么音乐多是由12个音组成

一直有一个疑问困扰着我,为什么大部分音乐都是由12个音组成?当然,为了严谨起见,这里所说的音乐并不包括中国传统音乐中的所谓「五声七音十二律」,但其实十二律和现在主流乐音体系中「十二平均律」是相通的。音乐本就无国界,这是一件相当神奇的事情,另外一个例子是全世界各种族的人类听到大三和弦就会感到很愉悦,一旦将大三和弦的中音降低半个音高就会变成小三和弦,这时候听者往往会感到悲伤。 言归正传,作为一个理科生,我还是想从物理学和一点点生理学的角度来研讨一下为什么现代音乐普遍是由12个音谱成。可能有人就要说了钢琴不是有88个键吗?难道不应该是至少有88个音吗?实际上,但从物理学的角度来看,音高就是音的频率高低,那么可用的音就会无穷无尽。但问题是,音的频率只是区分不同音的一个角度,如果从生理学的角度来看,音的频率反而不是很重要,比如,我们将某音乐作品的所有音都按照固定的比率升高之后演奏,就会发现发出的音乐还是和以前一样和谐。所以说,重要的不是音的频率,而是这些频率之间的音程。当我们从C调切换为D调来演奏《小星星》的时候,会发现它还是《小星星》。旋律本身不是一些列音的组合,而是一些列音程的组合。 由此来看,钢琴上的键虽然很多,但是这些键发出的音都是按照固定的比率分组重复,仔细观察就会发现每个分组都包括12个音,其中包括: 7个自然音:C-D-E-F-G-A-B 5个由自然音变化而来的演化音:C#-D#-F#-G#-A# 这些音的音高关系以及在键盘上的布局如下图所示: 回到上面的问题,为什么会选择这12个音呢? 单从物理学的角度很难回答这个问题,因为音乐本身就是很主观的,之所以选择这12个音是因为经过千万年的演化,大部分人类都认同这12个音以及对应的音程是最和谐悦耳最具音乐性的。说白了就是用这12个音谱成的旋律最适合演奏。 为什么这12个音最适合演奏? 要回答这个问题,就要引出Octave(八度)的概念。钢琴键盘上的所有键是由不同版本的12个音分组组成,我们称之为音组,相邻音组的同名音,我们称之为「八度」。八度是音乐中最和谐最重要的音程,相距八度的音听起来很自洽,它们是相同音的较高或较低表现形式,在乐音体系中称之为Octave Equivalence(八度相等)。从钢琴上看,我们会看到多个A布局在键盘上相邻八度的位置上。而且这种名为八度的音高差异,对应于频率的差异,并非一个固定的赫兹数,而是一个固定的频率比率,该比率为2:1。钢琴中央A音高对应之频率为440Hz,其高八度之A音对应的频率为880Hz;其低八度之A音对应的频率则为220Hz。对于弦乐器,比如吉他,弦长减半,则频率加倍,也就是说弦长一半的音是另一弦的高八度音。 现在世界上主流的音律系统包括西方的十二音律系统(后面会提及)都是首先确定八度,在用各种方式把八度分成不同的音。 这其中把八度分成12个音的方式可以很方便的演奏下面最重要的几个音程: 其中八度音已经介绍过了,我们快速过一下其他音程,然后再看看它们在键盘上的位置。 除了八度音之外被广泛认为最和谐的两个音程是纯五度与纯四度,其实越是和谐的音程,它们组成音的频率越是具备简单的关系,比如八度音的频率比是2:1,而纯五度与纯四度音的频率比分别是3:2与4:3。 注意这里不要混淆了因果,我们并不是因为音频率比简单而选择它们,而是因为音程越和谐,音之间的频率比越简单。这并不是巧合,从物理学的角度来看,两个音的频率比越简单,这两个音的波长就会完美的排列甚至同步。所以说,音频率的完美同步才是这些音程和谐的根本原因。 人们对于八度、纯五度与纯四度之外的其他音程和谐程度的排序会略有不同,然而大部分遵循下面这张图,纵坐标代表和谐程度,每个音程的位置越高代表越和谐: 还是列出这些主要和谐音程的频率比,如下表所示,从表中我们也很容易越和谐的音程其组成音的频率比越简单: 音程 根音的乘阶 频率比 八度 2 2:1 纯五度 1.5 3:2 纯四度 1.33 4:3 大三度 1.25 5:4 小三度 1.2 6:5 大六度 1.6 8:5 小六度 1.66 5:3 除了上表所列的和谐音程之外的剩下音程普遍被认为是不和谐的音程,但这并不代表这些音程没有用,虽然说“不和谐”看起来应该尽量避免使用,但是没有“不和谐”音程的音乐往往缺乏戏剧效果或者悬念,所以在某些电影音乐中经常能见到所谓的“不和谐”音程来增加张弛度,最典型的例子就是诺兰电影御用音乐制作大神汉斯-季默的作品。 这基本上就是现代音乐主要用到的12个音。可能有人又要问了,为什么不能在这些音中间再加入一些音,比如,在小三度与大三度之间加入一个“中三度音”,也可以在大七度与八度之间加入一个所谓的“超七音”,这样,就可以这样划分八度之间的音: 事实上,我们并不能这样加入微分音,因为八度之间的音“几乎”是平均分布的,这方便用乐器演奏不同的调,八度之间的这些标准音几乎是平均排列,如果再加入上面所示的两个微分音,就会打破这种平均的局面。即使要加入更多的音到八度之间,我们也得找到一种既能平均分割八度但又能涵盖上面讲到的各个重要音程(纯五度、纯四度、大三度等)的分割方法。最典型的两种分割方法是: 八度19音 八度24音 这两种分割方法都既保证了平均布局又保证涵盖各个重要音程,历史上,有些音乐家确实使用这些音律系统制作了乐器并演奏作品,比如四分音钢琴,也叫做24音阶钢琴,每个八度中包含24个键。可想而知,加入更多的微分音会让乐器更加难以演奏,而且这些新加入的微分音符大部分不太实用,也就是说不值得加入乐器。 乐器的设计必须在易用性和演奏更多音之间找到平衡。 几个世纪以来人们经过各种实践得出的结论是,八度12音是这种最优解,它是演奏最和谐和最有用音程的最佳组合方式。有了八度12音,依次增减排列就可以得到钢琴的所有键。 到这里,我们最初的问题似乎得到了解答。 但是,等等… 还有个问题,之前留了一个“坑”,我们提到过上面这种按照简单频率比来划分八度方式得到的12音的布局“几乎”是平均排列但并不是“严格”平均排列,这会不会造成其他的麻烦呢? 要回答此问题?需要介绍新的概念 - 律制 上面介绍的这种律制一般称之为均律,音程按照完美的频率比布局。但是即使是最简单最完美的频率比布局,也会到来其他的问题。因为乐器如果要按照不同的调演奏作品,音就需要尽可能平局排列,所以即使均律12音间隔近似相等,但并不是完美平均排列。 由此带来的问题是,换调演奏同一作品时,根音也跟着发生变化,所以就需要重新调律,但是调律之后就会发生“跑调”的问题。比如A-E之间是完美的3:2关系,所以如果用A大调来演奏音乐听起来会非常悦耳,但是如果在其他调演奏同一音乐,比如Eb大调,听起来就会很不和谐。原因是,虽然A-E的纯五度是完美的3:2关系,但是Eb-Bb的纯五度并不是3:2关系。 那么,怎么解决这个问题呢?答案是12 Equal Temperament(十二平均律)。 十二平均律保证了相邻音之间的距离严格相等,所以在十二平均律中,A-E是完美的纯五度,Bb-F也是完美的纯五度。 十二平均律除了八度音程还在使用2:1的频率比,不再保证其他各个音程的完美频率比,只不过将八度之间等分为12个音,这种分割方式演奏音乐的能力简单而出色,除了八度之外的其他音程的频率比会和完美比例稍有偏差,但是人耳很难听出这种差别。事实上,几乎我们听到的所有音乐都是使用十二平均律。 到此为止,所有涉及的概念基本都涵盖到了,题目看似简单的问题背后却有如此复杂的原因,既有物理与生理的因素,也有人文历史原因。当然,还是那句话,并不是说音乐只能用这12个音,全世界各种文化中的音乐往往有着不同的音律系统,但是这些音律系统最终的目的还是尽可能兼具实用性与易用性。

嘿,明年见

四天前是冬至日,北半球的昼夜等分点,意味着接下来朝着不断延伸的白昼行进,那时候我还构思着怎么来完美地跨年,犒劳一下辛苦了一年的自己。然而,两天后西安宣布紧急封城,一下子我们又回到了2020年年初。但是仔细想想,我个人受到封城的影响真的是微不足道,最多也就是得自己买菜做饭,暂时搁置健身计划等。有时候觉得自己挺幸运地选择了一个和实体经济不密切相关的行业,借助于现代科技只需要电脑与网络就可以完成工作,所以有效地规避了疫情反覆带来的影响。反过来讲,也正是因为这点儿幸运,所以不得不更「卷」。 言归正传,这是一篇正经的年终总结,但是我想水一水,因为重要的事情基本都囊括在上一篇博客中了。从10月到12月也没什么大事件发生,唯一值得记录的是Kubeconf China 2021。和去年一样,因为疫情原因,还是通过线上方式举行。这次我们在虚拟的展台介绍了OCM以及基于OCM的多云网络解决方案。连续两年的线上举行方式,感觉大家的热情都有所削减,也可能是K8s已经到了创新的倦怠期,很少有哪个主题能够吸引大批的目光。不管怎样,我们相信下个KubeConf会有更多关于多云的主题出现。 接下来的一年,大方向还是基于OCM做更多的尝试。其中一条线是关于多云的可观测性的演化,从基础架构层的观测扩展到用户程序的观测;另一条线是基于多云的服务网格尝试,虽然现在有好几种不同方案备选,但是最终的成型方案还有待商榷。当然这两条线并不是完全独立的,因为服务网格很重要的一个功能就是提供服务的可观测性。

这半年

好久没更新博客了,准确的说上次更新还是在半年多之前换工作之际。 半年前我的WX通讯录里面增加了很多同行,虽然其中大部分人都是在Istio社区认识的,但在那之前也没有真正互动过。回头想想找工作的那段时间,自由却不自在,好在很多同事、同行给了很多机会,使我顺利找到新工作。之前也一直没时间感谢认可我的同事、同行以及各厂的HR,借此由衷感谢特别是鹅厂与菊厂的大大们赏赐的机会,期待以后能够一起共事。当然,我也特别感激我现在的东家,红帽(Red Hat)给的机会。之所以选择红帽的原因也很简单,熟悉的团队加上熟悉的产品,以至于入职第一天开始提交了code… 展开来说,红帽到底是一家怎样的公司,估计很多人知道红帽玩开源很溜。没错,基本上红帽产品的商业模式都是基于开源社区的,当然我们组的产品也不例外,完全开源在Github上面,有兴趣的朋友们请前往传送门。借此,也宣传一下OCM(Open Cluster Management)项目,该项目是我们这条产品线RHACM(Red Hat Advanced Cluster Management for Kubernetes)的开源精简版本,目标是构建一个完全开源并且第三方中立的多云管理平台,我们也正在申请将它贡献到CNCF Sandbox里面。话说回来,其实红帽不止在源代码级别是“开放”的,更重要的是这种开放的文化其实是渗透到公司的管理文化中,任何人都可以参与到改进他认为不合适或者不正确的公司政策中去。总体来说,红帽是一家工程师文化特别浓重的公司,这也是经常能在红帽遇到业界大佬的缘由之一吧。 那我在其中参与什么样的角色呢?我目前所在的组主要是集中在基于ACM的可观测(Observability)的相关工作,所以这半年其实还是学习到不少监控相关的知识,当然不止是Prometheus+Grafana那一套比较可靠但扩展性不足的方案,我们目前的产品更多围绕Thanos(不是灭霸哦[doge][doge])来构建,后面我们会基于多云的案例扩展更多的功能。上面说的是我目前的主线任务,除此之外我还开了一个副本任务,多云环境中的服务网格。在这方面,红帽多少还是有一些落后,不过未来应该会很快赶上业界的脚步。 关于这半年的工作和生活的变化,简单聊几句吧。首先,我开始尝试了站立办公,实际上我目前站着工作的时间会和坐着的时间基本上是1比1左右,以前对于站立办公不怎么感冒,自从体验了公司的升降桌之后,发现“真香”。 另外,还有一个变化是开始了有规律的健身,基本上一周2-3次,有氧与无氧结合,坚持了有三个月左右了吧,计划后面增加无氧的比例。效果还是有的吧!?为了配合健身,通勤方式也发生了变化,我的“死飞”终于上路了,建议还是装个手刹,另外也可以像我一样,错峰出行,保证安全第一。 以上就是我这半年的流水账了。至于未来,还是有一大推事情需要推进,flag就不立了,在没有落地之前的想法都是扯淡。

Farewell, IBM

Farewell, IBM 今天是我在IBM的最后一个工作日,上午到公司后整理了私人物品,拿了离职证明,中午和几个同事吃了散伙饭,本来也想着轻轻松松度过在IBM的最后一天,但在归还笔记本和工牌的那一刻还是有些伤感。可能每一个从IBM“毕业”的人都和我有一样的感觉吧,就是那种在职时会花式吐槽它,但真到离开时才发现它真是良心公司。 我与IBM的缘分从大三就开始了,记得我的第一份实习就是从IBM上海研发中心开始,那时候自己刚从校园走出来,什么都不懂,但当时的mentor还是会耐心地给予指导,同事们也会在生活上尽可能地给我提供帮助,从来不会因为自己是实习生而差别对待,整个工作氛围轻松和谐。对IBM的好感从那时候就开始了,以至于后来研究生期间再去其他互联网公司实习再也没有找到这种感觉。 正式加入IBM也算是机缘巧合,15年那个夏天我研究生毕业后直接作为pure blue加入IBM的大家庭。从刚入职时的那股兴奋劲儿到后来EPH培训的大开眼界,从科技二路中清大厦到锦业一路软件园,从高性能计算LSF产品到云原生全栈升式ICP……将近六年的时间留下了太多回忆,伴随着这些回忆的便是成长。在IBM的这几年我学习到了以前没有机会接触的东西,特别是在云原生和开源领域,无论从广度还是深度方面来说,我主观上认为自己都有长足的进步。这一点儿真的非常感谢我的老板和mentor,感谢他们提供的宝贵机会。 天下没有不散的筵席,纵有再多眷恋,仍须策马扬鞭。现在我更愿意以成年人的方式结束我在IBM的六年,毕竟我的目标是星辰大海,LOL~ 在此,也祝愿我的老东家乘风破浪,再上“云端”! 青山不改,绿水长流,他日江湖再见!

复盘 kubernetes client-go

其实 Kubernetes 官方提供了各种语言的客户端类库,但是由于 golang 在云原生领域的先天优势,client-go 相对来说是使用较多的类库。但是要把 client-go 在一片天文章中讲清楚实属困难,所以本文不可能覆盖到所有的细节,尽量追求将主要框架描述清楚,并且配合代码片段探讨 client-go 常用的接口以及使用方法。 client-go 主体框架 其实要了解 client-go 的主体功能模块以及各个模块的功能,对于 Kubernetes API 的设计理念是基础,特别是对于 Kubernetes API 分组与版本转换。还不清楚的同学强烈建议先去阅读我之前的笔记: 初识 Kubernetes API 的组织结构 深入 kubernetes API 的源码实现 我们先来瞄一眼 client-go 的主要代码结构,我会给出各个主要部分的核心功能让大家有一个感性的认识: $ tree -L 2 client-go client-go ├── discovery # 包含dicoveryClient,用于发现k8s所支持GVR(Group/Version,/Resource),'kubectl api-resources'命令正是使用它来列出cluster中的各种资源。 ├── dynamic # 包含dynamicClient,它封装了 RESTClient,可以动态的指定api资源的GVR,结合unstructured.Unstructured类型来访问各种类型的k8s资源(如: Pod,Deploy...),也可以访问用户自定义资源(CRD)。 ├── informers # 为了减少client对于apiserver的频繁访问,需要informer来缓存apiserver中资源,只有当api资源对象发生变化的时候才收到通知。每种api资源会有自己的informer实现,也是按照api分组与版本来区分。 ├── kubernetes # 主要定义ClientSet,它也对restClient进行了封装,并且包含对各种k8s资源和版本的管理方法。每个api资源有单独的client,而ClientSet则是多个客户端的集合。ClientSet以及每种k8s内置资源的client的所有请求最终还是由restClient发出的;在typed目录包括具体每种k8s内置资源的client实现,也是按照api分组与版本来区分。 │ ├── clientset.go │ └── typed ├── listers # 包含各种k8s内置资源的只读客户端。每种lister都有Get()和List()方法,并且结果都是从缓存中读取的。 ├── rest # 包含真正给apiserver发请求的client,实现了Restful的API,同时支持Protobuf和JSON格式数据。 ├── scale # 只要包含scalClient用于Deploy, RS等的扩/缩容。 ├── tools # 各种类型的工具包,常见的比如获取kubeconfig的方法,以SharedInformer、Reflector、DealtFIFO和Indexer等工具,这些工具主要用于实现client查询和缓存机制,减轻apiserver的负载等。 Note: 为了简化,不重要的文件与目录没有列出来。

深入 kubernetes API 的源码实现

估计很多同学像我一样,第一次打开 Github 上面 kubernetes 项目源码的时候就被各种仓库搞晕了,kuberentes 组织下有很多个仓库,包括 kubernetes、client-go、api、apimachinery 等,该从哪儿仓库看起?kubernetes 仓库应该是 kubernetes 项目的核心仓库,它包含 kubernetes 控制平面核心组件的源码;client-go 从名字也不难看出是操作 kubernetes API 的 go 语言客户端;api 与 apimachinery 应该是与 kubernetes API 相关的仓库,但它们俩为啥要分成两个不同的仓库?这些代码仓库之间如何交互?apimachinery仓库中还有 api、apis 两个包,里面定义了各种复杂的接口与实现,清楚这些复杂接口对于扩展 kubernetes API 大有裨益。所以,这篇文章就重点关注 api 与 apimachinery 这两个仓库。 api 我们知道 kubernetes 官方提供了多种多样的的 API 资源类型,它们被定义在 k8s.io/api 这个仓库中,作为 kubernetes API 定义的规范地址。实际上,最开始这个仓库只是 kubernetes 核心仓库的一部分,后来 kubernetes API 被越来越多的其他仓库使用,例如 k8s.io/client-go、k8s.io/apimachinery、k8s.io/apiserver 等,为了避免交叉依赖,所以才把 api 拿出来作为单独的仓库。k8s.io/api 仓库是只读仓库,所有代码都同步自 https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/api 核心仓库。 在 k8s.io/api 仓库定义的kubernetes API 规范中,Pod 作为最基础的资源类型,一个典型的 YAML 形式的序列化 pod 对象如下所示: apiVersion: v1 kind: Pod metadata: name: webserver labels: app: webserver spec: containers: - name: webserver image: nginx ports: - containerPort: 80 从编程的角度来看,序列化的 pod 对象最终会被发送到 API-Server 并解码为 Pod 类型的 Go 结构体,同时 YAML 中的各个字段会被赋值给该 Go 结构体。那么,Pod 类型在 Go 语言结构体中是怎么定义的呢?

初识 Kubernetes API 的组织结构

话说自己入坑云原生也有好几年了,但是对 kubernetes 基础认识却不够深,导致写代码的时候经常需要打开 godoc 或者 kubernetes 源码查看某个接口或者方法的定义。这种快餐式的消费代码方式可以解决常见的问题,但有时候却会被一个简单的问题困扰很久。究其原因,还是没有对 kubernetes 有比较系统的学习,特别对于 kubernetes API 的设计与原理没有较为深入的认识,这也是我们平时扩展 kubernetes 功能绕不开的话题。与此同时,这也是很难讲清楚的一个话题,是因为 kubernetes 经过多个版本的迭代功能已经趋于成熟与复杂,这一点也可以从 Github 平台 kubernetes 组织下的多个仓库也可以看得出来,相信很多人和我一样,看到 kubernetes、client-go、api、apimachinery 等仓库就不知道如何下手。事实上,从 API 入手是比较简单的做法,特别是我们对于 kubernetes 核心组件的功能有了一定的了解之后。 接下来的几篇笔记,我将由浅入深地学习 kubernetes API 的设计以及背后的原理。我的计划是这样的: 初识 kubernetes API 的组织结构 深入 kubernetes API 的源码实现 扩展 kubernetes API 的典型方式 废话不多说,我们先来认识一下 kubernetes API 的基础结构以及背后的设计原理。 API-Server 我们知道 kubernetes 控制层面的核心组件包括 API-Server、 Controller Manager、Scheduler,其中 API-Server 对内与分布式存储系统 etcd 交互实现 kubernetes 资源(例如 pod、namespace、configMap、service 等)的持久化,对外提供通过 RESTFul 的形式提供 kubernetes API 的访问接口,除此之外,它还负责 API 请求的认证(authN)、授权(authZ)以及验证。刚提到的“对外”是相对的概念,因为除了像 kubectl 之类的命令行工具之外,kubernetes 的其他组件也会通过各种客户端库来访问 kubernetes API,关于官方提供的各种客户端库请查看 client-libraries 列表,其中最典型的是 Go 语言的客户端库 client-go。

使用 Go 从零开始实现 CNI

对于很多刚入坑云原生技术栈的同学来说,容器网络与 Kubernetes 网络一直很“神秘”,也是很多人容器技术上升曲线的瓶颈,但它也是我们深入走进云原生世界绕不过的话题。要彻底地搞清楚容器网络与 Kubernetes 网络,需要了解很多底层的网络概念,如 OSI 七层模型、Linux 网络栈、虚拟网络设备以及 iptables 等。 早在去年年初,我就对容器网络涉及到的基础概念做了一些列总结,小伙伴们请按需食用: Linux 数据包的接收与发送过程 Linux 虚拟网络设备 从 container 到 pod 容器网络(一) 容器网络(二) 浅聊 Kubernetes 网络模型 但是,这些总结还是停留在理论层面,如果不动手实践一下,这些知识的价值会大打折扣,而且很多技术只有在实际使用的时候我们才会发现理论和实践之间巨大的“鸿沟”。所以我其实也一直想着如何使用熟悉的语言来练手这些网络知识,但是因为事情太多而一拖再拖,直到上周我在查看一个 CNI Bug 的时候又快速过了一下官方 CNI 规范,这才有了使用 Go 语言从零开始实现一个 CNI 插件的契机。我的计划是: 简单回顾一下容器网络模型以及解读 Kubernetes 官方出品的 CNI 规范; 使用 Go 语言编写简单的 CNI 插件来实现 Kubernetes overlay 网络,功能包括 pod IP 地址的管理以及容器网络的配置; 编写 CNI 部署工具并在 Kubernetes 集群里面部署和测试我们的 CNI; 讨论潜在的问题以及未来的解决方案; Kubernetes 网络模型 不管是容器网络还是 Kubernetes 网络都需要解决以下两个核心问题: 容器/Pod IP 地址的管理 容器/Pod 之间的相互通信 容器/Pod IP 地址的管理包括容器 IP 地址的分配与回收,而容器/Pod 之间的相互通信包括同一主机的容器/Pod 之间和跨主机的容器/Pod 之间通信两种场景。这两个问题也不能完全分开来看,因为不同的解决方案往往要同时考虑以上两点。对于同一主机的容器/Pod 之间的通信来说实现相对容易,实际的挑战在于,不同容器/Pod 完全可能分布在不同的集群节点上,如何实现跨主机节点的通信不是一件容易的事情。

请回答「2020」

一转眼2020年就要过去了,依然记得12月初有人在朋友圈里发了这样一条状态更新: “距离迈进2021剩下不到一个月!” 我当时在想,大家应该都对于2020年都已经不再抱有希望,不再发“12月请善待我”之类的状态,而是直接呼唤“2021年”。 不用多想,2020年大事件的密度应该是前所未有的,我们的感官每天都被这这爆炸的信息量刷新着,如此魔幻的2020让我们见证了多少历史上的第一次。如果要为2020年做一个时间胶囊,里面应该放些什么?口罩?健康码?新冠疫苗?科比的球衣?打工人?月球土壤? 真的要我回答2020的话,我的关键字会是「波折」。 早在1月份的时候,在美国的大舅就提醒我们要注意武汉流感的发展,当时觉得也没有什么,反正对于在家工作这件事情也不排斥,甚至对自己的“独处”经验也是十分自信,对于可以自由支配的时间从来都是安排得明明白白。没想到一语成鉴,疫情的发展远远超出预期。到了3月份初,蔬菜水果也出现了供不应求的状况,每次都需要预约抢购,口罩价格不止水涨船高,关键是很难抢购得到,于是3月份出行受到了严重的限制。无奈最终只能求助于大舅从美国邮寄一批口罩回来,然而,等到口罩寄到国内的时候已经到了4月份,那是的疫情已经有所缓和。 也正是从3月份开始,不争气的智齿开始发炎,情况逐渐开始恶化,每天晚上都感觉有个电钻一直在钻腮帮子,太阳穴扯着神经痛,疼到无法集中化注意力思考,当时的精神状态蔫到极点。但是当时因为疫情原因不方便跑去医院,于是咬着牙坚持到了6月,医生看到拍的片子之后都觉得难以相信我坚持了那么久,最终把掉智齿也就用了不到5分钟(介于画面过于血腥,图片就不放出来了),但是整个人终于解脱了出来,瞬间感觉风轻云快。 然而,就当我以为这一切就像疫情开始好转的时候,没想到又被现实的巨浪无情地拍打。拔过智齿之后本想休几天假,不巧的是,时间点正好赶上另外一个产品组 Q2 的发布。这里有些背景需要解释一下,我们组所有云原生产品在 Q1 就率先完成了全面拥抱 Operator 的部署安装模式,也开始使用 OLM(Operator Lifecycle Manager) 与我们自研的 ODLM(Operand Deployment Lifecycle Manager) 来管理所有云原生产品的生命周期。这是一个非常重要的新特性,这意味着所有依赖于我们组云原生产品的上层产品都必须遵循相同的模式。于是我被临时抽调过去辅助他们完成 Q2 的发布版本。本来以为不会太艰难,但是他们产品组的混乱程度远超我的预期,6月份的产品发布被拖到7月底。在这期间打破了我在公司期间最长的加班记录,产品 upload 的前几天我后背开始出现过敏症状,我咬着牙坚持到了产品正式 GA 后的第一个周末去医院检查,才发现自己的了过敏性皮炎。 说实话,我此前一直对于自己的身体状态很有自信,周末高强度的打几个小时的球都轻轻松松,每天的运动量不够都不会上床休息,虽然偶尔饮食不规律,但从来没有出现过如此疲惫不堪的状况。现在,我要开始认真思考如何“养生”,到了这个年纪,是不是喝啤酒、吃冰淇淋都想放两粒枸杞? 回到工作,今年能够参与开源社区的时间明显减少了很多,这从我的 Github 提交历史就可以看出来: 主要原因是公司产品策略的变化,我自己也要跟着调整,以前注重的开源方向被挤压得基本没有了空间。这直接导致我在 istio 社区的活跃度明显降低,去年基本可以全勤参加 istio 社区 ENV WG 的夏令时周会,今年参加的次数屈指可数。但是,我也花时间接触了不少开源项目,包括迁移并成规模地开始应用 k8s 原生 CICD 系统 Prow,接管了目前已经开源的全部 Common Services 项目与部分 IBM MCM 项目的构建流水线,同时增加了 Prow 多平台构建的能力使之适应 IBM 的云原生产品构建目标。另外,年中的时候因为另外一个产品组的需求,使得我有机会深入了解 Kong API网关的功能与实现,还有它与其他 API 网关在用户场景上的区别,我甚至花了一段时间调研了 k8s 生态圈内的各种主流 API 网关的应用场景与实现细节,但是不久便被叫停。 2020年,光怪陆离,有人见尘埃,有人见星辰,虽然这一年之于我始终都是乱糟糟的,自己好像什么也没做好,尤其在这兵荒马乱的12月。但是新的转机已经暂露头角,希望这2020年所有的鸡毛蒜皮换成2021年的风和日丽。最后,以一句话祝福自己能在2021年“牛”转乾坤: 前路浩浩荡荡,万物皆可期待

浅聊 Kubernetes 网络模型

通过前面的一些列笔记,我们对各种容器网络模型的实现原理已经有了基本的认识,然而真正将容器技术发扬光大的是 Kubernetes 容器编排平台。Kubernetes 通过整合规模庞大的容器实例形成集群,这些容器实例可能运行在异构的底层网络环境中,如何保证这些容器间的互通是实际生产环境中首要考虑的问题之一。 Kubernetes 网络基本要求 Kubernetes 对容器技术做了更多的抽象,其中最重要的一点是提出 pod 的概念,pod 是 Kubernetes 资源调度的基本单元,我们可以简单地认为 pod 是容器的一种延伸扩展,从网络的角度来看,pod 必须满足以下条件: 每一个 pod 都有独特的 IP 地址,所有 pod 都在一个可以直接连通的、扁平的网络空间中; 同一个 pod 内的所有容器共享同一个网络命名空间; 基于这样的基本要求,我们可以知道: 同一个 pod 内的所有容器之间共享端口,可直接通过 localhost + port 来访问; 由于每个 pod 有单独的 IP,所以不需要考虑容器端口与主机端口映射以及端口冲突问题; 事实上,Kubernetes 进一步确定了对一个合格集群网络的基本要求: 任意两个 pod 之间其实是可以直接通信的,无需显式地使用 NAT 进行地址的转换; 任意集群节点与任意 pod 之间是可以直接通信的,无需使用明显的地址转换,反之亦然; 任意 pod 看到自己的 IP 跟别人看见它所用的 IP 是一样的,中间不能经过地址转换; 也就是说,必须同时满足以上三点的网络模型才能适用于 Kubernetes,事实上,在早期的 Kubernetes 中,并没有什么网络标准,只是提出了以上基本要求,只有满足这些要求的网络才可以部署 Kubernetes,基于这样的底层网络假设,Kubernetes 设计了pod-deployment-service 的经典三层服务访问机制。直到1.1发布,Kubernetes 才开始采用全新的 CNI(Container Network Interface) 网络标准。 CNI 其实,我们在前面介绍容器网络的时候,就提到了 CNI 网络规范,CNI 相对于 CNM(Container Network Model) 对开发者的约束更少、更开放,不依赖于容器运行时。事实上,CNI 规范确实非常简单,详情请移步至:https://github.

容器网络(二)

上一篇笔记中我们介绍的 bridge 网络模型主要用于解决同一主机间的容器相互访问以及容器对外暴露服务的问题,并没有涉及到怎么解决跨主机的容器之间互相访问的问题。 对于跨主机容器间的相互访问问题,我们能想到的最直观的解决方案就是直接使用宿主机网络,这时,容器完全复用复用宿主机的网络设备以及协议栈,容器 IP 就是主机 IP,这样,只要宿主机主机能通信,容器也就自然能通信。但是这样,为了暴露容器服务,每个容器需要占用宿主机上的一个端口,通过这个端口和外界通信。所以,就需要手动维护端口的分配,而且不能使不同的容器服务运行在一个端口上,正因为如此,这种容器网络模型很难被推广到生产环境。 因此解决跨主机通信的可行方案主要是让容器配置与宿主机不一样的 IP 地址,往往是在现有二层或三层网络之上再构建起来一个独立的 overlay 网络,这个网络通常会有自己独立的 IP 地址空间、交换或者路由的实现。由于容器有自己独立配置的 IP 地址,underlay 平面的底层网络设备如交换机、路由器等完全不感知这些 IP 的存在,也就导致容器的 IP 不能直接路由出去实现跨主机通信。 为了解决容器独立 IP 地址间的访问问题,主要有以下两个思路: 修改底层网络设备配置,加入容器网络 IP 地址的管理,修改路由器网关等,该方式主要和 SDN(Software define networking) 结合。 完全不修改底层网络设备配置,复用原有的 underlay 平面网络解决容器跨主机通信,主要有如下两种方式: 隧道传输(overlay):将容器的数据包封装到宿主机网络的三层或者四层数据包中,然后使用宿主机的 IP 或者 TCP/UDP 传输到目标主机,目标主机拆包后再转发给目标容器。overlay 隧道传输常见方案包括 VxLAN、ipip 等,目前使用 Overlay 隧道传输技术的主流容器网络有 Flannel 等。 修改主机路由:把容器网络加到宿主机网络的路由表中,把宿主机网络设备当作容器网关,通过路由规则转发到指定的主机,实现容器的三层互通。目前通过路由技术实现容器跨主机通信的网络如 Flannel host-gw、Calico 等。 技术术语 在开始之前,我们总结一些在容器网络介绍文章里面经常看到各种技术术语: IPAM(IP Address Management):即 IP 地址管理。IPAM 并不是容器时代特有的词汇,传统的标准网络协议比如 DHCP 其实也是一种 IPAM,负责从 MAC 地址分发 IP 地址;但是到了容器时代我们提到 IPAM,我们特指为每一个容器实例分配和回收 IP 地址,保证一个集群里面的所有容器都分配全局唯一的 IP 地址;主流的做法包括:基于 CIDR 的 IP 地址段分配地或精确为每一个容器分配 IP; overlay:在容器时代,就是在主机现有二层(数据链路层)或三层(IP 网络层)基础之上再构建起来一个独立的网络平面,这个 overlay 网络通常会有自己独立的 IP 地址空间、交换或者路由的实现; IPIP:一种基于 Linux 网络设备 TUN 实现的隧道协议,允许将三层(IP)网络包封装在另外一个三层网络包之上发送和接收,详情请看揭秘 IPIP 隧道; IPSec:跟 IPIP 隧道协议类似,是一种点对点的加密通信协议,一般会用到 overlay 网络的数据隧道里; VxLAN:最主要是解决 VLAN 支持虚拟网络数量(4096)过少的问题而由 VMware、Cisco、RedHat 等联合提出的解决方案;VxLAN 可以支持在一个 VPC(Virtual Private Cloud) 划分多达1600万个虚拟网络; BGP:主干网自治网络的路由协议,当代的互联网由很多小的 AS(Autonomous system)自治网络构成,自治网络之间的三层路由是由 BGP 协议实现的,简单来说,通过 BGP 协议 AS 告诉其他 AS 自己子网里都包括哪些 IP 地址段,自己的 AS 编号以及一些其他的信息; SDN(Software-Defined Networking):一种广义的概念,通过软件方式快速配置网络,往往包括一个中央控制层来集中配置底层基础网络设施; docker 原生 overlay docker 原生支持 overlay 网络用来解决容器间的跨主机通信问题,事实上,对于 docker 原生支持的 overlay 网络,Laurent Bernaille 在 DockerCon 2017上详细剖析了它的实现原理,甚至还有从头开始一步步实现该网络模型的实践教程,这三篇文章为:

容器网络(一)

容器网络需要解决的两大核心问题是: 容器 IP 地址的管理 容器之间的相互通信 其中,容器 IP 地址的管理包括容器 IP 地址的分配与回收,而容器之间的相互通信包括同一主机容器之间和跨主机容器之间通信两种场景。这两个问题也不能完全分开来看,因为不同的解决方案往往要同时考虑以上两点。 容器网络的发展已经相对成熟,这篇笔记先对主流容器网络模型做一些概述,然后将进一步对典型的容器网络模型展开实践。 CNM vs CNI 关于容器网络,docker 与 kubernetes 分别提出了不同的规范标准: docker 采用的 CNM(Container Network Model) kubernetes 支持的 CNI模型(Container Network Interface) CNM 基于 libnetwork,是 docker 内置的模型规范,它的总体架构如下图所示: 可以看到,CNM 规范主要定义了以下三个组件: Sandbox: 每个 Sandbox 包一个容器网络栈(network stack)的配置:容器的网口、路由表和 DNS 设置等,Sanbox 可以通过 Linux 网络命名空间 netns 来实现; Endpoint: 每个 Sandbox 通过 Endpoint 加入到一个 Network 里,Endpoint 可以通过 Linux 虚拟网络设备 veth 对来实现; Network: 一组能相互直接通信的 Endpoint,Network 可以通过 Linux网桥设备 bridge 或 VLAN 等实现 可以看到,底层实现原理还是我们之前介绍过的 Linux 虚拟网络设备、网络命名空间等。CNM 规范的典型场景是这样的:用户可以创建一个或多个 Network,一个容器 Sandbox 可以通过 Endpoint 加入到一个或多个 Network,同一个 Network 中容器 Sanbox 可以通信,不同 Network 中的容器 Sandbox 隔离。这样就可以实现从容器与网络的解耦,也就是锁,在创建容器之前,可以先创建网络,然后决定让容器加入哪个网络。

揭秘 IPIP 隧道

上一篇笔记中,我们在介绍 Linux 网络设备的时候简单地看到了一种通过 TUN/TAP 设备来实现 VPN 的方式,但是并没有实践 TUN/TAP 虚拟网络设备在 Linux 中具体是怎么发挥功能的。这篇笔记我们就来看看在云计算领域中如何基于TUN设备实现 IPIP 隧道。 IPIP 隧道 我们在之前的笔记中也提到了,TUN 网络设备能将三层(IP 网络数据包)数据包封装在另外一个三层数据包之中,这样通过 TUN 设备发送出来的数据包会像下面这样: MAC: xx:xx:xx:xx:xx:xx IP Header: <new destination IP> IP Body: IP: <original destination IP> TCP: stuff HTTP: stuff 这就是典型的 IPIP 数据包的结构。Linux 原生支持好几种不同的 IPIP 隧道类型,但都依赖于 TUN 网络设备,我们可以通过命令 ip tunnel help 来查看 IPIP 隧道的相关类型以及支持的操作: # ip tunnel help Usage: ip tunnel { add | change | del | show | prl | 6rd } [ NAME ] [ mode { ipip | gre | sit | isatap | vti } ] [ remote ADDR ] [ local ADDR ] [ [i|o]seq ] [ [i|o]key KEY ] [ [i|o]csum ] [ prl-default ADDR ] [ prl-nodefault ADDR ] [ prl-delete ADDR ] [ 6rd-prefix ADDR ] [ 6rd-relay_prefix ADDR ] [ 6rd-reset ] [ ttl TTL ] [ tos TOS ] [ [no]pmtudisc ] [ dev PHYS_DEV ] Where: NAME := STRING ADDR := { IP_ADDRESS | any } TOS := { STRING | 00.

从 container 到 pod

很多人应该像我一样,第一次接触 docker 的概念,都会见到或者听过下面这句话: docker 技术比虚拟机技术更为轻便、快捷,docker 容器本质上是进程 甚至我们或多或少都在潜移默化中接受了 container 实现是基于 linux 内核中 namespace 和 cgroup 这两个非常重要的特性。那么,namespace 和 cgroup 到底是怎么来隔离 docker 容器进程的呢?今天我们就来一探究竟。 container 在了解 docker 容器进程怎么隔离之前,我们先来看看 linux 内核中的 namespace 和 cgroup 到底是什么。 namespace 如果让我们自己实现一种类似于 vm 一样的虚拟技术,我们首先会想到的是怎么解决每个 vm 的进程与宿主机进程的隔离问题,防止进程权限“逃逸”。2008年发布的 linux 内核v2.6.24带来的命名空间(namespace)特性使得我们可以对 linux 做各种各样的隔离。 熟悉 chroot 命令的同学都应该大体能猜到 linux 内核中的 namespace 是如何发挥作用的,在 linux 系统中,系统默认的目录结构都是以根目录 / 开始的,chroot 命令用来以指定的位置作为根目录来运行指令。与此类似,了解 linux 启动流程的同学都知道在 linux 启动的时候又一个 pid 为1的 init 进程,其他所有的进程都由此进程衍生而来,init 和其他衍生而来的进程形成以 init 进程为根节点树状结构,在同一个进程树里的进程只要有足够的权限就可以审查甚至终止其他进程。这样,显然会带来安全性问题。而 pid namespace(linux namespace 的一种)允许我们创建单独的进程树,新进程树里面有自己的 pid 为1的进程,该进程一般为创建新进程树的时候指定。pid namespace 使得不同进程树里面的进程不能相互直接访问,提供了进程间的隔离,甚至可以创建嵌套的进程树:
❌