K8s集群架构到Deployment滚动更新与Pod配额排障课件

发布时间:2026/10/24 11:55:17
K8s集群架构到Deployment滚动更新与Pod配额排障课件

简介:面向 Kubernetes 初学者与云原生运维人员的这套课件,围绕集群架构、核心对象和日常管理展开,帮助建立系统化的 k8s 知识框架。资源包为 1 个 pptx 文件,约 1.25MB,篇幅集中,便于按章节浏览和课堂演示。内容先说明 apiserver、kubelet、kube-proxy 等组件职责,再梳理 Pod、Controllers、Service、Label、Namespace 等对象。结合 kubectl 命令与 YAML 清单,展示集群状态查看、资源监控、日志排查,并串联 Deployment 的创建、发布、升级、扩缩容和回滚流程,同时延伸调度、存储与集群安全。目前已有 2191 人学习,适合入门梳理与面试前复盘。

1. 一份把 K8s 从概念讲到配额边界的课件,适合谁啃

刚拿到 kubeconfig 的人,最常遇到的问题不是 YAML 缩进写错,而是 Pod 起不来时只会反复kubectl get pods刷新,说不清 apiserver、etcd、kubelet 各自卡在链路哪一段。这份 K8s 课件针对的正是这个薄弱点:从集群架构与组件职责入手,接着是监控与日志、应用生命周期管理、调度、存储、集群安全,六块内容按「先知道谁在干活,再知道怎么让它干活」的顺序排布。它不适合已经能顺手写 Operator 的人,但对刚接手集群、需要独立把一个 Deployment 从创建推到升级再回滚的运维和后端更合适。下面我按自己复盘的顺序拆开,重心放在命令背后的参数含义和几个容易出错的边界上。

2. 集群架构拆开看:apiserver、etcd、kubelet 各自守哪一段

2.1 控制面四个组件的职责边界

很多人把控制面当成一个整体,其实它内部的分工很明确,边界一旦搞混,排查方向就会偏。kube-apiserver 是集群唯一入口,所有组件彼此不直接通信,只跟 apiserver 通信;apiserver 也是唯一能读写 etcd 的进程。一个请求进来要过三道关:认证(证书、Token 或 ServiceAccount)、鉴权(RBAC 规则)、准入控制(各种 Admission 插件),全过了才落盘。这套设计的好处是审计点集中,坏处是 apiserver 一旦不可用,kubectl全线报错,但已经在跑的 Pod 不受影响,因为 kubelet 还在按本地缓存的期望状态干活。

kube-controller-manager 是一堆控制器的集合体,Deployment、ReplicaSet、Node、Endpoint、ServiceAccount 各有一个控制器在跑。每个控制器本质是个死循环:读期望状态(spec),读实际状态(status),算差值,发指令。这里有个容易被忽略的嵌套关系,Deployment 控制器并不直接管 Pod,它管的是 ReplicaSet,ReplicaSet 再管 Pod。正因为老版本的 ReplicaSet 会被保留并缩到 0 而不是删掉,回滚才有历史可退。

kube-scheduler 只做一件事:给新创建、还没绑定节点的 Pod 选一个 Node,然后写一个 Binding 对象把spec.nodeName填上。它不负责把容器启动起来,启动是 kubelet 的活。调度分两阶段,Filter 先过滤掉不满足条件的节点(资源不够、污点不匹配、端口冲突、亲和性不符),Score 再对剩下的节点打分。所以看到 Pod 处于 Pending 且 Events 里写着FailedScheduling,问题一定在调度器这一侧,不用去翻容器日志。

etcd 是分布式键值存储,默认监听 2379/2380,正常配置下只允许 apiserver 访问。集群里所有对象状态都在它里面,某种意义上删掉 etcd 数据目录等于删掉整个集群。

组件位置核心职责挂掉后的典型表现
kube-apiserver控制面唯一入口,认证鉴权准入,读写 etcdkubectl 全部报错,已运行 Pod 不受影响
kube-controller-manager控制面控制器循环,把实际状态拉向期望状态副本数不收敛,Service 没有 Endpoints
kube-scheduler控制面为新 Pod 选择节点并绑定新 Pod 一直 Pending
etcd控制面保存全部集群状态apiserver 起不来,集群事实性丢失
kubelet节点管理本机 Pod 生命周期该节点上的 Pod 状态不再更新
kube-proxy节点维护 Service 的四层转发规则Service 通但后端访问不到

