开源函数计算平台Refunc

Serverless介绍

云计算服务从IaaS到PaaS到如今炙手可热的Serverless,服务对象始终是计算资源使用者。Serverless是一种让开发者无需关心基础设施(服务器等),而是专注到应用程序业务逻辑上的计算服务模型。

加州大学伯克利分校在论文中尝试给出Serverless的定义:Serverless computing = FaaS + BaaS。

Serverless 包含两个组成部分 BaaS(后端即服务)和 FaaS(函数即服务)。对象存储、关系型数据库以及 MQ 等基础支撑服务属于BaaS。FaaS为开发人员提供了一种运行应用的抽象方式,在层次上更靠近应用程序开发者。

FaaS理解

FaaS为开发人员提供了一种运行应用的抽象方式,可以在无需管理服务器的情况下响应事件。例如,上载文件可触发自定义代码,从而将文件转码为各种格式。

FaaS通过事件驱动执行,它会随时待命,但不需要任何服务器进程在后台持续运行。当请求到来时,FaaS在毫秒内启动服务并处理各个请求,当请求减少时,FaaS会自动减少服务副本数量甚至关停服务。

动态扩缩容使FaaS在成本效益上颇具弹性空间,提供商可以仅对使用的资源收费,而不对空闲时间收费。

FaaS平台设计

从服务形态看,FaaS平台需要实现几个关键部分:

  • 函数环境与执行
  • 从0到N动态扩缩容
  • 事件驱动框架

函数环境可以分两个点,系统环境和函数依赖,系统环境主要指系统平台、RootFS等,函数依赖是用户代码本身依赖的第三方库等。函数执行需要为用户代码分配计算实体,具体可以是进程、容器、虚拟机、Wasm等等。

动态扩缩容是FaaS平台对函数状态感知到反馈的一个体现,可以收集函数执行环境和依赖的服务指标来实现,比如采集CPU、内存、带宽、IOPS等等指标判断函数负载采取扩缩容行为。

函数执行依靠事件驱动,事件框架是事件采集、传输的关键。所以设计FaaS平台事件框架需要考虑扩展性,事件传输效率,事件采集协议等等问题。

开源函数计算平台Refunc

先上项目地址: https://github.com/refunc/refunc

Refunc是一个基于Kubernetes的开源函数计算平台,架构如下图:

基于Kubernetes开发实现,核心的CRD包括:

  • Funcdef 函数定义
  • Funcinsts 函数实例
  • Xenv 运行环境
  • Trigger 触发器

工作原理概括为,基于Funcdef和Xenv解决函数环境与执行,定义的Trigger收集事件通过基于Nats的事件框架传递到Funcinsts驱动函数执行并返回结果,核心Operator扩展了Kubernetes的HPA实现了从0到N的函数动态扩缩容。

Refunc拥抱AWS Lambda生态,函数Runtime完全兼容Lambda,基于Lambda的Runtime生态支持各种编程语言,平台接口API兼容Lambda核心接口,完全可以使用aws官方cli工具。

阅读全文

2021年度总结

2021年度总结

Q1

  • Kubernetes网络
    • Calico BGP实现研究
  • 应用型负载均衡系统设计
  • 分布式文件系统研究
    • juicefs部分源码研究

Q2

  • 应用型负载均衡系统实现
  • Kubernetes部分源码研究
    • 定制编译k3s
    • kubernetes分享
  • Openresty/Nginx研究
    • openresty lua 模块开发

Q3

  • Linux网络栈研究
    • 防火墙/iptables工具/容器网络深入
  • CI/CD引擎调研
    • Cyclone部分源码研究
  • 容器运行时研究
    • Containerd部分源码研究
    • Runc部分源码研究
  • 容器网路研究
    • CNM/CNI部分实现研究
  • PHP-FPM部分源码研究
    • Web开发/PHP技术分享

Q4

  • CI/CD构建系统设计与实现
  • Serverless/FaaS平台建设
阅读全文

Nginx扩展之Openresty

Nginx模块

在前面文章nginx编译安装中我们提到,nginx模块是提前编译进去的,所以如果要基于nginx做扩展开发,就要相对比较收悉nginx代码结构和编译过程。

虽然未来nginx版本即使支持动态链接库,使用C语言写nginx扩展门槛也同样高。

复习一下,查询编译安装nginx模块信息,使用-V参数即可

