应用上K8S:K8S集成Java应用

时间:2023-01-09 20:55:54

需求

当我们对Java应用完成Maven/Gradle打包并将镜像推送至远程仓库后,剩下的工作就是应用上K8S了,涉及到的工作主要为:

  • 编写Deployment/Service/Ingress部署应用;
  • 环境变量传递Xmx、应用名、环境名等个性化配置;
  • configmap传递统一标准规范的jvm参数;
  • 共享目录统一管理配置文件;

通过对Java应用运行依赖的JVM参数、运行目录等内容的分析,需要分别通过K8S内置环境变量、Configmap、PV/PVC等功能进行不同程度的集成。

JVM参数管理

一套统一标准的JVM参数便于运维团队对Java进程的统一管理,例如统一的内存参数、日志目录、gc日志等等。我们将统一的JVM参数定义如下:

-server
-Xms2048m
-Xmx2048m
-XX:MaxPermSize:256m
-Dapp.name=test
-Denv=prod
-Djava.io.tmpdir=/tmp
# gc.log输出到统一的日志目录
-Xloggc:/data/logs/gc.log
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps 
-XX:+PrintGCDateStamps
-XX:+PrintHeapAtGC
-XX:+PrintReferenceGC
-Dsun.jnu.encoding=UTF-8
-XX:+HeapDumpOnOutOfMemoryError 
# dump文件输出到统一的日志目录
-XX:HeapDumpPath=/data/logs/HeapDumpOnOutOfMemoryError.dump

其中:

  • "-Xms2048m -Xmx2048m"从JVM参数拆分,需要K8S环境变量单独传递变量,以便应用内存个性化调整;
  • "-Denv=prod"从JVM参数拆分,需要K8S环境变量单独传递变量,以便不同环境应用配置文件访问;
  • "-Dapp.name=test"在Dockerfile构建阶段就通过代码内部的变量进行自动配置;
  • 剩余的JVM参数因没有个性化配置,统一通过configmap配置文件进行统一管理;

1.Dockerfile 参数化传递

# vim Dockerfile
FROM harbor.xxx.com/public/centos-jdk8:1.0.0
MAINTAINER yunwei
#VOLUME /tmp
ARG JAR_FILE
ARG APP_NAME
ENV APP_NAME=${APP_NAME}
COPY ${JAR_FILE} ${APP_NAME}.jar
ENTRYPOINT ["/bin/sh","-c","java -Dapp.name=${APP_NAME} -Denv=${PROFILE} ${XM} -Xbootclasspath/a:/data/config/${APP_NAME} $JVM_OPTS -jar /${APP_NAME}.jar"]

其中:

  • "-Dapp.name"通过gradle打包镜像传递;
  • "-Denv"和"XM"通过K8S环境变量传递;
  • "-Xbootclasspath"为配置文件共享目录;
  • "$JVM_OPTS"通过K8S的configmap传递;

2.K8S集成变量和配置

我们通过Deployment中的环境变量和configmap来进一步集成环境运行参数:

# vim configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: jvmoptions
  namespace: test
data:
  JVM_OPTS: "-server -XX:MaxPermSize:256m -Djava.io.tmpdir=/tmp -Xloggc:/data/logs/gc.log -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -XX:+PrintReferenceGC -Dsun.jnu.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/logs/HeapDumpOnOutOfMemoryError.dump"

# vim Deployment.yaml
apiVersion: apps/v1
kind: Deployment
...省略...
    spec:
      containers:
        - name: sysmonitor
          env:
            - name: PROFILE
              value: "test"
            - name: XM
              value: "-Xms2048m -Xmx2048m"
            - name: JVM_OPTS
              valueFrom:
                configMapKeyRef:
                  name: jvmoptions
                  key: JVM_OPTS
...省略...

目录管理

通过JVM参数可以发现,应用运行依赖的运行目录主要有:

  • /data/logs,gc日志目录和运行日志目录;
  • /data/config/应用名,应用名的配置文件目录;

以上目录我们一定要持久化,以便应用崩溃时能够进行排查或后续日志收集,K8S的解决方案是通过PV/PVC的方式实现持久卷的管理。

  • PV是集群中的一块存储,可以由管理员事先制备(静态制备), 或者使用存储类(Storage Class)来动态制备
  • PVC是用户对存储的请求, Pod 通过 PVC 申领 PV 资源,实现对存储的使用。

