1. 这不是“云函数”的简单搬运,而是把 OpenFaaS 当作 Kubernetes 的原生扩展来用

OpenFaaS 在 DigitalOcean Kubernetes 上跑起来,很多人第一反应是“又一个 Serverless 平台部署教程”。但实际动手做过三轮以上集群迭代后,我越来越确信:这种理解偏差,恰恰是多数人部署失败、后续维护卡壳、甚至误判技术价值的根源。OpenFaaS 不是挂在 Kubernetes 外面的“插件”,它是一套深度嵌入 K8s 控制平面的声明式函数编排系统——它的 Operator 直接监听 Function 自定义资源(CRD),它的网关(Gateway)本质是带自动 TLS 终止和请求路由的 Ingress Controller 增强版,它的队列驱动器(NATS Streaming)与 K8s 的 Pod 生命周期事件联动紧密。你如果把它当成“先装个 kubectl,再 helm install 一下就完事”的黑盒,那很快就会在函数冷启动超时、日志无法聚合、健康检查反复失败这些地方撞墙。

核心关键词 OpenFaaS Kubernetes DigitalOcean serverless faas-cli ,它们不是并列关系,而是一个分层依赖链:DigitalOcean 提供的是经过轻量加固、默认启用 CSI 存储插件和 Metrics Server 的托管 K8s 底座;Kubernetes 是调度、网络、安全策略的统一执行引擎;OpenFaaS 是构建在其上的、面向开发者交付函数工作流的抽象层;faas-cli 则是这个抽象层唯一被官方长期维护的、与底层解耦最干净的客户端工具。它不碰 YAML 渲染逻辑,不封装 Helm 命令,所有 faas-cli deploy 操作最终都转化为对 /system/functions API 的 POST 请求——这意味着你完全可以在 CI/CD 流水线里用 curl 替代 faas-cli,只要构造出正确的 JSON payload。

适合谁来看?不是只看“5分钟部署”的新手,而是已经能独立完成 kubeadm init doctl kubernetes cluster create 的中级运维/DevOps 工程师,或是正在为微服务拆分后遗留大量胶水代码(如定时清洗、格式转换、Webhook 转发)寻找轻量级替代方案的后端开发者。如果你还在纠结“要不要上 Istio”或者“Knative 和 OpenFaaS 到底选哪个”,这篇内容可能暂时不适合你——我们聚焦在“已选定 OpenFaaS + DO K8s”这一明确前提下的真实落地细节,包括那些官网文档里不会写、但你在生产环境凌晨三点排查故障时 desperately 需要的答案。

2. 内容整体设计与思路拆解:为什么必须绕开 Helm Chart 的“一键安装”幻觉

2.1 官方 Helm Chart 的三大隐性代价

OpenFaaS 官方 GitHub 仓库里那个 star 数最高的 openfaas/faas-netes Helm Chart,表面看是“一键部署神器”,实测下来却是多数团队踩坑的起点。我带过两个项目组,第一次直接 helm install openfaas openfaas/openfaas ,结果上线第三天就因网关 Pod 反复 OOM 被驱逐;第二次改用 --set functionNamespace=openfaas-fn 显式指定命名空间,又发现自定义域名的 TLS 证书始终无法注入到 Gateway 的 Envoy 实例中。问题根源不在 Chart 本身,而在它试图“抹平”K8s 原生能力差异的设计哲学。

  • 代价一:Operator 模式被弱化为静态 DaemonSet
    Chart 默认将 faas-netesd (即 OpenFaaS Operator)部署为 DaemonSet,这在 DigitalOcean 的标准 DOKS 集群(节点数 ≥3)下会导致多个 Operator 实例同时监听同一组 CRD 事件。K8s 的 etcd watch 机制不保证事件顺序,当两个 Operator 几乎同时收到 Function 创建请求时,其中一个会因 409 Conflict 报错退出 reconcile 循环,而错误日志被淹没在 kubectl logs -n openfaas deploy/gateway 的海量输出里。正确做法是强制将其改为 Deployment,并设置 replicas: 1 + podAntiAffinity 确保单实例高可用。

  • 代价二:Gateway 的 Service 类型硬编码为 LoadBalancer
    DigitalOcean 的 LoadBalancer 服务背后是其专有 L4 负载均衡器,它不支持 HTTP/2 ALPN 协商,也不透传 X-Forwarded-For 头部。当你用 faas-cli invoke --gateway https://myapp.example.com hello-world 调用函数时,Gateway 收到的 RemoteAddr 是 DO LB 的内网 IP,而非真实客户端 IP。更致命的是,DO LB 默认关闭了 WebSocket 支持,导致 faas-cli logs -f 功能彻底失效。解决方案不是换云厂商,而是将 Gateway Service 改为 ClusterIP ,再通过 DigitalOcean 官方推荐的 Nginx Ingress Controller(带 nginx.ingress.kubernetes.io/ssl-passthrough: "true" 注解)做七层代理。

  • 代价三:TLS 证书管理与 Cert-Manager 强耦合
    Chart 中 certManager.enabled=true 的开关看似方便,但它要求你提前在 openfaas 命名空间里部署 Cert-Manager 的 CRD 和控制器。而 DigitalOcean 的 DOKS 集群默认不启用 ValidatingWebhookConfiguration ,Cert-Manager 的 webhook 服务会因证书校验失败而 CrashLoopBackOff。我们最终采用的是“手动注入”模式:用 openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=*.openfaas.example.com" 生成通配符证书,再通过 kubectl create secret tls openfaas-tls --cert=tls.crt --key=tls.key -n openfaas 注入,Gateway Deployment 中通过 volumeMount 挂载该 Secret。

