一、Dockerfile 简介
Dockerfile 是构建镜像的文本文件,通过一系列指令描述镜像构建过程,构建操作由 Docker daemon 进行,它会先验证语法,然后逐一运行指令,每次生成一个新的镜像层,直到构建出最终的镜像。
Dockerfile 集成了构建镜像所需的全部步骤,从选择基础镜像到运行容器,极大地简化了应用程序的部署和环境配置流程。通过 Dockerfile,开发者可以实现自动化、可重复、一致的构建过程,这对于团队协作和持续集成 / 持续部署流程至关重要。
Docker 镜像是一个特殊的分层文件系统,包含应用程序和必要的依赖环境,但并不包含任何的动态信息。构建一个镜像,实际上就是为镜像中的每一层创建相应的配置。因此,可以把构建的命令语句、参数配置等信息都写入一个脚本中,这样,“docker commit” 命令的无法重复的问题、镜像臃肿的问题就都被解决了。这个脚本就是 Dockerfile。
(一)什么是 Dockerfile?
Dockerfile 是一个用来构建镜像的文本文件,文本内容包含了一条条构建镜像所需的指令和说明。构建一个镜像,就是为镜像中的每一层创建相应的配置,而 Dockerfile 则是将这些配置信息以指令的形式记录下来。Dockerfile 可以使用 “docker build” 命令进行编译。在编译过程中,每一条指令的内容就是描述该层应如何进行构建。当我们需要定制自己额外的需求时,只需要在 Dockerfile 文件的基础上添加或者修改指令,重新生成新的镜像即可。
(二)Dockerfile 示例
下面通过一个简单的示例来演示如何使用 Dockerfile。在这个示例中,将基于 Nginx 的镜像来构建一个新的镜像,并在该镜像中部署一个简单的 Web 网页。
- 创建一个文件 “Dockerfile”。在该文件中输入以下命令。
FROM nginx
RUN echo '<h1>This is a Demo HTML</h1>' >/usr/share/nginx/html/index.html
-
在 Dockerfile 所在的目录下执行 “docker build” 命令构建镜像。构建的过程如下图所示。提示:“docker build” 命令会在当前目录下寻找名为 “Dockerfile” 的文件,然后对该文件进行编译生成镜像。如果文件名不是 Dockerfile,则可以在使用 “docker build” 命令加上 “-f” 参数指定文件名称。
-
查看新生成的镜像,如下图所示。
-
使用新生成的镜像创建容器。
docker run -d -p 7788:80 mynginx
-
使用浏览器访问宿主机的 7788 端口,可以看到如下图所示界面。
(三)Docker File 文件详解
下图展示了 Docker 镜像、容器和 Dockerfile 三者的关系。可以看出使用 Dockerfile 文件定义镜像,然后运行镜像启动容器。下表列出了一个完整的 Dockerfile 文件的组成部分。当完成了 Dockerfile 的编写后,使用 “docker build” 命令将会根据 Dockerfile 中上下文的内容构建新 Docker 镜像。整个构建的过程会被递归处理。因此,如果在 Dockerfile 中含有子路径或 URL 等信息,则它们都将被递归进行构建。
提示:在使用 “docker build” 进程镜像构建时,还可以通过 “-t” 参数指定生成镜像的仓库地址和标签等信息。Dockerfile 构建镜像的过程请参考下图。“docker build” 命令在使用 Dockerfile 生成镜像时,会通过 Docker 的守护进程执行 Dockerfile 中的每一条指令,并在每一步执行完成后生成一个新镜像。当所有的指令执行完成后,会输出最终镜像的 ID。当镜像最终生成后,Docker 的守护进程会自动清理 Docker 的上下文环境,并自动重用已生成的中间镜像,以加速构建的速度。图中方框的部分表明,在构建过程中使用到了 Dockerfile 的缓存机制。
二、Dockerfile 关键字详解
1. FROM 关键字
指定基础镜像,新镜像是基于哪个镜像构建的,建议使用官方镜像作为基础镜像,如 Alpine,体积小且严格控制。在 Dockerfile 中,FROM
指令必须作为第一个指令出现,它为后续的构建步骤提供了基础环境。例如:FROM ubuntu:20.04
,这里指定了以 Ubuntu 20.04 版本的镜像作为基础镜像。
2. MAINTAINER/LABEL 关键字
MAINTAINER
指定维护者信息,例如:MAINTAINER John Doe <john.doe@example.com>
。但按照官方文档描述,MAINTAINER已逐渐过时,目前更推荐使用LABEL来设置元数据。
LABEL
用于给镜像打标签,相对MAINTAINER
更灵活。可以设置多个元数据,一个LABEL是键值对,多个键值对之间使用空格分开,命令换行时使用反斜杠\
。例如:LABEL version="1.0" description="This is a demo image" by="Developer Name"
。
3. RUN 关键字
构建过程中运行的命令,有 shell 格式和 exec 格式两种。
shell 格式:RUN <command>
,命令在 shell 中运行,默认情况下在 Linux 上是/bin/sh -c
,在 Windows 上是cmd /S /C
。例如:RUN apt-get update
。
exec 格式:RUN ["executable", "param1", "param2"]
。例如:RUN ["npm", "install"]
。
4. WORKDIR 关键字
指定进入容器时的目录。使用WORKDIR
可以设置容器内的工作目录,后续的指令将在这个目录下执行。例如:WORKDIR /app
,如果目录不存在,会自动创建。
5. ENV 关键字
设置容器内环境变量,方便访问程序。通过ENV
可以设置环境变量,这些变量在构建过程和容器运行时都有效。例如:ENV NODE_ENV production
,在后续的指令中可以使用这个环境变量,如RUN echo $NODE_ENV
。
6. ADD 关键字
将宿主机资源拷贝进镜像中,会自动解压缩,还能从远程读取资源。ADD
指令可以将宿主机上的文件或目录复制到镜像中。如果源是一个压缩文件,它会自动解压缩。例如:ADD myfile.tar.gz /app
,会将myfile.tar.gz
解压缩到容器的/app目录下。同时,ADD还支持从远程 URL 获取资源,例如:ADD http://example.com/file.txt /app
,会从指定 URL 下载文件并复制到容器的/app目录。
7. COPY 关键字
将宿主机资源拷贝到镜像中,只支持读取构建所在宿主机资源,更透明。COPY
与ADD
功能类似,但不会自动解压缩文件,也不能从远程读取资源。例如:COPY myfile.txt /app
,只会将宿主机上的myfile.txt
复制到容器的/app
目录。Docker 开发者推荐在满足同等功能的情况下,尽量使用COPY
指令,以避免ADD
指令可能带来的意外情况。
8. VOLUME 关键字
挂载数据卷,根据构建出来的镜像运行容器时,默认有构建时挂载信息。VOLUME
指令用于在容器中创建一个数据卷,这个数据卷可以被多个容器共享,并且数据写入不会影响镜像层。例如:VOLUME /data
,在容器运行时,可以将宿主机上的目录挂载到这个数据卷上,实现数据的持久化存储。
9. EXPOSE 关键字
指定运行容器时对外暴露的端口。EXPOSE
指令用于指定容器在运行时要对外暴露的端口。例如:EXPOSE 8080
,这只是声明了容器要暴露的端口,但要让外部能够访问这些端口,还需要在运行容器时使用-p
参数进行端口映射。
10. CMD 关键字
指定启动容器时要执行的命令,只有最后一个会生效,创建容器时指定命令会覆盖 CMD
命令。CMD
指令用于指定容器启动时默认执行的命令和参数。如果 Dockerfile 中有多个CMD指令,只有最后一个会生效。并且,在创建容器时,可以通过命令行参数覆盖CMD指定的命令。例如:CMD ["node", "app.js"]
,如果在运行容器时使用docker run <image> bash
,则会覆盖CMD指定的命令,执行bash命令。
11. ENTRYPOINT 关键字
指定启动容器时要执行的命令,可以追加命令,执行时机同 CMD。ENTRYPOINT
指令用于指定容器启动时要执行的命令,与CMD不同的是,ENTRYPOINT指定的命令不会被docker run
命令行参数覆盖,而是会将这些参数作为附加参数传递给ENTRYPOINT指定的命令。例如:ENTRYPOINT ["node"]
,如果运行容器时使用docker run <image> app.js
,则容器会执行node app.js
。
12. ARG 关键字
定义变量,和写代码时定义变量一样。ARG用于在构建镜像时定义变量,这些变量可以在后续的指令中使用。例如:ARG BUILD_ENV=production
,在RUN指令中可以使用这个变量,如RUN if [ "$BUILD_ENV" = "production" ]; then npm install --production; else npm install; fi
。
13. ONBUILD 关键字
基于父镜像构建新镜像时,父镜像的 ONBUILD 会被触发。ONBUILD
指令用于在构建新镜像时,当新镜像基于包含ONBUILD指令的父镜像构建时,父镜像的ONBUILD指令会被触发执行。例如:ONBUILD RUN echo "This is triggered when building a child image"
,当一个新镜像基于这个镜像构建时,这个命令会被执行。
三、Dockerfile 实战案例
1. 构建 Flask 镜像
使用 Python 和 Flask 构建一个简单的 Web 应用,通过 Dockerfile 构建镜像并启动容器,在网页输入服务器 IP 和端口即可看到输出。
首先创建一个 Flask 应用,以下是一个简单的示例:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
return 'Hello from Flask!'
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
然后编写 Dockerfile:
FROM python:3.10
WORKDIR /app
COPY..
RUN pip install flask
EXPOSE 5000
CMD ["python", "your_flask_app.py"]
在项目目录下执行以下命令构建镜像:
docker build -t flask-app.
启动容器:
docker run -d -p 5000:5000 flask-app
现在可以在网页中输入服务器 IP 和 5000 端口,就能看到 “Hello from Flask!” 的输出。
2. 配置 stress 容器
使用 Ubuntu 镜像构建一个 stress 容器,通过不同的启动参数来观察容器的行为。
1、构建镜像模拟测试编写 Dockerfile
FROM ubuntu:latest
RUN apt-get update && apt-get install stress
构建镜像:
docker build -t stress:latest.
启动容器:
docker run -it --rm --cpus=2 stress:latest /bin/bash
执行 stress 命令使用 stress 命令创建 4 个繁忙的进程消耗 cpu 资源:
stress -c 4
或者已存在的容器安装 stress:
安装前准备
yum install wget gcc gcc-c++ make -y
下载 stress
https://src.fedoraproject.org/repo/pkgs/stress/stress-1.0.4.tar.gz/a607afa695a511765b40993a64c6e2f4/
安装
tar zxf stress-1.0.4.tar.gz
cd stress-1.0.4
./configure
make
make install
3. 构建 SSH、Systemctl、nginx、tomcat、mysql 镜像
介绍了多种常见服务的镜像构建方法。
构建 SSH 镜像:
mkdir /opt/sshd
cd /opt/sshd
vim Dockerfile
#第一行必须指明基于的基础镜像
FROM centos:7
#作者信息
MAINTAINER this is ssh image <wl>
#镜像的操作指令
RUN yum -y update
RUN yum -y install openssh* net-tools lsof telnet passwd
RUN echo'abc1234'|passwd --stdin root
RUN sed -i 's/UsePAM yes/UsePAM no/g' /etc/ssh/sshd_config\t\t\t\t\t\t#不使用 PAM 认证
RUN sed -ri '/^session\\s+required\\s+pam_loginuid.so/s/^/#/' /etc/pam.d/sshd\t\t#取消 pam 限制
RUN ssh-keygen -t rsa -A\t\t\t\t\t\t\t\t\t\t\t\t\t\t#生成密钥认证文件
RUN mkdir -p /root/.ssh &&chown root.root /root &&chmod 700 /root/.ssh
EXPOSE 22
CMD ["/usr/sbin/sshd", "-D"]
#/usr/sbin/sshd -D 用于前台启动 sshd 服务
//生成镜像
docker build -t sshd:centos.
//启动容器并修改 root 密码
docker run -d -P sshd:centos
docker ps -a
ssh localhost -p 49155
构建 Systemctl 镜像:
mkdir /opt/systemctl
cd /opt/systemctl
vim Dockerfile
FROM sshd:centos
MAINTAINER this is systemctl image <wl>
ENV container docker
#除了 systemd-tmpfiles-setup.service,删除其它所有文件
RUN (cd /lib/systemd/system/sysinit.target.wants/;for i in *;do[$i== systemd-tmpfiles-setup.service ]||rm -f $i;done);\\rm -f /l
构建 nginx 镜像:
mkdir /opt/nginx
cd /opt/nginx
cp /opt/nginx-1.12.0.tar.gz /opt/nginx
vim Dockerfile
#基于基础镜像
FROM centos:7
#用户信息
MAINTAINER this is nginx image <zhuo>
#添加环境包
RUN yum -y update
RUN yum -y install pcre-devel zlib-devel gcc gcc-c++ make
RUN useradd -M -s /sbin/nologin nginx
#上传 nginx 软件压缩包,并解压
ADD nginx-1.12.0.tar.gz /usr/local/src/
#指定工作目录
WORKDIR /usr/local/src/nginx-1.12.0
RUN./configure \\
--prefix=/usr/local/nginx \\
--user=nginx \\
--group=nginx \\
--with-http_stub_status_module && make && make install
ENV PATH /usr/local/nginx/sbin:$PATH
#指定 http 和 https 端口
EXPOSE 80
EXPOSE 443
RUN echo "daemon off;">> /usr/local/nginx/conf/nginx.conf\t\t\t#关闭 nginx 在后台运行
#添加宿主机中 run.sh 到容器中
ADD run.sh /run.sh
RUN chmod 755 /run.sh
CMD ["/run.sh"]
编写脚本:
vim run.sh
#!/bin/bash
/usr/local/nginx/sbin/nginx
生成镜像:
docker build -t nginx:centos.
docker run -d -P nginx:centos
构建 tomcat 镜像:
mkdir /opt/tomcat
cd /opt/tomcat
cp /opt/jdk-8u91-linux-x64.tar.gz /opt/tomcat
cp /opt/apache-tomcat-8.5.16.tar.gz /opt/tomcat
vim Dockerfile
FROM centos:7
MAINTAINER this is tomcat image <zhuo>
ADD jdk-8u91-linux-x64.tar.gz /usr/local/
WORKDIR /usr/local/
RUN mv jdk1.8.0_91 /usr/local/java
ENV JAVA_HOME /usr/local/java
ENV JRE_HOME ${JAVA_HOME}/jre
ENV CLASSPATH.:${JAVA_HOME}/lib:${JRE_HOME}/lib
ENV PATH $JAVA_HOME/bin:$PATH
ADD apache-tomcat-8.5.16.tar.gz /usr/local/
WORKDIR /usr/local/
RUN mv apache-tomcat-8.5.16 /usr/local/tomcat
EXPOSE 8080
#CMD ["/usr/local/tomcat/bin/catalina.sh","run"]
ENTRYPOINT ["/usr/local/tomcat/bin/catalina.sh","run"]
生成镜像:
docker build -t tomcat:centos.
docker run -d --name tomcat01 -p 1216:8080 tomcat:centos
构建 MySQL 镜像:
mkdir /opt/mysqld
cd /opt/mysqld
vim Dockerfile
FROM centos:7
MAINTAINER this is mysql image <zhuo>
RUN yum -y install gcc gcc-c++ ncurses ncurses-devel bison cmake make
RUN useradd -M -s /sbin/nologin mysql
ADD mysql-boost-5.7.20.tar.gz /usr/local/src/
WORKDIR /usr/local/src/mysql-5.7.20/
RUN cmake \\
-DCMAKE_INSTALL_PREFIX=/usr/local/mysql \\
-DMYSQL_UNIX_ADDR=/usr/local/mysql/mysql.sock \\
-DSYSCONFDIR=/etc \\
-DSYSTEMD_PID_DIR=/usr/local/mysql \\
-DDEFAULT_CHARSET=utf8 \\
-DDEFAULT_COLLATION=utf8_general_ci \\
-DWITH_INNOBASE_STORAGE_ENGINE=1 \\
-DWITH_ARCHIVE_STORAGE_ENGINE=1 \\
-DWITH_BLACKHOLE_STORAGE_ENGINE=1 \\
-DWITH_PERFSCHEMA_STORAGE_ENGINE=1 \\
-DMYSQL_DATADIR=/usr/local/mysql/data \\
-DWITH_BOOST=boost \\
-DWITH_SYSTEMD=1&& make && make install
RUN chown -R mysql:mysql /usr/local/mysql/
RUN rm -rf /etc/my.cnf
ADD my.cnf /etc/
RUN chown mysql:mysql /etc/my.cnf
ENV PATH=/usr/local/mysql/bin:/usr/local/mysql/lib:$PATH
WORKDIR /usr/local/mysql/
RUN bin/mysqld \\
--initialize-insecure \\
--user=mysql \\
--basedir=/usr/local/mysql \\
--datadir=/usr/local/mysql/data
RUN cp /usr/local/mysql/usr/lib/systemd/system/mysqld.service /usr/lib/systemd/system/
EXPOSE 3306
ADD run.sh /usr/local/src
RUN chmod 755 /usr/local/src/run.sh
RUN sh /usr/local/src/run.sh
vim my.cnf
4. 构建自己的应用镜像
以 Tomcat 中运行程序为例,展示了如何编写 Dockerfile、构建镜像、运行镜像并部署项目。
首先准备好要部署的项目文件,假设是一个简单的 Web 应用。然后编写 Dockerfile:
FROM tomcat:latest
COPY your_project_directory /usr/local/tomcat/webapps/
在项目目录下执行以下命令构建镜像:
docker build -t your_app_image.
启动容器:
docker run -d -p 8080:8080 your_app_image
四、Dockerfile 注意事项
1. FROM 语法的选择
在选择镜像时,应遵循三个基本原则:
官方镜像优于非官方镜像,选择官方认证的镜像可以保证安全性,避免木马和侵入程序。
固定版本的 Tag,而非每次都使用latest
。进入镜像详细页面,选择Tags
选项卡,会看到很多版本的镜像。需根据项目需求选择固定版本,避免因前后版本不兼容带来的维护性和稳定性问题。
功能满足且体积小。选择基础镜像时要考虑到后续会在基础上添加很多内容和应用,镜像是分层的,体积越大复杂度越高,所以应选择体积小的镜像以保持简洁。
2. RUN 语法的优化
将多个命令放在一起执行,减少镜像层数和大小。例如,避免以下错误示范:
RUN apt-get update
RUN apt-get install wget
RUN wget https://github.com/ipinfo/cli/releases/download/ipinfo-2.0.1/ipinfo_2.0.1_linux_amd64.tar.gz
RUN tar zxf ipinfo_2.0.1_linux_amd64.tar.gz
RUN mv ipinfo_2.0.1_linux_amd64 /usr/bin/ipinfo
RUN rm -rf ipinfo_2.0.1_linux_amd64.tar.gz
正确的做法是:
RUN apt-get update && apt-get install wget && wget https://github.com/ipinfo/cli/releases/download/ipinfo-2.0.1/ipinfo_2.0.1_linux_amd64.tar.gz && tar zxf ipinfo_2.0.1_linux_amd64.tar.gz && mv ipinfo_2.0.1_linux_amd64 /usr/bin/ipinfo && rm -rf ipinfo_2.0.1_linux_amd64.tar.gz
这样用&&
将多个命令放在一起只RUN一次,可控制最终得到的镜像大小。
3. COPY 与 ADD 的区别
在复制普通文件时,COPY
和ADD
相似,但ADD
在添加 gzip 文件时可自动解压。同时,COPY
不支持从远程读取资源,而ADD可以从远程 URL 获取资源,例如:ADD http://example.com/file.txt /app
,会从指定 URL 下载文件并复制到容器的/app
目录。然而,Docker 开发者推荐在满足同等功能的情况下,尽量使用COPY
指令,以避免ADD
指令可能带来的意外情况。
4. WORKDIR 切换工作目录
WORKDIR
命令类似于 Linux 的cd
命令,为后续指令配置工作目录。例如:WORKDIR /app
,如果目录不存在,会自动创建。
5. ARG 与 ENV 命令的不同
ARG
和ENV
都可以用来设置变量,但有以下不同:
作用域不同,ARG
声明后可在构建时修改变量,而ENV
在构建过程和容器运行时都有效。
ARG
在构建阶段之外不能在后续指令中使用,如果想在构建阶段使用ARG
声明的带有默认值的变量,需要在构建阶段通过ARG
声明没有值的同名参数。
6. CMD 容器启动命令
CMD
设置容器启动时默认执行的命令,可被docker run
提供的参数覆盖,多个CMD时只有最后一个执行。例如:CMD ["node", "app.js"]
,如果在运行容器时使用docker run <image> bash
,则会覆盖CMD
指定的命令,执行bash
命令。
五、在项目中运用 Dockerfile
1. Docker 和 Dockerfile 的重要性
Docker 起源于 dotCloud 公司创始人 Solomon Hykes 在法国发起的一个公司内部项目,它是基于该公司多年云服务技术的一次革新,并于 2013 年 3 月以 Apache 2.0 授权协议开源。Docker 使用 Google 公司推出的 Go 语言进行开发实现,基于 Linux 内核的 cgroup,namespace,以及 OverlayFS 类的 Union FS 等技术,对进程进行封装隔离,属于操作系统层面的虚拟化技术。
Docker 有着诸多优势。其一,能提供一致的运行环境,便于迁移服务器,例如开发阶段的程序可以和其依赖的软件环境(如 jdk、tomcat、mysql 等)一起打包,无论在哪个机器上都能保证环境一致,避免因环境差异导致应用程序无法运行的问题;其二,它对进程进行封装隔离,容器与容器之间互不影响,可更高效地利用系统资源,比如公司举办活动面临大量流量时,运维人员只需将程序打包成镜像,然后按需运行多个容器即可,无需像以往那样耗时费力地去部署多台环境可能不一致的服务器;其三,通过镜像能够轻松复制出多个环境一致的容器。
而 Dockerfile 在自动化构建和团队协作方面发挥着关键作用。它是创建 Docker 镜像的文本文件,包含了构建镜像所需的全部指令,像选择基础镜像、安装软件包、添加文件、设置环境变量等步骤都能通过它来定义。借助 Dockerfile,开发人员无需手动一步步安装和配置环境,只要定义好容器的基础镜像、软件依赖以及启动命令等信息,就能快速自动化构建出完整的容器镜像。并且,将 Dockerfile 纳入版本控制系统(如 Git)后,团队中的每个成员都能使用相同的容器环境配置,可避免因开发环境不一致引发的各类问题,确保构建过程的可重复性和可追溯性,极大提高了团队协作效率以及应用的可靠性。
2. Docker 基础
(1)架构简介
Docker 使用客户端 - 服务器 (C/S) 架构模式,运用远程 API 来管理和创建 Docker 容器。其主要组件包括:
- Docker 客户端:是用户与 Docker 守护进程交互的命令行界面(CLI),用户通过它发出命令,这些命令会被发送到 Docker 守护进程,由守护进程执行相应操作,像创建容器、构建镜像、查看容器状态等常用操作都可通过 docker 这个常用命令行工具来实现。
- Docker 守护进程(通常是 dockerd):它是 Docker 架构的核心,负责管理容器生命周期、构建镜像、分发镜像等任务,一般以后台进程的方式运行,等待来自 Docker 客户端的 API 请求,像启动和停止容器、构建拉取推送镜像、管理容器网络和存储、查看容器日志等都由它来处理。
- Docker 镜像:可以看作是容器的只读模板,每个镜像包含了应用程序运行所需的操作系统、运行时、库、环境变量以及应用代码等内容,镜像是构建容器的基础,用户可从 Docker Hub 或私有仓库获取,也能通过 Dockerfile 自行构建,并且不同容器使用同一个镜像时,容器中的文件系统层是相互独立的。
- Docker 容器:是 Docker 镜像的运行实例,能在其中运行应用程序,提供独立的运行环境,确保应用程序在不同环境中有一致行为,容器在运行时与其他容器和宿主机共享操作系统内核,但彼此的文件系统和进程是隔离的,其生命周期由 Docker 守护进程管理,容器通常在任务完成后被销毁,不过可以在任何地方运行,因为其运行时依赖都已封装在镜像中。
- Docker 仓库:用来存储和管理 Docker 镜像,常见的有公共仓库 Docker Hub,用户能从中下载各种官方和社区维护的镜像(如 Ubuntu、Nginx、MySQL 等),也可以上传自己的镜像分享给其他人,此外,企业内部还可部署私有 Docker 仓库来管理镜像。
- Docker 网络:用于连接容器的虚拟网络,使得容器之间可以相互通信,Docker 支持多种网络类型,如 bridge、overlay、macvlan 等。
- Docker 存储驱动:用于管理 Docker 容器文件系统的组件,像 overlay2、aufs、btrfs 等都是 Docker 支持的存储驱动类型。
(2)安装设置环境
以 CentOS 7.x 系统为例,安装 Docker 的步骤如下:
卸载原始 docker(如果之前安装过):
sudo yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-engine
安装 docker 依赖:
sudo yum install -y yum-utils \
device-mapper-persistent-data \
lvm2
设置 docker 的 yum 源:
sudo yum-config-manager \
--add-repo \
https://download.docker.com/linux/centos/docker-ce.repo
安装最新版的 docker:
sudo yum install docker-ce docker-ce-cli containerd.io
启动 docker:
sudo systemctl enable docker
sudo systemctl start docker
安装完成后,可通过 docker info
命令查看 docker 信息,或使用 docker version
命令查看 docker 引擎版本来验证安装是否成功,还可以运行 sudo docker run hello-world
进行简单测试。
在其他系统(如通用所有平台的 bash 安装方式)中,Docker 官方为简化测试或开发环境下的安装流程,提供了一套便捷的安装脚本,CentOS 系统(所有 Linux 发行版本都可以使用)上可使用这套脚本安装,并且能通过 --mirror
选项使用国内源进行安装,命令如下(必须联网,sudo 表示以管理员身份运行,如果登录的就是管理员,sudo 可以省略):
curl -fsSL get.docker.com -o get-docker.sh
sudo sh get-docker.sh --mirror Aliyun
(3)常用命令基础
以下是一些 Docker 的常用命令:
- docker build:用于构建 Docker 镜像,例如
docker build -t my-image.
命令会在当前目录下寻找名为 Dockerfile 的文件(如果文件名不是 Dockerfile,则可以加上-f
参数指定文件名称),然后按照其中的指令构建出名为my-image
的镜像,最后的.
表示使用当前目录作为构建的上下文环境。 - docker run:用来运行容器,像 docker run -d -p 8080:8080 my-image 命令,其中
-d
表示让容器在后台运行,-p
参数用于进行端口映射(将宿主机的 8080 端口映射到容器内的 8080 端口),my-image 则是指定要运行的镜像名称。 - docker ps:列出正在运行的容器,若加上
-a
参数(即 docker ps -a),则会列出所有的容器(包括已停止的),会展示容器的相关信息,如容器 ID、镜像名称、创建时间、状态等。 - docker exec:可在运行的容器中执行命令,例如
docker exec -it my_container bash
命令能进入名为 my_container 的容器内部,并开启一个交互式的bash
终端,方便在容器内进行操作。 - docker pull:从 Docker Hub 或其他指定的注册表中拉取镜像,比如
docker pull ubuntu
就是拉取官方的 Ubuntu 镜像到本地。 - docker push:将本地构建好的镜像上传到 Docker Hub 或指定的私有仓库中,格式为
docker push <username>/<image_name>
,需要先登录相应仓库账号才行。
3. 编写第一个 Dockerfile
(1)选择基础镜像
选择基础镜像时通常要遵循以下原则:
首先,官方镜像优于非官方镜像,官方认证的镜像安全性更高,能避免木马和侵入程序等隐患。比如常用的 Ubuntu、Nginx、MySQL 等官方镜像,都是经过严格审核和维护的,可以放心使用。
其次,尽量选择固定版本的 Tag,而非每次都使用 latest。进入镜像详细页面,选择 Tags 选项卡,会看到很多版本的镜像,应根据项目需求挑选固定版本,否则可能因前后版本不兼容带来维护性和稳定性方面的问题,例如应用程序在开发环境使用某个依赖的最新版本正常运行,但到了生产环境由于 latest 版本更新导致出现兼容性故障。
最后,要考虑功能满足且体积小的镜像。因为镜像是分层的,基础镜像体积越大,后续添加内容和应用后复杂度越高,所以选择体积小的镜像有助于保持整个镜像简洁,像 Alpine 镜像就是以体积小且严格控制著称,常被作为基础镜像使用,例如 FROM alpine:latest 就是选择了最新版本的 Alpine 镜像作为基础来构建后续的镜像内容。
(2)编写步骤和最佳实践
下面以构建一个简单的 Python Flask 应用镜像为例,展示 Dockerfile 的编写步骤:
# 选择 Python 3.10 版本的官方镜像作为基础镜像
FROM python:3.10
# 设置容器内的工作目录为 /app,如果目录不存在会自动创建
WORKDIR /app
# 将当前目录(宿主机上 Dockerfile 所在目录)下的所有文件复制到容器内的 /app 目录下
COPY..
# 运行命令安装 Flask 依赖包
RUN pip install flask
# 声明容器运行时要对外暴露的端口为 5000
EXPOSE 5000
# 指定容器启动时要执行的命令,这里是运行名为 your_flask_app.py 的 Python 文件
CMD ["python", "your_flask_app.py"]
编写 Dockerfile 的最佳实践包括:
保持简洁易读和易于维护:尽量使用单行指令,避免过多的多行复杂指令,让 Dockerfile 的结构清晰明了,方便后续查看和修改,例如将多个相关的安装命令用 &&
连接起来放在一个 RUN 指令中,像 RUN apt-get update && apt-get install -y wget && wget https://example.com/file.tar.gz && tar zxf file.tar.gz
这样的形式,而不是分成多个 RUN
指令分别执行,有助于减少镜像层数,进而控制镜像大小。
合理利用版本控制系统:使用如 Git 这样的版本控制系统管理 Dockerfile 和相关文件,确保构建过程可重复、可追溯,团队成员可以方便地查看历史变更记录以及回滚到之前的稳定版本,便于协作开发和运维。
(3)构建过程详解
以上述 Flask 应用的 Dockerfile 为例,在项目目录下执行以下命令来构建镜像:
docker build -t flask-app.
执行该命令后,Docker 守护进程会读取 Dockerfile 中的指令,按顺序执行每一条指令。首先从指定的基础镜像(python:3.10
)启动一个临时容器,接着在这个容器内依次执行后续指令,比如执行 COPY 指令复制文件、RUN 指令安装依赖等,每执行完一条指令就会提交一个新的镜像层,最终当所有指令执行完成后,输出最终镜像的 ID,并根据 -t
参数(-t flask-app)给镜像打上指定的标签(flask-app),方便后续识别和使用该镜像。
构建好镜像后,使用以下命令启动容器:
docker run -d -p 5000:5000 flask-app
-d
参数让容器在后台运行,-p 5000:5000
则将宿主机的 5000 端口和容器内的 5000 端口进行映射,这样就能通过访问宿主机的 5000 端口来访问容器内运行的 Flask 应用了。
4. 高级 Dockerfile 技巧
(1)多阶段构建优化镜像大小和构建时间
在应用容器技术的软件开发中,控制容器镜像大小很重要。如果构建的镜像既是编译软件的环境,又是软件最终运行环境,往往很难控制镜像大小。多阶段构建就能很好地解决这个问题,它允许在不同的构建阶段使用不同的基础镜像和构建策略,优化构建过程并减少最终镜像的大小。
例如,构建一个 Go 语言应用的镜像,在编译阶段需要 Go 语言的编译环境,而运行阶段只需要一个小巧的运行环境(如 Alpine 镜像)即可。使用多阶段构建的 Dockerfile 示例如下:
# 第一阶段:构建阶段,选择 Go 语言特定版本的镜像作为基础镜像,并命名该阶段为 builder
FROM golang:1.7.3 AS builder
WORKDIR /go/src/github.com/alexellis/href-counter/
COPY app.go.
RUN go get -d -v golang.org/x/net/html \
&& CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app.
# 第二阶段:生成阶段,选择 Alpine 镜像作为基础镜像,用于最终的运行环境
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
# 从第一阶段(builder 阶段)构建好的镜像中复制编译生成的二进制文件到当前阶段的镜像中
COPY --from=builder /go/src/github.com/alexellis/href-counter/app.
CMD ["./app"]
通过这种方式,Go 语言的编译环境相关的大量依赖等内容只会存在于第一阶段的临时镜像中,最终生成的运行镜像只包含了应用运行必需的内容,大大减小了镜像体积,同时也保持了 Dockerfile 的可读性和可维护性,而且只需要一个 Dockerfile 文件就能完成整个复杂的构建过程,无需像以往那样编写多个 Dockerfile 和复杂的构建脚本。
(2)使用 ARG 和 ENV 管理变量
- ARG(构建时变量):用于在构建镜像时定义变量,这些变量可以在后续的构建指令中使用,作用域主要在构建阶段,在构建阶段之外不能在后续指令中使用。例如:
ARG BUILD_ENV=production
,可以在 RUN 指令中根据这个变量来执行不同的操作,像RUN if [ "$BUILD_ENV" = "production" ]; then npm install --production; else npm install; fi
,根据构建环境变量的值决定是安装生产环境依赖还是开发环境依赖。并且如果想在构建阶段使用 ARG 声明的带有默认值的变量,需要在构建阶段通过 ARG 声明没有值的同名参数来使用。 - ENV(环境变量):设置的变量在构建过程和容器运行时都有效,方便在容器内访问程序以及配置一些运行时需要的参数等。比如
ENV NODE_ENV production
,后续指令中就能通过$NODE_ENV
来引用这个变量,像RUN echo $NODE_ENV
这样进行操作,也可以在容器启动后运行的应用程序中使用这些环境变量进行相应的配置和逻辑处理。
合理运用 ARG
和 ENV
变量,可以让 Dockerfile 更灵活地适应不同的构建需求以及容器运行时的配置要求,提高镜像构建和应用部署的通用性和可配置性。