一、镜像是什么?

作为最为火爆的容器技术,docker快速占据市场的原因之一就是docker镜像。那么docker镜像是什么呢?docker镜像可以简单的理解为环境和应用的集合,是一种封装方式,类似于java的jar包,centos的rpm包。这一打包格式使得通过docker封装的应用可以快速的推广,并且在任何环境下运行此应用。所以说,镜像的规范是docker的最大的贡献,那么这一规范是什么呢?
我们先从dockerhub上拉取一个镜像

  1. [root@k8s03 ~]# docker pull tomcat
  2. Using default tag: latest
  3. latest: Pulling from library/tomcat
  4. d6ff36c9ec48: Pull complete
  5. c958d65b3090: Pull complete
  6. edaf0a6b092f: Pull complete
  7. 80931cf68816: Pull complete
  8. bf04b6bbed0c: Pull complete
  9. 8bf847804f9e: Pull complete
  10. 6bf89641a7f2: Pull complete
  11. 3e97fae54404: Pull complete
  12. 10dee6830d45: Pull complete
  13. 680b26b7a444: Pull complete
  14. Digest: sha256:cbdcddc4ca9b47e42c7d0c2db78cbc0f7a8b4bbe1fa395f4a53c5a236db29146
  15. Status: Downloaded newer image for tomcat:latest docker.io/library/tomcat:latest

这是一个从dockerhub上拉取的tomcat镜像,可以看到,在拉取过程中镜像分成了10层,下面可以查看一下镜像的详情

[root@k8s03 ~]# docker inspect d5eef28cf41d
...
    "Data": {                 
          "LowerDir": "/mnt/docker-lib/overlay2/ad0fdd6e7fadd5b77a7383d47553f77a727fa85c70da136be40ea44c6944ab11/diff:/mnt/docker-lib/overlay2/7b7f957f86be54f7497129f4a5cf664fc1a6879a5a83d5f37d720decbb229d78/diff:/mnt/docker-lib/overlay2/bc72109526d4f1a45ebab8b1abe9a36ceb5841ec37bad3d5b69873b9a41bb687/diff:/mnt/docker-lib/overlay2/eea0db10d31a06a305dd9d980d5c18e42f04f9026e2367f273066bd0e53ef2a5/diff:/mnt/docker-lib/overlay2/67512ff785e66fe5401dd44f2791decd1111026fd39f0b0dd8017e5a2a3d98bc/diff:/mnt/docker-lib/overlay2/1f7e4902a79ef2038947523535192aeadd097ee16020a0c95a695311534b70d3/diff:/mnt/docker-lib/overlay2/a46448ddb1c30de01b7e8539a958203b202bca669ad62707a9aca818c7273548/diff:/mnt/docker-lib/overlay2/62dc4b28677571e3abef933b1f199a85e62105cbe62752b840cc7e535300755d/diff:/mnt/docker-lib/overlay2/7a1f7a7de4e054c05ff4513a30bf087385f74caf5fce4462fc14bfb177d47239/diff",                 
          "MergedDir": "/mnt/docker-lib/overlay2/b6f983ae3751c3ea17d91c964060dd44d39430c75fc7821caac7eeb8871dfc77/merged",                 
          "UpperDir": "/mnt/docker-lib/overlay2/b6f983ae3751c3ea17d91c964060dd44d39430c75fc7821caac7eeb8871dfc77/diff",                 
          "WorkDir": "/mnt/docker-lib/overlay2/b6f983ae3751c3ea17d91c964060dd44d39430c75fc7821caac7eeb8871dfc77/work"             },
...

而镜像的数据就在这些目录之内,而这也就是基于UnionS实现的镜像的分层规范。同时除了最底层镜像,每一层都有一个指向底层的索引,便于找到这一层镜像的父层,而这就意味着,父层是可以进行复用的,通过这一共用父层的方式,可以降低储存的使用,提高构建的速率。

二、定制属于你的镜像

1、构建镜像的方法

