Docker基础概念

docker是一个用Go语言实现的开源项目,可以让我们方便的创建和使用容器,docker将程序以及程序所有的依赖都打包到docker container,这样你的程序可以在任何环境都会有一致的表现,这里程序运行的依赖也就是容器就好比集装箱,容器所处的操作系统环境就好比货船或港口,程序的表现只和集装箱有关系(容器),和集装箱放在哪个货船或者哪个港口(操作系统)没有关系。

想象一下,你要搬家,但不是搬家具,而是一个复杂的软件程序,比如 Stable Diffusion WebUI。
在没有 Docker 的世界里,你得在新“房子”(电脑)里手动安装所有东西:特定版本的 Python、特定的显卡驱动、还有一大堆它依赖的程序库…… 如果任何一个环节出错,程序就没法运行。这太痛苦了!
Docker 就是一个标准化的“魔法集装箱”。开发者会把程序本身,连同它需要的所有“水管”(依赖库)、“电路”(环境变量)、甚至“空气”(整个操作系统环境),全部打包进这个集装箱里。
这样一来,无论你把这个集装箱搬到哪个码头(任何一台安装了 Docker 的电脑),它都能原封不动地打开并完美运行。这就解决了那个最经典的程序员难题:“在我的电脑上明明是好的呀!”
在 Docker 的世界里,有两个最重要的概念:
1.镜像 (Image):可以理解为“集装箱的设计图”或“安装手册”。它是一个包含了程序和其运行环境所有指令的静态文件。镜像是只读的,你不能运行它,就像你不能直接住进一张房子的设计图里一样。
2.容器 (Container):根据“设计图”(Image)创建出来的、正在运行的“集装箱”实例。这才是真正活着的、可以交互的程序。你可以用同一份设计图(Image)盖出很多栋一模一样的房子(Container)。
所以,流程就是:开发者先构建一个镜像(Image),然后我们用这个镜像来运行一个或多个容器(Container)。

问:为什么Docker比虚拟机更好?

docker比虚拟机拥有更少的抽象层
由于docker不需要Hypervisor(虚拟机)实现硬件资源虚拟化,运⾏在docker容器上的程序直接使⽤的都是实际物理机的硬件资源。因此在CPU、内存利⽤率上docker将会在效率上有明显优势。

Docker三大底层技术

NameSpace

namespace是对全局系统资源的一种封装隔离。这样可以让不同namespace的进程拥有独立的全局系统资源。这样改变一个namespace的系统资源只会影响当前namespace中的进程,对其它namespace中的资源没有影响。
Docker主要使用了以下几种命名空间:

  • PID (Process ID) 命名空间

    • 作用:隔离进程 ID。每个容器都有自己的进程树,容器内部的进程 ID 从 1 开始编号。容器 A 无法看到容器 B 内部的进程,也无法直接操作它们。
    • 效果:使得容器内的进程看起来像是系统上的唯一进程,保证了进程之间的独立性。
  • NET (Network) 命名空间

    • 作用:隔离网络资源,包括网络接口、IP 地址、路由表、DNS 设置等。
    • 效果:每个容器都拥有独立的网络栈。容器可以有自己的 IP 地址、端口,甚至可以运行独立的防火墙规则,而不会影响宿主机或其他容器的网络配置。
  • MNT (Mount) 命名空间

    • 作用:隔离文件系统挂载点。
    • 效果:每个容器有自己的文件系统视图。容器对文件系统的挂载或卸载操作不会影响宿主机或其他容器的文件系统。这是实现容器文件系统隔离的基础。
  • UTS (UNIX Time-sharing System) 命名空间

    • 作用:隔离主机名和域名信息。
    • 效果:每个容器可以拥有自己的主机名,即使在同一台宿主机上,不同容器也可以设置不同的主机名,互不冲突。
  • IPC (Interprocess Communication) 命名空间

    • 作用:隔离进程间通信资源,如 System V IPC 共享内存、信号量等。
    • 效果:防止容器间通过 IPC 机制互相干扰,提高了安全性。
  • User (User ID) 命名空间

    • 作用:隔离用户和组 ID。
    • 效果:允许容器内的用户 ID 映射到宿主机上的不同用户 ID。这增强了安全性,即使容器内的 root 用户也可能在宿主机上是一个非特权用户。

