cyrus 2b2245b4ef 1 7 ay önce
..
api 552b19d614 1 7 ay önce
cmd f4e984f1b4 1 7 ay önce
config f4e984f1b4 1 7 ay önce
internal 2b2245b4ef 1 7 ay önce
test f4e984f1b4 1 7 ay önce
.gitignore 552b19d614 1 7 ay önce
Dockerfile.api c20efd48f0 1 7 ay önce
Dockerfile.controller c20efd48f0 1 7 ay önce
Makefile c20efd48f0 1 7 ay önce
README.md f4e984f1b4 1 7 ay önce
go.mod 552b19d614 1 7 ay önce
go.sum 552b19d614 1 7 ay önce

README.md

# k8s-orchestrator 生产级部署说明 + 全量接口文档(Spark + StarRocks)

文档版本:v1.0
适用代码:D:\project\wenshu-platform\k8s-orchestrator 当前实现
适用场景:真实物理机 Kubernetes 集群(非单机演示)


1. 系统定位

k8s-orchestrator 提供一个统一编排平面,管理三类资源:

  • 物理节点池(NodeInventory)
  • 逻辑 Spark 集群(SparkVirtualCluster)
  • 逻辑 StarRocks 集群(StarRocksVirtualCluster,并同步外部 starrocks.com/v1 StarRocksCluster)

对上提供 REST API;对内通过 ClusterOperation 实现异步幂等执行。


2. 功能总览(当前实现)

2.1 节点池能力

  • 新增节点(SSH + kubeadm join)
  • 删除节点(仅 IDLE)
  • 列出节点

2.2 Spark 编排能力

  • 创建 Spark 逻辑集群
  • 查询 Spark 集群
  • Spark 集群扩容
  • Spark 集群缩容
  • 释放 Spark 集群并回收节点
  • 提交 SparkApplication(依赖 Spark Operator)

2.3 StarRocks 编排能力

  • 创建 StarRocks 集群(FE/BE 分离)
  • 查询 StarRocks 集群
  • 释放 StarRocks 集群并回收节点
  • StarRocks 仅 BE 扩容
  • StarRocks 仅 BE 缩容(串行安全,逐个节点摘除)
  • 提交 StarRocks 任务(Kubernetes Job)

3. 生产环境架构建议(物理机)

3.1 Kubernetes 拓扑建议

  • 控制平面:至少 3 台(高可用)
  • 工作节点:按业务规模规划
  • 每台机器唯一 hostname
  • 节点间时间同步(NTP)

3.2 依赖组件

必须提前部署:

  • Kubernetes(kubeadm/kubelet/kubectl)
  • CNI(Calico/Cilium 等)
  • Spark Operator(用于 /v1/spark-jobs,每个集群安装一次)
  • StarRocks Operator + starrocks.com/v1 CRD(用于 StarRocks 集群,每个集群安装一次)

3.3 网络与安全建议

  • API 仅内网暴露,前置网关(TLS + 鉴权)
  • 打开控制器到被纳管物理机的 SSH(22)
  • 启用 K8s Secret at-rest encryption
  • API 网关侧做限流、审计

4. 从 0 搭建真实物理机 K8s(kubeadm)

以下为标准生产流程骨架。请按你公司标准替换镜像仓库、CIDR、CNI、证书方案。

4.1 所有节点执行(控制平面 + worker)

# 1) 关闭 swap
sudo swapoff -a
sudo sed -ri '/\sswap\s/s/^#?/#/' /etc/fstab

# 2) 内核参数
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
sudo modprobe overlay
sudo modprobe br_netfilter

cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables  = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward                 = 1
EOF
sudo sysctl --system

# 3) containerd
sudo apt-get update
sudo apt-get install -y containerd
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml >/dev/null
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
sudo systemctl restart containerd
sudo systemctl enable containerd

安装 kubeadm/kubelet/kubectl(版本按你的生产基线选择,示例 v1.35):

