news 2026/10/22 8:10:48

虚拟机搭建K8s与Docker本地实验环境实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
虚拟机搭建K8s与Docker本地实验环境实战指南

1. 为什么要在虚拟机里折腾K8s和Docker

先把话说在前头:这套方案不是给生产环境用的,它的核心价值在于学习和验证。我见过太多人一上来就买云服务器、开容器集群,结果账单烧了不少,底层原理还是一问三不知。用虚拟机搭一套本地实验环境,最大的好处是随便折腾、快照回滚、零成本试错。你把网络搞崩了?快照回滚,三十秒满血复活。这种安全感是云环境给不了的。

这个项目的核心目标很明确:在一台物理机上,通过虚拟化软件创建若干台虚拟机,在其中部署Docker容器运行时和K8s集群,最终形成一套可以跑通容器编排全流程的本地实验平台。它解决的是“没有真实服务器集群,但又想动手实践容器云技术”这个矛盾。适合谁看?适合正在学容器技术的学生、想从零理解K8s架构的运维新人、以及需要一套离线实验环境的开发者。你不需要有深厚的Linux功底,但至少要能熟练敲命令、看得懂YAML文件的基本结构。

我这次参考的是先电2.4容器云手册的思路,但说实话,手册给的是骨架,真正跑起来会遇到一堆手册上没写的问题。下面我把自己踩过的坑、绕过的弯、以及最终跑通的完整流程,从头到尾捋一遍。

2. 整体架构设计与方案选型

2.1 为什么选虚拟机而不是物理机或云主机

先说选型逻辑。物理机方案直接排除——你不可能为了学K8s专门买三台服务器。云主机方案看似方便,但有几个硬伤:一是成本,跑一套最小可用的K8s集群至少需要三台节点,按量付费一个月下来不少钱;二是网络环境不可控,很多云厂商对底层网络做了封装,你很难直观地看到节点间的真实通信过程;三是快照和回滚不如本地虚拟机灵活。

虚拟机方案的核心优势在于完全掌控。从BIOS到网卡到磁盘,所有东西都在你手里。你可以随意调整CPU核数、内存大小、磁盘类型,可以模拟网卡故障,可以抓包分析。这种掌控感对于理解K8s的网络模型、存储模型至关重要。而且虚拟机支持快照,你在做任何高风险操作之前拍个快照,搞砸了直接回滚,这个体验是云主机给不了的。

2.2 虚拟机软件的选择与考量

市面上主流的桌面虚拟化软件就那么几款。我最终选的是VMware Workstation Pro,原因有三:第一,网络配置灵活,支持NAT、桥接、仅主机三种模式,还能自定义虚拟网络,这对搭建K8s集群的多节点网络非常关键;第二,快照管理成熟稳定,支持多级快照树;第三,性能损耗相对较小,尤其是磁盘I/O方面,跑容器负载不会太吃力。

如果你用的是其他虚拟化方案,比如VirtualBox或者基于KVM的方案,整体思路是一样的,只是在网络配置和快照操作上有些差异。核心原则是:虚拟机之间要能互相通信,虚拟机要能访问外网(至少初期需要),宿主机要能方便地管理虚拟机。

2.3 集群拓扑规划

我规划的集群拓扑是这样的:一台虚拟机作为Master节点,两台虚拟机作为Worker节点。为什么是两台Worker?因为一台Worker体现不出调度的效果,三台又太吃资源。两台刚好能演示Pod调度、服务发现、负载均衡这些核心功能。

每台虚拟机的配置如下:

节点角色CPU核数内存磁盘操作系统
Master2核4GB40GBCentOS 7.9
Worker12核4GB40GBCentOS 7.9
Worker22核4GB40GBCentOS 7.9

这里有个经验之谈:内存千万别抠。K8s的Master节点光控制平面组件就要吃掉不少内存,再加上Docker运行时和系统本身的开销,2GB内存跑起来会非常卡,甚至直接OOM。4GB是底线,有条件的话给Master 8GB会更舒服。磁盘方面,40GB是起步,因为Docker镜像很占空间,如果你要拉取多个大镜像,建议给到60GB以上。

2.4 网络模式的选择

