关注

Docker 镜像制作:包含自定义镜像及常用命令

Docker镜像制作

镜像制作及原因

镜像制作是因为某种需求,官方的镜像无法满足需求,需要自定义镜像来满足要求:

往往因为以下原因自己制作镜像:

  1. 编写的代码如何打包到镜像中直接跟随镜像发布
  2. 第三方制作的内容安全性未知,如含有安全漏洞
  3. 特定的需求或者功能无法满足,如需要数据库,加审计功能
  4. 公司内部要求基于公司内部的系统制作镜像,如公司内部要求使用自己的操作系统作为基础镜像

Docker镜像制作方式

制作容器镜像,主要有两种方法:

  • 制作快照方式获得镜像(偶尔制作的镜像):在基础镜像上(比如Ubuntu),先登录容器中,然后安装需要的所有软件,最后整体制作快照。
  • Dockerfile方式构建镜像(经常更新的镜像):将软件安装的流程写成Dockerfile,使用docker build构建成容器镜像。

快照方式制作镜像

制作命令

docker commit
  • 功能:从容器创建一个新的镜像。
  • 语法:
docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
  • 参数:
    • -a:提交的镜像作者;
    • -c:使用Dockerfile指令来创建镜像;可以修改启动指令
    • -m:提交时的说明文字;
    • -p:在commit时,将容器暂停。
  • 样例:
docker commit c3f279d17e0a drw/mynginx:v01

快照制作镜像实战

实战一、C++ HelloWorld镜像制作
  1. 创建临时工作目录
mkdir -p /data/drw/commitimage
cd /data/drw/commitimage
  1. 编写C++源代码文件,demo.cpp
#include <stdio.h>

int main()
{
    printf("hello docker!\n");
    return 0;
}
  1. 启动一个centos7的容器
root@139-159-150-152:/data/drw/commitimage# docker run -it --name mycppcommit centos:7 bash
[root@40de1bf45017 /]#
  1. 安装编译软件,并创建源代码目录
[root@40de1bf45017 /]# yum install -y gcc
[root@40de1bf45017 /]# mkdir /src
  1. 打开另外一个shell,拷贝源代码到容器中
root@139-159-150-152:/data/drw/commitimage# docker cp ./demo.c mycppcommit:/src
Successfully copied 2.048kB to mycppcommit:/src

# 查看容器中
[root@40de1bf45017 /]# ls -l /src
total 4
-rw-r--r-- 1 root root 80 May 16 05:30 demo.c
  1. 编译运行
[root@40de1bf45017 /]# cd /src
[root@40de1bf45017 src]# gcc demo.c -o demo
[root@40de1bf45017 src]# ./demo
hello docker!
  1. 提交为一个镜像
root@139-159-150-152:/data/maxhou/commitimage# docker commit mycppcommit mycppimg:v1.0
sha256:97d178ba9e5d94d8276fe0e23dc73510abea7f03f7f3c3a59a978dc8f+a2c

root@139-159-150-152:/data/maxhou/commitimage# docker images
REPOSITORY   TAG       IMAGE ID       CREATED          SIZE
mycppimg     v1.0      97d178ba9e5d   13 seconds ago   714MB
  1. 测试镜像能否正常运行
root@139-159-150-152:/data/maxhou/commitimage# docker run -it mycppimg:v1.0 /src/demo
hello docker!
实战二、Springboot镜像制作
  1. 启动一个java8的容器

原来的java8改名为了openjdk:8,openjdk也有openjdk17等镜像

root@139-159-150-152:/data/maxhou/commitimage# docker run -it --name myjavacommit openjdk:8 bash
root@9ac4233dc4c8:/# java -version
openjdk version "1.8.0_111"
OpenJDK Runtime Environment (build 1.8.0_111-8u111-b14-2~bpo8+1-b14)
OpenJDK 64-Bit Server VM (build 25.111-b14, mixed mode)
  1. 打开另外一个shell窗口,拷贝制作过的jar包到容器的目录
docker cp ./springboot-demo-1.0-SNAPSHOT.jar myjavacommit:/app.jar
  1. java -jar完成启动测试
root@9ac4233dc4c8:/# java -jar ./app.jar

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.7.8)

2023-05-16 06:09:30.161  INFO 16 --- [           main] com.bit.Main                             : Starting Main v1.0-SNAPSHOT using Java 1.8.0_111 on 9ac4233dc4c8 with PID 16 (/app.jar started by root in /)
2023-05-16 06:09:30.173  INFO 16 --- [           main] com.bit.Main                             : No active profile set, falling back to 1 default profile: "default"
2023-05-16 06:09:32.912  INFO 16 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8799 (http)
2023-05-16 06:09:32.961  INFO 16 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2023-05-16 06:09:32.962  INFO 16 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.71]
2023-05-16 06:09:33.186  INFO 16 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2023-05-16 06:09:33.187  INFO 16 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 2814 ms
2023-05-16 06:09:34.629  INFO 16 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8799 (http) with context path ''
2023-05-16 06:09:34.654  INFO 16 --- [           main] com.bit.Main                             : Started Main in 5.777 seconds (JVM running for 7.582)
  1. 执行docker commit
root@139-159-150-152:/data/maxhou/commitimage# docker commit -c 'CMD ["java", "-jar", "/app.jar"]' myjavacommit myjavaimg:v1.0
sha256:865d428c7a87ec54819bbb55d2e21d071f6149011d77a074ba638223ffc491a22

root@139-159-150-152:/data/maxhou/commitimage# docker images
REPOSITORY   TAG       IMAGE ID       CREATED          SIZE
myjavaimg    v1.0      865d428c7a87   18 seconds ago   661MB
  1. 使用新创建的容器启动一个服务检查是否能够正常运行
root@139-159-150-152:/data/maxhou/commitimage# docker run -p 8799:8799 -d --name mycommittjavaimg1 myjavaimg:v1.0
e336d7157d6c0cd0fba16a5aeb1f7ba2c19b449dde6320f7ac2880529b1d

root@139-159-150-152:/data/maxhou/commitimage# docker ps
CONTAINER ID   IMAGE          COMMAND                  CREATED         STATUS         PORTS                                       NAMES
e336d7157d6c   myjavaimg:v1.0   "java -jar /app.jar"     2 seconds ago   Up 2 seconds   0.0.0.0:8799->8799/tcp, :::8799->8799/tcp   mycommittjavaimg1
  1. 通过浏览器访问8799端口对应的接口

访问:http://139.159.150.152:8799/hello
返回:Hello docker!

Dockerfile制作镜像

Dockerfile是什么

镜像的定制实际上就是定制每一层所添加的配置、文件。如果我们可以把每一层修改、安装、构建、操作的命令都写入一个脚本,用这个脚本来构建、定制镜像,那么就是Dockerfile。

Dockerfile格式

# Comment
INSTRUCTION arguments

该指令不区分大小写。约定大写以便更容易地将它们与参数区分开。
以#开头的行视为注释。行中其他任何地方的标记#都被视为参数:

# Run echo 'we are running some # of cool things'
RUN echo 'we are running some # of cool things'

为什么需要Dockerfile

Dockerfile 是容器镜像构建的标准最佳实践,它将镜像构建流程代码化,彻底替代了黑箱式的 docker commit,实现了可重复、可追溯的自动化构建;它让镜像变更可审计、维护更便捷,还能通过多阶段构建剔除冗余文件,产出更轻量、标准化的镜像。

Dockerfile指令

