目录
目录 1
1. 前言 1
2. 基本概念 2
2.1. 仓库 2
2.2. 镜像ID和容器ID 2
3. 最简镜像 2
3.1. 目录结构 2
3.2. hello.go 2
3.3. Dockerfile 3
3.4. CMD和ENTRYPOINT 3
3.5. RUN和CMD 4
3.6. 生成镜像 4
3.7. 启动容器 5
4. 镜像进阶 5
4.1. 下载基础镜像 6
4.2. 准备本地程序源码 6
4.3. 编写Dockerfile 6
4.4. 生成镜像 7
4.5. 启动容器 7
5. 常见问题 7
5.1. stat /bin/sh: no such file or directory 7
5.2. COPY failed: ... stat no such file or directory 7
5.3. exec user process caused "no such file or directory" 8
附:安装GO 8
- 前言
本文介绍在CentOS7上从构建一个最简单无依赖的镜像开始,逐步揭示Docker镜像的构建和Dockerfile的应用。
什么是镜像?可理解镜像(image)为一个可执行程序文件,而容器(container)则是进程(运行态),Kubernetes(即k8s)中的概念POD则相当于进程组。
谨记:容器运行在Linux内核之上,不包含位于内核之上的glibc等库,以及ls等命令。如果容器中的程序依赖glibc等库或者依赖ls等命令,则容器自身应当包含这些设施。另外,容器中的程序等必须和内核兼容,否则将会遇到“FATAL: kernel too old”错误,该错误和库文件ld-linux.so有关。
- 基本概念
- 仓库
Docker仓库(Repository)是存储Docker镜像的地方。
- 镜像ID和容器ID
镜像(image)是静态的,容器(container)是运行中的镜像。如果说镜像是程序文件,则容器是进程。把镜像ID看作文件名,则容器ID可视为进程ID,因此每次启动的容器ID是不相同的。
同一镜像可以启动多个容器,容器间的ID不会相同:
|
- 最简镜像
从最简镜像开始,有助于快速了解Dockerfile和Docker镜像的构建。
- 目录结构
|
- hello.go
GO编译出来的可执行程序不依赖libc、libdl、linux-vdso和libonion等库,可以构建最简单的Dockerfile和最小的镜像。hello.go源代码如下:
|
编译hello.go,生成可执行程序hello:
|
- Dockerfile
编写一个最简单(不基于任何已有镜像)的Dockerfile,仅将本地的hello程序打包到镜像中,并在启动容器时运行hello。内容如下:
|
Dockerfile格式解释:
关键词 | 说明 |
# | 表示注释 |
FROM | 用于指定基础镜像,scratch表示不基于任何基础镜像。 |
COPY | 表示复制本地文件到容器的指定目录,注意本地文件目录是相对Dockerfile文件所在的目录,而不是系统的根目录。如果是远端的文件,则需使用ADD命令。 |
CMD | 用于指定启动容器时默认执行的命令,一个Dockerfile只有最后一条CMD有效,其它的CMD会被忽略,CMD有三种书写格式。 |
- CMD和ENTRYPOINT
如果在Dockerfile中没有指定ENTRYPOINT,执行命令“docker run”也没有指定“--entrypoint”,则执行CMD指定的命令。另外,可通过命令行参数“--entrypoint”覆盖ENTRYPOINT。
Dockerfile中的CMD有三种书写格式:
| 书写格式 | 说明 |
格式1 | CMD ["executable","param1","param2"] | EXEC执行方式 |
格式2 | CMD ["","param2"] | 指定了ENTRYPOINT时,作为ENTRYPOINT的参数,请注意ENTRYPOINT也分EXEC和Shell两种书写格式。 |
格式3 | CMD command param1 param2 | Shell执行方式,这要求镜像中有可执行程序“/bin/sh”,执行时实际是: /bin/sh -c "command param1 param2", 如果镜像中无“/bin/sh”,则在启动容器时报错“stat /bin/sh: no such file or directory”。 |
- 什么是EXEC执行方式?
|
- 什么是Shell执行方式?
|
如果CMD和ENTRYPOINT组合使用,则两者均需JSON数组格式。
- RUN和CMD
Dockerfile中的每一条RUN命令均会产生一个新的镜像,因此应当尽可能减少RUN命令数,如使用“&&”将多条写成一条。
RUN mkdir /data/test && chown test /data/test |
RUN和CMD完全不同,RUN是生成镜像时执行,而CMD是启动容器时执行。RUN和镜像相关,CMD和容器相关。
- 生成镜像
执行命令“docker build”生成镜像(也叫构建镜像,一个镜像由镜像ID唯一标识),执行命令“docker images”查看镜像列表,生成镜像有点类似于编译。
|
- 启动容器
最简单的启动容器方法:
|
也可如下方式启动容器:
|
这里的参数“-i”和参数“-t”,分别表示:
参数 | 作用 |
-i | i是interactive的缩写,作用是让容器的标准输入保持打开,以进入命令交互界面模式 |
-t | t是tty的缩写,作用是让docker分配一个伪终端并绑定到容器的标准输入上 |
-d | d是deamon的缩写,作用是让容器以后台守护方式运行 |
-p | p是port的缩写,作用是指定端口映射 |
-P | P是port的缩写,作用是随机分配端口 |
--name | 为容器指定一个新的名字 |
--rm | 容器退出时自动删除,如果不指定,则需要通过命令“docker rm”来删除 |
- 镜像进阶
这一节的镜像不从零开始,而是基于已有镜像生成新的镜像。
从scratch创建一个实用的镜像不易,也是不必要的,除了学习目的。容器虽然运行在本地的Linux内核之上,但依赖的库(运行时环境)却需要容器本身包含,比如核心的libc和libdl等库。这也是在创建最简镜像时采用GO程序的原因,避免了这些依赖,然而实际中很难避免这些依赖,因此最好的办法是基于其它镜像构建自己的镜像。
alpine是Docker官方提交的只有5MB多大小的Linux镜像,包管理工具为apk,可以用来做学习研究用。alpine不带glibc库,它带的是musl libc(一个轻量级的C标准库)。如果有glibc需求,可用基于alpine的alpine-glibc镜像,这个也有Docker官方提供的。
另外,还有一个第三方的tinycore镜像,只有7MB多大小,包含了libc等更为丰富基础设施。如果可以访问docker.io,则可直接执行命令“docker pull tinycore”将tinycore镜像拉取到本地,否则通过Docker的镜像导出(先在一台可以访问docker.io机器上pull镜像,然后导出成tar文件)和导入功能间接拉取到。
不同的基础镜像除了所带的库等不同外,镜像大小也是考虑的重要因素之一,原则上越小越好,本节内容官方的Centos镜像。
- 下载基础镜像
这里选择官方的centos作为基础镜像,执行拉取镜像命令:
|
如想找其它的centos镜像,可执行命令“docker search centos”搜索。如果本地不能访问docker.io,则可在一台可访问docker.io机器先拉取下来,然后使用Docker的导出(save)导入(load)载入进来。
检查centos镜像是否可用:
|
检查镜像centos版本:
|
- 准备本地程序源码
以C程序为例,源代码如下:
|
编译生成可执行程序:
|
- 编写Dockerfile
|
- 生成镜像
|
- 启动容器
默认不带参数方式运行(因为Dockerfile.echo1中没有ENTRYPOINT,所以执行的是CMD部分命令):
|
带参数方式执行(实为“--entrypoint”方式):
|
上述等同于:
|
“--entrypoint”带参数方式如下(参数在最后,并不是“--entrypoint”值的一部分):
|
- 常见问题
- stat /bin/sh: no such file or directory
启动窗口时报如下错误,可能是Dockerfile中的CMD格式错误:
|
原因是CMD书写为Shell格式,但镜像中没有/bin/sh这个文件。
- COPY failed: ... stat no such file or directory
在创建镜像时报如下错误,是因为COPY命令的源文件或目录不是相对Dockerfile所在目录的路径,比如使用了本地路径。
|
比如下列COPY即会报这个错误:
|
解决办法是先将/bin/sh复制到Dockerfile文件所在目录,然后再创建镜像。
- exec user process caused "no such file or directory"
运行容器时报如下错误:
|
这个错误有多种原因,比如:
- Dockerfile非UNIX格式(换符符);
- 容器中的可执行程序依赖的库不存在,比如没有libc库;
- CMD格式错误。
附:安装GO
安装GO步骤:
- 下载安装包
从GO的官网(https://golang.org/dl/)上下载,选择Linux安装包(本文下载的为go1.13.5.linux-amd64.tar.gz)。
- 上传安装包
将安装包(比如go1.13.5.linux-amd64.tar.gz)上传到/usr/local目录。如果Linux能够访问网络,也可直接在/usr/local上下载,比如:
|
- 安装和设置
在/usr/local目录下解压即完成安装,实际上也可能解压到其它目录。
|
设置环境变量,以方便执行(go.sh可无可执行权限):
|
如果不想重新登录而直接生效,可手工直接执行一次go.sh:
|