VMware提供了三种网络模式:桥接、NAT、仅主机。我的建议是NAT模式。为什么?桥接模式下虚拟机会直接暴露在局域网中,IP地址由路由器分配,虽然方便但不够稳定,换个网络环境IP就变了。仅主机模式下虚拟机只能和宿主机通信,无法访问外网,初期安装软件会很麻烦。NAT模式折中了两者:虚拟机通过宿主机的网络访问外网,同时虚拟机之间在一个独立的子网内互相通信,IP地址由VMware的DHCP服务分配,相对稳定。

但NAT模式有个坑:默认的DHCP租约可能会导致IP地址变化。我的做法是在VMware的虚拟网络编辑器中,把DHCP租约时间设长,或者在虚拟机内部配置静态IP。我选择后者,直接给每台虚拟机配静态IP,一劳永逸。

3. 虚拟机创建与系统初始化

3.1 创建虚拟机的关键参数设置

打开VMware Workstation,新建虚拟机,选择“自定义(高级)”模式。为什么不选典型模式?因为典型模式会帮你做很多决定,而这些决定未必适合K8s场景。比如典型模式默认给20GB磁盘,不够用;默认网络模式可能不是你想要的。

在硬件兼容性页面,选择你当前VMware版本支持的最高版本。处理器配置页面,给2核。内存给4GB。网络类型选NAT。磁盘类型选SCSI,磁盘大小给40GB,选择“将虚拟磁盘拆分成多个文件”——这个选项的好处是虚拟机文件可以分散存储,不会因为单个文件过大导致宿主机磁盘碎片化。

创建完成后,先别急着启动。进入虚拟机设置,把“处理器”里的“虚拟化Intel VT-x/EPT或AMD-V/RVI”勾上。这个选项允许在虚拟机内部再跑虚拟化,虽然我们不在虚拟机里跑虚拟机,但某些容器运行时特性需要这个支持。

3.2 操作系统安装与基础配置

安装CentOS 7.9的过程就不赘述了,标准流程。装完之后第一件事是配置静态IP。编辑/etc/sysconfig/network-scripts/ifcfg-ens33文件,关键参数如下:

BOOTPROTO=static ONBOOT=yes IPADDR=192.168.100.10 NETMASK=255.255.255.0 GATEWAY=192.168.100.2 DNS1=114.114.114.114

这里的网关地址192.168.100.2是VMware NAT模式的默认网关,你可以在虚拟网络编辑器里确认。三台虚拟机的IP分别设为192.168.100.10、192.168.100.11、192.168.100.12。

配置完成后重启网络服务:systemctl restart network。然后用ping命令测试虚拟机之间、虚拟机与宿主机、虚拟机与外网的连通性。这一步很重要,网络不通后面全是白搭。

3.3 系统层面的必要调整

接下来做几项系统层面的调整,这些调整在手册上可能没写,但不做的话后面会出各种幺蛾子。

第一,关闭防火墙。K8s集群内部组件之间需要大量通信,防火墙会拦截很多端口。学习环境直接关掉:

systemctl stop firewalld systemctl disable firewalld

第二,关闭SELinux。SELinux的安全策略会阻止容器访问某些资源,导致容器启动失败。编辑/etc/selinux/config,把SELINUX=enforcing改为SELINUX=disabled,然后重启。

第三,配置主机名和hosts解析。给三台虚拟机分别设置主机名:

hostnamectl set-hostname master hostnamectl set-hostname worker1 hostnamectl set-hostname worker2

然后在每台机器的/etc/hosts文件里添加:

192.168.100.10 master 192.168.100.11 worker1 192.168.100.12 worker2

第四,配置SSH免密登录。在Master节点生成密钥对,然后把公钥分发到所有节点:

ssh-keygen -t rsa -N "" ssh-copy-id master ssh-copy-id worker1 ssh-copy-id worker2

这样后续在Master节点上操作其他节点时就不需要反复输密码了。

第五,加载内核模块。K8s的网络和存储功能依赖一些内核模块,需要提前加载:

modprobe br_netfilter modprobe ip_vs modprobe ip_vs_rr modprobe ip_vs_wrr modprobe ip_vs_sh modprobe nf_conntrack_ipv4

