拓冰建站拓冰建站
首页 / 资讯中心 / 正文

云原生架构实战:基于Knative与Istio构建弹性云函数与云应用

1. 从单体到云原生为什么我们需要重新思考应用构建方式几年前我还在为一个传统电商应用的后端架构焦头烂额。那是一个典型的单体应用每次大促前我们都要提前几周预估流量申请一堆虚拟机然后手动部署、扩容、配置负载均衡。流量高峰一过这些资源又大量闲置成本高企运维团队疲于奔命。这种“预测性扩容”的模式不仅反应迟钝还造成了巨大的资源浪费。直到我们开始接触云原生和Serverless整个开发和运维的范式才被彻底颠覆。今天我想和你深入聊聊“云原生之云函数云应用实现”这个主题这不仅仅是两个时髦词汇的堆砌而是一套能够让你真正实现“按需使用、按量付费”、将精力完全聚焦于业务逻辑的现代应用架构方法论。简单来说云函数Function as a Service, FaaS和云应用Cloud Native Application是云原生理念下的两种核心计算形态。云函数让你以函数为单位编写和运行代码无需管理服务器事件驱动毫秒级伸缩。而云应用在这里特指那些为云环境从头设计、能够充分利用云平台弹性能力的应用通常由容器封装通过服务网格进行治理。两者结合能够构建出既敏捷又健壮的系统。无论你是正在为应用的高并发弹性发愁的架构师还是厌倦了基础设施管理的开发者理解这套组合拳都能让你在云时代游刃有余。接下来我将从核心理念、关键技术选型、实战架构设计到具体的避坑经验为你完整拆解。2. 核心理念辨析云函数与云应用不是替代而是协同很多人初接触时会疑惑有了容器和Kubernetes为什么还需要云函数或者反过来云函数这么方便是不是可以取代所有传统应用这两种观点都失之偏颇。理解它们的定位和协作关系是设计出优秀架构的前提。云函数FaaS的本质是“事件驱动的无状态计算单元”。它的核心优势在于极致的敏捷性和成本效率。你只需要关心函数体内的业务逻辑从代码提交到上线运行可能只需要几分钟。它由事件如HTTP请求、消息队列消息、对象存储事件触发执行完成后资源立即释放你只为实际执行的时间和资源付费。这种模式非常适合处理突发流量、异步任务、数据ETL、IoT数据处理等场景。例如用户上传一张图片触发云函数进行缩略图处理订单支付成功触发云函数发送通知短信。然而云函数也有其局限性我称之为“三不”原则不适合长时间运行有超时限制通常几分钟到几十分钟、不适合有状态服务虽然可以通过外部存储实现状态但非原生支持、不适合内部通信密集的复杂应用冷启动延迟和函数间调用成本较高。云应用Cloud Native Application则是一个更宽泛的概念。它通常指基于容器技术通过微服务架构拆分的应用。每个服务可以独立开发、部署、伸缩。它更适合构建复杂的、有状态的、需要7x24小时运行的核心业务系统。比如用户服务、商品服务、订单服务等。云应用享受了容器的环境一致性、资源隔离等好处但依然需要你或你的平台来管理容器生命周期、服务发现、网络策略等。所以它们的关系是互补与协同。在一个典型的云原生架构中云应用作为稳定的、常驻的“基石”服务处理核心业务流程和状态管理而云函数则作为灵活的“粘合剂”和“扩展器”处理边缘计算、事件响应、异步任务等。例如一个电商系统用户、商品、订单等核心服务以云应用容器化微服务形式部署确保高可用和状态持久化而生成订单报表、处理用户行为日志、响应库存变更消息等任务则交给云函数实现资源的精准利用。3. 技术栈深度选型Knative、Istio与Envoy的黄金组合当我们决定拥抱云函数和云应用时技术选型是第一个实战关卡。市面上有各大云厂商的托管Serverless服务如AWS Lambda Azure Functions也有开源自建方案。对于追求控制力、需要混合云部署或定制化程度高的团队基于Kubernetes生态的自建方案是更优选择。这里Knative、Istio和Envoy构成了一个堪称“黄金组合”的解决方案。为什么是KnativeKnative本质上是在Kubernetes之上提供了一套用于构建、部署和管理现代化Serverless工作负载的中间件。它抽象掉了Kubernetes本身对于应用开发者而言的复杂性。Knative主要包含两个核心组件Serving用于部署和管理Serverless工作负载包括云函数和云应用。它实现了关键的“缩容到零”和“按请求扩容”能力。当没有流量时你的服务实例可以缩减到0个Pod不占用任何计算资源当请求到来时它能极速扩容通常几百毫秒内出新的Pod处理请求。这是实现成本优化的关键技术。Eventing用于管理事件的生产、路由和消费。它定义了清晰的事件源Source、事件通道Channel和事件消费者如云函数模型让事件驱动架构在Kubernetes内部变得标准且易于管理。Istio与Envoy的角色是什么这是很多人的困惑点。Knative Serving默认就集成了Istio作为其网络层。Istio是一个服务网格Service Mesh它负责服务间的流量管理、安全策略和可观测性。而Envoy是Istio的数据平面以Sidecar容器的形式注入到每个工作负载Pod中所有进出Pod的网络流量都经由Envoy代理。在Knative的语境下IstioEnvoy承担了至关重要的职责流量治理与灰度发布Knative Revision每次代码变更产生的版本之间的流量百分比分配就是通过Istio的VirtualService和DestinationRule实现的。你可以轻松实现蓝绿部署或金丝雀发布。自动伸缩的度量指标Knative的自动伸缩器Autoscaler需要监控流量指标来决定是否扩容。默认情况下它通过Istio Mixer旧版本或直接查询Envoy Sidecar新版本来获取每个Revision的并发请求数Concurrency或每秒请求数RPS。入口网关管理外部流量如何进入集群并路由到正确的Knative服务这依赖于Istio Ingress Gateway基于EnvoyKnative会为其自动配置路由规则。我个人的经验是直接采用“Knative Istio”的官方推荐组合是最稳妥的起步方案。虽然Knative也支持其他网络层如Contour、Kourier但Istio的功能最全面社区支持最好与Knative的集成也最成熟。Envoy作为Istio的默认数据平面其高性能和丰富的过滤器Filter生态为后续实现高级的流量切分、故障注入、链路追踪等需求打下了坚实基础。4. 实战部署从零搭建一个Knative on Kubernetes集群理论说得再多不如动手搭一遍。下面我将以在本地开发环境如Minikube或Kind部署一套完整的Knative Serving Istio环境为例手把手带你走通流程。生产环境的考量会在后续章节讨论。4.1 基础环境准备首先你需要一个可用的Kubernetes集群。这里使用Kind快速创建一个本地集群。# 安装kind (macOS with Homebrew) brew install kind # 创建一个名为knative的集群配置足够的资源 cat EOF | kind create cluster --name knative --config- kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 nodes: - role: control-plane extraPortMappings: - containerPort: 80 hostPort: 80 protocol: TCP - containerPort: 443 hostPort: 443 protocol: TCP - role: worker extraMounts: - hostPath: /var/run/docker.sock containerPath: /var/run/docker.sock EOF注意这里将宿主机的80和443端口映射到了控制平面节点是为了后续方便通过浏览器直接访问Knative服务。挂载Docker socket是为了让集群内的Pod能构建容器镜像如果需要。验证集群状态kubectl cluster-info --context kind-knative kubectl get nodes4.2 安装Istio与Knative Serving第一步安装Istio下载Istio命令行工具并安装到系统路径。安装Istio核心组件。我们选择demo配置集它包含了常用的功能。# 下载最新版Istio curl -L https://istio.io/downloadIstio | sh - cd istio-* export PATH$PWD/bin:$PATH # 安装Istio到集群 istioctl install --set profiledemo -y # 为default命名空间打上标签以自动注入Envoy Sidecar kubectl label namespace default istio-injectionenabled第二步安装Knative ServingKnative提供了官方的Operator安装方式非常简洁。# 安装Knative Serving Operator kubectl apply -f https://github.com/knative/operator/releases/download/knative-v1.11.0/operator.yaml # 等待Operator就绪 kubectl wait --forconditionready pod -l appknative-operator --timeout300s -n knative-operator # 创建KnativeServing自定义资源指定使用Istio cat EOF | kubectl apply -f - apiVersion: v1 kind: Namespace metadata: name: knative-serving --- apiVersion: operator.knative.dev/v1beta1 kind: KnativeServing metadata: name: knative-serving namespace: knative-serving spec: config: network: ingress-class: istio.ingress.networking.knative.dev autoscaler: # 配置自动伸缩行为例如目标并发数 target: 10 EOF这个配置的关键点在于ingress-class指定了使用Istio作为网络层。autoscaler.target设置为10意味着每个Pod的目标并发请求数是10Autoscaler会努力维持这个水平。第三步验证安装安装完成后检查关键组件是否运行正常。kubectl get pods -n knative-serving # 应该看到activator, autoscaler, controller, webhook, istio-webhook等Pod处于Running状态 kubectl get pods -n istio-system # 应该看到istiod, istio-ingressgateway等Pod运行正常5. 第一个云函数与云应用编写、部署与访问环境就绪现在我们来部署第一个工作负载。我将演示两种类型一个简单的HTTP云函数使用Knative Service以及一个更传统的云应用同样以Knative Service形式部署但代表一个常驻服务。5.1 部署一个Go语言HTTP云函数首先我们编写一个简单的Go Web应用。创建文件helloworld.gopackage main import ( fmt log net/http os ) func handler(w http.ResponseWriter, r *http.Request) { name : r.URL.Query().Get(name) if name { name World } fmt.Fprintf(w, Hello, %s!\n, name) log.Printf(Received request for name: %s, name) } func main() { port : os.Getenv(PORT) if port { port 8080 } http.HandleFunc(/, handler) log.Printf(Server listening on port %s, port) log.Fatal(http.ListenAndServe(:port, nil)) }这个函数监听HTTP请求并返回一个简单的问候语。接下来我们需要将其容器化。创建Dockerfile# 使用多阶段构建减少镜像体积 FROM golang:1.20-alpine AS builder WORKDIR /app COPY go.mod go.sum ./ RUN go mod download COPY *.go ./ RUN CGO_ENABLED0 GOOSlinux go build -o /server # 使用极简的运行时镜像 FROM alpine:latest RUN apk --no-cache add ca-certificates WORKDIR /root/ COPY --frombuilder /server . EXPOSE 8080 CMD [./server]构建并推送镜像到镜像仓库这里以Docker Hub为例你需要替换yourusernamedocker build -t yourusername/knative-helloworld-go:latest . docker push yourusername/knative-helloworld-go:latest现在创建Knative Service定义文件service.yamlapiVersion: serving.knative.dev/v1 kind: Service metadata: name: helloworld-go namespace: default spec: template: spec: containers: - image: yourusername/knative-helloworld-go:latest ports: - containerPort: 8080 env: - name: TZ value: Asia/Shanghai应用这个配置kubectl apply -f service.yaml几秒钟后检查服务状态kubectl get ksvc helloworld-go你会看到URL字段下生成一个地址格式类似http://helloworld-go.default.example.com。由于我们在本地需要先获取Istio Ingress Gateway的地址并配置本地DNS或使用端口转发来访问。获取访问地址并测试# 获取Istio Ingress Gateway的IP和端口在Kind中通常是localhost INGRESS_HOSTlocalhost INGRESS_PORT$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath{.spec.ports[?(.namehttp2)].nodePort}) # 通过curl访问注意需要携带Host头因为Knative服务是域名驱动的 curl -H Host: helloworld-go.default.example.com http://$INGRESS_HOST:$INGRESS_PORT?nameKnative你应该会收到响应Hello, Knative!。同时观察Pod的变化你会发现请求触发后Pod从0扩容到了1或更多kubectl get pods -l serving.knative.dev/servicehelloworld-go -w等待一段时间默认约90秒无流量这个Pod会自动缩容到0。这就是Serverless“缩容到零”的魅力。5.2 部署一个Python云应用常驻服务假设我们有一个Python Flask应用它提供了一个简单的用户信息API并连接了一个Redis缓存模拟有状态依赖。这个应用更适合作为“云应用”长期运行。app.py:from flask import Flask, jsonify import os import redis import time app Flask(__name__) cache_host os.getenv(REDIS_HOST, localhost) cache_port int(os.getenv(REDIS_PORT, 6379)) r redis.Redis(hostcache_host, portcache_port, decode_responsesTrue) app.route(/user/user_id) def get_user(user_id): # 先查缓存 cache_key fuser:{user_id} user_info r.get(cache_key) if user_info: return jsonify({source: cache, data: user_info}) # 模拟数据库查询耗时 time.sleep(0.1) user_info fUserInfo_{user_id} # 写入缓存过期时间60秒 r.setex(cache_key, 60, user_info) return jsonify({source: database, data: user_info}) if __name__ __main__: app.run(host0.0.0.0, port8080)requirements.txt:Flask2.3.2 redis4.5.5Dockerfile:FROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY app.py . EXPOSE 8080 CMD [python, app.py]同样构建推送镜像后创建Knative Service。但这次我们需要考虑它的依赖——Redis。在云原生环境中这类有状态中间件通常通过Operator或Helm单独部署。这里为了简化我们先在同一个命名空间部署一个Redis。kubectl create deployment redis --imageredis:7-alpine kubectl expose deployment redis --port6379然后创建云应用的Knative Service (user-service.yaml)apiVersion: serving.knative.dev/v1 kind: Service metadata: name: user-service namespace: default spec: template: metadata: annotations: # 关闭缩容到零因为这是一个常驻服务 autoscaling.knative.dev/minScale: 1 spec: containers: - image: yourusername/flask-user-service:latest ports: - containerPort: 8080 env: - name: REDIS_HOST value: redis.default.svc.cluster.local # Kubernetes Service DNS - name: REDIS_PORT value: 6379 resources: requests: memory: 256Mi cpu: 250m limits: memory: 512Mi cpu: 500m这里的关键注解是autoscaling.knative.dev/minScale: 1它告诉Knative Autoscaler无论有没有流量至少保持1个Pod运行。这适用于我们期望常驻的云应用服务。部署并测试kubectl apply -f user-service.yaml # 获取URL并访问 curl -H Host: user-service.default.example.com http://$INGRESS_HOST:$INGRESS_PORT/user/123首次访问会显示source: database60秒内再次访问则会显示source: cache证明应用和缓存工作正常。6. 高级配置与生产级考量弹性、观测与安全将服务跑起来只是第一步。要用于生产我们必须深入配置自动伸缩、建立完整的可观测性体系并考虑安全问题。6.1 精细化配置自动伸缩AutoscalingKnative的自动伸缩器非常强大默认基于并发请求数concurrency工作。但在不同场景下我们需要调整其行为。基于RPS每秒请求数伸缩对于API网关或高吞吐量的前端服务RPS可能比并发数更直观。apiVersion: serving.knative.dev/v1 kind: Service metadata: name: high-rps-service spec: template: metadata: annotations: autoscaling.knative.dev/metric: rps # 将度量指标改为RPS autoscaling.knative.dev/target: 100 # 目标每个Pod每秒处理100个请求设置扩缩容边界与行为annotations: autoscaling.knative.dev/minScale: 2 # 最少2个Pod保证可用性 autoscaling.knative.dev/maxScale: 50 # 最多50个Pod防止成本失控 autoscaling.knative.dev/panicWindowPercentage: 10.0 # 恐慌窗口设为10%的稳定窗口 autoscaling.knative.dev/panicThresholdPercentage: 200.0 # 当指标超过目标的200%时进入“恐慌模式”快速扩容panicThresholdPercentage是我强烈建议配置的参数。在流量瞬间暴涨时如秒杀开始默认的渐进扩容可能来不及导致请求堆积。开启恐慌模式后Autoscaler会以更激进的速度扩容待流量平稳后再回到稳定模式。KPA vs. HPAKnative默认使用KPAKnative Pod Autoscaler。你也可以启用HPAHorizontal Pod Autoscaler来支持基于CPU/内存等自定义指标的伸缩只需在KnativeServing CR中配置即可。6.2 可观测性日志、指标与追踪没有可观测性Serverless就是黑盒。Knative与Istio的集成让观测变得相对容易。日志所有Pod包括Knative服务Pod和Envoy Sidecar的日志都可以通过kubectl logs或集群的日志聚合系统如EFK Stack收集。对于云函数尤其要关注Activator和Autoscaler的日志以诊断伸缩问题。指标Knative自身指标Knative Controller和Autoscaler会暴露丰富的Prometheus指标如autoscaling_actual_pods当前Pod数、request_concurrency请求并发数等。你需要部署Prometheus来抓取这些指标。应用指标你的应用可以暴露自定义的Prometheus指标并通过ServiceMonitor被Prometheus发现和抓取。Istio指标Envoy Sidecar会生成大量关于流量的指标如请求数、延迟、错误码这是分析服务间通信和性能的黄金数据源。分布式追踪这是理解复杂调用链的关键。Istio集成了Jaeger、Zipkin等追踪后端。你需要部署一个追踪收集器并在Knative Service的模板中启用追踪采样。spec: template: metadata: annotations: sidecar.istio.io/inject: true proxy.istio.io/config: | tracing: sampling: 100 # 100%采样率生产环境可调低 zipkin: address: zipkin.istio-system:9411这样从Ingress Gateway到你的云函数再到其下游服务如Redis整个调用链都会被记录下来在Jaeger UI中可视化。6.3 安全与网络策略服务间认证与授权Istio可以轻松实现服务间的mTLS双向TLS通信确保Pod间的网络流量是加密且经过认证的。在Knative中这通常是默认或可配置的。此外可以使用Istio的AuthorizationPolicy来定义“哪个服务可以访问哪个服务”的细粒度规则。网络策略虽然Istio提供了高级的网络控制但Kubernetes NetworkPolicy可以作为一道基础防火墙。例如你可以限制只有特定的命名空间或Pod标签可以访问你的Knative服务。镜像安全确保使用来自可信仓库的镜像并扫描镜像中的漏洞。可以考虑集成像Trivy这样的工具到CI/CD流水线中。Secret管理应用的密码、API密钥等敏感信息必须通过Kubernetes Secret管理并以环境变量或Volume方式挂载到Pod中绝对不要硬编码在代码或镜像里。7. 避坑指南冷启动、配置管理与版本升级在实际生产中使用Knative我踩过不少坑。这里分享几个最典型的希望能帮你绕过去。7.1 冷启动延迟Serverless的阿喀琉斯之踵冷启动Cold Start是指从零个Pod扩容到第一个Pod ready并开始处理请求的时间。对于Go、Rust等编译型语言可能只有几百毫秒但对于Python、Java尤其是Spring Boot等需要加载庞大运行时和框架的语言冷启动可能达到数秒甚至十几秒这对用户体验是致命的。应对策略保持最小实例数minScale对于对延迟敏感的核心服务设置minScale: “1”或更大用一定的常驻成本换取零冷启动。这是最直接有效的方法。优化镜像体积使用多阶段构建选择最精简的基础镜像如distroless、alpine移除不必要的依赖和文件。镜像越小拉取和启动越快。使用更快的运行时对于新项目可以考虑使用Go、Rust等冷启动极快的语言。对于Java可以尝试Quarkus、Micronaut等原生编译或轻量级框架。预热Pre-warming通过定时任务或低流量持续请求定期调用你的服务使其保持“温热”状态。Knative社区也有一些预热工具但实现相对复杂。合理设置并发目标target如果你的服务处理请求很快可以适当提高target值比如从10调到50让单个Pod处理更多请求减少不必要的Pod数量波动从而间接减少冷启动概率。7.2 配置与Secret的动态管理Knative Service的配置环境变量、ConfigMap、Volume是定义在Revision模板中的。每次更新配置都会产生一个新的Revision。这带来了一个挑战如何在不重新部署代码的情况下更新配置如功能开关、第三方API地址解决方案外部化配置将配置存储在外部系统如Consul、etcd或云服务商的参数存储中。应用启动时或定期从这些系统拉取配置。这增加了应用逻辑的复杂性。使用Knative Configuration独立管理虽然不常见但你可以直接创建和更新Knative Configuration资源然后让Service引用它。这可以实现配置的独立更新和回滚。结合GitOps将Knative Service的YAML文件存放在Git仓库中。任何配置变更都通过提交PR、代码评审、然后由Argo CD或Flux等工具同步到集群。这保证了配置变更的可审计和可回滚是推荐的生产实践。对于Secret同样建议通过GitOps管理其加密后的版本如使用Sealed Secrets或SOPS避免在YAML中暴露明文。7.3 版本升级与流量管理Knative的Revision模型天然支持蓝绿部署。每次更新Service的spec.template镜像、环境变量等都会创建一个新的Revision。你可以通过traffic字段精确控制流量分配。apiVersion: serving.knative.dev/v1 kind: Service metadata: name: my-service spec: traffic: - revisionName: my-service-00001 # 旧版本 percent: 90 # 90%流量 - revisionName: my-service-00002 # 新版本 percent: 10 # 10%流量做金丝雀测试 - latestRevision: true # 指向最新创建的Revision percent: 0 # 但暂时不给它流量方便快速回滚 template: # ... 新的配置定义这会创建revision my-service-00003关键点latestRevision: true是一个指针总是指向由当前spec.template创建的最新Revision。在上面的例子中提交后latestRevision会指向my-service-00003但因为它有percent: 0所以没有流量。如果金丝雀测试my-service-00002通过你可以将流量全部切到latestRevision或者将my-service-00002的流量比例提高到100%。如果测试失败你可以快速将流量100%切回my-service-00001。这种流量控制能力结合Istio的细粒度路由规则如基于Header的路由构成了非常强大的发布策略。7.4 资源限制与OOMKill云函数因为其“用完即走”的特性很容易被忽视资源限制limits的设置。如果内存限制limits.memory设置过低而函数在处理某个请求时内存暴涨就会导致Pod被Kubernetes的OOMKiller强制终止请求失败。更棘手的是由于Knative的快速弹性这个失败的Pod可能被迅速替换使得问题在日志中一闪而过难以捕捉。我的经验是务必设置合理的内存和CPU limits。可以通过压力测试观察应用在处理典型负载时的内存使用峰值并在此基础上增加至少30%的安全余量。设置requests等于或略低于limits。对于CPUrequests会影响调度和压缩等级对于内存requests是Pod启动的保证。在应用层面增加内存监控和优雅降级。例如在Go中监控runtime.MemStats在Java中监控堆内存使用当接近限制时主动返回一个友好的错误响应而不是被动地被OOMKill。仔细查看Pod状态和事件。如果Pod频繁重启使用kubectl describe pod pod-name查看Last State和EventsOOMKilled会在这里明确显示。从传统架构迁移到云原生下的云函数与云应用不是一个简单的技术替换而是一次开发运维思想的进化。它要求我们更细致地思考应用边界、更精确地管理资源、更主动地构建可观测体系。我自己的体会是初期在环境搭建和问题排查上会花费较多精力但一旦这套体系稳定运行它带来的研发效率提升、运维负担减轻和成本优化是非常显著的。尤其是面对不确定的业务流量时那种“系统会自动处理好一切”的安心感是传统架构难以给予的。如果你刚开始探索不妨从一个非核心的、事件驱动的场景如图片处理、消息通知入手逐步积累经验再向核心业务推进。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门