dockerTips

dockerTips

优质教程:Docker — 从入门到实践


docker 的主要使用场景

当我的产品中包含了多个软件,同时包含了相关的数据的时候,我希望实现一个整体包,用户拿到这个整体包之后直接一键启动就可以看到效果,docker 就可以实现这个。

虽然虚拟机也可以实现这个,但是没有 docker 轻量。

配合 docker-compose,部署和维护更加的方便


我们一般将容器的数据文件挂载到宿主机上,假设这个数据有多个版本,那么只要挂载不同的目录,就可以实现容器不同数据的版本的切换,这样就很容易实现了容器数据多版本管理。


https://hub.docker.com/


镜像(image)相当于虚拟机模板,一个镜像可以以不同的容器名称启动多个容器


docker 相关技术栈

如何通过 x86 环境下载 arm 镜像

有的镜像会专门打一个 arm 版,例如 xxx-arm,有的则是集成在一个名字下面,比如这样

你如果在 x86 上 pull 这个镜像,再导出来,那在 arm 下是用不了的,你要在 pull 的时候指定文件签名,例如这个 tdengine 的 3.0.1.8 的 arm 版是

参考教程 docker x86平台 拉取 arm版镜像_test-lhc的博客-CSDN博客

docker pull tdengine/tdengine:3.0.1.8@sha256:4a4240eabc83908bafa38bb8a9a3155ddaabf193763c877f9c41d1d79bfcf2fc
docker pull tdengine/tdengine-aarch64:3.0.4.0

不过 tdengine 有一个专门的 arm 镜像,所以没这个问题,我只是举了个例子,实际你们直接用下面这个镜像就行了:Docker镜像地址

docker pull tdengine/tdengine-aarch64:3.0.1.8
docker pull tdengine/tdengine-aarch64:3.0.4.0

在一台 x86 电脑上 pull 完了,执行下面的命令就能导出 tar 包,执行命令的目录就是导出的 tar 包所在的目录。

参考教程 Docker容器镜像打成tar包_docker镜像打包成tar包_张某某啊哈的博客-CSDN博客

docker save -o tdengine-aarch64-3.0.1.8.tar docker.io/tdengine/tdengine-aarch64:3.0.1.8
docker save -o tdengine-aarch64-3.0.4.0.tar docker.io/tdengine/tdengine-aarch64:3.0.4.0

jib-maven-plugin

https://github.com/GoogleContainerTools/jib 将 Java 应用打包成 docker 镜像

教程:Docker 与 Jib(maven 插件版)实战_ITPUB 博客

docker 镜像下不下来的问题的解决方案,jibDockerBuild 发生 Unauthorized for registry-1.docker.io/library/openjdk_fangd_h 的博客-CSDN 博客

我想在本地搭建一个 docker 仓库,然后把镜像拉下来之后,离线打包,就跟 maven 的本地仓库一样。算了,我不想在本地装虚拟机,太重了,算了。

学会了这个插件,我就可以自己制作自己的 docker 包,然后全平台部署,哦 yes。

示例配置:

<!-- 打包 docker 镜像  -->
<plugin>
    <groupId>com.google.cloud.tools</groupId>
    <artifactId>jib-maven-plugin</artifactId>
    <version>3.2.1</version>
    <configuration>
        <!--from 节点用来设置镜像的基础镜像,相当于 Docerkfile 中的 FROM 关键字-->
        <from>
            <!-- 使用的公司仓库,可以替换,必须添加 host 192.168.115.208 harbor.dameng.io -->
            <image>
                openjdk:8u342-jre@sha256:46a298905a037f46e5bc93aae3b061e0e148beab5098e6d0c26d5e7981ac36e3
            </image>
            <auth>
                <username>${docker.auth.username}</username>
                <password>${docker.auth.password}</password>
            </auth>
        </from>
        <to>
            <!-- 镜像名称和 tag,用英文冒号分割-->
            <!-- 使用属性 docker.image.name 表示打出的 image 的名称,不建议使用当前构建的名字 ${project.name} ,因为 image 的名字不能有大写字母-->
            <image>${docker.image.name}:1.0</image>
            <!--                        <auth>-->
            <!--                            <username>${docker.auth.username}</username>-->
            <!--                            <password>${docker.auth.password}</password>-->
            <!--                        </auth>-->
        </to>
        <outputPaths>
            <tar>${project.build.directory}/${docker.image.name}-image.tar</tar>
            <digest>${project.build.directory}/${docker.image.name}-image.digest</digest>
            <imageId>${project.build.directory}/${docker.image.name}-image.id</imageId>
            <imageJson>${project.build.directory}/${docker.image.name}-image.json</imageJson>
        </outputPaths>
        <!--容器相关的属性-->
        <container>
            <!-- 主程序类 -->
            <mainClass>com.example.tdenginedatainsert.TDengineDataInsertApplication</mainClass>
            <!--jvm 内存参数-->
            <jvmFlags>
                <jvmFlag>-XX:+UseContainerSupport</jvmFlag>
                <jvmFlag>-XX:InitialRAMPercentage=50.0</jvmFlag>
                <jvmFlag>-XX:MinRAMPercentage=50.0</jvmFlag>
                <jvmFlag>-XX:MaxRAMPercentage=75.0</jvmFlag>
                <jvmFlag>-Xss512K</jvmFlag>
                <jvmFlag>-XX:MetaspaceSize=256m</jvmFlag>
                <jvmFlag>-XX:MaxMetaspaceSize=512m</jvmFlag>
                <jvmFlag>-Djava.awt.headless=true</jvmFlag>
                <jvmFlag>-Dfile.encoding=utf-8</jvmFlag>
                <jvmFlag>-Djava.security.egd=file:/dev/./urandom</jvmFlag>
                <jvmFlag>-XX:+DisableExplicitGC</jvmFlag>
                <jvmFlag>-XX:-UseAdaptiveSizePolicy</jvmFlag>
                <jvmFlag>-Duser.timezone=GMT+08</jvmFlag>
            </jvmFlags>
            <!--要暴露的端口-->
            <ports>
                <port>8090</port>
            </ports>
            <creationTime>USE_CURRENT_TIMESTAMP</creationTime>
        </container>
        <!--开启允许上传镜像到仓库中-->
        <!--                    <allowInsecureRegistries>true</allowInsecureRegistries>-->
        <!--外部文件-->
        <!-- 注意两点路径的对应: -->
        <!-- 1. 项目运行时的工作路径,就是打出的 docker 包运行起来的容器的 Linux 系统的根路径 -->
        <!-- 2. extraDirectories 的 paths 的 path 的 into 标签中指定的相对路径的起点,是打出的 docker 包运行起来的容器的 Linux 系统的根路径 -->
        <extraDirectories>
            <paths>
                <path>
                    <from>${project.build.directory}/classes/config</from>
                    <into>/config</into>
                </path>
                <path>
                    <from>${project.build.directory}/classes/coordinateData</from>
                    <into>/coordinateData</into>
                </path>
            </paths>
        </extraDirectories>
    </configuration>
</plugin>
<!-- 打包 docker 镜像 end -->

如何使用本地的 tar 包来打镜像

https://github.com/GoogleContainerTools/jib/issues/1468

<from>
    <image>tar://path/to/saved.tar</image>
</from>

Docker run