2.2 节点侧:kubelet 和 kube-proxy 不是一回事

kubelet 是节点上的代理,它做的是:向 apiserver 注册节点、watch 到绑定给自己的 Pod、通过 CRI 调用容器运行时创建容器、挂载数据卷、拉取 Secret 和 ConfigMap、执行存活与就绪探针、定时上报节点和 Pod 状态。kubelet 把每个 Pod 翻译成一组容器操作,这个翻译过程出错,Pod 就卡在 ContainerCreating 或者一直重启。

kube-proxy 完全不参与容器创建,它 watch Service 和 Endpoints 的变化,在节点上写 iptables 或 ipvs 规则,实现四层负载均衡。区分方法很简单:Pod 起不来,看 kubelet 和容器运行时;Pod 起来了但 Service 访问不通,先看 Endpoints 有没有后端,再看 kube-proxy 的规则。

2.3 用几条命令把组件状态验证一遍

课件里给了kubectl cluster-info,但单靠这一条信息量太少。我一般按下面这个顺序过一遍,每条命令看什么、返回什么算正常,心里要有数。

# 集群入口信息:apiserver 地址和集群 DNS 服务地址 kubectl cluster-info # 控制面组件是否都是 Running,以及各自落在哪个节点 kubectl get pods -n kube-system -o wide # 直接打 apiserver 的健康端点,返回 ok 说明 apiserver 自身活着 kubectl get --raw /healthz # 看单个节点的容量、已分配资源、污点和最近事件 kubectl describe node <node-name>

第一条命令的返回值里如果 DNS 那行是10.96.0.10:53这类地址,说明集群服务网段正常。第二条要重点看kube-controller-manager、kube-scheduler、etcd这几个静态 Pod 的 RESTARTS 次数,短时间反复重启通常意味着证书过期或者磁盘压力。第三条--raw是直接向 apiserver 发原始请求,绕开 kubectl 的客户端逻辑,用来判断「是 apiserver 挂了还是 kubectl 配置错了」特别快。

注意,网上很多老教程会写kubectl get componentstatuses,这个接口在较新版本里已经废弃,输出的警告比内容还长。用kubectl get --raw /healthz配合kubectl get pods -n kube-system替代更靠谱,也不用记一堆缩写。

2.4 etcd 快照:唯一的后悔药

集群层面最该养成的习惯是给 etcd 做快照。误删一个 Namespace 会连带删掉里面所有对象,控制器不会帮你恢复,因为对它来说那就是期望状态变了。

# 在控制面节点执行,证书路径按实际安装位置修改 ETCDCTL_API=3 etcdctl \ --endpoints=https://127.0.0.1:2379 \ --cacert=/etc/kubernetes/pki/etcd/ca.crt \ --cert=/etc/kubernetes/pki/etcd/server.crt \ --key=/etc/kubernetes/pki/etcd/server.key \ snapshot save /var/backups/etcd-$(date +%F-%H%M).db # 校验快照完整性,输出里 hash 和 revision 都要有值 etcdctl snapshot status /var/backups/etcd-20240101-1200.db --write-out=table

--endpoints指向本机 etcd 客户端端口,三个证书分别对应 CA、客户端证书和私钥,缺一个都会报context deadline exceeded,这个报错很有迷惑性,看起来像网络不通,实际多数是证书路径写错。snapshot save是热备份,不需要停 etcd,但恢复时必须停掉 apiserver 和 etcd 再执行snapshot restore,恢复动作不要在业务时段做。备份文件放本地磁盘意义有限,节点挂了就一起没了,我一般会再同步到集群外的存储。

3. 从 kubectl 到 YAML:把一个 Deployment 推到回滚的完整闭环

3.1 命令式创建:先用两条命令跑通

初学阶段别急着写 YAML,先用命令式把对象关系跑通,看到 Deployment、ReplicaSet、Pod 三层是怎么长出来的,比背字段有用得多。

