Docker
前言
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:配置容器启动时运行的命令。