# 软件装进客户集群后，如何依然看得见它

> 完整 VPC 或本地部署时，厂商既不掌握集群也不掌握数据，诊断信号必须从第一天就设计好。

原文: https://fdetimes.net/zh/guides/observability-customer-vpc-on-prem-deployments/

合同已经签了。客户的安全团队只提了一个条件：任何文档都不得离开他们的基础设施。从现在起，你的软件将运行在一个不属于你的集群上，躲在一道不是你配置的防火墙后面；凌晨 2 点出故障时，最先发现的很可能是客户。

文档处理软件公司 Reducto 正是在为这样的岗位招人：Forward Deployed Engineer, Infrastructure Specialist，即基础设施方向的前线部署工程师（FDE）。按照职位描述，这个人负责把 Reducto 部署进客户环境，并端到端地承担这项工作，从规划、配置到测试和上线。

如果你想做基础设施方向的 FDE，这是最先要练的技能。让软件在自己的机器上跑起来很容易，难的是让它在别人的机器上跑起来，并且依然知道它是健康还是出了问题。

## 数据在谁手里，计算在谁手里？

敲下第一条命令之前，你要先弄清客户买的是哪种模型，因为这决定了数据由谁持有、计算由谁运行，以及你将在哪里调试。Reducto 提供三种部署模型，其中 VPC 和本地部署（on-premise）仅面向 Enterprise 客户。基础设施方向的 FDE 最常遇到的两种区别如下。

| | 混合 VPC | 完整 VPC / 本地部署 |
|---|---|---|
| 数据与存储 | 留在客户的云账号中 | 位于客户环境内部 |
| 计算 | 完全由 Reducto 托管，使用临时的单租户 worker，除内存外不保留任何内容 | 在客户环境内部运行 |
| 你能看到什么 | Reducto 数据库中的作业记录：状态、请求设置、结果存放位置 | 只能看到客户允许的内容，因为可观测性访问和出站流量由客户控制 |
| 客户控制什么 | 云账号以及接收结果的存储 | 网络、API 访问、Kubernetes 策略、存储策略、可观测性、出站流量 |

最后一行体现了客户控制范围的扩大，而完整 VPC/本地部署这一列是最难的情况。在混合 VPC 下，结果写入客户的存储，但你仍有作业记录可以查看状态。在完整 VPC 或本地部署下，连查看系统的权限都需要你去申请。

所以项目启动时要问的不只是数据放在哪里，还要问：谁给你开通日志和指标的访问权限，信号允许通过哪条路径发出去。

## 不碰文档，也能看清系统

Reducto 的安全策略划了一条清晰的界线：遥测是只含元数据的可观测性手段，不是数据管道。它可以包含作业 ID、计数、状态、耗时、脱敏后的错误类别，以及其他用于调试的元数据。

职责划分同样分明。Reducto 负责应用安全和租户级访问控制；客户负责对外网络暴露、调用方授权、出站流量控制、Kubernetes 加固，以及遥测的路由和保留期限。换句话说，你的遥测发往哪里、保存多久，由客户决定。

**要点:** 在客户环境里，一开始没有度量的东西，日后就无从修复。

设想你为一个文档处理作业设计的遥测事件。下面是假设的示例，并非 Reducto 的数据结构：

```json
{
  "event": "job_completed",
  "job_id": "j_8f2c",
  "pages": 42,
  "file_type": "pdf",
  "duration_ms": 18350,
  "worker": "cpu-3",
  "status": "partial",
  "error_code": "TABLE_PARSE_TIMEOUT"
}
```

没有文件名，也没有文档里的任何一行文字。但当客户反馈“昨天那份合同文件返回的结果不完整”时，你依然可以筛选出 `partial` 状态的作业，发现它们都超过 40 页，并且带有同一个超时错误码。这样你就有了一个可以用自己的样本数据去验证的假设。

由此得出的教训是：错误类别必须足够具体。一个笼统的 `error: "failed"` 字段，会逼得你去请客户把文档发过来，而这恰恰是合同所禁止的。

## 一次升级，逐步拆解

首次安装只是开始，因为本地部署的版本之后还要升级很多次。Reducto 本地部署版 v1.12.6 的更新日志写明：Helm 要求 Redis 以及健康的 io/cpu worker（v1.12.6 或更高版本）就位后，才允许升级。

这句话有两种理解：要么是 worker 必须运行 v1.12.6 的镜像，要么是这种阻断机制从 chart v1.12.6 起才存在。所以不要把其中一种理解硬编码进脚本。去问厂商，并把版本检查保留为可选项。

这个阻断条件保护了你，但也可能让升级卡在客户安排好的维护窗口中途。在 Helm 有机会拒绝之前，先自己跑一遍检查。下面的脚本用来说明思路；其中的命名空间、标签、deployment 名称，以及镜像标签带有版本号的假设，都是虚构的：

