前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >CODING 实践指南 | 基于 CVM 离线部署 Kubernetes

CODING 实践指南 | 基于 CVM 离线部署 Kubernetes

作者头像
腾讯云 CODING
发布2020-11-10 14:23:13
1.1K0
发布2020-11-10 14:23:13
举报
文章被收录于专栏:CODING DevOps

本文作者:CODING - 王宽

背景

在 CODING 独立制品库产品的某个私有化项目交付过程中,因为底层 TCE 环境中的 TKE 服务无法使用,而项目交付时间又非常紧急的情况下,我们团队经过讨论决定采用多台 CVM 主机人工部署一套 Kubernetes 集群环境,一方面用于验证在特定环境中交付部署还存在哪些阻碍,另一方面也希望向客户传达我们正在努力解决问题的工作态度。

困难与挑战

目标环境完全与互联网隔绝

客户的目标环境是一个 TCE 专有云平台,该云环境完全没有接入公网(出网/入网都没有),所以安装 Kubernetes 涉及到的工具、二进制可执行程序、依赖组件以及相关的镜像、服务都要做成能够离线部署的资源/物料,再通过现场同事带到机房环境中去拷贝。

目标环境的软件源服务无法使用

按正常情况 TCE 中包含了本地可用的软件源服务供 yum/apt 安装 Linux 组件、服务,但是实际情况是服务均无法使用,所以安装过程中涉及到的一些运行时、系统组件都只能预先在 rpm/deb 软件源仓库下载好,再打包到离线部署的物料中。

自建的 Kubernetes 与云平台不兼容

在我们的部署方案中,有状态的服务存储采用的是腾讯云 CBS、COS/CSP、TDSQL(MariaDB),即便是临时自建 Kubernetes 部署,还是希望能保持这样的永久存储架构。好处是当该环境中的 TKE 服务被修复后,我们只需要在 TKE 中重新部署一个实例,引用已经存在的 CBS、COS/CSP、TDSQL(MariaDB)实例就可以实现从自建 Kubernetes 的临时方案到基于 TKE 的生产方案间的无缝迁移。

基于 COS/CSP 的对象存储以及 TDSQL(MariaDB)都是在应用层通过网络访问的方式进行对接,不论是 TKE 还是自建 Kubernetes 互通协议都是一致的,唯独 CBS 需要通过 Kubernetes 中的 Persistent Volume 对象来挂载。在 TKE 中腾讯已经在底层做好了兼容和支持,而自建的 Kubernetes 默认并不兼容 CBS,需要对自建的 Kubernetes 进行一些改造才能兼容 CBS。

改造和兼容参考:https://github.com/TencentCloud/kubernetes-csi-tencentcloud/blob/master/docs/README_CBS_zhCN.md

好不容易找到上面的方案让自建的 Kubernetes 能够兼容 CBS 产品,在改造的过程中却发现 csi-controller.yaml 中的某个服务是通过引用腾讯云 SDK 去连接云平台(查询元数据/调用创建资源 API 等操作),而该 SDK 中对于云平台的接口终结点地址却是写死的腾讯公有云地址,如果要兼容私有云需要魔改镜像,蛋隐隐作疼。最后只能退而求其次使用 rancher 的 local-path-provisioner 来挂载存储。

规划资源

按照临时解决方案的情况,我规划了一个 VPC 网络(192.168.0.0/16),并需要在这个 VPC 网络中分别创建 3 台 CVM,其中 2 台用于部署 Kubernetes ,另外 1 台用做部署过程中需要使用的工作站,3 台 CVM 的配置如下:

类型

CPU

内存

硬盘

系统

IP地址

备注

CVM

1C

4GB

50GB

Ubuntu 16

192.168.100.8

工作站

CVM

4C

16GB

50GB

Ubuntu 16

192.168.100.10

Kubernetes Master

CVM

8C

32GB

50GB

Ubuntu 16

192.168.100.11

Kubernetes Worker Node

准备相关物料

准备 Docker 相关物料

