HiHuo
首页
博客
手册
工具
首页
博客
手册
工具
  • 手撸容器系统

    • 完整手撸容器技术文档系列
    • 01-容器本质与基础概念
    • 02-Namespace隔离机制
    • 03-CGroup资源控制
    • 04-Capabilities与安全机制
    • 05-容器网络原理
    • 06-网络模式与实现
    • 07-CNI插件开发
    • 08-RootFS与文件系统隔离
    • 09-OverlayFS镜像分层
    • 10-命令行手撸容器
    • 11-Go实现最小容器
    • 12-Go实现完整容器
    • 13-容器生命周期管理
    • 14-调试技术与工具
    • 15-OCI规范与标准化
    • 16-进阶场景与优化
    • 常见问题与故障排查
    • 参考资料与延伸阅读

09-OverlayFS镜像分层

学习目标

  • 深入理解 OverlayFS 的工作原理和特性
  • 掌握镜像分层的概念和实现
  • 了解 OCI 镜像格式和结构
  • 能够手动操作 OverlayFS 和镜像
  • 掌握容器存储的高级技术

前置知识

  • 文件系统基础概念
  • 容器镜像原理
  • Linux 存储管理
  • JSON 数据处理

️ 一、OverlayFS 概述

1.1 OverlayFS 原理

OverlayFS 是 Linux 内核提供的联合文件系统:

graph TD
    A[OverlayFS] --> B[Lower Layer]
    A --> C[Upper Layer]
    A --> D[Work Layer]
    A --> E[Merged Layer]
    
    B --> B1[只读层]
    B --> B2[基础镜像]
    
    C --> C1[可写层]
    C --> C2[容器修改]
    
    D --> D1[工作目录]
    D --> D2[临时文件]
    
    E --> E1[合并视图]
    E --> E2[用户看到的内容]

1.2 OverlayFS 特性

特性说明优势
写时复制修改文件时复制到上层节省存储空间
层叠合并多个只读层合并为一个视图支持镜像分层
性能优化直接访问底层文件高性能
空间效率共享只读层节省磁盘空间

1.3 OverlayFS 架构

graph LR
    A[用户进程] --> B[Merged Layer]
    B --> C[Upper Layer]
    B --> D[Lower Layer]
    
    C --> C1[容器修改]
    C --> C2[新文件]
    C --> C3[删除标记]
    
    D --> D1[基础镜像]
    D --> D2[只读文件]
    
    E[Work Layer] --> F[临时文件]
    E --> G[元数据]

二、OverlayFS 操作

2.1 基本挂载命令

# 基本语法
mount -t overlay overlay \
  -o lowerdir=lower1:lower2,upperdir=upper,workdir=work \
  merged

# 参数说明
# lowerdir: 只读层,多个层用冒号分隔
# upperdir: 可写层
# workdir: 工作目录
# merged: 合并后的挂载点

2.2 OverlayFS 实战演示

2.2.1 创建基础环境

# 1. 创建目录结构
mkdir -p /tmp/overlay/{lower1,lower2,upper,work,merged}

# 2. 创建 lower1 层内容
echo "Hello from lower1" > /tmp/overlay/lower1/file1.txt
echo "Content from lower1" > /tmp/overlay/lower1/file2.txt
mkdir -p /tmp/overlay/lower1/dir1
echo "Lower1 dir1 content" > /tmp/overlay/lower1/dir1/file.txt

# 3. 创建 lower2 层内容
echo "Hello from lower2" > /tmp/overlay/lower2/file1.txt
echo "Content from lower2" > /tmp/overlay/lower2/file3.txt
mkdir -p /tmp/overlay/lower2/dir2
echo "Lower2 dir2 content" > /tmp/overlay/lower2/dir2/file.txt

# 4. 挂载 OverlayFS
mount -t overlay overlay \
  -o lowerdir=/tmp/overlay/lower1:/tmp/overlay/lower2,upperdir=/tmp/overlay/upper,workdir=/tmp/overlay/work \
  /tmp/overlay/merged