Cgroups

控制组(Control Groups,简称 Cgroups)是 Linux 内核的另一个强大特性,它允许你限制、审计和隔离一组进程所使用的物理资源(CPU、内存、I/O、网络带宽等)。 简单来说,Cgroups 就像一个“资源管家”,确保每个容器都只使用它被分配的资源,不会耗尽整个宿主机的资源而影响其他容器或宿主机本身。

Cgroups 的主要功能包括:

  • 资源限制 (Resource Limiting)

    • 限制进程组可以使用的 CPU 时间、内存大小(包括物理内存和交换空间)、磁盘 I/O 带宽等。
    • 示例:你可以限制一个容器最多只能使用 2GB 内存和 0.5 个 CPU 核心。
  • 优先级 (Prioritization)

    • 为不同的进程组分配不同的 CPU 或磁盘 I/O 优先级。
    • 示例:你可以让一个生产环境的关键容器拥有更高的 CPU 调度优先级。
  • 审计 (Accounting)

    • 测量进程组的资源使用量。
    • 示例:统计某个容器已经使用了多少 CPU 时间和内存。
  • 控制 (Control)

    • 挂起或恢复进程组。

Union文件系统ufs:

联合文件系统(Union File Systems,如 OverlayFS、AUFS 等)是一种特殊的文件系统,它允许将不同的目录或文件系统层叠在一起,形成一个单一的逻辑文件系统视图。 这种“分层”的特性是 Docker 镜像和容器轻量级、高效存储的关键。

Docker常用操作

Docker 作为目前流行的容器化技术,其日常操作涵盖了从镜像管理到容器生命周期控制的多个方面。掌握这些常用操作是高效使用 Docker 的关键。


镜像 (Image) 常用操作

镜像是 Docker 容器的基础,它们是轻量级、独立、可执行的软件包,包含运行应用程序所需的一切。

  • 拉取镜像: 从 Docker 仓库下载镜像到本地。
    docker pull <镜像名>:<标签>
    # 示例:docker pull ubuntu:latest (下载最新版Ubuntu)
    # 示例:docker pull nginx (不指定标签默认拉取latest)
  • 查看本地镜像: 列出所有已下载到本地的镜像。
    docker images
    # 或 docker image ls
  • 构建镜像: 从 Dockerfile 创建自定义镜像。
    docker build -t <新镜像名>:<标签> .
    # 示例:docker build -t my_app:1.0 . (注意末尾的点表示Dockerfile在当前目录)
  • 删除镜像: 删除本地不再需要的镜像。
    docker rmi <镜像ID或镜像名>:<标签>
    # 示例:docker rmi ubuntu:latest
    # 强制删除 (当镜像被容器使用时可能需要): docker rmi -f <镜像ID或镜像名>
  • 推送镜像: 将本地镜像上传到 Docker 仓库。
    docker push <镜像名>:<标签>
    # 通常需要先登录:docker login

容器 (Container) 常用操作

容器是镜像的运行实例。

  • 创建并启动容器:
    docker run [选项] <镜像名>:<标签> [命令] [参数]
    # -d: 后台运行 (detached mode)
    # -p <宿主机端口>:<容器端口>: 端口映射
    # --name <容器名>: 为容器指定名称
    # -it: 交互式终端 (通常与 bash 或 sh 结合使用)
    # --rm: 容器退出时自动删除
    # -v <宿主机路径>:<容器路径>: 挂载卷
    # 示例:docker run -d -p 80:80 --name my_web_server nginx
    # 示例:docker run -it --rm ubuntu bash (进入Ubuntu容器的bash终端,退出后自动删除)
  • 查看运行中的容器:
    docker ps
  • 查看所有容器 (包括已停止的):
    docker ps -a
  • 启动容器: 启动一个已停止的容器。
    docker start <容器ID或容器名>
  • 停止容器: 停止一个正在运行的容器。
    docker stop <容器ID或容器名>
  • 重启容器: 重启一个容器。
    docker restart <容器ID或容器名>
  • 删除容器: 删除一个或多个容器。
    docker rm <容器ID或容器名>
    # 强制删除运行中的容器: docker rm -f <容器ID或容器名>
    # 删除所有已停止的容器: docker container prune
  • 进入容器内部:
    docker exec -it <容器ID或容器名> bash
    # 示例:docker exec -it my_web_server bash
  • 查看容器日志:
    docker logs <容器ID或容器名>
    # 实时追踪日志: docker logs -f <容器ID或容器名>
  • 查看容器详细信息: 获取容器的网络、存储、配置等底层信息。
    docker inspect <容器ID或容器名>
  • 查看容器资源使用情况:
    docker stats