https://packages.ubuntu.com/ 中搜索相关的 deb 软件包,并下载到本地,我为我目标环境中的 CVM 准备了以下文件:

文件名

下载地址

libseccomp2_2.4.3-1ubuntu3.16.04.3_amd64.deb

http://security.ubuntu.com/ubuntu/pool/main/libs/libseccomp/libseccomp2_2.4.3-1ubuntu3.16.04.3_amd64.deb

docker-ce-cli_19.03.9_3-0_ubuntu-xenial_amd64.deb

https://download.docker.com/linux/ubuntu/dists/xenial/pool/stable/amd64/docker-ce_19.03.9~3-0~ubuntu-xenial_amd64.deb

containerd.io_1.3.7-1_amd64.deb

https://download.docker.com/linux/ubuntu/dists/xenial/pool/stable/amd64/containerd.io_1.3.7-1_amd64.deb

docker-ce_19.03.9_3-0_ubuntu-xenial_amd64.deb

https://download.docker.com/linux/ubuntu/dists/xenial/pool/stable/amd64/docker-ce_19.03.9~3-0~ubuntu-xenial_amd64.deb

准备 kubeadmin 相关物料

参考了 https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/#installing-kubeadm-kubelet-and-kubectl 文档,我们需要安装 kubeletkubeadmkubectl,于是通过 https://apt.kubernetes.io 找到并下载相关的 debu 软件包。我为我目标环境中的 CVM 准备了以下文件:

文件名

下载地址

kubernetes-cni_0.8.7-00_amd64_ca2303ea0eecadf379c65bad855f9ad7c95c16502c0e7b3d50edcb53403c500f.deb

https://packages.cloud.google.com/apt/pool/kubernetes-cni_0.8.7-00_amd64_ca2303ea0eecadf379c65bad855f9ad7c95c16502c0e7b3d50edcb53403c500f.deb

socat_1.7.3.1-1_amd64.deb

http://kr.archive.ubuntu.com/ubuntu/pool/universe/s/socat/socat_1.7.3.1-1_amd64.deb

ebtables_2.0.10.4-3.4ubuntu1_amd64.deb

http://kr.archive.ubuntu.com/ubuntu/pool/main/e/ebtables/ebtables_2.0.10.4-3.4ubuntu1_amd64.deb

conntrack_1.4.3-3_amd64.deb

http://kr.archive.ubuntu.com/ubuntu/pool/main/c/conntrack-tools/conntrack_1.4.3-3_amd64.deb

cri-tools_1.13.0-01_amd64_4ff4588f5589826775f4a3bebd95aec5b9fb591ba8fb89a62845ffa8efe8cf22.deb

https://packages.cloud.google.com/apt/pool/cri-tools_1.13.0-01_amd64_4ff4588f5589826775f4a3bebd95aec5b9fb591ba8fb89a62845ffa8efe8cf22.deb

kubectl_1.19.3-00_amd64_8b135a2c8ab59784a12ae239cdfd80989d7d08d1da4449522c475cea1025eaa3.deb

https://packages.cloud.google.com/apt/pool/kubectl_1.19.3-00_amd64_8b135a2c8ab59784a12ae239cdfd80989d7d08d1da4449522c475cea1025eaa3.deb

kubelet_1.19.3-00_amd64_03a16e592ababd5fbd11dc5c90503f166340d0f87efd69315892151df20f0a6a.deb

https://packages.cloud.google.com/apt/pool/kubelet_1.19.3-00_amd64_03a16e592ababd5fbd11dc5c90503f166340d0f87efd69315892151df20f0a6a.deb

kubeadm_1.19.3-00_amd64_a46577b3dcf105747a609a72e4349d7efb51a059a005a1f77bea280b3d7b7724.deb

https://packages.cloud.google.com/apt/pool/kubeadm_1.19.3-00_amd64_a46577b3dcf105747a609a72e4349d7efb51a059a005a1f77bea280b3d7b7724.deb

准备 Kubernetes 需要的服务镜像

