关于一个 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