虽然静态制备和动态制备都能实现我们对存储资源的使用,但是从目前对目录使用情况的分析,静态制备是最适合我们的使用方案。而动态制备更适用于多目录及不同等级存储资源的需求及动态分配,对我们目前的使用量不太适用。
对于存储的适用,我们统一基于NFS的文件存储:

# vim static-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-static
  labels:
    type: pv-static
spec:
  storageClassName: nfs
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Recycle
  mountOptions:
    - vers=3
    - async
    - rsize=1048576
    - wsize=1048576
  nfs:
    path: /data
    server: 10.10.20.250
  capacity: 
    storage: 10Gi

# vim static-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-static
  namespace: test
  labels:
    type: pvc-static
spec:
  storageClassName: nfs
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 5Gi

# Deployment挂载
# vim deployment.yaml
apiVersion: apps/v1
kind: Deployment
...省略...
  template:
    spec:
      containers:
        - name: test
          env:
            - name: PROFILE
              value: "test"
            - name: XM
              value: "-Xms2048m -Xmx2048m"
            - name: JVM_OPTS
              valueFrom:
                configMapKeyRef:
                  name: jvmoptions
                  key: JVM_OPTS
...省略...
          ports:
            - containerPort: 8090
          volumeMounts:
            - name: data
              mountPath: /data
...省略...
      volumes:
        - name: data
          persistentVolumeClaim:
            claimName: pvc-static

我们在Nas上创建一个 data (10G)共享目录,可方便对配置文件进行统一管理:

  • 将Nas 共享目录挂载到K8S 的一个master节点上,可在容器外进行管理;
  • /data/config目录下的所有应用配置文件,后续可在容器外手动git pull更新;
  • 通过pv/pvc 将Nas 共享目录 /data 挂载到应用pod容器内的 /data 目录,多个pod共享文件;
  • 其他logs或其他目录均由应用自动生成;

Deployment/Service/Ingress

完整的一套应用上K8S的部署如下:

# vim helloworld.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: helloworld
  namespace: test
spec:
  replicas: 1
  selector:
    matchLabels:
      app: helloworld
  template:
    metadata: 
      name: helloworld
      labels:
        app: helloworld
    spec:
      containers:
        - name: helloworld
          env:
            - name: PROFILE
              value: "p2"
            - name: XM
              value: "-Xms2048m -Xmx2048m"
            - name: JVM_OPTS
              valueFrom:
                configMapKeyRef:
                  name: jvmoptions
                  key: JVM_OPTS
          image: harbor.xxx.com:8000/helloworld:1.1.21
          imagePullPolicy: IfNotPresent
          livenessProbe:
            httpGet:
              path: /app/health
              port: 8090
            initialDelaySeconds: 60
            timeoutSeconds: 5
          readinessProbe:
            httpGet:
              path: /app/health
              port: 8090
            initialDelaySeconds: 60
            timeoutSeconds: 5
          ports:
            - containerPort: 8090
          volumeMounts:
            - name: data
              mountPath: /data
      imagePullSecrets: 
        - name: harbor-secret
      volumes:
        - name: data
          persistentVolumeClaim:
            claimName: pvc-static
        
---
apiVersion: v1
kind: Service
metadata:
  name: helloworld
  namespace: test
spec:
  type: NodePort
  selector:
    app: helloworld
  ports:
    - port: 8090
      targetPort: 8090

---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: helloworld
  namespace: test
spec:
  rules:
    - host: helloworld.xxx.net
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: sysmonitor
                port:
                  number: 8090

总结

应用上K8S后并不意味着结束,相反我们仍还有其他需要工作要做:

  • Prometheus监控应用状态;
  • 流水线实现应用的版本更新、快速交付;
  • 应用根据负载的弹性伸缩;
  • 应用的日志收集;

其实本文应用的日志最终写在NFS共享存储上,还是将其持久化到集群node节点上。尤其是在上百已经级别的情况下,毕竟NFS的性能也是我们不得不面对的一个问题。其最终的解决方案,需要我们去从容选择。