同时把这些模块写入/etc/rc.local或创建/etc/modules-load.d/k8s.conf文件,确保开机自动加载。

第六,调整内核参数。编辑/etc/sysctl.d/k8s.conf:

net.bridge.bridge-nf-call-ip6tables = 1 net.bridge.bridge-nf-call-iptables = 1 net.ipv4.ip_forward = 1

然后执行sysctl --system使配置生效。这几项参数的作用是让iptables能够正确处理桥接流量,并开启IP转发。

注意:以上所有操作需要在三台虚拟机上分别执行。如果你觉得重复劳动太烦,可以写个脚本批量执行,或者用Ansible这类工具。但手动做一遍的好处是你能记住每一步在干什么。

4. Docker容器运行时部署

4.1 Docker版本选择与安装

Docker的版本选择有个讲究。K8s对Docker版本有兼容性要求,太新的版本可能不被支持,太旧的版本又有已知bug。我选的是Docker CE 20.10.x系列,这个版本经过大量验证,与K8s 1.23兼容性良好。

安装步骤如下。首先配置Docker的yum源:

yum install -y yum-utils yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

然后安装指定版本的Docker:

yum install -y docker-ce-20.10.17 docker-ce-cli-20.10.17 containerd.io

安装完成后启动Docker并设置开机自启:

systemctl start docker systemctl enable docker

验证安装:docker version,能看到Client和Server的版本信息就说明安装成功了。

4.2 Docker配置优化

默认的Docker配置有几个地方需要调整。编辑/etc/docker/daemon.json:

{ "exec-opts": ["native.cgroupdriver=systemd"], "log-driver": "json-file", "log-opts": { "max-size": "100m", "max-file": "3" }, "storage-driver": "overlay2", "registry-mirrors": ["https://registry.cn-hangzhou.aliyuncs.com"] }

逐项解释一下。native.cgroupdriver=systemd是必须的,因为K8s默认使用systemd作为cgroup驱动,如果Docker用cgroupfs,两者会冲突,导致节点状态异常。日志配置是为了防止容器日志无限增长把磁盘撑爆。storage-driver选overlay2,这是目前性能最好、最稳定的存储驱动。registry-mirrors配置镜像加速,国内拉取官方镜像会快很多。

配置完成后重启Docker:

systemctl daemon-reload systemctl restart docker

4.3 验证Docker功能

跑一个测试容器验证Docker是否正常工作:

docker run --rm hello-world

如果能看到“Hello from Docker!”的输出,说明Docker安装配置都没问题。再跑一个Nginx容器测试网络和端口映射:

docker run -d --name test-nginx -p 8080:80 nginx

然后在宿主机浏览器访问http://192.168.100.10:8080,能看到Nginx欢迎页就说明端口映射也正常。

实操心得:如果docker run卡在拉取镜像阶段,大概率是镜像加速没配好或者网络有问题。先检查/etc/docker/daemon.json的格式是否正确,JSON格式错误会导致Docker启动失败。可以用docker info查看当前配置是否生效。

5. K8s集群部署与初始化

5.1 K8s组件安装

K8s的安装方式有很多种,kubeadm、二进制、minikube、kind等。我选kubeadm,因为它是官方推荐的集群部署工具,既能体现集群的真实架构,又不会像二进制方式那样繁琐到让人崩溃。

首先配置K8s的yum源。创建/etc/yum.repos.d/kubernetes.repo:

[kubernetes] name=Kubernetes baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/ enabled=1 gpgcheck=0

然后安装kubeadm、kubelet、kubectl三个组件:

yum install -y kubelet-1.23.0 kubeadm-1.23.0 kubectl-1.23.0

这里指定了1.23.0版本,为什么?因为从1.24开始,K8s移除了对Docker的直接支持,改用containerd作为默认运行时。虽然可以通过额外配置继续用Docker,但为了减少不必要的麻烦,我选择1.23这个仍然原生支持Docker的版本。

安装完成后设置kubelet开机自启:

systemctl enable kubelet

注意,此时不要启动kubelet,因为还没初始化集群,启动会报错。

5.2 Master节点初始化

在Master节点上执行初始化命令:

kubeadm init \ --apiserver-advertise-address=192.168.100.10 \ --image-repository=registry.aliyuncs.com/google_containers \ --kubernetes-version=v1.23.0 \ --service-cidr=10.96.0.0/12 \ --pod-network-cidr=10.244.0.0/16

逐项解释参数。--apiserver-advertise-address指定API Server监听的地址,填Master节点的IP。--image-repository指定镜像仓库,因为官方镜像在国内拉取困难,换成阿里云的镜像仓库。--kubernetes-version指定版本,必须和安装的kubeadm版本一致。--service-cidr是Service的网段,--pod-network-cidr是Pod的网段,这两个网段不能和宿主机网段重叠。

初始化过程会拉取一堆镜像,视网络情况可能需要几分钟到十几分钟。初始化成功后,终端会输出一段kubeadm join命令,类似这样:

kubeadm join 192.168.100.10:6443 --token abcdef.1234567890abcdef \ --discovery-token-ca-cert-hash sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

这段命令务必保存下来,Worker节点加入集群时需要用到。如果忘了保存,可以用kubeadm token create --print-join-command重新生成。

初始化完成后,按照提示配置kubectl:

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

然后执行kubectl get nodes,应该能看到Master节点,但状态是NotReady。这是正常的,因为还没部署网络插件。

5.3 部署网络插件

K8s的网络插件我选Calico。为什么选Calico而不是Flannel?Calico支持网络策略,功能更强大,而且性能更好。虽然配置稍微复杂一点,但值得。

kubectl apply -f https://docs.projectcalico.org/v3.21/manifests/calico.yaml

如果你的网络环境访问不了这个地址,可以先把文件下载下来再apply。部署完成后等几分钟,再执行kubectl get nodes,Master节点应该变成Ready状态。

5.4 Worker节点加入集群

在Worker1和Worker2上分别执行之前保存的kubeadm join命令。执行完成后,在Master节点上执行kubectl get nodes,应该能看到三个节点全部是Ready状态。

如果Worker节点加入失败,最常见的原因是token过期。默认token的有效期是24小时,过期后需要重新生成。另外检查Worker节点的时间是否和Master同步,时间偏差过大会导致证书验证失败。

5.5 集群状态验证

集群搭建完成后,跑几个命令验证一下:

kubectl get nodes kubectl get pods --all-namespaces kubectl cluster-info

kubectl get pods --all-namespaces应该能看到Calico的Pod、CoreDNS的Pod、API Server的Pod等都在Running状态。如果有Pod一直处于Pending或CrashLoopBackOff状态,需要进一步排查。

常见问题:Calico的Pod一直处于Pending状态,通常是因为镜像拉取失败。可以kubectl describe pod查看具体原因。如果是镜像问题,可以手动拉取镜像或者配置镜像加速。

6. 集群功能验证与常见问题排查

6.1 部署一个测试应用

集群搭好了,总得跑个东西验证一下。我部署一个Nginx Deployment:

kubectl create deployment nginx-test --image=nginx:1.21 --replicas=2

然后暴露服务:

kubectl expose deployment nginx-test --port=80 --type=NodePort

查看Service:

kubectl get svc nginx-test

你会看到一个NodePort类型的Service,端口号在30000-32767之间。然后在浏览器访问http://192.168.100.10:端口号,应该能看到Nginx欢迎页。多刷新几次,如果能看到不同的Pod响应,说明负载均衡也正常工作了。

6.2 常见问题速查表

下面这张表是我在实际操作中遇到过的典型问题及解决方法,按出现频率排序:

问题现象可能原因排查方法解决方案
节点状态NotReady网络插件未部署或异常kubectl describe node查看Conditions重新部署Calico,检查Pod日志
Pod一直Pending资源不足或调度失败kubectl describe pod查看Events检查节点资源,调整资源请求
镜像拉取失败网络问题或镜像不存在kubectl describe pod查看Events配置镜像加速,或手动拉取镜像
kubelet启动失败cgroup驱动不匹配journalctl -u kubelet查看日志统一Docker和K8s的cgroup驱动
节点加入失败token过期或时间不同步检查token有效期和节点时间重新生成token,同步时间
Service无法访问网络插件异常或iptables规则问题检查Calico Pod状态和iptables规则重启网络插件,清理iptables规则