指令清单
指令功能
FROM构建镜像基于哪个镜像,也就是基础镜像
MAINTAINER镜像维护者姓名或邮箱地址
LABEL为镜像添加元数据
COPY拷贝文件或者目录到镜像中,跟ADD类似
ADD拷贝文件或者目录到镜像中,如果是URL或压缩包便会自动下载或自动解压
WORKDIR指定工作目录
RUN指定docker build过程中运行的程序
VOLUME指定容器挂载点
EXPOSE声明容器的服务端口(仅仅是声明)
ENV设置环境变量
CMD运行容器时执行的命令
ENTRYPOINT运行容器时程序入口
ARG指定构建时的参数
SHELL指定采用哪个shell
USER指定当前用户
HEALTHCHECK健康检查指令
ONBUILD当前镜像被构建时,不会被执行,只有当以该镜像为基础镜像进行构建的时候才会被执行
STOPSIGNAL允许您覆盖发送到容器的默认信号

FROM

功能

  • FROM指令用于为镜像文件构建过程指定基础镜像,后续的指令运行于此基础镜像所提供的运行环境。
  • 实践中,基础镜像可以是任何可用镜像文件,默认情况下,docker build会在docker主机上查找指定的镜像文件,在其不存在时,则会自动从Docker Hub公共仓库拉取镜像下来。如果找不到指定的镜像文件,docker build会返回一个错误信息。
  • FROM可以在一个Dockerfile中出现多次,如果有需求在一个Dockerfile中创建多个镜像,或将一个构建阶段作为另一个的依赖。
  • 如果FROM语句没有指定镜像标签,则默认使用latest标签。

语法

FROM [--platform=<platform>] <image> [AS <name>]
FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]
FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]
  • 参数:
    • --platform=<platform>:构建的CPU架构,如linux/amd64, linux/arm64
    • <image>:指定作为base image的名称;
    • <tag>:base image的标签,省略时默认latest;
    • <digest>:镜像的哈希码;
    • AS <name>:指定构建的名称,配合COPY --from可以完成多级构建

样例

FROM mysql:latest

实战

  1. 创建Docker目录,确保目录没有内容
mkdir -p /data/mydocker/dockerfile/web1
cd /data/mydocker/dockerfile/web1
  1. 编辑Dockerfile,测试FROM指令和注释,在web1目录中vi Dockerfile,输入以下内容
#我的web站点 by me
FROM ubuntu:22.04 as buildbase
  1. 执行构建,打造镜像v0.1版本
root@139-159-150-152:/data/mydocker/dockerfile/web1# docker build -t web1:v0.1 .
[+] Building 0.1s (5/5) FINISHED
 => [internal] load build definition from Dockerfile
 => => transferring dockerfile: 95B
 => [internal] load .dockerignore
 => => transferring context: 2B
 => [internal] load metadata for docker.io/library/ubuntu:22.04
 => [1/1] FROM docker.io/library/ubuntu:22.04
 => exporting to image
 => => exporting layers
 => => writing image sha256:6c84b273e28b1d0a4f12b9d04dc8a329acddfb212134b961ee26
 => => naming to docker.io/library/web1:v0.1
  1. 运行制作的镜像,可以看到操作系统版本
root@139-159-150-152:/data/mydocker/dockerfile/web1# docker run -it --name test-ubuntu-22.04 web1:v0.1 cat /etc/release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=22.04
DISTRIB_CODENAME=jammy
DISTRIB_DESCRIPTION="Ubuntu 22.04.2 LTS"
PRETTY_NAME="Ubuntu 22.04.2 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04.2 LTS (Jammy Jellyfish)"
VERSION_CODENAME=jammy
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=jammy

MAINTAINER

功能

  • 用于让dockerfile制作者提供本人的详细信息
  • 该功能已经废弃,由label替代

语法

MAINTAINER <author's detail>
  • 参数:
    • <author's detail>:作者信息

样例

MAINTAINER "maxhou <[email protected]>"

实战

  1. FROM添加制作者信息,使用MAINTAINER
#我的web站点 by drw
FROM ubuntu:22.04 as buildbase
MAINTAINER "drw"
  1. 再次编译0.2版本
root@139-159-150-152:/data/mydocker/dockerfile/web1# docker build -t web1:v0.2 .
[+] Building 0.1s (5/5) FINISHED
 => [internal] load build definition from Dockerfile
 => => transferring dockerfile: 130B
 => [internal] load .dockerignore
 => => transferring context: 2B
 => [internal] load metadata for docker.io/library/ubuntu:22.04
 => CACHED [1/1] FROM docker.io/library/ubuntu:22.04
 => exporting to image
 => => exporting layers
 => => writing image sha256:b49e798e7b7b37dfd1edf796faa9cc2355dc7463b5219b7bc4134d8
 => => naming to docker.io/library/web1:v0.2
  1. 查看镜像信息,可以看到作者信息已经添加完成
root@139-159-150-152:/data/mydocker/dockerfile/web1# docker image inspect web1:v0.2
[
    ...
    "DockerVersion": "",
    "Author": "drw",
    ...
]

LABEL

功能

  • 为镜像添加元数据,元数据是key对形式

语法

LABEL <key>=<value> <key>=<value> <key>=<value> ...

样例

LABEL com.example.label-with-value="foo"
LABEL multi.label1="value1" multi.label2="value2" other="value3"

实战

  1. 我们使用LABEL添加额外的元数据信息,继续vi Dockerfile
#我的web站点 by maxhou
FROM ubuntu:22.04 as buildbase
MAINTAINER "maxhou <[email protected]>"
LABEL company="com.bit" app="nginx"
  1. 我们继续构建v0.3版本
root@139-159-150-152:/data/mydocker/dockerfile/web1# docker build -t web1:v0.3 .
[+] Building 0.1s (5/5) FINISHED
 => [internal] load build definition from Dockerfile
 => => transferring dockerfile: 188B
 => [internal] load .dockerignore
 => => transferring context: 2B
 => [internal] load metadata for docker.io/library/ubuntu:22.04
 => CACHED [1/1] FROM docker.io/library/ubuntu:22.04
 => exporting to image
 => => exporting layers
 => => writing image sha256:2d33a63430f8f43ceb3f1cc264f487499f05b0ffe46e5e5db53967ea5f24f
 => => naming to docker.io/library/web1:v0.3
  1. 查看镜像元数据
root@139-159-150-152:/data/mydocker/dockerfile/web1# docker inspect web1:v0.3
[
    ...
    "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/bash"
        ],
        "Image": "",
        "Volumes": null,
        "WorkingDir": "",
        "Entrypoint": null,
        "OnBuild": null,
        "Labels": {
            "app": "nginx",
            "company": "com.bit",
            "org.opencontainers.image.ref.name": "ubuntu",
            "org.opencontainers.image.version": "22.04"
        }
    },
    ...
]

COPY

功能

  • 用于从Docker主机复制文件或者目录至创建的新镜像指定路径中。

语法

COPY [--chown=<user>:<group>] <src>... <dest>
COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]
  • 参数:
    • <src>:要复制的源文件或目录,支持使用通配符;
    • <dest>:目标路径,即正在创建的image的文件系统路径;建议< dest>使用绝对路径,否则,COPY指定以WORKDIR为其起始路径;在路径中有空白字符时,通常使用第2种格式;
    • --chown:修改用户和组
    • --from <name>:可选的多阶段构建内容,结合FROM … AS < name>往往用作多级构建

