前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >K8S之Pod控制器

K8S之Pod控制器

作者头像
后场技术
发布2021-07-23 13:28:29
2910
发布2021-07-23 13:28:29
举报
文章被收录于专栏:后场技术后场技术

我们之前通过资源配置清单,自己创建了一个Pod资源,如果此时这个Pod被删除了,K8S是不会帮我们重新创建的。通过这种方式创建的Pod称之为自主式Pod资源,如果线上所有的服务都需要我们来手动管理Pod,那将是一个巨大的运维开销,那K8S就失去了其存在的意义,所以,K8S为我们提供了Pod控制器资源,专门用于对Pod的管理。Pod控制器可以帮我们自动保持Pod状态处于我们期望的状态,例如Pod的副本数,Pod中使用的容器镜像版本,Pod的更新策略等等。

当我们在创建Pod时,会给Pod打上标签,而我们的Pod控制器正是通过标签选择器来选中指定标签的Pod,从而实现对Pod的管理。

一、Pod控制器类型

常见的Pod控制器有如下类型:

ReplicationController:简称RC,旧版本K8S中使用的Pod控制器,ReplicaSet的前身,仅支持等式的标签选择器,官方不建议使用。

ReplicaSet:简称RS,新版本中用于顶替ReplicationController的Pod控制器,其支持集合式的标签选择器。ReplicaSet可以单独使用,但是单独使用时其并不支持rolling-update,官方建议通过Deployment来自动管理ReplicaSet。其由三部分组成:用户期望的副本数,标签选择器,Pod资源模板。用户期望的副本数表示启动的Pod的数量,标签选择器用来筛选那些被控制的Pod,Pod资源模板则定义了Pod的名称,容器,镜像等等,其本质上就是Pod资源的metadata和spec字段。

Deployment:简称deploy,最常用的Pod控制器,需要注意的是,其并不直接控制Pod,而是直接控制ReplicaSet,然后通过ReplicaSet来控制Pod,Deployment支持rolling-update,版本记录,回滚,暂停更新等操作。其每次更新时都会自动创建一个ReplicaSet,每一个ReplicaSet就是表示一个版本,这就表明在同一个Deployment下会有多个ReplicaSet,但是同一时间只有一个ReplicaSet在工作。

DaemonSet:简称DS,DaemonSet控制器会在K8S集群中所有Node节点上都启动且仅启动一个Pod,一般这类Pod都是用来运行集群中的公共服务,例如监控、日志收集等等。当有新的Node节点加入集群后,DaemonSet会立即在新的节点上创建Pod。

Job:负责一次性任务的处理,其控制下的Pod仅执行一次并成功退出。

CronJob:定时任务,负责在某个时间点或者以一定时间规律运行的任务。

StatefulSet:用于管理有状态应用的Pod控制器。

1、ReplicaSet

了解了Pod控制器的类型后,我们通过资源配置清单来创建一个ReplicaSet类型的Pod控制器。

代码语言:javascript
复制
[root@k8s7-200 ReplicaSet]# pwd
/data/k8s-yaml/ReplicaSet
[root@k8s7-200 ReplicaSet]# cat myapp-rs.yaml 
apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: myapp-rs
  namespace: default
spec:
  replicas: 2
  selector:
    matchLabels:
      app: myapp
      release: test-v1
  template:
    metadata:
      name: myapp
      labels:
        app: myapp
        release: test-v1
    spec:
      containers:
      - name: myapp-container
        image: harbor.od.com/public/myapp:v1

ReplicaSet.spec字段有如下几个二级字段:

replicas:副本数,即用户期望的Pod数量

selector:标签选择器,用来指定筛选Pod的标签,其下级字段有matchLabels用于匹配键值,matchExpressions用于匹配集合类型标签

template:定义Pod资源模板,其下就是Pod资源的metada和spec字段,此处需要注意,Pod资源的标签一定要能被selector中定义的标签选择器所匹配到。此字段下的metadata和spec字段可以参见《K8S系列 -- K8S资源配置清单》,此处不再赘述

