Cai鸟学Docker之——基础命令和Dockerfile

前言

1、什么是容器

1.1 什么是容器

容器是一种轻量级、可移植、自包含的软件打包技术,使应用程序可以在几乎任何地方以相同的方式运行。开发人员在自己笔记本上创建并测试好的容器,打包成镜像后,无需再做任何修改都能在生产系统的虚拟、物理服务器或公有云主机上运行。

1.2 容器不是虚拟化

在没有Docker的时代,商家通常使用硬件虚拟化(也称为虚拟机),以提供隔离。虚拟机提供虚拟的硬件,可以安装一个OS和APP。这会花费很长的时间,而且也需要很多的资源开销,因为他们除了要执行你需要的软件外,还得运行整个OS的副本。

不同于VM,Docker容器不使用硬件虚拟化。运行在Docker容器中的程序接口和宿主机的Linux内核直接打交道。因为容器中运行的程序和计算机的OS之间没有额外的中间层,没有资源被冗余软件的运行或虚拟硬件的模拟而浪费掉。这是一个很重要的区别。

1.3 Docker容器和虚拟机的区别

解释一:

  • VM(VMware)在宿主机器、宿主机器操作系统的基础上创建虚拟层、虚拟化的操作系统、虚拟化的仓库,然后再安装应用;
  • Container(Docker容器),在宿主机器、宿主机器操作系统上创建Docker引擎,在引擎的基础上再安装应用。

解释二:

使用虚拟机运行多个相互隔离的应用时,如下图:

  • 基础设施(Infrastructure)。它可以是你的个人电脑,数据中心的服务器,或者是云主机。
  • 主操作系统(Host Operating System)。你的个人电脑之上,运行的可能是MacOS,Windows或者某个Linux发行版。
  • 虚拟机管理系统(Hypervisor)。利用Hypervisor,可以在主操作系统之上运行多个不同的从操作系统。类型1的Hypervisor有支持MacOS的HyperKit,支持Windows的Hyper-V以及支持Linux的KVM。类型2的Hypervisor有VirtualBox和VMWare。
  • 从操作系统(Guest Operating System)。假设你需要运行3个相互隔离的应用,则需要使用Hypervisor启动3个从操作系统,也就是3个虚拟机。这些虚拟机都非常大,也许有700MB,这就意味着它们将占用2.1GB的磁盘空间。更糟糕的是,它们还会消耗很多CPU和内存。
  • 各种依赖。每一个从操作系统都需要安装许多依赖。如果你的的应用需要连接PostgreSQL的话,则需要安装libpq-dev;如果你使用Ruby的话,应该需要安装gems;如果使用其他编程语言,比如Python或者Node.js,都会需要安装对应的依赖库。
  • 应用。安装依赖之后,就可以在各个从操作系统分别运行应用了,这样各个应用就是相互隔离的。

使用Docker容器运行多个相互隔离的应用时,如下图:

  • 基础设施(Infrastructure)。
  • 主操作系统(Host Operating System)。所有主流的Linux发行版都可以运行Docker。对于MacOS和Windows,也有一些办法”运行”Docker。
  • Docker守护进程(Docker Daemon)。Docker守护进程取代了Hypervisor,它是运行在操作系统之上的后台进程,负责管理Docker容器。
  • 各种依赖。对于Docker,应用的所有依赖都打包在Docker镜像中,Docker容器是基于Docker镜像创建的。
  • 应用。应用的源代码与它的依赖都打包在Docker镜像中,不同的应用需要不同的Docker镜像。不同的应用运行在不同的Docker容器中,它们是相互隔离的。
对比虚拟机与Docker

Docker守护进程可以直接与主操作系统进行通信,为各个Docker容器分配资源;它还可以将容器与主操作系统隔离,并将各个容器互相隔离。虚拟机启动需要数分钟,而Docker容器可以在数毫秒内启动。由于没有臃肿的从操作系统,Docker可以节省大量的磁盘空间以及其他系统资源。

说了这么多Docker的优势,大家也没有必要完全否定虚拟机技术,因为两者有不同的使用场景。虚拟机更擅长于彻底隔离整个运行环境。例如,云服务提供商通常采用虚拟机技术隔离不同的用户。而Docker通常用于隔离不同的应用,例如前端,后端以及数据库。

2、为什么需要容器

一句话:容器使软件具备了超强的可移植能力

