我们中的许多人至少拥有一个树莓派,如果它不作为一个媒体播放器、复古游戏机、阻止广告或报告天气,那么它很可能在积聚灰尘。
我写这篇文章是为了给你一个借口,让你吹掉那些微芯片上的灰尘,让你的口袋大小的硅片作为世界上最小的API驱动的云来工作。
按照本文的说明,你将能够使用REST API从任何地方向你的口袋云部署计划任务、webhook接收器、网页和功能。

文章大纲
- 全尺寸的云是什么样子的?
- 一个口袋云的定义
- 擦亮你的树莓派
- 为API驱动的部署设置faasd
- 试用一个存储函数
- 探索异步调用系统
- 用GitHub行动进行部署
- 探索监控和度量
- 结论和更多资源
全尺寸的云是什么样子的?
根据NIST对云的最终定义”云计算是一种模式,用于实现无处不在的、方便的、按需的网络访问可配置的计算资源(如网络、服务器、存储、应用程序和服务)的共享池,可以在最小的管理努力或服务提供商的互动下快速配置和释放。”
当我读到这个定义时,作者似乎是在描述一个像AWS、GCP这样的管理云供应商,或一些像Kubernetes(https://kubernetes.io/)或主要的管理程序之一的可安装软件。
现在,我们不能从我们的树莓Pis上获得AWS,但我们可以通过在树莓Pis集群上安装Kubernetes,配置一个网络存储层,并使用netbooting来轻松配置和重新配置主机,从而相当接近定义的大部分内容。我在我的研讨会上谈到了如何做到这一点。使用K3s的树莓派的网络启动研讨会 (https://store.openfaas.com/l/netbooting-raspberrypi)
但我们不打算这样做,原因有二。第一个原因是树莓派短缺,所以这实际上是在使用你现有的设备,并将其用于工作。第二个原因是,Kubernetes有一个不小的成本,这对一个口袋云来说并不完全必要。
一个袖珍的定义
我们对云的定义将是按比例缩小,以适应你的口袋,在不到1GB的内存中。我们今天所构建的是一个运行我们的代码的地方,它可以通过API进行配置—使用容器进行打包,这样就很容易实现自动化和维护。我们的架构中会有一个单点故障,但我们会通过使用NVMe SSD而不是SD卡来应对,这延长了我们的存储故障时间。该软件很容易部署,所以我们可以将平均恢复时间(MTTR)减少到几分钟。
进入API驱动的部署
我在Twitter上花了太多的时间,经常看到一些有点像这样的推文。
“我用Go/Python/Node写了一个HTTP服务器,并把它部署到某个虚拟机上,但现在我不知道如何远程重新部署它”
在过去的好日子里,我们会使用FTP或SFTP将代码或二进制文件传输到生产中,然后通过SSH连接或控制面板UI来重新启动服务。这可能是当今最简单的解决方案之一,但它几乎不是 “API驱动 “的。
如果你能把你的代码打包在一个容器中,然后用curl请求来部署新的版本,会怎么样?
也许你已经尝试过OpenFaaS,也许你只是因为没有看到一个用例而将其撇开,或者觉得你 “必须为无服务器做好准备”?坦率地说,这是我自己的错,因为我在信息传递方面做得不好。让我们试着解决这个问题。
OpenFaaS是一个运行HTTP服务器的平台,被打包在容器中,有一个管理API。所以我们可以用它来运行我们的代码,而我们今天要探讨的OpenFaaS的版本也可以添加有状态的服务,比如Postgresql、MongoDB、Redis或者Grafana仪表盘。
为某人运行HTTP服务器的想法是如此引人注目,以至于谷歌云在2018年发布了 “Cloud Run”,如果你眯起眼睛,它们看起来非常相似。它是一种从容器镜像中运行HTTP服务器的方法,其他的就不多说了。
OpenFaaS堆栈有几个核心组件—一个带有REST管理API和内置Prometheus监控的网关和一个围绕NATS建立的队列系统,用于在后台运行代码。

OpenFaaS的概念性概述
模板系统在用容器构建HTTP服务的过程中承担了大量的工作—Docker文件与HTTP框架一起被抽象出来。你会得到一个处理程序,在那里你可以填写你的代码,并有一个简单的命令将其打包。
功能商店还提供了一个快速浏览预制图像的方法,如机器学习模型和网络工具,如nmap、curl、hey或nslookup。
你可以通过以下两种方式访问:
$ faas-cli template store list
NAME SOURCE DESCRIPTION
csharp openfaas Classic C
dockerfile openfaas Classic Dockerfile template
go openfaas Classic Golang template
java8 openfaas Java 8 template
java11 openfaas Java 11 template
java11-vert-x openfaas Java 11 Vert.x template
node14 openfaas HTTP-based Node 14 template
node12 openfaas HTTP-based Node 12 template
node openfaas Classic NodeJS 8 template
php7 openfaas Classic PHP 7 template
python openfaas Classic Python 2.7 template
python3 openfaas Classic Python 3.6 template
python3-dlrs intel Deep Learning Reference Stack v0.4 for ML workloads
ruby openfaas Classic Ruby 2.5 template
ruby-http openfaas Ruby 2.4 HTTP template
python27-flask openfaas Python 2.7 Flask template
....
$ faas-cli template store pull ruby-http
或者
faas-cli store list
FUNCTION DESCRIPTION
NodeInfo Get info about the machine that you'r...
alpine An Alpine Linux shell, set the "fproc...
env Print the environment variables prese...
sleep Simulate a 2s duration or pass an X-S...
shasum Generate a shasum for the given input
Figlet Generate ASCII logos with the figlet CLI
curl Use curl for network diagnostics, pas...
SentimentAnalysis Python function provides a rating on ...
hey HTTP load generator, ApacheBench (ab)...
nslookup Query the nameserver for the IP addre...
SSL/TLS cert info Returns SSL/TLS certificate informati...
Colorization Turn black and white photos to color ...
Inception This is a forked version of the work ...
Have I Been Pwned The Have I Been Pwned function lets y...
Face Detection with Pigo Detect faces in images using the Pigo...
Tesseract OCR This function brings OCR - Optical Ch...
...
$ faas-cli store deploy hey
现在,因为Kubernetes对我们来说太大了,我们将运行一个不同版本的OpenFaaS,叫做 “faasd”。全尺寸的OpenFaaS以Kubernetes为目标,Kubernetes通过许多层的中介,最终在一个容器中运行你的HTTP服务器,而faasd直接运行你的容器。

撕掉所有这些层的结果是,它仍然是API驱动的,是非常快速的,学习曲线大幅下降。它不再是高可用的、集群的或多节点的,所以把它想得更像一个设备。你在厨房里有3个烤面包机或烤箱,以备其中一个发生故障?
aasd与Kubernetes的距离也使得它非常稳定—没有什么可以出错的,也很少有破坏性的变化或 “弃用 “需要担心的。升级faasd设备只是更换 “faasd “二进制文件,也许还有containerd二进制文件。

有一些最终用户将faasd而不是OpenFaaS部署在Kubernetes上用于生产。faasd是一种为内部内网、客户项目或边缘设备部署代码的好方法。把它打包成一个虚拟机,装入你的功能,你就几乎可以忘记它了。
我去年在Equinix Metal的Proximity活动(https://metal.equinix.com/resources/events/proximity/)上发表了关于 “更接近金属 “的演讲(在堆栈中更低的位置,远离Kubernetes)。这次演讲是围绕着faasd和我们可以做的事情来进行的,而这些事情在K8s中是不可能实现的。例如,我们能够使冷启动达到亚秒级,并在单个节点上安排100倍的容器,使其更具成本效益。
擦去硅的灰尘
你至少需要一个有1GB内存的Raspberry Pi 3的功能,或者Raspberry Pi 4,它通常至少有2GB的内存可用。Raspberry Pi Zero W 2也可以使用,但只有512MB的内存。
我推荐使用 “经典 “的Raspberry Pi 32位操作系统精简版(Buster)或Ubuntu Server 20.04。Bullseye确实可以工作,但是操作系统有一些变化,你可以通过使用Buster来解决这些问题。
使用Linux主机上的dd,或者像etcher.io这样的图形用户界面(https://etcher.io/),将镜像闪到SD卡上。我有一个Linux主机,我用它来闪现SD卡,因为我经常这么做。
现在我们几乎已经准备好启动和获得无头访问,没有键盘或显示器。对于Raspberry Pi操作系统,你必须在启动分区创建一个名为ssh的文件。对于Ubuntu,SSH已经被启用。
通过ssh连接到你的Raspberry Pi ssh [email protected] 为Raspberry Pi操作系统,如果你使用Ubuntu,运行nmap -p 22 192.168.0.0/24,这将显示你网络上的任何新主机,你可以通过SSH连接到。
更改默认密码和主机名,对于Raspberry Pi操作系统,使用raspi-config
安装faasd
从该SSH会话中安装faasd是相对简单的。
git clone https://github.com/openfaas/faasd --depth=1cd faasd
./hack/install.sh
在安装的最后,你会得到一个如何寻找密码的命令。
现在,因为我们的小云是API驱动的,所以我们不应该再直接在它上面运行任何命令,而是从我们的笔记本上把它作为一个服务器使用。
到你的工作站去,安装OpenFaaS CLI。
curl -SLs https://cli.openfaas.com | sudo sh
登录:
export OPENFAAS_URL=http://IP:8080
faas-cli login --password-stdin
键入前一个会话的密码,然后点击回车。
试用商店里的一个功能
现在你可以从函数库中部署一个样本函数。这是一个阻塞性命令,可能需要几秒钟才能完成
faas-cli store deploy nodeinfo
然后列出你的功能,描述它并调用它。
$ faas-cli list -v
Function Image Invocations Replicas
nodeinfo ghcr.io/openfaas/nodeinfo:latest 0 1
$ faas-cli describe nodeinfo
Name: nodeinfo
Status: Ready
Replicas: 1
Available replicas: 1
Invocations: 0
Image:
Function process: node index.js
URL: http://127.0.0.1:8080/function/nodeinfo
Async URL: http://127.0.0.1:8080/async-function/nodeinfo
$ curl http://127.0.0.1:8080/function/nodeinfo
Hostname: localhost
Arch: arm
CPUs: 4
Total mem: 476MB
Platform: linux
Uptime: 5319.62
任何函数都可以被异步调用,这对于长期运行的函数—计划任务和webhook接收器很重要。
$ curl -d "" -i http://127.0.0.1:8080/async-function/nodeinfo
HTTP/1.1 202 Accepted
X-Call-Id: 9c766581-965a-4a11-9b6d-9c668cfcc388
你会收到一个X-Call-Id,可以用来关联请求和响应。一个X-Callback-Url是可选的,它可以用来将响应发布到HTTP bin,或其他一些你拥有的功能,以创建一个链。
比如说。
$ nc -l 8888
$ curl -d "" -H "X-Callback-Url: http://192.168.0.86:8888/" \
-i http://127.0.0.1:8080/async-function/nodeinfo
HTTP/1.1 202 Accepted
X-Call-Id: 926c4181-6b8e-4a43-ac9e-ec39807b0695
而在netcat中,我们看到:
POST / HTTP/1.1
Host: 192.168.0.86:8888
User-Agent: Go-http-client/1.1
Content-Length: 88
Content-Type: text/plain; charset=utf-8
Date: Wed, 23 Mar 2022 10:44:13 GMT
Etag: W/"58-zeIRnHjAybZGzdgnZVWGeAGymAI"
Keep-Alive: timeout=5
X-Call-Id: 926c4181-6b8e-4a43-ac9e-ec39807b0695
X-Duration-Seconds: 0.086076
X-Function-Name: nodeinfo
X-Function-Status: 200
X-Start-Time: 1648032253651730445
Accept-Encoding: gzip
Connection: close
Hostname: localhost
Arch: arm
CPUs: 4
Total mem: 476MB
Platform: linux
Uptime: 5577.28
建立你自己的功能
你可以部署一个预定义的HTTP服务器,在8080端口监听请求,使用一个自定义的Docker文件,或者从商店里购买一个openfaas模板。

建一个名为 “mystars “的函数 – 我们将用它来接收来自GitHub的webhooks,当有人给我们的某个仓库加星时。
export OPENFAAS_PREFIX="ghcr.io/alexellis"
faas-cli new --lang node16 mystars
mystars.yml
mystars/handler.js
mystars/handler.json
mystars.yml定义了如何构建和部署该功能。
mystars/handler.js定义了对HTTP请求的响应方式。
'use strict'module.exports = async (event, context) => {
const result = {
'body': JSON.stringify(event.body),
'content-type': event.headers["content-type"]
}
return context
.status(200)
.succeed(result)
}
这与AWS Lambda非常相似,你可以在电子书《Serverles For Everyone Else》中找到完整的文档,也可以在docs中找到更有限的概述。Node.js模板(of-watchdog模板)(https://docs.openfaas.com/cli/templates/#nodejs-templates-of-watchdog-template)
然后你可以通过运行cd mystars和运行npm install –save来安装你可能需要的任何NPM模块,如octokit(https://github.com/octokit/rest.js/)。
要在你的Raspberry Pi上尝试这个功能,请在你自己的主机上使用Docker构建它,将镜像发布到你的GHCR账户,然后进行部署。
faas-cli publish -f mystars.yml \
--platforms linux/amd64,linux/arm64,linux/arm/7
如果你想更快地构建,你可以删除其他平台,但上述交叉编译你的函数,以运行在32位ARM操作系统、64位操作系统(如Ubuntu)和PC(如你的工作站/笔记本电脑或普通云服务器)。
发布后,用以下方式部署该函数。
faas-cli deploy -f mystars.yml
最后,你可以同步调用你的函数,也可以异步调用,就像我们之前对nodeinfo函数做的那样。
想要更多的代码例子吗?
- 关于JavaScript和Node.js,请看。Serverless For Everyone Else (https://store.openfaas.com/l/serverless-for-everyone-else)
- 对于Golang(Go),见。Everyday Go (https://store.openfaas.com/l/everyday-golang),最后一章主要介绍用Go编写的函数。
OpenFaaS网站(https://openfaas.com/blog/)上也有很多关于使用Python、Java、C#、现有Dockerfiles等的博文。
使用GitHub Actions的CI/CD
对于一个袖珍云来说,我们希望尽可能多地实现自动化,这包括构建我们的镜像和部署它们。我最喜欢的解决方案是GitHub Actions,但你也可以使用你最熟悉的类似技术。
GitHub Actions(https://github.com/features/actions),加上GitHub的容器注册中心ghcr.io(https://ghcr.io/),为我们的功能提供了完美的组合。任何公共仓库和图像都可以自由构建、存储和部署。
在一个简单的管道中,我们可以安装faas-cli(https://github.com/openfaas/faas-cli),拉下我们需要的模板并发布我们的容器镜像。
我把构建和部署分开了,但你也可以把它们结合起来。
与其使用mystars.yml,不如将该文件重命名为stack.yml,这样我们在构建中就少了一个需要配置的东西。
name: buildon:push:branches:- '*'pull_request:branches:- '*'permissions:actions: readchecks: writecontents: readpackages: writejobs:build:runs-on: ubuntu-lateststeps:- uses: actions/checkout@masterwith:fetch-depth: 1- name: Get faas-clirun: curl -sLSf https://cli.openfaas.com | sudo sh- name: Set up QEMUuses: docker/setup-qemu-action@v1- name: Set up Docker Buildxuses: docker/setup-buildx-action@v1- name: Get TAGid: get_tagrun: echo ::set-output name=TAG::latest-dev- name: Get Repo Ownerid: get_repo_ownerrun: >
echo ::set-output name=repo_owner::$(echo ${{ github.repository_owner }} |
tr '[:upper:]' '[:lower:]') - name: Login to Container Registryuses: docker/login-action@v1with:username: ${{ github.repository_owner }}password: ${{ secrets.GITHUB_TOKEN }}registry: ghcr.io- name: Publish functionsrun: >
OWNER="${{ steps.get_repo_owner.outputs.repo_owner }}"
TAG="latest"
faas-cli publish
--extra-tag ${{ github.sha }}
--platforms linux/arm/v7
为了替换堆栈.yml中你的容器镜像的默认标签 “最新”,并将字段:image: ghcr.io/alexellis/mystars:latest改为image: ghcr.io/alexellis/mystars:${TAG:-latest}。
接下来的部分是部署该功能。每当我在GitHub repo中创建一个版本时,它就会运行,并将使用推送到GHCR的SHA部署最新的镜像。
name: publishon:push:tags:- '*'permissions:actions: readchecks: writecontents: readpackages: readjobs:publish:runs-on: ubuntu-lateststeps:- uses: actions/checkout@masterwith:fetch-depth: 1- name: Get faas-clirun: curl -sLSf https://cli.openfaas.com | sudo sh- name: Pull template definitionsrun: faas-cli template pull- name: Get TAGid: get_tagrun: echo ::set-output name=TAG::latest-dev- name: Get Repo Ownerid: get_repo_ownerrun: >
echo ::set-output name=repo_owner::$(echo ${{ github.repository_owner }} |
tr '[:upper:]' '[:lower:]') - name: Login to Container Registryuses: docker/login-action@v1with:username: ${{ github.repository_owner }}password: ${{ secrets.GITHUB_TOKEN }}registry: ghcr.io- name: Loginrun: >
echo ${{secrets.OPENFAAS_PASSWORD}} |
faas-cli login --gateway ${{secrets.OPENFAAS_URL}} --password-stdin - name: Deployrun: >
OWNER="${{ steps.get_repo_owner.outputs.repo_owner }}"
TAG="${{ github.sha }}"
faas-cli deploy --gateway ${{secrets.OPENFAAS_URL}}
The above examples are from Serverless For Everyone Else, you can see the example repo here: alexellis/faasd-example
最后一部分–部署可以通过inlets HTTPS隧道(https://inlets.dev/)进行。
你的inlets URL可以通过一个名为OPENFAAS_URL的秘密以及OPENFAAS_PASSWORD中的OpenFaaS API的密码进行配置。
关于入口的更多信息,请参见。
- 当你的ISP不给你一个静态IP时 (https://inlets.dev/blog/2021/04/13/your-isp-wont-give-you-a-static-ip.html)
- 自动化的HTTP隧道服务器 (https://docs.inlets.dev/tutorial/automated-http-server/)
以上例子来自Serverless For Everyone Else (https://store.openfaas.com/l/serverless-for-everyone-else),你可以在这里看到例子repo:alexellis/faasd-example (https://github.com/alexellis/faasd-example)
触发你的功能
如果你设置了一个inlets隧道,那么你就会有一个用于OpenFaaS API的HTTPS URL,并有可能在此基础上为你的功能设置一些自定义域名。
例如,在我的Treasure Trove功能中,insiders.alexellis.io域名映射到OpenFaaS的http://127.0.0.1:8080/function/trove。
因此,你可以为传入的webhooks提供一个外部系统的URL—来自Stripe、PayPal、GitHub、Strava等,或者你可以分享你的自定义域名的URL,就像我对Treasure Trove做的那样。在我上面提到的例子中,我提供了一个URL给IFTTT,用来发送JSON格式的推文,然后在发送到一个Discord频道前进行过滤。
这取决于你是否要使用同步或异步的URL。如果你的功能很慢,那么发端系统可能会重试消息,在这种情况下,异步URL会更好。
UI的服务端口是8080,但可以放在一个TLS终止的反向代理后面,如Caddy或Nginx。

在HTTP之后,第二种最流行的调用函数的方式是通过一个cron时间表。
想象一下,你有一个函数,每5分钟运行一次,在查询了数据库的待定行后,发送密码重置邮件。
它可能看起来有点像这样。
version: 1.0provider:name: openfaasgateway: http://127.0.0.1:8080functions:mystars:lang: node16handler: ./mystarsimage: ghcr.io/alexellis/send-password-resets:latesttopic: cron-functionschedule: "*/5 * * * *"secrets:- aws-sqs-secret-key- aws-sqs-secret-token- postgresql-username- postgresql-passwordenvironment:db_name: resets
除了代码之外,你还可以提供一些秘密和环境变量来配置这个功能。
你可以通过编辑faasd的docker-compose.yaml(https://github.com/openfaas/faasd/blob/master/docker-compose.yaml)文件,将有状态的容器添加到faasd中。不要被这个名字所迷惑,Docker和Compose并没有在faasd中使用,但我们确实使用了同样熟悉的规范来定义运行的内容。
下面是我们如何添加NATS,用一个bind-mount使它的数据在重启或重新启动之间保持不变。
nats:image: docker.io/library/nats-streaming:0.22.0user: "65534"command:- "/nats-streaming-server"- "-m"- "8222"- "--store=file"- "--dir=/nats"- "--cluster_id=faas-cluster"volumes:- type: bindsource: ./natstarget: /nats
你可以在《Serverless For Everyone Else》(https://store.openfaas.com/l/serverless-for-everyone-else)中了解如何配置cron时间表、有状态服务、秘密和环境变量。
监测
- OpenFaaS以Prometheus格式为自己的API和你通过OpenFaaS网关调用的任何函数发射度量。
- 浏览文档中的度量标准。OpenFaaS指标 (https://docs.openfaas.com/architecture/metrics/)
- 我建立了一个简单的仪表盘正在监控几个关键功能。
- Derek (https://github.com/alexellis/derek) – 安装在GitHub的各个组织上,德里克通过减少重复性工作来帮助减少维护者的疲劳。
- Treasure Trove (https://insiders.alexellis.io/) – 我为GitHub赞助商提供的门户 – 可以访问我所有的技术写作,时间可以追溯到2019年,并且我的电子书有折扣
- Filter-tweets – 由If This Then That (IFTTT)触发 (https://ifttt.com/) – 这个函数读取JSON主体,过滤掉垃圾信息和openfaas账户或我自己的推文,然后将信息转发到一个discord频道。
- Stars – 作为webhook安装在inlets、openfaas GitHub组织和我的一些个人仓库上。它接收来自GitHub的JSON事件,并将其翻译成格式良好的Discord消息。

当代码从GitHub收到无法处理的事件时,这些错误或非200/300的信息来自德里克。