Skip to content

JumpServer 集群内部署指南:通过堡垒机连接 KubeVirt 虚拟机

环境基线: RKE2 v1.35.6(6 节点)+ KubeVirt v1.9.0,Rocky Linux 9.8,Calico VXLAN(Pod CIDR 10.22.0.0/16natOutgoing: true入口节点: root@192.168.122.31(sza122031.local) 镜像仓库: Harbor 192.168.122.156:30000JumpServer 版本: v4.10.x LTS(官方 Chart appVersion v4.10.19;安全基线 ≥ v4.10.17) 关联文档: ./kubevirt-practical-operations-guide.md(3.4 固定 IP 与热迁移)、./kubevirt-tutorial-beginner-to-master.md(第 8 章网络)、../rke2-kubevirt-deployment-guide.md(2.1 Harbor 代理缓存)

目录

  1. 方案总览
  2. 组件架构
  3. 前置准备
  4. 部署 JumpServer
  5. 把 KubeVirt 虚拟机接入 JumpServer
  6. 日常使用方式
  7. 运维:备份、升级、排障
  8. 安全注意事项
  9. 入口变体:固定 ClusterIP 与固定物理网段 IP(测试场景)

1. 方案总览

在 RKE2 集群内部署 JumpServer(开源堡垒机),通过它统一连接、授权、审计 对 KubeVirt 虚拟机(物理网段固定 IP,如 192.168.122.102)的访问:

运维人员 ──浏览器/SSH 客户端──> JumpServer(K8s Pod,NodePort 入口)

                                    │ koko 组件发起 SSH(Pod CIDR → 物理网段)

                        VM eth1 固定 IP 192.168.122.102:22(Multus br-vm)

1.1 为什么用官方 Helm Chart

  • JumpServer 官方提供 Kubernetes Helm 部署方式(jumpserver/helm-charts), 组件拆分清晰,比 docker-compose 更适合已有 K8s 集群的环境;
  • 会话录像、配置落盘到 PVC,可直接使用现网 vm-nas(NFS)存储;
  • 与虚拟机同集群部署:JumpServer Pod 到 VM 固定 IP 一跳直达,无需网域/代理。

1.2 网络可达性(关键点)

JumpServer Pod 在 Calico Pod 网络(10.22.0.0/16)内,目标是物理网段 192.168.122.0/24 上的 VM 固定 IP:

  • 现网 IPPool 已配置 natOutgoing: true,Pod → 物理网段的流量会被 SNAT 为 节点 IP,VM 回包可达——无需任何额外路由
  • 验证方法(部署后执行):
bash
kubectl -n jumpserver exec deploy/jumpserver-jms-koko -- \
  python3 -c "import socket; s=socket.create_connection(('192.168.122.102',22),5); print('OK'); s.close()"
  • 若 VM 只有 masquerade Pod 网络(无固定 IP),Pod→Pod 互通同样可行, 但 Pod IP 会随重建变化,资产记录会失效——因此接入堡垒机的 VM 必须使用 Multus 第二网卡固定 IP(见实战手册 3.4)。

2. 组件架构

官方 Chart(v4.10.19)包含以下组件,不自带数据库——PostgreSQL 与 Redis 必须外部提供(第 3.3 节给出部署清单):

组件镜像(CE 版)作用本场景
core(含 celery 任务)docker.io/jumpserver/coreWeb 控制台、API、任务队列必需
kokodocker.io/jumpserver/kokoSSH 接入、Web 终端、文件传输必需(连 VM 的核心)
webdocker.io/jumpserver/webNginx 统一入口必需
liondocker.io/jumpserver/lionRDP/VNC Web 连接(Guacamole)可选
chendocker.io/jumpserver/chen数据库 Web 客户端可选
magnusdocker.io/jumpserver/magnus数据库客户端代理(端口 5525)可选
razorregistry.fit2cloud.com/jumpserver/razorRDP 原生客户端代理可选
necregistry.fit2cloud.com/jumpserver/necVNC 原生客户端代理可选
videoregistry.fit2cloud.com/jumpserver/video会话录像处理可选
faceliveregistry.fit2cloud.com/jumpserver/facelive人脸识别(企业合规)建议关闭
xrdpregistry.fit2cloud.com/jumpserver/xrdpRDP 应用发布默认关闭

⚠️ 注意:Chart 模板里 global.imageRegistry 会覆盖所有组件的镜像仓库, 而 razor/nec/video/facelive/xrdp 的镜像在 registry.fit2cloud.com 上 (docker.io 没有)。因此不能只设一个全局镜像仓库,必须按组件分别指定 image.registry(见 4.2 节 values),或者把用不到的组件 enabled: false

对外端口(官方网络端口说明,v4.10.x):

端口作用
80/443Web 控制台(本方案以 NodePort 30080 映射)
2222SSH 客户端接入(koko,本方案 NodePort 32222)
5525Magnus 数据库代理(v4.10.19 起统一;本场景用不到)
15900NEC(VNC,可选)

3. 前置准备

3.1 Harbor 新增代理缓存项目

沿用现网 kubevirt 项目(代理 quay.io/kubevirt)的做法,在 192.168.122.156:30000 上新增两个代理缓存项目:

项目名        类型          上游
dockerhub    Proxy Cache   docker.io(不填子路径)
fit2cloud    Proxy Cache   registry.fit2cloud.com/jumpserver

镜像映射(首次 helm install 时 containerd 经 Harbor 自动拉取缓存):

原始镜像                                              → Harbor 代理
docker.io/jumpserver/core:v4.10.19-ce                → 192.168.122.156:30000/dockerhub/jumpserver/core:v4.10.19-ce
docker.io/jumpserver/koko:v4.10.19-ce                → .../dockerhub/jumpserver/koko:v4.10.19-ce
docker.io/jumpserver/web:v4.10.19-ce                 → .../dockerhub/jumpserver/web:v4.10.19-ce
docker.io/jumpserver/{lion,chen,magnus}:v4.10.19-ce  → .../dockerhub/jumpserver/<组件>:v4.10.19-ce
registry.fit2cloud.com/jumpserver/{razor,nec,video}:v4.10.19-ce
                                                     → .../fit2cloud/<组件>:v4.10.19-ce
docker.io/library/postgres:16                        → 192.168.122.156:30000/dockerhub/postgres:16
docker.io/library/redis:7                            → 192.168.122.156:30000/dockerhub/redis:7

说明:Docker Hub 官方镜像(postgres/redis)在 Harbor 代理缓存中直接用 <项目名>/<镜像名> 引用,Harbor 会自动解析 library/ 前缀。 若确认不需要 Windows RDP / VNC / 录像处理,可将 razor/nec/video/facelive/xrdp 全部关闭,则 fit2cloud 项目可不建。

3.2 获取 Helm Chart

方式 A(节点可访问 github.io)

bash
helm repo add jumpserver https://jumpserver.github.io/helm-charts
helm repo update
helm search repo jumpserver/jumpserver --versions | head -5

方式 B(离线:chart 包托管在 GitHub Releases)

bash
# 在任一台可上外网的机器下载后传入集群入口节点
curl -L -o jumpserver-v4.10.19.tgz \
  https://github.com/jumpserver/helm-charts/releases/download/jumpserver-v4.10.19/jumpserver-v4.10.19.tgz
scp jumpserver-v4.10.19.tgz root@192.168.122.31:/root/
# 后续 helm install 直接引用本地文件 /root/jumpserver-v4.10.19.tgz

3.3 部署 PostgreSQL 与 Redis(Chart 外部依赖)

创建命名空间与凭据(密码务必替换,后文统一引用这两个 Secret):

bash
kubectl create namespace jumpserver

kubectl -n jumpserver create secret generic jms-postgresql \
  --from-literal=POSTGRES_USER=jumpserver \
  --from-literal=POSTGRES_PASSWORD='xxx' \
  --from-literal=POSTGRES_DB=jumpserver

kubectl -n jumpserver create secret generic jms-redis \
  --from-literal=REDIS_PASSWORD='xxx'

PostgreSQL 16(单实例 + vm-nas 持久化):

yaml
# jms-postgresql.yaml
apiVersion: v1
kind: Service
metadata:
  name: jms-postgresql
  namespace: jumpserver
spec:
  selector: { app: jms-postgresql }
  ports: [{ port: 5432, targetPort: 5432 }]
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: jms-postgresql
  namespace: jumpserver
spec:
  serviceName: jms-postgresql
  replicas: 1
  selector:
    matchLabels: { app: jms-postgresql }
  template:
    metadata:
      labels: { app: jms-postgresql }
    spec:
      containers:
      - name: postgres
        image: 192.168.122.156:30000/dockerhub/postgres:16
        args: ["-c", "max_connections=1000"]   # JumpServer 连接数需求较高
        envFrom:
        - secretRef: { name: jms-postgresql }
        ports: [{ containerPort: 5432 }]
        volumeMounts:
        - { name: data, mountPath: /var/lib/postgresql/data }
        resources:
          requests: { cpu: 200m, memory: 512Mi }
          limits:   { cpu: "2",  memory: 2Gi }
      volumes:
      - name: data
        persistentVolumeClaim:
          claimName: jms-postgresql-data
  volumeClaimTemplates:
  - metadata: { name: data }
    spec:
      storageClassName: vm-nas     # 单实例,RWO 即可
      accessModes: [ReadWriteOnce]
      resources: { requests: { storage: 50Gi } }

Redis 7:

yaml
# jms-redis.yaml
apiVersion: v1
kind: Service
metadata:
  name: jms-redis
  namespace: jumpserver
spec:
  selector: { app: jms-redis }
  ports: [{ port: 6379, targetPort: 6379 }]
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: jms-redis
  namespace: jumpserver
spec:
  serviceName: jms-redis
  replicas: 1
  selector:
    matchLabels: { app: jms-redis }
  template:
    metadata:
      labels: { app: jms-redis }
    spec:
      containers:
      - name: redis
        image: 192.168.122.156:30000/dockerhub/redis:7
        command: ["/bin/sh", "-c", "exec redis-server --requirepass \"$REDIS_PASSWORD\" --appendonly yes"]
        envFrom:
        - secretRef: { name: jms-redis }
        ports: [{ containerPort: 6379 }]
        volumeMounts:
        - { name: data, mountPath: /data }
        resources:
          requests: { cpu: 100m, memory: 256Mi }
          limits:   { cpu: "1",  memory: 1Gi }
      volumes:
      - name: data
        persistentVolumeClaim:
          claimName: jms-redis-data
  volumeClaimTemplates:
  - metadata: { name: data }
    spec:
      storageClassName: vm-nas
      accessModes: [ReadWriteOnce]
      resources: { requests: { storage: 10Gi } }
bash
kubectl apply -f jms-postgresql.yaml -f jms-redis.yaml
kubectl -n jumpserver rollout status statefulset/jms-postgresql --timeout=300s
kubectl -n jumpserver rollout status statefulset/jms-redis --timeout=300s

4. 部署 JumpServer

4.1 生成密钥

bash
SECRET_KEY=$(cat /dev/urandom | tr -dc A-Za-z0-9 | head -c 50)
BOOTSTRAP_TOKEN=$(cat /dev/urandom | tr -dc A-Za-z0-9 | head -c 24)
echo "SECRET_KEY=$SECRET_KEY"; echo "BOOTSTRAP_TOKEN=$BOOTSTRAP_TOKEN"

4.2 准备 values 文件

values-jumpserver.yaml按组件分别指定镜像仓库,原因见第 2 章注意事项):

yaml
# values-jumpserver.yaml
global:
  imageOwner: jumpserver          # 镜像组织名
  storageClass: vm-nas            # 全局覆盖各组件 PVC 的 StorageClass
  imagePullSecrets: []            # Harbor 代理缓存项目为公开读,无需凭据

ingress:
  enabled: false                  # 现网无 Ingress 控制器,统一走 NodePort

externalDatabase:
  engine: postgresql
  host: jms-postgresql.jumpserver.svc.cluster.local
  port: 5432
  user: jumpserver
  password: "xxx"           # 与 jms-postgresql Secret 保持一致
  database: jumpserver

externalRedis:
  host: jms-redis.jumpserver.svc.cluster.local
  port: 6379
  password: "xxx"        # 与 jms-redis Secret 保持一致

core:
  config:
    secretKey: "<4.1 生成的 SECRET_KEY>"
    bootstrapToken: "<4.1 生成的 BOOTSTRAP_TOKEN>"
  env:
    # CSRF 信任来源:NodePort 访问入口(换节点访问时需同步追加)
    DOMAINS: "192.168.122.31:30080"
  image:
    registry: 192.168.122.156:30000/dockerhub
  persistence:
    size: 50Gi
  resources:
    requests: { cpu: 500m, memory: 1Gi }
    limits:   { cpu: "4",  memory: 4Gi }

koko:
  image:
    registry: 192.168.122.156:30000/dockerhub
  service:
    type: NodePort                # 模板取 nodePort = ssh.port
    web: { port: 5000 }
    ssh: { port: 32222 }          # SSH 客户端接入端口(30000-32767 范围内)
  persistence:
    size: 10Gi
  resources:
    requests: { cpu: 200m, memory: 256Mi }
    limits:   { cpu: "2",  memory: 2Gi }

web:
  image:
    registry: 192.168.122.156:30000/dockerhub
  service:
    type: NodePort                # 模板取 nodePort = web.port
    web: { port: 30080 }          # Web 控制台入口
  persistence:
    size: 1Gi

# ---- 可选组件:不需要的直接 enabled: false ----
lion:                              # RDP/VNC Web 连接(Linux VM 用不上可关)
  enabled: true
  image: { registry: 192.168.122.156:30000/dockerhub }
chen:                              # 数据库 Web 客户端
  enabled: false
magnus:                            # 数据库客户端代理
  enabled: false
razor:                             # 以下组件镜像在 fit2cloud 仓库
  enabled: false
nec:
  enabled: false
video:
  enabled: false                   # 需要会话录像处理时开启(需 fit2cloud 代理项目)
facelive:
  enabled: false
xrdp:
  enabled: false

说明:

  • web/koko 服务模板在 type: NodePort 时以端口值作为 nodePort,所以 端口必须直接落在 30000-32767 区间;
  • koko 默认 securityContext.privileged: true(官方 chart 默认), 见第 8 章安全说明;
  • 需要会话录像回放时把 video.enabled 改为 true,并确保 3.1 的 fit2cloud 代理项目已建好。

4.3 安装与验证

bash
# 离线包方式
helm -n jumpserver install jumpserver /root/jumpserver-v4.10.19.tgz \
  -f values-jumpserver.yaml
# 或在线仓库方式
# helm -n jumpserver install jumpserver jumpserver/jumpserver \
#   --version v4.10.19 -f values-jumpserver.yaml

# 观察 Pod 启动(core 首次初始化数据库需 2~5 分钟)
kubectl -n jumpserver get pods -w
kubectl -n jumpserver logs deploy/jumpserver-jms-core -f   # 首次建表/迁移日志

验证:

bash
# 1. 组件健康(入口节点执行)
curl -s http://192.168.122.31:30080/api/health/

# 2. koko 到 VM 固定 IP 的连通性(部署后最关键的一步)
kubectl -n jumpserver exec deploy/jumpserver-jms-koko -- \
  python3 -c "import socket; socket.create_connection(('192.168.122.102',22),5); print('OK')"

# 3. 首次登录
#    浏览器: http://192.168.122.31:30080
#    默认账号: xxx / ChangeMe(登录后立即改密)

5. 把 KubeVirt 虚拟机接入 JumpServer

5.1 创建主机资产

控制台 → 资产管理 → 资产列表 → 创建 → 主机

字段说明
资产名称rocky9-vm2与 VM 名保持一致便于对应
地址192.168.122.102VM 的 Multus 固定 IP(不是 Pod IP)
平台Linux
协议ssh / 22
节点Default可按业务建节点树

账号页签添加特权账号:xxx root、密码即 VM 登录密码 (来自 ../kubevirt-network-ssh-password-guide.md 的配置)。 保存后点 测试——显示可连通即代表 koko → VM:22 链路正常。

💡 建议:把各 VM 密码改为互不相同的强密码并交由 JumpServer 托管, 配合 账号管理 → 账号改密 定期轮换;这样即使某台 VM 泄露也不横向扩散。

5.2 批量接入

多台 VM 时不必逐台手填:资产列表 → 导入 下载 CSV 模板, 每行一台(名称、地址、平台、协议、账号、密码),一次导入即可。 新增 VM 的固定 IP 规划建议与网络指南保持一致(如 192.168.122.110+ 递增)。

5.3 用户与授权

  1. 控制台 → 用户管理 → 用户列表:为每位运维人员创建账号 (建议开启 MFA:个人设置/安全设置);
  2. 授权管理 → 资产授权:创建规则,把用户(或用户组)与 资产(或节点)绑定,并指定可用的系统账号(如 root);
  3. 需要审批的场景可在 访问控制 → 命令过滤/命令组 中配置 高危命令拦截(如 rm -rf /shutdown)。

6. 日常使用方式

方式操作适用
Web 控制台浏览器打开 http://192.168.122.31:30080 → 我的资产 → 连接 → Web CLI最常用,无需装客户端
SSH 客户端ssh -p 32222 <jms用户名>@192.168.122.31,交互菜单中选择资产Xshell/PuTTY/MobaXterm
SFTP 文件传输sftp -P 32222 <jms用户名>@192.168.122.31,或 Web 终端内上传/下载向 VM 传文件
审计回放审计台 → 会话审计 → 会话记录(录像/命令)事后追溯

会话录像存储在 jumpserver-jms-core-data PVC(vm-nas NFS)上, 随 NAS 备份策略一同保护(见第 7 章)。


7. 运维:备份、升级、排障

7.1 备份

bash
# PostgreSQL(核心数据,务必定期)
kubectl -n jumpserver exec statefulset/jms-postgresql -- \
  pg_dump -U jumpserver jumpserver | gzip > jms-db-$(date +%F).sql.gz

# PVC 数据(录像/上传文件):对 NAS 侧目录做快照/同步
#   jms-postgresql-data / jms-core-data / jms-koko-data ...

7.2 升级

bash
helm repo update    # 在线仓库方式时
helm -n jumpserver upgrade jumpserver jumpserver/jumpserver \
  --version <新版> -f values-jumpserver.yaml

⚠️ 官方安全通告基线:v4 ≥ v4.10.17(fastjson RCE、koko SFTP 路径遍历等 漏洞已在此版本修复)。升级前先在测试环境验证,并备份数据库。

7.3 常见问题

现象原因与处理
Pod ImagePullBackOffHarbor 代理项目未建/名字不对:核对 dockerhubfit2cloud 项目与 3.1 的映射;crictl pull 手动验证
core 反复 CrashLoopBackOff数据库连不上:核对 externalDatabase 密码与 Secret;max_connections 是否 1000;查 kubectl -n jumpserver logs deploy/jumpserver-jms-core
资产测试失败(koko 连不上 VM)① VM sshd 是否监听 ② 1.2 的连通性验证是否通过(natOutgoing)③ 中间防火墙;在 koko Pod 内 python3 -c "import socket; socket.create_connection(('IP',22),5)" 定位
登录页 403 / CSRF 报错core.env.DOMAINS 未包含实际访问的 节点IP:30080,补充后 helm upgrade
PVC Pendingvm-nas provisioner 是否正常:kubectl get pods -A | grep nfs-subdir
Web 终端空白/转圈koko 与 core 之间令牌不一致(改过 bootstrapToken 需全组件一致重装/升级);查 deploy/jumpserver-jms-koko 日志
NodePort 无法访问确认使用入口节点 192.168.122.31;任一节点 30080/32222 理论可达,但 DOMAINS 只信任已登记入口

