Docker镜像管理及优化最佳实践

前面提到,Docker通过从一个Dockerfile包含所有命令的文本文件中读取指令来自动构建镜像,这些命令按顺序构建给定镜像。

一个Dockerfile遵循特定的格式和指令集,常用指令参考这里:https://blog.csdn.net/miss1181248983/article/details/88718517


Docker Build命令

docker build 命令是根据上下文自动构建镜像。构建上下文是指定位置PATH或文件集URL,PATH是本地文件系统上的目录,URL是一个Git仓库地址。

  • 示例:
Usage: docker build [OPTIONS] PATH | URL | - [flags]

# docker build .

# docker build -t lzx/myapp .

# docker build -t lzx/myapp -f /path/Dockerfile /path

# docker build -t lzx/myapp http://www.example.com/Dockerfile

构建由Docker守护程序运行,而不是CLI。构建过程第一件事是将整个上下文(递归)发送到守护进程。

建议将空目录作为上下文,并将Dockerfile保存在该目录中,目录中仅包含构建Dockerfile所需的文件。


构建PHP网站并部署

  • 目录结构:
# mkdir -p lnmp/{base,project}

# tree lnmp/
lnmp/
├── base
│   ├── Dockerfile-nginx
│   └── Dockerfile-php
└── project
    ├── Dockerfile-nginx
    ├── Dockerfile-php
    ├── nginx.conf
    └── wwwroot
        └── phpinfo.php
  • 编辑 Dockerfile-nginx:
# cd lnmp/base/

