# safety-eval-service 本地 JAR 打包发布到 100 服务器 K8s 完整流程

> 本文档记录了将本地 Windows 开发机上的 `safety-eval-service` JAR 包发布到 GBS K8s 集群（主节点 `192.168.20.100`）的全链路操作步骤，包括所用工具、命令、账号密码和注意事项。

---

## 1. 环境总览

### 1.1 网络与服务器

| 角色 | IP | 说明 |
|---|---|---|
| K8s Master | `192.168.20.100` | 控制面，Docker/Maven/Nexus/NFS |
| K8s Worker01 | `192.168.20.110` | Pod 运行节点 |
| K8s Worker02 | `192.168.20.120` | Pod 运行节点 |
| Nexus 私服 | `192.168.20.100:8578` | Maven snapshots/releases 仓库 |
| MySQL | `192.168.20.100:33080` | GBS 共享数据库 |
| Nacos | `prod-nacos:8848`（集群内）/ `192.168.20.100:30290`（NodePort） | 配置中心 + 注册中心 |
| Gateway | `192.168.20.100:30140`（NodePort） | SCG 统一入口 |

K8s 版本 `v1.27.6`，命名空间 `jjb-dragon`。

### 1.2 本地开发机

- OS：Windows（Git Bash / cmd / PowerShell 均可）
- 项目路径：`E:\works\projects\safety-eval-service`
- 模块：Maven 多模块（DDD 六层），启动模块为 `safety-eval-start`
- 本地 JDK：1.8.x
- 构建产物：`safety-eval-start/target/safety-eval-start-1.0-SNAPSHOT.jar`

### 1.3 容器镜像仓库

| 仓库 | 地址 | 用途 | 用户名 | 密码 |
|---|---|---|---|---|
| 杭州 ACR（默认发布仓） | `jjb-registry-registry.cn-hangzhou.cr.aliyuncs.com` | 主镜像仓，K8s `image-pull-secret` 指向此处 | `10952138@qq.com` | `idurCT!rIq9EzISD` |
| 张家口 ACR（备用/本地构建仓） | `registry.cn-zhangjiakou.aliyuncs.com` / VPC: `registry-vpc.cn-zhangjiakou.aliyuncs.com` | 另一台阿里云服务器本地构建用 | `kilaa` | `Kilaa@6666` |
| 张家口 ACR AK | — | — | `LTAI5tF8GbtKizLAowJbAYmT` | `G0ZYeaZtnpxhp0bO1rPp4HPrq57LaT` |

镜像命名规范：

- 杭州 ACR：`{ACR}/ali_img_ns/prod-aly-{env}-dragon-{app}:{env}-YYYYMMDD-{N}`
  - 示例：`jjb-registry-registry.cn-hangzhou.cr.aliyuncs.com/ali_img_ns/prod-aly-ota-dragon-jjb-saas-safety-eval:ota-20260703-1`
- 张家口 ACR：`registry.cn-zhangjiakou.aliyuncs.com/kilaa/zcloud-gbs-operation:jjb-saas-safety-eval-ota-YYYYMMDD-{N}`

### 1.4 SSH / 系统账号

| 目标 | 用户 | 密码 |
|---|---|---|
| `192.168.20.100`（master）SSH | `root` | `Zcloud@zcloud100` |

### 1.5 K8s 拉取凭据

- Secret 名：`image-pull-secret`（底座镜像）/ `image-pull-secret1`（应用镜像）
- 类型：`kubernetes.io/dockerconfigjson`
- **当前只配置了杭州 ACR 的 auth**，因此 K8s 部署必须使用杭州 ACR 地址

### 1.6 数据库

| 项 | 值 |
|---|---|
| Host | `192.168.20.100:33080` |
| 用户 | `root` |
| 密码 | `Mysql@zcloud33080` |

---

## 2. 本服务基础信息

