函数计算原来这么简单!

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 Triggerhttp触发器
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管理页面中查看函数执行日志
在这里插入图片描述

小结

  1. Kubeless 提供了一些基本常用的触发器,如果有其他事件源也可以通过自定义触发器接入。
  2. 不同事件源的接入方式不同,但最终都是通过访问函数 ClusterIP 类型的 service 触发函数执行。
  3. 本文介绍了3种常用的触发器,基本能够满足项目使用。但对于springboot如何发送消息到kafka触发函数执行未进行说明。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值