我们知道 Kubernetes 集群除了 kubelet 是作为系统服务的形式运行,其他的 etcdkube-apiserverkube-proxy 等服务都是通过自身 Pod 去运行的,而 Pod 创建后需要从某个镜像仓库中拉取相应的镜像,此时因为没有访问公网的条件,我们就需要在目标环境的本地(工作站)运行一个准备好相应镜像资源的私有镜像仓库。

代码语言:javascript
复制
$ kubeadm config images list

k8s.gcr.io/kube-apiserver:v1.19.3
k8s.gcr.io/kube-controller-manager:v1.19.3
k8s.gcr.io/kube-scheduler:v1.19.3
k8s.gcr.io/kube-proxy:v1.19.3
k8s.gcr.io/pause:3.2
k8s.gcr.io/etcd:3.4.13-0
k8s.gcr.io/coredns:1.7.0

通过 kubeadm config images list 我们可以查看到执行初始化时,依赖以上的资源。

代码语言:javascript
复制
docker run -d -p 5000:5000 --name offline-image-provider registry:2
docker pull k8s.gcr.io/kube-apiserver:v1.19.3
docker tag k8s.gcr.io/kube-apiserver:v1.19.3 127.0.0.1:5000/kube-apiserver:v1.19.3
docker push 127.0.0.1:5000/kube-apiserver:v1.19.3
...
docker stop offline-image-provider
docker commit offline-image-provider offline-image-provider
docker rm offline-image-provider
docker save -o ~/offline-image-provider.tar offline-image-provider

通过以上脚本,我们可以在本地运行一个私有的镜像仓库,然后从 k8s.gcr.io 的仓库中拉取相关镜像,再推送到本地的私有仓库中,最后将这个私有仓库作为镜像保存成 tar 归档文件。但是因为需要进行多次 push 操作,而每一次对 offline-image-provider 的操作都会生成 docker layer,导致最后导出的 tar 归档文件过大,可以通过以下基于本地文件挂载、Docker Build 的方案来制作私有镜像仓库的离线物料:

代码语言:javascript
复制
FROM registry:2
COPY registry /var/lib/registry
代码语言:javascript
复制
docker run -d -p 5000:5000 -v ./registry:/var/lib/registry --name offline-image-provider offline-image-provider
docker pull k8s.gcr.io/kube-apiserver:v1.19.3
docker tag k8s.gcr.io/kube-apiserver:v1.19.3 127.0.0.1:5000/kube-apiserver:v1.19.3
docker push 127.0.0.1:5000/kube-apiserver:v1.19.3
...
docker stop offline-image-provider
docker rm offline-image-provider
docker build -t offline-image-provider .
docker save -o ~/offline-image-provider.tar offline-image-provider

在本节中我们需要准备以下物料:

文件名

备注

offline-image-provider.tar

私有镜像仓库镜像归档文件

准备 Pod network add-on 相关镜像

及部署清单文件

当我们通过 kubeadm 初始化一个集群后,我们还需要为 Kubernetes 集群准备一个网络插件用于实现 Container Network Interface(CNI),参考自:

https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/create-cluster-kubeadm/#pod-network

在资源规划中我们规划了这个集群只有一个工作节点,所以我们选择 Calico 的小于 50 个工作节点方案:

https://docs.projectcalico.org/getting-started/kubernetes/self-managed-onprem/onpremises#install-calico-with-kubernetes-api-datastore-50-nodes-or-less

首先需要下载 calico.yaml 文件,从这个文件中我们可以发现配置 calico 需要使用到一些镜像,这些镜像也要在办公网络拉取好相应的镜像并推送和打包到 offline-image-provider.tar 物料中(参考上一节),同时我们还需要编辑修改 calico.yaml 镜像仓库的地址,变更为即将在工作站中运行的私有镜像仓库地址,例如:

原镜像

变更为

calico/cni:v3.16.3

192.168.100.8:5000/calico/cni:v3.16.3

calico/pod2daemon-flexvol:v3.16.3

192.168.100.8:5000/calico/pod2daemon-flexvol:v3.16.3