6.3 独家避坑技巧

第一个坑:虚拟机磁盘空间不足。Docker镜像和容器日志会迅速吃掉磁盘空间。我的做法是定期执行docker system prune -a清理无用镜像和容器,同时给/var/lib/docker单独挂一块盘。如果做不到单独挂盘,至少把日志限制配置好。

第二个坑:虚拟机时间不同步。虚拟机挂起再恢复后,系统时间可能会漂移,导致K8s证书验证失败。安装ntp或chrony服务,配置时间同步:

yum install -y chrony systemctl start chronyd systemctl enable chronyd

第三个坑:快照回滚后网络异常。虚拟机快照回滚后,MAC地址可能会变化,导致网络配置失效。回滚后检查/etc/sysconfig/network-scripts/ifcfg-ens33中的HWADDR是否与实际MAC一致,不一致就删掉这行或者更新它。

第四个坑:kubeadm reset不彻底。如果你想重新初始化集群,执行kubeadm reset后还需要手动清理一些残留文件:

kubeadm reset -f rm -rf $HOME/.kube rm -rf /etc/cni/net.d rm -rf /var/lib/cni iptables -F && iptables -t nat -F && iptables -t mangle -F && iptables -X

不清理干净的话,重新初始化可能会报端口被占用或配置文件冲突。

6.4 集群日常运维命令

集群跑起来之后,日常运维离不开这些命令:

# 查看所有节点状态 kubectl get nodes -o wide # 查看所有命名空间的Pod kubectl get pods --all-namespaces -o wide # 查看某个Pod的详细信息 kubectl describe pod <pod-name> -n <namespace> # 查看Pod日志 kubectl logs <pod-name> -n <namespace> # 进入Pod内部 kubectl exec -it <pod-name> -n <namespace> -- /bin/bash # 查看集群事件 kubectl get events --sort-by='.lastTimestamp' # 查看资源使用情况 kubectl top nodes kubectl top pods --all-namespaces

kubectl top命令需要部署Metrics Server才能使用。Metrics Server的部署不在本文范围内,但如果你需要监控集群资源,建议部署一个。

7. 环境固化与后续扩展

7.1 制作虚拟机模板

三台虚拟机都配置好之后,建议把Master节点做成模板。具体做法是:先清理掉集群相关的配置(执行kubeadm reset),然后关机,在VMware中右键虚拟机选择“模板”->“转换为模板”。以后需要新建节点时,直接从模板克隆,改一下IP和主机名就能用,省去了重复安装系统的时间。

克隆时选择“创建完整克隆”而不是“创建链接克隆”。链接克隆虽然省磁盘空间,但多个虚拟机共享基础磁盘,性能会受影响,而且一个虚拟机的磁盘操作可能影响其他虚拟机。

7.2 后续可以扩展的方向

这套基础环境搭好之后,可以往几个方向扩展。一是部署Dashboard,用Web界面管理集群,对新手更友好。二是部署Harbor私有镜像仓库,把常用镜像存在本地,拉取速度更快。三是部署Prometheus和Grafana做监控,观察集群和容器的资源使用情况。四是尝试部署有状态应用,比如MySQL主从集群,理解StatefulSet和持久化存储的用法。

每个方向都可以写一篇独立的文章,但前提是你先把基础环境跑通。基础不牢,地动山摇,这句话在容器技术领域尤其适用。

7.3 资源清理与快照管理

虚拟机快照不是越多越好。快照文件会占用大量磁盘空间,而且快照链太长会影响虚拟机性能。我的做法是:只在关键节点拍快照,比如系统装好之后、Docker装好之后、K8s集群跑通之后。每个快照命名清晰,比如“01-系统初始化完成”、“02-Docker部署完成”、“03-K8s集群就绪”。不需要的快照及时删除,释放磁盘空间。

另外,虚拟机的内存快照要慎用。内存快照会把虚拟机的内存状态也保存下来,文件很大,而且恢复后可能出现时钟漂移等问题。除非你要保存某个瞬间的运行状态,否则用普通快照就够了。

