1 Commits

Author SHA1 Message Date
hellozrh
185af1abc1 Merge 8d129d0ead into 85c5a280a4 2023-07-19 07:22:09 +00:00
434 changed files with 6684 additions and 16404 deletions

15
.drone.yml Normal file
View File

@@ -0,0 +1,15 @@
kind: pipeline
type: docker
name: build
steps:
- name: build
image: yeasy/docker_practice:latest
pull: if-not-exists # always never
environment:
TZ: Asia/Shanghai
commands:
- docker-entrypoint.sh build
trigger:
branch:
- master

2
.gitattributes vendored
View File

@@ -1,4 +1,4 @@
* text=auto eol=lf
* text=auto
*.sh text eol=lf

42
.github/CODEOWNERS vendored
View File

@@ -2,28 +2,28 @@
/.github/* @khs1994
/.travis/* @khs1994
/.vuepress/* @khs1994
/01_introduction/* @yeasy @khs1994
/02_basic_concept/* @yeasy @khs1994
/03_install/* @khs1994
/04_image/* @yeasy @khs1994
/05_container/* @yeasy @khs1994
/06_repository/* @khs1994
/07_dockerfile/* @yeasy @khs1994
/08_data/* @yeasy @khs1994
/09_network/* @yeasy @khs1994
/10_buildx/* @khs1994
/11_compose/* @yeasy @khs1994
/12_implementation/* @yeasy @khs1994
/13_kubernetes_concepts/* @yeasy @khs1994
/14_kubernetes_setup/* @yeasy @khs1994
/15_etcd/* @yeasy @khs1994
/16_cloud/* @khs1994
/17_ecosystem/* @khs1994
/18_security/* @yeasy @khs1994
/19_observability/* @yeasy @khs1994
/20_cases_os/* @yeasy @khs1994
/21_case_devops/* @yeasy @khs1994
/advanced_network/* @yeasy @khs1994
/appendix/* @yeasy @khs1994
/archive/* @khs1994
/basic_concept/* @yeasy @khs1994
/buildx/* @khs1994
/cases/* @yeasy @khs1994
/cloud/* @khs1994
/compose/* @yeasy @khs1994
/container/* @yeasy @khs1994
/coreos/* @khs1994
/data_management/* @khs1994
/etcd/* @khs1994
/IDE/* @khs1994
/image/* @yeasy @khs1994
/install/* @khs1994
/introduction/* @yeasy @khs1994
/kubernetes/* @yeasy @khs1994
/network/* @yeasy @khs1994
/opensource/* @khs1994
/repository/* @khs1994
/security/* @yeasy @khs1994
/underly/* @yeasy @khs1994
/.drone.yml @khs1994
/.editorconfig/ @khs1994
/.gitattributes @khs1994

View File

@@ -7,7 +7,6 @@ about: Create a report to help us improve
* [ ] Have u googled the problem? If no, pls do that first!
### Environment
<!--请提供环境信息包括操作系统版本等保留你的操作系统其他选项删除-->
<!--Provides env info like OS version-->
@@ -22,7 +21,6 @@ about: Create a report to help us improve
* [x] Others (Pls describe below)
### Docker Version
<!--如果你的 Docker 版本低于 20.10 请尽可能升级到该版本保留你的 Docker 版本其他选项删除-->
<!--if Docker version under 20.10, please upgrade Docker to 20.10-->
@@ -31,9 +29,13 @@ about: Create a report to help us improve
* [x] 1.13.0 or Before
### Problem Description
<!--描述你的问题请贴出操作步骤终端报错截图或文字信息-->
<!--describe problem with detailed steps and logs-->
<!--提交问题之前请点击预览标签符合要求之后再提交问题-->

View File

@@ -7,7 +7,6 @@ about: Create a issue about Docker
* [ ] Have u googled the problem? If no, pls do that first!
### Environment
<!--请提供环境信息包括操作系统版本等保留你的操作系统其他选项删除-->
<!--Provides env info like OS version-->
@@ -22,7 +21,6 @@ about: Create a issue about Docker
* [x] Others (Pls describe below)
### Docker Version
<!--如果你的 Docker 版本低于 20.10 请尽可能升级到该版本保留你的 Docker 版本其他选项删除-->
<!--if Docker version under 20.10, please upgrade Docker to 20.10-->
@@ -31,9 +29,13 @@ about: Create a issue about Docker
* [x] 1.13.0 or Before
### Problem Description
<!--描述你的问题请贴出操作步骤终端报错截图或文字信息-->
<!--describe problem with detailed steps and logs-->
<!--提交问题之前请点击预览标签符合要求之后再提交问题-->

View File

@@ -1,6 +1,6 @@
<!--
Thanks for your contribution.
See [CONTRIBUTING](../CONTRIBUTING.md) for contribution guidelines.
See [CONTRIBUTING](CONTRIBUTING.md) for contribution guidelines.
-->
**Proposed changes (Mandatory)**

View File

@@ -1,27 +0,0 @@
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
commit-message:
prefix: "chore(deps)"
labels:
- "dependencies"
groups:
dependencies:
patterns:
- "*"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
commit-message:
prefix: "chore(deps)"
labels:
- "dependencies"
groups:
dependencies:
patterns:
- "*"

View File

@@ -1,6 +1,8 @@
name: Check link
on:
# push:
# pull_request:
workflow_dispatch:
jobs:
@@ -8,7 +10,9 @@ jobs:
name: check-link
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6.0.2
- uses: actions/checkout@master
with:
fetch-depth: 1
# search Issues :-(
- run: |
docker run -i --rm \

View File

@@ -1,10 +1,10 @@
name: CI
on:
push:
pull_request:
workflow_dispatch:
name: CI
defaults:
run:
shell: bash --noprofile --norc -exo pipefail {0}
@@ -14,7 +14,9 @@ jobs:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6.0.2
- uses: actions/checkout@master
with:
fetch-depth: 2
- name: Build Gitbook
uses: docker://yeasy/docker_practice
with:
@@ -66,8 +68,8 @@ jobs:
PCIT_USERNAME: khs1994
- name: vuepress
run: |
export NODE_OPTIONS=--openssl-legacy-provider
sudo rm -rf _book
# npm i vuepress --save-dev
npm i
git clone https://github.com/docker-practice/.vuepress .vuepress2
@@ -78,19 +80,22 @@ jobs:
npx vuepress --version
npm run vuepress:build
# echo "vuepress.docker-practice.com" > .vuepress/dist/CNAME
echo "vuepress.mirror.docker-practice.com" > .vuepress/dist/CNAME
cp -r _images .vuepress/dist
mkdir -p .vuepress/dist/appendix
cp -r advanced_network/_images .vuepress/dist/advanced_network
cp -r appendix/_images .vuepress/dist/appendix
mkdir -p .vuepress/dist/cloud
cp -r 16_cloud/_images .vuepress/dist/cloud
mkdir -p .vuepress/dist/data_management
cp -r 08_data/_images .vuepress/dist/data_management
mkdir -p .vuepress/dist/etcd
cp -r 15_etcd/_images .vuepress/dist/etcd
mkdir -p .vuepress/dist/kubernetes
cp -r 13_kubernetes_concepts/_images .vuepress/dist/kubernetes
cp -r cases/ci/drone/_images .vuepress/dist/cases/ci/drone
cp -r cases/os/_images .vuepress/dist/cases/os
cp -r cloud/_images .vuepress/dist/cloud
cp -r data_management/_images .vuepress/dist/data_management
cp -r etcd/_images .vuepress/dist/etcd
cp -r image/_images .vuepress/dist/image
cp -r install/_images .vuepress/dist/install
cp -r introduction/_images .vuepress/dist/introduction
cp -r kubernetes/_images .vuepress/dist/kubernetes
cp -r underly/_images .vuepress/dist/underly
echo "include: [_images]" > .vuepress/dist/_config.yml
- name: Upload Vuepress dist

View File

@@ -1,27 +0,0 @@
name: Dependabot auto-merge
on: pull_request
permissions:
contents: write
pull-requests: write
jobs:
dependabot:
runs-on: ubuntu-latest
if: github.actor == 'dependabot[bot]'
steps:
- name: Dependabot metadata
id: metadata
uses: dependabot/fetch-metadata@v2
with:
github-token: "${{ secrets.GITHUB_TOKEN }}"
- name: Approve a PR
run: gh pr review --approve "$PR_URL"
env:
PR_URL: ${{github.event.pull_request.html_url}}
GH_TOKEN: ${{secrets.GITHUB_TOKEN}}
- name: Enable auto-merge for Dependabot PRs
run: gh pr merge --auto --merge "$PR_URL"
env:
PR_URL: ${{github.event.pull_request.html_url}}
GH_TOKEN: ${{secrets.GITHUB_TOKEN}}

18
.gitignore vendored
View File

@@ -3,7 +3,6 @@
*.tmp
.idea/
_book/
format_report.txt
*.swp
*.edx
.DS_Store
@@ -12,20 +11,3 @@ node_modules/
package-lock.json
docker-compose.override.yml
# Editor configs
.obsidian/
.vscode/
.agent/
__pycache__/
# Check scripts
check_project_rules.py
check_dashes.py
checker.py
find_lists_no_space.py
fix_missing_spaces.py
fix_project_rules.py
fixer.py
format_headings.py

26
.travis/Dockerfile Normal file
View File

@@ -0,0 +1,26 @@
FROM node:14.4.0-alpine
ENV TZ=Asia/Shanghai
WORKDIR /srv/gitbook
COPY book.json book.json
COPY docker-entrypoint.sh /usr/local/bin/
RUN set -x && apk add --no-cache \
tzdata bash \
&& npm install -g gitbook-cli \
&& gitbook install \
&& ln -s /usr/local/bin/docker-entrypoint.sh / \
&& rm -rf /root/.npm /tmp/*
EXPOSE 4000
VOLUME /srv/gitbook-src
WORKDIR /srv/gitbook-src
ENTRYPOINT ["docker-entrypoint.sh"]
CMD server

37
.travis/book.json Normal file
View File

@@ -0,0 +1,37 @@
{
"title": "Docker -- 从入门到实践",
"author": "yeasy",
"language": "zh-hans",
"links": {
"sidebar": {
"GitHub": "https://github.com/yeasy/docker_practice"
}
},
"plugins": [
"-livereload",
"image-captions",
"github",
"page-treeview@2.9.8",
"editlink"
],
"pluginsConfig": {
"image-captions": {
"attributes": {
"width": "600"
},
"caption": "图 _PAGE_LEVEL_._PAGE_IMAGE_NUMBER_ - _CAPTION_"
},
"github": {
"url": "https://github.com/yeasy/docker_practice"
},
"editlink": {
"base": "https://github.com/yeasy/docker_practice/blob/master/",
"label": "编辑本页"
},
"page-treeview": {
"copyright": "Copyright &#169; yeasy",
"minHeaderCount": "2",
"minHeaderDeep": "2"
}
}
}

43
.travis/conf.d/nginx.conf Normal file
View File

@@ -0,0 +1,43 @@
user root;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
#gzip on;
index index.html index.php;
server {
server_name localhost;
listen 4000;
root /srv/www/;
index index.html;
}
}

View File

@@ -0,0 +1,5 @@
sut:
build: .
volumes:
- ../:/srv/gitbook-src
command: build

23
.travis/docker-entrypoint.sh Executable file
View File

@@ -0,0 +1,23 @@
#!/bin/sh
START=`date "+%F %T"`
if [ $1 = "sh" ];then sh ; exit 0; fi
rm -rf node_modules _book
srcDir=$PWD
cp -a . /srv/gitbook
cd /srv/gitbook
main(){
if [ "$1" = build ];then
gitbook build && cp -a _book $srcDir && echo $START && date "+%F %T" && exit 0
else
exec gitbook serve
fi
}
main $1 $2 $3

8
.travis/update.sh Executable file
View File

@@ -0,0 +1,8 @@
#!/usr/bin/env bash
# cd .travis
# ./update.sh
if [ ! -f Dockerfile ];then exit 1; fi
cp -a ../book.json book.json

View File

@@ -106,7 +106,7 @@ module.exports = config({
},
{
text: '安装 Docker',
link: '/03_install/',
link: '/install/',
},
{
text: 'Docker 入门',
@@ -114,23 +114,23 @@ module.exports = config({
},
{
text: 'Docker 实战',
link: '/15_cases/os/'
link: '/cases/os/'
},
{
text: 'CI/CD',
link: '/15_cases/ci/'
link: '/cases/ci/'
},
{
text: 'Compose',
link: '/10_compose/',
link: '/compose/',
},
{
text: 'Kubernetes',
link: '/12_orchestration/kubernetes/',
link: '/kubernetes/',
},
{
text: "云计算",
link: "/13_ecosystem/cloud/",
link: "/cloud/",
},
// {
// text: 'GitHub',
@@ -152,8 +152,7 @@ module.exports = config({
// }]
// }
],
sidebar: "auto",
legacySidebar: {
sidebar: {
'/cloud/': [
'intro',
'tencentCloud',
@@ -296,8 +295,8 @@ module.exports = config({
'image/dockerfile/shell',
'image/dockerfile/onbuild',
'image/dockerfile/references',
'image/dockerfile/7.17_multistage_builds.md',
'image/dockerfile/7.18_multistage_builds_laravel.md',
'image/multistage-builds/',
'image/multistage-builds/laravel',
'image/manifest',
]
}, {

View File

@@ -1,3 +0,0 @@
node_modules/
.vuepress/
.git/

View File

@@ -1,68 +0,0 @@
## 1.1 快速上手
本节将通过一个简单的 Web 应用例子带你快速体验 Docker 的核心流程构建镜像运行容器
### 1.1.1 准备代码
创建一个名为 `hello-docker` 的文件夹并在其中创建一个 `index.html` 文件
```html
<h1>Hello, Docker!</h1>
```
### 1.1.2 编写 Dockerfile
在同级目录下创建一个名为 `Dockerfile` (无后缀) 的文件
```dockerfile
FROM nginx:alpine
COPY index.html /usr/share/nginx/html/index.html
```
### 1.1.3 构建镜像
打开终端进入该目录执行构建命令
```bash
$ docker build -t my-hello-world .
```
* `docker build`构建命令
* `-t my-hello-world`给镜像起个名字 (标签)
* `.`指定上下文路径为当前目录
### 1.1.4 运行容器
使用刚才构建的镜像启动一个容器
```bash
$ docker run -d -p 8080:80 my-hello-world
```
* `docker run`运行命令
* `-d`后台运行
* `-p 8080:80`将宿主机的 8080 端口映射到容器的 80 端口
### 1.1.5 访问测试
打开浏览器访问 [http://localhost:8080](http://localhost:8080),你应该能看到 “HelloDocker”。
### 1.1.6 清理
停止并删除容器
```bash
## 查看正在运行的容器 ID
$ docker ps
## 停止容器
$ docker stop <CONTAINER_ID>
## 删除容器
$ docker rm <CONTAINER_ID>
```
恭喜你已经完成了第一次 Docker 实战接下来请阅读 [Docker 核心概念](../02_basic_concept/README.md)做深入了解

View File

@@ -1,120 +0,0 @@
## 1.2 什么是 Docker
Docker 是彻底改变了软件开发和交付方式的革命性技术本节将从核心概念与传统虚拟机的对比技术基础以及历史生态等多个维度带你深入理解什么是 Docker
### 1.2.1 一句话理解 Docker
> **Docker 是一种轻量级的虚拟化技术它让应用程序及其依赖环境可以被打包成一个标准化的单元在任何地方都能一致地运行** 如果用一个生活中的类比**Docker 之于软件就像集装箱之于货物**
在集装箱发明之前货物的运输是一件麻烦的事情不同的货物需要不同的包装不同的装卸方式换一种运输工具就要重新装卸集装箱的出现改变了这一切无论里面装的是什么集装箱的外形是标准的可以用同样的方式装卸堆放和运输
Docker 做的事情类似无论你的应用是用 PythonJavaNode.js 还是其他语言写的无论它需要什么样的依赖库和环境一旦被打包成 Docker 镜像就可以用同样的方式在任何支持 Docker 的机器上运行
### 1.2.2 Docker 的核心价值
笔者认为Docker 解决的是软件开发中最古老的问题之一**在我机器上明明能跑啊**
```mermaid
flowchart LR
subgraph Dev ["开发环境"]
direction TB
A["Python 3.14<br/>Ubuntu 24.04<br/>特定版本的库"] --> B["运行正常"]
end
subgraph Prod ["生产环境"]
direction TB
C["Python 3.11<br/>Ubuntu 22.04<br/>不同版本的库"] --> D["运行失败!"]
end
A -. "≠" .-> C
```
有了 Docker
```mermaid
flowchart LR
subgraph Dev ["开发环境"]
direction TB
A["Docker 镜像<br/>(包含所有依赖)"] --> B["运行正常"]
end
subgraph Prod ["生产环境"]
direction TB
C["同一个镜像<br/>(完全一致)"] --> D["运行正常!"]
end
A === "=" === C
```
### 1.2.3 Docker vs 虚拟机
很多人第一次接触 Docker 时会问**这不就是虚拟机吗** 答案是**不是而且差别很大**
#### 传统虚拟机
传统虚拟机技术是虚拟出一套完整的硬件在其上运行一个完整的操作系统再在该系统上运行应用
![传统虚拟化](../_images/virtualization.png)
#### Docker 容器
Docker 容器内的应用直接运行于宿主的内核容器内没有自己的内核也没有进行硬件虚拟
![Docker](../_images/docker.png)
#### 关键区别
| 特性 | Docker 容器 | 传统虚拟机 |
|------|-------------|------------|
| **启动速度** | 秒级 | 分钟级 |
| **资源占用** | MB 级别 | GB 级别 |
| **性能** | 接近原生 | 有明显损耗 |
| **隔离级别** | 进程级隔离 | 完全隔离 |
| **单机数量** | 可运行上千个 | 通常几十个 |
> 笔者经常用这个类比来解释虚拟机像是每个应用都住在一栋独立的房子里 (有自己的地基水电系统)而容器像是大家住在同一栋公寓楼里的不同房间 (共享地基和水电系统但各自独立)
### 1.2.4 Docker 的技术基础
Docker 使用 [Go 语言](https://golang.google.cn/)开发,基于 Linux 内核的以下技术:
- **[Namespace](https://en.wikipedia.org/wiki/Linux_namespaces)**:实现资源隔离 (进程、网络、文件系统等)
- **[Cgroups](https://zh.wikipedia.org/wiki/Cgroups)**:实现资源限制 (CPU、内存、I/O 等)
- **[Union FS](https://en.wikipedia.org/wiki/Union_mount)**:实现分层存储 (如 OverlayFS)
> 如果你对这些底层技术感兴趣可以阅读本书的[底层实现](../12_implementation/README.md)章节
#### Docker 架构演进
Docker 的底层实现经历了多次演进
```mermaid
flowchart LR
subgraph Timeline
direction LR
LXC["LXC<br/>(2013)"] --> libcontainer["libcontainer<br/>(2014)"]
libcontainer --> runC["runC<br/>(2015)"]
runC --> containerd["containerd + runC<br/>(现在)"]
runC --> OCI["OCI<br/>标准化"]
end
```
- **LXC** (2013)Docker 最初基于 Linux Containers
- **libcontainer** (2014v0.7)Docker 自研的容器运行时
- **runC** (2015v1.11)捐献给 OCI 的标准容器运行时
- **containerd**高级容器运行时管理容器生命周期
![Docker 架构](../_images/docker-on-linux.png)
> `runc` 是一个 Linux 命令行工具用于根据 [OCI 容器运行时规范](https://github.com/opencontainers/runtime-spec)创建和运行容器。
> `containerd` 是一个守护程序它管理容器生命周期提供了在一个节点上执行容器和管理镜像的最小功能集
### 1.2.5 Docker 的历史与生态
**Docker** 最初是 `dotCloud` 公司创始人 [Solomon Hykes](https://github.com/shykes) 在法国期间发起的一个公司内部项目,于 [2013 年 3 月以 Apache 2.0 授权协议开源](https://en.wikipedia.org/wiki/Docker_(software))。
Docker 的发展历程
- **2013 3 **开源发布
- **2013 年底**dotCloud 公司改名为 DockerInc
- **2015 **成立[开放容器联盟 (OCI)](https://opencontainers.org/),推动容器标准化
- **至今**[GitHub 项目](https://github.com/moby/moby)超过 7 万星标
Docker 的成功推动了整个容器生态的发展催生了 KubernetesPodman 等众多相关项目笔者认为Docker 最大的贡献不仅是技术本身更是它 **让容器技术从系统管理员的工具变成了每个开发者都能使用的标准工具**

View File

@@ -1,212 +0,0 @@
## 1.3 为什么要用 Docker
在回答 为什么用 Docker 之前笔者想先问一个问题**你有没有经历过这些场景**
### 1.3.1 没有 Docker 的世界
Docker 出现之前软件开发和运维面临着诸多棘手的问题我们先来看看以下三个典型的痛点场景
#### 场景一在我电脑上明明能跑
```bash
周五下午 5:00
├── 开发者:代码写完了,本地测试通过,提交!🎉
├── 周一早上 9:00
│ └── 测试:"这个功能在测试环境跑不起来"
└── 开发者:" 不可能,在我电脑上明明能跑啊……"
```
笔者统计过这个问题通常由以下原因导致
- Python/Node/Java 版本不一致
- 依赖库版本不一致
- 操作系统配置不一致
- 某些环境变量没有设置
- 忘了说我本地装了个 XXX
#### 场景二环境配置的噩梦
```bash
新同事入职
├── Day 1领电脑配环境
├── Day 2继续配环境遇到问题
├── Day 3换种方法配环境
├── Day 4问老同事怎么配的他也忘了
└── Day 5终于能跑起来了但不知道为什么……
```
#### 场景三服务器迁移的恐惧
```bash
运维:"我们需要把服务迁移到新服务器"
开发:"旧服务器上的配置文档在哪?"
运维:"当时是一个已经离职的同事配的……"
所有人:😱
```
### 1.3.2 Docker 如何解决这些问题
Docker 的出现为上述问题提供了完美的解决方案它通过 一次构建到处运行 的核心理念从根本上改变了软件交付的方式
#### 核心理念一次构建到处运行
```mermaid
flowchart LR
dev["开发团队"] -->|创建| img["Docker 镜像"]
img -->|测试团队验证| test["测试团队"]
test -- "有问题<br/>反馈修改和更新" --> dev
test -- "没问题<br/>发布" --> prod["生产环境"]
```
### 1.3.3 Docker 的核心优势
除了解决上述痛点Docker 还拥有诸多显著的技术优势包括环境一致性秒级启动高效的资源利用等
#### 1. 环境一致性
Docker 镜像包含了应用运行所需的 **一切**代码运行时系统工具配置这意味着
- 开发环境和生产环境完全一致
- 不会再有 在我机器上能跑 的问题
- 新人入职一条命令就能启动开发环境
```bash
## 新同事入职第一天
$ git clone https://github.com/company/project.git
$ docker compose up
## 完整的开发环境就准备好了
...
```
#### 2. 秒级启动
传统虚拟机启动需要几分钟 (引导操作系统) Docker 容器启动通常只需要 **几秒甚至几百毫秒**
笔者实测数据
| 启动内容 | 虚拟机 | Docker 容器 |
|---------|--------|-------------|
| 空系统 | ~60 | ~0.5 |
| MySQL | ~90 | ~3 |
| 完整 Web 应用 | ~120 | ~5 |
这个差异对以下场景尤为重要
- **CI/CD 流水线**每次构建节省几分钟一天累积下来就是几小时
- **弹性扩容**流量高峰时能快速启动更多实例
- **开发体验**快速重启服务进行调试
#### 3. 资源效率
Docker 容器共享宿主机内核无需为每个应用运行完整的操作系统以一台 64GB 内存的物理服务器为例
- **传统虚拟机方案**每个虚拟机都需要运行完整的操作系统每个额外占用如 2GB 内存产生大量资源开销实际可用于应用的内存可能只有约 18GB
- **Docker 方案**容器直接共享宿主机系统只需付出很少的基础开销OS及引擎约 4GB即可将约 60GB 的内存全部用于实际应用
```mermaid
flowchart TD
subgraph VM ["传统虚拟机方案 ❌"]
direction TB
Server1["物理服务器 (64GB 内存)"]
subgraph VMs ["可用应用内存: 约 18GB"]
direction LR
VM1["VM 1: 应用 1<br/>(含 2GB OS)"]
VM2["VM 2: 应用 2<br/>(含 2GB OS)"]
VM3["VM 3: 应用 3<br/>(含 2GB OS)"]
end
Server1 --- VMs
end
subgraph Docker ["Docker 方案 ✅"]
direction TB
Server2["物理服务器 (64GB 内存)<br/>含约 4GB OS及引擎配置"]
subgraph Containers ["可用应用内存: 约 60GB"]
direction LR
C1["容器 1: 应用 1<br/>(按需分配)"]
C2["容器 2: 应用 2<br/>(按需分配)"]
C3["容器 3: 应用 3<br/>(按需分配)"]
end
Server2 --- Containers
end
```
#### 4. 持续交付和部署
Docker 完美契合 DevOps 的工作流程
```mermaid
flowchart LR
A["代码提交<br/>(Git push)"] --> B["自动构建镜像<br/>(docker build)"]
B --> C["自动测试<br/>(容器内运行测试)"]
C --> D["自动部署<br/>(容器滚动更新)"]
```
使用 [Dockerfile](../04_image/4.5_build.md) 定义镜像构建过程使得
- 构建过程 **可重复可追溯**
- 任何人都能从代码重建完全相同的镜像
- 配合 [GitHub Actions](../21_case_devops/21.2_github_actions.md) CI 系统实现自动化
#### 5. 轻松迁移
Docker 可以在几乎任何平台上运行
- 本地开发机 (macOSWindowsLinux)
- 公有云 (AWSAzureGCP阿里云腾讯云)
- 私有云和自建数据中心
- 边缘设备和 IoT
**同一个镜像在任何地方运行结果都一致** 这让应用迁移变得前所未有的简单
#### 6. 微服务架构的基石
现代微服务架构几乎都依赖容器技术Docker 让你可以
- **隔离服务**每个服务运行在独立容器中互不干扰
- **独立扩展**哪个服务负载高就单独扩展哪个
- **独立部署**更新一个服务不影响其他服务
- **技术多样**不同服务可以用不同语言和框架
```mermaid
flowchart TD
subgraph Microservices ["微服务架构示例"]
direction TB
subgraph AppLayer ["应用层"]
direction LR
Frontend["前端容器<br/>(Node.js)"]
API["API 容器<br/>(Python)"]
Worker["Worker 容器<br/>(Go)"]
end
Redis["Redis 容器"]
DB["PostgreSQL 容器"]
Frontend --> API
API --> Redis
API --> DB
Worker --> Redis
Worker --> DB
end
```
### 1.3.4 Docker 不适合的场景
笔者认为技术选型要客观Docker 并非银弹以下场景可能不太适合
- **需要完全隔离的场景**容器共享宿主机内核隔离性不如虚拟机如果需要运行不受信任的代码虚拟机可能更安全
- **需要特殊内核的场景**容器使用宿主机内核如果应用需要特定版本的内核或内核模块可能需要虚拟机
- **Windows 原生应用**虽然 Docker 支持 Windows 容器但生态不如 Linux 容器成熟传统 Windows 应用的容器化仍有挑战
- **桌面应用**Docker 主要面向服务端应用桌面 GUI 应用的容器化虽然可行但通常得不偿失
### 1.3.5 与传统虚拟机的对比总结
下表对比了容器技术与传统虚拟机的区别
| 特性 | Docker 容器 | 传统虚拟机 |
|:------|:-----------|:-----------|
| 启动速度 | 秒级 | 分钟级 |
| 磁盘占用 | MB 级别 | GB 级别 |
| 性能 | 接近原生 | 5-20% 损耗 |
| 单机支持量 | 上千个容器 | 几十个虚拟机 |
| 隔离性 | 进程级别 | 完全隔离 |
| 最佳场景 | 微服务CI/CD开发环境 | 多租户高安全需求 |

View File

@@ -1,27 +0,0 @@
# 第一章 Docker 简介
本章将带领你进入 **Docker** 的世界
## 本章内容
* [快速上手](1.1_quickstart.md)
* 通过一个简单的 Web 应用例子带你快速体验 Docker 的核心流程构建镜像运行容器
* [什么是 Docker](1.2_what.md)
* 介绍 Docker 的起源发展历程以及其背后的核心技术 (CgroupsNamespacesUnionFS)
* 了解 Docker 是如何改变软件交付方式的
* [为什么要用 Docker](1.3_why.md)
* 对比传统虚拟机技术阐述 Docker 在启动速度资源利用率交付效率等方面的巨大优势
* 探讨 Docker DevOps微服务架构中的关键作用
## 学习目标
通过本章的学习你将能够
1. 理解 Docker 的核心概念与架构
2. 明白 Docker 解决了现代软件开发与运维中的哪些痛点
3. 建立起对容器技术的初步认知为后续的实战操作打下基础
好吧让我们带着问题开始这神奇之旅

View File

@@ -1,8 +0,0 @@
## 本章小结
- Docker 是一种轻量级虚拟化技术核心价值是 **环境一致性**
- 与虚拟机相比Docker 更轻量更快速资源利用率更高
- Docker 基于 Linux 内核的 NamespaceCgroups Union FS 技术
- Docker 推动了容器技术的标准化 (OCI) 和生态发展
Docker 的核心价值可以用一句话概括**让应用的开发测试部署保持一致同时极大提高资源利用效率** 笔者认为对于现代软件开发者来说Docker 已经不是 要不要学 的问题而是 **必备技能**无论你是前端后端运维还是全栈开发者掌握 Docker 都能让你的工作更高效

View File

@@ -1,214 +0,0 @@
## 2.1 镜像
Docker 镜像作为容器运行的基石其设计理念和实现机制至关重要本节将深入探讨镜像的本质与操作系统的关系内容构成以及核心的分层存储机制
### 2.1.1 一句话理解镜像
> **Docker 镜像是一个只读的模板包含了运行应用所需的一切代码运行时环境变量和配置文件** 如果用一个类比**镜像就像是一张光盘或 ISO 文件**你可以用同一张光盘在不同电脑上安装系统而光盘本身不会被修改同样一个镜像可以创建多个容器而镜像本身保持不变
### 2.1.2 镜像与操作系统的关系
我们都知道操作系统分为 **内核** **用户空间**
```mermaid
flowchart TD
subgraph UserSpace ["用户空间"]
direction TB
App["应用程序、工具、库、配置文件...<br/>(这部分被打包成 Docker 镜像)"]
end
subgraph KernelSpace ["Linux 内核"]
direction TB
Kernel["容器共享宿主机的内核"]
end
UserSpace --- KernelSpace
```
对于 Linux 而言内核启动后会挂载 `root` 文件系统来提供用户空间支持**Docker 镜像** 本质上就是一个 `root` 文件系统
例如官方镜像 `ubuntu:24.04` 包含了一套完整的 Ubuntu 24.04 最小系统的 root 文件系统 **不包含 Linux 内核** (因为容器共享宿主机的内核)
### 2.1.3 镜像包含什么
Docker 镜像是一个特殊的文件系统包含
| 内容类型 | 示例 |
|---------|------|
| **程序文件** | 应用二进制文件Python/Node 解释器 |
| **库文件** | libcOpenSSL各种依赖库 |
| **配置文件** | nginx.confmy.cnf |
| **环境变量** | PATHLANG 等预设值 |
| **元数据** | 启动命令暴露端口数据卷定义 |
**关键特性**
- 镜像是 **只读**
- 镜像 **不包含** 动态数据
- 镜像构建后 **内容不会改变**
### 2.1.4 分层存储镜像的核心设计
镜像的分层存储机制是 Docker 最具创新性的特性之一通过 Union FS 技术Docker 能够高效地构建和管理镜像
#### 为什么需要分层
笔者认为分层存储是 Docker 最巧妙的设计之一
假设你有三个应用都基于 Ubuntu 运行
```mermaid
flowchart TD
subgraph Traditional ["传统方式(不分层)总计: 1.5GB ❌"]
direction LR
AppA_Trad["App A<br/>Ubuntu 500MB"]
AppB_Trad["App B<br/>Ubuntu 500MB"]
AppC_Trad["App C<br/>Ubuntu 500MB"]
end
subgraph DockerLayered ["Docker 分层方式 总计: 620MB ✅"]
direction TB
subgraph Apps ["应用层"]
direction LR
AppA["App A 50MB"]
AppB["App B 30MB"]
AppC["App C 40MB"]
end
Ubuntu["Ubuntu<br/>共享500MB"]
AppA --> Ubuntu
AppB --> Ubuntu
AppC --> Ubuntu
end
```
#### 分层是如何工作的
笔者用一个实际的 Dockerfile 来解释分层
```docker
FROM ubuntu:24.04 # 第 1 层:基础系统(约 78MB
RUN apt-get update # 第 2 层:更新包索引
RUN apt-get install nginx # 第 3 层:安装 nginx
COPY app.conf /etc/nginx/ # 第 4 层:复制配置文件
```
构建后的镜像结构
```mermaid
flowchart TD
Layer4["第 4 层: COPY app.conf (只读)<br/>← 最新添加的层"]
Layer3["第 3 层: nginx 安装文件 (只读)"]
Layer2["第 2 层: apt 缓存更新 (只读)"]
Layer1["第 1 层: Ubuntu 基础系统 (只读)<br/>← 基础镜像层"]
Layer4 --> Layer3 --> Layer2 --> Layer1
```
每一层的特点
- **只读**构建完成后不可修改
- **可共享**多个镜像可以共享相同的层
- **有缓存**未变化的层不会重新构建
#### 分层存储的 陷阱
> **笔者特别提醒**理解这一点可以帮你避免构建出臃肿的镜像**关键原理**每一层的文件变化会被记录 **删除操作只是标记不会真正减小镜像体积**
```docker
## 错误示范 ❌
FROM ubuntu:24.04
RUN apt-get update
RUN apt-get install -y build-essential # 安装编译工具(约 200MB
RUN make && make install # 编译应用
RUN apt-get remove build-essential # 试图删除编译工具
## 结果:镜像仍然包含 200MB 的编译工具!
```
```docker
## 正确做法 ✅
FROM ubuntu:24.04
RUN apt-get update && \
apt-get install -y build-essential && \
make && make install && \
apt-get remove -y build-essential && \
apt-get autoremove -y && \
rm -rf /var/lib/apt/lists/*
## 在同一层完成安装、使用、清理
```
#### 查看镜像的分层
```bash
## 查看镜像的历史(每层的构建记录)
$ docker history nginx:latest
IMAGE CREATED CREATED BY SIZE
a6bd71f48f68 2 weeks ago CMD ["nginx" "-g" "daemon off;"] 0B
<missing> 2 weeks ago STOPSIGNAL SIGQUIT 0B
<missing> 2 weeks ago EXPOSE map[80/tcp:{}] 0B
<missing> 2 weeks ago ENTRYPOINT ["/docker-entrypoint.sh"] 0B
<missing> 2 weeks ago COPY 30-tune-worker-processes.sh /docker-ent… 4.62kB
...
```
### 2.1.5 镜像的标识
Docker 镜像有多种标识方式
#### 1. 镜像名称和标签
格式`[仓库地址/]仓库名[:标签]`
```bash
## 完整格式
registry.example.com/myproject/myapp:v1.2.3
## 简写(使用 Docker Hub
nginx:1.25
ubuntu:24.04
## 省略标签(默认使用 latest
nginx # 等同于 nginx:latest
```
#### 2. 镜像 ID (Content-Addressable)
每个镜像有一个基于内容计算的唯一 ID
```bash
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx latest a6bd71f48f68 2 weeks ago 187MB
ubuntu 24.04 ca2b0f26964c 3 weeks ago 78.1MB
```
#### 3. 镜像摘要
更精确的标识基于镜像内容的 SHA256 哈希
```bash
$ docker images --digests
REPOSITORY TAG DIGEST IMAGE ID
nginx latest sha256:6db391d1c0cfb30588ba0bf72ea999404f2764184d8b8d10d89e8a9c6... a6bd71f48f68
```
> 💡 笔者建议在生产环境使用镜像摘要而非标签因为标签可以被覆盖但摘要是不可变的
### 2.1.6 镜像的来源
Docker 镜像可以通过以下方式获取
| 方式 | 说明 | 示例 |
|------|------|------|
| ** Registry 拉取** | 最常用的方式 | `docker pull nginx` |
| ** Dockerfile 构建** | 自定义镜像 | `docker build -t myapp .` |
| **从容器提交** | 保存容器状态 (不推荐)| `docker commit` |
| **从文件导入** | 离线传输 | `docker load < image.tar` |

View File

@@ -1,239 +0,0 @@
## 2.2 容器
容器是 Docker 技术的核心是应用实际运行的载体本节将从容器的本质与虚拟机的区别存储层机制以及生命周期管理等方面全面解析 Docker 容器
### 2.2.1 一句话理解容器
> **容器是镜像的运行实例如果把镜像比作程序那么容器就是进程** 用面向对象编程的术语来说**镜像是类 (Class)容器是对象 (Instance)**
- 一个镜像可以创建多个容器
- 每个容器相互独立互不影响
- 容器可以被创建启动停止删除暂停
### 2.2.2 容器的本质
> 💡 笔者认为理解这一点是理解 Docker 的关键**容器的本质是一个特殊的进程**
```mermaid
flowchart TD
subgraph NormalProcess ["普通进程"]
direction TB
N1["• 共享系统资源<br/>• 共享网络<br/>• 共享文件系统"]
end
subgraph ContainerProcess ["容器进程 (运行在宿主机内核上)"]
direction TB
C1["• 独立进程空间<br/>• 独立网络环境<br/>• 独立文件系统<br/>• 独立用户空间"]
end
```
这种隔离是通过 Linux 内核的 **Namespace** 技术实现的具体表现为
- **进程空间**容器看不到宿主机上的其他进程
- **网络**容器拥有独立的 IP端口等网络资源
- **文件系统**容器拥有独立的 root 目录
- **用户**容器内的 root 用户不等于宿主机的 root 用户
### 2.2.3 容器 vs 虚拟机核心区别
很多初学者会混淆容器和虚拟机笔者用一张图来说明
```mermaid
flowchart TD
subgraph VM ["虚拟机 (每个 VM 运行完整 OS)"]
direction TB
subgraph VMApp ["应用层"]
VA[App A] & VB[App B]
end
subgraph VMGuest ["Guest OS (完整系统)"]
G1[Guest OS] & G2[Guest OS]
end
V[Hypervisor]
VMH[Host OS]
VMHW[Hardware]
VMApp --> VMGuest --> V --> VMH --> VMHW
end
subgraph Container ["容器 (所有容器共享宿主机内核)"]
direction TB
subgraph CApp ["应用层"]
CA[App A] & CB[App B]
end
subgraph CContainer ["隔离层"]
CC1[Container 仅应用] & CC2[Container 仅应用]
end
CE[Docker Engine]
CH[Host OS]
CHW[Hardware]
CApp --> CContainer --> CE --> CH --> CHW
end
```
| 特性 | 容器 | 虚拟机 |
|------|------|--------|
| **隔离级别** | 进程级 (Namespace)| 硬件级 (Hypervisor)|
| **启动时间** | 秒级 (甚至毫秒)| 分钟级 |
| **资源占用** | MB 级别 | GB 级别 |
| **性能损耗** | 几乎为零 | 5-20% |
| **内核** | 共享宿主机内核 | 各自独立内核 |
### 2.2.4 容器的存储层
理解容器的存储层机制对于数据的持久化和镜像的优化至关重要本节将介绍容器的可写层以及 Copy-on-Write 机制
#### 镜像层 + 容器层
当容器运行时Docker 会在镜像的只读层之上创建一个 **可写层** (容器存储层)
```mermaid
flowchart TD
ContainerLayer["容器存储层(可读写)<br/>容器运行时创建,记录文件变化"]
ImageLayerN["镜像第 N 层(只读)"]
ImageLayerN1["镜像第 N-1 层(只读)"]
Dots["..."]
ImageLayer1["镜像第 1 层(只读)<br/>基础镜像层"]
ContainerLayer --> ImageLayerN --> ImageLayerN1 --> Dots --> ImageLayer1
```
#### Copy-on-Write (写时复制)
当容器需要修改镜像层中的文件时
1. Docker 将该文件 **复制** 到容器存储层
2. 在容器层中进行修改
3. 原始镜像层保持不变
```bash
读取文件:直接从镜像层读取(共享,高效)
修改文件:复制到容器层,然后修改(只有这个容器能看到修改)
```
#### 容器存储层的生命周期
> **笔者特别强调**这是新手最容易踩的坑**容器存储层与容器生命周期绑定容器删除数据就没了**
```bash
## 创建容器,写入数据
$ docker run -it ubuntu bash
root@abc123:/# echo "important data" > /data.txt
root@abc123:/# exit
## 删除容器
$ docker rm abc123
## 数据丢了!没有任何办法恢复!
```
#### 正确的数据持久化方式
按照 Docker 最佳实践容器存储层应该保持 **无状态**需要持久化的数据应该使用
| 方式 | 说明 | 适用场景 |
|------|------|---------|
| **[数据卷 (Volume) ](../08_data/8.1_volume.md)** | Docker 管理的存储 | 数据库应用数据 |
| **[绑定挂载 (Bind Mount) ](../08_data/8.2_bind-mounts.md)** | 挂载宿主机目录 | 开发时共享代码 |
```bash
## 使用数据卷(推荐)
$ docker run -v mydata:/var/lib/mysql mysql
## 使用绑定挂载
$ docker run -v /host/path:/container/path nginx
```
这些位置的读写 **会跳过容器存储层**直接写入宿主机性能更好也不会随容器删除而丢失
### 2.2.5 容器的生命周期
掌握容器的生命周期对于管理和调试 Docker 应用非常重要如图 2-1 所示容器会经历从创建到删除的完整状态流转
```mermaid
stateDiagram-v2
direction TB
[*] --> Created : docker create
Created --> Running : docker start
Running --> Stopped : docker stop
Running --> Paused : docker pause
Paused --> Running : docker unpause
Created --> Deleted : docker rm
Stopped --> Deleted : docker rm
Paused --> Deleted : docker rm
Deleted --> [*]
```
2-1 容器生命周期状态流转图
#### 常用生命周期命令
```bash
## 创建并启动容器(最常用)
$ docker run nginx
## 分步操作
$ docker create nginx # 创建容器(不启动)
$ docker start abc123 # 启动容器
## 停止容器
$ docker stop abc123 # 优雅停止(发送 SIGTERM等待后发送 SIGKILL
$ docker kill abc123 # 强制停止(直接发送 SIGKILL
## 暂停/恢复(不常用,但有时有用)
$ docker pause abc123 # 暂停容器内所有进程
$ docker unpause abc123 # 恢复
## 删除容器
$ docker rm abc123 # 删除已停止的容器
$ docker rm -f abc123 # 强制删除运行中的容器
```
### 2.2.6 容器与进程的关系
> **核心概念**容器的生命周期 = 主进程 (PID 1) 的生命周期
```bash
## 主进程运行,容器运行
## 主进程退出,容器停止
```
这就是为什么
```bash
## 这个容器会立即退出bash 没有输入就退出了)
$ docker run ubuntu
## 这个容器会持续运行nginx 作为守护进程持续运行)
$ docker run nginx
```
详细解释请参考[后台运行](../05_container/5.2_daemon.md)章节
### 2.2.7 容器的隔离性
Docker 容器通过以下 Namespace 实现隔离
| Namespace | 隔离内容 | 效果 |
|-----------|---------|------|
| **PID** | 进程 ID | 容器内 PID 1 是应用进程看不到宿主机其他进程 |
| **NET** | 网络 | 独立的网络栈IP 地址端口 |
| **MNT** | 文件系统 | 独立的根目录和挂载点 |
| **UTS** | 主机名 | 独立的主机名和域名 |
| **IPC** | 进程间通信 | 独立的信号量消息队列 |
| **USER** | 用户 | 独立的用户和组 ID |
> 想深入了解请阅读[底层实现 - 命名空间](../12_implementation/12.2_namespace.md)

View File

@@ -1,288 +0,0 @@
## 2.3 仓库
Docker Registry 是镜像分发和管理的核心组件本节将介绍 Registry 的基本概念公共和私有服务的选择以及镜像的安全管理
### 2.3.1 一句话理解 Registry
> **Docker Registry 是存储和分发 Docker 镜像的服务类似于代码的 GitHub 或包管理的 npm**
镜像构建完成后可以在当前机器上运行但如果需要在其他服务器上使用这个镜像就需要一个集中的存储和分发服务这就是 Docker Registry
### 2.3.2 核心概念
要熟练使用 Docker Registry首先需要理清它与仓库 (Repository)标签 (Tag) 之间的关系
#### Registry仓库标签的关系
Docker Registry 中可以包含多个 Repository每个 Repository 可以包含多个 Tag如图 2-2 所示它们之间具有清晰的层级关系
```mermaid
flowchart TB
subgraph Registry ["Docker Registry如 Docker Hub"]
direction TB
subgraph RepoNginx ["Repository仓库: nginx"]
direction LR
N1(":latest (tag)")
N2(":1.25 (tag)")
N3(":1.24 (tag)")
N4(":alpine (tag)")
N5("...")
N1 ~~~ N2 ~~~ N3 ~~~ N4 ~~~ N5
end
subgraph RepoMysql ["Repository仓库: mysql"]
direction LR
M1(":latest")
M2(":8.0")
M3(":5.7")
M4("...")
M1 ~~~ M2 ~~~ M3 ~~~ M4
end
RepoNginx ~~~ RepoMysql
end
```
2-2 RegistryRepository Tag 的层级关系
相关基本概念具体如下
| 概念 | 说明 | 示例 |
|------|------|------|
| **Registry** | 存储镜像的服务 | Docker Hubghcr.io |
| **Repository (仓库)** | 同一软件的镜像集合 | `nginx``mysql``mycompany/myapp` |
| **Tag (标签)** | 仓库内的版本标识 | `latest``1.25``alpine` |
#### 镜像的完整名称
一个完整的 Docker 镜像名称由 Registry 地址用户名/组织名仓库名和标签组成了解其结构有助于我们更准确地定位镜像基本格式如下
```bash
[registry地址/][用户名/]仓库名[:标签]
```
示例
```bash
## 完整格式
registry.example.com/mycompany/myapp:v1.2.3
│ │ │ │
│ │ │ └── 标签
│ │ └── 仓库名
│ └── 用户名/组织名
└── Registry 地址
## Docker Hub 官方镜像(省略 registry 和用户名)
nginx:1.25
ubuntu:24.04
## Docker Hub 用户镜像
jwilder/nginx-proxy:latest
## 其他 Registry
ghcr.io/username/myapp:v1.0
gcr.io/google-containers/pause:3.6
```
> 💡 **笔者提示**如果不指定 Registry 地址默认使用 Docker Hub如果不指定标签默认使用 `latest`
### 2.3.3 公共 Registry 服务
公共 Registry 服务为开发者提供了便捷的镜像获取途径其中最著名的是 Docker Hub
#### 默认的 Docker Hub
[Docker Hub](https://hub.docker.com/) 是最大的公共 Registry也是 Docker 的默认 Registry。
**特点**
- 拥有大量[官方镜像](https://hub.docker.com/search?q=&type=image&image_filter=official) (nginx、mysql、redis 等)
- 免费账户可以创建公开仓库
- 付费账户支持私有仓库
```bash
## 从 Docker Hub 拉取镜像
$ docker pull nginx # 官方镜像
$ docker pull bitnami/redis # 第三方镜像
## 推送镜像到 Docker Hub
$ docker login
$ docker push username/myapp:v1.0
```
#### 其他公共 Registry
除了 Docker Hub还有以下几个常见的公共 Registry
| Registry | 地址 | 说明 |
|----------|------|------|
| **GitHub Container Registry** | ghcr.io | GitHub 提供 GitHub Actions 集成好 |
| **Google Container Registry** | gcr.io | Google Cloud 提供Kubernetes 镜像常用 |
| **Quay.io** | quay.io | Red Hat 提供 |
| **阿里云容器镜像服务** | registry.cn-*.aliyuncs.com | 国内访问快 |
| **腾讯云容器镜像服务** | ccr.ccs.tencentyun.com | 国内访问快 |
### 2.3.4 镜像加速器
由于网络原因在国内直接访问 Docker Hub 可能会很慢可以配置 **镜像加速器** (Registry Mirror) 来加速下载配置示例如下
```json
// /etc/docker/daemon.json
{
"registry-mirrors": [
"https://your-accelerator-url"
]
}
```
详细配置方法请参考[镜像加速器](../03_install/3.9_mirror.md)章节
> **笔者提醒**镜像加速器的可用性经常变化使用前建议先测试是否可用
### 2.3.5 私有 Registry
出于安全和隐私的考虑企业往往需要搭建自己的私有 Registry以下是几种常见的搭建方案
#### 官方 Registry 镜像
Docker 官方提供了 [registry](https://hub.docker.com/_/registry/) 镜像,可以快速搭建私有 Registry
```bash
## 启动一个本地 Registry
$ docker run -d -p 5000:5000 --name registry registry:2
## 推送镜像到本地 Registry
$ docker tag myapp:v1.0 localhost:5000/myapp:v1.0
$ docker push localhost:5000/myapp:v1.0
## 从本地 Registry 拉取
$ docker pull localhost:5000/myapp:v1.0
```
#### 企业级解决方案
官方 Registry 功能较为基础企业环境常用以下方案
| 方案 | 特点 |
|------|------|
| **[Harbor](https://goharbor.io/)** | CNCF 项目,功能全面 (用户管理、漏洞扫描、镜像签名)|
| **[Nexus Repository](../06_repository/6.4_nexus3_registry.md)** | 支持多种制品类型 (DockerMavennpm )|
| **云厂商服务** | 阿里云 ACR腾讯云 TCRAWS ECR |
笔者建议
- 小团队可以先用官方 Registry够用即可
- 中大型团队推荐 Harbor功能完善且开源免费
- 已使用云服务直接用云厂商的 Registry 服务更省心
### 2.3.6 镜像的推送和拉取
掌握镜像的推送 (Push) 和拉取 (Pull) 是使用 Docker Registry 的基本功
#### 完整工作流程
如图 2-3 所示镜像从开发环境构建后推送到 Registry再由生产环境拉取并运行
```bash
开发者机器 Registry 生产服务器
│ │ │
│ docker build │ │
│ 构建镜像 │ │
│ │ │
│ docker push ─────────────▶ │
│ 推送镜像 │ 存储镜像 │
│ │ │
│ │ ◀───────────── docker pull │
│ │ 拉取镜像 │
│ │ │
│ │ docker run │
│ │ 运行容器 │
```
2-3 镜像构建推送与拉取流程
#### 常用命令
```bash
## 登录 Registry
$ docker login # 登录 Docker Hub
$ docker login registry.example.com # 登录其他 Registry
## 拉取镜像
$ docker pull nginx:1.25
## 标记镜像(准备推送)
$ docker tag myapp:latest registry.example.com/myteam/myapp:v1.0
## 推送镜像
$ docker push registry.example.com/myteam/myapp:v1.0
## 登出
$ docker logout
```
### 2.3.7 镜像的安全性
在使用公共镜像或维护私有镜像时安全性是不容忽视的重要环节
#### 使用官方镜像
Docker Hub [官方镜像](https://hub.docker.com/search?q=&type=image&image_filter=official) (标有 “Official Image” 标识) 经过 Docker 团队审核,相对更安全。示例如下:
```bash
## 官方镜像示例
nginx # ✅ 官方
mysql # ✅ 官方
redis # ✅ 官方
## 第三方镜像(需要自行评估可信度)
bitnami/redis # ⚠️ 需要评估
someuser/myapp # ⚠️ 需要评估
```
#### 镜像签名
当前更推荐使用 Sigstore / Notation 体系进行镜像签名与验证`Docker Content Trust (DCT)` 已进入退场阶段不建议作为新项目主方案
> 注意Cosign 默认会把签名写回镜像所在仓库请使用你有推送权限的镜像地址
```bash
## 准备一个你有写权限的镜像地址
$ export IMAGE=<你的仓库名>/nginx:1.27
$ docker pull nginx:1.27
$ docker tag nginx:1.27 $IMAGE
$ docker push $IMAGE
## 生成签名密钥(会生成 cosign.key / cosign.pub
$ cosign generate-key-pair
## 使用 Cosign 签名与验证
$ cosign sign --key cosign.key $IMAGE
$ cosign verify --key cosign.pub $IMAGE
```
#### 漏洞扫描
```bash
## 使用 Docker Scout 扫描镜像漏洞
$ docker scout cves nginx:latest
## 使用 Trivy开源工具
$ trivy image nginx:latest
```

View File

@@ -1,9 +0,0 @@
# 第二章 基本概念
**Docker** 包括三个基本概念
* **镜像** (`Image`)Docker 镜像是一个特殊的文件系统除了提供容器运行时所需的程序资源配置等文件外还包含了一些为运行时准备的一些配置参数 (如匿名卷环境变量用户等)镜像不包含任何动态数据其内容在构建之后也不会被改变
* **容器** (`Container`)镜像 (`Image`) 和容器 (`Container`) 的关系就像是面向对象程序设计中的 `` `实例` 一样镜像是静态的定义容器是镜像运行时的实体容器可以被创建启动停止删除暂停等
* **仓库** (`Repository`)镜像构建完成后可以很容易的在当前宿主机上运行但是如果需要在其它服务器上使用这个镜像我们就需要一个集中的存储分发镜像的服务Docker Registry 就是这样的服务
理解了这三个概念就理解了 **Docker** 的整个生命周期

View File

@@ -1,51 +0,0 @@
## 本章小结
| 概念 | 要点 |
|------|------|
| **镜像是什么** | 只读的应用模板包含运行所需的一切 |
| **分层存储** | 多层叠加共享基础层节省空间 |
| **只读特性** | 构建后不可修改保证一致性 |
| **层的陷阱** | 删除操作只是标记不减小体积 |
理解了镜像接下来让我们学习[容器](2.2_container.md)镜像的运行实例
### 2.4.1 延伸阅读
- [获取镜像](../04_image/4.1_pull.md) Registry 下载镜像
- [使用 Dockerfile 定制镜像](../04_image/4.5_build.md)创建自己的镜像
- [Dockerfile 最佳实践](../appendix/best_practices.md)构建高质量镜像的技巧
- [底层实现 - 联合文件系统](../12_implementation/12.4_ufs.md)深入理解分层存储的技术原理
| 概念 | 要点 |
|------|------|
| **容器是什么** | 镜像的运行实例本质是隔离的进程 |
| **容器 vs 虚拟机** | 共享内核更轻量但隔离性较弱 |
| **存储层** | 可写层随容器删除而消失 |
| **数据持久化** | 使用 Volume Bind Mount |
| **生命周期** | 与主进程 (PID 1) 绑定 |
理解了镜像和容器接下来让我们学习[仓库](2.3_repository.md)存储和分发镜像的服务
### 2.4.2 延伸阅读
- [启动容器](../05_container/5.1_run.md)详细的容器启动选项
- [后台运行](../05_container/5.2_daemon.md)理解容器为什么会 立即退出
- [进入容器](../05_container/5.4_attach_exec.md)如何操作运行中的容器
- [数据管理](../08_data/README.md)Volume 和数据持久化详解
| 概念 | 要点 |
|------|------|
| **Registry** | 存储和分发镜像的服务 |
| **仓库 (Repository)** | 同一软件的镜像集合 |
| **标签 (Tag)** | 版本标识默认为 latest |
| **Docker Hub** | 默认的公共 Registry |
| **私有 Registry** | 企业内部使用推荐 Harbor |
现在你已经了解了 Docker 的三个核心概念[镜像](2.1_image.md)[容器](2.2_container.md)和仓库接下来让我们开始[安装 Docker](../03_install/README.md)动手实践
### 2.4.3 延伸阅读
- [Docker Hub](../06_repository/6.1_dockerhub.md)Docker Hub 的详细使用
- [私有仓库](../06_repository/6.2_registry.md)搭建私有 Registry
- [私有仓库高级配置](../06_repository/6.3_registry_auth.md)认证TLS 配置
- [镜像加速器](../03_install/3.9_mirror.md)配置镜像加速

View File

@@ -1,198 +0,0 @@
## 3.5 Raspberry Pi
树莓派等 ARM 架构设备在物联网和边缘计算领域应用广泛本节介绍如何在树莓派上安装 Docker
>警告切勿在没有配置 Docker APT 源的情况下直接使用 apt 命令安装 Docker
### 3.5.1 系统要求
Docker ARM 架构有着良好的支持
Docker 不仅支持 `x86_64` 架构的计算机同时也支持 `ARM` 架构的计算机本小节内容以树莓派单片电脑为例讲解 `ARM` 架构安装 Docker
Docker 支持以下版本的 [Raspberry Pi OS](https://www.raspberrypi.org/software/operating-systems/) 操作系统:
* Raspberry Pi OS Trixie
* Raspberry Pi OS Bookworm
* Raspberry Pi OS Bullseye
**`Raspberry Pi OS` 由树莓派的开发与维护机构[树莓派基金会](https://www.raspberrypi.org/)官方支持,并推荐用作树莓派的首选系统,其基于 `Debian`。
### 3.5.2 使用 APT 安装
推荐使用 APT 包管理器进行安装以确保版本的稳定性和安全性
由于 apt 源使用 HTTPS 以确保软件下载过程中不被篡改因此我们首先需要添加使用 HTTPS 传输的软件包以及 CA 证书
```bash
$ sudo apt-get update
$ sudo apt-get install \
apt-transport-https \
ca-certificates \
curl \
gnupg \
lsb-release
```
鉴于国内网络问题强烈建议使用国内源官方源请在注释中查看
为了确认所下载软件包的合法性需要添加软件源的 GPG 密钥
```bash
$ sudo install -m 0755 -d /etc/apt/keyrings
$ curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/raspbian/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
$ sudo chmod a+r /etc/apt/keyrings/docker.gpg
## 官方源
## $ sudo install -m 0755 -d /etc/apt/keyrings
## $ curl -fsSL https://download.docker.com/linux/raspbian/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
## $ sudo chmod a+r /etc/apt/keyrings/docker.gpg
```
然后我们需要向 `sources.list` 中添加 Docker 软件源
```bash
$ echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://mirrors.aliyun.com/docker-ce/linux/raspbian \
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
## 官方源
## $ echo \
## "deb [arch=$ signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/raspbian \
## $ stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
```
>以上命令会添加稳定版本的 Docker APT 如果需要测试版本的 Docker 请将 stable 改为 test
#### 报错解决办法
`Raspberry Pi OS Bullseye/Bookworm` 如果你使用 `add-apt-repository` 添加源可能会出现如下报错 (推荐改用上面的 `tee` 方式来写入 `/etc/apt/sources.list.d/docker.list`可避免此问题)
```bash
Traceback (most recent call last):
File "/usr/bin/add-apt-repository", line 95, in <module>
sp = SoftwareProperties(options=options)
File "/usr/lib/python3/dist-packages/softwareproperties/SoftwareProperties.py", line 109, in __init__
self.reload_sourceslist()
File "/usr/lib/python3/dist-packages/softwareproperties/SoftwareProperties.py", line 599, in reload_sourceslist
self.distro.get_sources(self.sourceslist)
File "/usr/lib/python3/dist-packages/aptsources/distro.py", line 91, in get_sources
raise NoDistroTemplateException(
aptsources.distro.NoDistroTemplateException: Error: could not find a distribution template for Raspbian/bullseye
```
通过以下命令手动添加镜像源到 `/etc/apt/sources.list` 文件中即可解决
```bash
$ echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://mirrors.aliyun.com/docker-ce/linux/raspbian $(lsb_release -cs) stable" | sudo tee -a /etc/apt/sources.list
## 官方源
## $ echo "deb [arch=$ signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/raspbian $ stable" | sudo tee -a /etc/apt/sources.list
```
#### 安装 Docker
更新 apt 软件包缓存并安装 `docker-ce`
```bash
$ sudo apt-get update
$ sudo apt-get install docker-ce
```
### 3.5.3 使用脚本自动安装
在测试或开发环境中 Docker 官方为了简化安装流程提供了一套便捷的安装脚本Raspberry Pi OS 系统上可以使用这套脚本安装另外可以通过 `--mirror` 选项使用国内源进行安装
> 若你想安装测试版的 Docker请从 test.docker.com 获取脚本
```bash
## $ curl -fsSL test.docker.com -o get-docker.sh
$ curl -fsSL get.docker.com -o get-docker.sh
$ sudo sh get-docker.sh --mirror Aliyun
## $ sudo sh get-docker.sh --mirror AzureChinaCloud
```
执行这个命令后脚本就会自动的将一切准备工作做好并且把 Docker 的稳定 (stable) 版本安装在系统中
### 3.5.4 启动 Docker
```bash
$ sudo systemctl enable docker
$ sudo systemctl start docker
```
### 3.5.5 建立 docker 用户组
默认情况下`docker` 命令会使用 [Unix socket](https://en.wikipedia.org/wiki/Unix_domain_socket) 与 Docker 引擎通讯。而只有 `root` 用户和 `docker` 组的用户才可以访问 Docker 引擎的 Unix socket。出于安全考虑一般 Linux 系统上不会直接使用 `root` 用户。因此,更好地做法是将需要使用 `docker` 的用户加入 `docker` 用户组。
建立 `docker`
```bash
$ sudo groupadd docker
```
将当前用户加入 `docker`
```bash
$ sudo usermod -aG docker $USER
```
退出当前终端并重新登录进行如下测试
### 3.5.6 测试 Docker 是否安装正确
```bash
$ docker run --rm hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
4ee5c797bcd7: Pull complete
Digest: sha256:308866a43596e83578c7dfa15e27a73011bdd402185a84c5cd7f32a88b501a24
Status: Downloaded newer image for hello-world:latest
Hello from Docker!
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
(arm32v7)
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.
To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash
Share images, automate workflows, and more with a free Docker ID:
https://hub.docker.com/
For more examples and ideas, visit:
https://docs.docker.com/get-started/
```
若能正常输出以上信息则说明安装成功
*注意*ARM 平台不能使用 `x86` 镜像查看 Raspberry Pi OS 可使用镜像请访问 [arm32v7](https://hub.docker.com/u/arm32v7/) 或者 [arm64v8](https://hub.docker.com/u/arm64v8/)。
### 3.5.7 镜像加速
如果在使用过程中发现拉取 Docker 镜像十分缓慢可以配置 Docker [国内镜像加速](3.9_mirror.md)

View File

@@ -1,76 +0,0 @@
## 3.7 macOS
### 3.7.1 系统要求
[Docker Desktop for Mac](https://docs.docker.com/docker-for-mac/) 要求系统最低为 macOS Sonora 14.0 或更高版本,建议升级到最新版本的 macOS。
### 3.7.2 安装
> [!WARNING]
> **商业许可限制** 2021 年起Docker Desktop 对微型企业少于 250 名员工且年收入少于 1000 万美元个人使用教育和非商业开源项目仍然免费对于其他商业用途需要付费订阅企业用户请注意合规风险或考虑使用开源替代方案
Docker Desktop Mac 用户提供了无缝的 Docker 体验你可以选择使用 Homebrew 或手动下载安装包进行安装
#### 使用 Homebrew 安装
[Homebrew](https://brew.sh/) 的 [Cask](https://github.com/Homebrew/homebrew-cask) 已经支持 Docker Desktop for Mac因此可以很方便的使用 Homebrew Cask 来进行安装:
```bash
$ brew install --cask docker
```
#### 手动下载安装
如果需要手动下载请点击以下[链接](https://desktop.docker.com/mac/main/amd64/Docker.dmg)下载 Docker Desktop for Mac。
> 如果你的电脑搭载的是 Apple Silicon 芯片 (`arm64` 架构)请点击以下[链接](https://desktop.docker.com/mac/main/arm64/Docker.dmg)下载 Docker Desktop for Mac。你可以在[官方文档](https://docs.docker.com/docker-for-mac/apple-silicon/)查阅已知的问题。
如同 macOS 其它软件一样安装也非常简单双击下载的 `.dmg` 文件然后将那只叫 [Moby](https://www.docker.com/blog/call-me-moby-dock/) 的鲸鱼图标拖拽到 `Application` 文件夹即可 (其间需要输入用户密码)。
![](../_images/install-mac-dmg.jpg)
### 3.7.3 运行
从应用中找到 Docker 图标并点击运行
![](../_images/install-mac-apps.jpg)
运行之后会在右上角菜单栏看到多了一个鲸鱼图标这个图标表明了 Docker 的运行状态
![](../_images/install-mac-menubar.png)
每次点击鲸鱼图标会弹出操作菜单
![](../_images/install-mac-menu.png)
之后你可以在终端通过命令检查安装后的 Docker 版本
```bash
$ docker --version
Docker version 26.1.1, build 4cf5afa
```
如果 `docker version``docker info` 都正常的话可以尝试运行一个 [Nginx 服务器](https://hub.docker.com/_/nginx/)
```bash
$ docker run -d -p 80:80 --name webserver nginx
```
服务运行后可以访问 [http://localhost](http://localhost),如果看到了 “Welcome to nginx就说明 Docker Desktop for Mac 安装成功了。
![](../_images/install-mac-example-nginx.png)
要停止 Nginx 服务器并删除执行下面的命令
```bash
$ docker stop webserver
$ docker rm webserver
```
### 3.7.4 镜像加速
如果在使用过程中发现拉取 Docker 镜像十分缓慢可以配置 Docker [国内镜像加速](3.9_mirror.md)
### 3.7.5 参考链接
* [官方文档](https://docs.docker.com/desktop/setup/install/mac-install/)

View File

@@ -1,51 +0,0 @@
## 3.8 Windows 10/11
Windows 平台上Docker Desktop 提供了完整的 Docker 开发环境本节介绍在 Windows 10/11 上的安装和配置
### 3.8.1 系统要求
[Docker Desktop for Windows](https://docs.docker.com/desktop/setup/install/windows-install/) 支持 64 位版本的 Windows 11 或 Windows 10 (需开启 Hyper-V),推荐使用 Windows 11。
### 3.8.2 安装
> [!WARNING]
> **商业许可限制** 2021 年起Docker Desktop 对微型企业少于 250 名员工且年收入少于 1000 万美元个人使用教育和非商业开源项目仍然免费对于其他商业用途需要付费订阅企业用户请注意合规风险或考虑使用开源替代方案
**手动下载安装**
点击以下[链接](https://desktop.docker.com/win/main/amd64/Docker%20Desktop%20Installer.exe)下载 Docker Desktop for Windows。
下载好之后双击 `Docker Desktop Installer.exe` 开始安装
**使用** [**winget**](https://docs.microsoft.com/zh-cn/windows/package-manager/) **安装**
```powershell
$ winget install Docker.DockerDesktop
```
### 3.8.3 WSL2 运行 Docker
若你的 Windows 版本为 Windows 10 专业版或家庭版 v1903 及以上版本可以使用 WSL2 运行 Docker具体请查看 [Docker Desktop WSL 2 backend](https://docs.docker.com/docker-for-windows/wsl/)。
### 3.8.4 运行
Windows 搜索栏输入 **Docker** 点击 **Docker Desktop** 开始运行
![](../_images/install-win-docker-app-search.png)
Docker 启动之后会在 Windows 任务栏出现鲸鱼图标
![](../_images/install-win-taskbar-circle.png)
等待片刻当鲸鱼图标静止时说明 Docker 启动成功之后你可以打开 PowerShell 使用 Docker
> 推荐使用 [Windows Terminal](https://docs.microsoft.com/zh-cn/windows/terminal/get-started) 在终端使用 Docker。
### 3.8.5 镜像加速
如果在使用过程中发现拉取 Docker 镜像十分缓慢可以配置 Docker [国内镜像加速](3.9_mirror.md)
### 3.8.6 参考链接
* [官方文档](https://docs.docker.com/desktop/setup/install/windows-install/)
* [WSL 2 Support is coming to Windows 10 Versions 1903 and 1909](https://devblogs.microsoft.com/commandline/wsl-2-support-is-coming-to-windows-10-versions-1903-and-1909/)

View File

@@ -1,113 +0,0 @@
## 3.9 镜像加速器
国内从 Docker Hub 拉取镜像有时会遇到困难此时可以配置镜像加速器
> **注意**镜像加速器的可用性经常变化配置前请先访问 [docker-practice/docker-registry-cn-mirror-test](https://github.com/docker-practice/docker-registry-cn-mirror-test/actions) 查看各镜像站的实时状态。
### 3.9.1 推荐配置方案
针对不同的使用场景我们推荐以下几种镜像加速配置方案以确保最佳的拉取速度
1. **云服务器用户**优先使用所在云平台提供的内部加速器 (见本页末尾)
2. **本地开发用户**使用阿里云个人加速器或其他可用的公共加速器
3. **代理方案**如有条件可配置 HTTP 代理直接访问 Docker Hub
* [阿里云加速器](https://cr.console.aliyun.com/cn-hangzhou/instances) (需登录获取个人加速地址)
本节以 [AtomHub 可信镜像中心](https://hub.atomgit.com/)镜像服务 `https://hub.atomgit.com` 为例进行介绍。
> `hub.atomgit.com` 仅包含部分官方镜像可以满足初学者的使用
### 3.9.2 Ubuntu 22.04+Debian 12+Rocky/Alma/CentOS Stream 9+
目前主流 Linux 发行版均已使用 [systemd](https://systemd.io/) 进行服务管理,这里介绍如何在使用 systemd 的 Linux 发行版中配置镜像加速器。
请首先执行以下命令查看是否在 `docker.service` 文件中配置过镜像地址
```bash
$ systemctl cat docker | grep '\-\-registry\-mirror'
```
如果该命令有输出那么请执行 `$ systemctl cat docker` 查看 `ExecStart=` 出现的位置修改对应的文件内容去掉 `--registry-mirror` 参数及其值并按接下来的步骤进行配置
如果以上命令没有任何输出那么就可以在 `/etc/docker/daemon.json` 中写入如下内容 (如果文件不存在请新建该文件)
```json
{
"registry-mirrors": [
"https://hub.atomgit.com"
]
}
```
> 注意一定要保证该文件符合 json 规范否则 Docker 将不能启动
之后重新启动服务
```bash
$ sudo systemctl daemon-reload
$ sudo systemctl restart docker
```
### 3.9.3 Windows 10/11
对于使用 `Windows 10/11` 的用户在任务栏托盘 Docker 图标内右键菜单选择 `Change settings`打开配置窗口后在左侧导航菜单选择 `Docker Engine`在右侧像下边一样编辑 json 文件之后点击 `Apply & Restart` 保存后 Docker 就会重启并应用配置的镜像地址了
```json
{
"registry-mirrors": [
"https://hub.atomgit.com"
]
}
```
### 3.9.4 macOS
对于使用 macOS 的用户在任务栏点击 Docker Desktop 应用图标 -> `Settings...`在左侧导航菜单选择 `Docker Engine`在右侧像下边一样编辑 json 文件修改完成之后点击 `Apply & restart` 按钮Docker 就会重启并应用配置的镜像地址了
```json
{
"registry-mirrors": [
"https://hub.atomgit.com"
]
}
```
### 3.9.5 检查加速器是否生效
执行 `$ docker info`如果从结果中看到了如下内容说明配置成功
```bash
Registry Mirrors:
https://hub.atomgit.com/
```
### 3.9.6 Kubernetes 官方镜像地址迁移
可以登录[阿里云容器镜像服务](https://www.aliyun.com/product/acr?source=5176.11533457&userCode=8lx5zmtu&type=copy)**镜像中心**->**镜像搜索** 查找。
Kubernetes 社区已将官方镜像地址从 `k8s.gcr.io` 迁移到 `registry.k8s.io`建议优先使用新地址
一般情况下有如下对应关系
```bash
$ docker pull registry.k8s.io/xxx
```
### 3.9.7 不再提供服务的镜像
某些镜像不再提供服务添加无用的镜像加速器会拖慢镜像拉取速度你可以从镜像配置列表中删除它们
* https://dockerhub.azk8s.cn **已转为私有**
* https://reg-mirror.qiniu.com
* https://registry.docker-cn.com
* https://hub-mirror.c.163.com
* https://mirror.baidubce.com
建议 **watch (页面右上角)** [镜像测试](https://github.com/docker-practice/docker-registry-cn-mirror-test)这个 GitHub 仓库,我们会在此更新各个镜像地址的状态。
### 3.9.8 云服务商
某些云服务商提供了 **仅供内部** 访问的镜像服务当您的 Docker 运行在云平台时可以选择它们
* [腾讯云 `https://mirror.ccs.tencentyun.com`](https://cloud.tencent.com/act/cps/redirect?redirect=10058&cps_key=3a5255852d5db99dcd5da4c72f05df61)

View File

@@ -1,18 +0,0 @@
# 第三章 安装 Docker
Docker 分为 `stable` `test` `nightly` 三个更新频道
官方网站上有各种环境下的[安装指南](https://docs.docker.com/get-docker/),这里主要介绍 Docker 在 `Linux`、`Windows 10` 和 `macOS` 上的安装。
## 详细安装指南
* [Ubuntu](3.1_ubuntu.md)
* [Debian](3.2_debian.md)
* [Fedora](3.3_fedora.md)
* [CentOS](3.4_centos.md)
* [Raspberry Pi](3.5_raspberry-pi.md)
* [Linux 离线安装](3.6_offline.md)
* [macOS](3.7_mac.md)
* [Windows 10/11](3.8_windows.md)
* [镜像加速器](3.9_mirror.md)
* [开启实验特性](3.10_experimental.md)

View File

@@ -1,27 +0,0 @@
## 本章小结
Docker 支持在多种平台上安装和使用选择合适的安装方式是顺利使用 Docker 的第一步
| 平台 | 推荐方式 | 说明 |
|------|---------|------|
| **Ubuntu/Debian** | 官方 APT 仓库 | 最完善的支持推荐首选 |
| **CentOS/Fedora** | 官方 YUM/DNF 仓库 | 注意关闭 SELinux 或配置策略 |
| **macOS** | Docker Desktop | 图形化安装包含 Compose Kubernetes |
| **Windows 10/11** | Docker Desktop (WSL 2) | 需启用 WSL 2 后端 |
| **Raspberry Pi** | 官方安装脚本 | 支持 ARM 架构 |
| **离线环境** | 二进制包安装 | 适用于无法联网的服务器 |
### 3.11.1 安装后验证
安装完成后运行以下命令验证 Docker 是否正常工作
```bash
$ docker version
$ docker run --rm hello-world
```
### 3.11.2 延伸阅读
- [镜像加速器](3.9_mirror.md)解决国内拉取镜像慢的问题
- [开启实验特性](3.10_experimental.md)使用最新功能
- [Docker Hub](../06_repository/6.1_dockerhub.md)官方镜像仓库

View File

@@ -1,241 +0,0 @@
## 4.1 获取镜像
Docker 镜像仓库获取镜像可谓是 Docker 运作的第一步本节将介绍如何使用 `docker pull` 命令下载镜像以及如何理解下载过程
### 4.1.1 docker pull 命令
从镜像仓库获取镜像的命令是 `docker pull`
```bash
docker pull [选项] [Registry地址/]仓库名[:标签]
```
#### 镜像名称格式
Docker 镜像名称由 Registry 地址用户名仓库名和标签组成其标准格式如下
```bash
docker.io / library / ubuntu : 24.04
────┬──── ───┬─── ──┬─── ──┬──
│ │ │ │
Registry地址 用户名 仓库名 标签
(可省略) (可省略)
```
| 组成部分 | 说明 | 默认值 |
|---------|------|--------|
| Registry 地址 | 镜像仓库地址 | `docker.io` (Docker Hub)|
| 用户名 | 镜像所属用户/组织 | `library` (官方镜像)|
| 仓库名 | 镜像名称 | 必须指定 |
| 标签 | 版本标识 | `latest` |
#### 示例
```bash
## 完整格式
$ docker pull docker.io/library/ubuntu:24.04
## 省略 Registry默认 Docker Hub
$ docker pull library/ubuntu:24.04
## 省略 library官方镜像
$ docker pull ubuntu:24.04
## 省略标签(默认 latest
$ docker pull ubuntu
## 拉取第三方镜像
$ docker pull bitnami/redis:latest
## 从其他 Registry 拉取
$ docker pull ghcr.io/username/myapp:v1.0
```
---
### 4.1.2 下载过程解析
当我们执行 `docker pull` 命令时Docker 会输出详细的下载进度让我们以 `ubuntu:24.04` 为例来解析这些信息
```bash
$ docker pull ubuntu:24.04
24.04: Pulling from library/ubuntu
92dc2a97ff99: Pull complete
be13a9d27eb8: Pull complete
c8299583700a: Pull complete
Digest: sha256:4bc3ae6596938cb0d9e5ac51a1152ec9dcac2a1c50829c74abd9c4361e321b26
Status: Downloaded newer image for ubuntu:24.04
docker.io/library/ubuntu:24.04
```
#### 输出解读
| 输出内容 | 说明 |
|---------|------|
| `Pulling from library/ubuntu` | 正在从官方 ubuntu 仓库拉取 |
| `92dc2a97ff99: Pull complete` | 各层的下载状态 (显示层 ID 12 )|
| `Digest: sha256:...` | 镜像内容的唯一摘要 |
| `docker.io/library/ubuntu:24.04` | 镜像的完整名称 |
#### 分层下载
从输出可以看到镜像是 **分层下载**
```mermaid
flowchart TD
subgraph Image ["ubuntu:24.04 镜像"]
direction TB
L3["第3层 c8299583700a<br/>(已存在,跳过下载)"]
L2["第2层 be13a9d27eb8<br/>(下载中... 完成)"]
L1["第1层 92dc2a97ff99<br/>(下载中... 完成)"]
L3 --- L2 --- L1
end
```
如果本地已有相同的层Docker 会跳过下载节省带宽和时间
---
### 4.1.3 常用选项
`docker pull` 命令支持多种选项来满足不同的下载需求例如下载所有标签指定平台架构等
| 选项 | 说明 | 示例 |
|------|------|------|
| `--all-tags, -a` | 拉取所有标签 | `docker pull -a ubuntu` |
| `--platform` | 指定平台架构 | `docker pull --platform linux/arm64 nginx` |
| `--quiet, -q` | 静默模式 | `docker pull -q nginx` |
#### 指定平台
Apple Silicon Mac 上拉取 x86 镜像
```bash
$ docker pull --platform linux/amd64 nginx
```
---
### 4.1.4 拉取后运行
拉取镜像后可以基于它启动容器
```bash
## 拉取镜像
$ docker pull ubuntu:24.04
## 运行容器
$ docker run -it --rm ubuntu:24.04 bash
root@e7009c6ce357:/# cat /etc/os-release
PRETTY_NAME="Ubuntu 24.04 LTS"
...
root@e7009c6ce357:/# exit
```
**参数说明**
| 参数 | 说明 |
|------|------|
| `-it` | 交互式终端模式 |
| `--rm` | 退出后自动删除容器 |
| `bash` | 启动命令 |
> 💡 `docker run` 在需要时会自动 `pull` 镜像因此通常不需要单独执行 `docker pull`
---
### 4.1.5 镜像加速
Docker Hub 下载可能较慢可以配置镜像加速器
```json
// /etc/docker/daemon.json (Linux)
// ~/.docker/daemon.json (Docker Desktop)
{
"registry-mirrors": [
"https://your-accelerator-url"
]
}
```
配置后重启 Docker
```bash
$ sudo systemctl restart docker # Linux
## 或在 Docker Desktop 中重启
## 或在 Docker Desktop 中重启
```
详见[镜像加速器](../03_install/3.9_mirror.md)章节
---
### 4.1.6 验证镜像完整性
为了确保下载的镜像没有被篡改且内容一致我们可以校验镜像的摘要 (Digest)
#### 查看镜像摘要
```bash
$ docker images --digests ubuntu
REPOSITORY TAG DIGEST IMAGE ID
ubuntu 24.04 sha256:4bc3ae6596938cb0d9e5ac51a1152ec9dcac2a1c50829c74abd9c4361e321b26 ca2b0f26964c
```
#### 使用摘要拉取
用摘要拉取可确保获取完全相同的镜像
```bash
$ docker pull ubuntu@sha256:4bc3ae6596938cb0d9e5ac51a1152ec9dcac2a1c50829c74abd9c4361e321b26
```
> 笔者建议生产环境使用摘要而非标签因为标签可能被覆盖摘要则是不可变的
---
### 4.1.7 常见问题
在使用 `docker pull` 过程中可能会遇到下载速度慢镜像不存在或磁盘空间不足等问题以下是一些常见问题的排查思路
#### Q下载速度很慢
1. 配置镜像加速器
2. 检查网络连接
3. 尝试拉取更小的镜像版本 ( `alpine` 变体)
#### Q提示镜像不存在
```bash
Error: pull access denied, repository does not exist
```
可能原因
- 镜像名拼写错误
- 私有镜像未登录 (需要 `docker login`)
- 镜像确实不存在
#### Q磁盘空间不足
```bash
## 清理未使用的镜像
$ docker image prune
## 清理所有未使用资源
$ docker system prune
```
---

View File

@@ -1,267 +0,0 @@
## 4.2 列出镜像
在下载了镜像后我们可以使用 `docker image ls` 命令列出本地主机上的镜像
### 4.2.1 基本用法
查看本地已下载的镜像
```bash
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
redis latest 5f515359c7f8 5 days ago 183MB
nginx latest 05a60462f8ba 5 days ago 181MB
ubuntu 24.04 329ed837d508 3 days ago 78MB
ubuntu noble 329ed837d508 3 days ago 78MB
```
> 💡 `docker images` `docker image ls` 的简写两者等效
---
### 4.2.2 输出字段说明
`docker image ls` 命令默认输出的列表包含仓库名标签镜像 ID创建时间和占用空间等信息
| 字段 | 说明 |
|------|------|
| **REPOSITORY** | 仓库名 |
| **TAG** | 标签 (版本)|
| **IMAGE ID** | 镜像唯一标识 ( ID 12 )|
| **CREATED** | 创建时间 |
| **SIZE** | 本地占用空间 |
#### 同一镜像多个标签
注意上面的 `ubuntu:24.04` `ubuntu:noble` 拥有相同的 IMAGE ID它们是同一个镜像的不同标签只占用一份存储空间
---
### 4.2.3 理解镜像大小
Docker 镜像的大小可能与我们通常理解的文件大小有所不同这涉及到分层存储的概念
#### 本地大小 vs Hub 显示大小
| 位置 | 显示大小 | 说明 |
|------|---------|------|
| Docker Hub | 29MB | 压缩后的网络传输大小 |
| docker image ls | 78MB | 本地解压后的实际大小 |
#### 实际磁盘占用
由于镜像是分层存储不同镜像可能共享相同的层
```bash
ubuntu:24.04 nginx:latest redis:latest
│ │ │
└───────┬───────┘ │
▼ │
共享基础层 ◄───────────────────┘
```
因此`docker image ls` 中各镜像大小之和 > 实际磁盘占用
#### 查看实际空间占用
```bash
$ docker system df
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 15 3 2.5GB 1.8GB (72%)
Containers 5 2 100MB 80MB (80%)
Local Volumes 8 2 500MB 400MB (80%)
Build Cache 0 0 0B 0B
```
---
### 4.2.4 过滤镜像
随着本地镜像数量的增加我们需要更有效的方式来查找特定的镜像Docker 提供了多种过滤方式
#### 按仓库名过滤
```bash
## 列出所有 ubuntu 镜像
$ docker images ubuntu
REPOSITORY TAG IMAGE ID SIZE
ubuntu 24.04 329ed837d508 78MB
ubuntu noble 329ed837d508 78MB
ubuntu 22.04 a1b2c3d4e5f6 72MB
```
#### 按仓库名和标签过滤
```bash
$ docker images ubuntu:24.04
REPOSITORY TAG IMAGE ID SIZE
ubuntu 24.04 329ed837d508 78MB
```
#### 使用过滤器 --filter
| 过滤条件 | 说明 | 示例 |
|---------|------|------|
| `dangling=true` | 虚悬镜像 | `-f dangling=true` |
| `before=镜像` | 在某镜像之前创建 | `-f before=nginx:latest` |
| `since=镜像` | 在某镜像之后创建 | `-f since=nginx:latest` |
| `label=key=value` | LABEL 过滤 | `-f label=version=1.0` |
| `reference=pattern` | 按名称模式 | `-f reference='*:latest'` |
```bash
## 列出 nginx 之后创建的镜像
$ docker images -f since=nginx:latest
## 列出所有带 latest 标签的镜像
$ docker images -f reference='*:latest'
## 列出带特定 LABEL 的镜像
$ docker images -f label=maintainer=example@email.com
```
---
### 4.2.5 虚悬镜像
在镜像列表里你可能会看到一些仓库名和标签都为 `<none>` 的镜像这类镜像被称为虚悬镜像
#### 什么是虚悬镜像
仓库名和标签都显示为 `<none>` 的镜像
```bash
$ docker images
REPOSITORY TAG IMAGE ID SIZE
<none> <none> 00285df0df87 342MB
```
#### 产生原因
1. **镜像重新构建**新镜像使用了旧镜像的标签旧镜像标签被移除
2. **docker pull 更新**拉取更新版本时旧版本失去标签
#### 处理虚悬镜像
```bash
## 列出虚悬镜像
$ docker images -f dangling=true
## 删除虚悬镜像
$ docker image prune
```
---
### 4.2.6 中间层镜像
除了虚悬镜像`docker image ls` 默认列出的只是顶层镜像还有一种镜像是为了加速镜像构建重复利用资源而存在的中间层镜像
#### 查看所有镜像 (包含中间层)
```bash
$ docker images -a
```
会显示很多无标签镜像这些是构建过程中产生的中间层被其他镜像依赖
> 不要删除中间层镜像它们是其他镜像的依赖删除会导致上层镜像无法使用删除顶层镜像时会自动清理不再需要的中间层
---
### 4.2.7 格式化输出
为了配合脚本使用或展示更关注的信息我们可以使用 `--format` 参数来自定义输出格式
#### 只输出 ID
```bash
$ docker images -q
5f515359c7f8
05a60462f8ba
329ed837d508
```
常用于配合其他命令
```bash
## 删除所有镜像
$ docker rmi $(docker images -q)
## 删除所有 redis 镜像
$ docker rmi $(docker images -q redis)
```
#### 显示完整 ID
```bash
$ docker images --no-trunc
```
#### 显示摘要
```bash
$ docker images --digests
REPOSITORY TAG DIGEST IMAGE ID
nginx latest sha256:b4f0e0bdeb5... e43d811ce2f4
```
#### 自定义格式
使用 Go 模板语法自定义输出
```bash
## 只显示 ID 和仓库名
$ docker images --format "{{.ID}}: {{.Repository}}"
5f515359c7f8: redis
05a60462f8ba: nginx
329ed837d508: ubuntu
## 表格形式(带标题)
$ docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}"
REPOSITORY TAG SIZE
redis latest 183MB
nginx latest 181MB
ubuntu 24.04 78MB
```
#### 可用模板字段
| 字段 | 说明 |
|------|------|
| `.ID` | 镜像 ID |
| `.Repository` | 仓库名 |
| `.Tag` | 标签 |
| `.Digest` | 摘要 |
| `.CreatedSince` | 创建后经过的时间 |
| `.CreatedAt` | 创建时间 |
| `.Size` | 大小 |
---
### 4.2.8 常用命令组合
```bash
## 列出所有镜像及其大小,按大小排序(需要系统 sort 命令)
$ docker images --format "{{.Size}}\t{{.Repository}}:{{.Tag}}" | sort -h
## 查找大于 500MB 的镜像
$ docker images --format "{{.Size}}\t{{.Repository}}:{{.Tag}}" | grep -E "^[0-9]+GB|^[5-9][0-9]{2}MB"
## 导出镜像列表
$ docker images --format "{{.Repository}}:{{.Tag}}" > images.txt
```
---

View File

@@ -1,270 +0,0 @@
## 4.3 删除本地镜像
当不再需要某个镜像时我们可以将其删除以释放存储空间本节介绍删除镜像的常用方法
### 4.3.1 基本用法
使用 `docker image rm` 删除本地镜像
```bash
$ docker image rm [选项] <镜像1> [<镜像2> ...]
```
> 💡 `docker rmi` `docker image rm` 的简写两者等效
---
### 4.3.2 镜像标识方式
删除镜像时可以使用多种方式指定镜像
| 方式 | 说明 | 示例 |
|------|------|------|
| ** ID** | ID 的前几位 (通常 3-4 )| `docker rmi 501` |
| ** ID** | 完整的镜像 ID | `docker rmi 501ad78535f0...` |
| **镜像名:标签** | 仓库名和标签 | `docker rmi redis:alpine` |
| **镜像摘要** | 精确的内容摘要 | `docker rmi nginx@sha256:...` |
#### 使用短 ID 删除
```bash
$ docker image ls
REPOSITORY TAG IMAGE ID SIZE
redis alpine 501ad78535f0 30MB
nginx latest e43d811ce2f4 142MB
## 只需输入足够区分的前几位
$ docker rmi 501
Untagged: redis:alpine
Deleted: sha256:501ad78535f0...
```
#### 使用镜像名删除
```bash
$ docker rmi redis:alpine
Untagged: redis:alpine
Deleted: sha256:501ad78535f0...
```
#### 使用摘要删除
摘要删除最精确适用于 CI/CD 场景
```bash
## 查看镜像摘要
$ docker images --digests
REPOSITORY TAG DIGEST IMAGE ID
nginx latest sha256:b4f0e0bdeb5... e43d811ce2f4
## 使用摘要删除
$ docker rmi nginx@sha256:b4f0e0bdeb578043c1ea6862f0d40cc4afe32a4a582f3be235a3b164422be228
```
---
### 4.3.3 理解输出信息
执行删除命令后Docker 会输出一系列的操作记录理解这些信息有助于我们掌握镜像删除的机制
删除镜像时会看到两类信息**Untagged** **Deleted**
```bash
$ docker rmi redis:alpine
Untagged: redis:alpine
Untagged: redis@sha256:f1ed3708f538b537eb9c2a7dd50dc90a706f7debd7e1196c9264edeea521a86d
Deleted: sha256:501ad78535f015d88872e13fa87a828425117e3d28075d0c117932b05bf189b7
Deleted: sha256:96167737e29ca8e9d74982ef2a0dda76ed7b430da55e321c071f0dbff8c2899b
Deleted: sha256:32770d1dcf835f192cafd6b9263b7b597a1778a403a109e2cc2ee866f74adf23
```
#### Untagged vs Deleted
| 操作 | 含义 |
|------|------|
| **Untagged** | 移除镜像的标签 |
| **Deleted** | 删除镜像的存储层 |
#### 删除流程
Docker 会检测镜像是否有容器依赖或其他标签指向只有在确认为无用资源时才会真正删除存储层
```mermaid
flowchart TD
Start(["docker rmi redis:alpine"]) --> Step1
subgraph Process ["删除流程"]
direction TB
Step1["1. Untag移除 redis:alpine 标签"] --> Step2
Step2{"2. 检查是否还有其他标签指向此镜像"}
Step2 -- "有" --> Keep1["只 Untag不删除"]
Step2 -- "无" --> Step3
Step3{"3. 检查是否有容器依赖"}
Step3 -- "有" --> Error["报错,无法删除"]
Step3 -- "无" --> Step4
Step4{"4. 从上到下逐层删除,检查每层是否被其他镜像使用"}
Step4 -- "被使用" --> Keep2["保留该层"]
Step4 -- "未使用" --> Delete["Deleted (删除该层)"]
end
```
---
### 4.3.4 批量删除
手动一个一个删除镜像非常繁琐Docker 提供了 `image prune` 命令和 shell 组合命令来实现批量清理
#### 删除所有虚悬镜像
虚悬镜像 (dangling)没有标签的镜像通常是旧版本被新版本覆盖后产生的
```bash
## 查看虚悬镜像
$ docker images -f dangling=true
## 删除虚悬镜像
$ docker image prune
## 不提示确认
$ docker image prune -f
```
#### 删除所有未使用的镜像
```bash
## 删除所有没有被容器使用的镜像
$ docker image prune -a
## 保留最近 24 小时的
$ docker image prune -a --filter "until=24h"
```
#### 按条件删除
```bash
## 删除所有 redis 镜像
$ docker rmi $(docker images -q redis)
## 删除 mongo:8.0 之前的所有镜像
$ docker rmi $(docker images -q -f before=mongo:8.0)
## 删除某个时间之前的镜像
$ docker image prune -a --filter "until=168h" # 7天前
```
---
### 4.3.5 删除失败的常见原因
在删除镜像时Docker 可能会提示错误并拒绝执行这通常是为了防止误删正在使用的资源
#### 原因一有容器依赖
```bash
$ docker rmi nginx
Error: conflict: unable to remove repository reference "nginx"
(must force) - container abc123 is using its referenced image
```
**解决方案**
```bash
## 方案1先删除依赖的容器
$ docker rm abc123
$ docker rmi nginx
## 方案2强制删除镜像容器仍可运行但无法再创建新容器
$ docker rmi -f nginx
```
#### 原因二多个标签指向同一镜像
```bash
$ docker images
REPOSITORY TAG IMAGE ID
ubuntu 24.04 ca2b0f26964c
ubuntu latest ca2b0f26964c # 同一个镜像
$ docker rmi ubuntu:24.04
Untagged: ubuntu:24.04
## 只是移除标签,镜像仍存在(因为还有 ubuntu:latest 指向它)
当同一个镜像有多个标签时,`docker rmi` 只是删除指定的标签,不会删除镜像本身。
```
#### 原因三被其他镜像依赖 (中间层)
```bash
$ docker rmi some_base_image
Error: image has dependent child images
```
中间层镜像被其他镜像依赖无法删除需要先删除依赖它的镜像
---
### 4.3.6 常用过滤条件
| 过滤条件 | 说明 | 示例 |
|---------|------|------|
| `dangling=true` | 虚悬镜像 | `-f dangling=true` |
| `before=镜像` | 在某镜像之前 | `-f before=mongo:3.2` |
| `since=镜像` | 在某镜像之后 | `-f since=mongo:3.2` |
| `label=key=value` | 按标签过滤 | `-f label=version=1.0` |
| `reference=pattern` | 按名称模式 | `-f reference='*:latest'` |
---
### 4.3.7 清理策略
针对不同的环境 (开发环境 vs 生产环境)我们应该采用不同的镜像清理策略
#### 开发环境
```bash
## 定期清理虚悬镜像
$ docker image prune -f
## 一键清理所有未使用资源
$ docker system prune -a
```
#### CI/CD 环境
```bash
## 只保留最近使用的镜像
$ docker image prune -a --filter "until=72h" -f
```
#### 查看空间占用
```bash
$ docker system df
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 15 3 2.5GB 1.8GB (72%)
Containers 5 2 100MB 80MB (80%)
Local Volumes 8 2 500MB 400MB (80%)
Build Cache 0 0 0B 0B
```
---

View File

@@ -1,65 +0,0 @@
## 4.7 实现原理
Docker 镜像是怎么实现增量的修改和维护的为什么容器启动如此之快这一切都归功于 Docker 的镜像分层存储设计
### 4.7.1 镜像与分层存储
在之前的章节中我们一直强调镜像包含操作系统完整的 `root` 文件系统其体积往往是庞大的因此在 Docker 设计时就充分利用 **Union FS** 的技术将其设计为分层存储的架构
Docker 镜像并不是一个单纯的文件而是由一组文件系统叠加构成的
最底层的镜像称为 **基础镜像 (Base Image)**通常是各种 Linux 发行版的 root 文件系统 UbuntuDebianCentOS
当我们在基础镜像之上构建新的镜像时 (例如安装了 Nginx)Docker 并不是复制一份基础镜像而是在基础镜像之上**新建一个层 (Layer)**并在该层中仅记录为了安装 Nginx 而发生的文件变更 (添加修改删除)
这种分层存储结构使得镜像的复用分发变得非常高效
* **复用**如果多个镜像都基于同一个基础镜像 (例如都基于 `ubuntu:24.04`)那么宿主机只需要下载一份 `ubuntu:24.04`所有镜像都可以共享它
* **轻量**镜像仅仅记录了与基础镜像的差异因此体积非常小
### 4.7.2 容器层与读写
我们要理解的一个关键概念是**镜像的每一层都是只读的 (Read-only)**
那么既然镜像只读容器为什么能写文件呢
当容器启动时Docker 会在镜像的最上层添加一个新的 **可写层 (Writable Layer)**通常被称为 **容器层**
```mermaid
flowchart TD
subgraph Container ["运行中的容器"]
direction TB
L4["容器层 (可写, Writable Container Layer)"]
L3["镜像层 (只读, Read-only Image Layer)"]
L2["镜像层 (只读, Read-only Image Layer)"]
L1["基础镜像层 (只读, Base Image Layer)"]
L4 --- L3 --- L2 --- L1
end
Note["所有的写操作都在容器层这里"] -.-> L4
```
* **读取文件**当容器需要读取文件时Docker 会从最上层 (容器层) 开始向下层 (镜像层) 寻找直到找到该文件为止
* **修改文件**当容器需要修改某个文件时Docker 会从下层镜像中将该文件复制到上层的容器层然后对副本进行修改这被称为 **写时复制 (Copy-on-WriteCoW)** 策略
* **删除文件**当容器删除某个文件时Docker 并不是真的去下层删除它 (因为下层是只读的)而是在容器层创建一个特殊的 白障 (Whiteout) 文件用来标记该文件已被删除从而在容器视图中隐藏它
这就是为什么
1. **容器删除后数据会丢失**因为所有的数据修改都保存在最上层的容器层中容器销毁时这个层也就随之销毁了(除非使用了数据卷详见[数据管理](../08_data/README.md))
2. **镜像不可变**无论我们在容器里删除了多少文件基础镜像的体积并不会减小因为它们依然存在于底层的只读层中
### 4.7.3 内容寻址与镜像 ID
Docker 镜像的每一层都有一个唯一的 ID这个 ID 是根据该层的内容计算出来的哈希值 (SHA256)这意味着
* **内容即 ID**只要层的内容有一丁点变化ID 就会变
* **安全性**确保了镜像内容的完整性下载过程中如果数据损坏ID 校验就会失败
* **去重**如果两个不同的镜像 (甚至是不同来源的镜像) 包含相同的层 (ID 相同)Docker 引擎在本地只会存储一份绝不重复下载
### 4.7.4 联合文件系统
Docker 使用联合文件系统 (Union FS) 来实现这种分层挂载常见的驱动包括 `overlay2` (目前推荐)`aufs` (早期使用)`btrfs``zfs`
虽然实现细节不同但它们都遵循上述的 **分层 + CoW** 模型
> 想要深入了解 Overlay2 等文件系统的具体实现原理包括 WorkDirUpperDirLowerDir 等底层细节请阅读 **[第十二章 底层实现](../12_implementation/README.md)** 中的 **[联合文件系统](../12_implementation/12.4_ufs.md)** 章节

View File

@@ -1,17 +0,0 @@
# 第四章 使用镜像
在之前的介绍中我们知道镜像是 Docker 的三大组件之一
Docker 运行容器前需要本地存在对应的镜像如果本地不存在该镜像Docker 会从镜像仓库下载该镜像
## 本章内容
本章将介绍更多关于镜像的内容包括
* [从仓库获取镜像](4.1_pull.md)
* [列出镜像](4.2_list.md)
* [删除本地镜像](4.3_rm.md)
* [利用 commit 理解镜像构成](4.4_commit.md)
* [使用 Dockerfile 定制镜像](4.5_build.md)
* [其它制作镜像的方式](4.6_other.md)
* [镜像的实现原理](4.7_internal.md)

View File

@@ -1,46 +0,0 @@
## 本章小结
| 操作 | 命令 |
|------|------|
| 拉取镜像 | `docker pull 镜像名:标签` |
| 拉取所有标签 | `docker pull -a 镜像名` |
| 指定平台 | `docker pull --platform linux/amd64 镜像名` |
| 用摘要拉取 | `docker pull 镜像名@sha256:...` |
### 4.8.1 延伸阅读
- [列出镜像](4.2_list.md)查看本地镜像
- [删除镜像](4.3_rm.md)清理本地镜像
- [镜像加速器](../03_install/3.9_mirror.md)加速镜像下载
- [Docker Hub](../06_repository/6.1_dockerhub.md)官方镜像仓库
| 操作 | 命令 |
|------|------|
| 列出所有镜像 | `docker images` |
| 按仓库名过滤 | `docker images nginx` |
| 列出虚悬镜像 | `docker images -f dangling=true` |
| 只输出 ID | `docker images -q` |
| 显示摘要 | `docker images --digests` |
| 自定义格式 | `docker images --format "..."` |
| 查看空间占用 | `docker system df` |
### 4.8.2 延伸阅读
- [获取镜像](4.1_pull.md) Registry 拉取镜像
- [删除镜像](4.3_rm.md)清理本地镜像
- [镜像](../02_basic_concept/2.1_image.md)理解镜像概念
| 操作 | 命令 |
|------|------|
| 删除指定镜像 | `docker rmi 镜像名:标签` |
| 强制删除 | `docker rmi -f 镜像名` |
| 删除虚悬镜像 | `docker image prune` |
| 删除未使用镜像 | `docker image prune -a` |
| 批量删除 | `docker rmi $(docker images -q -f ...)` |
| 查看空间占用 | `docker system df` |
### 4.8.3 延伸阅读
- [列出镜像](4.2_list.md)查看和过滤镜像
- [删除容器](../05_container/5.6_rm.md)清理容器
- [数据卷](../08_data/8.1_volume.md)清理数据卷

View File

@@ -1,225 +0,0 @@
## 5.1 启动
本节将详细介绍 Docker 容器的启动方式包括新建启动和重新启动已停止的容器
### 5.1.1 启动方式概述
启动容器有两种方式
- **新建并启动**基于镜像创建新容器
- **重新启动**将已终止的容器重新运行
由于 Docker 容器非常轻量实际使用中常常是随时删除和新建容器而不是反复重启同一个容器
### 5.1.2 新建并启动
#### 基本语法
```bash
docker run [选项] 镜像 [命令] [参数...]
```
#### 最简单的例子
输出 Hello World 后容器自动终止
```bash
$ docker run ubuntu:24.04 /bin/echo 'Hello world'
Hello world
```
这与直接执行 `/bin/echo 'Hello world'` 几乎没有区别但实际上已经启动了一个完整的 Ubuntu 容器来执行这条命令
#### 交互式容器
启动一个可以交互的 bash 终端
```bash
$ docker run -it ubuntu:24.04 /bin/bash
root@af8bae53bdd3:/#
```
**参数说明**
| 参数 | 作用 |
|------|------|
| `-i` | 保持标准输入 (stdin) 打开允许输入 |
| `-t` | 分配伪终端 (pseudo-TTY)提供终端界面 |
| `-it` | 两者组合使用获得交互式终端 |
在交互模式下可以执行命令
```bash
root@af8bae53bdd3:/# pwd
/
root@af8bae53bdd3:/# ls
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
root@af8bae53bdd3:/# exit # 退出容器
```
### 5.1.3 docker run 的完整流程
执行 `docker run` Docker 在后台完成以下操作
```mermaid
flowchart TD
Cmd["docker run ubuntu:24.04 /bin/echo 'Hello'"] --> Step1
Step1{"1. 检查本地是否有 ubuntu:24.04 镜像"}
Step1 -- 有 --> Step1_Yes["使用本地镜像"]
Step1 -- 无 --> Step1_No["从 Registry 下载"]
Step1_Yes --> Step2
Step1_No --> Step2
Step2["2. 创建容器<br/>• 基于镜像的只读层<br/>• 添加一层可读写层(容器存储层)"] --> Step3
Step3["3. 配置网络<br/>• 创建虚拟网卡<br/>• 分配 IP 地址<br/>• 连接到 Docker 网桥"] --> Step4
Step4["4. 启动容器,执行指定命令"] --> Step5
Step5["5. 命令执行完毕,容器停止"]
```
### 5.1.4 常用启动选项
#### 基础选项
| 选项 | 说明 | 示例 |
|------|------|------|
| `-d` | 后台运行 (detach)| `docker run -d nginx` |
| `-it` | 交互式终端 | `docker run -it ubuntu bash` |
| `--name` | 指定容器名称 | `docker run --name myapp nginx` |
| `--rm` | 退出后自动删除容器 | `docker run --rm ubuntu echo hi` |
#### 端口映射
```bash
## 将容器的 80 端口映射到宿主机的 8080 端口
$ docker run -d -p 8080:80 nginx
## 随机映射端口
$ docker run -d -P nginx
## 只绑定到 localhost
$ docker run -d -p 127.0.0.1:8080:80 nginx
```
#### 数据卷挂载
```bash
## 挂载命名卷
$ docker run -v mydata:/data nginx
## 挂载宿主机目录
$ docker run -v /host/path:/container/path nginx
## 只读挂载
$ docker run -v /host/path:/container/path:ro nginx
```
#### 环境变量
```bash
## 设置单个环境变量
$ docker run -e MYSQL_ROOT_PASSWORD=secret mysql
## 从文件加载环境变量
$ docker run --env-file .env myapp
```
#### 资源限制
```bash
## 限制内存
$ docker run -m 512m nginx
## 限制 CPU
$ docker run --cpus=1.5 nginx
```
### 5.1.5 启动已终止容器
使用 `docker start` 重新启动已停止的容器
```bash
## 查看所有容器(包括已停止的)
$ docker ps -a
CONTAINER ID IMAGE STATUS NAMES
af8bae53bdd3 ubuntu Exited (0) 2 minutes ago myubuntu
## 重新启动
$ docker start myubuntu
## 启动并附加终端
$ docker start -ai myubuntu
```
### 5.1.6 容器内进程的特点
容器内只运行指定的应用程序及其必需资源
```bash
root@ba267838cc1b:/# ps
PID TTY TIME CMD
1 ? 00:00:00 bash
11 ? 00:00:00 ps
```
可见容器中仅运行了 `bash` 进程这种特点使得 Docker 对资源的利用率极高
> 💡 笔者提示容器内的 PID 1 进程很重要它是容器的主进程该进程退出则容器停止详见[后台运行](5.2_daemon.md)章节
### 5.1.7 常见问题
#### Q容器启动后立即退出
**原因**主进程执行完毕或无法保持运行
```bash
## 这个容器会立即退出echo 执行完就结束了)
$ docker run ubuntu echo "hello"
## 解决:使用能持续运行的命令
$ docker run -d nginx # nginx 是持续运行的服务
```
详细解释见[后台运行](5.2_daemon.md)
#### Q无法连接容器内的服务
**原因**未正确映射端口
```bash
## 错误:没有 -p 参数,外部无法访问
$ docker run -d nginx
## 正确:映射端口
$ docker run -d -p 80:80 nginx
```
#### Q容器内修改的文件丢失
**原因**未使用数据卷数据保存在容器存储层
```bash
## 使用数据卷持久化
$ docker run -v mydata:/app/data myapp
```
详见[数据管理](../08_data/README.md)

View File

@@ -1,234 +0,0 @@
## 5.2 守护态运行
在生产环境中我们通常需要容器持续运行不受终端关闭的影响本节将深入讲解如何让容器在后台运行以及理解容器生命周期的核心概念
### 5.2.1 核心概念前台 vs 后台
当你在终端运行一个程序时有两种模式
- **前台运行**程序占用当前终端输出直接显示关闭终端程序就停止
- **后台运行**程序在后台执行不占用终端终端关闭也不影响程序
Docker 容器默认是 **前台运行** 使用 `-d` (detach) 参数可以让容器在后台运行
### 5.2.2 基本使用
#### 前台运行 (默认)
```bash
$ docker run ubuntu:24.04 /bin/sh -c "while true; do echo hello world; sleep 1; done"
hello world
hello world
hello world
hello world
```
容器会把输出的结果 (STDOUT) 打印到宿主机上面此时
- 终端被占用无法执行其他命令
- `Ctrl+C` 会终止容器
- 关闭终端窗口容器也会停止
#### 后台运行 (使用 -d 参数)
```bash
$ docker run -d ubuntu:24.04 /bin/sh -c "while true; do echo hello world; sleep 1; done"
77b2dc01fe0f3f1265df143181e7b9af5e05279a884f4776ee75350ea9d8017a
```
使用 `-d` 参数后
- 容器在后台运行
- 返回容器的完整 ID
- 终端立即释放可以继续执行其他命令
- 输出不会直接显示 (需要用 `docker logs` 查看)
### 5.2.3 深入理解容器为什么会 立即退出
> **这是初学者最常遇到的困惑** 理解这个问题你就理解了 Docker 的核心设计理念
很多人尝试这样启动容器
```bash
$ docker run -d ubuntu:24.04
```
然后用 `docker ps` 查看发现容器根本不在运行这是为什么
#### 核心原理容器的生命周期与主进程绑定
```mermaid
flowchart TD
subgraph Lifecycle ["Docker 容器的生命周期 = 容器内 PID 1 进程的生命周期"]
direction LR
Start["主进程启动"] --> Run["容器运行"]
Exit["主进程退出"] --> Stop["容器停止"]
end
```
当你运行 `docker run -d ubuntu:24.04`
1. 容器启动
2. 没有指定命令默认执行 `/bin/bash`
3. 但没有交互式终端 (没有 `-it` 参数)bash 发现没有输入源
4. bash 立即退出
5. 主进程退出容器停止
**关键理解**
- `-d` 参数 **不是** 让容器 一直运行
- `-d` 参数是让容器 在后台运行能运行多久取决于主进程
#### 常见的 立即退出 场景
| 场景 | 原因 | 解决方案 |
|------|------|---------|
| `docker run -d ubuntu` | 默认 bash 无输入立即退出 | 指定长期运行的命令 |
| `docker run -d nginx` 后改了配置 | 配置错误导致 nginx 启动失败 | 查看 `docker logs` |
| 自定义镜像容器启动即退 | Dockerfile CMD 执行完毕 | 确保 CMD 是前台进程 |
### 5.2.4 查看后台容器
#### 查看运行中的容器
```bash
$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
77b2dc01fe0f ubuntu:24.04 /bin/sh -c 'while tr 2 minutes ago Up 1 minute agitated_wright
```
#### 查看容器输出日志
```bash
$ docker container logs 77b2dc01fe0f
hello world
hello world
hello world
...
```
**实时查看日志** (类似 `tail -f`)
```bash
$ docker container logs -f 77b2dc01fe0f
```
#### 查看已停止的容器
```bash
$ docker container ls -a
```
加上 `-a` 参数可以看到所有容器包括已停止的这对于调试 容器启动即退出 的问题非常有用
### 5.2.5 最佳实践
#### 1. 长期运行的服务使用 -d
```bash
## Web 服务器
$ docker run -d -p 80:80 nginx
## 数据库
$ docker run -d -p 3306:3306 mysql:8
## 缓存服务
$ docker run -d -p 6379:6379 redis
```
#### 2. 调试时先用前台模式
当容器启动有问题时**去掉 `-d` 参数** 可以直接看到输出和错误
```bash
## 有问题的容器,先前台运行看看发生了什么
$ docker run myimage:latest
```
#### 3. 使用 --rm 自动清理
对于一次性任务使用 `--rm` 参数让容器退出后自动删除
```bash
$ docker run --rm ubuntu:24.04 echo "Hello, World!"
Hello, World!
## 容器执行完后自动删除
...
```
#### 4. 配合日志查看
```bash
## 后台启动
$ docker run -d --name myapp myimage:latest
## 查看最近 100 行日志
$ docker logs --tail 100 myapp
## 实时跟踪日志
$ docker logs -f myapp
## 查看带时间戳的日志
$ docker logs -t myapp
```
### 5.2.6 常见问题排查
#### Q容器启动后立即退出
1. **查看退出状态码**
```bash
$ docker ps -a --filter "name=mycontainer"
# 查看 STATUS 列,如 “Exited (1)” 表示异常退出
```
2. **查看容器日志**
```bash
$ docker logs mycontainer
```
3. **以交互模式调试**
```bash
$ docker run -it myimage:latest /bin/sh
# 进入容器手动执行命令,查找问题
```
#### Q容器在后台运行但无法访问服务
1. **检查端口映射**
```bash
$ docker port mycontainer
```
2. **检查容器内服务状态**
```bash
$ docker exec mycontainer ps aux
```
#### Q如何让已经在后台运行的容器回到前台
使用 `docker attach`
```bash
$ docker attach mycontainer
```
> **注意**`attach` 会连接到容器的主进程如果主进程不是交互式的你可能只能看到输出使用 `Ctrl+P` `Ctrl+Q` 可以安全退出而不停止容器
### 5.2.7 延伸阅读
- [进入容器](5.4_attach_exec.md)如何进入正在运行的容器执行命令
- [容器日志](../appendix/best_practices.md)生产环境的日志管理最佳实践
- [HEALTHCHECK 健康检查](../07_dockerfile/7.12_healthcheck.md)自动检测容器内服务是否正常
- [Docker Compose](../11_compose/README.md)管理多个后台容器的更好方式

View File

@@ -1,233 +0,0 @@
## 5.3 终止
本节将介绍如何终止一个运行中的容器以及几种不同的终止方式及其区别
### 5.3.1 终止方式概述
终止容器有三种方式
| 方式 | 命令 | 说明 |
|------|------|------|
| **优雅停止** | `docker stop` | 先发 SIGTERM超时后发 SIGKILL |
| **强制停止** | `docker kill` | 直接发 SIGKILL |
| **自动终止** | - | 容器主进程退出时自动停止 |
---
### 5.3.2 docker stop (推荐)
#### docker stop 基本用法
```bash
$ docker stop 容器名或ID
```
#### 工作原理
```mermaid
flowchart TD
cmd["docker stop mycontainer"] --> A["1. 发送 SIGTERM 信号给容器主进程 (PID 1)"]
A --> B["2. 等待容器优雅退出 (默认 10 秒)"]
B --> C["3. 如果超时仍未退出,发送 SIGKILL 强制终止"]
```
#### 自定义超时时间
```bash
## 等待 30 秒后强制终止
$ docker stop -t 30 mycontainer
## 立即发送 SIGKILL相当于 docker kill
$ docker stop -t 0 mycontainer
```
#### 停止多个容器
```bash
## 停止多个指定容器
$ docker stop container1 container2 container3
## 停止所有运行中的容器
$ docker stop $(docker ps -q)
```
---
### 5.3.3 docker kill
#### 基本用法
```bash
$ docker kill 容器名或ID
```
#### stop 的区别
| 命令 | 信号 | 使用场景 |
|------|------|---------|
| `docker stop` | SIGTERM SIGKILL | 正常停止让应用优雅退出 |
| `docker kill` | SIGKILL | 应用无响应强制终止 |
#### 发送自定义信号
```bash
## 发送 SIGHUP让进程重新加载配置
$ docker kill -s HUP mycontainer
## 发送 SIGTERM
$ docker kill -s TERM mycontainer
```
---
### 5.3.4 容器自动终止
容器的生命周期与主进程绑定主进程退出时容器自动停止
```bash
## 主进程是交互式 bash
$ docker run -it ubuntu bash
root@abc123:/# exit # 退出 bash → 容器停止
## 主进程执行完毕
$ docker run ubuntu echo "Hello" # echo 执行完 → 容器停止
```
---
### 5.3.5 查看已停止的容器
```bash
$ docker ps -a
CONTAINER ID IMAGE COMMAND STATUS NAMES
ba267838cc1b ubuntu "/bin/bash" Exited (0) 2 minutes ago myubuntu
c5d3a5e8f7b2 nginx "nginx" Up 5 minutes mynginx
```
**STATUS 字段说明**
| 状态 | 说明 |
|------|------|
| `Up X minutes` | 运行中 |
| `Exited (0)` | 正常退出 (退出码 0)|
| `Exited (1)` | 异常退出 (非零退出码)|
| `Exited (137)` | SIGKILL 终止 (128 + 9)|
| `Exited (143)` | SIGTERM 终止 (128 + 15)|
---
### 5.3.6 重新启动容器
#### 启动已停止的容器
```bash
$ docker start 容器名或ID
## 启动并附加终端
$ docker start -ai 容器名
```
#### 重启运行中的容器
```bash
## 先停止再启动
$ docker restart 容器名
## 自定义停止超时
$ docker restart -t 30 容器名
```
---
### 5.3.7 生命周期状态图
```mermaid
stateDiagram-v2
direction TB
[*] --> Created : docker create
Created --> Running : docker start
Running --> Stopped : docker stop
Running --> Paused : docker pause
Paused --> Running : docker unpause
Created --> Deleted : docker rm
Stopped --> Deleted : docker rm
Paused --> Deleted : docker rm
Deleted --> [*]
```
---
### 5.3.8 批量操作
#### 停止所有容器
```bash
$ docker stop $(docker ps -q)
```
#### 删除所有已停止的容器
```bash
$ docker container prune
```
#### 停止并删除所有容器
```bash
$ docker stop $(docker ps -q) && docker container prune -f
```
---
### 5.3.9 常见问题
#### Q容器停止很慢
原因应用没有正确处理 SIGTERM 信号需要等待超时后强制终止
解决方案
1. 在应用中正确处理 SIGTERM
2. 使用 `docker stop -t 0` 立即终止
3. 检查 Dockerfile 中的 `STOPSIGNAL` 配置
#### Q如何让容器优雅退出
确保容器主进程正确处理信号
```dockerfile
## Dockerfile 示例
FROM node:22
...
## 使用 exec 形式确保信号能传递给 node 进程
CMD ["node", "server.js"]
```
#### Q容器无法停止
```bash
## 强制终止
$ docker kill 容器名
## 如果仍无法停止,检查系统资源
$ docker inspect 容器名
```
---

View File

@@ -1,282 +0,0 @@
## 5.4 进入容器
### 5.4.1 为什么需要进入容器
使用 `-d` 参数启动容器后容器在后台运行以下场景需要进入容器内部操作
| 场景 | 示例 |
|------|------|
| **调试问题** | 查看日志检查配置排查错误 |
| **临时操作** | 执行数据库迁移清理缓存 |
| **检查状态** | 查看进程网络连接文件系统 |
| **开发测试** | 交互式测试命令验证环境 |
### 5.4.2 两种进入方式
Docker 提供两种进入容器的命令
| 命令 | 推荐程度 | 特点 |
|------|---------|------|
| `docker exec` | **推荐** | 启动新进程退出不影响容器 |
| `docker attach` | 谨慎使用 | 附加到主进程退出可能停止容器 |
---
### 5.4.3 docker exec (推荐)
#### docker exec 基本用法
```bash
## 进入容器并启动交互式 shell
$ docker exec -it 容器名 /bin/bash
## 或使用 sh适用于 Alpine 等精简镜像)
$ docker exec -it 容器名 /bin/sh
```
#### 参数说明
| 参数 | 作用 |
|------|------|
| `-i` | 保持标准输入打开 (interactive)|
| `-t` | 分配伪终端 (TTY)|
| `-it` | 两者组合获得完整交互体验 |
| `-u` | 指定用户 ( `-u root`)|
| `-w` | 指定工作目录 |
| `-e` | 设置环境变量 |
#### docker exec 示例
```bash
## 启动一个后台容器
$ docker run -dit --name myubuntu ubuntu
69d137adef7a...
## 进入容器(交互式 shell
$ docker exec -it myubuntu bash
root@69d137adef7a:/# ls
bin boot dev etc home lib ...
root@69d137adef7a:/# exit
## 容器仍在运行!
$ docker ps
CONTAINER ID IMAGE STATUS NAMES
69d137adef7a ubuntu Up 2 minutes myubuntu
```
#### 执行单条命令
不进入交互模式直接执行命令
```bash
## 查看容器内进程
$ docker exec myubuntu ps aux
## 查看配置文件
$ docker exec myubuntu cat /etc/nginx/nginx.conf
## 以 root 用户执行
$ docker exec -u root myubuntu apt update
```
#### 只用 -i 不用 -t 的区别
```bash
## 只用 -i可以执行命令但没有提示符
$ docker exec -i myubuntu bash
ls # 输入命令
bin # 输出结果
boot
dev
...
## 用 -it有完整的终端体验
$ docker exec -it myubuntu bash
root@69d137adef7a:/# # 有提示符
```
> 💡 通常使用 `-it` 组合只有在脚本中需要通过管道传入命令时才只用 `-i`
---
### 5.4.4 docker attach (谨慎使用)
#### docker attach 基本用法
```bash
$ docker attach 容器名
```
#### 工作原理
`attach` 会附加到容器的 **主进程** (PID 1) 的标准输入输出
```mermaid
flowchart LR
subgraph Container ["容器"]
direction TB
subgraph Process ["主进程"]
P1["PID 1: /bin/bash<br>(你的输入直接发送到主进程)"]
end
end
Attach["docker attach"] -->|"附加到这里"| P1
```
#### docker attach 示例
```bash
## 启动容器
$ docker run -dit --name myubuntu ubuntu
243c32535da7...
## 附加到容器
$ docker attach myubuntu
root@243c32535da7:/#
```
#### 重要警告
** attach 会话中输入 `exit` 或按 `Ctrl+D` 会导致容器停止**
```bash
$ docker attach myubuntu
root@243c32535da7:/# exit # 这会停止容器!
$ docker ps
CONTAINER ID IMAGE STATUS NAMES
243c32535da7 ubuntu Exited (0) 2 seconds ago myubuntu
```
**原因**attach 附加到主进程退出主进程就等于退出容器
#### 安全退出 attach
使用 `Ctrl+P` 然后 `Ctrl+Q` 可以从 attach 会话中 **分离**而不停止容器
```bash
$ docker attach myubuntu
root@243c32535da7:/#
## 按 Ctrl+P 然后 Ctrl+Q
read escape sequence
$ docker ps # 容器仍在运行
CONTAINER ID IMAGE STATUS NAMES
243c32535da7 ubuntu Up 5 minutes myubuntu
```
---
### 5.4.5 exec vs attach 对比
| 特性 | docker exec | docker attach |
|------|-------------|---------------|
| **工作方式** | 在容器内启动新进程 | 附加到主进程 |
| **退出影响** | 不影响容器 | 可能停止容器 |
| **多终端** | 可以开多个 | 共享同一个会话 |
| **适用场景** | 调试临时操作 | 查看主进程输出 |
| **推荐程度** | 推荐 | 特殊场景使用 |
```mermaid
flowchart LR
subgraph Exec ["docker exec"]
direction TB
subgraph Container1 ["容器"]
E_PID1["PID 1: nginx"]
E_PID50["PID 50: bash"]
end
NewProc["新进程"] -- 附加到 --> E_PID50
end
subgraph Attach ["docker attach"]
direction TB
subgraph Container2 ["容器"]
A_PID1["PID 1: bash"]
end
MainProc["附加到主进程"] --> A_PID1
end
note1["退出 bash 不影响 nginx"]
note2["退出 bash 容器停止"]
Container1 -.-> note1
Container2 -.-> note2
```
---
### 5.4.6 最佳实践
#### 1. 首选 docker exec
```bash
## 进入容器调试
$ docker exec -it myapp bash
## 查看日志
$ docker exec myapp tail -f /var/log/app.log
## 执行数据库迁移
$ docker exec myapp python manage.py migrate
```
#### 2. 生产环境避免进入容器
笔者建议生产环境应尽量避免进入容器直接操作而是通过
- 日志系统查看日志 ( `docker logs` 或集中式日志)
- 监控系统查看状态
- 重新部署而非手动修改
#### 3. shell 镜像的处理
某些精简镜像 (如基于 `scratch` `distroless`) 没有 shell
```bash
## 这会失败
$ docker exec -it myapp bash
OCI runtime exec failed: exec failed: unable to start container process: exec: "bash": executable file not found
## 解决方案使用调试容器Docker Desktop 或 Kubernetes debug
$ docker debug myapp
```
---
### 5.4.7 常见问题
#### Qexec 进入后看不到其他终端的操作
这是正常的exec 启动的是独立进程多个 exec 会话互不影响
#### Q容器没有 bash
尝试使用 sh
```bash
$ docker exec -it myapp /bin/sh
```
#### Q需要 root 权限
```bash
$ docker exec -u root -it myapp bash
```
---

View File

@@ -1,246 +0,0 @@
## 5.6 删除
随着容器的创建和停止系统中会积累大量的容器本节将介绍如何删除不再需要的容器以及如何清理所有停止的容器
### 5.6.1 基本用法
使用 `docker rm` 删除已停止的容器
```bash
$ docker rm 容器名或ID
```
> 💡 `docker rm` `docker container rm` 的简写两者等效
---
### 5.6.2 删除选项
| 选项 | 说明 | 示例 |
|------|------|------|
| 无参数 | 删除已停止的容器 | `docker rm mycontainer` |
| `-f` | 强制删除运行中的容器 | `docker rm -f mycontainer` |
| `-v` | 同时删除关联的匿名卷 | `docker rm -v mycontainer` |
#### 删除已停止的容器
```bash
$ docker rm mycontainer
mycontainer
```
#### 强制删除运行中的容器
```bash
## 不加 -f 会报错
$ docker rm running_container
Error: cannot remove running container
## 加 -f 强制删除
$ docker rm -f running_container
running_container
```
> 强制删除会向容器发送 SIGKILL 信号可能导致数据丢失建议先 `docker stop` 优雅停止
#### 删除容器及其数据卷
```bash
## 删除容器时同时删除其匿名卷
$ docker rm -v mycontainer
```
> 注意只删除匿名卷命名卷不会被删除
---
### 5.6.3 批量删除
#### 删除所有已停止的容器
```bash
## 方式一:使用 prune 命令(推荐)
$ docker container prune
WARNING! This will remove all stopped containers.
Are you sure you want to continue? [y/N] y
Deleted Containers:
abc123...
def456...
Total reclaimed space: 150MB
## 方式二:不提示确认
$ docker container prune -f
```
#### 删除所有容器 (包括运行中的)
```bash
## 先停止所有容器,再删除
$ docker stop $(docker ps -q)
$ docker rm $(docker ps -aq)
## 或者直接强制删除
$ docker rm -f $(docker ps -aq)
```
#### 按条件删除
```bash
## 删除所有已退出的容器
$ docker rm $(docker ps -aq -f status=exited)
## 删除名称包含 "test" 的容器
$ docker rm $(docker ps -aq -f name=test)
## 删除 24 小时前创建的容器
$ docker container prune --filter "until=24h"
```
---
### 5.6.4 常用过滤条件
`docker ps` 的过滤条件可以配合 `rm` 使用
| 过滤条件 | 说明 | 示例 |
|---------|------|------|
| `status=exited` | 已退出的容器 | `-f status=exited` |
| `status=created` | 已创建未启动 | `-f status=created` |
| `name=xxx` | 名称匹配 | `-f name=myapp` |
| `ancestor=xxx` | 基于某镜像创建 | `-f ancestor=nginx` |
| `before=xxx` | 在某容器之前创建 | `-f before=mycontainer` |
| `since=xxx` | 在某容器之后创建 | `-f since=mycontainer` |
#### 示例
```bash
## 删除所有基于 nginx 镜像的容器
$ docker rm $(docker ps -aq -f ancestor=nginx)
## 删除所有创建后未启动的容器
$ docker rm $(docker ps -aq -f status=created)
```
---
### 5.6.5 容器与镜像的依赖关系
> 有容器依赖的镜像无法删除
```bash
## 尝试删除有容器依赖的镜像
$ docker image rm nginx
Error: image is being used by stopped container abc123
## 需要先删除依赖该镜像的容器
$ docker rm abc123
$ docker image rm nginx
```
---
### 5.6.6 清理策略建议
#### 开发环境
```bash
## 定期清理已停止的容器
$ docker container prune -f
## 一键清理所有未使用资源
$ docker system prune -f
```
#### 生产环境
```bash
## 使用 --rm 参数运行临时容器
$ docker run --rm ubuntu echo "Hello"
## 容器退出后自动删除
## 定期清理(设置保留时间)
$ docker container prune --filter "until=168h" # 保留 7 天内的
```
#### 完整清理脚本
```bash
#!/bin/bash
## cleanup.sh - Docker 资源清理脚本
echo "清理已停止的容器..."
docker container prune -f
echo "清理未使用的镜像..."
docker image prune -f
echo "清理未使用的数据卷..."
docker volume prune -f
echo "清理未使用的网络..."
docker network prune -f
echo "清理完成!"
docker system df
```
---
### 5.6.7 常见问题
#### Q容器无法删除
```bash
Error: container is running
```
解决先停止容器或使用 `-f` 强制删除
```bash
$ docker stop mycontainer
$ docker rm mycontainer
## 或
$ docker rm -f mycontainer
```
#### Q删除后磁盘空间没释放
可能原因
1. 容器的数据卷未删除 (使用 `-v` 参数)
2. 镜像未删除
3. 构建缓存未清理
解决
```bash
## 查看空间占用
$ docker system df
## 完整清理
$ docker system prune -a --volumes
```
---

View File

@@ -1,14 +0,0 @@
# 第五章 操作容器
容器是 Docker 又一核心概念
简单的说容器是独立运行的一个或一组应用以及它们的运行态环境对应的虚拟机可以理解为模拟运行的一整套操作系统 (提供了运行态环境和其他系统环境) 和跑在上面的应用
本章将具体介绍如何来管理一个容器包括创建启动和停止等
* [启动容器](5.1_run.md)
* [守护态运行](5.2_daemon.md)
* [终止容器](5.3_stop.md)
* [进入容器](5.4_attach_exec.md)
* [导出和导入容器](5.5_import_export.md)
* [删除容器](5.6_rm.md)

View File

@@ -1,55 +0,0 @@
## 本章小结
| 操作 | 命令 | 说明 |
|------|------|------|
| 新建并运行 | `docker run` | 最常用的启动方式 |
| 交互式启动 | `docker run -it` | 用于调试或临时操作 |
| 后台运行 | `docker run -d` | 用于服务类应用 |
| 启动已停止的容器 | `docker start` | 重用已有容器 |
### 5.7.1 延伸阅读
- [后台运行](5.2_daemon.md)理解 `-d` 参数和容器生命周期
- [进入容器](5.4_attach_exec.md)操作运行中的容器
- [网络配置](../09_network/README.md)理解端口映射的原理
- [数据管理](../08_data/README.md)数据持久化方案
| 操作 | 命令 | 说明 |
|------|------|------|
| 优雅停止 | `docker stop` | SIGTERM超时后 SIGKILL |
| 强制停止 | `docker kill` | 直接 SIGKILL |
| 重新启动 | `docker start` | 启动已停止的容器 |
| 重启 | `docker restart` | 停止后立即启动 |
| 停止全部 | `docker stop $(docker ps -q)` | 停止所有运行中容器 |
### 5.7.2 延伸阅读
- [启动容器](../05_container/5.1_run.md)容器启动详解
- [删除容器](5.6_rm.md)清理容器
- [容器日志](5.2_daemon.md)排查停止原因
| 需求 | 推荐命令 |
|------|---------|
| 进入容器调试 | `docker exec -it 容器名 bash` |
| 执行单条命令 | `docker exec 容器名 命令` |
| 查看主进程输出 | `docker attach 容器名` (慎用)|
### 5.7.3 延伸阅读
- [后台运行](5.2_daemon.md)理解容器主进程
- [查看容器](5.1_run.md)列出和过滤容器
- [容器日志](5.2_daemon.md)查看容器输出
| 操作 | 命令 |
|------|------|
| 删除已停止容器 | `docker rm 容器名` |
| 强制删除运行中容器 | `docker rm -f 容器名` |
| 删除容器及匿名卷 | `docker rm -v 容器名` |
| 清理所有已停止容器 | `docker container prune` |
| 删除所有容器 | `docker rm -f $(docker ps -aq)` |
### 5.7.4 延伸阅读
- [终止容器](5.3_stop.md)优雅停止容器
- [删除镜像](../04_image/4.3_rm.md)清理镜像
- [数据卷](../08_data/8.1_volume.md)数据卷管理

View File

@@ -1,127 +0,0 @@
## 6.1 Docker Hub
### 6.1.1 什么是 Docker Hub
Docker Hub Docker 的中央镜像仓库通过它您可以轻松地分享和获取 Docker 镜像
[Docker Hub](https://hub.docker.com/) 是 Docker 官方维护的公共镜像仓库,也是全球最大的容器镜像库。
它提供了
- **官方镜像** Docker 官方和软件厂商 ( NginxMySQLNode.js) 维护的高质量镜像
- **个人/组织仓库**用户可以上传自己的镜像
- **自动构建** GitHub/Bitbucket 集成 (需付费)
- **Webhooks**镜像更新时触发回调
---
### 6.1.2 核心功能
#### 1. 搜索镜像
我们可以通过 `docker search` 命令来查找官方仓库中的镜像并利用 `docker pull` 命令来将它下载到本地
除了网页搜索也可以使用命令行
```bash
$ docker search centos
NAME DESCRIPTION STARS OFFICIAL
centos The official build of CentOS. 7000+ [OK]
```
> **技巧**始终优先使用 `OFFICIAL` 标记为 `[OK]` 的镜像安全性更有保障
#### 2. 拉取镜像
```bash
$ docker pull nginx:alpine
```
#### 3. 推送镜像
需要先登录
```bash
$ docker login
## 输入用户名和密码
...
```
打标签并推送
```bash
## 1. 标记镜像
$ docker tag myapp:v1 username/myapp:v1
## 2. 推送
$ docker push username/myapp:v1
```
---
### 6.1.3 限制与配额
#### 镜像拉取限制
2020 11 月起Docker Hub 对匿名和免费用户实施了拉取速率限制
| 用户类型 | 限制 |
|---------|------|
| **匿名用户** (未登录) | 6 小时 100 次请求 |
| **免费账户** (已登录) | 6 小时 200 次请求 |
| **Pro/Team 账户** | 无限制 |
> **提示**如果在 CI/CD 环境中遇到 `toomanyrequests` 错误建议
> 1. CI 中配置 `docker login`
> 2. 使用国内镜像加速器
> 3. 搭建私有仓库代理
---
### 6.1.4 安全最佳实践
#### 1. 启用 2FA (双因素认证)
为了保护您的 Docker Hub 账号安全我们建议采取以下措施
Account Settings -> Security 中启用 2FA保护账号安全启用后CLI 登录需要使用 **Access Token** 而非密码
#### 2. 使用 Access Token
不要在脚本或 CI/CD 中直接使用登录密码
1. Docker Hub -> Account Settings -> Security -> Access Tokens 创建 Token
2. 使用 Token 作为密码登录
```bash
$ docker login -u username -p dckr_pat_xxxxxxx
```
#### 3. 关注镜像漏洞
Docker Hub 会对官方镜像和付费用户的镜像进行安全扫描在镜像标签页可以看到漏洞扫描结果
---
### 6.1.5 Webhooks
当镜像被推送时可以自动触发 HTTP 回调 (例如通知 CI 系统部署)
**配置方法**
仓库页面 -> Webhooks -> Create Webhook
---
### 6.1.6 自动构建
> 目前仅限付费用户 (Pro/Team) 使用
链接 GitHub/Bitbucket 仓库后当代码有提交或打标签时Docker Hub 会自动运行构建这保证了镜像总是与代码同步且由可信的官方环境构建
---

View File

@@ -1,7 +0,0 @@
# 第六章 访问仓库
仓库 (`Repository`) 是集中存放镜像的地方
一个容易混淆的概念是注册服务器 (`Registry`)实际上注册服务器是管理仓库的具体服务器每个服务器上可以有多个仓库而每个仓库下面有多个镜像从这方面来说仓库可以被认为是一个具体的项目或目录例如对于仓库地址 `docker.io/ubuntu` 来说`docker.io` 是注册服务器地址`ubuntu` 是仓库名
大部分时候并不需要严格区分这两者的概念

View File

@@ -1,15 +0,0 @@
## 本章小结
| 功能 | 说明 |
|------|------|
| **官方镜像** | 优先使用的基础镜像 |
| **拉取限制** | 匿名 100/6h登录 200/6h |
| **安全** | 推荐开启 2FA 并使用 Access Token |
| **自动化** | 支持 Webhooks 和自动构建 |
### 6.5.1 概述
### 6.5.2 延伸阅读
- [私有仓库](6.2_registry.md)搭建自己的 Registry
- [镜像加速器](../03_install/3.9_mirror.md)加速下载

View File

@@ -1,190 +0,0 @@
## 7.10 WORKDIR 指定工作目录
### 7.10.1 基本语法
```docker
WORKDIR <工作目录路径>
```
`WORKDIR` 指定后续指令的工作目录如果目录不存在Docker 会自动创建
---
### 7.10.2 基本用法
```docker
WORKDIR /app
RUN pwd # 输出 /app
RUN echo "hello" > world.txt # 创建 /app/world.txt
COPY . . # 复制到 /app/
```
---
### 7.10.3 为什么需要 WORKDIR
#### 常见错误
```docker
## ❌ 错误cd 在下一个 RUN 中无效
RUN cd /app
RUN echo "hello" > world.txt # 文件在根目录!
```
#### 原因分析
```dockerfile
RUN cd /app
启动容器 → cd /app仅内存变化→ 提交镜像层 → 容器销毁
↓ 工作目录未改变!
RUN echo "hello" > world.txt
启动新容器(工作目录在 /)→ 创建 /world.txt
```
每个 RUN 都在新容器中执行**前一个 RUN 的内存状态 (包括工作目录) 不会保留**
#### 正确做法
```docker
## ✅ 正确:使用 WORKDIR
WORKDIR /app
RUN echo "hello" > world.txt # 创建 /app/world.txt
```
---
### 7.10.4 相对路径
WORKDIR 支持相对路径基于上一个 WORKDIR
```docker
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd # 输出 /a/b/c
```
---
### 7.10.5 使用环境变量
```docker
ENV APP_HOME=/app
WORKDIR $APP_HOME
RUN pwd # 输出 /app
```
---
### 7.10.6 多阶段构建中的 WORKDIR
```docker
## 构建阶段
FROM node:20 AS builder
WORKDIR /build
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
## 生产阶段
FROM nginx:alpine
WORKDIR /usr/share/nginx/html
COPY --from=builder /build/dist .
```
---
### 7.10.7 最佳实践
#### 1. 尽早设置 WORKDIR
```docker
FROM node:20
WORKDIR /app # 尽早设置
COPY package*.json ./
RUN npm install
COPY . .
CMD ["node", "server.js"]
```
#### 2. 使用绝对路径
```docker
## ✅ 推荐:绝对路径,意图明确
WORKDIR /app
## ⚠️ 避免:相对路径可能造成混淆
WORKDIR app
```
#### 3. 不要用 RUN cd
```docker
## ❌ 避免
RUN cd /app && echo "hello" > world.txt
## ✅ 推荐
WORKDIR /app
RUN echo "hello" > world.txt
```
#### 4. 适时重置 WORKDIR
```docker
WORKDIR /app
## ... 应用相关操作 ...
WORKDIR /data
## ... 数据相关操作 ...
...
```
---
### 7.10.8 与其他指令的关系
| 指令 | WORKDIR 的影响 |
|------|---------------|
| `RUN` | WORKDIR 中执行命令 |
| `CMD` | WORKDIR 中启动 |
| `ENTRYPOINT` | WORKDIR 中启动 |
| `COPY` | 相对目标路径基于 WORKDIR |
| `ADD` | 相对目标路径基于 WORKDIR |
```docker
WORKDIR /app
RUN pwd # /app
COPY . . # 复制到 /app
CMD ["./start.sh"] # /app/start.sh
```
---
### 7.10.9 运行时覆盖
使用 `-w` 参数覆盖工作目录
```bash
$ docker run -w /tmp myimage pwd
/tmp
```
---

View File

@@ -1,279 +0,0 @@
## 7.11 USER 指定当前用户
### 7.11.1 基本语法
```docker
USER <用户名>[:<用户组>]
USER <UID>[:<GID>]
```
`USER` 指令切换后续指令 (RUNCMDENTRYPOINT) 的执行用户
---
### 7.11.2 为什么要使用 USER
> 笔者强调以非 root 用户运行容器是最重要的安全实践之一
```mermaid
flowchart LR
subgraph Root ["root 用户运行的风险:"]
direction TB
R_C["容器内 root"] -- 可能逃逸 --> R_H["宿主机 root"]
R_C -- 漏洞利用 --> R_Control["完全控制宿主机"]
end
subgraph NonRoot ["非 root 用户运行:"]
direction TB
NR_C["容器内普通用户"] -- 逃逸后 --> NR_H["宿主机普通用户"]
NR_C -- 权限受限,危害降低 --> NR_Safe["无法控制系统"]
end
```
---
### 7.11.3 基本用法
#### 创建并切换用户
```docker
FROM node:20-alpine
## 1. 创建用户和组
RUN addgroup -g 1001 appgroup && \
adduser -u 1001 -G appgroup -D appuser
## 2. 设置目录权限
WORKDIR /app
COPY --chown=appuser:appgroup . .
## 3. 切换用户
USER appuser
## 4. 后续命令以 appuser 身份运行
CMD ["node", "server.js"]
```
#### 使用 UID/GID
```docker
## 也可以使用数字
USER 1001:1001
```
---
### 7.11.4 用户必须已存在
`USER` 指令只能切换到 **已存在** 的用户
```docker
## ❌ 错误:用户不存在
USER nonexistent
## Error: unable to find user nonexistent
## ✅ 正确:先创建用户
RUN useradd -r -s /bin/false appuser
USER appuser
```
#### 创建用户的方式
**Debian/Ubuntu**
```docker
RUN groupadd -r appgroup && \
useradd -r -g appgroup appuser
```
**Alpine**
```docker
RUN addgroup -g 1001 -S appgroup && \
adduser -u 1001 -S -G appgroup appuser
```
| 选项 | 说明 |
|------|------|
| `-r` (useradd) / `-S` (adduser) | 创建系统用户 |
| `-g` | 指定主组 |
| `-G` | 指定附加组 |
| `-u` | 指定 UID |
| `-s /bin/false` | 禁用登录 shell |
---
### 7.11.5 运行时切换用户
#### 使用 gosu (推荐)
ENTRYPOINT 脚本中切换用户时不要使用 `su` `sudo`应使用 [gosu](https://github.com/tianon/gosu)
```docker
FROM debian:bookworm
## 创建用户
RUN groupadd -r redis && useradd -r -g redis redis
## 安装 gosu
RUN apt-get update && apt-get install -y gosu && rm -rf /var/lib/apt/lists/*
COPY docker-entrypoint.sh /usr/local/bin/
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["redis-server"]
```
**docker-entrypoint.sh**
```bash
#!/bin/bash
set -e
## 以 root 执行初始化
chown -R redis:redis /data
## 用 gosu 切换到 redis 用户运行服务
exec gosu redis "$@"
```
#### 为什么不用 su/sudo
| 问题 | su/sudo | gosu |
|------|---------|------|
| TTY 要求 | 需要 | 不需要 |
| 信号传递 | 不正确 | 正确 |
| 子进程 | | exec 替换 |
| 容器中使用 | | |
---
### 7.11.6 运行时覆盖用户
使用 `-u` `--user` 参数
```bash
## 以指定用户运行
$ docker run -u 1001:1001 myimage
## 以 root 运行(调试时)
$ docker run -u root myimage
```
---
### 7.11.7 文件权限处理
切换用户后确保应用有权访问文件
```docker
FROM node:20-alpine
## 创建用户
RUN adduser -D -u 1001 appuser
WORKDIR /app
## 方式1使用 --chown
COPY --chown=appuser:appuser . .
## 方式2手动 chown减少层数
## COPY . .
## RUN chown -R appuser:appuser /app
USER appuser
CMD ["node", "server.js"]
```
---
### 7.11.8 最佳实践
#### 1. 始终使用非 root 用户
```docker
## ✅ 推荐
RUN adduser -D appuser
USER appuser
CMD ["myapp"]
## ❌ 避免
CMD ["myapp"] # 以 root 运行
```
#### 2. 使用固定 UID/GID
便于在宿主机和容器间共享文件
```docker
## 使用常见的非 root UID
RUN addgroup -g 1000 -S appgroup && \
adduser -u 1000 -S -G appgroup appuser
USER 1000:1000
```
#### 3. 多阶段构建中的 USER
```docker
## 构建阶段可以用 root
FROM node:20 AS builder
WORKDIR /app
COPY . .
RUN npm install && npm run build
## 生产阶段用非 root
FROM node:20-alpine
RUN adduser -D appuser
WORKDIR /app
COPY --from=builder --chown=appuser:appuser /app/dist .
USER appuser
CMD ["node", "server.js"]
```
---
### 7.11.9 常见问题
#### Q权限被拒绝
```bash
permission denied: '/app/data.log'
```
**解决**确保目录权限正确
```docker
RUN mkdir -p /app/data && chown appuser:appuser /app/data
```
#### Q无法绑定低于 1024 的端口
root 用户无法绑定 80443 等端口
**解决**
1. 使用高端口 ( 8080)
2. 在运行时映射端口`docker run -p 80:8080`
---

View File

@@ -1,196 +0,0 @@
## 7.12 HEALTHCHECK 健康检查
### 7.12.1 基本语法
```docker
HEALTHCHECK [选项] CMD <命令>
HEALTHCHECK NONE
```
`HEALTHCHECK` 指令告诉 Docker 如何判断容器状态是否正常这是保障服务高可用的重要机制
---
### 7.12.2 为什么需要 HEALTHCHECK
在没有 HEALTHCHECK 之前Docker 只能通过 **进程退出码** 来判断容器状态**问题场景**
- Web 服务死锁无法响应请求但进程仍在运行
- 数据库正在启动中尚未准备好接受连接
- 应用陷入死循环CPU 爆满但进程存活
**引入 HEALTHCHECK **
Docker 定期执行指定的检查命令根据返回值判断容器是否 健康
```bash
容器状态转换:
Starting ──成功──> Healthy ──失败N次──> Unhealthy
▲ │
└──────成功──────┘
```
---
### 7.12.3 基本用法
#### Web 服务检查
```docker
FROM nginx
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
CMD curl -fs http://localhost/ || exit 1
```
#### 命令返回值
- `0`成功 (healthy)
- `1`失败 (unhealthy)
- `2`保留值 (不使用)
#### 常用选项
| 选项 | 说明 | 默认值 |
|------|------|--------|
| `--interval` | 两次检查的间隔 | 30s |
| `--timeout` | 检查命令的超时时间 | 30s |
| `--start-period` | 启动缓冲期 (期间失败不计入次数)| 0s |
| `--retries` | 连续失败多少次标记为 unhealthy | 3 |
---
### 7.12.4 屏蔽健康检查
如果基础镜像定义了 HEALTHCHECK但你不想使用它
```docker
FROM my-base-image
HEALTHCHECK NONE
```
---
### 7.12.5 常见检查脚本
#### HTTP 服务
使用 `curl` `wget`
```docker
## 使用 curl
HEALTHCHECK CMD curl -f http://localhost/ || exit 1
## 使用 wgetAlpine 默认包含)
HEALTHCHECK CMD wget -q --spider http://localhost/ || exit 1
```
#### 数据库
```docker
## MySQL
HEALTHCHECK CMD mysqladmin ping -h localhost || exit 1
## Redis
HEALTHCHECK CMD redis-cli ping || exit 1
```
#### 自定义脚本
```docker
COPY healthcheck.sh /usr/local/bin/
HEALTHCHECK CMD ["healthcheck.sh"]
```
---
### 7.12.6 Compose 中使用
可以在 `compose.yaml` ( `docker-compose.yml`) 中覆盖或定义健康检查
```yaml
services:
web:
image: nginx
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost"]
interval: 1m30s
timeout: 10s
retries: 3
start_period: 40s
```
带健康检查的依赖启动
```yaml
services:
web:
depends_on:
db:
condition: service_healthy # 等待 db 变健康才启动 web
db:
image: mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
```
---
### 7.12.7 查看健康状态
```bash
## 查看容器状态(包含健康信息)
$ docker ps
CONTAINER ID STATUS
abc123 Up 1 minute (healthy)
def456 Up 2 minutes (unhealthy)
## 查看详细健康日志
$ docker inspect --format '{{json .State.Health}}' mycontainer | jq
{
"Status": "healthy",
"FailingStreak": 0,
"Log": [
{
"Start": "...",
"End": "...",
"ExitCode": 0,
"Output": "..."
}
]
}
```
---
### 7.12.8 最佳实践
#### 1. 避免副作用
健康检查会被频繁执行不要在检查脚本中进行写操作或消耗大量资源的操作
#### 2. 使用轻量级工具
优先使用镜像中已有的工具 ( `wget`)避免为了健康检查安装庞大的依赖 ( `curl`)
#### 3. 设置合理的 Start Period
应用启动可能需要时间 ( Java 应用)设置 `--start-period` 可以防止在启动阶段因检查失败而误判
```docker
## 给应用 1 分钟启动时间
HEALTHCHECK --start-period=60s CMD curl -f http://localhost/ || exit 1
```
#### 4. 只检查核心依赖
健康检查应主要关注 **当前服务** 是否可用而不是检查其下游依赖 (数据库等)下游依赖的检查应由应用逻辑处理
---

View File

@@ -1,145 +0,0 @@
## 7.13 ONBUILD 为他人作嫁衣裳
### 7.13.1 基本语法
```docker
ONBUILD <其它指令>
```
`ONBUILD` 是一个特殊的指令它后面跟的是其它指令 ( `RUN``COPY` )这些指令 **在当前镜像构建时不会执行**只有当以当前镜像为基础镜像去构建下一级镜像时才会被执行
---
### 7.13.2 为什么需要 ONBUILD
`ONBUILD` 主要用于制作 **语言栈基础镜像** **框架基础镜像**
#### 场景维护 Node.js 项目
假设你有多个 Node.js 项目它们的构建流程都一样
1. 创建目录
2. 复制 `package.json`
3. 执行 `npm install`
4. 复制源码
5. 启动应用
如果不使用 `ONBUILD`每个项目的 Dockerfile 都要重复这些步骤且通过 `COPY` 复制文件时基础镜像无法预知子项目的文件名
#### 使用 ONBUILD 的解决方案
**基础镜像 (my-node-base)**
```docker
FROM node:20-alpine
WORKDIR /app
## 这些指令将在子镜像构建时执行
ONBUILD COPY package*.json ./
ONBUILD RUN npm install
ONBUILD COPY . .
CMD ["npm", "start"]
```
**子项目 Dockerfile**
```docker
FROM my-node-base
## 只需要一行!
## 构建时会自动执行 COPY 和 RUN
...
```
---
### 7.13.3 执行机制
```bash
基础镜像构建:
Dockerfile (含 ONBUILD) ──build──> 基础镜像 (记录了 ONBUILD 触发器)
(指令未执行)
子镜像构建:
FROM 基础镜像 ──build──> 读取基础镜像触发器 ──> 执行触发器指令 ──> 继续执行子 Dockerfile
```
---
### 7.13.4 常见使用场景
#### 1. 自动处理依赖安装
```docker
## Python 基础镜像
ONBUILD COPY requirements.txt ./
ONBUILD RUN pip install -r requirements.txt
```
#### 2. 自动编译代码
```docker
## Go 基础镜像
ONBUILD COPY . .
ONBUILD RUN go build -o app main.go
```
#### 3. 处理静态资源
```docker
## Nginx 静态网站基础镜像
ONBUILD COPY dist/ /usr/share/nginx/html/
```
---
### 7.13.5 注意事项
#### 1. 继承性限制
`ONBUILD` 指令 **只会继承一次**
- 镜像 A ( ONBUILD)
- 镜像 B (FROM A) -> 触发 ONBUILD
- 镜像 C (FROM B) -> **不会** 再次触发 ONBUILD
#### 2. 构建上下文
子镜像构建时`ONBUILD COPY . .` 中的 `.` 指的是 **子项目** 的构建上下文而不是基础镜像的上下文
#### 3. 不允许级联
`ONBUILD ONBUILD` 是非法的你不能写 `ONBUILD ONBUILD COPY ...`
#### 4. 可能会导致构建失败
由于 `ONBUILD` 实际上是在子镜像中执行指令如果子项目的上下文不满足要求 (例如缺少 `package.json`)会导致子镜像构建失败且错误信息可能比较隐晦
---
### 7.13.6 最佳实践
#### 1. 命名规范
建议在镜像标签中添加 `-onbuild` 后缀明确告知使用者该镜像包含触发器
```bash
node:20-onbuild
python:3.12-onbuild
```
#### 2. 避免执行耗时操作
尽量不要在 `ONBUILD` 中执行过于耗时或不确定的操作 (如更新系统软件)这会让子镜像构建变得缓慢且不可控
#### 3. 清理工作
如果 `ONBUILD` 指令产生了临时文件最好在同一个指令链中清理或者提供机制让子镜像清理
---

View File

@@ -1,144 +0,0 @@
## 7.14 LABEL 为镜像添加元数据
### 7.14.1 基本语法
```docker
LABEL <key>=<value> <key>=<value> ...
```
`LABEL` 指令以键值对的形式给镜像添加元数据这些数据不会影响镜像的功能但可以帮助用户理解镜像或被自动化工具使用
---
### 7.14.2 为什么需要 LABEL
1. **版本管理**记录版本号构建时间Git Commit ID
2. **联系信息**维护者邮箱文档地址支持渠道
3. **自动化工具**CI/CD 工具可以读取标签触发操作
4. **许可证信息**声明开源协议
---
### 7.14.3 基本用法
#### 定义单个标签
```docker
LABEL version="1.0"
LABEL description="这是一个 Web 应用服务器"
```
#### 定义多个标签 (推荐)
```docker
LABEL maintainer="user@example.com" \
version="1.2.0" \
description="My App Description" \
org.opencontainers.image.authors="Yeasy"
```
> 💡 包含空格的值需要用引号括起来
---
### 7.14.4 常用标签规范
为了标准和互操作性推荐使用 [OCI Image Format Specification](https://github.com/opencontainers/image-spec/blob/main/annotations.md#pre-defined-annotation-keys) 定义的标准标签:
| 标签 Key | 说明 | 示例 |
|----------|------|------|
| `org.opencontainers.image.created` | 构建时间(RFC 3339) | `2024-01-01T00:00:00Z` |
| `org.opencontainers.image.authors` | 作者/维护者 | `support@example.com` |
| `org.opencontainers.image.url` | 项目主页 | `https://example.com` |
| `org.opencontainers.image.documentation`| 文档地址 | `https://example.com/docs` |
| `org.opencontainers.image.source` | 源码仓库 | `https://github.com/user/repo` |
| `org.opencontainers.image.version` | 版本号 | `1.0.0` |
| `org.opencontainers.image.licenses` | 许可证 | `MIT` |
| `org.opencontainers.image.title` | 镜像标题 | `My App` |
| `org.opencontainers.image.description` | 描述 | `Production ready web server` |
#### 示例
```docker
LABEL org.opencontainers.image.authors="yeasy" \
org.opencontainers.image.documentation="https://yeasy.gitbooks.io" \
org.opencontainers.image.source="https://github.com/yeasy/docker_practice" \
org.opencontainers.image.licenses="MIT"
```
---
### 7.14.5 MAINTAINER 指令 (已废弃)
旧版本的 Dockerfile 中常看到 `MAINTAINER` 指令
```docker
## ❌ 已弃用
MAINTAINER user@example.com
```
现在推荐使用 `LABEL`
```docker
## ✅ 推荐
LABEL maintainer="user@example.com"
## 或
LABEL org.opencontainers.image.authors="user@example.com"
```
---
### 7.14.6 动态标签
配合 `ARG` 使用可以在构建时动态注入标签
```docker
ARG BUILD_DATE
ARG VCS_REF
LABEL org.opencontainers.image.created=$BUILD_DATE \
org.opencontainers.image.revision=$VCS_REF
```
构建命令
```bash
$ docker build \
--build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ') \
--build-arg VCS_REF=$(git rev-parse --short HEAD) \
.
```
---
### 7.14.7 查看标签
#### docker inspect
查看镜像的标签信息
```bash
$ docker inspect nginx --format '{{json .Config.Labels}}' | jq
{
"maintainer": "NGINX Docker Maintainers <docker-maint@nginx.com>"
}
```
#### 过滤器
可以使用标签过滤镜像
```bash
## 列出作者是 yeasy 的所有镜像
$ docker images --filter "label=org.opencontainers.image.authors=yeasy"
## 删除所有带有特定标签的镜像
$ docker rmi $(docker images -q --filter "label=stage=builder")
```
---

View File

@@ -1,138 +0,0 @@
## 7.15 SHELL 指令
### 7.15.1 基本语法
```docker
SHELL ["executable", "parameters"]
```
`SHELL` 指令允许覆盖 Docker 默认的 shell
- **Linux 默认**`["/bin/sh", "-c"]`
- **Windows 默认**`["cmd", "/S", "/C"]`
该指令会影响后续的 `RUN``CMD``ENTRYPOINT` 指令 (当它们使用 shell 格式时)
---
### 7.15.2 为什么要用 SHELL 指令
#### 1. 使用 bash 特性
默认的 `/bin/sh` (通常是 dash alpine ash) 功能有限如果你需要使用 bash 的特有功能 (如数组`{}` 扩展`pipefail` )可以切换 shell
```docker
FROM ubuntu:24.04
## 切换到 bash
SHELL ["/bin/bash", "-c"]
## 现在可以使用 bash 特性了
RUN echo {a..z}
```
#### 2. 增强错误处理
默认情况下管道命令 `cmd1 | cmd2` 只要 `cmd2` 成功整个指令就视为成功这可能掩盖构建错误
```docker
## ❌ 这里的 wget 失败了,但构建继续(因为 tar 成功了)
RUN wget -O - https://invalid-url | tar xz
```
使用 `SHELL` 启用 `pipefail`
```docker
## ✅ 启用 pipefail
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
## 如果 wget 失败,整个 RUN 就会失败
RUN wget -O - https://invalid-url | tar xz
```
#### 3. Windows 环境
Windows 容器中经常需要在 `cmd` `powershell` 之间切换
```docker
FROM mcr.microsoft.com/windows/servercore:ltsc2022
## 默认是 cmd
RUN echo Default shell is cmd
## 切换到 powershell
SHELL ["powershell", "-command"]
RUN Write-Host "Hello from PowerShell"
## 切回 cmd
SHELL ["cmd", "/S", "/C"]
```
---
### 7.15.3 作用范围
`SHELL` 指令可以出现多次每次只影响其后的指令
```docker
FROM ubuntu:24.04
## 使用默认 sh
RUN echo "Using sh"
SHELL ["/bin/bash", "-c"]
## 使用 bash
RUN echo "Using bash"
SHELL ["/bin/sh", "-c"]
## 回到 sh
RUN echo "Using sh again"
```
---
### 7.15.4 对其他指令的影响
`SHELL` 影响的是所有使用 **shell 格式** 的指令
| 指令格式 | 是否受 SHELL 影响 |
|---------|-------------------|
| `RUN command` | |
| `RUN ["exec", "param"]` | |
| `CMD command` | |
| `CMD ["exec", "param"]` | |
| `ENTRYPOINT command` | |
| `ENTRYPOINT ["exec", "param"]` | |
---
### 7.15.5 最佳实践
#### 1. 推荐开启 pipefail
对于使用 bash 的镜像强烈建议开启 `pipefail`以确保构建过程中的错误能被及时捕获
```docker
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
```
#### 2. 明确意图
如果由于脚本需求必须更改 shell最好在 Dockerfile 中显式声明而不是依赖默认行为
#### 3. 尽量保持一致
避免在 Dockerfile 中频繁切换 SHELL这会使构建过程难以理解和调试尽量在头部定义一次即可
---

View File

@@ -1,161 +0,0 @@
## 7.1 RUN 执行命令
### 7.1.1 基本语法
```docker
RUN <command>
RUN ["executable", "param1", "param2"]
```
`RUN` 指令是 Dockerfile 中最常用的指令之一它在 **当前镜像层** 之上创建一个新层执行指定的命令并提交结果
---
### 7.1.2 两种格式对比
#### 1. Shell 格式
```docker
RUN apt-get update
```
- **特点**默认通过 `/bin/sh -c` 执行
- **优势**可以使用环境变量管道重定向等 Shell 特性
- **示例**
```docker
RUN echo "Hello" > /test.txt
```
#### 2. Exec 格式
```docker
RUN ["apt-get", "update"]
```
- **特点**直接调用可执行文件不经过 Shell
- **优势**避免 Shell 字符串解析问题适用于参数中包含特殊字符的情况
- **注意**无法使用 `$VAR` 环境变量替换 (除非显式调用 shell)
---
### 7.1.3 常见最佳实践
#### 1. 组合命令 (减少层数)
每一个 `RUN` 指令都会新建一层镜像为了减少镜像体积和层数应使用 `&&` 连接命令
** 糟糕的写法** (创建 3 )
```docker
RUN apt-get update
RUN apt-get install -y nginx
RUN rm -rf /var/lib/apt/lists/*
```
** 推荐写法** (创建 1 )
```docker
RUN apt-get update && \
apt-get install -y nginx && \
rm -rf /var/lib/apt/lists/*
```
#### 2. 清理缓存
在安装完软件后立即清除缓存可以显著减小镜像体积
- **Debian/Ubuntu**:
```docker
RUN apt-get update && apt-get install -y package-bar \
&& rm -rf /var/lib/apt/lists/*
```
- **Alpine**:
```docker
RUN apk add --no-cache package-bar
```
#### 3. 使用 `set -e` `pipefail`
默认情况下管道命令 `cmd1 | cmd2` 只要 `cmd2` 成功整个 `RUN` 就视为成功
** 隐蔽的错误**
```docker
## 如果下载失败gzip 可能会报错,但如果不影响后续,构建可能继续
RUN wget http://error-url | gzip -d > file
```
** 推荐写法**
```docker
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
RUN wget http://url | gzip -d > file
```
---
### 7.1.4 常见问题
#### Q为什么 `RUN cd /app` 不生效
```docker
RUN cd /app
RUN touch hello.txt
```
**结果**`hello.txt` 会出现在根目录 `/`而不是 `/app`**原因**每个 `RUN` 都在一个新的 Shell/容器环境中执行`cd` 只影响当前 `RUN` 的环境**解决**使用 `WORKDIR` 指令
```docker
WORKDIR /app
RUN touch hello.txt
```
#### Q环境变量不生效
```docker
RUN export MY_VAR=hello
RUN echo $MY_VAR
```
**结果**输出为空**原因**同上环境变量只在当前 `RUN` 有效**解决**使用 `ENV` 指令或在同一行 `RUN` 中导出
```docker
ENV MY_VAR=hello
RUN echo $MY_VAR
```
---
### 7.1.5 高级技巧
#### 1. 使用 BuildKit 的挂载缓存
BuildKit 支持在 `RUN` 指令中使用 `--mount` 挂载缓存加速构建
```docker
## 缓存 apt 包
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
apt-get update && apt-get install -y gcc
```
```docker
## 缓存 Go 模块
RUN --mount=type=cache,target=/go/pkg/mod \
go build -o app
```
#### 2. 挂载密钥
安全地使用 SSH 密钥或 Token而不将其记录在镜像中
```docker
RUN --mount=type=secret,id=mysecret \
cat /run/secrets/mysecret
```
---

View File

@@ -1,270 +0,0 @@
## 7.2 COPY 复制文件
### 7.2.1 基本语法
```docker
COPY [选项] <源路径>... <目标路径>
COPY [选项] ["<源路径1>", "<源路径2>", ... "<目标路径>"]
```
`COPY` 指令将构建上下文中的文件或目录复制到镜像内
---
### 7.2.2 基本用法
#### 复制单个文件
```docker
## 复制文件到指定目录
COPY package.json /app/
## 复制文件并重命名
COPY config.json /app/settings.json
```
#### 复制多个文件
```docker
## 复制多个指定文件
COPY package.json package-lock.json /app/
## 使用通配符
COPY *.json /app/
COPY src/*.js /app/src/
```
#### 复制目录
```docker
## 复制整个目录的内容(不是目录本身)
COPY src/ /app/src/
```
> **注意**复制目录时复制的是目录的 **内容**不包含目录本身
```bash
构建上下文: 镜像内:
src/ /app/src/
├── index.js → ├── index.js
└── utils.js └── utils.js
```
---
### 7.2.3 通配符规则
COPY 支持 Go `filepath.Match` 通配符规则
| 通配符 | 说明 | 示例 |
|--------|------|------|
| `*` | 匹配任意字符序列 | `*.json` |
| `?` | 匹配单个字符 | `config?.json` |
| `[abc]` | 匹配括号内任一字符 | `[abc].txt` |
| `[a-z]` | 匹配范围内字符 | `file[0-9].txt` |
```docker
COPY hom* /mydir/ # home.txt, homework.md 等
COPY hom?.txt /mydir/ # home.txt, homy.txt 等
COPY app[0-9].js /app/ # app0.js ~ app9.js
```
---
### 7.2.4 目标路径
#### 绝对路径
```docker
COPY app.js /usr/src/app/
```
#### 相对路径 (基于 WORKDIR)
```docker
WORKDIR /app
COPY package.json ./ # 复制到 /app/package.json
COPY src/ ./src/ # 复制到 /app/src/
```
#### 自动创建目录
如果目标目录不存在Docker 会自动创建
```docker
## /app/config/ 不存在也会自动创建
COPY settings.json /app/config/
```
---
### 7.2.5 修改文件所有者
使用 `--chown` 选项设置文件的用户和组
```docker
## 使用用户名和组名
COPY --chown=node:node package.json /app/
## 使用 UID 和 GID
COPY --chown=1000:1000 . /app/
## 只指定用户
COPY --chown=node . /app/
```
> 💡 结合 `USER` 指令使用确保应用以非 root 用户运行
---
### 7.2.6 保留文件元数据
COPY 会保留源文件的元数据
- 执行权限
- 修改时间
这对于脚本文件特别重要
```docker
## start.sh 的可执行权限会被保留
COPY start.sh /app/
```
---
### 7.2.7 COPY vs ADD
| 特性 | COPY | ADD |
|------|------|-----|
| 复制本地文件 | | |
| 自动解压 tar | | |
| 支持 URL | | (不推荐)|
| 推荐程度 | **推荐** | 特殊场景使用 |
```docker
## 推荐:使用 COPY
COPY app.tar.gz /app/
RUN tar -xzf /app/app.tar.gz
## ADD 会自动解压(行为不明显,不推荐)
ADD app.tar.gz /app/
```
> 笔者建议除非需要自动解压 tar 文件否则始终使用 COPY明确的行为比隐式的魔法更好
---
### 7.2.8 多阶段构建中的 COPY
#### 从其他构建阶段复制
```docker
## 构建阶段
FROM node:20 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
## 生产阶段
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
```
#### 使用 --link 优化缓存
```docker
## 使用 --link 后,文件以独立层添加,不依赖前序指令
COPY --link --from=builder /app/dist /usr/share/nginx/html
```
`--link` 的优势
- 更高效利用构建缓存
- 并行化构建过程
- 加速多阶段构建
---
### 7.2.9 dockerignore
使用 `.dockerignore` 排除不需要复制的文件
```gitignore
## .dockerignore
node_modules
.git
.env
*.log
Dockerfile
.dockerignore
```
这可以
- 减小构建上下文大小
- 加速构建
- 避免复制敏感文件
---
### 7.2.10 最佳实践
#### 1. 利用缓存先复制依赖文件
```docker
## ✅ 好:先复制依赖定义,再安装,最后复制代码
COPY package.json package-lock.json ./
RUN npm install
COPY . .
## ❌ 差:一次性复制所有文件,代码变更会导致重新 npm install
COPY . .
RUN npm install
```
#### 2. 使用 .dockerignore
```docker
## 确保 node_modules 不被复制
COPY . .
## .dockerignore 中应包含 node_modules
...
```
#### 3. 明确复制路径
```docker
## ✅ 好:明确的路径
COPY src/ /app/src/
COPY package.json /app/
## ❌ 差:过于宽泛
COPY . .
```
---

View File

@@ -1,225 +0,0 @@
## 7.3 ADD 更高级的复制文件
### 7.3.1 基本语法
```docker
ADD [选项] <源路径>... <目标路径>
ADD [选项] ["<源路径>", ... "<目标路径>"]
```
`ADD` `COPY` 基础上增加了两个功能
1. 自动解压 tar 压缩包
2. 支持从 URL 下载文件 (不推荐)
---
### 7.3.2 ADD vs COPY
| 特性 | COPY | ADD |
|------|------|-----|
| 复制本地文件 | | |
| 自动解压 tar | | |
| 支持 URL | | (不推荐)|
| 行为可预测性 | | |
| 推荐程度 | **优先使用** | 仅解压场景 |
> 笔者建议除非需要自动解压 tar 文件否则始终使用 COPY明确的行为比隐式的魔法更好
---
### 7.3.3 自动解压功能
#### 基本用法 (自动解压本地 tar)
```docker
## 自动解压 tar.gz 到目标目录
ADD app.tar.gz /app/
```
ADD 会识别并解压以下格式
- `.tar`
- `.tar.gz` / `.tgz`
- `.tar.bz2` / `.tbz2`
- `.tar.xz` / `.txz`
#### 实际应用
官方基础镜像通常使用 ADD 解压根文件系统
```docker
FROM scratch
ADD ubuntu-noble-core-cloudimg-amd64-root.tar.gz /
```
#### 解压过程
```bash
ADD app.tar.gz /app/
├─ 识别 .tar.gz 格式
├─ 自动解压
└─ 内容放入 /app/
app.tar.gz 包含: /app/ 目录结果:
├── src/ ├── src/
│ └── main.py │ └── main.py
└── config.json └── config.json
```
---
### 7.3.4 URL 下载功能 (不推荐)
#### 基本用法
```docker
## 从 URL 下载文件
ADD https://example.com/app.zip /app/app.zip
```
#### 为什么不推荐
| 问题 | 说明 |
|------|------|
| 权限固定 | 下载的文件权限为 600通常需要额外 RUN 修改 |
| 不会解压 | URL 下载的压缩包不会自动解压 |
| 缓存问题 | URL 内容变化时不会重新下载 |
| 层数增加 | 需要额外 RUN 清理 |
#### 推荐替代方案
```docker
## ❌ 不推荐:使用 ADD 下载
ADD https://example.com/app.tar.gz /tmp/
RUN tar -xzf /tmp/app.tar.gz -C /app && rm /tmp/app.tar.gz
## ✅ 推荐:使用 RUN + curl
RUN curl -fsSL https://example.com/app.tar.gz | tar -xz -C /app
```
优势
- 一条 RUN 完成下载解压清理
- 减少镜像层数
- 更清晰的构建意图
---
### 7.3.5 修改文件所有者
```docker
ADD --chown=node:node app.tar.gz /app/
ADD --chown=1000:1000 files/ /app/
```
---
### 7.3.6 何时使用 ADD
#### 适合使用 ADD
```docker
## 解压本地 tar 文件
FROM scratch
ADD rootfs.tar.gz /
## 解压应用包
ADD dist.tar.gz /app/
```
#### 不适合使用 ADD
```docker
## 复制普通文件(用 COPY
ADD package.json /app/ # ❌
COPY package.json /app/ # ✅
## 下载文件(用 RUN + curl
ADD https://example.com/file / # ❌
RUN curl -fsSL ... -o /file # ✅
## 需要保留 tar 不解压(用 COPY
ADD archive.tar.gz /archives/ # ❌ 会解压
COPY archive.tar.gz /archives/ # ✅ 保持原样
```
---
### 7.3.7 缓存行为
ADD 可能导致构建缓存失效
```docker
## 如果 app.tar.gz 内容变化,此层及后续层都需重建
ADD app.tar.gz /app/
RUN npm install
```
**优化建议**
```docker
## 先复制依赖文件
COPY package*.json /app/
RUN npm install
## 再添加应用代码
ADD app.tar.gz /app/
```
---
### 7.3.8 最佳实践
#### 1. 默认使用 COPY
```docker
## ✅ 大多数场景使用 COPY
COPY . /app/
```
#### 2. 仅在需要解压时使用 ADD
```docker
## ✅ 自动解压场景
ADD app.tar.gz /app/
```
#### 3. 不要用 ADD 下载文件
```docker
## ❌ 避免
ADD https://example.com/file.tar.gz /tmp/
## ✅ 推荐
RUN curl -fsSL https://example.com/file.tar.gz | tar -xz -C /app
```
#### 4. 解压后清理
```docker
## 如果需要控制解压过程
COPY app.tar.gz /tmp/
RUN tar -xzf /tmp/app.tar.gz -C /app && \
rm /tmp/app.tar.gz
```
---

View File

@@ -1,278 +0,0 @@
## 7.4 CMD 容器启动命令
### 7.4.1 什么是 CMD
`CMD` 指令用于指定容器启动时默认执行的命令它定义了容器的 主进程
> **核心概念**容器的生命周期 = 主进程的生命周期CMD 指定的命令就是这个主进程
---
### 7.4.2 语法格式
CMD 有三种格式
| 格式 | 语法 | 推荐程度 |
|------|------|---------|
| **exec 格式**| `CMD [“可执行文件”, “参数1”, “参数2”]` | **推荐** |
| **shell 格式** | `CMD 命令 参数1 参数2` | 简单场景 |
| **参数格式** | `CMD [“参数1”, “参数2”]` | 配合 ENTRYPOINT |
#### exec 格式 (推荐)
```docker
CMD ["nginx", "-g", "daemon off;"]
CMD ["python", "app.py"]
CMD ["node", "server.js"]
```
**优点**
- 直接执行指定程序是容器的 PID 1
- 正确接收信号 ( SIGTERM)
- 无需 shell 解析
#### shell 格式
```docker
CMD echo "Hello World"
CMD nginx -g "daemon off;"
```
**实际执行**会被包装为 `sh -c`
```docker
## 你写的
CMD echo $HOME
## 实际执行的
CMD ["sh", "-c", "echo $HOME"]
```
**优点**可以使用环境变量管道等 shell 特性 **缺点**主进程是 sh信号无法正确传递给应用
---
### 7.4.3 exec 格式 vs shell 格式
| 特性 | exec 格式 | shell 格式 |
|------|----------|-----------|
| 主进程 | 指定的程序 | `/bin/sh` |
| 信号传递 | 正确 | 无法传递 |
| 环境变量 | 需要 shell 包装 | 自动解析 |
| 推荐使用 | 大多数场景 | 需要 shell 特性时 |
#### 信号传递问题示例
```docker
## ❌ shell 格式docker stop 会超时
CMD node server.js
## 实际是 sh -c "node server.js"
## SIGTERM 发给 sh不会传递给 node
## ✅ exec 格式docker stop 正常工作
CMD ["node", "server.js"]
## SIGTERM 直接发给 node
...
```
---
### 7.4.4 运行时覆盖 CMD
`docker run` 后的命令会覆盖 Dockerfile 中的 CMD
```bash
## ubuntu 默认 CMD 是 /bin/bash
$ docker run -it ubuntu # 进入 bash
$ docker run ubuntu cat /etc/os-release # 覆盖为 cat 命令
```
```bash
Dockerfile: docker run 命令:
CMD ["/bin/bash"] + cat /etc/os-release
│ │
└───────► 被覆盖 ◄───────┘
执行: cat /etc/os-release
```
---
### 7.4.5 经典错误容器立即退出
#### 错误示例
```docker
## ❌ 容器启动后立即退出
CMD service nginx start
```
#### 原因分析
```bash
1. CMD service nginx start
↓ 被转换为
2. CMD ["sh", "-c", "service nginx start"]
3. sh 启动,执行 service 命令
4. service 命令将 nginx 放到后台
5. service 命令结束sh 退出
6. 容器主进程sh退出 → 容器停止
```
#### 正确做法
```docker
## ✅ 让 nginx 在前台运行
CMD ["nginx", "-g", "daemon off;"]
```
---
### 7.4.6 CMD vs ENTRYPOINT
| 指令 | 用途 | 运行时行为 |
|------|------|-----------|
| **CMD**| 默认命令 | `docker run` 参数会 **覆盖** |
| **ENTRYPOINT**| 入口点 | `docker run` 参数会 **追加** 到它后面 |
#### 单独使用 CMD
```docker
## Dockerfile
CMD ["curl", "-s", "http://example.com"]
```
```bash
$ docker run myimage # 执行默认命令
$ docker run myimage curl -v ... # 完全覆盖
```
#### 搭配 ENTRYPOINT
```docker
## Dockerfile
ENTRYPOINT ["curl", "-s"]
CMD ["http://example.com"]
```
```bash
$ docker run myimage # curl -s http://example.com
$ docker run myimage http://other.com # curl -s http://other.com参数覆盖
```
详见 [ENTRYPOINT 入口点](7.5_entrypoint.md)章节
---
### 7.4.7 最佳实践
#### 1. 优先使用 exec 格式
```docker
## ✅ 推荐
CMD ["python", "app.py"]
## ⚠️ 仅在需要 shell 特性时使用
CMD ["sh", "-c", "echo $PATH && python app.py"]
```
#### 2. 确保应用在前台运行
```docker
## ✅ 前台运行
CMD ["nginx", "-g", "daemon off;"]
CMD ["apache2ctl", "-D", "FOREGROUND"]
CMD ["java", "-jar", "app.jar"]
## ❌ 不要使用后台服务命令
CMD service nginx start
CMD systemctl start nginx
```
#### 3. 使用双引号
```docker
## ✅ 正确:双引号
CMD ["node", "server.js"]
## ❌ 错误单引号JSON 不支持)
CMD ['node', 'server.js']
```
#### 4. 配合 ENTRYPOINT 使用
```docker
## 用于可配置参数的场景
ENTRYPOINT ["python", "app.py"]
CMD ["--port", "8080"]
## 运行时可以覆盖端口
$ docker run myapp --port 9000
...
```
---
### 7.4.8 常见问题
#### QCMD 可以写多个吗
不可以多个 CMD 只有最后一个生效
```docker
CMD ["echo", "first"]
CMD ["echo", "second"] # 只有这个生效
```
#### Q如何在 CMD 中使用环境变量
```docker
## 方法1使用 shell 格式
CMD echo "Port is $PORT"
## 方法2显式使用 sh -c
CMD ["sh", "-c", "echo Port is $PORT"]
```
#### Q为什么我的容器不响应 Ctrl+C
可能是使用了 shell 格式信号被 sh 吃掉了
```docker
## ❌ 信号无法传递
CMD python app.py
## ✅ 信号正确传递
CMD ["python", "app.py"]
```
---

View File

@@ -1,315 +0,0 @@
## 7.5 ENTRYPOINT 入口点
### 7.5.1 什么是 ENTRYPOINT
`ENTRYPOINT` 指定容器启动时运行的入口程序 CMD 不同ENTRYPOINT 定义的命令不会被 `docker run` 的参数覆盖而是 **接收这些参数**
> **核心作用**让镜像像一个可执行程序一样使用`docker run` 的参数作为这个程序的参数
---
### 7.5.2 语法格式
| 格式 | 语法 | 推荐程度 |
|------|------|---------|
| **exec 格式**| `ENTRYPOINT [“可执行文件”, “参数1”]` | **推荐** |
| **shell 格式** | `ENTRYPOINT 命令 参数` | 不推荐 |
```docker
## exec 格式(推荐)
ENTRYPOINT ["nginx", "-g", "daemon off;"]
## shell 格式(不推荐)
ENTRYPOINT nginx -g "daemon off;"
```
---
### 7.5.3 ENTRYPOINT vs CMD
#### 核心区别
| 特性 | ENTRYPOINT | CMD |
|------|------------|-----|
| **定位** | 固定的入口程序 | 默认参数 |
| **docker run 参数** | 追加为参数 | 完全覆盖 |
| **覆盖方式** | `--entrypoint` | 直接指定命令 |
| **适用场景** | 把镜像当命令用 | 提供默认行为 |
#### 行为对比
```docker
## 只用 CMD
CMD ["curl", "-s", "http://example.com"]
```
```bash
$ docker run myimage # curl -s http://example.com
$ docker run myimage -v # 执行 -v错误
$ docker run myimage curl -v ... # curl -v ...(完全替换)
```
```docker
## 只用 ENTRYPOINT
ENTRYPOINT ["curl", "-s"]
```
```bash
$ docker run myimage # curl -s缺参数
$ docker run myimage http://example.com # curl -s http://example.com ✓
```
```docker
## ENTRYPOINT + CMD 组合(推荐)
ENTRYPOINT ["curl", "-s"]
CMD ["http://example.com"]
```
```bash
$ docker run myimage # curl -s http://example.com默认
$ docker run myimage http://other.com # curl -s http://other.com ✓
$ docker run myimage -v http://other.com # curl -s -v http://other.com ✓
```
---
### 7.5.4 场景一让镜像像命令一样使用
#### 需求 (启动前准备)
创建一个查询公网 IP 命令 镜像
#### 使用 CMD 的问题
```docker
FROM ubuntu:24.04
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
CMD ["curl", "-s", "http://myip.ipip.net"]
```
```bash
$ docker run myip # ✓ 正常工作
当前 IP61.148.226.66
$ docker run myip -i # ✗ 错误!
exec: "-i": executable file not found
## -i 替换了整个 CMD被当作可执行文件
...
```
#### 使用 ENTRYPOINT 解决
```docker
FROM ubuntu:24.04
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
ENTRYPOINT ["curl", "-s", "http://myip.ipip.net"]
```
```bash
$ docker run myip # ✓ 正常工作
当前 IP61.148.226.66
$ docker run myip -i # ✓ 添加 -i 参数
HTTP/1.1 200 OK
...
当前 IP61.148.226.66
```
#### 交互图示
```bash
ENTRYPOINT ["curl", "-s", "http://myip.ipip.net"]
docker run myip -i
curl -s http://myip.ipip.net -i
└─────────────────────────────┘
ENTRYPOINT + docker run 参数
```
---
### 7.5.5 场景二启动前的准备工作
#### 需求
在启动主服务前执行初始化脚本 (如数据库迁移权限设置)
#### 实现方式
```docker
FROM redis:7-alpine
COPY docker-entrypoint.sh /usr/local/bin/
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["redis-server"]
```
**docker-entrypoint.sh**
```bash
#!/bin/sh
set -e
## 准备工作
echo "Initializing..."
## 如果第一个参数是 redis-server以 redis 用户运行
if [ "$1" = 'redis-server' ]; then
chown -R redis:redis /data
exec gosu redis "$@"
fi
## 其他命令直接执行
exec "$@"
```
#### 工作流程
```bash
docker run redis docker run redis bash
│ │
▼ ▼
docker-entrypoint.sh redis-server docker-entrypoint.sh bash
│ │
├─ 初始化 ├─ 初始化
├─ chown -R redis:redis /data │
└─ exec gosu redis redis-server └─ exec bash
(以 redis 用户运行) (以 root 用户运行)
```
#### 关键点
1. **exec $@**用传入的参数替换当前进程确保信号正确传递
2. **条件判断**根据 CMD 不同执行不同逻辑
3. **用户切换**使用 `gosu` 切换用户 ( `su` 更适合容器)
---
### 7.5.6 场景三带参数的应用
```docker
FROM python:3.12-slim
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
ENTRYPOINT ["python", "app.py"]
CMD ["--host", "0.0.0.0", "--port", "8080"]
```
```bash
## 使用默认参数
$ docker run myapp
## 执行: python app.py --host 0.0.0.0 --port 8080
## 覆盖参数
$ docker run myapp --host 0.0.0.0 --port 9000
## 执行: python app.py --host 0.0.0.0 --port 9000
## 完全不同的参数
$ docker run myapp --help
## 执行: python app.py --help
...
```
---
### 7.5.7 覆盖 ENTRYPOINT
使用 `--entrypoint` 参数覆盖
```bash
## 正常运行
$ docker run myimage
## 覆盖 ENTRYPOINT 进入 shell 调试
$ docker run --entrypoint /bin/sh myimage
## 覆盖 ENTRYPOINT 并传入参数
$ docker run --entrypoint /bin/cat myimage /etc/os-release
```
---
### 7.5.8 ENTRYPOINT CMD 组合表
| ENTRYPOINT | CMD | 最终执行命令 |
|------------|-----|-------------|
| | | (容器无法启动)|
| | `["cmd", "p1"]` | `cmd p1` |
| `["ep", "p1"]` | | `ep p1` |
| `["ep", "p1"]` | `["cmd", "p2"]` | `ep p1 cmd p2` |
| `ep p1` (shell)| `["cmd", "p2"]` | `/bin/sh -c "ep p1"` (CMD 被忽略)|
> **注意**shell 格式的 ENTRYPOINT 会忽略 CMD
---
### 7.5.9 最佳实践
#### 1. 使用 exec 格式
```docker
## ✅ 推荐
ENTRYPOINT ["python", "app.py"]
## ❌ 避免 shell 格式
ENTRYPOINT python app.py
```
#### 2. 提供有意义的默认参数
```docker
ENTRYPOINT ["nginx"]
CMD ["-g", "daemon off;"]
```
#### 3. 入口脚本使用 exec
```bash
#!/bin/sh
## 准备工作...
## 使用 exec 替换当前进程
exec "$@"
```
#### 4. 处理信号
确保 ENTRYPOINT 脚本能正确传递信号
```bash
#!/bin/bash
trap 'kill -TERM $PID' TERM INT
## 启动应用
app "$@" &
PID=$!
## 等待应用退出
wait $PID
```
---

View File

@@ -1,257 +0,0 @@
## 7.6 ENV 设置环境变量
### 7.6.1 基本语法
```docker
## 格式一:单个变量
ENV <key> <value>
## 格式二:多个变量(推荐)
ENV <key1>=<value1> <key2>=<value2> ...
```
---
### 7.6.2 基本用法
#### 设置单个变量
```docker
ENV NODE_VERSION 20.10.0
ENV APP_ENV production
```
#### 设置多个变量
```docker
ENV NODE_VERSION=20.10.0 \
APP_ENV=production \
APP_NAME="My Application"
```
> 💡 包含空格的值用双引号括起来
---
### 7.6.3 环境变量的作用
#### 1. 后续指令中使用
```docker
ENV NODE_VERSION=20.10.0
## 在 RUN 中使用
RUN curl -fsSL https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.xz \
| tar -xJ -C /usr/local --strip-components=1
## 在 WORKDIR 中使用
ENV APP_HOME=/app
WORKDIR $APP_HOME
## 在 COPY 中使用
COPY . $APP_HOME
```
#### 2. 容器运行时使用
```docker
ENV DATABASE_URL=postgres://localhost/mydb
```
应用代码中可以读取
```python
import os
db_url = os.environ.get('DATABASE_URL')
```
```javascript
const dbUrl = process.env.DATABASE_URL;
```
---
### 7.6.4 支持环境变量的指令
以下指令可以使用 `$变量名` `${变量名}` 格式
| 指令 | 示例 |
|------|------|
| `RUN` | `RUN echo $VERSION` |
| `CMD` | `CMD ["sh", "-c", "echo $HOME"]` |
| `ENTRYPOINT` | 同上 |
| `COPY` | `COPY . $APP_HOME` |
| `ADD` | `ADD app.tar.gz $APP_HOME` |
| `WORKDIR` | `WORKDIR $APP_HOME` |
| `EXPOSE` | `EXPOSE $PORT` |
| `VOLUME` | `VOLUME $DATA_DIR` |
| `USER` | `USER $USERNAME` |
| `LABEL` | `LABEL version=$VERSION` |
| `FROM` | `FROM node:$NODE_VERSION` |
---
### 7.6.5 运行时覆盖
使用 `-e` `--env` 覆盖 Dockerfile 中定义的环境变量
```bash
## 覆盖单个变量
$ docker run -e APP_ENV=development myimage
## 覆盖多个变量
$ docker run -e APP_ENV=development -e DEBUG=true myimage
## 从环境变量文件读取
$ docker run --env-file .env myimage
```
#### env 文件格式
```bash
## .env
APP_ENV=development
DEBUG=true
DATABASE_URL=postgres://localhost/mydb
```
---
### 7.6.6 ENV vs ARG
| 特性 | ENV | ARG |
|------|-----|-----|
| **生效时间** | 构建时 + 运行时 | 仅构建时 |
| **持久性** | 写入镜像运行时可用 | 构建后消失 |
| **覆盖方式** | `docker run -e` | `docker build --build-arg` |
| **适用场景** | 应用配置 | 构建参数 (如版本号)|
#### 组合使用
```docker
## ARG 接收构建时参数
ARG NODE_VERSION=20
## ENV 保存到运行时
ENV NODE_VERSION=$NODE_VERSION
## 后续指令使用
RUN curl -fsSL https://nodejs.org/dist/v${NODE_VERSION}/...
```
```bash
## 构建时指定版本
$ docker build --build-arg NODE_VERSION=18 -t myapp .
```
---
### 7.6.7 最佳实践
#### 1. 统一管理版本号
```docker
## ✅ 好:版本集中管理
ENV NGINX_VERSION=1.25.0 \
NODE_VERSION=20.10.0 \
PYTHON_VERSION=3.12.0
RUN apt-get install nginx=${NGINX_VERSION}
## ❌ 差:版本分散在各处
RUN apt-get install nginx=1.25.0
```
#### 2. 不要存储敏感信息
```docker
## ❌ 错误:密码写入镜像
ENV DB_PASSWORD=secret123
## ✅ 正确:运行时传入
## docker run -e DB_PASSWORD=xxx myimage
...
```
#### 3. 为应用提供合理默认值
```docker
ENV APP_ENV=production \
APP_PORT=8080 \
LOG_LEVEL=info
```
#### 4. 使用有意义的变量名
```docker
## ✅ 好:清晰的命名
ENV REDIS_HOST=localhost \
REDIS_PORT=6379
## ❌ 差:模糊的命名
ENV HOST=localhost \
PORT=6379
```
---
### 7.6.8 常见问题
#### Q环境变量在 CMD 中不展开
exec 格式不会自动展开环境变量
```docker
## ❌ 不会展开 $PORT
CMD ["python", "app.py", "--port", "$PORT"]
## ✅ 使用 shell 格式或显式调用 sh
CMD ["sh", "-c", "python app.py --port $PORT"]
```
#### Q如何查看容器的环境变量
```bash
$ docker inspect mycontainer --format '{{json .Config.Env}}'
$ docker exec mycontainer env
```
#### Q多行 ENV 还是多个 ENV
```docker
## ✅ 推荐:减少层数
ENV VAR1=value1 \
VAR2=value2 \
VAR3=value3
## ⚠️ 多个 ENV 会创建多层
ENV VAR1=value1
ENV VAR2=value2
ENV VAR3=value3
```
---

View File

@@ -1,242 +0,0 @@
## 7.7 ARG 构建参数
### 7.7.1 基本语法
```docker
ARG <参数名>[=<默认值>]
```
`ARG` 指令定义构建时的变量可以在 `docker build` 时通过 `--build-arg` 传入
---
### 7.7.2 ARG vs ENV
| 特性 | ARG | ENV |
|------|-----|-----|
| **生效时间** | 仅构建时 | 构建时 + 运行时 |
| **持久性** | 构建后消失 | 写入镜像 |
| **覆盖方式** | `docker build --build-arg` | `docker run -e` |
| **适用场景** | 构建参数 (版本号等)| 应用配置 |
| **可见性** | `docker history` 可见 | `docker inspect` 可见 |
```dockerfile
构建时 运行时
├─ ARG VERSION=1.0 │ ARG 已消失)
├─ ENV APP_ENV=prod │ APP_ENV=prod仍存在
└─ RUN echo $VERSION │
```
> **安全提示**不要用 ARG 传递密码等敏感信息`docker history` 可以查看所有 ARG
---
### 7.7.3 基本用法
#### 定义和使用
```docker
## 定义有默认值的 ARG
ARG NODE_VERSION=20
## 使用 ARG
FROM node:${NODE_VERSION}-alpine
RUN echo "Using Node.js $NODE_VERSION"
```
#### 构建时覆盖
```bash
## 使用默认值
$ docker build -t myapp .
## 覆盖默认值
$ docker build --build-arg NODE_VERSION=18 -t myapp .
```
---
### 7.7.4 ARG 的作用域
#### FROM 之前的 ARG
```docker
## FROM 之前的 ARG 只能用于 FROM 指令
ARG REGISTRY=docker.io
ARG IMAGE_NAME=node
FROM ${REGISTRY}/${IMAGE_NAME}:20
## ❌ 这里无法使用上面的 ARG
RUN echo $REGISTRY # 输出空
```
#### FROM 之后重新声明
```docker
ARG NODE_VERSION=20
FROM node:${NODE_VERSION}-alpine
## 需要再次声明才能使用
ARG NODE_VERSION
RUN echo "Node version: $NODE_VERSION"
```
#### 多阶段构建中的 ARG
```docker
ARG BASE_VERSION=alpine
FROM node:20-${BASE_VERSION} AS builder
## 需要重新声明
ARG NODE_VERSION=20
RUN echo "Building with Node $NODE_VERSION"
FROM node:20-${BASE_VERSION}
## 每个阶段都需要重新声明
ARG NODE_VERSION=20
RUN echo "Running with Node $NODE_VERSION"
```
---
### 7.7.5 常见使用场景
#### 1. 控制基础镜像版本
```docker
ARG ALPINE_VERSION=3.19
FROM alpine:${ALPINE_VERSION}
```
```bash
$ docker build --build-arg ALPINE_VERSION=3.18 .
```
#### 2. 设置软件版本
```docker
ARG NGINX_VERSION=1.25.0
RUN curl -fsSL https://nginx.org/download/nginx-${NGINX_VERSION}.tar.gz | tar -xz
```
#### 3. 配置构建环境
```docker
ARG BUILD_ENV=production
ARG ENABLE_DEBUG=false
RUN if [ "$ENABLE_DEBUG" = "true" ]; then \
npm install --include=dev; \
else \
npm install --production; \
fi
```
#### 4. 配置私有仓库
```docker
ARG NPM_TOKEN
RUN echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc && \
npm install && \
rm ~/.npmrc
```
```bash
## 构建时传入 token
$ docker build --build-arg NPM_TOKEN=xxx .
```
---
### 7.7.6 ARG 传递给 ENV
如果需要在运行时使用 ARG 的值
```docker
ARG VERSION=1.0.0
## 将 ARG 传递给 ENV
ENV APP_VERSION=$VERSION
## 运行时可用
CMD echo "App version: $APP_VERSION"
```
---
### 7.7.7 预定义 ARG
Docker 提供了一些预定义的 ARG无需声明即可使用
| ARG | 说明 |
|-----|------|
| `HTTP_PROXY` | HTTP 代理 |
| `HTTPS_PROXY` | HTTPS 代理 |
| `NO_PROXY` | 不使用代理的地址 |
| `FTP_PROXY` | FTP 代理 |
```bash
## 构建时使用代理
$ docker build --build-arg HTTP_PROXY=http://proxy:8080 .
```
---
### 7.7.8 最佳实践
#### 1. ARG 提供合理默认值
```docker
## ✅ 好:有默认值
ARG NODE_VERSION=20
## ⚠️ 需要每次传入
ARG NODE_VERSION
```
#### 2. 不要用 ARG 存储敏感信息
```docker
## ❌ 错误:密码会被记录在镜像历史中
ARG DB_PASSWORD
RUN echo "password=$DB_PASSWORD" > /app/.env
## ✅ 正确:使用 secrets 或运行时环境变量
...
```
#### 3. 使用 ARG 提高构建灵活性
```docker
ARG BASE_IMAGE=python:3.12-slim
FROM ${BASE_IMAGE}
## 可以构建不同基础镜像的版本
## docker build --build-arg BASE_IMAGE=python:3.11-alpine .
...
```
---

View File

@@ -1,257 +0,0 @@
## 7.8 VOLUME 定义匿名卷
### 7.8.1 基本语法
```docker
VOLUME ["/路径1", "/路径2"]
VOLUME /路径
```
`VOLUME` 指令创建挂载点并标记为外部挂载的卷
---
### 7.8.2 为什么使用 VOLUME
> **核心原则**容器存储层应该保持无状态任何运行时数据都应该存储在卷中
```mermaid
flowchart LR
subgraph NoVolume ["没有 VOLUME"]
direction TB
subgraph Container1 ["容器存储层"]
direction TB
Files["数据库文件 (问题)<br/>日志文件<br/>上传文件"]
end
Result1["容器删除 = 数据丢失"]
Container1 ~~~ Result1
end
subgraph UseVolume ["使用 VOLUME"]
direction TB
Container2["容器存储层<br/>(只读/无状态)"]
subgraph Volume ["数据卷"]
Data["持久化数据 (安全)"]
end
Container2 --> Volume
Result2["容器删除,数据保留"]
Volume ~~~ Result2
end
```
---
### 7.8.3 基本用法
#### 定义单个卷
```docker
FROM mysql:8.0
VOLUME /var/lib/mysql
```
#### 定义多个卷
```docker
FROM myapp
VOLUME ["/data", "/logs", "/config"]
```
---
### 7.8.4 VOLUME 的行为
#### 1. 自动创建匿名卷
如果运行时未指定挂载Docker 会自动创建匿名卷
```bash
$ docker run mysql:8.0
$ docker volume ls
DRIVER VOLUME NAME
local a1b2c3d4e5f6... # 自动创建的匿名卷
```
#### 2. 可被命名卷覆盖
```bash
## 使用命名卷替代匿名卷
$ docker run -v mysql_data:/var/lib/mysql mysql:8.0
```
#### 3. 可被 Bind Mount 覆盖
```bash
## 使用宿主机目录替代
$ docker run -v /my/data:/var/lib/mysql mysql:8.0
```
---
### 7.8.5 VOLUME 在构建时的特殊行为
> **重要**VOLUME 之后对该目录的修改会被丢弃
```docker
FROM ubuntu
VOLUME /data
## ❌ 这个文件不会出现在镜像中!
RUN echo "hello" > /data/test.txt
```
**原因**VOLUME 指令之后Docker 将该目录视为外部挂载点不再记录对它的修改
#### 正确做法
```docker
FROM ubuntu
## ✅ 先写入文件
RUN mkdir -p /data && echo "hello" > /data/test.txt
## 再声明 VOLUME
VOLUME /data
```
---
### 7.8.6 常见使用场景
#### 数据库持久化
```docker
FROM postgres:15
VOLUME /var/lib/postgresql/data
```
#### 日志目录
```docker
FROM nginx
VOLUME /var/log/nginx
```
#### 上传文件目录
```docker
FROM myapp
VOLUME /app/uploads
```
---
### 7.8.7 查看 VOLUME 定义
```bash
## 查看镜像定义的 VOLUME
$ docker inspect mysql:8.0 --format '{{json .Config.Volumes}}' | jq
{
"/var/lib/mysql": {}
}
## 查看容器挂载的卷
$ docker inspect mycontainer --format '{{json .Mounts}}' | jq
```
---
### 7.8.8 VOLUME vs docker run -v
| 特性 | Dockerfile VOLUME | docker run -v |
|------|-------------------|---------------|
| **定义时机** | 镜像构建时 | 容器运行时 |
| **默认行为** | 创建匿名卷 | 可指定命名卷或路径 |
| **灵活性** | (固定路径)| (可任意指定)|
| **适用场景** | 定义必须持久化的路径 | 灵活的数据管理 |
---
### 7.8.9 Compose
Compose 中配置如下
```yaml
services:
db:
image: postgres:15
volumes:
# 命名卷(推荐)
- postgres_data:/var/lib/postgresql/data
# Bind Mount
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
volumes:
postgres_data: # 声明命名卷
```
---
### 7.8.10 安全注意事项
#### 匿名卷可能导致数据丢失
```bash
## 使用 --rm 运行的容器,匿名卷会在容器删除时一起删除
$ docker run --rm mysql:8.0
## 容器停止后,数据丢失!
...
```
**解决**始终使用命名卷
```bash
$ docker run -v mysql_data:/var/lib/mysql mysql:8.0
```
---
### 7.8.11 最佳实践
#### 1. 定义必须持久化的路径
```docker
## 数据库必须使用卷
FROM postgres:15
VOLUME /var/lib/postgresql/data
```
#### 2. 不要在 VOLUME 后修改目录
```docker
## ❌ 避免
VOLUME /app/data
RUN cp init-data.json /app/data/
## ✅ 正确
RUN mkdir -p /app/data && cp init-data.json /app/data/
VOLUME /app/data
```
#### 3. 文档中说明 VOLUME 用途
```docker
## 持久化用户上传的文件
VOLUME /app/uploads
## 持久化数据库数据
VOLUME /var/lib/mysql
```
---

View File

@@ -1,218 +0,0 @@
## 7.9 EXPOSE 暴露端口
### 7.9.1 基本语法
```docker
EXPOSE <端口> [<端口>/<协议>...]
```
`EXPOSE` 声明容器运行时提供服务的端口这是一个 **文档性质的声明**告诉使用者容器会监听哪些端口
---
### 7.9.2 基本用法
```docker
## 声明单个端口
EXPOSE 80
## 声明多个端口
EXPOSE 80 443
## 声明 TCP 和 UDP 端口
EXPOSE 80/tcp
EXPOSE 53/udp
```
---
### 7.9.3 EXPOSE 的作用
#### 1. 文档说明
告诉镜像使用者容器将在哪些端口提供服务
```docker
## 使用者一看就知道这是 web 应用
EXPOSE 80 443
```
```bash
## 查看镜像暴露的端口
$ docker inspect nginx --format '{{.Config.ExposedPorts}}'
map[80/tcp:{}]
```
#### 2. 配合 -P 使用
使用 `docker run -P` Docker 会自动映射 EXPOSE 的端口到宿主机随机端口
```docker
## Dockerfile
EXPOSE 80
```
```bash
$ docker run -P nginx
$ docker port $(docker ps -q)
80/tcp -> 0.0.0.0:32768
```
---
### 7.9.4 EXPOSE vs -p
| 特性 | EXPOSE | -p |
|------|--------|-----|
| **位置** | Dockerfile | docker run 命令 |
| **作用** | 声明/文档 | 实际端口映射 |
| **是否必需** | | (外部访问时)|
| **映射发生时** | 不发生 | 运行时发生 |
```mermaid
flowchart TD
Expose["EXPOSE 80<br/>仅声明意图"]
Run["docker run -p<br/>实际端口映射<br/>宿主机 ←→ 容器"]
Expose ~~~ Run
```
#### 没有 EXPOSE 也能 -p
```docker
## 即使没有 EXPOSE也可以使用 -p
FROM nginx
## 没有 EXPOSE
...
```
```bash
## 仍然可以映射端口
$ docker run -p 8080:80 mynginx
```
---
### 7.9.5 常见误解
#### 误解EXPOSE 会打开端口
```docker
## ❌ 错误理解:这不会让容器可从外部访问
EXPOSE 80
```
EXPOSE 不会
- 自动进行端口映射
- 让服务可从外部访问
- 在容器启动时开启端口监听
EXPOSE 只是元数据声明容器是否实际监听该端口取决于容器内的应用
#### 正确理解
```docker
## Dockerfile
FROM nginx
EXPOSE 80 # 1. 声明:这个容器会在 80 端口提供服务
```
```bash
## 运行:需要 -p 才能从外部访问
$ docker run -p 8080:80 nginx # 2. 映射:宿主机 8080 → 容器 80
```
---
### 7.9.6 最佳实践
#### 1. 总是声明应用使用的端口
```docker
## Web 服务
FROM nginx
EXPOSE 80 443
## 数据库
FROM postgres
EXPOSE 5432
## Redis
FROM redis
EXPOSE 6379
```
#### 2. 使用明确的协议
```docker
## 默认是 TCP
EXPOSE 80
## 明确指定 UDP
EXPOSE 53/udp
## 同时支持 TCP 和 UDP
EXPOSE 53/tcp 53/udp
```
#### 3. 与应用实际端口保持一致
```docker
## ✅ 好EXPOSE 与应用端口一致
ENV PORT=3000
EXPOSE 3000
CMD ["node", "server.js"]
## ❌ 差EXPOSE 与应用端口不一致(误导)
EXPOSE 80
CMD ["node", "server.js"] # 实际监听 3000
```
---
### 7.9.7 使用环境变量
```docker
ARG PORT=80
EXPOSE $PORT
```
---
### 7.9.8 Compose
Compose 中配置如下
```yaml
services:
web:
build: .
ports:
- "8080:80" # 映射端口(类似 -p
expose:
- "80" # 仅声明(类似 EXPOSE
```
`expose` Compose 中仅用于容器间通信的文档说明不进行端口映射
---

View File

@@ -1,58 +0,0 @@
# 第七章 Dockerfile 指令详解
## 什么是 Dockerfile
Dockerfile 是一个文本文件其內包含了一条条的 **指令 (Instruction)**每一条指令构建一层therefore 每一条指令的内容就是描述该层应当如何构建
[第四章](../04_image/README.md)我们通过 `docker commit` 学习了镜像的构成但是手动 `commit` 只能作为临时修补并不适合作为生产环境镜像的构建方式
使用 Dockerfile 构建镜像有以下优势
* **自动化**可以通过 `docker build` 命令自动构建镜像
* **可重复性**由于 Dockerfile 是文本文件可以确保每次构建的结果一致
* **版本控制**Dockerfile 可以纳入版本控制系统 ( Git)便于追踪变更
* **透明性**任何人都可以通过阅读 Dockerfile 了解镜像的构建过程
## Dockerfile 基本结构
Dockerfile 一般分为四部分基础镜像信息维护者信息镜像操作指令和容器启动时执行指令
### 指令详解
本章将详细讲解 Dockerfile 中的各个指令
* [COPY 复制文件](7.2_copy.md)
* [ADD 更高级的复制文件](7.3_add.md)
* [CMD 容器启动命令](7.4_cmd.md)
* [ENTRYPOINT 入口点](7.5_entrypoint.md)
* [ENV 设置环境变量](7.6_env.md)
* [ARG 构建参数](7.7_arg.md)
* [VOLUME 定义匿名卷](7.8_volume.md)
* [EXPOSE 暴露端口](7.9_expose.md)
* [WORKDIR 指定工作目录](7.10_workdir.md)
* [USER 指定当前用户](7.11_user.md)
* [HEALTHCHECK 健康检查](7.12_healthcheck.md)
* [ONBUILD 为他人作嫁衣裳](7.13_onbuild.md)
* [LABEL 为镜像添加元数据](7.14_label.md)
* [SHELL 指令](7.15_shell.md)
* [RUN 执行命令](7.1_run.md)
此外我们还将介绍 Dockerfile 的最佳实践和常见问题
* [参考文档](7.16_references.md)
## 使用 Dockerfile 构建镜像
构建镜像的基本命令格式为
```bash
docker build [选项] <上下文路径/URL/->
```
例如 Dockerfile 所在目录执行
```bash
docker build -t my-image:v1 .
```
更多关于 `docker build` 的用法我们在实战中会结合具体指令进行演示

View File

@@ -1,208 +0,0 @@
## 本章小结
| 要点 | 说明 |
|------|------|
| **作用** | 设置后续指令的工作目录 |
| **语法** | `WORKDIR /path` |
| **自动创建** | 目录不存在会自动创建 |
| **持久性** | 影响后续所有指令直到下次 WORKDIR |
| **不要用** | `RUN cd /path` (无效)|
### 7.19.1 延伸阅读
- [COPY 复制文件](7.2_copy.md)文件复制
- [RUN 执行命令](../04_image/4.5_build.md)执行构建命令
- [最佳实践](../appendix/best_practices.md)Dockerfile 编写指南
| 要点 | 说明 |
|------|------|
| **作用** | 切换后续指令的执行用户 |
| **语法** | `USER username` `USER UID:GID` |
| **前提** | 用户必须已存在 |
| **运行时覆盖** | `docker run -u` |
| **切换工具** | 使用 gosu不用 su/sudo |
### 7.19.2 延伸阅读
- [安全](../18_security/README.md)容器安全实践
- [ENTRYPOINT](7.5_entrypoint.md)入口脚本中的用户切换
- [最佳实践](../appendix/best_practices.md)Dockerfile 安全
| 要点 | 说明 |
|------|------|
| **作用** | 检测容器应用是否真实可用 |
| **命令** | `HEALTHCHECK [选项] CMD command` |
| **状态** | starting, healthy, unhealthy |
| **Compose** | 支持 `condition: service_healthy` 依赖 |
| **注意** | 避免副作用节省资源 |
### 7.19.3 延伸阅读
- [CMD 容器启动命令](7.4_cmd.md)启动主进程
- [Compose 模板文件](../11_compose/11.5_compose_file.md)Compose 中的健康检查
- [Docker 调试](../appendix/debug.md)容器排障
| 要点 | 说明 |
|------|------|
| **作用** | 定义在子镜像构建时执行的指令 |
| **语法** | `ONBUILD INSTRUCTION` |
| **适用** | 基础架构镜像 (Node, Python, Go )|
| **限制** | 只继承一次不可级联 |
| **规范** | 建议使用 `-onbuild` 标签后缀 |
### 7.19.4 延伸阅读
- [COPY 指令](7.2_copy.md)文件复制
- [Dockerfile 最佳实践](../appendix/best_practices.md)基础镜像设计
| 要点 | 说明 |
|------|------|
| **作用** | 添加 key-value 元数据 |
| **语法** | `LABEL k=v k=v ...` |
| **规范** | 推荐使用 OCI 标准标签 |
| **弃用** | 不要再使用 `MAINTAINER` |
| **查看** | `docker inspect` |
### 7.19.5 延伸阅读
- [OCI 标签规范](https://github.com/opencontainers/image-spec/blob/main/annotations.md)
- [Dockerfile 最佳实践](../appendix/best_practices.md)
| 要点 | 说明 |
|------|------|
| **作用** | 更改 RUN/CMD/ENTRYPOINT 的默认 shell |
| **Linux 默认** | `["/bin/sh", "-c"]` |
| **Windows 默认** | `["cmd", "/S", "/C"]` |
| **推荐用法** | `SHELL ["/bin/bash", "-o", "pipefail", "-c"]` |
| **影响范围** | 后续所有使用 shell 格式的指令 |
### 7.19.6 延伸阅读
- [RUN 指令](../04_image/4.5_build.md)执行命令
- [Dockerfile 最佳实践](../appendix/best_practices.md)错误处理与调试
| 要点 | 说明 |
|------|------|
| **作用** | 在新层执行命令 |
| **原则** | 合并命令清理缓存 |
| **格式** | Shell (常用) vs Exec |
| **陷阱** | `cd` 不持久环境变量不持久 |
| **进阶** | 使用 Cache Mount 加速构建 |
### 7.19.7 延伸阅读
- [CMD 容器启动命令](7.4_cmd.md)容器启动时的命令
- [WORKDIR 指定工作目录](7.10_workdir.md)改变目录
- [Dockerfile 最佳实践](../appendix/best_practices.md)
| 操作 | 示例 |
|------|------|
| 复制文件 | `COPY app.js /app/` |
| 复制多个文件 | `COPY *.json /app/` |
| 复制目录内容 | `COPY src/ /app/src/` |
| 修改所有者 | `COPY --chown=node:node . /app/` |
| 从构建阶段复制 | `COPY --from=builder /app/dist ./` |
### 7.19.8 延伸阅读
- [ADD 指令](7.3_add.md)复制和解压
- [WORKDIR 指令](7.10_workdir.md)设置工作目录
- [多阶段构建](7.17_multistage_builds.md)优化镜像大小
- [最佳实践](../appendix/best_practices.md)Dockerfile 编写指南
| 场景 | 推荐指令 |
|------|---------|
| 复制普通文件 | `COPY` |
| 复制目录 | `COPY` |
| 自动解压 tar | `ADD` |
| URL 下载 | `RUN curl` |
| 保持 tar 不解压 | `COPY` |
### 7.19.9 延伸阅读
- [COPY 复制文件](7.2_copy.md)基本复制操作
- [多阶段构建](7.17_multistage_builds.md)减少镜像体积
- [最佳实践](../appendix/best_practices.md)Dockerfile 编写指南
| 要点 | 说明 |
|------|------|
| **作用** | 指定容器启动时的默认命令 |
| **推荐格式** | exec 格式 `CMD [“程序”, “参数”]` |
| **覆盖方式** | `docker run image 新命令` |
| ** ENTRYPOINT** | CMD 作为 ENTRYPOINT 的默认参数 |
| **核心原则** | 应用必须在前台运行 |
### 7.19.10 延伸阅读
- [ENTRYPOINT 入口点](7.5_entrypoint.md)固定的启动命令
- [后台运行](../05_container/5.2_daemon.md)容器前台/后台概念
- [最佳实践](../appendix/best_practices.md)Dockerfile 编写指南
| ENTRYPOINT | CMD | 适用场景 |
|------------|-----|---------|
| | | 镜像作为固定命令使用 |
| | | 简单的默认命令 |
| | | **推荐**固定命令 + 可配置参数 |
### 7.19.11 延伸阅读
- [CMD 容器启动命令](7.4_cmd.md)默认命令
- [最佳实践](../appendix/best_practices.md)启动命令设计
- [后台运行](../05_container/5.2_daemon.md)前台/后台概念
| 要点 | 说明 |
|------|------|
| **语法** | `ENV KEY=value` |
| **作用范围** | 构建时 + 运行时 |
| **覆盖方式** | `docker run -e KEY=value` |
| ** ARG** | ARG 仅构建时ENV 持久化到运行时 |
| **安全** | 不要存储敏感信息 |
### 7.19.12 延伸阅读
- [ARG 构建参数](7.7_arg.md)构建时变量
- [Compose 环境变量](../11_compose/11.5_compose_file.md)Compose 中的环境变量
- [最佳实践](../appendix/best_practices.md)Dockerfile 编写指南
| 要点 | 说明 |
|------|------|
| **作用** | 定义构建时变量 |
| **语法** | `ARG NAME=value` |
| **覆盖** | `docker build --build-arg NAME=value` |
| **作用域** | FROM 之后需要重新声明 |
| **vs ENV** | ARG 仅构建时ENV 构建+运行时 |
| **安全** | 不要存储敏感信息 |
### 7.19.13 延伸阅读
- [ENV 设置环境变量](7.6_env.md)运行时环境变量
- [FROM 指令](../04_image/4.5_build.md)基础镜像指定
- [多阶段构建](7.17_multistage_builds.md)复杂构建场景
| 要点 | 说明 |
|------|------|
| **作用** | 创建挂载点标记为外部卷 |
| **语法** | `VOLUME /path` |
| **默认行为** | 自动创建匿名卷 |
| **覆盖方式** | `docker run -v name:/path` |
| **注意** | VOLUME 之后的修改会丢失 |
### 7.19.14 延伸阅读
- [数据卷](../08_data/8.1_volume.md)卷的管理和使用
- [挂载主机目录](../08_data/8.2_bind-mounts.md)Bind Mount
- [Compose 数据管理](../11_compose/11.5_compose_file.md)Compose 中的卷配置
| 要点 | 说明 |
|------|------|
| **作用** | 声明容器提供服务的端口 (文档)|
| **不会** | 自动映射端口或开放外部访问 |
| **配合** | `docker run -P` 自动映射 |
| **外部访问** | 需要 `-p 宿主机端口:容器端口` |
| **语法** | `EXPOSE 80` `EXPOSE 80/tcp` |
### 7.19.15 延伸阅读
- [网络配置](../09_network/README.md)Docker 网络详解
- [端口映射](../09_network/9.5_port_mapping.md)-p 参数详解
- [Compose 端口](../11_compose/11.5_compose_file.md)Compose 中的端口配置

View File

@@ -1,369 +0,0 @@
## 8.1 数据卷
### 8.1.1 为什么需要数据卷
容器的存储层有一个关键问题**容器删除后数据就没了**
```mermaid
flowchart LR
Run[容器运行] --> Write[写入数据]
Write --> Delete[容器删除]
Delete -->|数据都在容器 writable 层| Lost[DATA LOST! ❌]
```
数据卷 (Volume) 解决了这个问题它的生命周期独立于容器
---
### 8.1.2 数据卷的特性
| 特性 | 说明 |
|------|------|
| **持久化** | 容器删除后数据仍然保留 |
| **共享** | 多个容器可以挂载同一个数据卷 |
| **即时生效** | 对数据卷的修改立即可见 |
| **不影响镜像** | 数据卷中的数据不会打包进镜像 |
| **性能更好** | 绕过 UnionFS直接读写 |
---
### 8.1.3 数据卷 vs 容器存储层
#### 容器存储层 (不推荐存储重要数据)
```mermaid
graph TD
subgraph Container [容器]
Writable[容器存储层<br>Writable]
Image[镜像层<br>ReadOnly]
Writable --- Image
end
Lifecycle[生命周期 = 容器生命周期] -.-> Container
Delete[容器删除] -->|导致| DataLost[数据丢失 ❌]
```
#### 数据卷 (推荐)
```mermaid
graph TD
subgraph Container [容器]
AppDir["/app/data"]
end
subgraph Volume [数据卷 my-data]
Data[持久化数据]
end
AppDir == 挂载 ==> Volume
Delete[容器删除] -.->|不会影响| Volume
```
---
### 8.1.4 数据卷基本操作
#### 创建数据卷
```bash
$ docker volume create my-vol
```
#### 列出所有数据卷
```bash
$ docker volume ls
DRIVER VOLUME NAME
local my-vol
local postgres_data
local redis_data
```
#### 查看数据卷详情
```bash
$ docker volume inspect my-vol
[
{
"CreatedAt": "2026-01-15T10:00:00Z",
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/my-vol/_data",
"Name": "my-vol",
"Options": {},
"Scope": "local"
}
]
```
**关键字段**
- `Mountpoint`数据卷在宿主机上的实际存储位置
- `Driver`存储驱动 (默认 local也可以用第三方驱动)
---
### 8.1.5 挂载数据卷
#### 方式一--mount (推荐)
```bash
$ docker run -d \
--name web \
--mount source=my-vol,target=/usr/share/nginx/html \
nginx
```
**参数说明**
| 参数 | 说明 |
|------|------|
| `source` | 数据卷名称 (不存在会自动创建)|
| `target` | 容器内挂载路径 |
| `readonly` | 可选只读挂载 |
#### 方式二-v (简写)
```bash
$ docker run -d \
--name web \
-v my-vol:/usr/share/nginx/html \
nginx
```
**格式**`-v 数据卷名:容器路径[:选项]`
#### 两种方式对比
| 特性 | --mount | -v |
|------|---------|-----|
| 语法 | 键值对更清晰 | 冒号分隔更简洁 |
| 自动创建卷 | source 不存在会报错 | 自动创建 |
| 推荐程度 | 推荐 (更明确)| 常用 (更简洁)|
#### 只读挂载
```bash
## --mount 方式
$ docker run -d \
--mount source=my-vol,target=/data,readonly \
nginx
## -v 方式
$ docker run -d \
-v my-vol:/data:ro \
nginx
```
---
### 8.1.6 使用场景示例
#### 场景一数据库持久化
```bash
## 创建数据卷
$ docker volume create postgres_data
## 启动 PostgreSQL数据存储在数据卷中
$ docker run -d \
--name postgres \
-e POSTGRES_PASSWORD=secret \
-v postgres_data:/var/lib/postgresql/data \
postgres:16
## 即使删除容器,数据仍然保留
$ docker rm -f postgres
## 重新启动,数据还在
$ docker run -d \
--name postgres \
-e POSTGRES_PASSWORD=secret \
-v postgres_data:/var/lib/postgresql/data \
postgres:16
```
#### 场景二多容器共享数据
```bash
## 创建共享数据卷
$ docker volume create shared-data
## 容器 A 写入数据
$ docker run -d --name writer \
-v shared-data:/data \
alpine sh -c "while true; do date >> /data/log.txt; sleep 5; done"
## 容器 B 读取数据
$ docker run --rm \
-v shared-data:/data \
alpine cat /data/log.txt
```
#### 场景三配置文件持久化
```bash
## 将 nginx 配置存储在数据卷中
$ docker run -d \
-v nginx-config:/etc/nginx/conf.d \
-v nginx-logs:/var/log/nginx \
-p 80:80 \
nginx
```
---
### 8.1.7 数据卷管理
#### 删除数据卷
```bash
## 删除指定数据卷
$ docker volume rm my-vol
## 删除容器时同时删除数据卷
$ docker rm -v container_name
```
#### 清理未使用的数据卷
```bash
## 查看未被任何容器使用的数据卷
$ docker volume ls -f dangling=true
## 删除所有未使用的数据卷
$ docker volume prune
## 强制删除(不提示确认)
$ docker volume prune -f
```
> **注意**数据卷不会自动垃圾回收长期运行的系统应定期清理无用数据卷
---
### 8.1.8 数据卷备份与恢复
#### 备份数据卷
```bash
## 使用临时容器挂载数据卷,打包备份
$ docker run --rm \
-v my-vol:/source:ro \
-v $(pwd):/backup \
alpine tar czf /backup/my-vol-backup.tar.gz -C /source .
```
**原理**
1. 创建临时容器
2. 挂载要备份的数据卷到 `/source`
3. 挂载当前目录到 `/backup`
4. 使用 tar 打包
#### 恢复数据卷
```bash
## 创建新数据卷
$ docker volume create my-vol-restored
## 解压备份到新数据卷
$ docker run --rm \
-v my-vol-restored:/target \
-v $(pwd):/backup:ro \
alpine tar xzf /backup/my-vol-backup.tar.gz -C /target
```
#### 备份脚本示例
```bash
#!/bin/bash
## backup-volume.sh
VOLUME_NAME=$1
BACKUP_DIR=${2:-/backups}
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
docker run --rm \
-v ${VOLUME_NAME}:/source:ro \
-v ${BACKUP_DIR}:/backup \
alpine tar czf /backup/${VOLUME_NAME}_${TIMESTAMP}.tar.gz -C /source .
echo "Backed up ${VOLUME_NAME} to ${BACKUP_DIR}/${VOLUME_NAME}_${TIMESTAMP}.tar.gz"
```
---
### 8.1.9 数据卷 vs 绑定挂载
Docker 有两种主要的数据持久化方式
| 特性 | 数据卷 (Volume) | 绑定挂载 (Bind Mount) |
|------|----------------|---------------------|
| **管理方式** | Docker 管理 | 用户管理 |
| **存储位置** | `/var/lib/docker/volumes/` | 任意宿主机路径 |
| **可移植性** | 更好 | 依赖宿主机路径 |
| **适用场景** | 生产数据持久化 | 开发时同步代码 |
| **备份** | 需要工具 | 直接访问文件 |
```bash
## 数据卷
$ docker run -v mydata:/app/data nginx
## 绑定挂载
$ docker run -v /host/path:/app/data nginx
```
详见[绑定挂载](8.2_bind-mounts.md)章节
---
### 8.1.10 常见问题
#### Q如何知道容器使用了哪些数据卷
```bash
$ docker inspect container_name --format '{{json .Mounts}}' | jq
```
#### Q数据卷的数据在哪里
```bash
## 查看数据卷详情
$ docker volume inspect my-vol
## Mountpoint 字段显示实际路径
"Mountpoint": "/var/lib/docker/volumes/my-vol/_data"
```
> **注意**不建议直接修改 Mountpoint 中的文件应通过容器操作
#### Q如何在不同机器间迁移数据卷
1. 在源机器备份`docker run --rm -v mydata:/data -v $(pwd):/backup alpine tar czf /backup/data.tar.gz -C /data .`
2. 传输 tar.gz 文件
3. 在目标机器恢复
---

View File

@@ -1,296 +0,0 @@
## 8.2 挂载主机目录
### 8.2.1 什么是绑定挂载
Bind Mount (绑定挂载) **宿主机的目录或文件** 直接挂载到容器中容器可以读写宿主机的文件系统
```mermaid
flowchart LR
subgraph Host ["宿主机"]
Dir1["/home/user/code/"]
end
subgraph Container ["容器"]
Dir2["/usr/share/nginx/html/"]
end
Dir1 <-->|Bind Mount| Dir2
```
目录结构同一份文件
```text
/home/user/code/ (或 /usr/share/nginx/html/)
├── index.html
├── style.css
└── app.js
```
---
### 8.2.2 Bind Mount vs Volume
| 特性 | Bind Mount | Volume |
|------|------------|--------|
| **数据位置** | 宿主机任意路径 | Docker 管理的目录 |
| **路径指定** | 必须是绝对路径 | 卷名 |
| **可移植性** | 依赖宿主机路径 | 更好 (Docker 管理)|
| **性能** | 依赖宿主机文件系统 | 优化的存储驱动 |
| **适用场景** | 开发环境配置文件 | 生产数据持久化 |
| **备份** | 直接访问文件 | 需要通过 Docker |
#### 选择建议
| 需求 | 推荐方案 |
|------|----------|
| 开发时同步代码 | Bind Mount |
| 持久化数据库数据 | Volume |
| 共享配置文件 | Bind Mount |
| 容器间共享数据 | Volume |
| 备份方便 | Bind Mount (直接访问)|
| 生产环境 | Volume |
---
### 8.2.3 基本语法
#### 使用 --mount (推荐)
```bash
$ docker run -d \
--mount type=bind,source=/宿主机路径,target=/容器路径 \
nginx
```
#### 使用 -v (简写)
```bash
$ docker run -d \
-v /宿主机路径:/容器路径 \
nginx
```
#### 两种语法对比
| 特性 | --mount | -v |
|------|---------|-----|
| 语法 | 键值对更清晰 | 冒号分隔更简洁 |
| 路径不存在时 | 报错 | 自动创建目录 |
| 推荐程度 | 推荐 | 常用 |
---
### 8.2.4 使用场景
#### 场景一开发环境代码同步
```bash
## 将本地代码目录挂载到容器
$ docker run -d \
-p 8080:80 \
--mount type=bind,source=$(pwd)/src,target=/usr/share/nginx/html \
nginx
## 修改本地文件,容器内立即生效(热更新)
$ echo "Hello" > src/index.html
## 浏览器刷新即可看到变化
...
```
#### 场景二配置文件挂载
```bash
## 挂载自定义 nginx 配置
$ docker run -d \
--mount type=bind,source=/path/to/nginx.conf,target=/etc/nginx/nginx.conf,readonly \
nginx
```
#### 场景三日志收集
```bash
## 将容器日志输出到宿主机目录
$ docker run -d \
--mount type=bind,source=/var/log/myapp,target=/app/logs \
myapp
```
#### 场景四共享 SSH 密钥
```bash
## 挂载 SSH 密钥(只读)
$ docker run --rm -it \
--mount type=bind,source=$HOME/.ssh,target=/root/.ssh,readonly \
alpine ssh user@remote
```
---
### 8.2.5 只读挂载
防止容器修改宿主机文件
```bash
## --mount 语法
$ docker run -d \
--mount type=bind,source=/config,target=/app/config,readonly \
myapp
## -v 语法
$ docker run -d \
-v /config:/app/config:ro \
myapp
```
容器内尝试写入会报错
```bash
$ touch /app/config/new.txt
touch: /app/config/new.txt: Read-only file system
```
---
### 8.2.6 挂载单个文件
```bash
## 挂载 bash 历史记录
$ docker run --rm -it \
--mount type=bind,source=$HOME/.bash_history,target=/root/.bash_history \
ubuntu bash
## 挂载自定义配置文件
$ docker run -d \
--mount type=bind,source=/path/to/my.cnf,target=/etc/mysql/my.cnf \
mysql
```
> **注意**挂载单个文件时如果宿主机上的文件被编辑器替换 (而非原地修改)容器内仍是旧文件的 inode建议重启容器或挂载目录
---
### 8.2.7 查看挂载信息
```bash
$ docker inspect mycontainer --format '{{json .Mounts}}' | jq
```
输出
```json
[
{
"Type": "bind",
"Source": "/home/user/code",
"Destination": "/app",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
}
]
```
| 字段 | 说明 |
|------|------|
| `Type` | 挂载类型 (bind)|
| `Source` | 宿主机路径 |
| `Destination` | 容器内路径 |
| `RW` | 是否可读写 |
| `Propagation` | 挂载传播模式 |
---
### 8.2.8 常见问题
#### Q路径不存在报错
```bash
$ docker run --mount type=bind,source=/not/exist,target=/app nginx
docker: Error response from daemon: invalid mount config for type "bind":
bind source path does not exist: /not/exist
```
**解决**确保源路径存在或改用 `-v` (会自动创建)
#### Q权限问题
容器内用户可能无权访问挂载的文件
```bash
## 方法1确保宿主机文件权限允许容器用户访问
$ chmod -R 755 /path/to/data
## 方法2以 root 运行容器
$ docker run -u root ...
## 方法3使用相同的 UID
$ docker run -u $(id -u):$(id -g) ...
```
#### QmacOS/Windows 性能问题
Docker Desktop Bind Mount 性能较差 (需要跨文件系统同步)
```bash
## 使用 :cached 或 :delegated 提高性能macOS
$ docker run -v /host/path:/container/path:cached myapp
```
| 选项 | 说明 |
|------|------|
| `:cached` | 宿主机权威容器读取可能延迟 |
| `:delegated` | 容器权威宿主机读取可能延迟 |
| `:consistent` | 默认完全一致 (最慢)|
---
### 8.2.9 最佳实践
#### 1. 开发环境使用 Bind Mount
```bash
## 代码热更新
$ docker run -v $(pwd):/app -p 3000:3000 node npm run dev
```
#### 2. 生产环境使用 Volume
```bash
## 数据持久化
$ docker run -v mysql_data:/var/lib/mysql mysql
```
#### 3. 配置文件使用只读挂载
```bash
$ docker run -v /config/nginx.conf:/etc/nginx/nginx.conf:ro nginx
```
#### 4. 注意路径安全
```bash
## ❌ 危险:挂载根目录或敏感目录
$ docker run -v /:/host ...
## ✅ 只挂载必要的目录
$ docker run -v /app/data:/data ...
```
---

View File

@@ -1,35 +0,0 @@
## 8.3 tmpfs 挂载
`tmpfs` 挂载会把数据放在宿主机内存中而不是写入容器可写层或数据卷
### 8.3.1 适用场景
- 临时缓存
- 会话数据
- 不希望落盘的敏感中间文件
### 8.3.2 基本用法
```bash
$ docker run --tmpfs /run:rw,noexec,nosuid,size=64m nginx
```
也可以使用 `--mount` 语法
```bash
$ docker run --mount type=tmpfs,destination=/run,tmpfs-size=67108864 nginx
```
### 8.3.3 注意事项
- 容器停止后`tmpfs` 数据会丢失
- `tmpfs` 占用宿主机内存建议显式限制大小
- 不适合需要持久化的数据
### 8.3.4 Volume / Bind Mount 对比
| 类型 | 数据位置 | 持久化 | 典型用途 |
|------|---------|-------|---------|
| Volume | Docker 管理目录 | | 数据库长期业务数据 |
| Bind Mount | 宿主机指定目录 | | 开发联调配置文件共享 |
| tmpfs | 内存 | | 高速临时数据敏感临时文件 |

View File

@@ -1,12 +0,0 @@
# 第八章 数据管理
如图 8-1 所示Docker 数据管理主要围绕三类挂载方式展开
![Docker 数据挂载类型](./_images/types-of-mounts.png)
8-1 Docker 数据挂载类型示意图
这一章介绍如何在 Docker 内部以及容器之间管理数据在容器中管理数据主要有两种方式
* [数据卷](8.1_volume.md)
* [挂载主机目录](8.2_bind-mounts.md)

View File

@@ -1,30 +0,0 @@
## 本章小结
| 要点 | 说明 |
|------|------|
| **作用** | 将宿主机目录挂载到容器 |
| **语法** | `-v /宿主机:/容器` `--mount type=bind,...` |
| **只读** | 添加 `readonly` `:ro` |
| **适用场景** | 开发环境配置文件日志 |
| **vs Volume** | Bind 更灵活Volume 更适合生产 |
### 8.4.1 延伸阅读
- [数据卷](8.1_volume.md)Docker 管理的持久化存储
- [tmpfs 挂载](8.3_tmpfs.md)内存临时存储
- [Compose 数据管理](../11_compose/11.5_compose_file.md)Compose 中的挂载配置
| 操作 | 命令 |
|------|------|
| 创建数据卷 | `docker volume create name` |
| 列出数据卷 | `docker volume ls` |
| 查看详情 | `docker volume inspect name` |
| 删除数据卷 | `docker volume rm name` |
| 清理未用 | `docker volume prune` |
| 挂载数据卷 | `-v name:/path` `--mount source=name,target=/path` |
### 8.4.2 延伸阅读
- [绑定挂载](8.2_bind-mounts.md)挂载宿主机目录
- [tmpfs 挂载](8.3_tmpfs.md)内存中的临时存储
- [存储驱动](../12_implementation/12.4_ufs.md)Docker 存储的底层原理

View File

@@ -1,108 +0,0 @@
## 9.1 配置 DNS
Docker 1.10.0 以后内建了一个 DNS 服务器使得容器可以直接通过容器名称通信方法很简单只要在创建容器时使用 `--name` 为容器命名即可
但是使用 Docker DNS 有个前提条件就是它只能在 **自定义网络** 中使用也就是说如果使用的是默认的 `bridge` 网络是无法使用 DNS 所以我们就需要自定义网络
### 9.2.1 容器的 DNS 机制
Docker 容器的 DNS 配置有两种情况
1. **默认 Bridge 网络**继承宿主机的 DNS 配置 (`/etc/resolv.conf`)
2. **自定义网络** (推荐)使用 Docker 嵌入式 DNS 服务器 (Embedded DNS)支持通过 **容器名** 进行服务发现
---
### 9.2.2 嵌入式 DNS
这是 Docker 网络最强大的功能之一在自定义网络中容器可以通过 名字 找到彼此而不需要知道对方的 IP (因为 IP 可能会变)
```bash
## 1. 创建自定义网络
$ docker network create mynet
## 2. 启动容器 web 并加入网络
$ docker run -d --name web --network mynet nginx
## 3. 启动容器 client 并尝试 ping web
$ docker run -it --rm --network mynet alpine ping web
PING web (172.18.0.2): 56 data bytes
64 bytes from 172.18.0.2: seq=0 ttl=64 time=0.074 ms
```
**原理**
Docker 守护进程在 `127.0.0.11` 运行了一个 DNS 服务器容器内的 DNS 请求会被转发到这里如果是容器名解析为容器 IP如果是外部域名 ( google.com)转发给上游 DNS
---
### 9.2.3 配置 DNS 参数
如果你需要手动配置容器的 DNS (例如使用内网 DNS 服务器)可以在 `docker run` 中使用以下参数
#### 1. --dns
指定 DNS 服务器 IP
```bash
$ docker run -it --dns=114.114.114.114 ubuntu cat /etc/resolv.conf
nameserver 114.114.114.114
```
#### 2. --dns-search
指定 DNS 搜索域例如设置为 `example.com` `ping host` 会尝试解析 `host.example.com`
```bash
$ docker run --dns-search=example.com myapp
```
#### 3. --hostname (-h)
设置容器的主机名
```bash
$ docker run -h myweb nginx
```
---
### 9.2.4 全局 DNS 配置
如果希望所有容器都使用特定的 DNS 服务器 (而不是继承宿主机)可以修改 `/etc/docker/daemon.json`
```json
{
"dns": [
"114.114.114.114",
"8.8.8.8"
]
}
```
修改后需要重启 Docker 服务`systemctl restart docker`
---
### 9.2.5 常见问题
以下是使用容器 DNS 时常见的问题及解决方法
#### Q容器无法解析域名
**现象**`ping www.baidu.com` 失败 `ping 8.8.8.8` 成功**解决**
1. 宿主机的 `/etc/resolv.conf` 可能有问题 (例如使用了本地回环地址 127.0.0.53特别是 Ubuntu 系统)Docker 可能会尝试修复但有时会失败
2. 尝试手动指定 DNS`docker run --dns 8.8.8.8 ...`
3. 检查防火墙是否拦截了 UDP 53 端口
#### Q无法通过容器名通信
**现象**`ping db` 提示 `bad address 'db'`**原因**
- 你可能在使用 **默认的 bridge 网络**默认 bridge 网络 **不支持** 通过容器名进行 DNS 解析 (这是一个历史遗留设计)
- **解决**使用自定义网络 (`docker network create ...`)
---

View File

@@ -1,78 +0,0 @@
## 9.2 网络类型
Docker 提供了多种网络驱动来满足不同的使用场景安装 Docker 系统会自动创建三个默认网络
```bash
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
abc123... bridge bridge local
def456... host host local
ghi789... none null local
```
### 9.2.1 网络类型对比
各网络类型的特点和适用场景如下
| 网络类型 | 说明 | 适用场景 |
|---------|------|---------|
| **bridge** | 默认类型容器连接到虚拟网桥 | 大多数单机场景 |
| **host** | 容器直接使用宿主机网络栈 | 需要最高网络性能时 |
| **none** | 禁用网络 | 完全隔离的容器 |
| **overlay** | 跨主机网络 | Docker Swarm 集群 |
| **macvlan** | 容器拥有独立 MAC 地址 | 需要直接接入物理网络 |
### 9.2.2 Bridge 网络 (默认)
Bridge Docker 默认使用的网络模式Docker 启动时会自动创建 `docker0` 虚拟网桥所有未指定网络的容器都会连接到这个网桥上
核心组件如下
| 组件 | 说明 |
|------|------|
| **docker0** | 虚拟网桥充当交换机角色 |
| **veth pair** | 虚拟网卡对一端在容器内一端连接网桥 |
| **容器 eth0** | 容器内的网卡 |
| **IP 地址** | 自动从 172.17.0.0/16 网段分配 |
### 9.2.3 Host 网络
使用 `--network host` 参数启动的容器会直接使用宿主机的网络栈不再拥有独立的网络命名空间容器内的端口就是宿主机的端口无需端口映射
```bash
$ docker run -d --network host nginx
```
这种模式下网络性能最高但容器之间和宿主机之间没有网络隔离
### 9.2.4 None 网络
使用 `--network none` 参数启动的容器只有 `lo` 回环网卡完全没有外部网络连接适用于只需要运行计算任务不需要网络的容器
```bash
$ docker run -it --network none alpine ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> ...
inet 127.0.0.1/8 scope host lo
```
### 9.2.5 数据流向
容器网络中的数据流向可以分为以下几种情况
```mermaid
flowchart LR
subgraph Comm1 ["容器间通信"]
direction LR
C1A["容器 A (172.17.0.2)"] --> D0A["docker0"] --> C1B["容器 B (172.17.0.3)"]
end
subgraph Comm2 ["访问外网"]
direction LR
C2A["容器 A (172.17.0.2)"] --> D0B["docker0"] --> Eth0A["eth0"] --> InternetA["互联网"]
end
subgraph Comm3 ["被外部访问,需端口映射"]
direction LR
Req["外部请求"] --> Eth0B["eth0"] --> D0C["docker0"] --> C3A["容器 A"]
end
```

View File

@@ -1,93 +0,0 @@
## 9.3 自定义网络
在生产环境中推荐使用用户自定义网络代替默认的 bridge 网络自定义网络提供了更好的隔离性和服务发现能力
### 9.4.1 为什么要用自定义网络
默认 bridge 网络存在以下局限而自定义网络可以很好地解决这些问题
| 问题 | 自定义网络的优势 |
|------|-----------------|
| 只能用 IP 通信 | 支持容器名 DNS 解析 |
| 所有容器在同一网络 | 更好的隔离性 |
| 需要 --link (已废弃)| 原生支持服务发现 |
### 9.4.2 创建自定义网络
使用 `docker network create` 命令可以创建自定义网络
```bash
## 创建网络
$ docker network create mynet
## 查看网络详情
$ docker network inspect mynet
```
### 9.4.3 使用自定义网络
启动容器时通过 `--network` 参数指定连接的网络
```bash
## 启动容器并连接到自定义网络
$ docker run -d --name web --network mynet nginx
$ docker run -d --name db --network mynet postgres
## 在 web 容器中可以直接用容器名访问 db
$ docker exec web ping db
PING db (172.18.0.3): 56 data bytes
64 bytes from 172.18.0.3: seq=0 ttl=64 time=0.083 ms
```
### 9.4.4 容器名 DNS 解析
自定义网络自动提供 DNS 服务Docker 守护进程在 `127.0.0.11` 运行了一个嵌入式 DNS 服务器容器内的 DNS 请求会被转发到这里
- 如果是容器名解析为容器 IP
- 如果是外部域名 ( google.com)转发给上游 DNS
```mermaid
flowchart LR
subgraph MyNet ["mynet 网络 : web 容器可以用 'db' 作为主机名访问 db 容器"]
direction LR
Web["web<br/>172.18.0.2"] -- "DNS: 'db' → 172.18.0.3" --> DB["db<br/>172.18.0.3"]
end
```
### 9.4.5 常用网络命令
以下是 Docker 网络管理中常用的命令
```bash
## 列出网络
$ docker network ls
## 创建网络
$ docker network create mynet
## 查看网络详情
$ docker network inspect mynet
## 连接容器到网络
$ docker network connect mynet mycontainer
## 断开网络连接
$ docker network disconnect mynet mycontainer
## 删除网络
$ docker network rm mynet
## 清理未使用的网络
$ docker network prune
```

View File

@@ -1,62 +0,0 @@
## 9.4 容器互联
容器之间的网络通信是 Docker 网络的核心功能之一本节介绍容器互联的几种方式
### 9.5.1 同一网络内的容器
同一自定义网络内的容器可以直接通过容器名通信这是推荐的容器互联方式
```bash
## 创建网络
$ docker network create app-net
## 启动应用和数据库
$ docker run -d --name redis --network app-net redis
$ docker run -d --name app --network app-net myapp
## app 容器中可以用 redis:6379 连接 Redis
...
```
### 9.5.2 连接到多个网络
一个容器可以同时连接到多个网络这对于需要跨网络通信的中间件容器特别有用
```bash
## 启动容器
$ docker run -d --name multi-net-container --network frontend nginx
## 再连接到另一个网络
$ docker network connect backend multi-net-container
## 查看容器的网络
$ docker inspect multi-net-container --format '{{json .NetworkSettings.Networks}}'
```
### 9.5.3 --link 已废弃
`--link` Docker 早期用于容器互联的方式**已经被废弃**不建议在新项目中使用请使用自定义网络替代
```bash
## 旧方式(不推荐)
$ docker run --link db:database myapp
## 新方式(推荐)
$ docker network create mynet
$ docker run --network mynet --name db postgres
$ docker run --network mynet --name app myapp
```
使用自定义网络的优势在于
- 原生支持 DNS 解析
- 不需要在容器启动时显式声明依赖
- 更灵活可以动态 connect/disconnect

View File

@@ -1,144 +0,0 @@
## 9.5 外部访问容器
容器运行在自己的隔离网络环境中 (通常是 Bridge 模式)为了让外部网络访问容器内的服务我们需要将容器的端口映射到宿主机的端口
### 9.6.1 为什么要映射端口
容器的网络访问规则如下
- **容器之间**可以通过 IP 或容器名 (自定义网络) 互通
- **宿主机访问容器**可以通过容器 IP 访问
- **外部网络访问容器** 默认无法直接访问
为了让外部 (如你的浏览器其他局域网机器) 访问容器内的服务我们需要将容器的端口 **映射** 到宿主机的端口
```mermaid
flowchart TD
User["外部用户 (Browser)"] --> Host["宿主机 (localhost:8080)"]
Host --> Proxy["Docker Proxy<br/>端口映射 (8080 -> 80)"]
Proxy --> Container["容器 (Class B: 80)"]
```
---
### 9.6.2 端口映射方式
Docker 提供了多种方式来指定端口映射
#### 1. 指定映射
使用 `-p <宿主机端口>:<容器端口>` 格式
```bash
## 将宿主机的 8080 端口映射到容器的 80 端口
$ docker run -d -p 8080:80 nginx
```
此时访问 `http://localhost:8080` 即可看到 Nginx 页面
**多种格式**
| 格式 | 含义 | 示例 |
|------|------|------|
| `ip:hostPort:containerPort` | 绑定指定 IP 的特定端口 | `-p 127.0.0.1:8080:80` (仅本机访问) |
| `ip::containerPort` | 绑定指定 IP 的随机端口 | `-p 127.0.0.1::80` |
| `hostPort:containerPort` | 绑定所有 IP (0.0.0.0) 的特定端口 | `-p 8080:80` (默认) |
| `containerPort` | 绑定所有 IP 的随机端口 | `-p 80` |
#### 2. 随机映射
如果不关心宿主机使用哪个端口可以使用随机映射使用 `-P` (大写) 参数Docker 会随机映射 Dockerfile `EXPOSE` 指令暴露的所有端口到宿主机的高端口 (49000-49900)
```bash
$ docker run -d -P nginx
```
查看映射结果
```bash
$ docker ps
CONTAINER ID PORTS
abc123456 0.0.0.0:49153->80/tcp
```
此时 Nginx 被映射到了宿主机的 49153 端口
---
### 9.6.3 查看端口映射
可以使用以下命令查看容器的端口映射
#### docker port
运行 `docker port` 可以查看到指定容器的端口映射情况
```bash
$ docker port mycontainer
80/tcp -> 0.0.0.0:8080
80/tcp -> [::]:8080
```
#### docker ps
运行 `docker ps` 可以查看到所有容器的端口映射列表
```bash
$ docker ps
CONTAINER ID IMAGE PORTS NAMES
abc123456 nginx 0.0.0.0:8080->80/tcp web
```
---
### 9.6.4 最佳实践与安全
在配置端口映射时需要注意以下安全事项
#### 1. 限制监听 IP
默认情况下`-p 8080:80` 会监听 `0.0.0.0:8080`这意味着任何人只要能连接你的宿主机 IP就能访问该服务
如果不希望对外暴露 (例如数据库服务)应绑定到 `127.0.0.1`
```bash
## 仅允许本机访问
$ docker run -d -p 127.0.0.1:3306:3306 mysql
```
#### 2. 避免端口冲突
如果宿主机 8080 已经被占用了容器将无法启动
**解决**
- 更换宿主机端口`-p 8081:80`
- Docker 自动分配`-p 80`
#### 3. UDP 映射
默认是 TCP 协议如果要映射 UDP 服务 ( DNSSyslog)
```bash
$ docker run -d -p 53:53/udp dns-server
```
---
### 9.6.5 实现原理
Docker 使用 `docker-proxy` 进程 (用户态) `iptables` DNAT 规则 (内核态) 来实现端口转发
当流量到达宿主机端口时iptables 规则将其目标地址修改为容器 IP 并转发
```bash
## 简化的 iptables 逻辑
iptables -t nat -A DOCKER -p tcp --dport 8080 -j DNAT --to-destination 172.17.0.2:80
```
这也是为什么你在容器内部看到的访问来源 IP 通常是网关 IP ( 172.17.0.1)而不是真实的外部 Client IP (除非使用 host 网络模式)
---

View File

@@ -1,81 +0,0 @@
## 9.6 网络隔离
Docker 网络提供了天然的隔离能力不同网络之间的容器默认无法通信这是 Docker 网络安全的重要基础
### 9.7.1 网络隔离原理
不同网络之间默认隔离容器只能与同一网络中的容器直接通信
```bash
## 创建两个网络
$ docker network create frontend
$ docker network create backend
## 容器 A 在 frontend
$ docker run -d --name web --network frontend nginx
## 容器 B 在 backend
$ docker run -d --name db --network backend postgres
## web 无法直接访问 db不同网络
$ docker exec web ping db
ping: db: Name or service not known
```
### 9.7.2 安全优势
这种隔离机制带来以下安全优势
| 场景 | 说明 |
|------|------|
| **前后端分离** | 前端容器无法直接访问数据库网络 |
| **微服务隔离** | 不同微服务组可以使用不同网络 |
| **多租户** | 不同租户的容器在不同网络中完全隔离 |
| **最小权限** | 容器只能访问必要的网络资源 |
### 9.7.3 跨网络通信
如果确实需要某个容器跨网络通信可以将其同时连接到多个网络
```bash
## 创建一个中间件容器,连接到两个网络
$ docker run -d --name api --network frontend myapi
$ docker network connect backend api
## 现在 api 容器既可以访问 frontend 中的 web也可以访问 backend 中的 db
```
这种方式让你可以精确控制哪些容器可以跨网络通信遵循最小权限原则
### 9.7.4 典型网络架构
一个典型的多层应用网络架构如下
```mermaid
graph TD
subgraph FrontendNet ["frontend 网络"]
LB["负载均衡器"]
Web1["Web 服务器 1"]
Web2["Web 服务器 2"]
end
subgraph BackendNet ["backend 网络"]
API["API 服务器"]
DB["数据库"]
Cache["Redis 缓存"]
end
LB --> Web1
LB --> Web2
Web1 -.-> API
Web2 -.-> API
API --> DB
API --> Cache
```
在这种架构中API 服务器同时连接到 `frontend` `backend` 网络充当两个网络之间的桥梁负载均衡器和 Web 服务器无法直接访问数据库增强了安全性

View File

@@ -1,41 +0,0 @@
# 第九章 网络配置
Docker 容器需要网络来与外部世界通信容器之间相互通信以及与宿主机通信Docker 在安装时会自动配置网络基础设施大多数情况下开箱即用
## 概述
Docker 启动时自动创建以下网络组件
```mermaid
graph TD
subgraph Host [宿主机]
eth0[物理网卡 eth0<br>192.168.1.100]
docker0[docker0 网桥<br>172.17.0.1]
subgraph Containers
subgraph ContainerA [容器 A]
eth0_A[eth0<br>172.17.0.2]
end
subgraph ContainerB [容器 B]
eth0_B[eth0<br>172.17.0.3]
end
end
eth0 <--> docker0
docker0 <--> eth0_A
docker0 <--> eth0_B
end
Internet((互联网)) <--> eth0
```
本章将详细介绍 Docker 网络配置的各个方面
## 本章内容
* [配置 DNS](9.1_dns.md)
* [外部访问容器](9.5_port_mapping.md)
* [网络类型](9.2_network_types.md)
* [自定义网络](9.3_custom_network.md)
* [容器互联](9.4_container_linking.md)
* [网络隔离](9.6_network_isolation.md)

View File

@@ -1,24 +0,0 @@
## 本章小结
本章介绍了 Docker 网络配置的各个方面
| 概念 | 要点 |
|------|------|
| **DNS 配置** | 自定义网络支持嵌入式 DNS可通过容器名解析 |
| **网络类型** | bridge (默认)hostnoneoverlaymacvlan |
| **自定义网络** | 推荐使用支持容器名 DNS 解析和更好的隔离 |
| **容器互联** | 同一自定义网络内容器可直接通过容器名通信 |
| **端口映射** | `-p 宿主机端口:容器端口` 暴露服务到外部 |
| **网络隔离** | 不同网络默认隔离增强安全性 |
| **--link** | 已废弃使用自定义网络替代 |
### 9.8.1 延伸阅读
- [配置 DNS](9.1_dns.md)自定义 DNS 设置
- [网络类型](9.2_network_types.md)BridgeHostNone 等网络模式
- [自定义网络](9.3_custom_network.md)创建和管理自定义网络
- [容器互联](9.4_container_linking.md)容器间通信方式
- [端口映射](9.5_port_mapping.md)高级端口配置
- [网络隔离](9.6_network_isolation.md)网络安全与隔离策略
- [EXPOSE 指令](../07_dockerfile/7.9_expose.md) Dockerfile 中声明端口
- [Compose 网络](../11_compose/11.5_compose_file.md)Compose 中的网络配置

View File

@@ -1,43 +0,0 @@
## 10.2 使用 buildx 构建镜像
### 10.2.1 使用
Buildx 的使用非常直观绝大多数情况下可以替代 `docker build` 命令
你可以直接使用 `docker buildx build` 命令构建镜像
```bash
$ docker buildx build .
[+] Building 8.4s (23/32)
=> ...
```
Buildx 使用 [BuildKit 引擎](10.1_buildkit.md)进行构建支持许多新的功能具体参考 [Buildkit](10.1_buildkit.md) 一节
#### 使用 `bake`
`docker buildx bake` 是一个高级构建命令支持从 HCLJSON Compose 文件中定义构建目标实现复杂的流水线构建
```bash
## 从 Compose 文件构建所有服务
$ docker buildx bake
## 仅构建指定目标
$ docker buildx bake web
```
#### 生成 SBOM
Buildx 支持在构建时直接生成 SBOM (Software Bill of Materials)这对于软件供应链安全至关重要
```bash
$ docker buildx build --sbom=true -t myimage .
```
该命令会在构建结果中包含 SPDX CycloneDX 格式的 SBOM 数据
### 10.2.2 官方文档
* https://docs.docker.com/engine/reference/commandline/buildx/

View File

@@ -1,131 +0,0 @@
## 10.3 使用 buildx 构建多种系统架构支持的 Docker 镜像
Docker 镜像可以支持多种系统架构这意味着你可以在 `x86_64``arm64` 等不同架构的机器上运行同一个镜像这是通过一个名为 manifest list (或称为 fat manifest) 的文件来实现的
### 10.3.1 Manifest List 是什么
为了理解多架构镜像的原理我们需要先了解 Manifest List 的概念
Manifest list 是一个包含了多个指向不同架构镜像的 manifest 的文件当你拉取一个支持多架构的镜像时Docker 会自动根据你当前的系统架构选择并拉取对应的镜像
例如官方的 `hello-world` 镜像就支持多种架构你可以使用 `docker manifest inspect` 命令来查看它的 manifest list
```bash
$ docker manifest inspect hello-world
{
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
"manifests": [
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"size": 525,
"digest": "sha256:80852a401a974d9e923719a948cc5335a0a4435be8778b475844a7153a2382e5",
"platform": {
"architecture": "amd64",
"os": "linux"
}
},
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"size": 525,
"digest": "sha256:3adea81344be1724b383d501736c3852939b33b3903d02474373700b25e5d6e3",
"platform": {
"architecture": "arm",
"os": "linux",
"variant": "v5"
}
},
// ... more architectures
]
}
```
### 10.3.2 使用 `docker buildx` 构建多架构镜像
`docker buildx` 是构建多架构镜像的最佳实践工具它屏蔽了底层的复杂性提供了一键构建多架构镜像的能力
Docker 19.03+ 版本中`docker buildx` 是推荐的用于构建多架构镜像的工具它使用 `BuildKit` 作为后端可以大大简化构建过程
#### 新建 `builder` 实例
首先你需要创建一个新的 `builder` 实例因为它支持同时为多个平台构建
```bash
$ docker buildx create --name mybuilder --use
$ docker buildx inspect --bootstrap
```
#### 构建和推送
使用 `docker buildx build` 命令并指定 `--platform` 参数可以同时构建支持多种架构的镜像`--push` 参数会将构建好的镜像和 manifest list 推送到 Docker 仓库
```dockerfile
## Dockerfile
FROM --platform=$TARGETPLATFORM alpine
RUN uname -a > /os.txt
CMD cat /os.txt
```
```bash
$ docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t your-username/multi-arch-image . --push
```
构建完成后你就可以在不同架构的机器上拉取并运行 `your-username/multi-arch-image` 这个镜像了
#### 架构相关的构建参数
`Dockerfile` 你可以使用一些预定义的构建参数来根据目标平台定制构建过程
* `TARGETPLATFORM`构建镜像的目标平台例如 `linux/amd64`
* `TARGETOS`目标平台的操作系统例如 `linux`
* `TARGETARCH`目标平台的架构例如 `amd64`
* `TARGETVARIANT`目标平台的变种例如 `v7`
* `BUILDPLATFORM`构建环境的平台
* `BUILDOS`构建环境的操作系统
* `BUILDARCH`构建环境的架构
* `BUILDVARIANT`构建环境的变种
例如你可以这样编写 `Dockerfile` 来拷贝特定架构的二进制文件
```dockerfile
FROM scratch
ARG TARGETOS
ARG TARGETARCH
COPY bin/dist-${TARGETOS}-${TARGETARCH} /dist
ENTRYPOINT ["/dist"]
```
### 10.3.3 使用 `docker manifest` (底层工具)
除了 `docker buildx`我们也可以直接操作 Manifest List 来手动组合不同架构的镜像
`docker manifest` 是一个更底层的命令可以用来创建检查和推送 manifest list虽然 `docker buildx` 在大多数情况下更方便但了解 `docker manifest` 仍然有助于理解其工作原理
#### 创建 manifest list
```bash
## 首先,为每个架构构建并推送镜像
$ docker buildx build --platform linux/amd64 -t your-username/my-app:amd64 . --push
$ docker buildx build --platform linux/arm64 -t your-username/my-app:arm64 . --push
## 然后,创建一个 manifest list将它们组合在一起
$ docker manifest create your-username/my-app:latest \
--amend your-username/my-app:amd64 \
--amend your-username/my-app:arm64
## 最后,推送 manifest list
$ docker manifest push your-username/my-app:latest
```
#### 检查 manifest list
你可以使用 `docker manifest inspect` 来查看一个 manifest list 的详细信息如上文所示

View File

@@ -1,13 +0,0 @@
# 第十章 Docker Buildx
Docker Buildx 是一个 docker CLI 插件其扩展了 docker 命令支持 [Moby BuildKit](10.1_buildkit.md) 提供的功能提供了与 docker build 相同的用户体验并增加了许多新功能
> Buildx 需要 Docker v19.03+在较新版本中已更常用且功能更完整
## 本章内容
本章将详细介绍 Docker Buildx 的使用包括
* [使用 BuildKit 构建镜像](10.1_buildkit.md)
* [使用 Buildx 构建镜像](10.2_buildx.md)
* [构建多种系统架构支持的 Docker 镜像](10.3_multi-arch-images.md)

View File

@@ -1,19 +0,0 @@
## 本章小结
Docker Buildx Docker 构建系统的重要进化提供了高效安全且支持多平台的镜像构建能力
| 概念 | 要点 |
|------|------|
| **BuildKit** | 下一代构建引擎Docker 23.0+ 默认启用 |
| **缓存挂载** | `RUN --mount=type=cache` 加速依赖安装 |
| **Secret 挂载** | `RUN --mount=type=secret` 安全传递密钥 |
| **buildx build** | 替代 `docker build`支持更多构建功能 |
| **多架构构建** | `--platform` 参数一键构建多种架构镜像 |
| **Manifest List** | 多架构镜像的索引文件 |
| **SBOM** | 通过 `--sbom=true` 生成软件物料清单 |
### 10.4.1 延伸阅读
- [Dockerfile 指令详解](../07_dockerfile/README.md)Dockerfile 编写基础
- [多阶段构建](../07_dockerfile/7.17_multistage_builds.md)优化镜像体积
- [Dockerfile 最佳实践](../appendix/best_practices.md)编写高效 Dockerfile

View File

@@ -1,35 +0,0 @@
## 11.1 简介
`Compose` 项目是 Docker 官方的开源项目负责实现对 Docker 容器集群的快速编排从功能上看 `OpenStack` 中的 `Heat` 十分类似
其代码目前在 [https://github.com/docker/compose](https://github.com/docker/compose) 上开源。
`Compose` 定位是 定义和运行多个 Docker 容器的应用 (Defining and running multi-container Docker applications)其前身是开源项目 Fig
通过第一部分中的介绍我们知道使用一个 `Dockerfile` 模板文件可以让用户很方便的定义一个单独的应用容器然而在日常工作中经常会碰到需要多个容器相互配合来完成某项任务的情况例如要实现一个 Web 项目除了 Web 服务容器本身往往还需要再加上后端的数据库服务容器甚至还包括负载均衡容器等
`Compose` 恰好满足了这样的需求它允许用户通过一个单独的 `compose.yaml` (历史默认名也常见为 `docker-compose.yml`) 模板文件 (YAML 格式) 来定义一组相关联的应用容器为一个项目 (project)
### 11.1.1 概述
### 11.1.2 模板文件规范
Compose 模板文件采用 YAML 格式扩展名为 `.yml` `.yaml`
> **注意** Compose V2 `version` 字段已不再强制要求 Docker Compose v5 规范已完全不需要顶层 `version` 字段为了保持最佳兼容性建议不在新文件中使用该字段
Docker Compose 默认使用 `compose.yaml`也兼容 `compose.yml``docker-compose.yaml``docker-compose.yml` 等文件名
`Compose` 中有两个重要的概念
* 服务 (`service`)一个应用的容器实际上可以包括若干运行相同镜像的容器实例
* 项目 (`project`)由一组关联的应用容器组成的一个完整业务单元 Compose 文件中定义
`Compose` 的默认管理对象是项目通过子命令对项目中的一组容器进行便捷地生命周期管理
`Compose` 项目早期由 Python 编写称为 Docker Compose V1
现在的 Docker Compose V2 是一个 Go 语言编写的 Docker CLI 插件已经集成到 Docker Desktop Docker Engine 直接通过 `docker compose` 命令使用它提供了更快的性能和更好的集成体验
只要所操作的平台支持 Docker API就可以在其上利用 `Compose` 来进行编排管理

View File

@@ -1,54 +0,0 @@
## 11.2 安装与卸载
`Compose` Docker 官方的开源项目负责实现对 Docker 容器集群的快速编排
`v2` 版本开始`Compose` 作为 `docker` 的子命令存在例如 `docker compose up`
`Compose` 支持 LinuxmacOSWindows 10 三大平台
`Docker Desktop for Mac/Windows` 自带 `docker compose` 命令
Linux 系统请使用以下介绍的方法安装
### 11.2.1 Linux
Linux 你可以通过下载 Docker Compose CLI 插件 (二进制文件名为 `docker-compose`) 来安装
[官方 GitHub Release](https://github.com/docker/compose/releases) 处直接下载编译好的二进制文件即可。
> **提示**版本更新较快请访问上述链接获取最新版本号替换下方命令中的版本号
例如 Linux 64 位系统上直接下载对应的二进制包 ( v5.0.2 为例)
```bash
$ DOCKER_CONFIG=${DOCKER_CONFIG:-$HOME/.docker}
$ mkdir -p $DOCKER_CONFIG/cli-plugins
$ curl -SL https://github.com/docker/compose/releases/download/v5.0.2/docker-compose-linux-x86_64 -o $DOCKER_CONFIG/cli-plugins/docker-compose
```
之后执行
```bash
$ chmod +x $DOCKER_CONFIG/cli-plugins/docker-compose
```
### 11.2.2 测试安装
```bash
$ docker compose version
Docker Compose version v5.0.2
```
### 11.2.3 bash 补全命令
```bash
$ curl -L https://raw.githubusercontent.com/docker/compose/v5.0.2/contrib/completion/bash/docker-compose | sudo tee /etc/bash_completion.d/docker-compose > /dev/null
```
### 11.2.4 卸载
如果是二进制包方式安装的删除二进制文件即可
```bash
$ rm $DOCKER_CONFIG/cli-plugins/docker-compose
```

View File

@@ -1,134 +0,0 @@
## 11.3 使用
本节将通过一个具体的 Web 应用案例介绍 Docker Compose 的基本概念和使用方法
### 11.3.1 术语
首先介绍几个术语
* 服务 (`service`)一个应用容器实际上可以运行多个相同镜像的实例
* 项目 (`project`)由一组关联的应用容器组成的一个完整业务单元
可见一个项目可以由多个服务 (容器) 关联而成`Compose` 面向项目进行管理
### 11.3.2 场景
最常见的项目是 web 网站该项目应该包含 web 应用和缓存
下面我们用 `Python` 来建立一个能够记录页面访问次数的 web 网站
#### web 应用
新建文件夹在该目录中编写 `app.py` 文件
```python
from flask import Flask
from redis import Redis
app = Flask(__name__)
redis = Redis(host='redis', port=6379)
@app.route('/')
def hello():
count = redis.incr('hits')
return 'Hello World! 该页面已被访问 {} 次。\n'.format(count)
if __name__ == "__main__":
app.run(host="0.0.0.0", debug=True)
```
#### Dockerfile
编写 `Dockerfile` 文件内容为
```docker
FROM python:3.12-alpine
ADD . /code
WORKDIR /code
RUN pip install redis flask
CMD ["python", "app.py"]
```
#### compose.yaml
编写 `compose.yaml` 文件这是 Compose 推荐使用的主模板文件 (也兼容 `docker-compose.yml` 等历史文件名)
```yaml
services:
web:
build: .
ports:
- "5000:5000"
redis:
image: "redis:alpine"
```
#### 运行 compose 项目
```bash
$ docker compose up
```
此时访问本地 `5000` 端口每次刷新页面计数就会加 1
按下 `Ctrl-C` 停止项目
#### 后台运行
```bash
$ docker compose up -d
```
#### 停止
```bash
$ docker compose stop
```
#### 进入服务
```bash
$ docker compose exec redis sh
/data # redis-cli
127.0.0.1:6379> get hits
"9"
```
#### 查看日志
```bash
$ docker compose logs -f
```
#### 构建镜像
```bash
$ docker compose build
```
#### 启动服务
```bash
$ docker compose start
```
#### 运行一次性命令
```bash
$ docker compose run web python app.py
```
#### 验证 Compose 文件
```bash
$ docker compose config
```
#### 删除项目
```bash
$ docker compose down
```

View File

@@ -1,364 +0,0 @@
## 11.6 实战 Django
> 本小节内容适合 `Python` 开发人员阅读
本节将使用 Docker Compose 配置并运行一个 **Django + PostgreSQL** 应用笔者不仅会介绍具体步骤还会解释每个配置项的作用以及开发环境和生产环境的差异
### 11.6.1 架构概览
在开始之前先看整体架构 (如图 10-1 所示)
```mermaid
flowchart TD
subgraph Network ["Docker Compose 网络"]
direction LR
subgraph Web ["web 服务"]
direction TB
Django["Django<br/>应用"]
Port8000[":8000"]
Django ~~~ Port8000
end
subgraph DB ["db 服务"]
direction TB
Postgres["PostgreSQL<br/>数据库"]
end
Django -- ":5432" --> Postgres
end
Browser["localhost:8000<br/>(浏览器访问)"]
Port8000 --> Browser
```
10-1 Django + PostgreSQL Compose 架构
**关键点**
- `web` 服务运行 Django 应用对外暴露 8000 端口
- `db` 服务运行 PostgreSQL 数据库只在内部网络可访问
- 两个服务通过 Docker Compose 自动创建的网络相互通信
- `web` 服务可以通过服务名 `db` 访问数据库 (Docker 内置 DNS)
### 11.6.2 准备工作
创建一个项目目录并进入
```bash
$ mkdir django-docker && cd django-docker
```
我们需要创建三个文件`Dockerfile``requirements.txt` `compose.yaml`
### 11.6.3 步骤 1创建 Dockerfile
```docker
FROM python:3.12-slim
## 防止 Python 缓冲 stdout/stderr让日志实时输出
ENV PYTHONUNBUFFERED=1
## 设置工作目录
WORKDIR /code
## 先复制依赖文件,利用 Docker 缓存加速构建
COPY requirements.txt /code/
## 安装依赖
RUN pip install --no-cache-dir -r requirements.txt
## 复制项目代码
COPY . /code/
```
**逐行解释**
| 指令 | 作用 | 为什么这样写 |
|------|------|-------------|
| `FROM python:3.12-slim` | 基础镜像 | `slim` 版本比完整版小很多但包含运行 Python 所需的一切 |
| `ENV PYTHONUNBUFFERED=1` | 关闭输出缓冲 | `print()` 和日志立即显示便于调试 |
| `WORKDIR /code` | 设置工作目录 | 后续命令都在此目录执行 |
| `COPY requirements.txt` 在前 | 分层复制 | 只有 requirements.txt 变化时才重新安装依赖加速构建 |
| `--no-cache-dir` | 不缓存 pip 下载 | 减小镜像体积 |
> 💡 **笔者建议**总是将变化频率低的文件先复制变化频率高的后复制这样可以最大化利用 Docker 的构建缓存
### 11.6.4 步骤 2创建 requirements.txt
```txt
Django>=5.0,<6.0
psycopg[binary]>=3.1,<4.0
gunicorn>=21.0,<22.0
```
**依赖说明**
| 包名 | 作用 |
|------|------|
| `Django` | Web 框架 |
| `psycopg[binary]` | PostgreSQL 数据库驱动 (推荐使用 psycopg 3)|
| `gunicorn` | 生产环境 WSGI 服务器 (可选开发时可不用)|
### 11.6.5 步骤 3创建 compose.yaml
配置如下
```yaml
services:
db:
image: postgres:16
environment:
POSTGRES_DB: django_db
POSTGRES_USER: django_user
POSTGRES_PASSWORD: django_password
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U django_user -d django_db"]
interval: 5s
timeout: 5s
retries: 5
web:
build: .
command: python manage.py runserver 0.0.0.0:8000
volumes:
- .:/code
ports:
- "8000:8000"
depends_on:
db:
condition: service_healthy
environment:
DATABASE_URL: postgres://django_user:django_password@db:5432/django_db
volumes:
postgres_data:
```
**配置详解**
#### db 服务
db 服务配置如下
```yaml
db:
image: postgres:16 # 使用官方 PostgreSQL 16 镜像
environment:
POSTGRES_DB: django_db # 创建的数据库名
POSTGRES_USER: django_user # 数据库用户
POSTGRES_PASSWORD: django_password # 数据库密码
volumes:
- postgres_data:/var/lib/postgresql/data # 持久化数据
healthcheck: # 健康检查,确保数据库就绪
test: ["CMD-SHELL", "pg_isready -U django_user -d django_db"]
interval: 5s
```
> **笔者提醒**`volumes` 配置很重要没有它每次容器重启数据都会丢失笔者见过不少新手因为忘记这一步导致开发数据全部丢失
#### web 服务
web 服务配置如下
```yaml
web:
build: . # 从当前目录的 Dockerfile 构建
command: python manage.py runserver # 启动 Django 开发服务器
volumes:
- .:/code # 挂载代码目录,支持热更新
ports:
- "8000:8000" # 映射端口
depends_on:
db:
condition: service_healthy # 等待数据库健康后再启动
```
**关键配置说明**
| 配置项 | 作用 | 笔者建议 |
|--------|------|---------|
| `volumes: .:/code` | 代码挂载 | 开发时必备修改代码无需重新构建镜像 |
| `depends_on` + `healthcheck` | 启动顺序 | 确保数据库就绪后 Django 才启动避免连接错误 |
| `environment` | 环境变量 | 推荐用环境变量管理配置避免硬编码 |
### 11.6.6 步骤 4创建 Django 项目
运行以下命令创建新的 Django 项目
```bash
$ docker compose run --rm web django-admin startproject mysite .
```
**命令解释**
- `docker compose run`运行一次性命令
- `--rm`命令执行后删除临时容器
- `web` web 服务环境中执行
- `django-admin startproject mysite .`在当前目录创建 Django 项目
生成的目录结构
```bash
django-docker/
├── compose.yaml
├── Dockerfile
├── requirements.txt
├── manage.py
└── mysite/
├── __init__.py
├── settings.py
├── urls.py
├── asgi.py
└── wsgi.py
```
> 💡 **Linux 用户注意**如果遇到权限问题执行 `sudo chown -R $USER:$USER .`
### 11.6.7 步骤 5配置数据库连接
修改 `mysite/settings.py`配置数据库连接
```python
import os
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': os.environ.get('POSTGRES_DB', 'django_db'),
'USER': os.environ.get('POSTGRES_USER', 'django_user'),
'PASSWORD': os.environ.get('POSTGRES_PASSWORD', 'django_password'),
'HOST': 'db', # Docker Compose 服务名
'PORT': 5432,
}
}
## 允许的主机(开发环境)
ALLOWED_HOSTS = ['*']
```
**为什么 HOST `db` 而不是 `localhost`**
Docker Compose 各服务通过服务名相互访问Docker 内置的 DNS 会将 `db` 解析为 db 服务容器的 IP 地址这是 Docker Compose 的核心功能之一
### 11.6.8 步骤 6启动应用
```bash
$ docker compose up
```
你会看到
1. 首先构建 web 镜像 (第一次运行)
2. 启动 db 服务等待健康检查通过
3. 启动 web 服务
```bash
db-1 | PostgreSQL init process complete; ready for start up.
db-1 | LOG: database system is ready to accept connections
web-1 | Watching for file changes with StatReloader
web-1 | Starting development server at http://0.0.0.0:8000/
```
打开浏览器访问 http://localhost:8000可以看到 Django 欢迎页面!
### 11.6.9 常用开发命令
在另一个终端窗口执行
```bash
## 执行数据库迁移
$ docker compose exec web python manage.py migrate
## 创建超级用户
$ docker compose exec web python manage.py createsuperuser
## 进入 Django shell
$ docker compose exec web python manage.py shell
## 进入 PostgreSQL 命令行
$ docker compose exec db psql -U django_user -d django_db
```
> 💡 笔者建议使用 `exec` 而不是 `run``exec` 在已运行的容器中执行命令`run` 会创建新容器
### 11.6.10 常见问题排查
#### Q1数据库连接失败
**错误信息**`django.db.utils.OperationalError: could not connect to server` **可能原因与解决方案**
| 原因 | 解决方案 |
|------|---------|
| 数据库还没启动完成 | 使用 `depends_on` + `healthcheck` |
| HOST 配置错误 | 确保使用服务名 `db` 而不是 `localhost` |
| 网络未创建 | 运行 `docker compose down` 后重新 `up` |
```bash
## 调试:检查数据库是否正常运行
$ docker compose ps
$ docker compose logs db
```
#### Q2代码修改没有生效
**可能原因**
1. **开发服务器没有自动重载**确保使用 `runserver` 而不是 `gunicorn`
2. **Volume 挂载问题**检查 `compose.yaml` 中的 volumes 配置
3. **缓存问题**尝试 `docker compose restart web`
#### Q3权限问题
```bash
## 如果容器内创建的文件 root 用户所有
$ sudo chown -R $USER:$USER .
```
### 11.6.11 开发 vs 生产关键差异
笔者特别提醒本节的配置是 **开发环境** 配置生产环境需要以下调整
| 配置项 | 开发环境 | 生产环境 |
|--------|---------|---------|
| **Web 服务器** | `runserver` | `gunicorn` + Nginx |
| **DEBUG** | `True` | `False` |
| **密码管理** | 明文写在配置 | 使用 Docker Secrets 或环境变量 |
| **Volume** | 挂载代码目录 | 代码直接 COPY 进镜像 |
| **ALLOWED_HOSTS**| `['*']` | 具体域名 |**生产环境 Compose 文件示例**
```yaml
## compose.prod.yaml
services:
web:
build: .
command: gunicorn mysite.wsgi:application --bind 0.0.0.0:8000
# 不挂载代码,使用镜像内的代码
environment:
DEBUG: 'False'
ALLOWED_HOSTS: 'example.com,www.example.com'
# ...
```
### 11.6.12 延伸阅读
- [Compose 模板文件详解](11.5_compose_file.md)深入理解 Compose 文件的所有配置项
- [使用 WordPress](11.8_wordpress.md)另一个 Compose 实战案例
- [Dockerfile 最佳实践](../appendix/best_practices.md)构建更小更安全的镜像
- [数据管理](../08_data/README.md)Volume 和数据持久化详解

View File

@@ -1,289 +0,0 @@
## 11.7 实战 Rails
> 本小节内容适合 Ruby 开发人员阅读
本节使用 Docker Compose 配置并运行一个 **Rails + PostgreSQL** 应用
### 11.7.1 架构概览
如图 10-2 所示Rails PostgreSQL 在同一 Compose 网络中协同工作
```mermaid
flowchart TD
subgraph Network ["Docker Compose 网络"]
direction LR
subgraph Web ["web 服务"]
direction TB
Rails["Rails<br/>应用"]
Port3000[":3000"]
Rails ~~~ Port3000
end
subgraph DB ["db 服务"]
direction TB
Postgres["PostgreSQL<br/>数据库"]
end
Rails -- ":5432" --> Postgres
end
Browser["localhost:3000"]
Port3000 --> Browser
```
10-2 Rails + PostgreSQL Compose 架构
### 11.7.2 准备工作
创建项目目录
```bash
$ mkdir rails-docker && cd rails-docker
```
需要创建三个文件`Dockerfile``Gemfile` `compose.yaml`
### 11.7.3 步骤 1创建 Dockerfile
```docker
FROM ruby:3.2
## 安装系统依赖
RUN apt-get update -qq && \
apt-get install -y build-essential libpq-dev nodejs && \
rm -rf /var/lib/apt/lists/*
## 设置工作目录
WORKDIR /myapp
## 先复制 Gemfile利用缓存加速构建
COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock
RUN bundle install
## 复制应用代码
COPY . /myapp
```
**配置说明**
| 指令 | 作用 |
|------|------|
| `build-essential` | 编译原生扩展所需 |
| `libpq-dev` | PostgreSQL 客户端库 |
| `nodejs` | Rails Asset Pipeline 需要 |
| 先复制 Gemfile | 只有依赖变化时才重新 `bundle install` |
### 11.7.4 步骤 2创建 Gemfile
创建一个初始的 `Gemfile`稍后会被 `rails new` 覆盖
```ruby
source 'https://rubygems.org'
gem 'rails', '~> 7.1'
```
创建空的 `Gemfile.lock`
```bash
$ touch Gemfile.lock
```
### 11.7.5 步骤 3创建 compose.yaml
配置如下
```yaml
services:
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: password
volumes:
- postgres_data:/var/lib/postgresql/data
web:
build: .
command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
volumes:
- .:/myapp
ports:
- "3000:3000"
depends_on:
- db
environment:
DATABASE_URL: postgres://postgres:password@db:5432/myapp_development
volumes:
postgres_data:
```
**配置详解**
| 配置项 | 说明 |
|--------|------|
| `rm -f tmp/pids/server.pid` | 清理上次异常退出留下的 PID 文件 |
| `volumes: .:/myapp` | 挂载代码目录支持热更新 |
| `depends_on: db` | 确保数据库先启动 |
| `DATABASE_URL` | Rails 12-factor 风格的数据库配置 |
### 11.7.6 步骤 4生成 Rails 项目
使用 `docker compose run` 生成项目骨架
```bash
$ docker compose run --rm web rails new . --force --database=postgresql --skip-bundle
```
**命令解释**
- `--rm`执行后删除临时容器
- `--force`覆盖已存在的文件
- `--database=postgresql`配置使用 PostgreSQL
- `--skip-bundle`暂不安装依赖 (稍后统一安装)
生成的目录结构
```bash
$ ls
Dockerfile Gemfile Rakefile config lib tmp
Gemfile.lock README.md app config.ru log vendor
compose.yaml bin db public
```
> **Linux 用户**如遇权限问题执行 `sudo chown -R $USER:$USER .`
### 11.7.7 步骤 5重新构建镜像
由于生成了新的 Gemfile需要重新构建镜像以安装完整依赖
```bash
$ docker compose build
```
### 11.7.8 步骤 6配置数据库连接
修改 `config/database.yml`
```yaml
default: &default
adapter: postgresql
encoding: unicode
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
url: <%= ENV['DATABASE_URL'] %>
development:
<<: *default
test:
<<: *default
database: myapp_test
production:
<<: *default
```
> 💡 使用 `DATABASE_URL` 环境变量配置数据库符合 12-factor 应用原则便于在不同环境间切换
### 11.7.9 步骤 7启动应用
```bash
$ docker compose up
```
输出示例
```bash
db-1 | PostgreSQL init process complete; ready for start up.
db-1 | LOG: database system is ready to accept connections
web-1 | => Booting Puma
web-1 | => Rails 7.1.0 application starting in development
web-1 | => Run `bin/rails server --help` for more startup options
web-1 | Puma starting in single mode...
web-1 | * Listening on http://0.0.0.0:3000
```
### 11.7.10 步骤 8创建数据库
在另一个终端执行
```bash
$ docker compose exec web rails db:create
Created database 'myapp_development'
Created database 'myapp_test'
```
访问 http://localhost:3000 查看 Rails 欢迎页面。
### 11.7.11 常用开发命令
```bash
## 数据库迁移
$ docker compose exec web rails db:migrate
## Rails 控制台
$ docker compose exec web rails console
## 运行测试
$ docker compose exec web rails test
## 生成脚手架
$ docker compose exec web rails generate scaffold Post title:string body:text
## 进入容器 Shell
$ docker compose exec web bash
```
### 11.7.12 常见问题
#### Q数据库连接失败
检查 `DATABASE_URL` 环境变量格式是否正确确保 db 服务已启动
```bash
$ docker compose ps
$ docker compose logs db
```
#### Qserver.pid 文件导致启动失败
错误信息`A server is already running`
已在 command 中添加 `rm -f tmp/pids/server.pid` 处理如仍有问题
```bash
$ docker compose exec web rm -f tmp/pids/server.pid
```
#### QGem 安装失败
可能需要更新 bundler 或清理缓存
```bash
$ docker compose run --rm web bundle update
```
### 11.7.13 开发 vs 生产
| 配置项 | 开发环境 | 生产环境 |
|--------|---------|---------|
| Rails 服务器 | Puma (开发模式) | Puma + Nginx |
| 代码挂载 | 使用 volumes | 代码打包进镜像 |
| 静态资源 | 动态编译 | 预编译 (`rails assets:precompile`) |
| 数据库密码 | 明文配置 | 使用 Secrets 管理 |
### 11.7.14 延伸阅读
- [使用 Django](11.6_django.md)Python Web 框架实战
- [Compose 模板文件](11.5_compose_file.md)配置详解
- [数据管理](../08_data/README.md)数据持久化

View File

@@ -1,213 +0,0 @@
## 11.8 实战 WordPress
WordPress 是全球最流行的内容管理系统 (CMS)使用 Docker Compose 可以在几分钟内搭建一个包含数据库Web 服务和持久化存储的生产级 WordPress 环境
---
### 11.8.1 项目结构
```bash
wordpress/
├── compose.yaml
├── .env # 环境变量(敏感信息)
└── nginx/ # 可选:反向代理配置
└── nginx.conf
```
---
### 11.8.2 编写 `compose.yaml`
这是一个生产可用的最小化配置
```yaml
services:
# 数据库服务
db:
image: mysql:8.0
container_name: wordpress_db
restart: always
command:
# 使用原生密码认证(旧版 WP 兼容性)
- --default-authentication-plugin=mysql_native_password
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
environment:
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: ${DB_PASSWORD}
volumes:
- db_data:/var/lib/mysql
networks:
- wp_net
# WordPress 服务
wordpress:
image: wordpress:latest
container_name: wordpress_app
restart: always
ports:
- "8000:80"
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: ${DB_PASSWORD}
WORDPRESS_DB_NAME: wordpress
volumes:
- wp_data:/var/www/html
# 增加上传文件大小限制
- ./uploads.ini:/usr/local/etc/php/conf.d/uploads.ini
depends_on:
- db
networks:
- wp_net
volumes:
db_data: # 数据库持久化
wp_data: # WordPress 文件(插件/主题/上传)持久化
networks:
wp_net:
```
---
### 11.8.3 配置文件详解
#### 1. 环境变量 (.env)
为了安全不要在 `compose.yaml` 中直接写密码创建 `.env` 文件
```ini
DB_ROOT_PASSWORD=somestrongrootpassword
DB_PASSWORD=somestronguserpassword
```
Compose 会自动读取此同级目录下的文件
#### 2. 数据持久化
我们定义了两个命名卷
- `db_data`确保 MySQL 容器重建后数据不丢失
- `wp_data`保存 WordPress 的核心文件插件主题和上传的媒体文件
#### 3. PHP 配置优化
默认的 WordPress 镜像上传文件限制较小 (通常 2MB)创建 `uploads.ini`
```ini
file_uploads = On
memory_limit = 256M
upload_max_filesize = 64M
post_max_size = 64M
max_execution_time = 600
```
---
### 11.8.4 启动与运行
1. 启动服务
```bash
$ docker compose up -d
```
2. 访问安装界面
打开浏览器访问 `http://localhost:8000`
3. 查看日志
```bash
$ docker compose logs -f
```
---
### 11.8.5 生产环境最佳实践
#### 1. 数据库备份
不要只依赖 Volume建议定期备份数据库
```bash
## 导出 SQL
$ docker exec wordpress_db mysqldump -u wordpress -pwordpress wordpress > backup.sql
```
或者添加一个自动备份容器
```yaml
backup:
image: tiredofit/db-backup
volumes:
- ./backups:/backup
environment:
- DB_TYPE=mysql
- DB_HOST=db
- DB_NAME=wordpress
- DB_USER=wordpress
- DB_PASS=${DB_PASSWORD}
- DB_DUMP_FREQ=1440 # 每天备份一次
depends_on:
- db
networks:
- wp_net
```
#### 2. 使用 Nginx 反向代理
在生产环境中不要直接暴露 WordPress 端口而是通过 Nginx 进行反向代理并配置 SSL
#### 3. 使用 Redis 缓存
WordPress 支持 Redis 缓存以提高性能
```yaml
redis:
image: redis:alpine
restart: always
networks:
- wp_net
```
WordPress 容器环境变量中添加
```yaml
WORDPRESS_REDIS_HOST: redis
```
并安装 Redis Object Cache 插件
---
### 11.8.6 常见问题
#### Q数据库连接错误
**现象**访问页面显示 Error establishing a database connection**排查**
1. 检查 `docker compose logs wordpress`
2. 确认 `.env` 中的密码与 YAML 文件引用一致
3. 确认 `WORDPRESS_DB_HOST` 也是 `db` (服务名)
4. MySQL 8.0 可能需要几秒钟启动WordPress 会自动重试稍等片刻即可
#### Q无法上传大文件
**解决**确保挂载了 `uploads.ini` 配置并且重启了容器
```bash
$ docker compose restart wordpress
```
---
### 11.8.7 延伸阅读
- [Compose 模板文件](11.5_compose_file.md)深入了解配置项
- [数据卷](../08_data/8.1_volume.md)理解数据持久化
- [Docker Hub WordPress](https://hub.docker.com/_/wordpress):官方镜像文档

View File

@@ -1,15 +0,0 @@
# 第十一章 Docker Compose
`Docker Compose` Docker 官方编排 (Orchestration) 项目之一负责快速的部署分布式应用
本章将介绍 `Compose` 项目情况以及安装和使用
* [简介](11.1_introduction.md)
* [安装与卸载](11.2_install.md)
* [使用](11.3_usage.md)
* [命令说明](11.4_commands.md)
* [Compose 模板文件](11.5_compose_file.md)
* [实战 Django](11.6_django.md)
* [实战 Rails](11.7_rails.md)
* [实战 WordPress](11.8_wordpress.md)
* [实战 LNMP](11.9_lnmp.md)

View File

@@ -1,21 +0,0 @@
## 本章小结
Docker Compose 是管理多容器应用的利器通过 YAML 文件声明式地定义服务网络和数据卷
| 概念 | 要点 |
|------|------|
| **核心概念** | 服务 (service) 和项目 (project) |
| **配置文件** | `compose.yaml` (推荐) `docker-compose.yml` |
| **版本** | Compose V2 Go 编写的 CLI 插件通过 `docker compose` 使用 |
| **启动** | `docker compose up -d` 启动所有服务 |
| **停止** | `docker compose down` 停止并移除容器 |
| **查看状态** | `docker compose ps` 查看服务状态 |
| **查看日志** | `docker compose logs` 查看服务日志 |
| **模板文件** | 支持 `services``networks``volumes` 等顶级配置 |
### 11.10.1 延伸阅读
- [Compose 模板文件](11.5_compose_file.md)详细模板语法参考
- [Compose 命令说明](11.4_commands.md)完整命令列表
- [网络配置](../09_network/README.md)Docker 网络基础
- [数据管理](../08_data/README.md)数据卷管理

View File

@@ -1,157 +0,0 @@
## 12.1 基本架构
Docker 的架构设计简洁而高效主要由客户端和服务端两部分组成
### 12.1.1 核心架构图
Docker 采用了 **C/S (客户端/服务端)** 架构Client Daemon 发送请求Daemon 负责构建运行和分发容器
```mermaid
graph LR
Client[客户端 (Docker CLI)] -- docker run --> Dockerd
Client -- docker pull --> Dockerd
subgraph "Docker Host"
Dockerd(dockerd<br>守护进程)
Containers(Containers<br>容器)
Images(Images<br>镜像)
Dockerd -- 管理 --> Containers
Dockerd -- 管理 --> Images
end
```
---
### 12.1.2 组件详解
Docker 的内部架构如同洋葱一样分层每一层专注解决特定问题
#### 1. Docker CLI (客户端)
用户与 Docker 交互的主要方式它将用户命令 ( `docker run`) 转换为 API 请求发送给 dockerd
#### 2. Dockerd (守护进程)
Docker 的大脑
- 监听 API 请求
- 管理 Docker 对象 (镜像容器网络)
- 编排下层组件完成工作
#### 3. Containerd (高级运行时)
行业标准的容器运行时 (CNCF 毕业项目)
- 管理容器的完整生命周期 (启动停止)
- 镜像拉取与存储
- **不包含** 复杂的与容器无关的功能 (如构建API)
- Kubernetes 也可以直接使用 containerd (跳过 Docker)
#### 4. Runc (低级运行时)
用于创建和运行容器的 CLI 工具
- 直接与内核交互 (NamespacesCgroups)
- 遵循 OCI (Open Container Initiative) 规范
- **主要职责**根据配置启动一个容器然后退出 (将控制权交给容器进程)
#### 5. Shim
每个容器都有一个 shim 进程
- **解耦**允许 dockerd 重启而不影响容器运行
- **保持 IO**维持容器的标准输入输出
- **状态汇报** containerd 汇报容器退出状态
---
### 12.1.3 容器启动流程
当执行 `docker run -d nginx` 内部发生了什么
```mermaid
flowchart TD
User((用户))
subgraph DockerCLI [Docker CLI]
Cmd[docker run -d nginx]
end
subgraph DockerHost [Docker Host]
Dockerd[Dockerd]
Containerd[Containerd]
subgraph ContainerRuntime [Runtime]
Shim[Containerd-shim]
Runc[Runc]
Container[容器进程 (nginx)]
end
end
User --> Cmd
Cmd -- 1. REST API --> Dockerd
Dockerd -- 2. gRPC --> Containerd
Containerd -- 3. 准备镜像 & Bundle --> Containerd
Containerd -- 4. Fork --> Shim
Shim -- 5. Exec --> Runc
Runc -- 6. Create Namespaces/Cgroups --> Container
Runc -.-> |7. Exit| Runc
Shim -.-> |8. Monitor IO/Exit| Container
```
1. **CLI** 发送请求给 **Dockerd**
2. **Dockerd** 解析请求调用 **Containerd**
3. **Containerd** 准备镜像转换为 OCI Bundle
4. **Containerd** 创建 **Shim** 进程
5. **Shim** 调用 **Runc**
6. **Runc** 与系统内核交互创建 Namespaces Cgroups
7. **Runc** 启动 nginx 进程后退出
8. **Shim** 接管容器 IO 和生命周期监控
---
### 12.1.4 Docker Engine v29+ 变化
Docker Engine v29 (2025/2026) 开始架构进一步简化和标准化
- **Containerd 镜像存储 (Image Store)**默认启用Docker 直接使用 Containerd 的镜像管理能力不再维护自己的一套 graphdriver
- **优势**多平台镜像支持更好镜像拉取更快 (lazy pulling) K8s 共享镜像
---
### 12.1.5 Docker Desktop 架构
macOS Windows 因为内核差异架构稍微复杂
```mermaid
flowchart TD
subgraph HostOS ["MacOS / Windows"]
CLI["Docker CLI"]
subgraph LinuxVM ["Linux VM (虚拟机)"]
Engine["Dockerd <--> Containerd <--> Runc"]
end
CLI -- "(Socket 映射)" --> Engine
end
```
- 使用轻量级虚拟机 (Apple Virtualization / WSL 2) 运行 Linux 内核
- 文件挂载 (Bind Mount) 需要跨越 VM 边界 (这也是文件 I/O 慢的原因)
- 网络端口需要从宿主机转发到 VM
---
### 12.1.6 总结
| 组件 | 角色 | 关键职责 |
|------|------|----------|
| **CLI** | 指挥官 | 发送指令展示结果 |
| **Dockerd** | 大管家 | API 接口整体调度 |
| **Containerd** | 经理 | 容器生命周期镜像管理 |
| **Shim** | 监工 | 保持 IO允许无守护进程重启 |
| **Runc** | 工人 | 真正干活 (创建容器)干完就走 |
### 12.1.7 延伸阅读
- [命名空间](./12.2_namespace.md)Runc 如何隔离容器
- [控制组](./12.3_cgroups.md)Runc 如何限制资源
- [联合文件系统](./12.4_ufs.md)镜像如何存储

View File

@@ -1,300 +0,0 @@
## 12.2 命名空间
命名空间是 Linux 内核一个强大的特性每个容器都有自己单独的命名空间运行在其中的应用都像是在独立的操作系统中运行一样命名空间保证了容器之间彼此互不影响
## 12.2 什么是 Namespace
> **Namespace Linux 内核提供的资源隔离机制它让容器内的进程仿佛运行在独立的操作系统中** Namespace 是容器技术的核心基础之一它回答了一个关键问题**如何让一个进程 以为 自己独占整个系统**
```mermaid
flowchart LR
subgraph Host ["宿主机视角"]
direction TB
H1["PID 1: systemd"]
H2["PID 2: sshd"]
H3["PID 3: dockerd"]
H4["PID 1234: nginx"]
H5["PID 1235: nginx worker"]
end
subgraph Container ["容器内视角"]
direction TB
C1["PID 1: nginx<br/>← 容器认为自己是 PID 1"]
C2["PID 2: nginx worker"]
end
H4 -. "(实际是宿主机的 1234" .- C1
```
### 12.2.1 Namespace 的类型
Linux 内核提供了以下几种 NamespaceDocker 容器使用了全部
| Namespace | 隔离内容 | 容器中的效果 |
|-----------|---------|-------------|
| **PID** | 进程 ID | 容器内 PID 1 开始看不到其他容器和宿主机进程 |
| **NET** | 网络栈 | 独立的网卡IP 地址端口路由表 |
| **MNT** | 挂载点 | 独立的文件系统视图自己的根目录 |
| **UTS** | 主机名 | 独立的主机名和域名 |
| **IPC** | 进程间通信 | 独立的信号量消息队列共享内存 |
| **USER** | 用户/ ID | 容器内的 root 可以映射为宿主机的普通用户 |
| **Cgroup** | Cgroup 根目录 | 隔离 cgroup 层级视图 (Linux 4.6+)|
---
### 12.2.2 PID Namespace
PID Namespace 负责进程 ID 的隔离使得容器内的进程彼此不可见
#### PID 的作用
隔离进程 ID让每个容器有自己的进程编号空间
#### PID 隔离效果
```bash
## 宿主机上查看进程
$ ps aux | grep nginx
root 12345 0.0 0.1 nginx: master process
root 12346 0.0 0.1 nginx: worker process
## 容器内查看进程
$ docker exec mycontainer ps aux
PID USER COMMAND
1 root nginx: master process ← 在容器内是 PID 1
2 root nginx: worker process
```
#### PID 关键点
- 容器内的 PID 1 进程特殊重要它是容器的主进程退出则容器停止
- 容器内无法看到宿主机或其他容器的进程
- 宿主机可以看到所有容器内的进程 ( PID 不同)
---
### 12.2.3 NET Namespace
NET Namespace 负责网络栈的隔离包括网卡路由表和 iptables 规则等
#### NET 的作用
隔离网络栈每个容器拥有独立的网络环境
#### NET 隔离效果
```mermaid
flowchart LR
subgraph Host ["宿主机"]
direction TB
H1["eth0: 192.168.1.10<br/>端口 80 可用"]
H2["docker0: 172.17.0.1"]
end
subgraph Container ["容器"]
direction TB
C1["eth0: 172.17.0.2<br/>端口 80 可用"]
C2["(veth pair 连接)"]
end
H2 <--> C2
```
#### NET 关键点
- 每个容器有独立的网卡IP路由表iptables 规则
- 多个容器可以监听相同端口 (如都监听 80)
- Docker 使用 veth pair 连接容器网络和宿主机网桥
---
### 12.2.4 MNT Namespace
MNT Namespace 负责文件系统挂载点的隔离确保容器看到独立的文件系统视图
#### MNT 的作用
隔离文件系统挂载点每个容器有自己的根目录
#### MNT 隔离效果
```bash
宿主机文件系统: 容器内看到的:
/ / ← 容器的根目录
├── bin/ ├── bin/
├── home/ ├── home/
├── var/ ├── var/
│ └── lib/ │ └── lib/
│ └── docker/ │
│ └── overlay2/ │
│ └── merged/ ────┼─── 这个目录成为容器的 /
└── ... └── ...
```
#### chroot 的区别
| 特性 | chroot | MNT Namespace |
|------|--------|---------------|
| 安全性 | 可以逃逸 | 更安全 |
| 挂载隔离 | | 完全隔离 |
| /proc/mounts | 共享 | 独立 |
---
### 12.2.5 UTS Namespace
UTS Namespace 主要用于隔离主机名和域名
#### UTS 的作用
隔离主机名和域名让每个容器可以有自己的主机名
#### UTS 隔离效果
```bash
## 宿主机
$ hostname
my-server
## 容器内
$ docker run --hostname mycontainer ubuntu hostname
mycontainer
```
UTS = UNIX Time-sharing System是历史遗留的名称
---
### 12.2.6 IPC Namespace
IPC Namespace 用于隔离进程间通信资源 System V IPC POSIX 消息队列
#### IPC 的作用
隔离 System V IPC POSIX 消息队列
#### 隔离的资源
- 信号量 (semaphores)
- 消息队列 (message queues)
- 共享内存 (shared memory)
#### IPC 关键点
- 同一容器内的进程可以通过 IPC 通信
- 不同容器的进程无法通过 IPC 通信 (除非显式共享)
---
### 12.2.7 USER Namespace
USER Namespace 允许将容器内的用户 ID 映射到宿主机的不同用户 ID
#### USER 的作用
隔离用户和组 ID实现权限隔离
#### USER 隔离效果
```mermaid
flowchart LR
subgraph Container ["容器内"]
direction TB
C1["UID 0 (root)"]
C2["UID 1 (daemon)"]
end
subgraph Host ["宿主机"]
direction TB
H1["UID 100000<br/>← 非特权用户"]
H2["UID 100001"]
end
C1 -- 映射 --> H1
C2 -- 映射 --> H2
```
#### 安全意义
容器内的 root 用户可以映射为宿主机上的普通用户即使容器被突破攻击者在宿主机上也只有普通权限
> 💡 笔者建议生产环境建议启用 User Namespace增强安全性
---
### 12.2.8 动手实验体验 Namespace
使用 `unshare` 命令可以在不使用 Docker 的情况下体验 Namespace
#### 实验 1UTS Namespace
```bash
## 创建新的 UTS namespace 并启动 shell
$ sudo unshare --uts /bin/bash
## 修改主机名(只影响这个 namespace
$ hostname container-test
$ hostname
container-test
## 退出后查看宿主机主机名(未改变)
$ exit
$ hostname
my-server
```
#### 实验 2PID Namespace
```bash
## 创建新的 PID 和 MNT namespace
$ sudo unshare --pid --mount --fork /bin/bash
## 挂载新的 /proc
$ mount -t proc proc /proc
## 查看进程(只能看到当前 shell
$ ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 8960 4516 pts/0 S 10:00 0:00 /bin/bash
root 8 0.0 0.0 10072 3200 pts/0 R+ 10:00 0:00 ps aux
```
#### 实验 3NET Namespace
```bash
## 创建新的网络 namespace
$ sudo unshare --net /bin/bash
## 查看网络接口(只有 lo
$ ip addr
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
```
---
### 12.2.9 Namespace 的局限性
Namespace 提供了隔离但不是安全边界
| 方面 | 说明 |
|------|------|
| **共享内核** | 所有容器共享宿主机内核内核漏洞可能影响所有容器 |
| **部分资源未隔离** | /proc/sys 部分内容仍可见时间无法隔离 |
| **非虚拟化** | 比虚拟机隔离性弱 |
> 需要更强隔离时可考虑 gVisorKata Containers 等安全容器方案
---

View File

@@ -1,271 +0,0 @@
## 12.3 控制组
控制组 (Cgroups) Linux 内核提供的另一种关键机制主要用于资源的限制和审计
### 12.3.1 什么是控制组
控制组 (Control Groups简称 cgroups) Linux 内核的一个特性用于 **限制记录和隔离** 进程组的资源使用 (CPU内存磁盘 I/O网络等)
> **核心作用**让多个容器公平共享宿主机资源防止单个容器耗尽系统资源
```mermaid
flowchart LR
subgraph NoLimit ["无 cgroups 限制"]
direction TB
subgraph HostRes1 ["宿主机资源"]
A["容器 A<br/>占用所有<br/>内存和 CPU"]
B["容器 B、C 饥饿"]
end
end
subgraph Limit ["有 cgroups 限制"]
direction TB
subgraph HostRes2 ["宿主机资源"]
direction LR
C_A["A<br/>1GB<br/>2核"]
C_B["B<br/>1GB<br/>1核"]
C_C["C<br/>1GB<br/>1核"]
end
end
```
---
### 12.3.2 cgroups 的历史
| 时间 | 事件 |
|------|------|
| 2006 | Google 工程师提出 cgroups 概念 |
| 2008 | Linux 2.6.24 正式支持 cgroups v1 |
| 2016 | Linux 4.5 引入 cgroups v2 |
| 现在 | Docker 默认使用 cgroups v2 (如系统支持)|
---
### 12.3.3 cgroups 可以限制的资源
| 资源类型 | 子系统 | 说明 |
|---------|--------|------|
| **CPU** | `cpu`, `cpuset` | CPU 使用时间和核心分配 |
| **内存** | `memory` | 内存使用上限和 swap |
| **块设备 I/O** | `blkio` | 磁盘读写速度限制 |
| **网络** | `net_cls`, `net_prio` | 网络带宽优先级 |
| **进程数** | `pids` | 限制进程/线程数量 |
---
### 12.3.4 Docker 中的资源限制
Docker 提供了丰富的参数来配置容器的资源限制主要包括内存CPU磁盘 I/O
#### 内存限制
```bash
## 限制容器最多使用 512MB 内存
$ docker run -m 512m myapp
## 限制内存 + swap
$ docker run -m 512m --memory-swap 1g myapp
## 软限制(超过时警告,不会 OOM Kill
$ docker run --memory-reservation 256m myapp
```
| 参数 | 说明 |
|------|------|
| `-m` / `--memory` | 硬限制 (超过会 OOM Kill)|
| `--memory-swap` | 内存 + swap 总限制 |
| `--memory-reservation` | 软限制 (内存竞争时生效)|
| `--oom-kill-disable` | 禁用 OOM Killer (谨慎使用)|
#### CPU 限制
```bash
## 限制使用 1.5 个 CPU 核心
$ docker run --cpus=1.5 myapp
## 限制使用 CPU 0 和 1
$ docker run --cpuset-cpus="0,1" myapp
## 设置 CPU 使用权重(相对值,默认 1024
$ docker run --cpu-shares=512 myapp
```
| 参数 | 说明 |
|------|------|
| `--cpus` | 限制 CPU 核心数 ( 1.5)|
| `--cpuset-cpus` | 绑定到特定 CPU 核心 |
| `--cpu-shares` | CPU 时间片权重 (相对值)|
| `--cpu-period` / `--cpu-quota` | 精细控制 CPU 配额 |
#### 磁盘 I/O 限制
```bash
## 限制设备写入速度为 10MB/s
$ docker run --device-write-bps /dev/sda:10mb myapp
## 限制设备读取速度
$ docker run --device-read-bps /dev/sda:10mb myapp
## 限制 IOPS
$ docker run --device-write-iops /dev/sda:100 myapp
```
#### 进程数限制
```bash
## 限制最多 100 个进程
$ docker run --pids-limit=100 myapp
```
---
### 12.3.5 查看容器资源使用
```bash
## 实时监控所有容器的资源使用
$ docker stats
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O
abc123 web 0.50% 45.5MiB / 512MiB 8.89% 1.2kB / 0B 0B / 0B
def456 db 2.30% 256MiB / 1GiB 25.00% 5.6kB / 3.2kB 4.1MB / 2.3MB
## 查看特定容器
$ docker stats mycontainer
## 查看容器的 cgroup 配置
$ docker inspect mycontainer --format '{{json .HostConfig}}' | jq
```
---
### 12.3.6 资源限制的效果
#### 内存超限
```bash
## 启动限制 100MB 内存的容器
$ docker run -m 100m stress --vm 1 --vm-bytes 200M
## 容器会被 OOM Killer 杀死
$ docker ps -a
CONTAINER ID STATUS NAMES
abc123 Exited (137) 5 seconds ago hopeful_darwin
## 137 = 128 + 9表示被 SIGKILL9 杀死
...
```
#### CPU 限制验证
```bash
## 不限制 CPU
$ docker run --rm stress --cpu 4
## 占满所有 CPU
## 限制为 1 个核心
$ docker run --rm --cpus=1 stress --cpu 4
## 只能使用约 100% CPU1 个核心)
...
```
---
### 12.3.7 cgroups v1 vs v2
| 特性 | cgroups v1 | cgroups v2 |
|------|-----------|-----------|
| 层级结构 | 多层级 (每个资源单独)| 统一层级 |
| 管理复杂度 | 复杂 | 简化 |
| 资源分配 | 基于层级 | 基于子树 |
| PSI (压力监控)| | |
| rootless 容器 | 部分支持 | 完整支持 |
#### 检查系统使用的版本
```bash
## 查看 cgroup 版本
$ mount | grep cgroup
cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime)
## 如果显示 cgroup2 表示 v2
## 或者
$ cat /proc/filesystems | grep cgroup
nodev cgroup
nodev cgroup2
```
---
### 12.3.8 Compose 中设置限制
Compose 中设置限制配置如下
```yaml
services:
web:
image: nginx
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
reservations:
cpus: '0.25'
memory: 256M
```
---
### 12.3.9 最佳实践
在使用 Cgroups 限制资源时遵循一些最佳实践可以避免潜在的问题
#### 1. 始终设置内存限制
```bash
## 防止 OOM 影响宿主机
$ docker run -m 1g myapp
```
#### 2. 为关键应用设置 CPU 保证
```bash
$ docker run --cpus=2 --cpu-shares=2048 critical-app
```
#### 3. 监控资源使用
```bash
## 配合 Prometheus + cAdvisor 监控
$ docker run -d --name cadvisor \
-v /:/rootfs:ro \
-v /var/run:/var/run:ro \
-v /sys:/sys:ro \
-v /var/lib/docker:/var/lib/docker:ro \
gcr.io/cadvisor/cadvisor
```
---

View File

@@ -1,221 +0,0 @@
## 12.4 联合文件系统
联合文件系统 (UnionFS) Docker 镜像分层存储的基础它允许将多个目录挂载为同一个虚拟文件系统
### 12.4.1 什么是联合文件系统
联合文件系统 (UnionFS) 是一种 **分层轻量级** 的文件系统它将多个目录 联合 挂载到同一个虚拟目录形成一个统一的文件系统视图
> **核心思想**将多个只读层叠加最上层可写形成完整的文件系统
```mermaid
flowchart TD
ContainerFS["容器看到的文件系统<br/>/bin /etc /lib /usr /var /app /data"]
UnionFS["UnionFS 联合挂载"]
ContainerLayer["容器层 (读写) : /app/data/log.txt (新写入)"]
ImageLayer3["镜像层3 (只读) : /app/app.py"]
ImageLayer2["镜像层2 (只读) : /usr/local/bin/python"]
ImageLayer1["镜像层1 (只读) : /bin /etc /lib (基础系统)"]
ContainerFS --> UnionFS
UnionFS --> ContainerLayer --> ImageLayer3 --> ImageLayer2 --> ImageLayer1
```
---
### 12.4.2 为什么 Docker 使用联合文件系统
Docker 选择联合文件系统作为其存储驱动主要基于以下几个核心优势
#### 1. 镜像分层复用
```mermaid
flowchart TD
Nginx["nginx:alpine"] --> Alpine["alpine:3.19 (共享基础层)"]
MyApp["myapp:latest"] --> Alpine
```
多个镜像共享相同的底层节省磁盘空间
#### 2. 快速构建
每个 Dockerfile 指令创建一层只有变化的层需要重建
```docker
FROM node:20 # 层1基础镜像
COPY package.json ./ # 层2依赖定义
RUN npm install # 层3安装依赖
COPY . . # 层4应用代码
```
代码变化时只需重建层 4 1-3 使用缓存
#### 3. 容器启动快
容器启动时不需要复制镜像只需
1. 在镜像层上创建一个薄的可写层
2. 联合挂载所有层
---
### 12.4.3 Copy-on-Write (写时复制)
当容器修改只读层中的文件时
```mermaid
flowchart LR
subgraph Before ["修改前"]
direction TB
B_C["容器层 (空)"]
B_I["镜像层<br/>/etc/nginx.conf"]
end
subgraph After ["修改后"]
direction TB
A_C["容器层<br/>/etc/nginx.conf ← 复制到容器层后修改"]
A_I["镜像层<br/>/etc/nginx.conf (原文件仍在,但被遮蔽)"]
end
B_C --- B_I
A_C --- A_I
```
**流程**
1. 从只读层读取文件
2. 复制到容器的可写层
3. 在可写层中修改
4. 后续读取使用可写层的版本
---
### 12.4.4 Docker 支持的存储驱动
Docker 可使用多种联合文件系统实现
| 存储驱动 | 说明 | 推荐程度 |
|---------|------|---------|
| **overlay2**| 现代 Linux 默认驱动性能优秀 | **推荐** |
| **aufs** | 早期默认兼容性好 | 遗留系统 |
| **btrfs** | 使用 Btrfs 子卷 | 特定场景 |
| **zfs** | 使用 ZFS 数据集 | 特定场景 |
| **devicemapper** | 块设备级存储 | 遗留系统 |
| **vfs** | 不使用 CoW每层完整复制 | 仅测试 |
#### 各发行版推荐
| Linux 发行版 | 推荐存储驱动 |
|-------------|-------------|
| Ubuntu 16.04+ | overlay2 |
| Debian Stretch+ | overlay2 |
| CentOS 7+ | overlay2 |
| RHEL 8+ | overlay2 |
| Fedora | overlay2 |
#### 查看当前存储驱动
```bash
$ docker info | grep "Storage Driver"
Storage Driver: overlay2
```
---
### 12.4.5 overlay2 工作原理
overlay2 是目前最推荐的存储驱动
```mermaid
flowchart TD
Merged["merged (合并视图)<br/>容器看到的完整文件系统"]
OverlayFS["OverlayFS"]
Upper["upper<br/>(容器层)<br/>读写"]
Lower2["lower2<br/>(镜像层)<br/>只读"]
Lower1["lower1<br/>(基础层)<br/>只读"]
Merged --> OverlayFS
OverlayFS --> Upper
OverlayFS --> Lower2
OverlayFS --> Lower1
```
- **lowerdir**只读的镜像层 (可以有多个)
- **upperdir**可写的容器层
- **workdir**OverlayFS 的工作目录
- **merged**联合挂载后的视图
#### 文件操作行为
| 操作 | 行为 |
|------|------|
| **读取** | 从上到下查找第一个匹配的文件 |
| **创建** | upper 层创建 |
| **修改** | 如果在 lower 先复制到 upper 层再修改 |
| **删除** | upper 层创建 whiteout 文件标记删除 |
---
### 12.4.6 查看镜像层
```bash
## 查看镜像的层信息
$ docker history nginx:alpine
IMAGE CREATED CREATED BY SIZE
a6eb2a334a9f 2 weeks ago CMD ["nginx" "-g" "daemon off;"] 0B
<missing> 2 weeks ago STOPSIGNAL SIGQUIT 0B
<missing> 2 weeks ago EXPOSE map[80/tcp:{}] 0B
<missing> 2 weeks ago ENTRYPOINT ["/docker-entrypoint.sh"] 0B
<missing> 2 weeks ago COPY 30-tune-worker-processes.sh /docker-ent… 4.62kB
...
## 查看层的存储位置
$ docker inspect nginx:alpine --format '{{json .GraphDriver.Data}}' | jq
{
"LowerDir": "/var/lib/docker/overlay2/.../diff:/var/lib/docker/overlay2/.../diff",
"MergedDir": "/var/lib/docker/overlay2/.../merged",
"UpperDir": "/var/lib/docker/overlay2/.../diff",
"WorkDir": "/var/lib/docker/overlay2/.../work"
}
```
---
### 12.4.7 最佳实践
为了构建高效轻量的镜像我们在使用联合文件系统时应注意以下几点
#### 1. 减少镜像层数
```docker
## ❌ 每条命令创建一层
RUN apt-get update
RUN apt-get install -y nginx
RUN rm -rf /var/lib/apt/lists/*
## ✅ 合并为一层
RUN apt-get update && \
apt-get install -y nginx && \
rm -rf /var/lib/apt/lists/*
```
#### 2. 避免在容器中写入大量数据
容器层的写入性能低于直接写入大量数据应使用
- 数据卷 (Volume)
- 绑定挂载 (Bind Mount)
#### 3. 使用 .dockerignore
排除不需要的文件可以
- 减小构建上下文
- 避免创建不必要的层
---

View File

@@ -1,44 +0,0 @@
## 本章小结
| Namespace | 隔离内容 | 一句话说明 |
|-----------|---------|-----------|
| PID | 进程 ID | 容器有自己的进程树 |
| NET | 网络 | 容器有自己的 IP 和端口 |
| MNT | 文件系统 | 容器有自己的根目录 |
| UTS | 主机名 | 容器有自己的 hostname |
| IPC | 进程间通信 | 容器间 IPC 隔离 |
| USER | 用户 ID | 容器 root 宿主机 root |
### 12.7.1 延伸阅读
- [控制组 (Cgroups)](12.3_cgroups.md)资源限制机制
- [联合文件系统](12.4_ufs.md)分层存储的实现
- [安全](../18_security/README.md)容器安全实践
- [Linux Namespace 官方文档](https://man7.org/linux/man-pages/man7/namespaces.7.html)
| 资源 | 限制参数 | 示例 |
|------|---------|------|
| **内存** | `-m` | `-m 512m` |
| **CPU 核心数** | `--cpus` | `--cpus=1.5` |
| **CPU 绑定** | `--cpuset-cpus` | `--cpuset-cpus="0,1"` |
| **磁盘 I/O** | `--device-write-bps` | `--device-write-bps /dev/sda:10mb` |
| **进程数** | `--pids-limit` | `--pids-limit=100` |
### 12.7.2 延伸阅读
- [命名空间](12.2_namespace.md)资源隔离
- [安全](../18_security/README.md)容器安全概述
- [Docker Stats](../05_container/README.md)监控容器资源
| 概念 | 说明 |
|------|------|
| **UnionFS** | 将多层目录联合挂载为一个文件系统 |
| **Copy-on-Write** | 写时复制修改时才复制到可写层 |
| **overlay2** | Docker 默认推荐的存储驱动 |
| **分层好处** | 镜像复用快速构建快速启动 |
### 12.7.3 延伸阅读
- [镜像](../02_basic_concept/2.1_image.md)理解镜像分层
- [容器](../02_basic_concept/2.2_container.md)容器存储层
- [构建镜像](../04_image/4.5_build.md)Dockerfile 层的创建

View File

@@ -1,93 +0,0 @@
## 13.1 简介
如图 13-1 所示Kubernetes 使用舵手图标作为项目标识
![Kubernetes 标识](./_images/kubernetes_logo.png)
13-1 Kubernetes 项目标识
### 13.1.1 什么是 Kubernetes
Kubernetes (常简称为 K8s) Google 开源的容器编排引擎如果说 Docker 解决了 如何打包和运送集装箱 的问题那么 Kubernetes 解决的就是 如何管理海量集装箱的调度运行和维护 的问题
它不仅仅是一个编排系统更是一个 **云原生应用操作系统**
> **名字由来**Kubernetes 在希腊语中意为 舵手 飞行员K8s 是因为 k s 之间有 8 个字母
---
### 13.1.2 为什么需要 Kubernetes
当我们在单机运行几个容器时Docker Compose 就足够了但在生产环境中我们需要面对
- **多主机调度**容器应该运行在哪台机器上
- **自动恢复**容器崩溃了怎么办节点挂了怎么办
- **服务发现**容器 IP 变了其他服务怎么找到它
- **负载均衡**流量大了如何分发给多个副本
- **滚动更新**如何不中断服务升级应用
Kubernetes 完美解决了这些问题
---
### 13.1.3 核心概念
#### Pod (豆荚)
Kubernetes 的最小调度单位一个 Pod 可以包含一个或多个紧密协作的容器 (共享网络和存储)就像豌豆荚里的豌豆一样
#### Node (节点)
运行 Pod 的物理机或虚拟机
#### Deployment (部署)
定义应用的期望状态 (需要 3 个副本镜像版本为 v1)K8s 会持续确保当前状态符合期望状态
#### Service (服务)
定义一组 Pod 的访问策略提供稳定的 Cluster IP DNS 名称负责负载均衡
#### Namespace (命名空间)
用于多租户资源隔离
---
### 13.1.4 Docker 用户如何过渡
如果你已经熟悉 Docker学习 K8s 会很容易
| Docker 概念 | Kubernetes 概念 | 说明 |
|------------|----------------|------|
| Container | Pod | K8s 增加了一层 Pod 包装 |
| Volume | PersistentVolume | K8s 的存储更加抽象和强大 |
| Network | Service/Ingress| K8s 的网络模型更扁平 |
| Compose | Deployment + Service | 声明式配置的理念是一致的 |
---
### 13.1.5 架构
Kubernetes 也是 C/S 架构 **Control Plane (控制平面)** **Worker Node (工作节点)** 组成
- **Control Plane**负责决策 (API ServerSchedulerController Manageretcd)
- **Worker Node**负责干活 (KubeletKube-proxyContainer Runtime)
---
### 13.1.6 学习建议
Kubernetes 的学习曲线较陡峭建议的学习路径
1. **理解基本概念**PodDeploymentService
2. **动手实践**使用 Minikube Kind 在本地搭建集群
3. **部署应用**编写 YAML 部署一个无状态应用
4. **深入原理**网络模型存储机制调度算法
---
### 13.1.7 延伸阅读
- [Minikube 安装](../14_kubernetes_setup/README.md)本地体验 K8s
- [Kubernetes 官网](https://kubernetes.io/):官方文档

View File

@@ -1,208 +0,0 @@
## 13.2 基本概念
如图 12-2 所示Kubernetes 由控制平面与工作节点构成
![Kubernetes 基本概念示意图](./_images/kubernetes_design.jpg)
12-2 Kubernetes 基本概念示意图
* 节点 (`Node`)一个节点是一个运行 Kubernetes 中的主机
* 容器组 (`Pod`)一个 Pod 对应于由若干容器组成的一个容器组同个组内的容器共享一个存储卷 (volume)
* 容器组生命周期 (`pod-states`)包含所有容器状态集合包括容器组状态类型容器组生命周期事件重启策略以及 replication controllers
* Replication Controllers主要负责指定数量的 pod 在同一时间一起运行
* 服务 (`services`)一个 Kubernetes 服务是容器组逻辑的高级抽象同时也对外提供访问容器组的策略
* (`volumes`)一个卷就是一个目录容器对其有访问权限
* 标签 (`labels`)标签是用来连接一组对象的比如容器组标签可以被用来组织和选择子对象
* 接口权限 (`accessing_the_api`)端口IP 地址和代理的防火墙规则
* web 界面 (`ux`)用户可以通过 web 界面操作 Kubernetes
* 命令行操作 (`cli`)`kubectl` 命令
### 13.2.1 节点
`Kubernetes` 节点是实际工作的点节点可以是虚拟机或者物理机器依赖于一个集群环境每个节点都有一些必要的服务以运行容器组并且它们都可以通过主节点来管理必要服务包括 Dockerkubelet 和代理服务
#### 容器状态
容器状态用来描述节点的当前状态现在其中包含三个信息
##### 主机 IP
主机 IP 需要云平台来查询`Kubernetes` 把它作为状态的一部分来保存如果 `Kubernetes` 没有运行在云平台上节点 ID 就是必需的IP 地址可以变化并且可以包含多种类型的 IP 地址如公共 IP私有 IP动态 IPipv6 等等
##### 节点周期
通常来说节点有 `Pending``Running``Terminated` 三个周期如果 Kubernetes 发现了一个节点并且其可用那么 Kubernetes 就把它标记为 `Pending`然后在某个时刻Kubernetes 将会标记其为 `Running`节点的结束周期称为 `Terminated`一个已经 `Terminated` 的节点不会接受和调度任何请求并且已经在其上运行的容器组也会删除
##### 节点状态
节点的状态主要是用来描述处于 `Running` 的节点当前可用的有 `NodeReachable` `NodeReady`以后可能会增加其他状态`NodeReachable` 表示集群可达`NodeReady` 表示 kubelet 返回 Status Ok 并且 HTTP 状态检查健康
#### 节点管理
节点并非 Kubernetes 创建而是由云平台创建或者就是物理机器虚拟机 Kubernetes 节点仅仅是一条记录节点创建之后Kubernetes 会检查其是否可用 Kubernetes 节点用如下结构保存
```json
{
"id": "10.1.2.3",
"kind": "Minion",
"apiVersion": "v1beta1",
"resources": {
"capacity": {
"cpu": 1000,
"memory": 1073741824
},
},
"labels": {
"name": "my-first-k8s-node",
},
}
```
Kubernetes 校验节点可用依赖于 ID在当前的版本中有两个接口可以用来管理节点节点控制和 Kube 管理
#### 节点控制
Kubernetes 主节点中节点控制器是用来管理节点的组件主要包含
* 集群范围内节点同步
* 单节点生命周期管理
节点控制有一个同步轮询主要监听所有云平台的虚拟实例会根据节点状态创建和删除可以通过 `--node_sync_period` 标志来控制该轮询如果一个实例已经创建节点控制将会为其创建一个结构同样的如果一个节点被删除节点控制也会删除该结构 Kubernetes 启动时可用通过 `--machines` 标记来显示指定节点同样可以使用 `kubectl` 来一条一条的添加节点两者是相同的通过设置 `--sync_nodes=false` 标记来禁止集群之间的节点同步你也可以使用 api/kubectl 命令行来增删节点
### 13.2.2 容器组
Kubernetes 使用的最小单位是容器组容器组是创建调度管理的最小单位一个容器组使用相同的 Docker 容器并共享卷 (挂载点)一个容器组是一个特定应用的打包集合包含一个或多个容器
和运行的容器类似一个容器组被认为只有很短的运行周期容器组被调度到一组节点运行直到容器的生命周期结束或者其被删除如果节点死掉运行在其上的容器组将会被删除而不是重新调度(也许在将来的版本中会添加容器组的移动)
#### 容器组设计的初衷
容器组 (Pod) 的设计主要是为了解决应用间的紧密协作和资源共享问题
#### 资源共享和通信
容器组主要是为了数据共享和它们之间的通信
在一个容器组中容器都使用相同的网络地址和端口可以通过本地网络来相互通信每个容器组都有独立的 IP可用通过网络来和其他物理主机或者容器通信
容器组有一组存储卷 (挂载点)主要是为了让容器在重启之后可以不丢失数据
#### 容器组管理
容器组是一个应用管理和部署的高层次抽象同时也是一组容器的接口容器组是部署水平放缩的最小单位
#### 容器组的使用
容器组可以通过组合来构建复杂的应用其本来的意义包含
* 内容管理文件和数据加载以及本地缓存管理等
* 日志和检查点备份压缩快照等
* 监听数据变化跟踪日志日志和监控代理消息发布等
* 代理网桥
* 控制器管理配置以及更新
#### 替代方案
为什么不在一个单一的容器里运行多个程序
* 1. 透明化为了使容器组中的容器保持一致的基础设施和服务比如进程管理和资源监控这样设计是为了用户的便利性
* 2. 解耦软件之间的依赖每个容器都可能重新构建和发布Kubernetes 必须支持热发布和热更新 (将来)
* 3. 方便使用用户不必运行独立的程序管理也不用担心每个应用程序的退出状态
* 4. 高效考虑到基础设施有更多的职责容器必须要轻量化
#### 容器组的生命状态
包括若干状态值`pending``running``succeeded``failed`
##### pending
容器组已经被节点接受但有一个或多个容器还没有运行起来这将包含某些节点正在下载镜像的时间这种情形会依赖于网络情况
##### running
容器组已经被调度到节点并且所有的容器都已经启动至少有一个容器处于运行状态 (或者处于重启状态)
##### succeeded
所有的容器都正常退出
##### failed
容器组中所有容器都意外中断了
#### 容器组生命周期
通常来说如果容器组被创建了就不会自动销毁除非被某种行为触发而触发此种情况可能是人为或者复制控制器所为唯一例外的是容器组由 succeeded 状态成功退出或者在一定时间内重试多次依然失败
如果某个节点死掉或者不能连接那么节点控制器将会标记其上的容器组的状态为 `failed`
举例如下
* 容器组状态 `running` 1 容器容器正常退出
* 记录完成事件
* 如果重启策略为
* 始终重启容器容器组保持 `running`
* 失败时容器组变为 `succeeded`
* 从不容器组变为 `succeeded`
* 容器组状态 `running` 1 容器容器异常退出
* 记录失败事件
* 如果重启策略为
* 始终重启容器容器组保持 `running`
* 失败时重启容器容器组保持 `running`
* 从不容器组变为 `failed`
* 容器组状态 `running` 2 容器 1 容器异常退出
* 记录失败事件
* 如果重启策略为
* 始终重启容器容器组保持 `running`
* 失败时重启容器容器组保持 `running`
* 从不容器组保持 `running`
* 当有 2 容器退出
* 记录失败事件
* 如果重启策略为
* 始终重启容器容器组保持 `running`
* 失败时重启容器容器组保持 `running`
* 从不容器组变为 `failed`
* 容器组状态 `running`容器内存不足
* 标记容器错误中断
* 记录内存不足事件
* 如果重启策略为
* 始终重启容器容器组保持 `running`
* 失败时重启容器容器组保持 `running`
* 从不记录错误事件容器组变为 `failed`
* 容器组状态 `running`一块磁盘死掉
* 杀死所有容器
* 记录事件
* 容器组变为 `failed`
* 如果容器组运行在一个控制器下容器组将会在其他地方重新创建
* 容器组状态 `running`对应的节点段溢出
* 节点控制器等到超时
* 节点控制器标记容器组 `failed`
* 如果容器组运行在一个控制器下容器组将会在其他地方重新创建
### 13.2.3 Replication Controllers
> Replication Controller (RC) 是早期的控制器类型现代 Kubernetes 更推荐使用 ReplicaSet/Deployment
### 13.2.4 服务
> 服务 (Service) 定义一组 Pod 的逻辑集合和访问它们的策略
### 13.2.5
> (Volume) 包含可被 Pod 中容器访问的数据的目录
### 13.2.6 标签
> 标签 (Label) 是附加到对象 ( Pods) 上的键值对用于组织和选择对象子集
### 13.2.7 接口权限
> 接口权限通过认证授权和准入控制来保护 Kubernetes API 的访问
### 13.2.8 web 界面
> Kubernetes Dashboard 是一个基于 Web 的用户界面用于管理集群
### 13.2.9 命令行操作
> kubectl Kubernetes 的命令行工具用于与集群进行交互

Some files were not shown because too many files have changed in this diff Show More