利用Azure Functions和k8s构建Serverless计算平台
題記:昨晚在一個(gè)技術(shù)社區(qū)直播分享了“利用Azure Functions和k8s構(gòu)建Serverless計(jì)算平臺(tái)”這一話題。整個(gè)分享分為4個(gè)部分:Serverless概念的介紹、Azure Functions的簡單介紹、k8s和KEDA的介紹和最后的演示。
Serverless
Serverless其實(shí)包含了兩種概念:BaaS(Backend as a Service)和FaaS(Function as a Service)。這次的分享主要針對(duì)的是FaaS概念。
FaaS的最大特征就是:無需管理自己的服務(wù)器或擁有自己的持續(xù)運(yùn)行的服務(wù)應(yīng)用的情況下運(yùn)行后端代碼。上面加粗的地方其實(shí)也揭示了FaaS和PaaS的本質(zhì)區(qū)別:你為了運(yùn)行后端代碼,需不需要擁有一套持續(xù)運(yùn)行的服務(wù)端完整應(yīng)用(不管是WebSite還是Web API)。
另外,FaaS還擁有如下特征:
可以使用任何語言,不需要針對(duì)特定框架和函數(shù)進(jìn)行編碼
部署方式和傳統(tǒng)系統(tǒng)有很大不同
水平伸縮完全自動(dòng)化、彈性,并由平臺(tái)供應(yīng)商管理
函數(shù)通常由事件觸發(fā),部分平臺(tái)供應(yīng)商支持接收HTTP觸發(fā)
當(dāng)然判斷什么東西不是FaaS也有一些標(biāo)準(zhǔn):
能否在20ms啟動(dòng)半秒執(zhí)行完,根本區(qū)別在于伸縮性的方式
FaaS也可能依賴容器,但是和其他使用容器的應(yīng)用區(qū)別在于伸縮性的自動(dòng)化、透明和細(xì)度
沒有傳統(tǒng)的Ops,但是應(yīng)用本身運(yùn)維過程還是需要,甚至更難(因?yàn)椴煌?#xff09;
使用FaaS有其優(yōu)缺點(diǎn),這里就報(bào)喜不報(bào)憂,只列一下優(yōu)點(diǎn):
降低運(yùn)維成本
基礎(chǔ)設(shè)施共享
減少基礎(chǔ)設(shè)施維護(hù)人工成本
降低伸縮成本
按量付費(fèi):偶爾請(qǐng)求,流量忽高忽低
優(yōu)化代碼即可省錢
更易運(yùn)維
伸縮的好處利于降低運(yùn)維難度
降低打包和部署復(fù)雜度
快速投入市場(chǎng),持續(xù)優(yōu)化
Azure Functions
以官方文檔的介紹:Azure Functions 允許你運(yùn)行小段代碼(稱為“函數(shù)”)且不需要擔(dān)心應(yīng)用程序基礎(chǔ)結(jié)構(gòu)。?借助 Azure Functions,云基礎(chǔ)結(jié)構(gòu)可以提供應(yīng)用程序保持規(guī)模化運(yùn)行所需的所有最新狀態(tài)的服務(wù)器。函數(shù)由特定類型的事件“觸發(fā)”。?支持的觸發(fā)器包括對(duì)數(shù)據(jù)更改做出響應(yīng)、對(duì)消息做出響應(yīng)、按計(jì)劃運(yùn)行,或者生成 HTTP 請(qǐng)求的結(jié)果。雖然你始終可以直接針對(duì)大量服務(wù)編寫代碼,但使用綁定可以簡化與其他服務(wù)的集成。?使用綁定,你能夠以聲明方式訪問各種 Azure 服務(wù)和第三方服務(wù)。
Azure Functions包含如下功能:
無服務(wù)器應(yīng)用程序:使用 Functions,可在 Microsoft Azure 上開發(fā)無服務(wù)器應(yīng)用程序。
語言選擇:使用所選的 C#、Java、JavaScript、Python 和 PowerShell 編寫函數(shù)。
按使用付費(fèi)定價(jià)模型:僅為運(yùn)行代碼所用的時(shí)間付費(fèi)。
自帶依賴項(xiàng):Functions 支持 NuGet 和 NPM,允許你訪問你喜歡的庫。
集成的安全性:使用 OAuth 提供程序(如 Azure Active Directory、Facebook、Google、Twitter 和 Microsoft 帳戶)保護(hù) HTTP 觸發(fā)的函數(shù)。
簡化的集成:輕松與 Azure 服務(wù)和軟件即服務(wù) (SaaS) 產(chǎn)品/服務(wù)進(jìn)行集成。
靈活開發(fā):直接在門戶中編寫函數(shù)代碼,或者通過 GitHub、Azure DevOps Services 和其他受支持的開發(fā)工具設(shè)置持續(xù)集成和部署代碼。
有狀態(tài)無服務(wù)器體系結(jié)構(gòu):使用 Durable Functions 協(xié)調(diào)無服務(wù)器應(yīng)用程序。
開放源代碼:Functions 運(yùn)行時(shí)是開源的,可在 GitHub 上找到。
大家看到了,Azure Functions雖然是來源于微軟Azure的技術(shù),但是是使用MIT協(xié)議開源的,且已經(jīng)貢獻(xiàn)給.NET Foundation。
所以,你可以使用Azure Functions來搭建(甚至定制)自己的Serverless計(jì)算平臺(tái)。開源的不僅是Azure Functions框架本身,還包括了命令行工具(可以支持本地調(diào)試)和VSCode的擴(kuò)展。當(dāng)然,開發(fā)工具除了前面兩者,你還是可以使用宇宙第一的IDE:Visual Studio。
下面是相關(guān)開源的地址:
框架:https://github.com/Azure/azure-functions-host
命令行工具:https://github.com/Azure/azure-functions-core-tools
VSCode擴(kuò)展:https://github.com/Microsoft/vscode-azurefunctions
只有開源的框架還不行,還需要運(yùn)行環(huán)境,正如大部分開源FaaS框架一樣,Azure Functions也把k8s作為運(yùn)行環(huán)境。不過為了達(dá)到自動(dòng)伸縮、不使用就不消耗資源的目標(biāo),還需要搭配其他中間件才能達(dá)到效果。
k8s和KEDA
眾所周知,Kubernetes已經(jīng)成為最主流的PaaS平臺(tái),各大公有云提供商都提供了k8s的服務(wù),比如微軟Azure上的AKS或者阿里云的ACK。
為了更好的理解為什么k8s可以作為Serverless完美的運(yùn)行環(huán)境,是需要對(duì)如下概念有一些深入的理解的:
Pod和Deployment:Pod代表了運(yùn)行函數(shù)的實(shí)例,而Deployment用于控制函數(shù)的實(shí)例數(shù)。
HPA(Horizontal Pod Autoscaler):k8s內(nèi)置的水平Pod自動(dòng)伸縮器,其基于一些度量指標(biāo)(比如內(nèi)存、CPU等)來對(duì)Deployment的Pod實(shí)例數(shù)進(jìn)行伸縮。
Helm Charts:一個(gè)強(qiáng)大的打包、發(fā)布k8s應(yīng)用的包管理器。我們開發(fā)好的函數(shù)在編譯為Docker Image之后,可以用Helm Charts來打包(當(dāng)然也可以直接用k8s的yaml文件)。
k8s雖然提供了HPA,但是它無法基于更靈活的事件源來進(jìn)行伸縮,也無法把Pod的實(shí)例數(shù)縮到0,或者由0伸到1。這個(gè)時(shí)候,就需要另外一個(gè)開源項(xiàng)目KEDA出場(chǎng)了(貢獻(xiàn)者來自微軟、AWS等大公司,以及很多社區(qū)志愿者)。
KEDA:Kubernetes Event-driven Autoscaling。項(xiàng)目地址在:https://github.com/kedacore/keda。其具有如下特點(diǎn):
事件驅(qū)動(dòng)
輕而易舉實(shí)現(xiàn)自動(dòng)伸縮
內(nèi)置伸縮器
多種負(fù)載類型
社區(qū)開源項(xiàng)目
支持Azure Functions
KEDA的架構(gòu)如下圖所示:從這個(gè)架構(gòu)圖,我們看到KEDA包含了3個(gè)組件,Metric Adapter給k8s的HPA提供度量指標(biāo)讓其進(jìn)行1-n/n-1的伸縮,Controller控制Pod進(jìn)行1-0/0-1的伸縮,Scaler偵聽配置的觸發(fā)器所觸發(fā)的事件。
且支持的伸縮器涵蓋了大部分主流云組件或中間件:
Apache Kafka
AWS CloudWatch
AWS Kinesis Stream
AWS SQS Queue
Azure Blob Storage
Azure Event Hubs
Azure Monitor
Azure Service Bus
Azure Storage Queue
External
GCP Pub/Sub
Huawei Cloudeye
Liiklus Topic
MySQL
NATS Streaming
PostgreSQL
Prometheus
RabbitMQ Queue
Redis List
演示
既然Azure Functions是開源技術(shù),為了驗(yàn)證技術(shù)中立性,在演示過程中特意選擇了阿里云的ACK作為運(yùn)行環(huán)境(Kubernetes托管版),并使用RabbitMQ作為伸縮觸發(fā)器。
同時(shí),我們采用C#/.NET Core來作為函數(shù)的開發(fā)語言。為什么用這個(gè)選擇,是因?yàn)橛械谌綄?duì)AWS Lambda上的支持的語言進(jìn)行了性能測(cè)試,得到的結(jié)論是.NET Core的C#和F#語言性能最高:來源:https://read.acloud.guru/comparing-aws-lambda-performance-of-node-js-python-java-c-and-go-29c1163c2581
環(huán)境準(zhǔn)備
首先,需要到阿里云上創(chuàng)建一個(gè)k8s集群,創(chuàng)建的選項(xiàng)截圖如下:
通過如下命令來部署KEDA到k8s:
helm?repo?add?kedacore?https://kedacore.github.io/charts kubectl?create?namespace?keda helm?install?keda?kedacore/keda?--namespace?keda通過如下命令來部署RabbitMQ到k8s:
helm?repo?add?bitnami?https://charts.bitnami.com/bitnami helm?install?rabbitmq?--set?rabbitmq.password=PASSWORD,service.type=LoadBalancer?bitnami/rabbitmq這里需要注意(當(dāng)然也可能是我打開方式不對(duì)),阿里云的ACK不能自動(dòng)創(chuàng)建pv,所以rabbitmq部署后會(huì)有問題,所以需要到阿里云的ACK的控制面板里面手動(dòng)創(chuàng)建pv,并重建rabbitmq所需的同名pvc。
創(chuàng)建Azure Functions項(xiàng)目
訪問:https://github.com/Azure/azure-functions-core-tools,安裝命令行工具。
在命令行中輸入:
func?init?--docker來初始化一個(gè)帶有Dockerfile的Azure Functions項(xiàng)目,worker runtime選擇dotnet。
在命令行中輸入:
func?function?create來創(chuàng)建一個(gè)函數(shù),template選擇QueueTrigger,輸入你想要的函數(shù)名稱。
使用你喜歡的編輯器(比如VSCode)打開項(xiàng)目文件夾,修改csproj文件中的PackageReference為如下內(nèi)容:
<ItemGroup><PackageReference?Include="Microsoft.NET.Sdk.Functions"?Version="3.0.3"?/><PackageReference?Include="Microsoft.Azure.WebJobs.Extensions.RabbitMQ"?Version="0.2.2029-beta"?/> </ItemGroup>修改函數(shù)代碼為如下內(nèi)容:
[FunctionName("MyMqFunction")] public?static?void?Run([RabbitMQTrigger("queue",?ConnectionStringSetting?=?"RabbitMqConnection")]?string?inputMessage,[RabbitMQ(QueueName?=?"downstream",?ConnectionStringSetting?=?"RabbitMqConnection")]?out?string?outputMessage,ILogger?log) {Thread.Sleep(5000);outputMessage?=?inputMessage;log.LogInformation($"RabittMQ?output?binding?function?sent?message:?{outputMessage}"); }這個(gè)函數(shù)從一個(gè)名為”queue“的隊(duì)列中讀取inputMessage,延遲5秒后,把消息存儲(chǔ)到名為”downstream"的隊(duì)列中。
打開local.settings.json文件,在Values節(jié)點(diǎn)下添加RabbitMqConnection:
"Values":?{"AzureWebJobsStorage":?"UseDevelopmentStorage=true","FUNCTIONS_WORKER_RUNTIME":?"dotnet","RabbitMqConnection":"amqp://user:PASSWORD@rabbitmq.default.svc.cluster.local:5672" },這里RabbitMQ的地址使用了k8s內(nèi)部的默認(rèn)Service地址,為了方便本地調(diào)試,你可以獲取到RabbitMQ在k8s的公網(wǎng)IP后,給這個(gè)域名添加host配置。
在命令行中輸入:
func?start就可以進(jìn)行本地調(diào)試了。調(diào)試無誤,就可以進(jìn)行發(fā)布到k8s的工作了。
以上示例代碼可以在這里找到:https://github.com/heavenwing/AzFuncOnK8S
發(fā)布函數(shù)到k8s并驗(yàn)證伸縮能力
考慮到我用的阿里云拉取Docker Hub比較慢,所以我是編譯出Docker Image后,push到了阿里云的鏡像倉庫當(dāng)中。另外,我這里還遇到一個(gè)問題,就是能在AKS中正常運(yùn)行的Docker Image在ACK中無法正常運(yùn)行,出現(xiàn)"Access to the path '/proc/1/map_files' is denied"的錯(cuò)誤,我的臨時(shí)解決辦法是修改Dockerfile文件,添加WORKDIR命令。
在把Docker Image推送到鏡像倉庫后,可以在命令行中輸入:
func?kubernetes?deploy?--name?azfunconk8s?--image-name?registry.cn-chengdu.aliyuncs.com/zygcloud/azfunconk8s:latest?--dry-run?>?deploy-funcs.yaml得到部署的yaml文件后,我們需要對(duì)ScaledObject進(jìn)行一點(diǎn)修改,為rabbitmq的trigger配置添加queueLength,根據(jù)需要配置maxReplicaCount屬性,如下所示:
apiVersion:?keda.k8s.io/v1alpha1 kind:?ScaledObject metadata:name:?azfunconk8snamespace:?defaultlabels:deploymentName:?azfunconk8s spec:scaleTargetRef:deploymentName:?azfunconk8smaxReplicaCount:?20triggers:-?type:?rabbitmqmetadata:type:?rabbitMQTriggerqueueName:?queuename:?inputMessagehost:?RabbitMqConnectionqueueLength:?"20"現(xiàn)在就可以把函數(shù)部署到k8s了,在命令行中輸入:
kubectl?apply?-f?.\deploy\deploy-funcs.yaml這個(gè)時(shí)候應(yīng)該可以看到k8s出現(xiàn)了名為azfunconk8s的Deployment,且需要實(shí)例和運(yùn)行實(shí)例數(shù)都是為0:
另外寫一個(gè)小程序,往RabbitMQ的queue隊(duì)列里面放一些測(cè)試消息,經(jīng)過30秒(默認(rèn)pollingInterval時(shí)間)那么就會(huì)看到這個(gè)Deployment的所需實(shí)例數(shù)在提高,一直提高到你設(shè)置的maxReplicaCount。等隊(duì)列中的消息處理完成,又會(huì)看到所需實(shí)例數(shù)在降低,等沒有消息需要處理之后過上5分鐘(默認(rèn)cooldownPeriod時(shí)間),所需實(shí)例數(shù)就會(huì)變?yōu)?。
總結(jié)
以上是生活随笔為你收集整理的利用Azure Functions和k8s构建Serverless计算平台的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 微软正式发布 gRPC-Web for
- 下一篇: 对 JsonConvert 的认识太肤浅