calico/node:v3.16.3

192.168.100.8:5000/calico/node:v3.16.3

calico/kube-controllers:v3.16.3

192.168.100.8:5000/calico/kube-controllers:v3.16.3

在本节中我们需要准备以下物料:

文件名

备注

calico.yaml

Calico 网络插件部署清单文件

准备 Kubernetes csi tencentcloud 相关镜像

及部署清单文件

自建的 Kubernetes 与云平台不兼容 中提到的情况,我们需要借助于 kubernetes-csi-tencentcloud 来为自建的 Kubernetes 提供兼容 CBS 服务的能力。

参考自:

https://github.com/TencentCloud/kubernetes-csi-tencentcloud/blob/master/docs/README_CBS_zhCN.md

从文档中得知,我们需要准备以下部署清单文件:

文件名

secret.yaml

csi-controller-rbac.yaml

csi-node-rbac.yaml

csi-controller.yaml

csi-node.yaml

snapshot-crd.yaml

并且从这些清单文件中得知,部署依赖以下镜像,和前面步骤一样,这些镜像也需要打包到 offline-image-provider.tar 物料中,同时需要修改部署清淡文件中对镜像的引用地址:

原镜像

变更为

quay.io/k8scsi/csi-provisioner:v1.6.0

192.168.100.8:5000/k8scsi/csi-provisioner:v1.6.0

quay.io/k8scsi/csi-attacher:v2.2.0

192.168.100.8:5000/k8scsi/csi-attacher:v2.2.0

quay.io/k8scsi/csi-snapshotter:v2.1.0

192.168.100.8:5000/k8scsi/csi-snapshotter:v2.1.0

quay.io/k8scsi/snapshot-controller:v2.1.0

192.168.100.8:5000/k8scsi/snapshot-controller:v2.1.0

quay.io/k8scsi/csi-resizer:v0.5.0

192.168.100.8:5000/k8scsi/csi-resizer:v0.5.0

ccr.ccs.tencentyun.com/k8scsi/csi-tencentcloud-cbs:v1.2.0

192.168.100.8:5000/k8scsi/csi-tencentcloud-cbs:v1.2.0

quay.io/k8scsi/csi-node-driver-registrar:v1.2.0

192.168.100.8:5000/k8scsi/csi-node-driver-registrar:v1.2.0

ccr.ccs.tencentyun.com/k8scsi/csi-tencentcloud-cbs:v1.2.0

192.168.100.8:5000/k8scsi/csi-tencentcloud-cbs:v1.2.0

准备 Local path provisioner 相关镜像

及部署清单文件

自建的 Kubernetes 与云平台不兼容 中提到的,改造 Kubernetes 兼容 CBS 的过程中遇到了需要魔改镜像的问题,出于时间考虑,我们临时决定还是采用 Local path provisioner 方案。

参考自:

https://github.com/rancher/local-path-provisioner

从文档中得知,我们需要准备以下镜像,这些镜像需要打包到 offline-image-provider.tar 物料中。

原镜像

变更为

rancher/local-path-provisioner:v0.0.18

192.168.100.8:5000/rancher/local-path-provisioner:v0.0.18

busybox

192.168.100.8:5000/busybox

在本节中我们需要准备以下物料:

文件名

备注

local-path-storage.yaml

Local path provisioner 部署清单文件

部署

上传物料

将我们准备好的物料上传至 工作站 中,之后可以通过 scp 将相关物料文件从工作站传输到 Kubernetes Master、Kubernetes Worker Node 中。

安装 docker

通过以下脚本,分别在工作站、Kubernetes Master、Kubernetes Worker Node 中安装 docker 引擎:

代码语言:javascript
复制
sudo dpkg -i libseccomp2_2.4.3-1ubuntu3.16.04.3_amd64.deb
sudo dpkg -i docker-ce-cli_19.03.9_3-0_ubuntu-xenial_amd64.deb
sudo dpkg -i containerd.io_1.3.7-1_amd64.deb
sudo dpkg -i docker-ce_19.03.9_3-0_ubuntu-xenial_amd64.deb