| 配置项 | 值 |
|---|---|
| app.name | `jjb-saas-cq-anquan` |
| gateway 路由名 | `cqanquan` |
| server.port（本地 / K8s 容器内） | `8095` |
| context-path | `/safety-eval` |
| Dubbo 端口 | `20895` |
| Nacos 服务名 | `safety-eval-service` |
| Spring Boot 版本 | `2.7.18` |
| 架构 | DDD 六层（adapter / app / client / domain / infrastructure / start） |

> **端口说明**：本服务在 K8s 容器内仍监听 `8095`，与本地一致；Service 通过 `port: 80 / targetPort: 8095` 对外暴露 80 端口。GBS 其他部分服务会在 Nacos prod 配置中将 `server.port` 覆盖为 80，本服务暂未做此覆盖，部署时务必确认实际监听端口再填写 `containerPort` 与 `targetPort`。

K8s 资源：

- Deployment：`jjb-saas-safety-eval-deploy`
- Pod Label：`app=jjb-saas-safety-eval-pod`
- Service：`jjb-saas-safety-eval`（ClusterIP，80→8095，20895→20895）

---

## 3. 完整发布流程

发布流程分为 5 个阶段：**本地编译 → 上传 JAR → 构建镜像 → 推送 ACR → K8s 部署**。

### 阶段 1：本地 Maven 编译

在 Windows 本地打包：

```bash
cd E:\works\projects\safety-eval-service
mvn clean package -DskipTests
```

产物路径：`safety-eval-start/target/safety-eval-start-1.0-SNAPSHOT.jar`（约 226 MB）。

> 也可将源码上传到 master 节点用 `/opt/maven`（Maven 3.8.8，阿里云镜像）远程编译，避免本地环境差异。

### 阶段 2：上传 JAR 到 master 节点

使用 SFTP 或 SCP 将 JAR 上传到 `192.168.20.100`：

```bash
# 方式 A：scp（需要 sshpass 或在 Git Bash 中交互式输密码）
scp safety-eval-start/target/safety-eval-start-1.0-SNAPSHOT.jar root@192.168.20.100:/tmp/safety-eval.jar

# 方式 B：Python paramiko 脚本（推荐，见 docs/build-tools/build_push.py）
python docs/build-tools/build_push.py ^
    E:\works\projects\safety-eval-service\safety-eval-start\target\safety-eval-start-1.0-SNAPSHOT.jar ^
    jjb-saas-safety-eval ota 1
```

### 阶段 3：构建 Docker 镜像

#### 方式 A：使用 `build.sh`（推荐，在 master 上执行）

`docs/build-tools/build.sh` 会在 master 上自动：

1. 将 JAR + JDK + Dockerfile 组装为 build context
2. `docker build` 生成镜像
3. `docker push` 推到杭州 ACR

```bash
ssh root@192.168.20.100
cd /path/to/build-tools
./build.sh jjb-saas-safety-eval ota /tmp/safety-eval.jar 1
```

生成镜像：`jjb-registry-registry.cn-hangzhou.cr.aliyuncs.com/ali_img_ns/prod-aly-ota-dragon-jjb-saas-safety-eval:ota-YYYYMMDD-1`

#### 方式 B：使用根目录 Dockerfile

项目根 `Dockerfile` 基于 `jjb-registry-registry.cn-hangzhou.cr.aliyuncs.com/pub/jdk:1.8.0_202`（镜像已含 JDK），构建更轻量：

```bash
# 在 master 上
mkdir -p /tmp/build-safety-eval/start/target
cp /tmp/safety-eval.jar /tmp/build-safety-eval/start/target/start.jar
cp Dockerfile /tmp/build-safety-eval/
cd /tmp/build-safety-eval
docker build -t jjb-registry-registry.cn-hangzhou.cr.aliyuncs.com/ali_img_ns/prod-aly-ota-dragon-jjb-saas-safety-eval:ota-YYYYMMDD-1 .
```

