2-3-1-镜像制作

熊 1 阅读 容器

1 . Docker镜像有没有内核?

从镜像大小上面来说 , 一个比较小的镜像只有十几MB , 而内核文件需要一百多兆 , 因此镜像里面是没有内核的 , 镜像在被启动为容器后将直接使用宿主机的内核 , 而镜像本身则只提供相应的rootfs , 即系统正常运行所必须的用户空间的文件系统 , 比如 /dev/ , /proc , /bin , /etc等目录,所以容器当中基本是没有/boot目录的 , 而/boot当中保存的就是与内核相关的文件和目录 。

2. 为什么没有内核?

由于容器启动和运行过程中是直接使用宿主机的内核 , 所以没有直接调用过物理硬件 , 也不会涉及到硬件驱动 , 因此也用不上内核和驱动 , 另外有内核的那是虚拟机。

1.手动制作镜像

1.1 命令使用说明

一般情况使用docker commit 、docker container commit命令实现手动制作镜像的功能

  • 语法 : docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]

  • OPTIONS

    • -a ,--author string : 提交镜像的作者。("John Hannibal Smith <hannibal@a-team.com>)

    • -c, --change list : 使用Dockerfile指令来创建镜像

    • -m, --message string : 提交时的说明文字

    • -p, --pause : commit时 , 将容器暂停

#如果没有指定REPOSITORY:TAG , 则 创建新的镜像中REPOSITORY的值为none,tag的值也为none
[root@centos7 ~]# docker commit -a "xxxxx@qq.com"  -m "nginx image" --change="EXPOSE 80 443 " d979e52a92ca 
sha256:6db9a3a42c719808ebb7ab77e2c0b9a6434ee2c2a1f82b126cbcb6dd8d25e3d6
[root@centos7 ~]# docker images
REPOSITORY   TAG       IMAGE ID       CREATED          SIZE
<none>       <none>    6db9a3a42c71   3 seconds ago    388MB

#如果指定了REPOSITORY ,没有指定TAG , 则TAG会默认设置为latest
[root@centos7 ~]# docker commit -a "xxxxx@qq.com"  -m "nginx image" --change="EXPOSE 80 443 " d979e52a92ca nginx-stop
sha256:9f3edc7b87f9edbb470b493a128f0e4212a9e7429716a89baec8adf9992ed866
[root@centos7 ~]# docker images
REPOSITORY   TAG       IMAGE ID       CREATED          SIZE
nginx-stop   latest    9f3edc7b87f9   2 seconds ago    388MB

1.2 手动制作yum版nginx步骤

Docker 制作类似于虚拟机的模板制作 , 即按照公司的实际业务务求将需要安装的软件、相关配置等基础环境配置完成 , 然后将虚拟机再提交为模板 , 最后再批量从模板批量创建新的虚拟机 , 这样可以极大的简化业务中相同环境的虚拟机 运行环境的部署工作 , Docker的镜像制作分为手动制作和自动制作(基于 DockerFile) , 企业通常都是基于Dockerfile制作镜像 , 其中手动制作镜像步骤具体

  • 下载镜像和初始化系统

    [root@centos7 ~]# docker pull centos
    [root@centos7 ~]# docker run -it -d  centos:latest 
    d979e52a92ca7f453634452e9013c8ef137473bdfa1e5969dfa0dbd13d960ca7
    [root@centos7 ~]# docker exec -it d9 bash
    [root@d979e52a92ca /]# sed -e "s|^mirrorlist=|#mirrorlist=|g" \
    >     -e "s|^#baseurl=http://mirror.centos.org/centos/\$releasever|baseurl=https://mirrors.tuna.tsinghua.edu.cn/centos-vault/8.4.2105|g" \
    >     -e "s|^#baseurl=http://mirror.centos.org/\$contentdir/\$releasever|baseurl=https://mirrors.tuna.tsinghua.edu.cn/centos-vault/8.4.2105|g" \
    >     -i.bak \
    >     /etc/yum.repos.d/CentOS-*.repo
    [root@d979e52a92ca /]#

  • yum安装并配置nginx

    [root@d979e52a92ca /]# yum install nginx -y  #yum安装nginx
    [root@d979e52a92ca /]# yum install -y vim wget  pcre  pcre-devel  zlib zlib-devel  openssl openssl-devel iproute net-tools iotop  #安装常用命令
  • 关闭nginx后台运行

    [root@0263c0d0873d /]# cat /etc/nginx/nginx.conf
    user nginx;
    worker_processes auto;
    error_log /var/log/nginx/error.log;
    pid /run/nginx.pid;
    daemon off;
  • 自定义网页

    [root@0263c0d0873d /]# echo "Docker Yum Nginx" > /usr/share/nginx/html/index.html
  • 提交为镜像

    提交的时候标记tag号 : 标记tag号 , 生产当中比较常用 , 后期可以根据tag标记创建不同版本的镜像以及创建不同版本的容器。

    [root@centos7 ~]# docker commit -a "xxxxx@qq.com"  -m "nginx image" --change="EXPOSE 80 443 " d979e52a92ca nginx:v1
    sha256:b179311545c318d473d2b698b1347e5220aee2577dd5bce1b78e8ae25fd9a000
  • 从自制镜像启动容器

    [root@centos7 ~]# docker run -it -d -p 80:80  --name nginx-test nginx:v1  /usr/sbin/nginx
    0263c0d0873d7c18c650bb4078dfb9a735cb28119d5ecf00b11f3e15c6fb8de3

2. DockerFile说明

2.1 DockerFile介绍

  Docker中有个非常重要的概念叫做——镜像(Image ) 。Docker镜像是一个特殊的文件系统 , 除了提供容器运行时所需的程序、库、资源、配置等文件外 , 还包含了一些为运行时准备的一些配置参数 ( 如匿名卷、环境变量、用户等 ) 。镜像不包含任何动态数据 , 其内容在构建之后也不会被改变。

  镜像的定制实际上就是定制每一层所添加的配置、文件。如果我们可以把每一层修改、安装、构建、操作的命令都写入一个脚本 , 用这个脚本来构建、定制镜像 , 那么之前提及的无法重复的问题、镜像构建透明性的问题、体积的问题就都会解决。这个脚本就是 Dockerfile。

  Dockerfile是一个文本文件 , 其内包含了一条条的指令(Instruction) , 每一条指令构建一层 , 因此每一条指令的内容 , 就是描述该层应当如何构建。

DockerFile可以说是 一种可以被Docker程序解释的脚本 , DockerFile是由一条条的命令组成的 , 每条命令对应linux下面的一条命令 , Docker程序将这些DockerFile指令再翻译成真正的指令再翻译成真正的linux命令 , Docker程序读取DockerFile并根据指令生成 Docker镜像 , 相比手动制作镜像的方式 , DockerFile更能直观的展示镜像是怎么产生的 , 有了DockerFile, 当后期有额外的需求时 , 只要在之前的DockerFile添加或者修改响应的命令即可。

  • Dockerfile是Docker中用于定义镜像自动化构建流程的配置文件

  • 在Dockerfile中 , 包含了构建镜像过程中需要执行的命令和其他操作

  • 它可以明确设定Docker镜像的制作过程 , 帮助我们在容器体系下能够完成自动构建

2.2 dockerfile基础

使用Dockerfile构建镜像的步骤 :

  1. 编写一个dockerfile文件

  2. docker build构建成为一个镜像

  3. docker run运行镜像

  4. docker push发布镜像 ( DockerHub 、阿里云仓库)

Dockerfile文件的结构

Dockerfile主要包含四部分内容 : 1. 基础镜像信息、2. 维护者信息 、3. 镜像操作指令、4. 容器启动时指令

可以将Dockerfile理解为一个由上往下执行指令的脚本文件

当调用构建命令 , 通过Dockerfile构建镜像时 , Docker会按照指令顺序进行对应的操作

Dockerfile文件构建过程 :

  1. 每个保留关键字(指令) 都是必须是大写字母

  2. 执行从上到下顺序

  3. #表示注释

  4. 每一个指令都会创建提交一个新的镜像层 , 并提交

为什么要用Dockerfile :

  • Dockerfile是面向开发的 , 以后要发布项目 , 做镜像 , 就需要编写dockerfile文件

  • Docker镜像逐渐成企业交付的标准 , 必须要掌握!

  • DockerFile构建镜像文件 , 定义了一切的步骤 , 源代码

  • Dockerfile的体积小 , 容易进行快速迁移部署

  • 环境构建流程记录在Dockerfile中 , 能够直观的看到镜像构建的顺序和逻辑

  • Docker Images通过DockerFile构建生成的镜像 , 最终发布和运行产品

  • Docker容器 , 镜像运行起来提供服务

示例1

  • 编写dockerfile文件

    在任意目录创建一个dockerfile文件 , 输入以下命令

    # 构建一个基于ubuntu的docker定制镜像
    # 基础镜像
    FROM ubuntu
    
    # 镜像作者
    MAINTAINER my_name polotest@domain.com
    
    # 执行命令
    ## 换成国内的软件源
    RUN sed -i 's/archive.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list 
    RUN sed -i 's/security.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list 
    
    ## 安装nginx
    RUN apt update >/dev/null 2>&1 
    RUN apt install nginx -y >/dev/null 2>&1
    
    # 暴露对外端口
    EXPOSE 80
  • 执行docker build

    docker build --network=host -t ubuntu-nginx:v1 . 
  • 镜像的构建过程

    [user@centos8 nginx]$ docker build --network=host -t ubuntu-nginx:v1 .
    # 将上下文求发送给Docker引擎
    Sending build context to Docker daemon   2.56kB
    # 下载依赖的镜像
    Step 1/7 : FROM ubuntu
    latest: Pulling from library/ubuntu
    d51af753c3d3: Pull complete
    fc878cd0a91c: Pull complete
    6154df8ff988: Pull complete
    fee5db0ff82f: Pull complete
    Digest: sha256:747d2dbbaaee995098c9792d99bd333c6783ce56150d1b11e333bbceed5c54d7
    Status: Downloaded newer image for ubuntu:latest
    # 生成镜像 1d622ef86b13
     ---> 1d622ef86b13
    Step 2/7 : MAINTAINER my_name myemail@domain.com
    # 运行容器 4eec6e3094f0 , 在容器内运行上面的这个命令 , 标记维护者信息
     ---> Running in 4eec6e3094f0
    # 移除临时容器 4eec6e3094f0
    Removing intermediate container 4eec6e3094f0
    # 生成镜像 6679d1c204e3
     ---> 6679d1c204e3
    Step 3/7 : RUN sed -i 's/archive.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list
    # 运行容器84d38c20d8c4 , 在容器内运行上面的这个命令 , 更换软件源记录
     ---> Running in 84d38c20d8c4
    # 移除临时容器 84d38c20d8c4
    Removing intermediate container 84d38c20d8c4
    # 生成镜像 83f29f7b055a
     ---> 83f29f7b055a
    Step 4/7 : RUN sed -i 's/security.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list
    # 运行容器 763e4493d93f ,  在容器内运行上面的这个命令 , 更换软件源记录
     ---> Running in 763e4493d93f
    # 移除临时容器 763e4493d93f
    Removing intermediate container 763e4493d93f
    # 生成镜像 6297f20605d9
     ---> 6297f20605d9
    Step 5/7 : RUN apt update >/dev/null 2>&1
    # 运行容器 2665a7e5a2e9 , 在容器内运行上面的这个命令 ,  更新软件源缓存
     ---> Running in 2665a7e5a2e9
    # 移除临时容器 2665a7e5a2e9
    Removing intermediate container 2665a7e5a2e9
    # 生成镜像 fdfed940ca4d
     ---> fdfed940ca4d
    Step 6/7 : RUN apt install nginx -y >/dev/null 2>&1
    # 运行 容器 722a9a544643 , 在容器内运行上面的这个命令 ,  安装nginx
     ---> Running in 722a9a544643
    # 移除临时容器 722a9a544643
    Removing intermediate container 722a9a544643
    # 生成镜像 6ee76f7df9e5
     ---> 6ee76f7df9e5
    Step 7/7 : EXPOSE 80
    # 运行容器 a12ed3216ee0 , 在容器内运行上面的这个命令 ,  暴露80端口
     ---> Running in a12ed3216ee0
    # 移除临时容器 a12ed3216ee0
    Removing intermediate container a12ed3216ee0
    # 生成最终的镜像 7cf64279ba98
     ---> 7cf64279ba98
    Successfully built 7cf64279ba98
    # 将这个镜像标记命名 ubuntu-nginx 版本号v1
    Successfully tagged ubuntu-nginx:v1 

    Dockerfile就是将在文件中书写的构建指令 , 一层一层从FROM指定的基础镜像使用临时容器过渡 , 逐层叠加起来最终生成目标镜像

  • 使用docker history查看镜像历史

    [user@centos8 nginx]$ docker history ubuntu-nginx:v1
    IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
    7cf64279ba98        21 minutes ago      /bin/sh -c #(nop)  EXPOSE 80                    0B
    6ee76f7df9e5        21 minutes ago      /bin/sh -c apt install nginx -y >/dev/null 2…   59.2MB
    fdfed940ca4d        21 minutes ago      /bin/sh -c apt update >/dev/null 2>&1           21.4MB
    6297f20605d9        21 minutes ago      /bin/sh -c sed -i 's/security.ubuntu.com/mir…   2.76kB
    83f29f7b055a        21 minutes ago      /bin/sh -c sed -i 's/archive.ubuntu.com/mirr…   2.76kB
    6679d1c204e3        21 minutes ago      /bin/sh -c #(nop)  MAINTAINER my_name myemai…   0B
    1d622ef86b13        2 weeks ago         /bin/sh -c #(nop)  CMD ["/bin/bash"]            0B
    <missing>           2 weeks ago         /bin/sh -c mkdir -p /run/systemd && echo 'do…   7B
    <missing>           2 weeks ago         /bin/sh -c set -xe   && echo '#!/bin/sh' > /…   811B
    <missing>           2 weeks ago         /bin/sh -c [ -z "$(apt-get indextargets)" ]     1.01MB
    <missing>           2 weeks ago         /bin/sh -c #(nop) ADD file:a58c8b447951f9e30…   72.8MB

示例2

  • dockerfile文件内容

    FROM centos:latest
    MAINTAINER polo
    
    ENV MYPATH /usr/local
    WORKDIR $MYPATH
    
    RUN yum -y install vim
    RUN yum -y install net-tools
    
    EXPOSE 80
    CMD echo $MYPATH
    CMD echo "-----end----"
    CMD /bin/bash
  • 执行docker build

    docker build -f test.dockerfile  -t test .
  • 查看镜像和容器

    docker-dockerfile-build-1

2.3 docker build命令详解

  • 语法 : docker buildx build [OPTIONS] PATH | URL | -

  • OPTIONS

    • -f ,--file : 指定dockerfile路径

      docker build -f /path/to/a/Dockerfile .

      不指定的话 , 默认会读取上下文路径(.)下的 dockerfile

    • -t ,--tag : 指定构建的镜像名和tag

      docker build -t ubuntu-nginx:v1 . 

      构建的镜像指定多个tag

      docker build -t shykes/myapp:1.0.2 -t shykes/myapp:latest .
    • --add-host :

      可以使用一个或多个--add-host标志将其他主机添加到容器的/etc/hosts文件中

      docker build --add-host=docker:10.180.0.1 .
    • --no-cache : 构建镜像时不使用缓存

    • --network : 在构建过程中为RUN指令设置网络模式

    • --target : 设置要构建的目标构建阶段

docker build命令原理

  • docker build命令从Dockerfile和上下文构建镜像

  • 构建的上下文 : 位于指定PATH或URL中的一组文件

  • 构建过程可以引用上下文中的任何文件。例如 , 构建可以使用COPY指令来引用上下文中的文件

  • PATH : 就是本地文件系统上的一个目录路径。表示要构建镜像的上下文路径 , 通常是包含Dockerfile的目录。

  • URL : Git地址。 是一个远程 Git 仓库的URL , 可以直接从远程仓库构建镜像。

  • - : 表示从标准输入流中读取Dockerfile的内容进行构建。

个人理解 : 以设置的上下文为根目录 , 在dockerfile中写的文件路径都会以这个上下文开始找

注 : 构建上下文是递归处理的。PATH包含任何子目录 , URL包含repository及其子模块

示例

上下文为当前目录 , 也是最简单的docker build使用方式

docker build .

指定 PATH 为 . , 因此本地目录中的所有文件都会被延迟并发送到Docker守护程序。PATH指定在哪里可以找到Docker守护程序上构建的“上下文”的文件

请记住 , 守护进程可以在远程机器上运行 , 并且不会在客户端 ( 运行docker build的地方 ) 解析Dockerfile。这意味着PATH中的所有文件都会被发送 , 而不仅仅是Dockerfile中列出的ADD文件

当看到Sending build context消息时 , docker客户端的意思是将上下文从本地机器传输到Docker守护进程。

构建由Docker守护程序(Daemon)运行 , 而不是CLI (命令行) 运行。构建过程做的第一件事是将整个上下文 ( 递归 ) 发送到守护进程

官方建议: 将一个空目录作为上下文起点 , 并将Dockerfile保存在该目录中 , 仅添加构建Dockerfile所需的文件

特别注意 : 不要使用根目录/作为构建上下文的PATH , 因为会导致构建时 , 将硬盘驱动器的全部内容发送到Docker守护程序

Docker守护进程将逐一运行Dockerfile中的指令 , 如有必要 , 会将每条指令的结果提交到新镜像 , 最后会输出一个最新镜像的 ID。Docker守护进程将自动清理发送的上下文

重点 : 每条指令都是独立运行的 , 并会创建一个新镜像 , 因此像RUN cd /tmp不会对下一条自定产生任何影响 。只要有可能 , Docker就会使用构建缓存来加速Docker构建过程 , 这由控制台输出中的CACHED消息指示。

 > docker build -t svendowideit/ambassador .
 [internal] load build definition from Dockerfile                       0.1s
 => transferring dockerfile: 286B                                       0.0s
 [internal] load .dockerignore                                          0.1s
 => transferring context: 2B                                            0.0s
 [internal] load metadata for docker.io/library/alpine:3.2              0.4s
 CACHED [1/2] FROM docker.io/library/alpine:3.2@sha256:e9a2035f9d0d7ce  0.0s
 CACHED [2/2] RUN apk add --no-cache socat                              0.0s
 exporting to image                                                     0.0s
 => exporting layers                                                    0.0s
 => writing image sha256:1affb80ca37018ac12067fa2af38cc5bcc2a8f09963de  0.0s
 => naming to docker.io/svendowideit/ambassador                         0.0s

2.4 Dockerfile详细指令

docker-dockerfile-command-explain-1
  • MAINTAINER : 官方写着已弃用 , 不学了

  • STOPSIGNAL : 暂不展开讲解 , 发送到容器退出的系统调用信号 , 还没搞清楚有什么作用

  • ONBUILD : ONBUILD是一个特殊的指令 , 它后面跟的是其它指令 , 比如RUN, COPY 等 , 而这些指令 , 在当前镜像构建时并不会被执行

    • 只有当以当前镜像为基础镜像 , 去构建下一级镜像的时候才会被执行

    • Dockerfile 中的其它指令都是为了定制当前镜像而准备的 , 唯有ONBUILD是为了帮助别人定制自己而准备的

      感觉实际应用场景不会很多 , 也不展开讲解

2.4.1 FROM

FROM指令必须是Dockerfile中非注释行的第一个指令 , 即一个Dockerfile从FROM语句开始 ; 除了ARG命令可以在FROM前面

FROM指令用于为镜像文件构建过程指定基础镜像 , 后续的指令运行于此基础镜像所提供的运行环境 ;

实践中 , 基准镜像可以是任何可用镜像文件 , 默认情况下 , docker build会在docker主机上查找指定的镜像文件 , 在其不存在时 , 则会自动从Docker的公共库pull镜像。如果找不到指定的镜像文件 , 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>]

