在 Docker 或虚拟机中托管集群时的 Telepresence 网络排障指南:递归路由问题与桥接网络方案
云原生开发工具微服务网络【免费下载链接】telepresenceLocal development against a remote Kubernetes or OpenShift cluster项目地址https://gitcode.com/gh_mirrors/te/telepresence点击查看免费下载导读在本地开发环境中开发者经常将 Kubernetes 集群跑在 Docker 容器Docker Desktop、Kind、Minikube、k3s或虚拟机VirtualBox Vagrant k3s里再通过 Telepresence 连接到该集群进行远程开发。这种集群与客户端同机的部署模式会引发一类典型的网络问题Telepresence 创建的虚拟网络接口VIF与集群节点自身接口同时映射集群子网导致请求在 VIF 与集群之间无限递归最终表现为超时并拖垮其他连接。本文基于 Telepresence 官方文档 docs/howtos/cluster-in-vm.md 展开完整讲解递归问题的成因、两种解决方案routing.recursionBlockDuration配置与 L2 桥接网络并给出可复制的 Vagrant VirtualBox k3s 三节点集群示例与内嵌 Docker Registry 配置。读完本文你将能够独立排查本地托管集群的连接超时问题并搭建一套支持 Telepresence 直连的本地集群环境。问题背景VIF 将集群子网映射到宿主机Telepresence 在连接集群时会在工作站的用户态守护进程中创建一个虚拟网络接口VIF。根据 docs/reference/routing.md 的说明VIF 是一个 TUN 设备它以 L3 IP 包的形式与工作站通信守护进程识别其中的 UDP 和 TCP 包并通过加密的 gRPC API 将其载荷隧道传输给 traffic-manager由后者在集群中建立对应的连接。VIF 负责将集群的服务子网与 Pod 子网映射到宿主机从而让本地任何工具都能直接访问集群服务同时它还接管 DNS 请求使集群内的服务名得以解析。当集群运行在宿主机本机Docker Desktop、Kind、Minikube、k3s 等时宿主机上的设备包括 VIF对集群节点也是可达的。这就带来了网络问题的根源集群节点的视角下映射集群子网的接口不止一个——节点自身已有的接口是一组Telepresence 的 VIF 又映射了一遍。递归连接的形成过程原文档用一个运行在无头 VirtualBox 虚拟机中的 k3s 集群作为例子来说明问题该虚拟机使用 host-only 网络这种网络同时允许宿主机到虚拟机、虚拟机到宿主机的双向连接。也就是说集群能够访问宿主机的网络而在 Telepresence 连接期间自然也能访问宿主机的 VIF。递归的完整链条如下一个请求到达 Telepresence其目标地址落在 VIF 映射的某个子网内该请求被路由到集群集群中找不到能够处理该请求的对应监听器例如某个服务已经不存在或没有对应规则集群最终会尝试访问宿主网络从而发现VIFVIF 再次把这个请求路由回集群——递归就此开始。递归的最终结果大概率是请求超时。更糟糕的是递归过程会消耗大量资源短时间内产生大量快速连接请求因此还会以糟糕的方式影响到其他连接。docs/troubleshooting.md 也把这个问题列为常见故障之一并引导用户查阅本文档对应的 howtodocs/troubleshooting.md。值得注意的是Telepresence 在更早的版本中就已经内置了针对递归的检测逻辑根据 docs/reference/routing.md当本地集群的 DNS 解析器解析失败时它可能回退到查询宿主机网络Telepresence 通过发送一个初始 DNS 查询解析tel2-recursion-check.kube-system来探测递归并对疑似递归的查询返回 NXNAME 记录。而本文档讨论的则是连接级L4路由层面的递归及其两种解决方案。方案一设置routing.recursionBlockDuration配置方式与原理要阻止 VIF 内的递归连接只需在客户端配置文件中设置client.routing.recursionBlockDuration属性将其设为一个较短的超时值client: routing: recursionBlockDuration: 1ms该配置的作用是在某个 IP:PORT 组合的连接建立之后立即临时阻止针对该 IP:PORT 的新连接阻止持续指定的时长。由于递归循环中同一目标地址会快速反复发起连接这种临时阻断能够切断循环回到 VIF 的连接从而有效终结递归。对于大多数场景1ms的取值通常就足够了。根据 docs/reference/config.md 中的client.routing配置表recursionBlockDuration的类型是 durationGo duration 字符串支持ms、s、m、h等单位后缀也支持1.5h、2h45m这类分数或组合写法表中没有默认值即默认不生效需要显式配置。源码层面的注意点该配置已被废弃需要特别提醒的是从当前仓库源码看recursionBlockDuration已经在较新的版本中被废弃。在 pkg/client/config.go 的Routing结构体中RecursionBlockDuration与RecursionBlockTreads字段都带有明确的注释// Deprecated: no longer used. Use the route-controller DaemonSet instead. RecursionBlockDuration time.Duration json:recursionBlockDuration,omitempty // Deprecated: no longer used. Use the route-controller DaemonSet instead. RecursionBlockTreads int json:recursionBlockTreads,omitempty在配置校验阶段pkg/client/config.go如果检测到这两个字段被设置客户端会打印警告日志提示改用 route-controller DaemonSetrouting.recursionBlockDuration and routing.recursionBlockTreads are deprecated and no longer used; use the route-controller DaemonSet instead (see docs/reference/route-controller.md)这与 CHANGELOG.yml 中记录的功能演进一致该配置最初是作为防止 VIF 递归的特性引入的CHANGELOG.yml此后递归防护机制被更彻底的方案取代。现代替代方案route-controller DaemonSet根据 docs/reference/route-controller.md递归问题的现代解决方案是route-controller一个以 host networking 运行、拥有NET_ADMIN权限的 DaemonSet部署在集群的每个节点上。它在启动时发现集群的服务 CIDR并通过 netlinkgithub.com/google/nftables不依赖 iptables/nft 二进制编程一个专用的telepresencenftables 表其forwardhook 链中的规则会丢弃任何目标地址落在服务 CIDR 集合内的转发包。这样被删除或从未分配的服务 IP 不会再经由节点的默认路由逃逸回宿主机 VIF从根源上切断了递归循环。route-controller 的启用方式在安装 traffic-manager 的 Helm chart 时指定# 强制启用例如远程集群也存在同样问题时 helm upgrade --install traffic-manager charts/telepresence-oss \ --set routeController.enabledtrue # 强制禁用例如在本地集群选择不使用 helm upgrade --install traffic-manager charts/telepresence-oss \ --set routeController.enabledfalserouteController.enabled默认值为null自动检测当image.registry被设为local或localhost:*这类本地集群约定地址时自动启用相关模板位于 charts/telepresence-oss/templates/routecontroller-daemonset.yaml。因此如果你当前使用的 Telepresence 版本已包含 route-controller递归防护的首选方式是启用它而不是设置已被废弃的recursionBlockDuration。方案二创建桥接网络L2 桥接原理routing.recursionBlockDuration之外的另一种方案是创建桥接网络。桥接网络作为一个链路层L2设备在网段之间转发流量。通过创建桥接网络可以让 Kubernetes 集群绕过宿主机的网络协议栈直接连接到与宿主机相同的路由器上。这样集群节点不再能访问宿主机的 VIF递归循环从网络拓扑层面被消除。具体做法是修改运行集群节点的虚拟机guest的网络设置使其直接连接宿主机上的物理网络设备。桥接的具体配置方式取决于所使用的虚拟化方案VirtualBox、VMware、QEMU 等各不相同。Vagrant VirtualBox k3s 完整示例原文档提供了一个完整的Vagrantfile示例用 Vagrant 在 VirtualBox 中拉起一个 k3s server 节点和两个 agent 节点全部使用无头headless模式 桥接网络并额外配置了让集群承载 Docker Registry 所需的内容如果希望节省带宽这一点非常实用。以下是完整的Vagrantfile# -*- mode: ruby -*- # vi: set ftruby : # bridge is the name of the hosts default network device $bridge wlp5s0 # default_route should be the IP of the hosts default route. $default_route 192.168.1.1 # nameserver must be the IP of an external DNS, such as 8.8.8.8 $nameserver 8.8.8.8 # server_name should also be added to the hosts /etc/hosts file and point to the server_ip # for easy access when pushing docker images server_name multi # static IPs for the server and agents. Those IPs must be on the default routers subnet server_ip 192.168.1.110 agents { agent1 192.168.1.111, agent2 192.168.1.112, } # Extra parameters in INSTALL_K3S_EXEC variable because of # K3s picking up the wrong interface when starting server and agent # https://github.com/alexellis/k3sup/issues/306 server_script -SHELL sudo -i apk add curl export INSTALL_K3S_EXEC--bind-address#{server_ip} --node-external-ip#{server_ip} --flannel-ifaceeth1 mkdir -p /etc/rancher/k3s cat -EOF /etc/rancher/k3s/registries.yaml mirrors: multi:5000: endpoint: - http://#{server_ip}:5000 EOF curl -sfL https://get.k3s.io | sh - echo Sleeping for 5 seconds to wait for k3s to start sleep 5 cp /var/lib/rancher/k3s/server/token /vagrant_shared cp /etc/rancher/k3s/k3s.yaml /vagrant_shared cp /etc/rancher/k3s/registries.yaml /vagrant_shared SHELL agent_script -SHELL sudo -i apk add curl export K3S_TOKEN_FILE/vagrant_shared/token export K3S_URLhttps://#{server_ip}:6443 export INSTALL_K3S_EXEC--flannel-ifaceeth1 mkdir -p /etc/rancher/k3s cat -EOF /etc/rancher/k3s/registries.yaml mirrors: multi:5000: endpoint: - http://#{server_ip}:5000 EOF curl -sfL https://get.k3s.io | sh - SHELL def config_vm(name, ip, script, vm) # The network_script has two objectives: # 1. Ensure that the guests default route is the bridged network (bypass the network of the host) # 2. Ensure that the DNS points to an external DNS service, as opposed to the DNS of the host that # the NAT network provides. network_script -SHELL sudo -i ip route delete default 21 /dev/null || true; ip route add default via #{$default_route} cp /etc/resolv.conf /etc/resolv.conf.orig sed s/^nameserver.*/nameserver #{$nameserver}/ /etc/resolv.conf.orig /etc/resolv.conf SHELL vm.hostname name vm.network public_network, bridge: $bridge, ip: ip vm.synced_folder ./shared, /vagrant_shared vm.provider virtualbox do |vb| vb.memory 4096 vb.cpus 2 end vm.provision shell, inline: script vm.provision shell, inline: network_script, run: always end Vagrant.configure(2) do |config| config.vm.box generic/alpine314 config.vm.define server, primary: true do |server| config_vm(server_name, server_ip, server_script, server.vm) end agents.each do |agent_name, agent_ip| config.vm.define agent_name do |agent| config_vm(agent_name, agent_ip, agent_script, agent.vm) end end end该示例的关键点如下$bridge宿主机默认网络设备的名称示例为wlp5s0Vagrant 会通过public_network让 guest 桥接到该物理网卡$default_route宿主机默认路由的 IPnetwork_script会删除 guest 中原有的默认路由NAT 网络提供改为经由桥接网络直达宿主机的路由器$nameserver外部 DNS 服务器 IP如8.8.8.8避免 guest 使用 NAT 网络提供、指向宿主机的 DNS静态 IPserver 与 agent 的 IP 必须在默认路由器所在的子网内示例为192.168.1.110、.111、.112server_name multi同时需要添加到宿主机的/etc/hosts指向server_ip这样向multi:5000推送 Docker 镜像时更方便config.vm.box generic/alpine314使用 Alpine 3.14 的通用 Vagrant boxvm.provision shell, inline: network_script, run: always网络脚本在每次vagrant up/reload时都会执行确保默认路由与 DNS 设置持续生效。k3s 与 Docker Registry 的配合由于节点使用桥接网络直接连接宿主机路由器集群不再经由宿主机的网络栈因此 VIF 递归在拓扑上被切断。而为了让集群能够承载一个 Docker Registry便于本地推送镜像、节省带宽server 与 agent 脚本中都会写入 k3s 的registries.yaml将multi:5000镜像仓库的 endpoint 指向http://#{server_ip}:5000。集群启动并运行后需要执行kubectl -f registry.yaml应用下面的 Kubernetes 清单来部署 Registry一个ReplicationController 一个LoadBalancer类型的ServiceapiVersion: v1 kind: ReplicationController metadata: name: kube-registry-v0 namespace: kube-system labels: k8s-app: kube-registry version: v0 spec: replicas: 1 selector: app: kube-registry version: v0 template: metadata: labels: app: kube-registry version: v0 spec: containers: - name: registry image: registry:2 resources: limits: cpu: 100m memory: 200Mi env: - name: REGISTRY_HTTP_ADDR value: :5000 - name: REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY value: /var/lib/registry volumeMounts: - name: image-store mountPath: /var/lib/registry ports: - containerPort: 5000 name: registry protocol: TCP volumes: - name: image-store hostPath: path: /var/lib/registry-storage --- apiVersion: v1 kind: Service metadata: name: kube-registry namespace: kube-system labels: app: kube-registry kubernetes.io/name: KubeRegistry spec: selector: app: kube-registry ports: - name: registry port: 5000 targetPort: 5000 protocol: TCP type: LoadBalancer该清单部署的是标准的registry:2镜像Registry 监听:5000镜像数据存储在宿主路径/var/lib/registry-storage通过 hostPath 卷挂载到容器的/var/lib/registryService 以 LoadBalancer 类型暴露 5000 端口。方案选择与适用场景对比方案原理层级改动范围适用场景routing.recursionBlockDuration客户端连接级临时阻断仅需修改客户端config.yml快速应急注意该配置在当前仓库版本中已被废弃route-controller DaemonSet集群节点 nftables FORWARD 丢弃安装 traffic-manager 时通过 Helm values 启用当前版本的推荐方案从根因上阻止服务 CIDR 逃逸L2 桥接网络网络拓扑层隔离修改虚拟机的网络配置Vagrantfile 等本地虚拟化集群VirtualBox/VMware 等希望集群与宿主机共用路由器、彻底避免 VIF 可达性三种思路并不互斥桥接网络适合从拓扑上彻底规避问题如果集群已经以 NAT/host-only 方式运行不便改动则优先考虑启用 route-controllerrecursionBlockDuration作为历史方案仍然在文档与配置结构中保留见 docs/reference/config.md 的配置表但源码已明确标注其废弃状态。小结本地托管的 Kubernetes 集群Docker Desktop、Kind、Minikube、k3s或 VirtualBox/VMware 虚拟机在使用 Telepresence 时VIF 与集群节点接口对同一批集群子网的重复映射会引发连接递归导致请求超时并殃及其他连接。解决思路分为两条主线一是在客户端配置routing.recursionBlockDuration历史方案当前版本推荐改用 route-controller DaemonSet在节点 nftables 层直接丢弃逃逸的服务 CIDR 流量二是从网络拓扑入手通过 L2 桥接网络让集群绕过宿主机的网络栈、直接连接宿主机路由器。本文给出的 Vagrant VirtualBox k3s 三节点桥接集群示例及配套的 Registry 清单可以直接作为搭建本地开发集群的参考起点。若你在实际部署中遇到连接超时问题建议先对照 docs/troubleshooting.md 排查再根据集群的托管方式选择上述方案之一实施。赞分享云原生开发工具微服务网络【免费下载链接】telepresenceLocal development against a remote Kubernetes or OpenShift cluster项目地址https://gitcode.com/gh_mirrors/te/telepresence点击查看免费下载相关推荐Telepresence项目在Docker或虚拟机中托管Kubernetes集群的网络配置指南Telepresence项目在Docker或虚拟机中托管Kubernetes集群的网络配置指南 概述 在使用Telepresence与本地托管的Kuberne云原生开发工具微服务网络上一篇STDC-Seg训练技巧10个提升分割精度的实用方法下一篇如何精准提升算法刷题效率LeetCodeRating的5步实战指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

