docker系列-03使用Dockerfile

seefly 2020年07月30日 78次浏览

使用Dockerfile 创建镜像

官方参考:https://docs.docker.com/engine/reference/builder/

镜像的定制实际上就是定制每一层所添加的配置文件。如果我们可以把每一层修改、 安装、 构建、 操作的命令都写入一个脚本, 用这个脚本来构建定制镜像,就能够解决docker commit产生的镜像黑盒、无法重复构建、镜像臃肿等问题。这就是Dockerfile的作用。

1、FROM 指定基础镜像

定制镜像,就是以一个镜像为基础,在其上进行定制,像是一个nginx镜像容器,我们修改它的相关配置文件,再打包成一个新的镜像一样。指定基础镜像是必须的。所以一个Dockerfile脚本中,FROM是必须的指令,且必须在第一条。

# 例如我们的jar包,需要运行在jvm环境中
FROM openjdk:8-jdk-alpine

2、RUN执行指令

RUN是用来执行命令行命令的,有两种格式

  • shell格式: RUN<命令> 和在shell中执行命令一样
RUN echo 'hello Dockerfile'
  • exec格式:RUN["可执行文件","参数1","参数2"]

注意:Dockerfile中的每一个指令都会建立一层镜像,RUN也不例外,每一个RUN的行为的执行过程都是,新建立一层镜像,执行命令,再commit这层的修改构成新的镜像,所以在Dockerfile脚本中不要写过多的RUN命令,合并相同功能的RUN。例如:

FROM debian:jessie
RUN buildDeps='gcc libc6-dev mak' \
&& apt-get update \
&& apt-get install -y $buildDeps \
...等

3、构建镜像

docker build -t <仓库>[:<标签>] <上下文路径>

# Dockerfile脚本
FROM nginx
RUN echo '<h1>Hello Docker!</h1>' > /usr/share/nginx/html/index.html

# 构建镜像
docker build  -t mynginx .

# 构建日志
# 把上下文发送到docker服务器
Sending build context to Docker daemon 2.048 kB
# 下载基础镜像
Step 1/2 : FROM nginx
Trying to pull repository docker.io/library/nginx ... 
latest: Pulling from docker.io/library/nginx
8ec398bc0356: Pull complete 
465560073b6f: Pull complete 
f473f9fd0a8c: Pull complete 
Digest: sha256:b2d89d0a210398b4d1120b3e3a7672c16a4ba09c2c4a0395f18b9f7999b768f2
Status: Downloaded newer image for docker.io/nginx:latest
 ---> f7bb5701a33c
 # 在临时容器内执行RUN命令
Step 2/2 : RUN echo '<h1>Hello Docker!</h1>' > /usr/share/nginx/html/index.html
 ---> Running in 768f5229529a
# 返回在这个容器内执行后打包的新镜像
 ---> eca190ef6c16
 # 删除临时容器
Removing intermediate container 768f5229529a
Successfully built eca190ef6c16

4、镜像构建中的上下文

可以看到命令 docker build -t mynginx . 中有一个 . ,这个不代表Dockerfile所在的路径,而是构建镜像时所需要的上下文所在的路径,docker只是默认在上下文路径中的Dockerfile文件作为构建脚本,准确一点可以通过 -f参数指定Dockerfile文件,啥是上下文呢?
首先需要理解docker build的工作原理,Docker在运行时分为Docker引擎和客户端工具,我们在执行docker build命令时看起来是在本地执行的,其实是通过接口调用的远程服务器
所以我们在构建的时候需要把构建镜像所需要的所有资料打包发到服务器,这些资料就是上下文。然后服务端才能成功执行例如COPY等命令。
所以,在Dockerfile中的COPY命令都是使用相对路径,不能使用绝对路径。且都是相对于上下文。
所以指定的上下文,必须只能包含构建镜像所需要的资料,不要包含其他的东西