# 创建一个 Deployment,镜像用内网仓库地址 kubectl create deployment web --image=registry.local/demo/java-web:1.0 # 查看 Deployment 和 Pod,-o wide 能看到 Pod 落在哪个节点 kubectl get deploy,pods -o wide # 暴露服务:Service 80 端口转发到容器 8080 kubectl expose deployment web \ --port=80 \ --target-port=8080 \ --type=NodePort \ --name=web kubectl get svc web

--port是 Service 自身监听的端口,集群内其他 Pod 通过web:80访问;--target-port是容器实际监听的端口,写错这个是最常见的 502 来源;--type=NodePort会在 30000–32767 区间随机分配一个节点端口,--name不写的话默认与 Deployment 同名。

创建完立刻kubectl get rs会看到一个名字形如web-7d9f8c6b5的 ReplicaSet,Pod 名字再在后面加随机后缀。这个三层结构不是冗余设计:Deployment 负责版本和滚动策略,ReplicaSet 负责某一版本的副本数,Pod 才是真正被调度的单元。理解了这一层,后面rollout history为什么能列出多条记录就顺理成章了。

3.2 YAML 逐字段拆:apiVersion 到 containers

命令式适合验证,生产必须落到 YAML,否则改一个副本数都要记参数。下面这份是课件里 Deployment 的加厚版,把滚动更新策略和资源配额一起带上。

apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment namespace: default labels: app: nginx spec: replicas: 3 selector: matchLabels: app: nginx # 必须与 template.metadata.labels 完全匹配 strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 # 滚动时最多超出期望副本数 1 个 maxUnavailable: 0 # 滚动时不允许有副本不可用 revisionHistoryLimit: 10 template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.25 imagePullPolicy: IfNotPresent ports: - containerPort: 80 resources: requests: cpu: 250m memory: 64Mi limits: cpu: 500m memory: 128Mi

apiVersion和kind是配对的,写错组合会直接报no matches for kind,不确定时用kubectl api-resources | grep deployment查当前集群支持哪个版本。selector.matchLabels与template.metadata.labels不匹配时 apply 会被拒绝,报错信息是selector does not match template labels,这条报错很直白,照着改就行。replicas是期望副本数,但控制器实际收敛需要几秒,刚 apply 完立刻查可能只有 1 个 Pod,属于正常现象。revisionHistoryLimit默认就是 10,意味着最多保留 10 个历史 ReplicaSet,调小能省 etcd 空间,但会让可回滚的版本变少。

3.3 升级、扩缩容与回滚的操作序列

# 升级镜像,改的是 Pod 模板,会触发滚动更新 kubectl set image deployment/web nginx=nginx:1.25 # 阻塞等待滚动完成,超时退出码非 0,适合放进脚本 kubectl rollout status deployment/web --timeout=120s # 查看历史版本,REVISION 列就是回滚时要用的编号 kubectl rollout history deployment/web # 回到指定版本 kubectl rollout undo deployment/web --to-revision=2 # 手动扩缩容 kubectl scale deployment web --replicas=10 # 配置自动扩缩容,CPU 超过 80% 时最多扩到 15 个 kubectl autoscale deployment web --min=10 --max=15 --cpu-percent=80

rollout status是我最推荐放进 CI 的一条命令,它比sleep 30靠谱得多,滚动失败时会立刻返回而不是傻等。--to-revision有一个容易踩的点:回滚本身会产生一个新的 revision 编号,比如当前是 3,回滚到 2 之后,历史里会多出一个 4,内容与 2 一致。所以脚本里写死 revision 号迟早出问题,更稳的做法是记镜像 tag。

scale和autoscale同时存在会打架。HPA 接管之后会周期性改写spec.replicas,手动 scale 上去的数字过一会儿就被覆盖回来,排查时看到副本数自己变,先查kubectl get hpa有没有对应的规则。

3.4 滚动更新参数:maxSurge 与 maxUnavailable 的实际影响

这两个参数决定了滚动更新的节奏,也是「升级时服务到底会不会抖」的直接原因。

maxSurge: 1加maxUnavailable: 0是最保守的组合:先起一个新 Pod,等它就绪再停一个旧的,全程副本数只多不少。代价是需要集群有富余资源,如果节点资源刚好卡满,新 Pod 调度不上去,滚动就会永远停在中间状态。maxSurge: 0加maxUnavailable: 1则相反,先停旧的再起新的,省资源但会短暂少一个副本,对流量敏感的服务不要这么配。两者不能同时为 0,否则滚动无法推进,apply 时会直接被拒。