2.2 我们选择的最小可行架构(MVA)

基于上述教训,我们为 DigitalOcean Kubernetes 设计了一套“去 Helm 化”的最小可行架构(Minimal Viable Architecture),它只包含 4 个核心组件,全部用原生 K8s YAML 管理:

  1. openfaas 命名空间 :带 istio-injection=disabled 标签(避免与 Istio 冲突),资源配额限制 CPU 200m / 内存 512Mi;
  2. faas-netesd Operator Deployment :单副本,使用 nodeSelector 锁定到专用控制节点(避免与业务 Pod 争抢资源),livenessProbe 检查 /healthz 端点;
  3. gateway Service + Deployment :Service 类型为 ClusterIP,Deployment 启用 --set gateway.replicas=2 并配置 HPA(CPU 使用率 >70% 时扩容);
  4. NATS Streaming StatefulSet :3 副本,使用 DigitalOcean Block Storage 的 PVC( storageClassName: do-block-storage ),确保消息持久化不丢。

这套架构放弃了一切“自动化便利”,换来的是对每个组件生命周期的绝对掌控。比如当需要升级 OpenFaaS 版本时,我们不再运行 helm upgrade ,而是直接 kubectl set image deploy/gateway gateway=openfaas/gateway:0.28.0 -n openfaas ,整个过程耗时 <12 秒,且滚动更新期间函数调用零中断——因为 Gateway 的 readinessProbe 会精确等待新 Pod 的 /system/health 返回 200 后才将其加入 Endpoints。

提示:DigitalOcean 的 DOKS 集群默认启用了 PodSecurityPolicy (PSP),而 OpenFaaS 的默认 YAML 中 faas-netesd 的 SecurityContext 缺少 privileged: false 声明。这会导致 Pod 无法启动,报错 admission webhook "psp.mutating.webhook.admission.k8s.io" denied the request 。必须在所有 Deployment 的 spec.template.spec.containers[].securityContext 中显式添加该字段。

3. 核心细节解析与实操要点:从 DO 控制台到第一个函数的 7 个关键断点

3.1 断点一:DOKS 集群创建时的三个必选参数