5、Dockerfile中的命令

  1. FROM
    该命令上面已经解释过了

  2. RUN

    • shell格式: RUN<命令> 和在shell中执行命令一样
    • exec格式:RUN ["可执行文件","参数1","参数2"]
  3. COPY
     COPY 指令将从构建上下文目录中 <源路径> 的文件/目录复制到新的一 层的镜像内的 <目标路径> 位置

      COPY <上下文路径> <容器内路径>
      COPY demo.jar /app.jar
      COPY *.html /html/
      COPY Hello?.html /html/
      # 上下文路径必须是相对路径,容器内的目标路径都可以,根据WORKID来决定
    
  4. ADD
    这是比COPY命令高级一点的,它支持从网络上下载文件,放到目标路径中去。如果文件是一个tar压缩文件的话,且压缩格式为 gzip\bzip2\xz的时候会自动解压。
    但是这个命令会使当前镜像缓存失效 ,且不是必须的情况下docker不推荐使用;原则是,所有文件的复制都使用COPY,尽在需要解压的场景使用ADD

  5. CMD
    用来指定容器启动时,容器内主进程的默认启动命令,这个命令如果我们在启动容器的时候没有在镜像后追加新命令的话就使用它,但是如果追加了新的命令,就会被替换

    • 指令格式

      • shell格式: CMD <命令>
            CMD java -jar app.jar
            # 实际转换为
            CMD ["sh","c","java -jar app.jar"]
            #由于被shell预先处理,所以可以使用系统中的环境变量
            CMD ["sh","c","echo $JAVA_HOME"]
        
      • exec格式: CMD ["可指定文件","参数1","参数2"]
            CMD ["java","-jar","app.jar"]
        
    • 替换默认Dockerfile中的CMD例子

          # Dockerfiel脚本
          FROM centos
          CMD ["ping","www.baidu.com"]
      
         #构建
         docker build -t cmd .
         #创建容器,并运行容器,执行默认CMD
         docker run cmd
         #显示的是执行CMD指定的ping命令
         PING www.a.shifen.com (14.215.177.39) 56(84) bytes of data.
      64 bytes from 14.215.177.39 (14.215.177.39): icmp_seq=1 ttl=51 time=7.20 ms
      64 bytes from 14.215.177.39 (14.215.177.39): icmp_seq=2 ttl=51 time=7.28 ms
      
      #如果这样创建容器,那默认的CMD就被替换了
      docker run cmd echo 'hello'
      hello
      
      
    • 推荐使用 exec格式

6. ENTRYPOINT
入口点指定和CMD指令的格式和功能一样,都是指定容器启动时的指令和参数,入口点指令也可以和CMD一样被替换,但是需要通过 --entrypoint指定。但是如果同时有 ENTRYPOINT 和 CMD,那么CMD中的内容会作为参数传递给ENTRYPOINT,变成 "CMD"

这里也能用环境变量

  • 使用方式一、给入口点传递参数
    
    
        FROM ubunt:16.04
        COPY myshell.sh /myshell.sh
        # shell脚本里可以根据传递的参数干不同的事情
        ENTRYPOINT ["/myshell.sh"]
    
        =================================
    
        # 构建...
        # run容器,这个param4shell就能在脚本里拿到了
        docker run imageName param4shell
    
        #也可以直接在ENTRYPOINT指令前后写CMD默认参数
    
  1. ENV
    就是设置环境变量,可以在设置后在之后的命令中使用,也可以在容器中获取到。

    
        FROM centos
        # 设置环境变量
        ENV RUOK YES
        # 下面的命令可以直接使用
        CMD echo $RUOK
    
        ======================
        # 创建镜像
        docker build -t env .
        # 启动
        docker run env
        -> YES
    
        #启动+进入容器,并在容器中查看该环境变量
        docker run -it env bash
        echo $AUOK
        ->YES
    
        # 使用-e替换ENV设置的环境变量
        docker run -e AUOK="NO" env
        ->NO
    
  2. ARG
    构建参数ARG和ENV的效果一样,都是设置环境变量,但是ARG设置的环境变量不能在容器运行时获取到。所以它只适用于镜像级别,在构建镜像时可以通过 --build-arg="abc"来替换。

        FROM openjdk:8-jdk-alpine
        #这里先声明
        ARG JAR_FILE
        #使用方式也和ENV不一样了
        COPY  ${JAR_FILE} app.jar
        ....
    
        ======================
        #构建镜像时,指定构建参数
        docker build --build-arg JAR_FILE="hello.jar" -t hello .
    

    注意,通过--build-arg指定的构建参数最好在Dockerfile里面声明,不然有警告,但也没啥吧

  3.  VOLUME 定义匿名卷
    在镜像中定义匿名卷的作用是,防止用户在启动容器时忘记将某些文件挂载为卷从而导致容器删除时某些需要保存的数据也丢了,也就是起了一个兜底的作用。由于制作的镜像可能会在任何系统中运行,所以只能定义匿名卷,不能实际指定存储位置
    linux下的挂载卷存储位置为: /var/lib/docker/volumes/

        FROM XXX
        # 此时在容器中任何向/data目录下写入的数据都
        # 可以在 /var/lib/docker/volumes/<UUID>/_data 下找到
        VOLUME /data
    
       =========================
       # 在Dockerfile中定义的匿名卷跟下面效果相同
       docker run -v /data imageName
       # 也可以覆盖定义的匿名卷
       docker run -v /otherDir:/data
       docker run -v otherNamedVolume:/data
    
    
  4. EXPOSE声明端口
    起到一个声明的作用,声明这个镜像创建的容器打算使用什么端口,方便镜像使用者理解,另一个用处是在 docker run -P时宿主机随机分配一个端口映射到声明的端口中。

  5. WORKDIR 指定工作目录
    由于Dockerfile中的每一个指令都创建一个镜像并提交,所以试图通过RUN cd /app 来进入指定目录并在后续进行操作的命令都是无效的。而WORKDIR <容器中工作目录路径> 就是解决这种问题的。

        FROM XXX
        WORKDIR /app
        # 此时hi.log会创建在/app/hi.log中
        RUN touch hi.log
    
  6. USER 指定当前用户

        FROM XXX
        RUN 创建用户A
        USER 用户A
        # 后续的操作以用户A来进行
        RUN touch z.txt