Compare commits

..

10 Commits

Author SHA1 Message Date
titor-z
0ce4c931b9 添加 储程序其它子页面完成 2024-12-26 20:08:38 +08:00
titor-z
0d74ca8d4b 添加 若干存储驱动程序子页面 2024-12-26 19:37:19 +08:00
titor-z
5476fe3a35 修改 配置 2024-12-26 17:03:10 +08:00
titor-z
2682ec50b7 Edit 链接引擎概述页链接 2024-12-20 12:38:36 +08:00
titor-z
cf95804a01 ADD 选择存储驱动程序 2024-12-20 12:26:21 +08:00
titor-z
f597aa9c4a ADD 存储驱动程序概述 2024-12-20 12:23:41 +08:00
titor-z
657577678f 扔掉的东西 2024-12-20 11:45:07 +08:00
titor-z
5f3af6576c 忽略 2024-12-20 11:44:56 +08:00
titor-z
6f5c2ba42d ignore 2024-12-20 11:43:02 +08:00
titor-z
5fc3e4d4f6 忽略 2024-12-20 11:41:35 +08:00
29 changed files with 1698 additions and 14289 deletions

3
.gitignore vendored
View File

@ -55,5 +55,4 @@ Temporary Items
# Production # Production
node_modules/ node_modules/
.vitepress/cache/ /.vitepress/cache/
.vitepress/cache/**/*

View File

@ -1,31 +0,0 @@
{
"hash": "97080672",
"configHash": "e0404c79",
"lockfileHash": "dcd677dd",
"browserHash": "3de085d8",
"optimized": {
"vue": {
"src": "../../../node_modules/.store/vue@3.5.13/node_modules/vue/dist/vue.runtime.esm-bundler.js",
"file": "vue.js",
"fileHash": "ae5a8a1c",
"needsInterop": false
},
"vitepress > @vue/devtools-api": {
"src": "../../../node_modules/.store/@vue+devtools-api@7.6.8/node_modules/@vue/devtools-api/dist/index.js",
"file": "vitepress___@vue_devtools-api.js",
"fileHash": "d4b049e5",
"needsInterop": false
},
"vitepress > @vueuse/core": {
"src": "../../../node_modules/.store/@vueuse+core@11.3.0/node_modules/@vueuse/core/index.mjs",
"file": "vitepress___@vueuse_core.js",
"fileHash": "485aa128",
"needsInterop": false
}
},
"chunks": {
"chunk-XEHQW2X6": {
"file": "chunk-XEHQW2X6.js"
}
}
}

View File