8. 安全注意事项

  1. 默认口令admin/ChangeMe 首次登录必须修改,并为所有用户启用 MFA;
  2. 入口收敛:JumpServer NodePort 只允许运维网段访问,例如在节点上: firewall-cmd --add-rich-rule='rule family="ipv4" source address="运维网段/24" port port="30080" protocol="tcp" accept' --permanent; 其余来源由 RKE2 网络策略或防火墙拒绝;
  3. koko 特权容器:官方 chart 默认 privileged: true,属已知设计; 应将 jumpserver 命名空间与业务命名空间隔离,RBAC 最小授权;
  4. 版本基线:保持 ≥ v4.10.17 LTS 并及时跟进官方安全通告;
  5. 审计留痕:开启命令记录与会话录像,录像保留策略结合合规要求设置;
  6. 密码托管:VM root 密码尽量交由 JumpServer 托管并定期改密, 避免多人共用同一密码直连(绕过堡垒机直连属于违规行为,可用网络策略 限制只有 jumpserver 命名空间可访问 VM 的 22 端口来强制收口—— 注意物理网段资产不受 K8s NetworkPolicy 管辖,需配合交换机/防火墙策略)。

9. 入口变体:固定 ClusterIP 与固定物理网段 IP(测试场景)

第 4 章的 NodePort 之外,还有两种"固定入口"玩法,适合集群安装验收、 内部互访等测试场景。两者对比:

方案 A:固定 ClusterIP方案 B:组件钉节点 + 辅助网络固定物理 IP
固定的是Service VIP(如 10.43.100.10),永不变web/koko Pod 的第二网卡物理网段 IP
谁能直达集群内 Pod + 所有节点本机(kube-proxy)物理网段任何机器 + VM
物理机/外部不可达,仍需 NodePort 兜底✅ 直达,可完全替代 NodePort
放弃什么放弃 HA:组件钉死单节点,该节点故障即中断
适用服务间互访、集群安装冒烟测试测试环境要"像传统堡垒机一样有固定物理 IP"

⚠️ 先纠正一个常见思路:"放弃热迁移、把 masquerade 主网络改成 bridge 来固定 Pod IP"是走不通的——Pod IP 由 CNI 分配,重建即变,与绑定方式无关。 要固定入口只能靠:Service VIP(方案 A)辅助网络 + 固定分配(方案 B)

9.1 方案 A:追加固定 ClusterIP Service

Chart 模板不支持自定义 clusterIP,直接手写一个与 jms-web Pod 标签匹配的 Service(与 Helm 托管对象共存,helm upgrade 不受影响):

yaml
# jms-web-vip.yaml
apiVersion: v1
kind: Service
metadata:
  name: jms-web-vip
  namespace: jumpserver
spec:
  type: ClusterIP
  clusterIP: 10.43.100.10      # 在 10.43.0.0/16 内选一个未占用地址
  sessionAffinity: ClientIP
  selector:                     # 与 chart 的 web Service selector 一致
    app.kubernetes.io/name: jumpserver
    app.kubernetes.io/instance: jumpserver
    app.jumpserver.org/name: jms-web
  ports:
  - { name: web, port: 80, targetPort: web }
---
apiVersion: v1
kind: Service
metadata:
  name: jms-koko-vip
  namespace: jumpserver
spec:
  type: ClusterIP
  clusterIP: 10.43.100.11
  sessionAffinity: ClientIP
  selector:
    app.kubernetes.io/name: jumpserver
    app.kubernetes.io/instance: jumpserver
    app.jumpserver.org/name: jms-koko
  ports:
  - { name: ssh, port: 2222, targetPort: ssh }
bash
kubectl get svc -A | grep 10.43.100 || true   # 先确认地址没被占用
kubectl apply -f jms-web-vip.yaml

可达性矩阵(现网:service-cidr 10.43.0.0/16,kube-proxy 每节点运行):

来源curl http://10.43.100.10/api/health/
集群内 Pod(含 virt-launcher)
任一 RKE2 节点本机✅(可直接作为集群安装冒烟测试项)
只有 masquerade 网络的 VM✅(借道所在节点转发)
物理网段直连的 PC / 二次网络 VM❌ ClusterIP 不出集群 → 用 NodePort

集群安装验收脚本可直接引用:

bash
# 在任一节点执行:JumpServer 作为集群应用部署的验收项
curl -sf http://10.43.100.10/api/health/ && echo "jms OK"
ssh -p 2222 -o ConnectTimeout=5 admin@10.43.100.11 exit 2>/dev/null; echo "koko tcp=$?"

9.2 方案 B:钉节点 + 辅助网络固定物理 IP(替代 NodePort)

前提:所选节点上已建好 br-vm 网桥(与虚拟机同桥,见网络指南)。

① NAD——用 host-local 且把地址范围夹到只剩一个地址,保证永远分到 同一个 IP(host-local 按节点本地记账,配合钉节点即"重建不变"):

yaml
# jms-physnet-nad.yaml
apiVersion: k8s.cni.cncf.io/v1
kind: NetworkAttachmentDefinition
metadata:
  name: jms-physnet
  namespace: jumpserver
spec:
  config: '{
    "cniVersion": "0.3.1",
    "name": "jms-physnet",
    "type": "bridge",
    "bridge": "br-vm",
    "vlan": 0,
    "ipam": {
      "type": "host-local",
      "subnet": "192.168.122.144/28",
      "rangeStart": "192.168.122.150",
      "rangeEnd": "192.168.122.150"
    }
  }'

② values 调整(相对第 4 章基础 values 的增量)——注意 web 容器监听端口 等于 web.service.web.port,物理直达入口要走 80 端口就得把它改回 80:

yaml
web:
  image:
    registry: 192.168.122.156:30000/dockerhub
  nodeSelector:                          # ★ 钉到建了 br-vm 的节点
    kubernetes.io/hostname: <节点名>
  service:
    type: ClusterIP                      # 物理入口改由第二网卡承载
    web: { port: 80 }
koko:
  nodeSelector:
    kubernetes.io/hostname: <节点名>     # SSH 入口 2222 也走第二网卡

③ 注入 Multus 注解(chart 不支持 podAnnotations,安装后用 patch; 每次 helm upgrade 后需重新执行):

bash
kubectl apply -f jms-physnet-nad.yaml

kubectl -n jumpserver patch deployment jumpserver-jms-web --type=json -p '[
  {"op":"add","path":"/spec/template/metadata/annotations",
   "value":{"k8s.v1.cni.cncf.io/networks":"jms-physnet"}}]'
kubectl -n jumpserver patch deployment jumpserver-jms-koko --type=json -p '[
  {"op":"add","path":"/spec/template/metadata/annotations",
   "value":{"k8s.v1.cni.cncf.io/networks":"jms-physnet"}}]'

kubectl -n jumpserver rollout status deploy/jumpserver-jms-web

④ 验证

bash
kubectl -n jumpserver exec deploy/jumpserver-jms-web -- ip -4 addr show
#   应看到 eth1: 192.168.122.150/28
# 物理机上:
curl -s http://192.168.122.150/api/health/     # Web 入口(:80)
ssh -p 2222 admin@192.168.122.150              # SSH 客户端入口

提示:方案 B 下 VM 资产测试方向不变(koko → VM:22);反过来物理机/其他 VM 也能以固定地址 192.168.122.150 直达堡垒机——这就是传统堡垒机的 网络形态。节点 <节点名>kubectl get node 查询。


附录:部署检查清单

  • [ ] Harbor dockerhub(+ 按需 fit2cloud)代理缓存项目已建
  • [ ] Helm chart v4.10.19 包已就位(在线仓库或离线 tgz)
  • [ ] jumpserver 命名空间、PG/Redis Secret 已创建
  • [ ] PostgreSQL/Redis StatefulSet Running,PVC Bound(vm-nas)
  • [ ] SECRET_KEY / BOOTSTRAP_TOKEN 已生成并写入 values
  • [ ] helm install 后全部 Pod Ready,/api/health/ 返回正常
  • [ ] koko → VM:22 连通性验证通过
  • [ ] 资产创建 + 账号测试通过,授权规则配置完成
  • [ ] admin 默认密码已修改,MFA 已启用
  • [ ] 备份任务(pg_dump + NAS 快照)已纳入例行运维