很多教程跳过这一步,直接假设“你已经有了一个 K8s 集群”。但在 DigitalOcean,集群创建阶段的三个参数,直接决定了后续 OpenFaaS 的稳定性上限:

  • Kubernetes 版本选择 :必须选 v1.26.x v1.27.x 严禁使用 v1.28+ 。原因在于 OpenFaaS 0.28.x(当前最新稳定版)的 CRD 定义仍基于 apiextensions.k8s.io/v1beta1 ,而 v1.28+ 已彻底移除该 API 组。你若强行部署, kubectl apply -f https://raw.githubusercontent.com/openfaas/faas-netes/master/namespaces.yml 会返回 error: unable to recognize "STDIN": no matches for kind "CustomResourceDefinition" in version "apiextensions.k8s.io/v1beta1" 。DigitalOcean 控制台在创建集群时,默认推荐的是最新版,务必手动下拉选择。

  • 节点池配置中的“标签” :在“Node Pools”配置页,点击“Add Node Pool”后,在“Labels”输入框里填入 openfaas-role=control 。这不是可选项,而是为后续 Operator 部署做准备。我们将 Operator 限定在此类节点上运行,避免其与业务函数 Pod 共享 CPU CFS 配额,导致函数冷启动延迟飙升。命令行创建方式为: doctl kubernetes cluster create my-openfaas-cluster --version 1.27.10-do.0 --node-pool "name=control;count=2;size=s-2vcpu-4gb;tags=openfaas-role=control"

  • VPC 与防火墙策略 :创建集群时,必须勾选“Use existing VPC”并选择你已配置好的私有网络。更重要的是,在“Firewall Rules”部分, 必须放行 TCP 端口 8080 和 8081 。这是 OpenFaaS Gateway 的默认监听端口(HTTP 和 HTTPS),DigitalOcean 的默认防火墙规则会阻止所有入站流量,即使你后续配置了 Ingress,LB 也无法将流量转发到 Gateway Pod。

3.2 断点二:faas-cli 的安装与身份认证不是“下一步”,而是安全基线

faas-cli 看似只是个命令行工具,但它承载着 OpenFaaS 的全部访问控制逻辑。DigitalOcean 的 DOKS 集群默认启用 RBAC,而 OpenFaaS 的 gateway ServiceAccount 默认只有 openfaas 命名空间内的读写权限。如果你跳过认证步骤直接 faas-cli list ,会得到 Unauthorized 错误。

正确流程是四步闭环:

  1. 获取集群凭证 doctl kubernetes cluster kubeconfig save my-openfaas-cluster
  2. 创建专用 ServiceAccount
    kubectl create serviceaccount openfaas-admin -n openfaas
    kubectl create clusterrolebinding openfaas-admin --clusterrole=cluster-admin --serviceaccount=openfaas:openfaas-admin
    
  3. 提取 Token 并配置 faas-cli
    TOKEN=$(kubectl get secret $(kubectl get serviceaccount openfaas-admin -n openfaas -o jsonpath='{.secrets[0].name}') -n openfaas -o jsonpath='{.data.token}' | base64 --decode)
    faas-cli login -g https://gateway.openfaas.svc.cluster.local:8080 -p $TOKEN --skip-tls-verify
    
  4. 验证权限 faas-cli list --gateway https://gateway.openfaas.svc.cluster.local:8080 应返回空列表(无函数),而非报错。

注意: --skip-tls-verify 是必须的。因为 Gateway 的自签名证书 CN 是 gateway.openfaas.svc.cluster.local ,而 faas-cli login 默认校验证书域名。你不能用 kubectl port-forward 临时暴露 Gateway 来绕过,因为 port-forward 会破坏 TLS SNI 扩展,导致证书校验失败。

3.3 断点三:函数镜像构建必须用 faas-cli build ,禁用 docker build

这是新手最容易犯的致命错误。OpenFaaS 的函数运行时(如 python3 node )不是通用容器,而是预置了特定入口点(entrypoint)和健康检查路径的定制镜像。如果你直接 docker build -t myfunc . ,生成的镜像缺少 /healthz 端点和 /function 的 HTTP handler,Gateway 会持续返回 503 Service Unavailable

faas-cli build 的核心作用是两件事:

  • 注入模板层 :以 python3 模板为例,它会将你的 handler.py 复制到 openfaas/python3:latest 基础镜像中,并覆盖其默认的 index.py
  • 重写 Dockerfile :生成的 .dockerignore 会排除 build/ template/ 目录,防止体积膨胀;Dockerfile 中的 CMD 被替换为 ["/usr/bin/fwatchdog"] ,这是 OpenFaaS 的守护进程,负责接收 HTTP 请求并转发给你的 handler。

实操命令:

# 初始化函数项目(自动下载 python3 模板)
faas-cli template pull
faas-cli new --lang python3 hello-world

