ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Azure Container App Debug Console 实战:无 SSH 时代容器排障的终极利器

Azure Container App Debug Console 实战:无 SSH 时代容器排障的终极利器 容器应用崩了日志里只剩一行exit code 139剩下的全是空白。你想进到容器里看看进程状态、跑几个命令定位问题却发现 Azure Container App 不像传统 VM 那样给你开 SSH。这是不少人第一次被 Azure Container App 的 Debug Console “救回来”的场景。Debug Console 是 Azure Container App 内置的交互式调试入口它的作用很直接让你在运行中的容器实例里执行命令查看环境变量、进程列表、网络连接、文件系统等。它不会替你做排障但能给足“视野”把问题从“看不见摸不着”变成“看得见摸得着”。这篇文章适合折腾过 Azure Container App、但总觉得排障手段不够的开发者也适合刚接触容器应用、想搞明白出问题时有哪些后路的运维朋友。我会从打开方式讲起把容器里能用的调试工具、常见排障链路、权限与限制一起说透。1. 无 SSH 时代的排障困境Debug Console 到底解决了什么1.1 传统服务器和容器应用之间最难受的差异以前管虚拟机出问题第一反应就是 SSH 上去ps aux、df -h、tail -f挨个跑一遍。但 Azure Container App 是 Serverless 容器平台默认不提供 SSH 入口甚至不保证你有一个“长期存在”的实例。容器实例随时可能因为扩容、缩容、更新、重启而重建。你还没来得及登录它可能已经没了。这就带来一个很现实的尴尬当应用起不来、反复崩溃、网络不通时你手上通常只有三样东西——日志、指标、配置。日志如果写得不全指标只能说明“有异常”配置只是“你以为你配置对了”。真正运行时的状态比如环境变量到底注入成什么样、配置文件路径对不对、DNS 解析到了哪个 IP、进程是不是内存吃爆了日志里往往完全没有答案。Debug Console 解决的就是这个信息断层。它给你一个进入正在运行的容器实例的通道让你像在普通 Linux 机器上一样直接执行命令查看现场。不需要修改代码不需要重新发布新版本也不会影响其他实例。1.2 Debug Console 的能力边界我习惯把 Debug Console 理解成“一个临时的、受限的观察窗口”。它和传统 SSH 有本质区别它不是一个独立服务而是容器实例内的一个交互式 Shell 进程它依托于容器镜像里自带的/bin/sh或/bin/bash镜像里没有 Shell 就没法用你能在容器里看到的文件系统、进程、网络栈都是这个实例的真实状态但你在里面做的任何修改都不会“固化”成新的镜像或配置容器重启后一切回到原样它的核心价值在于“看”和“验”。看实际运行状态验证你的配置假设。至于修改那是最后一步才需要考虑的事。这一点想清楚你就能理解为什么说 Debug Console 是排障利器但不是运维逃生舱。1.3 哪些场景必须靠 Debug Console 收场我实际用下来的经验下面这几类问题靠日志几乎不可能定位Debug Console 基本是必需的容器反复重启CrashLoopBackOff日志只显示进程退出了但看不到原因。需要抢在实例被回收前进去手工跑一次启动命令把真实报错抓出来。配置与预期不符环境变量、挂载的配置文件、启动命令写进 Bicep/Terraform 后到底变成了什么直接进容器核对是最快的。网络连通性问题应用能启动但调用依赖服务超时。光看日志只能看到一堆 Timeout但你完全不知道是 DNS 解析失败、TCP 被拒、还是握手阶段出问题。资源与性能问题实例内存或 CPU 使用率冲到 100%但不知道是哪个进程、哪个线程在消耗。这四类我后面都会用真实排查链路展开讲。先看一下怎么打开这个 Console。2. 打开 Debug Console 的两种姿势Portal 点点点与 CLI 一条命令2.1 Azure Portal 里的操作步骤Portal 方式的路径不算复杂操作步骤如下在 Azure Portal 中进入目标 Container App 的页面找到左侧菜单中的“Console”入口新版本 Portal 也会标成“Debug Console”。点击后界面会让你选择一个具体的容器实例。如果有多个副本Replica或 Revision需要先选定一个目标实例再选择要进入的容器。点“Connect”之后浏览器会打开一个 Web 终端。这个过程通常需要几秒钟期间 Portal 会建立到容器实例的连接在容器里启动/bin/sh。看到#提示符就说明成功了。有个实际操作中的细节容易忽略如果你的应用有多个副本你进的是其中一个实例而不是所有实例。排查时如果问题在特定实例上出现你得选中那个状态的实例而不是随便挑一个。另外Portal 里显示的实例列表不一定稳定容器崩溃重启后实例会被移除重建这直接影响你能否抢时间进去。2.2 命令行方式az containerapp exec如果整天泡在终端里我更推荐用 Azure CLI。一条命令az containerapp exec \ --name my-container-app \ --resource-group my-resource-group执行后 CLI 会找到第一个容器实例并直接进入交互式 Shell。如果你想指定容器或实例CLI 也支持参数比如az containerapp exec \ --name my-container-app \ --resource-group my-resource-group \ --container my-container和 Portal 相比CLI 的好处是你可以从本地终端直接进入复制粘贴命令更方便尤其适合做复杂排查时。它还适合自动化运维场景可以在脚本里拼上几条排查命令一次性把现场快照拉下来。顺带提一个高级替代方式如果你的 Container App 启用了虚拟网络集成az containerapp ssh命令也可以建立基于 SSH 协议的通道。实际用起来与 exec 的体验差异不大但 SSH 方式对网络和生产环境下受控环境支持得更稳一些。日常排查用exec足够。2.3 Debug Console 的底层机制不是黑魔法很多人好奇这个 Console 到底是怎么实现远程连接到容器里的。它的本质并不复杂Azure Container App 底层运行在 Kubernetes 集群上Console 就是通过 Kubernetes API 的 exec 机制在工作负载对应的 Pod 里启动一个/bin/sh进程然后把进程的标准输入、标准输出、标准错误流通过 WebSocket 转发到你的浏览器或 CLI。所以它有两个天然的约束条件容器镜像里必须存在可执行的 Shell比如/bin/sh或/bin/bash。如果你的镜像用了distrolessGoogle 那类极简镜像或仅包含静态编译二进制文件它就没有 Shell 可启动这时 Console 会直接报错或黑屏。当前运行账号必须允许启动进程。容器以非 root 用户运行时至少需要该用户在容器内有执行 Shell 的权限。理解了这个机制你就知道遇到“Console连不上”时该往哪个方向排查先看镜像类型再看运行时用户配置而不是一开始就怀疑是不是 Portal 出了问题。3. 容器里能用的调试工具你的镜像决定了你的武器库3.1 先摸清家底这个容器里到底有什么Debug Console 里的工具集完全继承自容器镜像。不同基础镜像差异非常大。基础镜像Shellcurlwgetpingnslookup/digncAlpine/bin/shbusybox无有busybox有busybox无无Ubuntu/Debian/bin/bash有有有视版本可能有默认无Distroless无无无无无无CentOS/RHEL/bin/bash默认有有有可能无默认无我习惯进入 Console 后首先跑一条“摸底”命令把这几个东西都过一遍ls -l /bin/sh /bin/bash /usr/bin/curl /usr/bin/wget /usr/bin/nc 2/dev/null这样能在一秒内判断出你手里有哪些牌。如果遇到完全没工具的情况《基础环境》里也有一条路可走用 Shell 内建能力或/dev/tcp完成很多原本依赖外部工具才能做的事后面专门讲。3.2 基础信息收集工具env、cat、ps、df、mount这些工具几乎是 Linux 排障的标配。Debug Console 里最常用的组合我列一下env列出全部环境变量。应用启动前注入的环境变量在这里能看到最终结果。注意关注像PORT、DATABASE_URL、CONNECTION_STRING这类关键值。cat/ls -l看配置文件是否真的挂载到了预期路径权限是否正确。我曾经排查过一个配置不生效的问题最后发现是配置文件挂载路径里多了一个度娘都搜不出来的中文字符。ps aux列出所有进程。重点看 PID 1 是谁、进程以什么用户跑、有没有 zombie 进程。df -h查看文件系统剩余空间。很多应用在磁盘写满后表现出的症状极其诡异比如请求挂起、偶发 500而不是直接报“磁盘满”。mount确认挂载点和数据卷的情况。id确认当前用户的 UID/GID排查权限问题时非常有用。这里有一个容易踩的坑top在精简镜像里不一定存在。如果top不识别可以直接到/proc里抓信息cat /proc/loadavg cat /proc/meminfo3.3 网络诊断工具curl、nslookup、nc 的实战用法网络问题最常用的是 curl。测试一个 HTTP 端点时我一般不会只用一个curl -v而是分层做curl -v -o /dev/null -s http://example-api.internal-v会把 DNS 解析、TCP 连接、TLS 握手、HTTP 响应头全部打出来。看输出你能区分问题到底卡在哪一层。如果 DNS 解析失败输出里会有Could not resolve host如果 TCP 连不上会有Connection refused或Connection timed out如果 TLS 有问题会卡在握手阶段直接报证书错误。DNS 解析单独测试用nslookup或dig。但很多剪裁过的镜像里面根本没有这两个命令这时可以用getent hosts来自 GNU libc在很多 Linux 基础镜像里都存在getent hosts db.internal如果连getent都没有还可以直接让 curl 来处理从-v输出的Trying 1.2.3.4:5432...一眼就能看出解析对没对。TCP 端口连通性可以用nc -zv host port来测。同样地精简镜像可能没有nc此时完全可以用 bash 内建的/dev/tcp替代timeout 5 bash -c echo /dev/tcp/10.0.0.5/5432 echo open || echo closed这个技巧在只有 busybox 或甚至只有 bash 的环境里特别好用是纯内建能力不依赖任何外部二进制。我已经不止一次在 Debug Console 里用它救场。3.4 运行时环境检查Node、Python、Java、.NET 各有各的招式如果你排查的是现代应用容器除了系统层面的工具还需要看运行时层面的信息。Node.js 应用node -v看版本ps aux | grep node看启动参数如果需要排查模块问题npm ls --depth0可以看顶层依赖树。内存问题优先看NODE_OPTIONS里有没有设--max-old-space-size。Python 应用python --version、pip freeze看已安装的包。启动文件是否存在、工作目录是否正确可以在 Console 里直接ls确认。Java 应用如果镜像里带了 JDKjstat -gcutil pid可以看 GC 情况jstack pid可以看线程堆栈。如果只有 JRE很多诊断命令不可用这时只能依赖应用日志。.NET 应用dotnet --info查看安装信息。若应用托管在 ASP.NET Core 环境dotnet-counters这类工具未必内置在镜像里Console 里跑不了的话不要死磕。3.5 工具缺失时怎么办两条实在的退路第一种退路临时装包。如果你的基础镜像是 Alpine理论上可以apk add --no-cache curlUbuntu 系列是apt-get update apt-get install -y curl但这里有个大前提你的网络必须具备访问外部软件源的出口。很多企业环境对出网有严格限制这个操作经常超时或者被防火墙拦掉所以别把希望都寄托在临时装工具上。第二种退路善用 Shell 内建能力和基础文件系统。比如端口测试用/dev/tcpHTTP 请求用curl不行时如果你的镜像里有 bash 且应用依赖 HTTP 接口可以用exec 3/dev/tcp/host/port手工发一个 HTTP 请求exec 3/dev/tcp/10.0.0.5/8080 printf GET /health HTTP/1.0\r\nHost: app\r\n\r\n 3 cat 3这个玩法对付最简单的 HTTP 健康检查场景足够用了。总之Debug Console 里工具不够的时候思路应该是“用系统自带能力代替外部工具”而不是硬等镜像重做。4. 四个生产环境排障实例从崩溃循环到网络黑洞4.1 实例反复重启抢在消失前抓到真实报错有次我负责的一个网关应用在更新后进入 CrashLoopBackOff。日志里只有一行terminated with exit code 139完全没有堆栈。我的第一反应是看新版本的启动配置里有没有引入除零的违规操作但很快发现日志根本不输出进程内部的初始化异常。我沿着下面这条链路排查打开 Debug Console进入一个正在重启的实例。这一步要快因为实例可能随时消失。先看 PID 1 是什么ps aux发现 PID 1 是一个包装脚本而不是应用进程本身。手工执行这个包装脚本/entrypoint.sh终端直接打印出真实错误原来是脚本尝试读取一个不存在的秘钥文件退出码 139 只是核心内存段的通用表现。再看文件系统ls -l /secrets/发现挂载路径和代码里读取的路径不一致差了一级目录。这个问题的根源是配置和代码路径不同步日志把真正的错误吞掉了。如果没有 Console 手工跑一次启动命令光看日志永远定位不到这一层。4.2 环境变量与配置文件“没生效”进容器对账另一个案例是应用能启动但行为明显不符合预期。配置中心明明设置了新的数据库连接地址应用却还是一直连旧库。团队里的开发同学反复确认代码没改、配置已发布一副“不可能”的样子。我用 Debug Console 直接进容器“对账”env | grep -i database结果环境变量里根本没有新地址而是出现了一个拼写错误DATABASE_CONNECTION_STRIN_Gsbq://wrong-server:5432问题出在应用配置模板里变量名的尾巴多打了一个下划线。代码使用的DATABASE_CONNECTION_STRING永远是空值于是应用直接走了默认值——连接旧库。这种问题靠日志排查简直是大海捞针但一条env命令 30 秒解决。要注意的是在 Console 里通过export修改的变量只影响当前 Shell不会改到容器配置更不能指望它持久化。发现问题后还是得回到配置定义处修复并重新部署。4.3 网络连接异常分步拆解 DNS、TCP、TLS生产环境下网络故障是最难排查的问题之一。有一次服务 A 调用服务 B 频繁超时B 返回的日志里没有任何异常A 的日志里全是Connection timed out。我用 Debug Console 在 A 的实例里做了一次分步测试。第一步测 DNSgetent hosts svc-b.internal输出正常解析到了内网 IP。说明 DNS 没问题排除域名误解析。第二步测 TCP 端口timeout 5 bash -c echo /dev/tcp/svc-b.internal/443 echo open || echo closed结果竟然是closed。这下问题收窄了不是应用问题而是 443 端口根本连不上。第三步看路由和防火墙。Azure 环境里容器之间走的就是虚拟网络。最终发现是网络安全组NSG规则只放行了特定来源网段到服务 B 的 443 端口而服务 A 所在的子网网段不在白名单里。修好 NSG 后连接立即恢复。这个链路最大的价值在于用命令把“连接超时”一步步拆开分别定位 DNS、TCP、TLS 各自的状态这比盲目改超时配置靠谱得多。4.4 内存耗尽OOMKilled 背后的真正元凶还有一个让我印象深刻的案例某定时任务的容器持续被 OOMKilled。Java 应用内存参数配置确实存在问题。但 Debug Console 的价值在于它让我在没有修改代码的情况下确认了元凶是堆外内存暴涨。现场操作free -m看到可用内存为 0大部分被 buff/cache 占据。随后ps aux --sort-%mem发现除了 Java 进程外还有一个内部代理进程占用了将近 300MB而这部分内存完全不在 JVM 堆参数控制范围内。最终确认是代理进程的内存泄漏导致堆外暴涨与 JVM 配置无关。这个结论如果只靠外面看指标会误掉进“堆栈调参”的大坑。Console 里还能看更细的线程和 FD 信息cat /proc/pid/status | grep -E VmRSS|Threads|FDSizeOOM 问题往往需要多维度信息交叉验证单看内存总量不够进程级画像才是关键。5. 权限、超时与镜像依赖使用 Debug Console 必须知道的边界5.1 不是你拿到 Azure 账号就能点开首先明确一点Debug Console 不是一个全账号开放的功能它受 Azure RBAC 控制。要能连接容器实例并执行命令你的角色必须包含Microsoft.App/containerApps/exec/action这个操作。如果你用的是 Contributor 角色默认是有的。但如果团队用了最小权限策略只有 Reader 或自定义角色你会发现 Portal 里 Console 按钮可能直接灰掉或者点击后报权限不足。我遇到过团队同事被这个卡住自定义角色里漏了这一项。排查办法是去 Azure Portal 的 Access Control (IAM) 里确认你的角色定义必要时请管理员为自定义角色加上Microsoft.App/containerApps/exec/action。这不是什么复杂操作但漏掉的人不少。5.2 会话超时与实例生命周期Console 不是持久连接Debug Console 建立的 Shell 会话绑定在具体实例上。只要发生下面任意一种情况会话就会中断容器实例被重启、重建比如更新 Revision、触发扩缩容容器应用因故障被自动替换实例Portal Web 终端空闲时间过长被会话超时机制断开你的本地网络中断导致 WebSocket 连接断开所以正确的排查姿势是打开 Console 尽量高效操作先把重要信息抓下来比如进程列表、环境变量、网络状态最好是复制到本地保存而不是看着终端发呆。5.3 镜像依赖的硬约束distroless 与 Windows 容器前面提过Debug Console 依赖容器内的 Shell。如果你的应用镜像基于distroless打完的那这个功能对你基本就是不可用的。Go 应用常用这种方式把镜像体积压得非常小生成出来的镜像里既没有 bash 也没有 sh甚至连执行权限管理都很严格。遇到这种镜像要怎么办我的经验是两条路在非生产环境临时用一个带调试工具的镜像替换一次比如切换到ubuntu:22.04为基础镜像重新构建定位完问题后再换回去。这需要重新构建发布成本稍高。依赖应用自身的健康检查和日志输出把问题定位重心移到日志结构化上。另外Azure Container App 对 Windows 容器的支持目前与 Linux 场景有差异Debug Console 对 Windows 容器的支持我不建议作为主要依赖。如果你的容器是 Windows先按照应用日志和平台指标排查是更稳妥的路线。5.4 我的几条使用心得用 Debug Console 这么久最后分享几条实在操作建议进去后先date看容器时间这个细节经常被忽略。容器时区如果和日志系统不一致分析时间线的时候会被带偏。不要一上来就vim或者乱改文件。Console 里的修改不会进入镜像改了只可能让当前实例更异常排查完后还是要回到 IaC 文件修复。抓现场信息时尽量“成批拉取”比如env /tmp/env.txt ps aux /tmp/ps.txt cat /etc/resolv.conf /tmp/dns.txt然后把这些文件内容复制出来留档。因为容器随时可能重启先保存到本地再慢慢分析是最安全的做法。遇到 Debug Console 本身连不上别急着提工单。先自查镜像里有没有 Shell、账号角色里有没有 exec 权限、实例是否处于 Running 状态。这三条检查完九成问题都出在这。Debug Console 听起来是个小功能但在生产环境真正出事的时候它就是那个让你不至于两眼一抹黑的窗口。能熟练用好它排障效率会明显不一样。
返回列表