关于一个 JFinal 项目的容器化部署
关于一个 JFinal 项目的容器化部署
昨天公司让部署一个基于 JFinal 框架开发的项目,记录一下部署过程及遇到的问题。
背景及需求如下:
- 项目代码托管在 CNB 上;
- 使用 CNB 的云原生构建部署到腾讯云的 TKE 集群;
第一次接触到这个框架,只能先看一下官方文档,了解一下项目的基本架构。
打包部署
个人理解,部署主要依赖的是 maven-assembly-plugin 插件,根据 package.xml 配置文件打包项目,然后通过 start.sh 脚本启动项目。
Maven 插件配置
这里遇到的坑是:项目中使用的 maven-compiler-plugin 插件中配置了 <bootclasspath>${java.home}/lib/rt.jar;${java.home}/lib/jce.jar</bootclasspath>。这个估计之前的打包环境中有这个文件,但是在容器中构架时报了“Fatal Error: Unable to find package java.lang in classpath or bootclasspath”的错。
这里的配置是用来解决“程序包 com.sun.image.codec.jpeg 不存在”的问题的。印象中之前的项目遇到过这个问题,查了下之前的文档,果然找到了相关记录。原因是这个包在 JDK 1.8 中已经移除了。为了打包通过,这边暂时先把这里注释掉了。如果有需要可以参考这里的方案二,修改为使用新的包。
下面是项目中使用的插件配置,仅供参考:
<build>
<plugins>
<!--
jar 包中的配置文件优先级高于 config 目录下的 "同名文件"
因此,打包时需要排除掉 jar 包中来自 src/main/resources 目录的
配置文件,否则部署时 config 目录中的同名配置文件不会生效
-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.6</version>
<configuration>
<excludes>
<exclude>*.txt</exclude>
<exclude>*.xml</exclude>
<exclude>*.properties</exclude>
</excludes>
</configuration>
</plugin>
<!--
使用 mvn clean package 打包
更多配置可参考官司方文档:http://maven.apache.org/plugins/maven-assembly-plugin/single-mojo.html
-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<!-- 打包生成的文件名 -->
<finalName>${project.artifactId}</finalName>
<!-- jar 等压缩文件在被打包进入 zip、tar.gz 时是否压缩,设置为 false 可加快打包速度 -->
<recompressZippedFiles>false</recompressZippedFiles>
<!-- 打包生成的文件是否要追加 release.xml 中定义的 id 值 -->
<appendAssemblyId>true</appendAssemblyId>
<!-- 指向打包描述文件 package.xml -->
<descriptors>
<descriptor>package.xml</descriptor>
</descriptors>
<!-- 打包结果输出的基础目录 -->
<outputDirectory>${project.build.directory}/</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<!--程序包com.sun.image.codec.jpeg不存在 问题的完美解决-->
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
<optimize>true</optimize>
<debug>true</debug>
<showDeprecation>true</showDeprecation>
<showWarnings>false</showWarnings>
<compilerArguments>
<verbose />
<!--<bootclasspath>${java.home}/lib/rt.jar;${java.home}/lib/jce.jar</bootclasspath>-->
</compilerArguments>
</configuration>
</plugin>
<!--程序包com.sun.image.codec.jpeg不存在 问题的完美解决-->
</plugins>
</build>
打包描述文件 package.xml
下面是项目中的 package.xml 配置,仅供参考。
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd">
<!--
assembly 打包配置更多配置可参考官方文档:
http://maven.apache.org/plugins/maven-assembly-plugin/assembly.html
-->
<id>release</id>
<!--
设置打包格式,可同时设置多种格式,常用格式有:dir、zip、tar、tar.gz
dir 格式便于在本地测试打包结果
zip 格式便于 windows 系统下解压运行
tar、tar.gz 格式便于 linux 系统下解压运行
-->
<formats>
<format>dir</format>
<format>zip</format>
<!-- <format>tar.gz</format> -->
</formats>
<!-- 打 zip 设置为 true 时,会在 zip 包中生成一个根目录,打 dir 时设置为 false 少层目录 -->
<includeBaseDirectory>true</includeBaseDirectory>
<fileSets>
<!-- src/main/resources 全部 copy 到 config 目录下 -->
<fileSet>
<directory>${basedir}/src/main/resources</directory>
<outputDirectory>config</outputDirectory>
</fileSet>
<!-- src/main/webapp 全部 copy 到 webapp 目录下 -->
<fileSet>
<directory>${basedir}/src/main/webapp</directory>
<outputDirectory>webapp</outputDirectory>
<excludes>
<exclude>WEB-INF</exclude>
<exclude>WEB-INF/web.xml</exclude>
</excludes>
</fileSet>
<!-- 项目根下面的脚本文件 copy 到根目录下 -->
<fileSet>
<directory>${basedir}</directory>
<outputDirectory></outputDirectory>
<!-- 脚本文件在 linux 下的权限设为 755,无需 chmod 可直接运行 -->
<fileMode>755</fileMode>
<includes>
<include>*.sh</include>
<include>logs</include>
</includes>
</fileSet>
</fileSets>
<!-- 依赖的 jar 包 copy 到 lib 目录下 -->
<dependencySets>
<dependencySet>
<outputDirectory>lib</outputDirectory>
</dependencySet>
</dependencySets>
</assembly>
Dockerfile
基础镜像这里使用的是 openjdk 的 java 8 镜像,大约 120M 左右。
# 基础镜像使用 Java 8
FROM openjdk:8u342-jre
# 拷贝target下编译后的整个项目文件夹到容器根目录下的jfinal-release目录
# 如果不确定该配置是否正确,可手动执行一次maven package命令,然后根据生成的目录结构来配置
COPY ./target/jfinal-release/jfinal /jfinal
# 后续命令行执行的基础路径,这里设置为容器的根路径
WORKDIR /
# 创建logs目录,用于存放应用的日志文件(如果输出到日志文件的话,需要在这里先创建对应的日志文件目录)
# RUN mkdir -p ./logs
# 默认暴露的容器端口号,可以在可视化配置中重新指定,但需要与undertow.port配置一致才可正常访问
EXPOSE 80
# 创建容器后的启动命令,即通过启动类的main函数运行容器内的web应用
# cp参数添加额外依赖,即执行main函数时需要依赖config下的所有配置文件和lib下所有的jar包
CMD java -Xverify:none -Dundertow.port=80 -Dundertow.host=0.0.0.0 -cp /jfinal/config:/jfinal/lib/* me.liujiajia.jfinal._Config
这里需要遇到的一个坑是:之前找的很多文档里,文件是直接保存在 target 下的 myapp-release 目录里的,但是我这里是 myapp-release/myapp,导致在启动容器时,无法找到启动类(Error: Could not find or load main class),卡了一段时间。
CNB 部署清单 .cnb.yml
这是 CNB 云原生构建的清单,使用 deploy-to-tke 插件部署镜像到 TKE 集群。
- pipeline 的镜像使用的是 maven:3.8.6-openjdk-8;
- 这里镜像仓库名称使用到的是
${PUB_ENV},方便根据部署的环境来保留容器镜像的数量,以避免占用过多的空间; - 镜像 tag 使用的是
${CNB_COMMIT},也就是提交的 git 版本号; - deploy-to-tke 插件使用的密钥等环境变量通过 imports 导入;
.jobs: &jobs
- name: mvn package
script:
- mvn clean package -Dmaven.test.skip=true
- name: docker build
script:
- java -version
- docker build -t ${CNB_DOCKER_REGISTRY}/${CNB_REPO_SLUG_LOWERCASE}/${PUB_ENV}:${CNB_COMMIT} .
- name: docker push
script:
- docker push ${CNB_DOCKER_REGISTRY}/${CNB_REPO_SLUG_LOWERCASE}/${PUB_ENV}:${CNB_COMMIT}
- name: 使用 tke 插件更新镜像
imports: https://cnb.cool/liujiajia-me/secret/-/blob/main/build/tencent.yml
image: tencentcom/deploy-to-tke
settings:
secret_id: ${SECRET_ID}
secret_key: ${SECRET_KEY}
region: ${TKE_REGION}
cluster_id: ${TKE_CLUSTER_ID}
namespace: rs-${PUB_ENV}
workload_kind: deployment
workload_name: jfinal-app-deploy
container_names: jfinal-app
container_images: ${CNB_DOCKER_REGISTRY}/${CNB_REPO_SLUG_LOWERCASE}/${PUB_ENV}:${CNB_COMMIT}
.pipeline: &pipeline
runner:
cups: 4
docker:
# 声明构建环境,可以在 dockerhub 上 https://hub.docker.com/_/maven 找到您需要maven和jdk版本
image: maven:3.8.6-openjdk-8
volumes:
# 声明式的构建缓存 https://docs.cnb.cool/grammar/pipeline.html#volumes
- /root/.m2:copy-on-write
services:
# 流水线中启用 docker 服务
- docker
stages: *jobs
relese:
push:
- <<: *pipeline
env:
PUB_ENV: prod
Kubernetes 部署清单 deployment.yaml
以下是使用的部署清单,仅供参考:
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-jfinal-deploy
namespace: jfinal
spec:
replicas: 0
revisionHistoryLimit: 2
selector:
matchLabels:
app: app-jfinal
template:
metadata:
labels:
app: app-jfinal
spec:
containers:
- env:
- name: TZ
value: Asia/Shanghai
image: >-
docker.cnb.cool/liujiajia-me/jfinal/jfinal/prod
imagePullPolicy: IfNotPresent
name: app-jfinal
ports:
- containerPort: 80
imagePullSecrets:
- name: my-artifacts
terminationGracePeriodSeconds: 90