# 5. 查看合并结果
ls -la /tmp/overlay/merged/
cat /tmp/overlay/merged/file1.txt
cat /tmp/overlay/merged/file2.txt
cat /tmp/overlay/merged/file3.txt

2.2.2 写时复制测试

# 1. 修改文件(触发写时复制)
echo "Modified content" > /tmp/overlay/merged/file1.txt

# 2. 查看 upper 层
ls -la /tmp/overlay/upper/
cat /tmp/overlay/upper/file1.txt

# 3. 查看 lower 层(未改变)
cat /tmp/overlay/lower1/file1.txt
cat /tmp/overlay/lower2/file1.txt

# 4. 创建新文件
echo "New file content" > /tmp/overlay/merged/newfile.txt

# 5. 查看 upper 层
ls -la /tmp/overlay/upper/
cat /tmp/overlay/upper/newfile.txt

2.2.3 删除文件测试

# 1. 删除文件
rm /tmp/overlay/merged/file2.txt

# 2. 查看 upper 层
ls -la /tmp/overlay/upper/
# 输出: 应该有一个 file2.txt 的删除标记文件

# 3. 查看删除标记
ls -la /tmp/overlay/upper/file2.txt
# 输出: 文件类型为 c,表示删除标记

# 4. 验证删除效果
ls -la /tmp/overlay/merged/
# 输出: file2.txt 应该不存在

2.3 OverlayFS 高级操作

2.3.1 多层叠加

# 1. 创建多个 lower 层
mkdir -p /tmp/overlay/{base,app,config,merged}

# 2. 创建 base 层
echo "Base layer content" > /tmp/overlay/base/base.txt
mkdir -p /tmp/overlay/base/bin
echo "#!/bin/bash" > /tmp/overlay/base/bin/hello
echo "echo 'Hello from base'" >> /tmp/overlay/base/bin/hello
chmod +x /tmp/overlay/base/bin/hello

# 3. 创建 app 层
echo "App layer content" > /tmp/overlay/app/app.txt
mkdir -p /tmp/overlay/app/bin
echo "#!/bin/bash" > /tmp/overlay/app/bin/world
echo "echo 'Hello from app'" >> /tmp/overlay/app/bin/world
chmod +x /tmp/overlay/app/bin/world

# 4. 创建 config 层
echo "Config layer content" > /tmp/overlay/config/config.txt
mkdir -p /tmp/overlay/config/etc
echo "app_name=myapp" > /tmp/overlay/config/etc/app.conf

# 5. 挂载多层 OverlayFS
mount -t overlay overlay \
  -o lowerdir=/tmp/overlay/config:/tmp/overlay/app:/tmp/overlay/base,upperdir=/tmp/overlay/upper,workdir=/tmp/overlay/work \
  /tmp/overlay/merged

# 6. 查看合并结果
ls -la /tmp/overlay/merged/
cat /tmp/overlay/merged/base.txt
cat /tmp/overlay/merged/app.txt
cat /tmp/overlay/merged/config.txt

2.3.2 只读 OverlayFS

# 1. 创建只读 OverlayFS
mount -t overlay overlay \
  -o lowerdir=/tmp/overlay/lower1:/tmp/overlay/lower2 \
  /tmp/overlay/readonly

# 2. 尝试写入(应该失败)
echo "Test" > /tmp/overlay/readonly/test.txt
# 输出: 应该报错,因为只读

# 3. 查看只读挂载
mount | grep overlay
# 输出: 应该显示只读挂载

三、Docker 镜像分层

3.1 Docker 镜像结构

graph TD
    A[Docker 镜像] --> B[Layer 1 - Base]
    A --> C[Layer 2 - OS]
    A --> D[Layer 3 - Runtime]
    A --> E[Layer 4 - App]
    A --> F[Layer 5 - Config]
    
    B --> B1[scratch/alpine]
    C --> C1[ubuntu:20.04]
    D --> D1[python:3.9]
    E --> E1[myapp:latest]
    F --> F1[配置文件]

3.2 Docker 镜像操作

3.2.1 查看镜像分层

# 1. 查看镜像历史
docker history ubuntu:20.04

# 2. 查看镜像详细信息
docker inspect ubuntu:20.04

# 3. 查看镜像层
docker image inspect ubuntu:20.04 | jq '.[0].RootFS.Layers'

# 4. 查看镜像大小
docker images ubuntu:20.04

3.2.2 手动操作镜像层

# 1. 导出镜像
docker save ubuntu:20.04 > ubuntu.tar

# 2. 解压镜像
mkdir -p /tmp/docker-image
tar -xf ubuntu.tar -C /tmp/docker-image

# 3. 查看镜像结构
ls -la /tmp/docker-image/
cat /tmp/docker-image/manifest.json

# 4. 查看层文件
ls -la /tmp/docker-image/*/

3.3 镜像层分析

3.3.1 分析镜像层内容

#!/bin/bash
# 分析 Docker 镜像层

IMAGE="ubuntu:20.04"
TEMP_DIR="/tmp/docker-analysis"

echo "=== 分析 Docker 镜像: $IMAGE ==="

# 1. 创建临时目录
mkdir -p $TEMP_DIR

# 2. 导出镜像
echo "导出镜像..."
docker save $IMAGE > $TEMP_DIR/image.tar

# 3. 解压镜像
echo "解压镜像..."
tar -xf $TEMP_DIR/image.tar -C $TEMP_DIR

# 4. 分析 manifest
echo "=== 镜像清单 ==="
cat $TEMP_DIR/manifest.json | jq '.'

# 5. 分析配置
echo "=== 镜像配置 ==="
CONFIG_FILE=$(cat $TEMP_DIR/manifest.json | jq -r '.[0].Config')
cat $TEMP_DIR/$CONFIG_FILE | jq '.'

# 6. 分析层
echo "=== 镜像层 ==="
LAYERS=$(cat $TEMP_DIR/manifest.json | jq -r '.[0].Layers[]')
for layer in $LAYERS; do
    echo "--- 层: $layer ---"
    echo "大小: $(du -h $TEMP_DIR/$layer | cut -f1)"
    echo "内容:"
    tar -tf $TEMP_DIR/$layer | head -10
    echo ""
done

# 7. 清理
rm -rf $TEMP_DIR
echo "分析完成"

3.3.2 手动挂载镜像层

#!/bin/bash
# 手动挂载 Docker 镜像层

IMAGE="ubuntu:20.04"
TEMP_DIR="/tmp/docker-mount"
MOUNT_DIR="/tmp/docker-merged"

echo "=== 手动挂载 Docker 镜像 ==="

# 1. 创建临时目录
mkdir -p $TEMP_DIR $MOUNT_DIR

# 2. 导出镜像
docker save $IMAGE > $TEMP_DIR/image.tar
tar -xf $TEMP_DIR/image.tar -C $TEMP_DIR

# 3. 获取层信息
LAYERS=$(cat $TEMP_DIR/manifest.json | jq -r '.[0].Layers[]')
LAYER_DIRS=()

# 4. 解压层
for layer in $LAYERS; do
    LAYER_DIR="$TEMP_DIR/$(basename $layer .tar)"
    mkdir -p $LAYER_DIR
    tar -xf $TEMP_DIR/$layer -C $LAYER_DIR
    LAYER_DIRS+=($LAYER_DIR)
done

# 5. 构建 lowerdir 参数
LOWERDIR=$(IFS=:; echo "${LAYER_DIRS[*]}")

# 6. 挂载 OverlayFS
echo "挂载 OverlayFS..."
mount -t overlay overlay \
  -o lowerdir=$LOWERDIR,upperdir=$TEMP_DIR/upper,workdir=$TEMP_DIR/work \
  $MOUNT_DIR

# 7. 查看合并结果
echo "=== 合并结果 ==="
ls -la $MOUNT_DIR/
echo "=== 系统信息 ==="
cat $MOUNT_DIR/etc/os-release

# 8. 清理
umount $MOUNT_DIR
rm -rf $TEMP_DIR $MOUNT_DIR
echo "挂载完成"

四、OCI 镜像格式

4.1 OCI 镜像结构

graph TD
    A[OCI 镜像] --> B[index.json]
    A --> C[manifest.json]
    A --> D[config.json]
    A --> E[layers/]
    
    B --> B1[镜像清单]
    C --> C1[层清单]
    D --> D1[镜像配置]
    E --> E1[层文件]
    
    E --> E1[layer1.tar]
    E --> E2[layer2.tar]
    E --> E3[layer3.tar]

4.2 OCI 镜像文件格式

4.2.1 index.json 格式

{
  "schemaVersion": 2,
  "mediaType": "application/vnd.oci.image.index.v1+json",
  "manifests": [
    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "size": 1234,
      "digest": "sha256:abc123...",
      "platform": {
        "architecture": "amd64",
        "os": "linux"
      }
    }
  ]
}

4.2.2 manifest.json 格式

{
  "schemaVersion": 2,
  "mediaType": "application/vnd.oci.image.manifest.v1+json",
  "config": {
    "mediaType": "application/vnd.oci.image.config.v1+json",
    "size": 1234,
    "digest": "sha256:def456..."
  },
  "layers": [
    {
      "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
      "size": 5678,
      "digest": "sha256:ghi789..."
    }
  ]
}

4.2.3 config.json 格式

{
  "architecture": "amd64",
  "os": "linux",
  "config": {
    "Env": ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],
    "Cmd": ["/bin/bash"],
    "WorkingDir": "/",
    "User": "root"
  },
  "rootfs": {
    "type": "layers",
    "diff_ids": [
      "sha256:abc123...",
      "sha256:def456...",
      "sha256:ghi789..."
    ]
  },
  "history": [
    {
      "created": "2023-01-01T00:00:00Z",
      "created_by": "RUN apt-get update"
    }
  ]
}

4.3 OCI 镜像操作

4.3.1 创建 OCI 镜像

#!/bin/bash
# 创建 OCI 镜像

OCI_DIR="/tmp/oci-image"
LAYER_DIR="$OCI_DIR/layers"

echo "=== 创建 OCI 镜像 ==="

# 1. 创建目录结构
mkdir -p $OCI_DIR/layers

# 2. 创建基础层
echo "创建基础层..."
mkdir -p $LAYER_DIR/layer1
echo "Hello from layer1" > $LAYER_DIR/layer1/hello.txt
mkdir -p $LAYER_DIR/layer1/bin
echo "#!/bin/bash" > $LAYER_DIR/layer1/bin/hello
echo "echo 'Hello from layer1'" >> $LAYER_DIR/layer1/bin/hello
chmod +x $LAYER_DIR/layer1/bin/hello

# 3. 创建应用层
echo "创建应用层..."
mkdir -p $LAYER_DIR/layer2
echo "Hello from layer2" > $LAYER_DIR/layer2/world.txt
mkdir -p $LAYER_DIR/layer2/bin
echo "#!/bin/bash" > $LAYER_DIR/layer2/bin/world
echo "echo 'Hello from layer2'" >> $LAYER_DIR/layer2/bin/world
chmod +x $LAYER_DIR/layer2/bin/world

# 4. 创建层文件
echo "创建层文件..."
cd $LAYER_DIR
tar -czf layer1.tar.gz -C layer1 .
tar -czf layer2.tar.gz -C layer2 .

# 5. 计算层摘要
LAYER1_DIGEST=$(sha256sum layer1.tar.gz | cut -d' ' -f1)
LAYER2_DIGEST=$(sha256sum layer2.tar.gz | cut -d' ' -f1)

# 6. 创建 config.json
cat > $OCI_DIR/config.json << EOF
{
  "architecture": "amd64",
  "os": "linux",
  "config": {
    "Env": ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],
    "Cmd": ["/bin/bash"],
    "WorkingDir": "/",
    "User": "root"
  },
  "rootfs": {
    "type": "layers",
    "diff_ids": [
      "sha256:$LAYER1_DIGEST",
      "sha256:$LAYER2_DIGEST"
    ]
  },
  "history": [
    {
      "created": "2023-01-01T00:00:00Z",
      "created_by": "创建基础层"
    },
    {
      "created": "2023-01-01T00:01:00Z",
      "created_by": "创建应用层"
    }
  ]
}
EOF

# 7. 创建 manifest.json
CONFIG_DIGEST=$(sha256sum config.json | cut -d' ' -f1)
CONFIG_SIZE=$(stat -c%s config.json)
LAYER1_SIZE=$(stat -c%s layer1.tar.gz)
LAYER2_SIZE=$(stat -c%s layer2.tar.gz)

cat > $OCI_DIR/manifest.json << EOF
{
  "schemaVersion": 2,
  "mediaType": "application/vnd.oci.image.manifest.v1+json",
  "config": {
    "mediaType": "application/vnd.oci.image.config.v1+json",
    "size": $CONFIG_SIZE,
    "digest": "sha256:$CONFIG_DIGEST"
  },
  "layers": [
    {
      "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
      "size": $LAYER1_SIZE,
      "digest": "sha256:$LAYER1_DIGEST"
    },
    {
      "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
      "size": $LAYER2_SIZE,
      "digest": "sha256:$LAYER2_DIGEST"
    }
  ]
}
EOF

# 8. 创建 index.json
MANIFEST_DIGEST=$(sha256sum manifest.json | cut -d' ' -f1)
MANIFEST_SIZE=$(stat -c%s manifest.json)

cat > $OCI_DIR/index.json << EOF
{
  "schemaVersion": 2,
  "mediaType": "application/vnd.oci.image.index.v1+json",
  "manifests": [
    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "size": $MANIFEST_SIZE,
      "digest": "sha256:$MANIFEST_DIGEST",
      "platform": {
        "architecture": "amd64",
        "os": "linux"
      }
    }
  ]
}
EOF

echo "=== OCI 镜像创建完成 ==="
echo "目录: $OCI_DIR"
echo "文件:"
ls -la $OCI_DIR/

4.3.2 挂载 OCI 镜像

#!/bin/bash
# 挂载 OCI 镜像

OCI_DIR="/tmp/oci-image"
MOUNT_DIR="/tmp/oci-merged"

echo "=== 挂载 OCI 镜像 ==="

# 1. 创建挂载目录
mkdir -p $MOUNT_DIR

# 2. 解压层
LAYER_DIRS=()
for layer in $OCI_DIR/layers/*.tar.gz; do
    LAYER_NAME=$(basename $layer .tar.gz)
    LAYER_DIR="$OCI_DIR/layers/$LAYER_NAME"
    mkdir -p $LAYER_DIR
    tar -xzf $layer -C $LAYER_DIR
    LAYER_DIRS+=($LAYER_DIR)
done

# 3. 构建 lowerdir 参数
LOWERDIR=$(IFS=:; echo "${LAYER_DIRS[*]}")

# 4. 挂载 OverlayFS
mount -t overlay overlay \
  -o lowerdir=$LOWERDIR,upperdir=$OCI_DIR/upper,workdir=$OCI_DIR/work \
  $MOUNT_DIR

# 5. 查看合并结果
echo "=== 合并结果 ==="
ls -la $MOUNT_DIR/
echo "=== 文件内容 ==="
cat $MOUNT_DIR/hello.txt
cat $MOUNT_DIR/world.txt

# 6. 测试可执行文件
echo "=== 测试可执行文件 ==="
$MOUNT_DIR/bin/hello
$MOUNT_DIR/bin/world

# 7. 清理
umount $MOUNT_DIR
rm -rf $MOUNT_DIR
echo "挂载完成"

️ 五、容器存储实现

5.1 存储驱动选择

驱动特点适用场景
overlay2性能好,支持多层生产环境推荐
aufs兼容性好旧系统
devicemapper块设备存储企业环境
btrfs快照支持开发环境

5.2 存储驱动配置

5.2.1 配置 overlay2 驱动

# 1. 编辑 Docker 配置
cat > /etc/docker/daemon.json << 'EOF'
{
  "storage-driver": "overlay2",
  "storage-opts": [
    "overlay2.override_kernel_check=true"
  ]
}
EOF

# 2. 重启 Docker
systemctl restart docker

# 3. 验证配置
docker info | grep "Storage Driver"

5.2.2 查看存储使用情况

# 1. 查看存储驱动
docker system df

# 2. 查看详细存储信息
docker system df -v

# 3. 清理未使用的存储
docker system prune -a

5.3 存储优化

5.3.1 存储空间优化

# 1. 清理悬空镜像
docker image prune -f

# 2. 清理未使用的容器
docker container prune -f

# 3. 清理未使用的网络
docker network prune -f

# 4. 清理未使用的卷
docker volume prune -f

# 5. 清理构建缓存
docker builder prune -f

5.3.2 存储性能优化

# 1. 使用 SSD 存储
# 将 Docker 数据目录移动到 SSD
systemctl stop docker
mv /var/lib/docker /mnt/ssd/docker
ln -s /mnt/ssd/docker /var/lib/docker
systemctl start docker

# 2. 调整存储参数
cat >> /etc/docker/daemon.json << 'EOF'
{
  "storage-opts": [
    "overlay2.override_kernel_check=true",
    "overlay2.size=20G"
  ]
}
EOF

六、验证检查清单

基础理解

  • [ ] 理解 OverlayFS 的工作原理和特性
  • [ ] 掌握镜像分层的概念和实现
  • [ ] 了解 OCI 镜像格式和结构
  • [ ] 理解容器存储的优化方法

实践能力

  • [ ] 能够手动操作 OverlayFS
  • [ ] 能够分析和操作 Docker 镜像
  • [ ] 能够创建和挂载 OCI 镜像
  • [ ] 能够优化容器存储性能

高级技能

  • [ ] 掌握存储驱动的选择和配置
  • [ ] 能够进行存储空间优化
  • [ ] 能够处理存储相关问题
  • [ ] 理解容器存储的最佳实践

实战实现

完整的 OverlayFS 实现

根据实际开发经验,以下是完整的 OverlayFS 实现代码:

1. OverlayFS 结构体定义

type OverlayPaths struct {
    Base     string // /var/lib/toyctr/containers/<id>
    Lower    string // 只读镜像层
    Upper    string // 可写层
    Work     string // 工作目录
    Merged   string // 合并后的根目录
    ImageRef string // 镜像引用
}

type OverlayManager struct {
    baseDir    string
    imageDir   string
    containers map[string]*OverlayPaths
    mu         sync.RWMutex
}

2. 创建 OverlayFS

func (om *OverlayManager) CreateOverlay(containerID, imageName string) (*OverlayPaths, error) {
    om.mu.Lock()
    defer om.mu.Unlock()
    
    // 检查镜像是否存在
    imgLower := filepath.Join(om.imageDir, imageName, "lower")
    if _, err := os.Stat(imgLower); err != nil {
        return nil, fmt.Errorf("image not found: %s", imageName)
    }
    
    // 创建容器目录结构
    base := filepath.Join(om.baseDir, containerID)
    upper := filepath.Join(base, "upper")
    work := filepath.Join(base, "work")
    merged := filepath.Join(base, "merged")
    
    for _, d := range []string{base, upper, work, merged} {
        if err := os.MkdirAll(d, 0755); err != nil {
            return nil, fmt.Errorf("create directory %s: %w", d, err)
        }
    }
    
    // 挂载 OverlayFS
    opts := fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s", 
        imgLower, upper, work)
    if err := unix.Mount("overlay", merged, "overlay", 0, opts); err != nil {
        return nil, fmt.Errorf("mount overlay: %w", err)
    }
    
    paths := &OverlayPaths{
        Base:     base,
        Lower:    imgLower,
        Upper:    upper,
        Work:     work,
        Merged:   merged,
        ImageRef: imageName,
    }
    
    om.containers[containerID] = paths
    return paths, nil
}

3. 清理 OverlayFS

func (om *OverlayManager) CleanupOverlay(containerID string, keepWritable bool) error {
    om.mu.Lock()
    defer om.mu.Unlock()
    
    paths, exists := om.containers[containerID]
    if !exists {
        return fmt.Errorf("container %s not found", containerID)
    }
    
    // 卸载 merged 目录
    if err := unix.Unmount(paths.Merged, unix.MNT_DETACH); err != nil {
        log.Printf("Warning: failed to unmount %s: %v", paths.Merged, err)
    }
    
    if !keepWritable {
        // 删除可写层
        for _, d := range []string{paths.Merged, paths.Work, paths.Upper} {
            if err := os.RemoveAll(d); err != nil {
                log.Printf("Warning: failed to remove %s: %v", d, err)
            }
        }
    }
    
    delete(om.containers, containerID)
    return nil
}

镜像管理

1. 导入镜像

func (om *OverlayManager) ImportImage(imageName, tarPath string) error {
    imgDir := filepath.Join(om.imageDir, imageName)
    lowerDir := filepath.Join(imgDir, "lower")
    
    if err := os.MkdirAll(lowerDir, 0755); err != nil {
        return err
    }
    
    // 解压镜像到 lower 目录
    cmd := exec.Command("tar", "-xf", tarPath, "-C", lowerDir)
    if err := cmd.Run(); err != nil {
        return fmt.Errorf("extract image: %w", err)
    }
    
    // 创建镜像元数据
    metadata := map[string]interface{}{
        "name":      imageName,
        "created":   time.Now().Format(time.RFC3339),
        "lower_dir": lowerDir,
    }
    
    metaFile := filepath.Join(imgDir, "metadata.json")
    data, _ := json.MarshalIndent(metadata, "", "  ")
    return os.WriteFile(metaFile, data, 0644)
}

2. 列出镜像

func (om *OverlayManager) ListImages() ([]string, error) {
    entries, err := os.ReadDir(om.imageDir)
    if err != nil {
        return nil, err
    }
    
    var images []string
    for _, entry := range entries {
        if entry.IsDir() {
            // 检查是否有 lower 目录
            lowerDir := filepath.Join(om.imageDir, entry.Name(), "lower")
            if _, err := os.Stat(lowerDir); err == nil {
                images = append(images, entry.Name())
            }
        }
    }
    
    return images, nil
}

高级特性

1. 多层镜像支持

func (om *OverlayManager) CreateMultiLayerOverlay(containerID string, layers []string) (*OverlayPaths, error) {
    // 构建多层 lowerdir
    var lowerDirs []string
    for _, layer := range layers {
        layerPath := filepath.Join(om.imageDir, layer, "lower")
        if _, err := os.Stat(layerPath); err != nil {
            return nil, fmt.Errorf("layer %s not found", layer)
        }
        lowerDirs = append(lowerDirs, layerPath)
    }
    
    // 创建容器目录
    base := filepath.Join(om.baseDir, containerID)
    upper := filepath.Join(base, "upper")
    work := filepath.Join(base, "work")
    merged := filepath.Join(base, "merged")
    
    for _, d := range []string{base, upper, work, merged} {
        if err := os.MkdirAll(d, 0755); err != nil {
            return nil, err
        }
    }
    
    // 挂载多层 OverlayFS
    lowerdir := strings.Join(lowerDirs, ":")
    opts := fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s", 
        lowerdir, upper, work)
    
    if err := unix.Mount("overlay", merged, "overlay", 0, opts); err != nil {
        return nil, fmt.Errorf("mount multi-layer overlay: %w", err)
    }
    
    return &OverlayPaths{
        Base:     base,
        Lower:    lowerdir,
        Upper:    upper,
        Work:     work,
        Merged:   merged,
        ImageRef: strings.Join(layers, "+"),
    }, nil
}

2. 只读根文件系统

func setupReadOnlyRootfs(merged string) error {
    // 创建必要的可写目录
    writableDirs := []string{
        "/tmp", "/var/tmp", "/run", "/var/run",
        "/proc", "/sys", "/dev",
    }
    
    for _, dir := range writableDirs {
        fullPath := filepath.Join(merged, dir)
        if err := os.MkdirAll(fullPath, 0755); err != nil {
            return err
        }
    }
    
    // 挂载 tmpfs 到可写目录
    for _, dir := range []string{"/tmp", "/var/tmp", "/run", "/var/run"} {
        fullPath := filepath.Join(merged, dir)
        if err := unix.Mount("tmpfs", fullPath, "tmpfs", 0, ""); err != nil {
            return fmt.Errorf("mount tmpfs %s: %w", dir, err)
        }
    }
    
    // 将根目录 remount 为只读
    if err := unix.Mount("", merged, "", unix.MS_REMOUNT|unix.MS_RDONLY, ""); err != nil {
        return fmt.Errorf("remount rootfs readonly: %w", err)
    }
    
    return nil
}

实战练习

练习 1:基础 OverlayFS 实现

  1. 实现基本的 OverlayFS 创建和清理
  2. 测试文件写入和读取
  3. 验证分层存储效果

验证步骤:

# 1. 准备测试镜像
mkdir -p /var/lib/toyctr/images/busybox/lower
docker export $(docker create busybox) | sudo tar -C /var/lib/toyctr/images/busybox/lower -xf -

# 2. 运行容器
sudo ./toyctr -image busybox -cmd /bin/sh

# 3. 在容器内测试
# 创建文件
echo "test content" > /test_file
ls -la /test_file

# 4. 退出容器后检查
# 文件应该在 upper 层
ls -la /var/lib/toyctr/containers/*/upper/test_file

练习 2:多层镜像支持

  1. 创建多个镜像层
  2. 实现多层 OverlayFS
  3. 测试层间文件覆盖

验证步骤:

# 1. 创建基础镜像
mkdir -p /var/lib/toyctr/images/base/lower
echo "base content" > /var/lib/toyctr/images/base/lower/base_file

# 2. 创建应用镜像
mkdir -p /var/lib/toyctr/images/app/lower
echo "app content" > /var/lib/toyctr/images/app/lower/app_file

# 3. 运行多层容器
sudo ./toyctr -image "base+app" -cmd /bin/sh

# 4. 验证文件
# 应该能看到两个文件
ls -la /base_file /app_file

练习 3:只读根文件系统

  1. 实现只读根文件系统
  2. 配置可写临时目录
  3. 测试权限限制

验证步骤:

# 1. 运行只读容器
sudo ./toyctr -image busybox -readonly -cmd /bin/sh

# 2. 测试只读限制
# 应该失败
touch /readonly_test
# touch: /readonly_test: Read-only file system

# 3. 测试可写目录
# 应该成功
touch /tmp/writable_test
ls -la /tmp/writable_test

性能优化

1. 存储优化

  • 使用 SSD 存储镜像层
  • 定期清理未使用的层
  • 压缩存储镜像数据

2. 挂载优化

  • 使用 noatime 挂载选项
  • 避免频繁的 remount 操作
  • 合理设置 work 目录大小

3. 内存优化

  • 限制 OverlayFS 缓存大小
  • 使用内存映射文件
  • 定期清理页面缓存

相关链接

  • 08-RootFS与文件系统隔离 - 文件系统隔离技术
  • 10-命令行手撸容器 - 综合实践应用
  • 14-调试技术与工具 - 调试技术详解

下一步:让我们学习命令行手撸容器,这是综合实践的开始!

Prev
08-RootFS与文件系统隔离
Next
10-命令行手撸容器