建设部网站13清单,江苏瀚和建设网站,千锋培训学费多少钱,华艺网络网站开发前言
很高兴给大家做一个技术分享及探讨。
下面给大家分享几个工作遇到有趣的例子。 docker
docker 进程
现象
客户的模型导入到BML平台发布预测服务后#xff0c;模型本身是用django提供的支持。按照本地docker的方式进行调试#xff0c;kill掉django的进程修改代码…前言
很高兴给大家做一个技术分享及探讨。
下面给大家分享几个工作遇到有趣的例子。 docker
docker 进程
现象
客户的模型导入到BML平台发布预测服务后模型本身是用django提供的支持。按照本地docker的方式进行调试kill掉django的进程修改代码后重启进程服务发现服务会重启。前提客户是有一定的容器使用经验的。
原脚本如下
#!/bin/bash
Nohup nginx start /dev/null
Nohup python mange.py runserver 127.0.0.1:8080 /dev/null 原因
对linux系统来说1号进程为init进程是由0号进程(内核进程)通过调用系统init函数创建的第一个用户进程1进程主要做用户态进程的管理垃圾回收等动作。
对docker来讲1号进程大多数情况下都是服务进程或者是用户自己开发的服务daemon进程这也是瘦容器的理论那服务进程作为1号进程有什么区别呢
docker进程管理的基础是LINUX内核中的PID命名空间技术在不同PID名空间中进程ID是独立的即在两个不同名空间下的进程可以有相同的PID。这句话的意思就是docker通过空间技术进行了容器间的进程隔离。
当创建一个Docker容器的时候就会新建一个PID名空间。容器启动进程在该名空间内PID为1。
当PID1进程结束之后Docker会销毁对应的PID名空间并向容器内所有其它的子进程发送SIGKILL。
容器的设计本身是“单进程”模型容器应用进程。
方案:
加上一个前台进程也是pid1的进程。
tail -f /etc/hosts
相关文档
BML模型纳管与模型调试
docker overlay 文件系统
背景
光大较为谨慎采用的是双线加负载方式。所以生产环境部署BML4.3需要上线2套环境需要在周末俩天之内部署完成部署时间还是比较紧张。
由于4.3部署采用的是镜像load的方式镜像load镜像时间极长。并且是把镜像load到一个docker的挂载卷上。
分析
docker提供了两种 OverlayFS一个是原本的 overlay文件系统另一个是更新、更稳定的 overlay2。在日常使用中应该更倾向于使用更好更稳定的 overlay2而不是 overlay。
查看/etc/docker/daemon.json文件来查看是用的那种方式。
{storage-driver: overlay2
}磁盘上的镜像和容器层
这里举拉取乌邦图镜像的例子来说明。
在使用指令docker pull ubuntu下载五层镜像的ubantu后可以在/var/lib/docker/overlay2看到有六个目录。
$ ls -l /var/lib/docker/overlay2total 24
drwx------ 5 root root 4096 Jun 20 07:36 223c2864175491657d238e2664251df13b63adb8d050924fd1bfcdb278b866f7
drwx------ 3 root root 4096 Jun 20 07:36 3a36935c9df35472229c57f4a27105a136f5e4dbef0f87905b2e506e494e348b
drwx------ 5 root root 4096 Jun 20 07:36 4e9fa83caff3e8f4cc83693fa407a4a9fac9573deaf481506c102d484dd1e6a1
drwx------ 5 root root 4096 Jun 20 07:36 e8876a226237217ec61c4baf238a32992291d059fdac95ed6303bdff3f59cff5
drwx------ 5 root root 4096 Jun 20 07:36 eca1e4e1694283e001f200a667bb3cb40853cf2d1b12c29feda7422fed78afed
drwx------ 2 root root 4096 Jun 20 07:36 l思考与方案
将A集群load好的镜像docker卷打包在B集群上进行解压以此完成快速部署的目的。
这个也在事后提出反馈并合并到后面的标版部署中。
缺点
由于是解压后卷镜像挂载不是导入的镜像所以使用docker images 等命令 无法查看到相关的镜像。
整理如下命令供参考
相应的参考命令:
#查询镜像
curl 仓库地址/v2/_catalog#查询镜像tag(版本)
curl 仓库地址/v2/镜像名/tags/list#查询镜像 digest_hash
curl --header Accept:application/vnd.docker.distribution.manifest.v2json -I -XGET 仓库地址/v2/镜像名/manifests/tag#删除镜像API
curl -I -X DELETE 仓库地址/v2/镜像名/manifests/镜像digest_hash#查询镜像
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NWsgcerW-1680159294012)(null)]
#查询镜像tag(版本)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3oEWYuwt-1680159294359)(null)]
docker 常见问题排查
场景一、生产集群磁盘爆满
根据目录查找所在的容器名称找到对应的容器名称根据对应的容器名称找到BMl 上对应的POD。
根据docker overylay2 目录下的文件夹名称 查找容器
docker ps -q | xargs docker inspect --format {{.State.Pid}}, {{.Id}}, {{.Name}}, {{.GraphDriver.Data.WorkDir}} | grep 文件夹名称最后发现是一个用户起的notebook,上传东西到容器内将磁盘撑爆了。 场景二、物理机上某个线程占用GPU、CPU 高
方法一
根据pid 查找容器名称
[rootgzbh-intel016 ~]# cd /proc/74841
[rootgzbh-intel016 74841]# pwd
/proc/74841
[rootgzbh-intel016 74841]# ps -ef | grep 74841
root 74841 74840 1 Dec18 ? 03:49:46 bin/signature_handwriting
root 89107 20906 0 17:48 pts/2 00:00:00 grep --colorauto 74841
[rootgzbh-intel016 74841]# ps -ef | grep 74840
root 74840 74327 0 Dec18 ? 00:00:00 bin/supervise.signature_handwriting -p status/signature_handwriting -f bin/signature_handwriting
root 74841 74840 1 Dec18 ? 03:49:47 bin/signature_handwriting
root 89663 20906 0 17:48 pts/2 00:00:00 grep --colorauto 74840
[rootgzbh-intel016 74841]# ps -ef | grep 74327
root 74327 74308 0 Dec18 ? 00:00:00 sh /home/work/app/console/all_start.sh 0 4g
root 74385 74327 0 Dec18 ? 00:00:00 sh java_start.sh
root 74389 74327 0 Dec18 ? 00:00:00 tail -f /etc/hosts
root 74840 74327 0 Dec18 ? 00:00:00 bin/supervise.signature_handwriting -p status/signature_handwriting -f bin/signature_handwriting
root 91492 20906 0 17:48 pts/2 00:00:00 grep --colorauto 74327
[rootgzbh-intel016 74841]#
[rootgzbh-intel016 74841]# ps -ef | grep 74308
root 74308 13670 0 Dec18 ? 00:01:05 docker-containerd-shim -namespace moby -workdir /data/lib/docker/containerd/daemon/io.containerd.runtime.v1.linux/moby/0000936684d05d4c98c67fa8606d89f367a7985a0d05449bbef812422897b1b2 -address /var/run/docker/containerd/docker-containerd.sock -containerd-binary /usr/bin/docker-containerd -runtime-root /var/run/docker/runtime-nvidia
root 74327 74308 0 Dec18 ? 00:00:00 sh /home/work/app/console/all_start.sh 0 4g
root 93870 20906 0 17:48 pts/2 00:00:00 grep --colorauto 74308最重发现 物理机 路径为 /data/lib/docker/containerd/daemon/io.containerd.runtime.v1.linux/moby/0000936684
查找容器名称
[rootgzbh-intel016 0000936684d05d4c98c67fa8606d89f367a7985a0d05449bbef812422897b1b2]# pwd
/data/lib/docker/containerd/daemon/io.containerd.runtime.v1.linux/moby/0000936684d05d4c98c67fa8606d89f367a7985a0d05449bbef812422897b1b2
[rootgzbh-intel016 0000936684d05d4c98c67fa8606d89f367a7985a0d05449bbef812422897b1b2]# ll
total 0
[rootgzbh-intel016 0000936684d05d4c98c67fa8606d89f367a7985a0d05449bbef812422897b1b2]# docker ps -a | grep 0000936684
0000936684d0 244aea358579 /bin/bash -ce sh /… 11 days ago Up 11 days k8s_guangda-cursive_guangda-cursive-86d55fcf65-7dpsw_default_d0b3091b-5fc3-11ec-9318-bc97e1b8d4ae_0可知容器名称为
k8s_guangda-cursive_guangda-cursive-86d55fcf65-7dpsw_default_d0b3091b-5fc3-11ec-9318-bc97e1b
方法二
根据进程号获取容器名称docker ps -q | xargs docker inspect -f {{.State.Pid}} {{.Config.Hostname}} | grep 2811再通过docker ps | grep 0bbea2sd3ds15
便可获取到容器名称场景三、GPU服务器上 启动docker容器识别不到GPU
概念
为了让docker支持nvidia显卡英伟达公司开发了nvidia-docker。该软件是对docker的包装使得容器能够看到并使用宿主机的nvidia显卡。
正常机器的对比
GPU服务器的docker 与daemon.json
[rootinstance-q2pzfrcc ~]# rpm -qa | grep docker
docker-ce-18.09.7-3.el7.x86_64
docker-ce-cli-18.09.7-3.el7.x86_64
nvidia-container-runtime-2.0.0-3.docker18.09.7.x86_64
nvidia-docker2-2.0.3-3.docker18.09.7.ce.noarch[rootgzbh-intel003 ~]# cat /etc/docker/daemon.json
{hosts: [tcp://10.36.245.149:8137, unix:///var/run/docker.sock],default-runtime: nvidia,runtimes: {nvidia: {path: /usr/bin/nvidia-container-runtime,runtimeArgs: []}}
}CPU服务器的docker 与daemon.json
[rootinstance-asdsadsadcds ~]# rpm -qa | grep docker
docker-ce-18.09.7-3.el7.x86_64
docker-ce-cli-18.09.7-3.el7.x86_64[rootgzbh-intelmbx001 images]# cat /etc/docker/daemon.json
{hosts: [tcp://10.36.252.167:8137, unix:///var/run/docker.sock],init: true,init-path: /usr/bin/docker-init,storage-opts: [overlay2.override_kernel_checktrue]
}原理
nvidia-docker是一个可以使用GPU的dockernvidia-docker是在docker上做了一层封装通过nvidia-docker-plugin然后调用到docker上其最终实现的还是在docker的启动命令上携带一些必要的参数。因此在安装nvidia-docker之前还是需要安装docker的。
Nvidia-dockerNvidia提供Nvidia-docker项目它是通过修改Docker的Runtime为nvidia runtime工作当我们执行 nvidia-docker create 或者 nvidia-docker run时它会默认加上 --runtimenvidia 参数。将runtime指定为nvidia。当然为了方便使用可以直接修改Docker daemon 的启动参数修改默认的 Runtime为 nvidia-container-runtime
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tHfdELOp-1680159294138)(null)]
注意
在docker19以前都需要单独下载nvidia-docker1或nvidia-docker2来启动容器自从升级了docker19后跑需要gpu的docker只需要加个参数–gpus all 即可
docker run -d -it -p 1518:1518 --namecentos --gpus all nvidia/cuda:9.1-cudnn7-runtime-centos7 /bin/bash其他场景问题
1、镜像八小时问题。
2、root 与 普通用户切换问题。
3、docker 迁移存储目录
4、添加信任仓库。
5、容器启动不使用网桥默认网段。
参考文档docker原理及运维 推荐工具
一、docker-compose
举例
使用命令docker-compose -f *.yaml up -d 启动mysql
类似于k8s的yaml 编排。
是单机管理编排容器可以同时管理多个 container 将多个相关的容器⼀次性启动⽐如运⾏⼀个jar需要依赖jdk、mysql、mq、redis等这些容器只需要 docker-composer up 就可以全部启动不需要⼀个个单独启动。
version: 2
services:mysql-db:container_name: mysqlrestart: alwaysimage: mysql:5.7ports:- 3306:3306environment:TZ: Asia/ShanghaiMYSQL_ROOT_PASSWORD: 123456command:--character-set-serverutf8mb4--collation-serverutf8mb4_general_ci--explicit_defaults_for_timestamptrue--lower_case_table_names1--max_allowed_packet128M--sql-modeSTRICT_TRANS_TABLES,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION,NO_ZERO_DATE,NO_ZERO_IN_DATE,ERROR_FOR_DIVISION_BY_ZEROvolumes:- /Users/wangjinlong05/app/data/mysqldockerdata/data:/var/lib/mysql二、portainer
适合客户侧容器小白使用
开启2375 docker远程管理端口
vim /usr/lib/systemd/system/docker.service
ExecStart/usr/bin/dockerd -H unix://var/run/docker.sock -H tcp://0.0.0.0:2375
systemctl daemon-reload systemctl restart docker拉取镜像
docker pull portainer/portainer 启动容器
docker run -d -p 9000:9000 \
-v /var/run/docker.sock:/var/run/docker.sock \
--restartalways \
portainer/portainer如果已经启动可以更新
docker run -d -p 9000:9000\
-v /var/run/docker.sock:/var/run/docker.sock \
update --restartalways \
portainer/portainerk8s
场景一集群数据库迁移
背景
客户测试环境部署4.3交付。
原部署计划是由客户提供mysql集群由我们部署对接由于客户测试环境的mysql环境没提前准备好所以我们集群自建mysql环境搭建。运行一个月后客户要求BML的mysql 迁移对接到他们的mysql行内集群。
难点
由于bml 部署配置繁杂mysql 初始化地方极多稍有不慎就会导致集群出现故障。迁移mysql 改造对接成本较高。
概念
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SF48FwhD-1680159294081)(null)]
具体的四表
filter表——涉及FORWARD、INPUT、OUTPUT三条链多用于本地和转发过程中数据过滤(负责过滤工程防火墙 )Nat表——涉及PREROUTING、OUTPUT、POSTROUTTING三条链多用于源地址/端口转换和目标地址/端口的转换(网络地址转换功能 network address translate )Mangle表——涉及整条链可实现拆解报文、修改报文、重新封装可常见于IPVS的PPC下多端口会话保持。 (拆解报文做出修改并重新封装 )Raw表——涉及PREROUTING和OUTPUT链决定数据包是否被状态跟踪机制处理需关闭nat表上的连接追踪机制。(关闭nat表上启用的连接追踪机制)
具体的五链
INPUT——进来的数据包应用此规则链中的策略OUTPUT——外出的数据包应用此规则链中的策略FORWARD——转发数据包时应用此规则链路中的策略PREROUTING——对数据包作路由选择前应用此链中的规则所有的数据包进来的时候都先由这个链处理POSTROUTING——对数据包作路由选择后应用此链中的规则所有的数据包出来的时候都先由这个链处理
链表关系
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hgyLFrkx-1680159294199)(null)]
iptables处理动作
iptables处理动作除了 ACCEPT、REJECT、DROP、REDIRECT 、MASQUERADE 以外还多出 LOG、ULOG、DNAT、RETURN、TOS、SNAT、MIRROR、QUEUE、TTL、MARK等。我们只说明其中最常用的动作
REJECT 拦阻该数据包并返回数据包通知对方可以返回的数据包有几个选择ICMP port-unreachable、ICMP echo-reply 或是tcp-reset这个数据包包会要求对方关闭联机进行完此处理动作后将不再比对其它规则直接中断过滤程序。 范例如下
iptables -A INPUT -p TCP --dport 22 -j REJECT --reject-with ICMP echo-reply
DROP 丢弃数据包不予处理进行完此处理动作后将不再比对其它规则直接中断过滤程序。
REDIRECT 将封包重新导向到另一个端口PNAT进行完此处理动作后将会继续比对其它规则。这个功能可以用来实作透明代理 或用来保护web 服务器。例如
iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT–to-ports 8081
MASQUERADE 改写封包来源IP为防火墙的IP可以指定port 对应的范围进行完此处理动作后直接跳往下一个规则链mangle:postrouting。这个功能与 SNAT 略有不同当进行IP 伪装时不需指定要伪装成哪个 IPIP 会从网卡直接读取当使用拨接连线时IP 通常是由 ISP 公司的 DHCP服务器指派的这个时候 MASQUERADE 特别有用。范例如下
iptables -t nat -A POSTROUTING -p TCP -j MASQUERADE --to-ports 21000-31000
LOG 将数据包相关信息纪录在 /var/log 中详细位置请查阅 /etc/syslog.conf 配置文件进行完此处理动作后将会继续比对其它规则。例如
iptables -A INPUT -p tcp -j LOG --log-prefix “input packet”
SNAT 改写封包来源 IP 为某特定 IP 或 IP 范围可以指定 port 对应的范围进行完此处理动作后将直接跳往下一个规则炼mangle:postrouting。范例如下
iptables -t nat -A POSTROUTING -p tcp -o eth0 -j SNAT --to-source 192.168.10.15-192.168.10.160:2100-3200
DNAT 改写数据包包目的地 IP 为某特定 IP 或 IP 范围可以指定 port 对应的范围进行完此处理动作后将会直接跳往下一个规则链filter:input 或 filter:forward。范例如下
iptables -t nat -A PREROUTING -p tcp -d 15.45.23.67 --dport 80 -j DNAT --to-destination 192.168.10.1-192.168.10.10:80-100
MIRROR 镜像数据包也就是将来源 IP与目的地IP对调后将数据包返回进行完此处理动作后将会中断过滤程序。
QUEUE 中断过滤程序将封包放入队列交给其它程序处理。透过自行开发的处理程序可以进行其它应用例如计算联机费用…等。
RETURN 结束在目前规则链中的过滤程序返回主规则链继续过滤如果把自订规则炼看成是一个子程序那么这个动作就相当于提早结束子程序并返回到主程序中。
MARK 将封包标上某个代号以便提供作为后续过滤的条件判断依据进行完此处理动作后将会继续比对其它规则。
方案
在不改变原集群任何的组件及配置的基础上进行使用iptables进行转发。
分为两部分请求出去的时候转发请求回来的时候处理转发。
将mysql请求发送出去不再由本机处理。
将请求到原地址36.6的3308转发到 3309
iptables -t nat -A PREROUTING -d 10.200.36.6 -p tcp -m tcp --dport 3308 -j DNAT --to-destination mysqlIP:3309说明 修改nat表 在规则链的末尾加入新规则 PREROUTING链匹配来自36.6 端口3308的请求-j 进行DNAT 操作 目的地为 mysqlIP:3309 将对方请求接收后转发回给微服务。
比如bml-dataset 请求原地址的mysql ip端口再由这个ip端口进行回应。
只要是从mysqlip 3309 过来的的通讯 POSTROUTING 到 36.6的3308上出去
iptables -t nat -A POSTROUTING -d mysqlIP -p tcp -m tcp --dport 3309 -j SNAT --to-source 10.200.36.6:3308
优缺点
优点; 对接改造成本比较低执行两个规则即可。
缺点所有的mysql请求都在一个节点上做不到mysql的高可用可接受
综述利大于弊。 场景二 预测服务转发无效
背景
用户在集群上发布了俩个预测服务A和B但是外面访问不到A服务但是可以访问到B服务。
kubect 查看pod 正常无日志输出。
概念
kube-proxy通过观察Kubernetes中service和endpoint对象的变化当有servcie创建时kube-proxy在iptables中追加新的规则。对于service的每一个endpoint会在iptables中追加一条规则设定动作为DNAT将目的地址设置成真正提供服务的pod地址再为servcie追加规则设定动作为跳转到对应的endpoint的规则上
默认情况下kube-proxy随机选择一个后端的服务可以通过iptables中的 -m recent 模块实现session affinity功能通过 -m statistic 模块实现负载均衡时的权重功能
排查过程
在Node节点上实现Pod网络代理维护网络规则和四层负载均衡工作。实现让Pod节点一个或者多个容器对外提供服务
service在逻辑上代表了后端的多个Pod外借通过service访问Pod。service接收到请求就需要kube-proxy完成转发到Pod的。每个Node都会运行kube-proxy服务负责将访问的service的TCP/UDP数据流转发到后端的容器。
第一步 初步怀疑kube-porxy
感觉问题出在kube-proxy上之前有过类似问题排查结果。初步怀疑是转发规则创建问题。
kubectl delete pod kube-porxy
等待kube-porxy重启
。。。。。。。
没起作用。
第二步 重建规则
清空规则重建规则
iptables -F
kubectl delete pod kube-porxy 等待kube-porxy重启
。。。。。。。
没起作用。
第三步 排查创建的规则并且抓包
[rootnf52801602]# iptables -t nat -nvL
Chain PREROUTING (policy ACCEPT 84 packets, 5040 bytes)pkts bytes target prot opt in out source destination
5216M 752G KUBE-SERVICES all -- * * 0.0.0.0/0 0.0.0.0/0 /* kubernetes service portals */37M 2240M CNI-HOSTPORT-DNAT all -- * * 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL查看svc的链 iptables -t nat -nvL KUBE-SERVICES
[rootnf52801602]# iptables -t nat -nvL KUBE-SERVICES
Chain KUBE-SERVICES (2 references)pkts bytes target prot opt in out source destination 0 0 KUBE-MARK-MASQ tcp -- * * !10.233.64.0/18 10.233.0.55 /* default/redis-cluster-proxy:proxy cluster IP */ tcp dpt:77770 0 KUBE-SVC-3FNBBYIZCXANEL3B tcp -- * * 0.0.0.0/0 10.233.0.55 /* default/redis-cluster-proxy:proxy cluster IP */ tcp dpt:777710.233.0.55 为svice ip
查看 KUBE-SVC-3FNBBYIZCXANEL3B链
[rootnf52801602]# iptables -t nat -nvL KUBE-SVC-3FNBBYIZCXANEL3B
Chain KUBE-SVC-3FNBBYIZCXANEL3B (2 references)pkts bytes target prot opt in out source destination 0 0 KUBE-SEP-E3VN2RK7BFTPB2QU all -- * * 0.0.0.0/0 0.0.0.0/0 statistic mode random probability 0.500000000000 0 KUBE-SEP-QWICKHRY75ZBMBDW all -- * * 0.0.0.0/0 0.0.0.0/0 发现2个pod是随机0.5概率
查看 KUBE-SEP-E3VN2RK7BFTPB2QU自定义链
[rootnf52801602]# iptables -t nat -nvL KUBE-SEP-E3VN2RK7BFTPB2QU
Chain KUBE-SEP-E3VN2RK7BFTPB2QU (1 references)pkts bytes target prot opt in out source destination 0 0 KUBE-MARK-MASQ all -- * * 10.233.64.160 0.0.0.0/0 0 0 DNAT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp to:10.233.64.160:7777发现DNAT转发到10.233.64.160:7777 正是上面的 pod ip
SNAT
[rootnf52801602]# iptables -t nat -nvL KUBE-POSTROUTING
Chain KUBE-POSTROUTING (1 references)pkts bytes target prot opt in out source destination 0 0 MASQUERADE all -- * * 0.0.0.0/0 0.0.0.0/0 /* kubernetes service traffic requiring SNAT */ mark match 0x4000/0x4000
[rootyq01-aip-aikefu08 ~]# ok 没有问题。
抓包 tcpdump,使用wireshark分析抓到了业务方请求。。
请求确实已经到达了我方。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-99If7b73-1680159294305)(null)]
对pod中的veth 设备对抓包发现没有请求。
#!/bin/bash
for container in $(docker ps -q); do iflinkdocker exec -it $container bash -c cat /sys/class/net/eth0/iflink iflinkecho $iflink|tr -d \r vethgrep -l $iflink /sys/class/net/veth*/ifindex vethecho $veth|sed -e s;^.*net/\(.*\)/ifindex$;\1; echo $container:$veth
done那就抓flannel.1网桥
tcpdump -i flannel.1 -nn icmp分析一波也有业务方请求。。
继续下一波
抓去cni,发现cni没有收到请求。
比较诡异重启flannel.1 与cni后正常。
其他场景
1、k8s集群证书过期k8s证书相关
2、api-server宕掉。
3、单节点容器数量上限。
4、ipvs替换iptables.
5、k8s集群服务访问集群外服务coredns映射。 结尾
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Rgbz5d1w-1680159294252)(null)]
今天我的分享就到这里谢谢大家