配置 Cloud Service Mesh 用户身份验证
Cloud Service Mesh 用户身份验证是一种集成式解决方案,用于基于浏览器的最终用户身份验证和对已部署工作负载的访问权限控制。该解决方案可让您与现有的身份提供商 (IDP) 集成以进行用户身份验证,并使用 Istio API 和授权政策管理访问权限。这是 Istio JSON Web 令牌 (JWT) 身份验证的易用替代方案。
典型使用场景是组织使用 Cloud Service Mesh 托管员工通过 Web 浏览器访问的 Web 应用时。此外,组织需要使用其现有身份提供商来管理用户身份。Cloud Service Mesh 用户身份验证可让用户使用标准基于 Web 的 OpenID Connect (OIDC) 登录和同意流程轻松地进行身份验证。用户进行身份验证时,Cloud Service Mesh 会强制执行 Istio 授权政策,成功授权后,它会以安全凭据格式将身份传输到工作负载。
工作原理
Cloud Service Mesh 用户身份验证引入了新的组件 authservice
。此组件与基于 Envoy 的 Ingress 集成,作为外部授权服务,该服务可拦截身份验证的所有传入请求。authservice
实现 OIDC 协议的客户端,可让用户通过浏览器访问应用,其中用户完成了交互式身份验证和同意流程,从而构建短期会话。authservice
实现了业界标准协议,以与充当 OIDC 授权服务器的任何身份提供方集成。当用户通过身份验证时,主信息以 JWT 格式封装在 RCToken
中,并由 authservice
签名,后者将转发到 Ingress 中的 Istio 授权层。该模型为发送到网格的流量提供了边界访问控制。如果用户有权访问资源,则此 RCToken 也将转发到微服务,以获取主信息并强制执行精细的访问权限控制。
下图显示了网格中 authservice
的位置以及它与网格其他部分(例如 Ingress、工作负载、用户的浏览器和任何现有 IDP)之间的关系。
管理员可以在安装 Cloud Service Mesh 时以插件形式安装 authservice
。安装后,authservice
读取 OIDC 端点配置和 UserAuth
自定义资源中定义的其他相关设置。管理员可以使用 Cloud Service Mesh ExternalAuthorization
API 将 auth_server
配置为 Ingress 的过滤器。
安装用户身份验证服务
以下步骤介绍了如何配置 authservice
。
前提条件
按照安装依赖工具并验证集群中的步骤操作:如果您在专用集群上使用代管式 Cloud Service Mesh,请确保集群能够将出站流量发送到 IDP。
获取
kpt
软件包:kpt pkg get https://github.com/GoogleCloudPlatform/asm-user-auth.git/@v1.2.3 . cd asm-user-auth/
此命令会从公共代码库检索包含推荐
authservice
配置的 kpt 软件包。该软件包包含用于将authservice
容器作为 Pod 部署在asm-user-auth
命名空间中的清单。它还会将入站网关配置为对所有请求都要求授权。
配置外部授权提供程序并部署入站流量网关
如需安装用户身份验证服务,您需要自定义 Cloud Service Mesh 安装以添加外部授权提供程序。所需步骤取决于您使用的是代管式 Cloud Service Mesh 还是集群内 Cloud Service Mesh。
代管式
更新 Istio ConfigMap 中的网格配置,以添加外部授权提供程序。在以下命令中,使用您在预配托管式 Cloud Service Mesh 时所用的
REVISION_LABEL
(例如asm-managed
、asm-managed-rapid
或asm-managed-stable
):kubectl edit configmap istio-REVISION_LABEL -n istio-system
在
mesh
字段下添加扩展程序提供程序:mesh: |- ... extensionProviders: - name: "asm-userauth-grpc" envoyExtAuthzGrpc: service: "authservice.asm-user-auth.svc.cluster.local" port: "10003"
创建
asm-user-auth
命名空间并为其添加标签。kubectl create namespace asm-user-auth kubectl label namespace asm-user-auth istio.io/rev=REVISION_LABEL --overwrite
在
asm-user-auth
命名空间中安装入站流量网关。kubectl apply -n asm-user-auth -f DIR_PATH/samples/gateways/istio-ingressgateway
集群内
获取用户身份验证叠加层示例,并在网格中进行任何自定义设置时更新该叠加层。建议的最佳做法是在源代码控制系统中维护此叠加文件。
curl https://raw.githubusercontent.com/GoogleCloudPlatform/asm-user-auth/v1.2.3/overlay/user-auth-overlay.yaml > user-auth-overlay.yaml
按照使用叠加层安装 Cloud Service Mesh 的说明,通过 Google 提供的脚本使用用户身份验证叠加层安装 Cloud Service Mesh。例如:
./asmcli install \ --project_id PROJECT_ID \ --cluster_name CLUSTER_NAME \ --cluster_location CLUSTER_LOCATION \ --fleet_id FLEET_PROJECT_ID \ --output_dir DIR_PATH \ --enable_all \ --custom_overlay user-auth-overlay.yaml
用户身份验证
kpt
软件包会创建一个AuthorizationPolicy
,用于引用pkg/ext-authz.yaml
指定的外部授权提供商。创建
asm-user-auth
命名空间并为其添加标签。kubectl create namespace asm-user-auth kubectl label namespace asm-user-auth istio.io/rev=REVISION --overwrite
您可以通过检查
kubectl get pod -n istio-system -L istio.io/rev
以找到REVISION
标签值在
asm-user-auth
命名空间中安装 Istio 网关。kubectl apply -n asm-user-auth -f DIR_PATH/samples/gateways/istio-ingressgateway
准备 OIDC 客户端配置
按照以下步骤设置 OIDC 客户端配置。本指南使用 Google 作为 IDP,但您可以使用任何支持 OIDC 身份验证的 IDP。
在 Google Cloud 控制台中,转到 API 和服务 > 凭据。
转到创建凭据,然后选择 OAuth 客户端 ID。根据需要设置 OAuth 同意屏幕选项,然后配置以下选项:
- 将应用类型设置为 Web 应用。
- 将已获授权的重定向 URI 设置为
https://REDIRECT_HOST/REDIRECT_PATH
。例如,对于 localhost,您可以将其设置为https://localhost:8443/_gcp_asm_authenticate
。
然后,点击保存。
此外,请保存您的 OIDC 客户端配置以备后续使用。
export OIDC_CLIENT_ID=CLIENT_ID export OIDC_CLIENT_SECRET=CLIENT_SECRET export OIDC_ISSUER_URI=ISSUER_URI export OIDC_REDIRECT_HOST=REDIRECT_HOST export OIDC_REDIRECT_PATH=REDIRECT_PATH
为 Ingress 网关设置重定向网址和 Secret
OAuth2
需要托管在 HTTPS 保护端点上的重定向网址。这些命令用于示例目的,并通过为 Istio Ingress Gateway 生成自签名证书来简化设置。
生成自签名证书:
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem \ -days 365 -nodes -subj '/CN=localhost'
为 Ingress 网关创建 Secret 来托管 HTTPS 流量:
kubectl create -n asm-user-auth secret tls userauth-tls-cert --key=key.pem \ --cert=cert.pem
应用加密和签名密钥
authservice
需要两组密钥才能成功运行。第一个是用于加密和解密的对称密钥。在将会话状态设置为 cookie 之前,此密钥用于加密会话状态。
第二组密钥是公钥/私钥对。该密钥用于将 JWT 格式的经过身份验证的用户信息签名为 RCToken。此密钥对的公钥发布在一个预定义端点上,辅助信息文件可将该端点用于验证 JWT。
用户身份验证 kpt
软件包包含两个用于快速设置的示例密钥。但是,您可以使用偏好的密钥管理系统生成这些密钥。
请使用以下格式准备会话加密密钥,或者使用 pkg 中的示例(可通过
cat ./samples/cookie_encryption_key.json
查看)。{ "keys":[ { "kty":"oct", "kid":"key-0", "k":"YOUR_AES_KEY", "useAfter": 1612813735 } ] }
您可以使用以下命令生成测试 AES 密钥:
openssl rand -base64 32
请遵循以下格式准备 RCToken 签名密钥,或使用 pkg 中的示例(可通过
cat ./samples/rctoken_signing_key.json
查看)。{ "keys":[ { "kty":"RSA", "kid":"rsa-signing-key", "k":"YOUR_AES_KEY", # k contains a Base64 encoded PEM format RSA signing key. "useAfter": 1612813735 # unix timestamp } ] }
您可以使用以下命令生成 256 位测试 RSA 私钥:
openssl genpkey -algorithm RSA -out rsa_private.pem -pkeyopt rsa_keygen_bits:256
创建 kubernetes Secret,
authservice
会将其装载到自己的文件系统中。kubectl create secret generic secret-key \ --from-file="session_cookie.key"="./samples/cookie_encryption_key.json" \ --from-file="rctoken.key"="./samples/rctoken_signing_key.json" \ --namespace=asm-user-auth
部署用户身份验证服务
以下命令会在 asm-user-auth
命名空间中创建用户身份验证服务和部署。
设置用户身份验证配置的必要值。客户端 ID 和 Secret 均存储为 Kubernetes Secret,因此我们使用 Base64 对其进行编码。请访问公共代码库,以查看所有可用的 setter。
kpt fn eval pkg --image gcr.io/kpt-fn/apply-setters:v0.2 --truncate-output=false -- \
client-id="$(echo -n ${OIDC_CLIENT_ID} | base64 -w0)" \
client-secret="$(echo -n ${OIDC_CLIENT_SECRET} | base64 -w0)" \
issuer-uri="${OIDC_ISSUER_URI}" \
redirect-host="${OIDC_REDIRECT_HOST}" \
redirect-path="${OIDC_REDIRECT_PATH}"
应用 kpt
软件包:
# Remove the potential alpha version CRD if exists.
kubectl delete crd userauthconfigs.security.anthos.io
kubectl apply -f ./pkg/asm_user_auth_config_v1beta1.yaml
kubectl apply -f ./pkg
authservice
会使用 UserAuthConfig
CRD 来提供最终用户身份验证。在运行时可配置 UserAuthConfig
,您可以将其更新,以更改 authservice
行为并使用任意 OIDC 授权服务器的端点对其进行配置。
您可以按 cat pkg/user_auth_config.yaml
查看文件,其中包含以下字段:
apiVersion: security.anthos.io/v1beta1
kind: UserAuthConfig
metadata:
name: user-auth-config
namespace: asm-user-auth
spec:
authentication:
oidc:
certificateAuthorityData: "" # kpt-set: ${ca-cert}
issuerURI: "<your issuer uri>" # kpt-set: ${issuer-uri}
proxy: "" # kpt-set: ${proxy}
oauthCredentialsSecret:
name: "oauth-secret" # kpt-set: ${secret-name}
namespace: "asm-user-auth" # kpt-set: ${secret-namespace}
redirectURIHost: "" # kpt-set: ${redirect-host}
redirectURIPath: "/_gcp_asm_authenticate" # kpt-set: ${redirect-path}
scopes: "" # kpt-set: ${scopes}
groupsClaim: "" # kpt-set: ${groups}
outputJWTAudience: "test_audience" # kpt-set: ${jwt-audience}
如需详细了解 user_auth_config.yaml
字段,请参阅用户身份验证配置详细信息。
执行安装后任务
完成安装步骤后,您需要执行以下任务。
为您的应用启用用户身份验证
本部分以 httpbin
示例应用为例,演示了如何启用用户身份验证。
Cloud Service Mesh 用户身份验证使用 CUSTOM
类型的授权政策来触发 OIDC 流程。
安装 Istio 网关后,将其配置为使用您刚才创建的 TLS 证书 userauth-tls-cert
传送 HTTPS 流量。以下是您刚刚安装的 pkg/gateway.yaml
配置。
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
name: userauth
namespace: asm-user-auth
spec:
selector:
istio: ingressgateway
servers:
- hosts:
- '*'
port:
name: https
number: 443
protocol: HTTPS
tls:
mode: SIMPLE
credentialName: userauth-tls-cert
---
# This ensures the OIDC endpoint has at least some route defined.
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: userauth-oidc
namespace: asm-user-auth
spec:
gateways:
- userauth
hosts:
- '*'
http:
- match:
- uri:
prefix: /status
- uri:
prefix: "your-oidc-redirect-path"
name: user-auth-route
route:
- destination:
host: authservice
port:
number: 10004
为
default
命名空间添加标签,以启用istio-proxy
自动注入功能。kubectl label namespace default istio.io/rev=REVISION --overwrite
将
httpbin
部署到default
命名空间。kubectl apply -f https://raw.githubusercontent.com/istio/istio/master/samples/httpbin/httpbin.yaml -n default
更新
httpbin
以使用此网关来处理 HTTPS 流量,并使用端口转发在本地访问该应用:kubectl apply -f./samples/httpbin-route.yaml -n default kubectl port-forward service/istio-ingressgateway 8443:443 -n asm-user-auth
端口 8443 上的 Ingress 网关将转发到
localhost
,以便能够在本地访问应用。部署
samples/rctoken-authz.yaml
以启用 RequestAuthentication 和 AuthorizationPolicy 来验证请求的 RCToken。kubectl apply -f ./samples/rctoken-authz.yaml -n asm-user-auth
示例
samples/rctoken-authz.yaml
:apiVersion: security.istio.io/v1beta1 kind: RequestAuthentication metadata: name: require-rc-token spec: selector: matchLabels: istio: ingressgateway jwtRules: - issuer: "authservice.asm-user-auth.svc.cluster.local" audiences: - "test_audience" jwksUri: "http://authservice.asm-user-auth.svc.cluster.local:10004/_gcp_user_auth/jwks" fromHeaders: - name: X-ASM-RCTOKEN forwardOriginalToken: true --- apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: require-rc-token spec: selector: matchLabels: istio: ingressgateway action: ALLOW rules: - when: - key: request.auth.claims[iss] values: - authservice.asm-user-auth.svc.cluster.local - key: request.auth.claims[aud] values: - test_audience
验证用户身份验证
httpbin
提供两个路径,/ip
可公开访问,而 /headers
则要求最终用户通过其配置的 IDP 登录。
通过访问
https://localhost:8443/ip
,验证您能否直接访问/ip
。通过访问
https://localhost:8443/headers
验证您是否能看到 OIDC 登录页面。登录后,点击下一步,然后验证其能否将您重定向到
/headers
页面。
配置授权政策
完成上述步骤中的配置后,每个用户都将通过基于 Web 的身份验证流程进行重定向。该流程完成后,authservice
将以 JWT 格式生成一个 RCToken
,用于传输经过身份验证的用户信息。
在 Ingress 中添加 Istio 授权政策,以确保对每个经过身份验证的用户执行授权检查:
kubectl apply -f ./samples/httpbin-authz.yaml -n asm-user-auth
httpbin-authz.yaml
文件会将 Ingress 网关配置为验证 authservice 发出的 RC 令牌,并且仅在 JWT 包含所需字段(例如目标对象和颁发者)时才授权。请参阅以下授权政策示例:
apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: require-rc-token spec: selector: matchLabels: istio: ingressgateway action: ALLOW rules: - to: - operation: paths: ["/ip"] - to: when: - key: request.auth.claims[iss] values: - authservice.asm-user-auth.svc.cluster.local - key: request.auth.claims[aud] values: - test_audience - key: request.auth.claims[sub] values: - allowed_user_sub_1 # Change this with the "sub" claim in the RC token. Wildcard '*' will match everything.
配置特定于环境的设置
上述步骤使用 localhost
和自签名 HTTPS 证书进行快速设置。对于实际生产用途,请使用您自己的网域,例如 example.com
。
此外,请确保 certificateAuthorityData
具备所需的根证书内容。例如,如果 IDP 受系统根证书信任,您可以将其留空。如果存在终止 HTTPS 连接的 HTTPS 代理,则应将其设置为该代理的根证书。
管理和轮替密钥
authservice
使用两组密钥。您可以单独轮替每个密钥。不过,在轮替密钥之前,请务必了解轮替的工作原理。
这两组密钥均采用 JSON 格式。useAfter
字段指定应使用密钥时的时间戳。在密钥轮替期间,您应该在 JSON 中同时包含新旧密钥。例如,在以下示例中,仅在时间戳 1712813735
后才使用 new-key
。
{
"keys":[
{
"kty":"RSA",
"kid":"old-key",
"K":"...", # k contains a Base64 encoded PEM format RSA signing key.
"useAfter": 1612813735, # unix timestamp
}
{
"kty":"RSA",
"kid":"new-key",
"K":"...", # k contains a Base64 encoded PEM format RSA signing key.
"useAfter": 1712813735, # unix timestamp
}
]
}
Cloud Service Mesh 使用对称密钥加密存储在浏览器 Cookie 中的会话数据。为确保现有会话的有效性,authservice
会尝试使用密钥集中的所有密钥进行解密。轮替后,authservice
将使用新密钥来加密新会话,并将继续尝试使用旧密钥进行解密。
公钥/私钥对用于为 RCToken
签名。istiod
会将公钥传输到 Sidecar,以进行 JWT 验证。Sidecar 在 authservice
开始使用新私钥为 RCToken
签名之前先接收新公钥,这一点至关重要。为此,authservice
在添加密钥后会立即开始发布公钥,但要等待很长时间才开始使用该公钥为 RCToken
签名。
总而言之,在执行密钥轮替时,我们建议:
- 定期执行密钥轮替或按需执行。
- 在 JSON 格式中,添加当前密钥和新密钥。新密钥应与将来的时间戳相关联。我们建议您在当前时间至少提前几个小时指定时间戳。
- 监控并确认新密钥使用后服务仍然运行状况良好。在新密钥被使用后,请等待至少一天再继续下一步。
- 从 JSON 条目中移除旧密钥。您不再需要它们。
多集群部署
Cloud Service Mesh 用户身份验证支持多集群部署。如上所述,您需要在每个集群中部署用户身份验证。用户身份验证配置(例如 UserAuth 自定义资源、OIDC 客户端密钥、加密密钥)需要在每个集群中复制。
默认情况下,入站流量网关会将身份验证请求负载均衡到任何 authservice
实例。您可以使用目标规则配置入站网关,以将请求发送到 authservice
并且仅故障切换到其他集群 authservice
。
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: authservice-fail-over
namespace: asm-user-auth
spec:
host: authservice.asm-user-auth.svc.cluster.local
trafficPolicy:
loadBalancer:
localityLbSetting:
enabled: true
failover:
- from: us-east
to: us-west
- from: us-west
to: us-east
与其他配置相同,这需要在每个集群中配置。
自定义声明映射
如需配置自定义声明映射,请配置 spec.authentication.oidc.attributeMapping
以定义原始身份提供方 IDToken 的任何映射。密钥将是 RCToken 中的声明名称,而值是有关如何从 IDToken 解析声明的 CEL 表达式,请使用 assertion
来引用 IDToken。
示例:
spec:
authentication:
oidc:
attributeMapping:
aud_copy: assertion.aud
decision: 'assertion.sub.startsWith("123") ? "success" : "fail"'
在 RCToken 中,嵌套的声明 attributes
包含已配置的声明:
"attributes": {
"aud_copy": "foo.googleusercontent.com",
"decision": "success"
}
如果 CEL 表达式无法从 IDToken 解析值,它将会忽略声明,但不会使身份验证流程失败。
用户身份验证升级
再次安装 user-auth 软件包,因为它包含新的 user-auth 版本的更新后二进制文件:
kpt pkg get https://github.com/GoogleCloudPlatform/asm-user-auth.git/@v1.2.3 . cd asm-user-auth/
保存您的 OIDC 客户端配置:
export OIDC_CLIENT_ID=CLIENT_ID export OIDC_CLIENT_SECRET=CLIENT_SECRET export OIDC_ISSUER_URI=ISSUER_URI export OIDC_REDIRECT_HOST=REDIRECT_HOST export OIDC_REDIRECT_PATH=REDIRECT_PATH
部署用户身份验证服务以升级到新版本。
用户身份验证配置详细信息
下表介绍了 CRD 中的每个字段:
字段名称 | 说明 |
---|---|
authentication.oidc |
本部分包含 OIDC 端点配置和 OIDC 流程中使用的参数。 |
authentication.oidc.certificateAuthorityData |
这是 OIDC 授权服务器或 HTTPS 代理(如果有)网域的 SSL 根证书。 |
authentication.oidc.oauthCredentialsSecret |
Kubernetes Opaque 类型 Secret 的 Secret 引用,其中包含 JSON 载荷中的 OAuth2 OIDC client_id 和 client_secret。 |
authentication.oidc.issuerURI |
用作输出 RCToken 中的颁发者的 URI。 |
authentication.oidc.proxy |
对于 OIDC IDP 的代理服务器(如果适用)。格式为 http://user:password@10.10.10.10:8888。 |
authentication.oidc.redirectURIHost |
用于 OAuth 终止 URI 的主机。如果将此字段留空,则系统将使用目标网址中的主机,并动态组合重定向 URI。 如果需要在更高级层进行用户身份验证 SSO 会话,则可以使用此值。例如,如需启用 profile.example.com/ 和 admin.example.com/ 之间的单点登录,可将此值设置为 example.com。这将允许在 example.com 上建立一个将在所有子网域之间共享的用户身份验证会话。注意:如果多个网域由同一网格 example1.com 和 example2.com 提供服务,则无法使用该功能,建议您将其留空。 |
authentication.oidc.redirectURIPath |
authservice 将在其中终止 OAuth 流程的端点路径。您应该将此 URI 路径以及主机注册为 authentication.oidc.clientID 的授权服务器中的授权重定向 URI。此外,应从启用了 authservice 的同一服务网格和 Ingress 提供此 URI。 |
authentication.oidc.scopes |
应在身份验证请求中请求的 OAuth 范围。 以英文逗号分隔的标识符列表,用于指定除了“openid”范围之外请求哪些访问特权,例如 “groups,allatclaim”。 |
authentication.oidc.groupsClaim |
如果 idtoken 包含群组声明,请使用此字段来指示其名称。如果指定此字段,则服务会将此声明中的数据传入到输出 groups 中的“群组”声明。此声明应包含以英文逗号分隔的字符串列表,例如:["group1", "group2"]。 |
authentication.oidc.attributeMapping |
包含一个或多个从 idtoken 的声明映射(来自 CEL 表达式)。所有声明都应由 assertion.X 引用,assertion 引用到原始 IDToken,例如 aud_copy: assertion.aud |
authentication.outputJWTAudience |
由 authservice 生成的 RCToken 目标对象。Sidecar 可以根据此目标对象值验证传入的 RCToken。 |
问题排查
IDP 的网络可访问性。
可能的日志:
error: TLS handshake failed.
。通过执行从
istio-proxy
容器到 IDP 颁发者 URI 的curl
实现验证。如果无法连接,用户可检查集群的防火墙规则或其他网络配置。根 CA 证书。
可能的日志:
error: The server's TLS certificate did not match expectations.
或error: TLS handshake failed.
。确保
certificateAuthorityData
保留正确的根 CA 证书。在没有 HTTPS 代理终止 HTTPS 流量时,它会保留该 IDP 的根 CA 证书。如果存在代理,则请改为保留代理的相应证书。重定向路径配置。
可能的观察结果:在 OIDC 身份验证流程中接收到 404 错误页面。
用户身份验证会返回“Set-Cookie”标头,但不使用路径特性;默认情况下,浏览器使用请求网址的目录作为 Cookie 路径(与路径相关的 Cookie 的范围)。因此,我们建议您不要在重定向路径中包含“/”(除非是故意而为)。
Sidecar 无法提取 jwksUri。
在某些情况下,Sidecar 限制可能会导致提取 jwksUri 失败。如果无法使用通配符(例如,
./*
或istio-system/*
)来提供命名空间,那么这将不起作用。您必须在出站流量 Sidecar 中手动添加其命名空间。
常见问题解答
如何升级启用了用户身份验证的 Cloud Service Mesh?
如果您使用的是集群内 Cloud Service Mesh,请遵循 Cloud Service Mesh 升级过程,并在命令行中将
--custom_overlay user-auth-overlay.yaml
添加到asmcli install
并指定叠加文件。托管式 Cloud Service Mesh 会自动升级。
我应该为
authservice
预配多少 CPU 和内存?它每秒能处理多少个请求?默认情况下,
authservice
配置了 2.0 vCPU 和 256Mi 内存。在此配置下,authservice
每秒可处理 500 个请求。如需处理更多请求,您应该预配更多 CPU,大致与其请求处理能力成正比。您还可以配置多个 authservice 副本,以提高横向可伸缩性。