# vim Dockerfile-nginx
FROM centos
MAINTAINER lzx [email protected]
RUN yum install -y gcc gcc-c++ make openssl-devel pcre-devel gd-devel libxslt-devel \
    iproute net-tools telnet wget curl lrzsz vim-enhanced  epel-release rsync unzip && \
    yum clean all && \
    rm -rf /var/cache/yum/*
RUN wget http://nginx.org/download/nginx-1.14.1.tar.gz && \
    tar zxf nginx-1.14.1.tar.gz && \
    cd nginx-1.14.1 && \
    ./configure --prefix=/usr/local/nginx \
    --with-http_ssl_module \
    --with-http_v2_module \
    --with-http_realip_module \
    --with-http_image_filter_module \
    --with-http_gunzip_module \
    --with-http_gzip_static_module \
    --with-http_secure_link_module \
    --with-http_stub_status_module \
    --with-stream \
    --with-stream_ssl_module && \
    make -j 4 && make install && \
    mkdir -p /usr/local/nginx/conf/vhost && \
    rm -rf /usr/local/nginx/html/* && \
    echo "OK" >> /usr/local/nginx/html/status.html && \
    cd / && rm -rf nginx-1.14.1*
ENV PATH $PATH:/usr/local/nginx/sbin
WORKDIR /usr/local/nginx
EXPOSE 80
CMD ["nginx","-g","daemon off;"]
  • 构建nginx的基础镜像:
# docker build -t nginx-141 -f Dockerfile-nginx .

# docker container run -d -p 80:80 nginx-141

在这里插入图片描述

  • 编辑 Dockerfile-php:
# vim Dockerfile-php
FROM centos
MAINTAINER lzx [email protected]
RUN yum install -y epel-release && \
    yum install -y gcc gcc-c++ make gd-devel libxml2-devel libcurl-devel \
    libjpeg-devel libpng-devel openssl-devel libmcrypt-devel libxslt-devel libtidy-devel \
    autoconf iproute net-tools telnet wget curl lrzsz vim-enhanced  rsync unzip nload git && \
    yum clean all && \
    rm -rf /var/cache/yum/*
RUN wget http://docs.php.net/distributions/php-5.6.36.tar.gz && \
    tar zxf php-5.6.36.tar.gz && \
    cd php-5.6.36 && \
    ./configure --prefix=/usr/local/php \
    --with-config-file-path=/usr/local/php/etc \
    --with-config-file-scan-dir=/usr/local/php/etc/php.d \
    --enable-fpm --enable-opcache --enable-static=no \
    --with-mysql --with-mysqli --with-pdo-mysql \
    --enable-phar --with-pear --enable-session \
    --enable-sysvshm --with-tidy --with-openssl \
    --with-zlib --with-curl --with-gd --enable-bcmath \
    --with-jpeg-dir --with-png-dir --with-freetype-dir \
    --with-iconv --enable-posix --enable-zip \
    --enable-mbstring --with-mhash --with-mcrypt --enable-hash \
    --enable-xml --enable-libxml --enable-debug=no && \
    make -j 4 && make install && \
    cp php.ini-production /usr/local/php/etc/php.ini && \
    cp sapi/fpm/php-fpm.conf /usr/local/php/etc/php-fpm.conf && \
    sed -i "90a \daemonize = no" /usr/local/php/etc/php-fpm.conf && \
    mkdir /usr/local/php/log && \
    cd / && rm -rf php*
ENV PATH $PATH:/usr/local/php/sbin
WORKDIR /usr/local/php
EXPOSE 9000
CMD ["php-fpm"]
  • 构建php的基础镜像:
# docker build -t php-56 -f Dockerfile-php .
  • 构建部署lnmp的php镜像php:v1:
# cd ../project/

# mkdir wwwroot

# cat wwwroot/phpinfo.php 
<?php
phpinfo();
?>

# vim Dockerfile-php

FROM php-56
COPY wwwroot /wwwroot
CMD ["php-fpm"]

# docker build -t php:v1 -f Dockerfile-php .
  • nginx.conf:
# cat nginx.conf

user    nobody;
worker_processes        1;
error_log       logs/error.log  info;
events {
        worker_connections      1024;
}

http {
        include         mime.types;
        default_type    application/octet-stream;
        log_format      main    '$remote_addr - $remote_user [$time_local] "$request"'
                                '$status $body_bytes_sent "$http_referer"'
                                '"$http_user_agent" "$http_x_forwarded_for"';
        sendfile        on;
        keepalive_timeout       65;
        server {
                listen  80;
                server_name     localhost;
                access_log      logs/host.access.log    main;

                location / {
                        root    html;
                        index   index.html index.htm index.php;
                }
                location ~\.php$ {
                        fastcgi_pass    127.0.0.1:9000;
                        fastcgi_index   index.php;
                        fastcgi_param   SCRIPT_FILENAME $document_root$fastcgi_script_name;
                        include         fastcgi_params;
                }
        }
}
  • 构建部署lnmp的nginx镜像nginx:v1:
# vim Dockerfile-nginx

FROM nginx-141
COPY nginx.conf /usr/local/nginx/conf/

# docker build -t nginx:v1 -f Dockerfile-nginx .
  • 部署LNMP:
# docker network create lnmp

# docker volume create wwwroot

# docker container run -d --name lnmp_nginx -p 80:80 --net lnmp --mount src=wwwroot,dst=/usr/local/nginx/html nginx:v1

# docker container run -d --name lnmp_php --net container:lnmp_nginx --mount src=wwwroot,dst=/usr/local/nginx/html php:v1

创建php容器时指定与nginx容器同一网络,这样nginx就可以代理127.0.0.1:9000到php-fpm了。

  • 访问宿主机IP:

在这里插入图片描述


构建JAVA网站镜像并部署

  • 在宿主机安装JDK,容器以挂载形式使用:
# tar zxf jdk-8u191-linux-x64.tar.gz

# mv jdk1.8.0_191/ /usr/local/jdk
  • 编辑 Dockerfile-java:
# vim Dockerfile-java
FROM centos
MAINTAINER lzx [email protected]

ADD jdk-8u191-linux-x64.tar.gz /usr/local/
RUN yum install -y vim wget curl unzip iproute net-tools && \
    yum clean all && \
    rm -rf /var/cache/yum/* && \
    mv /usr/local/jdk1.8.0_191/ /usr/local/jdk && \
    wget http://mirrors.shu.edu.cn/apache/tomcat/tomcat-8/v8.5.39/bin/apache-tomcat-8.5.39.tar.gz && \
    tar zxf apache-tomcat-8.5.39.tar.gz && \
    mv apache-tomcat-8.5.39 /usr/local/tomcat && \
    rm -rf apache-* jdk-* && \
    echo "OK" > /usr/local/tomcat/webapps/ROOT/status.html && \
    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

ENV JAVA_HOME /usr/local/jdk
ENV PATH $PATH:/usr/local/tomcat/bin
WORKDIR /usr/local/tomcat
EXPOSE 8080
CMD ["catalina.sh","run"]
  • 构建tomcat的基础镜像:
# docker build -t tomcat-85 -f Dockerfile-java .

# docker container run -d -p 89:8080 -v /usr/local/jdk:/usr/local/jdk tomcat-85
  • 访问网页:

在这里插入图片描述


编写Dockerfile最佳实践

  1. 减少镜像层

一次RUN指令形成新的一层镜像层,尽量shell命令都写在一行,减少镜像层。

  • 例如:
FROM centos
MAINTAINER lzx [email protected]
RUN yum install -y epel-release
RUN yum install -y gcc gcc-c++ make 
RUN wget http://docs.php.net/distributions/php-5.6.36.tar.gz
RUN tar zxf php-5.6.36.tar.gz
RUN cd php-5.6.36
RUN ./configure --prefix=/usr/local/php
RUN make -j 4 
RUN make install 
EXPOSE 9000
CMD ["php-fpm"]

应该写成:

FROM centos
MAINTAINER lzx [email protected]
RUN yum install -y epel-release && \
    yum install -y gcc gcc-c++ make
RUN wget http://docs.php.net/distributions/php-5.6.36.tar.gz && \
    tar zxf php-5.6.36.tar.gz && \
    cd php-5.6.36 && \
    ./configure --prefix=/usr/local/php && \
    make -j 4 && make install 
EXPOSE 9000
CMD ["php-fpm"]

结果:12层 → 6层

  1. 优化镜像大小,清理无用数据

一次RUN形成新的一层镜像层,如果没有在同一层删除,无论文件是否在最后删除,都会带到下一层,所以要在每一层清理对应的残留数据,减小镜像大小。

FROM centos
MAINTAINER lzx [email protected]
RUN yum install -y epel-release && \
    yum install -y gcc gcc-c++ make gd-devel libxml2-devel libcurl-devel \
    libjpeg-devel libpng-devel openssl-devel libmcrypt-devel libxslt-devel libtidy-devel \
    autoconf iproute net-tools telnet wget curl lrzsz vim-enhanced  rsync unzip&& \
    yum clean all && \
    rm -rf /var/cache/yum/*
RUN wget http://docs.php.net/distributions/php-5.6.36.tar.gz && \
    tar zxf php-5.6.36.tar.gz && \
    cd php-5.6.36 && \
    ./configure --prefix=/usr/local/php && \
    make -j 4 && make install && \
    cp php.ini-production /usr/local/php/etc/php.ini && \
    cp sapi/fpm/php-fpm.conf /usr/local/php/etc/php-fpm.conf && \
    sed -i "90a \daemonize = no" /usr/local/php/etc/php-fpm.conf && \
    mkdir /usr/local/php/log && \
    cd / && rm -rf php*

这样至少可以节省几十M,甚至上百M。

  1. 减少网络传输时间

最好在内部有一个存放软件包的地方,即应用仓库,类似于上面的PHP官方下载地址,如果用到maven构建这样的操作,同时也更改为私有的maven仓库,减少网络传输时间,提高镜像构建速度。

  1. 多阶段镜像构建

如果运行一个项目,根据上面的做法,是把代码拷贝到基础镜像里,如果是一个需要预先代码编译的项目呢?例如JAVA语言,如何代码编译、部署在一起完成呢?

上面做法需要事先在一个Dockerfile构建一个基础镜像,包括项目运行时环境及依赖库,再写一个Dockerfile将项目拷贝到运行环境中,有点繁琐了。

像JAVA这类语言如果代码编译是在Dockerfile里操作,还需要把源代码构建进去,但实际运行时只需要构建出的包,这种把源代码放进去会有一定的安全风险,也增加了镜像体积。

为了解决上述问题,Docker 17.05开始支持多阶段构建(multi-stage builds),可以简化Dockerfile,减少镜像大小。

  • 例如:
# git clone https://github.com/b3log/solo.git

# cd solo/

# vim Dockerfile-solo

FROM maven AS build
ADD ./pom.xml pom.xml
ADD ./src src/
RUN mvn clean package

FROM tomcat-85
RUN rm -rf /usr/local/tomcat/webapps/ROOT
COPY --from=build target/*.war /usr/local/tomcat/webapps/ROOT.war
CMD ["catalina.sh","run"]

# docker build -t solo:v1 -f Dockerfile-solo .

通过pom.xml中的配置,就能够获取到相应的war包,target/ 目录是maven的输出目录。

首先,第一个FROM后面多了个AS关键字,可以给这个阶段起个名字。
然后,第二部分FROM用的之前构建的Tomcat镜像,COPY关键字增加了--from参数,用于拷贝某个阶段的文件到当前阶段。

镜像小有很多好处,例如快速部署、快速回滚;减少服务中断时间,同时镜像仓库占用磁盘空间也会减少。