Compare commits
85 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d6fff9a3f7 | ||
|
|
fc276d3b4f | ||
|
|
40ded62baa | ||
|
|
89b0dc4425 | ||
|
|
330e084e00 | ||
|
|
e62b203f1a | ||
|
|
cb6bf74a2e | ||
|
|
a980e34276 | ||
|
|
3d33e00802 | ||
|
|
1a820c8c8b | ||
|
|
585b364574 | ||
|
|
a0a5de7f11 | ||
|
|
83ba6b7b47 | ||
|
|
53f20dede7 | ||
|
|
48c8b50cf7 | ||
|
|
70ef2cba58 | ||
|
|
f5cfa4140a | ||
|
|
d174cf327c | ||
|
|
6a9ce44c5a | ||
|
|
c09f66da55 | ||
|
|
635e05ad34 | ||
|
|
6f22d0f5f0 | ||
|
|
17517e26b7 | ||
|
|
9fcaff6673 | ||
|
|
a096947382 | ||
|
|
be09a95d0d | ||
|
|
3af007b176 | ||
|
|
551dcfd2cb | ||
|
|
0d2654fbf2 | ||
|
|
3894ba56bc | ||
|
|
0dd0d036a2 | ||
|
|
a9c4189c0c | ||
|
|
56deeaee0f | ||
|
|
0ff67cc893 | ||
|
|
70cab9e92d | ||
|
|
67547c0c3e | ||
|
|
0d012c2d27 | ||
|
|
1eddef954e | ||
|
|
6baf2c68f2 | ||
|
|
fd28d966ca | ||
|
|
ba0c388384 | ||
|
|
fc4f1ce2d0 | ||
|
|
276eb356a2 | ||
|
|
246fc35c8e | ||
|
|
745b7c812a | ||
|
|
ad3b86d727 | ||
|
|
16e884da51 | ||
|
|
5bd2c37c54 | ||
|
|
19aaad591c | ||
|
|
bbfc9d34ff | ||
|
|
ecab788013 | ||
|
|
dd449bc84f | ||
|
|
d6e0838807 | ||
|
|
c44cbfcd62 | ||
|
|
c6e2ab8a4a | ||
|
|
669da56662 | ||
|
|
4941ef171c | ||
|
|
572266b2f4 | ||
|
|
4ca47b0ea1 | ||
|
|
24d26c0797 | ||
|
|
e57704271d | ||
|
|
92ea9623b2 | ||
|
|
b9ac198f19 | ||
|
|
185da0cf88 | ||
|
|
8965f084eb | ||
|
|
175aaae48a | ||
|
|
33af380be1 | ||
|
|
2ab40eacc0 | ||
|
|
6aa7a51fef | ||
|
|
79ac9c639a | ||
|
|
ad68b2d973 | ||
|
|
5ae315c9dd | ||
|
|
47cfc173a6 | ||
|
|
063c3f1381 | ||
|
|
f68795f34c | ||
|
|
acc74f17bb | ||
|
|
4fa2fd8543 | ||
|
|
95572d3d47 | ||
|
|
e58bd39de5 | ||
|
|
aa589406b9 | ||
|
|
0835f8467a | ||
|
|
bae82e993a | ||
|
|
b44c9acd6c | ||
|
|
63377d0431 | ||
|
|
e669ee0fe8 |
2
.gitattributes
vendored
@@ -1,4 +1,4 @@
|
||||
* text=auto
|
||||
* text=auto eol=lf
|
||||
|
||||
*.sh text eol=lf
|
||||
|
||||
|
||||
42
.github/CODEOWNERS
vendored
@@ -2,28 +2,28 @@
|
||||
/.github/* @khs1994
|
||||
/.travis/* @khs1994
|
||||
/.vuepress/* @khs1994
|
||||
/advanced_network/* @yeasy @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
|
||||
/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
|
||||
|
||||
8
.github/ISSUE_TEMPLATE/Bug_report.md
vendored
@@ -7,6 +7,7 @@ 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-->
|
||||
|
||||
@@ -21,6 +22,7 @@ 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-->
|
||||
|
||||
@@ -29,13 +31,9 @@ about: Create a report to help us improve
|
||||
* [x] 1.13.0 or Before
|
||||
|
||||
### Problem Description
|
||||
|
||||
<!--描述你的问题,请贴出操作步骤,终端报错截图或文字信息-->
|
||||
<!--describe problem with detailed steps and logs-->
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!--提交问题之前请点击预览标签,符合要求之后再提交问题-->
|
||||
|
||||
8
.github/ISSUE_TEMPLATE/Custom.md
vendored
@@ -7,6 +7,7 @@ about: Create a issue about Docker
|
||||
* [ ] Have u googled the problem? If no, pls do that first!
|
||||
|
||||
### Environment
|
||||
|
||||
<!--请提供环境信息,包括操作系统版本等,保留你的操作系统,其他选项删除-->
|
||||
<!--Provides env info like OS version-->
|
||||
|
||||
@@ -21,6 +22,7 @@ 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-->
|
||||
|
||||
@@ -29,13 +31,9 @@ about: Create a issue about Docker
|
||||
* [x] 1.13.0 or Before
|
||||
|
||||
### Problem Description
|
||||
|
||||
<!--描述你的问题,请贴出操作步骤,终端报错截图或文字信息-->
|
||||
<!--describe problem with detailed steps and logs-->
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!--提交问题之前请点击预览标签,符合要求之后再提交问题-->
|
||||
|
||||
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -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)**
|
||||
|
||||
27
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
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:
|
||||
- "*"
|
||||
2
.github/workflows/check-link.yml
vendored
@@ -8,7 +8,7 @@ jobs:
|
||||
name: check-link
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.1
|
||||
- uses: actions/checkout@v6.0.2
|
||||
# search Issues :-(
|
||||
- run: |
|
||||
docker run -i --rm \
|
||||
|
||||
21
.github/workflows/ci.yaml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.1
|
||||
- uses: actions/checkout@v6.0.2
|
||||
- name: Build Gitbook
|
||||
uses: docker://yeasy/docker_practice
|
||||
with:
|
||||
@@ -81,17 +81,16 @@ jobs:
|
||||
echo "vuepress.mirror.docker-practice.com" > .vuepress/dist/CNAME
|
||||
|
||||
cp -r _images .vuepress/dist
|
||||
mkdir -p .vuepress/dist/appendix
|
||||
cp -r appendix/_images .vuepress/dist/appendix
|
||||
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
|
||||
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
|
||||
|
||||
echo "include: [_images]" > .vuepress/dist/_config.yml
|
||||
- name: Upload Vuepress dist
|
||||
|
||||
27
.github/workflows/dependabot-automerge.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
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}}
|
||||
10
.gitignore
vendored
@@ -3,6 +3,7 @@
|
||||
*.tmp
|
||||
.idea/
|
||||
_book/
|
||||
format_report.txt
|
||||
*.swp
|
||||
*.edx
|
||||
.DS_Store
|
||||
@@ -15,3 +16,12 @@ docker-compose.override.yml
|
||||
# Editor configs
|
||||
.obsidian/
|
||||
.vscode/
|
||||
|
||||
.agent/
|
||||
__pycache__/
|
||||
|
||||
# Check scripts
|
||||
check*.py
|
||||
find*.py
|
||||
fix*.py
|
||||
format*.py
|
||||
|
||||
@@ -106,7 +106,7 @@ module.exports = config({
|
||||
},
|
||||
{
|
||||
text: '安装 Docker',
|
||||
link: '/install/',
|
||||
link: '/03_install/',
|
||||
},
|
||||
{
|
||||
text: 'Docker 入门',
|
||||
@@ -114,23 +114,23 @@ module.exports = config({
|
||||
},
|
||||
{
|
||||
text: 'Docker 实战',
|
||||
link: '/cases/os/'
|
||||
link: '/15_cases/os/'
|
||||
},
|
||||
{
|
||||
text: 'CI/CD',
|
||||
link: '/cases/ci/'
|
||||
link: '/15_cases/ci/'
|
||||
},
|
||||
{
|
||||
text: 'Compose',
|
||||
link: '/compose/',
|
||||
link: '/10_compose/',
|
||||
},
|
||||
{
|
||||
text: 'Kubernetes',
|
||||
link: '/kubernetes/',
|
||||
link: '/12_orchestration/kubernetes/',
|
||||
},
|
||||
{
|
||||
text: "云计算",
|
||||
link: "/cloud/",
|
||||
link: "/13_ecosystem/cloud/",
|
||||
},
|
||||
// {
|
||||
// text: 'GitHub',
|
||||
@@ -152,7 +152,8 @@ module.exports = config({
|
||||
// }]
|
||||
// }
|
||||
],
|
||||
sidebar: {
|
||||
sidebar: "auto",
|
||||
legacySidebar: {
|
||||
'/cloud/': [
|
||||
'intro',
|
||||
'tencentCloud',
|
||||
@@ -295,8 +296,8 @@ module.exports = config({
|
||||
'image/dockerfile/shell',
|
||||
'image/dockerfile/onbuild',
|
||||
'image/dockerfile/references',
|
||||
'image/multistage-builds/',
|
||||
'image/multistage-builds/laravel',
|
||||
'image/dockerfile/7.17_multistage_builds.md',
|
||||
'image/dockerfile/7.18_multistage_builds_laravel.md',
|
||||
'image/manifest',
|
||||
]
|
||||
}, {
|
||||
|
||||
3
.zhlintignore
Normal file
@@ -0,0 +1,3 @@
|
||||
node_modules/
|
||||
.vuepress/
|
||||
.git/
|
||||
@@ -1,8 +1,8 @@
|
||||
# 快速上手 (5分钟)
|
||||
## 1.1 快速上手
|
||||
|
||||
本节将通过一个简单的 Web 应用例子,带你快速体验 Docker 的核心流程:构建镜像、运行容器。
|
||||
|
||||
## 1. 准备代码
|
||||
### 1.1.1 准备代码
|
||||
|
||||
创建一个名为 `hello-docker` 的文件夹,并在其中创建一个 `index.html` 文件:
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<h1>Hello, Docker!</h1>
|
||||
```
|
||||
|
||||
## 2. 编写 Dockerfile
|
||||
### 1.1.2 编写 Dockerfile
|
||||
|
||||
在同级目录下创建一个名为 `Dockerfile` (无后缀) 的文件:
|
||||
|
||||
@@ -19,7 +19,7 @@ FROM nginx:alpine
|
||||
COPY index.html /usr/share/nginx/html/index.html
|
||||
```
|
||||
|
||||
## 3. 构建镜像
|
||||
### 1.1.3 构建镜像
|
||||
|
||||
打开终端,进入该目录,执行构建命令:
|
||||
|
||||
@@ -27,11 +27,11 @@ COPY index.html /usr/share/nginx/html/index.html
|
||||
$ docker build -t my-hello-world .
|
||||
```
|
||||
|
||||
* `docker build`: 构建命令
|
||||
* `-t my-hello-world`: 给镜像起个名字(标签)
|
||||
* `.`: 指定上下文路径为当前目录
|
||||
* `docker build`:构建命令
|
||||
* `-t my-hello-world`:给镜像起个名字 (标签)
|
||||
* `.`:指定上下文路径为当前目录
|
||||
|
||||
## 4. 运行容器
|
||||
### 1.1.4 运行容器
|
||||
|
||||
使用刚才构建的镜像启动一个容器:
|
||||
|
||||
@@ -39,27 +39,30 @@ $ docker build -t my-hello-world .
|
||||
$ docker run -d -p 8080:80 my-hello-world
|
||||
```
|
||||
|
||||
* `docker run`: 运行命令
|
||||
* `-d`: 后台运行
|
||||
* `-p 8080:80`: 将宿主机的 8080 端口映射到容器的 80 端口
|
||||
* `docker run`:运行命令
|
||||
* `-d`:后台运行
|
||||
* `-p 8080:80`:将宿主机的 8080 端口映射到容器的 80 端口
|
||||
|
||||
## 5. 访问测试
|
||||
### 1.1.5 访问测试
|
||||
|
||||
打开浏览器访问 [http://localhost:8080](http://localhost:8080),你应该能看到 "Hello, Docker!"。
|
||||
打开浏览器访问 [http://localhost:8080](http://localhost:8080),你应该能看到 “Hello, Docker!”。
|
||||
|
||||
## 6. 清理
|
||||
### 1.1.6 清理
|
||||
|
||||
停止并删除容器:
|
||||
|
||||
```bash
|
||||
# 查看正在运行的容器 ID
|
||||
## 查看正在运行的容器 ID
|
||||
|
||||
$ docker ps
|
||||
|
||||
# 停止容器
|
||||
## 停止容器
|
||||
|
||||
$ docker stop <CONTAINER_ID>
|
||||
|
||||
# 删除容器
|
||||
## 删除容器
|
||||
|
||||
$ docker rm <CONTAINER_ID>
|
||||
```
|
||||
|
||||
恭喜!你已经完成了第一次 Docker 实战。接下来请阅读 [Docker 核心概念](../02_basic_concept/README.md) 做深入了解。
|
||||
恭喜!你已经完成了第一次 Docker 实战。接下来请阅读 [Docker 核心概念](../02_basic_concept/README.md)做深入了解。
|
||||
120
01_introduction/1.2_what.md
Normal file
@@ -0,0 +1,120 @@
|
||||
## 1.2 什么是 Docker
|
||||
|
||||
Docker 是彻底改变了软件开发和交付方式的革命性技术。本节将从核心概念、与传统虚拟机的对比、技术基础以及历史生态等多个维度,带你深入理解什么是 Docker。
|
||||
|
||||
### 1.2.1 一句话理解 Docker
|
||||
|
||||
> **Docker 是一种轻量级的虚拟化技术,它让应用程序及其依赖环境可以被打包成一个标准化的单元,在任何地方都能一致地运行。** 如果用一个生活中的类比:**Docker 之于软件,就像集装箱之于货物**。
|
||||
|
||||
在集装箱发明之前,货物的运输是一件麻烦的事情——不同的货物需要不同的包装、不同的装卸方式,换一种运输工具就要重新装卸。集装箱的出现改变了这一切:无论里面装的是什么,集装箱的外形是标准的,可以用同样的方式装卸、堆放和运输。
|
||||
|
||||
Docker 做的事情类似:无论你的应用是用 Python、Java、Node.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 时会问:**“这不就是虚拟机吗?”** 答案是:**不是,而且差别很大。**
|
||||
|
||||
#### 传统虚拟机
|
||||
|
||||
传统虚拟机技术是虚拟出一套完整的硬件,在其上运行一个完整的操作系统,再在该系统上运行应用:
|
||||
|
||||

|
||||
|
||||
#### Docker 容器
|
||||
|
||||
而 Docker 容器内的应用直接运行于宿主的内核,容器内没有自己的内核,也没有进行硬件虚拟:
|
||||
|
||||

|
||||
|
||||
#### 关键区别
|
||||
|
||||
| 特性 | 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** (2014,v0.7):Docker 自研的容器运行时
|
||||
- **runC** (2015,v1.11):捐献给 OCI 的标准容器运行时
|
||||
- **containerd**:高级容器运行时,管理容器生命周期
|
||||
|
||||

|
||||
|
||||
> `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 公司改名为 Docker,Inc。
|
||||
- **2015 年**:成立[开放容器联盟 (OCI)](https://opencontainers.org/),推动容器标准化
|
||||
- **至今**:[GitHub 项目](https://github.com/moby/moby)超过 7 万星标
|
||||
|
||||
Docker 的成功推动了整个容器生态的发展,催生了 Kubernetes、Podman 等众多相关项目。笔者认为,Docker 最大的贡献不仅是技术本身,更是它 **让容器技术从系统管理员的工具变成了每个开发者都能使用的标准工具**。
|
||||
206
01_introduction/1.3_why.md
Normal file
@@ -0,0 +1,206 @@
|
||||
## 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 可以在几乎任何平台上运行:
|
||||
|
||||
- ✅ 本地开发机 (macOS、Windows、Linux)
|
||||
- ✅ 公有云 (AWS、Azure、GCP、阿里云、腾讯云)
|
||||
- ✅ 私有云和自建数据中心
|
||||
- ✅ 边缘设备和 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 与传统虚拟机的对比总结
|
||||
|
||||
关于容器与虚拟机的详细特性对比,请参阅 [1.2.3 Docker vs 虚拟机](1.2_what.md) 中的对比表。总结来说:
|
||||
|
||||
- **性能差异**:虚拟机通常有 5-20% 的性能损耗,而容器接近原生性能。
|
||||
- **最佳场景**:Docker 容器适合微服务、CI/CD、开发环境;虚拟机适合多租户、高安全需求场景。
|
||||
@@ -1,17 +1,23 @@
|
||||
# 简介
|
||||
# 第一章 Docker 简介
|
||||
|
||||
本章将带领你进入 **Docker** 的世界。
|
||||
|
||||
> **版本提示**:本书内容及示例基于 **Docker Engine v29.x** 及以上版本。值得注意的是,自 Docker Engine v29 起,官方在全新安装场景下 **默认启用了 `containerd image store` 作为镜像存储后端**(取代了传统的经典存储引擎如 overlay2 graph driver)。这项底层革新极大增强了 Docker 对多架构镜像(Multi-platform)、以及软件供应链安全元数据(Attestations, SBOM, Provenance)的本地支持原生性。
|
||||
|
||||
## 本章内容
|
||||
|
||||
* [什么是 Docker](what.md)
|
||||
* 介绍 Docker 的起源、发展历程以及其背后的核心技术(Cgroups, Namespaces, UnionFS)。
|
||||
* [快速上手](1.1_quickstart.md)
|
||||
* 通过一个简单的 Web 应用例子,带你快速体验 Docker 的核心流程:构建镜像、运行容器。
|
||||
|
||||
* [什么是 Docker](1.2_what.md)
|
||||
* 介绍 Docker 的起源、发展历程以及其背后的核心技术 (Cgroups,Namespaces,UnionFS,以及 `containerd` 引擎的演进)。
|
||||
* 了解 Docker 是如何改变软件交付方式的。
|
||||
|
||||
* [为什么要用 Docker](why.md)
|
||||
* [为什么要用 Docker](1.3_why.md)
|
||||
* 对比传统虚拟机技术,阐述 Docker 在启动速度、资源利用率、交付效率等方面的巨大优势。
|
||||
* 探讨 Docker 在 DevOps、微服务架构中的关键作用。
|
||||
|
||||
|
||||
## 学习目标
|
||||
|
||||
通过本章的学习,你将能够:
|
||||
|
||||
11
01_introduction/summary.md
Normal file
@@ -0,0 +1,11 @@
|
||||
## 本章小结
|
||||
|
||||
- Docker 是一种轻量级虚拟化技术,核心价值是 **环境一致性**
|
||||
- 与虚拟机相比,Docker 更轻量、更快速、资源利用率更高
|
||||
- Docker 基于 Linux 内核的 Namespace、Cgroups 和 Union FS 技术
|
||||
- Docker 推动了容器技术的标准化 (OCI) 和生态发展
|
||||
|
||||
Docker 的核心价值可以用一句话概括:**让应用的开发、测试、部署保持一致,同时极大提高资源利用效率。** 笔者认为,对于现代软件开发者来说,Docker 已经不是 “要不要学” 的问题,而是 **必备技能**。无论你是前端、后端、运维还是全栈开发者,掌握 Docker 都能让你的工作更高效。
|
||||
---
|
||||
|
||||
> 📝 **发现错误或有改进建议?** 欢迎提交 [Issue](https://github.com/yeasy/docker_practice/issues) 或 [PR](https://github.com/yeasy/docker_practice/pulls)。
|
||||
@@ -1,124 +0,0 @@
|
||||
# 什么是 Docker
|
||||
|
||||
## 一句话理解 Docker
|
||||
|
||||
> **Docker 是一种轻量级的虚拟化技术,它让应用程序及其依赖环境可以被打包成一个标准化的单元,在任何地方都能一致地运行。**
|
||||
|
||||
如果用一个生活中的类比:**Docker 之于软件,就像集装箱之于货物**。
|
||||
|
||||
在集装箱发明之前,货物的运输是一件麻烦的事情——不同的货物需要不同的包装、不同的装卸方式,换一种运输工具就要重新装卸。集装箱的出现改变了这一切:无论里面装的是什么,集装箱的外形是标准的,可以用同样的方式装卸、堆放和运输。
|
||||
|
||||
Docker 做的事情类似:无论你的应用是用 Python、Java、Node.js 还是其他语言写的,无论它需要什么样的依赖库和环境,一旦被打包成 Docker 镜像,就可以用同样的方式在任何支持 Docker 的机器上运行。
|
||||
|
||||
## Docker 的核心价值
|
||||
|
||||
笔者认为,Docker 解决的是软件开发中最古老的问题之一:**"在我机器上明明能跑啊!"**
|
||||
|
||||
```
|
||||
开发环境 生产环境
|
||||
┌─────────────────┐ ┌─────────────────┐
|
||||
│ Python 3.14 │ ≠ │ Python 3.11 │
|
||||
│ Ubuntu 24.04 │ │ Ubuntu 22.04 │
|
||||
│ 特定版本的库 │ │ 不同版本的库 │
|
||||
└─────────────────┘ └─────────────────┘
|
||||
↓ ↓
|
||||
运行正常 运行失败!
|
||||
```
|
||||
|
||||
有了 Docker:
|
||||
|
||||
```
|
||||
开发环境 生产环境
|
||||
┌─────────────────┐ ┌─────────────────┐
|
||||
│ Docker 镜像 │ = │ 同一个镜像 │
|
||||
│ (包含所有依赖) │ │ (完全一致) │
|
||||
└─────────────────┘ └─────────────────┘
|
||||
↓ ↓
|
||||
运行正常 运行正常!
|
||||
```
|
||||
|
||||
## Docker vs 虚拟机
|
||||
|
||||
很多人第一次接触 Docker 时会问:**"这不就是虚拟机吗?"**
|
||||
|
||||
答案是:**不是,而且差别很大。**
|
||||
|
||||
### 传统虚拟机
|
||||
|
||||
传统虚拟机技术是虚拟出一套完整的硬件,在其上运行一个完整的操作系统,再在该系统上运行应用:
|
||||
|
||||

|
||||
|
||||
### Docker 容器
|
||||
|
||||
而 Docker 容器内的应用直接运行于宿主的内核,容器内没有自己的内核,也没有进行硬件虚拟:
|
||||
|
||||

|
||||
|
||||
### 关键区别
|
||||
|
||||
| 特性 | Docker 容器 | 传统虚拟机 |
|
||||
|------|-------------|------------|
|
||||
| **启动速度** | 秒级 | 分钟级 |
|
||||
| **资源占用** | MB 级别 | GB 级别 |
|
||||
| **性能** | 接近原生 | 有明显损耗 |
|
||||
| **隔离级别** | 进程级隔离 | 完全隔离 |
|
||||
| **单机数量** | 可运行上千个 | 通常几十个 |
|
||||
|
||||
> 笔者经常用这个类比来解释:虚拟机像是每个应用都住在一栋独立的房子里(有自己的地基、水电系统),而容器像是大家住在同一栋公寓楼里的不同房间(共享地基和水电系统,但各自独立)。
|
||||
|
||||
## 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)
|
||||
|
||||
> 如果你对这些底层技术感兴趣,可以阅读本书的[底层实现](../13_implementation/README.md)章节。
|
||||
|
||||
### Docker 架构演进
|
||||
|
||||
Docker 的底层实现经历了多次演进:
|
||||
|
||||
```
|
||||
2013 2014 2015 现在
|
||||
│ │ │ │
|
||||
▼ ▼ ▼ ▼
|
||||
LXC ──→ libcontainer ──→ runC ──→ containerd + runC
|
||||
│
|
||||
└── OCI 标准化
|
||||
```
|
||||
|
||||
- **LXC**(2013):Docker 最初基于 Linux Containers
|
||||
- **libcontainer**(2014,v0.7):Docker 自研的容器运行时
|
||||
- **runC**(2015,v1.11):捐献给 OCI 的标准容器运行时
|
||||
- **containerd**:高级容器运行时,管理容器生命周期
|
||||
|
||||

|
||||
|
||||
> `runc` 是一个 Linux 命令行工具,用于根据 [OCI 容器运行时规范](https://github.com/opencontainers/runtime-spec) 创建和运行容器。
|
||||
|
||||
> `containerd` 是一个守护程序,它管理容器生命周期,提供了在一个节点上执行容器和管理镜像的最小功能集。
|
||||
|
||||
## 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 公司改名为 Docker, Inc.
|
||||
- **2015 年**:成立 [开放容器联盟(OCI)](https://opencontainers.org/),推动容器标准化
|
||||
- **至今**:[GitHub 项目](https://github.com/moby/moby) 超过 7 万星标
|
||||
|
||||
Docker 的成功推动了整个容器生态的发展,催生了 Kubernetes、Podman 等众多相关项目。笔者认为,Docker 最大的贡献不仅是技术本身,更是它**让容器技术从系统管理员的工具变成了每个开发者都能使用的标准工具**。
|
||||
|
||||
## 本章小结
|
||||
|
||||
- Docker 是一种轻量级虚拟化技术,核心价值是**环境一致性**
|
||||
- 与虚拟机相比,Docker 更轻量、更快速、资源利用率更高
|
||||
- Docker 基于 Linux 内核的 Namespace、Cgroups 和 Union FS 技术
|
||||
- Docker 推动了容器技术的标准化(OCI)和生态发展
|
||||
|
||||
接下来,让我们了解[为什么要使用 Docker](why.md)。
|
||||
@@ -1,208 +0,0 @@
|
||||
# 为什么要使用 Docker?
|
||||
|
||||
在回答"为什么用 Docker"之前,笔者想先问一个问题:**你有没有经历过这些场景?**
|
||||
|
||||
## 没有 Docker 的世界
|
||||
|
||||
### 场景一:"在我电脑上明明能跑"
|
||||
|
||||
```
|
||||
周五下午 5:00
|
||||
├── 开发者:代码写完了,本地测试通过,提交!🎉
|
||||
├── 周一早上 9:00
|
||||
│ └── 测试:"这个功能在测试环境跑不起来"
|
||||
└── 开发者:" 不可能,在我电脑上明明能跑啊……"
|
||||
```
|
||||
|
||||
笔者统计过,这个问题通常由以下原因导致:
|
||||
- Python/Node/Java 版本不一致
|
||||
- 依赖库版本不一致
|
||||
- 操作系统配置不一致
|
||||
- 某些环境变量没有设置
|
||||
- "哦,忘了说我本地装了个 XXX"
|
||||
|
||||
### 场景二:环境配置的噩梦
|
||||
|
||||
```
|
||||
新同事入职
|
||||
├── Day 1:领电脑,配环境
|
||||
├── Day 2:继续配环境,遇到问题
|
||||
├── Day 3:换种方法配环境
|
||||
├── Day 4:问老同事怎么配的,他也忘了
|
||||
└── Day 5:终于能跑起来了!但不知道为什么……
|
||||
```
|
||||
|
||||
### 场景三:服务器迁移的恐惧
|
||||
|
||||
```
|
||||
运维:"我们需要把服务迁移到新服务器"
|
||||
开发:"旧服务器上的配置文档在哪?"
|
||||
运维:"当时是一个已经离职的同事配的……"
|
||||
所有人:😱
|
||||
```
|
||||
|
||||
## Docker 如何解决这些问题
|
||||
|
||||
### 核心理念:一次构建,到处运行
|
||||
|
||||
```
|
||||
开发环境 测试环境 生产环境
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌─────────┐ ┌─────────┐ ┌─────────┐
|
||||
│ Docker │ = │ Docker │ = │ 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 内存) │
|
||||
├──────────────┬───────────────┬─────────────────┤
|
||||
│ VM1 │ VM2 │ VM3 │
|
||||
│ 8GB 内存 │ 8GB 内存 │ 8GB 内存 │
|
||||
│ (含 OS 2GB) │ (含 OS 2GB) │ (含 OS 2GB) │
|
||||
│ 应用 1 │ 应用 2 │ 应用 3 │
|
||||
└──────────────┴───────────────┴─────────────────┘
|
||||
实际可用于应用:3 × 6GB = 18GB ❌
|
||||
|
||||
Docker 方案:
|
||||
┌────────────────────────────────────────────────┐
|
||||
│ 物理服务器 (64GB 内存) │
|
||||
│ 宿主机 OS + Docker (约 4GB) │
|
||||
├──────────────┬───────────────┬─────────────────┤
|
||||
│ 容器 1 │ 容器 2 │ 容器 3 │
|
||||
│ 应用 1 │ 应用 2 │ 应用 3 │
|
||||
│ (按需分配) │ (按需分配) │ (按需分配) │
|
||||
└──────────────┴───────────────┴─────────────────┘
|
||||
实际可用于应用:约 60GB ✅
|
||||
```
|
||||
|
||||
### 4. 持续交付和部署
|
||||
|
||||
Docker 完美契合 DevOps 的工作流程:
|
||||
|
||||
```
|
||||
代码提交 ──→ 自动构建镜像 ──→ 自动测试 ──→ 自动部署
|
||||
│ │ │ │
|
||||
▼ ▼ ▼ ▼
|
||||
Git docker 容器内 容器滚动
|
||||
push build 运行测试 更新
|
||||
```
|
||||
|
||||
使用 [Dockerfile](../04_image/build.md) 定义镜像构建过程,使得:
|
||||
- 构建过程**可重复、可追溯**
|
||||
- 任何人都能从代码重建完全相同的镜像
|
||||
- 配合 [GitHub Actions](../14_cases/ci/actions/README.md) 等 CI 系统实现自动化
|
||||
|
||||
### 5. 轻松迁移
|
||||
|
||||
Docker 可以在几乎任何平台上运行:
|
||||
- ✅ 本地开发机(macOS、Windows、Linux)
|
||||
- ✅ 公有云(AWS、Azure、GCP、阿里云、腾讯云)
|
||||
- ✅ 私有云和自建数据中心
|
||||
- ✅ 边缘设备和 IoT
|
||||
|
||||
**同一个镜像,在任何地方运行结果都一致。** 这让应用迁移变得前所未有的简单。
|
||||
|
||||
### 6. 微服务架构的基石
|
||||
|
||||
现代微服务架构几乎都依赖容器技术。Docker 让你可以:
|
||||
|
||||
- **隔离服务**:每个服务运行在独立容器中,互不干扰
|
||||
- **独立扩展**:哪个服务负载高,就单独扩展哪个
|
||||
- **独立部署**:更新一个服务不影响其他服务
|
||||
- **技术多样**:不同服务可以用不同语言和框架
|
||||
|
||||
```
|
||||
┌───────────────────────────────────────────────────┐
|
||||
│ 微服务架构示例 │
|
||||
├─────────────┬─────────────┬───────────────────────┤
|
||||
│ 前端容器 │ API 容器 │ Worker 容器 │
|
||||
│ (Node.js) │ (Python) │ (Go) │
|
||||
├─────────────┴─────────────┴───────────────────────┤
|
||||
│ Redis 容器 │
|
||||
├───────────────────────────────────────────────────┤
|
||||
│ PostgreSQL 容器 │
|
||||
└───────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Docker 不适合的场景
|
||||
|
||||
笔者认为,技术选型要客观。Docker 并非银弹,以下场景可能不太适合:
|
||||
|
||||
### 1. 需要完全隔离的场景
|
||||
|
||||
容器共享宿主机内核,隔离性不如虚拟机。如果你需要运行不受信任的代码,虚拟机可能更安全。
|
||||
|
||||
### 2. 需要特殊内核的场景
|
||||
|
||||
容器使用宿主机内核。如果应用需要特定版本的内核或内核模块,可能需要虚拟机。
|
||||
|
||||
### 3. Windows 原生应用
|
||||
|
||||
虽然 Docker 支持 Windows 容器,但生态不如 Linux 容器成熟。传统 Windows 应用的容器化仍有挑战。
|
||||
|
||||
### 4. 桌面应用
|
||||
|
||||
Docker 主要面向服务端应用。桌面 GUI 应用的容器化虽然可行,但通常得不偿失。
|
||||
|
||||
## 与传统虚拟机的对比总结
|
||||
|
||||
| 特性 | Docker 容器 | 传统虚拟机 |
|
||||
|:------|:-----------|:-----------|
|
||||
| 启动速度 | 秒级 | 分钟级 |
|
||||
| 磁盘占用 | MB 级别 | GB 级别 |
|
||||
| 性能 | 接近原生 | 有 5-20% 损耗 |
|
||||
| 单机支持量 | 上千个容器 | 几十个虚拟机 |
|
||||
| 隔离性 | 进程级别 | 完全隔离 |
|
||||
| 最佳场景 | 微服务、CI/CD、开发环境 | 多租户、高安全需求 |
|
||||
|
||||
## 本章小结
|
||||
|
||||
Docker 的核心价值可以用一句话概括:**让应用的开发、测试、部署保持一致,同时极大提高资源利用效率。**
|
||||
|
||||
笔者认为,对于现代软件开发者来说,Docker 已经不是"要不要学"的问题,而是**必备技能**。无论你是前端、后端、运维还是全栈开发者,掌握 Docker 都能让你的工作更高效。
|
||||
|
||||
接下来,让我们学习 Docker 的[基本概念](../02_basic_concept/README.md)。
|
||||
213
02_basic_concept/2.1_image.md
Normal file
@@ -0,0 +1,213 @@
|
||||
## 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 解释器 |
|
||||
| **库文件** | libc、OpenSSL、各种依赖库 |
|
||||
| **配置文件** | nginx.conf、my.cnf 等 |
|
||||
| **环境变量** | PATH、LANG 等预设值 |
|
||||
| **元数据** | 启动命令、暴露端口、数据卷定义 |
|
||||
|
||||
**关键特性**:
|
||||
- ✅ 镜像是 **只读** 的
|
||||
- ✅ 镜像 **不包含** 动态数据
|
||||
- ✅ 镜像构建后 **内容不会改变**
|
||||
|
||||
### 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` |
|
||||
239
02_basic_concept/2.2_container.md
Normal file
@@ -0,0 +1,239 @@
|
||||
## 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)。
|
||||
288
02_basic_concept/2.3_repository.md
Normal file
@@ -0,0 +1,288 @@
|
||||
## 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 Registry、Repository 与 Tag 的层级关系
|
||||
|
||||
相关基本概念具体如下:
|
||||
|
||||
| 概念 | 说明 | 示例 |
|
||||
|------|------|------|
|
||||
| **Registry** | 存储镜像的服务 | Docker Hub、ghcr.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)** | 支持多种制品类型 (Docker、Maven、npm 等)|
|
||||
| **云厂商服务** | 阿里云 ACR、腾讯云 TCR、AWS 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
|
||||
```
|
||||
@@ -1,9 +1,15 @@
|
||||
# 基本概念
|
||||
# 第二章 基本概念
|
||||
|
||||
**Docker** 包括三个基本概念:
|
||||
|
||||
* **镜像**(`Image`):Docker 镜像是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。镜像不包含任何动态数据,其内容在构建之后也不会被改变。
|
||||
* **容器**(`Container`):镜像(`Image`)和容器(`Container`)的关系,就像是面向对象程序设计中的 `类` 和 `实例` 一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。
|
||||
* **仓库**(`Repository`):镜像构建完成后,可以很容易的在当前宿主机上运行,但是,如果需要在其它服务器上使用这个镜像,我们就需要一个集中的存储、分发镜像的服务,Docker Registry 就是这样的服务。
|
||||
* **镜像** (`Image`):Docker 镜像是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数 (如匿名卷、环境变量、用户等)。镜像不包含任何动态数据,其内容在构建之后也不会被改变。
|
||||
* **容器** (`Container`):镜像 (`Image`) 和容器 (`Container`) 的关系,就像是面向对象程序设计中的 `类` 和 `实例` 一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。
|
||||
* **仓库** (`Repository`):镜像构建完成后,可以很容易的在当前宿主机上运行,但是,如果需要在其它服务器上使用这个镜像,我们就需要一个集中的存储、分发镜像的服务,Docker Registry 就是这样的服务。
|
||||
|
||||
理解了这三个概念,就理解了 **Docker** 的整个生命周期。
|
||||
|
||||
## 本章内容
|
||||
|
||||
* [Docker 镜像](2.1_image.md)
|
||||
* [Docker 容器](2.2_container.md)
|
||||
* [Docker 仓库](2.3_repository.md)
|
||||
|
||||
@@ -1,246 +0,0 @@
|
||||
# Docker 容器
|
||||
|
||||
## 一句话理解容器
|
||||
|
||||
> **容器是镜像的运行实例。如果把镜像比作程序,那么容器就是进程。**
|
||||
|
||||
用面向对象编程的术语来说:**镜像是类(Class),容器是对象(Instance)**。
|
||||
|
||||
- 一个镜像可以创建多个容器
|
||||
- 每个容器相互独立,互不影响
|
||||
- 容器可以被创建、启动、停止、删除、暂停
|
||||
|
||||
## 容器的本质
|
||||
|
||||
> 💡 **笔者认为,理解这一点是理解 Docker 的关键**
|
||||
|
||||
**容器的本质是一个特殊的进程。**
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 普通进程 │
|
||||
│ • 与其他进程共享系统资源 │
|
||||
│ • 可以看到其他进程 │
|
||||
│ • 共享网络和文件系统 │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 容器进程 │
|
||||
│ ┌───────────────────────────────────────────────────────┐ │
|
||||
│ │ • 有自己的进程空间(看不到宿主机上的其他进程) │ │
|
||||
│ │ • 有自己的网络(独立 IP、端口) │ │
|
||||
│ │ • 有自己的文件系统(独立的 root 目录) │ │
|
||||
│ │ • 有自己的用户(容器内的 root ≠ 宿主机的 root) │ │
|
||||
│ └───────────────────────────────────────────────────────┘ │
|
||||
│ 但仍然运行在宿主机的内核上 │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
这种隔离是通过 Linux 内核的 **Namespace** 技术实现的。
|
||||
|
||||
## 容器 vs 虚拟机:核心区别
|
||||
|
||||
很多初学者会混淆容器和虚拟机。笔者用一张图来说明:
|
||||
|
||||
```
|
||||
虚拟机 容器
|
||||
┌───────────────────────┐ ┌───────────────────────┐
|
||||
│ App A │ App B │ │ App A │ App B │
|
||||
├────────────┼──────────┤ ├────────────┼──────────┤
|
||||
│ Guest OS │ Guest OS │ │ Container │ Container│
|
||||
│ (完整系统) │ (完整系统)│ │ (仅应用) │ (仅应用) │
|
||||
├────────────┴──────────┤ └────────────┴──────────┤
|
||||
│ Hypervisor │ │ Docker Engine │
|
||||
├───────────────────────┤ ├───────────────────────┤
|
||||
│ Host OS │ │ Host OS │
|
||||
├───────────────────────┤ ├───────────────────────┤
|
||||
│ Hardware │ │ Hardware │
|
||||
└───────────────────────┘ └───────────────────────┘
|
||||
每个 VM 运行完整 OS 所有容器共享宿主机内核
|
||||
```
|
||||
|
||||
| 特性 | 容器 | 虚拟机 |
|
||||
|------|------|--------|
|
||||
| **隔离级别** | 进程级(Namespace) | 硬件级(Hypervisor) |
|
||||
| **启动时间** | 秒级(甚至毫秒) | 分钟级 |
|
||||
| **资源占用** | MB 级别 | GB 级别 |
|
||||
| **性能损耗** | 几乎为零 | 5-20% |
|
||||
| **内核** | 共享宿主机内核 | 各自独立内核 |
|
||||
|
||||
## 容器的存储层
|
||||
|
||||
### 镜像层 + 容器层
|
||||
|
||||
当容器运行时,Docker 会在镜像的只读层之上创建一个**可写层**(容器存储层):
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ 容器存储层(可读写) │ ← 容器运行时创建
|
||||
│ 运行时产生的文件变化记录在这里 │
|
||||
├─────────────────────────────────────────────┤
|
||||
│ 镜像第 N 层(只读) │
|
||||
├─────────────────────────────────────────────┤
|
||||
│ 镜像第 N-1 层(只读) │
|
||||
├─────────────────────────────────────────────┤
|
||||
│ ... │
|
||||
├─────────────────────────────────────────────┤
|
||||
│ 镜像第 1 层(只读) │ ← 基础镜像层
|
||||
└─────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Copy-on-Write(写时复制)
|
||||
|
||||
当容器需要修改镜像层中的文件时:
|
||||
|
||||
1. Docker 将该文件**复制**到容器存储层
|
||||
2. 在容器层中进行修改
|
||||
3. 原始镜像层保持不变
|
||||
|
||||
```
|
||||
读取文件:直接从镜像层读取(共享,高效)
|
||||
修改文件:复制到容器层,然后修改(只有这个容器能看到修改)
|
||||
```
|
||||
|
||||
### ⚠️ 容器存储层的生命周期
|
||||
|
||||
> **笔者特别强调**:这是新手最容易踩的坑!
|
||||
|
||||
**容器存储层与容器生命周期绑定。容器删除,数据就没了!**
|
||||
|
||||
```bash
|
||||
# 创建容器,写入数据
|
||||
$ docker run -it ubuntu bash
|
||||
root@abc123:/# echo "important data" > /data.txt
|
||||
root@abc123:/# exit
|
||||
|
||||
# 删除容器
|
||||
$ docker rm abc123
|
||||
|
||||
# 数据丢了!没有任何办法恢复!
|
||||
```
|
||||
|
||||
### 正确的数据持久化方式
|
||||
|
||||
按照 Docker 最佳实践,容器存储层应该保持**无状态**。需要持久化的数据应该使用:
|
||||
|
||||
| 方式 | 说明 | 适用场景 |
|
||||
|------|------|---------|
|
||||
| **[数据卷(Volume)](../07_data_network/data/volume.md)** | Docker 管理的存储 | 数据库、应用数据 |
|
||||
| **[绑定挂载(Bind Mount)](../07_data_network/data/bind-mounts.md)** | 挂载宿主机目录 | 开发时共享代码 |
|
||||
|
||||
```bash
|
||||
# 使用数据卷(推荐)
|
||||
$ docker run -v mydata:/var/lib/mysql mysql
|
||||
|
||||
# 使用绑定挂载
|
||||
$ docker run -v /host/path:/container/path nginx
|
||||
```
|
||||
|
||||
这些位置的读写**会跳过容器存储层**,直接写入宿主机,性能更好,也不会随容器删除而丢失。
|
||||
|
||||
## 容器的生命周期
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────┐
|
||||
│ 容器生命周期 │
|
||||
└──────────────────────────────────────────────────┘
|
||||
|
||||
docker create docker start docker stop
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌─────────┐ ┌─────────┐ ┌─────────┐
|
||||
│ Created │───────────▶│ Running │───────────▶│ Stopped │
|
||||
└─────────┘ └─────────┘ └─────────┘
|
||||
│ │ │
|
||||
│ │ docker pause │
|
||||
│ ▼ │
|
||||
│ ┌─────────┐ │
|
||||
│ │ Paused │ │
|
||||
│ └─────────┘ │
|
||||
│ │ │
|
||||
│ docker rm │ docker rm │
|
||||
└───────────────────────┴──────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────┐
|
||||
│ Deleted │
|
||||
└──────────┘
|
||||
```
|
||||
|
||||
### 常用生命周期命令
|
||||
|
||||
```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 # 强制删除运行中的容器
|
||||
```
|
||||
|
||||
## 容器与进程的关系
|
||||
|
||||
> **核心概念**:容器的生命周期 = 主进程(PID 1)的生命周期
|
||||
|
||||
```bash
|
||||
# 主进程运行,容器运行
|
||||
# 主进程退出,容器停止
|
||||
```
|
||||
|
||||
这就是为什么:
|
||||
|
||||
```bash
|
||||
# 这个容器会立即退出(bash 没有输入就退出了)
|
||||
$ docker run ubuntu
|
||||
|
||||
# 这个容器会持续运行(nginx 作为守护进程持续运行)
|
||||
$ docker run nginx
|
||||
```
|
||||
|
||||
详细解释请参考[后台运行](../05_container/daemon.md)章节。
|
||||
|
||||
## 容器的隔离性
|
||||
|
||||
Docker 容器通过以下 Namespace 实现隔离:
|
||||
|
||||
| Namespace | 隔离内容 | 效果 |
|
||||
|-----------|---------|------|
|
||||
| **PID** | 进程 ID | 容器内 PID 1 是应用进程,看不到宿主机其他进程 |
|
||||
| **NET** | 网络 | 独立的网络栈、IP 地址、端口 |
|
||||
| **MNT** | 文件系统 | 独立的根目录和挂载点 |
|
||||
| **UTS** | 主机名 | 独立的主机名和域名 |
|
||||
| **IPC** | 进程间通信 | 独立的信号量、消息队列 |
|
||||
| **USER** | 用户 | 独立的用户和组 ID |
|
||||
|
||||
> 想深入了解?请阅读[底层实现 - 命名空间](../13_implementation/namespace.md)。
|
||||
|
||||
## 本章小结
|
||||
|
||||
| 概念 | 要点 |
|
||||
|------|------|
|
||||
| **容器是什么** | 镜像的运行实例,本质是隔离的进程 |
|
||||
| **容器 vs 虚拟机** | 共享内核,更轻量,但隔离性较弱 |
|
||||
| **存储层** | 可写层随容器删除而消失 |
|
||||
| **数据持久化** | 使用 Volume 或 Bind Mount |
|
||||
| **生命周期** | 与主进程(PID 1)绑定 |
|
||||
|
||||
理解了镜像和容器,接下来让我们学习[仓库](repository.md)——存储和分发镜像的服务。
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [启动容器](../05_container/run.md):详细的容器启动选项
|
||||
- [后台运行](../05_container/daemon.md):理解容器为什么会"立即退出"
|
||||
- [进入容器](../05_container/attach_exec.md):如何操作运行中的容器
|
||||
- [数据管理](../07_data_network/README.md):Volume 和数据持久化详解
|
||||
@@ -1,222 +0,0 @@
|
||||
# Docker 镜像
|
||||
|
||||
## 一句话理解镜像
|
||||
|
||||
> **Docker 镜像是一个只读的模板,包含了运行应用所需的一切:代码、运行时、库、环境变量和配置文件。**
|
||||
|
||||
如果用一个类比:**镜像就像是一张光盘或 ISO 文件**。你可以用同一张光盘在不同电脑上安装系统,而光盘本身不会被修改。同样,一个镜像可以创建多个容器,而镜像本身保持不变。
|
||||
|
||||
## 镜像与操作系统的关系
|
||||
|
||||
我们都知道,操作系统分为**内核**和**用户空间**:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 用户空间 │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ 应用程序、工具、库、配置文件... │ │
|
||||
│ │ (这部分被打包成 Docker 镜像) │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Linux 内核 │
|
||||
│ (容器共享宿主机的内核) │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
对于 Linux 而言,内核启动后会挂载 `root` 文件系统来提供用户空间支持。**Docker 镜像**本质上就是一个 `root` 文件系统。
|
||||
|
||||
例如,官方镜像 `ubuntu:24.04` 包含了一套完整的 Ubuntu 24.04 最小系统的 root 文件系统——但**不包含 Linux 内核**(因为容器共享宿主机的内核)。
|
||||
|
||||
## 镜像包含什么?
|
||||
|
||||
Docker 镜像是一个特殊的文件系统,包含:
|
||||
|
||||
| 内容类型 | 示例 |
|
||||
|---------|------|
|
||||
| **程序文件** | 应用二进制文件、Python/Node 解释器 |
|
||||
| **库文件** | libc、OpenSSL、各种依赖库 |
|
||||
| **配置文件** | nginx.conf、my.cnf 等 |
|
||||
| **环境变量** | PATH、LANG 等预设值 |
|
||||
| **元数据** | 启动命令、暴露端口、数据卷定义 |
|
||||
|
||||
**关键特性**:
|
||||
- ✅ 镜像是**只读**的
|
||||
- ✅ 镜像**不包含**动态数据
|
||||
- ✅ 镜像构建后**内容不会改变**
|
||||
|
||||
## 分层存储:镜像的核心设计
|
||||
|
||||
### 为什么需要分层?
|
||||
|
||||
笔者认为,分层存储是 Docker 最巧妙的设计之一。
|
||||
|
||||
假设你有三个应用,都基于 Ubuntu 运行:
|
||||
|
||||
```
|
||||
传统方式(不分层):
|
||||
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
||||
│ App A │ │ App B │ │ App C │
|
||||
│ Ubuntu │ │ Ubuntu │ │ Ubuntu │
|
||||
│ 500MB │ │ 500MB │ │ 500MB │
|
||||
└─────────────┘ └─────────────┘ └─────────────┘
|
||||
总计:1.5GB ❌
|
||||
|
||||
Docker 分层方式:
|
||||
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
||||
│ App A │ │ App B │ │ App C │
|
||||
│ 50MB │ │ 30MB │ │ 40MB │
|
||||
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
|
||||
│ │ │
|
||||
└────────────────┼────────────────┘
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ Ubuntu │
|
||||
│ (共享)500MB │
|
||||
└─────────────────┘
|
||||
总计:620MB ✅
|
||||
```
|
||||
|
||||
### 分层是如何工作的?
|
||||
|
||||
笔者用一个实际的 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 层:复制配置文件
|
||||
```
|
||||
|
||||
构建后的镜像结构:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ 第 4 层: COPY app.conf (只读) │ ← 最新添加的层
|
||||
├─────────────────────────────────────┤
|
||||
│ 第 3 层: nginx 安装文件 (只读) │
|
||||
├─────────────────────────────────────┤
|
||||
│ 第 2 层: apt 缓存更新 (只读) │
|
||||
├─────────────────────────────────────┤
|
||||
│ 第 1 层: Ubuntu 基础系统 (只读) │ ← 基础镜像层
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
每一层的特点:
|
||||
- **只读**:构建完成后不可修改
|
||||
- **可共享**:多个镜像可以共享相同的层
|
||||
- **有缓存**:未变化的层不会重新构建
|
||||
|
||||
### 分层存储的"陷阱"
|
||||
|
||||
> ⚠️ **笔者特别提醒**:理解这一点可以帮你避免构建出臃肿的镜像。
|
||||
|
||||
**关键原理**:每一层的文件变化会被记录,但**删除操作只是标记,不会真正减小镜像体积**。
|
||||
|
||||
```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
|
||||
...
|
||||
```
|
||||
|
||||
## 镜像的标识
|
||||
|
||||
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. 镜像摘要(Digest)
|
||||
|
||||
更精确的标识,基于镜像内容的 SHA256 哈希:
|
||||
|
||||
```bash
|
||||
$ docker images --digests
|
||||
REPOSITORY TAG DIGEST IMAGE ID
|
||||
nginx latest sha256:6db391d1c0cfb30588ba0bf72ea999404f2764184d8b8d10d89e8a9c6... a6bd71f48f68
|
||||
```
|
||||
|
||||
> 💡 笔者建议:在生产环境使用镜像摘要而非标签,因为标签可以被覆盖,但摘要是不可变的。
|
||||
|
||||
## 镜像的来源
|
||||
|
||||
Docker 镜像可以通过以下方式获取:
|
||||
|
||||
| 方式 | 说明 | 示例 |
|
||||
|------|------|------|
|
||||
| **从 Registry 拉取** | 最常用的方式 | `docker pull nginx` |
|
||||
| **从 Dockerfile 构建** | 自定义镜像 | `docker build -t myapp .` |
|
||||
| **从容器提交** | 保存容器状态(不推荐) | `docker commit` |
|
||||
| **从文件导入** | 离线传输 | `docker load < image.tar` |
|
||||
|
||||
## 本章小结
|
||||
|
||||
| 概念 | 要点 |
|
||||
|------|------|
|
||||
| **镜像是什么** | 只读的应用模板,包含运行所需的一切 |
|
||||
| **分层存储** | 多层叠加,共享基础层,节省空间 |
|
||||
| **只读特性** | 构建后不可修改,保证一致性 |
|
||||
| **层的陷阱** | 删除操作只是标记,不减小体积 |
|
||||
|
||||
理解了镜像,接下来让我们学习[容器](container.md)——镜像的运行实例。
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [获取镜像](../04_image/pull.md):从 Registry 下载镜像
|
||||
- [使用 Dockerfile 定制镜像](../04_image/build.md):创建自己的镜像
|
||||
- [Dockerfile 最佳实践](../15_appendix/best_practices.md):构建高质量镜像的技巧
|
||||
- [底层实现 - 联合文件系统](../13_implementation/ufs.md):深入理解分层存储的技术原理
|
||||
@@ -1,250 +0,0 @@
|
||||
# Docker Registry
|
||||
|
||||
## 一句话理解 Registry
|
||||
|
||||
> **Docker Registry 是存储和分发 Docker 镜像的服务,类似于代码的 GitHub 或包管理的 npm。**
|
||||
|
||||
镜像构建完成后,可以在当前机器上运行。但如果需要在其他服务器上使用这个镜像,就需要一个集中的存储和分发服务——这就是 Docker Registry。
|
||||
|
||||
## 核心概念
|
||||
|
||||
### Registry、仓库、标签的关系
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Docker Registry │
|
||||
│ (如 Docker Hub) │
|
||||
│ ┌─────────────────────────────────────────────────────────────┐ │
|
||||
│ │ Repository(仓库): nginx │ │
|
||||
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
|
||||
│ │ │ :latest │ │ :1.25 │ │ :1.24 │ │ :alpine │ ... │ │
|
||||
│ │ │ (tag) │ │ (tag) │ │ (tag) │ │ (tag) │ │ │
|
||||
│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │
|
||||
│ └─────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────────┐ │
|
||||
│ │ Repository(仓库): mysql │ │
|
||||
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
|
||||
│ │ │ :latest │ │ :8.0 │ │ :5.7 │ ... │ │
|
||||
│ │ └─────────┘ └─────────┘ └─────────┘ │ │
|
||||
│ └─────────────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
| 概念 | 说明 | 示例 |
|
||||
|------|------|------|
|
||||
| **Registry** | 存储镜像的服务 | Docker Hub、ghcr.io |
|
||||
| **Repository(仓库)** | 同一软件的镜像集合 | `nginx`、`mysql`、`mycompany/myapp` |
|
||||
| **Tag(标签)** | 仓库内的版本标识 | `latest`、`1.25`、`alpine` |
|
||||
|
||||
### 镜像的完整名称
|
||||
|
||||
```
|
||||
[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`。
|
||||
|
||||
## 公共 Registry 服务
|
||||
|
||||
### 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
|
||||
|
||||
| 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 | 国内访问快 |
|
||||
|
||||
## 镜像加速器
|
||||
|
||||
由于网络原因,在国内直接访问 Docker Hub 可能会很慢。可以配置**镜像加速器**(Registry Mirror)来加速下载。
|
||||
|
||||
```json
|
||||
// /etc/docker/daemon.json
|
||||
{
|
||||
"registry-mirrors": [
|
||||
"https://your-accelerator-url"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
详细配置方法请参考[镜像加速器](../install/mirror.md)章节。
|
||||
|
||||
> ⚠️ **笔者提醒**:镜像加速器的可用性经常变化,使用前建议先测试是否可用。
|
||||
|
||||
## 私有 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](../repository/nexus3_registry.md)** | 支持多种制品类型(Docker、Maven、npm 等) |
|
||||
| **云厂商服务** | 阿里云 ACR、腾讯云 TCR、AWS ECR 等 |
|
||||
|
||||
笔者建议:
|
||||
- 小团队:可以先用官方 Registry,够用即可
|
||||
- 中大型团队:推荐 Harbor,功能完善且开源免费
|
||||
- 已使用云服务:直接用云厂商的 Registry 服务更省心
|
||||
|
||||
## 镜像的推送和拉取
|
||||
|
||||
### 完整工作流程
|
||||
|
||||
```
|
||||
开发者机器 Registry 生产服务器
|
||||
│ │ │
|
||||
│ docker build │ │
|
||||
│ 构建镜像 │ │
|
||||
│ │ │
|
||||
│ docker push ─────────────▶ │
|
||||
│ 推送镜像 │ 存储镜像 │
|
||||
│ │ │
|
||||
│ │ ◀───────────── docker pull │
|
||||
│ │ 拉取镜像 │
|
||||
│ │ │
|
||||
│ │ docker run │
|
||||
│ │ 运行容器 │
|
||||
```
|
||||
|
||||
### 常用命令
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
## 镜像的安全性
|
||||
|
||||
### 使用官方镜像
|
||||
|
||||
Docker Hub 的[官方镜像](https://hub.docker.com/search?q=&type=image&image_filter=official)(标有 "Official Image" 标识)经过 Docker 团队审核,相对更安全。
|
||||
|
||||
```bash
|
||||
# 官方镜像示例
|
||||
nginx # ✅ 官方
|
||||
mysql # ✅ 官方
|
||||
redis # ✅ 官方
|
||||
|
||||
# 第三方镜像(需要自行评估可信度)
|
||||
bitnami/redis # ⚠️ 需要评估
|
||||
someuser/myapp # ⚠️ 需要评估
|
||||
```
|
||||
|
||||
### 镜像签名
|
||||
|
||||
使用 Docker Content Trust (DCT) 验证镜像来源:
|
||||
|
||||
```bash
|
||||
# 启用镜像签名验证
|
||||
$ export DOCKER_CONTENT_TRUST=1
|
||||
|
||||
# 此后的 pull/push 会验证签名
|
||||
$ docker pull nginx:latest
|
||||
```
|
||||
|
||||
### 漏洞扫描
|
||||
|
||||
```bash
|
||||
# 使用 Docker Scout 扫描镜像漏洞
|
||||
$ docker scout cves nginx:latest
|
||||
|
||||
# 使用 Trivy(开源工具)
|
||||
$ trivy image nginx:latest
|
||||
```
|
||||
|
||||
## 本章小结
|
||||
|
||||
| 概念 | 要点 |
|
||||
|------|------|
|
||||
| **Registry** | 存储和分发镜像的服务 |
|
||||
| **仓库(Repository)** | 同一软件的镜像集合 |
|
||||
| **标签(Tag)** | 版本标识,默认为 latest |
|
||||
| **Docker Hub** | 默认的公共 Registry |
|
||||
| **私有 Registry** | 企业内部使用,推荐 Harbor |
|
||||
|
||||
现在你已经了解了 Docker 的三个核心概念:[镜像](image.md)、[容器](container.md)和仓库。接下来,让我们开始[安装 Docker](../install/README.md),动手实践!
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [Docker Hub](../repository/dockerhub.md):Docker Hub 的详细使用
|
||||
- [私有仓库](../repository/registry.md):搭建私有 Registry
|
||||
- [私有仓库高级配置](../repository/registry_auth.md):认证、TLS 配置
|
||||
- [镜像加速器](../install/mirror.md):配置镜像加速
|
||||
40
02_basic_concept/summary.md
Normal file
@@ -0,0 +1,40 @@
|
||||
## 本章小结
|
||||
|
||||
本章介绍了 Docker 的三个核心概念:镜像、容器和仓库。
|
||||
|
||||
| 概念 | 要点 |
|
||||
|------|------|
|
||||
| **镜像是什么** | 只读的应用模板,包含运行所需的一切 |
|
||||
| **分层存储** | 多层叠加,共享基础层,节省空间 |
|
||||
| **只读特性** | 构建后不可修改,保证一致性 |
|
||||
| **层的陷阱** | 删除操作只是标记,不减小体积 |
|
||||
| **容器是什么** | 镜像的运行实例,本质是隔离的进程 |
|
||||
| **容器 vs 虚拟机** | 共享内核,更轻量,但隔离性较弱 |
|
||||
| **存储层** | 可写层随容器删除而消失 |
|
||||
| **数据持久化** | 使用 Volume 或 Bind Mount |
|
||||
| **生命周期** | 与主进程 (PID 1) 绑定 |
|
||||
| **Registry** | 存储和分发镜像的服务 |
|
||||
| **仓库 (Repository)** | 同一软件的镜像集合 |
|
||||
| **标签 (Tag)** | 版本标识,默认为 latest |
|
||||
| **Docker Hub** | 默认的公共 Registry |
|
||||
| **私有 Registry** | 企业内部使用,推荐 Harbor |
|
||||
|
||||
现在你已经了解了 Docker 的三个核心概念:[镜像](2.1_image.md)、[容器](2.2_container.md)和仓库。接下来,让我们开始[安装 Docker](../03_install/README.md),动手实践!
|
||||
|
||||
### 延伸阅读
|
||||
|
||||
- [获取镜像](../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):深入理解分层存储的技术原理
|
||||
- [启动容器](../05_container/5.1_run.md):详细的容器启动选项
|
||||
- [后台运行](../05_container/5.2_daemon.md):理解容器为什么会“立即退出”
|
||||
- [进入容器](../05_container/5.4_attach_exec.md):如何操作运行中的容器
|
||||
- [数据管理](../08_data/README.md):Volume 和数据持久化详解
|
||||
- [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):配置镜像加速
|
||||
---
|
||||
|
||||
> 📝 **发现错误或有改进建议?** 欢迎提交 [Issue](https://github.com/yeasy/docker_practice/issues) 或 [PR](https://github.com/yeasy/docker_practice/pulls)。
|
||||
@@ -1,12 +1,14 @@
|
||||
# 开启实验特性
|
||||
## 3.10 开启实验特性
|
||||
|
||||
一些 docker 命令或功能仅当 **实验特性** 开启时才能使用,请按照以下方法进行设置。
|
||||
|
||||
## Docker CLI 的实验特性
|
||||
### 3.10.1 Docker CLI 的实验特性
|
||||
|
||||
CLI 的实验特性通常包含仍在开发中的新功能。幸运的是,在较新版本中这些特性已经更加易用。
|
||||
|
||||
从 `v20.10` 版本开始,Docker CLI 所有实验特性的命令均默认开启,无需再进行配置或设置系统环境变量。
|
||||
|
||||
## 开启 dockerd 的实验特性
|
||||
### 3.10.2 开启 dockerd 的实验特性
|
||||
|
||||
编辑 `/etc/docker/daemon.json`,新增如下条目
|
||||
|
||||
@@ -1,22 +1,26 @@
|
||||
# Ubuntu 安装 Docker
|
||||
## 3.1 Ubuntu
|
||||
|
||||
>警告:切勿在没有配置 Docker APT 源的情况下直接使用 apt 命令安装 Docker.
|
||||
Ubuntu 是 Docker 最常用的运行环境之一。本节将介绍如何在 Ubuntu 系统上安装 Docker,并配置国内镜像加速。
|
||||
|
||||
## 准备工作
|
||||
>警告:切勿在没有配置 Docker APT 源的情况下直接使用 apt 命令安装 Docker。
|
||||
|
||||
### 系统要求
|
||||
### 3.1.1 准备工作
|
||||
|
||||
Docker 支持诸多版本的 [Ubuntu](https://ubuntu.com/server) 操作系统。但是较旧的版本上将不会有 Docker 新版本的持续更新,以截至 2026 年初的几个 Ubuntu LTS(Long Term Support,长期支持)版本为例:
|
||||
在开始安装之前,我们需要确认系统版本是否满足要求,并清理可能存在的旧版本。
|
||||
|
||||
#### 系统要求
|
||||
|
||||
Docker 支持诸多版本的 [Ubuntu](https://ubuntu.com/server) 操作系统。但是较旧的版本上将不会有 Docker 新版本的持续更新,以截至 2026 年初的几个 Ubuntu LTS (Long Term Support,长期支持) 版本为例:
|
||||
|
||||
|
||||
* Ubuntu Noble 24.04 (LTS),Docker v30.x
|
||||
* Ubuntu Jammy 22.04 (LTS), Docker v30.x
|
||||
* Ubuntu Noble 24.04 (LTS),Docker v29.x
|
||||
* Ubuntu Jammy 22.04 (LTS), Docker v29.x
|
||||
|
||||
> **注意**:Ubuntu 20.04 LTS 已于 2025 年结束标准支持,不再推荐用于新部署。
|
||||
|
||||
在 Ubuntu LTS 版本上,目前 Docker 支持 amd64、arm64、armhf、ppc64el、s390x 等 5 个平台;而非 LTS 版本支持的平台通常较少。同时,LTS 版本会获得 5 年的升级维护支持,这样的系统会获得更长期的安全保障,因此在生产环境中推荐使用 LTS 版本。
|
||||
|
||||
### 卸载旧版本
|
||||
#### 卸载旧版本
|
||||
|
||||
旧版本的 Docker 称为 `docker` 或者 `docker-engine`,使用以下命令卸载旧版本:
|
||||
|
||||
@@ -33,7 +37,7 @@ do
|
||||
done
|
||||
```
|
||||
|
||||
## 使用 APT 安装
|
||||
### 3.1.2 使用 APT 安装
|
||||
|
||||
由于 `apt` 源使用 HTTPS 以确保软件下载过程中不被篡改。因此,我们首先需要添加使用 HTTPS 传输的软件包以及 CA 证书。
|
||||
|
||||
@@ -56,8 +60,10 @@ $ sudo apt install \
|
||||
$ curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
|
||||
|
||||
|
||||
# 官方源
|
||||
# $ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
|
||||
## 官方源
|
||||
|
||||
## $ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
|
||||
|
||||
```
|
||||
|
||||
然后,我们需要向 `sources.list` 中添加 Docker 软件源
|
||||
@@ -68,15 +74,19 @@ $ echo \
|
||||
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||
|
||||
|
||||
# 官方源
|
||||
# $ echo \
|
||||
# "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
|
||||
# $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||
## 官方源
|
||||
|
||||
## $ echo \
|
||||
|
||||
## "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
|
||||
|
||||
## $ stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||
|
||||
```
|
||||
|
||||
>以上命令会添加稳定版本的 Docker APT 镜像源,如果需要测试版本的 Docker 请将 stable 改为 test。
|
||||
|
||||
### 安装 Docker
|
||||
#### 安装 Docker
|
||||
|
||||
更新 apt 软件包缓存,并安装 `docker-ce`:
|
||||
|
||||
@@ -86,31 +96,38 @@ $ sudo apt update
|
||||
$ sudo apt install docker-ce docker-ce-cli containerd.io
|
||||
```
|
||||
|
||||
## 使用脚本自动安装
|
||||
### 3.1.3 使用脚本自动安装
|
||||
|
||||
在测试或开发环境中 Docker 官方为了简化安装流程,提供了一套便捷的安装脚本,Ubuntu 系统上可以使用这套脚本安装,另外可以通过 `--mirror` 选项使用国内源进行安装:
|
||||
|
||||
> 若你想安装测试版的 Docker, 请从 test.docker.com 获取脚本
|
||||
> 若你想安装测试版的 Docker,请从 test.docker.com 获取脚本
|
||||
|
||||
```bash
|
||||
# $ curl -fsSL test.docker.com -o get-docker.sh
|
||||
## $ 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
|
||||
## $ sudo sh get-docker.sh --mirror AzureChinaCloud
|
||||
|
||||
```
|
||||
|
||||
执行这个命令后,脚本就会自动的将一切准备工作做好,并且把 Docker 的稳定(stable)版本安装在系统中。
|
||||
执行这个命令后,脚本就会自动的将一切准备工作做好,并且把 Docker 的稳定 (stable) 版本安装在系统中。
|
||||
|
||||
## 启动 Docker
|
||||
### 3.1.4 启动 Docker
|
||||
|
||||
```bash
|
||||
$ sudo systemctl enable docker
|
||||
$ sudo systemctl start docker
|
||||
```
|
||||
|
||||
## 建立 docker 用户组
|
||||
### 3.1.5 建立 docker 用户组
|
||||
|
||||
默认情况下,`docker` 命令会使用 [Unix socket](https://en.wikipedia.org/wiki/Unix_domain_socket) 与 Docker 引擎通讯。而只有 `root` 用户和 `docker` 组的用户才可以访问 Docker 引擎的 Unix socket。出于安全考虑,一般 Linux 系统上不会直接使用 `root` 用户。因此,更好地做法是将需要使用 `docker` 的用户加入 `docker` 用户组。
|
||||
默认情况下,`docker` 命令会使用 [Unix socket](https://en.wikipedia.org/wiki/Unix_domain_socket) 与 Docker 引擎通讯。而只有 `root` 用户和 `docker` 组的用户才可以访问 Docker 引擎的 Unix socket。出于安全考虑,一般 Linux 系统上不会直接使用 `root` 用户。因此,更好的做法是将需要使用 `docker` 的用户加入 `docker` 用户组。
|
||||
|
||||
> ⚠️ **安全警告:`docker` 用户组等同于 `root` 权限**
|
||||
>
|
||||
> 将用户加入 `docker` 组免去了每次执行 `docker` 命令时输入 `sudo` 的繁琐,但这也意味着该用户可以轻易获取主机的最高 root 权限(例如通过挂载根目录运行容器)。
|
||||
> 如果你在一个多用户共享的生产系统上配置,切勿随意将普通用户加入此组。此时,更安全的替代方案是使用官方提供的 **[Rootless 模式 (Rootless mode)](https://docs.docker.com/engine/security/rootless/)**,它允许在没有任何 root 权限的情况下运行 Docker 守护进程和容器。
|
||||
|
||||
建立 `docker` 组:
|
||||
|
||||
@@ -126,7 +143,7 @@ $ sudo usermod -aG docker $USER
|
||||
|
||||
退出当前终端并重新登录,进行如下测试。
|
||||
|
||||
## 测试 Docker 是否安装正确
|
||||
### 3.1.6 测试 Docker 是否安装正确
|
||||
|
||||
```bash
|
||||
$ docker run --rm hello-world
|
||||
@@ -161,10 +178,10 @@ For more examples and ideas, visit:
|
||||
|
||||
若能正常输出以上信息,则说明安装成功。
|
||||
|
||||
## 镜像加速
|
||||
### 3.1.7 镜像加速
|
||||
|
||||
如果在使用过程中发现拉取 Docker 镜像十分缓慢,可以配置 Docker [国内镜像加速](mirror.md)。
|
||||
如果在使用过程中发现拉取 Docker 镜像十分缓慢,可以配置 Docker [国内镜像加速](3.9_mirror.md)。
|
||||
|
||||
## 参考文档
|
||||
### 3.1.8 参考文档
|
||||
|
||||
* [Docker 官方 Ubuntu 安装文档](https://docs.docker.com/install/linux/docker-ce/ubuntu/)
|
||||
* [Docker 官方 Ubuntu 安装文档](https://docs.docker.com/engine/install/ubuntu/)
|
||||
@@ -1,10 +1,14 @@
|
||||
# Debian 安装 Docker
|
||||
## 3.2 Debian
|
||||
|
||||
>警告:切勿在没有配置 Docker APT 源的情况下直接使用 apt 命令安装 Docker.
|
||||
Debian 以其稳定性著称,是 Docker 的理想宿主系统。本节将指导你在 Debian 上完成 Docker 的安装。
|
||||
|
||||
## 准备工作
|
||||
>警告:切勿在没有配置 Docker APT 源的情况下直接使用 apt 命令安装 Docker。
|
||||
|
||||
### 系统要求
|
||||
### 3.2.1 准备工作
|
||||
|
||||
安装前请仔细检查 Debian 版本支持情况,并卸载旧版本以避免冲突。
|
||||
|
||||
#### 系统要求
|
||||
|
||||
Docker 支持以下版本的 [Debian](https://www.debian.org/intro/about) 操作系统:
|
||||
|
||||
@@ -12,7 +16,7 @@ Docker 支持以下版本的 [Debian](https://www.debian.org/intro/about) 操作
|
||||
* Debian Bookworm 12 (oldstable)
|
||||
* Debian Bullseye 11 (LTS)
|
||||
|
||||
### 卸载旧版本
|
||||
#### 卸载旧版本
|
||||
|
||||
旧版本的 Docker 称为 `docker` 或者 `docker-engine`,使用以下命令卸载旧版本:
|
||||
|
||||
@@ -22,7 +26,7 @@ $ sudo apt-get remove docker \
|
||||
docker.io
|
||||
```
|
||||
|
||||
## 使用 APT 安装
|
||||
### 3.2.2 使用 APT 安装
|
||||
|
||||
由于 apt 源使用 HTTPS 以确保软件下载过程中不被篡改。因此,我们首先需要添加使用 HTTPS 传输的软件包以及 CA 证书。
|
||||
|
||||
@@ -45,13 +49,15 @@ $ sudo apt-get install \
|
||||
$ curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/debian/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
|
||||
|
||||
|
||||
# 官方源
|
||||
# $ curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
|
||||
## 官方源
|
||||
|
||||
## $ curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
|
||||
|
||||
```
|
||||
|
||||
然后,我们需要向 `sources.list` 中添加 Docker 软件源:
|
||||
|
||||
> 在一些基于 Debian 的 Linux 发行版中 `$(lsb_release -cs)` 可能不会返回 Debian 的版本代号,例如 [Kail Linux](https://www.kali.org/docs/policy/kali-linux-relationship-with-debian/)、 [BunsenLabs Linux](https://www.bunsenlabs.org/)。在这些发行版中我们需要将下面命令中的 `$(lsb_release -cs)` 替换为 https://mirrors.aliyun.com/docker-ce/linux/debian/dists/ 中支持的 Debian 版本代号,例如 `buster`。
|
||||
> 在一些基于 Debian 的 Linux 发行版中 `$(lsb_release -cs)` 可能不会返回 Debian 的版本代号,例如 [Kali Linux](https://www.kali.org/docs/policy/kali-linux-relationship-with-debian/)、[BunsenLabs Linux](https://www.bunsenlabs.org/)。在这些发行版中我们需要将下面命令中的 `$(lsb_release -cs)` 替换为 https://mirrors.aliyun.com/docker-ce/linux/debian/dists/ 中支持的 Debian 版本代号,例如 `buster`。
|
||||
|
||||
```bash
|
||||
$ echo \
|
||||
@@ -59,16 +65,19 @@ $ echo \
|
||||
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||
|
||||
|
||||
# 官方源
|
||||
# $ echo \
|
||||
# "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian \
|
||||
# $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||
## 官方源
|
||||
|
||||
## $ echo \
|
||||
|
||||
## "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian \
|
||||
|
||||
## $ stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||
|
||||
```
|
||||
|
||||
>以上命令会添加稳定版本的 Docker APT 源,如果需要测试版本的 Docker 请将 stable 改为 test。 Debian11可能不使用`/etc/apt/keyrings/`, 如gpg错误可以考虑更换为`/etc/apt/trusted.gpg.d`, 见[issue 15727](https://github.com/docker/docs/issues/15727)。
|
||||
>以上命令会添加稳定版本的 Docker APT 源,如果需要测试版本的 Docker 请将 stable 改为 test。Debian11 可能不使用 `/etc/apt/keyrings/`,如 gpg 错误可以考虑更换为 `/etc/apt/trusted.gpg.d`,见 [issue 15727](https://github.com/docker/docs/issues/15727)。
|
||||
|
||||
### 安装 Docker
|
||||
#### 安装 Docker
|
||||
|
||||
更新 apt 软件包缓存,并安装 `docker-ce`。
|
||||
|
||||
@@ -78,31 +87,38 @@ $ sudo apt-get update
|
||||
$ sudo apt-get install docker-ce docker-ce-cli containerd.io
|
||||
```
|
||||
|
||||
## 使用脚本自动安装
|
||||
### 3.2.3 使用脚本自动安装
|
||||
|
||||
在测试或开发环境中 Docker 官方为了简化安装流程,提供了一套便捷的安装脚本,Debian 系统上可以使用这套脚本安装,另外可以通过 `--mirror` 选项使用国内源进行安装:
|
||||
|
||||
> 若你想安装测试版的 Docker, 请从 test.docker.com 获取脚本
|
||||
> 若你想安装测试版的 Docker,请从 test.docker.com 获取脚本
|
||||
|
||||
```bash
|
||||
# $ curl -fsSL test.docker.com -o get-docker.sh
|
||||
## $ 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
|
||||
## $ sudo sh get-docker.sh --mirror AzureChinaCloud
|
||||
|
||||
```
|
||||
|
||||
执行这个命令后,脚本就会自动的将一切准备工作做好,并且把 Docker 的稳定(stable)版本安装在系统中。
|
||||
执行这个命令后,脚本就会自动的将一切准备工作做好,并且把 Docker 的稳定 (stable) 版本安装在系统中。
|
||||
|
||||
## 启动 Docker
|
||||
### 3.2.4 启动 Docker
|
||||
|
||||
```bash
|
||||
$ sudo systemctl enable docker
|
||||
$ sudo systemctl start docker
|
||||
```
|
||||
|
||||
## 建立 docker 用户组
|
||||
### 3.2.5 建立 docker 用户组
|
||||
|
||||
默认情况下,`docker` 命令会使用 [Unix socket](https://en.wikipedia.org/wiki/Unix_domain_socket) 与 Docker 引擎通讯。而只有 `root` 用户和 `docker` 组的用户才可以访问 Docker 引擎的 Unix socket。出于安全考虑,一般 Linux 系统上不会直接使用 `root` 用户。因此,更好地做法是将需要使用 `docker` 的用户加入 `docker` 用户组。
|
||||
默认情况下,`docker` 命令会使用 [Unix socket](https://en.wikipedia.org/wiki/Unix_domain_socket) 与 Docker 引擎通讯。而只有 `root` 用户和 `docker` 组的用户才可以访问 Docker 引擎的 Unix socket。出于安全考虑,一般 Linux 系统上不会直接使用 `root` 用户。因此,更好的做法是将需要使用 `docker` 的用户加入 `docker` 用户组。
|
||||
|
||||
> ⚠️ **安全警告:`docker` 用户组等同于 `root` 权限**
|
||||
>
|
||||
> 将用户加入 `docker` 组免去了每次执行 `docker` 命令时输入 `sudo` 的繁琐,但这也意味着该用户可以轻易获取主机的最高 root 权限(例如通过挂载根目录运行容器)。
|
||||
> 如果你在一个多用户共享的生产系统上配置,切勿随意将普通用户加入此组。此时,更安全的替代方案是使用官方提供的 **[Rootless 模式 (Rootless mode)](https://docs.docker.com/engine/security/rootless/)**,它允许在没有任何 root 权限的情况下运行 Docker 守护进程和容器。
|
||||
|
||||
建立 `docker` 组:
|
||||
|
||||
@@ -118,7 +134,7 @@ $ sudo usermod -aG docker $USER
|
||||
|
||||
退出当前终端并重新登录,进行如下测试。
|
||||
|
||||
## 测试 Docker 是否安装正确
|
||||
### 3.2.6 测试 Docker 是否安装正确
|
||||
|
||||
```bash
|
||||
$ docker run --rm hello-world
|
||||
@@ -153,10 +169,10 @@ For more examples and ideas, visit:
|
||||
|
||||
若能正常输出以上信息,则说明安装成功。
|
||||
|
||||
## 镜像加速
|
||||
### 3.2.7 镜像加速
|
||||
|
||||
如果在使用过程中发现拉取 Docker 镜像十分缓慢,可以配置 Docker [国内镜像加速](mirror.md)。
|
||||
如果在使用过程中发现拉取 Docker 镜像十分缓慢,可以配置 Docker [国内镜像加速](3.9_mirror.md)。
|
||||
|
||||
## 参考文档
|
||||
### 3.2.8 参考文档
|
||||
|
||||
* [Docker 官方 Debian 安装文档](https://docs.docker.com/install/linux/docker-ce/debian/)
|
||||
* [Docker 官方 Debian 安装文档](https://docs.docker.com/engine/install/debian/)
|
||||
@@ -1,10 +1,14 @@
|
||||
# Fedora 安装 Docker
|
||||
## 3.3 Fedora
|
||||
|
||||
>警告:切勿在没有配置 Docker dnf 源的情况下直接使用 dnf 命令安装 Docker.
|
||||
Fedora 作为技术前沿的 Linux 发行版,对 Docker 有着良好的支持。本节介绍在 Fedora 上的安装步骤。
|
||||
|
||||
## 准备工作
|
||||
>警告:切勿在没有配置 Docker dnf 源的情况下直接使用 dnf 命令安装 Docker。
|
||||
|
||||
### 系统要求
|
||||
### 3.3.1 准备工作
|
||||
|
||||
确保你的 Fedora 版本在支持列表中,并清理旧版本。
|
||||
|
||||
#### 系统要求
|
||||
|
||||
Docker 支持以下版本的 [Fedora](https://getfedora.org/) 操作系统:
|
||||
|
||||
@@ -12,7 +16,7 @@ Docker 支持以下版本的 [Fedora](https://getfedora.org/) 操作系统:
|
||||
* 42
|
||||
* 43
|
||||
|
||||
### 卸载旧版本
|
||||
#### 卸载旧版本
|
||||
|
||||
旧版本的 Docker 称为 `docker` 或者 `docker-engine`,使用以下命令卸载旧版本:
|
||||
|
||||
@@ -29,7 +33,9 @@ $ sudo dnf remove docker \
|
||||
docker-engine
|
||||
```
|
||||
|
||||
## 使用 dnf 安装
|
||||
### 3.3.2 使用 dnf 安装
|
||||
|
||||
使用 dnf 包管理器安装是推荐的方式,便于后续的更行和管理。
|
||||
|
||||
执行以下命令安装依赖包:
|
||||
|
||||
@@ -48,10 +54,14 @@ $ sudo dnf config-manager \
|
||||
|
||||
$ sudo sed -i 's/download.docker.com/mirrors.aliyun.com\/docker-ce/g' /etc/yum.repos.d/docker-ce.repo
|
||||
|
||||
# 官方源
|
||||
# $ sudo dnf config-manager \
|
||||
# --add-repo \
|
||||
# https://download.docker.com/linux/fedora/docker-ce.repo
|
||||
## 官方源
|
||||
|
||||
## $ sudo dnf config-manager \
|
||||
|
||||
## --add-repo \
|
||||
|
||||
## https://download.docker.com/linux/fedora/docker-ce.repo
|
||||
|
||||
```
|
||||
|
||||
如果需要测试版本的 Docker 请使用以下命令:
|
||||
@@ -66,7 +76,7 @@ $ sudo dnf config-manager --set-enabled docker-ce-test
|
||||
$ sudo dnf config-manager --set-disabled docker-ce-test
|
||||
```
|
||||
|
||||
### 安装 Docker
|
||||
#### 安装 Docker
|
||||
|
||||
更新 `dnf` 软件源缓存,并安装 `docker-ce`。
|
||||
|
||||
@@ -85,31 +95,38 @@ docker-ce.x86_64 18.06.1.ce-3.fc28 docker-ce-stable
|
||||
$ sudo dnf -y install docker-ce-18.06.1.ce
|
||||
```
|
||||
|
||||
## 使用脚本自动安装
|
||||
### 3.3.3 使用脚本自动安装
|
||||
|
||||
在测试或开发环境中 Docker 官方为了简化安装流程,提供了一套便捷的安装脚本,Debian 系统上可以使用这套脚本安装,另外可以通过 `--mirror` 选项使用国内源进行安装:
|
||||
在测试或开发环境中 Docker 官方为了简化安装流程,提供了一套便捷的安装脚本,Fedora 系统上可以使用这套脚本安装,另外可以通过 `--mirror` 选项使用国内源进行安装:
|
||||
|
||||
> 若你想安装测试版的 Docker, 请从 test.docker.com 获取脚本
|
||||
> 若你想安装测试版的 Docker,请从 test.docker.com 获取脚本
|
||||
|
||||
```bash
|
||||
# $ curl -fsSL test.docker.com -o get-docker.sh
|
||||
## $ 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
|
||||
## $ sudo sh get-docker.sh --mirror AzureChinaCloud
|
||||
|
||||
```
|
||||
|
||||
执行这个命令后,脚本就会自动的将一切准备工作做好,并且把 Docker 最新稳定(stable)版本安装在系统中。
|
||||
执行这个命令后,脚本就会自动的将一切准备工作做好,并且把 Docker 最新稳定 (stable) 版本安装在系统中。
|
||||
|
||||
## 启动 Docker
|
||||
### 3.3.4 启动 Docker
|
||||
|
||||
```bash
|
||||
$ sudo systemctl enable docker
|
||||
$ sudo systemctl start docker
|
||||
```
|
||||
|
||||
## 建立 docker 用户组
|
||||
### 3.3.5 建立 docker 用户组
|
||||
|
||||
默认情况下,`docker` 命令会使用 [Unix socket](https://en.wikipedia.org/wiki/Unix_domain_socket) 与 Docker 引擎通讯。而只有 `root` 用户和 `docker` 组的用户才可以访问 Docker 引擎的 Unix socket。出于安全考虑,一般 Linux 系统上不会直接使用 `root` 用户。因此,更好地做法是将需要使用 `docker` 的用户加入 `docker` 用户组。
|
||||
默认情况下,`docker` 命令会使用 [Unix socket](https://en.wikipedia.org/wiki/Unix_domain_socket) 与 Docker 引擎通讯。而只有 `root` 用户和 `docker` 组的用户才可以访问 Docker 引擎的 Unix socket。出于安全考虑,一般 Linux 系统上不会直接使用 `root` 用户。因此,更好的做法是将需要使用 `docker` 的用户加入 `docker` 用户组。
|
||||
|
||||
> ⚠️ **安全警告:`docker` 用户组等同于 `root` 权限**
|
||||
>
|
||||
> 将用户加入 `docker` 组免去了每次执行 `docker` 命令时输入 `sudo` 的繁琐,但这也意味着该用户可以轻易获取主机的最高 root 权限(例如通过挂载根目录运行容器)。
|
||||
> 如果你在一个多用户共享的生产系统上配置,切勿随意将普通用户加入此组。此时,更安全的替代方案是使用官方提供的 **[Rootless 模式 (Rootless mode)](https://docs.docker.com/engine/security/rootless/)**,它允许在没有任何 root 权限的情况下运行 Docker 守护进程和容器。
|
||||
|
||||
建立 `docker` 组:
|
||||
|
||||
@@ -125,7 +142,7 @@ $ sudo usermod -aG docker $USER
|
||||
|
||||
退出当前终端并重新登录,进行如下测试。
|
||||
|
||||
## 测试 Docker 是否安装正确
|
||||
### 3.3.6 测试 Docker 是否安装正确
|
||||
|
||||
```bash
|
||||
$ docker run --rm hello-world
|
||||
@@ -160,10 +177,10 @@ For more examples and ideas, visit:
|
||||
|
||||
若能正常输出以上信息,则说明安装成功。
|
||||
|
||||
## 镜像加速
|
||||
### 3.3.7 镜像加速
|
||||
|
||||
如果在使用过程中发现拉取 Docker 镜像十分缓慢,可以配置 Docker [国内镜像加速](mirror.md)。
|
||||
如果在使用过程中发现拉取 Docker 镜像十分缓慢,可以配置 Docker [国内镜像加速](3.9_mirror.md)。
|
||||
|
||||
## 参考文档
|
||||
### 3.3.8 参考文档
|
||||
|
||||
* [Docker 官方 Fedora 安装文档](https://docs.docker.com/install/linux/docker-ce/fedora)。
|
||||
* [Docker 官方 Fedora 安装文档](https://docs.docker.com/engine/install/fedora/)。
|
||||
@@ -1,10 +1,14 @@
|
||||
# CentOS 安装 Docker
|
||||
## 3.4 CentOS
|
||||
|
||||
>警告:切勿在没有配置 Docker YUM 源的情况下直接使用 yum 命令安装 Docker.
|
||||
CentOS (及其替代品 Rocky Linux、AlmaLinux) 是企业级服务器常用的操作系统。本节介绍在这些系统上安装 Docker 的步骤。
|
||||
|
||||
## 准备工作
|
||||
>警告:切勿在没有配置 Docker YUM 源的情况下直接使用 yum 命令安装 Docker。
|
||||
|
||||
### 系统要求
|
||||
### 3.4.1 准备工作
|
||||
|
||||
安装前请确认系统版本和内核版本满足 Docker 的运行要求。
|
||||
|
||||
#### 系统要求
|
||||
|
||||
> ⚠️ **重要提示**:CentOS 8 已于 2021 年 12 月 31 日停止维护,CentOS 7 已于 2024 年 6 月 30 日结束支持。建议新项目使用 **Rocky Linux** 或 **AlmaLinux** 作为替代。
|
||||
|
||||
@@ -12,7 +16,7 @@ Docker 支持 64 位版本 CentOS Stream 9、Rocky Linux 8/9、AlmaLinux 8/9,
|
||||
|
||||
对于 Rocky Linux、AlmaLinux 或 CentOS Stream,推荐使用 `dnf` 包管理器。
|
||||
|
||||
### 卸载旧版本
|
||||
#### 卸载旧版本
|
||||
|
||||
旧版本的 Docker 称为 `docker` 或者 `docker-engine`,使用以下命令卸载旧版本:
|
||||
|
||||
@@ -31,7 +35,9 @@ $ sudo yum remove docker \
|
||||
containerd.io
|
||||
```
|
||||
|
||||
## 使用 yum 安装
|
||||
### 3.4.2 使用 yum 安装
|
||||
|
||||
使用 yum/dnf 安装是管理 Docker 生命周期的标准方式。
|
||||
|
||||
执行以下命令安装依赖包:
|
||||
|
||||
@@ -48,21 +54,25 @@ $ sudo dnf config-manager \
|
||||
--add-repo \
|
||||
https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
|
||||
|
||||
$ sudo sed -i 's/download.docker.com/mirrors.aliyun.com\/docker-ce/g' /etc/dnf.repos.d/docker-ce.repo
|
||||
$ sudo sed -i 's/download.docker.com/mirrors.aliyun.com\/docker-ce/g' /etc/yum.repos.d/docker-ce.repo
|
||||
|
||||
## 官方源
|
||||
|
||||
## $ sudo dnf config-manager \
|
||||
|
||||
## --add-repo \
|
||||
|
||||
## https://download.docker.com/linux/centos/docker-ce.repo
|
||||
|
||||
# 官方源
|
||||
# $ sudo dnf config-manager \
|
||||
# --add-repo \
|
||||
# https://download.docker.com/linux/centos/docker-ce.repo
|
||||
```
|
||||
|
||||
如果需要测试版本的 Docker 请执行以下命令:
|
||||
|
||||
```bash
|
||||
$ sudo dnf config-manager --enable docker-ce-test
|
||||
$ sudo dnf config-manager --set-enabled docker-ce-test
|
||||
```
|
||||
|
||||
### 安装 Docker
|
||||
#### 安装 Docker
|
||||
|
||||
更新 `dnf` 软件源缓存,并安装 `docker-ce`。
|
||||
|
||||
@@ -70,14 +80,15 @@ $ sudo dnf config-manager --enable docker-ce-test
|
||||
$ sudo dnf install docker-ce docker-ce-cli containerd.io
|
||||
```
|
||||
|
||||
## CentOS8 额外设置
|
||||
### 3.4.3 CentOS8 额外设置
|
||||
|
||||
由于 CentOS8 防火墙使用了 `nftables`,但 Docker 尚未支持 `nftables`, 我们可以使用如下设置使用 `iptables`:
|
||||
CentOS 8/Stream 默认使用 `nftables`。Docker 在新版本中已提供 `nftables` 实验支持,但在一些环境下仍可能遇到兼容性问题。若你遇到容器网络异常,可以先切换回 `iptables` 后端:
|
||||
|
||||
更改 `/etc/firewalld/firewalld.conf`
|
||||
|
||||
```bash
|
||||
# FirewallBackend=nftables
|
||||
## FirewallBackend=nftables
|
||||
|
||||
FirewallBackend=iptables
|
||||
```
|
||||
|
||||
@@ -89,31 +100,38 @@ $ firewall-cmd --permanent --zone=trusted --add-interface=docker0
|
||||
$ firewall-cmd --reload
|
||||
```
|
||||
|
||||
## 使用脚本自动安装
|
||||
### 3.4.4 使用脚本自动安装
|
||||
|
||||
在测试或开发环境中 Docker 官方为了简化安装流程,提供了一套便捷的安装脚本,CentOS 系统上可以使用这套脚本安装,另外可以通过 `--mirror` 选项使用国内源进行安装:
|
||||
|
||||
> 若你想安装测试版的 Docker, 请从 test.docker.com 获取脚本
|
||||
> 若你想安装测试版的 Docker,请从 test.docker.com 获取脚本
|
||||
|
||||
```bash
|
||||
# $ curl -fsSL test.docker.com -o get-docker.sh
|
||||
## $ 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
|
||||
## $ sudo sh get-docker.sh --mirror AzureChinaCloud
|
||||
|
||||
```
|
||||
|
||||
执行这个命令后,脚本就会自动的将一切准备工作做好,并且把 Docker 的稳定(stable)版本安装在系统中。
|
||||
执行这个命令后,脚本就会自动的将一切准备工作做好,并且把 Docker 的稳定 (stable) 版本安装在系统中。
|
||||
|
||||
## 启动 Docker
|
||||
### 3.4.5 启动 Docker
|
||||
|
||||
```bash
|
||||
$ sudo systemctl enable docker
|
||||
$ sudo systemctl start docker
|
||||
```
|
||||
|
||||
## 建立 docker 用户组
|
||||
### 3.4.6 建立 docker 用户组
|
||||
|
||||
默认情况下,`docker` 命令会使用 [Unix socket](https://en.wikipedia.org/wiki/Unix_domain_socket) 与 Docker 引擎通讯。而只有 `root` 用户和 `docker` 组的用户才可以访问 Docker 引擎的 Unix socket。出于安全考虑,一般 Linux 系统上不会直接使用 `root` 用户。因此,更好地做法是将需要使用 `docker` 的用户加入 `docker` 用户组。
|
||||
默认情况下,`docker` 命令会使用 [Unix socket](https://en.wikipedia.org/wiki/Unix_domain_socket) 与 Docker 引擎通讯。而只有 `root` 用户和 `docker` 组的用户才可以访问 Docker 引擎的 Unix socket。出于安全考虑,一般 Linux 系统上不会直接使用 `root` 用户。因此,更好的做法是将需要使用 `docker` 的用户加入 `docker` 用户组。
|
||||
|
||||
> ⚠️ **安全警告:`docker` 用户组等同于 `root` 权限**
|
||||
>
|
||||
> 将用户加入 `docker` 组免去了每次执行 `docker` 命令时输入 `sudo` 的繁琐,但这也意味着该用户可以轻易获取主机的最高 root 权限(例如通过挂载根目录运行容器)。
|
||||
> 如果你在一个多用户共享的生产系统上配置,切勿随意将普通用户加入此组。此时,更安全的替代方案是使用官方提供的 **[Rootless 模式 (Rootless mode)](https://docs.docker.com/engine/security/rootless/)**,它允许在没有任何 root 权限的情况下运行 Docker 守护进程和容器。
|
||||
|
||||
建立 `docker` 组:
|
||||
|
||||
@@ -129,7 +147,7 @@ $ sudo usermod -aG docker $USER
|
||||
|
||||
退出当前终端并重新登录,进行如下测试。
|
||||
|
||||
## 测试 Docker 是否安装正确
|
||||
### 3.4.7 测试 Docker 是否安装正确
|
||||
|
||||
```bash
|
||||
$ docker run --rm hello-world
|
||||
@@ -164,11 +182,11 @@ For more examples and ideas, visit:
|
||||
|
||||
若能正常输出以上信息,则说明安装成功。
|
||||
|
||||
## 镜像加速
|
||||
### 3.4.8 镜像加速
|
||||
|
||||
如果在使用过程中发现拉取 Docker 镜像十分缓慢,可以配置 Docker [国内镜像加速](mirror.md)。
|
||||
如果在使用过程中发现拉取 Docker 镜像十分缓慢,可以配置 Docker [国内镜像加速](3.9_mirror.md)。
|
||||
|
||||
## 添加内核参数
|
||||
### 3.4.9 添加内核参数
|
||||
|
||||
如果在 CentOS 使用 Docker 看到下面的这些警告信息:
|
||||
|
||||
@@ -192,8 +210,8 @@ EOF
|
||||
$ sudo sysctl -p
|
||||
```
|
||||
|
||||
## 参考文档
|
||||
### 3.4.10 参考文档
|
||||
|
||||
* [Docker 官方 CentOS 安装文档](https://docs.docker.com/install/linux/docker-ce/centos/)。
|
||||
* [Docker 官方 CentOS 安装文档](https://docs.docker.com/engine/install/centos/)。
|
||||
* https://firewalld.org/2018/07/nftables-backend
|
||||
* https://github.com/moby/libnetwork/issues/2496
|
||||
@@ -1,8 +1,12 @@
|
||||
# 树莓派卡片电脑安装 Docker
|
||||
## 3.5 Raspberry Pi
|
||||
|
||||
>警告:切勿在没有配置 Docker APT 源的情况下直接使用 apt 命令安装 Docker.
|
||||
树莓派等 ARM 架构设备在物联网和边缘计算领域应用广泛。本节介绍如何在树莓派上安装 Docker。
|
||||
|
||||
## 系统要求
|
||||
>警告:切勿在没有配置 Docker APT 源的情况下直接使用 apt 命令安装 Docker。
|
||||
|
||||
### 3.5.1 系统要求
|
||||
|
||||
Docker 对 ARM 架构有着良好的支持。
|
||||
|
||||
Docker 不仅支持 `x86_64` 架构的计算机,同时也支持 `ARM` 架构的计算机,本小节内容以树莓派单片电脑为例讲解 `ARM` 架构安装 Docker。
|
||||
|
||||
@@ -12,9 +16,11 @@ Docker 支持以下版本的 [Raspberry Pi OS](https://www.raspberrypi.org/softw
|
||||
* Raspberry Pi OS Bookworm
|
||||
* Raspberry Pi OS Bullseye
|
||||
|
||||
*注:* `Raspberry Pi OS` 由树莓派的开发与维护机构 [树莓派基金会](https://www.raspberrypi.org/) 官方支持,并推荐用作树莓派的首选系统,其基于 `Debian`。
|
||||
*注:*`Raspberry Pi OS` 由树莓派的开发与维护机构[树莓派基金会](https://www.raspberrypi.org/)官方支持,并推荐用作树莓派的首选系统,其基于 `Debian`。
|
||||
|
||||
## 使用 APT 安装
|
||||
### 3.5.2 使用 APT 安装
|
||||
|
||||
推荐使用 APT 包管理器进行安装,以确保版本的稳定性和安全性。
|
||||
|
||||
由于 apt 源使用 HTTPS 以确保软件下载过程中不被篡改。因此,我们首先需要添加使用 HTTPS 传输的软件包以及 CA 证书。
|
||||
|
||||
@@ -25,9 +31,8 @@ $ sudo apt-get install \
|
||||
apt-transport-https \
|
||||
ca-certificates \
|
||||
curl \
|
||||
gnupg2 \
|
||||
lsb-release \
|
||||
software-properties-common
|
||||
gnupg \
|
||||
lsb-release
|
||||
```
|
||||
|
||||
鉴于国内网络问题,强烈建议使用国内源,官方源请在注释中查看。
|
||||
@@ -35,34 +40,44 @@ $ sudo apt-get install \
|
||||
为了确认所下载软件包的合法性,需要添加软件源的 GPG 密钥。
|
||||
|
||||
```bash
|
||||
$ curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/raspbian/gpg | sudo apt-key add -
|
||||
$ 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
|
||||
|
||||
|
||||
# 官方源
|
||||
# $ curl -fsSL https://download.docker.com/linux/raspbian/gpg | sudo apt-key add -
|
||||
## 官方源
|
||||
|
||||
## $ 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
|
||||
$ sudo add-apt-repository \
|
||||
"deb [arch=armhf] https://mirrors.aliyun.com/docker-ce/linux/raspbian \
|
||||
$(lsb_release -cs) \
|
||||
stable"
|
||||
$ 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
|
||||
|
||||
|
||||
# 官方源
|
||||
# $ sudo add-apt-repository \
|
||||
# "deb [arch=armhf] https://download.docker.com/linux/raspbian \
|
||||
# $(lsb_release -cs) \
|
||||
# stable"
|
||||
## 官方源
|
||||
|
||||
## $ 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` 中,添加 Docker 软件源的步骤可能会出现如下报错:
|
||||
在 `Raspberry Pi OS Bullseye/Bookworm` 中,如果你使用 `add-apt-repository` 添加源,可能会出现如下报错 (推荐改用上面的 `tee` 方式来写入 `/etc/apt/sources.list.d/docker.list`,可避免此问题):
|
||||
|
||||
```bash
|
||||
Traceback (most recent call last):
|
||||
@@ -77,17 +92,19 @@ Traceback (most recent call last):
|
||||
aptsources.distro.NoDistroTemplateException: Error: could not find a distribution template for Raspbian/bullseye
|
||||
```
|
||||
|
||||
通过以下命令手动添加镜像源到 `/etc/apt/sources.list` 文件中即可解决:
|
||||
通过以下命令手动添加镜像源到 `/etc/apt/sources.list` 文件中即可解决:
|
||||
|
||||
```bash
|
||||
$ sudo echo "deb [arch=armhf] https://mirrors.aliyun.com/docker-ce/linux/raspbian $(lsb_release -cs) stable" | sudo tee -a /etc/apt/sources.list
|
||||
$ 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
|
||||
|
||||
|
||||
# 官方源
|
||||
# $ sudo echo "deb [arch=armhf] https://download.docker.com/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
|
||||
#### 安装 Docker
|
||||
|
||||
更新 apt 软件包缓存,并安装 `docker-ce`。
|
||||
|
||||
@@ -97,31 +114,38 @@ $ sudo apt-get update
|
||||
$ sudo apt-get install docker-ce
|
||||
```
|
||||
|
||||
## 使用脚本自动安装
|
||||
### 3.5.3 使用脚本自动安装
|
||||
|
||||
在测试或开发环境中 Docker 官方为了简化安装流程,提供了一套便捷的安装脚本,Raspberry Pi OS 系统上可以使用这套脚本安装,另外可以通过 `--mirror` 选项使用国内源进行安装:
|
||||
|
||||
> 若你想安装测试版的 Docker, 请从 test.docker.com 获取脚本
|
||||
> 若你想安装测试版的 Docker,请从 test.docker.com 获取脚本
|
||||
|
||||
```bash
|
||||
# $ curl -fsSL test.docker.com -o get-docker.sh
|
||||
## $ 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
|
||||
## $ sudo sh get-docker.sh --mirror AzureChinaCloud
|
||||
|
||||
```
|
||||
|
||||
执行这个命令后,脚本就会自动的将一切准备工作做好,并且把 Docker 的稳定(stable)版本安装在系统中。
|
||||
执行这个命令后,脚本就会自动的将一切准备工作做好,并且把 Docker 的稳定 (stable) 版本安装在系统中。
|
||||
|
||||
## 启动 Docker
|
||||
### 3.5.4 启动 Docker
|
||||
|
||||
```bash
|
||||
$ sudo systemctl enable docker
|
||||
$ sudo systemctl start docker
|
||||
```
|
||||
|
||||
## 建立 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` 命令会使用 [Unix socket](https://en.wikipedia.org/wiki/Unix_domain_socket) 与 Docker 引擎通讯。而只有 `root` 用户和 `docker` 组的用户才可以访问 Docker 引擎的 Unix socket。出于安全考虑,一般 Linux 系统上不会直接使用 `root` 用户。因此,更好的做法是将需要使用 `docker` 的用户加入 `docker` 用户组。
|
||||
|
||||
> ⚠️ **安全警告:`docker` 用户组等同于 `root` 权限**
|
||||
>
|
||||
> 将用户加入 `docker` 组免去了每次执行 `docker` 命令时输入 `sudo` 的繁琐,但这也意味着该用户可以轻易获取主机的最高 root 权限(例如通过挂载根目录运行容器)。
|
||||
> 如果你在一个多用户共享的生产系统上配置,切勿随意将普通用户加入此组。此时,更安全的替代方案是使用官方提供的 **[Rootless 模式 (Rootless mode)](https://docs.docker.com/engine/security/rootless/)**,它允许在没有任何 root 权限的情况下运行 Docker 守护进程和容器。
|
||||
|
||||
建立 `docker` 组:
|
||||
|
||||
@@ -137,7 +161,7 @@ $ sudo usermod -aG docker $USER
|
||||
|
||||
退出当前终端并重新登录,进行如下测试。
|
||||
|
||||
## 测试 Docker 是否安装正确
|
||||
### 3.5.6 测试 Docker 是否安装正确
|
||||
|
||||
```bash
|
||||
$ docker run --rm hello-world
|
||||
@@ -172,8 +196,8 @@ For more examples and ideas, visit:
|
||||
|
||||
若能正常输出以上信息,则说明安装成功。
|
||||
|
||||
*注意:* ARM 平台不能使用 `x86` 镜像,查看 Raspberry Pi OS 可使用镜像请访问 [arm32v7](https://hub.docker.com/u/arm32v7/) 或者 [arm64v8](https://hub.docker.com/u/arm64v8/)。
|
||||
*注意:*ARM 平台不能使用 `x86` 镜像,查看 Raspberry Pi OS 可使用镜像请访问 [arm32v7](https://hub.docker.com/u/arm32v7/) 或者 [arm64v8](https://hub.docker.com/u/arm64v8/)。
|
||||
|
||||
## 镜像加速
|
||||
### 3.5.7 镜像加速
|
||||
|
||||
如果在使用过程中发现拉取 Docker 镜像十分缓慢,可以配置 Docker [国内镜像加速](mirror.md)。
|
||||
如果在使用过程中发现拉取 Docker 镜像十分缓慢,可以配置 Docker [国内镜像加速](3.9_mirror.md)。
|
||||
@@ -1,22 +1,22 @@
|
||||
# Linux 离线安装
|
||||
## 3.6 Linux 离线安装
|
||||
|
||||
\[TOC]
|
||||
|
||||
生产环境中一般都是没有公网资源的,本文介绍如何在生产服务器上离线部署`Docker`
|
||||
生产环境中一般都是没有公网资源的,本文介绍如何在生产服务器上离线部署 `Docker`
|
||||
|
||||
括号内的字母表示该操作需要在哪些服务器上执行
|
||||
|
||||

|
||||
|
||||
## CentOS/Rocky/AlmaLinux 离线安装Docker
|
||||
### 3.6.1 CentOS/Rocky/AlmaLinux 离线安装 Docker
|
||||
|
||||
在无法连接外网的安全环境中,离线安装是唯一的选择。本节介绍如何在 RHEL 系发行版中进行离线安装。
|
||||
|
||||
> 注意:以下命令以 CentOS 7 为例。对于 CentOS Stream 9、Rocky Linux 9 或 AlmaLinux 9,请将 `yum` 替换为 `dnf`,并将软件包后缀 `el7` 替换为 `el9`。
|
||||
|
||||
### YUM本地文件安装(推荐)
|
||||
#### YUM 本地文件安装 (推荐)
|
||||
|
||||
推荐这种方式,是因为在生产环境种一般会选定某个指定的文档软件版本使用。
|
||||
推荐这种方式,是因为在生产环境中一般会选定某个指定的文档软件版本使用。
|
||||
|
||||
#### 查询可用的软件版本(A)
|
||||
##### 查询可用的软件版本
|
||||
|
||||
```bash
|
||||
#下载清华的镜像源文件
|
||||
@@ -43,7 +43,7 @@ docker-ce.x86_64 3:19.03.1-3.el7 docker-ce-stable
|
||||
....
|
||||
```
|
||||
|
||||
#### 下载到指定文件夹(A)
|
||||
##### 下载到指定文件夹
|
||||
|
||||
```bash
|
||||
sudo yum install --downloadonly --downloaddir=/tmp/docker24_offline_install/ docker-ce-24.0.4-1.el7 docker-ce-cli-24.0.4-1.el7
|
||||
@@ -78,15 +78,15 @@ Total
|
||||
exiting because "Download Only" specified
|
||||
```
|
||||
|
||||
#### 复制到目标服务器之后进入文件夹安装(C-N)
|
||||
##### 复制到目标服务器之后进入文件夹安装 (C-N)
|
||||
|
||||
* 离线安装时,必须使用rpm命令不检查依赖的方式安装
|
||||
* 离线安装时,必须使用 rpm 命令不检查依赖的方式安装
|
||||
|
||||
```bash
|
||||
rpm -Uvh *.rpm --nodeps --force
|
||||
```
|
||||
|
||||
#### 锁定软件版本(C-N)
|
||||
##### 锁定软件版本 (C-N)
|
||||
|
||||
**下载锁定版本软件**
|
||||
|
||||
@@ -143,102 +143,117 @@ versionlock deleted: 1
|
||||
sudo yum versionlock delete all
|
||||
```
|
||||
|
||||
### YUM 本地源服务器搭建安装Docker
|
||||
#### YUM 本地源服务器搭建安装 Docker
|
||||
|
||||
#### 挂载 ISO 镜像搭建本地 File 源(AB)
|
||||
##### 挂载 ISO 镜像搭建本地 File 源
|
||||
|
||||
```bash
|
||||
# 删除其他网络源
|
||||
rm -f /etc/yum.repo.d/*
|
||||
# 挂载光盘或者iso镜像
|
||||
## 删除其他网络源
|
||||
|
||||
rm -f /etc/yum.repos.d/*
|
||||
## 挂载光盘或者iso镜像
|
||||
|
||||
mount /dev/cdrom /mnt
|
||||
```
|
||||
|
||||
```bash
|
||||
# 添加本地源
|
||||
## 添加本地源
|
||||
|
||||
cat >/etc/yum.repos.d/local_files.repo<< EOF
|
||||
[Local_Files]
|
||||
name=Local_Files
|
||||
baseurl=file:///mnt
|
||||
enable=1
|
||||
enabled=1
|
||||
gpgcheck=0
|
||||
gpgkey=file:///mnt/RPM-GPG-KEY-CentOS-7
|
||||
EOF
|
||||
```
|
||||
|
||||
```bash
|
||||
# 测试刚才的本地源,安装createrepo软件
|
||||
## 测试刚才的本地源,安装createrepo软件
|
||||
|
||||
yum clean all
|
||||
yum install createrepo -y
|
||||
```
|
||||
|
||||
#### 根据本地文件搭建BASE网络源(B)
|
||||
##### 根据本地文件搭建 BASE 网络源
|
||||
|
||||
```bash
|
||||
# 安装apache 服务器
|
||||
## 安装apache 服务器
|
||||
|
||||
yum install httpd -y
|
||||
# 挂载光盘
|
||||
## 挂载光盘
|
||||
|
||||
mount /dev/cdrom /mnt
|
||||
# 新建centos目录
|
||||
## 新建centos目录
|
||||
|
||||
mkdir /var/www/html/base
|
||||
# 复制光盘内的文件到刚才新建的目录
|
||||
## 复制光盘内的文件到刚才新建的目录
|
||||
|
||||
cp -R /mnt/Packages/* /var/www/html/base/
|
||||
createrepo /var/www/html/centos/
|
||||
createrepo /var/www/html/base/
|
||||
systemctl enable httpd
|
||||
systemctl start httpd
|
||||
```
|
||||
|
||||
#### 下载Docker-CE 镜像仓库(A)
|
||||
##### 下载 Docker-CE 镜像仓库
|
||||
|
||||
在有网络的服务器上下载Docker-ce镜像
|
||||
在有网络的服务器上下载 Docker-ce 镜像
|
||||
|
||||
```bash
|
||||
# 下载清华的镜像源文件
|
||||
## 下载清华的镜像源文件
|
||||
|
||||
wget -O /etc/yum.repos.d/docker-ce.repo https://download.docker.com/linux/centos/docker-ce.repo
|
||||
sudo sed -i 's+download.docker.com+mirrors.tuna.tsinghua.edu.cn/docker-ce+' /etc/yum.repos.d/docker-ce.repo
|
||||
```
|
||||
|
||||
```bash
|
||||
# 新建 docker-ce目录
|
||||
## 新建 docker-ce目录
|
||||
|
||||
mkdir /tmp/docker-ce/
|
||||
# 把镜像源同步到镜像文件中
|
||||
## 把镜像源同步到镜像文件中
|
||||
|
||||
reposync -r docker-ce-stable -p /tmp/docker-ce/
|
||||
```
|
||||
|
||||
#### 创建仓库索引(B)
|
||||
##### 创建仓库索引
|
||||
|
||||
把下载的 docker-ce 文件夹复制到离线的服务器
|
||||
|
||||
```bash
|
||||
# 把docker-ce 文件夹复制到/var/www/html/docker-ce
|
||||
# 重建索引
|
||||
## 把docker-ce 文件夹复制到/var/www/html/docker-ce
|
||||
|
||||
## 重建索引
|
||||
|
||||
createrepo /var/www/html/docker-ce/
|
||||
```
|
||||
|
||||
#### YUM 客户端设置(C...N)
|
||||
##### YUM 客户端设置 (C...N)
|
||||
|
||||
```bash
|
||||
rm -f /etc/yum.repo.d/*
|
||||
rm -f /etc/yum.repos.d/*
|
||||
cat >/etc/yum.repos.d/local_files.repo<< EOF
|
||||
[local_base]
|
||||
name=local_base
|
||||
# 改成B服务器地址
|
||||
## 改成B服务器地址
|
||||
|
||||
baseurl=http://x.x.x.x/base
|
||||
enable=1
|
||||
enabled=1
|
||||
gpgcheck=0
|
||||
proxy=_none_
|
||||
[docker_ce]
|
||||
name=docker_ce
|
||||
# 改成B服务器地址
|
||||
baseurl=http://x.x.x.x/base
|
||||
enable=1
|
||||
## 改成B服务器地址
|
||||
|
||||
baseurl=http://x.x.x.x/docker-ce
|
||||
enabled=1
|
||||
gpgcheck=0
|
||||
proxy=_none_
|
||||
EOF
|
||||
|
||||
```
|
||||
|
||||
#### Docker 安装(C...N)
|
||||
##### Docker 安装 (C...N)
|
||||
|
||||
```bash
|
||||
sudo yum makecache fast
|
||||
76
03_install/3.7_mac.md
Normal file
@@ -0,0 +1,76 @@
|
||||
## 3.7 macOS
|
||||
|
||||
### 3.7.1 系统要求
|
||||
|
||||
[Docker Desktop for Mac](https://docs.docker.com/docker-for-mac/) 要求系统最低为 macOS Sonoma 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` 文件夹即可 (其间需要输入用户密码)。
|
||||
|
||||

|
||||
|
||||
### 3.7.3 运行
|
||||
|
||||
从应用中找到 Docker 图标并点击运行。
|
||||
|
||||

|
||||
|
||||
运行之后,会在右上角菜单栏看到多了一个鲸鱼图标,这个图标表明了 Docker 的运行状态。
|
||||
|
||||

|
||||
|
||||
每次点击鲸鱼图标会弹出操作菜单。
|
||||
|
||||

|
||||
|
||||
之后,你可以在终端通过命令检查安装后的 Docker 版本。
|
||||
|
||||
```bash
|
||||
$ docker --version
|
||||
Docker version 27.0.3, build 7d4bcd8
|
||||
```
|
||||
|
||||
如果 `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 安装成功了。
|
||||
|
||||

|
||||
|
||||
要停止 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/)
|
||||
51
03_install/3.8_windows.md
Normal file
@@ -0,0 +1,51 @@
|
||||
## 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** 开始运行。
|
||||
|
||||

|
||||
|
||||
Docker 启动之后会在 Windows 任务栏出现鲸鱼图标。
|
||||
|
||||

|
||||
|
||||
等待片刻,当鲸鱼图标静止时,说明 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/)
|
||||
@@ -1,5 +1,18 @@
|
||||
# 安装 Docker
|
||||
# 第三章 安装 Docker
|
||||
|
||||
Docker 分为 `stable` `test` 和 `nightly` 三个更新频道。
|
||||
|
||||
官方网站上有各种环境下的 [安装指南](https://docs.docker.com/get-docker/),这里主要介绍 Docker 在 `Linux` 、`Windows 10` 和 `macOS` 上的安装。
|
||||
官方网站上有各种环境下的[安装指南](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)
|
||||
|
||||
|
Before Width: | Height: | Size: 118 KiB |
|
Before Width: | Height: | Size: 660 KiB |
|
Before Width: | Height: | Size: 291 KiB |
|
Before Width: | Height: | Size: 101 KiB |
|
Before Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 16 KiB |
@@ -1,71 +0,0 @@
|
||||
# macOS
|
||||
|
||||
## 系统要求
|
||||
|
||||
[Docker Desktop for Mac](https://docs.docker.com/docker-for-mac/) 要求系统最低为 macOS Sonora 14.0 或更高版本,建议升级到最新版本的 macOS。
|
||||
|
||||
## 安装
|
||||
|
||||
### 使用 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` 文件夹即可(其间需要输入用户密码)。
|
||||
|
||||

|
||||
|
||||
## 运行
|
||||
|
||||
从应用中找到 Docker 图标并点击运行。
|
||||
|
||||

|
||||
|
||||
运行之后,会在右上角菜单栏看到多了一个鲸鱼图标,这个图标表明了 Docker 的运行状态。
|
||||
|
||||

|
||||
|
||||
每次点击鲸鱼图标会弹出操作菜单。
|
||||
|
||||

|
||||
|
||||
之后,你可以在终端通过命令检查安装后的 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 安装成功了。
|
||||
|
||||

|
||||
|
||||
要停止 Nginx 服务器并删除执行下面的命令:
|
||||
|
||||
```bash
|
||||
$ docker stop webserver
|
||||
$ docker rm webserver
|
||||
```
|
||||
|
||||
## 镜像加速
|
||||
|
||||
如果在使用过程中发现拉取 Docker 镜像十分缓慢,可以配置 Docker [国内镜像加速](mirror.md)。
|
||||
|
||||
## 参考链接
|
||||
|
||||
* [官方文档](https://docs.docker.com/docker-for-mac/install/)
|
||||
30
03_install/summary.md
Normal file
@@ -0,0 +1,30 @@
|
||||
## 本章小结
|
||||
|
||||
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 架构 |
|
||||
| **离线环境** | 二进制包安装 | 适用于无法联网的服务器 |
|
||||
|
||||
### 安装后验证
|
||||
|
||||
安装完成后,运行以下命令验证 Docker 是否正常工作:
|
||||
|
||||
```bash
|
||||
$ docker version
|
||||
$ docker run --rm hello-world
|
||||
```
|
||||
|
||||
### 延伸阅读
|
||||
|
||||
- [镜像加速器](3.9_mirror.md):解决国内拉取镜像慢的问题
|
||||
- [开启实验特性](3.10_experimental.md):使用最新功能
|
||||
- [Docker Hub](../06_repository/6.1_dockerhub.md):官方镜像仓库
|
||||
---
|
||||
|
||||
> 📝 **发现错误或有改进建议?** 欢迎提交 [Issue](https://github.com/yeasy/docker_practice/issues) 或 [PR](https://github.com/yeasy/docker_practice/pulls)。
|
||||
@@ -1,46 +0,0 @@
|
||||
# Windows 10/11
|
||||
|
||||
## 系统要求
|
||||
|
||||
[Docker Desktop for Windows](https://docs.docker.com/docker-for-windows/install/) 支持 64 位版本的 Windows 11 或 Windows 10(需开启 Hyper-V),推荐使用 Windows 11。
|
||||
|
||||
## 安装
|
||||
|
||||
**手动下载安装**
|
||||
|
||||
点击以下 [链接](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
|
||||
```
|
||||
|
||||
## 在 WSL2 运行 Docker
|
||||
|
||||
若你的 Windows 版本为 Windows 10 专业版或家庭版 v1903 及以上版本可以使用 WSL2 运行 Docker,具体请查看 [Docker Desktop WSL 2 backend](https://docs.docker.com/docker-for-windows/wsl/)。
|
||||
|
||||
## 运行
|
||||
|
||||
在 Windows 搜索栏输入 **Docker** 点击 **Docker Desktop** 开始运行。
|
||||
|
||||

|
||||
|
||||
Docker 启动之后会在 Windows 任务栏出现鲸鱼图标。
|
||||
|
||||

|
||||
|
||||
等待片刻,当鲸鱼图标静止时,说明 Docker 启动成功,之后你可以打开 PowerShell 使用 Docker。
|
||||
|
||||
> 推荐使用 [Windows Terminal](https://docs.microsoft.com/zh-cn/windows/terminal/get-started) 在终端使用 Docker。
|
||||
|
||||
## 镜像加速
|
||||
|
||||
如果在使用过程中发现拉取 Docker 镜像十分缓慢,可以配置 Docker [国内镜像加速](mirror.md)。
|
||||
|
||||
## 参考链接
|
||||
|
||||
* [官方文档](https://docs.docker.com/docker-for-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/)
|
||||
@@ -1,6 +1,8 @@
|
||||
# 获取镜像
|
||||
## 4.1 获取镜像
|
||||
|
||||
## docker pull 命令
|
||||
从 Docker 镜像仓库获取镜像可谓是 Docker 运作的第一步。本节将介绍如何使用 `docker pull` 命令下载镜像,以及如何理解下载过程。
|
||||
|
||||
### 4.1.1 docker pull 命令
|
||||
|
||||
从镜像仓库获取镜像的命令是 `docker pull`:
|
||||
|
||||
@@ -8,9 +10,11 @@
|
||||
docker pull [选项] [Registry地址/]仓库名[:标签]
|
||||
```
|
||||
|
||||
### 镜像名称格式
|
||||
#### 镜像名称格式
|
||||
|
||||
```
|
||||
Docker 镜像名称由 Registry 地址、用户名、仓库名和标签组成。其标准格式如下:
|
||||
|
||||
```bash
|
||||
docker.io / library / ubuntu : 24.04
|
||||
────┬──── ───┬─── ──┬─── ──┬──
|
||||
│ │ │ │
|
||||
@@ -20,36 +24,44 @@ Registry地址 用户名 仓库名 标签
|
||||
|
||||
| 组成部分 | 说明 | 默认值 |
|
||||
|---------|------|--------|
|
||||
| Registry 地址 | 镜像仓库地址 | `docker.io`(Docker Hub) |
|
||||
| 用户名 | 镜像所属用户/组织 | `library`(官方镜像) |
|
||||
| Registry 地址 | 镜像仓库地址 | `docker.io` (Docker Hub)|
|
||||
| 用户名 | 镜像所属用户/组织 | `library` (官方镜像)|
|
||||
| 仓库名 | 镜像名称 | 必须指定 |
|
||||
| 标签 | 版本标识 | `latest` |
|
||||
|
||||
### 示例
|
||||
#### 示例
|
||||
|
||||
```bash
|
||||
# 完整格式
|
||||
## 完整格式
|
||||
|
||||
$ docker pull docker.io/library/ubuntu:24.04
|
||||
|
||||
# 省略 Registry(默认 Docker Hub)
|
||||
## 省略 Registry(默认 Docker Hub)
|
||||
|
||||
$ docker pull library/ubuntu:24.04
|
||||
|
||||
# 省略 library(官方镜像)
|
||||
## 省略 library(官方镜像)
|
||||
|
||||
$ docker pull ubuntu:24.04
|
||||
|
||||
# 省略标签(默认 latest)
|
||||
## 省略标签(默认 latest)
|
||||
|
||||
$ docker pull ubuntu
|
||||
|
||||
# 拉取第三方镜像
|
||||
## 拉取第三方镜像
|
||||
|
||||
$ docker pull bitnami/redis:latest
|
||||
|
||||
# 从其他 Registry 拉取
|
||||
## 从其他 Registry 拉取
|
||||
|
||||
$ docker pull ghcr.io/username/myapp:v1.0
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 下载过程解析
|
||||
### 4.1.2 下载过程解析
|
||||
|
||||
当我们执行 `docker pull` 命令时,Docker 会输出详细的下载进度。让我们以 `ubuntu:24.04` 为例来解析这些信息。
|
||||
|
||||
```bash
|
||||
$ docker pull ubuntu:24.04
|
||||
@@ -62,36 +74,37 @@ Status: Downloaded newer image for ubuntu:24.04
|
||||
docker.io/library/ubuntu:24.04
|
||||
```
|
||||
|
||||
### 输出解读
|
||||
#### 输出解读
|
||||
|
||||
| 输出内容 | 说明 |
|
||||
|---------|------|
|
||||
| `Pulling from library/ubuntu` | 正在从官方 ubuntu 仓库拉取 |
|
||||
| `92dc2a97ff99: Pull complete` | 各层的下载状态(显示层 ID 前 12 位) |
|
||||
| `92dc2a97ff99: Pull complete` | 各层的下载状态 (显示层 ID 前 12 位)|
|
||||
| `Digest: sha256:...` | 镜像内容的唯一摘要 |
|
||||
| `docker.io/library/ubuntu:24.04` | 镜像的完整名称 |
|
||||
|
||||
### 分层下载
|
||||
#### 分层下载
|
||||
|
||||
从输出可以看到,镜像是**分层下载**的:
|
||||
从输出可以看到,镜像是 **分层下载** 的:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ ubuntu:24.04 镜像 │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 第3层 c8299583700a ───────► 已存在,跳过下载 │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 第2层 be13a9d27eb8 ───────► 下载中... 完成 │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 第1层 92dc2a97ff99 ───────► 下载中... 完成 │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```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` 命令支持多种选项来满足不同的下载需求,例如下载所有标签、指定平台架构等。
|
||||
|
||||
| 选项 | 说明 | 示例 |
|
||||
|------|------|------|
|
||||
@@ -99,7 +112,7 @@ docker.io/library/ubuntu:24.04
|
||||
| `--platform` | 指定平台架构 | `docker pull --platform linux/arm64 nginx` |
|
||||
| `--quiet, -q` | 静默模式 | `docker pull -q nginx` |
|
||||
|
||||
### 指定平台
|
||||
#### 指定平台
|
||||
|
||||
在 Apple Silicon Mac 上拉取 x86 镜像:
|
||||
|
||||
@@ -109,15 +122,17 @@ $ 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"
|
||||
@@ -137,7 +152,7 @@ root@e7009c6ce357:/# exit
|
||||
|
||||
---
|
||||
|
||||
## 镜像加速
|
||||
### 4.1.5 镜像加速
|
||||
|
||||
从 Docker Hub 下载可能较慢。可以配置镜像加速器:
|
||||
|
||||
@@ -155,16 +170,18 @@ root@e7009c6ce357:/# exit
|
||||
|
||||
```bash
|
||||
$ sudo systemctl restart docker # Linux
|
||||
# 或在 Docker Desktop 中重启
|
||||
## 或在 Docker Desktop 中重启
|
||||
```
|
||||
|
||||
详见 [镜像加速器](../install/mirror.md) 章节。
|
||||
详见[镜像加速器](../03_install/3.9_mirror.md)章节。
|
||||
|
||||
---
|
||||
|
||||
## 验证镜像完整性
|
||||
### 4.1.6 验证镜像完整性
|
||||
|
||||
### 查看镜像摘要
|
||||
为了确保下载的镜像没有被篡改且内容一致,我们可以校验镜像的摘要 (Digest)。
|
||||
|
||||
#### 查看镜像摘要
|
||||
|
||||
```bash
|
||||
$ docker images --digests ubuntu
|
||||
@@ -172,7 +189,7 @@ REPOSITORY TAG DIGEST
|
||||
ubuntu 24.04 sha256:4bc3ae6596938cb0d9e5ac51a1152ec9dcac2a1c50829c74abd9c4361e321b26 ca2b0f26964c
|
||||
```
|
||||
|
||||
### 使用摘要拉取
|
||||
#### 使用摘要拉取
|
||||
|
||||
用摘要拉取可确保获取完全相同的镜像:
|
||||
|
||||
@@ -184,49 +201,38 @@ $ docker pull ubuntu@sha256:4bc3ae6596938cb0d9e5ac51a1152ec9dcac2a1c50829c74abd9
|
||||
|
||||
---
|
||||
|
||||
## 常见问题
|
||||
### 4.1.7 常见问题
|
||||
|
||||
### Q: 下载速度很慢
|
||||
在使用 `docker pull` 过程中,可能会遇到下载速度慢、镜像不存在或磁盘空间不足等问题。以下是一些常见问题的排查思路。
|
||||
|
||||
#### Q:下载速度很慢
|
||||
|
||||
1. 配置镜像加速器
|
||||
2. 检查网络连接
|
||||
3. 尝试拉取更小的镜像版本(如 `alpine` 变体)
|
||||
3. 尝试拉取更小的镜像版本 (如 `alpine` 变体)
|
||||
|
||||
### Q: 提示镜像不存在
|
||||
#### Q:提示镜像不存在
|
||||
|
||||
```bash
|
||||
Error: pull access denied, repository does not exist
|
||||
```
|
||||
|
||||
可能原因:
|
||||
|
||||
- 镜像名拼写错误
|
||||
- 私有镜像未登录(需要 `docker login`)
|
||||
- 私有镜像未登录 (需要 `docker login`)
|
||||
- 镜像确实不存在
|
||||
|
||||
### Q: 磁盘空间不足
|
||||
#### Q:磁盘空间不足
|
||||
|
||||
```bash
|
||||
# 清理未使用的镜像
|
||||
## 清理未使用的镜像
|
||||
|
||||
$ docker image prune
|
||||
|
||||
# 清理所有未使用资源
|
||||
## 清理所有未使用资源
|
||||
|
||||
$ docker system prune
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 本章小结
|
||||
|
||||
| 操作 | 命令 |
|
||||
|------|------|
|
||||
| 拉取镜像 | `docker pull 镜像名:标签` |
|
||||
| 拉取所有标签 | `docker pull -a 镜像名` |
|
||||
| 指定平台 | `docker pull --platform linux/amd64 镜像名` |
|
||||
| 用摘要拉取 | `docker pull 镜像名@sha256:...` |
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [列出镜像](list.md):查看本地镜像
|
||||
- [删除镜像](rm.md):清理本地镜像
|
||||
- [镜像加速器](../install/mirror.md):加速镜像下载
|
||||
- [Docker Hub](../repository/dockerhub.md):官方镜像仓库
|
||||
@@ -1,6 +1,8 @@
|
||||
# 列出镜像
|
||||
## 4.2 列出镜像
|
||||
|
||||
## 基本用法
|
||||
在下载了镜像后,我们可以使用 `docker image ls` 命令列出本地主机上的镜像。
|
||||
|
||||
### 4.2.1 基本用法
|
||||
|
||||
查看本地已下载的镜像:
|
||||
|
||||
@@ -17,36 +19,40 @@ ubuntu noble 329ed837d508 3 days ago 78MB
|
||||
|
||||
---
|
||||
|
||||
## 输出字段说明
|
||||
### 4.2.2 输出字段说明
|
||||
|
||||
`docker image ls` 命令默认输出的列表包含仓库名、标签、镜像 ID、创建时间和占用空间等信息。
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| **REPOSITORY** | 仓库名 |
|
||||
| **TAG** | 标签(版本) |
|
||||
| **IMAGE ID** | 镜像唯一标识(短 ID,前 12 位) |
|
||||
| **TAG** | 标签 (版本)|
|
||||
| **IMAGE ID** | 镜像唯一标识 (短 ID,前 12 位)|
|
||||
| **CREATED** | 创建时间 |
|
||||
| **SIZE** | 本地占用空间 |
|
||||
|
||||
### 同一镜像多个标签
|
||||
#### 同一镜像多个标签
|
||||
|
||||
注意上面的 `ubuntu:24.04` 和 `ubuntu:noble` 拥有相同的 IMAGE ID——它们是同一个镜像的不同标签,只占用一份存储空间。
|
||||
|
||||
---
|
||||
|
||||
## 理解镜像大小
|
||||
### 4.2.3 理解镜像大小
|
||||
|
||||
### 本地大小 vs Hub 显示大小
|
||||
Docker 镜像的大小可能与我们通常理解的文件大小有所不同,这涉及到分层存储的概念。
|
||||
|
||||
#### 本地大小 vs Hub 显示大小
|
||||
|
||||
| 位置 | 显示大小 | 说明 |
|
||||
|------|---------|------|
|
||||
| Docker Hub | 29MB | 压缩后的网络传输大小 |
|
||||
| docker image ls | 78MB | 本地解压后的实际大小 |
|
||||
|
||||
### 实际磁盘占用
|
||||
#### 实际磁盘占用
|
||||
|
||||
由于镜像是分层存储,不同镜像可能共享相同的层:
|
||||
|
||||
```
|
||||
```bash
|
||||
ubuntu:24.04 nginx:latest redis:latest
|
||||
│ │ │
|
||||
└───────┬───────┘ │
|
||||
@@ -56,7 +62,7 @@ ubuntu:24.04 nginx:latest redis:latest
|
||||
|
||||
因此,`docker image ls` 中各镜像大小之和 > 实际磁盘占用。
|
||||
|
||||
### 查看实际空间占用
|
||||
#### 查看实际空间占用
|
||||
|
||||
```bash
|
||||
$ docker system df
|
||||
@@ -69,12 +75,15 @@ Build Cache 0 0 0B 0B
|
||||
|
||||
---
|
||||
|
||||
## 过滤镜像
|
||||
### 4.2.4 过滤镜像
|
||||
|
||||
### 按仓库名过滤
|
||||
随着本地镜像数量的增加,我们需要更有效的方式来查找特定的镜像。Docker 提供了多种过滤方式。
|
||||
|
||||
#### 按仓库名过滤
|
||||
|
||||
```bash
|
||||
# 列出所有 ubuntu 镜像
|
||||
## 列出所有 ubuntu 镜像
|
||||
|
||||
$ docker images ubuntu
|
||||
REPOSITORY TAG IMAGE ID SIZE
|
||||
ubuntu 24.04 329ed837d508 78MB
|
||||
@@ -82,7 +91,7 @@ ubuntu noble 329ed837d508 78MB
|
||||
ubuntu 22.04 a1b2c3d4e5f6 72MB
|
||||
```
|
||||
|
||||
### 按仓库名和标签过滤
|
||||
#### 按仓库名和标签过滤
|
||||
|
||||
```bash
|
||||
$ docker images ubuntu:24.04
|
||||
@@ -90,7 +99,7 @@ REPOSITORY TAG IMAGE ID SIZE
|
||||
ubuntu 24.04 329ed837d508 78MB
|
||||
```
|
||||
|
||||
### 使用过滤器 --filter
|
||||
#### 使用过滤器 --filter
|
||||
|
||||
| 过滤条件 | 说明 | 示例 |
|
||||
|---------|------|------|
|
||||
@@ -101,21 +110,26 @@ ubuntu 24.04 329ed837d508 78MB
|
||||
| `reference=pattern` | 按名称模式 | `-f reference='*:latest'` |
|
||||
|
||||
```bash
|
||||
# 列出 nginx 之后创建的镜像
|
||||
## 列出 nginx 之后创建的镜像
|
||||
|
||||
$ docker images -f since=nginx:latest
|
||||
|
||||
# 列出所有带 latest 标签的镜像
|
||||
## 列出所有带 latest 标签的镜像
|
||||
|
||||
$ docker images -f reference='*:latest'
|
||||
|
||||
# 列出带特定 LABEL 的镜像
|
||||
## 列出带特定 LABEL 的镜像
|
||||
|
||||
$ docker images -f label=maintainer=example@email.com
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 虚悬镜像(Dangling Images)
|
||||
### 4.2.5 虚悬镜像
|
||||
|
||||
### 什么是虚悬镜像
|
||||
在镜像列表里,你可能会看到一些仓库名和标签都为 `<none>` 的镜像,这类镜像被称为虚悬镜像。
|
||||
|
||||
#### 什么是虚悬镜像
|
||||
|
||||
仓库名和标签都显示为 `<none>` 的镜像:
|
||||
|
||||
@@ -125,26 +139,30 @@ 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
|
||||
@@ -156,9 +174,11 @@ $ docker images -a
|
||||
|
||||
---
|
||||
|
||||
## 格式化输出
|
||||
### 4.2.7 格式化输出
|
||||
|
||||
### 只输出 ID
|
||||
为了配合脚本使用或展示更关注的信息,我们可以使用 `--format` 参数来自定义输出格式。
|
||||
|
||||
#### 只输出 ID
|
||||
|
||||
```bash
|
||||
$ docker images -q
|
||||
@@ -170,20 +190,22 @@ $ docker images -q
|
||||
常用于配合其他命令:
|
||||
|
||||
```bash
|
||||
# 删除所有镜像
|
||||
## 删除所有镜像
|
||||
|
||||
$ docker rmi $(docker images -q)
|
||||
|
||||
# 删除所有 redis 镜像
|
||||
## 删除所有 redis 镜像
|
||||
|
||||
$ docker rmi $(docker images -q redis)
|
||||
```
|
||||
|
||||
### 显示完整 ID
|
||||
#### 显示完整 ID
|
||||
|
||||
```bash
|
||||
$ docker images --no-trunc
|
||||
```
|
||||
|
||||
### 显示摘要
|
||||
#### 显示摘要
|
||||
|
||||
```bash
|
||||
$ docker images --digests
|
||||
@@ -191,18 +213,20 @@ REPOSITORY TAG DIGEST IMAGE ID
|
||||
nginx latest sha256:b4f0e0bdeb5... e43d811ce2f4
|
||||
```
|
||||
|
||||
### 自定义格式
|
||||
#### 自定义格式
|
||||
|
||||
使用 Go 模板语法自定义输出:
|
||||
|
||||
```bash
|
||||
# 只显示 ID 和仓库名
|
||||
## 只显示 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
|
||||
@@ -210,7 +234,7 @@ nginx latest 181MB
|
||||
ubuntu 24.04 78MB
|
||||
```
|
||||
|
||||
### 可用模板字段
|
||||
#### 可用模板字段
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
@@ -224,35 +248,20 @@ ubuntu 24.04 78MB
|
||||
|
||||
---
|
||||
|
||||
## 常用命令组合
|
||||
### 4.2.8 常用命令组合
|
||||
|
||||
```bash
|
||||
# 列出所有镜像及其大小,按大小排序(需要系统 sort 命令)
|
||||
## 列出所有镜像及其大小,按大小排序(需要系统 sort 命令)
|
||||
|
||||
$ docker images --format "{{.Size}}\t{{.Repository}}:{{.Tag}}" | sort -h
|
||||
|
||||
# 查找大于 500MB 的镜像
|
||||
## 查找大于 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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 本章小结
|
||||
|
||||
| 操作 | 命令 |
|
||||
|------|------|
|
||||
| 列出所有镜像 | `docker images` |
|
||||
| 按仓库名过滤 | `docker images nginx` |
|
||||
| 列出虚悬镜像 | `docker images -f dangling=true` |
|
||||
| 只输出 ID | `docker images -q` |
|
||||
| 显示摘要 | `docker images --digests` |
|
||||
| 自定义格式 | `docker images --format "..."` |
|
||||
| 查看空间占用 | `docker system df` |
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [获取镜像](pull.md):从 Registry 拉取镜像
|
||||
- [删除镜像](rm.md):清理本地镜像
|
||||
- [镜像](../02_basic_concept/image.md):理解镜像概念
|
||||
@@ -1,6 +1,8 @@
|
||||
# 删除本地镜像
|
||||
## 4.3 删除本地镜像
|
||||
|
||||
## 基本用法
|
||||
当不再需要某个镜像时,我们可以将其删除以释放存储空间。本节介绍删除镜像的常用方法。
|
||||
|
||||
### 4.3.1 基本用法
|
||||
|
||||
使用 `docker image rm` 删除本地镜像:
|
||||
|
||||
@@ -12,18 +14,18 @@ $ docker image rm [选项] <镜像1> [<镜像2> ...]
|
||||
|
||||
---
|
||||
|
||||
## 镜像标识方式
|
||||
### 4.3.2 镜像标识方式
|
||||
|
||||
删除镜像时,可以使用多种方式指定镜像:
|
||||
|
||||
| 方式 | 说明 | 示例 |
|
||||
|------|------|------|
|
||||
| **短 ID** | ID 的前几位(通常 3-4 位) | `docker rmi 501` |
|
||||
| **短 ID** | ID 的前几位 (通常 3-4 位)| `docker rmi 501` |
|
||||
| **长 ID** | 完整的镜像 ID | `docker rmi 501ad78535f0...` |
|
||||
| **镜像名:标签** | 仓库名和标签 | `docker rmi redis:alpine` |
|
||||
| **镜像摘要** | 精确的内容摘要 | `docker rmi nginx@sha256:...` |
|
||||
|
||||
### 使用短 ID 删除
|
||||
#### 使用短 ID 删除
|
||||
|
||||
```bash
|
||||
$ docker image ls
|
||||
@@ -31,13 +33,14 @@ 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
|
||||
@@ -45,23 +48,27 @@ 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**
|
||||
|
||||
@@ -74,83 +81,98 @@ Deleted: sha256:96167737e29ca8e9d74982ef2a0dda76ed7b430da55e321c071f0dbff8c2899b
|
||||
Deleted: sha256:32770d1dcf835f192cafd6b9263b7b597a1778a403a109e2cc2ee866f74adf23
|
||||
```
|
||||
|
||||
### Untagged vs Deleted
|
||||
#### Untagged vs Deleted
|
||||
|
||||
| 操作 | 含义 |
|
||||
|------|------|
|
||||
| **Untagged** | 移除镜像的标签 |
|
||||
| **Deleted** | 删除镜像的存储层 |
|
||||
|
||||
### 删除流程
|
||||
#### 删除流程
|
||||
|
||||
```
|
||||
docker rmi redis:alpine
|
||||
│
|
||||
▼
|
||||
┌───────────────────────────────────────────────────────────────┐
|
||||
│ 1. Untag:移除 redis:alpine 标签 │
|
||||
│ ↓ │
|
||||
│ 2. 检查是否还有其他标签指向这个镜像 │
|
||||
│ ├── 有 → 只 Untag,不删除 │
|
||||
│ └── 无 → │
|
||||
│ ↓ │
|
||||
│ 3. 检查是否有容器依赖 │
|
||||
│ ├── 有 → 报错,无法删除 │
|
||||
│ └── 无 → │
|
||||
│ ↓ │
|
||||
│ 4. 从上到下逐层删除,检查每层是否被其他镜像使用 │
|
||||
│ ├── 被使用 → 保留 │
|
||||
│ └── 未使用 → 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):没有标签的镜像,通常是旧版本被新版本覆盖后产生的
|
||||
#### 删除所有虚悬镜像
|
||||
|
||||
虚悬镜像 (dangling):没有标签的镜像,通常是旧版本被新版本覆盖后产生的
|
||||
|
||||
```bash
|
||||
# 查看虚悬镜像
|
||||
## 查看虚悬镜像
|
||||
|
||||
$ docker images -f dangling=true
|
||||
|
||||
# 删除虚悬镜像
|
||||
## 删除虚悬镜像
|
||||
|
||||
$ docker image prune
|
||||
|
||||
# 不提示确认
|
||||
## 不提示确认
|
||||
|
||||
$ docker image prune -f
|
||||
```
|
||||
|
||||
### 删除所有未使用的镜像
|
||||
#### 删除所有未使用的镜像
|
||||
|
||||
```bash
|
||||
# 删除所有没有被容器使用的镜像
|
||||
## 删除所有没有被容器使用的镜像
|
||||
|
||||
$ docker image prune -a
|
||||
|
||||
# 保留最近 24 小时的
|
||||
## 保留最近 24 小时的
|
||||
|
||||
$ docker image prune -a --filter "until=24h"
|
||||
```
|
||||
|
||||
### 按条件删除
|
||||
#### 按条件删除
|
||||
|
||||
```bash
|
||||
# 删除所有 redis 镜像
|
||||
## 删除所有 redis 镜像
|
||||
|
||||
$ docker rmi $(docker images -q redis)
|
||||
|
||||
# 删除 mongo:8.0 之前的所有镜像
|
||||
## 删除 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
|
||||
@@ -161,15 +183,17 @@ Error: conflict: unable to remove repository reference "nginx"
|
||||
**解决方案**:
|
||||
|
||||
```bash
|
||||
# 方案1:先删除依赖的容器
|
||||
## 方案1:先删除依赖的容器
|
||||
|
||||
$ docker rm abc123
|
||||
$ docker rmi nginx
|
||||
|
||||
# 方案2:强制删除镜像(容器仍可运行,但无法再创建新容器)
|
||||
## 方案2:强制删除镜像(容器仍可运行,但无法再创建新容器)
|
||||
|
||||
$ docker rmi -f nginx
|
||||
```
|
||||
|
||||
### 原因二:多个标签指向同一镜像
|
||||
#### 原因二:多个标签指向同一镜像
|
||||
|
||||
```bash
|
||||
$ docker images
|
||||
@@ -179,10 +203,12 @@ ubuntu latest ca2b0f26964c # 同一个镜像
|
||||
|
||||
$ docker rmi ubuntu:24.04
|
||||
Untagged: ubuntu:24.04
|
||||
# 只是移除标签,镜像仍存在(因为还有 ubuntu:latest 指向它)
|
||||
## 只是移除标签,镜像仍存在(因为还有 ubuntu:latest 指向它)
|
||||
```
|
||||
|
||||
### 原因三:被其他镜像依赖(中间层)
|
||||
当同一个镜像有多个标签时,`docker rmi` 只是删除指定的标签,不会删除镜像本身。
|
||||
|
||||
#### 原因三:被其他镜像依赖 (中间层)
|
||||
|
||||
```bash
|
||||
$ docker rmi some_base_image
|
||||
@@ -193,7 +219,7 @@ Error: image has dependent child images
|
||||
|
||||
---
|
||||
|
||||
## 常用过滤条件
|
||||
### 4.3.6 常用过滤条件
|
||||
|
||||
| 过滤条件 | 说明 | 示例 |
|
||||
|---------|------|------|
|
||||
@@ -205,26 +231,31 @@ Error: image has dependent child images
|
||||
|
||||
---
|
||||
|
||||
## 清理策略
|
||||
### 4.3.7 清理策略
|
||||
|
||||
### 开发环境
|
||||
针对不同的环境 (开发环境 vs 生产环境),我们应该采用不同的镜像清理策略。
|
||||
|
||||
#### 开发环境
|
||||
|
||||
```bash
|
||||
# 定期清理虚悬镜像
|
||||
## 定期清理虚悬镜像
|
||||
|
||||
$ docker image prune -f
|
||||
|
||||
# 一键清理所有未使用资源
|
||||
## 一键清理所有未使用资源
|
||||
|
||||
$ docker system prune -a
|
||||
```
|
||||
|
||||
### CI/CD 环境
|
||||
#### CI/CD 环境
|
||||
|
||||
```bash
|
||||
# 只保留最近使用的镜像
|
||||
## 只保留最近使用的镜像
|
||||
|
||||
$ docker image prune -a --filter "until=72h" -f
|
||||
```
|
||||
|
||||
### 查看空间占用
|
||||
#### 查看空间占用
|
||||
|
||||
```bash
|
||||
$ docker system df
|
||||
@@ -236,20 +267,3 @@ Build Cache 0 0 0B 0B
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 本章小结
|
||||
|
||||
| 操作 | 命令 |
|
||||
|------|------|
|
||||
| 删除指定镜像 | `docker rmi 镜像名:标签` |
|
||||
| 强制删除 | `docker rmi -f 镜像名` |
|
||||
| 删除虚悬镜像 | `docker image prune` |
|
||||
| 删除未使用镜像 | `docker image prune -a` |
|
||||
| 批量删除 | `docker rmi $(docker images -q -f ...)` |
|
||||
| 查看空间占用 | `docker system df` |
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [列出镜像](list.md):查看和过滤镜像
|
||||
- [删除容器](../05_container/rm.md):清理容器
|
||||
- [数据卷](../07_data_network/data/volume.md):清理数据卷
|
||||
@@ -1,8 +1,8 @@
|
||||
# 利用 commit 理解镜像构成
|
||||
## 4.4 利用 commit 理解镜像构成
|
||||
|
||||
> 注意:如果您是初学者,您可以暂时跳过后面的内容,直接学习 [容器](../05_container/) 一节。
|
||||
> 注意:如果您是初学者,您可以暂时跳过后面的内容,直接学习[容器](../05_container/)一节。
|
||||
|
||||
注意: `docker commit` 命令除了学习之外,还有一些特殊的应用场合,比如被入侵后保存现场等。但是,不要使用 `docker commit` 定制镜像,定制镜像应该使用 `Dockerfile` 来完成。如果你想要定制镜像请查看下一小节。
|
||||
注意:`docker commit` 命令除了学习之外,还有一些特殊的应用场合,比如被入侵后保存现场等。但是,不要使用 `docker commit` 定制镜像,定制镜像应该使用 `Dockerfile` 来完成。如果你想要定制镜像请查看下一小节。
|
||||
|
||||
镜像是容器的基础,每次执行 `docker run` 的时候都会指定哪个镜像作为容器运行的基础。在之前的例子中,我们所使用的都是来自于 Docker Hub 的镜像。直接使用这些镜像是可以满足一定的需求,而当这些镜像无法直接满足需求时,我们就需要定制这些镜像。接下来的几节就将讲解如何定制镜像。
|
||||
|
||||
@@ -16,11 +16,11 @@ $ docker run --name webserver -d -p 80:80 nginx
|
||||
|
||||
这条命令会用 `nginx` 镜像启动一个容器,命名为 `webserver`,并且映射了 80 端口,这样我们可以用浏览器去访问这个 `nginx` 服务器。
|
||||
|
||||
如果是在本机运行的 Docker,那么可以直接访问:`http://localhost` ,如果是在虚拟机、云服务器上安装的 Docker,则需要将 `localhost` 换为虚拟机地址或者实际云服务器地址。
|
||||
如果是在本机运行的 Docker,那么可以直接访问:`http://localhost`,如果是在虚拟机、云服务器上安装的 Docker,则需要将 `localhost` 换为虚拟机地址或者实际云服务器地址。
|
||||
|
||||
直接用浏览器访问的话,我们会看到默认的 Nginx 欢迎页面。
|
||||
|
||||

|
||||

|
||||
|
||||
现在,假设我们非常不喜欢这个欢迎页面,我们希望改成欢迎 Docker 的文字,我们可以使用 `docker exec` 命令进入容器,修改其内容。
|
||||
|
||||
@@ -37,7 +37,7 @@ exit
|
||||
|
||||
现在我们再刷新浏览器的话,会发现内容被改变了。
|
||||
|
||||

|
||||

|
||||
|
||||
我们修改了容器的文件,也就是改动了容器的存储层。我们可以通过 `docker diff` 命令看到具体的改动。
|
||||
|
||||
@@ -63,7 +63,7 @@ A /var/cache/nginx/uwsgi_temp
|
||||
|
||||
现在我们定制好了变化,我们希望能将其保存下来形成镜像。
|
||||
|
||||
要知道,当我们运行一个容器的时候(如果不使用卷的话),我们做的任何文件修改都会被记录于容器存储层里。而 Docker 提供了一个 `docker commit` 命令,可以将容器的存储层保存下来成为镜像。换句话说,就是在原有镜像的基础上,再叠加上容器的存储层,并构成新的镜像。以后我们运行这个新镜像的时候,就会拥有原有容器最后的文件变化。
|
||||
要知道,当我们运行一个容器的时候 (如果不使用卷的话),我们做的任何文件修改都会被记录于容器存储层里。而 Docker 提供了一个 `docker commit` 命令,可以将容器的存储层保存下来成为镜像。换句话说,就是在原有镜像的基础上,再叠加上容器的存储层,并构成新的镜像。以后我们运行这个新镜像的时候,就会拥有原有容器最后的文件变化。
|
||||
|
||||
`docker commit` 的语法格式为:
|
||||
|
||||
@@ -120,7 +120,7 @@ docker run --name web2 -d -p 81:80 nginx:v2
|
||||
|
||||
至此,我们第一次完成了定制镜像,使用的是 `docker commit` 命令,手动操作给旧的镜像添加了新的一层,形成新的镜像,对镜像多层存储应该有了更直观的感觉。
|
||||
|
||||
## 慎用 `docker commit`
|
||||
### 4.4.1 慎用 `docker commit`
|
||||
|
||||
使用 `docker commit` 命令虽然可以比较直观的帮助理解镜像分层存储的概念,但是实际环境中并不会这样使用。
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# 使用 Dockerfile 定制镜像
|
||||
## 4.5 使用 Dockerfile 定制镜像
|
||||
|
||||
从刚才的 `docker commit` 的学习中,我们可以了解到,镜像的定制实际上就是定制每一层所添加的配置、文件。如果我们可以把每一层修改、安装、构建、操作的命令都写入一个脚本,用这个脚本来构建、定制镜像,那么之前提及的无法重复的问题、镜像构建透明性的问题、体积的问题就都会解决。这个脚本就是 Dockerfile。
|
||||
|
||||
Dockerfile 是一个文本文件,其内包含了一条条的 **指令(Instruction)**,每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。
|
||||
Dockerfile 是一个文本文件,其内包含了一条条的 **指令 (Instruction)**,每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。
|
||||
|
||||
## 使用 docker init 快速创建(推荐)
|
||||
### 4.5.1 使用 docker init 快速创建 (推荐)
|
||||
|
||||
Docker 提供了 `docker init` 命令,可以根据项目类型自动生成 Dockerfile、.dockerignore 和 compose.yaml 文件:
|
||||
|
||||
@@ -12,9 +12,9 @@ Docker 提供了 `docker init` 命令,可以根据项目类型自动生成 Doc
|
||||
$ docker init
|
||||
```
|
||||
|
||||
该命令会交互式地询问项目类型(如 Go、Node.js、Python、Rust 等),并生成符合最佳实践的配置文件。对于新项目,这是推荐的起步方式。
|
||||
该命令会交互式地询问项目类型 (如 Go、Node.js、Python、Rust 等),并生成符合最佳实践的配置文件。对于新项目,这是推荐的起步方式。
|
||||
|
||||
## 手动创建 Dockerfile
|
||||
### 4.5.2 手动创建 Dockerfile
|
||||
|
||||
还以之前定制 `nginx` 镜像为例,这次我们使用 Dockerfile 来定制。
|
||||
|
||||
@@ -35,7 +35,7 @@ RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
|
||||
|
||||
这个 Dockerfile 很简单,一共就两行。涉及到了两条指令,`FROM` 和 `RUN`。
|
||||
|
||||
## FROM 指定基础镜像
|
||||
### 4.5.3 FROM 指定基础镜像
|
||||
|
||||
所谓定制镜像,那一定是以一个镜像为基础,在其上进行定制。就像我们之前运行了一个 `nginx` 镜像的容器,再进行修改一样,基础镜像是必须指定的。而 `FROM` 就是指定 **基础镜像**,因此一个 `Dockerfile` 中 `FROM` 是必备的指令,并且必须是第一条指令。
|
||||
|
||||
@@ -52,9 +52,9 @@ FROM scratch
|
||||
|
||||
如果你以 `scratch` 为基础镜像的话,意味着你不以任何镜像为基础,接下来所写的指令将作为镜像第一层开始存在。
|
||||
|
||||
不以任何系统为基础,直接将可执行文件复制进镜像的做法并不罕见,对于 Linux 下静态编译的程序来说,并不需要有操作系统提供运行时支持,所需的一切库都已经在可执行文件里了,因此直接 `FROM scratch` 会让镜像体积更加小巧。使用 [Go 语言](https://golang.google.cn/) 开发的应用很多会使用这种方式来制作镜像,这也是有人认为 Go 是特别适合容器微服务架构的语言的原因之一。
|
||||
不以任何系统为基础,直接将可执行文件复制进镜像的做法并不罕见,对于 Linux 下静态编译的程序来说,并不需要有操作系统提供运行时支持,所需的一切库都已经在可执行文件里了,因此直接 `FROM scratch` 会让镜像体积更加小巧。使用 [Go 语言](https://golang.google.cn/)开发的应用很多会使用这种方式来制作镜像,这也是有人认为 Go 是特别适合容器微服务架构的语言的原因之一。
|
||||
|
||||
## RUN 执行命令
|
||||
### 4.5.4 RUN 执行命令
|
||||
|
||||
`RUN` 指令是用来执行命令行命令的。由于命令行的强大能力,`RUN` 指令在定制镜像时是最常用的指令之一。其格式有两种:
|
||||
|
||||
@@ -64,57 +64,19 @@ FROM scratch
|
||||
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
|
||||
```
|
||||
|
||||
* *exec* 格式:`RUN ["可执行文件", "参数1", "参数2"]`,这更像是函数调用中的格式。
|
||||
* *exec* 格式:`RUN [“可执行文件”, “参数1”, “参数2”]`,这更像是函数调用中的格式。
|
||||
|
||||
既然 `RUN` 就像 Shell 脚本一样可以执行命令,那么我们是否就可以像 Shell 脚本一样把每个命令对应一个 RUN 呢?比如这样:
|
||||
Dockerfile 中每一个指令都会建立一层,`RUN` 也不例外。每一个 `RUN` 的行为,就和刚才我们手工建立镜像的过程一样:新建立一层,在其上执行这些命令,执行结束后,`commit` 这一层的修改,构成新的镜像。
|
||||
|
||||
```docker
|
||||
FROM debian:bookworm
|
||||
> **注意**
|
||||
>
|
||||
> 每一个 `RUN` 指令都会产生一个新的镜像层。为了减少镜像体积和层数,我们通常会将多个命令合并到一个 `RUN` 指令中执行。
|
||||
>
|
||||
> 更多关于 `RUN` 指令的详细用法、最佳实践 (如清理缓存、使用 pipefail 等) 及 `Union FS` 的层数限制等内容,请参阅 **[第七章 Dockerfile 指令详解](../07_dockerfile/README.md)** 中的 **[RUN 指令](../07_dockerfile/7.1_run.md)** 小节。
|
||||
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y gcc libc6-dev make wget
|
||||
RUN wget -O redis.tar.gz "http://download.redis.io/releases/redis-7.2.4.tar.gz"
|
||||
RUN mkdir -p /usr/src/redis
|
||||
RUN tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1
|
||||
RUN make -C /usr/src/redis
|
||||
RUN make -C /usr/src/redis install
|
||||
```
|
||||
要想编写优秀的 `Dockerfile`,必须了解每一条指令的作用和副作用。在 **[第七章 Dockerfile 指令详解](../07_dockerfile/README.md)** 中,我们将对 `COPY`,`ADD`,`CMD`,`ENTRYPOINT` 等指令进行详细讲解。
|
||||
|
||||
之前说过,Dockerfile 中每一个指令都会建立一层,`RUN` 也不例外。每一个 `RUN` 的行为,就和刚才我们手工建立镜像的过程一样:新建立一层,在其上执行这些命令,执行结束后,`commit` 这一层的修改,构成新的镜像。
|
||||
|
||||
而上面的这种写法,创建了 7 层镜像。这是完全没有意义的,而且很多运行时不需要的东西,都被装进了镜像里,比如编译环境、更新的软件包等等。结果就是产生非常臃肿、非常多层的镜像,不仅仅增加了构建部署的时间,也很容易出错。
|
||||
这是很多初学 Docker 的人常犯的一个错误。
|
||||
|
||||
*Union FS 是有最大层数限制的,比如 AUFS,曾经是最大不得超过 42 层,现在是不得超过 127 层。*
|
||||
|
||||
上面的 `Dockerfile` 正确的写法应该是这样:
|
||||
|
||||
```docker
|
||||
FROM debian:bookworm
|
||||
|
||||
RUN set -x; buildDeps='gcc libc6-dev make wget' \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y $buildDeps \
|
||||
&& wget -O redis.tar.gz "http://download.redis.io/releases/redis-7.2.4.tar.gz" \
|
||||
&& mkdir -p /usr/src/redis \
|
||||
&& tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1 \
|
||||
&& make -C /usr/src/redis \
|
||||
&& make -C /usr/src/redis install \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& rm redis.tar.gz \
|
||||
&& rm -r /usr/src/redis \
|
||||
&& apt-get purge -y --auto-remove $buildDeps
|
||||
```
|
||||
|
||||
首先,之前所有的命令只有一个目的,就是编译、安装 redis 可执行文件。因此没有必要建立很多层,这只是一层的事情。因此,这里没有使用很多个 `RUN` 一一对应不同的命令,而是仅仅使用一个 `RUN` 指令,并使用 `&&` 将各个所需命令串联起来。将之前的 7 层,简化为了 1 层。在撰写 Dockerfile 的时候,要经常提醒自己,这并不是在写 Shell 脚本,而是在定义每一层该如何构建。
|
||||
|
||||
并且,这里为了格式化还进行了换行。Dockerfile 支持 Shell 类的行尾添加 `\` 的命令换行方式,以及行首 `#` 进行注释的格式。良好的格式,比如换行、缩进、注释等,会让维护、排障更为容易,这是一个比较好的习惯。
|
||||
|
||||
此外,还可以看到这一组命令的最后添加了清理工作的命令,删除了为了编译构建所需要的软件,清理了所有下载、展开的文件,并且还清理了 `apt` 缓存文件。这是很重要的一步,我们之前说过,镜像是多层存储,每一层的东西并不会在下一层被删除,会一直跟随着镜像。因此镜像构建时,一定要确保每一层只添加真正需要添加的东西,任何无关的东西都应该清理掉。
|
||||
|
||||
很多人初学 Docker 制作出了很臃肿的镜像的原因之一,就是忘记了每一层构建的最后一定要清理掉无关文件。
|
||||
|
||||
## 构建镜像
|
||||
### 4.5.5 构建镜像
|
||||
|
||||
好了,让我们再回到之前定制的 nginx 镜像的 Dockerfile 来。现在我们明白了这个 Dockerfile 的内容,那么让我们来构建这个镜像吧。
|
||||
|
||||
@@ -142,11 +104,11 @@ docker build [选项] <上下文路径/URL/->
|
||||
|
||||
在这里我们指定了最终镜像的名称 `-t nginx:v3`,构建成功后,我们可以像之前运行 `nginx:v2` 那样来运行这个镜像,其结果会和 `nginx:v2` 一样。
|
||||
|
||||
## 镜像构建上下文(Context)
|
||||
### 4.5.6 镜像构建上下文
|
||||
|
||||
如果注意,会看到 `docker build` 命令最后有一个 `.`。`.` 表示当前目录,而 `Dockerfile` 就在当前目录,因此不少初学者以为这个路径是在指定 `Dockerfile` 所在路径,这么理解其实是不准确的。如果对应上面的命令格式,你可能会发现,这是在指定 **上下文路径**。那么什么是上下文呢?
|
||||
|
||||
首先我们要理解 `docker build` 的工作原理。Docker 在运行时分为 Docker 引擎(也就是服务端守护进程)和客户端工具。Docker 的引擎提供了一组 REST API,被称为 [Docker Remote API](https://docs.docker.com/develop/sdk/),而如 `docker` 命令这样的客户端工具,则是通过这组 API 与 Docker 引擎交互,从而完成各种功能。因此,虽然表面上我们好像是在本机执行各种 `docker` 功能,但实际上,一切都是使用的远程调用形式在服务端(Docker 引擎)完成。也因为这种 C/S 设计,让我们操作远程服务器的 Docker 引擎变得轻而易举。
|
||||
首先我们要理解 `docker build` 的工作原理。Docker 在运行时分为 Docker 引擎 (也就是服务端守护进程) 和客户端工具。Docker 的引擎提供了一组 REST API,被称为 [Docker Remote API](https://docs.docker.com/develop/sdk/),而如 `docker` 命令这样的客户端工具,则是通过这组 API 与 Docker 引擎交互,从而完成各种功能。因此,虽然表面上我们好像是在本机执行各种 `docker` 功能,但实际上,一切都是使用的远程调用形式在服务端 (Docker 引擎) 完成。也因为这种 C/S 设计,让我们操作远程服务器的 Docker 引擎变得轻而易举。
|
||||
|
||||
当我们进行镜像构建的时候,并非所有定制都会通过 `RUN` 指令完成,经常会需要将一些本地文件复制进镜像,比如通过 `COPY` 指令、`ADD` 指令等。而 `docker build` 命令构建镜像,其实并非在本地构建,而是在服务端,也就是 Docker 引擎中构建的。那么在这种客户端/服务端的架构中,如何才能让服务端获得本地文件呢?
|
||||
|
||||
@@ -158,7 +120,7 @@ docker build [选项] <上下文路径/URL/->
|
||||
COPY ./package.json /app/
|
||||
```
|
||||
|
||||
这并不是要复制执行 `docker build` 命令所在的目录下的 `package.json`,也不是复制 `Dockerfile` 所在目录下的 `package.json`,而是复制 **上下文(context)** 目录下的 `package.json`。
|
||||
这并不是要复制执行 `docker build` 命令所在的目录下的 `package.json`,也不是复制 `Dockerfile` 所在目录下的 `package.json`,而是复制 **上下文 (context)** 目录下的 `package.json`。
|
||||
|
||||
因此,`COPY` 这类指令中的源文件的路径都是*相对路径*。这也是初学者经常会问的为什么 `COPY ../package.json /app` 或者 `COPY /opt/xxxx /app` 无法工作的原因,因为这些路径已经超出了上下文的范围,Docker 引擎无法获得这些位置的文件。如果真的需要那些文件,应该将它们复制到上下文目录中去。
|
||||
|
||||
@@ -182,15 +144,16 @@ Sending build context to Docker daemon 2.048 kB
|
||||
|
||||
当然,一般大家习惯性的会使用默认的文件名 `Dockerfile`,以及会将其置于镜像构建上下文目录中。
|
||||
|
||||
## 其它 `docker build` 的用法
|
||||
### 4.5.7 其它 `docker build` 的用法
|
||||
|
||||
### 直接用 Git repo 进行构建
|
||||
#### 直接用 Git repo 进行构建
|
||||
|
||||
或许你已经注意到了,`docker build` 还支持从 URL 构建,比如可以直接从 Git repo 中构建:
|
||||
|
||||
```bash
|
||||
# $env:DOCKER_BUILDKIT=0
|
||||
# export DOCKER_BUILDKIT=0
|
||||
## $env:DOCKER_BUILDKIT=0
|
||||
|
||||
## export DOCKER_BUILDKIT=0
|
||||
|
||||
$ docker build -t hello-world https://github.com/docker-library/hello-world.git#master:amd64/hello-world
|
||||
|
||||
@@ -207,7 +170,7 @@ Successfully built 038ad4142d2b
|
||||
|
||||
这行命令指定了构建所需的 Git repo,并且指定分支为 `master`,构建目录为 `/amd64/hello-world/`,然后 Docker 就会自己去 `git clone` 这个项目、切换到指定分支、并进入到指定目录后开始构建。
|
||||
|
||||
### 用给定的 tar 压缩包构建
|
||||
#### 用给定的 tar 压缩包构建
|
||||
|
||||
```bash
|
||||
$ docker build http://server/context.tar.gz
|
||||
@@ -215,7 +178,7 @@ $ docker build http://server/context.tar.gz
|
||||
|
||||
如果所给出的 URL 不是个 Git repo,而是个 `tar` 压缩包,那么 Docker 引擎会下载这个包,并自动解压缩,以其作为上下文,开始构建。
|
||||
|
||||
### 从标准输入中读取 Dockerfile 进行构建
|
||||
#### 从标准输入中读取 Dockerfile 进行构建
|
||||
|
||||
```bash
|
||||
docker build - < Dockerfile
|
||||
@@ -229,7 +192,7 @@ cat Dockerfile | docker build -
|
||||
|
||||
如果标准输入传入的是文本文件,则将其视为 `Dockerfile`,并开始构建。这种形式由于直接从标准输入中读取 Dockerfile 的内容,它没有上下文,因此不可以像其他方法那样可以将本地文件 `COPY` 进镜像之类的事情。
|
||||
|
||||
### 从标准输入中读取上下文压缩包进行构建
|
||||
#### 从标准输入中读取上下文压缩包进行构建
|
||||
|
||||
```bash
|
||||
$ docker build - < context.tar.gz
|
||||
@@ -1,8 +1,8 @@
|
||||
# 其它制作镜像的方式
|
||||
## 4.6 其它制作镜像的方式
|
||||
|
||||
除了标准的使用 `Dockerfile` 生成镜像的方法外,由于各种特殊需求和历史原因,还提供了一些其它方法用以生成镜像。
|
||||
|
||||
## 从 rootfs 压缩包导入
|
||||
### 4.6.1 从 rootfs 压缩包导入
|
||||
|
||||
格式:`docker import [选项] <文件>|<URL>|- [<仓库名>[:<标签>]]`
|
||||
|
||||
@@ -37,11 +37,11 @@ IMAGE CREATED CREATED BY SIZE
|
||||
f477a6e18e98 About a minute ago 214.9 MB Imported from http://download.openvz.org/template/precreated/ubuntu-16.04-x86_64.tar.gz
|
||||
```
|
||||
|
||||
## Docker 镜像的导入和导出 `docker save` 和 `docker load`
|
||||
### 4.6.2 Docker 镜像的导入和导出 `docker save` 和 `docker load`
|
||||
|
||||
Docker 还提供了 `docker save` 和 `docker load` 命令,用以将镜像保存为一个文件,然后传输到另一个位置上,再加载进来。这是在没有 Docker Registry 时的做法,现在已经不推荐,镜像迁移应该直接使用 Docker Registry,无论是直接使用 Docker Hub 还是使用内网私有 Registry 都可以。
|
||||
|
||||
### 保存镜像
|
||||
#### 保存镜像
|
||||
|
||||
使用 `docker save` 命令可以将镜像保存为归档文件。
|
||||
|
||||
@@ -63,7 +63,7 @@ filename: POSIX tar archive
|
||||
|
||||
这里的 filename 可以为任意名称甚至任意后缀名,但文件的本质都是归档文件
|
||||
|
||||
**注意:如果同名则会覆盖(没有警告)**
|
||||
**注意:如果同名则会覆盖 (没有警告)**
|
||||
|
||||
若使用 `gzip` 压缩:
|
||||
|
||||
65
04_image/4.7_internal.md
Normal file
@@ -0,0 +1,65 @@
|
||||
## 4.7 实现原理
|
||||
|
||||
Docker 镜像是怎么实现增量的修改和维护的?为什么容器启动如此之快?这一切都归功于 Docker 的镜像分层存储设计。
|
||||
|
||||
### 4.7.1 镜像与分层存储
|
||||
|
||||
在之前的章节中,我们一直强调镜像包含操作系统完整的 `root` 文件系统,其体积往往是庞大的。因此在 Docker 设计时,就充分利用 **Union FS** 的技术,将其设计为分层存储的架构。
|
||||
|
||||
Docker 镜像并不是一个单纯的文件,而是由一组文件系统叠加构成的。
|
||||
|
||||
最底层的镜像称为 **基础镜像 (Base Image)**,通常是各种 Linux 发行版的 root 文件系统,如 Ubuntu、Debian、CentOS 等。
|
||||
|
||||
当我们在基础镜像之上构建新的镜像时 (例如安装了 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-Write,CoW)** 策略。
|
||||
* **删除文件**:当容器删除某个文件时,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 等文件系统的具体实现原理,包括 WorkDir、UpperDir、LowerDir 等底层细节,请阅读 **[第十二章 底层实现](../12_implementation/README.md)** 中的 **[联合文件系统](../12_implementation/12.4_ufs.md)** 章节。
|
||||
@@ -1,13 +1,22 @@
|
||||
# 使用 Docker 镜像
|
||||
# 第四章 使用镜像
|
||||
|
||||
在之前的介绍中,我们知道镜像是 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)
|
||||
|
||||
* 管理本地主机上的镜像;
|
||||
|
||||
* 介绍镜像实现的基本原理。
|
||||
> **版本提示:镜像存储后端的变迁**
|
||||
>
|
||||
> 在 Docker Engine v29 及后续版本中,Docker 全新安装默认启用了 **containerd image store**(替代了传统的 classic store)。这一底层架构级别的变迁,意味着 Docker 解锁了对 OCI Image Index 和 Attestations (例如原生的 provenance 来源证明与 SBOM 软件物料清单)的全量本地支持。
|
||||
> 读者在执行类似 `docker buildx build --provenance=mode=min --sbom=true` 甚至使用后续审查工具(如 `docker buildx imagetools inspect`)时,其元数据能够与镜像数据一并完好地管理于本地存储系统中,为供应链安全验证补齐了最后一块拼图。
|
||||
|
||||
|
Before Width: | Height: | Size: 77 KiB |
|
Before Width: | Height: | Size: 101 KiB |
@@ -1,3 +0,0 @@
|
||||
# Dockerfile 指令详解
|
||||
|
||||
我们已经介绍了 `FROM`,`RUN`,还提及了 `COPY`, `ADD`,其实 `Dockerfile` 功能很强大,它提供了十多个指令。下面我们继续讲解其他的指令。
|
||||
@@ -1,219 +0,0 @@
|
||||
# EXPOSE 声明端口
|
||||
|
||||
## 基本语法
|
||||
|
||||
```docker
|
||||
EXPOSE <端口> [<端口>/<协议>...]
|
||||
```
|
||||
|
||||
`EXPOSE` 声明容器运行时提供服务的端口。这是一个**文档性质的声明**,告诉使用者容器会监听哪些端口。
|
||||
|
||||
---
|
||||
|
||||
## 基本用法
|
||||
|
||||
```docker
|
||||
# 声明单个端口
|
||||
EXPOSE 80
|
||||
|
||||
# 声明多个端口
|
||||
EXPOSE 80 443
|
||||
|
||||
# 声明 TCP 和 UDP 端口
|
||||
EXPOSE 80/tcp
|
||||
EXPOSE 53/udp
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## EXPOSE vs -p
|
||||
|
||||
| 特性 | EXPOSE | -p |
|
||||
|------|--------|-----|
|
||||
| **位置** | Dockerfile | docker run 命令 |
|
||||
| **作用** | 声明/文档 | 实际端口映射 |
|
||||
| **是否必需** | 否 | 是(外部访问时) |
|
||||
| **映射发生时** | 不发生 | 运行时发生 |
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────────────────────┐
|
||||
│ EXPOSE 80 │
|
||||
│ ↓ │
|
||||
│ 仅声明意图 │
|
||||
│ └───────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ docker run -p │
|
||||
│ ↓ │
|
||||
│ ┌─────────────────────┐ │
|
||||
│ │ 实际端口映射 │ │
|
||||
│ │ 宿主机 ←→ 容器 │ │
|
||||
│ └─────────────────────┘ │
|
||||
└────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 没有 EXPOSE 也能 -p
|
||||
|
||||
```docker
|
||||
# 即使没有 EXPOSE,也可以使用 -p
|
||||
FROM nginx
|
||||
# 没有 EXPOSE
|
||||
```
|
||||
|
||||
```bash
|
||||
# 仍然可以映射端口
|
||||
$ docker run -p 8080:80 mynginx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 常见误解
|
||||
|
||||
### 误解: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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 使用环境变量
|
||||
|
||||
```docker
|
||||
ARG PORT=80
|
||||
EXPOSE $PORT
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 在 Compose 中
|
||||
|
||||
```yaml
|
||||
services:
|
||||
web:
|
||||
build: .
|
||||
ports:
|
||||
- "8080:80" # 映射端口(类似 -p)
|
||||
expose:
|
||||
- "80" # 仅声明(类似 EXPOSE)
|
||||
```
|
||||
|
||||
`expose` 在 Compose 中仅用于容器间通信的文档说明,不进行端口映射。
|
||||
|
||||
---
|
||||
|
||||
## 本章小结
|
||||
|
||||
| 要点 | 说明 |
|
||||
|------|------|
|
||||
| **作用** | 声明容器提供服务的端口(文档) |
|
||||
| **不会** | 自动映射端口或开放外部访问 |
|
||||
| **配合** | `docker run -P` 自动映射 |
|
||||
| **外部访问** | 需要 `-p 宿主机端口:容器端口` |
|
||||
| **语法** | `EXPOSE 80` 或 `EXPOSE 80/tcp` |
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [网络配置](../../network/README.md):Docker 网络详解
|
||||
- [端口映射](../../network/port_bindingbindingbinding.md):-p 参数详解
|
||||
- [Compose 端口](../../compose/compose_file.md):Compose 中的端口配置
|
||||
@@ -1,151 +0,0 @@
|
||||
# ONBUILD 为他人做嫁衣裳
|
||||
|
||||
## 基本语法
|
||||
|
||||
```docker
|
||||
ONBUILD <其它指令>
|
||||
```
|
||||
|
||||
`ONBUILD` 是一个特殊的指令,它后面跟的是其它指令(如 `RUN`, `COPY` 等),这些指令**在当前镜像构建时不会执行**,只有当以当前镜像为基础镜像去构建下一级镜像时才会被执行。
|
||||
|
||||
---
|
||||
|
||||
## 为什么需要 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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 执行机制
|
||||
|
||||
```
|
||||
基础镜像构建:
|
||||
Dockerfile (含 ONBUILD) ──build──> 基础镜像 (记录了 ONBUILD 触发器)
|
||||
(指令未执行)
|
||||
|
||||
子镜像构建:
|
||||
FROM 基础镜像 ──build──> 读取基础镜像触发器 ──> 执行触发器指令 ──> 继续执行子 Dockerfile
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 常见使用场景
|
||||
|
||||
### 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/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
### 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`),会导致子镜像构建失败,且错误信息可能比较隐晦。
|
||||
|
||||
---
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 命名规范
|
||||
|
||||
建议在镜像标签中添加 `-onbuild` 后缀,明确告知使用者该镜像包含触发器。
|
||||
|
||||
```
|
||||
node:20-onbuild
|
||||
python:3.12-onbuild
|
||||
```
|
||||
|
||||
### 2. 避免执行耗时操作
|
||||
|
||||
尽量不要在 `ONBUILD` 中执行过于耗时或不确定的操作(如更新系统软件),这会让子镜像构建变得缓慢且不可控。
|
||||
|
||||
### 3. 清理工作
|
||||
|
||||
如果 `ONBUILD` 指令产生了临时文件,最好在同一个指令链中清理,或者提供机制让子镜像清理。
|
||||
|
||||
---
|
||||
|
||||
## 本章小结
|
||||
|
||||
| 要点 | 说明 |
|
||||
|------|------|
|
||||
| **作用** | 定义在子镜像构建时执行的指令 |
|
||||
| **语法** | `ONBUILD INSTRUCTION` |
|
||||
| **适用** | 基础架构镜像(Node, Python, Go 等) |
|
||||
| **限制** | 只继承一次,不可级联 |
|
||||
| **规范** | 建议使用 `-onbuild` 标签后缀 |
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [COPY 指令](copy.md):文件复制
|
||||
- [Dockerfile 最佳实践](../../15_appendix/best_practices.md):基础镜像设计
|
||||
@@ -1,7 +0,0 @@
|
||||
# 参考文档
|
||||
|
||||
* `Dockerfile` 官方文档:https://docs.docker.com/engine/reference/builder/
|
||||
|
||||
* `Dockerfile` 最佳实践文档:https://docs.docker.com/develop/develop-images/dockerfile_best-practices/
|
||||
|
||||
* `Docker` 官方镜像 `Dockerfile`:https://github.com/docker-library/docs
|
||||
@@ -1,273 +0,0 @@
|
||||
# USER 指定当前用户
|
||||
|
||||
## 基本语法
|
||||
|
||||
```docker
|
||||
USER <用户名>[:<用户组>]
|
||||
USER <UID>[:<GID>]
|
||||
```
|
||||
|
||||
`USER` 指令切换后续指令(RUN、CMD、ENTRYPOINT)的执行用户。
|
||||
|
||||
---
|
||||
|
||||
## 为什么要使用 USER
|
||||
|
||||
> 笔者强调:以非 root 用户运行容器是最重要的安全实践之一。
|
||||
|
||||
```
|
||||
root 用户运行的风险:
|
||||
┌────────────────────────────────────────────────────────┐
|
||||
│ 容器内 root ←─ 可能逃逸 ─→ 宿主机 root │
|
||||
│ │ │ │
|
||||
│ └── 漏洞利用 ───────────────→ 完全控制宿主机 │
|
||||
└────────────────────────────────────────────────────────┘
|
||||
|
||||
非 root 用户运行:
|
||||
┌────────────────────────────────────────────────────────┐
|
||||
│ 容器内普通用户 ──逃逸后──→ 宿主机普通用户 │
|
||||
│ │ │ │
|
||||
│ └── 权限受限,危害降低 ─────→ 无法控制系统 │
|
||||
└────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 基本用法
|
||||
|
||||
### 创建并切换用户
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 用户必须已存在
|
||||
|
||||
`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 |
|
||||
|
||||
---
|
||||
|
||||
## 运行时切换用户
|
||||
|
||||
### 使用 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 替换 |
|
||||
| 容器中使用 | ❌ | ✅ |
|
||||
|
||||
---
|
||||
|
||||
## 运行时覆盖用户
|
||||
|
||||
使用 `-u` 或 `--user` 参数:
|
||||
|
||||
```bash
|
||||
# 以指定用户运行
|
||||
$ docker run -u 1001:1001 myimage
|
||||
|
||||
# 以 root 运行(调试时)
|
||||
$ docker run -u root myimage
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 文件权限处理
|
||||
|
||||
切换用户后,确保应用有权访问文件:
|
||||
|
||||
```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"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 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"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q: 权限被拒绝
|
||||
|
||||
```bash
|
||||
permission denied: '/app/data.log'
|
||||
```
|
||||
|
||||
**解决**:确保目录权限正确
|
||||
|
||||
```docker
|
||||
RUN mkdir -p /app/data && chown appuser:appuser /app/data
|
||||
```
|
||||
|
||||
### Q: 无法绑定低于 1024 的端口
|
||||
|
||||
非 root 用户无法绑定 80、443 等端口。
|
||||
|
||||
**解决**:
|
||||
1. 使用高端口(如 8080)
|
||||
2. 在运行时映射端口:`docker run -p 80:8080`
|
||||
|
||||
---
|
||||
|
||||
## 本章小结
|
||||
|
||||
| 要点 | 说明 |
|
||||
|------|------|
|
||||
| **作用** | 切换后续指令的执行用户 |
|
||||
| **语法** | `USER username` 或 `USER UID:GID` |
|
||||
| **前提** | 用户必须已存在 |
|
||||
| **运行时覆盖** | `docker run -u` |
|
||||
| **切换工具** | 使用 gosu,不用 su/sudo |
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [安全](../../security/README.md):容器安全实践
|
||||
- [ENTRYPOINT](entrypoint.md):入口脚本中的用户切换
|
||||
- [最佳实践](../../15_appendix/best_practices.md):Dockerfile 安全
|
||||
@@ -1,247 +0,0 @@
|
||||
# VOLUME 定义匿名卷
|
||||
|
||||
## 基本语法
|
||||
|
||||
```docker
|
||||
VOLUME ["/路径1", "/路径2"]
|
||||
VOLUME /路径
|
||||
```
|
||||
|
||||
`VOLUME` 指令创建挂载点,并标记为外部挂载的卷。
|
||||
|
||||
---
|
||||
|
||||
## 为什么使用 VOLUME
|
||||
|
||||
> **核心原则**:容器存储层应该保持无状态,任何运行时数据都应该存储在卷中。
|
||||
|
||||
```
|
||||
没有 VOLUME: 使用 VOLUME:
|
||||
┌─────────────────────┐ ┌─────────────────────┐
|
||||
│ 容器存储层 │ │ 容器存储层 │
|
||||
│ ┌─────────────┐ │ │ (只读/无状态) │
|
||||
│ │ 数据库文件 │←─问题 │ │
|
||||
│ │ 日志文件 │ │ └──────────┬──────────┘
|
||||
│ │ 上传文件 │ │ │
|
||||
│ └─────────────┘ │ ┌──────────▼──────────┐
|
||||
└─────────────────────┘ │ 数据卷 │
|
||||
容器删除 = 数据丢失 │ ┌─────────────┐ │
|
||||
│ │ 持久化数据 │←─安全
|
||||
│ └─────────────┘ │
|
||||
└─────────────────────┘
|
||||
容器删除,数据保留
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 基本用法
|
||||
|
||||
### 定义单个卷
|
||||
|
||||
```docker
|
||||
FROM mysql:8.0
|
||||
VOLUME /var/lib/mysql
|
||||
```
|
||||
|
||||
### 定义多个卷
|
||||
|
||||
```docker
|
||||
FROM myapp
|
||||
VOLUME ["/data", "/logs", "/config"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 常见使用场景
|
||||
|
||||
### 数据库持久化
|
||||
|
||||
```docker
|
||||
FROM postgres:15
|
||||
VOLUME /var/lib/postgresql/data
|
||||
```
|
||||
|
||||
### 日志目录
|
||||
|
||||
```docker
|
||||
FROM nginx
|
||||
VOLUME /var/log/nginx
|
||||
```
|
||||
|
||||
### 上传文件目录
|
||||
|
||||
```docker
|
||||
FROM myapp
|
||||
VOLUME /app/uploads
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 查看 VOLUME 定义
|
||||
|
||||
```bash
|
||||
# 查看镜像定义的 VOLUME
|
||||
$ docker inspect mysql:8.0 --format '{{json .Config.Volumes}}' | jq
|
||||
{
|
||||
"/var/lib/mysql": {}
|
||||
}
|
||||
|
||||
# 查看容器挂载的卷
|
||||
$ docker inspect mycontainer --format '{{json .Mounts}}' | jq
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## VOLUME vs docker run -v
|
||||
|
||||
| 特性 | Dockerfile VOLUME | docker run -v |
|
||||
|------|-------------------|---------------|
|
||||
| **定义时机** | 镜像构建时 | 容器运行时 |
|
||||
| **默认行为** | 创建匿名卷 | 可指定命名卷或路径 |
|
||||
| **灵活性** | 低(固定路径) | 高(可任意指定) |
|
||||
| **适用场景** | 定义必须持久化的路径 | 灵活的数据管理 |
|
||||
|
||||
---
|
||||
|
||||
## 在 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: # 声明命名卷
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 安全注意事项
|
||||
|
||||
### 匿名卷可能导致数据丢失
|
||||
|
||||
```bash
|
||||
# 使用 --rm 运行的容器,匿名卷会在容器删除时一起删除
|
||||
$ docker run --rm mysql:8.0
|
||||
# 容器停止后,数据丢失!
|
||||
```
|
||||
|
||||
**解决**:始终使用命名卷
|
||||
|
||||
```bash
|
||||
$ docker run -v mysql_data:/var/lib/mysql mysql:8.0
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 本章小结
|
||||
|
||||
| 要点 | 说明 |
|
||||
|------|------|
|
||||
| **作用** | 创建挂载点,标记为外部卷 |
|
||||
| **语法** | `VOLUME /path` |
|
||||
| **默认行为** | 自动创建匿名卷 |
|
||||
| **覆盖方式** | `docker run -v name:/path` |
|
||||
| **注意** | VOLUME 之后的修改会丢失 |
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [数据卷](../../07_data_network/data/volume.md):卷的管理和使用
|
||||
- [挂载主机目录](../../07_data_network/data/bind-mounts.md):Bind Mount
|
||||
- [Compose 数据管理](../../compose/compose_file.md):Compose 中的卷配置
|
||||
@@ -1,9 +0,0 @@
|
||||
# 镜像的实现原理
|
||||
|
||||
Docker 镜像是怎么实现增量的修改和维护的?
|
||||
|
||||
每个镜像都由很多层次构成,Docker 使用 [Union FS](https://en.wikipedia.org/wiki/UnionFS) 将这些不同的层结合到一个镜像中去。
|
||||
|
||||
通常 Union FS 有两个用途, 一方面可以实现不借助 LVM、RAID 将多个 disk 挂到同一个目录下,另一个更常用的就是将一个只读的分支和一个可写的分支联合在一起,Live CD 正是基于此方法可以允许在镜像不变的基础上允许用户在其上进行一些写操作。
|
||||
|
||||
Docker 在 OverlayFS 上构建的容器也是利用了类似的原理。
|
||||
36
04_image/summary.md
Normal file
@@ -0,0 +1,36 @@
|
||||
## 本章小结
|
||||
|
||||
本章介绍了 Docker 镜像的获取、列出、删除以及构建方式。
|
||||
|
||||
| 操作 | 命令 |
|
||||
|------|------|
|
||||
| 拉取镜像 | `docker pull 镜像名:标签` |
|
||||
| 拉取所有标签 | `docker pull -a 镜像名` |
|
||||
| 指定平台 | `docker pull --platform linux/amd64 镜像名` |
|
||||
| 用摘要拉取 | `docker pull 镜像名@sha256:...` |
|
||||
| 列出所有镜像 | `docker images` |
|
||||
| 按仓库名过滤 | `docker images nginx` |
|
||||
| 列出虚悬镜像 | `docker images -f dangling=true` |
|
||||
| 只输出 ID | `docker images -q` |
|
||||
| 显示摘要 | `docker images --digests` |
|
||||
| 自定义格式 | `docker images --format "..."` |
|
||||
| 查看空间占用 | `docker system df` |
|
||||
| 删除指定镜像 | `docker rmi 镜像名:标签` |
|
||||
| 强制删除 | `docker rmi -f 镜像名` |
|
||||
| 删除虚悬镜像 | `docker image prune` |
|
||||
| 删除未使用镜像 | `docker image prune -a` |
|
||||
| 批量删除 | `docker rmi $(docker images -q -f ...)` |
|
||||
|
||||
### 延伸阅读
|
||||
|
||||
- [获取镜像](4.1_pull.md):从 Registry 拉取镜像
|
||||
- [列出镜像](4.2_list.md):查看和过滤镜像
|
||||
- [删除镜像](4.3_rm.md):清理本地镜像
|
||||
- [镜像加速器](../03_install/3.9_mirror.md):加速镜像下载
|
||||
- [Docker Hub](../06_repository/6.1_dockerhub.md):官方镜像仓库
|
||||
- [镜像](../02_basic_concept/2.1_image.md):理解镜像概念
|
||||
- [删除容器](../05_container/5.6_rm.md):清理容器
|
||||
- [数据卷](../08_data/8.1_volume.md):清理数据卷
|
||||
---
|
||||
|
||||
> 📝 **发现错误或有改进建议?** 欢迎提交 [Issue](https://github.com/yeasy/docker_practice/issues) 或 [PR](https://github.com/yeasy/docker_practice/pulls)。
|
||||
225
05_container/5.1_run.md
Normal file
@@ -0,0 +1,225 @@
|
||||
## 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)。
|
||||
@@ -1,19 +1,19 @@
|
||||
# 后台运行
|
||||
## 5.2 守护态运行
|
||||
|
||||
在生产环境中,我们通常需要容器持续运行,不受终端关闭的影响。本节将深入讲解如何让容器在后台运行,以及理解容器生命周期的核心概念。
|
||||
|
||||
## 核心概念:前台 vs 后台
|
||||
### 5.2.1 核心概念:前台 vs 后台
|
||||
|
||||
当你在终端运行一个程序时,有两种模式:
|
||||
|
||||
- **前台运行**:程序占用当前终端,输出直接显示,关闭终端程序就停止
|
||||
- **后台运行**:程序在后台执行,不占用终端,终端关闭也不影响程序
|
||||
|
||||
Docker 容器默认是**前台运行**的。使用 `-d`(detach)参数可以让容器在后台运行。
|
||||
Docker 容器默认是 **前台运行** 的。使用 `-d` (detach) 参数可以让容器在后台运行。
|
||||
|
||||
## 基本使用
|
||||
### 5.2.2 基本使用
|
||||
|
||||
### 前台运行(默认)
|
||||
#### 前台运行 (默认)
|
||||
|
||||
```bash
|
||||
$ docker run ubuntu:24.04 /bin/sh -c "while true; do echo hello world; sleep 1; done"
|
||||
@@ -23,12 +23,13 @@ hello world
|
||||
hello world
|
||||
```
|
||||
|
||||
容器会把输出的结果(STDOUT)打印到宿主机上面。此时:
|
||||
容器会把输出的结果 (STDOUT) 打印到宿主机上面。此时:
|
||||
|
||||
- 终端被占用,无法执行其他命令
|
||||
- 按 `Ctrl+C` 会终止容器
|
||||
- 关闭终端窗口,容器也会停止
|
||||
|
||||
### 后台运行(使用 -d 参数)
|
||||
#### 后台运行 (使用 -d 参数)
|
||||
|
||||
```bash
|
||||
$ docker run -d ubuntu:24.04 /bin/sh -c "while true; do echo hello world; sleep 1; done"
|
||||
@@ -36,12 +37,13 @@ $ docker run -d ubuntu:24.04 /bin/sh -c "while true; do echo hello world; sleep
|
||||
```
|
||||
|
||||
使用 `-d` 参数后:
|
||||
|
||||
- 容器在后台运行
|
||||
- 返回容器的完整 ID
|
||||
- 终端立即释放,可以继续执行其他命令
|
||||
- 输出不会直接显示(需要用 `docker logs` 查看)
|
||||
- 输出不会直接显示 (需要用 `docker logs` 查看)
|
||||
|
||||
## 深入理解:容器为什么会"立即退出"?
|
||||
### 5.2.3 深入理解:容器为什么会 “立即退出”?
|
||||
|
||||
> **这是初学者最常遇到的困惑。** 理解这个问题,你就理解了 Docker 的核心设计理念。
|
||||
|
||||
@@ -53,29 +55,31 @@ $ docker run -d ubuntu:24.04
|
||||
|
||||
然后用 `docker ps` 查看,发现容器根本不在运行!这是为什么?
|
||||
|
||||
### 核心原理:容器的生命周期与主进程绑定
|
||||
#### 核心原理:容器的生命周期与主进程绑定
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Docker 容器的生命周期 = 容器内 PID 1 进程的生命周期 │
|
||||
│ │
|
||||
│ 主进程启动 → 容器运行 │
|
||||
│ 主进程退出 → 容器停止 │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```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 发现没有输入源
|
||||
3. 但没有交互式终端 (没有 `-it` 参数),bash 发现没有输入源
|
||||
4. bash 立即退出
|
||||
5. 主进程退出,容器停止
|
||||
|
||||
**关键理解**:
|
||||
- ❌ `-d` 参数**不是**让容器"一直运行"
|
||||
- ✅ `-d` 参数是让容器"在后台运行",能运行多久取决于主进程
|
||||
|
||||
### 常见的"立即退出"场景
|
||||
- ❌ `-d` 参数 **不是** 让容器 “一直运行”
|
||||
- ✅ `-d` 参数是让容器 “在后台运行”,能运行多久取决于主进程
|
||||
|
||||
#### 常见的 “立即退出” 场景
|
||||
|
||||
| 场景 | 原因 | 解决方案 |
|
||||
|------|------|---------|
|
||||
@@ -83,9 +87,9 @@ $ docker run -d ubuntu:24.04
|
||||
| `docker run -d nginx` 后改了配置 | 配置错误导致 nginx 启动失败 | 查看 `docker logs` |
|
||||
| 自定义镜像容器启动即退 | Dockerfile 的 CMD 执行完毕 | 确保 CMD 是前台进程 |
|
||||
|
||||
## 查看后台容器
|
||||
### 5.2.4 查看后台容器
|
||||
|
||||
### 查看运行中的容器
|
||||
#### 查看运行中的容器
|
||||
|
||||
```bash
|
||||
$ docker container ls
|
||||
@@ -93,7 +97,7 @@ CONTAINER ID IMAGE COMMAND CREATED STATUS PO
|
||||
77b2dc01fe0f ubuntu:24.04 /bin/sh -c 'while tr 2 minutes ago Up 1 minute agitated_wright
|
||||
```
|
||||
|
||||
### 查看容器输出日志
|
||||
#### 查看容器输出日志
|
||||
|
||||
```bash
|
||||
$ docker container logs 77b2dc01fe0f
|
||||
@@ -103,78 +107,89 @@ hello world
|
||||
...
|
||||
```
|
||||
|
||||
**实时查看日志**(类似 `tail -f`):
|
||||
**实时查看日志** (类似 `tail -f`):
|
||||
|
||||
```bash
|
||||
$ docker container logs -f 77b2dc01fe0f
|
||||
```
|
||||
|
||||
### 查看已停止的容器
|
||||
#### 查看已停止的容器
|
||||
|
||||
```bash
|
||||
$ docker container ls -a
|
||||
```
|
||||
|
||||
加上 `-a` 参数可以看到所有容器,包括已停止的。这对于调试"容器启动即退出"的问题非常有用。
|
||||
加上 `-a` 参数可以看到所有容器,包括已停止的。这对于调试 “容器启动即退出” 的问题非常有用。
|
||||
|
||||
## 最佳实践
|
||||
### 5.2.5 最佳实践
|
||||
|
||||
### 1. 长期运行的服务使用 -d
|
||||
#### 1. 长期运行的服务使用 -d
|
||||
|
||||
```bash
|
||||
# Web 服务器
|
||||
## Web 服务器
|
||||
|
||||
$ docker run -d -p 80:80 nginx
|
||||
|
||||
# 数据库
|
||||
## 数据库
|
||||
|
||||
$ docker run -d -p 3306:3306 mysql:8
|
||||
|
||||
# 缓存服务
|
||||
## 缓存服务
|
||||
|
||||
$ docker run -d -p 6379:6379 redis
|
||||
```
|
||||
|
||||
### 2. 调试时先用前台模式
|
||||
#### 2. 调试时先用前台模式
|
||||
|
||||
当容器启动有问题时,**去掉 `-d` 参数**可以直接看到输出和错误:
|
||||
当容器启动有问题时,**去掉 `-d` 参数** 可以直接看到输出和错误:
|
||||
|
||||
```bash
|
||||
# 有问题的容器,先前台运行看看发生了什么
|
||||
## 有问题的容器,先前台运行看看发生了什么
|
||||
|
||||
$ docker run myimage:latest
|
||||
```
|
||||
|
||||
### 3. 使用 --rm 自动清理
|
||||
#### 3. 使用 --rm 自动清理
|
||||
|
||||
对于一次性任务,使用 `--rm` 参数让容器退出后自动删除:
|
||||
|
||||
```bash
|
||||
$ docker run --rm ubuntu:24.04 echo "Hello, World!"
|
||||
Hello, World!
|
||||
# 容器执行完后自动删除
|
||||
## 容器执行完后自动删除
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
### 4. 配合日志查看
|
||||
#### 4. 配合日志查看
|
||||
|
||||
```bash
|
||||
# 后台启动
|
||||
## 后台启动
|
||||
|
||||
$ docker run -d --name myapp myimage:latest
|
||||
|
||||
# 查看最近 100 行日志
|
||||
## 查看最近 100 行日志
|
||||
|
||||
$ docker logs --tail 100 myapp
|
||||
|
||||
# 实时跟踪日志
|
||||
## 实时跟踪日志
|
||||
|
||||
$ docker logs -f myapp
|
||||
|
||||
# 查看带时间戳的日志
|
||||
## 查看带时间戳的日志
|
||||
|
||||
$ docker logs -t myapp
|
||||
```
|
||||
|
||||
## 常见问题排查
|
||||
### 5.2.6 常见问题排查
|
||||
|
||||
### Q: 容器启动后立即退出
|
||||
#### Q:容器启动后立即退出
|
||||
|
||||
1. **查看退出状态码**:
|
||||
```bash
|
||||
$ docker ps -a --filter "name=mycontainer"
|
||||
# 查看 STATUS 列,如 "Exited (1)" 表示异常退出
|
||||
# 查看 STATUS 列,如 “Exited (1)” 表示异常退出
|
||||
|
||||
```
|
||||
|
||||
2. **查看容器日志**:
|
||||
@@ -186,9 +201,10 @@ $ docker logs -t myapp
|
||||
```bash
|
||||
$ docker run -it myimage:latest /bin/sh
|
||||
# 进入容器手动执行命令,查找问题
|
||||
|
||||
```
|
||||
|
||||
### Q: 容器在后台运行但无法访问服务
|
||||
#### Q:容器在后台运行但无法访问服务
|
||||
|
||||
1. **检查端口映射**:
|
||||
```bash
|
||||
@@ -200,7 +216,7 @@ $ docker logs -t myapp
|
||||
$ docker exec mycontainer ps aux
|
||||
```
|
||||
|
||||
### Q: 如何让已经在后台运行的容器回到前台?
|
||||
#### Q:如何让已经在后台运行的容器回到前台?
|
||||
|
||||
使用 `docker attach`:
|
||||
|
||||
@@ -210,9 +226,9 @@ $ docker attach mycontainer
|
||||
|
||||
> **注意**:`attach` 会连接到容器的主进程。如果主进程不是交互式的,你可能只能看到输出。使用 `Ctrl+P` `Ctrl+Q` 可以安全退出而不停止容器。
|
||||
|
||||
## 延伸阅读
|
||||
### 5.2.7 延伸阅读
|
||||
|
||||
- [进入容器](attach_exec.md):如何进入正在运行的容器执行命令
|
||||
- [容器日志](../15_appendix/best_practices.md):生产环境的日志管理最佳实践
|
||||
- [HEALTHCHECK 健康检查](../04_image/dockerfile/healthcheck.md):自动检测容器内服务是否正常
|
||||
- [Docker Compose](../compose/README.md):管理多个后台容器的更好方式
|
||||
- [进入容器](5.4_attach_exec.md):如何进入正在运行的容器执行命令
|
||||
- [容器日志](../appendix/best_practices.md):生产环境的日志管理最佳实践
|
||||
- [HEALTHCHECK 健康检查](../07_dockerfile/7.12_healthcheck.md):自动检测容器内服务是否正常
|
||||
- [Docker Compose](../11_compose/README.md):管理多个后台容器的更好方式
|
||||
233
05_container/5.3_stop.md
Normal file
@@ -0,0 +1,233 @@
|
||||
## 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 容器名
|
||||
```
|
||||
|
||||
---
|
||||
282
05_container/5.4_attach_exec.md
Normal file
@@ -0,0 +1,282 @@
|
||||
## 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 常见问题
|
||||
|
||||
#### Q:exec 进入后看不到其他终端的操作
|
||||
|
||||
这是正常的。exec 启动的是独立进程,多个 exec 会话互不影响。
|
||||
|
||||
#### Q:容器没有 bash
|
||||
|
||||
尝试使用 sh:
|
||||
|
||||
```bash
|
||||
$ docker exec -it myapp /bin/sh
|
||||
```
|
||||
|
||||
#### Q:需要 root 权限
|
||||
|
||||
```bash
|
||||
$ docker exec -u root -it myapp bash
|
||||
```
|
||||
|
||||
---
|
||||
@@ -1,6 +1,8 @@
|
||||
# 导出和导入容器
|
||||
## 5.5 导出和导入
|
||||
|
||||
## 导出容器
|
||||
当我们需要迁移容器或者备份容器时,可以使用 Docker 的导入和导出功能。本节将介绍这两个命令的使用方法。
|
||||
|
||||
### 5.5.1 导出容器
|
||||
|
||||
如果要导出本地某个容器,可以使用 `docker export` 命令。
|
||||
```bash
|
||||
@@ -12,7 +14,7 @@ $ docker export 7691a814370e > ubuntu.tar
|
||||
|
||||
这样将导出容器快照到本地文件。
|
||||
|
||||
## 导入容器快照
|
||||
### 5.5.2 导入容器快照
|
||||
|
||||
可以使用 `docker import` 从容器快照文件中再导入为镜像,例如
|
||||
|
||||
@@ -29,4 +31,4 @@ test/ubuntu v1.0 9d37a6082e97 About a minute ago
|
||||
$ docker import http://example.com/exampleimage.tgz example/imagerepo
|
||||
```
|
||||
|
||||
*注:用户既可以使用 `docker load` 来导入镜像存储文件到本地镜像库,也可以使用 `docker import` 来导入一个容器快照到本地镜像库。这两者的区别在于容器快照文件将丢弃所有的历史记录和元数据信息(即仅保存容器当时的快照状态),而镜像存储文件将保存完整记录,体积也要大。此外,从容器快照文件导入时可以重新指定标签等元数据信息。*
|
||||
*注:用户既可以使用 `docker load` 来导入镜像存储文件到本地镜像库,也可以使用 `docker import` 来导入一个容器快照到本地镜像库。这两者的区别在于容器快照文件将丢弃所有的历史记录和元数据信息 (即仅保存容器当时的快照状态),而镜像存储文件将保存完整记录,体积也要大。此外,从容器快照文件导入时可以重新指定标签等元数据信息。*
|
||||
@@ -1,6 +1,8 @@
|
||||
# 删除容器
|
||||
## 5.6 删除
|
||||
|
||||
## 基本用法
|
||||
随着容器的创建和停止,系统中会积累大量的容器。本节将介绍如何删除不再需要的容器,以及如何清理所有停止的容器。
|
||||
|
||||
### 5.6.1 基本用法
|
||||
|
||||
使用 `docker rm` 删除已停止的容器:
|
||||
|
||||
@@ -12,7 +14,7 @@ $ docker rm 容器名或ID
|
||||
|
||||
---
|
||||
|
||||
## 删除选项
|
||||
### 5.6.2 删除选项
|
||||
|
||||
| 选项 | 说明 | 示例 |
|
||||
|------|------|------|
|
||||
@@ -20,31 +22,34 @@ $ docker rm 容器名或ID
|
||||
| `-f` | 强制删除运行中的容器 | `docker rm -f mycontainer` |
|
||||
| `-v` | 同时删除关联的匿名卷 | `docker rm -v mycontainer` |
|
||||
|
||||
### 删除已停止的容器
|
||||
#### 删除已停止的容器
|
||||
|
||||
```bash
|
||||
$ docker rm mycontainer
|
||||
mycontainer
|
||||
```
|
||||
|
||||
### 强制删除运行中的容器
|
||||
#### 强制删除运行中的容器
|
||||
|
||||
```bash
|
||||
# 不加 -f 会报错
|
||||
## 不加 -f 会报错
|
||||
|
||||
$ docker rm running_container
|
||||
Error: cannot remove running container
|
||||
|
||||
# 加 -f 强制删除
|
||||
## 加 -f 强制删除
|
||||
|
||||
$ docker rm -f running_container
|
||||
running_container
|
||||
```
|
||||
|
||||
> ⚠️ 强制删除会向容器发送 SIGKILL 信号,可能导致数据丢失。建议先 `docker stop` 优雅停止。
|
||||
|
||||
### 删除容器及其数据卷
|
||||
#### 删除容器及其数据卷
|
||||
|
||||
```bash
|
||||
# 删除容器时同时删除其匿名卷
|
||||
## 删除容器时同时删除其匿名卷
|
||||
|
||||
$ docker rm -v mycontainer
|
||||
```
|
||||
|
||||
@@ -52,12 +57,13 @@ $ docker rm -v mycontainer
|
||||
|
||||
---
|
||||
|
||||
## 批量删除
|
||||
### 5.6.3 批量删除
|
||||
|
||||
### 删除所有已停止的容器
|
||||
#### 删除所有已停止的容器
|
||||
|
||||
```bash
|
||||
# 方式一:使用 prune 命令(推荐)
|
||||
## 方式一:使用 prune 命令(推荐)
|
||||
|
||||
$ docker container prune
|
||||
|
||||
WARNING! This will remove all stopped containers.
|
||||
@@ -67,37 +73,43 @@ 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" 的容器
|
||||
## 删除名称包含 "test" 的容器
|
||||
|
||||
$ docker rm $(docker ps -aq -f name=test)
|
||||
|
||||
# 删除 24 小时前创建的容器
|
||||
## 删除 24 小时前创建的容器
|
||||
|
||||
$ docker container prune --filter "until=24h"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 常用过滤条件
|
||||
### 5.6.4 常用过滤条件
|
||||
|
||||
`docker ps` 的过滤条件可以配合 `rm` 使用:
|
||||
|
||||
@@ -110,62 +122,70 @@ $ docker container prune --filter "until=24h"
|
||||
| `before=xxx` | 在某容器之前创建 | `-f before=mycontainer` |
|
||||
| `since=xxx` | 在某容器之后创建 | `-f since=mycontainer` |
|
||||
|
||||
### 示例
|
||||
#### 示例
|
||||
|
||||
```bash
|
||||
# 删除所有基于 nginx 镜像的容器
|
||||
## 删除所有基于 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"
|
||||
# 容器退出后自动删除
|
||||
## 使用 --rm 参数运行临时容器
|
||||
|
||||
$ docker run --rm ubuntu echo "Hello"
|
||||
## 容器退出后自动删除
|
||||
|
||||
## 定期清理(设置保留时间)
|
||||
|
||||
# 定期清理(设置保留时间)
|
||||
$ docker container prune --filter "until=168h" # 保留 7 天内的
|
||||
```
|
||||
|
||||
### 完整清理脚本
|
||||
#### 完整清理脚本
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# cleanup.sh - Docker 资源清理脚本
|
||||
## cleanup.sh - Docker 资源清理脚本
|
||||
|
||||
echo "清理已停止的容器..."
|
||||
docker container prune -f
|
||||
@@ -185,9 +205,9 @@ docker system df
|
||||
|
||||
---
|
||||
|
||||
## 常见问题
|
||||
### 5.6.7 常见问题
|
||||
|
||||
### Q: 容器无法删除
|
||||
#### Q:容器无法删除
|
||||
|
||||
```bash
|
||||
Error: container is running
|
||||
@@ -198,41 +218,29 @@ Error: container is running
|
||||
```bash
|
||||
$ docker stop mycontainer
|
||||
$ docker rm mycontainer
|
||||
# 或
|
||||
## 或
|
||||
|
||||
$ docker rm -f mycontainer
|
||||
```
|
||||
|
||||
### Q: 删除后磁盘空间没释放
|
||||
#### Q:删除后磁盘空间没释放
|
||||
|
||||
可能原因:
|
||||
1. 容器的数据卷未删除(使用 `-v` 参数)
|
||||
|
||||
1. 容器的数据卷未删除 (使用 `-v` 参数)
|
||||
2. 镜像未删除
|
||||
3. 构建缓存未清理
|
||||
|
||||
解决:
|
||||
|
||||
```bash
|
||||
# 查看空间占用
|
||||
## 查看空间占用
|
||||
|
||||
$ docker system df
|
||||
|
||||
# 完整清理
|
||||
## 完整清理
|
||||
|
||||
$ docker system prune -a --volumes
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 本章小结
|
||||
|
||||
| 操作 | 命令 |
|
||||
|------|------|
|
||||
| 删除已停止容器 | `docker rm 容器名` |
|
||||
| 强制删除运行中容器 | `docker rm -f 容器名` |
|
||||
| 删除容器及匿名卷 | `docker rm -v 容器名` |
|
||||
| 清理所有已停止容器 | `docker container prune` |
|
||||
| 删除所有容器 | `docker rm -f $(docker ps -aq)` |
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [终止容器](stop.md):优雅停止容器
|
||||
- [删除镜像](../04_image/rm.md):清理镜像
|
||||
- [数据卷](../07_data_network/data/volume.md):数据卷管理
|
||||
@@ -1,7 +1,14 @@
|
||||
# 操作 Docker 容器
|
||||
# 第五章 操作容器
|
||||
|
||||
容器是 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)
|
||||
|
||||
@@ -1,264 +0,0 @@
|
||||
# 进入容器
|
||||
|
||||
## 为什么需要进入容器
|
||||
|
||||
使用 `-d` 参数启动容器后,容器在后台运行。以下场景需要进入容器内部操作:
|
||||
|
||||
| 场景 | 示例 |
|
||||
|------|------|
|
||||
| **调试问题** | 查看日志、检查配置、排查错误 |
|
||||
| **临时操作** | 执行数据库迁移、清理缓存 |
|
||||
| **检查状态** | 查看进程、网络连接、文件系统 |
|
||||
| **开发测试** | 交互式测试命令、验证环境 |
|
||||
|
||||
## 两种进入方式
|
||||
|
||||
Docker 提供两种进入容器的命令:
|
||||
|
||||
| 命令 | 推荐程度 | 特点 |
|
||||
|------|---------|------|
|
||||
| `docker exec` | ✅ **推荐** | 启动新进程,退出不影响容器 |
|
||||
| `docker attach` | ⚠️ 谨慎使用 | 附加到主进程,退出可能停止容器 |
|
||||
|
||||
---
|
||||
|
||||
## 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` | 设置环境变量 |
|
||||
|
||||
### 示例
|
||||
|
||||
```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`。
|
||||
|
||||
---
|
||||
|
||||
## docker attach(谨慎使用)
|
||||
|
||||
### 基本用法
|
||||
|
||||
```bash
|
||||
$ docker attach 容器名
|
||||
```
|
||||
|
||||
### 工作原理
|
||||
|
||||
`attach` 会附加到容器的**主进程**(PID 1)的标准输入输出:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ 容器 │
|
||||
│ ┌─────────────────────────────────┐ │
|
||||
│ │ PID 1: /bin/bash (主进程) │◄───┼─── 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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## exec vs attach 对比
|
||||
|
||||
| 特性 | docker exec | docker attach |
|
||||
|------|-------------|---------------|
|
||||
| **工作方式** | 在容器内启动新进程 | 附加到主进程 |
|
||||
| **退出影响** | 不影响容器 | 可能停止容器 |
|
||||
| **多终端** | 可以开多个 | 共享同一个会话 |
|
||||
| **适用场景** | 调试、临时操作 | 查看主进程输出 |
|
||||
| **推荐程度** | ✅ 推荐 | ⚠️ 特殊场景使用 |
|
||||
|
||||
```
|
||||
docker exec docker attach
|
||||
┌─────────────────────┐ ┌─────────────────────┐
|
||||
│ 容器 │ │ 容器 │
|
||||
│ ┌───────────────┐ │ │ ┌───────────────┐ │
|
||||
│ │ PID 1: nginx │ │ │ │ PID 1: bash │◄─┼── 附加到主进程
|
||||
│ ├───────────────┤ │ │ └───────────────┘ │
|
||||
│ │ PID 50: bash │◄─┼── 新进程 │ │
|
||||
│ └───────────────┘ │ │ │
|
||||
└─────────────────────┘ └─────────────────────┘
|
||||
退出 bash 不影响 nginx 退出 bash 容器停止
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q: exec 进入后看不到其他终端的操作
|
||||
|
||||
这是正常的。exec 启动的是独立进程,多个 exec 会话互不影响。
|
||||
|
||||
### Q: 容器没有 bash
|
||||
|
||||
尝试使用 sh:
|
||||
|
||||
```bash
|
||||
$ docker exec -it myapp /bin/sh
|
||||
```
|
||||
|
||||
### Q: 需要 root 权限
|
||||
|
||||
```bash
|
||||
$ docker exec -u root -it myapp bash
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 本章小结
|
||||
|
||||
| 需求 | 推荐命令 |
|
||||
|------|---------|
|
||||
| 进入容器调试 | `docker exec -it 容器名 bash` |
|
||||
| 执行单条命令 | `docker exec 容器名 命令` |
|
||||
| 查看主进程输出 | `docker attach 容器名`(慎用) |
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [后台运行](daemon.md):理解容器主进程
|
||||
- [查看容器](ls.md):列出和过滤容器
|
||||
- [容器日志](logs.md):查看容器输出
|
||||
@@ -1,229 +0,0 @@
|
||||
# 启动容器
|
||||
|
||||
## 启动方式概述
|
||||
|
||||
启动容器有两种方式:
|
||||
- **新建并启动**:基于镜像创建新容器
|
||||
- **重新启动**:将已终止的容器重新运行
|
||||
|
||||
由于 Docker 容器非常轻量,实际使用中常常是随时删除和新建容器,而不是反复重启同一个容器。
|
||||
|
||||
## 新建并启动
|
||||
|
||||
### 基本语法
|
||||
|
||||
```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 # 退出容器
|
||||
```
|
||||
|
||||
## docker run 的完整流程
|
||||
|
||||
执行 `docker run` 时,Docker 在后台完成以下操作:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ docker run ubuntu:24.04 /bin/echo "Hello" │
|
||||
└───────────────────────────────┬─────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ 1. 检查本地是否有 ubuntu:24.04 镜像 │
|
||||
│ ├── 有 → 使用本地镜像 │
|
||||
│ └── 无 → 从 Registry 下载 │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ 2. 创建容器 │
|
||||
│ • 基于镜像的只读层 │
|
||||
│ • 添加一层可读写层(容器存储层) │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ 3. 配置网络 │
|
||||
│ • 创建虚拟网卡 │
|
||||
│ • 分配 IP 地址 │
|
||||
│ • 连接到 Docker 网桥 │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ 4. 启动容器,执行指定命令 │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ 5. 命令执行完毕,容器停止 │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 常用启动选项
|
||||
|
||||
### 基础选项
|
||||
|
||||
| 选项 | 说明 | 示例 |
|
||||
|------|------|------|
|
||||
| `-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
|
||||
```
|
||||
|
||||
## 启动已终止容器
|
||||
|
||||
使用 `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
|
||||
```
|
||||
|
||||
## 容器内进程的特点
|
||||
|
||||
容器内只运行指定的应用程序及其必需资源:
|
||||
|
||||
```bash
|
||||
root@ba267838cc1b:/# ps
|
||||
PID TTY TIME CMD
|
||||
1 ? 00:00:00 bash
|
||||
11 ? 00:00:00 ps
|
||||
```
|
||||
|
||||
可见容器中仅运行了 `bash` 进程。这种特点使得 Docker 对资源的利用率极高。
|
||||
|
||||
> 💡 笔者提示:容器内的 PID 1 进程很重要——它是容器的主进程,该进程退出则容器停止。详见[后台运行](daemon.md)章节。
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q: 容器启动后立即退出
|
||||
|
||||
**原因**:主进程执行完毕或无法保持运行
|
||||
|
||||
```bash
|
||||
# 这个容器会立即退出(echo 执行完就结束了)
|
||||
$ docker run ubuntu echo "hello"
|
||||
|
||||
# 解决:使用能持续运行的命令
|
||||
$ docker run -d nginx # nginx 是持续运行的服务
|
||||
```
|
||||
|
||||
详细解释见[后台运行](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
|
||||
```
|
||||
|
||||
详见[数据管理](../07_data_network/README.md)。
|
||||
|
||||
## 本章小结
|
||||
|
||||
| 操作 | 命令 | 说明 |
|
||||
|------|------|------|
|
||||
| 新建并运行 | `docker run` | 最常用的启动方式 |
|
||||
| 交互式启动 | `docker run -it` | 用于调试或临时操作 |
|
||||
| 后台运行 | `docker run -d` | 用于服务类应用 |
|
||||
| 启动已停止的容器 | `docker start` | 重用已有容器 |
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [后台运行](daemon.md):理解 `-d` 参数和容器生命周期
|
||||
- [进入容器](attach_exec.md):操作运行中的容器
|
||||
- [网络配置](../network/README.md):理解端口映射的原理
|
||||
- [数据管理](../07_data_network/README.md):数据持久化方案
|
||||
@@ -1,256 +0,0 @@
|
||||
# 终止容器
|
||||
|
||||
## 终止方式概述
|
||||
|
||||
终止容器有三种方式:
|
||||
|
||||
| 方式 | 命令 | 说明 |
|
||||
|------|------|------|
|
||||
| **优雅停止** | `docker stop` | 先发 SIGTERM,超时后发 SIGKILL |
|
||||
| **强制停止** | `docker kill` | 直接发 SIGKILL |
|
||||
| **自动终止** | - | 容器主进程退出时自动停止 |
|
||||
|
||||
---
|
||||
|
||||
## docker stop(推荐)
|
||||
|
||||
### 基本用法
|
||||
|
||||
```bash
|
||||
$ docker stop 容器名或ID
|
||||
```
|
||||
|
||||
### 工作原理
|
||||
|
||||
```
|
||||
docker stop mycontainer
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ 1. 发送 SIGTERM 信号给容器主进程(PID 1) │
|
||||
│ ↓ │
|
||||
│ 2. 等待容器优雅退出(默认 10 秒) │
|
||||
│ ↓ │
|
||||
│ 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)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 容器自动终止
|
||||
|
||||
容器的生命周期与主进程绑定。主进程退出时,容器自动停止:
|
||||
|
||||
```bash
|
||||
# 主进程是交互式 bash
|
||||
$ docker run -it ubuntu bash
|
||||
root@abc123:/# exit # 退出 bash → 容器停止
|
||||
|
||||
# 主进程执行完毕
|
||||
$ docker run ubuntu echo "Hello" # echo 执行完 → 容器停止
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 查看已停止的容器
|
||||
|
||||
```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) |
|
||||
|
||||
---
|
||||
|
||||
## 重新启动容器
|
||||
|
||||
### 启动已停止的容器
|
||||
|
||||
```bash
|
||||
$ docker start 容器名或ID
|
||||
|
||||
# 启动并附加终端
|
||||
$ docker start -ai 容器名
|
||||
```
|
||||
|
||||
### 重启运行中的容器
|
||||
|
||||
```bash
|
||||
# 先停止再启动
|
||||
$ docker restart 容器名
|
||||
|
||||
# 自定义停止超时
|
||||
$ docker restart -t 30 容器名
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 生命周期状态图
|
||||
|
||||
```
|
||||
docker create
|
||||
│
|
||||
▼
|
||||
┌──────────┐
|
||||
┌────────│ Created │────────┐
|
||||
│ └──────────┘ │
|
||||
│ │ │
|
||||
│ │ docker start│
|
||||
│ ▼ │
|
||||
│ ┌──────────┐ │
|
||||
│ ┌────│ Running │────┐ │
|
||||
│ │ └──────────┘ │ │
|
||||
│ │ │ │ │
|
||||
│ │ docker │ docker │ │
|
||||
│ │ pause │ stop │ │
|
||||
│ ▼ │ │ │
|
||||
│ ┌──────┐ │ │ │
|
||||
│ │Paused│ │ │ │
|
||||
│ └──────┘ │ │ │
|
||||
│ │ │ │ │
|
||||
│ │ docker │ │ │
|
||||
│ │ unpause │ │ │
|
||||
│ ▼ ▼ │ │
|
||||
│ └──────►┌──────────┐◄┘ │
|
||||
│ │ Stopped │ │
|
||||
│ └──────────┘ │
|
||||
│ │ │
|
||||
│ │ docker rm │
|
||||
│ ▼ │
|
||||
│ ┌──────────┐ │
|
||||
└──────────►│ Deleted │◄────┘
|
||||
└──────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 批量操作
|
||||
|
||||
### 停止所有容器
|
||||
|
||||
```bash
|
||||
$ docker stop $(docker ps -q)
|
||||
```
|
||||
|
||||
### 删除所有已停止的容器
|
||||
|
||||
```bash
|
||||
$ docker container prune
|
||||
```
|
||||
|
||||
### 停止并删除所有容器
|
||||
|
||||
```bash
|
||||
$ docker stop $(docker ps -q) && docker container prune -f
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 常见问题
|
||||
|
||||
### 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 容器名
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 本章小结
|
||||
|
||||
| 操作 | 命令 | 说明 |
|
||||
|------|------|------|
|
||||
| 优雅停止 | `docker stop` | 先 SIGTERM,超时后 SIGKILL |
|
||||
| 强制停止 | `docker kill` | 直接 SIGKILL |
|
||||
| 重新启动 | `docker start` | 启动已停止的容器 |
|
||||
| 重启 | `docker restart` | 停止后立即启动 |
|
||||
| 停止全部 | `docker stop $(docker ps -q)` | 停止所有运行中容器 |
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [启动容器](run.md):容器启动详解
|
||||
- [删除容器](rm.md):清理容器
|
||||
- [容器日志](logs.md):排查停止原因
|
||||
33
05_container/summary.md
Normal file
@@ -0,0 +1,33 @@
|
||||
## 本章小结
|
||||
|
||||
本章介绍了 Docker 容器的启动、停止、进入和删除等核心操作。
|
||||
|
||||
| 操作 | 命令 | 说明 |
|
||||
|------|------|------|
|
||||
| 新建并运行 | `docker run` | 最常用的启动方式 |
|
||||
| 交互式启动 | `docker run -it` | 用于调试或临时操作 |
|
||||
| 后台运行 | `docker run -d` | 用于服务类应用 |
|
||||
| 启动已停止的容器 | `docker start` | 重用已有容器 |
|
||||
| 优雅停止 | `docker stop` | 先 SIGTERM,超时后 SIGKILL |
|
||||
| 强制停止 | `docker kill` | 直接 SIGKILL |
|
||||
| 重启 | `docker restart` | 停止后立即启动 |
|
||||
| 停止全部 | `docker stop $(docker ps -q)` | 停止所有运行中容器 |
|
||||
| 进入容器调试 | `docker exec -it 容器名 bash` | 推荐方式 |
|
||||
| 执行单条命令 | `docker exec 容器名 命令` | 不进入交互模式 |
|
||||
| 查看主进程输出 | `docker attach 容器名` | 慎用,退出可能停止容器 |
|
||||
| 删除已停止容器 | `docker rm 容器名` | 需先停止 |
|
||||
| 强制删除运行中容器 | `docker rm -f 容器名` | 直接删除 |
|
||||
| 删除容器及匿名卷 | `docker rm -v 容器名` | 同时清理匿名卷 |
|
||||
| 清理所有已停止容器 | `docker container prune` | 批量清理 |
|
||||
|
||||
### 延伸阅读
|
||||
|
||||
- [后台运行](5.2_daemon.md):理解 `-d` 参数和容器生命周期
|
||||
- [进入容器](5.4_attach_exec.md):操作运行中的容器
|
||||
- [网络配置](../09_network/README.md):理解端口映射的原理
|
||||
- [数据管理](../08_data/README.md):数据持久化方案
|
||||
- [删除镜像](../04_image/4.3_rm.md):清理镜像
|
||||
- [数据卷](../08_data/8.1_volume.md):数据卷管理
|
||||
---
|
||||
|
||||
> 📝 **发现错误或有改进建议?** 欢迎提交 [Issue](https://github.com/yeasy/docker_practice/issues) 或 [PR](https://github.com/yeasy/docker_practice/pulls)。
|
||||
136
06_repository/6.1_dockerhub.md
Normal file
@@ -0,0 +1,136 @@
|
||||
## 6.1 Docker Hub
|
||||
|
||||
### 6.1.1 什么是 Docker Hub
|
||||
|
||||
Docker Hub 是 Docker 的中央镜像仓库,通过它您可以轻松地分享和获取 Docker 镜像。
|
||||
|
||||
|
||||
[Docker Hub](https://hub.docker.com/) 是 Docker 官方维护的公共镜像仓库,也是全球最大的容器镜像库。
|
||||
|
||||
它提供了:
|
||||
|
||||
- **官方镜像**:由 Docker 官方和软件厂商 (如 Nginx,MySQL,Node.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
|
||||
## 默认情况下,不带其它参数进行 docker login 会自动走 Device Code Web Flow (浏览器认证)
|
||||
## 若在非交互 CI 环境中,推荐结合 --username 与 --password-stdin 参数使用
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
打标签并推送:
|
||||
|
||||
```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 账户** | 无限制 |
|
||||
|
||||
#### 滥用限流
|
||||
|
||||
除了上述针对特定账号拉取镜像数量的 Pull Rate Limit 之外,Docker Hub 对所有用户(包含已认证及付费用户)还实施了 **滥用保护限流 (Abuse Rate Limiting)**。它是根据网络出口 IP (IPv4 或 IPv6 /64 子网) 计算整体请求频率,阈值动态触发(通常为每分钟数千级别请求)。
|
||||
|
||||
**两类的差异与排查方法**:
|
||||
- **Pull Rate Limit**:针对拉取量达到上限。报错返回 `429 Too Many Requests`,并且 HTTP 返回体/CLI 错误提示中会带有明确的 `toomanyrequests: You have reached your pull rate limit` 提示,常附有账户升级链接。
|
||||
- **Abuse Rate Limit**:防范接口频率打击。报错仅返回简化的 `429 Too Many Requests`。这一限流不分付费与否,常发生在“多终端共享出口 IP”的企业局域网或者第三方云 CI 服务(如 GitHub Actions 等)中,即使你已正常配置 `docker login` 也依旧可能触发。
|
||||
|
||||
> **提示**:如果在 CI/CD 等环境遇到 429 错误,建议:
|
||||
> 1. 先甄别具体是哪类限流:普通的 pull rate limit 只要在 CI 中配置 `docker login` (并使用有效账号) 就能解除匿名限制。
|
||||
> 2. 如果是 Abuse 频控导致,应考虑搭建私有仓库作为拉取缓存代理 (Registry pull-through cache),避免频繁直接请求官方 Hub。
|
||||
> 3. 使用国内镜像加速器。
|
||||
|
||||
---
|
||||
|
||||
### 6.1.4 安全最佳实践
|
||||
|
||||
#### 1. 启用 2FA (双因素认证)
|
||||
|
||||
为了保护您的 Docker Hub 账号安全,我们建议采取以下措施。
|
||||
|
||||
|
||||
在 Account Settings -> Security 中启用 2FA,保护账号安全。启用后,CLI 登录需要使用 **Access Token** 而非密码。
|
||||
|
||||
#### 2. 使用 Access Token
|
||||
|
||||
> **⚠️ 警告**:绝不要在脚本或 CI/CD 系统中,直接使用 `-p` 参数传递密码或 Token (类似 `docker login -p xxx`)!这会导致凭证直接暴露在系统的命令历史、进程列表和终端输出中。
|
||||
|
||||
1. 在 Docker Hub -> Account Settings -> Security -> Access Tokens 创建 Token (PAT)。
|
||||
2. 将 Token 通过标准输入 (stdin) 安全传递给 Docker:
|
||||
|
||||
```bash
|
||||
$ echo "dckr_pat_xxxxxxx" | docker login --username username --password-stdin
|
||||
```
|
||||
|
||||
#### 3. 关注镜像漏洞
|
||||
|
||||
Docker Hub 会对官方镜像和付费用户的镜像进行安全扫描。在镜像标签页可以看到漏洞扫描结果。
|
||||
|
||||
---
|
||||
|
||||
### 6.1.5 Webhooks
|
||||
|
||||
当镜像被推送时,可以自动触发 HTTP 回调 (例如通知 CI 系统部署)。
|
||||
|
||||
**配置方法**:
|
||||
仓库页面 -> Webhooks -> Create Webhook。
|
||||
|
||||
---
|
||||
|
||||
### 6.1.6 自动构建
|
||||
|
||||
> ⚠️ 目前仅限付费用户 (Pro/Team) 使用。
|
||||
|
||||
链接 GitHub/Bitbucket 仓库后,当代码有提交或打标签时,Docker Hub 会自动运行构建。这保证了镜像总是与代码同步,且由可信的官方环境构建。
|
||||
|
||||
---
|
||||
@@ -1,10 +1,10 @@
|
||||
# 私有仓库高级配置
|
||||
## 6.3 私有仓库高级配置
|
||||
|
||||
上一节我们搭建了一个具有基础功能的私有仓库,本小节我们来使用 `Docker Compose` 搭建一个拥有权限认证、TLS 的私有仓库。
|
||||
|
||||
新建一个文件夹,以下步骤均在该文件夹中进行。
|
||||
|
||||
## 准备站点证书
|
||||
### 6.3.1 准备站点证书
|
||||
|
||||
如果你拥有一个域名,国内各大云服务商均提供免费的站点证书。你也可以使用 `openssl` 自行签发证书。
|
||||
|
||||
@@ -82,7 +82,7 @@ $ openssl x509 -req -days 750 -in "site.csr" -sha256 \
|
||||
|
||||
新建 `ssl` 文件夹并将 `docker.domain.com.key` `docker.domain.com.crt` `root-ca.crt` 这三个文件移入,删除其他文件。
|
||||
|
||||
## 配置私有仓库
|
||||
### 6.3.2 配置私有仓库
|
||||
|
||||
私有仓库默认的配置文件位于 `/etc/docker/registry/config.yml`,我们先在本地编辑 `config.yml`,之后挂载到容器中。
|
||||
|
||||
@@ -123,7 +123,7 @@ health:
|
||||
threshold: 3
|
||||
```
|
||||
|
||||
## 生成 http 认证文件
|
||||
### 6.3.3 生成 http 认证文件
|
||||
|
||||
```bash
|
||||
$ mkdir auth
|
||||
@@ -136,11 +136,11 @@ $ docker run --rm \
|
||||
|
||||
> 将上面的 `username` `password` 替换为你自己的用户名和密码。
|
||||
|
||||
## 编辑 `docker-compose.yml`
|
||||
### 6.3.4 编辑 Docker Compose 配置
|
||||
|
||||
编辑 `compose.yaml` (或 `docker-compose.yml`) 配置如下:
|
||||
|
||||
```yaml
|
||||
|
||||
|
||||
services:
|
||||
registry:
|
||||
image: registry
|
||||
@@ -154,7 +154,7 @@ volumes:
|
||||
registry-data:
|
||||
```
|
||||
|
||||
## 修改 hosts
|
||||
### 6.3.5 修改 Hosts 文件
|
||||
|
||||
编辑 `/etc/hosts`
|
||||
|
||||
@@ -162,7 +162,7 @@ volumes:
|
||||
127.0.0.1 docker.domain.com
|
||||
```
|
||||
|
||||
## 启动
|
||||
### 6.3.6 启动
|
||||
|
||||
```bash
|
||||
$ docker compose up -d
|
||||
@@ -170,7 +170,7 @@ $ docker compose up -d
|
||||
|
||||
这样我们就搭建好了一个具有权限认证、TLS 的私有仓库,接下来我们测试其功能是否正常。
|
||||
|
||||
## 测试私有仓库功能
|
||||
### 6.3.7 测试私有仓库功能
|
||||
|
||||
由于自行签发的 CA 根证书不被系统信任,所以我们需要将 CA 根证书 `ssl/root-ca.crt` 移入 `/etc/docker/certs.d/docker.domain.com` 文件夹中。
|
||||
|
||||
@@ -212,6 +212,6 @@ no basic auth credentials
|
||||
|
||||
发现会提示没有登录,不能将镜像推送到私有仓库中。
|
||||
|
||||
## 注意事项
|
||||
### 6.3.8 注意事项
|
||||
|
||||
如果你本机占用了 `443` 端口,你可以配置 [Nginx 代理](https://docs.docker.com/registry/recipes/nginx/),这里不再赘述。
|
||||
@@ -1,7 +1,14 @@
|
||||
# 访问仓库
|
||||
# 第六章 访问仓库
|
||||
|
||||
仓库(`Repository`)是集中存放镜像的地方。
|
||||
仓库 (`Repository`) 是集中存放镜像的地方。
|
||||
|
||||
一个容易混淆的概念是注册服务器(`Registry`)。实际上注册服务器是管理仓库的具体服务器,每个服务器上可以有多个仓库,而每个仓库下面有多个镜像。从这方面来说,仓库可以被认为是一个具体的项目或目录。例如对于仓库地址 `docker.io/ubuntu` 来说,`docker.io` 是注册服务器地址,`ubuntu` 是仓库名。
|
||||
一个容易混淆的概念是注册服务器 (`Registry`)。实际上注册服务器是管理仓库的具体服务器,每个服务器上可以有多个仓库,而每个仓库下面有多个镜像。从这方面来说,仓库可以被认为是一个具体的项目或目录。例如对于仓库地址 `docker.io/ubuntu` 来说,`docker.io` 是注册服务器地址,`ubuntu` 是仓库名。
|
||||
|
||||
大部分时候,并不需要严格区分这两者的概念。
|
||||
|
||||
## 本章内容
|
||||
|
||||
* [Docker Hub](6.1_dockerhub.md)
|
||||
* [私有仓库](6.2_registry.md)
|
||||
* [私有仓库高级配置](6.3_registry_auth.md)
|
||||
* [Nexus 3](6.4_nexus3_registry.md)
|
||||
|
||||
@@ -1,126 +0,0 @@
|
||||
# Docker Hub
|
||||
|
||||
## 什么是 Docker Hub
|
||||
|
||||
[Docker Hub](https://hub.docker.com/) 是 Docker 官方维护的公共镜像仓库,也是全球最大的容器镜像库。
|
||||
|
||||
它提供了:
|
||||
- **官方镜像**:由 Docker 官方和软件厂商(如 Nginx, MySQL, Node.js)维护的高质量镜像。
|
||||
- **个人/组织仓库**:用户可以上传自己的镜像。
|
||||
- **自动构建**:与 GitHub/Bitbucket 集成(需付费)。
|
||||
- **Webhooks**:镜像更新时触发回调。
|
||||
|
||||
---
|
||||
|
||||
## 核心功能
|
||||
|
||||
### 1. 搜索镜像
|
||||
|
||||
除了网页搜索,也可以使用命令行:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 限制与配额(重要)
|
||||
|
||||
### 镜像拉取限制 (Rate Limiting)
|
||||
|
||||
自 2020 年 11 月起,Docker Hub 对匿名和免费用户实施了拉取速率限制:
|
||||
|
||||
| 用户类型 | 限制 |
|
||||
|---------|------|
|
||||
| **匿名用户** (未登录) | 每 6 小时 100 次请求 |
|
||||
| **免费账户** (已登录) | 每 6 小时 200 次请求 |
|
||||
| **Pro/Team 账户** | 无限制 |
|
||||
|
||||
> **提示**:如果在 CI/CD 环境中遇到 `toomanyrequests` 错误,建议:
|
||||
> 1. 在 CI 中配置 `docker login`
|
||||
> 2. 使用国内镜像加速器
|
||||
> 3. 搭建私有仓库代理
|
||||
|
||||
---
|
||||
|
||||
## 安全最佳实践
|
||||
|
||||
### 1. 启用 2FA (双因素认证)
|
||||
|
||||
在 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 会对官方镜像和付费用户的镜像进行安全扫描。在镜像标签页可以看到漏洞扫描结果。
|
||||
|
||||
---
|
||||
|
||||
## Webhooks
|
||||
|
||||
当镜像被推送时,可以自动触发 HTTP 回调(例如通知 CI 系统部署)。
|
||||
|
||||
**配置方法**:
|
||||
仓库页面 -> Webhooks -> Create Webhook。
|
||||
|
||||
---
|
||||
|
||||
## 自动构建 (Automated Builds)
|
||||
|
||||
> ⚠️ 目前仅限付费用户 (Pro/Team) 使用。
|
||||
|
||||
链接 GitHub/Bitbucket 仓库后,当代码有提交或打标签时,Docker Hub 会自动运行构建。这保证了镜像总是与代码同步,且由可信的官方环境构建。
|
||||
|
||||
---
|
||||
|
||||
## 本章小结
|
||||
|
||||
| 功能 | 说明 |
|
||||
|------|------|
|
||||
| **官方镜像** | 优先使用的基础镜像 |
|
||||
| **拉取限制** | 匿名 100次/6h,登录 200次/6h |
|
||||
| **安全** | 推荐开启 2FA 并使用 Access Token |
|
||||
| **自动化** | 支持 Webhooks 和自动构建 |
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [私有仓库](registry.md):搭建自己的 Registry
|
||||
- [镜像加速器](../install/mirror.md):加速下载
|
||||
18
06_repository/summary.md
Normal file
@@ -0,0 +1,18 @@
|
||||
## 本章小结
|
||||
|
||||
本章介绍了 Docker Hub 的使用、私有仓库的搭建以及 Nexus 3 等企业级方案。
|
||||
|
||||
| 功能 | 说明 |
|
||||
|------|------|
|
||||
| **官方镜像** | 优先使用的基础镜像 |
|
||||
| **拉取限制** | 匿名 100 次/6h,登录 200 次/6h |
|
||||
| **安全** | 推荐开启 2FA 并使用 Access Token |
|
||||
| **自动化** | 支持 Webhooks 和自动构建 |
|
||||
|
||||
### 延伸阅读
|
||||
|
||||
- [私有仓库](6.2_registry.md):搭建自己的 Registry
|
||||
- [镜像加速器](../03_install/3.9_mirror.md):加速下载
|
||||
---
|
||||
|
||||
> 📝 **发现错误或有改进建议?** 欢迎提交 [Issue](https://github.com/yeasy/docker_practice/issues) 或 [PR](https://github.com/yeasy/docker_practice/pulls)。
|
||||
@@ -1,6 +0,0 @@
|
||||
# 数据与网络管理
|
||||
|
||||
本章将介绍 Docker 中的数据管理与网络配置。
|
||||
|
||||
* [数据管理](data/README.md)
|
||||
* [网络配置](network/README.md)
|
||||
@@ -1,8 +0,0 @@
|
||||
# 数据管理
|
||||
|
||||

|
||||
|
||||
这一章介绍如何在 Docker 内部以及容器之间管理数据,在容器中管理数据主要有两种方式:
|
||||
|
||||
* 数据卷(Volumes)
|
||||
* 挂载主机目录 (Bind mounts)
|
||||
@@ -1,284 +0,0 @@
|
||||
# 挂载主机目录(Bind Mounts)
|
||||
|
||||
## 什么是 Bind Mount
|
||||
|
||||
Bind Mount(绑定挂载)将**宿主机的目录或文件**直接挂载到容器中。容器可以读写宿主机的文件系统。
|
||||
|
||||
```
|
||||
宿主机 容器
|
||||
┌─────────────────────┐ ┌─────────────────────┐
|
||||
│ /home/user/code/ │ │ │
|
||||
│ ├── index.html │◄───────►│ /usr/share/nginx/ │
|
||||
│ ├── style.css │ Bind │ html/ │
|
||||
│ └── app.js │ Mount │ (同一份文件) │
|
||||
└─────────────────────┘ └─────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Bind Mount vs Volume
|
||||
|
||||
| 特性 | Bind Mount | Volume |
|
||||
|------|------------|--------|
|
||||
| **数据位置** | 宿主机任意路径 | Docker 管理的目录 |
|
||||
| **路径指定** | 必须是绝对路径 | 卷名 |
|
||||
| **可移植性** | 依赖宿主机路径 | 更好(Docker 管理) |
|
||||
| **性能** | 依赖宿主机文件系统 | 优化的存储驱动 |
|
||||
| **适用场景** | 开发环境、配置文件 | 生产数据持久化 |
|
||||
| **备份** | 直接访问文件 | 需要通过 Docker |
|
||||
|
||||
### 选择建议
|
||||
|
||||
```
|
||||
需求 推荐方案
|
||||
─────────────────────────────────────────
|
||||
开发时同步代码 → Bind Mount
|
||||
持久化数据库数据 → Volume
|
||||
共享配置文件 → Bind Mount
|
||||
容器间共享数据 → Volume
|
||||
备份方便 → Bind Mount(直接访问)
|
||||
生产环境 → Volume
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 基本语法
|
||||
|
||||
### 使用 --mount(推荐)
|
||||
|
||||
```bash
|
||||
$ docker run -d \
|
||||
--mount type=bind,source=/宿主机路径,target=/容器路径 \
|
||||
nginx
|
||||
```
|
||||
|
||||
### 使用 -v(简写)
|
||||
|
||||
```bash
|
||||
$ docker run -d \
|
||||
-v /宿主机路径:/容器路径 \
|
||||
nginx
|
||||
```
|
||||
|
||||
### 两种语法对比
|
||||
|
||||
| 特性 | --mount | -v |
|
||||
|------|---------|-----|
|
||||
| 语法 | 键值对,更清晰 | 冒号分隔,更简洁 |
|
||||
| 路径不存在时 | 报错 | 自动创建目录 |
|
||||
| 推荐程度 | ✅ 推荐 | 常用 |
|
||||
|
||||
---
|
||||
|
||||
## 使用场景
|
||||
|
||||
### 场景一:开发环境代码同步
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 只读挂载
|
||||
|
||||
防止容器修改宿主机文件:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 挂载单个文件
|
||||
|
||||
```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。建议重启容器或挂载目录。
|
||||
|
||||
---
|
||||
|
||||
## 查看挂载信息
|
||||
|
||||
```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` | 挂载传播模式 |
|
||||
|
||||
---
|
||||
|
||||
## 常见问题
|
||||
|
||||
### 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) ...
|
||||
```
|
||||
|
||||
### Q: macOS/Windows 性能问题
|
||||
|
||||
在 Docker Desktop 上,Bind Mount 性能较差(需要跨文件系统同步):
|
||||
|
||||
```bash
|
||||
# 使用 :cached 或 :delegated 提高性能(macOS)
|
||||
$ docker run -v /host/path:/container/path:cached myapp
|
||||
```
|
||||
|
||||
| 选项 | 说明 |
|
||||
|------|------|
|
||||
| `:cached` | 宿主机权威,容器读取可能延迟 |
|
||||
| `:delegated` | 容器权威,宿主机读取可能延迟 |
|
||||
| `:consistent` | 默认,完全一致(最慢) |
|
||||
|
||||
---
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 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 ...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 本章小结
|
||||
|
||||
| 要点 | 说明 |
|
||||
|------|------|
|
||||
| **作用** | 将宿主机目录挂载到容器 |
|
||||
| **语法** | `-v /宿主机:/容器` 或 `--mount type=bind,...` |
|
||||
| **只读** | 添加 `readonly` 或 `:ro` |
|
||||
| **适用场景** | 开发环境、配置文件、日志 |
|
||||
| **vs Volume** | Bind 更灵活,Volume 更适合生产 |
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [数据卷](volume.md):Docker 管理的持久化存储
|
||||
- [tmpfs 挂载](tmpfs.md):内存临时存储
|
||||
- [Compose 数据管理](../compose/compose_file.md):Compose 中的挂载配置
|
||||
@@ -1,291 +0,0 @@
|
||||
# 网络配置
|
||||
|
||||
## Docker 网络概述
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
### 核心组件
|
||||
|
||||
| 组件 | 说明 |
|
||||
|------|------|
|
||||
| **docker0** | 虚拟网桥,充当交换机角色 |
|
||||
| **veth pair** | 虚拟网卡对,一端在容器内,一端连接网桥 |
|
||||
| **容器 eth0** | 容器内的网卡 |
|
||||
| **IP 地址** | 自动从 172.17.0.0/16 网段分配 |
|
||||
|
||||
### 数据流向
|
||||
|
||||
```
|
||||
容器 A (172.17.0.2) → docker0 → 容器 B (172.17.0.3) (容器间通信)
|
||||
容器 A (172.17.0.2) → docker0 → eth0 → 互联网 (访问外网)
|
||||
外部请求 → eth0 → docker0 → 容器 A (被外部访问,需端口映射)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Docker 网络类型
|
||||
|
||||
查看默认网络:
|
||||
|
||||
```bash
|
||||
$ docker network ls
|
||||
NETWORK ID NAME DRIVER SCOPE
|
||||
abc123... bridge bridge local
|
||||
def456... host host local
|
||||
ghi789... none null local
|
||||
```
|
||||
|
||||
| 网络类型 | 说明 | 适用场景 |
|
||||
|---------|------|---------|
|
||||
| **bridge** | 默认类型,容器连接到虚拟网桥 | 大多数单机场景 |
|
||||
| **host** | 容器直接使用宿主机网络栈 | 需要最高网络性能时 |
|
||||
| **none** | 禁用网络 | 完全隔离的容器 |
|
||||
| **overlay** | 跨主机网络 | Docker Swarm 集群 |
|
||||
| **macvlan** | 容器拥有独立 MAC 地址 | 需要直接接入物理网络 |
|
||||
|
||||
---
|
||||
|
||||
## 用户自定义网络(推荐)
|
||||
|
||||
### 为什么要用自定义网络
|
||||
|
||||
默认 bridge 网络的局限:
|
||||
|
||||
| 问题 | 自定义网络的优势 |
|
||||
|------|-----------------|
|
||||
| 只能用 IP 通信 | 支持容器名 DNS 解析 |
|
||||
| 所有容器在同一网络 | 更好的隔离性 |
|
||||
| 需要 --link(已废弃) | 原生支持服务发现 |
|
||||
|
||||
### 创建自定义网络
|
||||
|
||||
```bash
|
||||
# 创建网络
|
||||
$ docker network create mynet
|
||||
|
||||
# 查看网络详情
|
||||
$ docker network inspect mynet
|
||||
```
|
||||
|
||||
### 使用自定义网络
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
### 容器名 DNS 解析
|
||||
|
||||
自定义网络自动提供 DNS 服务:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ mynet 网络 │
|
||||
│ │
|
||||
│ ┌─────────┐ DNS ┌─────────┐ │
|
||||
│ │ web │ ──── "db" → 172.18.0.3 ───► │ db │ │
|
||||
│ │172.18.0.2│ │172.18.0.3│ │
|
||||
│ └─────────┘ └─────────┘ │
|
||||
│ │
|
||||
│ web 容器可以用 "db" 作为主机名访问 db 容器 │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 容器互联
|
||||
|
||||
### 同一网络内的容器
|
||||
|
||||
同一自定义网络内的容器可以直接通信:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
### 连接到多个网络
|
||||
|
||||
一个容器可以连接到多个网络:
|
||||
|
||||
```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}}'
|
||||
```
|
||||
|
||||
### ⚠️ --link 已废弃
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 端口映射
|
||||
|
||||
容器默认只能在 Docker 网络内访问。要从外部访问容器,需要端口映射:
|
||||
|
||||
### 基本语法
|
||||
|
||||
```bash
|
||||
# -p 宿主机端口:容器端口
|
||||
$ docker run -d -p 8080:80 nginx
|
||||
```
|
||||
|
||||
### 映射方式
|
||||
|
||||
| 参数 | 说明 | 示例 |
|
||||
|------|------|------|
|
||||
| `-p 8080:80` | 指定端口映射 | 宿主机 8080 → 容器 80 |
|
||||
| `-p 80` | 随机宿主机端口 | 随机端口 → 容器 80 |
|
||||
| `-P` | 自动映射所有暴露端口 | 随机端口 → 所有 EXPOSE 端口 |
|
||||
| `-p 127.0.0.1:8080:80` | 只绑定本地 | 仅本机可访问 |
|
||||
| `-p 8080:80/udp` | UDP 端口 | UDP 协议 |
|
||||
|
||||
### 查看端口映射
|
||||
|
||||
```bash
|
||||
$ docker port mycontainer
|
||||
80/tcp -> 0.0.0.0:8080
|
||||
```
|
||||
|
||||
### 端口映射示意图
|
||||
|
||||
```
|
||||
外部请求 http://宿主机IP:8080
|
||||
│
|
||||
▼
|
||||
┌─────────────┐
|
||||
│ 宿主机:8080 │ ─── iptables NAT ───┐
|
||||
└─────────────┘ │
|
||||
▼
|
||||
┌───────────────┐
|
||||
│ 容器 nginx:80 │
|
||||
└───────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 网络隔离
|
||||
|
||||
不同网络之间默认隔离:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
这种隔离有助于安全:前端容器无法直接访问数据库网络。
|
||||
|
||||
---
|
||||
|
||||
## 常用命令
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 本章小结
|
||||
|
||||
| 概念 | 要点 |
|
||||
|------|------|
|
||||
| **默认网络** | docker0 网桥,172.17.0.0/16 网段 |
|
||||
| **自定义网络** | 推荐使用,支持容器名 DNS 解析 |
|
||||
| **端口映射** | `-p 宿主机端口:容器端口` 暴露服务 |
|
||||
| **网络隔离** | 不同网络默认隔离,增强安全性 |
|
||||
| **--link** | 已废弃,使用自定义网络替代 |
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [高级网络配置](linking.md):容器互联详解
|
||||
- [配置 DNS](dns.md):自定义 DNS 设置
|
||||
- [端口映射](port_bindbindbindport.md):高级端口配置
|
||||
- [Compose 网络](../compose/compose_file.md):Compose 中的网络配置
|
||||
@@ -1,114 +0,0 @@
|
||||
# 配置 DNS
|
||||
|
||||
## 容器的 DNS 机制
|
||||
|
||||
Docker 容器的 DNS 配置有两种情况:
|
||||
|
||||
1. **默认 Bridge 网络**:继承宿主机的 DNS 配置(`/etc/resolv.conf`)。
|
||||
2. **自定义网络**(推荐):使用 Docker 嵌入式 DNS 服务器 (Embedded DNS),支持通过**容器名**进行服务发现。
|
||||
|
||||
---
|
||||
|
||||
## 嵌入式 DNS (Embedded 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。
|
||||
|
||||
---
|
||||
|
||||
## 配置 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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 全局 DNS 配置
|
||||
|
||||
如果希望所有容器都使用特定的 DNS 服务器(而不是继承宿主机),可以修改 `/etc/docker/daemon.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": [
|
||||
"114.114.114.114",
|
||||
"8.8.8.8"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
修改后需要重启 Docker 服务:`systemctl restart docker`。
|
||||
|
||||
---
|
||||
|
||||
## 常见问题
|
||||
|
||||
### 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 ...`)。
|
||||
|
||||
---
|
||||
|
||||
## 本章小结
|
||||
|
||||
| 场景 | DNS 行为 | 备注 |
|
||||
|------|----------|------|
|
||||
| **默认网络** | 继承宿主机 | 不支持容器名解析 |
|
||||
| **自定义网络** | Docker 嵌入式 DNS | ✅ 支持容器名解析 |
|
||||
| **手动指定** | 使用 `--dns` | 覆盖默认配置 |
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [网络模式](README.md):Docker 网络概览
|
||||
- [端口映射](port_mapping.md):外部访问
|
||||
@@ -1,149 +0,0 @@
|
||||
# 外部访问容器
|
||||
|
||||
## 为什么要映射端口
|
||||
|
||||
容器运行在自己的隔离网络环境中(通常是 Bridge 模式)。这意味着:
|
||||
- **容器之间**:可以通过 IP 或容器名(自定义网络)互通。
|
||||
- **宿主机访问容器**:可以通过容器 IP 访问。
|
||||
- **外部网络访问容器**:❌ 默认无法直接访问。
|
||||
|
||||
为了让外部(如你的浏览器、其他局域网机器)访问容器内的服务,我们需要将容器的端口**映射**到宿主机的端口。
|
||||
|
||||
```
|
||||
外部用户 (Browser)
|
||||
│
|
||||
▼
|
||||
宿主机 (localhost:8080)
|
||||
│
|
||||
┌────┴────┐ 端口映射
|
||||
│ Docker │ (8080 -> 80)
|
||||
│ Proxy │
|
||||
└────┬────┘
|
||||
│
|
||||
▼
|
||||
容器 (Class B: 80)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 端口映射方式
|
||||
|
||||
### 1. 指定映射 (-p)
|
||||
|
||||
使用 `-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)
|
||||
|
||||
使用 `-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 端口。
|
||||
|
||||
---
|
||||
|
||||
## 查看端口映射
|
||||
|
||||
### docker port
|
||||
|
||||
```bash
|
||||
$ docker port mycontainer
|
||||
80/tcp -> 0.0.0.0:8080
|
||||
80/tcp -> [::]:8080
|
||||
```
|
||||
|
||||
### docker ps
|
||||
|
||||
```bash
|
||||
$ docker ps
|
||||
CONTAINER ID IMAGE PORTS NAMES
|
||||
abc123456 nginx 0.0.0.0:8080->80/tcp web
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 最佳实践与安全
|
||||
|
||||
### 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 服务(如 DNS, Syslog):
|
||||
|
||||
```bash
|
||||
$ docker run -d -p 53:53/udp dns-server
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 实现原理
|
||||
|
||||
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 网络模式)。
|
||||
|
||||
---
|
||||
|
||||
## 本章小结
|
||||
|
||||
| 要点 | 说明 |
|
||||
|------|------|
|
||||
| **-p** | 指定端口映射(常用),如 `8080:80` |
|
||||
| **-P** | 随机映射所有 EXPOSE 的端口 |
|
||||
| **安全性** | 默认监听所有 IP,敏感服务应绑定 `127.0.0.1` |
|
||||
| **查看** | 使用 `docker port` 或 `docker ps` |
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [EXPOSE 指令](../04_image/dockerfile/expose.md):在 Dockerfile 中声明端口
|
||||
- [网络模式](README.md):Host 模式不需要端口映射
|
||||
@@ -1,6 +1,6 @@
|
||||
# WORKDIR 指定工作目录
|
||||
## 7.10 WORKDIR 指定工作目录
|
||||
|
||||
## 基本语法
|
||||
### 7.10.1 基本语法
|
||||
|
||||
```docker
|
||||
WORKDIR <工作目录路径>
|
||||
@@ -10,7 +10,7 @@ WORKDIR <工作目录路径>
|
||||
|
||||
---
|
||||
|
||||
## 基本用法
|
||||
### 7.10.2 基本用法
|
||||
|
||||
```docker
|
||||
WORKDIR /app
|
||||
@@ -22,19 +22,20 @@ COPY . . # 复制到 /app/
|
||||
|
||||
---
|
||||
|
||||
## 为什么需要 WORKDIR
|
||||
### 7.10.3 为什么需要 WORKDIR
|
||||
|
||||
### 常见错误
|
||||
#### 常见错误
|
||||
|
||||
```docker
|
||||
# ❌ 错误:cd 在下一个 RUN 中无效
|
||||
## ❌ 错误:cd 在下一个 RUN 中无效
|
||||
|
||||
RUN cd /app
|
||||
RUN echo "hello" > world.txt # 文件在根目录!
|
||||
```
|
||||
|
||||
### 原因分析
|
||||
#### 原因分析
|
||||
|
||||
```
|
||||
```dockerfile
|
||||
RUN cd /app
|
||||
↓
|
||||
启动容器 → cd /app(仅内存变化)→ 提交镜像层 → 容器销毁
|
||||
@@ -45,19 +46,20 @@ RUN echo "hello" > world.txt
|
||||
启动新容器(工作目录在 /)→ 创建 /world.txt
|
||||
```
|
||||
|
||||
每个 RUN 都在新容器中执行,**前一个 RUN 的内存状态(包括工作目录)不会保留**。
|
||||
每个 RUN 都在新容器中执行,**前一个 RUN 的内存状态 (包括工作目录) 不会保留**。
|
||||
|
||||
### 正确做法
|
||||
#### 正确做法
|
||||
|
||||
```docker
|
||||
# ✅ 正确:使用 WORKDIR
|
||||
## ✅ 正确:使用 WORKDIR
|
||||
|
||||
WORKDIR /app
|
||||
RUN echo "hello" > world.txt # 创建 /app/world.txt
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 相对路径
|
||||
### 7.10.4 相对路径
|
||||
|
||||
WORKDIR 支持相对路径,基于上一个 WORKDIR:
|
||||
|
||||
@@ -71,7 +73,7 @@ RUN pwd # 输出 /a/b/c
|
||||
|
||||
---
|
||||
|
||||
## 使用环境变量
|
||||
### 7.10.5 使用环境变量
|
||||
|
||||
```docker
|
||||
ENV APP_HOME=/app
|
||||
@@ -82,10 +84,11 @@ RUN pwd # 输出 /app
|
||||
|
||||
---
|
||||
|
||||
## 多阶段构建中的 WORKDIR
|
||||
### 7.10.6 多阶段构建中的 WORKDIR
|
||||
|
||||
```docker
|
||||
# 构建阶段
|
||||
## 构建阶段
|
||||
|
||||
FROM node:20 AS builder
|
||||
WORKDIR /build
|
||||
COPY package*.json ./
|
||||
@@ -93,7 +96,8 @@ RUN npm install
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
# 生产阶段
|
||||
## 生产阶段
|
||||
|
||||
FROM nginx:alpine
|
||||
WORKDIR /usr/share/nginx/html
|
||||
COPY --from=builder /build/dist .
|
||||
@@ -101,9 +105,9 @@ COPY --from=builder /build/dist .
|
||||
|
||||
---
|
||||
|
||||
## 最佳实践
|
||||
### 7.10.7 最佳实践
|
||||
|
||||
### 1. 尽早设置 WORKDIR
|
||||
#### 1. 尽早设置 WORKDIR
|
||||
|
||||
```docker
|
||||
FROM node:20
|
||||
@@ -115,40 +119,46 @@ COPY . .
|
||||
CMD ["node", "server.js"]
|
||||
```
|
||||
|
||||
### 2. 使用绝对路径
|
||||
#### 2. 使用绝对路径
|
||||
|
||||
```docker
|
||||
# ✅ 推荐:绝对路径,意图明确
|
||||
## ✅ 推荐:绝对路径,意图明确
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# ⚠️ 避免:相对路径可能造成混淆
|
||||
## ⚠️ 避免:相对路径可能造成混淆
|
||||
|
||||
WORKDIR app
|
||||
```
|
||||
|
||||
### 3. 不要用 RUN cd
|
||||
#### 3. 不要用 RUN cd
|
||||
|
||||
```docker
|
||||
# ❌ 避免
|
||||
## ❌ 避免
|
||||
|
||||
RUN cd /app && echo "hello" > world.txt
|
||||
|
||||
# ✅ 推荐
|
||||
## ✅ 推荐
|
||||
|
||||
WORKDIR /app
|
||||
RUN echo "hello" > world.txt
|
||||
```
|
||||
|
||||
### 4. 适时重置 WORKDIR
|
||||
#### 4. 适时重置 WORKDIR
|
||||
|
||||
```docker
|
||||
WORKDIR /app
|
||||
# ... 应用相关操作 ...
|
||||
## ... 应用相关操作 ...
|
||||
|
||||
WORKDIR /data
|
||||
# ... 数据相关操作 ...
|
||||
## ... 数据相关操作 ...
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 与其他指令的关系
|
||||
### 7.10.8 与其他指令的关系
|
||||
|
||||
| 指令 | WORKDIR 的影响 |
|
||||
|------|---------------|
|
||||
@@ -168,7 +178,7 @@ CMD ["./start.sh"] # /app/start.sh
|
||||
|
||||
---
|
||||
|
||||
## 运行时覆盖
|
||||
### 7.10.9 运行时覆盖
|
||||
|
||||
使用 `-w` 参数覆盖工作目录:
|
||||
|
||||
@@ -178,19 +188,3 @@ $ docker run -w /tmp myimage pwd
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 本章小结
|
||||
|
||||
| 要点 | 说明 |
|
||||
|------|------|
|
||||
| **作用** | 设置后续指令的工作目录 |
|
||||
| **语法** | `WORKDIR /path` |
|
||||
| **自动创建** | 目录不存在会自动创建 |
|
||||
| **持久性** | 影响后续所有指令,直到下次 WORKDIR |
|
||||
| **不要用** | `RUN cd /path`(无效) |
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [COPY 复制文件](copy.md):文件复制
|
||||
- [RUN 执行命令](../../04_image/build.md):执行构建命令
|
||||
- [最佳实践](../../15_appendix/best_practices.md):Dockerfile 编写指南
|
||||
279
07_dockerfile/7.11_user.md
Normal file
@@ -0,0 +1,279 @@
|
||||
## 7.11 USER 指定当前用户
|
||||
|
||||
### 7.11.1 基本语法
|
||||
|
||||
```docker
|
||||
USER <用户名>[:<用户组>]
|
||||
USER <UID>[:<GID>]
|
||||
```
|
||||
|
||||
`USER` 指令切换后续指令 (RUN、CMD、ENTRYPOINT) 的执行用户。
|
||||
|
||||
---
|
||||
|
||||
### 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 用户无法绑定 80、443 等端口。
|
||||
|
||||
**解决**:
|
||||
|
||||
1. 使用高端口 (如 8080)
|
||||
2. 在运行时映射端口:`docker run -p 80:8080`
|
||||
|
||||
---
|
||||
@@ -1,6 +1,6 @@
|
||||
# HEALTHCHECK 健康检查
|
||||
## 7.12 HEALTHCHECK 健康检查
|
||||
|
||||
## 基本语法
|
||||
### 7.12.1 基本语法
|
||||
|
||||
```docker
|
||||
HEALTHCHECK [选项] CMD <命令>
|
||||
@@ -11,19 +11,18 @@ HEALTHCHECK NONE
|
||||
|
||||
---
|
||||
|
||||
## 为什么需要 HEALTHCHECK
|
||||
### 7.12.2 为什么需要 HEALTHCHECK
|
||||
|
||||
在没有 HEALTHCHECK 之前,Docker 只能通过**进程退出码**来判断容器状态。
|
||||
在没有 HEALTHCHECK 之前,Docker 只能通过 **进程退出码** 来判断容器状态。**问题场景**:
|
||||
|
||||
**问题场景**:
|
||||
- Web 服务死锁,无法响应请求,但进程仍在运行
|
||||
- 数据库正在启动中,尚未准备好接受连接
|
||||
- 应用陷入死循环,CPU 爆满但进程存活
|
||||
|
||||
**引入 HEALTHCHECK 后**:
|
||||
Docker 定期执行指定的检查命令,根据返回值判断容器是否"健康"。
|
||||
Docker 定期执行指定的检查命令,根据返回值判断容器是否 “健康”。
|
||||
|
||||
```
|
||||
```bash
|
||||
容器状态转换:
|
||||
Starting ──成功──> Healthy ──失败N次──> Unhealthy
|
||||
▲ │
|
||||
@@ -32,9 +31,9 @@ Starting ──成功──> Healthy ──失败N次──> Unhealthy
|
||||
|
||||
---
|
||||
|
||||
## 基本用法
|
||||
### 7.12.3 基本用法
|
||||
|
||||
### Web 服务检查
|
||||
#### Web 服务检查
|
||||
|
||||
```docker
|
||||
FROM nginx
|
||||
@@ -44,24 +43,24 @@ HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
|
||||
CMD curl -fs http://localhost/ || exit 1
|
||||
```
|
||||
|
||||
### 命令返回值
|
||||
#### 命令返回值
|
||||
|
||||
- `0`: 成功 (healthy)
|
||||
- `1`: 失败 (unhealthy)
|
||||
- `2`: 保留值 (不使用)
|
||||
- `0`:成功 (healthy)
|
||||
- `1`:失败 (unhealthy)
|
||||
- `2`:保留值 (不使用)
|
||||
|
||||
### 常用选项
|
||||
#### 常用选项
|
||||
|
||||
| 选项 | 说明 | 默认值 |
|
||||
|------|------|--------|
|
||||
| `--interval` | 两次检查的间隔 | 30s |
|
||||
| `--timeout` | 检查命令的超时时间 | 30s |
|
||||
| `--start-period` | 启动缓冲期(期间失败不计入次数) | 0s |
|
||||
| `--start-period` | 启动缓冲期 (期间失败不计入次数)| 0s |
|
||||
| `--retries` | 连续失败多少次标记为 unhealthy | 3 |
|
||||
|
||||
---
|
||||
|
||||
## 屏蔽健康检查
|
||||
### 7.12.4 屏蔽健康检查
|
||||
|
||||
如果基础镜像定义了 HEALTHCHECK,但你不想使用它:
|
||||
|
||||
@@ -72,31 +71,35 @@ HEALTHCHECK NONE
|
||||
|
||||
---
|
||||
|
||||
## 常见检查脚本
|
||||
### 7.12.5 常见检查脚本
|
||||
|
||||
### HTTP 服务
|
||||
#### HTTP 服务
|
||||
|
||||
使用 `curl` 或 `wget`:
|
||||
|
||||
```docker
|
||||
# 使用 curl
|
||||
## 使用 curl
|
||||
|
||||
HEALTHCHECK CMD curl -f http://localhost/ || exit 1
|
||||
|
||||
# 使用 wget (Alpine 默认包含)
|
||||
## 使用 wget(Alpine 默认包含)
|
||||
|
||||
HEALTHCHECK CMD wget -q --spider http://localhost/ || exit 1
|
||||
```
|
||||
|
||||
### 数据库
|
||||
#### 数据库
|
||||
|
||||
```docker
|
||||
# MySQL
|
||||
## MySQL
|
||||
|
||||
HEALTHCHECK CMD mysqladmin ping -h localhost || exit 1
|
||||
|
||||
# Redis
|
||||
## Redis
|
||||
|
||||
HEALTHCHECK CMD redis-cli ping || exit 1
|
||||
```
|
||||
|
||||
### 自定义脚本
|
||||
#### 自定义脚本
|
||||
|
||||
```docker
|
||||
COPY healthcheck.sh /usr/local/bin/
|
||||
@@ -105,9 +108,9 @@ HEALTHCHECK CMD ["healthcheck.sh"]
|
||||
|
||||
---
|
||||
|
||||
## 在 Compose 中使用
|
||||
### 7.12.6 在 Compose 中使用
|
||||
|
||||
可以在 `docker-compose.yml` 中覆盖或定义健康检查:
|
||||
可以在 `compose.yaml` (或 `docker-compose.yml`) 中覆盖或定义健康检查:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
@@ -137,16 +140,18 @@ services:
|
||||
|
||||
---
|
||||
|
||||
## 查看健康状态
|
||||
### 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",
|
||||
@@ -164,43 +169,28 @@ $ docker inspect --format '{{json .State.Health}}' mycontainer | jq
|
||||
|
||||
---
|
||||
|
||||
## 最佳实践
|
||||
### 7.12.8 最佳实践
|
||||
|
||||
### 1. 避免副作用
|
||||
#### 1. 避免副作用
|
||||
|
||||
健康检查会被频繁执行,不要在检查脚本中进行写操作或消耗大量资源的操作。
|
||||
|
||||
### 2. 使用轻量级工具
|
||||
#### 2. 使用轻量级工具
|
||||
|
||||
优先使用镜像中已有的工具(如 `wget`),避免为了健康检查安装庞大的依赖(如 `curl`)。
|
||||
优先使用镜像中已有的工具 (如 `wget`),避免为了健康检查安装庞大的依赖 (如 `curl`)。
|
||||
|
||||
### 3. 设置合理的 Start Period
|
||||
#### 3. 设置合理的 Start Period
|
||||
|
||||
应用启动可能需要时间(如 Java 应用)。设置 `--start-period` 可以防止在启动阶段因检查失败而误判。
|
||||
应用启动可能需要时间 (如 Java 应用)。设置 `--start-period` 可以防止在启动阶段因检查失败而误判。
|
||||
|
||||
```docker
|
||||
# 给应用 1 分钟启动时间
|
||||
## 给应用 1 分钟启动时间
|
||||
|
||||
HEALTHCHECK --start-period=60s CMD curl -f http://localhost/ || exit 1
|
||||
```
|
||||
|
||||
### 4. 只检查核心依赖
|
||||
#### 4. 只检查核心依赖
|
||||
|
||||
健康检查应主要关注**当前服务**是否可用,而不是检查其下游依赖(数据库等)。下游依赖的检查应由应用逻辑处理。
|
||||
健康检查应主要关注 **当前服务** 是否可用,而不是检查其下游依赖 (数据库等)。下游依赖的检查应由应用逻辑处理。
|
||||
|
||||
---
|
||||
|
||||
## 本章小结
|
||||
|
||||
| 要点 | 说明 |
|
||||
|------|------|
|
||||
| **作用** | 检测容器应用是否真实可用 |
|
||||
| **命令** | `HEALTHCHECK [选项] CMD command` |
|
||||
| **状态** | starting, healthy, unhealthy |
|
||||
| **Compose** | 支持 `condition: service_healthy` 依赖 |
|
||||
| **注意** | 避免副作用,节省资源 |
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [CMD 容器启动命令](cmd.md):启动主进程
|
||||
- [Compose 模板文件](../../compose/compose_file.md):Compose 中的健康检查
|
||||
- [Docker 调试](../../15_appendix/debug.md):容器排障
|
||||
145
07_dockerfile/7.13_onbuild.md
Normal file
@@ -0,0 +1,145 @@
|
||||
## 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` 指令产生了临时文件,最好在同一个指令链中清理,或者提供机制让子镜像清理。
|
||||
|
||||
---
|
||||
@@ -1,6 +1,6 @@
|
||||
# LABEL 为镜像添加元数据
|
||||
## 7.14 LABEL 为镜像添加元数据
|
||||
|
||||
## 基本语法
|
||||
### 7.14.1 基本语法
|
||||
|
||||
```docker
|
||||
LABEL <key>=<value> <key>=<value> ...
|
||||
@@ -10,25 +10,25 @@ LABEL <key>=<value> <key>=<value> ...
|
||||
|
||||
---
|
||||
|
||||
## 为什么需要 LABEL
|
||||
### 7.14.2 为什么需要 LABEL
|
||||
|
||||
1. **版本管理**:记录版本号、构建时间、Git Commit ID
|
||||
2. **联系信息**:维护者邮箱、文档地址、支持渠道
|
||||
3. **自动化工具**: CI/CD 工具可以读取标签触发操作
|
||||
3. **自动化工具**:CI/CD 工具可以读取标签触发操作
|
||||
4. **许可证信息**:声明开源协议
|
||||
|
||||
---
|
||||
|
||||
## 基本用法
|
||||
### 7.14.3 基本用法
|
||||
|
||||
### 定义单个标签
|
||||
#### 定义单个标签
|
||||
|
||||
```docker
|
||||
LABEL version="1.0"
|
||||
LABEL description="这是一个 Web 应用服务器"
|
||||
```
|
||||
|
||||
### 定义多个标签(推荐)
|
||||
#### 定义多个标签 (推荐)
|
||||
|
||||
```docker
|
||||
LABEL maintainer="user@example.com" \
|
||||
@@ -41,7 +41,7 @@ LABEL maintainer="user@example.com" \
|
||||
|
||||
---
|
||||
|
||||
## 常用标签规范 (OCI Annotations)
|
||||
### 7.14.4 常用标签规范
|
||||
|
||||
为了标准和互操作性,推荐使用 [OCI Image Format Specification](https://github.com/opencontainers/image-spec/blob/main/annotations.md#pre-defined-annotation-keys) 定义的标准标签:
|
||||
|
||||
@@ -57,7 +57,7 @@ LABEL maintainer="user@example.com" \
|
||||
| `org.opencontainers.image.title` | 镜像标题 | `My App` |
|
||||
| `org.opencontainers.image.description` | 描述 | `Production ready web server` |
|
||||
|
||||
### 示例
|
||||
#### 示例
|
||||
|
||||
```docker
|
||||
LABEL org.opencontainers.image.authors="yeasy" \
|
||||
@@ -68,29 +68,32 @@ LABEL org.opencontainers.image.authors="yeasy" \
|
||||
|
||||
---
|
||||
|
||||
## MAINTAINER 指令(已废弃)
|
||||
### 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` 使用,可以在构建时动态注入标签:
|
||||
配合 `ARG` 使用,可以在构建时动态注入标签:
|
||||
|
||||
```docker
|
||||
ARG BUILD_DATE
|
||||
@@ -111,9 +114,9 @@ $ docker build \
|
||||
|
||||
---
|
||||
|
||||
## 查看标签
|
||||
### 7.14.7 查看标签
|
||||
|
||||
### docker inspect
|
||||
#### docker inspect
|
||||
|
||||
查看镜像的标签信息:
|
||||
|
||||
@@ -124,31 +127,18 @@ $ docker inspect nginx --format '{{json .Config.Labels}}' | jq
|
||||
}
|
||||
```
|
||||
|
||||
### 过滤器
|
||||
#### 过滤器
|
||||
|
||||
可以使用标签过滤镜像:
|
||||
|
||||
```bash
|
||||
# 列出作者是 yeasy 的所有镜像
|
||||
## 列出作者是 yeasy 的所有镜像
|
||||
|
||||
$ docker images --filter "label=org.opencontainers.image.authors=yeasy"
|
||||
|
||||
# 删除所有带有特定标签的镜像
|
||||
## 删除所有带有特定标签的镜像
|
||||
|
||||
$ docker rmi $(docker images -q --filter "label=stage=builder")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 本章小结
|
||||
|
||||
| 要点 | 说明 |
|
||||
|------|------|
|
||||
| **作用** | 添加 key-value 元数据 |
|
||||
| **语法** | `LABEL k=v k=v ...` |
|
||||
| **规范** | 推荐使用 OCI 标准标签 |
|
||||
| **弃用** | 不要再使用 `MAINTAINER` |
|
||||
| **查看** | `docker inspect` |
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [OCI 标签规范](https://github.com/opencontainers/image-spec/blob/main/annotations.md)
|
||||
- [Dockerfile 最佳实践](../../15_appendix/best_practices.md)
|
||||
@@ -1,96 +1,108 @@
|
||||
# SHELL 指令
|
||||
## 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 格式时)。
|
||||
该指令会影响后续的 `RUN`,`CMD`,`ENTRYPOINT` 指令 (当它们使用 shell 格式时)。
|
||||
|
||||
---
|
||||
|
||||
## 为什么要用 SHELL 指令
|
||||
### 7.15.2 为什么要用 SHELL 指令
|
||||
|
||||
### 1. 使用 bash 特性
|
||||
#### 1. 使用 bash 特性
|
||||
|
||||
默认的 `/bin/sh`(通常是 dash 或 alpine 的 ash)功能有限。如果你需要使用 bash 的特有功能(如数组、`{}` 扩展、`pipefail` 等),可以切换 shell。
|
||||
默认的 `/bin/sh` (通常是 dash 或 alpine 的 ash) 功能有限。如果你需要使用 bash 的特有功能 (如数组、`{}` 扩展、`pipefail` 等),可以切换 shell。
|
||||
|
||||
```docker
|
||||
FROM ubuntu:24.04
|
||||
|
||||
# 切换到 bash
|
||||
## 切换到 bash
|
||||
|
||||
SHELL ["/bin/bash", "-c"]
|
||||
|
||||
# 现在可以使用 bash 特性了
|
||||
## 现在可以使用 bash 特性了
|
||||
|
||||
RUN echo {a..z}
|
||||
```
|
||||
|
||||
### 2. 增强错误处理 (pipefail)
|
||||
#### 2. 增强错误处理
|
||||
|
||||
默认情况下,管道命令 `cmd1 | cmd2` 只要 `cmd2` 成功,整个指令就视为成功。这可能掩盖构建错误。
|
||||
|
||||
```docker
|
||||
# ❌ 这里的 wget 失败了,但构建继续(因为 tar 成功了)
|
||||
## ❌ 这里的 wget 失败了,但构建继续(因为 tar 成功了)
|
||||
|
||||
RUN wget -O - https://invalid-url | tar xz
|
||||
```
|
||||
|
||||
使用 `SHELL` 启用 `pipefail`:
|
||||
|
||||
```docker
|
||||
# ✅ 启用 pipefail
|
||||
## ✅ 启用 pipefail
|
||||
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
|
||||
# 如果 wget 失败,整个 RUN 就会失败
|
||||
## 如果 wget 失败,整个 RUN 就会失败
|
||||
|
||||
RUN wget -O - https://invalid-url | tar xz
|
||||
```
|
||||
|
||||
### 3. Windows 环境
|
||||
#### 3. Windows 环境
|
||||
|
||||
在 Windows 容器中,经常需要在 `cmd` 和 `powershell` 之间切换。
|
||||
|
||||
```docker
|
||||
FROM mcr.microsoft.com/windows/servercore:ltsc2022
|
||||
|
||||
# 默认是 cmd
|
||||
## 默认是 cmd
|
||||
|
||||
RUN echo Default shell is cmd
|
||||
|
||||
# 切换到 powershell
|
||||
## 切换到 powershell
|
||||
|
||||
SHELL ["powershell", "-command"]
|
||||
RUN Write-Host "Hello from PowerShell"
|
||||
|
||||
# 切回 cmd
|
||||
## 切回 cmd
|
||||
|
||||
SHELL ["cmd", "/S", "/C"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 作用范围
|
||||
### 7.15.3 作用范围
|
||||
|
||||
`SHELL` 指令可以出现多次,每次只影响其后的指令:
|
||||
|
||||
```docker
|
||||
FROM ubuntu:24.04
|
||||
|
||||
# 使用默认 sh
|
||||
## 使用默认 sh
|
||||
|
||||
RUN echo "Using sh"
|
||||
|
||||
SHELL ["/bin/bash", "-c"]
|
||||
# 使用 bash
|
||||
## 使用 bash
|
||||
|
||||
RUN echo "Using bash"
|
||||
|
||||
SHELL ["/bin/sh", "-c"]
|
||||
# 回到 sh
|
||||
## 回到 sh
|
||||
|
||||
RUN echo "Using sh again"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 对其他指令的影响
|
||||
### 7.15.4 对其他指令的影响
|
||||
|
||||
`SHELL` 影响的是所有使用 **shell 格式** 的指令:
|
||||
|
||||
@@ -105,9 +117,9 @@ RUN echo "Using sh again"
|
||||
|
||||
---
|
||||
|
||||
## 最佳实践
|
||||
### 7.15.5 最佳实践
|
||||
|
||||
### 1. 推荐开启 pipefail
|
||||
#### 1. 推荐开启 pipefail
|
||||
|
||||
对于使用 bash 的镜像,强烈建议开启 `pipefail`,以确保构建过程中的错误能被及时捕获。
|
||||
|
||||
@@ -115,27 +127,12 @@ RUN echo "Using sh again"
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
```
|
||||
|
||||
### 2. 明确意图
|
||||
#### 2. 明确意图
|
||||
|
||||
如果由于脚本需求必须更改 shell,最好在 Dockerfile 中显式声明,而不是依赖默认行为。
|
||||
|
||||
### 3. 尽量保持一致
|
||||
#### 3. 尽量保持一致
|
||||
|
||||
避免在 Dockerfile 中频繁切换 SHELL,这会使构建过程难以理解和调试。尽量在头部定义一次即可。
|
||||
|
||||
---
|
||||
|
||||
## 本章小结
|
||||
|
||||
| 要点 | 说明 |
|
||||
|------|------|
|
||||
| **作用** | 更改 RUN/CMD/ENTRYPOINT 的默认 shell |
|
||||
| **Linux 默认** | `["/bin/sh", "-c"]` |
|
||||
| **Windows 默认** | `["cmd", "/S", "/C"]` |
|
||||
| **推荐用法** | `SHELL ["/bin/bash", "-o", "pipefail", "-c"]` |
|
||||
| **影响范围** | 后续所有使用 shell 格式的指令 |
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [RUN 指令](../../04_image/build.md):执行命令
|
||||
- [Dockerfile 最佳实践](../../15_appendix/best_practices.md):错误处理与调试
|
||||
36
07_dockerfile/7.16_references.md
Normal file
@@ -0,0 +1,36 @@
|
||||
## 7.16 参考文档
|
||||
|
||||
### 官方文档
|
||||
|
||||
* `Dockerfile` 官方参考手册:https://docs.docker.com/engine/reference/builder/
|
||||
|
||||
* `Dockerfile` 最佳实践指南:https://docs.docker.com/develop/develop-images/dockerfile_best-practices/
|
||||
|
||||
* `Docker` 官方镜像 `Dockerfile` 库:https://github.com/docker-library/docs
|
||||
|
||||
### 常用指令总结
|
||||
|
||||
Dockerfile 中的常用指令包括:
|
||||
|
||||
- **FROM**: 指定基础镜像,必须是第一条指令
|
||||
- **RUN**: 在镜像中执行命令,用于安装软件包等
|
||||
- **WORKDIR**: 设置工作目录
|
||||
- **COPY/ADD**: 复制文件到镜像中
|
||||
- **EXPOSE**: 声明容器监听的端口
|
||||
- **ENV**: 设置环境变量
|
||||
- **ENTRYPOINT**: 容器启动时的入口点
|
||||
- **CMD**: 容器默认执行的命令
|
||||
|
||||
### 最佳实践建议
|
||||
|
||||
1. 使用具体的基础镜像版本标签而非 latest
|
||||
2. 最小化镜像层数,合并 RUN 指令
|
||||
3. 使用 .dockerignore 文件排除不必要的文件
|
||||
4. 安装必要的软件包后清理缓存
|
||||
5. 使用多阶段构建减小最终镜像体积
|
||||
6. 避免以 root 身份运行容器应用
|
||||
|
||||
### 相关资源
|
||||
|
||||
- Docker 官方镜像库:https://hub.docker.com/
|
||||
- Docker 镜像构建最佳实践:https://docs.docker.com/build/building/best-practices/
|
||||
@@ -1,12 +1,10 @@
|
||||
# 多阶段构建
|
||||
|
||||
## 之前的做法
|
||||
## 7.17 多阶段构建
|
||||
|
||||
在 Docker 17.05 版本之前,我们构建 Docker 镜像时,通常会采用两种方式:
|
||||
|
||||
### 全部放入一个 Dockerfile
|
||||
### 7.17.1 全部放入一个 Dockerfile
|
||||
|
||||
一种方式是将所有的构建过程编包含在一个 `Dockerfile` 中,包括项目及其依赖库的编译、测试、打包等流程,这里可能会带来的一些问题:
|
||||
一种方式是将所有的构建过程包含在一个 `Dockerfile` 中,包括项目及其依赖库的编译、测试、打包等流程,这里可能会带来的一些问题:
|
||||
|
||||
* 镜像层次多,镜像体积较大,部署时间变长
|
||||
|
||||
@@ -51,7 +49,7 @@ CMD ["./app"]
|
||||
$ docker build -t go/helloworld:1 -f Dockerfile.one .
|
||||
```
|
||||
|
||||
### 分散到多个 Dockerfile
|
||||
### 7.17.2 分散到多个 Dockerfile
|
||||
|
||||
另一种方式,就是我们事先在一个 `Dockerfile` 将项目及其依赖库编译测试打包好后,再将其拷贝到运行环境中,这种方式需要我们编写两个 `Dockerfile` 和一些编译脚本才能将其两个阶段自动整合起来,这种方式虽然可以很好地规避第一种方式存在的风险,但明显部署过程较复杂。
|
||||
|
||||
@@ -120,7 +118,7 @@ go/helloworld 2 f7cf3465432c 22 seconds ago 6.47MB
|
||||
go/helloworld 1 f55d3e16affc 2 minutes ago 295MB
|
||||
```
|
||||
|
||||
## 使用多阶段构建
|
||||
### 7.17.3 使用多阶段构建
|
||||
|
||||
为解决以上问题,Docker v17.05 开始支持多阶段构建 (`multistage builds`)。使用多阶段构建我们就可以很容易解决前面提到的问题,并且只需要编写一个 `Dockerfile`:
|
||||
|
||||
@@ -169,7 +167,7 @@ go/helloworld 1 f55d3e16affc 2 minutes ago 295MB
|
||||
|
||||
很明显使用多阶段构建的镜像体积小,同时也完美解决了上边提到的问题。
|
||||
|
||||
### 只构建某一阶段的镜像
|
||||
### 7.17.4 只构建某一阶段的镜像
|
||||
|
||||
我们可以使用 `as` 来为某一阶段命名,例如
|
||||
|
||||
@@ -183,7 +181,7 @@ FROM golang:alpine as builder
|
||||
$ docker build --target builder -t username/imagename:tag .
|
||||
```
|
||||
|
||||
### 构建时从其他镜像复制文件
|
||||
### 7.17.5 构建时从其他镜像复制文件
|
||||
|
||||
上面例子中我们使用 `COPY --from=0 /go/src/github.com/go/helloworld/app .` 从上一阶段的镜像中复制文件,我们也可以复制任意镜像中的文件。
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# 实战多阶段构建 Laravel 镜像
|
||||
## 7.18 实战多阶段构建 Laravel 镜像
|
||||
|
||||
> 本节适用于 PHP 开发者阅读。`Laravel` 基于 8.x 版本,各个版本的文件结构可能会有差异,请根据实际自行修改。
|
||||
|
||||
## 准备
|
||||
### 7.18.1 准备
|
||||
|
||||
新建一个 `Laravel` 项目或在已有的 `Laravel` 项目根目录下新建 `Dockerfile` `.dockerignore` `laravel.conf` 文件。
|
||||
|
||||
@@ -25,7 +25,9 @@ yarn-error.log
|
||||
bootstrap/cache/*
|
||||
storage/
|
||||
|
||||
# 自行添加其他需要排除的文件,例如 .env.* 文件
|
||||
## 自行添加其他需要排除的文件,例如 .env.* 文件
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
在 `laravel.conf` 文件中写入 nginx 配置。
|
||||
@@ -45,13 +47,16 @@ server {
|
||||
include fastcgi.conf;
|
||||
|
||||
# fastcgi_connect_timeout 300;
|
||||
|
||||
# fastcgi_send_timeout 300;
|
||||
|
||||
# fastcgi_read_timeout 300;
|
||||
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 前端构建
|
||||
### 7.18.2 前端构建
|
||||
|
||||
第一阶段进行前端构建。
|
||||
|
||||
@@ -72,7 +77,7 @@ RUN set -x ; cd /app \
|
||||
&& npm run production
|
||||
```
|
||||
|
||||
## 安装 Composer 依赖
|
||||
### 7.18.3 安装 Composer 依赖
|
||||
|
||||
第二阶段安装 Composer 依赖。
|
||||
|
||||
@@ -92,7 +97,7 @@ RUN set -x ; cd /app \
|
||||
--prefer-dist
|
||||
```
|
||||
|
||||
## 整合以上阶段所生成的文件
|
||||
### 7.18.4 整合以上阶段所生成的文件
|
||||
|
||||
第三阶段对以上阶段生成的文件进行整合。
|
||||
|
||||
@@ -118,7 +123,7 @@ RUN set -x ; cd ${LARAVEL_PATH} \
|
||||
&& php artisan package:discover
|
||||
```
|
||||
|
||||
## 最后一个阶段构建 NGINX 镜像
|
||||
### 7.18.5 最后一个阶段构建 NGINX 镜像
|
||||
|
||||
```docker
|
||||
FROM nginx:alpine as nginx
|
||||
@@ -129,7 +134,7 @@ COPY laravel.conf /etc/nginx/conf.d/
|
||||
COPY --from=laravel ${LARAVEL_PATH}/public ${LARAVEL_PATH}/public
|
||||
```
|
||||
|
||||
## 构建 Laravel 及 Nginx 镜像
|
||||
### 7.18.6 构建 Laravel 及 Nginx 镜像
|
||||
|
||||
使用 `docker build` 命令构建镜像。
|
||||
|
||||
@@ -139,7 +144,7 @@ $ docker build -t my/laravel --target=laravel .
|
||||
$ docker build -t my/nginx --target=nginx .
|
||||
```
|
||||
|
||||
## 启动容器并测试
|
||||
### 7.18.7 启动容器并测试
|
||||
|
||||
新建 Docker 网络
|
||||
|
||||
@@ -147,7 +152,7 @@ $ docker build -t my/nginx --target=nginx .
|
||||
$ docker network create laravel
|
||||
```
|
||||
|
||||
启动 laravel 容器, `--name=laravel` 参数设定的名字必须与 `nginx` 配置文件中的 `fastcgi_pass laravel:9000;` 一致
|
||||
启动 laravel 容器,`--name=laravel` 参数设定的名字必须与 `nginx` 配置文件中的 `fastcgi_pass laravel:9000;` 一致
|
||||
|
||||
```bash
|
||||
$ docker run -dit --rm --name=laravel --network=laravel my/laravel
|
||||
@@ -163,13 +168,13 @@ $ docker run -dit --rm --network=laravel -p 8080:80 my/nginx
|
||||
|
||||
> 也许 Laravel 项目依赖其他外部服务,例如 redis、MySQL,请自行启动这些服务之后再进行测试,本小节不再赘述。
|
||||
|
||||
## 生产环境优化
|
||||
### 7.18.8 生产环境优化
|
||||
|
||||
本小节内容为了方便测试,将配置文件直接放到了镜像中,实际在使用时 **建议** 将配置文件作为 `config` 或 `secret` 挂载到容器中,请读者自行学习 `Kubernetes` 的相关内容。
|
||||
|
||||
由于篇幅所限本小节只是简单列出,更多内容可以参考 https://github.com/khs1994-docker/laravel-demo 项目。
|
||||
|
||||
## 附录
|
||||
### 7.18.9 附录
|
||||
|
||||
完整的 `Dockerfile` 文件如下。
|
||||
|
||||