hi,一起来尝试搭建一个可运行代码node代码片段serverless平台

前言

Serverless如此火热似乎没啥好介绍的,我就不当搬运工了...

本文主要介绍笔者如何利用vscode插件+k8s+node来搭建一个可运行代码片段的serveless平台

主要有以下两个部分。

  • vscode 插件
  • k8S集群

先看一下最终效果


hi,一起来尝试搭建一个可运行代码node代码片段serverless平台_第1张图片
image

架构图:

hi,一起来尝试搭建一个可运行代码node代码片段serverless平台_第2张图片
image

实践过程

vscode插件

vscode 插件是平台的客户端,用户上传代码片段,触发执行,接受返回结果等作用。也是实践过程中较为容易的部分。

只需要注册右击按钮和对于文本的处理即可(相关代码省略)


let disposable = vscode.commands.registerTextEditorCommand('disheng-serverless-exec', (textEditor,edit) => {
        const doc=textEditor.document;
        const selection=textEditor.selection;
        // 获取选取代码
        const selectedText=doc.getText(selection);
        // ..... post code
        axios({
            //.........
        }).then((response)=>{
            //处理返回并开始取回执行结果
            let getResultTask=setInterval(()=>{
            //.....
                textEditor.edit((editBuilder)=>{
                    editBuilder.insert(selection.end,`\n//执行结果:${resultValue}\n`)
                });
                clearInterval(getResultTask);
            //....
            },1000);
            }
        }); 
        })

环境准备(k8s集群)

一台2C2G 及其以上云服或者物理机器作为k8s master。(不要问为什么只用一台,因为穷)。

笔者采用是华为云新用户免费15天的2c4g的云服。

k8s的集群的安装笔者主要采用kubeadm 来安装.

国内安装主要是官方容器被墙的问题。

kubeadm config print init-defaults > kubeadm-init.yaml

修改repo 为registry.cn-hangzhou.aliyuncs.com/google_containers
再修改相关配置适合成自己的网络环境
执行

kubeadmin init --config kubeadmin-init.yaml

另由于笔者是采用单机来做k8s 集群,所以 很多非kube-system 的pod也会运行在master 节点上,所以我们需要更改master节点上的trait 的限制.

kubectl taint node {你的master节点的名称} node-role.kubernetes.io/master-

主要组件开发和部署

serverless-api

此组件主要是提供针对代码片段和执行结果的相关操作,笔者采用go&gin&redis 实现一个简单的resultful服务。

主要提供一下几个接口

  • insertCode // 提交新的运行片段
  • insertRunResult // 插入运行结果
  • getNextRunCodeId // 获取下一个可以执行的代码片段的code的id
  • getCode // 根据id获取code
  • getResult // 根据id获取result

具体代码就不贴了,主要是对于redis的一些curd操作。
由于此pod即需要对外提供访问服务,有需要对内提供访问。所以笔者采用pod + service+
ingress 的方式来组织serverless-api。
以下笔者创建几类服务

serverless-api-deploymenet

apiVersion: apps/v1
kind: Deployment
metadata:
  name: serverless-api-deployment
spec:
  selector:
    matchLabels:
      app: serverless-api
  template:
    metadata:
      labels:
        app: serverless-api
    spec:
      containers:
        - name: serverless-api
          image: ********
          ports:
            - containerPort: 8080
        - name: redis
          image: redis:latest
          ports:
            - containerPort: 6379
apiVersion: apps/v1
kind: Service
metadata:
  name: serverless-api-service
spec:
  selector:
    app: serverless-api
  ports:
    - protocol: TCP
      port: 8080
      targetPort: 8080
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: serverless-ingress
spec:
  rules:
    - host:*****.***.****
      http:
        paths:
          - path: /
            backend:
              serviceName: serverless-api-service
              servicePort: 8080

关于ingress的暴露 笔者主要采用的ingress controller + hostnework的方式来暴露。

// ........
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-ingress-controller
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: ingress-nginx
      app.kubernetes.io/part-of: ingress-nginx
  template:
    metadata:
      labels:
        app.kubernetes.io/name: ingress-nginx
        app.kubernetes.io/part-of: ingress-nginx
      annotations:
        prometheus.io/port: "10254"
        prometheus.io/scrape: "true"
    spec:
      # wait up to five minutes for the drain of connections
      terminationGracePeriodSeconds: 300
      serviceAccountName: nginx-ingress-serviceaccount
      nodeSelector:
        kubernetes.io/os: linux
      hostNetwork: true // 加入hostnewwork 。部署pod的node上会进行对应的端口绑定
      containers:
        - name: nginx-ingress-controller
          image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.26.2