注意事项

  • < src >必须是build上下文中的路径,不能是其父目录中的文件;
  • 如果< src> 是目录,则其内部文件或子目录会被递归复制,但< src>目录自身不会被复制;
  • 如果指定了多个< src>,或在< src>中使用了通配符,则< dest>必须是一个目录,且必须以/结尾;
  • 如果< dest>事先不存在,它将会被自动创建,这包括父目录路径。

样例

# 要确保dockerfile同级路径下有index.html文件
COPY index.html /data/web/html/

实战

  1. 创建一个index.html,作为我们站点的首页,vi index.html输入下面内容
<html>
    <h1>Hello My Home Page by bit</h1>
</html>
  1. 通过COPY命令添加到镜像中,并且指定我们的根目录为/data/web/www
#我的web站点 by drw
FROM ubuntu:22.04 as buildbase
COPY index.html /data/web/www/
  1. 再次编译v0.4版本镜像
root@139-159-150-152:/data/mydocker/dockerfile/web1# docker build -t web1:v0.4 .
[+] Building 0.2s (7/7) FINISHED
 => [internal] load build definition from Dockerfile
 => => transferring dockerfile: 199B
 => [internal] load .dockerignore
 => => transferring context: 2B
 => [internal] load metadata for docker.io/library/ubuntu:22.04
 => CACHED [1/2] FROM docker.io/library/ubuntu:22.04
 => [internal] load build context
 => => transferring context: 90B
 => [2/2] COPY index.html /data/web/www/
 => exporting to image
 => => exporting layers
 => => writing image sha256:ae167b38e1c42832e5012c8d0e417c7ba9745ea643f24ebcb5de9381ee2b
 => => naming to docker.io/library/web1:v0.4
  1. 运行镜像,可以看到index.html已经进去了
root@139-159-150-152:/data/mydocker/dockerfile/web1# docker run -it web1:v0.4 ls /data/web/www
index.html

ENV

功能

  • 用于为镜像定义所需的环境变量,并可被Dockerfile文件中位于其后的其它指令(如ENV、ADD、COPY等)所调用。调用格式为 $ variable_name或${variable_name}

语法

ENV <key>=<value> ...

样例

ENV myName="John Doe" myDog=Rex\ The\ Dog \
    myCat=fluffy

实战

  1. 目录后面可能变化,将目录的位置提取为变量,通过ENV来设置,再次编辑Dockerfile
#我的web站点 by drw
FROM ubuntu:22.04 as buildbase
ENV WEB_ROOT=/data/web/www/
COPY index.html ${WEB_ROOT}
  1. 再次编译v0.5版本
root@139-159-150-152:/data/mydocker/dockerfile/web1# docker build -t web1:v0.5 .
[+] Building 0.1s (7/7) FINISHED
 => [internal] load build definition from Dockerfile
 => => transferring dockerfile: 224B
 => [internal] load .dockerignore
 => => transferring context: 2B
 => [internal] load metadata for docker.io/library/ubuntu:22.04
 => CACHED [1/2] FROM docker.io/library/ubuntu:22.04
 => [internal] load build context
 => => transferring context: 31B
 => [2/2] COPY index.html ${WEB_ROOT}
 => exporting to image
 => => exporting layers
 => => writing image sha256:91a88cc63fecc10166610f43e16de20cb87b2a5dcb14a9d88
 => => naming to docker.io/library/web1:v0.5
  1. 运行v0.5版本镜像,验证index.html是否在$WEB_ROOT的目录下面
root@139-159-150-152:/data/mydocker/dockerfile/web1# docker run -it web1:v0.5 ls $WEB_ROOT
index.html
  1. 也可以通过inspect查看,可以看到环境变量已经被添加到镜像里面了
root@139-159-150-152:/data/mydocker/dockerfile/web1# docker image inspect web1:v0.5
[
    ...
    "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",
            "WEB_ROOT=/data/web/www/"
        ],
        ...
    },
    ...
]

WORKDIR

功能

  • 用于为Dockerfile中所有的RUN、CMD、ENTRYPOINT、COPY和ADD指定设定工作目录。

语法

WORKDIR /path/to/workdir

注意事项

  • 如果指定的工作目录不存在,它将会被自动创建
  • WORKDIR指令可多次出现,如果指定了相对路径,则它将相对于前一条WORKDIR指令的路径。例如:
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd
# 最终输出的输出Dockerfile为/a/b/c

实战

  1. 使用相对路径来执行下一个目录,再次编辑Dockerfile,指定WORKDIR
#我的web站点 by drw
FROM ubuntu:22.04 as buildbase
ENV WEB_ROOT=/data/web/www/
COPY index.html ${WEB_ROOT}
WORKDIR /usr/local
  1. 再次编译v0.6版本
root@139-159-150-152:/data/mydocker/dockerfile/web1# docker build -t web1:v0.6 .
[+] Building 0.1s (8/8) FINISHED
 => [internal] load build definition from Dockerfile
 => => transferring dockerfile: 243B
 => [internal] load .dockerignore
 => => transferring context: 2B
 => [internal] load metadata for docker.io/library/ubuntu:22.04
 => CACHED [2/3] FROM docker.io/library/ubuntu:22.04
 => [internal] load build context
 => => transferring context: 31B
 => CACHED [3/3] COPY index.html ${WEB_ROOT}
 => [4/4] WORKDIR /usr/local
 => exporting to image
 => => exporting layers
 => => writing image sha256:718a1930947993b8a32a9164d23d0e5202c5e4b2157f5d15036e17fd738
 => => naming to docker.io/library/web1:v0.6
  1. 执行pwd命令查看当前目录
root@139-159-150-152:/data/mydocker/dockerfile/web1# docker run -it --name web1-rm web1:v0.6 pwd
/usr/local

ADD

功能

  • ADD指令类似于COPY指令,ADD支持使用TAR文件和URL路径,会自动完成解压和下载

语法

ADD [--chown=<user>:<group>] <src>... <dest>
ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]
  • 参数:
    • <src>:要复制的源文件或目录,支持使用通配符;
    • <dest>:目标路径,即正在创建的image的文件系统路径;建议使用绝对路径,否则,ADD指定以WORKDIR为其真实路径;在路径中有空白字符时,通常使用第2种格式;
    • --chown:修改用户和组

实战

  1. 登录nginx官网https://nginx.org/,找到最新稳定版本的nginx的下载地址https://nginx.org/download/nginx-1.22.1.tar.gz .

  2. 通过ADD命令下载,可以提取nginx的版本为环境变量,再次编辑Dockerfile

#我的web站点 by drw
FROM ubuntu:22.04 as buildbase
ENV WEB_ROOT=/data/web/www/
ENV NGINX_VERSION=nginx-1.22.1
COPY index.html ${WEB_ROOT}
WORKDIR /usr/local
ADD https://nginx.org/download/${NGINX_VERSION}.tar.gz ./src
  1. 执行编译v0.7
root@139-159-150-152:/data/mydocker/dockerfile/web1# docker build -t web1:v0.7 .
[+] Building 2.3s (10/10) FINISHED
 => [internal] load build definition from Dockerfile
 => => transferring dockerfile: 337B
 => [internal] load .dockerignore
 => => transferring context: 2B
 => [internal] load metadata for docker.io/library/ubuntu:22.04
 => [internal] load build context
 => => transferring context: 31B
 => [1/4] FROM docker.io/library/ubuntu:22.04
 => CACHED [2/4] COPY index.html ${WEB_ROOT}
 => CACHED [3/4] WORKDIR /usr/local
 => [4/4] ADD https://nginx.org/download/nginx-1.22.1.tar.gz ./src
 => exporting to image
 => => exporting layers
 => => writing image sha256:8c235c363579b3c3f3fe6f1131684025a9717fac349ef9a7cae2ab7380
 => => naming to docker.io/library/web1:v0.7
  1. 运行v0.7镜像查看,看是否已经完成了下载
