Compare commits
6 Commits
4178cfaef1
...
cf95804a01
Author | SHA1 | Date | |
---|---|---|---|
|
cf95804a01 | ||
|
f597aa9c4a | ||
|
657577678f | ||
|
5f3af6576c | ||
|
6f5c2ba42d | ||
|
5fc3e4d4f6 |
3
.gitignore
vendored
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
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
3
.vitepress/cache/deps/package.json
vendored
@ -1,3 +0,0 @@
|
||||
{
|
||||
"type": "module"
|
||||
}
|
4512
.vitepress/cache/deps/vitepress___@vue_devtools-api.js
vendored
4512
.vitepress/cache/deps/vitepress___@vue_devtools-api.js
vendored
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
9370
.vitepress/cache/deps/vitepress___@vueuse_core.js
vendored
9370
.vitepress/cache/deps/vitepress___@vueuse_core.js
vendored
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
343
.vitepress/cache/deps/vue.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
7
.vitepress/cache/deps/vue.js.map
vendored
@ -1,7 +0,0 @@
|
||||
{
|
||||
"version": 3,
|
||||
"sources": [],
|
||||
"sourcesContent": [],
|
||||
"mappings": "",
|
||||
"names": []
|
||||
}
|
@ -39,7 +39,7 @@ export default defineConfig({
|
||||
collapsed: true,
|
||||
link: '/docs/storage/drivers/',
|
||||
items: [
|
||||
{ text: "选择一个存储驱动", link: '/docs/storage/drivers/0' },
|
||||
{ text: "选择存储驱动程序", link: '/docs/storage/drivers/select-storage-driver' },
|
||||
{ text: "BTRFS storage driver", link: '/docs/storage/drivers/1' },
|
||||
]
|
||||
},
|
||||
|
@ -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)
|
8
docs/storage/drivers/select-storage-driver.md
Normal file
8
docs/storage/drivers/select-storage-driver.md
Normal file
@ -0,0 +1,8 @@
|
||||
---
|
||||
outline: [2, 5]
|
||||
---
|
||||
|
||||
# 选择存储驱动程序
|
||||
理想情况下,写入容器的可写层的数据非常少,您可以使用 Docker 卷写入数据。但是,某些工作负载要求您能够写入容器的可写层。这就是存储驱动程序的用武之地。
|
||||
|
||||
Docker 使用可插拔架构支持多个存储驱动程序。这 存储驱动程序控制映像和容器在 Docker 主机。阅读完 [Storage Driver Overview](https://docs.docker.com/engine/storage/drivers/),下一步是为您的工作负载选择最佳存储驱动程序。在最常见的场景中,使用整体性能和稳定性最佳的存储驱动程序。
|
Loading…
Reference in New Issue
Block a user