创建容器镜像
编写正确、高效的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,并暴露一些端口。