AI基础设施可观测性实战:Docker部署与技能扫描漏报排查

发布时间:2026/9/30 5:42:35
AI基础设施可观测性实战:Docker部署与技能扫描漏报排查 1. 项目背景与核心定位先说清楚一件事AI-Infra-Guard 到底是个什么东西我第一次看到这个项目名的时候直觉告诉我它跟“AI 基础设施的守护”有关但真正上手之后才确认它不是传统意义上那种只盯着 CPU、内存、磁盘的监控系统而是一套面向 AI 应用的可观测性 技能资产盘点工具。它解决的痛点非常具体——现在大家用 Docker 把 DeepSeek、Ollama、Dify、Langfuse 这些服务一个个拉起来组成一条 AI 应用链路之后你会发现一个很尴尬的问题整个链路上跑着哪些服务、哪些接口对外暴露了、哪个节点具备所谓的“技能”Skill、哪个接口其实是没人管的老旧 API——你完全不知道。这就像你搬进一套精装房开关插座一堆但开发商没给你配电箱图。你只知道灯能亮、空调能开但哪一路电对应哪个房间、哪里埋了隐患全靠猜。AI-Infra-Guard 干的事就是给你画一张配电箱图顺便告诉你哪一路的电流已经超了。再解释一下“技能扫描”这个词。在 AI Agent 或大模型应用场景里“技能”指的是一个服务节点对外提供的能力集合比如“联网搜索”“代码执行”“文档解析”“数据库查询”等。这些技能通常以 API 接口、插件、工具调用的形式存在。技能扫描做的就是把链路里所有节点暴露的技能全部扫出来登记造册。这个能力在平时看起来没那么起眼一旦你维护的 AI 应用出问题、需要定位某个技能为什么不可用或者你想在接入新节点之前做个安全影响评估你就知道这玩意儿有多值钱了。这篇博文里我会完整走一遍 AI-Infra-Guard 的 Docker 部署流程把技能扫描的配置要点和输出解读讲透最后把我一次真实发生的“漏报”事件完整复盘。漏报这个词做监控的人一听就紧张——该报的没报比误报更让人头疼。那次排查花了我一个下午最后定位到的根因说穿了其实是一个特别低级的配置组合问题但如果你没经历过真的很难想到。这种教训我觉得比单纯跑通部署更有价值。适合看这篇内容的朋友画像大概是这三类一是自己折腾本地大模型部署、用 Docker 管了好几个 AI 服务的爱好者二是团队里负责 AI 应用链路运维、需要做服务梳理和暴露面排查的工程师三是对可观测性和技能资产管理感兴趣想找个顺手工具落地验证的开发者。无论你是哪一类我的建议是跟着走一遍半小时内能跑起来但它能帮你省下的排查时间往后只会越来越多。2. 整体设计与方案选型2.1 为什么选 Docker 一键起先说我第一次看到这个项目时的第一反应为什么作者不搞个二进制直接跑非要套一层 Docker等我细看了它的依赖清单就明白了。AI-Infra-Guard 底层用了好几类组件除了主程序之外还依赖一套用于流量采集的网络代理组件和一个用于指纹识别的规则库。这套东西如果手动装光是版本匹配就能折腾半天。假设你在一台机器上装好了换一台部署同样的报错可能会以不同顺序再出现一遍——这种环境漂移问题做运维的都懂。Docker 的核心价值在于把运行环境连同依赖一起打包成不可变单元。AI-Infra-Guard 的镜像里作者已经把主程序、扫描引擎、基础规则库、运行时需要的 Python/C 库全部固化好了。你拉下来跑和你在我机器上跑得到的底层环境是一致的这能砍掉至少 80% 的“在我这能跑”问题。另外这类工具天生就要捞网络流量、做端口探测需要访问宿主机的网络命名空间。Docker 的--network host模式在这种场景下特别好使容器共享宿主机网络栈不需要复杂的端口映射配置也不用担心容器内看到的内网地址和宿主机不一致。说实话如果这个项目只提供一个裸的二进制让我自己配环境我大概率不会认真用起来。但有了 Docker 一键起整个工具链的试用成本被压得很低我可以随时起一个、扫一遍、拆掉完全不污染宿主机的环境。这种“用完即弃”的体验对我这种经常在别人服务器上做排查的人来说特别重要。2.2 镜像里都装了什么拉下镜像之后我特意进了容器看了一下目录结构里面大致分几个部分核心扫描引擎负责端口探测、服务识别、技能指纹匹配、知识规则库JSON 格式的技能指纹定义、报告生成模块把扫描结果渲染成 Markdown 或 JSON、以及一个轻量的 Web 控制台用来查看扫描历史和技能资产清单。规则库这部分值得多说一句它决定了扫描器能不能“认出”某个技能。技能指纹的定义思路跟杀毒软件的病毒库有点像。每种技能会有几个维度的识别特征比如接口路径特征/v1/chat/completions、响应头特征、返回 JSON 的结构特征、甚至请求超时后的行为特征。AI-Infra-Guard 的规则库里对这些特征的组合做了权重评分综合打分超过阈值才判定为某类技能。这种设计比单纯匹配 URL 关键字要稳得多因为实际场景中同一个技能可能有多种路由写法光靠字符串匹配会漏掉一大片。镜像本身也比较克制我查了一下整体大小控制在几百 MB 的量级没有把任何模型权重塞进去。也就是说它只做链路技能的扫描和盘点不做推理。这个定位我觉得很聪明——绝不抢别人的活只守好自己的边界。2.3 技能扫描的工作机制技能扫描从原理上分两块主动探测和被动监听。主动探测就是扫描器主动向目标端口发送构造好的请求通过响应内容来匹配指纹库。这种方式的优点是快、直接、覆盖面广缺点是可能会对脆弱的服务造成压力甚至触发对方的告警。被动监听则是把 AI-Infra-Guard 部署在链路的关键节点上通过分析流经的网络流量来识别技能完全不打扰业务本身但能看到的范围受限只能看到经过你监听点的流量。AI-Infra-Guard 默认是两条腿走路先主动探测一遍拿到基础资产清单再根据配置决定是否启用被动监听做补充。主动扫描的阶段分得很清楚首先是端口发现把目标 IP 段或容器的所有开放端口摸出来然后是服务识别对每个端口做 banner 抓取和协议探测搞清楚这个端口上跑的是什么最后才是技能匹配把之前拿到的信息丢给指纹引擎逐条过规则库最终打上标签。这个过程有点像你进一个园区先看哪些门开着再走到门口看挂的是什么牌子的门牌最后敲门问里面到底是做什么的。我在实际使用中觉得被动监听这一块最适合部署Ingress网关的流量镜像口或者 AI 应用链路的汇聚层。不过这里有个现实问题不是所有环境都方便在交换机上做流量镜像Docker 部署模式下的被动监听能力实际上是通过宿主机抓包实现的如果你对容器网络没有那么深的控制力老老实实用主动扫描模式也能出活。我把这种设计理解为“最小阻力路径”——先保证基本功能人人可用再给进阶用户留高级玩法。3. 部署实操从拉镜像到跑通3.1 环境准备我这次部署用的是一台 Ubuntu 22.04 的服务器4 核 8G 内存Docker 版本是 24.0.xDocker Compose v2 也一并装了。如果你在 Windows 上用 Docker Desktop思路完全一样只是路径映射和端口访问要注意一下宿主机 IP 的差异。第一步先把 Docker 装好确认 Docker daemon 正常docker --version docker compose version docker ps这三条命令第一条确认 CLI 可用第二条确认 compose 插件在第三条确认 daemon 起来了。如果第三条报错大概率是 Docker Desktop 的虚拟化支持没开启。Windows 上常见的是 BIOS 里没开 Intel VT-x/VT-d或者 Hyper-V / WSL2 功能没启用。这类报错在社区里能看到很多基本都和虚拟化支持有关先把 BIOS 的虚拟化开关打开再在 Windows 功能里勾选“适用于 Linux 的 Windows 子系统”和“虚拟机平台”重启之后基本就能解决。准备好之后我给 AI-Infra-Guard 建了一个独立的工作目录规划好数据挂载点。为什么单独建目录因为它运行会产生规则库缓存、扫描报告、临时数据全堆在用户目录下面会很乱。我的目录规划是这样的mkdir -p /opt/ai-infra-guard/{data,reports,config}data放它的缓存和资产管理数据reports放历史扫描报告config放自定义的规则和配置文件。这样目录职责清晰备份也不会眉毛胡子一把抓。3.2 用 Docker 一键拉起来AI-Infra-Guard 官方提供了两种启动方式。一种是直接用docker run适合快速试用另一种是用 Compose 编排适合正式使用、挂持久化卷。我先用直跑的方式验证镜像没问题再切到 Compose。直跑的命令长这样docker run -d \ --name ai-infra-guard \ --network host \ --restart unless-stopped \ -v /opt/ai-infra-guard/data:/app/data \ -v /opt/ai-infra-guard/reports:/app/reports \ -v /opt/ai-infra-guard/config:/app/config \ -e AIG_MODEstandard \ -e AIG_WEB_PORT8088 \ ai-infra-guard:latest这条命令里几个关键参数我拆开讲一下。--network host是重中之重它让容器直接用宿主机的网络栈。这样做的好处前面说过一是端口发现和主动探测能直接扫宿主机所在的内网网段不需要容器网络再做一层 NAT避免了很多网络可达性的诡异问题二是 Web 控制台直接监听在宿主机的 8088 端口上访问http://服务器IP:8088就进去了不用-p做端口映射少了一层配置。--restart unless-stopped保证宿主机重启后容器自动拉起这个参数我认为是对这类常驻工具最基本的尊重——你总不希望一次断电之后就忘记把它拉起来。启动之后我建议立刻做两件事。第一件看容器日志确认启动流程没有报错docker logs -f ai-infra-guard正常日志里能看到 Web 控制台监听地址、规则库加载条数、扫描引擎初始化成功之类的信息。第二件确认 Web 控制台活着curl -I http://127.0.0.1:8088如果返回HTTP/1.1 200 OK说明服务已经健康。如果 curl 不通优先查容器日志不要瞎猜。3.3 用 Compose 固化配置容器跑通只是个开始正式使用我更推荐用 Compose 把配置固化下来方便后续迁移和团队协作。我的docker-compose.yml长这样version: 3.8 services: ai-infra-guard: image: ai-infra-guard:latest container_name: ai-infra-guard network_mode: host restart: unless-stopped environment: - AIG_MODEstandard - AIG_WEB_PORT8088 - AIG_LOG_LEVELinfo - AIG_SCAN_CONCURRENCY200 volumes: - /opt/ai-infra-guard/data:/app/data - /opt/ai-infra-guard/reports:/app/reports - /opt/ai-infra-guard/config:/app/config注意version: 3.8这里新版 Docker Compose 其实已经不太推荐写这个字段了但写上不影响删掉也能跑。我用 Compose 之后启动命令简化成一句docker compose up -d排错的时候看日志同样简单docker compose logs -f参数AIG_SCAN_CONCURRENCY200是控制扫描并发数的说白了就是同时有多少个探测任务在跑。如果你的目标网络比较大可以调到 500但注意被扫描方是小水管的话并发太高会让对方以为是攻击流量反而触发拦截我的建议是从 200 起步观察一下再调整。这个参数最典型的影响就是扫描总耗时的变化我在一个 C 段网段大约 250 个活跃 IP上实测200 并发大概 8 分钟能扫完一层调到 500 能压缩到 5 分钟内但再往上收益就递减了而且误报率会悄悄上升——大概是并发探测时指纹匹配的上下文切换太频繁导致的结果。3.4 第一次接入配置Web 控制台起来之后第一件事是填写要扫描的目标范围。在配置页面里你可以填一个 IP 网段也可以填一个容器的网络别名。如果 AI-Infra-Guard 和目标服务跑在同一台机器的 Docker 里这里有个小坑容器名不能直接拿来当扫描目标因为在 host 网络模式下容器名解析不一定可靠。我的做法是先查到目标容器的实际 IPdocker inspect -f {{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}} 目标容器名拿到 IP 之后填进去就绕开了容器名解析的问题。如果目标服务也监听在宿主机上直接填127.0.0.1就行。配置完保存它会自动触发一轮全量扫描。扫描过程可以在控制台实时看进度跑完之后会生成一份报告里面有发现的技能清单、端口列表、风险项提示。第一次跑完之后我强烈建议你把报告导出成 JSON 存档之后每次扫描完跟基线做 diff这比盯原始日志直观得多。4. 技能扫描实战让工具真正出活4.1 扫描前要做两件反直觉的事技能扫描之前我吃过一个亏拿到工具就急着扫结果扫出来的结果一塌糊涂。后来我养成了两个习惯先说第一个把目标的技能指纹规则做一次“烟测”。什么意思就是拿你自己已经知道的一个技能接口做验证看看扫描器能不能正确识别。比如你知道某个服务有个接口GET /v1/tools/search你先在规则库里搜一下有没有对应指纹再手动构造一个请求对比指纹规则确保规则本身没问题最后再扫描。第二个习惯是配一个“排除清单”。扫描器不会主动辨别“这是测试接口不用管”和“这是正式技能必须扫”它会一视同仁地探测和标记。如果你不想让它把内网的数据库、缓存服务都翻出来打上技能标签就在排除清单里把这些端口写掉。比如 3306、6379 这种不是 AI 技能相关的端口扫出来意义不大还容易触发不必要的告警。配置排除清单不是限制工具而是让它聚焦在真正有价值的 AI 服务节点上否则报告里塞满无关信息真正重要的反而被淹没了。4.2 执行扫描和结果解读当我配置好目标、规则、排除清单后点一下“立即扫描”它会先在任务列表里以 Pending 状态排队然后变 Running。点进任务详情页能看到实时状态每一行是一个探测项的反馈包括端口状态、服务类型、技能匹配结果和置信度。进度条走完之后状态变成 Finished这时候就能进报告页了。报告最核心的部分是一张技能资产表大致长这样节点地址开放端口服务类型识别技能置信度192.168.1.218000HTTP对话补全接口0.97192.168.1.218000HTTP文档解析工具0.83192.168.1.229090gRPC向量检索服务0.95这里要注意同一个端口可能对应多个技能因为同一个服务进程下可能挂了多个 API 路由。比如一个 AI 网关既可以转发对话补全请求又暴露了文档解析工具那么两个技能都会被标出来。置信度这个字段要重视低于 0.8 的应该人工复核因为它在指纹匹配上有模糊地带。报告还会展示“暴露面评分”——这个分数不是越高越好而是越高说明暴露的技能越多需要人工排查是不是正常的。我见过刚部署完就跑出 7.8 分的情况点开一看有个测试环境的入口被暴露在公网网卡上了这种就是送分题必须处理。4.3 怎么判断扫描结果质量判断一次扫描做得够不够好我的标准不是“扫出来的东西多不多”而是“有没有把该识别的技能都识别出来”。一个简单的验证方式是挑一个你已经完全掌握的目标服务比如你只部署了 Ollama那么你预期报告里应该看到什么至少应该有聊天补全接口、模型管理接口这类技能如果你预期 Ollama 结果里出现了什么数据库查询那大概率是误报。反之如果连聊天补全接口都没扫到那就是漏报说明配置有问题。我每次扫描完都会拿目标服务的真实情况对照一遍技能清单。这条我称为“抽样验证”的路径听起来很耗时实际上只需要几分钟。但它能帮你发现很多工具自身的理解偏差比如某个技能的实际路径和指纹库里的路径不吻合。AI 应用链路复杂在每一层的接口风格都不统一有的是 REST 规范有的是内部 gRPC甚至有的直接暴露了 WebSocket 接口。如果你的目标服务里这两类接口都有而扫描报告只出现了 REST 类技能大概率是规则库没覆盖 WebSocket 指纹。这种时候别急着怪工具先用项目自带的规则编辑器加一条自定义指纹把接口路径、请求方式、响应特征填进去再重新扫描就能把缺口补上。5. 漏报复盘一次真实排查实录5.1 现象该报的技能没报我那次漏报场景是这样公司内部有一个基于 FastAPI 构建的 AI Agent 服务内部名我代称为 agent-core挂在 8000 端口上。它对外会暴露一组工具调用接口其中有一个是/v1/tools/execute用于执行一段沙箱内的代码。这个接口属于典型的高危技能——如果被滥用相当于有人在你的沙箱里跑代码所以我很确定它需要被扫描器标记出来。扫描跑完之后我满怀期待打开报告结果傻眼了agent-core的技能清单里只有对话服务、会话管理这两个低风险技能代码执行工具完全不在列表里。我当时的第一反应是规则库没收录这个指纹。但我去规则库里搜代码执行、execute相关关键词发现规则明明存在而且匹配条件写得很清楚。这就怪了规则在、服务在、接口也在为什么没扫出来更诡异的是我从宿主机手动访问这个接口用 curl 发了一个最简单的 GET 请求服务端是正常返回响应结构的。这意味着服务本身没挂接口也没变问题一定出在扫描器对它的“认知”上。5.2 排查过程三层过滤逐层剥开我开始按部就班地排查。第一层我怀疑是端口发现阶段出了问题。AI-Infra-Guard 的扫描流程是先做端口发现如果端口发现阶段判断10000这个端口“未开放”或“无响应”就不会进入服务识别和技能匹配接口自然就漏掉了。我把扫描器的 debug 日志打开找到针对这个 IP 的端口探测记录发现 8000 端口在记录里确实是 Open 状态。端口发现阶段没问题问题不在最底层。第二层我开始怀疑服务识别。扫描器发现端口之后会发送探针确认对端跑的什么协议、什么服务。我看了服务识别的结果它识别出来的是HTTP、Python/3.x aiohttp这类信息方向是对的。但问题是服务识别不等于每个路径都知道它只能拿到根路径和通用 header 的信息之后要把这些信息交给技能匹配引擎去做路径和指纹的比对。我开始怀疑是技能匹配阶段的路径探测路径不全尤其可能是对 API 路由的探测用的请求方法不对。第三层我直接手动模拟了扫描器的请求。我照着规则库里的指纹要求先发了一个GET /v1/tools/execute请求但这一次我故意不带任何认证头。返回结果是403 Forbidden。然后我又带了一个简单的测试 token 重发返回变成了200 OK响应结构正好匹配指纹库里的特征。到这里真相已经浮出水面了。5.3 根因鉴权把探针挡在了门外问题出在 AI-Infra-Guard 默认的探测请求不携带身份认证信息。它作为一个资产盘点工具默认假设目标服务是可信的、多数接口可以匿名访问所以探针请求都是裸请求。但agent-core的/v1/tools/execute接口在近期加固时加了鉴权中间件所有未认证请求一律不返回真实响应。扫描器收到的就是 403它按照规则判定响应特征不匹配直接跳过了这个技能。这个根因听起来简单但你细品这不是扫描器坏了也不是规则库缺失而是目标环境的访问控制策略升级了而扫描器的探测方式没跟着升级。如果只盯着扫描报告看你永远找不到答案必须把请求层面的问题拉出来对比才能定位。修复方案是在 AI-Infra-Guard 的配置里给该目标添加认证凭据。它在配置里支持为特定目标配置请求头模板我加了一项credentials: - target: 192.168.1.21 headers: Authorization: Bearer ${AGENT_CORE_API_TOKEN}这里的AGENT_CORE_API_TOKEN我用环境变量注入避免明文写在配置文件里。加了凭据之后重新扫描代码执行工具果然出现在了技能清单里置信度 0.96。为了进一步验证不是歪打正着我用同样的配置去扫了同一网段另一个没有鉴权的测试服务结果完全正常该报的报、不该报的不报。这一次漏报最终定位到根因加复扫验证前后花了一个下午。5.4 漏报复盘给我留下的四个教训复盘完整次事件我给自己整理了四条经验。第一条扫描报告里的“无技能”和“服务不可达”是两码事。报告只说这个端口没匹配到技能但你要自己去确认到底是服务没这个能力还是探针被挡了看不见。第二条凡是加了鉴权的目标服务扫描前必须同步配置认证凭据。这不是可选项是必选项否则技能扫描对生产环境基本是半盲状态。第三条不要完全信任单轮扫描的结果关键服务至少要跑两轮一轮裸扫、一轮带凭据扫描两份报告做 diff差异就是鉴权屏蔽的技能。第四条每次漏报事件解决之后把根因写进团队的 scan playbook下次换人做同样的排查就不需要重新趟一遍雷。我把这套排查思路固化成了一个简短的决策清单之后每次遇到“技能好像少了”的情况按顺序做三轮检查先看报告里的端口发现再看服务识别结果最后手动模拟探测请求。90% 的漏报问题都能在这个循环里找到答案。6. 常见问题与排查技巧实录6.1 容器起不来docker run之后容器马上退出了这是新手最容易遇到的。我的排查套路是先看日志docker logs ai-infra-guard日志里出现Permission denied的概率最高多半是挂载目录的权限问题。我用的目录在/opt下面宿主机上如果目录属主不是当前用户容器内进程可能没权限写。解决办法很简单把目录属主改成当前用户或者直接chmod -R 755。另外还要检查是不是端口被占用了尤其AIG_WEB_PORT如果和宿主机已有服务冲突容器会起不来。改个端口再试基本能解决。6.2 扫描到了但接口报超时报告里出现大量超时条目首先排除是不是目标服务真的过载了。如果目标没问题大概率是扫描并发开太高把目标服务打冒烟了。我遇到过一次给一台 2C4G 的轻量服务扫一个子网开 500 并发对方直接拒绝连接。后来把并发降到 100扫描就正常完成。这里有个估算方法并发数乘以每个请求的平均耗时不大于对方的每秒请求处理能力一般不会出问题。如果目标服务明确是轻量级的我给的建议是宁可多等几分钟也要把并发压到 100 以内。6.3 Web 控制台开了但页面打不开容器起来、日志也正常但浏览器访问8088打不开。这种情况第一检查是不是防火墙拦了端口。Ubuntu 上 ufw 默认关闭还好如果是 CentOS 服务器大概率拦着。放行端口再说sudo firewall-cmd --zonepublic --add-port8088/tcp --permanent sudo firewall-cmd --reload还有一个隐蔽的原因如果你用了--network host但 Docker Desktop 的宿主机 IP 和虚拟机 IP 不是一个概念你直接在 Windows 浏览器访问要填localhost或 Docker Desktop 给的专用 IP而不是服务器的内网 IP。这类问题最坑但原理通了就很好理解。6.4 漏报定位速查表最后再给一张速查表把常见漏报场景、可能原因、排查方向合并在一起。以后你再遇到漏报对着表走一遍大概率能在十分钟内定位漏报场景可能原因排查方向某个已知技能没出现在报告里探针请求被鉴权拦截配置目标认证凭据重新扫描接口是 WebSocket 但没被识别规则库没有 WebSocket 指纹手动添加自定义指纹全部技能都没扫到端口发现阶段失败检查目标 IP 可达性和端口状态部分端口有技能部分没有服务识别阶段识别错误手动访问目标服务核对服务类型扫描结果与上次差异巨大规则库版本或目标配置变化对比两次扫描配置确认规则版本这张表是我把多次实战经验浓缩出来的本质就是在提醒你漏报不可怕可怕的是你拿报告里的“无技能”状态当结论而不去追问为什么。我踩过这个坑之后每次出报告都会多问一句“这个结果符合我对目标服务的了解吗”这句话的价值比任何工具参数都大。7. 从一次部署到长期运维的转变部署跑通、技能扫描能出报告、漏报问题也复盘完了但这套工具真正发挥价值的时间点是在你把它嵌入到日常运维节奏之后。我现在的习惯是每周跑一次自动化扫描目标范围固定为本地的 AI 服务网段和 Docker 容器网络。扫描之后对比基线报告有新增技能就更新资产清单有技能消失就去看是不是服务做了变更。这么做了几周之后我对整套 AI 应用链路的掌握程度比之前靠记忆和文档要扎实得多。AI-Infra-Guard 这种工具本质上不是那种用完一次就放下的“一次性扫描器”它更像一个持续更新的资产账本每次扫描都在给账本做审计差异就是变化变化就是你需要关注的地方。如果你团队里已经积累了多个 AI 服务我强烈建议把它纳入到常规巡检流程里跟已有的监控告警配合着用。监控告警解决的是“现在出问题了”的即时感知技能扫描解决的是“你可能会出问题的地方在哪”的提前梳理。两者一纵一横配合起来能把 AI 基础设施的稳定性往上拉一大截。最后分享一个我自己的习惯每次新接入一个 AI 服务节点第一件事不是写文档而是先让 AI-Infra-Guard 扫一遍把扫出来的技能清单作为这个节点的初始资产基线存档。后续再扫描拿新报告和基线做 diff。新增技能、端口变更、接口下线全部有迹可循。这套流程跑顺之后你再回头看一开始那股“前路雾蒙蒙”的不确定感会发现基础设施的安全感和掌控感其实是可以被量化出来的。工具在手上路就在脚下。