Docker运行原理
Docker是一个Client-Server结构的系统,Docker守护进程运行在主机上,然后通过Socker连接从客户端访问,守护进程从客户端接受命令并管理运行在主机上的容器,容器是一个运行时环境。
与虚拟机的区别
虚拟机(Virtual Machine)就是带环境安装的一种解决方案;虚拟机可以在一种操作系统里面运行另一种操作系统,比如在Windows系统运行Liunx系统,应用程对此毫无感知,因虚拟机看上去跟真实操作系统一模一样,而对于底层系统来说,虚拟机就是一个普通文件,不需要了就删除,对其他部分毫无影响,这类虚拟机完美的运行了另一套系统,能够使应用程序,操作系统和硬件三者之间的逻辑不变;
虚拟机的缺点就是资源占用多、冗余步骤多、启动慢,因此Liuynx发展出另一种虚拟化技术,就是Liunx容器(Liunx Containers,缩写是LXC)。
Liunx容器不是模拟一个完整的操作系统,而是对进程进行隔离,有了容器就可以将软件运行所需的所有资源打包到一个隔离的容器中;容器与虚拟机不同,不需要捆绑一整套操作系统,只需要软件工作所需的库资源和设置,系统因此变得高效、轻量并保证部署在任何环节中的软件都能始终如一的运行。
传统虚拟化技术是虚拟出一套硬件后,在其上运行一个完整的操作系统,在该系统上运行所需要的应用进程;
Docker容器内的应用程序直接运行于宿主机的内核,容器没有自己的内核,且也没有进行硬件虚拟,因此容器要比传统虚拟机更为轻便;
Docker每个容器之间互相隔离,每个容器有自己的文件系统,容器之间不会相互影响,能区分计算资源;
Docker为什么比虚拟机快?
Docker不需要Hypervisor实现硬件资源虚拟机化,运行在Docker容器上的程序直接使用的都是实际物理机的硬件资源,从而Docker有着比虚拟机更少的抽象层,因此在CPU、内存利用率上Docker将会在效率上有明显优势。
Docker利用的是宿主机的内存,不需要GuestOS,因此当新建一个容器时,Docker不需要和虚拟机一样重新加载一个操作系统内核,从而避免引寻、加载操作内核这些比较耗时、耗资源的过程,当新建一个虚拟机时,虚拟机软件需要加载GuestOS,新建过程是分钟级别的,而Docker是直接利用宿主机的操作系统,省略了整个过程,因此新建一个Docker容器只需要几秒钟。
Docker | 虚拟机 | |
系统 | 共享宿主机 | 宿主机上运行虚拟机OS |
存储 | 镜像小,便于存储和传输 | 镜像庞大(vmdk、vid…) |
性能 | 几乎无额外性能损失 | 额外的CPU、内存消耗 |
移植 | 轻便、灵活、适应于Liunx | 笨重,与虚拟化技术耦合度高 |
对象 | 软件开发者 | 硬件运维者 |
注:在docker中拉取下来一个centos镜像时,会发现很多命令丢失,因只留下内核相关的内容,所以也只有几百MB。
Docker常用命令
帮助命令
#docker版本
docker version
#docker信息描述
docker info
#命令帮助
docker --help
镜像命令
#列出本地的镜像列表(repository镜像仓库源或名称、TAG镜像标签、image id镜像id、created 镜像创建时间、size 镜像大小)
#同一个仓库源有多个TAG,代表这个仓库源的不同版本,使用REPOSITORY:TAG来定义不同镜像,如果不指定一个镜像的版本标签,例如使用ubuntu,docker将默认使用ubintu:latest镜像,默认版本是最新。
docker images
#列出本地所有的镜像,包含中间映像层
docker images -a
#只显示镜像ID
docker images -q
#显示镜像的摘要信息
docker image --digests
#显示完整的镜像信息
docker images –no-trunc
#显示所有镜像的ID
docker images -qa
#搜索镜像,docker search tomcat
#公开镜像仓库地址:https://hub.docker.com/
docker search 镜像名
#显示完整的镜像描述
docker search --no-trunc 镜像名
#列出收藏数不小于指定数的镜像(start列是收藏数)
docker search -s 数值 镜像名
#只列出automated build类型的镜像,即自动化操作的镜像
docker search --automated 镜像名
如:docker search -s 20 --automated --no-trunc centos
#从仓库拉取某个镜像,默认是最新版本,docker pull 镜像名 = docker pull 镜像名:latest
docker pull 镜像名
#从仓库拉取某个镜像的指定版本,docker pull tomcat:8.5
docker pull 镜像名:版本号
#删除镜像,没有写TAG,默认是latest,即最新版本
docker rmi 镜像名
docker rim 镜像名:TAG
#强制删除,如镜像内还有一层或有容器在运行就无法删除,那么则要强制删除
docker rmi -f 镜像名
#删除多个
docker rmi -f 镜像名1 镜像名2
#先用docker images -q查询出本地仓库的容器,删除查询出的结果
docker rmi -f $(docker images -q)
容器命令
#新建并启动容器
docker run [OPTIONS] image [COMMAND] [ARG…]
OPTIONS参数:
--name 容器名称:为容器指定一个名称,没有指定就默认
-d:守护式进程启动运行容器,并返回容器ID
-i:以交互式模式运行容器,通常与-t同时使用
-t:为容器重新分配一个伪输入终端,通常与-i同时使用
-P:随机端口映射
-p:指定端口映射;有四种格式,ip:hostPort:containerPort、ip::containerPort、hostPort:containerPort、containerPort
#以交互式模式启动一个容器
docker run -it 镜像Id
docker run -it centos
docker run -it --name 容器名 镜像名
docker run -it --name centos2 centos
#使用镜像centos:latest以交互式模式启动一个容器,在容器内支撑/bin/bvash命令
docker run -it centos /bin/bash
#以守护式进程的方式启动,即不进入交互模式,使用docker ps是没有容器,docker ps -a一看,会发现容器的状态是已经退出了;这涉及到Docker的一个机制,Dcoker容器后台运行就必须有一个前台进程,容器运行的命令如果不是那些一直挂起的命令(如top、tail)就会自动退出;如nginx容器,正常情况下,配置启动服务只需要service nginx start,但这样做,nginx为后台进程模式运行,就导致docker前台没有运行的应用,这样的容器后台启动后会自动停止,因此容器觉得没有事情做了,所以最佳解决方案就是将要运行的程序以前台进程形式进行;
docker run -d centos
#列出容器
docker ps [OPTIONS]
OPTIONS:
-a:列出当前所有正在运行的容器+历史运行过的
-l:显示最近创建的容器
-n:显示最近n个创建的容器
-q:静默模式,只显示容器编号
--no-trunc:不截断输出
#查看所有容器
docker ps
#显示2个容器
docker ps -n 2
#容器停止并退出
exit
#容器不停止退出
Crtl+P+Q
#启动容器
docker start 容器id
#重启容器
docker restart 容器id
#停止容器
docker stop 容器id或容器名
#强制关闭容器
docker kill 容器id或容器名
#删除容器
docker rm 容器id
#强制删除容器
docker rm -rf 容器id
#根据查询结果进行删除
docker rm -rf $(docker ps -qa)
#删除多个容器
docker ps -a -q | xargs docker rm
#查看容器日志
docker logs -f -t --tail 数值 容器id
-t:加入时间戳
-f:跟随最新的日志打印
--tail:数字,显示最后多少条
##测试
docker run -d centos /bin/sh -c “while true;do echo hello;sleep2;done”
docker logs -f -t --tail=all 容器id
docker logs -f -t --tail 10 容器id
docker logs -f -t --tail all 容器id
#查看容器内的进程
docker top 容器id
#查看容器详细信息
docker inspect 容器id
#重新进入容器
docker exec -it 容器id /bin/bash
#直接运行命令
docker exec -it 容器id ls -a
#重新进入docker容器
#atttach是直接进入容器启动命令的终端,不会启动新的进程,exec是在容器内打开新的终端,并且可以启动新的进程;
docker attach 容器id
#复制容器内文件到宿主机上
docker cp 容器id:容器内路径 宿主机路径
docker cp 容器id:/tmp/test.txt ~
Docker镜像
镜像是一种轻量级、可执行的独立软件包,用来打包软件运行环境和基于运行环境开发的软件,包含运行某个软件所需要的所有内容,包括代码、运行时库、环境变量和配置文件等…
镜像实际是UnionFs(联合文件系统)的缩写,是一种分层、轻量级、高性能的文件系统,支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂在同一个虚拟文件系统下(unite serveral directiories into a single virtual filesystem),union文件系统是Docker镜像的基础,镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。
特性
一次同时加载多个文件系统,但从外面看起来只能看到一个文件系统,联合加载会把各层文件系统叠加起来,这样最终的文件系统会包含所有底层文件和目录。
Docker镜像都是只读的,当容器启动时,一个新的可写层被加载到镜像的顶部,这一层通常被称作容器层,容器层之下都叫镜像层。
加载原理
Docker的镜像实际是由一层层的文件系统组成,这种层级的文件系统UnionFS。
bootfs(boot file system)主要包含bootloader和kernel,bootloader主要是引导加载kernel,Liunx刚启动时会加载bootfs文件系统,在Docker镜像的最底层就是bootfs,这一层与Liunx/Unix系统是一样的,包含boot加载器和内核,当boot加载完成之后整个内核就都在内存中了,此时内存使用权已由bootfs转交给内核,此时系统也会写在bootfs。
rootfs(root file system)在bootfs之上,包含的就是Liunx系统中的/dev、/proc、/bvin、/etc等标准目录和文件,rootfs就是各种不同的操作系统发行版,比如Ubuntu、CentOS等;这也是为什么平时的centos有几个G,docker的centos镜像只有几百MB,对于一个精简的OS,rootfs可以很小,只需要包括最基本的命令、工具和程序库就可以了,因为底层直接用Host的Kernel,自己只需要提供rootfs就可以了,由此可见对于不同的Liunx发行版,bootfs基本是一直的,rootfs会有差别,因此不同的发行版可以用共用的bootfa;
镜像分层
如:tomcat镜像包含:tomcat -> jdk -> centos
有很多镜像都是从相同的base镜像构建而来的,那么宿主机只需要在磁盘上保存一份base镜像,内存中也只需要加载一份base镜像,就可以为所有容器服务了,而且镜像的每一层都可以被贡献,进而达到资源共享的目的。
docker commit
docker commit 提交容器副本,使之成为一个新的镜像,如修改tomcat的配置等。
#语法
docker commit -a=”作者” -m=”描述信息” 容器id 要创建的目标镜像名:标签名
案例:
# 运行tomcat
Docker run -it -p 宿主机端口:容器端口 tomcat
Docker run -it -p 9998:8080 tomcat
#随机分配宿主机端口
Docker run -it -P tomcat
#浏览器访问
localhost:9998
#可以看到第一个是宿主机端口,第二个是容器内的端口
docker ps
#删除容器中的tomcat的webapp的docs文件夹,此时在浏览器中点击documention是看不到了
docker exec -it 容器id /bin/bash
cd webapps
rm -rf docs
#当前这个tomcat的实例是一个没有文档内容的容器依次为模板,commit一个没有doc的tomcat容器为新镜像
docker ps
docker commit -a “test” -m “test” 容器id 新镜像名:TAG
docker images
docker run -d –name mytomcat -p 9990:8080 新镜像名:TAG
#浏览器访问,查看doc还在不在
Localhostr:9990
Docker容器数据卷
将运行与运行的环境打包形成容器运行,运行可以伴随着容器,但对数据的要求是希望持久化的,并且容器之间可以共享数据。
Docker容器产生的数据,如果不通过docker commit 生成新的镜像,使数据作为镜像的一部分保存下来,那么当容器删除后,数据自然也就没了,为了能够保存数据在docker中,就要使用卷了。
卷是目录或文件,存在于一个或多个容器中,由Docker挂载到容器中,但不属于联合文件系统,因此能够绕过Union File System提供一些用于持续存储或共享数据的特性。
卷的设计目的就是数据的持久化,完全独立于容器的生存周期,因此Docker不会再容器删除时删除其挂载的数据卷。
特点
可在容器之间、主机与容器之间共享或重用数据
卷中的更改可以直接生效
数据卷中的更改不会包含在镜像的更新中
生命周期一直持续到没有容器使用为止
操作
案例一:使用-v添加卷
#创建容器时,创建卷,-v带有自建目录(文件夹)的功能
Docker run -it -v /宿主机目录:/容器内目录 镜像名
#此命令是带有权限的,ro即容器内对这个卷目录是只读不可写
docker run -it -v /宿主机目录:/容器内目录:ro 镜像名
#此命令是带有权限的,ro即容器内对这个卷目录是可读可写
docker run -it -v /宿主机目录:/容器内目录:rw 镜像名
docker run -it --name mycentos -v /htest:/test centos
#可以看到挂载的卷信息
docker inspect
cd test
touch test.txt
vi test.txt
退出容器
cd htest/
cat test.txt
rm -rf test.txt
docker exec -it 容器id /bin/bash
ll test
rm -rf iii.txt
ll test
退出容器
#在容器中删除了,宿主机也会删除,同理。
案例二:使用DockerFile添加卷
mkdir /mydocker
cd /myudocker
vi dockerfile
-----------------------------
FROM centos
VOLUME ["/test1","/test2"]
CMD echo "finished ----------"
CMD echo /bin/bash
-----------------------------
#构建dockerfile文件,构成新的镜像
docker build -f dockerfile -t mycentos:latest .
docker images
#运行镜像
docker run -it mycentos /bin/bash
#查看卷的位置
docker inspect 容器id
docker exec -it 容器id /bin/bash
注:在Docker数据卷挂在宿主机目录时,访问出现没有权限的错误是,可以在挂在的目录中多加一个—privileged true的参数即可;如:docker run -it -v /test:/test --privileged=true centos
案例三:父数据卷
容器挂载数据卷,其他容器通过挂载这个(父容器)实现数据贡献,挂载数据卷的容器称之为数据卷容器。
#语法
docker run -it --name 容器名称 --volumes -from 容器名称 镜像名称:TAG /bin/bash
docker run -it –name dc0 centos -v /test:/test /bin/bash
docker ps
docker run -it –name dc1 dc0:latest /bin/bash
docker ps
docker run -it –name dc2 --volumes-from dc1 centos:latest /bin/bash
ll /test
touch /test/dc2.txt
#退出容器
#进入容器1进行查看
#删除容器1
docker run -it --name dc3 --volumes-from dc2 centos:latest /bin/bash
ll /test
touch /test/dc3.txt
#进入容器2查看,删除容器2
#结论:容器之间配置信息的传递,数据卷的生命周期一直持续到没有容器使用为止。
DockerFile
用来构建Docker镜像的构建,是由一系列的命令和参数构成的脚本
构建步骤
编写dockerfile文件 à docker build à docker run
Base镜像
scratch是一个源镜像,即基础镜像,类似java的Object,Docker Hub中99%的镜像都是通过在base镜像中安装和配置西药的软件构建出来的。
基础内容
每条保留字指令必须大写字母,且后面跟随至少一个参数;
指令按照从上到下,顺序执行;
#表示注释;
每条指令都会创建一个新的镜像层,并对镜像进行提交;
执行流程
从基础镜像运行一个容器,即祖先镜像scratch;
执行一条指令并对容器做出修改
执行类似docker commit操作提交一个的镜像层;
docker在基于提交的镜像运行一个新的容器;
执行dockerfile中的下一条指令直到指令都执行完成;
小结
DockerFile、Docker镜像与Docker容器分别代表软件的三个不同阶段。
DockerFile是软软件的基础组成,面向开发,相当于云平台的laas层(基础服务层)
Docker镜像是软件成品、交付标准,相当于云平台的PasS层(平台服务层)
Docker容器是软件的运行形态、部署与运维,相当于云平台的SaaS层(软件服务)
常用保留字指令
FROM:基础镜像,当这个镜像是来自哪个镜像的;
MAINTAINER:镜像维护者的姓名和邮箱地址;
RUN:容器构建时运行的命令
EXPOSE:当前容器对外暴露出的端口,即位于宿主机的端口号;
WORKDIR:指定在创建容器后,终端默认登录进来的工作目录,即落脚点
ENV:用来构建镜像过程中设置环境变量;这个环境变量可以在后续的任何RUN指令中使用,这也就如同在命令前面指定了环境变量前缀一样,也可以在其他指令中直接使用这些环境变量;
ADD:将宿主机目录下的文件拷贝进镜像,且会自动处理URL和解压tar压缩包;
COPY:类似ADD,拷贝文件和目录到镜像中,将从构建上下文件目录中源路径的文件/目录复制到新的一层的镜像内(目标路径)的位置,如COPY src desc、COPY [“src”,”desc”];
VOLUME:容器数据卷,用于数据保存和持久化工作,但不能指定对应的宿主机地址,采用的宿主机默认地址;
CMD:指定一个容器启动时要运行的命令,DockerFile中可以有多个CMD指令,但只有最后一个生效,CMD会被docker run之后的参数替换。
ENTRYPOINT:指定一个容器启动时要运行的命令,ENTRYPOINT的目的和CMD一样,都是在指定容器启动程序及参数,在docker run之后的参数不会替换和覆盖,且会追加并会提交、执行;
ONBUILD:当构建一个被继承的Dcokerfile时运行命令,父镜像在被子继承后,父镜像的onbuild被触发;
操作
案例一:自定义镜像centos
mkdir /mydocker
vi mycentos.dockerfile
-------------------------------------
#指定镜像
FROM centos
#作者
MAINTAINER levilevi@test.com
#环境变量
ENV MYPATH /tmp
#落脚点
WORKDIR $MYPATH
#运行命令(容器构建时运行)
RUN yum -y install vim
RUN yum -y install net-tools
#容器对外暴露端口
EXPOSE 80
#命令(容器启动时运行)
CMD echo $MYPATH
CMD echo “success”
CMD /bin/bash
-------------------------------------
docker build -f /mydocker/mycentos.dockerfile -t mycentosdoc:1.0
docker images
docker history 镜像名
docker run -it mycentosdoc:1.0
案例二:CMD-ENTRYPOINT命令
#查看docker hub的tomcat的dockerfile
#会看到有输出日志,Tomcat也可以访问
docker run -it -p 8888:8080 tomcat
#会看到输出目录,最后一行的run也没有执行了,tomcat也没有运行了
docker run -it -p 8888:8080 tomcat ls l
vi /mydocker/cmd.dockerfile
-------------------------------------
#指定镜像
FROM centos
#运行命令
RUN yum install -y curl
#查询ip
CDM [“curl”,”-s”,”http://ip.cn”]
-------------------------------------
docker build -f /mydocker/cmd.dockerfile -t myip .
docker images
docker run -it myip
#此时出现的是报错,跟在镜像名后面的是command,运行时会替换掉CMD的默认值,一次你这里的-i替换掉原有的CMD,而不是添加在curl -s http://ip.cn后面,而-i根本不是命令,因此报错找不到
docker run -it myip -i
#正确
docker run -it myip curl -s -I http://ip.cn
vi /mydocker/entrypoint.dockerfile
-------------------------------------
#指定镜像
FROM centos
#运行命令
RUN yum install -y curl
#查询ip
#CDM [“curl”,”-s”,”http://ip.cn”]
ENTRYPOINT [“curl”,”-s”,”http://ip.cn”]
-------------------------------------
docker build -f /mydocker/entrypoint.dockerfile -t entrypoint:latest .
docker run -it entrypoint
#此时会看到不会报错了,因为此时是追加
docker run -it entrypoint -i
案例三:ONBUILD命令
vi /mydocker/onbuild.dockerfile
-------------------------------------
#指定镜像
FROM centos
#运行命令(构建时运行)
RUN yum install -y curl
#运行命令
ENTRYPOINT [“curl”,”-s”,”http://ip.cn”]
#只要有子镜像继承了父镜像就会输出此话
ONBUILD RUN echo “build ----------------”
-------------------------------------
docker build -f /mydocker/onbuild.dockerfile -t onbuild:latest .
vi /mydocker/onbuild-son.dockerfile
-------------------------------------
#指定镜像
FROM onbuild
#运行命令(构建时运行)
RUN yum install -y curl
#运行命令
ENTRYPOINT [“curl”,”-s”,”http://ip.cn”]
-------------------------------------
docker build -f /mydocker/onbuild-son.dockerfile -t onbuild_son:latest
#看日志
案例四:自定义Tomcat
#在/mydocker目录下存在以下这个2个文件
apache-tomcat-8.5.16.tar.gz
jdk-8u131-linux-x64.tar.gz
vi /mydocker/tomcat.dockerfile
-------------------------------------
#指定镜像
FROM centos
#作者信息
MAINTAINER levi
#宿主机的文件拷贝到容器内
COPY copy.txt /usr/local/copy.txt
#把JDK和tomcat添加到容器
ADD jdk-8u131-linux-x64.tar.gz /usr/local
ADD apache-tomcat-8.5.16.tar.gz /usr/local
#安装vim编辑器
RUN yum -y install vim
#设置工作访问时的落脚点
ENV MYPATH /usr/local
WORKDIR $MYPATH
#配置Java和Tomcat变量
ENV JAVA_HOME /usr/local/jdk1.8.0_131
ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
ENV CATALINA_HOME /usr/local/apache-tomcat-8.5.16
ENV CATALINA_BASE /usr/local/apache-tomcat-8.5.16
ENV PATH $PATH:$JAVA_HOME/bin:$CATALINA_HOME/lib:$CATALINA_BASE/bin
#容器运行时,对宿主机暴露的端口
EXPOSE 8080
#启动时,运行tomcat
#ENTRYPOINT ["/usr/local/apache-tomcat-8.5.16/bin/startup.sh"]
#CMD ["/usr/local/apache-tomcat-8.5.16/bin/startup.sh"]
CMD /usr/local/apache-tomcat-8.5.16/bin/startup.sh && tail -f /usr/local/apache-tomcat-8.5.16/logs/catalina.out
-------------------------------------
docker build -f /mydocker/tomcat.dockerfile -t mytomcat:latest .
docker run -d -p 9999:8080 --name mytomcat -v /test/logs:/usr/local/apache-tomcat-8.5.16/logs -v /test/html:/usr/local/apache-tomcat-8.5.16/webapps/test --privileged=true mytomcat
#浏览器访问localhost:9999
案例五:发布内容到Tomcat
cd /test
vi test.html
-------------------------------------
Ttest
-------------------------------------
#浏览器访问localhost:9999/test/test.html
#实现一次发布,多个容器起作用