export K8S_MINOR=v1.35
sudo mkdir -p /etc/apt/keyrings
curl -fsSL "https://pkgs.k8s.io/core:/stable:/${K8S_MINOR}/deb/Release.key" | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg

echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/${K8S_MINOR}/deb/ /" | sudo tee /etc/apt/sources.list.d/kubernetes.list

sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl
sudo systemctl enable --now kubelet

4.2 首个控制平面节点执行

sudo kubeadm init --pod-network-cidr=192.168.0.0/16

mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
kubectl get nodes

安装 CNI(示例用 Calico,生产可替换为你标准方案):

kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.30.3/manifests/calico.yaml
kubectl get pods -n kube-system

生成 worker 加入命令:

kubeadm token create --print-join-command

4.3 每个 worker 节点执行

# 用上一步输出的 join 命令替换
sudo kubeadm join <CONTROL_PLANE_IP>:6443 --token <TOKEN> --discovery-token-ca-cert-hash sha256:<HASH>

说明:

  • worker 节点只需要完成 kubeadm join(或由 AddNode 自动安装并加入)。
  • 不需要在每个 worker 上单独安装 Spark/StarRocks Operator。

控制平面验证:

kubectl get nodes -o wide

5. 安装 Spark / StarRocks Operator(生产必备)

重要说明(避免误解):

  • Operator 是集群级组件,只需在控制平面侧安装一次(每个 K8s 集群一次)。
  • 新增 worker 时不需要重复安装 Operator。

5.1 安装 Spark Operator(用于 Spark Job 提交)

helm repo add spark-operator https://kubeflow.github.io/spark-operator
helm repo update
helm search repo spark-operator/spark-operator --versions | head

# 选择经过你验证的 chart 版本
helm upgrade --install spark-operator spark-operator/spark-operator \
  --namespace spark-operator \
  --create-namespace \
  --set-json 'spark.jobNamespaces=[""]' \
  --set-json 'controller.volumes=[{"name":"tmp","emptyDir":{"sizeLimit":"1Gi"}},{"name":"wenshu-spark-ivy-cache","emptyDir":{}}]' \
  --set-json 'controller.volumeMounts=[{"name":"tmp","mountPath":"/tmp","readOnly":false},{"name":"wenshu-spark-ivy-cache","mountPath":"/var/lib/wenshu/spark-ivy","readOnly":false}]'

kubectl get pods -n spark-operator
kubectl get crd sparkapplications.sparkoperator.k8s.io

上面这组参数的关键点是:

  • spark.jobNamespaces=[""] 让 controller watch 所有 namespace,避免新建 spark-spark-* 集群漏掉。
  • controller 自己也需要一个可写的 Ivy cache,因此这里保留默认 /tmp 卷,并额外挂载一个 writable 的 spark-ivy 目录。

5.2 安装 StarRocks Operator(用于 StarRocks 集群)

kubectl apply -f https://raw.githubusercontent.com/StarRocks/starrocks-kubernetes-operator/main/deploy/starrocks.com_starrocksclusters.yaml
kubectl apply -f https://raw.githubusercontent.com/StarRocks/starrocks-kubernetes-operator/main/deploy/operator.yaml

kubectl get crd starrocksclusters.starrocks.com
kubectl get pods -n starrocks

5.3 镜像版本与预拉取配置(生产建议)

建议生产环境固定镜像版本(tag 或 digest),避免 latest 漂移。

Spark 集群创建接口的 sparkConfig 支持以下键:

  • spark.image:Spark 作业默认镜像(默认 apache/spark:3.5.1)
  • spark.imagePullPolicy:默认拉取策略(默认 IfNotPresent)
  • spark.prepull.enabled:是否在集群创建/扩容后预拉取镜像(默认 true)
  • spark.prepull.image:预拉取镜像(默认复用 spark.image)
  • spark.prepull.imagePullPolicy:预拉取拉取策略(默认复用 spark.imagePullPolicy)