#### 方式 C：复用 master 上已有的张家口 ACR 镜像（最快）

如果张家口 ACR 已有当天构建的镜像（如 `jjb-saas-safety-eval-ota-YYYYMMDD-N`），直接 retag + push 到杭州 ACR，免去重新构建：

```bash
# 在 master 上
SRC=registry.cn-zhangjiakou.aliyuncs.com/kilaa/zcloud-gbs-operation:jjb-saas-safety-eval-ota-YYYYMMDD-N
DST=jjb-registry-registry.cn-hangzhou.cr.aliyuncs.com/ali_img_ns/prod-aly-ota-dragon-jjb-saas-safety-eval:ota-YYYYMMDD-1

docker pull $SRC
docker tag $SRC $DST
echo 'idurCT!rIq9EzISD' | docker login --username=10952138@qq.com --password-stdin jjb-registry-registry.cn-hangzhou.cr.aliyuncs.com
docker push $DST
```

### 阶段 4：部署到 K8s

#### 首次部署（create）

```bash
# 在 master 上
/usr/bin/kubectl apply -f deployment.yaml -n jjb-dragon
/usr/bin/kubectl apply -f service.yaml -n jjb-dragon
```

`deployment.yaml` 参考（见 docs/deploy/safety-eval-deploy.yaml）：

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    deployLabel: jjb-saas-safety-eval-deploy
  name: jjb-saas-safety-eval-deploy
  namespace: jjb-dragon
spec:
  replicas: 1
  selector:
    matchLabels:
      app: jjb-saas-safety-eval-pod
  template:
    metadata:
      labels:
        app: jjb-saas-safety-eval-pod
        namespace: jjb-dragon
    spec:
      containers:
      - name: jjb-saas-safety-eval-app
        image: jjb-registry-registry.cn-hangzhou.cr.aliyuncs.com/ali_img_ns/prod-aly-ota-dragon-jjb-saas-safety-eval:ota-YYYYMMDD-N
        imagePullPolicy: Always
        command: ["/opt/jdk1.8.0_202/bin/java"]
        args:
        - -Dnacos.namespace=jjb-dragon
        - -Dnacos.url=prod-nacos:8848
        - -Dspring.profiles.active=prod
        - -Dmysql.password=Mysql@zcloud33080
        - -Dmysql.host=192.168.20.100
        - -Dmysql.port=33080
        - -Dmysql.username=root
        - -jar
        - /opt/app.jar
        env:
        - name: TZ
          value: Asia/Shanghai
        ports:
        - containerPort: 8095
          name: http
        - containerPort: 20895
          name: dubbo
        resources:
          limits:
            cpu: "1"
            memory: 2048M
          requests:
            cpu: 500m
            memory: 512M
      imagePullSecrets:
      - name: image-pull-secret
```

`service.yaml` 参考（见 docs/deploy/safety-eval-svc.yaml）：

```yaml
apiVersion: v1
kind: Service
metadata:
  name: jjb-saas-safety-eval
  namespace: jjb-dragon
spec:
  type: ClusterIP
  selector:
    app: jjb-saas-safety-eval-pod
  ports:
  - name: http
    port: 80
    targetPort: 80
  - name: dubbo
    port: 20895
    targetPort: 20895
```

#### 更新部署（已存在 deploy）

只替换镜像 tag：

```bash
/usr/bin/kubectl set image deployment/jjb-saas-safety-eval-deploy \
    jjb-saas-safety-eval-app=jjb-registry-registry.cn-hangzhou.cr.aliyuncs.com/ali_img_ns/prod-aly-ota-dragon-jjb-saas-safety-eval:ota-YYYYMMDD-N \
    -n jjb-dragon

# 强制滚动（不修改镜像时触发重启）
/usr/bin/kubectl rollout restart deployment/jjb-saas-safety-eval-deploy -n jjb-dragon
```

### 阶段 5：验证

```bash
# Pod 状态
/usr/bin/kubectl get pods -n jjb-dragon | grep safety-eval