定义好资源配置清单后,我们就可以来创建Pod控制器了

代码语言:javascript
复制
[root@k8s7-22 ~]# kubectl create -f http://k8s-yaml.od.com/ReplicaSet/myapp-rs.yaml  # 创建控制器
replicaset.apps/myapp-rs created
[root@k8s7-22 ~]# kubectl get rs     # 查看rs资源
NAME                  DESIRED   CURRENT   READY   AGE
myapp-rs              2         2         2       33m
[root@k8s7-22 ~]# kubectl get pods -l app=myapp,release=test-v1 --show-labels  # 查看pod
NAME             READY   STATUS    RESTARTS   AGE   LABELS
myapp-rs-v26gm   1/1     Running   0          34m   app=myapp,release=test-v1
myapp-rs-xvp5z   1/1     Running   0          34m   app=myapp,release=test-v1

我们将资源配置清单中的副本数量改成5,然后就可以对当前pod进行扩容了:

代码语言:javascript
复制
[root@k8s7-22 ~]# kubectl apply -f http://k8s-yaml.od.com/ReplicaSet/myapp-rs.yaml  # 应用新的配置清单
replicaset.apps/myapp-rs configured
[root@k8s7-22 ~]# kubectl get pods -l app=myapp,release=test-v1 --show-labels
NAME             READY   STATUS    RESTARTS   AGE   LABELS
myapp-rs-5vk9s   1/1     Running   0          9s    app=myapp,release=test-v1
myapp-rs-6sbvz   1/1     Running   0          9s    app=myapp,release=test-v1
myapp-rs-fqwdc   1/1     Running   0          9s    app=myapp,release=test-v1
myapp-rs-v26gm   1/1     Running   0          73m   app=myapp,release=test-v1
myapp-rs-xvp5z   1/1     Running   0          73m   app=myapp,release=test-v1

[root@k8s7-22 ~]# kubectl edit rs myapp-rs   # 动态修改资源配置

此时我们可以看到,我们集群中的Pod数量变成了5个,同理,我们也可以将集群Pod数量改回2,以此完成对集群的扩缩容。

除了对集群Pod的扩缩容外,我们还可以发布更新。比如我们要将我们的Pod镜像版本由V1升级为V2,我们可以直接编辑rs的配置清单:

代码语言:javascript
复制
[root@k8s7-22 ~]# kubectl edit rs myapp-rs
# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: extensions/v1beta1
kind: ReplicaSet
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"apps/v1","kind":"ReplicaSet","metadata":{"annotations":{},"name":"myapp-rs","namespace":"default"},"spec":{"replicas":5,"selector":{"matchLabels":{"app":"myapp","release":"test-v1"}},"template":{"metadata":{"labels":{"app":"myapp","release":"test-v1"},"name":"myapp"},"spec":{"containers":[{"image":"harbor.od.com/public/myapp:v1","name":"myapp-container"}]}}}}
  creationTimestamp: "2020-01-15T08:33:05Z"
  generation: 5
  labels:
    app: myapp
    release: test-v1
  name: myapp-rs
  namespace: default
  resourceVersion: "3349727"
  selfLink: /apis/extensions/v1beta1/namespaces/default/replicasets/myapp-rs
  uid: f161c8ee-7506-4461-a7a1-579f3a661e78
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
      release: test-v1
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: myapp
        release: test-v1
      name: myapp
    spec:
      containers:
      - image: harbor.od.com/public/myapp:v2    # 修改镜像版本为V2
        imagePullPolicy: IfNotPresent

此时,我们再来看下rs的详细信息以及pod内的版本:

代码语言:javascript
复制
[root@k8s7-22 ~]# kubectl get rs -o wide
NAME                  DESIRED   CURRENT   READY   AGE   CONTAINERS        IMAGES                          SELECTOR
myapp-rs              3         3         3       19h   myapp-container   harbor.od.com/public/myapp:v2   app=myapp,release=test-v1
[root@k8s7-22 ~]# kubectl get pods -o wide -l app=myapp,release=test-v1
NAME             READY   STATUS    RESTARTS   AGE   IP            NODE               NOMINATED NODE   READINESS GATES
myapp-rs-5vk9s   1/1     Running   0          18h   172.17.21.5   k8s7-21.host.com   <none>           <none>
myapp-rs-v26gm   1/1     Running   0          19h   172.17.22.4   k8s7-22.host.com   <none>           <none>
myapp-rs-xvp5z   1/1     Running   0          19h   172.17.21.3   k8s7-21.host.com   <none>           <none>
[root@k8s7-22 ~]# curl 172.17.21.5
abc
[root@k8s7-22 ~]# curl 172.17.22.4
abc
[root@k8s7-22 ~]# curl 172.17.21.3
abc

此时我们可以看到,我们的rs中镜像的版本已经变成了v2,但是我们的pod却没有被更新,依然是v1版本的内容。这是因为,我们更改了镜像的版本后,我们的ReplicaSet并不会去更新我们的Pod,而是当我们的Pod退出后,才会使用新版的镜像来重新创建,所以,我们来删掉其中一个Pod,让ReplicaSet为我们重建一个Pod,此时我们再来看新Pod中的应用版本:

代码语言:javascript
复制
[root@k8s7-22 ~]# kubectl delete pods myapp-rs-5vk9s
pod "myapp-rs-5vk9s" deleted
[root@k8s7-22 ~]# kubectl get pods -o wide -l app=myapp,release=test-v1
NAME             READY   STATUS    RESTARTS   AGE   IP            NODE               NOMINATED NODE   READINESS GATES
myapp-rs-8gmt4   1/1     Running   0          12s   172.17.21.4   k8s7-21.host.com   <none>           <none>
myapp-rs-v26gm   1/1     Running   0          19h   172.17.22.4   k8s7-22.host.com   <none>           <none>
myapp-rs-xvp5z   1/1     Running   0          19h   172.17.21.3   k8s7-21.host.com   <none>           <none>
[root@k8s7-22 ~]# curl 172.17.21.4
myapp | v2
[root@k8s7-22 ~]# curl 172.17.22.4
abc

此时我们就可以看到,当我们删除一个Pod后,我的ReplicaSet为我们自动重建了一个新的Pod,而此时新的Pod中就变成了V2版本,此时集群中新旧版本的Pod都存在,如果我们依次删除旧的Pod,就能实现灰度发布的效果,如果我们只删除部分Pod,就能实现金丝雀发布的效果。但是这些操作都是我们手动进行的,虽然便于控制更新频率,但是如果此时如果新版本有问题,我们想要快速回滚的话,依然需要改回旧版本,然后再手动删掉新Pod,这样就不是很方便了。

2、Deployment

Deployment类型的Pod控制器,其实并不会直接控制Pod,而是直接控制ReplicaSet,然后再通过ReplicaSet来控制Pod,这样,不同的ReplicaSet就会有不同的版本,如果需要回滚时,直接启用旧的ReplicaSet即可,这样就可以实现快速回滚的操作。接下来我们就创建一个Deployment类型的Pod控制器,其资源配置清单如下:

代码语言:javascript
复制
[root@k8s7-200 Deployment]# cat myapp-dp.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-dp
  namespace: default
spec:
  replicas: 2
  selector: 
    matchLabels:
      app: myapp-dp
      release: v1
  strategy:
    type: RollingUpdate
    rollingUpdate: 
      maxSurge: 10
      maxUnavailable: 0
  revisionHistoryLimit: 3
  paused: True
  template: 
    metadata: 
      name: myapp-dp
      labels:
        app: myapp-dp
        release: v1
    spec: 
      containers: 
      - name: myapp-dp-container
        image: harbor.od.com/public/myapp:v1
        imagePullPolicy: IfNotPresent
        ports: 
        - name: http
          containerPort: 80
        livenessProbe:
          httpGet: 
            port: http
            path: "/index.html"
          failureThreshold: 4
          periodSeconds: 20
          timeoutSeconds: 10
          initialDelaySeconds: 10
        readinessProbe:
          httpGet: 
            port: http
            path: "/index.html"
          failureThreshold: 4
          periodSeconds: 20
          timeoutSeconds: 10
          initialDelaySeconds: 10