其他常用操作

  • 清理 Docker 资源: 删除所有未被使用的容器、镜像、数据卷和网络。
    docker system prune
    # 移除所有悬空镜像: docker image prune
    # 移除所有停止的容器: docker container prune
  • 查看 Docker 版本信息:
    docker version
  • 查看 Docker 系统信息:
    docker info

镜像(images)构建:

从“Docker Hub”直接下载

有一个叫做 Docker Hub 的网站,你可以把它想象成“Docker 镜像的 GitHub”。全世界的开发者和公司都会把自己制作好的、公开的镜像放在上面供大家下载。比如 Python 官方、MySQL 官方都有自己的官方镜像。
我们用来下载镜像的命令是 docker pull

Dockerfile制作自定义镜像

有时候,我们想在官方镜像的基础上加一点自己的东西,或者有的项目比较特殊,没有现成的镜像。这时,开源项目通常会提供一个叫做 Dockerfile 的文件。

这个 Dockerfile 就是我们之前比喻的“安装手册”或“菜谱”,它是一个文本文件,里面一步步写清楚了如何制作这个镜像(比如:基于哪个基础镜像、需要安装什么软件、复制哪些代码进去等等)。我们使用 docker build 命令来读取这个文件,并创建出我们自己的镜像。

docker训练营实验作业

基础阶段实验;

exercise1:

# 练习 1:基础镜像和命令使用
#
# 要求:
# 1. 使用 ubuntu:22.04 作为基础镜像
# 2. 安装 nginx 和 curl 包
# 3. 创建一个简单的 HTML 文件,内容为 "Hello Docker!"
# 4. 将 HTML 文件复制到 nginx 的默认网站目录
# 5. 暴露 80 端口
# 6. 启动 nginx 服务
#
# 提示:
# - 使用 apt-get update 和 apt-get install 安装软件包
# - nginx 的默认网站目录是 /var/www/html/
# - 使用 CMD 或 ENTRYPOINT 启动 nginx
#
# 完成这个 Dockerfile 后,构建镜像并运行容器,访问 http://localhost:8080 应该能看到 "Hello Docker!" 页面

第一步:使用 ubuntu:22.04 作为基础镜像

FROM ubuntu:22.04

当 docker build 命令在 Dockerfile 里读到 FROM ubuntu:22.04 这行指令时,它会:
1.首先,检查你本地电脑上是否已经有了 ubuntu:22.04 这个镜像。
2.如果有,它就直接使用本地的镜像。
3.如果没有,它就会自动地去 Docker Hub 上为你 docker pull ubuntu:22.04,然后再使用它。

第二步:安装nginx和curl包:

RUN apt-get update && apt-get install -y nginx curl

第三步:创建html文件并辅助到nginx默认目录

COPY index.html /var/www/html/

第四步:暴露端口、启动服务

EXPOSE 80
CMD ["nginx","-g","daemon off;"]

最终作业测试:

# 测试命令:
# docker build -t exercise1 .
# docker run -d -p 8080:80 exercise1
# curl http://127.0.0.1:8080 | grep -q "Hello Docker"

思考:docker build ... 做了什么?

  1. 下达指令:Doc打包当前目录下的所有文件和文件夹,发送给一直在后台运行的docker守护进程()Daemon
  2. Deamon会逐步执行我们编写的Dockerfile文件.
  3. 所有dockerFile步骤执行完毕后会把所有图层和元数据打包在一起,形成一个完整统一的只读镜像。

exercise2:

实验任务:

# 练习 2:Python 应用构建
#
# 要求:
# 1. 创建一个简单的 Python Flask 应用的 Dockerfile
# 2. 实现以下功能:
# - 使用 python:3.11-slim 作为基础镜像
# - 安装应用依赖
# - 创建非 root 用户运行应用
# - 配置工作目录
# - 设置环境变量
# - 暴露应用端口
#
# 提示:
# - 使用 requirements.txt 管理依赖
# - 使用 WORKDIR 设置工作目录
# - 使用 USER 指令切换用户
# - 使用 ENV 设置环境变量
#
# 测试命令:
# docker build -t exercise2 .
# docker run -d -p 5000:5000 exercise2
# curl http://127.0.0.1:5000 | grep -q "Hello Docker"

# 在这里编写你的 Dockerfile 指令

FROM python:3.11-slim

跟练习一其实区别不大,dockerfile代码如下:

# 在这里编写你的 Dockerfile 指令

FROM python:3.11-slim
# 配置工作目录
WORKDIR /app

RUN useradd -m appuser

COPY requirements.txt .
RUN pip install -r requirements.txt

COPY . .

RUN chown -R appuser:appuser /app

USER appuser

EXPOSE 5000

CMD ["python","app.py"]

exercise3:

# 练习 3:Rust 多阶段构建
#
# 要求:
# 1. 使用多阶段构建来优化 Rust 应用的 Docker 镜像
# 2. 第一阶段:
# - 使用 rust:1.75-slim 作为基础镜像
# - 设置工作目录
# - 复制 Cargo.toml 和 Cargo.lock(如果存在)
# - 复制源代码
# - 安装 MUSL 目标环境, 支持交叉编译 rustup target add x86_64-unknown-linux-musl
# - 使用 cargo build --target x86_64-unknown-linux-musl --release 构建应用
# 3. 第二阶段:
# - 使用 alpine:latest 作为基础镜像
# - 从第一阶段复制编译好的二进制文件
# - 设置工作目录
# - 运行应用
#
# 提示:
# 1. 使用 COPY --from=builder 从构建阶段复制文件
# 2. 注意文件权限和所有权
# 3. 确保最终镜像尽可能小, 小于 20 M
#
# 测试命令:
# docker build -t rust-exercise3 .
# docker run rust-exercise3

# 在这里编写你的 Dockerfile

核心概念:什么是多阶段构建 (Multi-stage Build)?

想象一下盖房子。你需要脚手架、起重机、水泥搅拌机等各种重型工具(构建环境),但房子盖好后,你只需要房子本身,而不需要把这些工具留在里面(运行环境)。
在 Docker 中,多阶段构建也是这个道理:

  • 第一阶段(构建阶段):我们使用一个包含完整开发工具(比如 Rust 编译器 rustc 和包管理器 cargo)的 “大” 镜像。这个阶段的目标是编译源代码,生成最终的可执行文件。
  • 第二阶段(运行阶段):我们使用一个非常小、非常干净的 “小” 镜像(比如 alpine),然后只把第一阶段生成的可执行文件复制过来。
    这样做的好处是什么?
  1. 镜像极小:最终的镜像只包含你的应用程序和它运行所必需的最基本环境,完全不包含庞大的编译器和源代码。这能让镜像大小从几个 GB 减小到几十 MB。
  2. 更安全:最终的镜像里没有编译工具、没有包管理器,攻击者可利用的工具大大减少,提高了安全性。
  3. Dockerfile 更清晰:所有构建逻辑和运行逻辑被清晰地分离开来,易于理解和维护。

下面是练习代码:

# =========================================================================
# 阶段 1: 构建阶段 (Builder)
#
# 我们使用一个包含完整 Rust 工具链的镜像来编译我们的应用。
# 别名 `as builder` 让我们可以在后续阶段引用它。
# =========================================================================
FROM rust:1.75-slim as builder

# 1. 安装 MUSL 目标,这样我们可以构建一个静态链接的二进制文件。
# 静态链接意味着我们的程序不依赖于操作系统的库,因此可以在像 Alpine 这样
# 极简的系统上运行。
RUN rustup target add x86_64-unknown-linux-musl

# 2. 创建一个工作目录
WORKDIR /app