superpowers:让Codex CLI从会写代码到会做工程的技能库

superpowers:让Codex CLI从会写代码到会做工程的技能库

从第一次在终端里敲下codex那条命令开始,我一直觉得这类 AI 编程助手有种"聪明但不太会用"的感觉:你问它一句,它能答得像模像样;但真让它独立把一个功能从规划到落地做完,它经常会走一步看一步,甚…

2026/9/30 23:36:49 阅读更多 →
【证书】证书相关知识

【证书】证书相关知识

1.公钥1.1 本质就是很大的数,有数字e, n, 看到的如下的一些.pem文件中的密钥,是数经过base64编码得到的字符串,便于文本传输-----BEGIN RSA PRIVATE KEY-----xxxxx......-----END RSA PRIVATE KEY-----1.2 作用1.2.1 验证签名验签…

2026/10/1 6:21:34 阅读更多 →
AI工程从零构建:从数据到部署的完整实践指南

AI工程从零构建:从数据到部署的完整实践指南

1. 先看清AI工程这件事的本质1.1 "AI工程"到底在解决什么问题很多人第一次看到"ai-engineering-from-scratch"这个项目名时,第一反应是:这又是一个教你怎么调大模型的仓库吧。实际上,真正做下来之后你会发现,…

2026/10/1 2:17:20 阅读更多 →

