Compare commits
10 Commits
4178cfaef1
...
0ce4c931b9
Author | SHA1 | Date | |
---|---|---|---|
|
0ce4c931b9 | ||
|
0d74ca8d4b | ||
|
5476fe3a35 | ||
|
2682ec50b7 | ||
|
cf95804a01 | ||
|
f597aa9c4a | ||
|
657577678f | ||
|
5f3af6576c | ||
|
6f5c2ba42d | ||
|
5fc3e4d4f6 |
3
.gitignore
vendored
@ -55,5 +55,4 @@ Temporary Items
|
||||
|
||||
# Production
|
||||
node_modules/
|
||||
.vitepress/cache/
|
||||
.vitepress/cache/**/*
|
||||
/.vitepress/cache/
|
31
.vitepress/cache/deps/_metadata.json
vendored
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
3
.vitepress/cache/deps/package.json
vendored
@ -1,3 +0,0 @@
|
||||
{
|
||||
"type": "module"
|
||||
}
|
4512
.vitepress/cache/deps/vitepress___@vue_devtools-api.js
vendored
9370
.vitepress/cache/deps/vitepress___@vueuse_core.js
vendored
343
.vitepress/cache/deps/vue.js
vendored
@ -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
|
7
.vitepress/cache/deps/vue.js.map
vendored
@ -1,7 +0,0 @@
|
||||
{
|
||||
"version": 3,
|
||||
"sources": [],
|
||||
"sourcesContent": [],
|
||||
"mappings": "",
|
||||
"names": []
|
||||
}
|
@ -7,8 +7,8 @@ export default defineConfig({
|
||||
themeConfig: {
|
||||
// https://vitepress.dev/reference/default-theme-config
|
||||
nav: [
|
||||
{ text: 'Home', link: '/' },
|
||||
{ text: 'Examples', link: '/markdown-examples' }
|
||||
{ text: '家', link: '/' },
|
||||
{ text: '官网', link: 'https://docs.docker.com' }
|
||||
],
|
||||
|
||||
sidebar: {
|
||||
@ -17,7 +17,7 @@ export default defineConfig({
|
||||
text: "引擎",
|
||||
link: '/docs/engine/',
|
||||
items: [
|
||||
{
|
||||
{
|
||||
text: '安装',
|
||||
collapsed: true,
|
||||
link: '/docs/engine/about',
|
||||
@ -39,8 +39,13 @@ export default defineConfig({
|
||||
collapsed: true,
|
||||
link: '/docs/storage/drivers/',
|
||||
items: [
|
||||
{ text: "选择一个存储驱动", link: '/docs/storage/drivers/0' },
|
||||
{ text: "BTRFS storage driver", link: '/docs/storage/drivers/1' },
|
||||
{ text: "选择存储驱动程序", link: '/docs/storage/drivers/select-storage-driver' },
|
||||
{ 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' }
|
||||
@ -53,7 +58,7 @@ export default defineConfig({
|
||||
|
||||
|
||||
socialLinks: [
|
||||
{ icon: 'github', link: 'https://github.com/vuejs/vitepress' }
|
||||
// { icon: 'github', link: 'https://github.com/vuejs/vitepress' }
|
||||
]
|
||||
}
|
||||
})
|
||||
|
@ -2,7 +2,7 @@
|
||||
import NavList from '/components/navBtn.vue'
|
||||
const nav_lists =[
|
||||
{
|
||||
href: "baidu.com",
|
||||
href: "/docs/engine/about",
|
||||
title: '安装Docker引擎',
|
||||
desc: '学习如何为你的发行版安装开源的Docker引擎。'
|
||||
},
|
||||
|
BIN
docs/storage/drivers/btfs_container_layer.webp
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
docs/storage/drivers/btfs_pool.webp
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
docs/storage/drivers/btfs_snapshots.webp
Normal file
After Width: | Height: | Size: 9.3 KiB |
BIN
docs/storage/drivers/btfs_subvolume.webp
Normal file
After Width: | Height: | Size: 22 KiB |
170
docs/storage/drivers/btrfs-driver.md
Normal 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”显示其自己的内部目录树。
|
||||
|
||||

|
||||
|
||||
仅图像的基础层被存储为真正的子体积。所有其他层都存储为快照,其中仅包含该层中引入的差异。您可以创建快照的快照,如下图所示。
|
||||
|
||||

|
||||
|
||||
在磁盘上,快照看起来和感觉上都像子卷,但实际上它们要小得多并且更节省空间。写时复制用于最大化存储效率并最小化层大小,并且容器可写层中的写入在块级别进行管理。下图显示了子卷及其快照共享数据。
|
||||
|
||||

|
||||
|
||||
为了最大程度地提高效率,当容器需要更多空间时,会以大约 1 GB 大小的块进行分配。
|
||||
|
||||
Docker 的`btrfs`存储驱动程序将每个镜像层和容器存储在其自己的 Btrfs 子卷或快照中。映像的基础层存储为子卷,而子映像层和容器存储为快照。如下图所示。
|
||||
|
||||

|
||||
|
||||
在运行`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
|
528
docs/storage/drivers/device-mapper-driver.md
Normal 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` 容器。
|
||||
|
||||

|
||||
|
||||
## 容器读取和写入如何与 `devicemapper` 配合使用
|
||||
### 读取文件
|
||||
使用 `devicemapper` 时,读取发生在块级别。下图显示了在示例容器中读取单个块 (`0x44f`) 的高级过程。
|
||||
|
||||

|
||||
|
||||
应用程序对容器中的块`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
|
BIN
docs/storage/drivers/dm_container.webp
Normal file
After Width: | Height: | Size: 39 KiB |
@ -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` 语句首先从 `ubuntu:22.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 以实现高效映像。
|
||||
|
||||
这些层彼此堆叠在一起。创建新容器时,您会在底层层之上添加新的可写层。此层通常称为 “容器层”。对正在运行的容器所做的所有更改(例如写入新文件、修改现有文件和删除文件)都将写入此精简的可写容器层。下图显示了基于 `ubuntu:15.04` 映像的容器。
|
||||
|
||||

|
||||
|
||||
存储驱动程序处理有关这些层彼此交互方式的详细信息。可以使用不同的存储驱动程序,这些驱动程序在不同情况下各有优点和缺点。
|
||||
|
||||
## 容器和图层
|
||||
容器和映像之间的主要区别在于顶部可写层。对容器的所有添加新数据或修改现有数据的写入都存储在此可写层中。删除容器时,可写层也会被删除。底层图像保持不变。
|
||||
|
||||
由于每个容器都有自己的可写容器层,并且所有更改都存储在此容器层中,因此多个容器可以共享对同一底层映像的访问,但具有自己的数据状态。下图显示了共享同一 Ubuntu 15.04 映像的多个容器。
|
||||
|
||||

|
||||
|
||||
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-image:1.0` 的映像。
|
||||
|
||||
```dockerfile:line-numbers
|
||||
# syntax=docker/dockerfile:1
|
||||
FROM alpine
|
||||
RUN apk add --no-cache bash
|
||||
```
|
||||
|
||||
第二个基于 `acme/my-base-image:1.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-image:1.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)
|
BIN
docs/storage/drivers/overlay_constructs.webp
Normal file
After Width: | Height: | Size: 37 KiB |
285
docs/storage/drivers/overlayfs-driver.md
Normal 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` 的目录公开,该目录实际上是容器挂载点。
|
||||
|
||||

|
||||
|
||||
如果图像层和容器层包含相同的文件,则容器层 (`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`)。
|
||||
|
||||
#### 重命名目录
|
||||
只有当源路径和目标路径都位于顶层时,才允许为目录调用 `rename(2)`。否则,它将返回 `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` 引用同一个文件。但是,由于在第二次调用 `open(2)` 之后发生了复制操作,因此 Descriptors 引用不同的文件。`fd1` 继续引用映像中的文件 (`lowerdir`),而 fd2 引用容器中的文件 (`upperdir`)。解决方法是触摸文件,这会导致发生复制操作。所有后续的 `open(2)` 操作,无论只读还是读写访问模式,都会引用容器中的文件 (`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 不完全支持 `rename(2`) 系统调用。您的应用程序需要检测其故障并回退到“复制和取消链接”策略。
|
||||
|
||||
|
||||
[rootless-doc]:https://docs.docker.com/engine/security/rootless/
|
118
docs/storage/drivers/select-storage-driver.md
Normal 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
|
BIN
docs/storage/drivers/two_dm_container.webp
Normal file
After Width: | Height: | Size: 31 KiB |
88
docs/storage/drivers/vfs-driver.md
Normal 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)
|
29
docs/storage/drivers/windowsfilter-driver.md
Normal 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
|
149
docs/storage/drivers/zfs-driver.md
Normal 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:快照的读写副本。用于存储与上一层的差异。
|
||||
|
||||
创建克隆的过程:
|
||||

|
||||
1. 从文件系统创建只读快照。
|
||||
2. 将从快照创建可写克隆。这包含与父图层的任何差异。
|
||||
|
||||
文件系统、快照和克隆都从底层 `zpool` 的
|
||||
|
||||
### 磁盘上的映像和容器层
|
||||
每个正在运行的容器的统一文件系统都挂载在 `/var/lib/docker/zfs/graph/` 中。请继续阅读,了解统一文件系统是如何组成的。
|
||||
### 图像分层和共享
|
||||
映像的基础层是 ZFS 文件系统。每个子层都是基于其下层的 ZFS 快照的 ZFS 克隆。容器是基于创建它的映像顶层的 ZFS 快照的 ZFS 克隆。
|
||||
|
||||
下图显示了如何将其与基于两层映像的正在运行的容器组合在一起。
|
||||
|
||||

|
||||
|
||||
当您启动容器时,将按顺序执行以下步骤:
|
||||
1. 映像的 Base Layer 作为 ZFS 文件系统存在于 Docker 主机上。
|
||||
2. 其他图像图层是托管其正下方图像图层的数据集的克隆。
|
||||
在图中,通过拍摄 Base Layer 的 ZFS 快照,然后从该快照创建克隆来添加 “Layer 1”。克隆是可写的,并按需占用 zpool 中的空间。快照是只读的,将 Base Layer 维护为不可变对象。
|
||||
3. 启动容器时,将在图像上方添加一个可写层。
|
||||
在图中,容器的读写层是通过创建映像顶层(第 1 层)的快照并从该快照创建克隆来创建的。
|
||||
4. 当容器修改其可写层的内容时,将为更改的块分配空间。默认情况下,这些块为 128k。
|
||||
|
||||
## 容器读取和写入如何与 `zfs` 配合使用
|
||||
### 读取文件
|
||||
每个容器的可写层都是一个 ZFS 克隆,它与创建容器的数据集(其父层的快照)共享其所有数据。读取操作速度很快,即使正在读取的数据来自深层。下图说明了数据块共享的工作原理:
|
||||
|
||||

|
||||
|
||||
### 写入文件
|
||||
编写新文件:从底层 `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) 提供比旋转磁盘更快的读取和写入速度。
|
||||
- 将卷用于写入密集型工作负载:卷为写入密集型工作负载提供最佳且最可预测的性能。这是因为它们绕过了存储驱动程序,并且不会产生精简配置和写入时复制引入的任何潜在开销。卷还有其他好处,例如允许您在容器之间共享数据,以及即使没有正在运行的容器正在使用卷也可以保留它们。
|
BIN
docs/storage/drivers/zfs_clones.webp
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
docs/storage/drivers/zfs_zpool.webp
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
docs/storage/drivers/zpool_blocks.webp
Normal file
After Width: | Height: | Size: 23 KiB |