|
|
7 ay önce | |
|---|---|---|
| .. | ||
| api | 7 ay önce | |
| cmd | 7 ay önce | |
| config | 7 ay önce | |
| internal | 7 ay önce | |
| test | 7 ay önce | |
| .gitignore | 7 ay önce | |
| Dockerfile.api | 7 ay önce | |
| Dockerfile.controller | 7 ay önce | |
| Makefile | 7 ay önce | |
| README.md | 7 ay önce | |
| go.mod | 7 ay önce | |
| go.sum | 7 ay önce | |
# k8s-orchestrator 生产级部署说明 + 全量接口文档(Spark + StarRocks)
文档版本:v1.0
适用代码:D:\project\wenshu-platform\k8s-orchestrator 当前实现
适用场景:真实物理机 Kubernetes 集群(非单机演示)
k8s-orchestrator 提供一个统一编排平面,管理三类资源:
NodeInventory)SparkVirtualCluster)StarRocksVirtualCluster,并同步外部 starrocks.com/v1 StarRocksCluster)对上提供 REST API;对内通过 ClusterOperation 实现异步幂等执行。
kubeadm join)IDLE)必须提前部署:
/v1/spark-jobs,每个集群安装一次)starrocks.com/v1 CRD(用于 StarRocks 集群,每个集群安装一次)以下为标准生产流程骨架。请按你公司标准替换镜像仓库、CIDR、CNI、证书方案。
# 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
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
# 用上一步输出的 join 命令替换
sudo kubeadm join <CONTROL_PLANE_IP>:6443 --token <TOKEN> --discovery-token-ca-cert-hash sha256:<HASH>
说明:
kubeadm join(或由 AddNode 自动安装并加入)。控制平面验证:
kubectl get nodes -o wide
重要说明(避免误解):
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-* 集群漏掉。/tmp 卷,并额外挂载一个 writable 的 spark-ivy 目录。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
建议生产环境固定镜像版本(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 预拉取镜像。READY。POST /v1/spark-jobs 若未提供 sparkApplicationSpec.image,会自动使用 spark.image(或默认值)。latest)。项目已内置 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}
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
http(s)://<orchestrator-host>Idempotency-Key202 Accepted,异步执行GET /v1/operations/{operationId} 轮询结果成功受理(202):
{
"operationId": "op-xxx",
"resourceId": "xxx"
}
错误(400/404):
{
"error": "xxx"
}
ClusterOperation.status.phase:
PENDINGRUNNINGSUCCEEDEDFAILED轮询示例:
curl -s http://127.0.0.1:8080/v1/operations/<operationId> | jq .status
GET /healthz{"status":"ok"}
POST /v1/nodeskubeadm join)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 必须 > 0DELETE /v1/nodes/{nodeId}IDLE)Idempotency-Key 必填GET /v1/nodesPOST /v1/spark-clustersIdempotency-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}POST /v1/spark-clusters/{clusterId}/scale-outIdempotency-Key 必填{"nodeIds":["node-c"]}
POST /v1/spark-clusters/{clusterId}/scale-inBlockIfRunning,如果有 Running/Pending Pod 会阻塞)Idempotency-Key 必填{"nodeIds":["node-b"]}
POST /v1/spark-clusters/{clusterId}/releaseIdempotency-Key 必填POST /v1/spark-jobssparkoperator.k8s.io/v1beta2)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"}
}
}
实现行为:
nodeSelector 和 tolerations,确保作业只落在该 Spark 集群隔离节点上。sparkApplicationSpec.image,会自动回填为集群 sparkConfig.spark.image(默认 apache/spark:3.5.1)。latest)。POST /v1/starrocks-clustersIdempotency-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 必填starrocks-<clusterId>starrocks/fe-ubuntu:3.3.10starrocks/be-ubuntu:3.3.10GET /v1/starrocks-clusters/{clusterId}POST /v1/starrocks-clusters/{clusterId}/scale-outIdempotency-Key 必填{"nodeIds":["be-3"]}
POST /v1/starrocks-clusters/{clusterId}/scale-inIdempotency-Key 必填{"nodeIds":["be-1","be-2"]}
POST /v1/starrocks-clusters/{clusterId}/releaseIdempotency-Key 必填POST /v1/starrocks-jobsbatch/v1 JobIdempotency-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)GET /v1/operations/{operationId}ClusterOperation CR(含 spec + status)operationId)/v1/operations/{operationId}status.phase=SUCCEEDED 后再继续下一步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
}
假设 API 已通过网关暴露为:
export API=https://orchestrator.example.com
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 .
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"
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"
Idempotency-Key 必须全局唯一(至少在保留窗口内唯一),避免误复用历史操作。/v1/nodes 涉及 SSH 密码,务必走内网 + TLS + 网关鉴权。KUBEADM_JOIN_COMMAND 会过期(token TTL),需定期刷新 kubeadm-join Secret。Running/Pending Pod。POST /v1/starrocks-jobs 会创建 batch/v1 Job,请在 jobSpec 中显式配置镜像、重试策略、资源限制。spark.prepull.enabled 保持为 true,降低首任务冷启动耗时。kubeadm-join secret 中命令是否过期sparkapplications.sparkoperator.k8s.iostarrocksclusters.starrocks.com