2.1 容器解决的问题

以前:所有的应用采用MVC三层架构,系统部署在几台有限的物理服务器上。

现在:一个应用大多是由多种服务构建和组装起来的,而且应用很可能部署到不同的环境,比如虚拟服务器、私有云和公有云。

一方面应用包含多种服务,这些服务有自己所依赖的库和软件包;另一方面存在多种部署环境,服务在运行时可能需要动态迁移到不同的环境中。这就产生了一个问题:如何让每种服务能够在所有的部署环境中顺利运行?

现在所面临的问题是:各种服务和环境通过排列组合会产生各种各样的方式,开发人员在编写代码时需要考虑不同的运行环境,运维人员则需要为不同的服务和平台配置环境。而运维在部署时通常拿到的是开发提交的代码或者war包,进行部署。但是同一套代码可能在开发哪里运行没问题,但是拿到运维,就可能运行不成功,造成这样的原因可能是环境配置的问题。这就更加重了运维和开发的任务。

在这里插入图片描述

怎么解决这个问题呢?

与之类似的问题在现实生活中也是存在的。在运输行业中,每次运输货物的时候,货主和承运方都会担心获取因为类型不同导致损失,比如几个铁通放在了一堆香蕉上,会将香蕉压坏。同时采用什么样的交通工具去运输不同类型的货物也是个问题。这也是一个排列组合的问题。

在这里插入图片描述

那么他们是怎么解决的呢?

他们采用的是集装箱的方式,将不同的货物都放在大的集装箱中,有效的解决了这个问题。

在这里插入图片描述

Docker也采用了这种集装箱的思维,将软件打包,为代码提供了一个基于容器的标准化运输系统。(集装箱运输货物,Docker运输软件)

Docker可以将任何应用及其以来打包成一个轻量级、可移植、自包含的容器。容器可以运行在几乎所有的操作系统上。这一点也体系在了Docker的log上。

2.2 容器的优势

对于开发人员来说:Build Once,Run AnyWhere

容器意味着环境隔离和可重复性。开发人员只需要为应用创建一次运行环境,然后打包成镜像就可以运行在其他机器上。

对于运维人员:Configure Once,Run Anything

只需要配置好标准的runtime环境,服务器就可以运行任何容器。这使得运维人员的工作变得更高效、一致和可重复。容器消除了开发、测试、生产环境的不一致性。

3、镜像

可以将Docker镜像看成是一个只读模板,通过它可以创建多个Docker容器。

例如某个镜像可能包含一个Linux操纵系统、Tomcat以及用户开发的Web应用。

镜像有多种生成方式:

  • pull别人创建好的镜像
  • 在现有镜像上创建新的镜像
  • 通过Dockerfile文件创建镜像

4、容器

Docker容器就是Docker镜像的运行实例

类比到面向对象:

Docker的镜像就类似面向对象的类

Docker的容器就类似面向对象的对象实体

容器在docker host中实际上是一个进程,docker stop命令本质上是向该进程发送一个SIGTERM信号。如果想要快速停止容器,可使用docker kill命令,其作用是向容器进程发送SIGKILL信号。

资源限制

内存限额

容器可使用的内存包括两部分:物理内存和swap。

Docker通过两组参数来控制容器内存的使用量

  • -m-memory:设置内存的使用限额,例如100MB,2GB
  • --memory-swap:设置内存+swap的使用限额

eg:

docker run -m 200M --memory-swap=300M ubuntu

  • 该命令的含义是:允许该容器最多使用200MB的内存和100MB的swap。
  • 默认情况下,上面两组参数为-1,表示对容器内存和swap的使用没有限制
  • 如果容器启动的时候只指定-m而不指定–memory-swap,那么–memory-swap默认为-m的两倍
    • eg:docker run -it -m 200M ubuntu
      • 表示:容器最多使用200MB的物理内存和200MB的swap
CPU限额

默认情况下,所有容器可以平等地使用host CPU资源并且没有限制

Docker可以通过-c--cpu-shares设置容器使用CPU的权重(优先级)。如果不指定,默认值为1024

eg:启动两个容器A和B,A的权重是B的权重的2倍,则当两个容器都需要CPU资源的使用A容器可以得到的CPU将是B容器的2倍。

启动命令:

  • docker run --name containerA -c 1024 ubuntu
  • docker run --name containerB -c 512 ubuntu

注意:这种按权重分配CPU只会发生在CPU资源紧张的时候,如果A处于空闲状态时,为了充分利用CPU资源,B容器可以分配到全部的CPU

5、Docker命令

在这里插入图片描述

Images相关命令

  • rm:移除一个或者多个容器
  • rmi: 移除一个或者多个镜像
    • docker rmi xxx:删除一个镜像
    • docker rmi -f xxx:强制删除正在运行中的镜像
    • docker rmi -f $(docker images -aq):批量删除多个镜像
  • tag:给源镜像创建一个新的标签,重新生成一个镜像
    • docker tag 原镜像名:标签 新镜像名:标签
  • prune:移除游离镜像。游离镜像:没有镜像名字的
    • docker image prune

Container相关命令

  • create:创建新容器,但并不启动(注意与docker run 的区分)需要手动启动。

    • 简单创建:docker create redis,指按照redis:latest镜像启动一个容器

    • 复杂创建:docker create --name myredis -p 6379(主机的端口):6379(容器的端口) redis,指按照redis:latest镜像启动一个容器,并将虚拟机的6379端接映射到容器的6379端口

      • 我们在Linux机器上安装Docker环境,docker每运行一个程序都是一个容器,这个容器就是一个小的完整的linux系统,所以容器中有部署的程序自己的端口,要是想从外面访问到这个程序,就需要先到机器的端口,然后再由机器的端口转到容器的对应端口
    • start\stop、kill

      • docker start myredis:启动容器(状态为Up)
      • docker stop myredis:停止容器(状态为Exited)
        • stop是优雅停机(当前正在运行中的程序处理完所有事情后再停止)
      • docker kill myredis:杀死容器(状态为Exited)
        • kill是强制停机(直接拔电源)

      拓展:容器在docker host中实际上是一个进程,docker stop命令本质上是向该进程发送一个SIGTERM信号。如果想要快速停止容器,可使用docker kill命令,其作用是向容器进程发送SIGKILL信号。

    • restart

      • 重启一个容器
      • 容器可能会因某种错误而停止运行。对于服务类容器,我们通常希望在这种情况下容器能够自动重启。
        • docker run -d --restart=always httpd
          • --restart=always:意味着无论容器因何种原因退出(包括正常退出),都立即重启
          • --restart=on-failure:3:如果启动进程推出代码非0,则重启容器,最多重启3次。
    • pause\unpause

      • docker pause myredis:暂停容器(状态为Pause)
        • 暂停后的容器不能接收请求
        • 暂停后的容器不会占用CPU资源
      • docker unpause myredis:恢复容器(状态为Up)
    • 容器的状态:Created(新建)、Up(运行中)、Pause(暂停)、Exited(退出)

  • run:创建容器,并默认立即启动,且默认是前台启动的

    • 默认前台启动:docker run --name myredis -p 6379:6379
    • 在后台悄悄启动:docker run -d --name myredis -p 6379:6379
  • logs:获取容器日志;容器以前在前台控制台能输出的所有内容,都可以看到

    • docker logs myredis
    • docker logs -f myredis:实时追踪日志
  • attach:绑定到运行中容器的标准输入, 输出,以及错误流

    • 比如docker run nginx,这是启动了一个容器,相当于在Docker容器里装了一个nginx,要对nginx的所有修改都需要进容器,即通过attach操作进入容器。
    • attach绑定的是控制台,所以可能会导致容器停止,。要小心使用,一般不用它进入容器,而用exec
  • exec:在运行时的容器内运行命令

    • docker exec -it mynginx /bin/bash:以交互模式(-i),并分配一个新的终端(-t)进入容器
    • docker exec -it -u 0:0 --privileged mynginx4 /bin/bash: 0用户(root用户),以特权方式进入容器
  • commit:把容器的改变提交创建一个新的镜像

    在这里插入图片描述

    • docker commit -a zhangaoqi -m "first commit" mynginx5 mynginx:v1:表示提交将容器mynginx5 提交为一个镜像,镜像名和标签为mynginx:v1,提交人为zhangaoqi,提交信息为 first commit。
    • **注意:**如果将上面的语句连续执行两次,也就是说,提交了两次同名同标签的镜像。那么,第一次提交的镜像的名称和标签都会变为<none>!这时他就是游离镜像,这时可以用docker image prune命令来移除这个游离镜像。
      在这里插入图片描述在这里插入图片描述

