前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >学习 Kubernetes 原生 Serverless 无服务架构 Kubeless

学习 Kubernetes 原生 Serverless 无服务架构 Kubeless

作者头像
哎_小羊
发布2019-05-25 19:46:50
1.7K0
发布2019-05-25 19:46:50
举报
文章被收录于专栏:哎_小羊哎_小羊

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/aixiaoyang168/article/details/82379339

目录

    • 1、Serverless & Kubeless 介绍
      • 1.1、Serverless
      • 1.2、Kubeless

    • 2、环境、软件准备
    • 3、kubeless cli 安装
    • 4、kubeless 部署
    • 5、kubeless java function 演示
    • 6、kubeless java with dependencies function 演示
    • 7、kubeless-ui 部署和使用

1、Serverless & Kubeless 介绍

1.1、Serverless

Serverless AWS 官方对 Serverless 的介绍:服务器架构是基于互联网的系统,应用开发不使用常规的服务进程,而是依赖于第三方服务,客户端逻辑和服务托管远程过程调用的组合。简单的讲,Serverless 就是指应用的开发不再需要考虑服务器的硬件基础设施,而是依赖于第三提供的后端服务(Baas)和应用逻辑运行容器(FaaS),但是并不是意味着没有服务器,而是服务器以特定功能的第三方服务的形式存在。

Serverless 带来的好处:

  • 降低了硬件基础设施的部署和维护成本
  • 方便应用服务的扩展和监控,因为依赖的第三方运行平台基本都支持动态扩展和服务监控。
  • 降低了由于应用服务访问流量变化而带来的硬件资源的浪费。
  • 方便开发者专注应用的开发,更快速的发布应用,而不需要关注基础架构的问题。

1.2、Kubeless

Kubeless 是 Kubernetes 原生无服务器架构,目的是为了方便部署少量代码而不需要担心底层基础设施,它利用 Kubernetes 资源来提供自动缩放、API 路由、监控、故障排查等功能。Kubeless 利用 Kubernetes CRD (Custom Resource Definition) 来创建 functions 作为自定义 Kubernetes 资源类型,然后,运行一个集群控制器来监视这些自定义资源并按需启动运行,集群控制器动态得将函数代码注入到运行环境中,并通过 Http 或者 PubSub 机制使其可以被调用。

Kubeless 是利用 CRD 自定义资源类型来实现其整个流程,上一篇文章 Kubernetes CRD (CustomResourceDefinition) 自定义资源类型 中我对 Kubernetes CRD 的工作原理和源码做了简要的分析,可以先参考下该文章对其大概了解一下。

2、环境、软件准备

本次演示环境,我是在本机 MAC OS 上操作,以下是安装的软件及版本:

  • Docker: 17.09.0-ce
  • Oracle VirtualBox: 5.1.20 r114628 (Qt5.6.2)
  • Minikube: v0.28.2
  • Kubernetes: v1.10.0
  • Kubeless: v1.0.0-alpha.8
  • Kubectl:
    • Client Version: v1.11.1
    • Server Version: v1.10.0

注意:这里 Kubernetes 集群搭建使用 Minikube 来完成,Minikube 启动的单节点 k8s Node 实例是需要运行在本机的 VM 虚拟机里面,所以需要提前安装好 VM,这里我选择 Oracle VirtualBox。k8s 运行底层使用 Docker 容器,所以本机需要安装好 Docker 环境,这里忽略 Docker、VirtualBox、Minikube、Kubectl 的安装过程,可以参考之前文章 Minikube & kubectl 升级并配置, 这里着重介绍下 Kubeless 安装与部署。

3、kubeless cli 安装

首先,我们需要下载一个类似 kubectl 工具的 kubeless cli 命令行客户端,用来执行 kubeless 相关命令操作,可以从这里 Github kubeless release page 选择系统对应的版本安装包,这里我选择 kubeless_darwin-amd64.zip 版本即可(这里可以使用官网提供的适配脚本,下载系统对应的安装包)。

