其实大部分的都在前面的 CMD 里讲了原因
自己做的镜像的话是没有前台运行,主进程没维持容器存活
官方镜像的话多半是(或者你挂载的)配置文件或者自己传入的启动参数导致没前台运行,就像前面说的例如redis配置文件里写日志文件路径会导致非前台运行
错在你,你是不是挂载了配置文件或者目录了?entrypoint 或者 cmd 覆盖了带上-ti
选项主进程用 sh 或者bash 进去然后按照你的参数启动你的进程或者看下目录就知道了 。
ENTRYPOINT ["npm"]
CMD ["run", "xxx"]
假设镜像的属性为上面,我们run不起来,可以下面进去后手动启动看看报错啥的,调整了后起来了把修改的步骤做到Dockerfile里
docker run -ti --rm --entrypoint sh ImageName
(这里说一句,tomcat 和基于 tomcat 的应用正常 docker stop 的话退出码是非0的是 137 很正常,可以github上搜下原因)
也有种情况是 ulimit 数值太小,这个我之前帮人远程看 k8s 的 ingress-nginx 的无法启动,最终找到的原因
在重启或者断电后,镜像可能损坏了,该镜像启动的容器会报错:
$ docker run --rm -ti --entrypoint sh xxxx
standard_init_linux.go:207: exec user process caused "no such file or directory"
解决办法就是删掉该镜像,然后 docker load -i xxx.tar.gz
后再试试。如果还是有问题,save 下该镜像看看有报错不,有的话可以参考 docker daemon layer broken 文章里的解决办法。
一般是因为该二进制是 glibc 下编译的,挂载到 musl 的 alpine 里会这样,可以使用 glibc 的 alpine,或者安装 libc6-compat 不行再试试 gocmpat
这种情况看下镜像的docker inspect coredns/coredns:1.1.3 -f '{{.Architecture}}'
, 执行下 arch
命令看看和镜像的架构是否对应得上,一般这个问题是镜像架构和系统的 cpu 架构不一致。不一致的话构建镜像的RUN阶段和起容器执行命令都会出现这个问题。
也可能是容器里的二进制文件架构不对,可以把容器用 docker create --name test xxx
起来后 docker cp test:/xxx
到宿主机上,file
命令看下它的架构
多阶段构建,前面阶段构建失败,但是后面的构建还是走完了,go 里编译的二进制运行就会这个错误,其他的语言不确定是不是这个错误
如果 entrypoint 是脚本,那第一行的 shell bang 的 shell,例如 alpine 没有 bash,你第一行声明 bash 执行就会这样。另外就下面这种情况了
这个问题就是动态链接库的原因,底层的so文件不存在,可以ldd看看你编译的二进制文件,然后在容器里按照ldd的输出查找下so存在否。
对于这个问题如果是官方的二进制文件在alpine镜像里无法运行我们可以试试这样
RUN mkdir /lib64 \
&& ln -s /lib/libc.musl-x86_64.so.1 /lib64/ld-linux-x86-64.so.2
说到这个现在我们也顺带来探究下为啥hello-world镜像才几kb,下面是hello-world的dockerfile https://github.com/docker-library/hello-world/blob/master/amd64/hello-world/Dockerfile
FROM scratch
COPY hello /
CMD ["/hello"]
这个 hello 就是 c语言写的编译的二进制文件,scratch 就是空镜像,所有 rootfs 就是从空镜像构建的。我们可以在仓库上找到这个文件的源码
//#include <unistd.h>
#include <sys/syscall.h>
#ifndef DOCKER_IMAGE
#define DOCKER_IMAGE "hello-world"
#endif
#ifndef DOCKER_GREETING
#define DOCKER_GREETING "Hello from Docker!"
#endif
#ifndef DOCKER_ARCH
#define DOCKER_ARCH "amd64"
#endif
const char message[] =
"\n"
DOCKER_GREETING "\n"
"This message shows that your installation appears to be working correctly.\n"
"\n"
"To generate this message, Docker took the following steps:\n"
" 1. The Docker client contacted the Docker daemon.\n"
" 2. The Docker daemon pulled the \"" DOCKER_IMAGE "\" image from the Docker Hub.\n"
" (" DOCKER_ARCH ")\n"
" 3. The Docker daemon created a new container from that image which runs the\n"
" executable that produces the output you are currently reading.\n"
" 4. The Docker daemon streamed that output to the Docker client, which sent it\n"
" to your terminal.\n"
"\n"
"To try something more ambitious, you can run an Ubuntu container with:\n"
" $ docker run -it ubuntu bash\n"
"\n"
"Share images, automate workflows, and more with a free Docker ID:\n"
" https://hub.docker.com/\n"
"\n"
"For more examples and ideas, visit:\n"
" https://docs.docker.com/get-started/\n"
"\n";
void _start() {
//write(1, message, sizeof(message) - 1);
syscall(SYS_write, 1, message, sizeof(message) - 1);
//_exit(0);
syscall(SYS_exit, 0);
}
官方的 cmakefile 里我们可以找到编译用的 gcc,编译参数为
CFLAGS := -static -Os -nostartfiles -fno-asynchronous-unwind-tables
先说着这几个编译参数的作用
-
-static
表示静态链接,虽然对这个程序来说无所谓动态链接还是静态链接……,可以理解为不依赖动态链接库了,全部打包了; -
-Os
表示为空间进行-O2
级别的优化,专门用于减少目标文件大小; -
-nostartfiles
是关键编译选项,此选项表示不使用标准 C语言运行库(即crt0.o
),也不链接C标准库; -
-fno-asynchronous-unwind-tables
选项也是用于减少代码空间的,其大概含义是不产生C++异常处理机制中使用的.eh_frame
段,关于什么是unwind-tables
和.eh_frame
是个比这篇文章复杂多了的问题; -
上面选项参考来源于What is the use of _start() in C?
When is the gcc flag -nostartfiles used?
官方的代码比较多余,我们整个简单的
#include <sys/syscall.h>
const char message[] =
"Hello World!"
"\n";
void _start() {
syscall(SYS_write, 1, message, sizeof(message) - 1);
syscall(SYS_exit, 0);
}
既然是讲docker,我们跑一个gcc容器去构建二进制文件吧,挂载目录,容器里编译(忽略警告),我们故意不带-static可以方便我们ldd看动态链接库的so
然后用编译的 hello 做个我们自己的 hello-world 镜像(当然肯定是跑不起来的- -)
虽然报错不一样,但是我们看到是跑不起来的(虽说和那个报错不一样,但是本质是一样的),其实上面这个测试步骤如果会多阶段构建的话不用那么繁琐。
下面就是针对这个测试步骤写的一个多阶段构建,第一阶段构建了个二进制文件,第二阶段直接从第一阶段拷贝过来(图里的$name写字符串,不需要带美元符)
同时我们也使用了 ARG 动态传入构建的参数,最终可以看到可以运行。
golang的话会发现实际的项目中放到alpine还是无法运行,是因为 cgo 没关闭,这里可以参考下面多阶段构建,编译的时候关闭 CGO
[root@centos7 OverTimeWork]# ll
total 32
drwxr-xr-x 2 root root 38 Aug 30 15:11 api
-rw-r--r-- 1 root root 348 Aug 30 16:15 Dockerfile
-rw-r--r-- 1 root root 183 Aug 30 16:19 go.mod
-rw-r--r-- 1 root root 16788 Aug 30 16:19 go.sum
-rw-r--r-- 1 root root 1289 Aug 30 16:19 main.go
drwxr-xr-x 2 root root 25 Aug 30 15:11 model
drwxr-xr-x 2 root root 37 Aug 30 15:11 service
[root@centos7 OverTimeWork]# cat Dockerfile
FROM golang:latest as builder
WORKDIR $GOPATH/src/OverTimeWork
COPY . $GOPATH/src/OverTimeWork
ENV GO111MODULE=on
ARG GOPROXY=https://mirrors.aliyun.com/goproxy/
#ARG GOPROXY=https://goproxy.io
RUN CGO_ENABLED=0 go build -o /root/main main.go
FROM alpine:latest
WORKDIR /root
COPY --from=builder /root/main .
EXPOSE 8080
ENTRYPOINT ["./main"]
留个作业: docker 跑一个gcc然后构建加-static编译出的hello用ldd输出还会看到so路径吗?
另外推荐一个文章,涉及到so的一个非常棒的文章https://docs.lvrui.io/2018/10/19/%E4%B8%BA%E5%AE%B9%E5%99%A8%E9%95%9C%E5%83%8F%E5%AE%9A%E5%88%B6%E5%AE%89%E8%A3%85Linux%E5%B7%A5%E5%85%B7/
这个问题几句话就简单讲清楚了
这个是因为bash的引号保持了整体性,出现这个问题的原因在于你的CMD或者ENTRYPOINT是选项和参数啥的没分开写,例如下面
ENTRYPOINT ["xxx","-a dfg -d sadased"]
解决办法是全部分开写
ENTRYPOINT ["xxx","-a","dfg","-d","sadased"]
同理后续 docker-compose 或者 k8s 的给容器的 command 和 args 覆盖成指定的时候,你可能写成下面类似的
command: npm run start
因为是yaml,command和args都是数组,一行就是一个元素了,应该写成下面这样
command: ["npm", "run", "start"] # 或者下面
command:
- npm
- run
- start
第一种情况遇到的比较多,可以主进程 sh 或者 bash 进去排查,后两种看一遍就知道了。
pod一直crash的话,我们把command用下面这个,生存探针啥的全部注释掉。command用bash,tail之类的会在调试完成delete kill的时候阻塞住,参考 infinite blockings 。
["bash", "-c", "coproc { exec >&-; read; }; eval exec \"${COPROC[0]}<&-\"; wait"
这个其实大多数情况和代码没关系,一般是安全软件导致的,极少数情况下是关机导致镜像损坏了。
$ docker ps -a | grep xxxxxxxxx-certserver
$ docker-compose up -d
Creating xxxxxxxxx-certserver ... error
ERROR: for xxxxxxxxx-certserver Cannot start service xxxxxxxxx-certserver: endpoint with name xxxxxxxxx-certserver already exists in network host
ERROR: for xxxxxxxxx-certserver Cannot start service xxxxxxxxx-certserver: endpoint with name xxxxxxxxx-certserver already exists in network host
ERROR: Encountered errors while bringing up the project.
$ docker ps -a | grep xxxxxxxxx-certserver
0f8709c76733 xxxxxxxxxx:5000/wps/xxxxxxxxx-certserver:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx "/bin/sh /data/webof…" 3 seconds ago Created xxxxxxxxx-certserver
$ docker-compose down
Removing xxxxxxxxx-certserver ... done
需要 disconnect 下
$ docker network disconnect --force host xxxxxxxxx-certserver
$ docker-compose up -d
Creating xxxxxxxxx-certserver ... done
$ docker ps -a | grep xxxxxxxxx-certserver
85443ce71285 xxxxxxxxxx:5000/wps/xxxxxxxxx-certserver:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx "/bin/sh /data/webof…" 4 seconds ago Up 3 seconds xxxxxxxxx-certserver