runoob@runoob:~$ docker run -it nginx:latest /bin/bash  
root@b8573233d675:/#   

Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...]    
02.  
03.  -d, --detach=false         指定容器运行于前台还是后台,默认为 false     
04.  -i, --interactive=false   打开 STDIN,用于控制台交互    
05.  -t, --tty=false            分配 tty 设备,该可以支持终端登录,默认为 false    
06.  -u, --user=""              指定容器的用户    
07.  -a, --attach=[]            登录容器(必须是以 docker run -d 启动的容器)  
08.  -w, --workdir=""           指定容器的工作目录   
09.  -c, --cpu-shares=0        设置容器 CPU 权重,在 CPU 共享场景使用    
10.  -e, --env=[]               指定环境变量,容器中可以使用该环境变量    
11.  -m, --memory=""            指定容器的内存上限    
12.  -P, --publish-all=false    指定容器暴露的端口    
13.  -p, --publish=[]           指定容器暴露的端口   
14.  -h, --hostname=""          指定容器的主机名    
15.  -v, --volume=[]            给容器挂载存储卷,挂载到容器的某个目录    
16.  --volumes-from=[]          给容器挂载其他容器上的卷,挂载到容器的某个目录  
17.  --cap-add=[]               添加权限,权限清单详见:http://linux.die.net/man/7/capabilities    
18.  --cap-drop=[]              删除权限,权限清单详见:http://linux.die.net/man/7/capabilities    
19.  --cidfile=""               运行容器后,在指定文件中写入容器 PID 值,一种典型的监控系统用法    
20.  --cpuset=""                设置容器可以使用哪些 CPU,此参数可以用来容器独占 CPU    
21.  --device=[]                添加主机设备给容器,相当于设备直通    
22.  --dns=[]                   指定容器的 dns 服务器    
23.  --dns-search=[]            指定容器的 dns 搜索域名,写入到容器的/etc/resolv.conf 文件    
24.  --entrypoint=""            覆盖 image 的入口点    
25.  --env-file=[]              指定环境变量文件,文件格式为每行一个环境变量    
26.  --expose=[]                指定容器暴露的端口,即修改镜像的暴露端口    
27.  --link=[]                  指定容器间的关联,使用其他容器的 IP、env 等信息    
28.  --lxc-conf=[]              指定容器的配置文件,只有在指定--exec-driver=lxc 时使用    
29.  --name=""                  指定容器名字,后续可以通过名字进行容器管理,links 特性需要使用名字    
30.  --net="bridge"             容器网络设置:
31.                                bridge 使用 docker daemon 指定的网桥       
32.                                host    //容器使用主机的网络    
33.                                container:NAME_or_ID  >//使用其他容器的网路,共享 IP 和 PORT 等网络资源    
34.                                none 容器使用自己的网络(类似--net=bridge),但是不进行配置   
35.  --privileged=false         指定容器是否为特权容器,特权容器拥有所有的 capabilities    
36.  --restart="no"             指定容器停止后的重启策略:
37.                                no:容器退出时不重启    
38.                                on-failure:容器故障退出(返回值非零)时重启   
39.                                always:容器退出时总是重启    
40.  --rm=false                 指定容器停止后自动删除容器(不支持以 docker run -d 启动的容器)    
41.  --sig-proxy=true           设置由代理接受并处理信号,但是 SIGCHLD、SIGSTOP 和 SIGKILL 不能被代理    

这里重点说一下 -v 参数,这个参数除了指定挂载的路径外,还可以指定挂载内容的同步方式

docker run --name my-custom-nginx-container -v /host/path/nginx.conf:/etc/nginx/nginx.conf:ro -d nginx

其中 :ro 指定的就是 readonly 模式,其他模式呢?总共有三种模式 rw、ro 和不指定模式,这个参数关系到宿主机与容器的文件、文件夹变化关系,下面来一一详解

Docker desktop

https://www.docker.com/

放弃在 Windows 上装 docker desktop,报错

Unable to install Docker Desktop 4.8.2: Component CommunityInstaller.EnableFeaturesAction failed: Not found - Docker Desktop for Windows - Docker Community Forums

其中提到了电脑需要准备的东西 Install on Windows | Docker Documentation

如果是 Win11 家庭版,需要开启虚拟化,还要安装 WSL2,挺麻烦的,

Docker + Wasm = Awesome 挺诱人的

Linux 安装 docker

教程:centos7 安装 Docker 详细步骤(无坑版教程) - 腾讯云开发者社区-腾讯云


我们可以在容器启动之后通过 docker exec 进入容器

首先通过 docker ps 获取容器名称名称

方便起见,在 docker run 命令中,就通过 --name 参数指定容器名称

然后再执行语句

docker exec -it tdengine /bin/bash

然后就可以像进入了一个虚拟机中一样进行查看和修改,不过重启容器之后,这些修改就会丢失,所以保险的方式还是通过 -v 实现与主机的同步

也可以通过 docker attach tdengine 进入容器


E:\DaMeng\数据上传下达、项目部署、docker 包、安装步骤。txt 中的命令复制过来


docker exec 命令能够在运行着的容器中执行命令。docker exec 命令的使用格式:

docker exec [OPTIONS] container_name COMMAND [ARG...]
OPTIONS 说明:

-d,以后台方式执行命令;

-e,设置环境变量

-i,交互模式

-t,设置 TTY

-u,用户名或 UID,例如 myuser:myusergroup