代码语言:javascript
复制
$ export RELEASE=$(curl -s https://api.github.com/repos/kubeless/kubeless/releases/latest | grep tag_name | cut -d '"' -f 4)
$ export OS=$(uname -s| tr '[:upper:]' '[:lower:]')
$ curl -OL https://github.com/kubeless/kubeless/releases/download/$RELEASE/kubeless_$OS-amd64.zip && \
  unzip kubeless_$OS-amd64.zip && \
  sudo mv bundles/kubeless_$OS-amd64/kubeless /usr/local/bin/

安装完毕,通过 kubeless -h 查看该工具相关命令行参数说明。

代码语言:javascript
复制
$ kubeless -h
Serverless framework for Kubernetes

Usage:
  kubeless [command]

Available Commands:
  autoscale         manage autoscale to function on Kubeless
  completion        Output shell completion code for the specified shell.
  function          function specific operations
  get-server-config Print the current configuration of the controller
  help              Help about any command
  topic             manage message topics in Kubeless
  trigger           trigger specific operations
  version           Print the version of Kubeless

Flags:
  -h, --help   help for kubeless

Use "kubeless [command] --help" for more information about a command.

4、kubeless 部署

接下来,我们就可以部署 kubeless 了,首先可以创建一个新的 Namespace 来区分(默认 default),然后下载 kubeless yaml 文件执行部署。这里官网提供了三种方式的 yaml 部署文件:

  • kubeless-$RELEASE.yaml:适用开启 RBAC 认证的 Kubernetes 集群
  • kubeless-non-rbac-$RELEASE.yaml:适用未开启 RBAC 认证的 Kubernetes 集群
  • kubeless-openshift-$RELEASE.yaml:适用部署 kubeless 到 openshift(1.5+)

因为我们使用的 Minikube 对应的 Kubernetes 版本开启了 RBAC 认证,所以可以使用如下命令部署:

代码语言:javascript
复制
$ export RELEASE=$(curl -s https://api.github.com/repos/kubeless/kubeless/releases/latest | grep tag_name | cut -d '"' -f 4)
$ kubectl create ns kubeless
$ kubectl create -f https://github.com/kubeless/kubeless/releases/download/$RELEASE/kubeless-$RELEASE.yaml

$ kubectl get all -n kubeless
NAME                                               READY     STATUS    RESTARTS   AGE
pod/kubeless-controller-manager-5d7894857d-h4hr9   3/3       Running   0          1d

NAME                                          DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/kubeless-controller-manager   1         1         1            1           1d

NAME                                                     DESIRED   CURRENT   READY     AGE
replicaset.apps/kubeless-controller-manager-5d7894857d   1         1         1         1d

$ kubectl get crd
NAME                                    CREATED AT
cronjobtriggers.kubeless.io             2018-08-22T09:01:59Z
functions.kubeless.io                   2018-08-22T09:01:59Z
httptriggers.kubeless.io                2018-08-22T09:01:59Z

可以看到,部署完 kubeless 之后,默认创建了三个 CRD 资源类型,该服务依赖镜像有三个: kubeless/function-controller:v1.0.0-alpha.8 bitnami/http-trigger-controller:v1.0.0-alpha.9 bitnami/cronjob-trigger-controller:v1.0.0-alpha.9 还好,这次终于不需要访问外国网站啦!

接下来,我们来演示一下如何使用 kubeless 创建一个简单的 function。

首先创建一个 Python 文件 hello.py。

代码语言:javascript
复制
def hello(event, context):
  print event
  return event['data']

说明一下:

  • event 第一个参数,该参数包含关于事件源的所有信息,下边 event['data'] 中 key data 需要包含该方法请求的 body 体。
  • context 第二个参数,该参数包含该 function 的基本信息。
  • 最后返回一个字符串或 Object 对象给调用方。

接下来,通过 kubeless 部署该 function。

代码语言:javascript
复制
$ kubeless function deploy hello --runtime python2.7 --handler test.hello --from-file hello.py  -n kubeless
INFO[0000] Deploying function...
INFO[0000] Function hello submitted for deployment
INFO[0000] Check the deployment status executing 'kubeless function ls hello'

说明一下参数:

  • hello:这个是定义的要部署的 function 的名称
  • --runtime python2.7:这个是运行该 function 的运行环境,因为我们要运行的是 python 文件,所以得指定 python 运行环境,该版本 kubeless 默认支持的运行环境有:python2.7, python3.4, python3.6, nodejs6, nodejs8, nodejs_distroless8, ruby2.4, php7.2, go1.10, dotnetcore2.0, java1.8, ballerina0.981.0, jvm1.8,通过 kubeless get-server-config 可以查看支持列表。
  • --handler test.hello:这个是用来接收请求时指定文件暴漏的 function。例如,这里我们指定了暴漏 test.py 文件中的 hello 方法,当文件有多个 function 时,指定要暴漏的那个即可。
  • --from-file hello.py:这个是指定包含 function 的文件路径,它也支持 zip 类型的文件。

当然,它还支持其他很多参数,例如 --dependencies--label--port--schedule--secrets等等一系列参数,具体可以参考命令 kubeless function deploy --help

OK 稍等片刻,就部署好了,此时,我们通过 kubectl 命令查看本次部署都部署了那些东西。

代码语言:javascript
复制
$ kubectl get all -n kubeless
NAME                                               READY     STATUS    RESTARTS   AGE
pod/hello-6f666c5bd-wmntj                          1/1       Running   0          1d
pod/kubeless-controller-manager-5d7894857d-h4hr9   3/3       Running   0          1d

NAME                    TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
service/hello           ClusterIP   10.102.190.186   <none>        8080/TCP         1d

NAME                                          DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/hello                         1         1         1            1           1d
deployment.apps/kubeless-controller-manager   1         1         1            1           1d

NAME                                                     DESIRED   CURRENT   READY     AGE
replicaset.apps/hello-6f666c5bd                          1         1         1         1d
replicaset.apps/kubeless-controller-manager-5d7894857d   1         1         1         1d

$ kubectl get functions -n kubeless
NAME            CREATED AT
hello           1d

可以看到,执行一次部署 function,后端 Kubernetes 中启动了 replicasetdeploymentservicepodfunction 几种资源,这里就不详细分析其执行过程了,下边我会拿一个 java demo 分析一下其执行过程。通过 kubeless function ls hello 命令可以查看该 function 的一些基本信息,以及是否启动成功。

代码语言:javascript
复制
$ kubeless function ls hello -n kubeless
NAME    NAMESPACE   HANDLER     RUNTIME     DEPENDENCIES    STATUS
hello   kubeless    test.hello  python2.7                   1/1 READY

最后,我们来直接调用该 hello function 试试看,看能否调通吧!

代码语言:javascript
复制
$ kubeless function call hello --data 'This is first time call this function.'
This is first time call this function.

可以得到预期返回结果,确实很方便呵。但是这种调用方式,并不能满足我们日常需求,我们需要的是能够通过 http 方式调用该方法,那有什么办法呢?这个方法有很多种。kubeless 提供了很多种方案供我们选择,详细参考文档 Expose and secure Kubeless functions 这里,里面介绍了使用 Kubernetes Ingress 方式暴漏该服务,通过部署一种类型的 Ingress,然后配合使用 kubeless trigger http 命令,自动创建 Router 规则,从而实现该服务暴漏外部访问。

当然,本次测试我们可以简单的使用 kubectl proxy 代理 APIserver URL 方式来访问。

代码语言:javascript
复制
$ kubectl proxy -p 8080
$ curl -L --data 'Hello kubeless!' 127.0.0.1:8080/api/v1/namespaces/kubeless/services/hello:8080/proxy/
Hello kubeless!

这里提一下,上边 URL 地址中,kubeless 这个要指定对应的 Namespaces,hello:8080 这个要符合 <function-name>:<http-function-port> 方式,这里因为我们部署时未指定 port,那么就取默认端口号 8080

最后,如果想删除该 function,可以使用命令如下:

代码语言:javascript
复制
$ kubeless function delete hello

5、kubeless java function 演示

下边,我们新建一个 Java 类型代码,通过指定该运行环境为 java1.8,来看下通过 kubeless 部署一个此类型 function,底层容器到底执行了那些操作吧。

首先,新建一个简单的 Java 文件 HelloGet.java ,仅仅返回 Hello world! This is kubeless demo. 即可。

代码语言:javascript
复制
package io.kubeless;

import io.kubeless.Event;
import io.kubeless.Context;

public class Foo {
    public String foo(io.kubeless.Event event, io.kubeless.Context context) {
        return "Hello world! This is kubeless demo.";
    }
}

然后,部署该 function 并命名为 get-java

代码语言:javascript
复制
$ kubeless function deploy get-java --runtime java1.8 --handler Foo.foo --from-file HelloGet.java -n kubeless

部署完毕后,稍等片刻,如果正常的话,就会成功部署,通过 kubeless 命令查看下。

代码语言:javascript
复制
$ kubeless function ls -n kubeless
NAME        NAMESPACE   HANDLER     RUNTIME     DEPENDENCIES    STATUS
get-java    kubeless    Foo.foo     java1.8                     1/1 READY
hello       kubeless    test.hello  python2.7                   1/1 READY

$ kubectl get functions  -n kubeless
NAME       CREATED AT
get-java   4m
hello      17h

接下来,调用一下该 function,看是否能够成功返回。

代码语言:javascript
复制
$ kubeless function call get-java -n kubeless
Hello world! This is kubeless demo.

调用成功,说明部署没有问题,通过 kubectl 命令查看部署了那些资源。

代码语言:javascript
复制
$ kubectl get all -n kubeless
NAME                                               READY     STATUS    RESTARTS   AGE
pod/get-java-5ff45cd65d-2frkx                      1/1       Running   0          29m
pod/hello-6f666c5bd-wmntj                          1/1       Running   0          17h
pod/kubeless-controller-manager-5d7894857d-h4hr9   3/3       Running   0          18h

NAME               TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
service/get-java   ClusterIP   10.110.56.131    <none>        8080/TCP   29m
service/hello      ClusterIP   10.102.190.186   <none>        8080/TCP   17h

NAME                                          DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/get-java                      1         1         1            1           29m
deployment.apps/hello                         1         1         1            1           17h
deployment.apps/kubeless-controller-manager   1         1         1            1           18h

NAME                                                     DESIRED   CURRENT   READY     AGE
replicaset.apps/get-java-5ff45cd65d                      1         1         1         29m
replicaset.apps/hello-6f666c5bd                          1         1         1         17h
replicaset.apps/kubeless-controller-manager-5d7894857d   1         1         1         18h

这里,想必了解 K8s 的大家都知道 replicasetdeploymentservicepod这几个资源类型都分别起什么作用,这里不再赘述了。接下来,咱们看下 pod/get-java-5ff45cd65d-2frkx 该 Pod 针对我们提供的 HelloGet.javajava1.8 运行环境,内部到底是如何处理的呢?

代码语言:javascript
复制
$ kubectl describe pod/get-java-5ff45cd65d-2frkx -n kubeless

Name:           get-java-5ff45cd65d-2frkx
Namespace:      kubeless
......
Init Containers:
  prepare:
    Container ID:  docker://bffe706d1f0c8e8f4ed0659bc5a46c2624512d7b51a1923cef3787a108c782f6
    Image:         kubeless/unzip@sha256:f162c062973cca05459834de6ed14c039d45df8cdb76097f50b028a1621b3697
    Image ID:      docker-pullable://kubeless/unzip@sha256:f162c062973cca05459834de6ed14c039d45df8cdb76097f50b028a1621b3697
    Port:          <none>
    Host Port:     <none>
    Command:
      sh
      -c
    Args:
      echo 'e5e99052dd50822d654c935dd2f5c893cf4062bfd304d7245503366c83cba93a  /src/Foo.java' > /tmp/func.sha256 && sha256sum -c /tmp/func.sha256 && cp /src/Foo.java /kubeless/Foo.java && cp /src/pom.xml /kubeless
    State:          Terminated
      Reason:       Completed
      Exit Code:    0
      Started:      Thu, 23 Aug 2018 10:36:38 +0800
      Finished:     Thu, 23 Aug 2018 10:36:38 +0800
    Ready:          True
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /kubeless from get-java (rw)
      /src from get-java-deps (rw)
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-5qprx (ro)
  compile:
    Container ID:  docker://e4bb9e2acbb35b014236e8242ee867acf8b668020cb9638d421587061f1cfa44
    Image:         kubeless/java-init@sha256:7e5e4376d3ab76c336d4830c9ed1b7f9407415feca49b8c2bf013e279256878f
    Image ID:      docker-pullable://kubeless/java-init@sha256:7e5e4376d3ab76c336d4830c9ed1b7f9407415feca49b8c2bf013e279256878f
    Port:          <none>
    Host Port:     <none>
    Command:
      sh
      -c
    Args:
      cp -r /usr/src/myapp/* /kubeless/ && cp /kubeless/*.java /kubeless/function/src/main/java/io/kubeless/ && cp /kubeless/function-pom.xml /kubeless/function/pom.xml 2>/dev/null || true && mvn package > /dev/termination-log 2>&1 && mvn install > /dev/termination-log 2>&1
    State:      Terminated
      Reason:   Completed
      Message:  dependent!

[INFO] skip non existing resourceDirectory /kubeless/function/src/test/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ function ---
......
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO]
[INFO] kubeless ........................................... SUCCESS [02:05 min]
[INFO] params ............................................. SUCCESS [  0.499 s]
[INFO] function ........................................... SUCCESS [  0.031 s]
[INFO] handler ............................................ SUCCESS [  0.037 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 02:05 min
[INFO] Finished at: 2018-08-23T02:41:51Z
[INFO] Final Memory: 18M/192M
[INFO] ------------------------------------------------------------------------

      Exit Code:    0
      Started:      Thu, 23 Aug 2018 10:36:39 +0800
      Finished:     Thu, 23 Aug 2018 10:41:51 +0800
    Ready:          True
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /kubeless from get-java (rw)
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-5qprx (ro)
Containers:
  get-java:
    Container ID:   docker://0d1f5cfca8e3d7479ee48e938ca743563fca84221f71194bc460469b584b8ec5
    Image:          kubeless/java@sha256:debf9502545f4c0e955eb60fabb45748c5d98ed9365c4a508c07f38fc7fefaac
    Image ID:       docker-pullable://kubeless/java@sha256:debf9502545f4c0e955eb60fabb45748c5d98ed9365c4a508c07f38fc7fefaac
    Port:           8080/TCP
    Host Port:      0/TCP
    State:          Running
      Started:      Thu, 23 Aug 2018 10:41:54 +0800
    Ready:          True
    Restart Count:  0
    Liveness:       http-get http://:8080/healthz delay=3s timeout=1s period=30s #success=1 #failure=3
    Environment:
      FUNC_HANDLER:       foo
      MOD_NAME:           Foo
      FUNC_TIMEOUT:       180
      FUNC_RUNTIME:       java1.8
      FUNC_MEMORY_LIMIT:  0
      FUNC_PORT:          8080
    Mounts:
      /kubeless from get-java (rw)
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-5qprx (ro)
......
Events:
  Type    Reason                 Age   From               Message
  ----    ------                 ----  ----               -------
  Normal  Scheduled              30m   default-scheduler  Successfully assigned get-java-5ff45cd65d-2frkx to minikube
  Normal  SuccessfulMountVolume  30m   kubelet, minikube  MountVolume.SetUp succeeded for volume "get-java"
  Normal  SuccessfulMountVolume  30m   kubelet, minikube  MountVolume.SetUp succeeded for volume "get-java-deps"
  Normal  SuccessfulMountVolume  30m   kubelet, minikube  MountVolume.SetUp succeeded for volume "default-token-5qprx"
  Normal  Pulled                 30m   kubelet, minikube  Container image "kubeless/unzip@sha256:f162c062973cca05459834de6ed14c039d45df8cdb76097f50b028a1621b3697" already present on machine
  Normal  Created                30m   kubelet, minikube  Created container
  Normal  Started                30m   kubelet, minikube  Started container
  Normal  Pulled                 30m   kubelet, minikube  Container image "kubeless/java-init@sha256:7e5e4376d3ab76c336d4830c9ed1b7f9407415feca49b8c2bf013e279256878f" already present on machine
  Normal  Created                30m   kubelet, minikube  Created container
  Normal  Started                30m   kubelet, minikube  Started container
  Normal  Pulling                25m   kubelet, minikube  pulling image "kubeless/java@sha256:debf9502545f4c0e955eb60fabb45748c5d98ed9365c4a508c07f38fc7fefaac"
  Normal  Pulled                 25m   kubelet, minikube  Successfully pulled image "kubeless/java@sha256:debf9502545f4c0e955eb60fabb45748c5d98ed9365c4a508c07f38fc7fefaac"
  Normal  Created                25m   kubelet, minikube  Created container
  Normal  Started                25m   kubelet, minikube  Started container

从日志输出,我们可以看到,该 Pod 先后依赖了三个镜像:kubeless/unzipkubeless/java-initkubeless/java, 这三个镜像又分别干了些什么工作呢?

首先看下 kubeless/unzip 镜像,它主要作用是将我们的源文件挂载到容器指定位置,看启动命令里面主要干了两件事:一是 sha256 校验文件一致性,二是复制源文件以及 pom.xml 到指定目录。

代码语言:javascript
复制
echo 'e5e99052dd50822d654c935dd2f5c893cf4062bfd304d7245503366c83cba93a  /src/Foo.java' > /tmp/func.sha256 && sha256sum -c /tmp/func.sha256 && cp /src/Foo.java /kubeless/Foo.java && cp /src/pom.xml /kubeless

然后看下 kubeless/java-init 镜像,主要执行一些初始化操作,看启动命令主要干了两件事:一是复制指定文件到指定目录(这个指定的文件下边会讲到),二是执行 mvn package & install 编译操作。

代码语言:javascript
复制
cp -r /usr/src/myapp/* /kubeless/ && cp /kubeless/*.java /kubeless/function/src/main/java/io/kubeless/ && cp /kubeless/function-pom.xml /kubeless/function/pom.xml 2>/dev/null || true && mvn package > /dev/termination-log 2>&1 && mvn install > /dev/termination-log 2>&1

最后看下 kubeless/java 镜像,虽然日志上没有显示启动命令,但是它非常关键,它的作用是在 Java 环境中启动服务,调用我们指定的代码的类方法,来提供服务给外部调用。

我们进入到容器内部查看下,具体该 function 执行的时候依赖了那些模块和 jar 包。

代码语言:javascript
复制
$ kubectl exec -it get-java-5ff45cd65d-2frkx -n kubeless /bin/sh

/ $ cd /kubeless/
/kubeless $ ls -alt
total 48
drwxr-xr-x    1 root     root          4096 Aug 23 02:41 ..
drwxrwsrwx    7 root     1000          4096 Aug 23 02:41 .
drwxr-sr-x    2 1000     1000          4096 Aug 23 02:41 lib
drwxr-xr-x    4 1000     1000          4096 Aug 23 02:39 handler
drwxr-xr-x    4 1000     1000          4096 Aug 23 02:39 function
drwxr-xr-x    4 1000     1000          4096 Aug 23 02:38 params
drwxr-sr-x    3 1000     1000          4096 Aug 23 02:36 ?
-rw-r--r--    1 1000     1000           202 Aug 23 02:36 Dockerfile
-rw-r--r--    1 1000     1000            79 Aug 23 02:36 Dockerfile.init
-rw-r--r--    1 1000     1000           303 Aug 23 02:36 Makefile
-rw-r--r--    1 1000     1000           876 Aug 23 02:36 pom.xml
-rw-r--r--    1 1000     1000           236 Aug 23 02:36 Foo.java

/kubeless 目录为该 function 所在工作目录,上边提到复制指定文件到指定目录操作命令 cp -r /usr/src/myapp/* /kubeless/ && cp /kubeless/*.java /kubeless/function/src/main/java/io/kubeless/ && cp /kubeless/function-pom.xml /kubeless/function/pom.xml 这个,就是讲镜像中 /usr/src/myapp/ 目录下的 handler、function、params 目录复制到该目录,同时将我们编写的源文件 Foo.java 复制到 function 目录的指定位置以及将复制过来的 function-pom.xml 文件复制到 function 根目录去。那么,这些操作主要是干什么用呢?我们可以从该目录下 pom.xml 文件以及 mvn package & install 日志输出可以得到答案。

代码语言:javascript
复制
/kubeless $ cat pom.xml
<project>
    <modelVersion>4.0.0</modelVersion>
    <groupId>io.kubeless</groupId>
    <artifactId>kubeless</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>kubeless</name>
    <url>http://maven.apache.org</url>
    <modules>
        <module>params</module>
        <module>function</module>
        <module>handler</module>
    </modules>
    <build>
    <plugins>
        <plugin>
        <artifactId>maven-dependency-plugin</artifactId>
        <executions>
            <execution>
            <phase>install</phase>
            <goals>
                <goal>copy-dependencies</goal>
            </goals>
            <configuration>
                <outputDirectory>/kubeless/lib</outputDirectory>
            </configuration>
            </execution>
        </executions>
        </plugin>
    </plugins>
    </build>
</project>

从该 pom 文件中可以看到,该项目 io.kubeless.kubeless 项目下有三个子模块,就是上边提到的复制过来的 params、function、handler 三个子项目。当执行 mvn install 时执行编译,并将生成的 jar 包输出到 /kubeless/lib 目录去。我们在看下 function 模块下复制过去的 pom.xml 文件。

代码语言:javascript
复制
$ cat function/pom.xml
<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>function</artifactId>
  <name>function</name>
  <version>1.0-SNAPSHOT</version>
  <dependencies>
      <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>

function 子模块是存放我们 Foo.java 文件的模块,这也解释了为什么我们定义的 Foo.java 文件 package 为 package io.kubeless; 开头,因为 function 子模块的包定义结构就是如此:function/src/main/java/io/kubeless/Foo.java。从 pom 文件中可以看到它继承的父项目就是上边的 io.kubeless.kubeless 同时依赖了 io.kubeless.params 子模块。

params 子模块比较简单,里面仅仅是两个构造函数,也是我们 Foo.java 文件中 import 的 Event 和 Context 所引入的类所在地方。

代码语言:javascript
复制
/kubeless $ cat params/src/main/java/io/kubeless/Context.java
package io.kubeless;

/**
 * Context includes information about the function environment
 */
public class Context {
    String functionName;
    String timeout;
    String runtime;
    String memoryLimit;

    public Context(String functionName, String timeout, String runtime, String memoryLimit) {
        this.functionName = functionName;
        this.timeout = timeout;
        this.runtime = runtime;
        this.memoryLimit = memoryLimit;
    }

/kubeless $ cat params/src/main/java/io/kubeless/Event.java  
public class Event {
    String Data;
    String EventID;
    String EventType;
    String EventTime;
    String EventNamespace;

    public Event(String data, String eventId, String eventType, String eventTime, String eventNamespace) {
        this.Data = data;
        this.EventID = eventId;
        this.EventType = eventType;
        this.EventTime = eventTime;
        this.EventNamespace = eventNamespace;
    }
}  

handler 子模块就厉害了,它是我们整个 function 运行提供外部调用并返回响应的控制的地方,它通过环境变量方式解析到我们部署 function 指定的 handler 类名方法名,以及 runtime、port 等参数,启动一个 HttpServer 来提供两个 context:一个为 /healthz 返回 200 状态码的 “OK” 字符串给调用者,另一个为 / 它通过反射方式解析并调用我们 handler 定义的类名和方法名,并将方法执行结果返回给调用者。该子模块也继承了 io.kubeless.kubeless 父项目,还依赖了 io.kubeless.paramsio.kubeless.function 模块,同时还依赖一些其他的 jar,例如 io.prometheus.simpleclientlog4j 等等。

代码语言:javascript
复制
/kubeless $ cat handler/src/main/java/io/kubeless/Handler.java
public class Handler {

    // 环境变量中取值,在部署时 Pod 中已经添加
    static String className   = System.getenv("MOD_NAME");
    static String methodName  = System.getenv("FUNC_HANDLER");
    static String timeout     = System.getenv("FUNC_TIMEOUT");
    static String runtime     = System.getenv("FUNC_RUNTIME");
    static String memoryLimit = System.getenv("FUNC_MEMORY_LIMIT");
    static Method method;
    static Object obj;
    static Logger logger = Logger.getLogger(Handler.class.getName());

    static final Counter requests = Counter.build().name("function_calls_total").help("Total function calls.").register();
    static final Counter failures = Counter.build().name("function_failures_total").help("Total function call failuress.").register();
    static final Histogram requestLatency = Histogram.build().name("function_duration_seconds").help("Duration of time user function ran in seconds.").register();

    public static void main(String[] args) {

        BasicConfigurator.configure();

        String funcPort = System.getenv("FUNC_PORT");
        if(funcPort == null || funcPort.isEmpty()) {
            funcPort = "8080";
        }
        int port = Integer.parseInt(funcPort);
        try {
            // 启动一个 HttpServer
            HttpServer server = HttpServer.create(new InetSocketAddress(port), 0);
            server.createContext("/", new FunctionHandler());
            server.createContext("/healthz", new HealthHandler());
            server.setExecutor(java.util.concurrent.Executors.newFixedThreadPool(50));
            server.start();

            // JVM 查找并加载指定的类
            Class<?> c = Class.forName("io.kubeless."+className);
            obj = c.newInstance();
            method = c.getMethod(methodName, io.kubeless.Event.class, io.kubeless.Context.class);
        } catch (Exception e) {
            failures.inc();
            .......
        }
    }

    // 内部类针对 / Context function 的处理类
    static class FunctionHandler implements HttpHandler {
        @Override
        public void handle(HttpExchange he) throws IOException {
            Histogram.Timer requestTimer = requestLatency.startTimer();
            try {
                requests.inc();

                InputStreamReader reader = new InputStreamReader(he.getRequestBody(), StandardCharsets.UTF_8.name());
                BufferedReader br = new BufferedReader(reader);
                String requestBody = br.lines().collect(Collectors.joining());
                br.close();
                reader.close();

                // 获取请求 header 头中的指定 key 值
                Headers headers = he.getRequestHeaders();
                String eventId  = getEventId(headers);
                String eventType = getEventType(headers);
                String eventTime = getEventTime(headers);
                String eventNamespace = getEventNamespace(headers);

                Event event = new Event(requestBody, eventId, eventType, eventTime, eventNamespace);
                Context context = new Context(methodName, timeout, runtime, memoryLimit);

                // 通过反射调用该 method 获取返回结果,并将结果返回调用者。 
                Object returnValue = Handler.method.invoke(Handler.obj, event, context);
                String response = (String)returnValue;
                logger.info("Response: " + response);
                he.sendResponseHeaders(200, response.length());
                OutputStream os = he.getResponseBody();
                os.write(response.getBytes());
                os.close();
            } catch (Exception e) {
                failures.inc();
                ......
            } finally {
                requestTimer.observeDuration();
            }
        }
    }

    // 内部类针对 /Health Context 的处理类
    static class HealthHandler implements HttpHandler {
        @Override
        public void handle(HttpExchange t) throws IOException {
            String response = "OK";
            t.sendResponseHeaders(200, response.length());
            OutputStream os = t.getResponseBody();
            os.write(response.getBytes());
            os.close();
        }
    }

编译完成后,我们可以从 /kubeless/lib 目录看下具体都生成并依赖了那些 jar 包。

代码语言:javascript
复制
/kubeless $ ll lib/
total 604
drwxr-sr-x    2 1000     1000          4096 Aug 23 02:41 .
drwxrwsrwx    7 root     1000          4096 Aug 23 02:41 ..
-rw-r--r--    1 1000     1000          2488 Aug 23 02:41 function-1.0-SNAPSHOT.jar
-rw-r--r--    1 1000     1000        489884 Aug 23 02:41 log4j-1.2.17.jar
-rw-r--r--    1 1000     1000          2560 Aug 23 02:41 params-1.0-SNAPSHOT.jar
-rw-r--r--    1 1000     1000         58160 Aug 23 02:41 simpleclient-0.3.0.jar
-rw-r--r--    1 1000     1000          5838 Aug 23 02:41 simpleclient_common-0.3.0.jar
-rw-r--r--    1 1000     1000         17182 Aug 23 02:41 simpleclient_hotspot-0.3.0.jar
-rw-r--r--    1 1000     1000          9518 Aug 23 02:41 simpleclient_httpserver-0.3.0.jar
-rw-r--r--    1 1000     1000          6101 Aug 23 02:41 simpleclient_pushgateway-0.3.0.jar

这下,我们能够大概了解其各个镜像以及容器内部各个模块的作用了吧!

6、kubeless java with dependencies function 演示

上边我们演示了基本参数的 java function 部署和调用,对于需要自己指定 dependencies 的 function 该怎么办呢?kubeless function deploy 时是可以指定自己的 dependencies 文件的。

新建 HelloWithDeps.java 文件和指定自己依赖的的 pom.xml 文件

代码语言:javascript
复制
$ vim HelloWithDeps.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;
    }
}

$ vim pom.xml
<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>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>

这里对比上边示例中的 function/pom.xml 除了基础依赖外,增加了 joda-time 依赖,并且在代码中调用输出当前时间。

接下里,通过 kubeless 命令部署该 function。

代码语言:javascript
复制
$ kubeless function deploy get-java-deps --runtime java1.8 --handler Hello.sayHello --from-file HelloWithDeps.java --dependencies pom.xml
INFO[0000] Deploying function...
INFO[0000] Function get-java-deps submitted for deployment
INFO[0000] Check the deployment status executing 'kubeless function ls get-java-deps'

稍等片刻后,部署成功,通过 kubeless function ls 命令查看下该 function,会发现 DEPENDENCIES 列下就会显示我们指定的 pom.xml 信息。

代码语言:javascript
复制
$ kubeless function ls get-java-deps -n kubeless
NAME            NAMESPACE   HANDLER         RUNTIME DEPENDENCIES                                        STATUS
get-java-deps   kubeless    Hello.sayHello  java1.8 <project xmlns="http://maven.apache.org/POM/4.0.0"  1/1 READY
                                                    xmlns:xsi="http://www.w3.org/2001/XMLSchema-ins...
                                                    xsi:schemaLocation="http://maven.apache.org/POM...
                                                    http://maven.apache.org/xsd/maven-4.0.0.xsd">
                                                      <modelVersion>4.0.0</modelVersion>
                                                      <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>

接下来,我们调用一下该 function 试试看。

代码语言:javascript
复制
$ kubeless function call get-java-deps --data "This is kubeless demo" -n kubeless
Hello world! Current local time is: 09:52:25.038

可以成功调用,我们看下文件中的日志输出,是否打印了我们的调用传递的参数 This is kubeless demo 吧!

代码语言:javascript
复制
$ kubectl logs -l function=get-java-deps -n kubeless
This is kubeless demo
442330901 [pool-1-thread-9] INFO io.kubeless.Handler  - Response: Hello world! Current local time is: 09:52:25.038

这里就不在分析其执行过程了,跟上边示例的区别就是使用我们指定的 pom.xml 替代了默认 function/pom.xml 配置,其他操作都差不多。

7、kubeless-ui 部署和使用

通过上边的演示,我们现在可以正常部署并调用 functions 资源了,不过,如果我们部署的 function 比较多时,通过命令方式查看和调用就不是那么直观了,kubeless 提供了一个简洁的 Web UI 页面,通过该 UI 页面可以轻易的创建、更新、删除和测试调用集群中的 function,通过这里 kubeless-ui 可以查看相关文档。

Kubernetes 中通过 kubectl 安装最新版的 kubeless-ui。注意:默认最新版 kubeless-ui yaml 文件是配置了 RBAC 认证信息的,适用与开启 RBAC 认证的集群,若集群未开启 RBAC 认证,那么就需要删除 yaml 中 RBAC 认证相关配置了。

代码语言:javascript
复制
$ kubectl create -f https://raw.githubusercontent.com/kubeless/kubeless-ui/master/k8s.yaml

部署完毕后,通过 kubectl 命令查看下部署了那些跟 kubeless-ui 相关的资源类型。

代码语言:javascript
复制
$ kubectl get all -n kubeless
NAME                                               READY     STATUS    RESTARTS   AGE
pod/ui-5b87d84d96-vkmz7                            2/2       Running   0          1d

NAME                    TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
service/ui              NodePort    10.101.225.39    <none>        3000:32085/TCP   1d

NAME                                          DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/ui                            1         1         1            1           1d

NAME                                                     DESIRED   CURRENT   READY     AGE
replicaset.apps/ui-5b87d84d96                            1         1         1         1d

接下来,就可以通过浏览器访问 kubeless-ui WEB 页面了,访问 http://<cluster_ip>:<node_port>,因为这里是以 NodePort 方式启动的 service,所以,可以访问 http://192.168.99.100:32085/ 该地址即可,亦可 minikube service ui -n kubeless 命令行方式打开。

通过 UI 我们可以清晰看到上边创建的 helloget-javaget-java-deps 等 functions,并且可以在右侧直接执行调用,在页面下半部还能看到容器日志的输出,非常的简单明了。

参考资料

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018年09月04日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、Serverless & Kubeless 介绍
    • 1.1、Serverless
      • 1.2、Kubeless
      • 2、环境、软件准备
      • 3、kubeless cli 安装
      • 4、kubeless 部署
      • 5、kubeless java function 演示
      • 6、kubeless java with dependencies function 演示
      • 7、kubeless-ui 部署和使用
      相关产品与服务
      容器服务
      腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档