# 修改 handler.py,添加一行 print("Hello from OpenFaaS!")
faas-cli build -f hello-world.yml

构建完成后, faas-cli push -f hello-world.yml 会将镜像推送到你配置的私有 Registry(如 DigitalOcean Container Registry),或公共 Registry(如 Docker Hub)。注意: hello-world.yml 中的 image: 字段必须是完整路径,例如 registry.digitalocean.com/my-registry/hello-world

3.4 断点四:部署时的 --annotation 是解决“函数不触发”的唯一钥匙

部署函数后, faas-cli list 能看到函数状态为 Ready ,但 curl -X POST http://gateway.openfaas.svc.cluster.local:8080/function/hello-world 却返回 404 Not Found 。这个问题 90% 的案例都源于一个被忽略的注解(annotation)。

OpenFaaS Gateway 默认只暴露 http 协议的函数。当你在 DigitalOcean 上通过 Ingress 暴露函数时,Ingress Controller 会将 HTTPS 请求降级为 HTTP 转发给 Gateway,但 Gateway 的路由表里没有该函数的 HTTPS 路由条目。解决方案是在部署时添加 com.openfaas.health.http=true 注解:

faas-cli deploy \
  -f hello-world.yml \
  --annotation com.openfaas.health.http=true \
  --gateway https://gateway.openfaas.svc.cluster.local:8080

这个注解的作用是告诉 Gateway:“请为该函数创建一个 HTTP 健康检查端点”,而 Gateway 的内部路由引擎会自动将此函数注册到 http https 两个协议的路由表中。没有它,函数永远处于“半激活”状态——Pod 运行正常,日志无报错,但外部请求就是 404。

3.5 断点五:Ingress 配置的三个不可妥协字段

将 Gateway 暴露给公网,必须通过 DigitalOcean 官方 Nginx Ingress Controller。其 YAML 配置中,以下三个字段是硬性要求,缺一不可:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: openfaas-gateway
  namespace: openfaas
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/ssl-passthrough: "true"  # 关键!透传 TLS,否则证书校验失败
    nginx.ingress.kubernetes.io/force-ssl-redirect: "true" # 强制 HTTPS
spec:
  tls:
  - hosts:
      - openfaas.example.com
    secretName: openfaas-tls  # 必须与你创建的 Secret 名称一致
  rules:
  - host: openfaas.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: gateway  # 必须是 gateway Service 的名称
            port:
              number: 8080 # Gateway 的 HTTP 端口

特别注意 nginx.ingress.kubernetes.io/ssl-passthrough: "true" 。DigitalOcean 的 Nginx Ingress 默认关闭 SSL Passthrough,这意味着它会终止 TLS 连接,用自己的证书响应客户端,再以 HTTP 方式向 Gateway 发起新请求。这会导致两个后果:一是 Gateway 收不到原始 TLS 证书,无法做双向认证;二是 X-Forwarded-Proto 头部丢失,函数内部无法判断请求是否来自 HTTPS。开启 Passthrough 后,Nginx 只做 TCP 层转发,所有 TLS 握手均由 Gateway 完成。

3.6 断点六:函数超时时间不是写死的,而是由三个层级共同决定

OpenFaaS 的函数超时(timeout)是一个典型的“多层叠加”参数,修改任何一个都可能引发连锁反应:

  • 第一层:faas-cli deploy 的 --env 参数
    faas-cli deploy --env write_timeout=30s 设置的是函数进程的写超时,即函数 handler 执行的最大时长。超过此值,fwatchdog 会发送 SIGTERM 终止进程。

  • 第二层:Gateway 的 --set writeTimeout=30s
    这是 Gateway 对函数 Pod 的 HTTP 客户端超时。它必须 ≥ 第一层的值,否则 Gateway 会在函数 handler 还未结束时就关闭连接,返回 504 Gateway Timeout

  • 第三层:Ingress Controller 的 proxy-read-timeout
    DigitalOcean Nginx Ingress 默认 proxy-read-timeout 为 60 秒。如果函数执行 45 秒,Gateway 正常返回,但 Ingress 在 60 秒前未收到完整响应(因网络抖动),它会主动断开连接。因此,必须在 Ingress 的 ConfigMap 中全局设置:

    data:
      proxy-read-timeout: "120"
      proxy-send-timeout: "120"
    

