mirror of
https://github.com/yeasy/docker_practice.git
synced 2026-08-10 16:37:34 +00:00
Compare commits
129
Commits
fab4e41587
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
00d8a87f55 | ||
|
|
c3e1e4a5a6 | ||
|
|
33bca896b5 | ||
|
|
141fdc80bc | ||
|
|
08e981018e | ||
|
|
42253fc1e8 | ||
|
|
b81816bb07 | ||
|
|
0b526c1674 | ||
|
|
90ea597376 | ||
|
|
21e43e1927 | ||
|
|
95ee0aeca5 | ||
|
|
657c0b25df | ||
|
|
7391bf95ef | ||
|
|
4175760d1b | ||
|
|
b7d4410cf0 | ||
|
|
f8691a8868 | ||
|
|
eb939f69e5 | ||
|
|
898fb10bbe | ||
|
|
1e56295251 | ||
|
|
52f6c73478 | ||
|
|
7c96d19fd4 | ||
|
|
d705dfb631 | ||
|
|
1c8f0dc14d | ||
|
|
2c8acc6c9c | ||
|
|
a1f63a565b | ||
|
|
a42f94faad | ||
|
|
38c89065b6 | ||
|
|
3781d9c382 | ||
|
|
f82e13017d | ||
|
|
69bf233381 | ||
|
|
fe46145e4a | ||
|
|
51698c89c5 | ||
|
|
7340cc16a8 | ||
|
|
288212bbe9 | ||
|
|
6ad32cc5a8 | ||
|
|
937ad3f1c3 | ||
|
|
2ef6d80d4f | ||
|
|
44822cea16 | ||
|
|
1f970681ee | ||
|
|
397c66673b | ||
|
|
8cd6094ae9 | ||
|
|
ee0fbd7d38 | ||
|
|
daa6661b2b | ||
|
|
8eabe30dc2 | ||
|
|
bd944a2383 | ||
|
|
47a6f56049 | ||
|
|
9058882b73 | ||
|
|
68fec82301 | ||
|
|
a601794cd4 | ||
|
|
2bcc83dcf4 | ||
|
|
9fdffa9d91 | ||
|
|
f4e684afeb | ||
|
|
01a6b2dccd | ||
|
|
0cfd55af7b | ||
|
|
eb5e4397e8 | ||
|
|
070d4d6d69 | ||
|
|
67d5fc775a | ||
|
|
130d03bf65 | ||
|
|
8dd6a556c8 | ||
|
|
3c1720ccc9 | ||
|
|
6fc032f7d6 | ||
|
|
1cdd3c582a | ||
|
|
094965e039 | ||
|
|
48e86b7bec | ||
|
|
9230b49b6b | ||
|
|
2185211041 | ||
|
|
f09e800020 | ||
|
|
3a9ee19d22 | ||
|
|
ea26f96297 | ||
|
|
30f26d9195 | ||
|
|
fb361bb1f0 | ||
|
|
036f0486db | ||
|
|
28c23d003e | ||
|
|
6a55219310 | ||
|
|
8f4d88e350 | ||
|
|
7f83abc53b | ||
|
|
ec0fa15835 | ||
|
|
8b9e4518c8 | ||
|
|
dae0af9ae7 | ||
|
|
10b09f35eb | ||
|
|
e17bef96d2 | ||
|
|
a3567ff6a0 | ||
|
|
9ef842ebb3 | ||
|
|
2e625a3cdf | ||
|
|
d47afa7e75 | ||
|
|
1b651e5f8c | ||
|
|
e91fe87822 | ||
|
|
bb97d5a46a | ||
|
|
1f69884c8f | ||
|
|
4f92b3aa70 | ||
|
|
58504e9316 | ||
|
|
4075330dba | ||
|
|
e6bf228066 | ||
|
|
1b15b65bc5 | ||
|
|
26da467052 | ||
|
|
7abaff237a | ||
|
|
1c4e0538d8 | ||
|
|
0b8f6e9b60 | ||
|
|
5650315cb4 | ||
|
|
e21794ebde | ||
|
|
78ca8f6d19 | ||
|
|
1ba904a9ff | ||
|
|
705d162f05 | ||
|
|
92be0050fd | ||
|
|
a20d1b19c4 | ||
|
|
9f481e88ca | ||
|
|
ef5a97fa09 | ||
|
|
3c5c5911b0 | ||
|
|
e2742313f2 | ||
|
|
2cea196860 | ||
|
|
13fc8b34f0 | ||
|
|
29742c8f74 | ||
|
|
7cae0b6bb3 | ||
|
|
10381deee4 | ||
|
|
625d209fa8 | ||
|
|
b148d9efa9 | ||
|
|
4cb91a75f3 | ||
|
|
bf3107b775 | ||
|
|
7e3f90b522 | ||
|
|
e6527ae769 | ||
|
|
9c378b1ef9 | ||
|
|
340c8c9e61 | ||
|
|
89c2690a62 | ||
|
|
c554799b08 | ||
|
|
99c56217f5 | ||
|
|
9c98e35c62 | ||
|
|
0e0afbd4d3 | ||
|
|
30f0115cb9 | ||
|
|
62f48c1417 |
@@ -11,12 +11,9 @@
|
||||
"forwardPorts": [
|
||||
4000
|
||||
],
|
||||
"runArgs": [
|
||||
"--cap-add=SYS_ADMIN"
|
||||
],
|
||||
"postStartCommand": [
|
||||
"sh",
|
||||
"-cx",
|
||||
"pwd ; cd /workspaces/docker_practice ; mkdir -p ${PWD}/node_modules; mkdir -p ${PWD}/_book; mount --bind /srv/gitbook/node_modules ${PWD}/node_modules ; mount --bind /mnt ${PWD}/_book"
|
||||
"pwd ; cd /workspaces/docker_practice ; mkdir -p ${PWD}/node_modules ${PWD}/_book"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
* @yeasy @khs1994
|
||||
/.github/* @khs1994
|
||||
/.travis/* @khs1994
|
||||
/.vuepress/* @khs1994
|
||||
/01_introduction/* @yeasy @khs1994
|
||||
/02_basic_concept/* @yeasy @khs1994
|
||||
/03_install/* @khs1994
|
||||
|
||||
@@ -6,46 +6,171 @@ on:
|
||||
- 'v*'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
release:
|
||||
build:
|
||||
permissions:
|
||||
contents: read
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install Chromium and CJK fonts
|
||||
- name: Install locked Node dependencies
|
||||
env:
|
||||
PUPPETEER_SKIP_DOWNLOAD: "true"
|
||||
run: npm ci
|
||||
|
||||
- name: Check project rules and metadata
|
||||
run: |
|
||||
python3 check_project_rules.py
|
||||
npm test
|
||||
|
||||
- name: Install Chromium, CJK fonts, and PDF inspection tools
|
||||
id: setupchrome
|
||||
uses: browser-actions/setup-chrome@2e1d749697dd1612b833dba4a722266286fbefcd # v2.1.2
|
||||
with:
|
||||
chrome-version: stable
|
||||
|
||||
- name: Install CJK fonts and PDF inspection tools
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y chromium-browser fonts-noto-cjk fonts-noto-cjk-extra
|
||||
sudo apt-get install -y fonts-noto-cjk fonts-noto-cjk-extra poppler-utils
|
||||
|
||||
- name: Install mdpress (latest)
|
||||
- name: Resolve latest mdPress release
|
||||
run: |
|
||||
LATEST_TAG=$(curl -fsSL -H "Accept: application/vnd.github+json" -H "Authorization: Bearer ${{ github.token }}" https://api.github.com/repos/yeasy/mdpress/releases/latest | jq -r .tag_name)
|
||||
VERSION="${LATEST_TAG#v}"
|
||||
echo "Installing mdpress $VERSION"
|
||||
curl -fsSL "https://github.com/yeasy/mdPress/releases/download/$LATEST_TAG/mdpress_${VERSION}_linux_amd64.tar.gz" -o /tmp/mdpress.tar.gz
|
||||
tar xzf /tmp/mdpress.tar.gz -C /tmp mdpress
|
||||
sudo mv /tmp/mdpress /usr/local/bin/
|
||||
mdpress --version
|
||||
url="$(curl -fsSL --retry 3 -o /dev/null -w '%{url_effective}' https://github.com/yeasy/mdPress/releases/latest)"
|
||||
version="${url##*/v}"
|
||||
test -n "$version" || { echo "could not resolve latest mdPress release"; exit 1; }
|
||||
echo "MDPRESS_VERSION=$version" >> "$GITHUB_ENV"
|
||||
echo "resolved mdPress $version"
|
||||
- name: Install mdPress (checksum-verified)
|
||||
run: |
|
||||
archive="$RUNNER_TEMP/mdpress_${MDPRESS_VERSION}_linux_amd64.tar.gz"
|
||||
curl -fsSL --retry 3 \
|
||||
"https://github.com/yeasy/mdPress/releases/download/v${MDPRESS_VERSION}/mdpress_${MDPRESS_VERSION}_linux_amd64.tar.gz" \
|
||||
-o "$archive"
|
||||
expected="$(curl -fsSL --retry 3 "https://github.com/yeasy/mdPress/releases/download/v${MDPRESS_VERSION}/checksums.txt" \
|
||||
| awk -v f="mdpress_${MDPRESS_VERSION}_linux_amd64.tar.gz" '$2==f {print $1}')"
|
||||
test -n "$expected" || { echo "no published checksum for mdpress_${MDPRESS_VERSION}_linux_amd64.tar.gz"; exit 1; }
|
||||
echo "${expected} $archive" | sha256sum -c -
|
||||
tar xzf "$archive" -C "$RUNNER_TEMP" mdpress
|
||||
mkdir -p "$RUNNER_TEMP/bin"
|
||||
install -m 0755 "$RUNNER_TEMP/mdpress" "$RUNNER_TEMP/bin/mdpress"
|
||||
echo "$RUNNER_TEMP/bin" >> "$GITHUB_PATH"
|
||||
|
||||
- name: Extract tag name
|
||||
- name: Install Pandoc 3.5
|
||||
env:
|
||||
PANDOC_VERSION: "3.5"
|
||||
PANDOC_SHA256: "4f41d817d262ef3d17953a3e6e0fefddb971aa4f2f121544a9a86db449d945e1"
|
||||
run: |
|
||||
package="$RUNNER_TEMP/pandoc-${PANDOC_VERSION}-1-amd64.deb"
|
||||
curl -fsSL --retry 3 \
|
||||
"https://github.com/jgm/pandoc/releases/download/${PANDOC_VERSION}/pandoc-${PANDOC_VERSION}-1-amd64.deb" \
|
||||
-o "$package"
|
||||
echo "${PANDOC_SHA256} $package" | sha256sum -c -
|
||||
sudo dpkg -i "$package"
|
||||
pandoc --version | head -1
|
||||
|
||||
- name: Extract safe tag name
|
||||
id: tag
|
||||
run: echo "TAG_NAME=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
run: |
|
||||
if [[ "$GITHUB_REF" == refs/tags/* ]]; then
|
||||
tag_name="${GITHUB_REF#refs/tags/}"
|
||||
else
|
||||
tag_name="latest"
|
||||
fi
|
||||
safe_tag_name=$(printf '%s' "$tag_name" | sed 's/[^A-Za-z0-9._-]/-/g')
|
||||
safe_tag_name="${safe_tag_name:-latest}"
|
||||
echo "TAG_NAME=${tag_name}" >> "$GITHUB_OUTPUT"
|
||||
echo "SAFE_TAG_NAME=${safe_tag_name}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Prepare PDF sources
|
||||
run: python3 tools/prepare_pdf_sources.py --book-dir . --out "$RUNNER_TEMP/docker_practice-pdf-src"
|
||||
|
||||
- name: Build PDF
|
||||
run: mdpress build --format pdf --output docker_practice-${{ steps.tag.outputs.TAG_NAME || 'latest' }}.pdf
|
||||
env:
|
||||
SAFE_TAG_NAME: ${{ steps.tag.outputs.SAFE_TAG_NAME }}
|
||||
working-directory: ${{ runner.temp }}/docker_practice-pdf-src
|
||||
run: |
|
||||
# mdPress PDF generation drives headless Chrome, which intermittently dies with
|
||||
# "websocket url timeout reached" + dbus errors. Retry the build, not the whole job.
|
||||
mdpress() { local a; for a in 1 2 3; do command mdpress "$@" && return 0;
|
||||
echo "::warning::mdpress attempt $a failed; retrying in 10s"; sleep 10; done; return 1; }
|
||||
mkdir -p "$GITHUB_WORKSPACE/dist"
|
||||
mdpress build --format pdf \
|
||||
--output "$GITHUB_WORKSPACE/dist/docker_practice-${SAFE_TAG_NAME}.pdf"
|
||||
|
||||
- name: Create Release with PDF
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
uses: softprops/action-gh-release@v3
|
||||
- name: Build HTML reader
|
||||
env:
|
||||
SAFE_TAG_NAME: ${{ steps.tag.outputs.SAFE_TAG_NAME }}
|
||||
run: |
|
||||
title=$(python3 -c 'import json; print(json.load(open("book.json", encoding="utf-8"))["title"])')
|
||||
PATH="$GITHUB_WORKSPACE/node_modules/.bin:$PATH" \
|
||||
python3 tools/render_mermaid.py --book-dir . --svg-out "$RUNNER_TEMP/mmsvg" --strict
|
||||
python3 tools/build_html_reader.py \
|
||||
--book-dir . \
|
||||
--title "$title" \
|
||||
--svg-dir "$RUNNER_TEMP/mmsvg" \
|
||||
--out "dist/docker_practice-${SAFE_TAG_NAME}.html"
|
||||
|
||||
- name: Verify release artifacts
|
||||
env:
|
||||
SAFE_TAG_NAME: ${{ steps.tag.outputs.SAFE_TAG_NAME }}
|
||||
run: |
|
||||
title=$(python3 -c 'import json; print(json.load(open("book.json", encoding="utf-8"))["title"])')
|
||||
python3 tools/verify_artifacts.py \
|
||||
--title "$title" \
|
||||
--pdf "dist/docker_practice-${SAFE_TAG_NAME}.pdf" \
|
||||
--html "dist/docker_practice-${SAFE_TAG_NAME}.html" \
|
||||
--checksums dist/SHA256SUMS
|
||||
(cd dist && sha256sum -c SHA256SUMS)
|
||||
|
||||
- name: Upload verified release bundle
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: docker-practice-release-bundle
|
||||
path: dist/
|
||||
if-no-files-found: error
|
||||
|
||||
release:
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
attestations: write
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: Download verified release bundle
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: docker-practice-release-bundle
|
||||
path: dist
|
||||
|
||||
- name: Recheck artifact checksums
|
||||
run: (cd dist && sha256sum -c SHA256SUMS)
|
||||
|
||||
- name: Attest tagged release artifacts
|
||||
uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
|
||||
with:
|
||||
subject-path: |
|
||||
dist/docker_practice-*.pdf
|
||||
dist/docker_practice-*.html
|
||||
dist/SHA256SUMS
|
||||
|
||||
- name: Create GitHub release
|
||||
uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2
|
||||
with:
|
||||
generate_release_notes: true
|
||||
files: docker_practice-${{ steps.tag.outputs.TAG_NAME }}.pdf
|
||||
|
||||
- name: Upload PDF as artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: docker_practice-pdf
|
||||
path: "docker_practice-*.pdf"
|
||||
fail_on_unmatched_files: true
|
||||
files: |
|
||||
dist/docker_practice-*.pdf
|
||||
dist/docker_practice-*.html
|
||||
dist/SHA256SUMS
|
||||
|
||||
@@ -3,63 +3,69 @@ name: Check link
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
check-link:
|
||||
name: check-link
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
# search Issues :-(
|
||||
- run: |
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Check Markdown links
|
||||
run: |
|
||||
docker run -i --rm \
|
||||
-v $PWD:/mnt:ro \
|
||||
dkhamsing/awesome_bot \
|
||||
--white-list "localhost","0.0.0.0",\
|
||||
"server","example.com","docker",\
|
||||
"docker.domain.com","YourIP","register",\
|
||||
"172.16.238.100","172.16.238.101","172.16.238.102",\
|
||||
"192.168.199.100",\
|
||||
"github.com/settings",\
|
||||
"github.com/docker/compose/releases/download",\
|
||||
"github.com/etcd-io/etcd/releases/download",\
|
||||
"github.com/tianon/gosu/releases/download",\
|
||||
"github.com/yeasy/docker_practice",\
|
||||
"github.com/AliyunContainerService/k8s-for-docker-desktop/raw",\
|
||||
"dl-cdn.alpinelinux.org/alpine/edge/testing",\
|
||||
"www.w3.org/1999/xhtml",\
|
||||
"cr.console.aliyun.com",\
|
||||
"cloud.tencent.com",\
|
||||
"nodejs.org/dist/",\
|
||||
"c.163.com/hub",\
|
||||
"drone.yeasy.com",\
|
||||
"docs.docker.com",\
|
||||
"dockerhub.azk8s.cn",\
|
||||
"reg-mirror.qiniu.com",\
|
||||
"registry.docker-cn.com",\
|
||||
"mirror.ccs.tencentyun.com",\
|
||||
"vuepress.mirror.docker-practice.com",\
|
||||
"mc.qcloudimg.com/static/img",\
|
||||
"www.daocloud.io/mirror",\
|
||||
"download.docker.com",\
|
||||
"www.ubuntu.com",\
|
||||
"archive.ubuntu.com",\
|
||||
"security.ubuntu.com/ubuntu",\
|
||||
"nginx.com",\
|
||||
"img.shields.io/github/release/yeasy/docker_practice",\
|
||||
"launchpad.net",\
|
||||
"www.w3.org/1999",\
|
||||
"chat.freenode.net",\
|
||||
"en.wikipedia.org/wiki/UnionFS",\
|
||||
"product.china-pub.com",\
|
||||
"union-click.jd.com",\
|
||||
"x.x.x.x/base",\
|
||||
"x.x.x.x:9090",\
|
||||
"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 {} \;`
|
||||
name: check-link
|
||||
-v "$GITHUB_WORKSPACE:/mnt:ro" \
|
||||
-w /mnt \
|
||||
dkhamsing/awesome_bot@sha256:a8adaeb3b3bd5745304743e4d8a6d512127646e420544a6d22d9f58a07f35884 \
|
||||
--white-list "localhost","0.0.0.0",\
|
||||
"server","example.com","docker",\
|
||||
"docker.domain.com","YourIP","register",\
|
||||
"172.16.238.100","172.16.238.101","172.16.238.102",\
|
||||
"192.168.199.100",\
|
||||
"github.com/settings",\
|
||||
"github.com/docker/compose/releases/download",\
|
||||
"github.com/etcd-io/etcd/releases/download",\
|
||||
"github.com/tianon/gosu/releases/download",\
|
||||
"github.com/yeasy/docker_practice",\
|
||||
"github.com/AliyunContainerService/k8s-for-docker-desktop/raw",\
|
||||
"dl-cdn.alpinelinux.org/alpine/edge/testing",\
|
||||
"www.w3.org/1999/xhtml",\
|
||||
"cr.console.aliyun.com",\
|
||||
"cloud.tencent.com",\
|
||||
"nodejs.org/dist/",\
|
||||
"c.163.com/hub",\
|
||||
"drone.yeasy.com",\
|
||||
"docs.docker.com",\
|
||||
"dockerhub.azk8s.cn",\
|
||||
"reg-mirror.qiniu.com",\
|
||||
"registry.docker-cn.com",\
|
||||
"mirror.ccs.tencentyun.com",\
|
||||
"mc.qcloudimg.com/static/img",\
|
||||
"www.daocloud.io/mirror",\
|
||||
"download.docker.com",\
|
||||
"www.ubuntu.com",\
|
||||
"archive.ubuntu.com",\
|
||||
"security.ubuntu.com/ubuntu",\
|
||||
"nginx.com",\
|
||||
"img.shields.io/github/release/yeasy/docker_practice",\
|
||||
"launchpad.net",\
|
||||
"www.w3.org/1999",\
|
||||
"chat.freenode.net",\
|
||||
"en.wikipedia.org/wiki/UnionFS",\
|
||||
"product.china-pub.com",\
|
||||
"union-click.jd.com",\
|
||||
"x.x.x.x/base",\
|
||||
"x.x.x.x:9090",\
|
||||
"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" -print)
|
||||
timeout-minutes: 25
|
||||
|
||||
+104
-20
@@ -7,34 +7,118 @@ on:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
permissions:
|
||||
contents: read
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Chromium and CJK fonts
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install locked Node dependencies
|
||||
env:
|
||||
PUPPETEER_SKIP_DOWNLOAD: "true"
|
||||
run: npm ci
|
||||
|
||||
- name: Install canonical-example validators
|
||||
env:
|
||||
KUBECONFORM_VERSION: "0.8.0"
|
||||
KUBECONFORM_SHA256: "9bc2bffbf71f261128533edaf912153948b7ff238f9a531ae6d34466ec287883"
|
||||
ACTIONLINT_VERSION: "1.7.12"
|
||||
ACTIONLINT_SHA256: "8aca8db96f1b94770f1b0d72b6dddcb1ebb8123cb3712530b08cc387b349a3d8"
|
||||
run: |
|
||||
mkdir -p "$RUNNER_TEMP/bin"
|
||||
kube_archive="$RUNNER_TEMP/kubeconform.tar.gz"
|
||||
curl -fsSL --retry 3 \
|
||||
"https://github.com/yannh/kubeconform/releases/download/v${KUBECONFORM_VERSION}/kubeconform-linux-amd64.tar.gz" \
|
||||
-o "$kube_archive"
|
||||
echo "${KUBECONFORM_SHA256} $kube_archive" | sha256sum -c -
|
||||
tar xzf "$kube_archive" -C "$RUNNER_TEMP/bin" kubeconform
|
||||
|
||||
actionlint_archive="$RUNNER_TEMP/actionlint.tar.gz"
|
||||
curl -fsSL --retry 3 \
|
||||
"https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz" \
|
||||
-o "$actionlint_archive"
|
||||
echo "${ACTIONLINT_SHA256} $actionlint_archive" | sha256sum -c -
|
||||
tar xzf "$actionlint_archive" -C "$RUNNER_TEMP/bin" actionlint
|
||||
echo "$RUNNER_TEMP/bin" >> "$GITHUB_PATH"
|
||||
|
||||
- name: Check project rules, metadata, and canonical examples
|
||||
run: |
|
||||
python3 check_project_rules.py
|
||||
python3 check_emphasis.py
|
||||
npm test
|
||||
python3 tools/test_examples.py --require-tools
|
||||
|
||||
- name: Install Chromium
|
||||
uses: browser-actions/setup-chrome@2e1d749697dd1612b833dba4a722266286fbefcd # v2.1.2
|
||||
with:
|
||||
chrome-version: stable
|
||||
|
||||
- name: Install CJK fonts and PDF inspection tools
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y chromium-browser fonts-noto-cjk fonts-noto-cjk-extra
|
||||
- name: Install mdpress (latest)
|
||||
sudo apt-get install -y fonts-noto-cjk fonts-noto-cjk-extra poppler-utils
|
||||
|
||||
- name: Resolve latest mdPress release
|
||||
run: |
|
||||
LATEST_TAG=$(curl -fsSL -H "Accept: application/vnd.github+json" -H "Authorization: Bearer ${{ github.token }}" https://api.github.com/repos/yeasy/mdpress/releases/latest | jq -r .tag_name)
|
||||
VERSION="${LATEST_TAG#v}"
|
||||
echo "Installing mdpress $VERSION"
|
||||
curl -fsSL "https://github.com/yeasy/mdPress/releases/download/$LATEST_TAG/mdpress_${VERSION}_linux_amd64.tar.gz" -o mdpress.tar.gz
|
||||
tar xzf mdpress.tar.gz
|
||||
sudo mv mdpress /usr/local/bin/
|
||||
mdpress --version
|
||||
- name: Build site
|
||||
run: mdpress build --format site
|
||||
url="$(curl -fsSL --retry 3 -o /dev/null -w '%{url_effective}' https://github.com/yeasy/mdPress/releases/latest)"
|
||||
version="${url##*/v}"
|
||||
test -n "$version" || { echo "could not resolve latest mdPress release"; exit 1; }
|
||||
echo "MDPRESS_VERSION=$version" >> "$GITHUB_ENV"
|
||||
echo "resolved mdPress $version"
|
||||
- name: Install mdPress (checksum-verified)
|
||||
run: |
|
||||
archive="$RUNNER_TEMP/mdpress_${MDPRESS_VERSION}_linux_amd64.tar.gz"
|
||||
curl -fsSL --retry 3 \
|
||||
"https://github.com/yeasy/mdPress/releases/download/v${MDPRESS_VERSION}/mdpress_${MDPRESS_VERSION}_linux_amd64.tar.gz" \
|
||||
-o "$archive"
|
||||
expected="$(curl -fsSL --retry 3 "https://github.com/yeasy/mdPress/releases/download/v${MDPRESS_VERSION}/checksums.txt" \
|
||||
| awk -v f="mdpress_${MDPRESS_VERSION}_linux_amd64.tar.gz" '$2==f {print $1}')"
|
||||
test -n "$expected" || { echo "no published checksum for mdpress_${MDPRESS_VERSION}_linux_amd64.tar.gz"; exit 1; }
|
||||
echo "${expected} $archive" | sha256sum -c -
|
||||
tar xzf "$archive" -C "$RUNNER_TEMP" mdpress
|
||||
mkdir -p "$RUNNER_TEMP/bin"
|
||||
install -m 0755 "$RUNNER_TEMP/mdpress" "$RUNNER_TEMP/bin/mdpress"
|
||||
echo "$RUNNER_TEMP/bin" >> "$GITHUB_PATH"
|
||||
|
||||
- name: Prepare PDF sources
|
||||
run: python3 tools/prepare_pdf_sources.py --book-dir . --out "$RUNNER_TEMP/docker_practice-pdf-src"
|
||||
|
||||
- name: Build PDF
|
||||
run: mdpress build --format pdf --output docker_practice.pdf
|
||||
- name: Upload PDF as artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
working-directory: ${{ runner.temp }}/docker_practice-pdf-src
|
||||
run: |
|
||||
# mdPress PDF generation drives headless Chrome, which intermittently dies with
|
||||
# "websocket url timeout reached" + dbus errors. Retry the build, not the whole job.
|
||||
mdpress() { local a; for a in 1 2 3; do command mdpress "$@" && return 0;
|
||||
echo "::warning::mdpress attempt $a failed; retrying in 10s"; sleep 10; done; return 1; }
|
||||
mkdir -p "$GITHUB_WORKSPACE/dist"
|
||||
mdpress build --format pdf --output "$GITHUB_WORKSPACE/dist/docker_practice.pdf"
|
||||
|
||||
- name: Build site
|
||||
run: npm run build
|
||||
|
||||
- name: Verify build artifacts
|
||||
run: |
|
||||
title=$(python3 -c 'import json; print(json.load(open("book.json", encoding="utf-8"))["title"])')
|
||||
python3 tools/verify_artifacts.py \
|
||||
--title "$title" \
|
||||
--pdf dist/docker_practice.pdf \
|
||||
--site _site \
|
||||
--checksums dist/SHA256SUMS
|
||||
(cd dist && sha256sum -c SHA256SUMS)
|
||||
|
||||
- name: Upload verified PDF
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: docker_practice-pdf
|
||||
path: docker_practice.pdf
|
||||
path: |
|
||||
dist/docker_practice.pdf
|
||||
dist/SHA256SUMS
|
||||
if-no-files-found: error
|
||||
|
||||
@@ -1,26 +1,28 @@
|
||||
name: Dependabot auto-merge
|
||||
on: pull_request
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
dependabot:
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
checks: read
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
if: github.actor == 'dependabot[bot]'
|
||||
steps:
|
||||
- name: Dependabot metadata
|
||||
id: metadata
|
||||
uses: dependabot/fetch-metadata@v3
|
||||
uses: dependabot/fetch-metadata@25dd0e34f4fe68f24cc83900b1fe3fe149efef98 # v3.1.0
|
||||
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
|
||||
|
||||
- name: Enable auto-merge for low-risk Dependabot PRs
|
||||
if: >
|
||||
steps.metadata.outputs.package-ecosystem == 'github_actions' &&
|
||||
contains(fromJSON('["version-update:semver-patch","version-update:semver-minor"]'), steps.metadata.outputs.update-type)
|
||||
run: gh pr merge --auto --merge "$PR_URL"
|
||||
env:
|
||||
PR_URL: ${{github.event.pull_request.html_url}}
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
name: Identity Guard
|
||||
|
||||
# 拒绝把 AI 助手写成提交作者/提交者,或写进 Co-authored-by 尾注。
|
||||
# 本地 .git/hooks 不随仓库分发,云端会话推上来的提交不受其保护,
|
||||
# 因此这道检查必须放在服务端。
|
||||
|
||||
on:
|
||||
push:
|
||||
# 'branches: [**]' 覆盖所有分支且天然排除 tag。
|
||||
# 注意不能写成只有 tags-ignore:GitHub 的规则是"只给了 tag 过滤器就只跑 tag",
|
||||
# 于是 tags-ignore: ['**'] 等于"只跑 tag,但忽略所有 tag" —— 工作流永不触发。
|
||||
branches: ['**']
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
check-commit-identity:
|
||||
permissions:
|
||||
contents: read
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- name: Reject AI assistant identities and co-author trailers
|
||||
env:
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
BEFORE_SHA: ${{ github.event.before }}
|
||||
HEAD_SHA: ${{ github.sha }}
|
||||
PR_BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
||||
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
ZERO=0000000000000000000000000000000000000000
|
||||
US=$'\037' # 用不可能出现在姓名/邮箱里的 unit separator 作分隔符
|
||||
|
||||
# 确定扫描范围。三种情况:
|
||||
# 1. pull_request:base..head,只看本 PR 新增的提交
|
||||
# 2. push 且 before 指向一个真实存在的提交:before..head
|
||||
# 3. 其余(新建分支 before 全 0、force-push 后 before 不可达、
|
||||
# workflow_dispatch):退化为全量审计,扫描 HEAD 的完整历史
|
||||
if [ "$EVENT_NAME" = "pull_request" ]; then
|
||||
RANGE="${PR_BASE_SHA}..${PR_HEAD_SHA}"
|
||||
elif [ "$EVENT_NAME" = "push" ] \
|
||||
&& [ -n "${BEFORE_SHA:-}" ] \
|
||||
&& [ "$BEFORE_SHA" != "$ZERO" ] \
|
||||
&& git cat-file -e "${BEFORE_SHA}^{commit}" 2>/dev/null; then
|
||||
RANGE="${BEFORE_SHA}..${HEAD_SHA}"
|
||||
else
|
||||
RANGE="$HEAD_SHA"
|
||||
fi
|
||||
echo "Scanning range: ${RANGE}"
|
||||
|
||||
# 只有"确实没有 head 提交"(例如删除分支的 push)才允许放行;
|
||||
# 其余任何解析不出范围的情况一律 fail closed,不能静默通过。
|
||||
if [ -z "${HEAD_SHA:-}" ] || [ "$HEAD_SHA" = "$ZERO" ]; then
|
||||
echo "No head commit (branch deletion?); nothing to check."
|
||||
exit 0
|
||||
fi
|
||||
if ! COMMITS="$(git rev-list "$RANGE" 2>&1)"; then
|
||||
echo "::error::cannot resolve commit range ${RANGE}: ${COMMITS}"
|
||||
exit 1
|
||||
fi
|
||||
COUNT="$(printf '%s' "$COMMITS" | grep -c . || true)"
|
||||
echo "Commits in range: ${COUNT}"
|
||||
if [ "$COUNT" -eq 0 ]; then
|
||||
echo "No commits to check."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 身份判定刻意收窄,避免误伤:
|
||||
# - 邮箱在 AI 厂商域名下(含子域)
|
||||
# - 姓名恰好等于助手名,因此真人 "Claude Dubois" 不受影响
|
||||
# 正文里出现 Claude/Anthropic 是完全合法的(本仓库群里有专讲 Claude
|
||||
# 的书),所以只检查身份字段与 Co-authored-by 尾注,绝不扫描自由文本。
|
||||
BAD_EMAIL='@([a-z0-9.-]+\.)?(anthropic|openai)\.com$'
|
||||
BAD_NAME='^(claude([ ._-]?code)?|codex|chatgpt|copilot|anthropic|openai)(\[bot\])?$'
|
||||
BAD_TRAILER='^[[:space:]]*co-authored-by:.*(claude|anthropic|codex|copilot|openai|chatgpt)'
|
||||
# 粗筛:BAD_EMAIL 与 BAD_NAME 命中的行必然含下列词之一,
|
||||
# 因此它是二者的超集,用来跳过绝大多数干净提交,不会漏判。
|
||||
BAD_ANY='(claude|anthropic|codex|chatgpt|copilot|openai)'
|
||||
|
||||
failed=0
|
||||
|
||||
# 身份检查。命令替换先落地,set -e 才能捕获 git 失败;
|
||||
# while 循环用 here-string 喂数据,避免管道子 shell 吞掉 failed 赋值。
|
||||
IDENTS="$(git log --format="%H${US}%an${US}%ae${US}%cn${US}%ce" "$RANGE")"
|
||||
# 用 here-string 而不是管道:set -o pipefail 下 grep -q 命中即退出会让
|
||||
# 左侧进程收到 SIGPIPE,整条管道返回非 0,if 就会跳过检查——
|
||||
# 而且只在输入大到写不进管道缓冲区时才发生,是典型的隐性漏判。
|
||||
if grep -qiE "$BAD_ANY" <<< "$IDENTS"; then
|
||||
while IFS="$US" read -r sha an ae cn ce; do
|
||||
[ -n "$sha" ] || continue
|
||||
for role in author committer; do
|
||||
if [ "$role" = author ]; then name="$an"; mail="$ae"; else name="$cn"; mail="$ce"; fi
|
||||
lname="$(printf '%s' "$name" | tr '[:upper:]' '[:lower:]')"
|
||||
lmail="$(printf '%s' "$mail" | tr '[:upper:]' '[:lower:]')"
|
||||
if grep -qE "$BAD_EMAIL" <<<"$lmail" \
|
||||
|| grep -qE "$BAD_NAME" <<<"$lname"; then
|
||||
echo "::error::${sha} ${role} identity is an AI assistant: ${name} <${mail}>"
|
||||
failed=1
|
||||
fi
|
||||
done
|
||||
done <<< "$IDENTS"
|
||||
fi
|
||||
|
||||
# Co-authored-by 尾注检查(行首锚定并要求冒号,避免匹配正文叙述)。
|
||||
# 先用一次 git log 流式扫全部正文;只有确实命中时才逐个提交定位,
|
||||
# 否则全量审计要为每个提交起一个 git 子进程(1500+ 提交约 50 秒)。
|
||||
BODIES="$(git log --format='%B' "$RANGE")"
|
||||
if grep -qiE "$BAD_TRAILER" <<< "$BODIES"; then
|
||||
while read -r sha; do
|
||||
[ -n "$sha" ] || continue
|
||||
body="$(git log -1 --format='%B' "$sha")"
|
||||
if grep -qiE "$BAD_TRAILER" <<<"$body"; then
|
||||
echo "::error::${sha} has a Co-authored-by trailer referencing an AI assistant"
|
||||
printf '%s' "$body" | grep -iE "$BAD_TRAILER" | sed 's/^/ /'
|
||||
failed=1
|
||||
fi
|
||||
done <<< "$COMMITS"
|
||||
fi
|
||||
|
||||
if [ "$failed" -ne 0 ]; then
|
||||
echo ""
|
||||
echo "AI assistant attribution found in the commits above."
|
||||
echo "Rewrite them before pushing, e.g.:"
|
||||
echo " git rebase -i --exec 'git commit --amend --no-edit --reset-author' <base>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "OK: no AI assistant identity or Co-authored-by trailer in ${COUNT} commit(s)."
|
||||
@@ -0,0 +1,90 @@
|
||||
name: Pages
|
||||
|
||||
# One-time repository setup remains manual: Settings > Pages > Source -> GitHub Actions.
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
workflow_dispatch:
|
||||
|
||||
permissions: {}
|
||||
|
||||
concurrency:
|
||||
group: pages
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
build:
|
||||
permissions:
|
||||
contents: read
|
||||
pages: read
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install locked Node dependencies
|
||||
env:
|
||||
PUPPETEER_SKIP_DOWNLOAD: "true"
|
||||
run: npm ci
|
||||
|
||||
- name: Check project rules and metadata
|
||||
run: |
|
||||
python3 check_project_rules.py
|
||||
npm test
|
||||
|
||||
- name: Resolve latest mdPress release
|
||||
run: |
|
||||
url="$(curl -fsSL --retry 3 -o /dev/null -w '%{url_effective}' https://github.com/yeasy/mdPress/releases/latest)"
|
||||
version="${url##*/v}"
|
||||
test -n "$version" || { echo "could not resolve latest mdPress release"; exit 1; }
|
||||
echo "MDPRESS_VERSION=$version" >> "$GITHUB_ENV"
|
||||
echo "resolved mdPress $version"
|
||||
- name: Install mdPress (checksum-verified)
|
||||
run: |
|
||||
archive="$RUNNER_TEMP/mdpress_${MDPRESS_VERSION}_linux_amd64.tar.gz"
|
||||
curl -fsSL --retry 3 \
|
||||
"https://github.com/yeasy/mdPress/releases/download/v${MDPRESS_VERSION}/mdpress_${MDPRESS_VERSION}_linux_amd64.tar.gz" \
|
||||
-o "$archive"
|
||||
expected="$(curl -fsSL --retry 3 "https://github.com/yeasy/mdPress/releases/download/v${MDPRESS_VERSION}/checksums.txt" \
|
||||
| awk -v f="mdpress_${MDPRESS_VERSION}_linux_amd64.tar.gz" '$2==f {print $1}')"
|
||||
test -n "$expected" || { echo "no published checksum for mdpress_${MDPRESS_VERSION}_linux_amd64.tar.gz"; exit 1; }
|
||||
echo "${expected} $archive" | sha256sum -c -
|
||||
tar xzf "$archive" -C "$RUNNER_TEMP" mdpress
|
||||
mkdir -p "$RUNNER_TEMP/bin"
|
||||
install -m 0755 "$RUNNER_TEMP/mdpress" "$RUNNER_TEMP/bin/mdpress"
|
||||
echo "$RUNNER_TEMP/bin" >> "$GITHUB_PATH"
|
||||
|
||||
- name: Build mdPress site
|
||||
run: npm run build
|
||||
|
||||
- name: Verify site output
|
||||
run: |
|
||||
title=$(python3 -c 'import json; print(json.load(open("book.json", encoding="utf-8"))["title"])')
|
||||
python3 tools/verify_artifacts.py --title "$title" --site _site
|
||||
|
||||
- name: Configure Pages
|
||||
uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0
|
||||
|
||||
- name: Upload Pages artifact
|
||||
uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
|
||||
with:
|
||||
path: _site
|
||||
|
||||
deploy:
|
||||
needs: build
|
||||
permissions:
|
||||
pages: write
|
||||
id-token: write
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: Deploy Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0
|
||||
@@ -6,41 +6,113 @@ on:
|
||||
- master
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
permissions: {}
|
||||
|
||||
concurrency:
|
||||
group: preview-pdf
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
update-preview-pdf:
|
||||
name: Update preview release
|
||||
build:
|
||||
permissions:
|
||||
contents: read
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install Chromium and CJK fonts
|
||||
- name: Install locked Node dependencies
|
||||
env:
|
||||
PUPPETEER_SKIP_DOWNLOAD: "true"
|
||||
run: npm ci
|
||||
|
||||
- name: Check project rules and metadata
|
||||
run: |
|
||||
python3 check_project_rules.py
|
||||
npm test
|
||||
|
||||
- name: Install Chromium
|
||||
uses: browser-actions/setup-chrome@2e1d749697dd1612b833dba4a722266286fbefcd # v2.1.2
|
||||
with:
|
||||
chrome-version: stable
|
||||
|
||||
- name: Install CJK fonts and PDF inspection tools
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y chromium-browser fonts-noto-cjk fonts-noto-cjk-extra
|
||||
sudo apt-get install -y fonts-noto-cjk fonts-noto-cjk-extra poppler-utils
|
||||
|
||||
- name: Install mdpress (latest)
|
||||
- name: Resolve latest mdPress release
|
||||
run: |
|
||||
LATEST_TAG=$(curl -fsSL -H "Accept: application/vnd.github+json" -H "Authorization: Bearer ${{ github.token }}" https://api.github.com/repos/yeasy/mdpress/releases/latest | jq -r .tag_name)
|
||||
VERSION="${LATEST_TAG#v}"
|
||||
echo "Installing mdpress $VERSION"
|
||||
curl -fsSL "https://github.com/yeasy/mdPress/releases/download/$LATEST_TAG/mdpress_${VERSION}_linux_amd64.tar.gz" -o /tmp/mdpress.tar.gz
|
||||
tar xzf /tmp/mdpress.tar.gz -C /tmp mdpress
|
||||
sudo mv /tmp/mdpress /usr/local/bin/
|
||||
mdpress --version
|
||||
url="$(curl -fsSL --retry 3 -o /dev/null -w '%{url_effective}' https://github.com/yeasy/mdPress/releases/latest)"
|
||||
version="${url##*/v}"
|
||||
test -n "$version" || { echo "could not resolve latest mdPress release"; exit 1; }
|
||||
echo "MDPRESS_VERSION=$version" >> "$GITHUB_ENV"
|
||||
echo "resolved mdPress $version"
|
||||
- name: Install mdPress (checksum-verified)
|
||||
run: |
|
||||
archive="$RUNNER_TEMP/mdpress_${MDPRESS_VERSION}_linux_amd64.tar.gz"
|
||||
curl -fsSL --retry 3 \
|
||||
"https://github.com/yeasy/mdPress/releases/download/v${MDPRESS_VERSION}/mdpress_${MDPRESS_VERSION}_linux_amd64.tar.gz" \
|
||||
-o "$archive"
|
||||
expected="$(curl -fsSL --retry 3 "https://github.com/yeasy/mdPress/releases/download/v${MDPRESS_VERSION}/checksums.txt" \
|
||||
| awk -v f="mdpress_${MDPRESS_VERSION}_linux_amd64.tar.gz" '$2==f {print $1}')"
|
||||
test -n "$expected" || { echo "no published checksum for mdpress_${MDPRESS_VERSION}_linux_amd64.tar.gz"; exit 1; }
|
||||
echo "${expected} $archive" | sha256sum -c -
|
||||
tar xzf "$archive" -C "$RUNNER_TEMP" mdpress
|
||||
mkdir -p "$RUNNER_TEMP/bin"
|
||||
install -m 0755 "$RUNNER_TEMP/mdpress" "$RUNNER_TEMP/bin/mdpress"
|
||||
echo "$RUNNER_TEMP/bin" >> "$GITHUB_PATH"
|
||||
|
||||
- name: Prepare PDF sources
|
||||
run: python3 tools/prepare_pdf_sources.py --book-dir . --out "$RUNNER_TEMP/docker_practice-pdf-src"
|
||||
|
||||
- name: Build latest preview PDF
|
||||
working-directory: ${{ runner.temp }}/docker_practice-pdf-src
|
||||
run: |
|
||||
mkdir -p dist
|
||||
mdpress build --format pdf --output dist/docker_practice.pdf
|
||||
# mdPress PDF generation drives headless Chrome, which intermittently dies with
|
||||
# "websocket url timeout reached" + dbus errors. Retry the build, not the whole job.
|
||||
mdpress() { local a; for a in 1 2 3; do command mdpress "$@" && return 0;
|
||||
echo "::warning::mdpress attempt $a failed; retrying in 10s"; sleep 10; done; return 1; }
|
||||
mkdir -p "$GITHUB_WORKSPACE/dist"
|
||||
mdpress build --format pdf --output "$GITHUB_WORKSPACE/dist/docker_practice.pdf"
|
||||
|
||||
- name: Verify preview PDF
|
||||
run: |
|
||||
title=$(python3 -c 'import json; print(json.load(open("book.json", encoding="utf-8"))["title"])')
|
||||
python3 tools/verify_artifacts.py \
|
||||
--title "$title" \
|
||||
--pdf dist/docker_practice.pdf \
|
||||
--checksums dist/SHA256SUMS
|
||||
(cd dist && sha256sum -c SHA256SUMS)
|
||||
|
||||
- name: Upload verified preview bundle
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: docker-practice-preview
|
||||
path: dist/
|
||||
if-no-files-found: error
|
||||
|
||||
publish:
|
||||
permissions:
|
||||
contents: write
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
steps:
|
||||
- name: Download verified preview bundle
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: docker-practice-preview
|
||||
path: dist
|
||||
|
||||
- name: Recheck preview checksum
|
||||
run: (cd dist && sha256sum -c SHA256SUMS)
|
||||
|
||||
- name: Write release notes
|
||||
run: |
|
||||
@@ -50,28 +122,83 @@ jobs:
|
||||
- Branch: \`${GITHUB_REF_NAME}\`
|
||||
- Commit: https://github.com/${GITHUB_REPOSITORY}/commit/${GITHUB_SHA}
|
||||
- Run: https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}
|
||||
|
||||
This is a mutable preview without formal provenance. Use a tagged release for attested artifacts.
|
||||
EOF
|
||||
|
||||
- name: Create preview release
|
||||
- name: Synchronize mutable preview tag
|
||||
shell: bash
|
||||
run: |
|
||||
gh release view preview-pdf >/dev/null 2>&1 || \
|
||||
set -euo pipefail
|
||||
if [[ ! "$GITHUB_REPOSITORY" =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ ]]; then
|
||||
echo "Invalid GITHUB_REPOSITORY: $GITHUB_REPOSITORY" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ ! "$GITHUB_SHA" =~ ^[0-9a-f]{40}$ ]]; then
|
||||
echo "Invalid GITHUB_SHA" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
probe_dir=$(mktemp -d)
|
||||
trap 'rm -rf "$probe_dir"' EXIT
|
||||
set +e
|
||||
gh api --include --method GET \
|
||||
"repos/${GITHUB_REPOSITORY}/git/ref/tags/preview-pdf" \
|
||||
>"$probe_dir/response" 2>"$probe_dir/error"
|
||||
probe_rc=$?
|
||||
set -e
|
||||
http_status=$(awk '$1 ~ /^HTTP\// && $2 ~ /^[0-9][0-9][0-9]$/ { status=$2 } END { print status }' "$probe_dir/response")
|
||||
|
||||
if [[ $probe_rc -eq 0 && "$http_status" == "200" ]]; then
|
||||
gh api --silent --method PATCH \
|
||||
"repos/${GITHUB_REPOSITORY}/git/refs/tags/preview-pdf" \
|
||||
--raw-field sha="$GITHUB_SHA" \
|
||||
--field force=true
|
||||
elif [[ $probe_rc -ne 0 && "$http_status" == "404" ]]; then
|
||||
gh api --silent --method POST \
|
||||
"repos/${GITHUB_REPOSITORY}/git/refs" \
|
||||
--raw-field ref="refs/tags/preview-pdf" \
|
||||
--raw-field sha="$GITHUB_SHA"
|
||||
else
|
||||
cat "$probe_dir/response" >&2
|
||||
cat "$probe_dir/error" >&2
|
||||
echo "Preview tag lookup failed (exit=$probe_rc, HTTP=${http_status:-unavailable}); refusing to mutate refs or releases." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Create or update preview release
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
error_file=$(mktemp)
|
||||
trap 'rm -f "$error_file"' EXIT
|
||||
set +e
|
||||
gh release view preview-pdf >/dev/null 2>"$error_file"
|
||||
release_rc=$?
|
||||
set -e
|
||||
|
||||
if [[ $release_rc -eq 0 ]]; then
|
||||
gh release edit preview-pdf \
|
||||
--title "Latest Preview PDF" \
|
||||
--notes-file dist/release-notes.md \
|
||||
--prerelease
|
||||
elif [[ "$(tr -d '\r' < "$error_file")" == "release not found" ]]; then
|
||||
gh release create preview-pdf \
|
||||
--title "Latest Preview PDF" \
|
||||
--notes-file dist/release-notes.md \
|
||||
--prerelease \
|
||||
--latest=false \
|
||||
--target "$GITHUB_SHA"
|
||||
--verify-tag
|
||||
else
|
||||
cat "$error_file" >&2
|
||||
echo "Preview release lookup failed (exit=$release_rc); refusing to create or edit the release." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Update preview release metadata
|
||||
- name: Replace preview assets
|
||||
run: |
|
||||
gh release edit preview-pdf \
|
||||
--title "Latest Preview PDF" \
|
||||
--notes-file dist/release-notes.md \
|
||||
--prerelease \
|
||||
--target "$GITHUB_SHA"
|
||||
|
||||
- name: Remove legacy preview asset
|
||||
run: gh release delete-asset preview-pdf latest.pdf -y || true
|
||||
|
||||
- name: Upload preview PDF
|
||||
run: gh release upload preview-pdf dist/docker_practice.pdf --clobber
|
||||
gh release delete-asset preview-pdf latest.pdf -y || true
|
||||
gh release upload preview-pdf \
|
||||
dist/docker_practice.pdf \
|
||||
dist/SHA256SUMS \
|
||||
--clobber
|
||||
|
||||
+4
-1
@@ -10,9 +10,10 @@ format_report.txt
|
||||
.DS_Store
|
||||
|
||||
node_modules/
|
||||
package-lock.json
|
||||
|
||||
docker-compose.override.yml
|
||||
06_repository/demo/auth/nginx.htpasswd
|
||||
11_compose/demo/wordpress/secrets/
|
||||
|
||||
# Editor configs
|
||||
.obsidian/
|
||||
@@ -26,6 +27,8 @@ Docker -- *_site/
|
||||
|
||||
# Check scripts
|
||||
check*.py
|
||||
!check_project_rules.py
|
||||
!check_emphasis.py
|
||||
find*.py
|
||||
fix*.py
|
||||
format*.py
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
/*
|
||||
!.gitignore
|
||||
!config.js
|
||||
@@ -1,463 +0,0 @@
|
||||
const { config } = require('vuepress-theme-hope')
|
||||
|
||||
module.exports = config({
|
||||
title: 'Docker 从入门到实践',
|
||||
base: '/',
|
||||
head: [['script', {}, `
|
||||
var _hmt = _hmt || [];
|
||||
(function() {
|
||||
var hm = document.createElement("script");
|
||||
hm.src = "//hm.baidu.com/hm.js?81a3490c9cd141dbcf6d00bc18b6edae";
|
||||
var s = document.getElementsByTagName("script")[0];
|
||||
s.parentNode.insertBefore(hm, s);
|
||||
})();
|
||||
`],
|
||||
[
|
||||
'script', {}, `
|
||||
(function(){
|
||||
var bp = document.createElement('script');
|
||||
var curProtocol = window.location.protocol.split(':')[0];
|
||||
if (curProtocol === 'https') {
|
||||
bp.src = 'https://zz.bdstatic.com/linksubmit/push.js';
|
||||
}
|
||||
else {
|
||||
bp.src = 'http://push.zhanzhang.baidu.com/push.js';
|
||||
}
|
||||
var s = document.getElementsByTagName("script")[0];
|
||||
s.parentNode.insertBefore(bp, s);
|
||||
})();
|
||||
`
|
||||
]
|
||||
],
|
||||
plugins: {
|
||||
// sitemap: {
|
||||
// hostname: 'https://vuepress.mirror.docker-practice.com'
|
||||
// },
|
||||
// 'git-log': {
|
||||
// additionalArgs: '--no-merge',
|
||||
// onlyFirstAndLastCommit: true,
|
||||
// },
|
||||
},
|
||||
locales: {
|
||||
"/": {
|
||||
lang: "zh-CN"
|
||||
}
|
||||
},
|
||||
themeConfig: {
|
||||
blog: false,
|
||||
// comment: false,
|
||||
comment: {
|
||||
type: "disable", // 使用 Valine
|
||||
appId: "...", // your appId
|
||||
appKey: "...", // your appKey
|
||||
},
|
||||
pageInfo: [
|
||||
// 'author',
|
||||
'reading-time',
|
||||
'word',
|
||||
],
|
||||
footer: {
|
||||
content: "Made with <a target='_blank' href='https://github.com/vuepress-theme-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: {
|
||||
lineNumbers: true,
|
||||
},
|
||||
git: {
|
||||
contributor: false,
|
||||
},
|
||||
themeColor: {
|
||||
blue: '#2196f3',
|
||||
// red: '#f26d6d',
|
||||
// green: '#3eaf7c',
|
||||
// orange: '#fb9b5f'
|
||||
},
|
||||
locales: {
|
||||
"/": {
|
||||
lang: "zh-CN"
|
||||
}
|
||||
},
|
||||
|
||||
darkmode: 'auto-switch',
|
||||
|
||||
//
|
||||
|
||||
showAds: true,
|
||||
|
||||
docsRepo: 'yeasy/docker_practice',
|
||||
docsDir: '/',
|
||||
docsBranch: 'master',
|
||||
editLinks: true,
|
||||
nav: [
|
||||
{
|
||||
text: '微信交流群',
|
||||
link: 'https://docker_practice.gitee.io/pic/dpsig-wechat.jpg',
|
||||
},
|
||||
{
|
||||
text: '小程序',
|
||||
link: 'https://docker_practice.gitee.io/pic/dp-wechat-miniprogram.jpg',
|
||||
},
|
||||
{
|
||||
text: '安装 Docker',
|
||||
link: '/03_install/',
|
||||
},
|
||||
{
|
||||
text: 'Docker 入门',
|
||||
link: '/'
|
||||
},
|
||||
{
|
||||
text: 'Docker 实战',
|
||||
link: '/15_cases/os/'
|
||||
},
|
||||
{
|
||||
text: 'CI/CD',
|
||||
link: '/15_cases/ci/'
|
||||
},
|
||||
{
|
||||
text: 'Compose',
|
||||
link: '/10_compose/',
|
||||
},
|
||||
{
|
||||
text: 'Kubernetes',
|
||||
link: '/12_orchestration/kubernetes/',
|
||||
},
|
||||
{
|
||||
text: "云计算",
|
||||
link: "/13_ecosystem/cloud/",
|
||||
},
|
||||
// {
|
||||
// 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: '语言',
|
||||
// items: [{
|
||||
// text: 'English',
|
||||
// link: ''
|
||||
// }]
|
||||
// }
|
||||
],
|
||||
sidebar: "auto",
|
||||
legacySidebar: {
|
||||
'/cloud/': [
|
||||
'intro',
|
||||
'tencentCloud',
|
||||
'alicloud',
|
||||
'aws',
|
||||
'summary',
|
||||
],
|
||||
'/kubernetes/': [
|
||||
'intro',
|
||||
'concepts',
|
||||
'design',
|
||||
{
|
||||
title: "部署 Kubernetes",
|
||||
collapsable: true,
|
||||
children: [
|
||||
"setup/",
|
||||
"setup/kubeadm",
|
||||
"setup/docker-desktop",
|
||||
"setup/systemd",
|
||||
"setup/dashboard",
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Kubernetes 命令行 kubectl",
|
||||
collapsable: true,
|
||||
children: [
|
||||
'kubectl/'
|
||||
]
|
||||
}
|
||||
],
|
||||
'/compose/': [
|
||||
'introduction',
|
||||
'v2',
|
||||
'install',
|
||||
'usage',
|
||||
'commands',
|
||||
'compose_file',
|
||||
'django',
|
||||
'rails',
|
||||
'wordpress',
|
||||
'lnmp',
|
||||
],
|
||||
'/install/': [
|
||||
'ubuntu',
|
||||
'debian',
|
||||
'fedora',
|
||||
'centos',
|
||||
'raspberry-pi',
|
||||
// 'offline',
|
||||
'mac',
|
||||
'windows',
|
||||
'mirror',
|
||||
'experimental',
|
||||
],
|
||||
'/cases/os/': [
|
||||
{
|
||||
title: "操作系统",
|
||||
collapsable: false,
|
||||
children: [
|
||||
'busybox',
|
||||
'alpine',
|
||||
'debian',
|
||||
'centos',
|
||||
'summary',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "在 IDE 中使用 Docker",
|
||||
collapsable: false,
|
||||
children: [
|
||||
'/ide/',
|
||||
'/ide/vsCode',
|
||||
],
|
||||
},
|
||||
],
|
||||
'/cases/ci/': [
|
||||
'actions/',
|
||||
{
|
||||
title: "Drone",
|
||||
collapsable: true,
|
||||
children: [
|
||||
'drone/',
|
||||
'drone/install'
|
||||
]
|
||||
},
|
||||
],
|
||||
'/': [
|
||||
'/',
|
||||
'/CHANGELOG',
|
||||
'/CONTRIBUTING',
|
||||
{
|
||||
title: "Docker 简介",
|
||||
collapsable: false,
|
||||
children: [
|
||||
'introduction/',
|
||||
'introduction/what',
|
||||
'introduction/why',
|
||||
]
|
||||
}, {
|
||||
title: "基本概念",
|
||||
collapsable: false,
|
||||
children: [
|
||||
'basic_concept/',
|
||||
'basic_concept/image',
|
||||
'basic_concept/container',
|
||||
'basic_concept/repository'
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "使用镜像",
|
||||
collapsable: false,
|
||||
children: [
|
||||
'image/',
|
||||
'image/pull',
|
||||
'image/list',
|
||||
'image/rm',
|
||||
'image/commit',
|
||||
'image/build',
|
||||
'image/other.md',
|
||||
'image/internal.md',
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Dockerfile',
|
||||
collapsable: true,
|
||||
children: [
|
||||
"image/dockerfile/",
|
||||
'image/dockerfile/copy',
|
||||
'image/dockerfile/add',
|
||||
'image/dockerfile/cmd',
|
||||
'image/dockerfile/entrypoint',
|
||||
'image/dockerfile/env',
|
||||
'image/dockerfile/arg',
|
||||
'image/dockerfile/volume',
|
||||
'image/dockerfile/expose',
|
||||
'image/dockerfile/workdir',
|
||||
'image/dockerfile/user',
|
||||
'image/dockerfile/healthcheck',
|
||||
'image/dockerfile/label',
|
||||
'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/manifest',
|
||||
]
|
||||
}, {
|
||||
title: "操作容器",
|
||||
collapsable: false,
|
||||
children: [
|
||||
'container/',
|
||||
'container/run',
|
||||
'container/daemon',
|
||||
'container/stop',
|
||||
'container/attach_exec',
|
||||
'container/import_export',
|
||||
'container/rm',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Docker 仓库",
|
||||
collapsable: false,
|
||||
children: [
|
||||
'repository/',
|
||||
'repository/dockerhub',
|
||||
'repository/registry',
|
||||
'repository/registry_auth',
|
||||
'repository/nexus3_registry',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "数据管理",
|
||||
collapsable: false,
|
||||
children: [
|
||||
'data_management/',
|
||||
'data_management/volume',
|
||||
'data_management/bind-mounts',
|
||||
],
|
||||
}, {
|
||||
title: "使用网络",
|
||||
collapsable: false,
|
||||
children: [
|
||||
'network/',
|
||||
'network/port_mapping',
|
||||
'network/linking',
|
||||
'network/dns',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "高级网络配置",
|
||||
collapsable: true,
|
||||
children: [
|
||||
'advanced_network/',
|
||||
'advanced_network/quick_guide',
|
||||
'advanced_network/access_control',
|
||||
'advanced_network/port_mapping',
|
||||
'advanced_network/bridge',
|
||||
'advanced_network/example',
|
||||
'advanced_network/config_file',
|
||||
'advanced_network/ptp',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Swarm mode",
|
||||
collapsable: true,
|
||||
children: [
|
||||
'swarm_mode/',
|
||||
'swarm_mode/overview',
|
||||
'swarm_mode/create',
|
||||
'swarm_mode/deploy',
|
||||
'swarm_mode/stack',
|
||||
'swarm_mode/secret',
|
||||
'swarm_mode/config',
|
||||
'swarm_mode/rolling_update',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "安全",
|
||||
collapsable: true,
|
||||
children: [
|
||||
'security/',
|
||||
'security/kernel_ns',
|
||||
'security/control_group',
|
||||
'security/daemon_sec',
|
||||
'security/kernel_capability',
|
||||
'security/other_feature',
|
||||
'security/summary',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "底层实现",
|
||||
collapsable: true,
|
||||
children: [
|
||||
'underly/',
|
||||
'underly/arch',
|
||||
'underly/namespace',
|
||||
'underly/cgroups',
|
||||
'underly/ufs',
|
||||
'underly/container_format',
|
||||
'underly/network',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Docker Buildx",
|
||||
collapsable: false,
|
||||
children: [
|
||||
"buildx/",
|
||||
"buildx/buildkit",
|
||||
"buildx/buildx",
|
||||
"buildx/multi-arch-images",
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Etcd",
|
||||
collapsable: true,
|
||||
children: [
|
||||
'etcd/',
|
||||
'etcd/intro',
|
||||
'etcd/install',
|
||||
'etcd/cluster',
|
||||
'etcd/etcdctl',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Fedora CoreOS",
|
||||
collapsable: true,
|
||||
children: [
|
||||
'coreos/',
|
||||
'coreos/intro',
|
||||
'coreos/install',
|
||||
],
|
||||
},
|
||||
'podman/',
|
||||
'appendix/faq/',
|
||||
{
|
||||
title: "热门镜像介绍",
|
||||
collapsable: true,
|
||||
children: [
|
||||
'appendix/repo/',
|
||||
'appendix/repo/ubuntu',
|
||||
'appendix/repo/centos',
|
||||
'appendix/repo/nginx',
|
||||
'appendix/repo/php',
|
||||
'appendix/repo/nodejs',
|
||||
'appendix/repo/mysql',
|
||||
'appendix/repo/wordpress',
|
||||
'appendix/repo/mongodb',
|
||||
'appendix/repo/redis',
|
||||
'appendix/repo/minio',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Docker 命令",
|
||||
collapsable: true,
|
||||
children: [
|
||||
'appendix/command/',
|
||||
'appendix/command/docker',
|
||||
'appendix/command/dockerd',
|
||||
]
|
||||
},
|
||||
'appendix/best_practices',
|
||||
'appendix/debug',
|
||||
'appendix/resources',
|
||||
],
|
||||
},
|
||||
}
|
||||
});
|
||||
@@ -1,3 +1,2 @@
|
||||
node_modules/
|
||||
.vuepress/
|
||||
.git/
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
|
||||
> **版本说明**:本节示例基于 Docker v29.x 编写。示例中使用的 `nginx:alpine` 镜像标签为演示用途,请查阅 [Docker Hub - nginx](https://hub.docker.com/_/nginx) 确认最新可用版本。
|
||||
|
||||
开始前请先完成 [第 3 章安装 Docker](../03_install/README.md),并确认 `docker version` 与 `docker run hello-world` 可以正常执行。若你只是先浏览流程,可以读完本节后再回到安装章实践。
|
||||
|
||||
本节将通过一个简单的 Web 应用例子,带你快速体验 Docker 的核心流程:构建镜像、运行容器。
|
||||
|
||||
### 为什么选择 Nginx + HTML 作为入门例子?
|
||||
|
||||
在学习 Docker 之前,我们先来理解为什么这个例子是最适合初学者的。Docker 的核心价值在于**一致性交付**——无论你在本地、云端还是他人的机器上运行容器,应用的行为都是完全一致的。这个 Nginx + 静态 HTML 的例子之所以被广泛采用,是因为它展现了 Docker 工作流的三个核心阶段:
|
||||
在学习 Docker 之前,我们先来理解为什么这个例子适合初学者。Docker 的核心价值在于**一致性交付**:在相同镜像、CPU 架构、内核能力、配置和外部依赖都满足的前提下,应用行为可以保持高度一致。这个 Nginx + 静态 HTML 的例子之所以被广泛采用,是因为它展现了 Docker 工作流的三个核心阶段:
|
||||
|
||||
1. **镜像定义(Image Layer)**:通过 Dockerfile 描述如何把应用打包成一个自包含的单元
|
||||
2. **镜像构建(Build)**:执行 `docker build`,Docker 根据 Dockerfile 逐层构建镜像
|
||||
|
||||
@@ -4,11 +4,11 @@ Docker 是彻底改变了软件开发和交付方式的革命性技术。本节
|
||||
|
||||
### 1.2.1 一句话理解 Docker
|
||||
|
||||
> **Docker 是一种轻量级的虚拟化技术,它让应用程序及其依赖环境可以被打包成一个标准化的单元,在任何地方都能一致地运行。** 如果用一个生活中的类比:**Docker 之于软件,就像集装箱之于货物**。
|
||||
> **Docker 是一种轻量级的虚拟化技术,它让应用程序及其依赖环境可以被打包成一个标准化的单元,在满足架构、内核能力和外部依赖前提的环境中高度一致地运行。** 如果用一个生活中的类比:**Docker 之于软件,就像集装箱之于货物**。
|
||||
|
||||
在集装箱发明之前,货物的运输是一件麻烦的事情——不同的货物需要不同的包装、不同的装卸方式,换一种运输工具就要重新装卸。集装箱的出现改变了这一切:无论里面装的是什么,集装箱的外形是标准的,可以用同样的方式装卸、堆放和运输。
|
||||
|
||||
Docker 做的事情类似:无论你的应用是用 Python、Java、Node.js 还是其他语言写的,无论它需要什么样的依赖库和环境,一旦被打包成 Docker 镜像,就可以用同样的方式在任何支持 Docker 的机器上运行。
|
||||
Docker 做的事情类似:无论你的应用是用 Python、Java、Node.js 还是其他语言写的,无论它需要什么样的依赖库和环境,一旦被打包成 Docker 镜像,就可以用同样的方式在兼容的 Docker 环境中运行。
|
||||
|
||||
### 1.2.2 Docker 的核心价值
|
||||
|
||||
@@ -112,7 +112,7 @@ flowchart LR
|
||||
Docker 的发展历程:
|
||||
|
||||
- **2013 年 3 月**:开源发布
|
||||
- **2013 年底**:dotCloud 公司改名为 Docker,Inc。
|
||||
- **2013 年底**:dotCloud 公司改名为 Docker, Inc.
|
||||
- **2015 年**:成立[开放容器联盟 (OCI)](https://opencontainers.org/),推动容器标准化
|
||||
- **至今**:[GitHub 项目](https://github.com/moby/moby)超过 7 万星标
|
||||
|
||||
|
||||
@@ -63,10 +63,10 @@ flowchart LR
|
||||
|
||||
#### 1. 环境一致性
|
||||
|
||||
Docker 镜像包含了应用运行所需的 **一切**:代码、运行时、系统工具、库、配置。这意味着:
|
||||
Docker 镜像包含了应用运行所需的大部分用户态依赖:代码、运行时、系统工具、库和默认配置。它不包含宿主机内核,也不能消除 CPU 架构、内核能力、外部服务、网络、卷和运行时配置差异。这意味着:
|
||||
|
||||
- ✅ 开发环境和生产环境完全一致
|
||||
- ✅ 不会再有 “在我机器上能跑” 的问题
|
||||
- ✅ 开发环境和生产环境可以显著减少差异
|
||||
- ✅ 大幅降低 “在我机器上能跑” 的问题
|
||||
- ✅ 新人入职,一条命令就能启动开发环境
|
||||
|
||||
```bash
|
||||
|
||||
@@ -172,7 +172,7 @@ registry.example.com/myproject/myapp:v1.2.3
|
||||
|
||||
## 简写(使用 Docker Hub)
|
||||
|
||||
nginx:1.30
|
||||
nginx:1.28
|
||||
ubuntu:24.04
|
||||
|
||||
## 省略标签(默认使用 latest)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
## 2.3 仓库
|
||||
|
||||
> **版本说明**:本节示例基于 Docker v29.x 和常见镜像版本编写。示例中的版本号(如 `nginx:1.30`、`mysql:8.0`、`mysql:5.7` 等)为演示用途。实际使用时请访问 [Docker Hub 官方页面](https://hub.docker.com) 或相应镜像的发布页确认最新可用版本和标签。
|
||||
> **版本说明**:本节示例基于 Docker v29.x 和常见镜像版本编写。示例中的版本号(如 `nginx:1.28`、`mysql:8.4`、`mysql:5.7` 等)为演示用途。实际使用时请访问 [Docker Hub 官方页面](https://hub.docker.com) 或相应镜像的发布页确认最新可用版本和标签。
|
||||
|
||||
Docker Registry 是镜像分发和管理的核心组件。本节将介绍 Registry 的基本概念、公共和私有服务的选择,以及镜像的安全管理。
|
||||
|
||||
@@ -25,8 +25,8 @@ flowchart TB
|
||||
subgraph RepoNginx ["Repository(仓库): nginx"]
|
||||
direction LR
|
||||
N1(":latest (tag)")
|
||||
N2(":1.25 (tag)")
|
||||
N3(":1.24 (tag)")
|
||||
N2(":1.28 (tag)")
|
||||
N3(":1.26 (tag)")
|
||||
N4(":alpine (tag)")
|
||||
N5("...")
|
||||
N1 ~~~ N2 ~~~ N3 ~~~ N4 ~~~ N5
|
||||
@@ -50,7 +50,7 @@ flowchart TB
|
||||
|------|------|------|
|
||||
| **Registry** | 存储镜像的服务 | Docker Hub、ghcr.io |
|
||||
| **Repository (仓库)** | 同一软件的镜像集合 | `nginx`、`mysql`、`mycompany/myapp` |
|
||||
| **Tag (标签)** | 仓库内的版本标识 | `latest`、`1.25`、`alpine` |
|
||||
| **Tag (标签)** | 仓库内的版本标识 | `latest`、`1.28`、`alpine` |
|
||||
|
||||
#### 镜像的完整名称
|
||||
|
||||
@@ -73,7 +73,7 @@ registry.example.com/mycompany/myapp:v1.2.3
|
||||
|
||||
## Docker Hub 官方镜像(省略 registry 和用户名)
|
||||
|
||||
nginx:1.30
|
||||
nginx:1.28
|
||||
ubuntu:24.04
|
||||
|
||||
## Docker Hub 用户镜像
|
||||
@@ -131,7 +131,7 @@ Google 的 Container Registry 已废弃并完成下线,当前应优先使用 A
|
||||
|
||||
由于网络原因,在国内直接访问 Docker Hub 可能会很慢。可以配置 **镜像加速器** (Registry Mirror) 来加速下载。配置示例如下:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
// /etc/docker/daemon.json
|
||||
{
|
||||
"registry-mirrors": [
|
||||
@@ -217,7 +217,7 @@ $ docker login registry.example.com # 登录其他 Registry
|
||||
|
||||
## 拉取镜像
|
||||
|
||||
$ docker pull nginx:1.30
|
||||
$ docker pull nginx:1.28
|
||||
|
||||
## 标记镜像(准备推送)
|
||||
|
||||
@@ -255,15 +255,15 @@ someuser/myapp # ⚠️ 需要评估
|
||||
|
||||
#### 镜像签名
|
||||
|
||||
当前更推荐使用 Sigstore / Notation 体系进行镜像签名与验证。`Docker Content Trust (DCT)` 已进入弃用阶段:2025 年 8 月 8 日起最早的 DCT 签名证书开始过期,2025 年 9 月 30 日起不能在新 Registry 启用 DCT,2028 年 3 月 31 日将完全删除此功能。不建议作为新项目方案。
|
||||
当前更推荐使用 Sigstore / Notation 体系进行镜像签名与验证。`Docker Content Trust (DCT)` 已被正式退役:自 Docker Engine 29.0 起 DCT 已从 Docker CLI 中移除(`docker trust` 子命令不再随 CLI 发布,仅能作为独立插件自行构建;`docker push`、`docker pull`、`docker build`、`docker run` 等命令上的 `--disable-content-trust` 选项虽仍可解析,但已标记为废弃且不再有实际作用),Docker 官方的 Notary v1 服务 `notary.docker.io` 也将于 2026 年 12 月 8 日完全关闭;最早一批 DCT 签名证书自 2025 年 8 月 8 日起已开始过期,Docker 建议镜像发布者迁移到 Sigstore、Notation 等方案。不建议把 DCT 作为新项目方案。
|
||||
|
||||
> 注意:Cosign 默认会把签名推送回镜像所在仓库,请使用你有推送权限的镜像地址。
|
||||
|
||||
```bash
|
||||
## 准备一个你有写权限的镜像地址
|
||||
$ export IMAGE=<你的仓库名>/nginx:1.27
|
||||
$ docker pull nginx:1.27
|
||||
$ docker tag nginx:1.27 $IMAGE
|
||||
$ export IMAGE=<你的仓库名>/nginx:1.28
|
||||
$ docker pull nginx:1.28
|
||||
$ docker tag nginx:1.28 $IMAGE
|
||||
$ docker push $IMAGE
|
||||
|
||||
## 生成签名密钥(会生成 cosign.key / cosign.pub)
|
||||
|
||||
@@ -91,7 +91,7 @@ $ sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin do
|
||||
在真正执行前,建议先用 `--dry-run` 预览脚本动作:
|
||||
|
||||
```bash
|
||||
$ curl -fsSL get.docker.com -o get-docker.sh
|
||||
$ curl -fsSL https://get.docker.com -o get-docker.sh
|
||||
$ sudo sh ./get-docker.sh --dry-run
|
||||
|
||||
# 若需要测试频道:
|
||||
|
||||
@@ -81,7 +81,7 @@ $ sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin do
|
||||
在真正执行前,建议先用 `--dry-run` 预览脚本动作:
|
||||
|
||||
```bash
|
||||
$ curl -fsSL get.docker.com -o get-docker.sh
|
||||
$ curl -fsSL https://get.docker.com -o get-docker.sh
|
||||
$ sudo sh ./get-docker.sh --dry-run
|
||||
|
||||
# 若需要测试频道:
|
||||
|
||||
@@ -18,7 +18,6 @@ Fedora 的快速发布周期(每 6 个月发布新版本)决定了它的用
|
||||
|
||||
* Fedora 44
|
||||
* Fedora 43
|
||||
* Fedora 42
|
||||
|
||||
#### 卸载旧版本
|
||||
|
||||
@@ -39,7 +38,7 @@ $ sudo dnf remove docker \
|
||||
|
||||
### 3.3.2 使用 dnf 安装
|
||||
|
||||
使用 dnf 包管理器安装是推荐的方式,便于后续的更行和管理。
|
||||
使用 dnf 包管理器安装是推荐的方式,便于后续的更新和管理。
|
||||
|
||||
执行以下命令安装依赖包:
|
||||
|
||||
@@ -51,9 +50,7 @@ $ sudo dnf -y install dnf-plugins-core
|
||||
执行下面的命令添加 `dnf` 软件源:
|
||||
|
||||
```bash
|
||||
$ sudo dnf config-manager \
|
||||
--add-repo \
|
||||
https://download.docker.com/linux/fedora/docker-ce.repo
|
||||
$ sudo dnf config-manager addrepo --from-repofile https://download.docker.com/linux/fedora/docker-ce.repo
|
||||
```
|
||||
如果需要测试版本的 Docker 请使用以下命令:
|
||||
|
||||
@@ -91,7 +88,7 @@ $ sudo dnf -y install docker-ce-<VERSION_STRING> docker-ce-cli-<VERSION_STRING>
|
||||
在真正执行前,建议先用 `--dry-run` 预览脚本动作:
|
||||
|
||||
```bash
|
||||
$ curl -fsSL get.docker.com -o get-docker.sh
|
||||
$ curl -fsSL https://get.docker.com -o get-docker.sh
|
||||
$ sudo sh ./get-docker.sh --dry-run
|
||||
|
||||
# 若需要测试频道:
|
||||
|
||||
@@ -88,7 +88,7 @@ Docker 官方提醒:如果主机使用 `ufw` 或 `firewalld` 管理防火墙
|
||||
在真正执行前,建议先用 `--dry-run` 预览脚本动作:
|
||||
|
||||
```bash
|
||||
$ curl -fsSL get.docker.com -o get-docker.sh
|
||||
$ curl -fsSL https://get.docker.com -o get-docker.sh
|
||||
$ sudo sh ./get-docker.sh --dry-run
|
||||
|
||||
# 若需要测试频道:
|
||||
|
||||
@@ -114,7 +114,7 @@ $ sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugi
|
||||
在真正执行前,建议先用 `--dry-run` 预览脚本动作:
|
||||
|
||||
```bash
|
||||
$ curl -fsSL get.docker.com -o get-docker.sh
|
||||
$ curl -fsSL https://get.docker.com -o get-docker.sh
|
||||
$ sudo sh ./get-docker.sh --dry-run
|
||||
|
||||
# 若需要测试频道:
|
||||
@@ -185,7 +185,7 @@ For more examples and ideas, visit:
|
||||
```
|
||||
若能正常输出以上信息,则说明安装成功。
|
||||
|
||||
*注意:*ARM 平台不能使用 `x86` 镜像,查看 Raspberry Pi OS 可使用镜像请访问 [arm32v7](https://hub.docker.com/u/arm32v7/) 或者 [arm64v8](https://hub.docker.com/u/arm64v8/)。
|
||||
*注意:* ARM 平台不能使用 `x86` 镜像,查看 Raspberry Pi OS 可使用镜像请访问 [arm32v7](https://hub.docker.com/u/arm32v7/) 或者 [arm64v8](https://hub.docker.com/u/arm64v8/)。
|
||||
|
||||
### 3.5.7 镜像加速
|
||||
|
||||
|
||||
@@ -90,9 +90,11 @@ $ sudo dnf versionlock clear
|
||||
##### 挂载 ISO 镜像搭建本地 File 源
|
||||
|
||||
```bash
|
||||
## 删除其他网络源
|
||||
## 备份其他网络源
|
||||
|
||||
$ sudo rm -f /etc/yum.repos.d/*
|
||||
$ repo_backup="/etc/yum.repos.d/backup-$(date +%Y%m%d%H%M%S)"
|
||||
$ sudo mkdir -p "$repo_backup"
|
||||
$ sudo find /etc/yum.repos.d -maxdepth 1 -type f -name '*.repo' -exec mv {} "$repo_backup"/ \;
|
||||
|
||||
## 挂载光盘或者iso镜像
|
||||
|
||||
@@ -106,7 +108,7 @@ $ sudo tee /etc/yum.repos.d/local-base.repo <<EOF
|
||||
name=local_base
|
||||
baseurl=file:///mnt
|
||||
enabled=1
|
||||
gpgcheck=0
|
||||
gpgcheck=1
|
||||
EOF
|
||||
```
|
||||
```bash
|
||||
@@ -156,7 +158,9 @@ $ sudo createrepo_c /var/www/html/docker-ce/
|
||||
##### DNF 客户端设置
|
||||
|
||||
```bash
|
||||
$ sudo rm -f /etc/yum.repos.d/*
|
||||
$ repo_backup="/etc/yum.repos.d/backup-$(date +%Y%m%d%H%M%S)"
|
||||
$ sudo mkdir -p "$repo_backup"
|
||||
$ sudo find /etc/yum.repos.d -maxdepth 1 -type f -name '*.repo' -exec mv {} "$repo_backup"/ \;
|
||||
$ sudo tee /etc/yum.repos.d/local-files.repo <<EOF
|
||||
[local_base]
|
||||
name=local_base
|
||||
@@ -165,7 +169,7 @@ name=local_base
|
||||
|
||||
baseurl=http://x.x.x.x/base
|
||||
enabled=1
|
||||
gpgcheck=0
|
||||
gpgcheck=1
|
||||
proxy=_none_
|
||||
[docker-ce-stable]
|
||||
name=docker-ce-stable
|
||||
@@ -174,7 +178,7 @@ name=docker-ce-stable
|
||||
|
||||
baseurl=http://x.x.x.x/docker-ce
|
||||
enabled=1
|
||||
gpgcheck=0
|
||||
gpgcheck=1
|
||||
proxy=_none_
|
||||
EOF
|
||||
|
||||
|
||||
+20
-1
@@ -72,6 +72,25 @@ $ docker stop webserver
|
||||
$ docker rm webserver
|
||||
```
|
||||
|
||||
### 3.7.4 镜像加速
|
||||
### 3.7.4 替代容器运行时
|
||||
|
||||
Docker Desktop 并非 macOS 上运行容器的唯一选择。以下两个工具也广泛使用,各有侧重:
|
||||
|
||||
| 特性 | Docker Desktop | OrbStack | Colima |
|
||||
|------|---------------|-----------|--------|
|
||||
| 启动速度 | 较慢(约 10–30 秒) | 极快(约 2 秒) | 中等(约 5–10 秒) |
|
||||
| 空闲内存占用 | 4–6 GB | 200–300 MB | 约 400 MB |
|
||||
| 图形界面 | 完整 GUI | 轻量 GUI | 仅命令行 |
|
||||
| Apple Silicon | 支持 | 原生优化 | 支持 |
|
||||
| 商业许可 | 大型企业需付费 | 个人免费,商业付费 | MIT 开源 |
|
||||
| Kubernetes | 内置 | 内置 | 通过 k3s 支持 |
|
||||
|
||||
**[OrbStack](https://orbstack.dev/)**:以极低的资源占用和接近原生的 I/O 性能著称。对于 Apple Silicon 机型上需要频繁构建镜像的开发者,体验提升尤为明显。个人和教育用途免费。
|
||||
|
||||
**[Colima](https://github.com/abiosoft/colima)**:完全开源的命令行方案,底层基于 Lima 虚拟机。适合偏好终端工作流、不需要 GUI 的开发者,或企业中希望避免商业许可限制的团队。
|
||||
|
||||
> 上述三种工具均兼容标准的 `docker` CLI 命令。从 Docker Desktop 切换到 OrbStack 或 Colima 时,已有的镜像和容器配置通常可以平滑迁移。
|
||||
|
||||
### 3.7.5 镜像加速
|
||||
|
||||
如果在使用过程中发现拉取 Docker 镜像十分缓慢,可以配置 Docker [国内镜像加速](3.9_mirror.md)。
|
||||
|
||||
@@ -80,9 +80,7 @@ Registry Mirrors:
|
||||
|
||||
### 3.9.6 Kubernetes 官方镜像地址迁移
|
||||
|
||||
可以登录 [阿里云容器镜像服务](https://www.aliyun.com/product/acr?source=5176.11533457&userCode=8lx5zmtu&type=copy),在 **镜像中心** -> **镜像搜索** 中查找。
|
||||
|
||||
Kubernetes 社区已将官方镜像地址从 `k8s.gcr.io` 迁移到 `registry.k8s.io`。建议优先使用新地址。
|
||||
Kubernetes 社区已将官方镜像地址从 `k8s.gcr.io` 迁移到 `registry.k8s.io`。建议优先使用新地址。若需查找国内可用的替代镜像,可以登录 [阿里云容器镜像服务](https://www.aliyun.com/product/acr),在 **镜像中心** -> **镜像搜索** 中查找对应镜像。
|
||||
|
||||
一般情况下有如下对应关系:
|
||||
|
||||
@@ -108,4 +106,4 @@ $ docker pull registry.k8s.io/xxx
|
||||
|
||||
某些云服务商提供了 **仅供内部** 访问的镜像服务,当您的 Docker 运行在云平台时可以选择它们。
|
||||
|
||||
* [腾讯云 `https://mirror.ccs.tencentyun.com`](https://cloud.tencent.com/act/cps/redirect?redirect=10058&cps_key=3a5255852d5db99dcd5da4c72f05df61)
|
||||
* [腾讯云 `https://mirror.ccs.tencentyun.com`](https://cloud.tencent.com/product/tke)
|
||||
|
||||
@@ -18,7 +18,7 @@ Docker Engine 主要提供 `stable` 和 `test` 两个更新频道;`test.docker
|
||||
|
||||
**开发环境**(本地开发机、测试服务器):
|
||||
|
||||
- 使用**脚本自动安装**或**包管理器直接安装**
|
||||
- 配置 **Docker 官方源后** 使用包管理器安装,或在一次性测试环境使用官方脚本自动安装
|
||||
- 如果你想快速上手,官方脚本(`get.docker.com`)是最便捷的选择
|
||||
- 国内用户注意:这一步一定要选对镜像源,否则网络卡顿会严重影响体验
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
> **版本号最佳实践**
|
||||
>
|
||||
> - **永远指定版本号**:避免使用 `latest` 标签,应指定具体的版本(如 `ubuntu:24.04`、`nginx:1.27`),以确保镜像内容稳定一致。
|
||||
> - **永远指定版本号**:避免使用 `latest` 标签,应指定具体的版本(如 `ubuntu:24.04`、`nginx:1.28`),以确保镜像内容稳定一致。
|
||||
> - **在生产环境使用摘要**:优先使用镜像摘要(SHA256)而非标签,如 `nginx@sha256:abc123...`,因为摘要不可变。
|
||||
> - **定期评估依赖**:即使指定了版本号,仍应定期检查依赖的基础镜像是否有安全更新。
|
||||
|
||||
@@ -160,7 +160,7 @@ root@e7009c6ce357:/# exit
|
||||
|
||||
从 Docker Hub 下载可能较慢。可以配置镜像加速器:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
// /etc/docker/daemon.json (Linux)
|
||||
// ~/.docker/daemon.json (Docker Desktop)
|
||||
{
|
||||
|
||||
@@ -252,7 +252,7 @@ $ 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 "{{.Size}}\t{{.Repository}}:{{.Tag}}" | grep -E "^[0-9]+(\.[0-9]+)?GB|^[5-9][0-9]{2}MB"
|
||||
|
||||
## 导出镜像列表
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
现在让我们以定制一个 Web 服务器为例子,来讲解镜像是如何构建的。
|
||||
|
||||
> **版本提示**:以下示例中 `nginx` 镜像使用默认 `latest` 标签。生产环境建议指定具体版本号(如 `nginx:1.27`),以避免镜像更新带来的不兼容性。
|
||||
> **版本提示**:以下示例中 `nginx` 镜像使用默认 `latest` 标签。生产环境建议指定具体版本号(如 `nginx:1.28`),以避免镜像更新带来的不兼容性。
|
||||
|
||||
```bash
|
||||
$ docker run --name webserver -d -p 8080:80 nginx
|
||||
@@ -89,11 +89,11 @@ sha256:07e33465974800ce65751acc279adc6ed2dc5ed4e0838f8b86f0c87aa1795214
|
||||
$ docker image ls nginx
|
||||
REPOSITORY TAG IMAGE ID CREATED SIZE
|
||||
nginx v2 07e334659748 9 seconds ago 181.5 MB
|
||||
nginx 1.27 05a60462f8ba 12 days ago 181.5 MB
|
||||
nginx 1.30 05a60462f8ba 12 days ago 181.5 MB
|
||||
nginx latest e43d811ce2f4 4 weeks ago 181.5 MB
|
||||
```
|
||||
|
||||
> **版本说明**:上面示例中 `nginx:1.27` 代表 1.27 系列的最新 patch 版本。在实际应用中应根据需求选择确切的版本号,而不是盲目使用 `latest`。
|
||||
> **版本说明**:上面示例中 `nginx:1.30` 代表 1.30 系列的最新 patch 版本。在实际应用中应根据需求选择确切的版本号,而不是盲目使用 `latest`。
|
||||
|
||||
我们还可以用 `docker history` 具体查看镜像内的历史记录。例如先执行 `docker history nginx:v2`,再对比 `docker history nginx:latest`,就能看到我们刚刚提交出来的新层。
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ $ touch Dockerfile
|
||||
```
|
||||
其内容为:
|
||||
|
||||
> **版本提示**:下面示例中 `FROM nginx` 使用的是 `latest` 标签。在实际应用中应使用明确的版本号(如 `FROM nginx:1.27`),以确保 Dockerfile 的可重现性和稳定性。
|
||||
> **版本提示**:下面示例中 `FROM nginx` 使用的是 `latest` 标签。在实际应用中应使用明确的版本号(如 `FROM nginx:1.28`),以确保 Dockerfile 的可重现性和稳定性。
|
||||
|
||||
```docker
|
||||
FROM nginx
|
||||
@@ -36,11 +36,11 @@ RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
|
||||
|
||||
### 4.5.3 FROM 指定基础镜像
|
||||
|
||||
所谓定制镜像,那一定是以一个镜像为基础,在其上进行定制。就像我们之前运行了一个 `nginx` 镜像的容器,再进行修改一样,基础镜像是必须指定的。而 `FROM` 就是指定 **基础镜像**,因此一个 `Dockerfile` 中 `FROM` 是必备的指令,并且必须是第一条指令。
|
||||
所谓定制镜像,那一定是以一个镜像为基础,在其上进行定制。就像我们之前运行了一个 `nginx` 镜像的容器,再进行修改一样,基础镜像是必须指定的。而 `FROM` 就是指定 **基础镜像**,因此一个 `Dockerfile` 中 `FROM` 是必备的指令,并且必须是第一条**构建指令**——按官方 Dockerfile 参考,它之前只允许出现 parser 指令、注释和全局作用域的 `ARG`,`ARG` 是唯一能写在 `FROM` 之前的指令(见 [7.7 ARG](../07_dockerfile/7.7_arg.md))。
|
||||
|
||||
> **版本号最佳实践**:在 `FROM` 指令中 **务必指定具体版本号**(如 `FROM ubuntu:24.04` 或 `FROM python:3.12-slim`)而非 `FROM ubuntu` 或 `FROM python:latest`。这样可以确保 Dockerfile 在不同时间、不同环境下构建出的镜像内容一致,避免因基础镜像更新导致的不可预期的变化。
|
||||
|
||||
在 [Docker Hub](https://hub.docker.com/search?q=&type=image&image_filter=official) 上有非常多的高质量的官方镜像,有可以直接拿来使用的服务类的镜像,如 [`nginx`](https://hub.docker.com/_/nginx/)、[`redis`](https://hub.docker.com/_/redis/)、[`mongo`](https://hub.docker.com/_/mongo/)、[`mysql`](https://hub.docker.com/_/mysql/)、[`httpd`](https://hub.docker.com/_/httpd/)、[`php`](https://hub.docker.com/_/php/)、[`tomcat`](https://hub.docker.com/_/tomcat/) 等;也有一些方便开发、构建、运行各种语言应用的镜像,如 [`node`](https://hub.docker.com/_/node)、[`openjdk`](https://hub.docker.com/_/openjdk/)、[`python`](https://hub.docker.com/_/python/)、[`ruby`](https://hub.docker.com/_/ruby/)、[`golang`](https://hub.docker.com/_/golang/) 等。可以在其中寻找一个最符合我们最终目标的镜像为基础镜像进行定制。
|
||||
在 [Docker Hub](https://hub.docker.com/search?q=&type=image&image_filter=official) 上有非常多的高质量的官方镜像,有可以直接拿来使用的服务类的镜像,如 [`nginx`](https://hub.docker.com/_/nginx/)、[`redis`](https://hub.docker.com/_/redis/)、[`mongo`](https://hub.docker.com/_/mongo/)、[`mysql`](https://hub.docker.com/_/mysql/)、[`httpd`](https://hub.docker.com/_/httpd/)、[`php`](https://hub.docker.com/_/php/)、[`tomcat`](https://hub.docker.com/_/tomcat/) 等;也有一些方便开发、构建、运行各种语言应用的镜像,如 [`node`](https://hub.docker.com/_/node)、[`eclipse-temurin`](https://hub.docker.com/_/eclipse-temurin/)、[`python`](https://hub.docker.com/_/python/)、[`ruby`](https://hub.docker.com/_/ruby/)、[`golang`](https://hub.docker.com/_/golang/) 等。可以在其中寻找一个最符合我们最终目标的镜像为基础镜像进行定制。
|
||||
|
||||
如果没有找到对应服务的镜像,官方镜像中还提供了一些更为基础的操作系统镜像,如 [`ubuntu`](https://hub.docker.com/_/ubuntu/)、[`debian`](https://hub.docker.com/_/debian/)、[`centos`](https://hub.docker.com/_/centos/)、[`fedora`](https://hub.docker.com/_/fedora/)、[`alpine`](https://hub.docker.com/_/alpine/) 等,这些操作系统的软件库为我们提供了更广阔的扩展空间。
|
||||
|
||||
@@ -98,7 +98,7 @@ Step 2 : RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
|
||||
Removing intermediate container 9cdc27646c7b
|
||||
Successfully built 44aa4490ce2c
|
||||
```
|
||||
从命令的输出结果中,我们可以清晰的看到镜像的构建过程。在 `Step 2` 中,如同我们之前所说的那样,`RUN` 指令启动了一个容器 `9cdc27646c7b`,执行了所要求的命令,并最后提交了这一层 `44aa4490ce2c`,随后删除了所用到的这个容器 `9cdc27646c7b`。
|
||||
从命令的输出结果中,我们可以清晰地看到镜像的构建过程。在 `Step 2` 中,如同我们之前所说的那样,`RUN` 指令启动了一个容器 `9cdc27646c7b`,执行了所要求的命令,并最后提交了这一层 `44aa4490ce2c`,随后删除了所用到的这个容器 `9cdc27646c7b`。
|
||||
|
||||
这里我们使用了 `docker build` 命令进行镜像构建。其格式为:
|
||||
|
||||
@@ -180,4 +180,4 @@ cat Dockerfile | docker build -
|
||||
```bash
|
||||
$ docker build - < context.tar.gz
|
||||
```
|
||||
如果发现标准输入的文件格式是 `gzip`、`bzip2` 以及 `xz` 的话,将会使其为上下文压缩包,直接将其展开,将里面视为上下文,并开始构建。
|
||||
如果发现标准输入的文件格式是 `gzip`、`bzip2` 以及 `xz` 的话,将会将其视为上下文压缩包,直接将其展开,将里面视为上下文,并开始构建。
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
比如我们想要创建一个 [OpenVZ](https://openvz.org) 的 Ubuntu 16.04 [模板](https://wiki.openvz.org/Download/template/precreated)的镜像:
|
||||
|
||||
> **版本提示**:示例中的 Ubuntu 16.04 (Xenial) 已于 2021 年 4 月停止支持。如果用于生产环境,建议更新至 Ubuntu 22.04 LTS 或更新版本。
|
||||
> **版本提示**:`noble` 对应 Ubuntu 24.04 LTS。实际用于生产环境时,应选择仍在安全维护期内的发行版,并按团队的基础镜像更新策略定期重建。
|
||||
|
||||
```bash
|
||||
$ docker import \
|
||||
@@ -70,7 +70,7 @@ filename: POSIX tar archive
|
||||
```bash
|
||||
$ docker save alpine | gzip > alpine-latest.tar.gz
|
||||
```
|
||||
然后我们将 `alpine-latest.tar.gz` 文件复制到了到了另一个机器上,可以用下面这个命令加载镜像:
|
||||
然后我们将 `alpine-latest.tar.gz` 文件复制到了另一个机器上,可以用下面这个命令加载镜像:
|
||||
|
||||
```bash
|
||||
$ docker load -i alpine-latest.tar.gz
|
||||
|
||||
@@ -60,7 +60,7 @@ Docker 镜像的每一层都有一个唯一的 ID,这个 ID 是根据该层的
|
||||
|
||||
Docker 使用联合文件系统 (Union FS) 与写时复制思路来实现这种分层挂载。传统的实现方式常见于 `overlay2`、`aufs`、`btrfs`、`zfs` 等存储驱动;而在 Docker Engine 29.0 及之后的全新安装中,默认镜像后端已经变为 containerd image store,它使用 snapshotter 来管理这些层。
|
||||
|
||||
> **版本背景**:Docker Engine 29.0(发布于 2024 年 2 月)是一个重要版本分界点,引入了 containerd image store 作为默认镜像存储后端。这对镜像管理、OCI 合规性和供应链安全都有深远影响。如果你的 Docker 版本低于 29.0,镜像存储仍使用传统的 classic store 路径。
|
||||
> **版本背景**:Docker Engine 29.0.0 发布于 2025 年 11 月 10 日,是一个重要版本分界点。Docker Engine 29.0 及之后的全新安装默认使用 containerd image store;从更早版本升级的 daemon 会继续使用 legacy graph driver,直到显式启用 containerd image store。Docker Desktop 4.34 及之后也默认启用 containerd image store,实际环境仍应以当前配置为准。
|
||||
|
||||
虽然底层实现细节不同,但它们都遵循上述的 **分层 + CoW** 模型;因此,无论你看到的是 `overlay2` 还是 containerd snapshotter,理解镜像层、容器层和写时复制的方式都是一样重要的。
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# syntax = docker/dockerfile:experimental
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
FROM node:alpine as builder
|
||||
|
||||
@@ -6,24 +6,16 @@ WORKDIR /app
|
||||
|
||||
COPY package.json /app/
|
||||
|
||||
RUN --mount=type=cache,target=/app/node_modules,id=my_app_npm_module,sharing=locked \
|
||||
--mount=type=cache,target=/root/.npm,id=npm_cache \
|
||||
npm i --registry=https://registry.npmmirror.com
|
||||
RUN --mount=type=cache,target=/root/.npm,id=npm_cache \
|
||||
npm install --registry=https://registry.npmmirror.com
|
||||
|
||||
COPY src /app/src
|
||||
|
||||
RUN --mount=type=cache,target=/app/node_modules,id=my_app_npm_module,sharing=locked \
|
||||
# --mount=type=cache,target=/app/dist,id=my_app_dist,sharing=locked \
|
||||
npm run build
|
||||
RUN npm run build
|
||||
|
||||
FROM nginx:alpine
|
||||
|
||||
# COPY --from=builder /app/dist /app/dist
|
||||
|
||||
# 为了更直观的说明 from 和 source 指令,这里使用 RUN 指令
|
||||
RUN --mount=type=cache,target=/tmp/dist,from=builder,source=/app/dist \
|
||||
# --mount=type=cache,target/tmp/dist,from=my_app_dist,sharing=locked \
|
||||
mkdir -p /app/dist && cp -r /tmp/dist/* /app/dist
|
||||
COPY --from=builder /app/dist /app/dist
|
||||
|
||||
RUN --mount=type=bind,from=php:alpine,source=/usr/local/bin/docker-php-entrypoint,target=/docker-php-entrypoint \
|
||||
cat /docker-php-entrypoint
|
||||
@@ -32,6 +24,6 @@ RUN --mount=type=tmpfs,target=/temp \
|
||||
mount | grep /temp
|
||||
|
||||
RUN --mount=type=secret,id=aws,target=/root/.aws/credentials \
|
||||
cat /root/.aws/credentials
|
||||
test -s /root/.aws/credentials && echo "credentials mounted"
|
||||
|
||||
# docker build -t test --secret id=aws,src=$PWD/aws.txt --progress=plain -f Dockerfile.buildkit .
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
FROM golang:alpine as builder
|
||||
|
||||
RUN apk --no-cache add git
|
||||
|
||||
WORKDIR /go/src/github.com/go/helloworld/
|
||||
|
||||
RUN go get -d -v github.com/go-sql-driver/mysql
|
||||
|
||||
COPY app.go .
|
||||
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
|
||||
RUN go mod init helloworld \
|
||||
&& CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
|
||||
|
||||
FROM alpine:latest as prod
|
||||
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
FROM golang:alpine
|
||||
|
||||
RUN apk --no-cache add git
|
||||
|
||||
WORKDIR /go/src/github.com/go/helloworld
|
||||
|
||||
COPY app.go .
|
||||
|
||||
RUN go get -d -v github.com/go-sql-driver/mysql \
|
||||
RUN go mod init helloworld \
|
||||
&& CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
FROM golang:alpine
|
||||
|
||||
RUN apk --no-cache add git ca-certificates
|
||||
RUN apk --no-cache add ca-certificates
|
||||
|
||||
WORKDIR /go/src/github.com/go/helloworld/
|
||||
|
||||
COPY app.go .
|
||||
|
||||
RUN go get -d -v github.com/go-sql-driver/mysql \
|
||||
RUN go mod init helloworld \
|
||||
&& CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app . \
|
||||
&& cp /go/src/github.com/go/helloworld/app /root
|
||||
|
||||
|
||||
@@ -126,14 +126,14 @@ $ docker run -d -p 80:80 nginx:latest
|
||||
|
||||
## 数据库
|
||||
|
||||
$ docker run -d -p 3306:3306 mysql:8.0
|
||||
$ docker run -d -p 3306:3306 mysql:8.4
|
||||
|
||||
## 缓存服务
|
||||
|
||||
$ docker run -d -p 6379:6379 redis:latest
|
||||
```
|
||||
|
||||
> **版本说明**:示例使用常见的标签如 `latest` 或稳定大版本号如 `mysql:8.0`。具体版本可根据需求调整,生产环境建议明确指定版本号(如 `mysql:8.0.35`)而非使用 `latest`。
|
||||
> **版本说明**:示例使用常见的标签如 `latest` 或稳定大版本号如 `mysql:8.4`。具体版本可根据需求调整,生产环境建议明确指定版本号(如 `mysql:8.4.4`)而非使用 `latest`。
|
||||
|
||||
#### 2. 调试时先用前台模式
|
||||
|
||||
@@ -196,9 +196,9 @@ $ docker logs -t myapp
|
||||
|
||||
3. **以交互模式调试**:
|
||||
```bash
|
||||
# /bin/sh 覆盖镜像原本的启动命令,避免容器再次崩溃退出
|
||||
# 进入 shell 后可手动执行原启动命令,定位具体报错原因
|
||||
$ docker run -it myimage:v1.0.0 /bin/sh
|
||||
# 进入容器手动执行命令,查找问题
|
||||
|
||||
```
|
||||
|
||||
#### Q:容器在后台运行但无法访问服务
|
||||
|
||||
@@ -153,6 +153,7 @@ stateDiagram-v2
|
||||
[*] --> Created : docker create
|
||||
Created --> Running : docker start
|
||||
Running --> Stopped : docker stop
|
||||
Stopped --> Running : docker start
|
||||
Running --> Paused : docker pause
|
||||
Paused --> Running : docker unpause
|
||||
|
||||
@@ -212,7 +213,7 @@ FROM node:22-alpine
|
||||
CMD ["node", "server.js"]
|
||||
```
|
||||
|
||||
> **版本说明**:示例使用 `node:22-alpine`,这是一个精简的 Node.js 22 版本镜像。可根据需求替换为其他版本(如 `node:20-alpine`、`node:latest`)。
|
||||
> **版本说明**:示例使用 `node:22-alpine`,这是一个精简的 Node.js 22 版本镜像。可根据需求替换为其他版本(如 `node:24-alpine`、`node:latest`)。
|
||||
|
||||
#### Q:容器无法停止
|
||||
|
||||
|
||||
@@ -249,10 +249,11 @@ $ docker exec myapp python manage.py migrate
|
||||
$ docker exec -it myapp bash
|
||||
OCI runtime exec failed: exec failed: unable to start container process: exec: "bash": executable file not found
|
||||
|
||||
## 解决方案:使用调试容器
|
||||
## 解决方案:使用调试容器(需要 Docker Desktop Pro/Team/Business 订阅)
|
||||
|
||||
$ docker debug myapp
|
||||
```
|
||||
> **注意**:`docker debug` 是 Docker Desktop 4.33+ 提供的功能,需要 Pro、Team 或 Business 订阅。它会附加一个包含常用调试工具(vim、curl、htop 等)的工具箱到目标容器,即使目标镜像基于 `scratch` 也能使用。
|
||||
---
|
||||
|
||||
### 5.4.7 常见问题
|
||||
|
||||
@@ -10,11 +10,11 @@
|
||||
|
||||
本章示例涉及多个 Docker 镜像,遵循以下版本号最佳实践:
|
||||
|
||||
- **官方镜像**(如 `ubuntu`、`nginx`、`mysql`):使用具体大版本号(如 `ubuntu:24.04`、`mysql:8.0`)而非 `latest`,确保示例的可重复性
|
||||
- **官方镜像**(如 `ubuntu`、`nginx`、`mysql`):使用具体大版本号(如 `ubuntu:24.04`、`mysql:8.4`)而非 `latest`,确保示例的可重复性
|
||||
- **镜像标签约定**:
|
||||
- `latest` 或 `v1.0.0` 等:带标签的自定义镜像,示例中指定具体版本
|
||||
- `24.04`、`8.0`:官方镜像的稳定版本分支
|
||||
- 生产环境建议:指定精确版本号(如 `nginx:1.24.0`、`mysql:8.0.35`)而非仅大版本号
|
||||
- `24.04`、`8.4`:官方镜像的稳定版本分支
|
||||
- 生产环境建议:指定确切版本号(如 `nginx:1.28.0`、`mysql:8.4.4`)而非仅大版本号
|
||||
|
||||
* [启动容器](5.1_run.md)
|
||||
* [守护态运行](5.2_daemon.md)
|
||||
|
||||
@@ -11,7 +11,7 @@ Docker Hub 是 Docker 的中央镜像仓库,通过它您可以轻松地分享
|
||||
|
||||
- **官方镜像**:由 Docker 官方和软件厂商 (如 Nginx,MySQL,Node.js) 维护的高质量镜像。
|
||||
- **个人/组织仓库**:用户可以上传自己的镜像。
|
||||
- **自动构建**:与 GitHub/Bitbucket 集成 (需付费)。
|
||||
- **自动构建**:与 GitHub/Bitbucket 集成的历史功能,Docker 已标记为 deprecated,并计划于 2027-04-01 完全退役。
|
||||
- **Webhooks**:镜像更新时触发回调。
|
||||
|
||||
---
|
||||
@@ -27,8 +27,8 @@ Docker Hub 是 Docker 的中央镜像仓库,通过它您可以轻松地分享
|
||||
|
||||
```bash
|
||||
$ docker search centos
|
||||
NAME DESCRIPTION STARS OFFICIAL
|
||||
centos The official build of CentOS. 7000+ [OK]
|
||||
NAME DESCRIPTION STARS OFFICIAL
|
||||
centos DEPRECATED; The official build of CentOS. 7000+ [OK]
|
||||
```
|
||||
> **技巧**:始终优先使用 `OFFICIAL` 标记为 `[OK]` 的镜像,安全性更有保障。
|
||||
|
||||
@@ -75,7 +75,7 @@ Docker Hub 对不同类型用户实施拉取速率限制(基于 6 小时周期
|
||||
| **免费账户** (已登录) | 每 6 小时 200 次请求 |
|
||||
| **Pro/Team/Business 账户** | 无限制(公平使用政策) |
|
||||
|
||||
> **注意**:自 2025 年 4 月起,所有付费订阅用户享有无限制拉取额度。匿名用户和免费账户的限制保持不变,建议在 CI/CD 环境中始终配置 `docker login` 以获得更高的拉取额度。
|
||||
> **注意**:Docker 曾计划于 2025 年 4 月调整拉取限制策略,但在 2025 年 2 月宣布取消该计划。目前付费订阅用户享有无限制拉取额度,匿名用户和免费账户的限制保持不变。建议在 CI/CD 环境中始终配置 `docker login` 以获得更高的拉取额度。
|
||||
|
||||
#### 滥用限流
|
||||
|
||||
@@ -107,10 +107,11 @@ Docker Hub 对不同类型用户实施拉取速率限制(基于 6 小时周期
|
||||
> **⚠️ 警告**:绝不要在脚本或 CI/CD 系统中,直接使用 `-p` 参数传递密码或 Token (类似 `docker login -p xxx`)!这会导致凭证直接暴露在系统的命令历史、进程列表和终端输出中。
|
||||
|
||||
1. 在 Docker Hub -> Account Settings -> Security -> Access Tokens 创建 Token (PAT)。
|
||||
2. 将 Token 通过标准输入 (stdin) 安全传递给 Docker:
|
||||
2. 将 Token 保存在权限受限的本地文件或 CI secret 中,再通过标准输入 (stdin) 传递给 Docker,避免把真实 Token 写进脚本、命令历史或日志:
|
||||
|
||||
```bash
|
||||
$ echo "dckr_pat_xxxxxxx" | docker login --username username --password-stdin
|
||||
$ chmod 600 "$HOME/.dockerhub-token"
|
||||
$ cat "$HOME/.dockerhub-token" | docker login --username username --password-stdin
|
||||
```
|
||||
|
||||
#### 3. 关注镜像漏洞
|
||||
@@ -130,8 +131,8 @@ Docker Hub 提供 Docker Scout 安全扫描功能。官方镜像的漏洞扫描
|
||||
|
||||
### 6.1.6 自动构建
|
||||
|
||||
> ⚠️ 目前仅限付费用户 (Pro/Team) 使用。
|
||||
> ⚠️ Docker Hub Automated Builds 已被 Docker 标记为 deprecated,并计划于 2027-04-01 完全退役;新项目应优先使用 GitHub Actions、Buildx 或自有 CI 构建并推送镜像。
|
||||
|
||||
链接 GitHub/Bitbucket 仓库后,当代码有提交或打标签时,Docker Hub 会自动运行构建。这保证了镜像总是与代码同步,且由可信的官方环境构建。
|
||||
对于仍在迁移期内的旧仓库,链接 GitHub/Bitbucket 仓库后,当代码有提交或打标签时,Docker Hub 会自动运行构建。不要把它作为新架构的默认方案。
|
||||
|
||||
---
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
本节介绍如何使用本地仓库。
|
||||
|
||||
[Docker Registry](https://docs.docker.com/registry/) 是官方提供的工具,可以用于构建私有的镜像仓库。本文内容基于 [distribution/distribution](https://github.com/distribution/distribution) v2.x 版本。
|
||||
[Docker Registry](https://distribution.github.io/distribution/) 是官方提供的工具,可以用于构建私有的镜像仓库。本文示例沿用 [distribution/distribution](https://github.com/distribution/distribution) v2.x 兼容路径;新生产部署应评估 Distribution 3.x,并核对配置路径、迁移说明和生态兼容性。
|
||||
|
||||
### 6.2.1 安装运行 docker-registry
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
$ docker run -d -p 5000:5000 --restart=always --name registry registry:2
|
||||
```
|
||||
|
||||
> **版本说明**:使用 `registry:2` 表示 Docker Registry 2.x 版本,这是当前推荐的版本。旧版本 Registry 1.x 已停止维护,不建议使用。
|
||||
> **版本说明**:使用 `registry:2` 表示 Docker Registry 2.x 兼容示例。Distribution 3.x 已发布稳定版本;不要直接把本章 v2 配置原样套到 v3 生产环境,升级前应阅读迁移说明并做兼容测试。旧版本 Registry 1.x 已停止维护,不建议使用。
|
||||
这将使用官方的 `registry` 镜像来启动私有仓库。默认情况下,仓库会被创建在容器的 `/var/lib/registry` 目录下。你可以通过 `-v` 参数来将镜像文件存放在本地的指定路径。例如下面的例子将上传的镜像放到本地的 `/opt/data/registry` 目录。
|
||||
|
||||
```bash
|
||||
@@ -48,7 +48,7 @@ $ docker tag ubuntu:latest 127.0.0.1:5000/ubuntu:latest
|
||||
$ docker image ls
|
||||
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
|
||||
ubuntu latest ba5877dc9bec 6 weeks ago 192.7 MB
|
||||
127.0.0.1:5000/ubuntu:latest latest ba5877dc9bec 6 weeks ago 192.7 MB
|
||||
127.0.0.1:5000/ubuntu latest ba5877dc9bec 6 weeks ago 192.7 MB
|
||||
```
|
||||
使用 `docker push` 上传标记的镜像。
|
||||
|
||||
@@ -87,7 +87,7 @@ ebc34468f71d: Download complete
|
||||
|
||||
$ docker image ls
|
||||
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
|
||||
127.0.0.1:5000/ubuntu:latest latest ba5877dc9bec 6 weeks ago 192.7 MB
|
||||
127.0.0.1:5000/ubuntu latest ba5877dc9bec 6 weeks ago 192.7 MB
|
||||
```
|
||||
|
||||
### 6.2.3 配置非 https 仓库地址
|
||||
|
||||
@@ -70,10 +70,14 @@ $ openssl x509 -req -days 750 -in "site.csr" -sha256 \
|
||||
-CA "root-ca.crt" -CAkey "root-ca.key" -CAcreateserial \
|
||||
-out "docker.domain.com.crt" -extfile "site.cnf" -extensions server
|
||||
```
|
||||
|
||||
配套 demo 中的 [`ssl/README.md`](demo/ssl/README.md) 只保留本地生成证书的占位说明,真实私钥和证书不应提交到仓库。
|
||||
这样已经拥有了 `docker.domain.com` 的网站 SSL 私钥 `docker.domain.com.key` 和 SSL 证书 `docker.domain.com.crt` 及 CA 根证书 `root-ca.crt`。
|
||||
|
||||
新建 `ssl` 文件夹并将 `docker.domain.com.key` `docker.domain.com.crt` `root-ca.crt` 这三个文件移入,删除其他文件。
|
||||
|
||||
> **安全提示**:这些私钥和证书应在本地或受控部署环境中生成,不要提交进 Git 仓库。示例目录只保留占位说明,运行前请按上面的步骤重新生成。
|
||||
|
||||
### 6.3.2 配置私有仓库
|
||||
|
||||
私有仓库默认的配置文件位于 `/etc/docker/registry/config.yml`,我们先在本地编辑 `config.yml`,之后挂载到容器中。
|
||||
@@ -99,7 +103,7 @@ auth:
|
||||
realm: basic-realm
|
||||
path: /etc/docker/registry/auth/nginx.htpasswd
|
||||
http:
|
||||
addr: :443
|
||||
addr: :5000
|
||||
host: https://docker.domain.com
|
||||
headers:
|
||||
X-Content-Type-Options: [nosniff]
|
||||
@@ -112,7 +116,7 @@ health:
|
||||
storagedriver:
|
||||
enabled: true
|
||||
interval: 10s
|
||||
threshold: 3
|
||||
threshold: 3
|
||||
```
|
||||
|
||||
### 6.3.3 生成 http 认证文件
|
||||
@@ -126,6 +130,9 @@ $ docker run --rm \
|
||||
-Bbn username password > auth/nginx.htpasswd
|
||||
```
|
||||
> 将上面的 `username` `password` 替换为你自己的用户名和密码。
|
||||
>
|
||||
> **安全提示**:上述命令会将密码明文暴露在 shell 历史记录和进程列表中。生产环境建议使用交互式方式输入密码(不带 `-b` 参数),或通过环境变量/文件传入。
|
||||
> 配套 demo 的 [`auth/README.md`](demo/auth/README.md) 仅说明本地生成步骤,生成的 `auth/nginx.htpasswd` 已被忽略,不应提交。
|
||||
|
||||
> **版本说明**:使用 `httpd:2.4-alpine` 基于 Apache 2.4 的精简镜像。如需其他版本,可替换为 `httpd:latest` 或指定具体版本号如 `httpd:2.4.58-alpine`。
|
||||
|
||||
@@ -138,7 +145,7 @@ services:
|
||||
registry:
|
||||
image: registry:2
|
||||
ports:
|
||||
- "443:443"
|
||||
- "443:5000"
|
||||
volumes:
|
||||
- ./:/etc/docker/registry
|
||||
- registry-data:/var/lib/registry
|
||||
@@ -147,7 +154,9 @@ volumes:
|
||||
registry-data:
|
||||
```
|
||||
|
||||
> **版本说明**:Compose 配置中明确指定 `registry:2` 版本。生产环境建议固定版本号(如 `registry:2.8.3`)而非使用 `latest`,以保证部署的可重复性。
|
||||
本书配套的 `06_repository/demo/` 也采用同样约定:容器内 registry 监听 `:5000`,宿主机通过 `443:5000` 暴露 HTTPS 服务。这样可以避免在容器内占用特权端口,同时仍让客户端使用 `https://docker.domain.com` 访问。
|
||||
|
||||
> **版本说明**:Compose 配置中明确指定 `registry:2` 版本。生产环境建议固定具体补丁版本(如 `registry:2.8.x`),并在新部署时评估 Distribution 3.x;不要使用 `latest`,以保证部署的可重复性。
|
||||
|
||||
### 6.3.5 修改 Hosts 文件
|
||||
|
||||
@@ -206,4 +215,4 @@ no basic auth credentials
|
||||
|
||||
### 6.3.8 注意事项
|
||||
|
||||
如果你本机占用了 `443` 端口,你可以配置 [Nginx 代理](https://docs.docker.com/registry/recipes/nginx/),这里不再赘述。
|
||||
如果你本机占用了 `443` 端口,你可以配置 [Nginx 代理](https://distribution.github.io/distribution/recipes/nginx/),这里不再赘述。
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
$ docker run -d --name nexus3 --restart=always \
|
||||
-p 8081:8081 \
|
||||
--mount src=nexus-data,target=/nexus-data \
|
||||
sonatype/nexus3:3.69
|
||||
sonatype/nexus3:3.69.0
|
||||
```
|
||||
|
||||
> **版本说明**:使用 `sonatype/nexus3:3.69` 指定稳定的 Nexus 版本。使用具体版本号而非 `latest` 可以避免意外升级带来的不兼容问题。
|
||||
> **版本说明**:使用 `sonatype/nexus3:3.69.0` 指定稳定的 Nexus 版本。使用具体版本号而非 `latest` 可以避免意外升级带来的不兼容问题。
|
||||
首次运行需等待 3-5 分钟,你可以使用 `docker logs nexus3 -f` 查看日志:
|
||||
|
||||
```bash
|
||||
@@ -80,7 +80,7 @@ server {
|
||||
ssl_certificate_key key/example.key;
|
||||
|
||||
ssl_session_timeout 5m;
|
||||
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers HIGH:!aNULL:!MD5;
|
||||
ssl_prefer_server_ciphers on;
|
||||
large_client_header_buffers 4 32k;
|
||||
@@ -119,8 +119,11 @@ server {
|
||||
使用 SSL 加密以后程序需要访问就不能采用修改配置的方式了。具体方法如下:
|
||||
|
||||
```bash
|
||||
$ openssl s_client -showcerts -connect YourDomainName OR HostIP:443 </dev/null 2>/dev/null|openssl x509 -outform PEM >ca.crt
|
||||
## REGISTRY_HOST 填你的域名或主机 IP,二者选其一
|
||||
|
||||
$ REGISTRY_HOST=your-domain-or-host-ip
|
||||
$ openssl s_client -showcerts -connect "$REGISTRY_HOST":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
|
||||
$ sudo systemctl restart docker
|
||||
```
|
||||
使用 `docker login YourDomainName OR HostIP` 进行测试,用户名密码填写上面 Nexus 中设置的。
|
||||
使用 `docker login $REGISTRY_HOST` 进行测试,用户名密码填写上面 Nexus 中设置的。
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
本章涉及的 Registry 和相关工具版本说明:
|
||||
|
||||
- **Docker Registry**:使用 `registry:2` 推荐版本,已停止维护的 `registry:1` 不建议使用
|
||||
- **Docker Registry / CNCF Distribution**:本章示例使用 `registry:2` 兼容路径;新生产部署应评估 Distribution 3.x,并核对配置路径、迁移说明和生态兼容性。已停止维护的 `registry:1` 不建议使用
|
||||
- **Nexus 3**:建议指定具体版本(如 `sonatype/nexus3:3.69`)而非 `latest`,避免自动升级带来的兼容性问题
|
||||
- **镜像标签规范**:
|
||||
- 生产环境推送至仓库时应明确指定版本号(如 `myapp:v1.0.0`)
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# Generated Authentication File
|
||||
|
||||
Run the `htpasswd` command in section 6.3.3 to generate `nginx.htpasswd` locally before starting the demo registry.
|
||||
|
||||
Do not commit generated password hashes.
|
||||
@@ -1,2 +0,0 @@
|
||||
username:$2y$05$TRWvCC6ilpKpY3ICifw32Ok3.8SpG3etq8O5WGdCm9wvyDhtSbRgy
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
*
|
||||
!.gitignore
|
||||
!README.md
|
||||
@@ -0,0 +1,5 @@
|
||||
# Generated TLS Files
|
||||
|
||||
Run the certificate steps in section 6.3.1 to generate `docker.domain.com.key`, `docker.domain.com.crt`, and `root-ca.crt` locally before starting the demo registry.
|
||||
|
||||
Do not commit generated private keys or certificates.
|
||||
@@ -1,35 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIF/zCCA+egAwIBAgIJAMbgVbFo7I6IMA0GCSqGSIb3DQEBCwUAMHoxCzAJBgNV
|
||||
BAYTAkNOMQ8wDQYDVQQIDAZTaGFueGkxDzANBgNVBAcMBkRhdG9uZzEaMBgGA1UE
|
||||
CgwRWW91ciBDb21wYW55IE5hbWUxLTArBgNVBAMMJFlvdXIgQ29tcGFueSBOYW1l
|
||||
IERvY2tlciBSZWdpc3RyeSBDQTAeFw0xNzEyMTExMTI2NTRaFw0xOTEyMzExMTI2
|
||||
NTRaMGcxCzAJBgNVBAYTAkNOMQ8wDQYDVQQIDAZTaGFueGkxDzANBgNVBAcMBkRh
|
||||
dG9uZzEaMBgGA1UECgwRWW91ciBDb21wYW55IE5hbWUxGjAYBgNVBAMMEWRvY2tl
|
||||
ci5kb21haW4uY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA1Hbm
|
||||
1tZvAeC8J54pQLHTVCtrICQ8KFTLOZakPHSWox8iQ4i2fkZaSccvE/51LIFnCM1y
|
||||
yZv88ILucqitG4zvuhDG+cU8w1vRbWf3xfGCCsHyn6LKBHR0Kk6+WRIBZTdRSsW8
|
||||
ZvpL2Y7eBNAUkC2oeaOJEOQ8D50b3u5jhAFmXuAcTiZh90Ve4JBKZV8dGs8L81vO
|
||||
vb7tqvJrEvCNKuZO7mEcjXkgiwUdP3pkZYa0tPOV5UrLH/oEvgPDJfXrNntCY1+A
|
||||
+CBQ7Sq3S2YpNJN7VnK6SboRi7xpOEgQOXwNVJWm/5YBvnbAztNXKcE2q2wREL9T
|
||||
ulUJCqo2h6NRaGYPfiLZIUHEJ7vQitRBRDpkxcX8XJX3Jm7weRPXLDOG8RN3FzPD
|
||||
TGL2ZDTWKDIQ20OhYEmENInKkpC2bYpwMzNvBA7AJN6MCYHXl4VP/Df0pPtMM/Di
|
||||
ZX+68ng3RQXT5VzDYURIH2wP5kcTL0irsNr+L7lHD+5mA1VErdQsh6AHWjrwMrSM
|
||||
egJpLP11rK4J+P/HHnqsBEi5XZI/hvRmwD7gwskRqXdDMHaWnoIIpPO3TeNYQUjD
|
||||
xaV5KUQuWVME5Ihuy03FPMChXXB0WF+tclPJxjQyDkwEVL+6d8i5yCPUBS2gBSBy
|
||||
pQQINFH4hOcx2e9Lujx7dOWfidjX3xssC9bEe60CAwEAAaOBmjCBlzAfBgNVHSME
|
||||
GDAWgBTS1B3CPFKA/HcI0cdE8YD4zG143DAMBgNVHRMBAf8EAjAAMBMGA1UdJQQM
|
||||
MAoGCCsGAQUFBwMBMA4GA1UdDwEB/wQEAwIFoDAiBgNVHREEGzAZghFkb2NrZXIu
|
||||
ZG9tYWluLmNvbYcEfwAAATAdBgNVHQ4EFgQUSanj6Cs4KVEKaK6/+VA/fNwNg4Ew
|
||||
DQYJKoZIhvcNAQELBQADggIBAKYtI1WKAL4FoSgH6sTZakw6h90uebrxm9ojeZTA
|
||||
k0ues8bGTu3w3dsphd9J0V27oz/dGjkwoIzy4QMYC4h6epKVadWfDhnHUPUT1JIC
|
||||
nGl7qFR539CSPzW+J1mVAGTZ1QONVxe6rFEDRXTsm9oUNq9LUB6a9EBO/9O0x2o7
|
||||
SZVUJd2WfMGAhqYjKCtMt+8kQgPxayok5IwWLBf03nluoF09Xu1WbY3f9wGNrzQp
|
||||
ulNlLzkU3f7+dVgF4lvIbr4MPWSQL2A0RYYjqWuwvUlXggtR+Nl6ldotDe7Ae18V
|
||||
KhQPJzM4muHjRY5dLkzQIIAQifxNprZCYiurUCAmOyOcHYMt5RuiVUPlB/2hoP/E
|
||||
tuFqq66v0qsE4mCfmJrRq+Yjfgcqsg1quRpjWh9DWOGa9HUeYFkLEKOgXybxVHJQ
|
||||
ktYba34ZFfBJUMcbZRYrRH6R4zu4LpRiyiXm29F5ml9tarThDZB5g5DJ6BTEt3Zw
|
||||
+qQHsIAcmHZvJPKEZmM6883gxbGQQ1Xt7iDrp94YRXMguBMbJwEsqI7w+25BHija
|
||||
Hp4gctdoBvkQBYpXoEsn8wnguofqJt/JhVgu0EQXR4j3U0uI+Oo9ODHFb5t2T26w
|
||||
EifwcLH+NyUNmUQH45lxaCzb+tqFlP7cbHsdPniaS4AtmBYwKNJMjcrgxnZPtfy1
|
||||
7zJA
|
||||
-----END CERTIFICATE-----
|
||||
@@ -1,51 +0,0 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIJKgIBAAKCAgEA1Hbm1tZvAeC8J54pQLHTVCtrICQ8KFTLOZakPHSWox8iQ4i2
|
||||
fkZaSccvE/51LIFnCM1yyZv88ILucqitG4zvuhDG+cU8w1vRbWf3xfGCCsHyn6LK
|
||||
BHR0Kk6+WRIBZTdRSsW8ZvpL2Y7eBNAUkC2oeaOJEOQ8D50b3u5jhAFmXuAcTiZh
|
||||
90Ve4JBKZV8dGs8L81vOvb7tqvJrEvCNKuZO7mEcjXkgiwUdP3pkZYa0tPOV5UrL
|
||||
H/oEvgPDJfXrNntCY1+A+CBQ7Sq3S2YpNJN7VnK6SboRi7xpOEgQOXwNVJWm/5YB
|
||||
vnbAztNXKcE2q2wREL9TulUJCqo2h6NRaGYPfiLZIUHEJ7vQitRBRDpkxcX8XJX3
|
||||
Jm7weRPXLDOG8RN3FzPDTGL2ZDTWKDIQ20OhYEmENInKkpC2bYpwMzNvBA7AJN6M
|
||||
CYHXl4VP/Df0pPtMM/DiZX+68ng3RQXT5VzDYURIH2wP5kcTL0irsNr+L7lHD+5m
|
||||
A1VErdQsh6AHWjrwMrSMegJpLP11rK4J+P/HHnqsBEi5XZI/hvRmwD7gwskRqXdD
|
||||
MHaWnoIIpPO3TeNYQUjDxaV5KUQuWVME5Ihuy03FPMChXXB0WF+tclPJxjQyDkwE
|
||||
VL+6d8i5yCPUBS2gBSBypQQINFH4hOcx2e9Lujx7dOWfidjX3xssC9bEe60CAwEA
|
||||
AQKCAgEAqT/6zePOVFGhsXG17Rp7fY6E7PrQjVRW/A470QkTQui3U9MhhWAn5qPs
|
||||
peHLl+ORn5qCOYawrSuwJdim5c6U3cUlrKzppbqMD7qFz8J+1HECBRcaFQhrzZQi
|
||||
4DOOtwGlGYqBdgsnxyyfQng8GUq17ghPVQxrqAiAvktrLSosUaH4Cm1bFy7E0OFA
|
||||
0pY9SjDrlTZqcA8bp1Ur5M+JtUX4VL85jp2SRgyR6xJlzdbMN2Xf3+OAAn4ZrwCy
|
||||
QZgwgpsYHK9kvsSHkxa3IzJD2uUtmIUWT0sRVR6HN1V4z0I6IEqC2RG3W/Gf0GLd
|
||||
CZ8oHNCem5e+bC33YO6NN+nrHN5Isb+itdbtE392P6FgPbM1um1zuuTTewaUyXS7
|
||||
ATomznTpYXkHvCdvU5yOEH+yDYfcm99v77qVr0+arecVx2h0M2tqROwEza3Rw5Wp
|
||||
928vyxPFde9HFHQG4SWRzCGfKpnvIT/ce2ayWHEDEbvzlwL9lokZqe0YYu5KDYTL
|
||||
j3DsnzMPiMn6bpQUIBXlO5+eAh94vatPCriNpEkHw3aYQNyux1BmyvKxflj6pg9u
|
||||
lxKWGng8YOW88ysXvXlAssjpDe+k/Cvaja3ZeV3pyRbMnK3ARHXi3mQ6wBsOVa6e
|
||||
zt3cpBgXik7m7u6a82FhLafP2UfFIpW6WRTA28ercpKtHRKJJgUCggEBAPWJsfPa
|
||||
4movMI6ofySMFm74u779aO5rGMQumR52vwlWyNDmOWNRrQaU/tpyaFOiPeIOGt2V
|
||||
UM02rdWHbnAhsgYP0cjUs34aJV/Z4nunw5Jc6rrT+dhd3+ZgqM1X/sLt5JfEavHe
|
||||
bV/cDV+xDp4FAXrLOPeRldvLPT1dmdQnirPGK3A1WWi5GQ+//Kas+lHuqECWYRrU
|
||||
LVFx0tR/pmdCC5Lb58kuPOxFP4OaeC3PbGyA2y0gv+QR/5Nc0o1y/X6p6XIM5QxY
|
||||
fg4gDKwSewrZ40+9taRNtgMQz3xKkeYmaNgLKnCBnLhDYdLZPAeYkOUIytzJbaYg
|
||||
oWHzmdd5FIKCgzcCggEBAN2Ecd7lJRIISuQop5GQ44mRrsDZTxBWnZ9pn3VFWNfA
|
||||
tF5MHofrtED7mXBSAt88TryqndcyC6qMvaS4Ifk1cNSFdz9FLsNzxJj4wp5Cj+e1
|
||||
aSY5ARvXTXqTfKZErQNXFk1oCa+ARZIs5SPjh0LH+Iq2pd72cEap7pi3JT5RbOtE
|
||||
ReDCAKayyejFMKtekzccishIwH/nzYlNaNBkmG7MZG8V+FFlxYSieIa7Ohcbmmj3
|
||||
D3ssbi+y+peRt14wZB/daScybcIqdu544ZIvJOpm0i9rHxE49bodmTDhAZ9awqls
|
||||
nRsyI0NNDExPDhCrzPogcRLVHn2KbcZbjGPS5jossjsCggEAazYchaXlhwfj4+ae
|
||||
3Y5tnTbug46S6sfIoKDYKv0enS1PsidUl5FqQ517Slb6RspoyvPttyMjjPd7H+lq
|
||||
x3tvCEaQC2kUltNDzn6M7gFq29XGiJ1WUqtqwGUkT8VEcEj/r2UMbV/50gl7rXTa
|
||||
NRVqd/uUfEUNclNkAg+Ew6YgYi79eJlS2O85ii8CWqTdCDl1Lf57mANdZlqU/ERg
|
||||
nGWyOAXdR3LxFxmFiilAoIAZj6cUDLhoEWXqeqXlKe4z0cLPNAV9Xc6l+/Tyk4/e
|
||||
Ofa50m+7iGqGNwB4GIVW/292CB+YAFgX3j1N0YsZMxfi7J7SNWWegxNsZCDB49vy
|
||||
oKnsMQKCAQEA0iurbnOyrF050SfRdQcnG4shZs/HeBT2EB3CsR1OocWwXBeUkBlO
|
||||
OKl+d1cYan1ppw+qGlbdQr+t3u7lLPFLUBghf+I/8CmSyiCbZlR4/LrePOmw551r
|
||||
YXU1uvtFu/mQq3ieV+k4GOyHq3lhCDd61QFedyESfbkVK8f4ihvvX3izZAAtZfwU
|
||||
HcmZ174vpwZploWQPsrL9A2B+Na42ccLM2qA45nPwXv1Jr/U6b/CzPw7r/4DvTXv
|
||||
FIeolrELDkCgWBQ8lxB7Lt96BZy9Rbiwi1TzcP++BQu4IOwbAfq23tCybu8vDde4
|
||||
Z15KVf7qyBansdqKx0njxWNu2/dpgKCPqQKCAQEAzWV4aajox7eeTi3iudP1Oxu3
|
||||
OBiu8xie4xq0mlM13tMxAQry/uOAuTxbaQ48mNsSXdeYeSgmT45lUvFWROqdVScK
|
||||
8gh04G1NiRAkITzXwCCwKkAQxvQppgypZ+aksBHkFQFBAIg2/mLizS4cicXNEY4G
|
||||
vb+RImfn8MSqSMLu3cJ8zgFyqfRg6F0oHg9EgEwvbnCLYglN6Xm5KlZutjW4eu7m
|
||||
Q+1y0lg05e7vFyj2UIVylfzT/aoF/xzXzNt9/LZs0klO3vMmaVyTjuF/71+AyphH
|
||||
FTmpNs1wpZU2IRqKOMrimIhk6TTxvMSaN4pxKdLPqgHYLWhwkbsdmqtTnCljRg==
|
||||
-----END RSA PRIVATE KEY-----
|
||||
@@ -82,9 +82,9 @@ RUN pwd # 输出 /app
|
||||
|
||||
```docker
|
||||
## 构建阶段
|
||||
## 建议使用 node:20 或 node: 等具体版本标签,避免使用 latest
|
||||
## 建议使用 node:22 或 node: 等具体版本标签,避免使用 latest
|
||||
|
||||
FROM node:20 AS builder
|
||||
FROM node:22 AS builder
|
||||
WORKDIR /build
|
||||
COPY package*.json ./
|
||||
RUN npm install
|
||||
@@ -105,8 +105,8 @@ COPY --from=builder /build/dist .
|
||||
#### 1. 尽早设置 WORKDIR
|
||||
|
||||
```docker
|
||||
# 建议使用 node:20 等主/次版本号标签
|
||||
FROM node:20
|
||||
# 建议使用 node:22 等主/次版本号标签
|
||||
FROM node:22
|
||||
WORKDIR /app # 尽早设置
|
||||
|
||||
COPY package*.json ./
|
||||
|
||||
@@ -35,7 +35,7 @@ flowchart LR
|
||||
#### 创建并切换用户
|
||||
|
||||
```docker
|
||||
FROM node:20-alpine
|
||||
FROM node:22-alpine
|
||||
|
||||
## 1. 创建用户和组
|
||||
|
||||
@@ -173,7 +173,7 @@ $ docker run -u root myimage
|
||||
切换用户后,确保应用有权访问文件:
|
||||
|
||||
```docker
|
||||
FROM node:20-alpine
|
||||
FROM node:22-alpine
|
||||
|
||||
## 创建用户
|
||||
|
||||
@@ -229,14 +229,14 @@ USER 1000:1000
|
||||
```docker
|
||||
## 构建阶段可以用 root
|
||||
|
||||
FROM node:20 AS builder
|
||||
FROM node:22 AS builder
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
RUN npm install && npm run build
|
||||
|
||||
## 生产阶段用非 root
|
||||
|
||||
FROM node:20-alpine
|
||||
FROM node:22-alpine
|
||||
RUN adduser -D appuser
|
||||
WORKDIR /app
|
||||
COPY --from=builder --chown=appuser:appuser /app/dist .
|
||||
|
||||
@@ -34,7 +34,7 @@ Starting ──成功──> Healthy ──失败N次──> Unhealthy
|
||||
#### Web 服务检查
|
||||
|
||||
```docker
|
||||
# 注:nginx 镜像推荐使用具体的版本标签(如 nginx:1.30-alpine)
|
||||
# 注:nginx 镜像推荐使用具体的版本标签(如 nginx:1.28-alpine)
|
||||
FROM nginx
|
||||
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ ONBUILD <其它指令>
|
||||
**基础镜像 (my-node-base)**:
|
||||
|
||||
```docker
|
||||
FROM node:20-alpine
|
||||
FROM node:22-alpine
|
||||
WORKDIR /app
|
||||
|
||||
## 这些指令将在子镜像构建时执行
|
||||
@@ -126,7 +126,7 @@ ONBUILD COPY dist/ /usr/share/nginx/html/
|
||||
建议在镜像标签中添加 `-onbuild` 后缀,明确告知使用者该镜像包含触发器。
|
||||
|
||||
```bash
|
||||
node:20-onbuild
|
||||
node:22-onbuild
|
||||
python:3.12-onbuild
|
||||
```
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ LABEL maintainer="user@example.com" \
|
||||
|
||||
```docker
|
||||
LABEL org.opencontainers.image.authors="yeasy" \
|
||||
org.opencontainers.image.documentation="https://yeasy.gitbooks.io" \
|
||||
org.opencontainers.image.documentation="https://yeasy.gitbook.io/docker_practice/" \
|
||||
org.opencontainers.image.source="https://github.com/yeasy/docker_practice" \
|
||||
org.opencontainers.image.licenses="MIT"
|
||||
```
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
### 官方文档
|
||||
|
||||
* `Dockerfile` 官方参考手册:https://docs.docker.com/engine/reference/builder/
|
||||
* `Dockerfile` 官方参考手册:https://docs.docker.com/reference/dockerfile/
|
||||
|
||||
* `Dockerfile` 最佳实践指南:https://docs.docker.com/develop/develop-images/dockerfile_best-practices/
|
||||
* `Dockerfile` 最佳实践指南:https://docs.docker.com/build/building/best-practices/
|
||||
|
||||
* `Docker` 官方镜像 `Dockerfile` 库:https://github.com/docker-library/docs
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
Dockerfile 中的常用指令包括:
|
||||
|
||||
- **FROM**: 指定基础镜像,必须是第一条指令
|
||||
- **FROM**: 指定基础镜像,必须是第一条构建指令(只有全局 `ARG`、注释和 parser 指令可以在它之前)
|
||||
- **RUN**: 在镜像中执行命令,用于安装软件包等
|
||||
- **COPY**: 复制文件到镜像中
|
||||
- **ADD**: 更高级的复制文件(支持 URL 和自动解压)
|
||||
|
||||
@@ -33,7 +33,7 @@ WORKDIR /go/src/github.com/go/helloworld/
|
||||
COPY app.go .
|
||||
|
||||
RUN go mod init helloworld \
|
||||
&& go get -d -v github.com/go-sql-driver/mysql \
|
||||
&& go get github.com/go-sql-driver/mysql \
|
||||
&& CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app . \
|
||||
&& cp /go/src/github.com/go/helloworld/app /root
|
||||
|
||||
@@ -62,7 +62,8 @@ WORKDIR /go/src/github.com/go/helloworld
|
||||
|
||||
COPY app.go .
|
||||
|
||||
RUN go get -d -v github.com/go-sql-driver/mysql \
|
||||
RUN go mod init helloworld \
|
||||
&& go get github.com/go-sql-driver/mysql \
|
||||
&& CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
|
||||
```
|
||||
编写 `Dockerfile.copy` 文件
|
||||
@@ -125,7 +126,8 @@ RUN apk --no-cache add git
|
||||
|
||||
WORKDIR /go/src/github.com/go/helloworld/
|
||||
|
||||
RUN go get -d -v github.com/go-sql-driver/mysql
|
||||
RUN go mod init helloworld \
|
||||
&& go get github.com/go-sql-driver/mysql
|
||||
|
||||
COPY app.go .
|
||||
|
||||
@@ -137,7 +139,7 @@ RUN apk --no-cache add ca-certificates
|
||||
|
||||
WORKDIR /root/
|
||||
|
||||
COPY --from=0 /go/src/github.com/go/helloworld/app .
|
||||
COPY --from=builder /go/src/github.com/go/helloworld/app .
|
||||
|
||||
CMD ["./app"]
|
||||
```
|
||||
@@ -158,6 +160,15 @@ go/helloworld 1 f55d3e16affc 2 minutes ago 295MB
|
||||
```
|
||||
很明显使用多阶段构建的镜像体积小,同时也完美解决了上边提到的问题。
|
||||
|
||||
> **Go Modules 最佳实践**:上述示例为简化演示在 Dockerfile 中临时执行 `go mod init`。在实际项目中,通常已在代码仓库中维护好 `go.mod` 和 `go.sum` 文件。推荐的 Dockerfile 写法是先拷贝这两个文件并执行 `go mod download` 以利用 Docker 层缓存,再拷贝源码并构建:
|
||||
>
|
||||
> ```docker
|
||||
> COPY go.mod go.sum ./
|
||||
> RUN go mod download
|
||||
> COPY . .
|
||||
> RUN go build -o app .
|
||||
> ```
|
||||
|
||||
### 7.17.4 只构建某一阶段的镜像
|
||||
|
||||
我们可以使用 `as` 来为某一阶段命名,例如
|
||||
@@ -173,8 +184,8 @@ $ docker build --target builder -t username/imagename:tag .
|
||||
|
||||
### 7.17.5 构建时从其他镜像复制文件
|
||||
|
||||
上面例子中我们使用 `COPY --from=0 /go/src/github.com/go/helloworld/app .` 从上一阶段的镜像中复制文件,我们也可以复制任意镜像中的文件。
|
||||
上面例子中我们使用 `COPY --from=builder /go/src/github.com/go/helloworld/app .` 通过已命名阶段(`as builder`)从上一阶段的镜像中复制文件(命名引用比 `--from=0` 这种位置索引更易读,也是当前官方推荐的最佳实践);我们也可以复制任意镜像中的文件。
|
||||
|
||||
```docker
|
||||
COPY --from=nginx:1.30-alpine /etc/nginx/nginx.conf /nginx.conf
|
||||
COPY --from=nginx:1.28-alpine /etc/nginx/nginx.conf /nginx.conf
|
||||
```
|
||||
|
||||
@@ -60,8 +60,8 @@ server {
|
||||
第一阶段进行前端构建。
|
||||
|
||||
```docker
|
||||
# 注:node 镜像推荐使用具体的版本标签(如 node:20-alpine)
|
||||
FROM node:20-alpine as frontend
|
||||
# 注:node 镜像推荐使用具体的版本标签(如 node:22-alpine)
|
||||
FROM node:22-alpine as frontend
|
||||
|
||||
COPY package.json /app/
|
||||
|
||||
@@ -121,15 +121,16 @@ RUN set -x ; cd ${LARAVEL_PATH} \
|
||||
&& mkdir -p storage/framework/testing \
|
||||
&& mkdir -p storage/framework/views \
|
||||
&& mkdir -p storage/logs \
|
||||
&& chmod -R 777 storage \
|
||||
&& chown -R www-data:www-data storage \
|
||||
&& chmod -R 775 storage \
|
||||
&& php artisan package:discover
|
||||
```
|
||||
|
||||
### 7.18.5 最后一个阶段构建 NGINX 镜像
|
||||
|
||||
```docker
|
||||
# 注:nginx 镜像推荐使用具体的版本标签(如 nginx:1.30-alpine)
|
||||
FROM nginx:1.30-alpine as nginx
|
||||
# 注:nginx 镜像推荐使用具体的版本标签(如 nginx:1.28-alpine)
|
||||
FROM nginx:1.28-alpine as nginx
|
||||
|
||||
ARG LARAVEL_PATH=/app/laravel
|
||||
|
||||
@@ -179,8 +180,8 @@ $ docker run -dit --rm --network=laravel -p 8080:80 my/nginx
|
||||
完整的 `Dockerfile` 文件如下。
|
||||
|
||||
```docker
|
||||
# 注:生产环境推荐使用具体的版本标签,如 node:20-alpine、composer:2.x、php:8.3-fpm-alpine、nginx:1.30-alpine
|
||||
FROM node:20-alpine as frontend
|
||||
# 注:生产环境推荐使用具体的版本标签,如 node:22-alpine、composer:2.x、php:8.3-fpm-alpine、nginx:1.28-alpine
|
||||
FROM node:22-alpine as frontend
|
||||
|
||||
COPY package.json /app/
|
||||
|
||||
@@ -226,10 +227,11 @@ RUN set -x ; cd ${LARAVEL_PATH} \
|
||||
&& mkdir -p storage/framework/testing \
|
||||
&& mkdir -p storage/framework/views \
|
||||
&& mkdir -p storage/logs \
|
||||
&& chmod -R 777 storage \
|
||||
&& chown -R www-data:www-data storage \
|
||||
&& chmod -R 775 storage \
|
||||
&& php artisan package:discover
|
||||
|
||||
FROM nginx:1.30-alpine as nginx
|
||||
FROM nginx:1.28-alpine as nginx
|
||||
|
||||
ARG LARAVEL_PATH=/app/laravel
|
||||
|
||||
|
||||
@@ -158,9 +158,11 @@ RUN --mount=type=cache,target=/go/pkg/mod \
|
||||
|
||||
```docker
|
||||
RUN --mount=type=secret,id=mysecret \
|
||||
cat /run/secrets/mysecret
|
||||
my-private-tool --token-file /run/secrets/mysecret
|
||||
```
|
||||
|
||||
不要在构建命令中 `cat`、`echo` 或打印密钥内容;应把 `/run/secrets/<id>` 路径交给真正消费密钥的工具。
|
||||
|
||||
#### 3. Heredoc 语法
|
||||
|
||||
BuildKit 支持使用 heredoc 语法编写多行脚本,无需行末反斜杠 `\` 连接:
|
||||
|
||||
@@ -178,7 +178,7 @@ ADD app.tar.gz /app/
|
||||
```docker
|
||||
## 构建阶段
|
||||
|
||||
FROM node:20 AS builder
|
||||
FROM node:22 AS builder
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
RUN npm install
|
||||
|
||||
+29
-28
@@ -4,12 +4,12 @@
|
||||
|
||||
在开始前,让我们直言不讳:**在大多数情况下,你应该使用 COPY,而不是 ADD**。
|
||||
|
||||
`ADD` 在 `COPY` 基础上增加了两个额外功能,但这些功能往往引入复杂性而非便利:
|
||||
`ADD` 在 `COPY` 基础上增加了两个额外功能。它不是 `COPY` 的通用替代品,但在少数场景中更合适:
|
||||
|
||||
1. 自动解压 tar 压缩包(有时你想复制一个 .tar.gz 本身,而 ADD 会意外地解压它)
|
||||
2. 支持从 URL 下载文件(这个功能由于网络不稳定已被广泛认为是反模式)
|
||||
2. 支持从 URL 下载公开远程文件,并可配合 `--checksum` 做校验
|
||||
|
||||
**实践中的建议**:除非你明确需要自动解压功能(比如官方基础镜像构建根文件系统),否则始终使用 COPY。原因很简单——显式优于隐式。你的 Dockerfile 在 6 个月后被接手维护时,清晰的意图会让团队少走很多弯路。
|
||||
**实践中的建议**:本地普通文件默认用 COPY;本地 tar 自动解压或公开远程 artifact 下载并校验时用 ADD;需要认证、请求头、重试或自定义解压流程时用 `RUN curl/wget`。
|
||||
|
||||
### 7.3.1 基本语法
|
||||
|
||||
@@ -20,7 +20,7 @@ ADD [选项] ["<源路径>", ... "<目标路径>"]
|
||||
`ADD` 在 `COPY` 基础上增加了两个功能:
|
||||
|
||||
1. 自动解压 tar 压缩包
|
||||
2. 支持从 URL 下载文件 (不推荐)
|
||||
2. 支持从 URL 下载文件
|
||||
|
||||
---
|
||||
|
||||
@@ -30,11 +30,11 @@ ADD [选项] ["<源路径>", ... "<目标路径>"]
|
||||
|------|------|-----|
|
||||
| 复制本地文件 | ✅ | ✅ |
|
||||
| 自动解压 tar | ❌ | ✅ |
|
||||
| 支持 URL | ❌ | ✅ (不推荐)|
|
||||
| 支持 URL | ❌ | ✅ (公开 artifact 可配合校验使用)|
|
||||
| 行为可预测性 | ✅ 高 | ⚠️ 低 |
|
||||
| 推荐程度 | ✅ **优先使用** | 仅解压场景 |
|
||||
| 推荐程度 | ✅ **普通复制优先使用** | 解压、本地 Git/公开远程 artifact |
|
||||
|
||||
> **笔者建议**:除非需要自动解压 tar 文件,否则始终使用 COPY。明确的行为比隐式的魔法更好。
|
||||
> **笔者建议**:普通复制始终优先 COPY;只有当你明确需要 ADD 的额外语义时再使用 ADD,并把意图写清楚。
|
||||
|
||||
---
|
||||
|
||||
@@ -79,7 +79,7 @@ app.tar.gz 包含: /app/ 目录结果:
|
||||
```
|
||||
---
|
||||
|
||||
### 7.3.4 URL 下载功能:不推荐
|
||||
### 7.3.4 URL 下载功能:谨慎使用
|
||||
|
||||
#### 基本用法
|
||||
|
||||
@@ -89,32 +89,29 @@ app.tar.gz 包含: /app/ 目录结果:
|
||||
ADD https://example.com/app.zip /app/app.zip
|
||||
```
|
||||
|
||||
#### 为什么不推荐
|
||||
#### 使用边界
|
||||
|
||||
| 问题 | 说明 |
|
||||
| 场景 | 建议 |
|
||||
|------|------|
|
||||
| 权限固定 | 下载的文件权限为 600,通常需要额外 RUN 修改 |
|
||||
| 不会解压 | URL 下载的压缩包不会自动解压 |
|
||||
| 缓存问题 | URL 内容变化时不会重新下载 |
|
||||
| 层数增加 | 需要额外 RUN 清理 |
|
||||
| 公开、版本固定的远程 artifact | 使用 `ADD --checksum=sha256:... URL dest` |
|
||||
| 需要认证、请求头或复杂重试 | 使用 `RUN curl/wget` |
|
||||
| 下载后需要复杂解压、校验或清理 | 使用 `RUN curl/wget`,把流程显式写出 |
|
||||
| URL 内容可变但无校验 | 不建议直接写入 Dockerfile |
|
||||
|
||||
#### 推荐替代方案
|
||||
#### 推荐写法
|
||||
|
||||
```docker
|
||||
## ❌ 不推荐:使用 ADD 下载
|
||||
## ✅ 公开 artifact:使用 ADD 并固定校验值
|
||||
|
||||
ADD https://example.com/app.tar.gz /tmp/
|
||||
ADD --checksum=sha256:<digest> https://example.com/app.tar.gz /tmp/app.tar.gz
|
||||
RUN tar -xzf /tmp/app.tar.gz -C /app && rm /tmp/app.tar.gz
|
||||
|
||||
## ✅ 推荐:使用 RUN + curl
|
||||
## ✅ 需要认证、请求头或特殊处理:使用 RUN + curl
|
||||
|
||||
RUN curl -fsSL https://example.com/app.tar.gz | tar -xz -C /app
|
||||
```
|
||||
优势:
|
||||
`ADD --checksum` 的优势是缓存更精确,并且校验值直接绑定到 Dockerfile。`RUN curl/wget` 的优势是控制力更强,适合企业内网、认证下载或复杂处理。
|
||||
|
||||
- 一条 RUN 完成下载、解压、清理
|
||||
- 减少镜像层数
|
||||
- 更清晰的构建意图
|
||||
|
||||
---
|
||||
|
||||
@@ -139,6 +136,10 @@ ADD rootfs.tar.gz /
|
||||
## 解压应用包
|
||||
|
||||
ADD dist.tar.gz /app/
|
||||
|
||||
## 下载公开 artifact 并校验
|
||||
|
||||
ADD --checksum=sha256:<digest> https://example.com/app.tar.gz /tmp/app.tar.gz
|
||||
```
|
||||
|
||||
#### ❌ 不适合使用 ADD
|
||||
@@ -149,9 +150,9 @@ ADD dist.tar.gz /app/
|
||||
ADD package.json /app/ # ❌
|
||||
COPY package.json /app/ # ✅
|
||||
|
||||
## 下载文件(用 RUN + curl)
|
||||
## 需要认证或复杂下载逻辑(用 RUN + curl/wget)
|
||||
|
||||
ADD https://example.com/file / # ❌
|
||||
ADD https://example.com/file / # ❌ 无法传认证信息,也没有显式处理
|
||||
RUN curl -fsSL ... -o /file # ✅
|
||||
|
||||
## 需要保留 tar 不解压(用 COPY)
|
||||
@@ -203,14 +204,14 @@ COPY . /app/
|
||||
ADD app.tar.gz /app/
|
||||
```
|
||||
|
||||
#### 3. 不要用 ADD 下载文件
|
||||
#### 3. 远程 artifact 使用 ADD 时必须固定校验值
|
||||
|
||||
```docker
|
||||
## ❌ 避免
|
||||
## ✅ 公开 artifact
|
||||
|
||||
ADD https://example.com/file.tar.gz /tmp/
|
||||
ADD --checksum=sha256:<digest> https://example.com/file.tar.gz /tmp/file.tar.gz
|
||||
|
||||
## ✅ 推荐
|
||||
## ✅ 认证下载或复杂处理
|
||||
|
||||
RUN curl -fsSL https://example.com/file.tar.gz | tar -xz -C /app
|
||||
```
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
|
||||
这是 Dockerfile 使用中最常见的困惑之一。简单的答案是:
|
||||
|
||||
- **CMD**:定义容器的”默认命令”。如果用户在 `docker run` 时提供命令,CMD 会被覆盖
|
||||
- **ENTRYPOINT**:定义容器的”入口脚本”。通常用于启动应用的某个特定部分
|
||||
- **CMD**:定义容器的“默认命令”。如果用户在 `docker run` 时提供命令,CMD 会被覆盖
|
||||
- **ENTRYPOINT**:定义容器的“入口脚本”。通常用于启动应用的某个特定部分
|
||||
|
||||
**决策树**:
|
||||
|
||||
@@ -31,9 +31,9 @@ CMD 有三种格式:
|
||||
|
||||
| 格式 | 语法 | 推荐程度 |
|
||||
|------|------|---------|
|
||||
| **exec 格式**| `CMD [“可执行文件”, “参数1”, “参数2”]` | ✅**推荐** |
|
||||
| **exec 格式**| `CMD ["可执行文件", "参数1", "参数2"]` | ✅**推荐** |
|
||||
| **shell 格式** | `CMD 命令 参数1 参数2` | ⚠️ 简单场景 |
|
||||
| **参数格式** | `CMD [“参数1”, “参数2”]` | 配合 ENTRYPOINT |
|
||||
| **参数格式** | `CMD ["参数1", "参数2"]` | 配合 ENTRYPOINT |
|
||||
|
||||
#### exec 格式:推荐
|
||||
|
||||
@@ -65,7 +65,7 @@ CMD echo $HOME
|
||||
|
||||
CMD ["sh", "-c", "echo $HOME"]
|
||||
```
|
||||
**优点**:可以使用环境变量、管道等 shell 特性
|
||||
**优点**:可以使用 `$HOME` 这类 shell 变量展开、管道等 shell 特性
|
||||
|
||||
**缺点**:主进程是 sh,信号无法正确传递给应用
|
||||
|
||||
@@ -77,7 +77,7 @@ CMD ["sh", "-c", "echo $HOME"]
|
||||
|------|----------|-----------|
|
||||
| 主进程 | 指定的程序 | `/bin/sh` |
|
||||
| 信号传递 | ✅ 正确 | ❌ 无法传递 |
|
||||
| 环境变量 | ❌ 需要 shell 包装 | ✅ 自动解析 |
|
||||
| `$VAR` shell 展开 | ❌ 不自动展开;环境变量仍会传入进程 | ✅ 自动展开 |
|
||||
| 推荐使用 | ✅ 大多数场景 | 需要 shell 特性时 |
|
||||
|
||||
#### 信号传递问题示例
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
|
||||
| 格式 | 语法 | 推荐程度 |
|
||||
|------|------|---------|
|
||||
| **exec 格式**| `ENTRYPOINT [“可执行文件”, “参数1”]` | ✅**推荐** |
|
||||
| **exec 格式**| `ENTRYPOINT ["可执行文件", "参数1"]` | ✅**推荐** |
|
||||
| **shell 格式** | `ENTRYPOINT 命令 参数` | ⚠️ 不推荐 |
|
||||
|
||||
```docker
|
||||
@@ -164,7 +164,7 @@ curl -s http://myip.ipip.net -i
|
||||
#### 实现方式
|
||||
|
||||
```docker
|
||||
# 建议使用 redis:7 或 redis:latest,具体版本号根据生产需求选择
|
||||
# 建议使用 redis:7 等具体版本标签,避免 latest,具体版本号根据生产需求选择
|
||||
FROM redis:7-alpine
|
||||
COPY docker-entrypoint.sh /usr/local/bin/
|
||||
ENTRYPOINT ["docker-entrypoint.sh"]
|
||||
|
||||
@@ -5,12 +5,16 @@
|
||||
```docker
|
||||
## 格式一:单个变量
|
||||
|
||||
ENV <key> <value>
|
||||
ENV <key>=<value>
|
||||
|
||||
## 格式二:多个变量(推荐)
|
||||
## 格式二:多个变量
|
||||
|
||||
ENV <key1>=<value1> <key2>=<value2> ...
|
||||
```
|
||||
> ⚠️ 用空格分隔的旧写法 `ENV <key> <value>`(`ARG` 同理)虽然仍能构建,但已被官方弃用。BuildKit 的
|
||||
> [LegacyKeyValueFormat](https://docs.docker.com/reference/build-checks/legacy-key-value-format/) 构建检查会报
|
||||
> `"ENV key=value" should be used instead of legacy "ENV key value" format`,`docker buildx build --check`(见 [10.2](../10_buildx/10.2_buildx.md))会直接把它标出来。一律用等号。
|
||||
|
||||
---
|
||||
|
||||
### 7.6.2 基本用法
|
||||
@@ -18,8 +22,8 @@ ENV <key1>=<value1> <key2>=<value2> ...
|
||||
#### 设置单个变量
|
||||
|
||||
```docker
|
||||
ENV NODE_VERSION 20
|
||||
ENV APP_ENV production
|
||||
ENV NODE_VERSION=20
|
||||
ENV APP_ENV=production
|
||||
```
|
||||
|
||||
#### 设置多个变量
|
||||
@@ -89,6 +93,8 @@ const dbUrl = process.env.DATABASE_URL;
|
||||
| `LABEL` | `LABEL version=$VERSION` |
|
||||
| `FROM` | `FROM node:$NODE_VERSION` |
|
||||
|
||||
> **注意**:`FROM` 是上表中的特例——它只能引用在第一条 `FROM` 之前用 `ARG` 声明的变量,**无法**使用 `ENV` 定义的环境变量(`ENV` 要进入构建阶段后才生效)。其余指令才能引用 `ENV` 环境变量。
|
||||
|
||||
---
|
||||
|
||||
### 7.6.5 运行时覆盖
|
||||
@@ -158,15 +164,15 @@ $ docker build --build-arg NODE_VERSION=18 -t myapp .
|
||||
```docker
|
||||
## ✅ 好:版本集中管理
|
||||
|
||||
ENV NGINX_VERSION=1.25 \
|
||||
NODE_VERSION=20 \
|
||||
ENV NGINX_VERSION=1.30 \
|
||||
NODE_VERSION=22 \
|
||||
PYTHON_VERSION=3.12
|
||||
|
||||
RUN apt-get install nginx=${NGINX_VERSION}
|
||||
|
||||
## ❌ 差:版本分散在各处
|
||||
|
||||
RUN apt-get install nginx=1.25
|
||||
RUN apt-get install nginx=1.30
|
||||
```
|
||||
|
||||
#### 2. 不要存储敏感信息
|
||||
@@ -210,7 +216,7 @@ ENV HOST=localhost \
|
||||
|
||||
#### Q:环境变量在 CMD 中不展开
|
||||
|
||||
exec 格式不会自动展开环境变量:
|
||||
exec 格式不会自动执行 shell 展开,因此命令参数里的 `$PORT` 会按字面值传给进程;但环境变量本身仍会注入进程环境,应用可以通过语言运行时读取。
|
||||
|
||||
```docker
|
||||
## ❌ 不会展开 $PORT
|
||||
|
||||
@@ -93,14 +93,14 @@ RUN echo "Node version: $NODE_VERSION"
|
||||
```docker
|
||||
ARG BASE_VERSION=alpine
|
||||
|
||||
FROM node:20-${BASE_VERSION} AS builder
|
||||
FROM node:22-${BASE_VERSION} AS builder
|
||||
|
||||
## 需要重新声明
|
||||
|
||||
ARG NODE_VERSION=20
|
||||
RUN echo "Building with Node $NODE_VERSION"
|
||||
|
||||
FROM node:20-${BASE_VERSION}
|
||||
FROM node:22-${BASE_VERSION}
|
||||
|
||||
## 每个阶段都需要重新声明
|
||||
|
||||
@@ -125,8 +125,8 @@ $ docker build --build-arg ALPINE_VERSION=3.19 .
|
||||
#### 2. 设置软件版本
|
||||
|
||||
```docker
|
||||
# 使用次版本号 (1.25) 而非完整版本号 (1.25.0),以便自动更新到最新补丁版本
|
||||
ARG NGINX_VERSION=1.25
|
||||
# 使用次版本号 (1.30) 而非完整版本号 (1.30.0),以便自动更新到最新补丁版本
|
||||
ARG NGINX_VERSION=1.30
|
||||
|
||||
RUN curl -fsSL https://nginx.org/download/nginx-${NGINX_VERSION}.tar.gz | tar -xz
|
||||
```
|
||||
@@ -146,17 +146,19 @@ RUN if [ "$ENABLE_DEBUG" = "true" ]; then \
|
||||
|
||||
#### 4. 配置私有仓库
|
||||
|
||||
```docker
|
||||
ARG NPM_TOKEN
|
||||
不要用 `ARG` 或 `ENV` 传递仓库 token。Docker 官方构建检查会把这类写法视为不安全,因为构建参数和环境变量可能进入镜像元数据或历史记录。使用 BuildKit secret mount 只在单条 `RUN` 指令期间暴露凭据:
|
||||
|
||||
RUN echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc && \
|
||||
```docker
|
||||
RUN --mount=type=secret,id=npm_token \
|
||||
NPM_TOKEN="$(cat /run/secrets/npm_token)" && \
|
||||
printf "//registry.npmjs.org/:_authToken=%s\n" "$NPM_TOKEN" > ~/.npmrc && \
|
||||
npm install && \
|
||||
rm ~/.npmrc
|
||||
```
|
||||
```bash
|
||||
## 构建时传入 token
|
||||
|
||||
$ docker build --build-arg NPM_TOKEN=xxx .
|
||||
$ docker build --secret id=npm_token,env=NPM_TOKEN .
|
||||
```
|
||||
---
|
||||
|
||||
|
||||
+11
-11
@@ -44,7 +44,7 @@ flowchart LR
|
||||
#### 定义单个卷
|
||||
|
||||
```docker
|
||||
FROM mysql:8.0
|
||||
FROM mysql:8.4
|
||||
VOLUME /var/lib/mysql
|
||||
```
|
||||
|
||||
@@ -63,7 +63,7 @@ VOLUME ["/data", "/logs", "/config"]
|
||||
如果运行时未指定挂载,Docker 会自动创建匿名卷:
|
||||
|
||||
```bash
|
||||
$ docker run mysql:8.0
|
||||
$ docker run mysql:8.4
|
||||
$ docker volume ls
|
||||
DRIVER VOLUME NAME
|
||||
local a1b2c3d4e5f6... # 自动创建的匿名卷
|
||||
@@ -74,7 +74,7 @@ local a1b2c3d4e5f6... # 自动创建的匿名卷
|
||||
```bash
|
||||
## 使用命名卷替代匿名卷
|
||||
|
||||
$ docker run -v mysql_data:/var/lib/mysql mysql:8.0
|
||||
$ docker run -v mysql_data:/var/lib/mysql mysql:8.4
|
||||
```
|
||||
|
||||
#### 3. 可被 Bind Mount 覆盖
|
||||
@@ -82,23 +82,23 @@ $ docker run -v mysql_data:/var/lib/mysql mysql:8.0
|
||||
```bash
|
||||
## 使用宿主机目录替代
|
||||
|
||||
$ docker run -v /my/data:/var/lib/mysql mysql:8.0
|
||||
$ docker run -v /my/data:/var/lib/mysql mysql:8.4
|
||||
```
|
||||
---
|
||||
|
||||
### 7.8.5 VOLUME 在构建时的特殊行为
|
||||
|
||||
> ⚠️ **重要**:VOLUME 之后对该目录的修改会被丢弃!
|
||||
> ⚠️ **重要**:`VOLUME` 之后再写入该目录的构建语义取决于 builder。legacy builder 会丢弃这些修改;BuildKit 会保留。但运行容器时,一旦该路径挂载了卷,卷会遮蔽镜像内同路径的内容。
|
||||
|
||||
```docker
|
||||
FROM ubuntu
|
||||
VOLUME /data
|
||||
|
||||
## ❌ 这个文件不会出现在镜像中!
|
||||
## ⚠️ legacy builder 会丢弃;BuildKit 会保留,但运行时挂载卷会遮蔽它
|
||||
|
||||
RUN echo "hello" > /data/test.txt
|
||||
```
|
||||
**原因**:在构建过程中,VOLUME 指令会为该目录创建一个临时的匿名卷。后续 RUN 指令对该目录的写入实际发生在这个临时卷中,而非镜像层。当该 RUN 指令结束后,临时卷被丢弃,因此写入的内容不会保存到最终镜像中。注意:这与容器运行时创建的匿名卷是不同的——运行时创建的卷会在容器生命周期内持续存在。
|
||||
**原因**:旧 builder 会在构建过程中为该目录创建临时匿名卷,后续写入发生在临时卷中;BuildKit 则会把修改保留在镜像层。为了避免不同 builder 下出现不同结果,也为了避免运行时卷遮蔽镜像内初始化数据,不要把必须存在的初始化文件写在 `VOLUME` 之后。
|
||||
|
||||
#### 正确做法
|
||||
|
||||
@@ -120,7 +120,7 @@ VOLUME /data
|
||||
#### 数据库持久化
|
||||
|
||||
```docker
|
||||
# 建议使用 postgres:16 或 postgres:latest,具体版本号根据数据库兼容性需求选择
|
||||
# 建议使用 postgres:16 等具体版本标签,避免 latest,具体版本号根据数据库兼容性需求选择
|
||||
FROM postgres:16
|
||||
VOLUME /var/lib/postgresql/data
|
||||
```
|
||||
@@ -145,7 +145,7 @@ VOLUME /app/uploads
|
||||
```bash
|
||||
## 查看镜像定义的 VOLUME
|
||||
|
||||
$ docker inspect mysql:8.0 --format '{{json .Config.Volumes}}' | jq
|
||||
$ docker inspect mysql:8.4 --format '{{json .Config.Volumes}}' | jq
|
||||
{
|
||||
"/var/lib/mysql": {}
|
||||
}
|
||||
@@ -196,7 +196,7 @@ volumes:
|
||||
```bash
|
||||
## 使用 --rm 运行的容器,匿名卷会在容器删除时一起删除
|
||||
|
||||
$ docker run --rm mysql:8.0
|
||||
$ docker run --rm mysql:8.4
|
||||
|
||||
## 容器停止后,数据丢失!
|
||||
|
||||
@@ -205,7 +205,7 @@ $ docker run --rm mysql:8.0
|
||||
**解决**:始终使用命名卷
|
||||
|
||||
```bash
|
||||
$ docker run -v mysql_data:/var/lib/mysql mysql:8.0
|
||||
$ docker run -v mysql_data:/var/lib/mysql mysql:8.4
|
||||
```
|
||||
---
|
||||
|
||||
|
||||
+10
-1
@@ -29,6 +29,15 @@ Dockerfile 是一个文本文件,其内包含了一条条的 **指令 (Instruc
|
||||
|
||||
Dockerfile 一般分为四部分:基础镜像信息、维护者信息、镜像操作指令和容器启动时执行指令。
|
||||
|
||||
可运行 `docker buildx build --check` 校验的[完整 Dockerfile 示例](../examples/validated/dockerfile/Dockerfile)与配套 `index.html` 保存在仓库中。它使用 `scratch`,因此结构检查不会解析远程基础镜像元数据。下面的围栏与该文件由测试强制保持一致。
|
||||
|
||||
<!-- canonical-example: dockerfile -->
|
||||
```dockerfile
|
||||
FROM scratch
|
||||
|
||||
COPY index.html /index.html
|
||||
```
|
||||
|
||||
### 指令详解
|
||||
|
||||
本章将详细讲解 Dockerfile 中的各个指令:
|
||||
@@ -80,7 +89,7 @@ docker build -t my-image:1.0 .
|
||||
本章中的 Dockerfile 示例使用的基础镜像标签遵循以下原则:
|
||||
|
||||
- **通用标签**(如 `ubuntu:24.04`、`alpine`、`nginx`):保持原样,无需修改
|
||||
- **基础镜像版本号**(如 `node:20`、`python:3.12`):使用主或次版本号而非完整版本号(patch),这样可以自动获取最新的补丁版本,确保获得安全更新
|
||||
- **基础镜像版本号**(如 `node:22`、`python:3.12`):使用主或次版本号而非完整版本号(patch),这样可以自动获取最新的补丁版本,确保获得安全更新
|
||||
- **避免**:不建议使用 `latest` 标签和完整的 patch 版本号(如 `20.10.0`)作为基础镜像,因为这会导致构建的不可重现性或安全风险
|
||||
|
||||
读者在使用这些示例时,应根据实际生产环境需求选择合适的版本号。
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM node:20-alpine as frontend
|
||||
FROM node:22-alpine as frontend
|
||||
|
||||
COPY package.json /app/
|
||||
|
||||
@@ -47,7 +47,7 @@ RUN set -x ; cd ${LARAVEL_PATH} \
|
||||
&& chmod -R 777 storage \
|
||||
&& php artisan package:discover
|
||||
|
||||
FROM nginx:1.25-alpine as nginx
|
||||
FROM nginx:1.30-alpine as nginx
|
||||
|
||||
ARG LARAVEL_PATH=/app/laravel
|
||||
|
||||
|
||||
@@ -4,15 +4,15 @@
|
||||
|
||||
| 指令 | 作用 | 关键要点 |
|
||||
|------|------|---------|
|
||||
| **FROM** | 指定基础镜像 | 必须是第一条指令 |
|
||||
| **FROM** | 指定基础镜像 | 必须是第一条构建指令;只有全局 `ARG` 能写在它之前 |
|
||||
| **RUN** | 在新层执行命令 | 合并命令、清理缓存以减小体积 |
|
||||
| **COPY** | 复制文件 | 优先使用,支持 `--from` |
|
||||
| **ADD** | 更高级的复制 | 自动解压 tar,不推荐用于下载 |
|
||||
| **ADD** | 更高级的复制 | 自动解压 tar;公开远程 artifact 应配合 `--checksum` |
|
||||
| **CMD** | 容器启动默认命令 | 可被 `docker run` 参数覆盖 |
|
||||
| **ENTRYPOINT** | 容器入口点 | 固定启动命令,CMD 作为默认参数 |
|
||||
| **ENV** | 设置环境变量 | 构建时 + 运行时均生效 |
|
||||
| **ARG** | 构建参数 | 仅构建时生效,FROM 后需重新声明 |
|
||||
| **VOLUME** | 定义匿名卷 | VOLUME 之后的修改会丢失 |
|
||||
| **VOLUME** | 定义匿名卷 | 运行时挂载会遮蔽镜像内目录;构建后续写入语义依赖 builder |
|
||||
| **EXPOSE** | 声明端口 | 仅文档作用,不自动映射 |
|
||||
| **WORKDIR** | 指定工作目录 | 替代 `RUN cd`,目录不存在会自动创建 |
|
||||
| **USER** | 指定运行用户 | 用户必须已存在,推荐 gosu |
|
||||
@@ -21,6 +21,21 @@
|
||||
| **LABEL** | 添加元数据 | 推荐 OCI 标准标签,替代 MAINTAINER |
|
||||
| **SHELL** | 更改默认 shell | 推荐 `["/bin/bash", "-o", "pipefail", "-c"]` |
|
||||
|
||||
### 生产镜像快速检查清单
|
||||
|
||||
在将镜像推向生产之前,建议逐条过一遍以下清单:
|
||||
|
||||
- [ ] 基础镜像选择了最小化版本(如 `alpine`、`distroless`)
|
||||
- [ ] 使用了[多阶段构建](7.17_multistage_builds.md),最终镜像不含编译工具链
|
||||
- [ ] 以非 root 用户运行(`USER` 指令)
|
||||
- [ ] `COPY` 优先于 `ADD`,且仅复制必要文件
|
||||
- [ ] `RUN` 指令合并了 `apt-get update && install && rm -rf /var/lib/apt/lists/*`
|
||||
- [ ] 设置了 `HEALTHCHECK`
|
||||
- [ ] 使用了 `.dockerignore` 排除 `.git`、`node_modules` 等无关文件
|
||||
- [ ] 镜像标签使用了具体版本号或 commit hash,而非 `latest`
|
||||
|
||||
> 更完整的编写指南见[附录:Dockerfile 最佳实践](../appendix/best_practices.md)。
|
||||
|
||||
### 延伸阅读
|
||||
|
||||
- [使用 Dockerfile 定制镜像](../04_image/4.5_build.md):Dockerfile 入门
|
||||
|
||||
@@ -122,6 +122,8 @@ $ docker run -d \
|
||||
|
||||
#### 场景四:共享 SSH 密钥
|
||||
|
||||
只读挂载可以防止容器修改主机密钥,但不能防止容器读取并外传密钥。只有在镜像完全可信、密钥作用域很窄且可随时轮换时,才考虑这种做法。更稳妥的方式是使用 SSH agent socket 转发、一次性 deploy key,或在 CI/构建场景中使用 BuildKit `--ssh`。
|
||||
|
||||
```bash
|
||||
## 挂载 SSH 密钥(只读)
|
||||
|
||||
@@ -238,7 +240,7 @@ $ docker run -u $(id -u):$(id -g) ...
|
||||
在 Docker Desktop 上,Bind Mount 性能通常不如 Volume,因为数据需要在宿主机文件系统和 Linux VM 之间同步:
|
||||
|
||||
```bash
|
||||
## 使用 :cached 或 :delegated 提高性能(macOS)
|
||||
## 使用 :cached 或 :delegated 提高性能(macOS,仅 Docker Desktop 4.5 及更早版本)
|
||||
|
||||
$ docker run -v /host/path:/container/path:cached myapp
|
||||
```
|
||||
@@ -248,6 +250,8 @@ $ docker run -v /host/path:/container/path:cached myapp
|
||||
| `:delegated` | 容器权威,宿主机读取可能延迟 |
|
||||
| `:consistent` | 默认,完全一致 (最慢)|
|
||||
|
||||
> **注意**:Docker Desktop 4.6+ 默认使用 VirtioFS 文件共享引擎,上述 `:cached`/`:delegated` 选项已被静默忽略。如需优化文件同步性能,请参考 Docker Desktop 的 [Synchronized file shares](https://docs.docker.com/desktop/features/synchronized-file-sharing/) 功能。
|
||||
|
||||
---
|
||||
|
||||
### 8.2.9 最佳实践
|
||||
@@ -257,7 +261,7 @@ $ docker run -v /host/path:/container/path:cached myapp
|
||||
```bash
|
||||
## 代码热更新
|
||||
|
||||
$ docker run -v $(pwd):/app -p 3000:3000 node npm run dev
|
||||
$ docker run -v "$(pwd)":/app -w /app -p 3000:3000 node:22 npm run dev
|
||||
```
|
||||
|
||||
#### 2. 生产环境使用 Volume
|
||||
|
||||
@@ -20,11 +20,12 @@ ghi789... none null local
|
||||
| **host** | 容器直接使用宿主机网络栈 | 需要最高网络性能时 |
|
||||
| **none** | 禁用网络 | 完全隔离的容器 |
|
||||
| **overlay** | 跨主机网络 | Docker Swarm 集群 |
|
||||
| **macvlan** | 容器拥有独立 MAC 地址 | 需要直接接入物理网络 |
|
||||
| **macvlan** | 容器拥有独立 MAC 地址 | Linux 主机上需要直接接入物理网络 |
|
||||
| **ipvlan** | 容器共享父接口 MAC,独享 IP | 同网段大量容器、对 MAC 数量受限的网络 |
|
||||
|
||||
### 9.2.2 Bridge 网络:默认
|
||||
|
||||
Bridge 是 Docker 默认使用的网络模式。Docker 启动时会自动创建 `docker0` 虚拟网桥,所有未指定网络的容器都会连接到这个网桥上。
|
||||
Bridge 是 Docker 默认使用的网络模式。在原生 Linux Docker Engine 上,Docker 启动时通常会创建 `docker0` 虚拟网桥,未指定网络的容器会连接到这个网桥上。Docker Desktop 运行在虚拟机内,宿主机上不会直接看到 `docker0`,也不能假设宿主机可直接访问每个容器 IP。
|
||||
|
||||
核心组件如下:
|
||||
|
||||
@@ -44,6 +45,8 @@ $ docker run -d --network host nginx
|
||||
```
|
||||
这种模式下网络性能最高,但容器之间和宿主机之间没有网络隔离。
|
||||
|
||||
> 注意:host 网络优先用于明确需要高性能或大量端口的 Linux 主机场景。Docker Desktop 仅在较新版本中支持,且需要在设置中启用;使用 host 网络时 `-p` / `--publish` 端口映射不会生效。普通 Web 服务默认仍建议使用 bridge 网络并显式发布端口。
|
||||
|
||||
### 9.2.4 None 网络
|
||||
|
||||
使用 `--network none` 参数启动的容器只有 `lo` 回环网卡,完全没有外部网络连接。适用于只需要运行计算任务、不需要网络的容器。
|
||||
@@ -54,7 +57,11 @@ $ docker run -it --network none alpine ip addr
|
||||
inet 127.0.0.1/8 scope host lo
|
||||
```
|
||||
|
||||
### 9.2.5 数据流向
|
||||
### 9.2.5 Macvlan 与平台限制
|
||||
|
||||
Macvlan 适合少数需要让容器像物理主机一样直接出现在二层网络中的场景。它只支持 Linux 主机,不支持 Docker Desktop for Mac / Windows,也不支持 rootless 模式;多数云厂商网络会阻断或限制 macvlan。默认情况下,macvlan 容器与宿主机直接通信也需要额外路由或接口配置。
|
||||
|
||||
### 9.2.6 数据流向
|
||||
|
||||
容器网络中的数据流向可以分为以下几种情况:
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
容器的网络访问规则如下:
|
||||
|
||||
- **容器之间**:可以通过 IP 或容器名 (自定义网络) 互通。
|
||||
- **宿主机访问容器**:可以通过容器 IP 访问。
|
||||
- **宿主机访问容器**:原生 Linux 环境下可按网络配置访问容器 IP;Docker Desktop 上不能依赖容器 IP,应通过端口映射、容器名(容器间)或 `host.docker.internal` 等机制访问。
|
||||
- **外部网络访问容器**:❌ 默认无法直接访问。
|
||||
|
||||
为了让外部 (如你的浏览器、其他局域网机器) 访问容器内的服务,我们需要将容器的端口 **映射** 到宿主机的端口。
|
||||
@@ -16,7 +16,7 @@
|
||||
flowchart TD
|
||||
User["外部用户 (Browser)"] --> Host["宿主机 (localhost:8080)"]
|
||||
Host --> Proxy["Docker Proxy<br/>端口映射 (8080 -> 80)"]
|
||||
Proxy --> Container["容器 (Class B: 80)"]
|
||||
Proxy --> Container["容器 (端口: 80)"]
|
||||
```
|
||||
---
|
||||
|
||||
@@ -39,10 +39,10 @@ $ docker run -d -p 8080:80 nginx
|
||||
|
||||
| 格式 | 含义 | 示例 |
|
||||
|------|------|------|
|
||||
| `ip:hostPort:containerPort` | 绑定指定 IP 的特定端口 | `-p 127.0.0.1:8080:80` (仅本机访问) |
|
||||
| `ip:hostPort:containerPort` | 绑定指定 IP 的特定端口 | `-p 127.0.0.1:8080:80` (仅 IPv4 本机访问) |
|
||||
| `ip::containerPort` | 绑定指定 IP 的随机端口 | `-p 127.0.0.1::80` |
|
||||
| `hostPort:containerPort` | 绑定所有 IP (0.0.0.0) 的特定端口 | `-p 8080:80` (默认) |
|
||||
| `containerPort` | 绑定所有 IP 的随机端口 | `-p 80` |
|
||||
| `hostPort:containerPort` | 绑定所有地址(通常包括 `0.0.0.0` 和 `[::]`)的特定端口 | `-p 8080:80` (默认) |
|
||||
| `containerPort` | 绑定所有地址的随机端口 | `-p 80` |
|
||||
|
||||
#### 2. 随机映射
|
||||
|
||||
@@ -93,14 +93,15 @@ abc123456 nginx 0.0.0.0:8080->80/tcp web
|
||||
|
||||
#### 1. 限制监听 IP
|
||||
|
||||
默认情况下,`-p 8080:80` 会监听 `0.0.0.0:8080`,这意味着任何人只要能连接你的宿主机 IP,就能访问该服务。
|
||||
默认情况下,`-p 8080:80` 会监听所有可用地址,常见输出包括 `0.0.0.0:8080` 和 `[::]:8080`。这意味着任何人只要能连接你的宿主机 IP,就能访问该服务。
|
||||
|
||||
如果不希望对外暴露 (例如数据库服务),应绑定到 `127.0.0.1`:
|
||||
如果不希望对外暴露 (例如数据库服务),应绑定到回环地址。IPv4 使用 `127.0.0.1`,IPv6 使用 `[::1]`:
|
||||
|
||||
```bash
|
||||
## 仅允许本机访问
|
||||
|
||||
$ docker run -d -p 127.0.0.1:3306:3306 mysql
|
||||
$ docker run -d -p '[::1]:3306:3306' mysql
|
||||
```
|
||||
|
||||
#### 2. 避免端口冲突
|
||||
|
||||
@@ -8,7 +8,7 @@ Overlay 网络在现有网络基础上建立虚拟网络,允许容器跨宿主
|
||||
|
||||
#### Overlay 网络工作原理
|
||||
|
||||
Overlay 网络通过隧道封装技术(通常是 VXLAN)将容器网络流量封装在宿主机物理网络的 UDP 数据包中传输。
|
||||
Overlay 网络通过隧道封装技术(通常是 VXLAN)将容器网络流量封装在宿主机物理网络的 UDP 数据包中传输。Docker overlay 网络默认使用 `4789/udp` 作为数据通道端口,同时 Swarm 控制面与节点通信还需要相应开放 `2377/tcp`、`7946/tcp` 和 `7946/udp`。
|
||||
|
||||
```text
|
||||
容器 A (192.168.0.2)
|
||||
|
||||
+17
-29
@@ -2,7 +2,7 @@
|
||||
|
||||
**BuildKit** 是下一代的镜像构建组件,在 [moby/buildkit](https://github.com/moby/buildkit) 开源。
|
||||
|
||||
> **重要**:自 Docker 23 起,BuildKit 已成为 **默认稳定构建器**,无需手动启用。Docker Engine 29 进一步将 Containerd 镜像存储设为默认,提升与 Kubernetes 的互操作性。
|
||||
> **重要**:自 Docker 23 起,BuildKit 已成为 **默认稳定构建器**,无需手动启用。Docker Engine 29 在新安装场景中进一步将 containerd image store 设为默认,提升多平台镜像、SBOM/Provenance 等 OCI 元数据能力。
|
||||
|
||||
目前,Docker Hub 自动构建已经支持 BuildKit,具体请参考 [docker-practice/docker-hub-buildx](https://github.com/docker-practice/docker-hub-buildx)。
|
||||
|
||||
@@ -15,7 +15,7 @@ BuildKit 引入了多项新指令,旨在优化构建缓存和安全性。以
|
||||
要使用最新的 Dockerfile 语法特性,建议在 Dockerfile 开头添加语法指令:
|
||||
|
||||
```docker
|
||||
## syntax=docker/dockerfile:1
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
```
|
||||
这将使用最新的稳定版语法解析器,确保你可以使用所有最新特性。
|
||||
@@ -46,12 +46,12 @@ COPY --from=builder /app/dist /app/dist
|
||||
```
|
||||
使用多阶段构建,构建的镜像中只包含了目标文件夹 `dist`,但仍然存在一些问题,当 `package.json` 文件变动时,`RUN npm i && rm -rf ~/.npm` 这一层会重新执行,变更多次后,生成了大量的中间层镜像。
|
||||
|
||||
为解决这个问题,进一步的我们可以设想一个类似 **数据卷** 的功能,在镜像构建时把 `node_modules` 文件夹挂载上去,在构建完成后,这个 `node_modules` 文件夹会自动卸载,实际的镜像中并不包含 `node_modules` 这个文件夹,这样我们就省去了每次获取依赖的时间,大大增加了镜像构建效率,同时也避免了生成了大量的中间层镜像。
|
||||
为解决这个问题,可以把包管理器的下载缓存挂载到构建步骤中,例如 npm 的 `/root/.npm`。注意:`type=cache` 只能作为性能优化,不能成为构建正确性的前提。缓存目录可能被并发构建改写,也可能被 GC 清理,所以不要把 `node_modules`、编译产物等必须存在的内容只放在 cache mount 里。
|
||||
|
||||
`BuildKit` 提供了 `RUN --mount=type=cache` 指令,可以实现上边的设想。
|
||||
|
||||
```docker
|
||||
## syntax=docker/dockerfile:1
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
FROM node:alpine as builder
|
||||
|
||||
@@ -59,35 +59,23 @@ WORKDIR /app
|
||||
|
||||
COPY package.json /app/
|
||||
|
||||
RUN --mount=type=cache,target=/app/node_modules,id=my_app_npm_module,sharing=locked \
|
||||
--mount=type=cache,target=/root/.npm,id=npm_cache \
|
||||
npm i --registry=https://registry.npmmirror.com
|
||||
RUN --mount=type=cache,target=/root/.npm,id=npm_cache \
|
||||
npm install --registry=https://registry.npmmirror.com
|
||||
|
||||
COPY src /app/src
|
||||
|
||||
RUN --mount=type=cache,target=/app/node_modules,id=my_app_npm_module,sharing=locked \
|
||||
|
||||
## --mount=type=cache,target=/app/dist,id=my_app_dist,sharing=locked \
|
||||
|
||||
npm run build
|
||||
RUN npm run build
|
||||
|
||||
FROM nginx:alpine
|
||||
|
||||
## COPY --from=builder /app/dist /app/dist
|
||||
|
||||
## 为了更直观的说明 from 和 source 指令,这里使用 RUN 指令
|
||||
|
||||
RUN --mount=type=cache,target=/tmp/dist,from=builder,source=/app/dist \
|
||||
# --mount=type=cache,target=/tmp/dist,from=my_app_dist,sharing=locked \
|
||||
|
||||
mkdir -p /app/dist && cp -r /tmp/dist/* /app/dist
|
||||
COPY --from=builder /app/dist /app/dist
|
||||
```
|
||||
|
||||
第一个 `RUN` 指令执行后,`id` 为 `my_app_npm_module` 的缓存文件夹挂载到了 `/app/node_modules` 文件夹中。多次执行也不会产生多个中间层镜像。
|
||||
第一个 `RUN` 指令执行后,`id` 为 `npm_cache` 的缓存文件夹挂载到了 `/root/.npm`,后续构建可复用下载缓存。
|
||||
|
||||
第二个 `RUN` 指令执行时需要用到 `node_modules` 文件夹,`node_modules` 已经挂载,命令也可以正确执行。
|
||||
第二个 `RUN` 指令执行时,`node_modules` 已经作为上一层真实写入到 builder 阶段的文件系统中,构建不依赖缓存目录内容。
|
||||
|
||||
第三个 `RUN` 指令将上一阶段产生的文件复制到指定位置,`from` 指明缓存的来源,这里 `builder` 表示缓存来源于构建的第一阶段,`source` 指明缓存来源的文件夹。
|
||||
最后使用 `COPY --from=builder` 将上一阶段产生的文件复制到最终镜像。跨阶段复制构建产物应使用 `COPY --from`;如果只是临时读取上一阶段文件,可使用 `RUN --mount=type=bind,from=builder,source=/app/dist,target=/tmp/dist,ro`。
|
||||
|
||||
上面的 `Dockerfile` 中 `--mount=type=cache,...` 中指令作用如下:
|
||||
|
||||
@@ -105,7 +93,7 @@ RUN --mount=type=cache,target=/tmp/dist,from=builder,source=/app/dist \
|
||||
该指令可以将一个镜像 (或上一构建阶段) 的文件挂载到指定位置。
|
||||
|
||||
```docker
|
||||
## syntax=docker/dockerfile:1
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
RUN --mount=type=bind,from=php:alpine,source=/usr/local/bin/docker-php-entrypoint,target=/docker-php-entrypoint \
|
||||
cat /docker-php-entrypoint
|
||||
@@ -116,7 +104,7 @@ RUN --mount=type=bind,from=php:alpine,source=/usr/local/bin/docker-php-entrypoin
|
||||
该指令可以将一个 `tmpfs` 文件系统挂载到指定位置。
|
||||
|
||||
```docker
|
||||
## syntax=docker/dockerfile:1
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
RUN --mount=type=tmpfs,target=/temp \
|
||||
mount | grep /temp
|
||||
@@ -127,10 +115,10 @@ RUN --mount=type=tmpfs,target=/temp \
|
||||
该指令可以将一个文件 (例如密钥) 挂载到指定位置。
|
||||
|
||||
```docker
|
||||
## syntax=docker/dockerfile:1
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
RUN --mount=type=secret,id=aws,target=/root/.aws/credentials \
|
||||
cat /root/.aws/credentials
|
||||
test -s /root/.aws/credentials && echo "credentials mounted"
|
||||
```
|
||||
```bash
|
||||
$ docker build -t test --secret id=aws,src=$HOME/.aws/credentials .
|
||||
@@ -141,7 +129,7 @@ $ docker build -t test --secret id=aws,src=$HOME/.aws/credentials .
|
||||
该指令可以挂载 `ssh` 密钥。
|
||||
|
||||
```docker
|
||||
## syntax=docker/dockerfile:1
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
FROM alpine
|
||||
RUN apk add --no-cache openssh-client
|
||||
@@ -163,4 +151,4 @@ Docker Compose 同样支持 BuildKit,这使得多服务应用的构建更加
|
||||
|
||||
### 10.1.3 官方文档
|
||||
|
||||
* https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/experimental.md
|
||||
* https://docs.docker.com/reference/dockerfile/
|
||||
|
||||
@@ -13,6 +13,17 @@ $ docker buildx build .
|
||||
```
|
||||
Buildx 使用 [BuildKit 引擎](10.1_buildkit.md)进行构建,支持许多新的功能,具体参考 [Buildkit](10.1_buildkit.md) 一节。
|
||||
|
||||
需要注意的是,默认 `docker` driver 会把构建结果加载到本地镜像存储;使用 `docker-container`、remote、cloud 等 builder 时,如未指定 `--load`、`--push` 或 `--output`,结果通常只保留在构建缓存中。
|
||||
|
||||
#### 构建前检查
|
||||
|
||||
Buildx 0.15 起支持构建检查:常规构建会默认检查 Dockerfile 与构建参数;如果只想做检查而不真正构建,可以使用 `--check`:
|
||||
|
||||
```bash
|
||||
$ docker buildx build --check .
|
||||
```
|
||||
这适合作为 CI 的快速门禁:普通构建中的检查告警默认不会让构建失败,但 `--check` 发现问题会以非零状态退出;需要把告警提升为错误时,可在 Dockerfile 顶部配合 `# check=error=true`。
|
||||
|
||||
#### 使用 `bake`
|
||||
|
||||
`docker buildx bake` 是一个高级构建命令,支持从 HCL、JSON 或 Compose 文件中定义构建目标,实现复杂的流水线构建。
|
||||
@@ -34,12 +45,12 @@ Buildx 支持在构建时直接生成 SBOM (Software Bill of Materials),这对
|
||||
```bash
|
||||
$ docker buildx build --sbom=true -t myimage .
|
||||
```
|
||||
该命令会在构建结果中包含 SPDX 或 CycloneDX 格式的 SBOM 数据。
|
||||
该命令会把 SBOM 作为构建 attestation 附加到构建结果中;BuildKit 默认使用 SPDX SBOM attestation。需要 CycloneDX 文件时,可使用 Syft、Docker Scout 等工具另行生成或转换。
|
||||
|
||||
> **⚠️ 注意与失败模式**:
|
||||
> 要使 SBOM (或其它 attestation 元数据) 成功附着并可见,对底层的存储格式有前置要求:默认的 classic image store 不支持 manifest list/index 这种存放 attestation 的结构。
|
||||
>
|
||||
> 如果只简单运行上述命令,你可能会面临 **”命令成功执行,但本地镜像中看不到 SBOM”** 的体会落差。
|
||||
> 如果只简单运行上述命令,你可能会面临 **“命令成功执行,但本地镜像中看不到 SBOM”** 的体会落差。
|
||||
>
|
||||
> **正确的解决路径有两条**:
|
||||
> 1. **推送到远端仓库**:使用 `docker buildx build --sbom=true --push -t myimage:tag` 时,SBOM 会正确保存到远端仓库。远端 OCI 兼容的镜像仓库能够完整存储这些元数据。
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
Docker Buildx 是一个 docker CLI 插件,其扩展了 docker 命令,支持 [Moby BuildKit](10.1_buildkit.md) 提供的功能。提供了与 docker build 相同的用户体验,并增加了许多新功能。
|
||||
|
||||
> Buildx 需要 Docker v19.03+ (Docker 19.03 及以上版本)。在较新版本中已更常用且功能更完整。
|
||||
> Buildx 需要 Docker v23.0+(该版本起 BuildKit 成为默认构建引擎)。推荐使用 Docker v28 及以上版本以获得最完整的 Buildx 功能支持。
|
||||
|
||||
## 本章内容
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ Docker Buildx 是 Docker 构建系统的重要进化,提供了高效、安全
|
||||
| **缓存挂载** | `RUN --mount=type=cache` 加速依赖安装 |
|
||||
| **Secret 挂载** | `RUN --mount=type=secret` 安全传递密钥 |
|
||||
| **buildx build** | 替代 `docker build`,支持更多构建功能 |
|
||||
| **构建检查** | `--check` 可在不执行构建的情况下检查 Dockerfile 与构建参数 |
|
||||
| **多架构构建** | `--platform` 参数一键构建多种架构镜像 |
|
||||
| **Manifest List** | 多架构镜像的索引文件 |
|
||||
| **SBOM** | 通过 `--sbom=true` 生成软件物料清单 |
|
||||
|
||||
@@ -1,17 +1,32 @@
|
||||
## 11.2 安装与卸载
|
||||
|
||||
`Compose` 是 Docker 官方的开源项目,负责实现对 Docker 容器集群的快速编排。
|
||||
`Compose` 是 Docker 官方的开源项目,负责实现本地或单机多容器应用的快速编排。跨主机集群编排应使用 Swarm、Kubernetes 或云厂商托管服务。
|
||||
|
||||
当前的 Compose 以 `docker compose` 子命令的形式提供。Docker Desktop 在 macOS、Windows 和 Linux 上默认包含它;如果你已经在 Linux 上单独安装了 Docker Engine 和 Docker CLI,也可以再安装 Compose CLI 插件。
|
||||
|
||||
### 11.2.1 Linux
|
||||
|
||||
在 Linux 上,你可以通过 Docker 官方发布页安装 Compose CLI 插件。把二进制文件保存到 `$DOCKER_CONFIG/cli-plugins/docker-compose`,并赋予执行权限即可。
|
||||
在 Linux 上,默认建议通过 Docker 官方软件仓库安装 Compose CLI 插件,这样可以随系统包管理器更新。
|
||||
|
||||
Ubuntu / Debian:
|
||||
|
||||
```bash
|
||||
$ sudo apt-get update
|
||||
$ sudo apt-get install docker-compose-plugin
|
||||
```
|
||||
|
||||
Fedora / CentOS / RHEL 兼容发行版:
|
||||
|
||||
```bash
|
||||
$ sudo dnf install docker-compose-plugin
|
||||
```
|
||||
|
||||
如果是离线环境、需要锁定特定版本,或包管理器暂不覆盖你的架构,可以从 Docker 官方发布页手工安装。手工安装不会自动更新;下载 URL 中的版本号和架构(如 `x86_64`、`aarch64`)需要按目标机器替换。
|
||||
|
||||
```bash
|
||||
$ DOCKER_CONFIG=${DOCKER_CONFIG:-$HOME/.docker}
|
||||
$ mkdir -p $DOCKER_CONFIG/cli-plugins
|
||||
$ curl -SL https://github.com/docker/compose/releases/latest/download/docker-compose-linux-x86_64 -o $DOCKER_CONFIG/cli-plugins/docker-compose
|
||||
$ curl -SL https://github.com/docker/compose/releases/download/v5.1.2/docker-compose-linux-x86_64 -o $DOCKER_CONFIG/cli-plugins/docker-compose
|
||||
$ chmod +x $DOCKER_CONFIG/cli-plugins/docker-compose
|
||||
```
|
||||
|
||||
@@ -19,13 +34,17 @@ $ chmod +x $DOCKER_CONFIG/cli-plugins/docker-compose
|
||||
|
||||
```bash
|
||||
$ docker compose version
|
||||
Docker Compose version v5.x
|
||||
Docker Compose version v5.x.x
|
||||
```
|
||||
|
||||
### 11.2.3 卸载
|
||||
|
||||
如果是二进制包方式安装的,删除二进制文件即可。
|
||||
如果是仓库方式安装,使用包管理器卸载;如果是二进制包方式安装,删除二进制文件即可。
|
||||
|
||||
```bash
|
||||
$ sudo apt-get remove docker-compose-plugin
|
||||
# 或
|
||||
$ sudo dnf remove docker-compose-plugin
|
||||
|
||||
$ rm $DOCKER_CONFIG/cli-plugins/docker-compose
|
||||
```
|
||||
|
||||
@@ -42,7 +42,7 @@ if __name__ == "__main__":
|
||||
|
||||
```docker
|
||||
FROM python:3.12-alpine
|
||||
ADD . /code
|
||||
COPY . /code
|
||||
WORKDIR /code
|
||||
RUN pip install redis flask
|
||||
CMD ["python", "app.py"]
|
||||
|
||||
@@ -46,7 +46,7 @@ docker compose [-f=<arg>...] [options] [COMMAND] [ARGS...]
|
||||
|
||||
* `-p, --project-name NAME` 指定项目名称,默认将使用所在目录名称作为项目名。
|
||||
|
||||
* `--verbose` 输出更多调试信息。
|
||||
* `--verbose` 输出更多调试信息。(Compose V2 中该选项仍然生效,但已从 `--help` 中隐藏,不再是推荐用法;请改用 `docker --log-level debug compose ...`。)
|
||||
|
||||
* `-v, --version` 打印版本并退出。
|
||||
|
||||
@@ -58,7 +58,7 @@ docker compose [-f=<arg>...] [options] [COMMAND] [ARGS...]
|
||||
|
||||
构建 (重新构建) 项目中的服务容器。
|
||||
|
||||
服务容器一旦构建后,将会带上一个标记名,例如对于 web 项目中的一个 db 容器,可能是 web_db。
|
||||
服务容器一旦构建后,将会带上一个标记名,例如对于 web 项目中的一个 db 服务,构建出的镜像是 `web-db`。(Compose V1 用下划线拼接为 `web_db`,V2 起统一改为连字符,只有加 `--compatibility` 才会回到下划线。)
|
||||
|
||||
可以随时在项目目录下运行 `docker compose build` 来重新构建服务。
|
||||
|
||||
@@ -228,7 +228,7 @@ $ docker compose run --no-deps web python manage.py shell
|
||||
|
||||
#### `scale`
|
||||
|
||||
在当前 Compose CLI 中,更稳妥的扩缩容写法是通过 `docker compose up --scale` 完成。
|
||||
当前 Compose CLI 仍支持 `docker compose scale`。实际使用中,更常见也更便于和创建/重建流程放在一起的写法是通过 `docker compose up --scale` 完成。
|
||||
|
||||
例如:
|
||||
|
||||
@@ -238,7 +238,7 @@ $ docker compose up -d --scale web=3 --scale db=2
|
||||
|
||||
将启动 3 个容器运行 `web` 服务,2 个容器运行 `db` 服务。
|
||||
|
||||
> **说明**:有些旧环境或实验性入口中仍可能出现 `docker compose scale`,但它不应再被视为当前 Compose 的通用稳定默认命令。
|
||||
> **说明**:如果 Compose 文件为服务指定了 `container_name`,该服务无法扩展到多个容器。需要扩缩容的服务应使用 Compose 自动生成的容器名,并通过服务名做 DNS 访问。
|
||||
|
||||
一般的,当指定数目多于该服务当前实际运行容器,将新创建并启动容器;反之,将停止容器。
|
||||
|
||||
|
||||
@@ -4,14 +4,18 @@
|
||||
|
||||
默认的模板文件名称为 `compose.yaml` (也兼容 `docker-compose.yml` 等历史文件名),格式为 YAML。
|
||||
|
||||
本书同时维护一份可直接运行 `docker compose config` 校验的[完整 Compose 示例](../examples/validated/compose/compose.yaml)。下面的围栏与该文件由测试强制保持一致;需要复制完整文件时,以任一处为准。
|
||||
|
||||
<!-- canonical-example: compose -->
|
||||
```yaml
|
||||
name: docker-practice
|
||||
|
||||
services:
|
||||
webapp:
|
||||
image: examples/web
|
||||
web:
|
||||
image: nginx:1.28-alpine
|
||||
ports:
|
||||
- "80:80"
|
||||
volumes:
|
||||
- "/data"
|
||||
- "8080:80"
|
||||
restart: unless-stopped
|
||||
```
|
||||
注意每个服务都必须通过 `image` 指令指定镜像或 `build` 指令 (需要 Dockerfile) 等来自动构建生成镜像。
|
||||
|
||||
@@ -55,7 +59,7 @@ build:
|
||||
|
||||
### 11.5.2 `cap_add, cap_drop`
|
||||
|
||||
指定容器的内核能力 (capacity) 分配。
|
||||
指定容器的内核能力 (capability) 分配。
|
||||
|
||||
例如,让容器拥有所有能力可以指定为:
|
||||
|
||||
@@ -150,7 +154,7 @@ services:
|
||||
redis:
|
||||
image: redis
|
||||
healthcheck:
|
||||
test: [“CMD”, “redis-cli”, “ping”]
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 5
|
||||
@@ -158,7 +162,7 @@ services:
|
||||
db:
|
||||
image: postgres
|
||||
healthcheck:
|
||||
test: [“CMD-SHELL”, “pg_isready -U postgres”]
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 5
|
||||
@@ -338,7 +342,7 @@ logging:
|
||||
options:
|
||||
syslog-address: "tcp://192.168.0.42:123"
|
||||
```
|
||||
目前支持三种日志驱动类型。
|
||||
Docker 支持十余种日志驱动(`json-file`、`local`、`syslog`、`journald`、`gelf`、`fluentd`、`awslogs`、`splunk`、`gcplogs`、`none` 等,默认为 `json-file`),完整清单见[官方文档](https://docs.docker.com/engine/logging/configure/)。下面是几个常见取值:
|
||||
|
||||
```yaml
|
||||
driver: "json-file"
|
||||
@@ -411,14 +415,13 @@ ports:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
|
||||
mysql:
|
||||
image: mysql
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_root_password
|
||||
secrets:
|
||||
- db_root_password
|
||||
- my_other_secret
|
||||
mysql:
|
||||
image: mysql
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_root_password
|
||||
secrets:
|
||||
- db_root_password
|
||||
- my_other_secret
|
||||
|
||||
secrets:
|
||||
db_root_password:
|
||||
@@ -490,7 +493,7 @@ volumes:
|
||||
```yaml
|
||||
services:
|
||||
my_src:
|
||||
image: mysql:8.0
|
||||
image: mysql:8.4
|
||||
volumes:
|
||||
- mysql_data:/var/lib/mysql
|
||||
|
||||
@@ -524,7 +527,7 @@ domainname: your_website.com
|
||||
hostname: test
|
||||
mac_address: 08-00-27-00-0C-0A
|
||||
```
|
||||
允许容器中运行一些特权命令。
|
||||
允许容器中运行一些特权命令。`privileged: true` 会显著扩大容器对宿主机设备和 Linux capability 的访问范围,生产环境应优先使用更窄的 `cap_add`、`devices` 或只读挂载;更多说明见 [内核能力机制](../18_security/18.4_kernel_capability.md)。
|
||||
|
||||
```yaml
|
||||
privileged: true
|
||||
@@ -583,6 +586,14 @@ $ docker compose --profile debug up # 启动 web + debug
|
||||
$ docker compose up # 仅启动 web
|
||||
```
|
||||
|
||||
也可以通过环境变量激活:
|
||||
|
||||
```bash
|
||||
$ COMPOSE_PROFILES=debug,test docker compose up
|
||||
```
|
||||
|
||||
如果在命令行里显式指定某个带 profile 的服务,Compose 会自动启动这个目标服务及其声明的依赖;但不会自动启动同一 profile 下的其它服务,除非也显式指定或通过 `--profile` / `COMPOSE_PROFILES` 启用。
|
||||
|
||||
### 11.5.35 读取变量
|
||||
|
||||
Compose 模板文件支持动态读取主机的系统环境变量和当前目录下的 `.env` 文件中的变量。
|
||||
|
||||
@@ -116,7 +116,7 @@ services:
|
||||
environment:
|
||||
POSTGRES_DB: django_db
|
||||
POSTGRES_USER: django_user
|
||||
POSTGRES_PASSWORD: django_password
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
@@ -136,7 +136,9 @@ services:
|
||||
db:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
DATABASE_URL: postgres://django_user:django_password@db:5432/django_db
|
||||
# settings.py 直接读取 POSTGRES_PASSWORD,必须传入 web 容器
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}
|
||||
DATABASE_URL: postgres://django_user:${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}@db:5432/django_db
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
@@ -153,7 +155,7 @@ db:
|
||||
environment:
|
||||
POSTGRES_DB: django_db # 创建的数据库名
|
||||
POSTGRES_USER: django_user # 数据库用户
|
||||
POSTGRES_PASSWORD: django_password # 数据库密码
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD} # 数据库密码经环境变量注入,不写明文
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data # 持久化数据
|
||||
healthcheck: # 健康检查,确保数据库就绪
|
||||
@@ -229,7 +231,7 @@ DATABASES = {
|
||||
'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'),
|
||||
'PASSWORD': os.environ['POSTGRES_PASSWORD'],
|
||||
'HOST': 'db', # Docker Compose 服务名
|
||||
'PORT': 5432,
|
||||
}
|
||||
@@ -239,6 +241,9 @@ DATABASES = {
|
||||
|
||||
ALLOWED_HOSTS = ['*']
|
||||
```
|
||||
|
||||
生产环境优先使用 Docker Compose secrets 或外部密钥管理;即使是本地示例,也不要把固定密码写进 Compose 文件或 Django 默认值中。
|
||||
|
||||
**为什么 HOST 是 `db` 而不是 `localhost`?**
|
||||
|
||||
在 Docker Compose 中,各服务通过服务名相互访问。Docker 内置的 DNS 会将 `db` 解析为 db 服务容器的 IP 地址。这是 Docker Compose 的核心功能之一。
|
||||
@@ -328,7 +333,7 @@ $ sudo chown -R $USER:$USER .
|
||||
|--------|---------|---------|
|
||||
| **Web 服务器** | `runserver` | `gunicorn` + Nginx |
|
||||
| **DEBUG** | `True` | `False` |
|
||||
| **密码管理** | 明文写在配置 | 使用 Docker Secrets 或环境变量 |
|
||||
| **密码管理** | 环境变量注入(如本节 `${POSTGRES_PASSWORD:?}`) | 使用 Docker Secrets 或外部密钥管理 |
|
||||
| **Volume** | 挂载代码目录 | 代码直接 COPY 进镜像 |
|
||||
| **ALLOWED_HOSTS** | `['*']` | 具体域名 |
|
||||
|
||||
|
||||
@@ -96,16 +96,21 @@ $ touch Gemfile.lock
|
||||
|
||||
### 11.7.5 步骤 3:创建 compose.yaml
|
||||
|
||||
配置如下:
|
||||
配置如下。下面为了本地练习使用环境变量占位;不要把真实数据库密码写进 `compose.yaml`、`.env` 或 Git,生产环境应使用 Compose secrets、Rails credentials 或平台密钥管理。
|
||||
|
||||
```yaml
|
||||
services:
|
||||
db:
|
||||
image: postgres:16
|
||||
environment:
|
||||
POSTGRES_PASSWORD: password
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
web:
|
||||
build: .
|
||||
@@ -115,9 +120,10 @@ services:
|
||||
ports:
|
||||
- "3000:3000"
|
||||
depends_on:
|
||||
- db
|
||||
db:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
DATABASE_URL: postgres://postgres:password@db:5432/myapp_development
|
||||
DATABASE_URL: postgres://postgres:${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}@db:5432/myapp_development
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
@@ -128,7 +134,7 @@ volumes:
|
||||
|--------|------|
|
||||
| `rm -f tmp/pids/server.pid` | 清理上次异常退出留下的 PID 文件 |
|
||||
| `volumes: .:/myapp` | 挂载代码目录,支持热更新 |
|
||||
| `depends_on: db` | 确保数据库先启动 |
|
||||
| `depends_on` + `condition` | 等待数据库健康检查通过后再启动 |
|
||||
| `DATABASE_URL` | Rails 12-factor 风格的数据库配置 |
|
||||
|
||||
### 11.7.6 步骤 4:生成 Rails 项目
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
## 11.8 实战 WordPress
|
||||
|
||||
> **版本说明**:本示例使用以下镜像版本:
|
||||
> - MySQL:8.0(可替换为其他 8.x 版本,或使用 MariaDB 替代)
|
||||
> - MySQL:8.4(可替换为其他 8.x 版本,或使用 MariaDB 替代)
|
||||
> - WordPress:latest(建议在生产环境指定具体版本,如 6.x)
|
||||
|
||||
WordPress 是全球最流行的内容管理系统 (CMS)。使用 Docker Compose 可以在几分钟内搭建一个包含数据库、Web 服务和持久化存储的生产级 WordPress 环境。
|
||||
WordPress 是全球最流行的内容管理系统 (CMS)。使用 Docker Compose 可以在几分钟内搭建一个包含数据库、Web 服务和持久化存储的本地练习/单机演示环境。
|
||||
|
||||
---
|
||||
|
||||
@@ -13,7 +13,10 @@ WordPress 是全球最流行的内容管理系统 (CMS)。使用 Docker Compose
|
||||
```bash
|
||||
wordpress/
|
||||
├── compose.yaml
|
||||
├── .env # 环境变量(敏感信息)
|
||||
├── .env # 非敏感环境变量(如版本、端口)
|
||||
├── secrets/ # 本地密钥文件,生产环境应由密钥管理系统提供
|
||||
│ ├── db_root_password.txt
|
||||
│ └── db_password.txt
|
||||
└── nginx/ # 可选:反向代理配置
|
||||
└── nginx.conf
|
||||
```
|
||||
@@ -21,27 +24,30 @@ wordpress/
|
||||
|
||||
### 11.8.2 编写 `compose.yaml`
|
||||
|
||||
这是一个生产可用的最小化配置:
|
||||
这是一个可运行的单机最小配置,不是完整生产安全基线。正式部署前应固定镜像版本、放在反向代理/TLS 后面、限制公开端口、配置备份与监控,并按 WordPress 与插件生命周期做升级测试。
|
||||
|
||||
```yaml
|
||||
services:
|
||||
# 数据库服务
|
||||
|
||||
db:
|
||||
image: mysql:8.0
|
||||
image: mysql:8.4
|
||||
container_name: wordpress_db
|
||||
restart: always
|
||||
command:
|
||||
# 使用原生密码认证(旧版 WP 兼容性)
|
||||
# 启用原生密码认证(MySQL 8.4 默认禁用,旧版 WP 兼容性需要)
|
||||
|
||||
- --default-authentication-plugin=mysql_native_password
|
||||
- --mysql-native-password=ON
|
||||
- --character-set-server=utf8mb4
|
||||
- --collation-server=utf8mb4_unicode_ci
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
|
||||
MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_root_password
|
||||
MYSQL_DATABASE: wordpress
|
||||
MYSQL_USER: wordpress
|
||||
MYSQL_PASSWORD: ${DB_PASSWORD}
|
||||
MYSQL_PASSWORD_FILE: /run/secrets/db_password
|
||||
secrets:
|
||||
- db_root_password
|
||||
- db_password
|
||||
volumes:
|
||||
- db_data:/var/lib/mysql
|
||||
networks:
|
||||
@@ -50,16 +56,20 @@ services:
|
||||
# WordPress 服务
|
||||
|
||||
wordpress:
|
||||
# 示例保留 latest 以便读者快速体验;生产环境请固定到经过测试的明确版本标签
|
||||
image: wordpress:latest
|
||||
container_name: wordpress_app
|
||||
restart: always
|
||||
ports:
|
||||
- "8000:80"
|
||||
# 本机调试入口;生产环境请通过反向代理发布 HTTPS
|
||||
- "127.0.0.1:8000:80"
|
||||
environment:
|
||||
WORDPRESS_DB_HOST: db:3306
|
||||
WORDPRESS_DB_USER: wordpress
|
||||
WORDPRESS_DB_PASSWORD: ${DB_PASSWORD}
|
||||
WORDPRESS_DB_PASSWORD_FILE: /run/secrets/db_password
|
||||
WORDPRESS_DB_NAME: wordpress
|
||||
secrets:
|
||||
- db_password
|
||||
volumes:
|
||||
- wp_data:/var/www/html
|
||||
# 增加上传文件大小限制
|
||||
@@ -76,20 +86,29 @@ volumes:
|
||||
|
||||
networks:
|
||||
wp_net:
|
||||
|
||||
secrets:
|
||||
db_root_password:
|
||||
file: ./secrets/db_root_password.txt
|
||||
db_password:
|
||||
file: ./secrets/db_password.txt
|
||||
```
|
||||
---
|
||||
|
||||
### 11.8.3 配置文件详解
|
||||
|
||||
#### 1. 环境变量文件 .env
|
||||
#### 1. 密钥文件
|
||||
|
||||
为了安全,不要在 `compose.yaml` 中直接写密码。创建 `.env` 文件:
|
||||
不要把数据库密码写入 `compose.yaml`、`.env`、命令行或 Git。Compose 官方建议敏感值使用 secrets;本地练习可用只读密钥文件模拟:
|
||||
|
||||
```ini
|
||||
DB_ROOT_PASSWORD=somestrongrootpassword
|
||||
DB_PASSWORD=somestronguserpassword
|
||||
```bash
|
||||
mkdir -p secrets
|
||||
printf '%s\n' 'somestrongrootpassword' > secrets/db_root_password.txt
|
||||
printf '%s\n' 'somestronguserpassword' > secrets/db_password.txt
|
||||
chmod 600 secrets/*.txt
|
||||
```
|
||||
Compose 会自动读取此同级目录下的文件。
|
||||
|
||||
把 `secrets/` 加入 `.gitignore`。生产环境应改用平台密钥管理能力,而不是把真实密码放在项目目录。
|
||||
|
||||
#### 2. 数据持久化
|
||||
|
||||
@@ -138,7 +157,7 @@ $ docker compose logs -f
|
||||
```bash
|
||||
## 导出 SQL
|
||||
|
||||
$ docker exec wordpress_db mysqldump -u wordpress -pwordpress wordpress > backup.sql
|
||||
$ docker compose exec -T db sh -c 'tmp=$(mktemp) && printf "[client]\nuser=wordpress\npassword=%s\n" "$(cat /run/secrets/db_password)" > "$tmp" && mysqldump --defaults-extra-file="$tmp" wordpress; rc=$?; rm -f "$tmp"; exit "$rc"' > backup.sql
|
||||
```
|
||||
或者添加一个自动备份容器:
|
||||
|
||||
@@ -148,12 +167,15 @@ $ docker exec wordpress_db mysqldump -u wordpress -pwordpress wordpress > 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 # 每天备份一次
|
||||
# tiredofit/db-backup 4.x 起按 DB01_ 前缀配置备份任务,并原生支持 _FILE 读密
|
||||
- DB01_TYPE=mysql
|
||||
- DB01_HOST=db
|
||||
- DB01_NAME=wordpress
|
||||
- DB01_USER=wordpress
|
||||
- DB01_PASS_FILE=/run/secrets/db_password
|
||||
- DB01_BACKUP_INTERVAL=1440 # 每天备份一次(单位:分钟)
|
||||
secrets:
|
||||
- db_password
|
||||
depends_on:
|
||||
- db
|
||||
networks:
|
||||
@@ -190,9 +212,9 @@ WordPress 支持 Redis 缓存以提高性能。
|
||||
**现象**:访问页面显示 “Error establishing a database connection”。**排查**:
|
||||
|
||||
1. 检查 `docker compose logs wordpress`
|
||||
2. 确认 `.env` 中的密码与 YAML 文件引用一致
|
||||
2. 确认 `secrets/db_password.txt` 的内容正确,且与数据库初始化时使用的密码一致(改密码后需要重建 db 数据卷)
|
||||
3. 确认 `WORDPRESS_DB_HOST` 也是 `db` (服务名)
|
||||
4. MySQL 8.0 可能需要几秒钟启动,WordPress 会自动重试,稍等片刻即可。
|
||||
4. MySQL 8.4 可能需要几秒钟启动,WordPress 会自动重试,稍等片刻即可。
|
||||
|
||||
#### Q:无法上传大文件
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 第十一章 Docker Compose
|
||||
|
||||
`Docker Compose` 是 Docker 官方编排 (Orchestration) 项目之一,负责快速的部署分布式应用。
|
||||
`Docker Compose` 是 Docker 官方编排 (Orchestration) 项目之一,负责快速定义和启动本地或单机多容器应用。跨主机集群编排应交给 Swarm、Kubernetes 或云厂商托管服务。
|
||||
|
||||
> ⚠️ **重要提示:Compose V1 已停止支持**
|
||||
>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
FROM python:3.12-alpine
|
||||
ADD . /code
|
||||
COPY . /code
|
||||
WORKDIR /code
|
||||
RUN pip install redis flask
|
||||
CMD ["python", "app.py"]
|
||||
|
||||
@@ -2,6 +2,6 @@ FROM python:3
|
||||
ENV PYTHONUNBUFFERED 1
|
||||
RUN mkdir /code
|
||||
WORKDIR /code
|
||||
ADD requirements.txt /code/
|
||||
COPY requirements.txt /code/
|
||||
RUN pip install -r requirements.txt
|
||||
ADD . /code/
|
||||
COPY . /code/
|
||||
|
||||
@@ -2,9 +2,18 @@
|
||||
services:
|
||||
|
||||
db:
|
||||
image: postgres
|
||||
image: postgres:16
|
||||
environment:
|
||||
POSTGRES_PASSWORD: 'postgres'
|
||||
POSTGRES_DB: django_db
|
||||
POSTGRES_USER: django_user
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_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: .
|
||||
@@ -13,3 +22,13 @@ services:
|
||||
- .:/code
|
||||
ports:
|
||||
- "8000:8000"
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
# 与书中 11.6 节一致:settings.py 从环境变量读取数据库密码
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}
|
||||
DATABASE_URL: postgres://django_user:${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}@db:5432/django_db
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# WordPress Compose 示例
|
||||
|
||||
本示例使用 Docker Compose secrets 管理数据库密码,启动前需要先创建密钥文件(参见书中 11.8 节):
|
||||
|
||||
```bash
|
||||
mkdir -p secrets
|
||||
printf '%s\n' 'somestrongrootpassword' > secrets/db_root_password.txt
|
||||
printf '%s\n' 'somestronguserpassword' > secrets/db_password.txt
|
||||
chmod 600 secrets/*.txt
|
||||
```
|
||||
|
||||
然后启动:
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
注意:`secrets/` 目录不要提交到版本库;生产环境应改用平台的密钥管理能力。
|
||||
@@ -2,30 +2,41 @@
|
||||
services:
|
||||
|
||||
db:
|
||||
image: mysql:8.0
|
||||
image: mysql:8.4
|
||||
command:
|
||||
- --default_authentication_plugin=mysql_native_password
|
||||
- --mysql-native-password=ON
|
||||
- --character-set-server=utf8mb4
|
||||
- --collation-server=utf8mb4_unicode_ci
|
||||
volumes:
|
||||
- db_data:/var/lib/mysql
|
||||
restart: always
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: somewordpress
|
||||
MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_root_password
|
||||
MYSQL_DATABASE: wordpress
|
||||
MYSQL_USER: wordpress
|
||||
MYSQL_PASSWORD: wordpress
|
||||
MYSQL_PASSWORD_FILE: /run/secrets/db_password
|
||||
secrets:
|
||||
- db_root_password
|
||||
- db_password
|
||||
|
||||
wordpress:
|
||||
depends_on:
|
||||
- db
|
||||
image: wordpress:latest
|
||||
ports:
|
||||
- "8000:80"
|
||||
- "127.0.0.1:8000:80"
|
||||
restart: always
|
||||
environment:
|
||||
WORDPRESS_DB_HOST: db:3306
|
||||
WORDPRESS_DB_USER: wordpress
|
||||
WORDPRESS_DB_PASSWORD: wordpress
|
||||
WORDPRESS_DB_PASSWORD_FILE: /run/secrets/db_password
|
||||
secrets:
|
||||
- db_password
|
||||
volumes:
|
||||
db_data:
|
||||
|
||||
secrets:
|
||||
db_root_password:
|
||||
file: ./secrets/db_root_password.txt
|
||||
db_password:
|
||||
file: ./secrets/db_password.txt
|
||||
|
||||
@@ -75,7 +75,7 @@ flowchart TD
|
||||
E["退出"]
|
||||
|
||||
U -->|1. REST API| D
|
||||
K -->|2. gRPC| C
|
||||
D -->|2. gRPC| C
|
||||
C -->|3. 准备镜像和 Bundle| B
|
||||
C -->|4. 启动 Shim| S
|
||||
S -->|5. 执行| R
|
||||
@@ -100,7 +100,7 @@ flowchart TD
|
||||
从 Docker Engine v29.x 开始,架构进一步简化和标准化:
|
||||
|
||||
- **Containerd 镜像存储 (Image Store)**:在 v29.x 的新安装场景中默认启用。Docker 直接使用 Containerd 的镜像管理能力,不再维护自己的一套 graphdriver。
|
||||
- **优势**:多平台镜像支持更好、镜像拉取更快 (lazy pulling)、与 K8s 共享镜像。
|
||||
- **优势**:多平台镜像支持更好,可保存 SBOM/Provenance 等 attestations,并可使用 containerd snapshotters 的 lazy pulling 等能力。
|
||||
- **实验性 nftables 支持**:随着主流 Linux 发行版逐步弃用 iptables,Docker v29.x 引入了实验性 nftables 后端。启用方式为 `dockerd --firewall-backend=nftables`,可直接创建 nftables 规则而无需依赖 iptables-nft 转换层。生产环境请谨慎使用。
|
||||
|
||||
---
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
### 12.2.1 什么是 Namespace
|
||||
|
||||
> **Namespace 是 Linux 内核提供的资源隔离机制,它让容器内的进程仿佛运行在独立的操作系统中。**Namespace 是容器技术的核心基础之一。它回答了一个关键问题:**如何让一个进程 “以为” 自己独占整个系统?**
|
||||
> **Namespace 是 Linux 内核提供的资源隔离机制,它让容器内的进程仿佛运行在独立的操作系统中。** Namespace 是容器技术的核心基础之一。它回答了一个关键问题:**如何让一个进程 “以为” 自己独占整个系统?**
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
@@ -28,7 +28,7 @@ flowchart LR
|
||||
|
||||
### 12.2.2 Namespace 的类型
|
||||
|
||||
Linux 内核(5.6+)共提供 8 种 Namespace。Docker 容器默认使用其中 7 种(不含 Time):
|
||||
Linux 内核(5.6+)共提供 8 种 Namespace。Docker 容器默认启用其中的 PID、NET、MNT、UTS、IPC(在 cgroup v2 主机上还包括 Cgroup);USER Namespace 默认**不**启用,需要通过 `daemon.json` 的 `userns-remap` 显式开启(详见第 18.1 节),Time Namespace 则未被使用:
|
||||
|
||||
| Namespace | 隔离内容 | 容器中的效果 |
|
||||
|-----------|---------|-------------|
|
||||
@@ -37,7 +37,7 @@ Linux 内核(5.6+)共提供 8 种 Namespace。Docker 容器默认使用其
|
||||
| **MNT** | 挂载点 | 独立的文件系统视图,自己的根目录 |
|
||||
| **UTS** | 主机名 | 独立的主机名和域名 |
|
||||
| **IPC** | 进程间通信 | 独立的信号量、消息队列、共享内存 |
|
||||
| **USER** | 用户/组 ID | 容器内的 root 可以映射为宿主机的普通用户 |
|
||||
| **USER** | 用户/组 ID | 容器内的 root 可以映射为宿主机的普通用户(默认不启用,需 `userns-remap` 显式开启)|
|
||||
| **Cgroup** | Cgroup 根目录 | 隔离 cgroup 层级视图 (Linux 4.6+)|
|
||||
| **Time** | 系统时钟 | 隔离 CLOCK_MONOTONIC 和 CLOCK_BOOTTIME (Linux 5.6+)|
|
||||
|
||||
|
||||
@@ -198,14 +198,15 @@ $ docker run --rm --cpus=1 stress --cpu 4
|
||||
|
||||
#### Docker 对 cgroups v2 的支持
|
||||
|
||||
Docker 19.03+ 默认优先使用 cgroups v2(如果系统支持),提供更好的性能和资源隔离。如果需要明确控制或回退到 v1,可以通过 Docker 守护进程配置文件 `/etc/docker/daemon.json` 修改 `cgroup-driver` 参数:
|
||||
Docker 20.10+ 开始支持 cgroups v2(如果系统支持),提供更好的性能和资源隔离。cgroup 驱动可以通过 Docker 守护进程配置文件 `/etc/docker/daemon.json` 的 `exec-opts` 配置项指定:
|
||||
|
||||
```json
|
||||
{
|
||||
"cgroup-driver": "systemd"
|
||||
"exec-opts": ["native.cgroupdriver=systemd"]
|
||||
}
|
||||
```
|
||||
常见的 `cgroup-driver` 值包括 `systemd` (推荐) 和 `cgroupfs`。重启 Docker 守护进程后生效。
|
||||
|
||||
`native.cgroupdriver` 仅支持 `systemd` 和 `cgroupfs` 两个取值;若不指定,cgroup v1 主机默认使用 `cgroupfs`,带 systemd 的 cgroup v2 主机默认使用 `systemd`。重启 Docker 守护进程后生效。
|
||||
|
||||
#### 检查系统使用的版本
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ flowchart TD
|
||||
每个 Dockerfile 指令创建一层,只有变化的层需要重建:
|
||||
|
||||
```docker
|
||||
FROM node:20 # 层1:基础镜像
|
||||
FROM node:22 # 层1:基础镜像
|
||||
COPY package.json ./ # 层2:依赖定义
|
||||
RUN npm install # 层3:安装依赖
|
||||
COPY . . # 层4:应用代码
|
||||
@@ -92,11 +92,11 @@ Docker 的存储驱动经历了从早期各式各样的机制(如 aufs, device
|
||||
|
||||
| 存储后端 / 驱动 | 核心特性说明 | 推荐程度 |
|
||||
|---------|------|---------|
|
||||
| **containerd image store**| (v29.x 新一代默认后端,新装默认) 基于 containerd 的 snapshotters,原生支持 OCI image index、多架构镜像与 Attestations 构建溯源元数据存储。 | ✅**强烈推荐 (现代默认)** |
|
||||
| **containerd image store**| (v29.x 新一代默认后端,新装默认) 基于 containerd 的 snapshotters,原生支持 OCI image index、多架构镜像与 Attestations 构建溯源元数据存储;启用 `userns-remap` 时不可用。 | ✅**强烈推荐 (现代默认)** |
|
||||
| **overlay2**| (经典 Graph Driver) 传统架构下的现代 Linux 默认驱动,性能优秀,但在处理复杂溯源元数据(索引)时受限。 | ✅**推荐 (主要后备)** |
|
||||
| **aufs** | 早期默认,兼容性好 | 遗留系统 |
|
||||
| **aufs** | 早期默认,已从现代 Docker Engine 移除 | 历史资料 |
|
||||
| **btrfs**/**zfs** | 使用原生稳定文件系统快照能力 | 特定场景 |
|
||||
| **devicemapper** | 块设备级存储 | 遗留系统 (已被逐步弃用) |
|
||||
| **devicemapper** | 块设备级存储,已从现代 Docker Engine 移除 | 历史资料 |
|
||||
| **vfs** | 不使用 CoW,每层完整复制 | 仅测试 |
|
||||
|
||||
#### Classic Graph Drivers 与 Snapshotters 的核心差异
|
||||
@@ -104,7 +104,7 @@ Docker 的存储驱动经历了从早期各式各样的机制(如 aufs, device
|
||||
传统模型(如 `overlay2`)将镜像拉取解包的过程由 Docker 的 graph drivers 处理。而新的 `containerd image store` 则将这一职责彻底下放给了 `containerd` 自身的 `snapshotters`(底层在 Linux 发行版通常依然利用操作系统的 overlayfs)。这种架构改变带来了:
|
||||
|
||||
1. 本地免拉取查看多平台镜像 index manifest 与 attestations (SBOM、Provenance)。
|
||||
2. 避免了以前绕过 CRI 获取本地镜像的问题,带来更好的原生 Kubernetes 生态兼容性。
|
||||
2. 允许 Docker 使用 containerd snapshotters 管理镜像层,便于与现代 OCI 生态能力对齐。它不表示 kubelet/containerd 会自动复用 Docker 本地镜像;Kubernetes 镜像分发仍应以 registry、镜像拉取策略和运行时配置为准。
|
||||
|
||||
#### 查看当前存储驱动与后端
|
||||
|
||||
@@ -114,14 +114,14 @@ $ docker info | grep "Storage Driver"
|
||||
Storage Driver: overlay2
|
||||
|
||||
## 在 Engine v29.x 中,可以通过如下输出验证是否开启了 containerd 镜像后端:
|
||||
$ docker info | grep "containerd image store"
|
||||
containerd image store: true
|
||||
$ docker info -f '{{ .DriverStatus }}'
|
||||
[[driver-type io.containerd.snapshotter.v1]]
|
||||
```
|
||||
---
|
||||
|
||||
### 12.4.5 overlay2 工作原理
|
||||
|
||||
overlay2 是目前最推荐的存储驱动:
|
||||
在经典 graph driver 体系下,overlay2 仍是 Linux 上的主要推荐驱动;Docker Engine 29 新安装场景默认走 containerd image store/snapshotter。
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
|
||||
@@ -56,7 +56,7 @@ inet 172.17.0.1/16 scope global docker0
|
||||
创建一对 “veth pair” 接口 A 和 B,绑定 A 到网桥 `docker0`,并启用它
|
||||
```bash
|
||||
$ sudo ip link add A type veth peer name B
|
||||
$ sudo brctl addif docker0 A
|
||||
$ sudo ip link set A master docker0
|
||||
$ sudo ip link set A up
|
||||
```
|
||||
将 B 放到容器的网络命名空间,命名为 eth0,启动它并配置一个可用 IP (桥接网段) 和默认网关。
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
|
||||
| 技术 | 作用 | 要点 |
|
||||
|------|------|------|
|
||||
| **Namespace** | 资源隔离 | PID、NET、MNT、UTS、IPC、USER 六种命名空间 |
|
||||
| **Namespace** | 资源隔离 | Docker 默认启用 PID、NET、MNT、UTS、IPC(cgroup v2 主机上还有 Cgroup);USER 默认**不**启用,Time 未被使用 |
|
||||
| **Cgroups** | 资源限制 | 限制 CPU、内存、磁盘 I/O、进程数 |
|
||||
| **Union FS** | 分层存储 | overlay2 为推荐驱动,支持 Copy-on-Write |
|
||||
| **Union FS** | 分层存储 | 镜像分层与 Copy-on-Write 是核心;Engine 29 新装默认 containerd image store,overlay2 是经典 graph driver 场景的主要后备 |
|
||||
|
||||
| Namespace | 隔离内容 | 一句话说明 |
|
||||
|-----------|---------|-----------|
|
||||
@@ -15,7 +15,7 @@
|
||||
| MNT | 文件系统 | 容器有自己的根目录 |
|
||||
| UTS | 主机名 | 容器有自己的 hostname |
|
||||
| IPC | 进程间通信 | 容器间 IPC 隔离 |
|
||||
| USER | 用户 ID | 容器 root ≠ 宿主机 root |
|
||||
| USER | 用户 ID | 启用 `userns-remap` 后容器 root 才 ≠ 宿主机 root;**默认不启用** |
|
||||
|
||||
| 资源 | 限制参数 | 示例 |
|
||||
|------|---------|------|
|
||||
|
||||
@@ -7,19 +7,18 @@
|
||||
图 13-2:Kubernetes 基本概念示意图
|
||||
|
||||
* 节点 (`Node`):一个节点是一个运行 Kubernetes 中的主机。
|
||||
* 容器组 (`Pod`):一个 Pod 对应于由若干容器组成的一个容器组,同个组内的容器共享一个存储卷 (volume)。
|
||||
* 容器组生命周期 (`pod-states`):包含所有容器状态集合,包括容器组状态类型,容器组生命周期,事件,重启策略,以及 replication controllers。
|
||||
* Replication Controllers:主要负责指定数量的 pod 在同一时间一起运行。
|
||||
* 容器组 (`Pod`):Kubernetes 中最小的可部署和调度单元;一个 Pod 包含一个或多个紧密协作的容器,共享网络身份,并可共享一个或多个卷 (volume)。
|
||||
* 容器组生命周期 (`pod-states`):包含所有容器状态集合,包括容器组状态类型,容器组生命周期,事件,重启策略。
|
||||
* 服务 (`services`):一个 Kubernetes 服务是容器组逻辑的高级抽象,同时也对外提供访问容器组的策略。
|
||||
* 卷 (`volumes`):一个卷就是一个目录,容器对其有访问权限。
|
||||
* 标签 (`labels`):标签是用来连接一组对象的,比如容器组。标签可以被用来组织和选择子对象。
|
||||
* 接口权限 (`accessing_the_api`):端口,IP 地址和代理的防火墙规则。
|
||||
* web 界面 (`ux`):用户可以通过 web 界面操作 Kubernetes。
|
||||
* web 界面 (`ux`):用户可以通过 Headlamp 等仍在维护的 web 界面观察和操作 Kubernetes;历史 Dashboard 已停止维护。
|
||||
* 命令行操作 (`cli`):`kubectl` 命令。
|
||||
|
||||
### 13.2.1 节点
|
||||
|
||||
在 `Kubernetes` 中,节点是实际工作的点,节点可以是虚拟机或者物理机器,依赖于一个集群环境。每个节点都有一些必要的服务以运行容器组,并且它们都可以通过主节点来管理。必要服务包括 Docker,kubelet 和代理服务。
|
||||
在 `Kubernetes` 中,节点是实际工作的点,节点可以是虚拟机或者物理机器,依赖于一个集群环境。每个节点都有一些必要的组件以运行容器组,并且它们都可以通过控制平面来管理。必要组件包括 kubelet、容器运行时(如 containerd 或 CRI-O)和 kube-proxy;Docker 不再是 Kubernetes 节点的默认基线运行时。
|
||||
|
||||
#### 容器状态
|
||||
|
||||
@@ -29,13 +28,11 @@
|
||||
|
||||
主机 IP 需要云平台来查询,`Kubernetes` 把它作为状态的一部分来保存。如果 `Kubernetes` 没有运行在云平台上,节点 ID 就是必需的。IP 地址可以变化,并且可以包含多种类型的 IP 地址,如公共 IP,私有 IP,动态 IP,ipv6 等等。
|
||||
|
||||
##### 节点周期
|
||||
|
||||
通常来说节点有 `Pending`,`Running`,`Terminated` 三个周期,如果 Kubernetes 发现了一个节点并且其可用,那么 Kubernetes 就把它标记为 `Pending`。然后在某个时刻,Kubernetes 将会标记其为 `Running`。节点的结束周期称为 `Terminated`。一个已经 `Terminated` 的节点不会接受和调度任何请求,并且已经在其上运行的容器组也会删除。
|
||||
|
||||
##### 节点状态
|
||||
|
||||
节点的状态通过一组条件(Conditions)来描述。主要条件包括 `Ready`(kubelet 健康且可以接收 Pod)、`MemoryPressure`(内存不足)、`DiskPressure`(磁盘不足)和 `PIDPressure`(进程数过多)等。其中 `Ready` 条件最为关键:值为 `True` 表示节点健康可调度,`False` 表示节点异常,`Unknown` 表示节点控制器超过一定时间未收到心跳。
|
||||
注:`Pending` / `Running` / `Succeeded` / `Failed` / `Unknown` 是 **Pod 的生命周期阶段**(`pod.status.phase`),不是节点状态。节点本身没有 `Terminated` 状态。
|
||||
|
||||
节点的状态通过一组条件(Conditions)来描述。主要条件包括 `Ready`(kubelet 健康且可以接收 Pod)、`MemoryPressure`(内存不足)、`DiskPressure`(磁盘不足)和 `PIDPressure`(进程数过多)等。其中 `Ready` 条件最为关键:值为 `True` 表示节点健康可调度,`False` 表示节点异常,`Unknown` 表示节点控制器超过一定时间未收到心跳。被标记为 `Unknown` 较长时间的节点,其上的 Pod 会被驱逐重新调度到其他节点。
|
||||
|
||||
#### 节点管理
|
||||
|
||||
@@ -85,7 +82,7 @@ $ kubectl drain worker-1 --ignore-daemonsets
|
||||
|
||||
### 13.2.2 容器组
|
||||
|
||||
在 Kubernetes 中,使用的最小调度单位是容器组 (Pod),它是创建、调度、管理的最小单位。一个 Pod 包含一个或多个紧密协作的容器,它们共享网络命名空间和存储卷。
|
||||
在 Kubernetes 中,使用的最小调度单位是容器组 (Pod),它是创建、调度、管理的最小单位。一个 Pod 包含一个或多个紧密协作的容器,它们共享网络命名空间,并可共享一个或多个存储卷。
|
||||
|
||||
Pod 通常不会被直接创建,而是通过 Deployment 等控制器来管理。当节点发生故障时,控制器会在其他可用节点上重新创建 Pod。
|
||||
|
||||
@@ -97,9 +94,9 @@ Pod 通常不会被直接创建,而是通过 Deployment 等控制器来管理
|
||||
|
||||
容器组主要是为了数据共享和它们之间的通信。
|
||||
|
||||
在一个容器组中,容器都使用相同的网络地址和端口,可以通过本地网络来相互通信。每个容器组都有独立的 IP,可用通过网络来和其他物理主机或者容器通信。
|
||||
在一个容器组中,容器都使用相同的网络地址和端口,可以通过本地网络来相互通信。每个容器组都有独立的 IP,可以通过网络来和其他物理主机或者容器通信。
|
||||
|
||||
容器组有一组存储卷 (挂载点),主要是为了让容器在重启之后可以不丢失数据。
|
||||
容器组可以挂载一组存储卷 (挂载点)。卷不只用于持久化,也可用于 `emptyDir` 临时交换、ConfigMap/Secret 投射、日志旁路收集等场景。真正需要跨 Pod 生命周期保留的数据,应使用 PersistentVolume / PersistentVolumeClaim 等持久化机制。
|
||||
|
||||
#### 容器组管理
|
||||
|
||||
@@ -124,7 +121,7 @@ Pod 通常不会被直接创建,而是通过 Deployment 等控制器来管理
|
||||
|
||||
#### 容器组的生命状态
|
||||
|
||||
包括若干状态值:`Pending`、`Running`、`Succeeded`、`Failed`。
|
||||
Pod phase 包括若干状态值:`Pending`、`Running`、`Succeeded`、`Failed`、`Unknown`。
|
||||
|
||||
| 状态 | 说明 |
|
||||
|------|------|
|
||||
@@ -132,6 +129,7 @@ Pod 通常不会被直接创建,而是通过 Deployment 等控制器来管理
|
||||
| **Running** | Pod 已被调度到节点,并且所有容器都已启动。至少有一个容器处于运行状态。|
|
||||
| **Succeeded** | Pod 中的所有容器都正常退出,且不会被重启。|
|
||||
| **Failed** | Pod 中的所有容器都已终止,且至少有一个容器以失败状态退出。|
|
||||
| **Unknown** | 集群无法获取 Pod 状态,常见原因是节点失联。|
|
||||
|
||||
#### 容器组生命周期与重启策略
|
||||
|
||||
@@ -143,7 +141,7 @@ Pod 的重启策略 (`restartPolicy`) 决定了容器退出后的行为:
|
||||
| **OnFailure** | 不重启 | 重启容器 |
|
||||
| **Never** | 不重启 | 不重启 |
|
||||
|
||||
当节点故障或不可达时,节点控制器会将该节点上所有 Pod 的状态标记为 `Failed`。如果这些 Pod 由 Deployment 等控制器管理,控制器会自动在其他节点上重新创建。
|
||||
当节点故障或不可达时,Pod 可能先表现为 `Unknown`,之后根据控制器、驱逐策略和垃圾回收机制转为 `Failed` 或被重新创建。如果这些 Pod 由 Deployment 等控制器管理,控制器会自动在其他节点上重新创建。
|
||||
|
||||
### 13.2.3 Deployment 与 ReplicaSet
|
||||
|
||||
@@ -166,7 +164,7 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:1.30
|
||||
image: nginx:1.28
|
||||
ports:
|
||||
- containerPort: 80
|
||||
```
|
||||
@@ -200,7 +198,7 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- name: mysql
|
||||
image: mysql:8.0
|
||||
image: mysql:8.4
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /var/lib/mysql
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
|
||||
#### Etcd
|
||||
|
||||
这里 Etcd 即作为数据后端,又作为消息中间件。
|
||||
这里 Etcd 既作为数据后端,又作为消息中间件。
|
||||
|
||||
通过 Etcd 来存储所有的主节点上的状态信息,很容易实现主节点的分布式扩展。
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user