root@139-159-150-152:/data/mydocker/dockerfile/web1# docker run -it --name web1 --rm web1:v0.7 ls -l /usr/local/src
total 1052
-rw------- 1 root root 1073948 Oct 19 09:23 nginx-1.22.1.tar.gz
  1. 此时并没有被解压
  2. 手动下载下来这个压缩包,放到我们的服务器目录,此时目录的结构如下:
root@139-159-150-152:/data/mydocker/dockerfile/web1# wget https://nginx.org/download/nginx-1.22.1.tar.gz
--2023-03-14 15:32:34--  https://nginx.org/download/nginx-1.22.1.tar.gz
Resolving nginx.org (nginx.org)... 52.58.199.22, 3.125.197.172, 2a05:d014:edb:5704::6, ...
Connecting to nginx.org (nginx.org)|52.58.199.22|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 1073948 (1.0M) [application/octet-stream]
Saving to: ‘nginx-1.22.1.tar.gz’

nginx-1.22.1.tar.gz 100%[===================>]   1.02M  1.95MB/s    in 0.5s

2023-03-14 15:32:36 (1.95 MB/s) - ‘nginx-1.22.1.tar.gz’ saved [1073948/1073948]

root@139-159-150-152:/data/mydocker/dockerfile/web1# ll -h
drwxr-xr-x 2 root root 4.0K Mar 14 14:37 ./
drwxr-xr-x 3 root root 4.0K Mar 14 15:26 ../
-rw-r--r-- 1 root root  298 Mar 14 15:23 Dockerfile
-rw-r--r-- 1 root root  53 Oct 19 07:43 index.html
-rw-r--r-- 1 root root 1.1M Mar 14 15:32 nginx-1.22.1.tar.gz
  1. 再次编辑Dockerfile,添加将nginx放到src2目录
#我的web站点 by drw
FROM ubuntu:22.04 as buildbase
ENV WEB_ROOT=/data/web/www/
ENV NGINX_VERSION=nginx-1.22.1
COPY index.html ${WEB_ROOT}
WORKDIR /usr/local
ADD https://nginx.org/download/${NGINX_VERSION}.tar.gz ./src
ADD ${NGINX_VERSION}.tar.gz ./src2
  1. 执行命令编译v0.8
docker build -t web1:v0.8 .
  1. 运行v0.8版本的镜像,查看该镜像的/usr/local/src2目录,nginx已经被解压
root@139-159-150-152:/data/mydocker/dockerfile/web1# docker run -it --name web1 --rm web1:v0.8 ls -l /usr/local/src2
total 4
drwxr-xr-x 8 1001 1001 4096 Oct 19 08:02 nginx-1.22.1

RUN

功能

  • 用于指定docker build过程中运行的程序,其可以是任何命令

语法

# shell form
RUN <command>
# exec form
RUN ["executable", "param1", "param2"]
  • 参数:
    • 第一种格式中,< command>通常是一个shell命令,且以"/bin/sh -c"来运行它,这意味着此进程在容器中的PID不为1,不能接收信号,因此,当使用docker stop < container>命令停止容器时,此进程接收不到SIGTERM信号;
    • 第二种格式中的参数是JSON格式数组,其中< executable>为要运行的命令,后面的< paramN>为传递给命令的选项或参数;然而,此种格式指定的命令不会以"/bin/sh -c"来发起,因此常见的shell操作如变量替换以及通配符(?,*等)替换将不会进行;不过,如果要运行的命令依赖于此shell特性的话,可以将其替换为类似于下面的格式:
RUN ["/bin/bash", "-c", "<executable>", "<param1>"]

样例

ENV WEB_SERVER_PACKAGE nginx-1.21.1.tar.gz
RUN cd ./src && tar -xf ${WEB_SERVER_PACKAGE}

nginx安装实战

  1. nginx是源码所以我们需要先解压src文件,RUN命令可以完成nginx的解压
#我的web站点 by drw
FROM ubuntu:22.04 as buildbase
ENV WEB_ROOT=/data/web/www/
ENV NGINX_VERSION=nginx-1.22.1
COPY index.html ${WEB_ROOT}
WORKDIR /usr/local
ADD https://nginx.org/download/${NGINX_VERSION}.tar.gz ./src
#解压
RUN cd ./src && tar -zxvf ${NGINX_VERSION}.tar.gz
  1. 再次编译镜像v0.9,然后查看镜像 /usr/local/src 目录是否已经解压
root@139-159-150-152:/data/mydocker/dockerfile/web1# docker build -t web1:v0.9 .
[+] Building 4.3s (12/12) FINISHED
 => [internal] load build definition from Dockerfile
 => => transferring dockerfile: 429B
 => [internal] load .dockerignore
 => => transferring context: 2B
 => [internal] load metadata for docker.io/library/ubuntu:22.04
 => [internal] load build context
 => => transferring context: 71B
 => [1/6] FROM docker.io/library/ubuntu:22.04
 => CACHED [2/6] COPY index.html ${WEB_ROOT}
 => CACHED [3/6] WORKDIR /usr/local
 => CACHED [4/6] ADD https://nginx.org/download/nginx-1.22.1.tar.gz ./src
 => [5/6] RUN cd ./src && tar zxvf nginx-1.22.1.tar.gz
 => exporting to image
 => => exporting layers
 => => writing image sha256:946dd72dfd3e8106a52480a0b414bed6a69a14458581a46cc94f3e64bccbd
 => => naming to docker.io/library/web1:v0.9
root@139-159-150-152:/data/mydocker/dockerfile/web1# docker run -it --name web1 --rm web1:v0.9 ls -l /usr/local/src
total 1056
drwxr-xr-x 8 1001 1001    4096 Oct 19 08:02 nginx-1.22.1
-rw------- 1 root root 1073948 Oct 19 09:23 nginx-1.22.1.tar.gz
  1. 源码安装所以需要编译安装nginx,需要下载编译工具,已经依赖库信息,并通过RUN来完成安装,再次修改dockerfile
#我的web站点 by drw
FROM ubuntu:22.04 as buildbase
ENV WEB_ROOT=/data/web/www/
ENV NGINX_VERSION=nginx-1.22.1
COPY index.html ${WEB_ROOT}
WORKDIR /usr/local
ADD https://nginx.org/download/${NGINX_VERSION}.tar.gz ./src
#解压
RUN cd ./src && tar zxvf ${NGINX_VERSION}.tar.gz

#1.安装依赖
#2.安装编译工具
#3.进入nginx目录
RUN apt-get update && apt install -y build-essential libpcre3 libpcre3-dev zlib1g-dev zlib1g openssl libssl-dev \
    && cd ./src/${NGINX_VERSION} \
    && ./configure --prefix=/usr/local/nginx \
    && make && make install
  1. 再次构建,然后查看构建的nginx是否成功生成
root@139-159-150-152:/data/mydocker/dockerfile/web1# docker build -t web1:v1.0 .
root@139-159-150-152:/data/mydocker/dockerfile/web1# docker run -it --name web1 --rm web1:v1.0 /usr/local/nginx/sbin/nginx -v
nginx version: nginx/1.22.1
built by gcc 11.3.0 (Ubuntu 11.3.0-1ubuntu1~22.04)
configure arguments: --prefix=/usr/local/nginx
  1. nginx的默认配置文件为 /usr/local/nginx/conf/nginx.conf,我们修改server部分,配置自己的配置文件,然后覆盖
