联合文件系统

Union文件系统(UnionFs)是一种分层、轻量级并且高性能的文件系统,他支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下(unite several directories into a single virtual filesystem)。虚拟文件系统的目录结构就像普通 linux 的目录结构一样,镜像通过这些文件再加上宿主机的内核共同提供了一个 linux 的虚拟环境,每一层文件系统叫做一层 layer,联合文件系统可以对每一层文件系统设置三种权限,只读(readonly)、读写(readwrite)和写出(whiteout-able)。构建镜像的时候,从一个最基本的操作系统开始,每个构建提交的操作都相当于做一层的修改,增加了一层文件系统,一层层往上叠加,上层的修改会覆盖底层该位置的可见性,这也很容易理解,就像上层把底层遮住了一样,当使用镜像的时候,我们只会看到一个完全的整体,不知道里面有几层,也不需要知道里面有几层。
image.png
镜像,即创建容器的模版,含有启动容器所需要的文件系统及所需要的内容,因此镜像主要用于方便和快速的创建并启动容器,镜像的每一层文件系统都是只读的。镜像可以是自己创建,或者在别人的镜像基础上创建。我们常说的”ubuntu”镜像其实不是一个镜像名称,而是代表了一个名为ubuntu的Repository,同时在这个Repository下面有一系列打了tag的Image。
Docker镜像生成Docker容器时会在镜像的只读文件系统中创建一个可写层,容器中的进程就运行在这个可写层,这个可写层有两个状态,即运行态和退出态。当我们 docker run 运行容器后,docker 容器就进入了运行态,当我们停止正在运行中的容器时,docker 容器就进入了退出态。
image.png
我们可以通过docker commit将容器可写层的Container提交为Image。
我们可以通过docker image inspect查看docker的镜像层。

  1. [root@VM-8-9-centos ~]# docker images -a
  2. REPOSITORY TAG IMAGE ID CREATED SIZE
  3. <none> <none> 98c016594fb3 44 minutes ago 231MB
  4. mycentos v1.0 047f4d09c048 44 minutes ago 231MB
  5. tomcat 9.0.54-jre8-openjdk 07ec6fa0983a 3 days ago 294MB
  6. centos latest 5d0da3dc9764 4 weeks ago 231MB
  7. [root@VM-8-9-centos ~]# docker image inspect 047
  8. ...
  9. "RootFS": {
  10. "Type": "layers",
  11. "Layers": [
  12. "sha256:62a747bf1719d2d37fff5670ed40de6900a95743172de1b4434cb019b56f30b4",
  13. "sha256:0b3c02b5d746e8cc8d537922b7c2abc17b22da7de268d068f2a4ebd55ac4f6d7",
  14. "sha256:9f9f651e9303146e4dd98aca7da21fd8e21dd1a47d71da3d6d187da7691a6dc9",
  15. "sha256:6ccd0e6bdf7a87d4ab19aa6a58ddb3c1b02b7f1b1bdc1f38d8ca5554c68de574",
  16. "sha256:68c1e7509c6550f317b6957faa75b116e8041fa33d3c709a6e88d6cdb9cc4677",
  17. "sha256:09d9063df784f1403edf136090b79ad3e4ad082ce1ef6a9481f9a710a31a65f6",
  18. "sha256:c957181efe4d8e3b730bd9515d7d5207199fabd27ee050dff92385e8da104bea",
  19. "sha256:d3906c7e324d82201592bc7d4233be79aaa6cc37e639eb81fa718d0cd09ad15f",
  20. "sha256:daccf8928b9994bb841bb01d1551b696b895900f664a19e1084605479a47d431",
  21. "sha256:85f2cb3577a067cc394acbfbc2e842b6f2617d717ae34dd983506d42bd790ba0"
  22. ]
  23. },
  24. ...

之前说到,docker images只查看最终镜像,docker images -a会查看中间镜像。

  1. [root@VM-8-9-centos ~]# docker images
  2. REPOSITORY TAG IMAGE ID CREATED SIZE
  3. mycentos v1.0 047f4d09c048 57 minutes ago 231MB
  4. tomcat 9.0.54-jre8-openjdk 07ec6fa0983a 3 days ago 294MB
  5. centos latest 5d0da3dc9764 4 weeks ago 231MB
  6. [root@VM-8-9-centos ~]# docker images -a
  7. REPOSITORY TAG IMAGE ID CREATED SIZE
  8. mycentos v1.0 047f4d09c048 57 minutes ago 231MB
  9. <none> <none> 98c016594fb3 57 minutes ago 231MB
  10. tomcat 9.0.54-jre8-openjdk 07ec6fa0983a 3 days ago 294MB
  11. centos latest 5d0da3dc9764 4 weeks ago 231MB

98c016594fb3就是在构建mycentos:v1.0时创建的中间镜像。

面试题

问题:

  • Docker 镜像本质是什么?
  • Docker 中一个 centos 镜像为什么只有 200MB,而一个 centos 操作系统的 iso 文件要几个 G?
  • Docker 中一个 tomcat 镜像为什么有 500MB,而一个 tomcat 安装包只有 70 多 MB?