推送镜像到远程仓库

  • 首先在DockerHub里创建一个仓库

    • **注意:**DockerHub一个完整镜像的全路径是:docker.io/library/nginx:alp3.13(解释:docker.io/命名空间/仓库名:标签名)

      • 为什么是仓库名呢,不是镜像名呢?因为一般情况下仓库名就是镜像名,一个仓库下面放的是这个镜像的不同版本
    • 实际上docker images的时候镜像缩略了全名

      • 默认官方的镜像是没有显示docker.io/library
      • 而非官方的镜像只是没有显示docker.io/
      • 因此我们在推送镜像的时候也要加上这个命名空间!

      在这里插入图片描述

  • 登录远程仓库

    • docker login
  • 首先需要改镜像名(注意:一定要重新改一次镜像名,给他加上名称空间

    • 在这里插入图片描述
  • 上传镜像

    • docker push zhangaoqi/mynginx:v1
  • 如果DockerHub仓库上传的太慢了,可以用阿里云的镜像仓库,或者habor仓库

    • sudo docker tag [ImageId] registry.cn-hangzhou.aliyuncs.com/zaq/mynginx:镜像版本号
      sudo docker push registry.cn-hangzhou.aliyuncs.com/zaq/mynginx:镜像版本号
      阿里云的镜像名的统一命名格式:仓库网址/名称空间(zaq)/仓库名:版本号
      

其他命令

  • build:从一个 Dockerfile 文件构建镜像

  • commit:把容器的改变 提交创建一个新的镜像

  • cp:容器和本地文件系统间 复制 文件/文件夹

  • diff:检查容器里文件系统结构的更改【A:添加文件或目录 D:文件或者目录删除 C:文 件或者目录更改】

  • export:导出容器的文件系统为一个tar文件。commit是直接提交成镜像,export是导出成文 件方便传输

  • images:列出所有镜像

  • import 导入tar的内容创建一个镜像,再导入进来的镜像直接启动不了容器。 /docker-entrypoint.sh nginx -g ‘daemon o;’ docker ps --no-trunc 看下之前的完整启动命令再用他

  • inspect 获取docker对象的底层信息

  • kill 杀死一个或者多个容器

  • load 从 tar 文件加载镜像

  • login 登录Docker registry

  • logout 退出Docker registry

  • pause 暂停一个或者多个容器

  • ps 列出所有容器

  • pull 从registry下载一个image 或者repository

  • push 给registry推送一个image或者repository

  • save 把一个或者多个镜像保存为tar文件

  • start 启动一个或者多个容器

  • stop 停止一个或者多个容器

  • unpause pause的反操作

  • version Show the Docker version information

6、Dockerfile

一般而言,Dockerfile可以分为四部分

基础镜像信息、维护者信息、镜像操作指令、启动时执行指令

一个简单的Dockerfile文件

# 基础镜像
FROM alpine

# 给镜像加标签,维护者信息
LABEL maintainer="zaq" abc = def 

# 镜像操作指令
# 指的是镜像构建时运行的命令而非启动时运行的命令
RUN echo 111

# 镜像启动执行的命令,如果镜像启动要运行的命令较长,可以准备一个sh文件,让镜像启动运行sh文件
CMD sleep 10;echo success

构建命令:docker build -t myalpine:v111 -f Dockerfile . (这个 . 代表上下文环境;也就是代表前面指定的Dockerfile文件所在的当前目录)( . 指明build context为当前目录。Docker默认会从build context中查找Dockerfile文件,我们也可以通过-f参数指定Dockerfile的位置)。

指令 说明
FROM 指定基础镜像
MAINTAINER 指定维护者信息,已经过时,可以使用LABEL maintainer=xxx 来替代
RUN 运行命令(根据Dockerfile创建一个镜像的整个过程的时期运行的指令)注意:RUN指令并没有上下文的关系
CMD 指定启动容器时默认的命令 (根据之前创建的镜像启动一个容器,容器启动默认运行的命令)
ENTRYPOINT 指定镜像的默认入口.运行命令
EXPOSE 声明镜像内服务监听的端口
ENV 指定环境变量,可以在docker run的时候使用-e改变 v;会被固化到image的 config里面
ADD (从build context复制文件到镜像)复制指定的src路径下的内容到容器中的dest路径下,src可以为url会自动下载, 可以为tar文件,会自动解压
COPY (从build context复制文件到镜像)复制本地主机(宿主机)的src路径下的内容到镜像中的dest路径下,但不会自动解压等
LABEL 指定生成镜像的元数据标签信息
VOLUME 创建数据卷挂载点
USER 指定运行容器时的用户名或UID
WORKDIR 配置工作目录,为后续的RUN、CMD、ENTRYPOINT指令配置工作目录
ARG 指定镜像内使用的参数(如版本号信息等),可以在build的时候,使用–buildargs改变
OBBUILD 配置当创建的镜像作为其他镜像的基础镜像是,所指定的创建操作指令
STOPSIGNAL 容器退出的信号值
HEALTHCHECK 健康检查
SHELL 指定使用shell时的默认shell类型

6.1 RUN

两种写法:(shell格式和exec格式)

# 基础镜像
FROM alpine

# 给镜像加标签,维护者信息
LABEL maintainer="zaq" abc=def   ### 注意:key=value 中间不能加空格!!!

# 写法一:shell的形式(即:bash -c "echo 111")
RUN echo 111

# 写法二:exec形式
RUN ["echo", "222"]

# 镜像启动执行的命令,如果镜像启动要运行的命令较长,可以准备一个sh文件,让镜像启动运行sh文件
CMD sleep 10;echo success

注意:exec的形式取不到变量

# 基础镜像
FROM alpine

# 给镜像加标签,维护者信息
LABEL maintainer="zaq" abc = def 

# 指定环境变量
ENV parm=aaa

# shell的形式(即:bash -c "echo 111")
RUN echo $parm      # 输出aaa

# exec形式 
RUN ["echo", "$parm"]      # 输出$parm

# 镜像启动执行的命令,如果镜像启动要运行的命令较长,可以准备一个sh文件,让镜像启动运行sh文件
CMD sleep 10;echo success

总结: 由于[]不是shell形式,所以不能输出变量信息,而是输出$msg。其他任何/bin/sh -c 的形式都可以输出变量信息

6.2 ARG和ENV

  • ARG定义的变量,只能在它定义后的语句生效,可以通过 $xxx来取值
  • 注意:ARG定义的变量只在构建期有效,可以取到它的值;运行期无效,取不到它的值
  • ARG定义的变量可以在构建时进行改变(也就是执行docker build命令时改变)
    • 修改变量值:docker build --build-arg aaa="111" --build-arg bbb="222" -t demo:test -f Dockerfile .
  • ENV指定的变量,在构建期和运行期都可以生效
  • ENV指定的变量,不能在构建期进行修改,只能在运行期进行修改
    • 修改参数:docker run -it -e xxx=aaa demo:test
  • 使用ENV指令定义的环境变量始终会覆盖同名的ARG指令。
# ARG可以在任意位置定义,并在以后取值使用,
# 使用--build-arg version=3.13 改变;以我们传入的为准
ARG version=3.13.4
# 3.13  
FROM alpine:$version

LABEL maintainer="leifengyang" a=b \
c=dd

# 构建期+运行期都可以生效;但是只能在运行期进行修改
# 怎么修改:构建期修改和运行期修改
# 构建期不能改 ENV的值
# 运行期:docker run -e app=atguigu 就可以修改
ENV app=itdachang


## 测试构建期间生效
RUN echo $app
RUN echo $param

# 定义以后的剩下环节(不包括运行时)能生效:取值$param;
# 可以在构建时进行变化,docker build
# ARG不像ENV不能并排写
ARG param=123456  
ARG msg="hello docker"

#构建时期我们会运行的指令(根据Dockerfile创建一个镜像的整个过程时期)
RUN echo 11111
RUN echo $param
RUN echo $msg


#运行时期我们会运行的指令(根据之前创建的镜像启动一个容器,容器启动默认运行的命令)
#(docker run/docker start)
# CMD和ENTRYPOINT` 都是指定的运行时的指令

CMD ["/bin/sh","-c","echo 1111;echo $param;echo app_$app"]
关于ENV的坑
情景一:
FROM alpine

ARG msg=hello

# ENV肯定能引用ARG
ENV name=${msg} # 加不加{}都可以取到值
RUN echo ${name}
RUN echo ${msg}
CMD echo $name
  • 测试流程:构建上面的镜像文件,在构建期修改ARG的msg变量值为111,则后面输出的值都为111

    在这里插入图片描述

情景二:ENV的持久化问题
# env的坑
FROM alpine

# ENV只能运行期改掉
ENV msg1=hello
ENV msg2=$msg1
# 以上构建期间就已经确定好值了;ENV持久化问题。

RUN echo ${msg1}
RUN echo ${msg2}

# msg1=msg2没问题;如果我运行期修改了msg1=111的值,请求msg1;msg2输出什么
# 结果输出: 111   hello;  
# 原因:
# docker build的时候,env环境的信息会固化,直接在镜像配置里面就已经写死,msg1=hello,msg2=hello。
# -e 真的只能修改当前env本身
# 
# 
CMD ["/bin/sh","-c","echo ${msg1};echo ${msg2};"]

  • 测试流程:构建上面的镜像文件,在运行期如果修改ENV的msg1变量值为111,CMD输出的值则为 111 hello

    在这里插入图片描述

    这是为什么呢?因为docker build的时候,env环境的信息会被固化,直接在镜像配置里面就已经写死,msg1=hello,msg2=hello。而 -e 真的只能修改当前env本身,并不能改变引用的值

    这也就可以解释为什么运行期间能用ENV定义的所有值,这是因为一定是ENV存在某个地方,也就是整个配置文件中

    在这里插入图片描述

6.3 ADD和COPY

ADD和COPY唯一的区别就是:COPY不会自动解压和下载

FROM alpine
# ADD
# 把上下文Context指定的内容添加到镜像中
# 如果是远程文件,自动下载;
# 如果是压缩包,自动解压;

# 远程文件,自动下载
ADD https://download.redis.io/releases/redis-6.2.1.tar.gz  /dest/   ### 把当前内容复制到这个 alpine小系统的dest目录里面

RUN ls -l

# RUN指令上下并没有上下文关系;也就是说下面的cd指令虽然进入到了dest目录,但是执行下一个RUN指令列举目录文件的时候它并不知道,所以还是从根目录列举的
RUN cd /dest
# 当前还是列举的根目录
RUN ls -l

# 除非并列写,才能列举出dest目录下的文件
RUN cd /dest && ls -l
  • 上面的文件意外发现RUN指令是没有上下文关系的。
FROM alpine

# 将本地linux系统的内容文件添加进去  【宿主机   镜像内】
# docker build -t demo:test  -f Dockerfile .  ### .代表上下文环境;也就是代表前面指定的Dockerfile文件所在的当前目录
# 压缩包,自动解压
ADD *.tar.gz   /app/    ### 这句话的意思是,将当前上下文环境(构建时如果指定 . 也就是说Dockerfile文件的所在目录)中的所有.tar.gz的软件包复制到 /app目录下

RUN cd /app && ls -l

执行 docker build -t demo:test -f Dockerfile .命令后,首先Docker将build context中的所有文件发送给Docker daemon。build context为镜像构建提供所需要的文件或目录。

Dockerfile中的ADD、COPY等命令可以将build context中的文件添加到镜像中。

6.4 WORKDIR和VOLUME

WORKDIR
  • WORKDIR指令为Dockerfile中跟随它的所有 RUN,CMD,ENTRYPOINT,COPY,ADD 指令设置工作目录。 如果WORKDIR不存在,即使以后的Dockerfile指令中未使用它也将被创建。

    FROM alpine
    RUN pwd && ls -l
    
    # 为以下所有的命令运行指定了基础目录
    WORKDIR /app
    RUN pwd && ls -l 
    
    # 复制到当前目录下
    COPY *.txt ./
    RUN pwd && ls -l 
    

    在这里插入图片描述

  • WORKDIR指令可在Dockerfile中多次使用。 如果提供了相对路径,则它将相对于上一个WORKDIR指 令的路径。 例如:

    WORKDIR /a
    WORKDIR b
    WORKDIR c
    RUN pwd
    #结果 /a/b/c
    
  • WORKDIR可以为进入容器指定一个默认目录

    即:当在Dockerfile文件中指定WORKDIR的路径后,那么由该镜像文件创建的容器,在进入容器时会默认进入到这个WORKDIR指定的目录下。例如:

    • 根据下面的dockerfile文件先构建镜像

      FROM nginx
      WORKDIR /usr/share/ nginx/html
      
    • 在这里插入图片描述

VOLUME的坑

VOLUME的写法

VOLUME ["/var/log/"] #可以是JSON数组
VOLUME /var/log #可以直接写
VOLUME /var/log /var/db #可以空格分割多个
  • VOLUME和-v挂载出去的目录,一定是外面变,容器里面变,所有改变都生效了。但是当进行docker commit提交当前容器的所有变化为镜像的时候,这些变化都会被丢弃。

  • 挂载只有一点就是方便在外面修改,或者把外面的东西直接拿过来。在进行commit提交容器的时候,这些修改都会被丢弃

FROM alpine

RUN mkdir /hello && mkdir /app
RUN echo aaa > /hello/aaa.txt
RUN echo bbb > /app/bbb.txt
#挂载 容器的指定文件夹,如果不存在就创建。
#指定了 VOLUME ,即使启动容器没有指定 -v 参数,我们也会自动进行匿名卷挂载
VOLUME [ "/hello","/app" ]   # VOLUME 指定的挂载目录

# 这两句话没有生效
# VOLUME [ "/hello","/app" ] 容器以后自动挂载,在Dockerfile中对VOLUME的所有修改都不生效
# 所以VOLUME一般写在最后
RUN echo 6666 >> /hello/a.txt
RUN echo 8888 >> /app/b.txt

CMD ping baidu.com

6.5 EXPOSE

  • EXPOSE指令通知Docker容器在运行时在指定的网络端口上进行侦听。 可以指定端口是侦听TCP还 是UDP,如果未指定协议,则默认值为TCP。
  • EXPOSE指令实际上不会发布端口。 它充当构建映像的人员和运行容器的人员之间的一种文档,即:有关打算发布哪些端口的信息。 要在运行容器时实际发布端口,请在docker run上使用-p标志发布,并映射一个或多个端口,或使用-P标志发布所有公开的端口并将其映射到高阶端口。

6.6 CMD和ENTRYPOINT

  • Dockerfile文件中CMD的命令,在启动容器的时候是可以被修改的(docker run-it test:v1 ping taobao.com,这个ping taobao.com会覆写Dockerfile文件中的CMD命令)

    • eg:

      FROM alpine
      CMD ping baidu.com
      
      • 正常build后docker run-it test:v1会ping 百度

      • 如果docker run -it test:v1 ping taobao.com,它就会去ping淘宝

  • ENTRYPOINT是不能被修改的

    • eg:

      FROM alpine
      ENTRYPOINT ping baidu.com
      
      • 就算docker run -it test:v1 ping taobao.com启动容器,它也ping的是百度
      • 所以一般用ENTRYPOINT来写启动命令
  • ENTRYPOINT或者CMD只能写一个,且只有最后一个生效

  • CMD是给ENTRYPOINT提供参数的,CMD和ENTRYPOINT的搭配使用过程

    FROM alpine
    # 官方推荐的CMD和ENTRYPOINT的搭配使用写法,ENTRYPOINT和CMD都要用exec的方式
    
    # 变化的写CMD(也就是说参数写成CMD,方便在启动的时候修改参数值)
    CMD ["baidu.com"]
    
    # 不变的写ENTRYPOINT,它就是容器启动的唯一入口
    ENTRYPOINT ["ping"]
    
  • 最后在举个例子:希望在容器启动的时候ping 百度官网5次,正常的写法是:ping -c 5 baidu.com

    FROM alpine
    CMD ["5", "baidu.com"]  # 注意,如果要修改某个参数,则必须将所有的参数都传入,且参数要和CMD中写的对应上,否则只传一个的话它无法识别要改的是哪个变量的值。
    ENTRYPOINT ["ping", "-c"]
    

    注意:

    ENTRYPOINT的Exec格式用于设置要执行的命令及其参数,同时可通过CMD提供额外的参数。ENTRYPOINT中的参数始终会被使用,而CMD的额外参数可以在容器启动时动态替换掉。

    ENTRYPOINT的Shell格式会忽略任何CMD或docker run提供的参数

    总结:RUN、CMD和ENTRYPOINT

    RUN:执行命令并创建新的镜像层,RUN经常用于安装软件包。

    CMD:设置容器启动后默认执行的命令及其参数,但CMD能够被docker run后面跟的命令行参数替换。

    ENTRYPOINT:配置容器启动时运行的命令。

猜你喜欢

转载自blog.csdn.net/ysf15609260848/article/details/121594400