在AI模型开发和部署过程中,为团队提供一个共享但隔离的环境是基础设施工程师面临的核心挑战。JupyterHub是理想的解决方案,但要实现多租户的安全隔离和资源公平分配,必须依赖强大的容器编排工具——Kubernetes (K8s)。本文将深入探讨如何结合使用JupyterHub的KubeSpawner实现安全隔离。
Contents
1. 理解隔离的必要性
多租户AI平台中的隔离主要解决三个核心问题:
- 资源公平性 (Fairness): 确保一个用户的任务(如大规模模型训练)不会耗尽集群资源,影响其他用户。通过设置资源限制(Limits)和请求(Requests)实现。
- 数据与安全隔离 (Security): 确保用户A无法访问用户B的数据、进程或配置。通过Kubernetes的命名空间(Namespace)、持久卷(PVC)和RBAC策略实现。
- 环境一致性 (Consistency): 允许每个用户使用定制的、预配置的、且互不干扰的开发环境(如Python版本、CUDA库)。通过定制Docker镜像实现。
我们主要使用 Zero to JupyterHub (Z2JH) Helm Chart,它内置了KubeSpawner。
2. 核心机制:KubeSpawner与用户Pod
当用户通过JupyterHub登录并启动Notebook时,KubeSpawner会执行以下操作:
- 为该用户动态创建一个新的Kubernetes Pod。
- 该Pod在一个独立的命名空间(默认为Hub所在的命名空间,但具有隔离的用户ID/进程)。
- Pod配置了特定的资源限制、存储卷(PVC)和安全上下文(Security Context)。
以下是配置隔离的关键步骤和代码示例。
3. 实现资源限制和存储隔离
我们通过修改JupyterHub的config.yaml文件来配置KubeSpawner的行为,从而实现资源限制和存储隔离。
步骤 3.1: 配置资源限制 (CPU/GPU/Memory)
资源限制是最基本的隔离手段。以下配置强制要求用户Pod的CPU请求1核,最大限制为2核,内存请求2G,最大限制为4G。同时,如果需要GPU,我们定义了一个GPU Profile。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 hub:
# 使用默认的KubeSpawner
config:
KubeSpawner:
# 定义默认的资源请求和限制
cpu_limit: 2.0
cpu_guarantee: 1.0
mem_limit: 4G
mem_guarantee: 2G
# 定义用户可以选择的预设配置Profiles
profiles:
- display_name: "Standard CPU Development"
description: "1-2 CPU Cores, 4GB Memory"
kubespawner_override:
cpu_limit: 2.0
mem_limit: 4G
- display_name: "High-Performance GPU Training"
description: "4 CPU Cores, 16GB Memory, 1x NVIDIA T4"
kubespawner_override:
cpu_limit: 4.0
mem_limit: 16G
# 关键:限制GPU资源
extra_resource_limits:
nvidia.com/gpu: 1
步骤 3.2: 实现持久卷隔离 (PVC per User)
每个用户需要一个独立的、只有他们自己能访问的存储空间来保存代码和模型。Z2JH默认利用storage配置来实现这一点,它会为每个用户动态创建并挂载一个PersistentVolumeClaim (PVC)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 # 存储配置确保每个用户拥有独立的存储卷
storage:
type: dynamic
# 存储类,请根据您的K8s集群配置修改
storageClass: standard
capacity: 10Gi # 每个用户的存储容量
# 定义挂载点
homeMountPath: /home/jovyan/work
# 确保卷的所有权和权限正确设置(重要安全步骤)
extraVolumes:
- name: user-data
persistentVolumeClaim:
claimName: "jupyterhub-user-{username}"
extraVolumeMounts:
- name: user-data
mountPath: /home/jovyan/data
# 确保用户是唯一的拥有者
subPath: "{username}"
4. 增强隔离:网络策略和安全上下文
仅仅通过资源限制和存储卷不足以实现企业级的安全隔离。我们还需要利用Kubernetes的原生安全特性。
步骤 4.1: 配置Pod安全上下文
为了防止容器内的进程获得过高的权限(例如root权限),我们应当配置安全上下文 (Security Context)。
1
2
3
4
5
6
7
8
9
10
11 hub:
config:
KubeSpawner:
# 强制用户使用非root用户ID运行,提高隔离性
uid: 1000
fs_gid: 100
security_context:
allowPrivilegeEscalation: false
runAsUser: 1000
# 阻止容器以root用户身份运行
runAsNonRoot: true
步骤 4.2: 部署网络策略 (Network Policy)
网络策略是确保租户之间网络隔离的最强大工具。它定义了哪些Pod可以互相通信。例如,我们可以定义一个策略,禁止任何用户Pod访问其他用户Pod,只允许访问Hub、代理和外部资源。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37 # 示例:禁止默认命名空间内的Pod互相通信(需要K8s网络插件支持,如Calico)
# 注意:此策略不是通过JupyterHub配置,而是单独的Kubernetes资源。
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: user-pod-isolation-policy
namespace: jupyterhub-namespace # 替换为您的Hub所在命名空间
spec:
podSelector:
matchLabels:
app: jupyterhub
component: singleuser-server
policyTypes:
- Egress
- Ingress
ingress:
- from:
# 允许来自Hub/Proxy的连接
- podSelector:
matchLabels:
app: jupyterhub
component: network-proxy
- podSelector:
matchLabels:
app: jupyterhub
component: hub
egress:
# 允许所有出站流量到外部网络,但禁止到其他用户Pod
- to:
- ipBlock:
# 允许所有外部IP
cidr: 0.0.0.0/0
except:
# 排除K8s集群内部的私有IP范围,限制集群内部的横向通信
- 10.0.0.0/8
- 172.16.0.0/12
- 192.168.0.0/16
通过结合资源限制、独立的持久存储和严格的网络策略,您可以构建一个既安全又公平的多租户AI开发基础设施。
汤不热吧