滚动卡住的排查路径很固定:先kubectl rollout status看卡在哪一步,再kubectl describe rs <新 RS 名>看 Events。如果是FailedScheduling,就是资源或亲和性问题;如果是Readiness probe failed,就是应用起来了但没准备好接收流量,这时候要去查就绪探针的路径和端口,而不是怀疑镜像。

4. Pod 为什么不是容器的简单包装:infra container、拉取策略与配额

4.1 共享网络是怎么做到的

Pod 是 K8s 的最小调度单元,但它本身是个逻辑概念,节点上并没有一个叫 Pod 的东西。实现方式是每个 Pod 先起一个极小的 infra container(也叫 pause 容器),由它持有网络命名空间,然后把业务容器 join 进这个命名空间。于是同一 Pod 内的容器看到的网卡、IP、路由表完全一致,可以直接用127.0.0.1通信。

这解释了几个日常现象:同 Pod 内容器不能监听同一个端口,因为共享同一张网卡;nginx 加 php-fpm 这种组合放同一个 Pod 里,nginx 直接fastcgi_pass 127.0.0.1:9000就行,不用配 Service;Pod 重建后 IP 会变,因为它拿到的是新的网络命名空间。

存储共享走的是另一条路,靠 volume 挂载而不是命名空间。多个容器把同一个 emptyDir 挂到各自路径下,就能看到同样的文件。课件里那个 nginx 日志的例子就是这个思路,把容器内的日志目录挂到宿主机,方便采集。

apiVersion: v1 kind: Pod metadata: name: my-pod spec: containers: - name: web image: registry.local/demo/nginx-php:1.0 volumeMounts: - name: logs mountPath: /usr/local/nginx/logs volumes: - name: logs hostPath: path: /tmp/logs type: DirectoryOrCreate

type: DirectoryOrCreate比Directory安全,后者要求宿主机目录必须已存在,不存在时 Pod 会卡在 ContainerCreating 并报FailedMount。这一点在批量部署时格外明显,十台节点里只要有一台没建目录,那台机器上的 Pod 就永远起不来。

4.2 imagePullPolicy 三个值与 :latest 的隐式规则

三个取值的含义分别是:IfNotPresent本地没有才拉,Always每次创建都拉,Never只用手动导入的镜像。

真正需要记住的是默认值规则,它跟标签有关而不是固定的:当镜像标签是latest或者干脆不写标签时,默认是Always;写了具体标签时,默认是IfNotPresent。所以生产环境用latest有两个隐患,一是每次 Pod 重建都要打仓库,仓库抖动就直接ImagePullBackOff,二是同一个标签对应的镜像内容可能已经变了,回滚时你以为回到了旧版本,实际拉的是新内容。

我的做法是标签写死到小版本,重要服务用 digest 固定,形如nginx@sha256:...,这样镜像内容不可变,回滚才有确定性。

4.3 requests 与 limits:CPU 单位、OOMKilled 与 QoS 分类

这两个字段经常被混着写,其实职责完全不同。requests是调度依据,调度器按它计算节点还剩多少可分配资源;limits是运行时上限,由容器运行时强制执行。

CPU 超过 limits 会被节流,表现为响应变慢但进程还在;内存超过 limits 会直接触发 OOMKilled,容器被杀掉重启。这个差异决定了排查方向:CPU 问题看监控里的 throttling 指标,内存问题看kubectl describe pod里的Last State: Terminated, Reason: OOMKilled。

单位方面,CPU 的m是千分之一核,250m就是 0.25 核,1000m等于 1。内存的Mi、Gi是二进制单位(1024 进制),M、G是十进制,两者差 4.8% 左右,配额算得紧的时候这点差异会变成事故。

# 查看 Pod 的 QoS 等级 kubectl get pod web-7d9f8c6b5-abcde -o jsonpath='{.status.qosClass}' # 查看容器上次退出的原因,OOMKilled 会在这里体现 kubectl describe pod web-7d9f8c6b5-abcde | grep -A3 "Last State"