nginx -V
nginx version: openresty/1.19.3.2
built by gcc 9.3.1 20200408 (Red Hat 9.3.1-2) (GCC)
built with OpenSSL 1.1.1k  25 Mar 2021
TLS SNI support enabled
configure arguments: --prefix=/usr/local/openresty/nginx ... --with-http_v2_module --without-mail_pop3_module --without-mail_imap_module --without-mail_smtp_module --with-http_stub_status_module --with-http_realip_module --with-http_addition_module --with-http_auth_request_module --with-http_secure_link_module --with-http_random_index_module --with-http_gzip_static_module --with-http_sub_module --with-http_dav_module --with-http_flv_module --with-http_mp4_module --with-http_gunzip_module --with-threads --with-compat --with-stream --with-http_ssl_module

可以看到在configure arguments中详细列出了我们当前nginx中已经编译进去的模块。

Openresty

使用C并且重新编译nginx,对于我们基于nginx做业务开发,造成了诸多不便,开发测试、上线更新相对麻烦。Openresty的解决方式是首先使用C写了lua-nginx-module,将luajit嵌入了nginx中,打开了扩展的大门,然后业务开发模块可以使用lua脚本语言来开发。

openresty的官方解释为一个基于nginx的可编写脚本的Web平台,除了核心的lua-nginx-moduleopenresty还包含维护了非常多lua编写的第三方模块,开箱即用,很方便我们做业务定制开发。

安装Openresty

官方推荐安装方式为通过软件源二进制包安装,因为编译lua-nginx-module还是非常麻烦的,对于我们使用lua快速实现业务来说,这块精力可以不花,如果要深入openresty可以尝试完全从源码编译安装。

# add the yum repo:
wget https://openresty.org/package/centos/openresty.repo
sudo mv openresty.repo /etc/yum.repos.d/

# update the yum index:
sudo yum check-update

sudo yum install openresty

通过官方源安装openresty非常简单,而且安装之后所有文件都在/usr/local/openresty下面,非常简洁:

$ ls -al
总用量 64
drwxr-xr-x  10 root root  4096 6月   3 11:46 .
drwxr-xr-x. 16 root root  4096 6月   3 11:46 ..
drwxr-xr-x   2 root root  4096 6月   3 11:46 bin
-rw-r--r--   1 root root 22924 6月   1 13:11 COPYRIGHT
drwxr-xr-x   6 root root  4096 6月   3 11:46 luajit
drwxr-xr-x   5 root root  4096 6月   3 11:46 lualib
drwxr-xr-x   6 root root  4096 6月   3 11:46 nginx
drwxr-xr-x   4 root root  4096 6月   3 11:46 openssl111
drwxr-xr-x   3 root root  4096 6月   3 11:46 pcre
drwxr-xr-x   3 root root  4096 6月   3 11:46 site
drwxr-xr-x   3 root root  4096 6月   3 11:46 zlib

注意到这个目录下有opensslpcre两个目录,回顾我们编译安装nginx提到的依赖,可见openresty的源已经帮我们解决好了这个依赖,统一放到当前目录下。

启动Openresty

安装完openresty我们完全可以使用openresty命令替换nginx命令,所有的参数都一样,准备一个conf文件启动。

# mkdir -p /tmp/nginx/logs
# /tmp/nginx/demo.conf

worker_processes  1;

error_log logs/error.log debug;

events {
    worker_connections 1024;
}

daemon off;

http {

    server {
        listen 9090;
        location / {
            content_by_lua_block {
                ngx.say("hello world")
            }
        }
    }

}

# openresty -p /tmp/nginx -c /tmp/nginx/demo.conf

另起终端测试

