在istion中,虚拟服务(Virtual service)和目标规则(destination rule)是流量路由功能的关键组成部分。在Istio所提供的基本连接和发现基础上,通过虚拟服务,能够将请求路由到Istio网格中的特定服务。每个虚拟服务由一组路由规则组成,这些路由规则使Istio能够将虚拟服务的每个给定请求匹配到网格内特定的目标地址。
虚拟服务(VirtualService)定义了一组寻址主机时要使用的流量路由规则,每个路由规则为特定协议的流量定义匹配了条件。如果流量匹配,则将其发送到注册表中定义的命名目标服务(或其子集/版本)。同时流量来源也可以在路由规则中匹配,从而能够允许针对特定的客户端上下文自定义路由。下面是虚拟服务的一些关键内容:
- 服务(Service):应用绑定到服务注册表中的唯一名称。服务包含多个网络端点,这些端点由在Pod、容器和VM等上运行的工作负载实例进行实现。
- 服务版本(Service versions)或子集:在持续部署的场景中,对于给定的服务,可能存在不同的应用程序实例。它们可能是对同一服务的迭代更改,这些版本部署在不同的环境(产品,阶段和开发等)中。场景包括A / B测试和金丝雀发布等。可以根据各种标准(标头,网址等)和/或通过分配给每个版本的权重来确定特定版本的选择。每个服务都有一个包含其所有实例的默认版本。
- 来源(Source):调用服务的下游客户端。
- 主机(Host):下游客户端连接服务时所使用的地址。
- 访问模式(Access model):应用程序仅需要处理目标服务(主机),而无需了解各个服务版本(子集)。版本的实际选择由代理/sidecar决定,从而使应用程序代码能够与所依赖服务的脱钩。
在Kubernetes中,默认情况下以下示例将所有HTTP流量路由到带有标签“version:v1”的reviews服务。另外,以/ wpcatalog /或/ consumercatalog /开头的HTTP请求将被重写为/ newcatalog,并发送到标签为“ version:v2”的reviews服务。
apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: reviews-route spec: #客户端访问服务的地址 hosts: - reviews.prod.svc.cluster.local http: #如果请求的前缀为/wpcatalog、/consumercatalog,流量路由到v2版本的reviews中。 - name: "reviews-v2-routes" match: - uri: prefix: "/wpcatalog" - uri: prefix: "/consumercatalog" rewrite: uri: "/newcatalog" route: - destination: host: reviews.prod.svc.cluster.local # 匹配的服务版本或子集为v2 subset: v2 #其它请求,流量路由到v1版本的reviews中。 - name: "reviews-v1-route" route: - destination: host: reviews.prod.svc.cluster.local subset: v1
路由目标地址的子集/版本是通过引用命名服务子集来进行标识的,该子集/版本必须在相应的目标规则DestinationRule中进行声明 。
apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: reviews-destination spec: host: reviews.prod.svc.cluster.local # 定义reviews服务的子集或版本,对于Kuberntes,即为Pod打上标签 subsets: - name: v1 labels: version: v1 - name: v2 labels: version: v2
在定义虚拟服务时,需要设置的字段如下表所示。
表 虚拟服务字段
领域 | 类型 | 描述 | 必需 |
hosts | string[] | 接收请求流量的目标主机,这个主机可以是带有通配符前缀的DNS名称或IP地址。VirtualService可用于描述相应主机的所有流量属性,包括多个HTTP和TCP端口的流量属性。或者,可以使用多个VirtualService定义主机的流量属性,但要注意以下几点。
l Kubernetes用户的注意事项:当使用短名称(例如,使用“ reviews”而不是“ reviews.default.svc.cluster.local”)时,Istio将根据命名空间而不是服务来解释短名称。为避免潜在的错误配置,建议始终使用完全限定的域名代替短名称。 l hosts字段适用于HTTP和TCP服务。 l IP地址仅允许通过网关定义的服务使用。 |
是 |
gateways | string[] | 应用到路由的网关和sidecar名称。VirtualService用于网格内的sidecar以及一个或多个网关。在特定于协议的路由的匹配条件中,可以使用source字段来覆盖此字段施加的选择条件。
保留字mesh用于表示网格中的所有sidecar。省略此字段时,将使用默认网关(mesh),它将默认规则应用于网格中的所有sidecar。如果提供了网关名称列表,则规则将仅适用于网关。如果要将规则应用于网关和sidecar,请将其指定mesh为网关名称之一。 |
没有 |
http | HTTPRoute[] | HTTP流量路由规则的有序列表。 | 没有 |
tls | TLSRoute[] | 未终止的TLS和HTTPS流量路由规则的有序列表。 | 没有 |
tcp | TCPRoute[] | TCP流量路由规则的有序列表。 | 没有 |
exportTo | string[] | 虚拟服务所要暴露到的命名空间列表。被暴露的虚拟服务允许被其他命名空间中定义的sidecar和网关所使用。通过此功能,可以控制虚拟服务跨名称空间边界的可见性。
l 如果未指定命名空间,则默认情况下会将虚拟服务暴露到所有的命名空间。 l “.” 值被保留,定义虚拟服务仅在当前命名空间被使用。 l “ *” 值被保留,定义了虚拟服务可以在所有的命名空间使用。 l 注意:在当前版本中,该exportTo值限制为“.”或“ *”(即当前命名空间或所有命名空间)。 |
没有 |
1. HTTP路由
用于HTTP / 1.1,HTTP2和gRPC通信协议流量路由,设置路由的匹配条件和后续行为,这些行为包括重定向、重试、重新、超时、故障处理和镜像等。
领域 | 类型 | 描述 | 必需 |
name | string | http路由的名称。路由名称将与匹配名称串联在一起,并将被记录在访问日志中,以查找与此路由匹配的请求。 | 没有 |
match | HTTPMatchRequest[] | 设置匹配条件,以为流量选择合适路由。在单个匹配块内,所有条件都取与,而匹配块间都取或。如果任何匹配块匹配成功,则路由使用此规则。 | 没有 |
route | HTTPRouteDestination[] | http规则可以重定向或转发(默认)流量。转发目标可以是服务的多个版本之一。与服务版本关联的权重决定了它接收的流量比例。 | 没有 |
redirect | HTTPRedirect | http规则可以重定向或转发(默认)流量。如果在规则中指定了流量的通过选项,则会忽略路由/重定向。重定向可用于将HTTP 301重定向发送到其他URI或Authority。 | 没有 |
rewrite | HTTPRewrite | 重写HTTP URI和Authority标头。重写不能与重定向一起使用。重写将在转发之前执行。 | 没有 |
timeout | Duration | HTTP请求超时。 | 没有 |
retries | HTTPRetry | HTTP请求重试策略。 | 没有 |
fault | HTTPFaultInjection | 故障注入策略,适用于客户端的HTTP通信。请注意,如果在客户端启用了错误,则不会启用超时或重试。 | 没有 |
mirror | Destination | 除了能够将请求转发到预期目标之外,还可以将HTTP流量镜像到另一个目标。 | 没有 |
mirrorPercent | UInt32Value | mirror字段反映的流量百分比。如果不存在此字段,则将镜像所有流量(100%)。最大值为100。 | 没有 |
corsPolicy | CorsPolicy | 跨域资源共享策略(CORS)。 | 没有 |
headers | Headers | 标头操作规则 |
1.1. http故障注入
配置完网络(包括故障恢复策略)后,可以使用Istio的故障注入机制来测试整个应用程序的故障恢复能力。故障注入是一种将错误引入系统的测试方法,以确保它可以承受并从错误情况中恢复。与其他在网络层引入错误(例如延迟数据包或杀死Pod)的机制不同,Istio允许在应用程序层注入故障。Istio可以注入两种类型的故障,这两种故障都是使用虚拟服务配置的 :
- 延迟:延迟是计时失败。它们模仿增加的网络延迟或上游服务过载。
- 中止:中止是崩溃失败。它们模仿上游服务中的故障。中止通常以HTTP错误代码或TCP连接失败的形式表现出来。
例如,下面的虚拟服务为ratings服务的每1000个请求中的1个引入5秒的延迟。
在将指定的HTTP请求转发到路由时,HTTPFaultInjection可以注入的一个或多个故障。这些故障包括中止来自下游服务的Http请求和/或延迟请求的代理。故障规则必须是延迟或中止,或两者同时进行。
领域 | 类型 | 描述 | 需要 |
delay | Delay | 在转发之前延迟请求,模拟各种故障,例如网络问题,上游服务过载等。 | 没有 |
abort | Abort | 中止Http请求并尝试将错误代码返回给下游服务,给人的印象是上游服务有故障。 | 没有 |
1.1.1 中止故障
中止故障用于提前中止带有预先指定的错误代码的请求。在以下示例中,“ratings:v1”服务的每1000个请求将会返回1个HTTP 400错误代码。
httpStatus字段是返回给调用者的HTTP状态代码。可选的百分比字段可用于仅中止一定百分比的请求。如果未指定,则所有请求都将中止。
领域 | 类型 | 描述 | 需要 |
httpStatus | int32 (oneof) | 用于中止Http请求的HTTP状态代码。 | 是 |
percentage | Percent | 基于提供的错误代码,所中止请求的百分比。 | 没有 |
percent | int32 | 基于错误代码(0-100),所中止请求的百分比。 | 没有 |
1.1.2 延迟故障
延迟故障用于将延迟注入到请求的转发路由中。在下面的示例中,带有标签”env: prod”的Pod请求“reviews: v1”服务时,在每1000个请求中,有1个请求引入5秒的延迟:
fixeddelay字段用于设置延迟量。可选的百分比字段可用于仅延迟特定百分比的请求。如果未指定,所有请求将被延迟。
领域 | 类型 | 描述 | 需要 |
fixedDelay | Duration (oneof) | 在转发请求之前,设置的延迟。格式为:1h / 1m / 1s / 1ms。必须> = 1ms。 | 是 |
percentage | Percent | 基于提供的错误代码,所中止请求的百分比。 | 没有 |
percent | int32 | 基于提供的错误代码,所中止请求的百分比。 | 没有 |
1.2 Cors政策
描述给定服务的跨域资源共享(CORS)策略。 在下面的示例中,将来自example.com域的跨源请求限制为使用HTTP POST / GET请求,并将Access-Control-Allow-Credentials标头设置 为false。此外,它仅公开X-Foo-bar标题,并将有效期设置为1天。
领域 | 类型 | 描述 | 需要 |
allowOrigin | string[] | 允许执行CORS请求的来源列表。内容将被序列化到Access-Control-Allow-Origin标头中。通配符*将允许所有来源。 | 没有 |
allowMethods | string[] | 允许访问资源的HTTP方法列表。内容将被序列化到Access-Control-Allow-Methods标头中。 | 没有 |
allowHeaders | string[] | 请求资源时可以使用的HTTP标头列表。序列化为Access-Control-Allow-Headers头。 | 没有 |
exposeHeaders | string[] | 允许浏览器访问的HTTP标头的白名单。序列化为Access-Control-Expose-Headers头。 | 没有 |
maxAge | Duration | 指定可以将预检请求的结果缓存多长时间。转换为Access-Control-Max-Age标题。 | 没有 |
allowCredentials | BoolValue | 指示是否允许调用者使用凭据发送实际请求(而不是预检)。转换为 Access-Control-Allow-Credentials标题。 |
1.3 http匹配
HttpMatchRequest用于指定一组要需要满足的条件,以便将该规则应用于HTTP请求。例如,在以下的例子中,将规则限制为仅匹配URL路径以/ ratings / v2 /开头,且请求包含值为 jason 的end-user自定义标头。
HTTPMatchRequest不能为空。
领域 | 类型 | 描述 | 需要 |
name | string | 匹配项的名称。匹配的名称将与父路由的名称串联在一起,并将记录在访问日志中,以查找与此路由匹配的请求。 | 没有 |
uri | StringMatch | 匹配值的URI区分大小写,其格式如下:
l exact: “value” 精确匹配字符串 l prefix: “value” 用于基于前缀的匹配 l regex: “value” 用于ECMAscript样式基于正则表达式的匹配 注意:不区分大小写的匹配可以通过该ignore_uri_case标志启用 。 |
没有 |
scheme | StringMatch | URI方案值区分大小写,其格式如下:
l exact: “value” 精确匹配字符串 l prefix: “value” 用于基于前缀的匹配 l regex: “value” 用于ECMAscript样式基于正则表达式的匹配 |
没有 |
method | StringMatch | HTTP方法值区分大小写,其格式如下:
l exact: “value” 精确匹配字符串 l prefix: “value” 用于基于前缀的匹配 l regex: “value” 用于ECMAscript样式基于正则表达式的匹配 |
没有 |
authority | StringMatch | HTTP Authority值区分大小写,其格式如下:
l exact: “value” 精确匹配字符串 l prefix: “value” 用于基于前缀的匹配 l regex: “value” 用于ECMAscript样式基于正则表达式的匹配 |
没有 |
headers | map<string, StringMatch> | 标头键必须为小写并使用连字符作为分隔符,例如x-request-id。
标头值区分大小写,其格式如下: l exact: “value” 精确匹配字符串 l prefix: “value” 用于基于前缀的匹配 l regex: “value” 用于ECMAscript样式基于正则表达式的匹配 注:该键uri,scheme,method,和authority将被忽略。 |
没有 |
port | uint32 | 指定要寻址的主机上的端口。许多服务仅使用支持的协议公开单个端口或标签端口,在这些情况下,可以不需要显式选择端口。 | 没有 |
sourceLabels | map<string, string> | 一个或多个使用给定标签将规则对工作负载的适用性约束的标签。如果VirtualService在顶部指定了网关列表,则它必须包括保留的网关 mesh,此字段才适用。 | 没有 |
queryParams | map<string, StringMatch> | 查询参数以进行匹配。
例如:-对于“?key = true”之类的查询参数,映射键将为“ key”,字符串匹配项可定义为exact: “true”。-对于“?key”之类的查询参数,映射键将为“ key”,字符串匹配项可定义为exact: “”。-对于“?key = 123”之类的查询参数,映射键将为“ key”,字符串匹配项可定义为regex: “\d+$”。请注意,此配置将仅匹配“ 123”之类的值,而不匹配“ a123”或“ 123a”之类的值。 注意: prefix目前不支持匹配。 |
没有 |
ignoreUriCase | bool | 用于指定URI匹配是否不区分大小写的标志。
注意:仅当exact和prefix URI匹配时,才会忽略大小写。 |
没有 |
1.4 http重定向
HTTPRedirect用于向调用方发送301重定向响应,在此响应中的Authority/Host和URI可与指定的值交换。例如,在下面的例子中,对“/ v1 / getProductRatings”请求将会被重定向到“/ v1 / bookRatings”。
领域 | 类型 | 描述 | 需要 |
uri | string | 在重定向上,使用此值覆盖URL的“路径”部分。请注意,无论将请求URI匹配为确切路径还是前缀,都将替换整个路径。 | 没有 |
authority | string | 在重定向上,使用此值覆盖URL的Authority / Host部分。 | 没有 |
redirectCode | uint32 | 在重定向上,指定要在重定向响应中使用的HTTP状态代码。默认响应代码为MOVED_PERMANENTLY(301)。 |
1.5 http超时
超时是Envoy代理应等待来自给定服务的答复的时间,以确保服务不会无限期地等待答复,并确保呼叫在可预测的时间内成功或失败。HTTP请求的默认超时为15秒,这意味着如果服务在15秒内未响应,则调用将失败。
对于某些应用程序和服务,Istio的默认超时可能不合适。例如,超时时间过长可能导致等待失败服务的回复导致过多的延迟,而超时时间过短可能导致在等待涉及多个服务的操作返回时呼叫不必要地失败。为了查找和使用最佳超时设置,Istio可以使用虚拟服务轻松地按服务需求动态的调整超时,而无需编辑服务代码。下面的虚拟服务,它为评级服务的v1子集的调用指定了10秒的超时时间:
1.6 HTTP重试
HTTP请求失败时,所使用的重试策略。例如,在下面的示例中,将调用“Ratings:v1”服务的最大重试次数设置为3,每次重试尝试超时为2
类型 | 描述 | 需要 | |
attempts | int32 | 给定请求的重试次数。重试之间的时间间隔将自动确定(25毫秒以上)。尝试重试的实际次数取决于httpReqTimeout。 | 是 |
perTryTimeout | Duration | 给定请求的每次重试尝试超时。格式:1h / 1m / 1s / 1ms。必须> = 1ms。 | 没有 |
retryOn | string | 指定发生重试的条件。可以使用“,”分隔列表指定一个或多个策略。 | 没有 |
1.1.1.7 HTTP重写
在将请求转发到目标之前,HTTPRewrite可以重写HTTP请求的特定部分,重写只能与HTTPRouteDestination一起使用。以下示例展示了在进行实际的API调用之前,如何将api调用(/ ratings)的URL前缀重写为Ratings服务。
领域 | 类型 | 描述 | 需要 |
uri | string | 使用此值重写URI的路径(或前缀)部分。如果原始URI基于前缀匹配,则此字段中提供的值将替换相应的匹配前缀。 | 没有 |
authority | string | 用此值重写Authority / Host标头。 |
1.8 HTTP路由目标
每个路由规则都会与一个或多个服务的版本相关联,与版本关联的权重决定了它接收的流量比例。例如,在下面的示例中,将“reviews”服务的25%流量路由路由到“ v2”版本的实例中,其余流量(即75%)路由到“ v1”版本的实例中。
相关目标规则如下所示:
流量也可以分为两个完全不同的服务,而不必定义新的子集。例如,在下面的示例中,将25%的流量转发到reviews.com,75%的流量路由到dev.reviews.com。
类型 | 描述 | 需要 | |
destination | Destination | 目标的唯一标识,请求/连接应转发到的服务实例。 | 是 |
weight | int32 | 要转发到服务版本的流量比例,值为(0-100)。各个目标之间的权重之和应为100。如果规则中只有一个目标,则权重值将假定为100。 | 没有 |
headers | Headers | 标头操作规则 | 没有 |
1.9 http标头
当Envoy将请求转发到目标服务或来自目标服务的响应时,可以操纵消息头。可以为特定的路由目标或所有目标指定标头操作规则。在下面的示例中,VirtualService将值为ture的test标头添加到请求中,此请求将被路由到任何reviews服务目标。它还将移除来自服务reviews:v1子集(版本)的响应foo响应标头。
需要 | |||
request | HeaderOperations | 在将请求转发到目标服务之前,要应用的标头操作规则 | 没有 |
response | HeaderOperations | 在将响应返回给调用者之前,应用的头操作规则 | 没有 |
HeaderOperations描述要应用的标头操作。
领域 | 类型 | 描述 | 需要 |
set | map<string, string> | 用给定的值覆盖key指定的标头 | 没有 |
add | map<string, string> | 将给定值附加到键指定的标头(将创建逗号分隔的值列表) | 没有 |
remove | string[] | 删除指定的标题 | 没有 |
2 TCP路由
描述用于路由TCP流量的匹配条件和操作。在下面的示例中,路由规则将到达端口27017的mongo.prod.svc.cluster.local的流量转发到端口5555上的另一台mongo服务器。
领域 | 类型 | 描述 | 需要 |
match | L4MatchAttributes[] | 匹配要激活的规则要满足的条件。单个匹配块内的所有条件都具有AND语义,而匹配块列表具有OR语义。如果任何匹配块成功,则匹配该规则。 | 没有 |
route | RouteDestination[] | 连接应转发到的目标。 |
2.1 路由目标
L4路由规则加权目标。
领域 | 类型 | 描述 | 需要 |
destination | Destination | 目的地唯一地标识请求/连接应转发到的服务实例。 | 是 |
weight | int32 | 要转发到服务版本的流量比例。如果规则中只有一个目的地,则所有流量都将路由到该目的地,而与权重无关。 | 没有 |
2.2 L4MatchAttributes
L4连接匹配属性。请注意,L4连接匹配支持不完整。
领域 | 类型 | 描述 | 需要 |
destinationSubnets | string[] | 带有可选子网的目标的IPv4或IPv6 ip地址。例如,abcd / xx形式或仅abcd | 没有 |
port | uint32 | 指定要寻址的主机上的端口。许多服务仅使用支持的协议公开单个端口或标签端口,在这些情况下,不需要显式选择端口。 | 没有 |
sourceLabels | map<string, string> | 一个或多个使用给定标签将规则对工作负载的适用性约束的标签。如果VirtualService在顶部指定了网关列表,则它应包括保留的网关 mesh,以便此字段适用。 | 没有 |
gateways | string[] | 应将规则应用到的网关的名称。VirtualService顶部的网关名称(如果有)被覆盖。网关匹配独立于sourceLabels。 | 没有 |
3 TLS路由
描述用于路由未终止TLS流量(TLS / HTTPS)的匹配条件和操作,下面的示例中,路由规则根据SNI值将到达网关“ mygateway” 443端口的未终止TLS流量转发到网格中的内部服务。
领域 | 类型 | 描述 | 需要 |
match | TLSMatchAttributes[] | 匹配满足的条件要激活的规则。单个匹配块内的所有条件都具有AND语义,而匹配块列表具有OR语义。如果任何匹配块成功,则匹配该规则。 | 是 |
route | RouteDestination[] | 连接应转发到的目的地。 | 没有 |
TLS连接匹配属性。
领域 | 类型 | 描述 | 需要 |
sniHosts | string[] | SNI(服务器名称指示器)进行匹配。可以在SNI值中使用通配符前缀,例如* .com将匹配foo.example.com和example.com。SNI值必须是相应虚拟服务器主机的子集(即属于该域)。 | 是 |
destinationSubnets | string[] | 带有可选子网的目标的IPv4或IPv6 ip地址。例如,abcd / xx形式或仅abcd | 否 |
port | uint32 | 指定要寻址的主机上的端口。许多服务仅使用支持的协议公开单个端口或标签端口,在这些情况下,不需要显式选择端口。 | 没有 |
sourceLabels | map<string, string> | 一个或多个使用给定标签将规则对工作负载的适用性约束的标签。如果VirtualService在顶部指定了网关列表,则它应包括保留的网关 mesh,以便此字段适用。 | 否 |
gateways | string[] | 应将规则应用到的网关的名称。VirtualService顶部的网关名称(如果有)被覆盖。网关匹配独立于sourceLabels。 | 否 |
4 虚拟服务示例
在下面的YAML配置文件,虚拟服务根据特定用户,将请求路由到服务的不同版本。如果用户为zhangsan,则将请求流量路由到reviews:v2;如果请求来自于其他用户,则将请求流量路由到reviews:v3。
4.1 hosts字段
spec.hosts字段用于列出虚拟服务主机,这些路由规则适用于用户可寻址的目标,这是客户端向服务发送请求时使用的地址。
hosts: - reviews
虚拟服务主机名可以是IP地址或DNS名称,也可以是特定平台使用的短名称(例如Kubernetes服务短名称),该短名称隐式或显式地解析为完全限定的域名(FQDN)。还可以使用通配符(“ *”)前缀,从而为所有匹配的服务创建一套路由规则。虚拟服务主机实际上不一定是Istio服务注册表的一部分,它们只是虚拟目的地。
4.2 路由规则
spec.http部分包含虚拟服务的路由规则,描述了路由发送到主机字段中指定的目标的HTTP / 1.1,HTTP2和gRPC流量的匹配条件和操作(也可以使用tcp和 tls部分来配置TCP的路由规则 和未终止的 TLS 流量)。路由规则由希望流量流向的目的地以及零个或多个匹配条件组成,具体取决于用例。
- 匹配条件
在示例中,第一个路由规则具有条件,因此从该match字段开始 。在这种情况下,来自于“jason”用户的所有请求将使用这个路由。
- match: - headers: end-user: exact: jason
- 目标
spec.route部分的destination字段指定符合此条件流量的实际目的地。与虚拟服务的主机不同,目的地的主机必须是Istio的服务注册表中存在的真实目的地,否则Envoy将不知道将流量发送到哪里。这可以是带有代理的网格服务,也可以是使用服务条目添加的非网格服务。如果在Kubernetes上运行,主机名是Kubernetes服务名:
route: - destination:
# 主机名必须是Istio服务注册表中的目的地,如果在Kubernetes上运行,主机名为服务名
host: reviews subset: v2
4.3 路由规则优先级
路由规则是从上到下按顺序评估,其中虚拟服务定义中的第一个规则具有最高优先级。在这种情况下,不符合第一个路由规则的所有内容都转到第二个规则中指定的默认目标。因此,第二条规则没有匹配条件,仅将流量定向到v3子集。
- route: - destination: host: reviews subset: v3
建议在每个虚拟服务中都提供默认的“无条件”或基于权重的规则作为每个虚拟服务中的最后一个规则,以确保到虚拟服务的流量始终具有至少一条匹配的路由。
作者简介:
季向远,北京神舟航天软件技术有限公司。本文版权归原作者所有。
登录后评论
立即登录 注册