# 启动日志
/usr/bin/kubectl logs -f deployment/jjb-saas-safety-eval-deploy -n jjb-dragon --tail=100

# Nacos 注册检查（通过 NodePort）
curl -s "http://192.168.20.100:30290/nacos/v1/ns/instance/list?serviceName=safety-eval-service&namespaceId=jjb-dragon"

# Endpoints
/usr/bin/kubectl get endpoints jjb-saas-safety-eval -n jjb-dragon
```

正常日志标志：

```
Tomcat started on port(s): 8095 (http) with context path ''
Nacos registry, DEFAULT_GROUP safety-eval-service 10.x.x.x:8095 register finished
```

---

## 4. 工具清单

| 工具 | 版本 | 位置 | 用途 |
|---|---|---|---|
| Maven | 3.8.8 | 本地 / master `/opt/maven` | JAR 编译打包 |
| JDK | 1.8.0_202 | 本地 / master `/opt/jdk1.8.0_202` | Java 运行环境 |
| Docker | 26.1.4 | master `/usr/bin/docker` | 镜像构建推送 |
| kubectl | v1.27.6 | master `/usr/bin/kubectl` | K8s 资源管理 |
| scp / sftp | — | 本地 → master | JAR 文件上传 |
| paramiko | 5.0.0 | 本地 Python 3.11 | SSH 自动化（推荐） |
| ssh | OpenSSH | 本地 Git Bash / cmd | 远程登录 master |
| curl | — | master / 本地 | Nacos/HTTP 验证 |
| build.sh | 自研 | `docs/build-tools/build.sh` | 一键构建推送（Shell 版） |
| build_push.py | 自研 | `docs/build-tools/build_push.py` | 本地 Windows 一键构建推送（Python 版） |

---

## 5. 常用命令速查

```bash
# 登录 master
ssh root@192.168.20.100          # 密码：Zcloud@zcloud100

# 登录杭州 ACR
echo 'idurCT!rIq9EzISD' | docker login --username=10952138@qq.com --password-stdin jjb-registry-registry.cn-hangzhou.cr.aliyuncs.com

# 登录张家口 ACR
echo 'Kilaa@6666' | docker login --username=kilaa --password-stdin registry.cn-zhangjiakou.aliyuncs.com

# 查看 deploy
/usr/bin/kubectl get deploy,pods,svc -n jjb-dragon | grep safety-eval

# 查看日志
/usr/bin/kubectl logs -f deployment/jjb-saas-safety-eval-deploy -n jjb-dragon

# 重启（不改镜像）
/usr/bin/kubectl rollout restart deployment/jjb-saas-safety-eval-deploy -n jjb-dragon

# 进入 Pod
/usr/bin/kubectl exec -it deployment/jjb-saas-safety-eval-deploy -n jjb-dragon -- /bin/bash

# Nacos 控制台
http://192.168.20.100:30290/nacos      # nacos/nacos

# Nexus 私服
http://192.168.20.100:8578             # admin/admin123
```

---

## 6. 注意事项与踩坑

1. **端口差异排查**：本服务本地与容器内均为 `8095`，Service 用 `port: 80 / targetPort: 8095`。但 GBS 部分老服务会被 Nacos `xxx-prod.yml` 覆盖为 `80`，因此新服务上线前必须用 `kubectl get endpoints -n jjb-dragon` 或 Pod 启动日志确认实际监听端口，再填写 `containerPort` 与 `targetPort`。Dubbo 端口 `20895` 不受影响。

2. **镜像仓库选择**：K8s `image-pull-secret` 只配了杭州 ACR。如果从张家口 ACR 拉镜像，Pod 会报 `ImagePullBackOff`。解决方案：
   - 方案 A：在 master 上 retag + push 到杭州 ACR（推荐）
   - 方案 B：给 `image-pull-secret` 追加张家口 ACR 的 auth

3. **lb:// URI scheme**：SCG 网关路由中若有 `lb://` 协议，不会在 K8s 中被解析，需改为 `http://{svc-name}` 直连 K8s DNS，并设 `filterGatewaName=NULL`。