安装 kubeadmin

通过以下脚本,分别在 Kubernetes Master、Kubernetes Worker Node 中安装 docker 引擎:

代码语言:javascript
复制
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
sudo sysctl --system

sudo dpkg -i kubernetes-cni_0.8.7-00_amd64_ca2303ea0eecadf379c65bad855f9ad7c95c16502c0e7b3d50edcb53403c500f.deb
sudo dpkg -i socat_1.7.3.1-1_amd64.deb
sudo dpkg -i ebtables_2.0.10.4-3.4ubuntu1_amd64.deb
sudo dpkg -i conntrack_1.4.3-3_amd64.deb
sudo dpkg -i cri-tools_1.13.0-01_amd64_4ff4588f5589826775f4a3bebd95aec5b9fb591ba8fb89a62845ffa8efe8cf22.deb
sudo dpkg -i kubectl_1.19.3-00_amd64_8b135a2c8ab59784a12ae239cdfd80989d7d08d1da4449522c475cea1025eaa3.deb
sudo dpkg -i kubelet_1.19.3-00_amd64_03a16e592ababd5fbd11dc5c90503f166340d0f87efd69315892151df20f0a6a.deb
sudo dpkg -i kubeadm_1.19.3-00_amd64_a46577b3dcf105747a609a72e4349d7efb51a059a005a1f77bea280b3d7b7724.deb

sudo apt-mark hold kubelet kubeadm kubectl

在工作站中运行私有镜像仓库服务

在工作站中通过以下脚本启动我们准备好的镜像仓库服务,并监听 5000 端口:

代码语言:javascript
复制
docker load -i offline-image-provider.tar
docker run -d -p 5000:5000 --name offline-image-provider registry:2

初始化 kubernetes 集群

在 Kubernetes Master 中通过以下命令初始化一个 Kubernetes 集群实例:

代码语言:javascript
复制
kubeadm init --image-repository=192.168.100.8:5000

之后,你将看到类似以下输出结果:

代码语言:javascript
复制
[init] Using Kubernetes version: v1.19.3
[preflight] Running pre-flight checks
[preflight] Pulling images required for setting up a Kubernetes cluster
[preflight] This might take a minute or two, depending on the speed of your internet connection
[preflight] You can also perform this action in beforehand using 'kubeadm config images pull'
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Activating the kubelet service
[certs] Using certificateDir folder "/etc/kubernetes/pki"
[certs] Generating "etcd/ca" certificate and key
[certs] Generating "etcd/server" certificate and key
[certs] etcd/server serving cert is signed for DNS names [kubeadm-cp localhost] and IPs [10.138.0.4 127.0.0.1 ::1]
[certs] Generating "etcd/healthcheck-client" certificate and key
[certs] Generating "etcd/peer" certificate and key
[certs] etcd/peer serving cert is signed for DNS names [kubeadm-cp localhost] and IPs [10.138.0.4 127.0.0.1 ::1]
[certs] Generating "apiserver-etcd-client" certificate and key
[certs] Generating "ca" certificate and key
[certs] Generating "apiserver" certificate and key
[certs] apiserver serving cert is signed for DNS names [kubeadm-cp kubernetes kubernetes.default kubernetes.default.svc kubernetes.default.svc.cluster.local] and IPs [10.96.0.1 10.138.0.4]
[certs] Generating "apiserver-kubelet-client" certificate and key
[certs] Generating "front-proxy-ca" certificate and key
[certs] Generating "front-proxy-client" certificate and key
[certs] Generating "sa" key and public key
[kubeconfig] Using kubeconfig folder "/etc/kubernetes"
[kubeconfig] Writing "admin.conf" kubeconfig file
[kubeconfig] Writing "kubelet.conf" kubeconfig file
[kubeconfig] Writing "controller-manager.conf" kubeconfig file
[kubeconfig] Writing "scheduler.conf" kubeconfig file
[control-plane] Using manifest folder "/etc/kubernetes/manifests"
[control-plane] Creating static Pod manifest for "kube-apiserver"
[control-plane] Creating static Pod manifest for "kube-controller-manager"
[control-plane] Creating static Pod manifest for "kube-scheduler"
[etcd] Creating static Pod manifest for local etcd in "/etc/kubernetes/manifests"
[wait-control-plane] Waiting for the kubelet to boot up the control plane as static Pods from directory "/etc/kubernetes/manifests". This can take up to 4m0s
[apiclient] All control plane components are healthy after 31.501735 seconds
[uploadconfig] storing the configuration used in ConfigMap "kubeadm-config" in the "kube-system" Namespace
[kubelet] Creating a ConfigMap "kubelet-config-X.Y" in namespace kube-system with the configuration for the kubelets in the cluster
[patchnode] Uploading the CRI Socket information "/var/run/dockershim.sock" to the Node API object "kubeadm-cp" as an annotation
[mark-control-plane] Marking the node kubeadm-cp as control-plane by adding the label "node-role.kubernetes.io/master=''"
[mark-control-plane] Marking the node kubeadm-cp as control-plane by adding the taints [node-role.kubernetes.io/master:NoSchedule]
[bootstrap-token] Using token: <token>
[bootstrap-token] Configuring bootstrap tokens, cluster-info ConfigMap, RBAC Roles
[bootstraptoken] configured RBAC rules to allow Node Bootstrap tokens to post CSRs in order for nodes to get long term certificate credentials
[bootstraptoken] configured RBAC rules to allow the csrapprover controller automatically approve CSRs from a Node Bootstrap Token
[bootstraptoken] configured RBAC rules to allow certificate rotation for all node client certificates in the cluster
[bootstraptoken] creating the "cluster-info" ConfigMap in the "kube-public" namespace
[addons] Applied essential addon: CoreDNS
[addons] Applied essential addon: kube-proxy