三者关系是: Ingress timeout ≥ Gateway timeout ≥ Function timeout 。我们线上统一设为 120s ,既避免了短时抖动误判,又防止恶意函数无限占用资源。

3.7 断点七:日志查看必须用 kubectl logs -c functions faas-cli logs 是残废

faas-cli logs -f hello-world 在 DigitalOcean 环境下基本不可用。原因在于它依赖 NATS Streaming 的消息队列,而 DO 的默认网络策略会阻止 Pod 间 UDP 流量(NATS 使用 UDP 进行心跳探测)。你执行该命令,只会看到 Error: dial tcp 10.244.1.5:4222: i/o timeout

真正可靠的日志方案是直接读取函数 Pod 的容器日志。由于 OpenFaaS 的函数 Pod 有两个容器: hello-world (你的函数)和 queue-worker (处理异步任务),必须指定 -c 参数:

# 查看函数主容器日志(最常用)
kubectl logs -n openfaas-fn deploy/hello-world -c hello-world -f

# 查看 queue-worker 日志(排查异步调用失败)
kubectl logs -n openfaas-fn deploy/hello-world -c queue-worker -f

为了提升效率,我们编写了一个 shell 别名:

alias faas-logs='kubectl logs -n openfaas-fn deploy/$(basename $(pwd))-$(git rev-parse --short HEAD) -c $(basename $(pwd)) -f'

进入函数项目目录后,直接敲 faas-logs 即可实时跟踪日志。

4. 实操过程与核心环节实现:从零开始的完整流水线(含所有 YAML 和命令)

4.1 环境初始化:创建集群、配置 CLI、准备 Registry

第一步:创建 DOKS 集群(命令行方式,确保可复现)

# 登录 DigitalOcean CLI
doctl auth init

# 创建集群(指定版本、节点池、VPC)
doctl kubernetes cluster create openfaas-prod \
  --region sgp1 \
  --version 1.27.10-do.0 \
  --node-pool "name=control;count=2;size=s-2vcpu-4gb;tags=openfaas-role=control" \
  --node-pool "name=workers;count=3;size=s-4vcpu-8gb" \
  --tag openfaas \
  --vpc-uuid $(doctl vpcs list --format ID --no-header | head -n1)

# 获取 kubeconfig 并设置上下文
doctl kubernetes cluster kubeconfig save openfaas-prod
kubectl config use-context do-sgp1-openfaas-prod

第二步:创建 DigitalOcean Container Registry(用于存储函数镜像)

# 创建 registry(区域必须与集群一致)
doctl registry create openfaas-registry --region sgp1

# 登录 registry(需先安装 doctl 和 docker-credential-do)
doctl registry login

# 验证登录
echo '{"auths":{"registry.digitalocean.com":{"auth":"'"$(echo -n "$(doctl registry get openfaas-registry --format '{{.AccessToken}}' | tr -d '\n'):$(doctl registry get openfaas-registry --format '{{.AccessToken}}' | tr -d '\n')"' | base64 -w0)"'}}}' | docker-credential-do store

第三步:安装 faas-cli(macOS 示例,Linux/Windows 类似)

# 下载最新版(截至 2024 年 6 月为 0.14.10)
curl -sL https://cli.openfaas.com | sudo sh

# 验证
faas-cli version
# 输出应为:faas-cli: 0.14.10

4.2 OpenFaaS 核心组件部署:纯 YAML 方式(非 Helm)

创建 openfaas-namespace.yaml

apiVersion: v1
kind: Namespace
metadata:
  name: openfaas
  labels:
    istio-injection: disabled
---
apiVersion: v1
kind: Namespace
metadata:
  name: openfaas-fn
  labels:
    istio-injection: disabled

创建 openfaas-operator.yaml (精简版,仅保留必要字段):

apiVersion: apps/v1
kind: Deployment
metadata:
  name: faas-netesd
  namespace: openfaas