QoS 分三档:所有容器都设置了 requests 且等于 limits 是 Guaranteed;部分设置是 Burstable;一个都没设是 BestEffort。节点资源紧张触发驱逐时,顺序是 BestEffort 优先被赶走,Burstable 次之,Guaranteed 最后。所以核心服务把 requests 和 limits 设成相等,不只是为了稳定,也是为了在驱逐排序里靠后。

不设 requests 的后果比想象中严重。调度器会认为这个 Pod 不需要资源,把它塞到已经快满的节点上,实际跑起来内存一涨,节点整体进入压力状态,最后被驱逐的可能是隔壁那个规规矩矩设了配额的服务。这就是为什么我坚持新服务上线前先把 requests 填上,哪怕估值不准,也比空着强。

4.4 私有仓库拉取:imagePullSecrets 的创建与挂载

内网仓库基本都要认证,报错表现为ErrImagePull或ImagePullBackOff,describe的 Events 里会明确写no basic auth credentials或者unauthorized。

# 创建 docker-registry 类型的 Secret kubectl create secret docker-registry regcred \ --docker-server=registry.local \ --docker-username=devops \ --docker-password='<密码>' \ --docker-email=dev@example.com # 挂到命名空间的默认 ServiceAccount 上,该空间内 Pod 自动继承 kubectl patch serviceaccount default \ -p '{"imagePullSecrets":[{"name":"regcred"}]}'

挂载位置有两种选择。写进 Pod 的spec.imagePullSecrets只对当前 Pod 生效,适合跨团队共享集群但仓库权限不同的场景;patch 到 ServiceAccount 上则整个命名空间生效,省事但权限范围大。密码里有特殊字符时记得用单引号包住,$和!在双引号里会被 shell 展开,这个坑我在不同环境里见过好几次。

5. 监控与日志排查:kubectl top 不出数、日志抓不到的五个坑

5.1 metrics-server 的数据从哪来

kubectl top背后依赖的是 metrics-server,它本身不采集数据,而是定期向每个节点的 kubelet 请求资源用量,kubelet 再从 cAdvisor 拿容器级指标。metrics-server 把结果聚合成 Metrics API,通过 APIService 注册到 apiserver 上,kubectl 和 HPA 都是从这条路读数据。

这个链路决定了排查顺序,先看 APIService 有没有注册成功,再看 metrics-server Pod 是否健康,最后看它能不能连通 kubelet 的 10250 端口。

# APIService 的 AVAILABLE 必须是 True kubectl get apiservice v1beta1.metrics.k8s.io -o wide # 看节点和 Pod 的实时资源占用,按内存排序 kubectl top node kubectl top pod -A --sort-by=memory # metrics-server 自身日志,证书和连接错误都在这里 kubectl logs -n kube-system deploy/metrics-server --tail=50

需要清楚一点:metrics-server 只保留最近几分钟的数据,不做长期存储,想看过往趋势得另外上 Prometheus 之类的方案。它的定位就是给 HPA 和kubectl top供数,不要指望它当监控系统用。

5.2 日志的三条路径

容器日志一般分三层看。第一层是应用写到标准输出的内容,用kubectl logs读,这是最常用的。容器重启过要看上次的日志加--previous,多容器 Pod 要加-c指定容器名。

第二层是节点上的日志文件。容器运行时会把标准输出落到/var/log/pods/<namespace>_<pod>_<uid>/<container>/0.log,Pod 已经被删掉但节点上还有残留时,直接去这个路径捞。

第三层是组件自身的日志。kubelet 通常由 systemd 托管,用journalctl -u kubelet看;以静态 Pod 形式运行的控制面组件,用kubectl logs -n kube-system <pod>看。

# 持续跟踪日志,只显示最后 100 行 kubectl logs -f deploy/web --tail=100 # 看容器上一次崩溃前的日志 kubectl logs web-7d9f8c6b5-abcde -c nginx --previous # 跟踪节点上 kubelet 的日志,只看最近 10 分钟 journalctl -u kubelet -f --since "10 min ago"

如果应用是把日志写文件而不是标准输出,kubectl logs什么都看不到,这不是 bug。要么改成输出到标准输出,要么用 emptyDir 或 hostPath 把日志目录挂出来,再接采集器。

5.3 五条踩坑记录

现象一:kubectl top node报error: Metrics API not available,但 Pod 都正常。