@ -1,3 +0,0 @@
{
"type": "module"
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -1,343 +0,0 @@
import {
BaseTransition,
BaseTransitionPropsValidators,
Comment,
DeprecationTypes,
EffectScope,
ErrorCodes,
ErrorTypeStrings,
Fragment,
KeepAlive,
ReactiveEffect,
Static,
Suspense,
Teleport,
Text,
TrackOpTypes,
Transition,
TransitionGroup,
TriggerOpTypes,
VueElement,
assertNumber,
callWithAsyncErrorHandling,
callWithErrorHandling,
camelize,
capitalize,
cloneVNode,
compatUtils,
compile,
computed,
createApp,
createBaseVNode,
createBlock,
createCommentVNode,
createElementBlock,
createHydrationRenderer,
createPropsRestProxy,
createRenderer,
createSSRApp,
createSlots,
createStaticVNode,
createTextVNode,
createVNode,
customRef,
defineAsyncComponent,
defineComponent,
defineCustomElement,
defineEmits,
defineExpose,
defineModel,
defineOptions,
defineProps,
defineSSRCustomElement,
defineSlots,
devtools,
effect,
effectScope,
getCurrentInstance,
getCurrentScope,
getCurrentWatcher,
getTransitionRawChildren,
guardReactiveProps,
h,
handleError,
hasInjectionContext,
hydrate,
hydrateOnIdle,
hydrateOnInteraction,
hydrateOnMediaQuery,
hydrateOnVisible,
initCustomFormatter,
initDirectivesForSSR,
inject,
isMemoSame,
isProxy,
isReactive,
isReadonly,
isRef,
isRuntimeOnly,
isShallow,
isVNode,
markRaw,
mergeDefaults,
mergeModels,
mergeProps,
nextTick,
normalizeClass,
normalizeProps,
normalizeStyle,
onActivated,
onBeforeMount,
onBeforeUnmount,
onBeforeUpdate,
onDeactivated,
onErrorCaptured,
onMounted,
onRenderTracked,
onRenderTriggered,
onScopeDispose,
onServerPrefetch,
onUnmounted,
onUpdated,
onWatcherCleanup,
openBlock,
popScopeId,
provide,
proxyRefs,
pushScopeId,
queuePostFlushCb,
reactive,
readonly,
ref,
registerRuntimeCompiler,
render,
renderList,
renderSlot,
resolveComponent,
resolveDirective,
resolveDynamicComponent,
resolveFilter,
resolveTransitionHooks,
setBlockTracking,
setDevtoolsHook,
setTransitionHooks,
shallowReactive,
shallowReadonly,
shallowRef,
ssrContextKey,
ssrUtils,
stop,
toDisplayString,
toHandlerKey,
toHandlers,
toRaw,
toRef,
toRefs,
toValue,
transformVNodeArgs,
triggerRef,
unref,
useAttrs,
useCssModule,
useCssVars,
useHost,
useId,
useModel,
useSSRContext,
useShadowRoot,
useSlots,
useTemplateRef,
useTransitionState,
vModelCheckbox,
vModelDynamic,
vModelRadio,
vModelSelect,
vModelText,
vShow,
version,
warn,
watch,
watchEffect,
watchPostEffect,
watchSyncEffect,
withAsyncContext,
withCtx,
withDefaults,
withDirectives,
withKeys,
withMemo,
withModifiers,
withScopeId
} from "./chunk-XEHQW2X6.js";
export {
BaseTransition,
BaseTransitionPropsValidators,
Comment,
DeprecationTypes,
EffectScope,
ErrorCodes,
ErrorTypeStrings,
Fragment,
KeepAlive,
ReactiveEffect,
Static,
Suspense,
Teleport,
Text,
TrackOpTypes,
Transition,
TransitionGroup,
TriggerOpTypes,
VueElement,
assertNumber,
callWithAsyncErrorHandling,
callWithErrorHandling,
camelize,
capitalize,
cloneVNode,
compatUtils,
compile,
computed,
createApp,
createBlock,
createCommentVNode,
createElementBlock,
createBaseVNode as createElementVNode,
createHydrationRenderer,
createPropsRestProxy,
createRenderer,
createSSRApp,
createSlots,
createStaticVNode,
createTextVNode,
createVNode,
customRef,
defineAsyncComponent,
defineComponent,
defineCustomElement,
defineEmits,
defineExpose,
defineModel,
defineOptions,
defineProps,
defineSSRCustomElement,
defineSlots,
devtools,
effect,
effectScope,
getCurrentInstance,
getCurrentScope,
getCurrentWatcher,
getTransitionRawChildren,
guardReactiveProps,
h,
handleError,
hasInjectionContext,
hydrate,
hydrateOnIdle,
hydrateOnInteraction,
hydrateOnMediaQuery,
hydrateOnVisible,
initCustomFormatter,
initDirectivesForSSR,
inject,
isMemoSame,
isProxy,
isReactive,
isReadonly,
isRef,
isRuntimeOnly,
isShallow,
isVNode,
markRaw,
mergeDefaults,
mergeModels,
mergeProps,
nextTick,
normalizeClass,
normalizeProps,
normalizeStyle,
onActivated,
onBeforeMount,
onBeforeUnmount,
onBeforeUpdate,
onDeactivated,
onErrorCaptured,
onMounted,
onRenderTracked,
onRenderTriggered,
onScopeDispose,
onServerPrefetch,
onUnmounted,
onUpdated,
onWatcherCleanup,
openBlock,
popScopeId,
provide,
proxyRefs,
pushScopeId,
queuePostFlushCb,
reactive,
readonly,
ref,
registerRuntimeCompiler,
render,
renderList,
renderSlot,
resolveComponent,
resolveDirective,
resolveDynamicComponent,
resolveFilter,
resolveTransitionHooks,
setBlockTracking,
setDevtoolsHook,
setTransitionHooks,
shallowReactive,
shallowReadonly,
shallowRef,
ssrContextKey,
ssrUtils,
stop,
toDisplayString,
toHandlerKey,
toHandlers,
toRaw,
toRef,
toRefs,
toValue,
transformVNodeArgs,
triggerRef,
unref,
useAttrs,
useCssModule,
useCssVars,
useHost,
useId,
useModel,
useSSRContext,
useShadowRoot,
useSlots,
useTemplateRef,
useTransitionState,
vModelCheckbox,
vModelDynamic,
vModelRadio,
vModelSelect,
vModelText,
vShow,
version,
warn,
watch,
watchEffect,
watchPostEffect,
watchSyncEffect,
withAsyncContext,
withCtx,
withDefaults,
withDirectives,
withKeys,
withMemo,
withModifiers,
withScopeId
};
//# sourceMappingURL=vue.js.map

View File

@ -1,7 +0,0 @@
{
"version": 3,
"sources": [],
"sourcesContent": [],
"mappings": "",
"names": []
}

View File

@ -7,8 +7,8 @@ export default defineConfig({
themeConfig: { themeConfig: {
// https://vitepress.dev/reference/default-theme-config // https://vitepress.dev/reference/default-theme-config
nav: [ nav: [
{ text: 'Home', link: '/' }, { text: '', link: '/' },
{ text: 'Examples', link: '/markdown-examples' } { text: '官网', link: 'https://docs.docker.com' }
], ],
sidebar: { sidebar: {
@ -17,7 +17,7 @@ export default defineConfig({
text: "引擎", text: "引擎",
link: '/docs/engine/', link: '/docs/engine/',
items: [ items: [
{ {
text: '安装', text: '安装',
collapsed: true, collapsed: true,
link: '/docs/engine/about', link: '/docs/engine/about',
@ -39,8 +39,13 @@ export default defineConfig({
collapsed: true, collapsed: true,
link: '/docs/storage/drivers/', link: '/docs/storage/drivers/',
items: [ items: [
{ text: "选择一个存储驱动", link: '/docs/storage/drivers/0' }, { text: "选择存储驱动程序", link: '/docs/storage/drivers/select-storage-driver' },
{ text: "BTRFS storage driver", link: '/docs/storage/drivers/1' }, { text: "BTRFS 存储驱动程序", link: '/docs/storage/drivers/btrfs-driver/' },
{ text: "设备映射器存储驱动程序(已弃用)", link: '/docs/storage/drivers/device-mapper-driver/'},
{ text: "OverlayFS 存储驱动程序", link: '/docs/storage/drivers/overlayfs-driver/'},
{ text: "VFS 存储驱动程序", link: '/docs/storage/drivers/vfs-driver/'},
{ text: "WindowsFilter 存储驱动程序", link: '/docs/storage/drivers/windowsfilter-driver/'},
{ text: "ZFS 存储驱动程序", link: '/docs/storage/drivers/zfs-driver/'},
] ]
}, },
{ text: 'containerd 映像存储', link: '/docs/storage/container-image-store' } { text: 'containerd 映像存储', link: '/docs/storage/container-image-store' }
@ -53,7 +58,7 @@ export default defineConfig({
socialLinks: [ socialLinks: [
{ icon: 'github', link: 'https://github.com/vuejs/vitepress' } // { icon: 'github', link: 'https://github.com/vuejs/vitepress' }
] ]
} }
}) })

View File

@ -2,7 +2,7 @@
import NavList from '/components/navBtn.vue' import NavList from '/components/navBtn.vue'
const nav_lists =[ const nav_lists =[
{ {
href: "baidu.com", href: "/docs/engine/about",
title: '安装Docker引擎', title: '安装Docker引擎',
desc: '学习如何为你的发行版安装开源的Docker引擎。' desc: '学习如何为你的发行版安装开源的Docker引擎。'
}, },

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -0,0 +1,170 @@
---
outline: [2,5]
---
# BTRFS 存储驱动程序
Btrfs 是一种写入时复制文件系统,支持许多高级存储技术,非常适合 Docker。Btrfs 包含在主线 Linux 内核中。
Docker 的 `btrfs` 存储驱动程序利用许多 Btrfs 功能进行映像和容器管理。这些功能包括块级操作、精简配置、写入时复制快照和易于管理。您可以将多个物理块设备合并到一个 Btrfs 文件系统中。
本页将 Docker 的 Btrfs 存储驱动程序称为 `btrfs`,将整个 Btrfs 文件系统称为 Btrfs。
:::tip 注意
`btrfs` 存储驱动程序仅支持 SLES、Ubuntu 和 Debian 系统上的 Docker Engine CE。
:::
## 先决条件
如果您满足以下先决条件,则支持 `btrfs`
- `btrfs` 仅推荐在 Ubuntu 或 Debian 系统上与 Docker CE 一起使用。
- 更改存储驱动程序将使您已创建的任何容器在本地系统上都无法访问。使用 `docker save` 保存容器,并将现有镜像推送到 Docker Hub 或私有存储库,这样您以后就不需要重新创建它们。
- `btrfs` 需要专用的块存储设备,例如物理磁盘。此块设备必须针对 Btrfs 进行格式化,并挂载到 `/var/lib/docker/` 中。下面的配置说明将引导您完成此过程。默认情况下SLES `/` 文件系统使用 Btrfs 进行格式化,因此对于 SLES您不需要使用单独的块设备但出于性能原因您可以选择这样做。
- 内核中必须存在 `btrfs` 支持。要检查这一点,请运行以下命令:
```bash
grep btrfs /proc/filesystems
btrfs
```
- 要在操作系统级别管理 Btrfs 文件系统,您需要 `btrfs` 命令。如果您没有此命令,请安装 `btrfsprogs` package SLES`btrfs-tools` package Ubuntu 的 Package。
## 配置 Docker 以使用 btrfs 存储驱动程序
此过程在 SLES 和 Ubuntu 上基本相同。
1. 停止 Docker。
2. 将 `/var/lib/docker/` 的内容复制到备份位置,然后清空 `/var/lib/docker/` 的内容:
```bash
sudo cp -au /var/lib/docker /var/lib/docker.bk
sudo rm -rf /var/lib/docker/*
```
3. 将专用块设备格式化为 Btrfs 文件系统。此示例假定您使用的是两个名为 `/dev/xvdf``/dev/xvdg` 中。仔细检查块设备名称,因为这是一个破坏性操作。
```bash
sudo mkfs.btrfs -f /dev/xvdf /dev/xvdg
```
Btrfs 还有更多选项,包括条带化和 RAID。请参阅 [Btrfs 文档][0]。
4. 将新的 Btrfs 文件系统挂载到 `/var/lib/docker/` 挂载点。您可以指定用于创建 Btrfs 文件系统的任何块设备。
```bash
sudo mount -t btrfs /dev/xvdf /var/lib/docker
```
:::tip 注意
通过添加一个条目来使更改在重新启动后永久生效 `/etc/fstab`
:::
5. 将 `/var/lib/docker.bk` 的内容复制到 `/var/lib/docker/`
```bash
sudo cp -au /var/lib/docker.bk/* /var/lib/docker/
```
6. 配置 Docker 以使用`btrfs`存储驱动程序。即使`/var/lib/docker/`现在使用 Btrfs 文件系统,这也是必需的。编辑或创建文件`/etc/docker/daemon.json` 。如果是新文件,添加以下内容。如果它是现有文件,则仅添加键和值,如果不是结束大括号 ( `}` ) 之前的最后一行,请小心地以逗号结束该行。
```json
{
"storage-driver": "btrfs"
}
```
查看每个存储驱动程序的所有存储选项 [守护进程参考文档][1]
7. 启动 Docker。当它运行时验证`btrfs`是否被用作存储驱动程序。
```bash
docker info
Containers: 0
Running: 0
Paused: 0
Stopped: 0
Images: 0
Server Version: 17.03.1-ce
Storage Driver: btrfs
Build Version: Btrfs v4.4
Library Version: 101
<...>
```
8. 准备好后,删除 `/var/lib/docker.bk` 目录。
## 管理 Btrfs 卷
Btrfs 的优点之一是可以轻松管理 Btrfs 文件系统,无需卸载文件系统或重新启动 Docker。
当空间不足时Btrfs 会自动以大约 1 GB 的块扩展卷。
要将块设备添加到 Btrfs 卷,请使用`btrfs device add`和 `btrfs filesystem balance`命令。
```bash
sudo btrfs device add /dev/svdh /var/lib/docker
sudo btrfs filesystem balance /var/lib/docker
```
:::note 笔记
虽然您可以在 Docker 运行时执行这些操作,但性能会受到影响。最好计划一个中断窗口来平衡 Btrfs 文件系统。
:::
## `btrfs`存储驱动程序如何工作
`btrfs`存储驱动程序的工作方式与其他存储驱动程序不同,因为整个`/var/lib/docker/`目录都存储在 Btrfs 卷上。
### 磁盘上的映像和容器层
有关图像层和可写容器层的信息存储在 `/var/lib/docker/btrfs/subvolumes/` 。该子目录包含每个图像或容器层一个目录,以及从一层及其所有父层构建的统一文件系统。子卷本质上是写时复制,并从底层存储池按需分配空间。它们还可以嵌套和快照。下图显示了 4 个子卷。 “子卷 2”和“子卷 3”是嵌套的而“子卷 4”显示其自己的内部目录树。
![btfs_subvolume](./btfs_subvolume.webp)
仅图像的基础层被存储为真正的子体积。所有其他层都存储为快照,其中仅包含该层中引入的差异。您可以创建快照的快照,如下图所示。
![btfs_snapshots](./btfs_snapshots.webp)
在磁盘上,快照看起来和感觉上都像子卷,但实际上它们要小得多并且更节省空间。写时复制用于最大化存储效率并最小化层大小,并且容器可写层中的写入在块级别进行管理。下图显示了子卷及其快照共享数据。
![btfs_pool](./btfs_pool.webp)
为了最大程度地提高效率,当容器需要更多空间时,会以大约 1 GB 大小的块进行分配。
Docker 的`btrfs`存储驱动程序将每个镜像层和容器存储在其自己的 Btrfs 子卷或快照中。映像的基础层存储为子卷,而子映像层和容器存储为快照。如下图所示。
![btfs_container_layer](./btfs_container_layer.webp)
在运行`btrfs`驱动程序的 Docker 主机上创建映像和容器的高级流程如下:
1. 图像的基础层存储在 Btrfs子卷中 `/var/lib/docker/btrfs/subvolumes`
2. 后续映像层存储为父层子卷或快照的 Btrfs快照但包含该层引入的更改。这些差异存储在块级别。
3. 容器的可写层是最终镜像层的 Btrfs 快照,其中存在由运行容器引入的差异。这些差异存储在块级别。
## 容器如何与btrfs一起读写
### 读取文件
容器是镜像的节省空间的快照。快照中的元数据指向存储池中的实际数据块。这与子卷相同。因此,对快照执行的读取本质上与对子卷执行的读取相同。
### 写入文件
一般注意事项是,使用 Btrfs 写入和更新大量小文件可能会导致性能下降。
考虑容器使用 Btrfs 打开文件进行写访问的三种场景。
### 写入新文件
将新文件写入容器会调用按需分配操作,将新数据块分配给容器的快照。然后文件被写入这个新空间。按需分配操作是 Btrfs 的所有写入操作的本机操作,与将新数据写入子卷相同。因此,将新文件写入容器的快照将以本机 Btrfs 速度运行。
### 修改现有文件
更新容器中的现有文件是写时复制操作(写时重定向是 Btrfs 术语。从文件当前所在的层读取原始数据仅将修改的块写入容器的可写层。接下来Btrfs 驱动程序更新快照中的文件系统元数据以指向此新数据。此行为会产生少量开销。
### 删除文件或目录
如果容器删除了下层存在的文件或目录Btrfs 会屏蔽下层文件或目录的存在。如果容器创建一个文件然后删除它,则该操作将在 Btrfs 文件系统本身中执行,并回收空间。
## Btrfs 和 Docker 性能
`btrfs`下影响Docker性能的因素有几个 存储驱动程序。
:::note 笔记
通过使用 Docker 卷来处理写入量大的工作负载,而不是依赖于将数据存储在容器的可写层中,可以缓解许多这些因素。然而,就 Btrfs 而言Docker 卷仍然存在这些缺点,除非`/var/lib/docker/volumes/`不受 Btrfs 支持。
:::
### 页面缓存
Btrfs 不支持页面缓存共享。这意味着访问同一文件的每个进程都会将该文件复制到 Docker 主机的内存中。因此, `btrfs`驱动程序可能不是 PaaS 等高密度用例的最佳选择。
### 小写入
执行大量小写入的容器(这种使用模式也与您在短时间内启动和停止许多容器时发生的情况相匹配)可能会导致 Btrfs 块的使用不当。这可能会过早填满 Btrfs 文件系统并导致 Docker 主机上出现空间不足的情况。使用`btrfs filesys show`密切监视 Btrfs 设备上的可用空间量。
### 顺序写入
Btrfs 在写入磁盘时使用日志技术。这可能会影响顺序写入的性能,使性能降低高达 50%。
### 碎片化
碎片是 Btrfs 等写时复制文件系统的自然副产品。许多小的随机写入会使这个问题变得更加复杂。碎片可能表现为使用 SSD 时的 CPU 峰值或使用旋转磁盘时的磁头抖动。这些问题中的任何一个都会损害性能。
如果您的Linux内核版本是3.9或更高版本您可以启用autodefrag 安装 Btrfs 卷时的功能。在您自己的工作负载上测试此功能 在将其部署到生产中之前,因为一些测试显示出负面影响 关于性能。
### 固态硬盘性能
Btrfs 包括针对 SSD 介质的本机优化。要启用这些功能,请使用`-o ssd`挂载选项挂载 Btrfs 文件系统。这些优化包括通过避免优化(例如不适用于固态介质的寻道优化)来增强 SSD 写入性能。
### 经常平衡 Btrfs 文件系统
使用操作系统实用程序(例如`cron`作业)在非高峰时段定期平衡 Btrfs 文件系统。这会回收未分配的块并有助于防止文件系统不必要地填满。除非向文件系统添加额外的物理块设备,否则无法重新平衡完全满的 Btrfs 文件系统。
请参阅 [维基百科](https://btrfs.wiki.kernel.org/index.php/Balance_Filters#Balancing_to_fix_filesystem_full_errors)。
### 使用快速存储
固态硬盘 (SSD) 提供比旋转磁盘更快的读取和写入速度。
### 使用卷来处理写入量大的工作负载
卷为写入密集型工作负载提供最佳且最可预测的性能。这是因为它们绕过存储驱动程序,并且不会产生精简配置和写入时复制带来的任何潜在开销。卷还有其他好处,例如允许您在容器之间共享数据,甚至在没有正在运行的容器使用它们时也能保留数据。
## 相关信息
- [卷][20]
- [了解镜像、容器和存储驱动程序][21]
- [选择存储驱动程序][22]
[0]:https://btrfs.wiki.kernel.org/index.php/Using_Btrfs_with_Multiple_Devices
[1]:https://docs.docker.com/reference/cli/dockerd/#options-per-storage-driver
[20]:#
[21]:#
[22]:./select-storage-driver

View File

@ -0,0 +1,528 @@
---
outline: [2,5]
---
# 设备映射器存储驱动程序(已弃用)
:::info 已启用
设备映射器驱动程序 [已被弃用](https://docs.docker.com/engine/deprecated/#device-mapper-storage-driver) 并在 Docker Engine v25.0 中被删除。如果您使用设备映射器, 在升级到 Docker 之前,您必须迁移到受支持的存储驱动程序 引擎 v25.0。阅读 [Docker 存储驱动](./select-storage-driver) 支持的存储驱动程序页面。
:::
Device Mapper 是一个基于内核的框架,支持 Linux 上的许多高级卷管理技术。 Docker 的`devicemapper`存储驱动程序利用该框架的精简配置和快照功能来进行映像和容器管理。本文将 Device Mapper 存储驱动程序称为`devicemapper` ,将内核框架称为*Device Mapper* 。
对于支持它的系统, `devicemapper`支持包含在 Linux 内核中。但是,需要特定配置才能将其与 Docker 一起使用。
`devicemapper`驱动程序使用 Docker 专用的块设备,并在块级别(而不是文件级别)运行。这些设备可以通过向 Docker 主机添加物理存储来扩展,并且它们的性能比在操作系统 (OS) 级别使用文件系统更好。
## 先决条件
- `devicemapper` 在 Docker Engine - 在 CentOS、Fedora、SLES 15、Ubuntu、Debian 或 RHEL 上运行的社区受支持。
- `devicemapper` 需要安装 `lvm2``device-mapper-persistent-data` 包。
- 更改存储驱动程序将使您已创建的任何容器在本地系统上都无法访问。使用 `docker save` 保存容器,并将现有镜像推送到 Docker Hub 或私有存储库,这样您以后就不需要重新创建它们。
## 使用 `devicemapper` 存储驱动程序配置 Docker
在执行这些过程之前,您必须首先满足所有 [先决条件](#先决条件)。
### 配置 `loop-lvm` 模式进行测试
此配置仅适用于测试。 `loop-lvm`模式利用“环回”机制,允许读取和写入本地磁盘上的文件,就像它们是实际的物理磁盘或块设备一样。然而,环回机制的添加以及与操作系统文件系统层的交互意味着 IO 操作可能会很慢并且会占用大量资源。使用环回设备也会引入竞争条件。然而,设置`loop-lvm`模式可以帮助识别基本问题例如缺少用户空间包、内核驱动程序等然后再尝试启用direct-lvm模式所需的更复杂的设置。 `loop-lvm`模式应该 因此仅用于在配置之前执行基本测试 `direct-lvm` ​​。
对于生产系统,请参阅 [为生产配置 direct-lvm 模式](#为生产配置 direct-lvm 模式)。
1. 停止 Docker
```bash
sudo systemctl stop docker
```
2. 编辑`/etc/docker/daemon.json` 。如果尚不存在,请创建它。假设该文件为空,添加以下内容。
```json
{
"storage-driver": "devicemapper"
}
```
查看每个存储驱动程序的所有存储选项 [守护进程参考文档](https://docs.docker.com/reference/cli/dockerd/#options-per-storage-driver)
如果`daemon.json`文件包含格式错误的 JSON则 Docker 不会启动。
3. 启动 Docker。
```bash
sudo systemctl start docker
```
4. 验证守护程序是否正在使用`devicemapper`存储驱动程序。使用 `docker info`命令并查找`Storage Driver` 。
```bash
docker info
Containers: 0
Running: 0
Paused: 0
Stopped: 0
Images: 0
Server Version: 17.03.1-ce
Storage Driver: devicemapper
Pool Name: docker-202:1-8413957-pool
Pool Blocksize: 65.54 kB
Base Device Size: 10.74 GB
Backing Filesystem: xfs
Data file: /dev/loop0
Metadata file: /dev/loop1
Data Space Used: 11.8 MB
Data Space Total: 107.4 GB
Data Space Available: 7.44 GB
Metadata Space Used: 581.6 KB
Metadata Space Total: 2.147 GB
Metadata Space Available: 2.147 GB
Thin Pool Minimum Free Space: 10.74 GB
Udev Sync Supported: true
Deferred Removal Enabled: false
Deferred Deletion Enabled: false
Deferred Deleted Device Count: 0
Data loop file: /var/lib/docker/devicemapper/data
Metadata loop file: /var/lib/docker/devicemapper/metadata
Library Version: 1.02.135-RHEL7 (2016-11-16)
<...>
```
该主机在`loop-lvm`模式下运行,生产系统不支持该模式。 `Data loop file`这一事实表明了这一点 和`Metadata loop file`位于以下文件上 `/var/lib/docker/devicemapper` 。这些是环回安装的 稀疏文件。对于生产系统,请参阅 [为生产配置 direct-lvm 模式](#为生产配置-direct-lvm-模式)。
### 为生产配置 direct-lvm 模式
使用`devicemapper`存储驱动程序的生产主机必须使用`direct-lvm` 模式。此模式使用块设备创建精简池。这比 使用环回设备,更有效地使用系统资源,并阻止 设备可以根据需要增长。然而,比`loop-lvm`需要更多的设置 模式。
当你满足了 [先决条件](#先决条件),请按照以下步骤配置 Docker 以在中使用`devicemapper`存储驱动程序 `direct-lvm`模式。
:::warning 警告
更改存储驱动程序会使您已创建的任何容器在本地系统上无法访问。使用`docker save`保存容器,并将现有镜像推送到 Docker Hub 或私有存储库,这样您以后就不需要重新创建它们。
:::
### 允许Docker配置direct-lvm模式
Docker可以为你管理块设备简化`direct-lvm`的配置 模式。这仅适用于新的 Docker 设置。你只能使用一个 单块设备。如果需要使用多个块设备, 而是[手动配置 direct-lvm 模式](#手动配置 direct-lvm 模式)。提供以下新配置选项:
选项 | 描述 | 必需的? | 默认 | 例子
| - | - | - | - | - |
`dm.directlvm_device` | 要为`direct-lvm`配置的块设备的路径。| 是 | | `dm.directlvm_device="/dev/xvdf"`
`dm.thinp_percent` | 传入的块设备中用于存储的空间百分比。 | 不 | 95 | `dm.thinp_percent=95`
`dm.thinp_metapercent` | 传入的块设备中用于元数据存储的空间百分比。| 不 | 1 | `dm.thinp_metapercent=1`
`dm.thinp_autoextend_threshold` | lvm 应自动扩展精简池的阈值(占总存储空间的百分比)。 | 不 | 80 | `dm.thinp_autoextend_threshold=80`
`dm.thinp_autoextend_percent` | 触发自动扩展时精简池增加的百分比。| 不 | 20 | `dm.thinp_autoextend_percent=20`
`dm.directlvm_device_force` | 是否格式化块设备即使其上已存在文件系统。如果设置为false并且存在文件系统则会记录错误并且文件系统保持不变。 | 不 | false | `dm.directlvm_device_force=true`
编辑`daemon.json`文件并设置适当的选项,然后重新启动 Docker 以使更改生效。以下`daemon.json`配置设置上表中的所有选项。
```json
{
"storage-driver": "devicemapper",
"storage-opts": [
"dm.directlvm_device=/dev/xdf",
"dm.thinp_percent=95",
"dm.thinp_metapercent=1",
"dm.thinp_autoextend_threshold=80",
"dm.thinp_autoextend_percent=20",
"dm.directlvm_device_force=false"
]
}
```
查看每个存储驱动程序的所有存储选项 [守护进程参考文档](https://docs.docker.com/reference/cli/dockerd/#options-per-storage-driver)
重新启动 Docker 以使更改生效。 Docker 调用命令来为您配置块设备。
:::warning 警告
不支持在 Docker 为您准备好块设备后更改这些值,并且会导致错误。
:::
你还需要 [执行定期维护任务][定期维护任务]。
### 手动配置direct-lvm模式
以下过程创建一个配置为精简池的逻辑卷,以用作存储池的后备。它假设您在`/dev/xvdf`处有一个备用块设备,并且有足够的可用空间来完成任务。您的环境中的设备标识符和卷大小可能有所不同,您应该在整个过程中替换您自己的值。该过程还假设 Docker 守护程序处于`stopped`状态。
1. 确定您要使用的块设备。该设备位于 `/dev/` (例如`/dev/xvdf` )并且需要足够的可用空间来存储主机运行的工作负载的映像和容器层。固态硬盘是理想的选择。
2. 停止 Docker。
```bash
sudo systemctl stop docker
```
3. 安装以下软件包:
- RHEL / CentOS `device-mapper-persistent-dat`a 、 `lvm2`和所有依赖项
- Ubuntu / Debian / SLES 15 `thin-provisioning-tools``lvm2`和所有依赖项
4. 使用以下命令在步骤 1 中的块设备上创建物理卷 `pvcreate`命令。将`/dev/xvdf`替换为您的设备名称。
:::warning 警告
接下来的几个步骤具有破坏性,因此请确保您指定了正确的设备。
:::
```bash
sudo pvcreate /dev/xvdf
Physical volume "/dev/xvdf" successfully created.
```
5. 使用`vgcreate`在同一设备上创建`docker`卷组 命令。
```bash
sudo vgcreate docker /dev/xvdf
Volume group "docker" successfully created
```
6. 使用以下命令创建两个名为`thinpool`和`thinpoolmeta`的逻辑卷 `lvcreate`命令。最后一个参数指定可用空间量,以允许在空间不足时自动扩展数据或元数据,作为临时的权宜之计。这些是推荐值。
```bash
sudo lvcreate --wipesignatures y -n thinpool docker -l 95%VG
Logical volume "thinpool" created.
sudo lvcreate --wipesignatures y -n thinpoolmeta docker -l 1%VG
Logical volume "thinpoolmeta" created.
```
7. 使用`lvconvert`命令将卷转换为精简池以及精简池元数据的存储位置。
```bash
sudo lvconvert -y \
--zero n \
-c 512K \
--thinpool docker/thinpool \
--poolmetadata docker/thinpoolmeta
WARNING: Converting logical volume docker/thinpool and docker/thinpoolmeta to
thin pool's data and metadata volumes with metadata wiping.
THIS WILL DESTROY CONTENT OF LOGICAL VOLUME (filesystem etc.)
Converted docker/thinpool to thin pool.
```
8. 通过`lvm`配置文件配置精简池的自动扩展。
```bash
sudo vi /etc/lvm/profile/docker-thinpool.profile
```
9. 指定`thin_pool_autoextend_threshold`和`thin_pool_autoextend_percent` 价值观。
`thin_pool_autoextend_threshold`是`lvm`之前使用的空间百分比 尝试自动扩展可用空间100 = 禁用,不推荐)。
`thin_pool_autoextend_percent`是自动扩展时添加到设备的空间量0 = 禁用)。
下面的示例在磁盘使用率达到 80% 时增加 20% 的容量。
```text
activation {
thin_pool_autoextend_threshold=80
thin_pool_autoextend_percent=20
}
```
保存文件。
10. 使用`lvchange`命令应用 LVM 配置文件。
```bash
sudo lvchange --metadataprofile docker-thinpool docker/thinpool
Logical volume docker/thinpool changed.
```
11. 确保启用逻辑卷监控。
```bash
sudo lvs -o+seg_monitor
LV VG Attr LSize Pool Origin Data% Meta% Move Log Cpy%Sync Convert Monitor
thinpool docker twi-a-t--- 95.00g 0.00 0.01 not monitored
```
如果`Monitor`列中的输出报告如上所示,则卷为 `not monitored` ,则需要显式启用监控。如果没有此步骤,无论应用的配置文件中的任何设置如何,都不会发生逻辑卷的自动扩展。
```bash
sudo lvchange --monitor y docker/thinpool
```
通过运行 `sudo lvs -o+seg_monitor` 命令。`Monitor` 列现在应报告正在监控逻辑卷。
12. 如果您以前曾在此主机上运行过 Docker或者如果 `/var/lib/docker/` 存在,将其移开,以便 Docker 可以使用新的 LVM 池 存储 Image 和 Containers 的内容。
```bash
sudo su -
# mkdir /var/lib/docker.bk
# mv /var/lib/docker/* /var/lib/docker.bk
# exit
```
如果以下任何步骤失败,并且您需要恢复,您可以删除 `/var/lib/docker` 并将其替换为 `/var/lib/docker.bk`
13. 编辑 `/etc/docker/daemon.json` 并配置 `devicemapper` 存储驱动程序。如果文件以前为空,则它现在应包含以下内容:
```bash
{
"storage-driver": "devicemapper",
"storage-opts": [
"dm.thinpooldev=/dev/mapper/docker-thinpool",
"dm.use_deferred_removal=true",
"dm.use_deferred_deletion=true"
]
}
```
14. 启动 Docker。
systemd:
```bash
sudo systemctl start docker
```
service:
```bash
sudo service docker start
```
15. 使用 `docker info` 验证 Docker 是否正在使用新配置。
```bash
docker info
Containers: 0
Running: 0
Paused: 0
Stopped: 0
Images: 0
Server Version: 17.03.1-ce
Storage Driver: devicemapper
Pool Name: docker-thinpool
Pool Blocksize: 524.3 kB
Base Device Size: 10.74 GB
Backing Filesystem: xfs
Data file:
Metadata file:
Data Space Used: 19.92 MB
Data Space Total: 102 GB
Data Space Available: 102 GB
Metadata Space Used: 147.5 kB
Metadata Space Total: 1.07 GB
Metadata Space Available: 1.069 GB
Thin Pool Minimum Free Space: 10.2 GB
Udev Sync Supported: true
Deferred Removal Enabled: true
Deferred Deletion Enabled: true
Deferred Deleted Device Count: 0
Library Version: 1.02.135-RHEL7 (2016-11-16)
<...>
```
如果 Docker 配置正确,则 `Data file` (数据文件) 和 `Metadata file` (元数据文件) 为空,存储池名称为 `docker-thinpool`
16. 验证配置正确后,您可以删除 `/var/lib/docker.bk` 目录,其中包含前面的配置。
```bash
sudo rm -rf /var/lib/docker.bk
```
## 管理 devicemapper
### 监视精简池
不要单独依赖 LVM 自动扩展。卷组会自动扩展,但卷仍可能填满。您可以使用 `lvs``lvs -a` 监控卷上的可用空间。考虑在 OS 级别使用监控工具,例如 Nagios。
要查看 LVM 日志,您可以使用 `journalctl`
```bash
sudo journalctl -fu dm-event.service
```
如果反复遇到精简池问题,则可以设置存储选项 `dm.min_free_space``/etc/docker/daemon.json` 中。例如,将其设置为 `10` 可确保 当可用空间等于或接近 10% 时,操作失败并显示警告。 请参阅 [Engine 守护程序参考中的 storage driver 选项][0]。
### 增加正在运行的设备的容量
您可以增加正在运行的精简池设备上的池容量。这是 如果数据的逻辑卷已满且卷组已满,则很有用 能力。具体过程取决于您是否使用 [loop-lvm 精简池][1]或 [direct-lvm thin pool 的 Thin Pool][2]。
### 调整 loop-lvm thin 池的大小
调整 loop-lvm thin 池大小的最简单方法是 使用 [device_tool 实用程序][3] 但你可以 [使用操作系统实用程序][4] 相反。
### 使用 device_tool 实用程序
一个名为 `device_tool.go` 的社区贡献的脚本在 [moby/moby][5] Github 存储库。您可以使用此工具调整 `loop-lvm` thin pool 的大小,从而避免上述漫长的过程。此工具不能保证有效,但您应该只在非生产系统上使用 `loop-lvm`
如果您不想使用 `device_tool`,您可以 请[改为手动调整精简池的大小](#手动配置direct-lvm模式)。
1. 要使用该工具,请克隆 Github 存储库,更改为 `contrib/docker-device-tool` 中的说明,然后按照 `README.md` 以编译该工具。
2. 使用该工具。以下示例将精简池的大小调整为 200GB。
```console
./device_tool resize 200GB
```
### 使用操作系统实用程序
如果您不想 [使用 device-tool 实用程序](#使用-device_tool-实用程序),您可以按照以下过程手动调整 `loop-lvm` 精简池的大小。
`loop-lvm` 模式下,一个 loopback 设备用于存储数据,另一个用于存储元数据。`loop-lvm` 模式仅支持用于测试,因为它具有明显的性能和稳定性缺点。
如果您使用的是 `loop-lvm` 模式,`docker info` 的输出将显示 `Data loop file``Metadata loop file` 的文件路径:
```bash
docker info |grep 'loop file'
Data loop file: /var/lib/docker/devicemapper/data
Metadata loop file: /var/lib/docker/devicemapper/metadata
```
按照以下步骤增加精简池的大小。在此示例中,精简池为 100 GB并增加到 200 GB。
1. 列出设备的大小。
```bash
sudo ls -lh /var/lib/docker/devicemapper/
total 1175492
-rw------- 1 root root 100G Mar 30 05:22 data
-rw------- 1 root root 2.0G Mar 31 11:17 metadata
```
2. 使用 `truncate` 命令将`data`增加到 200 G该命令用于增大或减小文件大小。请注意减小大小是一种破坏性操作。
```bash
sudo truncate -s 200G /var/lib/docker/devicemapper/data
```
3. 验证文件大小是否已更改。
```bash
sudo ls -lh /var/lib/docker/devicemapper/
total 1.2G
-rw------- 1 root root 200G Apr 14 08:47 data
-rw------- 1 root root 2.0G Apr 19 13:27 metadata
```
4. 环回文件在磁盘上已更改,但在内存中未更改。列出内存中环回设备的大小(以 GB 为单位)。重新加载它,然后再次列出大小。重新加载后,大小为 200 GB。
```bash
echo $[ $(sudo blockdev --getsize64 /dev/loop0) / 1024 / 1024 / 1024 ]
100
sudo losetup -c /dev/loop0
echo $[ $(sudo blockdev --getsize64 /dev/loop0) / 1024 / 1024 / 1024 ]
200
```
5. 重新加载 devicemapper 精简池。
- a 一个。首先获取池名称。存储池名称是第一个字段,由 `:`。此命令将提取它。
```bash
sudo dmsetup status | grep ' thin-pool ' | awk -F ': ' {'print $1'}
docker-8:1-123141-pool
```
- b. 转储精简池的设备映射器表。
```bash
sudo dmsetup table docker-8:1-123141-pool
0 209715200 thin-pool 7:1 7:0 128 32768 1 skip_block_zeroing
```
- c. 使用输出的第二个字段计算精简池的总扇区数。该数字以 512-k 扇区表示。一个 100G 文件有 209715200 个 512-k 扇区。如果将此数字翻倍到 200G则得到419430400 512-k 扇区。
- d. 使用以下三个 dmsetup 命令,使用新的扇区号重新加载精简池。
```bash
sudo dmsetup suspend docker-8:1-123141-pool
sudo dmsetup reload docker-8:1-123141-pool --table '0 419430400 thin-pool 7:1 7:0 128 32768 1 skip_block_zeroing'
sudo dmsetup resume docker-8:1-123141-pool
```
### 调整 direct-lvm 精简池的大小
要扩展 `direct-lvm `精简池,您需要首先将新的块设备附加到 Docker 主机,并记下内核为其分配的名称。在此示例中,新的块存储设备是 `/dev/xvdg`
按照此过程扩展 `direct-lvm` 精简池,替换块设备和其他参数以适合您的情况。
1. 收集有关卷组的信息。
使用 `pvdisplay` 命令查找精简池当前正在使用的物理块设备,以及卷组的名称。
```bash
sudo pvdisplay |grep 'VG Name'
PV Name /dev/xvdf
VG Name docker
```
在以下步骤中,根据需要替换块存储设备或卷组名称。
2. 使用带有 `VG name``vgextend` 命令扩展卷组 以及新块存储设备的名称。
```bash
sudo vgextend docker /dev/xvdg
Physical volume "/dev/xvdg" successfully created.
Volume group "docker" successfully extended
```
3. 扩展 `docker/thinpool` 逻辑卷。此命令立即使用 100% 的卷,而不自动扩展。要改为扩展元数据精简池,请使用 `docker/thinpool_tmeta`
```bash
sudo lvextend -l+100%FREE -n docker/thinpool
Size of logical volume docker/thinpool_tdata changed from 95.00 GiB (24319 extents) to 198.00 GiB (50688 extents).
Logical volume docker/thinpool_tdata successfully resized.
```
4. 使用 `docker info` 输出中的 `Data Space Available` 字段验证新的精简池大小。如果您扩展了 `docker/thinpool_tmeta` 逻辑卷,请查找 `Metadata Space Available` (可用元数据空间)。
```console
Storage Driver: devicemapper
Pool Name: docker-thinpool
Pool Blocksize: 524.3 kB
Base Device Size: 10.74 GB
Backing Filesystem: xfs
Data file:
Metadata file:
Data Space Used: 212.3 MB
Data Space Total: 212.6 GB
Data Space Available: 212.4 GB
Metadata Space Used: 286.7 kB
Metadata Space Total: 1.07 GB
Metadata Space Available: 1.069 GB
<...>
```
### 重新启动后激活 `devicemapper`
如果重新启动主机并发现 `docker` 服务无法启动请查找错误“Non existing device”。您需要使用以下命令重新激活逻辑卷
```bash
sudo lvchange -ay docker/thinpool
```
## `devicemapper` 存储驱动程序的工作原理
:::warning 重要
不要直接操作 `/var/lib/docker/` 中。这些文件和目录由 Docker 管理。
:::
使用 `lsblk` 命令从操作系统的角度查看设备及其池:
```bash
sudo lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
xvda 202:0 0 8G 0 disk
└─xvda1 202:1 0 8G 0 part /
xvdf 202:80 0 100G 0 disk
├─docker-thinpool_tmeta 253:0 0 1020M 0 lvm
│ └─docker-thinpool 253:2 0 95G 0 lvm
└─docker-thinpool_tdata 253:1 0 95G 0 lvm
└─docker-thinpool 253:2 0 95G 0 lvm
```
使用 `mount` 命令查看 Docker 正在使用的挂载点:
```bash
mount |grep devicemapper
/dev/xvda1 on /var/lib/docker/devicemapper type xfs (rw,relatime,seclabel,attr2,inode64,noquota)
```
当您使用 `devicemapper`Docker 将映像和层内容存储在 thinpool 中,并通过将它们挂载到 `/var/lib/docker/devicemapper/` 的子目录下来将它们公开给容器。
### 磁盘上的映像和容器层
`/var/lib/docker/devicemapper/metadata/` 目录包含有关 Devicemapper 配置本身以及存在的每个映像和容器层的元数据。`devicemapper` 存储驱动程序使用快照,此元数据包括有关这些快照的信息。这些文件采用 JSON 格式。
`/var/lib/docker/devicemapper/mnt/` 目录包含存在的每个映像和容器层的挂载点。映像层挂载点为空,但容器的挂载点显示容器的文件系统,就像它在容器内部显示的那样。
### 图像分层和共享
`devicemapper` 存储驱动程序使用专用的块设备而不是格式化的文件系统,并在块级别对文件进行操作,以便在写入时复制 CoW 操作期间获得最佳性能。
### 快照
`devicemapper` 的另一个特点是它使用快照(有时也称为 *thin devices 或 virtual devices*),它们将每层中引入的差异存储为非常小的轻量级薄池。快照具有许多优势:
- 在容器之间共享的层仅在磁盘上存储一次,除非它们是可写的。例如,如果您有 10 个不同的图像,它们都基于 `alpine`,则 `alpine` 图像及其所有父图像在磁盘上只存储一次。
- 快照是写入时复制 CoW 策略的实现。这意味着,仅当给定文件或目录被容器修改或删除时,才会将其复制到容器的可写层。
- 由于 `devicemapper` 在块级别运行,因此可以同时修改可写层中的多个块。
- 可以使用标准的操作系统级备份实用程序备份快照。只需复制 `/var/lib/docker/devicemapper/`
### Devicemapper 工作流程
当您使用 `devicemapper` 存储驱动程序启动 Docker 时,所有对象 与 映像和容器层的 `/var/lib/docker/devicemapper/` 中,它由一个或多个块级设备提供支持,环回设备(仅限测试)或物理磁盘。
- *基本设备*是最低级别的对象。这是 thin pool 本身。您可以使用 `docker info` 对其进行检查。它包含一个文件系统。此基本设备是每个映像和容器层的起点。基本设备是 Device Mapper implementation detail而不是 Docker 层。
- 有关基本设备和每个映像或容器层的元数据存储在 JSON 格式的 `/var/lib/docker/devicemapper/metadata/` 中。这些层是写入时复制快照,这意味着它们在与父层分叉之前是空的。
- 每个容器的可写层都挂载在 `/var/lib/docker/devicemapper/mnt/` 中。每个只读映像层和每个已停止的容器都存在一个空目录。
每个图像图层都是其下方图层的快照。每个映像的最低层是池中存在的基本设备的快照。当您运行容器时,它是容器所基于的映像的快照。以下示例显示了具有两个正在运行的容器的 Docker 主机。第一个是 `ubuntu` 容器,第二个是 `busybox` 容器。
![](./two_dm_container.webp)
## 容器读取和写入如何与 `devicemapper` 配合使用
### 读取文件
使用 `devicemapper` 时,读取发生在块级别。下图显示了在示例容器中读取单个块 `0x44f` 的高级过程。
![](./dm_container.webp)
应用程序对容器中的块`0x44f`发出读取请求。由于容器是映像的精简快照,因此它没有数据块,但它有一个指针,指向它确实存在的最近父映像上的数据块,并从那里读取数据块。该块现在存在于容器的内存中。
### 写入文件
编写新文件:使用 `devicemapper` 驱动程序,通过按需分配操作将新数据写入容器。新文件的每个块都分配在容器的可写层中,并且该块被写入该层。
更新现有文件:从文件所在的最近层读取文件的相关块。当容器写入文件时,只有修改后的块会写入容器的可写层。
删除文件或目录:当您删除容器的可写层中的文件或目录时,或者当映像层删除其父层中存在的文件时,`devicemapper` 存储驱动程序会拦截对该文件或目录的进一步读取尝试,并响应该文件或目录不存在。
写入然后删除文件:如果容器写入文件,然后删除该文件,则所有这些操作都发生在容器的可写层中。在这种情况下,如果您使用的是 `direct-lvm`,则会释放块。如果使用 `loop-lvm`,则可能无法释放块。这是不使用 `loop-lvm` 中。
## Device Mapper 和 Docker 性能
- `allocate-on demand` (按需分配) 性能影响:
`devicemapper` 存储驱动程序使用按需`分配 (allocate-on-demand)`操作将新块从精简池分配到容器的可写层。每个块为 64KB因此这是用于写入的最小空间量。
- 写入时复制性能影响:容器首次修改特定块时,该块将写入容器的可写层。由于这些写入发生在块级别而不是文件级别,因此性能影响最小。但是,写入大量块仍会对性能产生负面影响,在这种情况下,`devicemapper` 存储驱动程序的性能实际上可能比其他存储驱动程序差。对于写入密集型工作负载,您应该使用数据卷,这会完全绕过存储驱动程序。
### 性能最佳实践
请记住这些事项,以便在使用 `devicemapper` 时最大限度地提高性能 storage 驱动程序。
- 使用 `direct-lvm``loop-lvm` 模式性能不佳,绝不应在生产中使用。
- 使用快速存储:固态驱动器 SSD 提供比旋转磁盘更快的读取和写入速度。
- 内存使用率:`devicemapper` 使用的内存比其他一些存储驱动程序多。每个启动的容器都会将其文件的一个或多个副本加载到内存中,具体取决于同时修改同一文件的块数。由于内存压力,`devicemapper` 存储驱动程序可能不是高密度使用案例中某些工作负载的正确选择。
- 将卷用于写入密集型工作负载:卷为写入密集型工作负载提供最佳且最可预测的性能。这是因为它们绕过了存储驱动程序,并且不会产生精简配置和写入时复制引入的任何潜在开销。卷还有其他好处,例如允许您在容器之间共享数据,以及即使没有正在运行的容器正在使用卷也可以保留它们。
:::warning 注意
使用 `devicemapper``json-file` 日志驱动程序时,容器生成的日志文件仍存储在 Docker 的 dataroot 目录中,默认为 `/var/lib/docker`。如果您的容器生成大量日志消息,这可能会导致磁盘使用量增加或由于磁盘已满而无法管理系统。您可以配置 [log 驱动程序][6]将容器日志存储在外部。
:::
## 相关信息
- [卷][21]
- [了解映像、容器和存储驱动程序][22]
- [选择存储驱动程序][20]
<!-- 链接集 -->
[定期维护任务]:#定期维护任务
[0]:https://docs.docker.com/reference/cli/dockerd/#daemon-storage-driver
[1]:#调整-loop-lvm-thin-池的大小
[2]:#调整-loop-lvm-thin-池的大小
[3]:#使用-device_tool-实用程序
[4]:#使用操作系统实用程序
[5]:https://github.com/moby/moby/tree/master/contrib/docker-device-tool
[6]:https://docs.docker.com/engine/logging/configure/
[20]:./select-storage-driver
[21]:./../volume
[22]:./index

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

View File

@ -3,3 +3,321 @@ outline: [2,4]
--- ---
# 存储驱动程序 # 存储驱动程序
要有效地使用存储驱动程序,了解 Docker 如何构建和存储映像以及容器如何使用这些映像非常重要。您可以使用此信息来做出明智的选择,以最佳方式保存应用程序中的数据,并在此过程中避免性能问题。
## 存储驱动程序与 Docker 卷
Docker 使用存储驱动程序来存储映像层,并将数据存储在容器的可写层中。容器的可写层在容器删除后不会保留,但适合存储运行时生成的临时数据。存储驱动程序针对空间效率进行了优化,但 (取决于存储驱动程序) 写入速度低于本机文件系统性能,尤其是对于使用写入时复制文件系统的存储驱动程序。写入密集型应用程序(如数据库存储)会受到性能开销的影响,尤其是在只读层中存在预先存在的数据时。
将 Docker 卷用于写入密集型数据,这些数据必须在 容器的生命周期,以及必须在容器之间共享的数据。指 这 [volumes 部分](/docs/storage/volume),了解如何使用卷来持久保存数据并提高性能。
## 图像和图层
Docker 镜像由一系列层构建而成。每个层都表示映像的 Dockerfile 中的一条指令。除最后一层外,每一层都是只读的。请考虑以下 Dockerfile
```dockerfile
# syntax=docker/dockerfile:1
FROM ubuntu:22.04
LABEL org.opencontainers.image.authors="org@example.com"
COPY . /app
RUN make /app
RUN rm -r $HOME/.cache
CMD python /app/app.py
```
此 Dockerfile 包含四个命令。修改文件系统的命令将创建一个新层。`FROM` 语句首先从 `ubuntu22.04` 创建一个层 图像。`LABEL` 命令仅修改图像的元数据,不会生成新图层。`COPY` 命令从 Docker 客户端的当前目录中添加一些文件。第一个 `RUN` 命令使用 `make` 命令构建您的应用程序,并将结果写入新层。第二个 `RUN` 命令删除缓存目录,并将结果写入新层。最后,`CMD` 指令指定要在容器内运行的命令,该命令仅修改图像的元数据,而不会生成图像层。
每个层只是与前一层的一组差异。请注意,两者 添加和删除文件将生成一个新图层。在上面的示例中,`$HOME/.cache` 目录已被删除,但仍可在 previous layer 并加起来等于图像的总大小。请参阅 [编写 Dockerfile 的最佳实践](https://docs.docker.com/build/building/best-practices/) 和 [使用多阶段构建](https://docs.docker.com/build/building/multi-stage/) 部分了解如何优化 Dockerfile 以实现高效映像。
这些层彼此堆叠在一起。创建新容器时,您会在底层层之上添加新的可写层。此层通常称为 “容器层”。对正在运行的容器所做的所有更改(例如写入新文件、修改现有文件和删除文件)都将写入此精简的可写容器层。下图显示了基于 `ubuntu15.04` 映像的容器。
![container-layers](./container-layers.webp)
存储驱动程序处理有关这些层彼此交互方式的详细信息。可以使用不同的存储驱动程序,这些驱动程序在不同情况下各有优点和缺点。
## 容器和图层
容器和映像之间的主要区别在于顶部可写层。对容器的所有添加新数据或修改现有数据的写入都存储在此可写层中。删除容器时,可写层也会被删除。底层图像保持不变。
由于每个容器都有自己的可写容器层,并且所有更改都存储在此容器层中,因此多个容器可以共享对同一底层映像的访问,但具有自己的数据状态。下图显示了共享同一 Ubuntu 15.04 映像的多个容器。
![sharing-layer](./sharing-layers.webp)
Docker 使用存储驱动程序来管理映像层和可写容器层的内容。每个存储驱动程序以不同的方式处理实现,但所有驱动程序都使用可堆叠映像层和写入时复制 CoW 策略。
:::warning 注意
如果您需要多个容器来共享访问权限,请使用 Docker 卷 完全相同的数据。请参阅 [volumes](/docs/storage/volume) 部分来了解卷。
:::
## 磁盘上的容器大小
要查看正在运行的容器的大致大小,您可以使用 `docker ps -s` 命令。两个不同的列与大小相关。
- `size`:用于每个容器的可写层的数据量(在磁盘上)。
- `virtual size` (虚拟大小):容器使用的只读图像数据使用的数据量加上容器的可写层`size`(大小)。多个容器可以共享部分或全部只读图像数据。从同一映像启动的两个容器共享 100% 的只读数据,而具有不同映像且具有公共层的两个容器共享这些公共层。因此,您不能只计算虚拟大小。这高估了磁盘总使用量,可能不是平凡的。
磁盘上所有正在运行的容器使用的总磁盘空间是每个容器的`size`(大小)和`virtual size`(虚拟大小)值的组合。如果多个容器从同一精确映像开始,则这些容器的磁盘总大小将为 SUM容器`size`(大小))加上一个映像大小(`virtual size`(虚拟大小) - `size`(大小))。
这也不计算容器占用磁盘空间的以下其他方式:
- 用于 [logging-驱动程序](https://docs.docker.com/engine/logging/)。如果您的容器生成大量日志记录数据并且未配置日志轮换,这可能并非易事。
- 容器使用的卷和绑定挂载。
- 用于容器配置文件的磁盘空间,通常很小。
- 写入磁盘的内存(如果启用了交换)。
- 检查点,如果您正在使用实验性检查点/恢复功能。
## 写入时复制 CoW 策略
写入时复制是一种共享和复制文件以实现最高效率的策略。如果文件或目录存在于映像中的较低层中,并且另一个层(包括可写层)需要对它进行读取访问,则它只使用现有文件。当另一个层首次需要修改文件时(在构建映像或运行容器时),该文件将复制到该层中并进行修改。这样可以最大限度地减少 I/O 和每个后续层的大小。下面将更深入地解释这些优势。
### 共享可提升较小的图像
当您使用 `docker pull` 从存储库中拉取映像时,或者当您从本地尚不存在的映像创建容器时,每个层都会单独下拉,并存储在 Docker 的本地存储区域(在 Linux 主机上通常为 `/var/lib/docker/`)。在此示例中,您可以看到这些层被拉取:
```bash
docker pull ubuntu:22.04
22.04: Pulling from library/ubuntu
f476d66f5408: Pull complete
8882c27f669e: Pull complete
d9af21273955: Pull complete
f5029279ec12: Pull complete
Digest: sha256:6120be6a2b7ce665d0cbddc3ce6eae60fe94637c6a66985312d1f02f63cc0bcd
Status: Downloaded newer image for ubuntu:22.04
docker.io/library/ubuntu:22.04
```
这些层中的每一个都存储在 Docker 主机的本地存储区域内自己的目录中。要检查文件系统上的层,请列出 `/var/lib/docker/<storage-driver>` 的内容。此示例使用 `overlay2` 存储驱动程序:
```bash
ls /var/lib/docker/overlay2
16802227a96c24dcbeab5b37821e2b67a9f921749cd9a2e386d5a6d5bc6fc6d3
377d73dbb466e0bc7c9ee23166771b35ebdbe02ef17753d79fd3571d4ce659d7
3f02d96212b03e3383160d31d7c6aeca750d2d8a1879965b89fe8146594c453d
ec1ec45792908e90484f7e629330666e7eee599f08729c93890a7205a6ba35f5
l
```
目录名称与图层 ID 不对应。
现在假设您有两个不同的 Dockerfile。您使用第一个创建名为 `acme/my-base-image1.0` 的映像。
```dockerfile:line-numbers
# syntax=docker/dockerfile:1
FROM alpine
RUN apk add --no-cache bash
```
第二个基于 `acme/my-base-image1.0`,但有一些额外的层:
```dockerfile:line-numbers
# syntax=docker/dockerfile:1
FROM acme/my-base-image:1.0
COPY . /app
RUN chmod +x /app/hello.sh
CMD /app/hello.sh
```
第二个映像包含第一个映像中的所有层,以及由 `COPY``RUN` 指令创建的新层以及一个读写容器层。Docker 已经拥有第一个镜像中的所有层,因此不需要再次拉取它们。这两个图像共享它们共有的任何图层。
如果您从两个 Dockerfile 构建镜像,则可以使用 `docker image ls``docker image history` 命令验证共享层的加密 ID 是否相同
1. 创建一个新目录 `cow-test/` 并切换到它。
2. 在 `cow-test/` 中,创建一个名为 `hello.sh` 的新文件,其中包含以下内容。
```bash
#!/usr/bin/env bash
echo "Hello world"
```
3. 将上面第一个 Dockerfile 的内容复制到一个名为 `Dockerfile.base`
4. 将上面第二个 Dockerfile 的内容复制到一个名为 `Dockerfile` 文件。
5. 在 `cow-test/` 目录中,构建第一个镜像。不要忘记在命令中包含最终的 `.` 。这将设置 `PATH`,它告诉 Docker 在何处查找需要添加到映像的任何文件。
```bash
docker build -t acme/my-base-image:1.0 -f Dockerfile.base .
[+] Building 6.0s (11/11) FINISHED
=> [internal] load build definition from Dockerfile.base 0.4s
=> => transferring dockerfile: 116B 0.0s
=> [internal] load .dockerignore 0.3s
=> => transferring context: 2B 0.0s
=> resolve image config for docker.io/docker/dockerfile:1 1.5s
=> [auth] docker/dockerfile:pull token for registry-1.docker.io 0.0s
=> CACHED docker-image://docker.io/docker/dockerfile:1@sha256:9e2c9eca7367393aecc68795c671... 0.0s
=> [internal] load .dockerignore 0.0s
=> [internal] load build definition from Dockerfile.base 0.0s
=> [internal] load metadata for docker.io/library/alpine:latest 0.0s
=> CACHED [1/2] FROM docker.io/library/alpine 0.0s
=> [2/2] RUN apk add --no-cache bash 3.1s
=> exporting to image 0.2s
=> => exporting layers 0.2s
=> => writing image sha256:da3cf8df55ee9777ddcd5afc40fffc3ead816bda99430bad2257de4459625eaa 0.0s
=> => naming to docker.io/acme/my-base-image:1.0 0.0s
```
6. 构建第二个映像。
```bash
docker build -t acme/my-final-image:1.0 -f Dockerfile .
[+] Building 3.6s (12/12) FINISHED
=> [internal] load build definition from Dockerfile 0.1s
=> => transferring dockerfile: 156B 0.0s
=> [internal] load .dockerignore 0.1s
=> => transferring context: 2B 0.0s
=> resolve image config for docker.io/docker/dockerfile:1 0.5s
=> CACHED docker-image://docker.io/docker/dockerfile:1@sha256:9e2c9eca7367393aecc68795c671... 0.0s
=> [internal] load .dockerignore 0.0s
=> [internal] load build definition from Dockerfile 0.0s
=> [internal] load metadata for docker.io/acme/my-base-image:1.0 0.0s
=> [internal] load build context 0.2s
=> => transferring context: 340B 0.0s
=> [1/3] FROM docker.io/acme/my-base-image:1.0 0.2s
=> [2/3] COPY . /app 0.1s
=> [3/3] RUN chmod +x /app/hello.sh 0.4s
=> exporting to image 0.1s
=> => exporting layers 0.1s
=> => writing image sha256:8bd85c42fa7ff6b33902ada7dcefaaae112bf5673873a089d73583b0074313dd 0.0s
=> => naming to docker.io/acme/my-final-image:1.0 0.0s
```
7. 查看图像的大小。
```console
docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
acme/my-final-image 1.0 8bd85c42fa7f About a minute ago 7.75MB
acme/my-base-image 1.0 da3cf8df55ee 2 minutes ago 7.75MB
```
8. 查看每张图片的历史记录。
```console
docker image history acme/my-base-image:1.0
IMAGE CREATED CREATED BY SIZE COMMENT
da3cf8df55ee 5 minutes ago RUN /bin/sh -c apk add --no-cache bash # bui… 2.15MB buildkit.dockerfile.v0
<missing> 7 weeks ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B
<missing> 7 weeks ago /bin/sh -c #(nop) ADD file:f278386b0cef68136… 5.6MB
```
某些步骤没有大小 `0B`),并且是仅元数据更改,不会生成图像图层,也不会占用除元数据本身之外的任何大小。上面的输出显示此图像由 2 个图像层组成。
```bash
docker image history acme/my-final-image:1.0
IMAGE CREATED CREATED BY SIZE COMMENT
8bd85c42fa7f 3 minutes ago CMD ["/bin/sh" "-c" "/app/hello.sh"] 0B buildkit.dockerfile.v0
<missing> 3 minutes ago RUN /bin/sh -c chmod +x /app/hello.sh # buil… 39B buildkit.dockerfile.v0
<missing> 3 minutes ago COPY . /app # buildkit 222B buildkit.dockerfile.v0
<missing> 4 minutes ago RUN /bin/sh -c apk add --no-cache bash # bui… 2.15MB buildkit.dockerfile.v0
<missing> 7 weeks ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B
<missing> 7 weeks ago /bin/sh -c #(nop) ADD file:f278386b0cef68136… 5.6MB
```
请注意,第一个图像的所有步骤也包含在最终图像中。最终图像包括第一个图像中的两个图层,以及第二个图像中添加的两个图层。
`docker history`输出中的 `<missing>` 行表示这些步骤要么是在另一个系统上构建的,是从 Docker Hub 拉取的 `alpine` 映像的一部分,要么是使用 BuildKit 作为构建器构建的。在 BuildKit 之前,“经典”构建器将为每个步骤生成一个新的“中间”映像以进行缓存,并且 `IMAGE` 列将显示该映像的 ID。
BuildKit 使用自己的缓存机制,不再需要中间 图像进行缓存。指 [构建套件](https://docs.docker.com/build/buildkit/) 以了解有关 BuildKit 中的其他增强功能的更多信息。
9. 查看每张图像的图层
使用 `docker image inspect` 命令查看每个映像中层的加密 ID
```bash
docker image inspect --format "{{json .RootFS.Layers}}" acme/my-base-image:1.0
[
"sha256:72e830a4dff5f0d5225cdc0a320e85ab1ce06ea5673acfe8d83a7645cbd0e9cf",
"sha256:07b4a9068b6af337e8b8f1f1dae3dd14185b2c0003a9a1f0a6fd2587495b204a"
]
```
```bash
docker image inspect --format "{{json .RootFS.Layers}}" acme/my-final-image:1.0
[
"sha256:72e830a4dff5f0d5225cdc0a320e85ab1ce06ea5673acfe8d83a7645cbd0e9cf",
"sha256:07b4a9068b6af337e8b8f1f1dae3dd14185b2c0003a9a1f0a6fd2587495b204a",
"sha256:cc644054967e516db4689b5282ee98e4bc4b11ea2255c9630309f559ab96562e",
"sha256:e84fb818852626e89a09f5143dbc31fe7f0e0a6a24cd8d2eb68062b904337af4"
]
```
请注意,两个图像中的前两个图层相同。第二个图像添加了两个额外的图层。共享映像层仅在 `/var/lib/docker/` 中存储一次,并且在将映像推送和拉取到映像注册表时也会共享。因此,共享映像层可以减少网络带宽和存储空间。
:::tip 使用 `--format` 选项格式化 Docker 命令的输出。
上面的示例使用带有 `--format``docker image inspect` 命令 选项查看图层 ID格式为 JSON 数组。`--format` Docker 命令上的选项可能是一项强大的功能,允许您 从输出中提取特定信息并设置其格式,而无需 其他工具,例如 `awk``sed`。要了解有关使用 `--format` 标志格式化 docker 命令输出的更多信息,请参阅 [format 命令和 log output 部分](https://docs.docker.com/engine/cli/formatting/)。 我们还使用 [`JQ` 实用程序](https://stedolan.github.io/jq/) 以提高可读性。
:::
### 复制使容器高效
启动容器时,将在其他层的顶部添加一个薄的可写容器层。容器对文件系统所做的任何更改都存储在此处。容器未更改的任何文件都不会复制到此可写层。这意味着可写层尽可能小。
修改容器中的现有文件时,存储驱动程序将执行写入时复制操作。涉及的具体步骤取决于特定的存储驱动程序。对于 `overlay2` 驱动程序,写入时复制操作遵循以下粗略顺序:
- 在图像图层中搜索要更新的文件。该过程从最新的图层开始,一次一个图层地向下工作到 Base Layer。找到结果后它们将被添加到缓存中以加快未来的操作速度。
- 对找到的文件的第一个副本执行 `copy_up` 操作,以将文件复制到容器的可写层。
- 将对文件的此副本进行任何修改,并且容器无法查看存在于较低层中的文件的只读副本。
Btrfs、ZFS 和其他驱动程序以不同的方式处理写入时复制。您可以稍后在详细说明中阅读有关这些驱动程序的方法的更多信息。
写入大量数据的容器比不写入数据的容器占用更多的空间。这是因为大多数写入操作都会占用容器的薄可写顶层中的新空间。请注意,更改文件的元数据(例如,更改文件的权限或所有权)也可能导致`copy_up`操作,因此会将文件复制到可写层。
:::tip 将卷用于写入密集型应用程序。
不要将数据存储在写入密集型应用程序的容器中。众所周知,此类应用程序(例如写入密集型数据库)存在问题,尤其是当只读层中存在预先存在的数据时。
相反,请使用 Docker 卷,这些卷独立于正在运行的容器 并且设计为高效的 I/O。此外还可以共享卷 ,并且不要增加容器的可写对象的大小 层。请参阅 [使用 volumes](/docs/storage/volume) 部分了解卷。
:::
`copy_up` 操作可能会产生明显的性能开销。此开销因使用的存储驱动程序而异。大文件、大量图层和深目录树会使影响更加明显。由于每个 `copy_up` 操作仅在第一次修改给定文件时发生,因此可以缓解这种情况。
为了验证写入时复制的工作方式,以下过程根据我们之前构建的 `acme/my-final-image1.0` 映像启动 5 个容器,并检查它们占用了多少空间。
1. 从 Docker 主机上的终端运行以下 `docker run` 命令。末尾的字符串是每个容器的 ID。
```bash
docker run -dit --name my_container_1 acme/my-final-image:1.0 bash \
&& docker run -dit --name my_container_2 acme/my-final-image:1.0 bash \
&& docker run -dit --name my_container_3 acme/my-final-image:1.0 bash \
&& docker run -dit --name my_container_4 acme/my-final-image:1.0 bash \
&& docker run -dit --name my_container_5 acme/my-final-image:1.0 bash
40ebdd7634162eb42bdb1ba76a395095527e9c0aa40348e6c325bd0aa289423c
a5ff32e2b551168b9498870faf16c9cd0af820edf8a5c157f7b80da59d01a107
3ed3c1a10430e09f253704116965b01ca920202d52f3bf381fbb833b8ae356bc
939b3bf9e7ece24bcffec57d974c939da2bdcc6a5077b5459c897c1e2fa37a39
cddae31c314fbab3f7eabeb9b26733838187abc9a2ed53f97bd5b04cd7984a5a
```
2. 运行带有 `--size` 选项的 `docker ps` 命令,以验证 5 个容器是否正在运行,并查看每个容器的大小。
```bash
docker ps --size --format "table {{.ID}}\t{{.Image}}\t{{.Names}}\t{{.Size}}"
CONTAINER ID IMAGE NAMES SIZE
cddae31c314f acme/my-final-image:1.0 my_container_5 0B (virtual 7.75MB)
939b3bf9e7ec acme/my-final-image:1.0 my_container_4 0B (virtual 7.75MB)
3ed3c1a10430 acme/my-final-image:1.0 my_container_3 0B (virtual 7.75MB)
a5ff32e2b551 acme/my-final-image:1.0 my_container_2 0B (virtual 7.75MB)
40ebdd763416 acme/my-final-image:1.0 my_container_1 0B (virtual 7.75MB)
```
上面的输出显示,所有容器共享映像的只读层 7.75MB),但没有数据写入容器的文件系统,因此没有为容器使用额外的存储空间。
:::details [高级:用于容器的元数据和日志存储]
:::warning 注意
此步骤需要 Linux 计算机,在 Docker Desktop 上不起作用,因为它需要访问 Docker 守护程序的文件存储。
虽然 `docker ps` 的输出提供了有关容器的可写层占用的磁盘空间的信息,但它不包括有关为每个容器存储的元数据和日志文件的信息。
通过浏览 Docker 守护程序的存储位置(默认为 `/var/lib/docker`)可以获得更多详细信息。
```bash
sudo du -sh /var/lib/docker/containers/*
36K /var/lib/docker/containers/3ed3c1a10430e09f253704116965b01ca920202d52f3bf381fbb833b8ae356bc
36K /var/lib/docker/containers/40ebdd7634162eb42bdb1ba76a395095527e9c0aa40348e6c325bd0aa289423c
36K /var/lib/docker/containers/939b3bf9e7ece24bcffec57d974c939da2bdcc6a5077b5459c897c1e2fa37a39
36K /var/lib/docker/containers/a5ff32e2b551168b9498870faf16c9cd0af820edf8a5c157f7b80da59d01a107
36K /var/lib/docker/containers/cddae31c314fbab3f7eabeb9b26733838187abc9a2ed53f97bd5b04cd7984a5a
```
这些容器中的每一个在文件系统上只占用 36k 的空间。
:::
3. 按容器存储
为了演示这一点,请运行以下命令,将单词 'hello' 写入容器可写层上的文件 `my_container_1``my_container_2``my_container_3`
```bash
for i in {1..3}; do docker exec my_container_$i sh -c 'printf hello > /out.txt'; done
```
之后再次运行 `docker ps` 命令会显示,这些容器现在每个容器消耗 5 个字节。此数据对于每个容器都是唯一的,并且不共享。容器的只读层不受影响,仍由所有容器共享。
```bash
docker ps --size --format "table {{.ID}}\t{{.Image}}\t{{.Names}}\t{{.Size}}"
CONTAINER ID IMAGE NAMES SIZE
cddae31c314f acme/my-final-image:1.0 my_container_5 0B (virtual 7.75MB)
939b3bf9e7ec acme/my-final-image:1.0 my_container_4 0B (virtual 7.75MB)
3ed3c1a10430 acme/my-final-image:1.0 my_container_3 5B (virtual 7.75MB)
a5ff32e2b551 acme/my-final-image:1.0 my_container_2 5B (virtual 7.75MB)
40ebdd763416 acme/my-final-image:1.0 my_container_1 5B (virtual 7.75MB)
```
前面的示例说明了写入时复制文件系统如何帮助提高容器的效率。写入时复制不仅可以节省空间还可以减少容器启动时间。当您创建容器或来自同一映像的多个容器Docker 只需要创建精简的可写容器层。
如果 Docker 每次都必须创建底层镜像堆栈的完整副本 创建新容器,容器创建时间和使用的磁盘空间将为 显著增加。这类似于虚拟机 工作,每个虚拟机使用一个或多个虚拟磁盘。这 [`VFS` 存储](https://docs.docker.com/engine/storage/drivers/vfs-driver/) 不提供 CoW 文件系统或其他优化。使用此存储时 驱动程序中,将为每个容器创建映像数据的完整副本。
## 相关信息
- [](/docs/storage/volume)
- [选择存储驱动程序](/docs/storage/drivers/select-storage-driver)

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

View File

@ -0,0 +1,285 @@
---
outline: [2,5]
---
# OverlayFS 存储驱动程序
OverlayFS 是一个联合文件系统。
本页将 Linux 内核驱动程序称为` OverlayFS`,将 Docker 存储驱动程序称为 `overlay2`
:::info 注意
对于 `fuse-overlayfs` 驱动程序,请检查 [无根模式文档][rootless-doc]。
:::
## 先决条件
OverlayFS 是推荐的存储驱动程序,如果您满足以下先决条件,则支持 OverlayFS
- Linux 内核版本 4.0 或更高版本,或者使用内核版本 3.10.0-514 或更高版本的 RHEL 或 CentOS。
- `xfs` 后备文件系统支持 `overlay2` 驱动程序,但仅在启用 `d_type=true` 的情况下受支持。
使用 `xfs_info` 验证 `ftype` 选项是否设置为 `1`。要格式化 `xfs` 文件系统中,请使用标志 `-n ftype=1`
- 更改存储驱动程序将使本地系统上的现有容器和映像无法访问。在更改存储驱动程序之前,使用 `docker save` 保存您构建的任何映像或将其推送到 Docker Hub 或私有注册表,这样您以后就不需要重新创建它们。
## 使用 `overlay2` 存储驱动程序配置 Docker
在执行此过程之前,您必须首先满足所有 [先决条件](#先决条件)。
以下步骤概述了如何配置 `overlay2` 存储驱动程序。
1. 停止 Docker。
```bash
sudo systemctl stop docker
```
2. 将 `/var/lib/docker` 的内容复制到临时位置。
```bash
cp -au /var/lib/docker /var/lib/docker.bk
```
3. 如果要使用与 `/var/lib/` 中,格式化文件系统并将其挂载到 `/var/lib/docker` 中。确保将此挂载添加到 `/etc/fstab` 以使其成为永久挂载。
4. 编辑 `/etc/docker/daemon.json`。如果尚不存在,请创建它。假设文件为空,请添加以下内容。
```json
{
"storage-driver": "overlay2"
}
```
如果 `daemon.json` 文件包含无效的 JSON则 Docker 不会启动。
5. 启动 Docker。
```bash
sudo systemctl start docker
```
6. 验证守护程序是否正在使用 `overlay2` 存储驱动程序。使用 `docker info` 命令并查找 `Storage Driver``Backing filesystem` (后备文件系统)。
```bash
docker info
Containers: 0
Images: 0
Storage Driver: overlay2
Backing Filesystem: xfs
Supports d_type: true
Native Overlay Diff: true
<...>
```
Docker 现在正在使用 `overlay2` 存储驱动程序,并已使用所需的 `lowerdir`、`upperdir`、`merged` 和 `workdir` 构造自动创建了覆盖挂载。
继续阅读有关 OverlayFS 如何在 Docker 容器中工作的详细信息,以及性能建议和有关其与不同后备文件系统的兼容性限制的信息。
## `overlay2` 驱动程序的工作原理
OverlayFS 在单个 Linux 主机上对两个目录进行分层,并将它们显示为单个目录。这些目录称为 层统一过程称为联合挂载。OverlayFS 将下层目录称为 `lowerdir`,将上层目录称为 `upperdir`。统一视图通过其自己的目录(称为 `merged`)公开。
`overlay2` 驱动程序原生支持多达 128 个较低的 OverlayFS 层。此功能为与层相关的 Docker 命令(如 `docker build``docker commit`)提供了更好的性能,并且占用的后备文件系统上的 inode 更少。
### 磁盘上的映像和容器层
使用 `docker pull ubuntu` 下载五层镜像后,您可以在 `/var/lib/docker/overlay2` 下看到六个目录。
:::warning 警告
不要直接操作 `/var/lib/docker/` 中。这些文件和目录由 Docker 管理。
:::
```bash
ls -l /var/lib/docker/overlay2
total 24
drwx------ 5 root root 4096 Jun 20 07:36 223c2864175491657d238e2664251df13b63adb8d050924fd1bfcdb278b866f7
drwx------ 3 root root 4096 Jun 20 07:36 3a36935c9df35472229c57f4a27105a136f5e4dbef0f87905b2e506e494e348b
drwx------ 5 root root 4096 Jun 20 07:36 4e9fa83caff3e8f4cc83693fa407a4a9fac9573deaf481506c102d484dd1e6a1
drwx------ 5 root root 4096 Jun 20 07:36 e8876a226237217ec61c4baf238a32992291d059fdac95ed6303bdff3f59cff5
drwx------ 5 root root 4096 Jun 20 07:36 eca1e4e1694283e001f200a667bb3cb40853cf2d1b12c29feda7422fed78afed
drwx------ 2 root root 4096 Jun 20 07:36 l
```
新的 `l`(小写 `L`)目录包含作为符号链接的缩短图层标识符。这些标识符用于避免触及 `mount` 命令参数的页面大小限制。
```bash
ls -l /var/lib/docker/overlay2/l
total 20
lrwxrwxrwx 1 root root 72 Jun 20 07:36 6Y5IM2XC7TSNIJZZFLJCS6I4I4 -> ../3a36935c9df35472229c57f4a27105a136f5e4dbef0f87905b2e506e494e348b/diff
lrwxrwxrwx 1 root root 72 Jun 20 07:36 B3WWEFKBG3PLLV737KZFIASSW7 -> ../4e9fa83caff3e8f4cc83693fa407a4a9fac9573deaf481506c102d484dd1e6a1/diff
lrwxrwxrwx 1 root root 72 Jun 20 07:36 JEYMODZYFCZFYSDABYXD5MF6YO -> ../eca1e4e1694283e001f200a667bb3cb40853cf2d1b12c29feda7422fed78afed/diff
lrwxrwxrwx 1 root root 72 Jun 20 07:36 NFYKDW6APBCCUCTOUSYDH4DXAT -> ../223c2864175491657d238e2664251df13b63adb8d050924fd1bfcdb278b866f7/diff
lrwxrwxrwx 1 root root 72 Jun 20 07:36 UL2MW33MSE3Q5VYIKBRN4ZAGQP -> ../e8876a226237217ec61c4baf238a32992291d059fdac95ed6303bdff3f59cff5/diff
```
最低层包含一个名为 `link` 的文件,其中包含缩短标识符的名称,以及一个名为 `diff` 的目录,其中包含该层的内容。
```bash
ls /var/lib/docker/overlay2/3a36935c9df35472229c57f4a27105a136f5e4dbef0f87905b2e506e494e348b/
diff link
cat /var/lib/docker/overlay2/3a36935c9df35472229c57f4a27105a136f5e4dbef0f87905b2e506e494e348b/link
6Y5IM2XC7TSNIJZZFLJCS6I4I4
ls /var/lib/docker/overlay2/3a36935c9df35472229c57f4a27105a136f5e4dbef0f87905b2e506e494e348b/diff
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
```
第二低的层和每个较高的层都包含一个名为 `lower` 的文件,该文件表示其父级,以及一个名为 `diff` 的目录,其中包含其内容。它还包含一个合并目录,该目录包含其父层和自身的统一内容,以及一个由 OverlayFS 内部使用的`work`(工作)目录。
```bash
ls /var/lib/docker/overlay2/223c2864175491657d238e2664251df13b63adb8d050924fd1bfcdb278b866f7
diff link lower merged work
cat /var/lib/docker/overlay2/223c2864175491657d238e2664251df13b63adb8d050924fd1bfcdb278b866f7/lower
l/6Y5IM2XC7TSNIJZZFLJCS6I4I4
ls /var/lib/docker/overlay2/223c2864175491657d238e2664251df13b63adb8d050924fd1bfcdb278b866f7/diff/
etc sbin usr var
```
要查看在 Docker 中使用 `overlay` (覆盖) 存储驱动程序时存在的挂载,请使用 `mount` 命令。为了提高可读性,下面的输出被截断。
```bash
mount | grep overlay
overlay on /var/lib/docker/overlay2/9186877cdf386d0a3b016149cf30c208f326dca307529e646afce5b3f83f5304/merged
type overlay (rw,relatime,
lowerdir=l/DJA75GUWHWG7EWICFYX54FIOVT:l/B3WWEFKBG3PLLV737KZFIASSW7:l/JEYMODZYFCZFYSDABYXD5MF6YO:l/UL2MW33MSE3Q5VYIKBRN4ZAGQP:l/NFYKDW6APBCCUCTOUSYDH4DXAT:l/6Y5IM2XC7TSNIJZZFLJCS6I4I4,
upperdir=9186877cdf386d0a3b016149cf30c208f326dca307529e646afce5b3f83f5304/diff,
workdir=9186877cdf386d0a3b016149cf30c208f326dca307529e646afce5b3f83f5304/work)
```
第二行上的` rw `显示`overlay`(覆盖)挂载是读写的。
下图显示了 Docker 映像和 Docker 容器的分层方式。image 层是 `lowerdir`,容器层是 `upperdir` 的 URL 中。如果图像具有多个图层,则使用多个 `lowerdir` 目录。统一视图通过一个名为 `merged` 的目录公开,该目录实际上是容器挂载点。
![overlay_constructs](./overlay_constructs.webp)
如果图像层和容器层包含相同的文件,则容器层 `upperdir` 优先并掩盖图像层中存在相同文件。
要创建容器,`overlay2` 驱动程序将表示映像顶层的目录与容器的新目录组合在一起。图像的图层是叠加中的 `lowerdirs`,并且是只读的。容器的新目录是 `upperdir` 并且是可写的。
### 磁盘上的映像和容器层
以下 `docker pull` 命令显示了一个 Docker 主机正在下载一个包含五个层的 Docker 镜像。
```bash
docker pull ubuntu
Using default tag: latest
latest: Pulling from library/ubuntu
5ba4f30e5bea: Pull complete
9d7d19c9dc56: Pull complete
ac6ad7efd0f9: Pull complete
e7491a747824: Pull complete
a3ed95caeb02: Pull complete
Digest: sha256:46fb5d001b88ad904c5c732b086b596b92cfb4a4840a3abd0e35dbb6870585e4
Status: Downloaded newer image for ubuntu:latest
```
### 图像图层
每个映像层在 `/var/lib/docker/overlay/` 中都有自己的目录,其中包含其内容,如以下示例所示。映像层 ID 与目录 ID 不对应。
:::warning 注意
不要直接操作 `/var/lib/docker/` 中。这些文件和目录由 Docker 管理。
:::
```bash
ls -l /var/lib/docker/overlay/
total 20
drwx------ 3 root root 4096 Jun 20 16:11 38f3ed2eac129654acef11c32670b534670c3a06e483fce313d72e3e0a15baa8
drwx------ 3 root root 4096 Jun 20 16:11 55f1e14c361b90570df46371b20ce6d480c434981cbda5fd68c6ff61aa0a5358
drwx------ 3 root root 4096 Jun 20 16:11 824c8a961a4f5e8fe4f4243dab57c5be798e7fd195f6d88ab06aea92ba931654
drwx------ 3 root root 4096 Jun 20 16:11 ad0fe55125ebf599da124da175174a4b8c1878afe6907bf7c78570341f308461
drwx------ 3 root root 4096 Jun 20 16:11 edab9b5e5bf73f2997524eebeac1de4cf9c8b904fa8ad3ec43b3504196aa3801
```
图像图层目录包含该图层独有的文件以及与较低图层共享的数据的硬链接。这允许有效地使用磁盘空间。
```bash
ls -i /var/lib/docker/overlay2/38f3ed2eac129654acef11c32670b534670c3a06e483fce313d72e3e0a15baa8/root/bin/ls
19793696 /var/lib/docker/overlay2/38f3ed2eac129654acef11c32670b534670c3a06e483fce313d72e3e0a15baa8/root/bin/ls
ls -i /var/lib/docker/overlay2/55f1e14c361b90570df46371b20ce6d480c434981cbda5fd68c6ff61aa0a5358/root/bin/ls
19793696 /var/lib/docker/overlay2/55f1e14c361b90570df46371b20ce6d480c434981cbda5fd68c6ff61aa0a5358/root/bin/ls
```
#### 容器层
容器也存在于 Docker 主机的文件系统中的磁盘上,位于 `/var/lib/docker/overlay/` 中。如果使用 `ls -l` 命令列出正在运行的容器的子目录,则存在三个目录和一个文件:
```bash
ls -l /var/lib/docker/overlay2/<directory-of-running-container>
total 16
-rw-r--r-- 1 root root 64 Jun 20 16:39 lower-id
drwxr-xr-x 1 root root 4096 Jun 20 16:39 merged
drwxr-xr-x 4 root root 4096 Jun 20 16:39 upper
drwx------ 3 root root 4096 Jun 20 16:39 work
```
`lower-id` 文件包含容器所基于的映像顶层的 ID即 OverlayFS `lowerdir`
```bash
cat /var/lib/docker/overlay2/ec444863a55a9f1ca2df72223d459c5d940a721b2288ff86a3f27be28b53be6c/lower-id
55f1e14c361b90570df46371b20ce6d480c434981cbda5fd68c6ff61aa0a5358
```
`upper` 目录包含容器的读写层的内容,对应于 OverlayFS `upperdir`
`merged` (合并)的目录是 `lowerdir``upperdirs` 的联合挂载,它包含正在运行的容器中的文件系统视图。
`work` (工作)目录是 OverlayFS 的内部目录。
要查看将 `overlay2` 存储驱动程序与 Docker 一起使用时存在的挂载,请使用 `mount` 命令。为了提高可读性,以下输出被截断。
```bash
mount | grep overlay
overlay on /var/lib/docker/overlay2/l/ec444863a55a.../merged
type overlay (rw,relatime,lowerdir=/var/lib/docker/overlay2/l/55f1e14c361b.../root,
upperdir=/var/lib/docker/overlay2/l/ec444863a55a.../upper,
workdir=/var/lib/docker/overlay2/l/ec444863a55a.../work)
```
第二行上的 `rw` 显示`overlay`(覆盖)挂载是读写的。
## 容器读取和写入如何与 `overlay2` 配合使用
### 读取文件
考虑三种情况,其中容器打开文件以进行 overlay 读取访问。
#### 文件在容器层中不存在
如果容器打开一个文件进行读取访问,并且该文件在容器中尚不存在 `upperdir`),则会从映像 `lowerdir` 中读取该文件。这会产生非常小的性能开销。
#### 该文件仅存在于容器层中
如果容器打开一个文件进行读取访问,并且该文件存在于容器 `upperdir` 中,而不是在映像 `lowerdir` 中,则直接从容器中读取该文件。
#### 该文件同时存在于容器层和图像层中
如果容器打开文件进行读取访问,并且该文件存在于映像层和容器层中,则会读取该文件在容器层中的版本。容器层 `upperdir` 中的文件会遮挡图像层 `lowerdir` 中具有相同名称的文件。
### 修改文件或目录
请考虑修改容器中文件的一些情况。
#### 首次写入文件
容器第一次写入现有文件时,该文件在容器中不存在 `upperdir`)。`overlay2` 驱动程序执行 `copy_up`将文件从镜像 `lowerdir` 复制到容器 `upperdir` 的操作。然后,容器将更改写入容器层中文件的新副本。
但是OverlayFS 在文件级别而不是块级别工作。这意味着所有 OverlayFS `copy_up`操作都会复制整个文件,即使文件很大并且只修改了一小部分。这可能会对容器写入性能产生明显影响。但是,有两件事值得注意:
- `copy_up` 操作仅在首次写入给定文件时发生。对同一文件的后续写入将针对已复制到容器的文件副本进行操作。
- OverlayFS 适用于多个图层。这意味着在具有多个图层的图像中搜索文件时,性能可能会受到影响。
#### 删除文件和目录
- 在容器中删除文件时,将在容器中创建一个 *whiteout* 文件 `upperdir`)。图像层中的文件版本 `lowerdir` 不会被删除(因为 `lowerdir` 是只读的。但是whiteout 文件会阻止它对容器可用。
- 在容器中删除*目录*时,将在容器内创建一个不透明目录 `upperdir`)。这与 whiteout 文件的工作方式相同,可以有效地防止访问该目录,即使它仍然存在于映像中 `lowerdir`)。
#### 重命名目录
只有当源路径和目标路径都位于顶层时,才允许为目录调用 `rename2`。否则,它将返回 `EXDEV` 错误“cross-device link not permitted”。您的应用程序需要设计为处理 `EXDEV` 并回退到“复制和取消链接”策略。
## OverlayFS 和 Docker 性能
`overlay2` 的性能可能比 `Btrfs` 好。但是,请注意以下详细信息:
### 页面缓存
OverlayFS 支持页面缓存共享。访问同一文件的多个容器共享该文件的单个页面缓存条目。这会使 `overlay2` 驱动程序对内存高效,是高密度用例的不错选择,例如 作为 PaaS。
### 复制
与其他写入时复制文件系统一样每当容器首次写入文件时OverlayFS 都会执行复制操作。这可能会增加写入操作的延迟,尤其是对于大型文件。但是,一旦文件被复制,对该文件的所有后续写入都将在上层进行,而无需进一步的复制操作。
### 性能最佳实践
以下通用性能最佳实践适用于 OverlayFS。
#### 使用快速存储
固态驱动器 SSD 的读取和写入速度比旋转磁盘更快。
#### 将卷用于写入密集型工作负载
卷为写入密集型工作负载提供最佳且最可预测的性能。这是因为它们绕过了存储驱动程序,并且不会产生精简配置和写入时复制引入的任何潜在开销。卷还有其他好处,例如允许您在容器之间共享数据,以及即使没有正在运行的容器正在使用数据也可以保留数据。
## OverlayFS 兼容性限制
总结 OverlayFS 与其他文件系统不兼容的方面:
### [`open(2)`](https://linux.die.net/man/2/open)
OverlayFS 仅实现 POSIX 标准的子集。 这可能会导致某些 OverlayFS 操作违反 POSIX 标准。一 此类操作称为复制操作。假设您的应用程序调用 `fd1=open“foo” O_RDONLY`,然后是 `fd2=open“foo” O_RDWR`。在这种情况下,您的应用程序希望 `fd1``fd2` 引用同一个文件。但是,由于在第二次调用 `open2` 之后发生了复制操作,因此 Descriptors 引用不同的文件。`fd1` 继续引用映像中的文件 `lowerdir`),而 fd2 引用容器中的文件 `upperdir`)。解决方法是触摸文件,这会导致发生复制操作。所有后续的 `open2` 操作,无论只读还是读写访问模式,都会引用容器中的文件 `upperdir`)。
除非安装了 `yum-plugin-ovl` 软件包,否则已知 `yum` 会受到影响。如果您的发行版(例如 6.8 或 7.2 之前的 RHEL/CentOS中没有 `yum-plugin-ovl` 软件包,您可能需要执行 `touch /var/lib/rpm/*` 在运行 `yum install` 之前。此软件包实现了上面提到的 `yum` 的`touch`(触摸)解决方法。
### [`rename(2)`](https://linux.die.net/man/2/rename)
OverlayFS 不完全支持 `rename2` 系统调用。您的应用程序需要检测其故障并回退到“复制和取消链接”策略。
[rootless-doc]:https://docs.docker.com/engine/security/rootless/

View File

@ -0,0 +1,118 @@
---
outline: [2, 5]
---
# 选择存储驱动程序
理想情况下,写入容器的可写层的数据非常少,您可以使用 Docker 卷写入数据。但是,某些工作负载要求您能够写入容器的可写层。这就是存储驱动程序的用武之地。
Docker 使用可插拔架构支持多个存储驱动程序。这 存储驱动程序控制映像和容器在 Docker 主机。阅读完 [Storage Driver Overview](https://docs.docker.com/engine/storage/drivers/),下一步是为您的工作负载选择最佳存储驱动程序。在最常见的场景中,使用整体性能和稳定性最佳的存储驱动程序。
:::warning 注意
本页讨论 Linux 上 Docker Engine 的存储驱动程序。如果你是 以 Windows 作为主机操作系统运行 Docker 守护程序,这是唯一支持的 存储驱动程序是 WindowsFilter。有关更多信息请参阅 [windows过滤器](https://docs.docker.com/engine/storage/drivers/windowsfilter-driver/)。
:::
Docker Engine 在 Linux 上提供以下存储驱动程序:
Driver | 描述
| - | - |
`overlay2` | `overlay2` 是当前支持的所有 Linux 发行版的首选存储驱动程序,不需要额外配置。
`fuse-overlayfs` | `fuse-overlayfs` 仅用于在不支持无根 `overlay2` 的旧主机上运行无根 Docker。从 Linux 内核 5.11 开始,不需要使用 `fuse-overlayfs` 驱动程序,即使在无根模式下,`overlay2` 也可以工作。请参阅 [rootless 模式文档](https://docs.docker.com/engine/security/rootless/)。
`btrfs``zfs` | `btrfs``zfs` 存储驱动程序允许使用高级选项,例如创建 “快照”,但需要更多的维护和设置。这些都依赖于正确配置的后备文件系统。
`vfs` | `vfs` 存储驱动程序用于测试目的,以及不能使用写入时复制文件系统的情况。此存储驱动程序的性能很差,通常不建议用于生产用途。
如果没有Docker 引擎有一个要使用的存储驱动程序的优先级列表 存储驱动程序,假设存储驱动程序满足 先决条件,并自动选择兼容的存储驱动程序。你 可以在 [Docker Engine 27.4.0 的源代码](https://github.com/moby/moby/blob/v27.4.0/daemon/graphdriver/driver_linux.go#L52-L53)。
某些存储驱动程序要求您对后备文件系统使用特定格式。 如果您有使用特定后备文件系统的外部要求,这可能会 限制您的选择。看 [支持的后备文件系统](https://docs.docker.com/engine/storage/drivers/select-storage-driver/#supported-backing-filesystems)。
缩小可以选择的存储驱动程序的范围后,您的选择 由工作负载的特征和稳定性级别决定 你需要。看 [有助于](https://docs.docker.com/engine/storage/drivers/select-storage-driver/#other-considerations)做出最终决定的其他注意事项。
## 每个 Linux 发行版支持的存储驱动程序
:::info 注意
Docker Desktop 不支持通过编辑守护程序配置文件来修改存储驱动程序。只有默认的 overlay2 驱动程序或 [containerd 存储](https://docs.docker.com/desktop/features/containerd/)。这 下表也不适用于无根中的 Docker 引擎 模式。有关在 rootless 模式下可用的驱动程序,请参阅 [无根模式文档](https://docs.docker.com/engine/security/rootless/)。
:::
您的操作系统和内核可能不支持每个存储驱动程序。例如,仅当系统使用 `btrfs` 作为存储时,才支持 `btrfs`。通常,以下配置适用于最新版本的 Linux 发行版:
Linux 发行版 | 推荐的存储驱动程序 | 其他驱动程序
| - | - | - |
Ubuntu | `overlay2` | `zfs`, `vfs`
Debian | `overlay2` | `vfs`
CentOS | `overlay2` | `zfs`, `vfs`
Fedora | `overlay2` | `zfs`, `vfs`
SLES 15 | `overlay2` | `vfs`
RHEL | `overlay2` | `vfs`
如有疑问,最好的全能配置是使用具有支持 `overlay2` 存储驱动程序的内核的现代 Linux 发行版,并将 Docker 卷用于写入密集型工作负载,而不是依赖将数据写入容器的可写层。
`vfs` 存储驱动程序通常不是最佳选择,主要用于在不支持其他存储驱动程序的情况下进行调试。在使用 `vfs` 存储驱动程序之前,请务必阅读 [其性能和存储特性和限制](https://docs.docker.com/engine/storage/drivers/vfs-driver/)。
众所周知,上表中的建议适用于大量用户。如果您使用推荐的配置并发现可重现的问题,则可能会很快得到修复。如果根据此表不推荐您要使用的驱动程序,您可以自行承担运行该驱动程序的风险。您可以而且仍然应该报告您遇到的任何问题。但是,此类问题的优先级低于使用推荐配置时遇到的问题。
根据您的 Linux 发行版,其他存储驱动程序(如 `btrfs`)可能可用。这些存储驱动程序对于特定用例可能具有优势,但可能需要额外的设置或维护,因此不建议将其用于常见场景。有关详细信息,请参阅这些存储驱动程序的文档。
## 支持的后备文件系统
对于 Docker后备文件系统是其中 `/var/lib/docker/` 的位置。某些存储驱动程序仅适用于特定的后备文件系统。
存储驱动程序 | 支持的后备文件系统
| - | - |
`overlay2` | `xfs` with ftype=1, `ext4`
`fuse-overlayfs` | 任何文件系统
`btrfs` | `btrfs`
`zfs` | `zfs`
`vfs` | 任何文件系统
## 其他注意事项
### 适合您的工作负载
此外,每个存储驱动程序都有自己的性能特征,使其或多或少适合不同的工作负载。考虑以下概括:
- `overlay2` 在文件级别而不是块级别运行。这样可以更有效地使用内存,但在写入密集型工作负载中,容器的可写层可能会变得相当大。
- 块级存储驱动程序(如 `btrfs``zfs`)对于写入密集型工作负载(尽管不如 Docker 卷)性能更好。
- `btrfs``zfs` 需要大量内存。
- `zfs` 是 PaaS 等高密度工作负载的不错选择。
有关性能、适用性和最佳实践的更多信息,请参阅每个存储驱动程序的文档。
### 共享存储系统和存储驱动程序
如果您使用 SAN、NAS、硬件 RAID 或其他共享存储系统这些系统可能会提供高可用性、更高的性能、精简配置、重复数据删除和压缩。在许多情况下Docker 可以在这些存储系统之上工作,但 Docker 并未与它们紧密集成。
每个 Docker 存储驱动程序都基于 Linux 文件系统或卷管理器。请务必遵循现有的最佳实践,在共享存储系统上运行存储驱动程序(文件系统或卷管理器)。例如,如果在共享存储系统上使用 ZFS 存储驱动程序,请确保遵循在该特定共享存储系统上运行 ZFS 文件系统的最佳实践。
### 稳定性
对于一些用户来说,稳定性比性能更重要。尽管 Docker 认为此处提到的所有存储驱动程序都是稳定的,但有些驱动程序较新,仍在积极开发中。通常,`overlay2` 提供最高的稳定性。
### 使用您自己的工作负载进行测试
您可以在不同的存储驱动程序上运行自己的工作负载时测试 Docker 的性能。确保使用等效的硬件和工作负载来匹配生产条件,以便您可以查看哪个存储驱动程序提供最佳的整体性能。
## 检查您当前的存储驱动程序
每个存储驱动程序的详细文档详细介绍了使用给定存储驱动程序的所有设置步骤。
要查看 Docker 当前正在使用的存储驱动程序,请使用 `docker info` 并查找 `Storage Driver` 行:
```bash
docker info
Containers: 0
Images: 0
Storage Driver: overlay2
Backing Filesystem: xfs
<...>
```
要更改存储驱动程序,请参阅新存储驱动程序的具体说明。某些驱动程序需要额外的配置,包括对 Docker 主机上的物理或逻辑磁盘的配置。
:::danger 重要
更改存储驱动程序时,任何现有映像和容器都将变得不可访问。这是因为新的存储驱动程序无法使用它们的层。如果您还原更改,则可以再次访问旧映像和容器,但使用新驱动程序提取或创建的任何映像和容器都将无法访问。
:::
## 相关信息
- [存储驱动程序][1]
- [`overlay2` 存储驱动程序][2]
- [`btrfs` 存储驱动程序][3]
- [`zfs` 存储驱动程序][4]
- [`WindowsFilter` 存储驱动程序][5]
[1]:https://baidu.com
[2]:https://baidu.com
[3]:https://baidu.com
[4]:https://baidu.com
[5]:https://baidu.com
[6]:https://baidu.com

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View File

@ -0,0 +1,88 @@
---
outline: [2,5]
---
# VFS 存储驱动程序
VFS 存储驱动程序不是联合文件系统。每个层都是磁盘上的一个目录,并且不支持写入时复制。要创建新图层,需要对前一图层进行“深层复制”。与其他存储驱动程序相比,这会导致性能较低且磁盘上使用的空间更多。但是,它健壮、稳定,并且适用于各种环境。它还可以用作在测试环境中验证其他存储后端的机制。
## 使用 `vfs` 存储驱动程序配置 Docker
1. 停止 Docker。
```bash
sudo systemctl stop docker
```
2. 编辑 `/etc/docker/daemon.json`。如果尚不存在,请创建它。假设文件为空,请添加以下内容。
```json
{
"storage-driver": "vfs"
}
```
如果要设置配额来控制 VFS 存储驱动程序可以使用的最大大小,请在 `storage-opts` 键上设置 `size` 选项。
```json
{
"storage-driver": "vfs",
"storage-opts": ["size=256M"]
}
```
如果 `daemon.json` 文件包含无效的 JSON则 Docker 不会启动。
3. 启动 Docker。
```bash
sudo systemctl start docker
```
4. 验证守护程序是否正在使用 `vfs` 存储驱动程序。使用 `docker info` 命令并查找 `Storage Driver`
```bash
docker info
Storage Driver: vfs
...
```
Docker 现在正在使用 `vfs` 存储驱动程序。Docker 已自动创建 `/var/lib/docker/vfs/` 目录,其中包含正在运行的容器使用的所有层。
## `vfs` 存储驱动程序的工作原理
每个映像层和可写容器层在 Docker 主机上表示为 `/var/lib/docker/` 中的子目录。union 挂载提供所有层的统一视图。目录名称并不直接与层本身的 ID 对应。
VFS 不支持写入时复制 COW。每次创建新图层时 它是其父图层的深层副本。这些图层都位于 `/var/lib/docker/vfs/dir/` 中。
### 示例:映像和容器磁盘构造
以下 `docker pull` 命令显示了一个 Docker 主机正在下载一个包含五个层的 Docker 镜像。
```bash
docker pull ubuntu
Using default tag: latest
latest: Pulling from library/ubuntu
e0a742c2abfd: Pull complete
486cb8339a27: Pull complete
dc6f0d824617: Pull complete
4f7a5649a30e: Pull complete
672363445ad2: Pull complete
Digest: sha256:84c334414e2bfdcae99509a6add166bbb4fa4041dc3fa6af08046a66fed3005f
Status: Downloaded newer image for ubuntu:latest
```
拉取后,这些层中的每一个都表示为 `/var/lib/docker/vfs/dir/` 中。目录名称与 `docker pull` 命令中显示的映像层 ID 不相关。要查看每一层在磁盘上占用的大小,您可以使用 `du -sh` 命令,该命令将大小作为人类可读的值提供。
```bash
ls -l /var/lib/docker/vfs/dir/
total 0
drwxr-xr-x. 2 root root 19 Aug 2 18:19 3262dfbe53dac3e1ab7dcc8ad5d8c4d586a11d2ac3c4234892e34bff7f6b821e
drwxr-xr-x. 21 root root 224 Aug 2 18:23 6af21814449345f55d88c403e66564faad965d6afa84b294ae6e740c9ded2561
drwxr-xr-x. 21 root root 224 Aug 2 18:23 6d3be4585ba32f9f5cbff0110e8d07aea5f5b9fbb1439677c27e7dfee263171c
drwxr-xr-x. 21 root root 224 Aug 2 18:23 9ecd2d88ca177413ab89f987e1507325285a7418fc76d0dcb4bc021447ba2bab
drwxr-xr-x. 21 root root 224 Aug 2 18:23 a292ac6341a65bf3a5da7b7c251e19de1294bd2ec32828de621d41c7ad31f895
drwxr-xr-x. 21 root root 224 Aug 2 18:23 e92be7a4a4e3ccbb7dd87695bca1a0ea373d4f673f455491b1342b33ed91446b
```
```bash
du -sh /var/lib/docker/vfs/dir/*
4.0K /var/lib/docker/vfs/dir/3262dfbe53dac3e1ab7dcc8ad5d8c4d586a11d2ac3c4234892e34bff7f6b821e
125M /var/lib/docker/vfs/dir/6af21814449345f55d88c403e66564faad965d6afa84b294ae6e740c9ded2561
104M /var/lib/docker/vfs/dir/6d3be4585ba32f9f5cbff0110e8d07aea5f5b9fbb1439677c27e7dfee263171c
125M /var/lib/docker/vfs/dir/9ecd2d88ca177413ab89f987e1507325285a7418fc76d0dcb4bc021447ba2bab
104M /var/lib/docker/vfs/dir/a292ac6341a65bf3a5da7b7c251e19de1294bd2ec32828de621d41c7ad31f895
104M /var/lib/docker/vfs/dir/e92be7a4a4e3ccbb7dd87695bca1a0ea373d4f673f455491b1342b33ed91446b
```
上面的输出显示,三层各占用 104M两层占用 125M。这些目录彼此之间只有很小的差异但它们都占用相同的磁盘空间量。这是使用 `vfs` 存储驱动程序的缺点之一。
## 相关信息
- [了解映像、容器和存储驱动程序](./../drivers/)
- [选择存储驱动程序](./select-storage-driver)

View File

@ -0,0 +1,29 @@
---
outline: [2,5]
---
# WindowsFilter 存储驱动程序
windowsfilter 存储驱动程序是 Windows 上 Docker Engine 的默认存储驱动程序。windowsfilter 驱动程序使用 Windows 原生文件系统层在磁盘上存储 Docker 层和卷数据。windowsfilter 存储驱动程序仅适用于使用 NTFS 格式化的文件系统。
## 配置 windowsfilter 存储驱动程序
对于大多数用例,无需配置 windowsfilter 存储驱动程序。
Windows 上 Docker Engine 的默认存储限制为 127GB。若要使用不同的存储大小请为 windowsfilter 存储设置 size 选项 司机。看 [windowsfilter 选项])[0]。
默认情况下,数据存储在 Docker 主机上 `C\ProgramData\docker``image``windowsfilter` 子目录中。您可以通过在 [守护进程配置文件][1]
```bash
{
"data-root": "d:\\docker"
}
```
您必须重新启动守护程序才能使配置更改生效。
## 其他信息:
有关容器存储在 Windows 上的工作原理的更多信息,请参阅 Microsoft 的 [Windows 上的容器文档][3]。
[0]:https://docs.docker.com/reference/cli/dockerd/#windowsfilter-options
[1]:https://docs.docker.com/reference/cli/dockerd/#on-windows
[3]:https://learn.microsoft.com/en-us/virtualization/windowscontainers/manage-containers/container-storage

View File

@ -0,0 +1,149 @@
---
outline: [2,5]
---
# ZFS 存储驱动程序
ZFS 是下一代文件系统,支持许多高级存储技术,如卷管理、快照、校验和、压缩和重复数据删除、复制等。
它由 Sun Microsystems现为 Oracle Corporation创建并根据 CDDL 许可证进行开源。由于 CDDL 和 GPL 之间的许可不兼容ZFS 不能作为主线 Linux 内核的一部分提供。但是ZFS On Linux ZoL 项目提供了树外内核模块和用户空间工具,这些工具可以单独安装。
ZFS on Linux ZoL 端口运行状况良好且日趋成熟。但是,目前不建议将 `zfs` Docker 存储驱动程序用于生产用途,除非您在 Linux 上具有丰富的 ZFS 使用经验。
:::warning 注意
Linux 平台上还有一个 ZFS 的 FUSE 实现。不建议这样做。本机 ZFS 驱动程序 ZoL 经过了更多的测试,具有更好的性能,并且使用范围更广。本文档的其余部分涉及本机 ZoL 端口。
:::
## 先决条件
- ZFS 需要一个或多个专用块设备,最好是固态驱动器 SSD
- `/var/lib/docker/` 目录必须挂载在 ZFS 格式的文件系统上。
- 更改存储驱动程序将使您已创建的任何容器在本地系统上都无法访问。使用 `docker save` 保存容器,并将现有镜像推送到 Docker Hub 或私有存储库,这样您以后就不需要重新创建它们。
:::warning 注意
无需使用 `MountFlags=slave`,因为 `dockerd``containerd` 位于不同的挂载命名空间中。
:::
## 使用 `zfs` 存储驱动程序配置 Docker
1. 停止 Docker。
2. 将 `/var/lib/docker/` 的内容复制到 `/var/lib/docker.bk`,并删除 `/var/lib/docker/` 的内容。
```bash
sudo cp -au /var/lib/docker /var/lib/docker.bk
sudo rm -rf /var/lib/docker/*
```
3. 在专用块设备或设备上创建新的 `zpool`,并将其挂载到 `/var/lib/docker/` 中。请确保指定了正确的设备,因为这是破坏性操作。此示例将两个设备添加到池中。
```bash
sudo zpool create -f zpool-docker -m /var/lib/docker /dev/xvdf /dev/xvdg
```
该命令将创建 `zpool` 并将其命名为 `zpool-docker`。该名称仅用于显示目的,您可以使用其他名称。使用 `zfs list` 检查池是否已正确创建和挂载。
```bash
sudo zfs list
NAME USED AVAIL REFER MOUNTPOINT
zpool-docker 55K 96.4G 19K /var/lib/docker
```
4. 将 Docker 配置为使用 `zfs`。编辑 `/etc/docker/daemon.json` 并设置 `storage-driver` 添加到 `zfs`。如果文件之前为空,则现在应如下所示:
```json
{
"storage-driver": "zfs"
}
```
保存并关闭文件。
5. 启动 Docker。使用 `docker info` 验证存储驱动程序是否为 `zfs`
```bash
sudo docker info
Containers: 0
Running: 0
Paused: 0
Stopped: 0
Images: 0
Server Version: 17.03.1-ce
Storage Driver: zfs
Zpool: zpool-docker
Zpool Health: ONLINE
Parent Dataset: zpool-docker
Space Used By Parent: 249856
Space Available: 103498395648
Parent Quota: no
Compression: off
<...>
```
## 管理 `zfs`
### 增加正在运行的设备的容量
要增加 `zpool` 的大小,您需要向 Docker 主机添加专用块设备,然后使用 `zpool add` 命令将其添加到 `zpool`
```bash
sudo zpool add zpool-docker /dev/xvdh
```
### 限制容器的可写存储配额
如果要按图像/数据集实施配额,则可以设置 `size` 存储选项来限制单个容器可用于其可写层的空间量。
编辑 `/etc/docker/daemon.json` 并添加以下内容:
```json
{
"storage-driver": "zfs",
"storage-opts": ["size=256M"]
}
```
查看每个存储驱动程序的所有存储选项 [守护进程参考文档](https://docs.docker.com/reference/cli/dockerd/#daemon-storage-driver)
保存并关闭文件,然后重新启动 Docker。
## `zfs` storage 驱动程序的工作原理
ZFS 使用以下对象:
- Filesystems精简配置按需从 `zpool` 分配空间。
- 快照:文件系统的只读空间高效时间点副本。
- clones快照的读写副本。用于存储与上一层的差异。
创建克隆的过程:
![zfs_clones](./zfs_clones.webp)
1. 从文件系统创建只读快照。
2. 将从快照创建可写克隆。这包含与父图层的任何差异。
文件系统、快照和克隆都从底层 `zpool`
### 磁盘上的映像和容器层
每个正在运行的容器的统一文件系统都挂载在 `/var/lib/docker/zfs/graph/` 中。请继续阅读,了解统一文件系统是如何组成的。
### 图像分层和共享
映像的基础层是 ZFS 文件系统。每个子层都是基于其下层的 ZFS 快照的 ZFS 克隆。容器是基于创建它的映像顶层的 ZFS 快照的 ZFS 克隆。
下图显示了如何将其与基于两层映像的正在运行的容器组合在一起。
![zfs_zpool](./zfs_zpool.webp)
当您启动容器时,将按顺序执行以下步骤:
1. 映像的 Base Layer 作为 ZFS 文件系统存在于 Docker 主机上。
2. 其他图像图层是托管其正下方图像图层的数据集的克隆。
在图中,通过拍摄 Base Layer 的 ZFS 快照,然后从该快照创建克隆来添加 “Layer 1”。克隆是可写的并按需占用 zpool 中的空间。快照是只读的,将 Base Layer 维护为不可变对象。
3. 启动容器时,将在图像上方添加一个可写层。
在图中,容器的读写层是通过创建映像顶层(第 1 层)的快照并从该快照创建克隆来创建的。
4. 当容器修改其可写层的内容时,将为更改的块分配空间。默认情况下,这些块为 128k。
## 容器读取和写入如何与 `zfs` 配合使用
### 读取文件
每个容器的可写层都是一个 ZFS 克隆,它与创建容器的数据集(其父层的快照)共享其所有数据。读取操作速度很快,即使正在读取的数据来自深层。下图说明了数据块共享的工作原理:
![zpool_blocks](./zpool_blocks.webp)
### 写入文件
编写新文件:从底层 `zpool` 按需分配空间 并且这些块直接写入容器的可写层。
修改现有文件:仅为更改的块分配空间,并且这些块使用写入时复制 CoW 策略写入容器的可写层。这样可以最小化层的大小并提高写入性能。
#### 删除文件或目录:
- 删除存在于较低层中的文件或目录时ZFS 驱动程序会掩盖容器的可写层中该文件或目录的存在,即使该文件或目录仍存在于较低的只读层中也是如此。
- 如果在容器的可写层中创建并删除文件或目录,则 zpool 将回收这些块。
## ZFS 和 Docker 性能
有几个因素会影响 Docker 的性能,使用 `ZFS` Storage 驱动程序。
- 内存:内存对 ZFS 性能有重大影响。ZFS 最初是为具有大量内存的大型企业级服务器设计的。
- ZFS 功能ZFS 包括重复数据删除功能。使用此功能可以节省磁盘空间,但会占用大量内存。建议您为与 Docker 一起使用的 `zpool` 禁用此功能,除非您使用的是 SAN、NAS 或其他硬件 RAID 技术。
- ZFS 缓存ZFS 将磁盘块缓存在称为自适应替换缓存 ARC 的内存结构中。ZFS 的单副本 *ARC* 功能允许块的单个缓存副本由 的多个克隆共享。使用此功能,多个正在运行的容器可以共享缓存块的单个副本。此功能使 ZFS 成为 PaaS 和其他高密度使用案例的不错选择。
- 碎片化:碎片化是写入时复制的自然副产品 像 ZFS 这样的文件系统。ZFS 通过使用 128k 的小块大小来缓解此问题。 ZFS 意图日志 ZIL 和写入的合并 (延迟写入) 有助于减少碎片化。您可以使用 `zpool status`。但是,如果不重新格式化和恢复文件系统,就无法对 ZFS 进行碎片整理。
- 使用适用于 Linux 的本机 ZFS 驱动程序:由于性能不佳,因此不建议使用 ZFS FUSE 实现。
### 性能最佳实践
- 使用快速存储:固态驱动器 SSD 提供比旋转磁盘更快的读取和写入速度。
- 将卷用于写入密集型工作负载:卷为写入密集型工作负载提供最佳且最可预测的性能。这是因为它们绕过了存储驱动程序,并且不会产生精简配置和写入时复制引入的任何潜在开销。卷还有其他好处,例如允许您在容器之间共享数据,以及即使没有正在运行的容器正在使用卷也可以保留它们。

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB