跳转至

基于 MinIO 对象存储的 Vertica Eon on K8s 部署实战

作者:JiangChong | 撰写时间:2026年08月

部署环境Kubernetes kubeadm 生产部署实战 搭建的 7 节点 K8s 集群(3 control-plane + 4 worker,Debian 13.6,2核4G,40GiB 系统盘,Flannel VXLAN CNI)。本文带你在此环境上部署一套 3 节点的 Vertica Eon 集群,公共存储使用 MinIO。

0 本文概览

本文是 Vertica Eon 模式在 K8s 上的实战部署指南——从 MinIO 部署到 vsql 查询验证,每一步都是可执行的命令。

与系列中 Vertica on Kubernetes 部署指南 的 kind 入门教程不同:kind 教程用单机假环境让你「认识 Vertica Operator」,本文用 Kubernetes kubeadm 生产部署实战 的真实 K8s 生产集群 + MinIO(官方支持的 S3 兼容存储),让你「会部署 Eon 模式」。

前置知识Kubernetes 核心架构(K8s 组件)、Kubernetes 存储体系(PV/PVC/CSI)、Kubernetes 网络模型


1. 概念速览

1.1 架构概览

flowchart TB
    subgraph VPC["阿里云 ECS VPC (172.20.65.0/24)"]
        subgraph W1["k8s-w1 (172.20.65.69)"]
            P0["vertica-primary-0<br/>(depot: emptyDir)"]
        end
        subgraph W2["k8s-w2 (.74)"]
            P1["vertica-primary-1"]
        end
        subgraph W3["k8s-w3 (172.20.65.70)"]
            P2["vertica-primary-2<br/>(depot: emptyDir)"]
        end
        MinIO["MinIO(公共存储,S3 兼容)<br/>minio.minio.svc.cluster.local:9000<br/>/vertica-communal/"]
        W1 --> MinIO
        W2 --> MinIO
        W3 --> MinIO
    end

1.2 Vertica Eon 关键概念

  • Eon 模式:存算分离架构。数据持久化在对象存储(MinIO),计算节点(K8s Pod)从 MinIO 读取数据,可弹性扩缩。
  • Communal Storage:共享数据层,存所有数据。本文用MinIO(S3 兼容,Vertica 官方支持,部署在同一 K8s 集群内)。
  • Depot:每个节点的本地缓存,加速热点数据访问。Pod 删除后可重建,数据不丢失(源头在 MinIO)。
  • Catalog:元数据存储(表结构、权限等),对延迟极其敏感。Vertica Operator 自动管理。
  • 子集群(Subcluster):一组功能相同的节点。可以分 primary(读写)和 secondary(只读)子集群。
  • K-Safety:高可用机制。K-safety=1 表示任意 1 个节点故障不丢数据。

2. 前置准备

执行位置说明:除特别标注外,以下 kubectl 命令均在 k8s-cp1 上执行(Kubernetes kubeadm 生产部署实战 3.3 节已配置 $HOME/.kube/config)。

2.1 K8s 集群

节点 IP 角色 说明
k8s-w1 172.20.65.69 worker Vertica Pod 调度目标
k8s-w2 172.20.65.74 worker Vertica Pod 调度目标
k8s-w3 172.20.65.70 worker Vertica Pod 调度目标
k8s-w4 172.20.65.78 worker 2.3 节添加,满足 MinIO 4 节点要求
k8s-cp1 172.20.65.73 control-plane 不跑 Vertica(默认有 NoSchedule taint)
k8s-cp2 172.20.65.72 control-plane 不跑 Vertica
k8s-cp3 172.20.65.71 control-plane 不跑 Vertica

确认集群状态:kubectl get nodes 7 台全部 Ready

2.2 资源评估

本文环境(2核4G 40GiB): 以下是本文各组件的资源配置及各组件实际消耗。

每台 worker 运行 Request Limit 备注
OS + K8s(kubelet/containerd/Flannel) ~1.5 Gi / 0.3 核 固定开销
Vertica Pod 1 Gi / 200m 2 Gi / 1 核 启动峰值 ~1.5 Gi,稳态 ~512 Mi
MinIO Pod 256 Mi / 100m 512 Mi / 500m 分布式模式,I/O 轻量
合计 request ~1.25 Gi / 300m 调度无障碍
峰值(全到 limit 后 OOM 前) ~2.5 Gi 仍有 ~1.5 Gi 余量
磁盘 ~21 Gi(OS 8G + MinIO 5G + catalog 5G + 镜像 3G) 40G 盘余 ~10 Gi

结论:能跑通,但无余量。禁止开 Query Profiling、UDF 或大结果集查询。

生产最低配置(每个 Vertica 节点):

项目 最低 说明
CPU 4 核 至少 1 核给 OS/后台线程 + 3 核给 Vertica
内存 16 Gi MAXMEMORYSIZE 建议设为物理内存的 80%,实际可用约 13 Gi
数据盘 500 GB SSD depot + catalog 分盘部署更佳
网络 万兆 节点间 Spread 和公共存储 I/O 对延迟敏感

生产最佳实践:

项目 推荐 说明
CPU 8-16 核 高并发攒批/复杂分析查询受益明显
内存 32-64 Gi 查询内存与 depot 缓存共享,越大命中率越高
数据盘 1 TB+ NVMe depot 使用 NVMe 显著降低从公共存储回读的频率
网络 25 GbE+ Eon 模式严重依赖公共存储带宽
公共存储 MinIO 4 节点+ / 商业 S3 分布式 MinIO 生产推荐 ≥4 节点;商业 S3(AWS S3/阿里云 OSS)免运维
节点数 worker ≥ Vertica 节点数 + 2 预留故障替换和滚动升级余量