原因:APIService 没注册成功,或者 metrics-server 连不上 kubelet 的 10250 端口。自签证书的集群上,metrics-server 校验 kubelet 证书失败是高频原因。

解决:先kubectl get apiservice v1beta1.metrics.k8s.io -o yaml看status.conditions里的 message,再去翻 metrics-server 日志。确认是证书问题的话,在它的启动参数里加--kubelet-insecure-tls,这是测试和生产自签环境都常见的做法,代价是放弃对 kubelet 证书的校验。

现象二:Pod 一直 ContainerCreating,describe里 Events 显示FailedMount。

原因:hostPath 写了type: Directory但宿主机上目录不存在,或者挂载的 ConfigMap、Secret 还没创建。

解决:type改成DirectoryOrCreate,或者提前在节点上把目录建好。用 DaemonSet 挂宿主机目录时尤其要注意,集群扩容进来的新节点不会自动带上目录,得靠初始化脚本或 DaemonSet 自己创建。

现象三:kubectl logs报previous terminated container "xxx" not found。

原因:容器已经重启过多次,旧日志被运行时轮转清理掉了,--previous也拿不回来。

解决:短期靠及时抓,长期靠把日志落盘到挂载卷或者接采集器。容器日志的轮转策略是运行时管的,默认单个文件到 10Mi 左右就会切,业务日志写得猛的话几分钟就没。

现象四:Service 访问不通,但后端 Pod 状态是 Running。

原因:Endpoints 为空,通常是 Service 的 selector 与 Pod 的 labels 不匹配,或者targetPort写成了 Service 端口。

解决:kubectl get endpoints <svc-name>先看有没有后端地址,空的话kubectl get svc <svc-name> -o yaml把 selector 抄出来,跟 Pod 的 labels 逐字比对。大小写和拼写错误是主要来源,app: nginx和app: Nginx在 YAML 里是两个完全不同的选择器。

现象五:出问题想查事件,kubectl get events是空的。

原因:事件默认只保留 1 小时(apiserver 的--event-ttl参数),排查拖久了就没了。

解决:故障当下立刻执行kubectl get events -A --sort-by=.lastTimestamp,把输出重定向到文件留档。超过保留期的只能靠提前部署的事件采集组件,这个能力在事后复盘时价值很高。

6. 调度、存储与安全:三个能当场跑通的验证动作

课件目录里还有调度、存储、集群安全三块,篇幅不多但都是容易「看着懂、上手懵」的部分。我的习惯是每块至少跑一个最小验证,确认自己理解的和集群实际行为一致。

调度这块,验证方式是把 Pod 钉到指定节点再观察结果。给 Pod 加nodeSelector或者直接用nodeName,创建后kubectl get pod -o wide看 NODE 列是不是预期的那台。用污点和容忍做反向验证更直观:给节点打上kubectl taint node <node> env=test:NoSchedule,不带容忍的 Pod 会 Pending,describe里的FailedScheduling会明确写出污点不匹配的原因。这比读十遍文档管用。

# 给节点打污点,再验证不带容忍的 Pod 是否被挡住 kubectl taint node node-01 env=test:NoSchedule kubectl run probe --image=nginx:1.25 kubectl describe pod probe | grep -A2 "FailedScheduling" # 验证某个 ServiceAccount 在命名空间里有哪些权限 kubectl auth can-i --list \ --as=system:serviceaccount:default:deployer \ -n default

存储这块,PVC 一直 Pending 时按三步查:kubectl get storageclass看有没有可用的 StorageClass,kubectl describe pvc <name>看 Events 里是no persistent volumes available还是waiting for a volume to be created,再比对 PVC 的accessModes和 PV 支持的是不是一致。ReadWriteOnce和ReadOnlyMany写混了不会报语法错,只会静静挂起。

安全这块,RBAC 的验证靠kubectl auth can-i,上面那条命令会把这个身份能做的动作全列出来。我在给新同事分配 kubeconfig 之前一定会跑一次,确认权限范围跟预期一致,避免出现「以为只给了只读,实际能删 Deployment」这种事。

有个习惯我改不掉:任何环境做变更之前,先跑一遍kubectl get --raw /healthz加一次 etcd 快照。这个动作花不到一分钟,但在我误删过一整个命名空间之后,它就成了肌肉记忆。希望帮到你。

本文还有配套的精品资源,点击获取