目录

[格物致雅] Docker初见

吾尝终日而思矣,不如须臾之所学也。

Docker核心概念与实战

1. Docker - 基础概念

  • Docker概述: Docker是一种高效的软件部署技术,利用容器化技术为应用程序封装独立的运行环境。每个运行环境即为一个容器,承载容器运行的计算机称为宿主机
  • 容器与虚拟机的区别: Docker容器共享同一系统内核,轻量、启动快;虚拟机包含完整内核,占用资源多。
  • 镜像 (Image): 容器的模板,类似软件安装包。
  • 容器 (Container): 基于镜像运行的应用实例。
  • Docker仓库 (Registry): 存放和分享镜像的场所,Docker Hub为官方公共仓库。

2. Docker - 安装

  • 运行环境: Docker基于Linux容器化技术,Windows/Mac通过虚拟化运行。
  • 安装步骤:
    • Linux: 通过getdocker.com获取并执行安装脚本。
    • Windows: 启用WSL,安装Docker Desktop。
    • Mac: 根据芯片类型下载Docker Desktop。
  • 验证安装: 运行docker --version

3. Docker - 镜像管理命令

  • docker pull: 下载镜像,格式为[registry_address/][namespace/]image_name[:tag]
  • docker images: 列出本地镜像。
  • docker rmi: 删除镜像。
  • 镜像架构: 默认选择适合宿主机的架构,可指定--platform

4. Docker - 容器管理命令

  • docker run: 创建并运行容器,支持参数:
    • -d: 后台运行。
    • -p: 端口映射。
    • -v: 数据持久化。
    • -e: 设置环境变量。
    • --name: 自定义容器名称。
    • -it: 交互式终端。
    • --rm: 运行结束后自动删除。
  • 容器管理:
    • docker ps: 查看运行中的容器。
    • docker stop/start: 停止/启动容器。
    • docker logs: 查看日志。
    • docker exec: 在容器内执行命令。

5. Dockerfile - 构建镜像的蓝图

  • 基本指令:
    • FROM: 指定基础镜像。
    • WORKDIR: 设置工作目录。
    • COPY: 拷贝文件。
    • RUN: 执行命令。
    • CMD/ENTRYPOINT: 容器启动命令。
  • 构建镜像: docker build -t <镜像名称> .
  • 推送镜像: 登录Docker Hub后使用docker push

6. Docker - 网络模式

  • Bridge: 默认模式,容器通过内部IP互通。
  • Host: 容器共享宿主机网络。
  • None: 无网络模式。
  • 网络管理命令:
    • docker network ls: 列出网络。
    • docker network create: 创建自定义网络。

7. DockerCompose - 多容器编排

  • 定义: 使用docker-compose.yml文件管理多容器应用。
  • 核心元素:
    • services: 定义服务。
    • volumes: 数据持久化。
    • ports: 端口映射。
    • depends_on: 启动顺序。
  • 常用命令:
    • docker compose up: 启动服务。
    • docker compose down: 停止并删除服务。

8. Docker的核心原理

容器镜像的技术实现基于多个Linux内核特性的协同工作。在存储层面,镜像采用分层架构,每个镜像层本质上是文件系统差异的只读快照。联合文件系统(如OverlayFS、AUFS)将这些只读层与一个可写层进行目录级合并,通过写时复制机制确保底层数据的不可变性。当容器修改文件时,联合文件系统会将目标文件复制到可写层进行操作,保持基础层不变。这种设计使得镜像构建具有可追溯性,且相同基础层可在多个容器间共享。

在进程隔离层面,容器通过Linux Namespace实现资源视图的隔离。PID Namespace为进程提供独立的进程ID空间,Network Namespace创建隔离的网络栈,Mount Namespace维护独立的文件系统挂载点,UTS Namespace提供独立的主机名域名。这些Namespace共同构建了容器的隔离环境。同时,Cgroups子系统对容器可使用的CPU时间、内存容量、设备I/O等资源进行硬性限制,防止单个容器耗尽宿主资源。

容器启动时,运行时引擎首先为容器创建独立的Namespace环境,随后通过chroot或pivot_root系统调用将进程的根目录切换到镜像的联合挂载点。容器内的所有用户空间程序(包括shell、应用二进制文件和依赖库)都来自镜像层,但所有系统调用都直接由宿主机内核处理。这意味着容器必须与宿主机使用相同架构的内核,且内核版本需支持所需特性。镜像中不包含任何内核模块或内核二进制文件,仅包含在用户空间运行所需的文件系统结构。

这种架构的优势在于轻量级和高性能。由于省去了虚拟化层的指令转换开销,容器进程几乎以原生性能运行。同时,基于Namespace的隔离相比完整虚拟机具有更低的内存开销和更快的启动速度。然而,这种设计也带来安全性考量——由于共享内核,内核漏洞可能影响所有容器,且容器突破隔离的风险高于虚拟机。

一些想法

初次接触Docker时,它给我一种相当复杂且冗余的印象。我当时的想法很直接:为什么要大费周章地配置一个隔离的环境?直接在宿主机上运行程序不就完了吗?这种初见的抵触,现在回想起来,多少显得有些幼稚和可笑。

然而,随着初步的学习,我的看法发生了根本性的转变。我逐渐意识到,Docker并非无中生有地创造了复杂性;恰恰相反,它只是把那些在软件开发流程中本应由程序员负责、却又常常在混乱的日常实践中被有意无意忽略的工作,给强制性地、规范化地要求了起来。我所感受到的“复杂与冗余”,其实是一种错觉,其根源在于我们长期以来习惯了那种不规范的、充满不确定性的工作方式。一个真正严谨的软件工程实践,本就应当包含对部署环境的精确描述与隔离。这就不难理解,为什么“在我电脑上能跑”会成为软件失效的头号借口——正是因为环境一致性这件事如此关键,我们才需要像Docker这样的工具来制定严格的、可复现的标准来进行约束。如此重要的一环,过去竟被普遍忽视,现在想来,这才是最不应该的。

回想起一次出差的经历。当时,我需要在一家研究所内部、完全无法连接互联网的计算机上,部署一个机器学习训练程序。整个过程耗费了巨大的精力,经历了无数次令人沮丧的尝试后,最终定位到问题的根源,竟只是一个简单的“Python版本不对”。这件事像一记警钟,暴露了我当时在环境依赖管理方面意识的淡薄,也让我对Docker所要解决的问题,有了切肤之痛。

在进行了简单的学习与实践之后,我不禁为Docker技术之精妙而赞叹。它带给我的震撼,与当年初次掌握Git时的感觉惊人地相似——那是一种“软件开发本就该如此进行!”的豁然开朗。我们苦版本控制混乱久矣,苦部署过程繁琐久矣!Docker和Git一样,提供了一种优雅而强大的解决方案。

而且,另一件让我颇感震撼的事情是,Docker并非一项刚刚诞生的新技术,它已经有了近十年的发展历史。这不禁让我陷入沉思:是啊,就像当年林纳斯·托瓦兹为了管理Linux内核开发而亲手打造了Git一样,这个世界从来都不缺少那些在遇到痛点时,能够动手创造出革命性工具的牛人。他们面对困境时的态度并非是妥协和忍受,而是“没有合适的工具?那我就亲手造一个!”。这种魄力与才华,或许正定义了牛人与凡人之间的区别。我们大多数人只是被动地适应环境,而他们,则主动地塑造着我们所使用的工具和环境。