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 实现
- 实现基本的 OverlayFS 创建和清理
- 测试文件写入和读取
- 验证分层存储效果
验证步骤:
# 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:多层镜像支持
- 创建多个镜像层
- 实现多层 OverlayFS
- 测试层间文件覆盖
验证步骤:
# 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. 运行只读容器
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-调试技术与工具 - 调试技术详解
下一步:让我们学习命令行手撸容器,这是综合实践的开始!