站在用户和应用程序的角度看操作系统操作系统内核是一个需要提供各种服务的软件，其服务对象是应用程序，而用户（这里可以理解为一般使用计算机的人）是通过应用程序的服务间接获得操作系统的服务的，因此操作系统内核藏在一般用户看不到的地方。但应用程序需要访问操作系统获得操作系统的服务，这就需要通过操作系统的接口才能完成。操作系统的接口形式就是应用程序二进制接口 (ABI, Application Binary Interface)。操作系统不能只提供面向单一编程语言的函数库的编程接口 (API, Application Programming Interface) ，它的接口需要考虑对基于各种编程语言的应用支持，以及访问安全等因素，使得应用软件不能像访问函数库一样的直接访问操作系统内部函数，更不能直接读写操作系统内部的地址空间。为此，操作系统设计了一套安全可靠的接口，我们称为系统调用接口 (System Call Interface)。系统调用接口通常面向应用程序提供了API的描述，但在具体实现上，还需要提供ABI的接口描述规范。[scode type="blue"]一些思考：提供ABI规范是便于底层开发人员进行驱动程序的开发，以及进行汇编级别的优化和高级编程语言运行库的开发等操作[/scode]在现代处理器的安全支持（特权级隔离，内存空间隔离等）下，应用程序就不能直接以函数调用的方式访问操作系统的函数，以及直接读写操作系统的数据变量。[scode type="red"]这里的不能直接以函数调用的方式访问操作系统的函数，我认为指的是操作系统内部使用的函数而非暴露出来的系统调用接口[/scode]不同类型的应用程序可以通过符合操作系统规定的ABI规范的系统调用接口，发出系统调用请求，来获得操作系统的服务。操作系统提供完服务后，返回应用程序继续执行。[scode type="share"]API 与 ABI 的区别应用程序二进制接口 ABI 是不同二进制代码片段的连接纽带。ABI 定义了二进制机器代码级别的规则，主要包括基本数据类型、通用寄存器的使用、参数的传递规则、以及堆栈的使用等等。ABI 与处理器和内存地址等硬件架构相关，是用来约束链接器 (Linker) 和汇编器 (Assembler) 的。在同一处理器下，基于不同高级语言编写的应用程序、库和操作系统，如果遵循同样的 ABI 定义，那么它们就能正确链接和执行。应用程序编程接口 API 是不同源代码片段的连接纽带。API 定义了一个源码级（如 C 语言）函数的参数，参数的类型，函数的返回值等。因此 API 是用来约束编译器 (Compiler) 的：一个 API 是给编译器的一些指令，它规定了源代码可以做以及不可以做哪些事。API 与编程语言相关，如 libc 是基于 C 语言编写的标准库，那么基于 C 的应用程序就可以通过编译器建立与 libc 的联系，并能在运行中正确访问 libc 中的函数。简单来说，API是编译器级别的，ABI是汇编级别的。[/scode]系统调用接口的一些比较重要的功能与相应的抽象对于系统调用接口提供的功能，下面列出了一些值得了解的功能：一个运行的程序可以创建另一个程序的实例吗？需要等待另外一个程序执行完成吗？操作系统能停止或恢复另一个正在运行的程序吗？一个运行的程序如何与连接到计算机的设备通信并通过它们与物理世界通信？多个运行的程序如何同步互斥地对共享资源进行访问？一个运行的程序可以要求更多（或更少）的内存空间吗？一个运行的程序如何持久地存储用户数据？一个运行的程序如何能输出字符信息？如何能获得输入字符信息？下面列出了一些相对比较重要的操作系统接口或抽象：进程（即程序运行过程）管理：复制创建进程 fork 、退出进程 exit 、执行进程 exec 等。线程管理：线程（即程序的一个执行流）的创建、执行、调度切换等。线程同步互斥的并发控制：互斥锁 mutex 、信号量 semaphore 、管程 monitor 、条件变量 condition variable 等。进程间通信：管道 pipe 、信号 signal 、事件 event 等。虚存管理：内存空间映射 mmap 、改变数据段地址空间大小 sbrk 、共享内存 shm 等。文件I/O操作：对存储设备中的文件进行读 read 、写 write 、打开 open 、关闭 close 等操作。外设I/O操作：外设包括键盘、显示器、串口、磁盘、时钟 … 但接口均采用了文件 I/O 操作的通用系统调用接口。[scode type="share"]上述表述在某种程度上说明了操作系统对计算机硬件重要组成的抽象和虚拟化，使得应用程序只需基于对简单的抽象概念的访问来达到对计算机系统资源的使用：文件 (File) 是外设的一种抽象和虚拟化。特别对于存储外设而言，文件是持久存储的抽象。地址空间 (Address Space) 是对内存的抽象和虚拟化。进程 (Process) 是对计算机资源的抽象和虚拟化。而其中最核心的部分是对CPU的抽象与虚拟化。[/scode]有了这些系统调用接口，简单的应用程序就不用考虑底层硬件细节，可以在操作系统的服务支持和管理下简洁地完成其应用功能了。
前言首先，关于集群管理软件我在很早之前就已经听说过kubernetes了，但是一直没有尝试去使用过，恰巧最近想要搭建一个订阅链接转换的服务，所以就刚好拿搭建集群来练练手，毕竟刚好手上一台4H8G的服务器。出师不利因为我只有一台服务器，所以觉得也没必要使用k8s，于是我瞄准了k3s的搭建，在安装的开始我就碰到了一个难题。之前对集群的一些概念了解的并不是很清楚，没想到集群的ingress controller会直接抢占端口号。然后k3s的默认安装是会安装一个叫做traefik的ingress controller，监听主机上的80和443的端口流量并接入到集群内部，而我的服务器上正用nginx跑着我的博客网站，所以直接导致网站down了整整一天，后来才发现是集群接管流量导致的问题。通过查看k3s的官方文档，我才找到如何关闭掉traefik自动部署的方法一般我们安装的默认参数如下curl -sfL https://get.k3s.io | sh - 然后我们加上--disable traefik就可以不安装traefik因为我想使用etcd数据库作为集群内数据管理工具，所以加上K3S_DATASTORE_ENDPOINT=etcd作为环境变量传入最后安装参数如下curl -sfL https://get.k3s.io | K3S_DATASTORE_ENDPOINT=etcd sh -s - --disable traefik这样就得到了一个最基本的k3s集群了安装kubernetes-dashboard为了更好地管理集群，我首先尝试安装kubernetes-dashboard，从而使得集群化管理拥有一个图形化页面。这一方面也是我碰到的第二座大山，如何将集群内的服务代理出去，虽然可以对集群内服务设置tls证书等等的操作，也可以把相关的service设置成NodePort，但是NodePort只会监听127.0.0.1这个地址，而要使得外网能够访问内部服务，必须要借助别的手段，于是我想到了nginx的反向代理。在nginx配置好相应的端口和证书，然后进行形同如下的配置location ^~ /xxx/ { proxy_pass https://127.0.0.1:xxxx/; # 转发规则 proxy_set_header Host $proxy_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }这样就可以访问相应域名的某个路由，然后将请求转发至127.0.0.1:xxxx/xxx了这里需要注意的一点就是，举个例子如果上面的/xxx/是/abc/，域名是www.onesnowwarrior.cn，那么当我们访问https://www.onesnowwarrior.cn/abc时，就会将请求转发至https://127.0.0.1:xxxx/。而如果是/abc而不是/abc/，则会转发到https://127.0.0.1:xxxx/abc。然后就是为了后面的目录跟随，我们不能使用~而是^~并且在proxy_pass写下的URL最后加上一个/这样配置之后，便可以使得反向代理生效。rancher配置rancher这一块的配置，我借助了helm进行应用管理，helm是k8s集群应用的管理工具，可以帮助快速部署，具体流程和kubernetes-dashboard其实差不多，但是当我进行反向代理配置的时候，碰到了又一个问题。就是在登录的时候，发现按照上述的反向代理配置会使得其请求127.0.0.1，而我们知道，在远程的本地哪里来的127.0.0.1拥有rancher的相关API。于是我猜想是我的反向代理有问题，于是经过了查阅无数文档，终于找到了相应的解决方法，并且还发现配置rancher的反向代理还需要配置ws相应的header location / { proxy_pass https://127.0.0.1:xxxx; # 转发规则 proxy_set_header Host $http_host; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; }这里就会想，$http_host和$proxy_host有啥区别呢，为啥会导致错误的产生。其实，主要是因为$proxy_host取值为proxy_pass中的值，然后这个值的确是本地的值，也即127.0.0.1，所以才使得请求判断的Host产生了错误，而$http_host则取自我们访问的网址的Host，从而可以得到正确的值。小结最后，经过这三座大山的跨越，我对集群网络管理，deployment，service，secret，pod，replicaset等概念有了一个比较清晰的了解，也算是得到了一定的收获吧。并且还解决了对于我个人来讲的一个比较偏的需求（网上关于这一块基本没有什么解决方案）