# 3. 复制依赖清单文件
# 我们先复制这两个文件,这样 Docker 就可以缓存依赖层。
# 如果 `src` 目录代码改变,但依赖没变,就不需要重新下载所有依赖。
COPY Cargo.toml Cargo.lock* ./

# 4. 复制源代码
COPY src ./src

# 5. 构建应用
# --target 指定了我们之前安装的 musl 环境
# --release 开启优化,生成用于生产环境的二进制文件
# 注意:这里假设你的应用名是 'app'。如果不是,cargo 会自动检测。
# 编译后的文件将位于 /app/target/x86_64-unknown-linux-musl/release/
RUN cargo build --target x86_64-unknown-linux-musl --release


# =========================================================================
# 阶段 2: 运行阶段 (Final Stage)
#
# 这是我们最终的镜像。我们使用一个极其精简的 Alpine 镜像作为基础,
# 然后只从“构建阶段”复制编译好的程序过来。
# =========================================================================
FROM alpine:latest

# 创建一个非 root 用户,增加安全性
RUN addgroup -S appgroup && adduser -S appuser -G appgroup

# 设置工作目录
WORKDIR /app

# 从构建阶段(builder)复制编译好的二进制文件到当前镜像
# 你需要将下面的 'your-binary-name' 替换成你在 Cargo.toml 中定义的真实二进制文件名
# 它位于 builder 阶段的 /app/target/x86_64-unknown-linux-musl/release/ 目录下
COPY --from=builder /app/target/x86_64-unknown-linux-musl/release/your-binary-name .

# 将文件的所有权交给我们的非 root 用户
RUN chown appuser:appgroup your-binary-name

# 切换到非 root 用户
USER appuser

# 设置容器启动时要执行的命令
# 运行我们刚刚复制过来的二进制文件
CMD ["./your-binary-name"]

Dockerfile文件常用指令:

下面是编写 Dockerfile 时最常用、最重要的指令清单,包含了它们的作用和常用示例。


基础与环境设置 (Base & Environment)

指令 作用 常用示例
FROM 定义该镜像的基础镜像。必须是 Dockerfile 的第一条指令 FROM python:3.11-slim
WORKDIR 设置工作目录。后续的 RUN, COPY, CMD 等指令都会在这个目录下执行。 WORKDIR /app
ENV 设置环境变量。这个变量在镜像的整个生命周期中(构建和运行时)都存在。 ENV APP_VERSION=1.0
ARG 设置构建时参数。只在 docker build 过程中有效,容器运行时不存在。 ARG BUILD_VERSION
LABEL 为镜像添加元数据,如作者、版本信息等。 LABEL maintainer="your-email@example.com"

文件操作 (File Operations)

指令 作用 常用示例
COPY 从构建上下文(你的电脑)复制文件或目录到镜像中。推荐首选 COPY . .COPY requirements.txt .
ADD 功能更强的 COPY。除了复制文件,还能自动解压 tar 压缩包和从 URL 下载文件。因其“魔法”特性,除非确实需要,否则建议用更明确的 COPY ADD https://example.com/file.tar.gz /app/
VOLUME 创建一个可以挂载的卷,用于持久化数据,绕过容器的写时复制机制。 VOLUME /var/lib/mysql

构建与执行 (Build & Execution)

指令 作用 常用示例
RUN 在构建镜像的过程中执行命令,比如安装软件包。每条 RUN 指令都会创建一个新的镜像层。 RUN apt-get update && apt-get install -y nginx
USER 切换执行后续指令以及容器启动时的用户。用于安全实践,避免使用 root 用户。 USER appuser

运行与配置 (Runtime & Configuration)

指令 作用 常用示例
EXPOSE 声明容器在运行时监听的端口。这只是一个文档性质的声明,不会实际发布端口。 EXPOSE 5000
CMD 容器启动时默认执行的命令。一个 Dockerfile 中只能有一条 CMD 生效。如果 docker run 时指定了其他命令,CMD 会被覆盖。 CMD ["python", "app.py"]
ENTRYPOINT 配置容器使其可执行。与 CMD 类似,但它不会被 docker run 的参数轻易覆盖,而是把 docker run 后的参数当作 ENTRYPOINT 的参数。 ENTRYPOINT ["java", "-jar", "/app.jar"]