答案:

  • Docker 的本质是一个联合文件系统。
  • Centos 的 iso 镜像文件包含 bootfs 和 rootfs,而 docker 的 centos 镜像服用操作系统的 bootfs,只有 rootfs 和其他镜像层。
  • 由于 docker 中镜像是分层的,tomcat 虽然只有 70 多 MB,但他需要依赖与父镜像和基础镜像,所有整个对外暴露的 tomcat 镜像大小 500 多 MB。

    dockerfile格式

    dockerfile常用命令

    1. FROM # 基础镜像,一切从这里开始构建
    2. MAINTAINER # 镜像是谁写的, 姓名+邮箱
    3. RUN # 镜像构建的时候需要运行的命令
    4. WORKDIR # 镜像的工作目录
    5. VOLUME # 挂载的目录
    6. EXPOSE # 保留端口配置
    7. CMD # 指定这个容器启动的时候要运行的命令,只有最后一个会生效,可被替代。
    8. ENTRYPOINT # 指定这个容器启动的时候要运行的命令,可以追加命令
    9. ONBUILD # 当构建一个继承了当前DockerFile的镜像的时候就会运行ONBUILD的指令。
    10. COPY # 类似ADD,将我们文件拷贝到镜像中
    11. ENV # 构建的时候设置环境变量!

    创建SpringBoot项目的镜像

    ```bash D:\docker-build\target>java -jar docker-build-1.0-SNAPSHOT.jar —server.port=8081

    . _ /\ / () \ \ \ \ ( ( )__ | ‘ | ‘| | ‘ \/ ` | \ \ \ \ \/ )| |)| | | | | || (| | ) ) ) ) ‘ |_| .|| ||| |\, | / / / / =========||==============|__/=///_/ :: Spring Boot :: (v2.5.1)

