Appearance
04. 查询与调度
目标:Doris 能查询、Harbor 能存镜像、DolphinScheduler 能调度、Spark 有固定 History Server。
为什么把查询和调度分开
- Doris 负责“快”:BI/交互查询走 Doris,不每次都起 Spark;
- DS 负责“编排”:什么时候跑、依赖谁、失败重试,不写在业务脚本里;
- Harbor 负责“交付”:镜像不可变,K8s 不依赖公网;
- SHS 负责“可追溯”:driver Pod 会重建,历史必须落到固定存储。
四者不是替代关系:Doris 不能调度,DS 不存数据,Harbor 不算计算,SHS 不参与执行。
1. Doris
为什么用 Operator 而不是手写 StatefulSet:FE/BE 有启动顺序、注册、扩缩容、配置分发,Operator 把这些做成 CRD;手写很容易漏掉 FE 先于 BE 的条件。
测试期 1FE/1BE 是为了省资源;生产至少 3FE/3BE,否则挂一台就不可用。
1.1 安装 Operator
bash
helm repo add doris https://charts.selectdb.com
helm repo update
helm upgrade --install doris-operator doris/doris-operator \
-n doris --create-namespace \
--version 25.8.0验证:
bash
kubectl -n doris get pods
kubectl get crd | grep doris1.2 DorisCluster
保存为 50-doris.yaml:
yaml
apiVersion: doris.selectdb.com/v1
kind: DorisCluster
metadata:
name: doris
namespace: doris
spec:
feSpec:
image: docker.io/apache/doris:fe-4.1.0-slim
replicas: 1
requests:
cpu: "4"
memory: 8Gi
limits:
cpu: "8"
memory: 16Gi
persistentVolumes:
- name: fe-meta
mountPath: /opt/apache-doris/fe/doris-meta
persistentVolumeClaimSpec:
storageClassName: local-path
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 20Gi
beSpec:
image: docker.io/apache/doris:be-4.1.0-slim
replicas: 1
requests:
cpu: "4"
memory: 8Gi
limits:
cpu: "8"
memory: 16Gi
persistentVolumes:
- name: be-storage
mountPath: /opt/apache-doris/be/storage
persistentVolumeClaimSpec:
storageClassName: local-path
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 50Gi字段说明:
| 字段 | 作用 |
|---|---|
kind: DorisCluster | Doris Operator 的 CRD,operator 监听它 |
feSpec.image | FE 镜像,版本要和 BE 一致 |
feSpec.replicas: 1 | 测试期单 FE;生产至少 3 |
feSpec.resources | FE 的 CPU/内存请求与限制 |
feSpec.persistentVolumes | FE 元数据目录 /doris-meta 的 PVC |
beSpec.image | BE 镜像 |
beSpec.replicas: 1 | 测试期单 BE;生产 3 BE |
beSpec.persistentVolumes | BE 数据目录 /be/storage 的 PVC |
storageClassName: local-path | 测试用本地盘;生产换分布式存储 |
应用:
bash
kubectl apply -f 50-doris.yaml
kubectl -n doris get pods预期 doris-fe-0、doris-be-0 都 1/1 Running。
1.3 验证
bash
kubectl -n doris exec doris-fe-0 -- mysql -h127.0.0.1 -P9030 -uroot -e \
"SHOW FRONTENDS; SHOW BACKENDS;"BE 必须 Alive: true。
建库建表:
sql
CREATE DATABASE IF NOT EXISTS demo;
CREATE TABLE demo.iceberg_orders (
id BIGINT,
user_id BIGINT,
amount DECIMAL(10,2),
status VARCHAR(20),
updated_at DATETIME
) DUPLICATE KEY(id)
DISTRIBUTED BY HASH(id) BUCKETS 3
PROPERTIES ("replication_num" = "1");Doris BE 启动失败优先看:CPU 是否支持 AVX2、容器
nofile是否太小、存储目录权限。
2. Harbor
为什么不用公网 Docker Hub:国内拉取不稳定,且生产不应把镜像供应链交给公网。
Harbor 提供私有仓库、项目权限、镜像清理;测试期关 TLS/Trivy 是为了省资源,生产必须开。
节点要配置 HTTP registry,因为测试期 Harbor 没证书;生产用 HTTPS 后应删掉skip_verify。
2.1 values
保存为 80-harbor-values.yaml,把 externalURL 改成 node1 内网 IP:
yaml
expose:
type: nodePort
tls:
enabled: false
nodePort:
ports:
http:
port: 80
nodePort: 30002
https:
port: 443
nodePort: 30003
externalURL: http://<NODE1>:30002
harborAdminPassword: "<HARBOR_PASS>"
persistence:
enabled: true
persistentVolumeClaim:
registry:
storageClass: local-path
size: 20Gi
database:
storageClass: local-path
size: 2Gi
redis:
storageClass: local-path
size: 1Gi
trivy:
enabled: false
notary:
enabled: false字段说明:
| 字段 | 作用 |
|---|---|
expose.type: nodePort | 用 NodePort 暴露,测试环境不依赖 Ingress |
expose.tls.enabled: false | 测试期关 TLS;生产必须开 |
nodePort.ports.http.nodePort: 30002 | 对外 HTTP 端口 |
externalURL | Harbor 生成 token/回跳用的外部地址 |
harborAdminPassword | 初始 admin 密码,首次登录后改 |
persistence.registry | 镜像层存储,20Gi |
persistence.database/redis | 内置 PG/Redis 的 PVC |
trivy.enabled: false | 测试期关漏洞扫描,省资源 |
notary.enabled: false | 不用镜像签名 |
2.2 安装
bash
helm repo add harbor https://helm.goharbor.io
helm repo update
helm upgrade --install harbor harbor/harbor \
-n harbor --create-namespace \
-f 80-harbor-values.yaml验证:
bash
kubectl -n harbor get pods
curl -sS -o /dev/null -w '%{http_code}\n' http://$NODE1:30002/预期 200。
2.3 节点配置 HTTP registry
Harbor 关 TLS 时,containerd 需要允许 HTTP:
bash
for n in node1 node2 node3; do
ssh $n "mkdir -p /etc/containerd/certs.d/$NODE1:30002 && \
printf 'server = \"http://$NODE1:30002\"\n[host.\"http://$NODE1:30002\"]\n capabilities = [\"pull\", \"resolve\"]\n skip_verify = true\n' \
> /etc/containerd/certs.d/$NODE1:30002/hosts.toml"
done生产必须用 HTTPS 和证书,不能关 TLS。
3. DolphinScheduler 集群
为什么不用 standalone:standalone 用 H2,重启丢工作流,也不能多 worker;生产/集群验证必须外部 PG + ZK。
为什么 ZK 单独部署:DS 的 master/worker 选主依赖注册中心;用 chart 内置 ZK 会和 DS 生命周期绑在一起,排障更麻烦。
为什么 resource center 用 S3:file 模式需要共享盘,K8s 多 Pod 下不可用;S3 是 DS 原生支持的对象存储后端。
3.1 准备 ZooKeeper
保存为 ds-zk.yaml:
yaml
apiVersion: v1
kind: Service
metadata:
name: zookeeper
namespace: ds-cluster
spec:
selector:
app: zookeeper
ports:
- name: client
port: 2181
targetPort: 2181
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: zookeeper
namespace: ds-cluster
spec:
replicas: 1
selector:
matchLabels:
app: zookeeper
template:
metadata:
labels:
app: zookeeper
spec:
containers:
- name: zookeeper
image: docker.io/library/zookeeper:3.9.2
imagePullPolicy: IfNotPresent
ports:
- containerPort: 2181
env:
- name: ZOO_STANDALONE_ENABLED
value: "true"
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 1Gibash
kubectl create namespace ds-cluster --dry-run=client -o yaml | kubectl apply -f -
kubectl apply -f ds-zk.yaml3.2 取 chart
bash
curl -L -o /tmp/ds.tar.gz \
https://github.com/apache/dolphinscheduler/archive/refs/tags/3.2.2.tar.gz
tar -xzf /tmp/ds.tar.gz -C /tmp
cd /tmp/dolphinscheduler-3.2.2/deploy/kubernetes/dolphinschedulerchart 会依赖 Bitnami 的 postgresql/zookeeper/mysql/minio。即使 values 里禁用,Helm 仍要求 charts/ 存在,创建空依赖:
bash
for c in postgresql zookeeper mysql minio; do
mkdir -p charts/$c
cat > charts/$c/Chart.yaml <<EOF
apiVersion: v2
name: $c
version: 0.0.0
EOF
done3.3 values
保存为 70-ds-values.yaml:
yaml
image:
registry: apache
tag: "3.2.2"
pullPolicy: IfNotPresent
busybox: "docker.io/library/busybox:latest"
postgresql:
enabled: false
mysql:
enabled: false
zookeeper:
enabled: false
minio:
enabled: false
externalDatabase:
enabled: true
type: postgresql
host: postgres.data.svc.cluster.local
port: "5432"
username: polaris
password: "<PG_PASS>"
database: dolphinscheduler
params: "characterEncoding=utf8"
driverClassName: org.postgresql.Driver
externalRegistry:
registryPluginName: "zookeeper"
registryServers: "zookeeper.ds-cluster.svc.cluster.local:2181"
conf:
common:
resource.storage.type: S3
resource.storage.upload.base.path: /dolphinscheduler
resource.aws.access.key.id: "<S3_AK>"
resource.aws.secret.access.key: "<S3_SK>"
resource.aws.region: us-east-1
resource.aws.s3.bucket.name: warehouse
resource.aws.s3.endpoint: http://seaweedfs-s3.storage.svc.cluster.local:8333
master:
replicas: "1"
worker:
replicas: "1"
api:
replicas: 1
alert:
enabled: true
replicas: 1字段说明:
| 字段 | 作用 |
|---|---|
image.registry/tag | DS 镜像仓库和版本 |
postgresql/mysql/zookeeper/minio.enabled: false | 不用 chart 内置依赖,全部外部化 |
externalDatabase | 指向外部 PG 的 dolphinscheduler 库 |
externalRegistry | 指向外部 ZK,master/worker 选主用 |
conf.common.resource.storage.type: S3 | resource center 用 S3,不用本地盘 |
resource.aws.* | S3 端点、bucket、凭证 |
master.replicas | master 副本数,测试 1,生产 ≥2 |
worker.replicas | worker 副本数,测试 1 |
api.replicas | API 副本数 |
alert.enabled | 是否起告警服务 |
安装:
bash
helm upgrade --install dolphinscheduler . \
-n ds-cluster \
-f 70-ds-values.yaml \
--timeout 15m验证:
bash
kubectl -n ds-cluster get pods
kubectl -n ds-cluster get svc预期 zookeeper、dolphinscheduler-master、worker、api、alert 都 Running;db-init 是 Completed。
UI:
bash
kubectl -n ds-cluster port-forward svc/dolphinscheduler-api 12345:12345
# http://localhost:12345/dolphinscheduler/ui/默认 admin / dolphinscheduler123,首次登录后立刻改密码。
4. Spark History Server
为什么不能直接看 driver UI:K8s cluster mode 每次提交都新建 driver Pod,Pod 删了 UI 就没了。
SHS 通过 event log 重建历史,入口固定;事件日志必须和 SHS 扫描目录一致,否则“任务跑过但查不到”。
事件日志放 S3 而不是本地盘:SHS 可以随时重建,不依赖某个节点。
保存为 60-spark-shs.yaml,把镜像和 S3 凭证换成自己的:
yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: spark-shs-config
namespace: data
data:
spark-defaults.conf: |
spark.history.fs.logDirectory s3a://warehouse/spark-events
spark.history.ui.port 18080
spark.history.fs.update.interval 10s
spark.history.retainedApplications 100
spark.hadoop.fs.s3a.endpoint http://seaweedfs-s3.storage.svc.cluster.local:8333
spark.hadoop.fs.s3a.endpoint.region us-east-1
spark.hadoop.fs.s3a.path.style.access true
spark.hadoop.fs.s3a.connection.ssl.enabled false
spark.hadoop.fs.s3a.access.key <S3_AK>
spark.hadoop.fs.s3a.secret.key <S3_SK>
spark.hadoop.fs.s3a.impl org.apache.hadoop.fs.s3a.S3AFileSystem
spark.hadoop.fs.s3a.aws.credentials.provider org.apache.hadoop.fs.s3a.SimpleAWSCredentialsProvider
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: spark-history-server
namespace: data
spec:
replicas: 1
selector:
matchLabels:
app: spark-history-server
template:
metadata:
labels:
app: spark-history-server
spec:
containers:
- name: shs
image: <REGISTRY>/spark:3.5.9-iceberg
imagePullPolicy: IfNotPresent
command: ["/opt/spark/bin/spark-class"]
args: ["org.apache.spark.deploy.history.HistoryServer"]
ports:
- containerPort: 18080
resources:
requests:
cpu: 500m
memory: 1Gi
limits:
cpu: "2"
memory: 3Gi
volumeMounts:
- name: shs-config
mountPath: /opt/spark/conf/spark-defaults.conf
subPath: spark-defaults.conf
volumes:
- name: shs-config
configMap:
name: spark-shs-config
---
apiVersion: v1
kind: Service
metadata:
name: spark-history-server
namespace: data
spec:
selector:
app: spark-history-server
ports:
- port: 18080
targetPort: 18080
nodePort: 30082
type: NodePort字段说明:
| 字段 | 作用 |
|---|---|
spark.history.fs.logDirectory | SHS 扫描事件日志的目录,必须和提交时一致 |
spark.history.ui.port | SHS Web 端口 |
spark.history.fs.update.interval | 多久刷新一次新事件 |
spark.history.retainedApplications | 保留多少个应用历史 |
fs.s3a.endpoint/access.key/secret.key | SHS 读 S3 事件日志的凭证 |
fs.s3a.path.style.access | SeaweedFS 必须 path-style |
image | 带 Spark 运行时的镜像 |
command/args | 启动 HistoryServer 主类 |
volumeMounts | 把 spark-defaults.conf 挂进 conf |
Service.nodePort: 30082 | 固定对外入口,避免 driver UI 变化 |
应用:
bash
kubectl apply -f 60-spark-shs.yaml
kubectl -n data get pods | grep spark-history
curl -sS -o /dev/null -w '%{http_code}\n' http://$NODE1:30082/5. 本阶段验收
bash
kubectl -n doris get pods
kubectl -n harbor get pods
kubectl -n ds-cluster get pods
kubectl -n data get pods | grep spark-history要求:
- Doris FE/BE Running,
SHOW BACKENDSAlive; - Harbor 200,能 push/pull;
- DS 5 个组件 Running;
- SHS 200。
下一步:Spark 与端到端。