7.4 关于学习路径的建议

最后说点个人体会。这套环境搭起来之后,不要急着往上堆应用。先把K8s的核心概念搞清楚:Pod、Service、Deployment、ReplicaSet、ConfigMap、Secret、Volume、Namespace,这些概念之间的关系是什么,各自解决什么问题。然后动手写YAML文件,从最简单的Pod开始,逐步增加复杂度。遇到报错不要慌,kubectl describe和kubectl logs能解决百分之八十的问题。

我见过不少人环境搭好了,然后不知道干什么,最后虚拟机吃灰。学习容器技术最好的方式就是带着问题去实践。比如你想部署一个博客系统,那就试着用K8s来编排它,遇到问题解决问题,这个过程比看十篇文章都管用。

这套基于虚拟机的K8s实验环境,我前后搭了不下五次,每次都有新的收获。第一次搭的时候花了整整两天,各种报错各种查资料;第五次搭的时候两个小时搞定,因为所有的坑都踩过了。技术这东西就是这样,纸上得来终觉浅,绝知此事要躬行。希望这篇内容能帮你少走一些弯路,把更多时间花在真正理解技术上,而不是跟环境较劲。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/22 8:10:27

Linux su命令详解:身份切换、参数与常见报错排查

1. su 命令的使用场景与设计思路1.1 su 到底解决什么问题先聊一个几乎所有 Linux 使用者都会撞上的场景&#xff1a;你用普通用户登录了一台服务器&#xff0c;正准备重启某个服务&#xff0c;结果系统甩给你一行Permission denied。这时候你才意识到&#xff0c;日常操作受限明…

作者头像 李华
网站建设 2026/10/22 8:10:14

逆向提示工程:从AI输出反推提示词的五步实操指南

在做一次内容流水线项目的过程中&#xff0c;甲方只丢给我一段AI生成的成稿&#xff0c;要求我按同样的风格复刻一批新内容。我一开始用最朴素的提示词去硬跑&#xff0c;结果出来的完全是另一副面孔&#xff1a;没有对比句式&#xff0c;没有短句节奏&#xff0c;更没有结尾那…

作者头像 李华
网站建设 2026/10/22 8:09:38

路面坑洼大小识别数据集:YOLO/COCO/VOC三格式与92.6%训练实战

坑洼检测这几年在道路养护圈子里讨论度很高。我之前做过一段时间道路病害识别方向的活儿&#xff0c;最深的感触是&#xff1a;数据比模型难搞。路面目标跟常规的目标检测完全不一样&#xff0c;坑洼没有固定形状、边界模糊、光照一变就认不出来&#xff0c;一套规范标注的数据…

作者头像 李华
网站建设 2026/10/22 8:09:36

交换机工作模式与命令底层原理深度解析

1. 这不是“背命令”的课&#xff0c;而是理解交换机怎么“思考”的实战入口 你打开PacketTracer&#xff0c;拖出一台2960交换机&#xff0c;点开CLI界面&#xff0c;敲下 enable ——接下来呢&#xff1f;是机械地抄写 show running-config 、 interface fa0/1 、 swi…

作者头像 李华
网站建设 2026/10/22 8:09:29

从零搭建Chaos Ball:游戏引擎物理碰撞与状态管理实战

1. 从零搭建一个Chaos Ball&#xff1a;为什么选这个练手项目如果你刚开始接触游戏引擎&#xff0c;或者已经看过一堆教程但始终觉得“眼睛会了手不会”&#xff0c;那Chaos Ball这个项目大概率是最适合你拿来破冰的实战选题。它的核心玩法非常直白&#xff1a;一个球在封闭场地…

作者头像 李华
网站建设 2026/10/22 8:09:15

环境标签缺失下的不变学习:EIIL与GroupDRO实战指南

1. 为什么“环境标签”成了不变学习的隐形天花板做机器学习研究或者搞推荐、风控、医疗辅助决策这类落地项目的人&#xff0c;大概率都遇到过同一个尴尬局面&#xff1a;你在一个数据集上训练好的模型&#xff0c;换到另一个来源、另一个时间段、另一批用户身上&#xff0c;效果…

作者头像 李华