文章 · 2022-03-10

从离线部署到K8s流水线发布:一线工程师的实战总结

在没有联网的环境中部署应用,我们采取了一套离线部署 Jar 包的方案。主要步骤包括:

scp app-1.0.jar [email protected]:/opt/deploy/app-1.0.jar
nohup java -jar /opt/deploy/app-1.0.jar --spring.config.location=/opt/deploy/config/ &

这样即使终端关闭,应用仍持续运行,日志输出重定向到 nohup.out 或指定的日志文件。

以上流程保证了在无外网环境下顺利部署应用。但该手工方式也存在明显缺点:每次更新都需人工介入,多台服务器部署容易出现版本不一致或遗漏步骤的问题。随着发布频率提高,我们意识到需要更加自动化和标准化的方式。

Cassandra 大表导出

项目运营过程中,我们曾需要将 Cassandra 数据库中某张包含亿级记录的"大表"导出备份。这项任务在没有合适工具时非常棘手:

COPY keyspace_name.table_name TO 'export.csv';

该命令可以将查询结果直接导出为 CSV 文件。然而在面对数亿行数据时,COPY TO 运行非常缓慢,中途容易因为网络波动或超时失败,恢复起来也麻烦。

dsbulk unload -k keyspace_name -t table_name -url export_data/ -maxRetries 5

DSBulk 内部对读取进行了优化和并行处理,导出效率较高,并提供断点续传等功能。在一次测试中,使用 DSBulk 将一张约5千万行的表导出为 CSV,耗时从最初的数小时缩短到不到1小时,大大提升了效率。

通过上述方法,我们成功解决了 Cassandra 大表导出难题。在没有专用工具时,分段导出是可行的折中方案;而借助专业工具后,大规模数据迁移的可靠性和效率都显著提高。

常见启动故障案例

在应用部署和运行过程中,我们还遇到过Java 应用启动失败的情况。其中两类印象深刻的故障来自第三方组件:Atomikos 分布式事务管理器和 Curator Zookeeper 客户端。下面分别分享我们排查和解决问题的经过。

Atomikos 导致的启动异常

我们有一套服务使用 Atomikos 作为分布式事务管理器(用于多数据源事务)。某次在同一台服务器上启动两套服务时,应用在初始化 Atomikos 事务管理器时抛出了异常,导致启动失败。日志片段如下:

com.atomikos.icatch.SysException: Error in init: Log already in use? tmlog in ./
    at com.atomikos.icatch.impux.TransactionServiceImp <...> 
Caused by: com.atomikos.recovery.LogException: Log already in use by another process.

从错误可以看出,Atomikos 尝试创建事务日志文件时发生冲突(Log already in use)。原因是同一环境中同时运行了多个使用 Atomikos 的应用,且它们默认使用相同路径的事务日志文件,导致后启动的进程无法获取文件锁。

解决过程:我们确认前一个服务正在使用 Atomikos 默认的事务日志(通常存放于应用运行目录下的 transaction-logs 文件夹)。为了解决冲突,我们采取了两种措施之一:

spring.jta.atomikos.log-dir=./transaction-logs-app2

这样第二个应用的事务日志将写入独立目录,避免与第一个应用争用同一文件。

采用了修改日志路径的方法后,我们重新启动应用,Atomikos 初始化成功,冲突不再发生。这个案例提醒我们:中间件的默认配置不一定适用于特殊场景,需根据部署情况做适当调整。例如,对于需要在同一主机部署多实例的组件,要检查是否有共享资源(文件、端口等)冲突,并通过配置加以区分。

Curator 导致的启动卡顿

另一问题来自于 Apache Curator,一个常用的 ZooKeeper 客户端框架。某微服务在启动时使用 Curator 连接 ZooKeeper 做服务注册,但我们发现在某环境下启动过程长时间卡住,日志不断打印异常:

org.apache.curator.CuratorConnectionLossException: KeeperErrorCode = ConnectionLoss
	at org.apache.curator.ConnectionState.getZooKeeper(ConnectionState.java:123)
	... 

日志提示 Curator 连接丢失,一直在重试 (ConnectionLoss 表明无法连接 ZooKeeper 集群)。这种情况导致应用阻塞在启动阶段。经过排查,我们找到以下原因:

解决方案:针对上述原因采取了相应措施——启动 ZooKeeper 服务进程,并调整防火墙策略允许访问 ZooKeeper 的端口(例如2181)。随后重启微服务,Curator 成功建立连接,应用顺利启动。为防止此类问题再次发生,我们还完善了启动脚本:在部署应用前增加对依赖服务的健康检查,如自动检测 ZooKeeper 的状态,如果未就绪则给予提示或延迟启动。同时,将 Curator 客户端的超时和重试参数调得更加合理,使其在连接异常时能及早抛出错误而非无限卡顿。

