共计 14623 个字符,预计需要花费 37 分钟才能阅读完成。
K8S API 概述
可参考:https://kubernetes.io/zh-cn/docs/concepts/overview/kubernetes…
Kubernetes API 是 Kubernetes 管制立体的外围。它是一组 REST API,用于与 Kubernetes 中的各种对象进行交互,如 Pods、Namespaces、ConfigMaps 和 Events 等。通过这些 API,能够查问和操作 Kubernetes 中 API 对象的状态。
API server 是 Kubernetes 集群中的一个组件,它公开了这些 REST API。Kubernetes 中的各种组件,包含 kubectl 命令行工具、kubeadm 等工具,都通过调用这些 API 来执行操作。
除了应用 kubectl 等工具之外,也能够间接应用 REST 调用来拜访 API。如果正在编写应用 Kubernetes API 的应用程序,请思考应用其中一个客户端库。
残缺的 API 详细信息都应用 OpenAPI 进行文档化,这使得运维开发人员能够很容易地理解 API 的性能和应用形式。
OpenAPI 标准
Kubernetes OpenAPI 标准实际上只有一种,它是基于 OpenAPI 3.0 标准的。之前版本的 Kubernetes API 应用的是 Swagger 2.0 标准,但当初曾经降级到了 OpenAPI 3.0 标准。
须要留神的是,尽管 OpenAPI 3.0 标准是 Swagger 2.0 标准的继承者,但它们之间有一些重要的区别,如参数、响应、申请体和平安等方面的定义形式都有所不同。因而,在应用 OpenAPI 标准时须要留神版本兼容性。接下来,别离理解一下 V2 和 V3。
OpenAPI V2
Kubernetes API 服务器提供了一个聚合的 OpenAPI v2 标准,通过拜访 /openapi/v2 端点获取。这个标准包含了所有的 API 组的定义,以及每个 API 组的所有 API 的定义,使得运维开发人员能够分明地理解 Kubernetes API 的构造和性能。
通过在 HTTP 申请头中指定不同的响应格局,运维开发人员能够取得不同格局的 OpenAPI 标准文档。下表列出了可用的申请头和响应格局:
头部 | 可选值 | 阐明 |
---|---|---|
Accept-Encoding | gzip | 不指定此头部也是能够的 |
Accept | application/com.github.proto-openapi.spec.v2@v1.0+protobuf | 次要用于集群外部 |
application/json | 默认值 | |
* | 提供 application/json |
通过应用这些申请头,开发人员能够获取他们所需的格式化的 OpenAPI 标准文档,以便在应用程序中进行解决和解析。
Kubernetes 的 API 应用 Protobuf 作为序列化格局进行外部通信。这种序列化格局有助于缩小网络传输的数据量和进步通信的效率。在 Kubernetes 中,每个 API 对象都有一个对应的 Protobuf 定义文件。这些文件形容了对象的构造和字段。Kubernetes 还提供了应用这些 Protobuf 定义文件生成客户端和服务器代码的工具,以便开发人员能够轻松地应用 Kubernetes API。除了 Protobuf 之外,Kubernetes 还反对应用其余序列化格局进行通信,例如 JSON 和 YAML。这些格局更易于浏览和编写,并且通常用于与内部零碎的集成。不过,在集群外部通信时,Protobuf 依然是最罕用的序列化格局。
想进一步理解 Kubernetes Protobuf 序列化可参考:https://github.com/kubernetes/design-proposals-archive/blob/main/api-machinery/protobuf.md
OpenAPI V3
OpenAPI V3 是 Kubernetes 反对的一种 API 形容格局。从 Kubernetes v1.27 版本开始,这个性能曾经被稳固反对。
Kubernetes 提供了一个名为 /discovery/v3 的端点来展现所有可用的 API 组和版本列表。这个端点只返回 JSON 格局的数据。这些 API 组和版本会以特定的格局出现:
{
"paths": {
...,
"api/v1": {"serverRelativeURL": "/openapi/v3/api/v1?hash=CC0E9BFD992D8C59AEC98A1E2336F899E8318D3CF4C68944C3DEC640AF5AB52D864AC50DAA8D145B3494F75FA3CFF939FCBDDA431DAD3CA79738B297795818CF"},
"apis/admissionregistration.k8s.io/v1": {"serverRelativeURL": "/openapi/v3/apis/admissionregistration.k8s.io/v1?hash=E19CC93A116982CE5422FC42B590A8AFAD92CDE9AE4D59B5CAAD568F083AD07946E6CB5817531680BCE6E215C16973CD39003B0425F3477CFD854E89A9DB6597"},
....
}
}
为了进步客户端缓存效率,这些绝对 URL 指向不可变的 OpenAPI 形容信息。为此,API 服务器还设置了适当的 HTTP 缓存标头(将 Expires 设置到将来的 1 年,将 Cache-Control 设置为不可变)。当应用过期的 URL 时,API 服务器会将其重定向到最新的 URL。
Kubernetes API 服务器在 /openapi/v3/apis/<group>/<version>?hash=<hash> 端点为每个 Kubernetes 组版本公布一个 OpenAPI v3 标准。
承受的申请标头请参考下表:
头部 | 可选值 | 阐明 |
---|---|---|
Accept-Encoding | gzip | 不提供此头部也是可承受的 |
Accept | application/com.github.proto-openapi.spec.v3@v1.0+protobuf | 次要用于集群外部应用 |
application/json | 默认 | |
* | 以 application/json 模式返回 |
API 参考
API 端点、资源类型以及示例可参考:https://kubernetes.io/zh-cn/docs/reference/kubernetes-api/
申请 API 经验的阶段
可参考:https://kubernetes.io/zh-cn/docs/concepts/security/controllin…
用户能够应用 kubectl、客户端库或通过进行 REST 申请来拜访 Kubernetes API。无论是人类用户还是 Kubernetes 服务账户,都能够被受权拜访 API。当申请达到 API 时,它会通过几个阶段,如下图所示:
![图片]()
连贯和证书:
- API Server 默认在 6443 端口上进行监听,也能够批改。
- 拜访 API,应用 TLS 建设连贯。
- API Server 证书,能够是公有 CA、也能够是公认 CA。
上图步骤的认证过程:
- 申请 API 时,会和 APIServer 建设 TLS 连贯。
- 进入身份认证模块(Authentication),验证拜访 API 的用户是否非法,认证不通过则返回 401。
- 进入鉴权模块(Authorization),确认该用户是否具备拜访某个资源或执行某个操作的权限,如果现有策略申明该用户有权实现申请的操作,则鉴权通过。
- 进入准入控制器(Admission Control),执行验证和 / 或变更操作。
- 通过所有准入控制器后,再查看对应的 API 对象,而后将其写入对象存储。
“
鉴权模块阐明:鉴权模块的实现有 RBAC、ABAC、Node、Webhook。可参考:https://kubernetes.io/zh-cn/docs/reference/access-authn-authz…
”
“
准入控制器阐明:准入控制器会在申请通过认证和鉴权之后、对象被长久化之前拦挡达到 APIServer 的申请,准入管制过程会运行两个阶段,别离是第 1 阶段是运行变更准入控制器,第 2 阶段是运行验证准入控制器。留神了,某些控制器既是变更准入控制器又是验证准入控制器。如果两个阶段之一的任何一个控制器回绝了某申请,则整个申请将立刻被回绝,并向最终用户返回谬误。如要进一步理解准入控制器可参考:https://kubernetes.io/zh-cn/docs/reference/access-authn-authz…
”
申请 API 之前筹备一个普通用户
所有 Kubernetes 集群都有两类用户:
- 由 Kubernetes 治理的服务账号
- 普通用户
在理论工作中要调用 K8S API,为了减少安全性,倡议创立一个专用的一般程序账号。
1. 创立普通用户的私钥
为了让普通用户可能通过认证并调用 API,须要执行几个步骤。首先,该用户必须领有 Kubernetes 集群签发的证书,而后将该证书提供给 Kubernetes API。
可参考:https://kubernetes.io/zh-cn/docs/reference/access-authn-authz…
# 创立一个普通用户的私钥和证书签名申请 (Certificate Signing Request, CSR)。能够应用 OpenSSL 工具生成私钥和 CSR:openssl genrsa -out tantianran.key 2048
openssl req -new -key tantianran.key -out tantianran.csr -subj "/CN=tantianran/O=noblameops"
这里 tantianran 是用户的名称,noblameops 是用户所属的组织。
2. 创立证书签名申请(CertificateSigningRequest),并提交到 Kubernetes 集群
将 CSR 提交给 Kubernetes 集群中的证书签名机构 (Certificate Authority, CA) 进行签名。能够应用 kubectl 工具提交 CSR 并获取签名后的证书:
cat <<EOF | kubectl apply -f -
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
name: tantianran
spec:
request: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURSBSRVFVRVNULS0tLS0KTUlJQ2J6Q0NBVmNDQVFBd0tqRVRNQkVHQTFVRUF3d0tkR0Z1ZEdsaGJuSmhiakVUTUJFR0ExVUVDZ3dLYm05aQpiR0Z0Wlc5d2N6Q0NBU0l3RFFZSktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQU1Jc0M0R2JSQ0NJCmE1L2RLenpYc3VtUXVxay9qclZRemJuSmpiVkY5ZVV6bU1OR3drTi9aanRrVU5ZTUNRMkRpY1JIRUJzTFVMTTIKSFhTdkZtV1lUUGN6OEJWOHgvR214YXlWcVNmQTU4aDNtdjJERjhZdFB2aFlVc3hmUVZpUUxFRGFwRXpoV29TcApVN3BwRjJ4YXZjeG9GbDd3emRQWE9YMnhDQXNSQ3pINjB6cG9zSEJiNHBaSGJjYjNyQ1hjdHBnVlFDeklubWRGCjFrNHJncHg5SGsrek4rNzQ1R04vS1dMMWdLcFNhN2YxemdHdXVaT2FrZEhKaldGdCtWYzNFSG90SFQ3V3g1VTEKazV4ZnpuZkk2VlkvN0NTbzR1K2hhSTg2RHBRaXZmdk1OM3ZGeURwOVR2UWVsOFJpZlFiMkxaMnlUYzdEL3hHQQpCWUZhQ25OUjl3a0NBd0VBQWFBQU1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQ0hyNmYxd3A0NFd1L1dtczAvCmRFdWswVjJrMElzQzZoOHppbkptN3BHMjlpSFVrVDZNWDNBR3E0WlVZNkVBN3BYa1VDazhsYXFGVjVQTmJoSXkKUTBEUndRdW82WHNqZ1JMQmdZZFNRaU5vVUYrR1ZCSEEyNmZEV3c2VU8zdjErZXVJODlOWXVJbXl4UGtpaE0xYgpyZkNoa1RCeXRBbUxHbVlwOU5OMnBHdDJyTW94cGtDME5PSElWOWdPUnp1Q1h3cytWTE5zS3VSS2diT1hsUVhMCmVOeVd4TGlCR0ZSZ1BsaWpyTnQrdnA1WktHRjV1SEVXYStjZ3NXN1cwZCtoRm9XMlYxczVDZ2ZzdU1IdUNlR3AKMUxSVnZheVJSaDVtekdnTlNrdUpkUTBHdU1lbk5tRGpoSDI1NU5CNVBzdHpTOVBSU1lCVUIvdUdIYi9yVXByWgprOHRECi0tLS0tRU5EIENFUlRJRklDQVRFIFJFUVVFU1QtLS0tLQo=
signerName: kubernetes.io/kube-apiserver-client
expirationSeconds: 86400 # one day
usages:
- client auth
EOF
须要留神的几点:
- usage 字段必须是 ‘client auth’
- expirationSeconds 能够设置为更长(例如 864000 是十天)或者更短(例如 3600 是一个小时)
-
request 字段是 CSR 文件内容的 base64 编码值。要失去该值,能够执行命令
cat tantianran.csr | base64 | tr -d "\n"
创立实现后查看一下 CSR 列表:
[root@k8s-a-master api-user]# kubectl get csr
NAME AGE SIGNERNAME REQUESTOR REQUESTEDDURATION CONDITION
tantianran 58s kubernetes.io/kube-apiserver-client kubernetes-admin 24h Pending
3. 批准证书签名申请(CertificateSigningRequest,简称 CSR)
[root@k8s-a-master api-user]# kubectl certificate approve tantianran
certificatesigningrequest.certificates.k8s.io/tantianran approved
反之,如果要驳回:
kubectl certificate deny tantianran
4. 获取证书
从 CSR 获取证书:
kubectl get csr/tantianran -o yaml
证书的内容应用 base64 编码,寄存在字段 status.certificate。
从 CertificateSigningRequest 导出颁发的证书:
kubectl get csr tantianran -o jsonpath='{.status.certificate}'| base64 -d > tantianran.crt
5. 基于 RBAC 的鉴权模式,创立 Role(角色)
“
在 Kubernetes 中,Role 和 ClusterRole 都是用于受权拜访 Kubernetes API 资源的对象,但它们之间有着不同的作用域。Role 是一个名字空间作用域的资源,它定义了一个角色,即一组操作权限,能够被授予给一个或多个用户、服务账户或其余角色,以管制它们在某个特定命名空间内的操作权限。因而,当您创立 Role 时,必须指定该 Role 所属的命名空间。与之绝对,ClusterRole 是一个集群作用域的资源,它定义了一组操作权限,能够授予给任何命名空间内的用户、服务账户或其余角色。因而,ClusterRole 能够用于受权对整个 Kubernetes 集群的操作权限。须要留神的是,因为 Kubernetes 对象要么是名字空间作用域的,要么是集群作用域的,因而 Role 和 ClusterRole 的名称不同,以便将它们辨别开来。如果您要在特定的命名空间内设置拜访权限,则应该应用 Role。如果您要在整个集群中设置拜访权限,则应该应用 ClusterRole。
”
创立了证书之后,为了让这个用户能拜访 Kubernetes 集群资源,当初就要创立 Role 和 RoleBinding(在下一大节创立)了。
上面命令将在 rook-ceph 命名空间中创立一个名为 developer 的角色,并为该角色调配一些操作权限。具体来说,该角色将被授予在该命名空间内创立、获取、列出、更新和删除 pods 资源的权限。
kubectl create role developer --verb=create --verb=get --verb=list --verb=update --verb=delete --namespace=rook-ceph --resource=pods
- kubectl create role developer:创立一个名为 developer 的角色。
- –verb=create –verb=get –verb=list –verb=update –verb=delete:指定该角色容许的操作权限,即创立、获取、列出、更新和删除。
- –namespace=rook-ceph:指定该角色所属的命名空间为 rook-ceph。
- –resource=pods:指定该角色所受权的资源类型为 pods。
对应的 yaml 如下:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: developer
namespace: rook-ceph
rules:
- apiGroups:
- ""
resources:
- pods
verbs:
- create
- get
- list
- update
- delete
列出 rook-ceph 命名空间下的 role:
[root@k8s-a-master api-user]# kubectl get roles -n rook-ceph
NAME CREATED AT
cephfs-external-provisioner-cfg 2023-04-03T08:28:33Z
developer 2023-04-19T08:10:25Z # 这个就是方才创立的
rbd-csi-nodeplugin 2023-04-03T08:28:33Z
rbd-external-provisioner-cfg 2023-04-03T08:28:33Z
rook-ceph-cmd-reporter 2023-04-03T08:28:33Z
rook-ceph-mgr 2023-04-03T08:28:33Z
rook-ceph-osd 2023-04-03T08:28:33Z
rook-ceph-purge-osd 2023-04-03T08:28:33Z
rook-ceph-rgw 2023-04-03T08:28:33Z
rook-ceph-system 2023-04-03T08:28:33Z
6. 基于 RBAC 的鉴权模式,创立 RoleBinding(角色绑定)
“
角色绑定(Role Binding)是将角色中定义的权限赋予一个或者一组用户。它蕴含若干 主体(用户、组或服务账户)的列表和对这些主体所取得的角色的援用。RoleBinding 在指定的名字空间中执行受权,而 ClusterRoleBinding 在集群范畴执行受权。一个 RoleBinding 能够援用同一的名字空间中的任何 Role。或者,一个 RoleBinding 能够援用某 ClusterRole 并将该 ClusterRole 绑定到 RoleBinding 所在的名字空间。如果你心愿将某 ClusterRole 绑定到集群中所有名字空间,你要应用 ClusterRoleBinding。
”
上面的命令是在 Kubernetes 集群中创立一个名为 developer-binding-tantianran 的角色绑定对象,其作用是将一个用户(tantianran)与一个名为 developer 的角色关联起来。
kubectl create rolebinding developer-binding-tantianran --role=developer --user=tantianran --namespace=rook-ceph
- –role 选项指定了要绑定的角色,这里是 developer。
- –user 选项指定了要绑定到该角色的用户,这里是 tantianran。
这意味着,一旦角色绑定对象被创立,用户 tantianran 就将取得 developer 角色的权限。
对应的 yaml 如下:
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: developer-binding-tantianran
namespace: rook-ceph
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: developer
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: tantianran
列出 rook-ceph 命名空间下的 RoleBinding:
[root@k8s-a-master api-user]# kubectl get rolebinding -n rook-ceph
NAME ROLE AGE
cephfs-csi-provisioner-role-cfg Role/cephfs-external-provisioner-cfg 15d
developer-binding-tantianran Role/developer 14s # 这个是方才创立的
rbd-csi-nodeplugin-role-cfg Role/rbd-csi-nodeplugin 15d
rbd-csi-provisioner-role-cfg Role/rbd-external-provisioner-cfg 15d
rook-ceph-cluster-mgmt ClusterRole/rook-ceph-cluster-mgmt 15d
rook-ceph-cmd-reporter Role/rook-ceph-cmd-reporter 15d
rook-ceph-mgr Role/rook-ceph-mgr 15d
rook-ceph-mgr-system ClusterRole/rook-ceph-mgr-system 15d
rook-ceph-osd Role/rook-ceph-osd 15d
rook-ceph-purge-osd Role/rook-ceph-purge-osd 15d
rook-ceph-rgw Role/rook-ceph-rgw 15d
rook-ceph-system Role/rook-ceph-system 15d
7. 增加到 kubeconfig
kubeconfig 是 Kubernetes 集群客户端的配置文件,它蕴含连贯到 Kubernetes API Server 所需的信息,包含 API Server 地址、证书、认证形式等。
最初一步是将这个用户增加到 kubeconfig 文件。首先,须要增加新的凭据:
kubectl config set-credentials tantianran --client-key=tantianran.key --client-certificate=tantianran.crt --embed-certs=true --namespace=rook-ceph
- tantianran 是用户凭据条目标名称,能够自定义。
- –client-key=tantianran.key 示意应用名为 tantianran.key 的客户端密钥文件作为用户凭据的一部分。客户端密钥用于对 API 服务器进行身份验证。
- –client-certificate=tantianran.crt 示意应用名为 tantianran.crt 的客户端证书文件作为用户凭据的一部分。客户端证书用于对 API 服务器进行身份验证。
- –embed-certs=true 示意将客户端证书嵌入到 kubeconfig 文件中,而不是将其作为文件援用。这能够帮忙简化 kubeconfig 文件的治理。
- –namespace=rook-ceph 示意在 rook-ceph 命名空间中应用该用户凭据。命名空间用于将 Kubernetes 资源划分为不同的逻辑组。
应用 kubectl config set-credentials 命令创立了名为 tantianran 的用户凭据,那么能够在输入后果中搜寻 tantianran 来查看该用户凭据的详细信息。上面是应用 kubectl config view 命令查看 kubeconfig 文件中用户凭据的示例输入:
[root@k8s-a-master api-user]# kubectl config view
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: DATA+OMITTED
server: https://192.168.11.10:6443
name: kubernetes
contexts:
- context:
cluster: kubernetes
user: kubernetes-admin
name: kubernetes-admin@kubernetes
- context:
cluster: kubernetes
user: tantianran
name: tantianran
current-context: kubernetes-admin@kubernetes
kind: Config
preferences: {}
users:
- name: kubernetes-admin
user:
client-certificate-data: REDACTED
client-key-data: REDACTED
- name: tantianran
user:
client-certificate-data: REDACTED
client-key-data: REDACTED
在上述输入中,能够看到名为 tantianran 的用户凭据信息,包含客户端证书、客户端密钥和命名空间等。
而后,增加上下文:
kubectl config set-context tantianran --cluster=kubernetes --user=tantianran --namespace=rook-ceph
kubectl config set-context 命令用于创立或批改 kubeconfig 文件中的上下文。上下文蕴含了与一个 Kubernetes 集群的连贯所需的所有信息,包含集群、用户和命名空间等。下面的参数阐明如下:
- tantianran 是上下文的名称
- –cluster 参数指定了集群名称为 kubernetes
- –user 参数指定了用户名称为 tantianran
- –namespace 参数指定了默认命名空间为 rook-ceph。简而言之,这个命令创立了一个名为 tantianran 的上下文,该上下文与 kubernetes 集群建设连贯,并应用 tantianran 用户进行身份验证。同时,该上下文默认的命名空间为 rook-ceph,通过实战,其实是没必要指定命名空间。因为,就算指定了命名空间,当不论是查看还是删除上下文的时候,不论有没有指定命名空间都是能够的。比方查看的时候,不指定命名空间也能查到,比方删除的时候,不指定命名空间照样也能删除。
如果要删除上下文能够用上面的命令:
kubectl config delete-context tantianran
# 或
kubectl config delete-context tantianran -n rook-ceph
增加后,查看以后可用的 Kubernetes 配置文件上下文:
[root@k8s-a-master api-user]# kubectl config get-contexts
CURRENT NAME CLUSTER AUTHINFO NAMESPACE
* kubernetes-admin@kubernetes kubernetes kubernetes-admin
tantianran kubernetes tantianran rook-ceph
# 或
[root@k8s-a-master api-user]# kubectl config get-contexts -n rook-ceph
CURRENT NAME CLUSTER AUTHINFO NAMESPACE
* kubernetes-admin@kubernetes kubernetes kubernetes-admin
tantianran kubernetes tantianran rook-ceph
[root@k8s-a-master api-user]#
上下文切换:
# 切换到普通用户的上下文
[root@k8s-a-master api-user]# kubectl config use-context tantianran
# 列出以后上下文
[root@k8s-a-master api-user]# kubectl config get-contexts
CURRENT NAME CLUSTER AUTHINFO NAMESPACE
kubernetes-admin@kubernetes kubernetes kubernetes-admin
* tantianran kubernetes tantianran # 此处的 * 号代表以后上下文是处于这个账户下
# 把上下文切换回 admin:kubectl config use-context kubernetes-admin@kubernetes
查看以后用户是否能够执行给定操作(无论应用何种鉴权模式该命令都能够工作,我这里是 RBAC(基于角色的访问控制)的鉴权模式):
# 在 admin 上下文中执行查看操作:[root@k8s-a-master api-user]# kubectl auth can-i list pods --namespace rook-ceph --as tantianran
yes
[root@k8s-a-master api-user]# kubectl auth can-i list pods --namespace default --as tantianran # 能够看到,处于 default 命名空间下的 pod 的,tantianran 是没有权限的
no
# 如果曾经切换到了一般账户的上下文中,那么能够用上面的命令查看:[root@k8s-a-master api-user]# kubectl config use-context tantianran
[root@k8s-a-master api-user]# kubectl auth can-i create pods --namespace rook-ceph
yes
[root@k8s-a-master api-user]# kubectl auth can-i create pods --namespace default
no
客户端库
当要应用 Kubernetes REST API 来操作 K8S 各种资源时,能够依据本人喜爱的编程语言来抉择适合的客户端库。客户端库有官网反对的,也有社区保护的。官网反对的 Kubernetes 客户端库有 Go、Python、C、Java 等等,作为运维开发工程师,能够应用 Go 或者 Python。而我,以前是写 Python 的,老早就曾经彻底转 Go 了,而且还是 Go 的深度发烧友。对于客户端库更多的信息可参考:https://kubernetes.io/zh-cn/docs/reference/using-api/client-l…
“
我打算别离应用 Golang 和 Python 的 K8S 客户端库来进行编码,明天的工夫无限,放到下篇分享。
”
本文转载于 WX 公众号:不背锅运维(喜爱的盆友关注咱们):https://mp.weixin.qq.com/s/G8jK0IBcfM3kxQfZD_ebEw