mirror of
https://github.com/yeasy/docker_practice.git
synced 2026-03-13 13:21:18 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ceaf6942e0 |
@@ -1,5 +1,3 @@
|
||||
// https://code.visualstudio.com/docs/remote/devcontainerjson-reference
|
||||
|
||||
{
|
||||
"image": "yeasy/docker_practice:latest",
|
||||
"mounts": [
|
||||
|
||||
15
.drone.yml
Normal file
15
.drone.yml
Normal file
@@ -0,0 +1,15 @@
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: build
|
||||
steps:
|
||||
- name: build
|
||||
image: yeasy/docker_practice:latest
|
||||
pull: if-not-exists # always never
|
||||
environment:
|
||||
TZ: Asia/Shanghai
|
||||
commands:
|
||||
- docker-entrypoint.sh build
|
||||
|
||||
trigger:
|
||||
branch:
|
||||
- master
|
||||
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
@@ -28,6 +28,7 @@
|
||||
/.editorconfig/ @khs1994
|
||||
/.gitattributes @khs1994
|
||||
/.gitignore @khs1994
|
||||
/.travis.yml @khs1994
|
||||
/_config.yml @yeasy @khs1994
|
||||
/book.json @yeasy @khs1994
|
||||
/CHANGELOG.md @yeasy @khs1994
|
||||
|
||||
13
.github/FUNDING.yml
vendored
13
.github/FUNDING.yml
vendored
@@ -1,13 +0,0 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: yeasy
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
8
.github/ISSUE_TEMPLATE/Bug_report.md
vendored
8
.github/ISSUE_TEMPLATE/Bug_report.md
vendored
@@ -21,11 +21,11 @@ about: Create a report to help us improve
|
||||
* [x] Others (Pls describe below)
|
||||
|
||||
### Docker Version
|
||||
<!--如果你的 Docker 版本低于 20.10 请尽可能升级到该版本,保留你的 Docker 版本,其他选项删除-->
|
||||
<!--if Docker version under 20.10, please upgrade Docker to 20.10-->
|
||||
<!--如果你的 Docker 版本低于 19.03 请尽可能升级到该版本,保留你的 Docker 版本,其他选项删除-->
|
||||
<!--if Docker version under 19.03, please upgrade Docker to 19.03-->
|
||||
|
||||
* [x] Test (v20.10)
|
||||
* [x] Stable (v20.10)
|
||||
* [x] Edge (v19.03)
|
||||
* [x] Stable (v19.03)
|
||||
* [x] 1.13.0 or Before
|
||||
|
||||
### Problem Description
|
||||
|
||||
8
.github/ISSUE_TEMPLATE/Custom.md
vendored
8
.github/ISSUE_TEMPLATE/Custom.md
vendored
@@ -21,11 +21,11 @@ about: Create a issue about Docker
|
||||
* [x] Others (Pls describe below)
|
||||
|
||||
### Docker Version
|
||||
<!--如果你的 Docker 版本低于 20.10 请尽可能升级到该版本,保留你的 Docker 版本,其他选项删除-->
|
||||
<!--if Docker version under 20.10, please upgrade Docker to 20.10-->
|
||||
<!--如果你的 Docker 版本低于 19.03 请尽可能升级到该版本,保留你的 Docker 版本,其他选项删除-->
|
||||
<!--if Docker version under 19.03, please upgrade Docker to 19.03-->
|
||||
|
||||
* [x] Test (v20.10)
|
||||
* [x] Stable (v20.10)
|
||||
* [x] Edge (v19.03)
|
||||
* [x] Stable (v19.03)
|
||||
* [x] 1.13.0 or Before
|
||||
|
||||
### Problem Description
|
||||
|
||||
16
.github/workflows/check-link.yml
vendored
16
.github/workflows/check-link.yml
vendored
@@ -1,6 +1,8 @@
|
||||
name: Check link
|
||||
name: check-link
|
||||
|
||||
on:
|
||||
# push:
|
||||
# pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
@@ -8,7 +10,9 @@ jobs:
|
||||
name: check-link
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.1
|
||||
- uses: actions/checkout@master
|
||||
with:
|
||||
fetch-depth: 1
|
||||
# search Issues :-(
|
||||
- run: |
|
||||
docker run -i --rm \
|
||||
@@ -21,6 +25,7 @@ jobs:
|
||||
"192.168.199.100",\
|
||||
"github.com/settings",\
|
||||
"github.com/docker/compose/releases/download",\
|
||||
"github.com/docker/machine/releases/download",\
|
||||
"github.com/etcd-io/etcd/releases/download",\
|
||||
"github.com/tianon/gosu/releases/download",\
|
||||
"github.com/yeasy/docker_practice",\
|
||||
@@ -32,6 +37,7 @@ jobs:
|
||||
"nodejs.org/dist/",\
|
||||
"c.163.com/hub",\
|
||||
"drone.yeasy.com",\
|
||||
"mirrors.ustc.edu.cn",\
|
||||
"docs.docker.com",\
|
||||
"dockerhub.azk8s.cn",\
|
||||
"reg-mirror.qiniu.com",\
|
||||
@@ -46,6 +52,7 @@ jobs:
|
||||
"security.ubuntu.com/ubuntu",\
|
||||
"nginx.com",\
|
||||
"img.shields.io/github/release/yeasy/docker_practice",\
|
||||
"travis-ci.org/yeasy/docker_practice.svg",\
|
||||
"launchpad.net",\
|
||||
"www.w3.org/1999",\
|
||||
"chat.freenode.net",\
|
||||
@@ -53,13 +60,12 @@ jobs:
|
||||
"product.china-pub.com",\
|
||||
"union-click.jd.com",\
|
||||
"x.x.x.x/base",\
|
||||
"x.x.x.x:9090",\
|
||||
"x.x.x.x:9000/minio/",\
|
||||
"yeasy.gitbooks.io",\
|
||||
"download.fastgit.org",\
|
||||
"www.aliyun.com" \
|
||||
--allow-dupe \
|
||||
--skip-save-results \
|
||||
-t 10 \
|
||||
`find . \( -path "./mesos" -o -path "./swarm_mode" \) -prune -o -name "*.md" -exec ls {} \;`
|
||||
`find . \( -path "./mesos" -o -path "./machine" -o -path "./swarm_mode" \) -prune -o -name "*.md" -exec ls {} \;`
|
||||
name: check-link
|
||||
timeout-minutes: 25
|
||||
|
||||
109
.github/workflows/ci.yaml
vendored
109
.github/workflows/ci.yaml
vendored
@@ -1,86 +1,40 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash --noprofile --norc -exo pipefail {0}
|
||||
name: CI
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
name: Build GitBook
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.1
|
||||
- name: Build Gitbook
|
||||
- uses: actions/checkout@master
|
||||
with:
|
||||
fetch-depth: 2
|
||||
- name: Build
|
||||
uses: docker://yeasy/docker_practice
|
||||
with:
|
||||
args: build
|
||||
- name: Build Gitbook Docker Image
|
||||
if: github.repository == 'docker-practice/docker_practice'
|
||||
run: |
|
||||
sudo chmod -R 777 _book
|
||||
echo "FROM nginx:alpine" >> Dockerfile
|
||||
echo "COPY _book /usr/share/nginx/html" >> Dockerfile
|
||||
echo "COPY .docker/docker-entrypoint.sh /" >> Dockerfile
|
||||
echo "ENTRYPOINT [\"/docker-entrypoint.sh\"]" >> Dockerfile
|
||||
|
||||
export VCS_REF=`git rev-parse --short HEAD`
|
||||
|
||||
docker build \
|
||||
-t dockerpracticesig/docker_practice \
|
||||
-t dockerpracticesig/docker_practice:gitbook \
|
||||
--label org.opencontainers.image.revision=$VCS_REF \
|
||||
--label org.opencontainers.image.source="https://github.com/yeasy/docker_practice" \
|
||||
--label maintainer="https://github.com/docker-practice" \
|
||||
.
|
||||
|
||||
docker run -d --rm -p 4000:80 dockerpracticesig/docker_practice
|
||||
|
||||
sleep 5
|
||||
|
||||
echo "::group::Test"
|
||||
curl 127.0.0.1:4000
|
||||
echo "::endgroup::"
|
||||
|
||||
echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
|
||||
docker push dockerpracticesig/docker_practice
|
||||
docker push dockerpracticesig/docker_practice:gitbook
|
||||
env:
|
||||
DOCKER_PASSWORD: ${{secrets.DOCKER_PASSWORD}}
|
||||
DOCKER_USERNAME: ${{secrets.DOCKER_USERNAME}}
|
||||
- name: Upload Gitbook dist
|
||||
uses: docker://pcit/pages
|
||||
if: github.repository == 'docker-practice/docker_practice'
|
||||
env:
|
||||
PCIT_EMAIL: khs1994@khs1994.com
|
||||
PCIT_GIT_TOKEN: ${{ secrets.PCIT_GIT_TOKEN }}
|
||||
PCIT_GIT_URL: github.com/docker-practice/zh-cn
|
||||
PCIT_KEEP_HISTORY: "true"
|
||||
PCIT_LOCAL_DIR: _book
|
||||
PCIT_MESSAGE: Build from yeasy/docker_practice@${{github.sha}}
|
||||
PCIT_TARGET_BRANCH: master
|
||||
PCIT_USERNAME: khs1994
|
||||
- name: vuepress
|
||||
run: |
|
||||
export NODE_OPTIONS=--openssl-legacy-provider
|
||||
sudo rm -rf _book
|
||||
# npm i vuepress --save-dev
|
||||
npm i
|
||||
|
||||
git clone https://github.com/docker-practice/.vuepress .vuepress2
|
||||
cp -r .vuepress2/. .vuepress/
|
||||
rm -rf .vuepress2
|
||||
find . \( -path "./mesos" -o -path "./swarm_mode" -o -path "./node_modules" -o -path "./.vuepress" -o -path "./_book" -o -path "./CHANGELOG.md" -o -path "./CONTRIBUTING.md" \) -prune -o -name "*.md" -exec sed -i 'N;2a\<AdSenseTitle/>\n' {} \;
|
||||
find . \( -path "./mesos" -o -path "./machine" -o -path "./swarm_mode" -o -path "./node_modules" -o -path "./.vuepress" -o -path "./_book" -o -path "./CHANGELOG.md" -o -path "./CONTRIBUTING.md" \) -prune -o -name "*.md" -exec sed -i 'N;2a\<AdSenseTitle/>\n' {} \;
|
||||
|
||||
npx vuepress --version
|
||||
|
||||
npm run vuepress:build
|
||||
echo "vuepress.mirror.docker-practice.com" > .vuepress/dist/CNAME
|
||||
echo "vuepress.docker-practice.com" > .vuepress/dist/CNAME
|
||||
|
||||
cp -r _images .vuepress/dist
|
||||
cp -r advanced_network/_images .vuepress/dist/advanced_network
|
||||
cp -r appendix/_images .vuepress/dist/appendix
|
||||
cp -r cases/ci/drone/_images .vuepress/dist/cases/ci/drone
|
||||
cp -r cases/os/_images .vuepress/dist/cases/os
|
||||
@@ -91,6 +45,7 @@ jobs:
|
||||
cp -r install/_images .vuepress/dist/install
|
||||
cp -r introduction/_images .vuepress/dist/introduction
|
||||
cp -r kubernetes/_images .vuepress/dist/kubernetes
|
||||
cp -r mesos/_images .vuepress/dist/mesos
|
||||
cp -r underly/_images .vuepress/dist/underly
|
||||
|
||||
echo "include: [_images]" > .vuepress/dist/_config.yml
|
||||
@@ -103,30 +58,30 @@ jobs:
|
||||
PCIT_GIT_URL: github.com/docker-practice/vuepress
|
||||
PCIT_KEEP_HISTORY: "true"
|
||||
PCIT_LOCAL_DIR: .vuepress/dist
|
||||
PCIT_MESSAGE: Build from yeasy/docker_practice@${{github.sha}}
|
||||
PCIT_MESSAGE: Sync from yeasy/docker_practice@${{github.sha}} by PCIT
|
||||
PCIT_TARGET_BRANCH: master
|
||||
PCIT_USERNAME: khs1994
|
||||
# - name: Set coding.net CNAME
|
||||
# run: |
|
||||
# echo "vuepress.mirror.docker-practice.com" > .vuepress/dist/CNAME
|
||||
# - name: Upload Vuepress dist to coding.net
|
||||
# uses: docker://pcit/pages
|
||||
# if: github.repository == 'docker-practice/docker_practice'
|
||||
# env:
|
||||
# PCIT_EMAIL: khs1994@khs1994.com
|
||||
# PCIT_GIT_TOKEN: ${{ secrets.CODING_GIT_TOKEN }}
|
||||
# PCIT_GIT_URL: e.coding.net/dpsigs/docker_practice
|
||||
# PCIT_KEEP_HISTORY: "true"
|
||||
# PCIT_LOCAL_DIR: .vuepress/dist
|
||||
# PCIT_MESSAGE: Build from yeasy/docker_practice@${{github.sha}}
|
||||
# PCIT_TARGET_BRANCH: master
|
||||
# PCIT_USERNAME: ptt0xjqzbke3
|
||||
- name: Set coding.net CNAME
|
||||
run: |
|
||||
echo "vuepress.mirror.docker-practice.com" > .vuepress/dist/CNAME
|
||||
- name: Upload Vuepress dist to coding.net
|
||||
uses: docker://pcit/pages
|
||||
if: github.repository == 'docker-practice/docker_practice'
|
||||
env:
|
||||
PCIT_EMAIL: khs1994@khs1994.com
|
||||
PCIT_GIT_TOKEN: ${{ secrets.CODING_GIT_TOKEN }}
|
||||
PCIT_GIT_URL: e.coding.net/dpsigs/docker_practice
|
||||
PCIT_KEEP_HISTORY: "true"
|
||||
PCIT_LOCAL_DIR: .vuepress/dist
|
||||
PCIT_MESSAGE: Sync from yeasy/docker_practice@${{github.sha}} by PCIT
|
||||
PCIT_TARGET_BRANCH: master
|
||||
PCIT_USERNAME: ptt0xjqzbke3
|
||||
- name: Build vuepress docker image
|
||||
if: github.repository == 'docker-practice/docker_practice'
|
||||
run: |
|
||||
sudo rm -rf .vuepress/dist/.git
|
||||
|
||||
echo "FROM nginx:alpine" > Dockerfile
|
||||
echo "FROM nginx:alpine" >> Dockerfile
|
||||
echo "COPY .vuepress/dist /usr/share/nginx/html" >> Dockerfile
|
||||
echo "COPY .docker/docker-entrypoint.sh /" >> Dockerfile
|
||||
echo "ENTRYPOINT [\"/docker-entrypoint.sh\"]" >> Dockerfile
|
||||
@@ -143,13 +98,9 @@ jobs:
|
||||
|
||||
docker push dockerpracticesig/docker_practice:vuepress
|
||||
|
||||
docker run -it --rm -d -p 4001:80 dockerpracticesig/docker_practice:vuepress
|
||||
docker run -it --rm -d -p 4000:80 dockerpracticesig/docker_practice:vuepress
|
||||
|
||||
sleep 5
|
||||
|
||||
echo "::group::Test"
|
||||
curl 127.0.0.1:4001
|
||||
echo "::endgroup::"
|
||||
curl 127.0.0.1:4000
|
||||
env:
|
||||
DOCKER_PASSWORD: ${{secrets.DOCKER_PASSWORD}}
|
||||
DOCKER_USERNAME: ${{secrets.DOCKER_USERNAME}}
|
||||
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -9,9 +9,3 @@ _book/
|
||||
|
||||
node_modules/
|
||||
package-lock.json
|
||||
|
||||
docker-compose.override.yml
|
||||
|
||||
# Editor configs
|
||||
.obsidian/
|
||||
.vscode/
|
||||
|
||||
65
.travis.yml
Normal file
65
.travis.yml
Normal file
@@ -0,0 +1,65 @@
|
||||
language: bash
|
||||
|
||||
services:
|
||||
- docker
|
||||
|
||||
before_install:
|
||||
- openssl aes-256-cbc -K $encrypted_6cc8cff04075_key -iv $encrypted_6cc8cff04075_iv
|
||||
-in .travis/id_rsa.enc -out ~/.ssh/id_rsa -d
|
||||
- chmod 600 ~/.ssh/id_rsa
|
||||
- export TZ='Asia/Shanghai'
|
||||
- date
|
||||
- git config --global user.name "khs1994"
|
||||
- git config --global user.email "khs1994@khs1994.com"
|
||||
|
||||
script:
|
||||
- docker run -it --rm -v $PWD:/srv/gitbook-src yeasy/docker_practice build
|
||||
|
||||
after_success:
|
||||
- sudo chmod -R 777 _book
|
||||
|
||||
- echo "FROM nginx:alpine" >> Dockerfile
|
||||
- echo "COPY _book /usr/share/nginx/html" >> Dockerfile
|
||||
- echo "COPY .docker/docker-entrypoint.sh /" >> Dockerfile
|
||||
- echo "ENTRYPOINT [\"/docker-entrypoint.sh\"]" >> Dockerfile
|
||||
|
||||
- echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
|
||||
- export VCS_REF=`git rev-parse --short HEAD`
|
||||
- |
|
||||
docker build \
|
||||
-t dockerpracticesig/docker_practice \
|
||||
-t dockerpracticesig/docker_practice:gitbook \
|
||||
--label org.opencontainers.image.revision=$VCS_REF \
|
||||
--label org.opencontainers.image.source="https://github.com/yeasy/docker_practice" \
|
||||
--label maintainer="https://github.com/docker-practice" \
|
||||
.
|
||||
|
||||
- docker run -dit --rm -p 4000:80 dockerpracticesig/docker_practice
|
||||
|
||||
- sleep 5
|
||||
- curl 127.0.0.1:4000
|
||||
|
||||
- docker push dockerpracticesig/docker_practice
|
||||
- docker push dockerpracticesig/docker_practice:gitbook
|
||||
|
||||
- cd _book
|
||||
- rm -rf .github/workflows
|
||||
- git init
|
||||
- git remote add origin "$DEPLOY_REPO"
|
||||
- git add .
|
||||
- COMMIT=`date "+%F %T"`
|
||||
- git commit -m "Travis CI Site updated $COMMIT yeasy/docker_practice@${TRAVIS_COMMIT}"
|
||||
- git push -f origin master:"$DEPLOY_BRANCH"
|
||||
|
||||
env:
|
||||
global:
|
||||
- DEPLOY_BRANCH: master
|
||||
- DEPLOY_REPO: git@github.com:docker-practice/zh-cn.git
|
||||
|
||||
addons:
|
||||
ssh_known_hosts:
|
||||
- github.com
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
26
.travis/Dockerfile
Normal file
26
.travis/Dockerfile
Normal file
@@ -0,0 +1,26 @@
|
||||
FROM node:14.4.0-alpine
|
||||
|
||||
ENV TZ=Asia/Shanghai
|
||||
|
||||
WORKDIR /srv/gitbook
|
||||
|
||||
COPY book.json book.json
|
||||
|
||||
COPY docker-entrypoint.sh /usr/local/bin/
|
||||
|
||||
RUN set -x && apk add --no-cache \
|
||||
tzdata bash \
|
||||
&& npm install -g gitbook-cli \
|
||||
&& gitbook install \
|
||||
&& ln -s /usr/local/bin/docker-entrypoint.sh / \
|
||||
&& rm -rf /root/.npm /tmp/*
|
||||
|
||||
EXPOSE 4000
|
||||
|
||||
VOLUME /srv/gitbook-src
|
||||
|
||||
WORKDIR /srv/gitbook-src
|
||||
|
||||
ENTRYPOINT ["docker-entrypoint.sh"]
|
||||
|
||||
CMD server
|
||||
37
.travis/book.json
Normal file
37
.travis/book.json
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"title": "Docker -- 从入门到实践",
|
||||
"author": "yeasy",
|
||||
"language": "zh-hans",
|
||||
"links": {
|
||||
"sidebar": {
|
||||
"GitHub": "https://github.com/yeasy/docker_practice"
|
||||
}
|
||||
},
|
||||
"plugins": [
|
||||
"-livereload",
|
||||
"image-captions",
|
||||
"github",
|
||||
"page-treeview@2.9.8",
|
||||
"editlink"
|
||||
],
|
||||
"pluginsConfig": {
|
||||
"image-captions": {
|
||||
"attributes": {
|
||||
"width": "600"
|
||||
},
|
||||
"caption": "图 _PAGE_LEVEL_._PAGE_IMAGE_NUMBER_ - _CAPTION_"
|
||||
},
|
||||
"github": {
|
||||
"url": "https://github.com/yeasy/docker_practice"
|
||||
},
|
||||
"editlink": {
|
||||
"base": "https://github.com/yeasy/docker_practice/blob/master/",
|
||||
"label": "编辑本页"
|
||||
},
|
||||
"page-treeview": {
|
||||
"copyright": "Copyright © yeasy",
|
||||
"minHeaderCount": "2",
|
||||
"minHeaderDeep": "2"
|
||||
}
|
||||
}
|
||||
}
|
||||
43
.travis/conf.d/nginx.conf
Normal file
43
.travis/conf.d/nginx.conf
Normal file
@@ -0,0 +1,43 @@
|
||||
user root;
|
||||
worker_processes auto;
|
||||
|
||||
error_log /var/log/nginx/error.log warn;
|
||||
pid /var/run/nginx.pid;
|
||||
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||
'$status $body_bytes_sent "$http_referer" '
|
||||
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||
|
||||
access_log /var/log/nginx/access.log main;
|
||||
|
||||
sendfile on;
|
||||
#tcp_nopush on;
|
||||
|
||||
keepalive_timeout 65;
|
||||
|
||||
#gzip on;
|
||||
|
||||
index index.html index.php;
|
||||
|
||||
server {
|
||||
|
||||
server_name localhost;
|
||||
|
||||
listen 4000;
|
||||
|
||||
root /srv/www/;
|
||||
|
||||
index index.html;
|
||||
|
||||
}
|
||||
}
|
||||
5
.travis/docker-compose.test.yml
Normal file
5
.travis/docker-compose.test.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
sut:
|
||||
build: .
|
||||
volumes:
|
||||
- ../:/srv/gitbook-src
|
||||
command: build
|
||||
23
.travis/docker-entrypoint.sh
Executable file
23
.travis/docker-entrypoint.sh
Executable file
@@ -0,0 +1,23 @@
|
||||
#!/bin/sh
|
||||
|
||||
START=`date "+%F %T"`
|
||||
|
||||
if [ $1 = "sh" ];then sh ; exit 0; fi
|
||||
|
||||
rm -rf node_modules _book
|
||||
|
||||
srcDir=$PWD
|
||||
|
||||
cp -a . /srv/gitbook
|
||||
|
||||
cd /srv/gitbook
|
||||
|
||||
main(){
|
||||
if [ "$1" = build ];then
|
||||
gitbook build && cp -a _book $srcDir && echo $START && date "+%F %T" && exit 0
|
||||
else
|
||||
exec gitbook serve
|
||||
fi
|
||||
}
|
||||
|
||||
main $1 $2 $3
|
||||
BIN
.travis/id_rsa.enc
Normal file
BIN
.travis/id_rsa.enc
Normal file
Binary file not shown.
8
.travis/update.sh
Executable file
8
.travis/update.sh
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# cd .travis
|
||||
# ./update.sh
|
||||
|
||||
if [ ! -f Dockerfile ];then exit 1; fi
|
||||
|
||||
cp -a ../book.json book.json
|
||||
@@ -1,6 +1,8 @@
|
||||
const { config } = require('vuepress-theme-hope')
|
||||
const resolve = require("vuepress-theme-hope/resolve");
|
||||
// const { config } = require('vuepress-theme-hope')
|
||||
|
||||
module.exports = config({
|
||||
// module.exports =config({
|
||||
module.exports = resolve({
|
||||
title: 'Docker 从入门到实践',
|
||||
base: '/',
|
||||
head: [['script', {}, `
|
||||
@@ -38,11 +40,6 @@ module.exports = config({
|
||||
// onlyFirstAndLastCommit: true,
|
||||
// },
|
||||
},
|
||||
locales: {
|
||||
"/": {
|
||||
lang: "zh-CN"
|
||||
}
|
||||
},
|
||||
themeConfig: {
|
||||
blog: false,
|
||||
// comment: false,
|
||||
@@ -52,40 +49,29 @@ module.exports = config({
|
||||
appKey: "...", // your appKey
|
||||
},
|
||||
pageInfo: [
|
||||
// 'author',
|
||||
'reading-time',
|
||||
'word',
|
||||
// 'Author',
|
||||
'ReadTime',
|
||||
'Word',
|
||||
],
|
||||
footer: {
|
||||
content: "Made with <a target='_blank' href='https://github.com/vuepress-theme-hope/vuepress-theme-hope'>vuepress-theme-hope</a>",
|
||||
content: "Made with <a target='_blank' href='https://github.com/mister-hope/vuepress-theme-hope'>vuepress-theme-hope</a>",
|
||||
display: true,
|
||||
copyright: false,
|
||||
},
|
||||
searchPlaceholder: 'Search',
|
||||
repo: 'yeasy/docker_practice',
|
||||
repoLabel: 'GitHub',
|
||||
repoDisplay: true,
|
||||
hostname: 'https://vuepress.mirror.docker-practice.com',
|
||||
// author: 'yeasy',
|
||||
mdEnhance: {
|
||||
markdown: {
|
||||
lineNumbers: true,
|
||||
},
|
||||
git: {
|
||||
contributor: false,
|
||||
},
|
||||
themeColor: {
|
||||
blue: '#2196f3',
|
||||
// red: '#f26d6d',
|
||||
// green: '#3eaf7c',
|
||||
// orange: '#fb9b5f'
|
||||
},
|
||||
locales: {
|
||||
"/": {
|
||||
lang: "zh-CN"
|
||||
}
|
||||
},
|
||||
|
||||
darkmode: 'auto-switch',
|
||||
|
||||
//
|
||||
|
||||
@@ -98,11 +84,11 @@ module.exports = config({
|
||||
nav: [
|
||||
{
|
||||
text: '微信交流群',
|
||||
link: 'https://docker_practice.gitee.io/pic/dpsig-wechat.jpg',
|
||||
link: 'https://yewm28.coding-pages.com/wechat.jpg',
|
||||
},
|
||||
{
|
||||
text: '小程序',
|
||||
link: 'https://docker_practice.gitee.io/pic/dp-wechat-miniprogram.jpg',
|
||||
link: 'https://yewm28.coding-pages.com/49682252-3ac4c500-faec-11e8-86ab-eafe0139be6b.jpg',
|
||||
},
|
||||
{
|
||||
text: '安装 Docker',
|
||||
@@ -132,17 +118,17 @@ module.exports = config({
|
||||
text: "云计算",
|
||||
link: "/cloud/",
|
||||
},
|
||||
// {
|
||||
// text: 'GitHub',
|
||||
// link: 'https://github.com/yeasy/docker_practice'
|
||||
// },
|
||||
{
|
||||
text: 'GitHub',
|
||||
link: 'https://github.com/yeasy/docker_practice'
|
||||
},
|
||||
// {
|
||||
// text: '捐赠',
|
||||
// link: ''
|
||||
// },
|
||||
{
|
||||
text: '云服务器99/元首年特惠',
|
||||
link: 'https://cloud.tencent.com/act/cps/redirect?redirect=1062&cps_key=3a5255852d5db99dcd5da4c72f05df61&from=console'
|
||||
text: '腾讯云容器服务',
|
||||
link: 'https://cloud.tencent.com/act/cps/redirect?redirect=10058&cps_key=3a5255852d5db99dcd5da4c72f05df61'
|
||||
},
|
||||
// {
|
||||
// text: '语言',
|
||||
@@ -166,7 +152,7 @@ module.exports = config({
|
||||
'design',
|
||||
{
|
||||
title: "部署 Kubernetes",
|
||||
collapsable: true,
|
||||
collapsable: false,
|
||||
children: [
|
||||
"setup/",
|
||||
"setup/kubeadm",
|
||||
@@ -177,7 +163,7 @@ module.exports = config({
|
||||
},
|
||||
{
|
||||
title: "Kubernetes 命令行 kubectl",
|
||||
collapsable: true,
|
||||
collapsable: false,
|
||||
children: [
|
||||
'kubectl/'
|
||||
]
|
||||
@@ -185,7 +171,6 @@ module.exports = config({
|
||||
],
|
||||
'/compose/': [
|
||||
'introduction',
|
||||
'v2',
|
||||
'install',
|
||||
'usage',
|
||||
'commands',
|
||||
@@ -200,8 +185,9 @@ module.exports = config({
|
||||
'debian',
|
||||
'fedora',
|
||||
'centos',
|
||||
'centos8',
|
||||
'raspberry-pi',
|
||||
// 'offline',
|
||||
'offline',
|
||||
'mac',
|
||||
'windows',
|
||||
'mirror',
|
||||
@@ -232,12 +218,13 @@ module.exports = config({
|
||||
'actions/',
|
||||
{
|
||||
title: "Drone",
|
||||
collapsable: true,
|
||||
collapsable: false,
|
||||
children: [
|
||||
'drone/',
|
||||
'drone/install'
|
||||
]
|
||||
},
|
||||
'travis/'
|
||||
],
|
||||
'/': [
|
||||
'/',
|
||||
@@ -277,7 +264,7 @@ module.exports = config({
|
||||
},
|
||||
{
|
||||
title: 'Dockerfile',
|
||||
collapsable: true,
|
||||
collapsable: false,
|
||||
children: [
|
||||
"image/dockerfile/",
|
||||
'image/dockerfile/copy',
|
||||
@@ -343,7 +330,7 @@ module.exports = config({
|
||||
},
|
||||
{
|
||||
title: "高级网络配置",
|
||||
collapsable: true,
|
||||
collapsable: false,
|
||||
children: [
|
||||
'advanced_network/',
|
||||
'advanced_network/quick_guide',
|
||||
@@ -357,7 +344,7 @@ module.exports = config({
|
||||
},
|
||||
{
|
||||
title: "Swarm mode",
|
||||
collapsable: true,
|
||||
collapsable: false,
|
||||
children: [
|
||||
'swarm_mode/',
|
||||
'swarm_mode/overview',
|
||||
@@ -371,7 +358,7 @@ module.exports = config({
|
||||
},
|
||||
{
|
||||
title: "安全",
|
||||
collapsable: true,
|
||||
collapsable: false,
|
||||
children: [
|
||||
'security/',
|
||||
'security/kernel_ns',
|
||||
@@ -384,7 +371,7 @@ module.exports = config({
|
||||
},
|
||||
{
|
||||
title: "底层实现",
|
||||
collapsable: true,
|
||||
collapsable: false,
|
||||
children: [
|
||||
'underly/',
|
||||
'underly/arch',
|
||||
@@ -407,7 +394,7 @@ module.exports = config({
|
||||
},
|
||||
{
|
||||
title: "Etcd",
|
||||
collapsable: true,
|
||||
collapsable: false,
|
||||
children: [
|
||||
'etcd/',
|
||||
'etcd/intro',
|
||||
@@ -418,7 +405,7 @@ module.exports = config({
|
||||
},
|
||||
{
|
||||
title: "Fedora CoreOS",
|
||||
collapsable: true,
|
||||
collapsable: false,
|
||||
children: [
|
||||
'coreos/',
|
||||
'coreos/intro',
|
||||
@@ -429,7 +416,7 @@ module.exports = config({
|
||||
'appendix/faq/',
|
||||
{
|
||||
title: "热门镜像介绍",
|
||||
collapsable: true,
|
||||
collapsable: false,
|
||||
children: [
|
||||
'appendix/repo/',
|
||||
'appendix/repo/ubuntu',
|
||||
@@ -446,7 +433,7 @@ module.exports = config({
|
||||
},
|
||||
{
|
||||
title: "Docker 命令",
|
||||
collapsable: true,
|
||||
collapsable: false,
|
||||
children: [
|
||||
'appendix/command/',
|
||||
'appendix/command/docker',
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
# 简介
|
||||
|
||||
本章将带领你进入 **Docker** 的世界。
|
||||
|
||||
## 本章内容
|
||||
|
||||
* [什么是 Docker](what.md)
|
||||
* 介绍 Docker 的起源、发展历程以及其背后的核心技术(Cgroups, Namespaces, UnionFS)。
|
||||
* 了解 Docker 是如何改变软件交付方式的。
|
||||
|
||||
* [为什么要用 Docker](why.md)
|
||||
* 对比传统虚拟机技术,阐述 Docker 在启动速度、资源利用率、交付效率等方面的巨大优势。
|
||||
* 探讨 Docker 在 DevOps、微服务架构中的关键作用。
|
||||
|
||||
## 学习目标
|
||||
|
||||
通过本章的学习,你将能够:
|
||||
|
||||
1. 理解 Docker 的核心概念与架构。
|
||||
2. 明白 Docker 解决了现代软件开发与运维中的哪些痛点。
|
||||
3. 建立起对容器技术的初步认知,为后续的实战操作打下基础。
|
||||
|
||||
好吧,让我们带着问题开始这神奇之旅。
|
||||
@@ -1,65 +0,0 @@
|
||||
# 快速上手 (5分钟)
|
||||
|
||||
本节将通过一个简单的 Web 应用例子,带你快速体验 Docker 的核心流程:构建镜像、运行容器。
|
||||
|
||||
## 1. 准备代码
|
||||
|
||||
创建一个名为 `hello-docker` 的文件夹,并在其中创建一个 `index.html` 文件:
|
||||
|
||||
```html
|
||||
<h1>Hello, Docker!</h1>
|
||||
```
|
||||
|
||||
## 2. 编写 Dockerfile
|
||||
|
||||
在同级目录下创建一个名为 `Dockerfile` (无后缀) 的文件:
|
||||
|
||||
```dockerfile
|
||||
FROM nginx:alpine
|
||||
COPY index.html /usr/share/nginx/html/index.html
|
||||
```
|
||||
|
||||
## 3. 构建镜像
|
||||
|
||||
打开终端,进入该目录,执行构建命令:
|
||||
|
||||
```bash
|
||||
$ docker build -t my-hello-world .
|
||||
```
|
||||
|
||||
* `docker build`: 构建命令
|
||||
* `-t my-hello-world`: 给镜像起个名字(标签)
|
||||
* `.`: 指定上下文路径为当前目录
|
||||
|
||||
## 4. 运行容器
|
||||
|
||||
使用刚才构建的镜像启动一个容器:
|
||||
|
||||
```bash
|
||||
$ docker run -d -p 8080:80 my-hello-world
|
||||
```
|
||||
|
||||
* `docker run`: 运行命令
|
||||
* `-d`: 后台运行
|
||||
* `-p 8080:80`: 将宿主机的 8080 端口映射到容器的 80 端口
|
||||
|
||||
## 5. 访问测试
|
||||
|
||||
打开浏览器访问 [http://localhost:8080](http://localhost:8080),你应该能看到 "Hello, Docker!"。
|
||||
|
||||
## 6. 清理
|
||||
|
||||
停止并删除容器:
|
||||
|
||||
```bash
|
||||
# 查看正在运行的容器 ID
|
||||
$ docker ps
|
||||
|
||||
# 停止容器
|
||||
$ docker stop <CONTAINER_ID>
|
||||
|
||||
# 删除容器
|
||||
$ docker rm <CONTAINER_ID>
|
||||
```
|
||||
|
||||
恭喜!你已经完成了第一次 Docker 实战。接下来请阅读 [Docker 核心概念](../02_basic_concept/README.md) 做深入了解。
|
||||
@@ -1,124 +0,0 @@
|
||||
# 什么是 Docker
|
||||
|
||||
## 一句话理解 Docker
|
||||
|
||||
> **Docker 是一种轻量级的虚拟化技术,它让应用程序及其依赖环境可以被打包成一个标准化的单元,在任何地方都能一致地运行。**
|
||||
|
||||
如果用一个生活中的类比:**Docker 之于软件,就像集装箱之于货物**。
|
||||
|
||||
在集装箱发明之前,货物的运输是一件麻烦的事情——不同的货物需要不同的包装、不同的装卸方式,换一种运输工具就要重新装卸。集装箱的出现改变了这一切:无论里面装的是什么,集装箱的外形是标准的,可以用同样的方式装卸、堆放和运输。
|
||||
|
||||
Docker 做的事情类似:无论你的应用是用 Python、Java、Node.js 还是其他语言写的,无论它需要什么样的依赖库和环境,一旦被打包成 Docker 镜像,就可以用同样的方式在任何支持 Docker 的机器上运行。
|
||||
|
||||
## Docker 的核心价值
|
||||
|
||||
笔者认为,Docker 解决的是软件开发中最古老的问题之一:**"在我机器上明明能跑啊!"**
|
||||
|
||||
```
|
||||
开发环境 生产环境
|
||||
┌─────────────────┐ ┌─────────────────┐
|
||||
│ Python 3.14 │ ≠ │ Python 3.11 │
|
||||
│ Ubuntu 24.04 │ │ Ubuntu 22.04 │
|
||||
│ 特定版本的库 │ │ 不同版本的库 │
|
||||
└─────────────────┘ └─────────────────┘
|
||||
↓ ↓
|
||||
运行正常 运行失败!
|
||||
```
|
||||
|
||||
有了 Docker:
|
||||
|
||||
```
|
||||
开发环境 生产环境
|
||||
┌─────────────────┐ ┌─────────────────┐
|
||||
│ Docker 镜像 │ = │ 同一个镜像 │
|
||||
│ (包含所有依赖) │ │ (完全一致) │
|
||||
└─────────────────┘ └─────────────────┘
|
||||
↓ ↓
|
||||
运行正常 运行正常!
|
||||
```
|
||||
|
||||
## Docker vs 虚拟机
|
||||
|
||||
很多人第一次接触 Docker 时会问:**"这不就是虚拟机吗?"**
|
||||
|
||||
答案是:**不是,而且差别很大。**
|
||||
|
||||
### 传统虚拟机
|
||||
|
||||
传统虚拟机技术是虚拟出一套完整的硬件,在其上运行一个完整的操作系统,再在该系统上运行应用:
|
||||
|
||||

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

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

|
||||
|
||||
> `runc` 是一个 Linux 命令行工具,用于根据 [OCI 容器运行时规范](https://github.com/opencontainers/runtime-spec) 创建和运行容器。
|
||||
|
||||
> `containerd` 是一个守护程序,它管理容器生命周期,提供了在一个节点上执行容器和管理镜像的最小功能集。
|
||||
|
||||
## Docker 的历史与生态
|
||||
|
||||
**Docker** 最初是 `dotCloud` 公司创始人 [Solomon Hykes](https://github.com/shykes) 在法国期间发起的一个公司内部项目,于 [2013 年 3 月以 Apache 2.0 授权协议开源](https://en.wikipedia.org/wiki/Docker_(software))。
|
||||
|
||||
Docker 的发展历程:
|
||||
|
||||
- **2013 年 3 月**:开源发布
|
||||
- **2013 年底**:dotCloud 公司改名为 Docker, Inc.
|
||||
- **2015 年**:成立 [开放容器联盟(OCI)](https://opencontainers.org/),推动容器标准化
|
||||
- **至今**:[GitHub 项目](https://github.com/moby/moby) 超过 7 万星标
|
||||
|
||||
Docker 的成功推动了整个容器生态的发展,催生了 Kubernetes、Podman 等众多相关项目。笔者认为,Docker 最大的贡献不仅是技术本身,更是它**让容器技术从系统管理员的工具变成了每个开发者都能使用的标准工具**。
|
||||
|
||||
## 本章小结
|
||||
|
||||
- Docker 是一种轻量级虚拟化技术,核心价值是**环境一致性**
|
||||
- 与虚拟机相比,Docker 更轻量、更快速、资源利用率更高
|
||||
- Docker 基于 Linux 内核的 Namespace、Cgroups 和 Union FS 技术
|
||||
- Docker 推动了容器技术的标准化(OCI)和生态发展
|
||||
|
||||
接下来,让我们了解[为什么要使用 Docker](why.md)。
|
||||
@@ -1,208 +0,0 @@
|
||||
# 为什么要使用 Docker?
|
||||
|
||||
在回答"为什么用 Docker"之前,笔者想先问一个问题:**你有没有经历过这些场景?**
|
||||
|
||||
## 没有 Docker 的世界
|
||||
|
||||
### 场景一:"在我电脑上明明能跑"
|
||||
|
||||
```
|
||||
周五下午 5:00
|
||||
├── 开发者:代码写完了,本地测试通过,提交!🎉
|
||||
├── 周一早上 9:00
|
||||
│ └── 测试:"这个功能在测试环境跑不起来"
|
||||
└── 开发者:" 不可能,在我电脑上明明能跑啊……"
|
||||
```
|
||||
|
||||
笔者统计过,这个问题通常由以下原因导致:
|
||||
- Python/Node/Java 版本不一致
|
||||
- 依赖库版本不一致
|
||||
- 操作系统配置不一致
|
||||
- 某些环境变量没有设置
|
||||
- "哦,忘了说我本地装了个 XXX"
|
||||
|
||||
### 场景二:环境配置的噩梦
|
||||
|
||||
```
|
||||
新同事入职
|
||||
├── Day 1:领电脑,配环境
|
||||
├── Day 2:继续配环境,遇到问题
|
||||
├── Day 3:换种方法配环境
|
||||
├── Day 4:问老同事怎么配的,他也忘了
|
||||
└── Day 5:终于能跑起来了!但不知道为什么……
|
||||
```
|
||||
|
||||
### 场景三:服务器迁移的恐惧
|
||||
|
||||
```
|
||||
运维:"我们需要把服务迁移到新服务器"
|
||||
开发:"旧服务器上的配置文档在哪?"
|
||||
运维:"当时是一个已经离职的同事配的……"
|
||||
所有人:😱
|
||||
```
|
||||
|
||||
## Docker 如何解决这些问题
|
||||
|
||||
### 核心理念:一次构建,到处运行
|
||||
|
||||
```
|
||||
开发环境 测试环境 生产环境
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌─────────┐ ┌─────────┐ ┌─────────┐
|
||||
│ Docker │ = │ Docker │ = │ Docker │
|
||||
│ 镜像 │ │ 镜像 │ │ 镜像 │
|
||||
└─────────┘ └─────────┘ └─────────┘
|
||||
↓ ↓ ↓
|
||||
完全一致 完全一致 完全一致
|
||||
```
|
||||
|
||||
## Docker 的核心优势
|
||||
|
||||
### 1. 环境一致性
|
||||
|
||||
Docker 镜像包含了应用运行所需的**一切**:代码、运行时、系统工具、库、配置。这意味着:
|
||||
|
||||
- ✅ 开发环境和生产环境完全一致
|
||||
- ✅ 不会再有"在我机器上能跑"的问题
|
||||
- ✅ 新人入职,一条命令就能启动开发环境
|
||||
|
||||
```bash
|
||||
# 新同事入职第一天
|
||||
$ git clone https://github.com/company/project.git
|
||||
$ docker compose up
|
||||
# 完整的开发环境就准备好了
|
||||
```
|
||||
|
||||
### 2. 秒级启动
|
||||
|
||||
传统虚拟机启动需要几分钟(引导操作系统),而 Docker 容器启动通常只需要**几秒甚至几百毫秒**。
|
||||
|
||||
笔者实测数据:
|
||||
|
||||
| 启动内容 | 虚拟机 | Docker 容器 |
|
||||
|---------|--------|-------------|
|
||||
| 空系统 | ~60 秒 | ~0.5 秒 |
|
||||
| MySQL | ~90 秒 | ~3 秒 |
|
||||
| 完整 Web 应用 | ~120 秒 | ~5 秒 |
|
||||
|
||||
这个差异对以下场景尤为重要:
|
||||
- **CI/CD 流水线**:每次构建节省几分钟,一天累积下来就是几小时
|
||||
- **弹性扩容**:流量高峰时能快速启动更多实例
|
||||
- **开发体验**:快速重启服务进行调试
|
||||
|
||||
### 3. 资源效率
|
||||
|
||||
Docker 容器共享宿主机内核,无需为每个应用运行完整的操作系统。
|
||||
|
||||
```
|
||||
传统虚拟机方案:
|
||||
┌────────────────────────────────────────────────┐
|
||||
│ 物理服务器 (64GB 内存) │
|
||||
├──────────────┬───────────────┬─────────────────┤
|
||||
│ VM1 │ VM2 │ VM3 │
|
||||
│ 8GB 内存 │ 8GB 内存 │ 8GB 内存 │
|
||||
│ (含 OS 2GB) │ (含 OS 2GB) │ (含 OS 2GB) │
|
||||
│ 应用 1 │ 应用 2 │ 应用 3 │
|
||||
└──────────────┴───────────────┴─────────────────┘
|
||||
实际可用于应用:3 × 6GB = 18GB ❌
|
||||
|
||||
Docker 方案:
|
||||
┌────────────────────────────────────────────────┐
|
||||
│ 物理服务器 (64GB 内存) │
|
||||
│ 宿主机 OS + Docker (约 4GB) │
|
||||
├──────────────┬───────────────┬─────────────────┤
|
||||
│ 容器 1 │ 容器 2 │ 容器 3 │
|
||||
│ 应用 1 │ 应用 2 │ 应用 3 │
|
||||
│ (按需分配) │ (按需分配) │ (按需分配) │
|
||||
└──────────────┴───────────────┴─────────────────┘
|
||||
实际可用于应用:约 60GB ✅
|
||||
```
|
||||
|
||||
### 4. 持续交付和部署
|
||||
|
||||
Docker 完美契合 DevOps 的工作流程:
|
||||
|
||||
```
|
||||
代码提交 ──→ 自动构建镜像 ──→ 自动测试 ──→ 自动部署
|
||||
│ │ │ │
|
||||
▼ ▼ ▼ ▼
|
||||
Git docker 容器内 容器滚动
|
||||
push build 运行测试 更新
|
||||
```
|
||||
|
||||
使用 [Dockerfile](../04_image/build.md) 定义镜像构建过程,使得:
|
||||
- 构建过程**可重复、可追溯**
|
||||
- 任何人都能从代码重建完全相同的镜像
|
||||
- 配合 [GitHub Actions](../14_cases/ci/actions/README.md) 等 CI 系统实现自动化
|
||||
|
||||
### 5. 轻松迁移
|
||||
|
||||
Docker 可以在几乎任何平台上运行:
|
||||
- ✅ 本地开发机(macOS、Windows、Linux)
|
||||
- ✅ 公有云(AWS、Azure、GCP、阿里云、腾讯云)
|
||||
- ✅ 私有云和自建数据中心
|
||||
- ✅ 边缘设备和 IoT
|
||||
|
||||
**同一个镜像,在任何地方运行结果都一致。** 这让应用迁移变得前所未有的简单。
|
||||
|
||||
### 6. 微服务架构的基石
|
||||
|
||||
现代微服务架构几乎都依赖容器技术。Docker 让你可以:
|
||||
|
||||
- **隔离服务**:每个服务运行在独立容器中,互不干扰
|
||||
- **独立扩展**:哪个服务负载高,就单独扩展哪个
|
||||
- **独立部署**:更新一个服务不影响其他服务
|
||||
- **技术多样**:不同服务可以用不同语言和框架
|
||||
|
||||
```
|
||||
┌───────────────────────────────────────────────────┐
|
||||
│ 微服务架构示例 │
|
||||
├─────────────┬─────────────┬───────────────────────┤
|
||||
│ 前端容器 │ API 容器 │ Worker 容器 │
|
||||
│ (Node.js) │ (Python) │ (Go) │
|
||||
├─────────────┴─────────────┴───────────────────────┤
|
||||
│ Redis 容器 │
|
||||
├───────────────────────────────────────────────────┤
|
||||
│ PostgreSQL 容器 │
|
||||
└───────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Docker 不适合的场景
|
||||
|
||||
笔者认为,技术选型要客观。Docker 并非银弹,以下场景可能不太适合:
|
||||
|
||||
### 1. 需要完全隔离的场景
|
||||
|
||||
容器共享宿主机内核,隔离性不如虚拟机。如果你需要运行不受信任的代码,虚拟机可能更安全。
|
||||
|
||||
### 2. 需要特殊内核的场景
|
||||
|
||||
容器使用宿主机内核。如果应用需要特定版本的内核或内核模块,可能需要虚拟机。
|
||||
|
||||
### 3. Windows 原生应用
|
||||
|
||||
虽然 Docker 支持 Windows 容器,但生态不如 Linux 容器成熟。传统 Windows 应用的容器化仍有挑战。
|
||||
|
||||
### 4. 桌面应用
|
||||
|
||||
Docker 主要面向服务端应用。桌面 GUI 应用的容器化虽然可行,但通常得不偿失。
|
||||
|
||||
## 与传统虚拟机的对比总结
|
||||
|
||||
| 特性 | Docker 容器 | 传统虚拟机 |
|
||||
|:------|:-----------|:-----------|
|
||||
| 启动速度 | 秒级 | 分钟级 |
|
||||
| 磁盘占用 | MB 级别 | GB 级别 |
|
||||
| 性能 | 接近原生 | 有 5-20% 损耗 |
|
||||
| 单机支持量 | 上千个容器 | 几十个虚拟机 |
|
||||
| 隔离性 | 进程级别 | 完全隔离 |
|
||||
| 最佳场景 | 微服务、CI/CD、开发环境 | 多租户、高安全需求 |
|
||||
|
||||
## 本章小结
|
||||
|
||||
Docker 的核心价值可以用一句话概括:**让应用的开发、测试、部署保持一致,同时极大提高资源利用效率。**
|
||||
|
||||
笔者认为,对于现代软件开发者来说,Docker 已经不是"要不要学"的问题,而是**必备技能**。无论你是前端、后端、运维还是全栈开发者,掌握 Docker 都能让你的工作更高效。
|
||||
|
||||
接下来,让我们学习 Docker 的[基本概念](../02_basic_concept/README.md)。
|
||||
@@ -1,9 +0,0 @@
|
||||
# 基本概念
|
||||
|
||||
**Docker** 包括三个基本概念:
|
||||
|
||||
* **镜像**(`Image`):Docker 镜像是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。镜像不包含任何动态数据,其内容在构建之后也不会被改变。
|
||||
* **容器**(`Container`):镜像(`Image`)和容器(`Container`)的关系,就像是面向对象程序设计中的 `类` 和 `实例` 一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。
|
||||
* **仓库**(`Repository`):镜像构建完成后,可以很容易的在当前宿主机上运行,但是,如果需要在其它服务器上使用这个镜像,我们就需要一个集中的存储、分发镜像的服务,Docker Registry 就是这样的服务。
|
||||
|
||||
理解了这三个概念,就理解了 **Docker** 的整个生命周期。
|
||||
@@ -1,246 +0,0 @@
|
||||
# Docker 容器
|
||||
|
||||
## 一句话理解容器
|
||||
|
||||
> **容器是镜像的运行实例。如果把镜像比作程序,那么容器就是进程。**
|
||||
|
||||
用面向对象编程的术语来说:**镜像是类(Class),容器是对象(Instance)**。
|
||||
|
||||
- 一个镜像可以创建多个容器
|
||||
- 每个容器相互独立,互不影响
|
||||
- 容器可以被创建、启动、停止、删除、暂停
|
||||
|
||||
## 容器的本质
|
||||
|
||||
> 💡 **笔者认为,理解这一点是理解 Docker 的关键**
|
||||
|
||||
**容器的本质是一个特殊的进程。**
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 普通进程 │
|
||||
│ • 与其他进程共享系统资源 │
|
||||
│ • 可以看到其他进程 │
|
||||
│ • 共享网络和文件系统 │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 容器进程 │
|
||||
│ ┌───────────────────────────────────────────────────────┐ │
|
||||
│ │ • 有自己的进程空间(看不到宿主机上的其他进程) │ │
|
||||
│ │ • 有自己的网络(独立 IP、端口) │ │
|
||||
│ │ • 有自己的文件系统(独立的 root 目录) │ │
|
||||
│ │ • 有自己的用户(容器内的 root ≠ 宿主机的 root) │ │
|
||||
│ └───────────────────────────────────────────────────────┘ │
|
||||
│ 但仍然运行在宿主机的内核上 │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
这种隔离是通过 Linux 内核的 **Namespace** 技术实现的。
|
||||
|
||||
## 容器 vs 虚拟机:核心区别
|
||||
|
||||
很多初学者会混淆容器和虚拟机。笔者用一张图来说明:
|
||||
|
||||
```
|
||||
虚拟机 容器
|
||||
┌───────────────────────┐ ┌───────────────────────┐
|
||||
│ App A │ App B │ │ App A │ App B │
|
||||
├────────────┼──────────┤ ├────────────┼──────────┤
|
||||
│ Guest OS │ Guest OS │ │ Container │ Container│
|
||||
│ (完整系统) │ (完整系统)│ │ (仅应用) │ (仅应用) │
|
||||
├────────────┴──────────┤ └────────────┴──────────┤
|
||||
│ Hypervisor │ │ Docker Engine │
|
||||
├───────────────────────┤ ├───────────────────────┤
|
||||
│ Host OS │ │ Host OS │
|
||||
├───────────────────────┤ ├───────────────────────┤
|
||||
│ Hardware │ │ Hardware │
|
||||
└───────────────────────┘ └───────────────────────┘
|
||||
每个 VM 运行完整 OS 所有容器共享宿主机内核
|
||||
```
|
||||
|
||||
| 特性 | 容器 | 虚拟机 |
|
||||
|------|------|--------|
|
||||
| **隔离级别** | 进程级(Namespace) | 硬件级(Hypervisor) |
|
||||
| **启动时间** | 秒级(甚至毫秒) | 分钟级 |
|
||||
| **资源占用** | MB 级别 | GB 级别 |
|
||||
| **性能损耗** | 几乎为零 | 5-20% |
|
||||
| **内核** | 共享宿主机内核 | 各自独立内核 |
|
||||
|
||||
## 容器的存储层
|
||||
|
||||
### 镜像层 + 容器层
|
||||
|
||||
当容器运行时,Docker 会在镜像的只读层之上创建一个**可写层**(容器存储层):
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ 容器存储层(可读写) │ ← 容器运行时创建
|
||||
│ 运行时产生的文件变化记录在这里 │
|
||||
├─────────────────────────────────────────────┤
|
||||
│ 镜像第 N 层(只读) │
|
||||
├─────────────────────────────────────────────┤
|
||||
│ 镜像第 N-1 层(只读) │
|
||||
├─────────────────────────────────────────────┤
|
||||
│ ... │
|
||||
├─────────────────────────────────────────────┤
|
||||
│ 镜像第 1 层(只读) │ ← 基础镜像层
|
||||
└─────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Copy-on-Write(写时复制)
|
||||
|
||||
当容器需要修改镜像层中的文件时:
|
||||
|
||||
1. Docker 将该文件**复制**到容器存储层
|
||||
2. 在容器层中进行修改
|
||||
3. 原始镜像层保持不变
|
||||
|
||||
```
|
||||
读取文件:直接从镜像层读取(共享,高效)
|
||||
修改文件:复制到容器层,然后修改(只有这个容器能看到修改)
|
||||
```
|
||||
|
||||
### ⚠️ 容器存储层的生命周期
|
||||
|
||||
> **笔者特别强调**:这是新手最容易踩的坑!
|
||||
|
||||
**容器存储层与容器生命周期绑定。容器删除,数据就没了!**
|
||||
|
||||
```bash
|
||||
# 创建容器,写入数据
|
||||
$ docker run -it ubuntu bash
|
||||
root@abc123:/# echo "important data" > /data.txt
|
||||
root@abc123:/# exit
|
||||
|
||||
# 删除容器
|
||||
$ docker rm abc123
|
||||
|
||||
# 数据丢了!没有任何办法恢复!
|
||||
```
|
||||
|
||||
### 正确的数据持久化方式
|
||||
|
||||
按照 Docker 最佳实践,容器存储层应该保持**无状态**。需要持久化的数据应该使用:
|
||||
|
||||
| 方式 | 说明 | 适用场景 |
|
||||
|------|------|---------|
|
||||
| **[数据卷(Volume)](../07_data_network/data/volume.md)** | Docker 管理的存储 | 数据库、应用数据 |
|
||||
| **[绑定挂载(Bind Mount)](../07_data_network/data/bind-mounts.md)** | 挂载宿主机目录 | 开发时共享代码 |
|
||||
|
||||
```bash
|
||||
# 使用数据卷(推荐)
|
||||
$ docker run -v mydata:/var/lib/mysql mysql
|
||||
|
||||
# 使用绑定挂载
|
||||
$ docker run -v /host/path:/container/path nginx
|
||||
```
|
||||
|
||||
这些位置的读写**会跳过容器存储层**,直接写入宿主机,性能更好,也不会随容器删除而丢失。
|
||||
|
||||
## 容器的生命周期
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────┐
|
||||
│ 容器生命周期 │
|
||||
└──────────────────────────────────────────────────┘
|
||||
|
||||
docker create docker start docker stop
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌─────────┐ ┌─────────┐ ┌─────────┐
|
||||
│ Created │───────────▶│ Running │───────────▶│ Stopped │
|
||||
└─────────┘ └─────────┘ └─────────┘
|
||||
│ │ │
|
||||
│ │ docker pause │
|
||||
│ ▼ │
|
||||
│ ┌─────────┐ │
|
||||
│ │ Paused │ │
|
||||
│ └─────────┘ │
|
||||
│ │ │
|
||||
│ docker rm │ docker rm │
|
||||
└───────────────────────┴──────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────┐
|
||||
│ Deleted │
|
||||
└──────────┘
|
||||
```
|
||||
|
||||
### 常用生命周期命令
|
||||
|
||||
```bash
|
||||
# 创建并启动容器(最常用)
|
||||
$ docker run nginx
|
||||
|
||||
# 分步操作
|
||||
$ docker create nginx # 创建容器(不启动)
|
||||
$ docker start abc123 # 启动容器
|
||||
|
||||
# 停止容器
|
||||
$ docker stop abc123 # 优雅停止(发送 SIGTERM,等待后发送 SIGKILL)
|
||||
$ docker kill abc123 # 强制停止(直接发送 SIGKILL)
|
||||
|
||||
# 暂停/恢复(不常用,但有时有用)
|
||||
$ docker pause abc123 # 暂停容器内所有进程
|
||||
$ docker unpause abc123 # 恢复
|
||||
|
||||
# 删除容器
|
||||
$ docker rm abc123 # 删除已停止的容器
|
||||
$ docker rm -f abc123 # 强制删除运行中的容器
|
||||
```
|
||||
|
||||
## 容器与进程的关系
|
||||
|
||||
> **核心概念**:容器的生命周期 = 主进程(PID 1)的生命周期
|
||||
|
||||
```bash
|
||||
# 主进程运行,容器运行
|
||||
# 主进程退出,容器停止
|
||||
```
|
||||
|
||||
这就是为什么:
|
||||
|
||||
```bash
|
||||
# 这个容器会立即退出(bash 没有输入就退出了)
|
||||
$ docker run ubuntu
|
||||
|
||||
# 这个容器会持续运行(nginx 作为守护进程持续运行)
|
||||
$ docker run nginx
|
||||
```
|
||||
|
||||
详细解释请参考[后台运行](../05_container/daemon.md)章节。
|
||||
|
||||
## 容器的隔离性
|
||||
|
||||
Docker 容器通过以下 Namespace 实现隔离:
|
||||
|
||||
| Namespace | 隔离内容 | 效果 |
|
||||
|-----------|---------|------|
|
||||
| **PID** | 进程 ID | 容器内 PID 1 是应用进程,看不到宿主机其他进程 |
|
||||
| **NET** | 网络 | 独立的网络栈、IP 地址、端口 |
|
||||
| **MNT** | 文件系统 | 独立的根目录和挂载点 |
|
||||
| **UTS** | 主机名 | 独立的主机名和域名 |
|
||||
| **IPC** | 进程间通信 | 独立的信号量、消息队列 |
|
||||
| **USER** | 用户 | 独立的用户和组 ID |
|
||||
|
||||
> 想深入了解?请阅读[底层实现 - 命名空间](../13_implementation/namespace.md)。
|
||||
|
||||
## 本章小结
|
||||
|
||||
| 概念 | 要点 |
|
||||
|------|------|
|
||||
| **容器是什么** | 镜像的运行实例,本质是隔离的进程 |
|
||||
| **容器 vs 虚拟机** | 共享内核,更轻量,但隔离性较弱 |
|
||||
| **存储层** | 可写层随容器删除而消失 |
|
||||
| **数据持久化** | 使用 Volume 或 Bind Mount |
|
||||
| **生命周期** | 与主进程(PID 1)绑定 |
|
||||
|
||||
理解了镜像和容器,接下来让我们学习[仓库](repository.md)——存储和分发镜像的服务。
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [启动容器](../05_container/run.md):详细的容器启动选项
|
||||
- [后台运行](../05_container/daemon.md):理解容器为什么会"立即退出"
|
||||
- [进入容器](../05_container/attach_exec.md):如何操作运行中的容器
|
||||
- [数据管理](../07_data_network/README.md):Volume 和数据持久化详解
|
||||
@@ -1,222 +0,0 @@
|
||||
# Docker 镜像
|
||||
|
||||
## 一句话理解镜像
|
||||
|
||||
> **Docker 镜像是一个只读的模板,包含了运行应用所需的一切:代码、运行时、库、环境变量和配置文件。**
|
||||
|
||||
如果用一个类比:**镜像就像是一张光盘或 ISO 文件**。你可以用同一张光盘在不同电脑上安装系统,而光盘本身不会被修改。同样,一个镜像可以创建多个容器,而镜像本身保持不变。
|
||||
|
||||
## 镜像与操作系统的关系
|
||||
|
||||
我们都知道,操作系统分为**内核**和**用户空间**:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 用户空间 │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ 应用程序、工具、库、配置文件... │ │
|
||||
│ │ (这部分被打包成 Docker 镜像) │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Linux 内核 │
|
||||
│ (容器共享宿主机的内核) │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
对于 Linux 而言,内核启动后会挂载 `root` 文件系统来提供用户空间支持。**Docker 镜像**本质上就是一个 `root` 文件系统。
|
||||
|
||||
例如,官方镜像 `ubuntu:24.04` 包含了一套完整的 Ubuntu 24.04 最小系统的 root 文件系统——但**不包含 Linux 内核**(因为容器共享宿主机的内核)。
|
||||
|
||||
## 镜像包含什么?
|
||||
|
||||
Docker 镜像是一个特殊的文件系统,包含:
|
||||
|
||||
| 内容类型 | 示例 |
|
||||
|---------|------|
|
||||
| **程序文件** | 应用二进制文件、Python/Node 解释器 |
|
||||
| **库文件** | libc、OpenSSL、各种依赖库 |
|
||||
| **配置文件** | nginx.conf、my.cnf 等 |
|
||||
| **环境变量** | PATH、LANG 等预设值 |
|
||||
| **元数据** | 启动命令、暴露端口、数据卷定义 |
|
||||
|
||||
**关键特性**:
|
||||
- ✅ 镜像是**只读**的
|
||||
- ✅ 镜像**不包含**动态数据
|
||||
- ✅ 镜像构建后**内容不会改变**
|
||||
|
||||
## 分层存储:镜像的核心设计
|
||||
|
||||
### 为什么需要分层?
|
||||
|
||||
笔者认为,分层存储是 Docker 最巧妙的设计之一。
|
||||
|
||||
假设你有三个应用,都基于 Ubuntu 运行:
|
||||
|
||||
```
|
||||
传统方式(不分层):
|
||||
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
||||
│ App A │ │ App B │ │ App C │
|
||||
│ Ubuntu │ │ Ubuntu │ │ Ubuntu │
|
||||
│ 500MB │ │ 500MB │ │ 500MB │
|
||||
└─────────────┘ └─────────────┘ └─────────────┘
|
||||
总计:1.5GB ❌
|
||||
|
||||
Docker 分层方式:
|
||||
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
||||
│ App A │ │ App B │ │ App C │
|
||||
│ 50MB │ │ 30MB │ │ 40MB │
|
||||
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
|
||||
│ │ │
|
||||
└────────────────┼────────────────┘
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ Ubuntu │
|
||||
│ (共享)500MB │
|
||||
└─────────────────┘
|
||||
总计:620MB ✅
|
||||
```
|
||||
|
||||
### 分层是如何工作的?
|
||||
|
||||
笔者用一个实际的 Dockerfile 来解释分层:
|
||||
|
||||
```docker
|
||||
FROM ubuntu:24.04 # 第 1 层:基础系统(约 78MB)
|
||||
RUN apt-get update # 第 2 层:更新包索引
|
||||
RUN apt-get install nginx # 第 3 层:安装 nginx
|
||||
COPY app.conf /etc/nginx/ # 第 4 层:复制配置文件
|
||||
```
|
||||
|
||||
构建后的镜像结构:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ 第 4 层: COPY app.conf (只读) │ ← 最新添加的层
|
||||
├─────────────────────────────────────┤
|
||||
│ 第 3 层: nginx 安装文件 (只读) │
|
||||
├─────────────────────────────────────┤
|
||||
│ 第 2 层: apt 缓存更新 (只读) │
|
||||
├─────────────────────────────────────┤
|
||||
│ 第 1 层: Ubuntu 基础系统 (只读) │ ← 基础镜像层
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
每一层的特点:
|
||||
- **只读**:构建完成后不可修改
|
||||
- **可共享**:多个镜像可以共享相同的层
|
||||
- **有缓存**:未变化的层不会重新构建
|
||||
|
||||
### 分层存储的"陷阱"
|
||||
|
||||
> ⚠️ **笔者特别提醒**:理解这一点可以帮你避免构建出臃肿的镜像。
|
||||
|
||||
**关键原理**:每一层的文件变化会被记录,但**删除操作只是标记,不会真正减小镜像体积**。
|
||||
|
||||
```docker
|
||||
# 错误示范 ❌
|
||||
FROM ubuntu:24.04
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y build-essential # 安装编译工具(约 200MB)
|
||||
RUN make && make install # 编译应用
|
||||
RUN apt-get remove build-essential # 试图删除编译工具
|
||||
# 结果:镜像仍然包含 200MB 的编译工具!
|
||||
```
|
||||
|
||||
```docker
|
||||
# 正确做法 ✅
|
||||
FROM ubuntu:24.04
|
||||
RUN apt-get update && \
|
||||
apt-get install -y build-essential && \
|
||||
make && make install && \
|
||||
apt-get remove -y build-essential && \
|
||||
apt-get autoremove -y && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
# 在同一层完成安装、使用、清理
|
||||
```
|
||||
|
||||
### 查看镜像的分层
|
||||
|
||||
```bash
|
||||
# 查看镜像的历史(每层的构建记录)
|
||||
$ docker history nginx:latest
|
||||
|
||||
IMAGE CREATED CREATED BY SIZE
|
||||
a6bd71f48f68 2 weeks ago CMD ["nginx" "-g" "daemon off;"] 0B
|
||||
<missing> 2 weeks ago STOPSIGNAL SIGQUIT 0B
|
||||
<missing> 2 weeks ago EXPOSE map[80/tcp:{}] 0B
|
||||
<missing> 2 weeks ago ENTRYPOINT ["/docker-entrypoint.sh"] 0B
|
||||
<missing> 2 weeks ago COPY 30-tune-worker-processes.sh /docker-ent… 4.62kB
|
||||
...
|
||||
```
|
||||
|
||||
## 镜像的标识
|
||||
|
||||
Docker 镜像有多种标识方式:
|
||||
|
||||
### 1. 镜像名称和标签
|
||||
|
||||
格式:`[仓库地址/]仓库名[:标签]`
|
||||
|
||||
```bash
|
||||
# 完整格式
|
||||
registry.example.com/myproject/myapp:v1.2.3
|
||||
|
||||
# 简写(使用 Docker Hub)
|
||||
nginx:1.25
|
||||
ubuntu:24.04
|
||||
|
||||
# 省略标签(默认使用 latest)
|
||||
nginx # 等同于 nginx:latest
|
||||
```
|
||||
|
||||
### 2. 镜像 ID(Content-Addressable)
|
||||
|
||||
每个镜像有一个基于内容计算的唯一 ID:
|
||||
|
||||
```bash
|
||||
$ docker images
|
||||
REPOSITORY TAG IMAGE ID CREATED SIZE
|
||||
nginx latest a6bd71f48f68 2 weeks ago 187MB
|
||||
ubuntu 24.04 ca2b0f26964c 3 weeks ago 78.1MB
|
||||
```
|
||||
|
||||
### 3. 镜像摘要(Digest)
|
||||
|
||||
更精确的标识,基于镜像内容的 SHA256 哈希:
|
||||
|
||||
```bash
|
||||
$ docker images --digests
|
||||
REPOSITORY TAG DIGEST IMAGE ID
|
||||
nginx latest sha256:6db391d1c0cfb30588ba0bf72ea999404f2764184d8b8d10d89e8a9c6... a6bd71f48f68
|
||||
```
|
||||
|
||||
> 💡 笔者建议:在生产环境使用镜像摘要而非标签,因为标签可以被覆盖,但摘要是不可变的。
|
||||
|
||||
## 镜像的来源
|
||||
|
||||
Docker 镜像可以通过以下方式获取:
|
||||
|
||||
| 方式 | 说明 | 示例 |
|
||||
|------|------|------|
|
||||
| **从 Registry 拉取** | 最常用的方式 | `docker pull nginx` |
|
||||
| **从 Dockerfile 构建** | 自定义镜像 | `docker build -t myapp .` |
|
||||
| **从容器提交** | 保存容器状态(不推荐) | `docker commit` |
|
||||
| **从文件导入** | 离线传输 | `docker load < image.tar` |
|
||||
|
||||
## 本章小结
|
||||
|
||||
| 概念 | 要点 |
|
||||
|------|------|
|
||||
| **镜像是什么** | 只读的应用模板,包含运行所需的一切 |
|
||||
| **分层存储** | 多层叠加,共享基础层,节省空间 |
|
||||
| **只读特性** | 构建后不可修改,保证一致性 |
|
||||
| **层的陷阱** | 删除操作只是标记,不减小体积 |
|
||||
|
||||
理解了镜像,接下来让我们学习[容器](container.md)——镜像的运行实例。
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [获取镜像](../04_image/pull.md):从 Registry 下载镜像
|
||||
- [使用 Dockerfile 定制镜像](../04_image/build.md):创建自己的镜像
|
||||
- [Dockerfile 最佳实践](../15_appendix/best_practices.md):构建高质量镜像的技巧
|
||||
- [底层实现 - 联合文件系统](../13_implementation/ufs.md):深入理解分层存储的技术原理
|
||||
@@ -1,250 +0,0 @@
|
||||
# Docker Registry
|
||||
|
||||
## 一句话理解 Registry
|
||||
|
||||
> **Docker Registry 是存储和分发 Docker 镜像的服务,类似于代码的 GitHub 或包管理的 npm。**
|
||||
|
||||
镜像构建完成后,可以在当前机器上运行。但如果需要在其他服务器上使用这个镜像,就需要一个集中的存储和分发服务——这就是 Docker Registry。
|
||||
|
||||
## 核心概念
|
||||
|
||||
### Registry、仓库、标签的关系
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Docker Registry │
|
||||
│ (如 Docker Hub) │
|
||||
│ ┌─────────────────────────────────────────────────────────────┐ │
|
||||
│ │ Repository(仓库): nginx │ │
|
||||
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
|
||||
│ │ │ :latest │ │ :1.25 │ │ :1.24 │ │ :alpine │ ... │ │
|
||||
│ │ │ (tag) │ │ (tag) │ │ (tag) │ │ (tag) │ │ │
|
||||
│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │
|
||||
│ └─────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────────┐ │
|
||||
│ │ Repository(仓库): mysql │ │
|
||||
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
|
||||
│ │ │ :latest │ │ :8.0 │ │ :5.7 │ ... │ │
|
||||
│ │ └─────────┘ └─────────┘ └─────────┘ │ │
|
||||
│ └─────────────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
| 概念 | 说明 | 示例 |
|
||||
|------|------|------|
|
||||
| **Registry** | 存储镜像的服务 | Docker Hub、ghcr.io |
|
||||
| **Repository(仓库)** | 同一软件的镜像集合 | `nginx`、`mysql`、`mycompany/myapp` |
|
||||
| **Tag(标签)** | 仓库内的版本标识 | `latest`、`1.25`、`alpine` |
|
||||
|
||||
### 镜像的完整名称
|
||||
|
||||
```
|
||||
[registry地址/][用户名/]仓库名[:标签]
|
||||
```
|
||||
|
||||
示例:
|
||||
|
||||
```bash
|
||||
# 完整格式
|
||||
registry.example.com/mycompany/myapp:v1.2.3
|
||||
│ │ │ │
|
||||
│ │ │ └── 标签
|
||||
│ │ └── 仓库名
|
||||
│ └── 用户名/组织名
|
||||
└── Registry 地址
|
||||
|
||||
# Docker Hub 官方镜像(省略 registry 和用户名)
|
||||
nginx:1.25
|
||||
ubuntu:24.04
|
||||
|
||||
# Docker Hub 用户镜像
|
||||
jwilder/nginx-proxy:latest
|
||||
|
||||
# 其他 Registry
|
||||
ghcr.io/username/myapp:v1.0
|
||||
gcr.io/google-containers/pause:3.6
|
||||
```
|
||||
|
||||
> 💡 **笔者提示**:如果不指定 Registry 地址,默认使用 Docker Hub。如果不指定标签,默认使用 `latest`。
|
||||
|
||||
## 公共 Registry 服务
|
||||
|
||||
### Docker Hub(默认)
|
||||
|
||||
[Docker Hub](https://hub.docker.com/) 是最大的公共 Registry,也是 Docker 的默认 Registry。
|
||||
|
||||
**特点**:
|
||||
- 拥有大量[官方镜像](https://hub.docker.com/search?q=&type=image&image_filter=official)(nginx、mysql、redis 等)
|
||||
- 免费账户可以创建公开仓库
|
||||
- 付费账户支持私有仓库
|
||||
|
||||
```bash
|
||||
# 从 Docker Hub 拉取镜像
|
||||
$ docker pull nginx # 官方镜像
|
||||
$ docker pull bitnami/redis # 第三方镜像
|
||||
|
||||
# 推送镜像到 Docker Hub
|
||||
$ docker login
|
||||
$ docker push username/myapp:v1.0
|
||||
```
|
||||
|
||||
### 其他公共 Registry
|
||||
|
||||
| Registry | 地址 | 说明 |
|
||||
|----------|------|------|
|
||||
| **GitHub Container Registry** | ghcr.io | GitHub 提供,与 GitHub Actions 集成好 |
|
||||
| **Google Container Registry** | gcr.io | Google Cloud 提供,Kubernetes 镜像常用 |
|
||||
| **Quay.io** | quay.io | Red Hat 提供 |
|
||||
| **阿里云容器镜像服务** | registry.cn-*.aliyuncs.com | 国内访问快 |
|
||||
| **腾讯云容器镜像服务** | ccr.ccs.tencentyun.com | 国内访问快 |
|
||||
|
||||
## 镜像加速器
|
||||
|
||||
由于网络原因,在国内直接访问 Docker Hub 可能会很慢。可以配置**镜像加速器**(Registry Mirror)来加速下载。
|
||||
|
||||
```json
|
||||
// /etc/docker/daemon.json
|
||||
{
|
||||
"registry-mirrors": [
|
||||
"https://your-accelerator-url"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
详细配置方法请参考[镜像加速器](../install/mirror.md)章节。
|
||||
|
||||
> ⚠️ **笔者提醒**:镜像加速器的可用性经常变化,使用前建议先测试是否可用。
|
||||
|
||||
## 私有 Registry
|
||||
|
||||
对于企业用户,通常需要搭建私有 Registry 来存储内部镜像。
|
||||
|
||||
### 官方 Registry 镜像
|
||||
|
||||
Docker 官方提供了 [registry](https://hub.docker.com/_/registry/) 镜像,可以快速搭建私有 Registry:
|
||||
|
||||
```bash
|
||||
# 启动一个本地 Registry
|
||||
$ docker run -d -p 5000:5000 --name registry registry:2
|
||||
|
||||
# 推送镜像到本地 Registry
|
||||
$ docker tag myapp:v1.0 localhost:5000/myapp:v1.0
|
||||
$ docker push localhost:5000/myapp:v1.0
|
||||
|
||||
# 从本地 Registry 拉取
|
||||
$ docker pull localhost:5000/myapp:v1.0
|
||||
```
|
||||
|
||||
### 企业级解决方案
|
||||
|
||||
官方 Registry 功能较为基础,企业环境常用以下方案:
|
||||
|
||||
| 方案 | 特点 |
|
||||
|------|------|
|
||||
| **[Harbor](https://goharbor.io/)** | CNCF 项目,功能全面(用户管理、漏洞扫描、镜像签名) |
|
||||
| **[Nexus Repository](../repository/nexus3_registry.md)** | 支持多种制品类型(Docker、Maven、npm 等) |
|
||||
| **云厂商服务** | 阿里云 ACR、腾讯云 TCR、AWS ECR 等 |
|
||||
|
||||
笔者建议:
|
||||
- 小团队:可以先用官方 Registry,够用即可
|
||||
- 中大型团队:推荐 Harbor,功能完善且开源免费
|
||||
- 已使用云服务:直接用云厂商的 Registry 服务更省心
|
||||
|
||||
## 镜像的推送和拉取
|
||||
|
||||
### 完整工作流程
|
||||
|
||||
```
|
||||
开发者机器 Registry 生产服务器
|
||||
│ │ │
|
||||
│ docker build │ │
|
||||
│ 构建镜像 │ │
|
||||
│ │ │
|
||||
│ docker push ─────────────▶ │
|
||||
│ 推送镜像 │ 存储镜像 │
|
||||
│ │ │
|
||||
│ │ ◀───────────── docker pull │
|
||||
│ │ 拉取镜像 │
|
||||
│ │ │
|
||||
│ │ docker run │
|
||||
│ │ 运行容器 │
|
||||
```
|
||||
|
||||
### 常用命令
|
||||
|
||||
```bash
|
||||
# 登录 Registry
|
||||
$ docker login # 登录 Docker Hub
|
||||
$ docker login registry.example.com # 登录其他 Registry
|
||||
|
||||
# 拉取镜像
|
||||
$ docker pull nginx:1.25
|
||||
|
||||
# 标记镜像(准备推送)
|
||||
$ docker tag myapp:latest registry.example.com/myteam/myapp:v1.0
|
||||
|
||||
# 推送镜像
|
||||
$ docker push registry.example.com/myteam/myapp:v1.0
|
||||
|
||||
# 登出
|
||||
$ docker logout
|
||||
```
|
||||
|
||||
## 镜像的安全性
|
||||
|
||||
### 使用官方镜像
|
||||
|
||||
Docker Hub 的[官方镜像](https://hub.docker.com/search?q=&type=image&image_filter=official)(标有 "Official Image" 标识)经过 Docker 团队审核,相对更安全。
|
||||
|
||||
```bash
|
||||
# 官方镜像示例
|
||||
nginx # ✅ 官方
|
||||
mysql # ✅ 官方
|
||||
redis # ✅ 官方
|
||||
|
||||
# 第三方镜像(需要自行评估可信度)
|
||||
bitnami/redis # ⚠️ 需要评估
|
||||
someuser/myapp # ⚠️ 需要评估
|
||||
```
|
||||
|
||||
### 镜像签名
|
||||
|
||||
使用 Docker Content Trust (DCT) 验证镜像来源:
|
||||
|
||||
```bash
|
||||
# 启用镜像签名验证
|
||||
$ export DOCKER_CONTENT_TRUST=1
|
||||
|
||||
# 此后的 pull/push 会验证签名
|
||||
$ docker pull nginx:latest
|
||||
```
|
||||
|
||||
### 漏洞扫描
|
||||
|
||||
```bash
|
||||
# 使用 Docker Scout 扫描镜像漏洞
|
||||
$ docker scout cves nginx:latest
|
||||
|
||||
# 使用 Trivy(开源工具)
|
||||
$ trivy image nginx:latest
|
||||
```
|
||||
|
||||
## 本章小结
|
||||
|
||||
| 概念 | 要点 |
|
||||
|------|------|
|
||||
| **Registry** | 存储和分发镜像的服务 |
|
||||
| **仓库(Repository)** | 同一软件的镜像集合 |
|
||||
| **标签(Tag)** | 版本标识,默认为 latest |
|
||||
| **Docker Hub** | 默认的公共 Registry |
|
||||
| **私有 Registry** | 企业内部使用,推荐 Harbor |
|
||||
|
||||
现在你已经了解了 Docker 的三个核心概念:[镜像](image.md)、[容器](container.md)和仓库。接下来,让我们开始[安装 Docker](../install/README.md),动手实践!
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [Docker Hub](../repository/dockerhub.md):Docker Hub 的详细使用
|
||||
- [私有仓库](../repository/registry.md):搭建私有 Registry
|
||||
- [私有仓库高级配置](../repository/registry_auth.md):认证、TLS 配置
|
||||
- [镜像加速器](../install/mirror.md):配置镜像加速
|
||||
@@ -1,17 +0,0 @@
|
||||
# 开启实验特性
|
||||
|
||||
一些 docker 命令或功能仅当 **实验特性** 开启时才能使用,请按照以下方法进行设置。
|
||||
|
||||
## Docker CLI 的实验特性
|
||||
|
||||
从 `v20.10` 版本开始,Docker CLI 所有实验特性的命令均默认开启,无需再进行配置或设置系统环境变量。
|
||||
|
||||
## 开启 dockerd 的实验特性
|
||||
|
||||
编辑 `/etc/docker/daemon.json`,新增如下条目
|
||||
|
||||
```json
|
||||
{
|
||||
"experimental": true
|
||||
}
|
||||
```
|
||||
@@ -1,46 +0,0 @@
|
||||
# Windows 10/11
|
||||
|
||||
## 系统要求
|
||||
|
||||
[Docker Desktop for Windows](https://docs.docker.com/docker-for-windows/install/) 支持 64 位版本的 Windows 11 或 Windows 10(需开启 Hyper-V),推荐使用 Windows 11。
|
||||
|
||||
## 安装
|
||||
|
||||
**手动下载安装**
|
||||
|
||||
点击以下 [链接](https://desktop.docker.com/win/main/amd64/Docker%20Desktop%20Installer.exe) 下载 Docker Desktop for Windows。
|
||||
|
||||
下载好之后双击 `Docker Desktop Installer.exe` 开始安装。
|
||||
|
||||
**使用** [**winget**](https://docs.microsoft.com/zh-cn/windows/package-manager/) **安装**
|
||||
|
||||
```powershell
|
||||
$ winget install Docker.DockerDesktop
|
||||
```
|
||||
|
||||
## 在 WSL2 运行 Docker
|
||||
|
||||
若你的 Windows 版本为 Windows 10 专业版或家庭版 v1903 及以上版本可以使用 WSL2 运行 Docker,具体请查看 [Docker Desktop WSL 2 backend](https://docs.docker.com/docker-for-windows/wsl/)。
|
||||
|
||||
## 运行
|
||||
|
||||
在 Windows 搜索栏输入 **Docker** 点击 **Docker Desktop** 开始运行。
|
||||
|
||||

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

|
||||
|
||||
等待片刻,当鲸鱼图标静止时,说明 Docker 启动成功,之后你可以打开 PowerShell 使用 Docker。
|
||||
|
||||
> 推荐使用 [Windows Terminal](https://docs.microsoft.com/zh-cn/windows/terminal/get-started) 在终端使用 Docker。
|
||||
|
||||
## 镜像加速
|
||||
|
||||
如果在使用过程中发现拉取 Docker 镜像十分缓慢,可以配置 Docker [国内镜像加速](mirror.md)。
|
||||
|
||||
## 参考链接
|
||||
|
||||
* [官方文档](https://docs.docker.com/docker-for-windows/install/)
|
||||
* [WSL 2 Support is coming to Windows 10 Versions 1903 and 1909](https://devblogs.microsoft.com/commandline/wsl-2-support-is-coming-to-windows-10-versions-1903-and-1909/)
|
||||
@@ -1,221 +0,0 @@
|
||||
# ADD 更高级的复制文件
|
||||
|
||||
## 基本语法
|
||||
|
||||
```docker
|
||||
ADD [选项] <源路径>... <目标路径>
|
||||
ADD [选项] ["<源路径>", ... "<目标路径>"]
|
||||
```
|
||||
|
||||
`ADD` 在 `COPY` 基础上增加了两个功能:
|
||||
1. 自动解压 tar 压缩包
|
||||
2. 支持从 URL 下载文件(不推荐)
|
||||
|
||||
---
|
||||
|
||||
## ADD vs COPY
|
||||
|
||||
| 特性 | COPY | ADD |
|
||||
|------|------|-----|
|
||||
| 复制本地文件 | ✅ | ✅ |
|
||||
| 自动解压 tar | ❌ | ✅ |
|
||||
| 支持 URL | ❌ | ✅(不推荐) |
|
||||
| 行为可预测性 | ✅ 高 | ⚠️ 低 |
|
||||
| 推荐程度 | ✅ **优先使用** | 仅解压场景 |
|
||||
|
||||
> 笔者建议:除非需要自动解压 tar 文件,否则始终使用 COPY。明确的行为比隐式的魔法更好。
|
||||
|
||||
---
|
||||
|
||||
## 自动解压功能
|
||||
|
||||
### 基本用法
|
||||
|
||||
```docker
|
||||
# 自动解压 tar.gz 到目标目录
|
||||
ADD app.tar.gz /app/
|
||||
```
|
||||
|
||||
ADD 会识别并解压以下格式:
|
||||
- `.tar`
|
||||
- `.tar.gz` / `.tgz`
|
||||
- `.tar.bz2` / `.tbz2`
|
||||
- `.tar.xz` / `.txz`
|
||||
|
||||
### 实际应用
|
||||
|
||||
官方基础镜像通常使用 ADD 解压根文件系统:
|
||||
|
||||
```docker
|
||||
FROM scratch
|
||||
ADD ubuntu-noble-core-cloudimg-amd64-root.tar.gz /
|
||||
```
|
||||
|
||||
### 解压过程
|
||||
|
||||
```
|
||||
ADD app.tar.gz /app/
|
||||
│
|
||||
├─ 识别 .tar.gz 格式
|
||||
├─ 自动解压
|
||||
└─ 内容放入 /app/
|
||||
|
||||
app.tar.gz 包含: /app/ 目录结果:
|
||||
├── src/ ├── src/
|
||||
│ └── main.py │ └── main.py
|
||||
└── config.json └── config.json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## URL 下载功能(不推荐)
|
||||
|
||||
### 基本用法
|
||||
|
||||
```docker
|
||||
# 从 URL 下载文件
|
||||
ADD https://example.com/app.zip /app/app.zip
|
||||
```
|
||||
|
||||
### 为什么不推荐
|
||||
|
||||
| 问题 | 说明 |
|
||||
|------|------|
|
||||
| 权限固定 | 下载的文件权限为 600,通常需要额外 RUN 修改 |
|
||||
| 不会解压 | URL 下载的压缩包不会自动解压 |
|
||||
| 缓存问题 | URL 内容变化时不会重新下载 |
|
||||
| 层数增加 | 需要额外 RUN 清理 |
|
||||
|
||||
### 推荐替代方案
|
||||
|
||||
```docker
|
||||
# ❌ 不推荐:使用 ADD 下载
|
||||
ADD https://example.com/app.tar.gz /tmp/
|
||||
RUN tar -xzf /tmp/app.tar.gz -C /app && rm /tmp/app.tar.gz
|
||||
|
||||
# ✅ 推荐:使用 RUN + curl
|
||||
RUN curl -fsSL https://example.com/app.tar.gz | tar -xz -C /app
|
||||
```
|
||||
|
||||
优势:
|
||||
- 一条 RUN 完成下载、解压、清理
|
||||
- 减少镜像层数
|
||||
- 更清晰的构建意图
|
||||
|
||||
---
|
||||
|
||||
## 修改文件所有者
|
||||
|
||||
```docker
|
||||
ADD --chown=node:node app.tar.gz /app/
|
||||
ADD --chown=1000:1000 files/ /app/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 何时使用 ADD
|
||||
|
||||
### ✅ 适合使用 ADD
|
||||
|
||||
```docker
|
||||
# 解压本地 tar 文件
|
||||
FROM scratch
|
||||
ADD rootfs.tar.gz /
|
||||
|
||||
# 解压应用包
|
||||
ADD dist.tar.gz /app/
|
||||
```
|
||||
|
||||
### ❌ 不适合使用 ADD
|
||||
|
||||
```docker
|
||||
# 复制普通文件(用 COPY)
|
||||
ADD package.json /app/ # ❌
|
||||
COPY package.json /app/ # ✅
|
||||
|
||||
# 下载文件(用 RUN + curl)
|
||||
ADD https://example.com/file / # ❌
|
||||
RUN curl -fsSL ... -o /file # ✅
|
||||
|
||||
# 需要保留 tar 不解压(用 COPY)
|
||||
ADD archive.tar.gz /archives/ # ❌ 会解压
|
||||
COPY archive.tar.gz /archives/ # ✅ 保持原样
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 缓存行为
|
||||
|
||||
ADD 可能导致构建缓存失效:
|
||||
|
||||
```docker
|
||||
# 如果 app.tar.gz 内容变化,此层及后续层都需重建
|
||||
ADD app.tar.gz /app/
|
||||
RUN npm install
|
||||
```
|
||||
|
||||
**优化建议**:
|
||||
|
||||
```docker
|
||||
# 先复制依赖文件
|
||||
COPY package*.json /app/
|
||||
RUN npm install
|
||||
|
||||
# 再添加应用代码
|
||||
ADD app.tar.gz /app/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 默认使用 COPY
|
||||
|
||||
```docker
|
||||
# ✅ 大多数场景使用 COPY
|
||||
COPY . /app/
|
||||
```
|
||||
|
||||
### 2. 仅在需要解压时使用 ADD
|
||||
|
||||
```docker
|
||||
# ✅ 自动解压场景
|
||||
ADD app.tar.gz /app/
|
||||
```
|
||||
|
||||
### 3. 不要用 ADD 下载文件
|
||||
|
||||
```docker
|
||||
# ❌ 避免
|
||||
ADD https://example.com/file.tar.gz /tmp/
|
||||
|
||||
# ✅ 推荐
|
||||
RUN curl -fsSL https://example.com/file.tar.gz | tar -xz -C /app
|
||||
```
|
||||
|
||||
### 4. 解压后清理
|
||||
|
||||
```docker
|
||||
# 如果需要控制解压过程
|
||||
COPY app.tar.gz /tmp/
|
||||
RUN tar -xzf /tmp/app.tar.gz -C /app && \
|
||||
rm /tmp/app.tar.gz
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 本章小结
|
||||
|
||||
| 场景 | 推荐指令 |
|
||||
|------|---------|
|
||||
| 复制普通文件 | `COPY` |
|
||||
| 复制目录 | `COPY` |
|
||||
| 自动解压 tar | `ADD` |
|
||||
| 从 URL 下载 | `RUN curl` |
|
||||
| 保持 tar 不解压 | `COPY` |
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [COPY 复制文件](copy.md):基本复制操作
|
||||
- [多阶段构建](../multistage-builds.md):减少镜像体积
|
||||
- [最佳实践](../../15_appendix/best_practices.md):Dockerfile 编写指南
|
||||
@@ -1,238 +0,0 @@
|
||||
# ARG 构建参数
|
||||
|
||||
## 基本语法
|
||||
|
||||
```docker
|
||||
ARG <参数名>[=<默认值>]
|
||||
```
|
||||
|
||||
`ARG` 指令定义构建时的变量,可以在 `docker build` 时通过 `--build-arg` 传入。
|
||||
|
||||
---
|
||||
|
||||
## ARG vs ENV
|
||||
|
||||
| 特性 | ARG | ENV |
|
||||
|------|-----|-----|
|
||||
| **生效时间** | 仅构建时 | 构建时 + 运行时 |
|
||||
| **持久性** | 构建后消失 | 写入镜像 |
|
||||
| **覆盖方式** | `docker build --build-arg` | `docker run -e` |
|
||||
| **适用场景** | 构建参数(版本号等) | 应用配置 |
|
||||
| **可见性** | `docker history` 可见 | `docker inspect` 可见 |
|
||||
|
||||
```
|
||||
构建时 运行时
|
||||
├─ ARG VERSION=1.0 │ (ARG 已消失)
|
||||
├─ ENV APP_ENV=prod │ APP_ENV=prod(仍存在)
|
||||
└─ RUN echo $VERSION │
|
||||
```
|
||||
|
||||
> ⚠️ **安全提示**:不要用 ARG 传递密码等敏感信息,`docker history` 可以查看所有 ARG 值。
|
||||
|
||||
---
|
||||
|
||||
## 基本用法
|
||||
|
||||
### 定义和使用
|
||||
|
||||
```docker
|
||||
# 定义有默认值的 ARG
|
||||
ARG NODE_VERSION=20
|
||||
|
||||
# 使用 ARG
|
||||
FROM node:${NODE_VERSION}-alpine
|
||||
RUN echo "Using Node.js $NODE_VERSION"
|
||||
```
|
||||
|
||||
### 构建时覆盖
|
||||
|
||||
```bash
|
||||
# 使用默认值
|
||||
$ docker build -t myapp .
|
||||
|
||||
# 覆盖默认值
|
||||
$ docker build --build-arg NODE_VERSION=18 -t myapp .
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ARG 的作用域
|
||||
|
||||
### FROM 之前的 ARG
|
||||
|
||||
```docker
|
||||
# FROM 之前的 ARG 只能用于 FROM 指令
|
||||
ARG REGISTRY=docker.io
|
||||
ARG IMAGE_NAME=node
|
||||
|
||||
FROM ${REGISTRY}/${IMAGE_NAME}:20
|
||||
|
||||
# ❌ 这里无法使用上面的 ARG
|
||||
RUN echo $REGISTRY # 输出空
|
||||
```
|
||||
|
||||
### FROM 之后重新声明
|
||||
|
||||
```docker
|
||||
ARG NODE_VERSION=20
|
||||
|
||||
FROM node:${NODE_VERSION}-alpine
|
||||
|
||||
# 需要再次声明才能使用
|
||||
ARG NODE_VERSION
|
||||
RUN echo "Node version: $NODE_VERSION"
|
||||
```
|
||||
|
||||
### 多阶段构建中的 ARG
|
||||
|
||||
```docker
|
||||
ARG BASE_VERSION=alpine
|
||||
|
||||
FROM node:20-${BASE_VERSION} AS builder
|
||||
# 需要重新声明
|
||||
ARG NODE_VERSION=20
|
||||
RUN echo "Building with Node $NODE_VERSION"
|
||||
|
||||
FROM node:20-${BASE_VERSION}
|
||||
# 每个阶段都需要重新声明
|
||||
ARG NODE_VERSION=20
|
||||
RUN echo "Running with Node $NODE_VERSION"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 常见使用场景
|
||||
|
||||
### 1. 控制基础镜像版本
|
||||
|
||||
```docker
|
||||
ARG ALPINE_VERSION=3.19
|
||||
FROM alpine:${ALPINE_VERSION}
|
||||
```
|
||||
|
||||
```bash
|
||||
$ docker build --build-arg ALPINE_VERSION=3.18 .
|
||||
```
|
||||
|
||||
### 2. 设置软件版本
|
||||
|
||||
```docker
|
||||
ARG NGINX_VERSION=1.25.0
|
||||
|
||||
RUN curl -fsSL https://nginx.org/download/nginx-${NGINX_VERSION}.tar.gz | tar -xz
|
||||
```
|
||||
|
||||
### 3. 配置构建环境
|
||||
|
||||
```docker
|
||||
ARG BUILD_ENV=production
|
||||
ARG ENABLE_DEBUG=false
|
||||
|
||||
RUN if [ "$ENABLE_DEBUG" = "true" ]; then \
|
||||
npm install --include=dev; \
|
||||
else \
|
||||
npm install --production; \
|
||||
fi
|
||||
```
|
||||
|
||||
### 4. 配置私有仓库
|
||||
|
||||
```docker
|
||||
ARG NPM_TOKEN
|
||||
|
||||
RUN echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc && \
|
||||
npm install && \
|
||||
rm ~/.npmrc
|
||||
```
|
||||
|
||||
```bash
|
||||
# 构建时传入 token
|
||||
$ docker build --build-arg NPM_TOKEN=xxx .
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 将 ARG 传递给 ENV
|
||||
|
||||
如果需要在运行时使用 ARG 的值:
|
||||
|
||||
```docker
|
||||
ARG VERSION=1.0.0
|
||||
|
||||
# 将 ARG 传递给 ENV
|
||||
ENV APP_VERSION=$VERSION
|
||||
|
||||
# 运行时可用
|
||||
CMD echo "App version: $APP_VERSION"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 预定义 ARG
|
||||
|
||||
Docker 提供了一些预定义的 ARG,无需声明即可使用:
|
||||
|
||||
| ARG | 说明 |
|
||||
|-----|------|
|
||||
| `HTTP_PROXY` | HTTP 代理 |
|
||||
| `HTTPS_PROXY` | HTTPS 代理 |
|
||||
| `NO_PROXY` | 不使用代理的地址 |
|
||||
| `FTP_PROXY` | FTP 代理 |
|
||||
|
||||
```bash
|
||||
# 构建时使用代理
|
||||
$ docker build --build-arg HTTP_PROXY=http://proxy:8080 .
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 为 ARG 提供合理默认值
|
||||
|
||||
```docker
|
||||
# ✅ 好:有默认值
|
||||
ARG NODE_VERSION=20
|
||||
|
||||
# ⚠️ 需要每次传入
|
||||
ARG NODE_VERSION
|
||||
```
|
||||
|
||||
### 2. 不要用 ARG 存储敏感信息
|
||||
|
||||
```docker
|
||||
# ❌ 错误:密码会被记录在镜像历史中
|
||||
ARG DB_PASSWORD
|
||||
RUN echo "password=$DB_PASSWORD" > /app/.env
|
||||
|
||||
# ✅ 正确:使用 secrets 或运行时环境变量
|
||||
```
|
||||
|
||||
### 3. 使用 ARG 提高构建灵活性
|
||||
|
||||
```docker
|
||||
ARG BASE_IMAGE=python:3.12-slim
|
||||
FROM ${BASE_IMAGE}
|
||||
|
||||
# 可以构建不同基础镜像的版本
|
||||
# docker build --build-arg BASE_IMAGE=python:3.11-alpine .
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 本章小结
|
||||
|
||||
| 要点 | 说明 |
|
||||
|------|------|
|
||||
| **作用** | 定义构建时变量 |
|
||||
| **语法** | `ARG NAME=value` |
|
||||
| **覆盖** | `docker build --build-arg NAME=value` |
|
||||
| **作用域** | FROM 之后需要重新声明 |
|
||||
| **vs ENV** | ARG 仅构建时,ENV 构建+运行时 |
|
||||
| **安全** | 不要存储敏感信息 |
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [ENV 设置环境变量](env.md):运行时环境变量
|
||||
- [FROM 指令](../../04_image/build.md):基础镜像指定
|
||||
- [多阶段构建](../multistage-builds.md):复杂构建场景
|
||||
@@ -1,268 +0,0 @@
|
||||
# CMD 容器启动命令
|
||||
|
||||
## 什么是 CMD
|
||||
|
||||
`CMD` 指令用于指定容器启动时默认执行的命令。它定义了容器的"主进程"。
|
||||
|
||||
> **核心概念**:容器的生命周期 = 主进程的生命周期。CMD 指定的命令就是这个主进程。
|
||||
|
||||
---
|
||||
|
||||
## 语法格式
|
||||
|
||||
CMD 有三种格式:
|
||||
|
||||
| 格式 | 语法 | 推荐程度 |
|
||||
|------|------|---------|
|
||||
| **exec 格式** | `CMD ["可执行文件", "参数1", "参数2"]` | ✅ **推荐** |
|
||||
| **shell 格式** | `CMD 命令 参数1 参数2` | ⚠️ 简单场景 |
|
||||
| **参数格式** | `CMD ["参数1", "参数2"]` | 配合 ENTRYPOINT |
|
||||
|
||||
### exec 格式(推荐)
|
||||
|
||||
```docker
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
CMD ["python", "app.py"]
|
||||
CMD ["node", "server.js"]
|
||||
```
|
||||
|
||||
**优点**:
|
||||
- 直接执行指定程序,是容器的 PID 1
|
||||
- 正确接收信号(如 SIGTERM)
|
||||
- 无需 shell 解析
|
||||
|
||||
### shell 格式
|
||||
|
||||
```docker
|
||||
CMD echo "Hello World"
|
||||
CMD nginx -g "daemon off;"
|
||||
```
|
||||
|
||||
**实际执行**:会被包装为 `sh -c`
|
||||
|
||||
```docker
|
||||
# 你写的
|
||||
CMD echo $HOME
|
||||
|
||||
# 实际执行的
|
||||
CMD ["sh", "-c", "echo $HOME"]
|
||||
```
|
||||
|
||||
**优点**:可以使用环境变量、管道等 shell 特性
|
||||
**缺点**:主进程是 sh,信号无法正确传递给应用
|
||||
|
||||
---
|
||||
|
||||
## exec 格式 vs shell 格式
|
||||
|
||||
| 特性 | exec 格式 | shell 格式 |
|
||||
|------|----------|-----------|
|
||||
| 主进程 | 指定的程序 | `/bin/sh` |
|
||||
| 信号传递 | ✅ 正确 | ❌ 无法传递 |
|
||||
| 环境变量 | ❌ 需要 shell 包装 | ✅ 自动解析 |
|
||||
| 推荐使用 | ✅ 大多数场景 | 需要 shell 特性时 |
|
||||
|
||||
### 信号传递问题示例
|
||||
|
||||
```docker
|
||||
# ❌ shell 格式:docker stop 会超时
|
||||
CMD node server.js
|
||||
# 实际是 sh -c "node server.js"
|
||||
# SIGTERM 发给 sh,不会传递给 node
|
||||
|
||||
# ✅ exec 格式:docker stop 正常工作
|
||||
CMD ["node", "server.js"]
|
||||
# SIGTERM 直接发给 node
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 运行时覆盖 CMD
|
||||
|
||||
`docker run` 后的命令会覆盖 Dockerfile 中的 CMD:
|
||||
|
||||
```bash
|
||||
# ubuntu 默认 CMD 是 /bin/bash
|
||||
$ docker run -it ubuntu # 进入 bash
|
||||
$ docker run ubuntu cat /etc/os-release # 覆盖为 cat 命令
|
||||
```
|
||||
|
||||
```
|
||||
Dockerfile: docker run 命令:
|
||||
CMD ["/bin/bash"] + cat /etc/os-release
|
||||
│ │
|
||||
└───────► 被覆盖 ◄───────┘
|
||||
↓
|
||||
执行: cat /etc/os-release
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 经典错误:容器立即退出
|
||||
|
||||
### 错误示例
|
||||
|
||||
```docker
|
||||
# ❌ 容器启动后立即退出
|
||||
CMD service nginx start
|
||||
```
|
||||
|
||||
### 原因分析
|
||||
|
||||
```
|
||||
1. CMD service nginx start
|
||||
↓ 被转换为
|
||||
2. CMD ["sh", "-c", "service nginx start"]
|
||||
↓
|
||||
3. sh 启动,执行 service 命令
|
||||
↓
|
||||
4. service 命令将 nginx 放到后台
|
||||
↓
|
||||
5. service 命令结束,sh 退出
|
||||
↓
|
||||
6. 容器主进程(sh)退出 → 容器停止
|
||||
```
|
||||
|
||||
### 正确做法
|
||||
|
||||
```docker
|
||||
# ✅ 让 nginx 在前台运行
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CMD vs ENTRYPOINT
|
||||
|
||||
| 指令 | 用途 | 运行时行为 |
|
||||
|------|------|-----------|
|
||||
| **CMD** | 默认命令 | `docker run` 参数会**覆盖**它 |
|
||||
| **ENTRYPOINT** | 入口点 | `docker run` 参数会**追加**到它后面 |
|
||||
|
||||
### 单独使用 CMD
|
||||
|
||||
```docker
|
||||
# Dockerfile
|
||||
CMD ["curl", "-s", "http://example.com"]
|
||||
```
|
||||
|
||||
```bash
|
||||
$ docker run myimage # 执行默认命令
|
||||
$ docker run myimage curl -v ... # 完全覆盖
|
||||
```
|
||||
|
||||
### 搭配 ENTRYPOINT
|
||||
|
||||
```docker
|
||||
# Dockerfile
|
||||
ENTRYPOINT ["curl", "-s"]
|
||||
CMD ["http://example.com"]
|
||||
```
|
||||
|
||||
```bash
|
||||
$ docker run myimage # curl -s http://example.com
|
||||
$ docker run myimage http://other.com # curl -s http://other.com(参数覆盖)
|
||||
```
|
||||
|
||||
详见 [ENTRYPOINT 入口点](entrypoint.md) 章节。
|
||||
|
||||
---
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 优先使用 exec 格式
|
||||
|
||||
```docker
|
||||
# ✅ 推荐
|
||||
CMD ["python", "app.py"]
|
||||
|
||||
# ⚠️ 仅在需要 shell 特性时使用
|
||||
CMD ["sh", "-c", "echo $PATH && python app.py"]
|
||||
```
|
||||
|
||||
### 2. 确保应用在前台运行
|
||||
|
||||
```docker
|
||||
# ✅ 前台运行
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
CMD ["apache2ctl", "-D", "FOREGROUND"]
|
||||
CMD ["java", "-jar", "app.jar"]
|
||||
|
||||
# ❌ 不要使用后台服务命令
|
||||
CMD service nginx start
|
||||
CMD systemctl start nginx
|
||||
```
|
||||
|
||||
### 3. 使用双引号
|
||||
|
||||
```docker
|
||||
# ✅ 正确:双引号
|
||||
CMD ["node", "server.js"]
|
||||
|
||||
# ❌ 错误:单引号(JSON 不支持)
|
||||
CMD ['node', 'server.js']
|
||||
```
|
||||
|
||||
### 4. 配合 ENTRYPOINT 使用
|
||||
|
||||
```docker
|
||||
# 用于可配置参数的场景
|
||||
ENTRYPOINT ["python", "app.py"]
|
||||
CMD ["--port", "8080"]
|
||||
|
||||
# 运行时可以覆盖端口
|
||||
$ docker run myapp --port 9000
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q: CMD 可以写多个吗?
|
||||
|
||||
不可以。多个 CMD 只有最后一个生效:
|
||||
|
||||
```docker
|
||||
CMD ["echo", "first"]
|
||||
CMD ["echo", "second"] # 只有这个生效
|
||||
```
|
||||
|
||||
### Q: 如何在 CMD 中使用环境变量?
|
||||
|
||||
```docker
|
||||
# 方法1:使用 shell 格式
|
||||
CMD echo "Port is $PORT"
|
||||
|
||||
# 方法2:显式使用 sh -c
|
||||
CMD ["sh", "-c", "echo Port is $PORT"]
|
||||
```
|
||||
|
||||
### Q: 为什么我的容器不响应 Ctrl+C?
|
||||
|
||||
可能是使用了 shell 格式,信号被 sh 吃掉了:
|
||||
|
||||
```docker
|
||||
# ❌ 信号无法传递
|
||||
CMD python app.py
|
||||
|
||||
# ✅ 信号正确传递
|
||||
CMD ["python", "app.py"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 本章小结
|
||||
|
||||
| 要点 | 说明 |
|
||||
|------|------|
|
||||
| **作用** | 指定容器启动时的默认命令 |
|
||||
| **推荐格式** | exec 格式 `CMD ["程序", "参数"]` |
|
||||
| **覆盖方式** | `docker run image 新命令` |
|
||||
| **与 ENTRYPOINT** | CMD 作为 ENTRYPOINT 的默认参数 |
|
||||
| **核心原则** | 应用必须在前台运行 |
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [ENTRYPOINT 入口点](entrypoint.md):固定的启动命令
|
||||
- [后台运行](../../05_container/daemon.md):容器前台/后台概念
|
||||
- [最佳实践](../../15_appendix/best_practices.md):Dockerfile 编写指南
|
||||
@@ -1,261 +0,0 @@
|
||||
# COPY 复制文件
|
||||
|
||||
## 基本语法
|
||||
|
||||
```docker
|
||||
COPY [选项] <源路径>... <目标路径>
|
||||
COPY [选项] ["<源路径1>", "<源路径2>", ... "<目标路径>"]
|
||||
```
|
||||
|
||||
`COPY` 指令将构建上下文中的文件或目录复制到镜像内。
|
||||
|
||||
---
|
||||
|
||||
## 基本用法
|
||||
|
||||
### 复制单个文件
|
||||
|
||||
```docker
|
||||
# 复制文件到指定目录
|
||||
COPY package.json /app/
|
||||
|
||||
# 复制文件并重命名
|
||||
COPY config.json /app/settings.json
|
||||
```
|
||||
|
||||
### 复制多个文件
|
||||
|
||||
```docker
|
||||
# 复制多个指定文件
|
||||
COPY package.json package-lock.json /app/
|
||||
|
||||
# 使用通配符
|
||||
COPY *.json /app/
|
||||
COPY src/*.js /app/src/
|
||||
```
|
||||
|
||||
### 复制目录
|
||||
|
||||
```docker
|
||||
# 复制整个目录的内容(不是目录本身)
|
||||
COPY src/ /app/src/
|
||||
```
|
||||
|
||||
> ⚠️ **注意**:复制目录时,复制的是目录的**内容**,不包含目录本身。
|
||||
|
||||
```
|
||||
构建上下文: 镜像内:
|
||||
src/ /app/src/
|
||||
├── index.js → ├── index.js
|
||||
└── utils.js └── utils.js
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 通配符规则
|
||||
|
||||
COPY 支持 Go 的 `filepath.Match` 通配符规则:
|
||||
|
||||
| 通配符 | 说明 | 示例 |
|
||||
|--------|------|------|
|
||||
| `*` | 匹配任意字符序列 | `*.json` |
|
||||
| `?` | 匹配单个字符 | `config?.json` |
|
||||
| `[abc]` | 匹配括号内任一字符 | `[abc].txt` |
|
||||
| `[a-z]` | 匹配范围内字符 | `file[0-9].txt` |
|
||||
|
||||
```docker
|
||||
COPY hom* /mydir/ # home.txt, homework.md 等
|
||||
COPY hom?.txt /mydir/ # home.txt, homy.txt 等
|
||||
COPY app[0-9].js /app/ # app0.js ~ app9.js
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 目标路径
|
||||
|
||||
### 绝对路径
|
||||
|
||||
```docker
|
||||
COPY app.js /usr/src/app/
|
||||
```
|
||||
|
||||
### 相对路径(基于 WORKDIR)
|
||||
|
||||
```docker
|
||||
WORKDIR /app
|
||||
COPY package.json ./ # 复制到 /app/package.json
|
||||
COPY src/ ./src/ # 复制到 /app/src/
|
||||
```
|
||||
|
||||
### 自动创建目录
|
||||
|
||||
如果目标目录不存在,Docker 会自动创建:
|
||||
|
||||
```docker
|
||||
# /app/config/ 不存在也会自动创建
|
||||
COPY settings.json /app/config/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 修改文件所有者
|
||||
|
||||
使用 `--chown` 选项设置文件的用户和组:
|
||||
|
||||
```docker
|
||||
# 使用用户名和组名
|
||||
COPY --chown=node:node package.json /app/
|
||||
|
||||
# 使用 UID 和 GID
|
||||
COPY --chown=1000:1000 . /app/
|
||||
|
||||
# 只指定用户
|
||||
COPY --chown=node . /app/
|
||||
```
|
||||
|
||||
> 💡 结合 `USER` 指令使用,确保应用以非 root 用户运行。
|
||||
|
||||
---
|
||||
|
||||
## 保留文件元数据
|
||||
|
||||
COPY 会保留源文件的元数据:
|
||||
- 读、写、执行权限
|
||||
- 修改时间
|
||||
|
||||
这对于脚本文件特别重要:
|
||||
|
||||
```docker
|
||||
# start.sh 的可执行权限会被保留
|
||||
COPY start.sh /app/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## COPY vs ADD
|
||||
|
||||
| 特性 | COPY | ADD |
|
||||
|------|------|-----|
|
||||
| 复制本地文件 | ✅ | ✅ |
|
||||
| 自动解压 tar | ❌ | ✅ |
|
||||
| 支持 URL | ❌ | ✅(不推荐) |
|
||||
| 推荐程度 | ✅ **推荐** | ⚠️ 特殊场景使用 |
|
||||
|
||||
```docker
|
||||
# 推荐:使用 COPY
|
||||
COPY app.tar.gz /app/
|
||||
RUN tar -xzf /app/app.tar.gz
|
||||
|
||||
# ADD 会自动解压(行为不明显,不推荐)
|
||||
ADD app.tar.gz /app/
|
||||
```
|
||||
|
||||
> 笔者建议:除非需要自动解压 tar 文件,否则始终使用 COPY。明确的行为比隐式的魔法更好。
|
||||
|
||||
---
|
||||
|
||||
## 多阶段构建中的 COPY
|
||||
|
||||
### 从其他构建阶段复制
|
||||
|
||||
```docker
|
||||
# 构建阶段
|
||||
FROM node:20 AS builder
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
RUN npm install
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
# 生产阶段
|
||||
FROM nginx:alpine
|
||||
COPY --from=builder /app/dist /usr/share/nginx/html
|
||||
```
|
||||
|
||||
### 使用 --link 优化缓存(BuildKit)
|
||||
|
||||
```docker
|
||||
# 使用 --link 后,文件以独立层添加,不依赖前序指令
|
||||
COPY --link --from=builder /app/dist /usr/share/nginx/html
|
||||
```
|
||||
|
||||
`--link` 的优势:
|
||||
- 更高效利用构建缓存
|
||||
- 并行化构建过程
|
||||
- 加速多阶段构建
|
||||
|
||||
---
|
||||
|
||||
## .dockerignore
|
||||
|
||||
使用 `.dockerignore` 排除不需要复制的文件:
|
||||
|
||||
```gitignore
|
||||
# .dockerignore
|
||||
node_modules
|
||||
.git
|
||||
.env
|
||||
*.log
|
||||
Dockerfile
|
||||
.dockerignore
|
||||
```
|
||||
|
||||
这可以:
|
||||
- 减小构建上下文大小
|
||||
- 加速构建
|
||||
- 避免复制敏感文件
|
||||
|
||||
---
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 利用缓存,先复制依赖文件
|
||||
|
||||
```docker
|
||||
# ✅ 好:先复制依赖定义,再安装,最后复制代码
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm install
|
||||
COPY . .
|
||||
|
||||
# ❌ 差:一次性复制所有文件,代码变更会导致重新 npm install
|
||||
COPY . .
|
||||
RUN npm install
|
||||
```
|
||||
|
||||
### 2. 使用 .dockerignore
|
||||
|
||||
```docker
|
||||
# 确保 node_modules 不被复制
|
||||
COPY . .
|
||||
# .dockerignore 中应包含 node_modules
|
||||
```
|
||||
|
||||
### 3. 明确复制路径
|
||||
|
||||
```docker
|
||||
# ✅ 好:明确的路径
|
||||
COPY src/ /app/src/
|
||||
COPY package.json /app/
|
||||
|
||||
# ❌ 差:过于宽泛
|
||||
COPY . .
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 本章小结
|
||||
|
||||
| 操作 | 示例 |
|
||||
|------|------|
|
||||
| 复制文件 | `COPY app.js /app/` |
|
||||
| 复制多个文件 | `COPY *.json /app/` |
|
||||
| 复制目录内容 | `COPY src/ /app/src/` |
|
||||
| 修改所有者 | `COPY --chown=node:node . /app/` |
|
||||
| 从构建阶段复制 | `COPY --from=builder /app/dist ./` |
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [ADD 指令](add.md):复制和解压
|
||||
- [WORKDIR 指令](workdir.md):设置工作目录
|
||||
- [多阶段构建](../multistage-builds.md):优化镜像大小
|
||||
- [最佳实践](../../15_appendix/best_practices.md):Dockerfile 编写指南
|
||||
@@ -1,306 +0,0 @@
|
||||
# ENTRYPOINT 入口点
|
||||
|
||||
## 什么是 ENTRYPOINT
|
||||
|
||||
`ENTRYPOINT` 指定容器启动时运行的入口程序。与 CMD 不同,ENTRYPOINT 定义的命令不会被 `docker run` 的参数覆盖,而是**接收这些参数**。
|
||||
|
||||
> **核心作用**:让镜像像一个可执行程序一样使用,`docker run` 的参数作为这个程序的参数。
|
||||
|
||||
---
|
||||
|
||||
## 语法格式
|
||||
|
||||
| 格式 | 语法 | 推荐程度 |
|
||||
|------|------|---------|
|
||||
| **exec 格式** | `ENTRYPOINT ["可执行文件", "参数1"]` | ✅ **推荐** |
|
||||
| **shell 格式** | `ENTRYPOINT 命令 参数` | ⚠️ 不推荐 |
|
||||
|
||||
```docker
|
||||
# exec 格式(推荐)
|
||||
ENTRYPOINT ["nginx", "-g", "daemon off;"]
|
||||
|
||||
# shell 格式(不推荐)
|
||||
ENTRYPOINT nginx -g "daemon off;"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ENTRYPOINT vs CMD
|
||||
|
||||
### 核心区别
|
||||
|
||||
| 特性 | ENTRYPOINT | CMD |
|
||||
|------|------------|-----|
|
||||
| **定位** | 固定的入口程序 | 默认参数 |
|
||||
| **docker run 参数** | 追加为参数 | 完全覆盖 |
|
||||
| **覆盖方式** | `--entrypoint` | 直接指定命令 |
|
||||
| **适用场景** | 把镜像当命令用 | 提供默认行为 |
|
||||
|
||||
### 行为对比
|
||||
|
||||
```docker
|
||||
# 只用 CMD
|
||||
CMD ["curl", "-s", "http://example.com"]
|
||||
```
|
||||
|
||||
```bash
|
||||
$ docker run myimage # curl -s http://example.com
|
||||
$ docker run myimage -v # 执行 -v(错误!)
|
||||
$ docker run myimage curl -v ... # curl -v ...(完全替换)
|
||||
```
|
||||
|
||||
```docker
|
||||
# 只用 ENTRYPOINT
|
||||
ENTRYPOINT ["curl", "-s"]
|
||||
```
|
||||
|
||||
```bash
|
||||
$ docker run myimage # curl -s(缺参数)
|
||||
$ docker run myimage http://example.com # curl -s http://example.com ✓
|
||||
```
|
||||
|
||||
```docker
|
||||
# ENTRYPOINT + CMD 组合(推荐)
|
||||
ENTRYPOINT ["curl", "-s"]
|
||||
CMD ["http://example.com"]
|
||||
```
|
||||
|
||||
```bash
|
||||
$ docker run myimage # curl -s http://example.com(默认)
|
||||
$ docker run myimage http://other.com # curl -s http://other.com ✓
|
||||
$ docker run myimage -v http://other.com # curl -s -v http://other.com ✓
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 场景一:让镜像像命令一样使用
|
||||
|
||||
### 需求
|
||||
|
||||
创建一个查询公网 IP 的"命令"镜像。
|
||||
|
||||
### 使用 CMD 的问题
|
||||
|
||||
```docker
|
||||
FROM ubuntu:24.04
|
||||
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
|
||||
CMD ["curl", "-s", "http://myip.ipip.net"]
|
||||
```
|
||||
|
||||
```bash
|
||||
$ docker run myip # ✓ 正常工作
|
||||
当前 IP:61.148.226.66
|
||||
|
||||
$ docker run myip -i # ✗ 错误!
|
||||
exec: "-i": executable file not found
|
||||
# -i 替换了整个 CMD,被当作可执行文件
|
||||
```
|
||||
|
||||
### 使用 ENTRYPOINT 解决
|
||||
|
||||
```docker
|
||||
FROM ubuntu:24.04
|
||||
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
|
||||
ENTRYPOINT ["curl", "-s", "http://myip.ipip.net"]
|
||||
```
|
||||
|
||||
```bash
|
||||
$ docker run myip # ✓ 正常工作
|
||||
当前 IP:61.148.226.66
|
||||
|
||||
$ docker run myip -i # ✓ 添加 -i 参数
|
||||
HTTP/1.1 200 OK
|
||||
...
|
||||
当前 IP:61.148.226.66
|
||||
```
|
||||
|
||||
### 交互图示
|
||||
|
||||
```
|
||||
ENTRYPOINT ["curl", "-s", "http://myip.ipip.net"]
|
||||
│
|
||||
docker run myip -i
|
||||
│
|
||||
▼
|
||||
curl -s http://myip.ipip.net -i
|
||||
└─────────────────────────────┘
|
||||
ENTRYPOINT + docker run 参数
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 场景二:启动前的准备工作
|
||||
|
||||
### 需求
|
||||
|
||||
在启动主服务前执行初始化脚本(如数据库迁移、权限设置)。
|
||||
|
||||
### 实现方式
|
||||
|
||||
```docker
|
||||
FROM redis:7-alpine
|
||||
COPY docker-entrypoint.sh /usr/local/bin/
|
||||
ENTRYPOINT ["docker-entrypoint.sh"]
|
||||
CMD ["redis-server"]
|
||||
```
|
||||
|
||||
**docker-entrypoint.sh**:
|
||||
|
||||
```bash
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
# 准备工作
|
||||
echo "Initializing..."
|
||||
|
||||
# 如果第一个参数是 redis-server,以 redis 用户运行
|
||||
if [ "$1" = 'redis-server' ]; then
|
||||
chown -R redis:redis /data
|
||||
exec gosu redis "$@"
|
||||
fi
|
||||
|
||||
# 其他命令直接执行
|
||||
exec "$@"
|
||||
```
|
||||
|
||||
### 工作流程
|
||||
|
||||
```
|
||||
docker run redis docker run redis bash
|
||||
│ │
|
||||
▼ ▼
|
||||
docker-entrypoint.sh redis-server docker-entrypoint.sh bash
|
||||
│ │
|
||||
├─ 初始化 ├─ 初始化
|
||||
├─ chown -R redis:redis /data │
|
||||
└─ exec gosu redis redis-server └─ exec bash
|
||||
(以 redis 用户运行) (以 root 用户运行)
|
||||
```
|
||||
|
||||
### 关键点
|
||||
|
||||
1. **exec "$@"**:用传入的参数替换当前进程,确保信号正确传递
|
||||
2. **条件判断**:根据 CMD 不同执行不同逻辑
|
||||
3. **用户切换**:使用 `gosu` 切换用户(比 `su` 更适合容器)
|
||||
|
||||
---
|
||||
|
||||
## 场景三:带参数的应用
|
||||
|
||||
```docker
|
||||
FROM python:3.12-slim
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
RUN pip install -r requirements.txt
|
||||
|
||||
ENTRYPOINT ["python", "app.py"]
|
||||
CMD ["--host", "0.0.0.0", "--port", "8080"]
|
||||
```
|
||||
|
||||
```bash
|
||||
# 使用默认参数
|
||||
$ docker run myapp
|
||||
# 执行: python app.py --host 0.0.0.0 --port 8080
|
||||
|
||||
# 覆盖参数
|
||||
$ docker run myapp --host 0.0.0.0 --port 9000
|
||||
# 执行: python app.py --host 0.0.0.0 --port 9000
|
||||
|
||||
# 完全不同的参数
|
||||
$ docker run myapp --help
|
||||
# 执行: python app.py --help
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 覆盖 ENTRYPOINT
|
||||
|
||||
使用 `--entrypoint` 参数覆盖:
|
||||
|
||||
```bash
|
||||
# 正常运行
|
||||
$ docker run myimage
|
||||
|
||||
# 覆盖 ENTRYPOINT 进入 shell 调试
|
||||
$ docker run --entrypoint /bin/sh myimage
|
||||
|
||||
# 覆盖 ENTRYPOINT 并传入参数
|
||||
$ docker run --entrypoint /bin/cat myimage /etc/os-release
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ENTRYPOINT 与 CMD 组合表
|
||||
|
||||
| ENTRYPOINT | CMD | 最终执行命令 |
|
||||
|------------|-----|-------------|
|
||||
| 无 | 无 | 无(容器无法启动) |
|
||||
| 无 | `["cmd", "p1"]` | `cmd p1` |
|
||||
| `["ep", "p1"]` | 无 | `ep p1` |
|
||||
| `["ep", "p1"]` | `["cmd", "p2"]` | `ep p1 cmd p2` |
|
||||
| `ep p1`(shell) | `["cmd", "p2"]` | `/bin/sh -c "ep p1"`(CMD 被忽略) |
|
||||
|
||||
> ⚠️ **注意**:shell 格式的 ENTRYPOINT 会忽略 CMD!
|
||||
|
||||
---
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 使用 exec 格式
|
||||
|
||||
```docker
|
||||
# ✅ 推荐
|
||||
ENTRYPOINT ["python", "app.py"]
|
||||
|
||||
# ❌ 避免 shell 格式
|
||||
ENTRYPOINT python app.py
|
||||
```
|
||||
|
||||
### 2. 提供有意义的默认参数
|
||||
|
||||
```docker
|
||||
ENTRYPOINT ["nginx"]
|
||||
CMD ["-g", "daemon off;"]
|
||||
```
|
||||
|
||||
### 3. 入口脚本使用 exec
|
||||
|
||||
```bash
|
||||
#!/bin/sh
|
||||
# 准备工作...
|
||||
|
||||
# 使用 exec 替换当前进程
|
||||
exec "$@"
|
||||
```
|
||||
|
||||
### 4. 处理信号
|
||||
|
||||
确保 ENTRYPOINT 脚本能正确传递信号:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
trap 'kill -TERM $PID' TERM INT
|
||||
|
||||
# 启动应用
|
||||
app "$@" &
|
||||
PID=$!
|
||||
|
||||
# 等待应用退出
|
||||
wait $PID
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 本章小结
|
||||
|
||||
| ENTRYPOINT | CMD | 适用场景 |
|
||||
|------------|-----|---------|
|
||||
| ✓ | ✗ | 镜像作为固定命令使用 |
|
||||
| ✗ | ✓ | 简单的默认命令 |
|
||||
| ✓ | ✓ | **推荐**:固定命令 + 可配置参数 |
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [CMD 容器启动命令](cmd.md):默认命令
|
||||
- [最佳实践](../../15_appendix/best_practices.md):启动命令设计
|
||||
- [后台运行](../../05_container/daemon.md):前台/后台概念
|
||||
@@ -1,248 +0,0 @@
|
||||
# ENV 设置环境变量
|
||||
|
||||
## 基本语法
|
||||
|
||||
```docker
|
||||
# 格式一:单个变量
|
||||
ENV <key> <value>
|
||||
|
||||
# 格式二:多个变量(推荐)
|
||||
ENV <key1>=<value1> <key2>=<value2> ...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 基本用法
|
||||
|
||||
### 设置单个变量
|
||||
|
||||
```docker
|
||||
ENV NODE_VERSION 20.10.0
|
||||
ENV APP_ENV production
|
||||
```
|
||||
|
||||
### 设置多个变量
|
||||
|
||||
```docker
|
||||
ENV NODE_VERSION=20.10.0 \
|
||||
APP_ENV=production \
|
||||
APP_NAME="My Application"
|
||||
```
|
||||
|
||||
> 💡 包含空格的值用双引号括起来。
|
||||
|
||||
---
|
||||
|
||||
## 环境变量的作用
|
||||
|
||||
### 1. 后续指令中使用
|
||||
|
||||
```docker
|
||||
ENV NODE_VERSION=20.10.0
|
||||
|
||||
# 在 RUN 中使用
|
||||
RUN curl -fsSL https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.xz \
|
||||
| tar -xJ -C /usr/local --strip-components=1
|
||||
|
||||
# 在 WORKDIR 中使用
|
||||
ENV APP_HOME=/app
|
||||
WORKDIR $APP_HOME
|
||||
|
||||
# 在 COPY 中使用
|
||||
COPY . $APP_HOME
|
||||
```
|
||||
|
||||
### 2. 容器运行时使用
|
||||
|
||||
```docker
|
||||
ENV DATABASE_URL=postgres://localhost/mydb
|
||||
```
|
||||
|
||||
应用代码中可以读取:
|
||||
|
||||
```python
|
||||
import os
|
||||
db_url = os.environ.get('DATABASE_URL')
|
||||
```
|
||||
|
||||
```javascript
|
||||
const dbUrl = process.env.DATABASE_URL;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 支持环境变量的指令
|
||||
|
||||
以下指令可以使用 `$变量名` 或 `${变量名}` 格式:
|
||||
|
||||
| 指令 | 示例 |
|
||||
|------|------|
|
||||
| `RUN` | `RUN echo $VERSION` |
|
||||
| `CMD` | `CMD ["sh", "-c", "echo $HOME"]` |
|
||||
| `ENTRYPOINT` | 同上 |
|
||||
| `COPY` | `COPY . $APP_HOME` |
|
||||
| `ADD` | `ADD app.tar.gz $APP_HOME` |
|
||||
| `WORKDIR` | `WORKDIR $APP_HOME` |
|
||||
| `EXPOSE` | `EXPOSE $PORT` |
|
||||
| `VOLUME` | `VOLUME $DATA_DIR` |
|
||||
| `USER` | `USER $USERNAME` |
|
||||
| `LABEL` | `LABEL version=$VERSION` |
|
||||
| `FROM` | `FROM node:$NODE_VERSION` |
|
||||
|
||||
---
|
||||
|
||||
## 运行时覆盖
|
||||
|
||||
使用 `-e` 或 `--env` 覆盖 Dockerfile 中定义的环境变量:
|
||||
|
||||
```bash
|
||||
# 覆盖单个变量
|
||||
$ docker run -e APP_ENV=development myimage
|
||||
|
||||
# 覆盖多个变量
|
||||
$ docker run -e APP_ENV=development -e DEBUG=true myimage
|
||||
|
||||
# 从环境变量文件读取
|
||||
$ docker run --env-file .env myimage
|
||||
```
|
||||
|
||||
### .env 文件格式
|
||||
|
||||
```bash
|
||||
# .env
|
||||
APP_ENV=development
|
||||
DEBUG=true
|
||||
DATABASE_URL=postgres://localhost/mydb
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ENV vs ARG
|
||||
|
||||
| 特性 | ENV | ARG |
|
||||
|------|-----|-----|
|
||||
| **生效时间** | 构建时 + 运行时 | 仅构建时 |
|
||||
| **持久性** | 写入镜像,运行时可用 | 构建后消失 |
|
||||
| **覆盖方式** | `docker run -e` | `docker build --build-arg` |
|
||||
| **适用场景** | 应用配置 | 构建参数(如版本号) |
|
||||
|
||||
### 组合使用
|
||||
|
||||
```docker
|
||||
# ARG 接收构建时参数
|
||||
ARG NODE_VERSION=20
|
||||
|
||||
# ENV 保存到运行时
|
||||
ENV NODE_VERSION=$NODE_VERSION
|
||||
|
||||
# 后续指令使用
|
||||
RUN curl -fsSL https://nodejs.org/dist/v${NODE_VERSION}/...
|
||||
```
|
||||
|
||||
```bash
|
||||
# 构建时指定版本
|
||||
$ docker build --build-arg NODE_VERSION=18 -t myapp .
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 统一管理版本号
|
||||
|
||||
```docker
|
||||
# ✅ 好:版本集中管理
|
||||
ENV NGINX_VERSION=1.25.0 \
|
||||
NODE_VERSION=20.10.0 \
|
||||
PYTHON_VERSION=3.12.0
|
||||
|
||||
RUN apt-get install nginx=${NGINX_VERSION}
|
||||
|
||||
# ❌ 差:版本分散在各处
|
||||
RUN apt-get install nginx=1.25.0
|
||||
```
|
||||
|
||||
### 2. 不要存储敏感信息
|
||||
|
||||
```docker
|
||||
# ❌ 错误:密码写入镜像
|
||||
ENV DB_PASSWORD=secret123
|
||||
|
||||
# ✅ 正确:运行时传入
|
||||
# docker run -e DB_PASSWORD=xxx myimage
|
||||
```
|
||||
|
||||
### 3. 为应用提供合理默认值
|
||||
|
||||
```docker
|
||||
ENV APP_ENV=production \
|
||||
APP_PORT=8080 \
|
||||
LOG_LEVEL=info
|
||||
```
|
||||
|
||||
### 4. 使用有意义的变量名
|
||||
|
||||
```docker
|
||||
# ✅ 好:清晰的命名
|
||||
ENV REDIS_HOST=localhost \
|
||||
REDIS_PORT=6379
|
||||
|
||||
# ❌ 差:模糊的命名
|
||||
ENV HOST=localhost \
|
||||
PORT=6379
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q: 环境变量在 CMD 中不展开
|
||||
|
||||
exec 格式不会自动展开环境变量:
|
||||
|
||||
```docker
|
||||
# ❌ 不会展开 $PORT
|
||||
CMD ["python", "app.py", "--port", "$PORT"]
|
||||
|
||||
# ✅ 使用 shell 格式或显式调用 sh
|
||||
CMD ["sh", "-c", "python app.py --port $PORT"]
|
||||
```
|
||||
|
||||
### Q: 如何查看容器的环境变量
|
||||
|
||||
```bash
|
||||
$ docker inspect mycontainer --format '{{json .Config.Env}}'
|
||||
$ docker exec mycontainer env
|
||||
```
|
||||
|
||||
### Q: 多行 ENV 还是多个 ENV
|
||||
|
||||
```docker
|
||||
# ✅ 推荐:减少层数
|
||||
ENV VAR1=value1 \
|
||||
VAR2=value2 \
|
||||
VAR3=value3
|
||||
|
||||
# ⚠️ 多个 ENV 会创建多层
|
||||
ENV VAR1=value1
|
||||
ENV VAR2=value2
|
||||
ENV VAR3=value3
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 本章小结
|
||||
|
||||
| 要点 | 说明 |
|
||||
|------|------|
|
||||
| **语法** | `ENV KEY=value` |
|
||||
| **作用范围** | 构建时 + 运行时 |
|
||||
| **覆盖方式** | `docker run -e KEY=value` |
|
||||
| **与 ARG** | ARG 仅构建时,ENV 持久化到运行时 |
|
||||
| **安全** | 不要存储敏感信息 |
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [ARG 构建参数](arg.md):构建时变量
|
||||
- [Compose 环境变量](../../compose/compose_file.md):Compose 中的环境变量
|
||||
- [最佳实践](../../15_appendix/best_practices.md):Dockerfile 编写指南
|
||||
@@ -1,219 +0,0 @@
|
||||
# EXPOSE 声明端口
|
||||
|
||||
## 基本语法
|
||||
|
||||
```docker
|
||||
EXPOSE <端口> [<端口>/<协议>...]
|
||||
```
|
||||
|
||||
`EXPOSE` 声明容器运行时提供服务的端口。这是一个**文档性质的声明**,告诉使用者容器会监听哪些端口。
|
||||
|
||||
---
|
||||
|
||||
## 基本用法
|
||||
|
||||
```docker
|
||||
# 声明单个端口
|
||||
EXPOSE 80
|
||||
|
||||
# 声明多个端口
|
||||
EXPOSE 80 443
|
||||
|
||||
# 声明 TCP 和 UDP 端口
|
||||
EXPOSE 80/tcp
|
||||
EXPOSE 53/udp
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## EXPOSE 的作用
|
||||
|
||||
### 1. 文档说明
|
||||
|
||||
告诉镜像使用者,容器将在哪些端口提供服务:
|
||||
|
||||
```docker
|
||||
# 使用者一看就知道这是 web 应用
|
||||
EXPOSE 80 443
|
||||
```
|
||||
|
||||
```bash
|
||||
# 查看镜像暴露的端口
|
||||
$ docker inspect nginx --format '{{.Config.ExposedPorts}}'
|
||||
map[80/tcp:{}]
|
||||
```
|
||||
|
||||
### 2. 配合 -P 使用
|
||||
|
||||
使用 `docker run -P` 时,Docker 会自动映射 EXPOSE 的端口到宿主机随机端口:
|
||||
|
||||
```docker
|
||||
# Dockerfile
|
||||
EXPOSE 80
|
||||
```
|
||||
|
||||
```bash
|
||||
$ docker run -P nginx
|
||||
$ docker port $(docker ps -q)
|
||||
80/tcp -> 0.0.0.0:32768
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## EXPOSE vs -p
|
||||
|
||||
| 特性 | EXPOSE | -p |
|
||||
|------|--------|-----|
|
||||
| **位置** | Dockerfile | docker run 命令 |
|
||||
| **作用** | 声明/文档 | 实际端口映射 |
|
||||
| **是否必需** | 否 | 是(外部访问时) |
|
||||
| **映射发生时** | 不发生 | 运行时发生 |
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────────────────────┐
|
||||
│ EXPOSE 80 │
|
||||
│ ↓ │
|
||||
│ 仅声明意图 │
|
||||
│ └───────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ docker run -p │
|
||||
│ ↓ │
|
||||
│ ┌─────────────────────┐ │
|
||||
│ │ 实际端口映射 │ │
|
||||
│ │ 宿主机 ←→ 容器 │ │
|
||||
│ └─────────────────────┘ │
|
||||
└────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 没有 EXPOSE 也能 -p
|
||||
|
||||
```docker
|
||||
# 即使没有 EXPOSE,也可以使用 -p
|
||||
FROM nginx
|
||||
# 没有 EXPOSE
|
||||
```
|
||||
|
||||
```bash
|
||||
# 仍然可以映射端口
|
||||
$ docker run -p 8080:80 mynginx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 常见误解
|
||||
|
||||
### 误解:EXPOSE 会打开端口
|
||||
|
||||
```docker
|
||||
# ❌ 错误理解:这不会让容器可从外部访问
|
||||
EXPOSE 80
|
||||
```
|
||||
|
||||
EXPOSE 不会:
|
||||
- 自动进行端口映射
|
||||
- 让服务可从外部访问
|
||||
- 在容器启动时开启端口监听
|
||||
|
||||
EXPOSE 只是元数据声明。容器是否实际监听该端口,取决于容器内的应用。
|
||||
|
||||
### 正确理解
|
||||
|
||||
```docker
|
||||
# Dockerfile
|
||||
FROM nginx
|
||||
EXPOSE 80 # 1. 声明:这个容器会在 80 端口提供服务
|
||||
```
|
||||
|
||||
```bash
|
||||
# 运行:需要 -p 才能从外部访问
|
||||
$ docker run -p 8080:80 nginx # 2. 映射:宿主机 8080 → 容器 80
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 总是声明应用使用的端口
|
||||
|
||||
```docker
|
||||
# Web 服务
|
||||
FROM nginx
|
||||
EXPOSE 80 443
|
||||
|
||||
# 数据库
|
||||
FROM postgres
|
||||
EXPOSE 5432
|
||||
|
||||
# Redis
|
||||
FROM redis
|
||||
EXPOSE 6379
|
||||
```
|
||||
|
||||
### 2. 使用明确的协议
|
||||
|
||||
```docker
|
||||
# 默认是 TCP
|
||||
EXPOSE 80
|
||||
|
||||
# 明确指定 UDP
|
||||
EXPOSE 53/udp
|
||||
|
||||
# 同时支持 TCP 和 UDP
|
||||
EXPOSE 53/tcp 53/udp
|
||||
```
|
||||
|
||||
### 3. 与应用实际端口保持一致
|
||||
|
||||
```docker
|
||||
# ✅ 好:EXPOSE 与应用端口一致
|
||||
ENV PORT=3000
|
||||
EXPOSE 3000
|
||||
CMD ["node", "server.js"]
|
||||
|
||||
# ❌ 差:EXPOSE 与应用端口不一致(误导)
|
||||
EXPOSE 80
|
||||
CMD ["node", "server.js"] # 实际监听 3000
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 使用环境变量
|
||||
|
||||
```docker
|
||||
ARG PORT=80
|
||||
EXPOSE $PORT
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 在 Compose 中
|
||||
|
||||
```yaml
|
||||
services:
|
||||
web:
|
||||
build: .
|
||||
ports:
|
||||
- "8080:80" # 映射端口(类似 -p)
|
||||
expose:
|
||||
- "80" # 仅声明(类似 EXPOSE)
|
||||
```
|
||||
|
||||
`expose` 在 Compose 中仅用于容器间通信的文档说明,不进行端口映射。
|
||||
|
||||
---
|
||||
|
||||
## 本章小结
|
||||
|
||||
| 要点 | 说明 |
|
||||
|------|------|
|
||||
| **作用** | 声明容器提供服务的端口(文档) |
|
||||
| **不会** | 自动映射端口或开放外部访问 |
|
||||
| **配合** | `docker run -P` 自动映射 |
|
||||
| **外部访问** | 需要 `-p 宿主机端口:容器端口` |
|
||||
| **语法** | `EXPOSE 80` 或 `EXPOSE 80/tcp` |
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [网络配置](../../network/README.md):Docker 网络详解
|
||||
- [端口映射](../../network/port_bindingbindingbinding.md):-p 参数详解
|
||||
- [Compose 端口](../../compose/compose_file.md):Compose 中的端口配置
|
||||
@@ -1,206 +0,0 @@
|
||||
# HEALTHCHECK 健康检查
|
||||
|
||||
## 基本语法
|
||||
|
||||
```docker
|
||||
HEALTHCHECK [选项] CMD <命令>
|
||||
HEALTHCHECK NONE
|
||||
```
|
||||
|
||||
`HEALTHCHECK` 指令告诉 Docker 如何判断容器状态是否正常。这是保障服务高可用的重要机制。
|
||||
|
||||
---
|
||||
|
||||
## 为什么需要 HEALTHCHECK
|
||||
|
||||
在没有 HEALTHCHECK 之前,Docker 只能通过**进程退出码**来判断容器状态。
|
||||
|
||||
**问题场景**:
|
||||
- Web 服务死锁,无法响应请求,但进程仍在运行
|
||||
- 数据库正在启动中,尚未准备好接受连接
|
||||
- 应用陷入死循环,CPU 爆满但进程存活
|
||||
|
||||
**引入 HEALTHCHECK 后**:
|
||||
Docker 定期执行指定的检查命令,根据返回值判断容器是否"健康"。
|
||||
|
||||
```
|
||||
容器状态转换:
|
||||
Starting ──成功──> Healthy ──失败N次──> Unhealthy
|
||||
▲ │
|
||||
└──────成功──────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 基本用法
|
||||
|
||||
### Web 服务检查
|
||||
|
||||
```docker
|
||||
FROM nginx
|
||||
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
|
||||
CMD curl -fs http://localhost/ || exit 1
|
||||
```
|
||||
|
||||
### 命令返回值
|
||||
|
||||
- `0`: 成功 (healthy)
|
||||
- `1`: 失败 (unhealthy)
|
||||
- `2`: 保留值 (不使用)
|
||||
|
||||
### 常用选项
|
||||
|
||||
| 选项 | 说明 | 默认值 |
|
||||
|------|------|--------|
|
||||
| `--interval` | 两次检查的间隔 | 30s |
|
||||
| `--timeout` | 检查命令的超时时间 | 30s |
|
||||
| `--start-period` | 启动缓冲期(期间失败不计入次数) | 0s |
|
||||
| `--retries` | 连续失败多少次标记为 unhealthy | 3 |
|
||||
|
||||
---
|
||||
|
||||
## 屏蔽健康检查
|
||||
|
||||
如果基础镜像定义了 HEALTHCHECK,但你不想使用它:
|
||||
|
||||
```docker
|
||||
FROM my-base-image
|
||||
HEALTHCHECK NONE
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 常见检查脚本
|
||||
|
||||
### HTTP 服务
|
||||
|
||||
使用 `curl` 或 `wget`:
|
||||
|
||||
```docker
|
||||
# 使用 curl
|
||||
HEALTHCHECK CMD curl -f http://localhost/ || exit 1
|
||||
|
||||
# 使用 wget (Alpine 默认包含)
|
||||
HEALTHCHECK CMD wget -q --spider http://localhost/ || exit 1
|
||||
```
|
||||
|
||||
### 数据库
|
||||
|
||||
```docker
|
||||
# MySQL
|
||||
HEALTHCHECK CMD mysqladmin ping -h localhost || exit 1
|
||||
|
||||
# Redis
|
||||
HEALTHCHECK CMD redis-cli ping || exit 1
|
||||
```
|
||||
|
||||
### 自定义脚本
|
||||
|
||||
```docker
|
||||
COPY healthcheck.sh /usr/local/bin/
|
||||
HEALTHCHECK CMD ["healthcheck.sh"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 在 Compose 中使用
|
||||
|
||||
可以在 `docker-compose.yml` 中覆盖或定义健康检查:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
web:
|
||||
image: nginx
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost"]
|
||||
interval: 1m30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
```
|
||||
|
||||
带健康检查的依赖启动:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
web:
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy # 等待 db 变健康才启动 web
|
||||
db:
|
||||
image: mysql
|
||||
healthcheck:
|
||||
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 查看健康状态
|
||||
|
||||
```bash
|
||||
# 查看容器状态(包含健康信息)
|
||||
$ docker ps
|
||||
CONTAINER ID STATUS
|
||||
abc123 Up 1 minute (healthy)
|
||||
def456 Up 2 minutes (unhealthy)
|
||||
|
||||
# 查看详细健康日志
|
||||
$ docker inspect --format '{{json .State.Health}}' mycontainer | jq
|
||||
{
|
||||
"Status": "healthy",
|
||||
"FailingStreak": 0,
|
||||
"Log": [
|
||||
{
|
||||
"Start": "...",
|
||||
"End": "...",
|
||||
"ExitCode": 0,
|
||||
"Output": "..."
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 避免副作用
|
||||
|
||||
健康检查会被频繁执行,不要在检查脚本中进行写操作或消耗大量资源的操作。
|
||||
|
||||
### 2. 使用轻量级工具
|
||||
|
||||
优先使用镜像中已有的工具(如 `wget`),避免为了健康检查安装庞大的依赖(如 `curl`)。
|
||||
|
||||
### 3. 设置合理的 Start Period
|
||||
|
||||
应用启动可能需要时间(如 Java 应用)。设置 `--start-period` 可以防止在启动阶段因检查失败而误判。
|
||||
|
||||
```docker
|
||||
# 给应用 1 分钟启动时间
|
||||
HEALTHCHECK --start-period=60s CMD curl -f http://localhost/ || exit 1
|
||||
```
|
||||
|
||||
### 4. 只检查核心依赖
|
||||
|
||||
健康检查应主要关注**当前服务**是否可用,而不是检查其下游依赖(数据库等)。下游依赖的检查应由应用逻辑处理。
|
||||
|
||||
---
|
||||
|
||||
## 本章小结
|
||||
|
||||
| 要点 | 说明 |
|
||||
|------|------|
|
||||
| **作用** | 检测容器应用是否真实可用 |
|
||||
| **命令** | `HEALTHCHECK [选项] CMD command` |
|
||||
| **状态** | starting, healthy, unhealthy |
|
||||
| **Compose** | 支持 `condition: service_healthy` 依赖 |
|
||||
| **注意** | 避免副作用,节省资源 |
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [CMD 容器启动命令](cmd.md):启动主进程
|
||||
- [Compose 模板文件](../../compose/compose_file.md):Compose 中的健康检查
|
||||
- [Docker 调试](../../15_appendix/debug.md):容器排障
|
||||
@@ -1,154 +0,0 @@
|
||||
# LABEL 为镜像添加元数据
|
||||
|
||||
## 基本语法
|
||||
|
||||
```docker
|
||||
LABEL <key>=<value> <key>=<value> ...
|
||||
```
|
||||
|
||||
`LABEL` 指令以键值对的形式给镜像添加元数据。这些数据不会影响镜像的功能,但可以帮助用户理解镜像,或被自动化工具使用。
|
||||
|
||||
---
|
||||
|
||||
## 为什么需要 LABEL
|
||||
|
||||
1. **版本管理**:记录版本号、构建时间、Git Commit ID
|
||||
2. **联系信息**:维护者邮箱、文档地址、支持渠道
|
||||
3. **自动化工具**: CI/CD 工具可以读取标签触发操作
|
||||
4. **许可证信息**:声明开源协议
|
||||
|
||||
---
|
||||
|
||||
## 基本用法
|
||||
|
||||
### 定义单个标签
|
||||
|
||||
```docker
|
||||
LABEL version="1.0"
|
||||
LABEL description="这是一个 Web 应用服务器"
|
||||
```
|
||||
|
||||
### 定义多个标签(推荐)
|
||||
|
||||
```docker
|
||||
LABEL maintainer="user@example.com" \
|
||||
version="1.2.0" \
|
||||
description="My App Description" \
|
||||
org.opencontainers.image.authors="Yeasy"
|
||||
```
|
||||
|
||||
> 💡 包含空格的值需要用引号括起来。
|
||||
|
||||
---
|
||||
|
||||
## 常用标签规范 (OCI Annotations)
|
||||
|
||||
为了标准和互操作性,推荐使用 [OCI Image Format Specification](https://github.com/opencontainers/image-spec/blob/main/annotations.md#pre-defined-annotation-keys) 定义的标准标签:
|
||||
|
||||
| 标签 Key | 说明 | 示例 |
|
||||
|----------|------|------|
|
||||
| `org.opencontainers.image.created` | 构建时间(RFC 3339) | `2024-01-01T00:00:00Z` |
|
||||
| `org.opencontainers.image.authors` | 作者/维护者 | `support@example.com` |
|
||||
| `org.opencontainers.image.url` | 项目主页 | `https://example.com` |
|
||||
| `org.opencontainers.image.documentation`| 文档地址 | `https://example.com/docs` |
|
||||
| `org.opencontainers.image.source` | 源码仓库 | `https://github.com/user/repo` |
|
||||
| `org.opencontainers.image.version` | 版本号 | `1.0.0` |
|
||||
| `org.opencontainers.image.licenses` | 许可证 | `MIT` |
|
||||
| `org.opencontainers.image.title` | 镜像标题 | `My App` |
|
||||
| `org.opencontainers.image.description` | 描述 | `Production ready web server` |
|
||||
|
||||
### 示例
|
||||
|
||||
```docker
|
||||
LABEL org.opencontainers.image.authors="yeasy" \
|
||||
org.opencontainers.image.documentation="https://yeasy.gitbooks.io" \
|
||||
org.opencontainers.image.source="https://github.com/yeasy/docker_practice" \
|
||||
org.opencontainers.image.licenses="MIT"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## MAINTAINER 指令(已废弃)
|
||||
|
||||
旧版本的 Dockerfile 中常看到 `MAINTAINER` 指令:
|
||||
|
||||
```docker
|
||||
# ❌ 已弃用
|
||||
MAINTAINER user@example.com
|
||||
```
|
||||
|
||||
现在推荐使用 `LABEL`:
|
||||
|
||||
```docker
|
||||
# ✅ 推荐
|
||||
LABEL maintainer="user@example.com"
|
||||
# 或
|
||||
LABEL org.opencontainers.image.authors="user@example.com"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 动态标签
|
||||
|
||||
配合 `ARG` 使用,可以在构建时动态注入标签:
|
||||
|
||||
```docker
|
||||
ARG BUILD_DATE
|
||||
ARG VCS_REF
|
||||
|
||||
LABEL org.opencontainers.image.created=$BUILD_DATE \
|
||||
org.opencontainers.image.revision=$VCS_REF
|
||||
```
|
||||
|
||||
构建命令:
|
||||
|
||||
```bash
|
||||
$ docker build \
|
||||
--build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ') \
|
||||
--build-arg VCS_REF=$(git rev-parse --short HEAD) \
|
||||
.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 查看标签
|
||||
|
||||
### docker inspect
|
||||
|
||||
查看镜像的标签信息:
|
||||
|
||||
```bash
|
||||
$ docker inspect nginx --format '{{json .Config.Labels}}' | jq
|
||||
{
|
||||
"maintainer": "NGINX Docker Maintainers <docker-maint@nginx.com>"
|
||||
}
|
||||
```
|
||||
|
||||
### 过滤器
|
||||
|
||||
可以使用标签过滤镜像:
|
||||
|
||||
```bash
|
||||
# 列出作者是 yeasy 的所有镜像
|
||||
$ docker images --filter "label=org.opencontainers.image.authors=yeasy"
|
||||
|
||||
# 删除所有带有特定标签的镜像
|
||||
$ docker rmi $(docker images -q --filter "label=stage=builder")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 本章小结
|
||||
|
||||
| 要点 | 说明 |
|
||||
|------|------|
|
||||
| **作用** | 添加 key-value 元数据 |
|
||||
| **语法** | `LABEL k=v k=v ...` |
|
||||
| **规范** | 推荐使用 OCI 标准标签 |
|
||||
| **弃用** | 不要再使用 `MAINTAINER` |
|
||||
| **查看** | `docker inspect` |
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [OCI 标签规范](https://github.com/opencontainers/image-spec/blob/main/annotations.md)
|
||||
- [Dockerfile 最佳实践](../../15_appendix/best_practices.md)
|
||||
@@ -1,151 +0,0 @@
|
||||
# ONBUILD 为他人做嫁衣裳
|
||||
|
||||
## 基本语法
|
||||
|
||||
```docker
|
||||
ONBUILD <其它指令>
|
||||
```
|
||||
|
||||
`ONBUILD` 是一个特殊的指令,它后面跟的是其它指令(如 `RUN`, `COPY` 等),这些指令**在当前镜像构建时不会执行**,只有当以当前镜像为基础镜像去构建下一级镜像时才会被执行。
|
||||
|
||||
---
|
||||
|
||||
## 为什么需要 ONBUILD
|
||||
|
||||
`ONBUILD` 主要用于制作**语言栈基础镜像**或**框架基础镜像**。
|
||||
|
||||
### 场景:维护 Node.js 项目
|
||||
|
||||
假设你有多个 Node.js 项目,它们的构建流程都一样:
|
||||
1. 创建目录
|
||||
2. 复制 `package.json`
|
||||
3. 执行 `npm install`
|
||||
4. 复制源码
|
||||
5. 启动应用
|
||||
|
||||
如果不使用 `ONBUILD`,每个项目的 Dockerfile 都要重复这些步骤,且通过 `COPY` 复制文件时,基础镜像无法预知子项目的文件名。
|
||||
|
||||
### 使用 ONBUILD 的解决方案
|
||||
|
||||
**基础镜像 (my-node-base)**:
|
||||
|
||||
```docker
|
||||
FROM node:20-alpine
|
||||
WORKDIR /app
|
||||
|
||||
# 这些指令将在子镜像构建时执行
|
||||
ONBUILD COPY package*.json ./
|
||||
ONBUILD RUN npm install
|
||||
ONBUILD COPY . .
|
||||
|
||||
CMD ["npm", "start"]
|
||||
```
|
||||
|
||||
**子项目 Dockerfile**:
|
||||
|
||||
```docker
|
||||
FROM my-node-base
|
||||
# 只需要一行!
|
||||
# 构建时会自动执行 COPY 和 RUN
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 执行机制
|
||||
|
||||
```
|
||||
基础镜像构建:
|
||||
Dockerfile (含 ONBUILD) ──build──> 基础镜像 (记录了 ONBUILD 触发器)
|
||||
(指令未执行)
|
||||
|
||||
子镜像构建:
|
||||
FROM 基础镜像 ──build──> 读取基础镜像触发器 ──> 执行触发器指令 ──> 继续执行子 Dockerfile
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 常见使用场景
|
||||
|
||||
### 1. 自动处理依赖安装
|
||||
|
||||
```docker
|
||||
# Python 基础镜像
|
||||
ONBUILD COPY requirements.txt ./
|
||||
ONBUILD RUN pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### 2. 自动编译代码
|
||||
|
||||
```docker
|
||||
# Go 基础镜像
|
||||
ONBUILD COPY . .
|
||||
ONBUILD RUN go build -o app main.go
|
||||
```
|
||||
|
||||
### 3. 处理静态资源
|
||||
|
||||
```docker
|
||||
# Nginx 静态网站基础镜像
|
||||
ONBUILD COPY dist/ /usr/share/nginx/html/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
### 1. 继承性限制
|
||||
|
||||
`ONBUILD` 指令**只会继承一次**。
|
||||
- 镜像 A (含 ONBUILD)
|
||||
- 镜像 B (FROM A) -> 触发 ONBUILD
|
||||
- 镜像 C (FROM B) -> **不会**再次触发 ONBUILD
|
||||
|
||||
### 2. 构建上下文
|
||||
|
||||
子镜像构建时,`ONBUILD COPY . .` 中的 `.` 指的是**子项目**的构建上下文,而不是基础镜像的上下文。
|
||||
|
||||
### 3. 不允许级联
|
||||
|
||||
`ONBUILD ONBUILD` 是非法的。你不能写 `ONBUILD ONBUILD COPY ...`。
|
||||
|
||||
### 4. 可能会导致构建失败
|
||||
|
||||
由于 `ONBUILD` 实际上是在子镜像中执行指令,如果子项目的上下文不满足要求(例如缺少 `package.json`),会导致子镜像构建失败,且错误信息可能比较隐晦。
|
||||
|
||||
---
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 命名规范
|
||||
|
||||
建议在镜像标签中添加 `-onbuild` 后缀,明确告知使用者该镜像包含触发器。
|
||||
|
||||
```
|
||||
node:20-onbuild
|
||||
python:3.12-onbuild
|
||||
```
|
||||
|
||||
### 2. 避免执行耗时操作
|
||||
|
||||
尽量不要在 `ONBUILD` 中执行过于耗时或不确定的操作(如更新系统软件),这会让子镜像构建变得缓慢且不可控。
|
||||
|
||||
### 3. 清理工作
|
||||
|
||||
如果 `ONBUILD` 指令产生了临时文件,最好在同一个指令链中清理,或者提供机制让子镜像清理。
|
||||
|
||||
---
|
||||
|
||||
## 本章小结
|
||||
|
||||
| 要点 | 说明 |
|
||||
|------|------|
|
||||
| **作用** | 定义在子镜像构建时执行的指令 |
|
||||
| **语法** | `ONBUILD INSTRUCTION` |
|
||||
| **适用** | 基础架构镜像(Node, Python, Go 等) |
|
||||
| **限制** | 只继承一次,不可级联 |
|
||||
| **规范** | 建议使用 `-onbuild` 标签后缀 |
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [COPY 指令](copy.md):文件复制
|
||||
- [Dockerfile 最佳实践](../../15_appendix/best_practices.md):基础镜像设计
|
||||
@@ -1,181 +0,0 @@
|
||||
# RUN 执行命令
|
||||
|
||||
## 基本语法
|
||||
|
||||
```docker
|
||||
RUN <command>
|
||||
RUN ["executable", "param1", "param2"]
|
||||
```
|
||||
|
||||
`RUN` 指令是 Dockerfile 中最常用的指令之一。它在**当前镜像层**之上创建一个新层,执行指定的命令,并提交结果。
|
||||
|
||||
---
|
||||
|
||||
## 两种格式对比
|
||||
|
||||
### 1. Shell 格式
|
||||
|
||||
```docker
|
||||
RUN apt-get update
|
||||
```
|
||||
|
||||
- **特点**:默认通过 `/bin/sh -c` 执行。
|
||||
- **优势**:可以使用环境变量、管道、重定向等 Shell 特性。
|
||||
- **示例**:
|
||||
```docker
|
||||
RUN echo "Hello" > /test.txt
|
||||
```
|
||||
|
||||
### 2. Exec 格式
|
||||
|
||||
```docker
|
||||
RUN ["apt-get", "update"]
|
||||
```
|
||||
|
||||
- **特点**:直接调用可执行文件,不经过 Shell。
|
||||
- **优势**:避免 Shell 字符串解析问题,适用于参数中包含特殊字符的情况。
|
||||
- **注意**:无法使用 `$VAR` 环境变量替换(除非显式调用 shell)。
|
||||
|
||||
---
|
||||
|
||||
## 常见最佳实践
|
||||
|
||||
### 1. 组合命令(减少层数)
|
||||
|
||||
每一个 `RUN` 指令都会新建一层镜像。为了减少镜像体积和层数,应使用 `&&` 连接命令。
|
||||
|
||||
**❌ 糟糕的写法**(创建 3 层):
|
||||
|
||||
```docker
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y nginx
|
||||
RUN rm -rf /var/lib/apt/lists/*
|
||||
```
|
||||
|
||||
**✅ 推荐写法**(创建 1 层):
|
||||
|
||||
```docker
|
||||
RUN apt-get update && \
|
||||
apt-get install -y nginx && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
```
|
||||
|
||||
### 2. 清理缓存
|
||||
|
||||
在安装完软件后,立即清除缓存,可以显著减小镜像体积。
|
||||
|
||||
- **Debian/Ubuntu**:
|
||||
```docker
|
||||
RUN apt-get update && apt-get install -y package-bar \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
```
|
||||
- **Alpine**:
|
||||
```docker
|
||||
RUN apk add --no-cache package-bar
|
||||
```
|
||||
|
||||
### 3. 使用 `set -e` 和 `pipefail`
|
||||
|
||||
默认情况下,管道命令 `cmd1 | cmd2` 只要 `cmd2` 成功,整个 `RUN` 就视为成功。
|
||||
|
||||
**❌ 隐蔽的错误**:
|
||||
|
||||
```docker
|
||||
# 如果下载失败,gzip 可能会报错,但如果不影响后续,构建可能继续
|
||||
RUN wget http://error-url | gzip -d > file
|
||||
```
|
||||
|
||||
**✅ 推荐写法**:
|
||||
|
||||
```docker
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
RUN wget http://url | gzip -d > file
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q: 为什么 `RUN cd /app` 不生效?
|
||||
|
||||
```docker
|
||||
RUN cd /app
|
||||
RUN touch hello.txt
|
||||
```
|
||||
|
||||
**结果**:`hello.txt` 会出现在根目录 `/`,而不是 `/app`。
|
||||
|
||||
**原因**:每个 `RUN` 都在一个新的 Shell/容器 环境中执行。`cd` 只影响当前 `RUN` 的环境。
|
||||
|
||||
**解决**:使用 `WORKDIR` 指令。
|
||||
|
||||
```docker
|
||||
WORKDIR /app
|
||||
RUN touch hello.txt
|
||||
```
|
||||
|
||||
### Q: 环境变量不生效?
|
||||
|
||||
```docker
|
||||
RUN export MY_VAR=hello
|
||||
RUN echo $MY_VAR
|
||||
```
|
||||
|
||||
**结果**:输出为空。
|
||||
|
||||
**原因**:同上,环境变量只在当前 `RUN` 有效。
|
||||
|
||||
**解决**:使用 `ENV` 指令,或在同一行 `RUN` 中导出。
|
||||
|
||||
```docker
|
||||
ENV MY_VAR=hello
|
||||
RUN echo $MY_VAR
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 高级技巧
|
||||
|
||||
### 1. 使用 BuildKit 的挂载缓存
|
||||
|
||||
BuildKit 支持在 `RUN` 指令中使用 `--mount` 挂载缓存,加速构建。
|
||||
|
||||
```docker
|
||||
# 缓存 apt 包
|
||||
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,target=/var/lib/apt,sharing=locked \
|
||||
apt-get update && apt-get install -y gcc
|
||||
```
|
||||
|
||||
```docker
|
||||
# 缓存 Go 模块
|
||||
RUN --mount=type=cache,target=/go/pkg/mod \
|
||||
go build -o app
|
||||
```
|
||||
|
||||
### 2. 挂载密钥
|
||||
|
||||
安全地使用 SSH 密钥或 Token,而不将其记录在镜像中。
|
||||
|
||||
```docker
|
||||
RUN --mount=type=secret,id=mysecret \
|
||||
cat /run/secrets/mysecret
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 本章小结
|
||||
|
||||
| 要点 | 说明 |
|
||||
|------|------|
|
||||
| **作用** | 在新层执行命令 |
|
||||
| **原则** | 合并命令,清理缓存 |
|
||||
| **格式** | Shell (常用) vs Exec |
|
||||
| **陷阱** | `cd` 不持久,环境变量不持久 |
|
||||
| **进阶** | 使用 Cache Mount 加速构建 |
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [CMD 容器启动命令](cmd.md):容器启动时的命令
|
||||
- [WORKDIR 指定工作目录](workdir.md):改变目录
|
||||
- [Dockerfile 最佳实践](../../15_appendix/best_practices.md)
|
||||
@@ -1,141 +0,0 @@
|
||||
# SHELL 指令
|
||||
|
||||
## 基本语法
|
||||
|
||||
```docker
|
||||
SHELL ["executable", "parameters"]
|
||||
```
|
||||
|
||||
`SHELL` 指令允许覆盖 Docker 默认的 shell。
|
||||
- **Linux 默认**:`["/bin/sh", "-c"]`
|
||||
- **Windows 默认**:`["cmd", "/S", "/C"]`
|
||||
|
||||
该指令会影响后续的 `RUN`, `CMD`, `ENTRYPOINT` 指令(当它们使用 shell 格式时)。
|
||||
|
||||
---
|
||||
|
||||
## 为什么要用 SHELL 指令
|
||||
|
||||
### 1. 使用 bash 特性
|
||||
|
||||
默认的 `/bin/sh`(通常是 dash 或 alpine 的 ash)功能有限。如果你需要使用 bash 的特有功能(如数组、`{}` 扩展、`pipefail` 等),可以切换 shell。
|
||||
|
||||
```docker
|
||||
FROM ubuntu:24.04
|
||||
|
||||
# 切换到 bash
|
||||
SHELL ["/bin/bash", "-c"]
|
||||
|
||||
# 现在可以使用 bash 特性了
|
||||
RUN echo {a..z}
|
||||
```
|
||||
|
||||
### 2. 增强错误处理 (pipefail)
|
||||
|
||||
默认情况下,管道命令 `cmd1 | cmd2` 只要 `cmd2` 成功,整个指令就视为成功。这可能掩盖构建错误。
|
||||
|
||||
```docker
|
||||
# ❌ 这里的 wget 失败了,但构建继续(因为 tar 成功了)
|
||||
RUN wget -O - https://invalid-url | tar xz
|
||||
```
|
||||
|
||||
使用 `SHELL` 启用 `pipefail`:
|
||||
|
||||
```docker
|
||||
# ✅ 启用 pipefail
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
|
||||
# 如果 wget 失败,整个 RUN 就会失败
|
||||
RUN wget -O - https://invalid-url | tar xz
|
||||
```
|
||||
|
||||
### 3. Windows 环境
|
||||
|
||||
在 Windows 容器中,经常需要在 `cmd` 和 `powershell` 之间切换。
|
||||
|
||||
```docker
|
||||
FROM mcr.microsoft.com/windows/servercore:ltsc2022
|
||||
|
||||
# 默认是 cmd
|
||||
RUN echo Default shell is cmd
|
||||
|
||||
# 切换到 powershell
|
||||
SHELL ["powershell", "-command"]
|
||||
RUN Write-Host "Hello from PowerShell"
|
||||
|
||||
# 切回 cmd
|
||||
SHELL ["cmd", "/S", "/C"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 作用范围
|
||||
|
||||
`SHELL` 指令可以出现多次,每次只影响其后的指令:
|
||||
|
||||
```docker
|
||||
FROM ubuntu:24.04
|
||||
|
||||
# 使用默认 sh
|
||||
RUN echo "Using sh"
|
||||
|
||||
SHELL ["/bin/bash", "-c"]
|
||||
# 使用 bash
|
||||
RUN echo "Using bash"
|
||||
|
||||
SHELL ["/bin/sh", "-c"]
|
||||
# 回到 sh
|
||||
RUN echo "Using sh again"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 对其他指令的影响
|
||||
|
||||
`SHELL` 影响的是所有使用 **shell 格式** 的指令:
|
||||
|
||||
| 指令格式 | 是否受 SHELL 影响 |
|
||||
|---------|-------------------|
|
||||
| `RUN command` | ✅ 是 |
|
||||
| `RUN ["exec", "param"]` | ❌ 否 |
|
||||
| `CMD command` | ✅ 是 |
|
||||
| `CMD ["exec", "param"]` | ❌ 否 |
|
||||
| `ENTRYPOINT command` | ✅ 是 |
|
||||
| `ENTRYPOINT ["exec", "param"]` | ❌ 否 |
|
||||
|
||||
---
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 推荐开启 pipefail
|
||||
|
||||
对于使用 bash 的镜像,强烈建议开启 `pipefail`,以确保构建过程中的错误能被及时捕获。
|
||||
|
||||
```docker
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
```
|
||||
|
||||
### 2. 明确意图
|
||||
|
||||
如果由于脚本需求必须更改 shell,最好在 Dockerfile 中显式声明,而不是依赖默认行为。
|
||||
|
||||
### 3. 尽量保持一致
|
||||
|
||||
避免在 Dockerfile 中频繁切换 SHELL,这会使构建过程难以理解和调试。尽量在头部定义一次即可。
|
||||
|
||||
---
|
||||
|
||||
## 本章小结
|
||||
|
||||
| 要点 | 说明 |
|
||||
|------|------|
|
||||
| **作用** | 更改 RUN/CMD/ENTRYPOINT 的默认 shell |
|
||||
| **Linux 默认** | `["/bin/sh", "-c"]` |
|
||||
| **Windows 默认** | `["cmd", "/S", "/C"]` |
|
||||
| **推荐用法** | `SHELL ["/bin/bash", "-o", "pipefail", "-c"]` |
|
||||
| **影响范围** | 后续所有使用 shell 格式的指令 |
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [RUN 指令](../../04_image/build.md):执行命令
|
||||
- [Dockerfile 最佳实践](../../15_appendix/best_practices.md):错误处理与调试
|
||||
@@ -1,273 +0,0 @@
|
||||
# USER 指定当前用户
|
||||
|
||||
## 基本语法
|
||||
|
||||
```docker
|
||||
USER <用户名>[:<用户组>]
|
||||
USER <UID>[:<GID>]
|
||||
```
|
||||
|
||||
`USER` 指令切换后续指令(RUN、CMD、ENTRYPOINT)的执行用户。
|
||||
|
||||
---
|
||||
|
||||
## 为什么要使用 USER
|
||||
|
||||
> 笔者强调:以非 root 用户运行容器是最重要的安全实践之一。
|
||||
|
||||
```
|
||||
root 用户运行的风险:
|
||||
┌────────────────────────────────────────────────────────┐
|
||||
│ 容器内 root ←─ 可能逃逸 ─→ 宿主机 root │
|
||||
│ │ │ │
|
||||
│ └── 漏洞利用 ───────────────→ 完全控制宿主机 │
|
||||
└────────────────────────────────────────────────────────┘
|
||||
|
||||
非 root 用户运行:
|
||||
┌────────────────────────────────────────────────────────┐
|
||||
│ 容器内普通用户 ──逃逸后──→ 宿主机普通用户 │
|
||||
│ │ │ │
|
||||
│ └── 权限受限,危害降低 ─────→ 无法控制系统 │
|
||||
└────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 基本用法
|
||||
|
||||
### 创建并切换用户
|
||||
|
||||
```docker
|
||||
FROM node:20-alpine
|
||||
|
||||
# 1. 创建用户和组
|
||||
RUN addgroup -g 1001 appgroup && \
|
||||
adduser -u 1001 -G appgroup -D appuser
|
||||
|
||||
# 2. 设置目录权限
|
||||
WORKDIR /app
|
||||
COPY --chown=appuser:appgroup . .
|
||||
|
||||
# 3. 切换用户
|
||||
USER appuser
|
||||
|
||||
# 4. 后续命令以 appuser 身份运行
|
||||
CMD ["node", "server.js"]
|
||||
```
|
||||
|
||||
### 使用 UID/GID
|
||||
|
||||
```docker
|
||||
# 也可以使用数字
|
||||
USER 1001:1001
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 用户必须已存在
|
||||
|
||||
`USER` 指令只能切换到**已存在**的用户:
|
||||
|
||||
```docker
|
||||
# ❌ 错误:用户不存在
|
||||
USER nonexistent
|
||||
# Error: unable to find user nonexistent
|
||||
|
||||
# ✅ 正确:先创建用户
|
||||
RUN useradd -r -s /bin/false appuser
|
||||
USER appuser
|
||||
```
|
||||
|
||||
### 创建用户的方式
|
||||
|
||||
**Debian/Ubuntu**:
|
||||
|
||||
```docker
|
||||
RUN groupadd -r appgroup && \
|
||||
useradd -r -g appgroup appuser
|
||||
```
|
||||
|
||||
**Alpine**:
|
||||
|
||||
```docker
|
||||
RUN addgroup -g 1001 -S appgroup && \
|
||||
adduser -u 1001 -S -G appgroup appuser
|
||||
```
|
||||
|
||||
| 选项 | 说明 |
|
||||
|------|------|
|
||||
| `-r` (useradd) / `-S` (adduser) | 创建系统用户 |
|
||||
| `-g` | 指定主组 |
|
||||
| `-G` | 指定附加组 |
|
||||
| `-u` | 指定 UID |
|
||||
| `-s /bin/false` | 禁用登录 shell |
|
||||
|
||||
---
|
||||
|
||||
## 运行时切换用户
|
||||
|
||||
### 使用 gosu(推荐)
|
||||
|
||||
在 ENTRYPOINT 脚本中切换用户时,不要使用 `su` 或 `sudo`,应使用 [gosu](https://github.com/tianon/gosu):
|
||||
|
||||
```docker
|
||||
FROM debian:bookworm
|
||||
|
||||
# 创建用户
|
||||
RUN groupadd -r redis && useradd -r -g redis redis
|
||||
|
||||
# 安装 gosu
|
||||
RUN apt-get update && apt-get install -y gosu && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY docker-entrypoint.sh /usr/local/bin/
|
||||
ENTRYPOINT ["docker-entrypoint.sh"]
|
||||
CMD ["redis-server"]
|
||||
```
|
||||
|
||||
**docker-entrypoint.sh**:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# 以 root 执行初始化
|
||||
chown -R redis:redis /data
|
||||
|
||||
# 用 gosu 切换到 redis 用户运行服务
|
||||
exec gosu redis "$@"
|
||||
```
|
||||
|
||||
### 为什么不用 su/sudo
|
||||
|
||||
| 问题 | su/sudo | gosu |
|
||||
|------|---------|------|
|
||||
| TTY 要求 | 需要 | 不需要 |
|
||||
| 信号传递 | 不正确 | 正确 |
|
||||
| 子进程 | 是 | exec 替换 |
|
||||
| 容器中使用 | ❌ | ✅ |
|
||||
|
||||
---
|
||||
|
||||
## 运行时覆盖用户
|
||||
|
||||
使用 `-u` 或 `--user` 参数:
|
||||
|
||||
```bash
|
||||
# 以指定用户运行
|
||||
$ docker run -u 1001:1001 myimage
|
||||
|
||||
# 以 root 运行(调试时)
|
||||
$ docker run -u root myimage
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 文件权限处理
|
||||
|
||||
切换用户后,确保应用有权访问文件:
|
||||
|
||||
```docker
|
||||
FROM node:20-alpine
|
||||
|
||||
# 创建用户
|
||||
RUN adduser -D -u 1001 appuser
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# 方式1:使用 --chown
|
||||
COPY --chown=appuser:appuser . .
|
||||
|
||||
# 方式2:手动 chown(减少层数)
|
||||
# COPY . .
|
||||
# RUN chown -R appuser:appuser /app
|
||||
|
||||
USER appuser
|
||||
CMD ["node", "server.js"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 始终使用非 root 用户
|
||||
|
||||
```docker
|
||||
# ✅ 推荐
|
||||
RUN adduser -D appuser
|
||||
USER appuser
|
||||
CMD ["myapp"]
|
||||
|
||||
# ❌ 避免
|
||||
CMD ["myapp"] # 以 root 运行
|
||||
```
|
||||
|
||||
### 2. 使用固定 UID/GID
|
||||
|
||||
便于在宿主机和容器间共享文件:
|
||||
|
||||
```docker
|
||||
# 使用常见的非 root UID
|
||||
RUN addgroup -g 1000 -S appgroup && \
|
||||
adduser -u 1000 -S -G appgroup appuser
|
||||
USER 1000:1000
|
||||
```
|
||||
|
||||
### 3. 多阶段构建中的 USER
|
||||
|
||||
```docker
|
||||
# 构建阶段可以用 root
|
||||
FROM node:20 AS builder
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
RUN npm install && npm run build
|
||||
|
||||
# 生产阶段用非 root
|
||||
FROM node:20-alpine
|
||||
RUN adduser -D appuser
|
||||
WORKDIR /app
|
||||
COPY --from=builder --chown=appuser:appuser /app/dist .
|
||||
USER appuser
|
||||
CMD ["node", "server.js"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q: 权限被拒绝
|
||||
|
||||
```bash
|
||||
permission denied: '/app/data.log'
|
||||
```
|
||||
|
||||
**解决**:确保目录权限正确
|
||||
|
||||
```docker
|
||||
RUN mkdir -p /app/data && chown appuser:appuser /app/data
|
||||
```
|
||||
|
||||
### Q: 无法绑定低于 1024 的端口
|
||||
|
||||
非 root 用户无法绑定 80、443 等端口。
|
||||
|
||||
**解决**:
|
||||
1. 使用高端口(如 8080)
|
||||
2. 在运行时映射端口:`docker run -p 80:8080`
|
||||
|
||||
---
|
||||
|
||||
## 本章小结
|
||||
|
||||
| 要点 | 说明 |
|
||||
|------|------|
|
||||
| **作用** | 切换后续指令的执行用户 |
|
||||
| **语法** | `USER username` 或 `USER UID:GID` |
|
||||
| **前提** | 用户必须已存在 |
|
||||
| **运行时覆盖** | `docker run -u` |
|
||||
| **切换工具** | 使用 gosu,不用 su/sudo |
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [安全](../../security/README.md):容器安全实践
|
||||
- [ENTRYPOINT](entrypoint.md):入口脚本中的用户切换
|
||||
- [最佳实践](../../15_appendix/best_practices.md):Dockerfile 安全
|
||||
@@ -1,247 +0,0 @@
|
||||
# VOLUME 定义匿名卷
|
||||
|
||||
## 基本语法
|
||||
|
||||
```docker
|
||||
VOLUME ["/路径1", "/路径2"]
|
||||
VOLUME /路径
|
||||
```
|
||||
|
||||
`VOLUME` 指令创建挂载点,并标记为外部挂载的卷。
|
||||
|
||||
---
|
||||
|
||||
## 为什么使用 VOLUME
|
||||
|
||||
> **核心原则**:容器存储层应该保持无状态,任何运行时数据都应该存储在卷中。
|
||||
|
||||
```
|
||||
没有 VOLUME: 使用 VOLUME:
|
||||
┌─────────────────────┐ ┌─────────────────────┐
|
||||
│ 容器存储层 │ │ 容器存储层 │
|
||||
│ ┌─────────────┐ │ │ (只读/无状态) │
|
||||
│ │ 数据库文件 │←─问题 │ │
|
||||
│ │ 日志文件 │ │ └──────────┬──────────┘
|
||||
│ │ 上传文件 │ │ │
|
||||
│ └─────────────┘ │ ┌──────────▼──────────┐
|
||||
└─────────────────────┘ │ 数据卷 │
|
||||
容器删除 = 数据丢失 │ ┌─────────────┐ │
|
||||
│ │ 持久化数据 │←─安全
|
||||
│ └─────────────┘ │
|
||||
└─────────────────────┘
|
||||
容器删除,数据保留
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 基本用法
|
||||
|
||||
### 定义单个卷
|
||||
|
||||
```docker
|
||||
FROM mysql:8.0
|
||||
VOLUME /var/lib/mysql
|
||||
```
|
||||
|
||||
### 定义多个卷
|
||||
|
||||
```docker
|
||||
FROM myapp
|
||||
VOLUME ["/data", "/logs", "/config"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## VOLUME 的行为
|
||||
|
||||
### 1. 自动创建匿名卷
|
||||
|
||||
如果运行时未指定挂载,Docker 会自动创建匿名卷:
|
||||
|
||||
```bash
|
||||
$ docker run mysql:8.0
|
||||
$ docker volume ls
|
||||
DRIVER VOLUME NAME
|
||||
local a1b2c3d4e5f6... # 自动创建的匿名卷
|
||||
```
|
||||
|
||||
### 2. 可被命名卷覆盖
|
||||
|
||||
```bash
|
||||
# 使用命名卷替代匿名卷
|
||||
$ docker run -v mysql_data:/var/lib/mysql mysql:8.0
|
||||
```
|
||||
|
||||
### 3. 可被 Bind Mount 覆盖
|
||||
|
||||
```bash
|
||||
# 使用宿主机目录替代
|
||||
$ docker run -v /my/data:/var/lib/mysql mysql:8.0
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## VOLUME 在构建时的特殊行为
|
||||
|
||||
> ⚠️ **重要**:VOLUME 之后对该目录的修改会被丢弃!
|
||||
|
||||
```docker
|
||||
FROM ubuntu
|
||||
VOLUME /data
|
||||
|
||||
# ❌ 这个文件不会出现在镜像中!
|
||||
RUN echo "hello" > /data/test.txt
|
||||
```
|
||||
|
||||
**原因**:VOLUME 指令之后,Docker 将该目录视为外部挂载点,不再记录对它的修改。
|
||||
|
||||
### 正确做法
|
||||
|
||||
```docker
|
||||
FROM ubuntu
|
||||
|
||||
# ✅ 先写入文件
|
||||
RUN mkdir -p /data && echo "hello" > /data/test.txt
|
||||
|
||||
# 再声明 VOLUME
|
||||
VOLUME /data
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 常见使用场景
|
||||
|
||||
### 数据库持久化
|
||||
|
||||
```docker
|
||||
FROM postgres:15
|
||||
VOLUME /var/lib/postgresql/data
|
||||
```
|
||||
|
||||
### 日志目录
|
||||
|
||||
```docker
|
||||
FROM nginx
|
||||
VOLUME /var/log/nginx
|
||||
```
|
||||
|
||||
### 上传文件目录
|
||||
|
||||
```docker
|
||||
FROM myapp
|
||||
VOLUME /app/uploads
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 查看 VOLUME 定义
|
||||
|
||||
```bash
|
||||
# 查看镜像定义的 VOLUME
|
||||
$ docker inspect mysql:8.0 --format '{{json .Config.Volumes}}' | jq
|
||||
{
|
||||
"/var/lib/mysql": {}
|
||||
}
|
||||
|
||||
# 查看容器挂载的卷
|
||||
$ docker inspect mycontainer --format '{{json .Mounts}}' | jq
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## VOLUME vs docker run -v
|
||||
|
||||
| 特性 | Dockerfile VOLUME | docker run -v |
|
||||
|------|-------------------|---------------|
|
||||
| **定义时机** | 镜像构建时 | 容器运行时 |
|
||||
| **默认行为** | 创建匿名卷 | 可指定命名卷或路径 |
|
||||
| **灵活性** | 低(固定路径) | 高(可任意指定) |
|
||||
| **适用场景** | 定义必须持久化的路径 | 灵活的数据管理 |
|
||||
|
||||
---
|
||||
|
||||
## 在 Compose 中
|
||||
|
||||
```yaml
|
||||
services:
|
||||
db:
|
||||
image: postgres:15
|
||||
volumes:
|
||||
# 命名卷(推荐)
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
# Bind Mount
|
||||
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
|
||||
|
||||
volumes:
|
||||
postgres_data: # 声明命名卷
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 安全注意事项
|
||||
|
||||
### 匿名卷可能导致数据丢失
|
||||
|
||||
```bash
|
||||
# 使用 --rm 运行的容器,匿名卷会在容器删除时一起删除
|
||||
$ docker run --rm mysql:8.0
|
||||
# 容器停止后,数据丢失!
|
||||
```
|
||||
|
||||
**解决**:始终使用命名卷
|
||||
|
||||
```bash
|
||||
$ docker run -v mysql_data:/var/lib/mysql mysql:8.0
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 定义必须持久化的路径
|
||||
|
||||
```docker
|
||||
# 数据库必须使用卷
|
||||
FROM postgres:15
|
||||
VOLUME /var/lib/postgresql/data
|
||||
```
|
||||
|
||||
### 2. 不要在 VOLUME 后修改目录
|
||||
|
||||
```docker
|
||||
# ❌ 避免
|
||||
VOLUME /app/data
|
||||
RUN cp init-data.json /app/data/
|
||||
|
||||
# ✅ 正确
|
||||
RUN mkdir -p /app/data && cp init-data.json /app/data/
|
||||
VOLUME /app/data
|
||||
```
|
||||
|
||||
### 3. 文档中说明 VOLUME 用途
|
||||
|
||||
```docker
|
||||
# 持久化用户上传的文件
|
||||
VOLUME /app/uploads
|
||||
|
||||
# 持久化数据库数据
|
||||
VOLUME /var/lib/mysql
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 本章小结
|
||||
|
||||
| 要点 | 说明 |
|
||||
|------|------|
|
||||
| **作用** | 创建挂载点,标记为外部卷 |
|
||||
| **语法** | `VOLUME /path` |
|
||||
| **默认行为** | 自动创建匿名卷 |
|
||||
| **覆盖方式** | `docker run -v name:/path` |
|
||||
| **注意** | VOLUME 之后的修改会丢失 |
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [数据卷](../../07_data_network/data/volume.md):卷的管理和使用
|
||||
- [挂载主机目录](../../07_data_network/data/bind-mounts.md):Bind Mount
|
||||
- [Compose 数据管理](../../compose/compose_file.md):Compose 中的卷配置
|
||||
@@ -1,196 +0,0 @@
|
||||
# WORKDIR 指定工作目录
|
||||
|
||||
## 基本语法
|
||||
|
||||
```docker
|
||||
WORKDIR <工作目录路径>
|
||||
```
|
||||
|
||||
`WORKDIR` 指定后续指令的工作目录。如果目录不存在,Docker 会自动创建。
|
||||
|
||||
---
|
||||
|
||||
## 基本用法
|
||||
|
||||
```docker
|
||||
WORKDIR /app
|
||||
|
||||
RUN pwd # 输出 /app
|
||||
RUN echo "hello" > world.txt # 创建 /app/world.txt
|
||||
COPY . . # 复制到 /app/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 为什么需要 WORKDIR
|
||||
|
||||
### 常见错误
|
||||
|
||||
```docker
|
||||
# ❌ 错误:cd 在下一个 RUN 中无效
|
||||
RUN cd /app
|
||||
RUN echo "hello" > world.txt # 文件在根目录!
|
||||
```
|
||||
|
||||
### 原因分析
|
||||
|
||||
```
|
||||
RUN cd /app
|
||||
↓
|
||||
启动容器 → cd /app(仅内存变化)→ 提交镜像层 → 容器销毁
|
||||
│
|
||||
↓ 工作目录未改变!
|
||||
RUN echo "hello" > world.txt
|
||||
↓
|
||||
启动新容器(工作目录在 /)→ 创建 /world.txt
|
||||
```
|
||||
|
||||
每个 RUN 都在新容器中执行,**前一个 RUN 的内存状态(包括工作目录)不会保留**。
|
||||
|
||||
### 正确做法
|
||||
|
||||
```docker
|
||||
# ✅ 正确:使用 WORKDIR
|
||||
WORKDIR /app
|
||||
RUN echo "hello" > world.txt # 创建 /app/world.txt
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 相对路径
|
||||
|
||||
WORKDIR 支持相对路径,基于上一个 WORKDIR:
|
||||
|
||||
```docker
|
||||
WORKDIR /a
|
||||
WORKDIR b
|
||||
WORKDIR c
|
||||
|
||||
RUN pwd # 输出 /a/b/c
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 使用环境变量
|
||||
|
||||
```docker
|
||||
ENV APP_HOME=/app
|
||||
WORKDIR $APP_HOME
|
||||
|
||||
RUN pwd # 输出 /app
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 多阶段构建中的 WORKDIR
|
||||
|
||||
```docker
|
||||
# 构建阶段
|
||||
FROM node:20 AS builder
|
||||
WORKDIR /build
|
||||
COPY package*.json ./
|
||||
RUN npm install
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
# 生产阶段
|
||||
FROM nginx:alpine
|
||||
WORKDIR /usr/share/nginx/html
|
||||
COPY --from=builder /build/dist .
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 尽早设置 WORKDIR
|
||||
|
||||
```docker
|
||||
FROM node:20
|
||||
WORKDIR /app # 尽早设置
|
||||
|
||||
COPY package*.json ./
|
||||
RUN npm install
|
||||
COPY . .
|
||||
CMD ["node", "server.js"]
|
||||
```
|
||||
|
||||
### 2. 使用绝对路径
|
||||
|
||||
```docker
|
||||
# ✅ 推荐:绝对路径,意图明确
|
||||
WORKDIR /app
|
||||
|
||||
# ⚠️ 避免:相对路径可能造成混淆
|
||||
WORKDIR app
|
||||
```
|
||||
|
||||
### 3. 不要用 RUN cd
|
||||
|
||||
```docker
|
||||
# ❌ 避免
|
||||
RUN cd /app && echo "hello" > world.txt
|
||||
|
||||
# ✅ 推荐
|
||||
WORKDIR /app
|
||||
RUN echo "hello" > world.txt
|
||||
```
|
||||
|
||||
### 4. 适时重置 WORKDIR
|
||||
|
||||
```docker
|
||||
WORKDIR /app
|
||||
# ... 应用相关操作 ...
|
||||
|
||||
WORKDIR /data
|
||||
# ... 数据相关操作 ...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 与其他指令的关系
|
||||
|
||||
| 指令 | WORKDIR 的影响 |
|
||||
|------|---------------|
|
||||
| `RUN` | 在 WORKDIR 中执行命令 |
|
||||
| `CMD` | 在 WORKDIR 中启动 |
|
||||
| `ENTRYPOINT` | 在 WORKDIR 中启动 |
|
||||
| `COPY` | 相对目标路径基于 WORKDIR |
|
||||
| `ADD` | 相对目标路径基于 WORKDIR |
|
||||
|
||||
```docker
|
||||
WORKDIR /app
|
||||
|
||||
RUN pwd # /app
|
||||
COPY . . # 复制到 /app
|
||||
CMD ["./start.sh"] # /app/start.sh
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 运行时覆盖
|
||||
|
||||
使用 `-w` 参数覆盖工作目录:
|
||||
|
||||
```bash
|
||||
$ docker run -w /tmp myimage pwd
|
||||
/tmp
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 本章小结
|
||||
|
||||
| 要点 | 说明 |
|
||||
|------|------|
|
||||
| **作用** | 设置后续指令的工作目录 |
|
||||
| **语法** | `WORKDIR /path` |
|
||||
| **自动创建** | 目录不存在会自动创建 |
|
||||
| **持久性** | 影响后续所有指令,直到下次 WORKDIR |
|
||||
| **不要用** | `RUN cd /path`(无效) |
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [COPY 复制文件](copy.md):文件复制
|
||||
- [RUN 执行命令](../../04_image/build.md):执行构建命令
|
||||
- [最佳实践](../../15_appendix/best_practices.md):Dockerfile 编写指南
|
||||
258
04_image/list.md
258
04_image/list.md
@@ -1,258 +0,0 @@
|
||||
# 列出镜像
|
||||
|
||||
## 基本用法
|
||||
|
||||
查看本地已下载的镜像:
|
||||
|
||||
```bash
|
||||
$ docker image ls
|
||||
REPOSITORY TAG IMAGE ID CREATED SIZE
|
||||
redis latest 5f515359c7f8 5 days ago 183MB
|
||||
nginx latest 05a60462f8ba 5 days ago 181MB
|
||||
ubuntu 24.04 329ed837d508 3 days ago 78MB
|
||||
ubuntu noble 329ed837d508 3 days ago 78MB
|
||||
```
|
||||
|
||||
> 💡 `docker images` 是 `docker image ls` 的简写,两者等效。
|
||||
|
||||
---
|
||||
|
||||
## 输出字段说明
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| **REPOSITORY** | 仓库名 |
|
||||
| **TAG** | 标签(版本) |
|
||||
| **IMAGE ID** | 镜像唯一标识(短 ID,前 12 位) |
|
||||
| **CREATED** | 创建时间 |
|
||||
| **SIZE** | 本地占用空间 |
|
||||
|
||||
### 同一镜像多个标签
|
||||
|
||||
注意上面的 `ubuntu:24.04` 和 `ubuntu:noble` 拥有相同的 IMAGE ID——它们是同一个镜像的不同标签,只占用一份存储空间。
|
||||
|
||||
---
|
||||
|
||||
## 理解镜像大小
|
||||
|
||||
### 本地大小 vs Hub 显示大小
|
||||
|
||||
| 位置 | 显示大小 | 说明 |
|
||||
|------|---------|------|
|
||||
| Docker Hub | 29MB | 压缩后的网络传输大小 |
|
||||
| docker image ls | 78MB | 本地解压后的实际大小 |
|
||||
|
||||
### 实际磁盘占用
|
||||
|
||||
由于镜像是分层存储,不同镜像可能共享相同的层:
|
||||
|
||||
```
|
||||
ubuntu:24.04 nginx:latest redis:latest
|
||||
│ │ │
|
||||
└───────┬───────┘ │
|
||||
▼ │
|
||||
共享基础层 ◄───────────────────┘
|
||||
```
|
||||
|
||||
因此,`docker image ls` 中各镜像大小之和 > 实际磁盘占用。
|
||||
|
||||
### 查看实际空间占用
|
||||
|
||||
```bash
|
||||
$ docker system df
|
||||
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
|
||||
Images 15 3 2.5GB 1.8GB (72%)
|
||||
Containers 5 2 100MB 80MB (80%)
|
||||
Local Volumes 8 2 500MB 400MB (80%)
|
||||
Build Cache 0 0 0B 0B
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 过滤镜像
|
||||
|
||||
### 按仓库名过滤
|
||||
|
||||
```bash
|
||||
# 列出所有 ubuntu 镜像
|
||||
$ docker images ubuntu
|
||||
REPOSITORY TAG IMAGE ID SIZE
|
||||
ubuntu 24.04 329ed837d508 78MB
|
||||
ubuntu noble 329ed837d508 78MB
|
||||
ubuntu 22.04 a1b2c3d4e5f6 72MB
|
||||
```
|
||||
|
||||
### 按仓库名和标签过滤
|
||||
|
||||
```bash
|
||||
$ docker images ubuntu:24.04
|
||||
REPOSITORY TAG IMAGE ID SIZE
|
||||
ubuntu 24.04 329ed837d508 78MB
|
||||
```
|
||||
|
||||
### 使用过滤器 --filter
|
||||
|
||||
| 过滤条件 | 说明 | 示例 |
|
||||
|---------|------|------|
|
||||
| `dangling=true` | 虚悬镜像 | `-f dangling=true` |
|
||||
| `before=镜像` | 在某镜像之前创建 | `-f before=nginx:latest` |
|
||||
| `since=镜像` | 在某镜像之后创建 | `-f since=nginx:latest` |
|
||||
| `label=key=value` | 按 LABEL 过滤 | `-f label=version=1.0` |
|
||||
| `reference=pattern` | 按名称模式 | `-f reference='*:latest'` |
|
||||
|
||||
```bash
|
||||
# 列出 nginx 之后创建的镜像
|
||||
$ docker images -f since=nginx:latest
|
||||
|
||||
# 列出所有带 latest 标签的镜像
|
||||
$ docker images -f reference='*:latest'
|
||||
|
||||
# 列出带特定 LABEL 的镜像
|
||||
$ docker images -f label=maintainer=example@email.com
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 虚悬镜像(Dangling Images)
|
||||
|
||||
### 什么是虚悬镜像
|
||||
|
||||
仓库名和标签都显示为 `<none>` 的镜像:
|
||||
|
||||
```bash
|
||||
$ docker images
|
||||
REPOSITORY TAG IMAGE ID SIZE
|
||||
<none> <none> 00285df0df87 342MB
|
||||
```
|
||||
|
||||
### 产生原因
|
||||
|
||||
1. **镜像重新构建**:新镜像使用了旧镜像的标签,旧镜像标签被移除
|
||||
2. **docker pull 更新**:拉取更新版本时,旧版本失去标签
|
||||
|
||||
### 处理虚悬镜像
|
||||
|
||||
```bash
|
||||
# 列出虚悬镜像
|
||||
$ docker images -f dangling=true
|
||||
|
||||
# 删除虚悬镜像
|
||||
$ docker image prune
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 中间层镜像
|
||||
|
||||
### 查看所有镜像(包含中间层)
|
||||
|
||||
```bash
|
||||
$ docker images -a
|
||||
```
|
||||
|
||||
会显示很多无标签镜像——这些是构建过程中产生的中间层,被其他镜像依赖。
|
||||
|
||||
> ⚠️ 不要删除中间层镜像。它们是其他镜像的依赖,删除会导致上层镜像无法使用。删除顶层镜像时会自动清理不再需要的中间层。
|
||||
|
||||
---
|
||||
|
||||
## 格式化输出
|
||||
|
||||
### 只输出 ID
|
||||
|
||||
```bash
|
||||
$ docker images -q
|
||||
5f515359c7f8
|
||||
05a60462f8ba
|
||||
329ed837d508
|
||||
```
|
||||
|
||||
常用于配合其他命令:
|
||||
|
||||
```bash
|
||||
# 删除所有镜像
|
||||
$ docker rmi $(docker images -q)
|
||||
|
||||
# 删除所有 redis 镜像
|
||||
$ docker rmi $(docker images -q redis)
|
||||
```
|
||||
|
||||
### 显示完整 ID
|
||||
|
||||
```bash
|
||||
$ docker images --no-trunc
|
||||
```
|
||||
|
||||
### 显示摘要
|
||||
|
||||
```bash
|
||||
$ docker images --digests
|
||||
REPOSITORY TAG DIGEST IMAGE ID
|
||||
nginx latest sha256:b4f0e0bdeb5... e43d811ce2f4
|
||||
```
|
||||
|
||||
### 自定义格式
|
||||
|
||||
使用 Go 模板语法自定义输出:
|
||||
|
||||
```bash
|
||||
# 只显示 ID 和仓库名
|
||||
$ docker images --format "{{.ID}}: {{.Repository}}"
|
||||
5f515359c7f8: redis
|
||||
05a60462f8ba: nginx
|
||||
329ed837d508: ubuntu
|
||||
|
||||
# 表格形式(带标题)
|
||||
$ docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}"
|
||||
REPOSITORY TAG SIZE
|
||||
redis latest 183MB
|
||||
nginx latest 181MB
|
||||
ubuntu 24.04 78MB
|
||||
```
|
||||
|
||||
### 可用模板字段
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| `.ID` | 镜像 ID |
|
||||
| `.Repository` | 仓库名 |
|
||||
| `.Tag` | 标签 |
|
||||
| `.Digest` | 摘要 |
|
||||
| `.CreatedSince` | 创建后经过的时间 |
|
||||
| `.CreatedAt` | 创建时间 |
|
||||
| `.Size` | 大小 |
|
||||
|
||||
---
|
||||
|
||||
## 常用命令组合
|
||||
|
||||
```bash
|
||||
# 列出所有镜像及其大小,按大小排序(需要系统 sort 命令)
|
||||
$ docker images --format "{{.Size}}\t{{.Repository}}:{{.Tag}}" | sort -h
|
||||
|
||||
# 查找大于 500MB 的镜像
|
||||
$ docker images --format "{{.Size}}\t{{.Repository}}:{{.Tag}}" | grep -E "^[0-9]+GB|^[5-9][0-9]{2}MB"
|
||||
|
||||
# 导出镜像列表
|
||||
$ docker images --format "{{.Repository}}:{{.Tag}}" > images.txt
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 本章小结
|
||||
|
||||
| 操作 | 命令 |
|
||||
|------|------|
|
||||
| 列出所有镜像 | `docker images` |
|
||||
| 按仓库名过滤 | `docker images nginx` |
|
||||
| 列出虚悬镜像 | `docker images -f dangling=true` |
|
||||
| 只输出 ID | `docker images -q` |
|
||||
| 显示摘要 | `docker images --digests` |
|
||||
| 自定义格式 | `docker images --format "..."` |
|
||||
| 查看空间占用 | `docker system df` |
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [获取镜像](pull.md):从 Registry 拉取镜像
|
||||
- [删除镜像](rm.md):清理本地镜像
|
||||
- [镜像](../02_basic_concept/image.md):理解镜像概念
|
||||
232
04_image/pull.md
232
04_image/pull.md
@@ -1,232 +0,0 @@
|
||||
# 获取镜像
|
||||
|
||||
## docker pull 命令
|
||||
|
||||
从镜像仓库获取镜像的命令是 `docker pull`:
|
||||
|
||||
```bash
|
||||
docker pull [选项] [Registry地址/]仓库名[:标签]
|
||||
```
|
||||
|
||||
### 镜像名称格式
|
||||
|
||||
```
|
||||
docker.io / library / ubuntu : 24.04
|
||||
────┬──── ───┬─── ──┬─── ──┬──
|
||||
│ │ │ │
|
||||
Registry地址 用户名 仓库名 标签
|
||||
(可省略) (可省略)
|
||||
```
|
||||
|
||||
| 组成部分 | 说明 | 默认值 |
|
||||
|---------|------|--------|
|
||||
| Registry 地址 | 镜像仓库地址 | `docker.io`(Docker Hub) |
|
||||
| 用户名 | 镜像所属用户/组织 | `library`(官方镜像) |
|
||||
| 仓库名 | 镜像名称 | 必须指定 |
|
||||
| 标签 | 版本标识 | `latest` |
|
||||
|
||||
### 示例
|
||||
|
||||
```bash
|
||||
# 完整格式
|
||||
$ docker pull docker.io/library/ubuntu:24.04
|
||||
|
||||
# 省略 Registry(默认 Docker Hub)
|
||||
$ docker pull library/ubuntu:24.04
|
||||
|
||||
# 省略 library(官方镜像)
|
||||
$ docker pull ubuntu:24.04
|
||||
|
||||
# 省略标签(默认 latest)
|
||||
$ docker pull ubuntu
|
||||
|
||||
# 拉取第三方镜像
|
||||
$ docker pull bitnami/redis:latest
|
||||
|
||||
# 从其他 Registry 拉取
|
||||
$ docker pull ghcr.io/username/myapp:v1.0
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 下载过程解析
|
||||
|
||||
```bash
|
||||
$ docker pull ubuntu:24.04
|
||||
24.04: Pulling from library/ubuntu
|
||||
92dc2a97ff99: Pull complete
|
||||
be13a9d27eb8: Pull complete
|
||||
c8299583700a: Pull complete
|
||||
Digest: sha256:4bc3ae6596938cb0d9e5ac51a1152ec9dcac2a1c50829c74abd9c4361e321b26
|
||||
Status: Downloaded newer image for ubuntu:24.04
|
||||
docker.io/library/ubuntu:24.04
|
||||
```
|
||||
|
||||
### 输出解读
|
||||
|
||||
| 输出内容 | 说明 |
|
||||
|---------|------|
|
||||
| `Pulling from library/ubuntu` | 正在从官方 ubuntu 仓库拉取 |
|
||||
| `92dc2a97ff99: Pull complete` | 各层的下载状态(显示层 ID 前 12 位) |
|
||||
| `Digest: sha256:...` | 镜像内容的唯一摘要 |
|
||||
| `docker.io/library/ubuntu:24.04` | 镜像的完整名称 |
|
||||
|
||||
### 分层下载
|
||||
|
||||
从输出可以看到,镜像是**分层下载**的:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ ubuntu:24.04 镜像 │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 第3层 c8299583700a ───────► 已存在,跳过下载 │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 第2层 be13a9d27eb8 ───────► 下载中... 完成 │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 第1层 92dc2a97ff99 ───────► 下载中... 完成 │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
如果本地已有相同的层,Docker 会跳过下载,节省带宽和时间。
|
||||
|
||||
---
|
||||
|
||||
## 常用选项
|
||||
|
||||
| 选项 | 说明 | 示例 |
|
||||
|------|------|------|
|
||||
| `--all-tags, -a` | 拉取所有标签 | `docker pull -a ubuntu` |
|
||||
| `--platform` | 指定平台架构 | `docker pull --platform linux/arm64 nginx` |
|
||||
| `--quiet, -q` | 静默模式 | `docker pull -q nginx` |
|
||||
|
||||
### 指定平台
|
||||
|
||||
在 Apple Silicon Mac 上拉取 x86 镜像:
|
||||
|
||||
```bash
|
||||
$ docker pull --platform linux/amd64 nginx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 拉取后运行
|
||||
|
||||
拉取镜像后,可以基于它启动容器:
|
||||
|
||||
```bash
|
||||
# 拉取镜像
|
||||
$ docker pull ubuntu:24.04
|
||||
|
||||
# 运行容器
|
||||
$ docker run -it --rm ubuntu:24.04 bash
|
||||
root@e7009c6ce357:/# cat /etc/os-release
|
||||
PRETTY_NAME="Ubuntu 24.04 LTS"
|
||||
...
|
||||
root@e7009c6ce357:/# exit
|
||||
```
|
||||
|
||||
**参数说明**:
|
||||
|
||||
| 参数 | 说明 |
|
||||
|------|------|
|
||||
| `-it` | 交互式终端模式 |
|
||||
| `--rm` | 退出后自动删除容器 |
|
||||
| `bash` | 启动命令 |
|
||||
|
||||
> 💡 `docker run` 在需要时会自动 `pull` 镜像,因此通常不需要单独执行 `docker pull`。
|
||||
|
||||
---
|
||||
|
||||
## 镜像加速
|
||||
|
||||
从 Docker Hub 下载可能较慢。可以配置镜像加速器:
|
||||
|
||||
```json
|
||||
// /etc/docker/daemon.json (Linux)
|
||||
// ~/.docker/daemon.json (Docker Desktop)
|
||||
{
|
||||
"registry-mirrors": [
|
||||
"https://your-accelerator-url"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
配置后重启 Docker:
|
||||
|
||||
```bash
|
||||
$ sudo systemctl restart docker # Linux
|
||||
# 或在 Docker Desktop 中重启
|
||||
```
|
||||
|
||||
详见 [镜像加速器](../install/mirror.md) 章节。
|
||||
|
||||
---
|
||||
|
||||
## 验证镜像完整性
|
||||
|
||||
### 查看镜像摘要
|
||||
|
||||
```bash
|
||||
$ docker images --digests ubuntu
|
||||
REPOSITORY TAG DIGEST IMAGE ID
|
||||
ubuntu 24.04 sha256:4bc3ae6596938cb0d9e5ac51a1152ec9dcac2a1c50829c74abd9c4361e321b26 ca2b0f26964c
|
||||
```
|
||||
|
||||
### 使用摘要拉取
|
||||
|
||||
用摘要拉取可确保获取完全相同的镜像:
|
||||
|
||||
```bash
|
||||
$ docker pull ubuntu@sha256:4bc3ae6596938cb0d9e5ac51a1152ec9dcac2a1c50829c74abd9c4361e321b26
|
||||
```
|
||||
|
||||
> 笔者建议:生产环境使用摘要而非标签,因为标签可能被覆盖,摘要则是不可变的。
|
||||
|
||||
---
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q: 下载速度很慢
|
||||
|
||||
1. 配置镜像加速器
|
||||
2. 检查网络连接
|
||||
3. 尝试拉取更小的镜像版本(如 `alpine` 变体)
|
||||
|
||||
### Q: 提示镜像不存在
|
||||
|
||||
```bash
|
||||
Error: pull access denied, repository does not exist
|
||||
```
|
||||
|
||||
可能原因:
|
||||
- 镜像名拼写错误
|
||||
- 私有镜像未登录(需要 `docker login`)
|
||||
- 镜像确实不存在
|
||||
|
||||
### Q: 磁盘空间不足
|
||||
|
||||
```bash
|
||||
# 清理未使用的镜像
|
||||
$ docker image prune
|
||||
|
||||
# 清理所有未使用资源
|
||||
$ docker system prune
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 本章小结
|
||||
|
||||
| 操作 | 命令 |
|
||||
|------|------|
|
||||
| 拉取镜像 | `docker pull 镜像名:标签` |
|
||||
| 拉取所有标签 | `docker pull -a 镜像名` |
|
||||
| 指定平台 | `docker pull --platform linux/amd64 镜像名` |
|
||||
| 用摘要拉取 | `docker pull 镜像名@sha256:...` |
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [列出镜像](list.md):查看本地镜像
|
||||
- [删除镜像](rm.md):清理本地镜像
|
||||
- [镜像加速器](../install/mirror.md):加速镜像下载
|
||||
- [Docker Hub](../repository/dockerhub.md):官方镜像仓库
|
||||
255
04_image/rm.md
255
04_image/rm.md
@@ -1,255 +0,0 @@
|
||||
# 删除本地镜像
|
||||
|
||||
## 基本用法
|
||||
|
||||
使用 `docker image rm` 删除本地镜像:
|
||||
|
||||
```bash
|
||||
$ docker image rm [选项] <镜像1> [<镜像2> ...]
|
||||
```
|
||||
|
||||
> 💡 `docker rmi` 是 `docker image rm` 的简写,两者等效。
|
||||
|
||||
---
|
||||
|
||||
## 镜像标识方式
|
||||
|
||||
删除镜像时,可以使用多种方式指定镜像:
|
||||
|
||||
| 方式 | 说明 | 示例 |
|
||||
|------|------|------|
|
||||
| **短 ID** | ID 的前几位(通常 3-4 位) | `docker rmi 501` |
|
||||
| **长 ID** | 完整的镜像 ID | `docker rmi 501ad78535f0...` |
|
||||
| **镜像名:标签** | 仓库名和标签 | `docker rmi redis:alpine` |
|
||||
| **镜像摘要** | 精确的内容摘要 | `docker rmi nginx@sha256:...` |
|
||||
|
||||
### 使用短 ID 删除
|
||||
|
||||
```bash
|
||||
$ docker image ls
|
||||
REPOSITORY TAG IMAGE ID SIZE
|
||||
redis alpine 501ad78535f0 30MB
|
||||
nginx latest e43d811ce2f4 142MB
|
||||
|
||||
# 只需输入足够区分的前几位
|
||||
$ docker rmi 501
|
||||
Untagged: redis:alpine
|
||||
Deleted: sha256:501ad78535f0...
|
||||
```
|
||||
|
||||
### 使用镜像名删除
|
||||
|
||||
```bash
|
||||
$ docker rmi redis:alpine
|
||||
Untagged: redis:alpine
|
||||
Deleted: sha256:501ad78535f0...
|
||||
```
|
||||
|
||||
### 使用摘要删除
|
||||
|
||||
摘要删除最精确,适用于 CI/CD 场景:
|
||||
|
||||
```bash
|
||||
# 查看镜像摘要
|
||||
$ docker images --digests
|
||||
REPOSITORY TAG DIGEST IMAGE ID
|
||||
nginx latest sha256:b4f0e0bdeb5... e43d811ce2f4
|
||||
|
||||
# 使用摘要删除
|
||||
$ docker rmi nginx@sha256:b4f0e0bdeb578043c1ea6862f0d40cc4afe32a4a582f3be235a3b164422be228
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 理解输出信息
|
||||
|
||||
删除镜像时会看到两类信息:**Untagged** 和 **Deleted**
|
||||
|
||||
```bash
|
||||
$ docker rmi redis:alpine
|
||||
Untagged: redis:alpine
|
||||
Untagged: redis@sha256:f1ed3708f538b537eb9c2a7dd50dc90a706f7debd7e1196c9264edeea521a86d
|
||||
Deleted: sha256:501ad78535f015d88872e13fa87a828425117e3d28075d0c117932b05bf189b7
|
||||
Deleted: sha256:96167737e29ca8e9d74982ef2a0dda76ed7b430da55e321c071f0dbff8c2899b
|
||||
Deleted: sha256:32770d1dcf835f192cafd6b9263b7b597a1778a403a109e2cc2ee866f74adf23
|
||||
```
|
||||
|
||||
### Untagged vs Deleted
|
||||
|
||||
| 操作 | 含义 |
|
||||
|------|------|
|
||||
| **Untagged** | 移除镜像的标签 |
|
||||
| **Deleted** | 删除镜像的存储层 |
|
||||
|
||||
### 删除流程
|
||||
|
||||
```
|
||||
docker rmi redis:alpine
|
||||
│
|
||||
▼
|
||||
┌───────────────────────────────────────────────────────────────┐
|
||||
│ 1. Untag:移除 redis:alpine 标签 │
|
||||
│ ↓ │
|
||||
│ 2. 检查是否还有其他标签指向这个镜像 │
|
||||
│ ├── 有 → 只 Untag,不删除 │
|
||||
│ └── 无 → │
|
||||
│ ↓ │
|
||||
│ 3. 检查是否有容器依赖 │
|
||||
│ ├── 有 → 报错,无法删除 │
|
||||
│ └── 无 → │
|
||||
│ ↓ │
|
||||
│ 4. 从上到下逐层删除,检查每层是否被其他镜像使用 │
|
||||
│ ├── 被使用 → 保留 │
|
||||
│ └── 未使用 → Deleted │
|
||||
└───────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 批量删除
|
||||
|
||||
### 删除所有虚悬镜像
|
||||
|
||||
虚悬镜像(dangling):没有标签的镜像,通常是旧版本被新版本覆盖后产生的
|
||||
|
||||
```bash
|
||||
# 查看虚悬镜像
|
||||
$ docker images -f dangling=true
|
||||
|
||||
# 删除虚悬镜像
|
||||
$ docker image prune
|
||||
|
||||
# 不提示确认
|
||||
$ docker image prune -f
|
||||
```
|
||||
|
||||
### 删除所有未使用的镜像
|
||||
|
||||
```bash
|
||||
# 删除所有没有被容器使用的镜像
|
||||
$ docker image prune -a
|
||||
|
||||
# 保留最近 24 小时的
|
||||
$ docker image prune -a --filter "until=24h"
|
||||
```
|
||||
|
||||
### 按条件删除
|
||||
|
||||
```bash
|
||||
# 删除所有 redis 镜像
|
||||
$ docker rmi $(docker images -q redis)
|
||||
|
||||
# 删除 mongo:8.0 之前的所有镜像
|
||||
$ docker rmi $(docker images -q -f before=mongo:8.0)
|
||||
|
||||
# 删除某个时间之前的镜像
|
||||
$ docker image prune -a --filter "until=168h" # 7天前
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 删除失败的常见原因
|
||||
|
||||
### 原因一:有容器依赖
|
||||
|
||||
```bash
|
||||
$ docker rmi nginx
|
||||
Error: conflict: unable to remove repository reference "nginx"
|
||||
(must force) - container abc123 is using its referenced image
|
||||
```
|
||||
|
||||
**解决方案**:
|
||||
|
||||
```bash
|
||||
# 方案1:先删除依赖的容器
|
||||
$ docker rm abc123
|
||||
$ docker rmi nginx
|
||||
|
||||
# 方案2:强制删除镜像(容器仍可运行,但无法再创建新容器)
|
||||
$ docker rmi -f nginx
|
||||
```
|
||||
|
||||
### 原因二:多个标签指向同一镜像
|
||||
|
||||
```bash
|
||||
$ docker images
|
||||
REPOSITORY TAG IMAGE ID
|
||||
ubuntu 24.04 ca2b0f26964c
|
||||
ubuntu latest ca2b0f26964c # 同一个镜像
|
||||
|
||||
$ docker rmi ubuntu:24.04
|
||||
Untagged: ubuntu:24.04
|
||||
# 只是移除标签,镜像仍存在(因为还有 ubuntu:latest 指向它)
|
||||
```
|
||||
|
||||
### 原因三:被其他镜像依赖(中间层)
|
||||
|
||||
```bash
|
||||
$ docker rmi some_base_image
|
||||
Error: image has dependent child images
|
||||
```
|
||||
|
||||
中间层镜像被其他镜像依赖,无法删除。需要先删除依赖它的镜像。
|
||||
|
||||
---
|
||||
|
||||
## 常用过滤条件
|
||||
|
||||
| 过滤条件 | 说明 | 示例 |
|
||||
|---------|------|------|
|
||||
| `dangling=true` | 虚悬镜像 | `-f dangling=true` |
|
||||
| `before=镜像` | 在某镜像之前 | `-f before=mongo:3.2` |
|
||||
| `since=镜像` | 在某镜像之后 | `-f since=mongo:3.2` |
|
||||
| `label=key=value` | 按标签过滤 | `-f label=version=1.0` |
|
||||
| `reference=pattern` | 按名称模式 | `-f reference='*:latest'` |
|
||||
|
||||
---
|
||||
|
||||
## 清理策略
|
||||
|
||||
### 开发环境
|
||||
|
||||
```bash
|
||||
# 定期清理虚悬镜像
|
||||
$ docker image prune -f
|
||||
|
||||
# 一键清理所有未使用资源
|
||||
$ docker system prune -a
|
||||
```
|
||||
|
||||
### CI/CD 环境
|
||||
|
||||
```bash
|
||||
# 只保留最近使用的镜像
|
||||
$ docker image prune -a --filter "until=72h" -f
|
||||
```
|
||||
|
||||
### 查看空间占用
|
||||
|
||||
```bash
|
||||
$ docker system df
|
||||
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
|
||||
Images 15 3 2.5GB 1.8GB (72%)
|
||||
Containers 5 2 100MB 80MB (80%)
|
||||
Local Volumes 8 2 500MB 400MB (80%)
|
||||
Build Cache 0 0 0B 0B
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 本章小结
|
||||
|
||||
| 操作 | 命令 |
|
||||
|------|------|
|
||||
| 删除指定镜像 | `docker rmi 镜像名:标签` |
|
||||
| 强制删除 | `docker rmi -f 镜像名` |
|
||||
| 删除虚悬镜像 | `docker image prune` |
|
||||
| 删除未使用镜像 | `docker image prune -a` |
|
||||
| 批量删除 | `docker rmi $(docker images -q -f ...)` |
|
||||
| 查看空间占用 | `docker system df` |
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [列出镜像](list.md):查看和过滤镜像
|
||||
- [删除容器](../05_container/rm.md):清理容器
|
||||
- [数据卷](../07_data_network/data/volume.md):清理数据卷
|
||||
@@ -1,264 +0,0 @@
|
||||
# 进入容器
|
||||
|
||||
## 为什么需要进入容器
|
||||
|
||||
使用 `-d` 参数启动容器后,容器在后台运行。以下场景需要进入容器内部操作:
|
||||
|
||||
| 场景 | 示例 |
|
||||
|------|------|
|
||||
| **调试问题** | 查看日志、检查配置、排查错误 |
|
||||
| **临时操作** | 执行数据库迁移、清理缓存 |
|
||||
| **检查状态** | 查看进程、网络连接、文件系统 |
|
||||
| **开发测试** | 交互式测试命令、验证环境 |
|
||||
|
||||
## 两种进入方式
|
||||
|
||||
Docker 提供两种进入容器的命令:
|
||||
|
||||
| 命令 | 推荐程度 | 特点 |
|
||||
|------|---------|------|
|
||||
| `docker exec` | ✅ **推荐** | 启动新进程,退出不影响容器 |
|
||||
| `docker attach` | ⚠️ 谨慎使用 | 附加到主进程,退出可能停止容器 |
|
||||
|
||||
---
|
||||
|
||||
## docker exec(推荐)
|
||||
|
||||
### 基本用法
|
||||
|
||||
```bash
|
||||
# 进入容器并启动交互式 shell
|
||||
$ docker exec -it 容器名 /bin/bash
|
||||
|
||||
# 或使用 sh(适用于 Alpine 等精简镜像)
|
||||
$ docker exec -it 容器名 /bin/sh
|
||||
```
|
||||
|
||||
### 参数说明
|
||||
|
||||
| 参数 | 作用 |
|
||||
|------|------|
|
||||
| `-i` | 保持标准输入打开(interactive) |
|
||||
| `-t` | 分配伪终端(TTY) |
|
||||
| `-it` | 两者组合,获得完整交互体验 |
|
||||
| `-u` | 指定用户(如 `-u root`) |
|
||||
| `-w` | 指定工作目录 |
|
||||
| `-e` | 设置环境变量 |
|
||||
|
||||
### 示例
|
||||
|
||||
```bash
|
||||
# 启动一个后台容器
|
||||
$ docker run -dit --name myubuntu ubuntu
|
||||
69d137adef7a...
|
||||
|
||||
# 进入容器(交互式 shell)
|
||||
$ docker exec -it myubuntu bash
|
||||
root@69d137adef7a:/# ls
|
||||
bin boot dev etc home lib ...
|
||||
root@69d137adef7a:/# exit
|
||||
|
||||
# 容器仍在运行!
|
||||
$ docker ps
|
||||
CONTAINER ID IMAGE STATUS NAMES
|
||||
69d137adef7a ubuntu Up 2 minutes myubuntu
|
||||
```
|
||||
|
||||
### 执行单条命令
|
||||
|
||||
不进入交互模式,直接执行命令:
|
||||
|
||||
```bash
|
||||
# 查看容器内进程
|
||||
$ docker exec myubuntu ps aux
|
||||
|
||||
# 查看配置文件
|
||||
$ docker exec myubuntu cat /etc/nginx/nginx.conf
|
||||
|
||||
# 以 root 用户执行
|
||||
$ docker exec -u root myubuntu apt update
|
||||
```
|
||||
|
||||
### 只用 -i 不用 -t 的区别
|
||||
|
||||
```bash
|
||||
# 只用 -i:可以执行命令,但没有提示符
|
||||
$ docker exec -i myubuntu bash
|
||||
ls # 输入命令
|
||||
bin # 输出结果
|
||||
boot
|
||||
dev
|
||||
...
|
||||
|
||||
# 用 -it:有完整的终端体验
|
||||
$ docker exec -it myubuntu bash
|
||||
root@69d137adef7a:/# # 有提示符
|
||||
```
|
||||
|
||||
> 💡 通常使用 `-it` 组合。只有在脚本中需要通过管道传入命令时才只用 `-i`。
|
||||
|
||||
---
|
||||
|
||||
## docker attach(谨慎使用)
|
||||
|
||||
### 基本用法
|
||||
|
||||
```bash
|
||||
$ docker attach 容器名
|
||||
```
|
||||
|
||||
### 工作原理
|
||||
|
||||
`attach` 会附加到容器的**主进程**(PID 1)的标准输入输出:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ 容器 │
|
||||
│ ┌─────────────────────────────────┐ │
|
||||
│ │ PID 1: /bin/bash (主进程) │◄───┼─── docker attach 附加到这里
|
||||
│ │ └─ 你的输入直接发送到主进程 │ │
|
||||
│ └─────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 示例
|
||||
|
||||
```bash
|
||||
# 启动容器
|
||||
$ docker run -dit --name myubuntu ubuntu
|
||||
243c32535da7...
|
||||
|
||||
# 附加到容器
|
||||
$ docker attach myubuntu
|
||||
root@243c32535da7:/#
|
||||
```
|
||||
|
||||
### ⚠️ 重要警告
|
||||
|
||||
**从 attach 会话中输入 `exit` 或按 `Ctrl+D` 会导致容器停止!**
|
||||
|
||||
```bash
|
||||
$ docker attach myubuntu
|
||||
root@243c32535da7:/# exit # 这会停止容器!
|
||||
|
||||
$ docker ps
|
||||
CONTAINER ID IMAGE STATUS NAMES
|
||||
243c32535da7 ubuntu Exited (0) 2 seconds ago myubuntu
|
||||
```
|
||||
|
||||
**原因**:attach 附加到主进程,退出主进程就等于退出容器。
|
||||
|
||||
### 安全退出 attach
|
||||
|
||||
使用 `Ctrl+P` 然后 `Ctrl+Q` 可以从 attach 会话中**分离**,而不停止容器:
|
||||
|
||||
```bash
|
||||
$ docker attach myubuntu
|
||||
root@243c32535da7:/#
|
||||
# 按 Ctrl+P 然后 Ctrl+Q
|
||||
read escape sequence
|
||||
|
||||
$ docker ps # 容器仍在运行
|
||||
CONTAINER ID IMAGE STATUS NAMES
|
||||
243c32535da7 ubuntu Up 5 minutes myubuntu
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## exec vs attach 对比
|
||||
|
||||
| 特性 | docker exec | docker attach |
|
||||
|------|-------------|---------------|
|
||||
| **工作方式** | 在容器内启动新进程 | 附加到主进程 |
|
||||
| **退出影响** | 不影响容器 | 可能停止容器 |
|
||||
| **多终端** | 可以开多个 | 共享同一个会话 |
|
||||
| **适用场景** | 调试、临时操作 | 查看主进程输出 |
|
||||
| **推荐程度** | ✅ 推荐 | ⚠️ 特殊场景使用 |
|
||||
|
||||
```
|
||||
docker exec docker attach
|
||||
┌─────────────────────┐ ┌─────────────────────┐
|
||||
│ 容器 │ │ 容器 │
|
||||
│ ┌───────────────┐ │ │ ┌───────────────┐ │
|
||||
│ │ PID 1: nginx │ │ │ │ PID 1: bash │◄─┼── 附加到主进程
|
||||
│ ├───────────────┤ │ │ └───────────────┘ │
|
||||
│ │ PID 50: bash │◄─┼── 新进程 │ │
|
||||
│ └───────────────┘ │ │ │
|
||||
└─────────────────────┘ └─────────────────────┘
|
||||
退出 bash 不影响 nginx 退出 bash 容器停止
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 首选 docker exec
|
||||
|
||||
```bash
|
||||
# 进入容器调试
|
||||
$ docker exec -it myapp bash
|
||||
|
||||
# 查看日志
|
||||
$ docker exec myapp tail -f /var/log/app.log
|
||||
|
||||
# 执行数据库迁移
|
||||
$ docker exec myapp python manage.py migrate
|
||||
```
|
||||
|
||||
### 2. 生产环境避免进入容器
|
||||
|
||||
笔者建议:生产环境应尽量避免进入容器直接操作,而是通过:
|
||||
- 日志系统查看日志(如 `docker logs` 或集中式日志)
|
||||
- 监控系统查看状态
|
||||
- 重新部署而非手动修改
|
||||
|
||||
### 3. 无 shell 镜像的处理
|
||||
|
||||
某些精简镜像(如基于 `scratch` 或 `distroless`)没有 shell:
|
||||
|
||||
```bash
|
||||
# 这会失败
|
||||
$ docker exec -it myapp bash
|
||||
OCI runtime exec failed: exec failed: unable to start container process: exec: "bash": executable file not found
|
||||
|
||||
# 解决方案:使用调试容器(Docker Desktop 或 Kubernetes debug)
|
||||
$ docker debug myapp
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q: exec 进入后看不到其他终端的操作
|
||||
|
||||
这是正常的。exec 启动的是独立进程,多个 exec 会话互不影响。
|
||||
|
||||
### Q: 容器没有 bash
|
||||
|
||||
尝试使用 sh:
|
||||
|
||||
```bash
|
||||
$ docker exec -it myapp /bin/sh
|
||||
```
|
||||
|
||||
### Q: 需要 root 权限
|
||||
|
||||
```bash
|
||||
$ docker exec -u root -it myapp bash
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 本章小结
|
||||
|
||||
| 需求 | 推荐命令 |
|
||||
|------|---------|
|
||||
| 进入容器调试 | `docker exec -it 容器名 bash` |
|
||||
| 执行单条命令 | `docker exec 容器名 命令` |
|
||||
| 查看主进程输出 | `docker attach 容器名`(慎用) |
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [后台运行](daemon.md):理解容器主进程
|
||||
- [查看容器](ls.md):列出和过滤容器
|
||||
- [容器日志](logs.md):查看容器输出
|
||||
@@ -1,218 +0,0 @@
|
||||
# 后台运行
|
||||
|
||||
在生产环境中,我们通常需要容器持续运行,不受终端关闭的影响。本节将深入讲解如何让容器在后台运行,以及理解容器生命周期的核心概念。
|
||||
|
||||
## 核心概念:前台 vs 后台
|
||||
|
||||
当你在终端运行一个程序时,有两种模式:
|
||||
|
||||
- **前台运行**:程序占用当前终端,输出直接显示,关闭终端程序就停止
|
||||
- **后台运行**:程序在后台执行,不占用终端,终端关闭也不影响程序
|
||||
|
||||
Docker 容器默认是**前台运行**的。使用 `-d`(detach)参数可以让容器在后台运行。
|
||||
|
||||
## 基本使用
|
||||
|
||||
### 前台运行(默认)
|
||||
|
||||
```bash
|
||||
$ docker run ubuntu:24.04 /bin/sh -c "while true; do echo hello world; sleep 1; done"
|
||||
hello world
|
||||
hello world
|
||||
hello world
|
||||
hello world
|
||||
```
|
||||
|
||||
容器会把输出的结果(STDOUT)打印到宿主机上面。此时:
|
||||
- 终端被占用,无法执行其他命令
|
||||
- 按 `Ctrl+C` 会终止容器
|
||||
- 关闭终端窗口,容器也会停止
|
||||
|
||||
### 后台运行(使用 -d 参数)
|
||||
|
||||
```bash
|
||||
$ docker run -d ubuntu:24.04 /bin/sh -c "while true; do echo hello world; sleep 1; done"
|
||||
77b2dc01fe0f3f1265df143181e7b9af5e05279a884f4776ee75350ea9d8017a
|
||||
```
|
||||
|
||||
使用 `-d` 参数后:
|
||||
- 容器在后台运行
|
||||
- 返回容器的完整 ID
|
||||
- 终端立即释放,可以继续执行其他命令
|
||||
- 输出不会直接显示(需要用 `docker logs` 查看)
|
||||
|
||||
## 深入理解:容器为什么会"立即退出"?
|
||||
|
||||
> **这是初学者最常遇到的困惑。** 理解这个问题,你就理解了 Docker 的核心设计理念。
|
||||
|
||||
很多人尝试这样启动容器:
|
||||
|
||||
```bash
|
||||
$ docker run -d ubuntu:24.04
|
||||
```
|
||||
|
||||
然后用 `docker ps` 查看,发现容器根本不在运行!这是为什么?
|
||||
|
||||
### 核心原理:容器的生命周期与主进程绑定
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Docker 容器的生命周期 = 容器内 PID 1 进程的生命周期 │
|
||||
│ │
|
||||
│ 主进程启动 → 容器运行 │
|
||||
│ 主进程退出 → 容器停止 │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
当你运行 `docker run -d ubuntu:24.04` 时:
|
||||
1. 容器启动
|
||||
2. 没有指定命令,默认执行 `/bin/bash`
|
||||
3. 但没有交互式终端(没有 `-it` 参数),bash 发现没有输入源
|
||||
4. bash 立即退出
|
||||
5. 主进程退出,容器停止
|
||||
|
||||
**关键理解**:
|
||||
- ❌ `-d` 参数**不是**让容器"一直运行"
|
||||
- ✅ `-d` 参数是让容器"在后台运行",能运行多久取决于主进程
|
||||
|
||||
### 常见的"立即退出"场景
|
||||
|
||||
| 场景 | 原因 | 解决方案 |
|
||||
|------|------|---------|
|
||||
| `docker run -d ubuntu` | 默认 bash 无输入立即退出 | 指定长期运行的命令 |
|
||||
| `docker run -d nginx` 后改了配置 | 配置错误导致 nginx 启动失败 | 查看 `docker logs` |
|
||||
| 自定义镜像容器启动即退 | Dockerfile 的 CMD 执行完毕 | 确保 CMD 是前台进程 |
|
||||
|
||||
## 查看后台容器
|
||||
|
||||
### 查看运行中的容器
|
||||
|
||||
```bash
|
||||
$ docker container ls
|
||||
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
|
||||
77b2dc01fe0f ubuntu:24.04 /bin/sh -c 'while tr 2 minutes ago Up 1 minute agitated_wright
|
||||
```
|
||||
|
||||
### 查看容器输出日志
|
||||
|
||||
```bash
|
||||
$ docker container logs 77b2dc01fe0f
|
||||
hello world
|
||||
hello world
|
||||
hello world
|
||||
...
|
||||
```
|
||||
|
||||
**实时查看日志**(类似 `tail -f`):
|
||||
|
||||
```bash
|
||||
$ docker container logs -f 77b2dc01fe0f
|
||||
```
|
||||
|
||||
### 查看已停止的容器
|
||||
|
||||
```bash
|
||||
$ docker container ls -a
|
||||
```
|
||||
|
||||
加上 `-a` 参数可以看到所有容器,包括已停止的。这对于调试"容器启动即退出"的问题非常有用。
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 长期运行的服务使用 -d
|
||||
|
||||
```bash
|
||||
# Web 服务器
|
||||
$ docker run -d -p 80:80 nginx
|
||||
|
||||
# 数据库
|
||||
$ docker run -d -p 3306:3306 mysql:8
|
||||
|
||||
# 缓存服务
|
||||
$ docker run -d -p 6379:6379 redis
|
||||
```
|
||||
|
||||
### 2. 调试时先用前台模式
|
||||
|
||||
当容器启动有问题时,**去掉 `-d` 参数**可以直接看到输出和错误:
|
||||
|
||||
```bash
|
||||
# 有问题的容器,先前台运行看看发生了什么
|
||||
$ docker run myimage:latest
|
||||
```
|
||||
|
||||
### 3. 使用 --rm 自动清理
|
||||
|
||||
对于一次性任务,使用 `--rm` 参数让容器退出后自动删除:
|
||||
|
||||
```bash
|
||||
$ docker run --rm ubuntu:24.04 echo "Hello, World!"
|
||||
Hello, World!
|
||||
# 容器执行完后自动删除
|
||||
```
|
||||
|
||||
### 4. 配合日志查看
|
||||
|
||||
```bash
|
||||
# 后台启动
|
||||
$ docker run -d --name myapp myimage:latest
|
||||
|
||||
# 查看最近 100 行日志
|
||||
$ docker logs --tail 100 myapp
|
||||
|
||||
# 实时跟踪日志
|
||||
$ docker logs -f myapp
|
||||
|
||||
# 查看带时间戳的日志
|
||||
$ docker logs -t myapp
|
||||
```
|
||||
|
||||
## 常见问题排查
|
||||
|
||||
### Q: 容器启动后立即退出
|
||||
|
||||
1. **查看退出状态码**:
|
||||
```bash
|
||||
$ docker ps -a --filter "name=mycontainer"
|
||||
# 查看 STATUS 列,如 "Exited (1)" 表示异常退出
|
||||
```
|
||||
|
||||
2. **查看容器日志**:
|
||||
```bash
|
||||
$ docker logs mycontainer
|
||||
```
|
||||
|
||||
3. **以交互模式调试**:
|
||||
```bash
|
||||
$ docker run -it myimage:latest /bin/sh
|
||||
# 进入容器手动执行命令,查找问题
|
||||
```
|
||||
|
||||
### Q: 容器在后台运行但无法访问服务
|
||||
|
||||
1. **检查端口映射**:
|
||||
```bash
|
||||
$ docker port mycontainer
|
||||
```
|
||||
|
||||
2. **检查容器内服务状态**:
|
||||
```bash
|
||||
$ docker exec mycontainer ps aux
|
||||
```
|
||||
|
||||
### Q: 如何让已经在后台运行的容器回到前台?
|
||||
|
||||
使用 `docker attach`:
|
||||
|
||||
```bash
|
||||
$ docker attach mycontainer
|
||||
```
|
||||
|
||||
> **注意**:`attach` 会连接到容器的主进程。如果主进程不是交互式的,你可能只能看到输出。使用 `Ctrl+P` `Ctrl+Q` 可以安全退出而不停止容器。
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [进入容器](attach_exec.md):如何进入正在运行的容器执行命令
|
||||
- [容器日志](../15_appendix/best_practices.md):生产环境的日志管理最佳实践
|
||||
- [HEALTHCHECK 健康检查](../04_image/dockerfile/healthcheck.md):自动检测容器内服务是否正常
|
||||
- [Docker Compose](../compose/README.md):管理多个后台容器的更好方式
|
||||
@@ -1,238 +0,0 @@
|
||||
# 删除容器
|
||||
|
||||
## 基本用法
|
||||
|
||||
使用 `docker rm` 删除已停止的容器:
|
||||
|
||||
```bash
|
||||
$ docker rm 容器名或ID
|
||||
```
|
||||
|
||||
> 💡 `docker rm` 是 `docker container rm` 的简写,两者等效。
|
||||
|
||||
---
|
||||
|
||||
## 删除选项
|
||||
|
||||
| 选项 | 说明 | 示例 |
|
||||
|------|------|------|
|
||||
| 无参数 | 删除已停止的容器 | `docker rm mycontainer` |
|
||||
| `-f` | 强制删除运行中的容器 | `docker rm -f mycontainer` |
|
||||
| `-v` | 同时删除关联的匿名卷 | `docker rm -v mycontainer` |
|
||||
|
||||
### 删除已停止的容器
|
||||
|
||||
```bash
|
||||
$ docker rm mycontainer
|
||||
mycontainer
|
||||
```
|
||||
|
||||
### 强制删除运行中的容器
|
||||
|
||||
```bash
|
||||
# 不加 -f 会报错
|
||||
$ docker rm running_container
|
||||
Error: cannot remove running container
|
||||
|
||||
# 加 -f 强制删除
|
||||
$ docker rm -f running_container
|
||||
running_container
|
||||
```
|
||||
|
||||
> ⚠️ 强制删除会向容器发送 SIGKILL 信号,可能导致数据丢失。建议先 `docker stop` 优雅停止。
|
||||
|
||||
### 删除容器及其数据卷
|
||||
|
||||
```bash
|
||||
# 删除容器时同时删除其匿名卷
|
||||
$ docker rm -v mycontainer
|
||||
```
|
||||
|
||||
> 注意:只删除匿名卷,命名卷不会被删除。
|
||||
|
||||
---
|
||||
|
||||
## 批量删除
|
||||
|
||||
### 删除所有已停止的容器
|
||||
|
||||
```bash
|
||||
# 方式一:使用 prune 命令(推荐)
|
||||
$ docker container prune
|
||||
|
||||
WARNING! This will remove all stopped containers.
|
||||
Are you sure you want to continue? [y/N] y
|
||||
Deleted Containers:
|
||||
abc123...
|
||||
def456...
|
||||
Total reclaimed space: 150MB
|
||||
|
||||
# 方式二:不提示确认
|
||||
$ docker container prune -f
|
||||
```
|
||||
|
||||
### 删除所有容器(包括运行中的)
|
||||
|
||||
```bash
|
||||
# 先停止所有容器,再删除
|
||||
$ docker stop $(docker ps -q)
|
||||
$ docker rm $(docker ps -aq)
|
||||
|
||||
# 或者直接强制删除
|
||||
$ docker rm -f $(docker ps -aq)
|
||||
```
|
||||
|
||||
### 按条件删除
|
||||
|
||||
```bash
|
||||
# 删除所有已退出的容器
|
||||
$ docker rm $(docker ps -aq -f status=exited)
|
||||
|
||||
# 删除名称包含 "test" 的容器
|
||||
$ docker rm $(docker ps -aq -f name=test)
|
||||
|
||||
# 删除 24 小时前创建的容器
|
||||
$ docker container prune --filter "until=24h"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 常用过滤条件
|
||||
|
||||
`docker ps` 的过滤条件可以配合 `rm` 使用:
|
||||
|
||||
| 过滤条件 | 说明 | 示例 |
|
||||
|---------|------|------|
|
||||
| `status=exited` | 已退出的容器 | `-f status=exited` |
|
||||
| `status=created` | 已创建未启动 | `-f status=created` |
|
||||
| `name=xxx` | 名称匹配 | `-f name=myapp` |
|
||||
| `ancestor=xxx` | 基于某镜像创建 | `-f ancestor=nginx` |
|
||||
| `before=xxx` | 在某容器之前创建 | `-f before=mycontainer` |
|
||||
| `since=xxx` | 在某容器之后创建 | `-f since=mycontainer` |
|
||||
|
||||
### 示例
|
||||
|
||||
```bash
|
||||
# 删除所有基于 nginx 镜像的容器
|
||||
$ docker rm $(docker ps -aq -f ancestor=nginx)
|
||||
|
||||
# 删除所有创建后未启动的容器
|
||||
$ docker rm $(docker ps -aq -f status=created)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 容器与镜像的依赖关系
|
||||
|
||||
> 有容器依赖的镜像无法删除。
|
||||
|
||||
```bash
|
||||
# 尝试删除有容器依赖的镜像
|
||||
$ docker image rm nginx
|
||||
Error: image is being used by stopped container abc123
|
||||
|
||||
# 需要先删除依赖该镜像的容器
|
||||
$ docker rm abc123
|
||||
$ docker image rm nginx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 清理策略建议
|
||||
|
||||
### 开发环境
|
||||
|
||||
```bash
|
||||
# 定期清理已停止的容器
|
||||
$ docker container prune -f
|
||||
|
||||
# 一键清理所有未使用资源
|
||||
$ docker system prune -f
|
||||
```
|
||||
|
||||
### 生产环境
|
||||
|
||||
```bash
|
||||
# 使用 --rm 参数运行临时容器
|
||||
$ docker run --rm ubuntu echo "Hello"
|
||||
# 容器退出后自动删除
|
||||
|
||||
# 定期清理(设置保留时间)
|
||||
$ docker container prune --filter "until=168h" # 保留 7 天内的
|
||||
```
|
||||
|
||||
### 完整清理脚本
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# cleanup.sh - Docker 资源清理脚本
|
||||
|
||||
echo "清理已停止的容器..."
|
||||
docker container prune -f
|
||||
|
||||
echo "清理未使用的镜像..."
|
||||
docker image prune -f
|
||||
|
||||
echo "清理未使用的数据卷..."
|
||||
docker volume prune -f
|
||||
|
||||
echo "清理未使用的网络..."
|
||||
docker network prune -f
|
||||
|
||||
echo "清理完成!"
|
||||
docker system df
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q: 容器无法删除
|
||||
|
||||
```bash
|
||||
Error: container is running
|
||||
```
|
||||
|
||||
解决:先停止容器,或使用 `-f` 强制删除
|
||||
|
||||
```bash
|
||||
$ docker stop mycontainer
|
||||
$ docker rm mycontainer
|
||||
# 或
|
||||
$ docker rm -f mycontainer
|
||||
```
|
||||
|
||||
### Q: 删除后磁盘空间没释放
|
||||
|
||||
可能原因:
|
||||
1. 容器的数据卷未删除(使用 `-v` 参数)
|
||||
2. 镜像未删除
|
||||
3. 构建缓存未清理
|
||||
|
||||
解决:
|
||||
|
||||
```bash
|
||||
# 查看空间占用
|
||||
$ docker system df
|
||||
|
||||
# 完整清理
|
||||
$ docker system prune -a --volumes
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 本章小结
|
||||
|
||||
| 操作 | 命令 |
|
||||
|------|------|
|
||||
| 删除已停止容器 | `docker rm 容器名` |
|
||||
| 强制删除运行中容器 | `docker rm -f 容器名` |
|
||||
| 删除容器及匿名卷 | `docker rm -v 容器名` |
|
||||
| 清理所有已停止容器 | `docker container prune` |
|
||||
| 删除所有容器 | `docker rm -f $(docker ps -aq)` |
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [终止容器](stop.md):优雅停止容器
|
||||
- [删除镜像](../04_image/rm.md):清理镜像
|
||||
- [数据卷](../07_data_network/data/volume.md):数据卷管理
|
||||
@@ -1,229 +0,0 @@
|
||||
# 启动容器
|
||||
|
||||
## 启动方式概述
|
||||
|
||||
启动容器有两种方式:
|
||||
- **新建并启动**:基于镜像创建新容器
|
||||
- **重新启动**:将已终止的容器重新运行
|
||||
|
||||
由于 Docker 容器非常轻量,实际使用中常常是随时删除和新建容器,而不是反复重启同一个容器。
|
||||
|
||||
## 新建并启动
|
||||
|
||||
### 基本语法
|
||||
|
||||
```bash
|
||||
docker run [选项] 镜像 [命令] [参数...]
|
||||
```
|
||||
|
||||
### 最简单的例子
|
||||
|
||||
输出 "Hello World" 后容器自动终止:
|
||||
|
||||
```bash
|
||||
$ docker run ubuntu:24.04 /bin/echo 'Hello world'
|
||||
Hello world
|
||||
```
|
||||
|
||||
这与直接执行 `/bin/echo 'Hello world'` 几乎没有区别,但实际上已经启动了一个完整的 Ubuntu 容器来执行这条命令。
|
||||
|
||||
### 交互式容器
|
||||
|
||||
启动一个可以交互的 bash 终端:
|
||||
|
||||
```bash
|
||||
$ docker run -it ubuntu:24.04 /bin/bash
|
||||
root@af8bae53bdd3:/#
|
||||
```
|
||||
|
||||
**参数说明**:
|
||||
|
||||
| 参数 | 作用 |
|
||||
|------|------|
|
||||
| `-i` | 保持标准输入(stdin)打开,允许输入 |
|
||||
| `-t` | 分配伪终端(pseudo-TTY),提供终端界面 |
|
||||
| `-it` | 两者组合使用,获得交互式终端 |
|
||||
|
||||
在交互模式下可以执行命令:
|
||||
|
||||
```bash
|
||||
root@af8bae53bdd3:/# pwd
|
||||
/
|
||||
root@af8bae53bdd3:/# ls
|
||||
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
|
||||
root@af8bae53bdd3:/# exit # 退出容器
|
||||
```
|
||||
|
||||
## docker run 的完整流程
|
||||
|
||||
执行 `docker run` 时,Docker 在后台完成以下操作:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ docker run ubuntu:24.04 /bin/echo "Hello" │
|
||||
└───────────────────────────────┬─────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ 1. 检查本地是否有 ubuntu:24.04 镜像 │
|
||||
│ ├── 有 → 使用本地镜像 │
|
||||
│ └── 无 → 从 Registry 下载 │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ 2. 创建容器 │
|
||||
│ • 基于镜像的只读层 │
|
||||
│ • 添加一层可读写层(容器存储层) │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ 3. 配置网络 │
|
||||
│ • 创建虚拟网卡 │
|
||||
│ • 分配 IP 地址 │
|
||||
│ • 连接到 Docker 网桥 │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ 4. 启动容器,执行指定命令 │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ 5. 命令执行完毕,容器停止 │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 常用启动选项
|
||||
|
||||
### 基础选项
|
||||
|
||||
| 选项 | 说明 | 示例 |
|
||||
|------|------|------|
|
||||
| `-d` | 后台运行(detach) | `docker run -d nginx` |
|
||||
| `-it` | 交互式终端 | `docker run -it ubuntu bash` |
|
||||
| `--name` | 指定容器名称 | `docker run --name myapp nginx` |
|
||||
| `--rm` | 退出后自动删除容器 | `docker run --rm ubuntu echo hi` |
|
||||
|
||||
### 端口映射
|
||||
|
||||
```bash
|
||||
# 将容器的 80 端口映射到宿主机的 8080 端口
|
||||
$ docker run -d -p 8080:80 nginx
|
||||
|
||||
# 随机映射端口
|
||||
$ docker run -d -P nginx
|
||||
|
||||
# 只绑定到 localhost
|
||||
$ docker run -d -p 127.0.0.1:8080:80 nginx
|
||||
```
|
||||
|
||||
### 数据卷挂载
|
||||
|
||||
```bash
|
||||
# 挂载命名卷
|
||||
$ docker run -v mydata:/data nginx
|
||||
|
||||
# 挂载宿主机目录
|
||||
$ docker run -v /host/path:/container/path nginx
|
||||
|
||||
# 只读挂载
|
||||
$ docker run -v /host/path:/container/path:ro nginx
|
||||
```
|
||||
|
||||
### 环境变量
|
||||
|
||||
```bash
|
||||
# 设置单个环境变量
|
||||
$ docker run -e MYSQL_ROOT_PASSWORD=secret mysql
|
||||
|
||||
# 从文件加载环境变量
|
||||
$ docker run --env-file .env myapp
|
||||
```
|
||||
|
||||
### 资源限制
|
||||
|
||||
```bash
|
||||
# 限制内存
|
||||
$ docker run -m 512m nginx
|
||||
|
||||
# 限制 CPU
|
||||
$ docker run --cpus=1.5 nginx
|
||||
```
|
||||
|
||||
## 启动已终止容器
|
||||
|
||||
使用 `docker start` 重新启动已停止的容器:
|
||||
|
||||
```bash
|
||||
# 查看所有容器(包括已停止的)
|
||||
$ docker ps -a
|
||||
CONTAINER ID IMAGE STATUS NAMES
|
||||
af8bae53bdd3 ubuntu Exited (0) 2 minutes ago myubuntu
|
||||
|
||||
# 重新启动
|
||||
$ docker start myubuntu
|
||||
|
||||
# 启动并附加终端
|
||||
$ docker start -ai myubuntu
|
||||
```
|
||||
|
||||
## 容器内进程的特点
|
||||
|
||||
容器内只运行指定的应用程序及其必需资源:
|
||||
|
||||
```bash
|
||||
root@ba267838cc1b:/# ps
|
||||
PID TTY TIME CMD
|
||||
1 ? 00:00:00 bash
|
||||
11 ? 00:00:00 ps
|
||||
```
|
||||
|
||||
可见容器中仅运行了 `bash` 进程。这种特点使得 Docker 对资源的利用率极高。
|
||||
|
||||
> 💡 笔者提示:容器内的 PID 1 进程很重要——它是容器的主进程,该进程退出则容器停止。详见[后台运行](daemon.md)章节。
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q: 容器启动后立即退出
|
||||
|
||||
**原因**:主进程执行完毕或无法保持运行
|
||||
|
||||
```bash
|
||||
# 这个容器会立即退出(echo 执行完就结束了)
|
||||
$ docker run ubuntu echo "hello"
|
||||
|
||||
# 解决:使用能持续运行的命令
|
||||
$ docker run -d nginx # nginx 是持续运行的服务
|
||||
```
|
||||
|
||||
详细解释见[后台运行](daemon.md)。
|
||||
|
||||
### Q: 无法连接容器内的服务
|
||||
|
||||
**原因**:未正确映射端口
|
||||
|
||||
```bash
|
||||
# 错误:没有 -p 参数,外部无法访问
|
||||
$ docker run -d nginx
|
||||
|
||||
# 正确:映射端口
|
||||
$ docker run -d -p 80:80 nginx
|
||||
```
|
||||
|
||||
### Q: 容器内修改的文件丢失
|
||||
|
||||
**原因**:未使用数据卷,数据保存在容器存储层
|
||||
|
||||
```bash
|
||||
# 使用数据卷持久化
|
||||
$ docker run -v mydata:/app/data myapp
|
||||
```
|
||||
|
||||
详见[数据管理](../07_data_network/README.md)。
|
||||
|
||||
## 本章小结
|
||||
|
||||
| 操作 | 命令 | 说明 |
|
||||
|------|------|------|
|
||||
| 新建并运行 | `docker run` | 最常用的启动方式 |
|
||||
| 交互式启动 | `docker run -it` | 用于调试或临时操作 |
|
||||
| 后台运行 | `docker run -d` | 用于服务类应用 |
|
||||
| 启动已停止的容器 | `docker start` | 重用已有容器 |
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [后台运行](daemon.md):理解 `-d` 参数和容器生命周期
|
||||
- [进入容器](attach_exec.md):操作运行中的容器
|
||||
- [网络配置](../network/README.md):理解端口映射的原理
|
||||
- [数据管理](../07_data_network/README.md):数据持久化方案
|
||||
@@ -1,256 +0,0 @@
|
||||
# 终止容器
|
||||
|
||||
## 终止方式概述
|
||||
|
||||
终止容器有三种方式:
|
||||
|
||||
| 方式 | 命令 | 说明 |
|
||||
|------|------|------|
|
||||
| **优雅停止** | `docker stop` | 先发 SIGTERM,超时后发 SIGKILL |
|
||||
| **强制停止** | `docker kill` | 直接发 SIGKILL |
|
||||
| **自动终止** | - | 容器主进程退出时自动停止 |
|
||||
|
||||
---
|
||||
|
||||
## docker stop(推荐)
|
||||
|
||||
### 基本用法
|
||||
|
||||
```bash
|
||||
$ docker stop 容器名或ID
|
||||
```
|
||||
|
||||
### 工作原理
|
||||
|
||||
```
|
||||
docker stop mycontainer
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ 1. 发送 SIGTERM 信号给容器主进程(PID 1) │
|
||||
│ ↓ │
|
||||
│ 2. 等待容器优雅退出(默认 10 秒) │
|
||||
│ ↓ │
|
||||
│ 3. 如果超时仍未退出,发送 SIGKILL 强制终止 │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 自定义超时时间
|
||||
|
||||
```bash
|
||||
# 等待 30 秒后强制终止
|
||||
$ docker stop -t 30 mycontainer
|
||||
|
||||
# 立即发送 SIGKILL(相当于 docker kill)
|
||||
$ docker stop -t 0 mycontainer
|
||||
```
|
||||
|
||||
### 停止多个容器
|
||||
|
||||
```bash
|
||||
# 停止多个指定容器
|
||||
$ docker stop container1 container2 container3
|
||||
|
||||
# 停止所有运行中的容器
|
||||
$ docker stop $(docker ps -q)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## docker kill
|
||||
|
||||
### 基本用法
|
||||
|
||||
```bash
|
||||
$ docker kill 容器名或ID
|
||||
```
|
||||
|
||||
### 与 stop 的区别
|
||||
|
||||
| 命令 | 信号 | 使用场景 |
|
||||
|------|------|---------|
|
||||
| `docker stop` | SIGTERM → SIGKILL | 正常停止,让应用优雅退出 |
|
||||
| `docker kill` | SIGKILL | 应用无响应,强制终止 |
|
||||
|
||||
### 发送自定义信号
|
||||
|
||||
```bash
|
||||
# 发送 SIGHUP(让进程重新加载配置)
|
||||
$ docker kill -s HUP mycontainer
|
||||
|
||||
# 发送 SIGTERM
|
||||
$ docker kill -s TERM mycontainer
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 容器自动终止
|
||||
|
||||
容器的生命周期与主进程绑定。主进程退出时,容器自动停止:
|
||||
|
||||
```bash
|
||||
# 主进程是交互式 bash
|
||||
$ docker run -it ubuntu bash
|
||||
root@abc123:/# exit # 退出 bash → 容器停止
|
||||
|
||||
# 主进程执行完毕
|
||||
$ docker run ubuntu echo "Hello" # echo 执行完 → 容器停止
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 查看已停止的容器
|
||||
|
||||
```bash
|
||||
$ docker ps -a
|
||||
CONTAINER ID IMAGE COMMAND STATUS NAMES
|
||||
ba267838cc1b ubuntu "/bin/bash" Exited (0) 2 minutes ago myubuntu
|
||||
c5d3a5e8f7b2 nginx "nginx" Up 5 minutes mynginx
|
||||
```
|
||||
|
||||
**STATUS 字段说明**:
|
||||
|
||||
| 状态 | 说明 |
|
||||
|------|------|
|
||||
| `Up X minutes` | 运行中 |
|
||||
| `Exited (0)` | 正常退出(退出码 0) |
|
||||
| `Exited (1)` | 异常退出(非零退出码) |
|
||||
| `Exited (137)` | 被 SIGKILL 终止(128 + 9) |
|
||||
| `Exited (143)` | 被 SIGTERM 终止(128 + 15) |
|
||||
|
||||
---
|
||||
|
||||
## 重新启动容器
|
||||
|
||||
### 启动已停止的容器
|
||||
|
||||
```bash
|
||||
$ docker start 容器名或ID
|
||||
|
||||
# 启动并附加终端
|
||||
$ docker start -ai 容器名
|
||||
```
|
||||
|
||||
### 重启运行中的容器
|
||||
|
||||
```bash
|
||||
# 先停止再启动
|
||||
$ docker restart 容器名
|
||||
|
||||
# 自定义停止超时
|
||||
$ docker restart -t 30 容器名
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 生命周期状态图
|
||||
|
||||
```
|
||||
docker create
|
||||
│
|
||||
▼
|
||||
┌──────────┐
|
||||
┌────────│ Created │────────┐
|
||||
│ └──────────┘ │
|
||||
│ │ │
|
||||
│ │ docker start│
|
||||
│ ▼ │
|
||||
│ ┌──────────┐ │
|
||||
│ ┌────│ Running │────┐ │
|
||||
│ │ └──────────┘ │ │
|
||||
│ │ │ │ │
|
||||
│ │ docker │ docker │ │
|
||||
│ │ pause │ stop │ │
|
||||
│ ▼ │ │ │
|
||||
│ ┌──────┐ │ │ │
|
||||
│ │Paused│ │ │ │
|
||||
│ └──────┘ │ │ │
|
||||
│ │ │ │ │
|
||||
│ │ docker │ │ │
|
||||
│ │ unpause │ │ │
|
||||
│ ▼ ▼ │ │
|
||||
│ └──────►┌──────────┐◄┘ │
|
||||
│ │ Stopped │ │
|
||||
│ └──────────┘ │
|
||||
│ │ │
|
||||
│ │ docker rm │
|
||||
│ ▼ │
|
||||
│ ┌──────────┐ │
|
||||
└──────────►│ Deleted │◄────┘
|
||||
└──────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 批量操作
|
||||
|
||||
### 停止所有容器
|
||||
|
||||
```bash
|
||||
$ docker stop $(docker ps -q)
|
||||
```
|
||||
|
||||
### 删除所有已停止的容器
|
||||
|
||||
```bash
|
||||
$ docker container prune
|
||||
```
|
||||
|
||||
### 停止并删除所有容器
|
||||
|
||||
```bash
|
||||
$ docker stop $(docker ps -q) && docker container prune -f
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q: 容器停止很慢
|
||||
|
||||
原因:应用没有正确处理 SIGTERM 信号,需要等待超时后强制终止。
|
||||
|
||||
解决方案:
|
||||
1. 在应用中正确处理 SIGTERM
|
||||
2. 使用 `docker stop -t 0` 立即终止
|
||||
3. 检查 Dockerfile 中的 `STOPSIGNAL` 配置
|
||||
|
||||
### Q: 如何让容器优雅退出
|
||||
|
||||
确保容器主进程正确处理信号:
|
||||
|
||||
```dockerfile
|
||||
# Dockerfile 示例
|
||||
FROM node:22
|
||||
...
|
||||
# 使用 exec 形式确保信号能传递给 node 进程
|
||||
CMD ["node", "server.js"]
|
||||
```
|
||||
|
||||
### Q: 容器无法停止
|
||||
|
||||
```bash
|
||||
# 强制终止
|
||||
$ docker kill 容器名
|
||||
|
||||
# 如果仍无法停止,检查系统资源
|
||||
$ docker inspect 容器名
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 本章小结
|
||||
|
||||
| 操作 | 命令 | 说明 |
|
||||
|------|------|------|
|
||||
| 优雅停止 | `docker stop` | 先 SIGTERM,超时后 SIGKILL |
|
||||
| 强制停止 | `docker kill` | 直接 SIGKILL |
|
||||
| 重新启动 | `docker start` | 启动已停止的容器 |
|
||||
| 重启 | `docker restart` | 停止后立即启动 |
|
||||
| 停止全部 | `docker stop $(docker ps -q)` | 停止所有运行中容器 |
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [启动容器](run.md):容器启动详解
|
||||
- [删除容器](rm.md):清理容器
|
||||
- [容器日志](logs.md):排查停止原因
|
||||
@@ -1,126 +0,0 @@
|
||||
# Docker Hub
|
||||
|
||||
## 什么是 Docker Hub
|
||||
|
||||
[Docker Hub](https://hub.docker.com/) 是 Docker 官方维护的公共镜像仓库,也是全球最大的容器镜像库。
|
||||
|
||||
它提供了:
|
||||
- **官方镜像**:由 Docker 官方和软件厂商(如 Nginx, MySQL, Node.js)维护的高质量镜像。
|
||||
- **个人/组织仓库**:用户可以上传自己的镜像。
|
||||
- **自动构建**:与 GitHub/Bitbucket 集成(需付费)。
|
||||
- **Webhooks**:镜像更新时触发回调。
|
||||
|
||||
---
|
||||
|
||||
## 核心功能
|
||||
|
||||
### 1. 搜索镜像
|
||||
|
||||
除了网页搜索,也可以使用命令行:
|
||||
|
||||
```bash
|
||||
$ docker search centos
|
||||
NAME DESCRIPTION STARS OFFICIAL
|
||||
centos The official build of CentOS. 7000+ [OK]
|
||||
```
|
||||
|
||||
> **技巧**:始终优先使用 `OFFICIAL` 标记为 `[OK]` 的镜像,安全性更有保障。
|
||||
|
||||
### 2. 拉取镜像
|
||||
|
||||
```bash
|
||||
$ docker pull nginx:alpine
|
||||
```
|
||||
|
||||
### 3. 推送镜像
|
||||
|
||||
需要先登录:
|
||||
|
||||
```bash
|
||||
$ docker login
|
||||
# 输入用户名和密码
|
||||
```
|
||||
|
||||
打标签并推送:
|
||||
|
||||
```bash
|
||||
# 1. 标记镜像
|
||||
$ docker tag myapp:v1 username/myapp:v1
|
||||
|
||||
# 2. 推送
|
||||
$ docker push username/myapp:v1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 限制与配额(重要)
|
||||
|
||||
### 镜像拉取限制 (Rate Limiting)
|
||||
|
||||
自 2020 年 11 月起,Docker Hub 对匿名和免费用户实施了拉取速率限制:
|
||||
|
||||
| 用户类型 | 限制 |
|
||||
|---------|------|
|
||||
| **匿名用户** (未登录) | 每 6 小时 100 次请求 |
|
||||
| **免费账户** (已登录) | 每 6 小时 200 次请求 |
|
||||
| **Pro/Team 账户** | 无限制 |
|
||||
|
||||
> **提示**:如果在 CI/CD 环境中遇到 `toomanyrequests` 错误,建议:
|
||||
> 1. 在 CI 中配置 `docker login`
|
||||
> 2. 使用国内镜像加速器
|
||||
> 3. 搭建私有仓库代理
|
||||
|
||||
---
|
||||
|
||||
## 安全最佳实践
|
||||
|
||||
### 1. 启用 2FA (双因素认证)
|
||||
|
||||
在 Account Settings -> Security 中启用 2FA,保护账号安全。启用后,CLI 登录需要使用 **Access Token** 而非密码。
|
||||
|
||||
### 2. 使用 Access Token
|
||||
|
||||
不要在脚本或 CI/CD 中直接使用登录密码。
|
||||
1. 在 Docker Hub -> Account Settings -> Security -> Access Tokens 创建 Token。
|
||||
2. 使用 Token 作为密码登录:
|
||||
|
||||
```bash
|
||||
$ docker login -u username -p dckr_pat_xxxxxxx
|
||||
```
|
||||
|
||||
### 3. 关注镜像漏洞
|
||||
|
||||
Docker Hub 会对官方镜像和付费用户的镜像进行安全扫描。在镜像标签页可以看到漏洞扫描结果。
|
||||
|
||||
---
|
||||
|
||||
## Webhooks
|
||||
|
||||
当镜像被推送时,可以自动触发 HTTP 回调(例如通知 CI 系统部署)。
|
||||
|
||||
**配置方法**:
|
||||
仓库页面 -> Webhooks -> Create Webhook。
|
||||
|
||||
---
|
||||
|
||||
## 自动构建 (Automated Builds)
|
||||
|
||||
> ⚠️ 目前仅限付费用户 (Pro/Team) 使用。
|
||||
|
||||
链接 GitHub/Bitbucket 仓库后,当代码有提交或打标签时,Docker Hub 会自动运行构建。这保证了镜像总是与代码同步,且由可信的官方环境构建。
|
||||
|
||||
---
|
||||
|
||||
## 本章小结
|
||||
|
||||
| 功能 | 说明 |
|
||||
|------|------|
|
||||
| **官方镜像** | 优先使用的基础镜像 |
|
||||
| **拉取限制** | 匿名 100次/6h,登录 200次/6h |
|
||||
| **安全** | 推荐开启 2FA 并使用 Access Token |
|
||||
| **自动化** | 支持 Webhooks 和自动构建 |
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [私有仓库](registry.md):搭建自己的 Registry
|
||||
- [镜像加速器](../install/mirror.md):加速下载
|
||||
@@ -1,6 +0,0 @@
|
||||
# 数据与网络管理
|
||||
|
||||
本章将介绍 Docker 中的数据管理与网络配置。
|
||||
|
||||
* [数据管理](data/README.md)
|
||||
* [网络配置](network/README.md)
|
||||
@@ -1,284 +0,0 @@
|
||||
# 挂载主机目录(Bind Mounts)
|
||||
|
||||
## 什么是 Bind Mount
|
||||
|
||||
Bind Mount(绑定挂载)将**宿主机的目录或文件**直接挂载到容器中。容器可以读写宿主机的文件系统。
|
||||
|
||||
```
|
||||
宿主机 容器
|
||||
┌─────────────────────┐ ┌─────────────────────┐
|
||||
│ /home/user/code/ │ │ │
|
||||
│ ├── index.html │◄───────►│ /usr/share/nginx/ │
|
||||
│ ├── style.css │ Bind │ html/ │
|
||||
│ └── app.js │ Mount │ (同一份文件) │
|
||||
└─────────────────────┘ └─────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Bind Mount vs Volume
|
||||
|
||||
| 特性 | Bind Mount | Volume |
|
||||
|------|------------|--------|
|
||||
| **数据位置** | 宿主机任意路径 | Docker 管理的目录 |
|
||||
| **路径指定** | 必须是绝对路径 | 卷名 |
|
||||
| **可移植性** | 依赖宿主机路径 | 更好(Docker 管理) |
|
||||
| **性能** | 依赖宿主机文件系统 | 优化的存储驱动 |
|
||||
| **适用场景** | 开发环境、配置文件 | 生产数据持久化 |
|
||||
| **备份** | 直接访问文件 | 需要通过 Docker |
|
||||
|
||||
### 选择建议
|
||||
|
||||
```
|
||||
需求 推荐方案
|
||||
─────────────────────────────────────────
|
||||
开发时同步代码 → Bind Mount
|
||||
持久化数据库数据 → Volume
|
||||
共享配置文件 → Bind Mount
|
||||
容器间共享数据 → Volume
|
||||
备份方便 → Bind Mount(直接访问)
|
||||
生产环境 → Volume
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 基本语法
|
||||
|
||||
### 使用 --mount(推荐)
|
||||
|
||||
```bash
|
||||
$ docker run -d \
|
||||
--mount type=bind,source=/宿主机路径,target=/容器路径 \
|
||||
nginx
|
||||
```
|
||||
|
||||
### 使用 -v(简写)
|
||||
|
||||
```bash
|
||||
$ docker run -d \
|
||||
-v /宿主机路径:/容器路径 \
|
||||
nginx
|
||||
```
|
||||
|
||||
### 两种语法对比
|
||||
|
||||
| 特性 | --mount | -v |
|
||||
|------|---------|-----|
|
||||
| 语法 | 键值对,更清晰 | 冒号分隔,更简洁 |
|
||||
| 路径不存在时 | 报错 | 自动创建目录 |
|
||||
| 推荐程度 | ✅ 推荐 | 常用 |
|
||||
|
||||
---
|
||||
|
||||
## 使用场景
|
||||
|
||||
### 场景一:开发环境代码同步
|
||||
|
||||
```bash
|
||||
# 将本地代码目录挂载到容器
|
||||
$ docker run -d \
|
||||
-p 8080:80 \
|
||||
--mount type=bind,source=$(pwd)/src,target=/usr/share/nginx/html \
|
||||
nginx
|
||||
|
||||
# 修改本地文件,容器内立即生效(热更新)
|
||||
$ echo "Hello" > src/index.html
|
||||
# 浏览器刷新即可看到变化
|
||||
```
|
||||
|
||||
### 场景二:配置文件挂载
|
||||
|
||||
```bash
|
||||
# 挂载自定义 nginx 配置
|
||||
$ docker run -d \
|
||||
--mount type=bind,source=/path/to/nginx.conf,target=/etc/nginx/nginx.conf,readonly \
|
||||
nginx
|
||||
```
|
||||
|
||||
### 场景三:日志收集
|
||||
|
||||
```bash
|
||||
# 将容器日志输出到宿主机目录
|
||||
$ docker run -d \
|
||||
--mount type=bind,source=/var/log/myapp,target=/app/logs \
|
||||
myapp
|
||||
```
|
||||
|
||||
### 场景四:共享 SSH 密钥
|
||||
|
||||
```bash
|
||||
# 挂载 SSH 密钥(只读)
|
||||
$ docker run --rm -it \
|
||||
--mount type=bind,source=$HOME/.ssh,target=/root/.ssh,readonly \
|
||||
alpine ssh user@remote
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 只读挂载
|
||||
|
||||
防止容器修改宿主机文件:
|
||||
|
||||
```bash
|
||||
# --mount 语法
|
||||
$ docker run -d \
|
||||
--mount type=bind,source=/config,target=/app/config,readonly \
|
||||
myapp
|
||||
|
||||
# -v 语法
|
||||
$ docker run -d \
|
||||
-v /config:/app/config:ro \
|
||||
myapp
|
||||
```
|
||||
|
||||
容器内尝试写入会报错:
|
||||
|
||||
```bash
|
||||
$ touch /app/config/new.txt
|
||||
touch: /app/config/new.txt: Read-only file system
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 挂载单个文件
|
||||
|
||||
```bash
|
||||
# 挂载 bash 历史记录
|
||||
$ docker run --rm -it \
|
||||
--mount type=bind,source=$HOME/.bash_history,target=/root/.bash_history \
|
||||
ubuntu bash
|
||||
|
||||
# 挂载自定义配置文件
|
||||
$ docker run -d \
|
||||
--mount type=bind,source=/path/to/my.cnf,target=/etc/mysql/my.cnf \
|
||||
mysql
|
||||
```
|
||||
|
||||
> ⚠️ **注意**:挂载单个文件时,如果宿主机上的文件被编辑器替换(而非原地修改),容器内仍是旧文件的 inode。建议重启容器或挂载目录。
|
||||
|
||||
---
|
||||
|
||||
## 查看挂载信息
|
||||
|
||||
```bash
|
||||
$ docker inspect mycontainer --format '{{json .Mounts}}' | jq
|
||||
```
|
||||
|
||||
输出:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"Type": "bind",
|
||||
"Source": "/home/user/code",
|
||||
"Destination": "/app",
|
||||
"Mode": "",
|
||||
"RW": true,
|
||||
"Propagation": "rprivate"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| `Type` | 挂载类型(bind) |
|
||||
| `Source` | 宿主机路径 |
|
||||
| `Destination` | 容器内路径 |
|
||||
| `RW` | 是否可读写 |
|
||||
| `Propagation` | 挂载传播模式 |
|
||||
|
||||
---
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q: 路径不存在报错
|
||||
|
||||
```bash
|
||||
$ docker run --mount type=bind,source=/not/exist,target=/app nginx
|
||||
docker: Error response from daemon: invalid mount config for type "bind":
|
||||
bind source path does not exist: /not/exist
|
||||
```
|
||||
|
||||
**解决**:确保源路径存在,或改用 `-v`(会自动创建)
|
||||
|
||||
### Q: 权限问题
|
||||
|
||||
容器内用户可能无权访问挂载的文件:
|
||||
|
||||
```bash
|
||||
# 方法1:确保宿主机文件权限允许容器用户访问
|
||||
$ chmod -R 755 /path/to/data
|
||||
|
||||
# 方法2:以 root 运行容器
|
||||
$ docker run -u root ...
|
||||
|
||||
# 方法3:使用相同的 UID
|
||||
$ docker run -u $(id -u):$(id -g) ...
|
||||
```
|
||||
|
||||
### Q: macOS/Windows 性能问题
|
||||
|
||||
在 Docker Desktop 上,Bind Mount 性能较差(需要跨文件系统同步):
|
||||
|
||||
```bash
|
||||
# 使用 :cached 或 :delegated 提高性能(macOS)
|
||||
$ docker run -v /host/path:/container/path:cached myapp
|
||||
```
|
||||
|
||||
| 选项 | 说明 |
|
||||
|------|------|
|
||||
| `:cached` | 宿主机权威,容器读取可能延迟 |
|
||||
| `:delegated` | 容器权威,宿主机读取可能延迟 |
|
||||
| `:consistent` | 默认,完全一致(最慢) |
|
||||
|
||||
---
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 开发环境使用 Bind Mount
|
||||
|
||||
```bash
|
||||
# 代码热更新
|
||||
$ docker run -v $(pwd):/app -p 3000:3000 node npm run dev
|
||||
```
|
||||
|
||||
### 2. 生产环境使用 Volume
|
||||
|
||||
```bash
|
||||
# 数据持久化
|
||||
$ docker run -v mysql_data:/var/lib/mysql mysql
|
||||
```
|
||||
|
||||
### 3. 配置文件使用只读挂载
|
||||
|
||||
```bash
|
||||
$ docker run -v /config/nginx.conf:/etc/nginx/nginx.conf:ro nginx
|
||||
```
|
||||
|
||||
### 4. 注意路径安全
|
||||
|
||||
```bash
|
||||
# ❌ 危险:挂载根目录或敏感目录
|
||||
$ docker run -v /:/host ...
|
||||
|
||||
# ✅ 只挂载必要的目录
|
||||
$ docker run -v /app/data:/data ...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 本章小结
|
||||
|
||||
| 要点 | 说明 |
|
||||
|------|------|
|
||||
| **作用** | 将宿主机目录挂载到容器 |
|
||||
| **语法** | `-v /宿主机:/容器` 或 `--mount type=bind,...` |
|
||||
| **只读** | 添加 `readonly` 或 `:ro` |
|
||||
| **适用场景** | 开发环境、配置文件、日志 |
|
||||
| **vs Volume** | Bind 更灵活,Volume 更适合生产 |
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [数据卷](volume.md):Docker 管理的持久化存储
|
||||
- [tmpfs 挂载](tmpfs.md):内存临时存储
|
||||
- [Compose 数据管理](../compose/compose_file.md):Compose 中的挂载配置
|
||||
@@ -1,362 +0,0 @@
|
||||
# 数据卷
|
||||
|
||||
## 为什么需要数据卷
|
||||
|
||||
容器的存储层有一个关键问题:**容器删除后,数据就没了**。
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Run[容器运行] --> Write[写入数据]
|
||||
Write --> Delete[容器删除]
|
||||
Delete -->|数据都在容器 writable 层| Lost[DATA LOST! ❌]
|
||||
```
|
||||
|
||||
数据卷(Volume)解决了这个问题,它的生命周期独立于容器。
|
||||
|
||||
---
|
||||
|
||||
## 数据卷的特性
|
||||
|
||||
| 特性 | 说明 |
|
||||
|------|------|
|
||||
| **持久化** | 容器删除后数据仍然保留 |
|
||||
| **共享** | 多个容器可以挂载同一个数据卷 |
|
||||
| **即时生效** | 对数据卷的修改立即可见 |
|
||||
| **不影响镜像** | 数据卷中的数据不会打包进镜像 |
|
||||
| **性能更好** | 绕过 UnionFS,直接读写 |
|
||||
|
||||
---
|
||||
|
||||
## 数据卷 vs 容器存储层
|
||||
|
||||
#### 容器存储层(不推荐存储重要数据)
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
subgraph Container [容器]
|
||||
Writable[容器存储层<br>Writable]
|
||||
Image[镜像层<br>ReadOnly]
|
||||
Writable --- Image
|
||||
end
|
||||
|
||||
Lifecycle[生命周期 = 容器生命周期] -.-> Container
|
||||
Delete[容器删除] -->|导致| DataLost[数据丢失 ❌]
|
||||
```
|
||||
|
||||
#### 数据卷(推荐)
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
subgraph Container [容器]
|
||||
AppDir["/app/data"]
|
||||
end
|
||||
|
||||
subgraph Volume [数据卷 my-data]
|
||||
Data[持久化数据]
|
||||
end
|
||||
|
||||
AppDir == 挂载 ==> Volume
|
||||
Delete[容器删除] -.->|不会影响| Volume
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 数据卷基本操作
|
||||
|
||||
### 创建数据卷
|
||||
|
||||
```bash
|
||||
$ docker volume create my-vol
|
||||
```
|
||||
|
||||
### 列出所有数据卷
|
||||
|
||||
```bash
|
||||
$ docker volume ls
|
||||
DRIVER VOLUME NAME
|
||||
local my-vol
|
||||
local postgres_data
|
||||
local redis_data
|
||||
```
|
||||
|
||||
### 查看数据卷详情
|
||||
|
||||
```bash
|
||||
$ docker volume inspect my-vol
|
||||
[
|
||||
{
|
||||
"CreatedAt": "2026-01-15T10:00:00Z",
|
||||
"Driver": "local",
|
||||
"Labels": {},
|
||||
"Mountpoint": "/var/lib/docker/volumes/my-vol/_data",
|
||||
"Name": "my-vol",
|
||||
"Options": {},
|
||||
"Scope": "local"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
**关键字段**:
|
||||
- `Mountpoint`:数据卷在宿主机上的实际存储位置
|
||||
- `Driver`:存储驱动(默认 local,也可以用第三方驱动)
|
||||
|
||||
---
|
||||
|
||||
## 挂载数据卷
|
||||
|
||||
### 方式一:--mount(推荐)
|
||||
|
||||
```bash
|
||||
$ docker run -d \
|
||||
--name web \
|
||||
--mount source=my-vol,target=/usr/share/nginx/html \
|
||||
nginx
|
||||
```
|
||||
|
||||
**参数说明**:
|
||||
|
||||
| 参数 | 说明 |
|
||||
|------|------|
|
||||
| `source` | 数据卷名称(不存在会自动创建) |
|
||||
| `target` | 容器内挂载路径 |
|
||||
| `readonly` | 可选,只读挂载 |
|
||||
|
||||
### 方式二:-v(简写)
|
||||
|
||||
```bash
|
||||
$ docker run -d \
|
||||
--name web \
|
||||
-v my-vol:/usr/share/nginx/html \
|
||||
nginx
|
||||
```
|
||||
|
||||
**格式**:`-v 数据卷名:容器路径[:选项]`
|
||||
|
||||
### 两种方式对比
|
||||
|
||||
| 特性 | --mount | -v |
|
||||
|------|---------|-----|
|
||||
| 语法 | 键值对,更清晰 | 冒号分隔,更简洁 |
|
||||
| 自动创建卷 | source 不存在会报错 | 自动创建 |
|
||||
| 推荐程度 | ✅ 推荐(更明确) | 常用(更简洁) |
|
||||
|
||||
### 只读挂载
|
||||
|
||||
```bash
|
||||
# --mount 方式
|
||||
$ docker run -d \
|
||||
--mount source=my-vol,target=/data,readonly \
|
||||
nginx
|
||||
|
||||
# -v 方式
|
||||
$ docker run -d \
|
||||
-v my-vol:/data:ro \
|
||||
nginx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 使用场景示例
|
||||
|
||||
### 场景一:数据库持久化
|
||||
|
||||
```bash
|
||||
# 创建数据卷
|
||||
$ docker volume create postgres_data
|
||||
|
||||
# 启动 PostgreSQL,数据存储在数据卷中
|
||||
$ docker run -d \
|
||||
--name postgres \
|
||||
-e POSTGRES_PASSWORD=secret \
|
||||
-v postgres_data:/var/lib/postgresql/data \
|
||||
postgres:16
|
||||
|
||||
# 即使删除容器,数据仍然保留
|
||||
$ docker rm -f postgres
|
||||
|
||||
# 重新启动,数据还在
|
||||
$ docker run -d \
|
||||
--name postgres \
|
||||
-e POSTGRES_PASSWORD=secret \
|
||||
-v postgres_data:/var/lib/postgresql/data \
|
||||
postgres:16
|
||||
```
|
||||
|
||||
### 场景二:多容器共享数据
|
||||
|
||||
```bash
|
||||
# 创建共享数据卷
|
||||
$ docker volume create shared-data
|
||||
|
||||
# 容器 A 写入数据
|
||||
$ docker run -d --name writer \
|
||||
-v shared-data:/data \
|
||||
alpine sh -c "while true; do date >> /data/log.txt; sleep 5; done"
|
||||
|
||||
# 容器 B 读取数据
|
||||
$ docker run --rm \
|
||||
-v shared-data:/data \
|
||||
alpine cat /data/log.txt
|
||||
```
|
||||
|
||||
### 场景三:配置文件持久化
|
||||
|
||||
```bash
|
||||
# 将 nginx 配置存储在数据卷中
|
||||
$ docker run -d \
|
||||
-v nginx-config:/etc/nginx/conf.d \
|
||||
-v nginx-logs:/var/log/nginx \
|
||||
-p 80:80 \
|
||||
nginx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 数据卷管理
|
||||
|
||||
### 删除数据卷
|
||||
|
||||
```bash
|
||||
# 删除指定数据卷
|
||||
$ docker volume rm my-vol
|
||||
|
||||
# 删除容器时同时删除数据卷
|
||||
$ docker rm -v container_name
|
||||
```
|
||||
|
||||
### 清理未使用的数据卷
|
||||
|
||||
```bash
|
||||
# 查看未被任何容器使用的数据卷
|
||||
$ docker volume ls -f dangling=true
|
||||
|
||||
# 删除所有未使用的数据卷
|
||||
$ docker volume prune
|
||||
|
||||
# 强制删除(不提示确认)
|
||||
$ docker volume prune -f
|
||||
```
|
||||
|
||||
> ⚠️ **注意**:数据卷不会自动垃圾回收。长期运行的系统应定期清理无用数据卷。
|
||||
|
||||
---
|
||||
|
||||
## 数据卷备份与恢复
|
||||
|
||||
### 备份数据卷
|
||||
|
||||
```bash
|
||||
# 使用临时容器挂载数据卷,打包备份
|
||||
$ docker run --rm \
|
||||
-v my-vol:/source:ro \
|
||||
-v $(pwd):/backup \
|
||||
alpine tar czf /backup/my-vol-backup.tar.gz -C /source .
|
||||
```
|
||||
|
||||
**原理**:
|
||||
1. 创建临时容器
|
||||
2. 挂载要备份的数据卷到 `/source`
|
||||
3. 挂载当前目录到 `/backup`
|
||||
4. 使用 tar 打包
|
||||
|
||||
### 恢复数据卷
|
||||
|
||||
```bash
|
||||
# 创建新数据卷
|
||||
$ docker volume create my-vol-restored
|
||||
|
||||
# 解压备份到新数据卷
|
||||
$ docker run --rm \
|
||||
-v my-vol-restored:/target \
|
||||
-v $(pwd):/backup:ro \
|
||||
alpine tar xzf /backup/my-vol-backup.tar.gz -C /target
|
||||
```
|
||||
|
||||
### 备份脚本示例
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# backup-volume.sh
|
||||
|
||||
VOLUME_NAME=$1
|
||||
BACKUP_DIR=${2:-/backups}
|
||||
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||||
|
||||
docker run --rm \
|
||||
-v ${VOLUME_NAME}:/source:ro \
|
||||
-v ${BACKUP_DIR}:/backup \
|
||||
alpine tar czf /backup/${VOLUME_NAME}_${TIMESTAMP}.tar.gz -C /source .
|
||||
|
||||
echo "Backed up ${VOLUME_NAME} to ${BACKUP_DIR}/${VOLUME_NAME}_${TIMESTAMP}.tar.gz"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 数据卷 vs 绑定挂载
|
||||
|
||||
Docker 有两种主要的数据持久化方式:
|
||||
|
||||
| 特性 | 数据卷 (Volume) | 绑定挂载 (Bind Mount) |
|
||||
|------|----------------|---------------------|
|
||||
| **管理方式** | Docker 管理 | 用户管理 |
|
||||
| **存储位置** | `/var/lib/docker/volumes/` | 任意宿主机路径 |
|
||||
| **可移植性** | 更好 | 依赖宿主机路径 |
|
||||
| **适用场景** | 生产数据持久化 | 开发时同步代码 |
|
||||
| **备份** | 需要工具 | 直接访问文件 |
|
||||
|
||||
```bash
|
||||
# 数据卷
|
||||
$ docker run -v mydata:/app/data nginx
|
||||
|
||||
# 绑定挂载
|
||||
$ docker run -v /host/path:/app/data nginx
|
||||
```
|
||||
|
||||
详见 [绑定挂载](bind-mounts.md) 章节。
|
||||
|
||||
---
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q: 如何知道容器使用了哪些数据卷?
|
||||
|
||||
```bash
|
||||
$ docker inspect container_name --format '{{json .Mounts}}' | jq
|
||||
```
|
||||
|
||||
### Q: 数据卷的数据在哪里?
|
||||
|
||||
```bash
|
||||
# 查看数据卷详情
|
||||
$ docker volume inspect my-vol
|
||||
|
||||
# Mountpoint 字段显示实际路径
|
||||
"Mountpoint": "/var/lib/docker/volumes/my-vol/_data"
|
||||
```
|
||||
|
||||
> ⚠️ **注意**:不建议直接修改 Mountpoint 中的文件,应通过容器操作。
|
||||
|
||||
### Q: 如何在不同机器间迁移数据卷?
|
||||
|
||||
1. 在源机器备份:`docker run --rm -v mydata:/data -v $(pwd):/backup alpine tar czf /backup/data.tar.gz -C /data .`
|
||||
2. 传输 tar.gz 文件
|
||||
3. 在目标机器恢复
|
||||
|
||||
---
|
||||
|
||||
## 本章小结
|
||||
|
||||
| 操作 | 命令 |
|
||||
|------|------|
|
||||
| 创建数据卷 | `docker volume create name` |
|
||||
| 列出数据卷 | `docker volume ls` |
|
||||
| 查看详情 | `docker volume inspect name` |
|
||||
| 删除数据卷 | `docker volume rm name` |
|
||||
| 清理未用 | `docker volume prune` |
|
||||
| 挂载数据卷 | `-v name:/path` 或 `--mount source=name,target=/path` |
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [绑定挂载](bind-mounts.md):挂载宿主机目录
|
||||
- [tmpfs 挂载](tmpfs.md):内存中的临时存储
|
||||
- [存储驱动](../13_implementation/ufs.md):Docker 存储的底层原理
|
||||
@@ -1,291 +0,0 @@
|
||||
# 网络配置
|
||||
|
||||
## Docker 网络概述
|
||||
|
||||
Docker 容器需要网络来:
|
||||
- 与外部世界通信(访问互联网、被外部访问)
|
||||
- 容器之间相互通信
|
||||
- 与宿主机通信
|
||||
|
||||
Docker 在安装时会自动配置网络基础设施,大多数情况下开箱即用。
|
||||
|
||||
## 默认网络架构
|
||||
|
||||
Docker 启动时自动创建以下网络组件:
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
subgraph Host [宿主机]
|
||||
eth0[物理网卡 eth0<br>192.168.1.100]
|
||||
docker0[docker0 网桥<br>172.17.0.1]
|
||||
|
||||
subgraph Containers
|
||||
subgraph ContainerA [容器 A]
|
||||
eth0_A[eth0<br>172.17.0.2]
|
||||
end
|
||||
subgraph ContainerB [容器 B]
|
||||
eth0_B[eth0<br>172.17.0.3]
|
||||
end
|
||||
end
|
||||
|
||||
eth0 <--> docker0
|
||||
docker0 <--> eth0_A
|
||||
docker0 <--> eth0_B
|
||||
end
|
||||
|
||||
Internet((互联网)) <--> eth0
|
||||
```
|
||||
|
||||
### 核心组件
|
||||
|
||||
| 组件 | 说明 |
|
||||
|------|------|
|
||||
| **docker0** | 虚拟网桥,充当交换机角色 |
|
||||
| **veth pair** | 虚拟网卡对,一端在容器内,一端连接网桥 |
|
||||
| **容器 eth0** | 容器内的网卡 |
|
||||
| **IP 地址** | 自动从 172.17.0.0/16 网段分配 |
|
||||
|
||||
### 数据流向
|
||||
|
||||
```
|
||||
容器 A (172.17.0.2) → docker0 → 容器 B (172.17.0.3) (容器间通信)
|
||||
容器 A (172.17.0.2) → docker0 → eth0 → 互联网 (访问外网)
|
||||
外部请求 → eth0 → docker0 → 容器 A (被外部访问,需端口映射)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Docker 网络类型
|
||||
|
||||
查看默认网络:
|
||||
|
||||
```bash
|
||||
$ docker network ls
|
||||
NETWORK ID NAME DRIVER SCOPE
|
||||
abc123... bridge bridge local
|
||||
def456... host host local
|
||||
ghi789... none null local
|
||||
```
|
||||
|
||||
| 网络类型 | 说明 | 适用场景 |
|
||||
|---------|------|---------|
|
||||
| **bridge** | 默认类型,容器连接到虚拟网桥 | 大多数单机场景 |
|
||||
| **host** | 容器直接使用宿主机网络栈 | 需要最高网络性能时 |
|
||||
| **none** | 禁用网络 | 完全隔离的容器 |
|
||||
| **overlay** | 跨主机网络 | Docker Swarm 集群 |
|
||||
| **macvlan** | 容器拥有独立 MAC 地址 | 需要直接接入物理网络 |
|
||||
|
||||
---
|
||||
|
||||
## 用户自定义网络(推荐)
|
||||
|
||||
### 为什么要用自定义网络
|
||||
|
||||
默认 bridge 网络的局限:
|
||||
|
||||
| 问题 | 自定义网络的优势 |
|
||||
|------|-----------------|
|
||||
| 只能用 IP 通信 | 支持容器名 DNS 解析 |
|
||||
| 所有容器在同一网络 | 更好的隔离性 |
|
||||
| 需要 --link(已废弃) | 原生支持服务发现 |
|
||||
|
||||
### 创建自定义网络
|
||||
|
||||
```bash
|
||||
# 创建网络
|
||||
$ docker network create mynet
|
||||
|
||||
# 查看网络详情
|
||||
$ docker network inspect mynet
|
||||
```
|
||||
|
||||
### 使用自定义网络
|
||||
|
||||
```bash
|
||||
# 启动容器并连接到自定义网络
|
||||
$ docker run -d --name web --network mynet nginx
|
||||
$ docker run -d --name db --network mynet postgres
|
||||
|
||||
# 在 web 容器中可以直接用容器名访问 db
|
||||
$ docker exec web ping db
|
||||
PING db (172.18.0.3): 56 data bytes
|
||||
64 bytes from 172.18.0.3: seq=0 ttl=64 time=0.083 ms
|
||||
```
|
||||
|
||||
### 容器名 DNS 解析
|
||||
|
||||
自定义网络自动提供 DNS 服务:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ mynet 网络 │
|
||||
│ │
|
||||
│ ┌─────────┐ DNS ┌─────────┐ │
|
||||
│ │ web │ ──── "db" → 172.18.0.3 ───► │ db │ │
|
||||
│ │172.18.0.2│ │172.18.0.3│ │
|
||||
│ └─────────┘ └─────────┘ │
|
||||
│ │
|
||||
│ web 容器可以用 "db" 作为主机名访问 db 容器 │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 容器互联
|
||||
|
||||
### 同一网络内的容器
|
||||
|
||||
同一自定义网络内的容器可以直接通信:
|
||||
|
||||
```bash
|
||||
# 创建网络
|
||||
$ docker network create app-net
|
||||
|
||||
# 启动应用和数据库
|
||||
$ docker run -d --name redis --network app-net redis
|
||||
$ docker run -d --name app --network app-net myapp
|
||||
|
||||
# app 容器中可以用 redis:6379 连接 Redis
|
||||
```
|
||||
|
||||
### 连接到多个网络
|
||||
|
||||
一个容器可以连接到多个网络:
|
||||
|
||||
```bash
|
||||
# 启动容器
|
||||
$ docker run -d --name multi-net-container --network frontend nginx
|
||||
|
||||
# 再连接到另一个网络
|
||||
$ docker network connect backend multi-net-container
|
||||
|
||||
# 查看容器的网络
|
||||
$ docker inspect multi-net-container --format '{{json .NetworkSettings.Networks}}'
|
||||
```
|
||||
|
||||
### ⚠️ --link 已废弃
|
||||
|
||||
```bash
|
||||
# 旧方式(不推荐)
|
||||
$ docker run --link db:database myapp
|
||||
|
||||
# 新方式(推荐)
|
||||
$ docker network create mynet
|
||||
$ docker run --network mynet --name db postgres
|
||||
$ docker run --network mynet --name app myapp
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 端口映射
|
||||
|
||||
容器默认只能在 Docker 网络内访问。要从外部访问容器,需要端口映射:
|
||||
|
||||
### 基本语法
|
||||
|
||||
```bash
|
||||
# -p 宿主机端口:容器端口
|
||||
$ docker run -d -p 8080:80 nginx
|
||||
```
|
||||
|
||||
### 映射方式
|
||||
|
||||
| 参数 | 说明 | 示例 |
|
||||
|------|------|------|
|
||||
| `-p 8080:80` | 指定端口映射 | 宿主机 8080 → 容器 80 |
|
||||
| `-p 80` | 随机宿主机端口 | 随机端口 → 容器 80 |
|
||||
| `-P` | 自动映射所有暴露端口 | 随机端口 → 所有 EXPOSE 端口 |
|
||||
| `-p 127.0.0.1:8080:80` | 只绑定本地 | 仅本机可访问 |
|
||||
| `-p 8080:80/udp` | UDP 端口 | UDP 协议 |
|
||||
|
||||
### 查看端口映射
|
||||
|
||||
```bash
|
||||
$ docker port mycontainer
|
||||
80/tcp -> 0.0.0.0:8080
|
||||
```
|
||||
|
||||
### 端口映射示意图
|
||||
|
||||
```
|
||||
外部请求 http://宿主机IP:8080
|
||||
│
|
||||
▼
|
||||
┌─────────────┐
|
||||
│ 宿主机:8080 │ ─── iptables NAT ───┐
|
||||
└─────────────┘ │
|
||||
▼
|
||||
┌───────────────┐
|
||||
│ 容器 nginx:80 │
|
||||
└───────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 网络隔离
|
||||
|
||||
不同网络之间默认隔离:
|
||||
|
||||
```bash
|
||||
# 创建两个网络
|
||||
$ docker network create frontend
|
||||
$ docker network create backend
|
||||
|
||||
# 容器 A 在 frontend
|
||||
$ docker run -d --name web --network frontend nginx
|
||||
|
||||
# 容器 B 在 backend
|
||||
$ docker run -d --name db --network backend postgres
|
||||
|
||||
# web 无法直接访问 db(不同网络)
|
||||
$ docker exec web ping db
|
||||
ping: db: Name or service not known
|
||||
```
|
||||
|
||||
这种隔离有助于安全:前端容器无法直接访问数据库网络。
|
||||
|
||||
---
|
||||
|
||||
## 常用命令
|
||||
|
||||
```bash
|
||||
# 列出网络
|
||||
$ docker network ls
|
||||
|
||||
# 创建网络
|
||||
$ docker network create mynet
|
||||
|
||||
# 查看网络详情
|
||||
$ docker network inspect mynet
|
||||
|
||||
# 连接容器到网络
|
||||
$ docker network connect mynet mycontainer
|
||||
|
||||
# 断开网络连接
|
||||
$ docker network disconnect mynet mycontainer
|
||||
|
||||
# 删除网络
|
||||
$ docker network rm mynet
|
||||
|
||||
# 清理未使用的网络
|
||||
$ docker network prune
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 本章小结
|
||||
|
||||
| 概念 | 要点 |
|
||||
|------|------|
|
||||
| **默认网络** | docker0 网桥,172.17.0.0/16 网段 |
|
||||
| **自定义网络** | 推荐使用,支持容器名 DNS 解析 |
|
||||
| **端口映射** | `-p 宿主机端口:容器端口` 暴露服务 |
|
||||
| **网络隔离** | 不同网络默认隔离,增强安全性 |
|
||||
| **--link** | 已废弃,使用自定义网络替代 |
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [高级网络配置](linking.md):容器互联详解
|
||||
- [配置 DNS](dns.md):自定义 DNS 设置
|
||||
- [端口映射](port_bindbindbindport.md):高级端口配置
|
||||
- [Compose 网络](../compose/compose_file.md):Compose 中的网络配置
|
||||
@@ -1,114 +0,0 @@
|
||||
# 配置 DNS
|
||||
|
||||
## 容器的 DNS 机制
|
||||
|
||||
Docker 容器的 DNS 配置有两种情况:
|
||||
|
||||
1. **默认 Bridge 网络**:继承宿主机的 DNS 配置(`/etc/resolv.conf`)。
|
||||
2. **自定义网络**(推荐):使用 Docker 嵌入式 DNS 服务器 (Embedded DNS),支持通过**容器名**进行服务发现。
|
||||
|
||||
---
|
||||
|
||||
## 嵌入式 DNS (Embedded DNS)
|
||||
|
||||
这是 Docker 网络最强大的功能之一。在自定义网络中,容器可以通过"名字"找到彼此,而不需要知道对方的 IP(因为 IP 可能会变)。
|
||||
|
||||
```bash
|
||||
# 1. 创建自定义网络
|
||||
$ docker network create mynet
|
||||
|
||||
# 2. 启动容器 web 并加入网络
|
||||
$ docker run -d --name web --network mynet nginx
|
||||
|
||||
# 3. 启动容器 client 并尝试 ping web
|
||||
$ docker run -it --rm --network mynet alpine ping web
|
||||
PING web (172.18.0.2): 56 data bytes
|
||||
64 bytes from 172.18.0.2: seq=0 ttl=64 time=0.074 ms
|
||||
```
|
||||
|
||||
**原理**:
|
||||
Docker 守护进程在 `127.0.0.11` 运行了一个 DNS 服务器。容器内的 DNS 请求会被转发到这里。如果是容器名,解析为容器 IP;如果是外部域名(如 google.com),转发给上游 DNS。
|
||||
|
||||
---
|
||||
|
||||
## 配置 DNS 参数
|
||||
|
||||
如果你需要手动配置容器的 DNS(例如使用内网 DNS 服务器),可以在 `docker run` 中使用以下参数:
|
||||
|
||||
### 1. --dns
|
||||
|
||||
指定 DNS 服务器 IP。
|
||||
|
||||
```bash
|
||||
$ docker run -it --dns=114.114.114.114 ubuntu cat /etc/resolv.conf
|
||||
nameserver 114.114.114.114
|
||||
```
|
||||
|
||||
### 2. --dns-search
|
||||
|
||||
指定 DNS 搜索域。例如设置为 `example.com`,则 `ping host` 会尝试解析 `host.example.com`。
|
||||
|
||||
```bash
|
||||
$ docker run --dns-search=example.com myapp
|
||||
```
|
||||
|
||||
### 3. --hostname (-h)
|
||||
|
||||
设置容器的主机名。
|
||||
|
||||
```bash
|
||||
$ docker run -h myweb nginx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 全局 DNS 配置
|
||||
|
||||
如果希望所有容器都使用特定的 DNS 服务器(而不是继承宿主机),可以修改 `/etc/docker/daemon.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": [
|
||||
"114.114.114.114",
|
||||
"8.8.8.8"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
修改后需要重启 Docker 服务:`systemctl restart docker`。
|
||||
|
||||
---
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q: 容器无法解析域名
|
||||
|
||||
**现象**:`ping www.baidu.com` 失败,但 `ping 8.8.8.8` 成功。
|
||||
|
||||
**解决**:
|
||||
1. 宿主机的 `/etc/resolv.conf` 可能有问题(例如使用了本地回环地址 127.0.0.53,特别是 Ubuntu 系统)。Docker 可能会尝试修复,但有时会失败。
|
||||
2. 尝试手动指定 DNS:`docker run --dns 8.8.8.8 ...`
|
||||
3. 检查防火墙是否拦截了 UDP 53 端口。
|
||||
|
||||
### Q: 无法通过容器名通信
|
||||
|
||||
**现象**:`ping db` 提示 `bad address 'db'`。
|
||||
|
||||
**原因**:
|
||||
- 你可能在使用**默认的 bridge 网络**。默认 bridge 网络**不支持**通过容器名进行 DNS 解析(这是一个历史遗留设计)。
|
||||
- **解决**:使用自定义网络 (`docker network create ...`)。
|
||||
|
||||
---
|
||||
|
||||
## 本章小结
|
||||
|
||||
| 场景 | DNS 行为 | 备注 |
|
||||
|------|----------|------|
|
||||
| **默认网络** | 继承宿主机 | 不支持容器名解析 |
|
||||
| **自定义网络** | Docker 嵌入式 DNS | ✅ 支持容器名解析 |
|
||||
| **手动指定** | 使用 `--dns` | 覆盖默认配置 |
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [网络模式](README.md):Docker 网络概览
|
||||
- [端口映射](port_mapping.md):外部访问
|
||||
@@ -1,149 +0,0 @@
|
||||
# 外部访问容器
|
||||
|
||||
## 为什么要映射端口
|
||||
|
||||
容器运行在自己的隔离网络环境中(通常是 Bridge 模式)。这意味着:
|
||||
- **容器之间**:可以通过 IP 或容器名(自定义网络)互通。
|
||||
- **宿主机访问容器**:可以通过容器 IP 访问。
|
||||
- **外部网络访问容器**:❌ 默认无法直接访问。
|
||||
|
||||
为了让外部(如你的浏览器、其他局域网机器)访问容器内的服务,我们需要将容器的端口**映射**到宿主机的端口。
|
||||
|
||||
```
|
||||
外部用户 (Browser)
|
||||
│
|
||||
▼
|
||||
宿主机 (localhost:8080)
|
||||
│
|
||||
┌────┴────┐ 端口映射
|
||||
│ Docker │ (8080 -> 80)
|
||||
│ Proxy │
|
||||
└────┬────┘
|
||||
│
|
||||
▼
|
||||
容器 (Class B: 80)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 端口映射方式
|
||||
|
||||
### 1. 指定映射 (-p)
|
||||
|
||||
使用 `-p <宿主机端口>:<容器端口>` 格式。
|
||||
|
||||
```bash
|
||||
# 将宿主机的 8080 端口映射到容器的 80 端口
|
||||
$ docker run -d -p 8080:80 nginx
|
||||
```
|
||||
|
||||
此时访问 `http://localhost:8080` 即可看到 Nginx 页面。
|
||||
|
||||
**多种格式**:
|
||||
|
||||
| 格式 | 含义 | 示例 |
|
||||
|------|------|------|
|
||||
| `ip:hostPort:containerPort` | 绑定指定 IP 的特定端口 | `-p 127.0.0.1:8080:80` (仅本机访问) |
|
||||
| `ip::containerPort` | 绑定指定 IP 的随机端口 | `-p 127.0.0.1::80` |
|
||||
| `hostPort:containerPort` | 绑定所有 IP (0.0.0.0) 的特定端口 | `-p 8080:80` (默认) |
|
||||
| `containerPort` | 绑定所有 IP 的随机端口 | `-p 80` |
|
||||
|
||||
### 2. 随机映射 (-P)
|
||||
|
||||
使用 `-P` (大写) 参数,Docker 会随机映射 Dockerfile 中 `EXPOSE` 指令暴露的所有端口到宿主机的高端口(49000-49900)。
|
||||
|
||||
```bash
|
||||
$ docker run -d -P nginx
|
||||
```
|
||||
|
||||
查看映射结果:
|
||||
|
||||
```bash
|
||||
$ docker ps
|
||||
CONTAINER ID PORTS
|
||||
abc123456 0.0.0.0:49153->80/tcp
|
||||
```
|
||||
|
||||
此时 Nginx 被映射到了宿主机的 49153 端口。
|
||||
|
||||
---
|
||||
|
||||
## 查看端口映射
|
||||
|
||||
### docker port
|
||||
|
||||
```bash
|
||||
$ docker port mycontainer
|
||||
80/tcp -> 0.0.0.0:8080
|
||||
80/tcp -> [::]:8080
|
||||
```
|
||||
|
||||
### docker ps
|
||||
|
||||
```bash
|
||||
$ docker ps
|
||||
CONTAINER ID IMAGE PORTS NAMES
|
||||
abc123456 nginx 0.0.0.0:8080->80/tcp web
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 最佳实践与安全
|
||||
|
||||
### 1. 限制监听 IP
|
||||
|
||||
默认情况下,`-p 8080:80` 会监听 `0.0.0.0:8080`,这意味着任何人只要能连接你的宿主机 IP,就能访问该服务。
|
||||
|
||||
如果不希望对外暴露(例如数据库服务),应绑定到 `127.0.0.1`:
|
||||
|
||||
```bash
|
||||
# 仅允许本机访问
|
||||
$ docker run -d -p 127.0.0.1:3306:3306 mysql
|
||||
```
|
||||
|
||||
### 2. 避免端口冲突
|
||||
|
||||
如果宿主机 8080 已经被占用了,容器将无法启动。
|
||||
|
||||
**解决**:
|
||||
- 更换宿主机端口:`-p 8081:80`
|
||||
- 让 Docker 自动分配:`-p 80`
|
||||
|
||||
### 3. UDP 映射
|
||||
|
||||
默认是 TCP 协议。如果要映射 UDP 服务(如 DNS, Syslog):
|
||||
|
||||
```bash
|
||||
$ docker run -d -p 53:53/udp dns-server
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 实现原理
|
||||
|
||||
Docker 使用 `docker-proxy` 进程(用户态)或 `iptables` DNAT 规则(内核态)来实现端口转发。
|
||||
|
||||
当流量到达宿主机端口时,iptables 规则将其目标地址修改为容器 IP 并转发:
|
||||
|
||||
```bash
|
||||
# 简化的 iptables 逻辑
|
||||
iptables -t nat -A DOCKER -p tcp --dport 8080 -j DNAT --to-destination 172.17.0.2:80
|
||||
```
|
||||
|
||||
这也是为什么你在容器内部看到的访问来源 IP 通常是网关 IP(如 172.17.0.1),而不是真实的外部 Client IP(除非使用 host 网络模式)。
|
||||
|
||||
---
|
||||
|
||||
## 本章小结
|
||||
|
||||
| 要点 | 说明 |
|
||||
|------|------|
|
||||
| **-p** | 指定端口映射(常用),如 `8080:80` |
|
||||
| **-P** | 随机映射所有 EXPOSE 的端口 |
|
||||
| **安全性** | 默认监听所有 IP,敏感服务应绑定 `127.0.0.1` |
|
||||
| **查看** | 使用 `docker port` 或 `docker ps` |
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [EXPOSE 指令](../04_image/dockerfile/expose.md):在 Dockerfile 中声明端口
|
||||
- [网络模式](README.md):Host 模式不需要端口映射
|
||||
@@ -1,39 +0,0 @@
|
||||
# 使用 Buildx 构建镜像
|
||||
|
||||
## 使用
|
||||
|
||||
你可以直接使用 `docker buildx build` 命令构建镜像。
|
||||
|
||||
```bash
|
||||
$ docker buildx build .
|
||||
[+] Building 8.4s (23/32)
|
||||
=> ...
|
||||
```
|
||||
|
||||
Buildx 使用 [BuildKit 引擎](buildkit.md) 进行构建,支持许多新的功能,具体参考 [Buildkit](buildkit.md) 一节。
|
||||
|
||||
### 使用 `bake`
|
||||
|
||||
`docker buildx bake` 是一个高级构建命令,支持从 HCL、JSON 或 Compose 文件中定义构建目标,实现复杂的流水线构建。
|
||||
|
||||
```bash
|
||||
# 从 docker-compose.yml 构建所有服务
|
||||
$ docker buildx bake
|
||||
|
||||
# 仅构建指定目标
|
||||
$ docker buildx bake web
|
||||
```
|
||||
|
||||
### 生成 SBOM
|
||||
|
||||
Buildx 支持在构建时直接生成 SBOM (Software Bill of Materials),这对于软件供应链安全至关重要。
|
||||
|
||||
```bash
|
||||
$ docker buildx build --sbom=true -t myimage .
|
||||
```
|
||||
|
||||
该命令会在构建结果中包含 SPDX 或 CycloneDX 格式的 SBOM 数据。
|
||||
|
||||
## 官方文档
|
||||
|
||||
* https://docs.docker.com/engine/reference/commandline/buildx/
|
||||
@@ -1,121 +0,0 @@
|
||||
# 构建多种系统架构支持的 Docker 镜像
|
||||
|
||||
Docker 镜像可以支持多种系统架构,这意味着你可以在 `x86_64`、`arm64` 等不同架构的机器上运行同一个镜像。这是通过一个名为 "manifest list"(或称为 "fat manifest")的文件来实现的。
|
||||
|
||||
## Manifest List 是什么?
|
||||
|
||||
Manifest list 是一个包含了多个指向不同架构镜像的 manifest 的文件。当你拉取一个支持多架构的镜像时,Docker 会自动根据你当前的系统架构选择并拉取对应的镜像。
|
||||
|
||||
例如,官方的 `hello-world` 镜像就支持多种架构。你可以使用 `docker manifest inspect` 命令来查看它的 manifest list:
|
||||
|
||||
```bash
|
||||
$ docker manifest inspect hello-world
|
||||
{
|
||||
"schemaVersion": 2,
|
||||
"mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
|
||||
"manifests": [
|
||||
{
|
||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||
"size": 525,
|
||||
"digest": "sha256:80852a401a974d9e923719a948cc5335a0a4435be8778b475844a7153a2382e5",
|
||||
"platform": {
|
||||
"architecture": "amd64",
|
||||
"os": "linux"
|
||||
}
|
||||
},
|
||||
{
|
||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||
"size": 525,
|
||||
"digest": "sha256:3adea81344be1724b383d501736c3852939b33b3903d02474373700b25e5d6e3",
|
||||
"platform": {
|
||||
"architecture": "arm",
|
||||
"os": "linux",
|
||||
"variant": "v5"
|
||||
}
|
||||
},
|
||||
// ... more architectures
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 使用 `docker buildx` 构建多架构镜像
|
||||
|
||||
在 Docker 19.03+ 版本中,`docker buildx` 是推荐的用于构建多架构镜像的工具。它使用 `BuildKit` 作为后端,可以大大简化构建过程。
|
||||
|
||||
### 新建 `builder` 实例
|
||||
|
||||
首先,你需要创建一个新的 `builder` 实例,因为它支持同时为多个平台构建。
|
||||
|
||||
```bash
|
||||
$ docker buildx create --name mybuilder --use
|
||||
$ docker buildx inspect --bootstrap
|
||||
```
|
||||
|
||||
### 构建和推送
|
||||
|
||||
使用 `docker buildx build` 命令并指定 `--platform` 参数,可以同时构建支持多种架构的镜像。`--push` 参数会将构建好的镜像和 manifest list 推送到 Docker 仓库。
|
||||
|
||||
```dockerfile
|
||||
# Dockerfile
|
||||
FROM --platform=$TARGETPLATFORM alpine
|
||||
|
||||
RUN uname -a > /os.txt
|
||||
|
||||
CMD cat /os.txt
|
||||
```
|
||||
|
||||
```bash
|
||||
$ docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t your-username/multi-arch-image . --push
|
||||
```
|
||||
|
||||
构建完成后,你就可以在不同架构的机器上拉取并运行 `your-username/multi-arch-image` 这个镜像了。
|
||||
|
||||
### 架构相关的构建参数
|
||||
|
||||
在 `Dockerfile` 中,你可以使用一些预定义的构建参数来根据目标平台定制构建过程:
|
||||
|
||||
* `TARGETPLATFORM`: 构建镜像的目标平台,例如 `linux/amd64`。
|
||||
* `TARGETOS`: 目标平台的操作系统,例如 `linux`。
|
||||
* `TARGETARCH`: 目标平台的架构,例如 `amd64`。
|
||||
* `TARGETVARIANT`: 目标平台的变种,例如 `v7`。
|
||||
* `BUILDPLATFORM`: 构建环境的平台。
|
||||
* `BUILDOS`: 构建环境的操作系统。
|
||||
* `BUILDARCH`: 构建环境的架构。
|
||||
* `BUILDVARIANT`: 构建环境的变种。
|
||||
|
||||
例如,你可以这样编写 `Dockerfile` 来拷贝特定架构的二进制文件:
|
||||
|
||||
```dockerfile
|
||||
FROM scratch
|
||||
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
|
||||
COPY bin/dist-${TARGETOS}-${TARGETARCH} /dist
|
||||
|
||||
ENTRYPOINT ["/dist"]
|
||||
```
|
||||
|
||||
## 使用 `docker manifest` (底层工具)
|
||||
|
||||
`docker manifest` 是一个更底层的命令,可以用来创建、检查和推送 manifest list。虽然 `docker buildx` 在大多数情况下更方便,但了解 `docker manifest` 仍然有助于理解其工作原理。
|
||||
|
||||
### 创建 manifest list
|
||||
|
||||
```bash
|
||||
# 首先,为每个架构构建并推送镜像
|
||||
$ docker buildx build --platform linux/amd64 -t your-username/my-app:amd64 . --push
|
||||
$ docker buildx build --platform linux/arm64 -t your-username/my-app:arm64 . --push
|
||||
|
||||
# 然后,创建一个 manifest list,将它们组合在一起
|
||||
$ docker manifest create your-username/my-app:latest \
|
||||
--amend your-username/my-app:amd64 \
|
||||
--amend your-username/my-app:arm64
|
||||
|
||||
# 最后,推送 manifest list
|
||||
$ docker manifest push your-username/my-app:latest
|
||||
```
|
||||
|
||||
### 检查 manifest list
|
||||
|
||||
你可以使用 `docker manifest inspect` 来查看一个 manifest list 的详细信息,如上文所示。
|
||||
@@ -1,339 +0,0 @@
|
||||
# 使用 Django
|
||||
|
||||
> 本小节内容适合 `Python` 开发人员阅读。
|
||||
|
||||
本节将使用 Docker Compose 配置并运行一个 **Django + PostgreSQL** 应用。笔者不仅会介绍具体步骤,还会解释每个配置项的作用,以及开发环境和生产环境的差异。
|
||||
|
||||
## 架构概览
|
||||
|
||||
在开始之前,让我们先理解我们要构建的架构:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Docker Compose 网络 │
|
||||
│ │
|
||||
│ ┌─────────────────────┐ ┌─────────────────────┐ │
|
||||
│ │ web 服务 │ │ db 服务 │ │
|
||||
│ │ ┌───────────────┐ │ │ ┌───────────────┐ │ │
|
||||
│ │ │ Django │ │──────│ │ PostgreSQL │ │ │
|
||||
│ │ │ 应用 │ │ :5432│ │ 数据库 │ │ │
|
||||
│ │ └───────────────┘ │ │ └───────────────┘ │ │
|
||||
│ │ :8000 │ │ │ │
|
||||
│ └──────────┬──────────┘ └─────────────────────┘ │
|
||||
│ │ │
|
||||
└─────────────┼───────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
localhost:8000
|
||||
(浏览器访问)
|
||||
```
|
||||
|
||||
**关键点**:
|
||||
- `web` 服务运行 Django 应用,对外暴露 8000 端口
|
||||
- `db` 服务运行 PostgreSQL 数据库,只在内部网络可访问
|
||||
- 两个服务通过 Docker Compose 自动创建的网络相互通信
|
||||
- `web` 服务可以通过服务名 `db` 访问数据库(Docker 内置 DNS)
|
||||
|
||||
## 准备工作
|
||||
|
||||
创建一个项目目录并进入:
|
||||
|
||||
```bash
|
||||
$ mkdir django-docker && cd django-docker
|
||||
```
|
||||
|
||||
我们需要创建三个文件:`Dockerfile`、`requirements.txt` 和 `docker-compose.yml`。
|
||||
|
||||
## Step 1: 创建 Dockerfile
|
||||
|
||||
```docker
|
||||
FROM python:3.12-slim
|
||||
|
||||
# 防止 Python 缓冲 stdout/stderr,让日志实时输出
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
|
||||
# 设置工作目录
|
||||
WORKDIR /code
|
||||
|
||||
# 先复制依赖文件,利用 Docker 缓存加速构建
|
||||
COPY requirements.txt /code/
|
||||
|
||||
# 安装依赖
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# 复制项目代码
|
||||
COPY . /code/
|
||||
```
|
||||
|
||||
**逐行解释**:
|
||||
|
||||
| 指令 | 作用 | 为什么这样写 |
|
||||
|------|------|-------------|
|
||||
| `FROM python:3.12-slim` | 基础镜像 | `slim` 版本比完整版小很多,但包含运行 Python 所需的一切 |
|
||||
| `ENV PYTHONUNBUFFERED=1` | 关闭输出缓冲 | 让 `print()` 和日志立即显示,便于调试 |
|
||||
| `WORKDIR /code` | 设置工作目录 | 后续命令都在此目录执行 |
|
||||
| `COPY requirements.txt` 在前 | 分层复制 | 只有 requirements.txt 变化时才重新安装依赖,加速构建 |
|
||||
| `--no-cache-dir` | 不缓存 pip 下载 | 减小镜像体积 |
|
||||
|
||||
> 💡 **笔者建议**:总是将变化频率低的文件先复制,变化频率高的后复制。这样可以最大化利用 Docker 的构建缓存。
|
||||
|
||||
## Step 2: 创建 requirements.txt
|
||||
|
||||
```txt
|
||||
Django>=5.0,<6.0
|
||||
psycopg[binary]>=3.1,<4.0
|
||||
gunicorn>=21.0,<22.0
|
||||
```
|
||||
|
||||
**依赖说明**:
|
||||
|
||||
| 包名 | 作用 |
|
||||
|------|------|
|
||||
| `Django` | Web 框架 |
|
||||
| `psycopg[binary]` | PostgreSQL 数据库驱动(推荐使用 psycopg 3) |
|
||||
| `gunicorn` | 生产环境 WSGI 服务器(可选,开发时可不用) |
|
||||
|
||||
## Step 3: 创建 docker-compose.yml
|
||||
|
||||
```yaml
|
||||
services:
|
||||
db:
|
||||
image: postgres:16
|
||||
environment:
|
||||
POSTGRES_DB: django_db
|
||||
POSTGRES_USER: django_user
|
||||
POSTGRES_PASSWORD: django_password
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U django_user -d django_db"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
web:
|
||||
build: .
|
||||
command: python manage.py runserver 0.0.0.0:8000
|
||||
volumes:
|
||||
- .:/code
|
||||
ports:
|
||||
- "8000:8000"
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
DATABASE_URL: postgres://django_user:django_password@db:5432/django_db
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
```
|
||||
|
||||
**配置详解**:
|
||||
|
||||
### db 服务
|
||||
|
||||
```yaml
|
||||
db:
|
||||
image: postgres:16 # 使用官方 PostgreSQL 16 镜像
|
||||
environment:
|
||||
POSTGRES_DB: django_db # 创建的数据库名
|
||||
POSTGRES_USER: django_user # 数据库用户
|
||||
POSTGRES_PASSWORD: django_password # 数据库密码
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data # 持久化数据
|
||||
healthcheck: # 健康检查,确保数据库就绪
|
||||
test: ["CMD-SHELL", "pg_isready -U django_user -d django_db"]
|
||||
interval: 5s
|
||||
```
|
||||
|
||||
> ⚠️ **笔者提醒**:`volumes` 配置很重要!没有它,每次容器重启数据都会丢失。笔者见过不少新手因为忘记这一步,导致开发数据全部丢失。
|
||||
|
||||
### web 服务
|
||||
|
||||
```yaml
|
||||
web:
|
||||
build: . # 从当前目录的 Dockerfile 构建
|
||||
command: python manage.py runserver # 启动 Django 开发服务器
|
||||
volumes:
|
||||
- .:/code # 挂载代码目录,支持热更新
|
||||
ports:
|
||||
- "8000:8000" # 映射端口
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy # 等待数据库健康后再启动
|
||||
```
|
||||
|
||||
**关键配置说明**:
|
||||
|
||||
| 配置项 | 作用 | 笔者建议 |
|
||||
|--------|------|---------|
|
||||
| `volumes: .:/code` | 代码挂载 | 开发时必备,修改代码无需重新构建镜像 |
|
||||
| `depends_on` + `healthcheck` | 启动顺序 | 确保数据库就绪后 Django 才启动,避免连接错误 |
|
||||
| `environment` | 环境变量 | 推荐用环境变量管理配置,避免硬编码 |
|
||||
|
||||
## Step 4: 创建 Django 项目
|
||||
|
||||
运行以下命令创建新的 Django 项目:
|
||||
|
||||
```bash
|
||||
$ docker compose run --rm web django-admin startproject mysite .
|
||||
```
|
||||
|
||||
**命令解释**:
|
||||
- `docker compose run`:运行一次性命令
|
||||
- `--rm`:命令执行后删除临时容器
|
||||
- `web`:在 web 服务环境中执行
|
||||
- `django-admin startproject mysite .`:在当前目录创建 Django 项目
|
||||
|
||||
生成的目录结构:
|
||||
|
||||
```
|
||||
django-docker/
|
||||
├── docker-compose.yml
|
||||
├── Dockerfile
|
||||
├── requirements.txt
|
||||
├── manage.py
|
||||
└── mysite/
|
||||
├── __init__.py
|
||||
├── settings.py
|
||||
├── urls.py
|
||||
├── asgi.py
|
||||
└── wsgi.py
|
||||
```
|
||||
|
||||
> 💡 **Linux 用户注意**:如果遇到权限问题,执行 `sudo chown -R $USER:$USER .`
|
||||
|
||||
## Step 5: 配置数据库连接
|
||||
|
||||
修改 `mysite/settings.py`,配置数据库连接:
|
||||
|
||||
```python
|
||||
import os
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.postgresql',
|
||||
'NAME': os.environ.get('POSTGRES_DB', 'django_db'),
|
||||
'USER': os.environ.get('POSTGRES_USER', 'django_user'),
|
||||
'PASSWORD': os.environ.get('POSTGRES_PASSWORD', 'django_password'),
|
||||
'HOST': 'db', # Docker Compose 服务名
|
||||
'PORT': 5432,
|
||||
}
|
||||
}
|
||||
|
||||
# 允许的主机(开发环境)
|
||||
ALLOWED_HOSTS = ['*']
|
||||
```
|
||||
|
||||
**为什么 HOST 是 `db` 而不是 `localhost`?**
|
||||
|
||||
在 Docker Compose 中,各服务通过服务名相互访问。Docker 内置的 DNS 会将 `db` 解析为 db 服务容器的 IP 地址。这是 Docker Compose 的核心功能之一。
|
||||
|
||||
## Step 6: 启动应用
|
||||
|
||||
```bash
|
||||
$ docker compose up
|
||||
```
|
||||
|
||||
你会看到:
|
||||
1. 首先构建 web 镜像(第一次运行)
|
||||
2. 启动 db 服务,等待健康检查通过
|
||||
3. 启动 web 服务
|
||||
|
||||
```
|
||||
db-1 | PostgreSQL init process complete; ready for start up.
|
||||
db-1 | LOG: database system is ready to accept connections
|
||||
web-1 | Watching for file changes with StatReloader
|
||||
web-1 | Starting development server at http://0.0.0.0:8000/
|
||||
```
|
||||
|
||||
打开浏览器访问 http://localhost:8000,可以看到 Django 欢迎页面!
|
||||
|
||||
## 常用开发命令
|
||||
|
||||
在另一个终端窗口执行:
|
||||
|
||||
```bash
|
||||
# 执行数据库迁移
|
||||
$ docker compose exec web python manage.py migrate
|
||||
|
||||
# 创建超级用户
|
||||
$ docker compose exec web python manage.py createsuperuser
|
||||
|
||||
# 进入 Django shell
|
||||
$ docker compose exec web python manage.py shell
|
||||
|
||||
# 进入 PostgreSQL 命令行
|
||||
$ docker compose exec db psql -U django_user -d django_db
|
||||
```
|
||||
|
||||
> 💡 笔者建议使用 `exec` 而不是 `run`。`exec` 在已运行的容器中执行命令,`run` 会创建新容器。
|
||||
|
||||
## 常见问题排查
|
||||
|
||||
### Q1: 数据库连接失败
|
||||
|
||||
**错误信息**:`django.db.utils.OperationalError: could not connect to server`
|
||||
|
||||
**可能原因与解决方案**:
|
||||
|
||||
| 原因 | 解决方案 |
|
||||
|------|---------|
|
||||
| 数据库还没启动完成 | 使用 `depends_on` + `healthcheck` |
|
||||
| HOST 配置错误 | 确保使用服务名 `db` 而不是 `localhost` |
|
||||
| 网络未创建 | 运行 `docker compose down` 后重新 `up` |
|
||||
|
||||
```bash
|
||||
# 调试:检查数据库是否正常运行
|
||||
$ docker compose ps
|
||||
$ docker compose logs db
|
||||
```
|
||||
|
||||
### Q2: 代码修改没有生效
|
||||
|
||||
**可能原因**:
|
||||
|
||||
1. **开发服务器没有自动重载**:确保使用 `runserver` 而不是 `gunicorn`
|
||||
2. **Volume 挂载问题**:检查 `docker-compose.yml` 中的 volumes 配置
|
||||
3. **缓存问题**:尝试 `docker compose restart web`
|
||||
|
||||
### Q3: 权限问题(Linux)
|
||||
|
||||
```bash
|
||||
# 如果容器内创建的文件 root 用户所有
|
||||
$ sudo chown -R $USER:$USER .
|
||||
```
|
||||
|
||||
## 开发 vs 生产:关键差异
|
||||
|
||||
笔者特别提醒,本节的配置是**开发环境**配置。生产环境需要以下调整:
|
||||
|
||||
| 配置项 | 开发环境 | 生产环境 |
|
||||
|--------|---------|---------|
|
||||
| **Web 服务器** | `runserver` | `gunicorn` + Nginx |
|
||||
| **DEBUG** | `True` | `False` |
|
||||
| **密码管理** | 明文写在配置 | 使用 Docker Secrets 或环境变量 |
|
||||
| **Volume** | 挂载代码目录 | 代码直接 COPY 进镜像 |
|
||||
| **ALLOWED_HOSTS** | `['*']` | 具体域名 |
|
||||
|
||||
**生产环境 docker-compose.yml 示例**:
|
||||
|
||||
```yaml
|
||||
# docker-compose.prod.yml
|
||||
services:
|
||||
web:
|
||||
build: .
|
||||
command: gunicorn mysite.wsgi:application --bind 0.0.0.0:8000
|
||||
# 不挂载代码,使用镜像内的代码
|
||||
environment:
|
||||
DEBUG: 'False'
|
||||
ALLOWED_HOSTS: 'example.com,www.example.com'
|
||||
# ...
|
||||
```
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [Compose 模板文件详解](compose_file.md):深入理解 docker-compose.yml 的所有配置项
|
||||
- [使用 WordPress](wordpress.md):另一个 Compose 实战案例
|
||||
- [Dockerfile 最佳实践](../15_appendix/best_practices.md):构建更小、更安全的镜像
|
||||
- [数据管理](../07_data_network/README.md):Volume 和数据持久化详解
|
||||
@@ -1,54 +0,0 @@
|
||||
# 安装与卸载
|
||||
|
||||
`Compose` 是 Docker 官方的开源项目,负责实现对 Docker 容器集群的快速编排。
|
||||
|
||||
从 `v2` 版本开始,`Compose` 作为 `docker` 的子命令存在,例如 `docker compose up`。
|
||||
|
||||
`Compose` 支持 Linux、macOS、Windows 10 三大平台。
|
||||
|
||||
`Docker Desktop for Mac/Windows` 自带 `docker compose` 命令。
|
||||
|
||||
Linux 系统请使用以下介绍的方法安装。
|
||||
|
||||
## Linux
|
||||
|
||||
在 Linux 上,你可以通过下载 `docker-compose` 二进制包来安装。
|
||||
|
||||
从 [官方 GitHub Release](https://github.com/docker/compose/releases) 处直接下载编译好的二进制文件即可。
|
||||
|
||||
> **提示**:版本更新较快,请访问上述链接获取最新版本号,替换下方命令中的版本号。
|
||||
|
||||
例如,在 Linux 64 位系统上直接下载对应的二进制包(以 v2.40.3 为例)。
|
||||
|
||||
```bash
|
||||
$ DOCKER_CONFIG=${DOCKER_CONFIG:-$HOME/.docker}
|
||||
$ mkdir -p $DOCKER_CONFIG/cli-plugins
|
||||
$ curl -SL https://github.com/docker/compose/releases/download/v2.40.3/docker-compose-linux-x86_64 -o $DOCKER_CONFIG/cli-plugins/docker-compose
|
||||
```
|
||||
|
||||
之后,执行
|
||||
|
||||
```bash
|
||||
$ chmod +x $DOCKER_CONFIG/cli-plugins/docker-compose
|
||||
```
|
||||
|
||||
## 测试安装
|
||||
|
||||
```bash
|
||||
$ docker compose version
|
||||
Docker Compose version v2.40.3
|
||||
```
|
||||
|
||||
## bash 补全命令
|
||||
|
||||
```bash
|
||||
$ curl -L https://raw.githubusercontent.com/docker/compose/v2.40.3/contrib/completion/bash/docker-compose > /etc/bash_completion.d/docker-compose
|
||||
```
|
||||
|
||||
## 卸载
|
||||
|
||||
如果是二进制包方式安装的,删除二进制文件即可。
|
||||
|
||||
```bash
|
||||
$ rm $DOCKER_CONFIG/cli-plugins/docker-compose
|
||||
```
|
||||
@@ -1,268 +0,0 @@
|
||||
# 使用 Rails
|
||||
|
||||
> 本小节内容适合 Ruby 开发人员阅读。
|
||||
|
||||
本节使用 Docker Compose 配置并运行一个 **Rails + PostgreSQL** 应用。
|
||||
|
||||
## 架构概览
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Docker Compose 网络 │
|
||||
│ │
|
||||
│ ┌─────────────────────┐ ┌─────────────────────┐ │
|
||||
│ │ web 服务 │ │ db 服务 │ │
|
||||
│ │ ┌───────────────┐ │ │ ┌───────────────┐ │ │
|
||||
│ │ │ Rails │ │──────│ │ PostgreSQL │ │ │
|
||||
│ │ │ 应用 │ │ :5432│ │ 数据库 │ │ │
|
||||
│ │ └───────────────┘ │ │ └───────────────┘ │ │
|
||||
│ │ :3000 │ │ │ │
|
||||
│ └──────────┬──────────┘ └─────────────────────┘ │
|
||||
│ │ │
|
||||
└─────────────┼───────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
localhost:3000
|
||||
```
|
||||
|
||||
## 准备工作
|
||||
|
||||
创建项目目录:
|
||||
|
||||
```bash
|
||||
$ mkdir rails-docker && cd rails-docker
|
||||
```
|
||||
|
||||
需要创建三个文件:`Dockerfile`、`Gemfile` 和 `docker-compose.yml`。
|
||||
|
||||
## Step 1: 创建 Dockerfile
|
||||
|
||||
```docker
|
||||
FROM ruby:3.2
|
||||
|
||||
# 安装系统依赖
|
||||
RUN apt-get update -qq && \
|
||||
apt-get install -y build-essential libpq-dev nodejs && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# 设置工作目录
|
||||
WORKDIR /myapp
|
||||
|
||||
# 先复制 Gemfile,利用缓存加速构建
|
||||
COPY Gemfile /myapp/Gemfile
|
||||
COPY Gemfile.lock /myapp/Gemfile.lock
|
||||
RUN bundle install
|
||||
|
||||
# 复制应用代码
|
||||
COPY . /myapp
|
||||
```
|
||||
|
||||
**配置说明**:
|
||||
|
||||
| 指令 | 作用 |
|
||||
|------|------|
|
||||
| `build-essential` | 编译原生扩展所需 |
|
||||
| `libpq-dev` | PostgreSQL 客户端库 |
|
||||
| `nodejs` | Rails Asset Pipeline 需要 |
|
||||
| 先复制 Gemfile | 只有依赖变化时才重新 `bundle install` |
|
||||
|
||||
## Step 2: 创建 Gemfile
|
||||
|
||||
创建一个初始的 `Gemfile`,稍后会被 `rails new` 覆盖:
|
||||
|
||||
```ruby
|
||||
source 'https://rubygems.org'
|
||||
gem 'rails', '~> 7.1'
|
||||
```
|
||||
|
||||
创建空的 `Gemfile.lock`:
|
||||
|
||||
```bash
|
||||
$ touch Gemfile.lock
|
||||
```
|
||||
|
||||
## Step 3: 创建 docker-compose.yml
|
||||
|
||||
```yaml
|
||||
services:
|
||||
db:
|
||||
image: postgres:16
|
||||
environment:
|
||||
POSTGRES_PASSWORD: password
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
|
||||
web:
|
||||
build: .
|
||||
command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
|
||||
volumes:
|
||||
- .:/myapp
|
||||
ports:
|
||||
- "3000:3000"
|
||||
depends_on:
|
||||
- db
|
||||
environment:
|
||||
DATABASE_URL: postgres://postgres:password@db:5432/myapp_development
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
```
|
||||
|
||||
**配置详解**:
|
||||
|
||||
| 配置项 | 说明 |
|
||||
|--------|------|
|
||||
| `rm -f tmp/pids/server.pid` | 清理上次异常退出留下的 PID 文件 |
|
||||
| `volumes: .:/myapp` | 挂载代码目录,支持热更新 |
|
||||
| `depends_on: db` | 确保数据库先启动 |
|
||||
| `DATABASE_URL` | Rails 12-factor 风格的数据库配置 |
|
||||
|
||||
## Step 4: 生成 Rails 项目
|
||||
|
||||
使用 `docker compose run` 生成项目骨架:
|
||||
|
||||
```bash
|
||||
$ docker compose run --rm web rails new . --force --database=postgresql --skip-bundle
|
||||
```
|
||||
|
||||
**命令解释**:
|
||||
- `--rm`:执行后删除临时容器
|
||||
- `--force`:覆盖已存在的文件
|
||||
- `--database=postgresql`:配置使用 PostgreSQL
|
||||
- `--skip-bundle`:暂不安装依赖(稍后统一安装)
|
||||
|
||||
生成的目录结构:
|
||||
|
||||
```bash
|
||||
$ ls
|
||||
Dockerfile Gemfile Rakefile config lib tmp
|
||||
Gemfile.lock README.md app config.ru log vendor
|
||||
docker-compose.yml bin db public
|
||||
```
|
||||
|
||||
> ⚠️ **Linux 用户**:如遇权限问题,执行 `sudo chown -R $USER:$USER .`
|
||||
|
||||
## Step 5: 重新构建镜像
|
||||
|
||||
由于生成了新的 Gemfile,需要重新构建镜像以安装完整依赖:
|
||||
|
||||
```bash
|
||||
$ docker compose build
|
||||
```
|
||||
|
||||
## Step 6: 配置数据库连接
|
||||
|
||||
修改 `config/database.yml`:
|
||||
|
||||
```yaml
|
||||
default: &default
|
||||
adapter: postgresql
|
||||
encoding: unicode
|
||||
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
|
||||
url: <%= ENV['DATABASE_URL'] %>
|
||||
|
||||
development:
|
||||
<<: *default
|
||||
|
||||
test:
|
||||
<<: *default
|
||||
database: myapp_test
|
||||
|
||||
production:
|
||||
<<: *default
|
||||
```
|
||||
|
||||
> 💡 使用 `DATABASE_URL` 环境变量配置数据库,符合 12-factor 应用原则,便于在不同环境间切换。
|
||||
|
||||
## Step 7: 启动应用
|
||||
|
||||
```bash
|
||||
$ docker compose up
|
||||
```
|
||||
|
||||
输出示例:
|
||||
|
||||
```
|
||||
db-1 | PostgreSQL init process complete; ready for start up.
|
||||
db-1 | LOG: database system is ready to accept connections
|
||||
web-1 | => Booting Puma
|
||||
web-1 | => Rails 7.1.0 application starting in development
|
||||
web-1 | => Run `bin/rails server --help` for more startup options
|
||||
web-1 | Puma starting in single mode...
|
||||
web-1 | * Listening on http://0.0.0.0:3000
|
||||
```
|
||||
|
||||
## Step 8: 创建数据库
|
||||
|
||||
在另一个终端执行:
|
||||
|
||||
```bash
|
||||
$ docker compose exec web rails db:create
|
||||
Created database 'myapp_development'
|
||||
Created database 'myapp_test'
|
||||
```
|
||||
|
||||
访问 http://localhost:3000 查看 Rails 欢迎页面。
|
||||
|
||||
## 常用开发命令
|
||||
|
||||
```bash
|
||||
# 数据库迁移
|
||||
$ docker compose exec web rails db:migrate
|
||||
|
||||
# Rails 控制台
|
||||
$ docker compose exec web rails console
|
||||
|
||||
# 运行测试
|
||||
$ docker compose exec web rails test
|
||||
|
||||
# 生成脚手架
|
||||
$ docker compose exec web rails generate scaffold Post title:string body:text
|
||||
|
||||
# 进入容器 Shell
|
||||
$ docker compose exec web bash
|
||||
```
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q: 数据库连接失败
|
||||
|
||||
检查 `DATABASE_URL` 环境变量格式是否正确,确保 db 服务已启动:
|
||||
|
||||
```bash
|
||||
$ docker compose ps
|
||||
$ docker compose logs db
|
||||
```
|
||||
|
||||
### Q: server.pid 文件导致启动失败
|
||||
|
||||
错误信息:`A server is already running`
|
||||
|
||||
已在 command 中添加 `rm -f tmp/pids/server.pid` 处理。如仍有问题:
|
||||
|
||||
```bash
|
||||
$ docker compose exec web rm -f tmp/pids/server.pid
|
||||
```
|
||||
|
||||
### Q: Gem 安装失败
|
||||
|
||||
可能需要更新 bundler 或清理缓存:
|
||||
|
||||
```bash
|
||||
$ docker compose run --rm web bundle update
|
||||
```
|
||||
|
||||
## 开发 vs 生产
|
||||
|
||||
| 配置项 | 开发环境 | 生产环境 |
|
||||
|--------|---------|---------|
|
||||
| Rails 服务器 | Puma (开发模式) | Puma + Nginx |
|
||||
| 代码挂载 | 使用 volumes | 代码打包进镜像 |
|
||||
| 静态资源 | 动态编译 | 预编译 (`rails assets:precompile`) |
|
||||
| 数据库密码 | 明文配置 | 使用 Secrets 管理 |
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [使用 Django](django.md):Python Web 框架实战
|
||||
- [Compose 模板文件](compose_file.md):配置详解
|
||||
- [数据管理](../07_data_network/README.md):数据持久化
|
||||
@@ -1,210 +0,0 @@
|
||||
# 实战 WordPress
|
||||
|
||||
## 简介
|
||||
|
||||
WordPress 是全球最流行的内容管理系统(CMS)。使用 Docker Compose 可以在几分钟内搭建一个包含数据库、Web 服务和持久化存储的生产级 WordPress 环境。
|
||||
|
||||
---
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
wordpress/
|
||||
├── docker-compose.yml
|
||||
├── .env # 环境变量(敏感信息)
|
||||
└── nginx/ # 可选:反向代理配置
|
||||
└── nginx.conf
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 编写 `docker-compose.yml`
|
||||
|
||||
这是一个生产可用的最小化配置:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
# 数据库服务
|
||||
db:
|
||||
image: mysql:8.0
|
||||
container_name: wordpress_db
|
||||
restart: always
|
||||
command:
|
||||
# 使用原生密码认证(旧版 WP 兼容性)
|
||||
- --default-authentication-plugin=mysql_native_password
|
||||
- --character-set-server=utf8mb4
|
||||
- --collation-server=utf8mb4_unicode_ci
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
|
||||
MYSQL_DATABASE: wordpress
|
||||
MYSQL_USER: wordpress
|
||||
MYSQL_PASSWORD: ${DB_PASSWORD}
|
||||
volumes:
|
||||
- db_data:/var/lib/mysql
|
||||
networks:
|
||||
- wp_net
|
||||
|
||||
# WordPress 服务
|
||||
wordpress:
|
||||
image: wordpress:latest
|
||||
container_name: wordpress_app
|
||||
restart: always
|
||||
ports:
|
||||
- "8000:80"
|
||||
environment:
|
||||
WORDPRESS_DB_HOST: db:3306
|
||||
WORDPRESS_DB_USER: wordpress
|
||||
WORDPRESS_DB_PASSWORD: ${DB_PASSWORD}
|
||||
WORDPRESS_DB_NAME: wordpress
|
||||
volumes:
|
||||
- wp_data:/var/www/html
|
||||
# 增加上传文件大小限制
|
||||
- ./uploads.ini:/usr/local/etc/php/conf.d/uploads.ini
|
||||
depends_on:
|
||||
- db
|
||||
networks:
|
||||
- wp_net
|
||||
|
||||
volumes:
|
||||
db_data: # 数据库持久化
|
||||
wp_data: # WordPress 文件(插件/主题/上传)持久化
|
||||
|
||||
networks:
|
||||
wp_net:
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 配置文件详解
|
||||
|
||||
### 1. 环境变量 (.env)
|
||||
|
||||
为了安全,不要在 `docker-compose.yml` 中直接写密码。创建 `.env` 文件:
|
||||
|
||||
```ini
|
||||
DB_ROOT_PASSWORD=somestrongrootpassword
|
||||
DB_PASSWORD=somestronguserpassword
|
||||
```
|
||||
|
||||
Compose 会自动读取此同级目录下的文件。
|
||||
|
||||
### 2. 数据持久化
|
||||
|
||||
我们定义了两个命名卷:
|
||||
- `db_data`: 确保 MySQL 容器重建后数据不丢失
|
||||
- `wp_data`: 保存 WordPress 的核心文件、插件、主题和上传的媒体文件
|
||||
|
||||
### 3. PHP 配置优化
|
||||
|
||||
默认的 WordPress 镜像上传文件限制较小(通常 2MB)。创建 `uploads.ini`:
|
||||
|
||||
```ini
|
||||
file_uploads = On
|
||||
memory_limit = 256M
|
||||
upload_max_filesize = 64M
|
||||
post_max_size = 64M
|
||||
max_execution_time = 600
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 启动与运行
|
||||
|
||||
1. 启动服务:
|
||||
|
||||
```bash
|
||||
$ docker compose up -d
|
||||
```
|
||||
|
||||
2. 访问安装界面:
|
||||
打开浏览器访问 `http://localhost:8000`
|
||||
|
||||
3. 查看日志:
|
||||
|
||||
```bash
|
||||
$ docker compose logs -f
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 生产环境最佳实践
|
||||
|
||||
### 1. 数据库备份
|
||||
|
||||
不要只依赖 Volume。建议定期备份数据库:
|
||||
|
||||
```bash
|
||||
# 导出 SQL
|
||||
$ docker exec wordpress_db mysqldump -u wordpress -pwordpress wordpress > backup.sql
|
||||
```
|
||||
|
||||
或者添加一个自动备份容器:
|
||||
|
||||
```yaml
|
||||
backup:
|
||||
image: tiredofit/db-backup
|
||||
volumes:
|
||||
- ./backups:/backup
|
||||
environment:
|
||||
- DB_TYPE=mysql
|
||||
- DB_HOST=db
|
||||
- DB_NAME=wordpress
|
||||
- DB_USER=wordpress
|
||||
- DB_PASS=${DB_PASSWORD}
|
||||
- DB_DUMP_FREQ=1440 # 每天备份一次
|
||||
depends_on:
|
||||
- db
|
||||
networks:
|
||||
- wp_net
|
||||
```
|
||||
|
||||
### 2. 使用 Nginx 反向代理
|
||||
|
||||
在生产环境中,不要直接暴露 WordPress 端口,而是通过 Nginx 进行反向代理并配置 SSL。
|
||||
|
||||
### 3. 使用 Redis 缓存
|
||||
|
||||
WordPress 支持 Redis 缓存以提高性能。
|
||||
|
||||
```yaml
|
||||
redis:
|
||||
image: redis:alpine
|
||||
restart: always
|
||||
networks:
|
||||
- wp_net
|
||||
```
|
||||
|
||||
在 WordPress 容器环境变量中添加:
|
||||
```yaml
|
||||
WORDPRESS_REDIS_HOST: redis
|
||||
```
|
||||
并安装 Redis Object Cache 插件。
|
||||
|
||||
---
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q: 数据库连接错误
|
||||
|
||||
**现象**:访问页面显示 "Error establishing a database connection"。
|
||||
|
||||
**排查**:
|
||||
1. 检查 `docker compose logs wordpress`
|
||||
2. 确认 `.env` 中的密码与 YAML 文件引用一致
|
||||
3. 确认 `WORDPRESS_DB_HOST` 也是 `db`(服务名)
|
||||
4. MySQL 8.0 可能需要几秒钟启动,WordPress 会自动重试,稍等片刻即可。
|
||||
|
||||
### Q: 无法上传大文件
|
||||
|
||||
**解决**:确保挂载了 `uploads.ini` 配置,并且重启了容器:
|
||||
```bash
|
||||
$ docker compose restart wordpress
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [Compose 模板文件](compose_file.md):深入了解配置项
|
||||
- [数据卷](../07_data_network/data/volume.md):理解数据持久化
|
||||
- [Docker Hub WordPress](https://hub.docker.com/_/wordpress):官方镜像文档
|
||||
@@ -1,7 +0,0 @@
|
||||
# 运维管理
|
||||
|
||||
本章将介绍 Docker 的运维管理,包括监控、日志与安全。
|
||||
|
||||
* [容器监控](monitor/README.md)
|
||||
* [日志管理](logs/README.md)
|
||||
* [安全](security/README.md)
|
||||
@@ -1,26 +0,0 @@
|
||||
# 日志管理
|
||||
|
||||
在容器化环境中,日志管理比传统环境更为复杂。容器是短暂的,意味着容器内的日志文件可能会随着容器的销毁而丢失。因此,我们需要一种集中式的日志管理方案来收集、存储和分析容器日志。
|
||||
|
||||
## Docker 日志驱动
|
||||
|
||||
Docker 提供了多种日志驱动(Log Driver)机制,允许我们将容器日志转发到不同的后端。
|
||||
|
||||
常见的日志驱动包括:
|
||||
|
||||
* `json-file`: 默认驱动,将日志以 JSON 格式写入本地文件。
|
||||
* `syslog`: 将日志转发到 syslog 服务器。
|
||||
* `journald`: 将日志写入 systemd journal。
|
||||
* `fluentd`: 将日志转发到 fluentd 收集器。
|
||||
* `gelf`: 支持 GELF 协议的日志后端(如 Graylog)。
|
||||
* `awslogs`: 发送到 Amazon CloudWatch Logs。
|
||||
|
||||
## 日志管理方案
|
||||
|
||||
对于大规模的容器集群,我们通常会采用 EFK (Elasticsearch + Fluentd + Kibana) 或 ELK (Elasticsearch + Logstash + Kibana) 方案。
|
||||
|
||||
* **Elasticsearch**: 负责日志的存储和全文检索。
|
||||
* **Fluentd/Logstash**: 负责日志的采集、过滤和转发。
|
||||
* **Kibana**: 负责日志的可视化展示。
|
||||
|
||||
本章将介绍如何使用 EFK 方案来处理 Docker 容器日志。
|
||||
@@ -1,127 +0,0 @@
|
||||
# ELK/EFK 堆栈
|
||||
|
||||
ELK (Elasticsearch, Logstash, Kibana) 是目前业界最流行的开源日志解决方案。而在容器领域,由于 Fluentd 更加轻量级且对容器支持更好,EFK (Elasticsearch, Fluentd, Kibana) 组合也变得非常流行。
|
||||
|
||||
## 方案架构
|
||||
|
||||
我们将采用以下架构:
|
||||
|
||||
1. **Docker Container**: 容器将日志输出到标准输出 (stdout/stderr)。
|
||||
2. **Fluentd**: 作为 Docker 的 Logging Driver 或运行为守护容器,收集容器日志。
|
||||
3. **Elasticsearch**: 存储从 Fluentd 接收到的日志数据。
|
||||
4. **Kibana**: 从 Elasticsearch 读取数据并进行可视化展示。
|
||||
|
||||
## 部署流程
|
||||
|
||||
### 1. 编写 docker-compose.yml
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
services:
|
||||
elasticsearch:
|
||||
image: docker.elastic.co/elasticsearch/elasticsearch:7.17.0
|
||||
container_name: elasticsearch
|
||||
environment:
|
||||
- "discovery.type=single-node"
|
||||
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
|
||||
ports:
|
||||
- "9200:9200"
|
||||
volumes:
|
||||
- es_data:/usr/share/elasticsearch/data
|
||||
networks:
|
||||
- logging
|
||||
|
||||
kibana:
|
||||
image: docker.elastic.co/kibana/kibana:7.17.0
|
||||
container_name: kibana
|
||||
environment:
|
||||
- ELASTICSEARCH_HOSTS=http://elasticsearch:9200
|
||||
ports:
|
||||
- "5601:5601"
|
||||
links:
|
||||
- elasticsearch
|
||||
networks:
|
||||
- logging
|
||||
|
||||
fluentd:
|
||||
image: fluent/fluentd-kubernetes-daemonset:v1.14.3-debian-elasticsearch7-1.0
|
||||
container_name: fluentd
|
||||
environment:
|
||||
- "FLUENT_ELASTICSEARCH_HOST=elasticsearch"
|
||||
- "FLUENT_ELASTICSEARCH_PORT=9200"
|
||||
- "FLUENT_ELASTICSEARCH_SCHEME=http"
|
||||
- "FLUENT_UID=0"
|
||||
ports:
|
||||
- "24224:24224"
|
||||
- "24224:24224/udp"
|
||||
links:
|
||||
- elasticsearch
|
||||
volumes:
|
||||
- ./fluentd/conf:/fluentd/etc
|
||||
networks:
|
||||
- logging
|
||||
|
||||
volumes:
|
||||
es_data:
|
||||
|
||||
networks:
|
||||
logging:
|
||||
```
|
||||
|
||||
### 2. 配置 Fluentd
|
||||
|
||||
创建 `fluentd/conf/fluent.conf`:
|
||||
|
||||
```conf
|
||||
<source>
|
||||
@type forward
|
||||
port 24224
|
||||
bind 0.0.0.0
|
||||
</source>
|
||||
|
||||
<match *.**>
|
||||
@type copy
|
||||
<store>
|
||||
@type elasticsearch
|
||||
host elasticsearch
|
||||
port 9200
|
||||
logstash_format true
|
||||
logstash_prefix docker
|
||||
logstash_dateformat %Y%m%d
|
||||
include_tag_key true
|
||||
type_name access_log
|
||||
tag_key @log_name
|
||||
flush_interval 1s
|
||||
</store>
|
||||
<store>
|
||||
@type stdout
|
||||
</store>
|
||||
</match>
|
||||
```
|
||||
|
||||
### 3. 配置应用容器使用 fluentd 驱动
|
||||
|
||||
启动一个测试容器,指定日志驱动为 `fluentd`:
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--log-driver=fluentd \
|
||||
--log-opt fluentd-address=localhost:24224 \
|
||||
--log-opt tag=nginx-test \
|
||||
--name nginx-test \
|
||||
nginx
|
||||
```
|
||||
|
||||
**注意**: 确保 `fluentd` 容器已经启动并监听在 `localhost:24224`。在生产环境中,如果你是在不同机器上,需要将 `localhost` 替换为运行 fluentd 的主机 IP。
|
||||
|
||||
### 4. 在 Kibana 中查看日志
|
||||
|
||||
1. 访问 `http://localhost:5601`。
|
||||
2. 进入 **Management** -> **Kibana** -> **Index Patterns**。
|
||||
3. 创建新的 Index Pattern,输入 `docker-*` (我们在 fluent.conf 中配置的前缀)。
|
||||
4. 选择 `@timestamp` 作为时间字段。
|
||||
5. 去 **Discover** 页面,你就能看到 Nginx 容器的日志了。
|
||||
|
||||
## 总结
|
||||
|
||||
通过 Docker 的日志驱动机制,结合 ELK/EFK 强大的收集和分析能力,我们可以轻松构建一个能够处理海量日志的监控平台,这对于排查生产问题至关重要。
|
||||
@@ -1,16 +0,0 @@
|
||||
# 容器监控
|
||||
|
||||
容器化技术的普及使得应用部署变得更加灵活和高效,但也给监控带来了新的挑战。
|
||||
|
||||
在传统架构中,我们通常关注主机的 CPU、内存、磁盘 IO 等指标。而在容器环境下,除了主机层面的监控,我们更关注容器级别的资源使用情况、服务的运行状态以及编排系统的健康状况。
|
||||
|
||||
## 常见的监控方案
|
||||
|
||||
目前主流的容器监控方案包括:
|
||||
|
||||
* **cAdvisor**: Google 开源的容器资源监控工具,Docker 原生支持。
|
||||
* **Prometheus**: CNCF 毕业项目,云原生时代最流行的监控系统。
|
||||
* **Grafana**: 强大的可视化平台,常与 Prometheus 配合使用。
|
||||
* **ELK/EFK Stack**: 主要用于日志管理,但也能提供一定的监控能力。
|
||||
|
||||
本章将重点介绍如何使用 Prometheus 和 Grafana 搭建一套完整的容器监控系统。
|
||||
@@ -1,107 +0,0 @@
|
||||
# Prometheus + Grafana
|
||||
|
||||
[Prometheus](https://prometheus.io/) 是一个开源的系统监控和报警工具包。它受 Google Borgmon 的启发,由 SoundCloud 在 2012 年创建。
|
||||
|
||||
## 架构简介
|
||||
|
||||
Prometheus 的主要组件包括:
|
||||
|
||||
* **Prometheus Server**: 核心组件,负责收集和存储时间序列数据。
|
||||
* **Exporters**: 负责向 Prometheus 暴露监控数据(如 Node Exporter, cAdvisor)。
|
||||
* **Alertmanager**: 处理报警发送。
|
||||
* **Pushgateway**: 用于支持短生命周期的 Job 推送数据。
|
||||
|
||||
## 快速部署
|
||||
|
||||
我们可以使用 Docker Compose 快速部署一套 Prometheus + Grafana 监控环境。
|
||||
|
||||
### 1. 准备配置文件
|
||||
|
||||
创建 `prometheus.yml`:
|
||||
|
||||
```yaml
|
||||
global:
|
||||
scrape_interval: 15s
|
||||
|
||||
scrape_configs:
|
||||
- job_name: 'prometheus'
|
||||
static_configs:
|
||||
- targets: ['localhost:9090']
|
||||
|
||||
- job_name: 'node-exporter'
|
||||
static_configs:
|
||||
- targets: ['node-exporter:9100']
|
||||
|
||||
- job_name: 'cadvisor'
|
||||
static_configs:
|
||||
- targets: ['cadvisor:8080']
|
||||
```
|
||||
|
||||
### 2. 编写 Docker Compose 文件
|
||||
|
||||
创建 `docker-compose.yml`:
|
||||
|
||||
```yaml
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
prometheus:
|
||||
image: prom/prometheus:latest
|
||||
volumes:
|
||||
- ./prometheus.yml:/etc/prometheus/prometheus.yml
|
||||
ports:
|
||||
- "9090:9090"
|
||||
networks:
|
||||
- monitoring
|
||||
|
||||
grafana:
|
||||
image: grafana/grafana:latest
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
- GF_SECURITY_ADMIN_PASSWORD=admin
|
||||
networks:
|
||||
- monitoring
|
||||
depends_on:
|
||||
- prometheus
|
||||
|
||||
node-exporter:
|
||||
image: prom/node-exporter:latest
|
||||
ports:
|
||||
- "9100:9100"
|
||||
networks:
|
||||
- monitoring
|
||||
|
||||
cadvisor:
|
||||
image: gcr.io/cadvisor/cadvisor:latest
|
||||
ports:
|
||||
- "8080:8080"
|
||||
volumes:
|
||||
- /:/rootfs:ro
|
||||
- /var/run:/var/run:ro
|
||||
- /sys:/sys:ro
|
||||
- /var/lib/docker/:/var/lib/docker:ro
|
||||
networks:
|
||||
- monitoring
|
||||
|
||||
networks:
|
||||
monitoring:
|
||||
```
|
||||
|
||||
### 3. 启动服务
|
||||
|
||||
```bash
|
||||
$ docker-compose up -d
|
||||
```
|
||||
|
||||
启动后,访问以下地址:
|
||||
|
||||
* Prometheus: `http://localhost:9090`
|
||||
* Grafana: `http://localhost:3000` (默认账号密码: admin/admin)
|
||||
|
||||
## 配置 Grafana 面板
|
||||
|
||||
1. 在 Grafana 中添加 Prometheus 数据源,URL 填写 `http://prometheus:9090`。
|
||||
2. 导入现成的 Dashboard 模板,例如 [Node Exporter Full](https://grafana.com/grafana/dashboards/1860) (ID: 1860) 和 [Docker Container](https://grafana.com/grafana/dashboards/193) (ID: 193)。
|
||||
|
||||
这样,你就拥有了一个直观的容器监控大屏。
|
||||
@@ -1,344 +0,0 @@
|
||||
# 安全
|
||||
|
||||
容器安全是生产环境部署的核心考量。本章介绍 Docker 的安全机制和最佳实践。
|
||||
|
||||
## 容器安全的本质
|
||||
|
||||
> **核心问题**:容器共享宿主机内核,隔离性弱于虚拟机。如何在便利性和安全性之间取得平衡?
|
||||
|
||||
```
|
||||
虚拟机安全模型: 容器安全模型:
|
||||
┌─────────────────┐ ┌─────────────────┐
|
||||
│ Guest OS │ │ 容器进程 │
|
||||
├─────────────────┤ │ (共享内核) │
|
||||
│ Hypervisor │◄── 隔离边界└────────┬────────┘
|
||||
├─────────────────┤ │
|
||||
│ Host OS │ ┌────────┴────────┐
|
||||
└─────────────────┘ │ Namespace │◄── 隔离边界
|
||||
│ Cgroups │
|
||||
完全隔离(性能损耗) │ Capabilities │
|
||||
└─────────────────┘
|
||||
进程隔离(轻量但需加固)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 核心安全机制
|
||||
|
||||
### 1. 命名空间(Namespace)
|
||||
|
||||
提供进程、网络、文件系统等资源的隔离:
|
||||
|
||||
| Namespace | 隔离内容 | 安全作用 |
|
||||
|-----------|---------|---------|
|
||||
| PID | 进程 | 容器看不到其他进程 |
|
||||
| NET | 网络 | 独立网络栈 |
|
||||
| MNT | 文件系统 | 独立的根目录 |
|
||||
| USER | 用户 | 容器 root ≠ 宿主机 root |
|
||||
| IPC | 进程通信 | 隔离共享内存 |
|
||||
| UTS | 主机名 | 独立主机名 |
|
||||
|
||||
详见 [命名空间](../13_implementation/namespace.md) 章节。
|
||||
|
||||
### 2. 控制组(Cgroups)
|
||||
|
||||
限制容器的资源使用,防止资源耗尽攻击:
|
||||
|
||||
```bash
|
||||
# 限制内存(超出会被 OOM Kill)
|
||||
$ docker run -m 512m myapp
|
||||
|
||||
# 限制 CPU
|
||||
$ docker run --cpus=1.5 myapp
|
||||
|
||||
# 限制磁盘 I/O
|
||||
$ docker run --device-write-bps /dev/sda:10mb myapp
|
||||
```
|
||||
|
||||
### 3. 能力机制(Capabilities)
|
||||
|
||||
Linux 将 root 权限拆分为多个细粒度的能力。Docker 默认禁用危险能力:
|
||||
|
||||
| 能力 | 说明 | 默认状态 |
|
||||
|------|------|---------|
|
||||
| `CAP_NET_ADMIN` | 网络管理 | ❌ 禁用 |
|
||||
| `CAP_SYS_ADMIN` | 系统管理 | ❌ 禁用 |
|
||||
| `CAP_SYS_PTRACE` | 进程追踪 | ❌ 禁用 |
|
||||
| `CAP_CHOWN` | 更改文件所有者 | ✅ 启用 |
|
||||
| `CAP_NET_BIND_SERVICE` | 绑定低端口 | ✅ 启用 |
|
||||
|
||||
```bash
|
||||
# 删除所有能力,只添加需要的
|
||||
$ docker run --cap-drop=all --cap-add=NET_BIND_SERVICE myapp
|
||||
|
||||
# 查看容器的能力
|
||||
$ docker exec myapp cat /proc/1/status | grep Cap
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 镜像安全
|
||||
|
||||
### 使用可信镜像
|
||||
|
||||
```bash
|
||||
# ✅ 使用官方镜像
|
||||
$ docker pull nginx
|
||||
|
||||
# ✅ 使用经过验证的镜像
|
||||
$ docker pull bitnami/nginx
|
||||
|
||||
# ⚠️ 谨慎使用未知来源镜像
|
||||
$ docker pull randomuser/suspicious-image
|
||||
```
|
||||
|
||||
### 漏洞扫描
|
||||
|
||||
扫描镜像中的已知安全漏洞:
|
||||
|
||||
```bash
|
||||
# Docker Scout(官方工具)
|
||||
$ docker scout cves nginx:latest
|
||||
$ docker scout recommendations nginx:latest
|
||||
|
||||
# Trivy(开源工具)
|
||||
$ trivy image nginx:latest
|
||||
|
||||
# Snyk(商业工具)
|
||||
$ snyk container test nginx:latest
|
||||
```
|
||||
|
||||
### 镜像签名验证
|
||||
|
||||
使用 Docker Content Trust (DCT) 验证镜像来源:
|
||||
|
||||
```bash
|
||||
# 启用镜像签名验证
|
||||
$ export DOCKER_CONTENT_TRUST=1
|
||||
|
||||
# 此后的 pull/push 会验证签名
|
||||
$ docker pull myregistry/myimage:latest
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 运行时安全
|
||||
|
||||
### 1. 非 root 用户运行
|
||||
|
||||
> 笔者强调:这是最重要的安全实践之一。
|
||||
|
||||
```dockerfile
|
||||
FROM node:22-alpine
|
||||
|
||||
# 创建非 root 用户
|
||||
RUN addgroup -g 1001 appgroup && \
|
||||
adduser -u 1001 -G appgroup -D appuser
|
||||
|
||||
# 设置工作目录权限
|
||||
WORKDIR /app
|
||||
COPY --chown=appuser:appgroup . .
|
||||
|
||||
# 切换用户
|
||||
USER appuser
|
||||
|
||||
CMD ["node", "server.js"]
|
||||
```
|
||||
|
||||
或在运行时指定:
|
||||
|
||||
```bash
|
||||
$ docker run -u 1001:1001 myapp
|
||||
```
|
||||
|
||||
### 2. 只读文件系统
|
||||
|
||||
```bash
|
||||
# 根文件系统只读
|
||||
$ docker run --read-only myapp
|
||||
|
||||
# 需要写入的目录使用 tmpfs
|
||||
$ docker run --read-only --tmpfs /tmp --tmpfs /var/run myapp
|
||||
```
|
||||
|
||||
### 3. 禁用特权模式
|
||||
|
||||
```bash
|
||||
# ❌ 绝对不要在生产环境使用
|
||||
$ docker run --privileged myapp
|
||||
|
||||
# ✅ 只添加必要的能力
|
||||
$ docker run --cap-add=SYS_TIME myapp
|
||||
```
|
||||
|
||||
### 4. 限制资源
|
||||
|
||||
```bash
|
||||
$ docker run \
|
||||
-m 512m \ # 内存限制
|
||||
--cpus=1 \ # CPU 限制
|
||||
--pids-limit=100 \ # 进程数限制
|
||||
--ulimit nofile=1024:1024 \ # 文件描述符限制
|
||||
myapp
|
||||
```
|
||||
|
||||
### 5. 网络隔离
|
||||
|
||||
```bash
|
||||
# 禁用网络(适用于不需要网络的任务)
|
||||
$ docker run --network=none myapp
|
||||
|
||||
# 使用自定义网络隔离
|
||||
$ docker network create --internal isolated_net
|
||||
$ docker run --network=isolated_net myapp
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Dockerfile 安全实践
|
||||
|
||||
### 1. 使用精简基础镜像
|
||||
|
||||
```dockerfile
|
||||
# ✅ 好:使用精简镜像
|
||||
FROM node:22-alpine # ~50MB
|
||||
FROM gcr.io/distroless/nodejs # ~20MB
|
||||
|
||||
# ❌ 差:使用完整镜像
|
||||
FROM node:22 # ~1GB
|
||||
FROM ubuntu:24.04 # ~78MB
|
||||
```
|
||||
|
||||
### 2. 多阶段构建
|
||||
|
||||
```dockerfile
|
||||
# 构建阶段
|
||||
FROM node:22 AS builder
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
RUN npm install && npm run build
|
||||
|
||||
# 生产阶段(不包含开发依赖和源码)
|
||||
FROM node:22-alpine
|
||||
COPY --from=builder /app/dist /app
|
||||
USER node
|
||||
CMD ["node", "/app/server.js"]
|
||||
```
|
||||
|
||||
### 3. 不存储敏感信息
|
||||
|
||||
```dockerfile
|
||||
# ❌ 错误:敏感信息写入镜像
|
||||
ENV DB_PASSWORD=secret123
|
||||
COPY .env /app/
|
||||
|
||||
# ✅ 正确:运行时传入
|
||||
# docker run -e DB_PASSWORD=xxx 或使用 Docker Secrets
|
||||
```
|
||||
|
||||
### 4. 固定依赖版本
|
||||
|
||||
```dockerfile
|
||||
# ✅ 固定版本
|
||||
FROM node:22.12.0-alpine3.21
|
||||
RUN apk add --no-cache curl=8.5.0-r0
|
||||
|
||||
# ❌ 使用 latest
|
||||
FROM node:latest
|
||||
RUN apk add curl
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 安全扫描清单
|
||||
|
||||
部署前检查:
|
||||
|
||||
| 检查项 | 命令/方法 |
|
||||
|--------|----------|
|
||||
| 漏洞扫描 | `docker scout cves` 或 `trivy` |
|
||||
| 非 root 运行 | 检查 Dockerfile 中的 `USER` |
|
||||
| 资源限制 | 检查 `-m`, `--cpus` 参数 |
|
||||
| 只读文件系统 | 检查 `--read-only` |
|
||||
| 无特权模式 | 确认没有 `--privileged` |
|
||||
| 最小能力 | 检查 `--cap-drop=all` |
|
||||
| 网络隔离 | 检查网络配置 |
|
||||
| 敏感信息 | 确认无硬编码密码 |
|
||||
|
||||
---
|
||||
|
||||
## 高级安全方案
|
||||
|
||||
### Seccomp 系统调用过滤
|
||||
|
||||
限制容器可以使用的系统调用:
|
||||
|
||||
```bash
|
||||
$ docker run --security-opt seccomp=/path/to/profile.json myapp
|
||||
```
|
||||
|
||||
### AppArmor / SELinux
|
||||
|
||||
使用强制访问控制:
|
||||
|
||||
```bash
|
||||
$ docker run --security-opt apparmor=docker-default myapp
|
||||
```
|
||||
|
||||
### 安全容器(gVisor / Kata)
|
||||
|
||||
需要更强隔离时:
|
||||
|
||||
```bash
|
||||
# 使用 gVisor 运行时
|
||||
$ docker run --runtime=runsc myapp
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 软件供应链安全
|
||||
|
||||
随着软件供应链攻击日益频繁,仅保障运行时安全已不足够。
|
||||
|
||||
### 1. SBOM (软件物料清单)
|
||||
|
||||
SBOM 类似于食品的配料表,列出了容器镜像中包含的所有软件包及其版本。
|
||||
|
||||
- **生成 SBOM**: 使用 `docker buildx build --sbom` 或 `docker scout sbom`。
|
||||
- **管理 SBOM**: 确保持续监控 SBOM 中的组件是否存在新披露的漏洞。
|
||||
|
||||
### 2. 镜像签名 (Sigstore / Notary v2)
|
||||
|
||||
确保镜像在构建后未被篡改,且确实来自可信的发布者。
|
||||
|
||||
- **Cosign**: Sigstore 项目的一部分,用于签署和验证容器镜像。
|
||||
```bash
|
||||
# 签署镜像
|
||||
$ cosign sign --key cosign.key myimage:tag
|
||||
|
||||
# 验证镜像
|
||||
$ cosign verify --key cosign.pub myimage:tag
|
||||
```
|
||||
|
||||
### 3. SLSA (Supply-chain Levels for Software Artifacts)
|
||||
|
||||
遵循 SLSA 框架,确保构建过程的完整性,例如使用 GitHub Actions 等受控环境进行构建,而非在开发者本地机器上构建发布。
|
||||
|
||||
---
|
||||
|
||||
## 本章小结
|
||||
|
||||
| 安全措施 | 重要程度 | 实现方式 |
|
||||
|---------|---------|---------|
|
||||
| 非 root 运行 | ⭐⭐⭐ | `USER` 指令 |
|
||||
| 漏洞扫描 | ⭐⭐⭐ | `docker scout`, `trivy` |
|
||||
| 资源限制 | ⭐⭐⭐ | `-m`, `--cpus` |
|
||||
| 只读文件系统 | ⭐⭐ | `--read-only` |
|
||||
| 最小能力 | ⭐⭐ | `--cap-drop=all` |
|
||||
| 镜像签名 | ⭐⭐ | Docker Content Trust |
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [命名空间](../13_implementation/namespace.md):隔离机制详解
|
||||
- [控制组](../13_implementation/cgroups.md):资源限制详解
|
||||
- [最佳实践](../15_appendix/best_practices.md):Dockerfile 安全配置
|
||||
@@ -1,8 +0,0 @@
|
||||
# 容器编排
|
||||
|
||||
本章将介绍容器编排相关的技术与工具。
|
||||
|
||||
* [Etcd 项目](etcd/README.md)
|
||||
* [Kubernetes - 开源容器编排引擎](kubernetes/README.md)
|
||||
* [部署 Kubernetes](setup/README.md)
|
||||
* [Kubernetes 命令行 kubectl](kubectl/README.md)
|
||||
@@ -1,61 +0,0 @@
|
||||
# Kubernetes 高级特性
|
||||
|
||||
掌握了 Kubernetes 的核心概念(Pod, Service, Deployment)后,我们需要了解更多高级特性以构建生产级应用。
|
||||
|
||||
## Helm - 包管理工具
|
||||
|
||||
[Helm](https://helm.sh/) 被称为 Kubernetes 的包管理器(类似于 Linux 的 apt/yum)。它将一组 Kubernetes 资源定义文件打包为一个 **Chart**。
|
||||
|
||||
* **安装应用**:`helm install my-release bitnami/mysql`
|
||||
* **版本管理**:轻松回滚应用的发布版本。
|
||||
* **模板化**:支持复杂的应用部署逻辑配置。
|
||||
|
||||
## Ingress - 服务的入口
|
||||
|
||||
Service 虽然提供了负载均衡,但通常是 4 层(TCP/UDP)。**Ingress** 提供了 7 层(HTTP/HTTPS)路由能力,充当集群的网关。
|
||||
|
||||
* **域名路由**:基于 Host 将请求转发不同服务 (api.example.com -> api-svc, web.example.com -> web-svc)。
|
||||
* **路径路由**:基于 Path 将请求转发 (/api -> api-svc, / -> web-svc)。
|
||||
* **SSL/TLS**:集中管理证书。
|
||||
|
||||
常见的 Ingress Controller有 Nginx Ingress Controller, Traefik, Istio Gateway 等。
|
||||
|
||||
## Persistent Volume (PV) 与 StorageClass
|
||||
|
||||
容器内的文件是临时的。对于有状态应用(如数据库),需要持久化存储。
|
||||
|
||||
* **PVC (Persistent Volume Claim)**:用户申请存储的声明。
|
||||
* **PV (Persistent Volume)**:实际的存储资源(NFS, AWS EBS, Ceph 等)。
|
||||
* **StorageClass**:定义存储类,支持动态创建 PV。
|
||||
|
||||
## Horizontal Pod Autoscaling (HPA)
|
||||
|
||||
HPA 根据 CPU 利用率或其他指标(如内存、自定义指标)自动扩缩 Deployment 或 ReplicaSet 中的 Pod 数量。
|
||||
|
||||
```yaml
|
||||
apiVersion: autoscaling/v2
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: php-apache
|
||||
spec:
|
||||
scaleTargetRef:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: php-apache
|
||||
minReplicas: 1
|
||||
maxReplicas: 10
|
||||
metrics:
|
||||
- type: Resource
|
||||
resource:
|
||||
name: cpu
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: 50
|
||||
```
|
||||
|
||||
## ConfigMap 与 Secret
|
||||
|
||||
* **ConfigMap**:存储非机密的配置数据(配置文件、环境变量)。
|
||||
* **Secret**:存储机密数据(密码、Token、证书),在 Etcd 中加密存储。
|
||||
|
||||
通过将配置与镜像分离,保证了容器的可移植性。
|
||||
@@ -1,83 +0,0 @@
|
||||
# Kubernetes 简介
|
||||
|
||||

|
||||
|
||||
## 什么是 Kubernetes
|
||||
|
||||
Kubernetes(常简称为 K8s)是 Google 开源的容器编排引擎。如果说 Docker 解决了"如何打包和运送集装箱"的问题,那么 Kubernetes 解决的就是"如何管理海量集装箱的调度、运行和维护"的问题。
|
||||
|
||||
它不仅仅是一个编排系统,更是一个**云原生应用操作系统**。
|
||||
|
||||
> **名字由来**:Kubernetes 在希腊语中意为"舵手"或"飞行员"。K8s 是因为 k 和 s 之间有 8 个字母。
|
||||
|
||||
---
|
||||
|
||||
## 为什么需要 Kubernetes
|
||||
|
||||
当我们在单机运行几个容器时,Docker Compose 就足够了。但在生产环境中,我们需要面对:
|
||||
|
||||
- **多主机调度**:容器应该运行在哪台机器上?
|
||||
- **自动恢复**:容器崩溃了怎么办?节点挂了怎么办?
|
||||
- **服务发现**:容器 IP 变了,其他服务怎么找到它?
|
||||
- **负载均衡**:流量大了,如何分发给多个副本?
|
||||
- **滚动更新**:如何不中断服务升级应用?
|
||||
|
||||
Kubernetes 完美解决了这些问题。
|
||||
|
||||
---
|
||||
|
||||
## 核心概念
|
||||
|
||||
### Pod (豆荚)
|
||||
Kubernetes 的最小调度单位。一个 Pod 可以包含一个或多个紧密协作的容器(共享网络和存储)。就像豌豆荚里的豌豆一样。
|
||||
|
||||
### Node (节点)
|
||||
运行 Pod 的物理机或虚拟机。
|
||||
|
||||
### Deployment (部署)
|
||||
定义应用的期望状态(如:需要 3 个副本,镜像版本为 v1)。K8s 会持续确保当前状态符合期望状态。
|
||||
|
||||
### Service (服务)
|
||||
定义一组 Pod 的访问策略。提供稳定的 Cluster IP 和 DNS 名称,负责负载均衡。
|
||||
|
||||
### Namespace (命名空间)
|
||||
用于多租户资源隔离。
|
||||
|
||||
---
|
||||
|
||||
## Docker 用户如何过渡
|
||||
|
||||
如果你已经熟悉 Docker,学习 K8s 会很容易:
|
||||
|
||||
| Docker 概念 | Kubernetes 概念 | 说明 |
|
||||
|------------|----------------|------|
|
||||
| Container | Pod | K8s 增加了一层 Pod 包装 |
|
||||
| Volume | PersistentVolume | K8s 的存储更加抽象和强大 |
|
||||
| Network | Service/Ingress| K8s 的网络模型更扁平 |
|
||||
| Compose | Deployment + Service | 声明式配置的理念是一致的 |
|
||||
|
||||
---
|
||||
|
||||
## 架构
|
||||
|
||||
Kubernetes 也是 C/S 架构,由 **Master (控制平面)** 和 **Worker (工作节点)** 组成:
|
||||
|
||||
- **Control Plane**:负责决策(API Server, Scheduler, Controller Manager, etcd)
|
||||
- **Worker Node**:负责干活(Kubelet, Kube-proxy, Container Runtime)
|
||||
|
||||
---
|
||||
|
||||
## 学习建议
|
||||
|
||||
Kubernetes 的学习曲线较陡峭。建议的学习路径:
|
||||
1. **理解基本概念**:Pod, Deployment, Service
|
||||
2. **动手实践**:使用 Minikube 或 Kind 在本地搭建集群
|
||||
3. **部署应用**:编写 YAML 部署一个无状态应用
|
||||
4. **深入原理**:网络模型、存储机制、调度算法
|
||||
|
||||
---
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [Minikube 安装](../kubernetes/setup/README.md):本地体验 K8s
|
||||
- [Kubernetes 官网](https://kubernetes.io/):官方文档
|
||||
@@ -1,99 +0,0 @@
|
||||
# Kubernetes 实战练习
|
||||
|
||||
本章将通过一个具体的案例:部署一个 Nginx 网站,并为其配置 Service 和 Ingress,来串联前面学到的知识。
|
||||
|
||||
## 目标
|
||||
|
||||
1. 部署一个 Nginx Deployment。
|
||||
2. 创建一个 Service 暴露 Nginx。
|
||||
3. (可选)通过 Ingress 访问服务。
|
||||
|
||||
## 步骤 1:创建 Deployment
|
||||
|
||||
创建一个名为 `nginx-deployment.yaml` 的文件:
|
||||
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: nginx-deployment
|
||||
labels:
|
||||
app: nginx
|
||||
spec:
|
||||
replicas: 2
|
||||
selector:
|
||||
matchLabels:
|
||||
app: nginx
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:1.24
|
||||
ports:
|
||||
- containerPort: 80
|
||||
```
|
||||
|
||||
应用配置:
|
||||
|
||||
```bash
|
||||
kubectl apply -f nginx-deployment.yaml
|
||||
```
|
||||
|
||||
## 步骤 2:创建 Service
|
||||
|
||||
创建一个名为 `nginx-service.yaml` 的文件:
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: nginx-service
|
||||
spec:
|
||||
selector:
|
||||
app: nginx
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 80
|
||||
targetPort: 80
|
||||
type: NodePort # 使用 NodePort 方便本地测试
|
||||
```
|
||||
|
||||
应用配置:
|
||||
|
||||
```bash
|
||||
kubectl apply -f nginx-service.yaml
|
||||
```
|
||||
|
||||
查看分配的端口:
|
||||
|
||||
```bash
|
||||
kubectl get svc nginx-service
|
||||
```
|
||||
|
||||
如果输出端口是 `80:30080/TCP`,你可以通过 `http://<NodeIP>:30080` 访问 Nginx。
|
||||
|
||||
## 步骤 3:模拟滚动更新 (Rolling Update)
|
||||
|
||||
修改 `nginx-deployment.yaml`,将镜像版本改为 `nginx:latest`。
|
||||
|
||||
```bash
|
||||
kubectl apply -f nginx-deployment.yaml
|
||||
```
|
||||
|
||||
观察更新过程:
|
||||
|
||||
```bash
|
||||
kubectl rollout status deployment/nginx-deployment
|
||||
```
|
||||
|
||||
## 步骤 4:清理资源
|
||||
|
||||
练习结束后,记得清理资源:
|
||||
|
||||
```bash
|
||||
kubectl delete -f nginx-service.yaml
|
||||
kubectl delete -f nginx-deployment.yaml
|
||||
```
|
||||
@@ -1,11 +0,0 @@
|
||||
# 部署 Kubernetes
|
||||
|
||||
目前,Kubernetes 支持在多种环境下使用,包括本地主机(Ubuntu、Debian、CentOS、Fedora 等)、云服务([腾讯云](https://cloud.tencent.com/act/cps/redirect?redirect=10058&cps_key=3a5255852d5db99dcd5da4c72f05df61)、[阿里云](https://www.aliyun.com/product/kubernetes?source=5176.11533457&userCode=8lx5zmtu&type=copy)、[百度云](https://cloud.baidu.com/product/cce.html) 等)。
|
||||
|
||||
你可以使用以下几种方式部署 Kubernetes:
|
||||
|
||||
* kubeadm
|
||||
* docker-desktop
|
||||
* k3s
|
||||
|
||||
接下来的小节会对以上几种方式进行详细介绍。
|
||||
@@ -1,19 +0,0 @@
|
||||
# Docker Desktop 启用 Kubernetes
|
||||
|
||||
使用 Docker Desktop 可以很方便的启用 Kubernetes。
|
||||
|
||||
## 启用 Kubernetes
|
||||
|
||||
在 Docker Desktop 设置页面,点击 `Kubernetes`,选择 `Enable Kubernetes`,稍等片刻,看到左下方 `Kubernetes` 变为 `running`,Kubernetes 启动成功。
|
||||
|
||||

|
||||
|
||||
> 注意:Kubernetes 的镜像存储在 `registry.k8s.io`,如果国内网络无法直接访问,可以在 Docker Desktop 配置中的 `Docker Engine` 处配置镜像加速器,或者利用国内云服务商的镜像仓库手动拉取镜像并 retag。
|
||||
|
||||
## 测试
|
||||
|
||||
```bash
|
||||
$ kubectl version
|
||||
```
|
||||
|
||||
如果正常输出信息,则证明 Kubernetes 成功启动。
|
||||
@@ -1,52 +0,0 @@
|
||||
# K3s - 轻量级 Kubernetes
|
||||
|
||||
[K3s](https://k3s.io/) 是一个轻量级的 Kubernetes 发行版,由 Rancher Labs 开发。它专为边缘计算、物联网、CI、ARM 等资源受限的环境设计。K3s 被打包为单个二进制文件,只有不到 100MB,但通过了 CNCF 的一致性测试。
|
||||
|
||||
## 核心特性
|
||||
|
||||
* **轻量级**:移除过时的、非必须的 Kubernetes 功能(如传统的云提供商插件),使用 SQLite 作为默认数据存储(也支持 Etcd/MySQL/Postgres)。
|
||||
* **单一二进制**:所有组件(API Server, Controller Manager, Scheduler, Kubelet, Kube-proxy)打包在一个进程中运行。
|
||||
* **开箱即用**:内置 Helm Controller、Traefik Ingress controller、ServiceLB、Local-Path-Provisioner。
|
||||
* **安全**:默认启用安全配置,基于 TLS 通信。
|
||||
|
||||
## 安装
|
||||
|
||||
### 脚本安装(Linux)
|
||||
|
||||
K3s 提供了极为便捷的安装脚本:
|
||||
|
||||
```bash
|
||||
curl -sfL https://get.k3s.io | sh -
|
||||
```
|
||||
|
||||
安装完成后,K3s 会自动启动并配置好 `systemd` 服务。
|
||||
|
||||
### 查看状态
|
||||
|
||||
```bash
|
||||
sudo k3s kubectl get nodes
|
||||
```
|
||||
|
||||
输出类似:
|
||||
```
|
||||
NAME STATUS ROLES AGE VERSION
|
||||
k3s-master Ready control-plane,master 1m v1.28.2+k3s1
|
||||
```
|
||||
|
||||
## 快速使用
|
||||
|
||||
K3s 内置了 `kubectl` 命令(通过 `k3s kubectl` 调用),为了方便,通常会建立别名或配置 `KUBECONFIG`。
|
||||
|
||||
```bash
|
||||
# 读取 K3s 的配置文件
|
||||
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
|
||||
|
||||
# 现在可以直接使用 kubectl
|
||||
kubectl get pods -A
|
||||
```
|
||||
|
||||
## 清理卸载
|
||||
|
||||
```bash
|
||||
/usr/local/bin/k3s-uninstall.sh
|
||||
```
|
||||
@@ -1,81 +0,0 @@
|
||||
# Kind - Kubernetes IN Docker
|
||||
|
||||
[Kind](https://kind.sigs.k8s.io/) (Kubernetes in Docker) 是一个使用 Docker 容器作为节点运行本地 Kubernetes 集群的工具。主要用于测试 Kubernetes 本身,也非常适合本地开发和 CI 环境。
|
||||
|
||||
## 为什么选择 Kind
|
||||
|
||||
* **轻量便捷**:只要有 Docker 环境即可,无需额外虚拟机。
|
||||
* **多集群支持**:可以轻松在本地启动多个集群。
|
||||
* **多版本支持**:支持指定 Kubernetes 版本进行测试。
|
||||
* **HA 支持**:支持模拟高可用集群(多 Control Plane)。
|
||||
|
||||
## 安装 Kind
|
||||
|
||||
### macOS
|
||||
|
||||
```bash
|
||||
brew install kind
|
||||
```
|
||||
|
||||
### Linux / Windows (WSL2)
|
||||
|
||||
可以下载二进制文件:
|
||||
|
||||
```bash
|
||||
# Linux AMD64
|
||||
curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.20.0/kind-linux-amd64
|
||||
chmod +x ./kind
|
||||
sudo mv ./kind /usr/local/bin/kind
|
||||
```
|
||||
|
||||
## 创建集群
|
||||
|
||||
最简单的创建方式:
|
||||
|
||||
```bash
|
||||
kind create cluster
|
||||
```
|
||||
|
||||
指定集群名称:
|
||||
|
||||
```bash
|
||||
kind create cluster --name my-cluster
|
||||
```
|
||||
|
||||
## 与集群交互
|
||||
|
||||
Kind 会自动将 kubeconfig 合并到 `~/.kube/config`。
|
||||
|
||||
```bash
|
||||
kubectl cluster-info --context kind-kind
|
||||
kubectl get nodes
|
||||
```
|
||||
|
||||
## 高级用法:配置集群
|
||||
|
||||
创建一个 `kind-config.yaml` 来定制集群,例如映射端口到宿主机:
|
||||
|
||||
```yaml
|
||||
kind: Cluster
|
||||
apiVersion: kind.x-k8s.io/v1alpha4
|
||||
nodes:
|
||||
- role: control-plane
|
||||
extraPortMappings:
|
||||
- containerPort: 80
|
||||
hostPort: 8080
|
||||
protocol: TCP
|
||||
- role: worker
|
||||
- role: worker
|
||||
```
|
||||
|
||||
应用配置:
|
||||
|
||||
```bash
|
||||
kind create cluster --config kind-config.yaml
|
||||
```
|
||||
|
||||
## 删除集群
|
||||
|
||||
```bash
|
||||
kind delete cluster
|
||||
```
|
||||
@@ -1,399 +0,0 @@
|
||||
# 使用 kubeadm 部署 kubernetes(CRI 使用 containerd)
|
||||
|
||||
`kubeadm` 提供了 `kubeadm init` 以及 `kubeadm join` 这两个命令作为快速创建 `kubernetes` 集群的最佳实践。
|
||||
|
||||
> **版本说明**:Kubernetes 版本更新较快(约每 4 个月一个新版本),本文档基于 Kubernetes 1.35 编写。请访问 [Kubernetes 官方发布页](https://kubernetes.io/releases/) 获取最新版本信息。
|
||||
|
||||
## 安装 containerd
|
||||
|
||||
参考 [安装 Docker](../../install) 一节添加 apt/yum 源,之后执行如下命令。
|
||||
|
||||
```bash
|
||||
# debian 系
|
||||
$ sudo apt install containerd.io
|
||||
|
||||
# rhel 系
|
||||
$ sudo yum install containerd.io
|
||||
```
|
||||
|
||||
## 配置 containerd
|
||||
|
||||
新建 `/etc/systemd/system/cri-containerd.service` 文件
|
||||
|
||||
```
|
||||
[Unit]
|
||||
Description=containerd container runtime for kubernetes
|
||||
Documentation=https://containerd.io
|
||||
After=network.target local-fs.target
|
||||
|
||||
[Service]
|
||||
ExecStartPre=-/sbin/modprobe overlay
|
||||
ExecStart=/usr/bin/containerd --config /etc/cri-containerd/config.toml
|
||||
|
||||
Type=notify
|
||||
Delegate=yes
|
||||
KillMode=process
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
# Having non-zero Limit*s causes performance problems due to accounting overhead
|
||||
# in the kernel. We recommend using cgroups to do container-local accounting.
|
||||
LimitNPROC=infinity
|
||||
LimitCORE=infinity
|
||||
LimitNOFILE=infinity
|
||||
# Comment TasksMax if your systemd version does not supports it.
|
||||
# Only systemd 226 and above support this version.
|
||||
TasksMax=infinity
|
||||
OOMScoreAdjust=-999
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
新建 `/etc/cri-containerd/config.toml` containerd 配置文件
|
||||
|
||||
```toml
|
||||
version = 2
|
||||
# persistent data location
|
||||
root = "/var/lib/cri-containerd"
|
||||
# runtime state information
|
||||
state = "/run/cri-containerd"
|
||||
plugin_dir = ""
|
||||
disabled_plugins = []
|
||||
required_plugins = []
|
||||
# set containerd's OOM score
|
||||
oom_score = 0
|
||||
|
||||
[grpc]
|
||||
address = "/run/cri-containerd/cri-containerd.sock"
|
||||
tcp_address = ""
|
||||
tcp_tls_cert = ""
|
||||
tcp_tls_key = ""
|
||||
# socket uid
|
||||
uid = 0
|
||||
# socket gid
|
||||
gid = 0
|
||||
max_recv_message_size = 16777216
|
||||
max_send_message_size = 16777216
|
||||
|
||||
[debug]
|
||||
address = ""
|
||||
format = "json"
|
||||
uid = 0
|
||||
gid = 0
|
||||
level = ""
|
||||
|
||||
[metrics]
|
||||
address = "127.0.0.1:1338"
|
||||
grpc_histogram = false
|
||||
|
||||
[cgroup]
|
||||
path = ""
|
||||
|
||||
[timeouts]
|
||||
"io.containerd.timeout.shim.cleanup" = "5s"
|
||||
"io.containerd.timeout.shim.load" = "5s"
|
||||
"io.containerd.timeout.shim.shutdown" = "3s"
|
||||
"io.containerd.timeout.task.state" = "2s"
|
||||
|
||||
[plugins]
|
||||
[plugins."io.containerd.gc.v1.scheduler"]
|
||||
pause_threshold = 0.02
|
||||
deletion_threshold = 0
|
||||
mutation_threshold = 100
|
||||
schedule_delay = "0s"
|
||||
startup_delay = "100ms"
|
||||
[plugins."io.containerd.grpc.v1.cri"]
|
||||
disable_tcp_service = true
|
||||
stream_server_address = "127.0.0.1"
|
||||
stream_server_port = "0"
|
||||
stream_idle_timeout = "4h0m0s"
|
||||
enable_selinux = false
|
||||
selinux_category_range = 1024
|
||||
sandbox_image = "registry.cn-hangzhou.aliyuncs.com/google_containers/pause:3.10"
|
||||
stats_collect_period = 10
|
||||
# systemd_cgroup = false
|
||||
enable_tls_streaming = false
|
||||
max_container_log_line_size = 16384
|
||||
disable_cgroup = false
|
||||
disable_apparmor = false
|
||||
restrict_oom_score_adj = false
|
||||
max_concurrent_downloads = 3
|
||||
disable_proc_mount = false
|
||||
unset_seccomp_profile = ""
|
||||
tolerate_missing_hugetlb_controller = true
|
||||
disable_hugetlb_controller = true
|
||||
ignore_image_defined_volumes = false
|
||||
[plugins."io.containerd.grpc.v1.cri".containerd]
|
||||
snapshotter = "overlayfs"
|
||||
default_runtime_name = "runc"
|
||||
no_pivot = false
|
||||
disable_snapshot_annotations = false
|
||||
discard_unpacked_layers = false
|
||||
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes]
|
||||
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
|
||||
runtime_type = "io.containerd.runc.v2"
|
||||
pod_annotations = []
|
||||
container_annotations = []
|
||||
privileged_without_host_devices = false
|
||||
base_runtime_spec = ""
|
||||
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
|
||||
# SystemdCgroup enables systemd cgroups.
|
||||
SystemdCgroup = true
|
||||
# BinaryName is the binary name of the runc binary.
|
||||
# BinaryName = "runc"
|
||||
# BinaryName = "crun"
|
||||
# NoPivotRoot disables pivot root when creating a container.
|
||||
# NoPivotRoot = false
|
||||
|
||||
# NoNewKeyring disables new keyring for the container.
|
||||
# NoNewKeyring = false
|
||||
|
||||
# ShimCgroup places the shim in a cgroup.
|
||||
# ShimCgroup = ""
|
||||
|
||||
# IoUid sets the I/O's pipes uid.
|
||||
# IoUid = 0
|
||||
|
||||
# IoGid sets the I/O's pipes gid.
|
||||
# IoGid = 0
|
||||
|
||||
# Root is the runc root directory.
|
||||
Root = ""
|
||||
|
||||
# CriuPath is the criu binary path.
|
||||
# CriuPath = ""
|
||||
|
||||
# CriuImagePath is the criu image path
|
||||
# CriuImagePath = ""
|
||||
|
||||
# CriuWorkPath is the criu work path.
|
||||
# CriuWorkPath = ""
|
||||
[plugins."io.containerd.grpc.v1.cri".cni]
|
||||
bin_dir = "/opt/cni/bin"
|
||||
conf_dir = "/etc/cni/net.d"
|
||||
max_conf_num = 1
|
||||
conf_template = ""
|
||||
[plugins."io.containerd.grpc.v1.cri".registry]
|
||||
config_path = "/etc/cri-containerd/certs.d"
|
||||
[plugins."io.containerd.grpc.v1.cri".registry.headers]
|
||||
# Foo = ["bar"]
|
||||
[plugins."io.containerd.grpc.v1.cri".image_decryption]
|
||||
key_model = ""
|
||||
[plugins."io.containerd.grpc.v1.cri".x509_key_pair_streaming]
|
||||
tls_cert_file = ""
|
||||
tls_key_file = ""
|
||||
[plugins."io.containerd.internal.v1.opt"]
|
||||
path = "/opt/cri-containerd"
|
||||
[plugins."io.containerd.internal.v1.restart"]
|
||||
interval = "10s"
|
||||
[plugins."io.containerd.metadata.v1.bolt"]
|
||||
content_sharing_policy = "shared"
|
||||
[plugins."io.containerd.monitor.v1.cgroups"]
|
||||
no_prometheus = false
|
||||
[plugins."io.containerd.runtime.v2.task"]
|
||||
platforms = ["linux/amd64"]
|
||||
[plugins."io.containerd.service.v1.diff-service"]
|
||||
default = ["walking"]
|
||||
[plugins."io.containerd.snapshotter.v1.devmapper"]
|
||||
root_path = ""
|
||||
pool_name = ""
|
||||
base_image_size = ""
|
||||
async_remove = false
|
||||
```
|
||||
|
||||
## 安装 **kubelet** **kubeadm** **kubectl** **cri-tools** **kubernetes-cni**
|
||||
|
||||
### Ubuntu/Debian
|
||||
|
||||
```bash
|
||||
$ apt-get update && apt-get install -y apt-transport-https
|
||||
$ curl https://mirrors.aliyun.com/kubernetes/apt/doc/apt-key.gpg | apt-key add -
|
||||
|
||||
$ cat <<EOF | sudo tee /etc/apt/sources.list.d/kubernetes.list
|
||||
deb https://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main
|
||||
EOF
|
||||
|
||||
$ apt-get update
|
||||
$ apt-get install -y kubelet kubeadm kubectl
|
||||
```
|
||||
|
||||
### CentOS/Fedora
|
||||
|
||||
```bash
|
||||
$ cat <<EOF | sudo tee /etc/yum.repos.d/kubernetes.repo
|
||||
[kubernetes]
|
||||
name=Kubernetes
|
||||
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/
|
||||
enabled=1
|
||||
gpgcheck=1
|
||||
repo_gpgcheck=1
|
||||
gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
|
||||
EOF
|
||||
|
||||
$ sudo yum install -y kubelet kubeadm kubectl
|
||||
```
|
||||
|
||||
## 修改内核的运行参数
|
||||
|
||||
```bash
|
||||
$ cat <<EOF | sudo tee /etc/sysctl.d/99-kubernetes-cri.conf
|
||||
net.bridge.bridge-nf-call-iptables = 1
|
||||
net.ipv4.ip_forward = 1
|
||||
net.bridge.bridge-nf-call-ip6tables = 1
|
||||
EOF
|
||||
|
||||
# 应用配置
|
||||
$ sysctl --system
|
||||
```
|
||||
|
||||
## 配置 kubelet
|
||||
|
||||
### 修改 `kubelet.service`
|
||||
|
||||
`/etc/systemd/system/kubelet.service.d/10-proxy-ipvs.conf` 写入以下内容
|
||||
|
||||
```bash
|
||||
# 启用 ipvs 相关内核模块
|
||||
[Service]
|
||||
ExecStartPre=-/sbin/modprobe ip_vs
|
||||
ExecStartPre=-/sbin/modprobe ip_vs_rr
|
||||
ExecStartPre=-/sbin/modprobe ip_vs_wrr
|
||||
ExecStartPre=-/sbin/modprobe ip_vs_sh
|
||||
```
|
||||
|
||||
执行以下命令应用配置。
|
||||
|
||||
```bash
|
||||
$ sudo systemctl daemon-reload
|
||||
```
|
||||
|
||||
## 部署
|
||||
|
||||
### master
|
||||
|
||||
```bash
|
||||
$ systemctl enable cri-containerd
|
||||
|
||||
$ systemctl start cri-containerd
|
||||
|
||||
$ sudo kubeadm init \
|
||||
--image-repository registry.cn-hangzhou.aliyuncs.com/google_containers \
|
||||
--pod-network-cidr 10.244.0.0/16 \
|
||||
--cri-socket /run/cri-containerd/cri-containerd.sock \
|
||||
--v 5 \
|
||||
--ignore-preflight-errors=all
|
||||
```
|
||||
|
||||
* `--pod-network-cidr 10.244.0.0/16` 参数与后续 CNI 插件有关,这里以 `flannel` 为例,若后续部署其他类型的网络插件请更改此参数。
|
||||
|
||||
> 执行可能出现错误,例如缺少依赖包,根据提示安装即可。
|
||||
|
||||
执行成功会输出
|
||||
|
||||
```bash
|
||||
...
|
||||
[addons] Applied essential addon: CoreDNS
|
||||
I1116 12:35:13.270407 86677 request.go:538] Throttling request took 181.409184ms, request: POST:https://192.168.199.100:6443/api/v1/namespaces/kube-system/serviceaccounts
|
||||
I1116 12:35:13.470292 86677 request.go:538] Throttling request took 186.088112ms, request: POST:https://192.168.199.100:6443/api/v1/namespaces/kube-system/configmaps
|
||||
[addons] Applied essential addon: kube-proxy
|
||||
|
||||
Your Kubernetes control-plane has initialized successfully!
|
||||
|
||||
To start using your cluster, you need to run the following as a regular user:
|
||||
|
||||
mkdir -p $HOME/.kube
|
||||
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
|
||||
sudo chown $(id -u):$(id -g) $HOME/.kube/config
|
||||
|
||||
You should now deploy a pod network to the cluster.
|
||||
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
|
||||
https://kubernetes.io/docs/concepts/cluster-administration/addons/
|
||||
|
||||
Then you can join any number of worker nodes by running the following on each as root:
|
||||
|
||||
kubeadm join 192.168.199.100:6443 --token cz81zt.orsy9gm9v649e5lf \
|
||||
--discovery-token-ca-cert-hash sha256:5edb316fd0d8ea2792cba15cdf1c899a366f147aa03cba52d4e5c5884ad836fe
|
||||
```
|
||||
|
||||
### node 工作节点
|
||||
|
||||
在 **另一主机** 重复 **部署** 小节以前的步骤,安装配置好 kubelet。根据提示,加入到集群。
|
||||
|
||||
```bash
|
||||
$ systemctl enable cri-containerd
|
||||
|
||||
$ systemctl start cri-containerd
|
||||
|
||||
$ kubeadm join 192.168.199.100:6443 \
|
||||
--token cz81zt.orsy9gm9v649e5lf \
|
||||
--discovery-token-ca-cert-hash sha256:5edb316fd0d8ea2792cba15cdf1c899a366f147aa03cba52d4e5c5884ad836fe \
|
||||
--cri-socket /run/cri-containerd/cri-containerd.sock
|
||||
```
|
||||
|
||||
## 查看服务
|
||||
|
||||
所有服务启动后,通过 `crictl` 查看本地实际运行的容器。这些服务大概分为三类:主节点服务、工作节点服务和其它服务。
|
||||
|
||||
```bash
|
||||
CONTAINER_RUNTIME_ENDPOINT=/run/cri-containerd/cri-containerd.sock crictl ps -a
|
||||
```
|
||||
|
||||
### 主节点服务
|
||||
|
||||
* `apiserver` 是整个系统的对外接口,提供 RESTful 方式供客户端和其它组件调用;
|
||||
|
||||
* `scheduler` 负责对资源进行调度,分配某个 pod 到某个节点上;
|
||||
|
||||
* `controller-manager` 负责管理控制器,包括 endpoint-controller(刷新服务和 pod 的关联信息)和 replication-controller(维护某个 pod 的复制为配置的数值)。
|
||||
|
||||
### 工作节点服务
|
||||
|
||||
* `proxy` 为 pod 上的服务提供访问的代理。
|
||||
|
||||
### 其它服务
|
||||
|
||||
* Etcd 是所有状态的存储数据库;
|
||||
|
||||
## 使用
|
||||
|
||||
将 `/etc/kubernetes/admin.conf` 复制到 `~/.kube/config`
|
||||
|
||||
执行 `$ kubectl get all -A` 查看启动的服务。
|
||||
|
||||
由于未部署 CNI 插件,CoreDNS 未正常启动。如何使用 Kubernetes,请参考后续章节。
|
||||
|
||||
## 部署 CNI
|
||||
|
||||
这里以 `flannel` 为例进行介绍。
|
||||
|
||||
### flannel
|
||||
|
||||
检查 podCIDR 设置
|
||||
|
||||
```bash
|
||||
$ kubectl get node -o yaml | grep CIDR
|
||||
|
||||
# 输出
|
||||
podCIDR: 10.244.0.0/16
|
||||
podCIDRs:
|
||||
```
|
||||
|
||||
```bash
|
||||
$ kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/v0.26.1/Documentation/kube-flannel.yml
|
||||
```
|
||||
|
||||
## master 节点默认不能运行 pod
|
||||
|
||||
如果用 `kubeadm` 部署一个单节点集群,默认情况下无法使用,请执行以下命令解除限制
|
||||
|
||||
```bash
|
||||
$ kubectl taint nodes --all node-role.kubernetes.io/master-
|
||||
|
||||
# 恢复默认值
|
||||
# $ kubectl taint nodes NODE_NAME node-role.kubernetes.io/master=true:NoSchedule
|
||||
```
|
||||
|
||||
## 参考文档
|
||||
|
||||
* [官方文档](https://kubernetes.io/zh/docs/setup/production-environment/tools/kubeadm/install-kubeadm/)
|
||||
* [Container runtimes](https://kubernetes.io/docs/setup/production-environment/container-runtimes/#containerd)
|
||||
@@ -1,7 +0,0 @@
|
||||
# 容器生态
|
||||
|
||||
本章将介绍容器生态圈的相关项目与服务。
|
||||
|
||||
* [Fedora CoreOS](coreos/README.md)
|
||||
* [容器与云计算](cloud/README.md)
|
||||
* [podman - 下一代 Linux 容器工具](podman/README.md)
|
||||
@@ -1,11 +0,0 @@
|
||||
# 阿里云
|
||||
|
||||

|
||||
|
||||
[阿里云](https://www.aliyun.com/?source=5176.11533457\&userCode=8lx5zmtu\&type=copy) 创立于 2009 年,是中国较早的云计算平台。阿里云致力于提供安全、可靠的计算和数据处理能力。
|
||||
|
||||
[阿里云](https://www.aliyun.com/?source=5176.11533457\&userCode=8lx5zmtu\&type=copy) 的客户群体中,活跃着微博、虎牙、魅族、优酷等一大批明星互联网公司。在天猫双 11 全球狂欢节等极富挑战的应用场景中,阿里云保持着良好的运行纪录。
|
||||
|
||||
[阿里云容器服务 Kubernetes 版 ACK](https://www.aliyun.com/product/kubernetes?source=5176.11533457\&userCode=8lx5zmtu\&type=copy) 提供了高性能、可伸缩的容器应用管理服务,支持在一组云服务器上通过 Docker 容器来进行应用生命周期管理。容器服务极大简化了用户对容器管理集群的搭建工作,无缝整合了阿里云虚拟化、存储、网络和安全能力。容器服务提供了多种应用发布方式和流水线般的持续交付能力,原生支持微服务架构,助力用户无缝上云和跨云管理。
|
||||
|
||||

|
||||
@@ -1,22 +0,0 @@
|
||||
# 简介
|
||||
|
||||
随着容器技术的普及,目前主流的云计算服务商都提供了成熟的容器服务。与容器相关的云计算服务主要分为以下几种类型:
|
||||
|
||||
## 1. 容器编排托管服务 (Managed K8s)
|
||||
|
||||
这是目前最主流的形式。云厂商托管 Kubernetes 的控制平面(Master节点),用户只需管理工作节点(Worker Node)。
|
||||
* **优势**:降低了 Kubernetes 集群的维护成本,高可用性由厂商保证。
|
||||
* **典型服务**:AWS EKS, Azure AKS, Google GKE, 阿里云 ACK, 腾讯云 TKE。
|
||||
|
||||
## 2. 容器实例服务 (Serverless Containers)
|
||||
|
||||
这一类服务通常被称为 CaaS (Container as a Service)。用户无需管理底层服务器(EC2/CVM),只需提供镜像和配置即可运行容器。
|
||||
* **优势**:极致的弹性,按秒计费,零运维。
|
||||
* **典型服务**:AWS Fargate, Azure Container Instances, Google Cloud Run, 阿里云 ECI。
|
||||
|
||||
## 3. 镜像仓库服务 (Container Registry)
|
||||
|
||||
提供安全、可靠的私有 Docker 镜像存储服务,通常与云厂商的 CI/CD 流水线深度集成。
|
||||
* **典型服务**:AWS ECR, Azure ACR, Google GCR/GAR, 阿里云 ACR。
|
||||
|
||||
本章将介绍如何在几个主流云平台上使用 Docker 和 Kubernetes 服务。
|
||||
@@ -1,40 +0,0 @@
|
||||
# 多云部署策略比较
|
||||
|
||||
企业在选择容器云平台时,通常会在 AWS EKS, Azure AKS, Google GKE 以及国内的阿里云 ACK, 腾讯云 TKE 之间进行权衡。
|
||||
|
||||
## 三大公有云 Kubernetes 服务对比
|
||||
|
||||
| 特性 | Google GKE | AWS EKS | Azure AKS |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **版本更新** | 最快,通常是 K8s 新特性的首发地 | 相对保守,注重稳定性 | 跟随社区,更新速度适中 |
|
||||
| **控制平面管理** | 全托管,自动升级,免费(部分区域) | 托管,每小时收费 | 全托管,控制平面免费 |
|
||||
| **节点管理** | GKE Autopilot 模式完全托管节点 | Managed Node Groups 简化管理 | Virtual Machine Scale Sets |
|
||||
| **网络模型** | VPC-native, 性能优秀 | AWS VPC CNI, Pod 直接获取 VPC IP | Azure CNI (消耗 IP 多) 或 Kubenet |
|
||||
| **集成度** | 与 GCP 数据分析、AI 服务集成紧密 | 与 AWS IAM, ALB, CloudWatch 集成深度高 | 与 Active Directory, Azure DevOps 集成好 |
|
||||
|
||||
## 多云部署策略
|
||||
|
||||
### 1. 跨云灾备 (Active-Passive)
|
||||
|
||||
主要业务运行在一个云(如 AWS),数据实时复制到另一个云(如阿里云)。当主云发生故障时,流量切换到备云。
|
||||
|
||||
* **优点**: 架构相对简单,数据一致性好控制。
|
||||
* **缺点**: 资源闲置浪费,切换可能有 RTO。
|
||||
|
||||
### 2. 多活部署 (Active-Active)
|
||||
|
||||
业务同时在多个云上运行,通过全局流量管理(DNS/GSLB)分发流量。
|
||||
|
||||
* **优点**: 高可用,就近接入提升用户体验。
|
||||
* **缺点**: 数据同步复杂,跨云网络延迟问题。
|
||||
|
||||
### 3. 混合云 (Hybrid Cloud)
|
||||
|
||||
核心数据和敏感业务保留在私有云(IDC),弹性业务或前端业务部署在公有云。
|
||||
|
||||
* **工具**: Google Anthos, AWS Outposts, Azure Arc 都是为了解决混合云统一管理而生。
|
||||
|
||||
## 建议
|
||||
|
||||
* **技术选型**: 尽量使用标准的 Kubernetes API,避免过度依赖特定云厂商的 CRD 或专有服务,以保持应用的可移植性。
|
||||
* **IaC 管理**: 使用 Terraform 或 Pulumi 等工具统一管理多云基础设施。
|
||||
@@ -1,11 +0,0 @@
|
||||
# 腾讯云
|
||||
|
||||

|
||||
|
||||
[腾讯云](https://cloud.tencent.com/act/cps/redirect?redirect=1040\&cps_key=3a5255852d5db99dcd5da4c72f05df61\&from=console) 在架构方面经过多年积累,并且有着多年对海量互联网服务的经验。不管是社交、游戏还是其他领域,都有多年的成熟产品来提供产品服务。腾讯在云端完成重要部署,为开发者及企业提供云服务、云数据、云运营等整体一站式服务方案。
|
||||
|
||||
具体包括 [云服务器](https://cloud.tencent.com/act/cps/redirect?redirect=1001\&cps_key=3a5255852d5db99dcd5da4c72f05df61\&from=console)、[云存储](https://cloud.tencent.com/act/cps/redirect?redirect=1020\&cps_key=3a5255852d5db99dcd5da4c72f05df61\&from=console)、[云数据库](https://cloud.tencent.com/act/cps/redirect?redirect=1003\&cps_key=3a5255852d5db99dcd5da4c72f05df61\&from=console)、[视频与CDN](https://cloud.tencent.com/act/cps/redirect?redirect=1019\&cps_key=3a5255852d5db99dcd5da4c72f05df61\&from=console) 和 [域名注册](https://dnspod.cloud.tencent.com) 等基础云服务;腾讯云分析(MTA)、腾讯云推送(信鸽)等腾讯整体大数据能力;以及 QQ互联、QQ 空间、微云、微社区等云端链接社交体系。这些正是腾讯云可以提供给这个行业的差异化优势,造就了可支持各种互联网使用场景的高品质的腾讯云技术平台。
|
||||
|
||||
[腾讯云容器服务 TKE](https://cloud.tencent.com/act/cps/redirect?redirect=10058\&cps_key=3a5255852d5db99dcd5da4c72f05df61) 是高度可扩展的高性能容器管理服务,用户可以在托管的云服务器实例集群上轻松运行应用程序。使用该服务,将无需安装、运维、扩展用户的集群管理基础设施,只需进行简单的 API 调用,便可启动和停止 Docker 应用程序,查询集群的完整状态,以及使用各种云服务。用户可以根据用户的资源需求和可用性要求在用户的集群中安排容器的置放,满足业务或应用程序的特定要求。
|
||||
|
||||

|
||||
@@ -1,104 +0,0 @@
|
||||
# podman
|
||||
|
||||
[`podman`](https://github.com/containers/podman) 是一个无守护进程、与 Docker 命令高度兼容的下一代 Linux 容器工具。它由 Red Hat 开发,旨在提供一个更安全的容器运行环境。
|
||||
|
||||
## Podman vs Docker
|
||||
|
||||
| 特性 | Docker | Podman |
|
||||
| :--- | :--- | :--- |
|
||||
| **架构** | C/S 架构,依赖守护进程 (`dockerd`) | 无守护进程 (Daemonless) |
|
||||
| **权限** | 默认需要 root 权限 (虽有 Rootless 模式) | 默认支持 Rootless (非 root 用户运行) |
|
||||
| **生态** | 完整的生态系统 (Compose, Swarm) | 专注单机容器,配合 Kubernetes 使用 |
|
||||
| **镜像构建** | `docker build` | `podman build` 或 `buildah` |
|
||||
|
||||
## 安装
|
||||
|
||||
### CentOS / RHEL
|
||||
|
||||
```bash
|
||||
$ sudo yum -y install podman
|
||||
```
|
||||
|
||||
### macOS
|
||||
|
||||
macOS 上需要安装 Podman Desktop 或通过 Homebrew 安装:
|
||||
|
||||
```bash
|
||||
$ brew install podman
|
||||
$ podman machine init
|
||||
$ podman machine start
|
||||
```
|
||||
|
||||
## 使用
|
||||
|
||||
`podman` 的命令行几乎与 `docker` 完全兼容,大多数情况下,你只需将 `docker` 替换为 `podman` 即可。
|
||||
|
||||
### 运行容器
|
||||
|
||||
```bash
|
||||
# $ docker run -d -p 80:80 nginx:alpine
|
||||
|
||||
$ podman run -d -p 80:80 nginx:alpine
|
||||
```
|
||||
|
||||
### 列出容器
|
||||
|
||||
```bash
|
||||
$ podman ps
|
||||
```
|
||||
|
||||
### 构建镜像
|
||||
|
||||
```bash
|
||||
$ podman build -t myimage .
|
||||
```
|
||||
|
||||
## Pods 的概念
|
||||
|
||||
与 Docker 不同,Podman 支持 "Pod" 的概念(类似于 Kubernetes 的 Pod),允许你在同一个网络命名空间中运行多个容器。
|
||||
|
||||
```bash
|
||||
# 创建一个 Pod
|
||||
$ podman pod create --name mypod -p 8080:80
|
||||
|
||||
# 在 Pod 中运行容器
|
||||
$ podman run -d --pod mypod --name webbing nginx
|
||||
```
|
||||
|
||||
## 迁移到 Podman
|
||||
|
||||
如果你习惯使用 `docker` 命令,可以简单地设置别名:
|
||||
|
||||
$ alias docker=podman
|
||||
```
|
||||
|
||||
## 进阶用法
|
||||
|
||||
### Systemd 集成
|
||||
|
||||
Podman 可以生成 systemd 单元文件,让容器像普通系统服务一样管理。
|
||||
|
||||
```bash
|
||||
# 创建容器
|
||||
$ podman run -d --name myweb -p 8080:80 nginx
|
||||
|
||||
# 生成 systemd 文件
|
||||
$ podman generate systemd --name myweb --files --new
|
||||
|
||||
# 启用并启动服务
|
||||
$ systemctl --user enable --now container-myweb.service
|
||||
```
|
||||
|
||||
### Podman Compose
|
||||
|
||||
虽然 Podman 兼容 Docker Compose,但在某些场景下你可能需要明确使用 `podman-compose`。
|
||||
|
||||
```bash
|
||||
$ pip3 install podman-compose
|
||||
$ podman-compose up -d
|
||||
```
|
||||
|
||||
## 参考
|
||||
|
||||
* [Podman 官方网站](https://podman.io/)
|
||||
* [Podman GitHub 仓库](https://github.com/containers/podman)
|
||||
@@ -1,149 +0,0 @@
|
||||
# 基本架构
|
||||
|
||||
## 核心架构图
|
||||
|
||||
Docker 采用了 **C/S (客户端/服务端)** 架构。Client 向 Daemon 发送请求,Daemon 负责构建、运行和分发容器。
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
Client[客户端 (Docker CLI)] -- docker run --> Dockerd
|
||||
Client -- docker pull --> Dockerd
|
||||
|
||||
subgraph "Docker Host"
|
||||
Dockerd(dockerd<br>守护进程)
|
||||
Containers(Containers<br>容器)
|
||||
Images(Images<br>镜像)
|
||||
|
||||
Dockerd -- 管理 --> Containers
|
||||
Dockerd -- 管理 --> Images
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 组件详解
|
||||
|
||||
Docker 的内部架构如同洋葱一样分层,每一层专注解决特定问题:
|
||||
|
||||
### 1. Docker CLI (客户端)
|
||||
用户与 Docker 交互的主要方式。它将用户命令(如 `docker run`)转换为 API 请求发送给 dockerd。
|
||||
|
||||
### 2. Dockerd (守护进程)
|
||||
Docker 的大脑。
|
||||
- 监听 API 请求
|
||||
- 管理 Docker 对象(镜像、容器、网络、卷)
|
||||
- 编排下层组件完成工作
|
||||
|
||||
### 3. Containerd (高级运行时)
|
||||
行业标准的容器运行时(CNCF 毕业项目)。
|
||||
- 管理容器的完整生命周期(启动、停止)
|
||||
- 镜像拉取与存储
|
||||
- **不包含** 复杂的与容器无关的功能(如构建、API)
|
||||
- Kubernetes 也可以直接使用 containerd(跳过 Docker)
|
||||
|
||||
### 4. Runc (低级运行时)
|
||||
用于创建和运行容器的 CLI 工具。
|
||||
- 直接与内核交互(Namespaces, Cgroups)
|
||||
- 遵循 OCI (Open Container Initiative) 规范
|
||||
- **主要职责**:根据配置启动一个容器,然后退出(将控制权交给容器进程)
|
||||
|
||||
### 5. Shim
|
||||
每个容器都有一个 shim 进程。
|
||||
- **解耦**:允许 dockerd 重启而不影响容器运行
|
||||
- **保持 IO**:维持容器的标准输入输出
|
||||
- **状态汇报**:向 containerd 汇报容器退出状态
|
||||
|
||||
---
|
||||
|
||||
## 容器启动流程
|
||||
|
||||
当执行 `docker run -d nginx` 时,内部发生了什么?
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
User((用户))
|
||||
|
||||
subgraph DockerCLI [Docker CLI]
|
||||
Cmd[docker run -d nginx]
|
||||
end
|
||||
|
||||
subgraph DockerHost [Docker Host]
|
||||
Dockerd[Dockerd]
|
||||
Containerd[Containerd]
|
||||
subgraph ContainerRuntime [Runtime]
|
||||
Shim[Containerd-shim]
|
||||
Runc[Runc]
|
||||
Container[容器进程 (nginx)]
|
||||
end
|
||||
end
|
||||
|
||||
User --> Cmd
|
||||
Cmd -- 1. REST API --> Dockerd
|
||||
Dockerd -- 2. gRPC --> Containerd
|
||||
Containerd -- 3. 准备镜像 & Bundle --> Containerd
|
||||
Containerd -- 4. Fork --> Shim
|
||||
Shim -- 5. Exec --> Runc
|
||||
Runc -- 6. Create Namespaces/Cgroups --> Container
|
||||
Runc -.-> |7. Exit| Runc
|
||||
Shim -.-> |8. Monitor IO/Exit| Container
|
||||
```
|
||||
|
||||
1. **CLI** 发送请求给 **Dockerd**
|
||||
2. **Dockerd** 解析请求,调用 **Containerd**
|
||||
3. **Containerd** 准备镜像,转换为 OCI Bundle
|
||||
4. **Containerd** 创建 **Shim** 进程
|
||||
5. **Shim** 调用 **Runc**
|
||||
6. **Runc** 与系统内核交互,创建 Namespaces 和 Cgroups
|
||||
7. **Runc** 启动 nginx 进程后退出
|
||||
8. **Shim** 接管容器 IO 和生命周期监控
|
||||
|
||||
---
|
||||
|
||||
## Docker Engine v29+ 变化
|
||||
|
||||
从 Docker Engine v29 (2025/2026) 开始,架构进一步简化和标准化:
|
||||
|
||||
- **Containerd 镜像存储 (Image Store)**:默认启用。Docker 直接使用 Containerd 的镜像管理能力,不再维护自己的一套 graphdriver。
|
||||
- **优势**:多平台镜像支持更好、镜像拉取更快(lazy pulling)、与 K8s 共享镜像。
|
||||
|
||||
---
|
||||
|
||||
## Docker Desktop 架构
|
||||
|
||||
在 macOS 和 Windows 上,因为内核差异,架构稍微复杂:
|
||||
|
||||
```
|
||||
┌────────────── MacOS / Windows ──────────────┐
|
||||
│ Docker CLI │
|
||||
│ │ │
|
||||
├──────┼──────────────────────────────────────┤
|
||||
│ ▼ (Socket 映射) │
|
||||
│ ┌────────── Linux VM (虚拟机) ───────────┐ │
|
||||
│ │ │ │
|
||||
│ │ Dockerd <--> Containerd <--> Runc │ │
|
||||
│ │ │ │
|
||||
│ └────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
- 使用轻量级虚拟机(Apple Virtualization / WSL 2)运行 Linux 内核
|
||||
- 文件挂载(Bind Mount)需要跨越 VM 边界(这也是文件 I/O 慢的原因)
|
||||
- 网络端口需要从宿主机转发到 VM
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
| 组件 | 角色 | 关键职责 |
|
||||
|------|------|----------|
|
||||
| **CLI** | 指挥官 | 发送指令,展示结果 |
|
||||
| **Dockerd** | 大管家 | API 接口,整体调度 |
|
||||
| **Containerd** | 经理 | 容器生命周期,镜像管理 |
|
||||
| **Shim** | 监工 | 保持 IO,允许无守护进程重启 |
|
||||
| **Runc** | 工人 | 真正干活(创建容器),干完就走 |
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [命名空间](./namespace.md):Runc 如何隔离容器
|
||||
- [控制组](./cgroups.md):Runc 如何限制资源
|
||||
- [联合文件系统](./ufs.md):镜像如何存储
|
||||
@@ -1,246 +0,0 @@
|
||||
# 控制组
|
||||
|
||||
## 什么是控制组
|
||||
|
||||
控制组(Control Groups,简称 cgroups)是 Linux 内核的一个特性,用于**限制、记录和隔离**进程组的资源使用(CPU、内存、磁盘 I/O、网络等)。
|
||||
|
||||
> **核心作用**:让多个容器公平共享宿主机资源,防止单个容器耗尽系统资源。
|
||||
|
||||
```
|
||||
无 cgroups 限制: 有 cgroups 限制:
|
||||
┌──────────────────────┐ ┌──────────────────────┐
|
||||
│ 宿主机资源 │ │ 宿主机资源 │
|
||||
│ ┌─────────────┐ │ │ ┌───┬───┬───┐ │
|
||||
│ │ 容器 A │ │ │ │ A │ B │ C │ │
|
||||
│ │ 占用所有 │ │ │ │1GB│1GB│1GB│ ← 限制│
|
||||
│ │ 内存和 CPU │ │ │ ├───┼───┼───┤ │
|
||||
│ └─────────────┘ │ │ │2核│1核│1核│ │
|
||||
│ 容器 B、C 饥饿 │ │ └───┴───┴───┘ │
|
||||
└──────────────────────┘ └──────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## cgroups 的历史
|
||||
|
||||
| 时间 | 事件 |
|
||||
|------|------|
|
||||
| 2006 | Google 工程师提出 cgroups 概念 |
|
||||
| 2008 | Linux 2.6.24 正式支持 cgroups v1 |
|
||||
| 2016 | Linux 4.5 引入 cgroups v2 |
|
||||
| 现在 | Docker 默认使用 cgroups v2(如系统支持) |
|
||||
|
||||
---
|
||||
|
||||
## cgroups 可以限制的资源
|
||||
|
||||
| 资源类型 | 子系统 | 说明 |
|
||||
|---------|--------|------|
|
||||
| **CPU** | `cpu`, `cpuset` | CPU 使用时间和核心分配 |
|
||||
| **内存** | `memory` | 内存使用上限和 swap |
|
||||
| **块设备 I/O** | `blkio` | 磁盘读写速度限制 |
|
||||
| **网络** | `net_cls`, `net_prio` | 网络带宽优先级 |
|
||||
| **进程数** | `pids` | 限制进程/线程数量 |
|
||||
|
||||
---
|
||||
|
||||
## Docker 中的资源限制
|
||||
|
||||
### 内存限制
|
||||
|
||||
```bash
|
||||
# 限制容器最多使用 512MB 内存
|
||||
$ docker run -m 512m myapp
|
||||
|
||||
# 限制内存 + swap
|
||||
$ docker run -m 512m --memory-swap 1g myapp
|
||||
|
||||
# 软限制(超过时警告,不会 OOM Kill)
|
||||
$ docker run --memory-reservation 256m myapp
|
||||
```
|
||||
|
||||
| 参数 | 说明 |
|
||||
|------|------|
|
||||
| `-m` / `--memory` | 硬限制(超过会 OOM Kill) |
|
||||
| `--memory-swap` | 内存 + swap 总限制 |
|
||||
| `--memory-reservation` | 软限制(内存竞争时生效) |
|
||||
| `--oom-kill-disable` | 禁用 OOM Killer(谨慎使用) |
|
||||
|
||||
### CPU 限制
|
||||
|
||||
```bash
|
||||
# 限制使用 1.5 个 CPU 核心
|
||||
$ docker run --cpus=1.5 myapp
|
||||
|
||||
# 限制使用 CPU 0 和 1
|
||||
$ docker run --cpuset-cpus="0,1" myapp
|
||||
|
||||
# 设置 CPU 使用权重(相对值,默认 1024)
|
||||
$ docker run --cpu-shares=512 myapp
|
||||
```
|
||||
|
||||
| 参数 | 说明 |
|
||||
|------|------|
|
||||
| `--cpus` | 限制 CPU 核心数(如 1.5) |
|
||||
| `--cpuset-cpus` | 绑定到特定 CPU 核心 |
|
||||
| `--cpu-shares` | CPU 时间片权重(相对值) |
|
||||
| `--cpu-period` / `--cpu-quota` | 精细控制 CPU 配额 |
|
||||
|
||||
### 磁盘 I/O 限制
|
||||
|
||||
```bash
|
||||
# 限制设备写入速度为 10MB/s
|
||||
$ docker run --device-write-bps /dev/sda:10mb myapp
|
||||
|
||||
# 限制设备读取速度
|
||||
$ docker run --device-read-bps /dev/sda:10mb myapp
|
||||
|
||||
# 限制 IOPS
|
||||
$ docker run --device-write-iops /dev/sda:100 myapp
|
||||
```
|
||||
|
||||
### 进程数限制
|
||||
|
||||
```bash
|
||||
# 限制最多 100 个进程
|
||||
$ docker run --pids-limit=100 myapp
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 查看容器资源使用
|
||||
|
||||
```bash
|
||||
# 实时监控所有容器的资源使用
|
||||
$ docker stats
|
||||
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O
|
||||
abc123 web 0.50% 45.5MiB / 512MiB 8.89% 1.2kB / 0B 0B / 0B
|
||||
def456 db 2.30% 256MiB / 1GiB 25.00% 5.6kB / 3.2kB 4.1MB / 2.3MB
|
||||
|
||||
# 查看特定容器
|
||||
$ docker stats mycontainer
|
||||
|
||||
# 查看容器的 cgroup 配置
|
||||
$ docker inspect mycontainer --format '{{json .HostConfig}}' | jq
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 资源限制的效果
|
||||
|
||||
### 内存超限
|
||||
|
||||
```bash
|
||||
# 启动限制 100MB 内存的容器
|
||||
$ docker run -m 100m stress --vm 1 --vm-bytes 200M
|
||||
|
||||
# 容器会被 OOM Killer 杀死
|
||||
$ docker ps -a
|
||||
CONTAINER ID STATUS NAMES
|
||||
abc123 Exited (137) 5 seconds ago hopeful_darwin
|
||||
|
||||
# 137 = 128 + 9,表示被 SIGKILL (9) 杀死
|
||||
```
|
||||
|
||||
### CPU 限制验证
|
||||
|
||||
```bash
|
||||
# 不限制 CPU
|
||||
$ docker run --rm stress --cpu 4
|
||||
# 占满所有 CPU
|
||||
|
||||
# 限制为 1 个核心
|
||||
$ docker run --rm --cpus=1 stress --cpu 4
|
||||
# 只能使用约 100% CPU(1 个核心)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## cgroups v1 vs v2
|
||||
|
||||
| 特性 | cgroups v1 | cgroups v2 |
|
||||
|------|-----------|-----------|
|
||||
| 层级结构 | 多层级(每个资源单独) | 统一层级 |
|
||||
| 管理复杂度 | 复杂 | 简化 |
|
||||
| 资源分配 | 基于层级 | 基于子树 |
|
||||
| PSI(压力监控) | ❌ | ✅ |
|
||||
| rootless 容器 | 部分支持 | 完整支持 |
|
||||
|
||||
### 检查系统使用的版本
|
||||
|
||||
```bash
|
||||
# 查看 cgroup 版本
|
||||
$ mount | grep cgroup
|
||||
cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime)
|
||||
# 如果显示 cgroup2 表示 v2
|
||||
|
||||
# 或者
|
||||
$ cat /proc/filesystems | grep cgroup
|
||||
nodev cgroup
|
||||
nodev cgroup2
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 在 Compose 中设置限制
|
||||
|
||||
```yaml
|
||||
services:
|
||||
web:
|
||||
image: nginx
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: '0.5'
|
||||
memory: 512M
|
||||
reservations:
|
||||
cpus: '0.25'
|
||||
memory: 256M
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 始终设置内存限制
|
||||
|
||||
```bash
|
||||
# 防止 OOM 影响宿主机
|
||||
$ docker run -m 1g myapp
|
||||
```
|
||||
|
||||
### 2. 为关键应用设置 CPU 保证
|
||||
|
||||
```bash
|
||||
$ docker run --cpus=2 --cpu-shares=2048 critical-app
|
||||
```
|
||||
|
||||
### 3. 监控资源使用
|
||||
|
||||
```bash
|
||||
# 配合 Prometheus + cAdvisor 监控
|
||||
$ docker run -d --name cadvisor \
|
||||
-v /:/rootfs:ro \
|
||||
-v /var/run:/var/run:ro \
|
||||
-v /sys:/sys:ro \
|
||||
-v /var/lib/docker:/var/lib/docker:ro \
|
||||
gcr.io/cadvisor/cadvisor
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 本章小结
|
||||
|
||||
| 资源 | 限制参数 | 示例 |
|
||||
|------|---------|------|
|
||||
| **内存** | `-m` | `-m 512m` |
|
||||
| **CPU 核心数** | `--cpus` | `--cpus=1.5` |
|
||||
| **CPU 绑定** | `--cpuset-cpus` | `--cpuset-cpus="0,1"` |
|
||||
| **磁盘 I/O** | `--device-write-bps` | `--device-write-bps /dev/sda:10mb` |
|
||||
| **进程数** | `--pids-limit` | `--pids-limit=100` |
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [命名空间](namespace.md):资源隔离
|
||||
- [安全](../security/README.md):容器安全概述
|
||||
- [Docker Stats](../05_container/README.md):监控容器资源
|
||||
@@ -1,267 +0,0 @@
|
||||
# 命名空间
|
||||
|
||||
## 什么是 Namespace
|
||||
|
||||
> **Namespace 是 Linux 内核提供的资源隔离机制,它让容器内的进程仿佛运行在独立的操作系统中。**
|
||||
|
||||
Namespace 是容器技术的核心基础之一。它回答了一个关键问题:**如何让一个进程"以为"自己独占整个系统?**
|
||||
|
||||
```
|
||||
宿主机视角: 容器内视角:
|
||||
┌─────────────────────────┐ ┌─────────────────────────┐
|
||||
│ PID 1: systemd │ │ PID 1: nginx │ ← 容器认为自己是 PID 1
|
||||
│ PID 2: sshd │ │ PID 2: nginx worker │
|
||||
│ PID 3: dockerd │ │ │
|
||||
│ PID 1234: nginx ←──────│─────│ (实际是宿主机的 1234) │
|
||||
│ PID 1235: nginx worker │ │ │
|
||||
└─────────────────────────┘ └─────────────────────────┘
|
||||
```
|
||||
|
||||
## Namespace 的类型
|
||||
|
||||
Linux 内核提供了以下几种 Namespace,Docker 容器使用了全部:
|
||||
|
||||
| Namespace | 隔离内容 | 容器中的效果 |
|
||||
|-----------|---------|-------------|
|
||||
| **PID** | 进程 ID | 容器内 PID 从 1 开始,看不到其他容器和宿主机进程 |
|
||||
| **NET** | 网络栈 | 独立的网卡、IP 地址、端口、路由表 |
|
||||
| **MNT** | 挂载点 | 独立的文件系统视图,自己的根目录 |
|
||||
| **UTS** | 主机名 | 独立的主机名和域名 |
|
||||
| **IPC** | 进程间通信 | 独立的信号量、消息队列、共享内存 |
|
||||
| **USER** | 用户/组 ID | 容器内的 root 可以映射为宿主机的普通用户 |
|
||||
| **Cgroup** | Cgroup 根目录 | 隔离 cgroup 层级视图(Linux 4.6+) |
|
||||
|
||||
---
|
||||
|
||||
## PID Namespace
|
||||
|
||||
### 作用
|
||||
|
||||
隔离进程 ID,让每个容器有自己的进程编号空间。
|
||||
|
||||
### 效果
|
||||
|
||||
```bash
|
||||
# 宿主机上查看进程
|
||||
$ ps aux | grep nginx
|
||||
root 12345 0.0 0.1 nginx: master process
|
||||
root 12346 0.0 0.1 nginx: worker process
|
||||
|
||||
# 容器内查看进程
|
||||
$ docker exec mycontainer ps aux
|
||||
PID USER COMMAND
|
||||
1 root nginx: master process ← 在容器内是 PID 1
|
||||
2 root nginx: worker process
|
||||
```
|
||||
|
||||
### 关键点
|
||||
|
||||
- 容器内的 PID 1 进程特殊重要——它是容器的主进程,退出则容器停止
|
||||
- 容器内无法看到宿主机或其他容器的进程
|
||||
- 宿主机可以看到所有容器内的进程(但 PID 不同)
|
||||
|
||||
---
|
||||
|
||||
## NET Namespace
|
||||
|
||||
### 作用
|
||||
|
||||
隔离网络栈,每个容器拥有独立的网络环境。
|
||||
|
||||
### 效果
|
||||
|
||||
```
|
||||
宿主机 容器
|
||||
┌─────────────────────┐ ┌─────────────────────┐
|
||||
│ eth0: 192.168.1.10 │ │ eth0: 172.17.0.2 │ ← 不同的 IP
|
||||
│ docker0: 172.17.0.1│◄───────►│ (veth pair 连接) │
|
||||
│ 端口 80 可用 │ │ 端口 80 可用 │ ← 可以使用相同端口
|
||||
└─────────────────────┘ └─────────────────────┘
|
||||
```
|
||||
|
||||
### 关键点
|
||||
|
||||
- 每个容器有独立的网卡、IP、路由表、iptables 规则
|
||||
- 多个容器可以监听相同端口(如都监听 80)
|
||||
- Docker 使用 veth pair 连接容器网络和宿主机网桥
|
||||
|
||||
---
|
||||
|
||||
## MNT Namespace
|
||||
|
||||
### 作用
|
||||
|
||||
隔离文件系统挂载点,每个容器有自己的根目录。
|
||||
|
||||
### 效果
|
||||
|
||||
```
|
||||
宿主机文件系统: 容器内看到的:
|
||||
/ / ← 容器的根目录
|
||||
├── bin/ ├── bin/
|
||||
├── home/ ├── home/
|
||||
├── var/ ├── var/
|
||||
│ └── lib/ │ └── lib/
|
||||
│ └── docker/ │
|
||||
│ └── overlay2/ │
|
||||
│ └── merged/ ────┼─── 这个目录成为容器的 /
|
||||
└── ... └── ...
|
||||
```
|
||||
|
||||
### 与 chroot 的区别
|
||||
|
||||
| 特性 | chroot | MNT Namespace |
|
||||
|------|--------|---------------|
|
||||
| 安全性 | 可以逃逸 | 更安全 |
|
||||
| 挂载隔离 | 无 | 完全隔离 |
|
||||
| /proc/mounts | 共享 | 独立 |
|
||||
|
||||
---
|
||||
|
||||
## UTS Namespace
|
||||
|
||||
### 作用
|
||||
|
||||
隔离主机名和域名,让每个容器可以有自己的主机名。
|
||||
|
||||
### 效果
|
||||
|
||||
```bash
|
||||
# 宿主机
|
||||
$ hostname
|
||||
my-server
|
||||
|
||||
# 容器内
|
||||
$ docker run --hostname mycontainer ubuntu hostname
|
||||
mycontainer
|
||||
```
|
||||
|
||||
UTS = "UNIX Time-sharing System",是历史遗留的名称。
|
||||
|
||||
---
|
||||
|
||||
## IPC Namespace
|
||||
|
||||
### 作用
|
||||
|
||||
隔离 System V IPC 和 POSIX 消息队列。
|
||||
|
||||
### 隔离的资源
|
||||
|
||||
- 信号量(semaphores)
|
||||
- 消息队列(message queues)
|
||||
- 共享内存(shared memory)
|
||||
|
||||
### 关键点
|
||||
|
||||
- 同一容器内的进程可以通过 IPC 通信
|
||||
- 不同容器的进程无法通过 IPC 通信(除非显式共享)
|
||||
|
||||
---
|
||||
|
||||
## USER Namespace
|
||||
|
||||
### 作用
|
||||
|
||||
隔离用户和组 ID,实现权限隔离。
|
||||
|
||||
### 效果
|
||||
|
||||
```
|
||||
容器内 宿主机
|
||||
┌─────────────────┐ ┌─────────────────┐
|
||||
│ UID 0 (root) │───映射────►│ UID 100000 │ ← 非特权用户
|
||||
│ UID 1 (daemon) │───映射────►│ UID 100001 │
|
||||
└─────────────────┘ └─────────────────┘
|
||||
```
|
||||
|
||||
### 安全意义
|
||||
|
||||
容器内的 root 用户可以映射为宿主机上的普通用户,即使容器被突破,攻击者在宿主机上也只有普通权限。
|
||||
|
||||
> 💡 笔者建议:生产环境建议启用 User Namespace,增强安全性。
|
||||
|
||||
---
|
||||
|
||||
## 动手实验:体验 Namespace
|
||||
|
||||
使用 `unshare` 命令可以在不使用 Docker 的情况下体验 Namespace:
|
||||
|
||||
### 实验 1:UTS Namespace
|
||||
|
||||
```bash
|
||||
# 创建新的 UTS namespace 并启动 shell
|
||||
$ sudo unshare --uts /bin/bash
|
||||
|
||||
# 修改主机名(只影响这个 namespace)
|
||||
$ hostname container-test
|
||||
$ hostname
|
||||
container-test
|
||||
|
||||
# 退出后查看宿主机主机名(未改变)
|
||||
$ exit
|
||||
$ hostname
|
||||
my-server
|
||||
```
|
||||
|
||||
### 实验 2:PID Namespace
|
||||
|
||||
```bash
|
||||
# 创建新的 PID 和 MNT namespace
|
||||
$ sudo unshare --pid --mount --fork /bin/bash
|
||||
|
||||
# 挂载新的 /proc
|
||||
$ mount -t proc proc /proc
|
||||
|
||||
# 查看进程(只能看到当前 shell)
|
||||
$ ps aux
|
||||
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
|
||||
root 1 0.0 0.0 8960 4516 pts/0 S 10:00 0:00 /bin/bash
|
||||
root 8 0.0 0.0 10072 3200 pts/0 R+ 10:00 0:00 ps aux
|
||||
```
|
||||
|
||||
### 实验 3:NET Namespace
|
||||
|
||||
```bash
|
||||
# 创建新的网络 namespace
|
||||
$ sudo unshare --net /bin/bash
|
||||
|
||||
# 查看网络接口(只有 lo)
|
||||
$ ip addr
|
||||
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN
|
||||
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Namespace 的局限性
|
||||
|
||||
Namespace 提供了隔离但不是安全边界:
|
||||
|
||||
| 方面 | 说明 |
|
||||
|------|------|
|
||||
| **共享内核** | 所有容器共享宿主机内核,内核漏洞可能影响所有容器 |
|
||||
| **部分资源未隔离** | /proc、/sys 部分内容仍可见;时间无法隔离 |
|
||||
| **非虚拟化** | 比虚拟机隔离性弱 |
|
||||
|
||||
> 需要更强隔离时,可考虑 gVisor、Kata Containers 等安全容器方案。
|
||||
|
||||
---
|
||||
|
||||
## 本章小结
|
||||
|
||||
| Namespace | 隔离内容 | 一句话说明 |
|
||||
|-----------|---------|-----------|
|
||||
| PID | 进程 ID | 容器有自己的进程树 |
|
||||
| NET | 网络 | 容器有自己的 IP 和端口 |
|
||||
| MNT | 文件系统 | 容器有自己的根目录 |
|
||||
| UTS | 主机名 | 容器有自己的 hostname |
|
||||
| IPC | 进程间通信 | 容器间 IPC 隔离 |
|
||||
| USER | 用户 ID | 容器 root ≠ 宿主机 root |
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [控制组(Cgroups)](cgroups.md):资源限制机制
|
||||
- [联合文件系统](ufs.md):分层存储的实现
|
||||
- [安全](../security/README.md):容器安全实践
|
||||
- [Linux Namespace 官方文档](https://man7.org/linux/man-pages/man7/namespaces.7.html)
|
||||
@@ -1,230 +0,0 @@
|
||||
# 联合文件系统
|
||||
|
||||
## 什么是联合文件系统
|
||||
|
||||
联合文件系统(UnionFS)是一种**分层、轻量级**的文件系统,它将多个目录"联合"挂载到同一个虚拟目录,形成一个统一的文件系统视图。
|
||||
|
||||
> **核心思想**:将多个只读层叠加,最上层可写,形成完整的文件系统。
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ 容器看到的文件系统 │
|
||||
│ /bin /etc /lib /usr /var /app /data │
|
||||
└────────────────────────┬────────────────────────────────┘
|
||||
│
|
||||
┌───────────────┴───────────────┐
|
||||
│ UnionFS 联合挂载 │
|
||||
└───────────────┬───────────────┘
|
||||
│
|
||||
┌────────────────────────┴────────────────────────────────┐
|
||||
│ 容器层 (读写) │ /app/data/log.txt (新写入) │
|
||||
├────────────────────┼────────────────────────────────────│
|
||||
│ 镜像层3 (只读) │ /app/app.py │
|
||||
├────────────────────┼────────────────────────────────────│
|
||||
│ 镜像层2 (只读) │ /usr/local/bin/python │
|
||||
├────────────────────┼────────────────────────────────────│
|
||||
│ 镜像层1 (只读) │ /bin /etc /lib (基础系统) │
|
||||
└────────────────────┴────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 为什么 Docker 使用联合文件系统
|
||||
|
||||
### 1. 镜像分层复用
|
||||
|
||||
```
|
||||
nginx:alpine myapp:latest
|
||||
│ │
|
||||
└────────┬────────────┘
|
||||
│
|
||||
▼
|
||||
alpine:3.19 (共享基础层)
|
||||
```
|
||||
|
||||
多个镜像共享相同的底层,节省磁盘空间。
|
||||
|
||||
### 2. 快速构建
|
||||
|
||||
每个 Dockerfile 指令创建一层,只有变化的层需要重建:
|
||||
|
||||
```docker
|
||||
FROM node:20 # 层1:基础镜像
|
||||
COPY package.json ./ # 层2:依赖定义
|
||||
RUN npm install # 层3:安装依赖
|
||||
COPY . . # 层4:应用代码
|
||||
```
|
||||
|
||||
代码变化时,只需重建层4,层1-3 使用缓存。
|
||||
|
||||
### 3. 容器启动快
|
||||
|
||||
容器启动时不需要复制镜像,只需:
|
||||
1. 在镜像层上创建一个薄的可写层
|
||||
2. 联合挂载所有层
|
||||
|
||||
---
|
||||
|
||||
## Copy-on-Write(写时复制)
|
||||
|
||||
当容器修改只读层中的文件时:
|
||||
|
||||
```
|
||||
修改前: 修改后:
|
||||
┌─────────────────────┐ ┌─────────────────────┐
|
||||
│ 容器层 (空) │ │ 容器层 │
|
||||
├─────────────────────┤ │ /etc/nginx.conf ←──┼── 复制到容器层后修改
|
||||
│ 镜像层 │ ├─────────────────────┤
|
||||
│ /etc/nginx.conf │ │ 镜像层 │
|
||||
└─────────────────────┘ │ /etc/nginx.conf │ (原文件仍在,但被遮蔽)
|
||||
└─────────────────────┘
|
||||
```
|
||||
|
||||
**流程**:
|
||||
1. 从只读层读取文件
|
||||
2. 复制到容器的可写层
|
||||
3. 在可写层中修改
|
||||
4. 后续读取使用可写层的版本
|
||||
|
||||
---
|
||||
|
||||
## Docker 支持的存储驱动
|
||||
|
||||
Docker 可使用多种联合文件系统实现:
|
||||
|
||||
| 存储驱动 | 说明 | 推荐程度 |
|
||||
|---------|------|---------|
|
||||
| **overlay2** | 现代 Linux 默认驱动,性能优秀 | ✅ **推荐** |
|
||||
| **aufs** | 早期默认,兼容性好 | 遗留系统 |
|
||||
| **btrfs** | 使用 Btrfs 子卷 | 特定场景 |
|
||||
| **zfs** | 使用 ZFS 数据集 | 特定场景 |
|
||||
| **devicemapper** | 块设备级存储 | 遗留系统 |
|
||||
| **vfs** | 不使用 CoW,每层完整复制 | 仅测试 |
|
||||
|
||||
### 各发行版推荐
|
||||
|
||||
| Linux 发行版 | 推荐存储驱动 |
|
||||
|-------------|-------------|
|
||||
| Ubuntu 16.04+ | overlay2 |
|
||||
| Debian Stretch+ | overlay2 |
|
||||
| CentOS 7+ | overlay2 |
|
||||
| RHEL 8+ | overlay2 |
|
||||
| Fedora | overlay2 |
|
||||
|
||||
### 查看当前存储驱动
|
||||
|
||||
```bash
|
||||
$ docker info | grep "Storage Driver"
|
||||
Storage Driver: overlay2
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## overlay2 工作原理
|
||||
|
||||
overlay2 是目前最推荐的存储驱动:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ merged(合并视图) │
|
||||
│ 容器看到的完整文件系统 │
|
||||
└─────────────────────────┬───────────────────────────────────┘
|
||||
│
|
||||
┌───────────────┴───────────────┐
|
||||
│ OverlayFS │
|
||||
└───────────────┬───────────────┘
|
||||
│
|
||||
┌────────────────┼────────────────┐
|
||||
▼ ▼ ▼
|
||||
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
||||
│ upper │ │ lower2 │ │ lower1 │
|
||||
│ (容器层) │ │ (镜像层) │ │ (基础层) │
|
||||
│ 读写 │ │ 只读 │ │ 只读 │
|
||||
└─────────────┘ └─────────────┘ └─────────────┘
|
||||
```
|
||||
|
||||
- **lowerdir**:只读的镜像层(可以有多个)
|
||||
- **upperdir**:可写的容器层
|
||||
- **workdir**:OverlayFS 的工作目录
|
||||
- **merged**:联合挂载后的视图
|
||||
|
||||
### 文件操作行为
|
||||
|
||||
| 操作 | 行为 |
|
||||
|------|------|
|
||||
| **读取** | 从上到下查找第一个匹配的文件 |
|
||||
| **创建** | 在 upper 层创建 |
|
||||
| **修改** | 如果在 lower 层,先复制到 upper 层再修改 |
|
||||
| **删除** | 在 upper 层创建 whiteout 文件标记删除 |
|
||||
|
||||
---
|
||||
|
||||
## 查看镜像层
|
||||
|
||||
```bash
|
||||
# 查看镜像的层信息
|
||||
$ docker history nginx:alpine
|
||||
IMAGE CREATED CREATED BY SIZE
|
||||
a6eb2a334a9f 2 weeks ago CMD ["nginx" "-g" "daemon off;"] 0B
|
||||
<missing> 2 weeks ago STOPSIGNAL SIGQUIT 0B
|
||||
<missing> 2 weeks ago EXPOSE map[80/tcp:{}] 0B
|
||||
<missing> 2 weeks ago ENTRYPOINT ["/docker-entrypoint.sh"] 0B
|
||||
<missing> 2 weeks ago COPY 30-tune-worker-processes.sh /docker-ent… 4.62kB
|
||||
...
|
||||
|
||||
# 查看层的存储位置
|
||||
$ docker inspect nginx:alpine --format '{{json .GraphDriver.Data}}' | jq
|
||||
{
|
||||
"LowerDir": "/var/lib/docker/overlay2/.../diff:/var/lib/docker/overlay2/.../diff",
|
||||
"MergedDir": "/var/lib/docker/overlay2/.../merged",
|
||||
"UpperDir": "/var/lib/docker/overlay2/.../diff",
|
||||
"WorkDir": "/var/lib/docker/overlay2/.../work"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 减少镜像层数
|
||||
|
||||
```docker
|
||||
# ❌ 每条命令创建一层
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y nginx
|
||||
RUN rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# ✅ 合并为一层
|
||||
RUN apt-get update && \
|
||||
apt-get install -y nginx && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
```
|
||||
|
||||
### 2. 避免在容器中写入大量数据
|
||||
|
||||
容器层的写入性能低于直接写入。大量数据应使用:
|
||||
- 数据卷(Volume)
|
||||
- 绑定挂载(Bind Mount)
|
||||
|
||||
### 3. 使用 .dockerignore
|
||||
|
||||
排除不需要的文件可以:
|
||||
- 减小构建上下文
|
||||
- 避免创建不必要的层
|
||||
|
||||
---
|
||||
|
||||
## 本章小结
|
||||
|
||||
| 概念 | 说明 |
|
||||
|------|------|
|
||||
| **UnionFS** | 将多层目录联合挂载为一个文件系统 |
|
||||
| **Copy-on-Write** | 写时复制,修改时才复制到可写层 |
|
||||
| **overlay2** | Docker 默认推荐的存储驱动 |
|
||||
| **分层好处** | 镜像复用、快速构建、快速启动 |
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [镜像](../02_basic_concept/image.md):理解镜像分层
|
||||
- [容器](../02_basic_concept/container.md):容器存储层
|
||||
- [构建镜像](../04_image/build.md):Dockerfile 层的创建
|
||||
@@ -1,7 +0,0 @@
|
||||
# 实战案例
|
||||
|
||||
本章将介绍 Docker 在不同场景下的实战案例。
|
||||
|
||||
* [实战案例 - 操作系统](os/README.md)
|
||||
* [实战案例 - CI/CD](ci/README.md)
|
||||
* [在 IDE 中使用 Docker](ide/README.md)
|
||||
@@ -1,74 +0,0 @@
|
||||
# DevOps 工作流完整示例
|
||||
|
||||
本章将演示一个基于 Docker, Kubernetes 和 Jenkins/GitLab CI 的完整 DevOps 工作流。
|
||||
|
||||
## 工作流概览
|
||||
|
||||
1. **Code**: 开发人员提交代码到 GitLab。
|
||||
2. **Build**: GitLab CI 触发构建任务。
|
||||
3. **Test**: 运行单元测试和集成测试。
|
||||
4. **Package**: 构建 Docker 镜像并推送到 Harbor/Registry。
|
||||
5. **Deploy (Staging)**: 自动部署到测试环境 Kubernetes 集群。
|
||||
6. **Verify**: 人工或自动化验证。
|
||||
7. **Release (Production)**: 审批后自动部署到生产环境。
|
||||
|
||||
## 关键配置示例
|
||||
|
||||
### 1. Dockerfile (多阶段构建)
|
||||
|
||||
```dockerfile
|
||||
# Build stage
|
||||
FROM golang:1.18 AS builder
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
RUN go build -o main .
|
||||
|
||||
# Final stage
|
||||
FROM alpine:latest
|
||||
WORKDIR /app
|
||||
COPY --from=builder /app/main .
|
||||
CMD ["./main"]
|
||||
```
|
||||
|
||||
### 2. GitLab CI (.gitlab-ci.yml)
|
||||
|
||||
```yaml
|
||||
stages:
|
||||
- test
|
||||
- build
|
||||
- deploy
|
||||
|
||||
unit_test:
|
||||
stage: test
|
||||
image: golang:1.18
|
||||
script:
|
||||
- go test ./...
|
||||
|
||||
build_image:
|
||||
stage: build
|
||||
image: docker:20.10.16
|
||||
services:
|
||||
- docker:20.10.16-dind
|
||||
script:
|
||||
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
|
||||
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
|
||||
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
|
||||
|
||||
deploy_staging:
|
||||
stage: deploy
|
||||
image: dtzar/helm-kubectl
|
||||
script:
|
||||
- kubectl config set-cluster k8s --server=$KUBE_URL --insecure-skip-tls-verify=true
|
||||
- kubectl config set-credentials admin --token=$KUBE_TOKEN
|
||||
- kubectl config set-context default --cluster=k8s --user=admin
|
||||
- kubectl config use-context default
|
||||
- kubectl set image deployment/myapp myapp=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA -n staging
|
||||
only:
|
||||
- develop
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **不可变基础设施**: 一旦镜像构建完成,在各个环境(Dev, Staging, Prod)中都应该使用同一个镜像 tag (通常是 commit hash),而不是重新构建。
|
||||
2. **配置分离**: 使用 ConfigMap 和 Secret 管理环境特定的配置,不要打包进镜像。
|
||||
3. **GitOps**: 考虑引入 ArgoCD,将部署配置也作为代码存储在 Git 中,实现 Git 驱动的部署同步。
|
||||
@@ -1,22 +0,0 @@
|
||||
# Drone CI Demo 项目
|
||||
|
||||
这是一个基于 Go 语言编写的简单 Web 应用示例,用于演示 Drone CI 的持续集成流程。
|
||||
|
||||
## 目录结构
|
||||
|
||||
* `app.go`: 简单的 Go Web 服务器代码。
|
||||
* `.drone.yml`: Drone CI 的配置文件,定义了构建和测试流程。
|
||||
* `Dockerfile`: 定义了如何将该应用构建为 Docker 镜像。
|
||||
|
||||
## 如何运行
|
||||
|
||||
1. 确保本地已安装 Docker 环境。
|
||||
2. 进入本目录构建镜像:
|
||||
```bash
|
||||
docker build -t drone-demo-app .
|
||||
```
|
||||
3. 运行容器:
|
||||
```bash
|
||||
docker run -p 8080:8080 drone-demo-app
|
||||
```
|
||||
4. 访问 `http://localhost:8080` 查看效果。
|
||||
@@ -1,12 +0,0 @@
|
||||
# 附录
|
||||
|
||||
本章包含了 Docker 相关的参考资料、常见问题解答以及最佳实践指南,旨在为读者提供便捷的查阅工具。
|
||||
|
||||
## 目录
|
||||
|
||||
* [**常见问题总结 (FAQ)**](faq/README.md):汇总了学习和使用 Docker 过程中的常见问题与错误解决方案。
|
||||
* [**热门镜像介绍**](repo/README.md):详细介绍了 Nginx, MySQL, Redis 等常用官方镜像的使用方法。
|
||||
* [**Docker 命令查询**](command/README.md):速查 Docker 客户端和服务端的常用命令。
|
||||
* [**Dockerfile 最佳实践**](best_practices.md):提供编写高效、安全 Dockerfile 的指导原则。
|
||||
* [**如何调试 Docker**](debug.md):介绍 Docker 调试技巧和工具。
|
||||
* [**资源链接**](resources.md):推荐更多 Docker 相关的学习资源。
|
||||
@@ -1,73 +0,0 @@
|
||||
# 如何调试 Docker
|
||||
|
||||
## 开启 Debug 模式
|
||||
|
||||
在 dockerd 配置文件 daemon.json(默认位于 /etc/docker/)中添加
|
||||
|
||||
```json
|
||||
{
|
||||
"debug": true
|
||||
}
|
||||
```
|
||||
|
||||
重启守护进程。
|
||||
|
||||
```bash
|
||||
$ sudo kill -SIGHUP $(pidof dockerd)
|
||||
```
|
||||
|
||||
此时 dockerd 会在日志中输入更多信息供分析。
|
||||
|
||||
## 检查内核日志
|
||||
|
||||
```bash
|
||||
$ sudo dmesg |grep dockerd
|
||||
$ sudo dmesg |grep runc
|
||||
```
|
||||
|
||||
## Docker 不响应时处理
|
||||
|
||||
可以杀死 dockerd 进程查看其堆栈调用情况。
|
||||
|
||||
```bash
|
||||
$ sudo kill -SIGUSR1 $(pidof dockerd)
|
||||
```
|
||||
|
||||
## 重置 Docker 本地数据
|
||||
|
||||
*注意,本操作会移除所有的 Docker 本地数据,包括镜像和容器等。*
|
||||
|
||||
$ sudo rm -rf /var/lib/docker
|
||||
```
|
||||
|
||||
## 常见故障排查
|
||||
|
||||
### 容器启动失败
|
||||
|
||||
如果容器启动后立即退出,可以使用 `docker logs` 查看原因。
|
||||
|
||||
* **Exit Code 1**: 应用程序错误。通常是配置错误或依赖缺失。
|
||||
* **Exit Code 137**: OOM (Out Of Memory)。容器内存不足被内核杀掉。
|
||||
* 检查宿主机内存。
|
||||
* 调整容器内存限制 (`--memory`)。
|
||||
* **Exit Code 127**: 命令未找到。可能是 `ENTRYPOINT` 或 `CMD` 指定的命令不存在。
|
||||
|
||||
### 网络连接问题
|
||||
|
||||
#### 容器内部无法联网
|
||||
|
||||
1. 检查 Docker DNS 配置 (`/etc/docker/daemon.json`)。
|
||||
2. 检查宿主机防火墙 (iptables/firewalld) 是否拦截了转发。
|
||||
3. 容器内测试: `ping 8.8.8.8` (测试连通性), `nslookup google.com` (测试 DNS)。
|
||||
|
||||
#### 端口映射不通
|
||||
|
||||
1. 检查容器端口是否正确监听: `netstat -tunlp` (宿主机) 或 `docker exec <container> netstat -tunlp`。
|
||||
2. 确认应用监听地址是 `0.0.0.0` 而不是 `127.0.0.1`。
|
||||
* 如果应用监听在 `127.0.0.1`,只有容器内部能访问,映射到宿主机外部也无法被外部请求访问。
|
||||
|
||||
### 镜像拉取失败
|
||||
|
||||
* **connection refused**: 检查网络或代理设置。
|
||||
* **image not found**: 检查镜像名称和 Tag 拼写。
|
||||
* **EOF / timeout**: 网络不稳定,尝试配置镜像加速器。
|
||||
@@ -1,13 +0,0 @@
|
||||
# 常见错误速查表
|
||||
|
||||
| 错误信息 / 现象 | 可能原因 | 解决方案 |
|
||||
| :--- | :--- | :--- |
|
||||
| `Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?` | Docker 服务未启动 | Linux: `sudo systemctl start docker`<br>Mac/Win: 启动 Docker Desktop |
|
||||
| `permission denied while trying to connect to the Docker daemon socket` | 当前用户不在 `docker` 用户组 | `sudo usermod -aG docker $USER` (需重新登录) |
|
||||
| `manifest for ... not found: manifest unknown` | 镜像 tag 不存在 | 检查 Docker Hub 该镜像是否存在该 tag,或拼写是否正确 |
|
||||
| `connection refused` (pull image) | 网络不通或镜像源无法访问 | 检查网络,配置[镜像加速器](../../install/mirror.md) |
|
||||
| `Bind for 0.0.0.0:8080 failed: port is already allocated` | 端口被占用 | 检查占用端口的进程 (`lsof -i:8080`) 并杀掉,或换个端口映射 (`-p 8081:80`) |
|
||||
| `exec user process caused "exec format error"` | 架构不匹配 (如在 x86 上跑 ARM 镜像) | 使用 `docker buildx` 构建多架构镜像,或拉取对应架构的镜像 |
|
||||
| `standard_init_linux.go:211: exec user process caused "no such file or directory"` | 找不到解释器或依赖库 | 检查 `ENTRYPOINT`/`CMD` 脚本开头的 shebang (`#!/bin/sh` vs `#!/bin/bash`),或确认二进制文件是否依赖缺失 (Alpine 常见缺少 glibc) |
|
||||
| `iptables: No chain/target/match by that name` | 防火墙规则缺失或冲突 | 重启 Docker 服务重置 iptables 链: `sudo systemctl restart docker` |
|
||||
| 容器内无法访问外网 | DNS 配置或转发问题 | 检查 `/etc/docker/daemon.json` 中的 DNS 配置 |
|
||||
@@ -1,32 +0,0 @@
|
||||
# [CentOS](https://hub.docker.com/_/centos)
|
||||
|
||||
## 基本信息
|
||||
|
||||
[CentOS](https://en.wikipedia.org/wiki/CentOS) 是流行的 Linux 发行版,其软件包大多跟 RedHat 系列保持一致。
|
||||
|
||||
> ⚠️ **重要提示**:CentOS 8 已于 2021 年 12 月 31 日停止维护(EOL),CentOS 7 也已于 2024 年 6 月 30 日**完全结束支持**。Docker Hub 上的 CentOS 官方镜像**已停止更新**且存在未修复的安全漏洞。
|
||||
>
|
||||
> 2026 年了,对于任何新项目,**强烈建议**使用以下生产级替代方案:
|
||||
> - [Rocky Linux](https://hub.docker.com/_/rockylinux):CentOS 原创始人发起的社区驱动项目,目前主流为 Rocky Linux 9。
|
||||
> - [AlmaLinux](https://hub.docker.com/_/almalinux):由 CloudLinux 支持的企业级发行版,提供长期支持。
|
||||
> - [CentOS Stream](https://hub.docker.com/r/centos/centos):RHEL 的上游开发分支(适合开发测试,不建议用于生产环境)。
|
||||
|
||||
该仓库位于 `https://hub.docker.com/_/centos`,提供了 CentOS 从 5 ~ 8 各个版本的镜像(仅作为历史归档,不再更新)。
|
||||
|
||||
## 使用方法
|
||||
|
||||
使用 Rocky Linux 9 替代(**推荐**):
|
||||
|
||||
```bash
|
||||
$ docker run --name rocky -it rockylinux:9 bash
|
||||
```
|
||||
|
||||
使用旧版 CentOS 7(**仅用于维护旧项目,不推荐**):
|
||||
|
||||
```bash
|
||||
$ docker run --name centos -it centos:7 bash
|
||||
```
|
||||
|
||||
## Dockerfile
|
||||
|
||||
请到 https://github.com/docker-library/docs/tree/master/centos 查看。
|
||||
@@ -1,58 +0,0 @@
|
||||
# minio
|
||||
|
||||
**MinIO** 是一个基于 Apache License v2.0 开源协议的对象存储服务。它兼容亚马逊 S3 云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几 kb 到最大 5T 不等。
|
||||
|
||||
MinIO 是一个非常轻量的服务,可以很简单的和其他应用的结合,类似 NodeJS, Redis 或者 MySQL。
|
||||
|
||||
[官方文档](https://docs.min.io/)
|
||||
|
||||
## 简单使用
|
||||
|
||||
测试、开发环境下不考虑数据存储的情况下可以使用下面的命令快速开启服务。
|
||||
|
||||
```bash
|
||||
$ docker run -d -p 9000:9000 -p 9090:9090 minio/minio server /data --console-address ':9090'
|
||||
```
|
||||
|
||||
## 离线部署
|
||||
|
||||
许多生产环境是一般是没有公网资源的,这就需要从有公网资源的服务器上把镜像导出,然后导入到需要运行镜像的内网服务器。
|
||||
|
||||
### 导出镜像
|
||||
|
||||
在有公网资源的服务器上下载好`minio/minio`镜像
|
||||
|
||||
```bash
|
||||
$ docker save -o minio.tar minio/minio:latest
|
||||
```
|
||||
|
||||
> 使用docker save 的时候,也可以使用image id 来导出,但是那样导出的时候,就会丢失原来的镜像名称,推荐,还是使用镜像名字+tag来导出镜像
|
||||
|
||||
### 导入镜像
|
||||
|
||||
把压缩文件复制到内网服务器上,使用下面的命令导入镜像
|
||||
|
||||
```bash
|
||||
$ docker load minio.tar
|
||||
```
|
||||
|
||||
### 运行 minio
|
||||
|
||||
- 把 `/mnt/data` 改成要替换的数据目录
|
||||
- 替换 `MINIO_ROOT_USER` 的值
|
||||
- 替换 `MINIO_ROOT_PASSWORD` 的值
|
||||
- 替换 name,minio1(可选)
|
||||
- 如果 9000、9090 端口冲突,替换端口前面的如 `9009:9000`
|
||||
|
||||
```bash
|
||||
$ sudo docker run -d -p 9000:9000 -p 9090:9090 --name minio1 \
|
||||
-e "MINIO_ROOT_USER=改成自己需要的" \
|
||||
-e "MINIO_ROOT_PASSWORD=改成自己需要的" \
|
||||
-v /mnt/data:/data \
|
||||
--restart=always \
|
||||
minio/minio server /data --console-address ':9090'
|
||||
```
|
||||
|
||||
### 访问 web 管理页面
|
||||
|
||||
http://x.x.x.x:9090
|
||||
@@ -1,38 +0,0 @@
|
||||
# [WordPress](https://hub.docker.com/_/wordpress/)
|
||||
|
||||
## 基本信息
|
||||
|
||||
[WordPress](https://en.wikipedia.org/wiki/WordPress) 是开源的 Blog 和内容管理系统框架,它基于 PHP 和 MySQL。
|
||||
|
||||
该仓库位于 `https://hub.docker.com/_/wordpress/` ,提供了 WordPress 4.x ~ 5.x 版本的镜像。
|
||||
|
||||
## 使用方法
|
||||
|
||||
启动容器需要 MySQL 的支持,默认端口为 `80`。
|
||||
|
||||
首先创建网络
|
||||
```bash
|
||||
$ docker network create my-wordpress-net
|
||||
```
|
||||
|
||||
启动 MySQL 容器
|
||||
```bash
|
||||
$ docker run --name some-mysql -d --network my-wordpress-net -e MYSQL_ROOT_PASSWORD=mysecretpassword mysql
|
||||
```
|
||||
|
||||
启动 WordPress 容器
|
||||
```bash
|
||||
$ docker run --name some-wordpress -d --network my-wordpress-net -e WORDPRESS_DB_HOST=some-mysql -e WORDPRESS_DB_PASSWORD=mysecretpassword wordpress
|
||||
```
|
||||
|
||||
启动 WordPress 容器时可以指定的一些环境变量包括:
|
||||
|
||||
* `WORDPRESS_DB_HOST`: MySQL 服务的主机名
|
||||
* `WORDPRESS_DB_USER`: MySQL 数据库的用户名
|
||||
* `WORDPRESS_DB_PASSWORD`: MySQL 数据库的密码
|
||||
* `WORDPRESS_DB_NAME`: WordPress 要使用的数据库名
|
||||
|
||||
|
||||
## Dockerfile
|
||||
|
||||
请到 https://github.com/docker-library/docs/tree/master/wordpress 查看。
|
||||
20
CHANGELOG.md
20
CHANGELOG.md
@@ -1,25 +1,5 @@
|
||||
# 修订记录
|
||||
|
||||
* 1.5.0 2026-02-05
|
||||
* 全面重构章节目录结构 (01-15)
|
||||
* 支持 Docker Engine v30.x
|
||||
* 优化文档图片引用路径
|
||||
|
||||
* 1.4.0 2026-01-11
|
||||
* 全面支持 Docker Engine v29 新版本
|
||||
* 更新 Docker Compose 至 v2.40.x
|
||||
* 更新 Kubernetes 相关章节至 1.35 版本
|
||||
* BuildKit 已成为默认稳定构建器,移除实验特性说明
|
||||
* 新增 Docker Scout、Docker Init 相关内容
|
||||
* 更新镜像加速器配置
|
||||
* 添加 CentOS EOL 警告,推荐使用 Rocky Linux/AlmaLinux
|
||||
* 扩充安全章节和底层架构章节内容
|
||||
|
||||
* 1.3.0 2021-12-31
|
||||
* 全面支持 Docker v20.10 新版本
|
||||
* 新增 Docker Compose v2
|
||||
* Docker Hub 自动构建转为付费功能
|
||||
|
||||
* 1.2.0 2020-12-20
|
||||
* 错误修复
|
||||
|
||||
|
||||
96
README.md
96
README.md
@@ -1,78 +1,68 @@
|
||||
# Docker — 从入门到实践
|
||||
|
||||
[](https://github.com/yeasy/docker_practice) [](https://github.com/yeasy/docker_practice/releases) [](https://github.com/docker/docker-ce) [][1]
|
||||
[](https://github.com/yeasy/docker_practice) [](https://travis-ci.org/yeasy/docker_practice) [](https://github.com/yeasy/docker_practice/releases) [](https://github.com/docker/docker-ce) [][1]
|
||||
|
||||
**v1.5.0**
|
||||
**v1.2.0**
|
||||
|
||||
| 语言 | 构建状态 | - |
|
||||
| :------------- | :------------- | :--- |
|
||||
| [zh-hans](https://github.com/yeasy/docker_practice) | [](https://travis-ci.org/yeasy/docker_practice)| [阅读](https://vuepress.mirror.docker-practice.com/) |
|
||||
| [us-en](https://github.com/yeasy/docker_practice/tree/english) | [](https://travis-ci.org/yeasy/docker_practice)| [阅读](https://docker_practice.gitee.io/us-en) |
|
||||
| [zh-hant](https://github.com/yeasy/docker_practice/tree/zh-Hant) | [](https://travis-ci.org/yeasy/docker_practice)| [阅读](https://docker_practice.gitee.io/zh-hant) |
|
||||
|
||||
[Docker](https://www.docker.com) 是个划时代的开源项目,它彻底释放了计算虚拟化的威力,极大提高了应用的维护效率,降低了云计算应用开发的成本!使用 Docker,可以让应用的部署、测试和分发都变得前所未有的高效和轻松!
|
||||
|
||||
无论是应用开发者、运维人员、还是其他信息技术从业人员,都有必要认识和掌握 Docker,节约有限的生命。
|
||||
|
||||
本书既适用于具备基础 Linux 知识的 Docker 初学者,也希望可供理解原理和实现的高级用户参考。同时,书中给出的实践案例,可供在进行实际部署时借鉴。
|
||||
本书既适用于具备基础 Linux 知识的 Docker 初学者,也希望可供理解原理和实现的高级用户参考。同时,书中给出的实践案例,可供在进行实际部署时借鉴。前六章为基础内容,供用户理解 Docker 的基本概念和操作;7 ~ 9 章介绍包括数据管理、网络等高级操作;第 10 ~ 12 章介绍了容器生态中的几个核心项目;13、14 章讨论了关于 Docker 安全和实现技术等高级话题。后续章节则分别介绍包括 Etcd、Fedora CoreOS、Kubernetes、容器云等相关热门开源项目。最后,还展示了使用容器技术的典型的应用场景和实践案例。
|
||||
|
||||
## 内容特色
|
||||
* 在线阅读:[docker-practice.com](https://vuepress.mirror.docker-practice.com/),[GitBook](https://yeasy.gitbook.io/docker_practice/),[Github](https://github.com/yeasy/docker_practice/blob/master/SUMMARY.md)
|
||||
* [离线阅读 `$ docker run -it --rm -p 4000:80 ccr.ccs.tencentyun.com/dockerpracticesig/docker_practice:vuepress`](https://github.com/yeasy/docker_practice/wiki/%E7%A6%BB%E7%BA%BF%E9%98%85%E8%AF%BB%E5%8A%9F%E8%83%BD%E8%AF%A6%E8%A7%A3)
|
||||
|
||||
* **入门基础**:前六章为基础内容,帮助深入理解 Docker 的基本概念(镜像、容器、仓库)和核心操作。
|
||||
* **进阶应用**:7 ~ 10 章涵盖数据管理、网络配置、Buildx、Compose、运维管理等高级操作。
|
||||
* **深入原理**:11 ~ 13 章介绍容器编排(Kubernetes、Etcd)、容器生态、底层实现技术。
|
||||
* **实战扩展**:14 ~ 15 章展示操作系统、CI/CD等典型应用场景和实践案例,以及工具参考。
|
||||
* **广泛扩展**:涵盖 Fedora CoreOS、容器云等热门开源项目,并展示典型的应用场景和实践案例。
|
||||
Docker 自身仍在快速发展中,生态环境也在蓬勃成长。建议初学者使用最新稳定版本的 Docker 进行学习实践。欢迎 [参与项目维护](CONTRIBUTING.md)。
|
||||
|
||||
## 阅读方式
|
||||
* [修订记录](CHANGELOG.md)
|
||||
* [贡献者名单](https://github.com/yeasy/docker_practice/graphs/contributors)
|
||||
|
||||
### 在线阅读
|
||||
> 推荐访问官方 GitBook,体验最佳。
|
||||
## 微信小程序
|
||||
|
||||
* **GitBook**: [yeasy.gitbook.io/docker_practice](https://yeasy.gitbook.io/docker_practice/)
|
||||
* **GitHub**: [github.com/yeasy/docker_practice](https://github.com/yeasy/docker_practice/blob/master/SUMMARY.md)
|
||||
* **Mirror**: [docker-practice.com](https://vuepress.mirror.docker-practice.com/)
|
||||
<p align="center">
|
||||
<img width="200" src="https://yewm28.coding-pages.com/49682252-3ac4c500-faec-11e8-86ab-eafe0139be6b.jpg">
|
||||
</p>
|
||||
|
||||
### 本地阅读
|
||||
<p align="center"><strong>微信扫码 随时随地阅读~</strong></p>
|
||||
|
||||
#### 方式 1:Docker 镜像(推荐)
|
||||
无需安装任何依赖,一条命令即可启动。
|
||||
## 技术交流
|
||||
|
||||
```bash
|
||||
docker run -it --rm -p 4000:80 ccr.ccs.tencentyun.com/dockerpracticesig/docker_practice:vuepress
|
||||
```
|
||||
启动后访问 [http://localhost:4000](http://localhost:4000)。
|
||||
[详情参考](https://github.com/yeasy/docker_practice/wiki/%E7%A6%BB%E7%BA%BF%E9%98%85%E8%AF%BB%E5%8A%9F%E8%83%BD%E8%AF%A6%E8%A7%A3)
|
||||
<p align="center">
|
||||
<img width="200" src="https://yewm28.coding-pages.com/wechat.jpg">
|
||||
</p>
|
||||
|
||||
#### 方式 2:本地构建(HonKit)
|
||||
适合想要修改内容或深度定制的读者。需要安装 Node.js 环境。
|
||||
<p align="center"><strong>微信扫码 加入群聊~ 或者微信添加 <code>dpsigs</code> 邀请入群</strong></p>
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npx honkit serve
|
||||
```
|
||||
启动后访问 [http://localhost:4000](http://localhost:4000)。
|
||||
欢迎加入 Docker 技术交流 QQ 群,分享 Docker 资源,交流 Docker 技术。
|
||||
|
||||
## 社区交流
|
||||
* QQ 群 I (已满):341410255
|
||||
* QQ 群 II (已满):419042067
|
||||
* QQ 群 III (已满):210028779
|
||||
* QQ 群 IV (已满):483702734
|
||||
* QQ 群 V (已满):460598761
|
||||
* QQ 群 VI (已满):581983671
|
||||
* QQ 群 VII (已满):252403484
|
||||
* QQ 群 VIII(已满):544818750
|
||||
* QQ 群 IX (已满):571502246
|
||||
* QQ 群 X (可加):145983035
|
||||
|
||||
欢迎加入 Docker 技术交流群,分享 Docker 资源,交流 Docker 技术。
|
||||
|
||||
* **GitHub Discussions**:[点击前往](https://github.com/yeasy/docker_practice/discussions)(技术问答、交流)
|
||||
* **GitHub Issues**:[提交 Bug](https://github.com/yeasy/docker_practice/issues/new/choose)(内容错误、建议)
|
||||
|
||||
> **交流 QQ 群**(部分已满,建议优先使用 GitHub Discussions):
|
||||
> * 341410255 (I), 419042067 (II), 210028779 (III), 483702734 (IV), 460598761 (V)
|
||||
> * 581983671 (VI), 252403484 (VII), 544818750 (VIII), 571502246 (IX), 145983035 (X)
|
||||
|
||||
## 参与贡献
|
||||
|
||||
欢迎 [参与项目维护](CONTRIBUTING.md)。
|
||||
|
||||
* [修订记录](CHANGELOG.md)
|
||||
* [贡献者名单](https://github.com/yeasy/docker_practice/graphs/contributors)
|
||||
>如果有容器相关的疑问,请通过 [Issues](https://github.com/yeasy/docker_practice/issues/new/choose) 来提出。
|
||||
|
||||
## 进阶学习
|
||||
|
||||
[][1]
|
||||
[][1]
|
||||
|
||||
《[Docker 技术入门与实战][1]》已更新到第 4 版,讲解最新容器技术栈知识,欢迎大家阅读并反馈建议。
|
||||
《[Docker 技术入门与实战][1]》第三版已经面世,介绍最新的容器技术栈,欢迎大家阅读使用并反馈建议。
|
||||
|
||||
* [京东图书][1]
|
||||
* [天猫图书](https://detail.tmall.com/item.htm?id=997383773726&skuId=6143496614475)
|
||||
* [京东图书][1]
|
||||
* [China-Pub](http://product.china-pub.com/8052127)
|
||||
|
||||
## 鼓励项目
|
||||
|
||||
@@ -82,8 +72,4 @@ npx honkit serve
|
||||
|
||||
<p align="center"><strong>欢迎鼓励项目一杯 coffee~</strong></p>
|
||||
|
||||
## Star History
|
||||
|
||||
[](https://star-history.com/#yeasy/docker_practice&Date)
|
||||
|
||||
[1]: https://item.jd.com/10200902362001.html
|
||||
[1]: https://union-click.jd.com/jdc?e=&p=AyIGZRtYFAcXBFIZWR0yEgRQH1kXAhs3EUQDS10iXhBeGlcJDBkNXg9JHU4YDk5ER1xOGRNLGEEcVV8BXURFUFdfC0RVU1JRUy1OVxUBFwNXGVscMlVYLlAaXAV1Z1JHA0dWEHVXZTliY1QLWStaJQAWB10fXhwKEDdlG1wlUHzf462DsLMO0%2F%2BUjp2VIgZlG18RBBcCUBlbEAoTBWUcWxwySVI7HAhBBxEOBUgOFQYQUGUraxYyIjdVK1glQHxXUEhYEVEUUFQcC0IHGgRRSAgVARAPAhsLFgNCDl0ZWiUAEwZREg%3D%3D&t=W1dCFFlQCxxKQgFHREkdSVJKSQVJHFRXFk9FUlpGQUpLCVBaTFhbXQtWVmpSWRtYEAYQBVUS
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user