user  nobody;
worker_processes  1;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';

    #access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;

    server {
        listen       80;
        server_name  localhost;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

        location / {
            root   /data/web/www;
            index  index.html index.htm;
        }

        #error_page  404              /404.html;

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }

        # proxy the PHP scripts to Apache listening on 127.0.0.1:80
        #
        #location ~ \.php$ {
        #    proxy_pass   http://127.0.0.1;
        #}

        # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
        #
        #location ~ \.php$ {
        #    root           html;
        #    fastcgi_pass   127.0.0.1:9000;
        #    fastcgi_index  index.php;
        #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
        #    include        fastcgi_params;
        #}

        # deny access to .htaccess files, if Apache's document root
        # concurs with nginx's one
        #
        #location ~ /\.ht {
        #    deny  all;
        #}
    }
}
  1. 可以看到包的编译特别耗时,此时 Dockerfile 如下:
#helloweb站点 by drw
FROM ubuntu:22.04 as buildbase
ENV WEB_ROOT=/web
ENV NGINX_VERSION=nginx-1.22.1

#1.安装依赖包
RUN apt-get update && apt install -y build-essential libpcre3 libpcre3-dev libssl-dev zlib1g-dev

#2.下载源码
WORKDIR /usr/local
ADD https://nginx.org/download/${NGINX_VERSION}.tar.gz /src
ADD ${NGINX_VERSION}.tar.gz ./src2

解压

RUN cd /src && tar xvf ${NGINX_VERSION}.tar.gz
  1. 进入 nginx 目录
  2. 执行编译和构建
RUN cd /src/${NGINX_VERSION} \
    && ./configure --prefix=/usr/local/nginx \
    && make && make install

目录结构如下:

drwxr-xr-x 2 root root 4096 Mar 14 14:47 .
drwxr-xr-x 1 root root 4096 Mar 14 14:47 ..
-rw-r--r-- 1 root root 790K Mar 14 14:31 nginx.conf
-rw-r--r-- 1 root root 187K Mar 14 14:47 nginx.conf.tar.gz
  1. 再构建镜像 v1.1
docker build -t web1:v1.1 .
  1. 再次运行 nginx 检查是否正常运行
docker run -d -p 80:80 --name web1 web1:v1.1
docker exec -it web1 /usr/local/nginx/sbin/nginx -v
nginx version: nginx/1.22.1
configure arguments: --prefix=/usr/local/nginx

CMD

功能

  • 类似于 RUN 指令,CMD 指令也可用于运行任何命令或应用程序,不过,二者的运行时间点不同
    • RUN 指令运行于镜像文件构建过程中,而 CMD 指令运行于基于 Dockerfile 构建出的新镜像文件启动一个容器时
  • CMD 指令的首要目的在于为启动的容器指定默认要运行的程序,且其运行结束后,容器也将终止;CMD 指定的命令可以被 docker run 的命令行选项所覆盖
  • 在 Dockerfile 中可以存在多个 CMD 指令,但仅最后一个会生效

语法

CMD ["executable","param1","param2"] (exec form, this is the preferred form)
CMD ["param1","param2"] (as default parameters to ENTRYPOINT)
CMD command param1 param2 (shell form)

注意事项

  • 第二种则用于为 ENTRYPOINT 指令提供默认参数
  • json 数组中要使用双引号,单引号会出错

样例

CMD ["/usr/bin/wc","--help"]

实战

  1. 因为 docker 是需要一个长时间后台运行的,让 nginx 进入前台运行,这就需要我们下面的 CMD 或者 ENTRYPOINT 来完成,我们先用 CMD 来配置,修改 Dockerfile 如下:
#helloweb站点 by maxhou
FROM ubuntu:22.04 as buildbase
LABEL maintainer="[email protected]"
ENV COMPAY="com.bit"
ENV WEB_ROOT=/web
ENV NGINX_VERSION=nginx-1.22.1

#1.安装依赖包
RUN apt-get update && apt install -y build-essential libpcre3 libpcre3-dev libssl-dev zlib1g-dev

#2.下载源码
WORKDIR /usr/local
COPY index.html ${WEB_ROOT}/
ADD https://nginx.org/download/${NGINX_VERSION}.tar.gz /src
ADD ${NGINX_VERSION}.tar.gz ./src2

解压

RUN cd /src && tar xvf ${NGINX_VERSION}.tar.gz
  1. 进入 nginx 目录
  2. 执行编译和构建
RUN cd /src/${NGINX_VERSION} \
    && ./configure --prefix=/usr/local/nginx \
    && make && make install
COPY nginx.conf /nginx/conf/nginx.conf
CMD ["/usr/local/nginx/sbin/nginx","-g","daemon off;"]
  1. 再编译 v1.2 版本
docker build -t web1:v1.2 .
  1. 我们再次运行,然后 docker ps 可以看到容器已经在运行了
docker run --name web1 -d -p 80:80 web1:v1.2
docker ps
CONTAINER ID   IMAGE       COMMAND                  CREATED         STATUS         PORTS                               NAMES
eb88a30e4dea   web1:v1.2   "/usr/local/nginx/sbin/…"   3 seconds ago   Up 2 seconds   80/tcp, 0.0.0.0:80->80/tcp          web1

EXPOSE

功能

  • 用于为容器打开指定要监听的端口以实现与外部通信
  • 该 EXPOSE 指令实际上并不发布端口。它充当构建镜像的人和运行容器的人之间的一种文档,关于要发布哪些端口或接口。要在运行容器时实际发布端口,使用 -p 参数发布端口到主机端口。

语法

EXPOSE <port> [<port>/<protocol>...]

参数

  • <protocol>: tcp/udp 协议
  • <port>: 端口

样例

EXPOSE 80/tcp

实战

  1. 通过外部的浏览器访问,发现无法访问,是因为端口还没对外开放
  2. 通过 EXPOSE 暴露端口看下,调整 Dockerfile 如下:
#helloweb站点 by drw
FROM ubuntu:22.04 as buildbase
ENV NGINX_VERSION=nginx-1.22.1

#1.安装依赖包
RUN apt-get update && apt install -y build-essential libpcre3 libpcre3-dev libssl-dev zlib1g-dev

#2.下载源码
COPY index.html /usr/
WORKDIR /usr/local
ADD https://nginx.org/download/${NGINX_VERSION}.tar.gz /src
ADD ${NGINX_VERSION}.tar.gz ./src2

解压

RUN cd /src && tar xvf ${NGINX_VERSION}.tar.gz

#3.进入 nginx 目录
#4.执行编译和构建

RUN cd /src/${NGINX_VERSION} \
    && ./configure --prefix=/usr/local/nginx \
    && make && make install
COPY nginx.conf /nginx/conf/nginx.conf
EXPOSE 80/tcp
CMD ["/usr/local/nginx/sbin/nginx","-g","daemon off;"]
  1. 此时构建 v1.3 版本
docker build -t web1:v1.3 .
  1. 再次运行,通过浏览器访问,不过要先停止之前的容器
docker stop web1
docker rm web1
docker run --name web1 -d web1:v1.3
docker ps
  1. 再次通过 docker ps 查看容器正常运行,但是通过浏览器访问还是不行,说明 EXPOSE 仅仅是声明,并没有起作用

ENTRYPOINT

功能

  • 用于指定容器的启动入口

语法

