serverless knative实战
Posted 程序员石磊
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了serverless knative实战相关的知识,希望对你有一定的参考价值。
1、部署一个service应用
在部署第一个Knative Service之前,我们先了解一下它的部署模型和对应的Kubernetes资源。如图6-2所示,在部署Knative Serving Service的过程中,Knative Serving控制器将创建configuration、Revision和Route三个资源对象。
配置(configuration):Knative configuration维护了部署的目标状态,提供了一个干净的代码和配置分离、遵循12要素开发原则的机制。基于目标状态,Knative configuration控制器为应用创建了一个新的Kubernetes部署应用。并且configuration的变更会体现在一个新的Kubernetes部署应用中。
修订版(Revision):Knative configuration遵循12要素开发原则,每次应用的变更将会创建一个新的Knative Revision。Revision类似于版本控制中的标签。Revision一旦创建,是不可改变的。每个Revision都有一个对应的Kubernetes Deployment。它允许将应用程序回滚到任何正确的最新配置。
路由(Route):Knative Route是访问Knative Service的URL。
接下来分别以java和node为例创建一个简单的web服务。该服务接收到HTTP GET请求时,会根据环境变量Target传递的内容向Response输出Hello$TATGET!内容。
1.1、制作service镜像
java
- 创建一个spring boot项目
package com.example.helloworld;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
public class HelloworldApplication
@Value("$TARGET:World")
String target;
@RestController
class HelloworldController
@GetMapping("/")
String hello()
return "Hello " + target + "!";
public static void main(String[] args)
SpringApplication.run(HelloworldApplication.class, args);
启动访问
./mvnw package && java -jar target/helloworld-0.0.1-SNAPSHOT.jar
- 制作Dockerfile文件
# Use the official maven/Java 8 image to create a build artifact.
# https://hub.docker.com/_/maven
FROM maven:3.5-jdk-8-alpine as builder
# Copy local code to the container image.
WORKDIR /app
COPY pom.xml .
COPY src ./src
# Build a release artifact.
RUN mvn package -DskipTests
# Use AdoptOpenJDK for base image.
# It's important to use OpenJDK 8u191 or above that has container support enabled.
# https://hub.docker.com/r/adoptopenjdk/openjdk8
# https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds
FROM adoptopenjdk/openjdk8:jdk8u202-b08-alpine-slim
# Copy the jar to the production image from the builder stage.
COPY --from=builder /app/target/helloworld-*.jar /helloworld.jar
# Run the web service on container startup.
CMD ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "/helloworld.jar"]
- 制作镜像、并push到Docker registry
# Build the container on your local machine
docker build -t username/helloworld-java-spring .
# Push the container to docker registry
docker push username/helloworld-java-spring
nodejs
- 创建一个Node项目
npm init
package name: (helloworld-nodejs)
version: (1.0.0)
description:
entry point: (index.js)
test command:
git repository:
keywords:
author:
license: (ISC) Apache-2.0
- 安装express包
npm install express
- 创建一个主程序index.js
const express = require('express');
const app = express();
app.get('/', (req, res) =>
console.log('Hello world received a request.');
const target = process.env.TARGET || 'World';
res.send(`Hello $target!\\n`);
);
const port = process.env.PORT || 8080;
app.listen(port, () =>
console.log('Hello world listening on port', port);
);
- 配置package.json
"name": "knative-serving-helloworld",
"version": "1.0.0",
"description": "Simple hello world sample in Node",
"main": "index.js",
"scripts":
"start": "node index.js"
,
"author": "",
"license": "Apache-2.0",
"dependencies":
"express": "^4.16.4"
- 制作Dockerfile文件
# Use the official lightweight Node.js 12 image.
# https://hub.docker.com/_/node
FROM node:12-slim
# Create and change to the app directory.
WORKDIR /usr/src/app
# Copy application dependency manifests to the container image.
# A wildcard is used to ensure both package.json AND package-lock.json are copied.
# Copying this separately prevents re-running npm install on every code change.
COPY package*.json ./
# Install production dependencies.
RUN npm install --only=production
# Copy local code to the container image.
COPY . ./
# Run the web service on container startup.
CMD [ "npm", "start" ]
制作镜像和推送镜像和java环境一样。
注意应用程序和docker暴露端口必须是8080,否则会部署失败。新版可以在service的yml中指定端口号。
1.2 部署应用
可以采用二种方式部署应用:
- yaml
- kn
- yaml方式
创建service.yml,image指定为上一步制作的镜像,文件如下:
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: helloworld-java-spring
namespace: default
spec:
template:
spec:
containers:
- image: docker.io/username/helloworld-java-spring
env:
- name: TARGET
value: "Java Spring Sample v1"
利用kubectl部署
kubectl apply --filename service.yaml
- kn部署
kn service create helloworld-java-spring --image=docker.io/username/helloworld-java-spring --env TARGET="Java Spring Sample v1"
在创建服务期间,Knative 会执行以下步骤:
- 为此版本的应用程序创建一个新的不可变修订版。
- 为您的应用程序创建路由、入口、服务和负载平衡。
- 自动向上和向下扩展您的 pod,包括缩小到零活动 pod。
1.3 验证
可以采用二种方式验证:
- kubectl
- kn
- kubectl
kubectl get ksvc helloworld-java-spring --output=custom-columns=NAME:.metadata.name,URL:.status.url
显示
NAME URL
helloworld-java-spring http://helloworld-java-spring.default.1.2.3.4.sslip.io
- kn
kn service describe helloworld-java-spring -o url
显示
http://helloworld-java-spring.default.1.2.3.4.sslip.io
访问:
curl http://helloworld-java-spring.default.1.2.3.4.sslip.io
Hello Java Spring Sample v1!
# Even easier with kn:
curl $(kn service describe helloworld-java-spring -o url)
1.4 删除service
- kubectl方式
kubectl delete --filename service.yaml
- kn方式
kn service delete helloworld-java-spring
1.4 更新Knative Service configuration
在部署完一个Knative Service后,我们会因为应用版本的升级、配置的变更等需要更新现有服务的configuration。Knative服务还提供了一种机制实现回滚变更。
12要素应用设计原则规定,应用程序与配置的变更应被视为一个新的修订版。修订版是不可变更的应用和配置的状态集合。它可以让你回滚到任何一个有效的修订版状态。
应用的更新包括容器镜像的更新、健康检查探针的调整、环境变量的变更。这些变更会导致Knative生成新的修订版。每一个新修订版将创建一个新的Kubernetes Deployment对象。
接下来,我们通过一个更新服务配置的示例来演示配置的变更。
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: helloworld-go # Service名称
namespace: default
spec:
template:
metadata:
name: helloworld-go-v2 # Knative Revision名称
spec:
containers:
- image: cnlab/helloworld-go
env:
- name: TARGET
value: "Go Sample v2"
livenessProbe:
httpGet:
path: /
readinessProbe:
httpGet:
path: /
配置文件(service.yaml)的变更如下。
1)更新修订版的名称(.spec.template.metadata.name)为helloworld-go-v2,区别于上一个修订版名称helloworld-go-v1。
2)更新环境变量TARFET(.spec.template.spec.containers[0].env[0].value)的值为Go Sample v2。
将配置更新到Knative:
# kubectl apply-f service.yaml
检查部署结果:
# kubectl get ksvc helloworld-go
NAME URL LATESTCREATED LATESTREADY READY REASON
helloworld-go http://helloworld-go. helloworld-go-v2 helloworld-go-v2 True
default.example.com
通过curl命令访问helloworld-go服务:
##获取集群任一节点的IP地址和nodePort端口
# IP_ADDRESS="$(kubectl get nodes-o 'jsonpath=.items[0].status.addresses[0].
address'):$(kubectl get svc istio-ingressgateway--namespace istio-system
--output 'jsonpath=.spec.ports[?(@.port==80)].nodePort')"
# curl-H "Host:helloworld-go.default.example.com" http://$IP_ADDRESS
Hello Go Sample v2!
查看部署后生成的Kubernetes Deployment:
# kubectl get deployments
NAME READY UP-TO-DATE AVAILABLE AGE
helloworld-go-v1-deployment 0/0 0 0 2m52s
helloworld-go-v2-deployment 1/1 1 1 2m19s
查看部署后生成的Kubernetes Pod:
# kubectl get pods
NAME READY STATUS RESTARTS AGE
helloworld-go-v2-deployment-589c5f7ff9-czpj2 3/3 Running 0 12s
helloworld-go对应的部署如果在扩缩容时间窗口期(默认60s)内没有请求,Knative将自动将对应的部署缩容为零。
查看部署后生成的Revision:
# kubectl get revision
NAME CONFIG NAME K8S SERVICE NAME GENERATION READY REASON
helloworld-go-v1 helloworld-go helloworld-go-v1 1 True
helloworld-go-v2 helloworld-go helloworld-go-v2 2 True
我们可以看到helloworld-go的配置有两个修订版,分别是helloworld-go-v1和helloworld-go-v2。配置的变更产生了新的修订版,然而并没有产生新的路由、服务和配置对象。我们可以通过下面的命令来验证这些资源对象的状态。
- 获取服务的路由信息的命令:kubectl get routes。
- 获取Knative服务的状态信息的命令:kubectl get ksvc。
- 获取Knative服务的配置信息的命令:kubectl get configurations。
- Knative默认路由策略是将所有流量转发给最新的修订版。
1.5 流量分发到不同版本
在典型的微服务部署中,实现流量在不同版本中分发是实现金丝雀或蓝绿部署方式的基础。Knative提供了这种流量分发方式的支持。
在Knative Service的yaml文件配置中,traffic区块描述了如何在多个版本之间进行流量分发。配置范例如下:
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: helloworld-go # Service名称
namespace: default
spec:
template:
metadata:
name: helloworld-go-v2 # Knative Revision名称
spec:
containers:
- image: cnlab/helloworld-go
env:
- name: TARGET
value: "Go Sample v2"
livenessProbe:
httpGet:
path: /
readinessProbe:
httpGet:
path: /
traffic:
- tag: v1
revisionName: helloworld-go-v1 # Revision的名称
percent: 50 #流量切分的百分比的数字值
- tag: v2
revisionName: helloworld-go-v2 # Revision的名称
percent: 50 #流量切分的百分比的数字值
traffic区块中可以有一个或多个条目。每个条目中带有如下属性。
- tag:流量分配的标识符。此标记将在路由中充当前缀,以便将流量分发到特定修订版。
- revisionName:参与流量分配的Knative服务修订版本的名称。
- percent:对应修订版被分配的流量百分比。这个值在0~100之间。在上述例子中,Knative分配给修订版helloworld-go-v1和helloworld-go-v2各50%的流量。
- Knative Serving会为每个Tag创建独特的URL。我们可以通过下面的命令查看:
# kubectl get ksvc helloworld-go-o jsonpath='.status.traffic[*].url'
http://v1-helloworld-go.default.example.com
http://v2-helloworld-go.default.example.com
通过访问URL可以直接访问到对应的修订版。
1.6 蓝绿部署与灰度发布
一般情况下,升级服务端应用需要先停掉老版本服务,再启动新版服务。但是这种简单的发布方式存在两个问题,一方面在新版本升级过程中,服务是暂时中断的;另一方面,如果新版本升级失败,回滚起来非常麻烦,容易造成更长时间的服务不可用。
- 蓝绿部署
所谓蓝绿部署,是指同时运行两个版本的应用,即部署的时候,并不停掉老版本,而是直接部署一套新版本,等新版本运行起来后,再将流量切换到新版本上,如图6-3所示。但是蓝绿部署要求服务端在升级过程中同时运行两套程序,对硬件资源的要求是日常所需的2倍。
Knative提供了一个简单的切换流量的方法,可将流量快速从Revison1切换到Revision 2。如果Revision2发生错误,服务可以快速回滚变更到Revison1。
接下来,我们将通过helloworld-go这个Knative服务来应用蓝绿色部署模式。上文拥有两个修订版的helloworld-go服务,名称分别为helloworld-go-v1和helloworld-go-v2。通过部署helloworld-go-v2,我们可以看到Knative自动将100%的流量路由到helloworld-go-v2。现在,假设出于某些原因,我们需要将helloworld-go-v2回滚到helloworld-go-v1。
以下示例中Knative Service与先前部署的helloworld-go相同,只是添加了traffic部分以指示将100%的流量路由到helloworld-go-v1。
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: helloworld-go # Service名称
namespace: default
spec:
template:
metadata:
name: helloworld-go-v2 # Knative Revision名称
spec:
containers:
- image: cnlab/helloworld-go
env:
- name: TARGET
value: "Go Sample v2"
livenessProbe:
httpGet:
path: /
readinessProbe:
httpGet:
path: /
traffic:
- tag: v1
revisionName: helloworld-go-v1 # Revision的名称
percent: 100 # 流量切分的百分比值
- tag: v2
revisionName: helloworld-go-v2 # Revision的名称
percent: 0 # 流量切分的百分比值
- tag: latest # 默认最新的Revision
latestRevision: true
percent: 0 # 关闭默认流量分配
- 灰度发布
灰度发布也叫金丝雀发布。如图所示,在灰度发布开始后,先启动一个新版本应用,但是并不直接将流量切过来,而是测试人员对新版本进行线上测试。启动的这个新版本应用,就是我们的金丝雀。如果测试没有问题,我们可以将少量的流量导入新版本,然后再对新版本做运行状态观察,收集各种运行时数据。如果此时对新旧版本做数据对比,就是所谓的A/B测试。
![image.png](https://img-blog.csdnimg.cn/img_convert/4f96a6f8b50731b535ead53b35c85cc0.png#clientId=u99c80860-5cc0-4&from=paste&height=159&id=u5901a3a8&margin=[object Object]&name=image.png&originHeight=318&originWidth=615&originalType=binary&ratio=1&size=27910&status=done&style=none&taskId=ua8d2f25c-211c-477f-a2fc-6fa3fbc417a&width=307.5)
当确认新版本运行良好后,再逐步将更多的流量导入新版本。在此期间,我们还可以不断地调整新旧两个版本运行的服务器副本数量,使得新版本能够承受更大的流量压力,直到将100%的流量切换到新版本上,最后关闭剩下的老版本服务,完成灰度发布。
如果我们在灰度发布过程中(灰度期)发现新版本有问题,应该立即将流量切回老版本,这样就会将负面影响控制在最小范围内。
以下示例通过不断变更helloworld-go-v1和helloworld-go-v2的流量比例来实现helloworld-go服务新版本的灰度发布。
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: helloworld-go # Service名称
namespace: default
spec:
template:
metadata:
name: helloworld-go-v2 # Knative Revision名称
spec:
containers:
- image: cnlab/helloworld-go
env:
- name: TARGET
value: "Go Sample v2"
livenessProbe:
httpGet:
path: /
readinessProbe:
httpGet:
path: /
traffic:
- tag: v1
revisionName: helloworld-go-v1 # Revision的名称
percent: 80 # 流量切分的百分比值
- tag: v2
revisionName: helloworld-go-v2 # Revision的名称
percent: 20 # 流量切分的百分比值
- tag: latest # 默认最新的Revision
latestRevision: true
percent: 0 # 关闭默认流量分配
使用 Knative CLI 路由和管理流量
1.5和1.6的操作也可以通过cli命令设置。
kn service update <service-name> --traffic <revision-name>=<percent>
是您为其配置流量路由的 Knative 服务的名称。
是要配置为接收一定百分比的流量的修订名称。
是要发送到 指定的修订版的流量百分比。
例如,要拆分名为 example 的 Service 的流量,将 80% 的流量发送到 Revision 绿色,将 20% 的流量发送到 Revision blue,您可以运行以下命令:
kn service update example-service --traffic green=80 --traffic blue=20
也可以将标签添加到修订版,然后根据您设置的标签拆分流量:
kn service update example --tag green=revision-0001 --tag blue=@latest
@latest 标签表示蓝色解析为服务的最新版本。 以下示例将 80% 的流量发送到最新版本,将 20% 的流量发送到名为 v1 的版本。
kn service update example-service --traffic @latest=80 --traffic v1=20
1.7 Knative Service的弹性伸缩配置
无服务器计算不仅能够终止未使用的服务,还可以按需扩展计算规模。Knative Serving支持这种弹性伸缩能力。
为了让Knative的Autoscaler更好地调度服务,我们需要根据实际情况在服务中添加相应的扩缩容配置项。下面以helloworld-go.yaml范例来演示扩缩容相关配置。
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: helloworld-go # Service名称
namespace: default
spec:
template:
metadata:
annotations:
autoscaling.knative.dev/class: "kpa.autoscaling.knative.dev"
# Autoscaler的实现方式,可选值有"kpa.autoscaling.knative.dev" 或
"hpa.autoscaling.knative.dev"
autoscaling.knative.dev/metric: "concurrency" # 设置度量指标为Concurrency
(默认值),还可以根据业务情
况选择RPS或CPU
autoscaling.knative.dev/target: "10" # 设置单个Pod最大并发数为10,默认值为100
autoscaling.knative.dev/minScale: "1" # minScale表示最小保留实例数为1
autoscaling.knative.dev/maxScale: "100" # maxScale表示最大扩容实例数为3
spec:
containerConcurrency: 10 # 并发请求数的硬性限制
containers:
- image: cnlab/helloworld-go
在上述配置中,revision中配置了修订版的弹性伸缩策略。各个属性代表的意义如下。
- autoscaling.knative.dev/class:表示Autoscaler的实现方式,这个属性的可选值有kpa.autoscaling.knative.dev或hpa.autoscaling.knative.dev。KPA支持缩容到零,HPA支持基于CPU的扩展机制。
- autoscaling.knative.dev/metric:度量指标默认为并发数,该属性还可以根据业务情况选择每秒请求数或CPU使用率。
- autoscaling.knative.dev/target:自动缩放的目标值是Autoscaler维护应用的每个副本度量指标的目标值。
- autoscaling.knative.dev/minScale:表示每个修订版副本需要保留的最小数量。在任何时间点,副本不会少于这个数量。通过该设置,我们可以有效地减少服务的冷启动时间。
- autoscaling.knative.dev/maxScale:表示每个修订版副本所能达到的最大数量。在任何时间点,副本都不会超过指定的最大值,从而避免资源被过度使用。
- containerConcurrency:限制容器在给定时间允许的并发请求的数量的硬性限制。只有当应用程序需要强制的并发约束时,才会使用到该属性。
部署helloworld-go服务并配置到Knative集群:
# kubectl apply-f helloworld-go.yaml
验证部署结果:
#IP_ADDRESS="$(kubectl get nodes-o 'jsonpath=.items[0].status.addresses[0].
address'):$(kubectl get svc istio-ingressgateway--namespace istio-system--
output 'jsonpath=.spec.ports[?(@.port==80)].nodePort')"
# curl-H "Host:helloworld-go.default.example.com" $IP_ADDRESS
Hello World!
压力测试:
# hey-c 50-z 30s-host "helloworld-go.default.knative.k8s.arch.dapp.com"
"http://$IP_ADDRESS"
通过hey工具发起50个并发请求,持续30秒对hellowrold-go服务进行压测。
查看压测期间Pod的副本数量:
# kubectl get pod -l serving.knative.dev/service=helloworld-go
NAME READY STATUS RESTARTS AGE
helloworld-go-7t7sg-deployment-6bfbdb84fd-5l5gc 3/3 Running 0 42s
helloworld-go-7t7sg-deployment-6bfbdb84fd-99cdr 3/3 Running 0 42s
helloworld-go-7t7sg-deployment-6bfbdb84fd-ls4ks 3/3 Running 0 44s
helloworld-go-7t7sg-deployment-6bfbdb84fd-n4s4k 3/3 Running 0 44s
helloworld-go-7t7sg-deployment-6bfbdb84fd-q9kr8 3/3 Running 0 40s
helloworld-go-7t7sg-deployment-6bfbdb84fd-r77tt 3/3 Running 0 22m
通过上面的命令,我们可以看到集群中产生了6个Pod副本。那么问题来了,我们发起的并发请求数是50个,服务自动缩放的目标值是10,按照“副本数=并发数/目标值”算法,Pod副本数应该是5个才对呀。这是由于Knative Serving还有一个控制参数叫目标使用率,一旦并发数达到预设目标的70%(默认值),Autoscaler就会继续扩容。引入目标使用率的主要目的是在扩容时减小由Pod启动时间带来的延迟,使负载到达前就将Pod实例启动起来。
引用
Hello world apps - Python - 《Knative v0.23 Documentation》 - 书栈网 · BookStack
Knative实战:基于Kubernetes的无服务器架构实践 by 李志伟 游杨 (z-lib.org)
serverless knative实战serverless-knative serving安装实战
serverless-knative serving安装实战