
以上是Docker官网架构图。
- 用户通过 Client(客户端)发送命令(如 build, pull, run),与运行在 DOCKER_HOST 上的 Docker daemon(守护进程)交互。
- Daemon 是核心,负责管理本地的 Images(镜像)和 Containers(容器),并与远端的 Registry(镜像仓库)通信,完成镜像的拉取和推送。
容器
- 是一个特殊的隔离环境,让进程可以看到环境里的有限信息,不能对外界环境施加影响
- 通过容器, 可以将引应用程序运行在一个有严密防护的 沙盒 环境中
- 容器技术的另一个本领就是为应用程序加上资源隔离,在系统里切分出一部分资源,让它只能使用指定的配额
容器与虚拟机的对比
虚拟机通过虚拟化硬件 ,在上面安装完整的操作系统后才能运行应用。这种方式会消耗大量的CPU、内存和硬盘等系统资源 ,但这些资源消耗主要用于运行额外的操作系统,并没有直接为应用带来价值。虚拟机的优点是隔离程度非常高 ,每个虚拟机之间可以做到完全无干扰。
优势
- 容器直接使用下层硬件和操作系统内核,无需额外的操作系统层,节约了CPU和内存资源,显得非常轻量级。
- 启动速度更快
- 资源利用率更高
- 镜像体积更小
劣势
- 隔离程度相对较低 。
- 虽然容器提供了良好的隔离环境,但相比虚拟机的完全隔离,容器在安全性方面仍有差距,多容器共享同一操作系统内核可能带来潜在的安全风险。
容器隔离的实现原理
容器技术通过以下三种主要机制实现隔离:
1. namespace
- 创建出独立的文件系统、主机名、进程号、网络等资源空间
- 实现了系统全局资源和进程局部资源的隔离
- Linux提供了多种namespace类型:PID(进程)、NET(网络)、IPC(进程间通信)、MNT(挂载点)、UTS(主机名)等
2. cgroup
- 控制组(control groups)实现对进程的CPU、内存等资源的优先级和配额限制
- 防止单个容器消耗所有系统资源
- 允许精细化的资源分配和管理
- 可以限制内存使用量、CPU份额、设备访问等
3. chroot
- 更改进程的根目录,限制其对文件系统的访问范围
- 提供文件系统级别的隔离
- 确保容器只能访问指定的文件和目录
Docker镜像
镜像不仅包含基本的可执行文件,还包括应用运行时的整个系统环境 (依赖库、配置文件、环境变量等)。这种设计让镜像具有非常好的跨平台便携性和兼容性 ,真正实现了"一次构建,到处运行"。
Docker指令访问官方文档(科学上网)
容器运行示例
当执行一条Docker命令时:
bash
docker run busybox echo hello world
Docker会执行以下步骤:
- 检查本地是否有busybox镜像,如果没有则从镜像仓库下载
- 提取镜像里的各种信息
- 运用namespace、cgroup、chroot技术创建出隔离环境
- 在隔离环境中运行busybox的echo命令
- 输出"hello world"字符串
镜像名称结构
Docker镜像名称由两部分组成:镜像名和标签,中间用冒号连接:
镜像名:标签
例如:
ubuntu:20.04
- 指定版本的Ubuntu镜像nginx:latest
- 最新的Nginx镜像(默认标签)my-app:v1.2.3
- 自定义应用的版本化镜像
容器化应用
应用程序不再直接和操作系统打交道,而是封装成镜像,再交给容器环境去运行。
优势
- 环境一致性:开发、测试、生产环境使用完全相同的镜像
- 版本控制:镜像版本可以追踪和管理
- 快速扩展:基于镜像可以快速创建多个容器实例
Dockerfile
Dockerfile是一个纯文本文件 ,里面记录了一系列的构建指令。每个指令都会生成一个镜像层(Layer),Docker会顺序执行这个文件里的所有步骤,最后创建出一个新的镜像。这种分层结构让镜像构建更加高效,且便于共享和存储。
RUN指令
RUN指令用于在镜像构建过程中执行任意的Shell命令
用途
- 安装应用程序依赖
- 下载文件
- 创建目录结构
- 编译程序代码
换行符编写
当需要执行多个命令时,一行放不下就需要引入换行符
- 每行的末尾使用续行符 \,命令之间用 && 来连接
dockerfile
# 不推荐的方式
RUN apt-get update && apt-get install -y git wget curl && rm -rf /var/lib/apt/lists/*
# 推荐的方式
RUN apt-get update \
&& apt-get install -y \
git \
wget \
curl \
&& rm -rf /var/lib/apt/lists/*
使用脚本文件
对于复杂的命令序列,可以创建脚本文件并在Dockerfile中使用
- 创建安装脚本
install-dependencies.sh
:
bash
#!/bin/bash
apt-get update
apt-get install -y git wget curl
rm -rf /var/lib/apt/lists/*
- 在Dockerfile中使用:
dockerfile
COPY install-dependencies.sh /tmp/
RUN chmod +x /tmp/install-dependencies.sh \
&& /tmp/install-dependencies.sh \
&& rm /tmp/install-dependencies.sh
ARG、ENV和EXPOSE指令详解
ARG指令
ARG指令用于定义在构建过程中使用的变量,这些变量只在镜像构建过程中可见,容器运行时不可见。
dockerfile
# 定义构建参数
ARG APP_VERSION=1.0.0
ARG BUILD_ENV=production
# 使用构建参数
RUN echo "Building version ${APP_VERSION} for ${BUILD_ENV} environment"
ENV指令
ENV指令用于设置环境变量,这些变量不仅在构建过程中可用,在容器运行时也会作为环境变量被应用程序使用。
dockerfile
# 设置环境变量
ENV NODE_ENV=production
ENV APP_PORT=3000
ENV DB_HOST=database
# 使用环境变量
EXPOSE ${APP_PORT}
CMD ["node", "app.js"]
EXPOSE指令
EXPOSE指令用于声明容器运行时监听的端口,这只是一个文档化的指令,并不会实际发布端口。
dockerfile
# 声明容器监听端口
EXPOSE 80/tcp
EXPOSE 443/tcp
EXPOSE 3000/udp
# 实际发布端口需要在运行容器时使用-p参数
# docker run -p 8080:80 my-app
完整示例
下面是一个完整的Node.js应用Dockerfile示例,展示了各种指令的使用:
dockerfile
# 使用官方Node.js运行时作为父镜像
FROM node:16-alpine
# 设置构建参数
ARG APP_VERSION=1.0.0
ARG NPM_REGISTRY=https://registry.npmjs.org/
# 设置环境变量
ENV NODE_ENV=production
ENV APP_PORT=3000
ENV APP_VERSION=${APP_VERSION}
# 设置工作目录
WORKDIR /app
# 复制package.json和package-lock.json
COPY package*.json ./
# 设置npm注册表并安装依赖
RUN npm config set registry ${NPM_REGISTRY} \
&& npm ci --only=production \
&& npm cache clean --force
# 复制应用程序源代码
COPY . .
# 创建非root用户运行应用(安全最佳实践)
RUN addgroup -g 1001 -S nodejs \
&& adduser -S nextjs -u 1001
# 更改文件所有权
RUN chown -R nextjs:nodejs /app
# 切换到非root用户
USER nextjs
# 暴露应用程序端口
EXPOSE ${APP_PORT}
# 定义健康检查
HEALTHCHECK --interval=30s --timeout=3s \
CMD curl -f http://localhost:${APP_PORT}/health || exit 1
# 启动应用程序
CMD ["npm", "start"]
构建和运行示例
构建镜像
使用以下命令构建Docker镜像:
bash
docker build -t my-node-app:1.0.0 .
运行容器
运行构建的镜像:
bash
docker run -d \
--name my-app \
-p 3000:3000 \
-e NODE_ENV=development \
my-node-app:1.0.0
查看运行状态
检查容器运行状态:
bash
docker ps
docker logs my-app
总结
容器技术通过namespace、cgroup和chroot实现了进程的隔离和资源限制,提供了轻量级的虚拟化解决方案。Docker作为最流行的容器平台,通过镜像和Dockerfile使应用打包和分发变得简单高效