ENTRYPOINT ["executable", "param1", "param2"] (exec form, preferred)
ENTRYPOINT command param1 param2 (shell form)

参数

  • json 数组中,要使用双引号,单引号会出错

样例

ENTRYPOINT ["nginx", "-g", "daemon off;"]

实战

  1. 我们将 CMD 调整为 EntryPoint 重新测试下,此时 Dockerfile 如下:
#helloweb站点 by drw
FROM ubuntu:22.04 as buildbase
ENV NGINX_VERSION=nginx-1.22.1

#1.安装依赖包
RUN apt-get update && apt install -y build-essential libpcre3 libpcre3-dev libssl-dev zlib1g-dev

#2.下载源码
COPY index.html /usr/
WORKDIR /usr/local
ADD https://nginx.org/download/${NGINX_VERSION}.tar.gz /src
ADD ${NGINX_VERSION}.tar.gz ./src2
RUN cd /src && tar xvf ${NGINX_VERSION}.tar.gz

#3.进入 nginx 目录
#4.执行编译和构建
RUN cd /src/${NGINX_VERSION} \
    && ./configure --prefix=/usr/local/nginx \
    && make && make install

COPY nginx.conf /nginx/conf/nginx.conf
EXPOSE 80/tcp
#CMD ["/usr/local/nginx/sbin/nginx","-g","daemon off;"]
ENTRYPOINT ["/usr/local/nginx/sbin/nginx","-g","daemon off;"]
  1. 再次构建,运行我们的镜像
docker build -t web1:v1.4 .
docker stop web1
docker rm web1
docker run --name web1 -d -p 80:80 web1:v1.4
  1. 此时通过浏览器可以再次访问,发现可以正常访问
    Hello, My Home Page! by bit

核心区别

  • CMD:提供容器启动的默认命令或参数,可以被 docker run 后面的命令行参数直接覆盖
  • ENTRYPOINT:指定容器启动的固定命令,不会被 docker run 的参数覆盖,而是将这些参数作为额外输入传递给 ENTRYPOINT。

ARG

功能

  • ARG 指令类似 ENV,定义了一个变量;区别于 ENV,用户可以在构建时 docker build --build-arg <varname>=<value> 进行对变量的修改;ENV 不可以。
  • docker build 指令中未在 Dockerfile 中定义的构建参数,那么构建输出警告。

语法

ARG <name>[=<default value>]

注意事项

  • Dockerfile 可以包含一个或多个 ARG 指令
  • ARG 支持指定默认值
  • 使用 ARG 定义之后才能使用,定义之前的为空,如下的实例,执行命令 docker build --build-arg username=someuser .,结果为 someuser 而不是指定的 build-arg 中的参数
    FROM busybox
    ARG username
    RUN echo "user is ${username}"
    ARG username=someuser
    
  • ENV 和 ARG 同时存在,ENV 会覆盖 ARG
FROM ubuntu
ARG UBUNTU_VER
ENV CON_IMG_VER=$UBUNTU_VER
RUN echo $CON_IMG_VER

执行下面指令:

docker build --build-arg UBUNTU_VER=v1.0.0 .

我们可以优化写法:

FROM ubuntu
ARG CON_IMG_VER=v1.0.0
ENV CON_IMG_VER=$CON_IMG_VER
RUN echo $CON_IMG_VER

系统内置了一些 ARG 变量

  • HTTP_PROXY
  • http_proxy
  • HTTPS_PROXY
  • https_proxy
  • FTP_PROXY
  • ftp_proxy
  • NO_PROXY
  • no_proxy
  • ALL_PROXY
  • all_proxy

实战

  1. 把基础镜像升级到 22.10, ARG 就排上用场了
  2. 定义一个 ARG 变量指定操作系统版本,修改后的 dockerfile 如下:
#helloweb站点 by drw
ARG UBUNTU_VER=22.04
FROM ubuntu:${UBUNTU_VER} as buildbase
ENV NGINX_VERSION=nginx-1.22.1

#1.安装依赖包
RUN apt-get update && apt install -y build-essential libpcre3 libpcre3-dev libssl-dev zlib1g-dev

#2.下载源码
COPY index.html /usr/
WORKDIR /usr/local
ADD https://nginx.org/download/${NGINX_VERSION}.tar.gz /src
ADD ${NGINX_VERSION}.tar.gz ./src2
RUN cd /src && tar xvf ${NGINX_VERSION}.tar.gz

#3.进入 nginx 目录
#4.执行编译和构建
RUN cd /src/${NGINX_VERSION} \
    && ./configure --prefix=/usr/local/nginx \
    && make && make install

COPY nginx.conf /nginx/conf/nginx.conf
EXPOSE 80/tcp
ENTRYPOINT ["/usr/local/nginx/sbin/nginx","-g","daemon off;"]
  1. 再次构建 v1.5 版本的镜像,指定 ARG 参数,可以看到镜像重新构建
docker build --build-arg UBUNTU_VER=22.10 -t web1:v1.5 .
  1. 再次运行 v1.5 镜像可以看到容器正常运行
docker stop web1
docker rm web1
docker run --name web1 -d -p 80:80 web1:v1.5

VOLUME

功能

  • 用于在 image 中创建一个挂载点目录
  • 通过 VOLUME 指令创建的挂载点,无法指定主机上对应的目录,是自动生成的。

语法

VOLUME ["<mountpoint>"]
VOLUME <mountpoint>

参数

  • <mountpoint>: 挂载点目录

注意事项

  • 如果挂载点目录路径下的文件/文件夹存在,docker run 命令会将其拷贝到挂载卷中
  • docker run -v 会覆盖 Dockerfile 中定义的 VOLUME,但是并没有将 VOLUME 中的内容拷贝到宿主机中
  • 容器中如果在 VOLUME 目录中写数据,那么容器删除后,数据依然保留在宿主机的挂载目录中,可用于持久化数据

样例

VOLUME /data

实战

  1. 我们创建一个 Dockerfile,如下指定一个卷
FROM busybox
RUN mkdir /data && echo "hello world" > /data/myvolume.txt
CMD ["tail","-f","/dev/null"]
  1. 构建镜像
docker build -t volume:v0.1 .
  1. 启动容器
docker run -d --name myvolume volume:v0.1
  1. 查看卷信息
docker inspect myvolume

输出片段:

"Mounts": [
    {
        "Type": "volume",
        "Name": "1b0b6a1a5f39dacc68acb03d144d67a90f853e8b42d760415faccf4c9a4b83fd",
        "Source": "/data/var/lib/docker/volumes/1b0b6a1a5f39dacc68acb03d144d67a90f853e8b42d760415faccf4c9a4b83fd/_data",
        "Destination": "/data",
        "Driver": "local",
        "Mode": "",
        "RW": true,
        "Propagation": ""
    }
]
  1. 可以看到挂载卷目录,进入目录可以看到文件
cd /data/var/lib/docker/volumes/1b0b6a1a5f39dacc68acb03d144d67a90f853e8b42d760415faccf4c9a4b83fd/_data
ls
myvolume.txt
cat myvolume.txt
hello world
  1. 删除容器,查看文件是没有被删除的
docker stop myvolume
docker rm myvolume
ls /data/var/lib/docker/volumes/1b0b6a1a5f39dacc68acb03d144d67a90f853e8b42d760415faccf4c9a4b83fd/_data
myvolume.txt

SHELL

