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

前言

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

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

主要有以下两个部分。

  • vscode 插件
  • k8S集群

先看一下最终效果


image

架构图:

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启动过程省略?

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容