4. **Dubbo 注册 namespace**：Dubbo provider 注册到 Nacos 的命名空间是 `jjb-dragon-facade`（带 `-facade` 后缀），consumer 必须配置 `dubbo.registry.parameters.namespace=jjb-dragon-facade`。

5. **Nacos 不可达导致启动失败**：Dubbo 3.2.x 无显式 `config-center` 时会自动从 registry 推导，需同时检查 config-center 配置。

6. **镜像 tag 冲突**：每天多次发布时，version-suffix（`ota-YYYYMMDD-N` 的 `N`）需递增，否则 `ImagePullPolicy: Always` 拉到的可能是缓存的旧镜像。

7. **主节点构建依赖**：master 上的 `/opt/jdk1.8.0_202` 是从 Docker 镜像提取的，`/opt/maven` 配置了阿里云镜像。`/opt/docker-templates/` 下有 Dockerfile 模板。

---

## 7. 附：账号密码汇总

| 用途 | 地址 | 账号 | 密码/Token |
|---|---|---|---|
| Master SSH | `192.168.20.100:22` | `root` | `Zcloud@zcloud100` |
| 杭州 ACR | `jjb-registry-registry.cn-hangzhou.cr.aliyuncs.com` | `10952138@qq.com` | `idurCT!rIq9EzISD` |
| 张家口 ACR | `registry.cn-zhangjiakou.aliyuncs.com` | `kilaa` | `Kilaa@6666` |
| 张家口 ACR AK | — | `LTAI5tF8GbtKizLAowJbAYmT` | `G0ZYeaZtnpxhp0bO1rPp4HPrq57LaT` |
| MySQL | `192.168.20.100:33080` | `root` | `Mysql@zcloud33080` |
| Nacos | `http://192.168.20.100:30290/nacos` | `nacos` | `nacos` |
| Nexus | `http://192.168.20.100:8578` | `admin` | `admin123` |

---

## 8. 文件索引

```
docs/
├── build-tools/
│   ├── build.sh          # Shell 一键构建推送（在 master 上执行）
│   ├── build_push.py     # Python 一键构建推送（Windows 本地执行）
│   ├── Dockerfile        # CentOS7+JDK 版 Dockerfile 模板
│   ├── README.md         # build-tools 使用说明
│   └── install_env.sh    # master 环境初始化脚本
├── deploy/
│   ├── safety-eval-deploy.yaml   # K8s Deployment
│   └── safety-eval-svc.yaml      # K8s Service
└── 本地JAR打包发布到100服务器完整流程.md   # 本文档
Dockerfile                          # 项目根 Dockerfile（基于 pub/jdk:1.8.0_202）
```

---

## 9. 服务启动验证（多种确认方式）

部署完成后需要多维度确认服务已真正可用。下面按"由浅入深"列出 8 种验证方式，建议至少跑完 1～4 步。

> 所有命令默认在 master 节点（`192.168.20.100`）上执行，已登录 root。

### 9.1 K8s 资源状态检查

```bash
# Pod / Deployment 是否 READY 1/1、Running
/usr/bin/kubectl get deploy,pods,svc -n jjb-dragon | grep safety-eval

# 滚动升级是否完成（无旧 ReplicaSet 残留）
/usr/bin/kubectl rollout status deployment/jjb-saas-safety-eval-deploy -n jjb-dragon --timeout=180s

# Endpoints 是否绑到 Pod IP（空说明 Service 没选到 Pod）
/usr/bin/kubectl get endpoints jjb-saas-safety-eval -n jjb-dragon

# 最近事件（ImagePullBackOff、CrashLoopBackOff、OOMKilled 等都能在这里看到）
/usr/bin/kubectl get events -n jjb-dragon --sort-by=.metadata.creationTimestamp \
    | grep -i safety-eval | tail -20

# Pod 详情（重点看 Conditions、ContainerStatus、LastState）
/usr/bin/kubectl describe pod -n jjb-dragon -l app=jjb-saas-safety-eval-pod
```