功能

  • SHELL 指令允许覆盖用于 shell 命令形式的默认 shell
    • Linux 上的默认 shell 是 ["/bin/sh", "-c"],在 Windows 上是 ["cmd", "/S", "/C"]
  • SHELL 指令必须以 JSON 格式写入 Dockerfile。

语法

SHELL ["executable", "parameters"]

参数

  • executable: shell 可执行文件的位置
  • parameters: shell 执行的参数

注意事项

  • SHELL 指令可以多次出现
  • 每个 SHELL 指令都会覆盖所有先前的 SHELL 指令,并影响所有后续指令
  • 该 SHELL 指令在 Windows 上特别有用,因为 windows 行有两种不同的 shell: cmd 和 powershell

样例

FROM microsoft/windowsservercore
# Executed as cmd /S /C echo default
RUN echo default
# Executed as cmd /S /C powershell -command Write-Host default
RUN powershell -command Write-Host default
# Executed as powershell -command Write-Host hello
SHELL ["powershell", "-command"]
RUN Write-Host hello
# Executed as cmd /S /C echo hello
SHELL ["cmd", "/S", "/C"]
RUN echo hello

实战

  1. 我们可以在构建的时候切换使用不同的 shell
  2. 新建一个目录
mkdir -p /data/myworkdir/dockerfile/shell
  1. 新建 Dockerfile,vi Dockerfile
FROM ubuntu:22.04
RUN ls -l / > /test1.txt
SHELL ["/bin/bash", "-c"]
RUN ls -l / > /test2.txt
  1. 构建镜像
docker build -t shell:v0.1 --no-cache --progress=plain .
  1. 运行查看结果
docker run -it --rm shell:v0.1 cat /test1.txt
docker run -it --rm shell:v0.1 cat /test2.txt

USER

功能

  • 用于指定运行 image 时的或运行 Dockerfile 中任何 RUN、CMD 或 ENTRYPOINT 指令指定的程序时的用户名或 UID
  • 默认情况下,container 的运行身份为 root 用户

语法

USER <user>[:<group>]
USER <UID>[:<GID>]

参数

  • user: 用户
  • group: 用户组
  • uid: 用户 id
  • gid: 组 id

注意事项

  • UID 可以为任意数字,但必须为 /etc/passwd 中某用户的有效 UID,否则运行失败

样例

USER docker:docker

实战

  1. USER 用于指定后续命令的运行用户,建议不要直接用 root 用户操作
  2. 我们创建一个目录
mkdir -p /data/myworkdir/dockerfile/user
  1. 创建 Dockerfile,添加以下内容
FROM ubuntu:22.04
RUN groupadd nginx
RUN useradd -r -g nginx nginx
USER nginx
RUN whoami > /tmp/user1.txt
USER root:root
RUN whoami > /tmp/user2.txt
USER mysql
RUN useradd mysql -g mysql
USER mysql
RUN whoami > /tmp/user3.txt
  1. 执行编译
docker build -t user:v0.1 .
  1. 查看用户
docker run -it --rm user:v0.1 cat /tmp/user1.txt
nginx
docker run -it --rm user:v0.1 cat /tmp/user2.txt
root
docker run -it --rm user:v0.1 cat /tmp/user3.txt
mysql

HEALTHCHECK

功能

  • HEALTHCHECK 指令告诉 Docker 如何测试容器以检查它是否仍在工作。
  • 即使 Web 服务器进程仍在运行,也可以检测出陷入无限循环且无法处理新连接的 Web 服务器等情况。

语法

HEALTHCHECK [OPTIONS] CMD command (check container health by running a command inside the container)
HEALTHCHECK NONE (disable any healthcheck inherited from the base image)

参数

  • OPTIONS 选项有:
    • --interval=DURATION (default: 30s): 每隔多长时间探测一次,默认 30 秒
    • --timeout=DURATION (default: 30s): 服务响应超时时长,默认 30 秒
    • --start-period=DURATION (default: 0s): 服务启动多久后开始探测,默认 0 秒
    • --retries=N (default: 3): 认为检测失败几次为宕机,默认 3 次
  • 返回值
    • 0: 容器成功是健康的,随时可以使用
    • 1: 不健康的容器无法正常工作
    • 2: 保留不使用此退出代码

样例

HEALTHCHECK --interval=5m --timeout=3s \
  CMD curl -f http://localhost/ || exit 1

实战

  1. 创建目录
mkdir -p /data/myworkdir/dockerfile/healthcheck
  1. 拉取一个 nginx 镜像,然后安装 curl,首先检查 80 端口,编辑 Dockerfile 如下
FROM nginx:1.22.1
RUN sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
HEALTHCHECK --interval=5s --timeout=3s \
    CMD curl -fs http://localhost/ || exit 1
  1. 构建镜像,然后运行
docker build -t healthcheck:v0.1 .
docker run -d --name hc1 healthcheck:v0.1
  1. docker ps 可以看到镜像成功运行,因为 nginx 默认是 80 端口
docker ps
CONTAINER ID   IMAGE              COMMAND                  CREATED         STATUS                    PORTS                               NAMES
4eb4760a8235   healthcheck:v0.1   "/docker-entrypoint.…"   5 seconds ago   Up 4 seconds (healthy)    80/tcp                              hc1
  1. 通过 docker inspect 我们可以看到我们配置的参数
docker inspect hc1

输出片段:

"Healthcheck": {
    "Test": [
        "CMD-SHELL",
        "curl -fs http://localhost/ || exit 1"
    ],
    "Interval": 5000000000,
    "Timeout": 3000000000,
}
  1. 调整 Dockerfile 端口为 10080,再次运行镜像
FROM nginx:1.22.1
RUN sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
HEALTHCHECK --interval=5s --timeout=3s \
    CMD curl -fs http://localhost:10080/ || exit 1
  1. 再构建镜像 v0.2,停止第一个镜像
docker build -t healthcheck:v0.2 .
docker stop hc1
docker rm hc1
docker run -d --name hc2 healthcheck:v0.2
  1. 查看 docker ps 可以看到显示不健康
docker ps
CONTAINER ID   IMAGE              COMMAND                  CREATED         STATUS                     PORTS                               NAMES
c731d080a82f   healthcheck:v0.2   "/docker-entrypoint.…"   2 minutes ago   Up 2 minutes (unhealthy)   80/tcp                              hc2
  1. 通过 docker inspect 查看可以看到已经发生了多次的失败
docker inspect hc2

输出片段:

"Health": {
    "Status": "unhealthy",
    "FailingStreak": 11,
    "Log": [
        {
            "Start": "2023-03-14T11:52:47.242951421+08:00",
            "End": "2023-03-14T11:52:47.473606451+08:00",
            "ExitCode": 1,
            "Output": ""
        }
    ]
}
  1. 删除容器释放空间
docker stop hc2
docker rm hc2

ONBUILD

功能

  • 用于在 Dockerfile 中定义一个触发器
  • 以该 Dockerfile 中的作为基础镜像的 FROM 指令在 build 过程中被执行时,将会“触发”创建其 base image 的 Dockerfile 文件中的 ONBUILD 指令定义的触发器

语法

ONBUILD <INSTRUCTION>

参数

  • INSTRUCTION: dockerfile 的一条指令

样例

ONBUILD ADD . /app/src

实战

  1. 创建目录
mkdir -p /data/myworkdir/dockerfile/build
  1. 在里面创建 Dockerfile1 构建第一个基础镜像,设置 ONBUILD 的时候写入一次文件,Dockerfile1 内容如下
FROM ubuntu:22.04
LABEL version="0.1"
ONBUILD RUN echo "in build" >> /tmp/build.txt
  1. 构建作为基础镜像