// .....

serverless-cronjob

此组件是集群内用于创建和调度作业容器,笔者这里设计了一个定时的任务。定时检查作业的容器,并且删除已经完成作业的任务的job容器,如果没有作业容器还在运行则获取下一段执行代码的id,创建新的作业容器
此组件 主要依赖 k8s.io/go-client

func main() {
    config, err := rest.InClusterConfig()
    if err != nil {
        panic(err.Error())
    }
    clientset, err := kubernetes.NewForConfig(config)
    if err != nil {
        panic(err.Error())
    }
    for {
        pods, err := clientset.CoreV1().Pods("serverless-job").List(metav1.ListOptions{})
        
        for pods,_ := range pods.Items{
          // ... 删除已完成的pods,并决定是否获取并创建下一个job
            
        }
        response, err := http.Get("http://******/getNextRunCodeId")
        // .......
    
        jobClient := clientset.BatchV1().Jobs("serverless-job")
        // 将获取的codeid利用env传递给作业容器内 
        job := batchv1.Job{
            ObjectMeta: metav1.ObjectMeta{
                Name:      "serverless-job-" + RandStr(12),
                Namespace: "serverless-job",
            },
            Spec: batchv1.JobSpec{
                Template: apiv1.PodTemplateSpec{
                    Spec: apiv1.PodSpec{
                        Containers: []apiv1.Container{
                            {
                                Name:  "serverless-job-container",
                                Image:"*********",
                                Env: []apiv1.EnvVar{
                                    {
                                        Name:  "CODEID",
                                        Value: string(body),
                                    },
                                },
                            },
                        },
                        RestartPolicy: apiv1.RestartPolicyNever,
                    },
                },
            },
        }
        // 创建job
        createResult, err := jobClient.Create(&job)
        // **** 
        
        time.Sleep(sleepTime * time.Second)
    }
}

由于cronjob组件需要调用kubectl 且在集群内中运行,所以我们不能仅仅使用default token,我们需要使用高级一点的角色权限,并绑定到pods上。
以下是笔者的相关实践

创建新的命名空间为作业容器的建立做好准备

kubectl create namespaces serverless-job
apiVersion: apps/v1
kind: Deployment
metadata:
  name: serverless-cronjob-deployment
spec:
  selector:
    matchLabels:
      app: serverless-cronjob
  template:
    metadata:
      labels:
        app: serverless-cronjob
    spec:
      serviceAccountName: serverless-cronjob // 关联上我们创建的账户
      containers:
        - name: serverless-cronjob
          image: ××××××
apiVersion: v1
kind: ServiceAccount
metadata:
  name: serverless-cronjob
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: kubernetes-dashboard
  labels:
    k8s-app: kubernetes-dashboard
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
  - kind: ServiceAccount
    name: serverless-cronjob
    namespace: default

于此同时,我们进入到pods内就可以看到秘钥成功的被挂在默认的目录下。

image

serverless-node-job

此组件主要是我们的作业容器。笔者这里仅仅实践了js的代码片段执行实践。
此组件主要作用就是获取代码 并利用node v8模块对代码运行并将结果插入到serverless-api中。

const { CODEID }=process.env
if(CODEID){
//.....
    axios({
        .../
    }).then(response=>{
    try{
        const runResult=runCode(response)
    }catch(err){
        insertResult(err)
    }
     insertResult(runResult)
    // .....
    })
}

至此,笔者关于serverless平台搭建的实践到此结束。

总结

笔者在这里仅仅简单实践了nodejs代码片段的运行,对于serverless更广泛的应用其实还并未进行尝试。例如函数的注册和各类方式的触发器,资源调度/伸缩,日志/监控,长伺服应用等等. 关于serverless市面上主要是提供serverless函数计算和云应用两项服务。其实也就是长短伺服业务。

但仅仅在笔者简单的实践过程中,也出现了一些颇为头疼的问题,一是函数执行的实时性的问题,可以从实践成果的gif中看到从提交片段到返回结果的过程中也有肉眼可查明显的延时,容器创建和node启动过程的耗时都是比较大的。因此还是需要进行一定的优化。比如创建一个同执行环境的容器池,对外开放触发接口,把容器创建和node启动过程省略?

更多问题不在这里赘述,有兴趣的朋友可以随时交流。

你可能感兴趣的:(hi,一起来尝试搭建一个可运行代码node代码片段serverless平台)