Skip to content

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 doris

1.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: DorisClusterDoris Operator 的 CRD,operator 监听它
feSpec.imageFE 镜像,版本要和 BE 一致
feSpec.replicas: 1测试期单 FE;生产至少 3
feSpec.resourcesFE 的 CPU/内存请求与限制
feSpec.persistentVolumesFE 元数据目录 /doris-meta 的 PVC
beSpec.imageBE 镜像
beSpec.replicas: 1测试期单 BE;生产 3 BE
beSpec.persistentVolumesBE 数据目录 /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 端口
externalURLHarbor 生成 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: 1Gi
bash
kubectl create namespace ds-cluster --dry-run=client -o yaml | kubectl apply -f -
kubectl apply -f ds-zk.yaml

3.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/dolphinscheduler

chart 会依赖 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
done

3.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/tagDS 镜像仓库和版本
postgresql/mysql/zookeeper/minio.enabled: false不用 chart 内置依赖,全部外部化
externalDatabase指向外部 PG 的 dolphinscheduler 库
externalRegistry指向外部 ZK,master/worker 选主用
conf.common.resource.storage.type: S3resource center 用 S3,不用本地盘
resource.aws.*S3 端点、bucket、凭证
master.replicasmaster 副本数,测试 1,生产 ≥2
worker.replicasworker 副本数,测试 1
api.replicasAPI 副本数
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.logDirectorySHS 扫描事件日志的目录,必须和提交时一致
spark.history.ui.portSHS Web 端口
spark.history.fs.update.interval多久刷新一次新事件
spark.history.retainedApplications保留多少个应用历史
fs.s3a.endpoint/access.key/secret.keySHS 读 S3 事件日志的凭证
fs.s3a.path.style.accessSeaweedFS 必须 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 BACKENDS Alive;
  • Harbor 200,能 push/pull;
  • DS 5 个组件 Running;
  • SHS 200。

下一步:Spark 与端到端。