```bash
#!/usr/bin/env bash
set -euo pipefail
NS="${1:-reducto}"
CHECK_VERSION="${CHECK_VERSION:-0}"   # 与厂商确认后设为 =1
MIN="1.12.6"

echo "== Redis =="
redis_ready=$(kubectl -n "$NS" get pods -l app=redis \
  -o jsonpath='{.items[*].status.containerStatuses[*].ready}')
[[ -n "$redis_ready" && "$redis_ready" != *false* ]] \
  || { echo "Redis 缺失或未就绪"; exit 1; }

for role in io cpu; do
  echo "== worker $role =="
  d="worker-$role"
  ready=$(kubectl -n "$NS" get deploy "$d" -o jsonpath='{.status.readyReplicas}')
  want=$(kubectl -n "$NS" get deploy "$d" -o jsonpath='{.spec.replicas}')
  [ "${ready:-0}" = "$want" ] \
    || { echo "$d 未就绪：${ready:-0}/$want"; exit 1; }

  if [ "$CHECK_VERSION" = "1" ]; then
    image=$(kubectl -n "$NS" get deploy "$d" \
      -o jsonpath='{.spec.template.spec.containers[0].image}')
    ver="${image##*:}"; ver="${ver#v}"
    lowest=$(printf '%s\n%s\n' "$MIN" "$ver" | sort -V | head -n1)
    [ "$lowest" = "$MIN" ] \
      || { echo "$d 当前版本为 $ver，低于 $MIN"; exit 1; }
  fi
done

echo "预检通过，可以执行 helm upgrade"
```

默认情况下，如果 Redis 没有 pod 或有容器未就绪，或者某个 worker 副本数不足，脚本就会停止。用 `CHECK_VERSION=1` 运行时，它还会把 worker 镜像标签中的版本与 v1.12.6 进行比较。它不能取代 Helm 自身的检查；如果客户使用 `latest` 之类的标签或镜像摘要，你就得换一种方式读取版本。

这个脚本的价值之一在于客户可以自己运行。如果客户不给你 kubeconfig，一个打包成单条命令、输出结果清晰易读的诊断工具，就能弥补你没有的访问权限。

## 集群无法访问互联网时

Honeycomb 把这种情况分成两种模型：由厂商自己运维平台，或者由客户管理全部基础设施。第二种适合物理隔离（air-gapped）环境，在那里，你原以为会“自动回传”的每一个信号都得另找出路。

Replicated 的做法是：收集物理隔离环境中安装实例的遥测，并存放在客户环境内的一个 Kubernetes Secret 中。只有当有人在该环境中生成支持包（support bundle）时，这些数据才会到达厂商，而且本地存储有容量上限。

实际影响是：如果客户每隔几个月才生成一次支持包，较早的信号可能早已被挤出存储。对于用量、日志、补丁等每一类数据流，都要在一开始就和客户确认：它怎么出去，由谁带出去，多久一次。

## 动手试试：上线前要完成的五项任务

第一项任务是和客户一起确定部署模型，回答三个问题：数据放在哪里，计算在哪里运行，集群能否访问互联网。把答案写进项目启动文档，并由安全团队签字确认。

接着列一张表，写明客户方每一项事务的负责人：对外网络暴露、出站流量、Kubernetes 策略、可观测性访问、遥测路由和保留期限。每一行都要有具体的人名。

然后以书面形式约定遥测字段清单，让安全团队逐个字段审查。之后，为每一次升级操作编写预检，并请客户在预发布环境中试运行。最后，写一份假设你没有任何访问权限的故障处理手册：客户要运行哪些命令，要把哪些输出发回给你。

## 让项目拖延一个月的那些错误

最常见的错误是把客户环境当成自己的开发环境。你可能会碰到内部镜像仓库、代理、网络策略和变更审批流程，而这些在厂商文档里从未提及。

其次是把遥测留到以后再做。由于遥测路由由客户控制，等到第一次出故障时再加一个日志字段，可能需要再走一轮安全审查。相反的错误是日志打得太多：只要有一行把文档内容打印到 stdout，就足以打破整份合同赖以成立的“只含元数据”承诺。

最后一个错误是把升级当成一条命令。它其实是一次排好期的工作会话，有前置条件，也有回滚路径。如果没有在预发布环境测试过回滚，你就等于没有回滚路径。

## 求职时如何证明这项技能

阅读 FDE 招聘启事时，留意“customer environments”“on-prem”“VPC”或“air-gapped”这类字眼。

在简历上，不要写“熟悉 Kubernetes”。可以写这样一行：在客户运维的集群上部署并升级系统，并提供客户运维人员可自行运行的诊断工具。

如果还没有真实项目，一个包含 Helm chart、预检脚本和只含元数据的遥测数据结构的代码仓库，就是面试官五分钟内能看完的证据。

客户的集群永远不会属于你。但只要从第一天起就为“看得见它”而设计，你依然能把工作做好。

**本周动手试试:**

- 在本地搭建一个 Kubernetes 集群（kind 或 minikube），部署 Redis 和两个 worker deployment，然后编写预检脚本：Redis 或 worker 未就绪时阻止升级，版本检查保留为可选。
- 找一个你在运行的服务，列出它记录的所有日志字段，然后划掉任何可能包含客户数据的字段。
- 针对“客户报告故障，而你无法访问集群”这一场景，写一页故障处理手册。

## 信源

- [Forward Deployed Engineer, Infrastructure Specialist](https://reducto.ai/careers/cf1751ef-0b97-4bd3-af10-1f95514b8f8e)

- [On-prem changelog (Reducto docs)](https://docs.reducto.ai/onprem/changelog.md)

- [Hybrid VPC deployment (Reducto docs)](https://docs.reducto.ai/onprem/hybrid-vpc-deployment)

- [Enterprise deployment options (Reducto docs)](https://docs.reducto.ai/onprem/enterprise_deployment_options)

- [Security model (Reducto docs)](https://docs.reducto.ai/onprem/security_model)

- [Enterprise readiness (Reducto docs)](https://docs.reducto.ai/enterprise/enterprise-readiness)

- [Telemetry for air gap instances (Replicated docs)](https://docs.replicated.com/vendor/telemetry-air-gap)

- [Deployment models (Honeycomb docs)](https://docs.honeycomb.io/private-cloud/deployment-models)