我们可以看到,在Deployment的资源配置清单中,spec字段下除了有和ReplicaSet相同的replicas、selector、template字段外,还多了如下字段:

代码语言:javascript
复制
strategy:用于指定更新策略,其下级字段如下:
  type: 更新的类型,支持 Recreate及RollingUpdate,默认值是RollingUpdate
  rollingUpdate:滚动更新策略,当type值是RollingUpdate时此字段才生效,其有如下下级字段:
  maxSurge: 最多可以多创建Pod数量,可以是数字,也可以是百分比。
  maxUnavailable: 最多不可用的Pod数量,可以是数字,也可以是百分比。
  
  注意:maxSurge和maxUnavailable的值不能同时为0。

  revisionHistoryLimit:保留的历史版本数,默认是10,及保留10个旧版本
  paused: 暂停设置,当设置此项后,在K8S集群中新建Deployment时,只会先创建Deployment,其下并不会创建ReplicaSet以及Pod,此时可以用来编辑我们的Deployment,默认是False。如果创建Deployment完成后,再将paused改为True,则此时再更改其配置清单后不会自动更新Pod。

至于template的内容,与ReplicaSet是一样的,也不再赘述。现在,我们创建一下Deployment。

代码语言:javascript
复制
[root@k8s7-22 ~]# kubectl create -f http://k8s-yaml.od.com/Deployment/myapp-dp.yaml
deployment.apps/myapp-dp created
[root@k8s7-22 ~]# kubectl get rs
No resources found.
[root@k8s7-22 ~]# kubectl get pods 
No resources found.
[root@k8s7-22 ~]# kubectl get deploy
NAME       READY   UP-TO-DATE   AVAILABLE   AGE
myapp-dp   0/2     0            0           17s

此时我们可以看到,我们创建了Deployment,但是ReplicaSet和Pod都没有被创建,就是因为我们在资源配置清单中指定了paused为True,接下来,我们将此项配置删掉,然后再看下我们的Pod资源:

代码语言:javascript
复制
[root@k8s7-22 ~]# kubectl edit deploy myapp-dp
deployment.extensions/myapp-dp edited
[root@k8s7-22 ~]# kubectl get deploy
NAME       READY   UP-TO-DATE   AVAILABLE   AGE
myapp-dp   0/2     2            0           39s
[root@k8s7-22 ~]# kubectl get rs
NAME                  DESIRED   CURRENT   READY   AGE
myapp-dp-7f46b784cc   2         2         0       7s
[root@k8s7-22 ~]# kubectl get pods
NAME                        READY   STATUS    RESTARTS   AGE
myapp-dp-7f46b784cc-jtv52   0/1     Running   0          11s
myapp-dp-7f46b784cc-lbnwf   0/1     Running   0          11s
[root@k8s7-22 ~]# kubectl get pods
NAME                        READY   STATUS    RESTARTS   AGE
myapp-dp-7f46b784cc-jtv52   0/1     Running   0          23s
myapp-dp-7f46b784cc-lbnwf   1/1     Running   0          23s
[root@k8s7-22 ~]# kubectl get pods
NAME                        READY   STATUS    RESTARTS   AGE
myapp-dp-7f46b784cc-jtv52   0/1     Running   0          25s
myapp-dp-7f46b784cc-lbnwf   1/1     Running   0          25s
[root@k8s7-22 ~]# kubectl get pods
NAME                        READY   STATUS    RESTARTS   AGE
myapp-dp-7f46b784cc-jtv52   1/1     Running   0          29s
myapp-dp-7f46b784cc-lbnwf   1/1     Running   0          29s