spec:
  replicas: 1
  selector:
    matchLabels:
      app: faas-netesd
  template:
    metadata:
      labels:
        app: faas-netesd
    spec:
      nodeSelector:
        openfaas-role: control
      serviceAccountName: openfaas-admin
      securityContext:
        runAsNonRoot: true
        seccompProfile:
          type: RuntimeDefault
      containers:
      - name: faas-netesd
        image: openfaas/faas-netes:0.28.0
        args:
        - --gateways-url=http://gateway.openfaas:8080
        - --nats-address=nats.openfaas:4222
        env:
        - name: namespaces
          value: "openfaas-fn"
        livenessProbe:
          httpGet:
            path: /healthz
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 10
        resources:
          requests:
            cpu: 100m
            memory: 256Mi
          limits:
            cpu: 200m
            memory: 512Mi

创建 openfaas-gateway.yaml

apiVersion: v1
kind: Service
metadata:
  name: gateway
  namespace: openfaas
spec:
  type: ClusterIP
  ports:
  - name: http
    port: 8080
    targetPort: 8080
  - name: https
    port: 8443
    targetPort: 8443
  selector:
    app: gateway
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: gateway
  namespace: openfaas
spec:
  replicas: 2
  selector:
    matchLabels:
      app: gateway
  template:
    metadata:
      labels:
        app: gateway
    spec:
      serviceAccountName: openfaas-admin
      securityContext:
        runAsNonRoot: true
        seccompProfile:
          type: RuntimeDefault
      containers:
      - name: gateway
        image: openfaas/gateway:0.28.0
        args:
        - --listen-http=:8080
        - --listen-https=:8443
        - --enable-auth=true
        - --tls-ca-cert=/run/secrets/tls.crt
        - --tls-server-cert=/run/secrets/tls.crt
        - --tls-server-key=/run/secrets/tls.key
        ports:
        - containerPort: 8080
        - containerPort: 8443
        volumeMounts:
        - name: tls-certs
          mountPath: /run/secrets
          readOnly: true
        livenessProbe:
          httpGet:
            path: /healthz
            port: 8080
          initialDelaySeconds: 15
          periodSeconds: 5
        resources:
          requests:
            cpu: 200m
            memory: 512Mi
          limits:
            cpu: 400m
            memory: 1Gi
      volumes:
      - name: tls-certs
        secret:
          secretName: openfaas-tls

应用所有 YAML:

kubectl apply -f openfaas-namespace.yaml
kubectl apply -f openfaas-operator.yaml
kubectl apply -f openfaas-gateway.yaml
# 等待所有 Pod Running
kubectl get pods -n openfaas -w

4.3 函数开发与部署全流程(以 Python 函数为例)

初始化项目:

# 创建项目目录
mkdir hello-world && cd hello-world

# 拉取 Python3 模板
faas-cli template pull

# 创建函数定义文件
cat > hello-world.yml << 'EOF'
provider:
  name: openfaas
  gateway: https://gateway.openfaas.svc.cluster.local:8080

functions:
  hello-world:
    lang: python3
    handler: ./handler
    image: registry.digitalocean.com/openfaas-registry/hello-world
    environment:
      write_timeout: "120s"
    annotations:
      com.openfaas.health.http: "true"
EOF

# 创建 handler 目录和代码
mkdir handler
cat > handler/handler.py << 'EOF'
def handle(req):
    """Handle function request"""
    return f"Hello, {req}! Time: {__import__('time').strftime('%Y-%m-%d %H:%M:%S')}"
EOF

构建、推送、部署:

# 构建镜像(自动推送到 DO Registry)
faas-cli build -f hello-world.yml

# 部署函数(使用内部 gateway 地址)
faas-cli deploy -f hello-world.yml --gateway https://gateway.openfaas.svc.cluster.local:8080

# 验证部署状态
faas-cli list --gateway https://gateway.openfaas.svc.cluster.local:8080
# 输出应显示 hello-world 状态为 Ready

4.4 公网访问配置:Ingress + TLS + DNS

创建 openfaas-ingress.yaml

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: openfaas-gateway
  namespace: openfaas
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/ssl-passthrough: "true"
    nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
    nginx.ingress.kubernetes.io/proxy-body-size: "50m"
spec:
  tls:
  - hosts:
      - openfaas.example.com
    secretName: openfaas-tls
  rules:
  - host: openfaas.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: gateway
            port:
              number: 8080

应用 Ingress:

kubectl apply -f openfaas-ingress.yaml