行为说明:

  • POST /v1/spark-clusters 完成节点隔离后,会在目标节点创建临时 DaemonSet 预拉取镜像。
  • 预拉取完成后才会把 Spark 集群标记为 READY。
  • POST /v1/spark-jobs 若未提供 sparkApplicationSpec.image,会自动使用 spark.image(或默认值)。
  • Spark 镜像必须为显式版本(不允许 latest)。

6. 构建并推送 orchestrator 镜像

项目已内置 Dockerfile.api 与 Dockerfile.controller,可直接构建。

cd /path/to/k8s-orchestrator

export REGISTRY=<your-registry>
export TAG=v1.0.0

docker build -f Dockerfile.api -t ${REGISTRY}/spark-orchestrator-api:${TAG} .
docker build -f Dockerfile.controller -t ${REGISTRY}/spark-orchestrator-controller:${TAG} .

docker push ${REGISTRY}/spark-orchestrator-api:${TAG}
docker push ${REGISTRY}/spark-orchestrator-controller:${TAG}

也可以用 Makefile:

make docker-build IMAGE_REGISTRY=${REGISTRY} IMAGE_TAG=${TAG}
make docker-push IMAGE_REGISTRY=${REGISTRY} IMAGE_TAG=${TAG}

7. 部署 orchestrator 到集群

cd /path/to/k8s-orchestrator

# 1) namespace + CRD + RBAC
kubectl apply -f config/samples/namespace.yaml
kubectl apply -k config/crd
kubectl apply -k config/rbac

# 2) controller 默认 join 命令(用于 AddNode)
JOIN_CMD="$(kubeadm token create --print-join-command)"
kubectl -n orchestrator-system create secret generic kubeadm-join \
  --from-literal=joinCommand="${JOIN_CMD}" \
  --dry-run=client -o yaml | kubectl apply -f -

# 3) 部署 API + controller
kubectl apply -f config/samples/deployment.yaml

# 4) 替换镜像
kubectl -n orchestrator-system set image deploy/spark-orchestrator-api \
  api=${REGISTRY}/spark-orchestrator-api:${TAG}
kubectl -n orchestrator-system set image deploy/spark-orchestrator-controller \
  controller=${REGISTRY}/spark-orchestrator-controller:${TAG}

# 5) 检查状态
kubectl -n orchestrator-system rollout status deploy/spark-orchestrator-api
kubectl -n orchestrator-system rollout status deploy/spark-orchestrator-controller
kubectl -n orchestrator-system get pods

生产暴露方式建议:Ingress/Gateway + TLS。
调试方式可临时使用:

kubectl -n orchestrator-system port-forward svc/spark-orchestrator-api 8080:80

8. API 使用约定(全局)

  • Base URL: http(s)://<orchestrator-host>
  • 所有变更接口必须带:Idempotency-Key
  • 变更接口返回 202 Accepted,异步执行
  • 通过 GET /v1/operations/{operationId} 轮询结果

8.1 通用响应

成功受理(202):

{
  "operationId": "op-xxx",
  "resourceId": "xxx"
}

错误(400/404):

{
  "error": "xxx"
}

8.2 Operation 状态机

ClusterOperation.status.phase:

  • PENDING
  • RUNNING
  • SUCCEEDED
  • FAILED

轮询示例:

curl -s http://127.0.0.1:8080/v1/operations/<operationId> | jq .status

9. 全量接口文档

9.1 健康检查

GET /healthz

  • 描述:API 存活检查
  • 成功示例:
{"status":"ok"}

9.2 节点池接口

POST /v1/nodes

  • 描述:注册并纳管物理节点(通过 SSH 执行 kubeadm join)
  • Header:Idempotency-Key 必填
  • 请求体:
{
  "nodeId": "node-a",
  "cpu": 16,
  "ram": 64,
  "ssd": 1024,
  "ipAddress": "10.0.0.11",
  "sshPassword": "******",
  "sshUser": "root",
  "sshPort": 22,
  "joinCommandOverride": "kubeadm join ..."
}
  • 校验:
    • ipAddress 必填
    • sshPassword 必填
    • cpu/ram/ssd 必须 > 0