当我们将myapp-dp的资源配置清单编辑完成后,删除了paused配置项,我们可以看到,Deployment立即继续执行了剩下的操作,创建了ReplicaSet,创建了Pod,因为我们的Pod设置了readinessProbe,所以直到可用性检测完成后,我们的Pod才标记为READY状态,此时,我们的Pod才可以对外提供服务,如果其前端有service资源的话,此时才能被加入service之中。

接下来,我们将我们的Pod升级到v2版本,我们来看下Deployment更新的过程,我们定义了更新策略是滚动更新,且最多可以多创建10个临时Pod用于升级,所以,我们直接用kubectl edit命令实时编辑其资源配置清单,此时就会立即触发更新操作:

代码语言:javascript
复制
[root@k8s7-22 ~]# kubectl edit deploy myapp-dp
deployment.extensions/myapp-dp edited
[root@k8s7-22 ~]# kubectl get pods -o wide
NAME                        READY   STATUS    RESTARTS   AGE     IP            NODE               NOMINATED NODE   READINESS GATES
myapp-dp-7f46b784cc-jtv52   1/1     Running   0          8m59s   172.17.22.2   k8s7-22.host.com   <none>           <none>
myapp-dp-7f46b784cc-lbnwf   1/1     Running   0          8m59s   172.17.21.3   k8s7-21.host.com   <none>           <none>
myapp-dp-b99666748-d8rhh    0/1     Running   0          12s     172.17.21.4   k8s7-21.host.com   <none>           <none>
myapp-dp-b99666748-kvt6w    0/1     Running   0          12s     172.17.22.3   k8s7-22.host.com   <none>           <none>
[root@k8s7-22 ~]# kubectl get rs -o wide
NAME                  DESIRED   CURRENT   READY   AGE     CONTAINERS           IMAGES                          SELECTOR
myapp-dp-7f46b784cc   0         0         0       9m12s   myapp-dp-container   harbor.od.com/public/myapp:v1   app=myapp-dp,pod-template-hash=7f46b784cc,release=v1
myapp-dp-b99666748    2         2         2       25s     myapp-dp-container   harbor.od.com/public/myapp:v2   app=myapp-dp,pod-template-hash=b99666748,release=v1
[root@k8s7-22 ~]# kubectl get pods -o wide
NAME                       READY   STATUS    RESTARTS   AGE   IP            NODE               NOMINATED NODE   READINESS GATES
myapp-dp-b99666748-d8rhh   1/1     Running   0          30s   172.17.21.4   k8s7-21.host.com   <none>           <none>
myapp-dp-b99666748-kvt6w   1/1     Running   0          30s   172.17.22.3   k8s7-22.host.com   <none>           <none>
[root@k8s7-22 ~]# curl 172.17.22.3
myapp | v2
[root@k8s7-22 ~]# curl 172.17.21.4
myapp | v2

我们看到,当我们编辑完成后,Deployment立即新建了两个新的Pod,而且这两个新的Pod是通过新的ReplicaSet创建的,而旧的ReplicaSet的期望副本数变成了0,此时我们还可以查看一下Deployment资源的更新版本号:

代码语言:javascript
复制
[root@k8s7-22 ~]# kubectl rollout history deploy myapp-dp
deployment.extensions/myapp-dp 
REVISION  CHANGE-CAUSE
1         <none>
2         <none>

此时,已经有两个版本了,通过kubectl rollout undo命令可以来回滚版本,例如,回退到上一个版本:

代码语言:javascript
复制
[root@k8s7-22 ~]# kubectl rollout undo deploy myapp-dp
deployment.extensions/myapp-dp rolled back
[root@k8s7-22 ~]# kubectl rollout history deploy myapp-dp
deployment.extensions/myapp-dp 
REVISION  CHANGE-CAUSE
2         <none>
3         <none>