**判断标准**：

| 项 | 健康值 |
|---|---|
| Deployment READY | `1/1` |
| Pod STATUS | `Running` |
| RESTARTS | `0`（或长期稳定的小数字） |
| Endpoints | 非空，且包含 Pod IP + `8095,20895` |

### 9.2 启动日志

```bash
# 实时跟踪日志
/usr/bin/kubectl logs -f deployment/jjb-saas-safety-eval-deploy -n jjb-dragon --tail=200

# 只看最后 50 行
/usr/bin/kubectl logs deployment/jjb-saas-safety-eval-deploy -n jjb-dragon --tail=50
```

**关键日志标志**：

```
Tomcat started on port(s): 8095 (http) with context path ''
Nacos registry, DEFAULT_GROUP safety-eval-service 10.244.x.x:8095 register finished
```

**失败标志**（出现任一则服务不可用）：

- `APPLICATION FAILED TO START`
- `BeanCreationException` / `UnsatisfiedDependencyException`
- `Connection refused` 到 MySQL / Nacos / Redis
- `Unable to acquire JDBC Connection`
- `Address already in use`
- `Killed`（OOM，需调大 `resources.limits.memory`）

### 9.3 Nacos 服务注册（控制台 + API）

**控制台**：浏览器打开 `http://192.168.20.100:30290/nacos`，账号 `nacos/nacos`。

1. 左上角切换命名空间到 `jjb-dragon`
2. 菜单 → 服务管理 → 服务列表 → 搜索 `safety-eval-service`
3. 点进去查看实例列表，健康状态应为绿色 `健康`

**API 方式**（适合脚本化）：

```bash
# 列出 jjb-dragon 命名空间下所有服务
curl -s "http://192.168.20.100:30290/nacos/v1/ns/service/list?pageNo=1&pageSize=200&namespaceId=jjb-dragon" \
  | python3 -c "import sys,json; d=json.load(sys.stdin); print([s for s in d['doms'] if 'safety' in s])"

# 查看 safety-eval-service 的实例健康状态
curl -s "http://192.168.20.100:30290/nacos/v1/ns/instance/list?serviceName=safety-eval-service&namespaceId=jjb-dragon" \
  | python3 -m json.tool
```

**判断标准**：`hosts[].healthy=true`、`enabled=true`、`port=8095`，且至少有一条实例 IP 是当前 Pod IP。

### 9.4 Dubbo Provider 注册

Dubbo 服务注册在独立的命名空间 `jjb-dragon-facade`：

```bash
# 列出 Dubbo 服务
curl -s "http://192.168.20.100:30290/nacos/v1/ns/service/list?pageNo=1&pageSize=500&namespaceId=jjb-dragon-facade" \
  | python3 -c "import sys,json; d=json.load(sys.stdin); print([s for s in d['doms'] if 'safety' in s.lower() or 'Cqanquan' in s or 'cqanquan' in s])"

# 查看某个 Dubbo 接口的实例
curl -s "http://192.168.20.100:30290/nacos/v1/ns/instance/list?serviceName=providers:com.xxx.XxxService::&namespaceId=jjb-dragon-facade"
```

**判断标准**：`providers:com.xxx.XxxService::jjb-dragon` 形式的条目存在且健康。

### 9.5 Spring Boot Actuator

如果 `application.yml` 开启了 actuator，可直接访问健康/信息端点。