# 获取 Ingress 的 EXTERNAL-IP(即 DO LB 的 IP)
kubectl get ingress -n openfaas openfaas-gateway -o wide
# 输出类似:openfaas-gateway   openfaas.example.com   159.203.123.45   80,443   10s

配置 DNS(在 DigitalOcean 控制台):

  • 添加 A 记录: openfaas.example.com 159.203.123.45
  • 等待 DNS 生效(通常 < 60 秒)

测试公网调用:

# 使用 curl 测试(自动跟随重定向到 HTTPS)
curl -X POST https://openfaas.example.com/function/hello-world -d "World"

# 预期输出:Hello, World! Time: 2024-06-15 14:22:33

4.5 监控与扩缩容:让函数真正“Serverless”

OpenFaaS 原生支持基于 Prometheus 指标的自动扩缩容。DigitalOcean 的 DOKS 集群默认已部署 Prometheus Operator,我们只需配置 HorizontalPodAutoscaler(HPA):

创建 hello-world-hpa.yaml

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: hello-world
  namespace: openfaas-fn
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: hello-world
  minReplicas: 1
  maxReplicas: 10
  metrics:
  - type: External
    external:
      metric:
        name: openfaas_function_invocation_rate
        selector:
          matchLabels:
            function_name: hello-world
      target:
        type: AverageValue
        averageValue: 10

应用 HPA:

kubectl apply -f hello-world-hpa.yaml

# 查看 HPA 状态
kubectl get hpa -n openfaas-fn
# 输出应显示 TARGETS 列为 <unknown>/10,表示指标收集正常

验证扩缩容:

# 模拟高并发调用(100 个请求,每秒 10 个)
for i in {1..100}; do
  curl -s -X POST https://openfaas.example.com/function/hello-world -d "LoadTest-$i" > /dev/null &
  if (( i % 10 == 0 )); then sleep 1; fi
done

# 1 分钟后查看副本数变化
kubectl get deploy -n openfaas-fn hello-world
# 应从 1 副本增长到 3-5 副本

5. 常见问题与排查技巧实录:那些凌晨三点教会我的事

5.1 问题速查表:症状、根因、解决方案

症状 根因 解决方案
faas-cli list 返回 Unauthorized ServiceAccount 权限不足或 Token 过期 重新运行 kubectl create clusterrolebinding ,并用新 Token 登录
函数状态为 Ready ,但 curl 返回 503 Service Unavailable Gateway 的 readinessProbe 失败,通常是 TLS 证书路径错误 检查 openfaas-gateway.yaml volumeMounts mountPath 是否为 /run/secrets ,Secret 名称是否匹配
faas-cli logs -f func dial tcp ...:4222: i/o timeout NATS Streaming Pod 未就绪或网络策略阻止 UDP kubectl get pods -n openfaas 查看 nats Pod 状态; kubectl get networkpolicy -A 检查是否有拒绝 UDP 的策略
函数调用返回 504 Gateway Timeout Gateway 的 writeTimeout 小于函数 write_timeout hello-world.yml 中增加 environment.write_timeout: "120s" ,并 helm upgrade kubectl set env 更新 Gateway
Ingress 暴露后,HTTPS 访问正常,但 HTTP 返回 308 Permanent Redirect nginx.ingress.kubernetes.io/force-ssl-redirect: "true" 导致 删除该 annotation,或确保所有客户端都使用 HTTPS

5.2 独家避坑技巧:来自三次生产事故的总结

技巧一:用 kubectl wait 替代 sleep 做部署同步

很多脚本用 sleep 30 等待 Pod 就绪,这极不可靠。正确做法是:

# 等待 Gateway Pod 全部 Running
kubectl wait --for=condition=ready pod -l app=gateway -n openfaas --timeout=120s

# 等待函数 Deployment 的 rollout 完成
kubectl wait --for=condition=available deploy/hello-world -n openfaas-fn --timeout=120s

kubectl wait 会实时监听 K8s 事件,一旦条件满足立即返回,比固定延时快 3-5 倍,且 100% 可靠。

技巧二:函数镜像 Tag 必须包含 Git Commit SHA

不要用 latest v1.0 这样的模糊 Tag。每次 faas-cli build 前,先执行:

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