DELETE /v1/nodes/{nodeId}

  • 描述:删除节点库存记录(仅当节点 IDLE)
  • Header:Idempotency-Key 必填

GET /v1/nodes

  • 描述:列出节点库存

9.3 Spark 集群接口

POST /v1/spark-clusters

  • 描述:创建逻辑 Spark 集群(节点隔离 + 命名空间)
  • Header:Idempotency-Key 必填
  • 请求体:
{
  "clusterId": "spark-a",
  "namespace": "spark-a",
  "nodeIds": ["node-a", "node-b"],
  "sparkConfig": {
    "spark.dynamicAllocation.enabled": "false",
    "spark.image": "apache/spark:3.5.1",
    "spark.imagePullPolicy": "IfNotPresent",
    "spark.prepull.enabled": "true"
  }
}

关键 sparkConfig 说明:

  • spark.image / spark.imagePullPolicy:作为 Spark 提交默认值。
  • spark.prepull.enabled=true 时,创建/扩容后会先预拉取镜像再进入 Ready。

GET /v1/spark-clusters/{clusterId}

  • 描述:查询 SparkVirtualCluster CR

POST /v1/spark-clusters/{clusterId}/scale-out

  • 描述:扩容
  • Header:Idempotency-Key 必填
  • 请求体:
{"nodeIds":["node-c"]}

POST /v1/spark-clusters/{clusterId}/scale-in

  • 描述:缩容(默认 BlockIfRunning,如果有 Running/Pending Pod 会阻塞)
  • Header:Idempotency-Key 必填
  • 请求体:
{"nodeIds":["node-b"]}

POST /v1/spark-clusters/{clusterId}/release

  • 描述:释放集群并回收节点
  • Header:Idempotency-Key 必填

9.4 Spark 作业接口

POST /v1/spark-jobs

  • 描述:提交 SparkApplication(sparkoperator.k8s.io/v1beta2)
  • Header:Idempotency-Key 必填
  • 请求体:
{
  "clusterId": "spark-a",
  "sparkApplicationSpec": {
    "type": "Scala",
    "mode": "cluster",
    "image": "apache/spark:3.5.1",
    "mainClass": "org.example.Main",
    "mainApplicationFile": "local:///opt/spark/app.jar",
    "driver": {"cores": 1, "memory": "1g"},
    "executor": {"instances": 2, "cores": 1, "memory": "2g"}
  }
}

实现行为:

  • 编排器会自动注入 driver/executor 的 nodeSelector 和 tolerations,确保作业只落在该 Spark 集群隔离节点上。
  • 若请求未传 sparkApplicationSpec.image,会自动回填为集群 sparkConfig.spark.image(默认 apache/spark:3.5.1)。
  • 镜像必须是显式版本 tag 或 digest(不允许 latest)。

9.5 StarRocks 集群接口

POST /v1/starrocks-clusters

  • 描述:创建 StarRocks 集群
  • Header:Idempotency-Key 必填
  • 请求体:
{
  "clusterId": "sr-a",
  "namespace": "starrocks-sr-a",
  "feNodeIds": ["fe-1"],
  "beNodeIds": ["be-1", "be-2"],
  "starRocksConfig": {
    "feImage": "starrocks/fe-ubuntu:3.3.10",
    "beImage": "starrocks/be-ubuntu:3.3.10",
    "feResources": {
      "requests": {"cpu": "1", "memory": "2Gi"},
      "limits": {"cpu": "2", "memory": "4Gi"}
    },
    "beResources": {
      "requests": {"cpu": "2", "memory": "4Gi"},
      "limits": {"cpu": "4", "memory": "8Gi"}
    }
  }
}

