
示例工程【免费下载链接】examplesKubernetes application example tutorials项目地址https://gitcode.com/gh_mirrors/examp/examples点击查看免费下载导读本文基于 Kubernetes 官方 examples 仓库中的javaweb-tomcat-sidecar示例讲解一种经典的 Java Web 应用部署模式不把war文件打包进 Tomcat 镜像也不把war挂载为持久卷而是用一个 init container初始化容器作为 war 文件提供者通过共享emptyDir卷把应用包交付给 Tomcat 容器。读完本文你将掌握两套完整可运行的 Pod 配置镜像内置脚本版与 Podcommand版、底层容器镜像的构建方式以及如何创建、验证和清理这类应用从而把应用版本管理与Web 服务器管理彻底解耦。为什么要用 init container 提供 WAR 包部署一个 Java Web 应用到 Kubernetes常见的做法有三种把war文件直接打进 Tomcat 镜像FROM tomcatADD sample.war。问题在于应用和服务器被绑定进同一个镜像一旦应用升级就必须重新构建整个 Tomcat 镜像同样Tomcat 打安全补丁时也要为每一个应用各构建一个新镜像。把war放在宿主机目录再以 volume 挂载给 Tomcat 容器。这要求你手动管理卷当 Pod 被重启或调度到另一台节点时目标主机的目录里不一定有这份war为规避这个问题通常还需要搭建分布式文件系统至少是 NFS若没有 GCE PD 这类云盘的话整体复杂度明显上升。本示例采用的方案用 init container 作为war文件提供者。init container 与 Tomcat 容器共享同一个emptyDir卷Pod 会保证所有 init container 全部成功退出之后才启动普通容器。这意味着Tomcat 容器启动时war文件必然已经就位。第三个方案带来两个直接收益镜像职责分离Tomcat 镜像不再携带任何业务包可以复用于大量不同的应用每个应用只需维护一个极小的、仅含war文件的容器镜像。升级应用只重新构建war 镜像升级服务器只重新构建 Tomcat 镜像互不干扰。滚动发布成本降低当需要滚动更新 Tomcat 以修复安全漏洞时不必重新构建 N 个应用镜像一套 Tomcat 镜像即可。需要说明本文示例来自仓库的_archived归档目录使用的是早期 KubernetesapiVersion: v1的 Pod 直配形式。其中的设计思想——把数据/内容提供者与服务运行时拆分成两个镜像并通过共享卷协同——与如今广泛使用的 init container 模式完全一致配置写法上只需注意使用现代集群仍兼容的字段。核心概念init container 与 emptyDir 共享卷在 Kubernetes 中Pod 是最小的可部署、可调度、可管理单元是一组共享 IP 和存储卷的、共置collocated的容器集合。本示例的核心机制由两个 Kubernetes 原语构成initContainers在 Pod 的普通容器启动之前按顺序执行的特殊容器。只有当全部 init container 都成功退出退出码为 0后kubelet 才会启动containers列表中的普通容器若任一 init container 失败Pod 会按重启策略重试。这一先决条件语义正是war文件先拷贝、后启动 Tomcat的保障。emptyDir 卷随 Pod 创建而创建的空目录生命周期与 Pod 绑定。Pod 内的多个容器可以同时挂载同一 emptyDir从而实现 init container 写文件、Tomcat 容器读文件的共享通道。emptyDir 不需要任何外部存储设施因此不需要管理分布式文件系统。整个工作流可以用下图概括从图中可以看出应用的源码GitHub 中的code经 CI 构建出sample.war再交给 image builder 依据 DockerfileFROM busybox:latest、ADD sample.war sample.war产出版本化的应用镜像如registry/sample:v2。这个镜像作为 Pod 内的 sidecar 容器把sample.war写入共享卷Tomcat 主容器mytomcat:7.0再从卷中读取并完成部署。两个容器共同组成一个原子调度单元应用与服务器的版本管理从此互不干扰。方案一镜像内置脚本版javaweb.yaml仓库中的第一份配置是 _archived/javaweb-tomcat-sidecar/javaweb.yaml对应文档最初设计的resouer/sample:v1镜像该镜像内置了mv.sh拷贝脚本。核心 YAML 如下apiVersion: v1 kind: Pod metadata: name: javaweb spec: initContainers: - image: resouer/sample:v1 name: war volumeMounts: - mountPath: /app name: app-volume containers: - image: resouer/mytomcat:7.0 name: tomcat command: [sh, -c, /root/apache-tomcat-7.0.42-v2/bin/start.sh] volumeMounts: - mountPath: /root/apache-tomcat-7.0.42-v2/webapps name: app-volume ports: - containerPort: 8080 hostPort: 8001 volumes: - name: app-volume emptyDir: {}关键设计点如下warinit container 必须定义在前面initContainers列表第一个因为它是应用包的提供者。两个容器都挂载了名为app-volume的emptyDir卷但挂载点不同init container 挂到/app拷贝目标Tomcat 容器挂到 Tomcat 的webapps目录/root/apache-tomcat-7.0.42-v2/webapps。Tomcat 会自动从webapps目录加载并解压部署sample.war。tomcat容器显式指定了启动命令指向start.sh对外通过hostPort: 8001暴露访问入口容器内部监听containerPort: 8080。这份配置唯一的魔法在resouer/sample:v1镜像其 Dockerfile 为FROM busybox:latest ADD sample.war sample.war CMD sh mv.sh其中mv.sh的内容是cp /sample.war /app tail -f /dev/null逐行解释其作用war容器只包含你的应用的war文件除此之外没有任何应用逻辑。容器启动后CMD 执行cp /sample.war /app把war文件拷贝到emptyDir卷的挂载点。最后的tail -f /dev/null只是用来占住容器不让其退出——因为早期 Replication Controller 不支持一次性任务one-off task容器必须保持运行状态。拷贝完成后tomcat容器从共享卷路径加载sample.war并完成部署。仓库中的实际javaweb.yaml文件在 init container 上直接写入了command: [cp, /sample.war, /app]与下方方案二写法一致README 文档则保留了 v1 镜像内置 mv.sh 脚本的原始设计。两份写法均可运行读者按需选择即可。方案二Pod command 直接拷贝版javaweb-2.yaml如果你不想在war镜像里内置mv.sh脚本可以把拷贝动作上移到 Pod 的command字段让war镜像保持只有war文件的纯粹状态。对应配置为 _archived/javaweb-tomcat-sidecar/javaweb-2.yamlapiVersion: v1 kind: Pod metadata: name: javaweb-2 spec: initContainers: - image: resouer/sample:v2 name: war command: - cp - /sample.war - /app volumeMounts: - mountPath: /app name: app-volume containers: - image: resouer/mytomcat:7.0 name: tomcat command: [sh,-c,/root/apache-tomcat-7.0.42-v2/bin/start.sh] volumeMounts: - mountPath: /root/apache-tomcat-7.0.42-v2/webapps name: app-volume ports: - containerPort: 8080 hostPort: 8001 volumes: - name: app-volume emptyDir: {}对应的resouer/sample:v2Dockerfile 更加精简FROM busybox:latest ADD sample.war sample.war CMD tail -f /dev/null与方案一的区别与要点war容器只包含war文件它的 CMD 仅用tail -f /dev/null保持容器存活不做任何拷贝。真正的拷贝由 Podcommand中的cp /sample.war /app完成即覆盖了镜像默认 CMD。tomcat容器同样从共享卷路径加载sample.war部署运行。这样你的war容器里除了sample.war之外什么都没有干净到了极致也更容易被复用。两种方案的取舍对比维度方案一镜像内置脚本sample:v1方案二Pod command 拷贝sample:v2拷贝逻辑位置镜像内的mv.sh脚本Pod 的command字段war镜像内容sample.warmv.sh仅sample.war镜像可复用性拷贝逻辑与镜像绑定改动需重建镜像同一镜像可通过不同command适配多种拷贝/挂载需求适用场景希望镜像自带完整行为、便于离线分发希望镜像最小化、行为由编排层Pod 配置决定两种方案在运行时行为上等价init container 先完成拷贝Pod 再启动 TomcatTomcat 从webapps目录自动加载应用。选择哪种取决于你更倾向于镜像自治还是编排自治。测试运行创建 Java Web Pod以方案二为例$ kubectl create -f _archived/javaweb-tomcat-sidecar/javaweb-2.yaml观察 Pod 状态变化$ kubectl get -w po NAME READY STATUS RESTARTS AGE javaweb-2 2/2 Running 0 7s注意观察输出中READY与STATUS两列的变化过程init container 执行期间 Pod 处于初始化阶段READY会短暂显示为0/1两个容器尚未全部就绪待war拷贝完成、Tomcat 启动就绪后变为2/2且STATUS为Running。之后即可在浏览器访问 Hello, World 页面http://localhost:8001/sample/index.htmljavaweb.yaml方案一可以用同样的方式创建和验证。清理资源验证完毕后删除创建的资源$ kubectl delete -f _archived/javaweb-tomcat-sidecar/javaweb-2.yaml由于整个方案只依赖emptyDir卷没有创建持久卷、Service、Deployment 等额外资源删除 Pod 即可完整清理不会留下孤儿资源。延伸思考从提供者到通用模式本文把war文件视为一种由专用镜像提供、经共享卷交付的资源。同样的思想可以推广到其他场景——例如用 init container 拉取配置、初始化数据目录、等待依赖服务就绪等这也是现代 Kubernetes 中 init container 最常见的用途。与更现代编排的衔接示例使用裸 Pod 便于聚焦概念在生产中同样的initContainers结构可以直接放进 Deployment / StatefulSet 的 Pod 模板中获得滚动更新、副本扩缩容等能力。文档中提到的Replication Controller 不支持一次性任务需要用tail -f占住容器是早期版本的约束在支持 Job 的现代集群中纯拷贝类任务也可改用 Job 完成但 init container 因其启动前保证的语义仍然是首选。遗留写法提醒hostPort: 8001会把容器端口直接映射到节点端口适合演示生产环境建议改用 Service 暴露。示例镜像resouer/sample、resouer/mytomcat为社区演示镜像实际使用时请替换为你自己的镜像仓库地址并注意start.sh路径与你的 Tomcat 版本保持一致。赞分享示例工程【免费下载链接】examplesKubernetes application example tutorials项目地址https://gitcode.com/gh_mirrors/examp/examples点击查看免费下载相关推荐Tomcat Web应用部署完全指南WAR包与热部署技巧Tomcat Web应用部署完全指南WAR包与热部署技巧 引言解决Java Web部署的核心痛点 你是否曾遭遇过Tomcat部署时的以下困境WAR包上传耗后端Web框架认证鉴权使用 Helm 在 Kubernetes 上为 Prometheus 部署 Thanos Sidecar 实战指南使用 Helm 在 Kubernetes 上为 Prometheus 部署 Thanos Sidecar 实战指南 本指南围绕仓库中 tutorials/kub可观测性云原生时序数据库运维在 Laradock 中运行 Apache Tomcat部署 Java WAR 应用的完整指南在 Laradock 中运行 Apache Tomcat部署 Java WAR 应用的完整指南 Apache Tomcat 是一个 Java Servlet后端开发工具DevOps上一篇TanStack Table与其他表格库对比为什么选择这个无头解决方案下一篇Dgeni vs JSDoc为什么AngularJS选择自己开发文档生成工具创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考