1. 前言
Serverless概念首次出现在2012年,Kubeless是基于Kubernetes的原生无服务器框架,其允许用户部署少量的代码(函数),而无需担心底层架构。它被设计部署在Kubernetes集群之上,并充分利用Kubernetes的特性及资源类型。
由于 Kubeless 的功能特性是建立在Kubernetes上的,所以对于熟悉 Kubernetes的人来说非常容易部署 Kubeless, 其主要实现是将用户编写的函数在Kubernetes中转变为 CRD( Custom Resource Definition,自定义资源), 并以容器的方式运行在集群中。
源码:https://github.com/kubeless/kubeless/
2.Kubeless架构
2.1 Kubeless基本组成
Kubeless主要由以下三部分组成:
- Functions
- Triggers
- Runtime
2.1.1Functions
Functions 表示要执行的代码,即为函数,在Kubeless中函数包含有关其运行时的依赖、构建等元数据。函数具有独立生命周期,并支持以下方法:
(1)Deploy: Kubeless 将函数部署为 Pod的形式运行在Kubernetes集群中,此步骤会涉及构建函数镜像等操作。
# kubeless function deploy 函数名 --runtime 运行环境 --from-file 文件名 --handler 文件(或类).方法
$ kubeless function deploy hello --runtime python3.8 --from-file hello.py --handler hello.hello
(2)Execute:执行函数,不通过任何事件源调用。
# kubeless function call 函数名 --data '参数'
$ kubeless function call hello --data '{"hello":"world"}'
(3)Update:修改函数元数据。
# kubeless function update 函数名 -f 文件名
$ kubeless function update hello -f hello.py
(4)Delete:在Kubernetes集群中删除函数的所有相关资源。
# kubeless function delete 函数名
$ kubeless function delete hello
(5)List:显示函数列表。
$ kubeless function ls
(6)Logs:函数实例在Kubernetes中生成及运行的日志。
# kubeless function logs 函数名
$ kubeless function logs hello
2.1.2Triggers
Triggers表示函数的事件源,当事件发生时,Kubeless确保最多调用一次函数,Triggers可以与单个功能相关联,也可与多个功能相关联,具体取决于事件源类型。Triggers与函数的生命周期解耦,可以进行如下操作:
(1)Create:使用事件源和相关功能的详细信息创建 Triggers。
(2)Update: 更新 Triggers元数据。
(3)Delete:删除Triggers及为其配置的任何资源。
(4)List:显示Triggers列表。
$ kubeless trigger --help
$ kubeless trigger http --help
2.1.3Runtime
函数使用语言因不同用户的喜好通常多样化, Kubeless 为用户带来了几乎所有的主流函数运行时, 目前含有:
(1) ballerina:0.981.0
(2)dotnetcore:即 .NET,2.0、2.1、2.2、3.1
(3)go : 1.13、1.14
(4)java:1.8、11
(5)python:3.6、3.7、3.8
(6)ruby:2.3、2.4、2.5、2.6
(7)nodejs:10、12、14
(8)php: 7.2、7.3、7.4
在Kubeless中,每个函数运行时都会以镜像的方式封装在容器镜像中,通过在Kubeless配置中引用这些镜像来使用。
2.2 Kubeless设计方式
Kubeless利用Kubernetes中的许多概念来完成对函数实例的部署。主要使用了 Kubernetes以下特性 :
(1) CRD( 自定义资源) 用于表示函数。
(2) 每个事件源都被当作为一个单独的 Trigger CRD 对象。
(3) CRD Controller 用于处理与 CRD 对象相应的 CRUD 操作。
(4) Deployment/Pod 运行相应的运行时。
(5) ConfigMap 将函数的代码注入运行时的 Pod。
kind: ConfigMap
apiVersion: v1
metadata:
name: hello
namespace: kubeless
selfLink: /api/v1/namespaces/kubeless/configmaps/hello
uid: d749e006-6cac-4e2f-b485-e63fb611c7a4
resourceVersion: '159802'
creationTimestamp: '2021-08-28T10:14:27Z'
labels:
created-by: kubeless
function: hello
ownerReferences:
- apiVersion: kubeless.io/v1beta1
kind: Function
name: hello
uid: d78578de-6369-462a-a25c-3f08d7b07a45
data:
handler: hello.hello
hello.py: |
def hello(event,context):
return event['data']["hello"].upper()
requirements.txt: ''
(6) Init-container 加载函数的依赖项。主要作用如下:
1. 将源码和依赖文件拷贝到指定目录;
2. 安装第三方依赖。
(7) 使用Service在集群中暴露函数( ClusterIP)。
(8) 使用Ingress资源对象暴露函数到外部,以实现函数的HTTP Trigger。
(9)基于CronJob实现函数的CronJobTrigger。
3.部署Kubeless
部署Kubeless前需要先部署k8s。
从零开始学习部署K8S: https://blog.csdn.net/shyflea/article/details/119758389
Kubeless的部署方法如下:
1、创建命名空间,创建kubeless 控制管理容器
$ cd home
$ kubectl create ns kubeless
# 安装方便切换空间的kubens
$ git clone https://hub.fastgit.org/ahmetb/kubectx.git ##clone项目
$ cp kubectx/kube* /usr/local/bin/ ##把相关文件放到可执行目录里
$ kubens kubeless
#根据官方提供的yaml ,创建Kubeless Controller Manager容器 (1.0.8为笔者安装时的最新版本)
$ kubectl create -f https://github.com/kubeless/kubeless/releases/download/v1.0.8/kubeless-v1.0.8.yaml
#kubless 空间下,可以查看到如下pod
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
kubeless-controller-manager-666ffb749-tzv57 3/3 Running 3 16h
2、下载、配置kubeless 客户端
# 下载地址 https://github.com/kubeless/kubeless/releases/
# linux版本选择v1.0.8的kubeless_linux-amd64.zip
$ rz
# 上传后解压
$ yum install unzip
$ unzip kubeless_linux-amd64.zip
$ cp bundles/kubeless_linux-amd64/kubeless /usr/local/bin/
4.函数创建
在Kubeless上部署函数的过程可分为以下三步:
- Kubeless CLI 读取用户输入的函数运行配置, 产生一个 Function 对象,并将其提交给Kubernetes API Server。
- Kubeless Function Controller(运行在Kubeless Controller Manager中, 安装完Kubeless后在集群中默认存在的 Deployment, 用于监听及处理函数的相应事件) 监测到有新的函数被创建并且读取函数信息,由提供的函数信息 Kubeless首先产生一个带有函数代码及其依赖关系的ConfigMap,再产生一个用于内部通过HTTP或其它方式访问的 Service,最后产生一个带有基本镜像的Deployment ,以上生成的顺序十分重要,因为若 Kubeless 中的Controller无法部署ConfigMap 或 Service,则不会创建Deployment。任何步骤中的失败都会中止该过程。
- 创建完函数对应的Deployment后, 集群中会跑一个对应的 Pod, Pod在启动时会动态的读取函数中的内容。
具体的创建方法如下:
4.1创建python函数
4.1.1创建函数
创建hello.py 文件,文件内容如下:
# 将传入参数hello的值转换成大小
def hello(event,context):
return event['data']["hello"].upper()
Kubeless 中的函数具有相同的格式,而与函数的语言或事件源无关。通常,每个函数:
- 接收一个对象 event 第一个参数。此参数包括有关事件源的所有信息(如:data、 event-id、 event-type、 event-time、 event-namespace 等)。特别是,键 ‘data’ 应包含功能请求的主体。
- 接收 context 带有有关该函数的常规信息(如:function-name、 timeout、runtime 等)为第二个对象。
- 返回用来响应调用者的字符串或对象。
#kubeless 空间下,运行一下
$ kubeless function deploy hello --runtime python3.8 --from-file hello.py --handler hello.hello
#任务正常执行,返回如下:
INFO[0000] Deploying function...
INFO[0000] Function hello submitted for deployment
INFO[0000] Check the deployment status executing 'kubeless function ls hello'
- hello:这是函数的名称。
- –runtime python3.8:这里指定了运行时。更多运行时可以通过 kubeless get-server-config 命令查看。
- –from-file hello.py:这个文件中包含了函数代码。
- –handler hello.hello:这里指定接收请求时使用的文件和函数。在这个示例中,我们使用 hello.py 文件中的函数 hello。
#查看下刚跑的function (首次可能比较长时间处于noready状态,请耐心等待)
$ kubeless function ls hello -o wide
AME NAMESPACE HANDLER RUNTIME TYPE TOPIC DEPENDENCIES STATUS MEMORY ENV LABEL SCHEDULE
hello kubeless hello.hello python3.8 1/1 READY created-by : kubeless
function : hello
查看K8S控制台,在kubeless命名空间下可以看到该函数
4.1.2执行函数
不通过任何事件源调用
$ kubeless function call hello --data '{"hello":"world"}'
#返回
WORLD
如果需要修改函数,可以通过如下命令重新部署修改后的代码
$ kubeless function update hello -f hello.py
4.2创建java函数
4.2.1创建函数
创建java类Hello.java,内容如下
package io.kubeless;
import io.kubeless.Event;
import io.kubeless.Context;
import org.joda.time.LocalTime;
public class Hello {
public String sayHello(io.kubeless.Event event, io.kubeless.Context context) {
System.out.println(event.Data);
LocalTime currentTime = new LocalTime();
return "Hello world! Current local time is: " + currentTime;
}
}
创建pom.xml文件,内容如下:
注意:除了新增加依赖的包,其他的不要变!该POM文件将替换kubeless中自带的pom文件
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!--注意artifactId和name不要变-->
<artifactId>function</artifactId>
<name>function</name>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.kubeless</groupId>
<artifactId>params</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
<parent>
<groupId>io.kubeless</groupId>
<artifactId>kubeless</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
</project>
部署
$ kubeless function deploy say-hello-java --runtime java1.8 --handler Hello.sayHello --from-file Hello.java --dependencies pom.xml
查看是否部署成功
$ kubeless function ls say-hello-java -o wide
查看异常
$ kubectl get pods say-hello-java
$ kubectl describe pods say-hello-java-66b47d7844-l2fq5
4.2.1执行函数
$ kubeless function call say-hello-java --data "This is kubeless demo" -n kubeless
# 正常执行,返回如下:
This is kubeless demo! Current local time is: 06:46:10.235
4.3其他语言样例
kubeless提供了各个语言的demo。
https://github.com/kubeless/kubeless/tree/master/examples
5.图形化管理界面
kubeless提供了一个简单的管理页面,支持新增、修改、删除、执行函数。
Ps:通过管理页面新增函数体验很差,笔者尚未成功过。
源码地址:https://github.com/kubeless/kubeless-ui
安装方法如下:
#这里默认空间为kubeless。由于网络问题,有时候会连接不上,此时建议直接下载
$ kubectl create -f https://raw.githubusercontent.com/kubeless/kubeless-ui/master/k8s.yaml
#可以查看下状态,如下Running已经安装了
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
hello-684545b7c6-sf6sz 1/1 Running 0 16m
kubeless-controller-manager-59d484f4d-5v7nv 3/3 Running 0 59m
ui-698c9989-tlw2b 2/2 Running 0 80s
#看下服务,可以看到服务的虚拟IP以及对外暴露的端口
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
hello ClusterIP 10.1.98.85 <none> 8080/TCP 48m
ui NodePort 10.1.132.250 <none> 3000:31236/TCP 23m
页面如下:
6.触发器
要从外部调用已部署的函数,需要创建触发器。一个函数可以有多个触发器,但每个触发器只引用一个已部署的函数。kubeless支持如下几种触发器:
触发方式 | 类别 |
---|---|
kubeless CLI | 客户端触发器 |
Http Trigger | http触发器 |
Cronjob Trigger | 定时任务触发器 |
Kafka Trigger | 发布/订阅触发器 |
Nats Trigger | 发布/订阅触发器 |
Kinesis Trigger | 数据流触发器 |
触发器的原理如下:
6.1HTTP trigger
如果希望通过发送 HTTP 请求触发函数执行,需要为函数创建 HTTP 触发器。 Kubeless 利用 K8s ingress 机制实现了 http trigger。Kubeless 创建了一个名为httptriggers.kubeless.io
的 CRD 来代表 http trigger 对象。同时,kubeless 包含一个名为http-trigger-controller
的 CRD controller,它会持续监听针对 http trigger 和 function 的 ADD、UPDATE、DELETE 事件,并执行对应的操作。
6.1.1部署ingress
创建http trigger之前需要先部署ingress。 当http触发器启动时, 会自动注册到Ingress中,通过Ingress 对外提供服务。
nginx-ingress-controller镜像下载与重命名
# 镜像下载&重命名
$ docker pull registry.cn-beijing.aliyuncs.com/google_registry/nginx-ingress-controller:0.30.0
$ docker tag 89ccad40ce8e quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.30.0
$ docker rmi registry.cn-beijing.aliyuncs.com/google_registry/nginx-ingress-controller:0.30.0
# 查看镜像
$ docker images quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.30.0
REPOSITORY TAG IMAGE ID CREATED SIZE
quay.io/kubernetes-ingress-controller/nginx-ingress-controller 0.30.0 89ccad40ce8e 18 months ago 323MB
下载ingress-nginx源码,并部署
$ wget https://github.com/kubernetes/ingress-nginx/archive/nginx-0.30.0.tar.gz
$ tar xf nginx-0.30.0.tar.gz
$ cp -a ingress-nginx-nginx-0.30.0/deploy/static/mandatory.yaml ./
# yaml文件配置修改
$ vim mandatory.yaml
kind: DaemonSet # 191行从Deployment改为DaemonSet
spec:
#replicas: 1 # 199行注释掉
nodeSelector:
kubernetes.io/hostname: master1 # 217行修改选择的节点为主节点
# 如下几行为新加行 作用【允许在master节点运行】
tolerations:
- key: node-role.kubernetes.io/master
effect: NoSchedule
ports:
- name: http
containerPort: 80
hostPort: 80 # 添加处【可在宿主机通过该端口访问Pod】
protocol: TCP
- name: https
containerPort: 443
hostPort: 443 # 添加处【可在宿主机通过该端口访问Pod】
protocol: TCP
# 启动
$ kubectl apply -f mandatory.yaml
查看ingress-nginx组件状态
# pod状态
$ kubectl get pods -n ingress-nginx
# service状态及暴露端口
$ kubectl get svc -n ingress-nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
default-http-backend ClusterIP 10.96.87.65 <none> 80/TCP 1m
ingress-nginx NodePort 10.100.48.237 <none> 80:32080/TCP,443:32443/TCP 1m
6.1.2创建HTTP trigger
以下命令将为函数 hello 创建一个名为http-hello
的 http trigger,并指定选用 nginx 作为 gateway。
$ kubeless trigger http create http-hello --function-name hello --gateway nginx --path echo --hostname shyflea.com
1、服务器上验证
服务器添加host:
$ vi /etc/hosts
192.168.159.101 shyflea.com
调用如下:
$ curl --data '{"hello": "world"}' \
--header "Host: shyflea.com" \
--header "Content-Type:application/json" \
shyflea.com/echo
# 返回
WORLD
2、客户端上验证
电脑上添加host
C:\Windows\System32\drivers\etc\hosts文件
192.168.159.101 shyflea.com
使用postman调用
6.2Cronjob trigger
cronjob 触发器用来定期触发函数执行。
原理:K8s 支持通过 CronJob定期运行任务,kubeless 利用这个特性实现了 cronjob trigger。
示例:
1、编写函数,上传到服务器上
time_print.py
import time
def print_time(event, context):
now = time.localtime()
nowt = time.strftime("%Y-%m-%d %H:%M:%S", now) # 这一步就是对时间进行格式化
print(nowt)
2、创建函数
#kubeless 空间下,运行一下
$ kubeless function deploy print-time --runtime python3.8 --from-file time_print.py --handler time_print.print_time
3、创建触发器
$ kubeless trigger cronjob create scheduled-print-time --function=print-time --schedule="*/1 * * * *"
这里 */1 * * * *
的含义是每一分钟启动一次。
查看CronJob的状态
$ kubectl get cronjob
查看函数日志,可以看到每分钟输出当前时间
$ kubeless function logs print-time
6.3 kafka trigger
6.3.1 通过NFS实现持久化存储
安装kafka和zookeeper之前需要先创建2个PV(Persistent Volume)供kafka和zookerper使用。
6.3.1.1配置nfs
master1做为nfs-server
node1、node2做为nfs-client
所有节点安装nfs
yum install -y nfs-common nfs-utils
在master节点创建共享目录
[root@master1 /]# mkdir /nfsdata
[root@master1 /]# mkdir /nfsdata/pv1
[root@master1 /]# mkdir /nfsdata/pv2
授权共享目录
[root@master1 /]# chmod 666 /nfsdata
编辑exports文件
[root@master1 /]# vim /etc/exports
/nfsdata *(rw,no_root_squash,no_all_squash,sync)
配置说明:
/nfsdata:是共享的数据目录:
*:表示任何人都有权限连接,当然也可以是一个网段,一个 IP,也可以是域名
rw:读写的权限
no_root_squash:保留共享文件的UID和GID(默认
sync:表示文件同时写入硬盘和内存
启动rpc和nfs(注意顺序)
$ systemctl start rpcbind
$ systemctl start nfs
作为准备工作,我们已经在 master 节点上搭建了一个 NFS 服务器,目录为 /nfsdata
:
$ showmount -e
Export list for master1:
/nfsdata *
6.3.1.2创建PV
下面创建一个 PV ,配置文件 nfs-pv1.yml
如下:
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv1
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Recycle
nfs:
server: 192.168.159.101
path: "/nfsdata/pv1"
① capacity
指定 PV 的容量为 1G。
② AccessModes
是用来对 PV 进行访问模式的设置,用于描述用户应用对存储资源的访问权限,访问权限包括下面几种方式:
- ReadWriteOnce(RWO):读写权限,但是只能被单个节点挂载
- ReadOnlyMany(ROX):只读权限,可以被多个节点挂载
- ReadWriteMany(RWX):读写权限,可以被多个节点挂载
③ persistentVolumeReclaimPolicy
指定当 PV 的回收策略为 Recycle
,支持的策略有:
Retain – 需要管理员手工回收。
Recycle – 清除 PV 中的数据,效果相当于执行 `rm -rf /thevolume/*`。
Delete – 删除 Storage Provider 上的对应存储资源,例如 AWS EBS、GCE PD、Azure Disk、OpenStack Cinder Volume 等。
④ storageClassName
指定 PV 的 class 为 nfs
。相当于为 PV 设置了一个分类,PVC 可以指定 class 申请相应 class 的 PV。
⑤ 指定 PV 在 NFS 服务器上对应的目录。
创建 pv1:
$ kubectl apply -f nfs-pv1.yml
# 查看是否部署成功
$ kubectl get pv
创建PV2
$ cp nfs-pv1.yml nfs-pv2.yml
$ vim nfs-pv2.yml
name: pv1 改成pv2
path: "/nfsdata/pv1" 改成 "/nfsdata/pv2"
$ kubectl apply -f nfs-pv2.yml
6.3.2 部署kafka trigger
(1)安装kafka和zk
# 当前最新版本为v1.1.0
$ export RELEASE=$(curl -s https://api.github.com/repos/kubeless/kafka-trigger/releases/latest | grep tag_name | cut -d '"' -f 4)
$ wget https://github.com/kubeless/kafka-trigger/releases/download/$RELEASE/kafka-zookeeper-$RELEASE.yaml
# 部署
$ kubectl create -f kafka-zookeeper-$RELEASE.yaml
(2)验证kafka和zk是否安装成功
$ kubectl -n kubeless get statefulset
NAME DESIRED CURRENT AGE
kafka 1 1 40s
zoo 1 1 42s
$ kubectl -n kubeless get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
broker ClusterIP None <**none**> 9092/TCP 1m
kafka ClusterIP 10.55.250.89 <**none**> 9092/TCP 1m
zoo ClusterIP None <**none**> 9092/TCP,3888/TCP 1m
zookeeper ClusterIP 10.55.249.102 <**none**> 2181/TCP 1m
2、创建函数
创建test_kafka.py
def foobar(event, context):
print(event['data'].upper())
return event['data']
创建函数
#kubeless 空间下,运行一下
$ kubeless function deploy test-kafka --runtime python3.8 --from-file test_kafka.py --handler test_kafka.foobar
3、创建触发器,同时创建消息主题
$ kubeless trigger kafka create test-kafka-trigger --function-selector created-by=kubeless,function=test-kafka --trigger-topic test-kafka-topic
4、发送信息
$ kubeless topic publish --topic test-kafka-topic --data "Hello World"
确认函数是否被触发: 在kubeless管理页面中查看函数执行日志
小结
- Kubeless 提供了一些基本常用的触发器,如果有其他事件源也可以通过自定义触发器接入。
- 不同事件源的接入方式不同,但最终都是通过访问函数 ClusterIP 类型的 service 触发函数执行。
- 本文介绍了3种常用的触发器,基本能够满足项目使用。但对于springboot如何发送消息到kafka触发函数执行未进行说明。