创建容器镜像
编写正确、高效的Dockerfile
写一个最简单的Dockerfile
# Dockerfile.busybox
FROM busybox # 选择基础镜像
CMD echo "hello world" # 启动容器时默认运行的命令
FROM
表示选择构建使用的基础镜像,相当于”打地基“,这里使用的是busybox
- 第二条
CMD
,它指定docker run
启动容器之后默认运行的命令,这里是用了echo
命令输出hello world
使用docker build
来创建出镜像
docker build -f Dockerfile .
Sending build context to Docker daemon 2.048kB
Step 1/2 : FROM busybox
---> 71a676dd070f
Step 2/2 : CMD echo "hello world"
---> Running in b62810bb8826
Removing intermediate container b62810bb8826
---> e79c13b4d138
Successfully built e79c13b4d138
-f
参数指定Dockerfile
文件名,后面必须跟一个文件路径,叫做构建上下文,这里是一个简单的.
号,表示当前路径的意思;Docker
逐行读取指令,依次创建镜像层,最后生成一个完整的镜像。
root@ARM-Virtual-Machine:/home/k8s-learn# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
<none> <none> e79c13b4d138 4 minutes ago 1.41MB
busybox latest 71a676dd070f 9 months ago 1.41MB
nginx 1.21-alpine e08a7adafd85 9 months ago 22.1MB
redis latest f16c30136ff3 10 months ago 107MB
alpine latest 8e1d7573f448 11 months ago 5.33MB
新的镜像暂时还没有名字,可以看到是一个<none>
,但我们可以直接使用IMAGE ID
来查看或者运行。
wxvirus@ARM-Virtual-Machine:/etc/apt$ docker inspect e79c13b4d138
[
{
"Id": "sha256:e79c13b4d138a408dbf08336c75dab09d7e1a75663f6d02f5cbfadc99656b414",
"RepoTags": [],
"RepoDigests": [],
"Parent": "sha256:71a676dd070f4b701c3272e566d84951362f1326ea07d5bbad119d1c4f6b3d02",
"Comment": "",
"Created": "2022-10-21T14:45:28.663079802Z",
"Container": "b62810bb88264b6f9ae80c415e11f19729248c050401750bda597f935c482108",
"ContainerConfig": {
"Hostname": "b62810bb8826",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
],
"Cmd": [
"/bin/sh",
"-c",
"#(nop) ",
"CMD [\"/bin/sh\" \"-c\" \"echo \\\"hello world\\\"\"]"
],
"Image": "sha256:71a676dd070f4b701c3272e566d84951362f1326ea07d5bbad119d1c4f6b3d02",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": null,
"OnBuild": null,
"Labels": {}
},
"DockerVersion": "20.10.12",
"Author": "",
"Config": {
"Hostname": "",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
],
"Cmd": [
"/bin/sh",
"-c",
"echo \"hello world\""
],
"Image": "sha256:71a676dd070f4b701c3272e566d84951362f1326ea07d5bbad119d1c4f6b3d02",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": null,
"OnBuild": null,
"Labels": null
},
"Architecture": "arm64",
"Variant": "v8",
"Os": "linux",
"Size": 1411612,
"VirtualSize": 1411612,
"GraphDriver": {
"Data": {
"MergedDir": "/var/lib/docker/overlay2/1fd0a027cca34b60f52f9857f1cb3fce5c8e7562ad035a484a75f7232f8d13eb/merged",
"UpperDir": "/var/lib/docker/overlay2/1fd0a027cca34b60f52f9857f1cb3fce5c8e7562ad035a484a75f7232f8d13eb/diff",
"WorkDir": "/var/lib/docker/overlay2/1fd0a027cca34b60f52f9857f1cb3fce5c8e7562ad035a484a75f7232f8d13eb/work"
},
"Name": "overlay2"
},
"RootFS": {
"Type": "layers",
"Layers": [
"sha256:468ad4d964cd60bde6271569ed2459b3cd52ee8a5a621f3f5fae86efa37e390c"
]
},
"Metadata": {
"LastTagTime": "0001-01-01T00:00:00Z"
}
}
]
运行
docker run e79c13b4d138
怎么编写正确、高效的Dockerfile
第一条指令必须是FROM
,所以基础镜像的选择非常关键;如果关注的是镜像的安全和代销,那么一般会选择Alpine
;如果关注的是应用的运行稳定性,那么可能会选择Ubuntu、Debian、CentOS
FORM alpine:3.15 # 选择alpine 镜像
FROM ubuntu:bionic # 选择ubuntu镜像
我们在本机上开发测试时会产生一些源码、配置等文件,需要打包进镜像里,这时可以使用 COPY
命令,它的用法和 Linux
的 cp
差不多,不过拷贝的源文件必须是“构建上下文”路径里的,不能随意指定文件。也就是说,如果要从本机向镜像拷贝文件,就必须把这些文件放到一个专门的目录,然后在 docker build
里指定“构建上下文”到这个目录才行。
COPY ./a.txt /tmp/a.txt # 把构建上下文里的a.txt拷贝到镜像的/tmp目录
COPY /etc/hosts /tmp # 错误!不能使用构建上下文之外的文件
接下来是一个RUN
命令,它可以执行任意的Shell
命令,比如更新系统、安装应用、下载文件、创建目录、编译程序等。实现任意的镜像构建步骤,非常灵活。
但是Dockerfile
里指令只能是一行,所以所有的RUN
指令会在每行末尾加上续行符\
,命令之间也是由&&
来连接,这样保证逻辑上是一行。
RUN apt-get update \
&& apt-get install -y \
build-essential \
curl \
make \
unzip \
&& cd /tmp \
&& curl -fSL xxx.tar.gz -o xxx.tar.gz\
&& tar xzf xxx.tar.gz \
&& cd xxx \
&& ./config \
&& make \
&& make clean
我们如果觉得太长,可以使用上面的COPY
命令,将内容全部写到一个.sh
脚本里,使用COPY
命令拷贝进去再进行执行。
COPY setup.sh /tmp/ # 拷贝脚本到/tmp目录
RUN cd /tmp && chmod +x setup.sh \ # 添加执行权限
&& ./setup.sh && rm setup.sh # 运行脚本然后再删除
RUN
指令实际上就是 Shell
编程,如果你对它有所了解,就应该知道它有变量的概念,可以实现参数化运行,这在 Dockerfile
里也可以做到,需要使用两个指令 ARG
和 ENV
。
它们区别在于 ARG
创建的变量只在镜像构建过程中可见,容器运行时不可见,而 ENV
创建的变量不仅能够在构建镜像的过程中使用,在容器运行时也能够以环境变量的形式被应用程序使用。
使用ARG
定义基础 镜像名称,和使用ENV
来定义环境变量:
ARG IMAGE_BASE="node"
ARG IMAGE_TAG="alpine"
ENV PATH=$PATH:/tmp
ENV DEBUG=OFF
还有一个重要的指令是 EXPOSE
,它用来声明容器对外服务的端口号,对现在基于 Node.js、Tomcat、Nginx、Go
等开发的微服务系统来说非常有用:
每个指令都会生成一个镜像层,所以 Dockerfile 里最好不要滥用指令,尽量精简合并,否则太多的层会导致镜像臃肿不堪。
docker
也有不需要打包的东西,我们可以使用.dockerignore
和.gitignore
一样,排除那些不需要的文件
# docker ignore
*.swp # 排除以 .swp 结尾的文件
*.sh # 排除以 .sh 结尾的文件
构建的时候一般使用-f
来显式指定构建文件,如果省略这个参数则docker build
会在当前目录下找名字是Dockerfile
的文件,所以只有一个构建文件时,直接叫Dockerfile
是最省事的。
总结
- 创建镜像需要编写 Dockerfile,写清楚创建镜像的步骤,每个指令都会生成一个
Layer
。 Dockerfile
里,第一个指令必须是FROM
,用来选择基础镜像,常用的有Alpine
、Ubuntu
等。其他常用的指令有:COPY
、RUN
、EXPOSE
,分别是拷贝文件,运行Shell
命令,声明服务端口号。docker build
需要用 -f 来指定Dockerfile
,如果不指定就使用当前目录下名字是“Dockerfile
”的文件。docker build
需要指定“构建上下文”,其中的文件会打包上传到Docker daemon
,所以尽量不要在“构建上下文”中存放多余的文件。- 创建镜像的时候应当尽量使用 -t 参数,为镜像起一个有意义的名字,方便管理。
案例demo
# Dockerfile
# docker build -t ngx-app .
# docker build -t ngx-app:1.0 .
ARG IMAGE_BASE="nginx"
ARG IMAGE_TAG="1.21-alpine"
FROM ${IMAGE_BASE}:${IMAGE_TAG}
COPY ./default.conf /etc/nginx/conf.d/
RUN cd /usr/share/nginx/html \
&& echo "hello nginx" > a.txt
EXPOSE 8081 8082 8083
基本意思就是构建一个nginx
轻量级镜像,并配置一个默认配置,简单写了一个htm
,并暴露一些端口。