2.4.1.1 一个dockerfile可以有多个FROM

  • 可以有多个FROM来创建多个镜像 , 或区分构建阶段 , 将一个构建阶段作为另一个构建阶段的依赖项

  • AS <name>就是命名当前构建阶段

  • 在后续构建阶段 , 可以给FROM、COPY指令用上 , 通过--from=<name>引用前面构建的镜像

# 第一构建阶段:将仅用于生成 requirements.txt 文件
FROM tiangolo/uvicorn-gunicorn:python3.9 as requirements-stage

# 将当前工作目录设置为 /tmp
WORKDIR /tmp

# 生成 requirements.txt
RUN touch requirements.txt

# 第二构建阶段 , 在这往后的任何内容都将保留在最终容器映像中
FROM python:3.9

# 将当前工作目录设置为 /code
WORKDIR /code

# 复制 requirements.txt;这个文件只存在于前一个 Docker 阶段 , 这就是使用 --from=requirements-stage 复制它的原因
COPY --from=requirements-stage /tmp/requirements.txt /code/requirements.txt

# 运行命令
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt

# 复制
COPY ./app /code/app

2.4.1.2 了解 ARG 和 FROM 如何交互

FROM指令支持由出现在第一个FROM之前的任何ARG指令声明的变量

ARG  CODE_VERSION=latest
FROM base:${CODE_VERSION}
CMD  /code/run-app

FROM extras:${CODE_VERSION}
CMD  /code/run-extras

注意

  • 在FROM之前声明的ARG在构建阶段之外 , 因此不能在FROM之后的任何指令中使用

  • 要使用在第一个FROM之前声明的ARG的默认值 , 要在构建阶段内声明一次没有值的ARG指令

ARG VERSION=latest
FROM busybox:$VERSION
ARG VERSION
RUN echo $VERSION > image_version

2.4.2 LABEL

可以为生成的镜像添加元数据标签信息 , 这些信息可以用来辅助过滤出特定镜像

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

示例一

# key 加了 "
LABEL "com.example.vendor"="ACME Incorporated"

# key 没有 "
LABEL com.example.label-with-value="foo"
LABEL version="1.0"

# 换行
LABEL description="This text illustrates \
that label-values can span multiple lines."

示例二

一行添加多个key=value

LABEL multi.label1="value1" multi.label2="value2" other="value3"

等价写法

LABEL multi.label1="value1" \
      multi.label2="value2" \
      other="value3"

通过 docker inspect 查看添加的元数据

> docker image inspect --format='' myimage
{
  "com.example.vendor": "ACME Incorporated",
  "com.example.label-with-value": "foo",
  "version": "1.0",
  "description": "This text illustrates that label-values can span multiple lines.",
  "multi.label1": "value1",
  "multi.label2": "value2",
  "other": "value3"
}

2.4.3 WORKDIR

切换到镜像中的指定路径 , 设置工作目录。 在WORKDIR中需要使用绝对路径 , 如果镜像中对应的路径不存在 , 会自动创建此目录。

一般用WORKDIR来替代RUN cd <path> && <do something>切换目录进行操作的指令。

WORKDIR指令为Dockerfile中跟随它的任何RUN、CMD、ENTRYPOINT、COPY、ADD指令设置工作目录。如果WORKDIR不存在 , 即使它没有在任何后续 , Dockerfile指令中使用 , 它也会被创建。

在Dockerfile文件中 , WORKDIR指令可出现多次 , 其路径也可以为相对路径 , 不过 , 其是相对此前一个WORKDIR指令指定的路径 ;

另外 , WORKDIR也可调用由ENV指定定义的变量 ;

使用相对路径的场景

WORKDIR指令可以在Dockerfile中多次使用

如果提供了相对路径 , 它将相对于前一个WORKDIR指令的路径

WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd

pwd的输出将会是/a/b/c

推荐 : 为了避免出错 , 推荐WORKDIR指令中只使用绝对路径

使用环境变量

WORKDIR指令可以使用前面ENV设置的环境变量

ENV DIRPATH=/path
WORKDIR $DIRPATH/$DIRNAME
RUN pwd    # /path/$DIRNAME

示例

会将宿主机的test.txt文件复制到 镜像的/tmp/test.txt

WORKDIR /tmp
COPY test.txt .

2.4.4 RUN

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

run指令的原理

  • RUN指令将在当前镜像上加新的一层 , 并执行任何命令和提交结果 , 生成的提交镜像将用于Dockfile中的后续步骤

  • 分层RUN指令和生成提交符合Docker核心概念 , 提交成本低 , 并且可以通过docker history中的任意步骤创建容器 , 像git代码控制一样

  • shell形式

    • 命令在shell中运行

    • Linux 上默认为/bin/sh -c

    • Windows上cmd /S /C

    RUN <command>

    第一种格式中 , <command>通常是一个shell命令 , 且以“/bin/sh -c”来运行它 , 这意味着此进程在容器中的PID不为1 ,不能接收Unix信号 , 因此 , 当使用docker stop <container>命令停止容器时 , 此进程接收不到SIGTERM信号 ;

  • exec形式

    RUN ["executable", "param1", "param2"] 

    必须双引号 , 不能是单引号

    第二种语法格式中的参数是一个JSON格式的数组 , 其中<executable>为要运行的命令 , 后面的<paramN>为传递给命令的选项或参数 ; 然而 , 此种格式指定的命令不会以"/bin/sh -c "来发起 , 因此常见的shell操作如变量替换以及通配符(?,*等)替换将不会进行 ; 不过 , 如果要运行的命令依赖于此shell特性的话 , 可以将其替换为类似下面的格式。

    RUN ["/bin/bash", "-c", "<executable>", "<param1>"]

两种写法的实际例子

RUN /bin/bash -c 'source $HOME/.bashrc; echo $HOME'
RUN ["/bin/bash", "-c", "echo hello"]

示例

shell模式下使用\ , 可以将单个RUN指令延续到下一行 , 等于执行了两个命令

RUN /bin/bash -c 'source $HOME/.bashrc; \
echo $HOME'

exec模式下使用环境变量

# 错误写法 ,  不会使用 HOME 环境变量
RUN [ "echo", "$HOME" ]

# 正确写法 ,  需要将 echo 和使用环境变量放一起
RUN [ "sh", "-c", "echo $HOME" ]

2.4.5 CMD

  • 类似于RUN指令 , CMD指令也可用于运行任何命令或应用程序 , 不过 , 二者的运行时间点不同

  • RUN指令运行于映像文件构建过程中 , 而CMD指令运行于基于Dockerfile构建出的新映像文件启动一个容器时

  • CMD指令的首要目的在于为启动的容器指定默认要运行的程序 , 且其运行结束后 , 容器也将终止 ; 不过 , CMD指定的命令其可以被docker run的命令行选项所覆盖 , 而ENTRYPOINT不会被后面的docker run命令覆盖。

  • 在Dockerfile中可以存在多个CMD指令 , 但仅最后一个会生效

RUN和CMD区别 :RUN可以在构建阶段运行很多个命令 , 而且每运行一个命令都会单独提交结果 ; CMD在构建阶段不执行任何操作 , 而是指定镜像默认执行的命令

#  exec 形式 , 推荐
CMD ["executable","param1","param2"]
CMD ["可执行命令", "参数1", "参数2"...]

# 作为ENTRYPOINT的默认参数
CMD ["param1","param2"]

# shell ,与run命令运行方式一样
CMD 命令 param1 param2 

exec模式下使用环境变量

# 错误写法 , 不会使用 HOME 环境变量
CMD [ "echo", "$HOME" ]

# 正确写法 , 需要将 echo 和使用环境变量放一起
CMD [ "sh", "-c", "echo $HOME" ]

2.4.6 EXPOSE