curl -v http://127.0.0.1:9090
*   Trying 127.0.0.1:9090...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 9090 (#0)
> GET / HTTP/1.1
> Host: 127.0.0.1:9090
> User-Agent: curl/7.68.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Server: openresty/1.19.3.2
< Date: Fri, 18 Jun 2021 09:04:56 GMT
< Content-Type: text/plain
< Transfer-Encoding: chunked
< Connection: keep-alive
<
hello world
* Connection #0 to host 127.0.0.1 left intact

Lua扩展编写

在上面demo.conf中我们已经实现了openresty lua的hello world,值得注意的是我们使用了一个content_by_lua_block指令,这个指令是lua-nginx-module提供的,意思为这个location响应内容通过执行lua产生。

在介绍更多指令之前我们先看一下lua-nginx-module的在nginx中的的流程图:

图中从上到下描述了lua-nginx-module可作用的:启动初始化lua、处理请求rewrite/access、产生响应、记录日志阶段,以及对应阶段可使用的指令。

每一个阶段的*_by_lua指令后面,我们可以引用我们的lua脚本,或者直接写片段的lua代码。对应的我们可以在lua脚本中实现请求当前阶段的业务逻辑,比如可以在access阶段做安全认证等逻辑。

更多指令: https://github.com/openresty/lua-nginx-module#directives

openresty除了实现lua-nginx-module开启了执行lua脚本能力外,还将许多nginx api直接封装进了lua,我们在实现业务逻辑的时候在脚本中可以直接使用,比如在上面demo.conf中我们调用的ngx.say(),便是一个类似print的api。

更多nginx lua api: https://github.com/openresty/lua-nginx-module#nginx-api-for-lua

总结

总的来说,使用openresty来做基于nginx的业务开发,门槛已经大大降低了,几乎不用去熟悉nginx的代码和数据结构。了解lua-nginx-module提供的指令、封装好的api、在nginx中的作用流程,是入坑的开始。

相关项目

  • ingress-nginx-controller

kubernetes社区官方基于lua能力实现的ingress-controller,使用lua主要解决动态upstream的问题。
https://github.com/kubernetes/ingress-nginx

  • kong

基于nginx+lua实现的api-gateway。
https://github.com/kong/kong

阅读全文

Linux网络-netfilter-iptables介绍

在Linux网络数据包的接收过程和数据包的发送过程两篇文章介绍数据包收发流程中, 已经提到了在内核协议栈处理包过程中在不同阶段提供了不同的hook点,在hook点上可以注册函数来自定义包处理逻辑,内核提供的这些hook点便是众多linux防火墙软件的基础。

在 Linux 生态系统中,iptables 是使用很广泛的防火墙工具之一,它基于内核的包过滤框架(packet filtering framework) netfilter。

netfilter最早是社区开发的防火墙框架,在内核2.4.x版本之后合并入了内核。

https://www.netfilter.org/

netfilter和iptables的关系可以简单理解为:

  • netfilter是内核态的包过滤框架。
  • iptables是用户态下操作包过滤规则的工具。

netfilter

netfilter 提供了 5 个 hook 点。包经过协议栈时会触发内核模块注册在这里的处理函数 。触发哪个 hook 取决于包的方向(是发送还是接收)、包的目的地址、以及包在上一个 hook 点是被丢弃还是拒绝等等。

  • NF_IP_PRE_ROUTING: 接收到的包进入协议栈后立即触发此 hook,在进行任何路由判断 (将包发往哪里)之前
  • NF_IP_LOCAL_IN: 接收到的包经过路由判断,如果目的是本机,将触发此 hook
  • NF_IP_FORWARD: 接收到的包经过路由判断,如果目的是其他机器,将触发此 hook
  • NF_IP_LOCAL_OUT: 本机产生的准备发送的包,在进入协议栈后立即触发此 hook
  • NF_IP_POST_ROUTING: 本机产生的准备发送的包或者转发的包,在经过路由判断之后, 将触发此 hook

iptables

iptables 使用 table 来组织规则,根据用来做什么类型的判断(the type of decisions they are used to make)标准,将规则分为不同 table。例如,如果规则是处理 网络地址转换的,那会放到 nat table;如果是判断是否允许包继续向前,那可能会放到 filter table。

在每个 table 内部,规则被进一步组织成 chain,内置的 chain 是由内置的 hook 触发 的。chain 基本上能决定(basically determin)规则何时被匹配。

  • PREROUTING: 由 NF_IP_PRE_ROUTING hook 触发
  • INPUT: 由 NF_IP_LOCAL_IN hook 触发
  • FORWARD: 由 NF_IP_FORWARD hook 触发
  • OUTPUT: 由 NF_IP_LOCAL_OUT hook 触发
  • POSTROUTING: 由 NF_IP_POST_ROUTING hook 触发

先来看看 iptables 提供的 table 类型。这些 table 是按规则类型区分的。

  • filter table 是最常用的 table 之一,用于判断是否允许一个包通过
  • nat table 用于实现网络地址转换规则。
  • mangle (修正)table 用于修改包的 IP 头。
  • raw table 定义的功能非常有限,目前其唯一目的就是提供一个让包绕过连接跟踪的框架。
  • security table 的作用是给包打上 SELinux 标记。

连接跟踪

连接跟踪是许多网络应用的基础。例如,Kubernetes Service、ServiceMesh sidecar、 软件四层负载均衡器 LVS/IPVS、Docker network、OVS、iptables 主机防火墙等等,都依赖连接跟踪功能。

连接跟踪所做的事情就是发现并跟踪这些连接的状态,具体包括:

  • 从数据包中提取元组(tuple)信息,辨别数据流(flow)和对应的连接(connection)
  • 为所有连接维护一个状态数据库(conntrack table),例如连接的创建时间、发送 包数、发送字节数等等
  • 回收过期的连接(GC)
  • 为更上层的功能(例如 NAT)提供服务

Linux 的连接跟踪是在 Netfilter 中实现的。

阅读全文