最新新闻

OpenWrite 多平台分发:搭建技术博客发布流水线

OpenWrite 多平台分发:搭建技术博客发布流水线

上周有人问我一个特别具体的问题:一篇两千多字的技术稿,为什么能在草稿箱里躺三天还没发出去。我让他把过程复述一遍,答案就出来了——他在一个平台写完,复制到第二个平台,发现代码块高亮没了;回去改一遍&a…

2026/10/1 17:58:26 阅读更多 →
PostgreSQL 16 PITR 实战:基于 WAL 归档的任意时间点恢复与脚本化实现

PostgreSQL 16 PITR 实战:基于 WAL 归档的任意时间点恢复与脚本化实现

数据库出故障时最怕什么?不是磁盘坏道,也不是机房断电,而是备份明明有,恢复出来的数据却停在了错误的时点。像“昨晚凌晨的全量备份 今天白天的一堆 binlog”这种经典组合,在 PostgreSQL 里对应的就是 Point-in-Time …

2026/10/1 17:58:26 阅读更多 →
PostgreSQL 16 PITR时间点恢复实战:从WAL归档到误删数据精确恢复

PostgreSQL 16 PITR时间点恢复实战:从WAL归档到误删数据精确恢复

1. 先搞清楚:PostgreSQL 16 的 PITR 到底能帮你解决什么问题说句实在话,我见过太多团队在 PostgreSQL 上跑了几年,备份策略还停留在“每天凌晨pg_dump一次,出事了就恢复到昨晚”。平时看着没啥问题,可真到关键时刻——…