既然镜像有这么多的优势,那么应该如何制作镜像呢?第一种是通过容器构建镜像,即容器镜像,通过docker commit 的方式进行构建,该方法本菜鸟并不推荐,原因有如下几点:1)镜像为分层结构,容器则为镜像顶层加了一个可写层,这一方式构建的镜像极容易镜像过大。2)容器镜像无法确定这一层可写层内的内容,对于安全性上,存在问题,而且也不易维护。第二种镜像构建的方法则为dockerfile,通过dockerfile来控制镜像的构建,这个也是官方推荐的方案。

2、深入理解dockerfile

那么dockerfile是什么样子的呢?


FROM centos:7        FROM centos:7
ENV VERSION=8.5.43        ENV VERSION=8.5.43
RUN yum install java-1.8.0-openjdk wget curl unzip iproute net-tools -y && \        RUN yum install java-1.8.0-openjdk wget curl unzip iproute net-tools -y && \
yum clean all && \            yum clean all && \
rm -rf /var/cache/yum/*            rm -rf /var/cache/yum/*
COPY apache-tomcat-${VERSION}.tar.gz /        COPY apache-tomcat-${VERSION}.tar.gz /
RUN tar zxf apache-tomcat-${VERSION}.tar.gz && \        RUN tar zxf apache-tomcat-${VERSION}.tar.gz && \
mv apache-tomcat-${VERSION} /usr/local/tomcat && \            mv apache-tomcat-${VERSION} /usr/local/tomcat && \
rm -rf apache-tomcat-${VERSION}.tar.gz /usr/local/tomcat/webapps/* && \            rm -rf apache-tomcat-${VERSION}.tar.gz /usr/local/tomcat/webapps/* && \
mkdir /usr/local/tomcat/webapps/test && \            mkdir /usr/local/tomcat/webapps/test && \
echo "ok" > /usr/local/tomcat/webapps/test/status.html && \            echo "ok" > /usr/local/tomcat/webapps/test/status.html && \
sed -i '1a JAVA_OPTS="-Djava.security.egd=file:/dev/./urandom"' /usr/local/tomcat/bin/catalina.sh && \            sed -i '1a JAVA_OPTS="-Djava.security.egd=file:/dev/./urandom"' /usr/local/tomcat/bin/catalina.sh && \
ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime            ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
ENV PATH $PATH:/usr/local/tomcat/bin        ENV PATH $PATH:/usr/local/tomcat/bin
WORKDIR /usr/local/tomcat        WORKDIR /usr/local/tomcat
EXPOSE 8080        EXPOSE 8080
CMD ["catalina.sh", "run"]        CMD ["catalina.sh", "run"]

这是一个tomcat的dockerfile,里面涉及到了dockerfile的专用语法,包括FROM、RUN、ENV、WORKDIR等等,那么都有什么作用呢?

2.1、FROM

FROM <img>:<tag>

镜像是分层的,通过FROM可以引用一个基础镜像,这个是必须的模块,而镜像是以此为基础,没执行一条命令,就在此基础上增加一层镜像,所以基础镜像需要尽可能的精简,同时要保证安全性。

2.2、WORKDIR

WORKDIR /path/to/workdir

这一模块用于指定工作目录,如果指定后,通过exec进入容器后,便在WORKDIR中,同时需要注意,通过相对路径执行命令时,一定要正确设置WORKDIR,避免出现因路径导致的失败。

2.3、COPY 和AND

COPY  源文件 目标路径        COPY  源文件 目标路径
ADD 源文件 目标路径        ADD 源文件 目标路径

这两个模块用处相同,都是将文件拷贝到镜像内,但是还是存在一些差异:
a、ADD 可以通过网络源下载文件封入镜像内。
b、ADD可以对tar、gzip、bzip2等文件进行解压。

2.4、RUN

RUN  command1 ... commandN

RUN模块引导的是构建镜像时执行的命令,没执行一次RUN,会生成一层镜像,所以建议在使用中,通过&& 链接来减少镜像层数,比如构建基础镜像时,通过一条RUN 将java和tomcat封装在同一层内做基础镜像,那么业务镜像仅需要在基础镜像外再加一层,这样便可以保障镜像的精简。

2.5、ENV

ENV key=value

ENV模块将环境变量注入到镜像内。

2.6、EXPOSE

EXPOSE  port XXXX

此模块用于声名暴露的端口。

2.7、USER

USER app        USER app
USER app:app        USER app:app              
USER 500        USER 500          
USER 500:500        USER 500:500          
USER app:500        USER app:500              
USER 500:app        USER 500:app

USER模块用于限定用户,可以用来实现降权运行容器内的应用,提高安全性。再使用这个模块时,需要使用useradd创建用户。

2.8、ENTRYPOINT

ENTRYPOINT  ["command","param"]        ENTRYPOINT  ["command","param"]
ENTRYPOINT  command param        ENTRYPOINT  command param

ENTRYPOINT模块用于设置容器启动时的命令,设置一个不退出的前台进程,同时,ENTRYPOINT设置的命令不会被docker run所覆盖。

2.9、CMD

CMD  ["command","param"] CMD command param CMD ["param1","param2"]

CMD模块和ENTRYPOINT 使用方法一致,但是CMD模块可以被docker run所覆盖,同时CMD 可以和ENTRYPOINT 配合使用,作为ENTRYPOINT 的参数出现。

3、构建属于你的镜像

构建镜像是通过docker build来实现的,下面我们来构建一个tomcat的镜像,先看一下dockerfile

FROM centos:7        FROM centos:7
COPY apache-tomcat-8.5.56.tar.gz /mnt        COPY apache-tomcat-8.5.56.tar.gz /mnt
COPY jdk-8u261-linux-x64.tar.gz /mnt        COPY jdk-8u261-linux-x64.tar.gz /mnt
RUN cd /mnt  && \        RUN cd /mnt  && \
tar zxf jdk-8u261-linux-x64.tar.gz && \            tar zxf jdk-8u261-linux-x64.tar.gz && \
mv jdk1.8.0_261 /usr/local/java && \            mv jdk1.8.0_261 /usr/local/java && \
tar zxf apache-tomcat-8.5.56.tar.gz && \            tar zxf apache-tomcat-8.5.56.tar.gz && \
mv apache-tomcat-8.5.56 /usr/local/tomcat && \            mv apache-tomcat-8.5.56 /usr/local/tomcat && \
rm -f apache-tomcat-8.5.56.tar.gz jdk-8u261-linux-x64.tar.gz && \            rm -f apache-tomcat-8.5.56.tar.gz jdk-8u261-linux-x64.tar.gz && \
ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime            ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
ENV JAVA_HOME=/usr/local/java        ENV JAVA_HOME=/usr/local/java
ENV JRE_HOME=/usr/local/java/jre        ENV JRE_HOME=/usr/local/java/jre
ENV PATH $PATH:/usr/local/java/bin:/usr/local/tomcat/bin        ENV PATH $PATH:/usr/local/java/bin:/usr/local/tomcat/bin
WORKDIR /usr/local/tomcat        WORKDIR /usr/local/tomcat
EXPOSE 8080        EXPOSE 8080
CMD ["catalina.sh", "run"]        CMD ["catalina.sh", "run"]

开始构建镜像

[root@k8s03 hgfs]# docker build -t tomcat8:base .  
Sending build context to Docker daemon  521.8MB 
Step 1/10 : FROM centos:7  ---> 7e6257c9f8d8 
Step 2/10 : COPY apache-tomcat-8.5.56.tar.gz /mnt  ---> 154214a58ae1 
Step 3/10 : COPY jdk-8u261-linux-x64.tar.gz /mnt  ---> 94af330c2ab1 
Step 4/10 : RUN cd /mnt  &&     tar zxf jdk-8u261-linux-x64.tar.gz &&   mv jdk1.8.0_261 /usr/local/java &&      tar zxf apache-tomcat-8.5.56.tar.gz &&       mv apache-tomcat-8.5.56 /usr/local/tomcat &&    rm -f apache-tomcat-8.5.56.tar.gz jdk-8u261-linux-x64.tar.gz &&     ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime  ---> Running in a53326701fb1 Removing intermediate container a53326701fb1  ---> 926a7bd6dade 
Step 5/10 : ENV JAVA_HOME=/usr/local/java  ---> Running in 3608b84318e5 Removing intermediate container 3608b84318e5  ---> c41f3ee011fb 
Step 6/10 : ENV JRE_HOME=/usr/local/java/jre  ---> Running in 4ed9b3a8fe13 Removing intermediate container 4ed9b3a8fe13  ---> c577b0979b9c 
Step 7/10 : ENV PATH $PATH:/usr/local/java/bin:/usr/local/tomcat/bin  ---> Running in a718feca40fb Removing intermediate container a718feca40fb  ---> 18f66356c84e 
Step 8/10 : WORKDIR /usr/local/tomcat  ---> Running in 5bd80d012f28 Removing intermediate container 5bd80d012f28  ---> db5a93e1adb0 
Step 9/10 : EXPOSE 8080  ---> Running in 488067edba1d Removing intermediate container 488067edba1d  ---> 96a08d9468c9 
Step 10/10 : CMD ["catalina.sh", "run"]  ---> Running in 703caa5216f1 Removing intermediate container 703caa5216f1  ---> e7d08a6fe4b6 
Successfully built e7d08a6fe4b6 
Successfully tagged tomcat8:base

这里有一个很有意思的事情,当构建构成中,执行docker ps命令可以看到一个构件中的容器

[root@k8s03 hgfs]# docker ps  CONTAINER ID        IMAGE                         COMMAND                  CREATED             STATUS              PORTS               NAMES d4621ccda168        df2785b6788e                  "/bin/sh -c 'cd /mnt…"   4 seconds ago       Up 3 seconds                            interesting_yalow

而这个容器在做的事情就是构建这个镜像,而当镜像构建成功后,这个容器也会被删掉。
下面我们验证一下这个镜像

[root@k8s03 hgfs]# docker run -d tomcat8:base ed0a14b3a86e6e96237ef84aed0229cad51ce3baa6125f28e16757178d67c01c [root@k8s03 hgfs]# docker ps  CONTAINER ID        IMAGE                         COMMAND                  CREATED             STATUS              PORTS               NAMES ed0a14b3a86e        tomcat8:base                  "catalina.sh run"        5 seconds ago       Up 5 seconds        8080/tcp

容器启动成功,那么进入容器内验证一下环境变量

[root@k8s03 hgfs]# docker exec -it ed0a14b3a86e bash  [root@ed0a14b3a86e tomcat]# echo $JAVA_HOME /usr/local/java [root@ed0a14b3a86e tomcat]# echo $JRE_HOME  /usr/local/java/jre [root@ed0a14b3a86e tomcat]# echo $PATH     /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/java/bin:/usr/local/tomcat/bin [root@ed0a14b3a86e tomcat]#

至此,一个基于java8 和 tomcat8的 centos7基础镜像构建完毕

三、镜像管理

1、清理dangling镜像

在日常工作中,常常会出现一个现象,就是针对一个部署包进行多次修改,每次修改都创建一个新的镜像,但是tag和image都为修改,这样每一次构建后,image和tag都会指向最新的镜像,而之前的镜像就会变成:的样式,也就是dangling镜像

[root@k8s03 hgfs]#  docker images -f "dangling=true" 
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE 
<none>              <none>              d4aa089b72f7        16 minutes ago      734MB 
<none>              <none>              e0d2d9814c3f        17 minutes ago      357MB

这一镜像是需要频繁清理以实现节约空间的目的

2、清理业务镜像

业务镜像理论上应谨慎清理,可定时清理超期镜像,但是为了实现版本控制,制品库内的镜像进行归档封存,不建议彻底清理。