2021-10-17 16:38:24.918 INFO 908 —- [ main] cn.addenda.Application : Starting Application v1.0-SNAPSHOT using Java 1.8.0_221 on LAPTOP-UCPC2H96 with PID 908 (D:\docker-build\target\docker-build-1.0-SNAPSHOT.jar started by ISJINHAO in D:\docker-build\target) 2021-10-17 16:38:24.921 INFO 908 —- [ main] cn.addenda.Application : No active profile set, falling back to default profiles: default 2021-10-17 16:38:25.687 INFO 908 —- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Multiple Spring Data modules found, entering strict repository configuration mode! 2021-10-17 16:38:25.692 INFO 908 —- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data Redis repositories in DEFAULT mode. 2021-10-17 16:38:25.719 INFO 908 —- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 6 ms. Found 0 Redis repository interfaces. 2021-10-17 16:38:26.628 INFO 908 —- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8081 (http) 2021-10-17 16:38:26.640 INFO 908 —- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat] 2021-10-17 16:38:26.640 INFO 908 —- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.46] 2021-10-17 16:38:26.706 INFO 908 —- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext 2021-10-17 16:38:26.706 INFO 908 —- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1732 ms 2021-10-17 16:38:27.640 INFO 908 —- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8081 (http) with context path ‘’ 2021-10-17 16:38:27.650 INFO 908 —- [ main] cn.addenda.Application : Started Application in 3.227 seconds (JVM running for 3.656)

  1. 有一个Springboot项目的可执行jar包,下面将其构建为docker镜像。
  2. <a name="wYfLa"></a>
  3. ### ENTRYPOINT
  4. ```bash
  5. [root@VM-8-9-centos docker_build]# ll
  6. total 28364
  7. -rw-r--r-- 1 root root 29038134 Oct 17 16:56 docker-build-1.0-SNAPSHOT.jar
  8. -rw-r--r-- 1 root root 237 Oct 17 17:19 dockerfile
  9. [root@VM-8-9-centos docker_build]# cat dockerfile
  10. # 指定父镜像
  11. FROM java:8
  12. # 作者
  13. MAINTAINER Addenda
  14. # 指定工作目录
  15. WORKDIR /apps/deploy
  16. # 运行一条指令
  17. RUN echo 'application begin to start!'
  18. # 从docker build上下文目录拷贝文件进来
  19. COPY ./*.jar /apps/deploy/app.jar
  20. # 设置参数,可通过命令行配置
  21. ENV app_port=8080
  22. # 申明端口
  23. EXPOSE $app_port
  24. # 指定启动命令
  25. ENTRYPOINT ["/bin/bash", "-c", "java -jar app.jar --server.port=$app_port"]

启动镜像

  1. [root@VM-8-9-centos docker_build]# docker run -d -it docker-sb
  2. 9e8b2fdb9c684d66bdbafe2df1b76f461d80ff81a8c7f5447c5a8eac602dc1ab
  3. [root@VM-8-9-centos docker_build]# docker exec -it 9e /bin/bash
  4. root@9e8b2fdb9c68:/apps/deploy# curl localhost:8080/hello
  5. hello worldroot@9e8b2fdb9c68:/apps/deploy#
  6. hello worldroot@9e8b2fdb9c68:/apps/deploy# exit;
  7. exit
  8. [root@VM-8-9-centos docker_build]# docker ps -a
  9. CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
  10. 9e8b2fdb9c68 docker-sb "/bin/bash -c 'java …" About a minute ago Up About a minute 8080/tcp naughty_goldwasser

docker ps输出的PORTS是我们在EXPOSE中指定的端口。

  1. [root@VM-8-9-centos docker_build]# docker run -d -it -e app_port=8081 docker-sb
  2. 834ff74b755ba95a98ad9c0b5ec802adcf5445fce7387c636a764e3e910996ca
  3. [root@VM-8-9-centos docker_build]# docker ps -a
  4. CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
  5. 834ff74b755b docker-sb "/bin/bash -c 'java …" 3 seconds ago Up 3 seconds 8080/tcp goofy_jemison
  6. 9e8b2fdb9c68 docker-sb "/bin/bash -c 'java …" 3 minutes ago Up 3 minutes 8080/tcp naughty_goldwasser
  7. [root@VM-8-9-centos docker_build]# docker exec -it 83 /bin/bash
  8. root@834ff74b755b:/apps/deploy# curl localhost:8080/hello
  9. curl: (7) Failed to connect to localhost port 8080: Connection refused
  10. root@834ff74b755b:/apps/deploy# curl localhost:8081/hello
  11. hello worldroot@834ff74b755b:/apps/deploy# exit
  12. exit

当我们在命令中-e指定了端口之后,这时候docker ps -a显示的端口依然是8080,但是实际上暴露是我们指定的8081端口,所以可以看出,EXPOSE暴露的端口仅仅是一个申明,且在docker镜像制作的时候就确定了。

  1. [root@VM-8-9-centos docker_build]# docker run -d -it -e app_port=8082 -p 80:8082 docker-sb
  2. 8399d34ed066a51e01af5fc104fff734ceea99424d0ad909a426e2981e68fc9f
  3. [root@VM-8-9-centos docker_build]# curl localhost/hello
  4. hello world[root@VM-8-9-centos docker_build]#

-p端口映射,需要映射到容器内真实打开的端口。

ENTRYPOINT 追加

  1. [root@VM-8-9-centos docker_build]# cat dockerfile
  2. FROM java:8
  3. MAINTAINER Addenda
  4. WORKDIR /apps/deploy
  5. RUN echo 'application begin to start!'
  6. COPY ./*.jar /apps/deploy/app.jar
  7. ENTRYPOINT ["java", "-jar", "app.jar"]
  1. [root@VM-8-9-centos docker_build]# docker run -d -it -p 8083:8083 docker-sb-e --server.port=8083
  2. [root@VM-8-9-centos docker_build]# curl localhost:8083/hello
  3. hello world[root@VM-8-9-centos docker_build]#

CMD 替换

  1. FROM java:8
  2. MAINTAINER Addenda
  3. WORKDIR /apps/deploy
  4. RUN echo 'application begin to start!'
  5. COPY ./*.jar /apps/deploy/app.jar
  6. CMD ["java", "-jar", "app.jar"]
  1. [root@VM-8-9-centos docker_build]# docker run -d -p 8084:8084 docker-sb-c java -jar app.jar --server.port=8084
  2. 14473830e9979b26cf3e78ada5ea1fa5715f496a3f31f319b4d54599ee6e6ada
  3. [root@VM-8-9-centos docker_build]# curl localhost:8084/hello
  4. hello world[root@VM-8-9-centos docker_build]#

如果dockerfile中用CMD启动服务,后面追加的命令会替换CMD里面的命令

CMD & ENTRYPOINT

当dockerfile里面既有CMD又有ENTRYPOINT时,最终执行的指令时

  1. FROM java:8
  2. MAINTAINER Addenda
  3. WORKDIR /apps/deploy
  4. RUN echo 'application begin to start!'
  5. COPY ./*.jar /apps/deploy/app.jar
  6. CMD ["--server.port=8082"]
  7. ENTRYPOINT ["java", "-jar", "app.jar"]
  1. [root@VM-8-9-centos docker_build]# docker run -d -p 8082:8082 docker-sb-ec
  2. f79e8a9eb69e28f463036581b69b4cdfa88c9d3545f5aa72928054e3f7d75e0a
  3. [root@VM-8-9-centos docker_build]# curl localhost:8082/hello
  4. hello world[root@VM-8-9-centos docker_build]#

我们还可以通过docker ps来查看COMMAND命令,但是需要加上—no-trunc,否则会把命令截断。

  1. [root@VM-8-9-centos docker_build]# docker ps -a --no-trunc
  2. CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
  3. f79e8a9eb69e28f463036581b69b4cdfa88c9d3545f5aa72928054e3f7d75e0a docker-sb-ec "java -jar app.jar --server.port=8082" 2 minutes ago Up 2 minutes 0.0.0.0:8082->8082/tcp, :::8082->8082/tcp youthful_cori

可以看出dockerfile里面的CMD命令被当做了ENTRYPOINT命令的参数。