通知Docker容器在运行时监听指定的网络端口

  • 语法 : EXPOSE <port>[/<protocol>] [<port>[/<protocol>] ....

    <protocol>用于指定传输层协议 , 可为tcp或udp二者之一 , 默认为TCP协议

    EXPOSE指令可一次指定多个端口 , 例如 : EXPOSE 11211/udp 11211/tcp

EXPOSE 原理

  • 个人理解 : EXPOSE暴露的端口更像是指明了该容器提供的服务需要用到的端口

  • EXPOSE并不会直接将端口自动和宿主机某个端口建立映射关系

  • 如果docker run , 指定了自动映射-P , 那么会将所有暴露的端口随机映射到宿主机的高阶端口

  • 如果docker run , 指定了--net=host宿主机网络模式 , 容器中EXPOSE指令暴露的端口会直接使用宿主机对应的端口 , 不存在映射关系

  • 如果EXPOSE暴露的端口确定要和某个宿主机端口建立映射关系 , 还是要用到docker run -p参数

  • EXPOSE显式地标明镜像开放端口 , 一定程度上提供了操作的便利 , 也提高了Dockerfile的可读性和可维护性

示例

下面四个例子子都以这个dockerfile来启动容器

FROM nginx
EXPOSE 80
  • EXPOST端口和docker run的例子

    启动容器命令

    docker run -it test

    通过docker inspect查看容器元数据

    docker inspect mynginx1

    容器80端口并没有映射到宿主机上

    docker-dockerfile-expose-1
  • EXPOSE端口和docker run -P的栗子

    启动容器命令

    docker run -it -d -P test

    通过docker inspect查看容器元数据

    docker inspect mynginx1

    容器 80 端口随机映射到宿主机的高阶端口 , 所谓高阶端口就是比较大的端口号啦

  • EXPOSE端口和docker run --network=host的栗子

    启动容器命令

    docker run -it -d -network=host test

    查看容器列表

    docker ps
    docker-dockerfile-expose-3

    PORTS为空 , 代表没有映射关系

    通过docker inspect查看元数据

    docker inspect nd_cray
    docker-dockerfile-expose-4

    宿主机后台可以查看到宿主机中的80端口直接映射到容器中的80端口

  • EXPOSE端口和docker run -p的栗子

    启动容器命令

    docker run -it -d -p 80:800 test

    通过docker inspect查看容器元数据

    docker inspect mynginx1
    docker-dockerfile-expose-5

    容器80端口成功映射到宿主机800端口

    验证nginx是否部署成功

    docker-dockerfile-expose-6

2.4.7 ENV

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

  • 格式

    ENV <key> <value>  或
    ENV <key>=<value> . .

    第一种格式中 , <key>之后的所有内容均会被视作其<value>的组成部分 , 因此 , 一次只能设置一个变量;

    第二种格式可用一次设置多个变量 , 每个变量为一个”<key>=<value>"的键值对 , 如果<value>中包含空格 , 可以以反斜线(\) 进行转义 , 也可通过对<value>加引号进行标识 ; 另外 , 反斜线也可用于续行;

    定义多个变量时 , 建议使用第二种方式 , 以便在同一层中完成所有功能

${variable_name}语法还支持以下指定的一些标准bash修饰符

  • ${variable:-word} : 表示如果设置了variable , 那么结果就是那个值 ; 如果未设置变量 , 则结果将是word

  • ${variable:+word} : 表示如果设置了variable , 则结果为word , 否则为空字符串

    在所有情况下 , word可以是任何字符串 , 包括额外的环境变量

示例一

FROM busybox
ENV FOO=/bar
WORKDIR ${FOO}   # WORKDIR /bar
ADD . $FOO       # ADD . /bar
COPY \$FOO /quux # COPY $FOO /quux

示例二

覆盖环境变量的栗子

ENV abc=hello
ENV abc=bye def=$abc
ENV ghi=$abc

def的值是hello , ghi的值是bye

注意

  • 当容器从生成的镜像运行时 , 使用ENV设置的环境变量将持续存在

  • 可以使用docker inspect查看值 , 并使用docker run --env <key>=<value>更改它们

示例三

dockerfile

FROM nginx
EXPOSE 80
ENV test=test1234

构建镜像 , 启动容器 , inspect查看

docker build -f test.dockerfile -t test2 .

docker run -it -d -P test2

docker inspect 120b
docker-dockerfile-env-1

通过ENV设置的环境变量是会保留到容器中的

2.4.8 USER

用于指定运行image时的用户名或UID , 后续的RUN等指令也会使用指定的用户身份, 运行Dockerfile中任何RUN、CMD或EntRyPoInT指令指定的程序时的用户名或UID

默认情况下 , container的运行身份为root用户

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

当容器中运行的服务不需要管理员权限时 , 可以先建立一个特定的用户和用户组 , 为它分配必要的权限 , 使用USER切换到这个用户

需要注意的是 , <UID>可以为任意数字 , 但实践中其必须为/etc/passwd中某用户的有效UID,否则 , docker run命令将运行失败

重点

  • 使用 USER 指定用户时 , 可以使用用户名、UID或GID , 或是两者的组合

  • 使用USER指定用户后 , Dockerfile中后续的命令RUN、CMD、ENTRYPOINT都将使用该用户

注意事项

  • 在Windows上 , 如果用户不是内置帐户 , 则必须先创建该用户

  • 可以通过Dockerfile的RUN net user命令来完成

FROM microsoft/windowsservercore

# 在容器创建新用户
RUN net user /add patrick

# 设置用户
USER patrick

也可以使用docker run -u指定用户

  • 替代默认设置的用户

  • 可以使用uid来指定用户

docker run -i -t -u 1001 busybox sh

2.4.9 ENTRYPOINT

2.4.9.1 ENTRYPOINT基础

类似CMD指令的功能 , 用于为容器指定默认运行程序 , 从而使得容器像是一个单独的可执行程序。 与CMD不同的是 , 由ENTRYPOINT启动的程序不会被docker run命令行指定的参数所覆盖 , 而且 , 这些命令行参数会被当作参数传递给ENTRYPOINT指定指定的程序

ENTRYPOINT指定镜像的默认入口命令 , 该入口命令会在启动容器时作为根命令执行 , 所有其他传入值作为该命令的参数。 不过 , docker run命令的--entrypoint选项的参数可覆盖ENTRYPOINT指令指定的程序

ENTRYPOINT 和 CMD 联合使用

  • 当指定了ENTRYPOINT后 , CMD的含义就发生了改变 , 不再是直接的运行其命令 , 而是将CMD的内容作为参数传给ENTRYPOINT指令

  • 换句话说实际执行时 , 会变成

    <ENTRYPOINT> "<CMD>"

那么有了CMD后 , 为什么还要有ENTRYPOINT呢 ? 这种<ENTRYPOINT> "<CMD>" 有什么好处么?

CMD                   # 指定这个容器启动的时候要运行的命令 , 不可以追加命令
ENTRYPOINT            # 指定这个容器启动的时候要运行的命令 , 可以追加命令

2.4.9.2 第一个场景测试

测试 CMD

  • 编写dockerfile文件

    FROM centos
    CMD ["ls","-a"]    
  • 构建镜像

    docker build -f CMD.dockerfile -t test .
  • 运行容器

    > docker run test
    .
    ..
    .dockerenv
    bin
    dev
    etc
    home
    lib
    lib64
    lost+found
    media
    mnt
    opt
    proc
    root
    run
    sbin
    srv
    sys
    tmp
    usr
    var
  • 运行容器并追加命令

    > docker run test -l
    docker: Error response from daemon: OCI runtime create failed: container_linux.go:380: starting container process caused: exec: "-l": executable file not found in $PATH: unknown. 

    看到可执行文件找不到的报错 , executable file not found

    跟在镜像名后面的是command , 运行时会替换CMD的默认值 , 因此这里的-l替换了原来的CMD , 而不是追加在原来的ls -a后面

    而-l根本不是命令 , 所以自然找不到

  • 如果想加入 -i 参数 , 必须重写 ls 命令

    > docker run test ls -a -l
    total 56
    drwxr-xr-x   1 root root 4096 Oct 28 09:36 .
    drwxr-xr-x   1 root root 4096 Oct 28 09:36 ..
    -rwxr-xr-x   1 root root    0 Oct 28 09:36 .dockerenv
    lrwxrwxrwx   1 root root    7 Nov  3  2020 bin -> usr/bin
    drwxr-xr-x   5 root root  340 Oct 28 09:36 dev
    drwxr-xr-x   1 root root 4096 Oct 28 09:36 etc
    drwxr-xr-x   2 root root 4096 Nov  3  2020 home
    lrwxrwxrwx   1 root root    7 Nov  3  2020 lib -> usr/lib
    lrwxrwxrwx   1 root root    9 Nov  3  2020 lib64 -> usr/lib64
    drwx------   2 root root 4096 Sep 15 14:17 lost+found
    drwxr-xr-x   2 root root 4096 Nov  3  2020 media
    drwxr-xr-x   2 root root 4096 Nov  3  2020 mnt
    drwxr-xr-x   2 root root 4096 Nov  3  2020 opt
    dr-xr-xr-x 221 root root    0 Oct 28 09:36 proc
    dr-xr-x---   2 root root 4096 Sep 15 14:17 root
    drwxr-xr-x  11 root root 4096 Sep 15 14:17 run
    lrwxrwxrwx   1 root root    8 Nov  3  2020 sbin -> usr/sbin
    drwxr-xr-x   2 root root 4096 Nov  3  2020 srv
    dr-xr-xr-x  13 root root    0 Oct 28 09:36 sys
    drwxrwxrwt   7 root root 4096 Sep 15 14:17 tmp
    drwxr-xr-x  12 root root 4096 Sep 15 14:17 usr
    drwxr-xr-x  20 root root 4096 Sep 15 14:17 var

    可以了 , 但这明显不是最优选择 , ENTRYPOINT就可以解决这个问题

测试 ENTRYPOINT

  • 编写dockerfile文件

    FROM centos
    ENTRYPOINT ["ls","-a"]  
  • 构建镜像

    docker build -f ENTRYPOINT.dockerfile -t test . 
  • 运行容器并追加命令

    > docker run test -l
    total 56
    drwxr-xr-x   1 root root 4096 Oct 28 09:38 .
    drwxr-xr-x   1 root root 4096 Oct 28 09:38 ..
    -rwxr-xr-x   1 root root    0 Oct 28 09:38 .dockerenv
    lrwxrwxrwx   1 root root    7 Nov  3  2020 bin -> usr/bin
    drwxr-xr-x   5 root root  340 Oct 28 09:38 dev
    drwxr-xr-x   1 root root 4096 Oct 28 09:38 etc
    drwxr-xr-x   2 root root 4096 Nov  3  2020 home
    lrwxrwxrwx   1 root root    7 Nov  3  2020 lib -> usr/lib
    lrwxrwxrwx   1 root root    9 Nov  3  2020 lib64 -> usr/lib64
    drwx------   2 root root 4096 Sep 15 14:17 lost+found
    drwxr-xr-x   2 root root 4096 Nov  3  2020 media
    drwxr-xr-x   2 root root 4096 Nov  3  2020 mnt
    drwxr-xr-x   2 root root 4096 Nov  3  2020 opt
    dr-xr-xr-x 207 root root    0 Oct 28 09:38 proc
    dr-xr-x---   2 root root 4096 Sep 15 14:17 root
    drwxr-xr-x  11 root root 4096 Sep 15 14:17 run
    lrwxrwxrwx   1 root root    8 Nov  3  2020 sbin -> usr/sbin
    drwxr-xr-x   2 root root 4096 Nov  3  2020 srv
    dr-xr-xr-x  13 root root    0 Oct 28 09:38 sys
    drwxrwxrwt   7 root root 4096 Sep 15 14:17 tmp
    drwxr-xr-x  12 root root 4096 Sep 15 14:17 usr
    drwxr-xr-x  20 root root 4096 Sep 15 14:17 var

2.4.9.3 第二个应用场景

  • 启动容器就是启动主进程 , 但启动主进程前 , 可能需要一些准备工作 , 比如mysql可能需要一些数据库配置、初始化的工作 , 这些工作要在最终的mysql服务器运行之前解决

  • 还可能希望避免使用root用户去启动服务 , 从而提高安全性 , 而在启动服务前还需要以root身份执行一些必要的准备工作 , 最后切换到服务用户身份启动服务

  • 这些准备工作是和容器CMD无关的 , 无论 CMD 为什么 , 都需要事先进行一个预处理的工作 , 这种情况下 , 可以写一个脚本 , 然后放入ENTRYPOINT中去执行 , 而这个脚本会将接到的参数 ( 也就是<CMD> ) 作为命令 , 在脚本最后执行

官方镜像redis

FROM alpine:3.4
...
RUN addgroup -S redis && adduser -S -G redis redis
...
ENTRYPOINT ["docker-entrypoint.sh"]

EXPOSE 6379
CMD [ "redis-server" ]

docker-entrypoint.sh

#!/bin/sh
...
# allow the container to be started with `--user`
if [ "$1" = 'redis-server' -a "$(id -u)" = '0' ]; then
    find . \! -user redis -exec chown redis '{}' +
    exec gosu redis "$0" "$@"
fi

exec "$@"

该脚本的内容就是根据CMD的内容来判断 , 如果是 redis-server 的话 , 则切换到 redis 用户身份启动服务器 , 否则依旧使用root身份执行

[root@poloyy ~]#  docker run -it redis id
uid=0(root) gid=0(root) groups=0(root)

# 直接进入容器内部
[root@poloyy ~]#  docker run -it redis
root@565f89976d63:/#

2.4.10 ADD

ADD指令类似于COPY指令 , ADD支持使用TAR文件和URL路径

  • 作用 : 添加内容到镜像

  • 详解 : 该命令将复制指定的<src> 路径下内容到镜像中的<dest>路径下

  • 语法 :

    ADD [--chown=<user>:<group>] <src>... <dest>
    ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]

    --chown : 仅适用于linux上的dockerfile , 在window上没有用户、组的概念

2.4.10.1 ADD作用

  • ADD 指令从<src> 复制新文件、目录或远程文件 URL , 并将它们添加到路径 <dest>

  • 可以指定多个<src>资源 , 但如果它们是文件或目录 , 则它们的路径被解析为相对于构建上下文的源

  • 每个 <src> 可能包含通配符 , 匹配将使用 Go 的 filepath.Match 规则完成

示例

  • *通配符

    把所有hom开头的文件添加到镜像文件系统的/mydir/目录下

    ADD hom* /mydir/
  • ?通配符

    ?匹配 0 或1个字符 , 比如会把home.txt文件添加到/mydir/目录下

    ADD hom?.txt /mydir/

重点 : <dest>是绝对路径 , 或相对于WORKDIR的路径 , 源将在目标容器内复制到该路径中

  • 使用相对路径的栗子

    ADD test.txt relativeDir/

    等价于

    ADD test.txt <WORKDIR>/relativeDir/
  • 使用绝对路径的栗子

    将test.txt添加到 /absoluteDir/ 目录下

    ADD test.txt /absoluteDir/
  • 包含特殊字符的文件

    添加名为arr[0].txt的文件

    ADD arr[[]0].txt /mydir/

