容器 03:PID 1 为什么改变信号与回收行为
在 PID namespace 中第一个子进程的 PID 是 1。这个数字不仅是用户界面的显示值:该进程承担接收孤儿进程、处理终止事件的职责。如果它结束,内核会终止此 PID namespace 中的其他进程。先掌握01 的 wait/信号和02 的 namespace 身份,否则容易把“应用没有退出”误判成运行时故障。 PID 号码与进程的两个视角 PID namespace 按层级组织。内侧看到的 PID 1 在外侧仍有宿主 PID,外侧能够观察内部进程,内部不能因此查看外侧所有进程。/proc 是一个挂载出来的进程信息视图;仅调用 unshare 而沿用旧 /proc 挂载,可能读到旧 namespace 的 PID,进而产生“shell 的 $$ 是 1,/proc/self/status 却有不同号码”的矛盾。应在私有挂载环境重新挂载 procfs,才用它检查当前 PID 视图;没有这个权限,明确标记观察限制,而不是补造一致的 PID 输出。 PID 1 对某些未安装处理函数的默认信号行为有特殊规则。它并不是“永远不会响应 TERM”:安装了 TERM han...
容器 02:namespace 改变的是哪些资源视图
在一个终端里改了主机名,另一个终端为什么没变?关键不是 shell 的变量,而是两个进程引用的 UTS namespace 不同。沿01 的进程生命周期继续,把隔离看成“进程持有哪些内核对象”,而不是把容器想象成一台小型物理机。 比较视图,不比较字符串 clone 可让新进程进入新建的 namespace;unshare 使调用者与先前共享的指定 namespace 分离;setns 把调用者加入已有 namespace,但有具体的权限、类型和多线程前提。UTS 隔离 hostname 与 domainname,IPC 隔离相应的进程间通信对象;PID、mount、net 与 user 各有自己的资源集合。/proc/<pid>/ns/<type> 的链接目标包含可比对的 namespace inode。两个进程具有相同 UTS inode,表示指向同一 UTS namespace;两个进程恰好都输出 hostname=demo,却可能在不同 UTS 对象里。不能跨类型比较数字本身。 普通读者并不必在宿主得到所有 capability。unshare -U...
容器 01:父进程退出,工作是否已经结束
“主进程退出了”既不能证明它启动的子进程已完成,也不能证明系统已经回收了全部进程状态。将这句话拆成 fork、exec、文件描述符、信号与 wait,才能解释容器里最常见的退出和日志问题。先阅读00:观测对象与实验基线;这一篇只使用普通进程建立可复核的生命周期,不假定容器运行时已经可用。 四个独立动作 fork() 创建子进程:父子最初有独立 PID,子进程继承打开的文件描述符,但调用返回后谁先执行取决于调度,不能把日志先后当程序依赖。execve() 用新程序替换当前进程映像,不会因为替换而分配新的 PID。默认打开的 FD 随之保留;设置 FD_CLOEXEC 才要求 exec 时关闭相应描述符。容器标准输出常作为由运行时提供的 FD 传入应用,因此新程序是否继承它是能否观察日志的关键。别把“关闭路径名对应的文件”和“关闭 FD”混为一谈:先前打开的 FD 指向已打开文件对象,删除路径也未必让引用立刻失效。 子进程结束时留下退出状态,父进程通过 waitpid() 读取并回收;WIFEXITED 与 WEXITSTATUS 用于正常退出,WIFSIGNALED 用于信号终止。...
容器 00:运行中的容器究竟是哪一个进程
同一份应用文件在本机运行时只是进程;打包为镜像之后也不会自动变为“正在运行的容器”。要解释一次请求为什么失败,先区分磁盘上的制品、运行时创建的对象、内核里的进程和应用的就绪状态。这四者不处在同一层;把它们都叫“容器”会使故障归因失去落点。 从制品到请求 OCI Image Specification 约定索引、描述符、配置和层等制品表示;Runtime Specification 约定 bundle 配置与执行环境。镜像的摘要标识内容,不是 PID;bundle 描述将要执行的命令与隔离、挂载等参数,不是已经建立的内核对象。运行时准备根文件系统、进程配置,创建或加入 namespace、应用资源及权限约束,最后让内核执行程序。一个处于不同 namespace 的 Linux 进程仍与同机进程共享一个内核;VM 内的 Linux 容器共享 VM 的内核,而非桌面宿主内核。 实际排查时需要沿对象逐一核验:镜像引用解析到哪个 digest;运行时对象的 ID 是什么;运行程序在宿主侧 PID 是什么;/proc/<pid>/ns/* 指向哪些 inode;/proc/<...
服务网格 01:SDK、网关与服务网格分别处理什么
同一请求,四处可能设置超时 上一期 第 00 篇 让 loadgen → frontend → orders → inventory-v1 / external-stub 的只读调用先跑通。下一步不是立即把所有网络选项移到代理,而是回答:调用方 SDK、入口网关、工作负载代理、应用分别知道什么?假设 frontend 对 orders 超时重发、orders 也对 inventory 重发,最后客户端只看到一次失败,这些重发造成的写入次数不一定是一次。 先修是区分连接失败、HTTP 响应与业务结果;这里讨论的是职责,不预设已有 Istio 集群。本篇记录的副作用实验使用本机进程直接访问 inventory,没有运行网关或网格代理。 图中业务请求由客户端经入口网关抵达 frontend;frontend 的 SDK 发起下游调用,若有 sidecar 则相邻的代理可能处理这段流量;orders/inventory 应用仍需决定一次业务操作能否重复执行。Istiod 的控制路径不在图中的业务转发链里。 职责不按功能名称,而按可见信息划分 位置 通常能看到什么 适合做什么 不能...
服务网格 00:一次服务调用需要哪些基础条件
从一个可定位的请求开始 loadgen 请求 frontend 查询合成商品 paper,后者调用 orders,orders 再读取 inventory-v1 和 external-stub。要让这次调用成功,客户端至少需要知道地址、能解析目标、连接正确的端口、找到可用后端,并获得与请求对应的响应。先把代理和控制面排除在外,后续才能判断新增的网格层究竟改变了哪一段。 本文要求读者能区分 HTTP 响应与 TCP 连接失败,并认识 Kubernetes 的 Pod 和 Service。实验代码使用 Go 标准库;本篇只有本机进程实测,尚无 Kubernetes、Envoy 或 Istio 的运行证据。 图中 orders 是 frontend 必须连到的上游:域名不正确,连接甚至不能发起;目标是空集合,应用没有可以选择的地址;端口不对,即使 IP 正确也可能被拒绝。图里的地址都是本机监听地址,不是 Kubernetes Service 的 ClusterIP。 把“找到服务”拆成四次判断 名称解析:orders.invalid 无法转换为可连接的地址;程序不会因为另一个进程叫...
深入 OpenTelemetry 00 - 导读与第一条 Trace
一个订单查询调用了 span.end(),是否就意味着在可观测性平台能查到请求?不能。结束 Span 只是生成与交付流程中的一个本地边界。先用单进程 Java 程序拿到一条真实的 SpanData,再看这条证据究竟能证明什么。 一条请求的两个路径 订单查询是业务路径;遥测记录是另一条路径。本篇只运行单进程 order.lookup,库存服务、数据库和接收端还不存在。 1234请求 -> order.lookup -> 返回业务结果 | +-> Tracer API -> Span -> SDK processor -> 内存 exporter -> SpanData [尚无 OTLP / Collector / 后端] Tracer 与 Span 是业务代码调用的 API;SdkTracerProvider、SimpleSpanProcessor、exporter 是可替换的 SDK 配置。...
深入 OpenTelemetry 01 - API、SDK 与初始化
代码里已经调用 GlobalOpenTelemetry.getTracer(...),为什么没有 Trace?入口可以存在,SDK 却未注册。第 00 篇显式组装 SDK;这次把显式、自动配置和根本没有 SDK JAR 的情况放到三个独立进程中,避免把单例的残留状态误认作配置效果。 API 的入口不等于 SDK 的安装 12应用 -> GlobalOpenTelemetry -> [已注册 SDK] -> processor -> exporter -> [未注册 / 无 SDK] -> noop 公开 GlobalOpenTelemetry.get 首先查看已经注册的全局实例;没有时尝试自动配置,仍不满足条件才设置 noop。这个入口本身不会神奇地创建处理器和发送端。get() 后再 set() 会抛 IllegalStateException:它不是可以随时覆盖的线程本地变量。上游全局初始化的边界测试见 OpenTelemetryTest。 显式分支由应用创建 SdkTracerProv...
深入 OpenTelemetry 02 - Resource、InstrumentationScope 与请求属性
同一张 Trace 中,订单服务与库存服务可能都有名为 lookup 的 Span。光看 Span 名称区分不出哪个服务生成,也无法知道是谁编写的插桩。service.name、instrumentation scope 与 order.id 属于三个不同层级,不应为了在界面上方便查找而全部塞进 Span attributes。 三个不同的归属 位置 本篇取值 生命周期 / 含义 Resource service.name=order-service 或 inventory-service provider 所代表的遥测生产实体,随该 provider 的 Span 共享 InstrumentationScope order.api@1.0 或 order.storage@2.0 获取 tracer 的插桩代码身份,可与其他 scope 共用同一 Resource Span attributes order.id=request-42 一次操作上的数据,未设置时不会从相邻 Span 自动继承 订单服务的两个 tracer 可以分别代表入口层和存储层,...
深入 OpenTelemetry 03 - Span 生命周期与异常状态
抛出了异常,Trace 却没有红色错误状态;或者方法提前返回,Span 始终没有进入导出器。这两种故障涉及不同的动作:记录异常、设置状态、结束 Span 并非同一个 API 的副作用。沿订单查询的三条控制流逐一验证。 三个动作不要合并 12startSpan -> [setAttribute / addEvent / recordException / setStatus] -> end 更新正在记录的 Span 内部数据 结束并回调 processor recordException(exception) 生成名为 exception 的事件;setStatus(ERROR) 修改状态;end() 记录结束时间并触发 processor 的 onEnd。三者彼此独立。正常结束的 Span 状态并非必须显式设置 OK;本篇实验中没有设置时为 UNSET,这不等于业务错误。 上游从哪里做了这些事 公开 tracer.spanBuilder(...).startSpan() 进入内部 SdkSpanBui...




