- 人工智能
- 大模型
- MLOps
- LLMOps
- 模型推理服务
- 云原生
- 后端
【免费下载链接】seldon-core
An MLOps framework to package, deploy, monitor and manage thousands of production machine learning models
本文基于 Seldon Core V2 的多命名空间(Multi-Namespace)部署示例,完整演示如何将控制平面集中安装在seldon-mesh命名空间、通过controller.clusterwide=true开启跨命名空间调谐,并配合kafka.topicPrefix/kafka.consumerGroupIdPrefix在共享 Kafka 集群中实现主题与消费组的租户级隔离。读完本文,你可以独立在 Kubernetes 上搭建“一套控制平面 + 多个业务命名空间运行时”的 Seldon V2 集群,并通过 REST/gRPC 对模型与 Pipeline 发起推理、验证 Kafka 隔离效果,最后按顺序完成拆除。
部署拓扑与前置条件
该示例的拓扑分为两层:
- 控制平面(集中式):CRD、调度器(scheduler)、operator(controller)安装在
seldon-mesh命名空间,负责监听并调谐集群中各业务命名空间的Model、Pipeline等 Seldon 自定义资源; - 数据面运行时(分布式):在每个业务命名空间(示例中的
ns1、ns2)分别安装 runtime(envoy、dataflow、modelgateway、pipelinegateway、hodometer 等)与模型服务器(mlserver、triton)。
前置条件包括:
- 一个可用的 Kubernetes 集群,且节点具备 LoadBalancer 能力(示例通过
kubectl get svc ... -o jsonpath='{.status.loadBalancer.ingress[0].ip}'获取入口 IP); - 一个 Kafka 集群。若通过仓库中的 Ansible playbook setup-ecosystem.yaml 安装,Kafka 会部署在
seldon-mesh命名空间(Pod 名为seldon-kafka-0),默认 bootstrap 地址即 Helm values 中的seldon-kafka-bootstrap.seldon-mesh:9092(见 values.yaml 中kafka.bootstrap); - 示例中的模型来自公共对象存储
gs://seldon-models/...,需要能够访问该存储(runtime 侧默认通过 rclone 拉取,参考 runtime 模板中的 seldon-rclone-gs-public)。
说明:文中所有
helm命令的 chart 路径(如k8s/helm-charts/...)均指仓库根目录下的相对路径。
安装 CRD 与控制平面
1. 安装 CRD
helm upgrade --install seldon-core-v2-crds k8s/helm-charts/seldon-core-v2-crds -n seldon-mesh输出示例:
Release "seldon-core-v2-crds" does not exist. Installing it now. NAME: seldon-core-v2-crds LAST DEPLOYED: Tue Aug 15 11:01:03 2023 NAMESPACE: seldon-mesh STATUS: deployed REVISION: 1 TEST SUITE: None2. 安装控制平面(seldon-v2 setup chart)
helm upgrade --install seldon-v2 k8s/helm-charts/seldon-core-v2-setup/ -n seldon-mesh \ --set controller.clusterwide=true \ --set kafka.topicPrefix=myorg \ --set kafka.consumerGroupIdPrefix=myorg输出示例:
Release "seldon-v2" does not exist. Installing it now. NAME: seldon-v2 LAST DEPLOYED: Tue Aug 15 11:01:07 2023 NAMESPACE: seldon-mesh STATUS: deployed REVISION: 1 TEST SUITE: None这条 setup 命令的三个--set参数是跨命名空间部署的核心,它们在 seldon-core-v2-setup/values.yaml 中均有对应默认值:
| 参数 | 默认值 | 作用 |
|---|---|---|
controller.clusterwide | false | 开启后 operator 调谐所有命名空间的 Seldon CR,并创建集群级 RBAC(ClusterRole + ClusterRoleBinding) |
kafka.topicPrefix | seldon | 所有 Kafka 主题名的全局前缀,用于共享 Kafka 集群时的命名空间 |
kafka.consumerGroupIdPrefix | 空 | 所有 Kafka consumer group 的前缀,用于隔离不同组织/环境的消费位点 |
文档中特别说明:即便本示例里 Kafka 是专门给 Seldon 安装的、并非真正“共享”,也演示了这套基于前缀的隔离配置——当 Kafka 集群与其他应用共用、需要强制命名约束时,这两项就是必需的。
clusterwide在 operator 侧的实际落地可以在源码中确认:operator/main.go 中定义了命令行参数
flag.StringVar(&watchNamespaces, "watch-namespaces", "", "Comma separated list of namespaces to watch. "+ "Only used when --clusterwide is set to true. "+ "Defaults to all namespaces if not set and --clusterwide is true.") flag.BoolVar(&clusterwide, "clusterwide", false, "Allow clusterwide operations")而 getWatchNamespaceConfig 决定了缓存(调谐范围)行为:未开启clusterwide时只 watch 自身所在命名空间;开启且watchNamespaces为空时 watch 全部命名空间;开启并传入逗号分隔的命名空间列表时,仅 watch 这些命名空间(外加 operator 自身命名空间)。Helm 模板 _components-statefulsets.tpl 将controller.clusterwide注入为容器环境变量CLUSTERWIDE,并以--clusterwide=$(CLUSTERWIDE)、--watch-namespaces=$(WATCH_NAMESPACES)启动 manager。
RBAC 层面,同一模板在clusterwide(或watchNamespaces)为真时额外渲染集群级角色:seldon-v2-manager-role ClusterRole 覆盖 configmaps、secrets、serviceaccounts、services、deployments/statefulsets 以及mlops.seldon.io全部资源组,并生成 ClusterRoleBinding 把seldon-v2-controller-managerServiceAccount 绑定到集群角色上——这正是 operator 能够跨命名空间管理ns1/ns2的权限来源。
在业务命名空间部署 runtime 与模型服务器
1. 创建业务命名空间
kubectl create namespace ns1 kubectl create namespace ns2namespace/ns1 created namespace/ns2 created2. 在每个命名空间安装 runtime 与 servers
ns1与ns2的安装步骤完全一致:
# ns1 helm install seldon-v2-runtime k8s/helm-charts/seldon-core-v2-runtime -n ns1 --wait helm install seldon-v2-servers k8s/helm-charts/seldon-core-v2-servers -n ns1 --wait # ns2 helm install seldon-v2-runtime k8s/helm-charts/seldon-core-v2-runtime -n ns2 --wait helm install seldon-v2-servers k8s/helm-charts/seldon-core-v2-servers -n ns2 --wait成功时每个 release 的输出形如:
NAME: seldon-v2-runtime LAST DEPLOYED: Tue Aug 15 11:01:11 2023 NAMESPACE: ns1 STATUS: deployed REVISION: 1 TEST SUITE: None这两个 chart 分别负责什么?
- seldon-core-v2-runtime 渲染一个
SeldonRuntime自定义资源(名为seldon),声明该命名空间中的数据面组件:hodometer、seldon-scheduler、seldon-envoy、seldon-dataflow-engine、seldon-modelgateway、seldon-pipelinegateway,并通过config.kafkaConfig.topicPrefix等字段下发 Kafka 配置。其 values.yaml 中scheduler.serviceType与envoy.serviceType默认为LoadBalancer,这正是后续各命名空间拥有独立入口 IP 的原因; - seldon-core-v2-servers 声明该命名空间可用的模型服务器,默认包含
mlserver与triton(各 1 副本),服务器镜像与能力项(serverCapabilities)来自 setup chart 的serverConfig段。
3. 等待服务器就绪
kubectl wait --for condition=ready --timeout=300s server --all -n ns1 kubectl wait --for condition=ready --timeout=300s server --all -n ns2server.mlops.seldon.io/mlserver condition met server.mlops.seldon.io/triton condition met4. 获取各命名空间的 mesh 入口 IP
每个命名空间的 envoy 暴露为独立的 LoadBalancer 服务seldon-mesh,需要分别获取 IP:
MESH_IP=!kubectl get svc seldon-mesh -n ns1 -o jsonpath='{.status.loadBalancer.ingress[0].ip}' MESH_IP_NS1=MESH_IP[0] import os os.environ['MESH_IP_NS1'] = MESH_IP_NS1 MESH_IP_NS1'172.18.255.2'MESH_IP=!kubectl get svc seldon-mesh -n ns2 -o jsonpath='{.status.loadBalancer.ingress[0].ip}' MESH_IP_NS2=MESH_IP[0] import os os.environ['MESH_IP_NS2'] = MESH_IP_NS2 MESH_IP_NS2'172.18.255.4'(具体 IP 值因集群而异,以实际输出为准。)
在不同命名空间运行模型
提示:Seldon CLI 允许你在非 Kubernetes 环境中查看底层 Seldon 资源信息并通过 scheduler 对其做变更;但在 Kubernetes 集群中它无法修改底层 manifest,因此在 Kubernetes 环境里不推荐用 CLI 做控制面操作,应直接
kubectl操作 CR。详见 Seldon CLI。
1. 在 ns1 创建 iris 模型
模型清单为 samples/models/sklearn-iris-gs.yaml:
apiVersion: mlops.seldon.io/v1alpha1 kind: Model metadata: name: iris spec: storageUri: "gs://seldon-models/scv2/samples/mlserver_1.3.5/iris-sklearn" requirements: - sklearn memory: 100KistorageUri指向模型对象存储地址,requirements中的sklearn决定 operator 将其调度到具备该 capability 的 mlserver。
kubectl create -f samples/models/sklearn-iris-gs.yaml -n ns1model.mlops.seldon.io/iris createdkubectl wait --for condition=ready --timeout=300s model --all -n ns1model.mlops.seldon.io/iris condition met2. 对 ns1 中的模型发起推理
REST(JSON)方式:
curl -k http://${MESH_IP_NS1}:80/v2/models/iris/infer \ -H "Host: seldon-mesh.inference.seldon" \ -H "Seldon-Model: iris" \ -H "Content-Type: application/json" \ -d '{ "inputs": [ { "name": "predict", "datatype": "FP32", "shape": [1,4], "data": [[1,2,3,4]] } ] }' | jq -M .gRPC(JSON over gRPC)方式:
curl -k http://${MESH_IP_NS1}:80/v2/models/iris/infer \ -H "Host: seldon-mesh.inference.seldon" \ -H "Seldon-Model: iris" \ -H "Content-Type: application/json" \ -d '{ "model_name": "iris", "inputs": [ { "name": "input", "datatype": "FP32", "shape": [1,4], "data": [1,2,3,4] } ] }' | jq -M .也可以用 seldon-cli 完成同样的调用:
# REST seldon model infer iris --inference-host ${MESH_IP_NS1}:80 \ '{"inputs": [{"name": "predict", "shape": [1, 4], "datatype": "FP32", "data": [[1, 2, 3, 4]]}]}' # gRPC seldon model infer iris --inference-mode grpc --inference-host ${MESH_IP_NS1}:80 \ '{"model_name":"iris","inputs":[{"name":"input","contents":{"fp32_contents":[1,2,3,4]},"datatype":"FP32","shape":[1,4]}]}' | jq -M .REST 返回示例:
{ "model_name": "iris_1", "model_version": "1", "id": "3ca1757c-df02-4e57-87c1-38311bcc5943", "parameters": {}, "outputs": [ { "name": "predict", "shape": [1, 1], "datatype": "INT64", "parameters": { "content_type": "np" }, "data": [2] } ] }gRPC 返回示例:
{ "modelName": "iris_1", "modelVersion": "1", "outputs": [ { "name": "predict", "datatype": "INT64", "shape": ["1", "1"], "parameters": { "content_type": { "stringParam": "np" } }, "contents": { "int64Contents": ["2"] } } ] }注意请求头Host: seldon-mesh.inference.seldon:envoy 通过该虚拟主机名(vhost)路由到对应模型的 pipeline/model 入口,Seldon-Model头则进一步标识目标资源。
3. 在 ns2 重复相同流程
kubectl create -f samples/models/sklearn-iris-gs.yaml -n ns2model.mlops.seldon.io/iris createdkubectl wait --for condition=ready --timeout=300s model --all -n ns2model.mlops.seldon.io/iris condition met之后把入口地址换成MESH_IP_NS2重复上一节的推理命令即可,例如:
curl -k http://${MESH_IP_NS2}:80/v2/models/iris/infer \ -H "Host: seldon-mesh.inference.seldon" \ -H "Seldon-Model: iris" \ -H "Content-Type: application/json" \ -d '{ "inputs": [ { "name": "predict", "datatype": "FP32", "shape": [1,4], "data": [[1,2,3,4]] } ] }' | jq -M .{ "model_name": "iris_1", "model_version": "1", "id": "f706a23e-775f-4765-bd18-2e98d83bf7d5", "parameters": {}, "outputs": [ { "name": "predict", "shape": [1, 1], "datatype": "INT64", "parameters": { "content_type": "np" }, "data": [2] } ] }gRPC 方式同理:
curl -k http://${MESH_IP_NS2}:80/v2/models/iris/infer \ -H "Host: seldon-mesh.inference.seldon" \ -H "Seldon-Model: iris" \ -H "Content-Type: application/json" \ -d '{ "model_name": "iris", "inputs": [ { "name": "input", "datatype": "FP32", "shape": [1,4], "data": [1,2,3,4] } ] }' | jq -M .返回结构与 ns1 的 gRPC 响应一致(modelName: iris_1,输出INT64类别预测值2,仅id不同)。
4. 清理模型
kubectl delete -f samples/models/sklearn-iris-gs.yaml -n ns1 kubectl delete -f samples/models/sklearn-iris-gs.yaml -n ns2model.mlops.seldon.io "iris" deleted model.mlops.seldon.io "iris" deleted跨命名空间的 Pipeline
Pipeline 场景使用 TensorFlow 简单加法模型tfsimple1/tfsimple2(模型清单见 tfsimple1.yaml、tfsimple2.yaml),以及由两个步骤组成的 Pipeline tfsimples.yaml:
apiVersion: mlops.seldon.io/v1alpha1 kind: Pipeline metadata: name: tfsimples spec: steps: - name: tfsimple1 - name: tfsimple2 inputs: - tfsimple1 tensorMap: tfsimple1.outputs.OUTPUT0: INPUT0 tfsimple1.outputs.OUTPUT1: INPUT1 output: steps: - tfsimple2即在ns1、ns2中各部署两个模型与同一条 pipeline:tfsimple2的输入来自tfsimple1的输出(通过tensorMap映射OUTPUT0/OUTPUT1→INPUT0/INPUT1),pipeline 的输出取自tfsimple2。
1. 创建模型并等待就绪
kubectl create -f samples/models/tfsimple1.yaml -n ns1 kubectl create -f samples/models/tfsimple2.yaml -n ns1 kubectl create -f samples/models/tfsimple1.yaml -n ns2 kubectl create -f samples/models/tfsimple2.yaml -n ns2model.mlops.seldon.io/tfsimple1 created model.mlops.seldon.io/tfsimple2 created model.mlops.seldon.io/tfsimple1 created model.mlops.seldon.io/tfsimple2 createdkubectl wait --for condition=ready --timeout=300s model --all -n ns1 kubectl wait --for condition=ready --timeout=300s model --all -n ns2model.mlops.seldon.io/tfsimple1 condition met model.mlops.seldon.io/tfsimple2 condition met model.mlops.seldon.io/tfsimple1 condition met model.mlops.seldon.io/tfsimple2 condition met2. 创建 pipeline 并等待就绪
kubectl create -f samples/pipelines/tfsimples.yaml -n ns1 kubectl create -f samples/pipelines/tfsimples.yaml -n ns2pipeline.mlops.seldon.io/tfsimples created pipeline.mlops.seldon.io/tfsimples createdkubectl wait --for condition=ready --timeout=300s pipeline --all -n ns1 kubectl wait --for condition=ready --timeout=300s pipeline --all -n ns2pipeline.mlops.seldon.io/tfsimples condition met pipeline.mlops.seldon.io/tfsimples condition met3. 对两个命名空间的 pipeline 发起推理
注意 pipeline 请求的Seldon-Model头值为tfsimples.pipeline(带.pipeline后缀),与 model 资源的iris不同;输入张量名为INPUT0/INPUT1,数据类型INT32,shape[1,16]。
ns1:
curl -k http://${MESH_IP_NS1}:80/v2/models/tfsimples/infer \ -H "Host: seldon-mesh.inference.seldon" \ -H "Seldon-Model: tfsimples.pipeline" \ -H "Content-Type: application/json" \ -d '{ "model_name": "simple", "inputs": [ { "name": "INPUT0", "datatype": "INT32", "shape": [1,16], "data": [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16] }, { "name": "INPUT1", "datatype": "INT32", "shape": [1,16], "data": [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16] } ] }' | jq -M .或 gRPC + CLI:
seldon pipeline infer tfsimples --inference-mode grpc --inference-host ${MESH_IP_NS1}:80 \ '{"model_name":"simple","inputs":[{"name":"INPUT0","contents":{"int_contents":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]},"datatype":"INT32","shape":[1,16]},{"name":"INPUT1","contents":{"int_contents":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]},"datatype":"INT32","shape":[1,16]}]}' | jq -M .ns2:将上面命令中的${MESH_IP_NS1}替换为${MESH_IP_NS2}后执行,命令体与 ns1 完全一致。
两个命名空间均返回相同的计算结果(每路输出为输入逐元素翻倍后的 1~16 与 1~16 之和,即 2~32 的偶数序列):
{ "outputs": [ { "name": "OUTPUT0", "datatype": "INT32", "shape": ["1", "16"], "contents": { "intContents": [2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32] } }, { "name": "OUTPUT1", "datatype": "INT32", "shape": ["1", "16"], "contents": { "intContents": [2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32] } } ] }4. 删除 pipeline 与模型
kubectl delete -f samples/pipelines/tfsimples.yaml -n ns1 kubectl delete -f samples/pipelines/tfsimples.yaml -n ns2pipeline.mlops.seldon.io "tfsimples" deleted pipeline.mlops.seldon.io "tfsimples" deletedkubectl delete -f samples/models/tfsimple1.yaml -n ns1 kubectl delete -f samples/models/tfsimple2.yaml -n ns1 kubectl delete -f samples/models/tfsimple1.yaml -n ns2 kubectl delete -f samples/models/tfsimple2.yaml -n ns2model.mlops.seldon.io "tfsimple1" deleted model.mlops.seldon.io "tfsimple2" deleted model.mlops.seldon.io "tfsimple1" deleted model.mlops.seldon.io "tfsimple2" deleted验证 Kafka 隔离:consumer group 与 topic 命名
如果 Kafka 是通过 Ansible playbooksetup-ecosystem安装的,可以直接在 Kafka Pod 内查看 consumer group 与 topic 列表,验证安装时设置的myorg前缀已经生效:
kubectl exec seldon-kafka-0 -n seldon-mesh -- bin/kafka-consumer-groups.sh --list --bootstrap-server localhost:9092myorg-ns2-seldon-pipelinegateway-dfd61b49-4bb9-4684-adce-0b7cc215d3af myorg-ns2-seldon-modelgateway-17 myorg-ns1-seldon-pipelinegateway-d4fc83e6-29cb-442e-90cd-92a389961cfe myorg-ns2-seldon-modelgateway-60 myorg-ns2-seldon-dataflow-73d465744b7b1b5be20e88d6245e50bd myorg-ns1-seldon-modelgateway-60 myorg-ns1-seldon-modelgateway-17 myorg-ns1-seldon-dataflow-f563e04e093caa20c03e6eced084331bkubectl exec seldon-kafka-0 -n seldon-mesh -- bin/kafka-topics.sh --bootstrap-server=localhost:9092 --list__consumer_offsets myorg.ns1.errors.errors myorg.ns1.model.iris.inputs myorg.ns1.model.iris.outputs myorg.ns1.model.tfsimple1.inputs myorg.ns1.model.tfsimple1.outputs myorg.ns1.model.tfsimple2.inputs myorg.ns1.model.tfsimple2.outputs myorg.ns1.pipeline.tfsimples.inputs myorg.ns1.pipeline.tfsimples.outputs myorg.ns2.errors.errors myorg.ns2.model.iris.inputs myorg.ns2.model.iris.outputs myorg.ns2.model.tfsimple1.inputs myorg.ns2.model.tfsimple1.outputs myorg.ns2.model.tfsimple2.inputs myorg.ns2.model.tfsimple2.outputs myorg.ns2.pipeline.tfsimples.inputs myorg.ns2.pipeline.tfsimples.outputs从输出可以看出隔离的两个维度:
- topic 命名:
<topicPrefix>.<namespace>.<model|pipeline>.<资源名>.<inputs|outputs>,例如myorg.ns1.model.iris.inputs;错误 topic 为<topicPrefix>.<namespace>.errors.errors; - consumer group 命名:
<consumerGroupIdPrefix>-<namespace>-<组件名>-<实例后缀>,例如myorg-ns1-seldon-modelgateway-17。
源码层面可以直接印证这套命名规则。scheduler/pkg/kafka/topics.go 中定义了分隔符与常量:
const ( defaultSeldonTopicPrefix = "seldon" modelTopic = "model" pipelineTopic = "pipeline" errorsTopic = "errors" inputsSuffix = "inputs" outputsSuffix = "outputs" errorsSuffix = "errors" TopicSeparator = "." )TopicNamer 按topicPrefix + namespace组合生成主题名,并提供正则校验:前缀只允许a-z、A-Z、0-9、.、_、-,不合法会直接报错;GetModelTopicInputs/GetPipelineTopicOutputs等方法即对应上面看到的...inputs/...outputs主题。前缀为空时回退到默认值seldon——这解释了 setup chart 中kafka.topicPrefix默认值为seldon的原因。consumer group 前缀的拼接逻辑在 scheduler/pkg/util/hash.go 的GetKafkaConsumerName中:当consumerGroupIdPrefix非空时,将其以-分隔写入消费者名称,最终形成myorg-ns1-seldon-modelgateway-...这样的组名。Helm 模板侧,kafka.topicPrefix/kafka.consumerGroupIdPrefix分别写入 SeldonRuntime 的kafkaConfig.topicPrefix/consumerGroupIdPrefix以及 dataflow 引擎的环境变量SELDON_KAFKA_CONSUMER_PREFIX(见 _components-statefulsets.tpl),从而保证同一控制平面下不同命名空间、不同组织的前缀不会互相冲突。
拆除(TearDown)
按“servers → runtime → 控制平面 → CRD → 命名空间”的顺序删除:
helm delete seldon-v2-servers -n ns1 --wait helm delete seldon-v2-servers -n ns2 --waitrelease "seldon-v2-servers" uninstalled release "seldon-v2-servers" uninstalledhelm delete seldon-v2-runtime -n ns1 --wait helm delete seldon-v2-runtime -n ns2 --waitrelease "seldon-v2-runtime" uninstalled release "seldon-v2-runtime" uninstalledhelm delete seldon-v2 -n seldon-mesh --waitrelease "seldon-v2" uninstalledhelm delete seldon-core-v2-crds -n seldon-meshrelease "seldon-core-v2-crds" uninstalled最后删除业务命名空间:
kubectl delete namespace ns1 kubectl delete namespace ns2namespace "ns1" deleted namespace "ns2" deleted小结
本文演示的 Seldon Core V2 多命名空间部署可归纳为三点:其一,seldon-core-v2-setup安装在seldon-mesh并设置controller.clusterwide=true后,operator 通过集群级 RBAC 与--clusterwide参数获得跨命名空间调谐能力,业务命名空间只需安装 runtime 与 servers 两个 chart 即可接入统一控制平面;其二,各命名空间的 envoy 独立暴露 LoadBalancer,请求经Host: seldon-mesh.inference.seldon与Seldon-Model头路由到指定 Model/Pipeline,支持 REST 与 gRPC 两种推理协议;其三,kafka.topicPrefix与kafka.consumerGroupIdPrefix通过 scheduler 的TopicNamer与消费者命名逻辑落到每个 topic / consumer group 上,实现myorg.ns1.*、myorg-ns1-*形式的租户隔离,适合多个团队共享同一 Kafka 集群的场景。
- 人工智能
- 大模型
- MLOps
- LLMOps
- 模型推理服务
- 云原生
- 后端
【免费下载链接】seldon-core
An MLOps framework to package, deploy, monitor and manage thousands of production machine learning models
相关推荐
react-bootstrap组件前缀:withBsPrefix实现命名空间隔离
react bootstrap组件前缀:withBsPrefix实现命名空间隔离 在前端开发中,随着项目规模扩大,组件命名冲突成为常见问题。react boot
前端UI组件iSH命名空间:进程隔离与资源命名空间
iSH命名空间:进程隔离与资源命名空间 在iOS设备上运行完整的Linux shell环境,iSH项目通过用户态x86模拟和系统调用转换实现了这一壮举。作为核心
操作系统虚拟化CLIWebsoft9故障排除手册:常见问题及解决方案大全
Websoft9故障排除手册:常见问题及解决方案大全 Websoft9是一个开源的自托管应用平台和DevOps工具,专为运行基于Web的Linux面板和轻量级P
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考