前言
Serverless如此火熱似乎沒啥好介紹的,我就不當(dāng)搬運(yùn)工了...
本文主要介紹筆者如何利用vscode插件+k8s+node來搭建一個可運(yùn)行代碼片段的serveless平臺
主要有以下兩個部分。
- vscode 插件
- k8S集群
先看一下最終效果

架構(gòu)圖:

實(shí)踐過程
vscode插件
vscode 插件是平臺的客戶端,用戶上傳代碼片段,觸發(fā)執(zhí)行,接受返回結(jié)果等作用。也是實(shí)踐過程中較為容易的部分。
只需要注冊右擊按鈕和對于文本的處理即可(相關(guān)代碼省略)
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)=>{
//處理返回并開始取回執(zhí)行結(jié)果
let getResultTask=setInterval(()=>{
//.....
textEditor.edit((editBuilder)=>{
editBuilder.insert(selection.end,`\n//執(zhí)行結(jié)果:${resultValue}\n`)
});
clearInterval(getResultTask);
//....
},1000);
}
});
})
環(huán)境準(zhǔn)備(k8s集群)
一臺2C2G 及其以上云服或者物理機(jī)器作為k8s master。(不要問為什么只用一臺,因?yàn)楦F)。
筆者采用是華為云新用戶免費(fèi)15天的2c4g的云服。
k8s的集群的安裝筆者主要采用kubeadm 來安裝.
國內(nèi)安裝主要是官方容器被墻的問題。
kubeadm config print init-defaults > kubeadm-init.yaml
修改repo 為registry.cn-hangzhou.aliyuncs.com/google_containers
再修改相關(guān)配置適合成自己的網(wǎng)絡(luò)環(huán)境
執(zhí)行
kubeadmin init --config kubeadmin-init.yaml
另由于筆者是采用單機(jī)來做k8s 集群,所以 很多非kube-system 的pod也會運(yùn)行在master 節(jié)點(diǎn)上,所以我們需要更改master節(jié)點(diǎn)上的trait 的限制.
kubectl taint node {你的master節(jié)點(diǎn)的名稱} node-role.kubernetes.io/master-
主要組件開發(fā)和部署
serverless-api
此組件主要是提供針對代碼片段和執(zhí)行結(jié)果的相關(guān)操作,筆者采用go&gin&redis 實(shí)現(xiàn)一個簡單的resultful服務(wù)。
主要提供一下幾個接口
- insertCode // 提交新的運(yùn)行片段
- insertRunResult // 插入運(yùn)行結(jié)果
- getNextRunCodeId // 獲取下一個可以執(zhí)行的代碼片段的code的id
- getCode // 根據(jù)id獲取code
- getResult // 根據(jù)id獲取result
具體代碼就不貼了,主要是對于redis的一些curd操作。
由于此pod即需要對外提供訪問服務(wù),有需要對內(nèi)提供訪問。所以筆者采用pod + service+
ingress 的方式來組織serverless-api。
以下筆者創(chuàng)建幾類服務(wù)
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
關(guān)于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上會進(jìn)行對應(yīng)的端口綁定
containers:
- name: nginx-ingress-controller
image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.26.2
// .....
serverless-cronjob
此組件是集群內(nèi)用于創(chuàng)建和調(diào)度作業(yè)容器,筆者這里設(shè)計了一個定時的任務(wù)。定時檢查作業(yè)的容器,并且刪除已經(jīng)完成作業(yè)的任務(wù)的job容器,如果沒有作業(yè)容器還在運(yùn)行則獲取下一段執(zhí)行代碼的id,創(chuàng)建新的作業(yè)容器
此組件 主要依賴 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,并決定是否獲取并創(chuàng)建下一個job
}
response, err := http.Get("http://******/getNextRunCodeId")
// .......
jobClient := clientset.BatchV1().Jobs("serverless-job")
// 將獲取的codeid利用env傳遞給作業(yè)容器內(nèi)
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,
},
},
},
}
// 創(chuàng)建job
createResult, err := jobClient.Create(&job)
// ****
time.Sleep(sleepTime * time.Second)
}
}
由于cronjob組件需要調(diào)用kubectl 且在集群內(nèi)中運(yùn)行,所以我們不能僅僅使用default token,我們需要使用高級一點(diǎn)的角色權(quán)限,并綁定到pods上。
以下是筆者的相關(guān)實(shí)踐
創(chuàng)建新的命名空間為作業(yè)容器的建立做好準(zhǔn)備
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 // 關(guān)聯(lián)上我們創(chuàng)建的賬戶
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
于此同時,我們進(jìn)入到pods內(nèi)就可以看到秘鑰成功的被掛在默認(rèn)的目錄下。

serverless-node-job
此組件主要是我們的作業(yè)容器。筆者這里僅僅實(shí)踐了js的代碼片段執(zhí)行實(shí)踐。
此組件主要作用就是獲取代碼 并利用node v8模塊對代碼運(yùn)行并將結(jié)果插入到serverless-api中。
const { CODEID }=process.env
if(CODEID){
//.....
axios({
.../
}).then(response=>{
try{
const runResult=runCode(response)
}catch(err){
insertResult(err)
}
insertResult(runResult)
// .....
})
}
至此,筆者關(guān)于serverless平臺搭建的實(shí)踐到此結(jié)束。
總結(jié)
筆者在這里僅僅簡單實(shí)踐了nodejs代碼片段的運(yùn)行,對于serverless更廣泛的應(yīng)用其實(shí)還并未進(jìn)行嘗試。例如函數(shù)的注冊和各類方式的觸發(fā)器,資源調(diào)度/伸縮,日志/監(jiān)控,長伺服應(yīng)用等等. 關(guān)于serverless市面上主要是提供serverless函數(shù)計算和云應(yīng)用兩項服務(wù)。其實(shí)也就是長短伺服業(yè)務(wù)。
但僅僅在筆者簡單的實(shí)踐過程中,也出現(xiàn)了一些頗為頭疼的問題,一是函數(shù)執(zhí)行的實(shí)時性的問題,可以從實(shí)踐成果的gif中看到從提交片段到返回結(jié)果的過程中也有肉眼可查明顯的延時,容器創(chuàng)建和node啟動過程的耗時都是比較大的。因此還是需要進(jìn)行一定的優(yōu)化。比如創(chuàng)建一個同執(zhí)行環(huán)境的容器池,對外開放觸發(fā)接口,把容器創(chuàng)建和node啟動過程省略?
更多問題不在這里贅述,有興趣的朋友可以隨時交流。