以上为 Vertica Eon on K8s 场景建议。Enterprise 模式不依赖公共存储,数据盘需更大(数据存本地),其他相近。

2.3 添加第4台 Worker

MinIO 分布式模式要求最少 4 个节点才能组成纠删码集群(官方要求)。当前只有 3 台 worker,先加一台。

参照 Kubernetes kubeadm 生产部署实战 加入节点流程,创建同规格 ECS(Debian 13.6,2核4G,40GiB),分配 IP 172.20.65.78,命名为 k8s-w4:

# ① 新节点上安装 containerd + kubeadm + kubelet(参考「Kubernetes kubeadm 生产部署实战」3.1、3.2 节)
# ② 从 k8s-cp1 获取 join 命令(token 过期则重新生成)
ssh k8s-cp1 sudo kubeadm token create --print-join-command
# ③ 在新节点执行 join
sudo kubeadm join 172.20.65.73:6443 --token <token> --discovery-token-ca-cert-hash <hash>
kubectl get nodes
# NAME       STATUS   ROLES           AGE
# k8s-w1     Ready    <none>          ...
# k8s-w2     Ready    <none>          ...
# k8s-w3     Ready    <none>          ...
# k8s-w4     Ready    <none>          10s   ← 新加入

4 台 worker 就绪后继续。

2.4 MinIO 准备(k8s-cp1)

MinIO 是 Vertica 官方支持的 S3 兼容对象存储。本文用 4 节点分布式模式部署在同一 K8s 集群内,数据通过纠删码分片到 4 台 worker 的本地磁盘,任意一台故障不影响服务。集群内延迟 <1ms。

分布式 MinIO 最少 4 节点,本文用 4 节点确保纠删码正常工作。

# ① 4 台 worker 上各创建数据目录
for host in k8s-w1 k8s-w2 k8s-w3 k8s-w4; do
  ssh $host mkdir -p /data/minio
done

# ② cp1 上:创建 Namespace + PV + StatefulSet
kubectl create namespace minio
kubectl apply -n minio -f - <<'EOF'
# 4 个 hostPath PV(每台 worker 一个)
apiVersion: v1
kind: PersistentVolume
metadata:
  name: minio-pv-0
spec:
  capacity: {storage: 5Gi}
  accessModes: [ReadWriteOnce]
  persistentVolumeReclaimPolicy: Retain
  storageClassName: ""
  hostPath: {path: /data/minio}
  nodeAffinity:
    required:
      nodeSelectorTerms:
      - matchExpressions:
        - {key: kubernetes.io/hostname, operator: In, values: [k8s-w1]}
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: minio-pv-1
spec:
  capacity: {storage: 5Gi}
  accessModes: [ReadWriteOnce]
  persistentVolumeReclaimPolicy: Retain
  storageClassName: ""
  hostPath: {path: /data/minio}
  nodeAffinity:
    required:
      nodeSelectorTerms:
      - matchExpressions:
        - {key: kubernetes.io/hostname, operator: In, values: [k8s-w2]}
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: minio-pv-2
spec:
  capacity: {storage: 5Gi}
  accessModes: [ReadWriteOnce]
  persistentVolumeReclaimPolicy: Retain
  storageClassName: ""
  hostPath: {path: /data/minio}
  nodeAffinity:
    required:
      nodeSelectorTerms:
      - matchExpressions:
        - {key: kubernetes.io/hostname, operator: In, values: [k8s-w3]}
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: minio-pv-3
spec:
  capacity: {storage: 5Gi}
  accessModes: [ReadWriteOnce]
  persistentVolumeReclaimPolicy: Retain
  storageClassName: ""
  hostPath: {path: /data/minio}
  nodeAffinity:
    required:
      nodeSelectorTerms:
      - matchExpressions:
        - {key: kubernetes.io/hostname, operator: In, values: [k8s-w4]}
---
# Headless Service(Pod 间 DNS 发现,StatefulSet 必需)
apiVersion: v1
kind: Service
metadata:
  name: minio-headless
spec:
  clusterIP: None
  ports:
  - name: api
    port: 9000
  selector:
    app: minio
---
# StatefulSet(4 节点分布式,Pod 反亲和自动分散到 4 台 worker)
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: minio
spec:
  serviceName: minio-headless
  replicas: 4
  podManagementPolicy: Parallel
  selector:
    matchLabels:
      app: minio
  template:
    metadata:
      labels:
        app: minio
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - {key: app, operator: In, values: [minio]}
            topologyKey: kubernetes.io/hostname
      containers:
      - name: minio
        image: minio/minio:latest
        args:
        - server
        - http://minio-{0...3}.minio-headless.minio.svc.cluster.local:9000/data
        - --console-address
        - :9001
        env:
        - name: MINIO_ROOT_USER
          value: admin
        - name: MINIO_ROOT_PASSWORD
          value: admin123456
        ports:
        - containerPort: 9000
          name: api
        - containerPort: 9001
          name: console
        resources:
          requests:
            cpu: 100m
            memory: 256Mi
          limits:
            cpu: 500m
            memory: 512Mi
        volumeMounts:
        - name: data
          mountPath: /data
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: [ReadWriteOnce]
      resources: {requests: {storage: 5Gi}}
      storageClassName: ""
---
# 常规 Service(Vertica 访问入口,负载均衡到 4 个 Pod)
apiVersion: v1
kind: Service
metadata:
  name: minio
spec:
  ports:
  - name: api
    port: 9000
  - name: console
    port: 9001
  selector:
    app: minio
EOF

