本篇不是介绍如何把一个大的镜像如何变小,主要实现的是,如何把 Java 服务镜像变化的部分变的尽可能的小。
方案依赖于 Kubernetes Pod 运行机制(纯Docker有兼容方案)。
Java服务的镜像都很大,当遇到离线环境需要拷贝的时候,每次都需要传递几百G的镜像,拷贝频繁时影响更大。
Java服务的镜像大主要在于基础运行环境,一个 openjdk 的镜像在 200M以上(jre环境更小,但是有限制),为了方便运维还会安装一些工具,最终可能会产生一个300M~500M左右的基础镜像。基础镜像的内容是基本不变的,可以联网传递 Docker 镜像时,分层机制也会只传输变化的层,真正的数据量基本上就是 Java 服务的 jar 包。但是离线环境时,传递的都是一个完整的镜像。
为了将传输的部分变的更小,可以将运行环境和程序分成两个独立的镜像,运行环境不会经常改变,离线部署时,只有第一次需要发送运行环境的镜像。
将 Java 打包好的 jar 文件放到一个单独的镜像中,这个镜像的大小基本上就是 jar 包的大小,每次更新服务的时候,只需要提供这个镜像。
1. 主容器
将容器运行的基础环境作为主容器进行运行,例如选择一个配置好各种工具的 openJdk11 镜像作为主容器。
2. 初始化容器
将程序打包的 jar,用最小镜像打包,最小镜像中只需要支持 mv 移动文件的命令即可(可用 busybox)。
初始化容器就是包含了服务程序的镜像,这个镜像无法单独运行,需要配合 主容器 运行。
3. 运行方式
纯 yaml 时可以一次性配置好,使用 Rancher 时需要分两步进行配置。
3.1 Rancher 方式
下面示例操作时,使用 Nginx + busybox 演示的,busybox 是init容器,会输出
index.html
文件。
选择主容器的镜像配置一个服务,配置好启动脚本(例如 java -jar /opt/dubbo-app/app.jar
),挂载一个 emptyDir 到 /opt/dubbo-app
目录,由于这个目录下面没有任何东西,所以命令启动的时候肯定会出错起不来。
主容器配置好后,在这个服务上点击【添加Sidecar】
配置带有服务程序的镜像为 Init容器
配置【数据卷】,这里会显示主容器已经挂载的卷,添加映射配置一个别的目录名(别覆盖容器中的已有目录)
打开高级选项配置【命令】,这一步就是要执行命令,把 app.jar 程序拷贝到 /app
目录(也就是主容器的 /opt/dubbo-app
目录,这俩是同一个目录在两个容器中的不同路径),命令内容配置如下:
点击下方的【启动】即可。
启动时,初始化容器先把程序拷贝到正确的位置,主容器此时在启动 app.jar 时就能启动成功。
后续服务升级的时候,只需要升级init初始化容器的版本。
3.2 yaml 方式
直接导出上面 Rancher 方式对应的 yaml,修改初始化容器的镜像和deploy的名称等各项信息。
apiVersion: apps/v1
kind: Deployment
metadata:
name: 服务名
namespace: default
spec:
progressDeadlineSeconds: 600
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
workload.user.cattle.io/workloadselector: deployment-default-服务名
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
type: RollingUpdate
template:
metadata:
labels:
workload.user.cattle.io/workloadselector: deployment-default-服务名
spec:
containers:
- image: openjdk11运行环境
imagePullPolicy: Always
name: 服务名
ports:
- containerPort: 80
name: 80tcp01
protocol: TCP
resources: {}
securityContext:
allowPrivilegeEscalation: false
capabilities: {}
privileged: false
procMount: Default
readOnlyRootFilesystem: false
runAsNonRoot: false
stdin: true
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
tty: true
volumeMounts:
- mountPath: /usr/share/nginx/html
name: vol1
dnsPolicy: ClusterFirst
initContainers:
- args:
- -c
- mv app.jar /app/app.jar
command:
- /bin/sh
image: 服务镜像jar:1.0.0
imagePullPolicy: Always
name: app-jar
resources: {}
securityContext:
allowPrivilegeEscalation: false
capabilities: {}
privileged: false
procMount: Default
readOnlyRootFilesystem: false
runAsNonRoot: false
stdin: true
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
tty: true
volumeMounts:
- mountPath: /html
name: vol1
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
terminationGracePeriodSeconds: 30
volumes:
- emptyDir: {}
name:
以上内容没有经过实际操作,仅供参考。
4. 纯 Docker 兼容方案
将带有 app.jar 的镜像发送给客户后,使用 Docker 多阶段构建创建一个新的镜像:
FROM 服务app-jar镜像 as app
FROM openjdk运行环境镜像
COPY --from=app app.jar /opt/dubbo-app/
通过上面的方式就可以将第一个镜像中的程序拷贝到第二个镜像中。
通过 docker build -t 服务镜像:版本号 .
就可以创建一个新镜像。启动创建的新镜像即可。
5. 参考
5.1 多阶段构建参考文章:
multistage-builddocker-images-part1-reducing-image-size
5.2 多容器Pod
communicate-containers-same-pod-shared-volumeinject-data-application/define-command-argument-container