约束与默认:

  • feNodeIds 必填
  • beNodeIds 必填
  • FE/BE 节点不能重叠
  • 默认 namespace:starrocks-<clusterId>
  • 默认镜像:
    • FE: starrocks/fe-ubuntu:3.3.10
    • BE: starrocks/be-ubuntu:3.3.10
  • 默认资源:按所选 FE/BE 节点容量各自取最小 CPU/RAM 作为 requests/limits

GET /v1/starrocks-clusters/{clusterId}

  • 描述:查询 StarRocksVirtualCluster CR

POST /v1/starrocks-clusters/{clusterId}/scale-out

  • 描述:仅 BE 扩容
  • Header:Idempotency-Key 必填
  • 请求体:
{"nodeIds":["be-3"]}

POST /v1/starrocks-clusters/{clusterId}/scale-in

  • 描述:仅 BE 缩容(串行逐个摘除,安全收敛)
  • Header:Idempotency-Key 必填
  • 请求体:
{"nodeIds":["be-1","be-2"]}

POST /v1/starrocks-clusters/{clusterId}/release

  • 描述:释放 StarRocks 集群并回收节点
  • Header:Idempotency-Key 必填

9.6 StarRocks 任务接口

POST /v1/starrocks-jobs

  • 描述:向目标 StarRocks 集群命名空间提交一个 Kubernetes batch/v1 Job
  • Header:Idempotency-Key 必填
  • 请求体:
{
  "clusterId": "sr-a",
  "jobSpec": {
    "backoffLimit": 1,
    "template": {
      "spec": {
        "restartPolicy": "Never",
        "containers": [
          {
            "name": "task",
            "image": "curlimages/curl:8.8.0",
            "command": ["sh", "-c", "echo starrocks task running"]
          }
        ]
      }
    }
  }
}

约束:

  • clusterId 必填
  • jobSpec 必填
  • jobSpec.template 必填(符合 K8s Job Spec)

9.7 操作查询接口

GET /v1/operations/{operationId}

  • 描述:查询异步操作状态
  • 返回对象:ClusterOperation CR(含 spec + status)

10. 生产调用流程(建议)

  1. 调用变更接口(返回 operationId)
  2. 轮询 /v1/operations/{operationId}
  3. status.phase=SUCCEEDED 后再继续下一步
  4. FAILED 时读取 status.message 定位错误

轮询脚本:

wait_op () {
  local op=$1
  local api=$2
  while true; do
    phase=$(curl -s "${api}/v1/operations/${op}" | jq -r '.status.phase // empty')
    echo "operation=${op} phase=${phase}"
    [ "$phase" = "SUCCEEDED" ] && return 0
    [ "$phase" = "FAILED" ] && return 1
    sleep 3
  done
}

11. 端到端示例(物理机集群)

假设 API 已通过网关暴露为:

export API=https://orchestrator.example.com

11.1 纳管三个物理节点

curl -s -X POST "$API/v1/nodes" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: add-node-fe-1' \
  -d '{"nodeId":"fe-1","cpu":16,"ram":64,"ssd":1024,"ipAddress":"10.10.1.11","sshPassword":"***"}' | jq .

curl -s -X POST "$API/v1/nodes" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: add-node-be-1' \
  -d '{"nodeId":"be-1","cpu":32,"ram":128,"ssd":2048,"ipAddress":"10.10.1.12","sshPassword":"***"}' | jq .

curl -s -X POST "$API/v1/nodes" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: add-node-be-2' \
  -d '{"nodeId":"be-2","cpu":32,"ram":128,"ssd":2048,"ipAddress":"10.10.1.13","sshPassword":"***"}' | jq .

11.2 创建 Spark 集群并提交作业

