最近把一套用户行为分析用的 ClickHouse 从手工脚本挪到了 Nomad 上,顺带把给 ClickHouse 供数的实时任务也一起收编进 Job 体系。这个项目在内部就叫“Nomad 组件部署 clickhouse-job”,听起来很绕,拆开其实就三件事:ClickHouse 服务本身要容器化托管、Kafka 到 ClickHouse 的实时管道要能被 Nomad 调度、日常清理和运维操作要能自动化。这篇文章会把这套东西从选型到落地的全过程讲一遍,包括 HCL 怎么写、内存和存储怎么规划、Flink 任务怎么优雅停止,尤其是报错can not stop with a savepoint job这个坑,我会把定位思路和处理方案完整写出来。正在从手工运维切换到 Nomad、或者打算用轻量调度器托管有状态数据服务的同学,这份经验可以直接参考。
1. 项目整体设计与技术选型思路
1.1 为什么在 ClickHouse 场景选 Nomad 而不是 Kubernetes
先说选型。团队里不缺会用 K8s 的人,我们也不是不会用,但评估完这个项目的真实体量之后,我还是坚持上了 Nomad。原因不复杂:我们需要的是一个能把进程管起来、能自动恢复、能滚动发布、能和 Consul 打通服务发现的调度器,而不是一个完整的容器管理全家桶。Nomad 最吸引我的地方是单个二进制文件就能把 Server 和 Client 都跑起来,没有 etcd、没有 apiserver、没有网络插件这一堆前置依赖,两三个节点的小集群从装机到跑第一个 job 半小时内就能搞定。ClickHouse 在这套体系里是一个有状态的分析数据库,它不像微服务那样需要频繁伸缩,也不需要 K8s 里那套复杂的 network policy、ingress、HPA 能力,反而对“数据目录持久化”“固定端口”“内存硬限制”这些朴素需求更敏感,而这恰恰是 Nomad 能清晰表达的东西。
Kubernetes 当然有它的优势,比如生态完善、社区资料多、招聘时容易找人手,但对应的代价是集群本身的运维成本和故障面也被放大了。我们之前出现过一次 K8s 节点 NotReady 导致大量 Pod 被驱逐的事故,复盘时发现其实业务根本不需要那么动态的调度。Nomad 的调度模型更直接:它把 CPU、内存、端口、磁盘当作资源维度,把 job 定义成 service 或 batch,一个大括号写清楚跑什么、跑几个、放哪类节点、挂了怎么处理。对于 ClickHouse 这种组件,简单反而意味着可预测。我在这套方案里把 ClickHouse 服务、Flink 供数任务、定时清理任务统一成三个独立 job,让它们各自拥有生命周期,这在后面排障时帮了大忙。
1.2 拆解“clickhouse-job”的三层组成
标题里的“clickhouse-job”容易让人以为只要部署一个服务就完事,真正做起来会发现是一个组合:长驻服务、流式任务、定时任务,三种形态差别不小,必须分开对待。
第一层是 ClickHouse server 本体,它需要 7x24 小时在线,属于 service 类型 job。这一层决定稳定性,配置重点在持久化、内存上限、健康检查和滚动升级策略。第二层是实时数据管道,我们的场景是 Flink 消费 Kafka 里的行为事件,清洗后批量写入 ClickHouse。这个任务也是长驻的,但从故障域和迭代节奏看,它和 ClickHouse server 完全是两码事:Flink 作业可能一天改两版,ClickHouse 一个月升一次级,混在一个 job 里会让更新互相绑架。第三层是每天凌晨清理过期分区的定时任务,用 batch 类型加 periodic 调度正合适,跑完即焚,不需要保持实例。
有人可能会想,既然都是 Nomad job,为什么不把所有 task 塞到一个 job 文件里统一管理?我第一版确实这么干过,结果升级 ClickHouse 的时候把管道任务也波及了。拆开之后每个 job 独立执行nomad job run、独立回滚、独立看日志,出问题时用nomad job status一眼就能定位是哪个 allocation 出了问题。所以我的建议很明确:按生命周期和故障域拆 job,不要按机器归属拆。
1.3 完整数据链路与资源规划
这条链路从业务侧看很简单:业务应用产生事件 → 写入 Kafka → Flink 作业消费并清洗 → 批量写入 ClickHouse → 报表查询直接读 ClickHouse 宽表。落地到 Nomad 集群里,资源规划却有几个容易被忽略的点。
ClickHouse 是典型的 IO 和内存敏感型应用,生产环境我不想让它和一堆无状态容器混跑在宿主机核心业务上。我给 ClickHouse 单独规划了节点组,数据盘用 SSD,挂载路径统一放在/srv/nomad/volumes下。内存这块,ClickHouse 官方镜像默认会自动探测宿主机内存并吃掉大半,这在容器里非常危险,必须显式限制。我的做法是:在 Nomad 的resources块里预留内存,同时在容器配置里设置镜像支持的max_server_memory_usage,让两层约束对齐,避免调度器以为内存够用、实际容器却 OOM。
网络规划上,ClickHouse 的 8123 HTTP 端口和 9000 native 端口我用了静态端口,因为下游 JDBC 地址、监控探活、客户端配置全都写死了,迁移成本最低。如果追求多实例共存,静态端口会有冲突风险,那就得改成动态端口,并强制所有客户端走 Consul 服务发现。这方面的取舍,后面章节会给出具体配置。
2. 核心细节解析与实操要点
2.1 Nomad Job HCL 结构与关键配置项
先理清 Nomad job 文件的层级关系:job是整个调度单位,group是一组必须调度到同一台机器上的 task 集合,task才是真正跑起来的容器或进程。这个层级很像 K8s 里的 Deployment 和 Pod 的关系,但表达上更直接。我的习惯是除非有 sidecar 需求,否则一个 group 只放一个 task,保持“一容器一职责”,排障时不用猜日志属于谁。
HCL 里几个必看的配置块,我用 ClickHouse 的 job 来说明。
type字段决定调度语义:service类型面向长驻服务,调度器会尽力维持实例存活;batch类型面向一次性任务,跑完就退出并回收。ClickHouse server 和 Flink 管道必须用service,定时清理任务用batch,这点不能搞错,否则任务异常退出时 reschedule 策略会表现得很不一样。
update块控制滚动发布。我给 ClickHouse 配了max_parallel = 1,保证一次最多只有一个新实例在替换,避免升级过程中出现两个实例同时挂载同一份数据目录。healthy_deadline = "5m"表示新实例必须在 5 分钟内通过健康检查,否则任务会被判定失败并触发auto_revert自动回滚。这里有个细节:ClickHouse 冷启动恢复数据可能要几分钟,健康检查的窗口期不能给得太短,否则会出现“新实例明明在恢复数据却被当成启动失败”的假阳性。
restart和reschedule也有区别:task 级别的restart管的是容器崩溃后的本地重启,group 级别的reschedule管的是整个 allocation 失败后是否换一台机器重新调度。数据库服务我通常会把restart的 attempts 调低,避免内存 OOM 后无限重启把日志刷爆。
2.2 ClickHouse 部署的关键参数与存储设计
ClickHouse 的配置项很多,但部署层面真正决定稳定性的就那么几个,踩过一遍的人都懂。
内存是最先要处理的。前面提过,镜像默认会探测宿主机内存,然后按照很大比例设置内存池。解决方法是显式写覆盖配置,我习惯在config.d/override.xml里固定max_server_memory_usage,比如单机测试环境直接给 3GB,避免它把容器所在宿主机的总内存当成了自己的预算。同时 Nomad 的 docker driver 会把resources.memory映射成容器的内存限制,这样配置文件和容器限制两边一致,问题就不容易发生。
存储设计上,ClickHouse 的数据目录/var/lib/clickhouse和日志目录/var/log/clickhouse-server必须放在宿主机挂载盘。我用 Nomad 的 host volume 机制,在客户端配置里定义一个指向/srv/nomad/volumes/clickhouse的卷,job 文件里通过volume_mount挂进容器。这样无论 allocation 怎么重启、容器怎么重建,数据都还在宿主机上。还有一个权限坑:容器内 clickhouse 用户是 uid 999,挂载的目录如果用 root 创建,容器会直接报 permission denied。我折腾过一次之后,把创建目录和chown -R 999:999写进了初始化脚本,以后再也不手动了。
端口和访问控制方面,8123 是 HTTP 接口,9000 是 native 协议接口,9009 用于集群副本通信。我在 Nomad 的network块里把它们声明成三个静态端口,并通过 env 注入到容器。注意不要直接在network里写static = 0,那是动态端口的语义,和静态端口写法完全两回事。
2.3 Flink 供数任务以容器形态托管进 Nomad
把这层拆开讲,是因为 Flink 任务的管理方式和 ClickHouse server 差别很大。实时管道我们用的是 Flink DataStream,消费 Kafka 后写入 ClickHouse。在 Nomad 里跑 Flink 有两种主流姿势:一种是把 JobManager 和 TaskManager 当作两个常驻服务分别托管,适合跑多个作业、需要复用集群的场景;另一种是 standalone application 模式,把 Flink job 打进镜像,容器启动后直接运行作业,整个生命周期由 Nomad 管理。这次我选了后者,因为管道任务迭代频繁,重新 build 一个镜像然后滚动更新,比维护一个常驻 Flink session 集群要轻得多。
实现细节上,Flink 官方镜像提供了standalone-job.sh入口,它会启动 Dispatcher 和 JobManager,并自动提交指定的用户 jar。我在 Dockerfile 里把 pipeline jar 放在/opt/flink/usrlib/下,CMD 参数里跟上入口类和各连接参数。容器运行时保持前台进程不退,Nomad 的健康检查通过后,就认为任务部署成功。这种方式在更新时非常清爽:nomad job run触发滚动,新容器起来、新代码生效,旧容器按策略停掉。
Flink 作业的状态恢复要提前想清楚。流式计算最怕的是容器重启后状态丢了,从头消费 Kafka。我在作业代码里开了 checkpoint,并将 checkpoint 配置成外部化存储,路径指向共享存储。这样容器无论因为升级还是故障被 Nomad 拉起,都能从最近一次成功的 checkpoint 恢复进度。这个机制和后面要讲的can not stop with a savepoint job也有关联,先埋个伏笔。
2.4 配置管理与密钥注入
实操中还有一个容易被忽略的环节:配置和密码怎么管。Nomad 本身不提供声明式的配置中心,但它有template块,可以在任务启动前把模板渲染到local目录,再挂载进容器或直接读。这个方法用来管 ClickHouse 的override.xml非常好用,因为配置文件本身跟着 job 走,镜像保持干净,升级镜像时不用重新 bake 配置。
密钥方面,测试环境我图省事直接在 env 里写了明文密码,生产环境建议接 HashiCorp Vault 或者至少用 Consul KV 保存,通过template的vault或consul函数渲染出来。Nomad 对 Vault 集成是原生支持的,job 文件里声明需要的 Vault policy,任务启动时就会自动获取临时 Token,用完即失效,比在镜像里写死密码安全得多。这里给一个最小例子:在这个项目里,我一开始图快把 ClickHouse 密码写死在 job 文件里,结果全仓库都能看到,后来改成 Consul KV 渲染,一分钟改完心里踏实多了。如果你团队已经有 Vault,直接走 Vault 集成,别犹豫。
3. 实操过程与核心环节实现
3.1 环境准备:数据目录、Host Volume 与客户端配置
开始写 job 之前,先把 Nomad 环境准备好。假设集群已经搭好,Server 和 Client 都在运行,下面几步是在 ClickHouse 将要运行的节点上做的。
第一步,创建数据目录并授权。ClickHouse 官方镜像里 clickhouse 用户的 uid 是 999,宿主机上新建的目录默认是 root 所有,必须改成 999 可写:
sudo mkdir -p /srv/nomad/volumes/clickhouse/data sudo mkdir -p /srv/nomad/volumes/clickhouse/logs sudo chown -R 999:999 /srv/nomad/volumes/clickhouse第二步,在 Nomad 客户端配置文件里声明 host volume,以 Linux 下/etc/nomad.d/client.hcl为例:
client { enabled = true host_volume "clickhouse-data" { path = "/srv/nomad/volumes/clickhouse/data" read_only = false } host_volume "clickhouse-logs" { path = "/srv/nomad/volumes/clickhouse/logs" read_only = false } }改完重启 nomad client 服务,让新配置生效。这时候用nomad node status查看节点属性,如果能看到 host volume 已经注册,说明环境就绪了。这一步很容易因为 client 配置没重启而被忽略,job 跑起来会一直 pending,光看 job 状态很难发现问题。
3.2 部署 clickhouse-server 的完整配置与验证
下面是实际在用的 ClickHouse job 文件(已脱敏)。它包含了持久化、内存限制、健康检查、滚动升级策略和自定义配置渲染,直接可以照着改:
job "clickhouse-server" { datacenters = ["dc1"] type = "service" group "clickhouse" { count = 1 network { port "http" { static = 8123 } port "native" { static = 9000 } port "intra" { static = 9009 } } volume "ck-data" { type = "host" source = "clickhouse-data" read_only = false } volume "ck-logs" { type = "host" source = "clickhouse-logs" read_only = false } task "server" { driver = "docker" config { image = "clickhouse/clickhouse-server:24.8.5.75" ports = ["http", "native", "intra"] mount { type = "bind" source = "local/config.d" target = "/etc/clickhouse-server/config.d" readonly = true } } volume_mount { volume = "ck-data" destination = "/var/lib/clickhouse" } volume_mount { volume = "ck-logs" destination = "/var/log/clickhouse-server" } template { data = <<-EOF <clickhouse> <max_server_memory_usage>3221225472</max_server_memory_usage> <listen_host>0.0.0.0</listen_host> <max_connections>4096</max_connections> <mark_cache_size>536870912</mark_cache_size> </clickhouse> EOF destination = "local/config.d/override.xml" change_mode = "restart" } env { CLICKHOUSE_DB = "analytics" CLICKHOUSE_USER = "default" CLICKHOUSE_PASSWORD = "change-me-in-prod" } resources { cpu = 2048 memory = 4096 } service { name = "clickhouse" port = "http" provider = "consul" tags = ["clickhouse", "analytics"] check { type = "http" path = "/ping" interval = "10s" timeout = "2s" } } } } update { max_parallel = 1 min_healthy_time = "30s" healthy_deadline = "5m" auto_revert = true } }有几个点特意说明一下。template块里的max_server_memory_usage单位是字节,3GB 对应 3221225472,不要写错成 GB 数字。配置渲染到local/config.d/override.xml后挂载到容器,change_mode = "restart"表示配置变化时自动重启容器。健康检查用的/ping是 ClickHouse HTTP 服务自带接口,返回 200 说明进程存活,比只做 TCP 端口检查更能反映真实可用性。
上线操作:
nomad job run clickhouse-server.hcl查看调度结果:
nomad job status clickhouse-server nomad alloc status <alloc-id>看到 running 后,进容器或直接在外面验证:
clickhouse-client --host 127.0.0.1 --port 9000 --query "SELECT version(), uptime()"如果客户端不在本机,记得用 8123 端口的 HTTP 接口或走 Consul 解析clickhouse.service.consul。到这里 ClickHouse 本身已经跑起来了。
3.3 部署 Flink 管道任务并验证数据链路
管道任务这层,我先把 Flink 作业打成镜像。Dockerfile 保持简单,重点是把 jar 放到 Flink 的 usrlib 目录下,让standalone-job.sh能扫描到:
FROM flink:1.17.2 COPY ClickhousePipeline.jar /opt/flink/usrlib/ClickhousePipeline.jar ENTRYPOINT ["/opt/flink/bin/standalone-job.sh"] CMD ["com.example.ClickhousePipeline", "--kafka.servers", "kafka.service.consul:9092", "--clickhouse.url", "jdbc:clickhouse://clickhouse.service.consul:8123/analytics"]在 Nomad 里定义对应的 service 类型 job:
job "clickhouse-pipeline" { datacenters = ["dc1"] type = "service" group "pipeline" { count = 1 task "flink" { driver = "docker" config { image = "registry.internal/clickhouse-pipeline:1.4.2" } env { CHECKPOINT_INTERVAL_MS = "60000" CHECKPOINT_EXTERNAL_RETENTION = "RETAIN_ON_CANCELLATION" } resources { cpu = 1024 memory = 2048 } restart { attempts = 5 delay = "30s" } } update { max_parallel = 1 min_healthy_time = "60s" healthy_deadline = "10m" auto_revert = true } } }这个 job 没有对外映射端口,数据管道是外连 Kafka 和 ClickHouse 的。启动后观察日志:
nomad job status clickhouse-pipeline nomad alloc logs -f <alloc-id>日志里能看到 Flink 提交作业的过程,以及 checkpoint 是否开始正常生成。等上几分钟,到 ClickHouse 里查一下:
SELECT count() FROM analytics.events; SELECT toStartOfHour(ts) AS hour, count() FROM analytics.events GROUP BY hour ORDER BY hour DESC LIMIT 10;有数据增长就说明链路通了。这里我特别重视 checkpoint 的状态,后面升级停止和故障恢复都依赖它。如果 checkpoint 一直失败,先别继续往下走,把 Kafka 消费组、ClickHouse sink 写入性能先调通,否则后面所有“优雅”操作都是空中楼阁。
3.4 用 batch 加 periodic 实现定时清理任务
分析数据会在 ClickHouse 里越积越多,过期分区必须定时清理。这一层用 Nomad 的 batch 类型 job 加 periodic 调度来跑,一行 cron 表达式搞定:
job "clickhouse-cleanup" { datacenters = ["dc1"] type = "batch" periodic { cron = "0 3 * * *" prohibit_overlap = true } group "cleanup" { task "cleanup" { driver = "docker" config { image = "clickhouse/clickhouse-client:24.8" command = "/usr/bin/clickhouse-client" args = [ "--host", "clickhouse.service.consul", "--query", "ALTER TABLE analytics.events DROP PARTITION WHERE day < now() - INTERVAL 30 DAY", ] } resources { cpu = 256 memory = 256 } } } }注意这里用的是DROP PARTITION而不是DELETE。大数据量下DELETE会生成 mutation 后台任务,磁盘 IO 消耗大,执行得很慢;按天分区并直接 drop 旧分区才是 ClickHouse 推荐的做法,秒级完成且不产生重写。分区表需要你在建表时就用PARTITION BY toYYYYMMDD(day)这类表达式,否则这条命令没有意义。
prohibit_overlap = true防止上一次清理还没跑完下一次又触发,避免两个清理任务同时操作同一张表产生锁竞争。定时任务单独一个 job 的好处在于:如果某天业务需要手动清理,直接nomad job run -detach clickhouse-cleanup手动触发一次就行,不影响主服务。
4. 常见问题与排查技巧实录
4.1 报错“can not stop with a savepoint job”的完整排查
项目里最值得一写的是这个报错。场景是这样的:某次要升级 Flink 管道任务版本,按流程应该先优雅停止作业并保留中间状态。我执行了:
/opt/flink/bin/flink stop -p /data/flink/savepoints <jobId>结果命令直接失败,提示can not stop with a savepoint job,作业还挂在 RUNNING。第一反应是查作业状态,flink list显示 RUNNING,Web UI 里 checkpoint 也在正常做,完全看不出异常。于是去翻 JobManager 日志,看到了更具体的报错信息:停止操作触发的 savepoint 在超时时间内没有完成,作业状态无法收敛。
我把这类问题的排查顺序整理成一个表,照着做基本能定位:
| 检查项 | 具体操作 | 解决办法 |
|---|---|---|
| 作业是否启用 checkpoint | flink list -v或在 Web UI 看 Checkpoint 页 | 代码里显式env.enableCheckpointing(60000),否则 stop 没有状态可保存 |
| source 是否可停止 | 看 source 有没有实现可停止接口,KafkaSource 一般可以 | 改用flink cancel -s,或换成可停止的 source |
| savepoint 存储路径是否可写 | 检查 JobManager 所在节点对于state.savepoints.dir是否有写权限 | 改路径权限,避免用不稳定的 NFS |
| 停止过程是否超时 | 看 JobManager 日志里有没有 stop/savepoint 超时字样 | 手动先 cancel,再从最近 checkpoint 恢复 |
这次问题的根子其实在最后一个检查项。我们的 ClickHouse sink 是自定义写入算子,数据先在内存缓冲批量攒着,快照回调里没有强制 flush 当前批次,导致触发 savepoint 时数据写不完,超时后被 Flink 判定为无法停止。简单说:不是 Nomad 的问题,也不是 ClickHouse 的问题,是流处理作业自己的生命周期没有处理干净。
最终解决用的是两阶段方案。先用flink cancel <jobId>把作业取消,这一步不会尝试生成 savepoint,所以不会卡住;然后从最近一次成功的 checkpoint 路径手动恢复任务:
/opt/flink/bin/flink run -s /data/flink/checkpoints/xxx/chk-123 /opt/jobs/pipeline.jarcheckpoint 原本就开着,恢复后的进度最多回退几十秒,Kafka 消费 offset 也能衔接上,端到端不重不丢,这个结果当时实测就很稳。之后我在新版本算子代码里修复了快照 flush 逻辑,再次验证flink stop -p就能正常生成 savepoint 了。
这里也想给个忠告:stop和cancel -s是有本质区别的。stop要求作业的 source 能主动停止,并且会触发一次严格的 savepoint,链路里任何一步卡住都会报错;cancel -s是强制取消的同时尝试保存状态,对作业内部状态的配合度要求低一些。遇到报错别慌,先确认你的任务是哪一类,再决定走哪条路径。ClickHouse 这类强状态 sink 的作业,一定要先把快照和缓冲 flush 的关系处理好,优雅停止才谈得上。
4.2 ClickHouse 容器资源与存储问题速查
除了那个大头报错,日常里这几个问题也很典型。
OOM 是 ClickHouse 容器最常见的故障。现象是容器反复重启,但看nomad alloc status还显示 running,因为重启太快。排查时先看 ClickHouse 日志里有没有Memory limit相关报错,再看容器的 cgroup 限制是否生效。Nomad 的resources.memory会映射给 docker driver 作为内存限制,但 ClickHouse 内部的内存池设置要单独配置,两者必须对齐。我之前遇到过配置里写了 4096MB 内存限制,但max_server_memory_usage没显式设置,结果 ClickHouse 自动探测后把内存池开到了接近宿主机全部内存,uv 直接爆了。
数据目录权限的问题也很常见。挂载了 host volume 但没做chown -R 999:999,容器启动时会报cannot create directory /var/lib/clickhouse: Permission denied。这个问题在第一次跑 job 时最坑,因为 job 文件看起来完全正常,调度也成功,就是容器反复启动失败,不看日志根本找不到原因。我后来把目录初始化做成了一个独立脚本,在创建 host volume 之后立刻执行,再也没犯过。
磁盘写满之后 ClickHouse 会把表变成只读,这个状态比想象中更隐蔽。看起来服务存活、端口正常、健康检查也过,但查询开始报READONLY或者写入全部失败。监控里一定要同时盯磁盘容量和 inode 使用率,只盯内存是不够的。配合 3.4 里的分区清理任务,基本上能把这类问题防在发生之前。
还有一个小坑:滚动升级时如果把count改成了 2,并且两个实例挂载了同一个 host volume,会出现两个 ClickHouse 进程抢同一个数据目录,轻则锁冲突,重则数据目录损坏。有状态服务不要轻易用多副本加共享卷的方式来搞高可用,正确姿势是一次只运行一个实例,通过备份恢复或真正的主从复制来保证可用性。
4.3 Nomad 调度与运维的其他高频问题
最后补充几个 Nomad 层面的高频问题,不算疑难杂症,但很影响体验。
job 长时间 pending,多半不是资源不够,而是datacenters写错或节点没有满足约束。用nomad job status -verbose看 Placement Metrics,它会直接告诉你没被调度上的原因,比如 “1 node exhausted by memory” 或者 “no nodes with datacenter match”,比瞎猜快得多。排障时记得还要看节点上的 client 配置是否和期望一致,host volume 注册失败也会导致 job 一直 pending 但日志什么都不报。
nomad job stop和nomad job restart是不同的操作。stop是彻底停掉这个 job 的所有 allocation,不再自动拉起;restart是重新调度一个 allocation。很多人刚上手会把它们混用:想重启某个容器结果执行了stop,紧接着发现服务没了,还得重新nomad job run。日常更新代码直接重新nomad job run触发滚动就行,不是必须先 stop。
分配节点时,constraint块里可以用attribute限定只跑在有某块 SSD 的机器上:
constraint { attribute = "${node.class}" value = "clickhouse" }这种方式比把 job 绑定到某个固定节点更灵活,节点故障时可以自动迁移。但记得给有状态服务配套好数据盘迁移方案,否则换了机器数据没了,反而比不迁移更糟。
5. 最后再说几句实操体会
整段实操做下来,我对“Nomad 组件部署 clickhouse-job”这件事最大的体会是:Nomad 本身不难,真正考验人的是怎么把有状态服务和无状态作业的边界划清楚。ClickHouse 的数据就是命根子,持久化、目录权限、内存上限这些事必须在 job 定义里显式写清楚,不能指望镜像默认行为恰好满足生产需求;Flink 这类流任务则要先想好停止和恢复的方式,checkpoint、savepoint 这套机制不是摆设,真出问题的时候能救命。还有一个小技巧分享给大家:给每个 job 的 metadata 里写上 owner 和用途,一行代码的事:
meta { owner = "data-platform" purpose = "clickhouse-analytics" }等集群里 job 多起来之后,nomad job status列表里扫一眼就知道哪个是谁的,省掉很多来回沟通的成本。这套方案跑了大半年,ClickHouse 的部署时间从以前的手工配系统服务加脚本,压缩到现在一条nomad job run加几行配置,稳定性反而更好了。如果你也在折腾数据组件的 Nomad 化部署,欢迎多交流,边踩坑边沉淀的经验拿出来分享,比自己闷头解决要有价值得多。