mirror of
https://github.com/yeasy/docker_practice.git
synced 2026-03-13 05:11:07 +00:00
Compare commits
1 Commits
70cab9e92d
...
v1.2.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ceaf6942e0 |
@@ -1,5 +1,3 @@
|
||||
// https://code.visualstudio.com/docs/remote/devcontainerjson-reference
|
||||
|
||||
{
|
||||
"image": "yeasy/docker_practice:latest",
|
||||
"mounts": [
|
||||
|
||||
15
.drone.yml
Normal file
15
.drone.yml
Normal file
@@ -0,0 +1,15 @@
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: build
|
||||
steps:
|
||||
- name: build
|
||||
image: yeasy/docker_practice:latest
|
||||
pull: if-not-exists # always never
|
||||
environment:
|
||||
TZ: Asia/Shanghai
|
||||
commands:
|
||||
- docker-entrypoint.sh build
|
||||
|
||||
trigger:
|
||||
branch:
|
||||
- master
|
||||
2
.gitattributes
vendored
2
.gitattributes
vendored
@@ -1,4 +1,4 @@
|
||||
* text=auto eol=lf
|
||||
* text=auto
|
||||
|
||||
*.sh text eol=lf
|
||||
|
||||
|
||||
43
.github/CODEOWNERS
vendored
43
.github/CODEOWNERS
vendored
@@ -2,32 +2,33 @@
|
||||
/.github/* @khs1994
|
||||
/.travis/* @khs1994
|
||||
/.vuepress/* @khs1994
|
||||
/01_introduction/* @yeasy @khs1994
|
||||
/02_basic_concept/* @yeasy @khs1994
|
||||
/03_install/* @khs1994
|
||||
/04_image/* @yeasy @khs1994
|
||||
/05_container/* @yeasy @khs1994
|
||||
/06_repository/* @khs1994
|
||||
/07_dockerfile/* @yeasy @khs1994
|
||||
/08_data/* @yeasy @khs1994
|
||||
/09_network/* @yeasy @khs1994
|
||||
/10_buildx/* @khs1994
|
||||
/11_compose/* @yeasy @khs1994
|
||||
/12_implementation/* @yeasy @khs1994
|
||||
/13_kubernetes_concepts/* @yeasy @khs1994
|
||||
/14_kubernetes_setup/* @yeasy @khs1994
|
||||
/15_etcd/* @yeasy @khs1994
|
||||
/16_cloud/* @khs1994
|
||||
/17_ecosystem/* @khs1994
|
||||
/18_security/* @yeasy @khs1994
|
||||
/19_observability/* @yeasy @khs1994
|
||||
/20_cases_os/* @yeasy @khs1994
|
||||
/21_case_devops/* @yeasy @khs1994
|
||||
/advanced_network/* @yeasy @khs1994
|
||||
/appendix/* @yeasy @khs1994
|
||||
/archive/* @khs1994
|
||||
/basic_concept/* @yeasy @khs1994
|
||||
/buildx/* @khs1994
|
||||
/cases/* @yeasy @khs1994
|
||||
/cloud/* @khs1994
|
||||
/compose/* @yeasy @khs1994
|
||||
/container/* @yeasy @khs1994
|
||||
/coreos/* @khs1994
|
||||
/data_management/* @khs1994
|
||||
/etcd/* @khs1994
|
||||
/IDE/* @khs1994
|
||||
/image/* @yeasy @khs1994
|
||||
/install/* @khs1994
|
||||
/introduction/* @yeasy @khs1994
|
||||
/kubernetes/* @yeasy @khs1994
|
||||
/network/* @yeasy @khs1994
|
||||
/opensource/* @khs1994
|
||||
/repository/* @khs1994
|
||||
/security/* @yeasy @khs1994
|
||||
/underly/* @yeasy @khs1994
|
||||
/.drone.yml @khs1994
|
||||
/.editorconfig/ @khs1994
|
||||
/.gitattributes @khs1994
|
||||
/.gitignore @khs1994
|
||||
/.travis.yml @khs1994
|
||||
/_config.yml @yeasy @khs1994
|
||||
/book.json @yeasy @khs1994
|
||||
/CHANGELOG.md @yeasy @khs1994
|
||||
|
||||
13
.github/FUNDING.yml
vendored
13
.github/FUNDING.yml
vendored
@@ -1,13 +0,0 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: yeasy
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
16
.github/ISSUE_TEMPLATE/Bug_report.md
vendored
16
.github/ISSUE_TEMPLATE/Bug_report.md
vendored
@@ -7,7 +7,6 @@ about: Create a report to help us improve
|
||||
* [ ] Have u googled the problem? If no, pls do that first!
|
||||
|
||||
### Environment
|
||||
|
||||
<!--请提供环境信息,包括操作系统版本等,保留你的操作系统,其他选项删除-->
|
||||
<!--Provides env info like OS version-->
|
||||
|
||||
@@ -22,18 +21,21 @@ about: Create a report to help us improve
|
||||
* [x] Others (Pls describe below)
|
||||
|
||||
### Docker Version
|
||||
<!--如果你的 Docker 版本低于 19.03 请尽可能升级到该版本,保留你的 Docker 版本,其他选项删除-->
|
||||
<!--if Docker version under 19.03, please upgrade Docker to 19.03-->
|
||||
|
||||
<!--如果你的 Docker 版本低于 20.10 请尽可能升级到该版本,保留你的 Docker 版本,其他选项删除-->
|
||||
<!--if Docker version under 20.10, please upgrade Docker to 20.10-->
|
||||
|
||||
* [x] Test (v20.10)
|
||||
* [x] Stable (v20.10)
|
||||
* [x] Edge (v19.03)
|
||||
* [x] Stable (v19.03)
|
||||
* [x] 1.13.0 or Before
|
||||
|
||||
### Problem Description
|
||||
|
||||
<!--描述你的问题,请贴出操作步骤,终端报错截图或文字信息-->
|
||||
<!--describe problem with detailed steps and logs-->
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!--提交问题之前请点击预览标签,符合要求之后再提交问题-->
|
||||
|
||||
16
.github/ISSUE_TEMPLATE/Custom.md
vendored
16
.github/ISSUE_TEMPLATE/Custom.md
vendored
@@ -7,7 +7,6 @@ about: Create a issue about Docker
|
||||
* [ ] Have u googled the problem? If no, pls do that first!
|
||||
|
||||
### Environment
|
||||
|
||||
<!--请提供环境信息,包括操作系统版本等,保留你的操作系统,其他选项删除-->
|
||||
<!--Provides env info like OS version-->
|
||||
|
||||
@@ -22,18 +21,21 @@ about: Create a issue about Docker
|
||||
* [x] Others (Pls describe below)
|
||||
|
||||
### Docker Version
|
||||
<!--如果你的 Docker 版本低于 19.03 请尽可能升级到该版本,保留你的 Docker 版本,其他选项删除-->
|
||||
<!--if Docker version under 19.03, please upgrade Docker to 19.03-->
|
||||
|
||||
<!--如果你的 Docker 版本低于 20.10 请尽可能升级到该版本,保留你的 Docker 版本,其他选项删除-->
|
||||
<!--if Docker version under 20.10, please upgrade Docker to 20.10-->
|
||||
|
||||
* [x] Test (v20.10)
|
||||
* [x] Stable (v20.10)
|
||||
* [x] Edge (v19.03)
|
||||
* [x] Stable (v19.03)
|
||||
* [x] 1.13.0 or Before
|
||||
|
||||
### Problem Description
|
||||
|
||||
<!--描述你的问题,请贴出操作步骤,终端报错截图或文字信息-->
|
||||
<!--describe problem with detailed steps and logs-->
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!--提交问题之前请点击预览标签,符合要求之后再提交问题-->
|
||||
|
||||
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
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
27
.github/dependabot.yml
vendored
@@ -1,27 +0,0 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
commit-message:
|
||||
prefix: "chore(deps)"
|
||||
labels:
|
||||
- "dependencies"
|
||||
groups:
|
||||
dependencies:
|
||||
patterns:
|
||||
- "*"
|
||||
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
commit-message:
|
||||
prefix: "chore(deps)"
|
||||
labels:
|
||||
- "dependencies"
|
||||
groups:
|
||||
dependencies:
|
||||
patterns:
|
||||
- "*"
|
||||
16
.github/workflows/check-link.yml
vendored
16
.github/workflows/check-link.yml
vendored
@@ -1,6 +1,8 @@
|
||||
name: Check link
|
||||
name: check-link
|
||||
|
||||
on:
|
||||
# push:
|
||||
# pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
@@ -8,7 +10,9 @@ jobs:
|
||||
name: check-link
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6.0.2
|
||||
- uses: actions/checkout@master
|
||||
with:
|
||||
fetch-depth: 1
|
||||
# search Issues :-(
|
||||
- run: |
|
||||
docker run -i --rm \
|
||||
@@ -21,6 +25,7 @@ jobs:
|
||||
"192.168.199.100",\
|
||||
"github.com/settings",\
|
||||
"github.com/docker/compose/releases/download",\
|
||||
"github.com/docker/machine/releases/download",\
|
||||
"github.com/etcd-io/etcd/releases/download",\
|
||||
"github.com/tianon/gosu/releases/download",\
|
||||
"github.com/yeasy/docker_practice",\
|
||||
@@ -32,6 +37,7 @@ jobs:
|
||||
"nodejs.org/dist/",\
|
||||
"c.163.com/hub",\
|
||||
"drone.yeasy.com",\
|
||||
"mirrors.ustc.edu.cn",\
|
||||
"docs.docker.com",\
|
||||
"dockerhub.azk8s.cn",\
|
||||
"reg-mirror.qiniu.com",\
|
||||
@@ -46,6 +52,7 @@ jobs:
|
||||
"security.ubuntu.com/ubuntu",\
|
||||
"nginx.com",\
|
||||
"img.shields.io/github/release/yeasy/docker_practice",\
|
||||
"travis-ci.org/yeasy/docker_practice.svg",\
|
||||
"launchpad.net",\
|
||||
"www.w3.org/1999",\
|
||||
"chat.freenode.net",\
|
||||
@@ -53,13 +60,12 @@ jobs:
|
||||
"product.china-pub.com",\
|
||||
"union-click.jd.com",\
|
||||
"x.x.x.x/base",\
|
||||
"x.x.x.x:9090",\
|
||||
"x.x.x.x:9000/minio/",\
|
||||
"yeasy.gitbooks.io",\
|
||||
"download.fastgit.org",\
|
||||
"www.aliyun.com" \
|
||||
--allow-dupe \
|
||||
--skip-save-results \
|
||||
-t 10 \
|
||||
`find . \( -path "./mesos" -o -path "./swarm_mode" \) -prune -o -name "*.md" -exec ls {} \;`
|
||||
`find . \( -path "./mesos" -o -path "./machine" -o -path "./swarm_mode" \) -prune -o -name "*.md" -exec ls {} \;`
|
||||
name: check-link
|
||||
timeout-minutes: 25
|
||||
|
||||
128
.github/workflows/ci.yaml
vendored
128
.github/workflows/ci.yaml
vendored
@@ -1,96 +1,52 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash --noprofile --norc -exo pipefail {0}
|
||||
name: CI
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
name: Build GitBook
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6.0.2
|
||||
- name: Build Gitbook
|
||||
- uses: actions/checkout@master
|
||||
with:
|
||||
fetch-depth: 2
|
||||
- name: Build
|
||||
uses: docker://yeasy/docker_practice
|
||||
with:
|
||||
args: build
|
||||
- name: Build Gitbook Docker Image
|
||||
if: github.repository == 'docker-practice/docker_practice'
|
||||
run: |
|
||||
sudo chmod -R 777 _book
|
||||
echo "FROM nginx:alpine" >> Dockerfile
|
||||
echo "COPY _book /usr/share/nginx/html" >> Dockerfile
|
||||
echo "COPY .docker/docker-entrypoint.sh /" >> Dockerfile
|
||||
echo "ENTRYPOINT [\"/docker-entrypoint.sh\"]" >> Dockerfile
|
||||
|
||||
export VCS_REF=`git rev-parse --short HEAD`
|
||||
|
||||
docker build \
|
||||
-t dockerpracticesig/docker_practice \
|
||||
-t dockerpracticesig/docker_practice:gitbook \
|
||||
--label org.opencontainers.image.revision=$VCS_REF \
|
||||
--label org.opencontainers.image.source="https://github.com/yeasy/docker_practice" \
|
||||
--label maintainer="https://github.com/docker-practice" \
|
||||
.
|
||||
|
||||
docker run -d --rm -p 4000:80 dockerpracticesig/docker_practice
|
||||
|
||||
sleep 5
|
||||
|
||||
echo "::group::Test"
|
||||
curl 127.0.0.1:4000
|
||||
echo "::endgroup::"
|
||||
|
||||
echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
|
||||
docker push dockerpracticesig/docker_practice
|
||||
docker push dockerpracticesig/docker_practice:gitbook
|
||||
env:
|
||||
DOCKER_PASSWORD: ${{secrets.DOCKER_PASSWORD}}
|
||||
DOCKER_USERNAME: ${{secrets.DOCKER_USERNAME}}
|
||||
- name: Upload Gitbook dist
|
||||
uses: docker://pcit/pages
|
||||
if: github.repository == 'docker-practice/docker_practice'
|
||||
env:
|
||||
PCIT_EMAIL: khs1994@khs1994.com
|
||||
PCIT_GIT_TOKEN: ${{ secrets.PCIT_GIT_TOKEN }}
|
||||
PCIT_GIT_URL: github.com/docker-practice/zh-cn
|
||||
PCIT_KEEP_HISTORY: "true"
|
||||
PCIT_LOCAL_DIR: _book
|
||||
PCIT_MESSAGE: Build from yeasy/docker_practice@${{github.sha}}
|
||||
PCIT_TARGET_BRANCH: master
|
||||
PCIT_USERNAME: khs1994
|
||||
- name: vuepress
|
||||
run: |
|
||||
export NODE_OPTIONS=--openssl-legacy-provider
|
||||
sudo rm -rf _book
|
||||
# npm i vuepress --save-dev
|
||||
npm i
|
||||
|
||||
git clone https://github.com/docker-practice/.vuepress .vuepress2
|
||||
cp -r .vuepress2/. .vuepress/
|
||||
rm -rf .vuepress2
|
||||
find . \( -path "./mesos" -o -path "./swarm_mode" -o -path "./node_modules" -o -path "./.vuepress" -o -path "./_book" -o -path "./CHANGELOG.md" -o -path "./CONTRIBUTING.md" \) -prune -o -name "*.md" -exec sed -i 'N;2a\<AdSenseTitle/>\n' {} \;
|
||||
find . \( -path "./mesos" -o -path "./machine" -o -path "./swarm_mode" -o -path "./node_modules" -o -path "./.vuepress" -o -path "./_book" -o -path "./CHANGELOG.md" -o -path "./CONTRIBUTING.md" \) -prune -o -name "*.md" -exec sed -i 'N;2a\<AdSenseTitle/>\n' {} \;
|
||||
|
||||
npx vuepress --version
|
||||
|
||||
npm run vuepress:build
|
||||
echo "vuepress.mirror.docker-practice.com" > .vuepress/dist/CNAME
|
||||
echo "vuepress.docker-practice.com" > .vuepress/dist/CNAME
|
||||
|
||||
cp -r _images .vuepress/dist
|
||||
mkdir -p .vuepress/dist/appendix
|
||||
cp -r advanced_network/_images .vuepress/dist/advanced_network
|
||||
cp -r appendix/_images .vuepress/dist/appendix
|
||||
mkdir -p .vuepress/dist/cloud
|
||||
cp -r 16_cloud/_images .vuepress/dist/cloud
|
||||
mkdir -p .vuepress/dist/data_management
|
||||
cp -r 08_data/_images .vuepress/dist/data_management
|
||||
mkdir -p .vuepress/dist/etcd
|
||||
cp -r 15_etcd/_images .vuepress/dist/etcd
|
||||
mkdir -p .vuepress/dist/kubernetes
|
||||
cp -r 13_kubernetes_concepts/_images .vuepress/dist/kubernetes
|
||||
cp -r cases/ci/drone/_images .vuepress/dist/cases/ci/drone
|
||||
cp -r cases/os/_images .vuepress/dist/cases/os
|
||||
cp -r cloud/_images .vuepress/dist/cloud
|
||||
cp -r data_management/_images .vuepress/dist/data_management
|
||||
cp -r etcd/_images .vuepress/dist/etcd
|
||||
cp -r image/_images .vuepress/dist/image
|
||||
cp -r install/_images .vuepress/dist/install
|
||||
cp -r introduction/_images .vuepress/dist/introduction
|
||||
cp -r kubernetes/_images .vuepress/dist/kubernetes
|
||||
cp -r mesos/_images .vuepress/dist/mesos
|
||||
cp -r underly/_images .vuepress/dist/underly
|
||||
|
||||
echo "include: [_images]" > .vuepress/dist/_config.yml
|
||||
- name: Upload Vuepress dist
|
||||
@@ -102,30 +58,30 @@ jobs:
|
||||
PCIT_GIT_URL: github.com/docker-practice/vuepress
|
||||
PCIT_KEEP_HISTORY: "true"
|
||||
PCIT_LOCAL_DIR: .vuepress/dist
|
||||
PCIT_MESSAGE: Build from yeasy/docker_practice@${{github.sha}}
|
||||
PCIT_MESSAGE: Sync from yeasy/docker_practice@${{github.sha}} by PCIT
|
||||
PCIT_TARGET_BRANCH: master
|
||||
PCIT_USERNAME: khs1994
|
||||
# - name: Set coding.net CNAME
|
||||
# run: |
|
||||
# echo "vuepress.mirror.docker-practice.com" > .vuepress/dist/CNAME
|
||||
# - name: Upload Vuepress dist to coding.net
|
||||
# uses: docker://pcit/pages
|
||||
# if: github.repository == 'docker-practice/docker_practice'
|
||||
# env:
|
||||
# PCIT_EMAIL: khs1994@khs1994.com
|
||||
# PCIT_GIT_TOKEN: ${{ secrets.CODING_GIT_TOKEN }}
|
||||
# PCIT_GIT_URL: e.coding.net/dpsigs/docker_practice
|
||||
# PCIT_KEEP_HISTORY: "true"
|
||||
# PCIT_LOCAL_DIR: .vuepress/dist
|
||||
# PCIT_MESSAGE: Build from yeasy/docker_practice@${{github.sha}}
|
||||
# PCIT_TARGET_BRANCH: master
|
||||
# PCIT_USERNAME: ptt0xjqzbke3
|
||||
- name: Set coding.net CNAME
|
||||
run: |
|
||||
echo "vuepress.mirror.docker-practice.com" > .vuepress/dist/CNAME
|
||||
- name: Upload Vuepress dist to coding.net
|
||||
uses: docker://pcit/pages
|
||||
if: github.repository == 'docker-practice/docker_practice'
|
||||
env:
|
||||
PCIT_EMAIL: khs1994@khs1994.com
|
||||
PCIT_GIT_TOKEN: ${{ secrets.CODING_GIT_TOKEN }}
|
||||
PCIT_GIT_URL: e.coding.net/dpsigs/docker_practice
|
||||
PCIT_KEEP_HISTORY: "true"
|
||||
PCIT_LOCAL_DIR: .vuepress/dist
|
||||
PCIT_MESSAGE: Sync from yeasy/docker_practice@${{github.sha}} by PCIT
|
||||
PCIT_TARGET_BRANCH: master
|
||||
PCIT_USERNAME: ptt0xjqzbke3
|
||||
- name: Build vuepress docker image
|
||||
if: github.repository == 'docker-practice/docker_practice'
|
||||
run: |
|
||||
sudo rm -rf .vuepress/dist/.git
|
||||
|
||||
echo "FROM nginx:alpine" > Dockerfile
|
||||
echo "FROM nginx:alpine" >> Dockerfile
|
||||
echo "COPY .vuepress/dist /usr/share/nginx/html" >> Dockerfile
|
||||
echo "COPY .docker/docker-entrypoint.sh /" >> Dockerfile
|
||||
echo "ENTRYPOINT [\"/docker-entrypoint.sh\"]" >> Dockerfile
|
||||
@@ -142,13 +98,9 @@ jobs:
|
||||
|
||||
docker push dockerpracticesig/docker_practice:vuepress
|
||||
|
||||
docker run -it --rm -d -p 4001:80 dockerpracticesig/docker_practice:vuepress
|
||||
docker run -it --rm -d -p 4000:80 dockerpracticesig/docker_practice:vuepress
|
||||
|
||||
sleep 5
|
||||
|
||||
echo "::group::Test"
|
||||
curl 127.0.0.1:4001
|
||||
echo "::endgroup::"
|
||||
curl 127.0.0.1:4000
|
||||
env:
|
||||
DOCKER_PASSWORD: ${{secrets.DOCKER_PASSWORD}}
|
||||
DOCKER_USERNAME: ${{secrets.DOCKER_USERNAME}}
|
||||
|
||||
27
.github/workflows/dependabot-automerge.yml
vendored
27
.github/workflows/dependabot-automerge.yml
vendored
@@ -1,27 +0,0 @@
|
||||
name: Dependabot auto-merge
|
||||
on: pull_request
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
dependabot:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.actor == 'dependabot[bot]'
|
||||
steps:
|
||||
- name: Dependabot metadata
|
||||
id: metadata
|
||||
uses: dependabot/fetch-metadata@v2
|
||||
with:
|
||||
github-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
- name: Approve a PR
|
||||
run: gh pr review --approve "$PR_URL"
|
||||
env:
|
||||
PR_URL: ${{github.event.pull_request.html_url}}
|
||||
GH_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
||||
- name: Enable auto-merge for Dependabot PRs
|
||||
run: gh pr merge --auto --merge "$PR_URL"
|
||||
env:
|
||||
PR_URL: ${{github.event.pull_request.html_url}}
|
||||
GH_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
||||
20
.gitignore
vendored
20
.gitignore
vendored
@@ -3,29 +3,9 @@
|
||||
*.tmp
|
||||
.idea/
|
||||
_book/
|
||||
format_report.txt
|
||||
*.swp
|
||||
*.edx
|
||||
.DS_Store
|
||||
|
||||
node_modules/
|
||||
package-lock.json
|
||||
|
||||
docker-compose.override.yml
|
||||
|
||||
# Editor configs
|
||||
.obsidian/
|
||||
.vscode/
|
||||
|
||||
.agent/
|
||||
__pycache__/
|
||||
|
||||
# Check scripts
|
||||
check_project_rules.py
|
||||
check_dashes.py
|
||||
checker.py
|
||||
find_lists_no_space.py
|
||||
fix_missing_spaces.py
|
||||
fix_project_rules.py
|
||||
fixer.py
|
||||
format_headings.py
|
||||
|
||||
65
.travis.yml
Normal file
65
.travis.yml
Normal file
@@ -0,0 +1,65 @@
|
||||
language: bash
|
||||
|
||||
services:
|
||||
- docker
|
||||
|
||||
before_install:
|
||||
- openssl aes-256-cbc -K $encrypted_6cc8cff04075_key -iv $encrypted_6cc8cff04075_iv
|
||||
-in .travis/id_rsa.enc -out ~/.ssh/id_rsa -d
|
||||
- chmod 600 ~/.ssh/id_rsa
|
||||
- export TZ='Asia/Shanghai'
|
||||
- date
|
||||
- git config --global user.name "khs1994"
|
||||
- git config --global user.email "khs1994@khs1994.com"
|
||||
|
||||
script:
|
||||
- docker run -it --rm -v $PWD:/srv/gitbook-src yeasy/docker_practice build
|
||||
|
||||
after_success:
|
||||
- sudo chmod -R 777 _book
|
||||
|
||||
- echo "FROM nginx:alpine" >> Dockerfile
|
||||
- echo "COPY _book /usr/share/nginx/html" >> Dockerfile
|
||||
- echo "COPY .docker/docker-entrypoint.sh /" >> Dockerfile
|
||||
- echo "ENTRYPOINT [\"/docker-entrypoint.sh\"]" >> Dockerfile
|
||||
|
||||
- echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
|
||||
- export VCS_REF=`git rev-parse --short HEAD`
|
||||
- |
|
||||
docker build \
|
||||
-t dockerpracticesig/docker_practice \
|
||||
-t dockerpracticesig/docker_practice:gitbook \
|
||||
--label org.opencontainers.image.revision=$VCS_REF \
|
||||
--label org.opencontainers.image.source="https://github.com/yeasy/docker_practice" \
|
||||
--label maintainer="https://github.com/docker-practice" \
|
||||
.
|
||||
|
||||
- docker run -dit --rm -p 4000:80 dockerpracticesig/docker_practice
|
||||
|
||||
- sleep 5
|
||||
- curl 127.0.0.1:4000
|
||||
|
||||
- docker push dockerpracticesig/docker_practice
|
||||
- docker push dockerpracticesig/docker_practice:gitbook
|
||||
|
||||
- cd _book
|
||||
- rm -rf .github/workflows
|
||||
- git init
|
||||
- git remote add origin "$DEPLOY_REPO"
|
||||
- git add .
|
||||
- COMMIT=`date "+%F %T"`
|
||||
- git commit -m "Travis CI Site updated $COMMIT yeasy/docker_practice@${TRAVIS_COMMIT}"
|
||||
- git push -f origin master:"$DEPLOY_BRANCH"
|
||||
|
||||
env:
|
||||
global:
|
||||
- DEPLOY_BRANCH: master
|
||||
- DEPLOY_REPO: git@github.com:docker-practice/zh-cn.git
|
||||
|
||||
addons:
|
||||
ssh_known_hosts:
|
||||
- github.com
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
26
.travis/Dockerfile
Normal file
26
.travis/Dockerfile
Normal file
@@ -0,0 +1,26 @@
|
||||
FROM node:14.4.0-alpine
|
||||
|
||||
ENV TZ=Asia/Shanghai
|
||||
|
||||
WORKDIR /srv/gitbook
|
||||
|
||||
COPY book.json book.json
|
||||
|
||||
COPY docker-entrypoint.sh /usr/local/bin/
|
||||
|
||||
RUN set -x && apk add --no-cache \
|
||||
tzdata bash \
|
||||
&& npm install -g gitbook-cli \
|
||||
&& gitbook install \
|
||||
&& ln -s /usr/local/bin/docker-entrypoint.sh / \
|
||||
&& rm -rf /root/.npm /tmp/*
|
||||
|
||||
EXPOSE 4000
|
||||
|
||||
VOLUME /srv/gitbook-src
|
||||
|
||||
WORKDIR /srv/gitbook-src
|
||||
|
||||
ENTRYPOINT ["docker-entrypoint.sh"]
|
||||
|
||||
CMD server
|
||||
37
.travis/book.json
Normal file
37
.travis/book.json
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"title": "Docker -- 从入门到实践",
|
||||
"author": "yeasy",
|
||||
"language": "zh-hans",
|
||||
"links": {
|
||||
"sidebar": {
|
||||
"GitHub": "https://github.com/yeasy/docker_practice"
|
||||
}
|
||||
},
|
||||
"plugins": [
|
||||
"-livereload",
|
||||
"image-captions",
|
||||
"github",
|
||||
"page-treeview@2.9.8",
|
||||
"editlink"
|
||||
],
|
||||
"pluginsConfig": {
|
||||
"image-captions": {
|
||||
"attributes": {
|
||||
"width": "600"
|
||||
},
|
||||
"caption": "图 _PAGE_LEVEL_._PAGE_IMAGE_NUMBER_ - _CAPTION_"
|
||||
},
|
||||
"github": {
|
||||
"url": "https://github.com/yeasy/docker_practice"
|
||||
},
|
||||
"editlink": {
|
||||
"base": "https://github.com/yeasy/docker_practice/blob/master/",
|
||||
"label": "编辑本页"
|
||||
},
|
||||
"page-treeview": {
|
||||
"copyright": "Copyright © yeasy",
|
||||
"minHeaderCount": "2",
|
||||
"minHeaderDeep": "2"
|
||||
}
|
||||
}
|
||||
}
|
||||
43
.travis/conf.d/nginx.conf
Normal file
43
.travis/conf.d/nginx.conf
Normal file
@@ -0,0 +1,43 @@
|
||||
user root;
|
||||
worker_processes auto;
|
||||
|
||||
error_log /var/log/nginx/error.log warn;
|
||||
pid /var/run/nginx.pid;
|
||||
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||
'$status $body_bytes_sent "$http_referer" '
|
||||
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||
|
||||
access_log /var/log/nginx/access.log main;
|
||||
|
||||
sendfile on;
|
||||
#tcp_nopush on;
|
||||
|
||||
keepalive_timeout 65;
|
||||
|
||||
#gzip on;
|
||||
|
||||
index index.html index.php;
|
||||
|
||||
server {
|
||||
|
||||
server_name localhost;
|
||||
|
||||
listen 4000;
|
||||
|
||||
root /srv/www/;
|
||||
|
||||
index index.html;
|
||||
|
||||
}
|
||||
}
|
||||
5
.travis/docker-compose.test.yml
Normal file
5
.travis/docker-compose.test.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
sut:
|
||||
build: .
|
||||
volumes:
|
||||
- ../:/srv/gitbook-src
|
||||
command: build
|
||||
23
.travis/docker-entrypoint.sh
Executable file
23
.travis/docker-entrypoint.sh
Executable file
@@ -0,0 +1,23 @@
|
||||
#!/bin/sh
|
||||
|
||||
START=`date "+%F %T"`
|
||||
|
||||
if [ $1 = "sh" ];then sh ; exit 0; fi
|
||||
|
||||
rm -rf node_modules _book
|
||||
|
||||
srcDir=$PWD
|
||||
|
||||
cp -a . /srv/gitbook
|
||||
|
||||
cd /srv/gitbook
|
||||
|
||||
main(){
|
||||
if [ "$1" = build ];then
|
||||
gitbook build && cp -a _book $srcDir && echo $START && date "+%F %T" && exit 0
|
||||
else
|
||||
exec gitbook serve
|
||||
fi
|
||||
}
|
||||
|
||||
main $1 $2 $3
|
||||
BIN
.travis/id_rsa.enc
Normal file
BIN
.travis/id_rsa.enc
Normal file
Binary file not shown.
8
.travis/update.sh
Executable file
8
.travis/update.sh
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# cd .travis
|
||||
# ./update.sh
|
||||
|
||||
if [ ! -f Dockerfile ];then exit 1; fi
|
||||
|
||||
cp -a ../book.json book.json
|
||||
@@ -1,6 +1,8 @@
|
||||
const { config } = require('vuepress-theme-hope')
|
||||
const resolve = require("vuepress-theme-hope/resolve");
|
||||
// const { config } = require('vuepress-theme-hope')
|
||||
|
||||
module.exports = config({
|
||||
// module.exports =config({
|
||||
module.exports = resolve({
|
||||
title: 'Docker 从入门到实践',
|
||||
base: '/',
|
||||
head: [['script', {}, `
|
||||
@@ -38,11 +40,6 @@ module.exports = config({
|
||||
// onlyFirstAndLastCommit: true,
|
||||
// },
|
||||
},
|
||||
locales: {
|
||||
"/": {
|
||||
lang: "zh-CN"
|
||||
}
|
||||
},
|
||||
themeConfig: {
|
||||
blog: false,
|
||||
// comment: false,
|
||||
@@ -52,40 +49,29 @@ module.exports = config({
|
||||
appKey: "...", // your appKey
|
||||
},
|
||||
pageInfo: [
|
||||
// 'author',
|
||||
'reading-time',
|
||||
'word',
|
||||
// 'Author',
|
||||
'ReadTime',
|
||||
'Word',
|
||||
],
|
||||
footer: {
|
||||
content: "Made with <a target='_blank' href='https://github.com/vuepress-theme-hope/vuepress-theme-hope'>vuepress-theme-hope</a>",
|
||||
content: "Made with <a target='_blank' href='https://github.com/mister-hope/vuepress-theme-hope'>vuepress-theme-hope</a>",
|
||||
display: true,
|
||||
copyright: false,
|
||||
},
|
||||
searchPlaceholder: 'Search',
|
||||
repo: 'yeasy/docker_practice',
|
||||
repoLabel: 'GitHub',
|
||||
repoDisplay: true,
|
||||
hostname: 'https://vuepress.mirror.docker-practice.com',
|
||||
// author: 'yeasy',
|
||||
mdEnhance: {
|
||||
markdown: {
|
||||
lineNumbers: true,
|
||||
},
|
||||
git: {
|
||||
contributor: false,
|
||||
},
|
||||
themeColor: {
|
||||
blue: '#2196f3',
|
||||
// red: '#f26d6d',
|
||||
// green: '#3eaf7c',
|
||||
// orange: '#fb9b5f'
|
||||
},
|
||||
locales: {
|
||||
"/": {
|
||||
lang: "zh-CN"
|
||||
}
|
||||
},
|
||||
|
||||
darkmode: 'auto-switch',
|
||||
|
||||
//
|
||||
|
||||
@@ -98,15 +84,15 @@ module.exports = config({
|
||||
nav: [
|
||||
{
|
||||
text: '微信交流群',
|
||||
link: 'https://docker_practice.gitee.io/pic/dpsig-wechat.jpg',
|
||||
link: 'https://yewm28.coding-pages.com/wechat.jpg',
|
||||
},
|
||||
{
|
||||
text: '小程序',
|
||||
link: 'https://docker_practice.gitee.io/pic/dp-wechat-miniprogram.jpg',
|
||||
link: 'https://yewm28.coding-pages.com/49682252-3ac4c500-faec-11e8-86ab-eafe0139be6b.jpg',
|
||||
},
|
||||
{
|
||||
text: '安装 Docker',
|
||||
link: '/03_install/',
|
||||
link: '/install/',
|
||||
},
|
||||
{
|
||||
text: 'Docker 入门',
|
||||
@@ -114,35 +100,35 @@ module.exports = config({
|
||||
},
|
||||
{
|
||||
text: 'Docker 实战',
|
||||
link: '/15_cases/os/'
|
||||
link: '/cases/os/'
|
||||
},
|
||||
{
|
||||
text: 'CI/CD',
|
||||
link: '/15_cases/ci/'
|
||||
link: '/cases/ci/'
|
||||
},
|
||||
{
|
||||
text: 'Compose',
|
||||
link: '/10_compose/',
|
||||
link: '/compose/',
|
||||
},
|
||||
{
|
||||
text: 'Kubernetes',
|
||||
link: '/12_orchestration/kubernetes/',
|
||||
link: '/kubernetes/',
|
||||
},
|
||||
{
|
||||
text: "云计算",
|
||||
link: "/13_ecosystem/cloud/",
|
||||
link: "/cloud/",
|
||||
},
|
||||
{
|
||||
text: 'GitHub',
|
||||
link: 'https://github.com/yeasy/docker_practice'
|
||||
},
|
||||
// {
|
||||
// text: 'GitHub',
|
||||
// link: 'https://github.com/yeasy/docker_practice'
|
||||
// },
|
||||
// {
|
||||
// text: '捐赠',
|
||||
// link: ''
|
||||
// },
|
||||
{
|
||||
text: '云服务器99/元首年特惠',
|
||||
link: 'https://cloud.tencent.com/act/cps/redirect?redirect=1062&cps_key=3a5255852d5db99dcd5da4c72f05df61&from=console'
|
||||
text: '腾讯云容器服务',
|
||||
link: 'https://cloud.tencent.com/act/cps/redirect?redirect=10058&cps_key=3a5255852d5db99dcd5da4c72f05df61'
|
||||
},
|
||||
// {
|
||||
// text: '语言',
|
||||
@@ -152,8 +138,7 @@ module.exports = config({
|
||||
// }]
|
||||
// }
|
||||
],
|
||||
sidebar: "auto",
|
||||
legacySidebar: {
|
||||
sidebar: {
|
||||
'/cloud/': [
|
||||
'intro',
|
||||
'tencentCloud',
|
||||
@@ -167,7 +152,7 @@ module.exports = config({
|
||||
'design',
|
||||
{
|
||||
title: "部署 Kubernetes",
|
||||
collapsable: true,
|
||||
collapsable: false,
|
||||
children: [
|
||||
"setup/",
|
||||
"setup/kubeadm",
|
||||
@@ -178,7 +163,7 @@ module.exports = config({
|
||||
},
|
||||
{
|
||||
title: "Kubernetes 命令行 kubectl",
|
||||
collapsable: true,
|
||||
collapsable: false,
|
||||
children: [
|
||||
'kubectl/'
|
||||
]
|
||||
@@ -186,7 +171,6 @@ module.exports = config({
|
||||
],
|
||||
'/compose/': [
|
||||
'introduction',
|
||||
'v2',
|
||||
'install',
|
||||
'usage',
|
||||
'commands',
|
||||
@@ -201,8 +185,9 @@ module.exports = config({
|
||||
'debian',
|
||||
'fedora',
|
||||
'centos',
|
||||
'centos8',
|
||||
'raspberry-pi',
|
||||
// 'offline',
|
||||
'offline',
|
||||
'mac',
|
||||
'windows',
|
||||
'mirror',
|
||||
@@ -233,12 +218,13 @@ module.exports = config({
|
||||
'actions/',
|
||||
{
|
||||
title: "Drone",
|
||||
collapsable: true,
|
||||
collapsable: false,
|
||||
children: [
|
||||
'drone/',
|
||||
'drone/install'
|
||||
]
|
||||
},
|
||||
'travis/'
|
||||
],
|
||||
'/': [
|
||||
'/',
|
||||
@@ -278,7 +264,7 @@ module.exports = config({
|
||||
},
|
||||
{
|
||||
title: 'Dockerfile',
|
||||
collapsable: true,
|
||||
collapsable: false,
|
||||
children: [
|
||||
"image/dockerfile/",
|
||||
'image/dockerfile/copy',
|
||||
@@ -296,8 +282,8 @@ module.exports = config({
|
||||
'image/dockerfile/shell',
|
||||
'image/dockerfile/onbuild',
|
||||
'image/dockerfile/references',
|
||||
'image/dockerfile/7.17_multistage_builds.md',
|
||||
'image/dockerfile/7.18_multistage_builds_laravel.md',
|
||||
'image/multistage-builds/',
|
||||
'image/multistage-builds/laravel',
|
||||
'image/manifest',
|
||||
]
|
||||
}, {
|
||||
@@ -344,7 +330,7 @@ module.exports = config({
|
||||
},
|
||||
{
|
||||
title: "高级网络配置",
|
||||
collapsable: true,
|
||||
collapsable: false,
|
||||
children: [
|
||||
'advanced_network/',
|
||||
'advanced_network/quick_guide',
|
||||
@@ -358,7 +344,7 @@ module.exports = config({
|
||||
},
|
||||
{
|
||||
title: "Swarm mode",
|
||||
collapsable: true,
|
||||
collapsable: false,
|
||||
children: [
|
||||
'swarm_mode/',
|
||||
'swarm_mode/overview',
|
||||
@@ -372,7 +358,7 @@ module.exports = config({
|
||||
},
|
||||
{
|
||||
title: "安全",
|
||||
collapsable: true,
|
||||
collapsable: false,
|
||||
children: [
|
||||
'security/',
|
||||
'security/kernel_ns',
|
||||
@@ -385,7 +371,7 @@ module.exports = config({
|
||||
},
|
||||
{
|
||||
title: "底层实现",
|
||||
collapsable: true,
|
||||
collapsable: false,
|
||||
children: [
|
||||
'underly/',
|
||||
'underly/arch',
|
||||
@@ -408,7 +394,7 @@ module.exports = config({
|
||||
},
|
||||
{
|
||||
title: "Etcd",
|
||||
collapsable: true,
|
||||
collapsable: false,
|
||||
children: [
|
||||
'etcd/',
|
||||
'etcd/intro',
|
||||
@@ -419,7 +405,7 @@ module.exports = config({
|
||||
},
|
||||
{
|
||||
title: "Fedora CoreOS",
|
||||
collapsable: true,
|
||||
collapsable: false,
|
||||
children: [
|
||||
'coreos/',
|
||||
'coreos/intro',
|
||||
@@ -430,7 +416,7 @@ module.exports = config({
|
||||
'appendix/faq/',
|
||||
{
|
||||
title: "热门镜像介绍",
|
||||
collapsable: true,
|
||||
collapsable: false,
|
||||
children: [
|
||||
'appendix/repo/',
|
||||
'appendix/repo/ubuntu',
|
||||
@@ -447,7 +433,7 @@ module.exports = config({
|
||||
},
|
||||
{
|
||||
title: "Docker 命令",
|
||||
collapsable: true,
|
||||
collapsable: false,
|
||||
children: [
|
||||
'appendix/command/',
|
||||
'appendix/command/docker',
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
node_modules/
|
||||
.vuepress/
|
||||
.git/
|
||||
@@ -1,68 +0,0 @@
|
||||
## 1.1 快速上手
|
||||
|
||||
本节将通过一个简单的 Web 应用例子,带你快速体验 Docker 的核心流程:构建镜像、运行容器。
|
||||
|
||||
### 1.1.1 准备代码
|
||||
|
||||
创建一个名为 `hello-docker` 的文件夹,并在其中创建一个 `index.html` 文件:
|
||||
|
||||
```html
|
||||
<h1>Hello, Docker!</h1>
|
||||
```
|
||||
|
||||
### 1.1.2 编写 Dockerfile
|
||||
|
||||
在同级目录下创建一个名为 `Dockerfile` (无后缀) 的文件:
|
||||
|
||||
```dockerfile
|
||||
FROM nginx:alpine
|
||||
COPY index.html /usr/share/nginx/html/index.html
|
||||
```
|
||||
|
||||
### 1.1.3 构建镜像
|
||||
|
||||
打开终端,进入该目录,执行构建命令:
|
||||
|
||||
```bash
|
||||
$ docker build -t my-hello-world .
|
||||
```
|
||||
|
||||
* `docker build`:构建命令
|
||||
* `-t my-hello-world`:给镜像起个名字 (标签)
|
||||
* `.`:指定上下文路径为当前目录
|
||||
|
||||
### 1.1.4 运行容器
|
||||
|
||||
使用刚才构建的镜像启动一个容器:
|
||||
|
||||
```bash
|
||||
$ docker run -d -p 8080:80 my-hello-world
|
||||
```
|
||||
|
||||
* `docker run`:运行命令
|
||||
* `-d`:后台运行
|
||||
* `-p 8080:80`:将宿主机的 8080 端口映射到容器的 80 端口
|
||||
|
||||
### 1.1.5 访问测试
|
||||
|
||||
打开浏览器访问 [http://localhost:8080](http://localhost:8080),你应该能看到 “Hello,Docker!”。
|
||||
|
||||
### 1.1.6 清理
|
||||
|
||||
停止并删除容器:
|
||||
|
||||
```bash
|
||||
## 查看正在运行的容器 ID
|
||||
|
||||
$ docker ps
|
||||
|
||||
## 停止容器
|
||||
|
||||
$ docker stop <CONTAINER_ID>
|
||||
|
||||
## 删除容器
|
||||
|
||||
$ docker rm <CONTAINER_ID>
|
||||
```
|
||||
|
||||
恭喜!你已经完成了第一次 Docker 实战。接下来请阅读 [Docker 核心概念](../02_basic_concept/README.md)做深入了解。
|
||||
@@ -1,120 +0,0 @@
|
||||
## 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 最大的贡献不仅是技术本身,更是它 **让容器技术从系统管理员的工具变成了每个开发者都能使用的标准工具**。
|
||||
@@ -1,212 +0,0 @@
|
||||
## 1.3 为什么要用 Docker
|
||||
|
||||
在回答 “为什么用 Docker” 之前,笔者想先问一个问题:**你有没有经历过这些场景?**
|
||||
|
||||
### 1.3.1 没有 Docker 的世界
|
||||
|
||||
在 Docker 出现之前,软件开发和运维面临着诸多棘手的问题。我们先来看看以下三个典型的痛点场景。
|
||||
|
||||
#### 场景一:“在我电脑上明明能跑”
|
||||
|
||||
```bash
|
||||
周五下午 5:00
|
||||
├── 开发者:代码写完了,本地测试通过,提交!🎉
|
||||
├── 周一早上 9:00
|
||||
│ └── 测试:"这个功能在测试环境跑不起来"
|
||||
└── 开发者:" 不可能,在我电脑上明明能跑啊……"
|
||||
```
|
||||
|
||||
笔者统计过,这个问题通常由以下原因导致:
|
||||
|
||||
- Python/Node/Java 版本不一致
|
||||
- 依赖库版本不一致
|
||||
- 操作系统配置不一致
|
||||
- 某些环境变量没有设置
|
||||
- “哦,忘了说我本地装了个 XXX”
|
||||
|
||||
#### 场景二:环境配置的噩梦
|
||||
|
||||
```bash
|
||||
新同事入职
|
||||
├── Day 1:领电脑,配环境
|
||||
├── Day 2:继续配环境,遇到问题
|
||||
├── Day 3:换种方法配环境
|
||||
├── Day 4:问老同事怎么配的,他也忘了
|
||||
└── Day 5:终于能跑起来了!但不知道为什么……
|
||||
```
|
||||
|
||||
#### 场景三:服务器迁移的恐惧
|
||||
|
||||
```bash
|
||||
运维:"我们需要把服务迁移到新服务器"
|
||||
开发:"旧服务器上的配置文档在哪?"
|
||||
运维:"当时是一个已经离职的同事配的……"
|
||||
所有人:😱
|
||||
```
|
||||
|
||||
### 1.3.2 Docker 如何解决这些问题
|
||||
|
||||
Docker 的出现为上述问题提供了完美的解决方案。它通过 “一次构建,到处运行” 的核心理念,从根本上改变了软件交付的方式。
|
||||
|
||||
#### 核心理念:一次构建,到处运行
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
dev["开发团队"] -->|创建| img["Docker 镜像"]
|
||||
img -->|测试团队验证| test["测试团队"]
|
||||
test -- "有问题<br/>反馈修改和更新" --> dev
|
||||
test -- "没问题<br/>发布" --> prod["生产环境"]
|
||||
```
|
||||
|
||||
### 1.3.3 Docker 的核心优势
|
||||
|
||||
除了解决上述痛点,Docker 还拥有诸多显著的技术优势,包括环境一致性、秒级启动、高效的资源利用等。
|
||||
|
||||
#### 1. 环境一致性
|
||||
|
||||
Docker 镜像包含了应用运行所需的 **一切**:代码、运行时、系统工具、库、配置。这意味着:
|
||||
|
||||
- ✅ 开发环境和生产环境完全一致
|
||||
- ✅ 不会再有 “在我机器上能跑” 的问题
|
||||
- ✅ 新人入职,一条命令就能启动开发环境
|
||||
|
||||
```bash
|
||||
## 新同事入职第一天
|
||||
|
||||
$ git clone https://github.com/company/project.git
|
||||
$ docker compose up
|
||||
## 完整的开发环境就准备好了
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
#### 2. 秒级启动
|
||||
|
||||
传统虚拟机启动需要几分钟 (引导操作系统),而 Docker 容器启动通常只需要 **几秒甚至几百毫秒**。
|
||||
|
||||
笔者实测数据:
|
||||
|
||||
| 启动内容 | 虚拟机 | Docker 容器 |
|
||||
|---------|--------|-------------|
|
||||
| 空系统 | ~60 秒 | ~0.5 秒 |
|
||||
| MySQL | ~90 秒 | ~3 秒 |
|
||||
| 完整 Web 应用 | ~120 秒 | ~5 秒 |
|
||||
|
||||
这个差异对以下场景尤为重要:
|
||||
|
||||
- **CI/CD 流水线**:每次构建节省几分钟,一天累积下来就是几小时
|
||||
- **弹性扩容**:流量高峰时能快速启动更多实例
|
||||
- **开发体验**:快速重启服务进行调试
|
||||
|
||||
#### 3. 资源效率
|
||||
|
||||
Docker 容器共享宿主机内核,无需为每个应用运行完整的操作系统。以一台 64GB 内存的物理服务器为例:
|
||||
- **传统虚拟机方案**:每个虚拟机都需要运行完整的操作系统(每个额外占用如 2GB 内存),产生大量资源开销,实际可用于应用的内存可能只有约 18GB。
|
||||
- **Docker 方案**:容器直接共享宿主机系统,只需付出很少的基础开销(OS及引擎约 4GB),即可将约 60GB 的内存全部用于实际应用。
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph VM ["传统虚拟机方案 ❌"]
|
||||
direction TB
|
||||
Server1["物理服务器 (64GB 内存)"]
|
||||
subgraph VMs ["可用应用内存: 约 18GB"]
|
||||
direction LR
|
||||
VM1["VM 1: 应用 1<br/>(含 2GB OS)"]
|
||||
VM2["VM 2: 应用 2<br/>(含 2GB OS)"]
|
||||
VM3["VM 3: 应用 3<br/>(含 2GB OS)"]
|
||||
end
|
||||
Server1 --- VMs
|
||||
end
|
||||
|
||||
subgraph Docker ["Docker 方案 ✅"]
|
||||
direction TB
|
||||
Server2["物理服务器 (64GB 内存)<br/>含约 4GB OS及引擎配置"]
|
||||
subgraph Containers ["可用应用内存: 约 60GB"]
|
||||
direction LR
|
||||
C1["容器 1: 应用 1<br/>(按需分配)"]
|
||||
C2["容器 2: 应用 2<br/>(按需分配)"]
|
||||
C3["容器 3: 应用 3<br/>(按需分配)"]
|
||||
end
|
||||
Server2 --- Containers
|
||||
end
|
||||
```
|
||||
|
||||
#### 4. 持续交付和部署
|
||||
|
||||
Docker 完美契合 DevOps 的工作流程:
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["代码提交<br/>(Git push)"] --> B["自动构建镜像<br/>(docker build)"]
|
||||
B --> C["自动测试<br/>(容器内运行测试)"]
|
||||
C --> D["自动部署<br/>(容器滚动更新)"]
|
||||
```
|
||||
|
||||
使用 [Dockerfile](../04_image/4.5_build.md) 定义镜像构建过程,使得:
|
||||
|
||||
- 构建过程 **可重复、可追溯**
|
||||
- 任何人都能从代码重建完全相同的镜像
|
||||
- 配合 [GitHub Actions](../21_case_devops/21.2_github_actions.md) 等 CI 系统实现自动化
|
||||
|
||||
#### 5. 轻松迁移
|
||||
|
||||
Docker 可以在几乎任何平台上运行:
|
||||
|
||||
- ✅ 本地开发机 (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 与传统虚拟机的对比总结
|
||||
|
||||
下表对比了容器技术与传统虚拟机的区别:
|
||||
|
||||
| 特性 | Docker 容器 | 传统虚拟机 |
|
||||
|:------|:-----------|:-----------|
|
||||
| 启动速度 | 秒级 | 分钟级 |
|
||||
| 磁盘占用 | MB 级别 | GB 级别 |
|
||||
| 性能 | 接近原生 | 有 5-20% 损耗 |
|
||||
| 单机支持量 | 上千个容器 | 几十个虚拟机 |
|
||||
| 隔离性 | 进程级别 | 完全隔离 |
|
||||
| 最佳场景 | 微服务、CI/CD、开发环境 | 多租户、高安全需求 |
|
||||
@@ -1,27 +0,0 @@
|
||||
# 第一章 Docker 简介
|
||||
|
||||
本章将带领你进入 **Docker** 的世界。
|
||||
|
||||
## 本章内容
|
||||
|
||||
* [快速上手](1.1_quickstart.md)
|
||||
* 通过一个简单的 Web 应用例子,带你快速体验 Docker 的核心流程:构建镜像、运行容器。
|
||||
|
||||
* [什么是 Docker](1.2_what.md)
|
||||
* 介绍 Docker 的起源、发展历程以及其背后的核心技术 (Cgroups,Namespaces,UnionFS)。
|
||||
* 了解 Docker 是如何改变软件交付方式的。
|
||||
|
||||
* [为什么要用 Docker](1.3_why.md)
|
||||
* 对比传统虚拟机技术,阐述 Docker 在启动速度、资源利用率、交付效率等方面的巨大优势。
|
||||
* 探讨 Docker 在 DevOps、微服务架构中的关键作用。
|
||||
|
||||
|
||||
## 学习目标
|
||||
|
||||
通过本章的学习,你将能够:
|
||||
|
||||
1. 理解 Docker 的核心概念与架构。
|
||||
2. 明白 Docker 解决了现代软件开发与运维中的哪些痛点。
|
||||
3. 建立起对容器技术的初步认知,为后续的实战操作打下基础。
|
||||
|
||||
好吧,让我们带着问题开始这神奇之旅。
|
||||
@@ -1,8 +0,0 @@
|
||||
## 本章小结
|
||||
|
||||
- Docker 是一种轻量级虚拟化技术,核心价值是 **环境一致性**
|
||||
- 与虚拟机相比,Docker 更轻量、更快速、资源利用率更高
|
||||
- Docker 基于 Linux 内核的 Namespace、Cgroups 和 Union FS 技术
|
||||
- Docker 推动了容器技术的标准化 (OCI) 和生态发展
|
||||
|
||||
Docker 的核心价值可以用一句话概括:**让应用的开发、测试、部署保持一致,同时极大提高资源利用效率。** 笔者认为,对于现代软件开发者来说,Docker 已经不是 “要不要学” 的问题,而是 **必备技能**。无论你是前端、后端、运维还是全栈开发者,掌握 Docker 都能让你的工作更高效。
|
||||
@@ -1,214 +0,0 @@
|
||||
## 2.1 镜像
|
||||
|
||||
Docker 镜像作为容器运行的基石,其设计理念和实现机制至关重要。本节将深入探讨镜像的本质、与操作系统的关系、内容构成以及核心的分层存储机制。
|
||||
|
||||
### 2.1.1 一句话理解镜像
|
||||
|
||||
> **Docker 镜像是一个只读的模板,包含了运行应用所需的一切:代码、运行时、库、环境变量和配置文件。** 如果用一个类比:**镜像就像是一张光盘或 ISO 文件**。你可以用同一张光盘在不同电脑上安装系统,而光盘本身不会被修改。同样,一个镜像可以创建多个容器,而镜像本身保持不变。
|
||||
|
||||
### 2.1.2 镜像与操作系统的关系
|
||||
|
||||
我们都知道,操作系统分为 **内核** 和 **用户空间**:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph UserSpace ["用户空间"]
|
||||
direction TB
|
||||
App["应用程序、工具、库、配置文件...<br/>(这部分被打包成 Docker 镜像)"]
|
||||
end
|
||||
|
||||
subgraph KernelSpace ["Linux 内核"]
|
||||
direction TB
|
||||
Kernel["容器共享宿主机的内核"]
|
||||
end
|
||||
|
||||
UserSpace --- KernelSpace
|
||||
```
|
||||
|
||||
对于 Linux 而言,内核启动后会挂载 `root` 文件系统来提供用户空间支持。**Docker 镜像** 本质上就是一个 `root` 文件系统。
|
||||
|
||||
例如,官方镜像 `ubuntu:24.04` 包含了一套完整的 Ubuntu 24.04 最小系统的 root 文件系统——但 **不包含 Linux 内核** (因为容器共享宿主机的内核)。
|
||||
|
||||
### 2.1.3 镜像包含什么?
|
||||
|
||||
Docker 镜像是一个特殊的文件系统,包含:
|
||||
|
||||
| 内容类型 | 示例 |
|
||||
|---------|------|
|
||||
| **程序文件** | 应用二进制文件、Python/Node 解释器 |
|
||||
| **库文件** | 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` |
|
||||
@@ -1,239 +0,0 @@
|
||||
## 2.2 容器
|
||||
|
||||
容器是 Docker 技术的核心,是应用实际运行的载体。本节将从容器的本质、与虚拟机的区别、存储层机制以及生命周期管理等方面,全面解析 Docker 容器。
|
||||
|
||||
### 2.2.1 一句话理解容器
|
||||
|
||||
> **容器是镜像的运行实例。如果把镜像比作程序,那么容器就是进程。** 用面向对象编程的术语来说:**镜像是类 (Class),容器是对象 (Instance)**。
|
||||
|
||||
- 一个镜像可以创建多个容器
|
||||
- 每个容器相互独立,互不影响
|
||||
- 容器可以被创建、启动、停止、删除、暂停
|
||||
|
||||
### 2.2.2 容器的本质
|
||||
|
||||
> 💡 笔者认为,理解这一点是理解 Docker 的关键:**容器的本质是一个特殊的进程**。
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph NormalProcess ["普通进程"]
|
||||
direction TB
|
||||
N1["• 共享系统资源<br/>• 共享网络<br/>• 共享文件系统"]
|
||||
end
|
||||
|
||||
subgraph ContainerProcess ["容器进程 (运行在宿主机内核上)"]
|
||||
direction TB
|
||||
C1["• 独立进程空间<br/>• 独立网络环境<br/>• 独立文件系统<br/>• 独立用户空间"]
|
||||
end
|
||||
```
|
||||
|
||||
这种隔离是通过 Linux 内核的 **Namespace** 技术实现的。具体表现为:
|
||||
- **进程空间**:容器看不到宿主机上的其他进程。
|
||||
- **网络**:容器拥有独立的 IP、端口等网络资源。
|
||||
- **文件系统**:容器拥有独立的 root 目录。
|
||||
- **用户**:容器内的 root 用户不等于宿主机的 root 用户。
|
||||
|
||||
### 2.2.3 容器 vs 虚拟机:核心区别
|
||||
|
||||
很多初学者会混淆容器和虚拟机。笔者用一张图来说明:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph VM ["虚拟机 (每个 VM 运行完整 OS)"]
|
||||
direction TB
|
||||
subgraph VMApp ["应用层"]
|
||||
VA[App A] & VB[App B]
|
||||
end
|
||||
subgraph VMGuest ["Guest OS (完整系统)"]
|
||||
G1[Guest OS] & G2[Guest OS]
|
||||
end
|
||||
V[Hypervisor]
|
||||
VMH[Host OS]
|
||||
VMHW[Hardware]
|
||||
VMApp --> VMGuest --> V --> VMH --> VMHW
|
||||
end
|
||||
|
||||
subgraph Container ["容器 (所有容器共享宿主机内核)"]
|
||||
direction TB
|
||||
subgraph CApp ["应用层"]
|
||||
CA[App A] & CB[App B]
|
||||
end
|
||||
subgraph CContainer ["隔离层"]
|
||||
CC1[Container 仅应用] & CC2[Container 仅应用]
|
||||
end
|
||||
CE[Docker Engine]
|
||||
CH[Host OS]
|
||||
CHW[Hardware]
|
||||
CApp --> CContainer --> CE --> CH --> CHW
|
||||
end
|
||||
```
|
||||
|
||||
| 特性 | 容器 | 虚拟机 |
|
||||
|------|------|--------|
|
||||
| **隔离级别** | 进程级 (Namespace)| 硬件级 (Hypervisor)|
|
||||
| **启动时间** | 秒级 (甚至毫秒)| 分钟级 |
|
||||
| **资源占用** | MB 级别 | GB 级别 |
|
||||
| **性能损耗** | 几乎为零 | 5-20% |
|
||||
| **内核** | 共享宿主机内核 | 各自独立内核 |
|
||||
|
||||
### 2.2.4 容器的存储层
|
||||
|
||||
理解容器的存储层机制对于数据的持久化和镜像的优化至关重要。本节将介绍容器的可写层以及 Copy-on-Write 机制。
|
||||
|
||||
#### 镜像层 + 容器层
|
||||
|
||||
当容器运行时,Docker 会在镜像的只读层之上创建一个 **可写层** (容器存储层):
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
ContainerLayer["容器存储层(可读写)<br/>容器运行时创建,记录文件变化"]
|
||||
ImageLayerN["镜像第 N 层(只读)"]
|
||||
ImageLayerN1["镜像第 N-1 层(只读)"]
|
||||
Dots["..."]
|
||||
ImageLayer1["镜像第 1 层(只读)<br/>基础镜像层"]
|
||||
|
||||
ContainerLayer --> ImageLayerN --> ImageLayerN1 --> Dots --> ImageLayer1
|
||||
```
|
||||
|
||||
#### Copy-on-Write (写时复制)
|
||||
|
||||
当容器需要修改镜像层中的文件时:
|
||||
|
||||
1. Docker 将该文件 **复制** 到容器存储层
|
||||
2. 在容器层中进行修改
|
||||
3. 原始镜像层保持不变
|
||||
|
||||
```bash
|
||||
读取文件:直接从镜像层读取(共享,高效)
|
||||
修改文件:复制到容器层,然后修改(只有这个容器能看到修改)
|
||||
```
|
||||
|
||||
#### ⚠️ 容器存储层的生命周期
|
||||
|
||||
> **笔者特别强调**:这是新手最容易踩的坑!**容器存储层与容器生命周期绑定。容器删除,数据就没了!**
|
||||
|
||||
```bash
|
||||
## 创建容器,写入数据
|
||||
|
||||
$ docker run -it ubuntu bash
|
||||
root@abc123:/# echo "important data" > /data.txt
|
||||
root@abc123:/# exit
|
||||
|
||||
## 删除容器
|
||||
|
||||
$ docker rm abc123
|
||||
|
||||
## 数据丢了!没有任何办法恢复!
|
||||
|
||||
```
|
||||
|
||||
#### 正确的数据持久化方式
|
||||
|
||||
按照 Docker 最佳实践,容器存储层应该保持 **无状态**。需要持久化的数据应该使用:
|
||||
|
||||
| 方式 | 说明 | 适用场景 |
|
||||
|------|------|---------|
|
||||
| **[数据卷 (Volume) ](../08_data/8.1_volume.md)** | Docker 管理的存储 | 数据库、应用数据 |
|
||||
| **[绑定挂载 (Bind Mount) ](../08_data/8.2_bind-mounts.md)** | 挂载宿主机目录 | 开发时共享代码 |
|
||||
|
||||
```bash
|
||||
## 使用数据卷(推荐)
|
||||
|
||||
$ docker run -v mydata:/var/lib/mysql mysql
|
||||
|
||||
## 使用绑定挂载
|
||||
|
||||
$ docker run -v /host/path:/container/path nginx
|
||||
```
|
||||
|
||||
这些位置的读写 **会跳过容器存储层**,直接写入宿主机,性能更好,也不会随容器删除而丢失。
|
||||
|
||||
### 2.2.5 容器的生命周期
|
||||
|
||||
掌握容器的生命周期对于管理和调试 Docker 应用非常重要。如图 2-1 所示,容器会经历从创建到删除的完整状态流转。
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
direction TB
|
||||
[*] --> Created : docker create
|
||||
Created --> Running : docker start
|
||||
Running --> Stopped : docker stop
|
||||
Running --> Paused : docker pause
|
||||
Paused --> Running : docker unpause
|
||||
|
||||
Created --> Deleted : docker rm
|
||||
Stopped --> Deleted : docker rm
|
||||
Paused --> Deleted : docker rm
|
||||
|
||||
Deleted --> [*]
|
||||
```
|
||||
|
||||
图 2-1 容器生命周期状态流转图
|
||||
|
||||
#### 常用生命周期命令
|
||||
|
||||
```bash
|
||||
## 创建并启动容器(最常用)
|
||||
|
||||
$ docker run nginx
|
||||
|
||||
## 分步操作
|
||||
|
||||
$ docker create nginx # 创建容器(不启动)
|
||||
$ docker start abc123 # 启动容器
|
||||
|
||||
## 停止容器
|
||||
|
||||
$ docker stop abc123 # 优雅停止(发送 SIGTERM,等待后发送 SIGKILL)
|
||||
$ docker kill abc123 # 强制停止(直接发送 SIGKILL)
|
||||
|
||||
## 暂停/恢复(不常用,但有时有用)
|
||||
|
||||
$ docker pause abc123 # 暂停容器内所有进程
|
||||
$ docker unpause abc123 # 恢复
|
||||
|
||||
## 删除容器
|
||||
|
||||
$ docker rm abc123 # 删除已停止的容器
|
||||
$ docker rm -f abc123 # 强制删除运行中的容器
|
||||
```
|
||||
|
||||
### 2.2.6 容器与进程的关系
|
||||
|
||||
> **核心概念**:容器的生命周期 = 主进程 (PID 1) 的生命周期
|
||||
|
||||
```bash
|
||||
## 主进程运行,容器运行
|
||||
|
||||
## 主进程退出,容器停止
|
||||
|
||||
```
|
||||
|
||||
这就是为什么:
|
||||
|
||||
```bash
|
||||
## 这个容器会立即退出(bash 没有输入就退出了)
|
||||
|
||||
$ docker run ubuntu
|
||||
|
||||
## 这个容器会持续运行(nginx 作为守护进程持续运行)
|
||||
|
||||
$ docker run nginx
|
||||
```
|
||||
|
||||
详细解释请参考[后台运行](../05_container/5.2_daemon.md)章节。
|
||||
|
||||
### 2.2.7 容器的隔离性
|
||||
|
||||
Docker 容器通过以下 Namespace 实现隔离:
|
||||
|
||||
| Namespace | 隔离内容 | 效果 |
|
||||
|-----------|---------|------|
|
||||
| **PID** | 进程 ID | 容器内 PID 1 是应用进程,看不到宿主机其他进程 |
|
||||
| **NET** | 网络 | 独立的网络栈、IP 地址、端口 |
|
||||
| **MNT** | 文件系统 | 独立的根目录和挂载点 |
|
||||
| **UTS** | 主机名 | 独立的主机名和域名 |
|
||||
| **IPC** | 进程间通信 | 独立的信号量、消息队列 |
|
||||
| **USER** | 用户 | 独立的用户和组 ID |
|
||||
|
||||
> 想深入了解?请阅读[底层实现 - 命名空间](../12_implementation/12.2_namespace.md)。
|
||||
@@ -1,288 +0,0 @@
|
||||
## 2.3 仓库
|
||||
|
||||
Docker Registry 是镜像分发和管理的核心组件。本节将介绍 Registry 的基本概念、公共和私有服务的选择,以及镜像的安全管理。
|
||||
|
||||
### 2.3.1 一句话理解 Registry
|
||||
|
||||
> **Docker Registry 是存储和分发 Docker 镜像的服务,类似于代码的 GitHub 或包管理的 npm。**
|
||||
|
||||
镜像构建完成后,可以在当前机器上运行。但如果需要在其他服务器上使用这个镜像,就需要一个集中的存储和分发服务——这就是 Docker Registry。
|
||||
|
||||
### 2.3.2 核心概念
|
||||
|
||||
要熟练使用 Docker Registry,首先需要理清它与仓库 (Repository)、标签 (Tag) 之间的关系。
|
||||
|
||||
#### Registry、仓库、标签的关系
|
||||
|
||||
Docker Registry 中可以包含多个 Repository,每个 Repository 可以包含多个 Tag。如图 2-2 所示,它们之间具有清晰的层级关系。
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph Registry ["Docker Registry(如 Docker Hub)"]
|
||||
direction TB
|
||||
subgraph RepoNginx ["Repository(仓库): nginx"]
|
||||
direction LR
|
||||
N1(":latest (tag)")
|
||||
N2(":1.25 (tag)")
|
||||
N3(":1.24 (tag)")
|
||||
N4(":alpine (tag)")
|
||||
N5("...")
|
||||
N1 ~~~ N2 ~~~ N3 ~~~ N4 ~~~ N5
|
||||
end
|
||||
subgraph RepoMysql ["Repository(仓库): mysql"]
|
||||
direction LR
|
||||
M1(":latest")
|
||||
M2(":8.0")
|
||||
M3(":5.7")
|
||||
M4("...")
|
||||
M1 ~~~ M2 ~~~ M3 ~~~ M4
|
||||
end
|
||||
RepoNginx ~~~ RepoMysql
|
||||
end
|
||||
```
|
||||
|
||||
图 2-2 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 +0,0 @@
|
||||
# 第二章 基本概念
|
||||
|
||||
**Docker** 包括三个基本概念:
|
||||
|
||||
* **镜像** (`Image`):Docker 镜像是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数 (如匿名卷、环境变量、用户等)。镜像不包含任何动态数据,其内容在构建之后也不会被改变。
|
||||
* **容器** (`Container`):镜像 (`Image`) 和容器 (`Container`) 的关系,就像是面向对象程序设计中的 `类` 和 `实例` 一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。
|
||||
* **仓库** (`Repository`):镜像构建完成后,可以很容易的在当前宿主机上运行,但是,如果需要在其它服务器上使用这个镜像,我们就需要一个集中的存储、分发镜像的服务,Docker Registry 就是这样的服务。
|
||||
|
||||
理解了这三个概念,就理解了 **Docker** 的整个生命周期。
|
||||
@@ -1,51 +0,0 @@
|
||||
## 本章小结
|
||||
|
||||
| 概念 | 要点 |
|
||||
|------|------|
|
||||
| **镜像是什么** | 只读的应用模板,包含运行所需的一切 |
|
||||
| **分层存储** | 多层叠加,共享基础层,节省空间 |
|
||||
| **只读特性** | 构建后不可修改,保证一致性 |
|
||||
| **层的陷阱** | 删除操作只是标记,不减小体积 |
|
||||
|
||||
理解了镜像,接下来让我们学习[容器](2.2_container.md)——镜像的运行实例。
|
||||
|
||||
### 2.4.1 延伸阅读
|
||||
|
||||
- [获取镜像](../04_image/4.1_pull.md):从 Registry 下载镜像
|
||||
- [使用 Dockerfile 定制镜像](../04_image/4.5_build.md):创建自己的镜像
|
||||
- [Dockerfile 最佳实践](../appendix/best_practices.md):构建高质量镜像的技巧
|
||||
- [底层实现 - 联合文件系统](../12_implementation/12.4_ufs.md):深入理解分层存储的技术原理
|
||||
|
||||
| 概念 | 要点 |
|
||||
|------|------|
|
||||
| **容器是什么** | 镜像的运行实例,本质是隔离的进程 |
|
||||
| **容器 vs 虚拟机** | 共享内核,更轻量,但隔离性较弱 |
|
||||
| **存储层** | 可写层随容器删除而消失 |
|
||||
| **数据持久化** | 使用 Volume 或 Bind Mount |
|
||||
| **生命周期** | 与主进程 (PID 1) 绑定 |
|
||||
|
||||
理解了镜像和容器,接下来让我们学习[仓库](2.3_repository.md)——存储和分发镜像的服务。
|
||||
|
||||
### 2.4.2 延伸阅读
|
||||
|
||||
- [启动容器](../05_container/5.1_run.md):详细的容器启动选项
|
||||
- [后台运行](../05_container/5.2_daemon.md):理解容器为什么会 “立即退出”
|
||||
- [进入容器](../05_container/5.4_attach_exec.md):如何操作运行中的容器
|
||||
- [数据管理](../08_data/README.md):Volume 和数据持久化详解
|
||||
|
||||
| 概念 | 要点 |
|
||||
|------|------|
|
||||
| **Registry** | 存储和分发镜像的服务 |
|
||||
| **仓库 (Repository)** | 同一软件的镜像集合 |
|
||||
| **标签 (Tag)** | 版本标识,默认为 latest |
|
||||
| **Docker Hub** | 默认的公共 Registry |
|
||||
| **私有 Registry** | 企业内部使用,推荐 Harbor |
|
||||
|
||||
现在你已经了解了 Docker 的三个核心概念:[镜像](2.1_image.md)、[容器](2.2_container.md)和仓库。接下来,让我们开始[安装 Docker](../03_install/README.md),动手实践!
|
||||
|
||||
### 2.4.3 延伸阅读
|
||||
|
||||
- [Docker Hub](../06_repository/6.1_dockerhub.md):Docker Hub 的详细使用
|
||||
- [私有仓库](../06_repository/6.2_registry.md):搭建私有 Registry
|
||||
- [私有仓库高级配置](../06_repository/6.3_registry_auth.md):认证、TLS 配置
|
||||
- [镜像加速器](../03_install/3.9_mirror.md):配置镜像加速
|
||||
@@ -1,19 +0,0 @@
|
||||
## 3.10 开启实验特性
|
||||
|
||||
一些 docker 命令或功能仅当 **实验特性** 开启时才能使用,请按照以下方法进行设置。
|
||||
|
||||
### 3.10.1 Docker CLI 的实验特性
|
||||
|
||||
CLI 的实验特性通常包含仍在开发中的新功能。幸运的是,在较新版本中这些特性已经更加易用。
|
||||
|
||||
从 `v20.10` 版本开始,Docker CLI 所有实验特性的命令均默认开启,无需再进行配置或设置系统环境变量。
|
||||
|
||||
### 3.10.2 开启 dockerd 的实验特性
|
||||
|
||||
编辑 `/etc/docker/daemon.json`,新增如下条目
|
||||
|
||||
```json
|
||||
{
|
||||
"experimental": true
|
||||
}
|
||||
```
|
||||
@@ -1,198 +0,0 @@
|
||||
## 3.5 Raspberry Pi
|
||||
|
||||
树莓派等 ARM 架构设备在物联网和边缘计算领域应用广泛。本节介绍如何在树莓派上安装 Docker。
|
||||
|
||||
>警告:切勿在没有配置 Docker APT 源的情况下直接使用 apt 命令安装 Docker。
|
||||
|
||||
### 3.5.1 系统要求
|
||||
|
||||
Docker 对 ARM 架构有着良好的支持。
|
||||
|
||||
Docker 不仅支持 `x86_64` 架构的计算机,同时也支持 `ARM` 架构的计算机,本小节内容以树莓派单片电脑为例讲解 `ARM` 架构安装 Docker。
|
||||
|
||||
Docker 支持以下版本的 [Raspberry Pi OS](https://www.raspberrypi.org/software/operating-systems/) 操作系统:
|
||||
|
||||
* Raspberry Pi OS Trixie
|
||||
* Raspberry Pi OS Bookworm
|
||||
* Raspberry Pi OS Bullseye
|
||||
|
||||
*注:*`Raspberry Pi OS` 由树莓派的开发与维护机构[树莓派基金会](https://www.raspberrypi.org/)官方支持,并推荐用作树莓派的首选系统,其基于 `Debian`。
|
||||
|
||||
### 3.5.2 使用 APT 安装
|
||||
|
||||
推荐使用 APT 包管理器进行安装,以确保版本的稳定性和安全性。
|
||||
|
||||
由于 apt 源使用 HTTPS 以确保软件下载过程中不被篡改。因此,我们首先需要添加使用 HTTPS 传输的软件包以及 CA 证书。
|
||||
|
||||
```bash
|
||||
$ sudo apt-get update
|
||||
|
||||
$ sudo apt-get install \
|
||||
apt-transport-https \
|
||||
ca-certificates \
|
||||
curl \
|
||||
gnupg \
|
||||
lsb-release
|
||||
```
|
||||
|
||||
鉴于国内网络问题,强烈建议使用国内源,官方源请在注释中查看。
|
||||
|
||||
为了确认所下载软件包的合法性,需要添加软件源的 GPG 密钥。
|
||||
|
||||
```bash
|
||||
$ sudo install -m 0755 -d /etc/apt/keyrings
|
||||
$ curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/raspbian/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
|
||||
$ sudo chmod a+r /etc/apt/keyrings/docker.gpg
|
||||
|
||||
|
||||
## 官方源
|
||||
|
||||
## $ sudo install -m 0755 -d /etc/apt/keyrings
|
||||
|
||||
## $ curl -fsSL https://download.docker.com/linux/raspbian/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
|
||||
|
||||
## $ sudo chmod a+r /etc/apt/keyrings/docker.gpg
|
||||
|
||||
```
|
||||
|
||||
然后,我们需要向 `sources.list` 中添加 Docker 软件源:
|
||||
|
||||
```bash
|
||||
$ echo \
|
||||
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://mirrors.aliyun.com/docker-ce/linux/raspbian \
|
||||
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||
|
||||
|
||||
## 官方源
|
||||
|
||||
## $ echo \
|
||||
|
||||
## "deb [arch=$ signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/raspbian \
|
||||
|
||||
## $ stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||
|
||||
```
|
||||
|
||||
>以上命令会添加稳定版本的 Docker APT 源,如果需要测试版本的 Docker 请将 stable 改为 test。
|
||||
|
||||
#### 报错解决办法
|
||||
|
||||
在 `Raspberry Pi OS Bullseye/Bookworm` 中,如果你使用 `add-apt-repository` 添加源,可能会出现如下报错 (推荐改用上面的 `tee` 方式来写入 `/etc/apt/sources.list.d/docker.list`,可避免此问题):
|
||||
|
||||
```bash
|
||||
Traceback (most recent call last):
|
||||
File "/usr/bin/add-apt-repository", line 95, in <module>
|
||||
sp = SoftwareProperties(options=options)
|
||||
File "/usr/lib/python3/dist-packages/softwareproperties/SoftwareProperties.py", line 109, in __init__
|
||||
self.reload_sourceslist()
|
||||
File "/usr/lib/python3/dist-packages/softwareproperties/SoftwareProperties.py", line 599, in reload_sourceslist
|
||||
self.distro.get_sources(self.sourceslist)
|
||||
File "/usr/lib/python3/dist-packages/aptsources/distro.py", line 91, in get_sources
|
||||
raise NoDistroTemplateException(
|
||||
aptsources.distro.NoDistroTemplateException: Error: could not find a distribution template for Raspbian/bullseye
|
||||
```
|
||||
|
||||
通过以下命令手动添加镜像源到 `/etc/apt/sources.list` 文件中即可解决:
|
||||
|
||||
```bash
|
||||
$ echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://mirrors.aliyun.com/docker-ce/linux/raspbian $(lsb_release -cs) stable" | sudo tee -a /etc/apt/sources.list
|
||||
|
||||
|
||||
## 官方源
|
||||
|
||||
## $ echo "deb [arch=$ signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/raspbian $ stable" | sudo tee -a /etc/apt/sources.list
|
||||
|
||||
```
|
||||
|
||||
#### 安装 Docker
|
||||
|
||||
更新 apt 软件包缓存,并安装 `docker-ce`。
|
||||
|
||||
```bash
|
||||
$ sudo apt-get update
|
||||
|
||||
$ sudo apt-get install docker-ce
|
||||
```
|
||||
|
||||
### 3.5.3 使用脚本自动安装
|
||||
|
||||
在测试或开发环境中 Docker 官方为了简化安装流程,提供了一套便捷的安装脚本,Raspberry Pi OS 系统上可以使用这套脚本安装,另外可以通过 `--mirror` 选项使用国内源进行安装:
|
||||
|
||||
> 若你想安装测试版的 Docker,请从 test.docker.com 获取脚本
|
||||
|
||||
```bash
|
||||
## $ curl -fsSL test.docker.com -o get-docker.sh
|
||||
|
||||
$ curl -fsSL get.docker.com -o get-docker.sh
|
||||
$ sudo sh get-docker.sh --mirror Aliyun
|
||||
## $ sudo sh get-docker.sh --mirror AzureChinaCloud
|
||||
|
||||
```
|
||||
|
||||
执行这个命令后,脚本就会自动的将一切准备工作做好,并且把 Docker 的稳定 (stable) 版本安装在系统中。
|
||||
|
||||
### 3.5.4 启动 Docker
|
||||
|
||||
```bash
|
||||
$ sudo systemctl enable docker
|
||||
$ sudo systemctl start docker
|
||||
```
|
||||
|
||||
### 3.5.5 建立 docker 用户组
|
||||
|
||||
默认情况下,`docker` 命令会使用 [Unix socket](https://en.wikipedia.org/wiki/Unix_domain_socket) 与 Docker 引擎通讯。而只有 `root` 用户和 `docker` 组的用户才可以访问 Docker 引擎的 Unix socket。出于安全考虑,一般 Linux 系统上不会直接使用 `root` 用户。因此,更好地做法是将需要使用 `docker` 的用户加入 `docker` 用户组。
|
||||
|
||||
建立 `docker` 组:
|
||||
|
||||
```bash
|
||||
$ sudo groupadd docker
|
||||
```
|
||||
|
||||
将当前用户加入 `docker` 组:
|
||||
|
||||
```bash
|
||||
$ sudo usermod -aG docker $USER
|
||||
```
|
||||
|
||||
退出当前终端并重新登录,进行如下测试。
|
||||
|
||||
### 3.5.6 测试 Docker 是否安装正确
|
||||
|
||||
```bash
|
||||
$ docker run --rm hello-world
|
||||
|
||||
Unable to find image 'hello-world:latest' locally
|
||||
latest: Pulling from library/hello-world
|
||||
4ee5c797bcd7: Pull complete
|
||||
Digest: sha256:308866a43596e83578c7dfa15e27a73011bdd402185a84c5cd7f32a88b501a24
|
||||
Status: Downloaded newer image for hello-world:latest
|
||||
|
||||
Hello from Docker!
|
||||
This message shows that your installation appears to be working correctly.
|
||||
|
||||
To generate this message, Docker took the following steps:
|
||||
1. The Docker client contacted the Docker daemon.
|
||||
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
|
||||
(arm32v7)
|
||||
3. The Docker daemon created a new container from that image which runs the
|
||||
executable that produces the output you are currently reading.
|
||||
4. The Docker daemon streamed that output to the Docker client, which sent it
|
||||
to your terminal.
|
||||
|
||||
To try something more ambitious, you can run an Ubuntu container with:
|
||||
$ docker run -it ubuntu bash
|
||||
|
||||
Share images, automate workflows, and more with a free Docker ID:
|
||||
https://hub.docker.com/
|
||||
|
||||
For more examples and ideas, visit:
|
||||
https://docs.docker.com/get-started/
|
||||
```
|
||||
|
||||
若能正常输出以上信息,则说明安装成功。
|
||||
|
||||
*注意:*ARM 平台不能使用 `x86` 镜像,查看 Raspberry Pi OS 可使用镜像请访问 [arm32v7](https://hub.docker.com/u/arm32v7/) 或者 [arm64v8](https://hub.docker.com/u/arm64v8/)。
|
||||
|
||||
### 3.5.7 镜像加速
|
||||
|
||||
如果在使用过程中发现拉取 Docker 镜像十分缓慢,可以配置 Docker [国内镜像加速](3.9_mirror.md)。
|
||||
@@ -1,76 +0,0 @@
|
||||
## 3.7 macOS
|
||||
|
||||
### 3.7.1 系统要求
|
||||
|
||||
[Docker Desktop for Mac](https://docs.docker.com/docker-for-mac/) 要求系统最低为 macOS Sonora 14.0 或更高版本,建议升级到最新版本的 macOS。
|
||||
|
||||
### 3.7.2 安装
|
||||
|
||||
> [!WARNING]
|
||||
> **商业许可限制**:自 2021 年起,Docker Desktop 对微型企业(少于 250 名员工且年收入少于 1000 万美元)、个人使用、教育和非商业开源项目仍然免费。对于其他商业用途,需要付费订阅。企业用户请注意合规风险,或考虑使用开源替代方案。
|
||||
|
||||
Docker Desktop 为 Mac 用户提供了无缝的 Docker 体验。你可以选择使用 Homebrew 或手动下载安装包进行安装。
|
||||
|
||||
#### 使用 Homebrew 安装
|
||||
|
||||
[Homebrew](https://brew.sh/) 的 [Cask](https://github.com/Homebrew/homebrew-cask) 已经支持 Docker Desktop for Mac,因此可以很方便的使用 Homebrew Cask 来进行安装:
|
||||
|
||||
```bash
|
||||
$ brew install --cask docker
|
||||
```
|
||||
|
||||
#### 手动下载安装
|
||||
|
||||
如果需要手动下载,请点击以下[链接](https://desktop.docker.com/mac/main/amd64/Docker.dmg)下载 Docker Desktop for Mac。
|
||||
|
||||
> 如果你的电脑搭载的是 Apple Silicon 芯片 (`arm64` 架构),请点击以下[链接](https://desktop.docker.com/mac/main/arm64/Docker.dmg)下载 Docker Desktop for Mac。你可以在[官方文档](https://docs.docker.com/docker-for-mac/apple-silicon/)查阅已知的问题。
|
||||
|
||||
如同 macOS 其它软件一样,安装也非常简单,双击下载的 `.dmg` 文件,然后将那只叫 [Moby](https://www.docker.com/blog/call-me-moby-dock/) 的鲸鱼图标拖拽到 `Application` 文件夹即可 (其间需要输入用户密码)。
|
||||
|
||||

|
||||
|
||||
### 3.7.3 运行
|
||||
|
||||
从应用中找到 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
|
||||
```
|
||||
|
||||
### 3.7.4 镜像加速
|
||||
|
||||
如果在使用过程中发现拉取 Docker 镜像十分缓慢,可以配置 Docker [国内镜像加速](3.9_mirror.md)。
|
||||
|
||||
### 3.7.5 参考链接
|
||||
|
||||
* [官方文档](https://docs.docker.com/desktop/setup/install/mac-install/)
|
||||
@@ -1,51 +0,0 @@
|
||||
## 3.8 Windows 10/11
|
||||
|
||||
在 Windows 平台上,Docker Desktop 提供了完整的 Docker 开发环境。本节介绍在 Windows 10/11 上的安装和配置。
|
||||
|
||||
### 3.8.1 系统要求
|
||||
|
||||
[Docker Desktop for Windows](https://docs.docker.com/desktop/setup/install/windows-install/) 支持 64 位版本的 Windows 11 或 Windows 10 (需开启 Hyper-V),推荐使用 Windows 11。
|
||||
|
||||
### 3.8.2 安装
|
||||
|
||||
> [!WARNING]
|
||||
> **商业许可限制**:自 2021 年起,Docker Desktop 对微型企业(少于 250 名员工且年收入少于 1000 万美元)、个人使用、教育和非商业开源项目仍然免费。对于其他商业用途,需要付费订阅。企业用户请注意合规风险,或考虑使用开源替代方案。
|
||||
|
||||
**手动下载安装**
|
||||
|
||||
点击以下[链接](https://desktop.docker.com/win/main/amd64/Docker%20Desktop%20Installer.exe)下载 Docker Desktop for Windows。
|
||||
|
||||
下载好之后双击 `Docker Desktop Installer.exe` 开始安装。
|
||||
|
||||
**使用** [**winget**](https://docs.microsoft.com/zh-cn/windows/package-manager/) **安装**
|
||||
|
||||
```powershell
|
||||
$ winget install Docker.DockerDesktop
|
||||
```
|
||||
|
||||
### 3.8.3 在 WSL2 运行 Docker
|
||||
|
||||
若你的 Windows 版本为 Windows 10 专业版或家庭版 v1903 及以上版本可以使用 WSL2 运行 Docker,具体请查看 [Docker Desktop WSL 2 backend](https://docs.docker.com/docker-for-windows/wsl/)。
|
||||
|
||||
### 3.8.4 运行
|
||||
|
||||
在 Windows 搜索栏输入 **Docker** 点击 **Docker Desktop** 开始运行。
|
||||
|
||||

|
||||
|
||||
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,113 +0,0 @@
|
||||
## 3.9 镜像加速器
|
||||
|
||||
国内从 Docker Hub 拉取镜像有时会遇到困难,此时可以配置镜像加速器。
|
||||
|
||||
> ⚠️ **注意**:镜像加速器的可用性经常变化。配置前请先访问 [docker-practice/docker-registry-cn-mirror-test](https://github.com/docker-practice/docker-registry-cn-mirror-test/actions) 查看各镜像站的实时状态。
|
||||
|
||||
### 3.9.1 推荐配置方案
|
||||
|
||||
针对不同的使用场景,我们推荐以下几种镜像加速配置方案,以确保最佳的拉取速度。
|
||||
|
||||
1. **云服务器用户**:优先使用所在云平台提供的内部加速器 (见本页末尾)
|
||||
2. **本地开发用户**:使用阿里云个人加速器或其他可用的公共加速器
|
||||
3. **代理方案**:如有条件,可配置 HTTP 代理直接访问 Docker Hub
|
||||
|
||||
* [阿里云加速器](https://cr.console.aliyun.com/cn-hangzhou/instances) (需登录获取个人加速地址)
|
||||
|
||||
本节以 [AtomHub 可信镜像中心](https://hub.atomgit.com/)镜像服务 `https://hub.atomgit.com` 为例进行介绍。
|
||||
|
||||
> `hub.atomgit.com` 仅包含部分官方镜像,可以满足初学者的使用。
|
||||
|
||||
### 3.9.2 Ubuntu 22.04+、Debian 12+、Rocky/Alma/CentOS Stream 9+
|
||||
|
||||
目前主流 Linux 发行版均已使用 [systemd](https://systemd.io/) 进行服务管理,这里介绍如何在使用 systemd 的 Linux 发行版中配置镜像加速器。
|
||||
|
||||
请首先执行以下命令,查看是否在 `docker.service` 文件中配置过镜像地址。
|
||||
|
||||
```bash
|
||||
$ systemctl cat docker | grep '\-\-registry\-mirror'
|
||||
```
|
||||
|
||||
如果该命令有输出,那么请执行 `$ systemctl cat docker` 查看 `ExecStart=` 出现的位置,修改对应的文件内容去掉 `--registry-mirror` 参数及其值,并按接下来的步骤进行配置。
|
||||
|
||||
如果以上命令没有任何输出,那么就可以在 `/etc/docker/daemon.json` 中写入如下内容 (如果文件不存在请新建该文件):
|
||||
|
||||
```json
|
||||
{
|
||||
"registry-mirrors": [
|
||||
"https://hub.atomgit.com"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
> 注意,一定要保证该文件符合 json 规范,否则 Docker 将不能启动。
|
||||
|
||||
之后重新启动服务。
|
||||
|
||||
```bash
|
||||
$ sudo systemctl daemon-reload
|
||||
$ sudo systemctl restart docker
|
||||
```
|
||||
|
||||
### 3.9.3 Windows 10/11
|
||||
|
||||
对于使用 `Windows 10/11` 的用户,在任务栏托盘 Docker 图标内右键菜单选择 `Change settings`,打开配置窗口后在左侧导航菜单选择 `Docker Engine`,在右侧像下边一样编辑 json 文件,之后点击 `Apply & Restart` 保存后 Docker 就会重启并应用配置的镜像地址了。
|
||||
|
||||
```json
|
||||
{
|
||||
"registry-mirrors": [
|
||||
"https://hub.atomgit.com"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 3.9.4 macOS
|
||||
|
||||
对于使用 macOS 的用户,在任务栏点击 Docker Desktop 应用图标 -> `Settings...`,在左侧导航菜单选择 `Docker Engine`,在右侧像下边一样编辑 json 文件。修改完成之后,点击 `Apply & restart` 按钮,Docker 就会重启并应用配置的镜像地址了。
|
||||
|
||||
```json
|
||||
{
|
||||
"registry-mirrors": [
|
||||
"https://hub.atomgit.com"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 3.9.5 检查加速器是否生效
|
||||
|
||||
执行 `$ docker info`,如果从结果中看到了如下内容,说明配置成功。
|
||||
|
||||
```bash
|
||||
Registry Mirrors:
|
||||
https://hub.atomgit.com/
|
||||
```
|
||||
|
||||
### 3.9.6 Kubernetes 官方镜像地址迁移
|
||||
|
||||
可以登录[阿里云容器镜像服务](https://www.aliyun.com/product/acr?source=5176.11533457&userCode=8lx5zmtu&type=copy)**镜像中心**->**镜像搜索** 查找。
|
||||
|
||||
Kubernetes 社区已将官方镜像地址从 `k8s.gcr.io` 迁移到 `registry.k8s.io`。建议优先使用新地址。
|
||||
|
||||
一般情况下有如下对应关系:
|
||||
|
||||
```bash
|
||||
$ docker pull registry.k8s.io/xxx
|
||||
```
|
||||
|
||||
### 3.9.7 不再提供服务的镜像
|
||||
|
||||
某些镜像不再提供服务,添加无用的镜像加速器,会拖慢镜像拉取速度,你可以从镜像配置列表中删除它们。
|
||||
|
||||
* https://dockerhub.azk8s.cn **已转为私有**
|
||||
* https://reg-mirror.qiniu.com
|
||||
* https://registry.docker-cn.com
|
||||
* https://hub-mirror.c.163.com
|
||||
* https://mirror.baidubce.com
|
||||
|
||||
建议 **watch (页面右上角)** [镜像测试](https://github.com/docker-practice/docker-registry-cn-mirror-test)这个 GitHub 仓库,我们会在此更新各个镜像地址的状态。
|
||||
|
||||
### 3.9.8 云服务商
|
||||
|
||||
某些云服务商提供了 **仅供内部** 访问的镜像服务,当您的 Docker 运行在云平台时可以选择它们。
|
||||
|
||||
* [腾讯云 `https://mirror.ccs.tencentyun.com`](https://cloud.tencent.com/act/cps/redirect?redirect=10058&cps_key=3a5255852d5db99dcd5da4c72f05df61)
|
||||
@@ -1,18 +0,0 @@
|
||||
# 第三章 安装 Docker
|
||||
|
||||
Docker 分为 `stable` `test` 和 `nightly` 三个更新频道。
|
||||
|
||||
官方网站上有各种环境下的[安装指南](https://docs.docker.com/get-docker/),这里主要介绍 Docker 在 `Linux`、`Windows 10` 和 `macOS` 上的安装。
|
||||
|
||||
## 详细安装指南
|
||||
|
||||
* [Ubuntu](3.1_ubuntu.md)
|
||||
* [Debian](3.2_debian.md)
|
||||
* [Fedora](3.3_fedora.md)
|
||||
* [CentOS](3.4_centos.md)
|
||||
* [Raspberry Pi](3.5_raspberry-pi.md)
|
||||
* [Linux 离线安装](3.6_offline.md)
|
||||
* [macOS](3.7_mac.md)
|
||||
* [Windows 10/11](3.8_windows.md)
|
||||
* [镜像加速器](3.9_mirror.md)
|
||||
* [开启实验特性](3.10_experimental.md)
|
||||
@@ -1,27 +0,0 @@
|
||||
## 本章小结
|
||||
|
||||
Docker 支持在多种平台上安装和使用,选择合适的安装方式是顺利使用 Docker 的第一步。
|
||||
|
||||
| 平台 | 推荐方式 | 说明 |
|
||||
|------|---------|------|
|
||||
| **Ubuntu/Debian** | 官方 APT 仓库 | 最完善的支持,推荐首选 |
|
||||
| **CentOS/Fedora** | 官方 YUM/DNF 仓库 | 注意关闭 SELinux 或配置策略 |
|
||||
| **macOS** | Docker Desktop | 图形化安装,包含 Compose 和 Kubernetes |
|
||||
| **Windows 10/11** | Docker Desktop (WSL 2) | 需启用 WSL 2 后端 |
|
||||
| **Raspberry Pi** | 官方安装脚本 | 支持 ARM 架构 |
|
||||
| **离线环境** | 二进制包安装 | 适用于无法联网的服务器 |
|
||||
|
||||
### 3.11.1 安装后验证
|
||||
|
||||
安装完成后,运行以下命令验证 Docker 是否正常工作:
|
||||
|
||||
```bash
|
||||
$ docker version
|
||||
$ docker run --rm hello-world
|
||||
```
|
||||
|
||||
### 3.11.2 延伸阅读
|
||||
|
||||
- [镜像加速器](3.9_mirror.md):解决国内拉取镜像慢的问题
|
||||
- [开启实验特性](3.10_experimental.md):使用最新功能
|
||||
- [Docker Hub](../06_repository/6.1_dockerhub.md):官方镜像仓库
|
||||
@@ -1,241 +0,0 @@
|
||||
## 4.1 获取镜像
|
||||
|
||||
从 Docker 镜像仓库获取镜像可谓是 Docker 运作的第一步。本节将介绍如何使用 `docker pull` 命令下载镜像,以及如何理解下载过程。
|
||||
|
||||
### 4.1.1 docker pull 命令
|
||||
|
||||
从镜像仓库获取镜像的命令是 `docker pull`:
|
||||
|
||||
```bash
|
||||
docker pull [选项] [Registry地址/]仓库名[:标签]
|
||||
```
|
||||
|
||||
#### 镜像名称格式
|
||||
|
||||
Docker 镜像名称由 Registry 地址、用户名、仓库名和标签组成。其标准格式如下:
|
||||
|
||||
```bash
|
||||
docker.io / library / ubuntu : 24.04
|
||||
────┬──── ───┬─── ──┬─── ──┬──
|
||||
│ │ │ │
|
||||
Registry地址 用户名 仓库名 标签
|
||||
(可省略) (可省略)
|
||||
```
|
||||
|
||||
| 组成部分 | 说明 | 默认值 |
|
||||
|---------|------|--------|
|
||||
| Registry 地址 | 镜像仓库地址 | `docker.io` (Docker Hub)|
|
||||
| 用户名 | 镜像所属用户/组织 | `library` (官方镜像)|
|
||||
| 仓库名 | 镜像名称 | 必须指定 |
|
||||
| 标签 | 版本标识 | `latest` |
|
||||
|
||||
#### 示例
|
||||
|
||||
```bash
|
||||
## 完整格式
|
||||
|
||||
$ docker pull docker.io/library/ubuntu:24.04
|
||||
|
||||
## 省略 Registry(默认 Docker Hub)
|
||||
|
||||
$ docker pull library/ubuntu:24.04
|
||||
|
||||
## 省略 library(官方镜像)
|
||||
|
||||
$ docker pull ubuntu:24.04
|
||||
|
||||
## 省略标签(默认 latest)
|
||||
|
||||
$ docker pull ubuntu
|
||||
|
||||
## 拉取第三方镜像
|
||||
|
||||
$ docker pull bitnami/redis:latest
|
||||
|
||||
## 从其他 Registry 拉取
|
||||
|
||||
$ docker pull ghcr.io/username/myapp:v1.0
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4.1.2 下载过程解析
|
||||
|
||||
当我们执行 `docker pull` 命令时,Docker 会输出详细的下载进度。让我们以 `ubuntu:24.04` 为例来解析这些信息。
|
||||
|
||||
```bash
|
||||
$ docker pull ubuntu:24.04
|
||||
24.04: Pulling from library/ubuntu
|
||||
92dc2a97ff99: Pull complete
|
||||
be13a9d27eb8: Pull complete
|
||||
c8299583700a: Pull complete
|
||||
Digest: sha256:4bc3ae6596938cb0d9e5ac51a1152ec9dcac2a1c50829c74abd9c4361e321b26
|
||||
Status: Downloaded newer image for ubuntu:24.04
|
||||
docker.io/library/ubuntu:24.04
|
||||
```
|
||||
|
||||
#### 输出解读
|
||||
|
||||
| 输出内容 | 说明 |
|
||||
|---------|------|
|
||||
| `Pulling from library/ubuntu` | 正在从官方 ubuntu 仓库拉取 |
|
||||
| `92dc2a97ff99: Pull complete` | 各层的下载状态 (显示层 ID 前 12 位)|
|
||||
| `Digest: sha256:...` | 镜像内容的唯一摘要 |
|
||||
| `docker.io/library/ubuntu:24.04` | 镜像的完整名称 |
|
||||
|
||||
#### 分层下载
|
||||
|
||||
从输出可以看到,镜像是 **分层下载** 的:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph Image ["ubuntu:24.04 镜像"]
|
||||
direction TB
|
||||
L3["第3层 c8299583700a<br/>(已存在,跳过下载)"]
|
||||
L2["第2层 be13a9d27eb8<br/>(下载中... 完成)"]
|
||||
L1["第1层 92dc2a97ff99<br/>(下载中... 完成)"]
|
||||
L3 --- L2 --- L1
|
||||
end
|
||||
```
|
||||
|
||||
如果本地已有相同的层,Docker 会跳过下载,节省带宽和时间。
|
||||
|
||||
---
|
||||
|
||||
### 4.1.3 常用选项
|
||||
|
||||
`docker pull` 命令支持多种选项来满足不同的下载需求,例如下载所有标签、指定平台架构等。
|
||||
|
||||
| 选项 | 说明 | 示例 |
|
||||
|------|------|------|
|
||||
| `--all-tags, -a` | 拉取所有标签 | `docker pull -a ubuntu` |
|
||||
| `--platform` | 指定平台架构 | `docker pull --platform linux/arm64 nginx` |
|
||||
| `--quiet, -q` | 静默模式 | `docker pull -q nginx` |
|
||||
|
||||
#### 指定平台
|
||||
|
||||
在 Apple Silicon Mac 上拉取 x86 镜像:
|
||||
|
||||
```bash
|
||||
$ docker pull --platform linux/amd64 nginx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4.1.4 拉取后运行
|
||||
|
||||
拉取镜像后,可以基于它启动容器:
|
||||
|
||||
```bash
|
||||
## 拉取镜像
|
||||
|
||||
$ docker pull ubuntu:24.04
|
||||
|
||||
## 运行容器
|
||||
|
||||
$ docker run -it --rm ubuntu:24.04 bash
|
||||
root@e7009c6ce357:/# cat /etc/os-release
|
||||
PRETTY_NAME="Ubuntu 24.04 LTS"
|
||||
...
|
||||
root@e7009c6ce357:/# exit
|
||||
```
|
||||
|
||||
**参数说明**:
|
||||
|
||||
| 参数 | 说明 |
|
||||
|------|------|
|
||||
| `-it` | 交互式终端模式 |
|
||||
| `--rm` | 退出后自动删除容器 |
|
||||
| `bash` | 启动命令 |
|
||||
|
||||
> 💡 `docker run` 在需要时会自动 `pull` 镜像,因此通常不需要单独执行 `docker pull`。
|
||||
|
||||
---
|
||||
|
||||
### 4.1.5 镜像加速
|
||||
|
||||
从 Docker Hub 下载可能较慢。可以配置镜像加速器:
|
||||
|
||||
```json
|
||||
// /etc/docker/daemon.json (Linux)
|
||||
// ~/.docker/daemon.json (Docker Desktop)
|
||||
{
|
||||
"registry-mirrors": [
|
||||
"https://your-accelerator-url"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
配置后重启 Docker:
|
||||
|
||||
```bash
|
||||
$ sudo systemctl restart docker # Linux
|
||||
## 或在 Docker Desktop 中重启
|
||||
|
||||
## 或在 Docker Desktop 中重启
|
||||
|
||||
```
|
||||
|
||||
详见[镜像加速器](../03_install/3.9_mirror.md)章节。
|
||||
|
||||
---
|
||||
|
||||
### 4.1.6 验证镜像完整性
|
||||
|
||||
为了确保下载的镜像没有被篡改且内容一致,我们可以校验镜像的摘要 (Digest)。
|
||||
|
||||
#### 查看镜像摘要
|
||||
|
||||
```bash
|
||||
$ docker images --digests ubuntu
|
||||
REPOSITORY TAG DIGEST IMAGE ID
|
||||
ubuntu 24.04 sha256:4bc3ae6596938cb0d9e5ac51a1152ec9dcac2a1c50829c74abd9c4361e321b26 ca2b0f26964c
|
||||
```
|
||||
|
||||
#### 使用摘要拉取
|
||||
|
||||
用摘要拉取可确保获取完全相同的镜像:
|
||||
|
||||
```bash
|
||||
$ docker pull ubuntu@sha256:4bc3ae6596938cb0d9e5ac51a1152ec9dcac2a1c50829c74abd9c4361e321b26
|
||||
```
|
||||
|
||||
> 笔者建议:生产环境使用摘要而非标签,因为标签可能被覆盖,摘要则是不可变的。
|
||||
|
||||
---
|
||||
|
||||
### 4.1.7 常见问题
|
||||
|
||||
在使用 `docker pull` 过程中,可能会遇到下载速度慢、镜像不存在或磁盘空间不足等问题。以下是一些常见问题的排查思路。
|
||||
|
||||
#### Q:下载速度很慢
|
||||
|
||||
1. 配置镜像加速器
|
||||
2. 检查网络连接
|
||||
3. 尝试拉取更小的镜像版本 (如 `alpine` 变体)
|
||||
|
||||
#### Q:提示镜像不存在
|
||||
|
||||
```bash
|
||||
Error: pull access denied, repository does not exist
|
||||
```
|
||||
|
||||
可能原因:
|
||||
|
||||
- 镜像名拼写错误
|
||||
- 私有镜像未登录 (需要 `docker login`)
|
||||
- 镜像确实不存在
|
||||
|
||||
#### Q:磁盘空间不足
|
||||
|
||||
```bash
|
||||
## 清理未使用的镜像
|
||||
|
||||
$ docker image prune
|
||||
|
||||
## 清理所有未使用资源
|
||||
|
||||
$ docker system prune
|
||||
```
|
||||
|
||||
---
|
||||
@@ -1,267 +0,0 @@
|
||||
## 4.2 列出镜像
|
||||
|
||||
在下载了镜像后,我们可以使用 `docker image ls` 命令列出本地主机上的镜像。
|
||||
|
||||
### 4.2.1 基本用法
|
||||
|
||||
查看本地已下载的镜像:
|
||||
|
||||
```bash
|
||||
$ docker image ls
|
||||
REPOSITORY TAG IMAGE ID CREATED SIZE
|
||||
redis latest 5f515359c7f8 5 days ago 183MB
|
||||
nginx latest 05a60462f8ba 5 days ago 181MB
|
||||
ubuntu 24.04 329ed837d508 3 days ago 78MB
|
||||
ubuntu noble 329ed837d508 3 days ago 78MB
|
||||
```
|
||||
|
||||
> 💡 `docker images` 是 `docker image ls` 的简写,两者等效。
|
||||
|
||||
---
|
||||
|
||||
### 4.2.2 输出字段说明
|
||||
|
||||
`docker image ls` 命令默认输出的列表包含仓库名、标签、镜像 ID、创建时间和占用空间等信息。
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| **REPOSITORY** | 仓库名 |
|
||||
| **TAG** | 标签 (版本)|
|
||||
| **IMAGE ID** | 镜像唯一标识 (短 ID,前 12 位)|
|
||||
| **CREATED** | 创建时间 |
|
||||
| **SIZE** | 本地占用空间 |
|
||||
|
||||
#### 同一镜像多个标签
|
||||
|
||||
注意上面的 `ubuntu:24.04` 和 `ubuntu:noble` 拥有相同的 IMAGE ID——它们是同一个镜像的不同标签,只占用一份存储空间。
|
||||
|
||||
---
|
||||
|
||||
### 4.2.3 理解镜像大小
|
||||
|
||||
Docker 镜像的大小可能与我们通常理解的文件大小有所不同,这涉及到分层存储的概念。
|
||||
|
||||
#### 本地大小 vs Hub 显示大小
|
||||
|
||||
| 位置 | 显示大小 | 说明 |
|
||||
|------|---------|------|
|
||||
| Docker Hub | 29MB | 压缩后的网络传输大小 |
|
||||
| docker image ls | 78MB | 本地解压后的实际大小 |
|
||||
|
||||
#### 实际磁盘占用
|
||||
|
||||
由于镜像是分层存储,不同镜像可能共享相同的层:
|
||||
|
||||
```bash
|
||||
ubuntu:24.04 nginx:latest redis:latest
|
||||
│ │ │
|
||||
└───────┬───────┘ │
|
||||
▼ │
|
||||
共享基础层 ◄───────────────────┘
|
||||
```
|
||||
|
||||
因此,`docker image ls` 中各镜像大小之和 > 实际磁盘占用。
|
||||
|
||||
#### 查看实际空间占用
|
||||
|
||||
```bash
|
||||
$ docker system df
|
||||
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
|
||||
Images 15 3 2.5GB 1.8GB (72%)
|
||||
Containers 5 2 100MB 80MB (80%)
|
||||
Local Volumes 8 2 500MB 400MB (80%)
|
||||
Build Cache 0 0 0B 0B
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4.2.4 过滤镜像
|
||||
|
||||
随着本地镜像数量的增加,我们需要更有效的方式来查找特定的镜像。Docker 提供了多种过滤方式。
|
||||
|
||||
#### 按仓库名过滤
|
||||
|
||||
```bash
|
||||
## 列出所有 ubuntu 镜像
|
||||
|
||||
$ docker images ubuntu
|
||||
REPOSITORY TAG IMAGE ID SIZE
|
||||
ubuntu 24.04 329ed837d508 78MB
|
||||
ubuntu noble 329ed837d508 78MB
|
||||
ubuntu 22.04 a1b2c3d4e5f6 72MB
|
||||
```
|
||||
|
||||
#### 按仓库名和标签过滤
|
||||
|
||||
```bash
|
||||
$ docker images ubuntu:24.04
|
||||
REPOSITORY TAG IMAGE ID SIZE
|
||||
ubuntu 24.04 329ed837d508 78MB
|
||||
```
|
||||
|
||||
#### 使用过滤器 --filter
|
||||
|
||||
| 过滤条件 | 说明 | 示例 |
|
||||
|---------|------|------|
|
||||
| `dangling=true` | 虚悬镜像 | `-f dangling=true` |
|
||||
| `before=镜像` | 在某镜像之前创建 | `-f before=nginx:latest` |
|
||||
| `since=镜像` | 在某镜像之后创建 | `-f since=nginx:latest` |
|
||||
| `label=key=value` | 按 LABEL 过滤 | `-f label=version=1.0` |
|
||||
| `reference=pattern` | 按名称模式 | `-f reference='*:latest'` |
|
||||
|
||||
```bash
|
||||
## 列出 nginx 之后创建的镜像
|
||||
|
||||
$ docker images -f since=nginx:latest
|
||||
|
||||
## 列出所有带 latest 标签的镜像
|
||||
|
||||
$ docker images -f reference='*:latest'
|
||||
|
||||
## 列出带特定 LABEL 的镜像
|
||||
|
||||
$ docker images -f label=maintainer=example@email.com
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4.2.5 虚悬镜像
|
||||
|
||||
在镜像列表里,你可能会看到一些仓库名和标签都为 `<none>` 的镜像,这类镜像被称为虚悬镜像。
|
||||
|
||||
#### 什么是虚悬镜像
|
||||
|
||||
仓库名和标签都显示为 `<none>` 的镜像:
|
||||
|
||||
```bash
|
||||
$ docker images
|
||||
REPOSITORY TAG IMAGE ID SIZE
|
||||
<none> <none> 00285df0df87 342MB
|
||||
```
|
||||
|
||||
#### 产生原因
|
||||
|
||||
1. **镜像重新构建**:新镜像使用了旧镜像的标签,旧镜像标签被移除
|
||||
2. **docker pull 更新**:拉取更新版本时,旧版本失去标签
|
||||
|
||||
#### 处理虚悬镜像
|
||||
|
||||
```bash
|
||||
## 列出虚悬镜像
|
||||
|
||||
$ docker images -f dangling=true
|
||||
|
||||
## 删除虚悬镜像
|
||||
|
||||
$ docker image prune
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4.2.6 中间层镜像
|
||||
|
||||
除了虚悬镜像,`docker image ls` 默认列出的只是顶层镜像。还有一种镜像是为了加速镜像构建、重复利用资源而存在的中间层镜像。
|
||||
|
||||
#### 查看所有镜像 (包含中间层)
|
||||
|
||||
```bash
|
||||
$ docker images -a
|
||||
```
|
||||
|
||||
会显示很多无标签镜像——这些是构建过程中产生的中间层,被其他镜像依赖。
|
||||
|
||||
> ⚠️ 不要删除中间层镜像。它们是其他镜像的依赖,删除会导致上层镜像无法使用。删除顶层镜像时会自动清理不再需要的中间层。
|
||||
|
||||
---
|
||||
|
||||
### 4.2.7 格式化输出
|
||||
|
||||
为了配合脚本使用或展示更关注的信息,我们可以使用 `--format` 参数来自定义输出格式。
|
||||
|
||||
#### 只输出 ID
|
||||
|
||||
```bash
|
||||
$ docker images -q
|
||||
5f515359c7f8
|
||||
05a60462f8ba
|
||||
329ed837d508
|
||||
```
|
||||
|
||||
常用于配合其他命令:
|
||||
|
||||
```bash
|
||||
## 删除所有镜像
|
||||
|
||||
$ docker rmi $(docker images -q)
|
||||
|
||||
## 删除所有 redis 镜像
|
||||
|
||||
$ docker rmi $(docker images -q redis)
|
||||
```
|
||||
|
||||
#### 显示完整 ID
|
||||
|
||||
```bash
|
||||
$ docker images --no-trunc
|
||||
```
|
||||
|
||||
#### 显示摘要
|
||||
|
||||
```bash
|
||||
$ docker images --digests
|
||||
REPOSITORY TAG DIGEST IMAGE ID
|
||||
nginx latest sha256:b4f0e0bdeb5... e43d811ce2f4
|
||||
```
|
||||
|
||||
#### 自定义格式
|
||||
|
||||
使用 Go 模板语法自定义输出:
|
||||
|
||||
```bash
|
||||
## 只显示 ID 和仓库名
|
||||
|
||||
$ docker images --format "{{.ID}}: {{.Repository}}"
|
||||
5f515359c7f8: redis
|
||||
05a60462f8ba: nginx
|
||||
329ed837d508: ubuntu
|
||||
|
||||
## 表格形式(带标题)
|
||||
|
||||
$ docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}"
|
||||
REPOSITORY TAG SIZE
|
||||
redis latest 183MB
|
||||
nginx latest 181MB
|
||||
ubuntu 24.04 78MB
|
||||
```
|
||||
|
||||
#### 可用模板字段
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| `.ID` | 镜像 ID |
|
||||
| `.Repository` | 仓库名 |
|
||||
| `.Tag` | 标签 |
|
||||
| `.Digest` | 摘要 |
|
||||
| `.CreatedSince` | 创建后经过的时间 |
|
||||
| `.CreatedAt` | 创建时间 |
|
||||
| `.Size` | 大小 |
|
||||
|
||||
---
|
||||
|
||||
### 4.2.8 常用命令组合
|
||||
|
||||
```bash
|
||||
## 列出所有镜像及其大小,按大小排序(需要系统 sort 命令)
|
||||
|
||||
$ docker images --format "{{.Size}}\t{{.Repository}}:{{.Tag}}" | sort -h
|
||||
|
||||
## 查找大于 500MB 的镜像
|
||||
|
||||
$ docker images --format "{{.Size}}\t{{.Repository}}:{{.Tag}}" | grep -E "^[0-9]+GB|^[5-9][0-9]{2}MB"
|
||||
|
||||
## 导出镜像列表
|
||||
|
||||
$ docker images --format "{{.Repository}}:{{.Tag}}" > images.txt
|
||||
```
|
||||
|
||||
---
|
||||
@@ -1,270 +0,0 @@
|
||||
## 4.3 删除本地镜像
|
||||
|
||||
当不再需要某个镜像时,我们可以将其删除以释放存储空间。本节介绍删除镜像的常用方法。
|
||||
|
||||
### 4.3.1 基本用法
|
||||
|
||||
使用 `docker image rm` 删除本地镜像:
|
||||
|
||||
```bash
|
||||
$ docker image rm [选项] <镜像1> [<镜像2> ...]
|
||||
```
|
||||
|
||||
> 💡 `docker rmi` 是 `docker image rm` 的简写,两者等效。
|
||||
|
||||
---
|
||||
|
||||
### 4.3.2 镜像标识方式
|
||||
|
||||
删除镜像时,可以使用多种方式指定镜像:
|
||||
|
||||
| 方式 | 说明 | 示例 |
|
||||
|------|------|------|
|
||||
| **短 ID** | ID 的前几位 (通常 3-4 位)| `docker rmi 501` |
|
||||
| **长 ID** | 完整的镜像 ID | `docker rmi 501ad78535f0...` |
|
||||
| **镜像名:标签** | 仓库名和标签 | `docker rmi redis:alpine` |
|
||||
| **镜像摘要** | 精确的内容摘要 | `docker rmi nginx@sha256:...` |
|
||||
|
||||
#### 使用短 ID 删除
|
||||
|
||||
```bash
|
||||
$ docker image ls
|
||||
REPOSITORY TAG IMAGE ID SIZE
|
||||
redis alpine 501ad78535f0 30MB
|
||||
nginx latest e43d811ce2f4 142MB
|
||||
|
||||
## 只需输入足够区分的前几位
|
||||
|
||||
$ docker rmi 501
|
||||
Untagged: redis:alpine
|
||||
Deleted: sha256:501ad78535f0...
|
||||
```
|
||||
|
||||
#### 使用镜像名删除
|
||||
|
||||
```bash
|
||||
$ docker rmi redis:alpine
|
||||
Untagged: redis:alpine
|
||||
Deleted: sha256:501ad78535f0...
|
||||
```
|
||||
|
||||
#### 使用摘要删除
|
||||
|
||||
摘要删除最精确,适用于 CI/CD 场景:
|
||||
|
||||
```bash
|
||||
## 查看镜像摘要
|
||||
|
||||
$ docker images --digests
|
||||
REPOSITORY TAG DIGEST IMAGE ID
|
||||
nginx latest sha256:b4f0e0bdeb5... e43d811ce2f4
|
||||
|
||||
## 使用摘要删除
|
||||
|
||||
$ docker rmi nginx@sha256:b4f0e0bdeb578043c1ea6862f0d40cc4afe32a4a582f3be235a3b164422be228
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4.3.3 理解输出信息
|
||||
|
||||
执行删除命令后,Docker 会输出一系列的操作记录,理解这些信息有助于我们掌握镜像删除的机制。
|
||||
|
||||
删除镜像时会看到两类信息:**Untagged** 和 **Deleted**
|
||||
|
||||
```bash
|
||||
$ docker rmi redis:alpine
|
||||
Untagged: redis:alpine
|
||||
Untagged: redis@sha256:f1ed3708f538b537eb9c2a7dd50dc90a706f7debd7e1196c9264edeea521a86d
|
||||
Deleted: sha256:501ad78535f015d88872e13fa87a828425117e3d28075d0c117932b05bf189b7
|
||||
Deleted: sha256:96167737e29ca8e9d74982ef2a0dda76ed7b430da55e321c071f0dbff8c2899b
|
||||
Deleted: sha256:32770d1dcf835f192cafd6b9263b7b597a1778a403a109e2cc2ee866f74adf23
|
||||
```
|
||||
|
||||
#### Untagged vs Deleted
|
||||
|
||||
| 操作 | 含义 |
|
||||
|------|------|
|
||||
| **Untagged** | 移除镜像的标签 |
|
||||
| **Deleted** | 删除镜像的存储层 |
|
||||
|
||||
#### 删除流程
|
||||
|
||||
Docker 会检测镜像是否有容器依赖或其他标签指向,只有在确认为无用资源时才会真正删除存储层。
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start(["docker rmi redis:alpine"]) --> Step1
|
||||
|
||||
subgraph Process ["删除流程"]
|
||||
direction TB
|
||||
Step1["1. Untag:移除 redis:alpine 标签"] --> Step2
|
||||
|
||||
Step2{"2. 检查是否还有其他标签指向此镜像"}
|
||||
Step2 -- "有" --> Keep1["只 Untag,不删除"]
|
||||
Step2 -- "无" --> Step3
|
||||
|
||||
Step3{"3. 检查是否有容器依赖"}
|
||||
Step3 -- "有" --> Error["报错,无法删除"]
|
||||
Step3 -- "无" --> Step4
|
||||
|
||||
Step4{"4. 从上到下逐层删除,检查每层是否被其他镜像使用"}
|
||||
Step4 -- "被使用" --> Keep2["保留该层"]
|
||||
Step4 -- "未使用" --> Delete["Deleted (删除该层)"]
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4.3.4 批量删除
|
||||
|
||||
手动一个一个删除镜像非常繁琐,Docker 提供了 `image prune` 命令和 shell 组合命令来实现批量清理。
|
||||
|
||||
#### 删除所有虚悬镜像
|
||||
|
||||
虚悬镜像 (dangling):没有标签的镜像,通常是旧版本被新版本覆盖后产生的
|
||||
|
||||
```bash
|
||||
## 查看虚悬镜像
|
||||
|
||||
$ docker images -f dangling=true
|
||||
|
||||
## 删除虚悬镜像
|
||||
|
||||
$ docker image prune
|
||||
|
||||
## 不提示确认
|
||||
|
||||
$ docker image prune -f
|
||||
```
|
||||
|
||||
#### 删除所有未使用的镜像
|
||||
|
||||
```bash
|
||||
## 删除所有没有被容器使用的镜像
|
||||
|
||||
$ docker image prune -a
|
||||
|
||||
## 保留最近 24 小时的
|
||||
|
||||
$ docker image prune -a --filter "until=24h"
|
||||
```
|
||||
|
||||
#### 按条件删除
|
||||
|
||||
```bash
|
||||
## 删除所有 redis 镜像
|
||||
|
||||
$ docker rmi $(docker images -q redis)
|
||||
|
||||
## 删除 mongo:8.0 之前的所有镜像
|
||||
|
||||
$ docker rmi $(docker images -q -f before=mongo:8.0)
|
||||
|
||||
## 删除某个时间之前的镜像
|
||||
|
||||
$ docker image prune -a --filter "until=168h" # 7天前
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4.3.5 删除失败的常见原因
|
||||
|
||||
在删除镜像时,Docker 可能会提示错误并拒绝执行。这通常是为了防止误删正在使用的资源。
|
||||
|
||||
#### 原因一:有容器依赖
|
||||
|
||||
```bash
|
||||
$ docker rmi nginx
|
||||
Error: conflict: unable to remove repository reference "nginx"
|
||||
(must force) - container abc123 is using its referenced image
|
||||
```
|
||||
|
||||
**解决方案**:
|
||||
|
||||
```bash
|
||||
## 方案1:先删除依赖的容器
|
||||
|
||||
$ docker rm abc123
|
||||
$ docker rmi nginx
|
||||
|
||||
## 方案2:强制删除镜像(容器仍可运行,但无法再创建新容器)
|
||||
|
||||
$ docker rmi -f nginx
|
||||
```
|
||||
|
||||
#### 原因二:多个标签指向同一镜像
|
||||
|
||||
```bash
|
||||
$ docker images
|
||||
REPOSITORY TAG IMAGE ID
|
||||
ubuntu 24.04 ca2b0f26964c
|
||||
ubuntu latest ca2b0f26964c # 同一个镜像
|
||||
|
||||
$ docker rmi ubuntu:24.04
|
||||
Untagged: ubuntu:24.04
|
||||
## 只是移除标签,镜像仍存在(因为还有 ubuntu:latest 指向它)
|
||||
|
||||
当同一个镜像有多个标签时,`docker rmi` 只是删除指定的标签,不会删除镜像本身。
|
||||
|
||||
```
|
||||
|
||||
#### 原因三:被其他镜像依赖 (中间层)
|
||||
|
||||
```bash
|
||||
$ docker rmi some_base_image
|
||||
Error: image has dependent child images
|
||||
```
|
||||
|
||||
中间层镜像被其他镜像依赖,无法删除。需要先删除依赖它的镜像。
|
||||
|
||||
---
|
||||
|
||||
### 4.3.6 常用过滤条件
|
||||
|
||||
| 过滤条件 | 说明 | 示例 |
|
||||
|---------|------|------|
|
||||
| `dangling=true` | 虚悬镜像 | `-f dangling=true` |
|
||||
| `before=镜像` | 在某镜像之前 | `-f before=mongo:3.2` |
|
||||
| `since=镜像` | 在某镜像之后 | `-f since=mongo:3.2` |
|
||||
| `label=key=value` | 按标签过滤 | `-f label=version=1.0` |
|
||||
| `reference=pattern` | 按名称模式 | `-f reference='*:latest'` |
|
||||
|
||||
---
|
||||
|
||||
### 4.3.7 清理策略
|
||||
|
||||
针对不同的环境 (开发环境 vs 生产环境),我们应该采用不同的镜像清理策略。
|
||||
|
||||
#### 开发环境
|
||||
|
||||
```bash
|
||||
## 定期清理虚悬镜像
|
||||
|
||||
$ docker image prune -f
|
||||
|
||||
## 一键清理所有未使用资源
|
||||
|
||||
$ docker system prune -a
|
||||
```
|
||||
|
||||
#### CI/CD 环境
|
||||
|
||||
```bash
|
||||
## 只保留最近使用的镜像
|
||||
|
||||
$ docker image prune -a --filter "until=72h" -f
|
||||
```
|
||||
|
||||
#### 查看空间占用
|
||||
|
||||
```bash
|
||||
$ docker system df
|
||||
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
|
||||
Images 15 3 2.5GB 1.8GB (72%)
|
||||
Containers 5 2 100MB 80MB (80%)
|
||||
Local Volumes 8 2 500MB 400MB (80%)
|
||||
Build Cache 0 0 0B 0B
|
||||
```
|
||||
|
||||
---
|
||||
@@ -1,65 +0,0 @@
|
||||
## 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,17 +0,0 @@
|
||||
# 第四章 使用镜像
|
||||
|
||||
在之前的介绍中,我们知道镜像是 Docker 的三大组件之一。
|
||||
|
||||
Docker 运行容器前需要本地存在对应的镜像,如果本地不存在该镜像,Docker 会从镜像仓库下载该镜像。
|
||||
|
||||
## 本章内容
|
||||
|
||||
本章将介绍更多关于镜像的内容,包括:
|
||||
|
||||
* [从仓库获取镜像](4.1_pull.md)
|
||||
* [列出镜像](4.2_list.md)
|
||||
* [删除本地镜像](4.3_rm.md)
|
||||
* [利用 commit 理解镜像构成](4.4_commit.md)
|
||||
* [使用 Dockerfile 定制镜像](4.5_build.md)
|
||||
* [其它制作镜像的方式](4.6_other.md)
|
||||
* [镜像的实现原理](4.7_internal.md)
|
||||
@@ -1,46 +0,0 @@
|
||||
## 本章小结
|
||||
|
||||
| 操作 | 命令 |
|
||||
|------|------|
|
||||
| 拉取镜像 | `docker pull 镜像名:标签` |
|
||||
| 拉取所有标签 | `docker pull -a 镜像名` |
|
||||
| 指定平台 | `docker pull --platform linux/amd64 镜像名` |
|
||||
| 用摘要拉取 | `docker pull 镜像名@sha256:...` |
|
||||
|
||||
### 4.8.1 延伸阅读
|
||||
|
||||
- [列出镜像](4.2_list.md):查看本地镜像
|
||||
- [删除镜像](4.3_rm.md):清理本地镜像
|
||||
- [镜像加速器](../03_install/3.9_mirror.md):加速镜像下载
|
||||
- [Docker Hub](../06_repository/6.1_dockerhub.md):官方镜像仓库
|
||||
|
||||
| 操作 | 命令 |
|
||||
|------|------|
|
||||
| 列出所有镜像 | `docker images` |
|
||||
| 按仓库名过滤 | `docker images nginx` |
|
||||
| 列出虚悬镜像 | `docker images -f dangling=true` |
|
||||
| 只输出 ID | `docker images -q` |
|
||||
| 显示摘要 | `docker images --digests` |
|
||||
| 自定义格式 | `docker images --format "..."` |
|
||||
| 查看空间占用 | `docker system df` |
|
||||
|
||||
### 4.8.2 延伸阅读
|
||||
|
||||
- [获取镜像](4.1_pull.md):从 Registry 拉取镜像
|
||||
- [删除镜像](4.3_rm.md):清理本地镜像
|
||||
- [镜像](../02_basic_concept/2.1_image.md):理解镜像概念
|
||||
|
||||
| 操作 | 命令 |
|
||||
|------|------|
|
||||
| 删除指定镜像 | `docker rmi 镜像名:标签` |
|
||||
| 强制删除 | `docker rmi -f 镜像名` |
|
||||
| 删除虚悬镜像 | `docker image prune` |
|
||||
| 删除未使用镜像 | `docker image prune -a` |
|
||||
| 批量删除 | `docker rmi $(docker images -q -f ...)` |
|
||||
| 查看空间占用 | `docker system df` |
|
||||
|
||||
### 4.8.3 延伸阅读
|
||||
|
||||
- [列出镜像](4.2_list.md):查看和过滤镜像
|
||||
- [删除容器](../05_container/5.6_rm.md):清理容器
|
||||
- [数据卷](../08_data/8.1_volume.md):清理数据卷
|
||||
@@ -1,225 +0,0 @@
|
||||
## 5.1 启动
|
||||
|
||||
本节将详细介绍 Docker 容器的启动方式,包括新建启动和重新启动已停止的容器。
|
||||
|
||||
### 5.1.1 启动方式概述
|
||||
|
||||
启动容器有两种方式:
|
||||
|
||||
- **新建并启动**:基于镜像创建新容器
|
||||
- **重新启动**:将已终止的容器重新运行
|
||||
|
||||
由于 Docker 容器非常轻量,实际使用中常常是随时删除和新建容器,而不是反复重启同一个容器。
|
||||
|
||||
### 5.1.2 新建并启动
|
||||
|
||||
#### 基本语法
|
||||
|
||||
```bash
|
||||
docker run [选项] 镜像 [命令] [参数...]
|
||||
```
|
||||
|
||||
#### 最简单的例子
|
||||
|
||||
输出 “Hello World” 后容器自动终止:
|
||||
|
||||
```bash
|
||||
$ docker run ubuntu:24.04 /bin/echo 'Hello world'
|
||||
Hello world
|
||||
```
|
||||
|
||||
这与直接执行 `/bin/echo 'Hello world'` 几乎没有区别,但实际上已经启动了一个完整的 Ubuntu 容器来执行这条命令。
|
||||
|
||||
#### 交互式容器
|
||||
|
||||
启动一个可以交互的 bash 终端:
|
||||
|
||||
```bash
|
||||
$ docker run -it ubuntu:24.04 /bin/bash
|
||||
root@af8bae53bdd3:/#
|
||||
```
|
||||
|
||||
**参数说明**:
|
||||
|
||||
| 参数 | 作用 |
|
||||
|------|------|
|
||||
| `-i` | 保持标准输入 (stdin) 打开,允许输入 |
|
||||
| `-t` | 分配伪终端 (pseudo-TTY),提供终端界面 |
|
||||
| `-it` | 两者组合使用,获得交互式终端 |
|
||||
|
||||
在交互模式下可以执行命令:
|
||||
|
||||
```bash
|
||||
root@af8bae53bdd3:/# pwd
|
||||
/
|
||||
root@af8bae53bdd3:/# ls
|
||||
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
|
||||
root@af8bae53bdd3:/# exit # 退出容器
|
||||
```
|
||||
|
||||
### 5.1.3 docker run 的完整流程
|
||||
|
||||
执行 `docker run` 时,Docker 在后台完成以下操作:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Cmd["docker run ubuntu:24.04 /bin/echo 'Hello'"] --> Step1
|
||||
|
||||
Step1{"1. 检查本地是否有 ubuntu:24.04 镜像"}
|
||||
Step1 -- 有 --> Step1_Yes["使用本地镜像"]
|
||||
Step1 -- 无 --> Step1_No["从 Registry 下载"]
|
||||
|
||||
Step1_Yes --> Step2
|
||||
Step1_No --> Step2
|
||||
|
||||
Step2["2. 创建容器<br/>• 基于镜像的只读层<br/>• 添加一层可读写层(容器存储层)"] --> Step3
|
||||
Step3["3. 配置网络<br/>• 创建虚拟网卡<br/>• 分配 IP 地址<br/>• 连接到 Docker 网桥"] --> Step4
|
||||
Step4["4. 启动容器,执行指定命令"] --> Step5
|
||||
Step5["5. 命令执行完毕,容器停止"]
|
||||
```
|
||||
|
||||
### 5.1.4 常用启动选项
|
||||
|
||||
#### 基础选项
|
||||
|
||||
| 选项 | 说明 | 示例 |
|
||||
|------|------|------|
|
||||
| `-d` | 后台运行 (detach)| `docker run -d nginx` |
|
||||
| `-it` | 交互式终端 | `docker run -it ubuntu bash` |
|
||||
| `--name` | 指定容器名称 | `docker run --name myapp nginx` |
|
||||
| `--rm` | 退出后自动删除容器 | `docker run --rm ubuntu echo hi` |
|
||||
|
||||
#### 端口映射
|
||||
|
||||
```bash
|
||||
## 将容器的 80 端口映射到宿主机的 8080 端口
|
||||
|
||||
$ docker run -d -p 8080:80 nginx
|
||||
|
||||
## 随机映射端口
|
||||
|
||||
$ docker run -d -P nginx
|
||||
|
||||
## 只绑定到 localhost
|
||||
|
||||
$ docker run -d -p 127.0.0.1:8080:80 nginx
|
||||
```
|
||||
|
||||
#### 数据卷挂载
|
||||
|
||||
```bash
|
||||
## 挂载命名卷
|
||||
|
||||
$ docker run -v mydata:/data nginx
|
||||
|
||||
## 挂载宿主机目录
|
||||
|
||||
$ docker run -v /host/path:/container/path nginx
|
||||
|
||||
## 只读挂载
|
||||
|
||||
$ docker run -v /host/path:/container/path:ro nginx
|
||||
```
|
||||
|
||||
#### 环境变量
|
||||
|
||||
```bash
|
||||
## 设置单个环境变量
|
||||
|
||||
$ docker run -e MYSQL_ROOT_PASSWORD=secret mysql
|
||||
|
||||
## 从文件加载环境变量
|
||||
|
||||
$ docker run --env-file .env myapp
|
||||
```
|
||||
|
||||
#### 资源限制
|
||||
|
||||
```bash
|
||||
## 限制内存
|
||||
|
||||
$ docker run -m 512m nginx
|
||||
|
||||
## 限制 CPU
|
||||
|
||||
$ docker run --cpus=1.5 nginx
|
||||
```
|
||||
|
||||
### 5.1.5 启动已终止容器
|
||||
|
||||
使用 `docker start` 重新启动已停止的容器:
|
||||
|
||||
```bash
|
||||
## 查看所有容器(包括已停止的)
|
||||
|
||||
$ docker ps -a
|
||||
CONTAINER ID IMAGE STATUS NAMES
|
||||
af8bae53bdd3 ubuntu Exited (0) 2 minutes ago myubuntu
|
||||
|
||||
## 重新启动
|
||||
|
||||
$ docker start myubuntu
|
||||
|
||||
## 启动并附加终端
|
||||
|
||||
$ docker start -ai myubuntu
|
||||
```
|
||||
|
||||
### 5.1.6 容器内进程的特点
|
||||
|
||||
容器内只运行指定的应用程序及其必需资源:
|
||||
|
||||
```bash
|
||||
root@ba267838cc1b:/# ps
|
||||
PID TTY TIME CMD
|
||||
1 ? 00:00:00 bash
|
||||
11 ? 00:00:00 ps
|
||||
```
|
||||
|
||||
可见容器中仅运行了 `bash` 进程。这种特点使得 Docker 对资源的利用率极高。
|
||||
|
||||
> 💡 笔者提示:容器内的 PID 1 进程很重要——它是容器的主进程,该进程退出则容器停止。详见[后台运行](5.2_daemon.md)章节。
|
||||
|
||||
### 5.1.7 常见问题
|
||||
|
||||
#### Q:容器启动后立即退出
|
||||
|
||||
**原因**:主进程执行完毕或无法保持运行
|
||||
|
||||
```bash
|
||||
## 这个容器会立即退出(echo 执行完就结束了)
|
||||
|
||||
$ docker run ubuntu echo "hello"
|
||||
|
||||
## 解决:使用能持续运行的命令
|
||||
|
||||
$ docker run -d nginx # nginx 是持续运行的服务
|
||||
```
|
||||
|
||||
详细解释见[后台运行](5.2_daemon.md)。
|
||||
|
||||
#### Q:无法连接容器内的服务
|
||||
|
||||
**原因**:未正确映射端口
|
||||
|
||||
```bash
|
||||
## 错误:没有 -p 参数,外部无法访问
|
||||
|
||||
$ docker run -d nginx
|
||||
|
||||
## 正确:映射端口
|
||||
|
||||
$ docker run -d -p 80:80 nginx
|
||||
```
|
||||
|
||||
#### Q:容器内修改的文件丢失
|
||||
|
||||
**原因**:未使用数据卷,数据保存在容器存储层
|
||||
|
||||
```bash
|
||||
## 使用数据卷持久化
|
||||
|
||||
$ docker run -v mydata:/app/data myapp
|
||||
```
|
||||
|
||||
详见[数据管理](../08_data/README.md)。
|
||||
@@ -1,234 +0,0 @@
|
||||
## 5.2 守护态运行
|
||||
|
||||
在生产环境中,我们通常需要容器持续运行,不受终端关闭的影响。本节将深入讲解如何让容器在后台运行,以及理解容器生命周期的核心概念。
|
||||
|
||||
### 5.2.1 核心概念:前台 vs 后台
|
||||
|
||||
当你在终端运行一个程序时,有两种模式:
|
||||
|
||||
- **前台运行**:程序占用当前终端,输出直接显示,关闭终端程序就停止
|
||||
- **后台运行**:程序在后台执行,不占用终端,终端关闭也不影响程序
|
||||
|
||||
Docker 容器默认是 **前台运行** 的。使用 `-d` (detach) 参数可以让容器在后台运行。
|
||||
|
||||
### 5.2.2 基本使用
|
||||
|
||||
#### 前台运行 (默认)
|
||||
|
||||
```bash
|
||||
$ docker run ubuntu:24.04 /bin/sh -c "while true; do echo hello world; sleep 1; done"
|
||||
hello world
|
||||
hello world
|
||||
hello world
|
||||
hello world
|
||||
```
|
||||
|
||||
容器会把输出的结果 (STDOUT) 打印到宿主机上面。此时:
|
||||
|
||||
- 终端被占用,无法执行其他命令
|
||||
- 按 `Ctrl+C` 会终止容器
|
||||
- 关闭终端窗口,容器也会停止
|
||||
|
||||
#### 后台运行 (使用 -d 参数)
|
||||
|
||||
```bash
|
||||
$ docker run -d ubuntu:24.04 /bin/sh -c "while true; do echo hello world; sleep 1; done"
|
||||
77b2dc01fe0f3f1265df143181e7b9af5e05279a884f4776ee75350ea9d8017a
|
||||
```
|
||||
|
||||
使用 `-d` 参数后:
|
||||
|
||||
- 容器在后台运行
|
||||
- 返回容器的完整 ID
|
||||
- 终端立即释放,可以继续执行其他命令
|
||||
- 输出不会直接显示 (需要用 `docker logs` 查看)
|
||||
|
||||
### 5.2.3 深入理解:容器为什么会 “立即退出”?
|
||||
|
||||
> **这是初学者最常遇到的困惑。** 理解这个问题,你就理解了 Docker 的核心设计理念。
|
||||
|
||||
很多人尝试这样启动容器:
|
||||
|
||||
```bash
|
||||
$ docker run -d ubuntu:24.04
|
||||
```
|
||||
|
||||
然后用 `docker ps` 查看,发现容器根本不在运行!这是为什么?
|
||||
|
||||
#### 核心原理:容器的生命周期与主进程绑定
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph Lifecycle ["Docker 容器的生命周期 = 容器内 PID 1 进程的生命周期"]
|
||||
direction LR
|
||||
Start["主进程启动"] --> Run["容器运行"]
|
||||
Exit["主进程退出"] --> Stop["容器停止"]
|
||||
end
|
||||
```
|
||||
|
||||
当你运行 `docker run -d ubuntu:24.04` 时:
|
||||
|
||||
1. 容器启动
|
||||
2. 没有指定命令,默认执行 `/bin/bash`
|
||||
3. 但没有交互式终端 (没有 `-it` 参数),bash 发现没有输入源
|
||||
4. bash 立即退出
|
||||
5. 主进程退出,容器停止
|
||||
|
||||
**关键理解**:
|
||||
|
||||
- ❌ `-d` 参数 **不是** 让容器 “一直运行”
|
||||
- ✅ `-d` 参数是让容器 “在后台运行”,能运行多久取决于主进程
|
||||
|
||||
#### 常见的 “立即退出” 场景
|
||||
|
||||
| 场景 | 原因 | 解决方案 |
|
||||
|------|------|---------|
|
||||
| `docker run -d ubuntu` | 默认 bash 无输入立即退出 | 指定长期运行的命令 |
|
||||
| `docker run -d nginx` 后改了配置 | 配置错误导致 nginx 启动失败 | 查看 `docker logs` |
|
||||
| 自定义镜像容器启动即退 | Dockerfile 的 CMD 执行完毕 | 确保 CMD 是前台进程 |
|
||||
|
||||
### 5.2.4 查看后台容器
|
||||
|
||||
#### 查看运行中的容器
|
||||
|
||||
```bash
|
||||
$ docker container ls
|
||||
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
|
||||
77b2dc01fe0f ubuntu:24.04 /bin/sh -c 'while tr 2 minutes ago Up 1 minute agitated_wright
|
||||
```
|
||||
|
||||
#### 查看容器输出日志
|
||||
|
||||
```bash
|
||||
$ docker container logs 77b2dc01fe0f
|
||||
hello world
|
||||
hello world
|
||||
hello world
|
||||
...
|
||||
```
|
||||
|
||||
**实时查看日志** (类似 `tail -f`):
|
||||
|
||||
```bash
|
||||
$ docker container logs -f 77b2dc01fe0f
|
||||
```
|
||||
|
||||
#### 查看已停止的容器
|
||||
|
||||
```bash
|
||||
$ docker container ls -a
|
||||
```
|
||||
|
||||
加上 `-a` 参数可以看到所有容器,包括已停止的。这对于调试 “容器启动即退出” 的问题非常有用。
|
||||
|
||||
### 5.2.5 最佳实践
|
||||
|
||||
#### 1. 长期运行的服务使用 -d
|
||||
|
||||
```bash
|
||||
## Web 服务器
|
||||
|
||||
$ docker run -d -p 80:80 nginx
|
||||
|
||||
## 数据库
|
||||
|
||||
$ docker run -d -p 3306:3306 mysql:8
|
||||
|
||||
## 缓存服务
|
||||
|
||||
$ docker run -d -p 6379:6379 redis
|
||||
```
|
||||
|
||||
#### 2. 调试时先用前台模式
|
||||
|
||||
当容器启动有问题时,**去掉 `-d` 参数** 可以直接看到输出和错误:
|
||||
|
||||
```bash
|
||||
## 有问题的容器,先前台运行看看发生了什么
|
||||
|
||||
$ docker run myimage:latest
|
||||
```
|
||||
|
||||
#### 3. 使用 --rm 自动清理
|
||||
|
||||
对于一次性任务,使用 `--rm` 参数让容器退出后自动删除:
|
||||
|
||||
```bash
|
||||
$ docker run --rm ubuntu:24.04 echo "Hello, World!"
|
||||
Hello, World!
|
||||
## 容器执行完后自动删除
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
#### 4. 配合日志查看
|
||||
|
||||
```bash
|
||||
## 后台启动
|
||||
|
||||
$ docker run -d --name myapp myimage:latest
|
||||
|
||||
## 查看最近 100 行日志
|
||||
|
||||
$ docker logs --tail 100 myapp
|
||||
|
||||
## 实时跟踪日志
|
||||
|
||||
$ docker logs -f myapp
|
||||
|
||||
## 查看带时间戳的日志
|
||||
|
||||
$ docker logs -t myapp
|
||||
```
|
||||
|
||||
### 5.2.6 常见问题排查
|
||||
|
||||
#### Q:容器启动后立即退出
|
||||
|
||||
1. **查看退出状态码**:
|
||||
```bash
|
||||
$ docker ps -a --filter "name=mycontainer"
|
||||
# 查看 STATUS 列,如 “Exited (1)” 表示异常退出
|
||||
|
||||
```
|
||||
|
||||
2. **查看容器日志**:
|
||||
```bash
|
||||
$ docker logs mycontainer
|
||||
```
|
||||
|
||||
3. **以交互模式调试**:
|
||||
```bash
|
||||
$ docker run -it myimage:latest /bin/sh
|
||||
# 进入容器手动执行命令,查找问题
|
||||
|
||||
```
|
||||
|
||||
#### Q:容器在后台运行但无法访问服务
|
||||
|
||||
1. **检查端口映射**:
|
||||
```bash
|
||||
$ docker port mycontainer
|
||||
```
|
||||
|
||||
2. **检查容器内服务状态**:
|
||||
```bash
|
||||
$ docker exec mycontainer ps aux
|
||||
```
|
||||
|
||||
#### Q:如何让已经在后台运行的容器回到前台?
|
||||
|
||||
使用 `docker attach`:
|
||||
|
||||
```bash
|
||||
$ docker attach mycontainer
|
||||
```
|
||||
|
||||
> **注意**:`attach` 会连接到容器的主进程。如果主进程不是交互式的,你可能只能看到输出。使用 `Ctrl+P` `Ctrl+Q` 可以安全退出而不停止容器。
|
||||
|
||||
### 5.2.7 延伸阅读
|
||||
|
||||
- [进入容器](5.4_attach_exec.md):如何进入正在运行的容器执行命令
|
||||
- [容器日志](../appendix/best_practices.md):生产环境的日志管理最佳实践
|
||||
- [HEALTHCHECK 健康检查](../07_dockerfile/7.12_healthcheck.md):自动检测容器内服务是否正常
|
||||
- [Docker Compose](../11_compose/README.md):管理多个后台容器的更好方式
|
||||
@@ -1,233 +0,0 @@
|
||||
## 5.3 终止
|
||||
|
||||
本节将介绍如何终止一个运行中的容器,以及几种不同的终止方式及其区别。
|
||||
|
||||
### 5.3.1 终止方式概述
|
||||
|
||||
终止容器有三种方式:
|
||||
|
||||
| 方式 | 命令 | 说明 |
|
||||
|------|------|------|
|
||||
| **优雅停止** | `docker stop` | 先发 SIGTERM,超时后发 SIGKILL |
|
||||
| **强制停止** | `docker kill` | 直接发 SIGKILL |
|
||||
| **自动终止** | - | 容器主进程退出时自动停止 |
|
||||
|
||||
---
|
||||
|
||||
### 5.3.2 docker stop (推荐)
|
||||
|
||||
#### docker stop 基本用法
|
||||
|
||||
```bash
|
||||
$ docker stop 容器名或ID
|
||||
```
|
||||
|
||||
#### 工作原理
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
cmd["docker stop mycontainer"] --> A["1. 发送 SIGTERM 信号给容器主进程 (PID 1)"]
|
||||
A --> B["2. 等待容器优雅退出 (默认 10 秒)"]
|
||||
B --> C["3. 如果超时仍未退出,发送 SIGKILL 强制终止"]
|
||||
```
|
||||
|
||||
#### 自定义超时时间
|
||||
|
||||
```bash
|
||||
## 等待 30 秒后强制终止
|
||||
|
||||
$ docker stop -t 30 mycontainer
|
||||
|
||||
## 立即发送 SIGKILL(相当于 docker kill)
|
||||
|
||||
$ docker stop -t 0 mycontainer
|
||||
```
|
||||
|
||||
#### 停止多个容器
|
||||
|
||||
```bash
|
||||
## 停止多个指定容器
|
||||
|
||||
$ docker stop container1 container2 container3
|
||||
|
||||
## 停止所有运行中的容器
|
||||
|
||||
$ docker stop $(docker ps -q)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5.3.3 docker kill
|
||||
|
||||
#### 基本用法
|
||||
|
||||
```bash
|
||||
$ docker kill 容器名或ID
|
||||
```
|
||||
|
||||
#### 与 stop 的区别
|
||||
|
||||
| 命令 | 信号 | 使用场景 |
|
||||
|------|------|---------|
|
||||
| `docker stop` | SIGTERM → SIGKILL | 正常停止,让应用优雅退出 |
|
||||
| `docker kill` | SIGKILL | 应用无响应,强制终止 |
|
||||
|
||||
#### 发送自定义信号
|
||||
|
||||
```bash
|
||||
## 发送 SIGHUP(让进程重新加载配置)
|
||||
|
||||
$ docker kill -s HUP mycontainer
|
||||
|
||||
## 发送 SIGTERM
|
||||
|
||||
$ docker kill -s TERM mycontainer
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5.3.4 容器自动终止
|
||||
|
||||
容器的生命周期与主进程绑定。主进程退出时,容器自动停止:
|
||||
|
||||
```bash
|
||||
## 主进程是交互式 bash
|
||||
|
||||
$ docker run -it ubuntu bash
|
||||
root@abc123:/# exit # 退出 bash → 容器停止
|
||||
|
||||
## 主进程执行完毕
|
||||
|
||||
$ docker run ubuntu echo "Hello" # echo 执行完 → 容器停止
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5.3.5 查看已停止的容器
|
||||
|
||||
```bash
|
||||
$ docker ps -a
|
||||
CONTAINER ID IMAGE COMMAND STATUS NAMES
|
||||
ba267838cc1b ubuntu "/bin/bash" Exited (0) 2 minutes ago myubuntu
|
||||
c5d3a5e8f7b2 nginx "nginx" Up 5 minutes mynginx
|
||||
```
|
||||
|
||||
**STATUS 字段说明**:
|
||||
|
||||
| 状态 | 说明 |
|
||||
|------|------|
|
||||
| `Up X minutes` | 运行中 |
|
||||
| `Exited (0)` | 正常退出 (退出码 0)|
|
||||
| `Exited (1)` | 异常退出 (非零退出码)|
|
||||
| `Exited (137)` | 被 SIGKILL 终止 (128 + 9)|
|
||||
| `Exited (143)` | 被 SIGTERM 终止 (128 + 15)|
|
||||
|
||||
---
|
||||
|
||||
### 5.3.6 重新启动容器
|
||||
|
||||
#### 启动已停止的容器
|
||||
|
||||
```bash
|
||||
$ docker start 容器名或ID
|
||||
|
||||
## 启动并附加终端
|
||||
|
||||
$ docker start -ai 容器名
|
||||
```
|
||||
|
||||
#### 重启运行中的容器
|
||||
|
||||
```bash
|
||||
## 先停止再启动
|
||||
|
||||
$ docker restart 容器名
|
||||
|
||||
## 自定义停止超时
|
||||
|
||||
$ docker restart -t 30 容器名
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5.3.7 生命周期状态图
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
direction TB
|
||||
[*] --> Created : docker create
|
||||
Created --> Running : docker start
|
||||
Running --> Stopped : docker stop
|
||||
Running --> Paused : docker pause
|
||||
Paused --> Running : docker unpause
|
||||
|
||||
Created --> Deleted : docker rm
|
||||
Stopped --> Deleted : docker rm
|
||||
Paused --> Deleted : docker rm
|
||||
|
||||
Deleted --> [*]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5.3.8 批量操作
|
||||
|
||||
#### 停止所有容器
|
||||
|
||||
```bash
|
||||
$ docker stop $(docker ps -q)
|
||||
```
|
||||
|
||||
#### 删除所有已停止的容器
|
||||
|
||||
```bash
|
||||
$ docker container prune
|
||||
```
|
||||
|
||||
#### 停止并删除所有容器
|
||||
|
||||
```bash
|
||||
$ docker stop $(docker ps -q) && docker container prune -f
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5.3.9 常见问题
|
||||
|
||||
#### Q:容器停止很慢
|
||||
|
||||
原因:应用没有正确处理 SIGTERM 信号,需要等待超时后强制终止。
|
||||
|
||||
解决方案:
|
||||
|
||||
1. 在应用中正确处理 SIGTERM
|
||||
2. 使用 `docker stop -t 0` 立即终止
|
||||
3. 检查 Dockerfile 中的 `STOPSIGNAL` 配置
|
||||
|
||||
#### Q:如何让容器优雅退出
|
||||
|
||||
确保容器主进程正确处理信号:
|
||||
|
||||
```dockerfile
|
||||
## Dockerfile 示例
|
||||
|
||||
FROM node:22
|
||||
...
|
||||
## 使用 exec 形式确保信号能传递给 node 进程
|
||||
|
||||
CMD ["node", "server.js"]
|
||||
```
|
||||
|
||||
#### Q:容器无法停止
|
||||
|
||||
```bash
|
||||
## 强制终止
|
||||
|
||||
$ docker kill 容器名
|
||||
|
||||
## 如果仍无法停止,检查系统资源
|
||||
|
||||
$ docker inspect 容器名
|
||||
```
|
||||
|
||||
---
|
||||
@@ -1,282 +0,0 @@
|
||||
## 5.4 进入容器
|
||||
|
||||
### 5.4.1 为什么需要进入容器
|
||||
|
||||
使用 `-d` 参数启动容器后,容器在后台运行。以下场景需要进入容器内部操作:
|
||||
|
||||
| 场景 | 示例 |
|
||||
|------|------|
|
||||
| **调试问题** | 查看日志、检查配置、排查错误 |
|
||||
| **临时操作** | 执行数据库迁移、清理缓存 |
|
||||
| **检查状态** | 查看进程、网络连接、文件系统 |
|
||||
| **开发测试** | 交互式测试命令、验证环境 |
|
||||
|
||||
### 5.4.2 两种进入方式
|
||||
|
||||
Docker 提供两种进入容器的命令:
|
||||
|
||||
| 命令 | 推荐程度 | 特点 |
|
||||
|------|---------|------|
|
||||
| `docker exec` | ✅ **推荐** | 启动新进程,退出不影响容器 |
|
||||
| `docker attach` | ⚠️ 谨慎使用 | 附加到主进程,退出可能停止容器 |
|
||||
|
||||
---
|
||||
|
||||
### 5.4.3 docker exec (推荐)
|
||||
|
||||
#### docker exec 基本用法
|
||||
|
||||
```bash
|
||||
## 进入容器并启动交互式 shell
|
||||
|
||||
$ docker exec -it 容器名 /bin/bash
|
||||
|
||||
## 或使用 sh(适用于 Alpine 等精简镜像)
|
||||
|
||||
$ docker exec -it 容器名 /bin/sh
|
||||
```
|
||||
|
||||
#### 参数说明
|
||||
|
||||
| 参数 | 作用 |
|
||||
|------|------|
|
||||
| `-i` | 保持标准输入打开 (interactive)|
|
||||
| `-t` | 分配伪终端 (TTY)|
|
||||
| `-it` | 两者组合,获得完整交互体验 |
|
||||
| `-u` | 指定用户 (如 `-u root`)|
|
||||
| `-w` | 指定工作目录 |
|
||||
| `-e` | 设置环境变量 |
|
||||
|
||||
#### docker exec 示例
|
||||
|
||||
```bash
|
||||
## 启动一个后台容器
|
||||
|
||||
$ docker run -dit --name myubuntu ubuntu
|
||||
69d137adef7a...
|
||||
|
||||
## 进入容器(交互式 shell)
|
||||
|
||||
$ docker exec -it myubuntu bash
|
||||
root@69d137adef7a:/# ls
|
||||
bin boot dev etc home lib ...
|
||||
root@69d137adef7a:/# exit
|
||||
|
||||
## 容器仍在运行!
|
||||
|
||||
$ docker ps
|
||||
CONTAINER ID IMAGE STATUS NAMES
|
||||
69d137adef7a ubuntu Up 2 minutes myubuntu
|
||||
```
|
||||
|
||||
#### 执行单条命令
|
||||
|
||||
不进入交互模式,直接执行命令:
|
||||
|
||||
```bash
|
||||
## 查看容器内进程
|
||||
|
||||
$ docker exec myubuntu ps aux
|
||||
|
||||
## 查看配置文件
|
||||
|
||||
$ docker exec myubuntu cat /etc/nginx/nginx.conf
|
||||
|
||||
## 以 root 用户执行
|
||||
|
||||
$ docker exec -u root myubuntu apt update
|
||||
```
|
||||
|
||||
#### 只用 -i 不用 -t 的区别
|
||||
|
||||
```bash
|
||||
## 只用 -i:可以执行命令,但没有提示符
|
||||
|
||||
$ docker exec -i myubuntu bash
|
||||
ls # 输入命令
|
||||
bin # 输出结果
|
||||
boot
|
||||
dev
|
||||
...
|
||||
|
||||
## 用 -it:有完整的终端体验
|
||||
|
||||
$ docker exec -it myubuntu bash
|
||||
root@69d137adef7a:/# # 有提示符
|
||||
```
|
||||
|
||||
> 💡 通常使用 `-it` 组合。只有在脚本中需要通过管道传入命令时才只用 `-i`。
|
||||
|
||||
---
|
||||
|
||||
### 5.4.4 docker attach (谨慎使用)
|
||||
|
||||
#### docker attach 基本用法
|
||||
|
||||
```bash
|
||||
$ docker attach 容器名
|
||||
```
|
||||
|
||||
#### 工作原理
|
||||
|
||||
`attach` 会附加到容器的 **主进程** (PID 1) 的标准输入输出:
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph Container ["容器"]
|
||||
direction TB
|
||||
subgraph Process ["主进程"]
|
||||
P1["PID 1: /bin/bash<br>(你的输入直接发送到主进程)"]
|
||||
end
|
||||
end
|
||||
Attach["docker attach"] -->|"附加到这里"| P1
|
||||
```
|
||||
|
||||
#### docker attach 示例
|
||||
|
||||
```bash
|
||||
## 启动容器
|
||||
|
||||
$ docker run -dit --name myubuntu ubuntu
|
||||
243c32535da7...
|
||||
|
||||
## 附加到容器
|
||||
|
||||
$ docker attach myubuntu
|
||||
root@243c32535da7:/#
|
||||
```
|
||||
|
||||
#### ⚠️ 重要警告
|
||||
|
||||
**从 attach 会话中输入 `exit` 或按 `Ctrl+D` 会导致容器停止!**
|
||||
|
||||
```bash
|
||||
$ docker attach myubuntu
|
||||
root@243c32535da7:/# exit # 这会停止容器!
|
||||
|
||||
$ docker ps
|
||||
CONTAINER ID IMAGE STATUS NAMES
|
||||
243c32535da7 ubuntu Exited (0) 2 seconds ago myubuntu
|
||||
```
|
||||
|
||||
**原因**:attach 附加到主进程,退出主进程就等于退出容器。
|
||||
|
||||
#### 安全退出 attach
|
||||
|
||||
使用 `Ctrl+P` 然后 `Ctrl+Q` 可以从 attach 会话中 **分离**,而不停止容器:
|
||||
|
||||
```bash
|
||||
$ docker attach myubuntu
|
||||
root@243c32535da7:/#
|
||||
## 按 Ctrl+P 然后 Ctrl+Q
|
||||
|
||||
read escape sequence
|
||||
|
||||
$ docker ps # 容器仍在运行
|
||||
CONTAINER ID IMAGE STATUS NAMES
|
||||
243c32535da7 ubuntu Up 5 minutes myubuntu
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5.4.5 exec vs attach 对比
|
||||
|
||||
| 特性 | docker exec | docker attach |
|
||||
|------|-------------|---------------|
|
||||
| **工作方式** | 在容器内启动新进程 | 附加到主进程 |
|
||||
| **退出影响** | 不影响容器 | 可能停止容器 |
|
||||
| **多终端** | 可以开多个 | 共享同一个会话 |
|
||||
| **适用场景** | 调试、临时操作 | 查看主进程输出 |
|
||||
| **推荐程度** | ✅ 推荐 | ⚠️ 特殊场景使用 |
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph Exec ["docker exec"]
|
||||
direction TB
|
||||
subgraph Container1 ["容器"]
|
||||
E_PID1["PID 1: nginx"]
|
||||
E_PID50["PID 50: bash"]
|
||||
end
|
||||
NewProc["新进程"] -- 附加到 --> E_PID50
|
||||
end
|
||||
|
||||
subgraph Attach ["docker attach"]
|
||||
direction TB
|
||||
subgraph Container2 ["容器"]
|
||||
A_PID1["PID 1: bash"]
|
||||
end
|
||||
MainProc["附加到主进程"] --> A_PID1
|
||||
end
|
||||
|
||||
note1["退出 bash 不影响 nginx"]
|
||||
note2["退出 bash 容器停止"]
|
||||
Container1 -.-> note1
|
||||
Container2 -.-> note2
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5.4.6 最佳实践
|
||||
|
||||
#### 1. 首选 docker exec
|
||||
|
||||
```bash
|
||||
## 进入容器调试
|
||||
|
||||
$ docker exec -it myapp bash
|
||||
|
||||
## 查看日志
|
||||
|
||||
$ docker exec myapp tail -f /var/log/app.log
|
||||
|
||||
## 执行数据库迁移
|
||||
|
||||
$ docker exec myapp python manage.py migrate
|
||||
```
|
||||
|
||||
#### 2. 生产环境避免进入容器
|
||||
|
||||
笔者建议:生产环境应尽量避免进入容器直接操作,而是通过:
|
||||
|
||||
- 日志系统查看日志 (如 `docker logs` 或集中式日志)
|
||||
- 监控系统查看状态
|
||||
- 重新部署而非手动修改
|
||||
|
||||
#### 3. 无 shell 镜像的处理
|
||||
|
||||
某些精简镜像 (如基于 `scratch` 或 `distroless`) 没有 shell:
|
||||
|
||||
```bash
|
||||
## 这会失败
|
||||
|
||||
$ docker exec -it myapp bash
|
||||
OCI runtime exec failed: exec failed: unable to start container process: exec: "bash": executable file not found
|
||||
|
||||
## 解决方案:使用调试容器(Docker Desktop 或 Kubernetes debug)
|
||||
|
||||
$ docker debug myapp
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5.4.7 常见问题
|
||||
|
||||
#### 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,246 +0,0 @@
|
||||
## 5.6 删除
|
||||
|
||||
随着容器的创建和停止,系统中会积累大量的容器。本节将介绍如何删除不再需要的容器,以及如何清理所有停止的容器。
|
||||
|
||||
### 5.6.1 基本用法
|
||||
|
||||
使用 `docker rm` 删除已停止的容器:
|
||||
|
||||
```bash
|
||||
$ docker rm 容器名或ID
|
||||
```
|
||||
|
||||
> 💡 `docker rm` 是 `docker container rm` 的简写,两者等效。
|
||||
|
||||
---
|
||||
|
||||
### 5.6.2 删除选项
|
||||
|
||||
| 选项 | 说明 | 示例 |
|
||||
|------|------|------|
|
||||
| 无参数 | 删除已停止的容器 | `docker rm mycontainer` |
|
||||
| `-f` | 强制删除运行中的容器 | `docker rm -f mycontainer` |
|
||||
| `-v` | 同时删除关联的匿名卷 | `docker rm -v mycontainer` |
|
||||
|
||||
#### 删除已停止的容器
|
||||
|
||||
```bash
|
||||
$ docker rm mycontainer
|
||||
mycontainer
|
||||
```
|
||||
|
||||
#### 强制删除运行中的容器
|
||||
|
||||
```bash
|
||||
## 不加 -f 会报错
|
||||
|
||||
$ docker rm running_container
|
||||
Error: cannot remove running container
|
||||
|
||||
## 加 -f 强制删除
|
||||
|
||||
$ docker rm -f running_container
|
||||
running_container
|
||||
```
|
||||
|
||||
> ⚠️ 强制删除会向容器发送 SIGKILL 信号,可能导致数据丢失。建议先 `docker stop` 优雅停止。
|
||||
|
||||
#### 删除容器及其数据卷
|
||||
|
||||
```bash
|
||||
## 删除容器时同时删除其匿名卷
|
||||
|
||||
$ docker rm -v mycontainer
|
||||
```
|
||||
|
||||
> 注意:只删除匿名卷,命名卷不会被删除。
|
||||
|
||||
---
|
||||
|
||||
### 5.6.3 批量删除
|
||||
|
||||
#### 删除所有已停止的容器
|
||||
|
||||
```bash
|
||||
## 方式一:使用 prune 命令(推荐)
|
||||
|
||||
$ docker container prune
|
||||
|
||||
WARNING! This will remove all stopped containers.
|
||||
Are you sure you want to continue? [y/N] y
|
||||
Deleted Containers:
|
||||
abc123...
|
||||
def456...
|
||||
Total reclaimed space: 150MB
|
||||
|
||||
## 方式二:不提示确认
|
||||
|
||||
$ docker container prune -f
|
||||
```
|
||||
|
||||
#### 删除所有容器 (包括运行中的)
|
||||
|
||||
```bash
|
||||
## 先停止所有容器,再删除
|
||||
|
||||
$ docker stop $(docker ps -q)
|
||||
$ docker rm $(docker ps -aq)
|
||||
|
||||
## 或者直接强制删除
|
||||
|
||||
$ docker rm -f $(docker ps -aq)
|
||||
```
|
||||
|
||||
#### 按条件删除
|
||||
|
||||
```bash
|
||||
## 删除所有已退出的容器
|
||||
|
||||
$ docker rm $(docker ps -aq -f status=exited)
|
||||
|
||||
## 删除名称包含 "test" 的容器
|
||||
|
||||
$ docker rm $(docker ps -aq -f name=test)
|
||||
|
||||
## 删除 24 小时前创建的容器
|
||||
|
||||
$ docker container prune --filter "until=24h"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5.6.4 常用过滤条件
|
||||
|
||||
`docker ps` 的过滤条件可以配合 `rm` 使用:
|
||||
|
||||
| 过滤条件 | 说明 | 示例 |
|
||||
|---------|------|------|
|
||||
| `status=exited` | 已退出的容器 | `-f status=exited` |
|
||||
| `status=created` | 已创建未启动 | `-f status=created` |
|
||||
| `name=xxx` | 名称匹配 | `-f name=myapp` |
|
||||
| `ancestor=xxx` | 基于某镜像创建 | `-f ancestor=nginx` |
|
||||
| `before=xxx` | 在某容器之前创建 | `-f before=mycontainer` |
|
||||
| `since=xxx` | 在某容器之后创建 | `-f since=mycontainer` |
|
||||
|
||||
#### 示例
|
||||
|
||||
```bash
|
||||
## 删除所有基于 nginx 镜像的容器
|
||||
|
||||
$ docker rm $(docker ps -aq -f ancestor=nginx)
|
||||
|
||||
## 删除所有创建后未启动的容器
|
||||
|
||||
$ docker rm $(docker ps -aq -f status=created)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5.6.5 容器与镜像的依赖关系
|
||||
|
||||
> 有容器依赖的镜像无法删除。
|
||||
|
||||
```bash
|
||||
## 尝试删除有容器依赖的镜像
|
||||
|
||||
$ docker image rm nginx
|
||||
Error: image is being used by stopped container abc123
|
||||
|
||||
## 需要先删除依赖该镜像的容器
|
||||
|
||||
$ docker rm abc123
|
||||
$ docker image rm nginx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5.6.6 清理策略建议
|
||||
|
||||
#### 开发环境
|
||||
|
||||
```bash
|
||||
## 定期清理已停止的容器
|
||||
|
||||
$ docker container prune -f
|
||||
|
||||
## 一键清理所有未使用资源
|
||||
|
||||
$ docker system prune -f
|
||||
```
|
||||
|
||||
#### 生产环境
|
||||
|
||||
```bash
|
||||
## 使用 --rm 参数运行临时容器
|
||||
|
||||
$ docker run --rm ubuntu echo "Hello"
|
||||
## 容器退出后自动删除
|
||||
|
||||
## 定期清理(设置保留时间)
|
||||
|
||||
$ docker container prune --filter "until=168h" # 保留 7 天内的
|
||||
```
|
||||
|
||||
#### 完整清理脚本
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
## cleanup.sh - Docker 资源清理脚本
|
||||
|
||||
echo "清理已停止的容器..."
|
||||
docker container prune -f
|
||||
|
||||
echo "清理未使用的镜像..."
|
||||
docker image prune -f
|
||||
|
||||
echo "清理未使用的数据卷..."
|
||||
docker volume prune -f
|
||||
|
||||
echo "清理未使用的网络..."
|
||||
docker network prune -f
|
||||
|
||||
echo "清理完成!"
|
||||
docker system df
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5.6.7 常见问题
|
||||
|
||||
#### Q:容器无法删除
|
||||
|
||||
```bash
|
||||
Error: container is running
|
||||
```
|
||||
|
||||
解决:先停止容器,或使用 `-f` 强制删除
|
||||
|
||||
```bash
|
||||
$ docker stop mycontainer
|
||||
$ docker rm mycontainer
|
||||
## 或
|
||||
|
||||
$ docker rm -f mycontainer
|
||||
```
|
||||
|
||||
#### Q:删除后磁盘空间没释放
|
||||
|
||||
可能原因:
|
||||
|
||||
1. 容器的数据卷未删除 (使用 `-v` 参数)
|
||||
2. 镜像未删除
|
||||
3. 构建缓存未清理
|
||||
|
||||
解决:
|
||||
|
||||
```bash
|
||||
## 查看空间占用
|
||||
|
||||
$ docker system df
|
||||
|
||||
## 完整清理
|
||||
|
||||
$ docker system prune -a --volumes
|
||||
```
|
||||
|
||||
---
|
||||
@@ -1,14 +0,0 @@
|
||||
# 第五章 操作容器
|
||||
|
||||
容器是 Docker 又一核心概念。
|
||||
|
||||
简单的说,容器是独立运行的一个或一组应用,以及它们的运行态环境。对应的,虚拟机可以理解为模拟运行的一整套操作系统 (提供了运行态环境和其他系统环境) 和跑在上面的应用。
|
||||
|
||||
本章将具体介绍如何来管理一个容器,包括创建、启动和停止等。
|
||||
|
||||
* [启动容器](5.1_run.md)
|
||||
* [守护态运行](5.2_daemon.md)
|
||||
* [终止容器](5.3_stop.md)
|
||||
* [进入容器](5.4_attach_exec.md)
|
||||
* [导出和导入容器](5.5_import_export.md)
|
||||
* [删除容器](5.6_rm.md)
|
||||
@@ -1,55 +0,0 @@
|
||||
## 本章小结
|
||||
|
||||
| 操作 | 命令 | 说明 |
|
||||
|------|------|------|
|
||||
| 新建并运行 | `docker run` | 最常用的启动方式 |
|
||||
| 交互式启动 | `docker run -it` | 用于调试或临时操作 |
|
||||
| 后台运行 | `docker run -d` | 用于服务类应用 |
|
||||
| 启动已停止的容器 | `docker start` | 重用已有容器 |
|
||||
|
||||
### 5.7.1 延伸阅读
|
||||
|
||||
- [后台运行](5.2_daemon.md):理解 `-d` 参数和容器生命周期
|
||||
- [进入容器](5.4_attach_exec.md):操作运行中的容器
|
||||
- [网络配置](../09_network/README.md):理解端口映射的原理
|
||||
- [数据管理](../08_data/README.md):数据持久化方案
|
||||
|
||||
| 操作 | 命令 | 说明 |
|
||||
|------|------|------|
|
||||
| 优雅停止 | `docker stop` | 先 SIGTERM,超时后 SIGKILL |
|
||||
| 强制停止 | `docker kill` | 直接 SIGKILL |
|
||||
| 重新启动 | `docker start` | 启动已停止的容器 |
|
||||
| 重启 | `docker restart` | 停止后立即启动 |
|
||||
| 停止全部 | `docker stop $(docker ps -q)` | 停止所有运行中容器 |
|
||||
|
||||
### 5.7.2 延伸阅读
|
||||
|
||||
- [启动容器](../05_container/5.1_run.md):容器启动详解
|
||||
- [删除容器](5.6_rm.md):清理容器
|
||||
- [容器日志](5.2_daemon.md):排查停止原因
|
||||
|
||||
| 需求 | 推荐命令 |
|
||||
|------|---------|
|
||||
| 进入容器调试 | `docker exec -it 容器名 bash` |
|
||||
| 执行单条命令 | `docker exec 容器名 命令` |
|
||||
| 查看主进程输出 | `docker attach 容器名` (慎用)|
|
||||
|
||||
### 5.7.3 延伸阅读
|
||||
|
||||
- [后台运行](5.2_daemon.md):理解容器主进程
|
||||
- [查看容器](5.1_run.md):列出和过滤容器
|
||||
- [容器日志](5.2_daemon.md):查看容器输出
|
||||
|
||||
| 操作 | 命令 |
|
||||
|------|------|
|
||||
| 删除已停止容器 | `docker rm 容器名` |
|
||||
| 强制删除运行中容器 | `docker rm -f 容器名` |
|
||||
| 删除容器及匿名卷 | `docker rm -v 容器名` |
|
||||
| 清理所有已停止容器 | `docker container prune` |
|
||||
| 删除所有容器 | `docker rm -f $(docker ps -aq)` |
|
||||
|
||||
### 5.7.4 延伸阅读
|
||||
|
||||
- [终止容器](5.3_stop.md):优雅停止容器
|
||||
- [删除镜像](../04_image/4.3_rm.md):清理镜像
|
||||
- [数据卷](../08_data/8.1_volume.md):数据卷管理
|
||||
@@ -1,127 +0,0 @@
|
||||
## 6.1 Docker Hub
|
||||
|
||||
### 6.1.1 什么是 Docker Hub
|
||||
|
||||
Docker Hub 是 Docker 的中央镜像仓库,通过它您可以轻松地分享和获取 Docker 镜像。
|
||||
|
||||
|
||||
[Docker Hub](https://hub.docker.com/) 是 Docker 官方维护的公共镜像仓库,也是全球最大的容器镜像库。
|
||||
|
||||
它提供了:
|
||||
|
||||
- **官方镜像**:由 Docker 官方和软件厂商 (如 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
|
||||
## 输入用户名和密码
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
打标签并推送:
|
||||
|
||||
```bash
|
||||
## 1. 标记镜像
|
||||
|
||||
$ docker tag myapp:v1 username/myapp:v1
|
||||
|
||||
## 2. 推送
|
||||
|
||||
$ docker push username/myapp:v1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 6.1.3 限制与配额
|
||||
|
||||
#### 镜像拉取限制
|
||||
|
||||
自 2020 年 11 月起,Docker Hub 对匿名和免费用户实施了拉取速率限制:
|
||||
|
||||
| 用户类型 | 限制 |
|
||||
|---------|------|
|
||||
| **匿名用户** (未登录) | 每 6 小时 100 次请求 |
|
||||
| **免费账户** (已登录) | 每 6 小时 200 次请求 |
|
||||
| **Pro/Team 账户** | 无限制 |
|
||||
|
||||
> **提示**:如果在 CI/CD 环境中遇到 `toomanyrequests` 错误,建议:
|
||||
> 1. 在 CI 中配置 `docker login`
|
||||
> 2. 使用国内镜像加速器
|
||||
> 3. 搭建私有仓库代理
|
||||
|
||||
---
|
||||
|
||||
### 6.1.4 安全最佳实践
|
||||
|
||||
#### 1. 启用 2FA (双因素认证)
|
||||
|
||||
为了保护您的 Docker Hub 账号安全,我们建议采取以下措施。
|
||||
|
||||
|
||||
在 Account Settings -> Security 中启用 2FA,保护账号安全。启用后,CLI 登录需要使用 **Access Token** 而非密码。
|
||||
|
||||
#### 2. 使用 Access Token
|
||||
|
||||
不要在脚本或 CI/CD 中直接使用登录密码。
|
||||
|
||||
1. 在 Docker Hub -> Account Settings -> Security -> Access Tokens 创建 Token。
|
||||
2. 使用 Token 作为密码登录:
|
||||
|
||||
```bash
|
||||
$ docker login -u username -p dckr_pat_xxxxxxx
|
||||
```
|
||||
|
||||
#### 3. 关注镜像漏洞
|
||||
|
||||
Docker Hub 会对官方镜像和付费用户的镜像进行安全扫描。在镜像标签页可以看到漏洞扫描结果。
|
||||
|
||||
---
|
||||
|
||||
### 6.1.5 Webhooks
|
||||
|
||||
当镜像被推送时,可以自动触发 HTTP 回调 (例如通知 CI 系统部署)。
|
||||
|
||||
**配置方法**:
|
||||
仓库页面 -> Webhooks -> Create Webhook。
|
||||
|
||||
---
|
||||
|
||||
### 6.1.6 自动构建
|
||||
|
||||
> ⚠️ 目前仅限付费用户 (Pro/Team) 使用。
|
||||
|
||||
链接 GitHub/Bitbucket 仓库后,当代码有提交或打标签时,Docker Hub 会自动运行构建。这保证了镜像总是与代码同步,且由可信的官方环境构建。
|
||||
|
||||
---
|
||||
@@ -1,126 +0,0 @@
|
||||
## 6.4 Nexus 3
|
||||
|
||||
使用 Docker 官方的 Registry 创建的仓库面临一些维护问题。比如某些镜像删除以后空间默认是不会回收的,需要一些命令去回收空间然后重启 Registry。在企业中把内部的一些工具包放入 `Nexus` 中是比较常见的做法,最新版本 `Nexus3.x` 全面支持 Docker 的私有镜像。所以使用 [`Nexus3.x`](https://www.sonatype.com/product/repository-oss-download) 一个软件来管理 `Docker`,`Maven`,`Yum`,`PyPI` 等是一个明智的选择。
|
||||
|
||||
### 6.4.1 启动 Nexus 容器
|
||||
|
||||
```bash
|
||||
$ docker run -d --name nexus3 --restart=always \
|
||||
-p 8081:8081 \
|
||||
--mount src=nexus-data,target=/nexus-data \
|
||||
sonatype/nexus3
|
||||
```
|
||||
|
||||
首次运行需等待 3-5 分钟,你可以使用 `docker logs nexus3 -f` 查看日志:
|
||||
|
||||
```bash
|
||||
$ docker logs nexus3 -f
|
||||
|
||||
2021-03-11 15:31:21,990+0000 INFO [jetty-main-1] *SYSTEM org.sonatype.nexus.bootstrap.jetty.JettyServer -
|
||||
-------------------------------------------------
|
||||
|
||||
Started Sonatype Nexus OSS 3.30.0-01
|
||||
|
||||
-------------------------------------------------
|
||||
```
|
||||
|
||||
如果你看到以上内容,说明 `Nexus` 已经启动成功,你可以使用浏览器打开 `http://YourIP:8081` 访问 `Nexus` 了。
|
||||
|
||||
首次运行请通过以下命令获取初始密码:
|
||||
|
||||
```bash
|
||||
$ docker exec nexus3 cat /nexus-data/admin.password
|
||||
|
||||
9266139e-41a2-4abb-92ec-e4142a3532cb
|
||||
```
|
||||
|
||||
首次启动 Nexus 的默认帐号是 `admin`,密码则是上边命令获取到的,点击右上角登录,首次登录需更改初始密码。
|
||||
|
||||
登录之后可以点击页面上方的齿轮按钮按照下面的方法进行设置。
|
||||
|
||||
### 6.4.2 创建仓库
|
||||
|
||||
创建一个私有仓库的方法:`Repository->Repositories` 点击右边菜单 `Create repository` 选择 `docker (hosted)`
|
||||
|
||||
* **Name**:仓库的名称
|
||||
* **HTTP**:仓库单独的访问端口 (例如:**5001**)
|
||||
* **Hosted -> Deployment policy**:请选择 **Allow redeploy** 否则无法上传 Docker 镜像。
|
||||
|
||||
其它的仓库创建方法请各位自己摸索,还可以创建一个 `docker (proxy)` 类型的仓库链接到 DockerHub 上。再创建一个 `docker (group)` 类型的仓库把刚才的 `hosted` 与 `proxy` 添加在一起。主机在访问的时候默认下载私有仓库中的镜像,如果没有将链接到 DockerHub 中下载并缓存到 Nexus 中。
|
||||
|
||||
### 6.4.3 添加访问权限
|
||||
|
||||
菜单 `Security->Realms` 把 Docker Bearer Token Realm 移到右边的框中保存。
|
||||
|
||||
添加用户规则:菜单 `Security->Roles`->`Create role` 在 `Privileges` 选项搜索 docker 把相应的规则移动到右边的框中然后保存。
|
||||
|
||||
添加用户:菜单 `Security->Users`->`Create local user` 在 `Roles` 选项中选中刚才创建的规则移动到右边的窗口保存。
|
||||
|
||||
### 6.4.4 NGINX 反向代理配置
|
||||
|
||||
证书的生成请参见 [`私有仓库高级配置`](6.3_registry_auth.md) 里面证书生成一节。
|
||||
|
||||
NGINX 示例配置如下
|
||||
|
||||
```nginx
|
||||
upstream register
|
||||
{
|
||||
server "YourHostName OR IP":5001; #端口为上面添加私有镜像仓库时设置的 HTTP 选项的端口号
|
||||
check interval=3000 rise=2 fall=10 timeout=1000 type=http;
|
||||
check_http_send "HEAD / HTTP/1.0\r\n\r\n";
|
||||
check_http_expect_alive http_4xx;
|
||||
}
|
||||
|
||||
server {
|
||||
server_name YourDomainName;#如果没有 DNS 服务器做解析,请删除此选项使用本机 IP 地址访问
|
||||
listen 443 ssl;
|
||||
|
||||
ssl_certificate key/example.crt;
|
||||
ssl_certificate_key key/example.key;
|
||||
|
||||
ssl_session_timeout 5m;
|
||||
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
|
||||
ssl_ciphers HIGH:!aNULL:!MD5;
|
||||
ssl_prefer_server_ciphers on;
|
||||
large_client_header_buffers 4 32k;
|
||||
client_max_body_size 300m;
|
||||
client_body_buffer_size 512k;
|
||||
proxy_connect_timeout 600;
|
||||
proxy_read_timeout 600;
|
||||
proxy_send_timeout 600;
|
||||
proxy_buffer_size 128k;
|
||||
proxy_buffers 4 64k;
|
||||
proxy_busy_buffers_size 128k;
|
||||
proxy_temp_file_write_size 512k;
|
||||
|
||||
location / {
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-Port $server_port;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
proxy_redirect off;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_pass http://register;
|
||||
proxy_read_timeout 900s;
|
||||
|
||||
}
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
}
|
||||
```
|
||||
|
||||
### 6.4.5 Docker 主机访问镜像仓库
|
||||
|
||||
如果不启用 SSL 加密可以通过[前面章节](6.2_registry.md)的方法添加非 https 仓库地址到 Docker 的配置文件中然后重启 Docker。
|
||||
|
||||
使用 SSL 加密以后程序需要访问就不能采用修改配置的方式了。具体方法如下:
|
||||
|
||||
```bash
|
||||
$ openssl s_client -showcerts -connect YourDomainName OR HostIP:443 </dev/null 2>/dev/null|openssl x509 -outform PEM >ca.crt
|
||||
$ cat ca.crt | sudo tee -a /etc/ssl/certs/ca-certificates.crt
|
||||
$ systemctl restart docker
|
||||
```
|
||||
|
||||
使用 `docker login YourDomainName OR HostIP` 进行测试,用户名密码填写上面 Nexus 中设置的。
|
||||
@@ -1,7 +0,0 @@
|
||||
# 第六章 访问仓库
|
||||
|
||||
仓库 (`Repository`) 是集中存放镜像的地方。
|
||||
|
||||
一个容易混淆的概念是注册服务器 (`Registry`)。实际上注册服务器是管理仓库的具体服务器,每个服务器上可以有多个仓库,而每个仓库下面有多个镜像。从这方面来说,仓库可以被认为是一个具体的项目或目录。例如对于仓库地址 `docker.io/ubuntu` 来说,`docker.io` 是注册服务器地址,`ubuntu` 是仓库名。
|
||||
|
||||
大部分时候,并不需要严格区分这两者的概念。
|
||||
@@ -1,15 +0,0 @@
|
||||
## 本章小结
|
||||
|
||||
| 功能 | 说明 |
|
||||
|------|------|
|
||||
| **官方镜像** | 优先使用的基础镜像 |
|
||||
| **拉取限制** | 匿名 100次/6h,登录 200次/6h |
|
||||
| **安全** | 推荐开启 2FA 并使用 Access Token |
|
||||
| **自动化** | 支持 Webhooks 和自动构建 |
|
||||
|
||||
### 6.5.1 概述
|
||||
|
||||
### 6.5.2 延伸阅读
|
||||
|
||||
- [私有仓库](6.2_registry.md):搭建自己的 Registry
|
||||
- [镜像加速器](../03_install/3.9_mirror.md):加速下载
|
||||
@@ -1,190 +0,0 @@
|
||||
## 7.10 WORKDIR 指定工作目录
|
||||
|
||||
### 7.10.1 基本语法
|
||||
|
||||
```docker
|
||||
WORKDIR <工作目录路径>
|
||||
```
|
||||
|
||||
`WORKDIR` 指定后续指令的工作目录。如果目录不存在,Docker 会自动创建。
|
||||
|
||||
---
|
||||
|
||||
### 7.10.2 基本用法
|
||||
|
||||
```docker
|
||||
WORKDIR /app
|
||||
|
||||
RUN pwd # 输出 /app
|
||||
RUN echo "hello" > world.txt # 创建 /app/world.txt
|
||||
COPY . . # 复制到 /app/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.10.3 为什么需要 WORKDIR
|
||||
|
||||
#### 常见错误
|
||||
|
||||
```docker
|
||||
## ❌ 错误:cd 在下一个 RUN 中无效
|
||||
|
||||
RUN cd /app
|
||||
RUN echo "hello" > world.txt # 文件在根目录!
|
||||
```
|
||||
|
||||
#### 原因分析
|
||||
|
||||
```dockerfile
|
||||
RUN cd /app
|
||||
↓
|
||||
启动容器 → cd /app(仅内存变化)→ 提交镜像层 → 容器销毁
|
||||
│
|
||||
↓ 工作目录未改变!
|
||||
RUN echo "hello" > world.txt
|
||||
↓
|
||||
启动新容器(工作目录在 /)→ 创建 /world.txt
|
||||
```
|
||||
|
||||
每个 RUN 都在新容器中执行,**前一个 RUN 的内存状态 (包括工作目录) 不会保留**。
|
||||
|
||||
#### 正确做法
|
||||
|
||||
```docker
|
||||
## ✅ 正确:使用 WORKDIR
|
||||
|
||||
WORKDIR /app
|
||||
RUN echo "hello" > world.txt # 创建 /app/world.txt
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.10.4 相对路径
|
||||
|
||||
WORKDIR 支持相对路径,基于上一个 WORKDIR:
|
||||
|
||||
```docker
|
||||
WORKDIR /a
|
||||
WORKDIR b
|
||||
WORKDIR c
|
||||
|
||||
RUN pwd # 输出 /a/b/c
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.10.5 使用环境变量
|
||||
|
||||
```docker
|
||||
ENV APP_HOME=/app
|
||||
WORKDIR $APP_HOME
|
||||
|
||||
RUN pwd # 输出 /app
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.10.6 多阶段构建中的 WORKDIR
|
||||
|
||||
```docker
|
||||
## 构建阶段
|
||||
|
||||
FROM node:20 AS builder
|
||||
WORKDIR /build
|
||||
COPY package*.json ./
|
||||
RUN npm install
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
## 生产阶段
|
||||
|
||||
FROM nginx:alpine
|
||||
WORKDIR /usr/share/nginx/html
|
||||
COPY --from=builder /build/dist .
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.10.7 最佳实践
|
||||
|
||||
#### 1. 尽早设置 WORKDIR
|
||||
|
||||
```docker
|
||||
FROM node:20
|
||||
WORKDIR /app # 尽早设置
|
||||
|
||||
COPY package*.json ./
|
||||
RUN npm install
|
||||
COPY . .
|
||||
CMD ["node", "server.js"]
|
||||
```
|
||||
|
||||
#### 2. 使用绝对路径
|
||||
|
||||
```docker
|
||||
## ✅ 推荐:绝对路径,意图明确
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
## ⚠️ 避免:相对路径可能造成混淆
|
||||
|
||||
WORKDIR app
|
||||
```
|
||||
|
||||
#### 3. 不要用 RUN cd
|
||||
|
||||
```docker
|
||||
## ❌ 避免
|
||||
|
||||
RUN cd /app && echo "hello" > world.txt
|
||||
|
||||
## ✅ 推荐
|
||||
|
||||
WORKDIR /app
|
||||
RUN echo "hello" > world.txt
|
||||
```
|
||||
|
||||
#### 4. 适时重置 WORKDIR
|
||||
|
||||
```docker
|
||||
WORKDIR /app
|
||||
## ... 应用相关操作 ...
|
||||
|
||||
WORKDIR /data
|
||||
## ... 数据相关操作 ...
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.10.8 与其他指令的关系
|
||||
|
||||
| 指令 | WORKDIR 的影响 |
|
||||
|------|---------------|
|
||||
| `RUN` | 在 WORKDIR 中执行命令 |
|
||||
| `CMD` | 在 WORKDIR 中启动 |
|
||||
| `ENTRYPOINT` | 在 WORKDIR 中启动 |
|
||||
| `COPY` | 相对目标路径基于 WORKDIR |
|
||||
| `ADD` | 相对目标路径基于 WORKDIR |
|
||||
|
||||
```docker
|
||||
WORKDIR /app
|
||||
|
||||
RUN pwd # /app
|
||||
COPY . . # 复制到 /app
|
||||
CMD ["./start.sh"] # /app/start.sh
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.10.9 运行时覆盖
|
||||
|
||||
使用 `-w` 参数覆盖工作目录:
|
||||
|
||||
```bash
|
||||
$ docker run -w /tmp myimage pwd
|
||||
/tmp
|
||||
```
|
||||
|
||||
---
|
||||
@@ -1,279 +0,0 @@
|
||||
## 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,196 +0,0 @@
|
||||
## 7.12 HEALTHCHECK 健康检查
|
||||
|
||||
### 7.12.1 基本语法
|
||||
|
||||
```docker
|
||||
HEALTHCHECK [选项] CMD <命令>
|
||||
HEALTHCHECK NONE
|
||||
```
|
||||
|
||||
`HEALTHCHECK` 指令告诉 Docker 如何判断容器状态是否正常。这是保障服务高可用的重要机制。
|
||||
|
||||
---
|
||||
|
||||
### 7.12.2 为什么需要 HEALTHCHECK
|
||||
|
||||
在没有 HEALTHCHECK 之前,Docker 只能通过 **进程退出码** 来判断容器状态。**问题场景**:
|
||||
|
||||
- Web 服务死锁,无法响应请求,但进程仍在运行
|
||||
- 数据库正在启动中,尚未准备好接受连接
|
||||
- 应用陷入死循环,CPU 爆满但进程存活
|
||||
|
||||
**引入 HEALTHCHECK 后**:
|
||||
Docker 定期执行指定的检查命令,根据返回值判断容器是否 “健康”。
|
||||
|
||||
```bash
|
||||
容器状态转换:
|
||||
Starting ──成功──> Healthy ──失败N次──> Unhealthy
|
||||
▲ │
|
||||
└──────成功──────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.12.3 基本用法
|
||||
|
||||
#### Web 服务检查
|
||||
|
||||
```docker
|
||||
FROM nginx
|
||||
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
|
||||
CMD curl -fs http://localhost/ || exit 1
|
||||
```
|
||||
|
||||
#### 命令返回值
|
||||
|
||||
- `0`:成功 (healthy)
|
||||
- `1`:失败 (unhealthy)
|
||||
- `2`:保留值 (不使用)
|
||||
|
||||
#### 常用选项
|
||||
|
||||
| 选项 | 说明 | 默认值 |
|
||||
|------|------|--------|
|
||||
| `--interval` | 两次检查的间隔 | 30s |
|
||||
| `--timeout` | 检查命令的超时时间 | 30s |
|
||||
| `--start-period` | 启动缓冲期 (期间失败不计入次数)| 0s |
|
||||
| `--retries` | 连续失败多少次标记为 unhealthy | 3 |
|
||||
|
||||
---
|
||||
|
||||
### 7.12.4 屏蔽健康检查
|
||||
|
||||
如果基础镜像定义了 HEALTHCHECK,但你不想使用它:
|
||||
|
||||
```docker
|
||||
FROM my-base-image
|
||||
HEALTHCHECK NONE
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.12.5 常见检查脚本
|
||||
|
||||
#### HTTP 服务
|
||||
|
||||
使用 `curl` 或 `wget`:
|
||||
|
||||
```docker
|
||||
## 使用 curl
|
||||
|
||||
HEALTHCHECK CMD curl -f http://localhost/ || exit 1
|
||||
|
||||
## 使用 wget(Alpine 默认包含)
|
||||
|
||||
HEALTHCHECK CMD wget -q --spider http://localhost/ || exit 1
|
||||
```
|
||||
|
||||
#### 数据库
|
||||
|
||||
```docker
|
||||
## MySQL
|
||||
|
||||
HEALTHCHECK CMD mysqladmin ping -h localhost || exit 1
|
||||
|
||||
## Redis
|
||||
|
||||
HEALTHCHECK CMD redis-cli ping || exit 1
|
||||
```
|
||||
|
||||
#### 自定义脚本
|
||||
|
||||
```docker
|
||||
COPY healthcheck.sh /usr/local/bin/
|
||||
HEALTHCHECK CMD ["healthcheck.sh"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.12.6 在 Compose 中使用
|
||||
|
||||
可以在 `compose.yaml` (或 `docker-compose.yml`) 中覆盖或定义健康检查:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
web:
|
||||
image: nginx
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost"]
|
||||
interval: 1m30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
```
|
||||
|
||||
带健康检查的依赖启动:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
web:
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy # 等待 db 变健康才启动 web
|
||||
db:
|
||||
image: mysql
|
||||
healthcheck:
|
||||
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.12.7 查看健康状态
|
||||
|
||||
```bash
|
||||
## 查看容器状态(包含健康信息)
|
||||
|
||||
$ docker ps
|
||||
CONTAINER ID STATUS
|
||||
abc123 Up 1 minute (healthy)
|
||||
def456 Up 2 minutes (unhealthy)
|
||||
|
||||
## 查看详细健康日志
|
||||
|
||||
$ docker inspect --format '{{json .State.Health}}' mycontainer | jq
|
||||
{
|
||||
"Status": "healthy",
|
||||
"FailingStreak": 0,
|
||||
"Log": [
|
||||
{
|
||||
"Start": "...",
|
||||
"End": "...",
|
||||
"ExitCode": 0,
|
||||
"Output": "..."
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.12.8 最佳实践
|
||||
|
||||
#### 1. 避免副作用
|
||||
|
||||
健康检查会被频繁执行,不要在检查脚本中进行写操作或消耗大量资源的操作。
|
||||
|
||||
#### 2. 使用轻量级工具
|
||||
|
||||
优先使用镜像中已有的工具 (如 `wget`),避免为了健康检查安装庞大的依赖 (如 `curl`)。
|
||||
|
||||
#### 3. 设置合理的 Start Period
|
||||
|
||||
应用启动可能需要时间 (如 Java 应用)。设置 `--start-period` 可以防止在启动阶段因检查失败而误判。
|
||||
|
||||
```docker
|
||||
## 给应用 1 分钟启动时间
|
||||
|
||||
HEALTHCHECK --start-period=60s CMD curl -f http://localhost/ || exit 1
|
||||
```
|
||||
|
||||
#### 4. 只检查核心依赖
|
||||
|
||||
健康检查应主要关注 **当前服务** 是否可用,而不是检查其下游依赖 (数据库等)。下游依赖的检查应由应用逻辑处理。
|
||||
|
||||
---
|
||||
@@ -1,145 +0,0 @@
|
||||
## 7.13 ONBUILD 为他人作嫁衣裳
|
||||
|
||||
### 7.13.1 基本语法
|
||||
|
||||
```docker
|
||||
ONBUILD <其它指令>
|
||||
```
|
||||
|
||||
`ONBUILD` 是一个特殊的指令,它后面跟的是其它指令 (如 `RUN`,`COPY` 等),这些指令 **在当前镜像构建时不会执行**,只有当以当前镜像为基础镜像去构建下一级镜像时才会被执行。
|
||||
|
||||
---
|
||||
|
||||
### 7.13.2 为什么需要 ONBUILD
|
||||
|
||||
`ONBUILD` 主要用于制作 **语言栈基础镜像** 或 **框架基础镜像**。
|
||||
|
||||
#### 场景:维护 Node.js 项目
|
||||
|
||||
假设你有多个 Node.js 项目,它们的构建流程都一样:
|
||||
|
||||
1. 创建目录
|
||||
2. 复制 `package.json`
|
||||
3. 执行 `npm install`
|
||||
4. 复制源码
|
||||
5. 启动应用
|
||||
|
||||
如果不使用 `ONBUILD`,每个项目的 Dockerfile 都要重复这些步骤,且通过 `COPY` 复制文件时,基础镜像无法预知子项目的文件名。
|
||||
|
||||
#### 使用 ONBUILD 的解决方案
|
||||
|
||||
**基础镜像 (my-node-base)**:
|
||||
|
||||
```docker
|
||||
FROM node:20-alpine
|
||||
WORKDIR /app
|
||||
|
||||
## 这些指令将在子镜像构建时执行
|
||||
|
||||
ONBUILD COPY package*.json ./
|
||||
ONBUILD RUN npm install
|
||||
ONBUILD COPY . .
|
||||
|
||||
CMD ["npm", "start"]
|
||||
```
|
||||
|
||||
**子项目 Dockerfile**:
|
||||
|
||||
```docker
|
||||
FROM my-node-base
|
||||
## 只需要一行!
|
||||
|
||||
## 构建时会自动执行 COPY 和 RUN
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.13.3 执行机制
|
||||
|
||||
```bash
|
||||
基础镜像构建:
|
||||
Dockerfile (含 ONBUILD) ──build──> 基础镜像 (记录了 ONBUILD 触发器)
|
||||
(指令未执行)
|
||||
|
||||
子镜像构建:
|
||||
FROM 基础镜像 ──build──> 读取基础镜像触发器 ──> 执行触发器指令 ──> 继续执行子 Dockerfile
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.13.4 常见使用场景
|
||||
|
||||
#### 1. 自动处理依赖安装
|
||||
|
||||
```docker
|
||||
## Python 基础镜像
|
||||
|
||||
ONBUILD COPY requirements.txt ./
|
||||
ONBUILD RUN pip install -r requirements.txt
|
||||
```
|
||||
|
||||
#### 2. 自动编译代码
|
||||
|
||||
```docker
|
||||
## Go 基础镜像
|
||||
|
||||
ONBUILD COPY . .
|
||||
ONBUILD RUN go build -o app main.go
|
||||
```
|
||||
|
||||
#### 3. 处理静态资源
|
||||
|
||||
```docker
|
||||
## Nginx 静态网站基础镜像
|
||||
|
||||
ONBUILD COPY dist/ /usr/share/nginx/html/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.13.5 注意事项
|
||||
|
||||
#### 1. 继承性限制
|
||||
|
||||
`ONBUILD` 指令 **只会继承一次**。
|
||||
|
||||
- 镜像 A (含 ONBUILD)
|
||||
- 镜像 B (FROM A) -> 触发 ONBUILD
|
||||
- 镜像 C (FROM B) -> **不会** 再次触发 ONBUILD
|
||||
|
||||
#### 2. 构建上下文
|
||||
|
||||
子镜像构建时,`ONBUILD COPY . .` 中的 `.` 指的是 **子项目** 的构建上下文,而不是基础镜像的上下文。
|
||||
|
||||
#### 3. 不允许级联
|
||||
|
||||
`ONBUILD ONBUILD` 是非法的。你不能写 `ONBUILD ONBUILD COPY ...`。
|
||||
|
||||
#### 4. 可能会导致构建失败
|
||||
|
||||
由于 `ONBUILD` 实际上是在子镜像中执行指令,如果子项目的上下文不满足要求 (例如缺少 `package.json`),会导致子镜像构建失败,且错误信息可能比较隐晦。
|
||||
|
||||
---
|
||||
|
||||
### 7.13.6 最佳实践
|
||||
|
||||
#### 1. 命名规范
|
||||
|
||||
建议在镜像标签中添加 `-onbuild` 后缀,明确告知使用者该镜像包含触发器。
|
||||
|
||||
```bash
|
||||
node:20-onbuild
|
||||
python:3.12-onbuild
|
||||
```
|
||||
|
||||
#### 2. 避免执行耗时操作
|
||||
|
||||
尽量不要在 `ONBUILD` 中执行过于耗时或不确定的操作 (如更新系统软件),这会让子镜像构建变得缓慢且不可控。
|
||||
|
||||
#### 3. 清理工作
|
||||
|
||||
如果 `ONBUILD` 指令产生了临时文件,最好在同一个指令链中清理,或者提供机制让子镜像清理。
|
||||
|
||||
---
|
||||
@@ -1,144 +0,0 @@
|
||||
## 7.14 LABEL 为镜像添加元数据
|
||||
|
||||
### 7.14.1 基本语法
|
||||
|
||||
```docker
|
||||
LABEL <key>=<value> <key>=<value> ...
|
||||
```
|
||||
|
||||
`LABEL` 指令以键值对的形式给镜像添加元数据。这些数据不会影响镜像的功能,但可以帮助用户理解镜像,或被自动化工具使用。
|
||||
|
||||
---
|
||||
|
||||
### 7.14.2 为什么需要 LABEL
|
||||
|
||||
1. **版本管理**:记录版本号、构建时间、Git Commit ID
|
||||
2. **联系信息**:维护者邮箱、文档地址、支持渠道
|
||||
3. **自动化工具**:CI/CD 工具可以读取标签触发操作
|
||||
4. **许可证信息**:声明开源协议
|
||||
|
||||
---
|
||||
|
||||
### 7.14.3 基本用法
|
||||
|
||||
#### 定义单个标签
|
||||
|
||||
```docker
|
||||
LABEL version="1.0"
|
||||
LABEL description="这是一个 Web 应用服务器"
|
||||
```
|
||||
|
||||
#### 定义多个标签 (推荐)
|
||||
|
||||
```docker
|
||||
LABEL maintainer="user@example.com" \
|
||||
version="1.2.0" \
|
||||
description="My App Description" \
|
||||
org.opencontainers.image.authors="Yeasy"
|
||||
```
|
||||
|
||||
> 💡 包含空格的值需要用引号括起来。
|
||||
|
||||
---
|
||||
|
||||
### 7.14.4 常用标签规范
|
||||
|
||||
为了标准和互操作性,推荐使用 [OCI Image Format Specification](https://github.com/opencontainers/image-spec/blob/main/annotations.md#pre-defined-annotation-keys) 定义的标准标签:
|
||||
|
||||
| 标签 Key | 说明 | 示例 |
|
||||
|----------|------|------|
|
||||
| `org.opencontainers.image.created` | 构建时间(RFC 3339) | `2024-01-01T00:00:00Z` |
|
||||
| `org.opencontainers.image.authors` | 作者/维护者 | `support@example.com` |
|
||||
| `org.opencontainers.image.url` | 项目主页 | `https://example.com` |
|
||||
| `org.opencontainers.image.documentation`| 文档地址 | `https://example.com/docs` |
|
||||
| `org.opencontainers.image.source` | 源码仓库 | `https://github.com/user/repo` |
|
||||
| `org.opencontainers.image.version` | 版本号 | `1.0.0` |
|
||||
| `org.opencontainers.image.licenses` | 许可证 | `MIT` |
|
||||
| `org.opencontainers.image.title` | 镜像标题 | `My App` |
|
||||
| `org.opencontainers.image.description` | 描述 | `Production ready web server` |
|
||||
|
||||
#### 示例
|
||||
|
||||
```docker
|
||||
LABEL org.opencontainers.image.authors="yeasy" \
|
||||
org.opencontainers.image.documentation="https://yeasy.gitbooks.io" \
|
||||
org.opencontainers.image.source="https://github.com/yeasy/docker_practice" \
|
||||
org.opencontainers.image.licenses="MIT"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.14.5 MAINTAINER 指令 (已废弃)
|
||||
|
||||
旧版本的 Dockerfile 中常看到 `MAINTAINER` 指令:
|
||||
|
||||
```docker
|
||||
## ❌ 已弃用
|
||||
|
||||
MAINTAINER user@example.com
|
||||
```
|
||||
|
||||
现在推荐使用 `LABEL`:
|
||||
|
||||
```docker
|
||||
## ✅ 推荐
|
||||
|
||||
LABEL maintainer="user@example.com"
|
||||
## 或
|
||||
|
||||
LABEL org.opencontainers.image.authors="user@example.com"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.14.6 动态标签
|
||||
|
||||
配合 `ARG` 使用,可以在构建时动态注入标签:
|
||||
|
||||
```docker
|
||||
ARG BUILD_DATE
|
||||
ARG VCS_REF
|
||||
|
||||
LABEL org.opencontainers.image.created=$BUILD_DATE \
|
||||
org.opencontainers.image.revision=$VCS_REF
|
||||
```
|
||||
|
||||
构建命令:
|
||||
|
||||
```bash
|
||||
$ docker build \
|
||||
--build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ') \
|
||||
--build-arg VCS_REF=$(git rev-parse --short HEAD) \
|
||||
.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.14.7 查看标签
|
||||
|
||||
#### docker inspect
|
||||
|
||||
查看镜像的标签信息:
|
||||
|
||||
```bash
|
||||
$ docker inspect nginx --format '{{json .Config.Labels}}' | jq
|
||||
{
|
||||
"maintainer": "NGINX Docker Maintainers <docker-maint@nginx.com>"
|
||||
}
|
||||
```
|
||||
|
||||
#### 过滤器
|
||||
|
||||
可以使用标签过滤镜像:
|
||||
|
||||
```bash
|
||||
## 列出作者是 yeasy 的所有镜像
|
||||
|
||||
$ docker images --filter "label=org.opencontainers.image.authors=yeasy"
|
||||
|
||||
## 删除所有带有特定标签的镜像
|
||||
|
||||
$ docker rmi $(docker images -q --filter "label=stage=builder")
|
||||
```
|
||||
|
||||
---
|
||||
@@ -1,138 +0,0 @@
|
||||
## 7.15 SHELL 指令
|
||||
|
||||
### 7.15.1 基本语法
|
||||
|
||||
```docker
|
||||
SHELL ["executable", "parameters"]
|
||||
```
|
||||
|
||||
`SHELL` 指令允许覆盖 Docker 默认的 shell。
|
||||
|
||||
- **Linux 默认**:`["/bin/sh", "-c"]`
|
||||
- **Windows 默认**:`["cmd", "/S", "/C"]`
|
||||
|
||||
该指令会影响后续的 `RUN`,`CMD`,`ENTRYPOINT` 指令 (当它们使用 shell 格式时)。
|
||||
|
||||
---
|
||||
|
||||
### 7.15.2 为什么要用 SHELL 指令
|
||||
|
||||
#### 1. 使用 bash 特性
|
||||
|
||||
默认的 `/bin/sh` (通常是 dash 或 alpine 的 ash) 功能有限。如果你需要使用 bash 的特有功能 (如数组、`{}` 扩展、`pipefail` 等),可以切换 shell。
|
||||
|
||||
```docker
|
||||
FROM ubuntu:24.04
|
||||
|
||||
## 切换到 bash
|
||||
|
||||
SHELL ["/bin/bash", "-c"]
|
||||
|
||||
## 现在可以使用 bash 特性了
|
||||
|
||||
RUN echo {a..z}
|
||||
```
|
||||
|
||||
#### 2. 增强错误处理
|
||||
|
||||
默认情况下,管道命令 `cmd1 | cmd2` 只要 `cmd2` 成功,整个指令就视为成功。这可能掩盖构建错误。
|
||||
|
||||
```docker
|
||||
## ❌ 这里的 wget 失败了,但构建继续(因为 tar 成功了)
|
||||
|
||||
RUN wget -O - https://invalid-url | tar xz
|
||||
```
|
||||
|
||||
使用 `SHELL` 启用 `pipefail`:
|
||||
|
||||
```docker
|
||||
## ✅ 启用 pipefail
|
||||
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
|
||||
## 如果 wget 失败,整个 RUN 就会失败
|
||||
|
||||
RUN wget -O - https://invalid-url | tar xz
|
||||
```
|
||||
|
||||
#### 3. Windows 环境
|
||||
|
||||
在 Windows 容器中,经常需要在 `cmd` 和 `powershell` 之间切换。
|
||||
|
||||
```docker
|
||||
FROM mcr.microsoft.com/windows/servercore:ltsc2022
|
||||
|
||||
## 默认是 cmd
|
||||
|
||||
RUN echo Default shell is cmd
|
||||
|
||||
## 切换到 powershell
|
||||
|
||||
SHELL ["powershell", "-command"]
|
||||
RUN Write-Host "Hello from PowerShell"
|
||||
|
||||
## 切回 cmd
|
||||
|
||||
SHELL ["cmd", "/S", "/C"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.15.3 作用范围
|
||||
|
||||
`SHELL` 指令可以出现多次,每次只影响其后的指令:
|
||||
|
||||
```docker
|
||||
FROM ubuntu:24.04
|
||||
|
||||
## 使用默认 sh
|
||||
|
||||
RUN echo "Using sh"
|
||||
|
||||
SHELL ["/bin/bash", "-c"]
|
||||
## 使用 bash
|
||||
|
||||
RUN echo "Using bash"
|
||||
|
||||
SHELL ["/bin/sh", "-c"]
|
||||
## 回到 sh
|
||||
|
||||
RUN echo "Using sh again"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.15.4 对其他指令的影响
|
||||
|
||||
`SHELL` 影响的是所有使用 **shell 格式** 的指令:
|
||||
|
||||
| 指令格式 | 是否受 SHELL 影响 |
|
||||
|---------|-------------------|
|
||||
| `RUN command` | ✅ 是 |
|
||||
| `RUN ["exec", "param"]` | ❌ 否 |
|
||||
| `CMD command` | ✅ 是 |
|
||||
| `CMD ["exec", "param"]` | ❌ 否 |
|
||||
| `ENTRYPOINT command` | ✅ 是 |
|
||||
| `ENTRYPOINT ["exec", "param"]` | ❌ 否 |
|
||||
|
||||
---
|
||||
|
||||
### 7.15.5 最佳实践
|
||||
|
||||
#### 1. 推荐开启 pipefail
|
||||
|
||||
对于使用 bash 的镜像,强烈建议开启 `pipefail`,以确保构建过程中的错误能被及时捕获。
|
||||
|
||||
```docker
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
```
|
||||
|
||||
#### 2. 明确意图
|
||||
|
||||
如果由于脚本需求必须更改 shell,最好在 Dockerfile 中显式声明,而不是依赖默认行为。
|
||||
|
||||
#### 3. 尽量保持一致
|
||||
|
||||
避免在 Dockerfile 中频繁切换 SHELL,这会使构建过程难以理解和调试。尽量在头部定义一次即可。
|
||||
|
||||
---
|
||||
@@ -1,161 +0,0 @@
|
||||
## 7.1 RUN 执行命令
|
||||
|
||||
### 7.1.1 基本语法
|
||||
|
||||
```docker
|
||||
RUN <command>
|
||||
RUN ["executable", "param1", "param2"]
|
||||
```
|
||||
|
||||
`RUN` 指令是 Dockerfile 中最常用的指令之一。它在 **当前镜像层** 之上创建一个新层,执行指定的命令,并提交结果。
|
||||
|
||||
---
|
||||
|
||||
### 7.1.2 两种格式对比
|
||||
|
||||
#### 1. Shell 格式
|
||||
|
||||
```docker
|
||||
RUN apt-get update
|
||||
```
|
||||
|
||||
- **特点**:默认通过 `/bin/sh -c` 执行。
|
||||
- **优势**:可以使用环境变量、管道、重定向等 Shell 特性。
|
||||
- **示例**:
|
||||
```docker
|
||||
RUN echo "Hello" > /test.txt
|
||||
```
|
||||
|
||||
#### 2. Exec 格式
|
||||
|
||||
```docker
|
||||
RUN ["apt-get", "update"]
|
||||
```
|
||||
|
||||
- **特点**:直接调用可执行文件,不经过 Shell。
|
||||
- **优势**:避免 Shell 字符串解析问题,适用于参数中包含特殊字符的情况。
|
||||
- **注意**:无法使用 `$VAR` 环境变量替换 (除非显式调用 shell)。
|
||||
|
||||
---
|
||||
|
||||
### 7.1.3 常见最佳实践
|
||||
|
||||
#### 1. 组合命令 (减少层数)
|
||||
|
||||
每一个 `RUN` 指令都会新建一层镜像。为了减少镜像体积和层数,应使用 `&&` 连接命令。
|
||||
|
||||
**❌ 糟糕的写法** (创建 3 层):
|
||||
|
||||
```docker
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y nginx
|
||||
RUN rm -rf /var/lib/apt/lists/*
|
||||
```
|
||||
|
||||
**✅ 推荐写法** (创建 1 层):
|
||||
|
||||
```docker
|
||||
RUN apt-get update && \
|
||||
apt-get install -y nginx && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
```
|
||||
|
||||
#### 2. 清理缓存
|
||||
|
||||
在安装完软件后,立即清除缓存,可以显著减小镜像体积。
|
||||
|
||||
- **Debian/Ubuntu**:
|
||||
```docker
|
||||
RUN apt-get update && apt-get install -y package-bar \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
```
|
||||
|
||||
- **Alpine**:
|
||||
```docker
|
||||
RUN apk add --no-cache package-bar
|
||||
```
|
||||
|
||||
#### 3. 使用 `set -e` 和 `pipefail`
|
||||
|
||||
默认情况下,管道命令 `cmd1 | cmd2` 只要 `cmd2` 成功,整个 `RUN` 就视为成功。
|
||||
|
||||
**❌ 隐蔽的错误**:
|
||||
|
||||
```docker
|
||||
## 如果下载失败,gzip 可能会报错,但如果不影响后续,构建可能继续
|
||||
|
||||
RUN wget http://error-url | gzip -d > file
|
||||
```
|
||||
|
||||
**✅ 推荐写法**:
|
||||
|
||||
```docker
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
RUN wget http://url | gzip -d > file
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.1.4 常见问题
|
||||
|
||||
#### Q:为什么 `RUN cd /app` 不生效?
|
||||
|
||||
```docker
|
||||
RUN cd /app
|
||||
RUN touch hello.txt
|
||||
```
|
||||
|
||||
**结果**:`hello.txt` 会出现在根目录 `/`,而不是 `/app`。**原因**:每个 `RUN` 都在一个新的 Shell/容器环境中执行。`cd` 只影响当前 `RUN` 的环境。**解决**:使用 `WORKDIR` 指令。
|
||||
|
||||
```docker
|
||||
WORKDIR /app
|
||||
RUN touch hello.txt
|
||||
```
|
||||
|
||||
#### Q:环境变量不生效?
|
||||
|
||||
```docker
|
||||
RUN export MY_VAR=hello
|
||||
RUN echo $MY_VAR
|
||||
```
|
||||
|
||||
**结果**:输出为空。**原因**:同上,环境变量只在当前 `RUN` 有效。**解决**:使用 `ENV` 指令,或在同一行 `RUN` 中导出。
|
||||
|
||||
```docker
|
||||
ENV MY_VAR=hello
|
||||
RUN echo $MY_VAR
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.1.5 高级技巧
|
||||
|
||||
#### 1. 使用 BuildKit 的挂载缓存
|
||||
|
||||
BuildKit 支持在 `RUN` 指令中使用 `--mount` 挂载缓存,加速构建。
|
||||
|
||||
```docker
|
||||
## 缓存 apt 包
|
||||
|
||||
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,target=/var/lib/apt,sharing=locked \
|
||||
apt-get update && apt-get install -y gcc
|
||||
```
|
||||
|
||||
```docker
|
||||
## 缓存 Go 模块
|
||||
|
||||
RUN --mount=type=cache,target=/go/pkg/mod \
|
||||
go build -o app
|
||||
```
|
||||
|
||||
#### 2. 挂载密钥
|
||||
|
||||
安全地使用 SSH 密钥或 Token,而不将其记录在镜像中。
|
||||
|
||||
```docker
|
||||
RUN --mount=type=secret,id=mysecret \
|
||||
cat /run/secrets/mysecret
|
||||
```
|
||||
|
||||
---
|
||||
@@ -1,270 +0,0 @@
|
||||
## 7.2 COPY 复制文件
|
||||
|
||||
### 7.2.1 基本语法
|
||||
|
||||
```docker
|
||||
COPY [选项] <源路径>... <目标路径>
|
||||
COPY [选项] ["<源路径1>", "<源路径2>", ... "<目标路径>"]
|
||||
```
|
||||
|
||||
`COPY` 指令将构建上下文中的文件或目录复制到镜像内。
|
||||
|
||||
---
|
||||
|
||||
### 7.2.2 基本用法
|
||||
|
||||
#### 复制单个文件
|
||||
|
||||
```docker
|
||||
## 复制文件到指定目录
|
||||
|
||||
COPY package.json /app/
|
||||
|
||||
## 复制文件并重命名
|
||||
|
||||
COPY config.json /app/settings.json
|
||||
```
|
||||
|
||||
#### 复制多个文件
|
||||
|
||||
```docker
|
||||
## 复制多个指定文件
|
||||
|
||||
COPY package.json package-lock.json /app/
|
||||
|
||||
## 使用通配符
|
||||
|
||||
COPY *.json /app/
|
||||
COPY src/*.js /app/src/
|
||||
```
|
||||
|
||||
#### 复制目录
|
||||
|
||||
```docker
|
||||
## 复制整个目录的内容(不是目录本身)
|
||||
|
||||
COPY src/ /app/src/
|
||||
```
|
||||
|
||||
> ⚠️ **注意**:复制目录时,复制的是目录的 **内容**,不包含目录本身。
|
||||
|
||||
```bash
|
||||
构建上下文: 镜像内:
|
||||
src/ /app/src/
|
||||
├── index.js → ├── index.js
|
||||
└── utils.js └── utils.js
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.2.3 通配符规则
|
||||
|
||||
COPY 支持 Go 的 `filepath.Match` 通配符规则:
|
||||
|
||||
| 通配符 | 说明 | 示例 |
|
||||
|--------|------|------|
|
||||
| `*` | 匹配任意字符序列 | `*.json` |
|
||||
| `?` | 匹配单个字符 | `config?.json` |
|
||||
| `[abc]` | 匹配括号内任一字符 | `[abc].txt` |
|
||||
| `[a-z]` | 匹配范围内字符 | `file[0-9].txt` |
|
||||
|
||||
```docker
|
||||
COPY hom* /mydir/ # home.txt, homework.md 等
|
||||
COPY hom?.txt /mydir/ # home.txt, homy.txt 等
|
||||
COPY app[0-9].js /app/ # app0.js ~ app9.js
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.2.4 目标路径
|
||||
|
||||
#### 绝对路径
|
||||
|
||||
```docker
|
||||
COPY app.js /usr/src/app/
|
||||
```
|
||||
|
||||
#### 相对路径 (基于 WORKDIR)
|
||||
|
||||
```docker
|
||||
WORKDIR /app
|
||||
COPY package.json ./ # 复制到 /app/package.json
|
||||
COPY src/ ./src/ # 复制到 /app/src/
|
||||
```
|
||||
|
||||
#### 自动创建目录
|
||||
|
||||
如果目标目录不存在,Docker 会自动创建:
|
||||
|
||||
```docker
|
||||
## /app/config/ 不存在也会自动创建
|
||||
|
||||
COPY settings.json /app/config/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.2.5 修改文件所有者
|
||||
|
||||
使用 `--chown` 选项设置文件的用户和组:
|
||||
|
||||
```docker
|
||||
## 使用用户名和组名
|
||||
|
||||
COPY --chown=node:node package.json /app/
|
||||
|
||||
## 使用 UID 和 GID
|
||||
|
||||
COPY --chown=1000:1000 . /app/
|
||||
|
||||
## 只指定用户
|
||||
|
||||
COPY --chown=node . /app/
|
||||
```
|
||||
|
||||
> 💡 结合 `USER` 指令使用,确保应用以非 root 用户运行。
|
||||
|
||||
---
|
||||
|
||||
### 7.2.6 保留文件元数据
|
||||
|
||||
COPY 会保留源文件的元数据:
|
||||
|
||||
- 读、写、执行权限
|
||||
- 修改时间
|
||||
|
||||
这对于脚本文件特别重要:
|
||||
|
||||
```docker
|
||||
## start.sh 的可执行权限会被保留
|
||||
|
||||
COPY start.sh /app/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.2.7 COPY vs ADD
|
||||
|
||||
| 特性 | COPY | ADD |
|
||||
|------|------|-----|
|
||||
| 复制本地文件 | ✅ | ✅ |
|
||||
| 自动解压 tar | ❌ | ✅ |
|
||||
| 支持 URL | ❌ | ✅ (不推荐)|
|
||||
| 推荐程度 | ✅ **推荐** | ⚠️ 特殊场景使用 |
|
||||
|
||||
```docker
|
||||
## 推荐:使用 COPY
|
||||
|
||||
COPY app.tar.gz /app/
|
||||
RUN tar -xzf /app/app.tar.gz
|
||||
|
||||
## ADD 会自动解压(行为不明显,不推荐)
|
||||
|
||||
ADD app.tar.gz /app/
|
||||
```
|
||||
|
||||
> 笔者建议:除非需要自动解压 tar 文件,否则始终使用 COPY。明确的行为比隐式的魔法更好。
|
||||
|
||||
---
|
||||
|
||||
### 7.2.8 多阶段构建中的 COPY
|
||||
|
||||
#### 从其他构建阶段复制
|
||||
|
||||
```docker
|
||||
## 构建阶段
|
||||
|
||||
FROM node:20 AS builder
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
RUN npm install
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
## 生产阶段
|
||||
|
||||
FROM nginx:alpine
|
||||
COPY --from=builder /app/dist /usr/share/nginx/html
|
||||
```
|
||||
|
||||
#### 使用 --link 优化缓存
|
||||
|
||||
```docker
|
||||
## 使用 --link 后,文件以独立层添加,不依赖前序指令
|
||||
|
||||
COPY --link --from=builder /app/dist /usr/share/nginx/html
|
||||
```
|
||||
|
||||
`--link` 的优势:
|
||||
|
||||
- 更高效利用构建缓存
|
||||
- 并行化构建过程
|
||||
- 加速多阶段构建
|
||||
|
||||
---
|
||||
|
||||
### 7.2.9 dockerignore
|
||||
|
||||
使用 `.dockerignore` 排除不需要复制的文件:
|
||||
|
||||
```gitignore
|
||||
## .dockerignore
|
||||
|
||||
node_modules
|
||||
.git
|
||||
.env
|
||||
*.log
|
||||
Dockerfile
|
||||
.dockerignore
|
||||
```
|
||||
|
||||
这可以:
|
||||
|
||||
- 减小构建上下文大小
|
||||
- 加速构建
|
||||
- 避免复制敏感文件
|
||||
|
||||
---
|
||||
|
||||
### 7.2.10 最佳实践
|
||||
|
||||
#### 1. 利用缓存,先复制依赖文件
|
||||
|
||||
```docker
|
||||
## ✅ 好:先复制依赖定义,再安装,最后复制代码
|
||||
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm install
|
||||
COPY . .
|
||||
|
||||
## ❌ 差:一次性复制所有文件,代码变更会导致重新 npm install
|
||||
|
||||
COPY . .
|
||||
RUN npm install
|
||||
```
|
||||
|
||||
#### 2. 使用 .dockerignore
|
||||
|
||||
```docker
|
||||
## 确保 node_modules 不被复制
|
||||
|
||||
COPY . .
|
||||
## .dockerignore 中应包含 node_modules
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
#### 3. 明确复制路径
|
||||
|
||||
```docker
|
||||
## ✅ 好:明确的路径
|
||||
|
||||
COPY src/ /app/src/
|
||||
COPY package.json /app/
|
||||
|
||||
## ❌ 差:过于宽泛
|
||||
|
||||
COPY . .
|
||||
```
|
||||
|
||||
---
|
||||
@@ -1,225 +0,0 @@
|
||||
## 7.3 ADD 更高级的复制文件
|
||||
|
||||
### 7.3.1 基本语法
|
||||
|
||||
```docker
|
||||
ADD [选项] <源路径>... <目标路径>
|
||||
ADD [选项] ["<源路径>", ... "<目标路径>"]
|
||||
```
|
||||
|
||||
`ADD` 在 `COPY` 基础上增加了两个功能:
|
||||
|
||||
1. 自动解压 tar 压缩包
|
||||
2. 支持从 URL 下载文件 (不推荐)
|
||||
|
||||
---
|
||||
|
||||
### 7.3.2 ADD vs COPY
|
||||
|
||||
| 特性 | COPY | ADD |
|
||||
|------|------|-----|
|
||||
| 复制本地文件 | ✅ | ✅ |
|
||||
| 自动解压 tar | ❌ | ✅ |
|
||||
| 支持 URL | ❌ | ✅ (不推荐)|
|
||||
| 行为可预测性 | ✅ 高 | ⚠️ 低 |
|
||||
| 推荐程度 | ✅ **优先使用** | 仅解压场景 |
|
||||
|
||||
> 笔者建议:除非需要自动解压 tar 文件,否则始终使用 COPY。明确的行为比隐式的魔法更好。
|
||||
|
||||
---
|
||||
|
||||
### 7.3.3 自动解压功能
|
||||
|
||||
#### 基本用法 (自动解压本地 tar)
|
||||
|
||||
```docker
|
||||
## 自动解压 tar.gz 到目标目录
|
||||
|
||||
ADD app.tar.gz /app/
|
||||
```
|
||||
|
||||
ADD 会识别并解压以下格式:
|
||||
|
||||
- `.tar`
|
||||
- `.tar.gz` / `.tgz`
|
||||
- `.tar.bz2` / `.tbz2`
|
||||
- `.tar.xz` / `.txz`
|
||||
|
||||
#### 实际应用
|
||||
|
||||
官方基础镜像通常使用 ADD 解压根文件系统:
|
||||
|
||||
```docker
|
||||
FROM scratch
|
||||
ADD ubuntu-noble-core-cloudimg-amd64-root.tar.gz /
|
||||
```
|
||||
|
||||
#### 解压过程
|
||||
|
||||
```bash
|
||||
ADD app.tar.gz /app/
|
||||
│
|
||||
├─ 识别 .tar.gz 格式
|
||||
├─ 自动解压
|
||||
└─ 内容放入 /app/
|
||||
|
||||
app.tar.gz 包含: /app/ 目录结果:
|
||||
├── src/ ├── src/
|
||||
│ └── main.py │ └── main.py
|
||||
└── config.json └── config.json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.3.4 URL 下载功能 (不推荐)
|
||||
|
||||
#### 基本用法
|
||||
|
||||
```docker
|
||||
## 从 URL 下载文件
|
||||
|
||||
ADD https://example.com/app.zip /app/app.zip
|
||||
```
|
||||
|
||||
#### 为什么不推荐
|
||||
|
||||
| 问题 | 说明 |
|
||||
|------|------|
|
||||
| 权限固定 | 下载的文件权限为 600,通常需要额外 RUN 修改 |
|
||||
| 不会解压 | URL 下载的压缩包不会自动解压 |
|
||||
| 缓存问题 | URL 内容变化时不会重新下载 |
|
||||
| 层数增加 | 需要额外 RUN 清理 |
|
||||
|
||||
#### 推荐替代方案
|
||||
|
||||
```docker
|
||||
## ❌ 不推荐:使用 ADD 下载
|
||||
|
||||
ADD https://example.com/app.tar.gz /tmp/
|
||||
RUN tar -xzf /tmp/app.tar.gz -C /app && rm /tmp/app.tar.gz
|
||||
|
||||
## ✅ 推荐:使用 RUN + curl
|
||||
|
||||
RUN curl -fsSL https://example.com/app.tar.gz | tar -xz -C /app
|
||||
```
|
||||
|
||||
优势:
|
||||
|
||||
- 一条 RUN 完成下载、解压、清理
|
||||
- 减少镜像层数
|
||||
- 更清晰的构建意图
|
||||
|
||||
---
|
||||
|
||||
### 7.3.5 修改文件所有者
|
||||
|
||||
```docker
|
||||
ADD --chown=node:node app.tar.gz /app/
|
||||
ADD --chown=1000:1000 files/ /app/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.3.6 何时使用 ADD
|
||||
|
||||
#### ✅ 适合使用 ADD
|
||||
|
||||
```docker
|
||||
## 解压本地 tar 文件
|
||||
|
||||
FROM scratch
|
||||
ADD rootfs.tar.gz /
|
||||
|
||||
## 解压应用包
|
||||
|
||||
ADD dist.tar.gz /app/
|
||||
```
|
||||
|
||||
#### ❌ 不适合使用 ADD
|
||||
|
||||
```docker
|
||||
## 复制普通文件(用 COPY)
|
||||
|
||||
ADD package.json /app/ # ❌
|
||||
COPY package.json /app/ # ✅
|
||||
|
||||
## 下载文件(用 RUN + curl)
|
||||
|
||||
ADD https://example.com/file / # ❌
|
||||
RUN curl -fsSL ... -o /file # ✅
|
||||
|
||||
## 需要保留 tar 不解压(用 COPY)
|
||||
|
||||
ADD archive.tar.gz /archives/ # ❌ 会解压
|
||||
COPY archive.tar.gz /archives/ # ✅ 保持原样
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.3.7 缓存行为
|
||||
|
||||
ADD 可能导致构建缓存失效:
|
||||
|
||||
```docker
|
||||
## 如果 app.tar.gz 内容变化,此层及后续层都需重建
|
||||
|
||||
ADD app.tar.gz /app/
|
||||
RUN npm install
|
||||
```
|
||||
|
||||
**优化建议**:
|
||||
|
||||
```docker
|
||||
## 先复制依赖文件
|
||||
|
||||
COPY package*.json /app/
|
||||
RUN npm install
|
||||
|
||||
## 再添加应用代码
|
||||
|
||||
ADD app.tar.gz /app/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.3.8 最佳实践
|
||||
|
||||
#### 1. 默认使用 COPY
|
||||
|
||||
```docker
|
||||
## ✅ 大多数场景使用 COPY
|
||||
|
||||
COPY . /app/
|
||||
```
|
||||
|
||||
#### 2. 仅在需要解压时使用 ADD
|
||||
|
||||
```docker
|
||||
## ✅ 自动解压场景
|
||||
|
||||
ADD app.tar.gz /app/
|
||||
```
|
||||
|
||||
#### 3. 不要用 ADD 下载文件
|
||||
|
||||
```docker
|
||||
## ❌ 避免
|
||||
|
||||
ADD https://example.com/file.tar.gz /tmp/
|
||||
|
||||
## ✅ 推荐
|
||||
|
||||
RUN curl -fsSL https://example.com/file.tar.gz | tar -xz -C /app
|
||||
```
|
||||
|
||||
#### 4. 解压后清理
|
||||
|
||||
```docker
|
||||
## 如果需要控制解压过程
|
||||
|
||||
COPY app.tar.gz /tmp/
|
||||
RUN tar -xzf /tmp/app.tar.gz -C /app && \
|
||||
rm /tmp/app.tar.gz
|
||||
```
|
||||
|
||||
---
|
||||
@@ -1,278 +0,0 @@
|
||||
## 7.4 CMD 容器启动命令
|
||||
|
||||
### 7.4.1 什么是 CMD
|
||||
|
||||
`CMD` 指令用于指定容器启动时默认执行的命令。它定义了容器的 “主进程”。
|
||||
|
||||
> **核心概念**:容器的生命周期 = 主进程的生命周期。CMD 指定的命令就是这个主进程。
|
||||
|
||||
---
|
||||
|
||||
### 7.4.2 语法格式
|
||||
|
||||
CMD 有三种格式:
|
||||
|
||||
| 格式 | 语法 | 推荐程度 |
|
||||
|------|------|---------|
|
||||
| **exec 格式**| `CMD [“可执行文件”, “参数1”, “参数2”]` | ✅**推荐** |
|
||||
| **shell 格式** | `CMD 命令 参数1 参数2` | ⚠️ 简单场景 |
|
||||
| **参数格式** | `CMD [“参数1”, “参数2”]` | 配合 ENTRYPOINT |
|
||||
|
||||
#### exec 格式 (推荐)
|
||||
|
||||
```docker
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
CMD ["python", "app.py"]
|
||||
CMD ["node", "server.js"]
|
||||
```
|
||||
|
||||
**优点**:
|
||||
|
||||
- 直接执行指定程序,是容器的 PID 1
|
||||
- 正确接收信号 (如 SIGTERM)
|
||||
- 无需 shell 解析
|
||||
|
||||
#### shell 格式
|
||||
|
||||
```docker
|
||||
CMD echo "Hello World"
|
||||
CMD nginx -g "daemon off;"
|
||||
```
|
||||
|
||||
**实际执行**:会被包装为 `sh -c`
|
||||
|
||||
```docker
|
||||
## 你写的
|
||||
|
||||
CMD echo $HOME
|
||||
|
||||
## 实际执行的
|
||||
|
||||
CMD ["sh", "-c", "echo $HOME"]
|
||||
```
|
||||
|
||||
**优点**:可以使用环境变量、管道等 shell 特性 **缺点**:主进程是 sh,信号无法正确传递给应用
|
||||
|
||||
---
|
||||
|
||||
### 7.4.3 exec 格式 vs shell 格式
|
||||
|
||||
| 特性 | exec 格式 | shell 格式 |
|
||||
|------|----------|-----------|
|
||||
| 主进程 | 指定的程序 | `/bin/sh` |
|
||||
| 信号传递 | ✅ 正确 | ❌ 无法传递 |
|
||||
| 环境变量 | ❌ 需要 shell 包装 | ✅ 自动解析 |
|
||||
| 推荐使用 | ✅ 大多数场景 | 需要 shell 特性时 |
|
||||
|
||||
#### 信号传递问题示例
|
||||
|
||||
```docker
|
||||
## ❌ shell 格式:docker stop 会超时
|
||||
|
||||
CMD node server.js
|
||||
## 实际是 sh -c "node server.js"
|
||||
|
||||
## SIGTERM 发给 sh,不会传递给 node
|
||||
|
||||
## ✅ exec 格式:docker stop 正常工作
|
||||
|
||||
CMD ["node", "server.js"]
|
||||
## SIGTERM 直接发给 node
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.4.4 运行时覆盖 CMD
|
||||
|
||||
`docker run` 后的命令会覆盖 Dockerfile 中的 CMD:
|
||||
|
||||
```bash
|
||||
## ubuntu 默认 CMD 是 /bin/bash
|
||||
|
||||
$ docker run -it ubuntu # 进入 bash
|
||||
$ docker run ubuntu cat /etc/os-release # 覆盖为 cat 命令
|
||||
```
|
||||
|
||||
```bash
|
||||
Dockerfile: docker run 命令:
|
||||
CMD ["/bin/bash"] + cat /etc/os-release
|
||||
│ │
|
||||
└───────► 被覆盖 ◄───────┘
|
||||
↓
|
||||
执行: cat /etc/os-release
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.4.5 经典错误:容器立即退出
|
||||
|
||||
#### 错误示例
|
||||
|
||||
```docker
|
||||
## ❌ 容器启动后立即退出
|
||||
|
||||
CMD service nginx start
|
||||
```
|
||||
|
||||
#### 原因分析
|
||||
|
||||
```bash
|
||||
1. CMD service nginx start
|
||||
↓ 被转换为
|
||||
2. CMD ["sh", "-c", "service nginx start"]
|
||||
↓
|
||||
3. sh 启动,执行 service 命令
|
||||
↓
|
||||
4. service 命令将 nginx 放到后台
|
||||
↓
|
||||
5. service 命令结束,sh 退出
|
||||
↓
|
||||
6. 容器主进程(sh)退出 → 容器停止
|
||||
```
|
||||
|
||||
#### 正确做法
|
||||
|
||||
```docker
|
||||
## ✅ 让 nginx 在前台运行
|
||||
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.4.6 CMD vs ENTRYPOINT
|
||||
|
||||
| 指令 | 用途 | 运行时行为 |
|
||||
|------|------|-----------|
|
||||
| **CMD**| 默认命令 | `docker run` 参数会 **覆盖** 它 |
|
||||
| **ENTRYPOINT**| 入口点 | `docker run` 参数会 **追加** 到它后面 |
|
||||
|
||||
#### 单独使用 CMD
|
||||
|
||||
```docker
|
||||
## Dockerfile
|
||||
|
||||
CMD ["curl", "-s", "http://example.com"]
|
||||
```
|
||||
|
||||
```bash
|
||||
$ docker run myimage # 执行默认命令
|
||||
$ docker run myimage curl -v ... # 完全覆盖
|
||||
```
|
||||
|
||||
#### 搭配 ENTRYPOINT
|
||||
|
||||
```docker
|
||||
## Dockerfile
|
||||
|
||||
ENTRYPOINT ["curl", "-s"]
|
||||
CMD ["http://example.com"]
|
||||
```
|
||||
|
||||
```bash
|
||||
$ docker run myimage # curl -s http://example.com
|
||||
$ docker run myimage http://other.com # curl -s http://other.com(参数覆盖)
|
||||
```
|
||||
|
||||
详见 [ENTRYPOINT 入口点](7.5_entrypoint.md)章节。
|
||||
|
||||
---
|
||||
|
||||
### 7.4.7 最佳实践
|
||||
|
||||
#### 1. 优先使用 exec 格式
|
||||
|
||||
```docker
|
||||
## ✅ 推荐
|
||||
|
||||
CMD ["python", "app.py"]
|
||||
|
||||
## ⚠️ 仅在需要 shell 特性时使用
|
||||
|
||||
CMD ["sh", "-c", "echo $PATH && python app.py"]
|
||||
```
|
||||
|
||||
#### 2. 确保应用在前台运行
|
||||
|
||||
```docker
|
||||
## ✅ 前台运行
|
||||
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
CMD ["apache2ctl", "-D", "FOREGROUND"]
|
||||
CMD ["java", "-jar", "app.jar"]
|
||||
|
||||
## ❌ 不要使用后台服务命令
|
||||
|
||||
CMD service nginx start
|
||||
CMD systemctl start nginx
|
||||
```
|
||||
|
||||
#### 3. 使用双引号
|
||||
|
||||
```docker
|
||||
## ✅ 正确:双引号
|
||||
|
||||
CMD ["node", "server.js"]
|
||||
|
||||
## ❌ 错误:单引号(JSON 不支持)
|
||||
|
||||
CMD ['node', 'server.js']
|
||||
```
|
||||
|
||||
#### 4. 配合 ENTRYPOINT 使用
|
||||
|
||||
```docker
|
||||
## 用于可配置参数的场景
|
||||
|
||||
ENTRYPOINT ["python", "app.py"]
|
||||
CMD ["--port", "8080"]
|
||||
|
||||
## 运行时可以覆盖端口
|
||||
|
||||
$ docker run myapp --port 9000
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.4.8 常见问题
|
||||
|
||||
#### Q:CMD 可以写多个吗?
|
||||
|
||||
不可以。多个 CMD 只有最后一个生效:
|
||||
|
||||
```docker
|
||||
CMD ["echo", "first"]
|
||||
CMD ["echo", "second"] # 只有这个生效
|
||||
```
|
||||
|
||||
#### Q:如何在 CMD 中使用环境变量?
|
||||
|
||||
```docker
|
||||
## 方法1:使用 shell 格式
|
||||
|
||||
CMD echo "Port is $PORT"
|
||||
|
||||
## 方法2:显式使用 sh -c
|
||||
|
||||
CMD ["sh", "-c", "echo Port is $PORT"]
|
||||
```
|
||||
|
||||
#### Q:为什么我的容器不响应 Ctrl+C?
|
||||
|
||||
可能是使用了 shell 格式,信号被 sh 吃掉了:
|
||||
|
||||
```docker
|
||||
## ❌ 信号无法传递
|
||||
|
||||
CMD python app.py
|
||||
|
||||
## ✅ 信号正确传递
|
||||
|
||||
CMD ["python", "app.py"]
|
||||
```
|
||||
|
||||
---
|
||||
@@ -1,315 +0,0 @@
|
||||
## 7.5 ENTRYPOINT 入口点
|
||||
|
||||
### 7.5.1 什么是 ENTRYPOINT
|
||||
|
||||
`ENTRYPOINT` 指定容器启动时运行的入口程序。与 CMD 不同,ENTRYPOINT 定义的命令不会被 `docker run` 的参数覆盖,而是 **接收这些参数**。
|
||||
|
||||
> **核心作用**:让镜像像一个可执行程序一样使用,`docker run` 的参数作为这个程序的参数。
|
||||
|
||||
---
|
||||
|
||||
### 7.5.2 语法格式
|
||||
|
||||
| 格式 | 语法 | 推荐程度 |
|
||||
|------|------|---------|
|
||||
| **exec 格式**| `ENTRYPOINT [“可执行文件”, “参数1”]` | ✅**推荐** |
|
||||
| **shell 格式** | `ENTRYPOINT 命令 参数` | ⚠️ 不推荐 |
|
||||
|
||||
```docker
|
||||
## exec 格式(推荐)
|
||||
|
||||
ENTRYPOINT ["nginx", "-g", "daemon off;"]
|
||||
|
||||
## shell 格式(不推荐)
|
||||
|
||||
ENTRYPOINT nginx -g "daemon off;"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.5.3 ENTRYPOINT vs CMD
|
||||
|
||||
#### 核心区别
|
||||
|
||||
| 特性 | ENTRYPOINT | CMD |
|
||||
|------|------------|-----|
|
||||
| **定位** | 固定的入口程序 | 默认参数 |
|
||||
| **docker run 参数** | 追加为参数 | 完全覆盖 |
|
||||
| **覆盖方式** | `--entrypoint` | 直接指定命令 |
|
||||
| **适用场景** | 把镜像当命令用 | 提供默认行为 |
|
||||
|
||||
#### 行为对比
|
||||
|
||||
```docker
|
||||
## 只用 CMD
|
||||
|
||||
CMD ["curl", "-s", "http://example.com"]
|
||||
```
|
||||
|
||||
```bash
|
||||
$ docker run myimage # curl -s http://example.com
|
||||
$ docker run myimage -v # 执行 -v(错误!)
|
||||
$ docker run myimage curl -v ... # curl -v ...(完全替换)
|
||||
```
|
||||
|
||||
```docker
|
||||
## 只用 ENTRYPOINT
|
||||
|
||||
ENTRYPOINT ["curl", "-s"]
|
||||
```
|
||||
|
||||
```bash
|
||||
$ docker run myimage # curl -s(缺参数)
|
||||
$ docker run myimage http://example.com # curl -s http://example.com ✓
|
||||
```
|
||||
|
||||
```docker
|
||||
## ENTRYPOINT + CMD 组合(推荐)
|
||||
|
||||
ENTRYPOINT ["curl", "-s"]
|
||||
CMD ["http://example.com"]
|
||||
```
|
||||
|
||||
```bash
|
||||
$ docker run myimage # curl -s http://example.com(默认)
|
||||
$ docker run myimage http://other.com # curl -s http://other.com ✓
|
||||
$ docker run myimage -v http://other.com # curl -s -v http://other.com ✓
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.5.4 场景一:让镜像像命令一样使用
|
||||
|
||||
#### 需求 (启动前准备)
|
||||
|
||||
创建一个查询公网 IP 的 “命令” 镜像。
|
||||
|
||||
#### 使用 CMD 的问题
|
||||
|
||||
```docker
|
||||
FROM ubuntu:24.04
|
||||
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
|
||||
CMD ["curl", "-s", "http://myip.ipip.net"]
|
||||
```
|
||||
|
||||
```bash
|
||||
$ docker run myip # ✓ 正常工作
|
||||
当前 IP:61.148.226.66
|
||||
|
||||
$ docker run myip -i # ✗ 错误!
|
||||
exec: "-i": executable file not found
|
||||
## -i 替换了整个 CMD,被当作可执行文件
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
#### 使用 ENTRYPOINT 解决
|
||||
|
||||
```docker
|
||||
FROM ubuntu:24.04
|
||||
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
|
||||
ENTRYPOINT ["curl", "-s", "http://myip.ipip.net"]
|
||||
```
|
||||
|
||||
```bash
|
||||
$ docker run myip # ✓ 正常工作
|
||||
当前 IP:61.148.226.66
|
||||
|
||||
$ docker run myip -i # ✓ 添加 -i 参数
|
||||
HTTP/1.1 200 OK
|
||||
...
|
||||
当前 IP:61.148.226.66
|
||||
```
|
||||
|
||||
#### 交互图示
|
||||
|
||||
```bash
|
||||
ENTRYPOINT ["curl", "-s", "http://myip.ipip.net"]
|
||||
│
|
||||
docker run myip -i
|
||||
│
|
||||
▼
|
||||
curl -s http://myip.ipip.net -i
|
||||
└─────────────────────────────┘
|
||||
ENTRYPOINT + docker run 参数
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.5.5 场景二:启动前的准备工作
|
||||
|
||||
#### 需求
|
||||
|
||||
在启动主服务前执行初始化脚本 (如数据库迁移、权限设置)。
|
||||
|
||||
#### 实现方式
|
||||
|
||||
```docker
|
||||
FROM redis:7-alpine
|
||||
COPY docker-entrypoint.sh /usr/local/bin/
|
||||
ENTRYPOINT ["docker-entrypoint.sh"]
|
||||
CMD ["redis-server"]
|
||||
```
|
||||
|
||||
**docker-entrypoint.sh**:
|
||||
|
||||
```bash
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
## 准备工作
|
||||
|
||||
echo "Initializing..."
|
||||
|
||||
## 如果第一个参数是 redis-server,以 redis 用户运行
|
||||
|
||||
if [ "$1" = 'redis-server' ]; then
|
||||
chown -R redis:redis /data
|
||||
exec gosu redis "$@"
|
||||
fi
|
||||
|
||||
## 其他命令直接执行
|
||||
|
||||
exec "$@"
|
||||
```
|
||||
|
||||
#### 工作流程
|
||||
|
||||
```bash
|
||||
docker run redis docker run redis bash
|
||||
│ │
|
||||
▼ ▼
|
||||
docker-entrypoint.sh redis-server docker-entrypoint.sh bash
|
||||
│ │
|
||||
├─ 初始化 ├─ 初始化
|
||||
├─ chown -R redis:redis /data │
|
||||
└─ exec gosu redis redis-server └─ exec bash
|
||||
(以 redis 用户运行) (以 root 用户运行)
|
||||
```
|
||||
|
||||
#### 关键点
|
||||
|
||||
1. **exec “$@”**:用传入的参数替换当前进程,确保信号正确传递
|
||||
2. **条件判断**:根据 CMD 不同执行不同逻辑
|
||||
3. **用户切换**:使用 `gosu` 切换用户 (比 `su` 更适合容器)
|
||||
|
||||
---
|
||||
|
||||
### 7.5.6 场景三:带参数的应用
|
||||
|
||||
```docker
|
||||
FROM python:3.12-slim
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
RUN pip install -r requirements.txt
|
||||
|
||||
ENTRYPOINT ["python", "app.py"]
|
||||
CMD ["--host", "0.0.0.0", "--port", "8080"]
|
||||
```
|
||||
|
||||
```bash
|
||||
## 使用默认参数
|
||||
|
||||
$ docker run myapp
|
||||
## 执行: python app.py --host 0.0.0.0 --port 8080
|
||||
|
||||
## 覆盖参数
|
||||
|
||||
$ docker run myapp --host 0.0.0.0 --port 9000
|
||||
## 执行: python app.py --host 0.0.0.0 --port 9000
|
||||
|
||||
## 完全不同的参数
|
||||
|
||||
$ docker run myapp --help
|
||||
## 执行: python app.py --help
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.5.7 覆盖 ENTRYPOINT
|
||||
|
||||
使用 `--entrypoint` 参数覆盖:
|
||||
|
||||
```bash
|
||||
## 正常运行
|
||||
|
||||
$ docker run myimage
|
||||
|
||||
## 覆盖 ENTRYPOINT 进入 shell 调试
|
||||
|
||||
$ docker run --entrypoint /bin/sh myimage
|
||||
|
||||
## 覆盖 ENTRYPOINT 并传入参数
|
||||
|
||||
$ docker run --entrypoint /bin/cat myimage /etc/os-release
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.5.8 ENTRYPOINT 与 CMD 组合表
|
||||
|
||||
| ENTRYPOINT | CMD | 最终执行命令 |
|
||||
|------------|-----|-------------|
|
||||
| 无 | 无 | 无 (容器无法启动)|
|
||||
| 无 | `["cmd", "p1"]` | `cmd p1` |
|
||||
| `["ep", "p1"]` | 无 | `ep p1` |
|
||||
| `["ep", "p1"]` | `["cmd", "p2"]` | `ep p1 cmd p2` |
|
||||
| `ep p1` (shell)| `["cmd", "p2"]` | `/bin/sh -c "ep p1"` (CMD 被忽略)|
|
||||
|
||||
> ⚠️ **注意**:shell 格式的 ENTRYPOINT 会忽略 CMD!
|
||||
|
||||
---
|
||||
|
||||
### 7.5.9 最佳实践
|
||||
|
||||
#### 1. 使用 exec 格式
|
||||
|
||||
```docker
|
||||
## ✅ 推荐
|
||||
|
||||
ENTRYPOINT ["python", "app.py"]
|
||||
|
||||
## ❌ 避免 shell 格式
|
||||
|
||||
ENTRYPOINT python app.py
|
||||
```
|
||||
|
||||
#### 2. 提供有意义的默认参数
|
||||
|
||||
```docker
|
||||
ENTRYPOINT ["nginx"]
|
||||
CMD ["-g", "daemon off;"]
|
||||
```
|
||||
|
||||
#### 3. 入口脚本使用 exec
|
||||
|
||||
```bash
|
||||
#!/bin/sh
|
||||
## 准备工作...
|
||||
|
||||
## 使用 exec 替换当前进程
|
||||
|
||||
exec "$@"
|
||||
```
|
||||
|
||||
#### 4. 处理信号
|
||||
|
||||
确保 ENTRYPOINT 脚本能正确传递信号:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
trap 'kill -TERM $PID' TERM INT
|
||||
|
||||
## 启动应用
|
||||
|
||||
app "$@" &
|
||||
PID=$!
|
||||
|
||||
## 等待应用退出
|
||||
|
||||
wait $PID
|
||||
```
|
||||
|
||||
---
|
||||
@@ -1,257 +0,0 @@
|
||||
## 7.6 ENV 设置环境变量
|
||||
|
||||
### 7.6.1 基本语法
|
||||
|
||||
```docker
|
||||
## 格式一:单个变量
|
||||
|
||||
ENV <key> <value>
|
||||
|
||||
## 格式二:多个变量(推荐)
|
||||
|
||||
ENV <key1>=<value1> <key2>=<value2> ...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.6.2 基本用法
|
||||
|
||||
#### 设置单个变量
|
||||
|
||||
```docker
|
||||
ENV NODE_VERSION 20.10.0
|
||||
ENV APP_ENV production
|
||||
```
|
||||
|
||||
#### 设置多个变量
|
||||
|
||||
```docker
|
||||
ENV NODE_VERSION=20.10.0 \
|
||||
APP_ENV=production \
|
||||
APP_NAME="My Application"
|
||||
```
|
||||
|
||||
> 💡 包含空格的值用双引号括起来。
|
||||
|
||||
---
|
||||
|
||||
### 7.6.3 环境变量的作用
|
||||
|
||||
#### 1. 后续指令中使用
|
||||
|
||||
```docker
|
||||
ENV NODE_VERSION=20.10.0
|
||||
|
||||
## 在 RUN 中使用
|
||||
|
||||
RUN curl -fsSL https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.xz \
|
||||
| tar -xJ -C /usr/local --strip-components=1
|
||||
|
||||
## 在 WORKDIR 中使用
|
||||
|
||||
ENV APP_HOME=/app
|
||||
WORKDIR $APP_HOME
|
||||
|
||||
## 在 COPY 中使用
|
||||
|
||||
COPY . $APP_HOME
|
||||
```
|
||||
|
||||
#### 2. 容器运行时使用
|
||||
|
||||
```docker
|
||||
ENV DATABASE_URL=postgres://localhost/mydb
|
||||
```
|
||||
|
||||
应用代码中可以读取:
|
||||
|
||||
```python
|
||||
import os
|
||||
db_url = os.environ.get('DATABASE_URL')
|
||||
```
|
||||
|
||||
```javascript
|
||||
const dbUrl = process.env.DATABASE_URL;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.6.4 支持环境变量的指令
|
||||
|
||||
以下指令可以使用 `$变量名` 或 `${变量名}` 格式:
|
||||
|
||||
| 指令 | 示例 |
|
||||
|------|------|
|
||||
| `RUN` | `RUN echo $VERSION` |
|
||||
| `CMD` | `CMD ["sh", "-c", "echo $HOME"]` |
|
||||
| `ENTRYPOINT` | 同上 |
|
||||
| `COPY` | `COPY . $APP_HOME` |
|
||||
| `ADD` | `ADD app.tar.gz $APP_HOME` |
|
||||
| `WORKDIR` | `WORKDIR $APP_HOME` |
|
||||
| `EXPOSE` | `EXPOSE $PORT` |
|
||||
| `VOLUME` | `VOLUME $DATA_DIR` |
|
||||
| `USER` | `USER $USERNAME` |
|
||||
| `LABEL` | `LABEL version=$VERSION` |
|
||||
| `FROM` | `FROM node:$NODE_VERSION` |
|
||||
|
||||
---
|
||||
|
||||
### 7.6.5 运行时覆盖
|
||||
|
||||
使用 `-e` 或 `--env` 覆盖 Dockerfile 中定义的环境变量:
|
||||
|
||||
```bash
|
||||
## 覆盖单个变量
|
||||
|
||||
$ docker run -e APP_ENV=development myimage
|
||||
|
||||
## 覆盖多个变量
|
||||
|
||||
$ docker run -e APP_ENV=development -e DEBUG=true myimage
|
||||
|
||||
## 从环境变量文件读取
|
||||
|
||||
$ docker run --env-file .env myimage
|
||||
```
|
||||
|
||||
#### 。env 文件格式
|
||||
|
||||
```bash
|
||||
## .env
|
||||
|
||||
APP_ENV=development
|
||||
DEBUG=true
|
||||
DATABASE_URL=postgres://localhost/mydb
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.6.6 ENV vs ARG
|
||||
|
||||
| 特性 | ENV | ARG |
|
||||
|------|-----|-----|
|
||||
| **生效时间** | 构建时 + 运行时 | 仅构建时 |
|
||||
| **持久性** | 写入镜像,运行时可用 | 构建后消失 |
|
||||
| **覆盖方式** | `docker run -e` | `docker build --build-arg` |
|
||||
| **适用场景** | 应用配置 | 构建参数 (如版本号)|
|
||||
|
||||
#### 组合使用
|
||||
|
||||
```docker
|
||||
## ARG 接收构建时参数
|
||||
|
||||
ARG NODE_VERSION=20
|
||||
|
||||
## ENV 保存到运行时
|
||||
|
||||
ENV NODE_VERSION=$NODE_VERSION
|
||||
|
||||
## 后续指令使用
|
||||
|
||||
RUN curl -fsSL https://nodejs.org/dist/v${NODE_VERSION}/...
|
||||
```
|
||||
|
||||
```bash
|
||||
## 构建时指定版本
|
||||
|
||||
$ docker build --build-arg NODE_VERSION=18 -t myapp .
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.6.7 最佳实践
|
||||
|
||||
#### 1. 统一管理版本号
|
||||
|
||||
```docker
|
||||
## ✅ 好:版本集中管理
|
||||
|
||||
ENV NGINX_VERSION=1.25.0 \
|
||||
NODE_VERSION=20.10.0 \
|
||||
PYTHON_VERSION=3.12.0
|
||||
|
||||
RUN apt-get install nginx=${NGINX_VERSION}
|
||||
|
||||
## ❌ 差:版本分散在各处
|
||||
|
||||
RUN apt-get install nginx=1.25.0
|
||||
```
|
||||
|
||||
#### 2. 不要存储敏感信息
|
||||
|
||||
```docker
|
||||
## ❌ 错误:密码写入镜像
|
||||
|
||||
ENV DB_PASSWORD=secret123
|
||||
|
||||
## ✅ 正确:运行时传入
|
||||
|
||||
## docker run -e DB_PASSWORD=xxx myimage
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
#### 3. 为应用提供合理默认值
|
||||
|
||||
```docker
|
||||
ENV APP_ENV=production \
|
||||
APP_PORT=8080 \
|
||||
LOG_LEVEL=info
|
||||
```
|
||||
|
||||
#### 4. 使用有意义的变量名
|
||||
|
||||
```docker
|
||||
## ✅ 好:清晰的命名
|
||||
|
||||
ENV REDIS_HOST=localhost \
|
||||
REDIS_PORT=6379
|
||||
|
||||
## ❌ 差:模糊的命名
|
||||
|
||||
ENV HOST=localhost \
|
||||
PORT=6379
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.6.8 常见问题
|
||||
|
||||
#### Q:环境变量在 CMD 中不展开
|
||||
|
||||
exec 格式不会自动展开环境变量:
|
||||
|
||||
```docker
|
||||
## ❌ 不会展开 $PORT
|
||||
|
||||
CMD ["python", "app.py", "--port", "$PORT"]
|
||||
|
||||
## ✅ 使用 shell 格式或显式调用 sh
|
||||
|
||||
CMD ["sh", "-c", "python app.py --port $PORT"]
|
||||
```
|
||||
|
||||
#### Q:如何查看容器的环境变量
|
||||
|
||||
```bash
|
||||
$ docker inspect mycontainer --format '{{json .Config.Env}}'
|
||||
$ docker exec mycontainer env
|
||||
```
|
||||
|
||||
#### Q:多行 ENV 还是多个 ENV
|
||||
|
||||
```docker
|
||||
## ✅ 推荐:减少层数
|
||||
|
||||
ENV VAR1=value1 \
|
||||
VAR2=value2 \
|
||||
VAR3=value3
|
||||
|
||||
## ⚠️ 多个 ENV 会创建多层
|
||||
|
||||
ENV VAR1=value1
|
||||
ENV VAR2=value2
|
||||
ENV VAR3=value3
|
||||
```
|
||||
|
||||
---
|
||||
@@ -1,242 +0,0 @@
|
||||
## 7.7 ARG 构建参数
|
||||
|
||||
### 7.7.1 基本语法
|
||||
|
||||
```docker
|
||||
ARG <参数名>[=<默认值>]
|
||||
```
|
||||
|
||||
`ARG` 指令定义构建时的变量,可以在 `docker build` 时通过 `--build-arg` 传入。
|
||||
|
||||
---
|
||||
|
||||
### 7.7.2 ARG vs ENV
|
||||
|
||||
| 特性 | ARG | ENV |
|
||||
|------|-----|-----|
|
||||
| **生效时间** | 仅构建时 | 构建时 + 运行时 |
|
||||
| **持久性** | 构建后消失 | 写入镜像 |
|
||||
| **覆盖方式** | `docker build --build-arg` | `docker run -e` |
|
||||
| **适用场景** | 构建参数 (版本号等)| 应用配置 |
|
||||
| **可见性** | `docker history` 可见 | `docker inspect` 可见 |
|
||||
|
||||
```dockerfile
|
||||
构建时 运行时
|
||||
├─ ARG VERSION=1.0 │ (ARG 已消失)
|
||||
├─ ENV APP_ENV=prod │ APP_ENV=prod(仍存在)
|
||||
└─ RUN echo $VERSION │
|
||||
```
|
||||
|
||||
> ⚠️ **安全提示**:不要用 ARG 传递密码等敏感信息,`docker history` 可以查看所有 ARG 值。
|
||||
|
||||
---
|
||||
|
||||
### 7.7.3 基本用法
|
||||
|
||||
#### 定义和使用
|
||||
|
||||
```docker
|
||||
## 定义有默认值的 ARG
|
||||
|
||||
ARG NODE_VERSION=20
|
||||
|
||||
## 使用 ARG
|
||||
|
||||
FROM node:${NODE_VERSION}-alpine
|
||||
RUN echo "Using Node.js $NODE_VERSION"
|
||||
```
|
||||
|
||||
#### 构建时覆盖
|
||||
|
||||
```bash
|
||||
## 使用默认值
|
||||
|
||||
$ docker build -t myapp .
|
||||
|
||||
## 覆盖默认值
|
||||
|
||||
$ docker build --build-arg NODE_VERSION=18 -t myapp .
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.7.4 ARG 的作用域
|
||||
|
||||
#### FROM 之前的 ARG
|
||||
|
||||
```docker
|
||||
## FROM 之前的 ARG 只能用于 FROM 指令
|
||||
|
||||
ARG REGISTRY=docker.io
|
||||
ARG IMAGE_NAME=node
|
||||
|
||||
FROM ${REGISTRY}/${IMAGE_NAME}:20
|
||||
|
||||
## ❌ 这里无法使用上面的 ARG
|
||||
|
||||
RUN echo $REGISTRY # 输出空
|
||||
```
|
||||
|
||||
#### FROM 之后重新声明
|
||||
|
||||
```docker
|
||||
ARG NODE_VERSION=20
|
||||
|
||||
FROM node:${NODE_VERSION}-alpine
|
||||
|
||||
## 需要再次声明才能使用
|
||||
|
||||
ARG NODE_VERSION
|
||||
RUN echo "Node version: $NODE_VERSION"
|
||||
```
|
||||
|
||||
#### 多阶段构建中的 ARG
|
||||
|
||||
```docker
|
||||
ARG BASE_VERSION=alpine
|
||||
|
||||
FROM node:20-${BASE_VERSION} AS builder
|
||||
## 需要重新声明
|
||||
|
||||
ARG NODE_VERSION=20
|
||||
RUN echo "Building with Node $NODE_VERSION"
|
||||
|
||||
FROM node:20-${BASE_VERSION}
|
||||
## 每个阶段都需要重新声明
|
||||
|
||||
ARG NODE_VERSION=20
|
||||
RUN echo "Running with Node $NODE_VERSION"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.7.5 常见使用场景
|
||||
|
||||
#### 1. 控制基础镜像版本
|
||||
|
||||
```docker
|
||||
ARG ALPINE_VERSION=3.19
|
||||
FROM alpine:${ALPINE_VERSION}
|
||||
```
|
||||
|
||||
```bash
|
||||
$ docker build --build-arg ALPINE_VERSION=3.18 .
|
||||
```
|
||||
|
||||
#### 2. 设置软件版本
|
||||
|
||||
```docker
|
||||
ARG NGINX_VERSION=1.25.0
|
||||
|
||||
RUN curl -fsSL https://nginx.org/download/nginx-${NGINX_VERSION}.tar.gz | tar -xz
|
||||
```
|
||||
|
||||
#### 3. 配置构建环境
|
||||
|
||||
```docker
|
||||
ARG BUILD_ENV=production
|
||||
ARG ENABLE_DEBUG=false
|
||||
|
||||
RUN if [ "$ENABLE_DEBUG" = "true" ]; then \
|
||||
npm install --include=dev; \
|
||||
else \
|
||||
npm install --production; \
|
||||
fi
|
||||
```
|
||||
|
||||
#### 4. 配置私有仓库
|
||||
|
||||
```docker
|
||||
ARG NPM_TOKEN
|
||||
|
||||
RUN echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc && \
|
||||
npm install && \
|
||||
rm ~/.npmrc
|
||||
```
|
||||
|
||||
```bash
|
||||
## 构建时传入 token
|
||||
|
||||
$ docker build --build-arg NPM_TOKEN=xxx .
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.7.6 将 ARG 传递给 ENV
|
||||
|
||||
如果需要在运行时使用 ARG 的值:
|
||||
|
||||
```docker
|
||||
ARG VERSION=1.0.0
|
||||
|
||||
## 将 ARG 传递给 ENV
|
||||
|
||||
ENV APP_VERSION=$VERSION
|
||||
|
||||
## 运行时可用
|
||||
|
||||
CMD echo "App version: $APP_VERSION"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.7.7 预定义 ARG
|
||||
|
||||
Docker 提供了一些预定义的 ARG,无需声明即可使用:
|
||||
|
||||
| ARG | 说明 |
|
||||
|-----|------|
|
||||
| `HTTP_PROXY` | HTTP 代理 |
|
||||
| `HTTPS_PROXY` | HTTPS 代理 |
|
||||
| `NO_PROXY` | 不使用代理的地址 |
|
||||
| `FTP_PROXY` | FTP 代理 |
|
||||
|
||||
```bash
|
||||
## 构建时使用代理
|
||||
|
||||
$ docker build --build-arg HTTP_PROXY=http://proxy:8080 .
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.7.8 最佳实践
|
||||
|
||||
#### 1. 为 ARG 提供合理默认值
|
||||
|
||||
```docker
|
||||
## ✅ 好:有默认值
|
||||
|
||||
ARG NODE_VERSION=20
|
||||
|
||||
## ⚠️ 需要每次传入
|
||||
|
||||
ARG NODE_VERSION
|
||||
```
|
||||
|
||||
#### 2. 不要用 ARG 存储敏感信息
|
||||
|
||||
```docker
|
||||
## ❌ 错误:密码会被记录在镜像历史中
|
||||
|
||||
ARG DB_PASSWORD
|
||||
RUN echo "password=$DB_PASSWORD" > /app/.env
|
||||
|
||||
## ✅ 正确:使用 secrets 或运行时环境变量
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
#### 3. 使用 ARG 提高构建灵活性
|
||||
|
||||
```docker
|
||||
ARG BASE_IMAGE=python:3.12-slim
|
||||
FROM ${BASE_IMAGE}
|
||||
|
||||
## 可以构建不同基础镜像的版本
|
||||
|
||||
## docker build --build-arg BASE_IMAGE=python:3.11-alpine .
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
---
|
||||
@@ -1,257 +0,0 @@
|
||||
## 7.8 VOLUME 定义匿名卷
|
||||
|
||||
### 7.8.1 基本语法
|
||||
|
||||
```docker
|
||||
VOLUME ["/路径1", "/路径2"]
|
||||
VOLUME /路径
|
||||
```
|
||||
|
||||
`VOLUME` 指令创建挂载点,并标记为外部挂载的卷。
|
||||
|
||||
---
|
||||
|
||||
### 7.8.2 为什么使用 VOLUME
|
||||
|
||||
> **核心原则**:容器存储层应该保持无状态,任何运行时数据都应该存储在卷中。
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph NoVolume ["没有 VOLUME:"]
|
||||
direction TB
|
||||
subgraph Container1 ["容器存储层"]
|
||||
direction TB
|
||||
Files["数据库文件 (问题)<br/>日志文件<br/>上传文件"]
|
||||
end
|
||||
Result1["容器删除 = 数据丢失"]
|
||||
Container1 ~~~ Result1
|
||||
end
|
||||
|
||||
subgraph UseVolume ["使用 VOLUME:"]
|
||||
direction TB
|
||||
Container2["容器存储层<br/>(只读/无状态)"]
|
||||
subgraph Volume ["数据卷"]
|
||||
Data["持久化数据 (安全)"]
|
||||
end
|
||||
Container2 --> Volume
|
||||
Result2["容器删除,数据保留"]
|
||||
Volume ~~~ Result2
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.8.3 基本用法
|
||||
|
||||
#### 定义单个卷
|
||||
|
||||
```docker
|
||||
FROM mysql:8.0
|
||||
VOLUME /var/lib/mysql
|
||||
```
|
||||
|
||||
#### 定义多个卷
|
||||
|
||||
```docker
|
||||
FROM myapp
|
||||
VOLUME ["/data", "/logs", "/config"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.8.4 VOLUME 的行为
|
||||
|
||||
#### 1. 自动创建匿名卷
|
||||
|
||||
如果运行时未指定挂载,Docker 会自动创建匿名卷:
|
||||
|
||||
```bash
|
||||
$ docker run mysql:8.0
|
||||
$ docker volume ls
|
||||
DRIVER VOLUME NAME
|
||||
local a1b2c3d4e5f6... # 自动创建的匿名卷
|
||||
```
|
||||
|
||||
#### 2. 可被命名卷覆盖
|
||||
|
||||
```bash
|
||||
## 使用命名卷替代匿名卷
|
||||
|
||||
$ docker run -v mysql_data:/var/lib/mysql mysql:8.0
|
||||
```
|
||||
|
||||
#### 3. 可被 Bind Mount 覆盖
|
||||
|
||||
```bash
|
||||
## 使用宿主机目录替代
|
||||
|
||||
$ docker run -v /my/data:/var/lib/mysql mysql:8.0
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.8.5 VOLUME 在构建时的特殊行为
|
||||
|
||||
> ⚠️ **重要**:VOLUME 之后对该目录的修改会被丢弃!
|
||||
|
||||
```docker
|
||||
FROM ubuntu
|
||||
VOLUME /data
|
||||
|
||||
## ❌ 这个文件不会出现在镜像中!
|
||||
|
||||
RUN echo "hello" > /data/test.txt
|
||||
```
|
||||
|
||||
**原因**:VOLUME 指令之后,Docker 将该目录视为外部挂载点,不再记录对它的修改。
|
||||
|
||||
#### 正确做法
|
||||
|
||||
```docker
|
||||
FROM ubuntu
|
||||
|
||||
## ✅ 先写入文件
|
||||
|
||||
RUN mkdir -p /data && echo "hello" > /data/test.txt
|
||||
|
||||
## 再声明 VOLUME
|
||||
|
||||
VOLUME /data
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.8.6 常见使用场景
|
||||
|
||||
#### 数据库持久化
|
||||
|
||||
```docker
|
||||
FROM postgres:15
|
||||
VOLUME /var/lib/postgresql/data
|
||||
```
|
||||
|
||||
#### 日志目录
|
||||
|
||||
```docker
|
||||
FROM nginx
|
||||
VOLUME /var/log/nginx
|
||||
```
|
||||
|
||||
#### 上传文件目录
|
||||
|
||||
```docker
|
||||
FROM myapp
|
||||
VOLUME /app/uploads
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.8.7 查看 VOLUME 定义
|
||||
|
||||
```bash
|
||||
## 查看镜像定义的 VOLUME
|
||||
|
||||
$ docker inspect mysql:8.0 --format '{{json .Config.Volumes}}' | jq
|
||||
{
|
||||
"/var/lib/mysql": {}
|
||||
}
|
||||
|
||||
## 查看容器挂载的卷
|
||||
|
||||
$ docker inspect mycontainer --format '{{json .Mounts}}' | jq
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.8.8 VOLUME vs docker run -v
|
||||
|
||||
| 特性 | Dockerfile VOLUME | docker run -v |
|
||||
|------|-------------------|---------------|
|
||||
| **定义时机** | 镜像构建时 | 容器运行时 |
|
||||
| **默认行为** | 创建匿名卷 | 可指定命名卷或路径 |
|
||||
| **灵活性** | 低 (固定路径)| 高 (可任意指定)|
|
||||
| **适用场景** | 定义必须持久化的路径 | 灵活的数据管理 |
|
||||
|
||||
---
|
||||
|
||||
### 7.8.9 在 Compose 中
|
||||
|
||||
在 Compose 中配置如下:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
db:
|
||||
image: postgres:15
|
||||
volumes:
|
||||
# 命名卷(推荐)
|
||||
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
# Bind Mount
|
||||
|
||||
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
|
||||
|
||||
volumes:
|
||||
postgres_data: # 声明命名卷
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.8.10 安全注意事项
|
||||
|
||||
#### 匿名卷可能导致数据丢失
|
||||
|
||||
```bash
|
||||
## 使用 --rm 运行的容器,匿名卷会在容器删除时一起删除
|
||||
|
||||
$ docker run --rm mysql:8.0
|
||||
## 容器停止后,数据丢失!
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
**解决**:始终使用命名卷
|
||||
|
||||
```bash
|
||||
$ docker run -v mysql_data:/var/lib/mysql mysql:8.0
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.8.11 最佳实践
|
||||
|
||||
#### 1. 定义必须持久化的路径
|
||||
|
||||
```docker
|
||||
## 数据库必须使用卷
|
||||
|
||||
FROM postgres:15
|
||||
VOLUME /var/lib/postgresql/data
|
||||
```
|
||||
|
||||
#### 2. 不要在 VOLUME 后修改目录
|
||||
|
||||
```docker
|
||||
## ❌ 避免
|
||||
|
||||
VOLUME /app/data
|
||||
RUN cp init-data.json /app/data/
|
||||
|
||||
## ✅ 正确
|
||||
|
||||
RUN mkdir -p /app/data && cp init-data.json /app/data/
|
||||
VOLUME /app/data
|
||||
```
|
||||
|
||||
#### 3. 文档中说明 VOLUME 用途
|
||||
|
||||
```docker
|
||||
## 持久化用户上传的文件
|
||||
|
||||
VOLUME /app/uploads
|
||||
|
||||
## 持久化数据库数据
|
||||
|
||||
VOLUME /var/lib/mysql
|
||||
```
|
||||
|
||||
---
|
||||
@@ -1,218 +0,0 @@
|
||||
## 7.9 EXPOSE 暴露端口
|
||||
|
||||
### 7.9.1 基本语法
|
||||
|
||||
```docker
|
||||
EXPOSE <端口> [<端口>/<协议>...]
|
||||
```
|
||||
|
||||
`EXPOSE` 声明容器运行时提供服务的端口。这是一个 **文档性质的声明**,告诉使用者容器会监听哪些端口。
|
||||
|
||||
---
|
||||
|
||||
### 7.9.2 基本用法
|
||||
|
||||
```docker
|
||||
## 声明单个端口
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
## 声明多个端口
|
||||
|
||||
EXPOSE 80 443
|
||||
|
||||
## 声明 TCP 和 UDP 端口
|
||||
|
||||
EXPOSE 80/tcp
|
||||
EXPOSE 53/udp
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.9.3 EXPOSE 的作用
|
||||
|
||||
#### 1. 文档说明
|
||||
|
||||
告诉镜像使用者,容器将在哪些端口提供服务:
|
||||
|
||||
```docker
|
||||
## 使用者一看就知道这是 web 应用
|
||||
|
||||
EXPOSE 80 443
|
||||
```
|
||||
|
||||
```bash
|
||||
## 查看镜像暴露的端口
|
||||
|
||||
$ docker inspect nginx --format '{{.Config.ExposedPorts}}'
|
||||
map[80/tcp:{}]
|
||||
```
|
||||
|
||||
#### 2. 配合 -P 使用
|
||||
|
||||
使用 `docker run -P` 时,Docker 会自动映射 EXPOSE 的端口到宿主机随机端口:
|
||||
|
||||
```docker
|
||||
## Dockerfile
|
||||
|
||||
EXPOSE 80
|
||||
```
|
||||
|
||||
```bash
|
||||
$ docker run -P nginx
|
||||
$ docker port $(docker ps -q)
|
||||
80/tcp -> 0.0.0.0:32768
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.9.4 EXPOSE vs -p
|
||||
|
||||
| 特性 | EXPOSE | -p |
|
||||
|------|--------|-----|
|
||||
| **位置** | Dockerfile | docker run 命令 |
|
||||
| **作用** | 声明/文档 | 实际端口映射 |
|
||||
| **是否必需** | 否 | 是 (外部访问时)|
|
||||
| **映射发生时** | 不发生 | 运行时发生 |
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Expose["EXPOSE 80<br/>仅声明意图"]
|
||||
Run["docker run -p<br/>实际端口映射<br/>宿主机 ←→ 容器"]
|
||||
Expose ~~~ Run
|
||||
```
|
||||
|
||||
#### 没有 EXPOSE 也能 -p
|
||||
|
||||
```docker
|
||||
## 即使没有 EXPOSE,也可以使用 -p
|
||||
|
||||
FROM nginx
|
||||
## 没有 EXPOSE
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
```bash
|
||||
## 仍然可以映射端口
|
||||
|
||||
$ docker run -p 8080:80 mynginx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.9.5 常见误解
|
||||
|
||||
#### 误解:EXPOSE 会打开端口
|
||||
|
||||
```docker
|
||||
## ❌ 错误理解:这不会让容器可从外部访问
|
||||
|
||||
EXPOSE 80
|
||||
```
|
||||
|
||||
EXPOSE 不会:
|
||||
|
||||
- 自动进行端口映射
|
||||
- 让服务可从外部访问
|
||||
- 在容器启动时开启端口监听
|
||||
|
||||
EXPOSE 只是元数据声明。容器是否实际监听该端口,取决于容器内的应用。
|
||||
|
||||
#### 正确理解
|
||||
|
||||
```docker
|
||||
## Dockerfile
|
||||
|
||||
FROM nginx
|
||||
EXPOSE 80 # 1. 声明:这个容器会在 80 端口提供服务
|
||||
```
|
||||
|
||||
```bash
|
||||
## 运行:需要 -p 才能从外部访问
|
||||
|
||||
$ docker run -p 8080:80 nginx # 2. 映射:宿主机 8080 → 容器 80
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.9.6 最佳实践
|
||||
|
||||
#### 1. 总是声明应用使用的端口
|
||||
|
||||
```docker
|
||||
## Web 服务
|
||||
|
||||
FROM nginx
|
||||
EXPOSE 80 443
|
||||
|
||||
## 数据库
|
||||
|
||||
FROM postgres
|
||||
EXPOSE 5432
|
||||
|
||||
## Redis
|
||||
|
||||
FROM redis
|
||||
EXPOSE 6379
|
||||
```
|
||||
|
||||
#### 2. 使用明确的协议
|
||||
|
||||
```docker
|
||||
## 默认是 TCP
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
## 明确指定 UDP
|
||||
|
||||
EXPOSE 53/udp
|
||||
|
||||
## 同时支持 TCP 和 UDP
|
||||
|
||||
EXPOSE 53/tcp 53/udp
|
||||
```
|
||||
|
||||
#### 3. 与应用实际端口保持一致
|
||||
|
||||
```docker
|
||||
## ✅ 好:EXPOSE 与应用端口一致
|
||||
|
||||
ENV PORT=3000
|
||||
EXPOSE 3000
|
||||
CMD ["node", "server.js"]
|
||||
|
||||
## ❌ 差:EXPOSE 与应用端口不一致(误导)
|
||||
|
||||
EXPOSE 80
|
||||
CMD ["node", "server.js"] # 实际监听 3000
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.9.7 使用环境变量
|
||||
|
||||
```docker
|
||||
ARG PORT=80
|
||||
EXPOSE $PORT
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.9.8 在 Compose 中
|
||||
|
||||
在 Compose 中配置如下:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
web:
|
||||
build: .
|
||||
ports:
|
||||
- "8080:80" # 映射端口(类似 -p)
|
||||
expose:
|
||||
- "80" # 仅声明(类似 EXPOSE)
|
||||
```
|
||||
|
||||
`expose` 在 Compose 中仅用于容器间通信的文档说明,不进行端口映射。
|
||||
|
||||
---
|
||||
@@ -1,58 +0,0 @@
|
||||
# 第七章 Dockerfile 指令详解
|
||||
|
||||
## 什么是 Dockerfile
|
||||
|
||||
Dockerfile 是一个文本文件,其內包含了一条条的 **指令 (Instruction)**,每一条指令构建一层,therefore 每一条指令的内容,就是描述该层应当如何构建。
|
||||
|
||||
在[第四章](../04_image/README.md)中,我们通过 `docker commit` 学习了镜像的构成。但是,手动 `commit` 只能作为临时修补,并不适合作为生产环境镜像的构建方式。
|
||||
|
||||
使用 Dockerfile 构建镜像有以下优势:
|
||||
|
||||
* **自动化**:可以通过 `docker build` 命令自动构建镜像。
|
||||
* **可重复性**:由于 Dockerfile 是文本文件,可以确保每次构建的结果一致。
|
||||
* **版本控制**:Dockerfile 可以纳入版本控制系统 (如 Git),便于追踪变更。
|
||||
* **透明性**:任何人都可以通过阅读 Dockerfile 了解镜像的构建过程。
|
||||
|
||||
## Dockerfile 基本结构
|
||||
|
||||
Dockerfile 一般分为四部分:基础镜像信息、维护者信息、镜像操作指令和容器启动时执行指令。
|
||||
|
||||
### 指令详解
|
||||
|
||||
本章将详细讲解 Dockerfile 中的各个指令:
|
||||
|
||||
* [COPY 复制文件](7.2_copy.md)
|
||||
* [ADD 更高级的复制文件](7.3_add.md)
|
||||
* [CMD 容器启动命令](7.4_cmd.md)
|
||||
* [ENTRYPOINT 入口点](7.5_entrypoint.md)
|
||||
* [ENV 设置环境变量](7.6_env.md)
|
||||
* [ARG 构建参数](7.7_arg.md)
|
||||
* [VOLUME 定义匿名卷](7.8_volume.md)
|
||||
* [EXPOSE 暴露端口](7.9_expose.md)
|
||||
* [WORKDIR 指定工作目录](7.10_workdir.md)
|
||||
* [USER 指定当前用户](7.11_user.md)
|
||||
* [HEALTHCHECK 健康检查](7.12_healthcheck.md)
|
||||
* [ONBUILD 为他人作嫁衣裳](7.13_onbuild.md)
|
||||
* [LABEL 为镜像添加元数据](7.14_label.md)
|
||||
* [SHELL 指令](7.15_shell.md)
|
||||
* [RUN 执行命令](7.1_run.md)
|
||||
|
||||
此外,我们还将介绍 Dockerfile 的最佳实践和常见问题。
|
||||
|
||||
* [参考文档](7.16_references.md)
|
||||
|
||||
## 使用 Dockerfile 构建镜像
|
||||
|
||||
构建镜像的基本命令格式为:
|
||||
|
||||
```bash
|
||||
docker build [选项] <上下文路径/URL/->
|
||||
```
|
||||
|
||||
例如,在 Dockerfile 所在目录执行:
|
||||
|
||||
```bash
|
||||
docker build -t my-image:v1 .
|
||||
```
|
||||
|
||||
更多关于 `docker build` 的用法,我们在实战中会结合具体指令进行演示。
|
||||
@@ -1,208 +0,0 @@
|
||||
## 本章小结
|
||||
|
||||
| 要点 | 说明 |
|
||||
|------|------|
|
||||
| **作用** | 设置后续指令的工作目录 |
|
||||
| **语法** | `WORKDIR /path` |
|
||||
| **自动创建** | 目录不存在会自动创建 |
|
||||
| **持久性** | 影响后续所有指令,直到下次 WORKDIR |
|
||||
| **不要用** | `RUN cd /path` (无效)|
|
||||
|
||||
### 7.19.1 延伸阅读
|
||||
|
||||
- [COPY 复制文件](7.2_copy.md):文件复制
|
||||
- [RUN 执行命令](../04_image/4.5_build.md):执行构建命令
|
||||
- [最佳实践](../appendix/best_practices.md):Dockerfile 编写指南
|
||||
|
||||
| 要点 | 说明 |
|
||||
|------|------|
|
||||
| **作用** | 切换后续指令的执行用户 |
|
||||
| **语法** | `USER username` 或 `USER UID:GID` |
|
||||
| **前提** | 用户必须已存在 |
|
||||
| **运行时覆盖** | `docker run -u` |
|
||||
| **切换工具** | 使用 gosu,不用 su/sudo |
|
||||
|
||||
### 7.19.2 延伸阅读
|
||||
|
||||
- [安全](../18_security/README.md):容器安全实践
|
||||
- [ENTRYPOINT](7.5_entrypoint.md):入口脚本中的用户切换
|
||||
- [最佳实践](../appendix/best_practices.md):Dockerfile 安全
|
||||
|
||||
| 要点 | 说明 |
|
||||
|------|------|
|
||||
| **作用** | 检测容器应用是否真实可用 |
|
||||
| **命令** | `HEALTHCHECK [选项] CMD command` |
|
||||
| **状态** | starting, healthy, unhealthy |
|
||||
| **Compose** | 支持 `condition: service_healthy` 依赖 |
|
||||
| **注意** | 避免副作用,节省资源 |
|
||||
|
||||
### 7.19.3 延伸阅读
|
||||
|
||||
- [CMD 容器启动命令](7.4_cmd.md):启动主进程
|
||||
- [Compose 模板文件](../11_compose/11.5_compose_file.md):Compose 中的健康检查
|
||||
- [Docker 调试](../appendix/debug.md):容器排障
|
||||
|
||||
| 要点 | 说明 |
|
||||
|------|------|
|
||||
| **作用** | 定义在子镜像构建时执行的指令 |
|
||||
| **语法** | `ONBUILD INSTRUCTION` |
|
||||
| **适用** | 基础架构镜像 (Node, Python, Go 等)|
|
||||
| **限制** | 只继承一次,不可级联 |
|
||||
| **规范** | 建议使用 `-onbuild` 标签后缀 |
|
||||
|
||||
### 7.19.4 延伸阅读
|
||||
|
||||
- [COPY 指令](7.2_copy.md):文件复制
|
||||
- [Dockerfile 最佳实践](../appendix/best_practices.md):基础镜像设计
|
||||
|
||||
| 要点 | 说明 |
|
||||
|------|------|
|
||||
| **作用** | 添加 key-value 元数据 |
|
||||
| **语法** | `LABEL k=v k=v ...` |
|
||||
| **规范** | 推荐使用 OCI 标准标签 |
|
||||
| **弃用** | 不要再使用 `MAINTAINER` |
|
||||
| **查看** | `docker inspect` |
|
||||
|
||||
### 7.19.5 延伸阅读
|
||||
|
||||
- [OCI 标签规范](https://github.com/opencontainers/image-spec/blob/main/annotations.md)
|
||||
- [Dockerfile 最佳实践](../appendix/best_practices.md)
|
||||
|
||||
| 要点 | 说明 |
|
||||
|------|------|
|
||||
| **作用** | 更改 RUN/CMD/ENTRYPOINT 的默认 shell |
|
||||
| **Linux 默认** | `["/bin/sh", "-c"]` |
|
||||
| **Windows 默认** | `["cmd", "/S", "/C"]` |
|
||||
| **推荐用法** | `SHELL ["/bin/bash", "-o", "pipefail", "-c"]` |
|
||||
| **影响范围** | 后续所有使用 shell 格式的指令 |
|
||||
|
||||
### 7.19.6 延伸阅读
|
||||
|
||||
- [RUN 指令](../04_image/4.5_build.md):执行命令
|
||||
- [Dockerfile 最佳实践](../appendix/best_practices.md):错误处理与调试
|
||||
|
||||
| 要点 | 说明 |
|
||||
|------|------|
|
||||
| **作用** | 在新层执行命令 |
|
||||
| **原则** | 合并命令,清理缓存 |
|
||||
| **格式** | Shell (常用) vs Exec |
|
||||
| **陷阱** | `cd` 不持久,环境变量不持久 |
|
||||
| **进阶** | 使用 Cache Mount 加速构建 |
|
||||
|
||||
### 7.19.7 延伸阅读
|
||||
|
||||
- [CMD 容器启动命令](7.4_cmd.md):容器启动时的命令
|
||||
- [WORKDIR 指定工作目录](7.10_workdir.md):改变目录
|
||||
- [Dockerfile 最佳实践](../appendix/best_practices.md)
|
||||
|
||||
| 操作 | 示例 |
|
||||
|------|------|
|
||||
| 复制文件 | `COPY app.js /app/` |
|
||||
| 复制多个文件 | `COPY *.json /app/` |
|
||||
| 复制目录内容 | `COPY src/ /app/src/` |
|
||||
| 修改所有者 | `COPY --chown=node:node . /app/` |
|
||||
| 从构建阶段复制 | `COPY --from=builder /app/dist ./` |
|
||||
|
||||
### 7.19.8 延伸阅读
|
||||
|
||||
- [ADD 指令](7.3_add.md):复制和解压
|
||||
- [WORKDIR 指令](7.10_workdir.md):设置工作目录
|
||||
- [多阶段构建](7.17_multistage_builds.md):优化镜像大小
|
||||
- [最佳实践](../appendix/best_practices.md):Dockerfile 编写指南
|
||||
|
||||
| 场景 | 推荐指令 |
|
||||
|------|---------|
|
||||
| 复制普通文件 | `COPY` |
|
||||
| 复制目录 | `COPY` |
|
||||
| 自动解压 tar | `ADD` |
|
||||
| 从 URL 下载 | `RUN curl` |
|
||||
| 保持 tar 不解压 | `COPY` |
|
||||
|
||||
### 7.19.9 延伸阅读
|
||||
|
||||
- [COPY 复制文件](7.2_copy.md):基本复制操作
|
||||
- [多阶段构建](7.17_multistage_builds.md):减少镜像体积
|
||||
- [最佳实践](../appendix/best_practices.md):Dockerfile 编写指南
|
||||
|
||||
| 要点 | 说明 |
|
||||
|------|------|
|
||||
| **作用** | 指定容器启动时的默认命令 |
|
||||
| **推荐格式** | exec 格式 `CMD [“程序”, “参数”]` |
|
||||
| **覆盖方式** | `docker run image 新命令` |
|
||||
| **与 ENTRYPOINT** | CMD 作为 ENTRYPOINT 的默认参数 |
|
||||
| **核心原则** | 应用必须在前台运行 |
|
||||
|
||||
### 7.19.10 延伸阅读
|
||||
|
||||
- [ENTRYPOINT 入口点](7.5_entrypoint.md):固定的启动命令
|
||||
- [后台运行](../05_container/5.2_daemon.md):容器前台/后台概念
|
||||
- [最佳实践](../appendix/best_practices.md):Dockerfile 编写指南
|
||||
|
||||
| ENTRYPOINT | CMD | 适用场景 |
|
||||
|------------|-----|---------|
|
||||
| ✓ | ✗ | 镜像作为固定命令使用 |
|
||||
| ✗ | ✓ | 简单的默认命令 |
|
||||
| ✓ | ✓ | **推荐**:固定命令 + 可配置参数 |
|
||||
|
||||
### 7.19.11 延伸阅读
|
||||
|
||||
- [CMD 容器启动命令](7.4_cmd.md):默认命令
|
||||
- [最佳实践](../appendix/best_practices.md):启动命令设计
|
||||
- [后台运行](../05_container/5.2_daemon.md):前台/后台概念
|
||||
|
||||
| 要点 | 说明 |
|
||||
|------|------|
|
||||
| **语法** | `ENV KEY=value` |
|
||||
| **作用范围** | 构建时 + 运行时 |
|
||||
| **覆盖方式** | `docker run -e KEY=value` |
|
||||
| **与 ARG** | ARG 仅构建时,ENV 持久化到运行时 |
|
||||
| **安全** | 不要存储敏感信息 |
|
||||
|
||||
### 7.19.12 延伸阅读
|
||||
|
||||
- [ARG 构建参数](7.7_arg.md):构建时变量
|
||||
- [Compose 环境变量](../11_compose/11.5_compose_file.md):Compose 中的环境变量
|
||||
- [最佳实践](../appendix/best_practices.md):Dockerfile 编写指南
|
||||
|
||||
| 要点 | 说明 |
|
||||
|------|------|
|
||||
| **作用** | 定义构建时变量 |
|
||||
| **语法** | `ARG NAME=value` |
|
||||
| **覆盖** | `docker build --build-arg NAME=value` |
|
||||
| **作用域** | FROM 之后需要重新声明 |
|
||||
| **vs ENV** | ARG 仅构建时,ENV 构建+运行时 |
|
||||
| **安全** | 不要存储敏感信息 |
|
||||
|
||||
### 7.19.13 延伸阅读
|
||||
|
||||
- [ENV 设置环境变量](7.6_env.md):运行时环境变量
|
||||
- [FROM 指令](../04_image/4.5_build.md):基础镜像指定
|
||||
- [多阶段构建](7.17_multistage_builds.md):复杂构建场景
|
||||
|
||||
| 要点 | 说明 |
|
||||
|------|------|
|
||||
| **作用** | 创建挂载点,标记为外部卷 |
|
||||
| **语法** | `VOLUME /path` |
|
||||
| **默认行为** | 自动创建匿名卷 |
|
||||
| **覆盖方式** | `docker run -v name:/path` |
|
||||
| **注意** | VOLUME 之后的修改会丢失 |
|
||||
|
||||
### 7.19.14 延伸阅读
|
||||
|
||||
- [数据卷](../08_data/8.1_volume.md):卷的管理和使用
|
||||
- [挂载主机目录](../08_data/8.2_bind-mounts.md):Bind Mount
|
||||
- [Compose 数据管理](../11_compose/11.5_compose_file.md):Compose 中的卷配置
|
||||
|
||||
| 要点 | 说明 |
|
||||
|------|------|
|
||||
| **作用** | 声明容器提供服务的端口 (文档)|
|
||||
| **不会** | 自动映射端口或开放外部访问 |
|
||||
| **配合** | `docker run -P` 自动映射 |
|
||||
| **外部访问** | 需要 `-p 宿主机端口:容器端口` |
|
||||
| **语法** | `EXPOSE 80` 或 `EXPOSE 80/tcp` |
|
||||
|
||||
### 7.19.15 延伸阅读
|
||||
|
||||
- [网络配置](../09_network/README.md):Docker 网络详解
|
||||
- [端口映射](../09_network/9.5_port_mapping.md):-p 参数详解
|
||||
- [Compose 端口](../11_compose/11.5_compose_file.md):Compose 中的端口配置
|
||||
@@ -1,369 +0,0 @@
|
||||
## 8.1 数据卷
|
||||
|
||||
### 8.1.1 为什么需要数据卷
|
||||
|
||||
容器的存储层有一个关键问题:**容器删除后,数据就没了**。
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Run[容器运行] --> Write[写入数据]
|
||||
Write --> Delete[容器删除]
|
||||
Delete -->|数据都在容器 writable 层| Lost[DATA LOST! ❌]
|
||||
```
|
||||
|
||||
数据卷 (Volume) 解决了这个问题,它的生命周期独立于容器。
|
||||
|
||||
---
|
||||
|
||||
### 8.1.2 数据卷的特性
|
||||
|
||||
| 特性 | 说明 |
|
||||
|------|------|
|
||||
| **持久化** | 容器删除后数据仍然保留 |
|
||||
| **共享** | 多个容器可以挂载同一个数据卷 |
|
||||
| **即时生效** | 对数据卷的修改立即可见 |
|
||||
| **不影响镜像** | 数据卷中的数据不会打包进镜像 |
|
||||
| **性能更好** | 绕过 UnionFS,直接读写 |
|
||||
|
||||
---
|
||||
|
||||
### 8.1.3 数据卷 vs 容器存储层
|
||||
|
||||
#### 容器存储层 (不推荐存储重要数据)
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
subgraph Container [容器]
|
||||
Writable[容器存储层<br>Writable]
|
||||
Image[镜像层<br>ReadOnly]
|
||||
Writable --- Image
|
||||
end
|
||||
|
||||
Lifecycle[生命周期 = 容器生命周期] -.-> Container
|
||||
Delete[容器删除] -->|导致| DataLost[数据丢失 ❌]
|
||||
```
|
||||
|
||||
#### 数据卷 (推荐)
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
subgraph Container [容器]
|
||||
AppDir["/app/data"]
|
||||
end
|
||||
|
||||
subgraph Volume [数据卷 my-data]
|
||||
Data[持久化数据]
|
||||
end
|
||||
|
||||
AppDir == 挂载 ==> Volume
|
||||
Delete[容器删除] -.->|不会影响| Volume
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 8.1.4 数据卷基本操作
|
||||
|
||||
#### 创建数据卷
|
||||
|
||||
```bash
|
||||
$ docker volume create my-vol
|
||||
```
|
||||
|
||||
#### 列出所有数据卷
|
||||
|
||||
```bash
|
||||
$ docker volume ls
|
||||
DRIVER VOLUME NAME
|
||||
local my-vol
|
||||
local postgres_data
|
||||
local redis_data
|
||||
```
|
||||
|
||||
#### 查看数据卷详情
|
||||
|
||||
```bash
|
||||
$ docker volume inspect my-vol
|
||||
[
|
||||
{
|
||||
"CreatedAt": "2026-01-15T10:00:00Z",
|
||||
"Driver": "local",
|
||||
"Labels": {},
|
||||
"Mountpoint": "/var/lib/docker/volumes/my-vol/_data",
|
||||
"Name": "my-vol",
|
||||
"Options": {},
|
||||
"Scope": "local"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
**关键字段**:
|
||||
|
||||
- `Mountpoint`:数据卷在宿主机上的实际存储位置
|
||||
- `Driver`:存储驱动 (默认 local,也可以用第三方驱动)
|
||||
|
||||
---
|
||||
|
||||
### 8.1.5 挂载数据卷
|
||||
|
||||
#### 方式一:--mount (推荐)
|
||||
|
||||
```bash
|
||||
$ docker run -d \
|
||||
--name web \
|
||||
--mount source=my-vol,target=/usr/share/nginx/html \
|
||||
nginx
|
||||
```
|
||||
|
||||
**参数说明**:
|
||||
|
||||
| 参数 | 说明 |
|
||||
|------|------|
|
||||
| `source` | 数据卷名称 (不存在会自动创建)|
|
||||
| `target` | 容器内挂载路径 |
|
||||
| `readonly` | 可选,只读挂载 |
|
||||
|
||||
#### 方式二:-v (简写)
|
||||
|
||||
```bash
|
||||
$ docker run -d \
|
||||
--name web \
|
||||
-v my-vol:/usr/share/nginx/html \
|
||||
nginx
|
||||
```
|
||||
|
||||
**格式**:`-v 数据卷名:容器路径[:选项]`
|
||||
|
||||
#### 两种方式对比
|
||||
|
||||
| 特性 | --mount | -v |
|
||||
|------|---------|-----|
|
||||
| 语法 | 键值对,更清晰 | 冒号分隔,更简洁 |
|
||||
| 自动创建卷 | source 不存在会报错 | 自动创建 |
|
||||
| 推荐程度 | ✅ 推荐 (更明确)| 常用 (更简洁)|
|
||||
|
||||
#### 只读挂载
|
||||
|
||||
```bash
|
||||
## --mount 方式
|
||||
|
||||
$ docker run -d \
|
||||
--mount source=my-vol,target=/data,readonly \
|
||||
nginx
|
||||
|
||||
## -v 方式
|
||||
|
||||
$ docker run -d \
|
||||
-v my-vol:/data:ro \
|
||||
nginx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 8.1.6 使用场景示例
|
||||
|
||||
#### 场景一:数据库持久化
|
||||
|
||||
```bash
|
||||
## 创建数据卷
|
||||
|
||||
$ docker volume create postgres_data
|
||||
|
||||
## 启动 PostgreSQL,数据存储在数据卷中
|
||||
|
||||
$ docker run -d \
|
||||
--name postgres \
|
||||
-e POSTGRES_PASSWORD=secret \
|
||||
-v postgres_data:/var/lib/postgresql/data \
|
||||
postgres:16
|
||||
|
||||
## 即使删除容器,数据仍然保留
|
||||
|
||||
$ docker rm -f postgres
|
||||
|
||||
## 重新启动,数据还在
|
||||
|
||||
$ docker run -d \
|
||||
--name postgres \
|
||||
-e POSTGRES_PASSWORD=secret \
|
||||
-v postgres_data:/var/lib/postgresql/data \
|
||||
postgres:16
|
||||
```
|
||||
|
||||
#### 场景二:多容器共享数据
|
||||
|
||||
```bash
|
||||
## 创建共享数据卷
|
||||
|
||||
$ docker volume create shared-data
|
||||
|
||||
## 容器 A 写入数据
|
||||
|
||||
$ docker run -d --name writer \
|
||||
-v shared-data:/data \
|
||||
alpine sh -c "while true; do date >> /data/log.txt; sleep 5; done"
|
||||
|
||||
## 容器 B 读取数据
|
||||
|
||||
$ docker run --rm \
|
||||
-v shared-data:/data \
|
||||
alpine cat /data/log.txt
|
||||
```
|
||||
|
||||
#### 场景三:配置文件持久化
|
||||
|
||||
```bash
|
||||
## 将 nginx 配置存储在数据卷中
|
||||
|
||||
$ docker run -d \
|
||||
-v nginx-config:/etc/nginx/conf.d \
|
||||
-v nginx-logs:/var/log/nginx \
|
||||
-p 80:80 \
|
||||
nginx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 8.1.7 数据卷管理
|
||||
|
||||
#### 删除数据卷
|
||||
|
||||
```bash
|
||||
## 删除指定数据卷
|
||||
|
||||
$ docker volume rm my-vol
|
||||
|
||||
## 删除容器时同时删除数据卷
|
||||
|
||||
$ docker rm -v container_name
|
||||
```
|
||||
|
||||
#### 清理未使用的数据卷
|
||||
|
||||
```bash
|
||||
## 查看未被任何容器使用的数据卷
|
||||
|
||||
$ docker volume ls -f dangling=true
|
||||
|
||||
## 删除所有未使用的数据卷
|
||||
|
||||
$ docker volume prune
|
||||
|
||||
## 强制删除(不提示确认)
|
||||
|
||||
$ docker volume prune -f
|
||||
```
|
||||
|
||||
> ⚠️ **注意**:数据卷不会自动垃圾回收。长期运行的系统应定期清理无用数据卷。
|
||||
|
||||
---
|
||||
|
||||
### 8.1.8 数据卷备份与恢复
|
||||
|
||||
#### 备份数据卷
|
||||
|
||||
```bash
|
||||
## 使用临时容器挂载数据卷,打包备份
|
||||
|
||||
$ docker run --rm \
|
||||
-v my-vol:/source:ro \
|
||||
-v $(pwd):/backup \
|
||||
alpine tar czf /backup/my-vol-backup.tar.gz -C /source .
|
||||
```
|
||||
|
||||
**原理**:
|
||||
|
||||
1. 创建临时容器
|
||||
2. 挂载要备份的数据卷到 `/source`
|
||||
3. 挂载当前目录到 `/backup`
|
||||
4. 使用 tar 打包
|
||||
|
||||
#### 恢复数据卷
|
||||
|
||||
```bash
|
||||
## 创建新数据卷
|
||||
|
||||
$ docker volume create my-vol-restored
|
||||
|
||||
## 解压备份到新数据卷
|
||||
|
||||
$ docker run --rm \
|
||||
-v my-vol-restored:/target \
|
||||
-v $(pwd):/backup:ro \
|
||||
alpine tar xzf /backup/my-vol-backup.tar.gz -C /target
|
||||
```
|
||||
|
||||
#### 备份脚本示例
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
## backup-volume.sh
|
||||
|
||||
VOLUME_NAME=$1
|
||||
BACKUP_DIR=${2:-/backups}
|
||||
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||||
|
||||
docker run --rm \
|
||||
-v ${VOLUME_NAME}:/source:ro \
|
||||
-v ${BACKUP_DIR}:/backup \
|
||||
alpine tar czf /backup/${VOLUME_NAME}_${TIMESTAMP}.tar.gz -C /source .
|
||||
|
||||
echo "Backed up ${VOLUME_NAME} to ${BACKUP_DIR}/${VOLUME_NAME}_${TIMESTAMP}.tar.gz"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 8.1.9 数据卷 vs 绑定挂载
|
||||
|
||||
Docker 有两种主要的数据持久化方式:
|
||||
|
||||
| 特性 | 数据卷 (Volume) | 绑定挂载 (Bind Mount) |
|
||||
|------|----------------|---------------------|
|
||||
| **管理方式** | Docker 管理 | 用户管理 |
|
||||
| **存储位置** | `/var/lib/docker/volumes/` | 任意宿主机路径 |
|
||||
| **可移植性** | 更好 | 依赖宿主机路径 |
|
||||
| **适用场景** | 生产数据持久化 | 开发时同步代码 |
|
||||
| **备份** | 需要工具 | 直接访问文件 |
|
||||
|
||||
```bash
|
||||
## 数据卷
|
||||
|
||||
$ docker run -v mydata:/app/data nginx
|
||||
|
||||
## 绑定挂载
|
||||
|
||||
$ docker run -v /host/path:/app/data nginx
|
||||
```
|
||||
|
||||
详见[绑定挂载](8.2_bind-mounts.md)章节。
|
||||
|
||||
---
|
||||
|
||||
### 8.1.10 常见问题
|
||||
|
||||
#### Q:如何知道容器使用了哪些数据卷?
|
||||
|
||||
```bash
|
||||
$ docker inspect container_name --format '{{json .Mounts}}' | jq
|
||||
```
|
||||
|
||||
#### Q:数据卷的数据在哪里?
|
||||
|
||||
```bash
|
||||
## 查看数据卷详情
|
||||
|
||||
$ docker volume inspect my-vol
|
||||
|
||||
## Mountpoint 字段显示实际路径
|
||||
|
||||
"Mountpoint": "/var/lib/docker/volumes/my-vol/_data"
|
||||
```
|
||||
|
||||
> ⚠️ **注意**:不建议直接修改 Mountpoint 中的文件,应通过容器操作。
|
||||
|
||||
#### Q:如何在不同机器间迁移数据卷?
|
||||
|
||||
1. 在源机器备份:`docker run --rm -v mydata:/data -v $(pwd):/backup alpine tar czf /backup/data.tar.gz -C /data .`
|
||||
2. 传输 tar.gz 文件
|
||||
3. 在目标机器恢复
|
||||
|
||||
---
|
||||
@@ -1,296 +0,0 @@
|
||||
## 8.2 挂载主机目录
|
||||
|
||||
### 8.2.1 什么是绑定挂载
|
||||
|
||||
Bind Mount (绑定挂载) 将 **宿主机的目录或文件** 直接挂载到容器中。容器可以读写宿主机的文件系统。
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph Host ["宿主机"]
|
||||
Dir1["/home/user/code/"]
|
||||
end
|
||||
|
||||
subgraph Container ["容器"]
|
||||
Dir2["/usr/share/nginx/html/"]
|
||||
end
|
||||
|
||||
Dir1 <-->|Bind Mount| Dir2
|
||||
```
|
||||
|
||||
目录结构(同一份文件):
|
||||
```text
|
||||
/home/user/code/ (或 /usr/share/nginx/html/)
|
||||
├── index.html
|
||||
├── style.css
|
||||
└── app.js
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 8.2.2 Bind Mount vs Volume
|
||||
|
||||
| 特性 | Bind Mount | Volume |
|
||||
|------|------------|--------|
|
||||
| **数据位置** | 宿主机任意路径 | Docker 管理的目录 |
|
||||
| **路径指定** | 必须是绝对路径 | 卷名 |
|
||||
| **可移植性** | 依赖宿主机路径 | 更好 (Docker 管理)|
|
||||
| **性能** | 依赖宿主机文件系统 | 优化的存储驱动 |
|
||||
| **适用场景** | 开发环境、配置文件 | 生产数据持久化 |
|
||||
| **备份** | 直接访问文件 | 需要通过 Docker |
|
||||
|
||||
#### 选择建议
|
||||
|
||||
| 需求 | 推荐方案 |
|
||||
|------|----------|
|
||||
| 开发时同步代码 | Bind Mount |
|
||||
| 持久化数据库数据 | Volume |
|
||||
| 共享配置文件 | Bind Mount |
|
||||
| 容器间共享数据 | Volume |
|
||||
| 备份方便 | Bind Mount (直接访问)|
|
||||
| 生产环境 | Volume |
|
||||
|
||||
---
|
||||
|
||||
### 8.2.3 基本语法
|
||||
|
||||
#### 使用 --mount (推荐)
|
||||
|
||||
```bash
|
||||
$ docker run -d \
|
||||
--mount type=bind,source=/宿主机路径,target=/容器路径 \
|
||||
nginx
|
||||
```
|
||||
|
||||
#### 使用 -v (简写)
|
||||
|
||||
```bash
|
||||
$ docker run -d \
|
||||
-v /宿主机路径:/容器路径 \
|
||||
nginx
|
||||
```
|
||||
|
||||
#### 两种语法对比
|
||||
|
||||
| 特性 | --mount | -v |
|
||||
|------|---------|-----|
|
||||
| 语法 | 键值对,更清晰 | 冒号分隔,更简洁 |
|
||||
| 路径不存在时 | 报错 | 自动创建目录 |
|
||||
| 推荐程度 | ✅ 推荐 | 常用 |
|
||||
|
||||
---
|
||||
|
||||
### 8.2.4 使用场景
|
||||
|
||||
#### 场景一:开发环境代码同步
|
||||
|
||||
```bash
|
||||
## 将本地代码目录挂载到容器
|
||||
|
||||
$ docker run -d \
|
||||
-p 8080:80 \
|
||||
--mount type=bind,source=$(pwd)/src,target=/usr/share/nginx/html \
|
||||
nginx
|
||||
|
||||
## 修改本地文件,容器内立即生效(热更新)
|
||||
|
||||
$ echo "Hello" > src/index.html
|
||||
## 浏览器刷新即可看到变化
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
#### 场景二:配置文件挂载
|
||||
|
||||
```bash
|
||||
## 挂载自定义 nginx 配置
|
||||
|
||||
$ docker run -d \
|
||||
--mount type=bind,source=/path/to/nginx.conf,target=/etc/nginx/nginx.conf,readonly \
|
||||
nginx
|
||||
```
|
||||
|
||||
#### 场景三:日志收集
|
||||
|
||||
```bash
|
||||
## 将容器日志输出到宿主机目录
|
||||
|
||||
$ docker run -d \
|
||||
--mount type=bind,source=/var/log/myapp,target=/app/logs \
|
||||
myapp
|
||||
```
|
||||
|
||||
#### 场景四:共享 SSH 密钥
|
||||
|
||||
```bash
|
||||
## 挂载 SSH 密钥(只读)
|
||||
|
||||
$ docker run --rm -it \
|
||||
--mount type=bind,source=$HOME/.ssh,target=/root/.ssh,readonly \
|
||||
alpine ssh user@remote
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 8.2.5 只读挂载
|
||||
|
||||
防止容器修改宿主机文件:
|
||||
|
||||
```bash
|
||||
## --mount 语法
|
||||
|
||||
$ docker run -d \
|
||||
--mount type=bind,source=/config,target=/app/config,readonly \
|
||||
myapp
|
||||
|
||||
## -v 语法
|
||||
|
||||
$ docker run -d \
|
||||
-v /config:/app/config:ro \
|
||||
myapp
|
||||
```
|
||||
|
||||
容器内尝试写入会报错:
|
||||
|
||||
```bash
|
||||
$ touch /app/config/new.txt
|
||||
touch: /app/config/new.txt: Read-only file system
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 8.2.6 挂载单个文件
|
||||
|
||||
```bash
|
||||
## 挂载 bash 历史记录
|
||||
|
||||
$ docker run --rm -it \
|
||||
--mount type=bind,source=$HOME/.bash_history,target=/root/.bash_history \
|
||||
ubuntu bash
|
||||
|
||||
## 挂载自定义配置文件
|
||||
|
||||
$ docker run -d \
|
||||
--mount type=bind,source=/path/to/my.cnf,target=/etc/mysql/my.cnf \
|
||||
mysql
|
||||
```
|
||||
|
||||
> ⚠️ **注意**:挂载单个文件时,如果宿主机上的文件被编辑器替换 (而非原地修改),容器内仍是旧文件的 inode。建议重启容器或挂载目录。
|
||||
|
||||
---
|
||||
|
||||
### 8.2.7 查看挂载信息
|
||||
|
||||
```bash
|
||||
$ docker inspect mycontainer --format '{{json .Mounts}}' | jq
|
||||
```
|
||||
|
||||
输出:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"Type": "bind",
|
||||
"Source": "/home/user/code",
|
||||
"Destination": "/app",
|
||||
"Mode": "",
|
||||
"RW": true,
|
||||
"Propagation": "rprivate"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| `Type` | 挂载类型 (bind)|
|
||||
| `Source` | 宿主机路径 |
|
||||
| `Destination` | 容器内路径 |
|
||||
| `RW` | 是否可读写 |
|
||||
| `Propagation` | 挂载传播模式 |
|
||||
|
||||
---
|
||||
|
||||
### 8.2.8 常见问题
|
||||
|
||||
#### Q:路径不存在报错
|
||||
|
||||
```bash
|
||||
$ docker run --mount type=bind,source=/not/exist,target=/app nginx
|
||||
docker: Error response from daemon: invalid mount config for type "bind":
|
||||
bind source path does not exist: /not/exist
|
||||
```
|
||||
|
||||
**解决**:确保源路径存在,或改用 `-v` (会自动创建)
|
||||
|
||||
#### Q:权限问题
|
||||
|
||||
容器内用户可能无权访问挂载的文件:
|
||||
|
||||
```bash
|
||||
## 方法1:确保宿主机文件权限允许容器用户访问
|
||||
|
||||
$ chmod -R 755 /path/to/data
|
||||
|
||||
## 方法2:以 root 运行容器
|
||||
|
||||
$ docker run -u root ...
|
||||
|
||||
## 方法3:使用相同的 UID
|
||||
|
||||
$ docker run -u $(id -u):$(id -g) ...
|
||||
```
|
||||
|
||||
#### Q:macOS/Windows 性能问题
|
||||
|
||||
在 Docker Desktop 上,Bind Mount 性能较差 (需要跨文件系统同步):
|
||||
|
||||
```bash
|
||||
## 使用 :cached 或 :delegated 提高性能(macOS)
|
||||
|
||||
$ docker run -v /host/path:/container/path:cached myapp
|
||||
```
|
||||
|
||||
| 选项 | 说明 |
|
||||
|------|------|
|
||||
| `:cached` | 宿主机权威,容器读取可能延迟 |
|
||||
| `:delegated` | 容器权威,宿主机读取可能延迟 |
|
||||
| `:consistent` | 默认,完全一致 (最慢)|
|
||||
|
||||
---
|
||||
|
||||
### 8.2.9 最佳实践
|
||||
|
||||
#### 1. 开发环境使用 Bind Mount
|
||||
|
||||
```bash
|
||||
## 代码热更新
|
||||
|
||||
$ docker run -v $(pwd):/app -p 3000:3000 node npm run dev
|
||||
```
|
||||
|
||||
#### 2. 生产环境使用 Volume
|
||||
|
||||
```bash
|
||||
## 数据持久化
|
||||
|
||||
$ docker run -v mysql_data:/var/lib/mysql mysql
|
||||
```
|
||||
|
||||
#### 3. 配置文件使用只读挂载
|
||||
|
||||
```bash
|
||||
$ docker run -v /config/nginx.conf:/etc/nginx/nginx.conf:ro nginx
|
||||
```
|
||||
|
||||
#### 4. 注意路径安全
|
||||
|
||||
```bash
|
||||
## ❌ 危险:挂载根目录或敏感目录
|
||||
|
||||
$ docker run -v /:/host ...
|
||||
|
||||
## ✅ 只挂载必要的目录
|
||||
|
||||
$ docker run -v /app/data:/data ...
|
||||
```
|
||||
|
||||
---
|
||||
@@ -1,35 +0,0 @@
|
||||
## 8.3 tmpfs 挂载
|
||||
|
||||
`tmpfs` 挂载会把数据放在宿主机内存中,而不是写入容器可写层或数据卷。
|
||||
|
||||
### 8.3.1 适用场景
|
||||
|
||||
- 临时缓存
|
||||
- 会话数据
|
||||
- 不希望落盘的敏感中间文件
|
||||
|
||||
### 8.3.2 基本用法
|
||||
|
||||
```bash
|
||||
$ docker run --tmpfs /run:rw,noexec,nosuid,size=64m nginx
|
||||
```
|
||||
|
||||
也可以使用 `--mount` 语法:
|
||||
|
||||
```bash
|
||||
$ docker run --mount type=tmpfs,destination=/run,tmpfs-size=67108864 nginx
|
||||
```
|
||||
|
||||
### 8.3.3 注意事项
|
||||
|
||||
- 容器停止后,`tmpfs` 数据会丢失。
|
||||
- `tmpfs` 占用宿主机内存,建议显式限制大小。
|
||||
- 不适合需要持久化的数据。
|
||||
|
||||
### 8.3.4 与 Volume / Bind Mount 对比
|
||||
|
||||
| 类型 | 数据位置 | 持久化 | 典型用途 |
|
||||
|------|---------|-------|---------|
|
||||
| Volume | Docker 管理目录 | 是 | 数据库、长期业务数据 |
|
||||
| Bind Mount | 宿主机指定目录 | 是 | 开发联调、配置文件共享 |
|
||||
| tmpfs | 内存 | 否 | 高速临时数据、敏感临时文件 |
|
||||
@@ -1,12 +0,0 @@
|
||||
# 第八章 数据管理
|
||||
|
||||
如图 8-1 所示,Docker 数据管理主要围绕三类挂载方式展开。
|
||||
|
||||

|
||||
|
||||
图 8-1 Docker 数据挂载类型示意图
|
||||
|
||||
这一章介绍如何在 Docker 内部以及容器之间管理数据,在容器中管理数据主要有两种方式:
|
||||
|
||||
* [数据卷](8.1_volume.md)
|
||||
* [挂载主机目录](8.2_bind-mounts.md)
|
||||
@@ -1,30 +0,0 @@
|
||||
## 本章小结
|
||||
|
||||
| 要点 | 说明 |
|
||||
|------|------|
|
||||
| **作用** | 将宿主机目录挂载到容器 |
|
||||
| **语法** | `-v /宿主机:/容器` 或 `--mount type=bind,...` |
|
||||
| **只读** | 添加 `readonly` 或 `:ro` |
|
||||
| **适用场景** | 开发环境、配置文件、日志 |
|
||||
| **vs Volume** | Bind 更灵活,Volume 更适合生产 |
|
||||
|
||||
### 8.4.1 延伸阅读
|
||||
|
||||
- [数据卷](8.1_volume.md):Docker 管理的持久化存储
|
||||
- [tmpfs 挂载](8.3_tmpfs.md):内存临时存储
|
||||
- [Compose 数据管理](../11_compose/11.5_compose_file.md):Compose 中的挂载配置
|
||||
|
||||
| 操作 | 命令 |
|
||||
|------|------|
|
||||
| 创建数据卷 | `docker volume create name` |
|
||||
| 列出数据卷 | `docker volume ls` |
|
||||
| 查看详情 | `docker volume inspect name` |
|
||||
| 删除数据卷 | `docker volume rm name` |
|
||||
| 清理未用 | `docker volume prune` |
|
||||
| 挂载数据卷 | `-v name:/path` 或 `--mount source=name,target=/path` |
|
||||
|
||||
### 8.4.2 延伸阅读
|
||||
|
||||
- [绑定挂载](8.2_bind-mounts.md):挂载宿主机目录
|
||||
- [tmpfs 挂载](8.3_tmpfs.md):内存中的临时存储
|
||||
- [存储驱动](../12_implementation/12.4_ufs.md):Docker 存储的底层原理
|
||||
@@ -1,108 +0,0 @@
|
||||
## 9.1 配置 DNS
|
||||
|
||||
Docker 1.10.0 以后,内建了一个 DNS 服务器,使得容器可以直接通过容器名称通信。方法很简单,只要在创建容器时使用 `--name` 为容器命名即可。
|
||||
|
||||
但是使用 Docker DNS 有个前提条件,就是它只能在 **自定义网络** 中使用。也就是说,如果使用的是默认的 `bridge` 网络,是无法使用 DNS 的,所以我们就需要自定义网络。
|
||||
|
||||
### 9.2.1 容器的 DNS 机制
|
||||
|
||||
Docker 容器的 DNS 配置有两种情况:
|
||||
|
||||
1. **默认 Bridge 网络**:继承宿主机的 DNS 配置 (`/etc/resolv.conf`)。
|
||||
2. **自定义网络** (推荐):使用 Docker 嵌入式 DNS 服务器 (Embedded DNS),支持通过 **容器名** 进行服务发现。
|
||||
|
||||
---
|
||||
|
||||
### 9.2.2 嵌入式 DNS
|
||||
|
||||
这是 Docker 网络最强大的功能之一。在自定义网络中,容器可以通过 “名字” 找到彼此,而不需要知道对方的 IP (因为 IP 可能会变)。
|
||||
|
||||
```bash
|
||||
## 1. 创建自定义网络
|
||||
|
||||
$ docker network create mynet
|
||||
|
||||
## 2. 启动容器 web 并加入网络
|
||||
|
||||
$ docker run -d --name web --network mynet nginx
|
||||
|
||||
## 3. 启动容器 client 并尝试 ping web
|
||||
|
||||
$ docker run -it --rm --network mynet alpine ping web
|
||||
PING web (172.18.0.2): 56 data bytes
|
||||
64 bytes from 172.18.0.2: seq=0 ttl=64 time=0.074 ms
|
||||
```
|
||||
|
||||
**原理**:
|
||||
Docker 守护进程在 `127.0.0.11` 运行了一个 DNS 服务器。容器内的 DNS 请求会被转发到这里。如果是容器名,解析为容器 IP;如果是外部域名 (如 google.com),转发给上游 DNS。
|
||||
|
||||
---
|
||||
|
||||
### 9.2.3 配置 DNS 参数
|
||||
|
||||
如果你需要手动配置容器的 DNS (例如使用内网 DNS 服务器),可以在 `docker run` 中使用以下参数:
|
||||
|
||||
#### 1. --dns
|
||||
|
||||
指定 DNS 服务器 IP。
|
||||
|
||||
```bash
|
||||
$ docker run -it --dns=114.114.114.114 ubuntu cat /etc/resolv.conf
|
||||
nameserver 114.114.114.114
|
||||
```
|
||||
|
||||
#### 2. --dns-search
|
||||
|
||||
指定 DNS 搜索域。例如设置为 `example.com`,则 `ping host` 会尝试解析 `host.example.com`。
|
||||
|
||||
```bash
|
||||
$ docker run --dns-search=example.com myapp
|
||||
```
|
||||
|
||||
#### 3. --hostname (-h)
|
||||
|
||||
设置容器的主机名。
|
||||
|
||||
```bash
|
||||
$ docker run -h myweb nginx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 9.2.4 全局 DNS 配置
|
||||
|
||||
如果希望所有容器都使用特定的 DNS 服务器 (而不是继承宿主机),可以修改 `/etc/docker/daemon.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": [
|
||||
"114.114.114.114",
|
||||
"8.8.8.8"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
修改后需要重启 Docker 服务:`systemctl restart docker`。
|
||||
|
||||
---
|
||||
|
||||
### 9.2.5 常见问题
|
||||
|
||||
以下是使用容器 DNS 时常见的问题及解决方法:
|
||||
|
||||
#### Q:容器无法解析域名
|
||||
|
||||
**现象**:`ping www.baidu.com` 失败,但 `ping 8.8.8.8` 成功。**解决**:
|
||||
|
||||
1. 宿主机的 `/etc/resolv.conf` 可能有问题 (例如使用了本地回环地址 127.0.0.53,特别是 Ubuntu 系统)。Docker 可能会尝试修复,但有时会失败。
|
||||
2. 尝试手动指定 DNS:`docker run --dns 8.8.8.8 ...`
|
||||
3. 检查防火墙是否拦截了 UDP 53 端口。
|
||||
|
||||
#### Q:无法通过容器名通信
|
||||
|
||||
**现象**:`ping db` 提示 `bad address 'db'`。**原因**:
|
||||
|
||||
- 你可能在使用 **默认的 bridge 网络**。默认 bridge 网络 **不支持** 通过容器名进行 DNS 解析 (这是一个历史遗留设计)。
|
||||
- **解决**:使用自定义网络 (`docker network create ...`)。
|
||||
|
||||
---
|
||||
@@ -1,78 +0,0 @@
|
||||
## 9.2 网络类型
|
||||
|
||||
Docker 提供了多种网络驱动来满足不同的使用场景。安装 Docker 后,系统会自动创建三个默认网络:
|
||||
|
||||
```bash
|
||||
$ docker network ls
|
||||
NETWORK ID NAME DRIVER SCOPE
|
||||
abc123... bridge bridge local
|
||||
def456... host host local
|
||||
ghi789... none null local
|
||||
```
|
||||
|
||||
### 9.2.1 网络类型对比
|
||||
|
||||
各网络类型的特点和适用场景如下:
|
||||
|
||||
| 网络类型 | 说明 | 适用场景 |
|
||||
|---------|------|---------|
|
||||
| **bridge** | 默认类型,容器连接到虚拟网桥 | 大多数单机场景 |
|
||||
| **host** | 容器直接使用宿主机网络栈 | 需要最高网络性能时 |
|
||||
| **none** | 禁用网络 | 完全隔离的容器 |
|
||||
| **overlay** | 跨主机网络 | Docker Swarm 集群 |
|
||||
| **macvlan** | 容器拥有独立 MAC 地址 | 需要直接接入物理网络 |
|
||||
|
||||
### 9.2.2 Bridge 网络 (默认)
|
||||
|
||||
Bridge 是 Docker 默认使用的网络模式。Docker 启动时会自动创建 `docker0` 虚拟网桥,所有未指定网络的容器都会连接到这个网桥上。
|
||||
|
||||
核心组件如下:
|
||||
|
||||
| 组件 | 说明 |
|
||||
|------|------|
|
||||
| **docker0** | 虚拟网桥,充当交换机角色 |
|
||||
| **veth pair** | 虚拟网卡对,一端在容器内,一端连接网桥 |
|
||||
| **容器 eth0** | 容器内的网卡 |
|
||||
| **IP 地址** | 自动从 172.17.0.0/16 网段分配 |
|
||||
|
||||
### 9.2.3 Host 网络
|
||||
|
||||
使用 `--network host` 参数启动的容器会直接使用宿主机的网络栈,不再拥有独立的网络命名空间。容器内的端口就是宿主机的端口,无需端口映射。
|
||||
|
||||
```bash
|
||||
$ docker run -d --network host nginx
|
||||
```
|
||||
|
||||
这种模式下网络性能最高,但容器之间和宿主机之间没有网络隔离。
|
||||
|
||||
### 9.2.4 None 网络
|
||||
|
||||
使用 `--network none` 参数启动的容器只有 `lo` 回环网卡,完全没有外部网络连接。适用于只需要运行计算任务、不需要网络的容器。
|
||||
|
||||
```bash
|
||||
$ docker run -it --network none alpine ip addr
|
||||
1: lo: <LOOPBACK,UP,LOWER_UP> ...
|
||||
inet 127.0.0.1/8 scope host lo
|
||||
```
|
||||
|
||||
### 9.2.5 数据流向
|
||||
|
||||
容器网络中的数据流向可以分为以下几种情况:
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph Comm1 ["容器间通信"]
|
||||
direction LR
|
||||
C1A["容器 A (172.17.0.2)"] --> D0A["docker0"] --> C1B["容器 B (172.17.0.3)"]
|
||||
end
|
||||
|
||||
subgraph Comm2 ["访问外网"]
|
||||
direction LR
|
||||
C2A["容器 A (172.17.0.2)"] --> D0B["docker0"] --> Eth0A["eth0"] --> InternetA["互联网"]
|
||||
end
|
||||
|
||||
subgraph Comm3 ["被外部访问,需端口映射"]
|
||||
direction LR
|
||||
Req["外部请求"] --> Eth0B["eth0"] --> D0C["docker0"] --> C3A["容器 A"]
|
||||
end
|
||||
```
|
||||
@@ -1,93 +0,0 @@
|
||||
## 9.3 自定义网络
|
||||
|
||||
在生产环境中,推荐使用用户自定义网络代替默认的 bridge 网络。自定义网络提供了更好的隔离性和服务发现能力。
|
||||
|
||||
### 9.4.1 为什么要用自定义网络
|
||||
|
||||
默认 bridge 网络存在以下局限,而自定义网络可以很好地解决这些问题:
|
||||
|
||||
| 问题 | 自定义网络的优势 |
|
||||
|------|-----------------|
|
||||
| 只能用 IP 通信 | 支持容器名 DNS 解析 |
|
||||
| 所有容器在同一网络 | 更好的隔离性 |
|
||||
| 需要 --link (已废弃)| 原生支持服务发现 |
|
||||
|
||||
### 9.4.2 创建自定义网络
|
||||
|
||||
使用 `docker network create` 命令可以创建自定义网络:
|
||||
|
||||
```bash
|
||||
## 创建网络
|
||||
|
||||
$ docker network create mynet
|
||||
|
||||
## 查看网络详情
|
||||
|
||||
$ docker network inspect mynet
|
||||
```
|
||||
|
||||
### 9.4.3 使用自定义网络
|
||||
|
||||
启动容器时通过 `--network` 参数指定连接的网络:
|
||||
|
||||
```bash
|
||||
## 启动容器并连接到自定义网络
|
||||
|
||||
$ docker run -d --name web --network mynet nginx
|
||||
$ docker run -d --name db --network mynet postgres
|
||||
|
||||
## 在 web 容器中可以直接用容器名访问 db
|
||||
|
||||
$ docker exec web ping db
|
||||
PING db (172.18.0.3): 56 data bytes
|
||||
64 bytes from 172.18.0.3: seq=0 ttl=64 time=0.083 ms
|
||||
```
|
||||
|
||||
### 9.4.4 容器名 DNS 解析
|
||||
|
||||
自定义网络自动提供 DNS 服务。Docker 守护进程在 `127.0.0.11` 运行了一个嵌入式 DNS 服务器,容器内的 DNS 请求会被转发到这里:
|
||||
|
||||
- 如果是容器名,解析为容器 IP
|
||||
- 如果是外部域名 (如 google.com),转发给上游 DNS
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph MyNet ["mynet 网络 : web 容器可以用 'db' 作为主机名访问 db 容器"]
|
||||
direction LR
|
||||
Web["web<br/>172.18.0.2"] -- "DNS: 'db' → 172.18.0.3" --> DB["db<br/>172.18.0.3"]
|
||||
end
|
||||
```
|
||||
|
||||
### 9.4.5 常用网络命令
|
||||
|
||||
以下是 Docker 网络管理中常用的命令:
|
||||
|
||||
```bash
|
||||
## 列出网络
|
||||
|
||||
$ docker network ls
|
||||
|
||||
## 创建网络
|
||||
|
||||
$ docker network create mynet
|
||||
|
||||
## 查看网络详情
|
||||
|
||||
$ docker network inspect mynet
|
||||
|
||||
## 连接容器到网络
|
||||
|
||||
$ docker network connect mynet mycontainer
|
||||
|
||||
## 断开网络连接
|
||||
|
||||
$ docker network disconnect mynet mycontainer
|
||||
|
||||
## 删除网络
|
||||
|
||||
$ docker network rm mynet
|
||||
|
||||
## 清理未使用的网络
|
||||
|
||||
$ docker network prune
|
||||
```
|
||||
@@ -1,62 +0,0 @@
|
||||
## 9.4 容器互联
|
||||
|
||||
容器之间的网络通信是 Docker 网络的核心功能之一。本节介绍容器互联的几种方式。
|
||||
|
||||
### 9.5.1 同一网络内的容器
|
||||
|
||||
同一自定义网络内的容器可以直接通过容器名通信,这是推荐的容器互联方式:
|
||||
|
||||
```bash
|
||||
## 创建网络
|
||||
|
||||
$ docker network create app-net
|
||||
|
||||
## 启动应用和数据库
|
||||
|
||||
$ docker run -d --name redis --network app-net redis
|
||||
$ docker run -d --name app --network app-net myapp
|
||||
|
||||
## app 容器中可以用 redis:6379 连接 Redis
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
### 9.5.2 连接到多个网络
|
||||
|
||||
一个容器可以同时连接到多个网络,这对于需要跨网络通信的中间件容器特别有用:
|
||||
|
||||
```bash
|
||||
## 启动容器
|
||||
|
||||
$ docker run -d --name multi-net-container --network frontend nginx
|
||||
|
||||
## 再连接到另一个网络
|
||||
|
||||
$ docker network connect backend multi-net-container
|
||||
|
||||
## 查看容器的网络
|
||||
|
||||
$ docker inspect multi-net-container --format '{{json .NetworkSettings.Networks}}'
|
||||
```
|
||||
|
||||
### 9.5.3 ⚠️ --link 已废弃
|
||||
|
||||
`--link` 是 Docker 早期用于容器互联的方式,**已经被废弃**,不建议在新项目中使用。请使用自定义网络替代:
|
||||
|
||||
```bash
|
||||
## 旧方式(不推荐)
|
||||
|
||||
$ docker run --link db:database myapp
|
||||
|
||||
## 新方式(推荐)
|
||||
|
||||
$ docker network create mynet
|
||||
$ docker run --network mynet --name db postgres
|
||||
$ docker run --network mynet --name app myapp
|
||||
```
|
||||
|
||||
使用自定义网络的优势在于:
|
||||
|
||||
- 原生支持 DNS 解析
|
||||
- 不需要在容器启动时显式声明依赖
|
||||
- 更灵活,可以动态 connect/disconnect
|
||||
@@ -1,144 +0,0 @@
|
||||
## 9.5 外部访问容器
|
||||
|
||||
容器运行在自己的隔离网络环境中 (通常是 Bridge 模式)。为了让外部网络访问容器内的服务,我们需要将容器的端口映射到宿主机的端口。
|
||||
|
||||
### 9.6.1 为什么要映射端口
|
||||
|
||||
容器的网络访问规则如下:
|
||||
|
||||
- **容器之间**:可以通过 IP 或容器名 (自定义网络) 互通。
|
||||
- **宿主机访问容器**:可以通过容器 IP 访问。
|
||||
- **外部网络访问容器**:❌ 默认无法直接访问。
|
||||
|
||||
为了让外部 (如你的浏览器、其他局域网机器) 访问容器内的服务,我们需要将容器的端口 **映射** 到宿主机的端口。
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
User["外部用户 (Browser)"] --> Host["宿主机 (localhost:8080)"]
|
||||
Host --> Proxy["Docker Proxy<br/>端口映射 (8080 -> 80)"]
|
||||
Proxy --> Container["容器 (Class B: 80)"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 9.6.2 端口映射方式
|
||||
|
||||
Docker 提供了多种方式来指定端口映射。
|
||||
|
||||
#### 1. 指定映射
|
||||
|
||||
使用 `-p <宿主机端口>:<容器端口>` 格式:
|
||||
|
||||
```bash
|
||||
## 将宿主机的 8080 端口映射到容器的 80 端口
|
||||
|
||||
$ docker run -d -p 8080:80 nginx
|
||||
```
|
||||
|
||||
此时访问 `http://localhost:8080` 即可看到 Nginx 页面。
|
||||
|
||||
**多种格式**:
|
||||
|
||||
| 格式 | 含义 | 示例 |
|
||||
|------|------|------|
|
||||
| `ip:hostPort:containerPort` | 绑定指定 IP 的特定端口 | `-p 127.0.0.1:8080:80` (仅本机访问) |
|
||||
| `ip::containerPort` | 绑定指定 IP 的随机端口 | `-p 127.0.0.1::80` |
|
||||
| `hostPort:containerPort` | 绑定所有 IP (0.0.0.0) 的特定端口 | `-p 8080:80` (默认) |
|
||||
| `containerPort` | 绑定所有 IP 的随机端口 | `-p 80` |
|
||||
|
||||
#### 2. 随机映射
|
||||
|
||||
如果不关心宿主机使用哪个端口,可以使用随机映射。使用 `-P` (大写) 参数,Docker 会随机映射 Dockerfile 中 `EXPOSE` 指令暴露的所有端口到宿主机的高端口 (49000-49900)。
|
||||
|
||||
```bash
|
||||
$ docker run -d -P nginx
|
||||
```
|
||||
|
||||
查看映射结果:
|
||||
|
||||
```bash
|
||||
$ docker ps
|
||||
CONTAINER ID PORTS
|
||||
abc123456 0.0.0.0:49153->80/tcp
|
||||
```
|
||||
|
||||
此时 Nginx 被映射到了宿主机的 49153 端口。
|
||||
|
||||
---
|
||||
|
||||
### 9.6.3 查看端口映射
|
||||
|
||||
可以使用以下命令查看容器的端口映射:
|
||||
|
||||
#### docker port
|
||||
|
||||
运行 `docker port` 可以查看到指定容器的端口映射情况:
|
||||
|
||||
```bash
|
||||
$ docker port mycontainer
|
||||
80/tcp -> 0.0.0.0:8080
|
||||
80/tcp -> [::]:8080
|
||||
```
|
||||
|
||||
#### docker ps
|
||||
|
||||
运行 `docker ps` 可以查看到所有容器的端口映射列表:
|
||||
|
||||
```bash
|
||||
$ docker ps
|
||||
CONTAINER ID IMAGE PORTS NAMES
|
||||
abc123456 nginx 0.0.0.0:8080->80/tcp web
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 9.6.4 最佳实践与安全
|
||||
|
||||
在配置端口映射时,需要注意以下安全事项:
|
||||
|
||||
#### 1. 限制监听 IP
|
||||
|
||||
默认情况下,`-p 8080:80` 会监听 `0.0.0.0:8080`,这意味着任何人只要能连接你的宿主机 IP,就能访问该服务。
|
||||
|
||||
如果不希望对外暴露 (例如数据库服务),应绑定到 `127.0.0.1`:
|
||||
|
||||
```bash
|
||||
## 仅允许本机访问
|
||||
|
||||
$ docker run -d -p 127.0.0.1:3306:3306 mysql
|
||||
```
|
||||
|
||||
#### 2. 避免端口冲突
|
||||
|
||||
如果宿主机 8080 已经被占用了,容器将无法启动。
|
||||
|
||||
**解决**:
|
||||
|
||||
- 更换宿主机端口:`-p 8081:80`
|
||||
- 让 Docker 自动分配:`-p 80`
|
||||
|
||||
#### 3. UDP 映射
|
||||
|
||||
默认是 TCP 协议。如果要映射 UDP 服务 (如 DNS,Syslog):
|
||||
|
||||
```bash
|
||||
$ docker run -d -p 53:53/udp dns-server
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 9.6.5 实现原理
|
||||
|
||||
Docker 使用 `docker-proxy` 进程 (用户态) 或 `iptables` DNAT 规则 (内核态) 来实现端口转发。
|
||||
|
||||
当流量到达宿主机端口时,iptables 规则将其目标地址修改为容器 IP 并转发:
|
||||
|
||||
```bash
|
||||
## 简化的 iptables 逻辑
|
||||
|
||||
iptables -t nat -A DOCKER -p tcp --dport 8080 -j DNAT --to-destination 172.17.0.2:80
|
||||
```
|
||||
|
||||
这也是为什么你在容器内部看到的访问来源 IP 通常是网关 IP (如 172.17.0.1),而不是真实的外部 Client IP (除非使用 host 网络模式)。
|
||||
|
||||
---
|
||||
@@ -1,81 +0,0 @@
|
||||
## 9.6 网络隔离
|
||||
|
||||
Docker 网络提供了天然的隔离能力,不同网络之间的容器默认无法通信。这是 Docker 网络安全的重要基础。
|
||||
|
||||
### 9.7.1 网络隔离原理
|
||||
|
||||
不同网络之间默认隔离,容器只能与同一网络中的容器直接通信:
|
||||
|
||||
```bash
|
||||
## 创建两个网络
|
||||
|
||||
$ docker network create frontend
|
||||
$ docker network create backend
|
||||
|
||||
## 容器 A 在 frontend
|
||||
|
||||
$ docker run -d --name web --network frontend nginx
|
||||
|
||||
## 容器 B 在 backend
|
||||
|
||||
$ docker run -d --name db --network backend postgres
|
||||
|
||||
## web 无法直接访问 db(不同网络)
|
||||
|
||||
$ docker exec web ping db
|
||||
ping: db: Name or service not known
|
||||
```
|
||||
|
||||
### 9.7.2 安全优势
|
||||
|
||||
这种隔离机制带来以下安全优势:
|
||||
|
||||
| 场景 | 说明 |
|
||||
|------|------|
|
||||
| **前后端分离** | 前端容器无法直接访问数据库网络 |
|
||||
| **微服务隔离** | 不同微服务组可以使用不同网络 |
|
||||
| **多租户** | 不同租户的容器在不同网络中完全隔离 |
|
||||
| **最小权限** | 容器只能访问必要的网络资源 |
|
||||
|
||||
### 9.7.3 跨网络通信
|
||||
|
||||
如果确实需要某个容器跨网络通信,可以将其同时连接到多个网络:
|
||||
|
||||
```bash
|
||||
## 创建一个中间件容器,连接到两个网络
|
||||
|
||||
$ docker run -d --name api --network frontend myapi
|
||||
$ docker network connect backend api
|
||||
|
||||
## 现在 api 容器既可以访问 frontend 中的 web,也可以访问 backend 中的 db
|
||||
```
|
||||
|
||||
这种方式让你可以精确控制哪些容器可以跨网络通信,遵循最小权限原则。
|
||||
|
||||
### 9.7.4 典型网络架构
|
||||
|
||||
一个典型的多层应用网络架构如下:
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
subgraph FrontendNet ["frontend 网络"]
|
||||
LB["负载均衡器"]
|
||||
Web1["Web 服务器 1"]
|
||||
Web2["Web 服务器 2"]
|
||||
end
|
||||
|
||||
subgraph BackendNet ["backend 网络"]
|
||||
API["API 服务器"]
|
||||
DB["数据库"]
|
||||
Cache["Redis 缓存"]
|
||||
end
|
||||
|
||||
LB --> Web1
|
||||
LB --> Web2
|
||||
Web1 -.-> API
|
||||
Web2 -.-> API
|
||||
API --> DB
|
||||
API --> Cache
|
||||
```
|
||||
|
||||
在这种架构中,API 服务器同时连接到 `frontend` 和 `backend` 网络,充当两个网络之间的桥梁。负载均衡器和 Web 服务器无法直接访问数据库,增强了安全性。
|
||||
@@ -1,41 +0,0 @@
|
||||
# 第九章 网络配置
|
||||
|
||||
Docker 容器需要网络来与外部世界通信、容器之间相互通信以及与宿主机通信。Docker 在安装时会自动配置网络基础设施,大多数情况下开箱即用。
|
||||
|
||||
## 概述
|
||||
|
||||
Docker 启动时自动创建以下网络组件:
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
subgraph Host [宿主机]
|
||||
eth0[物理网卡 eth0<br>192.168.1.100]
|
||||
docker0[docker0 网桥<br>172.17.0.1]
|
||||
|
||||
subgraph Containers
|
||||
subgraph ContainerA [容器 A]
|
||||
eth0_A[eth0<br>172.17.0.2]
|
||||
end
|
||||
subgraph ContainerB [容器 B]
|
||||
eth0_B[eth0<br>172.17.0.3]
|
||||
end
|
||||
end
|
||||
|
||||
eth0 <--> docker0
|
||||
docker0 <--> eth0_A
|
||||
docker0 <--> eth0_B
|
||||
end
|
||||
|
||||
Internet((互联网)) <--> eth0
|
||||
```
|
||||
|
||||
本章将详细介绍 Docker 网络配置的各个方面。
|
||||
|
||||
## 本章内容
|
||||
|
||||
* [配置 DNS](9.1_dns.md)
|
||||
* [外部访问容器](9.5_port_mapping.md)
|
||||
* [网络类型](9.2_network_types.md)
|
||||
* [自定义网络](9.3_custom_network.md)
|
||||
* [容器互联](9.4_container_linking.md)
|
||||
* [网络隔离](9.6_network_isolation.md)
|
||||
@@ -1,24 +0,0 @@
|
||||
## 本章小结
|
||||
|
||||
本章介绍了 Docker 网络配置的各个方面:
|
||||
|
||||
| 概念 | 要点 |
|
||||
|------|------|
|
||||
| **DNS 配置** | 自定义网络支持嵌入式 DNS,可通过容器名解析 |
|
||||
| **网络类型** | bridge (默认)、host、none、overlay、macvlan |
|
||||
| **自定义网络** | 推荐使用,支持容器名 DNS 解析和更好的隔离 |
|
||||
| **容器互联** | 同一自定义网络内容器可直接通过容器名通信 |
|
||||
| **端口映射** | `-p 宿主机端口:容器端口` 暴露服务到外部 |
|
||||
| **网络隔离** | 不同网络默认隔离,增强安全性 |
|
||||
| **--link** | 已废弃,使用自定义网络替代 |
|
||||
|
||||
### 9.8.1 延伸阅读
|
||||
|
||||
- [配置 DNS](9.1_dns.md):自定义 DNS 设置
|
||||
- [网络类型](9.2_network_types.md):Bridge、Host、None 等网络模式
|
||||
- [自定义网络](9.3_custom_network.md):创建和管理自定义网络
|
||||
- [容器互联](9.4_container_linking.md):容器间通信方式
|
||||
- [端口映射](9.5_port_mapping.md):高级端口配置
|
||||
- [网络隔离](9.6_network_isolation.md):网络安全与隔离策略
|
||||
- [EXPOSE 指令](../07_dockerfile/7.9_expose.md):在 Dockerfile 中声明端口
|
||||
- [Compose 网络](../11_compose/11.5_compose_file.md):Compose 中的网络配置
|
||||
@@ -1,43 +0,0 @@
|
||||
## 10.2 使用 buildx 构建镜像
|
||||
|
||||
### 10.2.1 使用
|
||||
|
||||
Buildx 的使用非常直观,绝大多数情况下可以替代 `docker build` 命令。
|
||||
|
||||
你可以直接使用 `docker buildx build` 命令构建镜像。
|
||||
|
||||
```bash
|
||||
$ docker buildx build .
|
||||
[+] Building 8.4s (23/32)
|
||||
=> ...
|
||||
```
|
||||
|
||||
Buildx 使用 [BuildKit 引擎](10.1_buildkit.md)进行构建,支持许多新的功能,具体参考 [Buildkit](10.1_buildkit.md) 一节。
|
||||
|
||||
#### 使用 `bake`
|
||||
|
||||
`docker buildx bake` 是一个高级构建命令,支持从 HCL、JSON 或 Compose 文件中定义构建目标,实现复杂的流水线构建。
|
||||
|
||||
```bash
|
||||
## 从 Compose 文件构建所有服务
|
||||
|
||||
$ docker buildx bake
|
||||
|
||||
## 仅构建指定目标
|
||||
|
||||
$ docker buildx bake web
|
||||
```
|
||||
|
||||
#### 生成 SBOM
|
||||
|
||||
Buildx 支持在构建时直接生成 SBOM (Software Bill of Materials),这对于软件供应链安全至关重要。
|
||||
|
||||
```bash
|
||||
$ docker buildx build --sbom=true -t myimage .
|
||||
```
|
||||
|
||||
该命令会在构建结果中包含 SPDX 或 CycloneDX 格式的 SBOM 数据。
|
||||
|
||||
### 10.2.2 官方文档
|
||||
|
||||
* https://docs.docker.com/engine/reference/commandline/buildx/
|
||||
@@ -1,131 +0,0 @@
|
||||
## 10.3 使用 buildx 构建多种系统架构支持的 Docker 镜像
|
||||
|
||||
Docker 镜像可以支持多种系统架构,这意味着你可以在 `x86_64`、`arm64` 等不同架构的机器上运行同一个镜像。这是通过一个名为 “manifest list” (或称为 “fat manifest”) 的文件来实现的。
|
||||
|
||||
### 10.3.1 Manifest List 是什么?
|
||||
|
||||
为了理解多架构镜像的原理,我们需要先了解 Manifest List 的概念。
|
||||
|
||||
Manifest list 是一个包含了多个指向不同架构镜像的 manifest 的文件。当你拉取一个支持多架构的镜像时,Docker 会自动根据你当前的系统架构选择并拉取对应的镜像。
|
||||
|
||||
例如,官方的 `hello-world` 镜像就支持多种架构。你可以使用 `docker manifest inspect` 命令来查看它的 manifest list:
|
||||
|
||||
```bash
|
||||
$ docker manifest inspect hello-world
|
||||
{
|
||||
"schemaVersion": 2,
|
||||
"mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
|
||||
"manifests": [
|
||||
{
|
||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||
"size": 525,
|
||||
"digest": "sha256:80852a401a974d9e923719a948cc5335a0a4435be8778b475844a7153a2382e5",
|
||||
"platform": {
|
||||
"architecture": "amd64",
|
||||
"os": "linux"
|
||||
}
|
||||
},
|
||||
{
|
||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||
"size": 525,
|
||||
"digest": "sha256:3adea81344be1724b383d501736c3852939b33b3903d02474373700b25e5d6e3",
|
||||
"platform": {
|
||||
"architecture": "arm",
|
||||
"os": "linux",
|
||||
"variant": "v5"
|
||||
}
|
||||
},
|
||||
// ... more architectures
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 10.3.2 使用 `docker buildx` 构建多架构镜像
|
||||
|
||||
`docker buildx` 是构建多架构镜像的最佳实践工具,它屏蔽了底层的复杂性,提供了一键构建多架构镜像的能力。
|
||||
|
||||
在 Docker 19.03+ 版本中,`docker buildx` 是推荐的用于构建多架构镜像的工具。它使用 `BuildKit` 作为后端,可以大大简化构建过程。
|
||||
|
||||
#### 新建 `builder` 实例
|
||||
|
||||
首先,你需要创建一个新的 `builder` 实例,因为它支持同时为多个平台构建。
|
||||
|
||||
```bash
|
||||
$ docker buildx create --name mybuilder --use
|
||||
$ docker buildx inspect --bootstrap
|
||||
```
|
||||
|
||||
#### 构建和推送
|
||||
|
||||
使用 `docker buildx build` 命令并指定 `--platform` 参数,可以同时构建支持多种架构的镜像。`--push` 参数会将构建好的镜像和 manifest list 推送到 Docker 仓库。
|
||||
|
||||
```dockerfile
|
||||
## Dockerfile
|
||||
|
||||
FROM --platform=$TARGETPLATFORM alpine
|
||||
|
||||
RUN uname -a > /os.txt
|
||||
|
||||
CMD cat /os.txt
|
||||
```
|
||||
|
||||
```bash
|
||||
$ docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t your-username/multi-arch-image . --push
|
||||
```
|
||||
|
||||
构建完成后,你就可以在不同架构的机器上拉取并运行 `your-username/multi-arch-image` 这个镜像了。
|
||||
|
||||
#### 架构相关的构建参数
|
||||
|
||||
在 `Dockerfile` 中,你可以使用一些预定义的构建参数来根据目标平台定制构建过程:
|
||||
|
||||
* `TARGETPLATFORM`:构建镜像的目标平台,例如 `linux/amd64`。
|
||||
* `TARGETOS`:目标平台的操作系统,例如 `linux`。
|
||||
* `TARGETARCH`:目标平台的架构,例如 `amd64`。
|
||||
* `TARGETVARIANT`:目标平台的变种,例如 `v7`。
|
||||
* `BUILDPLATFORM`:构建环境的平台。
|
||||
* `BUILDOS`:构建环境的操作系统。
|
||||
* `BUILDARCH`:构建环境的架构。
|
||||
* `BUILDVARIANT`:构建环境的变种。
|
||||
|
||||
例如,你可以这样编写 `Dockerfile` 来拷贝特定架构的二进制文件:
|
||||
|
||||
```dockerfile
|
||||
FROM scratch
|
||||
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
|
||||
COPY bin/dist-${TARGETOS}-${TARGETARCH} /dist
|
||||
|
||||
ENTRYPOINT ["/dist"]
|
||||
```
|
||||
|
||||
### 10.3.3 使用 `docker manifest` (底层工具)
|
||||
|
||||
除了 `docker buildx`,我们也可以直接操作 Manifest List 来手动组合不同架构的镜像。
|
||||
|
||||
`docker manifest` 是一个更底层的命令,可以用来创建、检查和推送 manifest list。虽然 `docker buildx` 在大多数情况下更方便,但了解 `docker manifest` 仍然有助于理解其工作原理。
|
||||
|
||||
#### 创建 manifest list
|
||||
|
||||
```bash
|
||||
## 首先,为每个架构构建并推送镜像
|
||||
|
||||
$ docker buildx build --platform linux/amd64 -t your-username/my-app:amd64 . --push
|
||||
$ docker buildx build --platform linux/arm64 -t your-username/my-app:arm64 . --push
|
||||
|
||||
## 然后,创建一个 manifest list,将它们组合在一起
|
||||
|
||||
$ docker manifest create your-username/my-app:latest \
|
||||
--amend your-username/my-app:amd64 \
|
||||
--amend your-username/my-app:arm64
|
||||
|
||||
## 最后,推送 manifest list
|
||||
|
||||
$ docker manifest push your-username/my-app:latest
|
||||
```
|
||||
|
||||
#### 检查 manifest list
|
||||
|
||||
你可以使用 `docker manifest inspect` 来查看一个 manifest list 的详细信息,如上文所示。
|
||||
@@ -1,13 +0,0 @@
|
||||
# 第十章 Docker Buildx
|
||||
|
||||
Docker Buildx 是一个 docker CLI 插件,其扩展了 docker 命令,支持 [Moby BuildKit](10.1_buildkit.md) 提供的功能。提供了与 docker build 相同的用户体验,并增加了许多新功能。
|
||||
|
||||
> Buildx 需要 Docker v19.03+。在较新版本中已更常用且功能更完整。
|
||||
|
||||
## 本章内容
|
||||
|
||||
本章将详细介绍 Docker Buildx 的使用,包括:
|
||||
|
||||
* [使用 BuildKit 构建镜像](10.1_buildkit.md)
|
||||
* [使用 Buildx 构建镜像](10.2_buildx.md)
|
||||
* [构建多种系统架构支持的 Docker 镜像](10.3_multi-arch-images.md)
|
||||
@@ -1,19 +0,0 @@
|
||||
## 本章小结
|
||||
|
||||
Docker Buildx 是 Docker 构建系统的重要进化,提供了高效、安全且支持多平台的镜像构建能力。
|
||||
|
||||
| 概念 | 要点 |
|
||||
|------|------|
|
||||
| **BuildKit** | 下一代构建引擎,Docker 23.0+ 默认启用 |
|
||||
| **缓存挂载** | `RUN --mount=type=cache` 加速依赖安装 |
|
||||
| **Secret 挂载** | `RUN --mount=type=secret` 安全传递密钥 |
|
||||
| **buildx build** | 替代 `docker build`,支持更多构建功能 |
|
||||
| **多架构构建** | `--platform` 参数一键构建多种架构镜像 |
|
||||
| **Manifest List** | 多架构镜像的索引文件 |
|
||||
| **SBOM** | 通过 `--sbom=true` 生成软件物料清单 |
|
||||
|
||||
### 10.4.1 延伸阅读
|
||||
|
||||
- [Dockerfile 指令详解](../07_dockerfile/README.md):Dockerfile 编写基础
|
||||
- [多阶段构建](../07_dockerfile/7.17_multistage_builds.md):优化镜像体积
|
||||
- [Dockerfile 最佳实践](../appendix/best_practices.md):编写高效 Dockerfile
|
||||
@@ -1,35 +0,0 @@
|
||||
## 11.1 简介
|
||||
|
||||
`Compose` 项目是 Docker 官方的开源项目,负责实现对 Docker 容器集群的快速编排。从功能上看,跟 `OpenStack` 中的 `Heat` 十分类似。
|
||||
|
||||
其代码目前在 [https://github.com/docker/compose](https://github.com/docker/compose) 上开源。
|
||||
|
||||
`Compose` 定位是 “定义和运行多个 Docker 容器的应用 (Defining and running multi-container Docker applications)”,其前身是开源项目 Fig。
|
||||
|
||||
通过第一部分中的介绍,我们知道使用一个 `Dockerfile` 模板文件,可以让用户很方便的定义一个单独的应用容器。然而,在日常工作中,经常会碰到需要多个容器相互配合来完成某项任务的情况。例如要实现一个 Web 项目,除了 Web 服务容器本身,往往还需要再加上后端的数据库服务容器,甚至还包括负载均衡容器等。
|
||||
|
||||
`Compose` 恰好满足了这样的需求。它允许用户通过一个单独的 `compose.yaml` (历史默认名也常见为 `docker-compose.yml`) 模板文件 (YAML 格式) 来定义一组相关联的应用容器为一个项目 (project)。
|
||||
|
||||
### 11.1.1 概述
|
||||
|
||||
### 11.1.2 模板文件规范
|
||||
|
||||
Compose 模板文件采用 YAML 格式,扩展名为 `.yml` 或 `.yaml`。
|
||||
|
||||
> **注意**:自 Compose V2 起,`version` 字段已不再强制要求。在 Docker Compose v5 中,规范已完全不需要顶层 `version` 字段。为了保持最佳兼容性,建议不在新文件中使用该字段。
|
||||
|
||||
Docker Compose 默认使用 `compose.yaml`,也兼容 `compose.yml`、`docker-compose.yaml`、`docker-compose.yml` 等文件名。
|
||||
|
||||
`Compose` 中有两个重要的概念:
|
||||
|
||||
* 服务 (`service`):一个应用的容器,实际上可以包括若干运行相同镜像的容器实例。
|
||||
|
||||
* 项目 (`project`):由一组关联的应用容器组成的一个完整业务单元,在 Compose 文件中定义。
|
||||
|
||||
`Compose` 的默认管理对象是项目,通过子命令对项目中的一组容器进行便捷地生命周期管理。
|
||||
|
||||
`Compose` 项目早期由 Python 编写,称为 Docker Compose V1。
|
||||
|
||||
现在的 Docker Compose V2 是一个 Go 语言编写的 Docker CLI 插件,已经集成到 Docker Desktop 和 Docker Engine 中,直接通过 `docker compose` 命令使用。它提供了更快的性能和更好的集成体验。
|
||||
|
||||
只要所操作的平台支持 Docker API,就可以在其上利用 `Compose` 来进行编排管理。
|
||||
@@ -1,54 +0,0 @@
|
||||
## 11.2 安装与卸载
|
||||
|
||||
`Compose` 是 Docker 官方的开源项目,负责实现对 Docker 容器集群的快速编排。
|
||||
|
||||
从 `v2` 版本开始,`Compose` 作为 `docker` 的子命令存在,例如 `docker compose up`。
|
||||
|
||||
`Compose` 支持 Linux、macOS、Windows 10 三大平台。
|
||||
|
||||
`Docker Desktop for Mac/Windows` 自带 `docker compose` 命令。
|
||||
|
||||
Linux 系统请使用以下介绍的方法安装。
|
||||
|
||||
### 11.2.1 Linux
|
||||
|
||||
在 Linux 上,你可以通过下载 Docker Compose CLI 插件 (二进制文件名为 `docker-compose`) 来安装。
|
||||
|
||||
从[官方 GitHub Release](https://github.com/docker/compose/releases) 处直接下载编译好的二进制文件即可。
|
||||
|
||||
> **提示**:版本更新较快,请访问上述链接获取最新版本号,替换下方命令中的版本号。
|
||||
|
||||
例如,在 Linux 64 位系统上直接下载对应的二进制包 (以 v5.0.2 为例)。
|
||||
|
||||
```bash
|
||||
$ DOCKER_CONFIG=${DOCKER_CONFIG:-$HOME/.docker}
|
||||
$ mkdir -p $DOCKER_CONFIG/cli-plugins
|
||||
$ curl -SL https://github.com/docker/compose/releases/download/v5.0.2/docker-compose-linux-x86_64 -o $DOCKER_CONFIG/cli-plugins/docker-compose
|
||||
```
|
||||
|
||||
之后,执行
|
||||
|
||||
```bash
|
||||
$ chmod +x $DOCKER_CONFIG/cli-plugins/docker-compose
|
||||
```
|
||||
|
||||
### 11.2.2 测试安装
|
||||
|
||||
```bash
|
||||
$ docker compose version
|
||||
Docker Compose version v5.0.2
|
||||
```
|
||||
|
||||
### 11.2.3 bash 补全命令
|
||||
|
||||
```bash
|
||||
$ curl -L https://raw.githubusercontent.com/docker/compose/v5.0.2/contrib/completion/bash/docker-compose | sudo tee /etc/bash_completion.d/docker-compose > /dev/null
|
||||
```
|
||||
|
||||
### 11.2.4 卸载
|
||||
|
||||
如果是二进制包方式安装的,删除二进制文件即可。
|
||||
|
||||
```bash
|
||||
$ rm $DOCKER_CONFIG/cli-plugins/docker-compose
|
||||
```
|
||||
@@ -1,134 +0,0 @@
|
||||
## 11.3 使用
|
||||
|
||||
本节将通过一个具体的 Web 应用案例,介绍 Docker Compose 的基本概念和使用方法。
|
||||
|
||||
### 11.3.1 术语
|
||||
|
||||
首先介绍几个术语。
|
||||
|
||||
* 服务 (`service`):一个应用容器,实际上可以运行多个相同镜像的实例。
|
||||
|
||||
* 项目 (`project`):由一组关联的应用容器组成的一个完整业务单元。
|
||||
|
||||
可见,一个项目可以由多个服务 (容器) 关联而成,`Compose` 面向项目进行管理。
|
||||
|
||||
### 11.3.2 场景
|
||||
|
||||
最常见的项目是 web 网站,该项目应该包含 web 应用和缓存。
|
||||
|
||||
下面我们用 `Python` 来建立一个能够记录页面访问次数的 web 网站。
|
||||
|
||||
#### web 应用
|
||||
|
||||
新建文件夹,在该目录中编写 `app.py` 文件
|
||||
|
||||
```python
|
||||
from flask import Flask
|
||||
from redis import Redis
|
||||
|
||||
app = Flask(__name__)
|
||||
redis = Redis(host='redis', port=6379)
|
||||
|
||||
@app.route('/')
|
||||
def hello():
|
||||
count = redis.incr('hits')
|
||||
return 'Hello World! 该页面已被访问 {} 次。\n'.format(count)
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(host="0.0.0.0", debug=True)
|
||||
```
|
||||
|
||||
#### Dockerfile
|
||||
|
||||
编写 `Dockerfile` 文件,内容为
|
||||
|
||||
```docker
|
||||
FROM python:3.12-alpine
|
||||
ADD . /code
|
||||
WORKDIR /code
|
||||
RUN pip install redis flask
|
||||
CMD ["python", "app.py"]
|
||||
```
|
||||
|
||||
#### compose.yaml
|
||||
|
||||
编写 `compose.yaml` 文件,这是 Compose 推荐使用的主模板文件 (也兼容 `docker-compose.yml` 等历史文件名)。
|
||||
|
||||
```yaml
|
||||
services:
|
||||
web:
|
||||
build: .
|
||||
ports:
|
||||
- "5000:5000"
|
||||
|
||||
redis:
|
||||
image: "redis:alpine"
|
||||
```
|
||||
|
||||
#### 运行 compose 项目
|
||||
|
||||
```bash
|
||||
$ docker compose up
|
||||
```
|
||||
|
||||
此时访问本地 `5000` 端口,每次刷新页面,计数就会加 1。
|
||||
|
||||
|
||||
按下 `Ctrl-C` 停止项目。
|
||||
|
||||
#### 后台运行
|
||||
|
||||
```bash
|
||||
$ docker compose up -d
|
||||
```
|
||||
|
||||
#### 停止
|
||||
|
||||
```bash
|
||||
$ docker compose stop
|
||||
```
|
||||
|
||||
#### 进入服务
|
||||
|
||||
```bash
|
||||
$ docker compose exec redis sh
|
||||
/data # redis-cli
|
||||
127.0.0.1:6379> get hits
|
||||
"9"
|
||||
```
|
||||
|
||||
#### 查看日志
|
||||
|
||||
```bash
|
||||
$ docker compose logs -f
|
||||
```
|
||||
|
||||
#### 构建镜像
|
||||
|
||||
```bash
|
||||
$ docker compose build
|
||||
```
|
||||
|
||||
#### 启动服务
|
||||
|
||||
```bash
|
||||
$ docker compose start
|
||||
```
|
||||
|
||||
#### 运行一次性命令
|
||||
|
||||
```bash
|
||||
$ docker compose run web python app.py
|
||||
```
|
||||
|
||||
#### 验证 Compose 文件
|
||||
|
||||
```bash
|
||||
$ docker compose config
|
||||
```
|
||||
|
||||
#### 删除项目
|
||||
|
||||
```bash
|
||||
$ docker compose down
|
||||
```
|
||||
@@ -1,364 +0,0 @@
|
||||
## 11.6 实战 Django
|
||||
|
||||
> 本小节内容适合 `Python` 开发人员阅读。
|
||||
|
||||
本节将使用 Docker Compose 配置并运行一个 **Django + PostgreSQL** 应用。笔者不仅会介绍具体步骤,还会解释每个配置项的作用,以及开发环境和生产环境的差异。
|
||||
|
||||
### 11.6.1 架构概览
|
||||
|
||||
在开始之前,先看整体架构 (如图 10-1 所示):
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph Network ["Docker Compose 网络"]
|
||||
direction LR
|
||||
subgraph Web ["web 服务"]
|
||||
direction TB
|
||||
Django["Django<br/>应用"]
|
||||
Port8000[":8000"]
|
||||
Django ~~~ Port8000
|
||||
end
|
||||
|
||||
subgraph DB ["db 服务"]
|
||||
direction TB
|
||||
Postgres["PostgreSQL<br/>数据库"]
|
||||
end
|
||||
|
||||
Django -- ":5432" --> Postgres
|
||||
end
|
||||
|
||||
Browser["localhost:8000<br/>(浏览器访问)"]
|
||||
Port8000 --> Browser
|
||||
```
|
||||
|
||||
图 10-1 Django + PostgreSQL 的 Compose 架构
|
||||
|
||||
**关键点**:
|
||||
|
||||
- `web` 服务运行 Django 应用,对外暴露 8000 端口
|
||||
- `db` 服务运行 PostgreSQL 数据库,只在内部网络可访问
|
||||
- 两个服务通过 Docker Compose 自动创建的网络相互通信
|
||||
- `web` 服务可以通过服务名 `db` 访问数据库 (Docker 内置 DNS)
|
||||
|
||||
### 11.6.2 准备工作
|
||||
|
||||
创建一个项目目录并进入:
|
||||
|
||||
```bash
|
||||
$ mkdir django-docker && cd django-docker
|
||||
```
|
||||
|
||||
我们需要创建三个文件:`Dockerfile`、`requirements.txt` 和 `compose.yaml`。
|
||||
|
||||
### 11.6.3 步骤 1:创建 Dockerfile
|
||||
|
||||
```docker
|
||||
FROM python:3.12-slim
|
||||
|
||||
## 防止 Python 缓冲 stdout/stderr,让日志实时输出
|
||||
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
|
||||
## 设置工作目录
|
||||
|
||||
WORKDIR /code
|
||||
|
||||
## 先复制依赖文件,利用 Docker 缓存加速构建
|
||||
|
||||
COPY requirements.txt /code/
|
||||
|
||||
## 安装依赖
|
||||
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
## 复制项目代码
|
||||
|
||||
COPY . /code/
|
||||
```
|
||||
|
||||
**逐行解释**:
|
||||
|
||||
| 指令 | 作用 | 为什么这样写 |
|
||||
|------|------|-------------|
|
||||
| `FROM python:3.12-slim` | 基础镜像 | `slim` 版本比完整版小很多,但包含运行 Python 所需的一切 |
|
||||
| `ENV PYTHONUNBUFFERED=1` | 关闭输出缓冲 | 让 `print()` 和日志立即显示,便于调试 |
|
||||
| `WORKDIR /code` | 设置工作目录 | 后续命令都在此目录执行 |
|
||||
| `COPY requirements.txt` 在前 | 分层复制 | 只有 requirements.txt 变化时才重新安装依赖,加速构建 |
|
||||
| `--no-cache-dir` | 不缓存 pip 下载 | 减小镜像体积 |
|
||||
|
||||
> 💡 **笔者建议**:总是将变化频率低的文件先复制,变化频率高的后复制。这样可以最大化利用 Docker 的构建缓存。
|
||||
|
||||
### 11.6.4 步骤 2:创建 requirements.txt
|
||||
|
||||
```txt
|
||||
Django>=5.0,<6.0
|
||||
psycopg[binary]>=3.1,<4.0
|
||||
gunicorn>=21.0,<22.0
|
||||
```
|
||||
|
||||
**依赖说明**:
|
||||
|
||||
| 包名 | 作用 |
|
||||
|------|------|
|
||||
| `Django` | Web 框架 |
|
||||
| `psycopg[binary]` | PostgreSQL 数据库驱动 (推荐使用 psycopg 3)|
|
||||
| `gunicorn` | 生产环境 WSGI 服务器 (可选,开发时可不用)|
|
||||
|
||||
### 11.6.5 步骤 3:创建 compose.yaml
|
||||
|
||||
配置如下:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
db:
|
||||
image: postgres:16
|
||||
environment:
|
||||
POSTGRES_DB: django_db
|
||||
POSTGRES_USER: django_user
|
||||
POSTGRES_PASSWORD: django_password
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U django_user -d django_db"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
web:
|
||||
build: .
|
||||
command: python manage.py runserver 0.0.0.0:8000
|
||||
volumes:
|
||||
- .:/code
|
||||
ports:
|
||||
- "8000:8000"
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
DATABASE_URL: postgres://django_user:django_password@db:5432/django_db
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
```
|
||||
|
||||
**配置详解**:
|
||||
|
||||
#### db 服务
|
||||
|
||||
db 服务配置如下:
|
||||
|
||||
```yaml
|
||||
db:
|
||||
image: postgres:16 # 使用官方 PostgreSQL 16 镜像
|
||||
environment:
|
||||
POSTGRES_DB: django_db # 创建的数据库名
|
||||
POSTGRES_USER: django_user # 数据库用户
|
||||
POSTGRES_PASSWORD: django_password # 数据库密码
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data # 持久化数据
|
||||
healthcheck: # 健康检查,确保数据库就绪
|
||||
test: ["CMD-SHELL", "pg_isready -U django_user -d django_db"]
|
||||
interval: 5s
|
||||
```
|
||||
|
||||
> ⚠️ **笔者提醒**:`volumes` 配置很重要!没有它,每次容器重启数据都会丢失。笔者见过不少新手因为忘记这一步,导致开发数据全部丢失。
|
||||
|
||||
#### web 服务
|
||||
|
||||
web 服务配置如下:
|
||||
|
||||
```yaml
|
||||
web:
|
||||
build: . # 从当前目录的 Dockerfile 构建
|
||||
command: python manage.py runserver # 启动 Django 开发服务器
|
||||
volumes:
|
||||
- .:/code # 挂载代码目录,支持热更新
|
||||
ports:
|
||||
- "8000:8000" # 映射端口
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy # 等待数据库健康后再启动
|
||||
```
|
||||
|
||||
**关键配置说明**:
|
||||
|
||||
| 配置项 | 作用 | 笔者建议 |
|
||||
|--------|------|---------|
|
||||
| `volumes: .:/code` | 代码挂载 | 开发时必备,修改代码无需重新构建镜像 |
|
||||
| `depends_on` + `healthcheck` | 启动顺序 | 确保数据库就绪后 Django 才启动,避免连接错误 |
|
||||
| `environment` | 环境变量 | 推荐用环境变量管理配置,避免硬编码 |
|
||||
|
||||
### 11.6.6 步骤 4:创建 Django 项目
|
||||
|
||||
运行以下命令创建新的 Django 项目:
|
||||
|
||||
```bash
|
||||
$ docker compose run --rm web django-admin startproject mysite .
|
||||
```
|
||||
|
||||
**命令解释**:
|
||||
|
||||
- `docker compose run`:运行一次性命令
|
||||
- `--rm`:命令执行后删除临时容器
|
||||
- `web`:在 web 服务环境中执行
|
||||
- `django-admin startproject mysite .`:在当前目录创建 Django 项目
|
||||
|
||||
生成的目录结构:
|
||||
|
||||
```bash
|
||||
django-docker/
|
||||
├── compose.yaml
|
||||
├── Dockerfile
|
||||
├── requirements.txt
|
||||
├── manage.py
|
||||
└── mysite/
|
||||
├── __init__.py
|
||||
├── settings.py
|
||||
├── urls.py
|
||||
├── asgi.py
|
||||
└── wsgi.py
|
||||
```
|
||||
|
||||
> 💡 **Linux 用户注意**:如果遇到权限问题,执行 `sudo chown -R $USER:$USER .`
|
||||
|
||||
### 11.6.7 步骤 5:配置数据库连接
|
||||
|
||||
修改 `mysite/settings.py`,配置数据库连接:
|
||||
|
||||
```python
|
||||
import os
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.postgresql',
|
||||
'NAME': os.environ.get('POSTGRES_DB', 'django_db'),
|
||||
'USER': os.environ.get('POSTGRES_USER', 'django_user'),
|
||||
'PASSWORD': os.environ.get('POSTGRES_PASSWORD', 'django_password'),
|
||||
'HOST': 'db', # Docker Compose 服务名
|
||||
'PORT': 5432,
|
||||
}
|
||||
}
|
||||
|
||||
## 允许的主机(开发环境)
|
||||
|
||||
ALLOWED_HOSTS = ['*']
|
||||
```
|
||||
|
||||
**为什么 HOST 是 `db` 而不是 `localhost`?**
|
||||
|
||||
在 Docker Compose 中,各服务通过服务名相互访问。Docker 内置的 DNS 会将 `db` 解析为 db 服务容器的 IP 地址。这是 Docker Compose 的核心功能之一。
|
||||
|
||||
### 11.6.8 步骤 6:启动应用
|
||||
|
||||
```bash
|
||||
$ docker compose up
|
||||
```
|
||||
|
||||
你会看到:
|
||||
|
||||
1. 首先构建 web 镜像 (第一次运行)
|
||||
2. 启动 db 服务,等待健康检查通过
|
||||
3. 启动 web 服务
|
||||
|
||||
```bash
|
||||
db-1 | PostgreSQL init process complete; ready for start up.
|
||||
db-1 | LOG: database system is ready to accept connections
|
||||
web-1 | Watching for file changes with StatReloader
|
||||
web-1 | Starting development server at http://0.0.0.0:8000/
|
||||
```
|
||||
|
||||
打开浏览器访问 http://localhost:8000,可以看到 Django 欢迎页面!
|
||||
|
||||
### 11.6.9 常用开发命令
|
||||
|
||||
在另一个终端窗口执行:
|
||||
|
||||
```bash
|
||||
## 执行数据库迁移
|
||||
|
||||
$ docker compose exec web python manage.py migrate
|
||||
|
||||
## 创建超级用户
|
||||
|
||||
$ docker compose exec web python manage.py createsuperuser
|
||||
|
||||
## 进入 Django shell
|
||||
|
||||
$ docker compose exec web python manage.py shell
|
||||
|
||||
## 进入 PostgreSQL 命令行
|
||||
|
||||
$ docker compose exec db psql -U django_user -d django_db
|
||||
```
|
||||
|
||||
> 💡 笔者建议使用 `exec` 而不是 `run`。`exec` 在已运行的容器中执行命令,`run` 会创建新容器。
|
||||
|
||||
### 11.6.10 常见问题排查
|
||||
|
||||
#### Q1:数据库连接失败
|
||||
|
||||
**错误信息**:`django.db.utils.OperationalError: could not connect to server` **可能原因与解决方案**:
|
||||
|
||||
| 原因 | 解决方案 |
|
||||
|------|---------|
|
||||
| 数据库还没启动完成 | 使用 `depends_on` + `healthcheck` |
|
||||
| HOST 配置错误 | 确保使用服务名 `db` 而不是 `localhost` |
|
||||
| 网络未创建 | 运行 `docker compose down` 后重新 `up` |
|
||||
|
||||
```bash
|
||||
## 调试:检查数据库是否正常运行
|
||||
|
||||
$ docker compose ps
|
||||
$ docker compose logs db
|
||||
```
|
||||
|
||||
#### Q2:代码修改没有生效
|
||||
|
||||
**可能原因**:
|
||||
|
||||
1. **开发服务器没有自动重载**:确保使用 `runserver` 而不是 `gunicorn`
|
||||
2. **Volume 挂载问题**:检查 `compose.yaml` 中的 volumes 配置
|
||||
3. **缓存问题**:尝试 `docker compose restart web`
|
||||
|
||||
#### Q3:权限问题
|
||||
|
||||
```bash
|
||||
## 如果容器内创建的文件 root 用户所有
|
||||
|
||||
$ sudo chown -R $USER:$USER .
|
||||
```
|
||||
|
||||
### 11.6.11 开发 vs 生产:关键差异
|
||||
|
||||
笔者特别提醒,本节的配置是 **开发环境** 配置。生产环境需要以下调整:
|
||||
|
||||
| 配置项 | 开发环境 | 生产环境 |
|
||||
|--------|---------|---------|
|
||||
| **Web 服务器** | `runserver` | `gunicorn` + Nginx |
|
||||
| **DEBUG** | `True` | `False` |
|
||||
| **密码管理** | 明文写在配置 | 使用 Docker Secrets 或环境变量 |
|
||||
| **Volume** | 挂载代码目录 | 代码直接 COPY 进镜像 |
|
||||
| **ALLOWED_HOSTS**| `['*']` | 具体域名 |**生产环境 Compose 文件示例**:
|
||||
|
||||
```yaml
|
||||
## compose.prod.yaml
|
||||
|
||||
services:
|
||||
web:
|
||||
build: .
|
||||
command: gunicorn mysite.wsgi:application --bind 0.0.0.0:8000
|
||||
# 不挂载代码,使用镜像内的代码
|
||||
|
||||
environment:
|
||||
DEBUG: 'False'
|
||||
ALLOWED_HOSTS: 'example.com,www.example.com'
|
||||
# ...
|
||||
|
||||
```
|
||||
|
||||
### 11.6.12 延伸阅读
|
||||
|
||||
- [Compose 模板文件详解](11.5_compose_file.md):深入理解 Compose 文件的所有配置项
|
||||
- [使用 WordPress](11.8_wordpress.md):另一个 Compose 实战案例
|
||||
- [Dockerfile 最佳实践](../appendix/best_practices.md):构建更小、更安全的镜像
|
||||
- [数据管理](../08_data/README.md):Volume 和数据持久化详解
|
||||
@@ -1,289 +0,0 @@
|
||||
## 11.7 实战 Rails
|
||||
|
||||
> 本小节内容适合 Ruby 开发人员阅读。
|
||||
|
||||
本节使用 Docker Compose 配置并运行一个 **Rails + PostgreSQL** 应用。
|
||||
|
||||
### 11.7.1 架构概览
|
||||
|
||||
如图 10-2 所示,Rails 与 PostgreSQL 在同一 Compose 网络中协同工作。
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph Network ["Docker Compose 网络"]
|
||||
direction LR
|
||||
subgraph Web ["web 服务"]
|
||||
direction TB
|
||||
Rails["Rails<br/>应用"]
|
||||
Port3000[":3000"]
|
||||
Rails ~~~ Port3000
|
||||
end
|
||||
|
||||
subgraph DB ["db 服务"]
|
||||
direction TB
|
||||
Postgres["PostgreSQL<br/>数据库"]
|
||||
end
|
||||
|
||||
Rails -- ":5432" --> Postgres
|
||||
end
|
||||
|
||||
Browser["localhost:3000"]
|
||||
Port3000 --> Browser
|
||||
```
|
||||
|
||||
图 10-2 Rails + PostgreSQL 的 Compose 架构
|
||||
|
||||
### 11.7.2 准备工作
|
||||
|
||||
创建项目目录:
|
||||
|
||||
```bash
|
||||
$ mkdir rails-docker && cd rails-docker
|
||||
```
|
||||
|
||||
需要创建三个文件:`Dockerfile`、`Gemfile` 和 `compose.yaml`。
|
||||
|
||||
### 11.7.3 步骤 1:创建 Dockerfile
|
||||
|
||||
```docker
|
||||
FROM ruby:3.2
|
||||
|
||||
## 安装系统依赖
|
||||
|
||||
RUN apt-get update -qq && \
|
||||
apt-get install -y build-essential libpq-dev nodejs && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
## 设置工作目录
|
||||
|
||||
WORKDIR /myapp
|
||||
|
||||
## 先复制 Gemfile,利用缓存加速构建
|
||||
|
||||
COPY Gemfile /myapp/Gemfile
|
||||
COPY Gemfile.lock /myapp/Gemfile.lock
|
||||
RUN bundle install
|
||||
|
||||
## 复制应用代码
|
||||
|
||||
COPY . /myapp
|
||||
```
|
||||
|
||||
**配置说明**:
|
||||
|
||||
| 指令 | 作用 |
|
||||
|------|------|
|
||||
| `build-essential` | 编译原生扩展所需 |
|
||||
| `libpq-dev` | PostgreSQL 客户端库 |
|
||||
| `nodejs` | Rails Asset Pipeline 需要 |
|
||||
| 先复制 Gemfile | 只有依赖变化时才重新 `bundle install` |
|
||||
|
||||
### 11.7.4 步骤 2:创建 Gemfile
|
||||
|
||||
创建一个初始的 `Gemfile`,稍后会被 `rails new` 覆盖:
|
||||
|
||||
```ruby
|
||||
source 'https://rubygems.org'
|
||||
gem 'rails', '~> 7.1'
|
||||
```
|
||||
|
||||
创建空的 `Gemfile.lock`:
|
||||
|
||||
```bash
|
||||
$ touch Gemfile.lock
|
||||
```
|
||||
|
||||
### 11.7.5 步骤 3:创建 compose.yaml
|
||||
|
||||
配置如下:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
db:
|
||||
image: postgres:16
|
||||
environment:
|
||||
POSTGRES_PASSWORD: password
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
|
||||
web:
|
||||
build: .
|
||||
command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
|
||||
volumes:
|
||||
- .:/myapp
|
||||
ports:
|
||||
- "3000:3000"
|
||||
depends_on:
|
||||
- db
|
||||
environment:
|
||||
DATABASE_URL: postgres://postgres:password@db:5432/myapp_development
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
```
|
||||
|
||||
**配置详解**:
|
||||
|
||||
| 配置项 | 说明 |
|
||||
|--------|------|
|
||||
| `rm -f tmp/pids/server.pid` | 清理上次异常退出留下的 PID 文件 |
|
||||
| `volumes: .:/myapp` | 挂载代码目录,支持热更新 |
|
||||
| `depends_on: db` | 确保数据库先启动 |
|
||||
| `DATABASE_URL` | Rails 12-factor 风格的数据库配置 |
|
||||
|
||||
### 11.7.6 步骤 4:生成 Rails 项目
|
||||
|
||||
使用 `docker compose run` 生成项目骨架:
|
||||
|
||||
```bash
|
||||
$ docker compose run --rm web rails new . --force --database=postgresql --skip-bundle
|
||||
```
|
||||
|
||||
**命令解释**:
|
||||
|
||||
- `--rm`:执行后删除临时容器
|
||||
- `--force`:覆盖已存在的文件
|
||||
- `--database=postgresql`:配置使用 PostgreSQL
|
||||
- `--skip-bundle`:暂不安装依赖 (稍后统一安装)
|
||||
|
||||
生成的目录结构:
|
||||
|
||||
```bash
|
||||
$ ls
|
||||
Dockerfile Gemfile Rakefile config lib tmp
|
||||
Gemfile.lock README.md app config.ru log vendor
|
||||
compose.yaml bin db public
|
||||
|
||||
```
|
||||
|
||||
> ⚠️ **Linux 用户**:如遇权限问题,执行 `sudo chown -R $USER:$USER .`
|
||||
|
||||
### 11.7.7 步骤 5:重新构建镜像
|
||||
|
||||
由于生成了新的 Gemfile,需要重新构建镜像以安装完整依赖:
|
||||
|
||||
```bash
|
||||
$ docker compose build
|
||||
```
|
||||
|
||||
### 11.7.8 步骤 6:配置数据库连接
|
||||
|
||||
修改 `config/database.yml`:
|
||||
|
||||
```yaml
|
||||
default: &default
|
||||
adapter: postgresql
|
||||
encoding: unicode
|
||||
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
|
||||
url: <%= ENV['DATABASE_URL'] %>
|
||||
|
||||
development:
|
||||
<<: *default
|
||||
|
||||
test:
|
||||
<<: *default
|
||||
database: myapp_test
|
||||
|
||||
production:
|
||||
<<: *default
|
||||
```
|
||||
|
||||
> 💡 使用 `DATABASE_URL` 环境变量配置数据库,符合 12-factor 应用原则,便于在不同环境间切换。
|
||||
|
||||
### 11.7.9 步骤 7:启动应用
|
||||
|
||||
```bash
|
||||
$ docker compose up
|
||||
```
|
||||
|
||||
输出示例:
|
||||
|
||||
```bash
|
||||
db-1 | PostgreSQL init process complete; ready for start up.
|
||||
db-1 | LOG: database system is ready to accept connections
|
||||
web-1 | => Booting Puma
|
||||
web-1 | => Rails 7.1.0 application starting in development
|
||||
web-1 | => Run `bin/rails server --help` for more startup options
|
||||
web-1 | Puma starting in single mode...
|
||||
web-1 | * Listening on http://0.0.0.0:3000
|
||||
```
|
||||
|
||||
### 11.7.10 步骤 8:创建数据库
|
||||
|
||||
在另一个终端执行:
|
||||
|
||||
```bash
|
||||
$ docker compose exec web rails db:create
|
||||
Created database 'myapp_development'
|
||||
Created database 'myapp_test'
|
||||
```
|
||||
|
||||
访问 http://localhost:3000 查看 Rails 欢迎页面。
|
||||
|
||||
### 11.7.11 常用开发命令
|
||||
|
||||
```bash
|
||||
## 数据库迁移
|
||||
|
||||
$ docker compose exec web rails db:migrate
|
||||
|
||||
## Rails 控制台
|
||||
|
||||
$ docker compose exec web rails console
|
||||
|
||||
## 运行测试
|
||||
|
||||
$ docker compose exec web rails test
|
||||
|
||||
## 生成脚手架
|
||||
|
||||
$ docker compose exec web rails generate scaffold Post title:string body:text
|
||||
|
||||
## 进入容器 Shell
|
||||
|
||||
$ docker compose exec web bash
|
||||
```
|
||||
|
||||
### 11.7.12 常见问题
|
||||
|
||||
#### Q:数据库连接失败
|
||||
|
||||
检查 `DATABASE_URL` 环境变量格式是否正确,确保 db 服务已启动:
|
||||
|
||||
```bash
|
||||
$ docker compose ps
|
||||
$ docker compose logs db
|
||||
```
|
||||
|
||||
#### Q:server.pid 文件导致启动失败
|
||||
|
||||
错误信息:`A server is already running`
|
||||
|
||||
已在 command 中添加 `rm -f tmp/pids/server.pid` 处理。如仍有问题:
|
||||
|
||||
```bash
|
||||
$ docker compose exec web rm -f tmp/pids/server.pid
|
||||
```
|
||||
|
||||
#### Q:Gem 安装失败
|
||||
|
||||
可能需要更新 bundler 或清理缓存:
|
||||
|
||||
```bash
|
||||
$ docker compose run --rm web bundle update
|
||||
```
|
||||
|
||||
### 11.7.13 开发 vs 生产
|
||||
|
||||
| 配置项 | 开发环境 | 生产环境 |
|
||||
|--------|---------|---------|
|
||||
| Rails 服务器 | Puma (开发模式) | Puma + Nginx |
|
||||
| 代码挂载 | 使用 volumes | 代码打包进镜像 |
|
||||
| 静态资源 | 动态编译 | 预编译 (`rails assets:precompile`) |
|
||||
| 数据库密码 | 明文配置 | 使用 Secrets 管理 |
|
||||
|
||||
### 11.7.14 延伸阅读
|
||||
|
||||
- [使用 Django](11.6_django.md):Python Web 框架实战
|
||||
- [Compose 模板文件](11.5_compose_file.md):配置详解
|
||||
- [数据管理](../08_data/README.md):数据持久化
|
||||
@@ -1,213 +0,0 @@
|
||||
## 11.8 实战 WordPress
|
||||
|
||||
WordPress 是全球最流行的内容管理系统 (CMS)。使用 Docker Compose 可以在几分钟内搭建一个包含数据库、Web 服务和持久化存储的生产级 WordPress 环境。
|
||||
|
||||
---
|
||||
|
||||
### 11.8.1 项目结构
|
||||
|
||||
```bash
|
||||
wordpress/
|
||||
├── compose.yaml
|
||||
├── .env # 环境变量(敏感信息)
|
||||
└── nginx/ # 可选:反向代理配置
|
||||
└── nginx.conf
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 11.8.2 编写 `compose.yaml`
|
||||
|
||||
这是一个生产可用的最小化配置:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
# 数据库服务
|
||||
|
||||
db:
|
||||
image: mysql:8.0
|
||||
container_name: wordpress_db
|
||||
restart: always
|
||||
command:
|
||||
# 使用原生密码认证(旧版 WP 兼容性)
|
||||
|
||||
- --default-authentication-plugin=mysql_native_password
|
||||
- --character-set-server=utf8mb4
|
||||
- --collation-server=utf8mb4_unicode_ci
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
|
||||
MYSQL_DATABASE: wordpress
|
||||
MYSQL_USER: wordpress
|
||||
MYSQL_PASSWORD: ${DB_PASSWORD}
|
||||
volumes:
|
||||
- db_data:/var/lib/mysql
|
||||
networks:
|
||||
- wp_net
|
||||
|
||||
# WordPress 服务
|
||||
|
||||
wordpress:
|
||||
image: wordpress:latest
|
||||
container_name: wordpress_app
|
||||
restart: always
|
||||
ports:
|
||||
- "8000:80"
|
||||
environment:
|
||||
WORDPRESS_DB_HOST: db:3306
|
||||
WORDPRESS_DB_USER: wordpress
|
||||
WORDPRESS_DB_PASSWORD: ${DB_PASSWORD}
|
||||
WORDPRESS_DB_NAME: wordpress
|
||||
volumes:
|
||||
- wp_data:/var/www/html
|
||||
# 增加上传文件大小限制
|
||||
|
||||
- ./uploads.ini:/usr/local/etc/php/conf.d/uploads.ini
|
||||
depends_on:
|
||||
- db
|
||||
networks:
|
||||
- wp_net
|
||||
|
||||
volumes:
|
||||
db_data: # 数据库持久化
|
||||
wp_data: # WordPress 文件(插件/主题/上传)持久化
|
||||
|
||||
networks:
|
||||
wp_net:
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 11.8.3 配置文件详解
|
||||
|
||||
#### 1. 环境变量 (.env)
|
||||
|
||||
为了安全,不要在 `compose.yaml` 中直接写密码。创建 `.env` 文件:
|
||||
|
||||
```ini
|
||||
DB_ROOT_PASSWORD=somestrongrootpassword
|
||||
DB_PASSWORD=somestronguserpassword
|
||||
```
|
||||
|
||||
Compose 会自动读取此同级目录下的文件。
|
||||
|
||||
#### 2. 数据持久化
|
||||
|
||||
我们定义了两个命名卷:
|
||||
|
||||
- `db_data`:确保 MySQL 容器重建后数据不丢失
|
||||
- `wp_data`:保存 WordPress 的核心文件、插件、主题和上传的媒体文件
|
||||
|
||||
#### 3. PHP 配置优化
|
||||
|
||||
默认的 WordPress 镜像上传文件限制较小 (通常 2MB)。创建 `uploads.ini`:
|
||||
|
||||
```ini
|
||||
file_uploads = On
|
||||
memory_limit = 256M
|
||||
upload_max_filesize = 64M
|
||||
post_max_size = 64M
|
||||
max_execution_time = 600
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 11.8.4 启动与运行
|
||||
|
||||
1. 启动服务:
|
||||
|
||||
```bash
|
||||
$ docker compose up -d
|
||||
```
|
||||
|
||||
2. 访问安装界面:
|
||||
打开浏览器访问 `http://localhost:8000`
|
||||
|
||||
3. 查看日志:
|
||||
|
||||
```bash
|
||||
$ docker compose logs -f
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 11.8.5 生产环境最佳实践
|
||||
|
||||
#### 1. 数据库备份
|
||||
|
||||
不要只依赖 Volume。建议定期备份数据库:
|
||||
|
||||
```bash
|
||||
## 导出 SQL
|
||||
|
||||
$ docker exec wordpress_db mysqldump -u wordpress -pwordpress wordpress > backup.sql
|
||||
```
|
||||
|
||||
或者添加一个自动备份容器:
|
||||
|
||||
```yaml
|
||||
backup:
|
||||
image: tiredofit/db-backup
|
||||
volumes:
|
||||
- ./backups:/backup
|
||||
environment:
|
||||
- DB_TYPE=mysql
|
||||
- DB_HOST=db
|
||||
- DB_NAME=wordpress
|
||||
- DB_USER=wordpress
|
||||
- DB_PASS=${DB_PASSWORD}
|
||||
- DB_DUMP_FREQ=1440 # 每天备份一次
|
||||
depends_on:
|
||||
- db
|
||||
networks:
|
||||
- wp_net
|
||||
```
|
||||
|
||||
#### 2. 使用 Nginx 反向代理
|
||||
|
||||
在生产环境中,不要直接暴露 WordPress 端口,而是通过 Nginx 进行反向代理并配置 SSL。
|
||||
|
||||
#### 3. 使用 Redis 缓存
|
||||
|
||||
WordPress 支持 Redis 缓存以提高性能。
|
||||
|
||||
```yaml
|
||||
redis:
|
||||
image: redis:alpine
|
||||
restart: always
|
||||
networks:
|
||||
- wp_net
|
||||
```
|
||||
|
||||
在 WordPress 容器环境变量中添加:
|
||||
```yaml
|
||||
WORDPRESS_REDIS_HOST: redis
|
||||
```
|
||||
并安装 Redis Object Cache 插件。
|
||||
|
||||
---
|
||||
|
||||
### 11.8.6 常见问题
|
||||
|
||||
#### Q:数据库连接错误
|
||||
|
||||
**现象**:访问页面显示 “Error establishing a database connection”。**排查**:
|
||||
|
||||
1. 检查 `docker compose logs wordpress`
|
||||
2. 确认 `.env` 中的密码与 YAML 文件引用一致
|
||||
3. 确认 `WORDPRESS_DB_HOST` 也是 `db` (服务名)
|
||||
4. MySQL 8.0 可能需要几秒钟启动,WordPress 会自动重试,稍等片刻即可。
|
||||
|
||||
#### Q:无法上传大文件
|
||||
|
||||
**解决**:确保挂载了 `uploads.ini` 配置,并且重启了容器:
|
||||
```bash
|
||||
$ docker compose restart wordpress
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 11.8.7 延伸阅读
|
||||
|
||||
- [Compose 模板文件](11.5_compose_file.md):深入了解配置项
|
||||
- [数据卷](../08_data/8.1_volume.md):理解数据持久化
|
||||
- [Docker Hub WordPress](https://hub.docker.com/_/wordpress):官方镜像文档
|
||||
@@ -1,15 +0,0 @@
|
||||
# 第十一章 Docker Compose
|
||||
|
||||
`Docker Compose` 是 Docker 官方编排 (Orchestration) 项目之一,负责快速的部署分布式应用。
|
||||
|
||||
本章将介绍 `Compose` 项目情况以及安装和使用。
|
||||
|
||||
* [简介](11.1_introduction.md)
|
||||
* [安装与卸载](11.2_install.md)
|
||||
* [使用](11.3_usage.md)
|
||||
* [命令说明](11.4_commands.md)
|
||||
* [Compose 模板文件](11.5_compose_file.md)
|
||||
* [实战 Django](11.6_django.md)
|
||||
* [实战 Rails](11.7_rails.md)
|
||||
* [实战 WordPress](11.8_wordpress.md)
|
||||
* [实战 LNMP](11.9_lnmp.md)
|
||||
@@ -1,21 +0,0 @@
|
||||
## 本章小结
|
||||
|
||||
Docker Compose 是管理多容器应用的利器,通过 YAML 文件声明式地定义服务、网络和数据卷。
|
||||
|
||||
| 概念 | 要点 |
|
||||
|------|------|
|
||||
| **核心概念** | 服务 (service) 和项目 (project) |
|
||||
| **配置文件** | `compose.yaml` (推荐) 或 `docker-compose.yml` |
|
||||
| **版本** | Compose V2 为 Go 编写的 CLI 插件,通过 `docker compose` 使用 |
|
||||
| **启动** | `docker compose up -d` 启动所有服务 |
|
||||
| **停止** | `docker compose down` 停止并移除容器 |
|
||||
| **查看状态** | `docker compose ps` 查看服务状态 |
|
||||
| **查看日志** | `docker compose logs` 查看服务日志 |
|
||||
| **模板文件** | 支持 `services`、`networks`、`volumes` 等顶级配置 |
|
||||
|
||||
### 11.10.1 延伸阅读
|
||||
|
||||
- [Compose 模板文件](11.5_compose_file.md):详细模板语法参考
|
||||
- [Compose 命令说明](11.4_commands.md):完整命令列表
|
||||
- [网络配置](../09_network/README.md):Docker 网络基础
|
||||
- [数据管理](../08_data/README.md):数据卷管理
|
||||
@@ -1,157 +0,0 @@
|
||||
## 12.1 基本架构
|
||||
|
||||
Docker 的架构设计简洁而高效,主要由客户端和服务端两部分组成。
|
||||
|
||||
### 12.1.1 核心架构图
|
||||
|
||||
Docker 采用了 **C/S (客户端/服务端)** 架构。Client 向 Daemon 发送请求,Daemon 负责构建、运行和分发容器。
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
Client[客户端 (Docker CLI)] -- docker run --> Dockerd
|
||||
Client -- docker pull --> Dockerd
|
||||
|
||||
subgraph "Docker Host"
|
||||
Dockerd(dockerd<br>守护进程)
|
||||
Containers(Containers<br>容器)
|
||||
Images(Images<br>镜像)
|
||||
|
||||
Dockerd -- 管理 --> Containers
|
||||
Dockerd -- 管理 --> Images
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 12.1.2 组件详解
|
||||
|
||||
Docker 的内部架构如同洋葱一样分层,每一层专注解决特定问题:
|
||||
|
||||
#### 1. Docker CLI (客户端)
|
||||
|
||||
用户与 Docker 交互的主要方式。它将用户命令 (如 `docker run`) 转换为 API 请求发送给 dockerd。
|
||||
|
||||
#### 2. Dockerd (守护进程)
|
||||
|
||||
Docker 的大脑。
|
||||
|
||||
- 监听 API 请求
|
||||
- 管理 Docker 对象 (镜像、容器、网络、卷)
|
||||
- 编排下层组件完成工作
|
||||
|
||||
#### 3. Containerd (高级运行时)
|
||||
|
||||
行业标准的容器运行时 (CNCF 毕业项目)。
|
||||
|
||||
- 管理容器的完整生命周期 (启动、停止)
|
||||
- 镜像拉取与存储
|
||||
- **不包含** 复杂的与容器无关的功能 (如构建、API)
|
||||
- Kubernetes 也可以直接使用 containerd (跳过 Docker)
|
||||
|
||||
#### 4. Runc (低级运行时)
|
||||
|
||||
用于创建和运行容器的 CLI 工具。
|
||||
|
||||
- 直接与内核交互 (Namespaces,Cgroups)
|
||||
- 遵循 OCI (Open Container Initiative) 规范
|
||||
- **主要职责**:根据配置启动一个容器,然后退出 (将控制权交给容器进程)
|
||||
|
||||
#### 5. Shim
|
||||
|
||||
每个容器都有一个 shim 进程。
|
||||
|
||||
- **解耦**:允许 dockerd 重启而不影响容器运行
|
||||
- **保持 IO**:维持容器的标准输入输出
|
||||
- **状态汇报**:向 containerd 汇报容器退出状态
|
||||
|
||||
---
|
||||
|
||||
### 12.1.3 容器启动流程
|
||||
|
||||
当执行 `docker run -d nginx` 时,内部发生了什么?
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
User((用户))
|
||||
|
||||
subgraph DockerCLI [Docker CLI]
|
||||
Cmd[docker run -d nginx]
|
||||
end
|
||||
|
||||
subgraph DockerHost [Docker Host]
|
||||
Dockerd[Dockerd]
|
||||
Containerd[Containerd]
|
||||
subgraph ContainerRuntime [Runtime]
|
||||
Shim[Containerd-shim]
|
||||
Runc[Runc]
|
||||
Container[容器进程 (nginx)]
|
||||
end
|
||||
end
|
||||
|
||||
User --> Cmd
|
||||
Cmd -- 1. REST API --> Dockerd
|
||||
Dockerd -- 2. gRPC --> Containerd
|
||||
Containerd -- 3. 准备镜像 & Bundle --> Containerd
|
||||
Containerd -- 4. Fork --> Shim
|
||||
Shim -- 5. Exec --> Runc
|
||||
Runc -- 6. Create Namespaces/Cgroups --> Container
|
||||
Runc -.-> |7. Exit| Runc
|
||||
Shim -.-> |8. Monitor IO/Exit| Container
|
||||
```
|
||||
|
||||
1. **CLI** 发送请求给 **Dockerd**
|
||||
2. **Dockerd** 解析请求,调用 **Containerd**
|
||||
3. **Containerd** 准备镜像,转换为 OCI Bundle
|
||||
4. **Containerd** 创建 **Shim** 进程
|
||||
5. **Shim** 调用 **Runc**
|
||||
6. **Runc** 与系统内核交互,创建 Namespaces 和 Cgroups
|
||||
7. **Runc** 启动 nginx 进程后退出
|
||||
8. **Shim** 接管容器 IO 和生命周期监控
|
||||
|
||||
---
|
||||
|
||||
### 12.1.4 Docker Engine v29+ 变化
|
||||
|
||||
从 Docker Engine v29 (2025/2026) 开始,架构进一步简化和标准化:
|
||||
|
||||
- **Containerd 镜像存储 (Image Store)**:默认启用。Docker 直接使用 Containerd 的镜像管理能力,不再维护自己的一套 graphdriver。
|
||||
- **优势**:多平台镜像支持更好、镜像拉取更快 (lazy pulling)、与 K8s 共享镜像。
|
||||
|
||||
---
|
||||
|
||||
### 12.1.5 Docker Desktop 架构
|
||||
|
||||
在 macOS 和 Windows 上,因为内核差异,架构稍微复杂:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph HostOS ["MacOS / Windows"]
|
||||
CLI["Docker CLI"]
|
||||
subgraph LinuxVM ["Linux VM (虚拟机)"]
|
||||
Engine["Dockerd <--> Containerd <--> Runc"]
|
||||
end
|
||||
CLI -- "(Socket 映射)" --> Engine
|
||||
end
|
||||
```
|
||||
|
||||
- 使用轻量级虚拟机 (Apple Virtualization / WSL 2) 运行 Linux 内核
|
||||
- 文件挂载 (Bind Mount) 需要跨越 VM 边界 (这也是文件 I/O 慢的原因)
|
||||
- 网络端口需要从宿主机转发到 VM
|
||||
|
||||
---
|
||||
|
||||
### 12.1.6 总结
|
||||
|
||||
| 组件 | 角色 | 关键职责 |
|
||||
|------|------|----------|
|
||||
| **CLI** | 指挥官 | 发送指令,展示结果 |
|
||||
| **Dockerd** | 大管家 | API 接口,整体调度 |
|
||||
| **Containerd** | 经理 | 容器生命周期,镜像管理 |
|
||||
| **Shim** | 监工 | 保持 IO,允许无守护进程重启 |
|
||||
| **Runc** | 工人 | 真正干活 (创建容器),干完就走 |
|
||||
|
||||
### 12.1.7 延伸阅读
|
||||
|
||||
- [命名空间](./12.2_namespace.md):Runc 如何隔离容器
|
||||
- [控制组](./12.3_cgroups.md):Runc 如何限制资源
|
||||
- [联合文件系统](./12.4_ufs.md):镜像如何存储
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user