Docker-镜像构建原因
在现代软件开发和运维中 Docker 已成为一种非常流行的工具 它通过容器化应用程序来简化部署过程
然而 默认的官方镜像往往只能满足基础需求 无法涵盖所有特定项目的具体要求 因此 构建自己的Docker 镜像是非常必要的
你负责维护一个线上服务,使用的是基于
nginx
的镜像,运行着一个前端静态站点,并配置了复杂的 Nginx 路由、HTTPS 证书和访问控制策略。现在公司要进行一次 灾备演练,要求你在不依赖线上环境的情况下,在测试集群中快速还原出一个“与生产几乎一致”的服务实例,用于模拟故障切换。
在某些场景下,确实不需要构建镜像
尤其是满足以下条件时:
所有配置文件都通过
volume
挂载应用数据也通过
volume
存储容器本身只是运行时环境,没有额外的修改
镜像存在的意义,是在什么情况下不可替代?
你负责一个部署内部服务,该服务基于
nginx:1.21-alpine
运行,但为了满足公司安全合规要求,你需要:
在容器中安装审计工具(如
auditd
,syslog
,osquery
)修改默认 root 用户为非 root 用户
设置 SELinux 或 AppArmor 策略
删除一些不必要的系统命令(如
telnet
,nc
,tcpdump
)强化日志输出格式并集成到统一日志平台
这些操作不能通过 volume 实现,因为它们涉及到:
容器内系统组件的变更
用户权限、安全策略等底层设置
系统二进制文件的删除或替换
完成这些操作后,希望把当前容器的状态保存下来,用于后续所有节点统一部署。
原因 | 说明 |
---|---|
系统级改动无法通过 volume 实现 | 修改用户、删除命令、安装系统组件等只能在镜像层完成 |
安全加固要求可复制、可验证 | 你不能让每个节点手动执行一遍加固脚本,必须统一标准 |
合规性要求可追溯 | 构建镜像可以打标签、记录作者和时间,方便审计和版本管理 |
镜像 vs Volume 的适用场景对比表
场景 | 是否需要构建镜像 | 使用 Volume 是否足够 |
---|---|---|
修改系统组件(如安装包、删命令) | 必须构建镜像 | 不够 |
用户权限、安全策略调整 | 必须构建镜像 | 不够 |
日志配置、访问控制等应用级配置 | 可以用 volume | 足够 |
数据持久化、网站静态资源 | 可以用 volume | 足够 |
安全合规、标准化部署 | 必须构建镜像 | 不够 |
Volume 是用来存放“可变的业务数据”,而镜像则是用来固化“不变的运行环境”。
构建 Docker 镜像的方式
编号 | 构建方式 | 说明 |
---|---|---|
1 | docker commit (容器提交) | 基于运行中的容器状态生成镜像 |
2 | Dockerfile + docker build | 通过编写 Dockerfile 文件定义镜像内容,并使用 docker build 构建 |
docker commit
docker commit 是一个用于将容器的当前状态保存为新镜像的命令
这种方法非常适合快速测试或对现有镜像进行小范围的修改
然而 它不推荐用于复杂的或需要长期维护的应用场景 因为它的透明度较低 不可重复性高 并且难以版本控制和团队协作
自定义 HTTP 镜像
在 CentOS 7 容器中安装 httpd
,并提交为新镜像 my-httpd-image
启动容器并进入交互式 shell
docker run -it --name my-httpd-container centos:7 /bin/bash
这会创建一个名为 my-httpd-container
的容器,并进入它的 bash 环境。
在容器内安装 httpd
curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
yum clean all
yum makecache
yum install -y httpd
配置启动服务
做一些简单配置,比如添加一个测试页面:
echo "Hello from Apache in Docker!" > /var/www/html/index.html
注意:容器中通常不推荐使用
systemctl
来启动服务。PID 1 问题:Docker 容器的设计初衷是运行单个进程。当你尝试在容器内运行 Systemd 或其他初始化系统作为 PID 1(即主进程),可能会遇到一些问题,包括无法正确处理信号和僵尸进程的管理。
权限问题:某些容器镜像可能并未包含完整的 Systemd 支持或未以 root 用户身份运行容器,这会导致
systemctl
命令不可用或者行为异常。复杂性增加:引入 Systemd 会增加容器的复杂性和资源消耗,违背了容器轻量化的原则。此外,这也可能导致维护困难和潜在的安全隐患。
对于大多数服务,可以直接启动它们,并以前台模式运行,而不是依赖于 Systemd。
例如,对于 Nginx 可以使用
nginx -g 'daemon off;'
,Apache 可以使用/usr/sbin/httpd -DFOREGROUND
。这种方式更符合 Docker 的设计理念,同时也简化了服务管理。
退出容器:
exit
提交容器为新镜像
docker commit my-httpd-container my-httpd-image:latest
查看镜像信息
docker images | grep my-httpd
你应该看到类似输出:
my-httpd-image latest xxxxxxxxxxxx ...
运行该镜像并启动 Apache
为了运行这个镜像并启动 Apache,可以这样运行:
docker run -d -p 8080:80 --name my-running-httpd my-httpd-image /usr/sbin/httpd -DFOREGROUND
解释:
-
-d
: 后台运行 -
-p 8080:80
: 映射宿主机端口 8080 到容器的 80 端口 -
/usr/sbin/httpd -DFOREGROUND
: 强制 Apache 前台运行(避免容器立即退出)
自定义 Nginx 镜像
在 CentOS 7 容器中安装 nginx
,并提交为新镜像 my-nginx-image
启动容器并进入交互式 shell
docker run -it --name my-nginx-container my-httpd-image /bin/bash
添加 EPEL 仓库并安装 nginx
CentOS 7 默认仓库中没有 nginx,需要先添加 EPEL 仓库:
yum install -y epel-release
yum install -y nginx
修改默认页面
同样地,可以修改默认首页内容:
echo "Hello from Nginx in Docker" > /usr/share/nginx/html/index.html
退出容器:
exit
提交容器为新镜像
docker commit my-nginx-container my-nginx-image:latest
查看镜像信息
docker images | grep my-nginx
运行该镜像并启动 Nginx
docker run -d -p 8080:80 --name my-running-nginx my-nginx-image nginx -g 'daemon off;'
解释:
-
nginx -g 'daemon off;'
表示以非守护模式运行 Nginx,确保容器不会退出
Dockerfile
Dockerfile 参考 | Docker 文档
Dockerfile 是一个文本文件 包含了一系列用于构建Docker 镜像的指令(Instructions )
它类似于shell 脚本 但不是直接运行命令 而是告诉Docker 如何一步步构建出一个定制化的镜像
一个完整的 Dockerfile 通常包含以下几个部分:
-
基础镜像
-
元数据信息
-
安装与配置
-
工作目录设置
-
文件复制
-
环境变量设置
-
端口暴露
-
启动命令
-
入口点
-
其他高级指令
指令详解
FROM
—— 指定基础镜像
作用:指定当前镜像构建所依赖的基础镜像。
格式:
FROM <image>[:<tag>]
说明:
必须是 Dockerfile 中的第一个有效指令(除非是多阶段构建)。
可以选择官方镜像(如 nginx
, python
, alpine
)或你自己定义的镜像。
示例:
FROM ubuntu:20.04
FROM centos:7
LABEL
—— 添加元数据标签
作用:为镜像添加描述性信息,比如作者、版本等。
格式:
LABEL <key>=<value> ...
说明:
替代已废弃的 MAINTAINER
指令。
可以多次使用,也可以一次写多个键值对。
示例:
LABEL maintainer="john@example.com" \
version="1.0" \
description="This is a custom Nginx image"
RUN
—— 构建时执行命令
作用:在构建过程中执行命令,通常用于安装软件包等操作。
格式:
Shell 形式(隐式调用 /bin/sh -c
):
RUN command arg1 arg2
Exec 形式(推荐):
RUN ["command", "arg1", "arg2"]
说明:
每个 RUN
指令都会生成一个新的镜像层(layer)。
建议将多个命令合并到一行中,减少层数。
示例:
RUN yum update && \
yum install -y nginx
WORKDIR
—— 设置工作目录
作用:设置后续指令(如 COPY
, ADD
, CMD
, ENTRYPOINT
)的工作目录。
格式:
WORKDIR /path/to/workdir
说明:
如果目录不存在,会自动创建。
推荐使用绝对路径。
示例:
WORKDIR /app
COPY
和 ADD
—— 复制文件到镜像中
COPY
作用:从本地主机复制文件/目录到镜像中。
优点:简单、安全、推荐使用。
格式:
COPY <src>... <dest>
示例:
COPY ./index.html /usr/share/nginx/html/index.html
ADD
作用:功能类似 COPY
,但还能自动解压 tar 文件、支持远程 URL 下载。
示例:
ADD app.tar.gz /opt/app/
ADD https://example.com/file.txt /tmp/
推荐:优先使用 COPY
,只有必要时才用 ADD
。
ENV
—— 设置环境变量
作用:设置容器内使用的环境变量。
格式:
ENV <key>=<value> ...
说明:
在后续指令中可以通过 $VAR_NAME
引用。
容器启动后也会保留这些变量。
示例:
ENV APP_HOME=/var/www/app \
NODE_ENV=production
EXPOSE
—— 声明容器监听端口
作用:声明容器在运行时会监听哪些端口。
格式:
EXPOSE <port>[/<protocol>]...
说明:
不会自动映射宿主机端口,只是文档性质的说明。
实际映射需在 docker run
时通过 -p
参数指定。
示例:
EXPOSE 80/tcp
EXPOSE 8000
示例运行时映射:
docker run -d -p 8080:80 my-nginx-image
CMD
—— 容器启动时默认执行的命令
作用:定义容器启动时默认执行的命令。
格式:
Exec 形式(推荐):
CMD ["command", "arg1", "arg2"]
Shell 形式(隐式调用 /bin/sh -c
):
CMD command arg1 arg2
说明:
可以被 docker run
命令覆盖。
一个 Dockerfile 中只能有一个有效的 CMD
(如果有多个,最后一个生效)。
示例:
CMD ["nginx", "-g", "daemon off;"]
ENTRYPOINT
—— 容器入口点(固定命令)
作用:设置容器启动时必须运行的命令,比 CMD
更“固定”。 格式:
ENTRYPOINT ["command", "param1", "param2"]
说明:
与 CMD
配合使用时,CMD
的内容作为参数传递给 ENTRYPOINT
。
不能轻易被 docker run
覆盖(除非加 --entrypoint
)。
示例:
ENTRYPOINT ["nginx"]
CMD ["-g", "daemon off;"]
含义:nginx -g daemon off;
会被执行。
指令 | 是否可省略 | 是否影响运行 | 是否可覆盖 | 典型用途 |
---|---|---|---|---|
FROM | ❌ | ✅ | ❌ | 指定基础镜像 |
LABEL | ✅ | ❌ | ❌ | 添加元数据 |
RUN | ✅ | ✅ | ❌ | 安装软件 |
CMD | ✅ | ✅ | ✅ | 设置默认启动命令 |
ENTRYPOINT | ✅ | ✅ | ❌(需特殊参数) | 固定入口命令 |
EXPOSE | ✅ | ❌ | ❌ | 声明监听端口 |
ENV | ✅ | ✅ | ❌ | 设置环境变量 |
COPY | ✅ | ✅ | ❌ | 复制文件 |
ADD | ✅ | ✅ | ❌ | 类似 COPY,支持 tar 和 URL |
WORKDIR | ✅ | ✅ | ❌ | 设置工作目录 |
构建 httpd 镜像
# 使用官方 CentOS 7 镜像作为基础镜像
FROM centos:7# 添加元数据信息
LABEL maintainer="admin" \
description="CentOS 7"# 备份原始的 CentOS-Base.repo
RUN mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.bak# 使用 curl 下载阿里云的 CentOS Base.repo 文件
RUN curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo# 清理并重建缓存
RUN yum clean all && yum makecache# 安装一个httpd
RUN yum install -y httpdEXPOSE 80
# 设置容器启动命令(示例)
CMD ["httpd", "-D", "FOREGROUND"]
构建 Docker 镜像
打开终端或命令行工具,导航到包含 Dockerfile 的目录,然后运行以下命令来构建 Docker 镜像。
请将 <your-image-name>
替换为你想要给这个镜像起的名字:
docker build -t <your-image-name> .
`-t` 参数用于指定镜像的名称。
`.` 表示当前目录是 Dockerfile 所在的位置。
构建 nginx 镜像
# 使用官方 CentOS 7 镜像作为基础镜像
FROM centos:7# 添加元数据信息
LABEL maintainer="admin" \
description="CentOS 7"# 备份原始的 CentOS-Base.repo
RUN mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.bak# 使用 curl 下载阿里云的 CentOS Base.repo 文件
RUN curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo# 清理并重建缓存
RUN yum clean all && yum makecache# 安装 EPEL 源(Nginx 在 EPEL 中)
RUN yum install -y epel-release && \
yum clean all && \
yum makecache# 安装 Nginx
RUN yum install -y nginx && \
yum clean all# 创建存放网页内容的目录
RUN mkdir -p /var/www/html# 将HTML压缩包复制到容器中
COPY html.zip /tmp/html.zip# 安装 unzip 工具
RUN yum install -y unzip && \
yum clean all# 解压HTML内容到指定目录
RUN unzip /tmp/html.zip -d /var/www/html/ && \
rm /tmp/html.zip# 修改 Nginx 默认配置文件,将根目录改为 /var/www/html
RUN sed -i 's|/usr/share/nginx/html|/var/www/html|g' /etc/nginx/nginx.conf# 暴露80端口
EXPOSE 80# 启动 Nginx 服务(前台模式)
CMD ["nginx", "-g", "daemon off;"]
构建服务镜像
FROM centos:7
LABEL maintainer="xyz" \
description="CentOS 7 with Java application"WORKDIR /usr/local/java
ADD jre-8u221-linux-x64.tar.gz ./
COPY msg.jar ./app.jarENV JAVA_HOME=/usr/local/java/jre1.8.0_221
ENV CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
ENV PATH=$JAVA_HOME/bin:$PATHEXPOSE 8080
CMD ["java", "-jar", "app.jar"]
使用云仓库
在实际应用中,大型企业和组织更倾向于使用云服务商提供的 Docker 仓库,因为它们能够提供高效、可靠的解决方案,并且减少了自行维护带来的挑战。
而对于一些中小企业或者对成本较为敏感的项目,如果团队有足够的技术能力并且对数据控制有较高要求的话,可能会选择自建私有仓库。
值得注意的是,很多情况下,企业会采用混合模式,即同时利用云服务提供商的仓库和自建的私有仓库,以便充分利用两者的优势。
例如,对外公开的服务可以托管在公有云仓库中,而内部敏感的应用则放在自建的私有仓库里。这种策略可以在保证效率的同时也兼顾了安全性和成本控制。
阿里云提供了容器镜像服务(Container Registry),允许用户方便地管理和分发 Docker 镜像。下面将详细介绍如何在阿里云容器镜像服务中进行镜像的上传(推送)与下载(拉取)。
创建命名空间和镜像仓库
登录 阿里云控制台,进入“容器镜像服务”页面。
在左侧菜单中选择“命名空间”,点击“创建命名空间”。为您的命名空间指定一个名称,例如 mycompany
。
配置 Docker 客户端认证
为了能够推送或拉取镜像,您需要登录到阿里云的 Docker Registry:
docker login --username=<your-aliyun-account> registry.cn-hangzhou.aliyuncs.com
这里的 <your-aliyun-account>
是您的阿里云账号用户名,registry.cn-hangzhou.aliyuncs.com
是默认的地域域名(您可以根据实际情况选择其他地域)。执行命令后,系统会提示输入密码,这里输入的是您的阿里云账号的密码或者是访问密钥中的 AccessKey Secret(如果启用了 RAM 用户的话)。
镜像标签与推送
假设你已经有了一个名为 myapp
的镜像,首先给它打上正确的标签以便于上传到阿里云容器镜像服务:
docker tag myapp:latest registry.cn-hangzhou.aliyuncs.com/<your-namespace>/<your-repo>:<tag>
例如:
docker tag myapp:latest registry.cn-hangzhou.aliyuncs.com/mycompany/myapp:v1
接着,使用 docker push
命令上传镜像:
docker push registry.cn-hangzhou.aliyuncs.com/mycompany/myapp:v1
从阿里云拉取镜像
当需要使用已上传的镜像时,可以通过以下命令拉取:
docker pull registry.cn-hangzhou.aliyuncs.com/mycompany/myapp:v1
这将会把指定版本的镜像下载到本地 Docker 环境中。