分布式模式关键参数解释: - http://minio-{0...3}.minio-headless.minio.svc.cluster.local:9000/data{0...3} 是 MinIO 的端点展开语法(三个点),告诉 MinIO 集群有 4 个成员,数据通过纠删码分片存储。minio-headless 是 Headless Service 名(StatefulSet 依赖它提供稳定的 Pod DNS)。 - podManagementPolicy: Parallel — 4 个 Pod 同时启动(MinIO 分布式模式要求半数以上节点同时在线才能形成 Quorum)。 - podAntiAffinity — 确保每个 Pod 在不同 worker 上,挂掉一台不影响其他。

# ③ 等待 4 个 Pod 全部 Running 后创建 Bucket
kubectl get pod -n minio -o wide
# NAME      READY   STATUS    RESTARTS   AGE   IP           NODE     NOMINATED NODE   READINESS GATES
# minio-0   1/1     Running   0          70s   10.244.3.4   k8s-w1   <none>           <none>
# minio-1   1/1     Running   0          70s   10.244.4.6   k8s-w2   <none>           <none>
# minio-2   1/1     Running   0          70s   10.244.5.4   k8s-w3   <none>           <none>
# minio-3   1/1     Running   0          70s   10.244.6.4   k8s-w4   <none>           <none>
kubectl wait --for=condition=Ready pod -l app=minio -n minio --timeout=120s
kubectl exec -n minio minio-0 -- sh -c '
  mc alias set local http://localhost:9000 admin admin123456
  mc mb local/vertica-communal
'

# ④ 创建 MinIO 凭证 Secret(VerticaDB 引用)
kubectl create namespace vertica 2>/dev/null || true
kubectl create secret generic vertica-minio-creds \
  --namespace vertica \
  --from-literal=accesskey=admin \
  --from-literal=secretkey=admin123456

MinIO 集群内访问延迟 <1ms(对比 OSS VPC 内网约 38ms),Bootstrap 从 >5 分钟降到 30 秒以内,不会被 NMA 探针超时打断。

MinIO 的 endpoint 是如何构成的? 上面部署完 MinIO 后,Vertica 连接时填的地址是 http://minio.minio.svc.cluster.local:9000。这个地址由 K8s DNS 依据 Service 定义自动生成,不需要手动配置,公式为:

