主要内容
什么是Docker
Docker是一种为了使应用的创建、部署和运行更简单而设计的容器技术。可以理解容器是一个独立的,隔离的运行空间,里面包含了应用运行所需的一切东西:例如运行库以及其他依赖等。通过把应用打包进容器, 开发人员就可以保证开发环境的一致性,不需要为了更换开发环境而烦恼;有了容器,运维人员的工作更加简单高效。
Docker有如下优点
- 灵活
- 轻量
- 可扩展
- 跨平台
Docker和虚拟机
一般Docker都会被拿来和虚拟机进行比较,但实际上Docker并不是虚拟机,最大的差别就是虚拟机需要创建一个完整的操作系统;而Docker只需要提供自己的文件系统,它的内核由宿主机提供,因此Docker比虚拟机更加轻量,使用的资源更加少,资源利用率更高。至少,我们可以在虚拟机里运行Docker,但不能在Docker中运行虚拟机。
下图展示了Docker和虚拟机之间的区别
Docker的架构
从大的方面讲,Docker包括三个组件:客户端、服务器端和仓库。
- 客户端通常指docker命令
- 服务器端则包含docker服务进程和runtime,docker的runtime是runc
- 仓库(registry)是保存image的地方,我们使用的image都是从指定的仓库拉取
镜像(image)和容器(container)
镜像和容器是Docker中两个基本概念,它们的关系如下:
- 镜像是应用包裹器,可以理解它是一个有层次的软件包
- 镜像中的内容添加后就不能修改
- 容器是镜像的一个实例,即容器生于镜像
- 容器处于镜像的最上层,容器中的数据可被修改
image和layer
镜像由一系列的layer构成。镜像不能被修改,但它可以被叠加,意思是我们不能修改现有镜像中的数据,但我们可以在现有镜像的基础上添加一层,然后在新添加的层中加入新数据。比如下面的Dockerfile中的命令
1 2 3 4 5 |
FROM ubuntu:15.04 COPY . /app RUN make /app CMD python /app/app.py |
上面的Dockerfile包括了4个命令,每个命令执行之后都生成一个layer,每个layer依次叠加,也就是说,这个镜像包含了4个layer。当我们使用这个镜像创建容器,容器就在这个镜像的最上层添加一个可写的layer。这种结构的其中一个好处是资源的复用,减少储存空间的浪费。如图:
container和layer
容器和镜像的主要不同就是最上层的可写层,我们对容器的所有修改都保存在这个layer中,但它会随着容器的删除而消失,每个容器都拥有各自的可写层,因此容器之间的数据不会互相受影响。
copy on write(写时复制)策略
当我们修改容器里面的文件的时候,实际上它会从镜像中的只读层(从上往下)搜索,一旦找到需要的文件(停止搜索),就把它复制到容器的可写层进行修改,这种方式称为写时复制。之后对同一文件的修改都只发生在可写层,镜像中的这个文件已经对容器不可见。
安装Docker
Docker提供了社区版(community edition)和企业版(enterprise edition),当然,我们使用的是社区版,系统是centos7
1 2 3 4 5 6 7 8 9 |
# 安装依赖 sudo yum install -y yum-utils device-mapper-persistent-data lvm2 # 安装repository sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo # 安装docker-ce sudo yum install docker-ce |
运行第一个docker容器
运行之前先检查docker守护进程是否已经运行
1 2 3 4 5 6 |
# 检查docker状态 systemctl status docker # 如果还没有运行 systemctl start docker |
然后就可以尝试运行一个容器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
[root@dellcentos7 ~]# docker run hello-world Unable to find image 'hello-world:latest' locally latest: Pulling from library/hello-world d1725b59e92d: Pull complete Digest: sha256:0add3ace90ecb4adbf7777e9aacf18357296e799f81cabc9fde470971e499788 Status: Downloaded newer image for hello-world:latest Hello from Docker! This message shows that your installation appears to be working correctly. To generate this message, Docker took the following steps: 1. The Docker client contacted the Docker daemon. 2. The Docker daemon pulled the "hello-world" image from the Docker Hub. (amd64) 3. The Docker daemon created a new container from that image which runs the executable that produces the output you are currently reading. 4. The Docker daemon streamed that output to the Docker client, which sent it to your terminal. To try something more ambitious, you can run an Ubuntu container with: $ docker run -it ubuntu bash Share images, automate workflows, and more with a free Docker ID: https://hub.docker.com/ For more examples and ideas, visit: https://docs.docker.com/get-started/ |
docker run
命令用于运行容器,它包含下面几个过程
- docker 客户端和docker 守护进程通讯
- 检查本地是否有hell-world镜像,如果没有就从仓库拉取(默认仓库是hub.docker.com)
- 从hello-world镜像创建一个容器
- 运行容器
转变为命令如下:
1 2 3 4 5 6 7 8 9 |
# 从registry拉取镜像 docker pull hello-world # 从镜像hello-world创建一个名为hello容器 docker create --name hello hello-world # 运行容器 docker start -i hello |
通过容器运行nginx
1 2 |
[root@dellcentos7 ~]# docker run --name nginx -d -p 80:80 nginx |
- -d的作用是使容器能够在后台运行
- -p的作用是把宿主机的80端口映射到容器的80端口,否则我们无法在外界访问nginx服务
通常我们需要修改nginx配置文件的参数,这个时候我们就需要进入容器的内部,使用docker exec
命令即可:
1 2 3 4 |
[root@dellcentos7 ~]# docker exec -it nginx /bin/bash #进入容器内部,并运行容器里面的/bin/bash程序 root@e5ccb998ae3d:/# ls #从前面的IDe5ccb998ae3d可以看到,我们已经进入了容器 bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var |
镜像的创建
虽然dockerhub上面已经有足够多的镜像提供下载,但是我们还是有自定义镜像的需求的。可以通过两种方式创建镜像
- docker commit 基于现有的镜像进行增强
- Dockerfile 可以从零创建镜像
docker commit
docker commit
命令从已修改的容器创建新的镜像。例如,一般的容器都不会提供vim编辑器,如果要修改容器的文件,可能不是那么方便,我们可以基于现有的镜像安装vim,然后把这个安装了vim的容器创建为一个新的镜像
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
# 首先启动容器 [root@dellcentos7 nginx]# docker run --name nginx -p 80:80 -d nginx # 进入容器 [root@dellcentos7 nginx]# docker exec -it nginx /bin/bash # 检查容器用的是什么包管理器 root@b19a1e35cb07:/# cat /etc/issue Debian GNU/Linux 9 \n \l # 是Debian,因此使用apt-get安装vim root@b19a1e35cb07:/# apt-get update && apt-get install -y vim # 安装完后退出容器,使用docker commit创建镜像,成功后会返回镜像的ID [root@dellcentos7 nginx]# docker commit nginx nginx-with-vim sha256:dd75f8aeb2c0d6e2ff889c16a1ddb518b3eaa2bdd730cb8c78e5192d614e263b |
Dockerfile
Dockerfile可以从零开始创建镜像,但是这种情况是很少遇到的,大多数情况是使用一个基础镜像来创建新镜像。
还是以上面的为例子,在nginx镜像里面添加vim,基本的Dockerfile格式如下:
1 2 3 |
FROM nginx RUN apt-get update && apt-get install -y vim |
另一个例子是创建tomcat容器
1 2 3 4 5 6 7 |
FROM ubuntu ADD apache-tomcat-8.5.34.tar.gz / ADD jdk-8u181-linux-x64.tar.gz / ENV JAVA_HOME /jdk1.8.0_181 EXPOSE 8080 CMD ["/bin/bash","/apache-tomcat-8.5.34/bin/catalina.sh","run"] |
注意:最后的CMD命令不能使用startup.sh脚本启动tomcat,因为startup.sh执行完毕会退出,最终会导致容器执行完CMD命令后自动停止
准备好Dockerfile之后就可以用docker build
命令创建镜像
1 2 3 4 5 |
[root@dellcentos7 tomcat]# pwd /root/tomcat [root@dellcentos7 tomcat]# ls apache-tomcat-8.5.34.tar.gz Dockerfile jdk-8u181-linux-x64.tar.gz |
使用docker build创建镜像
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
[root@dellcentos7 tomcat]# docker build -t tomcat:8.5 . Sending build context to Docker daemon 380.3MB Step 1/6 : FROM ubuntu ---> cd6d8154f1e1 Step 2/6 : ADD apache-tomcat-8.5.34.tar.gz / ---> 3252d81ce592 Step 3/6 : ADD jdk-8u181-linux-x64.tar.gz / ---> e44194b0ee41 Step 4/6 : ENV JAVA_HOME /jdk1.8.0_181 ---> Running in da89f7349452 Removing intermediate container da89f7349452 ---> fe21010f5140 Step 5/6 : EXPOSE 8080 ---> Running in 531285e6f0d6 Removing intermediate container 531285e6f0d6 ---> ea7f21966746 Step 6/6 : CMD ["/bin/bash","/apache-tomcat-8.5.34/bin/catalina.sh","run"] ---> Running in 8d4bcdf4a7c7 Removing intermediate container 8d4bcdf4a7c7 ---> 930372abc59a Successfully built 930372abc59a Successfully tagged tomcat:8.5 |
创建运行容器
1 2 |
[root@dellcentos7 tomcat]# docker run --name tomcat -p 80:8080 -d tomcat:8.5 |
可以通过docker history
查看镜像的构建历史
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
[root@dellcentos7 ~]# docker history tomcat:8.5 IMAGE CREATED CREATED BY SIZE COMMENT 930372abc59a 3 hours ago /bin/sh -c #(nop) CMD ["/bin/bash" "/apache… 0B ea7f21966746 3 hours ago /bin/sh -c #(nop) EXPOSE 8080 0B fe21010f5140 3 hours ago /bin/sh -c #(nop) ENV JAVA_HOME=/jdk1.8.0_1… 0B e44194b0ee41 3 hours ago /bin/sh -c #(nop) ADD file:5bdbb630ef5dc5be5… 382MB 3252d81ce592 3 hours ago /bin/sh -c #(nop) ADD file:d5e42a137fcdbf748… 199MB cd6d8154f1e1 9 days ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B <missing> 9 days ago /bin/sh -c mkdir -p /run/systemd && echo 'do… 7B <missing> 9 days ago /bin/sh -c sed -i 's/^#\s*\(deb.*universe\)$… 2.76kB <missing> 9 days ago /bin/sh -c rm -rf /var/lib/apt/lists/* 0B <missing> 9 days ago /bin/sh -c set -xe && echo '#!/bin/sh' > /… 745B <missing> 9 days ago /bin/sh -c #(nop) ADD file:3df374a69ce696c21… 84.1MB |
Dockerfile常用指令
- FROM
指定基础镜像
-
MAINTAINER(deprecated)
维护者的信息,现在建议使用LABEL指令替换
-
COPY
把文件添加进镜像
-
ADD
把文件添加进镜像,和COPY不一样的是,如果文件是一个压缩包,会先被解压再放进镜像
-
ENV
为镜像添加环境变量,ENV之后的所有指令都可以使用由ENV指令添加的变量
-
EXPOSE
指定docker容器运行时监听的端口
-
CMD
指定容器运行时执行的命令,多个CMD指令只有最后一个会被执行
-
ENTRYPOINT
指定容器运行时执行的命令
CMD和ENTRYPOINT的作用十分相似,它们都有两种书写形式,分别是exec形式和shell形式。
CMD的书写形式:
CMD ["executable","param1","param2"]
(exec form, this is the preferred form)CMD ["param1","param2"]
(as default parameters to ENTRYPOINT)CMD command param1 param2
(shell form)
ENTRYPOINT的书写形式:
ENTRYPOINT ["executable", "param1", "param2"]
(exec form, preferred)ENTRYPOINT command param1 param2
(shell form)
我们之前说过,ENV之后的指令都可以使用它定义的环境变量,但在使用CMD和ENTRYPOINT的exec形式的时候,需要特别注意,例如:
1 2 3 4 |
ENV HOME /home ENTRYPOINT [ "echo", "$HOME" ] CMD ["echo", "$HOME"] |
上面ENTRYPOINT和CMD指令的原意是使用ENV定义的变量值替换变量$HOME的值,实际上这个替换过程并没有发生,$HOME这里直接作为字符串输出,变量替换失败。如果希望exec形式进行变量替换,则需要显式引用sh,例如:
1 2 3 4 |
ENV HOME /home ENTRYPOINT [ "sh", "-c", "echo $HOME" ] CMD [ "sh", "-c", "echo $HOME" ] |
下表是ENTRYPOINT和CMD指令混合使用时的行为
No ENTRYPOINT | ENTRYPOINT exec_entry p1_entry | ENTRYPOINT [“exec_entry”, “p1_entry”] | |
---|---|---|---|
No CMD | error, not allowed | /bin/sh -c exec_entry p1_entry | exec_entry p1_entry |
CMD [“exec_cmd”, “p1_cmd”] | exec_cmd p1_cmd | /bin/sh -c exec_entry p1_entry | exec_entry p1_entry exec_cmd p1_cmd |
CMD [“p1_cmd”, “p2_cmd”] | p1_cmd p2_cmd | /bin/sh -c exec_entry p1_entry | exec_entry p1_entry p1_cmd p2_cmd |
CMD exec_cmd p1_cmd | /bin/sh -c exec_cmd p1_cmd | /bin/sh -c exec_entry p1_entry | exec_entry p1_entry /bin/sh -c exec_cmd p1_cmd |
从上表可以总结三个比较明显的结论
- shell形式的ENTRYPOINT指令默认作为sh的一个子进程运行
-
shell形式的ENTRYPOINT指令会阻止CMD指令的执行
- 只有exec形式的ENTRYPOINT指令可以和CMD指令同时存在
第一个结论造成的问题是:ENTRYPOINT执行的命令不响应任何信号,即当使用docker stop的时候,无法停止由ENTRYPOINT执行的命令。例如:
1 2 3 |
FROM ubuntu ENTRYPOINT top -b #不处理任何信号 |
如果希望改变这种行为,则需要使用exec命令
1 2 3 |
FROM ubuntu ENTRYPOINT exec top -b |
exec可以进行进程替换,因此top进程会替换为sh的进程ID,这样top就可以对信号作出响应。
自建Registry
Registry是一个镜像仓库,默认所有的镜像都存放在dockerhub上面。如果想把镜像保存在私有的仓库中,可以使用官方提供的registry镜像搭建。
第一步:拉取registry镜像并运行容器
1 2 |
docker run -d -p 5000:5000 --restart always --name registry registry:2 |
第二步:为将要推送的镜像打tag
镜像完整的repository名称建议为:hostname:portnum/maintainer/imageName
,只有官方registry的hostname:portnum可以被省略,因此,镜像要推送到本地仓库,需要提供完整的hostname和portnum。
例如把nginx推送到本地registry
1 2 |
docker tag nginx:latest localhost:5000/nginx:latest |
第三步:使用docker push
推送镜像
1 2 3 4 5 6 7 |
[root@dellcentos7 tomcat]# docker push localhost:5000/nginx:latest The push refers to repository [localhost:5000/nginx] 579c75bb43c0: Pushed 67d3ae5dfa34: Pushed 8b15606a9e3e: Pushed latest: digest: sha256:c0b69559d28fb325a64c6c8f47d14c26b95aa047312b29c699da10380e90b4d7 size: 948 |
http和https的问题,当使用非localhost作为registry的时候,Registry会拒绝http请求,如果希望强制使用http协议,可以修改/etc/docker/daemon.json
文件(如果没有该文件自己创建一个),并添加以下内容:
1 2 |
{"insecure-registries":["dellcentos7:5000"]} |
完成后重启docker守护进程即可。
转载请注明:Pure nonsense » Docker从入门到放弃