```bash
# 从 Pod 内部访问
/usr/bin/kubectl exec -it deployment/jjb-saas-safety-eval-deploy -n jjb-dragon -- \
    curl -s http://localhost:8095/actuator/health

# 从 master 经 Pod IP 访问
POD_IP=$(/usr/bin/kubectl get pod -n jjb-dragon -l app=jjb-saas-safety-eval-pod \
    -o jsonpath='{.items[0].status.podIP}')
curl -s "http://${POD_IP}:8095/actuator/health"
curl -s "http://${POD_IP}:8095/actuator/info"
```

**判断标准**：`{"status":"UP"}` 且各 component（db、nacos、dubbo 等）无 DOWN。

### 9.6 集群内直连（Pod → Pod）

进入一个同集群的 Pod，直接 curl 目标服务的 HTTP 接口：

```bash
# 进入 safety-eval 自己的 Pod 测试
/usr/bin/kubectl exec -it deployment/jjb-saas-safety-eval-deploy -n jjb-dragon -- /bin/bash
# 在 Pod 内
curl -s http://localhost:8095/safety-eval/            # context-path
curl -s http://localhost:8095/actuator/health

# 从另一个 Pod（如 jjb-saas-base）测试跨服务调用
/usr/bin/kubectl exec -it deployment/jjb-saas-base-deploy -n jjb-dragon -- \
    curl -s http://jjb-saas-safety-eval/safety-eval/
```

**判断标准**：HTTP 200 或返回业务 JSON，而非 `Connection refused` / `502`。

### 9.7 通过网关访问（NodePort）

网关 NodePort `30140`，路由通过 MySQL `jjb-saas-gateway.route` 表配置。本服务的 `gateway=cqanquan`，需携带 `filterGatewaName` 请求头：

```bash
# 从 master / 任意能访问 100 的机器
curl -i "http://192.168.20.100:30140/safety-eval/" \
    -H "filterGatewaName: cqanquan"

# 带 Token 的业务请求示例
curl "http://192.168.20.100:30140/safety-eval/api/xxx" \
    -H "filterGatewaName: cqanquan" \
    -H "Authorization: Bearer <token>"
```

**判断标准**：

- `200/401/404` 等业务状态码 → 服务已通
- `502 Bad Gateway` / `503 Service Unavailable` → 服务未注册或端口不对
- `404` 但网关日志有路由 → 路由正常，接口路径不对

### 9.8 本地开发机 port-forward（调试用）

把 K8s Service 映射到本地，浏览器或 Postman 直接访问：

```bash
# 本地 Windows PowerShell / Git Bash
ssh -L 8095:jjb-saas-safety-eval:80 root@192.168.20.100
# 或
kubectl port-forward svc/jjb-saas-safety-eval 8095:80 -n jjb-dragon
```

然后本地访问 `http://localhost:8095/safety-eval/` 即可。

---

### 验证清单（推荐顺序）

| # | 检查项 | 命令 / 工具 | 预期 |
|---|---|---|---|
| 1 | Pod Running + Endpoints 有值 | `kubectl get pods,endpoints` | `1/1 Running`、IP:8095,20895 |
| 2 | 启动日志无报错 | `kubectl logs --tail=200` | 出现 `Tomcat started on port(s): 8095` |
| 3 | Nacos 注册成功 | Nacos 控制台 / API | `safety-eval-service` healthy=true |
| 4 | Dubbo Provider 注册 | Nacos API（jjb-dragon-facade） | `providers:xxx::jjb-dragon` 存在 |
| 5 | Actuator 健康 | `curl /actuator/health` | `{"status":"UP"}` |
| 6 | Pod 内 HTTP 可达 | `curl localhost:8095/safety-eval/` | 200 或业务 JSON |
| 7 | 网关路由可达 | `curl :30140/safety-eval/ -H filterGatewaName:cqanquan` | 非 502/503 |
| 8 | 业务接口冒烟 | Postman 或 curl 调用真实接口 | 业务返回正常 |