docker build -t build:v0.1 -f Dockerfile1 .
  1. 使用 build:v0.1 作为基础镜像,新建一个 Dockerfile2,来配置构建 build:v0.2 的镜像,Dockerfile2 的内容如下
FROM build:v0.1
LABEL version="0.2"
  1. 构建 v0.2 镜像,可以看到我们在 v0.1 中设置的钩子自动执行了
docker build -t build:v0.2 -f Dockerfile2 .

STOPSIGNAL

功能

  • STOPSIGNAL 指令设置将发送到容器的系统调用信号
  • 此信号可以是与内核的系统调用表中的位置匹配的有效无符号数,例如 9,或者 SIGNAME 格式的信号名,例如 SIGKILL。

语法

STOPSIGNAL signal

参数

  • signal: 信号名或数字

常见信号:

代号名称内容
1SIGHUP启动被终止的程序,可让该进程重新读取自己的配置文件,类似重新启动。
2SIGINT相当于用键盘输入 [ctrl]-c 来中断一个程序的进行。
9SIGKILL代表强制中断一个程序的进行,如果该程序进行到一半,那么尚未完成的部分可能会有“半产品”产生,类似 vim 会有 .filename.swp 保留下来。
15SIGTERM以正常的方式来终止该程序。由于是正常的终止,所以后续的动作会将他完成。不过,如果该程序已经发生问题,就是无法使用正常的方法终止时,输入这个 signal 也是没有用的。
19SIGSTOP相当于用键盘输入 [ctrl]-z 来暂停一个程序的进行。

样例

STOPSIGNAL 9

实战

  1. 创建目录
mkdir -p /data/myworkdir/dockerfile/ss
  1. 编辑 Dockerfile 如下
FROM nginx:1.22.1
STOPSIGNAL 9
ENTRYPOINT ["nginx","-g","daemon off;"]
  1. 执行镜像构建
docker build -t stopsignal:v0.1 .
  1. 运行镜像
docker run --name stopsignal1 --rm -d stopsignal:v0.1
  1. 通过 docker ps 查看
docker ps
CONTAINER ID   IMAGE               COMMAND                  CREATED         STATUS         PORTS                               NAMES
9e9d645b604   stopsignal:v0.1     "nginx -g daemon of…"   3 seconds ago   Up 2 seconds   80/tcp                              stopsignal1
  1. 打开另外一个 shell 窗口 B,执行 docker logs -f 查看日志
docker logs -f stopsignal1
2023/03/14 11:33:09 [notice] 1#1: nginx/1.22.1
2023/03/14 11:33:09 [notice] 1#1: using the epoll event method
2023/03/14 11:33:09 [notice] 1#1: built by gcc 10.2.1 20210110 (Debian 10.2.1-6)
2023/03/14 11:33:09 [notice] 1#1: OS: Linux 5.4.0-100-generic
2023/03/14 11:33:09 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1024:1024
2023/03/14 11:33:09 [notice] 1#1: start worker processes
2023/03/14 11:33:09 [notice] 1#1: start worker process 6
  1. 在原来的 shell 窗口中执行 docker stop,然后查看日志是突然退出
docker stop stopsignal1

日志窗口无任何正常退出信息,进程直接消失。

  1. 再创建一个 Dockerfile2,不配置停止信号
FROM nginx:1.22.1
ENTRYPOINT ["nginx","-g","daemon off;"]
  1. 构建然后运行容器
docker build -t stopsignal:v0.2 .
docker run --name stopsignal2 --rm -d stopsignal:v0.2
  1. 在 shell 窗口 B 中执行 docker logs 查看日志
docker logs -f stopsignal2
2023/03/14 11:38:53 [notice] 1#1: nginx/1.22.1
2023/03/14 11:38:53 [notice] 1#1: using the epoll event method
2023/03/14 11:38:53 [notice] 1#1: built by gcc 10.2.1 20210110 (Debian 10.2.1-6)
2023/03/14 11:38:53 [notice] 1#1: OS: Linux 5.4.0-100-generic
2023/03/14 11:38:53 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1024:1024
2023/03/14 11:38:53 [notice] 1#1: start worker processes
2023/03/14 11:38:53 [notice] 1#1: start worker process 6
2023/03/14 11:39:45 [notice] 1#1: signal 3 (SIGQUIT) received, shutting down
2023/03/14 11:39:46 [notice] 6#6: gracefully shutting down
2023/03/14 11:39:46 [notice] 6#6: exiting
2023/03/14 11:39:46 [notice] 1#1: signal 17 (SIGCHLD) received
2023/03/14 11:39:46 [notice] 1#1: worker process 6 exited with code 0
2023/03/14 11:39:46 [notice] 1#1: exit
  1. 我们可以看到正常的退出 nginx 会打印优雅的退出,先退出子进程,再退出主进程,而不是强制退出信号,进程消失没有任何反应。

制作命令 docker build

功能

  • docker build 命令用于使用 Dockerfile 创建镜像。

语法

docker build [OPTIONS] PATH | URL | -

关键参数

  • --build-arg: 设置镜像创建时的变量;
  • -f: 指定要使用的 Dockerfile 路径;
  • --label: 设置镜像使用的元数据;
  • --no-cache: 创建镜像的过程不使用缓存;
  • --pull: 尝试去更新镜像的新版本;
  • --quiet, -q: 安静模式,成功后只输出镜像 ID;
  • --tag, -t: 镜像的名字及标签,通常 name:tag 或者 name 格式;可以在一次构建中为一个镜像设置多个标签。
  • --network: 默认 default。在构建期间设置 RUN 指令的网络模式

PATH\URL-等参数都分别代表本地目录路径、远程资源地址、从标准输入读取Dockerfile内容,不发送本地上下文。

样例

docker build -t mynginx:v1 .

Dockerfile 优秀编写应该具备以下几点

  1. 善用 .dockerignore 文件
    用它可以标记在执行 docker build 时忽略的路径和文件,避免发送不必要的数据内容。
  2. 镜像的多阶段构建
    通过多步骤构建,可以将编译和运行过程分开,保证最终生成的镜像只包含运行应用所需要的最小化环境。用户也可以通过分别构建编译镜像和运行镜像来达到类似的结果,但这种方式需要维护多个 Dockerfile。
  3. 合理使用缓存,减少内容目录下的文件,内容不变的指令尽量放在前面,这样可以重复使用 cache
  4. 基础镜像尽量使用官方镜像,并选择体积较小镜像
    容器的核心是应用,大的平台微服务可能几十上百个。选择过大的父镜像(如 Ubuntu 系统镜像)会造成最终生成应用镜像的臃肿,推荐选用 Busybox 或应用镜像(如 nginx:alpine),或者更为小巧的系统镜像(如 alpine、busybox 或 debian),减少镜像层数
  5. 减少镜像层数
    如果希望所生成镜像的层数尽量少,则要尽量将多个 RUN、ADD、COPY 指令合并到一行,多的 RUN 指令可以合并为一条 RUN 指令,如 apt-get install && apt install 尽量写到一行。
  6. 精简镜像用途
    尽量让每个镜像的用途都比较集中单一,避免构造大而复杂、多功能的镜像。
  7. 减少外部源的干扰
    如果确实要从外部引入数据,需要指定持久的地址,并带版本信息等,让他人可以复用而不出错。
  8. 减少不必要的包安装
    只安装需要的包,不要安装无用的包,减少镜像体积。

转载自CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/Dai_renwen/article/details/160793063

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--