# 直接交互式地进入容器内部
docker exec -it master /bin/bash
#  ./disql 是一个交互式的操作,我们可以通过这种方式,进行交互 ,查询或者执行脚本
docker exec -it dmdb /bin/bash -c 'cd /opt/dmdbms/bin ; ./disql SYSDBA/Dameng8888 ; '
# 直接执行某一种脚本
docker exec  dmdb /bin/bash -c 'cd /opt/dmdbms/bin ;  ./start.sh '

docker 容器的网络类型

Docker 容器运行的时候有 hostbridgenone 三种网络可供配置。默认是 bridge,即桥接网络,以桥接模式连接到宿主机;host 是宿主网络,即与宿主机共用网络;none 则表示无网络,容器将无法联网。

当容器使用 host 网络时,容器与宿主共用网络,这样就能在容器中访问宿主机网络,那么容器的 localhost 就是宿主机的 localhost

在 docker 中使用 --network host 来为容器配置 host 网络:注意,一旦采用 host 网络,将不需要 -p 端口隐射


自动重启

添加容器在 docker 运行时自动启有两种方法:

  1. 在使用 docker run 时,添加下面参数

--restart=always

  1. 在运行 docker 的时候添加

docker update --restart=always 07fb7442f813

其中 07fb7442f813 是容器 ID,当然你也可以将 07fb7442f813 替换为容器的名字

真鸡儿方便


指定了内存参数之后,-m 4096M,如果宿主机上剩余的内存不够,启动会失败。


通过 docker logs container_name 可以查看容器的启动日志 ,带上 -f 参数还能滚动更新,带上 --tail 200 可直接看最后 200 行,--tail 等同于 -n 代替

docker logs -f -n container_name

关于 docker 的所有命令:一张脑图整理 Docker 常用命令 - 三分恶的专栏 - SegmentFault 思否

我们也可以控制 docker 容器产生的日志的大小,避免容器中包含几百 G 的日志,这是可以配置的

官方文档:Configure logging drivers | Docker Docs

个人博客:【docker】docker 限制日志文件大小的方法+查看日志文件的方法 - Angel 挤一挤 - 博客园

  1. 新建 /etc/docker/daemon.json,若有就不用新建了。添加 log-dirverlog-opts 参数,样例如下:
$ vim /etc/docker/daemon.json
{
  "log-driver":"json-file",
  "log-opts": {"max-size":"500m", "max-file":"3"}
}

max-size=500m,意味着一个容器日志大小上限是 500M,

max-file=3,意味着一个容器有三个日志,分别是 id+.json、id+1.json、id+2.json。

  1. 然后重启 docker 的守护线程

命令如下:

systemctl daemon-reload
systemctl restart docker

【需要注意的是:设置的日志大小规则,只对新建的容器有效】


我们在删除容器,删除镜像的时候,

docker stop dmdb 
docker rm dmdb 
docker rmi -f $(docker images hub.dameng.com/arm64/cdb/dm8  -qa) 

如果 容器不存在,就会报错,此时我们可以通过将错误输出禁掉 (> /dev/null 2>&1) 来保证命令不报错

docker stop $(docker ps -q) > /dev/null 2>&1
docker rm $(docker ps -aq) > /dev/null 2>&1
docker rmi -f $(docker images -qa) > /dev/null 2>&1

这在使用 ansible 来远程部署的时候,非常有用。就算容器不存在,也不会被打断


docker rm -f container_name 如果容器正在运行,会强制停止


修改镜像的名称和版本:

docker tag 【镜像 ID】【镜像名称】:【tag 版本信息】 

docker tag 8ef375298394 mysql:v5.7

docker info:输出的内容包含了存储驱动和 docker 根目录的信息。

docker system prune -a:清理容器、网络文件、镜像和构建缓存(建议使用 Docker 命令来清理不再使用的容器)

docker inspect container_name : 查看容器的常规信息,包括保存路径等,有的时候,我们忘记了 docker run 的配置,可以在这里查找,比如 -v 的配置,可以在 HostConfig下的Binds 中看到。

docker image inspect nginx:查看镜像的常规信息


查看每个容器所占用的大小

docker system df -v

注意,这个容器大小,可能不准,最终,确定的容器的大小,还得看 /var/lib/docker/containers 下文件夹的大小。

Docker 镜像和容器的存储路径

Docker 容器由网络文件、卷和镜像组成。Docker 文件的存储路径取决于你的操作系统。常用操作系统中的路径如下:

例如在 Ubuntu 系统中,通过 docker info 查看 Docker Root Dir

Client: Docker Engine - Community
 Version:    25.0.3
 Context:    default
 Debug Mode: false
 Plugins:
  buildx: Docker Buildx (Docker Inc.)
    Version:  v0.12.1
    Path:     /usr/libexec/docker/cli-plugins/docker-buildx
  compose: Docker Compose (Docker Inc.)
    Version:  v2.24.5
    Path:     /usr/libexec/docker/cli-plugins/docker-compose

Server:
 Containers: 1
  Running: 0
  Paused: 0
  Stopped: 1
 Images: 2
 Server Version: 25.0.3
 Storage Driver: overlay2
  Backing Filesystem: extfs
  Supports d_type: true
  Using metacopy: false
  Native Overlay Diff: true
  userxattr: false
 Logging Driver: json-file
 Cgroup Driver: systemd
 Cgroup Version: 2
 Plugins:
  Volume: local
  Network: bridge host ipvlan macvlan null overlay
  Log: awslogs fluentd gcplogs gelf journald json-file local splunk syslog
 Swarm: inactive
 Runtimes: io.containerd.runc.v2 runc
 Default Runtime: runc
 Init Binary: docker-init
 containerd version: ae07eda36dd25f8a1b98dfbf587313b99c0190bb
 runc version: v1.1.12-0-g51d5e94
 init version: de40ad0
 Security Options:
  seccomp
   Profile: builtin
  cgroupns
 Kernel Version: 5.15.150.1-microsoft-standard-WSL2
 Operating System: Ubuntu 22.04.3 LTS
 OSType: linux
 Architecture: x86_64
 CPUs: 20
 Total Memory: 15.49GiB
 Name: LAPTOP-LK
 ID: 254e62ee-db00-419d-8a41-21d85456a8c9
 Docker Root Dir: /var/lib/docker
 Debug Mode: false
 Experimental: false
 Insecure Registries:
  127.0.0.0/8
 Live Restore Enabled: false

修改 Docker 的默认存储路径

How to change docker root data directory and why would you want to do that (hint: to optimize space) | Diego A. Carrasco Gubernatis

还有一个中文版本的翻译:

https://zhuanlan.zhihu.com/p/95533274?utm_id=0

方法如下啊

官方文档的修改办法是编辑 /etc/docker/daemon.json 文件:

vi /etc/docker/daemon.json

默认情况下这个配置文件是没有的,这里实际也就是新建一个,然后写入以下内容:

{
  "data-root": "/www/docker"
}

此文件还涉及默认源的设定,如果设定了国内源,那么实际就是在源地址下方加一行,写成:

{
  "registry-mirrors": ["http://hub-mirror.c.163.com"],
  "data-root": "/www/docker"
}

保存退出,然后重启 docker 服务:

systemctl restart docker

再次查看 docker 信息,可以看到目录已经变成了设定的 /www/docker:

注意,这样迁移,在老目录下的时候 docker 创建的那些容器是不会自动迁移到新的目录下的,新的目录下的 docker 就是一个全新的 docker,你需要从头开始导入镜像,启动容器。

因此,对于容器中的需要持久化的数据,我们最好挂载到宿主机上,这样当我们迁移 docker 的根目录的时候,容器中的数据不会丢失。


docker 目前无法合并镜像,只能做到将多个将多个镜像导出到一个 tar 中。


watchtower,自动更新容器的工具,

GitHub 地址

https://github.com/containrrr/watchtower?spm=a2c6h.12873639.article-detail.9.119f6d67l4VJ9q

使用博客

https://p3terx.com/archives/docker-watchtower.html

podman 也支持自动更新镜像

https://docs.podman.io/en/latest/markdown/podman-auto-update.1.html


docker 镜像的导出和导入

docker 镜像导出保存为 tar 和 tar 包导入成 docker 镜像_docker tar_万山寒的博客-CSDN 博客

# AAA:8.2,8.2 表示镜像版本号
docker save -o /opt/tar/名称.tar AAA:8.2 BBB:5.6

docker stats 命令可以跟 top 命令一样,监控每个容器的 CPU 占用,内存,磁盘,网络 IO,磁盘 IO 等性能指标,非常好用

