uniquejava / blog

My notes regarding the vibrating frontend :boom and the plain old java :rofl.
Creative Commons Zero v1.0 Universal
11 stars 5 forks source link

Layered Spring Boot Docker images #292

Open uniquejava opened 4 years ago

uniquejava commented 4 years ago

阅读前必读:

  1. Best practices for writing Dockerfiles最佳实践
  2. Docker image为什么要分层

当前的spring boot Dockerfile配置

Dockerfile

FROM adoptopenjdk/openjdk8:ubi-jre
EXPOSE 9090
RUN mkdir /opt/app
ADD target/myapp.jar /opt/app/app.jar
ENTRYPOINT [ "sh", "-c", "java -Dspring.profiles.active=dev -jar /opt/app/app.jar" ]

Tips:spec中的command能覆盖Dockerfile中的ENTRYPOINT.

k8s-myapp-dev.yaml

spec:
  restartPolicy: Always
  containers:
    - image: jp.icr.io/myorg/myapp:1.0.15
      imagePullPolicy: Always
      command:
        [
          "sh",
          "-c",
          "java -Dspring.profiles.active=dev -jar /opt/app/app.jar",
        ]
      name: myapp
      volumeMounts:
        - name: logs-persistent-storage
          mountPath: /logs

当前build和push的方式

详见: https://cloud.ibm.com/kubernetes/registry/main/start

# Log in to your IBM Cloud account.
ibmcloud login -a https://cloud.ibm.com --sso

# Ensure that you're targeting the correct IBM Cloud Container Registry region.
ibmcloud cr region-set ap-north

# Choose a name for your first namespace, and create that namespace.
ibmcloud cr namespace-add <my_namespace>

# Log your local Docker daemon into the IBM Cloud Container Registry.
ibmcloud cr login

# 查看当前有哪些image (找到最新image的版本号)
ibmcloud cr images

# 做一个更新的image (注意每次将版本号x加1)
export VERSION=1.0.1
# 在本地编译java项目
cd myapp
mvn clean package -DskipTests=true
docker build -t myorg/myapp:$VERSION .

# 打两个tag
docker tag myorg/myapp:$VERSION jp.icr.io/myorg/myapp:$VERSION

# 切换到 Context A
ibmcloud ks cluster config --cluster xxxxxxxxx
# 推送到 A 环境
docker push jp.icr.io/myorg/myapp:$VERSION
# 修改 k8s-myapp-dev.yaml中的版本号
vi ../kubernetes/development/k8s-myapp-dev.yaml
# 重新apply
kubectl apply -f ../kubernetes/development/k8s-myapp-dev.yaml

存在的问题

docker images
docker history myorg/myapp:$VERSION

Dockerfile中的每一条指令构成一个新的layer, 通过docker history命令可以看到 ADD target/myapp.jar 这一步build了一个相当大的image layer(60M)

我们需要将spring boot中不变的dependency单独build成一个独立的image layer。

References

Optimizing Spring Boot apps for Docker

https://blog.jdriven.com/2019/08/layered-spring-boot-docker-images/

uniquejava commented 4 years ago

A better Dockerfile

每一步的含义见: https://spring.io/blog/2018/11/08/spring-boot-in-a-container

Dockerfile

FROM adoptopenjdk/openjdk8:ubi-jre

EXPOSE 9090

RUN mkdir /app

ARG DEPENDENCY=target/dependency
COPY ${DEPENDENCY}/BOOT-INF/lib /app/lib
COPY ${DEPENDENCY}/META-INF /app/META-INF
COPY ${DEPENDENCY}/BOOT-INF/classes /app

ENTRYPOINT [ "sh", "-c", "java ${JAVA_OPTS} -cp app:app/lib/* com.xxx.MyAwesomeApplication ${0} ${@}" ]

像这样打包:

mvn clean package -DskipTests=true
mkdir target/dependency
(cd target/dependency; tar -zxf ../*.jar)
docker build -t myorg/myapp:$VERSION .

新的yaml (通过yaml向docker传递JAVA_OPTS)

参考: Define Environment Variables for a Container

    spec:
      restartPolicy: Always
      containers:
        - image: jp.icr.io/myorg/myapp:1.0.16
          imagePullPolicy: Always
          env:
            - name: JAVA_OPTS
              value: "-Dspring.profiles.active=dev"
          name: myapp

完整的过程

# Log your local Docker daemon into the IBM Cloud Container Registry.
ibmcloud cr login

# 查看当前有哪些image (找到最新image的版本号)
ibmcloud cr images

# 做一个更新的image (注意每次将版本号x加1)
export VERSION=1.0.1
# 在本地编译java项目, dual layered, 详见 https://spring.io/blog/2018/11/08/spring-boot-in-a-container
cd myapp
mvn clean package -DskipTests=true
mkdir target/dependency
(cd target/dependency; jar -xf ../*.jar)
docker build -t myorg/myapp:$VERSION .

# 打tag
docker tag myorg/myapp:$VERSION jp.icr.io/myorg/myapp:$VERSION

# 切换到 context A
ibmcloud ks cluster config --cluster xxxx
# 推送到 A 集群
docker push jp.icr.io/myorg/myapp:$VERSION
# 修改 k8s-myapp-dev.yaml中的版本号
vi ../kubernetes/development/k8s-myapp-dev.yaml
# 重新apply
kubectl apply -f ../kubernetes/development/k8s-myapp-dev.yaml
# 查看pod状态
kubectl get po
# 查看spring boot启动的日志文件 (网络原因有点卡), 两个pod实例的日志交织在一起
# 10.47.84.37 是node的内部ip地址
../kubernetes/development/nsenter-node.sh 10.47.84.37
=> If you dont see a command prompt, try pressing enter.
=> root@kube-xxx:~#
=> root@kube-xxx:~# tail -f /mnt/myapp/logs/sa.log (查看后台日志文件)
=> root@kube-xxx:~# exit
(按Ctrl + D)
=> logout
=> pod "cyper-nsenter-10.47.84.37" deleted

# tips: 查看当前 k8s 上下文
kubectl config get-contexts
uniquejava commented 4 years ago

继续优化

mkdir target/dependency
(cd target/dependency; tar -zxf ../*.jar)

写到pom.xml里

详见: https://blog.jdriven.com/2019/08/layered-spring-boot-docker-images/