<service-name>.<namespace>.svc.<cluster-domain>:<port>
本文值 来源字段
service-name minio Service metadata.name
namespace minio kubectl create namespace minio + kubectl apply -n minio
svc.cluster.local 固定 K8s 默认集群域名(生产环境可能是 svc.{自定义域名}
port 9000 Service spec.ports[].port(MinIO API 端口,非 console 9001)

换到你的环境时,只需把 service-namenamespace 换成你的值即可。

2.5 镜像中转(Docker Hub → 阿里云 ACR)

Vertica 被 OpenText 收购后,Operator 和软件镜像均在 Docker Hub opentext/ 命名空间下。Docker Hub 国内直连超时,DaoCloud 不代理该命名空间(403 Forbidden)。通过一台能访问 Docker Hub 的节点 + crane(Google 开源的镜像复制工具,单二进制文件,无需 Docker daemon)直接复制到 ACR。

① 创建 ACR 实例:阿里云控制台 → 容器镜像服务 ACR → 创建实例(个人版即可,免费)→ 创建命名空间(如 vertica)→ 访问凭证 → 设置固定密码。

② 在能访问 Docker Hub 的节点上执行(已开通公网 IP 的 ECS 或能科学上网的 Mac;Mac 用公网地址,ECS 可改用 -vpc 内网地址更快):

# 安装 crane(单文件 10MB,无需 Docker)
curl -fsSL https://github.com/google/go-containerregistry/releases/latest/download/go-containerregistry_Linux_x86_64.tar.gz \
  | tar xz -C /usr/local/bin crane
chmod +x /usr/local/bin/crane

# 登录 ACR(地址在 ACR 控制台 → 实例概览 查看)
crane auth login crpi-<acr-id>.cn-wulanchabu.personal.cr.aliyuncs.com \
  -u <阿里云账号> -p <ACR实例密码>

# 复制镜像——crane 直接从 Docker Hub 拉到 ACR,不占用本地磁盘
crane copy docker.io/opentext/verticadb-operator:25.4.0-0 \
  crpi-<acr-id>-vpc.cn-wulanchabu.personal.cr.aliyuncs.com/vertica/verticadb-operator:25.4.0-0

crane copy docker.io/opentext/vertica-k8s:25.4.0-0 \
  crpi-<acr-id>-vpc.cn-wulanchabu.personal.cr.aliyuncs.com/vertica/vertica-k8s:25.4.0-0

opentext/vertica-k8s 镜像较大(~550MB),约需 3-5 分钟。如遇 UNAUTHORIZED,用 docker login 登录 Docker Hub 后重试(部分仓库需登录才能拉取)。

2.6 安装 VerticaDB Operator(k8s-cp1)

# ⚠️ vertica/charts 和 vertica/vertica-kubernetes 是不同仓库,版本号不同步:
#    CRD: charts v26.2.1-0 | operator.yaml: vertica-kubernetes v25.4.0-0
CRD_VER="v26.2.1-0"
OP_VER="v25.4.0-0"

# 下载 operator.yaml 到本地
curl -fsSL -o /tmp/operator.yaml \
  https://github.com/vertica/vertica-kubernetes/releases/download/${OP_VER}/operator.yaml

# 替换镜像为 ACR 地址
sed -i "s|image: 'docker.io/opentext/verticadb-operator:[^']*'|image: 'crpi-<acr-id>-vpc.cn-wulanchabu.personal.cr.aliyuncs.com/vertica/verticadb-operator:25.4.0-0'|" /tmp/operator.yaml

# 安装(先 CRD 后 Operator)
kubectl apply --server-side=true --force-conflicts \
  -f https://github.com/vertica/charts/releases/download/${CRD_VER}/crds.yaml
# customresourcedefinition.apiextensions.k8s.io/eventtriggers.vertica.com serverside-applied
# customresourcedefinition.apiextensions.k8s.io/vclusterservers.vertica.com serverside-applied
# customresourcedefinition.apiextensions.k8s.io/verticaautoscalers.vertica.com serverside-applied
# customresourcedefinition.apiextensions.k8s.io/verticadbs.vertica.com serverside-applied
# customresourcedefinition.apiextensions.k8s.io/verticareplicators.vertica.com serverside-applied
# customresourcedefinition.apiextensions.k8s.io/verticarestorepointsqueries.vertica.com serverside-applied
# customresourcedefinition.apiextensions.k8s.io/verticascrutinizers.vertica.com serverside-applied

kubectl get crd | grep vertica
# eventtriggers.vertica.com                 2026-08-11T06:55:54Z
# vclusterservers.vertica.com               2026-08-11T06:55:54Z
# verticaautoscalers.vertica.com            2026-08-11T06:55:54Z
# verticadbs.vertica.com                    2026-08-11T06:55:54Z
# verticareplicators.vertica.com            2026-08-11T06:55:54Z
# verticarestorepointsqueries.vertica.com   2026-08-11T06:55:54Z
# verticascrutinizers.vertica.com           2026-08-11T06:55:55Z

kubectl apply -f /tmp/operator.yaml

crds.yaml 是做什么的? K8s 原生只认识 Pod、Service、Deployment 等内置资源。crds.yaml 向集群注册 VerticaDB 这个新资源类型(CustomResourceDefinition),之后 kubectl apply -f vertica-eon.yaml(kind: VerticaDB)才能被 K8s 识别。如果不先装 CRD,K8s 会直接报错:不认识 kind: VerticaDB

这里用 --server-side=true 而非普通的 kubectl apply,是因为 CRD 的 OpenAPI schema 定义很长,标准 apply 会将其存为 last-applied-configuration annotation,容易超出 annotation 的 256KB 限制。--server-side=true 将配置存储在 etcd 的 managedFields 中,不受此限。

CRD 和 Operator/镜像版本不一致会有问题吗? 不会——两者的版本号本来就不对应,因为它们来自不同仓库:

组件 仓库 职责
crds.yaml vertica/charts 定义 SchemaVerticaDB 有哪些字段、字段类型是什么
operator.yaml + 镜像 vertica/vertica-kubernetes 定义 行为:Controller 监听 VerticaDB 资源并创建数据库

可以理解为 CRD 是"合同模板",Operator 是"执行者"。只要合同上的字段执行者都认识,就没问题。实际操作中遵循一条原则:CRD 版本不要低于 Operator 版本。因为新版 Operator 可能引用新版 CRD 才有的字段,旧 CRD 不认识会导致 apply 报错。反之(CRD 比 Operator 新)是安全的——CRD 里多出的字段 Operator 不用就是了。

本文的组合 charts v26.2.1-0 + operator v25.4.0-0 是实际验证过的可用搭配,直接使用即可。如果你需要更换版本,先确认两个仓库各自的 release notes。

# 验证
kubectl get pods -n verticadb-operator -o wide
# NAME                                         READY   STATUS    RESTARTS   AGE   IP           NODE     NOMINATED NODE   READINESS GATES
# verticadb-operator-manager-b75f7975b-npb7k   1/1     Running   0          16m   10.244.3.7   k8s-w1   <none>           <none>

3. 部署 Vertica Eon 集群

3.1 创建 MinIO 凭证 Secret(已在 2.4 节创建,确认即可)

kubectl get secret vertica-minio-creds -n vertica

3.2 创建 dbadmin 密码 Secret

# 生产环境请替换为强密码
kubectl create secret generic vertica-db-password \
  --namespace vertica \
  --from-literal=password=Vertica123

3.3 Vertica 本地存储 PV

Eon 模式下 local 盘(catalog + depot)是缓存——数据源头在 MinIO,丢了也不影响数据完整性。但 VerticaDB Operator 始终为 local 创建 PVC,不提供 emptyDir 选项,需要在集群没有 StorageClass 时预建 hostPath PV。

# 在 3 台 worker 上创建数据目录
for host in k8s-w1 k8s-w2 k8s-w3; do
  ssh $host 'mkdir -p /data/vertica-local && chown 5000:5000 /data/vertica-local'
done

kubectl apply -f - <<'EOF'
apiVersion: v1
kind: PersistentVolume
metadata:
  name: vertica-local-pv-0
spec:
  capacity: {storage: 5Gi}
  accessModes: [ReadWriteOnce]
  persistentVolumeReclaimPolicy: Retain
  storageClassName: ""
  hostPath: {path: /data/vertica-local}
  nodeAffinity:
    required:
      nodeSelectorTerms:
      - matchExpressions:
        - {key: kubernetes.io/hostname, operator: In, values: [k8s-w1]}
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: vertica-local-pv-1
spec:
  capacity: {storage: 5Gi}
  accessModes: [ReadWriteOnce]
  persistentVolumeReclaimPolicy: Retain
  storageClassName: ""
  hostPath: {path: /data/vertica-local}
  nodeAffinity:
    required:
      nodeSelectorTerms:
      - matchExpressions:
        - {key: kubernetes.io/hostname, operator: In, values: [k8s-w2]}
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: vertica-local-pv-2
spec:
  capacity: {storage: 5Gi}
  accessModes: [ReadWriteOnce]
  persistentVolumeReclaimPolicy: Retain
  storageClassName: ""
  hostPath: {path: /data/vertica-local}
  nodeAffinity:
    required:
      nodeSelectorTerms:
      - matchExpressions:
        - {key: kubernetes.io/hostname, operator: In, values: [k8s-w3]}
EOF

hostPath PV 的局限:节点故障后 PVC 绑死在坏节点上,需手动删 PVC 让 Pod 漂移到备用节点(6.2 节详述)。生产环境推荐云盘 CSI(ESSD/EBS),PV 与节点解耦,故障自动漂移。

方案 故障漂移 配置复杂度 适用
hostPath PV ❌ 手动 学习/测试
云盘 CSI ✅ 自动 需 StorageClass 生产

3.4 编写 VerticaDB CR

cat > /tmp/vertica-eon.yaml <<'EOF'
apiVersion: vertica.com/v1
kind: VerticaDB
metadata:
  name: vertica-eon
  namespace: vertica
spec:
  # 镜像:2.5 节已推送到 ACR
  image: crpi-<acr-id>-vpc.cn-wulanchabu.personal.cr.aliyuncs.com/vertica/vertica-k8s:25.4.0-0
  imagePullPolicy: IfNotPresent

  initPolicy: Create

  # === 公共存储(MinIO)===
  communal:
    path: "s3://vertica-communal/vertica"
    endpoint: "http://minio.minio.svc.cluster.local:9000"
    credentialSecret: vertica-minio-creds

  local:
    requestSize: "5Gi"

  passwordSecret: vertica-db-password

  subclusters:
  - name: primary
    type: primary
    size: 3
    serviceType: ClusterIP
    resources:
      requests:
        cpu: 200m
        memory: 1Gi
      limits:
        cpu: "1"
        memory: 2Gi
EOF

⚠️ v1 API 与 v1beta1 有多处字段变化:kSafety 不再需要(由子集群 size 隐式决定)、volumes/volumeMounts 改为 depotSizeincludeUIDInPathsubclusterTerminationPolicy 已移除。Pod 反亲和在 v1 中由 Operator 自动管理。

关键字段说明:

字段 说明
communal.path s3://vertica-communal/vertica MinIO Bucket 的 S3 路径
communal.endpoint http://minio.minio.svc.cluster.local:9000 MinIO 集群内 Service,延迟 <1ms
communal.credentialSecret vertica-minio-creds 2.4 节创建的 Secret(admin/admin123456)
local.requestSize 5Gi catalog 目录大小(测试环境 <200MB,5Gi 足够)
subclusters[0].size 3 3 节点 = K-safety=1 的最低要求

3.5 部署

kubectl apply -f /tmp/vertica-eon.yaml

# 观察 Pod 创建过程(约 3-5 分钟首次启动)
kubectl get pods -n vertica -w
# vertica-eon-primary-0   0/1   Pending             0     0s
# vertica-eon-primary-0   0/1   ContainerCreating   0     5s
# vertica-eon-primary-0   1/1   Running             0     90s
# vertica-eon-primary-1   0/1   Pending             0     0s
# ...

3.6 验证集群就绪

# ① 看 VerticaDB 状态
kubectl get vdb -n vertica
# NAME          SUBCLUSTERS   VERSION     READY   AGE
# vertica-eon   1             v25.4.0-0   3/3     5m22s

# ② 进入 Pod 看节点状态(登进来是 root,server 进程以 dbadmin 运行,vsql 等工具不限制身份)
kubectl exec -it vertica-eon-primary-0 -n vertica -c server -- bash
bash-5.1$ vsql -Udbadmin -wVertica123 -c "SELECT node_name, node_state FROM nodes;"
#      node_name     | node_state
# -------------------+------------
#  v_vertdb_node0001 | UP
#  v_vertdb_node0002 | UP
#  v_vertdb_node0003 | UP

bash-5.1$ vsql -Udbadmin -wVertica123 -c "SELECT version();"
# Vertica Analytic Database v25.4.0-0

bash-5.1$ exit

如果节点显示 DOWN,等 1-2 分钟再试——节点首次从 MinIO 加载 catalog 需要时间。

3.7 创建测试数据

kubectl exec -it vertica-eon-primary-0 -n vertica -c server -- vsql -Udbadmin -wVertica123 <<'SQL'
CREATE TABLE test_eon (id INT, val VARCHAR(100));
INSERT INTO test_eon SELECT n, 'row_' || n FROM (SELECT ROW_NUMBER() OVER () AS n FROM columns) t LIMIT 100;
SELECT COUNT(*) FROM test_eon;
SQL

4. 存储架构

4.1 三层存储

flowchart TB
    subgraph S1["MinIO(Communal Storage)|主数据持久化"]
        M1["所有数据在 MinIO 上持久化"]
        M2["各 Pod 共享同一 Bucket"]
        M3["S3 兼容协议,内网访问免流量费"]
    end
    subgraph S2["emptyDir — depot(本地缓存)|读写缓存"]
        D1["从 MinIO 缓存热点数据"]
        D2["Pod 删除后丢失(可从 MinIO 重建)"]
    end
    subgraph S3["local PVC — catalog(本地目录)|元数据"]
        C1["数据库元数据、schema 定义"]
        C2["Vertica Operator 自动管理 PVC"]
    end
    S1 ~~~ S2
    S2 ~~~ S3

4.2 MinIO 配置要点

配置 推荐值 原因
endpoint http://minio.minio.svc.cluster.local:9000 集群内 Service,延迟 <1ms
协议 HTTP(同集群) 集群内无需 TLS,MinIO 默认 HTTP 更快
Bucket 权限 私有 + accesskey/secretkey 2.4 节创建的凭证(admin / admin123456)

公共存储的替代方案:Ceph RGW。 生产私有云也可以用 Ceph 的对象网关(RGW,S3 兼容)替代 MinIO 做公共存储——v26.2 官方文档 Supported Platforms 中有专门的 CEPH 章节(Red Hat Ceph Storage 渠道认证)。好处是与 Ceph RBD 块存储共用一套集群(见 4.3 一体化方案);代价是 RGW 的 bucket index 对海量小 IO 敏感,性能调优面比 MinIO 大,上线前必须做性能验收。公共存储是 Eon 的命根子,更换供应商前先确认 Vertica 支持范围与实际 IO 压测结果。

4.3 本地存储选型

方案 故障漂移 配置复杂度 适用场景
hostPath PV ❌ 手动删 PVC 重绑(6.2 节) 学习/测试(本文方案;depot 还可直接用 emptyDir,丢了不影响)
云盘 CSI(ESSD/EBS) ✅ 自动 中(需 StorageClass) 生产·公有云
Ceph RBD + RGW 一体化 ✅ 自动 高(需 Ceph 集群) 生产·私有云(已有 Ceph 运维体系)

Ceph 一体化方案(私有云生产推荐之一):Ceph RBD 做 local 盘(块存储)+ Ceph RGW 做公共存储(S3 兼容,见 4.2)——一套集群通吃,运维面统一。RBD 是网络块存储,PV 与节点解耦:节点故障后 Pod 漂移到备用节点,CSI 自动 detach/attach 同一个卷,不需要 6.2 节的手动删 PVC 操作,且 catalog 缓存不丢、恢复更快。注意两点:

  • RBD 是 RWO(同一时刻单节点挂载,可跨节点移动),正符合 local 盘需求;极端情况下卷卡住的手工处理发生在 Ceph 层面(rbd unmap/强制 detach),不是删 PVC
  • depot 是本地缓存,放 RBD 后读写走网络,与 RGW 公共存储共享带宽,生产需按 2.2 节配置做性能评估;学习环境(2核4G)扛不动 Ceph,故本文用 hostPath

架构权衡:全 Ceph 一体化后,公共存储与块存储共享同一故障域——Ceph 集群级故障会让两者同时不可用。若无成熟 Ceph 运维团队,更稳妥的组合是「MinIO + 云盘 CSI」(故障域隔离)。


5. 网络设计

在 VPC 内网 + Flannel VXLAN 环境下,Vertica 网络配置已足够简洁:

  • Pod 间通信:Flannel 提供跨节点 Pod 互通,Vertica 节点间通过 Pod IP 和 Service FQDN 通信
  • Vertica 端口:5433(客户端)、5434(节点间)、5444(Spread)

Operator 默认创建 ClusterIP Service,仅集群内可访问。要外部连接,需要额外创建一个固定端口的 NodePort Service,与 Operator 管理的 Service 分离:

kubectl apply -f - <<'EOF'
apiVersion: v1
kind: Service
metadata:
  name: vertica-vip
  namespace: vertica
spec:
  type: NodePort
  ports:
  - name: vsql
    port: 5433
    nodePort: 31421
  selector:
    app.kubernetes.io/instance: vertica-eon
    vertica.com/subcluster-svc: primary
EOF

💡 为什么单独建 Service? 如果通过修改 CR 的 serviceType 切换 NodePort,端口号由 K8s 随机分配且可能被 Operator 覆盖。独立 Service 端口固定 31421,重建多少次都不变。

⚠️ selector 字段容易写错:Pod 上 Operator 注入的标签是 vertica.com/subcluster-svc(带 -svc 后缀),不是 vertica.com/subcluster。写错会导致 endpoints 为空、端口不监听。

不知道用哪个标签时,两种方式确认:

# 方法一:直接看 Pod(什么标签、什么值一目了然)
kubectl get pod -n vertica -l app.kubernetes.io/instance=vertica-eon --show-labels

# 方法二:看 Operator 自建 Service 的 selector(它配的一定对)
kubectl get svc vertica-eon-primary -n vertica -o jsonpath='{.spec.selector}'
# {"app.kubernetes.io/instance":"vertica-eon","vertica.com/client-routing":"true","vertica.com/subcluster-svc":"primary"}

kubectl get svc -n vertica
# NAME                  TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)                               AGE
# vertica-eon           ClusterIP   None         <none>        5434/TCP,4803/TCP,8443/TCP,5554/TCP   80m
# vertica-eon-primary   ClusterIP   10.0.0.170   <none>        5433/TCP,8443/TCP                     80m
# vertica-vip           NodePort    10.0.0.169   <none>        5433:31421/TCP                        3m38s    ← 新加

从集群外任意机器连接:

# 直连 worker IP + NodePort
vsql -U dbadmin -w Vertica123 -h 172.20.65.69 -p 31421

生产推荐:SLB VIP 统一入口。 记多个 worker IP 不够稳定(节点可能更换),通过 SLB(阿里云负载均衡)挂一个 VIP 统一访问:

  1. 阿里云 SLB 控制台 → 监听管理 → 添加 TCP 监听器,端口 5433
  2. 后端服务器选 k8s-w1/w2/w3/w4,端口填上面分配的 NodePort(如 31421
  3. 健康检查选 TCP,端口 5433

之后只需记一个 VIP:

vsql -U dbadmin -w Vertica123 -h 172.20.65.75 -p 5433

SLB 自动剔除不健康的 worker,相比直连 NodePort 多了故障自动切换能力。


6. 高可用设计

6.1 K-safety 与 Pod 反亲和

v1 API 中 K-safety 由子集群 size 隐式决定(primary ≥ 3 = K-safety=1),Pod 反亲和由 Operator 自动管理,CR 中无需手动配置。本文 CR 中 size: 3 确保 3 个 Pod 分布在 3 台 worker 上——如果 worker 数量不足,Pod 将无法调度。

6.2 节点故障恢复

节点宕机后的 K8s 调度链:~40s 标记 NotReady → ~5m40s Pod 驱逐 → 调度器尝试重建。hostPath PV 的 PVC 绑定在坏节点上会阻止自动恢复:

flowchart LR
    A["Pod 被驱逐"] --> B["StatefulSet 重建 Pod"] --> C["Pod 尝试挂载同名 PVC"] --> D["PVC 仍绑定坏节点的 PV<br/>(nodeAffinity 指向宕机的节点)"] --> E["调度失败 ❌"]

需要手动删 PVC 释放旧绑定,让新 PVC 绑到备用节点的 PV(k8s-w4 的 vertica-local-pv-3):

kubectl delete pvc local-data-vertica-eon-primary-<N> -n vertica
# 有时还需清理 PV 的 claimRef
kubectl patch pv vertica-local-pv-<N> --type=json \
  -p '[{"op":"remove","path":"/spec/claimRef"}]'

Eon 模式的核心保障:不管哪种方案,数据不会丢——所有数据都在 MinIO 公共存储上。local 盘只是缓存,重建不改变数据完整性。

💡 什么时候不需要本节手动操作?local 盘用的是网络块存储(云盘 CSI 的 ESSD/EBS,或 Ceph RBD),PV 与节点解耦——Pod 漂移到新节点后 CSI 自动把同一个卷 detach/attach 过去,PVC 全程不用动,且 catalog 缓存保留、恢复更快。手动删 PVC 只是 hostPath 本地盘的特有操作(见 4.3 方案对比)。

6.3 PodDisruptionBudget

PDB 是什么? 当 K8s 节点需要维护(升级、缩容)时,kube-controller 会驱逐节点上的 Pod。如果没有 PDB,K8s 可能一次性驱逐多个 Vertica Pod,导致 K-safety=1 的集群丢 Quorum 不可用。PDB 的作用就是给 K8s 一个约束:「这批 Pod 最少要保持几个存活,驱逐时逐个来」。

本例中 minAvailable: 2 表示:3 节点集群在任何时刻最多只允许 1 个 Pod 被主动驱逐,始终保持至少 2 个节点在线。

注意:PDB 只约束主动驱逐(节点维护、kubectl drain 等),不约束节点宕机这类被动故障。后者由 6.2 节的自动调度恢复处理。

kubectl create -f - <<EOF
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: vertica-eon-pdb
  namespace: vertica
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app.kubernetes.io/instance: vertica-eon
EOF

7. Day 2 运维

为什么叫 Day 2? 这是运维领域的术语,把软件生命周期分为三个阶段:

阶段 含义 本文对应
Day 0 规划选型
Day 1 部署上线 第 2-6 节(把系统跑起来)
Day 2 持续运维 第 7 节(扩缩容、升级、备份、License)

部署是一次性的,运维占系统生命周期的 95% 以上。Day 2 才是真正考验系统成熟度的阶段。

7.1 安装 License

⚠️ 26.1 版本重要变更:从 26.1 开始,社区版(CE)不再提供, 25.4 及之前的版本为没有30天时间限制的 CE License。CE 数据库升级到 26.1+ 会直接锁定,必须迁移到商业 License。新安装的评估用户为 30 天试用 License官方文档)。

试用 License 的限制:

  • 最多 3 节点、1TB 原始数据
  • 有效期 30 天(从安装之日算起)
  • 仅限全新安装,不能用于已有 CE 数据库
  • 过期后数据库锁定,直到安装商业 License

获取方式:访问 OpenText Analytics Database Trial 注册账号,按邮件指引激活。

企业版需要商业 License 才能使用全部功能(无节点数/容量限制/不绑定机器)。以下步骤对试用 License 和企业 License 均适用——都是通过 Secret + CR 引用完成。

① 创建 License Secret

# 将 Vertica 提供的 license 文件(如 vertica_license.xml)创建为 Secret
kubectl create secret generic vertica-license \
  --namespace vertica \
  --from-file=license.dat=./vertica_license.xml

文件名必须为 license.dat——这是 VerticaDB Operator 读取 Secret 时的约定 key 名。

② 在 VerticaDB CR 中引用

spec:
  licenseSecret: vertica-license   # Secret 名称

③ 生效方式

⚠️ licenseSecret 仅在 initPolicy: Create(首次创建数据库)时由 Operator 自动安装。数据库已运行的情况下,patch CR 只是注册 Secret,不会自动触发安装,需要手动执行。

# 将 license 文件拷入 Pod 并手动安装
kubectl cp vertica_license.xml vertica/vertica-eon-primary-0:/tmp/vertica_license.xml -c server
kubectl exec -it vertica-eon-primary-0 -n vertica -c server -- \
  vsql -Udbadmin -wVertica123 -c "select install_license('/tmp/vertica_license.xml');"

同时在 CR 中注册(后续新建子集群或重建时自动使用):

kubectl patch vdb vertica-eon -n vertica \
  --type=merge \
  --patch='{"spec":{"licenseSecret":"vertica-license"}}'

验证:

kubectl exec -it vertica-eon-primary-0 -n vertica -c server -- \
  vsql -Udbadmin -wVertica123 -c "select display_license();"

注意:试用 License 过期前务必安装商业 License,否则数据库会锁定。商业 License 安装后不可降回试用/CE 版。升级前确认 License 有效期覆盖整个运维窗口。

7.2 添加 Worker 节点

k8s-w4 已在 2.3 节添加完毕(用于 MinIO 分布式集群)。当前 4 台 worker 中 3 台跑 Vertica,1 台空闲——下面 7.3 节可直接测试扩容到 4 节点。如需更多 worker(例如扩容到 5+ 节点或替换故障节点),参照 2.3 节流程重复操作即可。

7.3 扩缩容

⚠️ 扩容前确保目标节点有 Vertica 本地存储 PV。 每增加一个 Pod,Operator 自动创建一个 PVC(绑定 3.3 节创建的 PV)。

# 在新增 worker 上创建 `/data/vertica-local` + PV
ssh k8s-w4 'mkdir -p /data/vertica-local && chown 5000:5000 /data/vertica-local'

kubectl apply -f - <<'EOF'
apiVersion: v1
kind: PersistentVolume
metadata:
  name: vertica-local-pv-3
spec:
  capacity: {storage: 5Gi}
  accessModes: [ReadWriteOnce]
  persistentVolumeReclaimPolicy: Retain
  storageClassName: ""
  hostPath: {path: /data/vertica-local}
  nodeAffinity:
    required:
      nodeSelectorTerms:
      - matchExpressions:
        - {key: kubernetes.io/hostname, operator: In, values: [k8s-w4]}
EOF

# 扩容(社区版限制 3 节点,企业版可超过。本例仅示意 API 用法)
kubectl patch vdb vertica-eon -n vertica \
  --type=merge \
  --patch='{"spec":{"subclusters":[{"name":"primary","size":4}]}}'

kubectl get pod -n vertica
# NAME                    READY   STATUS    RESTARTS      AGE
# vertica-eon-primary-0   2/2     Running   0             71m
# vertica-eon-primary-1   2/2     Running   1 (70m ago)   71m
# vertica-eon-primary-2   2/2     Running   1 (70m ago)   72m
# vertica-eon-primary-3   2/2     Running   0             47s

# 添加只读子集群(生产读写分离推荐)
kubectl patch vdb vertica-eon -n vertica \
  --type=merge \
  --patch='{"spec":{"subclusters":[{"name":"analytics","type":"secondary","size":2}]}}'

缩容同理,改小 size 即可。Operator 会先驱逐目标 Pod、从 Vertica 集群中移除节点、再删 Pod:

# 缩回 3 节点
kubectl patch vdb vertica-eon -n vertica \
  --type=merge \
  --patch='{"spec":{"subclusters":[{"name":"primary","size":3}]}}'

kubectl get pod -n vertica -w
# vertica-eon-primary-3   Terminating  → 消失

Eon 模式下缩容不丢数据——数据在 MinIO 上,被移除的 Pod 只是释放了 depot 缓存和本地 catalog。

7.4 升级 Vertica 版本

# 升级 Operator:重新 apply 新版 YAML(kubectl 方式,同 2.6 节)
kubectl apply \
  -f https://github.com/vertica/vertica-kubernetes/releases/download/v<新版本>/operator.yaml

# 升级 Vertica 软件版本(滚动升级,逐个节点替换)
kubectl patch vdb vertica-eon -n vertica \
  --type=merge --patch='{"spec":{"image":"crpi-<acr-id>-vpc.cn-wulanchabu.personal.cr.aliyuncs.com/vertica/vertica-k8s:<新版本>"}}'

7.5 备份到 MinIO

opentext/vertica-k8s 镜像做了精简,vbr 工具不在其中。Eon 模式下数据本身就持久化在 MinIO 上,备份策略分两层:

① DDL 导出(表结构/权限/序列等元数据):

kubectl exec -it vertica-eon-primary-0 -n vertica -c server -- \
  vsql -U dbadmin -w Vertica123 -c "SELECT EXPORT_OBJECTS('', '');"

② 数据层备份(直接复制 MinIO Bucket):

kubectl exec -n minio minio-0 -- sh -c "
  mc alias set local http://localhost:9000 admin admin123456
  BUCKET=vertica-backup-\$(date +%Y%m%d)
  mc mb local/\$BUCKET
  mc mirror local/vertica-communal local/\$BUCKET
"

Eon 模式的恢复只需在新集群的 communal.path 指向备份 Bucket 复活集群即可。


8. 与 kind 教程的对比

维度 Vertica on Kubernetes 部署指南(kind) 本文(阿里云 Eon 实战)
K8s 环境 kind 单机容器 7 节点 kubeadm 生产集群
对象存储 Eon 模式(hostPath 本地目录) MinIO(Eon 模式)
存储 hostPath hostPath + MinIO(可替换 ESSD 云盘)
部署方式 CR(kubectl apply kubectl apply + CR
网络 NodePort(kind 端口映射) Flannel VXLAN + ClusterIP
高可用 K-safety + Pod 反亲和 + PDB
数据持久化 节点本地 MinIO 公共存储,Pod 可任意重建

9. 总结

本文在 Kubernetes kubeadm 生产部署实战 搭建的 7 节点集群上,完成了 Vertica Eon on K8s 的完整部署流程:

  1. MinIO 准备 → 部署 MinIO + 创建 Bucket + 凭证
  2. K8s 资源 → Secret(凭证/密码) + VerticaDB CR
  3. 部署验证kubectl apply + vsql
  4. 运维操作 → 扩缩容、升级、备份

关键差异在于:Eon 模式的数据持久化完全依赖 MinIO 公共存储,K8s 节点上的 depot 只是缓存——这使 Pod 迁移和弹性扩缩成为可能,是「数据库 on K8s」的正确打开方式。

延伸阅读:Kubernetes kubeadm 生产部署实战(集群搭建)、Vertica on Kubernetes 部署指南(kind 入门)、Kubernetes 存储体系(PV/PVC/CSI)。

扩展阅读