Your Kubernetes control-plane has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config

You should now deploy a Pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
  /docs/concepts/cluster-administration/addons/

You can now join any number of machines by running the following on each node
as root:

  kubeadm join <control-plane-host>:<control-plane-port> --token <token> --discovery-token-ca-cert-hash sha256:<hash>

根据提示运行以下脚本让普通用户使用 kubectl 管理集群:

代码语言:javascript
复制
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

记录下提示中的 kubeadm join xxx 脚本,该脚本用于 Worker Node 加入集群时使用。

部署 Calico

在 Kubernetes Master 中通过以下脚本部署 Calico:

代码语言:javascript
复制
kubectl apply -f calico.yaml

等待数分钟后,你可以通过 kubectl get pod -n kube-system 查看 Kubernetes Pod 状态 。

将工作节点加入到集群中

在 Kubernetes Worker Node 中运行之前记录的 kubeadm join xxx 脚本,等待数分钟后,你可以在 Kubernetes Master 中通过 kubectl get node 查看到工作节点已经加入到了集群中。

部署 Local path provisioner

在 Kubernetes Master 中通过以下脚本部署 Local path provisioner:

代码语言:javascript
复制
kubectl apply -f local-path-storage.yaml

结语

通过以上步骤,我们已经成功在没有公网的环境基于自建虚拟机,自备离线物料的方式部署了一个标准版 Kubernetes 集群,并且通过指定 storageClassNamelocal-path 可以基于工作节点的本地存储创建 PersistentVolume。基于以上自备物料的方法,还可以将任意的服务部署到这个 Kubernetes 集群内。

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

本文分享自 腾云 CODING 微信公众号,前往查看

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 目标环境完全与互联网隔绝
  • 自建的 Kubernetes 与云平台不兼容
  • 准备相关物料
相关产品与服务
云服务器
云服务器(Cloud Virtual Machine,CVM)提供安全可靠的弹性计算服务。 您可以实时扩展或缩减计算资源,适应变化的业务需求,并只需按实际使用的资源计费。使用 CVM 可以极大降低您的软硬件采购成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档