通过以上两个案例,我们深刻体会到启动故障排查需要结合日志迅速定位,并关注依赖组件的配置与运行环境。无论是事务管理器还是注册中心客户端,理解其工作机制和配置项,有助于快速找到问题根源并加以解决。

Kubernetes 标准发布流程

在解决了初步的部署和运行问题后,我们着手引入 Kubernetes (K8s) 来重构发布流程。目标是实现从构建、部署到发布的流水线自动化,将过去繁琐的手工步骤标准化。下面介绍我们落地 K8s 标准发布的实践过程:

FROM openjdk:8-jre-slim
COPY app-1.0.jar /app/app.jar
CMD ["java", "-jar", "/app/app.jar", "--spring.config.location=/app/config/"]

我们将应用运行所需的配置文件也打包进镜像的 /app/config/ 目录(或挂载 ConfigMap,见后续),以确保容器启动时能找到正确配置。完成 Dockerfile 后,通过内网的 CI 工具构建镜像并推送到私有镜像仓库(如 registry.example.com/myteam/app:1.0)。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-deployment
spec:
  replicas: 2
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
      - name: myapp-container
        image: registry.example.com/myteam/app:1.0
        ports:
        - containerPort: 8080
        env:
        - name: JAVA_OPTS
          value: "-Xms512m -Xmx512m"
        readinessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 15
          periodSeconds: 5
        livenessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 10

上述清单定义了两个副本的部署,并配置了应用容器使用我们构建的镜像。同时设置了 就绪探针 (Readiness Probe) 和 存活探针 (Liveness Probe),定期访问应用的健康检查接口 /health。这些探针确保应用只有在健康时才接受流量,并在异常时自动重启容器,提高发布过程的可靠性。

通过 Kubernetes 的标准化部署,我们的应用发布从此进入流水线作业,实现了一键部署和回滚,减少了人为失误。每次部署都有记录和监控,使得问题追溯和快速恢复更加方便。

日志监控体系搭建

随着系统逐步走向容器化和分布式,我们同步建立了完善的日志和监控体系,用于运维过程中的故障诊断和性能调优。

通过日志和监控体系的搭建,我们实现了对系统 可观察性(Observability) 的极大提升。从以前出故障"盲人摸象"式的猜测,转变为现在有数据支撑的精准分析。不仅故障恢复时间(MTTR)降低了,日常性能调优也有据可依,整体运行维护更加从容。

效果评估(优化前后对比)

通过上述一系列改进,我们对比了优化前后的效果:

方面 改进前(传统离线/手工方式) 改进后(自动化流水线 + K8s)
部署方式 手工传输 Jar 包,人工执行启动脚本;每次发布耗时长,易出错 标准化容器镜像部署,CI/CD 自动完成构建发布;速度快且可重复
发布可靠性 缺乏统一流程,遇到错误需人工回退;多台机器配置可能不一致 Kubernetes 滚动更新,无缝发布,失败自动回滚;环境配置一致
大数据处理 手工导出大表费时费力,过程中容易中断 使用工具批量导出,效率提升数倍;大型数据迁移更可控
故障排查 日志分散在各服务器,定位问题耗时 日志集中检索,监控实时告警;几分钟内即可发现并定位问题
系统监控 基本依赖人工观察,缺乏预警机制 完善的监控看板与告警策略,问题未发生已能提前预警

(表:系统在部署和运维方面优化前后的对比)

从上表可以看出,系统经过改造后在发布效率、可靠性和可维护性方面都有了显著提升。例如发布效率方面,由原来的每次发布耗时半小时、需多人配合,优化为流水线后通常几分钟即可完成,且基本零人工干预。再如故障排查,以前可能需要1-2小时集中分析日志才能找到问题,现在借助集中日志和监控报警,很多问题在几分钟内就能检测并通知相关人员。总体而言,这些实践优化了团队的 DevOps 工作模式,为业务快速迭代提供了坚实保障。

总结提升

通过这次从离线部署到 K8s 流水线发布的实践,我们团队收获了宝贵的经验教训,也验证了新技术在生产环境中的价值。在总结几点体会的同时,我们也展望未来的改进方向:

最后,希望本次实战总结对各位读者有所启发。技术改进是一个渐进的过程,从离线部署的摸索到云原生实践的落地,每一步都伴随着挑战和收获。作为一线工程师,我们应当拥抱新技术带来的变革,同时保持对细节问题的敏感,积累经验,不断优化系统的稳定性和交付效率。在未来的项目中,我们将继续沉淀更多实践案例,与大家分享交流!

© 2026 Yuxu Ge ·