[root@k8s7-22 ~]# kubectl get rs -o wide
NAME                  DESIRED   CURRENT   READY   AGE   CONTAINERS           IMAGES                          SELECTOR
myapp-dp-7f46b784cc   2         2         2       20m   myapp-dp-container   harbor.od.com/public/myapp:v1   app=myapp-dp,pod-template-hash=7f46b784cc,release=v1
myapp-dp-b99666748    0         0         0       11m   myapp-dp-container   harbor.od.com/public/myapp:v2   app=myapp-dp,pod-template-hash=b99666748,release=v1
[root@k8s7-22 ~]# kubectl get pods -o wide
NAME                        READY   STATUS    RESTARTS   AGE   IP            NODE               NOMINATED NODE   READINESS GATES
myapp-dp-7f46b784cc-jnq65   1/1     Running   0          54s   172.17.22.2   k8s7-22.host.com   <none>           <none>
myapp-dp-7f46b784cc-p4q6l   1/1     Running   0          54s   172.17.21.3   k8s7-21.host.com   <none>           <none>
[root@k8s7-22 ~]# curl 172.17.22.2
abc
[root@k8s7-22 ~]# curl 172.17.21.3
abc

除了能回退到上一个版本外,我们还可以回退到某个指定版本,只需要在回滚操作时加上参数 --to-revision=版本号 即可回滚到指定版本:

代码语言:javascript
复制
[root@k8s7-22 ~]# kubectl rollout undo deploy myapp-dp --to-revision=2
deployment.extensions/myapp-dp rolled back
[root@k8s7-22 ~]# kubectl get pods -o wide
NAME                       READY   STATUS    RESTARTS   AGE    IP            NODE               NOMINATED NODE   READINESS GATES
myapp-dp-b99666748-kxlxv   1/1     Running   0          63s    172.17.22.4   k8s7-22.host.com   <none>           <none>
myapp-dp-b99666748-nlp2z   1/1     Running   0          112s   172.17.22.2   k8s7-22.host.com   <none>           <none>
[root@k8s7-22 ~]# curl 172.17.22.4
myapp | v2
[root@k8s7-22 ~]# curl 172.17.22.2
myapp | v2

3、DeamonSet

DaemonSet型的Pod控制器,会在集群中所有的Node节点上启动且仅启动一个Pod,DaemonSet一般用于部署集群基础服务,例如监控、日志收集等等。我们来看一个DaemonSet的资源配置清单:

代码语言:javascript
复制
[root@k8s7-200 DaemonSet]# cat myapp-ds.yaml 
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: myapp-ds
  namespace: default
spec:
  selector:
    matchLabels:
      app: myapp-ds
      release: ds
  revisionHistoryLimit: 3
  updateStrategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1
  template:
    metadata:
      labels:
        app: myapp-ds
        release: ds
    spec:
      hostNetwork: True           # 使用Node节点的网络,此时Pod的IP地址会使用Node的地址,从集群中其他Node会直接访问到Pod的服务
      containers:
      - name: myapp-ds
        image: harbor.od.com/public/myapp:v1
        imagePullPolicy: IfNotPresent
        ports: 
        - name: http
          containerPort: 80
        livenessProbe:
          httpGet: 
            port: http
            path: "/index.html"
          failureThreshold: 4
          periodSeconds: 20
          timeoutSeconds: 10
          initialDelaySeconds: 10
        readinessProbe:
          httpGet: 
            port: http
            path: "/index.html"
          failureThreshold: 4
          periodSeconds: 20
          timeoutSeconds: 10
          initialDelaySeconds: 10

其实DaemonSet的资源配置清单和Deployment的资源配置清单字段差不多,只是DaemonSet需要在每个Node节点上部署一个Pod,所以不用再指定replicas字段了。DaemonSet也支持滚动更新,也可以定义滚动更新的策略,updateStrategy字段就是来定义更新策略的,其下级字段type可指定更新类型,支持 RollingUpdate 及 OnDelete,默认是RollingUpdate,当type选择RollingUpdate时,还可以设置rollingUpdate字段,其下级字段只有maxUnavailable可选,且这个字段不能为0,默认值是1。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-07-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 后场技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、Pod控制器类型
    • 1、ReplicaSet
      • 2、Deployment
        • 3、DeamonSet
        相关产品与服务
        容器服务
        腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档