输出 描述
CONTAINER 以短格式显示容器的 ID。
CPU % CPU 的使用情况。
MEM USAGE / LIMIT 当前使用的内存和最大可以使用的内存。
MEM % 以百分比的形式显示内存使用情况。
NET I/O 网络 I/O 数据。
BLOCK I/O 磁盘 I/O 数据。
PIDS PID 号。

Docker 不适合部署数据库的 7 大原因-阿里云开发者社区

为什么不建议把数据库装 Docker 里?

在 Docker 中水平伸缩只能用于无状态计算服务,而不是数据库

Docker 快速扩展的一个重要特征就是无状态,具有数据状态的都不适合直接放在 Docker 里面,如果 Docker 中安装数据库,存储服务需要单独提供。

目前,TX 云的 TDSQL(金融分布式数据库)和阿里云的 Oceanbase(分布式数据库系统)都直接运行中在物理机器上,并非使用便于管理的 Docker 上。


docker swarm 管理 docker 集群,确实简单又好用

Swarm mode - Docker — 从入门到实践


Linux 下有没有工具,可以整个 docker 防火墙,只允许想要指定的端口放行的? - V2EX

我本地没有这个问题,如果本地开启了防火墙(firewalld),没有开启容器需要的端口,然后直接在 docker run -p 中指定这个端口,启动的时候会直接报错。

而且在防火墙关闭的情况下,启动了容器,再开启防火墙,防火墙中没有设置此端口放行,那么容器将不能正常使用。

这都证明了 docker 是没有绕过防火墙或者设置了防火墙规则的


服务器的防护墙程序,可能会在服务器重新启动之后自启,这可能会导致服务无法访问。

在容器内部执行交互式命令

当我们直接在主机上安装数据库的时候,通过数据库自带的交互式命令来执行 sql,可以通过 echo + | 来退出交互式命令,例如

echo exit | echo start  /ansible/distData/appdata/dmdb/database_dm.sql | ./disql SYSDBA/SYSDBA

但是在容器中安装数据库之后,需要在容器中来执行上述命令,但是无法直接实现,比如下面这样的语句无法执行成功

docker exec -it dmdb echo exit | echo start  /ansible/distData/appdata/dmdb/database_dm.sql | ./disql SYSDBA/SYSDBA

一种简单的方式是,将 echo exit | echo start  /ansible/distData/appdata/dmdb/database_dm.sql | ./disql SYSDBA/SYSDBA,保存为一个脚本,映射到容器内部,然后再在宿主机中通过 docker exec -it dmdb xxx.sh 来执行这个脚本,这样就不会有问题了。

我们甚至可以将 echo exit | echo start  /ansible/distData/appdata/dmdb/database_dm.sql | ./disql SYSDBA/SYSDBA 写成一个方法,将 /ansible/distData/appdata/dmdb/database_dm.sql 当成一个参数传进来,这样就更方便了。

关于交互式命令,我们在《Linux 小技巧》中了解过。

启动容器的时候报错:Docker Networking Disabled: WARNING: IPv4 forwarding is disabled. Networking will not work

参考博客:Docker Networking Disabled: WARNING: IPv4 forwarding is disabled. Networking will not work

/etc/sysctl.conf 中添加

net.ipv4.ip_forward=1

然后重启网络

systemctl restart network

然后再启动容器即正常

通过 docker run 启动容器之后忘记配置怎么办?

参考博客:linuxea:如何复现查看docker run参数命令 - LinuxEA

可以通过 rekcod 找回,过程很简单

拉取镜像,然后给这个镜像的启动命令给一个别名,方便调用

$ docker pull nexdrew/rekcod
$ alias rekcod="docker run --rm -v /var/run/docker.sock:/var/run/docker.sock nexdrew/rekcod"

然后使用

$ rekcod <container> 

或者你可以直接使用,如果你本地没有下载这个镜像,系统会在后台自己下载。

$ docker run --rm -v /var/run/docker.sock:/var/run/docker.sock nexdrew/rekcod <container>

其实没有必要这样大费周章,推荐直接使用 docker-compose 编排容器。

docker stop xxx 的时候如何优雅地关闭 SpringBoot 打包出来的 container

一般情况下,以下配置是可行的

ENTRYPOINT ["java", "-jar", "/app.jar"]

如果不行,改成

ENTRYPOINT [ "sh", "-c", "exec java -jar  /app.jar"]

注意,exec 和后面的语句要在一起,不能分为两个数组元素