2.4.10.2 ADD遵循的规则

如果<src>为URL且<dest>不以/ 结尾 , 则<src>指定的文件将被下载并直接被创建为<dest>;如果<dest>以/结尾 , 则文件名URL指定的文件将被直接下载并保存为<dest>/<filename>

如果<src>是一个本地系统上的压缩格式的tar文件 , 它将被展开为一个目录 , 其行为类似于"tar-x"命令 ; 然而 , 通过URL获取到的tar文件将不会自动展开;

如果<src>有多个 , 或其间接或直接使用了通配符 , 则<dest>必须是一个以/结尾的目录路径 ;如果<dest>不以/结尾 , 则其被视作一个普通文件 , <src>的内容将被直接写入到<dest>;

  • <src>路径必须在构建的上下文中

    不能添加 ../something 、/something , 因为docker构建的第一步是将上下文目录(和子目录)发送到docker守护进程

    # test.txt 是相对路径 , 相对于构建上下文
    ADD test.txt /mkdir/
    
    # 错误写法 , 文件均不在上下文目录中 , 并不会被找到
    # 这个找的就是构建上下文的上级目录的 test.txt
    ADD ../test.txt /mkdir/
    
    # 这个找的是本机根目录下的 test.txt
    ADD /test.txt /mkdir/
  • <src> 是一个 URL

    • <dest>不以斜杠结尾 , 那么文件将从 URL 下载并复制到 <dest>

    • <dest>以斜杠结尾 , 则从 URL 推断文件名并将文件下载到<dest>/<filename>

      ADD http://example.com/foobar/
    • 这将创建文件 /foobar

    • URL必须有一个重要的路径 , 以便在这种情况下可以 找到文件名 ( http://example.com将不起作用 )

  • <src>是目录

    • 则复制目录的全部内容 , 包括文件系统元数据

    • 不会复制目录本身 , 只会复制其内容

    ADD dir /mydir/
  • <src>是压缩格式 ( gzip、bzip2、identity、xz ) 的本地tar文件

    • 会将它自动解压为目录

    • 但来自远程 URL 资源不会被解压缩

    • 当一个目录被复制或解压时 , 它的行为与tar -x相同

    注意 : 文件是否被识别为可识别的压缩格式完全取决于文件的内容 , 而不是文件的名称 ; 例如 , 如果一个空文件恰好以.tar.gz结尾 , 黄不会被识别为压缩文件 , 也不会生成任何类型的解压缩错误消息 , 而只会将该文件复制到目标位置

  • <src>是任何其他类型的文件

    • 则将其与其元数据一起单独复制

    • <dest>以斜杠 / 结尾 , 它将被视为一个目录 , 并且 <src> 的内容将写入 <dest>/base(<src>)

  • 指定了多个<src>资源 , 或者由于使用了通配符 , 则<dest>必须是一个目录 , 并且必须以斜杠 / 结尾

    ADD test1.txt test2.txt /mydir/
  • <dest>不以斜杠结尾

    它将被视为常规文件 , 并且<src>的内容将写入<dest>

    ADD test.txt /mytext
  • <dest>不存在

    路径中所有缺失的目录都会自动创建

    ADD test.txt /dir/test/my/

注意事项 : <src>的内容发生变化 , 第一个遇到的 ADD 指令将使来自 Dockerfile 的所有后续指令的缓存无效 , 这包括使RUN指令的缓存无效

完整练习的 dockerfile

FROM centos
# 添加文件到目录下
ADD test.txt /mydir/

# 将文件内容写入 mytest
ADD test.txt /mytest

# 压缩文件,自动解压
ADD jmeter.log.zip /myzipdir/

# 添加目录
ADD TeamFile /

# 其他文件
ADD jmeter.log /mydir/

# 多个文件
ADD test1.txt test2.txt /mydir/

# 通配符,dest 不存在自动创建
ADD test*.txt /mydir/test/

# 特殊字符串
ADD add[[]0].txt /mydir/

WORKDIR /data

# 相对路径
ADD test.txt test/

2.4.10.3 ADD 和 COPY 的区别和使用场景

  • ADD支持添加远程url和自动提取压缩格式的文件 , COPY只允许从本机中复制文件

  • COPY支持从其他构建阶段中复制源文件(--from)

  • 根据官方Dockerfile最佳实践 , 除非真的需要从远程 url 添加文件或自动提取压缩文件才用 ADD , 其他情况一律使用COPY

注意

  • ADD从远程url获取文件和复制的效果并不理想 , 因为该文件会增加Docker Image最终的大小

  • 相反 , 应该使用curl或wget来获取远程文件 , 然后在不需要它时进行删除

示例 : COPY网上路径(URL)的tar包

  1. 编写dockerfile文件

    # Description: test image
    FROM busybox:latest
    MAINTAINER "Along <along@along.com>"
    COPY index.html /data/web/html/
    COPY yum.repos.d /etc/yum.repos.d/
    ADD http://nginx.org/download/nginx-1.15.8.tar.gz /usr/local/src/

    注 : ADD的<src>是网上的nginx下载路径

  1. 使用build制作镜像

    [root@along img1]# docker build -t busyboxhttpd:v0.3 ./
    Sending build context to Docker daemon 27.65 kB
    Step 1/5 : FROM busybox:latest
     ---> 758ec7f3a1ee
    Step 2/5 : MAINTAINER "Along <along@along.com>"
     ---> Using cache
     ---> 5d2cda1631d7
    Step 3/5 : COPY index.html /data/web/html/
     ---> Using cache
     ---> 2e0fe0eb0cb7
    Step 4/5 : COPY yum.repos.d /etc/yum.repos.d/
     ---> Using cache
     ---> 68586925bdf9
    Step 5/5 : ADD http://nginx.org/download/nginx-1.15.8.tar.gz /usr/local/src/
    Downloading 1.028 MB/1.028 MB
     ---> 91c71b469a9e
    Removing intermediate container abbad8dcdefb
    Successfully built 91c71b469a9e
  2. 基于此新建镜像运行容器 , 进行验证

    [root@along ~]# docker run --name web1 --rm busyboxhttpd:v0.3 ls /usr/local/src
    nginx-1.15.8.tar.gz

示例 : COPY本地的路径的tar包

  1. 编写dockerfile文件

    # Description: test image
    FROM busybox:latest
    MAINTAINER "Along <along@along.com>"
    COPY index.html /data/web/html/
    COPY yum.repos.d /etc/yum.repos.d/
    #ADD http://nginx.org/download/nginx-1.15.8.tar.gz /usr/local/src/
    ADD nginx-1.15.8.tar.gz /usr/local/src/
  2. 在dockerfile同级目录下准备好yum.repos.d目录

    [root@along img1]# wget http://nginx.org/download/nginx-1.15.8.tar.gz
  3. 使用build制作镜像

    [root@along img1]# docker build -t busyboxhttpd:v0.4 ./
    Sending build context to Docker daemon 1.056 MB
    Step 1/5 : FROM busybox:latest
     ---> 758ec7f3a1ee
    Step 2/5 : MAINTAINER "Along <along@along.com>"
     ---> Using cache
     ---> 5d2cda1631d7
    Step 3/5 : COPY index.html /data/web/html/
     ---> Using cache
     ---> 2e0fe0eb0cb7
    Step 4/5 : COPY yum.repos.d /etc/yum.repos.d/
     ---> Using cache
     ---> 68586925bdf9
    Step 5/5 : ADD nginx-1.15.8.tar.gz /usr/local/src/
     ---> f99baec4b992
    Removing intermediate container 7f02f3fe649
  4. 基于此新建镜像运行容器 , 进行验证

    [root@along ~]# docker run --name web1 --rm busyboxhttpd:v0.4 ls /usr/local/src /usr/local/src/nginx-1.15.8
    /usr/local/src:
    nginx-1.15.8
     
    /usr/local/src/nginx-1.15.8:
    CHANGES
    CHANGES.ru
    LICENSE
    README
    auto
    conf
    configure
    contrib
    html
    man
    src

2.4.11 COPY

  • 作用 : 复制内容到镜像

  • 详解 : 复制本地主机的<src>下内容到镜像中的<dest> , 目标路径不存在时 , 会自动创建。

  • 路径 : 支持正则表达式 , COPY test* /tmp

  • 语法 :

    COPY <src>... <dest>  或
    COPY ["<src>",... "<dest>"]

    --chown : 仅适用于linux上的dockerfile , 在window上没有用户、组的概念

2.4.11.1 COPY的作用

  • COPY指令从<src>复制新文件、目录或远程文件URL , 并将它们添加到路径<dest>

  • 可以指定多个<src>资源 , 但如果它们是文件或目录 , 则它们的路径被解析为相对于构建上下文的源

  • 每个<src>可能包含通配符 , 匹配将使用 Go 的filepath.Match规则完成

示例

  • *通配符

    把所有hom开头的文件添加到镜像文件系统的/mydir/目录下

    COPY hom* /mydir/
  • ?通配符

    ?匹配 0 或1个字符 , 比如会把home.txt文件添加到/mydir/目录下

    COPY hom?.txt /mydir/

重点 : <dest>是绝对路径 , 或相对于WORKDIR的路径 , 源将在目标容器内复制到该路径中

  • 使用相对路径的栗子

    COPY test.txt relativeDir/

    等价于

    COPY test.txt <WORKDIR>/relativeDir/
  • 使用绝对路径的栗子

    将test.txt添加到 /absoluteDir/ 目录下

    COPY test.txt /absoluteDir/
  • 包含特殊字符的文件

    添加名为arr[0].txt的文件

    COPY arr[[]0].txt /mydir/

2.4.11.2 标志--from=<name>

FROM node:12.13.1
LABEL maintainer="a@abc.com"
COPY . /app/
WORKDIR /app
RUN npm install --registry=https://registry.npm.taobao.org --cache=$HOME/.npm/.cache/cnpm --disturl=https://npm.taobao.org/dist --userconfig=$HOME/.cnpmrc
RUN npm run build

FROM nginx
LABEL maintainer="a@abc.com"
COPY --from=0 /app/dist /usr/share/nginx/html

将从from指定的构建阶段中寻找源文件<src>。最后面的--from=0 参数, 从前边的阶段中拷贝文件到当前阶段中, Dockerfile中包含多个FROM语句时, 0代表第一个阶段。除了使用数字, 我们还可以给阶段命名

# 第一构建阶段:将仅用于生成 requirements.txt 文件
FROM tiangolo/uvicorn-gunicorn:python3.9 as requirements-stage

# 将当前工作目录设置为 /tmp
WORKDIR /tmp

# 生成 requirements.txt
RUN touch requirements.txt

# 第二构建阶段 , 在这往后的任何内容都将保留在最终容器映像中
FROM python:3.9

# 将当前工作目录设置为 /code
WORKDIR /code

# 从第一个阶段复制 requirements.txt;这个文件只存在于前一个 Docker 阶段 , 这就是使用 --from-requirements-stage 复制它的原因
COPY --from=requirements-stage /tmp/requirements.txt /code/requirements.txt

# 运行命令
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt

2.4.11.3 文件复制准则

<src>必须是build上下文中的路径 , 不能是其父目录中的文件 ;

如果<src>是目录 , 则其内部文件或子目录会被递归复制 , 但<src>目录自身不会被复制 ;

如果指定了多个<src> , 或在<src>中使用了通配符 , 则<dest>必须是一个目录 , 且必须以 / 结尾 ;

如果<dest>事先不存在 , 他将会被自动创建 , 这包括父目录路径。

  • <src>路径必须在构建的上下文中

    不能添加 ../something 、 /something , 因为docker构建的第一步是将上下文目录 ( 和子目录 ) 发送到docker守护进程

    # test.txt 是相对路径 , 相对于构建上下文
    COPY test.txt /mkdir/
    
    # 错误写法 , 文件均不在上下文目录中 , 并不会被找到
    # 这个找的就是构建上下文的上级目录的 test.txt
    COPY ../test.txt /mkdir/
    
    # 这个找的是本机根目录下的 test.txt
    COPY /test.txt /mkdir/
  • <src>是目录

    • 则复制目录的全部内容 , 包括文件系统元数据

    • 不会复制目录本身 , 只会复制其内容

    COPY dir /mydir/
  • <src>是任何其他类型的文件

    • 则将其与其元数据一起单独复制

    • <dest> 以斜杠 / 结尾 , 它将被视为一个目录 , 并且<src>的内容将写入 <dest>/base(<src>)

  • 指定了多个<src>资源 , 或者由于使用了通配符

    则 <dest> 必须是一个目录 , 并且必须以斜杠 / 结尾

    COPY test1.txt test2.txt /mydir/
  • <dest>不以斜杠结尾

    它将被视为常规文件 , 并且<src>的内容将写入<dest>

    COPY test.txt /mytext
  • <dest>不存在

    路径中所有缺失的目录都会自动创建

    COPY test.txt /dir/test/my/

注意事项 : <src>的内容发生变化 , 第一个遇到的COPY指令将使来自Dockerfile的所有后续指令的缓存无效 , 这包括使RUN指令的缓存无效

完整练习的 dockerfile

FROM centos
# 添加文件到目录下
COPY test.txt /mydir/

# 将文件内容写入 mytest
COPY test.txt /mytest

# 压缩文件,自动解压
COPY jmeter.log.zip /myzipdir/

# 添加目录
COPY TeamFile /

# 其他文件
COPY jmeter.log /mydir/

# 多个文件
COPY test1.txt test2.txt /mydir/

# 通配符,dest 不存在自动创建
COPY test*.txt /mydir/test/

# 特殊字符串
COPY COPY[[]0].txt /mydir/

WORKDIR /data

# 相对路径
COPY test.txt test/

2.4.12 HEALTHCHECK

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

当在一个镜像指定了HEALTHCHECK指令后 , 用其启动容器 , 初始状态会为starting , 在HEALTHCHECK指令检查成功后变为healthy , 如果连续一定次数失败 , 则会变为unhealthy

  • 格式

    # 设置检查容器健康状况的命令
    HEALTHCHECK [选项] CMD <命令>
    
    # 如果基础镜像有健康检查指令 , 使用这行可以屏蔽掉其健康检查指令
    HEALTHCHECK NONE
  • OPTIONS

    • --interval=DURATION(default: 30s) : 两次健康检查的间隔 , 默认为30秒 ;

    • -- timeout= DURATION (default: 30s) : 健康检查命令运行超时时间 , 如果超过这个时间 , 本次健康检查就被视为失败 , 默认30秒;

    • --start-period= DURATION (default: 0s) : 服务启动多久后开始探测 , 默认0秒

    • --retries=N(default: 3) : 当连续失败指定次数后 , 则将容器状态视为unhealthy , 默认 3 次

  • 返回值 :

    • 0 :成功 , 容器成功是健康的 , 随时可以使用

    • 1 : 失败 , 不健康的容器无法正常工作

    • 2 : 保留 , 保留不使用此退出代码

注意 : 和 CMD, ENTRYPOINT一样 , HEALTHCHECK只可以出现一次 , 如果写了多个 , 只有最后一个生效

为啥要用 HEALTHCHECK

  • 在没有 HEALTHCHECK 指令前 , Docker 引擎只可以通过容器内主进程是否退出来判断容器是否状态异常

  • 很多情况下这没问题 , 但是如果程序进入死锁状态 , 或者死循环状态 , 应用进程并不退出 , 但是该容器已经无法提供服务了

  • 在 1.12 以前 , Docker 不会检测到容器的这种状态 , 从而不会重新调度 , 导致可能会有部分容器已经无法提供服务了却还在接受用户请求

  • 自 1.12 之后 , Docker 提供了HEALTHCHECK指令 , 通过该指令指定一行命令 , 用这行命令来判断容器主进程的服务状态是否还正常 , 从而比较真实的反应容器实际状态

示例一

假设有个镜像是个最简单的Web服务 , 希望增加健康检查来判断其Web服务是否在正常工作 , 可以用curl来帮助其判断

  • dockerfile

    FROM nginx
    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

    $ docker build -f HEALTHCHEK.dockerifle -t myweb .
  • 执行docker run

    docker run -d --name web -p 80:80 myweb
  • 查看容器状态

    docker ps
    docker-dockerfile-healthcheck-1
    • 刚启动时的状态是health : starting , 过多几秒后就变成healthy了

    • 如果健康检查连续失败超过了重试次数 , 状态就会变为unhealthy

  • 查看容器健康检查信息

    为了帮助排障 , 健康检查命令的输出 ( 包括stdout以及stderr ) 都会被存储于健康状态里 , 可以用docker inspect来查看

    > docker inspect --format '{{json .State.Health}}' web | python -m json.tool
    {
        "FailingStreak": 0,
        "Log": [
            {
                "End": "2021-10-31T04:43:58.8446416Z",
                "ExitCode": 0,
                "Output": "<!DOCTYPE html>\n<html>\n<head>\n<title>Welcome to nginx!</title>\n<style>\nhtml { color-scheme: light dark; }\nbody { width: 35em; margin: 0 auto;\nfont-family: Tahoma, Verdana, Arial, sans-serif; }\n</style>\n</head>\n<body>\n<h1>Welcome to nginx!</h1>\n<p>If you see this page, the nginx web server is successfully installed and\nworking. Further configuration is required.</p>\n\n<p>For online documentation and support please refer to\n<a href=\"http://nginx.org/\">nginx.org</a>.<br/>\nCommercial support is available at\n<a href=\"http://nginx.com/\">nginx.com</a>.</p>\n\n<p><em>Thank you for using nginx.</em></p>\n</body>\n</html>\n",
                "Start": "2021-10-31T04:43:58.7591924Z"
            }
        ],
        "Status": "healthy"
    }

示例二

  1. 编写dockerfile文件

    检测web2容器的10080端口( 其实打开的是80端口 , 10080并没有 , 所有会检测失败)

    FROM busybox
    LABEL maintainer="Along <along@along.com>" app="httpd"
     
    ENV WEB_DOC_ROOT="/data/web/html"
     
    RUN mkdir -p ${WEB_DOC_ROOT} && \
        echo "<h1>Busybox httpd server</h1>" > ${WEB_DOC_ROOT}/index.html
     
    CMD /bin/httpd -f -h ${WEB_DOC_ROOT}
     
    HEALTHCHECK --start-period=3s CMD wget -O - -q http://${IP:-0.0.0.0}:10080/

    wget命令在上面过程中主要时输出页面内容 : -O - , 直接将页面内容输入到屏幕中 ; -q, 不输出其他命令提示内容

  2. 使用build制作镜像

    [root@docker2 image2]# docker build -t busyboxhttpd:v1.3 ./
    Sending build context to Docker daemon  2.048kB
    Step 1/6 : FROM busybox
     ---> 3a093384ac30
    Step 2/6 : LABEL maintainer="Along <along@along.com>" app="httpd"
     ---> Using cache
     ---> 164a0af1b5dc
    Step 3/6 : ENV WEB_DOC_ROOT="/data/web/html"
     ---> Using cache
     ---> 930023fca80b
    Step 4/6 : RUN mkdir -p ${WEB_DOC_ROOT} &&     echo "<h1>Busybox httpd server</h1>" > ${WEB_DOC_ROOT}/index.html
     ---> Using cache
     ---> 1df364fca443
    Step 5/6 : CMD /bin/httpd -f -h ${WEB_DOC_ROOT}
     ---> Running in 671f9491d0c3
    Removing intermediate container 671f9491d0c3
     ---> 71451fa629e9
    Step 6/6 : HEALTHCHECK --start-period=3s CMD wget -O - -q http://${IP:-0.0.0.0}:${PORT:-80}/
     ---> Running in 474395f265da
    Removing intermediate container 474395f265da
     ---> 757ab550ea25
    Successfully built 757ab550ea25
    Successfully tagged busyboxhttpd:v1.3
  3. 基于此新建镜像运行容器 , 进行验证

    [root@docker2 ~]# docker run --name web2 --rm -d busyboxhttpd:v1.3
    7060a6717e77acecd683a6b05332ab7cec128b836e6aa2d7d5ce8fb1b38b7fd6
    --- 容器刚创建 , 还没检测完时 , 容器还是health 健康状态
    [root@docker2 ~]# docker ps
    CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                            PORTS               NAMES
    7060a6717e77        busyboxhttpd:v1.3   "/bin/sh -c '/bin/ht…"   3 seconds ago       Up 3 seconds (health: starting)                       web2
    --- 30s 检测完时 , 没有检测到10080端口 , 容器变为unhealthy不健康状态
    [root@docker2 ~]# docker ps
    CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                      PORTS               NAMES
    7060a6717e77        busyboxhttpd:v1.3   "/bin/sh -c '/bin/ht…"   30 seconds ago      Up 30 seconds (unhealthy)                       web2

2.4.13 SHELL

SHELL指令允许覆盖用于shell命令形式的默认shell。

Linux上的默认shell是[“/bin /sh” , “-c”] , 在Windows上是[“cmd” , “/ S” , “/ C”]

SHELL指令必须以JSON格式写入Dockerfile。

  • 语法 : SHELL ["executable", "parameters"]

    SHELL指令可以多次出现。

    每个SHELL指令都会覆盖所有先前的SHELL指令 , 并影响所有后续指令。

示例

SHELL ["/bin/sh", "-c"]

RUN lll ; ls

SHELL ["/bin/sh", "-cex"]

RUN lll ; ls

两个RUN运行同一命令

第二个RUN运行的命令会打印出每条命令并当遇到错误时退出

2.4.14 VOLUME

用于在image中创建一个挂载点目录 , 以挂载Docker host上的卷或其它容器上的卷

  • 语法

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

mountpoint : 代表容器中的目录 , 与docker run不同 , Dockerfile中不能指定宿主机目录 , 默认使用docker管理的挂载点

如果挂载点目录路径下此前在文件中存在 , docker run命令会在卷挂载完成后将此前的所有文件复制到新挂载的卷中

VOLUME ["/data"] 

这里的/data目录就会在运行时自动挂载为匿名卷 , 任何向/data中写入的信息都不会记录进容器存储层 , 从而保证了容器存储层的无状态化

注意事项

  1. 使用基于Windows的容器时

    挂载的目录必须是以下之一 :

    • 一个不存在或空的目录

    • C盘以外的盘

  2. 无法通过VOLUME挂载主机目录

    • 为了镜像的可移植性 , VOLUME指令不支持指定主机目录参数(像 docker run -v <主机目录>:<容器目录>是可以指定主机目录的)

    • 如果要指定 , 还是要通过docker run -v来指定主机目录哦

示例一 : 使用docker run -v

# 下面的命令是将宿主机上面的/home/data 目录挂载到了容器中/data 目录 , 无论在容器里面还是物理机中修改对应目录下面的内容 , 都会同步
[root@docker01 ~]# docker run --name test -it -v /home/data:/data centos /bin/sh
# 容器中加入一个文件 
sh-4.4# cd /data/
sh-4.4# ls
sh-4.4# pwd
/data
sh-4.4# echo "hello" > 1.txt
sh-4.4# cat 1.txt 
hello

# 物理机中查看一下
[root@docker01 dockerfiledaemon]# cat /home/data/1.txt 
hello

# 查看一下容器的详情
[root@docker01 dockerfiledaemon]# docker ps
CONTAINER ID   IMAGE     COMMAND     CREATED         STATUS         PORTS     NAMES
8c11c574a6e7   centos    "/bin/sh"   3 minutes ago   Up 3 minutes             test

[root@docker01 dockerfiledaemon]# docker inspect 8c11c574a6e7

        "Mounts": [
            {
                "Type": "bind",
                "Source": "/home/data",
                "Destination": "/data",
                "Mode": "",
                "RW": true,
                "Propagation": "rprivate"
            }
        ],

示例二 : 使用docker run -v参数 , 但不指定物理机的目录

# 启动一个docker 实例
[root@docker01 ~]# docker run --name test -it --rm -v /data centos /bin/sh

# 查看容器运行情况
[root@docker01 dockerfiledaemon]# docker ps
CONTAINER ID   IMAGE     COMMAND     CREATED         STATUS         PORTS     NAMES
d42f7d885c87   centos    "/bin/sh"   6 seconds ago   Up 4 seconds             test

# 从下面可以看到 , source 的路径是自动分配的一个目录
[root@docker01 dockerfiledaemon]# docker inspect d42f7d885c87

        "Mounts": [
            {
                "Type": "volume",
                "Name": "fa853d8d48b38edf36af1064854f5d85102c577dc2581184b979479be21495ed",
                "Source": "/var/lib/docker/volumes/fa853d8d48b38edf36af1064854f5d85102c577dc2581184b979479be21495ed/_data",
                "Destination": "/data",
                "Driver": "local",
                "Mode": "",
                "RW": true,
                "Propagation": ""
            }
        ],

示例三 : 使用dockerfile中挂载点进行

通过docker run命令的-v标识创建的挂载点只能对创建的容器有效。通过dockerfile的VOLUME指令可以在镜像中创建挂载点 , 这样只要通过该镜像创建的容器都有了挂载点。但在dockerfile中无法指定主机上对应的目录 , 是自动生成的。

# 新建一个dockerfile
FROM centos
VOLUME ["/data1","/data2"]


# 构建镜像
[root@docker01 dockerfiledaemon]# docker build -t volume:01 .
Sending build context to Docker daemon  2.048kB
Step 1/2 : FROM centos
 ---> 300e315adb2f
Step 2/2 : VOLUME ["/data1","/data2"]
 ---> Running in 2ac39c2c1918
Removing intermediate container 2ac39c2c1918
 ---> 65ce3fb0ff1a
Successfully built 65ce3fb0ff1a
Successfully tagged volume:01

[root@docker01 dockerfiledaemon]# docker image ls
REPOSITORY    TAG       IMAGE ID       CREATED          SIZE
volume        01        65ce3fb0ff1a   14 seconds ago   209MB


# 创建一个容器
[root@docker01 dockerfiledaemon]# docker run -it --name volumename volume:01 /bin/sh
sh-4.4# 


# 查看容器id
[root@docker01 ~]# docker ps
CONTAINER ID   IMAGE       COMMAND     CREATED          STATUS          PORTS     NAMES
071fdcf0a2eb   volume:01   "/bin/sh"   26 seconds ago   Up 24 seconds             volumename

# 查看挂载点 , 会发现一共有两个自动挂载的目录
[root@docker01 ~]# docker inspect 071fdcf0a2eb
"Mounts": [
            {
                "Type": "volume",
                "Name": "479d38b89f7eb8cbf52fa4b50f1e7c0caf358707bca418a46dcc330b43983817",
                "Source": "/var/lib/docker/volumes/479d38b89f7eb8cbf52fa4b50f1e7c0caf358707bca418a46dcc330b43983817/_data",
                "Destination": "/data2",
                "Driver": "local",
                "Mode": "",
                "RW": true,
                "Propagation": ""
            },
            {
                "Type": "volume",
                "Name": "926f1a3c58d1574433b0488b1f8b0a88011e1143925621729c6ba5b8fd2b5150",
                "Source": "/var/lib/docker/volumes/926f1a3c58d1574433b0488b1f8b0a88011e1143925621729c6ba5b8fd2b5150/_data",
                "Destination": "/data1",
                "Driver": "local",
                "Mode": "",
                "RW": true,
                "Propagation": ""
            }
        ],

2.4.15 ARG

ARG指令类似ENV , 定义了一个变量 ; 区别于ENV : 用户可以在构建时docker build --build-arg <varname> = <value>进行对变量的修改 , ENV不可以 ;

如果用户指定了未在Dockerfile中定义的构建参数 , 那么构建输出警告

  • 语法 : ARG <name>[= <default value>]

当镜像编译成功后 , ARG 指定的变量将不再存在(ENV指定的变量将在镜像中保留)

Docker内置了一些镜像创建变量 , 用户可以直接使用而无须声明 , 包括(不区分大小写)HTTP_PROXY、HTTPS_PROXY、FTP_PROXY、NO_PROXY

ARG和ENV的区别

  • ARG定义的变量只会存在于镜像构建过程 , 启动容器后并不保留这些变量

  • ENV定义的变量在启动容器后仍然保留

ARG 指令有生效范围

如果在FROM指令之前指定 , 那么只能用于FROM指令中

ARG DOCKER_USERNAME=library

FROM ${DOCKER_USERNAME}/alpine

RUN set -x ; echo ${DOCKER_USERNAME}
  • 使用上述Dockerfile会发现无法输出${DOCKER_USERNAME}变量的值

  • 要想正常输出 , 必须在FROM之后再次指定ARG

# 只在 FROM 中生效
ARG DOCKER_USERNAME=library

FROM ${DOCKER_USERNAME}/alpine

# 要想在 FROM 之后使用 , 必须再次指定
ARG DOCKER_USERNAME=library

RUN set -x ; echo ${DOCKER_USERNAME}

多阶段构建的时候 , ARG定义的变量 , 每个FROM都能用

# 这个变量在每个 FROM 中都生效
ARG DOCKER_USERNAME=library

FROM ${DOCKER_USERNAME}/alpine

RUN set -x ; echo 1

FROM ${DOCKER_USERNAME}/alpine

RUN set -x ; echo 2

对于上述 Dockerfile 两个 FROM 指令都可以使用 ${DOCKER_USERNAME} , 对于在各个阶段中使用的变量都必须在每个阶段分别指定 :

ARG DOCKER_USERNAME=library

FROM ${DOCKER_USERNAME}/alpine

# 在FROM 之后使用变量 , 必须在每个阶段分别指定
ARG DOCKER_USERNAME=library

RUN set -x ; echo ${DOCKER_USERNAME}

FROM ${DOCKER_USERNAME}/alpine

# 在FROM 之后使用变量 , 必须在每个阶段分别指定
ARG DOCKER_USERNAME=library

RUN set -x ; echo ${DOCKER_USERNAME}

2.5 DockerFile 多阶段构建

在Docker 17.05版本之前 , 我们构建Docker镜像时 , 通常会采用两种方式 :

2.5.1 全部放入一个 Dockerfile

一种方式是将所有的构建过程编包含在一个 Dockerfile 中 , 包括项目及其依赖库的编译、测试、打包等流程 , 这里可能会带来的一些问题 :

  • 镜像层次多 , 镜像体积较大 , 部署时间变长

  • 源代码存在泄露的风险

例如 , 编写 app.go 文件 , 该程序输出 Hello World!

package main

import "fmt"

func main(){
    fmt.Printf("Hello World!");
}

编写 Dockerfile.one 文件

FROM golang:alpine

RUN apk --no-cache add git ca-certificates

WORKDIR /go/src/github.com/go/helloworld/

COPY app.go .

RUN go get -d -v github.com/go-sql-driver/mysql \
  && CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app . \
  && cp /go/src/github.com/go/helloworld/app /root

WORKDIR /root/

CMD ["./app"]

构建镜像

$ docker build -t go/helloworld:1 -f Dockerfile.one .

2.5.2 分散到多个 Dockerfile

另一种方式 , 就是我们事先在一个 Dockerfile 将项目及其依赖库编译测试打包好后 , 再将其拷贝到运行环境中 , 这种方式需要我们编写两个 Dockerfile 和一些编译脚本才能将其两个阶段自动整合起来 , 这种方式虽然可以很好地规避第一种方式存在的风险 , 但明显部署过程较复杂。

例如 , 编写 Dockerfile.build 文件

FROM golang:alpine

RUN apk --no-cache add git

WORKDIR /go/src/github.com/go/helloworld

COPY app.go .

RUN go get -d -v github.com/go-sql-driver/mysql \
  && CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .

编写 Dockerfile.copy 文件

FROM alpine:latest

RUN apk --no-cache add ca-certificates

WORKDIR /root/

COPY app .

CMD ["./app"]

新建 build.sh

#!/bin/sh
echo Building go/helloworld:build

docker build -t go/helloworld:build . -f Dockerfile.build

docker create --name extract go/helloworld:build
docker cp extract:/go/src/github.com/go/helloworld/app ./app
docker rm -f extract

echo Building go/helloworld:2

docker build --no-cache -t go/helloworld:2 . -f Dockerfile.copy
rm ./app

现在运行脚本即可构建镜像

$ chmod +x build.sh

$ ./build.sh

对比两种方式生成的镜像大小

$ docker image ls

REPOSITORY      TAG    IMAGE ID        CREATED         SIZE
go/helloworld   2      f7cf3465432c    22 seconds ago  6.47MB
go/helloworld   1      f55d3e16affc    2 minutes ago   295MB

2.5.3 使用多阶段构建

为解决以上问题 , Docker v17.05开始支持多阶段构建 (multistage builds)。使用多阶段构建我们就可以很容易解决前面提到的问题 , 并且只需要编写一个 Dockerfile :

例如 , 编写 Dockerfile 文件

FROM golang:alpine as builder

RUN apk --no-cache add git

WORKDIR /go/src/github.com/go/helloworld/

RUN go get -d -v github.com/go-sql-driver/mysql

COPY app.go .

RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .

FROM alpine:latest as prod

RUN apk --no-cache add ca-certificates

WORKDIR /root/

COPY --from=0 /go/src/github.com/go/helloworld/app .

CMD ["./app"]

构建镜像

$ docker build -t go/helloworld:3 .

对比三个镜像大小

$ docker image ls

REPOSITORY        TAG   IMAGE ID         CREATED            SIZE
go/helloworld     3     d6911ed9c846     7 seconds ago      6.47MB
go/helloworld     2     f7cf3465432c     22 seconds ago     6.47MB
go/helloworld     1     f55d3e16affc     2 minutes ago      295MB

很明显使用多阶段构建的镜像体积小 , 同时也完美解决了上边提到的问题。

2.5.4 只构建某一阶段的镜像

我们可以使用 as 来为某一阶段命名 , 例如

FROM golang:alpine as builder

例如当我们只想构建 builder 阶段的镜像时 , 增加 --target=builder 参数即可

$ docker build --target builder -t username/imagename:tag .

2.5.5 构建时从其他镜像复制文件

上面例子中我们使用 COPY --from=0 /go/src/github.com/go/helloworld/app . 从上一阶段的镜像中复制文件 , 我们也可以复制任意镜像中的文件。

$ COPY --from=nginx:latest /etc/nginx/nginx.conf /nginx.conf

3. DockerFile制作镜像

3.1 nginx镜像制作

3.1.1 制作yum版nginx镜像

CentOS安装nginx

  1. 先经过docker下载centos镜像

    [root@centos-7 ~]# yum install docker-ce  -y #安装官网docker-ce , 默认安装最新版
    [root@centos-7 ~]# systemctl start docker  #启动docker
    [root@centos-7 ~]# docker pull centos  #下载centos镜像
  2. 切换到一个新建的nginx目录下 , 进行实验测试

    [root@centos-7 ~]# mkdir /data/nginx
    [root@centos-7 ~]# cd /data/nginx
  3. 创建一个nginx测试页 , 然后访问网页测试

    [root@centos-7 nginx]# cd  /data/nginx
    [root@centos-7 nginx]# cat index.html  #修改一个测试页面
    nginx web test
    [root@centos-7 nginx]# cat index1.html  #修改一个测试页面
    nginx web index1 test
    [root@centos-7 nginx]# tar zcvf  code.tar.gz index1.html index.html
  4. 开始在nginx目录下创建一个Dockerfile文件 , 创建的nginx镜像 , 不能在镜像内改配置文件 , 只能将配置文件传出来 , 在宿主机内修改 , 然后再传到配置文件中 , 测试的代码也是如此。

    FROM centos
    
    # docker image inspect 查看镜像元数据时 ,写入"Author": "liu 958421213@@qq.com"
    MAINTAINER liu 958421213@@qq.com
    
    # 写入镜像元数据的LABEL标签中
    LABEL auth="liu 9584@qq.com" 
    
    #修改镜像中的yum源
    RUN sed -e "s|^mirrorlist=|#mirrorlist=|g" \
        -e "s|^#baseurl=http://mirror.centos.org/centos/\$releasever|baseurl=https://mirrors.tuna.tsinghua.edu.cn/centos-vault/8.4.2105|g" \
        -e "s|^#baseurl=http://mirror.centos.org/\$contentdir/\$releasever|baseurl=https://mirrors.tuna.tsinghua.edu.cn/centos-vault/8.4.2105|g" \
        -i.bak \
        /etc/yum.repos.d/CentOS-*.repo
    
    #通过yum源安装nginx
    RUN yum install epel-release  -y &&  yum install nginx -y   && rm -rf /usr/share/nginx/html/index.html  
      
    ENV MY_HOST 192.168.7.101
    
    
    ADD code.tar.gz /usr/share/nginx/html/   
    
    
    EXPOSE 80 443
     
    CMD ["nginx","-g" ,"daemon off;"]
  5. 使用docker build命令生成新的镜像

    docker build -t nginx:make .
  6. 运行容器

    [root@centos7 ~]# docker run -it -d -p 8080:80 nginx:make

Ubuntu安装nginx

  1. 编写dockerfile

    [root@centos7 ubuntu]# cat dockerfile 
    # 构建一个基于ubuntu的docker定制镜像
    # 基础镜像
    FROM ubuntu
    
    # 镜像作者
    MAINTAINER my_name polotest@domain.com
    
    # 执行命令
    ## 换成国内的软件源
    RUN sed -i 's/archive.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list 
    RUN sed -i 's/security.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list 
    
    ## 安装nginx
    RUN apt update >/dev/null 2>&1 
    RUN apt install nginx -y >/dev/null 2>&1
    
    # 暴露对外端口
    EXPOSE 80
  2. 使用docker build命令生成新的镜像

    docker build --network=host -t ubuntu-nginx:v2 .
  3. 运行容器

    [root@centos7 ~]# docker run -it -d -p 80:80 ubuntu-nginx:v2  nginx -g 'daemon off;'

    需要注意的是,容器中因为没有像CentOS系统中systemctl接管服务, 所以容器中的命令不能直接设置成daemon ,在后台运行。而是通过脚本或者命令, 在容器中的前台运行。

3.1.2 制作编译nginx镜像

nginx源码包下载官网 : https://nginx.org/en/download.html

  1. 创建一个nginx测试页 , 然后访问网页测试 

    [root@centos7 ~]# cd /opt/dockerfile/system/centos/
    [root@centos7 centos]# cat index.html  #修改一个测试页面
    nginx web test
    [root@centos7 centos]# cat index1.html  #修改一个测试页面
    nginx web index1 test
    [root@centos7 centos]# tar zcvf  code.tar.gz index1.html index.html
    [root@centos7 centos]# ls
    code.tar.gz  dockerfile  index1.html  index.html  nginx-1.24.0.tar.gz  nginx.conf
    [root@centos7 centos]# 
  2. 在上面的基础修改DockerFile配置文件

    [root@centos7 centos]# cat dockerfile 
    FROM centos
     
    MAINTAINER liu xxxxx@@qq.com
    
    RUN sed -e "s|^mirrorlist=|#mirrorlist=|g" \
        -e "s|^#baseurl=http://mirror.centos.org/centos/\$releasever|baseurl=https://mirrors.tuna.tsinghua.edu.cn/centos-vault/8.4.2105|g" \
        -e "s|^#baseurl=http://mirror.centos.org/\$contentdir/\$releasever|baseurl=https://mirrors.tuna.tsinghua.edu.cn/centos-vault/8.4.2105|g" \
        -i.bak \
        /etc/yum.repos.d/CentOS-*.repo
    
    RUN yum install epel-release -y && yum -y install gcc gcc-c++ make automake pcre pcre-devel zlib zlib-devel openssl openssl-devel iproute net-tools iotop
    
    ADD nginx-1.24.0.tar.gz /usr/local/src/
    
    WORKDIR /usr/local/src/nginx-1.24.0/
    #RUN cd /usr/local/src/nginx-1.24.0/  
    
    RUN ./configure --prefix=/apps/nginx && make && make install 
    
    RUN ln -sv /apps/nginx/sbin/nginx /usr/sbin/nginx
    
    
    ADD code.tar.gz /apps/nginx/html/   
     
    EXPOSE 80 443
     
    CMD ["nginx","-g","daemon off;"]
  3. 制作镜像

    docker build -t nginx:v1 .
  4. 生成对应容器, 并启动

    docker run -it -d -p 80:80 nginx:v1 

3.2 jdk业务镜像制作

基于官方提供的centos基础镜像构建JDK和tomcat镜像 , 先构建JDK镜像 , 然后再基于JDK镜像构建tomcat镜像。

3.2.1 制作JDK镜像

JDK下载路径 : Java Downloads | Oracle 中国

  1. 先下载基础centos镜像

    [root@centos7 centos]# docker pull centos
    Using default tag: latest
    latest: Pulling from library/centos
    a1d0c7532777: Already exists 
    Digest: sha256:a27fd8080b517143cbbbab9dfb7c8571c40d67d534bbdee55bd6c473f432b177
    Status: Downloaded newer image for centos:latest
    docker.io/library/centos:latest
    [root@centos7 centos]# docker images
    REPOSITORY   TAG       IMAGE ID       CREATED       SIZE
    centos       latest    5d0da3dc9764   2 years ago   231MB
  2. 切换到指定的centos系统目录下 , 安装一些常用命令

    [root@centos7 ~]#  mkdir  /opt/dockerfile/{web/{nginx,tomcat,jdk,apache},system/{centos,ubuntu,redhat}} -p # 先创建存放镜像目录
    [root@centos7 ~]# cd /opt/dockerfile/system/centos
  3. 创建一个Dockerfile文件

    [root@centos7 centos]# cat dockerfile
    FROM centos
     
    
    RUN sed -e "s|^mirrorlist=|#mirrorlist=|g" \
        -e "s|^#baseurl=http://mirror.centos.org/centos/\$releasever|baseurl=https://mirrors.tuna.tsinghua.edu.cn/centos-vault/8.4.2105|g" \
        -e "s|^#baseurl=http://mirror.centos.org/\$contentdir/\$releasever|baseurl=https://mirrors.tuna.tsinghua.edu.cn/centos-vault/8.4.2105|g" \
        -i.bak \
        /etc/yum.repos.d/CentOS-*.repo
    
    RUN yum install epel-release -y && yum -y install gcc gcc-c++ make automake pcre pcre-devel zlib zlib-devel openssl openssl-devel iproute net-tools iotop
    
    
     
    RUN useradd nginx -u 2019 && useradd tomcat -u  2020
    
    RUN rm -rf /etc/localtime && ln -sv /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
  4. 生成CentOS系统基础命令镜像

    [root@centos7 centos]# docker build -t centos-base:8.4.2105 . 
    [+] Building 1.8s (9/9) FINISHED                                                                                                                                                                                  
     => [internal] load build definition from dockerfile                                                                                                                                                         0.1s
     => => transferring dockerfile: 727B                                                                                                                                                                         0.1s
     => [internal] load .dockerignore                                                                                                                                                                            0.1s
     => => transferring context: 2B                                                                                                                                                                              0.0s
     => [internal] load metadata for docker.io/library/centos:latest                                                                                                                                             0.0s
     => [1/5] FROM docker.io/library/centos                                                                                                                                                                      0.0s
     => CACHED [2/5] RUN sed -e "s|^mirrorlist=|#mirrorlist=|g"     -e "s|^#baseurl=http://mirror.centos.org/centos/$releasever|baseurl=https://mirrors.tuna.tsinghua.edu.cn/centos-vault/8.4.2105|g"     -e "s  0.0s
     => CACHED [3/5] RUN yum install epel-release -y && yum -y install gcc gcc-c++ make automake pcre pcre-devel zlib zlib-devel openssl openssl-devel iproute net-tools iotop                                   0.0s
     => [4/5] RUN useradd nginx -u 2019 && useradd tomcat -u  2020                                                                                                                                               0.9s
     => [5/5] RUN rm -rf /etc/localtime && ln -sv /usr/share/zoneinfo/Asia/Shanghai /etc/localtime                                                                                                               0.4s
     => exporting to image                                                                                                                                                                                       0.1s
     => => exporting layers                                                                                                                                                                                      0.1s
     => => writing image sha256:1fed1882e596c4191b1cdecb2263eccad0e55e4ad9bd827906d723861661683b                                                                                                                 0.0s
     => => naming to docker.io/library/centos-base:8.4.2105                
  5. 查看创建好的CentOS镜像

    image-20240211160410661
  6. 切换到jdk目录下 , 创建指定的jdk版本目录 , 制作profile环境变量文件 , 并制作JDK镜像

    切换到指定的jdk目录下 , 并将下载下来的jdk文件传到/opt/dockerfile/web/jdk/

    [root@centos7 jdk]# ls
    jdk-8u401-linux-x64.tar.gz  profile

    生成profile

    [root@centos-7 8u92]# cat profile  #在当前目录下创建profile环境变量文件
    # /etc/profile
     
    # System wide environment and startup programs, for login setup
    # Functions and aliases go in /etc/bashrc
     
    # It's NOT a good idea to change this file unless you know what you
    # are doing. It's much better to create a custom.sh shell script in
    # /etc/profile.d/ to make custom changes to your environment, as this
    # will prevent the need for merging in future updates.
     
    pathmunge () {
        case ":${PATH}:" in
            *:"$1":*)
                ;;
            *)
                if [ "$2" = "after" ] ; then
                    PATH=$PATH:$1
                else
                    PATH=$1:$PATH
                fi
        esac
    }
     
     
    if [ -x /usr/bin/id ]; then
        if [ -z "$EUID" ]; then
            # ksh workaround
            EUID=`/usr/bin/id -u`
            UID=`/usr/bin/id -ru`
        fi
        USER="`/usr/bin/id -un`"
        LOGNAME=$USER
        MAIL="/var/spool/mail/$USER"
    fi
     
    # Path manipulation
    if [ "$EUID" = "0" ]; then
        pathmunge /usr/sbin
        pathmunge /usr/local/sbin
    else
        pathmunge /usr/local/sbin after
        pathmunge /usr/sbin after
    fi
     
    HOSTNAME=`/usr/bin/hostname 2>/dev/null`
    HISTSIZE=1000
    if [ "$HISTCONTROL" = "ignorespace" ] ; then
        export HISTCONTROL=ignoreboth
    else
        export HISTCONTROL=ignoredups
    fi
     
    export PATH USER LOGNAME MAIL HOSTNAME HISTSIZE HISTCONTROL
     
    # By default, we want umask to get set. This sets it for login shell
    # Current threshold for system reserved uid/gids is 200
    # You could check uidgid reservation validity in
    # /usr/share/doc/setup-*/uidgid file
    if [ $UID -gt 199 ] && [ "`/usr/bin/id -gn`" = "`/usr/bin/id -un`" ]; then
        umask 002
    else
        umask 022
    fi
     
    for i in /etc/profile.d/*.sh /etc/profile.d/sh.local ; do
        if [ -r "$i" ]; then
            if [ "${-#*i}" != "$-" ]; then
                . "$i"
            else
                . "$i" >/dev/null
            fi
        fi
    done
     
    unset i
    unset -f pathmunge
     
    export JAVA_HOME=/usr/local/jdk
    export TOMCAT_HOME=/apps/tomcat
    export PATH=$JAVA_HOME/bin:$JAVA_HOME/jre/bin:$TOMCAT_HOME/bin:$PATH
    export CLASSPATH=.$CLASSPATH:$JAVA_HOME/lib:$JAVA_HOME/jre/lib:$JAVA_HOME/lib/tools.jar
  7. 创建Dockerfile文件

    [root@centos7 jdk]# cat dockerfile 
    FROM centos-base:8.4.2105
    
    ADD jdk-8u401-linux-x64.tar.gz /usr/local/src/
    
    RUN ln -sv /usr/local/src/jdk1.8.0_401 /usr/local/jdk
    
    ADD profile /etc/profile
    
    ENV JAVA_HOME /usr/local/jdk
    ENV JRE_HOME $JAVA_HOME/jre
    ENV CLASSPATH $JAVA_HOME/lib/:$JRE_HOME/lib/
    ENV PATH $PATH:$JAVA_HOME/bin
  8. 生成jdk镜像

    [root@centos7 jdk]# docker build -t jdk-base:1.8.0.401 .
    [+] Building 32.4s (9/9) FINISHED                                                                                                                                                                                 
     => [internal] load build definition from dockerfile                                                                                                                                                         0.0s
     => => transferring dockerfile: 327B                                                                                                                                                                         0.0s
     => [internal] load .dockerignore                                                                                                                                                                            0.0s
     => => transferring context: 2B                                                                                                                                                                              0.0s
     => [internal] load metadata for docker.io/library/centos-base:8.4.2105                                                                                                                                      0.0s
     => [1/4] FROM docker.io/library/centos-base:8.4.2105                                                                                                                                                        0.5s
     => [internal] load build context                                                                                                                                                                            6.7s
     => => transferring context: 141.63MB                                                                                                                                                                        6.6s
     => [2/4] ADD jdk-8u401-linux-x64.tar.gz /usr/local/src/                                                                                                                                                    19.9s
     => [3/4] RUN ln -sv /usr/local/src/jdk1.8.0_401 /usr/local/jdk                                                                                                                                              4.0s
     => [4/4] ADD profile /etc/profile                                                                                                                                                                           0.0s
     => exporting to image                                                                                                                                                                                       1.5s
     => => exporting layers                                                                                                                                                                                      1.4s
     => => writing image sha256:1c21819c43fc7511f28b22833517f7f1ed8be05db1a00baeaa707df5b308ba49                                                                                                                 0.0s
     => => naming to docker.io/library/jdk-base:1.8.0.401   
  9. 测试生成的镜像

    [root@centos7 jdk]# docker images
    REPOSITORY    TAG         IMAGE ID       CREATED             SIZE
    jdk-base      1.8.0.401   1c21819c43fc   8 seconds ago       883MB
    centos-base   8.4.2105    1fed1882e596   About an hour ago   533MB
    centos        latest      5d0da3dc9764   2 years ago         231MB
    [root@centos7 jdk]# docker run -d -it --rm jdk-base:1.8.0.401 bash
    714b1e6e0f1590fc47840294206875d5551d477840d926d83e14c257abac5852
    [root@centos7 jdk]# docker exec -it  714 bash
    [root@714b1e6e0f15 /]# ls
    bin  dev  etc  home  lib  lib64  lost+found  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
    [root@714b1e6e0f15 /]# java -version
    java version "1.8.0_401"
    Java(TM) SE Runtime Environment (build 1.8.0_401-b10)
    Java HotSpot(TM) 64-Bit Server VM (build 25.401-b10, mixed mode)

3.2.2 制作tomcat镜像

tomcat 镜像下载: https://archive.apache.org/dist/tomcat/tomcat-8/

  1. 切换到tomcat 目录, 准备dockerfile文件

    [root@centos7 tomcat-base]# cd /opt/dockerfile/web/tomcat/tomcat-base/
    [root@centos7 tomcat-base]# ls
    apache-tomcat-8.5.98.tar.gz
    [root@centos7 tomcat-base]# cat dockerfile 
    FROM jdk-base:1.8.0.401
    
    ADD apache-tomcat-8.5.98.tar.gz /apps
    
    RUN ln -sv /apps/apache-tomcat-8.5.98 /apps/tomcat 
  2. 生成tomcat-base镜像

    [root@centos7 tomcat-base]# docker build -t tomcat-base:8.5.98 .
    [+] Building 1.4s (8/8) FINISHED                                                                                                                                                                                  
     => [internal] load build definition from dockerfile                                                                                                                                                         0.0s
     => => transferring dockerfile: 153B                                                                                                                                                                         0.0s
     => [internal] load .dockerignore                                                                                                                                                                            0.0s
     => => transferring context: 2B                                                                                                                                                                              0.0s
     => [internal] load metadata for docker.io/library/jdk-base:1.8.0.401                                                                                                                                        0.0s
     => [internal] load build context                                                                                                                                                                            0.3s
     => => transferring context: 10.86MB                                                                                                                                                                         0.2s
     => [1/3] FROM docker.io/library/jdk-base:1.8.0.401                                                                                                                                                          0.1s
     => [2/3] ADD apache-tomcat-8.5.98.tar.gz /apps                                                                                                                                                              0.4s
     => [3/3] RUN ln -sv /apps/apache-tomcat-8.5.98 /apps/tomcat                                                                                                                                                 0.5s
     => exporting to image                                                                                                                                                                                       0.2s
     => => exporting layers                                                                                                                                                                                      0.2s
     => => writing image sha256:02e0b9f22b6ded7e30b9b08d63cae8b49134256a519324343ccf1b8647dd9c0f                                                                                                                 0.0s
     => => naming to docker.io/library/tomcat-base:8.5.98    
  3. 将Dockerfile、build脚本与要执行的tomcat脚本进行分离, 因此在/opt/dockerfile/web/tomcat/目录下创建一个目录 : tomcat-app1

    [root@centos7 tomcat]# mkdir tomcat-app1
    [root@centos7 tomcat]# cd tomcat-app1
  4. 如果宿主机安装好了tomcat文件, 然后修改里边的sever.xml文件, 指定index.html文件路径。

    vim  /etc/tomcat/conf/server.xml #修改本地的server.xml文件并传到此目录下
    <Host name="localhost"  appBase="/data/tomcat/webapps"  unpackWARs="true" autoDeploy="true">  # 指定存放代码路径到 /data/tomcat/webapps目录下
  5. 创建一个index.html测试页面

    [root@centos7 tomcat-app1]# cat index.html 
    tomcat web app1 page
  6. 创建Dockerfile文件和run_tomcat.sh脚本

    [root@centos7 tomcat-app1]# cat Dockerfile 
    FROM tomcat-base:8.5.98
    
    COPY index.html /data/tomcat/webapps/app1/
    
    COPY run_tomcat.sh /apps/tomcat/bin/
    
    COPY server.xml /apps/tomcat/conf/
    
    RUN chown -R tomcat.tomcat /apps/apache-tomcat-8.5.98 /apps/tomcat /data/tomcat
    
    EXPOSE 8080 443
    
    CMD ["/apps/tomcat/bin/run_tomcat.sh"]
    [root@centos7 tomcat-app1]# cat run_tomcat.sh 
    #!/bin/bash
    source /etc/profile
    
    echo "1.2.1.3 ww.google.net" >> /etc/hosts
    
    su - tomcat -c "/apps/tomcat/bin/catalina.sh start"
    #su - tomcat -c "/apps/tomcat/bin/catalina.sh run"
    
    #为了测试拉起容器, 执行脚本时, 可以运行起tomcat, 没有此命令, 就会运行完容器又会退出tomcat服务
    tail -f /etc/hosts
    
    [root@centos7 tomcat-app1]# chmod +x run_tomcat.sh 
    [root@centos7 tomcat-app1]# ls
    Dockerfile  index.html  run_tomcat.sh  server.xml
    [root@centos7 tomcat-app1]#
  7. 生成镜像

    [root@centos7 tomcat-app1]# docker build -t tomcat-app1:v1  .
    [+] Building 1.6s (10/10) FINISHED                                                                                                                                                                                
     => [internal] load build definition from Dockerfile                                                                                                                                                         0.0s
     => => transferring dockerfile: 319B                                                                                                                                                                         0.0s
     => [internal] load .dockerignore                                                                                                                                                                            0.0s
     => => transferring context: 2B                                                                                                                                                                              0.0s
     => [internal] load metadata for docker.io/library/tomcat-base:8.5.98                                                                                                                                        0.0s
     => [1/5] FROM docker.io/library/tomcat-base:8.5.98                                                                                                                                                          0.0s
     => [internal] load build context                                                                                                                                                                            0.0s
     => => transferring context: 7.97kB                                                                                                                                                                          0.0s
     => CACHED [2/5] COPY index.html /data/tomcat/webapps/app1/                                                                                                                                                  0.0s
     => CACHED [3/5] COPY run_tomcat.sh /apps/tomcat/bin/                                                                                                                                                        0.0s
     => [4/5] COPY server.xml /apps/tomcat/conf/                                                                                                                                                                 0.0s
     => [5/5] RUN chown -R tomcat.tomcat /apps/apache-tomcat-8.5.98 /apps/tomcat /data/tomcat                                                                                                                    1.4s
     => exporting to image                                                                                                                                                                                       0.1s
     => => exporting layers                                                                                                                                                                                      0.1s
     => => writing image sha256:9e7334922dd921cfd23d86f2c3e420905917e7f0c25bbc0b8b387baaba456ea1                                                                                                                 0.0s
     => => naming to docker.io/library/tomcat-app1:v1
  8. 生成对应容器,并启动

    docker run -it -d --rm -p 8080:8080 tomcat-app1:v1  
  9. 测试tomcat页面

    image-20240211223908896

3.2.3 创建第二个tomcat服务

方法很简单, 只需要将tomcat-app1目录复制一份, 修改里边的index.html文件和docker build命令

  1. 准备目录

    [root@centos7 tomcat-app1]# pwd
    /opt/dockerfile/web/tomcat/tomcat-app1
    [root@centos7 tomcat-app1]# cd ..
    [root@centos7 tomcat]#  cp -ra tomcat-app1 tomcat-app2
    [root@centos7 tomcat]# cd tomcat-app2
    [root@centos7 tomcat-app2]# 
  2. 修改tomcat访问页面

    # vim  index.html
    tomcat web app2 page
  3. 生成新的tomcat镜像

    docker build -t tomcat-app2:v1  .
  4. 启动容器

    docker run -it -d --rm -p 8081:8080 tomcat-app2:v1

3.3 haproxy镜像制作

官网下载haproxy源码包:https://www.haproxy.org/

lua 下载地址: https://www.lua.org/ftp/lua-5.4.6.tar.gz

haproxy 下载地址 : https://www.haproxy.org/download/2.9/src/haproxy-2.9.4.tar.gz

  1. 准备haproxy的dockerfile

    [root@centos7 haproxy]# cat Dockerfile 
    FROM centos-base:8.4.2105
    
    ADD haproxy-2.9.4.tar.gz /usr/local/src/
    ADD lua-5.4.6.tar.gz /usr/local/src/
    
    WORKDIR /usr/local/src/lua-5.4.6/
    RUN make linux test
    
    WORKDIR /usr/local/src/haproxy-2.9.4/
    
    RUN make  ARCH=x86_64 TARGET=linux-glibc USE_PCRE=1 USE_OPENSSL=1 USE_ZLIB=1   USE_CPU_AFFINITY=1 USE_LUA=1 LUA_INC=/usr/local/src/lua-5.4.6/src/ LUA_LIB=/usr/local/src/lua-5.4.6/src/ PREFIX=/usr/local/haproxy 
    
    RUN  make install PREFIX=/usr/local/haproxy && mkdir /usr/local/haproxy/run/
    
    COPY haproxy.cfg /etc/haproxy/
    
    COPY run_haproxy.sh /usr/bin/
    
    
    EXPOSE 80 9999
    
    CMD ["/usr/bin/run_haproxy.sh"]
    
    [root@centos7 haproxy]# pwd
    /opt/dockerfile/web/haproxy
  2. 准备运行的cmd脚本

    [root@centos7 haproxy]# cat run_haproxy.sh 
    #!/bin/bash
     
    /usr/local/haproxy/sbin/haproxy -f /etc/haproxy/haproxy.cfg  # 执行此脚本, 运行haproxy
     
    tail -f /etc/hosts
    
    [root@centos7 haproxy]# ls
    Dockerfile  haproxy-2.9.4.tar.gz  haproxy.cfg  lua-5.4.6.tar.gz  run_haproxy.sh
  3. 准备haproxy的配置文件

    [root@centos7 haproxy]# cat haproxy.cfg 
    global
    maxconn 100000
    chroot /usr/local/haproxy
    #stats socket /var/lib/haproxy/haproxy.sock mode 600 level admin
    uid 99
    gid 99
    daemon
    #nbproc 2
    #cpu-map 1 0
    #cpu-map 2 1
    pidfile /usr/local/haproxy/run/haproxy.pid
    log 127.0.0.1 local3 info
     
    defaults
    option http-keep-alive
    option  forwardfor
    maxconn 100000
    mode http
    timeout connect 300000ms
    timeout client  300000ms
    timeout server  300000ms
     
    listen stats
     mode http
     bind 0.0.0.0:9999
     stats enable
     log global
     stats uri     /haproxy-status
     stats auth    haadmin:123456
     
    listen  web_port
     bind 0.0.0.0:80
     mode http
     log global
     server web1  192.168.194.130:8080  check inter 3000 fall 2 rise 5
     server web2  192.168.194.130:8081  check inter 3000 fall 2 rise 5  
  4. 构建镜像

    [root@centos7 haproxy]# docker build -t haproxy:2.9.4 .                                        
  5. 运行容器

    [root@centos7 haproxy]# docker run -d -it --rm -p 80:80 -p 9999:9999 haproxy:2.9.4 
    8ac76d58b6de5ffc4ef8951a9207ebc072a551501d09cd2bb1f324c5c0aefc69
  6. 查看运行的容器

    [root@centos7 haproxy]# docker ps -a
    CONTAINER ID   IMAGE            COMMAND                  CREATED         STATUS         PORTS                                                                          NAMES
    8ac76d58b6de   haproxy:2.9.4    "/usr/bin/run_haprox…"   8 seconds ago   Up 7 seconds   0.0.0.0:80->80/tcp, :::80->80/tcp, 0.0.0.0:9999->9999/tcp, :::9999->9999/tcp   musing_merkle
    532fbe362f96   tomcat-app2:v1   "/apps/tomcat/bin/ru…"   7 hours ago     Up 7 hours     443/tcp, 0.0.0.0:8081->8080/tcp, :::8081->8080/tcp                             boring_merkle
    fd0fca27554f   tomcat-app1:v1   "/apps/tomcat/bin/ru…"   7 hours ago     Up 7 hours     443/tcp, 0.0.0.0:8080->8080/tcp, :::8080->8080/tcp                             cranky_darwin
  7. 测试网页

    image-20240212004746208

    直接网页访问

    image-20240212004841910

3.4 alpine

3.4.1 anpine镜像介绍

Alpine 操作系统是一个面向安全的轻型 Linux 发行版。它不同于通常 Linux 发行版 , Alpine 采用了 musl libc 和 busybox 以减小系统的体积和运行时资源消耗 , 但功能上比busybox 又完善的多 , 因此得到开源社区越来越多的青睐。在保持瘦身的同时 , Alpine 还提供了自己的包管理工具 apk , 可以通过 https://pkgs.alpinelinux.org/packages 网站上查询包信息 , 也可以直接通过 apk 命令直接查询和安装各种软件。

Alpine 由非商业组织维护的 , 支持广泛场景的 Linux发行版 , 它特别为资深/重度Linux用户而优化 , 关注安全 , 性能和资源效能。Alpine 镜像可以适用于更多常用场景 , 并且是一个优秀的可以适用于生产的基础系统/环境。

Alpine Docker 镜像也继承了 Alpine Linux 发行版的这些优势。相比于其他 Docker 镜像 , 它的容量非常小 , 仅仅只有 5 MB 左右(对比Ubuntu 系列镜像接近 200 MB) , 且拥有非常友好的包管理机制。官方镜像来自 docker-alpine 项目。

目前 Docker 官方已开始推荐使用 Alpine 替代之前的 Ubuntu 做为基础镜像环境。这样会带来多个好处。包括镜像下载速度加快、镜像安全性提高 、主机之间的切换更方便 、占用更少磁盘空间等。

下表是官方镜像的大小比较 :

REPOSITORY          TAG           IMAGE ID          VIRTUAL SIZE
alpine              latest        4e38e38c8ce0      4.799 MB
debian              latest        4d6ce913b130      84.98 MB
ubuntu              latest        b39b81afc8ca      188.3 MB
centos              latest        8efe422e6104      210 MB

范例 : alpine安装软件

#修改源替换成阿里源, 将里面 dl-cdn.alpinelinux.org 的 改成 mirrors.aliyun.com
vi etc/apk/repositories
http://mirrors.aliyun.com/alpine/v3.8/main/
http://mirrors.aliyun.com/alpine/v3.8/community/

#更新源
apk update

#安装软件
apk add vim

#删除软件
apk del openssh openntp vim

范例 :

/ # apk --help
apk-tools 2.10.4, compiled for x86_64.

Installing and removing packages:
  add       Add PACKAGEs to 'world' and install (or upgrade) them, while ensuring that all dependencies are met
  del       Remove PACKAGEs from 'world' and uninstall them

System maintenance:
  fix       Repair package or upgrade it without modifying main dependencies
  update    Update repository indexes from all remote repositories
  upgrade   Upgrade currently installed packages to match repositories
  cache     Download missing PACKAGEs to cache and/or delete unneeded files from cache

Querying information about packages:
  info      Give detailed information about PACKAGEs or repositories
  list      List packages by PATTERN and other criteria
  dot       Generate graphviz graphs
  policy    Show repository policy for packages

Repository maintenance:
  index     Create repository index file from FILEs
  fetch     Download PACKAGEs from global repositories to a local directory
  verify    Verify package integrity and signature
  manifest  Show checksums of package contents

Use apk <command> --help for command-specific help.
Use apk --help --verbose for a full command listing.

This apk has coffee making abilities.

/ # apk add  nginx
/ # apk info nginx
nginx-1.16.1-r6 description:
HTTP and reverse proxy server (stable version)

nginx-1.16.1-r6 webpage:
https://www.nginx.org/

nginx-1.16.1-r6 installed size:
1126400

~ # apk manifest nginx
sha1:d21a96358a10b731f8847e6d32799efdc2a7f421  etc/logrotate.d/nginx
sha1:50bd6d3b4f3e6b577d852f12cd6939719d2c2db5  etc/init.d/nginx
sha1:379c1e2a2a5ffb8c91a07328d4c9be2bc58799fd  etc/nginx/scgi_params
sha1:da38e2a0dded838afbe0eade6cb837ac30fd8046  etc/nginx/fastcgi_params
sha1:cc2fcdb4605dcac23d59f667889ccbdfdc6e3668  etc/nginx/uwsgi_params
sha1:cbf596ddb3433a8e0d325f3c188bec9c1bb746b3  etc/nginx/fastcgi.conf
sha1:e39dbc36680b717ec902fadc805a302f1cf62245  etc/nginx/mime.types
sha1:e9dddf20f1196bb67eef28107438b60c4060f4d3  etc/nginx/nginx.conf
sha1:7b2a4da1a14166442c10cbf9e357fa9fb53542ca  etc/nginx/conf.d/default.conf
sha1:cd7f5dc8ccdc838a2d0107511c90adfe318a81e7  etc/conf.d/nginx
sha1:05f050f6ed86c5e6b48c2d2328e81583315431be  usr/sbin/nginx
sha1:c3f02ca81f7f2c6bde3f878b3176f225c7781c7d  var/lib/nginx/modules
sha1:0510312d465b86769136983657df98c1854f0b60  var/lib/nginx/run
sha1:35db17c18ce0b9f84a3cc113c8a9e94e19f632b1  var/lib/nginx/logs
sha1:7dd71afcfb14e105e80b0c0d7fce370a28a41f0a  var/lib/nginx/html/index.html
sha1:95de71d58b37f9f74bede0e91bc381d6059fc2d7  var/lib/nginx/html/50x.html
~ # ls -l /bin
total 824
lrwxrwxrwx    1 root     root            12 Jan 16 21:52 arch -> /bin/busybox
lrwxrwxrwx    1 root     root            12 Jan 16 21:52 ash -> /bin/busybox
lrwxrwxrwx    1 root     root            12 Jan 16 21:52 base64 -> /bin/busybox
lrwxrwxrwx    1 root     root            12 Jan 16 21:52 bbconfig -> /bin/busybox
-rwxr-xr-x    1 root     root        841288 Jan 15 10:36 busybox
lrwxrwxrwx    1 root     root            12 Jan 16 21:52 cat -> /bin/busybox
lrwxrwxrwx    1 root     root            12 Jan 16 21:52 chgrp -> /bin/busybox
lrwxrwxrwx    1 root     root            12 Jan 16 21:52 chmod -> /bin/busybox
lrwxrwxrwx    1 root     root            12 Jan 16 21:52 chown -> /bin/busybox

3.4.2 DockerFile配置

FROM alpine

RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories

RUN apk update 
RUN apk add iotop gcc libgcc libc-dev libcurl libc-utils pcre-dev zlib-dev libnfs make pcre pcre2 zip unzip net-tools pstree wget libevent libevent-dev iproute2

ADD nginx-1.24.0.tar.gz /opt/

WORKDIR /opt/nginx-1.24.0/

RUN ./configure --prefix=/apps/nginx && make && make install


RUN addgroup -g 2019 -S nginx && adduser -s /sbin/nologin -S -D -u 2019 -G nginx nginx 

RUN chown -R nginx.nginx  /apps/nginx/

EXPOSE 80 443

CMD ["/apps/nginx/sbin/nginx","-g","daemon off;"]

3.5 ubuntu dockerfile配置

按照编译nginx的方式显示dockerfile

[root@centos7 ubuntu]# cat dockerfile 
FROM ubuntu

RUN sed -i 's/archive.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list 
RUN sed -i 's/security.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list 

RUN apt update
RUN apt install -y libpcre3 libpcre3-dev zlib1g-dev   gcc openssh-server   openssl libssl-dev libpcre3 libpcre3-dev zlib1g-dev  unzip zip make 

ADD nginx-1.24.0.tar.gz /usr/local/src/

WORKDIR /usr/local/src/nginx-1.24.0/

RUN ./configure --prefix=/apps/nginx && make && make install 

RUN groupadd -g 2019 nginx && useradd -g nginx -s /usr/sbin/nologin -u 2019 nginx  

RUN chown -R nginx.nginx /apps/nginx

EXPOSE 80 443

CMD ["/apps/nginx/sbin/nginx","-g","daemon off;"]


参考链接

Docker ( 44) - 初识 Dockerfile - 小菠萝测试笔记 - 博客园 (cnblogs.com)

Docker ( 45 ) - docker build 命令详解 - 小菠萝测试笔记 - 博客园 (cnblogs.com)

Docker ( 46 ) - 详解 Dockerfile 指令 - 小菠萝测试笔记 - 博客园 (cnblogs.com)

Docker之六----Dockerfile详解 - 一叶知秋~~ - 博客园 (cnblogs.com)

Dockerfile( 6) - EXPOSE 指令详解 - 小菠萝测试笔记 - 博客园 (cnblogs.com)

Dockerfile ( 9 ) - ENTRYPOINT 指令详解 - 小菠萝测试笔记 - 博客园 (cnblogs.com)

Dockerfile 多阶段构建 · Docker -- 从入门到实践 (docker-practice.github.io)

Docker之三----java业务镜像制作 - 一叶知秋~~ - 博客园 (cnblogs.com)