2026/10/1 17:58:26 阅读更多 →
数据库约束条件实战:主键、唯一、外键与CHECK如何选型

数据库约束条件实战:主键、唯一、外键与CHECK如何选型

做数据库这么多年,我接手过的最头疼的一张表,是一张几千万行的订单流水表。上线前三个月一切岁月静好,等数据量一上来,各种问题接踵而至:订单号重复、关联用户对不上、状态值永远是"pending"没法推进。后来排…

2026/10/1 17:58:26 阅读更多 →
MySQL自动备份实战:用Navicat配置定时备份与恢复

MySQL自动备份实战:用Navicat配置定时备份与恢复

数据库备份这件事,真的不能等出事了才想起来做了这么多年开发,我一直觉得数据库备份是那种“大家都知道重要、但很少有人认真对待”的事。直到有一次我帮朋友恢复一个跑了两年的业务库,因为备份文件是三个月前的,最后硬生生丢了将…

2026/10/1 17:58:26 阅读更多 →
MySQL索引面试高频考点:B+树、覆盖索引与索引失效实战解析

MySQL索引面试高频考点:B+树、覆盖索引与索引失效实战解析

1. 聊聊为什么面试官总爱问索引 先说实话,我在后台看到有人留言问"能不能系统整理一下索引相关的面试题",刚好最近也在帮团队做技术面试,前前后后面了差不多三十个候选人,索引这块几乎是必考题。不是面试官偷懒&#xf…

2026/10/1 17:57:26 阅读更多 →

日新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 0:00:30 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 0:00:30 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 1:01:17 阅读更多 →

周新闻

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解 【免费下载链接】spirula-studio Cross-vendor 3D Gaussian Splatting trainer - video to splat to mesh, Vulkan or CUDA. 项目地址: https://gitcode.com/GitHub_Trending/sp/spirula-studio Sp…

2026/9/30 13:14:22 阅读更多 →
SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南 模板网站太丑不够用?别急着加滤镜,那是治标不治本。很多老板盯着后台流量掉得眼红,却还在纠结首页Banner的圆角是不是3像素。这就像穿着西装去挖土,姿势不对,努力白费。我整理这份 速查手册…

2026/9/30 18:13:06 阅读更多 →
FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏

FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏

FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏 【免费下载链接】FireRed-OpenStoryline FireRed-OpenStoryline is an AI video editing agent that transforms manual editing into intention-driven directing through natural language …

2026/9/30 13:14:49 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 0:00:30 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 0:00:30 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 1:01:17 阅读更多 →