SPARK_CREATE_OP=$(curl -s -X POST "$API/v1/spark-clusters" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: create-spark-a' \
  -d '{"clusterId":"spark-a","nodeIds":["be-1","be-2"]}' | jq -r .operationId)

wait_op "$SPARK_CREATE_OP" "$API"

SPARK_JOB_OP=$(curl -s -X POST "$API/v1/spark-jobs" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: submit-spark-a-1' \
  -d '{"clusterId":"spark-a","sparkApplicationSpec":{"type":"Scala","mode":"cluster","image":"apache/spark:3.5.1","mainClass":"org.example.Main","mainApplicationFile":"local:///opt/spark/app.jar","driver":{"cores":1,"memory":"1g"},"executor":{"instances":2,"cores":1,"memory":"2g"}}}' | jq -r .operationId)

wait_op "$SPARK_JOB_OP" "$API"

11.3 创建 StarRocks、提交任务、BE 扩缩容、释放

SR_CREATE_OP=$(curl -s -X POST "$API/v1/starrocks-clusters" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: create-sr-a' \
  -d '{"clusterId":"sr-a","feNodeIds":["fe-1"],"beNodeIds":["be-1"]}' | jq -r .operationId)
wait_op "$SR_CREATE_OP" "$API"

SR_JOB_OP=$(curl -s -X POST "$API/v1/starrocks-jobs" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: submit-sr-a-task-1' \
  -d '{"clusterId":"sr-a","jobSpec":{"backoffLimit":1,"template":{"spec":{"restartPolicy":"Never","containers":[{"name":"task","image":"busybox:1.36","command":["sh","-c","echo task-ok"]}]}}}}' | jq -r .operationId)
wait_op "$SR_JOB_OP" "$API"

SR_SCALEOUT_OP=$(curl -s -X POST "$API/v1/starrocks-clusters/sr-a/scale-out" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: scaleout-sr-a-1' \
  -d '{"nodeIds":["be-2"]}' | jq -r .operationId)
wait_op "$SR_SCALEOUT_OP" "$API"

SR_SCALEIN_OP=$(curl -s -X POST "$API/v1/starrocks-clusters/sr-a/scale-in" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: scalein-sr-a-1' \
  -d '{"nodeIds":["be-1"]}' | jq -r .operationId)
wait_op "$SR_SCALEIN_OP" "$API"

SR_RELEASE_OP=$(curl -s -X POST "$API/v1/starrocks-clusters/sr-a/release" \
  -H 'Idempotency-Key: release-sr-a-1' | jq -r .operationId)
wait_op "$SR_RELEASE_OP" "$API"

12. 生产运行注意事项

  1. Idempotency-Key 必须全局唯一(至少在保留窗口内唯一),避免误复用历史操作。
  2. /v1/nodes 涉及 SSH 密码,务必走内网 + TLS + 网关鉴权。
  3. KUBEADM_JOIN_COMMAND 会过期(token TTL),需定期刷新 kubeadm-join Secret。
  4. Spark 缩容/释放、StarRocks 缩容/释放会检查目标命名空间是否有 Running/Pending Pod。
  5. StarRocks BE 缩容是串行执行;不要并发发起多个缩容请求。
  6. POST /v1/starrocks-jobs 会创建 batch/v1 Job,请在 jobSpec 中显式配置镜像、重试策略、资源限制。
  7. 建议将 Spark 集群 spark.prepull.enabled 保持为 true,降低首任务冷启动耗时。
  8. 强烈建议在网关层增加:认证、授权、限流、审计。

13. 常见故障排查

13.1 AddNode 一直失败

  • 检查 controller 到目标节点 22 端口连通
  • 检查节点是否已安装 kubeadm/kubelet/containerd
  • 检查 kubeadm-join secret 中命令是否过期

13.2 SparkJob 提交失败

  • 检查是否安装 Spark Operator
  • 检查 CRD:sparkapplications.sparkoperator.k8s.io

13.3 StarRocks 集群创建失败

  • 检查是否安装 StarRocks Operator
  • 检查 CRD:starrocksclusters.starrocks.com
  • 检查 FE/BE 节点是否重叠、是否可分配

13.4 缩容/释放被阻塞

  • 目标命名空间存在 Running/Pending Pod
  • 先清理业务 Pod,再重试

14. 官方文档参考