[
  {
    "path": ".devcontainer/devcontainer.json",
    "content": "// https://code.visualstudio.com/docs/remote/devcontainerjson-reference\n\n{\n  \"image\": \"yeasy/docker_practice:latest\",\n  \"mounts\": [\n    \"source=dp-code-remote-cache,target=/root/.vscode-server,type=volume\"\n  ],\n  \"settings\": {\n    \"terminal.integrated.shell.linux\": \"/bin/sh\"\n  },\n  \"forwardPorts\": [\n    4000\n  ],\n  \"runArgs\": [\n    \"--cap-add=SYS_ADMIN\"\n  ],\n  \"postStartCommand\": [\n    \"sh\",\n    \"-cx\",\n    \"pwd ; cd /workspaces/docker_practice ; mkdir -p ${PWD}/node_modules; mkdir -p ${PWD}/_book; mount --bind /srv/gitbook/node_modules ${PWD}/node_modules ; mount --bind /mnt ${PWD}/_book\"\n  ]\n}\n"
  },
  {
    "path": ".docker/docker-entrypoint.sh",
    "content": "#!/usr/bin/env sh\n\necho\necho\necho \"Please open your browser: 127.0.0.1:4000\"\necho\necho \"欢迎加入 QQ 群：【 145983035 】 分享 Docker 资源，交流 Docker 技术\"\necho\necho\n\nexec nginx -g \"daemon off;\"\n"
  },
  {
    "path": ".editorconfig",
    "content": "# EditorConfig is awesome: https://EditorConfig.org\n\nroot = true\n\n[*]\n\nindent_style = space\n\nindent_size = 2\n\nend_of_line = lf\n\ncharset = utf-8\n\ntrim_trailing_whitespace = true\n\ninsert_final_newline = true\n\n[*.md]\n\ntrim_trailing_whitespace = false\n\n[*.py]\n\nindent_size = 4\n\n[Makefile]\n\nindent_style = tab\n"
  },
  {
    "path": ".gitattributes",
    "content": "*           text=auto eol=lf\n\n*.sh        text eol=lf\n\n*           linguist-language=go\n"
  },
  {
    "path": ".github/CODEOWNERS",
    "content": "*                     @yeasy @khs1994\n/.github/*            @khs1994\n/.travis/*            @khs1994\n/.vuepress/*          @khs1994\n/01_introduction/*    @yeasy @khs1994\n/02_basic_concept/*   @yeasy @khs1994\n/03_install/*         @khs1994\n/04_image/*           @yeasy @khs1994\n/05_container/*       @yeasy @khs1994\n/06_repository/*      @khs1994\n/07_dockerfile/*      @yeasy @khs1994\n/08_data/*            @yeasy @khs1994\n/09_network/*         @yeasy @khs1994\n/10_buildx/*          @khs1994\n/11_compose/*         @yeasy @khs1994\n/12_implementation/*  @yeasy @khs1994\n/13_kubernetes_concepts/* @yeasy @khs1994\n/14_kubernetes_setup/* @yeasy @khs1994\n/15_etcd/*            @yeasy @khs1994\n/16_cloud/*           @khs1994\n/17_ecosystem/*       @khs1994\n/18_security/*        @yeasy @khs1994\n/19_observability/*   @yeasy @khs1994\n/20_cases_os/*        @yeasy @khs1994\n/21_case_devops/*     @yeasy @khs1994\n/appendix/*           @yeasy @khs1994\n/.drone.yml           @khs1994\n/.editorconfig/       @khs1994\n/.gitattributes       @khs1994\n/.gitignore           @khs1994\n/_config.yml          @yeasy @khs1994\n/book.json            @yeasy @khs1994\n/CHANGELOG.md         @yeasy @khs1994\n/CONTRIBUTING.md      @yeasy @khs1994\n/docker-compose.yml   @khs1994\n/manifest             @khs1994\n/package.json         @khs1994\n/README.md            @yeasy @khs1994\n/SUMMARY.md           @yeasy @khs1994\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: yeasy\npatreon: # Replace with a single Patreon username\nopen_collective: # Replace with a single Open Collective username\nko_fi: # Replace with a single Ko-fi username\ntidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel\ncommunity_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry\nliberapay: # Replace with a single Liberapay username\nissuehunt: # Replace with a single IssueHunt username\notechie: # Replace with a single Otechie username\nlfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry\ncustom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/Bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\n\n---\n\n* [ ] Have u googled the problem? If no, pls do that first!\n\n### Environment\n\n<!--请提供环境信息，包括操作系统版本等，保留你的操作系统，其他选项删除-->\n<!--Provides env info like OS version-->\n\n* [x] Linux\n   * [x] CentOS 7\n   * [x] Fedora\n   * [x] Ubuntu 16.04 +\n   * [x] Debian 9 +\n* [x] macOS\n* [x] Windows 10\n* [x] Raspberry Pi (ARM)\n* [x] Others (Pls describe below)\n\n### Docker Version\n\n<!--如果你的 Docker 版本低于 20.10 请尽可能升级到该版本，保留你的 Docker 版本，其他选项删除-->\n<!--if Docker version under 20.10, please upgrade Docker to 20.10-->\n\n* [x] Test (v20.10)\n* [x] Stable (v20.10)\n* [x] 1.13.0 or Before\n\n### Problem Description\n\n<!--描述你的问题，请贴出操作步骤，终端报错截图或文字信息-->\n<!--describe problem with detailed steps and logs-->\n\n\n<!--提交问题之前请点击预览标签，符合要求之后再提交问题-->\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/Custom.md",
    "content": "---\nname: Custom issue template\nabout: Create a issue about Docker\n\n---\n\n* [ ] Have u googled the problem? If no, pls do that first!\n\n### Environment\n\n<!--请提供环境信息，包括操作系统版本等，保留你的操作系统，其他选项删除-->\n<!--Provides env info like OS version-->\n\n* [x] Linux\n   * [x] CentOS 7\n   * [x] Fedora\n   * [x] Ubuntu 16.04 +\n   * [x] Debian 9 +\n* [x] macOS\n* [x] Windows 10\n* [x] Raspberry Pi (ARM)\n* [x] Others (Pls describe below)\n\n### Docker Version\n\n<!--如果你的 Docker 版本低于 20.10 请尽可能升级到该版本，保留你的 Docker 版本，其他选项删除-->\n<!--if Docker version under 20.10, please upgrade Docker to 20.10-->\n\n* [x] Test (v20.10)\n* [x] Stable (v20.10)\n* [x] 1.13.0 or Before\n\n### Problem Description\n\n<!--描述你的问题，请贴出操作步骤，终端报错截图或文字信息-->\n<!--describe problem with detailed steps and logs-->\n\n\n<!--提交问题之前请点击预览标签，符合要求之后再提交问题-->\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/Feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for docker_practice\n\n---\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "<!--\n    Thanks for your contribution.\n    See [CONTRIBUTING](../CONTRIBUTING.md) for contribution guidelines.\n-->\n\n**Proposed changes (Mandatory)**\n\n<!--\n    Tell us what you did and why:\n\n    One line short description\n\n    And details in other paragraphs.\n-->\n\n**Fix issues (Optional)**\n\n<!--\n    Tell us what issues you fixed, e.g., fix #123\n-->\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: \"npm\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n    commit-message:\n      prefix: \"chore(deps)\"\n    labels:\n      - \"dependencies\"\n    groups:\n      dependencies:\n        patterns:\n          - \"*\"\n\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n    commit-message:\n      prefix: \"chore(deps)\"\n    labels:\n      - \"dependencies\"\n    groups:\n      dependencies:\n        patterns:\n          - \"*\"\n"
  },
  {
    "path": ".github/workflows/check-link.yml",
    "content": "name: Check link\n\non:\n  workflow_dispatch:\n\njobs:\n  check-link:\n    name: check-link\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6.0.2\n      # search Issues :-(\n      - run: |\n          docker run -i --rm \\\n          -v $PWD:/mnt:ro \\\n          dkhamsing/awesome_bot \\\n          --white-list \"localhost\",\"0.0.0.0\",\\\n          \"server\",\"example.com\",\"docker\",\\\n          \"docker.domain.com\",\"YourIP\",\"register\",\\\n          \"172.16.238.100\",\"172.16.238.101\",\"172.16.238.102\",\\\n          \"192.168.199.100\",\\\n          \"github.com/settings\",\\\n          \"github.com/docker/compose/releases/download\",\\\n          \"github.com/etcd-io/etcd/releases/download\",\\\n          \"github.com/tianon/gosu/releases/download\",\\\n          \"github.com/yeasy/docker_practice\",\\\n          \"github.com/AliyunContainerService/k8s-for-docker-desktop/raw\",\\\n          \"dl-cdn.alpinelinux.org/alpine/edge/testing\",\\\n          \"www.w3.org/1999/xhtml\",\\\n          \"cr.console.aliyun.com\",\\\n          \"cloud.tencent.com\",\\\n          \"nodejs.org/dist/\",\\\n          \"c.163.com/hub\",\\\n          \"drone.yeasy.com\",\\\n          \"docs.docker.com\",\\\n          \"dockerhub.azk8s.cn\",\\\n          \"reg-mirror.qiniu.com\",\\\n          \"registry.docker-cn.com\",\\\n          \"mirror.ccs.tencentyun.com\",\\\n          \"vuepress.mirror.docker-practice.com\",\\\n          \"mc.qcloudimg.com/static/img\",\\\n          \"www.daocloud.io/mirror\",\\\n          \"download.docker.com\",\\\n          \"www.ubuntu.com\",\\\n          \"archive.ubuntu.com\",\\\n          \"security.ubuntu.com/ubuntu\",\\\n          \"nginx.com\",\\\n          \"img.shields.io/github/release/yeasy/docker_practice\",\\\n          \"launchpad.net\",\\\n          \"www.w3.org/1999\",\\\n          \"chat.freenode.net\",\\\n          \"en.wikipedia.org/wiki/UnionFS\",\\\n          \"product.china-pub.com\",\\\n          \"union-click.jd.com\",\\\n          \"x.x.x.x/base\",\\\n          \"x.x.x.x:9090\",\\\n          \"yeasy.gitbooks.io\",\\\n          \"download.fastgit.org\",\\\n          \"www.aliyun.com\" \\\n          --allow-dupe \\\n          --skip-save-results \\\n          -t 10 \\\n          `find . \\( -path \"./mesos\" -o -path \"./swarm_mode\" \\) -prune -o -name \"*.md\" -exec ls {} \\;`\n        name: check-link\n        timeout-minutes: 25\n"
  },
  {
    "path": ".github/workflows/ci.yaml",
    "content": "name: CI\n\non:\n  push:\n  pull_request:\n  workflow_dispatch:\n\ndefaults:\n  run:\n    shell: bash --noprofile --norc -exo pipefail {0}\n\njobs:\n  build:\n    name: Build\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6.0.2\n      - uses: actions/setup-node@v6\n        with:\n          node-version: 20\n      - name: Install docs dependencies\n        run: npm install\n      - name: Build Gitbook\n        run: npm run honkit:build\n      - name: Build Gitbook Docker Image\n        if: github.repository == 'docker-practice/docker_practice'\n        run: |\n          sudo chmod -R 777 _book\n          echo \"FROM nginx:alpine\" >> Dockerfile\n          echo \"COPY _book /usr/share/nginx/html\" >> Dockerfile\n          echo \"COPY .docker/docker-entrypoint.sh /\" >> Dockerfile\n          echo \"ENTRYPOINT [\\\"/docker-entrypoint.sh\\\"]\" >> Dockerfile\n\n          export VCS_REF=`git rev-parse --short HEAD`\n\n          docker build \\\n          -t dockerpracticesig/docker_practice \\\n          -t dockerpracticesig/docker_practice:gitbook \\\n          --label org.opencontainers.image.revision=$VCS_REF \\\n          --label org.opencontainers.image.source=\"https://github.com/yeasy/docker_practice\" \\\n          --label maintainer=\"https://github.com/docker-practice\" \\\n          .\n\n          docker run -d --rm -p 4000:80 dockerpracticesig/docker_practice\n\n          sleep 5\n\n          echo \"::group::Test\"\n          curl 127.0.0.1:4000\n          echo \"::endgroup::\"\n\n          echo \"$DOCKER_PASSWORD\" | docker login -u \"$DOCKER_USERNAME\" --password-stdin\n          docker push dockerpracticesig/docker_practice\n          docker push dockerpracticesig/docker_practice:gitbook\n        env:\n          DOCKER_PASSWORD: ${{secrets.DOCKER_PASSWORD}}\n          DOCKER_USERNAME: ${{secrets.DOCKER_USERNAME}}\n      - name: Upload Gitbook dist\n        uses: docker://pcit/pages\n        if: github.repository == 'docker-practice/docker_practice'\n        env:\n          PCIT_EMAIL: khs1994@khs1994.com\n          PCIT_GIT_TOKEN: ${{ secrets.PCIT_GIT_TOKEN }}\n          PCIT_GIT_URL: github.com/docker-practice/zh-cn\n          PCIT_KEEP_HISTORY: \"true\"\n          PCIT_LOCAL_DIR: _book\n          PCIT_MESSAGE: Build from yeasy/docker_practice@${{github.sha}}\n          PCIT_TARGET_BRANCH: master\n          PCIT_USERNAME: khs1994\n      - name: vuepress\n        run: |\n          export NODE_OPTIONS=--openssl-legacy-provider\n          sudo rm -rf _book\n\n          git clone https://github.com/docker-practice/.vuepress  .vuepress2\n          cp -r .vuepress2/. .vuepress/\n          rm -rf .vuepress2\n          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' {} \\;\n\n          npx vuepress --version\n\n          npm run vuepress:build\n          echo \"vuepress.mirror.docker-practice.com\" > .vuepress/dist/CNAME\n\n          cp -r _images .vuepress/dist\n          mkdir -p .vuepress/dist/appendix\n          cp -r appendix/_images .vuepress/dist/appendix\n          mkdir -p .vuepress/dist/cloud\n          cp -r 16_cloud/_images .vuepress/dist/cloud\n          mkdir -p .vuepress/dist/data_management\n          cp -r 08_data/_images .vuepress/dist/data_management\n          mkdir -p .vuepress/dist/etcd\n          cp -r 15_etcd/_images .vuepress/dist/etcd\n          mkdir -p .vuepress/dist/kubernetes\n          cp -r 13_kubernetes_concepts/_images .vuepress/dist/kubernetes\n\n          echo \"include: [_images]\" > .vuepress/dist/_config.yml\n      - name: Upload Vuepress dist\n        uses: docker://pcit/pages\n        if: github.repository == 'docker-practice/docker_practice'\n        env:\n          PCIT_EMAIL: khs1994@khs1994.com\n          PCIT_GIT_TOKEN: ${{ secrets.PCIT_GIT_TOKEN }}\n          PCIT_GIT_URL: github.com/docker-practice/vuepress\n          PCIT_KEEP_HISTORY: \"true\"\n          PCIT_LOCAL_DIR: .vuepress/dist\n          PCIT_MESSAGE: Build from yeasy/docker_practice@${{github.sha}}\n          PCIT_TARGET_BRANCH: master\n          PCIT_USERNAME: khs1994\n      # - name: Set coding.net CNAME\n      #   run: |\n      #     echo \"vuepress.mirror.docker-practice.com\" > .vuepress/dist/CNAME\n      # - name: Upload Vuepress dist to coding.net\n      #   uses: docker://pcit/pages\n      #   if: github.repository == 'docker-practice/docker_practice'\n      #   env:\n      #     PCIT_EMAIL: khs1994@khs1994.com\n      #     PCIT_GIT_TOKEN: ${{ secrets.CODING_GIT_TOKEN }}\n      #     PCIT_GIT_URL: e.coding.net/dpsigs/docker_practice\n      #     PCIT_KEEP_HISTORY: \"true\"\n      #     PCIT_LOCAL_DIR: .vuepress/dist\n      #     PCIT_MESSAGE: Build from yeasy/docker_practice@${{github.sha}}\n      #     PCIT_TARGET_BRANCH: master\n      #     PCIT_USERNAME: ptt0xjqzbke3\n      - name: Build vuepress docker image\n        if: github.repository == 'docker-practice/docker_practice'\n        run: |\n          sudo rm -rf .vuepress/dist/.git\n\n          echo \"FROM nginx:alpine\" > Dockerfile\n          echo \"COPY .vuepress/dist /usr/share/nginx/html\" >> Dockerfile\n          echo \"COPY .docker/docker-entrypoint.sh /\" >> Dockerfile\n          echo \"ENTRYPOINT [\\\"/docker-entrypoint.sh\\\"]\" >> Dockerfile\n\n          echo \"$DOCKER_PASSWORD\" | docker login -u \"$DOCKER_USERNAME\" --password-stdin\n\n          VCS_REF=`git rev-parse --short HEAD`\n\n          docker build -t dockerpracticesig/docker_practice:vuepress \\\n          --label org.opencontainers.image.revision=$VCS_REF \\\n          --label org.opencontainers.image.source=\"https://github.com/yeasy/docker_practice\" \\\n          --label maintainer=\"https://github.com/docker-practice\" \\\n          .\n\n          docker push dockerpracticesig/docker_practice:vuepress\n\n          docker run -it --rm -d -p 4001:80 dockerpracticesig/docker_practice:vuepress\n\n          sleep 5\n\n          echo \"::group::Test\"\n          curl 127.0.0.1:4001\n          echo \"::endgroup::\"\n        env:\n          DOCKER_PASSWORD: ${{secrets.DOCKER_PASSWORD}}\n          DOCKER_USERNAME: ${{secrets.DOCKER_USERNAME}}\n"
  },
  {
    "path": ".github/workflows/dependabot-automerge.yml",
    "content": "name: Dependabot auto-merge\non: pull_request\n\npermissions:\n  contents: write\n  pull-requests: write\n\njobs:\n  dependabot:\n    runs-on: ubuntu-latest\n    if: github.actor == 'dependabot[bot]'\n    steps:\n      - name: Dependabot metadata\n        id: metadata\n        uses: dependabot/fetch-metadata@v2\n        with:\n          github-token: \"${{ secrets.GITHUB_TOKEN }}\"\n      - name: Approve a PR\n        run: gh pr review --approve \"$PR_URL\"\n        env:\n          PR_URL: ${{github.event.pull_request.html_url}}\n          GH_TOKEN: ${{secrets.GITHUB_TOKEN}}\n      - name: Enable auto-merge for Dependabot PRs\n        run: gh pr merge --auto --merge \"$PR_URL\"\n        env:\n          PR_URL: ${{github.event.pull_request.html_url}}\n          GH_TOKEN: ${{secrets.GITHUB_TOKEN}}\n"
  },
  {
    "path": ".github/workflows/release-pdf.yml",
    "content": "name: Build PDF on Release\n\non:\n  release:\n    types: [published]\n  workflow_dispatch:\n\npermissions:\n  contents: write\n\njobs:\n  build-pdf:\n    name: Generate PDF\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n\n      - name: Install Chromium\n        run: |\n          sudo apt-get update\n          sudo apt-get install -y chromium-browser\n\n      - name: Install mdpress (latest)\n        run: |\n          LATEST_TAG=$(curl -fsSL https://api.github.com/repos/yeasy/mdpress/releases/latest | jq -r .tag_name)\n          VERSION=\"${LATEST_TAG#v}\"\n          echo \"Installing mdpress $VERSION\"\n          curl -fsSL \"https://github.com/yeasy/mdPress/releases/download/$LATEST_TAG/mdpress_${VERSION}_linux_amd64.tar.gz\" -o mdpress.tar.gz\n          tar xzf mdpress.tar.gz\n          sudo mv mdpress /usr/local/bin/\n          mdpress --version\n\n      - name: Build PDF\n        run: mdpress build --format pdf --output docker_practice.pdf\n\n      - name: Upload PDF to Release\n        if: github.event_name == 'release'\n        uses: softprops/action-gh-release@v2\n        with:\n          files: docker_practice.pdf\n\n      - name: Upload PDF as artifact\n        if: github.event_name == 'workflow_dispatch'\n        uses: actions/upload-artifact@v4\n        with:\n          name: docker_practice-pdf\n          path: docker_practice.pdf\n"
  },
  {
    "path": ".gitignore",
    "content": "# Created by .gitignore support plugin (hsz.mobi)\n*.~\n*.tmp\n.idea/\n_book/\nformat_report.txt\n*.swp\n*.edx\n.DS_Store\n\nnode_modules/\npackage-lock.json\n\ndocker-compose.override.yml\n\n# Editor configs\n.obsidian/\n.vscode/\n\n.agent/\n__pycache__/\n\n# Check scripts\ncheck*.py\nfind*.py\nfix*.py\nformat*.py\n"
  },
  {
    "path": ".vuepress/.gitignore",
    "content": "/*\n!.gitignore\n!config.js\n"
  },
  {
    "path": ".vuepress/config.js",
    "content": "const { config } = require('vuepress-theme-hope')\n\nmodule.exports = config({\n  title: 'Docker 从入门到实践',\n  base: '/',\n  head: [['script', {}, `\n  var _hmt = _hmt || [];\n  (function() {\n  var hm = document.createElement(\"script\");\n  hm.src = \"//hm.baidu.com/hm.js?81a3490c9cd141dbcf6d00bc18b6edae\";\n  var s = document.getElementsByTagName(\"script\")[0];\n  s.parentNode.insertBefore(hm, s);\n  })();\n`],\n  [\n    'script', {}, `\n  (function(){\n    var bp = document.createElement('script');\n    var curProtocol = window.location.protocol.split(':')[0];\n    if (curProtocol === 'https') {\n        bp.src = 'https://zz.bdstatic.com/linksubmit/push.js';\n    }\n    else {\n        bp.src = 'http://push.zhanzhang.baidu.com/push.js';\n    }\n    var s = document.getElementsByTagName(\"script\")[0];\n    s.parentNode.insertBefore(bp, s);\n})();\n  `\n  ]\n  ],\n  plugins: {\n    // sitemap: {\n    //   hostname: 'https://vuepress.mirror.docker-practice.com'\n    // },\n    // 'git-log': {\n    //   additionalArgs: '--no-merge',\n    //   onlyFirstAndLastCommit: true,\n    // },\n  },\n  locales: {\n    \"/\": {\n      lang: \"zh-CN\"\n    }\n  },\n  themeConfig: {\n    blog: false,\n    // comment: false,\n    comment: {\n      type: \"disable\", // 使用 Valine\n      appId: \"...\", // your appId\n      appKey: \"...\", // your appKey\n    },\n    pageInfo: [\n      // 'author',\n      'reading-time',\n      'word',\n    ],\n    footer: {\n      content: \"Made with <a target='_blank' href='https://github.com/vuepress-theme-hope/vuepress-theme-hope'>vuepress-theme-hope</a>\",\n      display: true,\n      copyright: false,\n    },\n    searchPlaceholder: 'Search',\n    repo: 'yeasy/docker_practice',\n    repoLabel: 'GitHub',\n    repoDisplay: true,\n    hostname: 'https://vuepress.mirror.docker-practice.com',\n    // author: 'yeasy',\n    mdEnhance: {\n      lineNumbers: true,\n    },\n    git: {\n      contributor: false,\n    },\n    themeColor: {\n      blue: '#2196f3',\n      // red: '#f26d6d',\n      // green: '#3eaf7c',\n      // orange: '#fb9b5f'\n    },\n    locales: {\n      \"/\": {\n        lang: \"zh-CN\"\n      }\n    },\n\n    darkmode: 'auto-switch',\n\n    //\n\n    showAds: true,\n\n    docsRepo: 'yeasy/docker_practice',\n    docsDir: '/',\n    docsBranch: 'master',\n    editLinks: true,\n    nav: [\n      {\n        text: '微信交流群',\n        link: 'https://docker_practice.gitee.io/pic/dpsig-wechat.jpg',\n      },\n      {\n        text: '小程序',\n        link: 'https://docker_practice.gitee.io/pic/dp-wechat-miniprogram.jpg',\n      },\n      {\n        text: '安装 Docker',\n        link: '/03_install/',\n      },\n      {\n        text: 'Docker 入门',\n        link: '/'\n      },\n      {\n        text: 'Docker 实战',\n        link: '/15_cases/os/'\n      },\n      {\n        text: 'CI/CD',\n        link: '/15_cases/ci/'\n      },\n      {\n        text: 'Compose',\n        link: '/10_compose/',\n      },\n      {\n        text: 'Kubernetes',\n        link: '/12_orchestration/kubernetes/',\n      },\n      {\n        text: \"云计算\",\n        link: \"/13_ecosystem/cloud/\",\n      },\n      // {\n      //   text: 'GitHub',\n      //   link: 'https://github.com/yeasy/docker_practice'\n      // },\n      // {\n      //   text: '捐赠',\n      //   link: ''\n      // },\n      {\n        text: '云服务器99/元首年特惠',\n        link: 'https://cloud.tencent.com/act/cps/redirect?redirect=1062&cps_key=3a5255852d5db99dcd5da4c72f05df61&from=console'\n      },\n      // {\n      //   text: '语言',\n      //   items: [{\n      //     text: 'English',\n      //     link: ''\n      //   }]\n      // }\n    ],\n    sidebar: \"auto\",\n    legacySidebar: {\n      '/cloud/': [\n        'intro',\n        'tencentCloud',\n        'alicloud',\n        'aws',\n        'summary',\n      ],\n      '/kubernetes/': [\n        'intro',\n        'concepts',\n        'design',\n        {\n          title: \"部署 Kubernetes\",\n          collapsable: true,\n          children: [\n            \"setup/\",\n            \"setup/kubeadm\",\n            \"setup/docker-desktop\",\n            \"setup/systemd\",\n            \"setup/dashboard\",\n          ]\n        },\n        {\n          title: \"Kubernetes 命令行 kubectl\",\n          collapsable: true,\n          children: [\n            'kubectl/'\n          ]\n        }\n      ],\n      '/compose/': [\n        'introduction',\n        'v2',\n        'install',\n        'usage',\n        'commands',\n        'compose_file',\n        'django',\n        'rails',\n        'wordpress',\n        'lnmp',\n      ],\n      '/install/': [\n        'ubuntu',\n        'debian',\n        'fedora',\n        'centos',\n        'raspberry-pi',\n        // 'offline',\n        'mac',\n        'windows',\n        'mirror',\n        'experimental',\n      ],\n      '/cases/os/': [\n        {\n          title: \"操作系统\",\n          collapsable: false,\n          children: [\n            'busybox',\n            'alpine',\n            'debian',\n            'centos',\n            'summary',\n          ],\n        },\n        {\n          title: \"在 IDE 中使用 Docker\",\n          collapsable: false,\n          children: [\n            '/ide/',\n            '/ide/vsCode',\n          ],\n        },\n      ],\n      '/cases/ci/': [\n        'actions/',\n        {\n          title: \"Drone\",\n          collapsable: true,\n          children: [\n            'drone/',\n            'drone/install'\n          ]\n        },\n      ],\n      '/': [\n        '/',\n        '/CHANGELOG',\n        '/CONTRIBUTING',\n        {\n          title: \"Docker 简介\",\n          collapsable: false,\n          children: [\n            'introduction/',\n            'introduction/what',\n            'introduction/why',\n          ]\n        }, {\n          title: \"基本概念\",\n          collapsable: false,\n          children: [\n            'basic_concept/',\n            'basic_concept/image',\n            'basic_concept/container',\n            'basic_concept/repository'\n          ]\n        },\n        {\n          title: \"使用镜像\",\n          collapsable: false,\n          children: [\n            'image/',\n            'image/pull',\n            'image/list',\n            'image/rm',\n            'image/commit',\n            'image/build',\n            'image/other.md',\n            'image/internal.md',\n          ]\n        },\n        {\n          title: 'Dockerfile',\n          collapsable: true,\n          children: [\n            \"image/dockerfile/\",\n            'image/dockerfile/copy',\n            'image/dockerfile/add',\n            'image/dockerfile/cmd',\n            'image/dockerfile/entrypoint',\n            'image/dockerfile/env',\n            'image/dockerfile/arg',\n            'image/dockerfile/volume',\n            'image/dockerfile/expose',\n            'image/dockerfile/workdir',\n            'image/dockerfile/user',\n            'image/dockerfile/healthcheck',\n            'image/dockerfile/label',\n            'image/dockerfile/shell',\n            'image/dockerfile/onbuild',\n            'image/dockerfile/references',\n            'image/dockerfile/7.17_multistage_builds.md',\n            'image/dockerfile/7.18_multistage_builds_laravel.md',\n            'image/manifest',\n          ]\n        }, {\n          title: \"操作容器\",\n          collapsable: false,\n          children: [\n            'container/',\n            'container/run',\n            'container/daemon',\n            'container/stop',\n            'container/attach_exec',\n            'container/import_export',\n            'container/rm',\n          ],\n        },\n        {\n          title: \"Docker 仓库\",\n          collapsable: false,\n          children: [\n            'repository/',\n            'repository/dockerhub',\n            'repository/registry',\n            'repository/registry_auth',\n            'repository/nexus3_registry',\n          ],\n        },\n        {\n          title: \"数据管理\",\n          collapsable: false,\n          children: [\n            'data_management/',\n            'data_management/volume',\n            'data_management/bind-mounts',\n          ],\n        }, {\n          title: \"使用网络\",\n          collapsable: false,\n          children: [\n            'network/',\n            'network/port_mapping',\n            'network/linking',\n            'network/dns',\n          ],\n        },\n        {\n          title: \"高级网络配置\",\n          collapsable: true,\n          children: [\n            'advanced_network/',\n            'advanced_network/quick_guide',\n            'advanced_network/access_control',\n            'advanced_network/port_mapping',\n            'advanced_network/bridge',\n            'advanced_network/example',\n            'advanced_network/config_file',\n            'advanced_network/ptp',\n          ],\n        },\n        {\n          title: \"Swarm mode\",\n          collapsable: true,\n          children: [\n            'swarm_mode/',\n            'swarm_mode/overview',\n            'swarm_mode/create',\n            'swarm_mode/deploy',\n            'swarm_mode/stack',\n            'swarm_mode/secret',\n            'swarm_mode/config',\n            'swarm_mode/rolling_update',\n          ],\n        },\n        {\n          title: \"安全\",\n          collapsable: true,\n          children: [\n            'security/',\n            'security/kernel_ns',\n            'security/control_group',\n            'security/daemon_sec',\n            'security/kernel_capability',\n            'security/other_feature',\n            'security/summary',\n          ],\n        },\n        {\n          title: \"底层实现\",\n          collapsable: true,\n          children: [\n            'underly/',\n            'underly/arch',\n            'underly/namespace',\n            'underly/cgroups',\n            'underly/ufs',\n            'underly/container_format',\n            'underly/network',\n          ],\n        },\n        {\n          title: \"Docker Buildx\",\n          collapsable: false,\n          children: [\n            \"buildx/\",\n            \"buildx/buildkit\",\n            \"buildx/buildx\",\n            \"buildx/multi-arch-images\",\n          ],\n        },\n        {\n          title: \"Etcd\",\n          collapsable: true,\n          children: [\n            'etcd/',\n            'etcd/intro',\n            'etcd/install',\n            'etcd/cluster',\n            'etcd/etcdctl',\n          ],\n        },\n        {\n          title: \"Fedora CoreOS\",\n          collapsable: true,\n          children: [\n            'coreos/',\n            'coreos/intro',\n            'coreos/install',\n          ],\n        },\n        'podman/',\n        'appendix/faq/',\n        {\n          title: \"热门镜像介绍\",\n          collapsable: true,\n          children: [\n            'appendix/repo/',\n            'appendix/repo/ubuntu',\n            'appendix/repo/centos',\n            'appendix/repo/nginx',\n            'appendix/repo/php',\n            'appendix/repo/nodejs',\n            'appendix/repo/mysql',\n            'appendix/repo/wordpress',\n            'appendix/repo/mongodb',\n            'appendix/repo/redis',\n            'appendix/repo/minio',\n          ],\n        },\n        {\n          title: \"Docker 命令\",\n          collapsable: true,\n          children: [\n            'appendix/command/',\n            'appendix/command/docker',\n            'appendix/command/dockerd',\n          ]\n        },\n        'appendix/best_practices',\n        'appendix/debug',\n        'appendix/resources',\n      ],\n    },\n  }\n});\n"
  },
  {
    "path": ".zhlintignore",
    "content": "node_modules/\n.vuepress/\n.git/\n"
  },
  {
    "path": "01_introduction/1.1_quickstart.md",
    "content": "## 1.1 快速上手\n\n本节将通过一个简单的 Web 应用例子，带你快速体验 Docker 的核心流程：构建镜像、运行容器。\n\n### 1.1.1 准备代码\n\n创建一个名为 `hello-docker` 的文件夹，并在其中创建一个 `index.html` 文件：\n\n```html\n<h1>Hello, Docker!</h1>\n```\n\n### 1.1.2 编写 Dockerfile\n\n在同级目录下创建一个名为 `Dockerfile` (无后缀) 的文件：\n\n```dockerfile\nFROM nginx:alpine\nCOPY index.html /usr/share/nginx/html/index.html\n```\n\n### 1.1.3 构建镜像\n\n打开终端，进入该目录，执行构建命令：\n\n```bash\n$ docker build -t my-hello-world .\n```\n\n* `docker build`：构建命令\n* `-t my-hello-world`：给镜像起个名字 (标签)\n* `.`：指定上下文路径为当前目录\n\n### 1.1.4 运行容器\n\n使用刚才构建的镜像启动一个容器：\n\n```bash\n$ docker run -d -p 8080:80 my-hello-world\n```\n\n* `docker run`：运行命令\n* `-d`：后台运行\n* `-p 8080:80`：将宿主机的 8080 端口映射到容器的 80 端口\n\n### 1.1.5 访问测试\n\n打开浏览器访问 [http://localhost:8080](http://localhost:8080)，你应该能看到 “Hello, Docker!”。\n\n### 1.1.6 清理\n\n停止并删除容器：\n\n```bash\n## 查看正在运行的容器 ID\n\n$ docker ps\n\n## 停止容器\n\n$ docker stop <CONTAINER_ID>\n\n## 删除容器\n\n$ docker rm <CONTAINER_ID>\n```\n\n恭喜！你已经完成了第一次 Docker 实战。接下来请阅读 [Docker 核心概念](../02_basic_concept/README.md)做深入了解。\n"
  },
  {
    "path": "01_introduction/1.2_what.md",
    "content": "## 1.2 什么是 Docker\n\nDocker 是彻底改变了软件开发和交付方式的革命性技术。本节将从核心概念、与传统虚拟机的对比、技术基础以及历史生态等多个维度，带你深入理解什么是 Docker。\n\n### 1.2.1 一句话理解 Docker\n\n> **Docker 是一种轻量级的虚拟化技术，它让应用程序及其依赖环境可以被打包成一个标准化的单元，在任何地方都能一致地运行。** 如果用一个生活中的类比：**Docker 之于软件，就像集装箱之于货物**。\n\n在集装箱发明之前，货物的运输是一件麻烦的事情——不同的货物需要不同的包装、不同的装卸方式，换一种运输工具就要重新装卸。集装箱的出现改变了这一切：无论里面装的是什么，集装箱的外形是标准的，可以用同样的方式装卸、堆放和运输。\n\nDocker 做的事情类似：无论你的应用是用 Python、Java、Node.js 还是其他语言写的，无论它需要什么样的依赖库和环境，一旦被打包成 Docker 镜像，就可以用同样的方式在任何支持 Docker 的机器上运行。\n\n### 1.2.2 Docker 的核心价值\n\n笔者认为，Docker 解决的是软件开发中最古老的问题之一：**“在我机器上明明能跑啊！”**\n\n```mermaid\nflowchart LR\n    subgraph Dev [\"开发环境\"]\n        direction TB\n        A[\"Python 3.14<br/>Ubuntu 24.04<br/>特定版本的库\"] --> B[\"运行正常\"]\n    end\n    subgraph Prod [\"生产环境\"]\n        direction TB\n        C[\"Python 3.11<br/>Ubuntu 22.04<br/>不同版本的库\"] --> D[\"运行失败！\"]\n    end\n    A -.->|不一致| C\n```\n\n有了 Docker：\n\n```mermaid\nflowchart LR\n    subgraph Dev [\"开发环境\"]\n        direction TB\n        A[\"Docker 镜像<br/>(包含所有依赖)\"] --> B[\"运行正常\"]\n    end\n    subgraph Prod [\"生产环境\"]\n        direction TB\n        C[\"同一个镜像<br/>(完全一致)\"] --> D[\"运行正常！\"]\n    end\n    A == 一致 ==> C\n```\n\n### 1.2.3 Docker vs 虚拟机\n\n很多人第一次接触 Docker 时会问：**“这不就是虚拟机吗？”** 答案是：**不是，而且差别很大。**\n\n#### 传统虚拟机\n\n传统虚拟机技术是虚拟出一套完整的硬件，在其上运行一个完整的操作系统，再在该系统上运行应用：\n\n![传统虚拟化](../_images/virtualization.png)\n\n#### Docker 容器\n\n而 Docker 容器内的应用直接运行于宿主的内核，容器内没有自己的内核，也没有进行硬件虚拟：\n\n![Docker](../_images/docker.png)\n\n#### 关键区别\n\n| 特性 | Docker 容器 | 传统虚拟机 |\n|------|-------------|------------|\n| **启动速度** | 秒级 | 分钟级 |\n| **资源占用** | MB 级别 | GB 级别 |\n| **性能** | 接近原生 | 有明显损耗 |\n| **隔离级别** | 进程级隔离 | 完全隔离 |\n| **单机数量** | 可运行上千个 | 通常几十个 |\n\n> 笔者经常用这个类比来解释：虚拟机像是每个应用都住在一栋独立的房子里 (有自己的地基、水电系统)，而容器像是大家住在同一栋公寓楼里的不同房间 (共享地基和水电系统，但各自独立)。\n\n### 1.2.4 Docker 的技术基础\n\nDocker 使用 [Go 语言](https://golang.google.cn/)开发，基于 Linux 内核的以下技术：\n\n- **[Namespace](https://en.wikipedia.org/wiki/Linux_namespaces)**：实现资源隔离 (进程、网络、文件系统等)\n- **[Cgroups](https://zh.wikipedia.org/wiki/Cgroups)**：实现资源限制 (CPU、内存、I/O 等)\n- **[Union FS](https://en.wikipedia.org/wiki/Union_mount)**：实现分层存储 (如 OverlayFS)\n\n> 如果你对这些底层技术感兴趣，可以阅读本书的[底层实现](../12_implementation/README.md)章节。\n\n#### Docker 架构演进\n\nDocker 的底层实现经历了多次演进：\n\n```mermaid\nflowchart LR\n    subgraph Timeline\n        direction LR\n        LXC[\"LXC<br/>(2013)\"] --> libcontainer[\"libcontainer<br/>(2014)\"]\n        libcontainer --> runC[\"runC<br/>(2015)\"]\n        runC --> containerd[\"containerd + runC<br/>(现在)\"]\n        runC --> OCI[\"OCI<br/>标准化\"]\n    end\n```\n\n- **LXC** (2013)：Docker 最初基于 Linux Containers\n- **libcontainer** (2014，v0.7)：Docker 自研的容器运行时\n- **runC** (2015，v1.11)：捐献给 OCI 的标准容器运行时\n- **containerd**：高级容器运行时，管理容器生命周期\n\n![Docker 架构](../_images/docker-on-linux.png)\n\n> `runc` 是一个 Linux 命令行工具，用于根据 [OCI 容器运行时规范](https://github.com/opencontainers/runtime-spec)创建和运行容器。\n\n> `containerd` 是一个守护程序，它管理容器生命周期，提供了在一个节点上执行容器和管理镜像的最小功能集。\n\n### 1.2.5 Docker 的历史与生态\n\n**Docker** 最初是 `dotCloud` 公司创始人 [Solomon Hykes](https://github.com/shykes) 在法国期间发起的一个公司内部项目，于 [2013 年 3 月以 Apache 2.0 授权协议开源](https://en.wikipedia.org/wiki/Docker_(software))。\n\nDocker 的发展历程：\n\n- **2013 年 3 月**：开源发布\n- **2013 年底**：dotCloud 公司改名为 Docker，Inc。\n- **2015 年**：成立[开放容器联盟 (OCI)](https://opencontainers.org/)，推动容器标准化\n- **至今**：[GitHub 项目](https://github.com/moby/moby)超过 7 万星标\n\nDocker 的成功推动了整个容器生态的发展，催生了 Kubernetes、Podman 等众多相关项目。笔者认为，Docker 最大的贡献不仅是技术本身，更是它 **让容器技术从系统管理员的工具变成了每个开发者都能使用的标准工具**。\n"
  },
  {
    "path": "01_introduction/1.3_why.md",
    "content": "## 1.3 为什么要用 Docker\n\n在回答 “为什么用 Docker” 之前，笔者想先问一个问题：**你有没有经历过这些场景？**\n\n### 1.3.1 没有 Docker 的世界\n\n在 Docker 出现之前，软件开发和运维面临着诸多棘手的问题。我们先来看看以下三个典型的痛点场景。\n\n#### 场景一：“在我电脑上明明能跑”\n\n```text\n周五下午 5:00\n├── 开发者：代码写完了，本地测试通过，提交！🎉\n├── 周一早上 9:00\n│   └── 测试：\"这个功能在测试环境跑不起来\"\n└── 开发者：\"不可能，在我电脑上明明能跑啊……\"\n```\n\n笔者统计过，这个问题通常由以下原因导致：\n\n- Python/Node/Java 版本不一致\n- 依赖库版本不一致\n- 操作系统配置不一致\n- 某些环境变量没有设置\n- “哦，忘了说我本地装了个 XXX”\n\n#### 场景二：环境配置的噩梦\n\n```bash\n新同事入职\n├── Day 1：领电脑，配环境\n├── Day 2：继续配环境，遇到问题\n├── Day 3：换种方法配环境\n├── Day 4：问老同事怎么配的，他也忘了\n└── Day 5：终于能跑起来了！但不知道为什么……\n```\n\n#### 场景三：服务器迁移的恐惧\n\n```bash\n运维：\"我们需要把服务迁移到新服务器\"\n开发：\"旧服务器上的配置文档在哪？\"\n运维：\"当时是一个已经离职的同事配的……\"\n所有人：😱\n```\n\n### 1.3.2 Docker 如何解决这些问题\n\nDocker 的出现为上述问题提供了完美的解决方案。它通过 “一次构建，到处运行” 的核心理念，从根本上改变了软件交付的方式。\n\n#### 核心理念：一次构建，到处运行\n\n```mermaid\nflowchart LR\n    dev[\"开发团队\"] -->|创建| img[\"Docker 镜像\"]\n    img -->|测试团队验证| test[\"测试团队\"]\n    test -- \"有问题<br/>反馈修改和更新\" --> dev\n    test -- \"没问题<br/>发布\" --> prod[\"生产环境\"]\n```\n\n### 1.3.3 Docker 的核心优势\n\n除了解决上述痛点，Docker 还拥有诸多显著的技术优势，包括环境一致性、秒级启动、高效的资源利用等。\n\n#### 1. 环境一致性\n\nDocker 镜像包含了应用运行所需的 **一切**：代码、运行时、系统工具、库、配置。这意味着：\n\n- ✅ 开发环境和生产环境完全一致\n- ✅ 不会再有 “在我机器上能跑” 的问题\n- ✅ 新人入职，一条命令就能启动开发环境\n\n```bash\n## 新同事入职第一天\n\n$ git clone https://github.com/company/project.git\n$ docker compose up\n## 完整的开发环境就准备好了\n\n...\n```\n\n#### 2. 秒级启动\n\n传统虚拟机启动需要几分钟 (引导操作系统)，而 Docker 容器启动通常只需要 **几秒甚至几百毫秒**。\n\n笔者实测数据：\n\n| 启动内容 | 虚拟机 | Docker 容器 |\n|---------|--------|-------------|\n| 空系统 | ~60 秒 | ~0.5 秒 |\n| MySQL | ~90 秒 | ~3 秒 |\n| 完整 Web 应用 | ~120 秒 | ~5 秒 |\n\n这个差异对以下场景尤为重要：\n\n- **CI/CD 流水线**：每次构建节省几分钟，一天累积下来就是几小时\n- **弹性扩容**：流量高峰时能快速启动更多实例\n- **开发体验**：快速重启服务进行调试\n\n#### 3. 资源效率\n\nDocker 容器共享宿主机内核，无需为每个应用运行完整的操作系统。以一台 64GB 内存的物理服务器为例：\n- **传统虚拟机方案**：每个虚拟机都需要运行完整的操作系统（每个额外占用如 2GB 内存），产生大量资源开销，实际可用于应用的内存可能只有约 18GB。\n- **Docker 方案**：容器直接共享宿主机系统，只需付出很少的基础开销（OS 及引擎约 4GB），即可将约 60GB 的内存全部用于实际应用。\n\n```mermaid\nflowchart TD\n    subgraph VM [\"传统虚拟机方案 ❌\"]\n        direction TB\n        Server1[\"物理服务器 (64GB 内存)\"]\n        subgraph VMs [\"可用应用内存: 约 18GB\"]\n            direction LR\n            VM1[\"VM 1: 应用 1<br/>(含 2GB OS)\"]\n            VM2[\"VM 2: 应用 2<br/>(含 2GB OS)\"]\n            VM3[\"VM 3: 应用 3<br/>(含 2GB OS)\"]\n        end\n        Server1 --- VMs\n    end\n\n    subgraph Docker [\"Docker 方案 ✅\"]\n        direction TB\n        Server2[\"物理服务器 (64GB 内存)<br/>含约 4GB OS及引擎配置\"]\n        subgraph Containers [\"可用应用内存: 约 60GB\"]\n            direction LR\n            C1[\"容器 1: 应用 1<br/>(按需分配)\"]\n            C2[\"容器 2: 应用 2<br/>(按需分配)\"]\n            C3[\"容器 3: 应用 3<br/>(按需分配)\"]\n        end\n        Server2 --- Containers\n    end\n```\n\n#### 4. 持续交付和部署\n\nDocker 完美契合 DevOps 的工作流程：\n\n```mermaid\nflowchart LR\n    A[\"代码提交<br/>(Git push)\"] --> B[\"自动构建镜像<br/>(docker build)\"]\n    B --> C[\"自动测试<br/>(容器内运行测试)\"]\n    C --> D[\"自动部署<br/>(容器滚动更新)\"]\n```\n\n使用 [Dockerfile](../04_image/4.5_build.md) 定义镜像构建过程，使得：\n\n- 构建过程 **可重复、可追溯**\n- 任何人都能从代码重建完全相同的镜像\n- 配合 [GitHub Actions](../21_case_devops/21.2_github_actions.md) 等 CI 系统实现自动化\n\n#### 5. 轻松迁移\n\nDocker 可以在几乎任何平台上运行：\n\n- ✅ 本地开发机 (macOS、Windows、Linux)\n- ✅ 公有云 (AWS、Azure、GCP、阿里云、腾讯云)\n- ✅ 私有云和自建数据中心\n- ✅ 边缘设备和 IoT\n\n**同一个镜像，在任何地方运行结果都一致。** 这让应用迁移变得前所未有的简单。\n\n#### 6. 微服务架构的基石\n\n现代微服务架构几乎都依赖容器技术。Docker 让你可以：\n\n- **隔离服务**：每个服务运行在独立容器中，互不干扰\n- **独立扩展**：哪个服务负载高，就单独扩展哪个\n- **独立部署**：更新一个服务不影响其他服务\n- **技术多样**：不同服务可以用不同语言和框架\n\n```mermaid\nflowchart TD\n    subgraph Microservices [\"微服务架构示例\"]\n        direction TB\n        subgraph AppLayer [\"应用层\"]\n            direction LR\n            Frontend[\"前端容器<br/>(Node.js)\"]\n            API[\"API 容器<br/>(Python)\"]\n            Worker[\"Worker 容器<br/>(Go)\"]\n        end\n        Redis[\"Redis 容器\"]\n        DB[\"PostgreSQL 容器\"]\n        \n        Frontend --> API\n        API --> Redis\n        API --> DB\n        Worker --> Redis\n        Worker --> DB\n    end\n```\n\n### 1.3.4 Docker 不适合的场景\n\n笔者认为，技术选型要客观。Docker 并非银弹，以下场景可能不太适合：\n\n- **需要完全隔离的场景**：容器共享宿主机内核，隔离性不如虚拟机。如果需要运行不受信任的代码，虚拟机可能更安全。\n- **需要特殊内核的场景**：容器使用宿主机内核。如果应用需要特定版本的内核或内核模块，可能需要虚拟机。\n- **Windows 原生应用**：虽然 Docker 支持 Windows 容器，但生态不如 Linux 容器成熟。传统 Windows 应用的容器化仍有挑战。\n- **桌面应用**：Docker 主要面向服务端应用。桌面 GUI 应用的容器化虽然可行，但通常得不偿失。\n\n### 1.3.5 与传统虚拟机的对比总结\n\n关于容器与虚拟机的详细特性对比，请参阅 [1.2.3 Docker vs 虚拟机](1.2_what.md) 中的对比表。总结来说：\n\n- **性能差异**：虚拟机通常有 5-20% 的性能损耗，而容器接近原生性能。\n- **最佳场景**：Docker 容器适合微服务、CI/CD、开发环境；虚拟机适合多租户、高安全需求场景。\n"
  },
  {
    "path": "01_introduction/README.md",
    "content": "# 第一章 Docker 简介\n\n本章将带领你进入 **Docker** 的世界。\n\n> **版本提示**：本书内容及示例基于 **Docker Engine v29.x** 及以上版本。值得注意的是，自 Docker Engine v29 起，官方在全新安装场景下 **默认启用了 `containerd image store` 作为镜像存储后端**（取代了传统的经典存储引擎如 overlay2 graph driver）。这项底层革新极大增强了 Docker 对多架构镜像（Multi-platform）、以及软件供应链安全元数据（Attestations, SBOM, Provenance）的本地支持原生性。\n\n## 本章内容\n\n* [快速上手](1.1_quickstart.md)\n  * 通过一个简单的 Web 应用例子，带你快速体验 Docker 的核心流程：构建镜像、运行容器。\n\n* [什么是 Docker](1.2_what.md)\n  * 介绍 Docker 的起源、发展历程以及其背后的核心技术 (Cgroups，Namespaces，UnionFS，以及 `containerd` 引擎的演进)。\n  * 了解 Docker 是如何改变软件交付方式的。\n\n* [为什么要用 Docker](1.3_why.md)\n  * 对比传统虚拟机技术，阐述 Docker 在启动速度、资源利用率、交付效率等方面的巨大优势。\n  * 探讨 Docker 在 DevOps、微服务架构中的关键作用。\n\n\n## 学习目标\n\n通过本章的学习，你将能够：\n\n1. 理解 Docker 的核心概念与架构。\n2. 明白 Docker 解决了现代软件开发与运维中的哪些痛点。\n3. 建立起对容器技术的初步认知，为后续的实战操作打下基础。\n\n好吧，让我们带着问题开始这神奇之旅。\n"
  },
  {
    "path": "01_introduction/summary.md",
    "content": "## 本章小结\n\n- Docker 是一种轻量级虚拟化技术，核心价值是 **环境一致性**\n- 与虚拟机相比，Docker 更轻量、更快速、资源利用率更高\n- Docker 基于 Linux 内核的 Namespace、Cgroups 和 Union FS 技术\n- Docker 推动了容器技术的标准化 (OCI) 和生态发展\n\nDocker 的核心价值可以用一句话概括：**让应用的开发、测试、部署保持一致，同时极大提高资源利用效率。** 笔者认为，对于现代软件开发者来说，Docker 已经不是 “要不要学” 的问题，而是 **必备技能**。无论你是前端、后端、运维还是全栈开发者，掌握 Docker 都能让你的工作更高效。\n\n---\n\n> 📝 **发现错误或有改进建议？** 欢迎提交 [Issue](https://github.com/yeasy/docker_practice/issues) 或 [PR](https://github.com/yeasy/docker_practice/pulls)。\n"
  },
  {
    "path": "02_basic_concept/2.1_image.md",
    "content": "## 2.1 镜像\n\nDocker 镜像作为容器运行的基石，其设计理念和实现机制至关重要。本节将深入探讨镜像的本质、与操作系统的关系、内容构成以及核心的分层存储机制。\n\n### 2.1.1 一句话理解镜像\n\n> **Docker 镜像是一个只读的模板，包含了运行应用所需的一切：代码、运行时、库、环境变量和配置文件。** 如果用一个类比：**镜像就像是一张光盘或 ISO 文件**。你可以用同一张光盘在不同电脑上安装系统，而光盘本身不会被修改。同样，一个镜像可以创建多个容器，而镜像本身保持不变。\n\n### 2.1.2 镜像与操作系统的关系\n\n我们都知道，操作系统分为 **内核** 和 **用户空间**：\n\n```mermaid\nflowchart TD\n    subgraph UserSpace [\"用户空间\"]\n        direction TB\n        App[\"应用程序、工具、库、配置文件...<br/>（这部分被打包成 Docker 镜像）\"]\n    end\n    \n    subgraph KernelSpace [\"Linux 内核\"]\n        direction TB\n        Kernel[\"容器共享宿主机的内核\"]\n    end\n    \n    UserSpace --- KernelSpace\n```\n\n对于 Linux 而言，内核启动后会挂载 `root` 文件系统来提供用户空间支持。**Docker 镜像** 本质上就是一个 `root` 文件系统。\n\n例如，官方镜像 `ubuntu:24.04` 包含了一套完整的 Ubuntu 24.04 最小系统的 root 文件系统——但 **不包含 Linux 内核** (因为容器共享宿主机的内核)。\n\n### 2.1.3 镜像包含什么？\n\nDocker 镜像是一个特殊的文件系统，包含：\n\n| 内容类型 | 示例 |\n|---------|------|\n| **程序文件** | 应用二进制文件、Python/Node 解释器 |\n| **库文件** | libc、OpenSSL、各种依赖库 |\n| **配置文件** | nginx.conf、my.cnf 等 |\n| **环境变量** | PATH、LANG 等预设值 |\n| **元数据** | 启动命令、暴露端口、数据卷定义 |\n\n**关键特性**：\n- ✅ 镜像是 **只读** 的\n- ✅ 镜像 **不包含** 动态数据\n- ✅ 镜像构建后 **内容不会改变**\n\n### 2.1.4 分层存储：镜像的核心设计\n\n镜像的分层存储机制是 Docker 最具创新性的特性之一。通过 Union FS 技术，Docker 能够高效地构建和管理镜像。\n\n#### 为什么需要分层？\n\n笔者认为，分层存储是 Docker 最巧妙的设计之一。\n\n假设你有三个应用，都基于 Ubuntu 运行：\n\n```mermaid\nflowchart TD\n    subgraph Traditional [\"传统方式（不分层）总计: 1.5GB ❌\"]\n        direction LR\n        AppA_Trad[\"App A<br/>Ubuntu 500MB\"]\n        AppB_Trad[\"App B<br/>Ubuntu 500MB\"]\n        AppC_Trad[\"App C<br/>Ubuntu 500MB\"]\n    end\n    \n    subgraph DockerLayered [\"Docker 分层方式 总计: 620MB ✅\"]\n        direction TB\n        subgraph Apps [\"应用层\"]\n            direction LR\n            AppA[\"App A 50MB\"]\n            AppB[\"App B 30MB\"]\n            AppC[\"App C 40MB\"]\n        end\n        Ubuntu[\"Ubuntu<br/>（共享）500MB\"]\n        \n        AppA --> Ubuntu\n        AppB --> Ubuntu\n        AppC --> Ubuntu\n    end\n```\n\n#### 分层是如何工作的？\n\n笔者用一个实际的 Dockerfile 来解释分层：\n\n```docker\nFROM ubuntu:24.04          # 第 1 层：基础系统（约 78MB）\nRUN apt-get update         # 第 2 层：更新包索引\nRUN apt-get install nginx  # 第 3 层：安装 nginx\nCOPY app.conf /etc/nginx/  # 第 4 层：复制配置文件\n```\n\n构建后的镜像结构：\n\n```mermaid\nflowchart TD\n    Layer4[\"第 4 层: COPY app.conf (只读)<br/>← 最新添加的层\"]\n    Layer3[\"第 3 层: nginx 安装文件 (只读)\"]\n    Layer2[\"第 2 层: apt 缓存更新 (只读)\"]\n    Layer1[\"第 1 层: Ubuntu 基础系统 (只读)<br/>← 基础镜像层\"]\n    \n    Layer4 --> Layer3 --> Layer2 --> Layer1\n```\n\n每一层的特点：\n\n- **只读**：构建完成后不可修改\n- **可共享**：多个镜像可以共享相同的层\n- **有缓存**：未变化的层不会重新构建\n\n#### 分层存储的 “陷阱”\n\n> ⚠️ **笔者特别提醒**：理解这一点可以帮你避免构建出臃肿的镜像。**关键原理**：每一层的文件变化会被记录，但 **删除操作只是标记，不会真正减小镜像体积**。\n\n```docker\n## 错误示范 ❌\n\nFROM ubuntu:24.04\nRUN apt-get update\nRUN apt-get install -y build-essential  # 安装编译工具（约 200MB）\nRUN make && make install                  # 编译应用\nRUN apt-get remove build-essential        # 试图删除编译工具\n## 结果：镜像仍然包含 200MB 的编译工具！\n```\n\n```docker\n## 正确做法 ✅\n\nFROM ubuntu:24.04\nRUN apt-get update && \\\n    apt-get install -y build-essential && \\\n    make && make install && \\\n    apt-get remove -y build-essential && \\\n    apt-get autoremove -y && \\\n    rm -rf /var/lib/apt/lists/*\n## 在同一层完成安装、使用、清理\n\n```\n\n#### 查看镜像的分层\n\n```bash\n## 查看镜像的历史（每层的构建记录）\n\n$ docker history nginx:latest\n\nIMAGE          CREATED       CREATED BY                                      SIZE\na6bd71f48f68   2 weeks ago   CMD [\"nginx\" \"-g\" \"daemon off;\"]                0B\n<missing>      2 weeks ago   STOPSIGNAL SIGQUIT                              0B\n<missing>      2 weeks ago   EXPOSE map[80/tcp:{}]                           0B\n<missing>      2 weeks ago   ENTRYPOINT [\"/docker-entrypoint.sh\"]            0B\n<missing>      2 weeks ago   COPY 30-tune-worker-processes.sh /docker-ent…   4.62kB\n...\n```\n\n### 2.1.5 镜像的标识\n\nDocker 镜像有多种标识方式：\n\n#### 1. 镜像名称和标签\n\n格式：`[仓库地址/]仓库名[:标签]`\n\n```bash\n## 完整格式\n\nregistry.example.com/myproject/myapp:v1.2.3\n\n## 简写（使用 Docker Hub）\n\nnginx:1.25\nubuntu:24.04\n\n## 省略标签（默认使用 latest）\n\nnginx  # 等同于 nginx:latest\n```\n\n#### 2. 镜像 ID：Content-Addressable 标识\n\n每个镜像有一个基于内容计算的唯一 ID：\n\n```bash\n$ docker images\nREPOSITORY   TAG       IMAGE ID       CREATED        SIZE\nnginx        latest    a6bd71f48f68   2 weeks ago    187MB\nubuntu       24.04     ca2b0f26964c   3 weeks ago    78.1MB\n```\n\n#### 3. 镜像摘要\n\n更精确的标识，基于镜像内容的 SHA256 哈希：\n\n```bash\n$ docker images --digests\nREPOSITORY  TAG     DIGEST                                                                    IMAGE ID\nnginx       latest  sha256:6db391d1c0cfb30588ba0bf72ea999404f2764184d8b8d10d89e8a9c6... a6bd71f48f68\n```\n\n> 💡 笔者建议：在生产环境使用镜像摘要而非标签，因为标签可以被覆盖，但摘要是不可变的。\n\n### 2.1.6 镜像的来源\n\nDocker 镜像可以通过以下方式获取：\n\n| 方式 | 说明 | 示例 |\n|------|------|------|\n| **从 Registry 拉取** | 最常用的方式 | `docker pull nginx` |\n| **从 Dockerfile 构建** | 自定义镜像 | `docker build -t myapp .` |\n| **从容器提交** | 保存容器状态 (不推荐)| `docker commit` |\n| **从文件导入** | 离线传输 | `docker load < image.tar` |\n"
  },
  {
    "path": "02_basic_concept/2.2_container.md",
    "content": "## 2.2 容器\n\n容器是 Docker 技术的核心，是应用实际运行的载体。本节将从容器的本质、与虚拟机的区别、存储层机制以及生命周期管理等方面，全面解析 Docker 容器。\n\n### 2.2.1 一句话理解容器\n\n> **容器是镜像的运行实例。如果把镜像比作程序，那么容器就是进程。** 用面向对象编程的术语来说：**镜像是类 (Class)，容器是对象 (Instance)**。\n\n- 一个镜像可以创建多个容器\n- 每个容器相互独立，互不影响\n- 容器可以被创建、启动、停止、删除、暂停\n\n### 2.2.2 容器的本质\n\n> 💡 笔者认为，理解这一点是理解 Docker 的关键：**容器的本质是一个特殊的进程**。\n\n```mermaid\nflowchart TD\n    subgraph NormalProcess [\"普通进程\"]\n        direction TB\n        N1[\"• 共享系统资源<br/>• 共享网络<br/>• 共享文件系统\"]\n    end\n    \n    subgraph ContainerProcess [\"容器进程 (运行在宿主机内核上)\"]\n        direction TB\n        C1[\"• 独立进程空间<br/>• 独立网络环境<br/>• 独立文件系统<br/>• 独立用户空间\"]\n    end\n```\n\n这种隔离是通过 Linux 内核的 **Namespace** 技术实现的。具体表现为：\n- **进程空间**：容器看不到宿主机上的其他进程。\n- **网络**：容器拥有独立的 IP、端口等网络资源。\n- **文件系统**：容器拥有独立的 root 目录。\n- **用户**：容器内的 root 用户不等于宿主机的 root 用户。\n\n### 2.2.3 容器 vs 虚拟机：核心区别\n\n很多初学者会混淆容器和虚拟机。笔者用一张图来说明：\n\n```mermaid\nflowchart TD\n    subgraph VM [\"虚拟机 (每个 VM 运行完整 OS)\"]\n        direction TB\n        subgraph VMApp [\"应用层\"]\n            VA[App A] & VB[App B]\n        end\n        subgraph VMGuest [\"Guest OS (完整系统)\"]\n            G1[Guest OS] & G2[Guest OS]\n        end\n        V[Hypervisor]\n        VMH[Host OS]\n        VMHW[Hardware]\n        VMApp --> VMGuest --> V --> VMH --> VMHW\n    end\n    \n    subgraph Container [\"容器 (所有容器共享宿主机内核)\"]\n        direction TB\n        subgraph CApp [\"应用层\"]\n            CA[App A] & CB[App B]\n        end\n        subgraph CContainer [\"隔离层\"]\n            CC1[Container 仅应用] & CC2[Container 仅应用]\n        end\n        CE[Docker Engine]\n        CH[Host OS]\n        CHW[Hardware]\n        CApp --> CContainer --> CE --> CH --> CHW\n    end\n```\n\n| 特性 | 容器 | 虚拟机 |\n|------|------|--------|\n| **隔离级别** | 进程级 (Namespace)| 硬件级 (Hypervisor)|\n| **启动时间** | 秒级 (甚至毫秒)| 分钟级 |\n| **资源占用** | MB 级别 | GB 级别 |\n| **性能损耗** | 几乎为零 | 5-20% |\n| **内核** | 共享宿主机内核 | 各自独立内核 |\n\n### 2.2.4 容器的存储层\n\n理解容器的存储层机制对于数据的持久化和镜像的优化至关重要。本节将介绍容器的可写层以及 Copy-on-Write 机制。\n\n#### 镜像层 + 容器层\n\n当容器运行时，Docker 会在镜像的只读层之上创建一个 **可写层** (容器存储层)：\n\n```mermaid\nflowchart TD\n    ContainerLayer[\"容器存储层（可读写）<br/>容器运行时创建，记录文件变化\"]\n    ImageLayerN[\"镜像第 N 层（只读）\"]\n    ImageLayerN1[\"镜像第 N-1 层（只读）\"]\n    Dots[\"...\"]\n    ImageLayer1[\"镜像第 1 层（只读）<br/>基础镜像层\"]\n    \n    ContainerLayer --> ImageLayerN --> ImageLayerN1 --> Dots --> ImageLayer1\n```\n\n#### Copy-on-Write：写时复制\n\n当容器需要修改镜像层中的文件时：\n\n1. Docker 将该文件 **复制** 到容器存储层\n2. 在容器层中进行修改\n3. 原始镜像层保持不变\n\n```bash\n读取文件：直接从镜像层读取（共享，高效）\n修改文件：复制到容器层，然后修改（只有这个容器能看到修改）\n```\n\n#### ⚠️ 容器存储层的生命周期\n\n> **笔者特别强调**：这是新手最容易踩的坑！**容器存储层与容器生命周期绑定。容器删除，数据就没了！**\n\n```bash\n## 创建容器，写入数据\n\n$ docker run -it ubuntu bash\nroot@abc123:/# echo \"important data\" > /data.txt\nroot@abc123:/# exit\n\n## 删除容器\n\n$ docker rm abc123\n\n## 数据丢了！没有任何办法恢复！\n\n```\n\n#### 正确的数据持久化方式\n\n按照 Docker 最佳实践，容器存储层应该保持 **无状态**。需要持久化的数据应该使用：\n\n| 方式 | 说明 | 适用场景 |\n|------|------|---------|\n| **[数据卷 (Volume) ](../08_data/8.1_volume.md)** | Docker 管理的存储 | 数据库、应用数据 |\n| **[绑定挂载 (Bind Mount) ](../08_data/8.2_bind-mounts.md)** | 挂载宿主机目录 | 开发时共享代码 |\n\n```bash\n## 使用数据卷（推荐）\n\n$ docker run -v mydata:/var/lib/mysql mysql\n\n## 使用绑定挂载\n\n$ docker run -v /host/path:/container/path nginx\n```\n\n这些位置的读写 **会跳过容器存储层**，直接写入宿主机，性能更好，也不会随容器删除而丢失。\n\n### 2.2.5 容器的生命周期\n\n掌握容器的生命周期对于管理和调试 Docker 应用非常重要。如图 2-1 所示，容器会经历从创建到删除的完整状态流转。\n\n```mermaid\nstateDiagram-v2\n    direction TB\n    [*] --> Created : docker create\n    Created --> Running : docker start\n    Running --> Stopped : docker stop\n    Running --> Paused : docker pause\n    Paused --> Running : docker unpause\n    \n    Created --> Deleted : docker rm\n    Stopped --> Deleted : docker rm\n    Paused --> Deleted : docker rm\n    \n    Deleted --> [*]\n```\n\n图 2-1：容器生命周期状态流转图\n\n#### 常用生命周期命令\n\n```bash\n## 创建并启动容器（最常用）\n\n$ docker run nginx\n\n## 分步操作\n\n$ docker create nginx    # 创建容器（不启动）\n$ docker start abc123    # 启动容器\n\n## 停止容器\n\n$ docker stop abc123     # 优雅停止（发送 SIGTERM，等待后发送 SIGKILL）\n$ docker kill abc123     # 强制停止（直接发送 SIGKILL）\n\n## 暂停/恢复（不常用，但有时有用）\n\n$ docker pause abc123    # 暂停容器内所有进程\n$ docker unpause abc123  # 恢复\n\n## 删除容器\n\n$ docker rm abc123       # 删除已停止的容器\n$ docker rm -f abc123    # 强制删除运行中的容器\n```\n\n### 2.2.6 容器与进程的关系\n\n> **核心概念**：容器的生命周期 = 主进程 (PID 1) 的生命周期\n\n```bash\n## 主进程运行，容器运行\n\n## 主进程退出，容器停止\n\n```\n\n这就是为什么：\n\n```bash\n## 这个容器会立即退出（bash 没有输入就退出了）\n\n$ docker run ubuntu\n\n## 这个容器会持续运行（nginx 作为守护进程持续运行）\n\n$ docker run nginx\n```\n\n详细解释请参考[后台运行](../05_container/5.2_daemon.md)章节。\n\n### 2.2.7 容器的隔离性\n\nDocker 容器通过以下 Namespace 实现隔离：\n\n| Namespace | 隔离内容 | 效果 |\n|-----------|---------|------|\n| **PID** | 进程 ID | 容器内 PID 1 是应用进程，看不到宿主机其他进程 |\n| **NET** | 网络 | 独立的网络栈、IP 地址、端口 |\n| **MNT** | 文件系统 | 独立的根目录和挂载点 |\n| **UTS** | 主机名 | 独立的主机名和域名 |\n| **IPC** | 进程间通信 | 独立的信号量、消息队列 |\n| **USER** | 用户 | 独立的用户和组 ID |\n\n> 想深入了解？请阅读[底层实现 - 命名空间](../12_implementation/12.2_namespace.md)。\n"
  },
  {
    "path": "02_basic_concept/2.3_repository.md",
    "content": "## 2.3 仓库\n\nDocker Registry 是镜像分发和管理的核心组件。本节将介绍 Registry 的基本概念、公共和私有服务的选择，以及镜像的安全管理。\n\n### 2.3.1 一句话理解 Registry\n\n> **Docker Registry 是存储和分发 Docker 镜像的服务，类似于代码的 GitHub 或包管理的 npm。**\n\n镜像构建完成后，可以在当前机器上运行。但如果需要在其他服务器上使用这个镜像，就需要一个集中的存储和分发服务——这就是 Docker Registry。\n\n### 2.3.2 核心概念\n\n要熟练使用 Docker Registry，首先需要理清它与仓库 (Repository)、标签 (Tag) 之间的关系。\n\n#### Registry、仓库、标签的关系\n\nDocker Registry 中可以包含多个 Repository，每个 Repository 可以包含多个 Tag。如图 2-2 所示，它们之间具有清晰的层级关系。\n\n```mermaid\nflowchart TB\n    subgraph Registry [\"Docker Registry（如 Docker Hub）\"]\n        direction TB\n        subgraph RepoNginx [\"Repository（仓库）: nginx\"]\n            direction LR\n            N1(\":latest (tag)\")\n            N2(\":1.25 (tag)\")\n            N3(\":1.24 (tag)\")\n            N4(\":alpine (tag)\")\n            N5(\"...\")\n            N1 ~~~ N2 ~~~ N3 ~~~ N4 ~~~ N5\n        end\n        subgraph RepoMysql [\"Repository（仓库）: mysql\"]\n            direction LR\n            M1(\":latest\")\n            M2(\":8.0\")\n            M3(\":5.7\")\n            M4(\"...\")\n            M1 ~~~ M2 ~~~ M3 ~~~ M4\n        end\n        RepoNginx ~~~ RepoMysql\n    end\n```\n\n图 2-2：Registry、Repository 与 Tag 的层级关系\n\n相关基本概念具体如下：\n\n| 概念 | 说明 | 示例 |\n|------|------|------|\n| **Registry** | 存储镜像的服务 | Docker Hub、ghcr.io |\n| **Repository (仓库)** | 同一软件的镜像集合 | `nginx`、`mysql`、`mycompany/myapp` |\n| **Tag (标签)** | 仓库内的版本标识 | `latest`、`1.25`、`alpine` |\n\n#### 镜像的完整名称\n\n一个完整的 Docker 镜像名称由 Registry 地址、用户名/组织名、仓库名和标签组成。了解其结构有助于我们更准确地定位镜像。基本格式如下：\n\n```bash\n[registry 地址/][用户名/]仓库名[:标签]\n```\n\n示例：\n\n```bash\n## 完整格式\n\nregistry.example.com/mycompany/myapp:v1.2.3\n│                    │         │     │\n│                    │         │     └── 标签\n│                    │         └── 仓库名\n│                    └── 用户名/组织名\n└── Registry 地址\n\n## Docker Hub 官方镜像（省略 registry 和用户名）\n\nnginx:1.25\nubuntu:24.04\n\n## Docker Hub 用户镜像\n\njwilder/nginx-proxy:latest\n\n## 其他 Registry\n\nghcr.io/username/myapp:v1.0\ngcr.io/google-containers/pause:3.6\n```\n\n> 💡 **笔者提示**：如果不指定 Registry 地址，默认使用 Docker Hub。如果不指定标签，默认使用 `latest`。\n\n### 2.3.3 公共 Registry 服务\n\n公共 Registry 服务为开发者提供了便捷的镜像获取途径。其中最著名的是 Docker Hub。\n\n#### 默认的 Docker Hub\n\n[Docker Hub](https://hub.docker.com/) 是最大的公共 Registry，也是 Docker 的默认 Registry。\n\n**特点**：\n\n- 拥有大量[官方镜像](https://hub.docker.com/search?q=&type=image&image_filter=official) (nginx、mysql、redis 等)\n- 免费账户可以创建公开仓库\n- 付费账户支持私有仓库\n\n```bash\n## 从 Docker Hub 拉取镜像\n\n$ docker pull nginx              # 官方镜像\n$ docker pull bitnami/redis      # 第三方镜像\n\n## 推送镜像到 Docker Hub\n\n$ docker login\n$ docker push username/myapp:v1.0\n```\n\n#### 其他公共 Registry\n\n除了 Docker Hub，还有以下几个常见的公共 Registry：\n\n| Registry | 地址 | 说明 |\n|----------|------|------|\n| **GitHub Container Registry** | ghcr.io | GitHub 提供，与 GitHub Actions 集成好 |\n| **Google Container Registry** | gcr.io | Google Cloud 提供，Kubernetes 镜像常用 |\n| **Quay.io** | quay.io | Red Hat 提供 |\n| **阿里云容器镜像服务** | registry.cn-*.aliyuncs.com | 国内访问快 |\n| **腾讯云容器镜像服务** | ccr.ccs.tencentyun.com | 国内访问快 |\n\n### 2.3.4 镜像加速器\n\n由于网络原因，在国内直接访问 Docker Hub 可能会很慢。可以配置 **镜像加速器** (Registry Mirror) 来加速下载。配置示例如下：\n\n```json\n// /etc/docker/daemon.json\n{\n  \"registry-mirrors\": [\n    \"https://your-accelerator-url\"\n  ]\n}\n```\n\n详细配置方法请参考[镜像加速器](../03_install/3.9_mirror.md)章节。\n\n> ⚠️ **笔者提醒**：镜像加速器的可用性经常变化，使用前建议先测试是否可用。\n\n### 2.3.5 私有 Registry\n\n出于安全和隐私的考虑，企业往往需要搭建自己的私有 Registry。以下是几种常见的搭建方案。\n\n#### 官方 Registry 镜像\n\nDocker 官方提供了 [registry](https://hub.docker.com/_/registry/) 镜像，可以快速搭建私有 Registry：\n\n```bash\n## 启动一个本地 Registry\n\n$ docker run -d -p 5000:5000 --name registry registry:2\n\n## 推送镜像到本地 Registry\n\n$ docker tag myapp:v1.0 localhost:5000/myapp:v1.0\n$ docker push localhost:5000/myapp:v1.0\n\n## 从本地 Registry 拉取\n\n$ docker pull localhost:5000/myapp:v1.0\n```\n\n#### 企业级解决方案\n\n官方 Registry 功能较为基础，企业环境常用以下方案：\n\n| 方案 | 特点 |\n|------|------|\n| **[Harbor](https://goharbor.io/)** | CNCF 项目，功能全面 (用户管理、漏洞扫描、镜像签名)|\n| **[Nexus Repository](../06_repository/6.4_nexus3_registry.md)** | 支持多种制品类型 (Docker、Maven、npm 等)|\n| **云厂商服务** | 阿里云 ACR、腾讯云 TCR、AWS ECR 等 |\n\n笔者建议：\n\n- 小团队：可以先用官方 Registry，够用即可\n- 中大型团队：推荐 Harbor，功能完善且开源免费\n- 已使用云服务：直接用云厂商的 Registry 服务更省心\n\n### 2.3.6 镜像的推送和拉取\n\n掌握镜像的推送 (Push) 和拉取 (Pull) 是使用 Docker Registry 的基本功。\n\n#### 完整工作流程\n\n如图 2-3 所示，镜像从开发环境构建后推送到 Registry，再由生产环境拉取并运行。\n\n```bash\n开发者机器                    Registry                    生产服务器\n     │                           │                             │\n     │  docker build             │                             │\n     │  构建镜像                  │                             │\n     │                           │                             │\n     │  docker push ─────────────▶                             │\n     │  推送镜像                  │  存储镜像                   │\n     │                           │                             │\n     │                           │  ◀───────────── docker pull │\n     │                           │                  拉取镜像    │\n     │                           │                             │\n     │                           │                  docker run │\n     │                           │                  运行容器    │\n```\n\n图 2-3：镜像构建、推送与拉取流程\n\n#### 常用命令\n\n```bash\n## 登录 Registry\n\n$ docker login                      # 登录 Docker Hub\n$ docker login registry.example.com # 登录其他 Registry\n\n## 拉取镜像\n\n$ docker pull nginx:1.25\n\n## 标记镜像（准备推送）\n\n$ docker tag myapp:latest registry.example.com/myteam/myapp:v1.0\n\n## 推送镜像\n\n$ docker push registry.example.com/myteam/myapp:v1.0\n\n## 登出\n\n$ docker logout\n```\n\n### 2.3.7 镜像的安全性\n\n在使用公共镜像或维护私有镜像时，安全性是不容忽视的重要环节。\n\n#### 使用官方镜像\n\nDocker Hub 的[官方镜像](https://hub.docker.com/search?q=&type=image&image_filter=official) (标有 “Official Image” 标识) 经过 Docker 团队审核，相对更安全。示例如下：\n\n```bash\n## 官方镜像示例\n\nnginx          # ✅ 官方\nmysql          # ✅ 官方\nredis          # ✅ 官方\n\n## 第三方镜像（需要自行评估可信度）\n\nbitnami/redis  # ⚠️ 需要评估\nsomeuser/myapp # ⚠️ 需要评估\n```\n\n#### 镜像签名\n\n当前更推荐使用 Sigstore / Notation 体系进行镜像签名与验证。`Docker Content Trust (DCT)` 已进入退场阶段，不建议作为新项目主方案。\n\n> 注意：Cosign 默认会把签名写回镜像所在仓库，请使用你有推送权限的镜像地址。\n\n```bash\n## 准备一个你有写权限的镜像地址\n$ export IMAGE=<你的仓库名>/nginx:1.27\n$ docker pull nginx:1.27\n$ docker tag nginx:1.27 $IMAGE\n$ docker push $IMAGE\n\n## 生成签名密钥（会生成 cosign.key / cosign.pub）\n$ cosign generate-key-pair\n\n## 使用 Cosign 签名与验证\n$ cosign sign --key cosign.key $IMAGE\n$ cosign verify --key cosign.pub $IMAGE\n```\n\n#### 漏洞扫描\n\n```bash\n## 使用 Docker Scout 扫描镜像漏洞\n\n$ docker scout cves nginx:latest\n\n## 使用 Trivy（开源工具）\n\n$ trivy image nginx:latest\n```\n"
  },
  {
    "path": "02_basic_concept/README.md",
    "content": "# 第二章 基本概念\n\n**Docker** 包括三个基本概念：\n\n* **镜像** (`Image`)：Docker 镜像是一个特殊的文件系统，除了提供容器运行时所需的程序、库、资源、配置等文件外，还包含了一些为运行时准备的一些配置参数 (如匿名卷、环境变量、用户等)。镜像不包含任何动态数据，其内容在构建之后也不会被改变。\n* **容器** (`Container`)：镜像 (`Image`) 和容器 (`Container`) 的关系，就像是面向对象程序设计中的 `类` 和 `实例` 一样，镜像是静态的定义，容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。\n* **仓库** (`Repository`)：镜像构建完成后，可以很容易的在当前宿主机上运行，但是，如果需要在其它服务器上使用这个镜像，我们就需要一个集中的存储、分发镜像的服务，Docker Registry 就是这样的服务。\n\n理解了这三个概念，就理解了 **Docker** 的整个生命周期。\n\n## 本章内容\n\n* [Docker 镜像](2.1_image.md)\n* [Docker 容器](2.2_container.md)\n* [Docker 仓库](2.3_repository.md)\n"
  },
  {
    "path": "02_basic_concept/summary.md",
    "content": "## 本章小结\n\n本章介绍了 Docker 的三个核心概念：镜像、容器和仓库。\n\n| 概念 | 要点 |\n|------|------|\n| **镜像是什么** | 只读的应用模板，包含运行所需的一切 |\n| **分层存储** | 多层叠加，共享基础层，节省空间 |\n| **只读特性** | 构建后不可修改，保证一致性 |\n| **层的陷阱** | 删除操作只是标记，不减小体积 |\n| **容器是什么** | 镜像的运行实例，本质是隔离的进程 |\n| **容器 vs 虚拟机** | 共享内核，更轻量，但隔离性较弱 |\n| **存储层** | 可写层随容器删除而消失 |\n| **数据持久化** | 使用 Volume 或 Bind Mount |\n| **生命周期** | 与主进程 (PID 1) 绑定 |\n| **Registry** | 存储和分发镜像的服务 |\n| **仓库 (Repository)** | 同一软件的镜像集合 |\n| **标签 (Tag)** | 版本标识，默认为 latest |\n| **Docker Hub** | 默认的公共 Registry |\n| **私有 Registry** | 企业内部使用，推荐 Harbor |\n\n现在你已经了解了 Docker 的三个核心概念：[镜像](2.1_image.md)、[容器](2.2_container.md)和仓库。接下来，让我们开始[安装 Docker](../03_install/README.md)，动手实践！\n\n### 延伸阅读\n\n- [获取镜像](../04_image/4.1_pull.md)：从 Registry 下载镜像\n- [使用 Dockerfile 定制镜像](../04_image/4.5_build.md)：创建自己的镜像\n- [Dockerfile 最佳实践](../appendix/best_practices.md)：构建高质量镜像的技巧\n- [底层实现 - 联合文件系统](../12_implementation/12.4_ufs.md)：深入理解分层存储的技术原理\n- [启动容器](../05_container/5.1_run.md)：详细的容器启动选项\n- [后台运行](../05_container/5.2_daemon.md)：理解容器为什么会“立即退出”\n- [进入容器](../05_container/5.4_attach_exec.md)：如何操作运行中的容器\n- [数据管理](../08_data/README.md)：Volume 和数据持久化详解\n- [Docker Hub](../06_repository/6.1_dockerhub.md)：Docker Hub 的详细使用\n- [私有仓库](../06_repository/6.2_registry.md)：搭建私有 Registry\n- [私有仓库高级配置](../06_repository/6.3_registry_auth.md)：认证、TLS 配置\n- [镜像加速器](../03_install/3.9_mirror.md)：配置镜像加速\n---\n\n> 📝 **发现错误或有改进建议？** 欢迎提交 [Issue](https://github.com/yeasy/docker_practice/issues) 或 [PR](https://github.com/yeasy/docker_practice/pulls)。\n"
  },
  {
    "path": "03_install/3.10_experimental.md",
    "content": "## 3.10 开启实验特性\n\n一些 docker 命令或功能仅当 **实验特性** 开启时才能使用，请按照以下方法进行设置。\n\n### 3.10.1 Docker CLI 的实验特性\n\nCLI 的实验特性通常包含仍在开发中的新功能。幸运的是，在较新版本中这些特性已经更加易用。\n\n从 `v20.10` 版本开始，Docker CLI 所有实验特性的命令均默认开启，无需再进行配置或设置系统环境变量。\n\n### 3.10.2 开启 dockerd 的实验特性\n\n编辑 `/etc/docker/daemon.json`，新增如下条目\n\n```json\n{\n  \"experimental\": true\n}\n```\n"
  },
  {
    "path": "03_install/3.1_ubuntu.md",
    "content": "## 3.1 Ubuntu\n\nUbuntu 是 Docker 最常用的运行环境之一。本节将介绍如何在 Ubuntu 系统上安装 Docker，并配置国内镜像加速。\n\n> 警告：切勿在没有配置 Docker APT 源的情况下直接使用 apt 命令安装 Docker。\n\n### 3.1.1 准备工作\n\n在开始安装之前，我们需要确认系统版本是否满足要求，并清理可能存在的旧版本。\n\n#### 系统要求\n\nDocker 支持诸多版本的 [Ubuntu](https://ubuntu.com/server) 操作系统。但是较旧的版本上将不会有 Docker 新版本的持续更新，以截至 2026 年初的几个 Ubuntu LTS (Long Term Support，长期支持) 版本为例：\n\n\n* Ubuntu Noble 24.04 (LTS)，Docker v29.x\n* Ubuntu Jammy 22.04 (LTS), Docker v29.x\n\n> **注意**：Ubuntu 20.04 LTS 已于 2025 年结束标准支持，不再推荐用于新部署。\n\n在 Ubuntu LTS 版本上，目前 Docker 支持 amd64、arm64、armhf、ppc64el、s390x 等 5 个平台；而非 LTS 版本支持的平台通常较少。同时，LTS 版本会获得 5 年的升级维护支持，这样的系统会获得更长期的安全保障，因此在生产环境中推荐使用 LTS 版本。\n\n#### 卸载旧版本\n\n旧版本的 Docker 称为 `docker` 或者 `docker-engine`，使用以下命令卸载旧版本：\n\n```bash\n$ for pkg in docker \\\n           docker-engine \\\n           docker.io \\\n           docker-doc \\\n           podman-docker \\\n           containerd \\\n           runc;\ndo\n    sudo apt remove $pkg;\ndone\n```\n\n### 3.1.2 使用 APT 安装\n\n由于 `apt` 源使用 HTTPS 以确保软件下载过程中不被篡改。因此，我们首先需要添加使用 HTTPS 传输的软件包以及 CA 证书。\n\n```bash\n$ sudo apt update\n\n$ sudo apt install \\\n    apt-transport-https \\\n    ca-certificates \\\n    curl \\\n    gnupg \\\n    lsb-release\n```\n\n鉴于国内网络问题，强烈建议使用国内源，官方源请在注释中查看。\n\n为了确认所下载软件包的合法性，需要添加软件源的 `GPG` 密钥。\n\n```bash\n$ curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg\n\n# 官方源\n# $ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg\n```\n\n然后，我们需要向 `sources.list` 中添加 Docker 软件源\n\n```bash\n$ echo \\\n  \"deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://mirrors.aliyun.com/docker-ce/linux/ubuntu \\\n  $(lsb_release -cs) stable\" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null\n\n# 官方源\n# $ echo \\\n#   \"deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \\\n#   $(lsb_release -cs) stable\" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null\n```\n\n> 以上命令会添加稳定版本的 Docker APT 镜像源，如果需要测试版本的 Docker 请将 stable 改为 test。\n\n#### 安装 Docker\n\n更新 apt 软件包缓存，并安装 `docker-ce`：\n\n```bash\n$ sudo apt update\n\n$ sudo apt install docker-ce docker-ce-cli containerd.io\n```\n\n### 3.1.3 使用脚本自动安装\n\n在测试或开发环境中 Docker 官方为了简化安装流程，提供了一套便捷的安装脚本，Ubuntu 系统上可以使用这套脚本安装，另外可以通过 `--mirror` 选项使用国内源进行安装：\n\n> 若你想安装测试版的 Docker，请从 test.docker.com 获取脚本\n\n```bash\n# $ curl -fsSL test.docker.com -o get-docker.sh\n\n$ curl -fsSL get.docker.com -o get-docker.sh\n$ sudo sh get-docker.sh --mirror Aliyun\n# $ sudo sh get-docker.sh --mirror AzureChinaCloud\n```\n\n执行这个命令后，脚本就会自动的将一切准备工作做好，并且把 Docker 的稳定 (stable) 版本安装在系统中。\n\n### 3.1.4 启动 Docker\n\n```bash\n$ sudo systemctl enable docker\n$ sudo systemctl start docker\n```\n\n### 3.1.5 建立 docker 用户组\n\n默认情况下，`docker` 命令会使用 [Unix socket](https://en.wikipedia.org/wiki/Unix_domain_socket) 与 Docker 引擎通讯。而只有 `root` 用户和 `docker` 组的用户才可以访问 Docker 引擎的 Unix socket。出于安全考虑，一般 Linux 系统上不会直接使用 `root` 用户。因此，更好的做法是将需要使用 `docker` 的用户加入 `docker` 用户组。\n\n> ⚠️ **安全警告：`docker` 用户组等同于 `root` 权限**\n> \n> 将用户加入 `docker` 组免去了每次执行 `docker` 命令时输入 `sudo` 的繁琐，但这也意味着该用户可以轻易获取主机的最高 root 权限（例如通过挂载根目录运行容器）。\n> 如果你在一个多用户共享的生产系统上配置，切勿随意将普通用户加入此组。此时，更安全的替代方案是使用官方提供的 **[Rootless 模式 (Rootless mode)](https://docs.docker.com/engine/security/rootless/)**，它允许在没有任何 root 权限的情况下运行 Docker 守护进程和容器。\n\n建立 `docker` 组：\n\n```bash\n$ sudo groupadd docker\n```\n\n将当前用户加入 `docker` 组：\n\n```bash\n$ sudo usermod -aG docker $USER\n```\n\n退出当前终端并重新登录，进行如下测试。\n\n### 3.1.6 测试 Docker 是否安装正确\n\n```bash\n$ docker run --rm hello-world\n\nUnable to find image 'hello-world:latest' locally\nlatest: Pulling from library/hello-world\nb8dfde127a29: Pull complete\nDigest: sha256:308866a43596e83578c7dfa15e27a73011bdd402185a84c5cd7f32a88b501a24\nStatus: Downloaded newer image for hello-world:latest\n\nHello from Docker!\nThis message shows that your installation appears to be working correctly.\n\nTo generate this message, Docker took the following steps:\n 1. The Docker client contacted the Docker daemon.\n 2. The Docker daemon pulled the \"hello-world\" image from the Docker Hub.\n    (amd64)\n 3. The Docker daemon created a new container from that image which runs the\n    executable that produces the output you are currently reading.\n 4. The Docker daemon streamed that output to the Docker client, which sent it\n    to your terminal.\n\nTo try something more ambitious, you can run an Ubuntu container with:\n $ docker run -it ubuntu bash\n\nShare images, automate workflows, and more with a free Docker ID:\n https://hub.docker.com/\n\nFor more examples and ideas, visit:\n https://docs.docker.com/get-started/\n```\n\n若能正常输出以上信息，则说明安装成功。\n\n### 3.1.7 镜像加速\n\n如果在使用过程中发现拉取 Docker 镜像十分缓慢，可以配置 Docker [国内镜像加速](3.9_mirror.md)。\n\n### 3.1.8 参考文档\n\n* [Docker 官方 Ubuntu 安装文档](https://docs.docker.com/engine/install/ubuntu/)\n"
  },
  {
    "path": "03_install/3.2_debian.md",
    "content": "## 3.2 Debian\n\nDebian 以其稳定性著称，是 Docker 的理想宿主系统。本节将指导你在 Debian 上完成 Docker 的安装。\n\n> 警告：切勿在没有配置 Docker APT 源的情况下直接使用 apt 命令安装 Docker。\n\n### 3.2.1 准备工作\n\n安装前请仔细检查 Debian 版本支持情况，并卸载旧版本以避免冲突。\n\n#### 系统要求\n\nDocker 支持以下版本的 [Debian](https://www.debian.org/intro/about) 操作系统：\n\n* Debian Trixie 13 (stable)\n* Debian Bookworm 12 (oldstable)\n* Debian Bullseye 11 (LTS)\n\n#### 卸载旧版本\n\n旧版本的 Docker 称为 `docker` 或者 `docker-engine`，使用以下命令卸载旧版本：\n\n```bash\n$ sudo apt-get remove docker \\\n               docker-engine \\\n               docker.io\n```\n\n### 3.2.2 使用 APT 安装\n\n由于 apt 源使用 HTTPS 以确保软件下载过程中不被篡改。因此，我们首先需要添加使用 HTTPS 传输的软件包以及 CA 证书。\n\n```bash\n$ sudo apt-get update\n\n$ sudo apt-get install \\\n     apt-transport-https \\\n     ca-certificates \\\n     curl \\\n     gnupg \\\n     lsb-release\n```\n\n鉴于国内网络问题，强烈建议使用国内源，官方源请在注释中查看。\n\n为了确认所下载软件包的合法性，需要添加软件源的 GPG 密钥。\n\n```bash\n$ curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/debian/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg\n\n# 官方源\n# $ curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg\n```\n\n然后，我们需要向 `sources.list` 中添加 Docker 软件源：\n\n> 在一些基于 Debian 的 Linux 发行版中 `$(lsb_release -cs)` 可能不会返回 Debian 的版本代号，例如 [Kali Linux](https://www.kali.org/docs/policy/kali-linux-relationship-with-debian/)、[BunsenLabs Linux](https://www.bunsenlabs.org/)。在这些发行版中我们需要将下面命令中的 `$(lsb_release -cs)` 替换为 https://mirrors.aliyun.com/docker-ce/linux/debian/dists/ 中支持的 Debian 版本代号，例如 `buster`。\n\n```bash\n$ echo \\\n  \"deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://mirrors.aliyun.com/docker-ce/linux/debian \\\n  $(lsb_release -cs) stable\" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null\n\n# 官方源\n# $ echo \\\n#   \"deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian \\\n#   $(lsb_release -cs) stable\" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null\n```\n\n> 以上命令会添加稳定版本的 Docker APT 源，如果需要测试版本的 Docker 请将 stable 改为 test。Debian11 可能不使用 `/etc/apt/keyrings/`，如 gpg 错误可以考虑更换为 `/etc/apt/trusted.gpg.d`，见 [issue 15727](https://github.com/docker/docs/issues/15727)。\n\n#### 安装 Docker\n\n更新 apt 软件包缓存，并安装 `docker-ce`。\n\n```bash\n$ sudo apt-get update\n\n$ sudo apt-get install docker-ce docker-ce-cli containerd.io\n```\n\n### 3.2.3 使用脚本自动安装\n\n在测试或开发环境中 Docker 官方为了简化安装流程，提供了一套便捷的安装脚本，Debian 系统上可以使用这套脚本安装，另外可以通过 `--mirror` 选项使用国内源进行安装：\n\n> 若你想安装测试版的 Docker，请从 test.docker.com 获取脚本\n\n```bash\n# $ curl -fsSL test.docker.com -o get-docker.sh\n\n$ curl -fsSL get.docker.com -o get-docker.sh\n$ sudo sh get-docker.sh --mirror Aliyun\n# $ sudo sh get-docker.sh --mirror AzureChinaCloud\n```\n\n执行这个命令后，脚本就会自动的将一切准备工作做好，并且把 Docker 的稳定 (stable) 版本安装在系统中。\n\n### 3.2.4 启动 Docker\n\n```bash\n$ sudo systemctl enable docker\n$ sudo systemctl start docker\n```\n\n### 3.2.5 建立 docker 用户组\n\n默认情况下，`docker` 命令会使用 [Unix socket](https://en.wikipedia.org/wiki/Unix_domain_socket) 与 Docker 引擎通讯。而只有 `root` 用户和 `docker` 组的用户才可以访问 Docker 引擎的 Unix socket。出于安全考虑，一般 Linux 系统上不会直接使用 `root` 用户。因此，更好的做法是将需要使用 `docker` 的用户加入 `docker` 用户组。\n\n> ⚠️ **安全警告：`docker` 用户组等同于 `root` 权限**\n> \n> 将用户加入 `docker` 组免去了每次执行 `docker` 命令时输入 `sudo` 的繁琐，但这也意味着该用户可以轻易获取主机的最高 root 权限（例如通过挂载根目录运行容器）。\n> 如果你在一个多用户共享的生产系统上配置，切勿随意将普通用户加入此组。此时，更安全的替代方案是使用官方提供的 **[Rootless 模式 (Rootless mode)](https://docs.docker.com/engine/security/rootless/)**，它允许在没有任何 root 权限的情况下运行 Docker 守护进程和容器。\n\n建立 `docker` 组：\n\n```bash\n$ sudo groupadd docker\n```\n\n将当前用户加入 `docker` 组：\n\n```bash\n$ sudo usermod -aG docker $USER\n```\n\n退出当前终端并重新登录，进行如下测试。\n\n### 3.2.6 测试 Docker 是否安装正确\n\n```bash\n$ docker run --rm hello-world\n\nUnable to find image 'hello-world:latest' locally\nlatest: Pulling from library/hello-world\nb8dfde127a29: Pull complete\nDigest: sha256:308866a43596e83578c7dfa15e27a73011bdd402185a84c5cd7f32a88b501a24\nStatus: Downloaded newer image for hello-world:latest\n\nHello from Docker!\nThis message shows that your installation appears to be working correctly.\n\nTo generate this message, Docker took the following steps:\n 1. The Docker client contacted the Docker daemon.\n 2. The Docker daemon pulled the \"hello-world\" image from the Docker Hub.\n    (amd64)\n 3. The Docker daemon created a new container from that image which runs the\n    executable that produces the output you are currently reading.\n 4. The Docker daemon streamed that output to the Docker client, which sent it\n    to your terminal.\n\nTo try something more ambitious, you can run an Ubuntu container with:\n $ docker run -it ubuntu bash\n\nShare images, automate workflows, and more with a free Docker ID:\n https://hub.docker.com/\n\nFor more examples and ideas, visit:\n https://docs.docker.com/get-started/\n```\n\n若能正常输出以上信息，则说明安装成功。\n\n### 3.2.7 镜像加速\n\n如果在使用过程中发现拉取 Docker 镜像十分缓慢，可以配置 Docker [国内镜像加速](3.9_mirror.md)。\n\n### 3.2.8 参考文档\n\n* [Docker 官方 Debian 安装文档](https://docs.docker.com/engine/install/debian/)\n"
  },
  {
    "path": "03_install/3.3_fedora.md",
    "content": "## 3.3 Fedora\n\nFedora 作为技术前沿的 Linux 发行版，对 Docker 有着良好的支持。本节介绍在 Fedora 上的安装步骤。\n\n> 警告：切勿在没有配置 Docker dnf 源的情况下直接使用 dnf 命令安装 Docker。\n\n### 3.3.1 准备工作\n\n确保你的 Fedora 版本在支持列表中，并清理旧版本。\n\n#### 系统要求\n\nDocker 支持以下版本的 [Fedora](https://getfedora.org/) 操作系统：\n\n* 41\n* 42\n* 43\n\n#### 卸载旧版本\n\n旧版本的 Docker 称为 `docker` 或者 `docker-engine`，使用以下命令卸载旧版本：\n\n```bash\n$ sudo dnf remove docker \\\n                  docker-client \\\n                  docker-client-latest \\\n                  docker-common \\\n                  docker-latest \\\n                  docker-latest-logrotate \\\n                  docker-logrotate \\\n                  docker-selinux \\\n                  docker-engine-selinux \\\n                  docker-engine\n```\n\n### 3.3.2 使用 dnf 安装\n\n使用 dnf 包管理器安装是推荐的方式，便于后续的更行和管理。\n\n执行以下命令安装依赖包：\n\n```bash\n$ sudo dnf -y install dnf-plugins-core\n```\n\n鉴于国内网络问题，强烈建议使用国内源，官方源请在注释中查看。\n\n执行下面的命令添加 `dnf` 软件源：\n\n```bash\n$ sudo dnf config-manager \\\n    --add-repo \\\n    https://mirrors.aliyun.com/docker-ce/linux/fedora/docker-ce.repo\n\n$ sudo sed -i 's/download.docker.com/mirrors.aliyun.com\\/docker-ce/g' /etc/yum.repos.d/docker-ce.repo\n\n# 官方源\n# $ sudo dnf config-manager \\\n#    --add-repo \\\n#    https://download.docker.com/linux/fedora/docker-ce.repo\n```\n\n如果需要测试版本的 Docker 请使用以下命令：\n\n```bash\n$ sudo dnf config-manager --set-enabled docker-ce-test\n```\n\n你也可以禁用测试版本的 Docker\n\n```bash\n$ sudo dnf config-manager --set-disabled docker-ce-test\n```\n\n#### 安装 Docker\n\n更新 `dnf` 软件源缓存，并安装 `docker-ce`。\n\n```bash\n$ sudo dnf update\n$ sudo dnf install docker-ce docker-ce-cli containerd.io\n```\n\n你也可以使用以下命令安装指定版本的 Docker\n\n```bash\n$ dnf list docker-ce  --showduplicates | sort -r\n\ndocker-ce.x86_64          18.06.1.ce-3.fc28                     docker-ce-stable\n\n$ sudo dnf -y install docker-ce-18.06.1.ce\n```\n\n### 3.3.3 使用脚本自动安装\n\n在测试或开发环境中 Docker 官方为了简化安装流程，提供了一套便捷的安装脚本，Fedora 系统上可以使用这套脚本安装，另外可以通过 `--mirror` 选项使用国内源进行安装：\n\n> 若你想安装测试版的 Docker，请从 test.docker.com 获取脚本\n\n```bash\n# $ curl -fsSL test.docker.com -o get-docker.sh\n\n$ curl -fsSL get.docker.com -o get-docker.sh\n$ sudo sh get-docker.sh --mirror Aliyun\n# $ sudo sh get-docker.sh --mirror AzureChinaCloud\n```\n\n执行这个命令后，脚本就会自动的将一切准备工作做好，并且把 Docker 最新稳定 (stable) 版本安装在系统中。\n\n### 3.3.4 启动 Docker\n\n```bash\n$ sudo systemctl enable docker\n$ sudo systemctl start docker\n```\n\n### 3.3.5 建立 docker 用户组\n\n默认情况下，`docker` 命令会使用 [Unix socket](https://en.wikipedia.org/wiki/Unix_domain_socket) 与 Docker 引擎通讯。而只有 `root` 用户和 `docker` 组的用户才可以访问 Docker 引擎的 Unix socket。出于安全考虑，一般 Linux 系统上不会直接使用 `root` 用户。因此，更好的做法是将需要使用 `docker` 的用户加入 `docker` 用户组。\n\n> ⚠️ **安全警告：`docker` 用户组等同于 `root` 权限**\n> \n> 将用户加入 `docker` 组免去了每次执行 `docker` 命令时输入 `sudo` 的繁琐，但这也意味着该用户可以轻易获取主机的最高 root 权限（例如通过挂载根目录运行容器）。\n> 如果你在一个多用户共享的生产系统上配置，切勿随意将普通用户加入此组。此时，更安全的替代方案是使用官方提供的 **[Rootless 模式 (Rootless mode)](https://docs.docker.com/engine/security/rootless/)**，它允许在没有任何 root 权限的情况下运行 Docker 守护进程和容器。\n\n建立 `docker` 组：\n\n```bash\n$ sudo groupadd docker\n```\n\n将当前用户加入 `docker` 组：\n\n```bash\n$ sudo usermod -aG docker $USER\n```\n\n退出当前终端并重新登录，进行如下测试。\n\n### 3.3.6 测试 Docker 是否安装正确\n\n```bash\n$ docker run --rm hello-world\n\nUnable to find image 'hello-world:latest' locally\nlatest: Pulling from library/hello-world\nb8dfde127a29: Pull complete\nDigest: sha256:308866a43596e83578c7dfa15e27a73011bdd402185a84c5cd7f32a88b501a24\nStatus: Downloaded newer image for hello-world:latest\n\nHello from Docker!\nThis message shows that your installation appears to be working correctly.\n\nTo generate this message, Docker took the following steps:\n 1. The Docker client contacted the Docker daemon.\n 2. The Docker daemon pulled the \"hello-world\" image from the Docker Hub.\n    (amd64)\n 3. The Docker daemon created a new container from that image which runs the\n    executable that produces the output you are currently reading.\n 4. The Docker daemon streamed that output to the Docker client, which sent it\n    to your terminal.\n\nTo try something more ambitious, you can run an Ubuntu container with:\n $ docker run -it ubuntu bash\n\nShare images, automate workflows, and more with a free Docker ID:\n https://hub.docker.com/\n\nFor more examples and ideas, visit:\n https://docs.docker.com/get-started/\n```\n\n若能正常输出以上信息，则说明安装成功。\n\n### 3.3.7 镜像加速\n\n如果在使用过程中发现拉取 Docker 镜像十分缓慢，可以配置 Docker [国内镜像加速](3.9_mirror.md)。\n\n### 3.3.8 参考文档\n\n* [Docker 官方 Fedora 安装文档](https://docs.docker.com/engine/install/fedora/)。\n"
  },
  {
    "path": "03_install/3.4_centos.md",
    "content": "## 3.4 CentOS\n\nCentOS (及其替代品 Rocky Linux、AlmaLinux) 是企业级服务器常用的操作系统。本节介绍在这些系统上安装 Docker 的步骤。\n\n> 警告：切勿在没有配置 Docker YUM 源的情况下直接使用 yum 命令安装 Docker。\n\n### 3.4.1 准备工作\n\n安装前请确认系统版本和内核版本满足 Docker 的运行要求。\n\n#### 系统要求\n\n> ⚠️ **重要提示**：CentOS 8 已于 2021 年 12 月 31 日停止维护，CentOS 7 已于 2024 年 6 月 30 日结束支持。建议新项目使用 **Rocky Linux** 或 **AlmaLinux** 作为替代。\n\nDocker 支持 64 位版本 CentOS Stream 9、Rocky Linux 8/9、AlmaLinux 8/9，并且要求内核版本不低于 3.10。\n\n对于 Rocky Linux、AlmaLinux 或 CentOS Stream，推荐使用 `dnf` 包管理器。\n\n#### 卸载旧版本\n\n旧版本的 Docker 称为 `docker` 或者 `docker-engine`，使用以下命令卸载旧版本：\n\n```bash\n$ sudo yum remove docker \\\n                  docker-client \\\n                  docker-client-latest \\\n                  docker-common \\\n                  docker-latest \\\n                  docker-latest-logrotate \\\n                  docker-logrotate \\\n                  docker-selinux \\\n                  docker-engine-selinux \\\n                  docker-engine \\\n                  docker-ce-cli \\\n                  containerd.io\n```\n\n### 3.4.2 使用 yum 安装\n\n使用 yum/dnf 安装是管理 Docker 生命周期的标准方式。\n\n执行以下命令安装依赖包：\n\n```bash\n$ sudo dnf install -y dnf-utils\n```\n\n鉴于国内网络问题，强烈建议使用国内源，官方源请在注释中查看。\n\n执行下面的命令添加 `yum` 软件源：\n\n```bash\n$ sudo dnf config-manager \\\n    --add-repo \\\n    https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo\n\n$ sudo sed -i 's/download.docker.com/mirrors.aliyun.com\\/docker-ce/g' /etc/yum.repos.d/docker-ce.repo\n\n# 官方源\n# $ sudo dnf config-manager \\\n#     --add-repo \\\n#     https://download.docker.com/linux/centos/docker-ce.repo\n```\n\n如果需要测试版本的 Docker 请执行以下命令：\n\n```bash\n$ sudo dnf config-manager --set-enabled docker-ce-test\n```\n\n#### 安装 Docker\n\n更新 `dnf` 软件源缓存，并安装 `docker-ce`。\n\n```bash\n$ sudo dnf install docker-ce docker-ce-cli containerd.io\n```\n\n### 3.4.3 CentOS8 额外设置\n\nCentOS 8/Stream 默认使用 `nftables`。Docker 在新版本中已提供 `nftables` 实验支持，但在一些环境下仍可能遇到兼容性问题。若你遇到容器网络异常，可以先切换回 `iptables` 后端：\n\n更改 `/etc/firewalld/firewalld.conf`\n\n```bash\n## FirewallBackend=nftables\n\nFirewallBackend=iptables\n```\n\n或者执行如下命令：\n\n```bash\n$ firewall-cmd --permanent --zone=trusted --add-interface=docker0\n\n$ firewall-cmd --reload\n```\n\n### 3.4.4 使用脚本自动安装\n\n在测试或开发环境中 Docker 官方为了简化安装流程，提供了一套便捷的安装脚本，CentOS 系统上可以使用这套脚本安装，另外可以通过 `--mirror` 选项使用国内源进行安装：\n\n> 若你想安装测试版的 Docker，请从 test.docker.com 获取脚本\n\n```bash\n# $ curl -fsSL test.docker.com -o get-docker.sh\n\n$ curl -fsSL get.docker.com -o get-docker.sh\n$ sudo sh get-docker.sh --mirror Aliyun\n# $ sudo sh get-docker.sh --mirror AzureChinaCloud\n\n```\n\n执行这个命令后，脚本就会自动的将一切准备工作做好，并且把 Docker 的稳定 (stable) 版本安装在系统中。\n\n### 3.4.5 启动 Docker\n\n```bash\n$ sudo systemctl enable docker\n$ sudo systemctl start docker\n```\n\n### 3.4.6 建立 docker 用户组\n\n默认情况下，`docker` 命令会使用 [Unix socket](https://en.wikipedia.org/wiki/Unix_domain_socket) 与 Docker 引擎通讯。而只有 `root` 用户和 `docker` 组的用户才可以访问 Docker 引擎的 Unix socket。出于安全考虑，一般 Linux 系统上不会直接使用 `root` 用户。因此，更好的做法是将需要使用 `docker` 的用户加入 `docker` 用户组。\n\n> ⚠️ **安全警告：`docker` 用户组等同于 `root` 权限**\n> \n> 将用户加入 `docker` 组免去了每次执行 `docker` 命令时输入 `sudo` 的繁琐，但这也意味着该用户可以轻易获取主机的最高 root 权限（例如通过挂载根目录运行容器）。\n> 如果你在一个多用户共享的生产系统上配置，切勿随意将普通用户加入此组。此时，更安全的替代方案是使用官方提供的 **[Rootless 模式 (Rootless mode)](https://docs.docker.com/engine/security/rootless/)**，它允许在没有任何 root 权限的情况下运行 Docker 守护进程和容器。\n\n建立 `docker` 组：\n\n```bash\n$ sudo groupadd docker\n```\n\n将当前用户加入 `docker` 组：\n\n```bash\n$ sudo usermod -aG docker $USER\n```\n\n退出当前终端并重新登录，进行如下测试。\n\n### 3.4.7 测试 Docker 是否安装正确\n\n```bash\n$ docker run --rm hello-world\n\nUnable to find image 'hello-world:latest' locally\nlatest: Pulling from library/hello-world\nb8dfde127a29: Pull complete\nDigest: sha256:308866a43596e83578c7dfa15e27a73011bdd402185a84c5cd7f32a88b501a24\nStatus: Downloaded newer image for hello-world:latest\n\nHello from Docker!\nThis message shows that your installation appears to be working correctly.\n\nTo generate this message, Docker took the following steps:\n 1. The Docker client contacted the Docker daemon.\n 2. The Docker daemon pulled the \"hello-world\" image from the Docker Hub.\n    (amd64)\n 3. The Docker daemon created a new container from that image which runs the\n    executable that produces the output you are currently reading.\n 4. The Docker daemon streamed that output to the Docker client, which sent it\n    to your terminal.\n\nTo try something more ambitious, you can run an Ubuntu container with:\n $ docker run -it ubuntu bash\n\nShare images, automate workflows, and more with a free Docker ID:\n https://hub.docker.com/\n\nFor more examples and ideas, visit:\n https://docs.docker.com/get-started/\n```\n\n若能正常输出以上信息，则说明安装成功。\n\n### 3.4.8 镜像加速\n\n如果在使用过程中发现拉取 Docker 镜像十分缓慢，可以配置 Docker [国内镜像加速](3.9_mirror.md)。\n\n### 3.4.9 添加内核参数\n\n如果在 CentOS 使用 Docker 看到下面的这些警告信息：\n\n```bash\nWARNING: bridge-nf-call-iptables is disabled\nWARNING: bridge-nf-call-ip6tables is disabled\n```\n\n请添加内核配置参数以启用这些功能。\n\n```bash\n$ sudo tee -a /etc/sysctl.conf <<-EOF\nnet.bridge.bridge-nf-call-ip6tables = 1\nnet.bridge.bridge-nf-call-iptables = 1\nEOF\n```\n\n然后重新加载 `sysctl.conf` 即可\n\n```bash\n$ sudo sysctl -p\n```\n\n### 3.4.10 参考文档\n\n* [Docker 官方 CentOS 安装文档](https://docs.docker.com/engine/install/centos/)。\n* https://firewalld.org/2018/07/nftables-backend\n* https://github.com/moby/libnetwork/issues/2496\n"
  },
  {
    "path": "03_install/3.5_raspberry-pi.md",
    "content": "## 3.5 Raspberry Pi\n\n树莓派等 ARM 架构设备在物联网和边缘计算领域应用广泛。本节介绍如何在树莓派上安装 Docker。\n\n> 警告：切勿在没有配置 Docker APT 源的情况下直接使用 apt 命令安装 Docker。\n\n### 3.5.1 系统要求\n\nDocker 对 ARM 架构有着良好的支持。\n\nDocker 不仅支持 `x86_64` 架构的计算机，同时也支持 `ARM` 架构的计算机，本小节内容以树莓派单片电脑为例讲解 `ARM` 架构安装 Docker。\n\nDocker 支持以下版本的 [Raspberry Pi OS](https://www.raspberrypi.org/software/operating-systems/) 操作系统：\n\n* Raspberry Pi OS Trixie\n* Raspberry Pi OS Bookworm\n* Raspberry Pi OS Bullseye\n\n*注：*`Raspberry Pi OS` 由树莓派的开发与维护机构[树莓派基金会](https://www.raspberrypi.org/)官方支持，并推荐用作树莓派的首选系统，其基于 `Debian`。\n\n### 3.5.2 使用 APT 安装\n\n推荐使用 APT 包管理器进行安装，以确保版本的稳定性和安全性。\n\n由于 apt 源使用 HTTPS 以确保软件下载过程中不被篡改。因此，我们首先需要添加使用 HTTPS 传输的软件包以及 CA 证书。\n\n```bash\n$ sudo apt-get update\n\n$ sudo apt-get install \\\n     apt-transport-https \\\n     ca-certificates \\\n     curl \\\n     gnupg \\\n     lsb-release\n```\n\n鉴于国内网络问题，强烈建议使用国内源，官方源请在注释中查看。\n\n为了确认所下载软件包的合法性，需要添加软件源的 GPG 密钥。\n\n```bash\n$ sudo install -m 0755 -d /etc/apt/keyrings\n$ curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/raspbian/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg\n$ sudo chmod a+r /etc/apt/keyrings/docker.gpg\n\n# 官方源\n# $ sudo install -m 0755 -d /etc/apt/keyrings\n# $ curl -fsSL https://download.docker.com/linux/raspbian/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg\n# $ sudo chmod a+r /etc/apt/keyrings/docker.gpg\n```\n\n然后，我们需要向 `sources.list` 中添加 Docker 软件源：\n\n```bash\n$ echo \\\n  \"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://mirrors.aliyun.com/docker-ce/linux/raspbian \\\n  $(lsb_release -cs) stable\" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null\n\n# 官方源\n# $ echo \\\n#   \"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/raspbian \\\n#   $(lsb_release -cs) stable\" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null\n```\n\n> 以上命令会添加稳定版本的 Docker APT 源，如果需要测试版本的 Docker 请将 stable 改为 test。\n\n#### 报错解决办法\n\n在 `Raspberry Pi OS Bullseye/Bookworm` 中，如果你使用 `add-apt-repository` 添加源，可能会出现如下报错 (推荐改用上面的 `tee` 方式来写入 `/etc/apt/sources.list.d/docker.list`，可避免此问题)：\n\n```bash\nTraceback (most recent call last):\n File \"/usr/bin/add-apt-repository\", line 95, in <module>\n   sp = SoftwareProperties(options=options)\n File \"/usr/lib/python3/dist-packages/softwareproperties/SoftwareProperties.py\", line 109, in __init__\n   self.reload_sourceslist()\n File \"/usr/lib/python3/dist-packages/softwareproperties/SoftwareProperties.py\", line 599, in reload_sourceslist\n   self.distro.get_sources(self.sourceslist)    \n File \"/usr/lib/python3/dist-packages/aptsources/distro.py\", line 91, in get_sources\n   raise NoDistroTemplateException(\naptsources.distro.NoDistroTemplateException: Error: could not find a distribution template for Raspbian/bullseye\n```\n\n通过以下命令手动添加镜像源到 `/etc/apt/sources.list` 文件中即可解决：\n\n```bash\n$ echo \"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://mirrors.aliyun.com/docker-ce/linux/raspbian $(lsb_release -cs) stable\" | sudo tee -a /etc/apt/sources.list\n\n\n## 官方源\n\n# $ echo \"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/raspbian $(lsb_release -cs) stable\" | sudo tee -a /etc/apt/sources.list\n\n```\n\n#### 安装 Docker\n\n更新 apt 软件包缓存，并安装 `docker-ce`。\n\n```bash\n$ sudo apt-get update\n\n$ sudo apt-get install docker-ce\n```\n\n### 3.5.3 使用脚本自动安装\n\n在测试或开发环境中 Docker 官方为了简化安装流程，提供了一套便捷的安装脚本，Raspberry Pi OS 系统上可以使用这套脚本安装，另外可以通过 `--mirror` 选项使用国内源进行安装：\n\n> 若你想安装测试版的 Docker，请从 test.docker.com 获取脚本\n\n```bash\n# $ curl -fsSL test.docker.com -o get-docker.sh\n\n$ curl -fsSL get.docker.com -o get-docker.sh\n$ sudo sh get-docker.sh --mirror Aliyun\n# $ sudo sh get-docker.sh --mirror AzureChinaCloud\n\n```\n\n执行这个命令后，脚本就会自动的将一切准备工作做好，并且把 Docker 的稳定 (stable) 版本安装在系统中。\n\n### 3.5.4 启动 Docker\n\n```bash\n$ sudo systemctl enable docker\n$ sudo systemctl start docker\n```\n\n### 3.5.5 建立 docker 用户组\n\n默认情况下，`docker` 命令会使用 [Unix socket](https://en.wikipedia.org/wiki/Unix_domain_socket) 与 Docker 引擎通讯。而只有 `root` 用户和 `docker` 组的用户才可以访问 Docker 引擎的 Unix socket。出于安全考虑，一般 Linux 系统上不会直接使用 `root` 用户。因此，更好的做法是将需要使用 `docker` 的用户加入 `docker` 用户组。\n\n> ⚠️ **安全警告：`docker` 用户组等同于 `root` 权限**\n> \n> 将用户加入 `docker` 组免去了每次执行 `docker` 命令时输入 `sudo` 的繁琐，但这也意味着该用户可以轻易获取主机的最高 root 权限（例如通过挂载根目录运行容器）。\n> 如果你在一个多用户共享的生产系统上配置，切勿随意将普通用户加入此组。此时，更安全的替代方案是使用官方提供的 **[Rootless 模式 (Rootless mode)](https://docs.docker.com/engine/security/rootless/)**，它允许在没有任何 root 权限的情况下运行 Docker 守护进程和容器。\n\n建立 `docker` 组：\n\n```bash\n$ sudo groupadd docker\n```\n\n将当前用户加入 `docker` 组：\n\n```bash\n$ sudo usermod -aG docker $USER\n```\n\n退出当前终端并重新登录，进行如下测试。\n\n### 3.5.6 测试 Docker 是否安装正确\n\n```bash\n$ docker run --rm hello-world\n\nUnable to find image 'hello-world:latest' locally\nlatest: Pulling from library/hello-world\n4ee5c797bcd7: Pull complete\nDigest: sha256:308866a43596e83578c7dfa15e27a73011bdd402185a84c5cd7f32a88b501a24\nStatus: Downloaded newer image for hello-world:latest\n\nHello from Docker!\nThis message shows that your installation appears to be working correctly.\n\nTo generate this message, Docker took the following steps:\n 1. The Docker client contacted the Docker daemon.\n 2. The Docker daemon pulled the \"hello-world\" image from the Docker Hub.\n    (arm32v7)\n 3. The Docker daemon created a new container from that image which runs the\n    executable that produces the output you are currently reading.\n 4. The Docker daemon streamed that output to the Docker client, which sent it\n    to your terminal.\n\nTo try something more ambitious, you can run an Ubuntu container with:\n $ docker run -it ubuntu bash\n\nShare images, automate workflows, and more with a free Docker ID:\n https://hub.docker.com/\n\nFor more examples and ideas, visit:\n https://docs.docker.com/get-started/\n```\n\n若能正常输出以上信息，则说明安装成功。\n\n*注意：*ARM 平台不能使用 `x86` 镜像，查看 Raspberry Pi OS 可使用镜像请访问 [arm32v7](https://hub.docker.com/u/arm32v7/) 或者 [arm64v8](https://hub.docker.com/u/arm64v8/)。\n\n### 3.5.7 镜像加速\n\n如果在使用过程中发现拉取 Docker 镜像十分缓慢，可以配置 Docker [国内镜像加速](3.9_mirror.md)。\n"
  },
  {
    "path": "03_install/3.6_offline.md",
    "content": "## 3.6 Linux 离线安装\n\n生产环境中一般都是没有公网资源的，本文介绍如何在生产服务器上离线部署 `Docker`\n\n括号内的字母表示该操作需要在哪些服务器上执行\n\n![Docker-offile-install-top](../_images/image-20200412202617411.png)\n\n### 3.6.1 CentOS/Rocky/AlmaLinux 离线安装 Docker\n\n在无法连接外网的安全环境中，离线安装是唯一的选择。本节介绍如何在 RHEL 系发行版中进行离线安装。\n\n> 注意：以下命令以 CentOS 7 为例。对于 CentOS Stream 9、Rocky Linux 9 或 AlmaLinux 9，请将 `yum` 替换为 `dnf`，并将软件包后缀 `el7` 替换为 `el9`。\n\n#### YUM 本地文件安装：推荐\n\n推荐这种方式，是因为在生产环境中一般会选定某个指定的文档软件版本使用。\n\n##### 查询可用的软件版本\n\n```bash\n#下载清华的镜像源文件\nwget -O /etc/yum.repos.d/docker-ce.repo https://download.docker.com/linux/centos/docker-ce.repo\n\nsudo sed -i 's+download.docker.com+mirrors.tuna.tsinghua.edu.cn/docker-ce+' /etc/yum.repos.d/docker-ce.repo\n\nyum update\n```\n\n```bash\nsudo yum list docker-ce --showduplicates|sort -r\n\nLoading mirror speeds from cached hostfile\nLoaded plugins: fastestmirror\ndocker-ce.x86_64            24.0.4-1.el7                        docker-ce-stable\ndocker-ce.x86_64            3:19.03.7-3.el7                     docker-ce-stable\ndocker-ce.x86_64            3:19.03.6-3.el7                     docker-ce-stable\ndocker-ce.x86_64            3:19.03.5-3.el7                     docker-ce-stable\ndocker-ce.x86_64            3:19.03.4-3.el7                     docker-ce-stable\ndocker-ce.x86_64            3:19.03.3-3.el7                     docker-ce-stable\ndocker-ce.x86_64            3:19.03.2-3.el7                     docker-ce-stable\ndocker-ce.x86_64            3:19.03.1-3.el7                     docker-ce-stable\n....\n```\n\n##### 下载到指定文件夹\n\n```bash\nsudo yum install --downloadonly --downloaddir=/tmp/docker24_offline_install/ docker-ce-24.0.4-1.el7 docker-ce-cli-24.0.4-1.el7\n```\n\n```bash\nDependencies Resolved\n\n====================================================================================================================================================================================\n Package                                          Arch                                  Version                                         Repository                             Size\n====================================================================================================================================================================================\nInstalling:\n docker-ce                                        x86_64                                24.0.4-1.el7                                 docker                                 25 M\nInstalling for dependencies:\n container-selinux                                noarch                                24.0.4-1.el7                                   extras                                 39 k\n containerd.io                                    x86_64                                24.0.4-1.el7                                  docker                                 23 M\n docker-ce-cli                                    x86_64                                24.0.4-1.el7                                 docker                                 40 M\n\nTransaction Summary\n====================================================================================================================================================================================\nInstall  1 Package (+3 Dependent packages)\n\nTotal download size: 87 M\nInstalled size: 363 M\nBackground downloading packages, then exiting:\n(1/4): container-selinux-24.0.4-1.el7.noarch.rpm                                                                                                              |  39 kB  00:00:00\n(2/4): containerd.io-24.0.4-1.el7.x86_64.rpm                                                                                                               |  23 MB  00:00:00\n(3/4): docker-ce-24.0.4-1.el7.x86_64.rpm                                                                                                                    |  25 MB  00:00:00\n(4/4): docker-ce-cli-24.0.4-1.el7.x86_64.rpm                                                                                                                |  40 MB  00:00:00\n------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\nTotal                                                                                                                                               118 MB/s |  87 MB  00:00:00\nexiting because \"Download Only\" specified\n```\n\n##### 复制到目标服务器之后进入文件夹安装：C-N\n\n* 离线安装时，必须使用 rpm 命令不检查依赖的方式安装\n\n```bash\nrpm -Uvh *.rpm --nodeps --force\n```\n\n##### 锁定软件版本：C-N\n\n**下载锁定版本软件**\n\n可参考下文的网络源搭建\n\n```bash\nsudo yum install yum-plugin-versionlock\n```\n\n**锁定软件版本**\n\n```bash\nsudo yum versionlock add docker\n```\n\n**查看锁定列表**\n\n```bash\nsudo yum versionlock list\n```\n\n```bash\nLoaded plugins: fastestmirror, versionlock\n3:docker-ce-24.0.4-1.el7.*\nversionlock list done\n```\n\n**锁定后无法再更新**\n\n```bash\nsudo yum install docker-ce\nLoaded plugins: fastestmirror, versionlock\nLoading mirror speeds from cached hostfile\nExcluding 1 update due to versionlock (use \"yum versionlock status\" to show it)\nPackage 3:docker-ce-24.0.4-1.el7.x86_64 already installed and latest version\nNothing to do\n```\n\n**解锁指定软件**\n\n```bash\nsudo yum versionlock delete docker-ce\n```\n\n```bash\nLoaded plugins: fastestmirror, versionlock\nDeleting versionlock for: 3:docker-ce-24.0.4-1.el7.*\nversionlock deleted: 1\n```\n\n**解锁所有软件**\n\n```bash\nsudo yum versionlock delete all\n```\n\n#### YUM 本地源服务器搭建安装 Docker\n\n##### 挂载 ISO 镜像搭建本地 File 源\n\n```bash\n## 删除其他网络源\n\nrm -f /etc/yum.repos.d/*\n## 挂载光盘或者iso镜像\n\nmount /dev/cdrom /mnt\n```\n\n```bash\n## 添加本地源\n\ncat >/etc/yum.repos.d/local_files.repo<< EOF\n[Local_Files]\nname=Local_Files\nbaseurl=file:///mnt\nenabled=1\ngpgcheck=0\ngpgkey=file:///mnt/RPM-GPG-KEY-CentOS-7\nEOF\n```\n\n```bash\n## 测试刚才的本地源,安装createrepo软件\n\nyum clean all \nyum install createrepo -y\n```\n\n##### 根据本地文件搭建 BASE 网络源\n\n```bash\n## 安装apache 服务器\n\nyum install httpd -y\n## 挂载光盘\n\nmount /dev/cdrom /mnt\n## 新建centos目录\n\nmkdir /var/www/html/base\n## 复制光盘内的文件到刚才新建的目录\n\ncp -R /mnt/Packages/* /var/www/html/base/\ncreaterepo  /var/www/html/base/\nsystemctl enable httpd\nsystemctl start httpd\n```\n\n##### 下载 Docker-CE 镜像仓库\n\n在有网络的服务器上下载 Docker-ce 镜像\n\n```bash\n## 下载清华的镜像源文件\n\nwget -O /etc/yum.repos.d/docker-ce.repo https://download.docker.com/linux/centos/docker-ce.repo\nsudo sed -i 's+download.docker.com+mirrors.tuna.tsinghua.edu.cn/docker-ce+' /etc/yum.repos.d/docker-ce.repo\n```\n\n```bash\n## 新建 docker-ce目录\n\nmkdir /tmp/docker-ce/\n## 把镜像源同步到镜像文件中\n\nreposync -r docker-ce-stable -p /tmp/docker-ce/\n```\n\n##### 创建仓库索引\n\n把下载的 docker-ce 文件夹复制到离线的服务器\n\n```bash\n## 把docker-ce 文件夹复制到/var/www/html/docker-ce\n\n## 重建索引\n\ncreaterepo  /var/www/html/docker-ce/\n```\n\n##### YUM 客户端设置：C...N\n\n```bash\nrm -f /etc/yum.repos.d/*\ncat >/etc/yum.repos.d/local_files.repo<< EOF\n[local_base]\nname=local_base\n## 改成B服务器地址\n\nbaseurl=http://x.x.x.x/base\nenabled=1\ngpgcheck=0\nproxy=_none_\n[docker_ce]\nname=docker_ce\n## 改成B服务器地址\n\nbaseurl=http://x.x.x.x/docker-ce\nenabled=1\ngpgcheck=0\nproxy=_none_\nEOF\n\n```\n\n##### Docker 安装：C...N\n\n```bash\nsudo yum makecache fast\nsudo yum install docker-ce docker-ce-cli containerd.io\nsudo systemctl enable docker\n```\n"
  },
  {
    "path": "03_install/3.7_mac.md",
    "content": "## 3.7 macOS\n\n### 3.7.1 系统要求\n\n[Docker Desktop for Mac](https://docs.docker.com/docker-for-mac/) 要求系统最低为 macOS Sonoma 14.0 或更高版本，建议升级到最新版本的 macOS。\n\n### 3.7.2 安装\n\n> [!WARNING]\n> **商业许可限制**：自 2021 年起，Docker Desktop 对微型企业（少于 250 名员工且年收入少于 1000 万美元）、个人使用、教育和非商业开源项目仍然免费。对于其他商业用途，需要付费订阅。企业用户请注意合规风险，或考虑使用开源替代方案。\n\nDocker Desktop 为 Mac 用户提供了无缝的 Docker 体验。你可以选择使用 Homebrew 或手动下载安装包进行安装。\n\n#### 使用 Homebrew 安装\n\n[Homebrew](https://brew.sh/) 的 [Cask](https://github.com/Homebrew/homebrew-cask) 已经支持 Docker Desktop for Mac，因此可以很方便的使用 Homebrew Cask 来进行安装：\n\n```bash\n$ brew install --cask docker\n```\n\n#### 手动下载安装\n\n如果需要手动下载，请点击以下[链接](https://desktop.docker.com/mac/main/amd64/Docker.dmg)下载 Docker Desktop for Mac。\n\n> 如果你的电脑搭载的是 Apple Silicon 芯片 (`arm64` 架构)，请点击以下[链接](https://desktop.docker.com/mac/main/arm64/Docker.dmg)下载 Docker Desktop for Mac。你可以在[官方文档](https://docs.docker.com/docker-for-mac/apple-silicon/)查阅已知的问题。\n\n如同 macOS 其它软件一样，安装也非常简单，双击下载的 `.dmg` 文件，然后将那只叫 [Moby](https://www.docker.com/blog/call-me-moby-dock/) 的鲸鱼图标拖拽到 `Application` 文件夹即可 (其间需要输入用户密码)。\n\n![图](../_images/install-mac-dmg.jpg)\n\n### 3.7.3 运行\n\n从应用中找到 Docker 图标并点击运行。\n\n![图](../_images/install-mac-apps.jpg)\n\n运行之后，会在右上角菜单栏看到多了一个鲸鱼图标，这个图标表明了 Docker 的运行状态。\n\n![图](../_images/install-mac-menubar.png)\n\n每次点击鲸鱼图标会弹出操作菜单。\n\n![图](../_images/install-mac-menu.png)\n\n之后，你可以在终端通过命令检查安装后的 Docker 版本。\n\n```bash\n$ docker --version\nDocker version 27.0.3, build 7d4bcd8\n```\n\n如果 `docker version`、`docker info` 都正常的话，可以尝试运行一个 [Nginx 服务器](https://hub.docker.com/_/nginx/)：\n\n```bash\n$ docker run -d -p 80:80 --name webserver nginx\n```\n\n服务运行后，可以访问 [http://localhost](http://localhost)，如果看到了 “Welcome to nginx！”，就说明 Docker Desktop for Mac 安装成功了。\n\n![图](../_images/install-mac-example-nginx.png)\n\n要停止 Nginx 服务器并删除执行下面的命令：\n\n```bash\n$ docker stop webserver\n$ docker rm webserver\n```\n\n### 3.7.4 镜像加速\n\n如果在使用过程中发现拉取 Docker 镜像十分缓慢，可以配置 Docker [国内镜像加速](3.9_mirror.md)。\n\n### 3.7.5 参考链接\n\n* [官方文档](https://docs.docker.com/desktop/setup/install/mac-install/)\n"
  },
  {
    "path": "03_install/3.8_windows.md",
    "content": "## 3.8 Windows 10/11\n\n在 Windows 平台上，Docker Desktop 提供了完整的 Docker 开发环境。本节介绍在 Windows 10/11 上的安装和配置。\n\n### 3.8.1 系统要求\n\n[Docker Desktop for Windows](https://docs.docker.com/desktop/setup/install/windows-install/) 支持 64 位版本的 Windows 11 或 Windows 10 (需开启 Hyper-V)，推荐使用 Windows 11。\n\n### 3.8.2 安装\n\n> [!WARNING]\n> **商业许可限制**：自 2021 年起，Docker Desktop 对微型企业（少于 250 名员工且年收入少于 1000 万美元）、个人使用、教育和非商业开源项目仍然免费。对于其他商业用途，需要付费订阅。企业用户请注意合规风险，或考虑使用开源替代方案。\n\n**手动下载安装**\n\n点击以下[链接](https://desktop.docker.com/win/main/amd64/Docker%20Desktop%20Installer.exe)下载 Docker Desktop for Windows。\n\n下载好之后双击 `Docker Desktop Installer.exe` 开始安装。\n\n**使用**[**winget**](https://docs.microsoft.com/zh-cn/windows/package-manager/)**安装**\n\n```powershell\n$ winget install Docker.DockerDesktop\n```\n\n### 3.8.3 在 WSL2 运行 Docker\n\n若你的 Windows 版本为 Windows 10 专业版或家庭版 v1903 及以上版本可以使用 WSL2 运行 Docker，具体请查看 [Docker Desktop WSL 2 backend](https://docs.docker.com/docker-for-windows/wsl/)。\n\n### 3.8.4 运行\n\n在 Windows 搜索栏输入 **Docker** 点击 **Docker Desktop** 开始运行。\n\n![图](../_images/install-win-docker-app-search.png)\n\nDocker 启动之后会在 Windows 任务栏出现鲸鱼图标。\n\n![图](../_images/install-win-taskbar-circle.png)\n\n等待片刻，当鲸鱼图标静止时，说明 Docker 启动成功，之后你可以打开 PowerShell 使用 Docker。\n\n> 推荐使用 [Windows Terminal](https://docs.microsoft.com/zh-cn/windows/terminal/get-started) 在终端使用 Docker。\n\n### 3.8.5 镜像加速\n\n如果在使用过程中发现拉取 Docker 镜像十分缓慢，可以配置 Docker [国内镜像加速](3.9_mirror.md)。\n\n### 3.8.6 参考链接\n\n* [官方文档](https://docs.docker.com/desktop/setup/install/windows-install/)\n* [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/)\n"
  },
  {
    "path": "03_install/README.md",
    "content": "# 第三章 安装 Docker\n\nDocker 分为 `stable` `test` 和 `nightly` 三个更新频道。\n\n官方网站上有各种环境下的[安装指南](https://docs.docker.com/get-docker/)，这里主要介绍 Docker 在 `Linux`、`Windows 10` 和 `macOS` 上的安装。\n\n## 详细安装指南\n\n* [Ubuntu](3.1_ubuntu.md)\n* [Debian](3.2_debian.md)\n* [Fedora](3.3_fedora.md)\n* [CentOS](3.4_centos.md)\n* [Raspberry Pi](3.5_raspberry-pi.md)\n* [Linux 离线安装](3.6_offline.md)\n* [macOS](3.7_mac.md)\n* [Windows 10/11](3.8_windows.md)\n* [镜像加速器](3.9_mirror.md)\n* [开启实验特性](3.10_experimental.md)\n"
  },
  {
    "path": "03_install/summary.md",
    "content": "## 本章小结\n\nDocker 支持在多种平台上安装和使用，选择合适的安装方式是顺利使用 Docker 的第一步。\n\n| 平台 | 推荐方式 | 说明 |\n|------|---------|------|\n| **Ubuntu/Debian** | 官方 APT 仓库 | 最完善的支持，推荐首选 |\n| **CentOS/Fedora** | 官方 YUM/DNF 仓库 | 注意关闭 SELinux 或配置策略 |\n| **macOS** | Docker Desktop | 图形化安装，包含 Compose 和 Kubernetes |\n| **Windows 10/11** | Docker Desktop (WSL 2) | 需启用 WSL 2 后端 |\n| **Raspberry Pi** | 官方安装脚本 | 支持 ARM 架构 |\n| **离线环境** | 二进制包安装 | 适用于无法联网的服务器 |\n\n### 安装后验证\n\n安装完成后，运行以下命令验证 Docker 是否正常工作：\n\n```bash\n$ docker version\n$ docker run --rm hello-world\n```\n\n### 延伸阅读\n\n- [镜像加速器](3.9_mirror.md)：解决国内拉取镜像慢的问题\n- [开启实验特性](3.10_experimental.md)：使用最新功能\n- [Docker Hub](../06_repository/6.1_dockerhub.md)：官方镜像仓库\n---\n\n> 📝 **发现错误或有改进建议？** 欢迎提交 [Issue](https://github.com/yeasy/docker_practice/issues) 或 [PR](https://github.com/yeasy/docker_practice/pulls)。\n"
  },
  {
    "path": "04_image/4.1_pull.md",
    "content": "## 4.1 获取镜像\n\n从 Docker 镜像仓库获取镜像可谓是 Docker 运作的第一步。本节将介绍如何使用 `docker pull` 命令下载镜像，以及如何理解下载过程。\n\n### 4.1.1 docker pull 命令\n\n从镜像仓库获取镜像的命令是 `docker pull`：\n\n```bash\ndocker pull [选项] [Registry地址/]仓库名[:标签]\n```\n\n#### 镜像名称格式\n\nDocker 镜像名称由 Registry 地址、用户名、仓库名和标签组成。其标准格式如下：\n\n```bash\ndocker.io / library / ubuntu : 24.04\n────┬────   ───┬───   ──┬───   ──┬──\n    │         │        │        │\nRegistry地址  用户名    仓库名    标签\n (可省略)    (可省略)\n```\n\n| 组成部分 | 说明 | 默认值 |\n|---------|------|--------|\n| Registry 地址 | 镜像仓库地址 | `docker.io` (Docker Hub)|\n| 用户名 | 镜像所属用户/组织 | `library` (官方镜像)|\n| 仓库名 | 镜像名称 | 必须指定 |\n| 标签 | 版本标识 | `latest` |\n\n#### 示例\n\n```bash\n## 完整格式\n\n$ docker pull docker.io/library/ubuntu:24.04\n\n## 省略 Registry（默认 Docker Hub）\n\n$ docker pull library/ubuntu:24.04\n\n## 省略 library（官方镜像）\n\n$ docker pull ubuntu:24.04\n\n## 省略标签（默认 latest）\n\n$ docker pull ubuntu\n\n## 拉取第三方镜像\n\n$ docker pull bitnami/redis:latest\n\n## 从其他 Registry 拉取\n\n$ docker pull ghcr.io/username/myapp:v1.0\n```\n\n---\n\n### 4.1.2 下载过程解析\n\n当我们执行 `docker pull` 命令时，Docker 会输出详细的下载进度。让我们以 `ubuntu:24.04` 为例来解析这些信息。\n\n```bash\n$ docker pull ubuntu:24.04\n24.04: Pulling from library/ubuntu\n92dc2a97ff99: Pull complete\nbe13a9d27eb8: Pull complete\nc8299583700a: Pull complete\nDigest: sha256:4bc3ae6596938cb0d9e5ac51a1152ec9dcac2a1c50829c74abd9c4361e321b26\nStatus: Downloaded newer image for ubuntu:24.04\ndocker.io/library/ubuntu:24.04\n```\n\n#### 输出解读\n\n| 输出内容 | 说明 |\n|---------|------|\n| `Pulling from library/ubuntu` | 正在从官方 ubuntu 仓库拉取 |\n| `92dc2a97ff99: Pull complete` | 各层的下载状态 (显示层 ID 前 12 位)|\n| `Digest: sha256:...` | 镜像内容的唯一摘要 |\n| `docker.io/library/ubuntu:24.04` | 镜像的完整名称 |\n\n#### 分层下载\n\n从输出可以看到，镜像是 **分层下载** 的：\n\n```mermaid\nflowchart TD\n    subgraph Image [\"ubuntu:24.04 镜像\"]\n        direction TB\n        L3[\"第3层 c8299583700a<br/>(已存在，跳过下载)\"]\n        L2[\"第2层 be13a9d27eb8<br/>(下载中... 完成)\"]\n        L1[\"第1层 92dc2a97ff99<br/>(下载中... 完成)\"]\n        L3 --- L2 --- L1\n    end\n```\n\n如果本地已有相同的层，Docker 会跳过下载，节省带宽和时间。\n\n---\n\n### 4.1.3 常用选项\n\n`docker pull` 命令支持多种选项来满足不同的下载需求，例如下载所有标签、指定平台架构等。\n\n| 选项 | 说明 | 示例 |\n|------|------|------|\n| `--all-tags, -a` | 拉取所有标签 | `docker pull -a ubuntu` |\n| `--platform` | 指定平台架构 | `docker pull --platform linux/arm64 nginx` |\n| `--quiet, -q` | 静默模式 | `docker pull -q nginx` |\n\n#### 指定平台\n\n在 Apple Silicon Mac 上拉取 x86 镜像：\n\n```bash\n$ docker pull --platform linux/amd64 nginx\n```\n\n---\n\n### 4.1.4 拉取后运行\n\n拉取镜像后，可以基于它启动容器：\n\n```bash\n## 拉取镜像\n\n$ docker pull ubuntu:24.04\n\n## 运行容器\n\n$ docker run -it --rm ubuntu:24.04 bash\nroot@e7009c6ce357:/# cat /etc/os-release\nPRETTY_NAME=\"Ubuntu 24.04 LTS\"\n...\nroot@e7009c6ce357:/# exit\n```\n\n**参数说明**：\n\n| 参数 | 说明 |\n|------|------|\n| `-it` | 交互式终端模式 |\n| `--rm` | 退出后自动删除容器 |\n| `bash` | 启动命令 |\n\n> 💡 `docker run` 在需要时会自动 `pull` 镜像，因此通常不需要单独执行 `docker pull`。\n\n---\n\n### 4.1.5 镜像加速\n\n从 Docker Hub 下载可能较慢。可以配置镜像加速器：\n\n```json\n// /etc/docker/daemon.json (Linux)\n// ~/.docker/daemon.json (Docker Desktop)\n{\n  \"registry-mirrors\": [\n    \"https://your-accelerator-url\"\n  ]\n}\n```\n\n配置后重启 Docker：\n\n```bash\n$ sudo systemctl restart docker  # Linux\n## 或在 Docker Desktop 中重启\n```\n\n详见[镜像加速器](../03_install/3.9_mirror.md)章节。\n\n---\n\n### 4.1.6 验证镜像完整性\n\n为了确保下载的镜像没有被篡改且内容一致，我们可以校验镜像的摘要 (Digest)。\n\n#### 查看镜像摘要\n\n```bash\n$ docker images --digests ubuntu\nREPOSITORY   TAG     DIGEST                                                                    IMAGE ID\nubuntu       24.04   sha256:4bc3ae6596938cb0d9e5ac51a1152ec9dcac2a1c50829c74abd9c4361e321b26   ca2b0f26964c\n```\n\n#### 使用摘要拉取\n\n用摘要拉取可确保获取完全相同的镜像：\n\n```bash\n$ docker pull ubuntu@sha256:4bc3ae6596938cb0d9e5ac51a1152ec9dcac2a1c50829c74abd9c4361e321b26\n```\n\n> 笔者建议：生产环境使用摘要而非标签，因为标签可能被覆盖，摘要则是不可变的。\n\n---\n\n### 4.1.7 常见问题\n\n在使用 `docker pull` 过程中，可能会遇到下载速度慢、镜像不存在或磁盘空间不足等问题。以下是一些常见问题的排查思路。\n\n#### Q：下载速度很慢\n\n1. 配置镜像加速器\n2. 检查网络连接\n3. 尝试拉取更小的镜像版本 (如 `alpine` 变体)\n\n#### Q：提示镜像不存在\n\n```bash\nError: pull access denied, repository does not exist\n```\n\n可能原因：\n\n- 镜像名拼写错误\n- 私有镜像未登录 (需要 `docker login`)\n- 镜像确实不存在\n\n#### Q：磁盘空间不足\n\n```bash\n## 清理未使用的镜像\n\n$ docker image prune\n\n## 清理所有未使用资源\n\n$ docker system prune\n```\n\n---\n"
  },
  {
    "path": "04_image/4.2_list.md",
    "content": "## 4.2 列出镜像\n\n在下载了镜像后，我们可以使用 `docker image ls` 命令列出本地主机上的镜像。\n\n### 4.2.1 基本用法\n\n查看本地已下载的镜像：\n\n```bash\n$ docker image ls\nREPOSITORY   TAG       IMAGE ID       CREATED        SIZE\nredis        latest    5f515359c7f8   5 days ago     183MB\nnginx        latest    05a60462f8ba   5 days ago     181MB\nubuntu       24.04     329ed837d508   3 days ago     78MB\nubuntu       noble     329ed837d508   3 days ago     78MB\n```\n\n> 💡 `docker images` 是 `docker image ls` 的简写，两者等效。\n\n---\n\n### 4.2.2 输出字段说明\n\n`docker image ls` 命令默认输出的列表包含仓库名、标签、镜像 ID、创建时间和占用空间等信息。\n\n| 字段 | 说明 |\n|------|------|\n| **REPOSITORY** | 仓库名 |\n| **TAG** | 标签 (版本)|\n| **IMAGE ID** | 镜像唯一标识 (短 ID，前 12 位)|\n| **CREATED** | 创建时间 |\n| **SIZE** | 本地占用空间 |\n\n#### 同一镜像多个标签\n\n注意上面的 `ubuntu:24.04` 和 `ubuntu:noble` 拥有相同的 IMAGE ID——它们是同一个镜像的不同标签，只占用一份存储空间。\n\n---\n\n### 4.2.3 理解镜像大小\n\nDocker 镜像的大小可能与我们通常理解的文件大小有所不同，这涉及到分层存储的概念。\n\n#### 本地大小 vs Hub 显示大小\n\n| 位置 | 显示大小 | 说明 |\n|------|---------|------|\n| Docker Hub | 29MB | 压缩后的网络传输大小 |\n| docker image ls | 78MB | 本地解压后的实际大小 |\n\n#### 实际磁盘占用\n\n由于镜像是分层存储，不同镜像可能共享相同的层：\n\n```bash\nubuntu:24.04    nginx:latest    redis:latest\n    │               │                │\n    └───────┬───────┘                │\n            ▼                        │\n       共享基础层 ◄───────────────────┘\n```\n\n因此，`docker image ls` 中各镜像大小之和 > 实际磁盘占用。\n\n#### 查看实际空间占用\n\n```bash\n$ docker system df\nTYPE            TOTAL   ACTIVE   SIZE      RECLAIMABLE\nImages          15      3        2.5GB     1.8GB (72%)\nContainers      5       2        100MB     80MB (80%)\nLocal Volumes   8       2        500MB     400MB (80%)\nBuild Cache     0       0        0B        0B\n```\n\n---\n\n### 4.2.4 过滤镜像\n\n随着本地镜像数量的增加，我们需要更有效的方式来查找特定的镜像。Docker 提供了多种过滤方式。\n\n#### 按仓库名过滤\n\n```bash\n## 列出所有 ubuntu 镜像\n\n$ docker images ubuntu\nREPOSITORY   TAG     IMAGE ID       SIZE\nubuntu       24.04   329ed837d508   78MB\nubuntu       noble   329ed837d508   78MB\nubuntu       22.04   a1b2c3d4e5f6   72MB\n```\n\n#### 按仓库名和标签过滤\n\n```bash\n$ docker images ubuntu:24.04\nREPOSITORY   TAG     IMAGE ID       SIZE\nubuntu       24.04   329ed837d508   78MB\n```\n\n#### 使用过滤器 --filter\n\n| 过滤条件 | 说明 | 示例 |\n|---------|------|------|\n| `dangling=true` | 虚悬镜像 | `-f dangling=true` |\n| `before=镜像` | 在某镜像之前创建 | `-f before=nginx:latest` |\n| `since=镜像` | 在某镜像之后创建 | `-f since=nginx:latest` |\n| `label=key=value` | 按 LABEL 过滤 | `-f label=version=1.0` |\n| `reference=pattern` | 按名称模式 | `-f reference='*:latest'` |\n\n```bash\n## 列出 nginx 之后创建的镜像\n\n$ docker images -f since=nginx:latest\n\n## 列出所有带 latest 标签的镜像\n\n$ docker images -f reference='*:latest'\n\n## 列出带特定 LABEL 的镜像\n\n$ docker images -f label=maintainer=example@email.com\n```\n\n---\n\n### 4.2.5 虚悬镜像\n\n在镜像列表里，你可能会看到一些仓库名和标签都为 `<none>` 的镜像，这类镜像被称为虚悬镜像。\n\n#### 什么是虚悬镜像\n\n仓库名和标签都显示为 `<none>` 的镜像：\n\n```bash\n$ docker images\nREPOSITORY   TAG       IMAGE ID       SIZE\n<none>       <none>    00285df0df87   342MB\n```\n\n#### 产生原因\n\n1. **镜像重新构建**：新镜像使用了旧镜像的标签，旧镜像标签被移除\n2. **docker pull 更新**：拉取更新版本时，旧版本失去标签\n\n#### 处理虚悬镜像\n\n```bash\n## 列出虚悬镜像\n\n$ docker images -f dangling=true\n\n## 删除虚悬镜像\n\n$ docker image prune\n```\n\n---\n\n### 4.2.6 中间层镜像\n\n除了虚悬镜像，`docker image ls` 默认列出的只是顶层镜像。还有一种镜像是为了加速镜像构建、重复利用资源而存在的中间层镜像。\n\n#### 查看所有镜像：包含中间层\n\n```bash\n$ docker images -a\n```\n\n会显示很多无标签镜像——这些是构建过程中产生的中间层，被其他镜像依赖。\n\n> ⚠️ 不要删除中间层镜像。它们是其他镜像的依赖，删除会导致上层镜像无法使用。删除顶层镜像时会自动清理不再需要的中间层。\n\n---\n\n### 4.2.7 格式化输出\n\n为了配合脚本使用或展示更关注的信息，我们可以使用 `--format` 参数来自定义输出格式。\n\n#### 只输出 ID\n\n```bash\n$ docker images -q\n5f515359c7f8\n05a60462f8ba\n329ed837d508\n```\n\n常用于配合其他命令：\n\n```bash\n## 删除所有镜像\n\n$ docker rmi $(docker images -q)\n\n## 删除所有 redis 镜像\n\n$ docker rmi $(docker images -q redis)\n```\n\n#### 显示完整 ID\n\n```bash\n$ docker images --no-trunc\n```\n\n#### 显示摘要\n\n```bash\n$ docker images --digests\nREPOSITORY   TAG     DIGEST                    IMAGE ID\nnginx        latest  sha256:b4f0e0bdeb5...    e43d811ce2f4\n```\n\n#### 自定义格式\n\n使用 Go 模板语法自定义输出：\n\n```bash\n## 只显示 ID 和仓库名\n\n$ docker images --format \"{{.ID}}: {{.Repository}}\"\n5f515359c7f8: redis\n05a60462f8ba: nginx\n329ed837d508: ubuntu\n\n## 表格形式（带标题）\n\n$ docker images --format \"table {{.Repository}}\\t{{.Tag}}\\t{{.Size}}\"\nREPOSITORY   TAG       SIZE\nredis        latest    183MB\nnginx        latest    181MB\nubuntu       24.04     78MB\n```\n\n#### 可用模板字段\n\n| 字段 | 说明 |\n|------|------|\n| `.ID` | 镜像 ID |\n| `.Repository` | 仓库名 |\n| `.Tag` | 标签 |\n| `.Digest` | 摘要 |\n| `.CreatedSince` | 创建后经过的时间 |\n| `.CreatedAt` | 创建时间 |\n| `.Size` | 大小 |\n\n---\n\n### 4.2.8 常用命令组合\n\n```bash\n## 列出所有镜像及其大小，按大小排序（需要系统 sort 命令）\n\n$ docker images --format \"{{.Size}}\\t{{.Repository}}:{{.Tag}}\" | sort -h\n\n## 查找大于 500MB 的镜像\n\n$ docker images --format \"{{.Size}}\\t{{.Repository}}:{{.Tag}}\" | grep -E \"^[0-9]+GB|^[5-9][0-9]{2}MB\"\n\n## 导出镜像列表\n\n$ docker images --format \"{{.Repository}}:{{.Tag}}\" > images.txt\n```\n\n---\n"
  },
  {
    "path": "04_image/4.3_rm.md",
    "content": "## 4.3 删除本地镜像\n\n当不再需要某个镜像时，我们可以将其删除以释放存储空间。本节介绍删除镜像的常用方法。\n\n### 4.3.1 基本用法\n\n使用 `docker image rm` 删除本地镜像：\n\n```bash\n$ docker image rm [选项] <镜像1> [<镜像2> ...]\n```\n\n> 💡 `docker rmi` 是 `docker image rm` 的简写，两者等效。\n\n---\n\n### 4.3.2 镜像标识方式\n\n删除镜像时，可以使用多种方式指定镜像：\n\n| 方式 | 说明 | 示例 |\n|------|------|------|\n| **短 ID** | ID 的前几位 (通常 3-4 位)| `docker rmi 501` |\n| **长 ID** | 完整的镜像 ID | `docker rmi 501ad78535f0...` |\n| **镜像名:标签** | 仓库名和标签 | `docker rmi redis:alpine` |\n| **镜像摘要** | 精确的内容摘要 | `docker rmi nginx@sha256:...` |\n\n#### 使用短 ID 删除\n\n```bash\n$ docker image ls\nREPOSITORY   TAG     IMAGE ID       SIZE\nredis        alpine  501ad78535f0   30MB\nnginx        latest  e43d811ce2f4   142MB\n\n## 只需输入足够区分的前几位\n\n$ docker rmi 501\nUntagged: redis:alpine\nDeleted: sha256:501ad78535f0...\n```\n\n#### 使用镜像名删除\n\n```bash\n$ docker rmi redis:alpine\nUntagged: redis:alpine\nDeleted: sha256:501ad78535f0...\n```\n\n#### 使用摘要删除\n\n摘要删除最精确，适用于 CI/CD 场景：\n\n```bash\n## 查看镜像摘要\n\n$ docker images --digests\nREPOSITORY   TAG    DIGEST                   IMAGE ID\nnginx        latest sha256:b4f0e0bdeb5...    e43d811ce2f4\n\n## 使用摘要删除\n\n$ docker rmi nginx@sha256:b4f0e0bdeb578043c1ea6862f0d40cc4afe32a4a582f3be235a3b164422be228\n```\n\n---\n\n### 4.3.3 理解输出信息\n\n执行删除命令后，Docker 会输出一系列的操作记录，理解这些信息有助于我们掌握镜像删除的机制。\n\n删除镜像时会看到两类信息：**Untagged** 和 **Deleted**\n\n```bash\n$ docker rmi redis:alpine\nUntagged: redis:alpine\nUntagged: redis@sha256:f1ed3708f538b537eb9c2a7dd50dc90a706f7debd7e1196c9264edeea521a86d\nDeleted: sha256:501ad78535f015d88872e13fa87a828425117e3d28075d0c117932b05bf189b7\nDeleted: sha256:96167737e29ca8e9d74982ef2a0dda76ed7b430da55e321c071f0dbff8c2899b\nDeleted: sha256:32770d1dcf835f192cafd6b9263b7b597a1778a403a109e2cc2ee866f74adf23\n```\n\n#### Untagged vs Deleted\n\n| 操作 | 含义 |\n|------|------|\n| **Untagged** | 移除镜像的标签 |\n| **Deleted** | 删除镜像的存储层 |\n\n#### 删除流程\n\nDocker 会检测镜像是否有容器依赖或其他标签指向，只有在确认为无用资源时才会真正删除存储层。\n\n```mermaid\nflowchart TD\n    Start([\"docker rmi redis:alpine\"]) --> Step1\n    \n    subgraph Process [\"删除流程\"]\n        direction TB\n        Step1[\"1. Untag：移除 redis:alpine 标签\"] --> Step2\n        \n        Step2{\"2. 检查是否还有其他标签指向此镜像\"}\n        Step2 -- \"有\" --> Keep1[\"只 Untag，不删除\"]\n        Step2 -- \"无\" --> Step3\n        \n        Step3{\"3. 检查是否有容器依赖\"}\n        Step3 -- \"有\" --> Error[\"报错，无法删除\"]\n        Step3 -- \"无\" --> Step4\n        \n        Step4{\"4. 从上到下逐层删除，检查每层是否被其他镜像使用\"}\n        Step4 -- \"被使用\" --> Keep2[\"保留该层\"]\n        Step4 -- \"未使用\" --> Delete[\"Deleted (删除该层)\"]\n    end\n```\n\n---\n\n### 4.3.4 批量删除\n\n手动一个一个删除镜像非常繁琐，Docker 提供了 `image prune` 命令和 shell 组合命令来实现批量清理。\n\n#### 删除所有虚悬镜像\n\n虚悬镜像 (dangling)：没有标签的镜像，通常是旧版本被新版本覆盖后产生的\n\n```bash\n## 查看虚悬镜像\n\n$ docker images -f dangling=true\n\n## 删除虚悬镜像\n\n$ docker image prune\n\n## 不提示确认\n\n$ docker image prune -f\n```\n\n#### 删除所有未使用的镜像\n\n```bash\n## 删除所有没有被容器使用的镜像\n\n$ docker image prune -a\n\n## 保留最近 24 小时的\n\n$ docker image prune -a --filter \"until=24h\"\n```\n\n#### 按条件删除\n\n```bash\n## 删除所有 redis 镜像\n\n$ docker rmi $(docker images -q redis)\n\n## 删除 mongo:8.0 之前的所有镜像\n\n$ docker rmi $(docker images -q -f before=mongo:8.0)\n\n## 删除某个时间之前的镜像\n\n$ docker image prune -a --filter \"until=168h\"  # 7天前\n```\n\n---\n\n### 4.3.5 删除失败的常见原因\n\n在删除镜像时，Docker 可能会提示错误并拒绝执行。这通常是为了防止误删正在使用的资源。\n\n#### 原因一：有容器依赖\n\n```bash\n$ docker rmi nginx\nError: conflict: unable to remove repository reference \"nginx\" \n(must force) - container abc123 is using its referenced image\n```\n\n**解决方案**：\n\n```bash\n## 方案1：先删除依赖的容器\n\n$ docker rm abc123\n$ docker rmi nginx\n\n## 方案2：强制删除镜像（容器仍可运行，但无法再创建新容器）\n\n$ docker rmi -f nginx\n```\n\n#### 原因二：多个标签指向同一镜像\n\n```bash\n$ docker images\nREPOSITORY   TAG     IMAGE ID\nubuntu       24.04   ca2b0f26964c\nubuntu       latest  ca2b0f26964c   # 同一个镜像\n\n$ docker rmi ubuntu:24.04\nUntagged: ubuntu:24.04\n## 只是移除标签，镜像仍存在（因为还有 ubuntu:latest 指向它）\n```\n\n当同一个镜像有多个标签时，`docker rmi` 只是删除指定的标签，不会删除镜像本身。\n\n#### 原因三：被其他镜像依赖：中间层\n\n```bash\n$ docker rmi some_base_image\nError: image has dependent child images\n```\n\n中间层镜像被其他镜像依赖，无法删除。需要先删除依赖它的镜像。\n\n---\n\n### 4.3.6 常用过滤条件\n\n| 过滤条件 | 说明 | 示例 |\n|---------|------|------|\n| `dangling=true` | 虚悬镜像 | `-f dangling=true` |\n| `before=镜像` | 在某镜像之前 | `-f before=mongo:3.2` |\n| `since=镜像` | 在某镜像之后 | `-f since=mongo:3.2` |\n| `label=key=value` | 按标签过滤 | `-f label=version=1.0` |\n| `reference=pattern` | 按名称模式 | `-f reference='*:latest'` |\n\n---\n\n### 4.3.7 清理策略\n\n针对不同的环境 (开发环境 vs 生产环境)，我们应该采用不同的镜像清理策略。\n\n#### 开发环境\n\n```bash\n## 定期清理虚悬镜像\n\n$ docker image prune -f\n\n## 一键清理所有未使用资源\n\n$ docker system prune -a\n```\n\n#### CI/CD 环境\n\n```bash\n## 只保留最近使用的镜像\n\n$ docker image prune -a --filter \"until=72h\" -f\n```\n\n#### 查看空间占用\n\n```bash\n$ docker system df\nTYPE            TOTAL   ACTIVE   SIZE      RECLAIMABLE\nImages          15      3        2.5GB     1.8GB (72%)\nContainers      5       2        100MB     80MB (80%)\nLocal Volumes   8       2        500MB     400MB (80%)\nBuild Cache     0       0        0B        0B\n```\n\n---\n"
  },
  {
    "path": "04_image/4.4_commit.md",
    "content": "## 4.4 利用 commit 理解镜像构成\n\n> 注意：如果您是初学者，您可以暂时跳过后面的内容，直接学习[容器](../05_container/)一节。\n\n注意：`docker commit` 命令除了学习之外，还有一些特殊的应用场合，比如被入侵后保存现场等。但是，不要使用 `docker commit` 定制镜像，定制镜像应该使用 `Dockerfile` 来完成。如果你想要定制镜像请查看下一小节。\n\n镜像是容器的基础，每次执行 `docker run` 的时候都会指定哪个镜像作为容器运行的基础。在之前的例子中，我们所使用的都是来自于 Docker Hub 的镜像。直接使用这些镜像是可以满足一定的需求，而当这些镜像无法直接满足需求时，我们就需要定制这些镜像。接下来的几节就将讲解如何定制镜像。\n\n回顾一下之前我们学到的知识，镜像是多层存储，每一层是在前一层的基础上进行的修改；而容器同样也是多层存储，是在以镜像为基础层，在其基础上加一层作为容器运行时的存储层。\n\n现在让我们以定制一个 Web 服务器为例子，来讲解镜像是如何构建的。\n\n```bash\n$ docker run --name webserver -d -p 80:80 nginx\n```\n\n这条命令会用 `nginx` 镜像启动一个容器，命名为 `webserver`，并且映射了 80 端口，这样我们可以用浏览器去访问这个 `nginx` 服务器。\n\n如果是在本机运行的 Docker，那么可以直接访问：`http://localhost`，如果是在虚拟机、云服务器上安装的 Docker，则需要将 `localhost` 换为虚拟机地址或者实际云服务器地址。\n\n直接用浏览器访问的话，我们会看到默认的 Nginx 欢迎页面。\n\n![图](../_images/images-mac-example-nginx.png)\n\n现在，假设我们非常不喜欢这个欢迎页面，我们希望改成欢迎 Docker 的文字，我们可以使用 `docker exec` 命令进入容器，修改其内容。\n\n```bash\n$ docker exec -it webserver bash\nroot@3729b97e8226:/# echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html\nroot@3729b97e8226:/# exit\nexit\n```\n\n我们以交互式终端方式进入 `webserver` 容器，并执行了 `bash` 命令，也就是获得一个可操作的 Shell。\n\n然后，我们用 `<h1>Hello, Docker!</h1>` 覆盖了 `/usr/share/nginx/html/index.html` 的内容。\n\n现在我们再刷新浏览器的话，会发现内容被改变了。\n\n![图](../_images/images-create-nginx-docker.png)\n\n我们修改了容器的文件，也就是改动了容器的存储层。我们可以通过 `docker diff` 命令看到具体的改动。\n\n```bash\n$ docker diff webserver\nC /root\nA /root/.bash_history\nC /run\nC /usr\nC /usr/share\nC /usr/share/nginx\nC /usr/share/nginx/html\nC /usr/share/nginx/html/index.html\nC /var\nC /var/cache\nC /var/cache/nginx\nA /var/cache/nginx/client_temp\nA /var/cache/nginx/fastcgi_temp\nA /var/cache/nginx/proxy_temp\nA /var/cache/nginx/scgi_temp\nA /var/cache/nginx/uwsgi_temp\n```\n\n现在我们定制好了变化，我们希望能将其保存下来形成镜像。\n\n要知道，当我们运行一个容器的时候 (如果不使用卷的话)，我们做的任何文件修改都会被记录于容器存储层里。而 Docker 提供了一个 `docker commit` 命令，可以将容器的存储层保存下来成为镜像。换句话说，就是在原有镜像的基础上，再叠加上容器的存储层，并构成新的镜像。以后我们运行这个新镜像的时候，就会拥有原有容器最后的文件变化。\n\n`docker commit` 的语法格式为：\n\n```bash\ndocker commit [选项] <容器ID或容器名> [<仓库名>[:<标签>]]\n```\n\n我们可以用下面的命令将容器保存为镜像：\n\n```bash\n$ docker commit \\\n    --author \"Tao Wang <twang2218@gmail.com>\" \\\n    --message \"修改了默认网页\" \\\n    webserver \\\n    nginx:v2\nsha256:07e33465974800ce65751acc279adc6ed2dc5ed4e0838f8b86f0c87aa1795214\n```\n\n其中 `--author` 是指定修改的作者，而 `--message` 则是记录本次修改的内容。这点和 `git` 版本控制相似，不过这里这些信息可以省略留空。\n\n我们可以在 `docker image ls` 中看到这个新定制的镜像：\n\n```bash\n$ docker image ls nginx\nREPOSITORY          TAG                 IMAGE ID            CREATED             SIZE\nnginx               v2                  07e334659748        9 seconds ago       181.5 MB\nnginx               1.27                05a60462f8ba        12 days ago         181.5 MB\nnginx               latest              e43d811ce2f4        4 weeks ago         181.5 MB\n```\n\n我们还可以用 `docker history` 具体查看镜像内的历史记录，如果比较 `nginx:latest` 的历史记录，我们会发现新增了我们刚刚提交的这一层。\n\n```bash\n$ docker history nginx:v2\nIMAGE               CREATED             CREATED BY                                      SIZE                COMMENT\n07e334659748        54 seconds ago      nginx -g daemon off;                            95 B                修改了默认网页\ne43d811ce2f4        4 weeks ago         /bin/sh -c #(nop)  CMD [\"nginx\" \"-g\" \"daemon    0 B\n<missing>           4 weeks ago         /bin/sh -c #(nop)  EXPOSE 443/tcp 80/tcp        0 B\n<missing>           4 weeks ago         /bin/sh -c ln -sf /dev/stdout /var/log/nginx/   22 B\n<missing>           4 weeks ago         /bin/sh -c apt-key adv --keyserver hkp://pgp.   58.46 MB\n<missing>           4 weeks ago         /bin/sh -c #(nop)  ENV NGINX_VERSION=1.27.0-1   0 B\n<missing>           4 weeks ago         /bin/sh -c #(nop)  MAINTAINER NGINX Docker Ma   0 B\n<missing>           4 weeks ago         /bin/sh -c #(nop)  CMD [\"/bin/bash\"]            0 B\n<missing>           4 weeks ago         /bin/sh -c #(nop) ADD file:23aa4f893e3288698c   123 MB\n```\n\n新的镜像定制好后，我们可以来运行这个镜像。\n\n```bash\ndocker run --name web2 -d -p 81:80 nginx:v2\n```\n\n这里我们命名为新的服务为 `web2`，并且映射到 `81` 端口。访问 `http://localhost:81` 看到结果，其内容应该和之前修改后的 `webserver` 一样。\n\n至此，我们第一次完成了定制镜像，使用的是 `docker commit` 命令，手动操作给旧的镜像添加了新的一层，形成新的镜像，对镜像多层存储应该有了更直观的感觉。\n\n### 4.4.1 慎用 `docker commit`\n\n使用 `docker commit` 命令虽然可以比较直观的帮助理解镜像分层存储的概念，但是实际环境中并不会这样使用。\n\n首先，如果仔细观察之前的 `docker diff webserver` 的结果，你会发现除了真正想要修改的 `/usr/share/nginx/html/index.html` 文件外，由于命令的执行，还有很多文件被改动或添加了。这还仅仅是最简单的操作，如果是安装软件包、编译构建，那会有大量的无关内容被添加进来，将会导致镜像极为臃肿。\n\n此外，使用 `docker commit` 意味着所有对镜像的操作都是黑箱操作，生成的镜像也被称为 **黑箱镜像**，换句话说，就是除了制作镜像的人知道执行过什么命令、怎么生成的镜像，别人根本无从得知。而且，即使是这个制作镜像的人，过一段时间后也无法记清具体的操作。这种黑箱镜像的维护工作是非常痛苦的。\n\n而且，回顾之前提及的镜像所使用的分层存储的概念，除当前层外，之前的每一层都是不会发生改变的，换句话说，任何修改的结果仅仅是在当前层进行标记、添加、修改，而不会改动上一层。如果使用 `docker commit` 制作镜像，以及后期修改的话，每一次修改都会让镜像更加臃肿一次，所删除的上一层的东西并不会丢失，会一直如影随形的跟着这个镜像，即使根本无法访问到。这会让镜像更加臃肿。\n"
  },
  {
    "path": "04_image/4.5_build.md",
    "content": "## 4.5 使用 Dockerfile 定制镜像\n\n从刚才的 `docker commit` 的学习中，我们可以了解到，镜像的定制实际上就是定制每一层所添加的配置、文件。如果我们可以把每一层修改、安装、构建、操作的命令都写入一个脚本，用这个脚本来构建、定制镜像，那么之前提及的无法重复的问题、镜像构建透明性的问题、体积的问题就都会解决。这个脚本就是 Dockerfile。\n\nDockerfile 是一个文本文件，其内包含了一条条的 **指令 (Instruction)**，每一条指令构建一层，因此每一条指令的内容，就是描述该层应当如何构建。\n\n### 4.5.1 使用 docker init 快速创建：推荐\n\nDocker 提供了 `docker init` 命令，可以根据项目类型自动生成 Dockerfile、.dockerignore 和 compose.yaml 文件：\n\n```bash\n$ docker init\n```\n\n该命令会交互式地询问项目类型 (如 Go、Node.js、Python、Rust 等)，并生成符合最佳实践的配置文件。对于新项目，这是推荐的起步方式。\n\n### 4.5.2 手动创建 Dockerfile\n\n还以之前定制 `nginx` 镜像为例，这次我们使用 Dockerfile 来定制。\n\n在一个空白目录中，建立一个文本文件，并命名为 `Dockerfile`：\n\n```bash\n$ mkdir mynginx\n$ cd mynginx\n$ touch Dockerfile\n```\n\n其内容为：\n\n```docker\nFROM nginx\nRUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html\n```\n\n这个 Dockerfile 很简单，一共就两行。涉及到了两条指令，`FROM` 和 `RUN`。\n\n### 4.5.3 FROM 指定基础镜像\n\n所谓定制镜像，那一定是以一个镜像为基础，在其上进行定制。就像我们之前运行了一个 `nginx` 镜像的容器，再进行修改一样，基础镜像是必须指定的。而 `FROM` 就是指定 **基础镜像**，因此一个 `Dockerfile` 中 `FROM` 是必备的指令，并且必须是第一条指令。\n\n在 [Docker Hub](https://hub.docker.com/search?q=&type=image&image_filter=official) 上有非常多的高质量的官方镜像，有可以直接拿来使用的服务类的镜像，如 [`nginx`](https://hub.docker.com/_/nginx/)、[`redis`](https://hub.docker.com/_/redis/)、[`mongo`](https://hub.docker.com/_/mongo/)、[`mysql`](https://hub.docker.com/_/mysql/)、[`httpd`](https://hub.docker.com/_/httpd/)、[`php`](https://hub.docker.com/_/php/)、[`tomcat`](https://hub.docker.com/_/tomcat/) 等；也有一些方便开发、构建、运行各种语言应用的镜像，如 [`node`](https://hub.docker.com/_/node)、[`openjdk`](https://hub.docker.com/_/openjdk/)、[`python`](https://hub.docker.com/_/python/)、[`ruby`](https://hub.docker.com/_/ruby/)、[`golang`](https://hub.docker.com/_/golang/) 等。可以在其中寻找一个最符合我们最终目标的镜像为基础镜像进行定制。\n\n如果没有找到对应服务的镜像，官方镜像中还提供了一些更为基础的操作系统镜像，如 [`ubuntu`](https://hub.docker.com/_/ubuntu/)、[`debian`](https://hub.docker.com/_/debian/)、[`centos`](https://hub.docker.com/_/centos/)、[`fedora`](https://hub.docker.com/_/fedora/)、[`alpine`](https://hub.docker.com/_/alpine/) 等，这些操作系统的软件库为我们提供了更广阔的扩展空间。\n\n除了选择现有镜像为基础镜像外，Docker 还存在一个特殊的镜像，名为 `scratch`。这个镜像是虚拟的概念，并不实际存在，它表示一个空白的镜像。\n\n```docker\nFROM scratch\n...\n```\n\n如果你以 `scratch` 为基础镜像的话，意味着你不以任何镜像为基础，接下来所写的指令将作为镜像第一层开始存在。\n\n不以任何系统为基础，直接将可执行文件复制进镜像的做法并不罕见，对于 Linux 下静态编译的程序来说，并不需要有操作系统提供运行时支持，所需的一切库都已经在可执行文件里了，因此直接 `FROM scratch` 会让镜像体积更加小巧。使用 [Go 语言](https://golang.google.cn/)开发的应用很多会使用这种方式来制作镜像，这也是有人认为 Go 是特别适合容器微服务架构的语言的原因之一。\n\n### 4.5.4 RUN 执行命令\n\n`RUN` 指令是用来执行命令行命令的。由于命令行的强大能力，`RUN` 指令在定制镜像时是最常用的指令之一。其格式有两种：\n\n* *shell* 格式：`RUN <命令>`，就像直接在命令行中输入的命令一样。刚才写的 Dockerfile 中的 `RUN` 指令就是这种格式。\n\n```docker\nRUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html\n```\n\n* *exec* 格式：`RUN [“可执行文件”, “参数1”, “参数2”]`，这更像是函数调用中的格式。\n\nDockerfile 中每一个指令都会建立一层，`RUN` 也不例外。每一个 `RUN` 的行为，就和刚才我们手工建立镜像的过程一样：新建立一层，在其上执行这些命令，执行结束后，`commit` 这一层的修改，构成新的镜像。\n\n> **注意**\n>\n> 每一个 `RUN` 指令都会产生一个新的镜像层。为了减少镜像体积和层数，我们通常会将多个命令合并到一个 `RUN` 指令中执行。\n>\n> 更多关于 `RUN` 指令的详细用法、最佳实践 (如清理缓存、使用 pipefail 等) 及 `Union FS` 的层数限制等内容，请参阅 **[第七章 Dockerfile 指令详解](../07_dockerfile/README.md)** 中的 **[RUN 指令](../07_dockerfile/7.1_run.md)** 小节。\n\n要想编写优秀的 `Dockerfile`，必须了解每一条指令的作用和副作用。在 **[第七章 Dockerfile 指令详解](../07_dockerfile/README.md)** 中，我们将对 `COPY`，`ADD`，`CMD`，`ENTRYPOINT` 等指令进行详细讲解。\n\n### 4.5.5 构建镜像\n\n好了，让我们再回到之前定制的 nginx 镜像的 Dockerfile 来。现在我们明白了这个 Dockerfile 的内容，那么让我们来构建这个镜像吧。\n\n在 `Dockerfile` 文件所在目录执行：\n\n```bash\n$ docker build -t nginx:v3 .\nSending build context to Docker daemon 2.048 kB\nStep 1 : FROM nginx\n ---> e43d811ce2f4\nStep 2 : RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html\n ---> Running in 9cdc27646c7b\n ---> 44aa4490ce2c\nRemoving intermediate container 9cdc27646c7b\nSuccessfully built 44aa4490ce2c\n```\n\n从命令的输出结果中，我们可以清晰的看到镜像的构建过程。在 `Step 2` 中，如同我们之前所说的那样，`RUN` 指令启动了一个容器 `9cdc27646c7b`，执行了所要求的命令，并最后提交了这一层 `44aa4490ce2c`，随后删除了所用到的这个容器 `9cdc27646c7b`。\n\n这里我们使用了 `docker build` 命令进行镜像构建。其格式为：\n\n```bash\ndocker build [选项] <上下文路径/URL/->\n```\n\n在这里我们指定了最终镜像的名称 `-t nginx:v3`，构建成功后，我们可以像之前运行 `nginx:v2` 那样来运行这个镜像，其结果会和 `nginx:v2` 一样。\n\n### 4.5.6 镜像构建上下文\n\n如果注意，会看到 `docker build` 命令最后有一个 `.`。`.` 表示当前目录，而 `Dockerfile` 就在当前目录，因此不少初学者以为这个路径是在指定 `Dockerfile` 所在路径，这么理解其实是不准确的。如果对应上面的命令格式，你可能会发现，这是在指定 **上下文路径**。那么什么是上下文呢？\n\n首先我们要理解 `docker build` 的工作原理。Docker 在运行时分为 Docker 引擎 (也就是服务端守护进程) 和客户端工具。Docker 的引擎提供了一组 REST API，被称为 [Docker Remote API](https://docs.docker.com/develop/sdk/)，而如 `docker` 命令这样的客户端工具，则是通过这组 API 与 Docker 引擎交互，从而完成各种功能。因此，虽然表面上我们好像是在本机执行各种 `docker` 功能，但实际上，一切都是使用的远程调用形式在服务端 (Docker 引擎) 完成。也因为这种 C/S 设计，让我们操作远程服务器的 Docker 引擎变得轻而易举。\n\n当我们进行镜像构建的时候，并非所有定制都会通过 `RUN` 指令完成，经常会需要将一些本地文件复制进镜像，比如通过 `COPY` 指令、`ADD` 指令等。而 `docker build` 命令构建镜像，其实并非在本地构建，而是在服务端，也就是 Docker 引擎中构建的。那么在这种客户端/服务端的架构中，如何才能让服务端获得本地文件呢？\n\n这就引入了上下文的概念。当构建的时候，用户会指定构建镜像上下文的路径，`docker build` 命令得知这个路径后，会将路径下的所有内容打包，然后上传给 Docker 引擎。这样 Docker 引擎收到这个上下文包后，展开就会获得构建镜像所需的一切文件。\n\n如果在 `Dockerfile` 中这么写：\n\n```docker\nCOPY ./package.json /app/\n```\n\n这并不是要复制执行 `docker build` 命令所在的目录下的 `package.json`，也不是复制 `Dockerfile` 所在目录下的 `package.json`，而是复制 **上下文 (context)** 目录下的 `package.json`。\n\n因此，`COPY` 这类指令中的源文件的路径都是*相对路径*。这也是初学者经常会问的为什么 `COPY ../package.json /app` 或者 `COPY /opt/xxxx /app` 无法工作的原因，因为这些路径已经超出了上下文的范围，Docker 引擎无法获得这些位置的文件。如果真的需要那些文件，应该将它们复制到上下文目录中去。\n\n现在就可以理解刚才的命令 `docker build -t nginx:v3 .` 中的这个 `.`，实际上是在指定上下文的目录，`docker build` 命令会将该目录下的内容打包交给 Docker 引擎以帮助构建镜像。\n\n如果观察 `docker build` 输出，我们其实已经看到了这个发送上下文的过程：\n\n```bash\n$ docker build -t nginx:v3 .\nSending build context to Docker daemon 2.048 kB\n...\n```\n\n理解构建上下文对于镜像构建是很重要的，避免犯一些不应该的错误。比如有些初学者在发现 `COPY /opt/xxxx /app` 不工作后，于是干脆将 `Dockerfile` 放到了硬盘根目录去构建，结果发现 `docker build` 执行后，在发送一个几十 GB 的东西，极为缓慢而且很容易构建失败。那是因为这种做法是在让 `docker build` 打包整个硬盘，这显然是使用错误。\n\n一般来说，应该会将 `Dockerfile` 置于一个空目录下，或者项目根目录下。如果该目录下没有所需文件，那么应该把所需文件复制一份过来。如果目录下有些东西确实不希望构建时传给 Docker 引擎，那么可以用 `.gitignore` 一样的语法写一个 `.dockerignore`，该文件是用于剔除不需要作为上下文传递给 Docker 引擎的。\n\n那么为什么会有人误以为 `.` 是指定 `Dockerfile` 所在目录呢？这是因为在默认情况下，如果不额外指定 `Dockerfile` 的话，会将上下文目录下的名为 `Dockerfile` 的文件作为 Dockerfile。\n\n这只是默认行为，实际上 `Dockerfile` 的文件名并不要求必须为 `Dockerfile`，而且并不要求必须位于上下文目录中，比如可以用 `-f ../Dockerfile.php` 参数指定某个文件作为 `Dockerfile`。\n\n当然，一般大家习惯性的会使用默认的文件名 `Dockerfile`，以及会将其置于镜像构建上下文目录中。\n\n### 4.5.7 其它 `docker build` 的用法\n\n#### 直接用 Git repo 进行构建\n\n或许你已经注意到了，`docker build` 还支持从 URL 构建，比如可以直接从 Git repo 中构建：\n\n```bash\n## $env:DOCKER_BUILDKIT=0\n\n## export DOCKER_BUILDKIT=0\n\n$ docker build -t hello-world https://github.com/docker-library/hello-world.git#master:amd64/hello-world\n\nStep 1/3 : FROM scratch\n --->\nStep 2/3 : COPY hello /\n ---> ac779757d46e\nStep 3/3 : CMD [\"/hello\"]\n ---> Running in d2a513a760ed\nRemoving intermediate container d2a513a760ed\n ---> 038ad4142d2b\nSuccessfully built 038ad4142d2b\n```\n\n这行命令指定了构建所需的 Git repo，并且指定分支为 `master`，构建目录为 `/amd64/hello-world/`，然后 Docker 就会自己去 `git clone` 这个项目、切换到指定分支、并进入到指定目录后开始构建。\n\n#### 用给定的 tar 压缩包构建\n\n```bash\n$ docker build http://server/context.tar.gz\n```\n\n如果所给出的 URL 不是个 Git repo，而是个 `tar` 压缩包，那么 Docker 引擎会下载这个包，并自动解压缩，以其作为上下文，开始构建。\n\n#### 从标准输入中读取 Dockerfile 进行构建\n\n```bash\ndocker build - < Dockerfile\n```\n\n或\n\n```bash\ncat Dockerfile | docker build -\n```\n\n如果标准输入传入的是文本文件，则将其视为 `Dockerfile`，并开始构建。这种形式由于直接从标准输入中读取 Dockerfile 的内容，它没有上下文，因此不可以像其他方法那样可以将本地文件 `COPY` 进镜像之类的事情。\n\n#### 从标准输入中读取上下文压缩包进行构建\n\n```bash\n$ docker build - < context.tar.gz\n```\n\n如果发现标准输入的文件格式是 `gzip`、`bzip2` 以及 `xz` 的话，将会使其为上下文压缩包，直接将其展开，将里面视为上下文，并开始构建。\n"
  },
  {
    "path": "04_image/4.6_other.md",
    "content": "## 4.6 其它制作镜像的方式\n\n除了标准的使用 `Dockerfile` 生成镜像的方法外，由于各种特殊需求和历史原因，还提供了一些其它方法用以生成镜像。\n\n### 4.6.1 从 rootfs 压缩包导入\n\n格式：`docker import [选项] <文件>|<URL>|- [<仓库名>[:<标签>]]`\n\n压缩包可以是本地文件、远程 Web 文件，甚至是从标准输入中得到。压缩包将会在镜像 `/` 目录展开，并直接作为镜像第一层提交。\n\n比如我们想要创建一个 [OpenVZ](https://openvz.org) 的 Ubuntu 16.04 [模板](https://wiki.openvz.org/Download/template/precreated)的镜像：\n\n```bash\n$ docker import \\\n    http://download.openvz.org/template/precreated/ubuntu-16.04-x86_64.tar.gz \\\n    openvz/ubuntu:16.04\n\nDownloading from http://download.openvz.org/template/precreated/ubuntu-16.04-x86_64.tar.gz\nsha256:412b8fc3e3f786dca0197834a698932b9c51b69bd8cf49e100c35d38c9879213\n```\n\n这条命令自动下载了 `ubuntu-16.04-x86_64.tar.gz` 文件，并且作为根文件系统展开导入，并保存为镜像 `openvz/ubuntu:16.04`。\n\n导入成功后，我们可以用 `docker image ls` 看到这个导入的镜像：\n\n```bash\n$ docker image ls openvz/ubuntu\nREPOSITORY          TAG                 IMAGE ID            CREATED             SIZE\nopenvz/ubuntu       16.04               412b8fc3e3f7        55 seconds ago      505MB\n```\n\n如果我们查看其历史的话，会看到描述中有导入的文件链接：\n\n```bash\n$ docker history openvz/ubuntu:16.04\nIMAGE               CREATED              CREATED BY          SIZE                COMMENT\nf477a6e18e98        About a minute ago                       214.9 MB            Imported from http://download.openvz.org/template/precreated/ubuntu-16.04-x86_64.tar.gz\n```\n\n### 4.6.2 Docker 镜像的导入和导出 `docker save` 和 `docker load`\n\nDocker 还提供了 `docker save` 和 `docker load` 命令，用以将镜像保存为一个文件，然后传输到另一个位置上，再加载进来。这是在没有 Docker Registry 时的做法，现在已经不推荐，镜像迁移应该直接使用 Docker Registry，无论是直接使用 Docker Hub 还是使用内网私有 Registry 都可以。\n\n#### 保存镜像\n\n使用 `docker save` 命令可以将镜像保存为归档文件。\n\n比如我们希望保存这个 `alpine` 镜像。\n\n```bash\n$ docker image ls alpine\nREPOSITORY          TAG                 IMAGE ID            CREATED             SIZE\nalpine              latest              baa5d63471ea        5 weeks ago         4.803 MB\n```\n\n保存镜像的命令为：\n\n```bash\n$ docker save alpine -o filename\n$ file filename\nfilename: POSIX tar archive\n```\n\n这里的 filename 可以为任意名称甚至任意后缀名，但文件的本质都是归档文件\n\n**注意：如果同名则会覆盖 (没有警告)**\n\n若使用 `gzip` 压缩：\n\n```bash\n$ docker save alpine | gzip > alpine-latest.tar.gz\n```\n\n然后我们将 `alpine-latest.tar.gz` 文件复制到了到了另一个机器上，可以用下面这个命令加载镜像：\n\n```bash\n$ docker load -i alpine-latest.tar.gz\nLoaded image: alpine:latest\n```\n\n如果我们结合这两个命令以及 `ssh` 甚至 `pv` 的话，利用 Linux 强大的管道，我们可以写一个命令完成从一个机器将镜像迁移到另一个机器，并且带进度条的功能：\n\n```bash\ndocker save <镜像名> | bzip2 | pv | ssh <用户名>@<主机名> 'cat | docker load'\n```\n"
  },
  {
    "path": "04_image/4.7_internal.md",
    "content": "## 4.7 实现原理\n\nDocker 镜像是怎么实现增量的修改和维护的？为什么容器启动如此之快？这一切都归功于 Docker 的镜像分层存储设计。\n\n### 4.7.1 镜像与分层存储\n\n在之前的章节中，我们一直强调镜像包含操作系统完整的 `root` 文件系统，其体积往往是庞大的。因此在 Docker 设计时，就充分利用 **Union FS** 的技术，将其设计为分层存储的架构。\n\nDocker 镜像并不是一个单纯的文件，而是由一组文件系统叠加构成的。\n\n最底层的镜像称为 **基础镜像 (Base Image)**，通常是各种 Linux 发行版的 root 文件系统，如 Ubuntu、Debian、CentOS 等。\n\n当我们在基础镜像之上构建新的镜像时 (例如安装了 Nginx)，Docker 并不是复制一份基础镜像，而是在基础镜像之上，**新建一个层 (Layer)**，并在该层中仅记录为了安装 Nginx 而发生的文件变更 (添加、修改、删除)。\n\n这种分层存储结构使得镜像的复用、分发变得非常高效：\n\n*   **复用**：如果多个镜像都基于同一个基础镜像 (例如都基于 `ubuntu:24.04`)，那么宿主机只需要下载一份 `ubuntu:24.04`，所有镜像都可以共享它。\n*   **轻量**：镜像仅仅记录了与基础镜像的差异，因此体积非常小。\n\n### 4.7.2 容器层与读写\n\n我们要理解的一个关键概念是：**镜像的每一层都是只读的 (Read-only)**。\n\n那么，既然镜像只读，容器为什么能写文件呢？\n\n当容器启动时，Docker 会在镜像的最上层，添加一个新的 **可写层 (Writable Layer)**，通常被称为 **容器层**。\n\n```mermaid\nflowchart TD\n    subgraph Container [\"运行中的容器\"]\n        direction TB\n        L4[\"容器层 (可写, Writable Container Layer)\"]\n        L3[\"镜像层 (只读, Read-only Image Layer)\"]\n        L2[\"镜像层 (只读, Read-only Image Layer)\"]\n        L1[\"基础镜像层 (只读, Base Image Layer)\"]\n        \n        L4 --- L3 --- L2 --- L1\n    end\n    Note[\"所有的写操作都在容器层这里\"] -.-> L4\n```\n\n*   **读取文件**：当容器需要读取文件时，Docker 会从最上层 (容器层) 开始向下层 (镜像层) 寻找，直到找到该文件为止。\n*   **修改文件**：当容器需要修改某个文件时，Docker 会从下层镜像中将该文件复制到上层的容器层，然后对副本进行修改。这被称为 **写时复制 (Copy-on-Write，CoW)** 策略。\n*   **删除文件**：当容器删除某个文件时，Docker 并不是真的去下层删除它 (因为下层是只读的)，而是在容器层创建一个特殊的 “白障 (Whiteout)” 文件，用来标记该文件已被删除，从而在容器视图中隐藏它。\n\n这就是为什么：\n\n1.  **容器删除后数据会丢失**：因为所有的数据修改都保存在最上层的容器层中，容器销毁时，这个层也就随之销毁了。(除非使用了数据卷，详见[数据管理](../08_data/README.md))。\n2.  **镜像不可变**：无论我们在容器里删除了多少文件，基础镜像的体积并不会减小，因为它们依然存在于底层的只读层中。\n\n### 4.7.3 内容寻址与镜像 ID\n\nDocker 镜像的每一层都有一个唯一的 ID，这个 ID 是根据该层的内容计算出来的哈希值 (SHA256)。这意味着：\n\n*   **内容即 ID**：只要层的内容有一丁点变化，ID 就会变。\n*   **安全性**：确保了镜像内容的完整性，下载过程中如果数据损坏，ID 校验就会失败。\n*   **去重**：如果两个不同的镜像 (甚至是不同来源的镜像) 包含相同的层 (ID 相同)，Docker 引擎在本地只会存储一份，绝不重复下载。\n\n### 4.7.4 联合文件系统\n\nDocker 使用联合文件系统 (Union FS) 来实现这种分层挂载。常见的驱动包括 `overlay2` (目前推荐)、`aufs` (早期使用)、`btrfs`、`zfs` 等。\n\n虽然实现细节不同，但它们都遵循上述的 **分层 + CoW** 模型。\n\n> 想要深入了解 Overlay2 等文件系统的具体实现原理，包括 WorkDir、UpperDir、LowerDir 等底层细节，请阅读 **[第十二章 底层实现](../12_implementation/README.md)** 中的 **[联合文件系统](../12_implementation/12.4_ufs.md)** 章节。\n"
  },
  {
    "path": "04_image/README.md",
    "content": "# 第四章 使用镜像\n\n在之前的介绍中，我们知道镜像是 Docker 的三大组件之一。\n\nDocker 运行容器前需要本地存在对应的镜像，如果本地不存在该镜像，Docker 会从镜像仓库下载该镜像。\n\n## 本章内容\n\n本章将介绍更多关于镜像的内容，包括：\n\n* [从仓库获取镜像](4.1_pull.md)\n* [列出镜像](4.2_list.md)\n* [删除本地镜像](4.3_rm.md)\n* [利用 commit 理解镜像构成](4.4_commit.md)\n* [使用 Dockerfile 定制镜像](4.5_build.md)\n* [其它制作镜像的方式](4.6_other.md)\n* [镜像的实现原理](4.7_internal.md)\n\n> **版本提示：镜像存储后端的变迁**\n> \n> 在 Docker Engine v29 及后续版本中，Docker 全新安装默认启用了 **containerd image store**（替代了传统的 classic store）。这一底层架构级别的变迁，意味着 Docker 解锁了对 OCI Image Index 和 Attestations （例如原生的 provenance 来源证明与 SBOM 软件物料清单）的全量本地支持。\n> 读者在执行类似 `docker buildx build --provenance=mode=min --sbom=true` 甚至使用后续审查工具（如 `docker buildx imagetools inspect`）时，其元数据能够与镜像数据一并完好地管理于本地存储系统中，为供应链安全验证补齐了最后一块拼图。\n"
  },
  {
    "path": "04_image/demo/buildkit/Dockerfile",
    "content": "FROM node:alpine as builder\n\nWORKDIR /app\n\nCOPY package.json /app/\n\nRUN npm i --registry=https://registry.npmmirror.com \\\n        && rm -rf ~/.npm\n\nCOPY src /app/src\n\nRUN npm run build\n\nFROM nginx:alpine\n\nCOPY --from=builder /app/dist /app/dist\n"
  },
  {
    "path": "04_image/demo/buildkit/Dockerfile.buildkit",
    "content": "# syntax = docker/dockerfile:experimental\n\nFROM node:alpine as builder\n\nWORKDIR /app\n\nCOPY package.json /app/\n\nRUN --mount=type=cache,target=/app/node_modules,id=my_app_npm_module,sharing=locked \\\n    --mount=type=cache,target=/root/.npm,id=npm_cache \\\n        npm i --registry=https://registry.npmmirror.com\n\nCOPY src /app/src\n\nRUN --mount=type=cache,target=/app/node_modules,id=my_app_npm_module,sharing=locked \\\n# --mount=type=cache,target=/app/dist,id=my_app_dist,sharing=locked \\\n        npm run build\n\nFROM nginx:alpine\n\n# COPY --from=builder /app/dist /app/dist\n\n# 为了更直观的说明 from 和 source 指令，这里使用 RUN 指令\nRUN --mount=type=cache,target=/tmp/dist,from=builder,source=/app/dist \\\n    # --mount=type=cache,target/tmp/dist,from=my_app_dist,sharing=locked \\\n    mkdir -p /app/dist && cp -r /tmp/dist/* /app/dist\n\nRUN --mount=type=bind,from=php:alpine,source=/usr/local/bin/docker-php-entrypoint,target=/docker-php-entrypoint \\\n        cat /docker-php-entrypoint\n\nRUN --mount=type=tmpfs,target=/temp \\\n        mount | grep /temp\n\nRUN --mount=type=secret,id=aws,target=/root/.aws/credentials \\\n        cat /root/.aws/credentials\n\n# docker build -t test --secret id=aws,src=$PWD/aws.txt --progress=plain -f Dockerfile.buildkit .\n"
  },
  {
    "path": "04_image/demo/buildkit/aws.txt",
    "content": "awskey\n"
  },
  {
    "path": "04_image/demo/buildkit/package.json",
    "content": "{\n  \"name\": \"my_app\",\n  \"version\": \"19.6.0\",\n  \"devDependencies\": {\n    \"webpack\": \"*\",\n    \"webpack-cli\": \"*\"\n  },\n  \"scripts\": {\n    \"build\": \"mkdir -p $PWD/dist && cp -r src/* dist/\"\n  }\n}\n"
  },
  {
    "path": "04_image/demo/buildkit/src/index.js",
    "content": "console.log(1);\n"
  },
  {
    "path": "04_image/demo/multi-arch/Dockerfile",
    "content": "FROM --platform=$TARGETPLATFORM alpine\n\nRUN uname -a > /os.txt\n\nCMD cat /os.txt\n"
  },
  {
    "path": "04_image/demo/multistage-builds/.gitignore",
    "content": "app\n"
  },
  {
    "path": "04_image/demo/multistage-builds/Dockerfile",
    "content": "FROM golang:alpine as builder\n\nRUN apk --no-cache add git\n\nWORKDIR /go/src/github.com/go/helloworld/\n\nRUN go get -d -v github.com/go-sql-driver/mysql\n\nCOPY app.go .\n\nRUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .\n\nFROM alpine:latest as prod\n\nRUN apk --no-cache add ca-certificates\n\nWORKDIR /root/\n\nCOPY --from=0 /go/src/github.com/go/helloworld/app .\n\nCMD [\"./app\"]\n"
  },
  {
    "path": "04_image/demo/multistage-builds/Dockerfile.build",
    "content": "FROM golang:alpine\n\nRUN apk --no-cache add git\n\nWORKDIR /go/src/github.com/go/helloworld\n\nCOPY app.go .\n\nRUN go get -d -v github.com/go-sql-driver/mysql \\\n  && CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .\n"
  },
  {
    "path": "04_image/demo/multistage-builds/Dockerfile.copy",
    "content": "FROM alpine:latest\n\nRUN apk --no-cache add ca-certificates\n\nWORKDIR /root/\n\nCOPY app .\n\nCMD [\"./app\"]\n"
  },
  {
    "path": "04_image/demo/multistage-builds/Dockerfile.one",
    "content": "FROM golang:alpine\n\nRUN apk --no-cache add git ca-certificates\n\nWORKDIR /go/src/github.com/go/helloworld/\n\nCOPY app.go .\n\nRUN go get -d -v github.com/go-sql-driver/mysql \\\n  && CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app . \\\n  && cp /go/src/github.com/go/helloworld/app /root\n\nWORKDIR /root/\n\nCMD [\"./app\"]\n"
  },
  {
    "path": "04_image/demo/multistage-builds/app.go",
    "content": "package main\n\nimport \"fmt\"\n\nfunc main(){\n    fmt.Printf(\"Hello World!\");\n}\n"
  },
  {
    "path": "04_image/demo/multistage-builds/build.sh",
    "content": "#!/bin/sh\n\necho Building go/helloworld:build\n\ndocker build -t go/helloworld:build . -f Dockerfile.build\n\ndocker create --name extract go/helloworld:build\ndocker cp extract:/go/src/github.com/go/helloworld/app ./app\ndocker rm -f extract\n\necho Building go/helloworld:2\n\ndocker build --no-cache -t go/helloworld:2 . -f Dockerfile.copy\nrm ./app\n"
  },
  {
    "path": "04_image/summary.md",
    "content": "## 本章小结\n\n本章介绍了 Docker 镜像的获取、列出、删除以及构建方式。\n\n| 操作 | 命令 |\n|------|------|\n| 拉取镜像 | `docker pull 镜像名:标签` |\n| 拉取所有标签 | `docker pull -a 镜像名` |\n| 指定平台 | `docker pull --platform linux/amd64 镜像名` |\n| 用摘要拉取 | `docker pull 镜像名@sha256:...` |\n| 列出所有镜像 | `docker images` |\n| 按仓库名过滤 | `docker images nginx` |\n| 列出虚悬镜像 | `docker images -f dangling=true` |\n| 只输出 ID | `docker images -q` |\n| 显示摘要 | `docker images --digests` |\n| 自定义格式 | `docker images --format \"...\"` |\n| 查看空间占用 | `docker system df` |\n| 删除指定镜像 | `docker rmi 镜像名:标签` |\n| 强制删除 | `docker rmi -f 镜像名` |\n| 删除虚悬镜像 | `docker image prune` |\n| 删除未使用镜像 | `docker image prune -a` |\n| 批量删除 | `docker rmi $(docker images -q -f ...)` |\n\n### 延伸阅读\n\n- [获取镜像](4.1_pull.md)：从 Registry 拉取镜像\n- [列出镜像](4.2_list.md)：查看和过滤镜像\n- [删除镜像](4.3_rm.md)：清理本地镜像\n- [镜像加速器](../03_install/3.9_mirror.md)：加速镜像下载\n- [Docker Hub](../06_repository/6.1_dockerhub.md)：官方镜像仓库\n- [镜像](../02_basic_concept/2.1_image.md)：理解镜像概念\n- [删除容器](../05_container/5.6_rm.md)：清理容器\n- [数据卷](../08_data/8.1_volume.md)：清理数据卷\n---\n\n> 📝 **发现错误或有改进建议？** 欢迎提交 [Issue](https://github.com/yeasy/docker_practice/issues) 或 [PR](https://github.com/yeasy/docker_practice/pulls)。\n"
  },
  {
    "path": "05_container/5.1_run.md",
    "content": "## 5.1 启动\n\n本节将详细介绍 Docker 容器的启动方式，包括新建启动和重新启动已停止的容器。\n\n### 5.1.1 启动方式概述\n\n启动容器有两种方式：\n\n- **新建并启动**：基于镜像创建新容器\n- **重新启动**：将已终止的容器重新运行\n\n由于 Docker 容器非常轻量，实际使用中常常是随时删除和新建容器，而不是反复重启同一个容器。\n\n### 5.1.2 新建并启动\n\n#### 基本语法\n\n```bash\ndocker run [选项] 镜像 [命令] [参数...]\n```\n\n#### 最简单的例子\n\n输出 “Hello World” 后容器自动终止：\n\n```bash\n$ docker run ubuntu:24.04 /bin/echo 'Hello world'\nHello world\n```\n\n这与直接执行 `/bin/echo 'Hello world'` 几乎没有区别，但实际上已经启动了一个完整的 Ubuntu 容器来执行这条命令。\n\n#### 交互式容器\n\n启动一个可以交互的 bash 终端：\n\n```bash\n$ docker run -it ubuntu:24.04 /bin/bash\nroot@af8bae53bdd3:/#\n```\n\n**参数说明**：\n\n| 参数 | 作用 |\n|------|------|\n| `-i` | 保持标准输入 (stdin) 打开，允许输入 |\n| `-t` | 分配伪终端 (pseudo-TTY)，提供终端界面 |\n| `-it` | 两者组合使用，获得交互式终端 |\n\n在交互模式下可以执行命令：\n\n```bash\nroot@af8bae53bdd3:/# pwd\n/\nroot@af8bae53bdd3:/# ls\nbin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var\nroot@af8bae53bdd3:/# exit  # 退出容器\n```\n\n### 5.1.3 docker run 的完整流程\n\n执行 `docker run` 时，Docker 在后台完成以下操作：\n\n```mermaid\nflowchart TD\n    Cmd[\"docker run ubuntu:24.04 /bin/echo 'Hello'\"] --> Step1\n    \n    Step1{\"1. 检查本地是否有 ubuntu:24.04 镜像\"}\n    Step1 -- 有 --> Step1_Yes[\"使用本地镜像\"]\n    Step1 -- 无 --> Step1_No[\"从 Registry 下载\"]\n    \n    Step1_Yes --> Step2\n    Step1_No --> Step2\n    \n    Step2[\"2. 创建容器<br/>• 基于镜像的只读层<br/>• 添加一层可读写层（容器存储层）\"] --> Step3\n    Step3[\"3. 配置网络<br/>• 创建虚拟网卡<br/>• 分配 IP 地址<br/>• 连接到 Docker 网桥\"] --> Step4\n    Step4[\"4. 启动容器，执行指定命令\"] --> Step5\n    Step5[\"5. 命令执行完毕，容器停止\"]\n```\n\n### 5.1.4 常用启动选项\n\n#### 基础选项\n\n| 选项 | 说明 | 示例 |\n|------|------|------|\n| `-d` | 后台运行 (detach)| `docker run -d nginx` |\n| `-it` | 交互式终端 | `docker run -it ubuntu bash` |\n| `--name` | 指定容器名称 | `docker run --name myapp nginx` |\n| `--rm` | 退出后自动删除容器 | `docker run --rm ubuntu echo hi` |\n\n#### 端口映射\n\n```bash\n## 将容器的 80 端口映射到宿主机的 8080 端口\n\n$ docker run -d -p 8080:80 nginx\n\n## 随机映射端口\n\n$ docker run -d -P nginx\n\n## 只绑定到 localhost\n\n$ docker run -d -p 127.0.0.1:8080:80 nginx\n```\n\n#### 数据卷挂载\n\n```bash\n## 挂载命名卷\n\n$ docker run -v mydata:/data nginx\n\n## 挂载宿主机目录\n\n$ docker run -v /host/path:/container/path nginx\n\n## 只读挂载\n\n$ docker run -v /host/path:/container/path:ro nginx\n```\n\n#### 环境变量\n\n```bash\n## 设置单个环境变量\n\n$ docker run -e MYSQL_ROOT_PASSWORD=secret mysql\n\n## 从文件加载环境变量\n\n$ docker run --env-file .env myapp\n```\n\n#### 资源限制\n\n```bash\n## 限制内存\n\n$ docker run -m 512m nginx\n\n## 限制 CPU\n\n$ docker run --cpus=1.5 nginx\n```\n\n### 5.1.5 启动已终止容器\n\n使用 `docker start` 重新启动已停止的容器：\n\n```bash\n## 查看所有容器（包括已停止的）\n\n$ docker ps -a\nCONTAINER ID  IMAGE   STATUS                     NAMES\naf8bae53bdd3  ubuntu  Exited (0) 2 minutes ago   myubuntu\n\n## 重新启动\n\n$ docker start myubuntu\n\n## 启动并附加终端\n\n$ docker start -ai myubuntu\n```\n\n### 5.1.6 容器内进程的特点\n\n容器内只运行指定的应用程序及其必需资源：\n\n```bash\nroot@ba267838cc1b:/# ps\n  PID TTY          TIME CMD\n    1 ?        00:00:00 bash\n   11 ?        00:00:00 ps\n```\n\n可见容器中仅运行了 `bash` 进程。这种特点使得 Docker 对资源的利用率极高。\n\n> 💡 笔者提示：容器内的 PID 1 进程很重要——它是容器的主进程，该进程退出则容器停止。详见[后台运行](5.2_daemon.md)章节。\n\n### 5.1.7 常见问题\n\n#### Q：容器启动后立即退出\n\n**原因**：主进程执行完毕或无法保持运行\n\n```bash\n## 这个容器会立即退出（echo 执行完就结束了）\n\n$ docker run ubuntu echo \"hello\"\n\n## 解决：使用能持续运行的命令\n\n$ docker run -d nginx  # nginx 是持续运行的服务\n```\n\n详细解释见[后台运行](5.2_daemon.md)。\n\n#### Q：无法连接容器内的服务\n\n**原因**：未正确映射端口\n\n```bash\n## 错误：没有 -p 参数，外部无法访问\n\n$ docker run -d nginx\n\n## 正确：映射端口\n\n$ docker run -d -p 80:80 nginx\n```\n\n#### Q：容器内修改的文件丢失\n\n**原因**：未使用数据卷，数据保存在容器存储层\n\n```bash\n## 使用数据卷持久化\n\n$ docker run -v mydata:/app/data myapp\n```\n\n详见[数据管理](../08_data/README.md)。\n"
  },
  {
    "path": "05_container/5.2_daemon.md",
    "content": "## 5.2 守护态运行\n\n在生产环境中，我们通常需要容器持续运行，不受终端关闭的影响。本节将深入讲解如何让容器在后台运行，以及理解容器生命周期的核心概念。\n\n### 5.2.1 核心概念：前台 vs 后台\n\n当你在终端运行一个程序时，有两种模式：\n\n- **前台运行**：程序占用当前终端，输出直接显示，关闭终端程序就停止\n- **后台运行**：程序在后台执行，不占用终端，终端关闭也不影响程序\n\nDocker 容器默认是 **前台运行** 的。使用 `-d` (detach) 参数可以让容器在后台运行。\n\n### 5.2.2 基本使用\n\n#### 前台运行：默认\n\n```bash\n$ docker run ubuntu:24.04 /bin/sh -c \"while true; do echo hello world; sleep 1; done\"\nhello world\nhello world\nhello world\nhello world\n```\n\n容器会把输出的结果 (STDOUT) 打印到宿主机上面。此时：\n\n- 终端被占用，无法执行其他命令\n- 按 `Ctrl+C` 会终止容器\n- 关闭终端窗口，容器也会停止\n\n#### 后台运行：使用 -d 参数\n\n```bash\n$ docker run -d ubuntu:24.04 /bin/sh -c \"while true; do echo hello world; sleep 1; done\"\n77b2dc01fe0f3f1265df143181e7b9af5e05279a884f4776ee75350ea9d8017a\n```\n\n使用 `-d` 参数后：\n\n- 容器在后台运行\n- 返回容器的完整 ID\n- 终端立即释放，可以继续执行其他命令\n- 输出不会直接显示 (需要用 `docker logs` 查看)\n\n### 5.2.3 深入理解：容器为什么会 “立即退出”？\n\n> **这是初学者最常遇到的困惑。** 理解这个问题，你就理解了 Docker 的核心设计理念。\n\n很多人尝试这样启动容器：\n\n```bash\n$ docker run -d ubuntu:24.04\n```\n\n然后用 `docker ps` 查看，发现容器根本不在运行！这是为什么？\n\n#### 核心原理：容器的生命周期与主进程绑定\n\n```mermaid\nflowchart TD\n    subgraph Lifecycle [\"Docker 容器的生命周期 = 容器内 PID 1 进程的生命周期\"]\n        direction LR\n        Start[\"主进程启动\"] --> Run[\"容器运行\"]\n        Exit[\"主进程退出\"] --> Stop[\"容器停止\"]\n    end\n```\n\n当你运行 `docker run -d ubuntu:24.04` 时：\n\n1. 容器启动\n2. 没有指定命令，默认执行 `/bin/bash`\n3. 但没有交互式终端 (没有 `-it` 参数)，bash 发现没有输入源\n4. bash 立即退出\n5. 主进程退出，容器停止\n\n**关键理解**：\n\n- ❌ `-d` 参数 **不是** 让容器 “一直运行”\n- ✅ `-d` 参数是让容器 “在后台运行”，能运行多久取决于主进程\n\n#### 常见的 “立即退出” 场景\n\n| 场景 | 原因 | 解决方案 |\n|------|------|---------|\n| `docker run -d ubuntu` | 默认 bash 无输入立即退出 | 指定长期运行的命令 |\n| `docker run -d nginx` 后改了配置 | 配置错误导致 nginx 启动失败 | 查看 `docker logs` |\n| 自定义镜像容器启动即退 | Dockerfile 的 CMD 执行完毕 | 确保 CMD 是前台进程 |\n\n### 5.2.4 查看后台容器\n\n#### 查看运行中的容器\n\n```bash\n$ docker container ls\nCONTAINER ID  IMAGE         COMMAND               CREATED        STATUS       PORTS NAMES\n77b2dc01fe0f  ubuntu:24.04  /bin/sh -c 'while tr  2 minutes ago  Up 1 minute        agitated_wright\n```\n\n#### 查看容器输出日志\n\n```bash\n$ docker container logs 77b2dc01fe0f\nhello world\nhello world\nhello world\n...\n```\n\n**实时查看日志** (类似 `tail -f`)：\n\n```bash\n$ docker container logs -f 77b2dc01fe0f\n```\n\n#### 查看已停止的容器\n\n```bash\n$ docker container ls -a\n```\n\n加上 `-a` 参数可以看到所有容器，包括已停止的。这对于调试 “容器启动即退出” 的问题非常有用。\n\n### 5.2.5 最佳实践\n\n#### 1. 长期运行的服务使用 -d\n\n```bash\n## Web 服务器\n\n$ docker run -d -p 80:80 nginx\n\n## 数据库\n\n$ docker run -d -p 3306:3306 mysql:8\n\n## 缓存服务\n\n$ docker run -d -p 6379:6379 redis\n```\n\n#### 2. 调试时先用前台模式\n\n当容器启动有问题时，**去掉 `-d` 参数** 可以直接看到输出和错误：\n\n```bash\n## 有问题的容器，先前台运行看看发生了什么\n\n$ docker run myimage:latest\n```\n\n#### 3. 使用 --rm 自动清理\n\n对于一次性任务，使用 `--rm` 参数让容器退出后自动删除：\n\n```bash\n$ docker run --rm ubuntu:24.04 echo \"Hello, World!\"\nHello, World!\n## 容器执行完后自动删除\n\n...\n```\n\n#### 4. 配合日志查看\n\n```bash\n## 后台启动\n\n$ docker run -d --name myapp myimage:latest\n\n## 查看最近 100 行日志\n\n$ docker logs --tail 100 myapp\n\n## 实时跟踪日志\n\n$ docker logs -f myapp\n\n## 查看带时间戳的日志\n\n$ docker logs -t myapp\n```\n\n### 5.2.6 常见问题排查\n\n#### Q：容器启动后立即退出\n\n1. **查看退出状态码**：\n   ```bash\n   $ docker ps -a --filter \"name=mycontainer\"\n   # 查看 STATUS 列，如 “Exited (1)” 表示异常退出\n\n   ```\n\n2. **查看容器日志**：\n   ```bash\n   $ docker logs mycontainer\n   ```\n\n3. **以交互模式调试**：\n   ```bash\n   $ docker run -it myimage:latest /bin/sh\n   # 进入容器手动执行命令，查找问题\n\n   ```\n\n#### Q：容器在后台运行但无法访问服务\n\n1. **检查端口映射**：\n   ```bash\n   $ docker port mycontainer\n   ```\n\n2. **检查容器内服务状态**：\n   ```bash\n   $ docker exec mycontainer ps aux\n   ```\n\n#### Q：如何让已经在后台运行的容器回到前台？\n\n使用 `docker attach`：\n\n```bash\n$ docker attach mycontainer\n```\n\n> **注意**：`attach` 会连接到容器的主进程。如果主进程不是交互式的，你可能只能看到输出。使用 `Ctrl+P` `Ctrl+Q` 可以安全退出而不停止容器。\n\n### 5.2.7 延伸阅读\n\n- [进入容器](5.4_attach_exec.md)：如何进入正在运行的容器执行命令\n- [容器日志](../appendix/best_practices.md)：生产环境的日志管理最佳实践\n- [HEALTHCHECK 健康检查](../07_dockerfile/7.12_healthcheck.md)：自动检测容器内服务是否正常\n- [Docker Compose](../11_compose/README.md)：管理多个后台容器的更好方式\n"
  },
  {
    "path": "05_container/5.3_stop.md",
    "content": "## 5.3 终止\n\n本节将介绍如何终止一个运行中的容器，以及几种不同的终止方式及其区别。\n\n### 5.3.1 终止方式概述\n\n终止容器有三种方式：\n\n| 方式 | 命令 | 说明 |\n|------|------|------|\n| **优雅停止** | `docker stop` | 先发 SIGTERM，超时后发 SIGKILL |\n| **强制停止** | `docker kill` | 直接发 SIGKILL |\n| **自动终止** | - | 容器主进程退出时自动停止 |\n\n---\n\n### 5.3.2 docker stop：推荐\n\n#### docker stop 基本用法\n\n```bash\n$ docker stop 容器名或ID\n```\n\n#### 工作原理\n\n```mermaid\nflowchart TD\n    cmd[\"docker stop mycontainer\"] --> A[\"1. 发送 SIGTERM 信号给容器主进程 (PID 1)\"]\n    A --> B[\"2. 等待容器优雅退出 (默认 10 秒)\"]\n    B --> C[\"3. 如果超时仍未退出，发送 SIGKILL 强制终止\"]\n```\n\n#### 自定义超时时间\n\n```bash\n## 等待 30 秒后强制终止\n\n$ docker stop -t 30 mycontainer\n\n## 立即发送 SIGKILL（相当于 docker kill）\n\n$ docker stop -t 0 mycontainer\n```\n\n#### 停止多个容器\n\n```bash\n## 停止多个指定容器\n\n$ docker stop container1 container2 container3\n\n## 停止所有运行中的容器\n\n$ docker stop $(docker ps -q)\n```\n\n---\n\n### 5.3.3 docker kill\n\n#### 基本用法\n\n```bash\n$ docker kill 容器名或ID\n```\n\n#### 与 stop 的区别\n\n| 命令 | 信号 | 使用场景 |\n|------|------|---------|\n| `docker stop` | SIGTERM → SIGKILL | 正常停止，让应用优雅退出 |\n| `docker kill` | SIGKILL | 应用无响应，强制终止 |\n\n#### 发送自定义信号\n\n```bash\n## 发送 SIGHUP（让进程重新加载配置）\n\n$ docker kill -s HUP mycontainer\n\n## 发送 SIGTERM\n\n$ docker kill -s TERM mycontainer\n```\n\n---\n\n### 5.3.4 容器自动终止\n\n容器的生命周期与主进程绑定。主进程退出时，容器自动停止：\n\n```bash\n## 主进程是交互式 bash\n\n$ docker run -it ubuntu bash\nroot@abc123:/# exit    # 退出 bash → 容器停止\n\n## 主进程执行完毕\n\n$ docker run ubuntu echo \"Hello\"    # echo 执行完 → 容器停止\n```\n\n---\n\n### 5.3.5 查看已停止的容器\n\n```bash\n$ docker ps -a\nCONTAINER ID   IMAGE    COMMAND       STATUS                      NAMES\nba267838cc1b   ubuntu   \"/bin/bash\"   Exited (0) 2 minutes ago    myubuntu\nc5d3a5e8f7b2   nginx    \"nginx\"       Up 5 minutes                mynginx\n```\n\n**STATUS 字段说明**：\n\n| 状态 | 说明 |\n|------|------|\n| `Up X minutes` | 运行中 |\n| `Exited (0)` | 正常退出 (退出码 0)|\n| `Exited (1)` | 异常退出 (非零退出码)|\n| `Exited (137)` | 被 SIGKILL 终止 (128 + 9)|\n| `Exited (143)` | 被 SIGTERM 终止 (128 + 15)|\n\n---\n\n### 5.3.6 重新启动容器\n\n#### 启动已停止的容器\n\n```bash\n$ docker start 容器名或ID\n\n## 启动并附加终端\n\n$ docker start -ai 容器名\n```\n\n#### 重启运行中的容器\n\n```bash\n## 先停止再启动\n\n$ docker restart 容器名\n\n## 自定义停止超时\n\n$ docker restart -t 30 容器名\n```\n\n---\n\n### 5.3.7 生命周期状态图\n\n```mermaid\nstateDiagram-v2\n    direction TB\n    [*] --> Created : docker create\n    Created --> Running : docker start\n    Running --> Stopped : docker stop\n    Running --> Paused : docker pause\n    Paused --> Running : docker unpause\n    \n    Created --> Deleted : docker rm\n    Stopped --> Deleted : docker rm\n    Paused --> Deleted : docker rm\n    \n    Deleted --> [*]\n```\n\n---\n\n### 5.3.8 批量操作\n\n#### 停止所有容器\n\n```bash\n$ docker stop $(docker ps -q)\n```\n\n#### 删除所有已停止的容器\n\n```bash\n$ docker container prune\n```\n\n#### 停止并删除所有容器\n\n```bash\n$ docker stop $(docker ps -q) && docker container prune -f\n```\n\n---\n\n### 5.3.9 常见问题\n\n#### Q：容器停止很慢\n\n原因：应用没有正确处理 SIGTERM 信号，需要等待超时后强制终止。\n\n解决方案：\n\n1. 在应用中正确处理 SIGTERM\n2. 使用 `docker stop -t 0` 立即终止\n3. 检查 Dockerfile 中的 `STOPSIGNAL` 配置\n\n#### Q：如何让容器优雅退出\n\n确保容器主进程正确处理信号：\n\n```dockerfile\n## Dockerfile 示例\n\nFROM node:22\n...\n## 使用 exec 形式确保信号能传递给 node 进程\n\nCMD [\"node\", \"server.js\"]\n```\n\n#### Q：容器无法停止\n\n```bash\n## 强制终止\n\n$ docker kill 容器名\n\n## 如果仍无法停止，检查系统资源\n\n$ docker inspect 容器名\n```\n\n---\n"
  },
  {
    "path": "05_container/5.4_attach_exec.md",
    "content": "## 5.4 进入容器\n\n### 5.4.1 为什么需要进入容器\n\n使用 `-d` 参数启动容器后，容器在后台运行。以下场景需要进入容器内部操作：\n\n| 场景 | 示例 |\n|------|------|\n| **调试问题** | 查看日志、检查配置、排查错误 |\n| **临时操作** | 执行数据库迁移、清理缓存 |\n| **检查状态** | 查看进程、网络连接、文件系统 |\n| **开发测试** | 交互式测试命令、验证环境 |\n\n### 5.4.2 两种进入方式\n\nDocker 提供两种进入容器的命令：\n\n| 命令 | 推荐程度 | 特点 |\n|------|---------|------|\n| `docker exec` | ✅ **推荐** | 启动新进程，退出不影响容器 |\n| `docker attach` | ⚠️ 谨慎使用 | 附加到主进程，退出可能停止容器 |\n\n---\n\n### 5.4.3 docker exec：推荐\n\n#### docker exec 基本用法\n\n```bash\n## 进入容器并启动交互式 shell\n\n$ docker exec -it 容器名 /bin/bash\n\n## 或使用 sh（适用于 Alpine 等精简镜像）\n\n$ docker exec -it 容器名 /bin/sh\n```\n\n#### 参数说明\n\n| 参数 | 作用 |\n|------|------|\n| `-i` | 保持标准输入打开 (interactive)|\n| `-t` | 分配伪终端 (TTY)|\n| `-it` | 两者组合，获得完整交互体验 |\n| `-u` | 指定用户 (如 `-u root`)|\n| `-w` | 指定工作目录 |\n| `-e` | 设置环境变量 |\n\n#### docker exec 示例\n\n```bash\n## 启动一个后台容器\n\n$ docker run -dit --name myubuntu ubuntu\n69d137adef7a...\n\n## 进入容器（交互式 shell）\n\n$ docker exec -it myubuntu bash\nroot@69d137adef7a:/# ls\nbin  boot  dev  etc  home  lib  ...\nroot@69d137adef7a:/# exit\n\n## 容器仍在运行！\n\n$ docker ps\nCONTAINER ID   IMAGE    STATUS         NAMES\n69d137adef7a   ubuntu   Up 2 minutes   myubuntu\n```\n\n#### 执行单条命令\n\n不进入交互模式，直接执行命令：\n\n```bash\n## 查看容器内进程\n\n$ docker exec myubuntu ps aux\n\n## 查看配置文件\n\n$ docker exec myubuntu cat /etc/nginx/nginx.conf\n\n## 以 root 用户执行\n\n$ docker exec -u root myubuntu apt update\n```\n\n#### 只用 -i 不用 -t 的区别\n\n```bash\n## 只用 -i：可以执行命令，但没有提示符\n\n$ docker exec -i myubuntu bash\nls           # 输入命令\nbin          # 输出结果\nboot\ndev\n...\n\n## 用 -it：有完整的终端体验\n\n$ docker exec -it myubuntu bash\nroot@69d137adef7a:/#    # 有提示符\n```\n\n> 💡 通常使用 `-it` 组合。只有在脚本中需要通过管道传入命令时才只用 `-i`。\n\n---\n\n### 5.4.4 docker attach：谨慎使用\n\n#### docker attach 基本用法\n\n```bash\n$ docker attach 容器名\n```\n\n#### 工作原理\n\n`attach` 会附加到容器的 **主进程** (PID 1) 的标准输入输出：\n\n```mermaid\nflowchart LR\n    subgraph Container [\"容器\"]\n        direction TB\n        subgraph Process [\"主进程\"]\n            P1[\"PID 1: /bin/bash<br>(你的输入直接发送到主进程)\"]\n        end\n    end\n    Attach[\"docker attach\"] -->|\"附加到这里\"| P1\n```\n\n#### docker attach 示例\n\n```bash\n## 启动容器\n\n$ docker run -dit --name myubuntu ubuntu\n243c32535da7...\n\n## 附加到容器\n\n$ docker attach myubuntu\nroot@243c32535da7:/#\n```\n\n#### ⚠️ 重要警告\n\n**从 attach 会话中输入 `exit` 或按 `Ctrl+D` 会导致容器停止！**\n\n```bash\n$ docker attach myubuntu\nroot@243c32535da7:/# exit    # 这会停止容器！\n\n$ docker ps\nCONTAINER ID   IMAGE    STATUS                     NAMES\n243c32535da7   ubuntu   Exited (0) 2 seconds ago   myubuntu\n```\n\n**原因**：attach 附加到主进程，退出主进程就等于退出容器。\n\n#### 安全退出 attach\n\n使用 `Ctrl+P` 然后 `Ctrl+Q` 可以从 attach 会话中 **分离**，而不停止容器：\n\n```bash\n$ docker attach myubuntu\nroot@243c32535da7:/# \n## 按 Ctrl+P 然后 Ctrl+Q\n\nread escape sequence\n\n$ docker ps    # 容器仍在运行\nCONTAINER ID   IMAGE    STATUS         NAMES\n243c32535da7   ubuntu   Up 5 minutes   myubuntu\n```\n\n---\n\n### 5.4.5 exec vs attach 对比\n\n| 特性 | docker exec | docker attach |\n|------|-------------|---------------|\n| **工作方式** | 在容器内启动新进程 | 附加到主进程 |\n| **退出影响** | 不影响容器 | 可能停止容器 |\n| **多终端** | 可以开多个 | 共享同一个会话 |\n| **适用场景** | 调试、临时操作 | 查看主进程输出 |\n| **推荐程度** | ✅ 推荐 | ⚠️ 特殊场景使用 |\n\n```mermaid\nflowchart LR\n    subgraph Exec [\"docker exec\"]\n        direction TB\n        subgraph Container1 [\"容器\"]\n            E_PID1[\"PID 1: nginx\"]\n            E_PID50[\"PID 50: bash\"]\n        end\n        NewProc[\"新进程\"] -- 附加到 --> E_PID50\n    end\n    \n    subgraph Attach [\"docker attach\"]\n        direction TB\n        subgraph Container2 [\"容器\"]\n            A_PID1[\"PID 1: bash\"]\n        end\n        MainProc[\"附加到主进程\"] --> A_PID1\n    end\n    \n    note1[\"退出 bash 不影响 nginx\"]\n    note2[\"退出 bash 容器停止\"]\n    Container1 -.-> note1\n    Container2 -.-> note2\n```\n\n---\n\n### 5.4.6 最佳实践\n\n#### 1. 首选 docker exec\n\n```bash\n## 进入容器调试\n\n$ docker exec -it myapp bash\n\n## 查看日志\n\n$ docker exec myapp tail -f /var/log/app.log\n\n## 执行数据库迁移\n\n$ docker exec myapp python manage.py migrate\n```\n\n#### 2. 生产环境避免进入容器\n\n笔者建议：生产环境应尽量避免进入容器直接操作，而是通过：\n\n- 日志系统查看日志 (如 `docker logs` 或集中式日志)\n- 监控系统查看状态\n- 重新部署而非手动修改\n\n#### 3. 无 shell 镜像的处理\n\n某些精简镜像 (如基于 `scratch` 或 `distroless`) 没有 shell：\n\n```bash\n## 这会失败\n\n$ docker exec -it myapp bash\nOCI runtime exec failed: exec failed: unable to start container process: exec: \"bash\": executable file not found\n\n## 解决方案：使用调试容器\n\n$ docker debug myapp\n```\n\n---\n\n### 5.4.7 常见问题\n\n#### Q：exec 进入后看不到其他终端的操作\n\n这是正常的。exec 启动的是独立进程，多个 exec 会话互不影响。\n\n#### Q：容器没有 bash\n\n尝试使用 sh：\n\n```bash\n$ docker exec -it myapp /bin/sh\n```\n\n#### Q：需要 root 权限\n\n```bash\n$ docker exec -u root -it myapp bash\n```\n\n---\n"
  },
  {
    "path": "05_container/5.5_import_export.md",
    "content": "## 5.5 导出和导入\n\n当我们需要迁移容器或者备份容器时，可以使用 Docker 的导入和导出功能。本节将介绍这两个命令的使用方法。\n\n### 5.5.1 导出容器\n\n如果要导出本地某个容器，可以使用 `docker export` 命令。\n```bash\n$ docker container ls -a\nCONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                    PORTS               NAMES\n7691a814370e        ubuntu:24.04        \"/bin/bash\"         36 hours ago        Exited (0) 21 hours ago                       test\n$ docker export 7691a814370e > ubuntu.tar\n```\n\n这样将导出容器快照到本地文件。\n\n### 5.5.2 导入容器快照\n\n可以使用 `docker import` 从容器快照文件中再导入为镜像，例如\n\n```bash\n$ cat ubuntu.tar | docker import - test/ubuntu:v1.0\n$ docker image ls\nREPOSITORY          TAG                 IMAGE ID            CREATED              VIRTUAL SIZE\ntest/ubuntu         v1.0                9d37a6082e97        About a minute ago   171.3 MB\n```\n\n此外，也可以通过指定 URL 或者某个目录来导入，例如\n\n```bash\n$ docker import http://example.com/exampleimage.tgz example/imagerepo\n```\n\n*注：用户既可以使用 `docker load` 来导入镜像存储文件到本地镜像库，也可以使用 `docker import` 来导入一个容器快照到本地镜像库。这两者的区别在于容器快照文件将丢弃所有的历史记录和元数据信息 (即仅保存容器当时的快照状态)，而镜像存储文件将保存完整记录，体积也要大。此外，从容器快照文件导入时可以重新指定标签等元数据信息。*\n"
  },
  {
    "path": "05_container/5.6_rm.md",
    "content": "## 5.6 删除\n\n随着容器的创建和停止，系统中会积累大量的容器。本节将介绍如何删除不再需要的容器，以及如何清理所有停止的容器。\n\n### 5.6.1 基本用法\n\n使用 `docker rm` 删除已停止的容器：\n\n```bash\n$ docker rm 容器名或ID\n```\n\n> 💡 `docker rm` 是 `docker container rm` 的简写，两者等效。\n\n---\n\n### 5.6.2 删除选项\n\n| 选项 | 说明 | 示例 |\n|------|------|------|\n| 无参数 | 删除已停止的容器 | `docker rm mycontainer` |\n| `-f` | 强制删除运行中的容器 | `docker rm -f mycontainer` |\n| `-v` | 同时删除关联的匿名卷 | `docker rm -v mycontainer` |\n\n#### 删除已停止的容器\n\n```bash\n$ docker rm mycontainer\nmycontainer\n```\n\n#### 强制删除运行中的容器\n\n```bash\n## 不加 -f 会报错\n\n$ docker rm running_container\nError: cannot remove running container\n\n## 加 -f 强制删除\n\n$ docker rm -f running_container\nrunning_container\n```\n\n> ⚠️ 强制删除会向容器发送 SIGKILL 信号，可能导致数据丢失。建议先 `docker stop` 优雅停止。\n\n#### 删除容器及其数据卷\n\n```bash\n## 删除容器时同时删除其匿名卷\n\n$ docker rm -v mycontainer\n```\n\n> 注意：只删除匿名卷，命名卷不会被删除。\n\n---\n\n### 5.6.3 批量删除\n\n#### 删除所有已停止的容器\n\n```bash\n## 方式一：使用 prune 命令（推荐）\n\n$ docker container prune\n\nWARNING! This will remove all stopped containers.\nAre you sure you want to continue? [y/N] y\nDeleted Containers:\nabc123...\ndef456...\nTotal reclaimed space: 150MB\n\n## 方式二：不提示确认\n\n$ docker container prune -f\n```\n\n#### 删除所有容器：包括运行中的\n\n```bash\n## 先停止所有容器，再删除\n\n$ docker stop $(docker ps -q)\n$ docker rm $(docker ps -aq)\n\n## 或者直接强制删除\n\n$ docker rm -f $(docker ps -aq)\n```\n\n#### 按条件删除\n\n```bash\n## 删除所有已退出的容器\n\n$ docker rm $(docker ps -aq -f status=exited)\n\n## 删除名称包含 \"test\" 的容器\n\n$ docker rm $(docker ps -aq -f name=test)\n\n## 删除 24 小时前创建的容器\n\n$ docker container prune --filter \"until=24h\"\n```\n\n---\n\n### 5.6.4 常用过滤条件\n\n`docker ps` 的过滤条件可以配合 `rm` 使用：\n\n| 过滤条件 | 说明 | 示例 |\n|---------|------|------|\n| `status=exited` | 已退出的容器 | `-f status=exited` |\n| `status=created` | 已创建未启动 | `-f status=created` |\n| `name=xxx` | 名称匹配 | `-f name=myapp` |\n| `ancestor=xxx` | 基于某镜像创建 | `-f ancestor=nginx` |\n| `before=xxx` | 在某容器之前创建 | `-f before=mycontainer` |\n| `since=xxx` | 在某容器之后创建 | `-f since=mycontainer` |\n\n#### 示例\n\n```bash\n## 删除所有基于 nginx 镜像的容器\n\n$ docker rm $(docker ps -aq -f ancestor=nginx)\n\n## 删除所有创建后未启动的容器\n\n$ docker rm $(docker ps -aq -f status=created)\n```\n\n---\n\n### 5.6.5 容器与镜像的依赖关系\n\n> 有容器依赖的镜像无法删除。\n\n```bash\n## 尝试删除有容器依赖的镜像\n\n$ docker image rm nginx\nError: image is being used by stopped container abc123\n\n## 需要先删除依赖该镜像的容器\n\n$ docker rm abc123\n$ docker image rm nginx\n```\n\n---\n\n### 5.6.6 清理策略建议\n\n#### 开发环境\n\n```bash\n## 定期清理已停止的容器\n\n$ docker container prune -f\n\n## 一键清理所有未使用资源\n\n$ docker system prune -f\n```\n\n#### 生产环境\n\n```bash\n## 使用 --rm 参数运行临时容器\n\n$ docker run --rm ubuntu echo \"Hello\"\n## 容器退出后自动删除\n\n## 定期清理（设置保留时间）\n\n$ docker container prune --filter \"until=168h\"  # 保留 7 天内的\n```\n\n#### 完整清理脚本\n\n```bash\n#!/bin/bash\n## cleanup.sh - Docker 资源清理脚本\n\necho \"清理已停止的容器...\"\ndocker container prune -f\n\necho \"清理未使用的镜像...\"\ndocker image prune -f\n\necho \"清理未使用的数据卷...\"\ndocker volume prune -f\n\necho \"清理未使用的网络...\"\ndocker network prune -f\n\necho \"清理完成！\"\ndocker system df\n```\n\n---\n\n### 5.6.7 常见问题\n\n#### Q：容器无法删除\n\n```bash\nError: container is running\n```\n\n解决：先停止容器，或使用 `-f` 强制删除\n\n```bash\n$ docker stop mycontainer\n$ docker rm mycontainer\n## 或\n\n$ docker rm -f mycontainer\n```\n\n#### Q：删除后磁盘空间没释放\n\n可能原因：\n\n1. 容器的数据卷未删除 (使用 `-v` 参数)\n2. 镜像未删除\n3. 构建缓存未清理\n\n解决：\n\n```bash\n## 查看空间占用\n\n$ docker system df\n\n## 完整清理\n\n$ docker system prune -a --volumes\n```\n\n---\n"
  },
  {
    "path": "05_container/README.md",
    "content": "# 第五章 操作容器\n\n容器是 Docker 又一核心概念。\n\n简单的说，容器是独立运行的一个或一组应用，以及它们的运行态环境。对应的，虚拟机可以理解为模拟运行的一整套操作系统 (提供了运行态环境和其他系统环境) 和跑在上面的应用。\n\n本章将具体介绍如何来管理一个容器，包括创建、启动和停止等。\n\n* [启动容器](5.1_run.md)\n* [守护态运行](5.2_daemon.md)\n* [终止容器](5.3_stop.md)\n* [进入容器](5.4_attach_exec.md)\n* [导出和导入容器](5.5_import_export.md)\n* [删除容器](5.6_rm.md)\n"
  },
  {
    "path": "05_container/summary.md",
    "content": "## 本章小结\n\n本章介绍了 Docker 容器的启动、停止、进入和删除等核心操作。\n\n| 操作 | 命令 | 说明 |\n|------|------|------|\n| 新建并运行 | `docker run` | 最常用的启动方式 |\n| 交互式启动 | `docker run -it` | 用于调试或临时操作 |\n| 后台运行 | `docker run -d` | 用于服务类应用 |\n| 启动已停止的容器 | `docker start` | 重用已有容器 |\n| 优雅停止 | `docker stop` | 先 SIGTERM，超时后 SIGKILL |\n| 强制停止 | `docker kill` | 直接 SIGKILL |\n| 重启 | `docker restart` | 停止后立即启动 |\n| 停止全部 | `docker stop $(docker ps -q)` | 停止所有运行中容器 |\n| 进入容器调试 | `docker exec -it 容器名 bash` | 推荐方式 |\n| 执行单条命令 | `docker exec 容器名 命令` | 不进入交互模式 |\n| 查看主进程输出 | `docker attach 容器名` | 慎用，退出可能停止容器 |\n| 删除已停止容器 | `docker rm 容器名` | 需先停止 |\n| 强制删除运行中容器 | `docker rm -f 容器名` | 直接删除 |\n| 删除容器及匿名卷 | `docker rm -v 容器名` | 同时清理匿名卷 |\n| 清理所有已停止容器 | `docker container prune` | 批量清理 |\n\n### 延伸阅读\n\n- [后台运行](5.2_daemon.md)：理解 `-d` 参数和容器生命周期\n- [进入容器](5.4_attach_exec.md)：操作运行中的容器\n- [网络配置](../09_network/README.md)：理解端口映射的原理\n- [数据管理](../08_data/README.md)：数据持久化方案\n- [删除镜像](../04_image/4.3_rm.md)：清理镜像\n- [数据卷](../08_data/8.1_volume.md)：数据卷管理\n---\n\n> 📝 **发现错误或有改进建议？** 欢迎提交 [Issue](https://github.com/yeasy/docker_practice/issues) 或 [PR](https://github.com/yeasy/docker_practice/pulls)。\n"
  },
  {
    "path": "06_repository/6.1_dockerhub.md",
    "content": "## 6.1 Docker Hub\n\n### 6.1.1 什么是 Docker Hub\n\nDocker Hub 是 Docker 的中央镜像仓库，通过它您可以轻松地分享和获取 Docker 镜像。\n\n\n[Docker Hub](https://hub.docker.com/) 是 Docker 官方维护的公共镜像仓库，也是全球最大的容器镜像库。\n\n它提供了：\n\n- **官方镜像**：由 Docker 官方和软件厂商 (如 Nginx，MySQL，Node.js) 维护的高质量镜像。\n- **个人/组织仓库**：用户可以上传自己的镜像。\n- **自动构建**：与 GitHub/Bitbucket 集成 (需付费)。\n- **Webhooks**：镜像更新时触发回调。\n\n---\n\n### 6.1.2 核心功能\n\n#### 1. 搜索镜像\n\n我们可以通过 `docker search` 命令来查找官方仓库中的镜像，并利用 `docker pull` 命令来将它下载到本地。\n\n\n除了网页搜索，也可以使用命令行：\n\n```bash\n$ docker search centos\nNAME      DESCRIPTION                      STARS     OFFICIAL\ncentos    The official build of CentOS.    7000+     [OK]\n```\n\n> **技巧**：始终优先使用 `OFFICIAL` 标记为 `[OK]` 的镜像，安全性更有保障。\n\n#### 2. 拉取镜像\n\n```bash\n$ docker pull nginx:alpine\n```\n\n#### 3. 推送镜像\n\n需要先登录：\n\n```bash\n$ docker login\n## 默认情况下，不带其它参数进行 docker login 会自动走 Device Code Web Flow (浏览器认证)\n## 若在非交互 CI 环境中，推荐结合 --username 与 --password-stdin 参数使用\n\n...\n```\n\n打标签并推送：\n\n```bash\n## 1. 标记镜像\n\n$ docker tag myapp:v1 username/myapp:v1\n\n## 2. 推送\n\n$ docker push username/myapp:v1\n```\n\n---\n\n### 6.1.3 限制与配额\n\n#### 镜像拉取限制\n\n自 2020 年 11 月起，Docker Hub 对匿名和免费用户实施了拉取速率限制：\n\n| 用户类型 | 限制 |\n|---------|------|\n| **匿名用户** (未登录) | 每 6 小时 100 次请求 |\n| **免费账户** (已登录) | 每 6 小时 200 次请求 |\n| **Pro/Team 账户** | 无限制 |\n\n#### 滥用限流\n\n除了上述针对特定账号拉取镜像数量的 Pull Rate Limit 之外，Docker Hub 对所有用户（包含已认证及付费用户）还实施了 **滥用保护限流 (Abuse Rate Limiting)**。它是根据网络出口 IP (IPv4 或 IPv6 /64 子网) 计算整体请求频率，阈值动态触发（通常为每分钟数千级别请求）。\n\n**两类的差异与排查方法**：\n- **Pull Rate Limit**：针对拉取量达到上限。报错返回 `429 Too Many Requests`，并且 HTTP 返回体/CLI 错误提示中会带有明确的 `toomanyrequests: You have reached your pull rate limit` 提示，常附有账户升级链接。\n- **Abuse Rate Limit**：防范接口频率打击。报错仅返回简化的 `429 Too Many Requests`。这一限流不分付费与否，常发生在“多终端共享出口 IP”的企业局域网或者第三方云 CI 服务（如 GitHub Actions 等）中，即使你已正常配置 `docker login` 也依旧可能触发。\n\n> **提示**：如果在 CI/CD 等环境遇到 429 错误，建议：\n> 1. 先甄别具体是哪类限流：普通的 pull rate limit 只要在 CI 中配置 `docker login` (并使用有效账号) 就能解除匿名限制。\n> 2. 如果是 Abuse 频控导致，应考虑搭建私有仓库作为拉取缓存代理 (Registry pull-through cache)，避免频繁直接请求官方 Hub。\n> 3. 使用国内镜像加速器。\n\n---\n\n### 6.1.4 安全最佳实践\n\n#### 1. 启用 2FA：双因素认证\n\n为了保护您的 Docker Hub 账号安全，我们建议采取以下措施。\n\n\n在 Account Settings -> Security 中启用 2FA，保护账号安全。启用后，CLI 登录需要使用 **Access Token** 而非密码。\n\n#### 2. 使用 Access Token\n\n> **⚠️ 警告**：绝不要在脚本或 CI/CD 系统中，直接使用 `-p` 参数传递密码或 Token (类似 `docker login -p xxx`)！这会导致凭证直接暴露在系统的命令历史、进程列表和终端输出中。\n\n1. 在 Docker Hub -> Account Settings -> Security -> Access Tokens 创建 Token (PAT)。\n2. 将 Token 通过标准输入 (stdin) 安全传递给 Docker:\n\n```bash\n$ echo \"dckr_pat_xxxxxxx\" | docker login --username username --password-stdin\n```\n\n#### 3. 关注镜像漏洞\n\nDocker Hub 会对官方镜像和付费用户的镜像进行安全扫描。在镜像标签页可以看到漏洞扫描结果。\n\n---\n\n### 6.1.5 Webhooks\n\n当镜像被推送时，可以自动触发 HTTP 回调 (例如通知 CI 系统部署)。\n\n**配置方法**：\n仓库页面 -> Webhooks -> Create Webhook。\n\n---\n\n### 6.1.6 自动构建\n\n> ⚠️ 目前仅限付费用户 (Pro/Team) 使用。\n\n链接 GitHub/Bitbucket 仓库后，当代码有提交或打标签时，Docker Hub 会自动运行构建。这保证了镜像总是与代码同步，且由可信的官方环境构建。\n\n---\n"
  },
  {
    "path": "06_repository/6.2_registry.md",
    "content": "## 6.2 私有仓库\n\n有时候使用 Docker Hub 这样的公共仓库可能不方便，用户可以创建一个本地仓库供私人使用。\n\n本节介绍如何使用本地仓库。\n\n[Docker Registry](https://docs.docker.com/registry/) 是官方提供的工具，可以用于构建私有的镜像仓库。本文内容基于 [docker/distribution](https://github.com/docker/distribution) v2.x 版本。\n\n### 6.2.1 安装运行 docker-registry\n\n#### 容器运行\n\n如果您需要搭建私有仓库，可以通过官方提供的 `registry` 镜像快速部署。\n\n你可以使用官方 `registry` 镜像来运行。\n\n```bash\n$ docker run -d -p 5000:5000 --restart=always --name registry registry\n```\n\n这将使用官方的 `registry` 镜像来启动私有仓库。默认情况下，仓库会被创建在容器的 `/var/lib/registry` 目录下。你可以通过 `-v` 参数来将镜像文件存放在本地的指定路径。例如下面的例子将上传的镜像放到本地的 `/opt/data/registry` 目录。\n\n```bash\n$ docker run -d \\\n    -p 5000:5000 \\\n    -v /opt/data/registry:/var/lib/registry \\\n    registry\n```\n\n### 6.2.2 在私有仓库上传、搜索、下载镜像\n\n创建好私有仓库之后，就可以使用 `docker tag` 来标记一个镜像，然后推送它到仓库。例如私有仓库地址为 `127.0.0.1:5000`。\n\n先在本机查看已有的镜像。\n\n```bash\n$ docker image ls\nREPOSITORY                        TAG                 IMAGE ID            CREATED             VIRTUAL SIZE\nubuntu                            latest              ba5877dc9bec        6 weeks ago         192.7 MB\n```\n\n使用 `docker tag` 将 `ubuntu:latest` 这个镜像标记为 `127.0.0.1:5000/ubuntu:latest`。\n\n格式为 `docker tag IMAGE[:TAG] [REGISTRY_HOST[:REGISTRY_PORT]/]REPOSITORY[:TAG]`。\n\n```bash\n$ docker tag ubuntu:latest 127.0.0.1:5000/ubuntu:latest\n$ docker image ls\nREPOSITORY                        TAG                 IMAGE ID            CREATED             VIRTUAL SIZE\nubuntu                            latest              ba5877dc9bec        6 weeks ago         192.7 MB\n127.0.0.1:5000/ubuntu:latest      latest              ba5877dc9bec        6 weeks ago         192.7 MB\n```\n\n使用 `docker push` 上传标记的镜像。\n\n```bash\n$ docker push 127.0.0.1:5000/ubuntu:latest\nThe push refers to repository [127.0.0.1:5000/ubuntu]\n373a30c24545: Pushed\na9148f5200b0: Pushed\ncdd3de0940ab: Pushed\nfc56279bbb33: Pushed\nb38367233d37: Pushed\n2aebd096e0e2: Pushed\nlatest: digest: sha256:fe4277621f10b5026266932ddf760f5a756d2facd505a94d2da12f4f52f71f5a size: 1568\n```\n\n用 `curl` 查看仓库中的镜像。\n\n```bash\n$ curl 127.0.0.1:5000/v2/_catalog\n{\"repositories\":[\"ubuntu\"]}\n```\n\n这里可以看到 `{\"repositories\":[\"ubuntu\"]}`，表明镜像已经被成功上传了。\n\n先删除已有镜像，再尝试从私有仓库中下载这个镜像。\n\n```bash\n$ docker image rm 127.0.0.1:5000/ubuntu:latest\n\n$ docker pull 127.0.0.1:5000/ubuntu:latest\nPulling repository 127.0.0.1:5000/ubuntu:latest\nba5877dc9bec: Download complete\n511136ea3c5a: Download complete\n9bad880da3d2: Download complete\n25f11f5fb0cb: Download complete\nebc34468f71d: Download complete\n2318d26665ef: Download complete\n\n$ docker image ls\nREPOSITORY                         TAG                 IMAGE ID            CREATED             VIRTUAL SIZE\n127.0.0.1:5000/ubuntu:latest       latest              ba5877dc9bec        6 weeks ago         192.7 MB\n```\n\n### 6.2.3 配置非 https 仓库地址\n\n如果你不想使用 `127.0.0.1:5000` 作为仓库地址，比如想让本网段的其他主机也能把镜像推送到私有仓库。你就得把例如 `192.168.199.100:5000` 这样的内网地址作为私有仓库地址，这时你会发现无法成功推送镜像。\n\n这是因为 Docker 默认不允许非 `HTTPS` 方式推送镜像。我们可以通过 Docker 的配置选项来取消这个限制，或者查看下一节配置能够通过 `HTTPS` 访问的私有仓库。\n\n#### Linux\n\n默认情况下，Docker 强制使用 HTTPS 协议推送镜像。如果您搭建的私有仓库是 HTTP 协议，需要进行如下配置。\n\n\n对于使用 `systemd` 的系统，请在 `/etc/docker/daemon.json` 中写入如下内容 (如果文件不存在请新建该文件)\n\n```json\n{\n  \"registry-mirrors\": [\n    \"https://hub-mirror.c.163.com\",\n    \"https://mirror.baidubce.com\"\n  ],\n  \"insecure-registries\": [\n    \"192.168.199.100:5000\"\n  ]\n}\n```\n\n> 注意：该文件必须符合 `json` 规范，否则 Docker 将不能启动。\n\n### 6.2.4 其他\n\n对于 Docker Desktop for Windows、Docker Desktop for Mac 在设置中的 `Docker Engine` 中进行编辑，增加和上边一样的字符串即可。\n"
  },
  {
    "path": "06_repository/6.3_registry_auth.md",
    "content": "## 6.3 私有仓库高级配置\n\n上一节我们搭建了一个具有基础功能的私有仓库，本小节我们来使用 `Docker Compose` 搭建一个拥有权限认证、TLS 的私有仓库。\n\n新建一个文件夹，以下步骤均在该文件夹中进行。\n\n### 6.3.1 准备站点证书\n\n如果你拥有一个域名，国内各大云服务商均提供免费的站点证书。你也可以使用 `openssl` 自行签发证书。\n\n这里假设我们将要搭建的私有仓库地址为 `docker.domain.com`，下面我们介绍使用 `openssl` 自行签发 `docker.domain.com` 的站点 SSL 证书。\n\n第一步创建 `CA` 私钥。\n\n```bash\n$ openssl genrsa -out \"root-ca.key\" 4096\n```\n\n第二步利用私钥创建 `CA` 根证书请求文件。\n\n```bash\n$ openssl req \\\n          -new -key \"root-ca.key\" \\\n          -out \"root-ca.csr\" -sha256 \\\n          -subj '/C=CN/ST=Shanxi/L=Datong/O=Your Company Name/CN=Your Company Name Docker Registry CA'\n```\n\n> 以上命令中 `-subj` 参数里的 `/C` 表示国家，如 `CN`；`/ST` 表示省；`/L` 表示城市或者地区；`/O` 表示组织名；`/CN` 通用名称。\n\n第三步配置 `CA` 根证书，新建 `root-ca.cnf`。\n\n```bash\n[root_ca]\nbasicConstraints = critical,CA:TRUE,pathlen:1\nkeyUsage = critical, nonRepudiation, cRLSign, keyCertSign\nsubjectKeyIdentifier=hash\n```\n\n第四步签发根证书。\n\n```bash\n$ openssl x509 -req  -days 3650  -in \"root-ca.csr\" \\\n               -signkey \"root-ca.key\" -sha256 -out \"root-ca.crt\" \\\n               -extfile \"root-ca.cnf\" -extensions \\\n               root_ca\n```\n\n第五步生成站点 `SSL` 私钥。\n\n```bash\n$ openssl genrsa -out \"docker.domain.com.key\" 4096\n```\n\n第六步使用私钥生成证书请求文件。\n\n```bash\n$ openssl req -new -key \"docker.domain.com.key\" -out \"site.csr\" -sha256 \\\n          -subj '/C=CN/ST=Shanxi/L=Datong/O=Your Company Name/CN=docker.domain.com'\n```\n\n第七步配置证书，新建 `site.cnf` 文件。\n\n```bash\n[server]\nauthorityKeyIdentifier=keyid,issuer\nbasicConstraints = critical,CA:FALSE\nextendedKeyUsage=serverAuth\nkeyUsage = critical, digitalSignature, keyEncipherment\nsubjectAltName = DNS:docker.domain.com, IP:127.0.0.1\nsubjectKeyIdentifier=hash\n```\n\n第八步签署站点 `SSL` 证书。\n\n```bash\n$ openssl x509 -req -days 750 -in \"site.csr\" -sha256 \\\n    -CA \"root-ca.crt\" -CAkey \"root-ca.key\"  -CAcreateserial \\\n    -out \"docker.domain.com.crt\" -extfile \"site.cnf\" -extensions server\n```\n\n这样已经拥有了 `docker.domain.com` 的网站 SSL 私钥 `docker.domain.com.key` 和 SSL 证书 `docker.domain.com.crt` 及 CA 根证书 `root-ca.crt`。\n\n新建 `ssl` 文件夹并将 `docker.domain.com.key` `docker.domain.com.crt` `root-ca.crt` 这三个文件移入，删除其他文件。\n\n### 6.3.2 配置私有仓库\n\n私有仓库默认的配置文件位于 `/etc/docker/registry/config.yml`，我们先在本地编辑 `config.yml`，之后挂载到容器中。\n\n```yaml\nlog:\n  accesslog:\n    disabled: true\n  level: debug\n  formatter: text\n  fields:\n    service: registry\n    environment: staging\nstorage:\n  delete:\n    enabled: true\n  cache:\n    blobdescriptor: inmemory\n  filesystem:\n    rootdirectory: /var/lib/registry\nauth:\n  htpasswd:\n    realm: basic-realm\n    path: /etc/docker/registry/auth/nginx.htpasswd\nhttp:\n  addr: :443\n  host: https://docker.domain.com\n  headers:\n    X-Content-Type-Options: [nosniff]\n  http2:\n    disabled: false\n  tls:\n    certificate: /etc/docker/registry/ssl/docker.domain.com.crt\n    key: /etc/docker/registry/ssl/docker.domain.com.key\nhealth:\n  storagedriver:\n    enabled: true\n    interval: 10s\nthreshold: 3\n```\n\n### 6.3.3 生成 http 认证文件\n\n```bash\n$ mkdir auth\n\n$ docker run --rm \\\n    --entrypoint htpasswd \\\n    httpd:alpine \\\n    -Bbn username password > auth/nginx.htpasswd\n```\n\n> 将上面的 `username` `password` 替换为你自己的用户名和密码。\n\n### 6.3.4 编辑 Docker Compose 配置\n\n编辑 `compose.yaml` (或 `docker-compose.yml`) 配置如下：\n\n```yaml\nservices:\n  registry:\n    image: registry\n    ports:\n      - \"443:443\"\n    volumes:\n      - ./:/etc/docker/registry\n      - registry-data:/var/lib/registry\n\nvolumes:\n  registry-data:\n```\n\n### 6.3.5 修改 Hosts 文件\n\n编辑 `/etc/hosts`\n\n```bash\n127.0.0.1 docker.domain.com\n```\n\n### 6.3.6 启动\n\n```bash\n$ docker compose up -d\n```\n\n这样我们就搭建好了一个具有权限认证、TLS 的私有仓库，接下来我们测试其功能是否正常。\n\n### 6.3.7 测试私有仓库功能\n\n由于自行签发的 CA 根证书不被系统信任，所以我们需要将 CA 根证书 `ssl/root-ca.crt` 移入 `/etc/docker/certs.d/docker.domain.com` 文件夹中。\n\n```bash\n$ sudo mkdir -p /etc/docker/certs.d/docker.domain.com\n\n$ sudo cp ssl/root-ca.crt /etc/docker/certs.d/docker.domain.com/ca.crt\n```\n\n登录到私有仓库。\n\n```bash\n$ docker login docker.domain.com\n```\n\n尝试推送、拉取镜像。\n\n```bash\n$ docker pull ubuntu:24.04\n\n$ docker tag ubuntu:24.04 docker.domain.com/username/ubuntu:24.04\n\n$ docker push docker.domain.com/username/ubuntu:24.04\n\n$ docker image rm docker.domain.com/username/ubuntu:24.04\n\n$ docker pull docker.domain.com/username/ubuntu:24.04\n```\n\n如果我们退出登录，尝试推送镜像。\n\n```bash\n$ docker logout docker.domain.com\n\n$ docker push docker.domain.com/username/ubuntu:24.04\n\nno basic auth credentials\n```\n\n发现会提示没有登录，不能将镜像推送到私有仓库中。\n\n### 6.3.8 注意事项\n\n如果你本机占用了 `443` 端口，你可以配置 [Nginx 代理](https://docs.docker.com/registry/recipes/nginx/)，这里不再赘述。\n"
  },
  {
    "path": "06_repository/6.4_nexus3_registry.md",
    "content": "## 6.4 Nexus 3\n\n使用 Docker 官方的 Registry 创建的仓库面临一些维护问题。比如某些镜像删除以后空间默认是不会回收的，需要一些命令去回收空间然后重启 Registry。在企业中把内部的一些工具包放入 `Nexus` 中是比较常见的做法，最新版本 `Nexus3.x` 全面支持 Docker 的私有镜像。所以使用 [Nexus 3](https://www.sonatype.com/product/repository-oss-download) 一个软件来管理 `Docker`，`Maven`，`Yum`，`PyPI` 等是一个明智的选择。\n\n### 6.4.1 启动 Nexus 容器\n\n```bash\n$ docker run -d --name nexus3 --restart=always \\\n    -p 8081:8081 \\\n    --mount src=nexus-data,target=/nexus-data \\\n    sonatype/nexus3\n```\n\n首次运行需等待 3-5 分钟，你可以使用 `docker logs nexus3 -f` 查看日志：\n\n```bash\n$ docker logs nexus3 -f\n\n2021-03-11 15:31:21,990+0000 INFO  [jetty-main-1] *SYSTEM org.sonatype.nexus.bootstrap.jetty.JettyServer -\n-------------------------------------------------\n\nStarted Sonatype Nexus OSS 3.30.0-01\n\n-------------------------------------------------\n```\n\n如果你看到以上内容，说明 `Nexus` 已经启动成功，你可以使用浏览器打开 `http://YourIP:8081` 访问 `Nexus` 了。\n\n首次运行请通过以下命令获取初始密码：\n\n```bash\n$ docker exec nexus3 cat /nexus-data/admin.password\n\n9266139e-41a2-4abb-92ec-e4142a3532cb\n```\n\n首次启动 Nexus 的默认账号是 `admin`，密码则是上边命令获取到的，点击右上角登录，首次登录需更改初始密码。\n\n登录之后可以点击页面上方的齿轮按钮按照下面的方法进行设置。\n\n### 6.4.2 创建仓库\n\n创建一个私有仓库的方法：`Repository->Repositories` 点击右边菜单 `Create repository` 选择 `docker (hosted)`\n\n* **Name**：仓库的名称\n* **HTTP**：仓库单独的访问端口 (例如：**5001**)\n* **Hosted -> Deployment policy**：请选择 **Allow redeploy** 否则无法上传 Docker 镜像。\n\n其它的仓库创建方法请各位自己摸索，还可以创建一个 `docker (proxy)` 类型的仓库链接到 DockerHub 上。再创建一个 `docker (group)` 类型的仓库把刚才的 `hosted` 与 `proxy` 添加在一起。主机在访问的时候默认下载私有仓库中的镜像，如果没有将链接到 DockerHub 中下载并缓存到 Nexus 中。\n\n### 6.4.3 添加访问权限\n\n菜单 `Security->Realms` 把 Docker Bearer Token Realm 移到右边的框中保存。\n\n添加用户规则：菜单 `Security->Roles`->`Create role` 在 `Privileges` 选项搜索 docker 把相应的规则移动到右边的框中然后保存。\n\n添加用户：菜单 `Security->Users`->`Create local user` 在 `Roles` 选项中选中刚才创建的规则移动到右边的窗口保存。\n\n### 6.4.4 NGINX 反向代理配置\n\n证书的生成请参见 [私有仓库认证](6.3_registry_auth.md) 里面证书生成一节。\n\nNGINX 示例配置如下\n\n```nginx\nupstream register\n{\n    server \"YourHostName OR IP\":5001; #端口为上面添加私有镜像仓库时设置的 HTTP 选项的端口号\n    check interval=3000 rise=2 fall=10 timeout=1000 type=http;\n    check_http_send \"HEAD / HTTP/1.0\\r\\n\\r\\n\";\n    check_http_expect_alive http_4xx;\n}\n\nserver {\n    server_name YourDomainName;#如果没有 DNS 服务器做解析，请删除此选项使用本机 IP 地址访问\n    listen       443 ssl;\n\n    ssl_certificate key/example.crt;\n    ssl_certificate_key key/example.key;\n\n    ssl_session_timeout  5m;\n    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;\n    ssl_ciphers  HIGH:!aNULL:!MD5;\n    ssl_prefer_server_ciphers   on;\n    large_client_header_buffers 4 32k;\n    client_max_body_size 300m;\n    client_body_buffer_size 512k;\n    proxy_connect_timeout 600;\n    proxy_read_timeout   600;\n    proxy_send_timeout   600;\n    proxy_buffer_size    128k;\n    proxy_buffers       4 64k;\n    proxy_busy_buffers_size 128k;\n    proxy_temp_file_write_size 512k;\n\n    location / {\n        proxy_set_header Host $host;\n        proxy_set_header X-Forwarded-Proto $scheme;\n        proxy_set_header X-Forwarded-Port $server_port;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_http_version 1.1;\n        proxy_set_header Upgrade $http_upgrade;\n        proxy_set_header Connection $connection_upgrade;\n        proxy_redirect off;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_pass http://register;\n        proxy_read_timeout 900s;\n\n    }\n    error_page   500 502 503 504  /50x.html;\n}\n```\n\n### 6.4.5 Docker 主机访问镜像仓库\n\n如果不启用 SSL 加密可以通过[前面章节](6.2_registry.md)的方法添加非 https 仓库地址到 Docker 的配置文件中然后重启 Docker。\n\n使用 SSL 加密以后程序需要访问就不能采用修改配置的方式了。具体方法如下：\n\n```bash\n$ openssl s_client -showcerts -connect YourDomainName OR HostIP:443 </dev/null 2>/dev/null|openssl x509 -outform PEM >ca.crt\n$ cat ca.crt | sudo tee -a /etc/ssl/certs/ca-certificates.crt\n$ systemctl restart docker\n```\n\n使用 `docker login YourDomainName OR HostIP` 进行测试，用户名密码填写上面 Nexus 中设置的。\n"
  },
  {
    "path": "06_repository/README.md",
    "content": "# 第六章 访问仓库\n\n仓库 (`Repository`) 是集中存放镜像的地方。\n\n一个容易混淆的概念是注册服务器 (`Registry`)。实际上注册服务器是管理仓库的具体服务器，每个服务器上可以有多个仓库，而每个仓库下面有多个镜像。从这方面来说，仓库可以被认为是一个具体的项目或目录。例如对于仓库地址 `docker.io/ubuntu` 来说，`docker.io` 是注册服务器地址，`ubuntu` 是仓库名。\n\n大部分时候，并不需要严格区分这两者的概念。\n\n## 本章内容\n\n* [Docker Hub](6.1_dockerhub.md)\n* [私有仓库](6.2_registry.md)\n* [私有仓库高级配置](6.3_registry_auth.md)\n* [Nexus 3](6.4_nexus3_registry.md)\n"
  },
  {
    "path": "06_repository/demo/auth/nginx.htpasswd",
    "content": "username:$2y$05$TRWvCC6ilpKpY3ICifw32Ok3.8SpG3etq8O5WGdCm9wvyDhtSbRgy\n\n"
  },
  {
    "path": "06_repository/demo/config.yml",
    "content": "version: 0.1\nlog:\n  accesslog:\n    disabled: true\n  level: debug\n  formatter: text\n  fields:\n    service: registry\n    environment: staging\nstorage:\n  delete:\n    enabled: true\n  cache:\n    blobdescriptor: inmemory\n  filesystem:\n    rootdirectory: /var/lib/registry\nauth:\n  htpasswd:\n    realm: basic-realm\n    path: /etc/docker/registry/auth/nginx.htpasswd\nhttp:\n  addr: :5000\n  host: https://docker.domain.com\n  headers:\n    X-Content-Type-Options: [nosniff]\n  http2:\n    disabled: false\n  tls:\n    certificate: /etc/docker/registry/ssl/docker.domain.com.crt\n    key: /etc/docker/registry/ssl/docker.domain.com.key\nhealth:\n  storagedriver:\n    enabled: true\n    interval: 10s\nthreshold: 3\n"
  },
  {
    "path": "06_repository/demo/docker-compose.yml",
    "content": "\n\nservices:\n  registry:\n    image: registry:2\n    ports:\n      - \"443:5000\"\n    volumes:\n      - ./:/etc/docker/registry\n      - registry-data:/var/lib/registry\n\nvolumes:\n  registry-data:\n"
  },
  {
    "path": "06_repository/demo/root-ca.cnf",
    "content": "[root_ca]\nbasicConstraints = critical,CA:TRUE,pathlen:1\nkeyUsage = critical, nonRepudiation, cRLSign, keyCertSign\nsubjectKeyIdentifier=hash\n"
  },
  {
    "path": "06_repository/demo/site.cnf",
    "content": "[server]\nauthorityKeyIdentifier=keyid,issuer\nbasicConstraints = critical,CA:FALSE\nextendedKeyUsage=serverAuth\nkeyUsage = critical, digitalSignature, keyEncipherment\nsubjectAltName = DNS:docker.domain.com, IP:127.0.0.1\nsubjectKeyIdentifier=hash\n"
  },
  {
    "path": "06_repository/demo/ssl/docker.domain.com.crt",
    "content": "-----BEGIN CERTIFICATE-----\nMIIF/zCCA+egAwIBAgIJAMbgVbFo7I6IMA0GCSqGSIb3DQEBCwUAMHoxCzAJBgNV\nBAYTAkNOMQ8wDQYDVQQIDAZTaGFueGkxDzANBgNVBAcMBkRhdG9uZzEaMBgGA1UE\nCgwRWW91ciBDb21wYW55IE5hbWUxLTArBgNVBAMMJFlvdXIgQ29tcGFueSBOYW1l\nIERvY2tlciBSZWdpc3RyeSBDQTAeFw0xNzEyMTExMTI2NTRaFw0xOTEyMzExMTI2\nNTRaMGcxCzAJBgNVBAYTAkNOMQ8wDQYDVQQIDAZTaGFueGkxDzANBgNVBAcMBkRh\ndG9uZzEaMBgGA1UECgwRWW91ciBDb21wYW55IE5hbWUxGjAYBgNVBAMMEWRvY2tl\nci5kb21haW4uY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA1Hbm\n1tZvAeC8J54pQLHTVCtrICQ8KFTLOZakPHSWox8iQ4i2fkZaSccvE/51LIFnCM1y\nyZv88ILucqitG4zvuhDG+cU8w1vRbWf3xfGCCsHyn6LKBHR0Kk6+WRIBZTdRSsW8\nZvpL2Y7eBNAUkC2oeaOJEOQ8D50b3u5jhAFmXuAcTiZh90Ve4JBKZV8dGs8L81vO\nvb7tqvJrEvCNKuZO7mEcjXkgiwUdP3pkZYa0tPOV5UrLH/oEvgPDJfXrNntCY1+A\n+CBQ7Sq3S2YpNJN7VnK6SboRi7xpOEgQOXwNVJWm/5YBvnbAztNXKcE2q2wREL9T\nulUJCqo2h6NRaGYPfiLZIUHEJ7vQitRBRDpkxcX8XJX3Jm7weRPXLDOG8RN3FzPD\nTGL2ZDTWKDIQ20OhYEmENInKkpC2bYpwMzNvBA7AJN6MCYHXl4VP/Df0pPtMM/Di\nZX+68ng3RQXT5VzDYURIH2wP5kcTL0irsNr+L7lHD+5mA1VErdQsh6AHWjrwMrSM\negJpLP11rK4J+P/HHnqsBEi5XZI/hvRmwD7gwskRqXdDMHaWnoIIpPO3TeNYQUjD\nxaV5KUQuWVME5Ihuy03FPMChXXB0WF+tclPJxjQyDkwEVL+6d8i5yCPUBS2gBSBy\npQQINFH4hOcx2e9Lujx7dOWfidjX3xssC9bEe60CAwEAAaOBmjCBlzAfBgNVHSME\nGDAWgBTS1B3CPFKA/HcI0cdE8YD4zG143DAMBgNVHRMBAf8EAjAAMBMGA1UdJQQM\nMAoGCCsGAQUFBwMBMA4GA1UdDwEB/wQEAwIFoDAiBgNVHREEGzAZghFkb2NrZXIu\nZG9tYWluLmNvbYcEfwAAATAdBgNVHQ4EFgQUSanj6Cs4KVEKaK6/+VA/fNwNg4Ew\nDQYJKoZIhvcNAQELBQADggIBAKYtI1WKAL4FoSgH6sTZakw6h90uebrxm9ojeZTA\nk0ues8bGTu3w3dsphd9J0V27oz/dGjkwoIzy4QMYC4h6epKVadWfDhnHUPUT1JIC\nnGl7qFR539CSPzW+J1mVAGTZ1QONVxe6rFEDRXTsm9oUNq9LUB6a9EBO/9O0x2o7\nSZVUJd2WfMGAhqYjKCtMt+8kQgPxayok5IwWLBf03nluoF09Xu1WbY3f9wGNrzQp\nulNlLzkU3f7+dVgF4lvIbr4MPWSQL2A0RYYjqWuwvUlXggtR+Nl6ldotDe7Ae18V\nKhQPJzM4muHjRY5dLkzQIIAQifxNprZCYiurUCAmOyOcHYMt5RuiVUPlB/2hoP/E\ntuFqq66v0qsE4mCfmJrRq+Yjfgcqsg1quRpjWh9DWOGa9HUeYFkLEKOgXybxVHJQ\nktYba34ZFfBJUMcbZRYrRH6R4zu4LpRiyiXm29F5ml9tarThDZB5g5DJ6BTEt3Zw\n+qQHsIAcmHZvJPKEZmM6883gxbGQQ1Xt7iDrp94YRXMguBMbJwEsqI7w+25BHija\nHp4gctdoBvkQBYpXoEsn8wnguofqJt/JhVgu0EQXR4j3U0uI+Oo9ODHFb5t2T26w\nEifwcLH+NyUNmUQH45lxaCzb+tqFlP7cbHsdPniaS4AtmBYwKNJMjcrgxnZPtfy1\n7zJA\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "06_repository/demo/ssl/docker.domain.com.key",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nMIIJKgIBAAKCAgEA1Hbm1tZvAeC8J54pQLHTVCtrICQ8KFTLOZakPHSWox8iQ4i2\nfkZaSccvE/51LIFnCM1yyZv88ILucqitG4zvuhDG+cU8w1vRbWf3xfGCCsHyn6LK\nBHR0Kk6+WRIBZTdRSsW8ZvpL2Y7eBNAUkC2oeaOJEOQ8D50b3u5jhAFmXuAcTiZh\n90Ve4JBKZV8dGs8L81vOvb7tqvJrEvCNKuZO7mEcjXkgiwUdP3pkZYa0tPOV5UrL\nH/oEvgPDJfXrNntCY1+A+CBQ7Sq3S2YpNJN7VnK6SboRi7xpOEgQOXwNVJWm/5YB\nvnbAztNXKcE2q2wREL9TulUJCqo2h6NRaGYPfiLZIUHEJ7vQitRBRDpkxcX8XJX3\nJm7weRPXLDOG8RN3FzPDTGL2ZDTWKDIQ20OhYEmENInKkpC2bYpwMzNvBA7AJN6M\nCYHXl4VP/Df0pPtMM/DiZX+68ng3RQXT5VzDYURIH2wP5kcTL0irsNr+L7lHD+5m\nA1VErdQsh6AHWjrwMrSMegJpLP11rK4J+P/HHnqsBEi5XZI/hvRmwD7gwskRqXdD\nMHaWnoIIpPO3TeNYQUjDxaV5KUQuWVME5Ihuy03FPMChXXB0WF+tclPJxjQyDkwE\nVL+6d8i5yCPUBS2gBSBypQQINFH4hOcx2e9Lujx7dOWfidjX3xssC9bEe60CAwEA\nAQKCAgEAqT/6zePOVFGhsXG17Rp7fY6E7PrQjVRW/A470QkTQui3U9MhhWAn5qPs\npeHLl+ORn5qCOYawrSuwJdim5c6U3cUlrKzppbqMD7qFz8J+1HECBRcaFQhrzZQi\n4DOOtwGlGYqBdgsnxyyfQng8GUq17ghPVQxrqAiAvktrLSosUaH4Cm1bFy7E0OFA\n0pY9SjDrlTZqcA8bp1Ur5M+JtUX4VL85jp2SRgyR6xJlzdbMN2Xf3+OAAn4ZrwCy\nQZgwgpsYHK9kvsSHkxa3IzJD2uUtmIUWT0sRVR6HN1V4z0I6IEqC2RG3W/Gf0GLd\nCZ8oHNCem5e+bC33YO6NN+nrHN5Isb+itdbtE392P6FgPbM1um1zuuTTewaUyXS7\nATomznTpYXkHvCdvU5yOEH+yDYfcm99v77qVr0+arecVx2h0M2tqROwEza3Rw5Wp\n928vyxPFde9HFHQG4SWRzCGfKpnvIT/ce2ayWHEDEbvzlwL9lokZqe0YYu5KDYTL\nj3DsnzMPiMn6bpQUIBXlO5+eAh94vatPCriNpEkHw3aYQNyux1BmyvKxflj6pg9u\nlxKWGng8YOW88ysXvXlAssjpDe+k/Cvaja3ZeV3pyRbMnK3ARHXi3mQ6wBsOVa6e\nzt3cpBgXik7m7u6a82FhLafP2UfFIpW6WRTA28ercpKtHRKJJgUCggEBAPWJsfPa\n4movMI6ofySMFm74u779aO5rGMQumR52vwlWyNDmOWNRrQaU/tpyaFOiPeIOGt2V\nUM02rdWHbnAhsgYP0cjUs34aJV/Z4nunw5Jc6rrT+dhd3+ZgqM1X/sLt5JfEavHe\nbV/cDV+xDp4FAXrLOPeRldvLPT1dmdQnirPGK3A1WWi5GQ+//Kas+lHuqECWYRrU\nLVFx0tR/pmdCC5Lb58kuPOxFP4OaeC3PbGyA2y0gv+QR/5Nc0o1y/X6p6XIM5QxY\nfg4gDKwSewrZ40+9taRNtgMQz3xKkeYmaNgLKnCBnLhDYdLZPAeYkOUIytzJbaYg\noWHzmdd5FIKCgzcCggEBAN2Ecd7lJRIISuQop5GQ44mRrsDZTxBWnZ9pn3VFWNfA\ntF5MHofrtED7mXBSAt88TryqndcyC6qMvaS4Ifk1cNSFdz9FLsNzxJj4wp5Cj+e1\naSY5ARvXTXqTfKZErQNXFk1oCa+ARZIs5SPjh0LH+Iq2pd72cEap7pi3JT5RbOtE\nReDCAKayyejFMKtekzccishIwH/nzYlNaNBkmG7MZG8V+FFlxYSieIa7Ohcbmmj3\nD3ssbi+y+peRt14wZB/daScybcIqdu544ZIvJOpm0i9rHxE49bodmTDhAZ9awqls\nnRsyI0NNDExPDhCrzPogcRLVHn2KbcZbjGPS5jossjsCggEAazYchaXlhwfj4+ae\n3Y5tnTbug46S6sfIoKDYKv0enS1PsidUl5FqQ517Slb6RspoyvPttyMjjPd7H+lq\nx3tvCEaQC2kUltNDzn6M7gFq29XGiJ1WUqtqwGUkT8VEcEj/r2UMbV/50gl7rXTa\nNRVqd/uUfEUNclNkAg+Ew6YgYi79eJlS2O85ii8CWqTdCDl1Lf57mANdZlqU/ERg\nnGWyOAXdR3LxFxmFiilAoIAZj6cUDLhoEWXqeqXlKe4z0cLPNAV9Xc6l+/Tyk4/e\nOfa50m+7iGqGNwB4GIVW/292CB+YAFgX3j1N0YsZMxfi7J7SNWWegxNsZCDB49vy\noKnsMQKCAQEA0iurbnOyrF050SfRdQcnG4shZs/HeBT2EB3CsR1OocWwXBeUkBlO\nOKl+d1cYan1ppw+qGlbdQr+t3u7lLPFLUBghf+I/8CmSyiCbZlR4/LrePOmw551r\nYXU1uvtFu/mQq3ieV+k4GOyHq3lhCDd61QFedyESfbkVK8f4ihvvX3izZAAtZfwU\nHcmZ174vpwZploWQPsrL9A2B+Na42ccLM2qA45nPwXv1Jr/U6b/CzPw7r/4DvTXv\nFIeolrELDkCgWBQ8lxB7Lt96BZy9Rbiwi1TzcP++BQu4IOwbAfq23tCybu8vDde4\nZ15KVf7qyBansdqKx0njxWNu2/dpgKCPqQKCAQEAzWV4aajox7eeTi3iudP1Oxu3\nOBiu8xie4xq0mlM13tMxAQry/uOAuTxbaQ48mNsSXdeYeSgmT45lUvFWROqdVScK\n8gh04G1NiRAkITzXwCCwKkAQxvQppgypZ+aksBHkFQFBAIg2/mLizS4cicXNEY4G\nvb+RImfn8MSqSMLu3cJ8zgFyqfRg6F0oHg9EgEwvbnCLYglN6Xm5KlZutjW4eu7m\nQ+1y0lg05e7vFyj2UIVylfzT/aoF/xzXzNt9/LZs0klO3vMmaVyTjuF/71+AyphH\nFTmpNs1wpZU2IRqKOMrimIhk6TTxvMSaN4pxKdLPqgHYLWhwkbsdmqtTnCljRg==\n-----END RSA PRIVATE KEY-----\n"
  },
  {
    "path": "06_repository/summary.md",
    "content": "## 本章小结\n\n本章介绍了 Docker Hub 的使用、私有仓库的搭建以及 Nexus 3 等企业级方案。\n\n| 功能 | 说明 |\n|------|------|\n| **官方镜像** | 优先使用的基础镜像 |\n| **拉取限制** | 匿名 100 次/6h，登录 200 次/6h |\n| **安全** | 推荐开启 2FA 并使用 Access Token |\n| **自动化** | 支持 Webhooks 和自动构建 |\n\n### 延伸阅读\n\n- [私有仓库](6.2_registry.md)：搭建自己的 Registry\n- [镜像加速器](../03_install/3.9_mirror.md)：加速下载\n---\n\n> 📝 **发现错误或有改进建议？** 欢迎提交 [Issue](https://github.com/yeasy/docker_practice/issues) 或 [PR](https://github.com/yeasy/docker_practice/pulls)。\n"
  },
  {
    "path": "07_dockerfile/7.10_workdir.md",
    "content": "## 7.10 WORKDIR 指定工作目录\n\n### 7.10.1 基本语法\n\n```docker\nWORKDIR <工作目录路径>\n```\n\n`WORKDIR` 指定后续指令的工作目录。如果目录不存在，Docker 会自动创建。\n\n---\n\n### 7.10.2 基本用法\n\n```docker\nWORKDIR /app\n\nRUN pwd          # 输出 /app\nRUN echo \"hello\" > world.txt    # 创建 /app/world.txt\nCOPY . .         # 复制到 /app/\n```\n\n---\n\n### 7.10.3 为什么需要 WORKDIR\n\n#### 常见错误\n\n```docker\n## ❌ 错误：cd 在下一个 RUN 中无效\n\nRUN cd /app\nRUN echo \"hello\" > world.txt    # 文件在根目录！\n```\n\n#### 原因分析\n\n```dockerfile\nRUN cd /app\n    ↓\n启动容器 → cd /app（仅内存变化）→ 提交镜像层 → 容器销毁\n                                   │\n                                   ↓ 工作目录未改变！\nRUN echo \"hello\" > world.txt\n    ↓\n启动新容器（工作目录在 /）→ 创建 /world.txt\n```\n\n每个 RUN 都在新容器中执行，**前一个 RUN 的内存状态 (包括工作目录) 不会保留**。\n\n#### 正确做法\n\n```docker\n## ✅ 正确：使用 WORKDIR\n\nWORKDIR /app\nRUN echo \"hello\" > world.txt    # 创建 /app/world.txt\n```\n\n---\n\n### 7.10.4 相对路径\n\nWORKDIR 支持相对路径，基于上一个 WORKDIR：\n\n```docker\nWORKDIR /a\nWORKDIR b\nWORKDIR c\n\nRUN pwd    # 输出 /a/b/c\n```\n\n---\n\n### 7.10.5 使用环境变量\n\n```docker\nENV APP_HOME=/app\nWORKDIR $APP_HOME\n\nRUN pwd    # 输出 /app\n```\n\n---\n\n### 7.10.6 多阶段构建中的 WORKDIR\n\n```docker\n## 构建阶段\n\nFROM node:20 AS builder\nWORKDIR /build\nCOPY package*.json ./\nRUN npm install\nCOPY . .\nRUN npm run build\n\n## 生产阶段\n\nFROM nginx:alpine\nWORKDIR /usr/share/nginx/html\nCOPY --from=builder /build/dist .\n```\n\n---\n\n### 7.10.7 最佳实践\n\n#### 1. 尽早设置 WORKDIR\n\n```docker\nFROM node:20\nWORKDIR /app    # 尽早设置\n\nCOPY package*.json ./\nRUN npm install\nCOPY . .\nCMD [\"node\", \"server.js\"]\n```\n\n#### 2. 使用绝对路径\n\n```docker\n## ✅ 推荐：绝对路径，意图明确\n\nWORKDIR /app\n\n## ⚠️ 避免：相对路径可能造成混淆\n\nWORKDIR app\n```\n\n#### 3. 不要用 RUN cd\n\n```docker\n## ❌ 避免\n\nRUN cd /app && echo \"hello\" > world.txt\n\n## ✅ 推荐\n\nWORKDIR /app\nRUN echo \"hello\" > world.txt\n```\n\n#### 4. 适时重置 WORKDIR\n\n```docker\nWORKDIR /app\n## ... 应用相关操作 ...\n\nWORKDIR /data\n## ... 数据相关操作 ...\n\n...\n```\n\n---\n\n### 7.10.8 与其他指令的关系\n\n| 指令 | WORKDIR 的影响 |\n|------|---------------|\n| `RUN` | 在 WORKDIR 中执行命令 |\n| `CMD` | 在 WORKDIR 中启动 |\n| `ENTRYPOINT` | 在 WORKDIR 中启动 |\n| `COPY` | 相对目标路径基于 WORKDIR |\n| `ADD` | 相对目标路径基于 WORKDIR |\n\n```docker\nWORKDIR /app\n\nRUN pwd                    # /app\nCOPY . .                   # 复制到 /app\nCMD [\"./start.sh\"]         # /app/start.sh\n```\n\n---\n\n### 7.10.9 运行时覆盖\n\n使用 `-w` 参数覆盖工作目录：\n\n```bash\n$ docker run -w /tmp myimage pwd\n/tmp\n```\n\n---\n"
  },
  {
    "path": "07_dockerfile/7.11_user.md",
    "content": "## 7.11 USER 指定当前用户\n\n### 7.11.1 基本语法\n\n```docker\nUSER <用户名>[:<用户组>]\nUSER <UID>[:<GID>]\n```\n\n`USER` 指令切换后续指令 (RUN、CMD、ENTRYPOINT) 的执行用户。\n\n---\n\n### 7.11.2 为什么要使用 USER\n\n> 笔者强调：以非 root 用户运行容器是最重要的安全实践之一。\n\n```mermaid\nflowchart LR\n    subgraph Root [\"root 用户运行的风险：\"]\n        direction TB\n        R_C[\"容器内 root\"] -- 可能逃逸 --> R_H[\"宿主机 root\"]\n        R_C -- 漏洞利用 --> R_Control[\"完全控制宿主机\"]\n    end\n    \n    subgraph NonRoot [\"非 root 用户运行：\"]\n        direction TB\n        NR_C[\"容器内普通用户\"] -- 逃逸后 --> NR_H[\"宿主机普通用户\"]\n        NR_C -- 权限受限，危害降低 --> NR_Safe[\"无法控制系统\"]\n    end\n```\n\n---\n\n### 7.11.3 基本用法\n\n#### 创建并切换用户\n\n```docker\nFROM node:20-alpine\n\n## 1. 创建用户和组\n\nRUN addgroup -g 1001 appgroup && \\\n    adduser -u 1001 -G appgroup -D appuser\n\n## 2. 设置目录权限\n\nWORKDIR /app\nCOPY --chown=appuser:appgroup . .\n\n## 3. 切换用户\n\nUSER appuser\n\n## 4. 后续命令以 appuser 身份运行\n\nCMD [\"node\", \"server.js\"]\n```\n\n#### 使用 UID/GID\n\n```docker\n## 也可以使用数字\n\nUSER 1001:1001\n```\n\n---\n\n### 7.11.4 用户必须已存在\n\n`USER` 指令只能切换到 **已存在** 的用户：\n\n```docker\n## ❌ 错误：用户不存在\n\nUSER nonexistent\n## Error: unable to find user nonexistent\n\n## ✅ 正确：先创建用户\n\nRUN useradd -r -s /bin/false appuser\nUSER appuser\n```\n\n#### 创建用户的方式\n\n**Debian/Ubuntu**：\n\n```docker\nRUN groupadd -r appgroup && \\\n    useradd -r -g appgroup appuser\n```\n\n**Alpine**：\n\n```docker\nRUN addgroup -g 1001 -S appgroup && \\\n    adduser -u 1001 -S -G appgroup appuser\n```\n\n| 选项 | 说明 |\n|------|------|\n| `-r` (useradd) / `-S` (adduser) | 创建系统用户 |\n| `-g` | 指定主组 |\n| `-G` | 指定附加组 |\n| `-u` | 指定 UID |\n| `-s /bin/false` | 禁用登录 shell |\n\n---\n\n### 7.11.5 运行时切换用户\n\n#### 使用 gosu：推荐\n\n在 ENTRYPOINT 脚本中切换用户时，不要使用 `su` 或 `sudo`，应使用 [gosu](https://github.com/tianon/gosu)：\n\n```docker\nFROM debian:bookworm\n\n## 创建用户\n\nRUN groupadd -r redis && useradd -r -g redis redis\n\n## 安装 gosu\n\nRUN apt-get update && apt-get install -y gosu && rm -rf /var/lib/apt/lists/*\n\nCOPY docker-entrypoint.sh /usr/local/bin/\nENTRYPOINT [\"docker-entrypoint.sh\"]\nCMD [\"redis-server\"]\n```\n\n**docker-entrypoint.sh**：\n\n```bash\n#!/bin/bash\nset -e\n\n## 以 root 执行初始化\n\nchown -R redis:redis /data\n\n## 用 gosu 切换到 redis 用户运行服务\n\nexec gosu redis \"$@\"\n```\n\n#### 为什么不用 su/sudo\n\n| 问题 | su/sudo | gosu |\n|------|---------|------|\n| TTY 要求 | 需要 | 不需要 |\n| 信号传递 | 不正确 | 正确 |\n| 子进程 | 是 | exec 替换 |\n| 容器中使用 | ❌ | ✅ |\n\n---\n\n### 7.11.6 运行时覆盖用户\n\n使用 `-u` 或 `--user` 参数：\n\n```bash\n## 以指定用户运行\n\n$ docker run -u 1001:1001 myimage\n\n## 以 root 运行（调试时）\n\n$ docker run -u root myimage\n```\n\n---\n\n### 7.11.7 文件权限处理\n\n切换用户后，确保应用有权访问文件：\n\n```docker\nFROM node:20-alpine\n\n## 创建用户\n\nRUN adduser -D -u 1001 appuser\n\nWORKDIR /app\n\n## 方式1：使用 --chown\n\nCOPY --chown=appuser:appuser . .\n\n## 方式2：手动 chown（减少层数）\n\n## COPY . .\n\n## RUN chown -R appuser:appuser /app\n\nUSER appuser\nCMD [\"node\", \"server.js\"]\n```\n\n---\n\n### 7.11.8 最佳实践\n\n#### 1. 始终使用非 root 用户\n\n```docker\n## ✅ 推荐\n\nRUN adduser -D appuser\nUSER appuser\nCMD [\"myapp\"]\n\n## ❌ 避免\n\nCMD [\"myapp\"]  # 以 root 运行\n```\n\n#### 2. 使用固定 UID/GID\n\n便于在宿主机和容器间共享文件：\n\n```docker\n## 使用常见的非 root UID\n\nRUN addgroup -g 1000 -S appgroup && \\\n    adduser -u 1000 -S -G appgroup appuser\nUSER 1000:1000\n```\n\n#### 3. 多阶段构建中的 USER\n\n```docker\n## 构建阶段可以用 root\n\nFROM node:20 AS builder\nWORKDIR /app\nCOPY . .\nRUN npm install && npm run build\n\n## 生产阶段用非 root\n\nFROM node:20-alpine\nRUN adduser -D appuser\nWORKDIR /app\nCOPY --from=builder --chown=appuser:appuser /app/dist .\nUSER appuser\nCMD [\"node\", \"server.js\"]\n```\n\n---\n\n### 7.11.9 常见问题\n\n#### Q：权限被拒绝\n\n```bash\npermission denied: '/app/data.log'\n```\n\n**解决**：确保目录权限正确\n\n```docker\nRUN mkdir -p /app/data && chown appuser:appuser /app/data\n```\n\n#### Q：无法绑定低于 1024 的端口\n\n非 root 用户无法绑定 80、443 等端口。\n\n**解决**：\n\n1. 使用高端口 (如 8080)\n2. 在运行时映射端口：`docker run -p 80:8080`\n\n---\n"
  },
  {
    "path": "07_dockerfile/7.12_healthcheck.md",
    "content": "## 7.12 HEALTHCHECK 健康检查\n\n### 7.12.1 基本语法\n\n```docker\nHEALTHCHECK [选项] CMD <命令>\nHEALTHCHECK NONE\n```\n\n`HEALTHCHECK` 指令告诉 Docker 如何判断容器状态是否正常。这是保障服务高可用的重要机制。\n\n---\n\n### 7.12.2 为什么需要 HEALTHCHECK\n\n在没有 HEALTHCHECK 之前，Docker 只能通过 **进程退出码** 来判断容器状态。**问题场景**：\n\n- Web 服务死锁，无法响应请求，但进程仍在运行\n- 数据库正在启动中，尚未准备好接受连接\n- 应用陷入死循环，CPU 爆满但进程存活\n\n**引入 HEALTHCHECK 后**：\nDocker 定期执行指定的检查命令，根据返回值判断容器是否 “健康”。\n\n```bash\n容器状态转换：\nStarting ──成功──> Healthy ──失败N次──> Unhealthy\n                    ▲                │\n                    └──────成功──────┘\n```\n\n---\n\n### 7.12.3 基本用法\n\n#### Web 服务检查\n\n```docker\nFROM nginx\nRUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*\n\nHEALTHCHECK --interval=30s --timeout=3s --retries=3 \\\n  CMD curl -fs http://localhost/ || exit 1\n```\n\n#### 命令返回值\n\n- `0`：成功 (healthy)\n- `1`：失败 (unhealthy)\n- `2`：保留值 (不使用)\n\n#### 常用选项\n\n| 选项 | 说明 | 默认值 |\n|------|------|--------|\n| `--interval` | 两次检查的间隔 | 30s |\n| `--timeout` | 检查命令的超时时间 | 30s |\n| `--start-period` | 启动缓冲期 (期间失败不计入次数)| 0s |\n| `--retries` | 连续失败多少次标记为 unhealthy | 3 |\n\n---\n\n### 7.12.4 屏蔽健康检查\n\n如果基础镜像定义了 HEALTHCHECK，但你不想使用它：\n\n```docker\nFROM my-base-image\nHEALTHCHECK NONE\n```\n\n---\n\n### 7.12.5 常见检查脚本\n\n#### HTTP 服务\n\n使用 `curl` 或 `wget`：\n\n```docker\n## 使用 curl\n\nHEALTHCHECK CMD curl -f http://localhost/ || exit 1\n\n## 使用 wget（Alpine 默认包含）\n\nHEALTHCHECK CMD wget -q --spider http://localhost/ || exit 1\n```\n\n#### 数据库\n\n```docker\n## MySQL\n\nHEALTHCHECK CMD mysqladmin ping -h localhost || exit 1\n\n## Redis\n\nHEALTHCHECK CMD redis-cli ping || exit 1\n```\n\n#### 自定义脚本\n\n```docker\nCOPY healthcheck.sh /usr/local/bin/\nHEALTHCHECK CMD [\"healthcheck.sh\"]\n```\n\n---\n\n### 7.12.6 在 Compose 中使用\n\n可以在 `compose.yaml` (或 `docker-compose.yml`) 中覆盖或定义健康检查：\n\n```yaml\nservices:\n  web:\n    image: nginx\n    healthcheck:\n      test: [\"CMD\", \"curl\", \"-f\", \"http://localhost\"]\n      interval: 1m30s\n      timeout: 10s\n      retries: 3\n      start_period: 40s\n```\n\n带健康检查的依赖启动：\n\n```yaml\nservices:\n  web:\n    depends_on:\n      db:\n        condition: service_healthy  # 等待 db 变健康才启动 web\n  db:\n    image: mysql\n    healthcheck:\n      test: [\"CMD\", \"mysqladmin\", \"ping\", \"-h\", \"localhost\"]\n```\n\n---\n\n### 7.12.7 查看健康状态\n\n```bash\n## 查看容器状态（包含健康信息）\n\n$ docker ps\nCONTAINER ID   STATUS\nabc123         Up 1 minute (healthy)\ndef456         Up 2 minutes (unhealthy)\n\n## 查看详细健康日志\n\n$ docker inspect --format '{{json .State.Health}}' mycontainer | jq\n{\n  \"Status\": \"healthy\",\n  \"FailingStreak\": 0,\n  \"Log\": [\n    {\n      \"Start\": \"...\",\n      \"End\": \"...\",\n      \"ExitCode\": 0,\n      \"Output\": \"...\"\n    }\n  ]\n}\n```\n\n---\n\n### 7.12.8 最佳实践\n\n#### 1. 避免副作用\n\n健康检查会被频繁执行，不要在检查脚本中进行写操作或消耗大量资源的操作。\n\n#### 2. 使用轻量级工具\n\n优先使用镜像中已有的工具 (如 `wget`)，避免为了健康检查安装庞大的依赖 (如 `curl`)。\n\n#### 3. 设置合理的 Start Period\n\n应用启动可能需要时间 (如 Java 应用)。设置 `--start-period` 可以防止在启动阶段因检查失败而误判。\n\n```docker\n## 给应用 1 分钟启动时间\n\nHEALTHCHECK --start-period=60s CMD curl -f http://localhost/ || exit 1\n```\n\n#### 4. 只检查核心依赖\n\n健康检查应主要关注 **当前服务** 是否可用，而不是检查其下游依赖 (数据库等)。下游依赖的检查应由应用逻辑处理。\n\n---\n"
  },
  {
    "path": "07_dockerfile/7.13_onbuild.md",
    "content": "## 7.13 ONBUILD 为他人作嫁衣裳\n\n### 7.13.1 基本语法\n\n```docker\nONBUILD <其它指令>\n```\n\n`ONBUILD` 是一个特殊的指令，它后面跟的是其它指令 (如 `RUN`，`COPY` 等)，这些指令 **在当前镜像构建时不会执行**，只有当以当前镜像为基础镜像去构建下一级镜像时才会被执行。\n\n---\n\n### 7.13.2 为什么需要 ONBUILD\n\n`ONBUILD` 主要用于制作 **语言栈基础镜像** 或 **框架基础镜像**。\n\n#### 场景：维护 Node.js 项目\n\n假设你有多个 Node.js 项目，它们的构建流程都一样：\n\n1. 创建目录\n2. 复制 `package.json`\n3. 执行 `npm install`\n4. 复制源码\n5. 启动应用\n\n如果不使用 `ONBUILD`，每个项目的 Dockerfile 都要重复这些步骤，且通过 `COPY` 复制文件时，基础镜像无法预知子项目的文件名。\n\n#### 使用 ONBUILD 的解决方案\n\n**基础镜像 (my-node-base)**：\n\n```docker\nFROM node:20-alpine\nWORKDIR /app\n\n## 这些指令将在子镜像构建时执行\n\nONBUILD COPY package*.json ./\nONBUILD RUN npm install\nONBUILD COPY . .\n\nCMD [\"npm\", \"start\"]\n```\n\n**子项目 Dockerfile**：\n\n```docker\nFROM my-node-base\n## 只需要一行！\n\n## 构建时会自动执行 COPY 和 RUN\n\n...\n```\n\n---\n\n### 7.13.3 执行机制\n\n```bash\n基础镜像构建：\nDockerfile (含 ONBUILD) ──build──> 基础镜像 (记录了 ONBUILD 触发器)\n                                    (指令未执行)\n\n子镜像构建：\nFROM 基础镜像 ──build──> 读取基础镜像触发器 ──> 执行触发器指令 ──> 继续执行子 Dockerfile\n```\n\n---\n\n### 7.13.4 常见使用场景\n\n#### 1. 自动处理依赖安装\n\n```docker\n## Python 基础镜像\n\nONBUILD COPY requirements.txt ./\nONBUILD RUN pip install -r requirements.txt\n```\n\n#### 2. 自动编译代码\n\n```docker\n## Go 基础镜像\n\nONBUILD COPY . .\nONBUILD RUN go build -o app main.go\n```\n\n#### 3. 处理静态资源\n\n```docker\n## Nginx 静态网站基础镜像\n\nONBUILD COPY dist/ /usr/share/nginx/html/\n```\n\n---\n\n### 7.13.5 注意事项\n\n#### 1. 继承性限制\n\n`ONBUILD` 指令 **只会继承一次**。\n\n- 镜像 A (含 ONBUILD)\n- 镜像 B (FROM A) -> 触发 ONBUILD\n- 镜像 C (FROM B) -> **不会** 再次触发 ONBUILD\n\n#### 2. 构建上下文\n\n子镜像构建时，`ONBUILD COPY . .` 中的 `.` 指的是 **子项目** 的构建上下文，而不是基础镜像的上下文。\n\n#### 3. 不允许级联\n\n`ONBUILD ONBUILD` 是非法的。你不能写 `ONBUILD ONBUILD COPY ...`。\n\n#### 4. 可能会导致构建失败\n\n由于 `ONBUILD` 实际上是在子镜像中执行指令，如果子项目的上下文不满足要求 (例如缺少 `package.json`)，会导致子镜像构建失败，且错误信息可能比较隐晦。\n\n---\n\n### 7.13.6 最佳实践\n\n#### 1. 命名规范\n\n建议在镜像标签中添加 `-onbuild` 后缀，明确告知使用者该镜像包含触发器。\n\n```bash\nnode:20-onbuild\npython:3.12-onbuild\n```\n\n#### 2. 避免执行耗时操作\n\n尽量不要在 `ONBUILD` 中执行过于耗时或不确定的操作 (如更新系统软件)，这会让子镜像构建变得缓慢且不可控。\n\n#### 3. 清理工作\n\n如果 `ONBUILD` 指令产生了临时文件，最好在同一个指令链中清理，或者提供机制让子镜像清理。\n\n---\n"
  },
  {
    "path": "07_dockerfile/7.14_label.md",
    "content": "## 7.14 LABEL 为镜像添加元数据\n\n### 7.14.1 基本语法\n\n```docker\nLABEL <key>=<value> <key>=<value> ...\n```\n\n`LABEL` 指令以键值对的形式给镜像添加元数据。这些数据不会影响镜像的功能，但可以帮助用户理解镜像，或被自动化工具使用。\n\n---\n\n### 7.14.2 为什么需要 LABEL\n\n1. **版本管理**：记录版本号、构建时间、Git Commit ID\n2. **联系信息**：维护者邮箱、文档地址、支持渠道\n3. **自动化工具**：CI/CD 工具可以读取标签触发操作\n4. **许可证信息**：声明开源协议\n\n---\n\n### 7.14.3 基本用法\n\n#### 定义单个标签\n\n```docker\nLABEL version=\"1.0\"\nLABEL description=\"这是一个 Web 应用服务器\"\n```\n\n#### 定义多个标签：推荐\n\n```docker\nLABEL maintainer=\"user@example.com\" \\\n      version=\"1.2.0\" \\\n      description=\"My App Description\" \\\n      org.opencontainers.image.authors=\"Yeasy\"\n```\n\n> 💡 包含空格的值需要用引号括起来。\n\n---\n\n### 7.14.4 常用标签规范\n\n为了标准和互操作性，推荐使用 [OCI Image Format Specification](https://github.com/opencontainers/image-spec/blob/main/annotations.md#pre-defined-annotation-keys) 定义的标准标签：\n\n| 标签 Key | 说明 | 示例 |\n|----------|------|------|\n| `org.opencontainers.image.created` | 构建时间(RFC 3339) | `2024-01-01T00:00:00Z` |\n| `org.opencontainers.image.authors` | 作者/维护者 | `support@example.com` |\n| `org.opencontainers.image.url` | 项目主页 | `https://example.com` |\n| `org.opencontainers.image.documentation`| 文档地址 | `https://example.com/docs` |\n| `org.opencontainers.image.source` | 源码仓库 | `https://github.com/user/repo` |\n| `org.opencontainers.image.version` | 版本号 | `1.0.0` |\n| `org.opencontainers.image.licenses` | 许可证 | `MIT` |\n| `org.opencontainers.image.title` | 镜像标题 | `My App` |\n| `org.opencontainers.image.description` | 描述 | `Production ready web server` |\n\n#### 示例\n\n```docker\nLABEL org.opencontainers.image.authors=\"yeasy\" \\\n      org.opencontainers.image.documentation=\"https://yeasy.gitbooks.io\" \\\n      org.opencontainers.image.source=\"https://github.com/yeasy/docker_practice\" \\\n      org.opencontainers.image.licenses=\"MIT\"\n```\n\n---\n\n### 7.14.5 MAINTAINER 指令：已废弃\n\n旧版本的 Dockerfile 中常看到 `MAINTAINER` 指令：\n\n```docker\n## ❌ 已弃用\n\nMAINTAINER user@example.com\n```\n\n现在推荐使用 `LABEL`：\n\n```docker\n## ✅ 推荐\n\nLABEL maintainer=\"user@example.com\"\n## 或\n\nLABEL org.opencontainers.image.authors=\"user@example.com\"\n```\n\n---\n\n### 7.14.6 动态标签\n\n 配合 `ARG` 使用，可以在构建时动态注入标签：\n\n```docker\nARG BUILD_DATE\nARG VCS_REF\n\nLABEL org.opencontainers.image.created=$BUILD_DATE \\\n      org.opencontainers.image.revision=$VCS_REF\n```\n\n构建命令：\n\n```bash\n$ docker build \\\n  --build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ') \\\n  --build-arg VCS_REF=$(git rev-parse --short HEAD) \\\n  .\n```\n\n---\n\n### 7.14.7 查看标签\n\n#### docker inspect\n\n查看镜像的标签信息：\n\n```bash\n$ docker inspect nginx --format '{{json .Config.Labels}}' | jq\n{\n  \"maintainer\": \"NGINX Docker Maintainers <docker-maint@nginx.com>\"\n}\n```\n\n#### 过滤器\n\n可以使用标签过滤镜像：\n\n```bash\n## 列出作者是 yeasy 的所有镜像\n\n$ docker images --filter \"label=org.opencontainers.image.authors=yeasy\"\n\n## 删除所有带有特定标签的镜像\n\n$ docker rmi $(docker images -q --filter \"label=stage=builder\")\n```\n\n---\n"
  },
  {
    "path": "07_dockerfile/7.15_shell.md",
    "content": "## 7.15 SHELL 指令\n\n### 7.15.1 基本语法\n\n```docker\nSHELL [\"executable\", \"parameters\"]\n```\n\n`SHELL` 指令允许覆盖 Docker 默认的 shell。\n\n- **Linux 默认**：`[\"/bin/sh\", \"-c\"]`\n- **Windows 默认**：`[\"cmd\", \"/S\", \"/C\"]`\n\n该指令会影响后续的 `RUN`，`CMD`，`ENTRYPOINT` 指令 (当它们使用 shell 格式时)。\n\n---\n\n### 7.15.2 为什么要用 SHELL 指令\n\n#### 1. 使用 bash 特性\n\n默认的 `/bin/sh` (通常是 dash 或 alpine 的 ash) 功能有限。如果你需要使用 bash 的特有功能 (如数组、`{}` 扩展、`pipefail` 等)，可以切换 shell。\n\n```docker\nFROM ubuntu:24.04\n\n## 切换到 bash\n\nSHELL [\"/bin/bash\", \"-c\"]\n\n## 现在可以使用 bash 特性了\n\nRUN echo {a..z}\n```\n\n#### 2. 增强错误处理\n\n默认情况下，管道命令 `cmd1 | cmd2` 只要 `cmd2` 成功，整个指令就视为成功。这可能掩盖构建错误。\n\n```docker\n## ❌ 这里的 wget 失败了，但构建继续（因为 tar 成功了）\n\nRUN wget -O - https://invalid-url | tar xz\n```\n\n使用 `SHELL` 启用 `pipefail`：\n\n```docker\n## ✅ 启用 pipefail\n\nSHELL [\"/bin/bash\", \"-o\", \"pipefail\", \"-c\"]\n\n## 如果 wget 失败，整个 RUN 就会失败\n\nRUN wget -O - https://invalid-url | tar xz\n```\n\n#### 3. Windows 环境\n\n在 Windows 容器中，经常需要在 `cmd` 和 `powershell` 之间切换。\n\n```docker\nFROM mcr.microsoft.com/windows/servercore:ltsc2022\n\n## 默认是 cmd\n\nRUN echo Default shell is cmd\n\n## 切换到 powershell\n\nSHELL [\"powershell\", \"-command\"]\nRUN Write-Host \"Hello from PowerShell\"\n\n## 切回 cmd\n\nSHELL [\"cmd\", \"/S\", \"/C\"]\n```\n\n---\n\n### 7.15.3 作用范围\n\n`SHELL` 指令可以出现多次，每次只影响其后的指令：\n\n```docker\nFROM ubuntu:24.04\n\n## 使用默认 sh\n\nRUN echo \"Using sh\"\n\nSHELL [\"/bin/bash\", \"-c\"]\n## 使用 bash\n\nRUN echo \"Using bash\"\n\nSHELL [\"/bin/sh\", \"-c\"]\n## 回到 sh\n\nRUN echo \"Using sh again\"\n```\n\n---\n\n### 7.15.4 对其他指令的影响\n\n`SHELL` 影响的是所有使用 **shell 格式** 的指令：\n\n| 指令格式 | 是否受 SHELL 影响 |\n|---------|-------------------|\n| `RUN command` | ✅ 是 |\n| `RUN [\"exec\", \"param\"]` | ❌ 否 |\n| `CMD command` | ✅ 是 |\n| `CMD [\"exec\", \"param\"]` | ❌ 否 |\n| `ENTRYPOINT command` | ✅ 是 |\n| `ENTRYPOINT [\"exec\", \"param\"]` | ❌ 否 |\n\n---\n\n### 7.15.5 最佳实践\n\n#### 1. 推荐开启 pipefail\n\n对于使用 bash 的镜像，强烈建议开启 `pipefail`，以确保构建过程中的错误能被及时捕获。\n\n```docker\nSHELL [\"/bin/bash\", \"-o\", \"pipefail\", \"-c\"]\n```\n\n#### 2. 明确意图\n\n如果由于脚本需求必须更改 shell，最好在 Dockerfile 中显式声明，而不是依赖默认行为。\n\n#### 3. 尽量保持一致\n\n避免在 Dockerfile 中频繁切换 SHELL，这会使构建过程难以理解和调试。尽量在头部定义一次即可。\n\n---\n"
  },
  {
    "path": "07_dockerfile/7.16_references.md",
    "content": "## 7.16 参考文档\n\n### 官方文档\n\n* `Dockerfile` 官方参考手册：https://docs.docker.com/engine/reference/builder/\n\n* `Dockerfile` 最佳实践指南：https://docs.docker.com/develop/develop-images/dockerfile_best-practices/\n\n* `Docker` 官方镜像 `Dockerfile` 库：https://github.com/docker-library/docs\n\n### 常用指令总结\n\nDockerfile 中的常用指令包括：\n\n- **FROM**: 指定基础镜像，必须是第一条指令\n- **RUN**: 在镜像中执行命令，用于安装软件包等\n- **WORKDIR**: 设置工作目录\n- **COPY/ADD**: 复制文件到镜像中\n- **EXPOSE**: 声明容器监听的端口\n- **ENV**: 设置环境变量\n- **ENTRYPOINT**: 容器启动时的入口点\n- **CMD**: 容器默认执行的命令\n\n### 最佳实践建议\n\n1. 使用具体的基础镜像版本标签而非 latest\n2. 最小化镜像层数，合并 RUN 指令\n3. 使用 .dockerignore 文件排除不必要的文件\n4. 安装必要的软件包后清理缓存\n5. 使用多阶段构建减小最终镜像体积\n6. 避免以 root 身份运行容器应用\n\n### 相关资源\n\n- Docker 官方镜像库：https://hub.docker.com/\n- Docker 镜像构建最佳实践：https://docs.docker.com/build/building/best-practices/\n"
  },
  {
    "path": "07_dockerfile/7.17_multistage_builds.md",
    "content": "## 7.17 多阶段构建\n\n在 Docker 17.05 版本之前，我们构建 Docker 镜像时，通常会采用两种方式：\n\n### 7.17.1 全部放入一个 Dockerfile\n\n一种方式是将所有的构建过程包含在一个 `Dockerfile` 中，包括项目及其依赖库的编译、测试、打包等流程，这里可能会带来的一些问题：\n\n  * 镜像层次多，镜像体积较大，部署时间变长\n\n  * 源代码存在泄露的风险\n\n例如，编写 `app.go` 文件，该程序输出 `Hello World!`\n\n```go\npackage main\n\nimport \"fmt\"\n\nfunc main(){\n    fmt.Printf(\"Hello World!\");\n}\n```\n\n编写 `Dockerfile.one` 文件\n\n```docker\nFROM golang:alpine\n\nRUN apk --no-cache add git ca-certificates\n\nWORKDIR /go/src/github.com/go/helloworld/\n\nCOPY app.go .\n\nRUN go mod init helloworld \\\n  && go get -d -v github.com/go-sql-driver/mysql \\\n  && CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app . \\\n  && cp /go/src/github.com/go/helloworld/app /root\n\nWORKDIR /root/\n\nCMD [\"./app\"]\n```\n\n构建镜像\n\n```bash\n$ docker build -t go/helloworld:1 -f Dockerfile.one .\n```\n\n### 7.17.2 分散到多个 Dockerfile\n\n另一种方式，就是我们事先在一个 `Dockerfile` 将项目及其依赖库编译测试打包好后，再将其拷贝到运行环境中，这种方式需要我们编写两个 `Dockerfile` 和一些编译脚本才能将其两个阶段自动整合起来，这种方式虽然可以很好地规避第一种方式存在的风险，但明显部署过程较复杂。\n\n例如，编写 `Dockerfile.build` 文件\n\n```docker\nFROM golang:alpine\n\nRUN apk --no-cache add git\n\nWORKDIR /go/src/github.com/go/helloworld\n\nCOPY app.go .\n\nRUN go get -d -v github.com/go-sql-driver/mysql \\\n  && CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .\n```\n\n编写 `Dockerfile.copy` 文件\n\n```docker\nFROM alpine:latest\n\nRUN apk --no-cache add ca-certificates\n\nWORKDIR /root/\n\nCOPY app .\n\nCMD [\"./app\"]\n```\n\n新建 `build.sh`\n\n```bash\n#!/bin/sh\necho Building go/helloworld:build\n\ndocker build -t go/helloworld:build . -f Dockerfile.build\n\ndocker create --name extract go/helloworld:build\ndocker cp extract:/go/src/github.com/go/helloworld/app ./app\ndocker rm -f extract\n\necho Building go/helloworld:2\n\ndocker build --no-cache -t go/helloworld:2 . -f Dockerfile.copy\nrm ./app\n```\n\n现在运行脚本即可构建镜像\n\n```bash\n$ chmod +x build.sh\n\n$ ./build.sh\n```\n\n对比两种方式生成的镜像大小\n\n```bash\n$ docker image ls\n\nREPOSITORY      TAG    IMAGE ID        CREATED         SIZE\ngo/helloworld   2      f7cf3465432c    22 seconds ago  6.47MB\ngo/helloworld   1      f55d3e16affc    2 minutes ago   295MB\n```\n\n### 7.17.3 使用多阶段构建\n\n为解决以上问题，Docker v17.05 开始支持多阶段构建 (`multistage builds`)。使用多阶段构建我们就可以很容易解决前面提到的问题，并且只需要编写一个 `Dockerfile`：\n\n例如，编写 `Dockerfile` 文件\n\n```docker\nFROM golang:alpine as builder\n\nRUN apk --no-cache add git\n\nWORKDIR /go/src/github.com/go/helloworld/\n\nRUN go get -d -v github.com/go-sql-driver/mysql\n\nCOPY app.go .\n\nRUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .\n\nFROM alpine:latest as prod\n\nRUN apk --no-cache add ca-certificates\n\nWORKDIR /root/\n\nCOPY --from=0 /go/src/github.com/go/helloworld/app .\n\nCMD [\"./app\"]\n```\n\n构建镜像\n\n```bash\n$ docker build -t go/helloworld:3 .\n```\n\n对比三个镜像大小\n\n```bash\n$ docker image ls\n\nREPOSITORY        TAG   IMAGE ID         CREATED            SIZE\ngo/helloworld     3     d6911ed9c846     7 seconds ago      6.47MB\ngo/helloworld     2     f7cf3465432c     22 seconds ago     6.47MB\ngo/helloworld     1     f55d3e16affc     2 minutes ago      295MB\n```\n\n很明显使用多阶段构建的镜像体积小，同时也完美解决了上边提到的问题。\n\n### 7.17.4 只构建某一阶段的镜像\n\n我们可以使用 `as` 来为某一阶段命名，例如\n\n```docker\nFROM golang:alpine as builder\n```\n\n例如当我们只想构建 `builder` 阶段的镜像时，增加 `--target=builder` 参数即可\n\n```bash\n$ docker build --target builder -t username/imagename:tag .\n```\n\n### 7.17.5 构建时从其他镜像复制文件\n\n上面例子中我们使用 `COPY --from=0 /go/src/github.com/go/helloworld/app .` 从上一阶段的镜像中复制文件，我们也可以复制任意镜像中的文件。\n\n```docker\nCOPY --from=nginx:latest /etc/nginx/nginx.conf /nginx.conf\n```\n"
  },
  {
    "path": "07_dockerfile/7.18_multistage_builds_laravel.md",
    "content": "## 7.18 实战多阶段构建 Laravel 镜像\n\n> 本节适用于 PHP 开发者阅读。`Laravel` 基于 8.x 版本，各个版本的文件结构可能会有差异，请根据实际自行修改。\n\n### 7.18.1 准备\n\n新建一个 `Laravel` 项目或在已有的 `Laravel` 项目根目录下新建 `Dockerfile` `.dockerignore` `laravel.conf` 文件。\n\n在 `.dockerignore` 文件中写入以下内容。\n\n```bash\n.idea/\n.git/\n\nvendor/\n\nnode_modules/\n\npublic/js/\npublic/css/\npublic/mix-manifest.json\n\nyarn-error.log\n\nbootstrap/cache/*\nstorage/\n\n## 自行添加其他需要排除的文件，例如 .env.* 文件\n\n...\n```\n\n在 `laravel.conf` 文件中写入 nginx 配置。\n\n```nginx\nserver {\n  listen 80 default_server;\n  root /app/laravel/public;\n  index index.php index.html;\n\n  location / {\n      try_files $uri $uri/ /index.php?$query_string;\n  }\n\n  location ~ .*\\.php(\\/.*)*$ {\n    fastcgi_pass   laravel:9000;\n    include        fastcgi.conf;\n\n    # fastcgi_connect_timeout 300;\n\n    # fastcgi_send_timeout 300;\n\n    # fastcgi_read_timeout 300;\n\n  }\n}\n```\n\n### 7.18.2 前端构建\n\n第一阶段进行前端构建。\n\n```docker\nFROM node:alpine as frontend\n\nCOPY package.json /app/\n\nRUN set -x ; cd /app \\\n      && npm install --registry=https://registry.npmmirror.com\n\nCOPY webpack.mix.js webpack.config.js tailwind.config.js /app/\nCOPY resources/ /app/resources/\n\nRUN set -x ; cd /app \\\n      && touch artisan \\\n      && mkdir -p public \\\n      && npm run production\n```\n\n### 7.18.3 安装 Composer 依赖\n\n第二阶段安装 Composer 依赖。\n\n```docker\nFROM composer as composer\n\nCOPY database/ /app/database/\nCOPY composer.json composer.lock /app/\n\nRUN set -x ; cd /app \\\n      && composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/ \\\n      && composer install \\\n           --ignore-platform-reqs \\\n           --no-interaction \\\n           --no-plugins \\\n           --no-scripts \\\n           --prefer-dist\n```\n\n### 7.18.4 整合以上阶段所生成的文件\n\n第三阶段对以上阶段生成的文件进行整合。\n\n```docker\nFROM php:7.4-fpm-alpine as laravel\n\nARG LARAVEL_PATH=/app/laravel\n\nCOPY --from=composer /app/vendor/ ${LARAVEL_PATH}/vendor/\nCOPY . ${LARAVEL_PATH}\nCOPY --from=frontend /app/public/js/ ${LARAVEL_PATH}/public/js/\nCOPY --from=frontend /app/public/css/ ${LARAVEL_PATH}/public/css/\nCOPY --from=frontend /app/public/mix-manifest.json ${LARAVEL_PATH}/public/mix-manifest.json\n\nRUN set -x ; cd ${LARAVEL_PATH} \\\n      && mkdir -p storage \\\n      && mkdir -p storage/framework/cache \\\n      && mkdir -p storage/framework/sessions \\\n      && mkdir -p storage/framework/testing \\\n      && mkdir -p storage/framework/views \\\n      && mkdir -p storage/logs \\\n      && chmod -R 777 storage \\\n      && php artisan package:discover\n```\n\n### 7.18.5 最后一个阶段构建 NGINX 镜像\n\n```docker\nFROM nginx:alpine as nginx\n\nARG LARAVEL_PATH=/app/laravel\n\nCOPY laravel.conf /etc/nginx/conf.d/\nCOPY --from=laravel ${LARAVEL_PATH}/public ${LARAVEL_PATH}/public\n```\n\n### 7.18.6 构建 Laravel 及 Nginx 镜像\n\n使用 `docker build` 命令构建镜像。\n\n```bash\n$ docker build -t my/laravel --target=laravel .\n\n$ docker build -t my/nginx --target=nginx .\n```\n\n### 7.18.7 启动容器并测试\n\n新建 Docker 网络\n\n```bash\n$ docker network create laravel\n```\n\n启动 laravel 容器，`--name=laravel` 参数设定的名字必须与 `nginx` 配置文件中的 `fastcgi_pass   laravel:9000;` 一致\n\n```bash\n$ docker run -dit --rm --name=laravel --network=laravel my/laravel\n```\n\n启动 nginx 容器\n\n```bash\n$ docker run -dit --rm --network=laravel -p 8080:80 my/nginx\n```\n\n浏览器访问 `127.0.0.1:8080` 可以看到 Laravel 项目首页。\n\n> 也许 Laravel 项目依赖其他外部服务，例如 redis、MySQL，请自行启动这些服务之后再进行测试，本小节不再赘述。\n\n### 7.18.8 生产环境优化\n\n本小节内容为了方便测试，将配置文件直接放到了镜像中，实际在使用时 **建议** 将配置文件作为 `config` 或 `secret` 挂载到容器中，请读者自行学习 `Kubernetes` 的相关内容。\n\n由于篇幅所限本小节只是简单列出，更多内容可以参考 [khs1994-docker/laravel-demo](https://github.com/khs1994-docker/laravel-demo) 项目。\n\n### 7.18.9 附录\n\n完整的 `Dockerfile` 文件如下。\n\n```docker\nFROM node:alpine as frontend\n\nCOPY package.json /app/\n\nRUN set -x ; cd /app \\\n      && npm install --registry=https://registry.npmmirror.com\n\nCOPY webpack.mix.js webpack.config.js tailwind.config.js /app/\nCOPY resources/ /app/resources/\n\nRUN set -x ; cd /app \\\n      && touch artisan \\\n      && mkdir -p public \\\n      && npm run production\n\nFROM composer as composer\n\nCOPY database/ /app/database/\nCOPY composer.json /app/\n\nRUN set -x ; cd /app \\\n      && composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/ \\\n      && composer install \\\n           --ignore-platform-reqs \\\n           --no-interaction \\\n           --no-plugins \\\n           --no-scripts \\\n           --prefer-dist\n\nFROM php:7.4-fpm-alpine as laravel\n\nARG LARAVEL_PATH=/app/laravel\n\nCOPY --from=composer /app/vendor/ ${LARAVEL_PATH}/vendor/\nCOPY . ${LARAVEL_PATH}\nCOPY --from=frontend /app/public/js/ ${LARAVEL_PATH}/public/js/\nCOPY --from=frontend /app/public/css/ ${LARAVEL_PATH}/public/css/\nCOPY --from=frontend /app/public/mix-manifest.json ${LARAVEL_PATH}/public/mix-manifest.json\n\nRUN set -x ; cd ${LARAVEL_PATH} \\\n      && mkdir -p storage \\\n      && mkdir -p storage/framework/cache \\\n      && mkdir -p storage/framework/sessions \\\n      && mkdir -p storage/framework/testing \\\n      && mkdir -p storage/framework/views \\\n      && mkdir -p storage/logs \\\n      && chmod -R 777 storage \\\n      && php artisan package:discover\n\nFROM nginx:alpine as nginx\n\nARG LARAVEL_PATH=/app/laravel\n\nCOPY laravel.conf /etc/nginx/conf.d/\nCOPY --from=laravel ${LARAVEL_PATH}/public ${LARAVEL_PATH}/public\n```\n"
  },
  {
    "path": "07_dockerfile/7.1_run.md",
    "content": "## 7.1 RUN 执行命令\n\n### 7.1.1 基本语法\n\n```docker\nRUN <command>\nRUN [\"executable\", \"param1\", \"param2\"]\n```\n\n`RUN` 指令是 Dockerfile 中最常用的指令之一。它在 **当前镜像层** 之上创建一个新层，执行指定的命令，并提交结果。\n\n---\n\n### 7.1.2 两种格式对比\n\n#### 1. Shell 格式\n\n```docker\nRUN apt-get update\n```\n\n- **特点**：默认通过 `/bin/sh -c` 执行。\n- **优势**：可以使用环境变量、管道、重定向等 Shell 特性。\n- **示例**：\n  ```docker\n  RUN echo \"Hello\" > /test.txt\n  ```\n\n#### 2. Exec 格式\n\n```docker\nRUN [\"apt-get\", \"update\"]\n```\n\n- **特点**：直接调用可执行文件，不经过 Shell。\n- **优势**：避免 Shell 字符串解析问题，适用于参数中包含特殊字符的情况。\n- **注意**：无法使用 `$VAR` 环境变量替换 (除非显式调用 shell)。\n\n---\n\n### 7.1.3 常见最佳实践\n\n#### 1. 组合命令：减少层数\n\n每一个 `RUN` 指令都会新建一层镜像。为了减少镜像体积和层数，应使用 `&&` 连接命令。\n\n**❌ 糟糕的写法** (创建 3 层)：\n\n```docker\nRUN apt-get update\nRUN apt-get install -y nginx\nRUN rm -rf /var/lib/apt/lists/*\n```\n\n**✅ 推荐写法** (创建 1 层)：\n\n```docker\nRUN apt-get update && \\\n    apt-get install -y nginx && \\\n    rm -rf /var/lib/apt/lists/*\n```\n\n#### 2. 清理缓存\n\n在安装完软件后，立即清除缓存，可以显著减小镜像体积。\n\n- **Debian/Ubuntu**:\n  ```docker\n  RUN apt-get update && apt-get install -y package-bar \\\n      && rm -rf /var/lib/apt/lists/*\n  ```\n\n- **Alpine**:\n  ```docker\n  RUN apk add --no-cache package-bar\n  ```\n\n#### 3. 使用 `set -e` 和 `pipefail`\n\n默认情况下，管道命令 `cmd1 | cmd2` 只要 `cmd2` 成功，整个 `RUN` 就视为成功。\n\n**❌ 隐蔽的错误**：\n\n```docker\n## 如果下载失败，gzip 可能会报错，但如果不影响后续，构建可能继续\n\nRUN wget http://error-url | gzip -d > file\n```\n\n**✅ 推荐写法**：\n\n```docker\nSHELL [\"/bin/bash\", \"-o\", \"pipefail\", \"-c\"]\nRUN wget http://url | gzip -d > file\n```\n\n---\n\n### 7.1.4 常见问题\n\n#### Q：为什么 `RUN cd /app` 不生效？\n\n```docker\nRUN cd /app\nRUN touch hello.txt\n```\n\n**结果**：`hello.txt` 会出现在根目录 `/`，而不是 `/app`。**原因**：每个 `RUN` 都在一个新的 Shell/容器环境中执行。`cd` 只影响当前 `RUN` 的环境。**解决**：使用 `WORKDIR` 指令。\n\n```docker\nWORKDIR /app\nRUN touch hello.txt\n```\n\n#### Q：环境变量不生效？\n\n```docker\nRUN export MY_VAR=hello\nRUN echo $MY_VAR\n```\n\n**结果**：输出为空。**原因**：同上，环境变量只在当前 `RUN` 有效。**解决**：使用 `ENV` 指令，或在同一行 `RUN` 中导出。\n\n```docker\nENV MY_VAR=hello\nRUN echo $MY_VAR\n```\n\n---\n\n### 7.1.5 高级技巧\n\n#### 1. 使用 BuildKit 的挂载缓存\n\nBuildKit 支持在 `RUN` 指令中使用 `--mount` 挂载缓存，加速构建。\n\n```docker\n## 缓存 apt 包\n\nRUN --mount=type=cache,target=/var/cache/apt,sharing=locked \\\n    --mount=type=cache,target=/var/lib/apt,sharing=locked \\\n    apt-get update && apt-get install -y gcc\n```\n\n```docker\n## 缓存 Go 模块\n\nRUN --mount=type=cache,target=/go/pkg/mod \\\n    go build -o app\n```\n\n#### 2. 挂载密钥\n\n安全地使用 SSH 密钥或 Token，而不将其记录在镜像中。\n\n```docker\nRUN --mount=type=secret,id=mysecret \\\n    cat /run/secrets/mysecret\n```\n\n---\n"
  },
  {
    "path": "07_dockerfile/7.2_copy.md",
    "content": "## 7.2 COPY 复制文件\n\n### 7.2.1 基本语法\n\n```docker\nCOPY [选项] <源路径>... <目标路径>\nCOPY [选项] [\"<源路径1>\", \"<源路径2>\", ... \"<目标路径>\"]\n```\n\n`COPY` 指令将构建上下文中的文件或目录复制到镜像内。\n\n---\n\n### 7.2.2 基本用法\n\n#### 复制单个文件\n\n```docker\n## 复制文件到指定目录\n\nCOPY package.json /app/\n\n## 复制文件并重命名\n\nCOPY config.json /app/settings.json\n```\n\n#### 复制多个文件\n\n```docker\n## 复制多个指定文件\n\nCOPY package.json package-lock.json /app/\n\n## 使用通配符\n\nCOPY *.json /app/\nCOPY src/*.js /app/src/\n```\n\n#### 复制目录\n\n```docker\n## 复制整个目录的内容（不是目录本身）\n\nCOPY src/ /app/src/\n```\n\n> ⚠️ **注意**：复制目录时，复制的是目录的 **内容**，不包含目录本身。\n\n```bash\n构建上下文：              镜像内：\nsrc/                     /app/src/\n├── index.js      →      ├── index.js\n└── utils.js             └── utils.js\n```\n\n---\n\n### 7.2.3 通配符规则\n\nCOPY 支持 Go 的 `filepath.Match` 通配符规则：\n\n| 通配符 | 说明 | 示例 |\n|--------|------|------|\n| `*` | 匹配任意字符序列 | `*.json` |\n| `?` | 匹配单个字符 | `config?.json` |\n| `[abc]` | 匹配括号内任一字符 | `[abc].txt` |\n| `[a-z]` | 匹配范围内字符 | `file[0-9].txt` |\n\n```docker\nCOPY hom* /mydir/       # home.txt, homework.md 等\nCOPY hom?.txt /mydir/   # home.txt, homy.txt 等\nCOPY app[0-9].js /app/  # app0.js ~ app9.js\n```\n\n---\n\n### 7.2.4 目标路径\n\n#### 绝对路径\n\n```docker\nCOPY app.js /usr/src/app/\n```\n\n#### 相对路径：基于 WORKDIR\n\n```docker\nWORKDIR /app\nCOPY package.json ./        # 复制到 /app/package.json\nCOPY src/ ./src/            # 复制到 /app/src/\n```\n\n#### 自动创建目录\n\n如果目标目录不存在，Docker 会自动创建：\n\n```docker\n## /app/config/ 不存在也会自动创建\n\nCOPY settings.json /app/config/\n```\n\n---\n\n### 7.2.5 修改文件所有者\n\n使用 `--chown` 选项设置文件的用户和组：\n\n```docker\n## 使用用户名和组名\n\nCOPY --chown=node:node package.json /app/\n\n## 使用 UID 和 GID\n\nCOPY --chown=1000:1000 . /app/\n\n## 只指定用户\n\nCOPY --chown=node . /app/\n```\n\n> 💡 结合 `USER` 指令使用，确保应用以非 root 用户运行。\n\n---\n\n### 7.2.6 保留文件元数据\n\nCOPY 会保留源文件的元数据：\n\n- 读、写、执行权限\n- 修改时间\n\n这对于脚本文件特别重要：\n\n```docker\n## start.sh 的可执行权限会被保留\n\nCOPY start.sh /app/\n```\n\n---\n\n### 7.2.7 COPY vs ADD\n\n| 特性 | COPY | ADD |\n|------|------|-----|\n| 复制本地文件 | ✅ | ✅ |\n| 自动解压 tar | ❌ | ✅ |\n| 支持 URL | ❌ | ✅ (不推荐)|\n| 推荐程度 | ✅ **推荐** | ⚠️ 特殊场景使用 |\n\n```docker\n## 推荐：使用 COPY\n\nCOPY app.tar.gz /app/\nRUN tar -xzf /app/app.tar.gz\n\n## ADD 会自动解压（行为不明显，不推荐）\n\nADD app.tar.gz /app/\n```\n\n> 笔者建议：除非需要自动解压 tar 文件，否则始终使用 COPY。明确的行为比隐式的魔法更好。\n\n---\n\n### 7.2.8 多阶段构建中的 COPY\n\n#### 从其他构建阶段复制\n\n```docker\n## 构建阶段\n\nFROM node:20 AS builder\nWORKDIR /app\nCOPY package*.json ./\nRUN npm install\nCOPY . .\nRUN npm run build\n\n## 生产阶段\n\nFROM nginx:alpine\nCOPY --from=builder /app/dist /usr/share/nginx/html\n```\n\n#### 使用 --link 优化缓存\n\n```docker\n## 使用 --link 后，文件以独立层添加，不依赖前序指令\n\nCOPY --link --from=builder /app/dist /usr/share/nginx/html\n```\n\n`--link` 的优势：\n\n- 更高效利用构建缓存\n- 并行化构建过程\n- 加速多阶段构建\n\n---\n\n### 7.2.9 dockerignore\n\n使用 `.dockerignore` 排除不需要复制的文件：\n\n```text\n## .dockerignore\n\nnode_modules\n.git\n.env\n*.log\nDockerfile\n.dockerignore\n```\n\n这可以：\n\n- 减小构建上下文大小\n- 加速构建\n- 避免复制敏感文件\n\n---\n\n### 7.2.10 最佳实践\n\n#### 1. 利用缓存，先复制依赖文件\n\n```docker\n## ✅ 好：先复制依赖定义，再安装，最后复制代码\n\nCOPY package.json package-lock.json ./\nRUN npm install\nCOPY . .\n\n## ❌ 差：一次性复制所有文件，代码变更会导致重新 npm install\n\nCOPY . .\nRUN npm install\n```\n\n#### 2. 使用 .dockerignore\n\n```docker\n## 确保 node_modules 不被复制\n\nCOPY . .\n## .dockerignore 中应包含 node_modules\n\n...\n```\n\n#### 3. 明确复制路径\n\n```docker\n## ✅ 好：明确的路径\n\nCOPY src/ /app/src/\nCOPY package.json /app/\n\n## ❌ 差：过于宽泛\n\nCOPY . .\n```\n\n---\n\n> **🔥 踩坑实录**\n>\n> 某公司在优化 Node.js 应用的 Docker 镜像时，发现构建出来的镜像体积超过了 2GB，远远超过生产部署的需求。排查发现，Dockerfile 中使用了 `COPY . .` 把整个构建上下文复制进镜像，导致 `node_modules/`、`.git/` 目录和大量测试数据全部被打包进镜像。最初他们没有创建 `.dockerignore` 文件，默认会复制所有文件。解决方案是添加一个 `.dockerignore` 文件，排除这些不必要的目录，使镜像缩小到了 200MB。这个教训深刻地说明了：`.dockerignore` 和 `.gitignore` 一样重要，应该在项目初始化时就创建，而不是等到出现问题时才想起来。建议的标准做法是先复制 `package.json` 和 `package-lock.json` 安装依赖，再复制应用代码，同时在 `.dockerignore` 中明确列出 `node_modules`、`.git`、test 目录等。\n"
  },
  {
    "path": "07_dockerfile/7.3_add.md",
    "content": "## 7.3 ADD 更高级的复制文件\n\n### 7.3.1 基本语法\n\n```docker\nADD [选项] <源路径>... <目标路径>\nADD [选项] [\"<源路径>\", ... \"<目标路径>\"]\n```\n\n`ADD` 在 `COPY` 基础上增加了两个功能：\n\n1. 自动解压 tar 压缩包\n2. 支持从 URL 下载文件 (不推荐)\n\n---\n\n### 7.3.2 ADD vs COPY\n\n| 特性 | COPY | ADD |\n|------|------|-----|\n| 复制本地文件 | ✅ | ✅ |\n| 自动解压 tar | ❌ | ✅ |\n| 支持 URL | ❌ | ✅ (不推荐)|\n| 行为可预测性 | ✅ 高 | ⚠️ 低 |\n| 推荐程度 | ✅ **优先使用** | 仅解压场景 |\n\n> 笔者建议：除非需要自动解压 tar 文件，否则始终使用 COPY。明确的行为比隐式的魔法更好。\n\n---\n\n### 7.3.3 自动解压功能\n\n#### 基本用法：自动解压本地 tar\n\n```docker\n## 自动解压 tar.gz 到目标目录\n\nADD app.tar.gz /app/\n```\n\nADD 会识别并解压以下格式：\n\n- `.tar`\n- `.tar.gz` / `.tgz`\n- `.tar.bz2` / `.tbz2`\n- `.tar.xz` / `.txz`\n\n#### 实际应用\n\n官方基础镜像通常使用 ADD 解压根文件系统：\n\n```docker\nFROM scratch\nADD ubuntu-noble-core-cloudimg-amd64-root.tar.gz /\n```\n\n#### 解压过程\n\n```bash\nADD app.tar.gz /app/\n        │\n        ├─ 识别 .tar.gz 格式\n        ├─ 自动解压\n        └─ 内容放入 /app/\n\napp.tar.gz 包含：        /app/ 目录结果：\n├── src/                 ├── src/\n│   └── main.py          │   └── main.py\n└── config.json          └── config.json\n```\n\n---\n\n### 7.3.4 URL 下载功能：不推荐\n\n#### 基本用法\n\n```docker\n## 从 URL 下载文件\n\nADD https://example.com/app.zip /app/app.zip\n```\n\n#### 为什么不推荐\n\n| 问题 | 说明 |\n|------|------|\n| 权限固定 | 下载的文件权限为 600，通常需要额外 RUN 修改 |\n| 不会解压 | URL 下载的压缩包不会自动解压 |\n| 缓存问题 | URL 内容变化时不会重新下载 |\n| 层数增加 | 需要额外 RUN 清理 |\n\n#### 推荐替代方案\n\n```docker\n## ❌ 不推荐：使用 ADD 下载\n\nADD https://example.com/app.tar.gz /tmp/\nRUN tar -xzf /tmp/app.tar.gz -C /app && rm /tmp/app.tar.gz\n\n## ✅ 推荐：使用 RUN + curl\n\nRUN curl -fsSL https://example.com/app.tar.gz | tar -xz -C /app\n```\n\n优势：\n\n- 一条 RUN 完成下载、解压、清理\n- 减少镜像层数\n- 更清晰的构建意图\n\n---\n\n### 7.3.5 修改文件所有者\n\n```docker\nADD --chown=node:node app.tar.gz /app/\nADD --chown=1000:1000 files/ /app/\n```\n\n---\n\n### 7.3.6 何时使用 ADD\n\n#### ✅ 适合使用 ADD\n\n```docker\n## 解压本地 tar 文件\n\nFROM scratch\nADD rootfs.tar.gz /\n\n## 解压应用包\n\nADD dist.tar.gz /app/\n```\n\n#### ❌ 不适合使用 ADD\n\n```docker\n## 复制普通文件（用 COPY）\n\nADD package.json /app/          # ❌\nCOPY package.json /app/         # ✅\n\n## 下载文件（用 RUN + curl）\n\nADD https://example.com/file /  # ❌\nRUN curl -fsSL ... -o /file     # ✅\n\n## 需要保留 tar 不解压（用 COPY）\n\nADD archive.tar.gz /archives/   # ❌ 会解压\nCOPY archive.tar.gz /archives/  # ✅ 保持原样\n```\n\n---\n\n### 7.3.7 缓存行为\n\nADD 可能导致构建缓存失效：\n\n```docker\n## 如果 app.tar.gz 内容变化，此层及后续层都需重建\n\nADD app.tar.gz /app/\nRUN npm install\n```\n\n**优化建议**：\n\n```docker\n## 先复制依赖文件\n\nCOPY package*.json /app/\nRUN npm install\n\n## 再添加应用代码\n\nADD app.tar.gz /app/\n```\n\n---\n\n### 7.3.8 最佳实践\n\n#### 1. 默认使用 COPY\n\n```docker\n## ✅ 大多数场景使用 COPY\n\nCOPY . /app/\n```\n\n#### 2. 仅在需要解压时使用 ADD\n\n```docker\n## ✅ 自动解压场景\n\nADD app.tar.gz /app/\n```\n\n#### 3. 不要用 ADD 下载文件\n\n```docker\n## ❌ 避免\n\nADD https://example.com/file.tar.gz /tmp/\n\n## ✅ 推荐\n\nRUN curl -fsSL https://example.com/file.tar.gz | tar -xz -C /app\n```\n\n#### 4. 解压后清理\n\n```docker\n## 如果需要控制解压过程\n\nCOPY app.tar.gz /tmp/\nRUN tar -xzf /tmp/app.tar.gz -C /app && \\\n    rm /tmp/app.tar.gz\n```\n\n---\n"
  },
  {
    "path": "07_dockerfile/7.4_cmd.md",
    "content": "## 7.4 CMD 容器启动命令\n\n### 7.4.1 什么是 CMD\n\n`CMD` 指令用于指定容器启动时默认执行的命令。它定义了容器的 “主进程”。\n\n> **核心概念**：容器的生命周期 = 主进程的生命周期。CMD 指定的命令就是这个主进程。\n\n---\n\n### 7.4.2 语法格式\n\nCMD 有三种格式：\n\n| 格式 | 语法 | 推荐程度 |\n|------|------|---------|\n| **exec 格式**| `CMD [“可执行文件”, “参数1”, “参数2”]` | ✅**推荐** |\n| **shell 格式** | `CMD 命令 参数1 参数2` | ⚠️ 简单场景 |\n| **参数格式** | `CMD [“参数1”, “参数2”]` | 配合 ENTRYPOINT |\n\n#### exec 格式：推荐\n\n```docker\nCMD [\"nginx\", \"-g\", \"daemon off;\"]\nCMD [\"python\", \"app.py\"]\nCMD [\"node\", \"server.js\"]\n```\n\n**优点**：\n\n- 直接执行指定程序，是容器的 PID 1\n- 正确接收信号 (如 SIGTERM)\n- 无需 shell 解析\n\n#### shell 格式\n\n```docker\nCMD echo \"Hello World\"\nCMD nginx -g \"daemon off;\"\n```\n\n**实际执行**：会被包装为 `sh -c`\n\n```docker\n## 你写的\n\nCMD echo $HOME\n\n## 实际执行的\n\nCMD [\"sh\", \"-c\", \"echo $HOME\"]\n```\n\n**优点**：可以使用环境变量、管道等 shell 特性 **缺点**：主进程是 sh，信号无法正确传递给应用\n\n---\n\n### 7.4.3 exec 格式 vs shell 格式\n\n| 特性 | exec 格式 | shell 格式 |\n|------|----------|-----------|\n| 主进程 | 指定的程序 | `/bin/sh` |\n| 信号传递 | ✅ 正确 | ❌ 无法传递 |\n| 环境变量 | ❌ 需要 shell 包装 | ✅ 自动解析 |\n| 推荐使用 | ✅ 大多数场景 | 需要 shell 特性时 |\n\n#### 信号传递问题示例\n\n```docker\n## ❌ shell 格式：docker stop 会超时\n\nCMD node server.js\n## 实际是 sh -c \"node server.js\"\n\n## SIGTERM 发给 sh，不会传递给 node\n\n## ✅ exec 格式：docker stop 正常工作\n\nCMD [\"node\", \"server.js\"]\n## SIGTERM 直接发给 node\n\n...\n```\n\n---\n\n### 7.4.4 运行时覆盖 CMD\n\n`docker run` 后的命令会覆盖 Dockerfile 中的 CMD：\n\n```bash\n## ubuntu 默认 CMD 是 /bin/bash\n\n$ docker run -it ubuntu        # 进入 bash\n$ docker run ubuntu cat /etc/os-release  # 覆盖为 cat 命令\n```\n\n```bash\nDockerfile:              docker run 命令:\nCMD [\"/bin/bash\"]   +    cat /etc/os-release\n        │                        │\n        └───────► 被覆盖 ◄───────┘\n                    ↓\n           执行: cat /etc/os-release\n```\n\n---\n\n### 7.4.5 经典错误：容器立即退出\n\n#### 错误示例\n\n```docker\n## ❌ 容器启动后立即退出\n\nCMD service nginx start\n```\n\n#### 原因分析\n\n```bash\n1. CMD service nginx start\n   ↓ 被转换为\n2. CMD [\"sh\", \"-c\", \"service nginx start\"]\n   ↓\n3. sh 启动，执行 service 命令\n   ↓\n4. service 命令将 nginx 放到后台\n   ↓\n5. service 命令结束，sh 退出\n   ↓\n6. 容器主进程（sh）退出 → 容器停止\n```\n\n#### 正确做法\n\n```docker\n## ✅ 让 nginx 在前台运行\n\nCMD [\"nginx\", \"-g\", \"daemon off;\"]\n```\n\n---\n\n### 7.4.6 CMD vs ENTRYPOINT\n\n| 指令 | 用途 | 运行时行为 |\n|------|------|-----------|\n| **CMD**| 默认命令 | `docker run` 参数会 **覆盖** 它 |\n| **ENTRYPOINT**| 入口点 | `docker run` 参数会 **追加** 到它后面 |\n\n#### 单独使用 CMD\n\n```docker\n## Dockerfile\n\nCMD [\"curl\", \"-s\", \"http://example.com\"]\n```\n\n```bash\n$ docker run myimage              # 执行默认命令\n$ docker run myimage curl -v ...  # 完全覆盖\n```\n\n#### 搭配 ENTRYPOINT\n\n```docker\n## Dockerfile\n\nENTRYPOINT [\"curl\", \"-s\"]\nCMD [\"http://example.com\"]\n```\n\n```bash\n$ docker run myimage              # curl -s http://example.com\n$ docker run myimage http://other.com  # curl -s http://other.com（参数覆盖）\n```\n\n详见 [ENTRYPOINT 入口点](7.5_entrypoint.md)章节。\n\n---\n\n### 7.4.7 最佳实践\n\n#### 1. 优先使用 exec 格式\n\n```docker\n## ✅ 推荐\n\nCMD [\"python\", \"app.py\"]\n\n## ⚠️ 仅在需要 shell 特性时使用\n\nCMD [\"sh\", \"-c\", \"echo $PATH && python app.py\"]\n```\n\n#### 2. 确保应用在前台运行\n\n```docker\n## ✅ 前台运行\n\nCMD [\"nginx\", \"-g\", \"daemon off;\"]\nCMD [\"apache2ctl\", \"-D\", \"FOREGROUND\"]\nCMD [\"java\", \"-jar\", \"app.jar\"]\n\n## ❌ 不要使用后台服务命令\n\nCMD service nginx start\nCMD systemctl start nginx\n```\n\n#### 3. 使用双引号\n\n```docker\n## ✅ 正确：双引号\n\nCMD [\"node\", \"server.js\"]\n\n## ❌ 错误：单引号（JSON 不支持）\n\nCMD ['node', 'server.js']\n```\n\n#### 4. 配合 ENTRYPOINT 使用\n\n```docker\n## 用于可配置参数的场景\n\nENTRYPOINT [\"python\", \"app.py\"]\nCMD [\"--port\", \"8080\"]\n\n## 运行时可以覆盖端口\n\n$ docker run myapp --port 9000\n\n...\n```\n\n---\n\n### 7.4.8 常见问题\n\n#### Q：CMD 可以写多个吗？\n\n不可以。多个 CMD 只有最后一个生效：\n\n```docker\nCMD [\"echo\", \"first\"]\nCMD [\"echo\", \"second\"]  # 只有这个生效\n```\n\n#### Q：如何在 CMD 中使用环境变量？\n\n```docker\n## 方法1：使用 shell 格式\n\nCMD echo \"Port is $PORT\"\n\n## 方法2：显式使用 sh -c\n\nCMD [\"sh\", \"-c\", \"echo Port is $PORT\"]\n```\n\n#### Q：为什么我的容器不响应 Ctrl+C？\n\n可能是使用了 shell 格式，信号被 sh 吃掉了：\n\n```docker\n## ❌ 信号无法传递\n\nCMD python app.py\n\n## ✅ 信号正确传递\n\nCMD [\"python\", \"app.py\"]\n```\n\n---\n"
  },
  {
    "path": "07_dockerfile/7.5_entrypoint.md",
    "content": "## 7.5 ENTRYPOINT 入口点\n\n### 7.5.1 什么是 ENTRYPOINT\n\n`ENTRYPOINT` 指定容器启动时运行的入口程序。与 CMD 不同，ENTRYPOINT 定义的命令不会被 `docker run` 的参数覆盖，而是 **接收这些参数**。\n\n> **核心作用**：让镜像像一个可执行程序一样使用，`docker run` 的参数作为这个程序的参数。\n\n---\n\n### 7.5.2 语法格式\n\n| 格式 | 语法 | 推荐程度 |\n|------|------|---------|\n| **exec 格式**| `ENTRYPOINT [“可执行文件”, “参数1”]` | ✅**推荐** |\n| **shell 格式** | `ENTRYPOINT 命令 参数` | ⚠️ 不推荐 |\n\n```docker\n## exec 格式（推荐）\n\nENTRYPOINT [\"nginx\", \"-g\", \"daemon off;\"]\n\n## shell 格式（不推荐）\n\nENTRYPOINT nginx -g \"daemon off;\"\n```\n\n---\n\n### 7.5.3 ENTRYPOINT vs CMD\n\n#### 核心区别\n\n| 特性 | ENTRYPOINT | CMD |\n|------|------------|-----|\n| **定位** | 固定的入口程序 | 默认参数 |\n| **docker run 参数** | 追加为参数 | 完全覆盖 |\n| **覆盖方式** | `--entrypoint` | 直接指定命令 |\n| **适用场景** | 把镜像当命令用 | 提供默认行为 |\n\n#### 行为对比\n\n```docker\n## 只用 CMD\n\nCMD [\"curl\", \"-s\", \"http://example.com\"]\n```\n\n```bash\n$ docker run myimage              # curl -s http://example.com\n$ docker run myimage -v           # 执行 -v（错误！）\n$ docker run myimage curl -v ...  # curl -v ...（完全替换）\n```\n\n```docker\n## 只用 ENTRYPOINT\n\nENTRYPOINT [\"curl\", \"-s\"]\n```\n\n```bash\n$ docker run myimage                      # curl -s（缺参数）\n$ docker run myimage http://example.com   # curl -s http://example.com ✓\n```\n\n```docker\n## ENTRYPOINT + CMD 组合（推荐）\n\nENTRYPOINT [\"curl\", \"-s\"]\nCMD [\"http://example.com\"]\n```\n\n```bash\n$ docker run myimage                      # curl -s http://example.com（默认）\n$ docker run myimage http://other.com     # curl -s http://other.com ✓\n$ docker run myimage -v http://other.com  # curl -s -v http://other.com ✓\n```\n\n---\n\n### 7.5.4 场景一：让镜像像命令一样使用\n\n#### 需求：启动前准备\n\n创建一个查询公网 IP 的 “命令” 镜像。\n\n#### 使用 CMD 的问题\n\n```docker\nFROM ubuntu:24.04\nRUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*\nCMD [\"curl\", \"-s\", \"http://myip.ipip.net\"]\n```\n\n```bash\n$ docker run myip           # ✓ 正常工作\n当前 IP：61.148.226.66\n\n$ docker run myip -i        # ✗ 错误！\nexec: \"-i\": executable file not found\n## -i 替换了整个 CMD，被当作可执行文件\n\n...\n```\n\n#### 使用 ENTRYPOINT 解决\n\n```docker\nFROM ubuntu:24.04\nRUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*\nENTRYPOINT [\"curl\", \"-s\", \"http://myip.ipip.net\"]\n```\n\n```bash\n$ docker run myip           # ✓ 正常工作\n当前 IP：61.148.226.66\n\n$ docker run myip -i        # ✓ 添加 -i 参数\nHTTP/1.1 200 OK\n...\n当前 IP：61.148.226.66\n```\n\n#### 交互图示\n\n```bash\nENTRYPOINT [\"curl\", \"-s\", \"http://myip.ipip.net\"]\n            │\ndocker run myip -i\n            │\n            ▼\ncurl -s http://myip.ipip.net -i\n└─────────────────────────────┘\n     ENTRYPOINT + docker run 参数\n```\n\n---\n\n### 7.5.5 场景二：启动前的准备工作\n\n#### 需求\n\n在启动主服务前执行初始化脚本 (如数据库迁移、权限设置)。\n\n#### 实现方式\n\n```docker\nFROM redis:7-alpine\nCOPY docker-entrypoint.sh /usr/local/bin/\nENTRYPOINT [\"docker-entrypoint.sh\"]\nCMD [\"redis-server\"]\n```\n\n**docker-entrypoint.sh**：\n\n```bash\n#!/bin/sh\nset -e\n\n## 准备工作\n\necho \"Initializing...\"\n\n## 如果第一个参数是 redis-server，以 redis 用户运行\n\nif [ \"$1\" = 'redis-server' ]; then\n    chown -R redis:redis /data\n    exec gosu redis \"$@\"\nfi\n\n## 其他命令直接执行\n\nexec \"$@\"\n```\n\n#### 工作流程\n\n```bash\ndocker run redis                    docker run redis bash\n        │                                    │\n        ▼                                    ▼\ndocker-entrypoint.sh redis-server   docker-entrypoint.sh bash\n        │                                    │\n        ├─ 初始化                            ├─ 初始化\n        ├─ chown -R redis:redis /data        │\n        └─ exec gosu redis redis-server      └─ exec bash\n           (以 redis 用户运行)                  (以 root 用户运行)\n```\n\n#### 关键点\n\n1. **exec “$@”**：用传入的参数替换当前进程，确保信号正确传递\n2. **条件判断**：根据 CMD 不同执行不同逻辑\n3. **用户切换**：使用 `gosu` 切换用户 (比 `su` 更适合容器)\n\n---\n\n### 7.5.6 场景三：带参数的应用\n\n```docker\nFROM python:3.12-slim\nWORKDIR /app\nCOPY . .\nRUN pip install -r requirements.txt\n\nENTRYPOINT [\"python\", \"app.py\"]\nCMD [\"--host\", \"0.0.0.0\", \"--port\", \"8080\"]\n```\n\n```bash\n## 使用默认参数\n\n$ docker run myapp\n## 执行: python app.py --host 0.0.0.0 --port 8080\n\n## 覆盖参数\n\n$ docker run myapp --host 0.0.0.0 --port 9000\n## 执行: python app.py --host 0.0.0.0 --port 9000\n\n## 完全不同的参数\n\n$ docker run myapp --help\n## 执行: python app.py --help\n\n...\n```\n\n---\n\n### 7.5.7 覆盖 ENTRYPOINT\n\n使用 `--entrypoint` 参数覆盖：\n\n```bash\n## 正常运行\n\n$ docker run myimage\n\n## 覆盖 ENTRYPOINT 进入 shell 调试\n\n$ docker run --entrypoint /bin/sh myimage\n\n## 覆盖 ENTRYPOINT 并传入参数\n\n$ docker run --entrypoint /bin/cat myimage /etc/os-release\n```\n\n---\n\n### 7.5.8 ENTRYPOINT 与 CMD 组合表\n\n| ENTRYPOINT | CMD | 最终执行命令 |\n|------------|-----|-------------|\n| 无 | 无 | 无 (容器无法启动)|\n| 无 | `[\"cmd\", \"p1\"]` | `cmd p1` |\n| `[\"ep\", \"p1\"]` | 无 | `ep p1` |\n| `[\"ep\", \"p1\"]` | `[\"cmd\", \"p2\"]` | `ep p1 cmd p2` |\n| `ep p1` (shell)| `[\"cmd\", \"p2\"]` | `/bin/sh -c \"ep p1\"` (CMD 被忽略)|\n\n> ⚠️ **注意**：shell 格式的 ENTRYPOINT 会忽略 CMD！\n\n---\n\n### 7.5.9 最佳实践\n\n#### 1. 使用 exec 格式\n\n```docker\n## ✅ 推荐\n\nENTRYPOINT [\"python\", \"app.py\"]\n\n## ❌ 避免 shell 格式\n\nENTRYPOINT python app.py\n```\n\n#### 2. 提供有意义的默认参数\n\n```docker\nENTRYPOINT [\"nginx\"]\nCMD [\"-g\", \"daemon off;\"]\n```\n\n#### 3. 入口脚本使用 exec\n\n```bash\n#!/bin/sh\n## 准备工作...\n\n## 使用 exec 替换当前进程\n\nexec \"$@\"\n```\n\n#### 4. 处理信号\n\n确保 ENTRYPOINT 脚本能正确传递信号：\n\n```bash\n#!/bin/bash\ntrap 'kill -TERM $PID' TERM INT\n\n## 启动应用\n\napp \"$@\" &\nPID=$!\n\n## 等待应用退出\n\nwait $PID\n```\n\n---\n"
  },
  {
    "path": "07_dockerfile/7.6_env.md",
    "content": "## 7.6 ENV 设置环境变量\n\n### 7.6.1 基本语法\n\n```docker\n## 格式一：单个变量\n\nENV <key> <value>\n\n## 格式二：多个变量（推荐）\n\nENV <key1>=<value1> <key2>=<value2> ...\n```\n\n---\n\n### 7.6.2 基本用法\n\n#### 设置单个变量\n\n```docker\nENV NODE_VERSION 20.10.0\nENV APP_ENV production\n```\n\n#### 设置多个变量\n\n```docker\nENV NODE_VERSION=20.10.0 \\\n    APP_ENV=production \\\n    APP_NAME=\"My Application\"\n```\n\n> 💡 包含空格的值用双引号括起来。\n\n---\n\n### 7.6.3 环境变量的作用\n\n#### 1. 后续指令中使用\n\n```docker\nENV NODE_VERSION=20.10.0\n\n## 在 RUN 中使用\n\nRUN curl -fsSL https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.xz \\\n    | tar -xJ -C /usr/local --strip-components=1\n\n## 在 WORKDIR 中使用\n\nENV APP_HOME=/app\nWORKDIR $APP_HOME\n\n## 在 COPY 中使用\n\nCOPY . $APP_HOME\n```\n\n#### 2. 容器运行时使用\n\n```docker\nENV DATABASE_URL=postgres://localhost/mydb\n```\n\n应用代码中可以读取：\n\n```python\nimport os\ndb_url = os.environ.get('DATABASE_URL')\n```\n\n```javascript\nconst dbUrl = process.env.DATABASE_URL;\n```\n\n---\n\n### 7.6.4 支持环境变量的指令\n\n以下指令可以使用 `$变量名` 或 `${变量名}` 格式：\n\n| 指令 | 示例 |\n|------|------|\n| `RUN` | `RUN echo $VERSION` |\n| `CMD` | `CMD [\"sh\", \"-c\", \"echo $HOME\"]` |\n| `ENTRYPOINT` | 同上 |\n| `COPY` | `COPY . $APP_HOME` |\n| `ADD` | `ADD app.tar.gz $APP_HOME` |\n| `WORKDIR` | `WORKDIR $APP_HOME` |\n| `EXPOSE` | `EXPOSE $PORT` |\n| `VOLUME` | `VOLUME $DATA_DIR` |\n| `USER` | `USER $USERNAME` |\n| `LABEL` | `LABEL version=$VERSION` |\n| `FROM` | `FROM node:$NODE_VERSION` |\n\n---\n\n### 7.6.5 运行时覆盖\n\n使用 `-e` 或 `--env` 覆盖 Dockerfile 中定义的环境变量：\n\n```bash\n## 覆盖单个变量\n\n$ docker run -e APP_ENV=development myimage\n\n## 覆盖多个变量\n\n$ docker run -e APP_ENV=development -e DEBUG=true myimage\n\n## 从环境变量文件读取\n\n$ docker run --env-file .env myimage\n```\n\n#### .env 文件格式\n\n```bash\n## .env\n\nAPP_ENV=development\nDEBUG=true\nDATABASE_URL=postgres://localhost/mydb\n```\n\n---\n\n### 7.6.6 ENV vs ARG\n\n| 特性 | ENV | ARG |\n|------|-----|-----|\n| **生效时间** | 构建时 + 运行时 | 仅构建时 |\n| **持久性** | 写入镜像，运行时可用 | 构建后消失 |\n| **覆盖方式** | `docker run -e` | `docker build --build-arg` |\n| **适用场景** | 应用配置 | 构建参数 (如版本号)|\n\n#### 组合使用\n\n```docker\n## ARG 接收构建时参数\n\nARG NODE_VERSION=20\n\n## ENV 保存到运行时\n\nENV NODE_VERSION=$NODE_VERSION\n\n## 后续指令使用\n\nRUN curl -fsSL https://nodejs.org/dist/v${NODE_VERSION}/...\n```\n\n```bash\n## 构建时指定版本\n\n$ docker build --build-arg NODE_VERSION=18 -t myapp .\n```\n\n---\n\n### 7.6.7 最佳实践\n\n#### 1. 统一管理版本号\n\n```docker\n## ✅ 好：版本集中管理\n\nENV NGINX_VERSION=1.25.0 \\\n    NODE_VERSION=20.10.0 \\\n    PYTHON_VERSION=3.12.0\n\nRUN apt-get install nginx=${NGINX_VERSION}\n\n## ❌ 差：版本分散在各处\n\nRUN apt-get install nginx=1.25.0\n```\n\n#### 2. 不要存储敏感信息\n\n```docker\n## ❌ 错误：密码写入镜像\n\nENV DB_PASSWORD=secret123\n\n## ✅ 正确：运行时传入\n\n## docker run -e DB_PASSWORD=xxx myimage\n\n...\n```\n\n#### 3. 为应用提供合理默认值\n\n```docker\nENV APP_ENV=production \\\n    APP_PORT=8080 \\\n    LOG_LEVEL=info\n```\n\n#### 4. 使用有意义的变量名\n\n```docker\n## ✅ 好：清晰的命名\n\nENV REDIS_HOST=localhost \\\n    REDIS_PORT=6379\n\n## ❌ 差：模糊的命名\n\nENV HOST=localhost \\\n    PORT=6379\n```\n\n---\n\n### 7.6.8 常见问题\n\n#### Q：环境变量在 CMD 中不展开\n\nexec 格式不会自动展开环境变量：\n\n```docker\n## ❌ 不会展开 $PORT\n\nCMD [\"python\", \"app.py\", \"--port\", \"$PORT\"]\n\n## ✅ 使用 shell 格式或显式调用 sh\n\nCMD [\"sh\", \"-c\", \"python app.py --port $PORT\"]\n```\n\n#### Q：如何查看容器的环境变量\n\n```bash\n$ docker inspect mycontainer --format '{{json .Config.Env}}'\n$ docker exec mycontainer env\n```\n\n#### Q：多行 ENV 还是多个 ENV\n\n```docker\n## ✅ 推荐：减少层数\n\nENV VAR1=value1 \\\n    VAR2=value2 \\\n    VAR3=value3\n\n## ⚠️ 多个 ENV 会创建多层\n\nENV VAR1=value1\nENV VAR2=value2\nENV VAR3=value3\n```\n\n---\n"
  },
  {
    "path": "07_dockerfile/7.7_arg.md",
    "content": "## 7.7 ARG 构建参数\n\n### 7.7.1 基本语法\n\n```docker\nARG <参数名>[=<默认值>]\n```\n\n`ARG` 指令定义构建时的变量，可以在 `docker build` 时通过 `--build-arg` 传入。\n\n---\n\n### 7.7.2 ARG vs ENV\n\n| 特性 | ARG | ENV |\n|------|-----|-----|\n| **生效时间** | 仅构建时 | 构建时 + 运行时 |\n| **持久性** | 构建后消失 | 写入镜像 |\n| **覆盖方式** | `docker build --build-arg` | `docker run -e` |\n| **适用场景** | 构建参数 (版本号等)| 应用配置 |\n| **可见性** | `docker history` 可见 | `docker inspect` 可见 |\n\n```dockerfile\n构建时                         运行时\n├─ ARG VERSION=1.0             │ （ARG 已消失）\n├─ ENV APP_ENV=prod            │ APP_ENV=prod（仍存在）\n└─ RUN echo $VERSION           │\n```\n\n> ⚠️ **安全提示**：不要用 ARG 传递密码等敏感信息，`docker history` 可以查看所有 ARG 值。\n\n---\n\n### 7.7.3 基本用法\n\n#### 定义和使用\n\n```docker\n## 定义有默认值的 ARG\n\nARG NODE_VERSION=20\n\n## 使用 ARG\n\nFROM node:${NODE_VERSION}-alpine\nRUN echo \"Using Node.js $NODE_VERSION\"\n```\n\n#### 构建时覆盖\n\n```bash\n## 使用默认值\n\n$ docker build -t myapp .\n\n## 覆盖默认值\n\n$ docker build --build-arg NODE_VERSION=18 -t myapp .\n```\n\n---\n\n### 7.7.4 ARG 的作用域\n\n#### FROM 之前的 ARG\n\n```docker\n## FROM 之前的 ARG 只能用于 FROM 指令\n\nARG REGISTRY=docker.io\nARG IMAGE_NAME=node\n\nFROM ${REGISTRY}/${IMAGE_NAME}:20\n\n## ❌ 这里无法使用上面的 ARG\n\nRUN echo $REGISTRY  # 输出空\n```\n\n#### FROM 之后重新声明\n\n```docker\nARG NODE_VERSION=20\n\nFROM node:${NODE_VERSION}-alpine\n\n## 需要再次声明才能使用\n\nARG NODE_VERSION\nRUN echo \"Node version: $NODE_VERSION\"\n```\n\n#### 多阶段构建中的 ARG\n\n```docker\nARG BASE_VERSION=alpine\n\nFROM node:20-${BASE_VERSION} AS builder\n## 需要重新声明\n\nARG NODE_VERSION=20\nRUN echo \"Building with Node $NODE_VERSION\"\n\nFROM node:20-${BASE_VERSION}\n## 每个阶段都需要重新声明\n\nARG NODE_VERSION=20\nRUN echo \"Running with Node $NODE_VERSION\"\n```\n\n---\n\n### 7.7.5 常见使用场景\n\n#### 1. 控制基础镜像版本\n\n```docker\nARG ALPINE_VERSION=3.19\nFROM alpine:${ALPINE_VERSION}\n```\n\n```bash\n$ docker build --build-arg ALPINE_VERSION=3.18 .\n```\n\n#### 2. 设置软件版本\n\n```docker\nARG NGINX_VERSION=1.25.0\n\nRUN curl -fsSL https://nginx.org/download/nginx-${NGINX_VERSION}.tar.gz | tar -xz\n```\n\n#### 3. 配置构建环境\n\n```docker\nARG BUILD_ENV=production\nARG ENABLE_DEBUG=false\n\nRUN if [ \"$ENABLE_DEBUG\" = \"true\" ]; then \\\n        npm install --include=dev; \\\n    else \\\n        npm install --production; \\\n    fi\n```\n\n#### 4. 配置私有仓库\n\n```docker\nARG NPM_TOKEN\n\nRUN echo \"//registry.npmjs.org/:_authToken=${NPM_TOKEN}\" > ~/.npmrc && \\\n    npm install && \\\n    rm ~/.npmrc\n```\n\n```bash\n## 构建时传入 token\n\n$ docker build --build-arg NPM_TOKEN=xxx .\n```\n\n---\n\n### 7.7.6 将 ARG 传递给 ENV\n\n如果需要在运行时使用 ARG 的值：\n\n```docker\nARG VERSION=1.0.0\n\n## 将 ARG 传递给 ENV\n\nENV APP_VERSION=$VERSION\n\n## 运行时可用\n\nCMD echo \"App version: $APP_VERSION\"\n```\n\n---\n\n### 7.7.7 预定义 ARG\n\nDocker 提供了一些预定义的 ARG，无需声明即可使用：\n\n| ARG | 说明 |\n|-----|------|\n| `HTTP_PROXY` | HTTP 代理 |\n| `HTTPS_PROXY` | HTTPS 代理 |\n| `NO_PROXY` | 不使用代理的地址 |\n| `FTP_PROXY` | FTP 代理 |\n\n```bash\n## 构建时使用代理\n\n$ docker build --build-arg HTTP_PROXY=http://proxy:8080 .\n```\n\n---\n\n### 7.7.8 最佳实践\n\n#### 1. 为 ARG 提供合理默认值\n\n```docker\n## ✅ 好：有默认值\n\nARG NODE_VERSION=20\n\n## ⚠️ 需要每次传入\n\nARG NODE_VERSION\n```\n\n#### 2. 不要用 ARG 存储敏感信息\n\n```docker\n## ❌ 错误：密码会被记录在镜像历史中\n\nARG DB_PASSWORD\nRUN echo \"password=$DB_PASSWORD\" > /app/.env\n\n## ✅ 正确：使用 secrets 或运行时环境变量\n\n...\n```\n\n#### 3. 使用 ARG 提高构建灵活性\n\n```docker\nARG BASE_IMAGE=python:3.12-slim\nFROM ${BASE_IMAGE}\n\n## 可以构建不同基础镜像的版本\n\n## docker build --build-arg BASE_IMAGE=python:3.11-alpine .\n\n...\n```\n\n---\n"
  },
  {
    "path": "07_dockerfile/7.8_volume.md",
    "content": "## 7.8 VOLUME 定义匿名卷\n\n### 7.8.1 基本语法\n\n```docker\nVOLUME [\"/路径1\", \"/路径2\"]\nVOLUME /路径\n```\n\n`VOLUME` 指令创建挂载点，并标记为外部挂载的卷。\n\n---\n\n### 7.8.2 为什么使用 VOLUME\n\n> **核心原则**：容器存储层应该保持无状态，任何运行时数据都应该存储在卷中。\n\n```mermaid\nflowchart LR\n    subgraph NoVolume [\"没有 VOLUME：\"]\n        direction TB\n        subgraph Container1 [\"容器存储层\"]\n            direction TB\n            Files[\"数据库文件 (问题)<br/>日志文件<br/>上传文件\"]\n        end\n        Result1[\"容器删除 = 数据丢失\"]\n        Container1 ~~~ Result1\n    end\n    \n    subgraph UseVolume [\"使用 VOLUME：\"]\n        direction TB\n        Container2[\"容器存储层<br/>（只读/无状态）\"]\n        subgraph Volume [\"数据卷\"]\n            Data[\"持久化数据 (安全)\"]\n        end\n        Container2 --> Volume\n        Result2[\"容器删除，数据保留\"]\n        Volume ~~~ Result2\n    end\n```\n\n---\n\n### 7.8.3 基本用法\n\n#### 定义单个卷\n\n```docker\nFROM mysql:8.0\nVOLUME /var/lib/mysql\n```\n\n#### 定义多个卷\n\n```docker\nFROM myapp\nVOLUME [\"/data\", \"/logs\", \"/config\"]\n```\n\n---\n\n### 7.8.4 VOLUME 的行为\n\n#### 1. 自动创建匿名卷\n\n如果运行时未指定挂载，Docker 会自动创建匿名卷：\n\n```bash\n$ docker run mysql:8.0\n$ docker volume ls\nDRIVER    VOLUME NAME\nlocal     a1b2c3d4e5f6...  # 自动创建的匿名卷\n```\n\n#### 2. 可被命名卷覆盖\n\n```bash\n## 使用命名卷替代匿名卷\n\n$ docker run -v mysql_data:/var/lib/mysql mysql:8.0\n```\n\n#### 3. 可被 Bind Mount 覆盖\n\n```bash\n## 使用宿主机目录替代\n\n$ docker run -v /my/data:/var/lib/mysql mysql:8.0\n```\n\n---\n\n### 7.8.5 VOLUME 在构建时的特殊行为\n\n> ⚠️ **重要**：VOLUME 之后对该目录的修改会被丢弃！\n\n```docker\nFROM ubuntu\nVOLUME /data\n\n## ❌ 这个文件不会出现在镜像中！\n\nRUN echo \"hello\" > /data/test.txt\n```\n\n**原因**：在构建过程中，VOLUME 指令会为该目录创建一个临时的匿名卷。后续 RUN 指令对该目录的写入实际发生在这个临时卷中，而非镜像层。当该 RUN 指令结束后，临时卷被丢弃，因此写入的内容不会保存到最终镜像中。注意：这与容器运行时创建的匿名卷是不同的——运行时创建的卷会在容器生命周期内持续存在。\n\n#### 正确做法\n\n```docker\nFROM ubuntu\n\n## ✅ 先写入文件\n\nRUN mkdir -p /data && echo \"hello\" > /data/test.txt\n\n## 再声明 VOLUME\n\nVOLUME /data\n```\n\n---\n\n### 7.8.6 常见使用场景\n\n#### 数据库持久化\n\n```docker\nFROM postgres:16\nVOLUME /var/lib/postgresql/data\n```\n\n#### 日志目录\n\n```docker\nFROM nginx\nVOLUME /var/log/nginx\n```\n\n#### 上传文件目录\n\n```docker\nFROM myapp\nVOLUME /app/uploads\n```\n\n---\n\n### 7.8.7 查看 VOLUME 定义\n\n```bash\n## 查看镜像定义的 VOLUME\n\n$ docker inspect mysql:8.0 --format '{{json .Config.Volumes}}' | jq\n{\n  \"/var/lib/mysql\": {}\n}\n\n## 查看容器挂载的卷\n\n$ docker inspect mycontainer --format '{{json .Mounts}}' | jq\n```\n\n---\n\n### 7.8.8 VOLUME vs docker run -v\n\n| 特性 | Dockerfile VOLUME | docker run -v |\n|------|-------------------|---------------|\n| **定义时机** | 镜像构建时 | 容器运行时 |\n| **默认行为** | 创建匿名卷 | 可指定命名卷或路径 |\n| **灵活性** | 低 (固定路径)| 高 (可任意指定)|\n| **适用场景** | 定义必须持久化的路径 | 灵活的数据管理 |\n\n---\n\n### 7.8.9 在 Compose 中\n\n在 Compose 中配置如下：\n\n```yaml\nservices:\n  db:\n    image: postgres:16\n    volumes:\n      # 命名卷（推荐）\n\n      - postgres_data:/var/lib/postgresql/data\n      # Bind Mount\n\n      - ./init.sql:/docker-entrypoint-initdb.d/init.sql\n\nvolumes:\n  postgres_data:  # 声明命名卷\n```\n\n---\n\n### 7.8.10 安全注意事项\n\n#### 匿名卷可能导致数据丢失\n\n```bash\n## 使用 --rm 运行的容器，匿名卷会在容器删除时一起删除\n\n$ docker run --rm mysql:8.0\n## 容器停止后，数据丢失！\n\n...\n```\n\n**解决**：始终使用命名卷\n\n```bash\n$ docker run -v mysql_data:/var/lib/mysql mysql:8.0\n```\n\n---\n\n### 7.8.11 最佳实践\n\n#### 1. 定义必须持久化的路径\n\n```docker\n## 数据库必须使用卷\n\nFROM postgres:16\nVOLUME /var/lib/postgresql/data\n```\n\n#### 2. 不要在 VOLUME 后修改目录\n\n```docker\n## ❌ 避免\n\nVOLUME /app/data\nRUN cp init-data.json /app/data/\n\n## ✅ 正确\n\nRUN mkdir -p /app/data && cp init-data.json /app/data/\nVOLUME /app/data\n```\n\n#### 3. 文档中说明 VOLUME 用途\n\n```docker\n## 持久化用户上传的文件\n\nVOLUME /app/uploads\n\n## 持久化数据库数据\n\nVOLUME /var/lib/mysql\n```\n\n---\n"
  },
  {
    "path": "07_dockerfile/7.9_expose.md",
    "content": "## 7.9 EXPOSE 暴露端口\n\n### 7.9.1 基本语法\n\n```docker\nEXPOSE <端口> [<端口>/<协议>...]\n```\n\n`EXPOSE` 声明容器运行时提供服务的端口。这是一个 **文档性质的声明**，告诉使用者容器会监听哪些端口。\n\n---\n\n### 7.9.2 基本用法\n\n```docker\n## 声明单个端口\n\nEXPOSE 80\n\n## 声明多个端口\n\nEXPOSE 80 443\n\n## 声明 TCP 和 UDP 端口\n\nEXPOSE 80/tcp\nEXPOSE 53/udp\n```\n\n---\n\n### 7.9.3 EXPOSE 的作用\n\n#### 1. 文档说明\n\n告诉镜像使用者，容器将在哪些端口提供服务：\n\n```docker\n## 使用者一看就知道这是 web 应用\n\nEXPOSE 80 443\n```\n\n```bash\n## 查看镜像暴露的端口\n\n$ docker inspect nginx --format '{{.Config.ExposedPorts}}'\nmap[80/tcp:{}]\n```\n\n#### 2. 配合 -P 使用\n\n使用 `docker run -P` 时，Docker 会自动映射 EXPOSE 的端口到宿主机随机端口：\n\n```docker\n## Dockerfile\n\nEXPOSE 80\n```\n\n```bash\n$ docker run -P nginx\n$ docker port $(docker ps -q)\n80/tcp -> 0.0.0.0:32768\n```\n\n---\n\n### 7.9.4 EXPOSE vs -p\n\n| 特性 | EXPOSE | -p |\n|------|--------|-----|\n| **位置** | Dockerfile | docker run 命令 |\n| **作用** | 声明/文档 | 实际端口映射 |\n| **是否必需** | 否 | 是 (外部访问时)|\n| **映射发生时** | 不发生 | 运行时发生 |\n\n```mermaid\nflowchart TD\n    Expose[\"EXPOSE 80<br/>仅声明意图\"]\n    Run[\"docker run -p<br/>实际端口映射<br/>宿主机 ←→ 容器\"]\n    Expose ~~~ Run\n```\n\n#### 没有 EXPOSE 也能 -p\n\n```docker\n## 即使没有 EXPOSE，也可以使用 -p\n\nFROM nginx\n## 没有 EXPOSE\n\n...\n```\n\n```bash\n## 仍然可以映射端口\n\n$ docker run -p 8080:80 mynginx\n```\n\n---\n\n### 7.9.5 常见误解\n\n#### 误解：EXPOSE 会打开端口\n\n```docker\n## ❌ 错误理解：这不会让容器可从外部访问\n\nEXPOSE 80\n```\n\nEXPOSE 不会：\n\n- 自动进行端口映射\n- 让服务可从外部访问\n- 在容器启动时开启端口监听\n\nEXPOSE 只是元数据声明。容器是否实际监听该端口，取决于容器内的应用。\n\n#### 正确理解\n\n```docker\n## Dockerfile\n\nFROM nginx\nEXPOSE 80    # 1. 声明：这个容器会在 80 端口提供服务\n```\n\n```bash\n## 运行：需要 -p 才能从外部访问\n\n$ docker run -p 8080:80 nginx    # 2. 映射：宿主机 8080 → 容器 80\n```\n\n---\n\n### 7.9.6 最佳实践\n\n#### 1. 总是声明应用使用的端口\n\n```docker\n## Web 服务\n\nFROM nginx\nEXPOSE 80 443\n\n## 数据库\n\nFROM postgres\nEXPOSE 5432\n\n## Redis\n\nFROM redis\nEXPOSE 6379\n```\n\n#### 2. 使用明确的协议\n\n```docker\n## 默认是 TCP\n\nEXPOSE 80\n\n## 明确指定 UDP\n\nEXPOSE 53/udp\n\n## 同时支持 TCP 和 UDP\n\nEXPOSE 53/tcp 53/udp\n```\n\n#### 3. 与应用实际端口保持一致\n\n```docker\n## ✅ 好：EXPOSE 与应用端口一致\n\nENV PORT=3000\nEXPOSE 3000\nCMD [\"node\", \"server.js\"]\n\n## ❌ 差：EXPOSE 与应用端口不一致（误导）\n\nEXPOSE 80\nCMD [\"node\", \"server.js\"]  # 实际监听 3000\n```\n\n---\n\n### 7.9.7 使用环境变量\n\n```docker\nARG PORT=80\nEXPOSE $PORT\n```\n\n---\n\n### 7.9.8 在 Compose 中\n\n在 Compose 中配置如下：\n\n```yaml\nservices:\n  web:\n    build: .\n    ports:\n      - \"8080:80\"    # 映射端口（类似 -p）\n    expose:\n      - \"80\"         # 仅声明（类似 EXPOSE）\n```\n\n`expose` 在 Compose 中仅用于容器间通信的文档说明，不进行端口映射。\n\n---\n"
  },
  {
    "path": "07_dockerfile/README.md",
    "content": "# 第七章 Dockerfile 指令详解\n\n## 什么是 Dockerfile\n\nDockerfile 是一个文本文件，其内包含了一条条的 **指令 (Instruction)**，每一条指令构建一层，因此每一条指令的内容，就是描述该层应当如何构建。\n\n在[第四章](../04_image/README.md)中，我们通过 `docker commit` 学习了镜像的构成。但是，手动 `commit` 只能作为临时修补，并不适合作为生产环境镜像的构建方式。\n\n使用 Dockerfile 构建镜像有以下优势：\n\n*   **自动化**：可以通过 `docker build` 命令自动构建镜像。\n*   **可重复性**：由于 Dockerfile 是文本文件，可以确保每次构建的结果一致。\n*   **版本控制**：Dockerfile 可以纳入版本控制系统 (如 Git)，便于追踪变更。\n*   **透明性**：任何人都可以通过阅读 Dockerfile 了解镜像的构建过程。\n\n## Dockerfile 基本结构\n\nDockerfile 一般分为四部分：基础镜像信息、维护者信息、镜像操作指令和容器启动时执行指令。\n\n### 指令详解\n\n本章将详细讲解 Dockerfile 中的各个指令：\n\n*   [RUN 执行命令](7.1_run.md)\n*   [COPY 复制文件](7.2_copy.md)\n*   [ADD 更高级的复制文件](7.3_add.md)\n*   [CMD 容器启动命令](7.4_cmd.md)\n*   [ENTRYPOINT 入口点](7.5_entrypoint.md)\n*   [ENV 设置环境变量](7.6_env.md)\n*   [ARG 构建参数](7.7_arg.md)\n*   [VOLUME 定义匿名卷](7.8_volume.md)\n*   [EXPOSE 暴露端口](7.9_expose.md)\n*   [WORKDIR 指定工作目录](7.10_workdir.md)\n*   [USER 指定当前用户](7.11_user.md)\n*   [HEALTHCHECK 健康检查](7.12_healthcheck.md)\n*   [ONBUILD 为他人作嫁衣裳](7.13_onbuild.md)\n*   [LABEL 为镜像添加元数据](7.14_label.md)\n*   [SHELL 指令](7.15_shell.md)\n\n### 高级特性\n\n本章还将介绍 Dockerfile 的高级特性：\n\n*   [多阶段构建](7.17_multistage_builds.md)\n*   [多阶段构建实战：Laravel 应用](7.18_multistage_builds_laravel.md)\n\n### 参考与最佳实践\n\n此外，我们还将介绍 Dockerfile 的最佳实践和常见问题。\n\n*   [参考文档](7.16_references.md)\n\n## 使用 Dockerfile 构建镜像\n\n构建镜像的基本命令格式为：\n\n```bash\ndocker build [选项] <上下文路径/URL/->\n```\n\n例如，在 Dockerfile 所在目录执行：\n\n```bash\ndocker build -t my-image:v1 .\n```\n\n更多关于 `docker build` 的用法，我们在实战中会结合具体指令进行演示。\n"
  },
  {
    "path": "07_dockerfile/multistage_example/laravel/.dockerignore",
    "content": ".idea/\n.git/\n\nvendor/\n\nnode_modules/\n\npublic/js/\npublic/css/\npublic/mix-manifest.json\n\nyarn-error.log\n\nbootstrap/cache/*\nstorage/\n\n# 自行添加其他需要排除的文件，例如 .env.* 文件\n"
  },
  {
    "path": "07_dockerfile/multistage_example/laravel/Dockerfile",
    "content": "FROM node:alpine as frontend\n\nCOPY package.json /app/\n\nRUN set -x ; cd /app \\\n      && npm install --registry=https://registry.npm.taobao.org\n\nCOPY webpack.mix.js webpack.config.js tailwind.config.js /app/\nCOPY resources/ /app/resources/\n\nRUN set -x ; cd /app \\\n      && touch artisan \\\n      && mkdir -p public \\\n      && npm run production\n\nFROM composer as composer\n\nCOPY database/ /app/database/\nCOPY composer.json /app/\n\nRUN set -x ; cd /app \\\n      && composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/ \\\n      && composer install \\\n           --ignore-platform-reqs \\\n           --no-interaction \\\n           --no-plugins \\\n           --no-scripts \\\n           --prefer-dist\n\nFROM php:7.4-fpm-alpine as laravel\n\nARG LARAVEL_PATH=/app/laravel\n\nCOPY --from=composer /app/vendor/ ${LARAVEL_PATH}/vendor/\nCOPY . ${LARAVEL_PATH}\nCOPY --from=frontend /app/public/js/ ${LARAVEL_PATH}/public/js/\nCOPY --from=frontend /app/public/css/ ${LARAVEL_PATH}/public/css/\nCOPY --from=frontend /app/public/mix-manifest.json ${LARAVEL_PATH}/public/mix-manifest.json\n\nRUN set -x ; cd ${LARAVEL_PATH} \\\n      && mkdir -p storage \\\n      && mkdir -p storage/framework/cache \\\n      && mkdir -p storage/framework/sessions \\\n      && mkdir -p storage/framework/testing \\\n      && mkdir -p storage/framework/views \\\n      && mkdir -p storage/logs \\\n      && chmod -R 777 storage \\\n      && php artisan package:discover\n\nFROM nginx:alpine as nginx\n\nARG LARAVEL_PATH=/app/laravel\n\nCOPY laravel.conf /etc/nginx/conf.d/\nCOPY --from=laravel ${LARAVEL_PATH}/public ${LARAVEL_PATH}/public\n"
  },
  {
    "path": "07_dockerfile/multistage_example/laravel/laravel.conf",
    "content": "server {\n  listen 80 default_server;\n  root /app/laravel/public;\n  index index.php index.html;\n\n  location / {\n      try_files $uri $uri/ /index.php?$query_string;\n  }\n\n  location ~ .*\\.php(\\/.*)*$ {\n    fastcgi_pass   laravel:9000;\n    include        fastcgi.conf;\n\n    # fastcgi_connect_timeout 300;\n    # fastcgi_send_timeout 300;\n    # fastcgi_read_timeout 300;\n  }\n}\n"
  },
  {
    "path": "07_dockerfile/summary.md",
    "content": "## 本章小结\n\n本章详细介绍了 Dockerfile 的所有核心指令，以下是各指令要点的速查表。\n\n| 指令 | 作用 | 关键要点 |\n|------|------|---------|\n| **FROM** | 指定基础镜像 | 必须是第一条指令 |\n| **RUN** | 在新层执行命令 | 合并命令、清理缓存以减小体积 |\n| **COPY** | 复制文件 | 优先使用，支持 `--from` |\n| **ADD** | 更高级的复制 | 自动解压 tar，不推荐用于下载 |\n| **CMD** | 容器启动默认命令 | 可被 `docker run` 参数覆盖 |\n| **ENTRYPOINT** | 容器入口点 | 固定启动命令，CMD 作为默认参数 |\n| **ENV** | 设置环境变量 | 构建时 + 运行时均生效 |\n| **ARG** | 构建参数 | 仅构建时生效，FROM 后需重新声明 |\n| **VOLUME** | 定义匿名卷 | VOLUME 之后的修改会丢失 |\n| **EXPOSE** | 声明端口 | 仅文档作用，不自动映射 |\n| **WORKDIR** | 指定工作目录 | 替代 `RUN cd`，目录不存在会自动创建 |\n| **USER** | 指定运行用户 | 用户必须已存在，推荐 gosu |\n| **HEALTHCHECK** | 健康检查 | 支持 starting/healthy/unhealthy 状态 |\n| **ONBUILD** | 延迟执行指令 | 只继承一次，不可级联 |\n| **LABEL** | 添加元数据 | 推荐 OCI 标准标签，替代 MAINTAINER |\n| **SHELL** | 更改默认 shell | 推荐 `[\"/bin/bash\", \"-o\", \"pipefail\", \"-c\"]` |\n\n### 延伸阅读\n\n- [使用 Dockerfile 定制镜像](../04_image/4.5_build.md)：Dockerfile 入门\n- [多阶段构建](7.17_multistage_builds.md)：优化镜像大小\n- [Dockerfile 最佳实践](../appendix/best_practices.md)：编写指南\n- [安全](../18_security/README.md)：容器安全实践\n- [Compose 模板文件](../11_compose/11.5_compose_file.md)：Compose 中的配置\n---\n\n> 📝 **发现错误或有改进建议？** 欢迎提交 [Issue](https://github.com/yeasy/docker_practice/issues) 或 [PR](https://github.com/yeasy/docker_practice/pulls)。\n"
  },
  {
    "path": "08_data/8.1_volume.md",
    "content": "## 8.1 数据卷\n\n### 8.1.1 为什么需要数据卷\n\n容器的存储层有一个关键问题：**容器删除后，数据就没了**。\n\n```mermaid\nflowchart LR\n    Run[容器运行] --> Write[写入数据]\n    Write --> Delete[容器删除]\n    Delete -->|数据都在容器 writable 层| Lost[DATA LOST! ❌]\n```\n\n数据卷 (Volume) 解决了这个问题，它的生命周期独立于容器。\n\n---\n\n### 8.1.2 数据卷的特性\n\n| 特性 | 说明 |\n|------|------|\n| **持久化** | 容器删除后数据仍然保留 |\n| **共享** | 多个容器可以挂载同一个数据卷 |\n| **即时生效** | 对数据卷的修改立即可见 |\n| **不影响镜像** | 数据卷中的数据不会打包进镜像 |\n| **性能更好** | 绕过 UnionFS，直接读写 |\n\n---\n\n### 8.1.3 数据卷 vs 容器存储层\n\n#### 容器存储层：不推荐存储重要数据\n\n```mermaid\ngraph TD\n    subgraph Container [容器]\n        Writable[容器存储层<br>Writable]\n        Image[镜像层<br>ReadOnly]\n        Writable --- Image\n    end\n    \n    Lifecycle[生命周期 = 容器生命周期] -.-> Container\n    Delete[容器删除] -->|导致| DataLost[数据丢失 ❌]\n```\n\n#### 数据卷：推荐\n\n```mermaid\ngraph TD\n    subgraph Container [容器]\n        AppDir[\"/app/data\"]\n    end\n    \n    subgraph Volume [数据卷 my-data]\n        Data[持久化数据]\n    end\n    \n    AppDir == 挂载 ==> Volume\n    Delete[容器删除] -.->|不会影响| Volume\n```\n\n---\n\n### 8.1.4 数据卷基本操作\n\n#### 创建数据卷\n\n```bash\n$ docker volume create my-vol\n```\n\n#### 列出所有数据卷\n\n```bash\n$ docker volume ls\nDRIVER    VOLUME NAME\nlocal     my-vol\nlocal     postgres_data\nlocal     redis_data\n```\n\n#### 查看数据卷详情\n\n```bash\n$ docker volume inspect my-vol\n[\n    {\n        \"CreatedAt\": \"2026-01-15T10:00:00Z\",\n        \"Driver\": \"local\",\n        \"Labels\": {},\n        \"Mountpoint\": \"/var/lib/docker/volumes/my-vol/_data\",\n        \"Name\": \"my-vol\",\n        \"Options\": {},\n        \"Scope\": \"local\"\n    }\n]\n```\n\n**关键字段**：\n\n- `Mountpoint`：数据卷在宿主机上的实际存储位置\n- `Driver`：存储驱动 (默认 local，也可以用第三方驱动)\n\n---\n\n### 8.1.5 挂载数据卷\n\n#### 方式一：--mount：推荐\n\n```bash\n$ docker run -d \\\n    --name web \\\n    --mount source=my-vol,target=/usr/share/nginx/html \\\n    nginx\n```\n\n**参数说明**：\n\n| 参数 | 说明 |\n|------|------|\n| `source` | 数据卷名称 (不存在会自动创建)|\n| `target` | 容器内挂载路径 |\n| `readonly` | 可选，只读挂载 |\n\n#### 方式二：-v：简写\n\n```bash\n$ docker run -d \\\n    --name web \\\n    -v my-vol:/usr/share/nginx/html \\\n    nginx\n```\n\n**格式**：`-v 数据卷名:容器路径[:选项]`\n\n#### 两种方式对比\n\n| 特性 | --mount | -v |\n|------|---------|-----|\n| 语法 | 键值对，更清晰 | 冒号分隔，更简洁 |\n| **数据卷 (Volume)** 挂载行为 | 卷不存在会自动创建，与 `-v` 结果一致 | 卷不存在会自动创建 |\n| **绑定挂载 (Bind Mount)** 行为 | ⭐**宿主机路径不存在会报错**，不会自动创建 | 宿主机路径不存在会 **自动创建为目录** |\n| 推荐程度 | ✅ 推荐 (更明确安全，避免误创建)| 常用 (更简洁)|\n\n> **提示**：官方更推荐使用 `--mount`。除了语法格式可读性更好之外，最重要的行为差异发生在 **绑定挂载 (Bind Mount)** 时：如果挂载的宿主机源路径尚未存在，`-v` 会擅自将其自动创建为一个空目录；而 `--mount` 则会严格检查并直接报错。这能有效避免因路径拼写错误而在宿主机上留下垃圾目录（以及导致的容器访问空目录问题）。而对于本节的 **数据卷 (Volume)** 挂载而言，两者在目标指定的卷不存在时皆会自动创建卷，产生的结果是 **完全一致** 的。\n\n#### 只读挂载\n\n```bash\n## --mount 方式\n\n$ docker run -d \\\n    --mount source=my-vol,target=/data,readonly \\\n    nginx\n\n## -v 方式\n\n$ docker run -d \\\n    -v my-vol:/data:ro \\\n    nginx\n```\n\n---\n\n### 8.1.6 使用场景示例\n\n#### 场景一：数据库持久化\n\n```bash\n## 创建数据卷\n\n$ docker volume create postgres_data\n\n## 启动 PostgreSQL，数据存储在数据卷中\n\n$ docker run -d \\\n    --name postgres \\\n    -e POSTGRES_PASSWORD=secret \\\n    -v postgres_data:/var/lib/postgresql/data \\\n    postgres:16\n\n## 即使删除容器，数据仍然保留\n\n$ docker rm -f postgres\n\n## 重新启动，数据还在\n\n$ docker run -d \\\n    --name postgres \\\n    -e POSTGRES_PASSWORD=secret \\\n    -v postgres_data:/var/lib/postgresql/data \\\n    postgres:16\n```\n\n#### 场景二：多容器共享数据\n\n```bash\n## 创建共享数据卷\n\n$ docker volume create shared-data\n\n## 容器 A 写入数据\n\n$ docker run -d --name writer \\\n    -v shared-data:/data \\\n    alpine sh -c \"while true; do date >> /data/log.txt; sleep 5; done\"\n\n## 容器 B 读取数据\n\n$ docker run --rm \\\n    -v shared-data:/data \\\n    alpine cat /data/log.txt\n```\n\n#### 场景三：配置文件持久化\n\n```bash\n## 将 nginx 配置存储在数据卷中\n\n$ docker run -d \\\n    -v nginx-config:/etc/nginx/conf.d \\\n    -v nginx-logs:/var/log/nginx \\\n    -p 80:80 \\\n    nginx\n```\n\n---\n\n### 8.1.7 数据卷管理\n\n#### 删除数据卷\n\n```bash\n## 删除指定数据卷\n\n$ docker volume rm my-vol\n\n## 删除容器时同时删除数据卷\n\n$ docker rm -v container_name\n```\n\n#### 清理未使用的数据卷\n\n```bash\n## 查看未被任何容器使用的数据卷\n\n$ docker volume ls -f dangling=true\n\n## 删除所有未使用的数据卷\n\n$ docker volume prune\n\n## 强制删除（不提示确认）\n\n$ docker volume prune -f\n```\n\n> ⚠️ **注意**：数据卷不会自动垃圾回收。长期运行的系统应定期清理无用数据卷。\n\n---\n\n### 8.1.8 数据卷备份与恢复\n\n#### 备份数据卷\n\n```bash\n## 使用临时容器挂载数据卷，打包备份\n\n$ docker run --rm \\\n    -v my-vol:/source:ro \\\n    -v $(pwd):/backup \\\n    alpine tar czf /backup/my-vol-backup.tar.gz -C /source .\n```\n\n**原理**：\n\n1. 创建临时容器\n2. 挂载要备份的数据卷到 `/source`\n3. 挂载当前目录到 `/backup`\n4. 使用 tar 打包\n\n#### 恢复数据卷\n\n```bash\n## 创建新数据卷\n\n$ docker volume create my-vol-restored\n\n## 解压备份到新数据卷\n\n$ docker run --rm \\\n    -v my-vol-restored:/target \\\n    -v $(pwd):/backup:ro \\\n    alpine tar xzf /backup/my-vol-backup.tar.gz -C /target\n```\n\n#### 备份脚本示例\n\n```bash\n#!/bin/bash\n## backup-volume.sh\n\nVOLUME_NAME=$1\nBACKUP_DIR=${2:-/backups}\nTIMESTAMP=$(date +%Y%m%d_%H%M%S)\n\ndocker run --rm \\\n    -v ${VOLUME_NAME}:/source:ro \\\n    -v ${BACKUP_DIR}:/backup \\\n    alpine tar czf /backup/${VOLUME_NAME}_${TIMESTAMP}.tar.gz -C /source .\n\necho \"Backed up ${VOLUME_NAME} to ${BACKUP_DIR}/${VOLUME_NAME}_${TIMESTAMP}.tar.gz\"\n```\n\n---\n\n### 8.1.9 数据卷 vs 绑定挂载\n\nDocker 有两种主要的数据持久化方式：\n\n| 特性 | 数据卷 (Volume) | 绑定挂载 (Bind Mount) |\n|------|----------------|---------------------|\n| **管理方式** | Docker 管理 | 用户管理 |\n| **存储位置** | `/var/lib/docker/volumes/` | 任意宿主机路径 |\n| **可移植性** | 更好 | 依赖宿主机路径 |\n| **适用场景** | 生产数据持久化 | 开发时同步代码 |\n| **备份** | 需要工具 | 直接访问文件 |\n\n```bash\n## 数据卷\n\n$ docker run -v mydata:/app/data nginx\n\n## 绑定挂载\n\n$ docker run -v /host/path:/app/data nginx\n```\n\n详见[绑定挂载](8.2_bind-mounts.md)章节。\n\n---\n\n### 8.1.10 常见问题\n\n#### Q：如何知道容器使用了哪些数据卷？\n\n```bash\n$ docker inspect container_name --format '{{json .Mounts}}' | jq\n```\n\n#### Q：数据卷的数据在哪里？\n\n```bash\n## 查看数据卷详情\n\n$ docker volume inspect my-vol\n\n## Mountpoint 字段显示实际路径\n\n\"Mountpoint\": \"/var/lib/docker/volumes/my-vol/_data\"\n```\n\n> ⚠️ **注意**：不建议直接修改 Mountpoint 中的文件，应通过容器操作。\n\n#### Q：如何在不同机器间迁移数据卷？\n\n1. 在源机器备份：`docker run --rm -v mydata:/data -v $(pwd):/backup alpine tar czf /backup/data.tar.gz -C /data .`\n2. 传输 tar.gz 文件\n3. 在目标机器恢复\n\n---\n"
  },
  {
    "path": "08_data/8.2_bind-mounts.md",
    "content": "## 8.2 挂载主机目录\n\n### 8.2.1 什么是绑定挂载\n\nBind Mount (绑定挂载) 将 **宿主机的目录或文件** 直接挂载到容器中。容器可以读写宿主机的文件系统。\n\n```mermaid\nflowchart LR\n    subgraph Host [\"宿主机\"]\n        Dir1[\"/home/user/code/\"]\n    end\n    \n    subgraph Container [\"容器\"]\n        Dir2[\"/usr/share/nginx/html/\"]\n    end\n    \n    Dir1 <-->|Bind Mount| Dir2\n```\n\n目录结构（同一份文件）：\n```text\n/home/user/code/ (或 /usr/share/nginx/html/)\n├── index.html\n├── style.css\n└── app.js\n```\n\n---\n\n### 8.2.2 Bind Mount vs Volume\n\n| 特性 | Bind Mount | Volume |\n|------|------------|--------|\n| **数据位置** | 宿主机任意路径 | Docker 管理的目录 |\n| **路径指定** | 必须是绝对路径 | 卷名 |\n| **可移植性** | 依赖宿主机路径 | 更好 (Docker 管理)|\n| **性能** | 依赖宿主机文件系统 | 优化的存储驱动 |\n| **适用场景** | 开发环境、配置文件 | 生产数据持久化 |\n| **备份** | 直接访问文件 | 需要通过 Docker |\n\n#### 选择建议\n\n| 需求 | 推荐方案 |\n|------|----------|\n| 开发时同步代码 | Bind Mount |\n| 持久化数据库数据 | Volume |\n| 共享配置文件 | Bind Mount |\n| 容器间共享数据 | Volume |\n| 备份方便 | Bind Mount (直接访问)|\n| 生产环境 | Volume |\n\n---\n\n### 8.2.3 基本语法\n\n#### 使用 --mount：推荐\n\n```bash\n$ docker run -d \\\n    --mount type=bind,source=/宿主机路径,target=/容器路径 \\\n    nginx\n```\n\n#### 使用 -v：简写\n\n```bash\n$ docker run -d \\\n    -v /宿主机路径:/容器路径 \\\n    nginx\n```\n\n#### 两种语法对比\n\n| 特性 | --mount | -v |\n|------|---------|-----|\n| 语法 | 键值对，更清晰 | 冒号分隔，更简洁 |\n| 路径不存在时 | 直接报错 (Fail Fast) | 静默自动创建 **目录** |\n| 推荐程度 | ✅ 推荐 | 常用 |\n\n> **⚠️ 陷阱**：如果不小心挂载了一个不存在的宿主机路径，使用 `-v` 会在宿主机上静默创建一个 **空目录**（即使你本来想挂载的是一个文件），这常常会导致权限错误或应用无法正常读取。这也正是为什么 Docker 官方更推荐使用 `--mount` 的原因：它会遵循“Fail Fast”原则直接报错，避免弄巧成拙。\n\n---\n\n### 8.2.4 使用场景\n\n#### 场景一：开发环境代码同步\n\n```bash\n## 将本地代码目录挂载到容器\n\n$ docker run -d \\\n    -p 8080:80 \\\n    --mount type=bind,source=$(pwd)/src,target=/usr/share/nginx/html \\\n    nginx\n\n## 修改本地文件，容器内立即生效（热更新）\n\n$ echo \"Hello\" > src/index.html\n## 浏览器刷新即可看到变化\n\n...\n```\n\n#### 场景二：配置文件挂载\n\n```bash\n## 挂载自定义 nginx 配置\n\n$ docker run -d \\\n    --mount type=bind,source=/path/to/nginx.conf,target=/etc/nginx/nginx.conf,readonly \\\n    nginx\n```\n\n#### 场景三：日志收集\n\n```bash\n## 将容器日志输出到宿主机目录\n\n$ docker run -d \\\n    --mount type=bind,source=/var/log/myapp,target=/app/logs \\\n    myapp\n```\n\n#### 场景四：共享 SSH 密钥\n\n```bash\n## 挂载 SSH 密钥（只读）\n\n$ docker run --rm -it \\\n    --mount type=bind,source=$HOME/.ssh,target=/root/.ssh,readonly \\\n    alpine ssh user@remote\n```\n\n---\n\n### 8.2.5 只读挂载\n\n防止容器修改宿主机文件：\n\n```bash\n## --mount 语法\n\n$ docker run -d \\\n    --mount type=bind,source=/config,target=/app/config,readonly \\\n    myapp\n\n## -v 语法\n\n$ docker run -d \\\n    -v /config:/app/config:ro \\\n    myapp\n```\n\n容器内尝试写入会报错：\n\n```bash\n$ touch /app/config/new.txt\ntouch: /app/config/new.txt: Read-only file system\n```\n\n---\n\n### 8.2.6 挂载单个文件\n\n```bash\n## 挂载 bash 历史记录\n\n$ docker run --rm -it \\\n    --mount type=bind,source=$HOME/.bash_history,target=/root/.bash_history \\\n    ubuntu bash\n\n## 挂载自定义配置文件\n\n$ docker run -d \\\n    --mount type=bind,source=/path/to/my.cnf,target=/etc/mysql/my.cnf \\\n    mysql\n```\n\n> ⚠️ **注意**：挂载单个文件时，如果宿主机上的文件被编辑器替换 (而非原地修改)，容器内仍是旧文件的 inode。建议重启容器或挂载目录。\n\n---\n\n### 8.2.7 查看挂载信息\n\n```bash\n$ docker inspect mycontainer --format '{{json .Mounts}}' | jq\n```\n\n输出：\n\n```json\n[\n  {\n    \"Type\": \"bind\",\n    \"Source\": \"/home/user/code\",\n    \"Destination\": \"/app\",\n    \"Mode\": \"\",\n    \"RW\": true,\n    \"Propagation\": \"rprivate\"\n  }\n]\n```\n\n| 字段 | 说明 |\n|------|------|\n| `Type` | 挂载类型 (bind)|\n| `Source` | 宿主机路径 |\n| `Destination` | 容器内路径 |\n| `RW` | 是否可读写 |\n| `Propagation` | 挂载传播模式 |\n\n---\n\n### 8.2.8 常见问题\n\n#### Q：路径不存在报错\n\n```bash\n$ docker run --mount type=bind,source=/not/exist,target=/app nginx\ndocker: Error response from daemon: invalid mount config for type \"bind\": \nbind source path does not exist: /not/exist\n```\n\n**解决**：确保源路径存在，或改用 `-v` (会自动创建)\n\n#### Q：权限问题\n\n容器内用户可能无权访问挂载的文件：\n\n```bash\n## 方法1：确保宿主机文件权限允许容器用户访问\n\n$ chmod -R 755 /path/to/data\n\n## 方法2：以 root 运行容器\n\n$ docker run -u root ...\n\n## 方法3：使用相同的 UID\n\n$ docker run -u $(id -u):$(id -g) ...\n```\n\n#### Q：macOS/Windows 性能问题\n\n在 Docker Desktop 上，Bind Mount 性能较差 (需要跨文件系统同步)：\n\n```bash\n## 使用 :cached 或 :delegated 提高性能（macOS）\n\n$ docker run -v /host/path:/container/path:cached myapp\n```\n\n| 选项 | 说明 |\n|------|------|\n| `:cached` | 宿主机权威，容器读取可能延迟 |\n| `:delegated` | 容器权威，宿主机读取可能延迟 |\n| `:consistent` | 默认，完全一致 (最慢)|\n\n---\n\n### 8.2.9 最佳实践\n\n#### 1. 开发环境使用 Bind Mount\n\n```bash\n## 代码热更新\n\n$ docker run -v $(pwd):/app -p 3000:3000 node npm run dev\n```\n\n#### 2. 生产环境使用 Volume\n\n```bash\n## 数据持久化\n\n$ docker run -v mysql_data:/var/lib/mysql mysql\n```\n\n#### 3. 配置文件使用只读挂载\n\n```bash\n$ docker run -v /config/nginx.conf:/etc/nginx/nginx.conf:ro nginx\n```\n\n#### 4. 注意路径安全\n\n```bash\n## ❌ 危险：挂载根目录或敏感目录\n\n$ docker run -v /:/host ...\n\n## ✅ 只挂载必要的目录\n\n$ docker run -v /app/data:/data ...\n```\n\n---\n"
  },
  {
    "path": "08_data/8.3_tmpfs.md",
    "content": "## 8.3 tmpfs 挂载\n\n`tmpfs` 挂载会把数据放在宿主机内存中，而不是写入容器可写层或数据卷。\n\n### 8.3.1 适用场景\n\n- 临时缓存\n- 会话数据\n- 不希望落盘的敏感中间文件\n\n### 8.3.2 基本用法\n\n使用 `--mount` 语法（推荐）：\n\n```bash\n$ docker run --mount type=tmpfs,destination=/run,tmpfs-size=67108864,tmpfs-mode=1770 nginx\n```\n\n也可以使用 `--tmpfs` 简写语法：\n\n```bash\n$ docker run --tmpfs /run:size=64m nginx\n```\n\n> **注意**：`--tmpfs` 支持的选项有限，主要为 `size` 和 `mode`。如果需要更精细的控制（如 `noexec`、`nosuid`），推荐使用 `--mount` 语法并通过 `tmpfs-mode` 参数设置权限。\n\n### 8.3.3 注意事项\n\n- 容器停止后，`tmpfs` 数据会丢失。\n- `tmpfs` 占用宿主机内存，建议显式限制大小。\n- 不适合需要持久化的数据。\n\n### 8.3.4 与 Volume / Bind Mount 对比\n\n| 类型 | 数据位置 | 持久化 | 典型用途 |\n|------|---------|-------|---------|\n| Volume | Docker 管理目录 | 是 | 数据库、长期业务数据 |\n| Bind Mount | 宿主机指定目录 | 是 | 开发联调、配置文件共享 |\n| tmpfs | 内存 | 否 | 高速临时数据、敏感临时文件 |\n"
  },
  {
    "path": "08_data/README.md",
    "content": "# 第八章 数据管理\n\n如图 8-1 所示，Docker 数据管理主要围绕三类挂载方式展开。\n\n![Docker 数据挂载类型](./_images/types-of-mounts.png)\n\n图 8-1：Docker 数据挂载类型示意图\n\n这一章介绍如何在 Docker 内部以及容器之间管理数据，在容器中管理数据主要有以下几种方式：\n\n* [数据卷](8.1_volume.md)\n* [挂载主机目录](8.2_bind-mounts.md)\n* [tmpfs 挂载](8.3_tmpfs.md)\n"
  },
  {
    "path": "08_data/summary.md",
    "content": "## 本章小结\n\n本章介绍了 Docker 的三种数据管理方式：数据卷 (Volume)、绑定挂载 (Bind Mount) 和 tmpfs 挂载。\n\n| 方式 | 特点 | 适用场景 |\n|------|------|---------|\n| **数据卷 (Volume)** | Docker 管理，生命周期独立于容器 | 数据库、应用数据（推荐生产环境） |\n| **绑定挂载 (Bind Mount)** | 挂载宿主机目录，更灵活 | 开发环境、配置文件、日志 |\n| **tmpfs 挂载** | 仅存储在内存中，容器停止即消失 | 临时敏感数据、高速缓存 |\n\n| 操作 | 命令 |\n|------|------|\n| 创建数据卷 | `docker volume create name` |\n| 列出数据卷 | `docker volume ls` |\n| 查看详情 | `docker volume inspect name` |\n| 删除数据卷 | `docker volume rm name` |\n| 清理未用 | `docker volume prune` |\n| 挂载数据卷 | `-v name:/path` 或 `--mount source=name,target=/path` |\n\n### 延伸阅读\n\n- [数据卷](8.1_volume.md)：Docker 管理的持久化存储\n- [绑定挂载](8.2_bind-mounts.md)：挂载宿主机目录\n- [tmpfs 挂载](8.3_tmpfs.md)：内存中的临时存储\n- [存储驱动](../12_implementation/12.4_ufs.md)：Docker 存储的底层原理\n- [Compose 数据管理](../11_compose/11.5_compose_file.md)：Compose 中的挂载配置\n---\n\n> 📝 **发现错误或有改进建议？** 欢迎提交 [Issue](https://github.com/yeasy/docker_practice/issues) 或 [PR](https://github.com/yeasy/docker_practice/pulls)。\n"
  },
  {
    "path": "09_network/9.1_dns.md",
    "content": "## 9.1 配置 DNS\n\nDocker 1.10.0 以后，内建了一个 DNS 服务器，使得容器可以直接通过容器名称通信。方法很简单，只要在创建容器时使用 `--name` 为容器命名即可。\n\n但是使用 Docker DNS 有个前提条件，就是它只能在 **自定义网络** 中使用。也就是说，如果使用的是默认的 `bridge` 网络，是无法使用 DNS 的，所以我们就需要自定义网络。\n\n### 9.1.1 容器的 DNS 机制\n\nDocker 容器的 DNS 配置有两种情况：\n\n1. **默认 Bridge 网络**：继承宿主机的 DNS 配置 (`/etc/resolv.conf`)。\n2. **自定义网络**(推荐)：使用 Docker 嵌入式 DNS 服务器 (Embedded DNS)，支持通过 **容器名** 进行服务发现。\n\n---\n\n### 9.1.2 嵌入式 DNS\n\n这是 Docker 网络最强大的功能之一。在自定义网络中，容器可以通过 “名字” 找到彼此，而不需要知道对方的 IP (因为 IP 可能会变)。\n\n```bash\n## 1. 创建自定义网络\n\n$ docker network create mynet\n\n## 2. 启动容器 web 并加入网络\n\n$ docker run -d --name web --network mynet nginx\n\n## 3. 启动容器 client 并尝试 ping web\n\n$ docker run -it --rm --network mynet alpine ping web\nPING web (172.18.0.2): 56 data bytes\n64 bytes from 172.18.0.2: seq=0 ttl=64 time=0.074 ms\n```\n\n**原理**：\nDocker 守护进程在 `127.0.0.11` 运行了一个 DNS 服务器。容器内的 DNS 请求会被转发到这里。如果是容器名，解析为容器 IP；如果是外部域名 (如 google.com)，转发给上游 DNS。\n\n---\n\n### 9.1.3 配置 DNS 参数\n\n如果你需要手动配置容器的 DNS (例如使用内网 DNS 服务器)，可以在 `docker run` 中使用以下参数：\n\n#### 1. --dns\n\n指定 DNS 服务器 IP。\n\n```bash\n$ docker run -it --dns=114.114.114.114 ubuntu cat /etc/resolv.conf\nnameserver 114.114.114.114\n```\n\n#### 2. --dns-search\n\n指定 DNS 搜索域。例如设置为 `example.com`，则 `ping host` 会尝试解析 `host.example.com`。\n\n```bash\n$ docker run --dns-search=example.com myapp\n```\n\n#### 3. --hostname 与 -h\n\n设置容器的主机名。\n\n```bash\n$ docker run -h myweb nginx\n```\n\n---\n\n### 9.1.4 全局 DNS 配置\n\n如果希望所有容器都使用特定的 DNS 服务器 (而不是继承宿主机)，可以修改 `/etc/docker/daemon.json`：\n\n```json\n{\n  \"dns\": [\n    \"114.114.114.114\",\n    \"8.8.8.8\"\n  ]\n}\n```\n\n修改后需要重启 Docker 服务：`systemctl restart docker`。\n\n---\n\n### 9.1.5 常见问题\n\n以下是使用容器 DNS 时常见的问题及解决方法：\n\n#### Q：容器无法解析域名\n\n**现象**：`ping www.baidu.com` 失败，但 `ping 8.8.8.8` 成功。**解决**：\n\n1. 宿主机的 `/etc/resolv.conf` 可能有问题 (例如使用了本地回环地址 127.0.0.53，特别是 Ubuntu 系统)。Docker 可能会尝试修复，但有时会失败。\n2. 尝试手动指定 DNS：`docker run --dns 8.8.8.8 ...`\n3. 检查防火墙是否拦截了 UDP 53 端口。\n\n#### Q：无法通过容器名通信\n\n**现象**：`ping db` 提示 `bad address 'db'`。**原因**：\n\n- 你可能在使用 **默认的 bridge 网络**。默认 bridge 网络 **不支持** 通过容器名进行 DNS 解析 (这是一个历史遗留设计)。\n- **解决**：使用自定义网络 (`docker network create ...`)。\n\n---\n"
  },
  {
    "path": "09_network/9.2_network_types.md",
    "content": "## 9.2 网络类型\n\nDocker 提供了多种网络驱动来满足不同的使用场景。安装 Docker 后，系统会自动创建三个默认网络：\n\n```bash\n$ docker network ls\nNETWORK ID     NAME      DRIVER    SCOPE\nabc123...      bridge    bridge    local\ndef456...      host      host      local\nghi789...      none      null      local\n```\n\n### 9.2.1 网络类型对比\n\n各网络类型的特点和适用场景如下：\n\n| 网络类型 | 说明 | 适用场景 |\n|---------|------|---------| \n| **bridge** | 默认类型，容器连接到虚拟网桥 | 大多数单机场景 |\n| **host** | 容器直接使用宿主机网络栈 | 需要最高网络性能时 |\n| **none** | 禁用网络 | 完全隔离的容器 |\n| **overlay** | 跨主机网络 | Docker Swarm 集群 |\n| **macvlan** | 容器拥有独立 MAC 地址 | 需要直接接入物理网络 |\n\n### 9.2.2 Bridge 网络：默认\n\nBridge 是 Docker 默认使用的网络模式。Docker 启动时会自动创建 `docker0` 虚拟网桥，所有未指定网络的容器都会连接到这个网桥上。\n\n核心组件如下：\n\n| 组件 | 说明 |\n|------|------|\n| **docker0** | 虚拟网桥，充当交换机角色 |\n| **veth pair** | 虚拟网卡对，一端在容器内，一端连接网桥 |\n| **容器 eth0** | 容器内的网卡 |\n| **IP 地址** | 自动从 172.17.0.0/16 网段分配 |\n\n### 9.2.3 Host 网络\n\n使用 `--network host` 参数启动的容器会直接使用宿主机的网络栈，不再拥有独立的网络命名空间。容器内的端口就是宿主机的端口，无需端口映射。\n\n```bash\n$ docker run -d --network host nginx\n```\n\n这种模式下网络性能最高，但容器之间和宿主机之间没有网络隔离。\n\n### 9.2.4 None 网络\n\n使用 `--network none` 参数启动的容器只有 `lo` 回环网卡，完全没有外部网络连接。适用于只需要运行计算任务、不需要网络的容器。\n\n```bash\n$ docker run -it --network none alpine ip addr\n1: lo: <LOOPBACK,UP,LOWER_UP> ...\n    inet 127.0.0.1/8 scope host lo\n```\n\n### 9.2.5 数据流向\n\n容器网络中的数据流向可以分为以下几种情况：\n\n```mermaid\nflowchart LR\n    subgraph Comm1 [\"容器间通信\"]\n        direction LR\n        C1A[\"容器 A (172.17.0.2)\"] --> D0A[\"docker0\"] --> C1B[\"容器 B (172.17.0.3)\"]\n    end\n    \n    subgraph Comm2 [\"访问外网\"]\n        direction LR\n        C2A[\"容器 A (172.17.0.2)\"] --> D0B[\"docker0\"] --> Eth0A[\"eth0\"] --> InternetA[\"互联网\"]\n    end\n    \n    subgraph Comm3 [\"被外部访问，需端口映射\"]\n        direction LR\n        Req[\"外部请求\"] --> Eth0B[\"eth0\"] --> D0C[\"docker0\"] --> C3A[\"容器 A\"]\n    end\n```\n"
  },
  {
    "path": "09_network/9.3_custom_network.md",
    "content": "## 9.3 自定义网络\n\n在生产环境中，推荐使用用户自定义网络代替默认的 bridge 网络。自定义网络提供了更好的隔离性和服务发现能力。\n\n### 9.3.1 为什么要用自定义网络\n\n默认 bridge 网络存在以下局限，而自定义网络可以很好地解决这些问题：\n\n| 问题 | 自定义网络的优势 |\n|------|-----------------| \n| 只能用 IP 通信 | 支持容器名 DNS 解析 |\n| 所有容器在同一网络 | 更好的隔离性 |\n| 需要 --link (已废弃)| 原生支持服务发现 |\n\n### 9.3.2 创建自定义网络\n\n使用 `docker network create` 命令可以创建自定义网络：\n\n```bash\n## 创建网络\n\n$ docker network create mynet\n\n## 查看网络详情\n\n$ docker network inspect mynet\n```\n\n### 9.3.3 使用自定义网络\n\n启动容器时通过 `--network` 参数指定连接的网络：\n\n```bash\n## 启动容器并连接到自定义网络\n\n$ docker run -d --name web --network mynet nginx\n$ docker run -d --name db --network mynet postgres\n\n## 在 web 容器中可以直接用容器名访问 db\n\n$ docker exec web ping db\nPING db (172.18.0.3): 56 data bytes\n64 bytes from 172.18.0.3: seq=0 ttl=64 time=0.083 ms\n```\n\n### 9.3.4 容器名 DNS 解析\n\n自定义网络自动提供 DNS 服务。Docker 守护进程在 `127.0.0.11` 运行了一个嵌入式 DNS 服务器，容器内的 DNS 请求会被转发到这里：\n\n- 如果是容器名，解析为容器 IP\n- 如果是外部域名 (如 google.com)，转发给上游 DNS\n\n```mermaid\nflowchart LR\n    subgraph MyNet [\"mynet 网络 : web 容器可以用 'db' 作为主机名访问 db 容器\"]\n        direction LR\n        Web[\"web<br/>172.18.0.2\"] -- \"DNS: 'db' → 172.18.0.3\" --> DB[\"db<br/>172.18.0.3\"]\n    end\n```\n\n### 9.3.5 常用网络命令\n\n以下是 Docker 网络管理中常用的命令：\n\n```bash\n## 列出网络\n\n$ docker network ls\n\n## 创建网络\n\n$ docker network create mynet\n\n## 查看网络详情\n\n$ docker network inspect mynet\n\n## 连接容器到网络\n\n$ docker network connect mynet mycontainer\n\n## 断开网络连接\n\n$ docker network disconnect mynet mycontainer\n\n## 删除网络\n\n$ docker network rm mynet\n\n## 清理未使用的网络\n\n$ docker network prune\n```\n\n---\n\n> **🔥 踩坑实录**\n>\n> 一个新手开发者通过 `docker compose` 部署了两个容器化服务：服务 A 和服务 B。他在服务 A 的代码中尝试用 `localhost:3000` 访问服务 B，结果始终连接超时。这个错误非常隐蔽——在本地单机开发时看不出问题，因为他可能在同一个进程中测试。排查时他错误地认为是防火墙或网络配置问题。实际原因是：每个容器都有独立的网络命名空间，`localhost` 在容器内部只指向容器自己，不是宿主机也不是其他容器。正确的做法是使用 Compose 自动创建的服务名作为主机名：`http://service-b:3000`。Compose 会自动在网络中注册服务名的 DNS，这样容器间通信才能正确解析。改动仅需一行代码，问题随之消失。\n"
  },
  {
    "path": "09_network/9.4_container_linking.md",
    "content": "## 9.4 容器互联\n\n容器之间的网络通信是 Docker 网络的核心功能之一。本节介绍容器互联的几种方式。\n\n### 9.4.1 同一网络内的容器\n\n同一自定义网络内的容器可以直接通过容器名通信，这是推荐的容器互联方式：\n\n```bash\n## 创建网络\n\n$ docker network create app-net\n\n## 启动应用和数据库\n\n$ docker run -d --name redis --network app-net redis\n$ docker run -d --name app --network app-net myapp\n\n## app 容器中可以用 redis:6379 连接 Redis\n\n...\n```\n\n### 9.4.2 连接到多个网络\n\n一个容器可以同时连接到多个网络，这对于需要跨网络通信的中间件容器特别有用：\n\n```bash\n## 启动容器\n\n$ docker run -d --name multi-net-container --network frontend nginx\n\n## 再连接到另一个网络\n\n$ docker network connect backend multi-net-container\n\n## 查看容器的网络\n\n$ docker inspect multi-net-container --format '{{json .NetworkSettings.Networks}}'\n```\n\n### 9.4.3 ⚠️ --link 已废弃\n\n`--link` 是 Docker 早期用于容器互联的方式，**已经被废弃**，不建议在新项目中使用。请使用自定义网络替代：\n\n```bash\n## 旧方式（不推荐）\n\n$ docker run --link db:database myapp\n\n## 新方式（推荐）\n\n$ docker network create mynet\n$ docker run --network mynet --name db postgres\n$ docker run --network mynet --name app myapp\n```\n\n使用自定义网络的优势在于：\n\n- 原生支持 DNS 解析\n- 不需要在容器启动时显式声明依赖\n- 更灵活，可以动态 connect/disconnect\n"
  },
  {
    "path": "09_network/9.5_port_mapping.md",
    "content": "## 9.5 外部访问容器\n\n容器运行在自己的隔离网络环境中 (通常是 Bridge 模式)。为了让外部网络访问容器内的服务，我们需要将容器的端口映射到宿主机的端口。\n\n### 9.5.1 为什么要映射端口\n\n容器的网络访问规则如下：\n\n- **容器之间**：可以通过 IP 或容器名 (自定义网络) 互通。\n- **宿主机访问容器**：可以通过容器 IP 访问。\n- **外部网络访问容器**：❌ 默认无法直接访问。\n\n为了让外部 (如你的浏览器、其他局域网机器) 访问容器内的服务，我们需要将容器的端口 **映射** 到宿主机的端口。\n\n```mermaid\nflowchart TD\n    User[\"外部用户 (Browser)\"] --> Host[\"宿主机 (localhost:8080)\"]\n    Host --> Proxy[\"Docker Proxy<br/>端口映射 (8080 -> 80)\"]\n    Proxy --> Container[\"容器 (Class B: 80)\"]\n```\n\n---\n\n### 9.5.2 端口映射方式\n\nDocker 提供了多种方式来指定端口映射。\n\n#### 1. 指定映射\n\n使用 `-p <宿主机端口>:<容器端口>` 格式：\n\n```bash\n## 将宿主机的 8080 端口映射到容器的 80 端口\n\n$ docker run -d -p 8080:80 nginx\n```\n\n此时访问 `http://localhost:8080` 即可看到 Nginx 页面。\n\n**多种格式**：\n\n| 格式 | 含义 | 示例 |\n|------|------|------|\n| `ip:hostPort:containerPort` | 绑定指定 IP 的特定端口 | `-p 127.0.0.1:8080:80` (仅本机访问) |\n| `ip::containerPort` | 绑定指定 IP 的随机端口 | `-p 127.0.0.1::80` |\n| `hostPort:containerPort` | 绑定所有 IP (0.0.0.0) 的特定端口 | `-p 8080:80` (默认) |\n| `containerPort` | 绑定所有 IP 的随机端口 | `-p 80` |\n\n#### 2. 随机映射\n\n如果不关心宿主机使用哪个端口，可以使用随机映射。使用 `-P` (大写) 参数，Docker 会随机映射 Dockerfile 中 `EXPOSE` 指令暴露的所有端口到宿主机的高端口 (49000-49900)。\n\n```bash\n$ docker run -d -P nginx\n```\n\n查看映射结果：\n\n```bash\n$ docker ps\nCONTAINER ID   PORTS\nabc123456      0.0.0.0:49153->80/tcp\n```\n\n此时 Nginx 被映射到了宿主机的 49153 端口。\n\n---\n\n### 9.5.3 查看端口映射\n\n可以使用以下命令查看容器的端口映射：\n\n#### docker port\n\n运行 `docker port` 可以查看到指定容器的端口映射情况：\n\n```bash\n$ docker port mycontainer\n80/tcp -> 0.0.0.0:8080\n80/tcp -> [::]:8080\n```\n\n#### docker ps\n\n运行 `docker ps` 可以查看到所有容器的端口映射列表：\n\n```bash\n$ docker ps\nCONTAINER ID   IMAGE     PORTS                  NAMES\nabc123456      nginx     0.0.0.0:8080->80/tcp   web\n```\n\n---\n\n### 9.5.4 最佳实践与安全\n\n在配置端口映射时，需要注意以下安全事项：\n\n#### 1. 限制监听 IP\n\n默认情况下，`-p 8080:80` 会监听 `0.0.0.0:8080`，这意味着任何人只要能连接你的宿主机 IP，就能访问该服务。\n\n如果不希望对外暴露 (例如数据库服务)，应绑定到 `127.0.0.1`：\n\n```bash\n## 仅允许本机访问\n\n$ docker run -d -p 127.0.0.1:3306:3306 mysql\n```\n\n#### 2. 避免端口冲突\n\n如果宿主机 8080 已经被占用了，容器将无法启动。\n\n**解决**：\n\n- 更换宿主机端口：`-p 8081:80`\n- 让 Docker 自动分配：`-p 80`\n\n#### 3. UDP 映射\n\n默认是 TCP 协议。如果要映射 UDP 服务 (如 DNS，Syslog)：\n\n```bash\n$ docker run -d -p 53:53/udp dns-server\n```\n\n---\n\n### 9.5.5 实现原理\n\nDocker 使用 `docker-proxy` 进程 (用户态) 或 `iptables` DNAT 规则 (内核态) 来实现端口转发。\n\n当流量到达宿主机端口时，iptables 规则将其目标地址修改为容器 IP 并转发：\n\n```bash\n## 简化的 iptables 逻辑\n\niptables -t nat -A DOCKER -p tcp --dport 8080 -j DNAT --to-destination 172.17.0.2:80\n```\n\n这也是为什么你在容器内部看到的访问来源 IP 通常是网关 IP (如 172.17.0.1)，而不是真实的外部 Client IP (除非使用 host 网络模式)。\n\n---\n"
  },
  {
    "path": "09_network/9.6_network_isolation.md",
    "content": "## 9.6 网络隔离\n\nDocker 网络提供了天然的隔离能力，不同网络之间的容器默认无法通信。这是 Docker 网络安全的重要基础。\n\n### 9.6.1 网络隔离原理\n\n不同网络之间默认隔离，容器只能与同一网络中的容器直接通信：\n\n```bash\n## 创建两个网络\n\n$ docker network create frontend\n$ docker network create backend\n\n## 容器 A 在 frontend\n\n$ docker run -d --name web --network frontend nginx\n\n## 容器 B 在 backend  \n\n$ docker run -d --name db --network backend postgres\n\n## web 无法直接访问 db（不同网络）\n\n$ docker exec web ping db\nping: db: Name or service not known\n```\n\n### 9.6.2 安全优势\n\n这种隔离机制带来以下安全优势：\n\n| 场景 | 说明 |\n|------|------|\n| **前后端分离** | 前端容器无法直接访问数据库网络 |\n| **微服务隔离** | 不同微服务组可以使用不同网络 |\n| **多租户** | 不同租户的容器在不同网络中完全隔离 |\n| **最小权限** | 容器只能访问必要的网络资源 |\n\n### 9.6.3 跨网络通信\n\n如果确实需要某个容器跨网络通信，可以将其同时连接到多个网络：\n\n```bash\n## 创建一个中间件容器，连接到两个网络\n\n$ docker run -d --name api --network frontend myapi\n$ docker network connect backend api\n\n## 现在 api 容器既可以访问 frontend 中的 web，也可以访问 backend 中的 db\n```\n\n这种方式让你可以精确控制哪些容器可以跨网络通信，遵循最小权限原则。\n\n### 9.6.4 典型网络架构\n\n一个典型的多层应用网络架构如下：\n\n```mermaid\ngraph TD\n    subgraph FrontendNet [\"frontend 网络\"]\n        LB[\"负载均衡器\"]\n        Web1[\"Web 服务器 1\"]\n        Web2[\"Web 服务器 2\"]\n    end\n    \n    subgraph BackendNet [\"backend 网络\"]\n        API[\"API 服务器\"]\n        DB[\"数据库\"]\n        Cache[\"Redis 缓存\"]\n    end\n    \n    LB --> Web1\n    LB --> Web2\n    Web1 -.-> API\n    Web2 -.-> API\n    API --> DB\n    API --> Cache\n```\n\n在这种架构中，API 服务器同时连接到 `frontend` 和 `backend` 网络，充当两个网络之间的桥梁。负载均衡器和 Web 服务器无法直接访问数据库，增强了安全性。\n"
  },
  {
    "path": "09_network/9.7_advanced_networking.md",
    "content": "## 9.7 容器网络高级特性\n\n深入探讨容器网络的核心机制、Overlay 网络、CNI 插件生态、容器 DNS 解析、网络策略等高级特性，为生产级别的网络架构打下坚实基础。\n\n### 9.7.1 Overlay 网络原理与配置\n\nOverlay 网络在现有网络基础上建立虚拟网络，允许容器跨宿主机通信。它是 Kubernetes 和 Swarm 模式的基础。\n\n#### Overlay 网络工作原理\n\nOverlay 网络通过隧道封装技术（通常是 VXLAN）将容器网络流量封装在宿主机物理网络的 UDP 数据包中传输。\n\n```text\n容器 A (192.168.0.2)\n    ↓\nveth 对\n    ↓\nbr-net (网桥)\n    ↓\nDocker 引擎 (VXLAN 封装)\n    ↓\n物理网络 (172.16.0.0/24)\n    ↓\nDocker 引擎 (VXLAN 解封装)\n    ↓\nbr-net (网桥)\n    ↓\nveth 对\n    ↓\n容器 B (192.168.0.3，不同宿主机)\n```\n\n#### 创建和使用 Overlay 网络\n\n**Docker Swarm 模式下的 Overlay 网络：**\n\n```bash\n# 初始化 Swarm（创建集群）\ndocker swarm init\n\n# 创建 overlay 网络\ndocker network create --driver overlay \\\n  --subnet 192.168.0.0/24 \\\n  --opt com.docker.network.driver.mtu=1450 \\\n  my-overlay-net\n\n# 验证网络创建\ndocker network ls\ndocker network inspect my-overlay-net\n\n# 在 Swarm 服务中使用 overlay 网络\ndocker service create --name web \\\n  --network my-overlay-net \\\n  --replicas 3 \\\n  nginx:latest\n\n# 验证服务跨节点通信\ndocker service ps web\n```\n\n**单机 Overlay 网络模拟（Linux 容器）：**\n\n```bash\n# 创建自定义 overlay 网络\ndocker network create --driver overlay custom-overlay\n\n# 创建两个容器\ndocker run -d --name container1 --network custom-overlay nginx:latest\ndocker run -d --name container2 --network custom-overlay nginx:latest\n\n# 测试跨容器通信\ndocker exec container1 ping container2\ndocker exec container1 curl http://container2\n\n# 检查网络配置\ndocker network inspect custom-overlay\n```\n\n#### Overlay 网络性能优化\n\n```bash\n# 调整 MTU（Maximum Transmission Unit）避免分片\n# VXLAN 开销 50 字节，物理 MTU 1500，建议设置为 1450\ndocker network create --driver overlay \\\n  --opt com.docker.network.driver.mtu=1450 \\\n  optimized-overlay\n\n# 启用 IP 地址管理（IPAM）自定义\ndocker network create --driver overlay \\\n  --subnet 10.0.9.0/24 \\\n  --aux-address \"my-router=10.0.9.2\" \\\n  my-custom-overlay\n\n# 在 Compose 中使用 overlay 网络\nversion: '3.9'\nservices:\n  web:\n    image: nginx\n    networks:\n      - backend\n\n  db:\n    image: postgres\n    networks:\n      - backend\n\nnetworks:\n  backend:\n    driver: overlay\n    driver_opts:\n      com.docker.network.driver.mtu: 1450\n```\n\n### 9.7.2 CNI 插件生态概览\n\n容器网络接口（CNI）是容器编排平台（尤其是 Kubernetes）的标准化网络接口。不同的 CNI 插件提供不同的网络能力。\n\n#### 主流 CNI 插件对比\n\n**Calico - 基于 BGP 的网络**\n\nCalico 使用 BGP 协议进行路由，支持网络策略和 eBPF 加速。\n\n```yaml\n# Kubernetes 中安装 Calico\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: calico-config\n  namespace: kube-system\ndata:\n  cni_network_config: |\n    {\n      \"name\": \"k8s-pod-network\",\n      \"cniVersion\": \"0.4.0\",\n      \"plugins\": [\n        {\n          \"type\": \"calico\",\n          \"datastore_type\": \"kubernetes\",\n          \"mtu\": 1450,\n          \"ipam\": {\n            \"type\": \"calico-ipam\"\n          }\n        },\n        {\n          \"type\": \"portmap\",\n          \"snat\": true,\n          \"capabilities\": {\"portMappings\": true}\n        }\n      ]\n    }\n```\n\n**Flannel - 简单可靠的 Overlay**\n\nFlannel 提供简单的 overlay 网络实现，适合小到中等规模的集群。\n\n```bash\n# 安装 Flannel\nkubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml\n\n# 配置 Flannel 后端（VXLAN、UDP、AWS VPC 等）\ncat <<EOF | kubectl apply -f -\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: kube-flannel-cfg\n  namespace: kube-flannel\ndata:\n  cni-conf.json: |\n    {\n      \"name\": \"cbr0\",\n      \"cniVersion\": \"0.3.1\",\n      \"plugins\": [\n        {\n          \"type\": \"flannel\",\n          \"delegate\": {\n            \"hairpinMode\": true,\n            \"isDefaultGateway\": true\n          }\n        },\n        {\n          \"type\": \"portmap\",\n          \"capabilities\": {\n            \"portMappings\": true\n          }\n        }\n      ]\n    }\n  net-conf.json: |\n    {\n      \"Network\": \"10.244.0.0/16\",\n      \"Backend\": {\n        \"Type\": \"vxlan\"\n      }\n    }\nEOF\n```\n\n**Cilium - eBPF 驱动的先进网络**\n\nCilium 使用 eBPF 在内核级别实现网络策略和可观测性，性能优异。\n\n```bash\n# 安装 Cilium\nhelm repo add cilium https://helm.cilium.io\nhelm install cilium cilium/cilium \\\n  --namespace kube-system \\\n  --set image.tag=v1.14.0 \\\n  --set operator.replicas=1\n\n# 启用 Hubble（可观测性）\nhelm upgrade cilium cilium/cilium \\\n  --namespace kube-system \\\n  --reuse-values \\\n  --set hubble.enabled=true \\\n  --set hubble.ui.enabled=true\n```\n\n**Weave - 跨主机网络通信**\n\nWeave 提供简单的跨主机通信，支持加密和多播。\n\n```bash\n# Docker 中使用 Weave 网络\ndocker run -d --name weave \\\n  --net=host \\\n  --cap-add=NET_ADMIN \\\n  --cap-add=SYS_PTRACE \\\n  ghcr.io/weaveworks/weave:latest\n\n# 连接到 Weave 网络\ndocker run -d --network weave --name web nginx:latest\n```\n\n**CNI 插件对比表：**\n\n| 特性 | Calico | Flannel | Cilium | Weave |\n|------|--------|---------|--------|-------|\n| 路由方式 | BGP | VXLAN/UDP | eBPF | VxLAN |\n| 网络策略 | ✓ | ✗ | ✓ (L3-L7) | ✗ |\n| 性能 | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |\n| 可观测性 | 中 | 低 | ⭐⭐⭐⭐⭐ | 中 |\n| 学习曲线 | 中 | 低 | 高 | 低 |\n| 生产就绪 | ✓ | ✓ | ✓ | ✓ |\n\n### 9.7.3 容器 DNS 解析机制\n\nDocker 内置 DNS 服务器，但 DNS 解析涉及多个层面的配置。\n\n#### DNS 解析流程\n\n```text\n容器应用 (dig www.example.com)\n    ↓\n容器内 /etc/resolv.conf (127.0.0.11:53)\n    ↓\nDocker 内嵌 DNS 服务器 (127.0.0.11)\n    ↓\n用户自定义 DNS 或宿主机 /etc/resolv.conf\n    ↓\n外部 DNS 服务器 (8.8.8.8 等)\n    ↓\nDNS 响应 → 容器缓存 → 应用\n```\n\n#### 配置容器 DNS\n\n**在运行时指定 DNS：**\n\n```bash\n# 单个容器\ndocker run -d \\\n  --dns 8.8.8.8 \\\n  --dns 1.1.1.1 \\\n  --dns-search example.com \\\n  nginx:latest\n\n# DNS 选项\ndocker run -d \\\n  --dns-option ndots:2 \\\n  --dns-option timeout:1 \\\n  --dns-option attempts:3 \\\n  nginx:latest\n\n# 查看容器 DNS 配置\ndocker exec <container_id> cat /etc/resolv.conf\n```\n\n**Docker Compose DNS 配置：**\n\n```yaml\nversion: '3.9'\n\nservices:\n  web:\n    image: nginx\n    dns:\n      - 8.8.8.8\n      - 1.1.1.1\n    dns_search:\n      - example.com\n      - local\n\n  db:\n    image: postgres\n    networks:\n      - backend\n    hostname: postgres-db\n\nnetworks:\n  backend:\n    driver: bridge\n\n# 容器内 /etc/resolv.conf 将被自动配置\n# search example.com local\n# nameserver 8.8.8.8\n# nameserver 1.1.1.1\n```\n\n**Docker 守护进程级别配置：**\n\n```json\n{\n  \"dns\": [\"8.8.8.8\", \"1.1.1.1\"],\n  \"dns-search\": [\"example.com\"],\n  \"insecure-registries\": [],\n  \"registry-mirrors\": [\"https://mirror.example.com\"]\n}\n```\n\n#### 自定义服务发现\n\n**使用 Docker 内建 DNS 的服务发现：**\n\n```bash\n# 创建自定义网络\ndocker network create mynet\n\n# 运行服务\ndocker run -d --name web --network mynet nginx:latest\ndocker run -d --name db --network mynet postgres:latest\n\n# 在其他容器中通过服务名访问\ndocker run -it --network mynet busybox sh\n# ping web  # 自动解析到 web 容器 IP\n# ping db   # 自动解析到 db 容器 IP\n```\n\n**Compose 服务名自动发现：**\n\n```yaml\nversion: '3.9'\n\nservices:\n  frontend:\n    image: nginx\n    depends_on:\n      - backend\n    environment:\n      BACKEND_URL: http://backend:8080\n\n  backend:\n    image: myapp\n    depends_on:\n      - database\n\n  database:\n    image: postgres\n    environment:\n      POSTGRES_DB: mydb\n\n# frontend 容器可以直接访问 http://backend:8080\n# backend 容器可以直接访问 postgres://database:5432\n```\n\n#### DNS 性能优化\n\n```bash\n# 检查 DNS 延迟\ntime docker exec <container> nslookup www.example.com\n\n# 优化 DNS 解析\ndocker run -d \\\n  --dns 127.0.0.1 \\  # 使用本地缓存 DNS (Dnsmasq)\n  nginx:latest\n\n# 在 Kubernetes 中优化\nkubectl patch deployment -n kube-system coredns --patch '{\n  \"spec\": {\n    \"template\": {\n      \"spec\": {\n        \"containers\": [\n          {\n            \"name\": \"coredns\",\n            \"resources\": {\n              \"limits\": {\n                \"memory\": \"512Mi\",\n                \"cpu\": \"500m\"\n              }\n            }\n          }\n        ]\n      }\n    }\n  }\n}'\n```\n\n### 9.7.4 网络策略实践\n\n网络策略定义了容器间的流量控制规则，是微服务架构中的安全基础。\n\n#### 基本网络策略\n\n**默认拒绝所有入站流量的策略：**\n\n```yaml\napiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  name: default-deny-ingress\n  namespace: default\nspec:\n  podSelector: {}\n  policyTypes:\n  - Ingress\n  # 不指定 ingress 规则，表示拒绝所有入站流量\n```\n\n**允许特定来源的入站流量：**\n\n```yaml\napiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  name: allow-from-frontend\nspec:\n  podSelector:\n    matchLabels:\n      tier: backend\n  policyTypes:\n  - Ingress\n  ingress:\n  - from:\n    - podSelector:\n        matchLabels:\n          tier: frontend\n    ports:\n    - protocol: TCP\n      port: 8080\n```\n\n**允许出站流量到数据库：**\n\n```yaml\napiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  name: allow-backend-to-db\nspec:\n  podSelector:\n    matchLabels:\n      tier: backend\n  policyTypes:\n  - Egress\n  egress:\n  # 允许到数据库的流量\n  - to:\n    - podSelector:\n        matchLabels:\n          tier: database\n    ports:\n    - protocol: TCP\n      port: 5432\n  # 允许 DNS 查询\n  - to:\n    - namespaceSelector: {}\n    ports:\n    - protocol: UDP\n      port: 53\n  # 允许到外部 API 的流量\n  - to:\n    - ipBlock:\n        cidr: 0.0.0.0/0\n        except:\n        - 169.254.169.254/32  # 阻止元数据服务\n    ports:\n    - protocol: TCP\n      port: 443\n```\n\n#### 微服务网络策略示例\n\n```yaml\n---\n# 拒绝所有默认\napiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  name: default-deny-all\nspec:\n  podSelector: {}\n  policyTypes:\n  - Ingress\n  - Egress\n\n---\n# Frontend 容器策略\napiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  name: allow-frontend\nspec:\n  podSelector:\n    matchLabels:\n      app: frontend\n  policyTypes:\n  - Ingress\n  - Egress\n  ingress:\n  # 允许来自 Ingress Controller 的流量\n  - from:\n    - namespaceSelector:\n        matchLabels:\n          name: ingress-nginx\n    - podSelector:\n        matchLabels:\n          app: ingress-controller\n    ports:\n    - protocol: TCP\n      port: 3000\n  egress:\n  # 允许到 API 的流量\n  - to:\n    - podSelector:\n        matchLabels:\n          app: api\n    ports:\n    - protocol: TCP\n      port: 8080\n  # 允许 DNS\n  - to:\n    - namespaceSelector: {}\n      podSelector:\n        matchLabels:\n          k8s-app: kube-dns\n    ports:\n    - protocol: UDP\n      port: 53\n\n---\n# API 容器策略\napiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  name: allow-api\nspec:\n  podSelector:\n    matchLabels:\n      app: api\n  policyTypes:\n  - Ingress\n  - Egress\n  ingress:\n  - from:\n    - podSelector:\n        matchLabels:\n          app: frontend\n    ports:\n    - protocol: TCP\n      port: 8080\n  egress:\n  # 允许到数据库的流量\n  - to:\n    - podSelector:\n        matchLabels:\n          app: postgres\n    ports:\n    - protocol: TCP\n      port: 5432\n  # 允许 DNS\n  - to:\n    - namespaceSelector: {}\n      podSelector:\n        matchLabels:\n          k8s-app: kube-dns\n    ports:\n    - protocol: UDP\n      port: 53\n\n---\n# 数据库容器策略\napiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  name: allow-postgres\nspec:\n  podSelector:\n    matchLabels:\n      app: postgres\n  policyTypes:\n  - Ingress\n  ingress:\n  - from:\n    - podSelector:\n        matchLabels:\n          app: api\n    ports:\n    - protocol: TCP\n      port: 5432\n```\n\n#### 使用 Calico/Cilium 的高级网络策略\n\n**L7 应用层策略（仅 Cilium 支持）：**\n\n```yaml\napiVersion: \"cilium.io/v2\"\nkind: CiliumNetworkPolicy\nmetadata:\n  name: \"api-gateway-policy\"\nspec:\n  description: \"L7 policy for API gateway\"\n  endpointSelector:\n    matchLabels:\n      app: api\n  ingress:\n  - fromEndpoints:\n    - matchLabels:\n        app: frontend\n    toPorts:\n    - ports:\n      - port: \"8080\"\n        protocol: TCP\n      rules:\n        http:\n        # 允许 GET /api/users\n        - method: \"GET\"\n          path: \"/api/users/.*\"\n        # 允许 POST /api/users 仅从管理员来源\n        - method: \"POST\"\n          path: \"/api/users\"\n          sourceIPs:\n          - \"10.0.0.0/8\"\n```\n\n### 9.7.5 跨主机容器通信方案对比\n\n#### 方案对比表\n\n| 方案 | 隔离性 | 性能 | 复杂度 | 适用场景 |\n|------|--------|------|--------|---------|\n| 主机网络 | ✗ | ⭐⭐⭐⭐⭐ | 低 | 高性能，单主机 |\n| Bridge + Host Port | 中 | ⭐⭐⭐⭐ | 低 | 小规模集群 |\n| Overlay (VXLAN) | ✓ | ⭐⭐⭐ | 中 | 跨域通信 |\n| BGP (Calico) | ✓ | ⭐⭐⭐⭐ | 中 | 大规模集群 |\n| eBPF (Cilium) | ✓ | ⭐⭐⭐⭐⭐ | 高 | 高性能大集群 |\n\n#### 选择建议\n\n```bash\n# 1. 开发环境：使用 Bridge 网络\ndocker network create my-app\ndocker compose up  # 默认使用 bridge\n\n# 2. 小规模生产（< 50 节点）：使用 Flannel\nkubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml\n\n# 3. 中等规模（50-500 节点）：使用 Calico\nkubeadm init --pod-network-cidr=192.168.0.0/16\nkubectl apply -f https://docs.projectcalico.org/v3.24/manifests/tigera-operator.yaml\n\n# 4. 大规模（> 500 节点）或需要 L7 策略：使用 Cilium\nhelm install cilium cilium/cilium --namespace kube-system\n\n# 5. 需要多云/跨域：使用 Weave\n```\n\n### 9.7.6 网络故障排查\n\n**常见网络问题诊断：**\n\n```bash\n# 1. 容器无法访问外部网络\ndocker exec <container> ping 8.8.8.8\ndocker exec <container> cat /etc/resolv.conf\ndocker logs <container> | grep -i network\n\n# 2. 容器间无法通信\ndocker network inspect <network>\ndocker exec <container1> ping <container2>\n\n# 3. 端口映射失效\ndocker port <container>\nnetstat -tlnp | grep <port>\n\n# 4. DNS 解析失败\ndocker exec <container> nslookup example.com\ndocker exec <container> cat /etc/hosts\n\n# 5. 网络延迟\ndocker run --rm --network host iperf3:latest -c <target>\ndocker exec <container> mtr -r example.com\n\n# 使用 tcpdump 抓包分析\ndocker run --rm --cap-add NET_ADMIN --network host \\\n  corfr/tcpdump -i eth0 -n \"port 80\"\n```\n"
  },
  {
    "path": "09_network/README.md",
    "content": "# 第九章 网络配置\n\nDocker 容器需要网络来与外部世界通信、容器之间相互通信以及与宿主机通信。Docker 在安装时会自动配置网络基础设施，大多数情况下开箱即用。\n\n## 概述\n\nDocker 启动时自动创建以下网络组件：\n\n```mermaid\ngraph TD\n    subgraph Host [宿主机]\n        eth0[物理网卡 eth0<br>192.168.1.100]\n        docker0[docker0 网桥<br>172.17.0.1]\n        \n        subgraph Containers\n            subgraph ContainerA [容器 A]\n                eth0_A[eth0<br>172.17.0.2]\n            end\n            subgraph ContainerB [容器 B]\n                eth0_B[eth0<br>172.17.0.3]\n            end\n        end\n        \n        eth0 <--> docker0\n        docker0 <--> eth0_A\n        docker0 <--> eth0_B\n    end\n    \n    Internet((互联网)) <--> eth0\n```\n\n本章将详细介绍 Docker 网络配置的各个方面。\n\n## 本章内容\n\n* [配置 DNS](9.1_dns.md)\n* [网络类型](9.2_network_types.md)\n* [自定义网络](9.3_custom_network.md)\n* [容器互联](9.4_container_linking.md)\n* [外部访问容器](9.5_port_mapping.md)\n* [网络隔离](9.6_network_isolation.md)\n* [高级网络配置](9.7_advanced_networking.md)\n"
  },
  {
    "path": "09_network/summary.md",
    "content": "## 本章小结\n\n本章介绍了 Docker 网络配置的各个方面：\n\n| 概念 | 要点 |\n|------|------|\n| **DNS 配置** | 自定义网络支持嵌入式 DNS，可通过容器名解析 |\n| **网络类型** | bridge (默认)、host、none、overlay、macvlan |\n| **自定义网络** | 推荐使用，支持容器名 DNS 解析和更好的隔离 |\n| **容器互联** | 同一自定义网络内容器可直接通过容器名通信 |\n| **端口映射** | `-p 宿主机端口:容器端口` 暴露服务到外部 |\n| **网络隔离** | 不同网络默认隔离，增强安全性 |\n| **--link** | 已废弃，使用自定义网络替代 |\n\n### 延伸阅读\n\n- [配置 DNS](9.1_dns.md)：自定义 DNS 设置\n- [网络类型](9.2_network_types.md)：Bridge、Host、None 等网络模式\n- [自定义网络](9.3_custom_network.md)：创建和管理自定义网络\n- [容器互联](9.4_container_linking.md)：容器间通信方式\n- [端口映射](9.5_port_mapping.md)：高级端口配置\n- [网络隔离](9.6_network_isolation.md)：网络安全与隔离策略\n- [EXPOSE 指令](../07_dockerfile/7.9_expose.md)：在 Dockerfile 中声明端口\n- [Compose 网络](../11_compose/11.5_compose_file.md)：Compose 中的网络配置\n---\n\n> 📝 **发现错误或有改进建议？** 欢迎提交 [Issue](https://github.com/yeasy/docker_practice/issues) 或 [PR](https://github.com/yeasy/docker_practice/pulls)。\n"
  },
  {
    "path": "10_buildx/10.1_buildkit.md",
    "content": "## 10.1 BuildKit\n\n**BuildKit** 是下一代的镜像构建组件，在 [moby/buildkit](https://github.com/moby/buildkit) 开源。\n\n> **重要**：自 Docker 23.0 起，BuildKit 已成为 **默认稳定构建器**，无需手动启用。Docker Engine v29 进一步将 Containerd 镜像存储设为默认，提升与 Kubernetes 的互操作性。\n\n目前，Docker Hub 自动构建已经支持 BuildKit，具体请参考 [docker-practice/docker-hub-buildx](https://github.com/docker-practice/docker-hub-buildx)。\n\n### 10.1.1 `Dockerfile` 新增指令详解\n\nBuildKit 引入了多项新指令，旨在优化构建缓存和安全性。以下将详细介绍这些指令的用法。\n\n使用 BuildKit 后，我们可以使用下面几个新的 `Dockerfile` 指令来加快镜像构建。\n\n要使用最新的 Dockerfile 语法特性，建议在 Dockerfile 开头添加语法指令：\n\n```docker\n## syntax=docker/dockerfile:1\n\n```\n\n这将使用最新的稳定版语法解析器，确保你可以使用所有最新特性。\n\n#### `RUN --mount=type=cache`\n\n目前，几乎所有的程序都会使用依赖管理工具，例如 `Go` 中的 `go mod`、`Node.js` 中的 `npm` 等等，当我们构建一个镜像时，往往会重复的从互联网中获取依赖包，难以缓存，大大降低了镜像的构建效率。\n\n例如一个前端工程需要用到 `npm`：\n\n```docker\nFROM node:alpine as builder\n\nWORKDIR /app\n\nCOPY package.json /app/\n\nRUN npm i --registry=https://registry.npmmirror.com \\\n        && rm -rf ~/.npm\n\nCOPY src /app/src\n\nRUN npm run build\n\nFROM nginx:alpine\n\nCOPY --from=builder /app/dist /app/dist\n```\n\n使用多阶段构建，构建的镜像中只包含了目标文件夹 `dist`，但仍然存在一些问题，当 `package.json` 文件变动时，`RUN npm i && rm -rf ~/.npm` 这一层会重新执行，变更多次后，生成了大量的中间层镜像。\n\n为解决这个问题，进一步的我们可以设想一个类似 **数据卷** 的功能，在镜像构建时把 `node_modules` 文件夹挂载上去，在构建完成后，这个 `node_modules` 文件夹会自动卸载，实际的镜像中并不包含 `node_modules` 这个文件夹，这样我们就省去了每次获取依赖的时间，大大增加了镜像构建效率，同时也避免了生成了大量的中间层镜像。\n\n`BuildKit` 提供了 `RUN --mount=type=cache` 指令，可以实现上边的设想。\n\n```docker\n## syntax=docker/dockerfile:1\n\nFROM node:alpine as builder\n\nWORKDIR /app\n\nCOPY package.json /app/\n\nRUN --mount=type=cache,target=/app/node_modules,id=my_app_npm_module,sharing=locked \\\n    --mount=type=cache,target=/root/.npm,id=npm_cache \\\n        npm i --registry=https://registry.npmmirror.com\n\nCOPY src /app/src\n\nRUN --mount=type=cache,target=/app/node_modules,id=my_app_npm_module,sharing=locked \\\n## --mount=type=cache,target=/app/dist,id=my_app_dist,sharing=locked \\\n\n        npm run build\n\nFROM nginx:alpine\n\n## COPY --from=builder /app/dist /app/dist\n\n## 为了更直观的说明 from 和 source 指令，这里使用 RUN 指令\n\nRUN --mount=type=cache,target=/tmp/dist,from=builder,source=/app/dist \\\n    # --mount=type=cache,target=/tmp/dist,from=my_app_dist,sharing=locked \\\n\n    mkdir -p /app/dist && cp -r /tmp/dist/* /app/dist\n```\n\n\n第一个 `RUN` 指令执行后，`id` 为 `my_app_npm_module` 的缓存文件夹挂载到了 `/app/node_modules` 文件夹中。多次执行也不会产生多个中间层镜像。\n\n第二个 `RUN` 指令执行时需要用到 `node_modules` 文件夹，`node_modules` 已经挂载，命令也可以正确执行。\n\n第三个 `RUN` 指令将上一阶段产生的文件复制到指定位置，`from` 指明缓存的来源，这里 `builder` 表示缓存来源于构建的第一阶段，`source` 指明缓存来源的文件夹。\n\n上面的 `Dockerfile` 中 `--mount=type=cache,...` 中指令作用如下：\n\n|Option               |Description|\n|---------------------|-----------|\n|`id`                 | `id` 设置一个标志，以便区分缓存。|\n|`target` (必填项)     | 缓存的挂载目标文件夹。|\n|`ro`,`readonly`      | 只读，缓存文件夹不能被写入。 |\n|`sharing`            | 有 `shared` `private` `locked` 值可供选择。`sharing` 设置当一个缓存被多次使用时的表现，由于 `BuildKit` 支持并行构建，当多个步骤使用同一缓存时 (同一 `id`) 会发生冲突。`shared` 表示多个步骤可以同时读写，`private` 表示当多个步骤使用同一缓存时，每个步骤使用不同的缓存，`locked` 表示当一个步骤完成释放缓存后，后一个步骤才能继续使用该缓存。|\n|`from`               | 缓存来源 (构建阶段)，不填写时为空文件夹。|\n|`source`             | 来源的文件夹路径。|\n\n#### `RUN --mount=type=bind`\n\n该指令可以将一个镜像 (或上一构建阶段) 的文件挂载到指定位置。\n\n```docker\n## syntax=docker/dockerfile:1\n\nRUN --mount=type=bind,from=php:alpine,source=/usr/local/bin/docker-php-entrypoint,target=/docker-php-entrypoint \\\n        cat /docker-php-entrypoint\n```\n\n#### `RUN --mount=type=tmpfs`\n\n该指令可以将一个 `tmpfs` 文件系统挂载到指定位置。\n\n```docker\n## syntax=docker/dockerfile:1\n\nRUN --mount=type=tmpfs,target=/temp \\\n        mount | grep /temp\n```\n\n#### `RUN --mount=type=secret`\n\n该指令可以将一个文件 (例如密钥) 挂载到指定位置。\n\n```docker\n## syntax=docker/dockerfile:1\n\nRUN --mount=type=secret,id=aws,target=/root/.aws/credentials \\\n        cat /root/.aws/credentials\n```\n\n```bash\n$ docker build -t test --secret id=aws,src=$HOME/.aws/credentials .\n```\n\n#### `RUN --mount=type=ssh`\n\n该指令可以挂载 `ssh` 密钥。\n\n```docker\n## syntax=docker/dockerfile:1\n\nFROM alpine\nRUN apk add --no-cache openssh-client\nRUN mkdir -p -m 0700 ~/.ssh && ssh-keyscan gitlab.com >> ~/.ssh/known_hosts\nRUN --mount=type=ssh ssh git@gitlab.com | tee /hello\n```\n\n```bash\n$ eval $(ssh-agent)\n$ ssh-add ~/.ssh/id_rsa\n(Input your passphrase here)\n$ docker build -t test --ssh default=$SSH_AUTH_SOCK .\n```\n\n### 10.1.2 使用 `docker compose build` 与 BuildKit\n\nDocker Compose 同样支持 BuildKit，这使得多服务应用的构建更加高效。\n\n自 Docker 23.0 起，BuildKit 已默认启用，无需额外配置。如果使用旧版本，可设置 `DOCKER_BUILDKIT=1` 环境变量启用。\n\n### 10.1.3 官方文档\n\n* https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/experimental.md\n"
  },
  {
    "path": "10_buildx/10.2_buildx.md",
    "content": "## 10.2 使用 buildx 构建镜像\n\n### 10.2.1 使用\n\nBuildx 的使用非常直观，绝大多数情况下可以替代 `docker build` 命令。\n\n你可以直接使用 `docker buildx build` 命令构建镜像。\n\n```bash\n$ docker buildx build .\n[+] Building 8.4s (23/32)\n => ...\n```\n\nBuildx 使用 [BuildKit 引擎](10.1_buildkit.md)进行构建，支持许多新的功能，具体参考 [Buildkit](10.1_buildkit.md) 一节。\n\n#### 使用 `bake`\n\n`docker buildx bake` 是一个高级构建命令，支持从 HCL、JSON 或 Compose 文件中定义构建目标，实现复杂的流水线构建。\n\n```bash\n## 从 Compose 文件构建所有服务\n\n$ docker buildx bake\n\n## 仅构建指定目标\n\n$ docker buildx bake web\n```\n\n#### 生成 SBOM\n\nBuildx 支持在构建时直接生成 SBOM (Software Bill of Materials)，这对于软件供应链安全至关重要。\n\n```bash\n$ docker buildx build --sbom=true -t myimage .\n```\n\n该命令会在构建结果中包含 SPDX 或 CycloneDX 格式的 SBOM 数据。\n\n> **⚠️ 注意与失败模式**：\n> 要使 SBOM (或其它 attestation 元数据) 成功附着并可见，对底层的存储格式有前置要求：默认的 classic image store 不支持 manifest list/index 这种存放 attestation 的结构。\n>\n> 如果只简单运行上述命令，你可能会面临 **”命令成功执行，但本地镜像中看不到 SBOM”** 的体会落差。\n>\n> **正确的解决路径有两条**：\n> 1. **推送到远端仓库**：使用 `docker buildx build --sbom=true --push -t myimage:tag` 时，SBOM 会正确保存到远端仓库。远端 OCI 兼容的镜像仓库能够完整存储这些元数据。\n> 2. **启用 containerd image store**：在 Docker 守护进程中启用 `containerd image store` 特性（Docker v29+，现代 Docker Desktop 默认推荐），可以在本地查看和管理 SBOM 等 attestation 元数据。\n\n### 10.2.2 官方文档\n\n* [Docker buildx 命令文档](https://docs.docker.com/engine/reference/commandline/buildx/)\n"
  },
  {
    "path": "10_buildx/10.3_multi-arch-images.md",
    "content": "## 10.3 使用 buildx 构建多种系统架构支持的 Docker 镜像\n\nDocker 镜像可以支持多种系统架构，这意味着你可以在 `x86_64`、`arm64` 等不同架构的机器上运行同一个镜像。这是通过一个名为 “manifest list” (或称为 “fat manifest”) 的文件来实现的。\n\n### 10.3.1 Manifest List 是什么？\n\n为了理解多架构镜像的原理，我们需要先了解 Manifest List 的概念。\n\nManifest list 是一个包含了多个指向不同架构镜像的 manifest 的文件。当你拉取一个支持多架构的镜像时，Docker 会自动根据你当前的系统架构选择并拉取对应的镜像。\n\n例如，官方的 `hello-world` 镜像就支持多种架构。你可以使用 `docker manifest inspect` 命令来查看它的 manifest list：\n\n```bash\n$ docker manifest inspect hello-world\n{\n   \"schemaVersion\": 2,\n   \"mediaType\": \"application/vnd.docker.distribution.manifest.list.v2+json\",\n   \"manifests\": [\n      {\n         \"mediaType\": \"application/vnd.docker.distribution.manifest.v2+json\",\n         \"size\": 525,\n         \"digest\": \"sha256:80852a401a974d9e923719a948cc5335a0a4435be8778b475844a7153a2382e5\",\n         \"platform\": {\n            \"architecture\": \"amd64\",\n            \"os\": \"linux\"\n         }\n      },\n      {\n         \"mediaType\": \"application/vnd.docker.distribution.manifest.v2+json\",\n         \"size\": 525,\n         \"digest\": \"sha256:3adea81344be1724b383d501736c3852939b33b3903d02474373700b25e5d6e3\",\n         \"platform\": {\n            \"architecture\": \"arm\",\n            \"os\": \"linux\",\n            \"variant\": \"v5\"\n         }\n      },\n      // ... more architectures\n   ]\n}\n```\n\n### 10.3.2 使用 `docker buildx` 构建多架构镜像\n\n`docker buildx` 是构建多架构镜像的最佳实践工具，它屏蔽了底层的复杂性，提供了一键构建多架构镜像的能力。\n\n在 Docker 19.03+ 版本中，`docker buildx` 是推荐的用于构建多架构镜像的工具。它使用 `BuildKit` 作为后端，可以大大简化构建过程。\n\n#### 新建 `builder` 实例\n\n首先，你需要创建一个新的 `builder` 实例，因为它支持同时为多个平台构建。\n\n```bash\n$ docker buildx create --name mybuilder --use\n$ docker buildx inspect --bootstrap\n```\n\n#### 构建和推送\n\n使用 `docker buildx build` 命令并指定 `--platform` 参数，可以同时构建支持多种架构的镜像。`--push` 参数会将构建好的镜像和 manifest list 推送到 Docker 仓库。\n\n```dockerfile\n## Dockerfile\n\nFROM --platform=$TARGETPLATFORM alpine\n\nRUN uname -a > /os.txt\n\nCMD cat /os.txt\n```\n\n```bash\n$ docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t your-username/multi-arch-image . --push\n```\n\n构建完成后，你就可以在不同架构的机器上拉取并运行 `your-username/multi-arch-image` 这个镜像了。\n\n#### 架构相关的构建参数\n\n在 `Dockerfile` 中，你可以使用一些预定义的构建参数来根据目标平台定制构建过程：\n\n*   `TARGETPLATFORM`：构建镜像的目标平台，例如 `linux/amd64`。\n*   `TARGETOS`：目标平台的操作系统，例如 `linux`。\n*   `TARGETARCH`：目标平台的架构，例如 `amd64`。\n*   `TARGETVARIANT`：目标平台的变种，例如 `v7`。\n*   `BUILDPLATFORM`：构建环境的平台。\n*   `BUILDOS`：构建环境的操作系统。\n*   `BUILDARCH`：构建环境的架构。\n*   `BUILDVARIANT`：构建环境的变种。\n\n例如，你可以这样编写 `Dockerfile` 来拷贝特定架构的二进制文件：\n\n```dockerfile\nFROM scratch\n\nARG TARGETOS\nARG TARGETARCH\n\nCOPY bin/dist-${TARGETOS}-${TARGETARCH} /dist\n\nENTRYPOINT [\"/dist\"]\n```\n\n### 10.3.3 使用 `docker manifest`：底层工具\n\n除了 `docker buildx`，我们也可以直接操作 Manifest List 来手动组合不同架构的镜像。\n\n`docker manifest` 是一个更底层的命令，可以用来创建、检查和推送 manifest list。虽然 `docker buildx` 在大多数情况下更方便，但了解 `docker manifest` 仍然有助于理解其工作原理。\n\n#### 创建 manifest list\n\n```bash\n## 首先，为每个架构构建并推送镜像\n\n$ docker buildx build --platform linux/amd64 -t your-username/my-app:amd64 . --push\n$ docker buildx build --platform linux/arm64 -t your-username/my-app:arm64 . --push\n\n## 然后，创建一个 manifest list，将它们组合在一起\n\n$ docker manifest create your-username/my-app:latest \\\n    --amend your-username/my-app:amd64 \\\n    --amend your-username/my-app:arm64\n\n## 最后，推送 manifest list\n\n$ docker manifest push your-username/my-app:latest\n```\n\n#### 检查 manifest list\n\n你可以使用 `docker manifest inspect` 来查看一个 manifest list 的详细信息，如上文所示。\n"
  },
  {
    "path": "10_buildx/README.md",
    "content": "# 第十章 Docker Buildx\n\nDocker Buildx 是一个 docker CLI 插件，其扩展了 docker 命令，支持 [Moby BuildKit](10.1_buildkit.md) 提供的功能。提供了与 docker build 相同的用户体验，并增加了许多新功能。\n\n> Buildx 需要 Docker v19.03+。在较新版本中已更常用且功能更完整。\n\n## 本章内容\n\n本章将详细介绍 Docker Buildx 的使用，包括：\n\n* [使用 BuildKit 构建镜像](10.1_buildkit.md)\n* [使用 Buildx 构建镜像](10.2_buildx.md)\n* [构建多种系统架构支持的 Docker 镜像](10.3_multi-arch-images.md)\n\n> **供应链安全与存储后端前瞻**：现代软件供应链中，镜像来源证明（Provenance，在 BuildKit 中默认以 `mode=min` 添加）和软件物料清单（SBOM，可通过 `--sbom=true` 显式开启）已经成为极其重要的构建产出。这些 Attestations 数据会作为 manifest 附着在 **镜像索引 (Image Index)** 上。\n> 正是基于此诉求，自 Docker Engine v29 开始默认启用的 `containerd image store` 提供对 Image Index 的完美本地支持能力，解决了传统经典存储后端（Classic Store）无法有效处理带 Attestations 镜像索引的瓶颈。这使得你可以利用 `docker buildx imagetools inspect` 等手段，甚至做到无需拉取完整镜像内容即可在 Registry 或本地高效校验镜像的安全元数据。\n"
  },
  {
    "path": "10_buildx/summary.md",
    "content": "## 本章小结\n\nDocker Buildx 是 Docker 构建系统的重要进化，提供了高效、安全且支持多平台的镜像构建能力。\n\n| 概念 | 要点 |\n|------|------|\n| **BuildKit** | 下一代构建引擎，Docker 23.0+ 默认启用 |\n| **缓存挂载** | `RUN --mount=type=cache` 加速依赖安装 |\n| **Secret 挂载** | `RUN --mount=type=secret` 安全传递密钥 |\n| **buildx build** | 替代 `docker build`，支持更多构建功能 |\n| **多架构构建** | `--platform` 参数一键构建多种架构镜像 |\n| **Manifest List** | 多架构镜像的索引文件 |\n| **SBOM** | 通过 `--sbom=true` 生成软件物料清单 |\n\n### 10.4.1 延伸阅读\n\n- [Dockerfile 指令详解](../07_dockerfile/README.md)：Dockerfile 编写基础\n- [多阶段构建](../07_dockerfile/7.17_multistage_builds.md)：优化镜像体积\n- [Dockerfile 最佳实践](../appendix/best_practices.md)：编写高效 Dockerfile\n---\n\n> 📝 **发现错误或有改进建议？** 欢迎提交 [Issue](https://github.com/yeasy/docker_practice/issues) 或 [PR](https://github.com/yeasy/docker_practice/pulls)。\n"
  },
  {
    "path": "11_compose/11.1_introduction.md",
    "content": "## 11.1 简介\n\n`Compose` 项目是 Docker 官方的开源项目，负责实现对 Docker 容器集群的快速编排。从功能上看，跟 `OpenStack` 中的 `Heat` 十分类似。\n\n其代码目前在 [docker/compose 仓库](https://github.com/docker/compose) 上开源。\n\n`Compose` 定位是 “定义和运行多个 Docker 容器的应用 (Defining and running multi-container Docker applications)”，其前身是开源项目 Fig。\n\n通过第一部分中的介绍，我们知道使用一个 `Dockerfile` 模板文件，可以让用户很方便的定义一个单独的应用容器。然而，在日常工作中，经常会碰到需要多个容器相互配合来完成某项任务的情况。例如要实现一个 Web 项目，除了 Web 服务容器本身，往往还需要再加上后端的数据库服务容器，甚至还包括负载均衡容器等。\n\n`Compose` 恰好满足了这样的需求。它允许用户通过一个单独的 `compose.yaml` (历史默认名也常见为 `docker-compose.yml`) 模板文件 (YAML 格式) 来定义一组相关联的应用容器为一个项目 (project)。\n\n### 11.1.1 概述\n\nDocker Compose 让用户能够以声明式方式定义和管理多容器应用。它的核心价值在于：用一个 YAML 文件取代一连串手动的 `docker run` 命令，使得复杂应用的启动、停止和重建变得一键可达。\n\n对于开发团队而言，Compose 解决了三个关键问题：环境一致性（“在我机器上能跑”的问题）、服务依赖管理（确保数据库在应用之前启动）、以及开发-测试-生产的配置差异管理（通过 `compose.override.yaml` 实现多环境适配）。\n\n### 11.1.2 模板文件规范\n\nCompose 模板文件采用 YAML 格式，扩展名为 `.yml` 或 `.yaml`。\n\n> **注意**：自 Compose V2 起，`version` 字段已不再强制要求。在 Docker Compose v5 中，规范已完全不需要顶层 `version` 字段。为了保持最佳兼容性，建议不在新文件中使用该字段。\n\nDocker Compose 默认使用 `compose.yaml`，也兼容 `compose.yml`、`docker-compose.yaml`、`docker-compose.yml` 等文件名。\n\n`Compose` 中有两个重要的概念：\n\n* 服务 (`service`)：一个应用的容器，实际上可以包括若干运行相同镜像的容器实例。\n\n* 项目 (`project`)：由一组关联的应用容器组成的一个完整业务单元，在 Compose 文件中定义。\n\n`Compose` 的默认管理对象是项目，通过子命令对项目中的一组容器进行便捷地生命周期管理。\n\n`Compose` 项目早期由 Python 编写，称为 Docker Compose V1。\n\n现在的 Docker Compose V2 是一个 Go 语言编写的 Docker CLI 插件，已经集成到 Docker Desktop 和 Docker Engine 中，直接通过 `docker compose` 命令使用。它提供了更快的性能和更好的集成体验。\n\n只要所操作的平台支持 Docker API，就可以在其上利用 `Compose` 来进行编排管理。\n"
  },
  {
    "path": "11_compose/11.2_install.md",
    "content": "## 11.2 安装与卸载\n\n`Compose` 是 Docker 官方的开源项目，负责实现对 Docker 容器集群的快速编排。\n\n从 `v2` 版本开始，`Compose` 作为 `docker` 的子命令存在，例如 `docker compose up`。\n\n`Compose` 支持 Linux、macOS、Windows 10 三大平台。\n\n`Docker Desktop for Mac/Windows` 自带 `docker compose` 命令。\n\nLinux 系统请使用以下介绍的方法安装。\n\n### 11.2.1 Linux\n\n在 Linux 上，你可以通过下载 Docker Compose CLI 插件 (二进制文件名为 `docker-compose`) 来安装。\n\n从[官方 GitHub Release](https://github.com/docker/compose/releases) 处直接下载编译好的二进制文件即可。\n\n> **提示**：版本更新较快，请访问上述链接获取最新版本号，替换下方命令中的版本号。\n\n例如，在 Linux 64 位系统上直接下载对应的二进制包 (以 v5.1.0 为例)。\n\n```bash\n$ DOCKER_CONFIG=${DOCKER_CONFIG:-$HOME/.docker}\n$ mkdir -p $DOCKER_CONFIG/cli-plugins\n$ curl -SL https://github.com/docker/compose/releases/download/v5.1.0/docker-compose-linux-x86_64 -o $DOCKER_CONFIG/cli-plugins/docker-compose\n```\n\n之后，执行\n\n```bash\n$ chmod +x $DOCKER_CONFIG/cli-plugins/docker-compose\n```\n\n### 11.2.2 测试安装\n\n```bash\n$ docker compose version\nDocker Compose version v5.1.0\n```\n\n### 11.2.3 bash 补全命令\n\n```bash\n$ curl -L https://raw.githubusercontent.com/docker/compose/v5.1.0/contrib/completion/bash/docker-compose | sudo tee /etc/bash_completion.d/docker-compose > /dev/null\n```\n\n### 11.2.4 卸载\n\n如果是二进制包方式安装的，删除二进制文件即可。\n\n```bash\n$ rm $DOCKER_CONFIG/cli-plugins/docker-compose\n```\n"
  },
  {
    "path": "11_compose/11.3_usage.md",
    "content": "## 11.3 使用\n\n本节将通过一个具体的 Web 应用案例，介绍 Docker Compose 的基本概念和使用方法。\n\n### 11.3.1 术语\n\n首先介绍几个术语。\n\n* 服务 (`service`)：一个应用容器，实际上可以运行多个相同镜像的实例。\n\n* 项目 (`project`)：由一组关联的应用容器组成的一个完整业务单元。\n\n可见，一个项目可以由多个服务 (容器) 关联而成，`Compose` 面向项目进行管理。\n\n### 11.3.2 场景\n\n最常见的项目是 web 网站，该项目应该包含 web 应用和缓存。\n\n下面我们用 `Python` 来建立一个能够记录页面访问次数的 web 网站。\n\n#### web 应用\n\n新建文件夹，在该目录中编写 `app.py` 文件\n\n```python\nfrom flask import Flask\nfrom redis import Redis\n\napp = Flask(__name__)\nredis = Redis(host='redis', port=6379)\n\n@app.route('/')\ndef hello():\n    count = redis.incr('hits')\n    return 'Hello World! 该页面已被访问 {} 次。\\n'.format(count)\n\nif __name__ == \"__main__\":\n    app.run(host=\"0.0.0.0\", debug=True)\n```\n\n#### Dockerfile\n\n编写 `Dockerfile` 文件，内容为\n\n```docker\nFROM python:3.12-alpine\nADD . /code\nWORKDIR /code\nRUN pip install redis flask\nCMD [\"python\", \"app.py\"]\n```\n\n#### compose.yaml\n\n编写 `compose.yaml` 文件，这是 Compose 推荐使用的主模板文件 (也兼容 `docker-compose.yml` 等历史文件名)。\n\n```yaml\nservices:\n  web:\n    build: .\n    ports:\n     - \"5000:5000\"\n\n  redis:\n    image: \"redis:alpine\"\n```\n\n#### 运行 compose 项目\n\n```bash\n$ docker compose up\n```\n\n此时访问本地 `5000` 端口，每次刷新页面，计数就会加 1。\n\n\n按下 `Ctrl-C` 停止项目。\n\n#### 后台运行\n\n```bash\n$ docker compose up -d\n```\n\n#### 停止\n\n```bash\n$ docker compose stop\n```\n\n#### 进入服务\n\n```bash\n$ docker compose exec redis sh\n/data # redis-cli\n127.0.0.1:6379> get hits\n\"9\"\n```\n\n#### 查看日志\n\n```bash\n$ docker compose logs -f\n```\n\n#### 构建镜像\n\n```bash\n$ docker compose build\n```\n\n#### 启动服务\n\n```bash\n$ docker compose start\n```\n\n#### 运行一次性命令\n\n```bash\n$ docker compose run web python app.py\n```\n\n#### 验证 Compose 文件\n\n```bash\n$ docker compose config\n```\n\n#### 删除项目\n\n```bash\n$ docker compose down\n```\n"
  },
  {
    "path": "11_compose/11.4_commands.md",
    "content": "## 11.4 命令说明\n\nDocker Compose 提供了丰富的命令来管理项目和容器。本节将详细介绍这些命令的使用格式和常用选项。\n\n### 11.4.1 命令对象与格式\n\n对于 Compose 来说，大部分命令的对象既可以是项目本身，也可以指定为项目中的服务或者容器。如果没有特别的说明，命令对象将是项目，这意味着项目中所有的服务都会受到命令影响。\n\n执行 `docker compose [COMMAND] --help` 或者 `docker compose help [COMMAND]` 可以查看具体某个命令的使用格式。\n\n`docker compose` 命令的基本的使用格式是\n\n```bash\ndocker compose [-f=<arg>...] [options] [COMMAND] [ARGS...]\n```\n\n### 11.4.2 命令选项\n\n* `-f, --file FILE` 指定使用的 Compose 模板文件。默认会自动识别 `compose.yaml` (也兼容 `docker-compose.yml` 等)，并且可以多次指定。\n\n* `-p, --project-name NAME` 指定项目名称，默认将使用所在目录名称作为项目名。\n\n* `--verbose` 输出更多调试信息。\n\n* `-v, --version` 打印版本并退出。\n\n### 11.4.3 命令使用说明\n\n#### `build`\n\n格式为 `docker compose build [options] [SERVICE...]`。\n\n构建 (重新构建) 项目中的服务容器。\n\n服务容器一旦构建后，将会带上一个标记名，例如对于 web 项目中的一个 db 容器，可能是 web_db。\n\n可以随时在项目目录下运行 `docker compose build` 来重新构建服务。\n\n选项包括：\n\n* `--force-rm` 删除构建过程中的临时容器。\n\n* `--no-cache` 构建镜像过程中不使用 cache (这将加长构建过程)。\n\n* `--pull` 始终尝试通过 pull 来获取更新版本的镜像。\n\n#### `config`\n\n验证 Compose 文件格式是否正确，若正确则显示配置，若格式错误显示错误原因。\n\n#### `down`\n\n此命令将会停止 `up` 命令所启动的容器，并移除网络\n\n#### `exec`\n\n进入指定的容器。\n\n#### `help`\n\n获得一个命令的帮助。\n\n#### `images`\n\n列出 Compose 文件中包含的镜像。\n\n#### `kill`\n\n格式为 `docker compose kill [options] [SERVICE...]`。\n\n通过发送 `SIGKILL` 信号来强制停止服务容器。\n\n支持通过 `-s` 参数来指定发送的信号，例如通过如下指令发送 `SIGINT` 信号。\n\n```bash\n$ docker compose kill -s SIGINT\n```\n\n#### `logs`\n\n格式为 `docker compose logs [options] [SERVICE...]`。\n\n查看服务容器的输出。默认情况下，docker compose 将对不同的服务输出使用不同的颜色来区分。可以通过 `--no-color` 来关闭颜色。\n\n该命令在调试问题的时候十分有用。\n\n#### `pause`\n\n格式为 `docker compose pause [SERVICE...]`。\n\n暂停一个服务容器。\n\n#### `port`\n\n格式为 `docker compose port [options] SERVICE PRIVATE_PORT`。\n\n打印某个容器端口所映射的公共端口。\n\n选项：\n\n* `--protocol=proto` 指定端口协议，tcp (默认值) 或者 udp。\n\n* `--index=index` 如果同一服务存在多个容器，指定命令对象容器的序号 (默认为 1)。\n\n#### `ps`\n\n格式为 `docker compose ps [options] [SERVICE...]`。\n\n列出项目中目前的所有容器。\n\n选项：\n\n* `-q` 只打印容器的 ID 信息。\n\n#### `pull`\n\n格式为 `docker compose pull [options] [SERVICE...]`。\n\n拉取服务依赖的镜像。\n\n选项：\n\n* `--ignore-pull-failures` 忽略拉取镜像过程中的错误。\n\n#### `push`\n\n推送服务依赖的镜像到 Docker 镜像仓库。\n\n#### `restart`\n\n格式为 `docker compose restart [options] [SERVICE...]`。\n\n重启项目中的服务。\n\n选项：\n\n* `-t, --timeout TIMEOUT` 指定重启前停止容器的超时 (默认为 10 秒)。\n\n#### `rm`\n\n格式为 `docker compose rm [options] [SERVICE...]`。\n\n删除所有 (停止状态的) 服务容器。推荐先执行 `docker compose stop` 命令来停止容器。\n\n选项：\n\n* `-f, --force` 强制直接删除，包括非停止状态的容器。一般尽量不要使用该选项。\n\n* `-v` 删除容器所挂载的数据卷。\n\n#### `run`\n\n格式为 `docker compose run [options] [-p PORT...] [-e KEY=VAL...] SERVICE [COMMAND] [ARGS...]`。\n\n在指定服务上执行一个命令。\n\n例如：\n\n```bash\n$ docker compose run ubuntu ping docker.com\n```\n\n将会启动一个 ubuntu 服务容器，并执行 `ping docker.com` 命令。\n\n默认情况下，如果存在关联，则所有关联的服务将会自动被启动，除非这些服务已经在运行中。\n\n该命令类似启动容器后运行指定的命令，相关卷、链接等等都将会按照配置自动创建。\n\n两个不同点：\n\n* 给定命令将会覆盖原有的自动运行命令；\n\n* 不会自动创建端口，以避免冲突。\n\n如果不希望自动启动关联的容器，可以使用 `--no-deps` 选项，例如\n\n```bash\n$ docker compose run --no-deps web python manage.py shell\n```\n\n将不会启动 web 容器所关联的其它容器。\n\n选项：\n\n* `-d` 后台运行容器。\n\n* `--name NAME` 为容器指定一个名字。\n\n* `--entrypoint CMD` 覆盖默认的容器启动指令。\n\n* `-e KEY=VAL` 设置环境变量值，可多次使用选项来设置多个环境变量。\n\n* `-u, --user=\"\"` 指定运行容器的用户名或者 uid。\n\n* `--no-deps` 不自动启动关联的服务容器。\n\n* `--rm` 运行命令后自动删除容器，`d` 模式下将忽略。\n\n* `-p, --publish=[]` 映射容器端口到本地主机。\n\n* `--service-ports` 配置服务端口并映射到本地主机。\n\n* `-T` 不分配伪 tty，意味着依赖 tty 的指令将无法运行。\n\n#### `scale`\n\n格式为 `docker compose scale [options] [SERVICE=NUM...]`。\n\n设置指定服务运行的容器个数。\n\n通过 `service=num` 的参数来设置数量。例如：\n\n```bash\n$ docker compose scale web=3 db=2\n```\n\n将启动 3 个容器运行 web 服务，2 个容器运行 db 服务。\n\n> **提示**：部分版本的 Compose 可能不再提供独立的 `scale` 子命令 (或不推荐使用)。此时可使用 `docker compose up` 的 `--scale` 选项达到同样效果：\n>\n> ```bash\n> $ docker compose up -d --scale web=3 --scale db=2\n> ```\n\n一般的，当指定数目多于该服务当前实际运行容器，将新创建并启动容器；反之，将停止容器。\n\n选项：\n\n* `-t, --timeout TIMEOUT` 停止容器时候的超时 (默认为 10 秒)。\n\n#### `start`\n\n格式为 `docker compose start [SERVICE...]`。\n\n启动已经存在的服务容器。\n\n#### `stop`\n\n格式为 `docker compose stop [options] [SERVICE...]`。\n\n停止已经处于运行状态的容器，但不删除它。通过 `docker compose start` 可以再次启动这些容器。\n\n选项：\n\n* `-t, --timeout TIMEOUT` 停止容器时候的超时 (默认为 10 秒)。\n\n#### `top`\n\n查看各个服务容器内运行的进程。\n\n#### `unpause`\n\n格式为 `docker compose unpause [SERVICE...]`。\n\n恢复处于暂停状态中的服务。\n\n#### `up`\n\n格式为 `docker compose up [options] [SERVICE...]`。\n\n该命令十分强大，它将尝试自动完成包括构建镜像，(重新) 创建服务，启动服务，并关联服务相关容器的一系列操作。\n\n链接的服务都将会被自动启动，除非已经处于运行状态。\n\n可以说，大部分时候都可以直接通过该命令来启动一个项目。\n\n默认情况，`docker compose up` 启动的容器都在前台，控制台将会同时打印所有容器的输出信息，可以很方便进行调试。\n\n当通过 `Ctrl-C` 停止命令时，所有容器将会停止。\n\n如果使用 `docker compose up -d`，将会在后台启动并运行所有的容器。一般推荐生产环境下使用该选项。\n\n默认情况，如果服务容器已经存在，`docker compose up` 将会尝试停止容器，然后重新创建 (保持使用 `volumes-from` 挂载的卷)，以保证新启动的服务匹配 Compose 文件的最新内容。如果用户不希望容器被停止并重新创建，可以使用 `docker compose up --no-recreate`。这样将只会启动处于停止状态的容器，而忽略已经运行的服务。如果用户只想重新部署某个服务，可以使用 `docker compose up --no-deps -d <SERVICE_NAME>` 来重新创建服务并后台停止旧服务，启动新服务，并不会影响到其所依赖的服务。\n\n选项：\n\n* `-d` 在后台运行服务容器。\n\n* `--no-color` 不使用颜色来区分不同的服务的控制台输出。\n\n* `--no-deps` 不启动服务所链接的容器。\n\n* `--force-recreate` 强制重新创建容器，不能与 `--no-recreate` 同时使用。\n\n* `--no-recreate` 如果容器已经存在了，则不重新创建，不能与 `--force-recreate` 同时使用。\n\n* `--no-build` 不自动构建缺失的服务镜像。\n\n* `-t, --timeout TIMEOUT` 停止容器时候的超时 (默认为 10 秒)。\n\n#### `version`\n\n格式为 `docker compose version`。\n\n打印版本信息。\n\n#### `watch`\n\n格式为 `docker compose watch [options] [SERVICE...]`。\n\n启用开发模式，自动监视源代码并在文件发生变化时刷新服务。这需要项目中有 `compose.yaml` (或 `docker-compose.yml`)，且定义了 `x-develop` 或 `develop` 配置段。\n\n例如：\n\n```yaml\nservices:\n  web:\n    build: .\n    develop:\n      watch:\n        - action: sync\n          path: ./web\n          target: /src/web\n          ignore:\n            - node_modules/\n        - action: rebuild\n          path: package.json\n```\n\n选项：\n\n* `--no-up` 不自动启动服务。\n\n* `--quiet` 静默模式。\n"
  },
  {
    "path": "11_compose/11.5_compose_file.md",
    "content": "## 11.5 Compose 模板文件\n\n模板文件是使用 `Compose` 的核心，涉及到的指令关键字也比较多。但大家不用担心，这里面大部分指令跟 `docker run` 相关参数的含义都是类似的。\n\n默认的模板文件名称为 `compose.yaml` (也兼容 `docker-compose.yml` 等历史文件名)，格式为 YAML。\n\n```yaml\nservices:\n  webapp:\n    image: examples/web\n    ports:\n      - \"80:80\"\n    volumes:\n      - \"/data\"\n```\n\n注意每个服务都必须通过 `image` 指令指定镜像或 `build` 指令 (需要 Dockerfile) 等来自动构建生成镜像。\n\n如果使用 `build` 指令，在 `Dockerfile` 中设置的选项 (例如：`CMD`、`EXPOSE`、`VOLUME`、`ENV` 等) 将会自动被获取，无需在 Compose 文件中重复设置。\n\n下面分别介绍各个指令的用法。\n\n### 11.5.1 `build`\n\n指定 `Dockerfile` 所在文件夹的路径 (可以是绝对路径，或者相对 Compose 文件的路径)。`Compose` 将会利用它自动构建这个镜像，然后使用这个镜像。\n\n```yaml\nservices:\n  webapp:\n    build: ./dir\n```\n\n你也可以使用 `context` 指令指定 `Dockerfile` 所在文件夹的路径。\n\n使用 `dockerfile` 指令指定 `Dockerfile` 文件名。\n\n使用 `arg` 指令指定构建镜像时的变量。\n\n```yaml\nservices:\n  webapp:\n    build:\n      context: ./dir\n      dockerfile: Dockerfile-alternate\n      args:\n        buildno: 1\n```\n\n使用 `cache_from` 指定构建镜像的缓存\n\n```yaml\nbuild:\n  context: .\n  cache_from:\n    - alpine:latest\n    - corp/web_app:3.14\n```\n\n### 11.5.2 `cap_add, cap_drop`\n\n指定容器的内核能力 (capacity) 分配。\n\n例如，让容器拥有所有能力可以指定为：\n\n```yaml\ncap_add:\n  - ALL\n```\n\n去掉 NET_ADMIN 能力可以指定为：\n\n```yaml\ncap_drop:\n  - NET_ADMIN\n```\n\n### 11.5.3 `command`\n\n覆盖容器启动后默认执行的命令。\n\n```yaml\ncommand: echo \"hello world\"\n```\n\n### 11.5.4 `configs`\n\n`configs` 来自 Compose Specification。它在 Swarm 中是原生对象；在本地 `docker compose` 模式下通常以文件挂载的形式实现，具体能力取决于 Compose 版本与运行平台。\n\n### 11.5.5 `cgroup_parent`\n\n指定父 `cgroup` 组，意味着将继承该组的资源限制。\n\n例如，创建了一个 cgroup 组名称为 `cgroups_1`。\n\n```yaml\ncgroup_parent: cgroups_1\n```\n\n### 11.5.6 `container_name`\n\n指定容器名称。默认将会使用 `项目名称_服务名称_序号` 这样的格式。\n\n```yaml\ncontainer_name: docker-web-container\n```\n\n> 注意：指定容器名称后，该服务将无法进行扩展 (scale)，因为 Docker 不允许多个容器具有相同的名称。\n\n### 11.5.7 `deploy`\n\n`deploy` 用于描述副本数、更新策略、资源限制等部署参数。该字段在 Swarm 中支持最完整；在本地 `docker compose up` 场景下通常只有部分字段生效。\n\n### 11.5.8 `devices`\n\n指定设备映射关系。\n\n```yaml\ndevices:\n  - \"/dev/ttyUSB1:/dev/ttyUSB0\"\n```\n\n### 11.5.9 `depends_on`\n\n解决容器的依赖、启动先后的问题。以下例子中会先启动 `redis` `db` 再启动 `web`\n\n```yaml\nservices:\n  web:\n    build: .\n    depends_on:\n      - db\n      - redis\n\n  redis:\n    image: redis\n\n  db:\n    image: postgres\n```\n\n> 注意：`web` 服务不会等待 `redis` `db` “完全启动” 之后才启动。\n\n### 11.5.10 `dns`\n\n自定义 `DNS` 服务器。可以是一个值，也可以是一个列表。\n\n```yaml\ndns: 8.8.8.8\n\ndns:\n  - 8.8.8.8\n  - 114.114.114.114\n```\n\n### 11.5.11 `dns_search`\n\n配置 `DNS` 搜索域。可以是一个值，也可以是一个列表。\n\n```yaml\ndns_search: example.com\n\ndns_search:\n  - domain1.example.com\n  - domain2.example.com\n```\n\n### 11.5.12 `tmpfs`\n\n挂载一个 tmpfs 文件系统到容器。\n\n```yaml\ntmpfs: /run\ntmpfs:\n  - /run\n  - /tmp\n```\n\n### 11.5.13 `env_file`\n\n从文件中获取环境变量，可以为单独的文件路径或列表。\n\n如果通过 `docker compose -f FILE` 方式来指定 Compose 模板文件，则 `env_file` 中变量的路径会基于模板文件路径。\n\n如果有变量名称与 `environment` 指令冲突，则按照惯例，以后者为准。\n\n```bash\nenv_file: .env\n\nenv_file:\n  - ./common.env\n  - ./apps/web.env\n  - /opt/secrets.env\n```\n\n环境变量文件中每一行必须符合格式，支持 `#` 开头的注释行。\n\n```bash\n## common.env: Set development environment\n\nPROG_ENV=development\n```\n\n### 11.5.14 `environment`\n\n设置环境变量。你可以使用数组或字典两种格式。\n\n只给定名称的变量会自动获取运行 Compose 主机上对应变量的值，可以用来防止泄露不必要的数据。\n\n```yaml\nenvironment:\n  RACK_ENV: development\n  SESSION_SECRET:\n\nenvironment:\n  - RACK_ENV=development\n  - SESSION_SECRET\n```\n\n如果变量名称或者值中用到 `true|false，yes|no` 等表达[布尔](https://yaml.org/type/bool.html)含义的词汇，最好放到引号里，避免 YAML 自动解析某些内容为对应的布尔语义。这些特定词汇，包括\n\n```bash\ny|Y|yes|Yes|YES|n|N|no|No|NO|true|True|TRUE|false|False|FALSE|on|On|ON|off|Off|OFF\n```\n\n### 11.5.15 `expose`\n\n暴露端口，但不映射到宿主机，只被连接的服务访问。\n\n仅可以指定内部端口为参数\n\n```yaml\nexpose:\n - \"3000\"\n - \"8000\"\n```\n\n### 11.5.16 `external_links`\n\n> 注意：不建议使用该指令。\n\n链接到 Compose 文件外部的容器，甚至并非 `Compose` 管理的外部容器。\n\n```yaml\nexternal_links:\n - redis_1\n - project_db_1:mysql\n - project_db_1:postgresql\n```\n\n### 11.5.17 `extra_hosts`\n\n类似 Docker 中的 `--add-host` 参数，指定额外的 host 名称映射信息。\n\n```yaml\nextra_hosts:\n - \"googledns:8.8.8.8\"\n - \"dockerhub:52.1.157.61\"\n```\n\n会在启动后的服务容器中 `/etc/hosts` 文件中添加如下两条条目。\n\n```bash\n8.8.8.8 googledns\n52.1.157.61 dockerhub\n```\n\n### 11.5.18 `healthcheck`\n\n通过命令检查容器是否健康运行。\n\n```yaml\nhealthcheck:\n  test: [\"CMD\", \"curl\", \"-f\", \"http://localhost\"]\n  interval: 1m30s\n  timeout: 10s\n  retries: 3\n```\n\n### 11.5.19 `image`\n\n指定为镜像名称或镜像 ID。如果镜像在本地不存在，`Compose` 将会尝试拉取这个镜像。\n\n```yaml\nimage: ubuntu\nimage: orchardup/postgresql\nimage: a4bc65fd\n```\n\n### 11.5.20 `labels`\n\n为容器添加 Docker 元数据 (metadata) 信息。例如可以为容器添加辅助说明信息。\n\n```yaml\nlabels:\n  com.startupteam.description: \"webapp for a startup team\"\n  com.startupteam.department: \"devops department\"\n  com.startupteam.release: \"rc3 for v1.0\"\n```\n\n### 11.5.21 `links`\n\n> 注意：不推荐使用该指令。容器之间应通过 Docker 网络 (networks) 进行互联。\n\n### 11.5.22 `logging`\n\n配置日志选项。\n\n```yaml\nlogging:\n  driver: syslog\n  options:\n    syslog-address: \"tcp://192.168.0.42:123\"\n```\n\n目前支持三种日志驱动类型。\n\n```yaml\ndriver: \"json-file\"\ndriver: \"syslog\"\ndriver: \"none\"\n```\n\n`options` 配置日志驱动的相关参数。\n\n```yaml\noptions:\n  max-size: \"200k\"\n  max-file: \"10\"\n```\n\n### 11.5.23 `network_mode`\n\n设置网络模式。使用和 `docker run` 的 `--network` 参数一样的值。\n\n```yaml\nnetwork_mode: \"bridge\"\nnetwork_mode: \"host\"\nnetwork_mode: \"none\"\nnetwork_mode: \"service:[service name]\"\nnetwork_mode: \"container:[container name/id]\"\n```\n\n### 11.5.24 `networks`\n\n配置容器连接的网络。\n\n```yaml\n\nservices:\n\n  some-service:\n    networks:\n     - some-network\n     - other-network\n\nnetworks:\n  some-network:\n  other-network:\n```\n\n### 11.5.25 `pid`\n\n跟主机系统共享进程命名空间。打开该选项的容器之间，以及容器和宿主机系统之间可以通过进程 ID 来相互访问和操作。\n\n```yaml\npid: \"host\"\n```\n\n### 11.5.26 `ports`\n\n暴露端口信息。\n\n使用宿主端口：容器端口 `(HOST:CONTAINER)` 格式，或者仅仅指定容器的端口 (宿主将会随机选择端口) 都可以。\n\n```yaml\nports:\n - \"3000\"\n - \"8000:8000\"\n - \"49100:22\"\n - \"127.0.0.1:8001:8001\"\n```\n\n*注意：当使用 `HOST:CONTAINER` 格式来映射端口时，如果你使用的容器端口小于 60 并且没放到引号里，可能会得到错误结果，因为 `YAML` 会自动解析 `xx:yy` 这种数字格式为 60 进制。为避免出现这种问题，建议数字串都采用引号包括起来的字符串格式。*\n\n### 11.5.27 `secrets`\n\n存储敏感数据，例如 `mysql` 服务密码。\n\n```yaml\n\nservices:\n\nmysql:\n  image: mysql\n  environment:\n    MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_root_password\n  secrets:\n    - db_root_password\n    - my_other_secret\n\nsecrets:\n  db_root_password:\n    file: ./db_root_password.txt\n  my_other_secret:\n    external: true\n```\n\n### 11.5.28 `security_opt`\n\n指定容器模板标签 (label) 机制的默认属性 (用户、角色、类型、级别等)。例如配置标签的用户名和角色名。\n\n```yaml\nsecurity_opt:\n    - label:user:USER\n    - label:role:ROLE\n```\n\n### 11.5.29 `stop_signal`\n\n设置另一个信号来停止容器。在默认情况下使用的是 SIGTERM 停止容器。\n\n```yaml\nstop_signal: SIGUSR1\n```\n\n### 11.5.30 `sysctls`\n\n配置容器内核参数。\n\n```yaml\nsysctls:\n  net.core.somaxconn: 1024\n  net.ipv4.tcp_syncookies: 0\n\nsysctls:\n  - net.core.somaxconn=1024\n  - net.ipv4.tcp_syncookies=0\n```\n\n### 11.5.31 `ulimits`\n\n指定容器的 ulimits 限制值。\n\n例如，指定最大进程数为 65535，指定文件句柄数为 20000 (软限制，应用可以随时修改，不能超过硬限制) 和 40000 (系统硬限制，只能 root 用户提高)。\n\n```yaml\n  ulimits:\n    nproc: 65535\n    nofile:\n      soft: 20000\n      hard: 40000\n```\n\n### 11.5.32 `volumes`\n\n数据卷所挂载路径设置。可以设置为宿主机路径 (`HOST:CONTAINER`) 或者数据卷名称 (`VOLUME:CONTAINER`)，并且可以设置访问模式 (`HOST:CONTAINER:ro`)。\n\n该指令中路径支持相对路径。\n\n```yaml\nvolumes:\n - /var/lib/mysql\n - cache/:/tmp/cache\n - ~/configs:/etc/configs/:ro\n```\n\n如果路径为数据卷名称，必须在文件中配置数据卷。\n\n```yaml\nservices:\n  my_src:\n    image: mysql:8.0\n    volumes:\n      - mysql_data:/var/lib/mysql\n\nvolumes:\n  mysql_data:  \n```\n\n### 11.5.33 其它指令\n\n此外，还有包括 `domainname, entrypoint, hostname, ipc, mac_address, privileged, read_only, shm_size, restart, stdin_open, tty, user, working_dir` 等指令，基本跟 `docker run` 中对应参数的功能一致。\n\n指定服务容器启动后执行的入口文件。\n\n```yaml\nentrypoint: /code/entrypoint.sh\n```\n\n指定容器中运行应用的用户名。\n\n```yaml\nuser: nginx\n```\n\n指定容器中工作目录。\n\n```yaml\nworking_dir: /code\n```\n\n指定容器中搜索域名、主机名、mac 地址等。\n\n```yaml\ndomainname: your_website.com\nhostname: test\nmac_address: 08-00-27-00-0C-0A\n```\n\n允许容器中运行一些特权命令。\n\n```yaml\nprivileged: true\n```\n\n指定容器退出后的重启策略为始终重启。该命令对保持服务始终运行十分有效，在生产环境中推荐配置为 `always` 或者 `unless-stopped`。\n\n```yaml\nrestart: always\n```\n\n以只读模式挂载容器的 root 文件系统，意味着不能对容器内容进行修改。\n\n```yaml\nread_only: true\n```\n\n打开标准输入，可以接受外部输入。\n\n```yaml\nstdin_open: true\n```\n\n模拟一个伪终端。\n\n```yaml\ntty: true\n```\n\n### 11.5.34 读取变量\n\nCompose 模板文件支持动态读取主机的系统环境变量和当前目录下的 `.env` 文件中的变量。\n\n例如，下面的 Compose 文件将从运行它的环境中读取变量 `${MONGO_VERSION}` 的值，并写入执行的指令中。\n\n```yaml\n\nservices:\n\ndb:\n  image: \"mongo:${MONGO_VERSION}\"\n```\n\n如果执行 `MONGO_VERSION=3.2 docker compose up` 则会启动一个 `mongo:3.2` 镜像的容器；如果执行 `MONGO_VERSION=2.8 docker compose up` 则会启动一个 `mongo:2.8` 镜像的容器。\n\n若当前目录存在 `.env` 文件，执行 `docker compose` 命令时将从该文件中读取变量。\n\n在当前目录新建 `.env` 文件并写入以下内容。\n\n```bash\n## 支持 # 号注释\n\nMONGO_VERSION=3.6\n```\n\n执行 `docker compose up` 则会启动一个 `mongo:3.6` 镜像的容器。\n"
  },
  {
    "path": "11_compose/11.6_django.md",
    "content": "## 11.6 实战 Django\n\n> 本小节内容适合 `Python` 开发人员阅读。\n\n本节将使用 Docker Compose 配置并运行一个 **Django + PostgreSQL** 应用。笔者不仅会介绍具体步骤，还会解释每个配置项的作用，以及开发环境和生产环境的差异。\n\n### 11.6.1 架构概览\n\n在开始之前，先看整体架构 (如图 11-1：所示)：\n\n```mermaid\nflowchart TD\n    subgraph Network [\"Docker Compose 网络\"]\n        direction LR\n        subgraph Web [\"web 服务\"]\n            direction TB\n            Django[\"Django<br/>应用\"]\n            Port8000[\":8000\"]\n            Django ~~~ Port8000\n        end\n        \n        subgraph DB [\"db 服务\"]\n            direction TB\n            Postgres[\"PostgreSQL<br/>数据库\"]\n        end\n        \n        Django -- \":5432\" --> Postgres\n    end\n    \n    Browser[\"localhost:8000<br/>（浏览器访问）\"]\n    Port8000 --> Browser\n```\n\n图 11-1：Django + PostgreSQL 的 Compose 架构\n\n**关键点**：\n\n- `web` 服务运行 Django 应用，对外暴露 8000 端口\n- `db` 服务运行 PostgreSQL 数据库，只在内部网络可访问\n- 两个服务通过 Docker Compose 自动创建的网络相互通信\n- `web` 服务可以通过服务名 `db` 访问数据库 (Docker 内置 DNS)\n\n### 11.6.2 准备工作\n\n创建一个项目目录并进入：\n\n```bash\n$ mkdir django-docker && cd django-docker\n```\n\n我们需要创建三个文件：`Dockerfile`、`requirements.txt` 和 `compose.yaml`。\n\n### 11.6.3 步骤 1：创建 Dockerfile\n\n```docker\nFROM python:3.12-slim\n\n## 防止 Python 缓冲 stdout/stderr，让日志实时输出\n\nENV PYTHONUNBUFFERED=1\n\n## 设置工作目录\n\nWORKDIR /code\n\n## 先复制依赖文件，利用 Docker 缓存加速构建\n\nCOPY requirements.txt /code/\n\n## 安装依赖\n\nRUN pip install --no-cache-dir -r requirements.txt\n\n## 复制项目代码\n\nCOPY . /code/\n```\n\n**逐行解释**：\n\n| 指令 | 作用 | 为什么这样写 |\n|------|------|-------------|\n| `FROM python:3.12-slim` | 基础镜像 | `slim` 版本比完整版小很多，但包含运行 Python 所需的一切 |\n| `ENV PYTHONUNBUFFERED=1` | 关闭输出缓冲 | 让 `print()` 和日志立即显示，便于调试 |\n| `WORKDIR /code` | 设置工作目录 | 后续命令都在此目录执行 |\n| `COPY requirements.txt` 在前 | 分层复制 | 只有 requirements.txt 变化时才重新安装依赖，加速构建 |\n| `--no-cache-dir` | 不缓存 pip 下载 | 减小镜像体积 |\n\n> 💡 **笔者建议**：总是将变化频率低的文件先复制，变化频率高的后复制。这样可以最大化利用 Docker 的构建缓存。\n\n### 11.6.4 步骤 2：创建 requirements.txt\n\n```txt\nDjango>=5.0,<6.0\npsycopg[binary]>=3.1,<4.0\ngunicorn>=21.0,<22.0\n```\n\n**依赖说明**：\n\n| 包名 | 作用 |\n|------|------|\n| `Django` | Web 框架 |\n| `psycopg[binary]` | PostgreSQL 数据库驱动 (推荐使用 psycopg 3)|\n| `gunicorn` | 生产环境 WSGI 服务器 (可选，开发时可不用)|\n\n### 11.6.5 步骤 3：创建 compose.yaml\n\n配置如下：\n\n```yaml\nservices:\n  db:\n    image: postgres:16\n    environment:\n      POSTGRES_DB: django_db\n      POSTGRES_USER: django_user\n      POSTGRES_PASSWORD: django_password\n    volumes:\n      - postgres_data:/var/lib/postgresql/data\n    healthcheck:\n      test: [\"CMD-SHELL\", \"pg_isready -U django_user -d django_db\"]\n      interval: 5s\n      timeout: 5s\n      retries: 5\n\n  web:\n    build: .\n    command: python manage.py runserver 0.0.0.0:8000\n    volumes:\n      - .:/code\n    ports:\n      - \"8000:8000\"\n    depends_on:\n      db:\n        condition: service_healthy\n    environment:\n      DATABASE_URL: postgres://django_user:django_password@db:5432/django_db\n\nvolumes:\n  postgres_data:\n```\n\n**配置详解**：\n\n#### db 服务\n\ndb 服务配置如下：\n\n```yaml\ndb:\n  image: postgres:16                    # 使用官方 PostgreSQL 16 镜像\n  environment:\n    POSTGRES_DB: django_db              # 创建的数据库名\n    POSTGRES_USER: django_user          # 数据库用户\n    POSTGRES_PASSWORD: django_password  # 数据库密码\n  volumes:\n    - postgres_data:/var/lib/postgresql/data  # 持久化数据\n  healthcheck:                          # 健康检查，确保数据库就绪\n    test: [\"CMD-SHELL\", \"pg_isready -U django_user -d django_db\"]\n    interval: 5s\n```\n\n> ⚠️ **笔者提醒**：`volumes` 配置很重要！没有它，每次容器重启数据都会丢失。笔者见过不少新手因为忘记这一步，导致开发数据全部丢失。\n\n#### web 服务\n\nweb 服务配置如下：\n\n```yaml\nweb:\n  build: .                              # 从当前目录的 Dockerfile 构建\n  command: python manage.py runserver   # 启动 Django 开发服务器\n  volumes:\n    - .:/code                           # 挂载代码目录，支持热更新\n  ports:\n    - \"8000:8000\"                       # 映射端口\n  depends_on:\n    db:\n      condition: service_healthy        # 等待数据库健康后再启动\n```\n\n**关键配置说明**：\n\n| 配置项 | 作用 | 笔者建议 |\n|--------|------|---------|\n| `volumes: .:/code` | 代码挂载 | 开发时必备，修改代码无需重新构建镜像 |\n| `depends_on` + `healthcheck` | 启动顺序 | 确保数据库就绪后 Django 才启动，避免连接错误 |\n| `environment` | 环境变量 | 推荐用环境变量管理配置，避免硬编码 |\n\n### 11.6.6 步骤 4：创建 Django 项目\n\n运行以下命令创建新的 Django 项目：\n\n```bash\n$ docker compose run --rm web django-admin startproject mysite .\n```\n\n**命令解释**：\n\n- `docker compose run`：运行一次性命令\n- `--rm`：命令执行后删除临时容器\n- `web`：在 web 服务环境中执行\n- `django-admin startproject mysite .`：在当前目录创建 Django 项目\n\n生成的目录结构：\n\n```bash\ndjango-docker/\n├── compose.yaml\n├── Dockerfile\n├── requirements.txt\n├── manage.py\n└── mysite/\n    ├── __init__.py\n    ├── settings.py\n    ├── urls.py\n    ├── asgi.py\n    └── wsgi.py\n```\n\n> 💡 **Linux 用户注意**：如果遇到权限问题，执行 `sudo chown -R $USER:$USER .`\n\n### 11.6.7 步骤 5：配置数据库连接\n\n修改 `mysite/settings.py`，配置数据库连接：\n\n```python\nimport os\n\nDATABASES = {\n    'default': {\n        'ENGINE': 'django.db.backends.postgresql',\n        'NAME': os.environ.get('POSTGRES_DB', 'django_db'),\n        'USER': os.environ.get('POSTGRES_USER', 'django_user'),\n        'PASSWORD': os.environ.get('POSTGRES_PASSWORD', 'django_password'),\n        'HOST': 'db',  # Docker Compose 服务名\n        'PORT': 5432,\n    }\n}\n\n## 允许的主机（开发环境）\n\nALLOWED_HOSTS = ['*']\n```\n\n**为什么 HOST 是 `db` 而不是 `localhost`？**\n\n在 Docker Compose 中，各服务通过服务名相互访问。Docker 内置的 DNS 会将 `db` 解析为 db 服务容器的 IP 地址。这是 Docker Compose 的核心功能之一。\n\n### 11.6.8 步骤 6：启动应用\n\n```bash\n$ docker compose up\n```\n\n你会看到：\n\n1. 首先构建 web 镜像 (第一次运行)\n2. 启动 db 服务，等待健康检查通过\n3. 启动 web 服务\n\n```bash\ndb-1   | PostgreSQL init process complete; ready for start up.\ndb-1   | LOG:  database system is ready to accept connections\nweb-1  | Watching for file changes with StatReloader\nweb-1  | Starting development server at http://0.0.0.0:8000/\n```\n\n打开浏览器访问 `http://localhost:8000`，可以看到 Django 欢迎页面！\n\n### 11.6.9 常用开发命令\n\n在另一个终端窗口执行：\n\n```bash\n## 执行数据库迁移\n\n$ docker compose exec web python manage.py migrate\n\n## 创建超级用户\n\n$ docker compose exec web python manage.py createsuperuser\n\n## 进入 Django shell\n\n$ docker compose exec web python manage.py shell\n\n## 进入 PostgreSQL 命令行\n\n$ docker compose exec db psql -U django_user -d django_db\n```\n\n> 💡 笔者建议使用 `exec` 而不是 `run`。`exec` 在已运行的容器中执行命令，`run` 会创建新容器。\n\n### 11.6.10 常见问题排查\n\n#### Q1：数据库连接失败\n\n**错误信息**：`django.db.utils.OperationalError: could not connect to server` **可能原因与解决方案**：\n\n| 原因 | 解决方案 |\n|------|---------|\n| 数据库还没启动完成 | 使用 `depends_on` + `healthcheck` |\n| HOST 配置错误 | 确保使用服务名 `db` 而不是 `localhost` |\n| 网络未创建 | 运行 `docker compose down` 后重新 `up` |\n\n```bash\n## 调试：检查数据库是否正常运行\n\n$ docker compose ps\n$ docker compose logs db\n```\n\n#### Q2：代码修改没有生效\n\n**可能原因**：\n\n1. **开发服务器没有自动重载**：确保使用 `runserver` 而不是 `gunicorn`\n2. **Volume 挂载问题**：检查 `compose.yaml` 中的 volumes 配置\n3. **缓存问题**：尝试 `docker compose restart web`\n\n#### Q3：权限问题\n\n```bash\n## 如果容器内创建的文件 root 用户所有\n\n$ sudo chown -R $USER:$USER .\n```\n\n### 11.6.11 开发 vs 生产：关键差异\n\n笔者特别提醒，本节的配置是 **开发环境** 配置。生产环境需要以下调整：\n\n| 配置项 | 开发环境 | 生产环境 |\n|--------|---------|---------|\n| **Web 服务器** | `runserver` | `gunicorn` + Nginx |\n| **DEBUG** | `True` | `False` |\n| **密码管理** | 明文写在配置 | 使用 Docker Secrets 或环境变量 |\n| **Volume** | 挂载代码目录 | 代码直接 COPY 进镜像 |\n| **ALLOWED_HOSTS**| `['*']` | 具体域名 |**生产环境 Compose 文件示例**：\n\n```yaml\n## compose.prod.yaml\n\nservices:\n  web:\n    build: .\n    command: gunicorn mysite.wsgi:application --bind 0.0.0.0:8000\n    # 不挂载代码，使用镜像内的代码\n\n    environment:\n      DEBUG: 'False'\n      ALLOWED_HOSTS: 'example.com,www.example.com'\n    # ...\n\n```\n\n### 11.6.12 延伸阅读\n\n- [Compose 模板文件详解](11.5_compose_file.md)：深入理解 Compose 文件的所有配置项\n- [使用 WordPress](11.8_wordpress.md)：另一个 Compose 实战案例\n- [Dockerfile 最佳实践](../appendix/best_practices.md)：构建更小、更安全的镜像\n- [数据管理](../08_data/README.md)：Volume 和数据持久化详解\n"
  },
  {
    "path": "11_compose/11.7_rails.md",
    "content": "## 11.7 实战 Rails\n\n> 本小节内容适合 Ruby 开发人员阅读。\n\n本节使用 Docker Compose 配置并运行一个 **Rails + PostgreSQL** 应用。\n\n### 11.7.1 架构概览\n\n如图 11-2：所示，Rails 与 PostgreSQL 在同一 Compose 网络中协同工作。\n\n```mermaid\nflowchart TD\n    subgraph Network [\"Docker Compose 网络\"]\n        direction LR\n        subgraph Web [\"web 服务\"]\n            direction TB\n            Rails[\"Rails<br/>应用\"]\n            Port3000[\":3000\"]\n            Rails ~~~ Port3000\n        end\n        \n        subgraph DB [\"db 服务\"]\n            direction TB\n            Postgres[\"PostgreSQL<br/>数据库\"]\n        end\n        \n        Rails -- \":5432\" --> Postgres\n    end\n    \n    Browser[\"localhost:3000\"]\n    Port3000 --> Browser\n```\n\n图 11-2：Rails + PostgreSQL 的 Compose 架构\n\n### 11.7.2 准备工作\n\n创建项目目录：\n\n```bash\n$ mkdir rails-docker && cd rails-docker\n```\n\n需要创建三个文件：`Dockerfile`、`Gemfile` 和 `compose.yaml`。\n\n### 11.7.3 步骤 1：创建 Dockerfile\n\n```docker\nFROM ruby:3.2\n\n## 安装系统依赖\n\nRUN apt-get update -qq && \\\n    apt-get install -y build-essential libpq-dev nodejs && \\\n    rm -rf /var/lib/apt/lists/*\n\n## 设置工作目录\n\nWORKDIR /myapp\n\n## 先复制 Gemfile，利用缓存加速构建\n\nCOPY Gemfile /myapp/Gemfile\nCOPY Gemfile.lock /myapp/Gemfile.lock\nRUN bundle install\n\n## 复制应用代码\n\nCOPY . /myapp\n```\n\n**配置说明**：\n\n| 指令 | 作用 |\n|------|------|\n| `build-essential` | 编译原生扩展所需 |\n| `libpq-dev` | PostgreSQL 客户端库 |\n| `nodejs` | Rails Asset Pipeline 需要 |\n| 先复制 Gemfile | 只有依赖变化时才重新 `bundle install` |\n\n### 11.7.4 步骤 2：创建 Gemfile\n\n创建一个初始的 `Gemfile`，稍后会被 `rails new` 覆盖：\n\n```ruby\nsource 'https://rubygems.org'\ngem 'rails', '~> 7.1'\n```\n\n创建空的 `Gemfile.lock`：\n\n```bash\n$ touch Gemfile.lock\n```\n\n### 11.7.5 步骤 3：创建 compose.yaml\n\n配置如下：\n\n```yaml\nservices:\n  db:\n    image: postgres:16\n    environment:\n      POSTGRES_PASSWORD: password\n    volumes:\n      - postgres_data:/var/lib/postgresql/data\n\n  web:\n    build: .\n    command: bash -c \"rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'\"\n    volumes:\n      - .:/myapp\n    ports:\n      - \"3000:3000\"\n    depends_on:\n      - db\n    environment:\n      DATABASE_URL: postgres://postgres:password@db:5432/myapp_development\n\nvolumes:\n  postgres_data:\n```\n\n**配置详解**：\n\n| 配置项 | 说明 |\n|--------|------|\n| `rm -f tmp/pids/server.pid` | 清理上次异常退出留下的 PID 文件 |\n| `volumes: .:/myapp` | 挂载代码目录，支持热更新 |\n| `depends_on: db` | 确保数据库先启动 |\n| `DATABASE_URL` | Rails 12-factor 风格的数据库配置 |\n\n### 11.7.6 步骤 4：生成 Rails 项目\n\n使用 `docker compose run` 生成项目骨架：\n\n```bash\n$ docker compose run --rm web rails new . --force --database=postgresql --skip-bundle\n```\n\n**命令解释**：\n\n- `--rm`：执行后删除临时容器\n- `--force`：覆盖已存在的文件\n- `--database=postgresql`：配置使用 PostgreSQL\n- `--skip-bundle`：暂不安装依赖 (稍后统一安装)\n\n生成的目录结构：\n\n```bash\n$ ls\nDockerfile       Gemfile          Rakefile         config           lib              tmp\nGemfile.lock     README.md        app              config.ru        log              vendor\ncompose.yaml     bin              db               public\n\n```\n\n> ⚠️ **Linux 用户**：如遇权限问题，执行 `sudo chown -R $USER:$USER .`\n\n### 11.7.7 步骤 5：重新构建镜像\n\n由于生成了新的 Gemfile，需要重新构建镜像以安装完整依赖：\n\n```bash\n$ docker compose build\n```\n\n### 11.7.8 步骤 6：配置数据库连接\n\n修改 `config/database.yml`：\n\n```yaml\ndefault: &default\n  adapter: postgresql\n  encoding: unicode\n  pool: <%= ENV.fetch(\"RAILS_MAX_THREADS\") { 5 } %>\n  url: <%= ENV['DATABASE_URL'] %>\n\ndevelopment:\n  <<: *default\n\ntest:\n  <<: *default\n  database: myapp_test\n\nproduction:\n  <<: *default\n```\n\n> 💡 使用 `DATABASE_URL` 环境变量配置数据库，符合 12-factor 应用原则，便于在不同环境间切换。\n\n### 11.7.9 步骤 7：启动应用\n\n```bash\n$ docker compose up\n```\n\n输出示例：\n\n```bash\ndb-1   | PostgreSQL init process complete; ready for start up.\ndb-1   | LOG:  database system is ready to accept connections\nweb-1  | => Booting Puma\nweb-1  | => Rails 7.1.0 application starting in development\nweb-1  | => Run `bin/rails server --help` for more startup options\nweb-1  | Puma starting in single mode...\nweb-1  | * Listening on http://0.0.0.0:3000\n```\n\n### 11.7.10 步骤 8：创建数据库\n\n在另一个终端执行：\n\n```bash\n$ docker compose exec web rails db:create\nCreated database 'myapp_development'\nCreated database 'myapp_test'\n```\n\n访问 http://localhost:3000 查看 Rails 欢迎页面。\n\n### 11.7.11 常用开发命令\n\n```bash\n## 数据库迁移\n\n$ docker compose exec web rails db:migrate\n\n## Rails 控制台\n\n$ docker compose exec web rails console\n\n## 运行测试\n\n$ docker compose exec web rails test\n\n## 生成脚手架\n\n$ docker compose exec web rails generate scaffold Post title:string body:text\n\n## 进入容器 Shell\n\n$ docker compose exec web bash\n```\n\n### 11.7.12 常见问题\n\n#### Q：数据库连接失败\n\n检查 `DATABASE_URL` 环境变量格式是否正确，确保 db 服务已启动：\n\n```bash\n$ docker compose ps\n$ docker compose logs db\n```\n\n#### Q：server.pid 文件导致启动失败\n\n错误信息：`A server is already running`\n\n已在 command 中添加 `rm -f tmp/pids/server.pid` 处理。如仍有问题：\n\n```bash\n$ docker compose exec web rm -f tmp/pids/server.pid\n```\n\n#### Q：Gem 安装失败\n\n可能需要更新 bundler 或清理缓存：\n\n```bash\n$ docker compose run --rm web bundle update\n```\n\n### 11.7.13 开发 vs 生产\n\n| 配置项 | 开发环境 | 生产环境 |\n|--------|---------|---------|\n| Rails 服务器 | Puma (开发模式) | Puma + Nginx |\n| 代码挂载 | 使用 volumes | 代码打包进镜像 |\n| 静态资源 | 动态编译 | 预编译 (`rails assets:precompile`) |\n| 数据库密码 | 明文配置 | 使用 Secrets 管理 |\n\n### 11.7.14 延伸阅读\n\n- [使用 Django](11.6_django.md)：Python Web 框架实战\n- [Compose 模板文件](11.5_compose_file.md)：配置详解\n- [数据管理](../08_data/README.md)：数据持久化\n"
  },
  {
    "path": "11_compose/11.8_wordpress.md",
    "content": "## 11.8 实战 WordPress\n\nWordPress 是全球最流行的内容管理系统 (CMS)。使用 Docker Compose 可以在几分钟内搭建一个包含数据库、Web 服务和持久化存储的生产级 WordPress 环境。\n\n---\n\n### 11.8.1 项目结构\n\n```bash\nwordpress/\n├── compose.yaml\n├── .env                # 环境变量（敏感信息）\n└── nginx/              # 可选：反向代理配置\n    └── nginx.conf\n```\n\n---\n\n### 11.8.2 编写 `compose.yaml`\n\n这是一个生产可用的最小化配置：\n\n```yaml\nservices:\n  # 数据库服务\n\n  db:\n    image: mysql:8.0\n    container_name: wordpress_db\n    restart: always\n    command: \n      # 使用原生密码认证（旧版 WP 兼容性）\n\n      - --default-authentication-plugin=mysql_native_password\n      - --character-set-server=utf8mb4\n      - --collation-server=utf8mb4_unicode_ci\n    environment:\n      MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}\n      MYSQL_DATABASE: wordpress\n      MYSQL_USER: wordpress\n      MYSQL_PASSWORD: ${DB_PASSWORD}\n    volumes:\n      - db_data:/var/lib/mysql\n    networks:\n      - wp_net\n\n  # WordPress 服务\n\n  wordpress:\n    image: wordpress:latest\n    container_name: wordpress_app\n    restart: always\n    ports:\n      - \"8000:80\"\n    environment:\n      WORDPRESS_DB_HOST: db:3306\n      WORDPRESS_DB_USER: wordpress\n      WORDPRESS_DB_PASSWORD: ${DB_PASSWORD}\n      WORDPRESS_DB_NAME: wordpress\n    volumes:\n      - wp_data:/var/www/html\n      # 增加上传文件大小限制\n\n      - ./uploads.ini:/usr/local/etc/php/conf.d/uploads.ini\n    depends_on:\n      - db\n    networks:\n      - wp_net\n\nvolumes:\n  db_data:  # 数据库持久化\n  wp_data:  # WordPress 文件(插件/主题/上传)持久化\n\nnetworks:\n  wp_net:\n```\n\n---\n\n### 11.8.3 配置文件详解\n\n#### 1. 环境变量文件 .env\n\n为了安全，不要在 `compose.yaml` 中直接写密码。创建 `.env` 文件：\n\n```ini\nDB_ROOT_PASSWORD=somestrongrootpassword\nDB_PASSWORD=somestronguserpassword\n```\n\nCompose 会自动读取此同级目录下的文件。\n\n#### 2. 数据持久化\n\n我们定义了两个命名卷：\n\n- `db_data`：确保 MySQL 容器重建后数据不丢失\n- `wp_data`：保存 WordPress 的核心文件、插件、主题和上传的媒体文件\n\n#### 3. PHP 配置优化\n\n默认的 WordPress 镜像上传文件限制较小 (通常 2MB)。创建 `uploads.ini`：\n\n```ini\nfile_uploads = On\nmemory_limit = 256M\nupload_max_filesize = 64M\npost_max_size = 64M\nmax_execution_time = 600\n```\n\n---\n\n### 11.8.4 启动与运行\n\n1. 启动服务：\n\n```bash\n$ docker compose up -d\n```\n\n2. 访问安装界面：\n   打开浏览器访问 `http://localhost:8000`\n\n3. 查看日志：\n\n```bash\n$ docker compose logs -f\n```\n\n---\n\n### 11.8.5 生产环境最佳实践\n\n#### 1. 数据库备份\n\n不要只依赖 Volume。建议定期备份数据库：\n\n```bash\n## 导出 SQL\n\n$ docker exec wordpress_db mysqldump -u wordpress -pwordpress wordpress > backup.sql\n```\n\n或者添加一个自动备份容器：\n\n```yaml\n  backup:\n    image: tiredofit/db-backup\n    volumes:\n      - ./backups:/backup\n    environment:\n      - DB_TYPE=mysql\n      - DB_HOST=db\n      - DB_NAME=wordpress\n      - DB_USER=wordpress\n      - DB_PASS=${DB_PASSWORD}\n      - DB_DUMP_FREQ=1440 # 每天备份一次\n    depends_on:\n      - db\n    networks:\n      - wp_net\n```\n\n#### 2. 使用 Nginx 反向代理\n\n在生产环境中，不要直接暴露 WordPress 端口，而是通过 Nginx 进行反向代理并配置 SSL。\n\n#### 3. 使用 Redis 缓存\n\nWordPress 支持 Redis 缓存以提高性能。\n\n```yaml\n  redis:\n    image: redis:alpine\n    restart: always\n    networks:\n      - wp_net\n```\n\n在 WordPress 容器环境变量中添加：\n```yaml\n      WORDPRESS_REDIS_HOST: redis\n```\n并安装 Redis Object Cache 插件。\n\n---\n\n### 11.8.6 常见问题\n\n#### Q：数据库连接错误\n\n**现象**：访问页面显示 “Error establishing a database connection”。**排查**：\n\n1. 检查 `docker compose logs wordpress`\n2. 确认 `.env` 中的密码与 YAML 文件引用一致\n3. 确认 `WORDPRESS_DB_HOST` 也是 `db` (服务名)\n4. MySQL 8.0 可能需要几秒钟启动，WordPress 会自动重试，稍等片刻即可。\n\n#### Q：无法上传大文件\n\n**解决**：确保挂载了 `uploads.ini` 配置，并且重启了容器：\n```bash\n$ docker compose restart wordpress\n```\n\n---\n\n### 11.8.7 延伸阅读\n\n- [Compose 模板文件](11.5_compose_file.md)：深入了解配置项\n- [数据卷](../08_data/8.1_volume.md)：理解数据持久化\n- [Docker Hub WordPress](https://hub.docker.com/_/wordpress)：官方镜像文档\n"
  },
  {
    "path": "11_compose/11.9_lnmp.md",
    "content": "## 11.9 实战 LNMP\n\n### 什么是 LNMP\n\nLNMP 是一个经典的 Web 应用栈，由以下四个开源软件组合而成：\n\n- **L**：Linux（操作系统）\n- **N**：Nginx（Web 服务器）\n- **M**：MySQL（数据库服务器）\n- **P**：PHP（脚本语言）\n\n这个组合被广泛用于构建高性能的 Web 应用。\n\n### 使用 Docker Compose 部署 LNMP\n\n本项目的维护者 [khs1994](https://github.com/khs1994) 的开源项目 [khs1994-docker/lnmp](https://github.com/khs1994-docker/lnmp) 使用 Docker Compose 搭建了一套完整的 LNMP 环境。\n\n### 参考项目\n\n该项目中包含的服务：\n\n- **Nginx**：Web 服务器，用于处理 HTTP 请求\n- **MySQL/MariaDB**：关系型数据库服务\n- **PHP-FPM**：PHP 处理器，与 Nginx 通过 Fast CGI 协议通信\n- **Redis**：可选的内存缓存服务（用于会话或缓存）\n\n### 学习资源\n\n各位开发者可以参考该项目在以下场景中运行 LNMP：\n\n- Docker 容器化部署\n- Kubernetes 集群编排\n- 开发环境快速搭建\n- 生产环境配置参考\n\n项目地址：[khs1994-docker/lnmp](https://github.com/khs1994-docker/lnmp)\n\n通过该项目，你可以学习到如何使用 Docker Compose 定义多个相互关联的服务，以及如何在容器化环境中管理应用的生命周期。\n"
  },
  {
    "path": "11_compose/README.md",
    "content": "# 第十一章 Docker Compose\n\n`Docker Compose` 是 Docker 官方编排 (Orchestration) 项目之一，负责快速的部署分布式应用。\n\n> ⚠️ **重要提示：Compose V1 已停止支持**\n> \n> 早期基于 Python 编写的 Compose V1（命令为 `docker-compose`）已于 2023 年中正式停止支持。现已全面升级为基于 Go 编写的 Compose V2，作为 Docker CLI 的官方插件提供（命令为 `docker compose`，中间为空格）。本书强烈推荐且后续章节均以 V2 为核心标准进行讲解。\n\n本章将介绍 `Compose` 项目情况以及安装和使用。\n\n* [简介](11.1_introduction.md)\n* [安装与卸载](11.2_install.md)\n* [使用](11.3_usage.md)\n* [命令说明](11.4_commands.md)\n* [Compose 模板文件](11.5_compose_file.md)\n* [实战 Django](11.6_django.md)\n* [实战 Rails](11.7_rails.md)\n* [实战 WordPress](11.8_wordpress.md)\n* [实战 LNMP](11.9_lnmp.md)\n"
  },
  {
    "path": "11_compose/demo/app/Dockerfile",
    "content": "FROM python:3.12-alpine\nADD . /code\nWORKDIR /code\nRUN pip install redis flask\nCMD [\"python\", \"app.py\"]\n"
  },
  {
    "path": "11_compose/demo/app/app.py",
    "content": "from flask import Flask\nfrom redis import Redis\n\napp = Flask(__name__)\nredis = Redis(host='redis', port=6379)\n\n@app.route('/')\ndef hello():\n    count = redis.incr('hits')\n    return 'Hello World! 该页面已被访问 {} 次。\\n'.format(count)\n\nif __name__ == \"__main__\":\n    app.run(host=\"0.0.0.0\", debug=True)\n"
  },
  {
    "path": "11_compose/demo/app/docker-compose.yml",
    "content": "\nservices:\n\n  web:\n    build: .\n    ports:\n     - \"5000:5000\"\n\n  redis:\n    image: \"redis:alpine\"\n"
  },
  {
    "path": "11_compose/demo/django/.gitignore",
    "content": "django_example\nmanage.py\n"
  },
  {
    "path": "11_compose/demo/django/Dockerfile",
    "content": "FROM python:3\nENV PYTHONUNBUFFERED 1\nRUN mkdir /code\nWORKDIR /code\nADD requirements.txt /code/\nRUN pip install -r requirements.txt\nADD . /code/\n"
  },
  {
    "path": "11_compose/demo/django/docker-compose.yml",
    "content": "\nservices:\n\n  db:\n    image: postgres\n    environment:\n      POSTGRES_PASSWORD: 'postgres'\n\n  web:\n    build: .\n    command: python manage.py runserver 0.0.0.0:8000\n    volumes:\n      - .:/code\n    ports:\n      - \"8000:8000\"\n"
  },
  {
    "path": "11_compose/demo/django/requirements.txt",
    "content": "Django>=5.0.6,<6.0\npsycopg2>=2.7,<3.0\n"
  },
  {
    "path": "11_compose/demo/wordpress/docker-compose.yml",
    "content": "\nservices:\n\n   db:\n     image: mysql:8.0\n     command:\n      - --default_authentication_plugin=mysql_native_password\n      - --character-set-server=utf8mb4\n      - --collation-server=utf8mb4_unicode_ci\n     volumes:\n       - db_data:/var/lib/mysql\n     restart: always\n     environment:\n       MYSQL_ROOT_PASSWORD: somewordpress\n       MYSQL_DATABASE: wordpress\n       MYSQL_USER: wordpress\n       MYSQL_PASSWORD: wordpress\n\n   wordpress:\n     depends_on:\n       - db\n     image: wordpress:latest\n     ports:\n       - \"8000:80\"\n     restart: always\n     environment:\n       WORDPRESS_DB_HOST: db:3306\n       WORDPRESS_DB_USER: wordpress\n       WORDPRESS_DB_PASSWORD: wordpress\nvolumes:\n  db_data:\n"
  },
  {
    "path": "11_compose/summary.md",
    "content": "## 本章小结\n\nDocker Compose 是管理多容器应用的利器，通过 YAML 文件声明式地定义服务、网络和数据卷。\n\n| 概念 | 要点 |\n|------|------|\n| **核心概念** | 服务 (service) 和项目 (project) |\n| **配置文件** | `compose.yaml` (推荐) 或 `docker-compose.yml` |\n| **版本** | Compose V2 为 Go 编写的 CLI 插件，通过 `docker compose` 使用 |\n| **启动** | `docker compose up -d` 启动所有服务 |\n| **停止** | `docker compose down` 停止并移除容器 |\n| **查看状态** | `docker compose ps` 查看服务状态 |\n| **查看日志** | `docker compose logs` 查看服务日志 |\n| **模板文件** | 支持 `services`、`networks`、`volumes` 等顶级配置 |\n\n### 延伸阅读\n\n- [Compose 模板文件](11.5_compose_file.md)：详细模板语法参考\n- [Compose 命令说明](11.4_commands.md)：完整命令列表\n- [网络配置](../09_network/README.md)：Docker 网络基础\n- [数据管理](../08_data/README.md)：数据卷管理\n---\n\n> 📝 **发现错误或有改进建议？** 欢迎提交 [Issue](https://github.com/yeasy/docker_practice/issues) 或 [PR](https://github.com/yeasy/docker_practice/pulls)。\n"
  },
  {
    "path": "12_implementation/12.1_arch.md",
    "content": "## 12.1 基本架构\n\nDocker 的架构设计简洁而高效，主要由客户端和服务端两部分组成。\n\n### 12.1.1 核心架构图\n\nDocker 采用了 **C/S (客户端/服务端)** 架构。Client 向 Daemon 发送请求，Daemon 负责构建、运行和分发容器。\n\n```mermaid\ngraph LR\n  C1[\"客户端\"] -->|docker run| D[\"dockerd\\n守护进程\"]\n  C1 -->|docker pull| D\n  D -->|管理| C2[\"Containers\\n容器\"]\n  D -->|管理| C3[\"Images\\n镜像\"]\n```\n\n---\n\n### 12.1.2 组件详解\n\nDocker 的内部架构如同洋葱一样分层，每一层专注解决特定问题：\n\n#### 1. Docker CLI：客户端\n\n用户与 Docker 交互的主要方式。它将用户命令 (如 `docker run`) 转换为 API 请求发送给 dockerd。\n\n#### 2. Dockerd：守护进程\n\nDocker 的大脑。\n\n- 监听 API 请求\n- 管理 Docker 对象 (镜像、容器、网络、卷)\n- 编排下层组件完成工作\n\n#### 3. Containerd：高级运行时\n\n行业标准的容器运行时 (CNCF 毕业项目)。\n\n- 管理容器的完整生命周期 (启动、停止)\n- 镜像拉取与存储\n- **不包含** 复杂的与容器无关的功能 (如构建、API)\n- Kubernetes 也可以直接使用 containerd (跳过 Docker)\n\n#### 4. Runc：低级运行时\n\n用于创建和运行容器的 CLI 工具。\n\n- 直接与内核交互 (Namespaces，Cgroups)\n- 遵循 OCI (Open Container Initiative) 规范\n- **主要职责**：根据配置启动一个容器，然后退出 (将控制权交给容器进程)\n\n#### 5. Shim\n\n每个容器都有一个 shim 进程。\n\n- **解耦**：允许 dockerd 重启而不影响容器运行\n- **保持 IO**：维持容器的标准输入输出\n- **状态汇报**：向 containerd 汇报容器退出状态\n\n---\n\n### 12.1.3 容器启动流程\n\n当执行 `docker run -d nginx` 时，内部发生了什么？\n\n```mermaid\nflowchart TD\n  U[\"用户\"]\n  K[\"docker run -d nginx\"]\n  D[\"Dockerd\"]\n  C[\"Containerd\"]\n  B[\"OCI Bundle\"]\n  S[\"Containerd-shim\"]\n  R[\"Runc\"]\n  P[\"容器进程\\nnginx\"]\n  E[\"退出\"]\n\n  U -->|1. REST API| D\n  K -->|2. gRPC| C\n  C -->|3. 准备镜像和 Bundle| B\n  C -->|4. 启动 Shim| S\n  S -->|5. 执行| R\n  R -->|6. 创建 Namespaces 和 Cgroups| P\n  R -->|7. 进程退出| E\n  S -->|8. 监控 IO 和退出| P\n```\n\n1. **CLI** 发送请求给 **Dockerd**\n2. **Dockerd** 解析请求，调用 **Containerd**\n3. **Containerd** 准备镜像，转换为 OCI Bundle\n4. **Containerd** 创建 **Shim** 进程\n5. **Shim** 调用 **Runc**\n6. **Runc** 与系统内核交互，创建 Namespaces 和 Cgroups\n7. **Runc** 启动 nginx 进程后退出\n8. **Shim** 接管容器 IO 和生命周期监控\n\n---\n\n### 12.1.4 Docker Engine v29+ 变化\n\n从 Docker Engine v29 (2025) 开始，架构进一步简化和标准化：\n\n- **Containerd 镜像存储 (Image Store)**：默认启用。Docker 直接使用 Containerd 的镜像管理能力，不再维护自己的一套 graphdriver。\n  - **优势**：多平台镜像支持更好、镜像拉取更快 (lazy pulling)、与 K8s 共享镜像。\n\n---\n\n### 12.1.5 Docker Desktop 架构\n\n在 macOS 和 Windows 上，因为内核差异，架构稍微复杂：\n\n```mermaid\nflowchart TD\n    subgraph HostOS [\"MacOS / Windows\"]\n        CLI[\"Docker CLI\"]\n        subgraph LinuxVM [\"Linux VM (虚拟机)\"]\n            Engine[\"Dockerd <--> Containerd <--> Runc\"]\n        end\n        CLI -- \"(Socket 映射)\" --> Engine\n    end\n```\n\n- 使用轻量级虚拟机 (Apple Virtualization / WSL 2) 运行 Linux 内核\n- 文件挂载 (Bind Mount) 需要跨越 VM 边界 (这也是文件 I/O 慢的原因)\n- 网络端口需要从宿主机转发到 VM\n\n---\n\n### 12.1.6 总结\n\n| 组件 | 角色 | 关键职责 |\n|------|------|----------|\n| **CLI** | 指挥官 | 发送指令，展示结果 |\n| **Dockerd** | 大管家 | API 接口，整体调度 |\n| **Containerd** | 经理 | 容器生命周期，镜像管理 |\n| **Shim** | 监工 | 保持 IO，允许无守护进程重启 |\n| **Runc** | 工人 | 真正干活 (创建容器)，干完就走 |\n\n### 12.1.7 延伸阅读\n\n- [命名空间](./12.2_namespace.md)：Runc 如何隔离容器\n- [控制组](./12.3_cgroups.md)：Runc 如何限制资源\n- [联合文件系统](./12.4_ufs.md)：镜像如何存储\n"
  },
  {
    "path": "12_implementation/12.2_namespace.md",
    "content": "## 12.2 命名空间\n\n命名空间是 Linux 内核一个强大的特性。每个容器都有自己单独的命名空间，运行在其中的应用都像是在独立的操作系统中运行一样。命名空间保证了容器之间彼此互不影响。\n\n### 12.2.1 什么是 Namespace\n\n> **Namespace 是 Linux 内核提供的资源隔离机制，它让容器内的进程仿佛运行在独立的操作系统中。**Namespace 是容器技术的核心基础之一。它回答了一个关键问题：**如何让一个进程 “以为” 自己独占整个系统？**\n\n```mermaid\nflowchart LR\n    subgraph Host [\"宿主机视角\"]\n        direction TB\n        H1[\"PID 1: systemd\"]\n        H2[\"PID 2: sshd\"]\n        H3[\"PID 3: dockerd\"]\n        H4[\"PID 1234: nginx\"]\n        H5[\"PID 1235: nginx worker\"]\n    end\n    \n    subgraph Container [\"容器内视角\"]\n        direction TB\n        C1[\"PID 1: nginx<br/>← 容器认为自己是 PID 1\"]\n        C2[\"PID 2: nginx worker\"]\n    end\n    \n    H4 -. \"（实际是宿主机的 1234）\" .- C1\n```\n\n### 12.2.2 Namespace 的类型\n\nLinux 内核提供了以下几种 Namespace，Docker 容器使用了全部：\n\n| Namespace | 隔离内容 | 容器中的效果 |\n|-----------|---------|-------------|\n| **PID** | 进程 ID | 容器内 PID 从 1 开始，看不到其他容器和宿主机进程 |\n| **NET** | 网络栈 | 独立的网卡、IP 地址、端口、路由表 |\n| **MNT** | 挂载点 | 独立的文件系统视图，自己的根目录 |\n| **UTS** | 主机名 | 独立的主机名和域名 |\n| **IPC** | 进程间通信 | 独立的信号量、消息队列、共享内存 |\n| **USER** | 用户/组 ID | 容器内的 root 可以映射为宿主机的普通用户 |\n| **Cgroup** | Cgroup 根目录 | 隔离 cgroup 层级视图 (Linux 4.6+)|\n\n---\n\n### 12.2.3 PID Namespace\n\nPID Namespace 负责进程 ID 的隔离，使得容器内的进程彼此不可见。\n\n#### PID 的作用\n\n隔离进程 ID，让每个容器有自己的进程编号空间。\n\n#### PID 隔离效果\n\n```bash\n## 宿主机上查看进程\n\n$ ps aux | grep nginx\nroot     12345  0.0  0.1  nginx: master process\nroot     12346  0.0  0.1  nginx: worker process\n\n## 容器内查看进程\n\n$ docker exec mycontainer ps aux\nPID   USER     COMMAND\n  1   root     nginx: master process    ← 在容器内是 PID 1\n  2   root     nginx: worker process\n```\n\n#### PID 关键点\n\n- 容器内的 PID 1 进程特殊重要——它是容器的主进程，退出则容器停止\n- 容器内无法看到宿主机或其他容器的进程\n- 宿主机可以看到所有容器内的进程 (但 PID 不同)\n\n---\n\n### 12.2.4 NET Namespace\n\nNET Namespace 负责网络栈的隔离，包括网卡、路由表和 iptables 规则等。\n\n#### NET 的作用\n\n隔离网络栈，每个容器拥有独立的网络环境。\n\n#### NET 隔离效果\n\n```mermaid\nflowchart LR\n    subgraph Host [\"宿主机\"]\n        direction TB\n        H1[\"eth0: 192.168.1.10<br/>端口 80 可用\"]\n        H2[\"docker0: 172.17.0.1\"]\n    end\n    \n    subgraph Container [\"容器\"]\n        direction TB\n        C1[\"eth0: 172.17.0.2<br/>端口 80 可用\"]\n        C2[\"(veth pair 连接)\"]\n    end\n    \n    H2 <--> C2\n```\n\n#### NET 关键点\n\n- 每个容器有独立的网卡、IP、路由表、iptables 规则\n- 多个容器可以监听相同端口 (如都监听 80)\n- Docker 使用 veth pair 连接容器网络和宿主机网桥\n\n---\n\n### 12.2.5 MNT Namespace\n\nMNT Namespace 负责文件系统挂载点的隔离，确保容器看到独立的文件系统视图。\n\n#### MNT 的作用\n\n隔离文件系统挂载点，每个容器有自己的根目录。\n\n#### MNT 隔离效果\n\n```bash\n宿主机文件系统：                  容器内看到的：\n/                               /  ← 容器的根目录\n├── bin/                        ├── bin/\n├── home/                       ├── home/\n├── var/                        ├── var/\n│   └── lib/                    │   └── lib/\n│       └── docker/             │\n│           └── overlay2/       │\n│               └── merged/ ────┼─── 这个目录成为容器的 /\n└── ...                         └── ...\n```\n\n#### 与 chroot 的区别\n\n| 特性 | chroot | MNT Namespace |\n|------|--------|---------------|\n| 安全性 | 可以逃逸 | 更安全 |\n| 挂载隔离 | 无 | 完全隔离 |\n| /proc/mounts | 共享 | 独立 |\n\n---\n\n### 12.2.6 UTS Namespace\n\nUTS Namespace 主要用于隔离主机名和域名。\n\n#### UTS 的作用\n\n隔离主机名和域名，让每个容器可以有自己的主机名。\n\n#### UTS 隔离效果\n\n```bash\n## 宿主机\n\n$ hostname\nmy-server\n\n## 容器内\n\n$ docker run --hostname mycontainer ubuntu hostname\nmycontainer\n```\n\nUTS = “UNIX Time-sharing System”，是历史遗留的名称。\n\n---\n\n### 12.2.7 IPC Namespace\n\nIPC Namespace 用于隔离进程间通信资源，如 System V IPC 和 POSIX 消息队列。\n\n#### IPC 的作用\n\n隔离 System V IPC 和 POSIX 消息队列。\n\n#### 隔离的资源\n\n- 信号量 (semaphores)\n- 消息队列 (message queues)\n- 共享内存 (shared memory)\n\n#### IPC 关键点\n\n- 同一容器内的进程可以通过 IPC 通信\n- 不同容器的进程无法通过 IPC 通信 (除非显式共享)\n\n---\n\n### 12.2.8 USER Namespace\n\nUSER Namespace 允许将容器内的用户 ID 映射到宿主机的不同用户 ID。\n\n#### USER 的作用\n\n隔离用户和组 ID，实现权限隔离。\n\n#### USER 隔离效果\n\n```mermaid\nflowchart LR\n    subgraph Container [\"容器内\"]\n        direction TB\n        C1[\"UID 0 (root)\"]\n        C2[\"UID 1 (daemon)\"]\n    end\n\n    subgraph Host [\"宿主机\"]\n        direction TB\n        H1[\"UID 100000<br/>← 非特权用户\"]\n        H2[\"UID 100001\"]\n    end\n\n    C1 -- 映射 --> H1\n    C2 -- 映射 --> H2\n```\n\n#### 安全意义\n\n容器内的 root 用户可以映射为宿主机上的普通用户，即使容器被突破，攻击者在宿主机上也只有普通权限。\n\n> 💡 笔者建议：生产环境建议启用 User Namespace，增强安全性。\n\n---\n\n### 12.2.9 动手实验：体验 Namespace\n\n使用 `unshare` 命令可以在不使用 Docker 的情况下体验 Namespace：\n\n#### 实验 1：UTS Namespace\n\n```bash\n## 创建新的 UTS namespace 并启动 shell\n\n$ sudo unshare --uts /bin/bash\n\n## 修改主机名（只影响这个 namespace）\n\n$ hostname container-test\n$ hostname\ncontainer-test\n\n## 退出后查看宿主机主机名（未改变）\n\n$ exit\n$ hostname\nmy-server\n```\n\n#### 实验 2：PID Namespace\n\n```bash\n## 创建新的 PID 和 MNT namespace\n\n$ sudo unshare --pid --mount --fork /bin/bash\n\n## 挂载新的 /proc\n\n$ mount -t proc proc /proc\n\n## 查看进程（只能看到当前 shell）\n\n$ ps aux\nUSER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND\nroot         1  0.0  0.0   8960  4516 pts/0    S    10:00   0:00 /bin/bash\nroot         8  0.0  0.0  10072  3200 pts/0    R+   10:00   0:00 ps aux\n```\n\n#### 实验 3：NET Namespace\n\n```bash\n## 创建新的网络 namespace\n\n$ sudo unshare --net /bin/bash\n\n## 查看网络接口（只有 lo）\n\n$ ip addr\n1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN\n    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00\n```\n\n---\n\n### 12.2.10 Namespace 的局限性\n\nNamespace 提供了隔离但不是安全边界：\n\n| 方面 | 说明 |\n|------|------|\n| **共享内核** | 所有容器共享宿主机内核，内核漏洞可能影响所有容器 |\n| **部分资源未隔离** | /proc、/sys 部分内容仍可见；时间无法隔离 |\n| **非虚拟化** | 比虚拟机隔离性弱 |\n\n> 需要更强隔离时，可考虑 gVisor、Kata Containers 等安全容器方案。\n\n---\n"
  },
  {
    "path": "12_implementation/12.3_cgroups.md",
    "content": "## 12.3 控制组\n\n控制组 (Cgroups) 是 Linux 内核提供的另一种关键机制，主要用于资源的限制和审计。\n\n### 12.3.1 什么是控制组\n\n控制组 (Control Groups，简称 cgroups) 是 Linux 内核的一个特性，用于 **限制、记录和隔离** 进程组的资源使用 (CPU、内存、磁盘 I/O、网络等)。\n\n> **核心作用**：让多个容器公平共享宿主机资源，防止单个容器耗尽系统资源。\n\n```mermaid\nflowchart LR\n    subgraph NoLimit [\"无 cgroups 限制\"]\n        direction TB\n        subgraph HostRes1 [\"宿主机资源\"]\n            A[\"容器 A<br/>占用所有<br/>内存和 CPU\"]\n            B[\"容器 B、C 饥饿\"]\n        end\n    end\n    \n    subgraph Limit [\"有 cgroups 限制\"]\n        direction TB\n        subgraph HostRes2 [\"宿主机资源\"]\n            direction LR\n            C_A[\"A<br/>1GB<br/>2核\"]\n            C_B[\"B<br/>1GB<br/>1核\"]\n            C_C[\"C<br/>1GB<br/>1核\"]\n        end\n    end\n```\n\n---\n\n### 12.3.2 cgroups 的历史\n\n| 时间 | 事件 |\n|------|------|\n| 2006 | Google 工程师提出 “process containers” 概念 |\n| 2007 | 为避免与 Linux 容器概念混淆，更名为 “control groups” (cgroups) |\n| 2008 | Linux 2.6.24（2008年1月）正式合并 cgroups v1 |\n| 2016 | Linux 4.5 引入 cgroups v2 |\n| 现在 | Docker 在宿主机支持 cgroups v2 时会自动使用 v2，否则回退到 v1 |\n\n---\n\n### 12.3.3 cgroups 可以限制的资源\n\n| 资源类型 | 子系统 | 说明 |\n|---------|--------|------|\n| **CPU** | `cpu`, `cpuset` | CPU 使用时间和核心分配 |\n| **内存** | `memory` | 内存使用上限和 swap |\n| **块设备 I/O** | `blkio` | 磁盘读写速度限制 |\n| **网络** | `net_cls`, `net_prio` | 网络带宽优先级 |\n| **进程数** | `pids` | 限制进程/线程数量 |\n\n---\n\n### 12.3.4 Docker 中的资源限制\n\nDocker 提供了丰富的参数来配置容器的资源限制，主要包括内存、CPU、磁盘 I/O 等。\n\n#### 内存限制\n\n```bash\n## 限制容器最多使用 512MB 内存\n\n$ docker run -m 512m myapp\n\n## 限制内存 + swap\n\n$ docker run -m 512m --memory-swap 1g myapp\n\n## 软限制（超过时警告，不会 OOM Kill）\n\n$ docker run --memory-reservation 256m myapp\n```\n\n| 参数 | 说明 |\n|------|------|\n| `-m` / `--memory` | 硬限制 (超过会 OOM Kill)|\n| `--memory-swap` | 内存 + swap 总限制 |\n| `--memory-reservation` | 软限制 (内存竞争时生效)|\n| `--oom-kill-disable` | 禁用 OOM Killer (谨慎使用)|\n\n#### CPU 限制\n\n```bash\n## 限制使用 1.5 个 CPU 核心\n\n$ docker run --cpus=1.5 myapp\n\n## 限制使用 CPU 0 和 1\n\n$ docker run --cpuset-cpus=\"0,1\" myapp\n\n## 设置 CPU 使用权重（相对值，默认 1024）\n\n$ docker run --cpu-shares=512 myapp\n```\n\n| 参数 | 说明 |\n|------|------|\n| `--cpus` | 限制 CPU 核心数 (如 1.5)|\n| `--cpuset-cpus` | 绑定到特定 CPU 核心 |\n| `--cpu-shares` | CPU 时间片权重 (相对值)|\n| `--cpu-period` / `--cpu-quota` | 精细控制 CPU 配额 |\n\n#### 磁盘 I/O 限制\n\n```bash\n## 限制设备写入速度为 10MB/s\n\n$ docker run --device-write-bps /dev/sda:10mb myapp\n\n## 限制设备读取速度\n\n$ docker run --device-read-bps /dev/sda:10mb myapp\n\n## 限制 IOPS\n\n$ docker run --device-write-iops /dev/sda:100 myapp\n```\n\n#### 进程数限制\n\n```bash\n## 限制最多 100 个进程\n\n$ docker run --pids-limit=100 myapp\n```\n\n---\n\n### 12.3.5 查看容器资源使用\n\n```bash\n## 实时监控所有容器的资源使用\n\n$ docker stats\nCONTAINER ID   NAME    CPU %   MEM USAGE / LIMIT   MEM %   NET I/O        BLOCK I/O\nabc123         web     0.50%   45.5MiB / 512MiB    8.89%   1.2kB / 0B     0B / 0B\ndef456         db      2.30%   256MiB / 1GiB       25.00%  5.6kB / 3.2kB  4.1MB / 2.3MB\n\n## 查看特定容器\n\n$ docker stats mycontainer\n\n## 查看容器的 cgroup 配置\n\n$ docker inspect mycontainer --format '{{json .HostConfig}}' | jq\n```\n\n---\n\n### 12.3.6 资源限制的效果\n\n#### 内存超限\n\n```bash\n## 启动限制 100MB 内存的容器\n\n$ docker run -m 100m stress --vm 1 --vm-bytes 200M\n\n## 容器会被 OOM Killer 杀死\n\n$ docker ps -a\nCONTAINER ID   STATUS                      NAMES\nabc123         Exited (137) 5 seconds ago  hopeful_darwin\n\n## 137 = 128 + 9，表示被 SIGKILL（9） 杀死\n\n...\n```\n\n#### CPU 限制验证\n\n```bash\n## 不限制 CPU\n\n$ docker run --rm stress --cpu 4\n## 占满所有 CPU\n\n## 限制为 1 个核心\n\n$ docker run --rm --cpus=1 stress --cpu 4\n## 只能使用约 100% CPU（1 个核心）\n\n...\n```\n\n---\n\n### 12.3.7 cgroups v1 vs v2\n\n| 特性 | cgroups v1 | cgroups v2 |\n|------|-----------|-----------|\n| 层级结构 | 多层级 (每个资源单独)| 统一层级 |\n| 管理复杂度 | 复杂 | 简化 |\n| 资源分配 | 基于层级 | 基于子树 |\n| PSI (压力监控)| ❌ | ✅ |\n| rootless 容器 | 部分支持 | 完整支持 |\n\n#### Docker 对 cgroups v2 的支持\n\nDocker 19.03+ 默认优先使用 cgroups v2（如果系统支持），提供更好的性能和资源隔离。如果需要明确控制或回退到 v1，可以通过 Docker 守护进程配置文件 `/etc/docker/daemon.json` 修改 `cgroup-driver` 参数：\n\n```json\n{\n  \"cgroup-driver\": \"systemd\"\n}\n```\n\n常见的 `cgroup-driver` 值包括 `systemd` (推荐) 和 `cgroupfs`。重启 Docker 守护进程后生效。\n\n#### 检查系统使用的版本\n\n```bash\n## 查看 cgroup 版本\n\n$ mount | grep cgroup\ncgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime)\n## 如果显示 cgroup2 表示 v2\n\n## 或者\n\n$ cat /proc/filesystems | grep cgroup\nnodev   cgroup\nnodev   cgroup2\n```\n\n---\n\n### 12.3.8 在 Compose 中设置限制\n\n在 Compose 中设置限制配置如下：\n\n```yaml\nservices:\n  web:\n    image: nginx\n    deploy:\n      resources:\n        limits:\n          cpus: '0.5'\n          memory: 512M\n        reservations:\n          cpus: '0.25'\n          memory: 256M\n```\n\n---\n\n### 12.3.9 最佳实践\n\n在使用 Cgroups 限制资源时，遵循一些最佳实践可以避免潜在的问题。\n\n#### 1. 始终设置内存限制\n\n```bash\n## 防止 OOM 影响宿主机\n\n$ docker run -m 1g myapp\n```\n\n#### 2. 为关键应用设置 CPU 保证\n\n```bash\n$ docker run --cpus=2 --cpu-shares=2048 critical-app\n```\n\n#### 3. 监控资源使用\n\n```bash\n## 配合 Prometheus + cAdvisor 监控\n\n$ docker run -d --name cadvisor \\\n    -v /:/rootfs:ro \\\n    -v /var/run:/var/run:ro \\\n    -v /sys:/sys:ro \\\n    -v /var/lib/docker:/var/lib/docker:ro \\\n    ghcr.io/google/cadvisor\n```\n\n---\n"
  },
  {
    "path": "12_implementation/12.4_ufs.md",
    "content": "## 12.4 联合文件系统\n\n联合文件系统 (UnionFS) 是 Docker 镜像分层存储的基础，它允许将多个目录挂载为同一个虚拟文件系统。\n\n### 12.4.1 什么是联合文件系统\n\n联合文件系统 (UnionFS) 是一种 **分层、轻量级** 的文件系统，它将多个目录 “联合” 挂载到同一个虚拟目录，形成一个统一的文件系统视图。\n\n> **核心思想**：将多个只读层叠加，最上层可写，形成完整的文件系统。\n\n```mermaid\nflowchart TD\n    ContainerFS[\"容器看到的文件系统<br/>/bin /etc /lib /usr /var /app /data\"]\n    UnionFS[\"UnionFS 联合挂载\"]\n    \n    ContainerLayer[\"容器层 (读写) : /app/data/log.txt (新写入)\"]\n    ImageLayer3[\"镜像层3 (只读) : /app/app.py\"]\n    ImageLayer2[\"镜像层2 (只读) : /usr/local/bin/python\"]\n    ImageLayer1[\"镜像层1 (只读) : /bin /etc /lib (基础系统)\"]\n    \n    ContainerFS --> UnionFS\n    UnionFS --> ContainerLayer --> ImageLayer3 --> ImageLayer2 --> ImageLayer1\n```\n\n---\n\n### 12.4.2 为什么 Docker 使用联合文件系统\n\nDocker 选择联合文件系统作为其存储驱动，主要基于以下几个核心优势。\n\n#### 1. 镜像分层复用\n\n```mermaid\nflowchart TD\n    Nginx[\"nginx:alpine\"] --> Alpine[\"alpine:3.19 (共享基础层)\"]\n    MyApp[\"myapp:latest\"] --> Alpine\n```\n\n多个镜像共享相同的底层，节省磁盘空间。\n\n#### 2. 快速构建\n\n每个 Dockerfile 指令创建一层，只有变化的层需要重建：\n\n```docker\nFROM node:20          # 层1：基础镜像\nCOPY package.json ./  # 层2：依赖定义\nRUN npm install       # 层3：安装依赖\nCOPY . .              # 层4：应用代码\n```\n\n代码变化时，只需重建层 4，层 1-3 使用缓存。\n\n#### 3. 容器启动快\n\n容器启动时不需要复制镜像，只需：\n\n1. 在镜像层上创建一个薄的可写层\n2. 联合挂载所有层\n\n---\n\n### 12.4.3 Copy-on-Write：写时复制\n\n当容器修改只读层中的文件时：\n\n```mermaid\nflowchart LR\n    subgraph Before [\"修改前\"]\n        direction TB\n        B_C[\"容器层 (空)\"]\n        B_I[\"镜像层<br/>/etc/nginx.conf\"]\n    end\n    \n    subgraph After [\"修改后\"]\n        direction TB\n        A_C[\"容器层<br/>/etc/nginx.conf ← 复制到容器层后修改\"]\n        A_I[\"镜像层<br/>/etc/nginx.conf (原文件仍在，但被遮蔽)\"]\n    end\n    B_C --- B_I\n    A_C --- A_I\n```\n\n**流程**：\n\n1. 从只读层读取文件\n2. 复制到容器的可写层\n3. 在可写层中修改\n4. 后续读取使用可写层的版本\n\n---\n\n### 12.4.4 Docker 支持的存储驱动\n\nDocker 的存储驱动经历了从早期各式各样的机制（如 aufs, devicemapper），到被广泛使用的现代经典 graph driver (`overlay2`)，再到当下（Engine v29 及以后）**默认启用的 containerd 镜像存储引擎（containerd image store）** 的演进。\n\n| 存储后端 / 驱动 | 核心特性说明 | 推荐程度 |\n|---------|------|---------|\n| **containerd image store**| (v29+ 新一代默认引擎) 基于 containerd 的 snapshotters，原生支持 OCI image index、多架构镜像与 Attestations 构建溯源元数据存储。 | ✅**强烈推荐 (现代默认)** |\n| **overlay2**| (经典 Graph Driver) 传统架构下的现代 Linux 默认驱动，性能优秀，但在处理复杂溯源元数据（索引）时受限。 | ✅**推荐 (主要后备)** |\n| **aufs** | 早期默认，兼容性好 | 遗留系统 |\n| **btrfs**/**zfs** | 使用原生稳定文件系统快照能力 | 特定场景 |\n| **devicemapper** | 块设备级存储 | 遗留系统 (已被逐步弃用) |\n| **vfs** | 不使用 CoW，每层完整复制 | 仅测试 |\n\n#### Classic Graph Drivers 与 Snapshotters 的核心差异\n\n传统模型（如 `overlay2`）将镜像拉取解包的过程由 Docker 的 graph drivers 处理。而新的 `containerd image store` 则将这一职责彻底下放给了 `containerd` 自身的 `snapshotters`（底层在 Linux 发行版通常依然利用操作系统的 overlayfs）。这种架构改变带来了：\n1. 本地免拉取查看多平台镜像 index manifest 与 attestations (SBOM、Provenance)。\n2. 避免了以前绕过 CRI 获取本地镜像的问题，带来更好的原生 Kubernetes 生态兼容性。\n\n#### 查看当前存储驱动与后端\n\n```bash\n## 查看默认存储驱动 (Storage Driver)\n$ docker info | grep \"Storage Driver\"\nStorage Driver: overlay2\n\n## 在 Engine v29+ 中，可以通过如下输出验证是否开启了 containerd 镜像后端：\n$ docker info | grep \"containerd image store\"\n containerd image store: true\n```\n\n---\n\n### 12.4.5 overlay2 工作原理\n\noverlay2 是目前最推荐的存储驱动：\n\n```mermaid\nflowchart TD\n    Merged[\"merged (合并视图)<br/>容器看到的完整文件系统\"]\n    OverlayFS[\"OverlayFS\"]\n    \n    Upper[\"upper<br/>(容器层)<br/>读写\"]\n    Lower2[\"lower2<br/>(镜像层)<br/>只读\"]\n    Lower1[\"lower1<br/>(基础层)<br/>只读\"]\n    \n    Merged --> OverlayFS\n    OverlayFS --> Upper\n    OverlayFS --> Lower2\n    OverlayFS --> Lower1\n```\n\n- **lowerdir**：只读的镜像层 (可以有多个)\n- **upperdir**：可写的容器层\n- **workdir**：OverlayFS 的工作目录\n- **merged**：联合挂载后的视图\n\n#### 文件操作行为\n\n| 操作 | 行为 |\n|------|------|\n| **读取** | 从上到下查找第一个匹配的文件 |\n| **创建** | 在 upper 层创建 |\n| **修改** | 如果在 lower 层，先复制到 upper 层再修改 |\n| **删除** | 在 upper 层创建 whiteout 文件标记删除 |\n\n---\n\n### 12.4.6 查看镜像层\n\n```bash\n## 查看镜像的层信息\n\n$ docker history nginx:alpine\nIMAGE          CREATED       CREATED BY                                      SIZE\na6eb2a334a9f   2 weeks ago   CMD [\"nginx\" \"-g\" \"daemon off;\"]                0B\n<missing>      2 weeks ago   STOPSIGNAL SIGQUIT                              0B\n<missing>      2 weeks ago   EXPOSE map[80/tcp:{}]                           0B\n<missing>      2 weeks ago   ENTRYPOINT [\"/docker-entrypoint.sh\"]            0B\n<missing>      2 weeks ago   COPY 30-tune-worker-processes.sh /docker-ent…   4.62kB\n...\n\n## 查看层的存储位置\n\n$ docker inspect nginx:alpine --format '{{json .GraphDriver.Data}}' | jq\n{\n  \"LowerDir\": \"/var/lib/docker/overlay2/.../diff:/var/lib/docker/overlay2/.../diff\",\n  \"MergedDir\": \"/var/lib/docker/overlay2/.../merged\",\n  \"UpperDir\": \"/var/lib/docker/overlay2/.../diff\",\n  \"WorkDir\": \"/var/lib/docker/overlay2/.../work\"\n}\n```\n\n---\n\n### 12.4.7 最佳实践\n\n为了构建高效、轻量的镜像，我们在使用联合文件系统时应注意以下几点。\n\n#### 1. 减少镜像层数\n\n```docker\n## ❌ 每条命令创建一层\n\nRUN apt-get update\nRUN apt-get install -y nginx\nRUN rm -rf /var/lib/apt/lists/*\n\n## ✅ 合并为一层\n\nRUN apt-get update && \\\n    apt-get install -y nginx && \\\n    rm -rf /var/lib/apt/lists/*\n```\n\n#### 2. 避免在容器中写入大量数据\n\n容器层的写入性能低于直接写入。大量数据应使用：\n\n- 数据卷 (Volume)\n- 绑定挂载 (Bind Mount)\n\n#### 3. 使用 .dockerignore\n\n排除不需要的文件可以：\n\n- 减小构建上下文\n- 避免创建不必要的层\n\n---\n"
  },
  {
    "path": "12_implementation/12.5_container_format.md",
    "content": "## 12.5 容器格式\n\n### Docker 容器格式的演进\n\n最初，Docker 采用了 `LXC` 中的容器格式。从 0.7 版本以后开始去除 LXC 的依赖，转而使用自行开发的 [libcontainer](https://github.com/docker/libcontainer)。从 1.11 开始，则进一步演进为使用 [runC](https://github.com/opencontainers/runc) 和 [containerd](https://github.com/containerd/containerd)。\n\n### 关键组件说明\n\n#### LXC\n\nDocker 早期版本（0.1-0.7）直接使用 LXC 作为容器运行时，利用 Linux Namespaces 和 Cgroups 实现容器隔离。\n\n#### libcontainer\n\n- Docker 自行开发的容器库\n- 提供了容器的通用接口\n- 不依赖于特定的 Linux 容器实现\n- 更灵活和可控\n\n#### runC\n\n- OCI（Open Container Initiative）标准实现\n- 轻量级的容器运行时\n- 独立的二进制文件，可单独使用\n- 基于 libcontainer 发展而来\n\n#### containerd\n\n- Docker 开源的容器运行时\n- 提供了容器的完整生命周期管理\n- 支持 runC 和其他 OCI 兼容的运行时\n- 在 Kubernetes 等编排系统中广泛使用\n\n### 容器规范标准\n\nDocker 积极参与 Open Container Initiative (OCI) 的制定，推动了以下规范的发展：\n\n- **Image Spec**：容器镜像格式规范\n- **Runtime Spec**：容器运行时接口规范\n- **Distribution Spec**：容器镜像分发规范\n\n### 架构演变的优势\n\n从 LXC → libcontainer → runC/containerd 的演变提供了以下优势：\n\n1. 减少外部依赖\n2. 提高运行效率\n3. 遵循行业标准（OCI）\n4. 增强可移植性和互操作性\n5. 支持多种容器运行时选择\n"
  },
  {
    "path": "12_implementation/12.6_network.md",
    "content": "## 12.6 网络\n\nDocker 的网络实现其实就是利用了 Linux 上的网络命名空间和虚拟网络设备 (特别是 veth pair)。建议先熟悉了解这两部分的基本概念再阅读本章。\n\n### 12.6.1 基本原理\n\n首先，要实现网络通信，机器需要至少一个网络接口 (物理接口或虚拟接口) 来收发数据包；此外，如果不同子网之间要进行通信，需要路由机制。\n\nDocker 中的网络接口默认都是虚拟的接口。虚拟接口的优势之一是转发效率较高。\nLinux 通过在内核中进行数据复制来实现虚拟接口之间的数据转发，发送接口的发送缓存中的数据包被直接复制到接收接口的接收缓存中。对于本地系统和容器内系统看来就像是一个正常的以太网卡，只是它不需要真正同外部网络设备通信，速度要快很多。\n\nDocker 容器网络就利用了这项技术。它在本地主机和容器内分别创建一个虚拟接口，并让它们彼此连通 (这样的一对接口叫做 `veth pair`)。\n\n### 12.6.2 创建网络参数\n\nDocker 创建一个容器的时候，会执行如下操作：\n\n* 创建一对虚拟接口，分别放到本地主机和新容器中；\n* 本地主机一端桥接到默认的 docker0 或指定网桥上，并具有一个唯一的名字，如 veth65f9；\n* 容器一端放到新容器中，并修改名字作为 eth0，这个接口只在容器的命名空间可见；\n* 从网桥可用地址段中获取一个空闲地址分配给容器的 eth0，并配置默认路由到桥接网卡 veth65f9。\n\n完成这些之后，容器就可以使用 eth0 虚拟网卡来连接其他容器和其他网络。\n\n可以在 `docker run` 的时候通过 `--net` 参数来指定容器的网络配置，有 4 个可选值：\n\n* `--net=bridge` 这个是默认值，连接到默认的网桥。\n* `--net=host` 告诉 Docker 不要将容器网络放到隔离的命名空间中，即不要容器化容器内的网络。此时容器使用本地主机的网络，它拥有完全的本地主机接口访问权限。容器进程可以跟主机其它 root 进程一样可以打开低范围的端口，可以访问本地网络服务比如 D-bus，还可以让容器做一些影响整个主机系统的事情，比如重启主机。因此使用这个选项的时候要非常小心。如果进一步的使用 `--privileged=true`，容器会被允许直接配置主机的网络堆栈。\n* `--net=container:NAME_or_ID` 让 Docker 将新建容器的进程放到一个已存在容器的网络栈中，新容器进程有自己的文件系统、进程列表和资源限制，但会和已存在的容器共享 IP 地址和端口等网络资源，两者进程可以直接通过 `lo` 环回接口通信。\n* `--net=none` 让 Docker 将新容器放到隔离的网络栈中，但是不进行网络配置。之后，用户可以自己进行配置。\n\n### 12.6.3 网络配置细节\n\n用户使用 `--net=none` 后，可以自行配置网络，让容器达到跟平常一样具有访问网络的权限。通过这个过程，可以了解 Docker 配置网络的细节。\n\n首先，启动一个 `/bin/bash` 容器，指定 `--net=none` 参数。\n```bash\n$ docker run -i -t --rm --net=none base /bin/bash\nroot@63f36fc01b5f:/#\n```\n在本地主机查找容器的进程 id，并为它创建网络命名空间。\n```bash\n$ docker inspect -f '{{.State.Pid}}' 63f36fc01b5f\n2778\n$ pid=2778\n$ sudo mkdir -p /var/run/netns\n$ sudo ln -s /proc/$pid/ns/net /var/run/netns/$pid\n```\n检查桥接网卡的 IP 和子网掩码信息。\n```bash\n$ ip addr show docker0\n21: docker0: ...\ninet 172.17.42.1/16 scope global docker0\n...\n```\n创建一对 “veth pair” 接口 A 和 B，绑定 A 到网桥 `docker0`，并启用它\n```bash\n$ sudo ip link add A type veth peer name B\n$ sudo brctl addif docker0 A\n$ sudo ip link set A up\n```\n将 B 放到容器的网络命名空间，命名为 eth0，启动它并配置一个可用 IP (桥接网段) 和默认网关。\n```bash\n$ sudo ip link set B netns $pid\n$ sudo ip netns exec $pid ip link set dev B name eth0\n$ sudo ip netns exec $pid ip link set eth0 up\n$ sudo ip netns exec $pid ip addr add 172.17.42.99/16 dev eth0\n$ sudo ip netns exec $pid ip route add default via 172.17.42.1\n```\n以上，就是 Docker 配置网络的具体过程。\n\n当容器结束后，Docker 会清空容器，容器内的 eth0 会随网络命名空间一起被清除，A 接口也被自动从 `docker0` 卸载。\n\n此外，用户可以使用 `ip netns exec` 命令来在指定网络命名空间中进行配置，从而配置容器内的网络。\n"
  },
  {
    "path": "12_implementation/README.md",
    "content": "# 第十二章 底层实现\n\nDocker 底层的核心技术包括 Linux 上的命名空间 (Namespaces)、控制组 (Control groups)、Union 文件系统 (Union file systems) 和容器格式 (Container format)。\n\n我们知道，传统的虚拟机通过在宿主主机中运行 hypervisor 来模拟一整套完整的硬件环境提供给虚拟机的操作系统。虚拟机系统看到的环境是可限制的，也是彼此隔离的。\n这种直接的做法实现了对资源最完整的封装，但很多时候往往意味着系统资源的浪费。\n例如，以宿主机和虚拟机系统都为 Linux 系统为例，虚拟机中运行的应用其实可以利用宿主机系统中的运行环境。\n\n我们知道，在操作系统中，包括内核、文件系统、网络、PID、UID、IPC、内存、硬盘、CPU 等等，所有的资源都是应用进程直接共享的。\n要想实现虚拟化，除了要实现对内存、CPU、网络 IO、硬盘 IO、存储空间等的限制外，还要实现文件系统、网络、PID、UID、IPC 等等的相互隔离。\n前者相对容易实现一些，后者则需要宿主机系统的深入支持。\n\n随着 Linux 系统对于命名空间功能的完善实现，程序员已经可以实现上面的所有需求，让某些进程在彼此隔离的命名空间中运行。大家虽然都共用一个内核和某些运行时环境 (例如一些系统命令和系统库)，但是彼此却看不到，都以为系统中只有自己的存在。这种机制就是容器 (Container)，利用命名空间来做权限的隔离控制，利用 cgroups 来做资源分配。\n\n## 本章内容\n\n* [基本架构](12.1_arch.md)\n* [命名空间](12.2_namespace.md)\n* [控制组](12.3_cgroups.md)\n* [联合文件系统](12.4_ufs.md)\n* [容器格式](12.5_container_format.md)\n* [网络](12.6_network.md)\n"
  },
  {
    "path": "12_implementation/summary.md",
    "content": "## 本章小结\n\n本章深入介绍了 Docker 的底层实现，包括命名空间、控制组和联合文件系统三大核心技术。\n\n| 技术 | 作用 | 要点 |\n|------|------|------|\n| **Namespace** | 资源隔离 | PID、NET、MNT、UTS、IPC、USER 六种命名空间 |\n| **Cgroups** | 资源限制 | 限制 CPU、内存、磁盘 I/O、进程数 |\n| **Union FS** | 分层存储 | overlay2 为推荐驱动，支持 Copy-on-Write |\n\n| Namespace | 隔离内容 | 一句话说明 |\n|-----------|---------|-----------| \n| PID | 进程 ID | 容器有自己的进程树 |\n| NET | 网络 | 容器有自己的 IP 和端口 |\n| MNT | 文件系统 | 容器有自己的根目录 |\n| UTS | 主机名 | 容器有自己的 hostname |\n| IPC | 进程间通信 | 容器间 IPC 隔离 |\n| USER | 用户 ID | 容器 root ≠ 宿主机 root |\n\n| 资源 | 限制参数 | 示例 |\n|------|---------|------|\n| **内存** | `-m` | `-m 512m` |\n| **CPU 核心数** | `--cpus` | `--cpus=1.5` |\n| **CPU 绑定** | `--cpuset-cpus` | `--cpuset-cpus=\"0,1\"` |\n| **磁盘 I/O** | `--device-write-bps` | `--device-write-bps /dev/sda:10mb` |\n| **进程数** | `--pids-limit` | `--pids-limit=100` |\n\n### 延伸阅读\n\n- [命名空间](12.2_namespace.md)：资源隔离机制详解\n- [控制组 (Cgroups)](12.3_cgroups.md)：资源限制机制\n- [联合文件系统](12.4_ufs.md)：分层存储的实现\n- [安全](../18_security/README.md)：容器安全实践\n- [镜像](../02_basic_concept/2.1_image.md)：理解镜像分层\n- [容器](../02_basic_concept/2.2_container.md)：容器存储层\n- [构建镜像](../04_image/4.5_build.md)：Dockerfile 层的创建\n---\n\n> 📝 **发现错误或有改进建议？** 欢迎提交 [Issue](https://github.com/yeasy/docker_practice/issues) 或 [PR](https://github.com/yeasy/docker_practice/pulls)。\n"
  },
  {
    "path": "13_kubernetes_concepts/13.1_intro.md",
    "content": "## 13.1 简介\n\n如图 13-1 所示，Kubernetes 使用舵手图标作为项目标识。\n\n![Kubernetes 标识](./_images/kubernetes_logo.png)\n\n图 13-1：Kubernetes 项目标识\n\n### 13.1.1 什么是 Kubernetes\n\nKubernetes (常简称为 K8s) 是 Google 开源的容器编排引擎。如果说 Docker 解决了 “如何打包和运送集装箱” 的问题，那么 Kubernetes 解决的就是 “如何管理海量集装箱的调度、运行和维护” 的问题。\n\n它不仅仅是一个编排系统，更是一个 **云原生应用操作系统**。\n\n> **名字由来**：Kubernetes 在希腊语中意为 “舵手” 或 “飞行员”。K8s 是因为 k 和 s 之间有 8 个字母。\n\n---\n\n### 13.1.2 为什么需要 Kubernetes\n\n当我们在单机运行几个容器时，Docker Compose 就足够了。但在生产环境中，我们需要面对：\n\n- **多主机调度**：容器应该运行在哪台机器上？\n- **自动恢复**：容器崩溃了怎么办？节点挂了怎么办？\n- **服务发现**：容器 IP 变了，其他服务怎么找到它？\n- **负载均衡**：流量大了，如何分发给多个副本？\n- **滚动更新**：如何不中断服务升级应用？\n\nKubernetes 完美解决了这些问题。\n\n---\n\n### 13.1.3 核心概念\n\n#### Pod：豆荚\n\nKubernetes 的最小调度单位。一个 Pod 可以包含一个或多个紧密协作的容器 (共享网络和存储)。就像豌豆荚里的豌豆一样。\n\n#### Node：节点\n\n运行 Pod 的物理机或虚拟机。\n\n#### Deployment：部署\n\n定义应用的期望状态 (如：需要 3 个副本，镜像版本为 v1)。K8s 会持续确保当前状态符合期望状态。\n\n#### Service：服务\n\n定义一组 Pod 的访问策略。提供稳定的 Cluster IP 和 DNS 名称，负责负载均衡。\n\n#### Namespace：命名空间\n\n用于多租户资源隔离。\n\n---\n\n### 13.1.4 Docker 用户如何过渡\n\n如果你已经熟悉 Docker，学习 K8s 会很容易：\n\n| Docker 概念 | Kubernetes 概念 | 说明 |\n|------------|----------------|------|\n| Container  | Pod            | K8s 增加了一层 Pod 包装 |\n| Volume     | PersistentVolume | K8s 的存储更加抽象和强大 |\n| Network    | Service/Ingress| K8s 的网络模型更扁平 |\n| Compose    | Deployment + Service | 声明式配置的理念是一致的 |\n\n---\n\n### 13.1.5 架构\n\nKubernetes 也是 C/S 架构，由 **Control Plane (控制平面)** 和 **Worker Node (工作节点)** 组成：\n\n- **Control Plane**：负责决策 (API Server，Scheduler，Controller Manager，etcd)\n- **Worker Node**：负责干活 (Kubelet，Kube-proxy，Container Runtime)\n\n---\n\n### 13.1.6 学习建议\n\nKubernetes 的学习曲线较陡峭。建议的学习路径：\n\n1. **理解基本概念**：Pod，Deployment，Service\n2. **动手实践**：使用 Minikube 或 Kind 在本地搭建集群\n3. **部署应用**：编写 YAML 部署一个无状态应用\n4. **深入原理**：网络模型、存储机制、调度算法\n\n---\n\n### 13.1.7 延伸阅读\n\n- [Minikube 安装](../14_kubernetes_setup/README.md)：本地体验 K8s\n- [Kubernetes 官网](https://kubernetes.io/)：官方文档\n"
  },
  {
    "path": "13_kubernetes_concepts/13.2_concepts.md",
    "content": "## 13.2 基本概念\n\n如图 13-2 所示，Kubernetes 由控制平面与工作节点构成。\n\n![Kubernetes 基本概念示意图](./_images/kubernetes_design.jpg)\n\n图 13-2：Kubernetes 基本概念示意图\n\n* 节点 (`Node`)：一个节点是一个运行 Kubernetes 中的主机。\n* 容器组 (`Pod`)：一个 Pod 对应于由若干容器组成的一个容器组，同个组内的容器共享一个存储卷 (volume)。\n* 容器组生命周期 (`pod-states`)：包含所有容器状态集合，包括容器组状态类型，容器组生命周期，事件，重启策略，以及 replication controllers。\n* Replication Controllers：主要负责指定数量的 pod 在同一时间一起运行。\n* 服务 (`services`)：一个 Kubernetes 服务是容器组逻辑的高级抽象，同时也对外提供访问容器组的策略。\n* 卷 (`volumes`)：一个卷就是一个目录，容器对其有访问权限。\n* 标签 (`labels`)：标签是用来连接一组对象的，比如容器组。标签可以被用来组织和选择子对象。\n* 接口权限 (`accessing_the_api`)：端口，IP 地址和代理的防火墙规则。\n* web 界面 (`ux`)：用户可以通过 web 界面操作 Kubernetes。\n* 命令行操作 (`cli`)：`kubectl` 命令。\n\n### 13.2.1 节点\n\n在 `Kubernetes` 中，节点是实际工作的点，节点可以是虚拟机或者物理机器，依赖于一个集群环境。每个节点都有一些必要的服务以运行容器组，并且它们都可以通过主节点来管理。必要服务包括 Docker，kubelet 和代理服务。\n\n#### 容器状态\n\n容器状态用来描述节点的当前状态。现在，其中包含三个信息：\n\n##### 主机 IP\n\n主机 IP 需要云平台来查询，`Kubernetes` 把它作为状态的一部分来保存。如果 `Kubernetes` 没有运行在云平台上，节点 ID 就是必需的。IP 地址可以变化，并且可以包含多种类型的 IP 地址，如公共 IP，私有 IP，动态 IP，ipv6 等等。\n\n##### 节点周期\n\n通常来说节点有 `Pending`，`Running`，`Terminated` 三个周期，如果 Kubernetes 发现了一个节点并且其可用，那么 Kubernetes 就把它标记为 `Pending`。然后在某个时刻，Kubernetes 将会标记其为 `Running`。节点的结束周期称为 `Terminated`。一个已经 `Terminated` 的节点不会接受和调度任何请求，并且已经在其上运行的容器组也会删除。\n\n##### 节点状态\n\n节点的状态主要是用来描述处于 `Running` 的节点。当前可用的有 `NodeReachable` 和 `NodeReady`。以后可能会增加其他状态。`NodeReachable` 表示集群可达。`NodeReady` 表示 kubelet 返回 Status Ok 并且 HTTP 状态检查健康。\n\n#### 节点管理\n\n节点并非 Kubernetes 创建，而是由云平台创建，或者就是物理机器、虚拟机。在 Kubernetes 中，节点仅仅是一条记录，节点创建之后，Kubernetes 会检查其是否可用。可以通过 `kubectl` 查看节点信息：\n\n```bash\n$ kubectl get nodes\nNAME           STATUS   ROLES           AGE   VERSION\ncontrol-plane  Ready    control-plane   10d   v1.35.1\nworker-1       Ready    <none>          10d   v1.35.1\nworker-2       Ready    <none>          10d   v1.35.1\n```\n\n每个节点的详细信息以如下结构保存：\n\n```yaml\napiVersion: v1\nkind: Node\nmetadata:\n  name: worker-1\n  labels:\n    kubernetes.io/os: linux\nstatus:\n  capacity:\n    cpu: \"4\"\n    memory: 8Gi\n  conditions:\n    - type: Ready\n      status: \"True\"\n```\n\n#### 节点控制器\n\n在 Kubernetes 控制平面中，节点控制器 (Node Controller) 负责管理节点的生命周期，主要包含：\n\n* 集群范围内节点状态同步\n* 单节点生命周期管理\n\n节点控制器会持续监控节点的健康状态。当节点变为不可达时，控制器会等待一个超时期限，然后将该节点上的 Pod 标记为失败，并触发重新调度。可以使用 `kubectl` 来管理节点，例如标记节点为不可调度或排空节点上的工作负载：\n\n```bash\n## 标记节点为不可调度\n$ kubectl cordon worker-1\n\n## 排空节点上的 Pod\n$ kubectl drain worker-1 --ignore-daemonsets\n```\n\n### 13.2.2 容器组\n\n在 Kubernetes 中，使用的最小调度单位是容器组 (Pod)，它是创建、调度、管理的最小单位。一个 Pod 包含一个或多个紧密协作的容器，它们共享网络命名空间和存储卷。\n\nPod 通常不会被直接创建，而是通过 Deployment 等控制器来管理。当节点发生故障时，控制器会在其他可用节点上重新创建 Pod。\n\n#### 容器组设计的初衷\n\n容器组 (Pod) 的设计主要是为了解决应用间的紧密协作和资源共享问题。\n\n#### 资源共享和通信\n\n容器组主要是为了数据共享和它们之间的通信。\n\n在一个容器组中，容器都使用相同的网络地址和端口，可以通过本地网络来相互通信。每个容器组都有独立的 IP，可用通过网络来和其他物理主机或者容器通信。\n\n容器组有一组存储卷 (挂载点)，主要是为了让容器在重启之后可以不丢失数据。\n\n#### 容器组管理\n\n容器组是一个应用管理和部署的高层次抽象，同时也是一组容器的接口。容器组是部署、水平放缩的最小单位。\n\n#### 容器组的使用\n\n容器组可以通过组合来构建复杂的应用，典型的使用模式包含：\n\n* 内容管理，文件和数据加载以及本地缓存管理等。\n* 日志和检查点备份，压缩，快照等。\n* 监听数据变化，跟踪日志，日志和监控代理，消息发布等。\n* 代理，网桥\n* 控制器，管理，配置以及更新\n\n#### 为什么不在一个容器里运行多个程序\n\n1. **透明化**：为了使容器组中的容器保持一致的基础设施和服务，比如进程管理和资源监控。\n2. **解耦依赖**：每个容器都可能独立地重新构建和发布。\n3. **方便使用**：用户不必运行独立的程序管理，也不用担心每个应用程序的退出状态。\n4. **高效**：考虑到基础设施有更多的职责，容器必须要轻量化。\n\n#### 容器组的生命状态\n\n包括若干状态值：`Pending`、`Running`、`Succeeded`、`Failed`。\n\n| 状态 | 说明 |\n|------|------|\n| **Pending** | Pod 已被集群接受，但有一个或多个容器还没有运行起来（可能在拉取镜像）。|\n| **Running** | Pod 已被调度到节点，并且所有容器都已启动。至少有一个容器处于运行状态。|\n| **Succeeded** | Pod 中的所有容器都正常退出，且不会被重启。|\n| **Failed** | Pod 中的所有容器都已终止，且至少有一个容器以失败状态退出。|\n\n#### 容器组生命周期与重启策略\n\nPod 的重启策略 (`restartPolicy`) 决定了容器退出后的行为：\n\n| 重启策略 | 容器正常退出 | 容器异常退出 |\n|---------|------------|------------|\n| **Always** (默认) | 重启容器 | 重启容器 |\n| **OnFailure** | 不重启 | 重启容器 |\n| **Never** | 不重启 | 不重启 |\n\n当节点故障或不可达时，节点控制器会将该节点上所有 Pod 的状态标记为 `Failed`。如果这些 Pod 由 Deployment 等控制器管理，控制器会自动在其他节点上重新创建。\n\n### 13.2.3 Deployment 与 ReplicaSet\n\nDeployment 是管理无状态应用的推荐方式，它通过 ReplicaSet 来确保指定数量的 Pod 副本始终在运行。\n\n```yaml\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: nginx-deployment\nspec:\n  replicas: 3\n  selector:\n    matchLabels:\n      app: nginx\n  template:\n    metadata:\n      labels:\n        app: nginx\n    spec:\n      containers:\n        - name: nginx\n          image: nginx:1.27\n          ports:\n            - containerPort: 80\n```\n\nDeployment 的核心能力包括：\n\n* **副本管理**：确保始终有指定数量的 Pod 在运行\n* **滚动更新**：逐步替换旧版本 Pod，实现零停机部署\n* **回滚**：如果新版本出现问题，可以快速回滚到之前的版本\n\n> 早期 Kubernetes 使用 Replication Controller (RC) 来管理副本，现已被 ReplicaSet/Deployment 取代。\n\n### 13.2.4 服务\n\n服务 (Service) 定义了一组 Pod 的逻辑集合和访问策略。由于 Pod 的 IP 地址是动态分配的，Service 提供了一个稳定的访问入口。\n\n```yaml\napiVersion: v1\nkind: Service\nmetadata:\n  name: nginx-service\nspec:\n  selector:\n    app: nginx\n  ports:\n    - port: 80\n      targetPort: 80\n  type: ClusterIP\n```\n\n常见的 Service 类型：\n\n| 类型 | 说明 |\n|------|------|\n| **ClusterIP** | 默认类型，仅集群内部可访问 |\n| **NodePort** | 在每个节点上开放固定端口，集群外部可通过 `节点IP:端口` 访问 |\n| **LoadBalancer** | 通过云平台的负载均衡器暴露服务 |\n\n### 13.2.5 卷\n\n卷 (Volume) 为 Pod 中的容器提供持久化存储。Kubernetes 支持多种卷类型：\n\n| 卷类型 | 说明 |\n|-------|------|\n| **emptyDir** | 临时存储，Pod 删除后数据丢失 |\n| **hostPath** | 挂载节点上的文件或目录 |\n| **PersistentVolumeClaim** | 使用持久卷声明，与底层存储解耦 |\n| **configMap / secret** | 将配置或敏感数据挂载为文件 |\n\n生产环境中，推荐使用 PersistentVolume (PV) 和 PersistentVolumeClaim (PVC) 来管理存储，实现存储资源与使用者的解耦。\n\n### 13.2.6 标签\n\n标签 (Label) 是附加到 Kubernetes 对象上的键值对，用于组织和选择对象子集。标签是 Kubernetes 中实现松耦合的关键机制。\n\n```bash\n## 为 Pod 添加标签\n$ kubectl label pod my-pod env=production\n\n## 通过标签选择器查询\n$ kubectl get pods -l env=production\n```\n\nService、Deployment 等资源都通过标签选择器 (`selector`) 来关联目标 Pod。\n\n### 13.2.7 API 访问控制\n\nKubernetes API 的访问通过三个阶段进行控制：\n\n1. **认证 (Authentication)**：验证请求者的身份（如证书、Token、OIDC）\n2. **授权 (Authorization)**：判断请求者是否有权限执行操作（通常使用 RBAC）\n3. **准入控制 (Admission Control)**：在请求被持久化之前对其进行校验或修改\n\n### 13.2.8 Dashboard\n\nKubernetes Dashboard 是一个基于 Web 的用户界面，用于部署容器化应用、监控集群资源和排查问题。Dashboard 的部署方法详见[部署 Dashboard](../14_kubernetes_setup/14.7_dashboard.md) 章节。\n\n### 13.2.9 命令行工具 kubectl\n\n`kubectl` 是 Kubernetes 的命令行工具，用于与集群进行交互。常用命令如下：\n\n```bash\n## 查看集群中的资源\n$ kubectl get pods,deployments,services,nodes\n\n## 创建资源\n$ kubectl apply -f deployment.yaml\n\n## 查看 Pod 日志\n$ kubectl logs my-pod\n\n## 进入 Pod 执行命令\n$ kubectl exec -it my-pod -- /bin/sh\n\n## 查看资源详情\n$ kubectl describe pod my-pod\n```\n\n更多 kubectl 操作详见[kubectl 命令行](../14_kubernetes_setup/14.8_kubectl.md)章节。\n"
  },
  {
    "path": "13_kubernetes_concepts/13.3_design.md",
    "content": "## 13.3 架构设计\n\n任何优秀的项目都离不开优秀的架构设计。本小节将介绍 Kubernetes 在架构方面的设计考虑。\n\n### 13.3.1 基本考虑\n\n如果让我们自己从头设计一套容器管理平台，有如下几个方面是很容易想到的：\n\n* 分布式架构，保证扩展性；\n* 逻辑集中式的控制平面 + 物理分布式的运行平面；\n* 一套资源调度系统，管理哪个容器该分配到哪个节点上；\n* 一套对容器内服务进行抽象和 HA 的系统。\n\n### 13.3.2 运行原理\n\n如图 13-3 所示，该图完整展示了 Kubernetes 的运行原理。\n\n![Kubernetes 架构](./_images/k8s_architecture.png)\n\n图 13-3：Kubernetes 运行原理图\n\n可见，Kubernetes 首先是一套分布式系统，由多个节点组成，节点分为两类：一类是属于管理平面的主节点/控制节点 (Master Node)；一类是属于运行平面的工作节点 (Worker Node)。\n\n显然，复杂的工作肯定都交给控制节点去做了，工作节点负责提供稳定的操作接口和能力抽象即可。\n\n从这张图上，我们没有能发现 Kubernetes 中对于控制平面的分布式实现，但是由于数据后端自身就是一套分布式的数据库 Etcd，因此可以很容易扩展到分布式实现。\n\n### 13.3.3 控制平面\n\n控制平面 (Control Plane) 是 Kubernetes 集群的大脑，负责做出全局决策 (如调度) 以及检测和响应集群事件。\n\n#### 主节点服务\n\n主节点上需要提供如下的管理服务：\n\n* `apiserver` 是整个系统的对外接口，提供一套 RESTful 的 [Kubernetes API](https://kubernetes.io/zh/docs/concepts/overview/kubernetes-api/)，供客户端和其它组件调用；\n* `scheduler` 负责对资源进行调度，分配某个 pod 到某个节点上。是 pluggable 的，意味着很容易选择其它实现方式；\n* `controller-manager` 负责管理控制器，包括 endpoint-controller (刷新服务和 pod 的关联信息) 和 replication-controller (维护某个 pod 的复制为配置的数值)。\n\n#### Etcd\n\n这里 Etcd 即作为数据后端，又作为消息中间件。\n\n通过 Etcd 来存储所有的主节点上的状态信息，很容易实现主节点的分布式扩展。\n\n组件可以自动的去侦测 Etcd 中的数值变化来获得通知，并且获得更新后的数据来执行相应的操作。\n\n### 13.3.4 工作节点\n\n* kubelet 是工作节点执行操作的 agent，负责具体的容器生命周期管理，根据从数据库中获取的信息来管理容器，并上报 pod 运行状态等；\n* kube-proxy 是一个简单的网络访问代理，同时也是一个 Load Balancer。它负责将访问到某个服务的请求具体分配给工作节点上的 Pod (同一类标签)。\n\n![Proxy 代理对服务的请求](./_images/kube-proxy.png)\n\n图 13-4：kube-proxy 请求转发示意图\n"
  },
  {
    "path": "13_kubernetes_concepts/13.4_advanced.md",
    "content": "## 13.4 高级特性\n\n掌握了 Kubernetes 的核心概念 (Pod，Service，Deployment) 后，我们需要了解更多高级特性以构建生产级应用。\n\n### 13.4.1 Helm - 包管理工具\n\n[Helm](https://helm.sh/) 被称为 Kubernetes 的包管理器 (类似于 Linux 的 apt/yum)。它将一组 Kubernetes 资源定义文件打包为一个 **Chart**。\n\n*   **安装应用**：`helm install my-release bitnami/mysql`\n*   **版本管理**：轻松回滚应用的发布版本。\n*   **模板化**：支持复杂的应用部署逻辑配置。\n\n### 13.4.2 Ingress - 服务的入口\n\nService 虽然提供了负载均衡，但通常是 4 层 (TCP/UDP)。**Ingress** 提供了 7 层 (HTTP/HTTPS) 路由能力，充当集群的网关。\n\n*   **域名路由**：基于 Host 将请求转发不同服务 (api.example.com -> api-svc，web.example.com -> web-svc)。\n*   **路径路由**：基于 Path 将请求转发 (/api -> api-svc， / -> web-svc)。\n*   **SSL/TLS**：集中管理证书。\n\n常见的 Ingress Controller 有 Nginx Ingress Controller，Traefik，Istio Gateway 等。\n\n### 13.4.3 Persistent Volume 与 StorageClass\n\n容器内的文件是临时的。对于有状态应用 (如数据库)，需要持久化存储。\n\n*   **PVC (Persistent Volume Claim)**：用户申请存储的声明。\n*   **PV (Persistent Volume)**：实际的存储资源 (NFS，AWS EBS，Ceph 等)。\n*   **StorageClass**：定义存储类，支持动态创建 PV。\n\n### 13.4.4 Horizontal Pod Autoscaling\n\nHPA 根据 CPU 利用率或其他指标 (如内存、自定义指标) 自动扩缩 Deployment 或 ReplicaSet 中的 Pod 数量。\n\n```yaml\napiVersion: autoscaling/v2\nkind: HorizontalPodAutoscaler\nmetadata:\n  name: php-apache\nspec:\n  scaleTargetRef:\n    apiVersion: apps/v1\n    kind: Deployment\n    name: php-apache\n  minReplicas: 1\n  maxReplicas: 10\n  metrics:\n  - type: Resource\n    resource:\n      name: cpu\n      target:\n        type: Utilization\n        averageUtilization: 50\n```\n\n### 13.4.5 ConfigMap 与 Secret\n\n*   **ConfigMap**：存储非机密的配置数据 (配置文件、环境变量)。\n*   **Secret**：存储机密数据 (密码、Token、证书)，在 Etcd 中加密存储。\n\n通过将配置与镜像分离，保证了容器的可移植性。\n"
  },
  {
    "path": "13_kubernetes_concepts/13.5_practice.md",
    "content": "## 13.5 实战练习\n\n本章将通过一个具体的案例：部署一个 Nginx 网站，并为其配置 Service 和 Ingress，来串联前面学到的知识。\n\n### 13.5.1 目标\n\n1.  部署一个 Nginx Deployment。\n2.  创建一个 Service 暴露 Nginx。\n3.  (可选) 通过 Ingress 访问服务。\n\n### 13.5.2 步骤 1：创建 Deployment\n\n创建一个名为 `nginx-deployment.yaml` 的文件：\n\n```yaml\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: nginx-deployment\n  labels:\n    app: nginx\nspec:\n  replicas: 2\n  selector:\n    matchLabels:\n      app: nginx\n  template:\n    metadata:\n      labels:\n        app: nginx\n    spec:\n      containers:\n      - name: nginx\n        image: nginx:1.27\n        ports:\n        - containerPort: 80\n```\n\n应用配置：\n\n```bash\nkubectl apply -f nginx-deployment.yaml\n```\n\n### 13.5.3 步骤 2：创建 Service\n\n创建一个名为 `nginx-service.yaml` 的文件：\n\n```yaml\napiVersion: v1\nkind: Service\nmetadata:\n  name: nginx-service\nspec:\n  selector:\n    app: nginx\n  ports:\n    - protocol: TCP\n      port: 80\n      targetPort: 80\n  type: NodePort # 使用 NodePort 方便本地测试\n```\n\n应用配置：\n\n```bash\nkubectl apply -f nginx-service.yaml\n```\n\n查看分配的端口：\n\n```bash\nkubectl get svc nginx-service\n```\n\n如果输出端口是 `80:30080/TCP`，你可以通过 `http://<NodeIP>:30080` 访问 Nginx。\n\n### 13.5.4 步骤 3：模拟滚动更新\n\n修改 `nginx-deployment.yaml`，将镜像版本改为 `nginx:1.27-alpine`。\n\n```bash\nkubectl apply -f nginx-deployment.yaml\n```\n\n观察更新过程：\n\n```bash\nkubectl rollout status deployment/nginx-deployment\n```\n\n### 13.5.5 步骤 4：清理资源\n\n练习结束后，记得清理资源：\n\n```bash\nkubectl delete -f nginx-service.yaml\nkubectl delete -f nginx-deployment.yaml\n```\n"
  },
  {
    "path": "13_kubernetes_concepts/README.md",
    "content": "# 第十三章 容器编排基础\n\n`Kubernetes` 是 Google 发起的开源容器编排系统，它支持多种云平台与私有数据中心。\n\n`Kubernetes` 负责对容器工作负载进行调度与编排，其目的是让用户通过集群声明式地管理应用，而无需手动干预每个容器的生命周期细节。\n\nKubernetes 的最小调度单位是 `Pod`。一个 `Pod` 由一组紧密协作的容器构成，它们共享网络命名空间、IP 以及部分存储资源，也可以根据需要对 Pod 进行端口映射。\n\n本章将分为 5 节介绍 `Kubernetes`：\n\n* [简介](13.1_intro.md)\n* [基本概念](13.2_concepts.md)\n* [架构设计](13.3_design.md)\n* [高级特性](13.4_advanced.md)\n* [实战练习](13.5_practice.md)\n"
  },
  {
    "path": "13_kubernetes_concepts/summary.md",
    "content": "## 本章小结\n\nKubernetes 是当前最主流的容器编排平台，其声明式管理模型和丰富的 API 为大规模容器化应用提供了坚实的基础。\n\n| 概念 | 要点 |\n|------|------|\n| **Pod** | 最小调度单位，包含一组共享网络和存储的容器 |\n| **Deployment** | 管理 Pod 副本集，支持滚动更新和回滚 |\n| **Service** | 为 Pod 提供稳定的网络访问入口和负载均衡 |\n| **Namespace** | 资源隔离和多租户支持 |\n| **ConfigMap/Secret** | 配置与敏感信息的管理 |\n| **Master 节点** | 运行 API Server、Scheduler、Controller Manager |\n| **Worker 节点** | 运行 kubelet、kube-proxy 和容器运行时 |\n\n### 13.6.1 延伸阅读\n\n- [部署 Kubernetes](../14_kubernetes_setup/README.md)：搭建 Kubernetes 集群\n- [Etcd](../15_etcd/README.md)：Kubernetes 使用的分布式存储\n- [底层实现](../12_implementation/README.md)：容器技术原理\n---\n\n> 📝 **发现错误或有改进建议？** 欢迎提交 [Issue](https://github.com/yeasy/docker_practice/issues) 或 [PR](https://github.com/yeasy/docker_practice/pulls)。\n"
  },
  {
    "path": "14_kubernetes_setup/14.1_kubeadm.md",
    "content": "## 14.1 使用 kubeadm 部署 Kubernetes\n\n`kubeadm` 提供了 `kubeadm init` 以及 `kubeadm join` 这两个命令，作为快速创建 `Kubernetes` 集群的最佳实践。\n\n> **版本说明**：Kubernetes 版本更新较快 (约每 4 个月一个新版本)，本文档基于 Kubernetes 1.35 编写。请访问 [Kubernetes 官方发布页](https://kubernetes.io/releases/)获取最新版本信息。\n\n### 14.1.1 安装 containerd\n\n参考[安装 Docker](../03_install/README.md) 一节添加 apt/yum 源，之后执行如下命令。\n\n```bash\n## debian 系\n\n$ sudo apt install containerd.io\n\n## rhel 系\n\n$ sudo yum install containerd.io\n```\n\n### 14.1.2 配置 containerd\n\n新建 `/etc/systemd/system/cri-containerd.service` 文件\n\n```bash\n[Unit]\nDescription=containerd container runtime for kubernetes\nDocumentation=https://containerd.io\nAfter=network.target local-fs.target\n\n[Service]\nExecStartPre=-/sbin/modprobe overlay\nExecStart=/usr/bin/containerd --config /etc/cri-containerd/config.toml\n\nType=notify\nDelegate=yes\nKillMode=process\nRestart=always\nRestartSec=5\n## Having non-zero Limit*s causes performance problems due to accounting overhead\n\n## in the kernel. We recommend using cgroups to do container-local accounting.\n\nLimitNPROC=infinity\nLimitCORE=infinity\nLimitNOFILE=infinity\n## Comment TasksMax if your systemd version does not supports it.\n\n## Only systemd 226 and above support this version.\n\nTasksMax=infinity\nOOMScoreAdjust=-999\n\n[Install]\nWantedBy=multi-user.target\n```\n\n新建 `/etc/cri-containerd/config.toml` containerd 配置文件\n\n```toml\nversion = 2\n## persistent data location\n\nroot = \"/var/lib/cri-containerd\"\n## runtime state information\n\nstate = \"/run/cri-containerd\"\nplugin_dir = \"\"\ndisabled_plugins = []\nrequired_plugins = []\n## set containerd's OOM score\n\noom_score = 0\n\n[grpc]\n  address = \"/run/cri-containerd/cri-containerd.sock\"\n  tcp_address = \"\"\n  tcp_tls_cert = \"\"\n  tcp_tls_key = \"\"\n  # socket uid\n\n  uid = 0\n  # socket gid\n\n  gid = 0\n  max_recv_message_size = 16777216\n  max_send_message_size = 16777216\n\n[debug]\n  address = \"\"\n  format = \"json\"\n  uid = 0\n  gid = 0\n  level = \"\"\n\n[metrics]\n  address = \"127.0.0.1:1338\"\n  grpc_histogram = false\n\n[cgroup]\n  path = \"\"\n\n[timeouts]\n  \"io.containerd.timeout.shim.cleanup\" = \"5s\"\n  \"io.containerd.timeout.shim.load\" = \"5s\"\n  \"io.containerd.timeout.shim.shutdown\" = \"3s\"\n  \"io.containerd.timeout.task.state\" = \"2s\"\n\n[plugins]\n  [plugins.\"io.containerd.gc.v1.scheduler\"]\n    pause_threshold = 0.02\n    deletion_threshold = 0\n    mutation_threshold = 100\n    schedule_delay = \"0s\"\n    startup_delay = \"100ms\"\n  [plugins.\"io.containerd.grpc.v1.cri\"]\n    disable_tcp_service = true\n    stream_server_address = \"127.0.0.1\"\n    stream_server_port = \"0\"\n    stream_idle_timeout = \"4h0m0s\"\n    enable_selinux = false\n    selinux_category_range = 1024\n    sandbox_image = \"registry.cn-hangzhou.aliyuncs.com/google_containers/pause:3.10\"\n    stats_collect_period = 10\n    # systemd_cgroup = false\n\n    enable_tls_streaming = false\n    max_container_log_line_size = 16384\n    disable_cgroup = false\n    disable_apparmor = false\n    restrict_oom_score_adj = false\n    max_concurrent_downloads = 3\n    disable_proc_mount = false\n    unset_seccomp_profile = \"\"\n    tolerate_missing_hugetlb_controller = true\n    disable_hugetlb_controller = true\n    ignore_image_defined_volumes = false\n    [plugins.\"io.containerd.grpc.v1.cri\".containerd]\n      snapshotter = \"overlayfs\"\n      default_runtime_name = \"runc\"\n      no_pivot = false\n      disable_snapshot_annotations = false\n      discard_unpacked_layers = false\n      [plugins.\"io.containerd.grpc.v1.cri\".containerd.runtimes]\n        [plugins.\"io.containerd.grpc.v1.cri\".containerd.runtimes.runc]\n          runtime_type = \"io.containerd.runc.v2\"\n          pod_annotations = []\n          container_annotations = []\n          privileged_without_host_devices = false\n          base_runtime_spec = \"\"\n          [plugins.\"io.containerd.grpc.v1.cri\".containerd.runtimes.runc.options]\n            # SystemdCgroup enables systemd cgroups.\n\n            SystemdCgroup = true\n            # BinaryName is the binary name of the runc binary.\n\n            # BinaryName = \"runc\"\n\n            # BinaryName = \"crun\"\n\n            # NoPivotRoot disables pivot root when creating a container.\n\n            # NoPivotRoot = false\n\n            # NoNewKeyring disables new keyring for the container.\n\n            # NoNewKeyring = false\n\n            # ShimCgroup places the shim in a cgroup.\n\n            # ShimCgroup = \"\"\n\n            # IoUid sets the I/O's pipes uid.\n\n            # IoUid = 0\n\n            # IoGid sets the I/O's pipes gid.\n\n            # IoGid = 0\n\n            # Root is the runc root directory.\n\n            Root = \"\"\n\n            # CriuPath is the criu binary path.\n\n            # CriuPath = \"\"\n\n            # CriuImagePath is the criu image path\n\n            # CriuImagePath = \"\"\n\n            # CriuWorkPath is the criu work path.\n\n            # CriuWorkPath = \"\"\n\n    [plugins.\"io.containerd.grpc.v1.cri\".cni]\n      bin_dir = \"/opt/cni/bin\"\n      conf_dir = \"/etc/cni/net.d\"\n      max_conf_num = 1\n      conf_template = \"\"\n    [plugins.\"io.containerd.grpc.v1.cri\".registry]\n      config_path = \"/etc/cri-containerd/certs.d\"\n      [plugins.\"io.containerd.grpc.v1.cri\".registry.headers]\n        # Foo = [\"bar\"]\n\n    [plugins.\"io.containerd.grpc.v1.cri\".image_decryption]\n      key_model = \"\"\n    [plugins.\"io.containerd.grpc.v1.cri\".x509_key_pair_streaming]\n      tls_cert_file = \"\"\n      tls_key_file = \"\"\n  [plugins.\"io.containerd.internal.v1.opt\"]\n    path = \"/opt/cri-containerd\"\n  [plugins.\"io.containerd.internal.v1.restart\"]\n    interval = \"10s\"\n  [plugins.\"io.containerd.metadata.v1.bolt\"]\n    content_sharing_policy = \"shared\"\n  [plugins.\"io.containerd.monitor.v1.cgroups\"]\n    no_prometheus = false\n  [plugins.\"io.containerd.runtime.v2.task\"]\n    platforms = [\"linux/amd64\"]\n  [plugins.\"io.containerd.service.v1.diff-service\"]\n    default = [\"walking\"]\n  [plugins.\"io.containerd.snapshotter.v1.devmapper\"]\n    root_path = \"\"\n    pool_name = \"\"\n    base_image_size = \"\"\n    async_remove = false\n```\n\n### 14.1.3 安装 **kubelet**、**kubeadm**、**kubectl**、**cri-tools**、**kubernetes-cni**\n\n需要在每台机器上安装以下的软件包：\n\n#### Ubuntu/Debian\n\n```bash\n$ K8S_MINOR=\"v1.35\"\n\n$ sudo apt-get update\n$ sudo apt-get install -y ca-certificates curl gpg\n\n$ sudo install -m 0755 -d /etc/apt/keyrings\n$ curl -fsSL \"https://pkgs.k8s.io/core:/stable:/${K8S_MINOR}/deb/Release.key\" | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg\n$ sudo chmod a+r /etc/apt/keyrings/kubernetes-apt-keyring.gpg\n\n$ echo \"deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/${K8S_MINOR}/deb/ /\" | sudo tee /etc/apt/sources.list.d/kubernetes.list > /dev/null\n\n$ sudo apt-get update\n$ sudo apt-get install -y kubelet kubeadm kubectl cri-tools kubernetes-cni\n\n$ sudo apt-mark hold kubelet kubeadm kubectl\n```\n\n#### CentOS/Fedora\n\n```bash\n$ K8S_MINOR=\"v1.35\"\n\n$ cat <<EOF | sudo tee /etc/yum.repos.d/kubernetes.repo\n[kubernetes]\nname=Kubernetes\nbaseurl=https://pkgs.k8s.io/core:/stable:/${K8S_MINOR}/rpm/\nenabled=1\ngpgcheck=1\nrepo_gpgcheck=1\ngpgkey=https://pkgs.k8s.io/core:/stable:/${K8S_MINOR}/rpm/repodata/repomd.xml.key\nEOF\n\n$ sudo yum install -y kubelet kubeadm kubectl cri-tools kubernetes-cni\n```\n\n### 14.1.4 修改内核的运行参数\n\n#### 加载内核模块\n\n```bash\n$ cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf\noverlay\nbr_netfilter\nEOF\n\n$ sudo modprobe overlay\n$ sudo modprobe br_netfilter\n```\n\n#### 禁用 swap：必须\n\nkubelet 默认要求禁用 swap，否则可能导致初始化失败或节点无法加入集群。\n\n```bash\n$ sudo swapoff -a\n\n## 如需永久禁用，可在 /etc/fstab 中注释 swap 对应行\n```\n\n```bash\n$ cat <<EOF | sudo tee /etc/sysctl.d/99-kubernetes-cri.conf\nnet.bridge.bridge-nf-call-iptables  = 1\nnet.ipv4.ip_forward                 = 1\nnet.bridge.bridge-nf-call-ip6tables = 1\nEOF\n\n## 应用配置\n\n$ sysctl --system\n```\n\n### 14.1.5 配置 kubelet\n\n为了让 kubelet 正确运行，我们需要对其进行一些必要的配置。\n\n#### 修改 `kubelet.service`\n\n`/etc/systemd/system/kubelet.service.d/10-proxy-ipvs.conf` 写入以下内容\n\n```bash\n## 启用 ipvs 相关内核模块\n\n[Service]\nExecStartPre=-/sbin/modprobe ip_vs\nExecStartPre=-/sbin/modprobe ip_vs_rr\nExecStartPre=-/sbin/modprobe ip_vs_wrr\nExecStartPre=-/sbin/modprobe ip_vs_sh\n```\n\n执行以下命令应用配置。\n\n```bash\n$ sudo systemctl daemon-reload\n```\n\n### 14.1.6 部署\n\n安装配置完成后，我们将分别在 Master 节点和 Worker 节点上进行部署操作。\n\n#### master\n\n```bash\n$ systemctl enable cri-containerd\n\n$ systemctl start cri-containerd\n\n$ sudo kubeadm init \\\n      --image-repository registry.cn-hangzhou.aliyuncs.com/google_containers \\\n      --pod-network-cidr 10.244.0.0/16 \\\n      --cri-socket /run/cri-containerd/cri-containerd.sock \\\n      --v 5 \\\n      --ignore-preflight-errors=all\n```\n\n* `--pod-network-cidr 10.244.0.0/16` 参数与后续 CNI 插件有关，这里以 `flannel` 为例，若后续部署其他类型的网络插件请更改此参数。\n\n> 执行可能出现错误，例如缺少依赖包，根据提示安装即可。\n\n执行成功会输出\n\n```bash\n...\n[addons] Applied essential addon: CoreDNS\nI1116 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\nI1116 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\n[addons] Applied essential addon: kube-proxy\n\nYour Kubernetes control-plane has initialized successfully!\n\nTo start using your cluster, you need to run the following as a regular user:\n\n  mkdir -p $HOME/.kube\n  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config\n  sudo chown $(id -u):$(id -g) $HOME/.kube/config\n\nYou should now deploy a pod network to the cluster.\nRun \"kubectl apply -f [podnetwork].yaml\" with one of the options listed at:\n  https://kubernetes.io/docs/concepts/cluster-administration/addons/\n\nThen you can join any number of worker nodes by running the following on each as root:\n\nkubeadm join 192.168.199.100:6443 --token cz81zt.orsy9gm9v649e5lf \\\n    --discovery-token-ca-cert-hash sha256:5edb316fd0d8ea2792cba15cdf1c899a366f147aa03cba52d4e5c5884ad836fe\n```\n\n#### node 工作节点\n\n在 **另一主机** 重复 **部署** 小节以前的步骤，安装配置好 kubelet。根据提示，加入到集群。\n\n```bash\n$ systemctl enable cri-containerd\n\n$ systemctl start cri-containerd\n\n$ kubeadm join 192.168.199.100:6443 \\\n    --token cz81zt.orsy9gm9v649e5lf \\\n    --discovery-token-ca-cert-hash sha256:5edb316fd0d8ea2792cba15cdf1c899a366f147aa03cba52d4e5c5884ad836fe \\\n    --cri-socket /run/cri-containerd/cri-containerd.sock\n```\n\n### 14.1.7 查看服务\n\n所有服务启动后，通过 `crictl` 查看本地实际运行的容器。这些服务大概分为三类：主节点服务、工作节点服务和其它服务。\n\n```bash\nCONTAINER_RUNTIME_ENDPOINT=/run/cri-containerd/cri-containerd.sock crictl ps -a\n```\n\n#### 主节点服务\n\n* `apiserver` 是整个系统的对外接口，提供 RESTful 方式供客户端和其它组件调用；\n\n* `scheduler` 负责对资源进行调度，分配某个 pod 到某个节点上；\n\n* `controller-manager` 负责管理控制器，包括 endpoint-controller (刷新服务和 pod 的关联信息) 和 replication-controller (维护某个 pod 的复制为配置的数值)。\n\n#### 工作节点服务\n\n* `proxy` 为 pod 上的服务提供访问的代理。\n\n#### 其它服务\n\n* Etcd 是所有状态的存储数据库；\n\n### 14.1.8 使用\n\n将 `/etc/kubernetes/admin.conf` 复制到 `~/.kube/config`\n\n执行 `$ kubectl get all -A` 查看启动的服务。\n\n由于未部署 CNI 插件，CoreDNS 未正常启动。如何使用 Kubernetes，请参考后续章节。\n\n### 14.1.9 部署 CNI\n\n这里以 `flannel` 为例进行介绍。\n\n#### flannel\n\n检查 podCIDR 设置\n\n```bash\n$ kubectl get node -o yaml | grep CIDR\n\n## 输出\n\n    podCIDR: 10.244.0.0/16\n    podCIDRs:\n```\n\n```bash\n$ kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/v0.28.1/Documentation/kube-flannel.yml\n```\n\n### 14.1.10 master 节点默认不能运行 pod\n\n如果用 `kubeadm` 部署一个单节点集群，默认情况下无法使用，请执行以下命令解除限制\n\n```bash\n$ kubectl taint nodes --all node-role.kubernetes.io/master-\n\n## 部分较新版本使用 control-plane taint\n\n## $ kubectl taint nodes --all node-role.kubernetes.io/control-plane-\n\n## 恢复默认值\n\n## $ kubectl taint nodes NODE_NAME node-role.kubernetes.io/master=true:NoSchedule\n\n...\n```\n\n### 14.1.11 参考文档\n\n* [官方文档](https://kubernetes.io/zh/docs/setup/production-environment/tools/kubeadm/install-kubeadm/)\n* [Container runtimes](https://kubernetes.io/docs/setup/production-environment/container-runtimes/#containerd)\n"
  },
  {
    "path": "14_kubernetes_setup/14.2_kubeadm-docker.md",
    "content": "## 14.2 使用 kubeadm 部署 Kubernetes：使用 Docker\n\n`kubeadm` 提供了 `kubeadm init` 以及 `kubeadm join` 这两个命令，作为快速创建 `Kubernetes` 集群的最佳实践。\n\n> ⚠️ **强烈提示：Docker 与 Kubernetes 环境的时代分界**\n>\n> 自 Kubernetes v1.24 起，内置的 `dockershim` 组件已被正式移除。这意味着 **Kubernetes 不再将 Docker Engine 作为默认内置的容器运行时**。虽然 Docker 仍然是你本地构建、管理镜像的绝佳工具，但它已不再是 kubelet 的默认运行时选项。\n> \n> 因此，**强烈推荐** 读者直接参考同目录下的《[使用 kubeadm 部署 Kubernetes (CRI 使用 containerd)](14.1_kubeadm.md)》作为主要的部署路线。\n>\n> 本文档保留，主要用于历史环境维护或特殊需求场景：如果你必须在较新的 Kubernetes 集群中继续使用 Docker Engine 作为底层运行时，你必须理解 CRI 层机制，额外部署并配置第三方兼容层 `cri-dockerd`，同时在部署时手动补充 `--cri-socket` 等参数约束。\n\n### 14.2.1 安装 Docker\n\n参考[安装 Docker](../03_install/README.md) 一节安装 Docker。\n\n### 14.2.2 安装并配置 cri-dockerd\n\n由于 Kubernetes v1.24 移除了内置的 dockershim，需要额外安装 **cri-dockerd** 作为 Docker 与 kubelet 之间的 CRI（Container Runtime Interface）适配层。\n\n#### Ubuntu/Debian\n\n```bash\n## 安装 cri-dockerd\n\n$ cd /tmp\n$ wget https://github.com/Mirantis/cri-dockerd/releases/download/v0.3.14/cri-dockerd-0.3.14.amd64.tgz\n$ tar xzvf cri-dockerd-0.3.14.amd64.tgz\n$ sudo mv cri-dockerd/cri-dockerd /usr/local/bin/\n\n## 下载并安装 systemd service 文件\n\n$ wget https://raw.githubusercontent.com/Mirantis/cri-dockerd/master/packaging/systemd/cri-docker.service\n$ wget https://raw.githubusercontent.com/Mirantis/cri-dockerd/master/packaging/systemd/cri-docker.socket\n$ sudo mv cri-docker.service cri-docker.socket /etc/systemd/system/\n\n## 启动 cri-dockerd\n\n$ sudo systemctl daemon-reload\n$ sudo systemctl enable cri-docker\n$ sudo systemctl start cri-docker\n\n## 验证安装\n\n$ sudo /usr/local/bin/cri-dockerd --version\n```\n\n#### CentOS/Fedora\n\n```bash\n## 安装 cri-dockerd\n\n$ cd /tmp\n$ wget https://github.com/Mirantis/cri-dockerd/releases/download/v0.3.14/cri-dockerd-0.3.14.amd64.tgz\n$ tar xzvf cri-dockerd-0.3.14.amd64.tgz\n$ sudo mv cri-dockerd/cri-dockerd /usr/local/bin/\n\n## 下载并安装 systemd service 文件\n\n$ wget https://raw.githubusercontent.com/Mirantis/cri-dockerd/master/packaging/systemd/cri-docker.service\n$ wget https://raw.githubusercontent.com/Mirantis/cri-dockerd/master/packaging/systemd/cri-docker.socket\n$ sudo mv cri-docker.service cri-docker.socket /etc/systemd/system/\n\n## 启动 cri-dockerd\n\n$ sudo systemctl daemon-reload\n$ sudo systemctl enable cri-docker\n$ sudo systemctl start cri-docker\n```\n\n### 14.2.3 安装 **kubelet**、**kubeadm**、**kubectl**\n\n需要在每台机器上安装以下的软件包：\n\n#### Ubuntu/Debian\n\n```bash\n$ K8S_MINOR=\"v1.35\"\n\n$ sudo apt-get update\n$ sudo apt-get install -y ca-certificates curl gpg\n\n$ sudo install -m 0755 -d /etc/apt/keyrings\n$ curl -fsSL \"https://pkgs.k8s.io/core:/stable:/${K8S_MINOR}/deb/Release.key\" | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg\n$ sudo chmod a+r /etc/apt/keyrings/kubernetes-apt-keyring.gpg\n\n$ echo \"deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/${K8S_MINOR}/deb/ /\" | sudo tee /etc/apt/sources.list.d/kubernetes.list > /dev/null\n\n$ sudo apt-get update\n$ sudo apt-get install -y kubelet kubeadm kubectl\n\n$ sudo apt-mark hold kubelet kubeadm kubectl\n```\n\n#### CentOS/Fedora\n\n```bash\n$ K8S_MINOR=\"v1.35\"\n\n$ cat <<EOF | sudo tee /etc/yum.repos.d/kubernetes.repo\n[kubernetes]\nname=Kubernetes\nbaseurl=https://pkgs.k8s.io/core:/stable:/${K8S_MINOR}/rpm/\nenabled=1\ngpgcheck=1\nrepo_gpgcheck=1\ngpgkey=https://pkgs.k8s.io/core:/stable:/${K8S_MINOR}/rpm/repodata/repomd.xml.key\nEOF\n\n$ sudo yum install -y kubelet kubeadm kubectl\n```\n\n### 14.2.4 修改内核的运行参数\n\n#### 加载内核模块\n\n```bash\n$ cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf\noverlay\nbr_netfilter\nEOF\n\n$ sudo modprobe overlay\n$ sudo modprobe br_netfilter\n```\n\n#### 禁用 swap：必须\n\nkubelet 默认要求禁用 swap，否则可能导致初始化失败或节点无法加入集群。\n\n```bash\n$ sudo swapoff -a\n\n## 如需永久禁用，可在 /etc/fstab 中注释 swap 对应行\n```\n\n```bash\n$ cat <<EOF | sudo tee /etc/sysctl.d/99-kubernetes-cri.conf\nnet.bridge.bridge-nf-call-iptables  = 1\nnet.ipv4.ip_forward                 = 1\nnet.bridge.bridge-nf-call-ip6tables = 1\nEOF\n\n## 应用配置\n\n$ sysctl --system\n```\n\n### 14.2.5 配置 kubelet\n\n为了让 kubelet 正确运行，我们需要对其进行一些必要的配置。\n\n#### 修改 `kubelet.service`\n\n`/etc/systemd/system/kubelet.service.d/10-proxy-ipvs.conf` 写入以下内容\n\n```bash\n## 启用 ipvs 相关内核模块\n\n[Service]\nExecStartPre=-/sbin/modprobe ip_vs\nExecStartPre=-/sbin/modprobe ip_vs_rr\nExecStartPre=-/sbin/modprobe ip_vs_wrr\nExecStartPre=-/sbin/modprobe ip_vs_sh\n```\n\n执行以下命令应用配置。\n\n```bash\n$ sudo systemctl daemon-reload\n```\n\n### 14.2.6 部署\n\n安装配置完成后，我们将分别在 Master 节点和 Worker 节点上进行部署操作。\n\n#### master\n\n```bash\n$ sudo kubeadm init --image-repository registry.cn-hangzhou.aliyuncs.com/google_containers \\\n      --pod-network-cidr 10.244.0.0/16 \\\n      --cri-socket unix:///var/run/cri-dockerd.sock \\\n      --v 5 \\\n      --ignore-preflight-errors=all\n```\n\n* `--cri-socket unix:///var/run/cri-dockerd.sock` 参数指定使用 cri-dockerd 作为容器运行时接口。\n* `--pod-network-cidr 10.244.0.0/16` 参数与后续 CNI 插件有关，这里以 `flannel` 为例，若后续部署其他类型的网络插件请更改此参数。\n\n> 执行可能出现错误，例如缺少依赖包，根据提示安装即可。\n\n执行成功会输出\n\n```bash\n...\n[addons] Applied essential addon: CoreDNS\nI1116 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\nI1116 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\n[addons] Applied essential addon: kube-proxy\n\nYour Kubernetes control-plane has initialized successfully!\n\nTo start using your cluster, you need to run the following as a regular user:\n\n  mkdir -p $HOME/.kube\n  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config\n  sudo chown $(id -u):$(id -g) $HOME/.kube/config\n\nYou should now deploy a pod network to the cluster.\nRun \"kubectl apply -f [podnetwork].yaml\" with one of the options listed at:\n  https://kubernetes.io/docs/concepts/cluster-administration/addons/\n\nThen you can join any number of worker nodes by running the following on each as root:\n\nkubeadm join 192.168.199.100:6443 --token cz81zt.orsy9gm9v649e5lf \\\n    --discovery-token-ca-cert-hash sha256:5edb316fd0d8ea2792cba15cdf1c899a366f147aa03cba52d4e5c5884ad836fe\n```\n\n#### node 工作节点\n\n在 **另一主机** 重复 **部署** 小节以前的步骤，安装配置好 kubelet。根据提示，加入到集群。\n\n```bash\n$ kubeadm join 192.168.199.100:6443 --token cz81zt.orsy9gm9v649e5lf \\\n    --discovery-token-ca-cert-hash sha256:5edb316fd0d8ea2792cba15cdf1c899a366f147aa03cba52d4e5c5884ad836fe\n```\n\n### 14.2.7 查看服务\n\n所有服务启动后，查看本地实际运行的 Docker 容器。这些服务大概分为三类：主节点服务、工作节点服务和其它服务。\n\n#### 主节点服务\n\n* `apiserver` 是整个系统的对外接口，提供 RESTful 方式供客户端和其它组件调用；\n\n* `scheduler` 负责对资源进行调度，分配某个 pod 到某个节点上；\n\n* `controller-manager` 负责管理控制器，包括 endpoint-controller (刷新服务和 pod 的关联信息) 和 replication-controller (维护某个 pod 的复制为配置的数值)。\n\n#### 工作节点服务\n\n* `proxy` 为 pod 上的服务提供访问的代理。\n\n#### 其它服务\n\n* Etcd 是所有状态的存储数据库；\n\n### 14.2.8 使用\n\n将 `/etc/kubernetes/admin.conf` 复制到 `~/.kube/config`\n\n执行 `$ kubectl get all -A` 查看启动的服务。\n\n由于未部署 CNI 插件，CoreDNS 未正常启动。如何使用 Kubernetes，请参考后续章节。\n\n### 14.2.9 部署 CNI\n\n这里以 `flannel` 为例进行介绍。\n\n#### flannel\n\n检查 podCIDR 设置\n\n```bash\n$ kubectl get node -o yaml | grep CIDR\n\n## 输出\n\n    podCIDR: 10.244.0.0/16\n    podCIDRs:\n```\n\n```bash\n$ kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/v0.28.1/Documentation/kube-flannel.yml\n```\n\n### 14.2.10 master 节点默认不能运行 pod\n\n如果用 `kubeadm` 部署一个单节点集群，默认情况下无法使用，请执行以下命令解除限制\n\n```bash\n$ kubectl taint nodes --all node-role.kubernetes.io/master-\n\n## 部分较新版本使用 control-plane taint\n\n## $ kubectl taint nodes --all node-role.kubernetes.io/control-plane-\n\n## 恢复默认值\n\n## $ kubectl taint nodes NODE_NAME node-role.kubernetes.io/master=true:NoSchedule\n\n...\n```\n\n### 14.2.11 参考文档\n\n* [官方文档](https://kubernetes.io/zh/docs/setup/production-environment/tools/kubeadm/install-kubeadm/)\n"
  },
  {
    "path": "14_kubernetes_setup/14.3_docker-desktop.md",
    "content": "## 14.3 在 Docker Desktop 使用\n\n使用 Docker Desktop 可以很方便的启用 Kubernetes。\n\n### 14.3.1 启用 Kubernetes\n\n在 Docker Desktop 设置页面，点击 `Kubernetes`，选择 `Enable Kubernetes`，稍等片刻，看到左下方 `Kubernetes` 变为 `running`，Kubernetes 启动成功。\n\n![图](https://github.com/docker/docs/raw/main/assets/images/desktop/settings-kubernetes.png)\n\n> 注意：Kubernetes 的镜像存储在 `registry.k8s.io`，如果国内网络无法直接访问，可以在 Docker Desktop 配置中的 `Docker Engine` 处配置镜像加速器，或者利用国内云服务商的镜像仓库手动拉取镜像并 retag。\n\n### 14.3.2 测试\n\n```bash\n$ kubectl version\n```\n\n如果正常输出信息，则证明 Kubernetes 成功启动。\n"
  },
  {
    "path": "14_kubernetes_setup/14.4_kind.md",
    "content": "## 14.4 Kind - Kubernetes IN Docker\n\n[Kind](https://kind.sigs.k8s.io/) (Kubernetes in Docker) 是一个使用 Docker 容器作为节点运行本地 Kubernetes 集群的工具。主要用于测试 Kubernetes 本身，也非常适合本地开发和 CI 环境。\n\n### 14.4.1 为什么选择 Kind\n\nKind 相比其他本地集群方案 (如 Minikube) 有以下显著优势：\n\n*   **轻量便捷**：只要有 Docker 环境即可，无需额外虚拟机。\n*   **多集群支持**：可以轻松在本地启动多个集群。\n*   **多版本支持**：支持指定 Kubernetes 版本进行测试。\n*   **HA 支持**：支持模拟高可用集群 (多 Control Plane)。\n\n### 14.4.2 安装 Kind\n\nKind 是一个二进制文件，并在 PATH 中即可使用。以下是不同系统的安装方法。\n\n#### macOS\n\n```bash\nbrew install kind\n```\n\n#### Linux / Windows\n\n可以下载二进制文件：\n\n```bash\n## Linux AMD64\n\ncurl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.20.0/kind-linux-amd64\nchmod +x ./kind\nsudo mv ./kind /usr/local/bin/kind\n```\n\n### 14.4.3 创建集群\n\n最简单的创建方式：\n\n```bash\nkind create cluster\n```\n\n指定集群名称：\n\n```bash\nkind create cluster --name my-cluster\n```\n\n### 14.4.4 与集群交互\n\nKind 会自动将 kubeconfig 合并到 `~/.kube/config`。\n\n```bash\nkubectl cluster-info --context kind-kind\nkubectl get nodes\n```\n\n### 14.4.5 高级用法：配置集群\n\n创建一个 `kind-config.yaml` 来定制集群，例如映射端口到宿主机：\n\n```yaml\nkind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nnodes:\n- role: control-plane\n  extraPortMappings:\n  - containerPort: 80\n    hostPort: 8080\n    protocol: TCP\n- role: worker\n- role: worker\n```\n\n应用配置：\n\n```bash\nkind create cluster --config kind-config.yaml\n```\n\n### 14.4.6 删除集群\n\n```bash\nkind delete cluster\n```\n"
  },
  {
    "path": "14_kubernetes_setup/14.5_k3s.md",
    "content": "## 14.5 K3s - 轻量级 Kubernetes\n\n[K3s](https://k3s.io/) 是一个轻量级的 Kubernetes 发行版，由 Rancher Labs 开发。它专为边缘计算、物联网、CI、ARM 等资源受限的环境设计。K3s 被打包为单个二进制文件，只有不到 100MB，但通过了 CNCF 的一致性测试。\n\n### 14.5.1 核心特性\n\n*   **轻量级**：移除过时的、非必须的 Kubernetes 功能 (如传统的云提供商插件)，使用 SQLite 作为默认数据存储 (也支持 Etcd/MySQL/Postgres)。\n*   **单一二进制**：所有组件 (API Server，Controller Manager，Scheduler，Kubelet，Kube-proxy) 打包在一个进程中运行。\n*   **开箱即用**：内置 Helm Controller、Traefik Ingress controller、ServiceLB、Local-Path-Provisioner。\n*   **安全**：默认启用安全配置，基于 TLS 通信。\n\n### 14.5.2 安装\n\nK3s 的安装非常简单，官方提供了便捷的安装脚本。\n\n#### 脚本安装\n\nK3s 提供了极为便捷的安装脚本：\n\n```bash\ncurl -sfL https://get.k3s.io | sh -\n```\n\n安装完成后，K3s 会自动启动并配置好 `systemd` 服务。\n\n#### 查看状态\n\n```bash\nsudo k3s kubectl get nodes\n```\n\n输出类似：\n```bash\nNAME          STATUS   ROLES                  AGE   VERSION\nk3s-master    Ready    control-plane,master   1m    v1.35.1+k3s1\n```\n\n### 14.5.3 快速使用\n\nK3s 内置了 `kubectl` 命令 (通过 `k3s kubectl` 调用)，为了方便，通常会建立别名或配置 `KUBECONFIG`。\n\n```bash\n## 读取 K3s 的配置文件\n\nexport KUBECONFIG=/etc/rancher/k3s/k3s.yaml\n\n## 现在可以直接使用 kubectl\n\nkubectl get pods -A\n```\n\n### 14.5.4 清理卸载\n\n```bash\n/usr/local/bin/k3s-uninstall.sh\n```\n"
  },
  {
    "path": "14_kubernetes_setup/14.6_systemd.md",
    "content": "## 14.6 一步步部署 Kubernetes 集群\n\n### 概述\n\n部署 Kubernetes 集群涉及多个组件的安装和配置，包括 Master 节点和 Worker 节点。本章介绍如何使用 systemd 管理这些服务的生命周期。\n\n### Kubernetes 主要组件\n\n#### Master 节点组件\n\n- **kube-apiserver**：API 服务器，Kubernetes 集群的中心\n- **kube-controller-manager**：控制器管理器\n- **kube-scheduler**：调度器，负责 Pod 调度\n- **etcd**：分布式键值存储，存储集群数据\n\n#### Worker 节点组件\n\n- **kubelet**：节点代理，管理容器生命周期\n- **kube-proxy**：网络代理，处理服务网络\n- **Container Runtime**：容器运行时（Docker、containerd 等）\n\n### 使用 systemd 管理 Kubernetes 服务\n\n#### 服务单元文件\n\n为了让 systemd 管理 Kubernetes 服务，需要创建相应的 `.service` 文件，例如：\n\n```text\n/etc/systemd/system/kubelet.service\n/etc/systemd/system/kube-proxy.service\n/etc/systemd/system/kube-apiserver.service\n```\n\n#### 常用命令\n\n```bash\n# 启动服务\nsudo systemctl start kubelet\n\n# 停止服务\nsudo systemctl stop kubelet\n\n# 重启服务\nsudo systemctl restart kubelet\n\n# 查看服务状态\nsudo systemctl status kubelet\n\n# 设置开机自启\nsudo systemctl enable kubelet\n```\n\n### 参考资源\n\n详细的部署步骤和配置说明，可以参考以下项目：\n\n- [opsnull/follow-me-install-kubernetes-cluster](https://github.com/opsnull/follow-me-install-kubernetes-cluster)：一个完整的 Kubernetes 集群部署指南项目\n\n该项目提供了详细的步骤说明，涵盖 Master 节点、Worker 节点的安装配置，以及如何使用 systemd 管理这些组件的生命周期。\n\n### 推荐学习路径\n\n1. 理解 Kubernetes 架构和各组件的作用\n2. 准备所需的系统环境（Linux 主机、网络配置等）\n3. 按步骤安装各个 Kubernetes 组件\n4. 配置 systemd 服务单元文件\n5. 验证集群健康状态\n"
  },
  {
    "path": "14_kubernetes_setup/14.7_dashboard.md",
    "content": "## 14.7 部署 Dashboard\n\n[Kubernetes Dashboard](https://github.com/kubernetes/dashboard) 是基于网页的 Kubernetes 用户界面。\n\n![图](https://d33wubrfki0l68.cloudfront.net/349824f68836152722dab89465835e604719caea/6e0b7/images/docs/ui-dashboard.png)\n\n### 14.7.1 部署\n\n执行以下命令即可部署 Dashboard：\n\n```bash\nkubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.7.0/aio/deploy/recommended.yaml\n```\n\n### 14.7.2 访问\n\n通过命令行代理访问，执行以下命令：\n\n```bash\n$ kubectl proxy\n```\n\n到 http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/ 即可访问。\n\n### 14.7.3 登录\n\n目前，Dashboard 仅支持使用 Bearer 令牌登录。下面教大家如何创建该令牌：\n\n```bash\n$ kubectl create sa dashboard-admin -n kube-system\n\n$ kubectl create clusterrolebinding dashboard-admin --clusterrole=cluster-admin --serviceaccount=kube-system:dashboard-admin\n\n$ ADMIN_SECRET=$(kubectl get secrets -n kube-system | grep dashboard-admin | awk '{print $1}')\n\n$ DASHBOARD_LOGIN_TOKEN=$(kubectl describe secret -n kube-system ${ADMIN_SECRET} | grep -E '^token' | awk '{print $2}')\n\necho ${DASHBOARD_LOGIN_TOKEN}\n```\n\n将结果粘贴到登录页面，即可登录。\n\n### 14.7.4 参考文档\n\n* [官方文档](https://kubernetes.io/zh/docs/tasks/access-application-cluster/web-ui-dashboard/)\n"
  },
  {
    "path": "14_kubernetes_setup/14.8_kubectl.md",
    "content": "## 14.8 Kubernetes 命令行 kubectl\n\n[kubectl](https://github.com/kubernetes/kubernetes) 是 Kubernetes 自带的客户端，可以用它来直接操作 Kubernetes。\n\n使用格式有两种：\n```bash\nkubectl [flags]\nkubectl [command]\n```\n\n### 14.8.1 get\n\n显示一个或多个资源\n\n### 14.8.2 describe\n\n显示资源详情\n\n### 14.8.3 create\n\n从文件或标准输入创建资源\n\n### 14.8.4 update\n\n从文件或标准输入更新资源\n\n### 14.8.5 delete\n\n通过文件名、标准输入、资源名或者 label selector 删除资源\n\n### 14.8.6 logs\n\n输出 pod 中一个容器的日志\n\n### 14.8.7 rollout\n\n对 Deployment 等资源执行滚动更新/回滚\n\n### 14.8.8 exec\n\n在容器内部执行命令\n\n### 14.8.9 port-forward\n\n将本地端口转发到 Pod\n\n### 14.8.10 proxy\n\n为 Kubernetes API server 启动代理服务器\n\n### 14.8.11 run\n\n在集群中使用指定镜像启动容器\n\n### 14.8.12 expose\n\n将 replication controller service 或 pod 暴露为新的 Kubernetes service\n\n### 14.8.13 label\n\n更新资源的 label\n\n### 14.8.14 config\n\n修改 Kubernetes 配置文件\n\n### 14.8.15 cluster-info\n\n显示集群信息\n\n### 14.8.16 api-versions\n\n以 “组/版本” 的格式输出服务端支持的 API 版本\n\n### 14.8.17 version\n\n输出服务端和客户端的版本信息\n\n### 14.8.18 help\n\n显示各个命令的帮助信息\n"
  },
  {
    "path": "14_kubernetes_setup/README.md",
    "content": "# 第十四章 部署 Kubernetes\n\n目前，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)等)。\n\n你可以使用以下几种方式部署 Kubernetes，接下来的小节会对各种方式进行详细介绍。\n\n* [使用 kubeadm 部署 (CRI 使用 containerd)](14.1_kubeadm.md)\n* [使用 kubeadm 部署 (使用 Docker)](14.2_kubeadm-docker.md)\n* [在 Docker Desktop 使用](14.3_docker-desktop.md)\n* [Kind - Kubernetes IN Docker](14.4_kind.md)\n* [K3s - 轻量级 Kubernetes](14.5_k3s.md)\n* [一步步部署 Kubernetes 集群](14.6_systemd.md)\n* [部署 Dashboard](14.7_dashboard.md)\n* [Kubernetes 命令行 kubectl](14.8_kubectl.md)\n"
  },
  {
    "path": "14_kubernetes_setup/summary.md",
    "content": "## 本章小结\n\n部署 Kubernetes 集群有多种方式，应根据使用场景选择合适的方案。\n\n| 部署方式 | 适用场景 | 特点 |\n|---------|---------|------|\n| **kubeadm** | 生产环境 | 官方推荐的集群部署工具 |\n| **Docker Desktop** | 本地开发 | 一键启用，开箱即用 |\n| **Kind** | CI/CD 测试 | Kubernetes IN Docker，快速创建集群 |\n| **K3s** | 边缘计算/IoT | 轻量级，资源占用少 |\n| **手动部署** | 学习原理 | 逐步配置每个组件，加深理解 |\n\n### 14.9.1 延伸阅读\n\n- [容器编排基础](../13_kubernetes_concepts/README.md)：Kubernetes 核心概念\n- [Dashboard](14.7_dashboard.md)：部署可视化管理界面\n- [kubectl](14.8_kubectl.md)：命令行工具使用指南\n---\n\n> 📝 **发现错误或有改进建议？** 欢迎提交 [Issue](https://github.com/yeasy/docker_practice/issues) 或 [PR](https://github.com/yeasy/docker_practice/pulls)。\n"
  },
  {
    "path": "15_etcd/15.1_intro.md",
    "content": "## 15.1 简介\n\n如图 15-1：所示，etcd 项目使用该标识。\n\n![etcd 标识](./_images/etcd_logo.png)\n\n图 15-1：etcd 项目标识\n\n`etcd` 是 `CoreOS` 团队于 2013 年 6 月发起的开源项目，它的目标是构建一个高可用的分布式键值 (`key-value`) 数据库，基于 `Go` 语言实现。我们知道，在分布式系统中，各种服务的配置信息的管理分享，服务的发现是一个很基本同时也是很重要的问题。`CoreOS` 项目就希望基于 `etcd` 来解决这一问题。\n\n`etcd` 目前在 [github.com/etcd-io/etcd](https://github.com/etcd-io/etcd) 进行维护。\n\n受到 [Apache ZooKeeper](https://zookeeper.apache.org/) 项目和 [doozer](https://github.com/ha/doozerd) 项目的启发，`etcd` 在设计的时候重点考虑了下面四个要素：\n\n* 简单：具有定义良好、面向用户的 `API` ([gRPC](https://github.com/grpc/grpc))\n* 安全：支持 `HTTPS` 方式的访问\n* 快速：支持并发 `10 k/s` 的写操作\n* 可靠：支持分布式结构，基于 `Raft` 的一致性算法\n\n_Apache ZooKeeper 是一套知名的分布式系统中进行同步和一致性管理的工具。_\n\n_doozer 是一个一致性分布式数据库。_\n\n[_Raft_](https://raft.github.io/) _是一套通过选举主节点来实现分布式系统一致性的算法，相比于大名鼎鼎的 Paxos 算法，它的过程更容易被人理解，由 Stanford 大学的 Diego Ongaro 和 John Ousterhout 提出。更多细节可以参考_ [_raftconsensus.github.io_](http://raftconsensus.github.io)_。_\n\n一般情况下，用户使用 `etcd` 可以在多个节点上启动多个实例，并添加它们为一个集群。同一个集群中的 `etcd` 实例将会保持彼此信息的一致性。\n"
  },
  {
    "path": "15_etcd/15.2_install.md",
    "content": "## 15.2 安装\n\n本节将介绍 etcd 的几种常见安装方式，包括二进制安装、Docker 镜像运行以及在 macOS 上的安装。\n\n`etcd` 基于 `Go` 语言实现，因此，用户可以从[项目主页](https://github.com/etcd-io/etcd)下载源代码自行编译，也可以下载编译好的二进制文件，甚至直接使用制作好的 `Docker` 镜像文件来体验。\n\n> 注意：本章节内容基于 etcd `3.4.x` 版本编写。etcd 3.4 的官方支持将于 **2026 年 5 月 15 日结束**，新部署建议使用 etcd `3.5` 或 `3.6` 版本。请访问 [etcd 官方发布页](https://github.com/etcd-io/etcd/releases) 获取最新版本。\n\n### 15.2.1 二进制文件方式下载\n\n编译好的二进制文件都在 [github.com/etcd-io/etcd/releases](https://github.com/etcd-io/etcd/releases/) 页面，用户可以选择需要的版本，或通过下载工具下载。\n\n例如，使用 `curl` 工具下载压缩包，并解压。\n\n```bash\n$ curl -L https://github.com/etcd-io/etcd/releases/download/v3.5.17/etcd-v3.5.17-linux-amd64.tar.gz -o etcd-v3.5.17-linux-amd64.tar.gz\n\n## 国内用户可选择就近的网络加速方式（以可用镜像站为准）\n\n$ tar xzvf etcd-v3.5.17-linux-amd64.tar.gz\n$ cd etcd-v3.5.17-linux-amd64\n```\n\n解压后，可以看到文件包括\n\n```bash\n$ ls\nDocumentation README-etcdctl.md README.md READMEv2-etcdctl.md etcd etcdctl\n```\n\n其中 `etcd` 是服务主文件，`etcdctl` 是提供给用户的命令客户端，其他文件是支持文档。\n\n下面将 `etcd` `etcdctl` 文件放到系统可执行目录 (例如 `/usr/local/bin/`)。\n\n```bash\n$ sudo cp etcd* /usr/local/bin/\n```\n\n默认 `2379` 端口处理客户端的请求，`2380` 端口用于集群各成员间的通信。启动 `etcd` 显示类似如下的信息：\n\n```bash\n$ etcd\n...\n2017-12-03 11:18:34.411579 I | embed: listening for peers on http://localhost:2380\n2017-12-03 11:18:34.411938 I | embed: listening for client requests on localhost:2379\n```\n\n此时，可以使用 `etcdctl` 命令进行测试，设置和获取键值 `testkey: \"hello world\"`，检查 `etcd` 服务是否启动成功：\n\n```bash\n$ ETCDCTL_API=3 etcdctl member list\n8e9e05c52164694d, started, default, http://localhost:2380, http://localhost:2379\n\n$ ETCDCTL_API=3 etcdctl put testkey \"hello world\"\nOK\n\n$ etcdctl get testkey\ntestkey\nhello world\n```\n\n说明 etcd 服务已经成功启动了。\n\n### 15.2.2 Docker 镜像方式运行\n\n镜像名称为 `quay.io/coreos/etcd`，可以通过下面的命令启动 `etcd` 服务监听到 `2379` 和 `2380` 端口。\n\n```bash\n$ docker run \\\n-p 2379:2379 \\\n-p 2380:2380 \\\n--mount type=bind,source=/tmp/etcd-data.tmp,destination=/etcd-data \\\n--name etcd-gcr-v3.5.17 \\\nquay.io/coreos/etcd:v3.5.17 \\\n/usr/local/bin/etcd \\\n--name s1 \\\n--data-dir /etcd-data \\\n--listen-client-urls http://0.0.0.0:2379 \\\n--advertise-client-urls http://0.0.0.0:2379 \\\n--listen-peer-urls http://0.0.0.0:2380 \\\n--initial-advertise-peer-urls http://0.0.0.0:2380 \\\n--initial-cluster s1=http://0.0.0.0:2380 \\\n--initial-cluster-token tkn \\\n--initial-cluster-state new \\\n--log-level info \\\n--logger zap \\\n--log-outputs stderr\n```\n\n打开新的终端按照上一步的方法测试 `etcd` 是否成功启动。\n\n### 15.2.3 macOS 中运行\n\n```bash\n$ brew install etcd\n\n$ etcd\n\n$ etcdctl member list\n```\n"
  },
  {
    "path": "15_etcd/15.3_cluster.md",
    "content": "## 15.3 集群\n\n下面我们使用 [Docker Compose](../11_compose/README.md) 模拟启动一个 3 节点的 `etcd` 集群。\n\n编辑 `compose.yaml` (或 `docker-compose.yml`) 文件\n\n```yaml\nservices:\n\n  node1:\n    image: quay.io/coreos/etcd:v3.4.0\n    volumes:\n      - node1-data:/etcd-data\n    expose:\n      - 2379\n      - 2380\n    networks:\n      cluster_net:\n        ipv4_address: 172.16.238.100\n    environment:\n      - ETCDCTL_API=3\n    command:\n      - /usr/local/bin/etcd\n      - --data-dir=/etcd-data\n      - --name\n      - node1\n      - --initial-advertise-peer-urls\n      - http://172.16.238.100:2380\n      - --listen-peer-urls\n      - http://0.0.0.0:2380\n      - --advertise-client-urls\n      - http://172.16.238.100:2379\n      - --listen-client-urls\n      - http://0.0.0.0:2379\n      - --initial-cluster\n      - node1=http://172.16.238.100:2380,node2=http://172.16.238.101:2380,node3=http://172.16.238.102:2380\n      - --initial-cluster-state\n      - new\n      - --initial-cluster-token\n      - docker-etcd\n\n  node2:\n    image: quay.io/coreos/etcd:v3.4.0\n    volumes:\n      - node2-data:/etcd-data\n    networks:\n      cluster_net:\n        ipv4_address: 172.16.238.101\n    environment:\n      - ETCDCTL_API=3\n    expose:\n      - 2379\n      - 2380\n    command:\n      - /usr/local/bin/etcd\n      - --data-dir=/etcd-data\n      - --name\n      - node2\n      - --initial-advertise-peer-urls\n      - http://172.16.238.101:2380\n      - --listen-peer-urls\n      - http://0.0.0.0:2380\n      - --advertise-client-urls\n      - http://172.16.238.101:2379\n      - --listen-client-urls\n      - http://0.0.0.0:2379\n      - --initial-cluster\n      - node1=http://172.16.238.100:2380,node2=http://172.16.238.101:2380,node3=http://172.16.238.102:2380\n      - --initial-cluster-state\n      - new\n      - --initial-cluster-token\n      - docker-etcd\n\n  node3:\n    image: quay.io/coreos/etcd:v3.4.0\n    volumes:\n      - node3-data:/etcd-data\n    networks:\n      cluster_net:\n        ipv4_address: 172.16.238.102\n    environment:\n      - ETCDCTL_API=3\n    expose:\n      - 2379\n      - 2380\n    command:\n      - /usr/local/bin/etcd\n      - --data-dir=/etcd-data\n      - --name\n      - node3\n      - --initial-advertise-peer-urls\n      - http://172.16.238.102:2380\n      - --listen-peer-urls\n      - http://0.0.0.0:2380\n      - --advertise-client-urls\n      - http://172.16.238.102:2379\n      - --listen-client-urls\n      - http://0.0.0.0:2379\n      - --initial-cluster\n      - node1=http://172.16.238.100:2380,node2=http://172.16.238.101:2380,node3=http://172.16.238.102:2380\n      - --initial-cluster-state\n      - new\n      - --initial-cluster-token\n      - docker-etcd\n\nvolumes:\n  node1-data:\n  node2-data:\n  node3-data:\n\nnetworks:\n  cluster_net:\n    driver: bridge\n    ipam:\n      driver: default\n      config:\n      -\n        subnet: 172.16.238.0/24\n```\n\n使用 `docker compose up` 启动集群之后使用 `docker exec` 命令登录到任一节点测试 `etcd` 集群。\n\n```bash\n/ # etcdctl member list\ndaf3fd52e3583ff, started, node3, http://172.16.238.102:2380, http://172.16.238.102:2379\n422a74f03b622fef, started, node1, http://172.16.238.100:2380, http://172.16.238.100:2379\ned635d2a2dbef43d, started, node2, http://172.16.238.101:2380, http://172.16.238.101:2379\n```\n"
  },
  {
    "path": "15_etcd/15.4_etcdctl.md",
    "content": "## 15.4 使用 etcdctl\n\n`etcdctl` 是一个命令行客户端，它能提供一些简洁的命令，供用户直接跟 `etcd` 服务打交道，而无需基于 `HTTP API` 方式。这在某些情况下将很方便，例如用户对服务进行测试或者手动修改数据库内容。我们也推荐在刚接触 `etcd` 时通过 `etcdctl` 命令来熟悉相关的操作，这些操作跟 `HTTP API` 实际上是对应的。\n\n`etcd` 项目二进制发行包中已经包含了 `etcdctl` 工具，没有的话，可以从 [github.com/etcd-io/etcd/releases](https://github.com/etcd-io/etcd/releases) 下载。\n\n`etcdctl` 支持如下的命令，大体上分为数据库操作和非数据库操作两类，后面将分别进行解释。\n\n```bash\nNAME:\n\tetcdctl - A simple command line client for etcd3.\n\nUSAGE:\n\tetcdctl\n\nVERSION:\n\t3.4.0\n\nAPI VERSION:\n\t3.4\n\n\nCOMMANDS:\n\tget\t\t\tGets the key or a range of keys\n\tput\t\t\tPuts the given key into the store\n\tdel\t\t\tRemoves the specified key or range of keys [key, range_end)\n\ttxn\t\t\tTxn processes all the requests in one transaction\n\tcompaction\t\tCompacts the event history in etcd\n\talarm disarm\t\tDisarms all alarms\n\talarm list\t\tLists all alarms\n\tdefrag\t\t\tDefragments the storage of the etcd members with given endpoints\n\tendpoint health\t\tChecks the healthiness of endpoints specified in `--endpoints` flag\n\tendpoint status\t\tPrints out the status of endpoints specified in `--endpoints` flag\n\twatch\t\t\tWatches events stream on keys or prefixes\n\tversion\t\t\tPrints the version of etcdctl\n\tlease grant\t\tCreates leases\n\tlease revoke\t\tRevokes leases\n\tlease timetolive\tGet lease information\n\tlease keep-alive\tKeeps leases alive (renew)\n\tmember add\t\tAdds a member into the cluster\n\tmember remove\t\tRemoves a member from the cluster\n\tmember update\t\tUpdates a member in the cluster\n\tmember list\t\tLists all members in the cluster\n\tsnapshot save\t\tStores an etcd node backend snapshot to a given file\n\tsnapshot restore\tRestores an etcd member snapshot to an etcd directory\n\tsnapshot status\t\tGets backend snapshot status of a given file\n\tmake-mirror\t\tMakes a mirror at the destination etcd cluster\n\tmigrate\t\t\tMigrates keys in a v2 store to a mvcc store\n\tlock\t\t\tAcquires a named lock\n\telect\t\t\tObserves and participates in leader election\n\tauth enable\t\tEnables authentication\n\tauth disable\t\tDisables authentication\n\tuser add\t\tAdds a new user\n\tuser delete\t\tDeletes a user\n\tuser get\t\tGets detailed information of a user\n\tuser list\t\tLists all users\n\tuser passwd\t\tChanges password of user\n\tuser grant-role\t\tGrants a role to a user\n\tuser revoke-role\tRevokes a role from a user\n\trole add\t\tAdds a new role\n\trole delete\t\tDeletes a role\n\trole get\t\tGets detailed information of a role\n\trole list\t\tLists all roles\n\trole grant-permission\tGrants a key to a role\n\trole revoke-permission\tRevokes a key from a role\n\tcheck perf\t\tCheck the performance of the etcd cluster\n\thelp\t\t\tHelp about any command\n\nOPTIONS:\n      --cacert=\"\"\t\t\t\tverify certificates of TLS-enabled secure servers using this CA bundle\n      --cert=\"\"\t\t\t\t\tidentify secure client using this TLS certificate file\n      --command-timeout=5s\t\t\ttimeout for short running command (excluding dial timeout)\n      --debug[=false]\t\t\t\tenable client-side debug logging\n      --dial-timeout=2s\t\t\t\tdial timeout for client connections\n      --endpoints=[127.0.0.1:2379]\t\tgRPC endpoints\n      --hex[=false]\t\t\t\tprint byte strings as hex encoded strings\n      --insecure-skip-tls-verify[=false]\tskip server certificate verification\n      --insecure-transport[=true]\t\tdisable transport security for client connections\n      --key=\"\"\t\t\t\t\tidentify secure client using this TLS key file\n      --user=\"\"\t\t\t\t\tusername[:password] for authentication (prompt if password is not supplied)\n  -w, --write-out=\"simple\"\t\t\tset the output format (fields, json, protobuf, simple, table)\n```\n\n### 15.4.1 数据库操作\n\n数据库操作围绕对键值和目录的 CRUD (符合 REST 风格的一套操作：Create) 完整生命周期的管理。\n\netcd 在键的组织上采用了层次化的空间结构 (类似于文件系统中目录的概念)，用户指定的键可以为单独的名字，如 `testkey`，此时实际上放在根目录 `/` 下面，也可以为指定目录结构，如 `cluster1/node2/testkey`，则将创建相应的目录结构。\n\n> 注：CRUD 即 Create，Read，Update，Delete，是符合 REST 风格的一套 API 操作。\n\n#### put\n\n```bash\n$ etcdctl put /testdir/testkey \"Hello world\"\nOK\n```\n\n#### get\n\n获取指定键的值。例如\n\n```bash\n$ etcdctl put testkey hello\nOK\n$ etcdctl get testkey\ntestkey\nhello\n```\n\n支持的选项为\n\n`--sort` 对结果进行排序\n\n`--consistent` 将请求发给主节点，保证获取内容的一致性\n\n#### del\n\n删除某个键值。例如\n\n```bash\n$ etcdctl del testkey\n1\n```\n\n### 15.4.2 非数据库操作\n\n#### watch\n\n监测一个键值的变化，一旦键值发生更新，就会输出最新的值。\n\n例如，用户更新 `testkey` 键值为 `Hello world`。\n\n```bash\n$ etcdctl watch testkey\nPUT\ntestkey\n2\n```\n\n#### member\n\n通过 `list`、`add`、`update`、`remove` 命令列出、添加、更新、删除 etcd 实例到 etcd 集群中。\n\n例如本地启动一个 `etcd` 服务实例后，可以用如下命令进行查看。\n\n```bash\n$ etcdctl member list\n422a74f03b622fef, started, node1, http://172.16.238.100:2380, http://172.16.238.100:23\n```\n"
  },
  {
    "path": "15_etcd/README.md",
    "content": "# 第十五章 Etcd 项目\n\n`etcd` 是 `CoreOS` 团队发起的一个管理配置信息和服务发现 (`Service Discovery`) 的项目，在这一章里面，我们将基于 `etcd 3.x` 版本介绍该项目的目标，安装和使用，以及实现的技术。\n\n## 本章内容\n\n* [简介](15.1_intro.md)\n* [安装](15.2_install.md)\n* [集群](15.3_cluster.md)\n* [使用 etcdctl](15.4_etcdctl.md)\n"
  },
  {
    "path": "15_etcd/demo/cluster/docker-compose.yml",
    "content": "version: \"3.6\"\nservices:\n\n  node1:\n    image: quay.io/coreos/etcd:v3.4.0\n    volumes:\n      - node1-data:/etcd-data\n    expose:\n      - 2379\n      - 2380\n    networks:\n      cluster_net:\n        ipv4_address: 172.16.238.100\n    environment:\n      - ETCDCTL_API=3\n    command:\n      - /usr/local/bin/etcd\n      - --data-dir=/etcd-data\n      - --name\n      - node1\n      - --initial-advertise-peer-urls\n      - http://172.16.238.100:2380\n      - --listen-peer-urls\n      - http://0.0.0.0:2380\n      - --advertise-client-urls\n      - http://172.16.238.100:2379\n      - --listen-client-urls\n      - http://0.0.0.0:2379\n      - --initial-cluster\n      - node1=http://172.16.238.100:2380,node2=http://172.16.238.101:2380,node3=http://172.16.238.102:2380\n      - --initial-cluster-state\n      - new\n      - --initial-cluster-token\n      - docker-etcd\n\n  node2:\n    image: quay.io/coreos/etcd:v3.4.0\n    volumes:\n      - node2-data:/etcd-data\n    networks:\n      cluster_net:\n        ipv4_address: 172.16.238.101\n    environment:\n      - ETCDCTL_API=3\n    expose:\n      - 2379\n      - 2380\n    command:\n      - /usr/local/bin/etcd\n      - --data-dir=/etcd-data\n      - --name\n      - node2\n      - --initial-advertise-peer-urls\n      - http://172.16.238.101:2380\n      - --listen-peer-urls\n      - http://0.0.0.0:2380\n      - --advertise-client-urls\n      - http://172.16.238.101:2379\n      - --listen-client-urls\n      - http://0.0.0.0:2379\n      - --initial-cluster\n      - node1=http://172.16.238.100:2380,node2=http://172.16.238.101:2380,node3=http://172.16.238.102:2380\n      - --initial-cluster-state\n      - new\n      - --initial-cluster-token\n      - docker-etcd\n\n  node3:\n    image: quay.io/coreos/etcd:v3.4.0\n    volumes:\n      - node3-data:/etcd-data\n    networks:\n      cluster_net:\n        ipv4_address: 172.16.238.102\n    environment:\n      - ETCDCTL_API=3\n    expose:\n      - 2379\n      - 2380\n    command:\n      - /usr/local/bin/etcd\n      - --data-dir=/etcd-data\n      - --name\n      - node3\n      - --initial-advertise-peer-urls\n      - http://172.16.238.102:2380\n      - --listen-peer-urls\n      - http://0.0.0.0:2380\n      - --advertise-client-urls\n      - http://172.16.238.102:2379\n      - --listen-client-urls\n      - http://0.0.0.0:2379\n      - --initial-cluster\n      - node1=http://172.16.238.100:2380,node2=http://172.16.238.101:2380,node3=http://172.16.238.102:2380\n      - --initial-cluster-state\n      - new\n      - --initial-cluster-token\n      - docker-etcd\n\nvolumes:\n  node1-data:\n  node2-data:\n  node3-data:\n\nnetworks:\n  cluster_net:\n    driver: bridge\n    ipam:\n      driver: default\n      config:\n      -\n        subnet: 172.16.238.0/24\n"
  },
  {
    "path": "15_etcd/summary.md",
    "content": "## 本章小结\n\netcd 是 Kubernetes 的核心存储组件，为分布式系统提供可靠的键值存储和服务发现能力。\n\n| 概念 | 要点 |\n|------|------|\n| **定位** | 分布式键值存储系统，用于配置管理和服务发现 |\n| **协议** | 基于 Raft 一致性算法，保证数据强一致 |\n| **API** | 提供 gRPC 和 HTTP API |\n| **集群** | 建议使用奇数节点 (3 或 5 个) 部署 |\n| **etcdctl** | 命令行管理工具，支持 put/get/del/watch 等操作 |\n| **安全** | 支持 TLS 加密通信和 RBAC 访问控制 |\n\n### 15.5.1 延伸阅读\n\n- [容器编排基础](../13_kubernetes_concepts/README.md)：Kubernetes 如何使用 etcd\n- [部署 Kubernetes](../14_kubernetes_setup/README.md)：在集群中部署 etcd\n---\n\n> 📝 **发现错误或有改进建议？** 欢迎提交 [Issue](https://github.com/yeasy/docker_practice/issues) 或 [PR](https://github.com/yeasy/docker_practice/pulls)。\n"
  },
  {
    "path": "16_cloud/16.1_intro.md",
    "content": "## 16.1 简介\n\n随着容器技术的普及，目前主流的云计算服务商都提供了成熟的容器服务。与容器相关的云计算服务主要分为以下几种类型：\n\n### 16.1.1 容器编排托管服务\n\n这是目前最主流的形式。云厂商托管 Kubernetes 的控制平面 (Master 节点)，用户只需管理工作节点 (Worker Node)。\n\n* **优势**：降低了 Kubernetes 集群的维护成本，高可用性由厂商保证。\n* **典型服务**：AWS EKS，Azure AKS，Google GKE，阿里云 ACK，腾讯云 TKE。\n\n### 16.1.2 容器实例服务\n\n这一类服务通常被称为 CaaS (Container as a Service)。用户无需管理底层服务器 (EC2/CVM)，只需提供镜像和配置即可运行容器。\n\n* **优势**：极致的弹性，按秒计费，零运维。\n* **典型服务**：AWS Fargate，Azure Container Instances，Google Cloud Run，阿里云 ECI。\n\n### 16.1.3 镜像仓库服务\n\n提供安全、可靠的私有 Docker 镜像存储服务，通常与云厂商的 CI/CD 流水线深度集成。\n\n* **典型服务**：AWS ECR，Azure ACR，Google GCR/GAR，阿里云 ACR。\n\n本章将介绍如何在几个主流云平台上使用 Docker 和 Kubernetes 服务。\n"
  },
  {
    "path": "16_cloud/16.2_tencentCloud.md",
    "content": "## 16.2 腾讯云\n\n如图 16-1：所示，腾讯云提供完整的云基础设施与容器能力。\n\n![腾讯云](./_images/qcloud-logo.jpg)\n\n图 16-1：腾讯云标识\n\n[腾讯云](https://cloud.tencent.com/act/cps/redirect?redirect=1040\\&cps_key=3a5255852d5db99dcd5da4c72f05df61\\&from=console)在架构方面经过多年积累，并且有着多年对海量互联网服务的经验。不管是社交、游戏还是其他领域，都有多年的成熟产品来提供产品服务。腾讯在云端完成重要部署，为开发者及企业提供云服务、云数据、云运营等整体一站式服务方案。\n\n具体包括[云服务器](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 空间、微云、微社区等云端链接社交体系。这些正是腾讯云可以提供给这个行业的差异化优势，造就了可支持各种互联网使用场景的高品质的腾讯云技术平台。\n\n[腾讯云容器服务 TKE](https://cloud.tencent.com/act/cps/redirect?redirect=10058\\&cps_key=3a5255852d5db99dcd5da4c72f05df61) 是高度可扩展的高性能容器管理服务，用户可以在托管的云服务器实例集群上轻松运行应用程序。使用该服务，将无需安装、运维、扩展用户的集群管理基础设施，只需进行简单的 API 调用，便可启动和停止 Docker 应用程序，查询集群的完整状态，以及使用各种云服务。用户可以根据用户的资源需求和可用性要求在用户的集群中安排容器的置放，满足业务或应用程序的特定要求。\n\n![腾讯云容器服务界面](https://mc.qcloudimg.com/static/img/0581dbeb97c869bbe6e62025dbc592d7/image.png)\n\n图 16-2：腾讯云容器服务示意图\n\n### 腾讯云容器服务：TKE 简介\n\n腾讯云容器服务 (TKE, Tencent Kubernetes Engine) 是一款容器编排平台，基于原生 Kubernetes 提供，支持自动扩展、负载均衡、多可用区高可用等企业级功能。TKE 帮助开发者快速部署和管理容器化应用，消除集群运维的复杂度。\n\n### 基本使用步骤\n\n#### 1. 创建集群\n\n登录腾讯云控制台，进入容器服务模块：\n- 选择 “创建集群”，配置集群名称、地域和网络\n- 选择节点配置（云服务器规格和数量）\n- 设置 Kubernetes 版本和安全组\n- 完成创建后获得集群 kubeconfig 文件\n\n```bash\n# 下载 kubeconfig 文件后，配置本地环境\nexport KUBECONFIG=/path/to/kubeconfig.yaml\nkubectl cluster-info\n```\n\n#### 2. 部署容器应用\n\n创建 Deployment 部署应用：\n\n```yaml\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: nginx-app\nspec:\n  replicas: 3\n  selector:\n    matchLabels:\n      app: nginx\n  template:\n    metadata:\n      labels:\n        app: nginx\n    spec:\n      containers:\n      - name: nginx\n        image: nginx:latest\n        ports:\n        - containerPort: 80\n```\n\n应用配置文件：\n\n```bash\nkubectl apply -f deployment.yaml\nkubectl get pods\nkubectl get svc\n```\n\n#### 3. 管理镜像\n\n通过腾讯云容器镜像服务 (TCR) 存储和管理私有镜像：\n\n```bash\n# 登录腾讯云镜像仓库\ndocker login ccr.ccs.tencentyun.com -u <username>\n\n# 标记本地镜像\ndocker tag my-app:latest ccr.ccs.tencentyun.com/namespace/my-app:latest\n\n# 推送镜像到腾讯云\ndocker push ccr.ccs.tencentyun.com/namespace/my-app:latest\n```\n\n### 腾讯云 Docker 镜像加速器配置\n\n为了加快镜像拉取速度，腾讯云提供了镜像加速服务。配置方法如下：\n\n#### Linux 系统配置\n\n编辑 `/etc/docker/daemon.json` 文件（如果不存在则创建）：\n\n```bash\n# 创建或编辑配置文件\nsudo mkdir -p /etc/docker\nsudo nano /etc/docker/daemon.json\n```\n\n添加以下内容：\n\n```json\n{\n  \"registry-mirrors\": [\n    \"https://mirror.ccs.tencentyun.com\"\n  ],\n  \"insecure-registries\": []\n}\n```\n\n重启 Docker 服务：\n\n```bash\nsudo systemctl daemon-reload\nsudo systemctl restart docker\n```\n\n验证配置：\n\n```bash\n# 查看镜像源是否生效\ndocker info | grep -A 5 \"Registry Mirrors\"\n```\n\n#### Windows/Mac 配置\n\n对于 Docker Desktop，在设置界面中：\n1. 打开 Docker Desktop 设置\n2. 导航到 “Docker Engine”\n3. 在 JSON 配置中添加上述 `registry-mirrors` 字段\n4. 点击 “Apply & Restart”\n\n### 腾讯云容器镜像服务：TCR\n\n腾讯云容器镜像服务 (TCR) 提供企业级容器镜像存储和分发能力：\n\n- **私有镜像仓库**：支持命名空间隔离，完整的访问权限控制\n- **镜像扫描**：自动扫描镜像漏洞，提供安全建议\n- **镜像加速**：支持跨地域镜像分发和加速\n- **Webhook 通知**：镜像推送时自动触发 CI/CD 流程\n- **镜像版本管理**：标签管理、镜像清理策略、生命周期管理\n\n#### 快速开始\n\n1. 在腾讯云控制台创建个人版或企业版 TCR 实例\n2. 创建命名空间和镜像仓库\n3. 配置 Docker 登录凭证\n4. 本地构建镜像并推送到 TCR\n5. 在 TKE 集群部署时引用 TCR 镜像地址\n\n#### 完整推送/拉取示例\n\n```bash\n# 登录到腾讯云 TCR（使用 API 密钥）\ndocker login ccr.ccs.tencentyun.com \\\n  --username <腾讯云账号ID> \\\n  --password <API_KEY>\n\n# 拉取公开镜像\ndocker pull ccr.ccs.tencentyun.com/library/nginx:latest\n\n# 构建本地镜像\ndocker build -t my-app:v1.0 .\n\n# 标记镜像为 TCR 地址\ndocker tag my-app:v1.0 \\\n  ccr.ccs.tencentyun.com/my-namespace/my-app:v1.0\n\n# 推送镜像到 TCR\ndocker push ccr.ccs.tencentyun.com/my-namespace/my-app:v1.0\n\n# 在 Dockerfile 中使用 TCR 镜像\nFROM ccr.ccs.tencentyun.com/my-namespace/my-app:v1.0\nRUN echo \"使用腾讯云镜像作为基础镜像\"\n```\n\n#### TKE 集群中使用 TCR 镜像\n\n配置镜像拉取凭证后，在 Deployment 中直接引用 TCR 镜像：\n\n```yaml\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: my-app-deployment\n  namespace: default\nspec:\n  replicas: 3\n  selector:\n    matchLabels:\n      app: my-app\n  template:\n    metadata:\n      labels:\n        app: my-app\n    spec:\n      imagePullSecrets:\n      - name: tcr-secret  # 需提前创建该 Secret\n      containers:\n      - name: my-app\n        image: ccr.ccs.tencentyun.com/my-namespace/my-app:v1.0\n        ports:\n        - containerPort: 8080\n        resources:\n          requests:\n            memory: \"256Mi\"\n            cpu: \"100m\"\n          limits:\n            memory: \"512Mi\"\n            cpu: \"500m\"\n```\n"
  },
  {
    "path": "16_cloud/16.3_alicloud.md",
    "content": "## 16.3 阿里云\n\n如图 16-3：所示，阿里云是国内主流云服务平台之一。\n\n![阿里云](./_images/aliyun-logo.png)\n\n图 16-3：阿里云标识\n\n[阿里云](https://www.aliyun.com/?source=5176.11533457\\&userCode=8lx5zmtu\\&type=copy)创立于 2009 年，是中国较早的云计算平台。阿里云致力于提供安全、可靠的计算和数据处理能力。\n\n[阿里云](https://www.aliyun.com/?source=5176.11533457\\&userCode=8lx5zmtu\\&type=copy)的客户群体中，活跃着微博、虎牙、魅族、优酷等一大批明星互联网公司。在天猫双 11 全球狂欢节等极富挑战的应用场景中，阿里云保持着良好的运行纪录。\n\n[阿里云容器服务 Kubernetes 版 ACK](https://www.aliyun.com/product/kubernetes?source=5176.11533457\\&userCode=8lx5zmtu\\&type=copy) 提供了高性能、可伸缩的容器应用管理服务，支持在一组云服务器上通过 Docker 容器来进行应用生命周期管理。容器服务极大简化了用户对容器管理集群的搭建工作，无缝整合了阿里云虚拟化、存储、网络和安全能力。容器服务提供了多种应用发布方式和流水线般的持续交付能力，原生支持微服务架构，助力用户无缝上云和跨云管理。\n\n<!-- 注意：原阿里云容器服务截图链接已失效，请参考阿里云官方文档获取最新界面截图 -->\n<!-- 原链接: https://img.alicdn.com/tps/TB10yjtPpXXXXacXXXXXXXXXXXX-1531-1140.png -->\n\n图 16-4：阿里云容器服务示意图（请访问 [阿里云容器服务 ACK 官方文档](https://help.aliyun.com/product/85222.html) 查看最新界面）\n\n### 阿里云容器服务 ACK 简介\n\n阿里云容器服务 Kubernetes 版 (ACK, Container Service for Kubernetes) 是一款托管式 Kubernetes 服务，基于开源 Kubernetes 构建，提供企业级的容器编排和管理能力。ACK 集成了阿里云存储、网络和安全能力，支持多种应用部署模式和持续交付流程。\n\n### 基本使用步骤\n\n#### 1. 创建集群\n\n登录阿里云控制台，进入容器服务 > Kubernetes 集群：\n- 点击 “创建集群”，选择集群配置\n- 配置集群名称、地域、可用区和节点类型\n- 选择节点规格和数量（支持弹性伸缩）\n- 配置网络参数和安全设置\n- 完成创建，下载 kubeconfig 文件\n\n```bash\n# 配置本地 kubectl\nexport KUBECONFIG=/path/to/kubeconfig.yaml\nkubectl get nodes\n```\n\n#### 2. 部署容器应用\n\n通过 Deployment 部署应用示例：\n\n```yaml\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: web-server\nspec:\n  replicas: 2\n  selector:\n    matchLabels:\n      app: web\n  template:\n    metadata:\n      labels:\n        app: web\n    spec:\n      containers:\n      - name: web\n        image: registry.cn-hangzhou.aliyuncs.com/myapp/web:v1\n        ports:\n        - containerPort: 8080\n        resources:\n          limits:\n            memory: \"512Mi\"\n            cpu: \"500m\"\n```\n\n部署应用：\n\n```bash\nkubectl apply -f deployment.yaml\nkubectl get pods -o wide\nkubectl logs <pod-name>\n```\n\n#### 3. 暴露服务\n\n创建 Service 暴露应用：\n\n```yaml\napiVersion: v1\nkind: Service\nmetadata:\n  name: web-service\nspec:\n  type: LoadBalancer\n  ports:\n  - port: 80\n    targetPort: 8080\n  selector:\n    app: web\n```\n\n应用：\n\n```bash\nkubectl apply -f service.yaml\nkubectl get svc web-service\n```\n\n### 阿里云 Docker 镜像加速器配置\n\n为了加快从阿里云镜像源拉取官方镜像的速度，可以配置镜像加速器。阿里云为容器服务 ACK 用户提供了免费的镜像加速服务。\n\n#### 获取加速器地址\n\n登录阿里云容器镜像服务控制台，在 “镜像工具” > “镜像加速器” 中可获取个人的加速器地址（类似于 `https://xxxxxx.mirror.aliyuncs.com`）。\n\n#### Linux 系统配置\n\n编辑或创建 `/etc/docker/daemon.json` 文件：\n\n```bash\nsudo mkdir -p /etc/docker\nsudo nano /etc/docker/daemon.json\n```\n\n添加或修改以下内容（替换为你的加速器地址）：\n\n```json\n{\n  \"registry-mirrors\": [\n    \"https://xxxxxx.mirror.aliyuncs.com\"\n  ]\n}\n```\n\n重新加载并重启 Docker：\n\n```bash\nsudo systemctl daemon-reload\nsudo systemctl restart docker\n```\n\n验证配置生效：\n\n```bash\ndocker info | grep -A 5 \"Registry Mirrors\"\n```\n\n#### Windows/Mac 配置\n\n在 Docker Desktop 的 Settings 中：\n1. 进入 “Docker Engine” 标签\n2. 编辑 JSON 配置，添加 `registry-mirrors` 字段\n3. 点击 “Apply & Restart”\n\n#### 测试加速效果\n\n```bash\n# 从加速器拉取镜像（速度应该明显提升）\ndocker pull nginx:latest\ntime docker pull alpine:latest\n```\n\n### 阿里云容器镜像服务：ACR\n\n阿里云容器镜像服务 (ACR, Container Registry) 是企业级的容器镜像存储和分发平台：\n\n- **私有镜像仓库**：支持多个命名空间，细粒度权限控制\n- **镜像构建**：云端编译和构建，支持自动化 CI/CD\n- **镜像扫描**：自动检测镜像中的漏洞和恶意代码\n- **跨地域复制**：支持镜像在多个地域的同步和加速\n- **集成 ACK**：与 ACK 无缝集成，自动身份认证\n- **镜像版本管理**：标签管理、镜像过期清理、保留策略\n\n#### 完整推送/拉取示例\n\n```bash\n# 登录阿里云镜像仓库（使用 Docker 登录）\n# 使用阿里云账户 ID 和 RAM 访问密钥或密码\ndocker login registry.cn-hangzhou.aliyuncs.com \\\n  --username=<阿里云账户ID>\n\n# 拉取阿里云公开镜像\ndocker pull registry.cn-hangzhou.aliyuncs.com/library/nginx:latest\n\n# 构建本地镜像\ndocker build -t my-app:v1.0 .\n\n# 标记镜像为阿里云仓库地址\ndocker tag my-app:v1.0 \\\n  registry.cn-hangzhou.aliyuncs.com/myapp/my-app:v1.0\n\n# 推送镜像到阿里云 ACR\ndocker push registry.cn-hangzhou.aliyuncs.com/myapp/my-app:v1.0\n\n# 在 Dockerfile 中使用 ACR 镜像\nFROM registry.cn-hangzhou.aliyuncs.com/myapp/my-app:v1.0\nCOPY . /app\nRUN echo \"已成功使用阿里云镜像\"\n```\n\n#### ACK 集群中使用 ACR 镜像\n\n在 ACK 集群中，需要先配置镜像拉取凭证（Secret），然后在 Deployment 中引用：\n\n```yaml\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: web-server\n  namespace: default\nspec:\n  replicas: 3\n  selector:\n    matchLabels:\n      app: web\n  template:\n    metadata:\n      labels:\n        app: web\n    spec:\n      # 如果是私有镜像，需配置镜像拉取凭证\n      imagePullSecrets:\n      - name: acr-secret\n      containers:\n      - name: web\n        image: registry.cn-hangzhou.aliyuncs.com/myapp/web:v2.0\n        imagePullPolicy: IfNotPresent\n        ports:\n        - containerPort: 8080\n        resources:\n          requests:\n            memory: \"256Mi\"\n            cpu: \"100m\"\n          limits:\n            memory: \"512Mi\"\n            cpu: \"500m\"\n      affinity:\n        # 配置 Pod 反亲和性，分散到不同节点\n        podAntiAffinity:\n          preferredDuringSchedulingIgnoredDuringExecution:\n          - weight: 100\n            podAffinityTerm:\n              labelSelector:\n                matchExpressions:\n                - key: app\n                  operator: In\n                  values:\n                  - web\n              topologyKey: kubernetes.io/hostname\n```\n\n#### 创建镜像拉取凭证\n\n在 ACK 集群中创建 Secret，用于拉取私有镜像：\n\n```bash\n# 创建镜像拉取 Secret\nkubectl create secret docker-registry acr-secret \\\n  --docker-server=registry.cn-hangzhou.aliyuncs.com \\\n  --docker-username=<阿里云账户ID> \\\n  --docker-password=<RAM访问密钥或密码> \\\n  --docker-email=<邮箱地址>\n\n# 查看创建的 Secret\nkubectl get secret acr-secret\nkubectl describe secret acr-secret\n```\n\n#### ACR 优势\n\n- 在 ACK 集群中与镜像仓库无缝集成，简化身份认证\n- 支持 Helm Chart 存储和版本管理，方便应用交付\n- 提供完整的图形化镜像仓库管理界面\n- 完整的审计日志和操作追踪功能\n- 支持镜像自动扫描和漏洞报告\n"
  },
  {
    "path": "16_cloud/16.4_aws.md",
    "content": "## 16.4 亚马逊云\n\n如图 16-5：所示，AWS 是全球主流云服务平台之一。\n\n![AWS](./_images/aws-logo.jpg)\n\n图 16-5：AWS 标识\n\n[AWS](https://www.amazonaws.cn)，即 Amazon Web Services，是亚马逊 (Amazon) 公司的 IaaS 和 PaaS 平台服务。AWS 提供了一整套基础设施和应用程序服务，使用户几乎能够在云中运行一切应用程序：从企业应用程序和大数据项目，到社交游戏和移动应用程序。AWS 面向用户提供包括弹性计算、存储、数据库、应用程序在内的一整套云计算服务，能够帮助企业降低 IT 投入成本和维护成本。\n\n在容器领域，AWS 目前主流能力可以按场景分为四类：\n\n1. `Amazon EKS`：托管 Kubernetes 控制平面，适合标准云原生工作负载。\n2. `Amazon ECS`：AWS 原生容器编排服务，适合深度集成 AWS 生态 (IAM、ALB、CloudWatch) 场景。\n3. `AWS Fargate`：无服务器容器运行时，可与 EKS/ECS 结合使用，减少节点运维。\n4. `Amazon ECR`：镜像仓库服务，提供私有镜像管理、扫描与访问控制。\n\n实践建议：\n\n* 团队已具备 Kubernetes 经验，优先选择 EKS；\n* 追求更低运维复杂度且业务主要运行在 AWS，可优先 ECS + Fargate；\n* 无论编排方案如何，都建议使用 ECR 统一管理镜像生命周期。\n\n![AWS 容器服务](./_images/ECS.jpg)\n\n图 16-6：AWS 容器服务示意图\n"
  },
  {
    "path": "16_cloud/16.5_multicloud.md",
    "content": "## 16.5 多云部署策略\n\n企业在选择容器云平台时，通常会在 AWS EKS，Azure AKS，Google GKE 以及国内的阿里云 ACK，腾讯云 TKE 之间进行权衡。\n\n### 16.5.1 三大公有云 Kubernetes 服务对比\n\n| 特性 | Google GKE | AWS EKS | Azure AKS |\n| :--- | :--- | :--- | :--- |\n| **版本更新** | 最快，通常是 K8s 新特性的首发地 | 相对保守，注重稳定性 | 跟随社区，更新速度适中 |\n| **控制平面管理** | 全托管，自动升级，免费 (部分区域)| 托管，每小时收费 | 全托管，控制平面免费 |\n| **节点管理** | GKE Autopilot 模式完全托管节点 | Managed Node Groups 简化管理 | Virtual Machine Scale Sets |\n| **网络模型** | VPC-native, 性能优秀 | AWS VPC CNI, Pod 直接获取 VPC IP | Azure CNI (消耗 IP 多) 或 Kubenet |\n| **集成度** | 与 GCP 数据分析、AI 服务集成紧密 | 与 AWS IAM, ALB, CloudWatch 集成深度高 | 与 Active Directory, Azure DevOps 集成好 |\n\n### 16.5.2 多云部署策略\n\n随着企业业务的扩展，单一云平台可能无法满足所有需求，多云部署成为趋势。\n\n#### 1. 跨云灾备：Active-Passive\n\n主要业务运行在一个云 (如 AWS)，数据实时复制到另一个云 (如阿里云)。当主云发生故障时，流量切换到备云。\n\n* **优点**：架构相对简单，数据一致性好控制。\n* **缺点**：资源闲置浪费，切换可能有 RTO。\n\n#### 2. 多活部署：Active-Active\n\n业务同时在多个云上运行，通过全局流量管理 (DNS/GSLB) 分发流量。\n\n* **优点**：高可用，就近接入提升用户体验。\n* **缺点**：数据同步复杂，跨云网络延迟问题。\n\n#### 3. 混合云\n\n核心数据和敏感业务保留在私有云 (IDC)，弹性业务或前端业务部署在公有云。\n\n* **工具**：Google Anthos，AWS Outposts，Azure Arc 都是为了解决混合云统一管理而生。\n\n### 16.5.3 建议\n\n* **技术选型**：尽量使用标准的 Kubernetes API，避免过度依赖特定云厂商的 CRD 或专有服务，以保持应用的可移植性。\n* **IaC 管理**：使用 Terraform 或 Pulumi 等工具统一管理多云基础设施。\n"
  },
  {
    "path": "16_cloud/README.md",
    "content": "# 第十六章 容器与云计算\n\nDocker 目前已经得到了众多公有云平台的支持，并成为除虚拟机之外的核心云业务。\n\n除了 AWS、Google、Azure 等，国内的各大公有云厂商，基本上都同时支持了虚拟机服务和基于 Kubernetes 的容器云业务。有的还推出了其他服务，例如[容器镜像服务](https://cloud.tencent.com/act/cps/redirect?redirect=11588&cps_key=3a5255852d5db99dcd5da4c72f05df61)让用户在云上享有安全高效的镜像托管、分发等服务。\n\n## 本章内容\n\n* [简介](16.1_intro.md)\n* [腾讯云](16.2_tencentCloud.md)\n* [阿里云](16.3_alicloud.md)\n* [亚马逊云](16.4_aws.md)\n* [多云部署策略](16.5_multicloud.md)\n"
  },
  {
    "path": "16_cloud/summary.md",
    "content": "## 本章小结\n\n本章介绍了公有云服务对 Docker 的积极支持，以及新出现的容器云平台。\n\n事实上，Docker 技术的出现自身就极大推动了云计算行业的发展。\n\n通过整合公有云的虚拟机和 Docker 方式，可能获得更多的好处，包括\n\n* 更快速的持续交付和部署能力；\n* 利用内核级虚拟化，对公有云中服务器资源进行更加高效地利用；\n* 利用公有云和 Docker 的特性更加方便的迁移和扩展应用。\n\n同时，容器将作为与虚拟机类似的业务直接提供给用户使用，极大的丰富了应用开发和部署的场景。\n---\n\n> 📝 **发现错误或有改进建议？** 欢迎提交 [Issue](https://github.com/yeasy/docker_practice/issues) 或 [PR](https://github.com/yeasy/docker_practice/pulls)。\n"
  },
  {
    "path": "17_ecosystem/17.1_coreos_intro.md",
    "content": "## 17.1 Fedora CoreOS 简介\n\n[Fedora CoreOS](https://getfedora.org/coreos/) 是一个自动更新的，最小的，整体的，以容器为中心的操作系统，不仅适用于集群，而且可独立运行，并针对运行 Kubernetes 进行了优化。它旨在结合 CoreOS Container Linux 和 Fedora Atomic Host 的优点，将 Container Linux 中的 [Ignition](https://github.com/coreos/ignition) 与 [rpm-ostree](https://github.com/coreos/rpm-ostree) 和 Project Atomic 中的 SELinux 强化等技术相集成。其目标是提供最佳的容器主机，以安全，大规模地运行容器化的工作负载。\n\n### 17.1.1 FCOS 特性\n\n#### 一个最小化操作系统\n\nFCOS 被设计成一个基于容器的最小化的现代操作系统。它比现有的 Linux 安装平均节省 40% 的 RAM (大约 114M) 并允许从 PXE 或 iPXE 非常快速的启动。\n\n#### 系统初始化\n\nIgnition 是一种配置实用程序，可读取配置文件 (JSON 格式) 并根据该配置配置 FCOS 系统。可配置的组件包括存储，文件系统，systemd 和用户。\n\nIgnition 在系统首次启动期间 (在 initramfs 中) 仅运行一次。由于 Ignition 在启动过程中的早期运行，因此它可以在用户空间开始启动之前重新对磁盘分区，格式化文件系统，创建用户并写入文件。当 systemd 启动时，systemd 服务已被写入磁盘，从而加快了启动时间。\n\n#### 自动更新\n\nFCOS 使用 rpm-ostree 系统进行事务性升级。无需像 yum 升级那样升级单个软件包，而是 rpm-ostree 将 OS 升级作为一个原子单元进行。新的 OS 部署在升级期间进行，并在下次重新引导时生效。如果升级出现问题，则一次回滚和重新启动会使系统返回到先前的状态。确保了系统升级对群集容量的影响降到最小。\n\n#### 容器工具\n\n对于诸如构建，复制和其他管理容器的任务，FCOS 用一组容器工具代替了 **Docker CLI**。**podman CLI** 工具支持许多容器运行时功能，例如运行，启动，停止，列出和删除容器和镜像。**skopeo CLI** 工具可以复制，认证和签名镜像。您还可以使用 **crictl CLI** 工具来处理 CRI-O 容器引擎中的容器和镜像。\n"
  },
  {
    "path": "17_ecosystem/17.2_coreos_install.md",
    "content": "## 17.2 Fedora CoreOS 安装\n\n### 17.2.1 下载 ISO\n\n在[下载页面](https://getfedora.org/coreos/download/) `Bare Metal & Virtualized` 标签页下载 ISO。\n\n### 17.2.2 编写 FCC\n\nFCC 是 Fedora CoreOS Configuration (Fedora CoreOS 配置) 的简称。\n\n```yaml\n## example.fcc\n\nvariant: fcos\nversion: 1.0.0\npasswd:\n  users:\n    - name: core\n      ssh_authorized_keys:\n        - ssh-rsa AAAA...\n```\n\n将 `ssh-rsa AAAA...` 替换为自己的 SSH 公钥 (位于 `~/.ssh/id_rsa.pub`)。\n\n### 17.2.3 转换 FCC 为 Ignition\n\n```bash\n$ docker run -i --rm quay.io/coreos/fcct:v0.5.0 --pretty --strict < example.fcc > example.ign\n```\n\n### 17.2.4 挂载 ISO 启动虚拟机并安装\n\n> 虚拟机需要分配 3GB 以上内存，否则会无法启动。\n\n在虚拟机终端执行以下命令安装：\n\n```bash\n$ sudo coreos-installer install /dev/sda --ignition-file example.ign\n```\n\n安装之后重新启动即可使用。\n\n### 17.2.5 使用\n\n```bash\n$ ssh core@虚拟机IP\n\n$ docker --version\n```\n"
  },
  {
    "path": "17_ecosystem/17.3_podman.md",
    "content": "## 17.3 Podman - 下一代 Linux 容器工具\n\n[Podman](https://github.com/containers/podman) 是一个无守护进程、与 Docker 命令高度兼容的下一代 Linux 容器工具。它由 Red Hat 开发，旨在提供一个更安全的容器运行环境。\n\n### 17.3.1 Podman vs Docker\n\nPodman 和 Docker 在设计理念上存在显著差异，主要体现在架构和权限模型上。\n\n| 特性 | Docker | Podman |\n| :--- | :--- | :--- |\n| **架构** | C/S 架构，依赖守护进程 (`dockerd`) | 无守护进程 (Daemonless) |\n| **权限** | 默认需要 root 权限 (虽有 Rootless 模式) | 默认支持 Rootless (非 root 用户运行) |\n| **生态** | 完整的生态系统 (Compose, Swarm) | 专注单机容器，配合 Kubernetes 使用 |\n| **镜像构建** | `docker build` | `podman build` 或 `buildah` |\n\n### 17.3.2 安装\n\nPodman 支持多种操作系统，安装过程也相对简单。\n\n#### CentOS / RHEL\n\n```bash\n$ sudo yum -y install podman\n```\n\n#### macOS\n\nmacOS 上需要安装 Podman Desktop 或通过 Homebrew 安装：\n\n```bash\n$ brew install podman\n$ podman machine init\n$ podman machine start\n```\n\n### 17.3.3 基本使用\n\n`podman` 的命令行几乎与 `docker` 完全兼容，大多数情况下，你只需将 `docker` 替换为 `podman` 即可。\n\n#### 运行容器\n\n```bash\n## $ docker run -d -p 80:80 nginx:alpine\n\n$ podman run -d -p 80:80 nginx:alpine\n```\n\n#### 列出容器\n\n```bash\n$ podman ps\n```\n\n#### 构建镜像\n\n```bash\n$ podman build -t myimage .\n```\n\n### 17.3.4 Pods 的概念\n\n与 Docker 不同，Podman 支持“Pod”的概念 (类似于 Kubernetes 的 Pod)，允许你在同一个网络命名空间中运行多个容器。\n\n```bash\n## 创建一个 Pod\n\n$ podman pod create --name mypod -p 8080:80\n\n## 在 Pod 中运行容器\n\n$ podman run -d --pod mypod --name webbing nginx\n```\n\n### 17.3.5 迁移到 Podman\n\n如果你习惯使用 `docker` 命令，可以简单地设置别名：\n\n```bash\n$ alias docker=podman\n```\n\n#### Systemd 集成\n\nPodman 可以生成 systemd 单元文件，让容器像普通系统服务一样管理。\n\n```bash\n## 创建容器\n\n$ podman run -d --name myweb -p 8080:80 nginx\n\n## 生成 systemd 文件\n\n$ podman generate systemd --name myweb --files --new\n\n## 启用并启动服务\n\n$ systemctl --user enable --now container-myweb.service\n```\n\n#### Podman Compose\n\n虽然 Podman 兼容 Docker Compose，但在某些场景下你可能需要明确使用 `podman-compose`。\n\n```bash\n$ pip3 install podman-compose\n$ podman-compose up -d\n```\n"
  },
  {
    "path": "17_ecosystem/17.4_buildah.md",
    "content": "## 17.4 Buildah - 容器镜像构建工具\n\n本节介绍 Buildah，包括其基础概念、应用场景以及基本指令。\n\n### Buildah 简介\n\nBuildah 是一个用于构建 OCI（Open Container Initiative）兼容格式容器镜像的开源命令行工具。与 Docker 需要一直运行的守护进程（daemon）不同，Buildah 的设计初衷是无需守护进程（daemonless）即可工作，并且也不强制要求 root 权限（rootless）。这使得在持续集成/持续部署（CI/CD）环境中构建镜像时能够更加轻量且安全。\n\nBuildah 由 Red Hat 主导开发，通常和 Podman、Skopeo 一起使用，被认为是构建、运行和管理容器的一套现代化工具链。在很多需要增强安全性和无需依赖守护进程的场景中，Buildah 是 `docker build` 命令的最佳替代方案。\n\n### 核心特性\n\n- **无守护进程（Daemonless）**：Buildah 直接通过系统调用拉取、构建和推送镜像，减少了单点故障的风险和资源开销。\n- **构建效率高**：可以挂载镜像的根文件系统到本地，并直接利用宿主机的工具对其进行操作，非常灵活。\n- **兼容性**：不仅支持处理传统的 Dockerfile，还能完全兼容 OCI（Open Container Initiative）标准和 Docker 格式。\n- **与 Podman 集成**：Podman 自身构建镜像的命令 `podman build` 底层实际上也是依赖 Buildah 库来实现的。\n\n### 安装 Buildah\n\n在许多主流的 Linux 发行版中都可以通过包管理器直接安装 Buildah。\n\n以 Fedora/CentOS/RHEL 为例：\n\n```bash\n$ sudo dnf install -y buildah\n```\n\n以 Ubuntu/Debian 为例（需引入官方源后）：\n\n```bash\n$ sudo apt-get update\n$ sudo apt-get -y install buildah\n```\n\n### 基础用法示例\n\n#### 1. 从现有的 Dockerfile 构建镜像\n\nBuildah 最常见的用法就是像 Docker 一样根据 `Dockerfile` 来构建镜像，可以直接使用 `buildah bud`（或者 `buildah build-using-dockerfile`）命令：\n\n```bash\n$ buildah bud -t my-app:latest .\n```\n\n可以看到在这点上，它与 `docker build` 的体验完全一致。\n\n#### 2. 交互式从空镜像开始构建\n\n除了使用 Dockerfile，Buildah 最强大的功能来自于它的交互式和脚本化构建机制。我们可以从一个极简的镜像（或基础镜像）开始构建：\n\n```bash\n# 获取一个基础镜像\n$ container=$(buildah from alpine:latest)\n\n# 获取挂载点，并查看其路径\n$ mnt=$(buildah mount $container)\n$ echo $mnt\n/var/lib/containers/storage/overlay/xxx/merged\n\n# 利用宿主机直接创建文件，而不需要在容器内部运行命令\n$ echo \"Hello Buildah\" > $mnt/hello.txt\n\n# 添加一些配置和命令\n$ buildah config --cmd \"cat /hello.txt\" $container\n\n# 将容器提交为镜像\n$ buildah commit $container my-hello-image:latest\n\n# 构建完成后可以卸载并清理容器上下文\n$ buildah unmount $container\n$ buildah rm $container\n```\n\n这种模式在自动化流水线中极为有用，因为我们可以将上述过程编写成标准的 bash 脚本，无需为了构建镜像而撰写只在其独立语法中运行的 Dockerfile 指令。\n\n#### 3. 查看和推送镜像\n\n通过 `buildah images` 可以查看当前环境中的镜像。推送镜像到外部 Registry 也十分安全方便：\n\n```bash\n# 查看本地构建的镜像\n$ buildah images\n\n# 推送镜像到 Docker Hub（注意需要先登录）\n$ buildah push my-hello-image:latest docker://docker.io/username/my-hello-image:latest\n```\n\n结合其无需特权和灵活脚本的优点，Buildah 正变得越来越受到构建和分发 OCI 镜像的用户喜爱。\n"
  },
  {
    "path": "17_ecosystem/17.5_skopeo.md",
    "content": "## 17.5 Skopeo - 容器镜像管理工具\n\n本节介绍 Skopeo，包括其基础概念、应用场景以及基本指令。\n\n### Skopeo 简介\n\nSkopeo 是一个由 Red Hat 赞助开源的命令行工具，它可以在不需要运行容器守护进程（如 Docker Daemon）的前提下，对容器镜像进行极其高效的操作和管理，包括：检查、复制、删除和签名等操作。\n\nSkopeo 最大的特点是其可以在“不将镜像拉取到本地”的情况下，直接在远端 Registry（镜像仓库）之间完成检查和搬运，从而大幅度节省带宽和磁盘空间。这也是它在容器运维和分发领域非常受欢迎的原因。\n\n### 核心特性\n\n- **远程巡检**：通过 `skopeo inspect` 可以查看远端仓库中镜像的元数据（例如包含哪些层、环境变量信息、入口命令等），而完全无需拉取该镜像。\n- **镜像复制与同步**：支持各种格式之间的相互传输，如在不同的容器仓库之间、或者从仓库拉取到本地的目录、或者存储为 OCI 布局结构等。\n- **镜像删除**：可以远程删除仓库中的镜像（需要拥有权限）。\n- **签名验证**：支持在分发镜像前进行数字签名以保障安全性。\n\n### 安装 Skopeo\n\n类似于 Buildah，Skopeo 也直接包含在大部分主流的 Linux 源中。\n\n在 Fedora/CentOS/RHEL 等发行版中：\n\n```bash\n$ sudo dnf install -y skopeo\n```\n\n在 Ubuntu/Debian 中：\n\n```bash\n$ sudo apt-get update\n$ sudo apt-get -y install skopeo\n```\n\n如果是 macOS 环境，可以通过 Homebrew 安装：\n\n```bash\n$ brew install skopeo\n```\n\n### 基础用法示例\n\n#### 1. 远程检查镜像\n\n有时候我们只想知道远端镜像的详细信息，并不想拉取它。下面是检查 Docker Hub 上的 Alpine 镜像的例子：\n\n```bash\n$ skopeo inspect docker://docker.io/library/alpine:latest\n```\n\n这个命令会返回一段 JSON 格式的数据，其中包含了诸如镜像摘要（Digest）、创建时间、架构（Architecture）、标签（Tags）等丰富信息。在自动化的工具和系统审查环境中，这是一个不可或缺的利器。\n\n#### 2. 同步与复制镜像\n\n`skopeo copy` 是使用最为广泛的命令。它可以在不同的仓库、不同的格式之间无缝搬运镜像。\n\n例如，从一个公共仓库直接搬运镜像到一个私有企业仓库（无需将其先 `docker pull` 到本地，再 `docker push`）：\n\n```bash\n$ skopeo copy docker://docker.io/library/alpine:latest docker://registry.example.com/library/alpine:latest\n```\n\n又或者，你可以将远程镜像拉取到本地，只为了检查其拆解后的格式，比如将镜像解压到本地某个目录下以 OCI 规范存放：\n\n```bash\n$ skopeo copy docker://docker.io/library/alpine:latest oci:alpine-oci\n```\n\n如果我们要将本地的某个目录下的打包好的镜像再次推向 Registry 或转换为其它存储类型也是完全支持的，诸如：\n- `docker://` 远端 Registry\n- `docker-archive:` / `docker-daemon:` Docker 对应的归档文件或本地守护进程\n- `oci:` / `oci-archive:` OCI 相关文件格式\n- `dir:` 本地纯目录\n\n#### 3. 校验机制与安全\n\n如果你不信任公开仓库上的镜像，或是需要通过特定的 TLS 证书和鉴权，Skopeo 的功能也是能很好的支持的，比如它可以直接传递诸如 `--src-creds` 或 `--dest-tls-verify=false` 等参数，这在进行网络隔离的复杂镜像搬运操作中常常会用到。\n\n对于复杂的容器环境或那些纯粹用于镜像资产管理的节点来说，Skopeo 提供了一个直接而强大的数据“搬运工”。\n"
  },
  {
    "path": "17_ecosystem/17.6_containerd.md",
    "content": "## 17.6 containerd - 核心容器运行时\n\n本节介绍 containerd，它是现代容器技术栈中最为核心的基础组件之一。了解 containerd 有助于更深入地理解 Docker 和 Kubernetes 的底层运行机制。\n\n### containerd 简介\n\n[containerd](https://containerd.io/) 是一个行业标准的容器运行时，它最初是由 Docker 引擎中剥离出来的一个核心组件，后来 Docker 将其捐赠给了云原生计算基金会（CNCF），目前已经是一个 CNCF 毕业（Graduated）项目。\n\n它的主要职责是管理单个宿主机上完整的容器生命周期，包括：\n- 镜像的传输和存储\n- 容器执行和管理\n- 存储和网络接口的管理\n\n简单来说，当你在使用 Docker 或者 Kubernetes 时，真正去底层调用操作系统接口（如 Linux 的 Namespace 和 Cgroups）来启动和管理容器进程的，往往是 containerd（及它所调用的 runc 组件）。\n\n### 与 Docker 和 Kubernetes 的关系\n\n理解 containerd，首先要理清它与用户日常操作的 Docker 以及 Kubernetes 的关系。\n\n#### Docker 的架构\n\n在早期，Docker 引擎是一个包含了所有功能的单体架构。随着技术的发展和标准化要求，Docker 将底层关于容器运行时的部分解耦出来，形成了 `containerd` 和 `runc`。\n\n当你执行一个 `docker run` 命令时，调用链路大致如下：\n1. Docker Client 发送请求给 Docker Daemon（`dockerd`）。\n2. `dockerd` 将请求转发给 `containerd`。\n3. `containerd` 准备好镜像和容器的必要环境，然后调用 `runc`。\n4. `runc` 负责按照 OCI（Open Container Initiative）标准，拉起并运行真正的容器进程。\n\n因此，Docker 现在实际上是构建在 containerd 之上的一个包含更多开发者友好特性（如构建镜像、Compose 管理等）的增强平台。\n\n#### Kubernetes 与 CRI\n\nKubernetes 作为一个容器编排系统，为了屏蔽底层不同容器运行时的实现差异，引入了 CRI（Container Runtime Interface）标准。\n\n- 早期版本中，Kubernetes 默认使用 docker 作为运行时，通过一个名为 `dockershim` 的桥接组件对接 Docker，Docker 再对接 containerd。\n- 随着 containerd 原生支持了 CRI 插件，Kubernetes 开始直接与 containerd 通信，去掉了 `dockershim` 和 `dockerd` 的中间层。这就是为什么从 Kubernetes v1.24 开始“弃用 Docker”引发了广泛关注，实际上 Kubernetes 只是弃用 `dockershim`，底层依然在使用从 Docker 基因中诞生的 containerd。\n\n### 为什么直接使用 containerd？\n\n对普通应用开发者来说，Docker 依然是本地开发和测试的首选。但对于构建云平台、自动化流水线或深度管理 Kubernetes 集群的系统工程师来说，直接使用 containerd 可以带来：\n- **更高的性能与更少的开销**：去掉了 Docker Daemon 等附加组件的资源占用，链路更短。\n- **更强的稳定性**：作为专注于运行时的底层组件，它的核心功能极为稳定且更新受控。\n- **直接符合 Kubernetes CRI 标准**：在生产级 Kubernetes 集群中作为标准配置。\n\n### 基础用法与工具介绍\n\n不同于 `docker` 命令行工具，containerd 提供了不同的客户端来满足不同的使用场景：\n\n- **ctr**：containerd 自带的调试用客户端。它功能比较基础，主要用于开发者在开发 containerd 时进行快速调试，一般不作为最终用户的日常管理工具。\n- **crictl**：Kubernetes 提供的 CRI 命令行工具。它用于排查 Kubernetes 节点上的容器和沙箱（Pod）问题。\n- **nerdctl**：这是一个由系统社区成员（主要是 containerd 项目的维护者）开发的，完全兼容 Docker CLI 体验的 containerd 命令行客户端。对于习惯了 `docker run/ps/build` 命令的用户来说，`nerdctl` 可以作为直接操作 containerd 的理想替代品，并且它还支持直接构建镜像（依赖 BuildKit）。\n\n#### nerdctl 使用示例\n\n安装完 containerd 和 nerdctl 后，你可以体验到几乎与 Docker 完全一致的命令行：\n\n```bash\n# 启动一个 nginx 容器\n$ nerdctl run -d -p 8080:80 --name my-nginx nginx:alpine\n\n# 查看运行中的容器\n$ nerdctl ps\n\n# 查看本地镜像\n$ nerdctl images\n```\n\n对于那些希望在生产服务器上剥离 Docker 庞大体积，但又想要保留类似 Docker 方便的命令行体验的用户，`containerd` + `nerdctl` 是一个极佳的组合。\n"
  },
  {
    "path": "17_ecosystem/17.7_secure_runtime.md",
    "content": "## 17.7 安全容器运行时\n\n本节介绍容器技术生态中的安全运行时机制，主要探讨在隔离性和安全性上比标准 Linux 容器更进一步的方案，重点介绍 Kata Containers 和 gVisor。\n\n### 为什么需要安全容器？\n\n标准的 Linux 容器（如 Docker、Podman 或基础的 containerd/runc 所提供的）依赖于 Linux 内核的 **Namespace（命名空间）** 和 **Cgroups（控制组）** 来实现进程级别的隔离与资源限制。这种轻量级的虚拟化方式共享同一个宿主机的内核。\n\n尽管这种方式在性能和启动速度上拥有巨大优势，但也带来了一个显著的缺点：**隔离性（Isolation）不足**。如果某个容器内的恶意进程利用了宿主机内核的漏洞完成了越狱（Privilege Escalation），它将对整个宿主机以及其上运行的所有其他容器造成毁灭性威胁。\n\n如果在公有云环境（多租户场景）或运行不可信的第三方代码时，共享内核显然是不够安全的。为了解决这一问题，社区推出了“安全容器（Secure Containers/Sandboxed Containers）”的概念。安全容器的核心理念是：提供类似虚拟机的强隔离性，同时保持类似容器的轻量、快速启动和标准化管理。\n\n### 什么是 Kata Containers？\n\n[Kata Containers](https://katacontainers.io/) 是一个开源项目，由 OpenStack Foundation（现更名为 OpenInfra Foundation）托管。它将早期的两个项目——Intel Clear Containers 和 Hyper runV 结合而成。\n\nKata Containers 的核心思路是：**使用轻量级的虚拟机（Lightweight VM）来运行每一个容器或者 Pod**。\n\n#### 工作原理\n\n当使用 Kata Containers 时，它不是在宿主机上启动一个普通的独立进程，而是调用一个精简高度优化的虚拟机管理程序（如 QEMU、Firecracker 或 Cloud Hypervisor）启动一个小型的虚拟机。容器内的应用进程运行在这个虚拟机拥有独立特制内核的沙箱中。\n\n- **高度隔离**：因为拥有自己的独立内核，即使容器内的应用利用内核漏洞溢出，它也只能破坏虚拟机虚拟出来的内核，根本无法触及宿主机真正的内核。\n- **兼容性**：Kata Containers 完全实现了 OCI（Open Container Initiative）规范和 CRI（Container Runtime Interface）标准。这意味着它可以作为 `containerd` 或 `Docker` 的底层运行时无缝替换默认的 `runc`。\n- **与 Kubernetes 集成**：在 Kubernetes 中，你可以为一个特定的 Pod 指定 `runtimeClassName: kata`，让高敏感的任务自动运行在虚拟机级别的隔离环境中。\n\n### 什么是 gVisor？\n\n[gVisor](https://gvisor.dev/) 是由 Google 开发并开源的一种不同流派的沙箱容器运行时方案。\n\n它采取了与 Kata Containers 完全不同的技术路线，它不是启动完整的虚拟机，而是提供了一个 **应用态内核（User-space Kernel）**。\n\n#### 工作原理\n\ngVisor 的核心组件是一个名为 **Sentry** 的用户空间进程。Sentry 扮演了一个“内核代理”的角色。\n\n- 当容器内的应用想要进行系统调用（System Call，比如读写文件、网络通信）时，这些调用会被 Sentry 拦截并进行一层虚拟化处理，然后再由 Sentry 把经过安全过滤和转换的请求转发给宿主机内核。\n- 因为 Sentry 在用户态实现了一套 Linux 系统调用接口，它极大减少了应用直接接触底层操作系统内核的表面积。这样就有效防御了利用底层内核漏洞突破隔离的攻击方式。\n- gVisor 同样兼容 OCI 规范，其核心运行时组件称为 `runsc`，可以作为底层运行时与 Docker 或 Kubernetes 进行集成。\n\n### 总结与对比\n\n| 特性 | 标准 Linux 容器 (runc) | Kata Containers | gVisor (runsc) |\n| :--- | :--- | :--- | :--- |\n| **隔离技术** | Namespace & Cgroups | 轻量级虚拟机 (独立内核) | 用户态内核 (系统调用拦截) |\n| **安全性** | 较低（共享宿主机内核） | 极高（硬件级虚拟化隔离） | 高（减少内核攻击面） |\n| **性能开销** | 极小（原生进程） | 中等（因轻量化而优于传统 VM） | 中等到较高（取决于系统调用频率开销） |\n| **启动速度** | 极快（毫秒/秒级） | 快（秒级之内） | 快（接近原生容器） |\n| **兼容性** | 完美（所有系统调用均支持原始实现） | 极好（拥有完整内核） | 好（但在极少数复杂的未被拦截支持的系统调用中可能报错） |\n\n如今，诸如 AWS 这样的云厂商也推出了针对无服务器容器功能（Serverless Containers）的高度优化的轻量级虚拟机管理器 **Firecracker**，可以看作是安全容器生态中与 Kata 类似方案的底层基石。\n\n对于普通的微服务开发来说可能不需要考虑使用安全容器，但在提供多租户平台即服务（PaaS）、运行无状态边缘计算函数（FaaS）等对安全隔离要求极高的场景中，以 Kata Containers 和 gVisor 为代表的安全容器技术展现出了巨大的价值。\n"
  },
  {
    "path": "17_ecosystem/17.8_wasm.md",
    "content": "## 17.8 WebAssembly 与容器\n\n本节介绍 WebAssembly (简写为 Wasm) 以及它为何成为现代容器生态中备受瞩目的前沿技术路线。\n\n### 什么是 WebAssembly？\n\n[WebAssembly (Wasm)](https://webassembly.org/) 最初是由 W3C 主导的一项为了解决网页中 JavaScript 性能瓶颈而发明的技术标准。它是一种小体积的、加载极快的、提供安全沙盒的高效二进制格式的指令集架构。通过将 C/C++、Rust、Go 等高级语言编译成 `.wasm` 格式，这些程序可以直接在所有现代的浏览器中以接近原生代码的速度安全地运行。\n\n然而，一项原本用于前端领域的技术，为何如今却与容器云计算生态产生了强烈的化学反应？\n\n因为开源社区很快意识到，Wasm 所具备的核心特性完美契合了云原生后端的诉求。人们制定了诸如 **WASI (WebAssembly System Interface)** 这样的标准，将其能力从浏览器扩展到了服务器端操作系统上。\n\n### Wasm 与容器特性的完美契合\n\n将 Wasm 应用于服务端时，它展现出了一些可能比传统 Linux 容器更为优异的特性：\n\n1. **极速的冷启动性能**：\n   传统 Linux 容器虽然比虚拟机轻量很多，但它启动依然需要建立 Namespace 和 Cgroups 以及一整套文件系统，通常需要近百毫秒到几秒。而 Wasm 模块不需要这样庞杂的环境初始化，能够在几毫秒之内完成从加载到执行，这对于无服务器函数（Serverless Functions）而言是巨大的提升。\n\n2. **跨平台性 (Write Once, Run Anywhere)**：\n   我们知道 Docker 等容器通常是绑定架构的。如果是 x86_64 平台上打出的镜像，通常无法直接在基于 ARM 的系统（如苹果 M 系列芯片甚至树莓派）上直接运行原生代码，除非使用 QEMU 进行低效转译或者专门构建多架构（Multi-arch）镜像和 manifests。\n   而 Wasm 二进制本身是平台无关的平台中间语言代码形式！你编译出来的一份 `.wasm` 可执行模块，不用做任何修改，就可以在 x86 的 Linux 服务器、ARM 的边缘设备、甚至是 Windows 和 macOS 上直接通过 Wasm 运行时来驱动和执行。真正做到了“编写一次，到处运行”。\n\n3. **天然的安全沙箱机制**：\n   Wasm 设计之初就是在不被信任的浏览器沙盒环境中运行未知代码的，因此执行环境非常安全，采用了极好的能力导向安全模型。应用只能访问它被明确授予权限的文件或能力。其默认安全隔离性比起依靠 Namespaces 机制的共享内核的 Linux 容器更加坚固。\n\n4. **极小的包体积**：\n   Linux 容器需要打包一整套依赖甚至是简化的 OS 根目录结构。而一个编译好的功能完善的 Wasm 模块体积常常不到几兆甚至仅仅几十 Kb，极大地加快了存储及网络利用效率。\n\n### 当 Docker 遇上 WebAssembly\n\n在现代的容器生态系统中，Wasm 并不被看作是要被取代传统的 Docker 或者 Kubernetes 的技术，而是成为了一种 **与 Linux 容器互补并且共生** 的全新工作负载类型。\n\n目前，这通过 OCI (Open Container Initiative) 和 CRI 标准实现了集成上的统一：\n\n1. **将 Wasm 打包为 OCI 镜像**：虽然内容并非传统的 Linux RootFS，但是通过标准化的打包工具同样可以将应用程序及其 `.wasm` 构建结果转化为一个可以被推送至 Docker Hub 或其他 registry 的标准化镜像规范。\n2. **通过容器运行时直接执行**：Docker 已经与如 [WasmEdge](https://wasmedge.org/) 和 [Spin](https://developer.fermyon.com/spin) 等高性能的企业级 Wasm 运行时进行了官方集成合作。\n\n如今在 Docker Desktop 或者集成了 `containerd` 的环境中，我们可以十分简易地以类似普通镜像的形式去拉取并运行一个基于 Wasm 编译的后端服务（通过指定相应的 `--platform` 或者是特别的 `--runtime=io.containerd.wasmedge.v1` 设置），将其如同对待一个标准应用进程一样让 Docker 为其接管日志、配置相关的网络端口映射，甚至通过 Docker Compose 将一个普通的数据库容器实例与一个 Wasm 微服务实例协同起来混布。\n\n### 总结\n\n随着技术底座如 WASI 规范不断的成熟完善（例如提供完备的套接字网络支持以及系统资源访问支持），我们有理由相信不仅是边缘计算与无服务器调用，会有越来越多对于速度和安全性有极高指标要求的云原生后端微服务开始采用这一颠覆传统边界的轻量级“微型智能体”架构。在可见的将来，Wasm 势必成为云原生与 Docker 生态的重要拼图。\n"
  },
  {
    "path": "17_ecosystem/README.md",
    "content": "# 第十七章 容器其它生态\n\n本章将介绍 Docker 和 Kubernetes 之外的容器生态技术。\n\n## 本章内容\n\n* [Fedora CoreOS 简介](17.1_coreos_intro.md)\n  * 专为容器化工作负载设计的操作系统。\n\n* [Fedora CoreOS 安装与配置](17.2_coreos_install.md)\n  * CoreOS 的安装方式与基本配置。\n\n* [Podman](17.3_podman.md)\n  * 兼容 Docker CLI 的下一代无守护进程容器引擎。\n\n* [Buildah](17.4_buildah.md)\n  * 无需守护进程的 OCI 容器镜像构建工具。\n\n* [Skopeo](17.5_skopeo.md)\n  * 远程检查和管理容器镜像的利器。\n\n* [containerd](17.6_containerd.md)\n  * 作为现代容器生态基石的核心容器运行时。\n\n* [安全容器运行时](17.7_secure_runtime.md)\n  * 通过提供更强隔离性来保证安全的技术方案（如 Kata Containers、gVisor）。\n\n* [WebAssembly](17.8_wasm.md)\n  * 一种极具潜力的轻量级跨平台二进制指令格式。\n"
  },
  {
    "path": "17_ecosystem/coreos_README.md",
    "content": "## Fedora CoreOS\n\n`CoreOS` 是一个专门为安全和大规模运行容器化工作负载而构建的新 Fedora 版本，它继承了 Fedora Atomic Host 和 CoreOS Container Linux 的优势。\n\n`CoreOS` 的安装文件和运行依赖非常小，它提供了精简的 Linux 系统。它使用 Linux 容器在更高的抽象层来管理你的服务，而不是通过常规的包管理工具 `yum` 或 `apt` 来安装包。\n\n同时，`CoreOS` 几乎可以运行在任何平台：`VirtualBox` `Amazon EC2` `QEMU/KVM` `VMware` `Bare Metal` 和 `OpenStack` 等。\n"
  },
  {
    "path": "17_ecosystem/demo/example.fcc",
    "content": "variant: fcos\nversion: 1.0.0\npasswd:\n  users:\n    - name: core\n      ssh_authorized_keys:\n        - ssh-rsa AAAA...\n"
  },
  {
    "path": "17_ecosystem/summary.md",
    "content": "## 本章小结\n\nDocker 并非容器生态的唯一选择，了解其他工具有助于根据场景做出合适的技术选型。\n\n| 项目 | 定位 | 特点 |\n|------|------|------|\n| **Fedora CoreOS** | 容器化操作系统 | 自动更新、不可变基础设施、专为运行容器设计 |\n| **Podman** | 容器管理引擎 | 无守护进程、兼容 Docker CLI、支持 Rootless 模式、支持原生 Pod |\n| **Buildah** | 镜像构建工具 | Daemonless 工作模式、灵活的脚本化构建能力 |\n| **Skopeo** | 镜像仓库管理 | 无需拉取即可检查远端镜像、跨仓库/格式无缝迁移镜像 |\n| **containerd** | 核心底层运行时 | 稳定高效、符合 CRI 规范、是 Docker 的基石之一 |\n| **安全容器** | 强隔离沙箱运行 | 利用轻量级虚拟机 (Kata) 或用户态内核 (gVisor) 防止越狱，极其安全 |\n| **Wasm** | 新型工作负载 | 体积极小、冷启动超快且具备跨平台及高度特征化沙盒能力的后端架构新方向 |\n\n### Podman vs Docker\n\n两者的主要区别：\n\n| 对比项 | Docker | Podman |\n|--------|--------|--------|\n| **守护进程** | 需要 dockerd | 无需守护进程 |\n| **权限** | 默认需要 root | 原生支持 Rootless |\n| **CLI 兼容** | - | 与 Docker 命令兼容 |\n| **Pod 支持** | 不支持 | 原生支持 Pod 概念 |\n| **Compose** | docker compose | podman-compose 或兼容模式 |\n\n### 延伸阅读\n\n- [底层实现](../12_implementation/README.md)：容器技术的内核基础\n- [安全](../18_security/README.md)：容器安全实践\n---\n\n> 📝 **发现错误或有改进建议？** 欢迎提交 [Issue](https://github.com/yeasy/docker_practice/issues) 或 [PR](https://github.com/yeasy/docker_practice/pulls)。\n"
  },
  {
    "path": "18_security/18.1_kernel_ns.md",
    "content": "## 18.1 内核命名空间\n\n命名空间 (Namespace) 是 Linux 容器隔离的基础，它确保了容器内的进程无法直接干扰主机或其他容器。虽然在本书第 12 章中我们已经从底层实现的角度介绍了 Namespace，但在本节中，我们将重点探讨其 **安全意义** 及相关配置。\n\n### 18.1.1 隔离的安全本质\n\nDocker 守护进程在启动容器时，会在后台为容器创建一套独立的命名空间。命名空间提供了最基础也是最直接的隔离：\n\n- **PID Namespace**：防止容器内的进程查看或终止宿主机或其他容器的进程。恶意攻击者即使在容器内获得了 root 权限，也无法通过 `kill` 命令影响宿主机上的关键服务。\n- **NET Namespace**：每个容器都有自己独立的网络栈。如果没有显式地进行端口映射或将容器连接到同一网络，容器之间无法网络互通，从而限制了横向移动的能力。\n- **MNT Namespace**：为容器提供独立的文件系统视图。这可以防止容器不经意或恶意地修改宿主机的重要系统文件（如 `/etc/passwd`）。\n\n### 18.1.2 命名空间不是绝对安全的护城河\n\n尽管命名空间提供了很好的隔离性，但我们必须认识到：**所有的容器依然共享同一个宿主机的 Linux 内核**。\n\n这意味着，一旦宿主机的内核存在提权漏洞（如著名的 Dirty COW 漏洞），攻击者有可能通过突破 Namespace 的限制，直接在内核层面执行恶意代码，从而实现“容器逃逸”。\n\n> [!WARNING]\n> 为了缓解内核漏洞带来的威胁，生产环境务必保持宿主机 Linux 内核的及时修补与更新，或者借助诸如 gVisor、Kata Containers 等提供了独立内核的安全容器技术。\n\n通过命名空间，Docker 也能限制进程从外部环境获取信息。\n例如，由于进程环境被隔离，进程在内部其实是无法感知到外部宿主机的存在的。它既不能获取其他容器的进程列表，也无法通过网络与其他系统进行交互（除非经过配置）。\n\n### 18.1.3 用户命名空间与提权防护\n\n在所有的 Namespace 中，**User Namespace** 对安全的影响尤为关键。\n\n在默认情况下，容器内的 `root` 用户（UID=0）就是宿主机上的 `root` 用户。如果攻击者设法突破了容器的其他隔离机制获取了宿主机的访问权限，他将拥有宿主机的最高系统权限。\n\n通过启用 **User Namespace Remapping (用户命名空间映射)**，我们可以将容器内的 `root` 用户映射到宿主机上的一个无特权普通用户。\n\n#### 如何配置 User Namespace\n\n要在 Docker 服务端启用这一特性，需要修改 Docker 的配置文件 `/etc/docker/daemon.json`。\n\n1. **设置映射策略**\n\n编辑配置文件，添加 `userns-remap` 配置项：\n\n```json\n{\n  \"userns-remap\": \"default\"\n}\n```\n\n使用 `default` 值时，Docker 会自动在宿主机上创建一个名为 `dockremap` 的用户和用户组。\n\n2. **验证子 UID 和子 GID 分配**\n\nDocker 会通过 `/etc/subuid` 和 `/etc/subgid` 文件为 `dockremap` 分配一个高位的 UID 范围：\n\n```bash\n$ cat /etc/subuid\ndockremap:165536:65536\n```\n这意味着：容器内的 UID `0`（root 用户）在宿主机上实际被映射成了 UID `165536`。如果是容器内的 UID `1`，对应宿主机的 `165537`，以此类推。\n\n3. **重启 Docker 守护进程**\n\n```bash\n$ sudo systemctl restart docker\n```\n\n#### 验证映射效果\n\n我们可以运行一个简单的容器并执行 `sleep` 命令，同时在宿主机上观察进程的所有者：\n\n```bash\n## 在容器内以 root 身份运行\n$ docker run -d --name userns_test alpine sleep 3600\n\n## 在宿主机上查看该 sleep 进程\n$ ps aux | grep sleep\n165536    12345  0.0  0.0   1568     4 ?        Ss   14:20   0:00 sleep 3600\n```\n\n你会发现，尽管在容器内该进程是由 root 启动的，但在宿主机上，它的属主是 `165536`（一个完全没有特权的用户）。\n\n> [!TIP]\n> 启用 User Namespace 会对容器共享宿主机数据卷（Bind Mount）产生权限影响。你需要确保映射后的高位 UID 对宿主机上的挂载目录具有合适的读写权限。\n\n### 18.1.4 总结\n\n内核命名空间从 Linux 2.6.15 版本 (2006 年) 被引入，十余年间，这些机制的可靠性在诸多大型生产系统中被实践验证。通过合理利用命名空间（尤其是 User Namespace），可以极大地收窄攻击面，显著提升容器部署的安全性。\n"
  },
  {
    "path": "18_security/18.2_control_group.md",
    "content": "## 18.2 控制组\n\n控制组 (Cgroups) 是 Linux 容器机制的另外一个关键组件。如果说命名空间 (Namespace) 决定了容器能 **看到** 什么，那么控制组就决定了容器能 **使用** 多少资源。\n\n在安全领域中，资源的不可用性本身就是一种安全威胁。控制组负责实现资源的审计和限制，这对于抵御资源耗尽型攻击（如拒绝服务攻击 DoS）至关重要。\n\n### 18.2.1 为什么资源限制关乎安全？\n\n默认情况下，Docker 容器对系统资源的使用是没有限制的：一个容器理论上可以使用宿主机所有的 CPU 计算能力、吃光所有的内存、耗尽所有的系统 PID。\n\n想象一下以下场景：\n- 一个恶意用户向你暴露在公网的应用发起海量并发请求。\n- 应用程序逻辑中存在内存泄漏漏洞。\n- 黑客在入侵容器后，在里面运行了挖矿木马程序。\n\n如果没有 Cgroups 的限制，某个容器内的异常行为（或恶意攻击）将会榨干宿主机的资源，导致宿主机上其他健康的容器甚至 Docker 守护进程自身因为 OOM（Out Of Memory）崩溃或 CPU 饥饿而停止响应。\n\n### 18.2.2 核心资源限制实战\n\n为了确保多租户平台（如公有或私有的 PaaS 平台）的稳定性，或者在生产环境防止服务级联故障，我们要养成在启动容器时 **显式声明资源上限** 的习惯。\n\n#### 1. 内存限制\n\n限制内存可以防止应用程序因内存泄漏或恶意载荷导致宿主机 OOM。\n\n**关键参数：**\n- `-m, --memory=\"\"`：硬限制，容器可使用的最大内存量。\n- `--memory-swap=\"\"`：限制容器可使用的内存与 Swap 总量。\n\n**实战示例：**\n\n限制容器最多只能使用 512MB 内存，并且禁用 Swap（将 memory 和 memory-swap 设置成一样的值即可）：\n\n```bash\n$ docker run -d \\\n  --name web_app \\\n  --memory=\"512m\" \\\n  --memory-swap=\"512m\" \\\n  nginx:alpine\n```\n\n如果该容器内的应用尝试分配超过 512MB 的内存，该进程将会被内核的 OOM Killer 杀掉，但绝不会波及到宿主机的其他部分。\n\n#### 2. CPU 限制\n\n限制 CPU 可以防止个别计算密集型的容器垄断 CPU 时间片，保证系统的调度公平性。\n\n**关键参数：**\n- `--cpus=<value>`：指定容器可以使用的 CPU 核心数量（可以是小数）。\n- `-c, --cpu-shares=0`：软限制，设置容器使用 CPU 的相对权重（默认是 1024）。\n\n**实战示例：**\n\n限制容器最多使用 1.5 个 CPU 核心的算力：\n\n```bash\n$ docker run -d \\\n  --name worker_app \\\n  --cpus=\"1.5\" \\\n  busybox \\\n  md5sum /dev/urandom\n```\n\n即使上面的命令是一个死循环的哈希计算进程，容器也永远无法吃满双核 CPU 系统的全部算力。\n\n#### 3. 进程数限制\n\n进程炸弹（Fork Bomb）是一种典型的拒绝服务攻击方式，它通过不断 `fork()` 新进程来耗尽系统的进程表条目，导致系统无法创建任何新任务。\n\n**关键参数：**\n- `--pids-limit=<number>`：限制容器内允许创建的最大进程数。\n\n**实战示例：**\n\n一个常规的 Web 服务进程数通常在几十到上百之间。我们可以设定一个合理的上限来防范 Fork 炸弹：\n\n```bash\n$ docker run -d \\\n  --name app_service \\\n  --pids-limit=100 \\\n  python:alpine python app.py\n```\n\n当容器内的进程总数达到 100 时，任何尝试派生新进程的操作都会失败并返回 `Resource temporarily unavailable`，从而挫败相关的攻击行为。\n\n### 18.2.3 最佳实践建议\n\n在生产环境中，不仅要在单机使用 Docker 命令时设置这些参数，更应当在集群编排工具中将资源配额制度化。\n\n例如，在 Kubernetes 中，强烈建议为每个 Pod 设置 `requests` 和 `limits`：\n\n```yaml\nresources:\n  requests:\n    memory: \"256Mi\"\n    cpu: \"250m\"\n  limits:\n    memory: \"512Mi\"\n    cpu: \"500m\"\n```\n\n通过 Cgroups 的资源边界控制，你可以从根本上切断一条导致整个系统雪崩的脆弱链路。这也进一步使得 Docker 以及容器技术成为了现代高可用服务的基础设施首选。\n"
  },
  {
    "path": "18_security/18.3_daemon_sec.md",
    "content": "## 18.3 服务端防护\n\nDocker 守护进程（`dockerd`）是容器生命周期的核心驱动力。默认情况下，Docker 服务的运行需要极高的系统特权（root 权限），因此其安全性关系到整台宿主机的生死存亡。\n\n如果 Docker 守护进程的访问控制没有做好，恶意攻击者可以通过 Docker API 轻易地启动一个特权容器，并将宿主机的根目录（`/`）挂载到容器中，从而完全接管服务器。\n\n为了加强对服务端的保护，我们需要从访问控制、通信加密和权限最小化三个维度进行加固。\n\n### 18.3.1 限制 API 访问\n\nDocker 客户端（`docker` 命令）通过 REST API 与守护进程进行通信。\n\n在早期版本中，Docker 有时会绑定在 `127.0.0.1` 的 TCP 套接字上，但这容易遭遇跨站脚本（跨协议）攻击。现在的发行版默认使用 Unix Domain Socket（`/var/run/docker.sock`）并依赖文件系统的权限控制。\n\n#### 原则 1：决不可将无认证的 TCP 端口暴露在公网\n\n这是最常见的 Docker 被入侵抓去挖矿的原因！绝不能在没有任何安全控制的情况下强行开启 `-H tcp://0.0.0.0:2375`。\n\n如果业务确实需要远程访问 Docker 守护进程，**必须启用 TLS 认证机制**，让客户端和服务端互相进行证书校验。\n\n#### 开启 TLS 认证双向加密\n\n利用安全机制，确保只有经过授权的主机网络，并在强证书保护下进行通信：\n\n1. 首先使用 `openssl` 或基于本地 CA 工具生成一套客户端与服务器的证书。\n2. 配置 Docker 守护进程（通常是 `daemon.json` 或 `dockerd` 启动参数），指定证书路径：\n\n```bash\ndockerd \\\n  --tlsverify \\\n  --tlscacert=ca.pem \\\n  --tlscert=server-cert.pem \\\n  --tlskey=server-key.pem \\\n  -H=0.0.0.0:2376\n```\n\n3. 客户端想要连接时，也必须出示客户端证书。\n\n> [!TIP]\n> 配置 TLS 生成证书的完整步骤可以查阅 [Docker 官方 TLS 文档](https://docs.docker.com/engine/security/protect-access/)。在现代编排系统（如 Kubernetes）中，通常会有自动化方案管理这些凭据。\n\n### 18.3.2 保护本地 Socket 访问\n\n哪怕不开启网络端口，本地的 `/var/run/docker.sock` 也需要谨慎对待。\n\n任何被授予该 Socket 读写权限的用户（通常被加入 `docker` 用户组），等同于拥有了对宿主机的零成本提权途径，即“无需密码的免密 `sudo` 权限”。\n\n> [!CAUTION]\n> 永远不要将不可信的普通用户加入到 `docker` 用户组中。同样，在容器编排时尽量避免将宿主机的 `/var/run/docker.sock` 直接映射给普通容器使用，这种模式被称为 Docker-in-Docker (DinD) 或 Docker-out-of-Docker (DooD)，存在极高的越权风险。\n\n### 18.3.3 Rootless 模式：非特权运行\n\n为了从根本上解决“拥有 Docker socket 就是 root”的问题，Docker 在近年推出了 **Rootless 模式**。\n\nRootless 模式允许在完全局限于非 `root` 用户的环境中运行 Docker 守护进程（`dockerd`）和容器。该模式利用了现代 Linux 内核的 User Namespace 技术和非特权网络命名空间实现。\n\n#### 配置运行 Rootless Docker\n\n要在非 root 环境中运行 Docker，只需要简单几步：\n\n1. 安装必要的依赖（通常是 `uidmap` 工具包以便系统支持 `newuidmap` 和 `newgidmap`）：\n```bash\n$ sudo apt-get install uidmap\n```\n\n2. 切换到一个没有任何 `sudo` 权限的普通用户（假设用户名为 `testuser`）：\n```bash\n$ su - testuser\n```\n\n3. 运行 Docker 官方提供的 Rootless 安装脚本：\n```bash\n$ curl -fsSL https://get.docker.com/rootless | sh\n```\n\n4. 配置环境变量指向新创建的私有 socket：\n```bash\n$ export DOCKER_HOST=unix:///run/user/1000/docker.sock\n$ docker version\n```\n\n安装并暴露相应的配置后，该用户的环境将能独立启动属于他自己的 Docker Daemon。即使由于某些未知 0-Day 漏洞使得攻击者突破了容器，他们也只会受限于 `testuser` 这个非特权用户所在的有限系统环境内。\n\n### 18.3.4 结语\n\n保障 Docker 服务端的安全主要是做减法：关闭不必要的网络监听点，严管 Socket 访问权限。而一旦基础系统条件允许，**毫不犹豫地在生产环境启用 Rootless 模式** 将是一项划算的安全加固选择。\n"
  },
  {
    "path": "18_security/18.4_kernel_capability.md",
    "content": "## 18.4 内核能力机制\n\n传统 Linux 的权限模型非常粗放：进程分为“特权进程”（以 root 用户 `UID 0` 运行）和“非特权进程”（其他 UID 运行）。这带来了一个致命问题——只要一个后台服务需要一个微小的特权（例如绑定低于 1024 的端口），就必须被赋予所有的 root 权限。一旦该服务被攻陷，系统便会全面沦陷。\n\n为了解决这一问题，Linux 引入了 **能力机制（Capabilities）**。它将传统的全能 root 权限划分为几十个细粒度的操作能力。\n\n### 18.4.1 容器内置的 Capability 白名单\n\n在默认情况下，即便一个容器是在以 `root` 用户运行，Docker 也只为其内核授予了所有可用能力中的 **一小部分“白名单”能力**。\n\n常见的 Linux Capabilities 包含：\n- `CAP_CHOWN`: 修改文件所有者。\n- `CAP_NET_BIND_SERVICE`: 绑定特权端口（即 1024 以下的端口）。\n- `CAP_NET_ADMIN`: 网络管理的最高权限（例如调整路由配置，设置防火墙规则等）。\n- `CAP_SYS_ADMIN`: 被誉为“Linux 内核的特权网管”，允许各种高危操作（挂载磁盘、访问敏感设备等）。\n\n为了在 **“最小特权原则”** 的指导下加强安全，Docker 默认 **移除了** 大量可能导致容器大范围破坏宿主机的能力，例如：\n* 完全禁止了任何通过 `CAP_SYS_ADMIN` 进行的核心挂载或设备操作。\n* 禁止修改内核模块。\n* 禁止直接访问硬件套接字。\n\n这种“非完整”的 root 用户能保证大部分应用在拥有其所需权限的同时，把恶意行为对系统的影响降到最低。\n\n### 18.4.2 实战：添加与剥夺能力\n\n当启动一个 Docker 容器时，我们可以利用 `--cap-add`（增加特权）和 `--cap-drop`（剥夺特权）两个参数精细地控制进程环境。\n\n#### 实战场景一：构建极限安全的 Web 靶机\n\n假设你正在提供一个公共的 Web 容器。你不希望里面的任何恶意脚本修改进程权限或者创建设备节点，你可以通过命令先移除 **所有** 默认能力，然后再按需授权该守护进程一个仅仅能绑端口的能力。\n\n```bash\n$ docker run -d \\\n  --name max_secure_web \\\n  --cap-drop ALL \\\n  --cap-add NET_BIND_SERVICE \\\n  nginx:alpine\n```\n\n这里的 `--cap-drop ALL` 是实现“特权最小化”的最强杀手锏。此时，即便某黑客利用 0-Day 手段拿到了 Web 服务的容器 root Shell，当他试图改变任何不属于他自己的进程配置或者所有权时，系统都会报错拒绝访问。\n\n#### 实战场景二：需要捕获网络数据包的网络实验\n\n假设容器内的主程序是一个网络嗅探器（如 tshark 或 tcpdump），这显然不在 Docker 提供的默认白名单之内，因为该程序试图直接操纵底层网卡流量，会触发 Permission Denied。\n\n此时，我们需要给它适当补发缺失的部分核心管理能力：\n\n```bash\n$ docker run -it --rm \\\n  --name network_sniffer \\\n  --cap-add NET_ADMIN \\\n  --cap-add NET_RAW \\\n  tshark-image /bin/bash\n```\n\n我们只授予了所需的网络管理控制（NET_ADMIN）和侦听底层套接字的权限（NET_RAW），而免去了赋予整个容器终极杀器 `--privileged` 参数。\n\n> [!WARNING]\n> 大量开发人员遇到了“权限遭到拒绝”的错误时，往往习惯性图省事添加 `--privileged` 这个核选项。但这将把 **宿主机上一切特权和所有访问设备完全投射给容器内的根用户**，其危险性等价于根本没有做隔离！请务必查明进程出错的实际原因，精准施加必要的隔离 `CAP_*` 能力。\n\n### 18.4.3 总结\n\n利用能力机制（Capabilities）是进行精细化系统级访问控制的关键一环。遵循“**白名单剥夺一切不必要权利（--cap-drop ALL）**”的极端配置并不过分，这将使得即便程序本身漏洞百出，攻击面也被死死压缩在一个几乎毫无后续伸展潜力的受限维度中。\n"
  },
  {
    "path": "18_security/18.5_other_feature.md",
    "content": "## 18.5 其它安全特性\n\n除了上述的命名空间、控制组以及能力机制，Linux 内核与云原生生态还提供了大量安全增强功能，它们共同筑成了一道防御纵深的“马奇诺防线”。本节主要介绍强制访问控制、系统调用拦截以及自动化的容器漏洞扫描技术。\n\n### 18.5.1 系统调用过滤\n\n`Seccomp`（Secure Computing mode）是 Linux 内核的一个安全机制，用于限制进程能够发起的系统调用数量。\n\n一个普通的 Linux 内核提供了 300 多个系统调用，而一个正常运行的容器化应用（例如 Nginx 服务）通常只会用到几十个调用，这就给攻击者留下了大量的闲置入口点来进行内核层的缓冲区溢出攻击。\n\nDocker 默认启用了 Seccomp 并利用预置的 [默认配置文件](https://github.com/moby/moby/blob/master/profiles/seccomp/default.json) 将可以利用的系统调用缩减到了不足一半（默认禁用了 44 个危险的统调用，比如修改时区或重启系统）。\n\n如果你对应用的系统调用特征了如指掌，你可以为容器定制专属规则。\n\n#### 实战：禁用 chmod 系统调用过滤\n\n首先，编写一个 `no-chmod.json` 的策略文件：\n```json\n{\n    \"defaultAction\": \"SCMP_ACT_ALLOW\",\n    \"syscalls\": [\n        {\n            \"name\": \"chmod\",\n            \"action\": \"SCMP_ACT_ERRNO\"\n        }\n    ]\n}\n```\n\n在启动时告诉 Docker 载入这套过滤配置：\n\n```bash\n$ docker run --rm -it \\\n  --security-opt seccomp=no-chmod.json \\\n  alpine sh\n\n/ # chmod 777 /etc/passwd\nchmod: /etc/passwd: Operation not permitted\n```\n应用只要被劫持进行越界尝试，其操作系统层命令便会立刻吃瘪。\n\n### 18.5.2 强制访问控制：AppArmor / SELinux\n\n传统的 Linux 模型遵循 DAC（自主访问控制），这意味着如果一个文件被赋予了全员读写权限（`777`），普通隔离下任何人便都能修改。但 **MAC（强制访问控制）** 技术，诸如 `AppArmor` (常用于 Ubuntu/Debian) 或 `SELinux` (常用于 CentOS/RHEL)，可以制定比“文件所有权”更宏观且优先的策略控制模块。\n\n在开启了上述机制的机器上：\n- **AppArmor**: Docker 为所有启动的应用加载了一个默认的 `docker-default` 模板文件，如果你的某些异常写行为（比如往特殊的内核心脏目录写入配置）不在 AppArmor 许可列表之上，即使拥有物理 Root，写入同样失败。\n- **SELinux**: 所有的 Docker 操作强制附加特殊上下文标识标签。就算把主机的 `/` 绑定给了黑客的某服务，黑客对不属于 Docker 可见的标签的文件进行读写尝试亦会被阻止。\n\n如果想为某些受信任应用施加特定的外部强化文件策略，可以通过如下方法指派规则表：\n\n```bash\n$ docker run --rm -it \\\n  --security-opt apparmor=custom-nginx-profile \\\n  nginx\n```\n\n### 18.5.3 容器镜像漏洞静态扫描\n\n现代防护的防御已经不仅仅在运行阶段，而向“左”延伸至了构建与分发时期控制。很多安全隐患并不是用户代码中的直接逻辑异常，而是打包环境或者引入库的基础 `APT` 安装层面潜伏了开源界众所周知的历史漏洞。\n\n#### 使用 Trivy 识别风险\n\n[Trivy](https://github.com/aquasecurity/trivy) 是由 Aqua Security 发行的一款针对容器技术的快速镜像漏洞扫描利器。在分发应用前通过它的扫描可以规避绝大多数风险。\n\n```bash\n## 如果使用本地命令行扫描容器镜像\n$ trivy image alpine:3.10\n\n2024-03-01T10:05:07.124Z\tINFO\tNumber of language-specific files: 1\n2024-03-01T10:05:07.124Z\tINFO\tDetecting vulnerabilities...\n\nalpine:3.10 (alpine 3.10.3)\n===========================\nTotal: 2 (UNKNOWN: 0, LOW: 0, MEDIUM: 1, HIGH: 1, CRITICAL: 0)\n\n+---------+------------------+----------+-------------------+---------------+---------------------------------------+\n| LIBRARY | VULNERABILITY ID | SEVERITY | INSTALLED VERSION | FIXED VERSION | TITLE                                 |\n+---------+------------------+----------+-------------------+---------------+---------------------------------------+\n| busybox | CVE-2022-28391   | HIGH     | 1.30.1-r3         | 1.30.1-r4     | busybox: out-of-bounds read in...     |\n...\n```\n\n只要确保所有上传给私有或公共仓库分发服务的产物先被引入至 CI/CD 流水线，如果出现 `HIGH` 及 `CRITICAL` 严重的报错记录强行阻拦部署，这本身便是构建环节极其有力的自动化安全大门保障。除 Trivy 外，最新的 Docker 版本也已内置支持官方扫描利刃 **Docker Scout**。\n\n### 18.5.4 容器核心层基石结语\n\n到这里，Docker 为保障宿主和容器界限安全的几个护城河：从 **资源剥离限制**(`Cgroups`) 到 **进程/网络/身份蒙蔽**(`Namespace`)、到 **特权能力回收**(`Capabilities`) 再到 **内核强制策略拦截管制**(`Seccomp`/`AppArmor`) 已悉数交代完毕。虽然绝没有“100% 免疫网络穿刺的防线”，只要开发者牢记 **权限最小化原则** ，容器的堡垒就可以做到令攻击者望洋兴叹。\n"
  },
  {
    "path": "18_security/18.6_image_security.md",
    "content": "## 18.6 容器镜像安全扫描与供应链安全\n\n在 DevOps 流程中，容器镜像安全已经成为不容忽视的关键环节。从开发、构建、存储到部署，镜像的整个生命周期都需要安全防护。本节深入讨论镜像漏洞扫描、软件物料清单（SBOM）、镜像签名验证等供应链安全实践。\n\n### 18.6.1 容器镜像漏洞扫描工具对比\n\n#### Trivy - 轻量级通用扫描器\n\nTrivy 是由 Aqua Security 开发的开源漏洞扫描器，以其轻量级、快速、准确而闻名，已成为业界标准。\n\n**优点：**\n- 零依赖，单个二进制文件\n- 扫描速度快（秒级）\n- 支持镜像、文件系统、Git 仓库多种扫描源\n- 数据库每日自动更新\n- 支持多种输出格式（JSON、表格、SBOM 等）\n\n**安装与基本使用：**\n\n```bash\n# 安装 Trivy\ncurl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin\n\n# 扫描本地镜像\ntrivy image nginx:latest\n\n# 生成 JSON 格式报告\ntrivy image -f json -o report.json nginx:latest\n\n# 扫描文件系统\ntrivy fs /path/to/project\n\n# 扫描 Git 仓库\ntrivy repo https://github.com/aquasecurity/trivy\n```\n\n**在 CI/CD 中集成：**\n\n```bash\n# 设置严重程度过滤\ntrivy image --severity HIGH,CRITICAL \\\n  --exit-code 1 \\\n  myregistry.com/myapp:v1.0.0\n```\n\n#### Grype - 支持多种软件包的扫描器\n\nGrype 由 Anchore 开发，支持更广泛的软件包管理器和语言。\n\n**优点：**\n- 支持 Java、Python、Go、Ruby、JavaScript 等多种语言的依赖检测\n- 与 Syft（SBOM 生成器）配合效果好\n- 可自定义漏洞数据库源\n- 支持离线扫描模式\n\n**安装与使用：**\n\n```bash\n# 安装 Grype\ncurl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin\n\n# 扫描镜像\ngrype docker:nginx:latest\n\n# 与 Syft 配合生成 SBOM\nsyft docker:nginx:latest -o json > sbom.json\ngrype sbom:sbom.json\n\n# 扫描特定目录\ngrype dir:/path/to/app\n```\n\n#### Snyk - 完整的安全平台\n\nSnyk 提供了商业级的安全扫描服务，特别适合企业环境。\n\n**特点：**\n- 支持开源漏洞和许可证扫描\n- 与多个 Git 平台深度集成（GitHub、GitLab、Bitbucket）\n- 提供修复建议和自动化修复 PR\n- 支持 Kubernetes 部署后安全监控\n\n**基本使用：**\n\n```bash\n# 安装 Snyk CLI\nnpm install -g snyk\n\n# 认证\nsnyk auth\n\n# 扫描镜像\nsnyk container test docker-archive://image.tar\n\n# 监控仓库\nsnyk monitor --docker\n```\n\n**工具对比表：**\n\n| 特性 | Trivy | Grype | Snyk |\n|------|-------|-------|------|\n| 零依赖 | ✓ | ✗ | ✗ |\n| 离线模式 | ✓ | ✓ | ✗ |\n| 许可证扫描 | ✗ | ✓ | ✓ |\n| 自动修复 | ✗ | ✗ | ✓ |\n| 开源免费 | ✓ | ✓ | 部分 |\n| IDE 集成 | ✓ | ✓ | ✓ |\n\n### 18.6.2 SBOM（软件物料清单）生成与管理\n\nSBOM（Software Bill of Materials）是一份详细列表，记录了软件中使用的所有组件、依赖库及其版本信息。SBOM 在供应链安全中至关重要，特别是在发现新的安全漏洞时，能快速定位受影响的应用。\n\n#### Syft - SBOM 生成工具\n\nSyft 是 Anchore 推出的专业 SBOM 生成工具。\n\n**安装：**\n\n```bash\ncurl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin\n```\n\n**生成 SBOM：**\n\n```bash\n# 从镜像生成 SBOM（多种格式）\nsyft docker:nginx:latest -o json > sbom.json\nsyft docker:nginx:latest -o spdx > sbom.spdx\nsyft docker:nginx:latest -o cyclonedx > sbom.xml\n\n# 从本地文件系统生成\nsyft dir:/path/to/app -o json > sbom.json\n\n# 从 OCI 镜像档案生成\nsyft oci-archive:image.tar -o json > sbom.json\n```\n\n#### CycloneDX 与 SPDX 格式\n\n两种主流的 SBOM 格式：\n\n**CycloneDX 格式示例：**\n\n```xml\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<bom xmlns=\"http://cyclonedx.org/schema/bom/1.4\" version=\"1\">\n  <components>\n    <component type=\"library\">\n      <name>openssl</name>\n      <version>1.1.1k</version>\n      <purl>pkg:deb/debian/openssl@1.1.1k-1+deb11u5</purl>\n    </component>\n    <component type=\"library\">\n      <name>curl</name>\n      <version>7.74.0-1.3+deb11u1</version>\n      <purl>pkg:deb/debian/curl@7.74.0-1.3+deb11u1</purl>\n    </component>\n  </components>\n</bom>\n```\n\n**SPDX 格式示例：**\n\n```json\n{\n  \"SPDXID\": \"SPDXRef-DOCUMENT\",\n  \"spdxVersion\": \"SPDX-2.2\",\n  \"creationInfo\": {\n    \"created\": \"2024-03-01T12:00:00Z\",\n    \"creators\": [\"Tool: syft\"]\n  },\n  \"packages\": [\n    {\n      \"SPDXID\": \"SPDXRef-Package-openssl\",\n      \"name\": \"openssl\",\n      \"versionInfo\": \"1.1.1k\",\n      \"downloadLocation\": \"NOASSERTION\"\n    }\n  ]\n}\n```\n\n#### SBOM 的应用场景\n\n**漏洞关联：**\n\n当新的 CVE 被发现时，可快速查询受影响的应用：\n\n```bash\n# 使用 Grype 针对 SBOM 进行漏洞扫描\ngrype sbom:sbom.json --add-cpes-if-none\n```\n\n**合规性报告：**\n\n将 SBOM 保存为构建产物，用于审计和合规性检查。\n\n**依赖升级决策：**\n\n通过分析 SBOM 中的依赖版本，制定安全升级计划。\n\n### 18.6.3 镜像签名与验证\n\n镜像签名确保镜像的来源可信且未被篡改。两种主流方案是 Cosign 和 Notary。\n\n#### Cosign - 现代签名解决方案\n\nCosign 是 Sigstore 项目的核心工具，支持无密钥签名，适合现代 CI/CD 流程。\n\n**安装：**\n\n```bash\nwget https://github.com/sigstore/cosign/releases/latest/download/cosign-linux-amd64\nchmod +x cosign-linux-amd64\nsudo mv cosign-linux-amd64 /usr/local/bin/cosign\n```\n\n**生成密钥对（传统方式）：**\n\n```bash\ncosign generate-key-pair\n# 生成 cosign.key 和 cosign.pub\n```\n\n**签名镜像：**\n\n```bash\n# 使用私钥签名（推送到仓库前）\ncosign sign --key cosign.key myregistry.com/myapp:v1.0.0\n\n# 系统会提示输入私钥密码\n```\n\n**验证签名：**\n\n```bash\n# 使用公钥验证\ncosign verify --key cosign.pub myregistry.com/myapp:v1.0.0\n\n# 输出结果示例\n# Verification successful!\n# {\n#   \"critical\": {\n#     \"identity\": {...},\n#     \"image\": {...},\n#     \"type\": \"cosign container image signature\"\n#   },\n#   \"optional\": {...}\n# }\n```\n\n**Keyless 签名（推荐用于 CI/CD）：**\n\n```bash\n# 在 GitHub Actions 等 CI 中无需存储密钥\ncosign sign --yes myregistry.com/myapp:v1.0.0\n\n# 验证时自动使用 OIDC 令牌验证身份\ncosign verify myregistry.com/myapp:v1.0.0 \\\n  --certificate-identity https://github.com/myorg/myrepo/.github/workflows/build.yml@refs/heads/main \\\n  --certificate-oidc-issuer https://token.actions.githubusercontent.com\n```\n\n#### Docker Content Trust 与 Notary\n\nDocker Content Trust 使用 Notary 实现镜像签名，是 Docker 官方的签名解决方案。\n\n**启用 DCT：**\n\n```bash\n# 在环境中启用 DCT\nexport DOCKER_CONTENT_TRUST=1\n\n# 此后所有 docker push/pull 都需要签名\ndocker push myregistry.com/myapp:v1.0.0\n# 如果镜像未签名，操作会被拒绝\n\n# 禁用 DCT（仅用于特定操作）\ndocker push --disable-content-trust myregistry.com/myapp:v1.0.0\n```\n\n**签名密钥管理：**\n\n```bash\n# 首次推送时会提示创建 Delegation Key\n# 密钥存储在 ~/.docker/trust/private/root_keys/ 和 ~/.docker/trust/private/tuf_keys/\n\n# 查看签名信息\ndocker inspect --format='{{.RepoDigests}}' myregistry.com/myapp:v1.0.0\n```\n\n### 18.6.4 供应链安全最佳实践\n\n#### 1. 基础镜像安全\n\n```dockerfile\n# ❌ 不推荐：使用 latest 标签\nFROM ubuntu:latest\nRUN apt-get update && apt-get install -y curl\n\n# ✓ 推荐：固定基础镜像版本和摘要\nFROM ubuntu:22.04@sha256:a6d2b38300ce017add71440577d5b0a90460d0e6e0e14...（完整 64 位哈希）\nRUN apt-get update && apt-get install -y curl=7.68.0-1ubuntu1\n```\n\n#### 2. 构建时扫描\n\n在 Dockerfile 中集成安全扫描：\n\n```dockerfile\nFROM golang:1.20-alpine AS builder\nWORKDIR /app\nCOPY . .\n\n# 使用 Trivy 扫描源代码\nRUN apk add --no-cache curl && \\\n    curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin && \\\n    trivy fs . --exit-code 1 --severity HIGH,CRITICAL\n\nRUN go build -o app .\n\nFROM alpine:3.17@sha256:abcd1234...（请替换为实际完整的 64 位摘要哈希）\nCOPY --from=builder /app/app /app\n```\n\n#### 3. 运行时镜像扫描策略\n\n```bash\n# 镜像构建完成后立即扫描\ntrivy image --severity HIGH,CRITICAL \\\n  --exit-code 1 \\\n  --timeout 30m \\\n  $IMAGE_NAME:$IMAGE_TAG\n\n# 定期扫描已部署的镜像\ntrivy image --scanners vuln,misconfig registry:5000/myapp:latest\n```\n\n#### 4. 镜像仓库安全配置\n\n**Harbor（私有镜像仓库）的安全扫描：**\n\n```yaml\n# harbor.yml 配置示例\ntrivy:\n  enabled: true\n  # 启用镜像扫描\n  image_source: \"Official\"\n\n# 默认扫描配置\nscan_on_push: true  # 推送时自动扫描\nscan_all: true      # 扫描仓库中的所有镜像\n```\n\n#### 5. 政策执行\n\n在 Kubernetes 环境中使用 Admission Webhook 强制镜像签名和扫描：\n\n```yaml\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  name: image-security-policy\nwebhooks:\n- name: image-security.example.com\n  clientConfig:\n    service:\n      name: image-security-webhook\n      namespace: security\n      path: \"/validate\"\n  rules:\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"\"]\n    apiVersions: [\"v1\"]\n    resources: [\"pods\"]\n  admissionReviewVersions: [\"v1\"]\n  sideEffects: None\n```\n\n### 18.6.5 CI/CD 中集成安全扫描\n\n#### GitHub Actions 工作流示例\n\n```yaml\nname: Build and Scan Image\n\non:\n  push:\n    branches: [main, develop]\n  pull_request:\n    branches: [main]\n\nenv:\n  REGISTRY: ghcr.io\n  IMAGE_NAME: ${{ github.repository }}\n\njobs:\n  build-scan:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: read\n      packages: write\n      security-events: write\n\n    steps:\n    - name: Checkout code\n      uses: actions/checkout@v4\n\n    - name: Set up Docker Buildx\n      uses: docker/setup-buildx-action@v3\n\n    - name: Build Docker image\n      uses: docker/build-push-action@v6\n      with:\n        context: .\n        push: false\n        load: true\n        tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest\n\n    - name: Run Trivy vulnerability scan\n      uses: aquasecurity/trivy-action@master\n      with:\n        image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest\n        format: 'sarif'\n        output: 'trivy-results.sarif'\n        severity: 'HIGH,CRITICAL'\n\n    - name: Upload Trivy results to GitHub Security tab\n      uses: github/codeql-action/upload-sarif@v3\n      with:\n        sarif_file: 'trivy-results.sarif'\n\n    - name: Generate SBOM\n      uses: anchore/sbom-action@v0\n      with:\n        image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest\n        format: cyclonedx-json\n        output-file: sbom-cyclonedx.json\n\n    - name: Upload SBOM\n      uses: actions/upload-artifact@v4\n      with:\n        name: sbom\n        path: sbom-cyclonedx.json\n\n    - name: Sign image with Cosign\n      if: github.event_name == 'push'\n      run: |\n        cosign sign --yes ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest\n\n    - name: Login to Registry and Push\n      if: github.event_name == 'push'\n      uses: docker/login-action@v3\n      with:\n        registry: ${{ env.REGISTRY }}\n        username: ${{ github.actor }}\n        password: ${{ secrets.GITHUB_TOKEN }}\n\n    - name: Push image\n      uses: docker/build-push-action@v6\n      with:\n        context: .\n        push: true\n        tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest\n```\n\n#### GitLab CI 工作流示例\n\n```yaml\nstages:\n  - build\n  - scan\n  - sign\n  - push\n\nvariables:\n  REGISTRY: registry.gitlab.com\n  IMAGE_NAME: $REGISTRY/$CI_PROJECT_PATH\n\nbuild:\n  stage: build\n  image: docker:latest\n  services:\n    - docker:dind\n  script:\n    - docker build -t $IMAGE_NAME:$CI_COMMIT_SHA .\n    - docker save $IMAGE_NAME:$CI_COMMIT_SHA > image.tar\n\nscan:trivy:\n  stage: scan\n  image: aquasec/trivy:latest\n  script:\n    - trivy image --severity HIGH,CRITICAL --exit-code 1 docker-archive://image.tar\n  allow_failure: false\n\nscan:grype:\n  stage: scan\n  image: anchore/grype:latest\n  script:\n    - grype docker-archive://image.tar\n\ngenerate:sbom:\n  stage: scan\n  image: anchore/syft:latest\n  script:\n    - syft docker-archive://image.tar -o cyclonedx > sbom.xml\n  artifacts:\n    reports:\n      sbom: sbom.xml\n\nsign:\n  stage: sign\n  image: gcr.io/projectsigstore/cosign:latest\n  script:\n    - cosign sign --key $COSIGN_KEY $IMAGE_NAME:$CI_COMMIT_SHA\n  only:\n    - main\n\npush:\n  stage: push\n  image: docker:latest\n  services:\n    - docker:dind\n  script:\n    - docker load < image.tar\n    - docker login -u $REGISTRY_USER -p $REGISTRY_PASSWORD $REGISTRY\n    - docker push $IMAGE_NAME:$CI_COMMIT_SHA\n  only:\n    - main\n```\n\n### 18.6.6 常见问题与最佳实践\n\n**Q: 扫描报告中有过时的 CVE，如何处理？**\n\nA: 某些 CVE 可能已经被修复但数据库未更新，可以：\n- 手动验证安全补丁是否已应用\n- 使用工具的忽略列表功能（如 Trivy 的 `.trivyignore`）\n- 定期更新扫描工具和漏洞数据库\n\n**Q: 如何平衡镜像大小和安全性？**\n\nA:\n- 使用多阶段构建减少最终镜像大小\n- 使用精简基础镜像（Alpine、Distroless）\n- 定期更新依赖而不是一味求小\n- 优先安全性，体积次之\n\n**Q: 如何管理和轮换签名密钥？**\n\nA:\n- 在密钥管理系统（如 HashiCorp Vault）中存储密钥\n- 定期轮换密钥（建议每 90 天）\n- 使用 Keyless 签名消除密钥管理复杂性\n- 保留密钥轮换的审计日志\n"
  },
  {
    "path": "18_security/README.md",
    "content": "# 第十八章 安全\n\n容器安全是生产环境部署的核心考量。本章介绍 Docker 的安全机制和最佳实践。\n\n## 容器安全的本质\n\n> **核心问题**：容器共享宿主机内核，隔离性弱于虚拟机。如何在便利性和安全性之间取得平衡？\n\n```mermaid\nflowchart LR\n    subgraph VM [\"虚拟机安全模型：<br/>完全隔离（性能损耗）\"]\n        direction TB\n        Guest[\"Guest OS\"]\n        Hyper[\"Hypervisor<br/>&lt;-- 隔离边界\"]\n        Host[\"Host OS\"]\n        Guest --> Hyper --> Host\n    end\n    \n    subgraph Container [\"容器安全模型：<br/>进程隔离（轻量但需加固）\"]\n        direction TB\n        Proc[\"容器进程<br/>(共享内核)\"]\n        Mech[\"Namespace &lt;-- 隔离边界<br/>Cgroups<br/>Capabilities\"]\n        Proc --> Mech\n    end\n```\n\n## 本章内容\n\n本章涵盖 Docker 安全的多个层面，从内核隔离机制到运行时防护和供应链安全。\n\n* [内核命名空间](18.1_kernel_ns.md)\n  * 命名空间的安全意义、User Namespace 与提权防护。\n\n* [控制组](18.2_control_group.md)\n  * 通过 Cgroups 限制容器资源使用，防止资源耗尽攻击。\n\n* [服务端防护](18.3_daemon_sec.md)\n  * Docker 守护进程的安全配置与网络访问控制。\n\n* [内核能力机制](18.4_kernel_capability.md)\n  * Linux Capabilities 的细粒度权限控制。\n\n* [其它安全特性](18.5_other_feature.md)\n  * 镜像安全（漏洞扫描、签名验证）、运行时安全（非 root 运行、只读文件系统、Seccomp、AppArmor）、Dockerfile 安全实践、软件供应链安全（SBOM、SLSA）。\n\n* [镜像安全](18.6_image_security.md)\n  * 容器镜像的安全扫描、漏洞检测与签名验证。\n\n## 安全扫描清单\n\n部署前检查：\n\n| 检查项 | 命令/方法 |\n|--------|----------|\n| 漏洞扫描 | `docker scout cves` 或 `trivy` |\n| 非 root 运行 | 检查 Dockerfile 中的 `USER` |\n| 资源限制 | 检查 `-m`, `--cpus` 参数 |\n| 只读文件系统 | 检查 `--read-only` |\n| 无特权模式 | 确认没有 `--privileged` |\n| 最小能力 | 检查 `--cap-drop=all` |\n| 网络隔离 | 检查网络配置 |\n| 敏感信息 | 确认无硬编码密码 |\n"
  },
  {
    "path": "18_security/summary.md",
    "content": "## 本章小结\n\nDocker 的安全性依赖于多层隔离机制的协同工作，同时需要用户遵循最佳实践。\n\n总体来看，Docker 容器还是十分安全的，特别是在容器内不使用 root 权限来运行进程的话。\n\n另外，用户可以使用现有工具，比如 [Apparmor](https://docs.docker.com/engine/security/apparmor/)，[Seccomp](https://docs.docker.com/engine/security/seccomp/)，SELinux，GRSEC 来增强安全性；甚至自己在内核中实现更复杂的安全机制。\n---\n\n> 📝 **发现错误或有改进建议？** 欢迎提交 [Issue](https://github.com/yeasy/docker_practice/issues) 或 [PR](https://github.com/yeasy/docker_practice/pulls)。\n"
  },
  {
    "path": "19_observability/19.1_prometheus.md",
    "content": "## 19.1 Prometheus\n\nPrometheus 和 Grafana 是目前最流行的开源监控组合，前者负责数据采集与存储，后者负责数据可视化。\n\n[Prometheus](https://prometheus.io/) 是一个开源的系统监控和报警工具包。它受 Google Borgmon 的启发，由 SoundCloud 在 2012 年创建。\n\n### 19.1.1 架构简介\n\nPrometheus 的主要组件包括：\n\n* **Prometheus Server**：核心组件，负责收集和存储时间序列数据。\n* **Exporters**：负责向 Prometheus 暴露监控数据 (如 Node Exporter，cAdvisor)。\n* **Alertmanager**：处理报警发送。\n* **Pushgateway**：用于支持短生命周期的 Job 推送数据。\n\n### 19.1.2 快速部署\n\n我们可以使用 Docker Compose 快速部署一套 Prometheus + Grafana 监控环境。\n\n本节示例使用了：\n\n* `node-exporter`：采集宿主机指标 (CPU、内存、磁盘、网络等)。\n* `cAdvisor`：采集容器指标 (容器 CPU/内存/网络 IO、文件系统等)。\n\n在生产环境中，建议将 Prometheus 的数据目录做持久化，并显式配置数据保留周期。\n\n#### 1. 准备配置文件\n\n创建 `prometheus.yml`：\n\n```yaml\nglobal:\n  scrape_interval: 15s\n\nscrape_configs:\n  - job_name: 'prometheus'\n    static_configs:\n      - targets: ['localhost:9090']\n\n  - job_name: 'node-exporter'\n    static_configs:\n      - targets: ['node-exporter:9100']\n\n  - job_name: 'cadvisor'\n    static_configs:\n      - targets: ['cadvisor:8080']\n\nrule_files:\n  - /etc/prometheus/rules.yml\n```\n\n#### 2. 编写 Docker Compose 文件\n\n创建 `compose.yaml` (或 `docker-compose.yml`)：\n\n```yaml\nservices:\n  prometheus:\n    image: prom/prometheus:latest\n    volumes:\n      - ./prometheus.yml:/etc/prometheus/prometheus.yml\n      - ./rules.yml:/etc/prometheus/rules.yml\n      - prometheus_data:/prometheus\n    ports:\n      - \"9090:9090\"\n    command:\n      - --config.file=/etc/prometheus/prometheus.yml\n      - --storage.tsdb.path=/prometheus\n      - --storage.tsdb.retention.time=15d\n    networks:\n      - monitoring\n\n  grafana:\n    image: grafana/grafana:latest\n    ports:\n      - \"3000:3000\"\n    environment:\n      - GF_SECURITY_ADMIN_PASSWORD=admin\n    networks:\n      - monitoring\n    depends_on:\n      - prometheus\n\n  node-exporter:\n    image: prom/node-exporter:latest\n    ports:\n      - \"9100:9100\"\n    networks:\n      - monitoring\n\n  cadvisor:\n    image: ghcr.io/google/cadvisor:latest\n    ports:\n      - \"8080:8080\"\n    volumes:\n      - /:/rootfs:ro\n      - /var/run:/var/run:ro\n      - /sys:/sys:ro\n      - /var/lib/docker/:/var/lib/docker:ro\n    networks:\n      - monitoring\n\nnetworks:\n  monitoring:\n\nvolumes:\n  prometheus_data:\n```\n\n#### 3. 启动服务\n\n```bash\n$ docker compose up -d\n```\n\n启动后，访问以下地址：\n\n* Prometheus: `http://localhost:9090`\n* Grafana：`http://localhost:3000` (默认账号密码：admin/admin)\n\n### 19.1.3 配置 Grafana 面板\n\n1. 在 Grafana 中添加 Prometheus 数据源，URL 填写 `http://prometheus:9090`。\n2. 导入现成的 Dashboard 模板，例如 [Node Exporter Full](https://grafana.com/grafana/dashboards/1860) (ID：1860) 和 [Docker Container](https://grafana.com/grafana/dashboards/193) (ID：193)。\n\n这样，你就拥有了一个直观的容器监控大屏。\n\n### 19.1.4 生产要点与告警闭环\n\n完成部署后，建议补齐以下生产要点。\n\n#### 指标采集的“最小闭环”\n\n1. 在 Prometheus 页面打开 **Status -> Targets**，确认 `prometheus`、`node-exporter`、`cadvisor` 的 `State` 均为 `UP`。\n2. 在 **Graph** 中尝试查询：\n\n   * `up`\n   * `rate(container_cpu_usage_seconds_total[5m])`\n\n3. 在 Grafana Dashboard 中重点关注：\n\n   * 宿主机 CPU/Load/内存/磁盘\n   * 容器 CPU/内存使用率、容器重启次数\n\n如果你发现“面板为空”，通常不是 Grafana 的问题，而是 Prometheus 没抓到数据或查询标签与 Dashboard 不匹配。\n\n#### 常见问题排查\n\n* **Target down**：检查容器网络是否互通，端口是否暴露到同一网络，以及 exporter 是否在容器内正常监听。\n* **cAdvisor 无数据或报错**：确认挂载了 Docker 目录与宿主机的 `/sys`、`/var/run` 等路径，并确保宿主机上 Docker 运行正常。\n* **指标缺失**：确认你的 Docker/内核版本与 cAdvisor 兼容；对于 containerd 等运行时，采集方式会不同。\n\n#### 关键指标速查：节点/容器\n\n在生产环境排障时，建议优先关注下面几类指标，并在 Grafana 面板中建立对应的常用视图。\n\n* **节点 CPU 使用率**：`100 - (avg by (instance) (rate(node_cpu_seconds_total{mode=\"idle\"}[5m])) * 100)`\n* **节点内存使用率**：`(1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100`\n* **节点磁盘空间使用率**：`(1 - (node_filesystem_avail_bytes{fstype!~\"tmpfs|overlay\"} / node_filesystem_size_bytes{fstype!~\"tmpfs|overlay\"})) * 100`\n* **容器 CPU**：`sum by (name) (rate(container_cpu_usage_seconds_total[5m]))`\n* **容器内存**：`sum by (name) (container_memory_working_set_bytes)`\n\n说明：不同版本的 cAdvisor/Docker 对 label 命名可能存在差异 (如 `name`、`container`、`container_name`)，如果查询为空，建议先用 `label_values(container_cpu_usage_seconds_total, __name__)` 或在 Prometheus 的图形界面查看可用 label。\n\n#### Targets down 排错清单\n\n当 **Status -> Targets** 出现 `DOWN` 时，建议按以下顺序排查：\n\n1. **网络连通性**：Prometheus 容器是否能解析并访问目标 (同一 Docker network、DNS、端口)。\n2. **端口/路径**：确认 exporter 监听端口与 Prometheus 配置一致；必要时在 Prometheus 容器内 `curl http://node-exporter:9100/metrics`。\n3. **权限/挂载**：cAdvisor 需要访问宿主机 `/sys`、`/var/lib/docker` 等挂载路径，缺失会导致指标不全或报错。\n4. **时间问题**：宿主机与容器时间偏差过大可能导致“数据看起来断档”，需要检查 NTP/时区配置。\n5. **目标本身异常**：确认 exporter 容器是否在重启，查看 `docker logs`。\n\n#### Alertmanager 告警建议\n\n生产环境建议引入 Alertmanager 做告警聚合与路由，并在 Prometheus 中配置 `alerting` 与 `rule_files`。\n\n为了保持“最小告警闭环”，建议至少覆盖两类告警：\n\n* **采集链路告警**：例如 `up == 0`，用于发现 exporter 或网络故障。\n* **资源风险告警**：例如节点磁盘空间不足，用于提前发现容量风险。\n\n##### 1. 准备告警规则文件\n\n创建 `rules.yml`：\n\n```yaml\ngroups:\n  - name: docker_practice\n    rules:\n      - alert: PrometheusTargetDown\n        expr: up == 0\n        for: 2m\n        labels:\n          severity: warning\n        annotations:\n          summary: \"Prometheus 抓取目标不可达\"\n          description: \"Job={{ $labels.job }}, Instance={{ $labels.instance }}\"\n\n      - alert: HostDiskSpaceLow\n        expr: |\n          (node_filesystem_avail_bytes{fstype!~\"tmpfs|overlay\"} / node_filesystem_size_bytes{fstype!~\"tmpfs|overlay\"}) < 0.10\n        for: 10m\n        labels:\n          severity: critical\n        annotations:\n          summary: \"磁盘可用空间不足\"\n          description: \"Instance={{ $labels.instance }}, Mountpoint={{ $labels.mountpoint }}\"\n```\n\n说明：这里的规则是“可用空间低于 10%”的阈值告警，并非“未来 24 小时写满”的预测。生产环境建议针对特定文件系统与挂载点做更精确的过滤。\n\n##### 2. 配置 Prometheus 加载规则并接入 Alertmanager\n\n修改 `prometheus.yml`，增加：\n\n```yaml\nrule_files:\n  - /etc/prometheus/rules.yml\n\nalerting:\n  alertmanagers:\n    - static_configs:\n        - targets: [\"alertmanager:9093\"]\n```\n\n并在 Compose 中挂载规则文件。\n\n##### 3. 部署 Alertmanager\n\n创建 `alertmanager.yml`：\n\n```yaml\nroute:\n  receiver: default\n\nreceivers:\n  - name: default\n    webhook_configs:\n      - url: http://example.com/webhook\n```\n\n再在 `compose.yaml` 增加服务：\n\n```yaml\n  alertmanager:\n    image: prom/alertmanager:latest\n    volumes:\n      - ./alertmanager.yml:/etc/alertmanager/alertmanager.yml\n    ports:\n      - \"9093:9093\"\n    networks:\n      - monitoring\n```\n\n生产环境中，建议将告警发送到可追踪的渠道 (如 IM 机器人、事件平台、工单系统)，并在告警中附带 Dashboard 链接与排障入口，避免告警成为噪声。\n\n#### 建议的文件清单\n\n为了避免示例难以复现，建议在同一目录下准备以下文件：\n\n* `compose.yaml`：Prometheus、Grafana、exporters、Alertmanager 的部署文件\n* `prometheus.yml`：Prometheus 抓取配置与告警配置\n* `rules.yml`：告警规则\n* `alertmanager.yml`：告警路由与接收器配置\n"
  },
  {
    "path": "19_observability/19.2_elk.md",
    "content": "## 19.2 ELK 套件\n\nELK (Elasticsearch，Logstash，Kibana) 是目前业界最流行的开源日志解决方案。而在容器领域，由于 Fluentd 更加轻量级且对容器支持更好，EFK (Elasticsearch，Fluentd，Kibana) 组合也变得非常流行。\n\n### 19.2.1 方案架构\n\n我们将采用以下架构：\n\n1. **Docker Container**：容器将日志输出到标准输出 (stdout/stderr)。\n2. **Fluentd**：作为 Docker 的 Logging Driver 或运行为守护容器，收集容器日志。\n3. **Elasticsearch**：存储从 Fluentd 接收到的日志数据。\n4. **Kibana**：从 Elasticsearch 读取数据并进行可视化展示。\n\n### 19.2.2 部署流程\n\n我们将使用 Docker Compose 来一键部署整个日志堆栈。\n\n#### 1. 编写 Compose 文件\n\n1. 编写 `compose.yaml` (或 `docker-compose.yml`) 配置如下：\n\n```yaml\nservices:\n  elasticsearch:\n    image: docker.elastic.co/elasticsearch/elasticsearch:8.17.0\n    container_name: elasticsearch\n    environment:\n      - \"discovery.type=single-node\"\n      - \"xpack.security.enabled=false\"\n      - \"ES_JAVA_OPTS=-Xms512m -Xmx512m\"\n    ports:\n      - \"9200:9200\"\n    volumes:\n      - es_data:/usr/share/elasticsearch/data\n    networks:\n      - logging\n\n  kibana:\n    image: docker.elastic.co/kibana/kibana:8.17.0\n    container_name: kibana\n    environment:\n      - ELASTICSEARCH_HOSTS=http://elasticsearch:9200\n    ports:\n      - \"5601:5601\"\n    links:\n      - elasticsearch\n    networks:\n      - logging\n\n  fluentd:\n    image: fluent/fluentd-kubernetes-daemonset:v1.17-debian-elasticsearch8-1\n    container_name: fluentd\n    environment:\n      - \"FLUENT_ELASTICSEARCH_HOST=elasticsearch\"\n      - \"FLUENT_ELASTICSEARCH_PORT=9200\"\n      - \"FLUENT_ELASTICSEARCH_SCHEME=http\"\n      - \"FLUENT_UID=0\"\n    ports:\n      - \"24224:24224\"\n      - \"24224:24224/udp\"\n    links:\n      - elasticsearch\n    volumes:\n      - ./fluentd/conf:/fluentd/etc\n    networks:\n      - logging\n\nvolumes:\n  es_data:\n\nnetworks:\n  logging:\n```\n\n#### 2. 配置 Fluentd\n\n创建 `fluentd/conf/fluent.conf`：\n\n```ini\n<source>\n  @type forward\n  port 24224\n  bind 0.0.0.0\n</source>\n\n<match *.**>\n  @type copy\n  <store>\n    @type elasticsearch\n    host elasticsearch\n    port 9200\n    logstash_format true\n    logstash_prefix docker\n    logstash_dateformat %Y%m%d\n    include_tag_key true\n    type_name access_log\n    tag_key @log_name\n    flush_interval 1s\n  </store>\n  <store>\n    @type stdout\n  </store>\n</match>\n```\n\n#### 3. 配置应用容器使用 fluentd 驱动\n\n启动一个测试容器，指定日志驱动为 `fluentd`：\n\n```bash\ndocker run -d \\\n  --log-driver=fluentd \\\n  --log-opt fluentd-address=localhost:24224 \\\n  --log-opt tag=nginx-test \\\n  --name nginx-test \\\n  nginx\n```\n\n**注意**：确保 `fluentd` 容器已经启动并监听在 `localhost:24224`。在生产环境中，如果你是在不同机器上，需要将 `localhost` 替换为运行 fluentd 的主机 IP。\n\n#### 4. 在 Kibana 中查看日志\n\n1. 访问 `http://localhost:5601`。\n2. 进入 **Management**->**Kibana**->**Index Patterns**。\n3. 创建新的 Index Pattern，输入 `docker-*` (我们在 fluent.conf 中配置的前缀)。\n4. 选择 `@timestamp` 作为时间字段。\n5. 去 **Discover** 页面，你就能看到 Nginx 容器的日志了。\n\n#### Kibana 建索引模式常见坑\n\n首次接入 EFK/ELK 时，“Elasticsearch 有数据但 Kibana 看不到”很常见，通常是 Kibana 配置或时间窗口问题：\n\n* **Index Pattern 不匹配**：确认 Kibana 的 Index Pattern 与实际索引前缀一致。可以先用 `_cat/indices` 查看真实索引名。\n* **时间字段选择错误**：若索引里包含 `@timestamp`，一般选择它；如果选择了错误的字段，会导致 Discover 无法按时间筛选。\n* **时间窗口/时区**：Discover 右上角的时间范围默认可能是最近 15 分钟，且时区可能影响显示。建议先把范围扩大到最近 24 小时再验证。\n* **数据解析失败**：若日志是非结构化文本，仍可入库但字段不可用；生产环境建议输出 JSON 并在采集端解析。\n\n#### 5. 验证日志是否写入 Elasticsearch：生产排错必备\n\n当你在 Kibana 看不到日志时，建议先跳过 UI，从存储端直接验证“日志是否入库”。\n\n1. 查看索引是否创建：\n\n   ```bash\n   curl -s http://localhost:9200/_cat/indices?v\n   ```\n\n   如果 Fluentd 使用了 `logstash_format true` 且 `logstash_prefix docker`，通常会看到形如 `docker-YYYY.MM.DD` 的索引。\n\n2. 查看最近一段时间的日志文档：\n\n   ```bash\n   curl -s -H 'Content-Type: application/json' \\\n     http://localhost:9200/docker-*/_search \\\n     -d '{\"size\":1,\"sort\":[{\"@timestamp\":\"desc\"}]}'\n   ```\n\n如果 Elasticsearch 中已经有文档，但 Kibana 仍然为空，常见原因是：\n\n* Index Pattern 没匹配到索引 (例如写成了 `docker-*` 但实际索引前缀不同)。\n* 时间字段没选对或时区不一致，导致 Discover 时间窗口内看不到数据。\n\n### 19.2.3 总结\n\n通过 Docker 的日志驱动机制，结合 ELK/EFK 强大的收集和分析能力，我们可以轻松构建一个能够处理海量日志的监控平台，这对于排查生产问题至关重要。\n\n### 19.2.4 生产要点\n\n在生产环境中，日志系统往往比监控系统更容易因为“容量与写入压力”出问题，建议特别关注：\n\n* **容量规划**：日志增长速度与磁盘占用直接相关。建议设置日志保留周期与索引生命周期策略 (ILM)，避免 Elasticsearch 因磁盘水位触发只读或不可用。\n* **资源配置**：Elasticsearch 对 JVM Heap 较敏感。除示例中的 `ES_JAVA_OPTS` 外，生产环境需要结合节点内存、分片规模、查询压力做评估。\n* **链路可靠性**：采集端到存储端要考虑网络抖动、背压与重试策略；当 Elasticsearch 写入变慢时，采集端的缓冲与落盘策略决定了是否会丢日志。\n* **日志格式**：推荐应用输出结构化日志 (JSON) 并包含关键字段 (如 `trace_id`、`request_id`、`service`、`env`)，以便快速过滤与关联分析。\n\n#### 索引与保留策略的落地建议\n\n无论是 EFK 还是 ELK，生产上都需要回答两个问题：\n\n* 日志保留多久？\n* 保留期内的日志如何保证可查询、不过度占用存储？\n\n建议按环境与业务重要性对日志分层，并制定不同的保留周期，例如：\n\n* **生产环境**：7～30 天\n* **测试环境**：1～7 天\n\n实现方式通常有两类：\n\n* **按天滚动索引**：如 `docker-YYYY.MM.DD`，再定期删除过期索引。\n* **使用 ILM**：定义 Hot/Warm/Cold/删除阶段，按时间与容量自动滚动与回收。\n\n对于中小规模集群，先把“按天滚动 + 过期删除”做扎实，往往就能解决 80% 的容量问题；当日志量上来、查询压力变大后，再逐步引入 ILM、分层存储与更精细的分片规划。\n\n#### 最小可用的“过期索引清理”示例\n\n如果你采用按天滚动索引 (例如 `docker-YYYY.MM.DD`)，可以通过 Elasticsearch API 定期清理过期索引。\n\n下面示例仅用于演示思路：获取所有 `docker-` 前缀索引并删除指定索引。生产环境建议基于日期计算、灰度验证与权限控制后再执行自动化清理。\n\n1. 列出索引：\n\n   ```bash\n   curl -s http://localhost:9200/_cat/indices/docker-*?v\n   ```\n\n2. 删除某个过期索引 (示例)：\n\n   ```bash\n   curl -X DELETE http://localhost:9200/docker-2026.02.01\n   ```\n\n如果你希望更自动化的治理能力，可以进一步使用 ILM 为索引配置滚动与删除策略。\n"
  },
  {
    "path": "19_observability/19.3_performance_optimization.md",
    "content": "## 19.3 容器性能优化与故障诊断\n\n容器的轻量级特性不代表性能问题会自动消失。在实际运维中，性能瓶颈可能来自 CPU 限制、内存溢出、磁盘 I/O、网络拥塞等多个层面。本节深入讨论容器性能监控、诊断方法和优化策略。\n\n### 19.3.1 容器性能监控指标\n\n#### 核心性能指标体系\n\n容器性能监控涉及以下关键指标：\n\n**CPU 相关指标：**\n- `cpu.usage_usec`：容器 CPU 使用时间（微秒）\n- `cpu.stat.nr_throttled`：CPU 限流发生次数\n- `cpu.stat.throttled_usec`：CPU 限流总时间\n- `cpu_percent`：CPU 使用百分比\n- `cpu_quota`：CPU 配额设置（微秒）\n\n**内存相关指标：**\n- `memory.usage_bytes`：当前内存使用量\n- `memory.max_usage_bytes`：内存使用峰值\n- `memory.limit_in_bytes`：内存限制\n- `memory.fail_cnt`：OOM（Out of Memory）失败次数\n- `memory.stat.cache`：页面缓存占用\n- `memory.stat.rss`：实际内存占用（RSS）\n- `memory.stat.swap`：SWAP 使用量\n\n**网络相关指标：**\n- `rx_bytes`：接收字节数\n- `tx_bytes`：发送字节数\n- `rx_packets`：接收包数\n- `tx_packets`：发送包数\n- `rx_errors`：接收错误数\n- `tx_errors`：发送错误数\n- `rx_dropped`：接收丢包数\n- `tx_dropped`：发送丢包数\n\n**I/O 相关指标：**\n- `io_service_bytes`：I/O 操作字节数\n- `io_service_time`：I/O 操作耗时\n- `io_queued`：I/O 队列长度\n- `fs_limit_bytes`：文件系统限制\n- `fs_usage_bytes`：文件系统使用量\n\n### 19.3.2 使用 docker stats 实时监控\n\n`docker stats` 是最基础但强大的监控工具，提供实时的容器资源使用情况。\n\n**基本使用：**\n\n```bash\n# 实时监控所有运行中的容器\ndocker stats\n\n# 输出示例：\n# CONTAINER ID   NAME      CPU %   MEM USAGE / LIMIT     MEM %     NET I/O       BLOCK I/O\n# abc123def456   nginx     0.45%   24.3 MiB / 256 MiB    9.49%     1.2kB / 3.4kB 0 B / 0 B\n# def789ghi012   redis     0.23%   12.5 MiB / 512 MiB    2.44%     2.1kB / 1.5kB 0 B / 0 B\n\n# 只监控特定容器\ndocker stats nginx redis\n\n# 一次性输出不进入交互模式\ndocker stats --no-stream\n\n# 指定刷新间隔（单位：秒，默认 1 秒）\ndocker stats --no-stream --interval 2\n\n# 格式化输出（使用 Go 模板）\ndocker stats --format \"table {{.Container}}\\t{{.CPUPerc}}\\t{{.MemUsage}}\" --no-stream\n\n# 导出为 JSON 格式用于日志记录\ndocker stats --format json --no-stream > stats.json\n```\n\n**在脚本中使用：**\n\n```bash\n#!/bin/bash\n\n# 持续监控并记录到文件\nwhile true; do\n    timestamp=$(date '+%Y-%m-%d %H:%M:%S')\n    docker stats --no-stream --format \"{{.Container}},{{.CPUPerc}},{{.MemUsage}}\" | \\\n        awk -v ts=\"$timestamp\" '{print ts\",\"$0}' >> container_stats.log\n    sleep 10\ndone\n```\n\n**性能指标解读：**\n\n```bash\n# CPU % 超过 80%：需要增加 CPU 限制或优化应用\n# MEM % 接近 100%：容器即将 OOM，需要增加内存或排查内存泄漏\n# 如果 NET I/O 中 dropped 为非零：网络拥塞或丢包\n```\n\n### 19.3.3 cAdvisor 容器监控系统\n\ncAdvisor 是 Google 开发的容器监控工具，提供比 `docker stats` 更详细的性能数据。\n\n**Docker Compose 部署 cAdvisor：**\n\n```yaml\nservices:\n  cadvisor:\n    image: ghcr.io/google/cadvisor:v0.51.0\n    container_name: cadvisor\n    ports:\n      - \"8080:8080\"\n    volumes:\n      - /:/rootfs:ro\n      - /var/run:/var/run:ro\n      - /sys:/sys:ro\n      - /var/lib/docker/:/var/lib/docker:ro\n      - /dev/disk/:/dev/disk:ro\n    privileged: true\n    devices:\n      - /dev/kmsg\n    networks:\n      - monitoring\n\nnetworks:\n  monitoring:\n    driver: bridge\n```\n\n启动后访问 `http://localhost:8080` 查看：\n- 容器性能统计\n- 系统资源使用情况\n- 历史性能数据\n\n**从 cAdvisor 提取指标：**\n\n```bash\n# 获取所有容器的 JSON 格式性能数据\ncurl http://localhost:8080/api/v1.3/machine | jq .\n\n# 获取特定容器信息\ncurl http://localhost:8080/api/v1.3/docker | jq '.docker | keys' | head -5\n\n# 获取容器统计信息\ncurl http://localhost:8080/api/v1.3/docker/abc123/ | jq '.stats[-1]'\n```\n\n**与 Prometheus 集成：**\n\n```yaml\n# prometheus.yml 配置\nglobal:\n  scrape_interval: 15s\n\nscrape_configs:\n  - job_name: 'cadvisor'\n    static_configs:\n      - targets: ['localhost:8080']\n    metrics_path: '/metrics'\n```\n\n### 19.3.4 Prometheus 容器监控配置\n\n使用 Prometheus 和 node-exporter 进行长期的容器性能监控。\n\n**完整监控栈部署：**\n\n```yaml\nservices:\n  prometheus:\n    image: prom/prometheus:latest\n    container_name: prometheus\n    ports:\n      - \"9090:9090\"\n    volumes:\n      - ./prometheus.yml:/etc/prometheus/prometheus.yml\n      - prometheus_data:/prometheus\n    command:\n      - '--config.file=/etc/prometheus/prometheus.yml'\n      - '--storage.tsdb.path=/prometheus'\n      - '--storage.tsdb.retention.time=30d'\n    networks:\n      - monitoring\n\n  node-exporter:\n    image: prom/node-exporter:latest\n    container_name: node-exporter\n    ports:\n      - \"9100:9100\"\n    volumes:\n      - /proc:/host/proc:ro\n      - /sys:/host/sys:ro\n      - /:/rootfs:ro\n    command:\n      - '--path.procfs=/host/proc'\n      - '--path.sysfs=/host/sys'\n      - '--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)'\n    networks:\n      - monitoring\n\n  cadvisor:\n    image: ghcr.io/google/cadvisor:v0.51.0\n    container_name: cadvisor\n    ports:\n      - \"8080:8080\"\n    volumes:\n      - /:/rootfs:ro\n      - /var/run:/var/run:ro\n      - /sys:/sys:ro\n      - /var/lib/docker/:/var/lib/docker:ro\n    privileged: true\n    networks:\n      - monitoring\n\n  grafana:\n    image: grafana/grafana:latest\n    container_name: grafana\n    ports:\n      - \"3000:3000\"\n    environment:\n      - GF_SECURITY_ADMIN_PASSWORD=admin\n      - GF_INSTALL_PLUGINS=grafana-piechart-panel\n    volumes:\n      - grafana_data:/var/lib/grafana\n    networks:\n      - monitoring\n\nvolumes:\n  prometheus_data:\n  grafana_data:\n\nnetworks:\n  monitoring:\n    driver: bridge\n```\n\n**Prometheus 配置文件（prometheus.yml）：**\n\n```yaml\nglobal:\n  scrape_interval: 15s\n  evaluation_interval: 15s\n\nscrape_configs:\n  - job_name: 'prometheus'\n    static_configs:\n      - targets: ['localhost:9090']\n\n  - job_name: 'node-exporter'\n    static_configs:\n      - targets: ['node-exporter:9100']\n\n  - job_name: 'cadvisor'\n    static_configs:\n      - targets: ['cadvisor:8080']\n\n  - job_name: 'docker'\n    static_configs:\n      - targets: ['localhost:9323']\n```\n\n**常用的 Prometheus 查询（PromQL）：**\n\n```text\n# 容器 CPU 使用百分比\nrate(container_cpu_usage_seconds_total[5m]) * 100\n\n# 容器内存使用百分比\n(container_memory_usage_bytes / container_spec_memory_limit_bytes) * 100\n\n# 容器网络入站流量（MB/s）\nrate(container_network_receive_bytes_total[5m]) / 1024 / 1024\n\n# 容器网络出站流量（MB/s）\nrate(container_network_transmit_bytes_total[5m]) / 1024 / 1024\n\n# 容器磁盘读取速率（MB/s）\nrate(container_fs_io_current[5m]) / 1024 / 1024\n\n# CPU 限流情况\nrate(container_cpu_cfs_throttled_seconds_total[5m])\n\n# 内存缓存占比\ncontainer_memory_cache_bytes / container_memory_usage_bytes\n\n# 按镜像统计容器数\ncount(container_memory_usage_bytes) by (image)\n```\n\n### 19.3.5 容器 OOM 排查与内存限制调优\n\n#### OOM 问题诊断\n\n```bash\n# 检查容器是否因 OOM 被杀死\ndocker inspect <container_id> | grep OOMKilled\n\n# 查看容器退出码：137 表示被 OOM 杀死\ndocker ps -a --format \"{{.ID}}\\t{{.Status}}\" | grep \"137\"\n\n# 查看容器日志中的 OOM 信息\ndocker logs <container_id> 2>&1 | grep -i \"out of memory\\|oom\"\n\n# 从宿主机日志查看 OOM 事件\ndmesg | grep -i \"oom\\|kill\"\njournalctl -u docker -n 100 | grep -i \"oom\"\n```\n\n#### 内存泄漏检测\n\n使用专项工具分析应用内存使用：\n\n**Python 应用内存泄漏检测：**\n\n```python\n# Dockerfile\nFROM python:3.11-slim\nWORKDIR /app\nCOPY requirements.txt .\nRUN pip install -r requirements.txt memory_profiler tracemalloc\n\nCOPY app.py .\nCMD [\"python\", \"-m\", \"memory_profiler\", \"app.py\"]\n```\n\n```python\n# app.py - 内存泄漏示例\nfrom memory_profiler import profile\nimport tracemalloc\n\n@profile\ndef memory_leak():\n    # 不断创建未释放的列表\n    data = []\n    while True:\n        data.append([0] * 1000000)\n        print(f\"List size: {len(data)}\")\n\n# 使用 tracemalloc 跟踪内存分配\ntracemalloc.start()\n\n# 执行可能泄漏的代码\n# ...\n\ncurrent, peak = tracemalloc.get_traced_memory()\nprint(f\"Current: {current / 1024 / 1024:.2f} MB\")\nprint(f\"Peak: {peak / 1024 / 1024:.2f} MB\")\n```\n\n**Java 应用内存分析：**\n\n```bash\n# 在容器中启用 JVM 远程调试\ndocker run -e JAVA_OPTS=\"-Xmx512m -Xms256m -XX:+UseG1GC\" \\\n  -p 5005:5005 \\\n  myapp:latest\n\n# 使用 jstat 检查垃圾回收情况\njstat -gc <pid> 1000  # 每秒采样一次\n\n# 输出示例：\n#  S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC       MU    CCSC   CCSU\n# 6144   6144      0   6144   39424    12288   149504     84320     50552   47689  6464   5989\n```\n\n#### 内存限制最佳实践\n\n```bash\n# 为容器设置内存限制\ndocker run -m 512m --memory-swap 1g myapp:latest\n\n# 参数说明：\n# -m / --memory：内存限制（这里是 512MB）\n# --memory-swap：内存+SWAP 总额（这里是 1GB，意味着 SWAP 为 512MB）\n# 如果不设置 --memory-swap，则等于 --memory 值\n\n# Docker Compose 配置\nservices:\n  app:\n    image: myapp:latest\n    deploy:\n      resources:\n        limits:\n          memory: 512M\n        reservations:\n          memory: 256M\n```\n\n**内存超额提交（Memory Overcommit）：**\n\n```bash\n# 在 Docker Compose 中区分限制和预留\n# limits：绝不能超过的最大值\n# reservations：Compose 排期时的参考值\n\nservices:\n  web:\n    memory: 512M  # 限制\n    memswap_limit: 1G  # SWAP 限制\n\n  db:\n    memory: 2G\n    memory_reservation: 1G  # 预留 1GB，允许突发到 2GB\n```\n\n### 19.3.6 镜像体积优化与多阶段构建\n\n#### 镜像体积分析工具\n\n**使用 dive 分析镜像层：**\n\n```bash\n# 安装 dive\nwget https://github.com/wagoodman/dive/releases/download/v0.11.0/dive_0.11.0_linux_amd64.deb\nsudo apt install ./dive_0.11.0_linux_amd64.deb\n\n# 分析镜像\ndive myapp:latest\n\n# 输出详细的分层信息，显示每一层的大小和内容\n```\n\n**使用 Dockerfile 分析工具：**\n\n```bash\n# 安装 hadolint\ncurl https://github.com/hadolint/hadolint/releases/download/v2.12.0/hadolint-Linux-x86_64 -L -o hadolint\nchmod +x hadolint\n\n# 检查 Dockerfile 最佳实践\n./hadolint Dockerfile\n```\n\n#### 多阶段构建最佳实践\n\n**Go 应用的最小化镜像构建：**\n\n```dockerfile\n# Stage 1: 构建阶段\nFROM golang:1.20-alpine AS builder\n\nWORKDIR /build\n\n# 安装依赖\nRUN apk add --no-cache git ca-certificates tzdata\n\nCOPY go.mod go.sum ./\nRUN go mod download\n\nCOPY . .\n\n# 构建静态二进制（支持 scratch 基础镜像）\nRUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \\\n    -a -installsuffix cgo \\\n    -ldflags=\"-w -s\" \\\n    -o app .\n\n# Stage 2: 运行阶段\nFROM scratch\n\n# 从 builder 复制必要的文件\nCOPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/\nCOPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo\nCOPY --from=builder /build/app /app\n\nEXPOSE 8080\nENTRYPOINT [\"/app\"]\n\n# 最终镜像大小通常 < 15MB（相比 golang:1.20-alpine 的 ~1GB）\n```\n\n**Node.js 应用的多阶段构建：**\n\n```dockerfile\n# Stage 1: 依赖安装\nFROM node:18-alpine AS dependencies\n\nWORKDIR /app\nCOPY package*.json ./\nRUN npm ci --only=production && \\\n    npm cache clean --force\n\n# Stage 2: 构建阶段\nFROM node:18-alpine AS builder\n\nWORKDIR /app\nCOPY package*.json ./\nRUN npm ci\n\nCOPY . .\nRUN npm run build\n\n# Stage 3: 运行阶段\nFROM node:18-alpine\n\nWORKDIR /app\n\n# 从依赖阶段复制 node_modules\nCOPY --from=dependencies /app/node_modules ./node_modules\n# 从构建阶段复制构建产物\nCOPY --from=builder /app/dist ./dist\nCOPY --from=builder /app/package*.json ./\n\n# 删除开发依赖和不必要的文件\nRUN rm -rf src tests *.config.js\n\nUSER node\nEXPOSE 3000\n\nCMD [\"node\", \"dist/index.js\"]\n\n# 镜像大小对比：\n# 不优化：~500MB\n# 多阶段构建后：~120MB（减少 76%）\n```\n\n**Python 应用的多阶段构建：**\n\n```dockerfile\n# Stage 1: 构建阶段\nFROM python:3.11-slim AS builder\n\nWORKDIR /build\n\nRUN apt-get update && apt-get install -y --no-install-recommends \\\n    build-essential \\\n    && rm -rf /var/apt/lists/*\n\nCOPY requirements.txt .\nRUN pip install --user --no-cache-dir -r requirements.txt\n\n# Stage 2: 运行阶段\nFROM python:3.11-slim\n\nWORKDIR /app\n\n# 从 builder 复制虚拟环境\nCOPY --from=builder /root/.local /root/.local\n\n# 设置 PATH\nENV PATH=/root/.local/bin:$PATH \\\n    PYTHONUNBUFFERED=1 \\\n    PYTHONDONTWRITEBYTECODE=1\n\nCOPY . .\n\nUSER nobody\nEXPOSE 5000\n\nCMD [\"python\", \"app.py\"]\n```\n\n#### 镜像体积优化检查清单\n\n```bash\n# 检查清单\n□ 使用精简基础镜像（Alpine、Distroless）\n□ 清理包管理器缓存（apt-get clean、rm -rf /var/cache/*）\n□ 在同一 RUN 指令中安装和清理依赖\n□ 使用 .dockerignore 排除不必要的文件\n□ 多阶段构建避免构建依赖污染最终镜像\n□ 去除调试符号：-ldflags=\"-w -s\"（Go）、strip 命令（C/C++）\n□ 压缩静态资源和应用文件\n□ 使用 BuildKit 缓存优化加速构建\n\n# 优化示例：\nFROM ubuntu:22.04\n\n# ❌ 不推荐\nRUN apt-get update\nRUN apt-get install -y curl wget git\nRUN apt-get clean\n\n# ✓ 推荐\nRUN apt-get update && \\\n    apt-get install -y --no-install-recommends \\\n    curl \\\n    wget \\\n    git && \\\n    apt-get clean && \\\n    rm -rf /var/lib/apt/lists/*\n```\n\n### 19.3.7 常见性能问题及解决方案\n\n**问题 1: 容器频繁被 OOM 杀死**\n\n症状：容器进程被无故杀死，exit code 137\n解决方案：\n```bash\n# 增加内存限制\ndocker update -m 1g <container_id>\n\n# 排查内存泄漏\ndocker exec <container_id> ps aux | grep -E \"VSZ|RSS\"\n\n# 使用 docker stats 实时监控\ndocker stats <container_id>\n\n# 启用内存交换（作为最后手段）\ndocker run -m 512m --memory-swap 1g myapp:latest\n```\n\n**问题 2: CPU 被限流（CPU Throttling）**\n\n症状：应用性能突然下降，但 CPU 使用率不高\n诊断：\n```bash\n# 查看 CPU 限流统计\ndocker exec <container_id> cat /sys/fs/cgroup/cpu/cpu.stat\n\n# 如果 throttled_time > 0，说明发生了 CPU 限流\n# 解决方案：增加 CPU 限制\ndocker update --cpus 2 <container_id>\n```\n\n**问题 3: 网络丢包或延迟高**\n\n诊断：\n```bash\n# 进入容器检查网络状态\ndocker exec <container_id> ip -s link show\n\n# 检查路由和 DNS\ndocker exec <container_id> cat /etc/resolv.conf\n\n# 测试网络延迟\ndocker exec <container_id> ping 8.8.8.8\n\n# 检查容器网络驱动\ndocker inspect <container_id> | grep -A 10 NetworkSettings\n\n# 解决方案：更换网络驱动或调整 MTU\ndocker run --net=host myapp:latest  # 使用宿主机网络（性能最佳）\n```\n"
  },
  {
    "path": "19_observability/README.md",
    "content": "# 第十九章 容器监控与日志\n\n在生产环境中，容器化应用部署完成后，实时掌握容器的运行状态以及应用日志非常重要。本章将以 Docker/Compose 的场景为主，介绍容器监控与日志管理的落地思路与最小实践闭环。\n\n对于 Kubernetes 场景，可观测性链路与组件选择通常会有所不同 (例如使用 Prometheus Operator、日志采集 DaemonSet 等)。本章会在关键点给出迁移提示，但不会展开为完整的 Kubernetes 教程。\n\n我们将重点探讨以下内容：\n\n- **容器监控**：以 Prometheus 为主，讲解如何采集和展示容器性能指标。\n- **日志管理**：以 ELK (Elasticsearch, Logstash, Kibana) 套件为例，介绍集中式日志收集平台。\n\n为了让读者能够在生产环境中真正用起来，本章会补齐以下”最小闭环”：\n\n* 关键指标与日志的验证方法\n* 常见故障排查路径\n* 最小告警闭环 (Prometheus -> Alertmanager -> 接收端)\n* 日志容量治理的最小实践\n\n## 本章内容\n\n* [Prometheus 监控](19.1_prometheus.md)\n  * 容器监控基础、指标采集与告警配置。\n\n* [ELK 日志管理](19.2_elk.md)\n  * 集中式日志收集、存储与检索。\n\n* [性能优化](19.3_performance_optimization.md)\n  * 容器和应用性能优化实践。\n"
  },
  {
    "path": "19_observability/summary.md",
    "content": "## 本章小结\n\n本章从两个维度介绍了容器可观测性：\n\n* **指标监控**：以 Prometheus + Grafana 为主，完成指标采集、存储与可视化。\n* **日志管理**：以 EFK/ELK 为例，完成容器日志的集中采集、检索与分析。\n\n生产环境中，建议将”可观测性”当成一个完整闭环：**采集 -> 存储 -> 展示 -> 告警 -> 排错 -> 容量治理**。\n\n## 扩展阅读：Docker 日志驱动\n\nDocker 提供了多种日志驱动 (Log Driver)，用于将容器标准输出的日志转发到不同后端。\n\n常见的日志驱动包括：\n\n* `json-file`：默认驱动，将日志以 JSON 格式写入本地文件。\n* `syslog`：将日志转发到 syslog 服务器。\n* `journald`：将日志写入 systemd journal。\n* `fluentd`：将日志转发到 fluentd 收集器。\n* `gelf`：支持 GELF 协议的日志后端 (如 Graylog)。\n* `awslogs`：发送到 Amazon CloudWatch Logs。\n\n生产建议：无论采用哪种驱动，都要明确日志的保留周期、容量上限与传输可靠性，避免“日志把磁盘写满”或“链路抖动导致丢日志”。\n\n## 19.4 日志平台选型对比与注意事项\n\n日志平台通常由“采集/处理/存储/查询展示”几部分组成。常见选型包括：\n\n* **EFK/ELK**：Elasticsearch + Fluentd/Logstash + Kibana，适合全文检索与结构化查询。\n* **Loki + Grafana**：更偏“日志像指标一样存储”的思路，部署与成本可能更友好，但查询能力与使用习惯不同。\n\n选型时建议关注：\n\n* **写入压力与背压**：当存储端变慢时，采集端是否会缓冲、落盘、重试，是否会影响业务。\n* **容量治理**：是否具备按天/按大小滚动、保留策略、生命周期管理 (ILM) 等能力。\n* **安全与合规**：鉴权、TLS、审计、敏感字段脱敏。\n* **可运维性**：升级策略、备份恢复、告警指标是否齐全。\n\n## 19.5 上线前检查清单\n\n你可以用下面的清单快速检查“是否具备最小生产可用性”：\n\n* Prometheus 数据目录已持久化，并设置了合理的保留周期。\n* Prometheus Targets 全部为 `UP`，并且关键查询 (CPU/内存/容器指标) 有数据。\n* Grafana 已导入面板并能定位到具体实例/容器；默认账号密码已修改。\n* 至少有一条关键告警已打通 Alertmanager 的接收链路，并验证告警能被正确发送与抑制。\n* Elasticsearch 数据目录已持久化，并有明确的日志保留周期与容量上限策略。\n* Kibana 能查询到最新日志；当 UI 异常时能用 Elasticsearch API 验证入库。\n* 可观测性组件未直接暴露到公网，访问已加鉴权或置于内网。\n---\n\n> 📝 **发现错误或有改进建议？** 欢迎提交 [Issue](https://github.com/yeasy/docker_practice/issues) 或 [PR](https://github.com/yeasy/docker_practice/pulls)。\n"
  },
  {
    "path": "20_cases_os/20.1_busybox.md",
    "content": "## 20.1 Busybox\n\n### 20.1.1 简介\n\n下图直观地展示了本节内容：\n\n![Busybox - Linux 瑞士军刀](../_images/busybox-logo.png)\n\n\n`BusyBox` 是一个集成了一百多个最常用 Linux 命令和工具 (如 `cat`、`echo`、`grep`、`mount`、`telnet` 等) 的精简工具箱，它只需要几 MB 的大小，很方便进行各种快速验证，被誉为 “Linux 系统的瑞士军刀”。\n\n`BusyBox` 可运行于多款 `POSIX` 环境的操作系统中，如 `Linux` (包括 `Android`)、`Hurd`、`FreeBSD` 等。\n\n### 20.1.2 获取官方镜像\n\n可以使用 `docker pull` 指令下载 `busybox:latest` 镜像：\n\n```bash\n$ docker pull busybox:latest\nlatest: Pulling from library/busybox\n5c4213be9af9: Pull complete\nDigest: sha256:c6b45a95f932202dbb27c31333c4789f45184a744060f6e569cc9d2bf1b9ad6f\nStatus: Downloaded newer image for busybox:latest\ndocker.io/library/busybox:latest\n```\n\n下载后，可以看到 `busybox` 镜像只有 **2.433 MB**：\n\n```bash\n$ docker image ls\nREPOSITORY                   TAG                 IMAGE ID            CREATED             VIRTUAL SIZE\nbusybox                   latest              e72ac664f4f0        6 weeks ago         2.433 MB\n```\n\n### 20.1.3 运行 busybox\n\n启动一个 `busybox` 容器，并在容器中执行 `grep` 命令。\n\n```bash\n$ docker run -it busybox\n/ # grep\nBusyBox v1.22.1 (2014-05-22 23:22:11 UTC) multi-call binary.\n\nUsage: grep [-HhnlLoqvsriwFE] [-m N] [-A/B/C N] PATTERN/-e PATTERN.../-f FILE [FILE]...\n\nSearch for PATTERN in FILEs (or stdin)\n\n        -H      Add 'filename:' prefix\n        -h      Do not add 'filename:' prefix\n        -n      Add 'line_no:' prefix\n        -l      Show only names of files that match\n        -L      Show only names of files that don't match\n        -c      Show only count of matching lines\n        -o      Show only the matching part of line\n        -q      Quiet. Return 0 if PATTERN is found, 1 otherwise\n        -v      Select non-matching lines\n        -s      Suppress open and read errors\n        -r      Recurse\n        -i      Ignore case\n        -w      Match whole words only\n        -x      Match whole lines only\n        -F      PATTERN is a literal (not regexp)\n        -E      PATTERN is an extended regexp\n        -m N    Match up to N times per file\n        -A N    Print N lines of trailing context\n        -B N    Print N lines of leading context\n        -C N    Same as '-A N -B N'\n        -e PTRN Pattern to match\n        -f FILE Read pattern from file\n```\n\n查看容器内的挂载信息。\n\n```bash\n/ # mount\noverlay on / type overlay (rw,relatime,lowerdir=/var/lib/docker/overlay2/l/BOTCI5RF24AMC4A2UWF4N6ZWFP:/var/lib/docker/overlay2/l/TWVP5T5DMKJGXZOROR7CAPWGFP,upperdir=/var/lib/docker/overlay2/801ef0bf6cce35288dbb8fe00a4f9cc47760444693bfdf339ed0bdcf926e12a3/diff,workdir=/var/lib/docker/overlay2/801ef0bf6cce35288dbb8fe00a4f9cc47760444693bfdf339ed0bdcf926e12a3/work)\nproc on /proc type proc (rw,nosuid,nodev,noexec,relatime)\ntmpfs on /dev type tmpfs (rw,nosuid,size=65536k,mode=755)\ndevpts on /dev/pts type devpts (rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=666)\nsysfs on /sys type sysfs (ro,nosuid,nodev,noexec,relatime)\ntmpfs on /sys/fs/cgroup type tmpfs (ro,nosuid,nodev,noexec,relatime,mode=755)\ncgroup on /sys/fs/cgroup/systemd type cgroup (ro,nosuid,nodev,noexec,relatime,xattr,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd)\ncgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (ro,nosuid,nodev,noexec,relatime,net_cls,net_prio)\ncgroup on /sys/fs/cgroup/freezer type cgroup (ro,nosuid,nodev,noexec,relatime,freezer)\ncgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (ro,nosuid,nodev,noexec,relatime,cpu,cpuacct)\ncgroup on /sys/fs/cgroup/cpuset type cgroup (ro,nosuid,nodev,noexec,relatime,cpuset)\ncgroup on /sys/fs/cgroup/blkio type cgroup (ro,nosuid,nodev,noexec,relatime,blkio)\ncgroup on /sys/fs/cgroup/perf_event type cgroup (ro,nosuid,nodev,noexec,relatime,perf_event)\ncgroup on /sys/fs/cgroup/memory type cgroup (ro,nosuid,nodev,noexec,relatime,memory)\ncgroup on /sys/fs/cgroup/devices type cgroup (ro,nosuid,nodev,noexec,relatime,devices)\ncgroup on /sys/fs/cgroup/pids type cgroup (ro,nosuid,nodev,noexec,relatime,pids)\nmqueue on /dev/mqueue type mqueue (rw,nosuid,nodev,noexec,relatime)\nshm on /dev/shm type tmpfs (rw,nosuid,nodev,noexec,relatime,size=65536k)\n/dev/vda1 on /etc/resolv.conf type ext3 (rw,noatime,data=ordered)\n/dev/vda1 on /etc/hostname type ext3 (rw,noatime,data=ordered)\n/dev/vda1 on /etc/hosts type ext3 (rw,noatime,data=ordered)\ndevpts on /dev/console type devpts (rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=666)\nproc on /proc/bus type proc (ro,relatime)\nproc on /proc/fs type proc (ro,relatime)\nproc on /proc/irq type proc (ro,relatime)\nproc on /proc/sys type proc (ro,relatime)\nproc on /proc/sysrq-trigger type proc (ro,relatime)\ntmpfs on /proc/acpi type tmpfs (ro,relatime)\ntmpfs on /proc/kcore type tmpfs (rw,nosuid,size=65536k,mode=755)\ntmpfs on /proc/keys type tmpfs (rw,nosuid,size=65536k,mode=755)\ntmpfs on /proc/timer_list type tmpfs (rw,nosuid,size=65536k,mode=755)\ntmpfs on /proc/sched_debug type tmpfs (rw,nosuid,size=65536k,mode=755)\ntmpfs on /sys/firmware type tmpfs (ro,relatime)\n```\n\n`busybox` 镜像虽然小巧，但包括了大量常见的 `Linux` 命令，读者可以用它快速熟悉 `Linux` 命令。\n\n### 20.1.4 相关资源\n\n* `Busybox` 官网：https://busybox.net/\n* `Busybox` 官方仓库：https://git.busybox.net/busybox/\n* `Busybox` 官方镜像：https://hub.docker.com/\\_/busybox/\n* `Busybox` 官方仓库：https://github.com/docker-library/busybox\n"
  },
  {
    "path": "20_cases_os/20.2_alpine.md",
    "content": "## 20.2 Alpine\n\n### 20.2.1 简介\n\n下图直观地展示了本节内容：\n\n![Alpine Linux 操作系统](../_images/alpinelinux-logo.png)\n\n\n`Alpine` 操作系统是一个面向安全的轻型 `Linux` 发行版。它不同于通常 `Linux` 发行版，`Alpine` 采用了 `musl libc` 和 `busybox` 以减小系统的体积和运行时资源消耗，但功能上比 `busybox` 又完善的多，因此得到开源社区越来越多的青睐。在保持瘦身的同时，`Alpine` 还提供了自己的包管理工具 `apk`，可以通过 [Alpine Packages](https://pkgs.alpinelinux.org/packages) 网站上查询包信息，也可以直接通过 `apk` 命令直接查询和安装各种软件。\n\n\n`Alpine` 由非商业组织维护的，支持广泛场景的 `Linux` 发行版，它特别为资深/重度 `Linux` 用户而优化，关注安全，性能和资源效能。`Alpine` 镜像可以适用于更多常用场景，并且是一个优秀的可以适用于生产的基础系统/环境。\n\n`Alpine` Docker 镜像也继承了 `Alpine Linux` 发行版的这些优势。相比于其他 `Docker` 镜像，它的容量非常小，仅仅只有 **5 MB** 左右 (对比 `Ubuntu` 系列镜像接近 `200 MB`)，且拥有非常友好的包管理机制。官方镜像来自 `docker-alpine` 项目。\n\n目前 Docker 官方已开始推荐使用 `Alpine` 替代之前的 `Ubuntu` 做为基础镜像环境。这样会带来多个好处。包括镜像下载速度加快，镜像安全性提高，主机之间的切换更方便，占用更少磁盘空间等。\n\n下表是官方镜像的大小比较：\n\n```bash\nREPOSITORY          TAG           IMAGE ID          VIRTUAL SIZE\nalpine              latest        4e38e38c8ce0      4.799 MB\ndebian              latest        4d6ce913b130      84.98 MB\nubuntu              latest        b39b81afc8ca      188.3 MB\ncentos              latest        8efe422e6104      210 MB\n```\n\n### 20.2.2 获取并使用官方镜像\n\n由于镜像很小，下载时间往往很短，读者可以直接使用 `docker run` 指令直接运行一个 `Alpine` 容器，并指定运行的 Linux 指令，例如：\n\n```bash\n$ docker run alpine echo '123'\n123\n```\n\n### 20.2.3 迁移至 Alpine 基础镜像\n\n目前，大部分 Docker 官方镜像都已经支持 `Alpine` 作为基础镜像，可以很容易进行迁移。\n\n例如：\n\n* `ubuntu/debian` -> `alpine`\n* `python:3` -> `python:3-alpine`\n* `ruby:2.6` -> `ruby:2.6-alpine`\n\n另外，如果使用 `Alpine` 镜像替换 `Ubuntu` 基础镜像，安装软件包时需要用 `apk` 包管理器替换 `apt` 工具，如\n\n```bash\n$ apk add --no-cache <package>\n```\n\n`Alpine` 中软件安装包的名字可能会与其他发行版有所不同，可以在 `https://pkgs.alpinelinux.org/packages` 网站搜索并确定安装包名称。如果需要的安装包不在主索引内，但是在测试或社区索引中。那么可以按照以下方法使用这些安装包。\n\n```bash\n$ echo \"http://dl-cdn.alpinelinux.org/alpine/edge/testing\" >> /etc/apk/repositories\n$ apk --update add --no-cache <package>\n```\n\n由于在国内访问 `apk` 仓库较缓慢，建议在使用 `apk` 之前先替换仓库地址为国内镜像。\n\n```docker\nRUN sed -i \"s/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g\" /etc/apk/repositories \\\n      && apk add --no-cache <package>\n```\n\n### 20.2.4 相关资源\n\n* `Alpine` 官网：https://www.alpinelinux.org/\n* `Alpine` 官方仓库：https://github.com/alpinelinux\n* `Alpine` 官方镜像：https://hub.docker.com/\\_/alpine/\n* `Alpine` 官方镜像仓库：https://github.com/gliderlabs/docker-alpine\n"
  },
  {
    "path": "20_cases_os/20.3_debian.md",
    "content": "## 20.3 Debian Ubuntu\n\n`Debian` 和 `Ubuntu` 都是目前较为流行的 **Debian 系** 的服务器操作系统，十分适合研发场景。`Docker Hub` 上提供了官方镜像，国内各大容器云服务也基本都提供了相应的支持。\n\n### 20.3.1 Debian 系统简介\n\n下图直观地展示了本节内容：\n\n![Debian 操作系统](../_images/debian-logo.png)\n\n\n`Debian` 是由 `GPL` 和其他自由软件许可协议授权的自由软件组成的操作系统，由 **Debian 计划 (Debian Project)** 组织维护。**Debian 计划** 是一个独立的、分散的组织，由 `3000` 人志愿者组成，接受世界多个非盈利组织的资金支持，`Software in the Public Interest` 提供支持并持有商标作为保护机构。`Debian` 以其坚守 `Unix` 和自由软件的精神，以及其给予用户的众多选择而闻名。现时 `Debian` 包括了超过 `25,000` 个软件包并支持 `12` 个计算机系统结构。\n\n`Debian` 作为一个大的系统组织框架，其下有多种不同操作系统核心的分支计划，主要为采用 `Linux` 核心的 `Debian GNU/Linux` 系统，其他还有采用 `GNU Hurd` 核心的 `Debian GNU/Hurd` 系统、采用 `FreeBSD` 核心的 `Debian GNU/kFreeBSD` 系统，以及采用 `NetBSD` 核心的 `Debian GNU/NetBSD` 系统。甚至还有利用 `Debian` 的系统架构和工具，采用 `OpenSolaris` 核心构建而成的 `Nexenta OS` 系统。在这些 `Debian` 系统中，以采用 `Linux` 核心的 `Debian GNU/Linux` 最为著名。\n\n众多的 `Linux` 发行版，例如 `Ubuntu`、`Knoppix` 和 `Linspire` 及 `Xandros` 等，都基于 `Debian GNU/Linux`。\n\n#### 使用 Debian 官方镜像\n\nDebian 是一个也是一个常用的基础镜像。\n\n\n官方提供了大家熟知的 `debian` 镜像以及面向科研领域的 `neurodebian` 镜像。可以使用 `docker run` 直接运行 `Debian` 镜像。\n\n```bash\n$ docker run -it debian bash\nroot@668e178d8d69:/# cat /etc/issue\nDebian GNU/Linux 8\n```\n\n`Debian` 镜像很适合作为基础镜像，构建自定义镜像。\n\n### 20.3.2 Ubuntu 系统简介\n\n下图直观地展示了本节内容：\n\n![Ubuntu 操作系统](../_images/ubuntu-logo.jpg)\n\n\n`Ubuntu` 是一个以桌面应用为主的 `GNU/Linux` 操作系统，其名称来自非洲南部祖鲁语或豪萨语的 “ubuntu” 一词 (官方译名 “友帮拓”，另有 “吾帮托”、“乌班图”、“有奔头” 或 “乌斑兔” 等译名)。`Ubuntu` 意思是 “人性” 以及 “我的存在是因为大家的存在”，是非洲传统的一种价值观，类似华人社会的 “仁爱” 思想。`Ubuntu` 基于 `Debian` 发行版和 `GNOME/Unity` 桌面环境，与 `Debian` 的不同在于它每 6 个月会发布一个新版本，每 2 年推出一个长期支持 **(Long Term Support，LTS)** 版本，一般支持 3 年时间。\n\n#### 使用 Ubuntu 官方镜像\n\nUbuntu 是目前最流行的 Linux 发行版之一。\n\n\n下面以 `ubuntu:24.04` 为例，演示如何使用该镜像安装一些常用软件。\n\n首先使用 `-ti` 参数启动容器，登录 `bash`，查看 `ubuntu` 的发行版本号。\n\n```bash\n$ docker run -ti ubuntu:24.04 /bin/bash\nroot@7d93de07bf76:/# cat /etc/os-release\nPRETTY_NAME=\"Ubuntu 24.04 LTS\"\nNAME=\"Ubuntu\"\nVERSION_ID=\"24.04\"\nVERSION=\"24.04 LTS (Noble Numbat)\"\nVERSION_CODENAME=noble\nID=ubuntu\nID_LIKE=debian\nHOME_URL=\"https://www.ubuntu.com/\"\nSUPPORT_URL=\"https://help.ubuntu.com/\"\nBUG_REPORT_URL=\"https://bugs.launchpad.net/ubuntu/\"\n```\n\n当试图直接使用 `apt-get` 安装一个软件的时候，会提示 `E: Unable to locate package`。\n\n```bash\nroot@7d93de07bf76:/# apt-get install curl\nReading package lists... Done\nBuilding dependency tree... Done\nReading state information... Done\nE: Unable to locate package curl\n```\n\n这并非系统不支持 `apt-get` 命令。Docker 镜像在制作时为了精简清除了 `apt` 仓库信息，因此需要先执行 `apt-get update` 命令来更新仓库信息。更新信息后即可成功通过 `apt-get` 命令来安装软件。\n\n```bash\nroot@7d93de07bf76:/# apt-get update\nGet:1 http://archive.ubuntu.com/ubuntu noble InRelease [256 kB]\nGet:2 http://security.ubuntu.com/ubuntu noble-security InRelease [126 kB]\n...\nFetched 25.8 MB in 8s (3215 kB/s)\nReading package lists... Done\n```\n\n首先，安装 `curl` 工具。\n\n```bash\nroot@7d93de07bf76:/# apt-get install curl\nReading package lists... Done\nBuilding dependency tree\nReading state information... Done\nThe following additional packages will be installed:\n  ca-certificates krb5-locales libasn1-8-heimdal libcurl4 libgssapi-krb5-2 libgssapi3-heimdal libhcrypto4-heimdal libheimbase1-heimdal libheimntlm0-heimdal libhx509-5-heimdal\n  libk5crypto3 libkeyutils1 libkrb5-26-heimdal libkrb5-3 libkrb5support0 libldap-2.4-2 libldap-common libnghttp2-14 libpsl5 libroken18-heimdal librtmp1 libsasl2-2 libsasl2-modules libsasl2-modules-db libsqlite3-0 libssl1.1 libwind0-heimdal openssl publicsuffix\n...\nroot@7d93de07bf76:/# curl\ncurl: try 'curl --help' or 'curl --manual' for more information\n```\n\n接下来，再安装 `apache` 服务。\n\n```bash\nroot@7d93de07bf76:/# apt-get install -y apache2\nReading package lists... Done\nBuilding dependency tree\nReading state information... Done\nThe following additional packages will be installed:\n  apache2-bin apache2-data apache2-utils file libapr1 libaprutil1 libaprutil1-dbd-sqlite3 libaprutil1-ldap libexpat1 libgdbm-compat4 libgdbm5 libicu60 liblua5.2-0 libmagic-mgc libmagic1 libperl5.26 libxml2 mime-support netbase perl perl-modules-5.26 ssl-cert xz-utils\n...\n```\n\n启动这个 `apache` 服务，然后使用 `curl` 来测试本地访问。\n\n```bash\nroot@7d93de07bf76:/# service apache2 start\n * Starting web server apache2                                                                                                                               AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 172.17.0.2. Set the 'ServerName' directive globally to suppress this message\n *\nroot@7d93de07bf76:/# curl 127.0.0.1\n\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\n  <!--\n    Modified from the Debian original for Ubuntu\n    Last updated: 2016-11-16\n    See: https://launchpad.net/bugs/1288690\n  -->\n  <head>\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n    <title>Apache2 Ubuntu Default Page: It works</title>\n    <style type=\"text/css\" media=\"screen\">\n...\n```\n\n配合使用 `-p` 参数对外映射服务端口，可以允许容器外来访问该服务。\n\n### 20.3.3 相关资源\n\n* `Debian` 官网：https://www.debian.org/\n* `Neuro Debian` 官网：http://neuro.debian.net/\n* `Debian` 官方仓库：https://github.com/Debian\n* `Debian` 官方镜像：https://hub.docker.com/\\_/debian/\n* `Debian` 官方镜像仓库：https://github.com/tianon/docker-brew-debian/\n* `Ubuntu` 官网：https://ubuntu.com\n* `Ubuntu` 官方仓库：https://github.com/ubuntu\n* `Ubuntu` 官方镜像：https://hub.docker.com/\\_/ubuntu/\n* `Ubuntu` 官方镜像仓库：https://github.com/tianon/docker-brew-ubuntu-core\n"
  },
  {
    "path": "20_cases_os/20.4_centos.md",
    "content": "## 20.4 CentOS Fedora\n\n### 20.4.1 CentOS 系统简介\n\n`CentOS` 和 `Fedora` 都是基于 `Redhat` 的常见 Linux 分支。`CentOS` 是目前企业级服务器的常用操作系统；`Fedora` 则主要面向个人桌面用户。\n\n![CentOS 操作系统](../_images/centos-logo.png)\n\n\nCentOS (Community Enterprise Operating System，中文意思是：社区企业操作系统)，它是基于 `Red Hat Enterprise Linux` 源代码编译而成。由于 `CentOS` 与 `Redhat Linux` 源于相同的代码基础，所以很多成本敏感且需要高稳定性的公司就使用 `CentOS` 来替代商业版 `Red Hat Enterprise Linux`。`CentOS` 自身不包含闭源软件。\n\n#### 使用 CentOS 官方镜像\n\nCentOS 官方镜像的使用非常简单。\n\n\n**注意：CentOS 8 已于 2021 年 12 月 31 日停止维护 (EOL)。对于新部署，推荐使用 CentOS Stream，或 Rocky Linux、AlmaLinux 等替代发行版。**\n\n使用 `docker run` 直接运行 `CentOS 7` 镜像，并登录 `bash`。\n\n```bash\n$ docker run -it centos:7 bash\nUnable to find image 'centos:7' locally\n7: Pulling from library/centos\n3d8673bd162a: Pull complete\nDigest: sha256:a66ffcb73930584413de83311ca11a4cb4938c9b2521d331026dad970c19adf4\nStatus: Downloaded newer image for centos:7\n[root@43eb3b194d48 /]# cat /etc/redhat-release\nCentOS Linux release 7.9.2009 (Core)\n```\n\n### 20.4.2 Fedora 系统简介\n\n下图直观地展示了本节内容：\n\n![Fedora 操作系统](../_images/fedora-logo.png)\n\n\n`Fedora` 由 `Fedora Project` 社区开发，红帽公司赞助的 `Linux` 发行版。它的目标是创建一套新颖、多功能并且自由和开源的操作系统。`Fedora` 的功能对于用户而言，它是一套功能完备的，可以更新的免费操作系统，而对赞助商 `Red Hat` 而言，它是许多新技术的测试平台。被认为可用的技术最终会加入到 `Red Hat Enterprise Linux` 中。\n\n#### 使用 Fedora 官方镜像\n\n使用 `docker run` 命令直接运行 `Fedora` 官方镜像，并登录 `bash`。\n\n```bash\n$ docker run -it fedora bash\nUnable to find image 'fedora:latest' locally\nlatest: Pulling from library/fedora\n2bf01635e2a0: Pull complete\nDigest: sha256:64a02df6aac27d1200c2572fe4b9949f1970d05f74d367ce4af994ba5dc3669e\nStatus: Downloaded newer image for fedora:latest\n[root@196ca341419b /]# cat /etc/redhat-release\nFedora release 39 (Thirty Nine)\n```\n\n\n### 20.4.3 相关资源\n\n* `Fedora` 官网：https://getfedora.org/\n* `Fedora` 官方仓库：https://github.com/fedora-infra\n* `Fedora` 官方镜像：https://hub.docker.com/\\_/fedora/\n* `Fedora` 官方镜像仓库：https://github.com/fedora-cloud/docker-brew-fedora\n* `CentOS` 官网：https://www.centos.org\n* `CentOS` 官方仓库：https://github.com/CentOS\n* `CentOS` 官方镜像：https://hub.docker.com/\\_/centos/\n* `CentOS` 官方镜像仓库：https://github.com/CentOS/CentOS-Dockerfiles\n"
  },
  {
    "path": "20_cases_os/README.md",
    "content": "# 第二十章 实战案例 - 操作系统\n\n## 章节概述\n\n本章将介绍 Docker 在不同操作系统镜像场景下的实战案例。当你构建容器化应用时，选择合适的基础镜像至关重要。不同的操作系统镜像在大小、功能和性能方面各有特点，适用于不同的使用场景。本章通过具体的案例，详细讲解如何在 Docker 中使用主流操作系统镜像，包括轻量级镜像 (Busybox、Alpine) 和完整功能镜像 (Debian、Ubuntu、CentOS 等)。\n\n## 为什么选择合适的操作系统镜像很重要\n\n在容器化应用开发中，选择合适的基础操作系统镜像直接影响容器的大小、启动速度、安全性和运行性能。不同的镜像提供了不同的功能集和资源占用：\n\n- **轻量级镜像** (Busybox、Alpine) - 镜像大小仅几 MB，启动快速，适合微服务、IoT 设备和对资源敏感的环境。Busybox 是最小的选择，集成了常见的 Unix 工具；Alpine 则提供了完整的包管理器，方便安装额外工具。\n- **通用镜像** (Debian、Ubuntu) - 提供完整的 Linux 功能和丰富的软件生态，镜像大小通常在 100-300 MB 之间。适合需要灵活安装各种依赖和工具的应用场景。\n- **企业级镜像** (CentOS、Fedora) - 基于 Red Hat 生态，广泛应用于企业环境和复杂系统应用。提供了 yum 包管理器和强大的系统管理工具。\n\n选择镜像的关键原则是 “小而够用”——选择满足应用需求的最小镜像。这样可以减少安全漏洞表面积、加快镜像拉取和推送速度、降低存储成本，同时也使容器更便于分发和部署。\n\n## 常用操作系统镜像对比\n\n| 镜像 | 大小 | 包管理器 | 适用场景 | 优势 |\n|------|------|--------|--------|------|\n| **Busybox** | ~1 MB | 无 | 最小化工具集、initrd | 极致轻量，启动秒级 |\n| **Alpine** | ~5 MB | apk | 微服务、静态应用 | 体积小，有包管理器 |\n| **Debian** | ~100 MB | apt-get | 通用应用、开发环境 | 软件包丰富，稳定性强 |\n| **Ubuntu** | ~80 MB | apt-get | 类似 Debian，现代化系统 | 更新频繁，用户多 |\n| **CentOS** | ~200 MB | yum | 企业应用、兼容性需求 | 企业级支持，稳定性高 |\n| **Fedora** | ~200 MB | dnf | 新特性需求、开发环境 | 最新技术栈，创新性强 |\n\n## 学习目标\n\n通过学习本章内容，你将能够：\n\n- 理解不同操作系统镜像的特点、大小和适用场景\n- 掌握在 Docker 中使用各类操作系统镜像的方法和最佳实践\n- 学习如何根据实际需求选择合适的基础镜像，实现镜像优化\n- 了解如何在不同操作系统容器中安装、配置和管理应用程序\n- 掌握多阶段构建等高级技巧，最小化最终镜像大小\n- 学会使用 Docker Compose 编排多个操作系统容器环境\n\n## 章节内容导航\n\n* [Busybox](20.1_busybox.md) — 超轻量级工具集镜像，适合嵌入式和最小化容器\n* [Alpine](20.2_alpine.md) — 轻量级 Linux 镜像，广泛用于生产环境微服务\n* [Debian Ubuntu](20.3_debian.md) — 功能完整的通用 Linux 镜像，生态丰富\n* [CentOS Fedora](20.4_centos.md) — 企业级 Linux 镜像，适合复杂系统应用\n* [本章小结](summary.md)\n"
  },
  {
    "path": "20_cases_os/summary.md",
    "content": "## 本章小结\n\n本章讲解了典型操作系统镜像的下载和使用。\n\n除了官方的镜像外，在 `Docker Hub` 上还有许多第三方组织或个人上传的 Docker 镜像。\n\n读者可以根据具体情况来选择。一般来说：\n\n* 官方镜像体积都比较小，只带有一些基本的组件。精简的系统有利于安全、稳定和高效的运行，也适合进行个性化定制。\n\n* 出于安全考虑，几乎所有官方制作的镜像都没有安装 SSH 服务，无法通过用户名和密码直接登录到容器中。\n---\n\n> 📝 **发现错误或有改进建议？** 欢迎提交 [Issue](https://github.com/yeasy/docker_practice/issues) 或 [PR](https://github.com/yeasy/docker_practice/pulls)。\n"
  },
  {
    "path": "21_case_devops/21.1_devops_workflow.md",
    "content": "## 21.1 DevOps 完整工作流\n\n本章将演示一个基于 Docker、Kubernetes 和 Jenkins/GitLab CI 的完整 DevOps 工作流。\n\n### 21.1.1 工作流概览\n\n1. **Code**：开发人员提交代码到 GitLab。\n2. **Build**：GitLab CI 触发构建任务。\n3. **Test**：运行单元测试和集成测试。\n4. **Package**：构建 Docker 镜像并推送到 Harbor/Registry。\n5. **Deploy (Staging)**：自动部署到测试环境 Kubernetes 集群。\n6. **Verify**：人工或自动化验证。\n7. **Release (Production)**：审批后自动部署到生产环境。\n\n### 21.1.2 关键配置示例\n\n本节通过一组最小可用的片段，展示典型 DevOps 流程中与 Docker 相关的关键配置。\n\n#### 1. Dockerfile 多阶段构建\n\n使用 Docker 多阶段构建可以有效减小镜像体积。\n\n```dockerfile\n## Build stage\n\nFROM golang:1.23 AS builder\nWORKDIR /app\nCOPY . .\nRUN go build -o main .\n\n## Final stage\n\nFROM alpine:latest\nWORKDIR /app\nCOPY --from=builder /app/main .\nCMD [\"./main\"]\n```\n\n#### 2. GitLab CI 配置\n\nGitLab CI（`.gitlab-ci.yml`）配置如下：\n\n```yaml\nstages:\n  - test\n  - build\n  - deploy\n\nunit_test:\n  stage: test\n  image: golang:1.23\n  script:\n    - go test ./...\n\nbuild_image:\n  stage: build\n  image: docker:27\n  services:\n    - docker:27-dind\n  script:\n    - echo \"$CI_REGISTRY_PASSWORD\" | docker login -u \"$CI_REGISTRY_USER\" --password-stdin $CI_REGISTRY\n    - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .\n    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA\n\ndeploy_staging:\n  stage: deploy\n  image: dtzar/helm-kubectl\n  script:\n    - kubectl config set-cluster k8s --server=$KUBE_URL --insecure-skip-tls-verify=true\n    - kubectl config set-credentials admin --token=$KUBE_TOKEN\n    - kubectl config set-context default --cluster=k8s --user=admin\n    - kubectl config use-context default\n    - kubectl set image deployment/myapp myapp=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA\n      -n staging\n  only:\n    - develop\n```\n\n### 21.1.3 最佳实践\n\n1. **不可变基础设施**：一旦镜像构建完成，在各个环境（Dev、Staging、Prod）中都应该使用同一个镜像 tag（通常是 commit hash），而不是重新构建。\n2. **配置分离**：使用 ConfigMap 和 Secret 管理环境特定的配置，不要打包进镜像。\n3. **应对 Docker Hub 限额 (Rate Limits)**：\n   - Docker Hub 对匿名拉取实施了严格的限制 (6 小时内约 100 次)。若在 CI/CD 中频繁构建，极易触发 `toomanyrequests` 错误。\n   - **最佳策略**：\n     - 在流水线开头始终执行安全的身份认证 (使用 PAT，而非密码)。\n     - 将常用的基础镜像缓存到自建的 Harbor/Nexus，使用 Pull-Through Cache。\n     - 开启 Docker 构建引擎 (BuildKit) 的 inline 或 registry 缓存功能，以降低全量拉取频率。\n4. **GitOps**：考虑引入 ArgoCD，将部署配置也作为代码存储在 Git 中，实现 Git 驱动的部署同步。\n"
  },
  {
    "path": "21_case_devops/21.2_github_actions.md",
    "content": "## 21.2 GitHub Actions\n\nGitHub [Actions](https://github.com/features/actions) 是 GitHub 推出的一款 CI/CD 工具。\n\n我们可以在每个 `job` 的 `step` 中使用 Docker 执行构建步骤。\n\n## 21.2.1 最小可用示例\n\n在仓库根目录创建 `/.github/workflows/ci.yml`：\n\n```yaml\nname: CI\n\non:\n  push:\n  pull_request:\n\npermissions:\n  contents: read\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: docker/setup-buildx-action@v3\n      - uses: docker/build-push-action@v6\n        with:\n          context: .\n          push: false\n          tags: local/test:ci\n```\n\n该示例会在 GitHub Actions 中构建当前仓库的 Docker 镜像（不推送到 registry）。\n\n## 21.2.2 最佳实践\n\n* 固定 action 的主版本（例如 `@v4` / `@v6`），避免使用 `@master` 这类浮动引用。\n* 设置最小权限（例如 `contents: read`），需要写入权限时再打开。\n* 需要依赖缓存时，优先使用官方支持的缓存方案（例如针对语言包管理器的 cache 或 BuildKit cache）。\n\n如果你需要在某个步骤里直接运行容器镜像（而不是构建镜像），可以使用 `docker://` 语法：\n\n```yaml\n- name: Run container step\n  uses: docker://golang:alpine\n  with:\n    args: go version\n```\n\n## 21.2.3 参考资料\n\n* [Actions Docs](https://docs.github.com/en/actions)\n"
  },
  {
    "path": "21_case_devops/21.3_drone.md",
    "content": "## 21.3 Drone\n\n基于 `Docker` 的 `CI/CD` 工具 `Drone`，所有编译、测试的流程都在容器中进行。\n\n开发者只需在项目中包含 `.drone.yml` 文件，将代码推送到 git 仓库，`Drone` 就能够自动化地进行编译、测试、发布。\n\n本小节以 `GitHub` + `Drone` 来演示 `Drone` 的工作流程。\n当然在实际开发过程中，你的代码也许不在 GitHub 托管，那么你可以尝试使用 `Gogs` + `Drone` 来进行 CI/CD。\n\n## 21.3.1 关联项目\n\n在 GitHub 新建一个名为 `drone-demo` 的仓库。\n\n打开我们已经部署好的 Drone 网站或者 [Drone Cloud](https://cloud.drone.io)，\n使用 GitHub 账号登录，在界面中关联刚刚新建的 `drone-demo` 仓库。\n\n## 21.3.2 编写项目源代码\n\n初始化一个 git 仓库：\n\n```bash\nmkdir drone-demo\ncd drone-demo\n\ngit init\n\ngit remote add origin git@github.com:username/drone-demo.git\n```\n\n这里以一个简单的 `Go` 程序为例，该程序输出 `Hello World!`\n\n编写 `app.go` 文件：\n\n```go\npackage main\n\nimport \"fmt\"\n\nfunc main() {\n    fmt.Printf(\"Hello World!\\n\")\n}\n```\n\n编写 `.drone.yml` 文件：\n\n```yaml\nkind: pipeline\ntype: docker\nname: build\n\nsteps:\n  - name: build\n    image: golang:alpine\n    pull: if-not-exists\n    environment:\n      KEY: VALUE\n    commands:\n      - echo $KEY\n      - pwd\n      - ls\n      - CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .\n      - ./app\n\ntrigger:\n  branch:\n    - master\n```\n\n现在目录结构如下：\n\n```bash\n.\n├── .drone.yml\n└── app.go\n```\n\n## 21.3.3 推送项目源代码到 GitHub\n\n```bash\ngit add .\n\ngit commit -m \"test drone ci\"\n\ngit push origin master\n```\n\n## 21.3.4 查看项目构建过程及结果\n\n打开我们部署好的 `Drone` 网站或者 Drone Cloud，即可看到构建结果。\n\n![图](../_images/drone-build.png)\n\n当然我们也可以把构建结果上传到 GitHub、Docker Registry、\n云服务商提供的对象存储，或者生产环境中。\n\n## 21.3.5 参考链接\n\n* [Drone Github](https://github.com/drone/drone)\n* [Drone 文档](https://docs.drone.io/)\n* [Drone 示例](https://github.com/docker-practice/drone-demo)\n"
  },
  {
    "path": "21_case_devops/21.4_drone_demo.md",
    "content": "## 21.4 Drone Demo\n\n## 21.4.1 Demo 项目说明\n\n这是一个基于 Go 语言编写的简单 Web 应用示例，用于演示 Drone CI 的持续集成流程。\n\n## 21.4.2 目录结构\n\n* `drone_demo.app.go`：简单的 Go Web 服务器代码。\n* `drone_demo.drone.yml`：Drone CI 的配置文件，定义了构建和测试流程。\n\n## 21.4.3 如何使用\n\n1. 确保本地已安装 Docker 环境。\n2. 将示例文件重命名为 Drone 期望的文件名：\n\n   ```bash\n   cp drone_demo.app.go app.go\n   cp drone_demo.drone.yml .drone.yml\n   ```\n\n3. 将 `app.go` 与 `.drone.yml` 推送到你的 `drone-demo` 仓库，即可在 Drone 中看到构建结果。\n"
  },
  {
    "path": "21_case_devops/21.5_ide.md",
    "content": "## 21.5 在 IDE 中使用 Docker\n\n使用 IDE 进行开发，往往要求本地安装好工具链。一些 IDE 支持 Docker 容器中的工具链，这样充分利用了 Docker 的优点，而无需在本地安装。\n\n本节关注一个核心目标：**把“开发依赖”放进容器，把“源码编辑体验”留在本地 IDE**。\n\n## 21.5.1 适用场景\n\n* 团队希望统一开发环境（Go/Node/Python 版本、系统依赖、编译链）。\n* 本地系统不方便安装依赖（例如 Windows、公司管控环境）。\n* 项目依赖较重（例如需要 `gcc`、数据库客户端、特定系统库）。\n\n不太适合的场景：强依赖本机 GPU/USB 设备、或需要非常低延迟文件 IO 的工程（此时可能需要额外调优挂载/同步策略）。\n\n## 21.5.2 最小可用模式：docker compose + 开发容器\n\n下面用一个“长期运行的开发容器”作为例子（以 Go 为例，你可以替换为 Node/Python）。\n\n1. 在项目中创建 `compose.yaml`（或复用你已有的 compose 文件）：\n\n   ```yaml\n   services:\n     dev:\n       image: golang:1.22\n       working_dir: /work\n       volumes:\n         - ./:/work\n       command: sleep infinity\n   ```\n\n1. 启动开发容器：\n\n   ```bash\n   docker compose up -d\n   ```\n\n1. 进入容器安装依赖/执行命令：\n\n   ```bash\n   docker compose exec dev bash\n   go version\n   go test ./...\n   ```\n\n这个模式的优点是“简单直接、IDE 无关”，缺点是 IDE 需要额外配置\n（例如配置远程解释器/语言服务，或使用 VS Code Dev Containers）。\n\n## 21.5.3 目录挂载与权限建议\n\n* Linux 下如果遇到容器内写文件权限问题，优先确保容器内用户与宿主机 UID/GID 对齐。\n  VS Code Dev Containers 支持自动处理；手写 Dockerfile/compose 时也可以显式设置用户。\n* 如果遇到文件变更监听不生效（常见于 macOS/Windows 的虚拟化文件系统），\n  优先使用语言/工具支持的轮询模式或提高 watcher 限制。\n"
  },
  {
    "path": "21_case_devops/21.6_vsCode.md",
    "content": "## 21.6 VS Code\n\nVS Code 的 [Dev Containers](https://code.visualstudio.com/docs/devcontainers/containers)\n可以把“开发环境”放进容器，同时保留 VS Code 的编辑、补全、调试体验。\n\n本节提供一个最小可用示例：把任意项目（以 Go 为例）变成“打开即开发”的容器化环境。\n\n## 21.6.1 前置条件\n\n* 安装 Docker Desktop（或 Linux 上的 Docker Engine）。\n* VS Code 安装扩展：Dev Containers（`ms-vscode-remote.remote-containers`）。\n\n## 21.6.2 最小示例：.devcontainer/devcontainer.json\n\n在项目根目录创建 `.devcontainer/devcontainer.json`：\n\n```json\n{\n  \"name\": \"docker-practice-dev\",\n  \"image\": \"golang:1.22\",\n  \"workspaceFolder\": \"/work\",\n  \"workspaceMount\": \"source=${localWorkspaceFolder},target=/work,type=bind\",\n  \"customizations\": {\n    \"vscode\": {\n      \"extensions\": [\n        \"golang.Go\"\n      ]\n    }\n  },\n  \"postCreateCommand\": \"go version\"\n}\n```\n\n然后在 VS Code 命令面板选择：\n\n* `Dev Containers: Reopen in Container`\n\nVS Code 会拉取镜像并启动容器，随后你就可以在容器内运行：\n\n```bash\ngo test ./...\n```\n\n## 21.6.3 结合 Docker Compose（可选）\n\n如果项目同时依赖数据库/缓存（例如 Postgres/Redis），可以使用 `dockerComposeFile`\n把依赖一起拉起。\n\n示例（`devcontainer.json` 片段）：\n\n```json\n{\n  \"name\": \"compose-dev\",\n  \"dockerComposeFile\": [\n    \"../docker-compose.yml\"\n  ],\n  \"service\": \"dev\",\n  \"workspaceFolder\": \"/work\"\n}\n```\n\n注意：`service` 需要对应 compose 里的服务名。\n"
  },
  {
    "path": "21_case_devops/21.7_practical_examples.md",
    "content": "## 21.7 实战案例：Go/Rust/数据库/微服务\n\n本节通过实际项目案例演示如何为不同类型的应用构建最优化的 Docker 镜像，以及如何使用 Docker Compose 构建完整的开发和生产环境。\n\n### 21.7.1 Go 应用的最小化镜像构建\n\nGo 语言因其编译为静态二进制和快速启动而特别适合容器化。以下展示如何构建极小的 Go 应用镜像。\n\n#### 超小 Go Web 服务\n\n**应用代码（main.go）：**\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"net/http\"\n\t\"os\"\n)\n\nfunc healthHandler(w http.ResponseWriter, r *http.Request) {\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tfmt.Fprintf(w, `{\"status\":\"healthy\",\"version\":\"1.0.0\"}`)\n}\n\nfunc helloHandler(w http.ResponseWriter, r *http.Request) {\n\thostname, _ := os.Hostname()\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tfmt.Fprintf(w, `{\"message\":\"Hello from %s\",\"version\":\"1.0.0\"}`, hostname)\n}\n\nfunc main() {\n\thttp.HandleFunc(\"/health\", healthHandler)\n\thttp.HandleFunc(\"/hello\", helloHandler)\n\thttp.HandleFunc(\"/\", helloHandler)\n\n\tport := \":8080\"\n\tlog.Printf(\"Server starting on %s\", port)\n\n\tif err := http.ListenAndServe(port, nil); err != nil {\n\t\tlog.Fatalf(\"Server failed: %v\", err)\n\t}\n}\n```\n\n**多阶段 Dockerfile：**\n\n```dockerfile\n# Stage 1: 构建阶段\nFROM golang:1.20-alpine AS builder\n\nWORKDIR /build\n\n# 安装构建依赖\nRUN apk add --no-cache git ca-certificates tzdata\n\n# 复制模块文件（利用缓存）\nCOPY go.mod go.sum ./\nRUN go mod download\n\n# 复制源代码\nCOPY . .\n\n# 构建静态二进制\n# -ldflags=\"-w -s\" 去除调试符号减小体积\nRUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \\\n    -a -installsuffix cgo \\\n    -ldflags=\"-w -s -X main.Version=1.0.0 -X main.BuildTime=$(date -u +'%Y-%m-%dT%H:%M:%SZ')\" \\\n    -o app .\n\n# Stage 2: 运行阶段（scratch 镜像）\nFROM scratch\n\n# 复制 CA 证书（用于 HTTPS）\nCOPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/\n\n# 复制时区数据（用于时间处理）\nCOPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo\n\n# 复制应用二进制\nCOPY --from=builder /build/app /app\n\nEXPOSE 8080\n\n# 使用绝对路径作为 ENTRYPOINT\nENTRYPOINT [\"/app\"]\n```\n\n**构建和测试：**\n\n```bash\n# 构建镜像\ndocker build -t go-app:latest .\n\n# 检查镜像大小\ndocker images go-app\n\n# 运行容器\ndocker run -d -p 8080:8080 --name go-demo go-app:latest\n\n# 测试应用\ncurl http://localhost:8080/health | jq .\n\n# 进入容器验证\ndocker exec go-demo ls -la /\n# 只包含 /app 和系统必要文件\n\n# 镜像大小通常 < 10MB（相比 golang:1.20-alpine 的 ~1GB）\ndocker history go-app:latest\n```\n\n**go.mod 和 go.sum 示例：**\n\n```text\nmodule github.com/example/go-app\n\ngo 1.20\n\nrequire (\n    // 如果需要依赖\n)\n```\n\n#### 带依赖的 Go 应用\n\n**应用代码（使用 Gin 框架）：**\n\n```go\npackage main\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\t\"log\"\n)\n\nfunc main() {\n\trouter := gin.Default()\n\n\trouter.GET(\"/health\", func(c *gin.Context) {\n\t\tc.JSON(200, gin.H{\n\t\t\t\"status\": \"ok\",\n\t\t})\n\t})\n\n\trouter.GET(\"/api/users\", func(c *gin.Context) {\n\t\tc.JSON(200, gin.H{\n\t\t\t\"users\": []string{\"alice\", \"bob\"},\n\t\t})\n\t})\n\n\tlog.Fatal(router.Run(\":8080\"))\n}\n```\n\n**优化的 Dockerfile：**\n\n```dockerfile\nFROM golang:1.20-alpine AS builder\n\nWORKDIR /src\n\nRUN apk add --no-cache git ca-certificates tzdata\n\nCOPY go.mod go.sum ./\nRUN go mod download\n\nCOPY . .\n\nRUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \\\n    go build -a -installsuffix cgo \\\n    -ldflags=\"-w -s\" \\\n    -o app .\n\n# 最终镜像\nFROM alpine:3.17\n\nRUN apk add --no-cache ca-certificates tzdata\n\nWORKDIR /root/\n\nCOPY --from=builder /src/app .\n\nEXPOSE 8080\n\nCMD [\"./app\"]\n```\n\n### 21.7.2 Rust 应用的最小化镜像构建\n\nRust 因其性能和安全性在系统级应用中备受青睐。\n\n**应用代码（main.rs）：**\n\n```rust\nuse actix_web::{web, App, HttpServer, HttpResponse};\nuse std::sync::Mutex;\n\n#[actix_web::main]\nasync fn main() -> std::io::Result<()> {\n    println!(\"Starting server on 0.0.0.0:8080\");\n\n    HttpServer::new(|| {\n        App::new()\n            .route(\"/health\", web::get().to(health))\n            .route(\"/hello\", web::get().to(hello))\n    })\n    .bind(\"0.0.0.0:8080\")?\n    .run()\n    .await\n}\n\nasync fn health() -> HttpResponse {\n    HttpResponse::Ok().json(serde_json::json!({\n        \"status\": \"healthy\"\n    }))\n}\n\nasync fn hello() -> HttpResponse {\n    HttpResponse::Ok().json(serde_json::json!({\n        \"message\": \"Hello from Rust\"\n    }))\n}\n```\n\n**Cargo.toml：**\n\n```toml\n[package]\nname = \"rust-app\"\nversion = \"0.1.0\"\nedition = \"2021\"\n\n[[bin]]\nname = \"rust-app\"\npath = \"src/main.rs\"\n\n[dependencies]\nactix-web = \"4.4\"\ntokio = { version = \"1.35\", features = [\"full\"] }\nserde = { version = \"1.0\", features = [\"derive\"] }\nserde_json = \"1.0\"\n```\n\n**多阶段构建 Dockerfile：**\n\n```dockerfile\n# Stage 1: 编译\nFROM rust:1.75-alpine AS builder\n\nRUN apk add --no-cache musl-dev\n\nWORKDIR /src\n\nCOPY Cargo.* ./\nCOPY src ./src\n\n# 构建优化的发布版本\nRUN cargo build --release\n\n# Stage 2: 运行镜像\nFROM alpine:3.17\n\nRUN apk add --no-cache ca-certificates\n\nCOPY --from=builder /src/target/release/rust-app /app\n\nEXPOSE 8080\n\nCMD [\"/app\"]\n```\n\n**构建和验证：**\n\n```bash\ndocker build -t rust-app:latest .\ndocker run -d -p 8080:8080 rust-app:latest\ncurl http://localhost:8080/health | jq .\n\n# Rust 应用通常比 Go 更小：5-20MB（取决于依赖）\ndocker images rust-app\n```\n\n### 21.7.3 数据库容器化最佳实践\n\n#### PostgreSQL 生产部署\n\n**自定义 PostgreSQL 镜像：**\n\n```dockerfile\nFROM postgres:16-alpine\n\n# 安装额外工具\nRUN apk add --no-cache \\\n    postgresql-contrib \\\n    pg-stat-monitor \\\n    curl\n\n# 复制初始化脚本\nCOPY init-db.sql /docker-entrypoint-initdb.d/\nCOPY health-check.sh /\n\nRUN chmod +x /health-check.sh\n\nHEALTHCHECK --interval=10s --timeout=5s --start-period=40s --retries=3 \\\n    CMD /health-check.sh\n\nEXPOSE 5432\n```\n\n**初始化脚本（init-db.sql）：**\n\n```sql\n-- 创建自定义用户\nCREATE USER appuser WITH PASSWORD 'secure_password';\n\n-- 创建数据库\nCREATE DATABASE myappdb OWNER appuser;\n\n-- 创建扩展\n\\c myappdb\n\nCREATE EXTENSION IF NOT EXISTS uuid-ossp;\nCREATE EXTENSION IF NOT EXISTS hstore;\nCREATE EXTENSION IF NOT EXISTS pg_trgm;\n\n-- 创建表\nCREATE TABLE users (\n    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),\n    username VARCHAR(255) NOT NULL UNIQUE,\n    email VARCHAR(255) NOT NULL UNIQUE,\n    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP\n);\n\n-- 创建索引\nCREATE INDEX idx_users_username ON users (username);\nCREATE INDEX idx_users_email ON users (email);\n\n-- 授予权限\nGRANT CONNECT ON DATABASE myappdb TO appuser;\nGRANT USAGE ON SCHEMA public TO appuser;\nGRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO appuser;\nGRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO appuser;\n```\n\n**健康检查脚本（health-check.sh）：**\n\n```bash\n#!/bin/bash\n\nPGPASSWORD=$POSTGRES_PASSWORD pg_isready \\\n  -h localhost \\\n  -U $POSTGRES_USER \\\n  -d $POSTGRES_DB \\\n  -p 5432 > /dev/null 2>&1\n\nexit $?\n```\n\n**Docker Compose 配置：**\n\n```yaml\nversion: '3.9'\n\nservices:\n  postgres:\n    build:\n      context: .\n      dockerfile: Dockerfile.postgres\n    container_name: postgres-db\n    environment:\n      POSTGRES_DB: myappdb\n      POSTGRES_USER: postgres\n      POSTGRES_PASSWORD: postgres_password\n      POSTGRES_INITDB_ARGS: \"--encoding=UTF8 --locale=en_US.UTF-8\"\n    volumes:\n      - postgres_data:/var/lib/postgresql/data\n      - ./backups:/backups\n    ports:\n      - \"5432:5432\"\n    networks:\n      - backend\n    restart: unless-stopped\n    logging:\n      driver: \"json-file\"\n      options:\n        max-size: \"10m\"\n        max-file: \"3\"\n\n  # 备份服务\n  backup:\n    image: postgres:16-alpine\n    depends_on:\n      - postgres\n    environment:\n      PGPASSWORD: postgres_password\n    volumes:\n      - ./backups:/backups\n    command: |\n      sh -c 'while true; do\n        pg_dump -h postgres -U postgres -d myappdb > /backups/backup_$$(date +%Y%m%d_%H%M%S).sql\n        echo \"Backup completed at $$(date)\"\n        sleep 86400\n      done'\n    networks:\n      - backend\n\nvolumes:\n  postgres_data:\n    driver: local\n\nnetworks:\n  backend:\n    driver: bridge\n```\n\n**性能优化配置：**\n\n```yaml\nversion: '3.9'\n\nservices:\n  postgres:\n    image: postgres:16-alpine\n    environment:\n      POSTGRES_DB: myappdb\n    command:\n      - \"postgres\"\n      - \"-c\"\n      - \"max_connections=200\"\n      - \"-c\"\n      - \"shared_buffers=256MB\"\n      - \"-c\"\n      - \"effective_cache_size=1GB\"\n      - \"-c\"\n      - \"maintenance_work_mem=64MB\"\n      - \"-c\"\n      - \"checkpoint_completion_target=0.9\"\n      - \"-c\"\n      - \"wal_buffers=16MB\"\n      - \"-c\"\n      - \"default_statistics_target=100\"\n      - \"-c\"\n      - \"random_page_cost=1.1\"\n      - \"-c\"\n      - \"effective_io_concurrency=200\"\n      - \"-c\"\n      - \"work_mem=1310kB\"\n      - \"-c\"\n      - \"min_wal_size=1GB\"\n      - \"-c\"\n      - \"max_wal_size=4GB\"\n      - \"-c\"\n      - \"max_worker_processes=4\"\n      - \"-c\"\n      - \"max_parallel_workers_per_gather=2\"\n      - \"-c\"\n      - \"max_parallel_workers=4\"\n      - \"-c\"\n      - \"max_parallel_maintenance_workers=2\"\n    volumes:\n      - postgres_data:/var/lib/postgresql/data\n    ports:\n      - \"5432:5432\"\n\nvolumes:\n  postgres_data:\n```\n\n#### MySQL/MariaDB 部署\n\n```dockerfile\nFROM mariadb:11\n\n# 复制自定义配置\nCOPY my.cnf /etc/mysql/conf.d/custom.cnf\n\n# 初始化脚本\nCOPY init.sql /docker-entrypoint-initdb.d/\n\nEXPOSE 3306\n\nHEALTHCHECK --interval=30s --timeout=10s --retries=3 \\\n    CMD mariadb-admin ping -h localhost || exit 1\n```\n\n**自定义 my.cnf：**\n\n```ini\n[mysqld]\n# 性能优化\nmax_connections = 200\ndefault_storage_engine = InnoDB\ninnodb_buffer_pool_size = 1GB\ninnodb_log_file_size = 256MB\nquery_cache_type = 0\nquery_cache_size = 0\n\n# 日志配置\nlog_error = /var/log/mysql/error.log\nslow_query_log = 1\nslow_query_log_file = /var/log/mysql/slow.log\nlong_query_time = 2\n\n# 复制配置\nserver_id = 1\nlog_bin = mysql-bin\nbinlog_format = ROW\n```\n\n#### Redis 缓存部署\n\n```dockerfile\nFROM redis:7-alpine\n\n# 复制 Redis 配置\nCOPY redis.conf /usr/local/etc/redis/redis.conf\n\n# 使用配置文件启动\nCMD [\"redis-server\", \"/usr/local/etc/redis/redis.conf\"]\n\nEXPOSE 6379\n\nHEALTHCHECK --interval=5s --timeout=3s --retries=5 \\\n    CMD redis-cli ping || exit 1\n```\n\n**redis.conf 配置：**\n\n```text\n# 绑定地址\nbind 0.0.0.0\n\n# 端口\nport 6379\n\n# 密码保护\nrequirepass your_secure_password\n\n# 内存管理\nmaxmemory 512mb\nmaxmemory-policy allkeys-lru\n\n# 持久化\nsave 900 1\nsave 300 10\nsave 60 10000\n\n# AOF 持久化\nappendonly yes\nappendfsync everysec\n\n# 日志\nloglevel notice\nlogfile \"\"\n\n# 客户端输出缓冲限制\nclient-output-buffer-limit normal 0 0 0\nclient-output-buffer-limit slave 256mb 64mb 60\nclient-output-buffer-limit pubsub 32mb 8mb 60\n```\n\n### 21.7.4 微服务架构的 Docker Compose 编排\n\n**三层微服务架构示例：**\n\n```yaml\nversion: '3.9'\n\nservices:\n  # 前端服务\n  frontend:\n    build:\n      context: ./frontend\n      dockerfile: Dockerfile\n    container_name: frontend\n    ports:\n      - \"3000:3000\"\n    environment:\n      REACT_APP_API_URL: http://localhost:8000\n      NODE_ENV: production\n    depends_on:\n      - api\n    networks:\n      - frontend-network\n    restart: unless-stopped\n    healthcheck:\n      test: [\"CMD\", \"curl\", \"-f\", \"http://localhost:3000\"]\n      interval: 30s\n      timeout: 10s\n      retries: 3\n\n  # API 服务\n  api:\n    build:\n      context: ./api\n      dockerfile: Dockerfile\n    container_name: api\n    ports:\n      - \"8000:8000\"\n    environment:\n      DATABASE_URL: postgresql://appuser:password@postgres:5432/myappdb\n      REDIS_URL: redis://redis:6379\n      LOG_LEVEL: info\n    depends_on:\n      postgres:\n        condition: service_healthy\n      redis:\n        condition: service_healthy\n    networks:\n      - frontend-network\n      - backend-network\n    restart: unless-stopped\n    healthcheck:\n      test: [\"CMD\", \"curl\", \"-f\", \"http://localhost:8000/health\"]\n      interval: 30s\n      timeout: 10s\n      retries: 3\n    deploy:\n      resources:\n        limits:\n          cpus: '1'\n          memory: 512M\n        reservations:\n          cpus: '0.5'\n          memory: 256M\n\n  # PostgreSQL 数据库\n  postgres:\n    image: postgres:16-alpine\n    container_name: postgres\n    environment:\n      POSTGRES_DB: myappdb\n      POSTGRES_USER: appuser\n      POSTGRES_PASSWORD: password\n    volumes:\n      - postgres_data:/var/lib/postgresql/data\n      - ./db/init.sql:/docker-entrypoint-initdb.d/init.sql\n    networks:\n      - backend-network\n    restart: unless-stopped\n    healthcheck:\n      test: [\"CMD-SHELL\", \"pg_isready -U appuser -d myappdb\"]\n      interval: 10s\n      timeout: 5s\n      retries: 5\n\n  # Redis 缓存\n  redis:\n    image: redis:7-alpine\n    container_name: redis\n    command: redis-server --appendonly yes --requirepass redispass\n    volumes:\n      - redis_data:/data\n    networks:\n      - backend-network\n    restart: unless-stopped\n    healthcheck:\n      test: [\"CMD\", \"redis-cli\", \"--raw\", \"incr\", \"ping\"]\n      interval: 10s\n      timeout: 5s\n      retries: 5\n\n  # Nginx 反向代理\n  nginx:\n    image: nginx:alpine\n    container_name: nginx\n    ports:\n      - \"80:80\"\n      - \"443:443\"\n    volumes:\n      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro\n      - ./nginx/conf.d:/etc/nginx/conf.d:ro\n      - ./ssl:/etc/nginx/ssl:ro\n    depends_on:\n      - frontend\n      - api\n    networks:\n      - frontend-network\n    restart: unless-stopped\n    healthcheck:\n      test: [\"CMD\", \"wget\", \"--quiet\", \"--tries=1\", \"--spider\", \"http://localhost/health\"]\n      interval: 30s\n      timeout: 10s\n      retries: 3\n\nvolumes:\n  postgres_data:\n    driver: local\n  redis_data:\n    driver: local\n\nnetworks:\n  frontend-network:\n    driver: bridge\n  backend-network:\n    driver: bridge\n```\n\n**nginx.conf 配置：**\n\n```nginx\nupstream frontend {\n    server frontend:3000;\n}\n\nupstream api {\n    server api:8000;\n}\n\nserver {\n    listen 80;\n    server_name localhost;\n    client_max_body_size 100M;\n\n    # 健康检查端点\n    location /health {\n        access_log off;\n        return 200 \"OK\\n\";\n        add_header Content-Type text/plain;\n    }\n\n    # 前端应用\n    location / {\n        proxy_pass http://frontend;\n        proxy_set_header Host $host;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n    }\n\n    # API 接口\n    location /api/ {\n        proxy_pass http://api/;\n        proxy_set_header Host $host;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n        proxy_redirect off;\n\n        # WebSocket 支持\n        proxy_http_version 1.1;\n        proxy_set_header Upgrade $http_upgrade;\n        proxy_set_header Connection \"upgrade\";\n    }\n\n    # 静态资源缓存\n    location ~* ^.+\\.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {\n        proxy_pass http://frontend;\n        expires 30d;\n        add_header Cache-Control \"public, immutable\";\n    }\n}\n```\n\n### 21.7.5 使用 VS Code Dev Containers\n\nDev Containers 让整个开发环境容器化，提升团队一致性。\n\n**.devcontainer/devcontainer.json：**\n\n```json\n{\n  \"name\": \"Python Dev Environment\",\n  \"image\": \"mcr.microsoft.com/devcontainers/python:3.11\",\n\n  \"features\": {\n    \"ghcr.io/devcontainers/features/docker-in-docker:2\": {},\n    \"ghcr.io/devcontainers/features/git:1\": {}\n  },\n\n  \"customizations\": {\n    \"vscode\": {\n      \"extensions\": [\n        \"ms-python.python\",\n        \"ms-python.vscode-pylance\",\n        \"ms-python.pylint\",\n        \"charliermarsh.ruff\",\n        \"ms-vscode-remote.remote-containers\"\n      ],\n      \"settings\": {\n        \"python.linting.enabled\": true,\n        \"python.linting.pylintEnabled\": true,\n        \"python.formatting.provider\": \"black\",\n        \"[python]\": {\n          \"editor.formatOnSave\": true,\n          \"editor.defaultFormatter\": \"ms-python.python\"\n        }\n      }\n    }\n  },\n\n  \"postCreateCommand\": \"pip install -r requirements.txt && pip install pytest black pylint\",\n\n  \"forwardPorts\": [8000, 5432, 6379],\n  \"portsAttributes\": {\n    \"8000\": {\n      \"label\": \"Application\",\n      \"onAutoForward\": \"notify\"\n    },\n    \"5432\": {\n      \"label\": \"PostgreSQL\",\n      \"onAutoForward\": \"ignore\"\n    },\n    \"6379\": {\n      \"label\": \"Redis\",\n      \"onAutoForward\": \"ignore\"\n    }\n  },\n\n  \"mounts\": [\n    \"source=${localEnv:HOME}/.ssh,target=/home/vscode/.ssh,readonly\"\n  ],\n\n  \"remoteUser\": \"vscode\"\n}\n```\n\n**.devcontainer/Dockerfile：**\n\n```dockerfile\nFROM mcr.microsoft.com/devcontainers/python:3.11\n\n# 安装额外工具\nRUN apt-get update && apt-get install -y \\\n    postgresql-client \\\n    redis-tools \\\n    curl \\\n    git \\\n    && rm -rf /var/lib/apt/lists/*\n\n# 创建虚拟环境\nRUN python -m venv /opt/venv\nENV PATH=\"/opt/venv/bin:$PATH\"\n\nWORKDIR /workspace\n```\n\n**Docker Compose 用于 Dev Containers：**\n\n```yaml\n# .devcontainer/docker-compose.yml\nversion: '3.9'\n\nservices:\n  app:\n    build:\n      context: .\n      dockerfile: Dockerfile\n    environment:\n      DATABASE_URL: postgresql://dev:dev@postgres:5432/myapp\n      REDIS_URL: redis://redis:6379\n    volumes:\n      - ..:/workspace:cached\n    ports:\n      - \"8000:8000\"\n    depends_on:\n      - postgres\n      - redis\n\n  postgres:\n    image: postgres:16-alpine\n    environment:\n      POSTGRES_USER: dev\n      POSTGRES_PASSWORD: dev\n      POSTGRES_DB: myapp\n    volumes:\n      - postgres_data:/var/lib/postgresql/data\n\n  redis:\n    image: redis:7-alpine\n\nvolumes:\n  postgres_data:\n```\n"
  },
  {
    "path": "21_case_devops/README.md",
    "content": "# 第二十一章 实战案例 - DevOps\n\n## DevOps 背景介绍\n\nDevOps 是一种重要的开发和运维文化，强调开发团队和运维团队之间的协作和自动化。它致力于通过自动化和流程优化，加快软件交付速度，同时提高系统的稳定性和可靠性。Docker 作为容器化技术的领导者，已成为现代 DevOps 工作流中不可或缺的工具。通过容器化应用，开发团队可以确保“一次构建，处处运行”，消除开发、测试和生产环境的差异，大大简化了部署流程。\n\n## Docker 在 DevOps 中的角色\n\nDocker 在 DevOps 工作流中承担多个关键角色。首先，它标准化了应用的开发和部署环境，使得团队成员在相同的 Docker 容器中工作，避免了“在我的机器上可以运行”的问题。其次，Docker 与 CI/CD 流程无缝集成，通过自动化的镜像构建、测试和部署，实现快速的迭代周期。此外，Docker 还支持微服务架构和容器编排，使团队能够更灵活地扩展应用和管理基础设施。\n\n## CI/CD 管道的重要性\n\n持续集成与持续部署 (CI/CD) 是现代 DevOps 的核心。通过自动化的代码检测、测试、构建和部署流程，团队可以更加频繁地发布新版本，同时保持系统的稳定性和可靠性。Docker 在 CI/CD 中扮演了重要角色：\n\n- **标准化构建环境** - Docker 确保开发、测试和生产环境完全一致，消除了环境差异带来的问题\n- **加速流水线** - 容器的快速启动和轻量级特性，大幅加快了 CI/CD 流程的执行效率\n- **灵活的测试框架** - 可以轻松创建短生命周期的测试容器，并行运行多个测试\n- **自动化镜像发布** - CI/CD 工具可以自动构建、扫描、标记和推送 Docker 镜像到仓库\n- **蓝绿部署和金丝雀发布** - 利用容器的隔离性和可重复性，实现高级发布策略\n\n本章将通过介绍 GitHub Actions、Drone 等流行的 CI/CD 工具，展示如何在实际项目中构建完整的自动化流水线。我们还将演示如何在本地开发环境中集成 Docker，使用 IDE 的容器开发插件，加快本地迭代周期。\n\n## 本章学习目标\n\n通过学习本章内容，你将能够：\n\n- 理解 DevOps 文化、CI/CD 流程和容器化的紧密关系\n- 掌握完整的 Docker 工作流，从代码提交到线上部署的每一个环节\n- 学习如何使用 GitHub Actions 实现自动化 CI/CD，以及工作流的编写和优化\n- 了解 Drone 等第三方 CI/CD 工具的架构、部署和配置方式\n- 学会在本地 IDE (VS Code) 中集成 Docker，利用容器开发工具提升开发效率\n- 掌握实战中常见的 DevOps 场景、最佳实践和故障排查方法\n\n## 章节内容导航\n\n* [DevOps 完整工作流](21.1_devops_workflow.md) — 从代码到部署的全流程\n* [GitHub Actions](21.2_github_actions.md) — 使用 GitHub Actions 实现 CI/CD\n* [Drone](21.3_drone.md) — Drone CI/CD 平台简介和配置\n* [Drone Demo](21.4_drone_demo.md) — Drone 实战演示和应用\n* [在 IDE 中使用 Docker](21.5_ide.md) — IDE 与 Docker 集成的好处\n* [VS Code](21.6_vsCode.md) — Visual Studio Code 容器开发指南\n* [实战例子](21.7_practical_examples.md) — 真实项目中的 DevOps 应用案例\n* [本章小结](summary.md)\n"
  },
  {
    "path": "21_case_devops/drone_demo.app.go",
    "content": "package main\n\nimport \"fmt\"\n\nfunc main(){\n    fmt.Printf(\"Hello World!\\n\");\n}\n"
  },
  {
    "path": "21_case_devops/drone_demo.drone.yml",
    "content": "kind: pipeline\ntype: docker\nname: build\nsteps:\n- name: build\n  image: golang:alpine\n  pull: if-not-exists # always never\n  environment:\n    KEY: VALUE\n  commands:\n    - echo $KEY\n    - pwd\n    - ls\n    - CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .\n    - ./app\n\ntrigger:\n  branch:\n  - master\n"
  },
  {
    "path": "21_case_devops/drone_docker-compose.yml",
    "content": "\n\nservices:\n\n  drone-server:\n    image: drone/drone:2.3.1\n    ports:\n      - 443:443\n      - 80:80\n    volumes:\n      - drone-data:/data:rw\n      - ./ssl:/etc/certs\n    restart: always\n    environment:\n      - DRONE_SERVER_HOST=${DRONE_SERVER_HOST:-drone.domain.com}\n      - DRONE_SERVER_PROTO=${DRONE_SERVER_PROTO:-https}\n      - DRONE_RPC_SECRET=${DRONE_RPC_SECRET:-secret}\n      - DRONE_GITHUB_SERVER=https://github.com\n      - DRONE_GITHUB_CLIENT_ID=${DRONE_GITHUB_CLIENT_ID}\n      - DRONE_GITHUB_CLIENT_SECRET=${DRONE_GITHUB_CLIENT_SECRET}\n\n  drone-agent:\n    image: drone/drone-runner-docker:1\n    restart: always\n    depends_on:\n      - drone-server\n    volumes:\n      - /var/run/docker.sock:/var/run/docker.sock:rw\n    environment:\n      - DRONE_RPC_PROTO=http\n      - DRONE_RPC_HOST=drone-server\n      - DRONE_RPC_SECRET=${DRONE_RPC_SECRET:-secret}\n      - DRONE_RUNNER_NAME=${HOSTNAME:-demo}\n      - DRONE_RUNNER_CAPACITY=2\n    dns: 114.114.114.114\n\nvolumes:\n  drone-data:\n"
  },
  {
    "path": "21_case_devops/drone_env.example",
    "content": "DRONE_SERVER_HOST=\nDRONE_SERVER_PROTO=\nDRONE_RPC_SECRET=\nHOSTNAME=\nDRONE_GITHUB_CLIENT_ID=\nDRONE_GITHUB_CLIENT_SECRET=\n"
  },
  {
    "path": "21_case_devops/drone_gitignore",
    "content": ".env\nssl/*\n"
  },
  {
    "path": "21_case_devops/summary.md",
    "content": "## 本章小结\n\n本章通过一个完整的 DevOps 工作流示例，串联了从代码提交、自动化测试、镜像构建、\n到部署发布的一整套实践路径。\n\n在落地时，建议重点把握以下原则：\n\n* **不可变交付物**：同一份产物（镜像）在不同环境中流转，使用 commit hash 等方式标识版本。\n* **最小权限与密钥管理**：CI 平台权限尽量收敛，敏感信息使用 Secret 管理并定期轮换。\n* **流水线可观测与可回滚**：为关键步骤保留日志与制品，发布失败时能快速定位并回滚。\n* **开发环境一致性**：通过 Dev Containers / compose 开发容器减少“在我电脑上可以”的问题。\n\n下一步你可以根据团队现状选择一条主线深入：\n\n* 已在用 GitHub：优先补全 Actions 的缓存、制品、发布策略。\n* 自建体系：结合私有 Registry、Kubernetes 与 GitOps 工具完善部署与审计。\n---\n\n> 📝 **发现错误或有改进建议？** 欢迎提交 [Issue](https://github.com/yeasy/docker_practice/issues) 或 [PR](https://github.com/yeasy/docker_practice/pulls)。\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# 修订记录\n\n* 1.6.1 2026-02-28\n  * 修正数据卷 `--mount` 与 `-v` 的行为差异及数据卷管理说明\n  * 补充 Docker Hub 限流机制说明，区分 pull rate limit 与 abuse rate limit\n  * 完善安全权限警告，强化用户加入 docker 组等同于 root 的风险意识\n  * 增补 Docker Engine v29 containerd image store 与 BuildKit provenance attestations 默认行为说明\n\n* 1.6.0 2026-02-20\n  * 全面统一使用 `docker compose` (V2) 为默认标准，提供 V1 迁移说明\n  * 修复全书大量排版错误，建立附录与正文的双向索引与引用\n  * 更新 Kubernetes 至 1.35 兼容说明及运行时环境提示\n\n* 1.5.4 2026-02-15\n  * 移除 combine.py\n  * 修复若干问题\n\n\n* 1.5.3 2026-02-15\n  * 修复 CI 流程中的图片引用路径错误\n  * 修复 CODEOWNERS 文件路径匹配问题\n  * 更新项目配置版本号\n\n* 1.5.0 2026-02-05\n  * 全面重构章节目录结构 (01-15)\n  * 支持 Docker Engine v29.x\n  * 优化文档图片引用路径\n\n* 1.4.0 2026-01-11\n  * 全面支持 Docker Engine v29 新版本\n  * 更新 Docker Compose 至 v2.40.x\n  * 更新 Kubernetes 相关章节至 1.35 版本\n  * BuildKit 已成为默认稳定构建器，移除实验特性说明\n  * 新增 Docker Scout、Docker Init 相关内容\n  * 更新镜像加速器配置\n  * 添加 CentOS EOL 警告，推荐使用 Rocky Linux/AlmaLinux\n  * 扩充安全章节和底层架构章节内容\n\n* 1.3.0 2021-12-31\n  * 全面支持 Docker v20.10 新版本\n  * 新增 Docker Compose v2\n  * Docker Hub 自动构建转为付费功能\n\n* 1.2.0 2020-12-20\n  * 错误修复\n\n* 1.1.0 2019-12-31\n  * 全面支持 Docker v19.03 新版本\n  * 增加 `BuildKit`\n  * 增加 `docker buildx` 命令使用说明\n  * 增加 `docker manifest` 命令使用说明\n  * 移除 `Ubuntu 14.04` `Debian 8` `Debian 7`\n\n* 1.0.0: 2018-12-31\n  * 全面支持 Docker v18.x 新版本\n  * 添加如何调试 Docker\n  * 错误修正\n\n* 0.9.0: 2017-12-31\n  * 对 v1.13.x 旧版本的最后支持\n\n* 0.9.0-rc2: 2017-12-10\n\n  * 增加 Docker 中文资源链接\n  * 增加介绍基于 Docker 的 CI/CD 工具 `Drone`\n  * 增加 `docker secret` 相关内容\n  * 增加 `docker config` 相关内容\n  * 增加 `LinuxKit` 相关内容\n\n  * 更新 `CoreOS` 章节\n  * 更新 `etcd` 章节，基于 3.x 版本\n\n  * 删除 `Docker Compose` 中的 `links` 指令\n\n  * 替换 `docker daemon` 命令为 `dockerd`\n  * 替换 `docker ps` 命令为 `docker container ls`\n  * 替换 `docker images` 命令为 `docker image ls`\n\n  * 修改 `安装 Docker` 一节中部分文字表述\n\n  * 移除历史遗留文件和错误的文件\n  * 优化文字排版\n  * 调整目录结构\n  * 修复内容逻辑错误\n  * 修复 `404` 链接\n\n* 0.9.0-rc1: 2017-11-29\n\n  * 根据最新版本 (v17.09) 修订内容\n\n  * 增加 `Dockerfile` 多阶段构建 (`multistage builds`) `Docker 17.05` 新增特性\n  * 增加 `docker exec` 子命令介绍\n  * 增加 `docker` 管理子命令 `container` `image` `network` `volume` 介绍\n  * 增加 `树莓派单片电脑` 安装 Docker\n  * 增加 Docker 存储驱动 `OverlayFS` 相关内容\n\n  * 更新 `Docker CE` `v17.x` 安装说明\n  * 更新 `Docker 网络` 一节\n  * 更新 `Docker Machine` 基于 0.13.0 版本\n  * 更新 `Docker Compose` 基于 3 文件格式\n\n  * 删除 `Docker Swarm` 相关内容，替换为 `Swarm mode` `Docker 1.12.0` 新增特性\n  * 删除 `docker run` `--link` 参数\n\n  * 精简 `Docker Registry` 一节\n\n  * 替换 `docker run` `-v` 参数为 `--mount`\n\n  * 修复 `404` 链接\n  * 优化文字排版\n  * 增加离线阅读功能\n\n* 0.8.0: 2017-01-08\n\n  * 修正文字内容\n  * 根据最新版本 (1.12) 修订安装使用\n  * 补充附录章节\n\n* 0.7.0: 2016-06-12\n\n  * 根据最新版本进行命令调整\n  * 修正若干文字描述\n\n* 0.6.0: 2015-12-24\n\n  * 补充 Machine 项目\n  * 修正若干 bug\n\n* 0.5.0: 2015-06-29\n\n  * 添加 Compose 项目\n  * 添加 Machine 项目\n  * 添加 Swarm 项目\n  * 完善 Kubernetes 项目内容\n  * 添加 Mesos 项目内容\n\n* 0.4.0: 2015-05-08\n\n  * 添加 Etcd 项目\n  * 添加 Fig 项目\n  * 添加 CoreOS 项目\n  * 添加 Kubernetes 项目\n\n* 0.3.0: 2014-11-25\n\n  * 完成仓库章节\n  * 重写安全章节\n  * 修正底层实现章节的架构、命名空间、控制组、文件系统、容器格式等内容\n  * 添加对常见仓库和镜像的介绍\n  * 添加 Dockerfile 的介绍\n  * 重新校订中英文混排格式\n  * 修订文字表达\n  * 发布繁体版本分支：zh-Hant\n\n* 0.2.0: 2014-09-18\n\n  * 对照官方文档重写介绍、基本概念、安装、镜像、容器、仓库、数据管理、网络等章节\n  * 添加底层实现章节\n  * 添加命令查询和资源链接章节\n  * 其它修正\n\n* 0.1.0: 2014-09-05\n\n  * 添加基本内容\n  * 修正错别字和表达不通顺的地方\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "## 如何贡献\n\n领取或创建新的 [Issue](https://github.com/yeasy/docker_practice/issues)，如 [issue 235](https://github.com/yeasy/docker_practice/issues/235)，添加自己为 `Assignee`。\n\n在 [GitHub](https://github.com/yeasy/docker_practice/fork) 上 `fork` 到自己的仓库，如 `docker_user/docker_practice`，然后 `clone` 到本地，并设置用户信息。\n\n```bash\n$ git clone git@github.com:docker_user/docker_practice.git\n\n$ cd docker_practice\n```\n\n修改代码后提交，并推送到自己的仓库，注意修改提交消息为对应 Issue 号和描述。\n\n```bash\n# Update the content\n\n$ git commit -a -s\n\n# In commit msg dialog, add content like \"Fix issue #235: describe ur change\"\n\n$ git push\n```\n\n在 [GitHub](https://github.com/yeasy/docker_practice/pulls) 上提交 `Pull Request`，添加标签，并邀请维护者进行 `Review`。\n\n定期使用项目仓库内容更新自己仓库内容。\n\n```bash\n$ git remote add upstream https://github.com/yeasy/docker_practice\n\n$ git fetch upstream\n\n$ git rebase upstream/master\n\n$ git push -f origin master\n```\n\n## 排版规范\n\n本开源书籍遵循[中文排版指南](https://github.com/mzlogin/chinese-copywriting-guidelines)规范。\n"
  },
  {
    "path": "README.md",
    "content": "# 前言\n\n[![图](https://img.shields.io/github/stars/yeasy/docker_practice.svg?style=social&label=Stars)](https://github.com/yeasy/docker_practice) [![图](https://img.shields.io/github/release/yeasy/docker_practice/all.svg)](https://github.com/yeasy/docker_practice/releases) [![图](https://img.shields.io/badge/Based-Docker%20Engine%20v29.x-blue.svg)](https://docs.docker.com/engine/release-notes/) [![图](https://img.shields.io/badge/Docker%20%E6%8A%80%E6%9C%AF%E5%85%A5%E9%97%A8%E4%B8%8E%E5%AE%9E%E6%88%98-jd.com-red.svg)][1]\n\n**v1.6.5**\n\n[Docker](https://www.docker.com) 是个划时代的开源项目，它彻底释放了计算虚拟化的威力，极大提高了应用的维护效率，降低了云计算应用开发的成本！使用 Docker，可以让应用的部署、测试和分发都变得前所未有的高效和轻松！\n\n无论是应用开发者、运维人员、还是其他信息技术从业人员，都有必要认识和掌握 Docker，节约有限的生命。\n\n本书既适用于具备基础 Linux 知识的 Docker 初学者，也希望可供理解原理和实现的高级用户参考。同时，书中给出的实践案例，可供在进行实际部署时借鉴。\n\n## 内容特色\n\n*   **入门基础**：第 1 ~ 6 章为基础内容，帮助深入理解 Docker 的基本概念 (镜像、容器、仓库) 和核心操作。\n*   **进阶应用**：第 7 ~ 11 章涵盖 Dockerfile 指令详解、数据与网络管理、Buildx、Compose 等高级配置和管理操作。\n*   **深入原理**：第 12 ~ 17 章介绍其底层实现技术，深入探讨容器编排体系 (Kubernetes、Etcd)，并延伸涉及容器与云计算及其它关键生态项目 (Fedora CoreOS、Podman 等)。\n*   **实战扩展**：第 18 ~ 21 章重点讨论容器安全防护机制、监控与日志聚合系统 (Prometheus、ELK)，并展示操作系统、CI/CD 自动化构建等典型实践案例。\n\n## 五分钟快速上手\n\n“5分钟运行第一个容器”——跟随以下步骤快速体验 Docker：\n\n1. **安装 Docker**（第1章）：根据操作系统完成 Docker 的安装与验证\n2. **第一个容器**：执行 `docker run hello-world`，体验最简单的容器运行\n3. **交互式容器**：执行 `docker run -it ubuntu bash`，进入容器内部与系统交互\n4. **镜像与仓库**（第2章）：理解镜像的概念、查找镜像、拉取和使用官方镜像\n5. **自定义镜像**（第7章）：学习如何编写 Dockerfile 创建自己的镜像\n\n## 学习路线图\n\n```mermaid\ngraph LR\n    Start[Docker 学习入口] --> Ch1[第1章：基础安装]\n\n    Ch1 --> Role1[\"运维新手<br/>第1-4章\"]\n    Ch1 --> Role2[\"开发者<br/>第1-3章 → 第5-8章\"]\n    Ch1 --> Role3[\"DevOps 工程师<br/>第1章 → 第9-14章 → 第18章\"]\n    Ch1 --> Role4[\"架构师<br/>第1章 → 第15-21章\"]\n\n    Role1 --> End1[\"掌握基本操作\"]\n    Role2 --> End2[\"构建与部署应用\"]\n    Role3 --> End3[\"自动化与运维\"]\n    Role4 --> End4[\"设计容器方案\"]\n```\n\n| 读者角色 | 学习重点 | 核心成果 |\n|---------|---------|---------|\n| **运维新手** | 第1-4章 | 掌握容器的基本概念与操作 |\n| **开发者** | 第1-3章 → 第5-8章 | 学会容器化应用的构建与部署 |\n| **DevOps 工程师** | 第1章 → 第9-14章 → 第18章 | 实现容器编排与自动化部署流程 |\n| **架构师** | 第1章 → 第15-21章 | 设计高可用、高性能的容器基础设施 |\n\n## 阅读方式\n\n本书按需提供多种阅读模式，具体如下：\n\n### 在线阅读\n\n*   **GitBook**: [在线阅读](https://yeasy.gitbook.io/docker_practice/)\n*   **GitHub**: [仓库目录](https://github.com/yeasy/docker_practice/blob/master/SUMMARY.md)\n*   **Mirror**: [镜像站点](https://vuepress.mirror.docker-practice.com/)\n\n### 本地阅读\n\n您也可以选择以下方式在本地离线阅读。\n\n#### 方式 1：mdPress（推荐）\n\n使用 [mdPress](https://github.com/yeasy/mdpress) 构建：\n\n```bash\nbrew tap yeasy/tap && brew install mdpress\nmdpress serve\n```\n启动后访问 [本地阅读地址](http://localhost:4000)。\n\n#### 方式 2：Docker 镜像\n\n无需安装任何依赖，一条命令即可启动。\n\n```bash\ndocker run -it --rm -p 4000:80 ccr.ccs.tencentyun.com/dockerpracticesig/docker_practice:vuepress\n```\n启动后访问 [本地阅读地址](http://localhost:4000)。\n[离线阅读说明](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)\n\n## 社区交流\n\n欢迎加入 Docker 技术交流群，分享 Docker 资源，交流 Docker 技术。\n\n*   **GitHub Discussions**：[点击前往](https://github.com/yeasy/docker_practice/discussions) (技术问答、交流)\n*   **GitHub Issues**：[提交 Bug](https://github.com/yeasy/docker_practice/issues/new/choose) (内容错误、建议)\n\n> **交流 QQ 群** (部分已满，建议优先使用 GitHub Discussions)：\n> *   341410255 (I), 419042067 (II), 210028779 (III), 483702734 (IV), 460598761 (V)\n> *   581983671 (VI), 252403484 (VII), 544818750 (VIII), 571502246 (IX), 145983035 (X)\n\n## 参与贡献\n\n欢迎[参与项目维护](CONTRIBUTING.md)。\n\n*   [修订记录](CHANGELOG.md)\n*   [贡献者名单](https://github.com/yeasy/docker_practice/graphs/contributors)\n\n## 进阶学习\n\n[![图](https://github.com/yeasy/docker_practice/raw/master/_images/docker_primer4.jpg)][1]\n\n《[Docker 技术入门与实战][1]》已更新到第 4 版，讲解最新容器技术栈知识，欢迎大家阅读并反馈建议。\n\n*   [京东图书][1]\n*   [天猫图书](https://detail.tmall.com/item.htm?id=997383773726&skuId=6143496614475)\n\n## 推荐阅读\n\n本书是技术丛书的一部分。以下书籍与本书形成互补：\n\n| 书名 | 与本书的关系 |\n|------|------------|\n| [《区块链技术指南》](https://github.com/yeasy/blockchain_guide) | 利用 Docker 部署区块链节点 |\n| [《OpenClaw 从入门到精通》](https://github.com/yeasy/openclaw_guide) | 利用 Docker 部署 AI 智能体 |\n\n## 鼓励项目\n\n<p align=\"center\">\n<img width=\"200\" src=\"_images/donate.jpeg\">\n</p>\n\n<p align=\"center\"><strong>欢迎鼓励项目一杯 coffee~</strong></p>\n\n## Star History\n\n[![Star History Chart](https://api.star-history.com/svg?repos=yeasy/docker_practice&type=Date)](https://star-history.com/#yeasy/docker_practice&Date)\n\n[1]: https://item.jd.com/10200902362001.html\n"
  },
  {
    "path": "SUMMARY.md",
    "content": "## 目录\n\n* [前言](README.md)\n* [修订记录](CHANGELOG.md)\n* [如何贡献](CONTRIBUTING.md)\n\n## 第一部分：入门篇\n\n* [第一章 Docker 简介](01_introduction/README.md)\n  * [1.1 快速上手](01_introduction/1.1_quickstart.md)\n  * [1.2 什么是 Docker](01_introduction/1.2_what.md)\n  * [1.3 为什么要用 Docker](01_introduction/1.3_why.md)\n  * [本章小结](01_introduction/summary.md)\n* [第二章 基本概念](02_basic_concept/README.md)\n  * [2.1 镜像](02_basic_concept/2.1_image.md)\n  * [2.2 容器](02_basic_concept/2.2_container.md)\n  * [2.3 仓库](02_basic_concept/2.3_repository.md)\n  * [本章小结](02_basic_concept/summary.md)\n* [第三章 安装 Docker](03_install/README.md)\n  * [3.1 Ubuntu](03_install/3.1_ubuntu.md)\n  * [3.2 Debian](03_install/3.2_debian.md)\n  * [3.3 Fedora](03_install/3.3_fedora.md)\n  * [3.4 CentOS](03_install/3.4_centos.md)\n  * [3.5 Raspberry Pi](03_install/3.5_raspberry-pi.md)\n  * [3.6 Linux 离线安装](03_install/3.6_offline.md)\n  * [3.7 macOS](03_install/3.7_mac.md)\n  * [3.8 Windows 10/11](03_install/3.8_windows.md)\n  * [3.9 镜像加速器](03_install/3.9_mirror.md)\n  * [3.10 开启实验特性](03_install/3.10_experimental.md)\n  * [本章小结](03_install/summary.md)\n* [第四章 使用镜像](04_image/README.md)\n  * [4.1 获取镜像](04_image/4.1_pull.md)\n  * [4.2 列出镜像](04_image/4.2_list.md)\n  * [4.3 删除本地镜像](04_image/4.3_rm.md)\n  * [4.4 利用 commit 理解镜像构成](04_image/4.4_commit.md)\n  * [4.5 使用 Dockerfile 定制镜像](04_image/4.5_build.md)\n  * [4.6 其它制作镜像的方式](04_image/4.6_other.md)\n  * [4.7 实现原理](04_image/4.7_internal.md)\n  * [本章小结](04_image/summary.md)\n* [第五章 操作容器](05_container/README.md)\n  * [5.1 启动](05_container/5.1_run.md)\n  * [5.2 守护态运行](05_container/5.2_daemon.md)\n  * [5.3 终止](05_container/5.3_stop.md)\n  * [5.4 进入容器](05_container/5.4_attach_exec.md)\n  * [5.5 导出和导入](05_container/5.5_import_export.md)\n  * [5.6 删除](05_container/5.6_rm.md)\n  * [本章小结](05_container/summary.md)\n* [第六章 访问仓库](06_repository/README.md)\n  * [6.1 Docker Hub](06_repository/6.1_dockerhub.md)\n  * [6.2 私有仓库](06_repository/6.2_registry.md)\n  * [6.3 私有仓库高级配置](06_repository/6.3_registry_auth.md)\n  * [6.4 Nexus 3](06_repository/6.4_nexus3_registry.md)\n  * [本章小结](06_repository/summary.md)\n\n## 第二部分：进阶篇\n\n* [第七章 Dockerfile 指令详解](07_dockerfile/README.md)\n  * [7.1 RUN 执行命令](07_dockerfile/7.1_run.md)\n  * [7.2 COPY 复制文件](07_dockerfile/7.2_copy.md)\n  * [7.3 ADD 更高级的复制文件](07_dockerfile/7.3_add.md)\n  * [7.4 CMD 容器启动命令](07_dockerfile/7.4_cmd.md)\n  * [7.5 ENTRYPOINT 入口点](07_dockerfile/7.5_entrypoint.md)\n  * [7.6 ENV 设置环境变量](07_dockerfile/7.6_env.md)\n  * [7.7 ARG 构建参数](07_dockerfile/7.7_arg.md)\n  * [7.8 VOLUME 定义匿名卷](07_dockerfile/7.8_volume.md)\n  * [7.9 EXPOSE 暴露端口](07_dockerfile/7.9_expose.md)\n  * [7.10 WORKDIR 指定工作目录](07_dockerfile/7.10_workdir.md)\n  * [7.11 USER 指定当前用户](07_dockerfile/7.11_user.md)\n  * [7.12 HEALTHCHECK 健康检查](07_dockerfile/7.12_healthcheck.md)\n  * [7.13 ONBUILD 为他人作嫁衣裳](07_dockerfile/7.13_onbuild.md)\n  * [7.14 LABEL 为镜像添加元数据](07_dockerfile/7.14_label.md)\n  * [7.15 SHELL 指令](07_dockerfile/7.15_shell.md)\n  * [7.16 参考文档](07_dockerfile/7.16_references.md)\n  * [7.17 多阶段构建](07_dockerfile/7.17_multistage_builds.md)\n  * [7.18 实战多阶段构建 Laravel 镜像](07_dockerfile/7.18_multistage_builds_laravel.md)\n  * [本章小结](07_dockerfile/summary.md)\n* [第八章 数据管理](08_data/README.md)\n  * [8.1 数据卷](08_data/8.1_volume.md)\n  * [8.2 挂载主机目录](08_data/8.2_bind-mounts.md)\n  * [8.3 tmpfs 挂载](08_data/8.3_tmpfs.md)\n  * [本章小结](08_data/summary.md)\n* [第九章 网络配置](09_network/README.md)\n  * [9.1 配置 DNS](09_network/9.1_dns.md)\n  * [9.2 网络类型](09_network/9.2_network_types.md)\n  * [9.3 自定义网络](09_network/9.3_custom_network.md)\n  * [9.4 容器互联](09_network/9.4_container_linking.md)\n  * [9.5 外部访问容器](09_network/9.5_port_mapping.md)\n  * [9.6 网络隔离](09_network/9.6_network_isolation.md)\n  * [9.7 容器网络高级特性](09_network/9.7_advanced_networking.md)\n  * [本章小结](09_network/summary.md)\n* [第十章 Docker Buildx](10_buildx/README.md)\n  * [10.1 BuildKit](10_buildx/10.1_buildkit.md)\n  * [10.2 使用 buildx 构建镜像](10_buildx/10.2_buildx.md)\n  * [10.3 使用 buildx 构建多种系统架构支持的 Docker 镜像](10_buildx/10.3_multi-arch-images.md)\n  * [本章小结](10_buildx/summary.md)\n* [第十一章 Docker Compose](11_compose/README.md)\n  * [11.1 简介](11_compose/11.1_introduction.md)\n  * [11.2 安装与卸载](11_compose/11.2_install.md)\n  * [11.3 使用](11_compose/11.3_usage.md)\n  * [11.4 命令说明](11_compose/11.4_commands.md)\n  * [11.5 Compose 模板文件](11_compose/11.5_compose_file.md)\n  * [11.6 实战 Django](11_compose/11.6_django.md)\n  * [11.7 实战 Rails](11_compose/11.7_rails.md)\n  * [11.8 实战 WordPress](11_compose/11.8_wordpress.md)\n  * [11.9 实战 LNMP](11_compose/11.9_lnmp.md)\n  * [本章小结](11_compose/summary.md)\n\n## 第三部分：深入篇\n\n* [第十二章 底层实现](12_implementation/README.md)\n  * [12.1 基本架构](12_implementation/12.1_arch.md)\n  * [12.2 命名空间](12_implementation/12.2_namespace.md)\n  * [12.3 控制组](12_implementation/12.3_cgroups.md)\n  * [12.4 联合文件系统](12_implementation/12.4_ufs.md)\n  * [12.5 容器格式](12_implementation/12.5_container_format.md)\n  * [12.6 网络](12_implementation/12.6_network.md)\n  * [本章小结](12_implementation/summary.md)\n* [第十三章 容器编排基础](13_kubernetes_concepts/README.md)\n  * [13.1 简介](13_kubernetes_concepts/13.1_intro.md)\n  * [13.2 基本概念](13_kubernetes_concepts/13.2_concepts.md)\n  * [13.3 架构设计](13_kubernetes_concepts/13.3_design.md)\n  * [13.4 高级特性](13_kubernetes_concepts/13.4_advanced.md)\n  * [13.5 实战练习](13_kubernetes_concepts/13.5_practice.md)\n  * [本章小结](13_kubernetes_concepts/summary.md)\n* [第十四章 部署 Kubernetes](14_kubernetes_setup/README.md)\n  * [14.1 使用 kubeadm 部署 Kubernetes (CRI 使用 containerd)](14_kubernetes_setup/14.1_kubeadm.md)\n  * [14.2 使用 kubeadm 部署 Kubernetes (使用 Docker)](14_kubernetes_setup/14.2_kubeadm-docker.md)\n  * [14.3 在 Docker Desktop 使用](14_kubernetes_setup/14.3_docker-desktop.md)\n  * [14.4 Kind - Kubernetes IN Docker](14_kubernetes_setup/14.4_kind.md)\n  * [14.5 K3s - 轻量级 Kubernetes](14_kubernetes_setup/14.5_k3s.md)\n  * [14.6 一步步部署 Kubernetes 集群](14_kubernetes_setup/14.6_systemd.md)\n  * [14.7 部署 Dashboard](14_kubernetes_setup/14.7_dashboard.md)\n  * [14.8 Kubernetes 命令行 kubectl](14_kubernetes_setup/14.8_kubectl.md)\n  * [本章小结](14_kubernetes_setup/summary.md)\n* [第十五章 Etcd 项目](15_etcd/README.md)\n  * [15.1 简介](15_etcd/15.1_intro.md)\n  * [15.2 安装](15_etcd/15.2_install.md)\n  * [15.3 集群](15_etcd/15.3_cluster.md)\n  * [15.4 使用 etcdctl](15_etcd/15.4_etcdctl.md)\n  * [本章小结](15_etcd/summary.md)\n* [第十六章 容器与云计算](16_cloud/README.md)\n  * [16.1 简介](16_cloud/16.1_intro.md)\n  * [16.2 腾讯云](16_cloud/16.2_tencentCloud.md)\n  * [16.3 阿里云](16_cloud/16.3_alicloud.md)\n  * [16.4 亚马逊云](16_cloud/16.4_aws.md)\n  * [16.5 多云部署策略](16_cloud/16.5_multicloud.md)\n  * [本章小结](16_cloud/summary.md)\n* [第十七章 容器其它生态](17_ecosystem/README.md)\n  * [17.1 Fedora CoreOS 简介](17_ecosystem/17.1_coreos_intro.md)\n  * [17.2 Fedora CoreOS 安装](17_ecosystem/17.2_coreos_install.md)\n  * [17.3 podman - 下一代 Linux 容器工具](17_ecosystem/17.3_podman.md)\n  * [17.4 Buildah - 容器镜像构建工具](17_ecosystem/17.4_buildah.md)\n  * [17.5 Skopeo - 容器镜像管理工具](17_ecosystem/17.5_skopeo.md)\n  * [17.6 containerd - 核心容器运行时](17_ecosystem/17.6_containerd.md)\n  * [17.7 安全容器运行时](17_ecosystem/17.7_secure_runtime.md)\n  * [17.8 WebAssembly 与容器](17_ecosystem/17.8_wasm.md)\n  * [本章小结](17_ecosystem/summary.md)\n\n## 第四部分：实战篇\n\n* [第十八章 安全](18_security/README.md)\n  * [18.1 内核命名空间](18_security/18.1_kernel_ns.md)\n  * [18.2 控制组](18_security/18.2_control_group.md)\n  * [18.3 服务端防护](18_security/18.3_daemon_sec.md)\n  * [18.4 内核能力机制](18_security/18.4_kernel_capability.md)\n  * [18.5 其它安全特性](18_security/18.5_other_feature.md)\n  * [18.6 容器镜像安全扫描与供应链安全](18_security/18.6_image_security.md)\n  * [本章小结](18_security/summary.md)\n* [第十九章 容器监控与日志](19_observability/README.md)\n  * [19.1 Prometheus](19_observability/19.1_prometheus.md)\n  * [19.2 ELK 套件](19_observability/19.2_elk.md)\n  * [19.3 容器性能优化与故障诊断](19_observability/19.3_performance_optimization.md)\n  * [本章小结](19_observability/summary.md)\n* [第二十章 实战案例 - 操作系统](20_cases_os/README.md)\n  * [20.1 Busybox](20_cases_os/20.1_busybox.md)\n  * [20.2 Alpine](20_cases_os/20.2_alpine.md)\n  * [20.3 Debian Ubuntu](20_cases_os/20.3_debian.md)\n  * [20.4 CentOS Fedora](20_cases_os/20.4_centos.md)\n  * [本章小结](20_cases_os/summary.md)\n* [第二十一章 实战案例 - DevOps](21_case_devops/README.md)\n  * [21.1 DevOps 完整工作流](21_case_devops/21.1_devops_workflow.md)\n  * [21.2 GitHub Actions](21_case_devops/21.2_github_actions.md)\n  * [21.3 Drone](21_case_devops/21.3_drone.md)\n  * [21.4 Drone Demo](21_case_devops/21.4_drone_demo.md)\n  * [21.5 在 IDE 中使用 Docker](21_case_devops/21.5_ide.md)\n  * [21.6 VS Code](21_case_devops/21.6_vsCode.md)\n  * [21.7 实战案例：Go/Rust/数据库/微服务](21_case_devops/21.7_practical_examples.md)\n  * [本章小结](21_case_devops/summary.md)\n\n## 附录\n\n* [附录](appendix/README.md)\n  * [附录一：常见问题与错误速查](appendix/faq/README.md)\n  * [附录二：热门镜像介绍](appendix/repo/README.md)\n    * [Ubuntu](appendix/repo/ubuntu.md)\n    * [CentOS](appendix/repo/centos.md)\n    * [Nginx](appendix/repo/nginx.md)\n    * [PHP](appendix/repo/php.md)\n    * [Node.js](appendix/repo/nodejs.md)\n    * [MySQL](appendix/repo/mysql.md)\n    * [WordPress](appendix/repo/wordpress.md)\n    * [MongoDB](appendix/repo/mongodb.md)\n    * [Redis](appendix/repo/redis.md)\n    * [Minio](appendix/repo/minio.md)\n  * [附录三：Docker 命令查询](appendix/command/README.md)\n    * [客户端命令 - docker](appendix/command/docker.md)\n    * [服务端命令 - dockerd](appendix/command/dockerd.md)\n  * [附录四：Dockerfile 最佳实践](appendix/best_practices.md)\n  * [附录五：如何调试 Docker](appendix/debug.md)\n  * [附录六：资源链接](appendix/resources.md)\n  * [附录七：术语表](appendix/glossary.md)\n  * [附录八：Docker 学习路线图与知识体系](appendix/learning_roadmap.md)\n"
  },
  {
    "path": "_config.yml",
    "content": "theme: jekyll-theme-slate\ninclude: [_images]\n"
  },
  {
    "path": "appendix/README.md",
    "content": "# 附录\n\n本章包含了 Docker 相关的参考资料、常见问题解答以及最佳实践指南，旨在为读者提供便捷的查阅工具。\n\n## 目录\n\n*   [**常见问题总结 (FAQ)**](faq/README.md)：汇总了学习和使用 Docker 过程中的常见问题与错误解决方案。\n*   [**热门镜像介绍**](repo/README.md)：详细介绍了 Nginx，MySQL，Redis 等常用官方镜像的使用方法。\n*   [**Docker 命令查询**](command/README.md)：速查 Docker 客户端和服务端的常用命令。\n*   [**Dockerfile 最佳实践**](best_practices.md)：提供编写高效、安全 Dockerfile 的指导原则。\n*   [**如何调试 Docker**](debug.md)：介绍 Docker 调试技巧和工具。\n*   [**资源链接**](resources.md)：推荐更多 Docker 相关的学习资源。\n*   [**术语词表**](glossary.md)：统一全书中英文术语、缩写与命令写法。\n"
  },
  {
    "path": "appendix/_images/cmd_logic.dot",
    "content": "//dot -Tpng xx.dot -o xx.png\ndigraph G {\n    rankdir=TB;\n    fontname = \"Microsoft YaHei\";\n    fontsize = 14;\n    penwidth = 3;\n    compound=true;\n    rankdir=LR;\n\n    node [shape = record];\n    edge [fontname = \"Arial\", fontsize = 12, color=\"darkgreen\" ];\n\n    image[label=\"Image\",color=blue];\n    registry[label=\"Registry\",color=blue];\n    tar[label=\"Tar files\",color=blue];\n\n    subgraph cluster_container {\n\t\tlabel = \"Container\";\n        style = \"bold\";\n        color = blue;\n        edge [fontname = \"Arial\", fontsize = 11, color=\"skyblue\" ];\n\t\t//node [style=filled];\n        run[label=\"Running\",shape=circle, style=filled, fillcolor=green];\n        stop[label=\"Stopped\",shape=circle, style=filled, fillcolor=red];\n        pause[label=\"Paused\",shape=circle, style=filled, fillcolor=blue];\n\n        run->pause[label=\"pause\"];\n        pause->run[label=\"unpause\"];\n        run->run[label=\"restart\"];\n        run->stop[label=\"kill\"];\n        stop->run[label=\"start\"];\n\t}\n\n    run->image[label=\"commit\",ltail=cluster_container];\n    image->run[label=\"start\"];\n\n    image->tar[label=\"export|save\"];\n    tar->image[label=\"import\"];\n\n    image->registry[label=\"push\"];\n    registry->image[label=\"pull\"];\n\n    //heat[label=\"heat commands\",color=blue];\n    //heatshell[label=\"heatclient.shell.HeatShell\",color=blue];\n    //shell[label=\"{heatclient.v1.shell|+do_stack_create\\l+do_stack_show\\l+do_stack_update\\l...\\l+do_event_list\\l...\\l+do_resource_list\\l...\\l+do_resource_type_show\\l...\\l+do_template_show\\l...\\l}\",color=blue];\n    //heatclient[label=\"heatclient.client.Client\",color=blue];\n    //client[label=\"heatclient.v1.client.Client\",color=blue];\n    //httpclient[label=\"heatclient.common.http.HTTPClient\",color=blue];\n\n\n\n    //openstackservices[label=\"{OpenStack Services|+Nova\\l+Neutron\\l+Keystone\\l...}\",color=blue];\n\n    //{rank=same; image cluster_container}\n    //{rank=same; rpcproxy apimixin}\n}\n"
  },
  {
    "path": "appendix/_images/cmd_logic.dot.bak",
    "content": "//dot -Tpng cmd_logic.dot -o cmd_logic.png\ndigraph G {\n    rankdir=TB;\n    rankdir=LR;\n    nodesep=1;\n//ranksep=1\n    fontname = \"Microsoft YaHei\";\n    fontsize = 28;\n    penwidth = 4;\n    compound=true;\n\n    node [shape = record];\n    edge [fontname = \"Arial\", fontsize = 20, color=\"darkgreen\" ];\n\n    user[label=\"User\",color=blue,shape=ellipse, style=filled, fillcolor=green];\n    dockerfile[label=\"Dockerfile\",color=blue];\n    daemon[label=\"Daemon\",color=blue];\n    image[label=\"Image\",color=blue];\n    registry[label=\"Registry\",color=blue];\n    tar[label=\"Tar files\",color=blue];\n    network[label=\"Network\",color=blue]\n    service[label=\"Service\",color=blue]\n    swarm[label=\"Swarm\",color=blue]\n    volume[label=\"Volume\",color=blue]\n\n    subgraph cluster_container {\n\t\tlabel = \"Container\";\n        labelloc = \"c\";\n        nodesep=.5;\n        style = \"bold\";\n        color = blue;\n        edge [fontname = \"Arial\", fontsize = 20, color=\"skyblue\" ];\n\t\t//node [style=filled];\n        create[label=\"Created\",shape=circle, style=filled, fillcolor=lightblue];\n        run[label=\"Running\",shape=circle, style=filled, fillcolor=green];\n        pause[label=\"Paused\",shape=circle, style=filled, fillcolor=blue];\n        stop[label=\"Stopped\",shape=circle, style=filled, fillcolor=red];\n        exit[label=\"Exited\",shape=circle, style=filled, fillcolor=gray];\n\n        create->run[label=<<i>start</i>>];\n        run->pause[label=\"pause\"];\n        pause->run[label=\"unpause\"];\n        run->run[label=\"restart\"];\n        run->stop[label=\"stop\"];\n        run->exit[label=\"kill\"];\n        stop->run[label=\"start\"];\n\t}\n\n    //dockerfile\n    dockerfile->image[label=\"build\"];\n\n    //container\n    run->image[headlabel=\"commit\", labeldistance=7.5, ltail=cluster_container];\n    run->tar[label=\"export\",ltail=cluster_container];\n    run->network[label=\"connect | disconnect\",ltail=cluster_container];\n\n    //image\n    image->create[label=\"create\"];\n    image->run[label=\"run\"];\n    image->tar[label=\"save\"];\n    image->registry[label=\"push\"];\n\n    //tar\n    tar->image[label=\"import | load\"];\n    image->registry[label=\"push\"];\n\n    //registry\n    registry->image[label=\"pull\"];\n\n    //network\n    network->network[label=\"create | rm | ls | inspect\"]\n\n    //user\n    user->run[label=\"attach | cp | diff | exec | inspect | logs | ps | rename | rm | stats | top | update | wait\",lhead=cluster_container]\n    user->image[label=\"history | images | rmi | tag\"]\n    user->daemon[label=\"event | info | version\"]\n    user->registry[label=\"login | logout | search\"]\n\n    //heat[label=\"heat commands\",color=blue];\n    //heatshell[label=\"heatclient.shell.HeatShell\",color=blue];\n    //shell[label=\"{heatclient.v1.shell|+do_stack_create\\l+do_stack_show\\l+do_stack_update\\l...\\l+do_event_list\\l...\\l+do_resource_list\\l...\\l+do_resource_type_show\\l...\\l+do_template_show\\l...\\l}\",color=blue];\n    //heatclient[label=\"heatclient.client.Client\",color=blue];\n    //client[label=\"heatclient.v1.client.Client\",color=blue];\n    //httpclient[label=\"heatclient.common.http.HTTPClient\",color=blue];\n\n\n\n    //openstackservices[label=\"{OpenStack Services|+Nova\\l+Neutron\\l+Keystone\\l...}\",color=blue];\n\n    //{rank=same; image registry dockerfile tar}\n    //{rank=same; container}\n    //{rank=same; user}\n}\n"
  },
  {
    "path": "appendix/_images/container_status.dot",
    "content": "//dot -Tpng container_status.dot -o container_status.png\ndigraph G {\n    rankdir=TB;\n    rankdir=LR;\n    nodesep=1;\n//ranksep=1\n    fontname = \"Microsoft YaHei\";\n    fontsize = 28;\n    penwidth = 4;\n    compound=true;\n    style = \"bold\";\n    color = blue;\n\n    node [shape = record];\n    edge [fontname = \"Arial\", fontsize = 20, color=\"darkgreen\" ];\n    image[label=\"Image\",color=blue];\n    image->create[label=\"create\"];\n    image->run[label=\"run\"];\n\n    edge [fontname = \"Arial\", fontsize = 20, color=\"skyblue\" ];\n    //node [style=filled];\n    create[label=\"Created\",shape=circle, style=filled, fillcolor=lightblue];\n    run[label=\"Running\",shape=circle, style=filled, fillcolor=green];\n    pause[label=\"Paused\",shape=circle, style=filled, fillcolor=blue];\n    stop[label=\"Stopped\",shape=circle, style=filled, fillcolor=red];\n    exit[label=\"Exited\",shape=circle, style=filled, fillcolor=gray];\n\n    create->run[label=<<i>start</i>>];\n    run->pause[label=\"pause\"];\n    pause->run[label=\"unpause\"];\n    run->run[label=\"restart\"];\n    run->stop[label=\"stop\"];\n    run->exit[label=\"kill\"];\n    stop->run[label=\"start\"];\n\n\n    //heat[label=\"heat commands\",color=blue];\n    //heatshell[label=\"heatclient.shell.HeatShell\",color=blue];\n    //shell[label=\"{heatclient.v1.shell|+do_stack_create\\l+do_stack_show\\l+do_stack_update\\l...\\l+do_event_list\\l...\\l+do_resource_list\\l...\\l+do_resource_type_show\\l...\\l+do_template_show\\l...\\l}\",color=blue];\n    //heatclient[label=\"heatclient.client.Client\",color=blue];\n    //client[label=\"heatclient.v1.client.Client\",color=blue];\n    //httpclient[label=\"heatclient.common.http.HTTPClient\",color=blue];\n\n\n\n    //openstackservices[label=\"{OpenStack Services|+Nova\\l+Neutron\\l+Keystone\\l...}\",color=blue];\n\n    //{rank=same; image registry dockerfile tar}\n    //{rank=same; container}\n    //{rank=same; user}\n}\n"
  },
  {
    "path": "appendix/best_practices.md",
    "content": "## 附录四：Dockerfile 最佳实践\n\n本附录是笔者对 Docker 官方文档中 [Best practices for writing Dockerfiles](https://docs.docker.com/develop/develop-images/dockerfile_best-practices/) 的理解与翻译。\n\n### 一般性的指南和建议\n\n#### 容器应该是短暂的\n\n通过 `Dockerfile` 构建的镜像所启动的容器应该尽可能短暂 (生命周期短)。“短暂” 意味着可以停止和销毁容器，并且创建一个新容器并部署好所需的设置和配置工作量应该是极小的。\n\n#### 使用 `.dockerignore` 文件\n\n使用 `Dockerfile` 构建镜像时最好是将 `Dockerfile` 放置在一个新建的空目录下。然后将构建镜像所需要的文件添加到该目录中。为了提高构建镜像的效率，你可以在目录下新建一个 `.dockerignore` 文件来指定要忽略的文件和目录。`.dockerignore` 文件的排除模式语法和 Git 的 `.gitignore` 文件相似。\n\n#### 使用多阶段构建\n\n在 Docker 17.05 以上版本中，你可以使用[多阶段构建](../07_dockerfile/7.17_multistage_builds.md)来减少所构建镜像的大小。\n\n#### 避免安装不必要的包\n\n为了降低复杂性、减少依赖、减小文件大小、节约构建时间，你应该避免安装任何不必要的包。例如，不要在数据库镜像中包含一个文本编辑器。\n\n#### 一个容器只运行一个进程\n\n应该保证在一个容器中只运行一个进程。将多个应用解耦到不同容器中，保证了容器的横向扩展和复用。例如 web 应用应该包含三个容器：web 应用、数据库、缓存。\n\n如果容器互相依赖，你可以使用 [Docker 自定义网络](../09_network/9.3_custom_network.md)来把这些容器连接起来。\n\n#### 镜像层数尽可能少\n\n你需要在 `Dockerfile` 可读性 (也包括长期的可维护性) 和减少层数之间做一个平衡。\n\n#### 将多行参数排序\n\n将多行参数按字母顺序排序 (比如要安装多个包时)。这可以帮助你避免重复包含同一个包，更新包列表时也更容易。也便于 `PRs` 阅读和审查。建议在反斜杠符号 `\\` 之前添加一个空格，以增加可读性。\n\n下面是来自 `buildpack-deps` 镜像的例子：\n\n```docker\nRUN apt-get update && apt-get install -y \\\n  bzr \\\n  cvs \\\n  git \\\n  mercurial \\\n  subversion\n```\n\n#### 构建缓存\n\n在镜像的构建过程中，Docker 会遍历 `Dockerfile` 文件中的指令，然后按顺序执行。在执行每条指令之前，Docker 都会在缓存中查找是否已经存在可重用的镜像，如果有就使用现存的镜像，不再重复创建。如果你不想在构建过程中使用缓存，你可以在 `docker build` 命令中使用 `--no-cache=true` 选项。\n\n但是，如果你想在构建的过程中使用缓存，你得明白什么时候会，什么时候不会找到匹配的镜像，遵循的基本规则如下：\n\n* 从一个基础镜像开始 (`FROM` 指令指定)，下一条指令将和该基础镜像的所有子镜像进行匹配，检查这些子镜像被创建时使用的指令是否和被检查的指令完全一样。如果不是，则缓存失效。\n* 在大多数情况下，只需要简单地对比 `Dockerfile` 中的指令和子镜像。然而，有些指令需要更多的检查和解释。\n* 对于 `ADD` 和 `COPY` 指令，镜像中对应文件的内容也会被检查，每个文件都会计算出一个校验和。文件的最后修改时间和最后访问时间不会纳入校验。在缓存的查找过程中，会将这些校验和与已存在镜像中的文件校验和进行对比。如果文件有任何改变，比如内容和元数据，则缓存失效。\n* 除了 `ADD` 和 `COPY` 指令，缓存匹配过程不会查看临时容器中的文件来决定缓存是否匹配。例如，当执行完 `RUN apt-get -y update` 指令后，容器中一些文件被更新，但 Docker 不会检查这些文件。这种情况下，只有指令字符串本身被用来匹配缓存。\n\n一旦缓存失效，所有后续的 `Dockerfile` 指令都将产生新的镜像，缓存不会被使用。\n\n### Dockerfile 指令\n\n下面针对 `Dockerfile` 中各种指令的最佳编写方式给出建议。\n\n#### FROM\n\n尽可能使用当前官方仓库作为你构建镜像的基础。推荐使用 [Alpine](https://hub.docker.com/_/alpine/) 镜像，因为它被严格控制并保持最小尺寸 (目前小于 5 MB)，但它仍然是一个完整的发行版。\n\n#### LABEL\n\n你可以给镜像添加标签来帮助组织镜像、记录许可信息、辅助自动化构建等。每个标签一行，由 `LABEL` 开头加上一个或多个标签对。下面的示例展示了各种不同的可能格式。`#` 开头的行是注释内容。\n\n> 注意：如果你的字符串中包含空格，必须将字符串放入引号中或者对空格使用转义。如果字符串内容本身就包含引号，必须对引号使用转义。\n\n```docker\n## Set one or more individual labels\n\nLABEL com.example.version=\"0.0.1-beta\"\n\nLABEL vendor=\"ACME Incorporated\"\n\nLABEL com.example.release-date=\"2015-02-12\"\n\nLABEL com.example.version.is-production=\"\"\n```\n\n一个镜像可以包含多个标签，但建议将多个标签放入到一个 `LABEL` 指令中。\n\n```docker\n## Set multiple labels at once, using line-continuation characters to break long lines\n\nLABEL vendor=ACME\\ Incorporated \\\n      com.example.is-beta= \\\n      com.example.is-production=\"\" \\\n      com.example.version=\"0.0.1-beta\" \\\n      com.example.release-date=\"2015-02-12\"\n```\n\n关于标签可以接受的键值对，参考 [Understanding object labels](https://docs.docker.com/config/labels-custom-metadata/)。关于查询标签信息，参考 [Managing labels on objects](https://docs.docker.com/config/labels-custom-metadata/)。\n\n#### RUN\n\n为了保持 `Dockerfile` 文件的可读性，可理解性，以及可维护性，建议将长的或复杂的 `RUN` 指令用反斜杠 `\\` 分割成多行。\n\n##### apt-get\n\n`RUN` 指令最常见的用法是安装包用的 `apt-get`。因为 `RUN apt-get` 指令会安装包，所以有几个问题需要注意。\n\n不要使用 `RUN apt-get upgrade` 或 `dist-upgrade`，因为许多基础镜像中的 “必须” 包不会在一个非特权容器中升级。如果基础镜像中的某个包过时了，你应该联系它的维护者。如果你确定某个特定的包，比如 `foo`，需要升级，使用 `apt-get install -y foo` 就行，该指令会自动升级 `foo` 包。\n\n永远将 `RUN apt-get update` 和 `apt-get install` 组合成一条 `RUN` 声明，例如：\n\n```docker\nRUN apt-get update && apt-get install -y \\\n        package-bar \\\n        package-baz \\\n        package-foo\n```\n\n将 `apt-get update` 放在一条单独的 `RUN` 声明中会导致缓存问题以及后续的 `apt-get install` 失败。比如，假设你有一个 `Dockerfile` 文件：\n\n```docker\nFROM ubuntu:24.04\n\nRUN apt-get update\n\nRUN apt-get install -y curl\n```\n\n构建镜像后，所有的层都在 Docker 的缓存中。假设你后来又修改了其中的 `apt-get install` 添加了一个包：\n\n```docker\nFROM ubuntu:24.04\n\nRUN apt-get update\n\nRUN apt-get install -y curl nginx\n```\n\nDocker 发现修改后的 `RUN apt-get update` 指令和之前的完全一样。所以，`apt-get update` 不会执行，而是使用之前的缓存镜像。因为 `apt-get update` 没有运行，后面的 `apt-get install` 可能安装的是过时的 `curl` 和 `nginx` 版本。\n\n使用 `RUN apt-get update && apt-get install -y` 可以确保你的 Dockerfiles 每次安装的都是包的最新的版本，而且这个过程不需要进一步的编码或额外干预。这项技术叫作 `cache busting`。你也可以显示指定一个包的版本号来达到 `cache-busting`，这就是所谓的固定版本，例如：\n\n```docker\nRUN apt-get update && apt-get install -y \\\n    package-bar \\\n    package-baz \\\n    package-foo=1.3.*\n```\n\n固定版本会迫使构建过程检索特定的版本，而不管缓存中有什么。这项技术也可以减少因所需包中未预料到的变化而导致的失败。\n\n下面是一个 `RUN` 指令的示例模板，展示了所有关于 `apt-get` 的建议。\n\n```docker\nRUN apt-get update && apt-get install -y \\\n    aufs-tools \\\n    automake \\\n    build-essential \\\n    curl \\\n    dpkg-sig \\\n    libcap-dev \\\n    libsqlite3-dev \\\n    git \\\n    redis-server \\\n && rm -rf /var/lib/apt/lists/*\n```\n\n其中 `redis-server` 是示例包。确保安装的是最新版本。\n\n另外，清理掉 apt 缓存 `var/lib/apt/lists` 可以减小镜像大小。因为 `RUN` 指令的开头为 `apt-get update`，包缓存总是会在 `apt-get install` 之前刷新。\n\n> 注意：官方的 Debian 和 Ubuntu 镜像会自动运行 apt-get clean，所以不需要显式的调用 apt-get clean。\n\n#### CMD\n\n`CMD` 指令用于执行目标镜像中包含的软件，可以包含参数。`CMD` 大多数情况下都应该以 `CMD ['executable', 'param1', 'param2'...]` 的形式使用。因此，如果创建镜像的目的是为了部署某个服务 (比如 `Apache`)，你可能会执行类似于 `CMD ['apache2', '-DFOREGROUND']` 形式的命令。我们建议任何服务镜像都使用这种形式的命令。\n\n多数情况下，`CMD` 都需要一个交互式的 `shell` (bash，Python，perl 等)，例如 `CMD ['perl', '-de0']`，或者 `CMD ['PHP', '-a']`。使用这种形式意味着，当你执行类似 `docker run -it python` 时，你会进入一个准备好的 `shell` 中。`CMD` 应该在极少的情况下才能以 `CMD ['param', 'param']` 的形式与 `ENTRYPOINT` 协同使用，除非你和你的镜像使用者都对 `ENTRYPOINT` 的工作方式十分熟悉。\n\n#### EXPOSE\n\n`EXPOSE` 指令用于指定容器将要监听的端口。因此，你应该为你的应用程序使用常见的端口。例如，提供 `Apache` web 服务的镜像应该使用 `EXPOSE 80`，而提供 `MongoDB` 服务的镜像使用 `EXPOSE 27017`。\n\n对于外部访问，用户可以在执行 `docker run` 时使用一个标志来指示如何将指定的端口映射到所选择的端口。\n\n#### ENV\n\n为了方便新程序运行，你可以使用 `ENV` 来为容器中安装的程序更新 `PATH` 环境变量。例如使用 `ENV PATH /usr/local/nginx/bin:$PATH` 来确保 `CMD [\"nginx\"]` 能正确运行。\n\n`ENV` 指令也可用于为你想要容器化的服务提供必要的环境变量，比如 Postgres 需要的 `PGDATA`。\n\n最后，`ENV` 也能用于设置常见的版本号，比如下面的示例：\n\n```docker\nENV PG_MAJOR 9.3\n\nENV PG_VERSION 9.3.4\n\nRUN curl -SL http://example.com/postgres-$PG_VERSION.tar.xz | tar -xJC /usr/src/postgress && …\n\nENV PATH /usr/local/postgres-$PG_MAJOR/bin:$PATH\n```\n\n类似于程序中的常量，这种方法可以让你只需改变 `ENV` 指令来自动的改变容器中的软件版本。\n\n#### ADD 和 COPY\n\n虽然 `ADD` 和 `COPY` 功能类似，但一般优先使用 `COPY`。因为它比 `ADD` 更透明。`COPY` 只支持简单将本地文件拷贝到容器中，而 `ADD` 有一些并不明显的功能 (比如本地 tar 提取和远程 URL 支持)。因此，`ADD` 的最佳用例是将本地 tar 文件自动提取到镜像中，例如 `ADD rootfs.tar.xz`。\n\n如果你的 `Dockerfile` 有多个步骤需要使用上下文中不同的文件。单独 `COPY` 每个文件，而不是一次性的 `COPY` 所有文件，这将保证每个步骤的构建缓存只在特定的文件变化时失效。例如：\n\n```docker\nCOPY requirements.txt /tmp/\n\nRUN pip install --requirement /tmp/requirements.txt\n\nCOPY . /tmp/\n```\n\n如果将 `COPY . /tmp/` 放置在 `RUN` 指令之前，只要 `.` 目录中任何一个文件变化，都会导致后续指令的缓存失效。\n\n为了让镜像尽量小，最好不要使用 `ADD` 指令从远程 URL 获取包，而是使用 `curl` 和 `wget`。这样你可以在文件提取完之后删掉不再需要的文件来避免在镜像中额外添加一层。比如尽量避免下面的用法：\n\n```docker\nADD http://example.com/big.tar.xz /usr/src/things/\n\nRUN tar -xJf /usr/src/things/big.tar.xz -C /usr/src/things\n\nRUN make -C /usr/src/things all\n```\n\n而是应该使用下面这种方法：\n\n```docker\nRUN mkdir -p /usr/src/things \\\n    && curl -SL http://example.com/big.tar.xz \\\n    | tar -xJC /usr/src/things \\\n    && make -C /usr/src/things all\n```\n\n上面使用的管道操作，所以没有中间文件需要删除。\n\n对于其他不需要 `ADD` 的自动提取功能的文件或目录，你应该使用 `COPY`。\n\n#### ENTRYPOINT\n\n`ENTRYPOINT` 的最佳用处是设置镜像的主命令，允许将镜像当成命令本身来运行 (用 `CMD` 提供默认选项)。\n\n例如，下面的示例镜像提供了命令行工具 `s3cmd`：\n\n```docker\nENTRYPOINT [\"s3cmd\"]\n\nCMD [\"--help\"]\n```\n\n现在直接运行该镜像创建的容器会显示命令帮助：\n\n```bash\n$ docker run s3cmd\n```\n\n或者提供正确的参数来执行某个命令：\n\n```bash\n$ docker run s3cmd ls s3://mybucket\n```\n\n这样镜像名可以当成命令行的参考。\n\n`ENTRYPOINT` 指令也可以结合一个辅助脚本使用，和前面命令行风格类似，即使启动工具需要不止一个步骤。\n\n例如，`Postgres` 官方镜像使用下面的脚本作为 `ENTRYPOINT`：\n\n```bash\n#!/bin/bash\nset -e\n\nif [ \"$1\" = 'postgres' ]; then\n    chown -R postgres \"$PGDATA\"\n\n    if [ -z \"$(ls -A \"$PGDATA\")\" ]; then\n        gosu postgres initdb\n    fi\n\n    exec gosu postgres \"$@\"\nfi\n\nexec \"$@\"\n```\n\n> 注意：该脚本使用了 Bash 的内置命令 exec，所以最后运行的进程就是容器的 PID 为 1 的进程。这样，进程就可以接收到任何发送给容器的 Unix 信号了。\n\n该辅助脚本被拷贝到容器，并在容器启动时通过 `ENTRYPOINT` 执行：\n\n```docker\nCOPY ./docker-entrypoint.sh /\n\nENTRYPOINT [\"/docker-entrypoint.sh\"]\n```\n\n该脚本可以让用户用几种不同的方式和 `Postgres` 交互。\n\n你可以很简单地启动 `Postgres`：\n\n```bash\n$ docker run postgres\n```\n\n也可以执行 `Postgres` 并传递参数：\n\n```bash\n$ docker run postgres postgres --help\n```\n\n最后，你还可以启动另外一个完全不同的工具，比如 `Bash`：\n\n```bash\n$ docker run --rm -it postgres bash\n```\n\n#### VOLUME\n\n`VOLUME` 指令用于暴露任何数据库存储文件，配置文件，或容器创建的文件和目录。强烈建议使用 `VOLUME` 来管理镜像中的可变部分和用户可以改变的部分。\n\n#### USER\n\n如果某个服务不需要特权执行，建议使用 `USER` 指令切换到非 root 用户。先在 `Dockerfile` 中使用类似 `RUN groupadd -r postgres && useradd -r -g postgres postgres` 的指令创建用户和用户组。\n\n> 注意：在镜像中，用户和用户组每次被分配的 UID/GID 都是不确定的，下次重新构建镜像时被分配到的 UID/GID 可能会不一样。如果要依赖确定的 UID/GID，你应该显式的指定一个 UID/GID。\n\n你应该避免使用 `sudo`，因为它不可预期的 TTY 和信号转发行为可能造成的问题比它能解决的问题还多。如果你真的需要和 `sudo` 类似的功能 (例如，以 root 权限初始化某个守护进程，以非 root 权限执行它)，你可以使用 [gosu](https://github.com/tianon/gosu)。\n\n最后，为了减少层数和复杂度，避免频繁地使用 `USER` 来回切换用户。\n\n#### WORKDIR\n\n为了清晰性和可靠性，你应该总是在 `WORKDIR` 中使用绝对路径。另外，你应该使用 `WORKDIR` 来替代类似于 `RUN cd ... && do-something` 的指令，后者难以阅读、排错和维护。\n\n### 官方镜像示例\n\n这些官方镜像的 Dockerfile 都是参考典范，详见 [docker-library/docs](https://github.com/docker-library/docs)。\n"
  },
  {
    "path": "appendix/command/README.md",
    "content": "# 附录三：Docker 命令查询\n\n## 基本语法\n\nDocker 命令有两大类，客户端命令和服务端命令。前者是主要的操作接口，后者用来启动 Docker Daemon。\n\n* 客户端命令：基本命令格式为 `docker [OPTIONS] COMMAND [arg...]`；\n\n* 服务端命令：基本命令格式为 `dockerd [OPTIONS]`。\n\n可以通过 `man docker` 或 `docker help` 来查看这些命令。\n\n接下来的小节对这两个命令进行介绍。\n"
  },
  {
    "path": "appendix/command/docker.md",
    "content": "## 客户端命令 - docker\n\n### 客户端命令选项\n\n* `--config=\"\"`：指定客户端配置文件，默认为 `~/.docker`；\n* `-D=true|false`：是否使用 debug 模式。默认不开启；\n* `-H, --host=[]`：指定命令对应 Docker 守护进程的监听接口，可以为 unix 套接字 `unix:///path/to/socket`，文件句柄 `fd://socketfd` 或 tcp 套接字 `tcp://[host[:port]]`，默认为 `unix:///var/run/docker.sock`；\n* `-l, --log-level=\"debug|info|warn|error|fatal\"`：指定日志输出级别；\n* `--tls=true|false`：是否对 Docker 守护进程启用 TLS 安全机制，默认为否；\n* `--tlscacert=/.docker/ca.pem`：TLS CA 签名的可信证书文件路径；\n* `--tlscert=/.docker/cert.pem`：TLS 可信证书文件路径；\n* `--tlskey=/.docker/key.pem`：TLS 密钥文件路径；\n* `--tlsverify=true|false`：启用 TLS 校验，默认为否。\n\n### 客户端命令\n\n可以通过 `docker COMMAND --help` 来查看这些命令的具体用法。\n\n* `attach`：依附到一个正在运行的容器中；\n* `build`：从一个 Dockerfile 创建一个镜像；\n* `commit`：从一个容器的修改中创建一个新的镜像；\n* `cp`：在容器和本地宿主系统之间复制文件中；\n* `create`：创建一个新容器，但并不运行它；\n* `diff`：检查一个容器内文件系统的修改，包括修改和增加；\n* `events`：从服务端获取实时的事件；\n* `exec`：在运行的容器内执行命令；\n* `export`：导出容器内容为一个 `tar` 包；\n* `history`：显示一个镜像的历史信息；\n* `images`：列出存在的镜像；\n* `import`：导入一个文件 (典型为 `tar` 包) 路径或目录来创建一个本地镜像；\n* `info`：显示一些相关的系统信息；\n* `inspect`：显示一个容器的具体配置信息；\n* `kill`：关闭一个运行中的容器 (包括进程和所有相关资源)；\n* `load`：从一个 tar 包中加载一个镜像；\n* `login`：注册或登录到一个 Docker 的仓库服务器；\n* `logout`：从 Docker 的仓库服务器登出；\n* `logs`：获取容器的 log 信息；\n* `network`：管理 Docker 的网络，包括查看、创建、删除、挂载、卸载等；\n* `node`：管理 swarm 集群中的节点，包括查看、更新、删除、提升/取消管理节点等；\n* `pause`：暂停一个容器中的所有进程；\n* `port`：查找一个 nat 到一个私有网口的公共口；\n* `ps`：列出主机上的容器；\n* `pull`：从一个 Docker 的仓库服务器下拉一个镜像或仓库；\n* `push`：将一个镜像或者仓库推送到一个 Docker 的注册服务器；\n* `rename`：重命名一个容器；\n* `restart`：重启一个运行中的容器；\n* `rm`：删除给定的若干个容器；\n* `rmi`：删除给定的若干个镜像；\n* `run`：创建一个新容器，并在其中运行给定命令；\n* `save`：保存一个镜像为 tar 包文件；\n* `search`：在 Docker index 中搜索一个镜像；\n* `service`：管理 Docker 所启动的应用服务，包括创建、更新、删除等；\n* `start`：启动一个容器；\n* `stats`：输出 (一个或多个) 容器的资源使用统计信息；\n* `stop`：终止一个运行中的容器；\n* `swarm`：管理 Docker swarm 集群，包括创建、加入、退出、更新等；\n* `tag`：为一个镜像打标签；\n* `top`：查看一个容器中的正在运行的进程信息；\n* `unpause`：将一个容器内所有的进程从暂停状态中恢复；\n* `update`：更新指定的若干容器的配置信息；\n* `version`：输出 Docker 的版本信息；\n* `volume`：管理 Docker volume，包括查看、创建、删除等；\n* `wait`：阻塞直到一个容器终止，然后输出它的退出符。\n\n### 一张图总结 Docker 的命令\n\n如图 16-1 所示，Docker 常用客户端命令可按功能分组理解。\n\n![Docker 命令总结](../../_images/cmd_logic.jpg)\n\n图 A-1：Docker 客户端命令分类示意图\n\n### 参考\n\n* [官方文档](https://docs.docker.com/reference/cli/docker/)\n"
  },
  {
    "path": "appendix/command/dockerd.md",
    "content": "## 服务端命令 - dockerd\n\n### 使用说明\n\n`dockerd` 参数会随版本变化。建议优先在目标机器上执行 `dockerd --help`，并以 `daemon.json` 为主进行持久化配置。\n\n### 常用选项：Docker Engine 29.x\n\n* `--config-file=\"/etc/docker/daemon.json\"`：指定 daemon 配置文件路径；\n* `--data-root=\"\"`：Docker 数据目录 (默认 `/var/lib/docker`)；\n* `-H, --host=[]`：指定 daemon 监听地址 (Unix socket / TCP)；\n* `-D, --debug`：开启调试日志；\n* `-l, --log-level=\"debug|info|warn|error|fatal\"`：日志级别；\n* `--group=\"\"`：Unix socket 所属用户组 (默认 `docker`)；\n* `--containerd=\"\"`：指定 containerd socket；\n* `--exec-opt=[]`：运行时执行选项 (如 cgroup 驱动)；\n* `--default-ulimit=[]`：设置容器默认 ulimit；\n* `--dns=[]` / `--dns-search=[]` / `--dns-opt=[]`：DNS 配置；\n* `--registry-mirror=[]`：镜像加速地址；\n* `--insecure-registry=[]`：允许访问不安全仓库；\n* `--iptables=true|false` / `--ip-forward=true|false` / `--ip-masq=true|false`：网络转发与 NAT 规则控制；\n* `--ipv6=true|false`：启用 IPv6；\n* `--storage-driver=\"\"` / `--storage-opt=[]`：存储驱动及参数；\n* `--log-driver=\"\"` / `--log-opt=[]`：容器日志驱动与参数；\n* `--authorization-plugin=[]`：鉴权插件；\n* `--selinux-enabled=true|false`：启用 SELinux 集成 (依赖发行版策略)；\n* `--userns-remap=...`：用户命名空间映射；\n* `--tls` / `--tlscacert` / `--tlscert` / `--tlskey` / `--tlsverify`：TLS 安全配置。\n\n### 历史参数提示\n\n以下参数已移除或不建议继续使用：\n\n* `--graph`：请改用 `--data-root`；\n* `--cluster-store` / `--cluster-advertise` / `--cluster-store-opt`：已移除；\n* `--disable-legacy-registry`：已移除。\n\n### 参考\n\n* [官方文档](https://docs.docker.com/reference/cli/dockerd/)\n"
  },
  {
    "path": "appendix/debug.md",
    "content": "## 附录五：如何调试 Docker\n\n### 开启 Debug 模式\n\n在 dockerd 配置文件 daemon.json (默认位于 /etc/docker/) 中添加\n\n```json\n{\n  \"debug\": true\n}\n```\n\n重启守护进程。\n\n```bash\n$ sudo kill -SIGHUP $(pidof dockerd)\n```\n\n此时 dockerd 会在日志中输入更多信息供分析。\n\n### 检查内核日志\n\n```bash\n$ sudo dmesg |grep dockerd\n$ sudo dmesg |grep runc\n```\n\n### Docker 不响应时处理\n\n可以杀死 dockerd 进程查看其堆栈调用情况。\n\n```bash\n$ sudo kill -SIGUSR1 $(pidof dockerd)\n```\n\n### 重置 Docker 本地数据\n\n*注意，本操作会移除所有的 Docker 本地数据，包括镜像和容器等。*\n\n更安全的替代方式是优先使用以下命令进行清理：\n\n```bash\n$ docker system prune\n```\n\n如果你只是想 “恢复出厂设置”，在 Docker Desktop 里也提供了相应入口。\n\n```bash\n$ sudo rm -rf /var/lib/docker\n```\n\n### 常见故障排查\n\n#### 容器启动失败\n\n如果容器启动后立即退出，可以使用 `docker logs` 查看原因。\n\n* **Exit Code 1**：应用程序错误。通常是配置错误或依赖缺失。\n* **Exit Code 137**：OOM (Out Of Memory)。容器内存不足被内核杀掉。\n  * 检查宿主机内存。\n  * 调整容器内存限制 (`--memory`)。\n* **Exit Code 127**：命令未找到。可能是 `ENTRYPOINT` 或 `CMD` 指定的命令不存在。\n\n#### 网络连接问题\n\n##### 容器内部无法联网\n\n1. 检查 Docker DNS 配置 (`/etc/docker/daemon.json`)。\n2. 检查宿主机防火墙 (iptables/firewalld) 是否拦截了转发。\n3. 容器内测试：`ping 8.8.8.8` (测试连通性)，`nslookup google.com` (测试 DNS)。\n\n##### 端口映射不通\n\n1. 检查容器端口是否正确监听：`netstat -tunlp` (宿主机) 或 `docker exec <container> netstat -tunlp`。\n2. 确认应用监听地址是 `0.0.0.0` 而不是 `127.0.0.1`。\n   * 如果应用监听在 `127.0.0.1`，只有容器内部能访问，映射到宿主机外部也无法被外部请求访问。\n\n#### 镜像拉取失败\n\n* **connection refused**：检查网络或代理设置。\n* **image not found**：检查镜像名称和 Tag 拼写。\n* **EOF / timeout**：网络不稳定，尝试配置镜像加速器。\n"
  },
  {
    "path": "appendix/faq/README.md",
    "content": "# 附录一：常见问题与错误速查\n\n## 镜像相关\n\n### 如何批量清理临时镜像文件？\n\n答：可以使用 `docker image prune` 命令。\n\n### 如何查看镜像支持的环境变量？\n\n答：可以使用 `docker run IMAGE env` 命令。\n\n### 本地的镜像文件都存放在哪里？\n\n答：与 Docker 相关的本地资源默认存放在 `/var/lib/docker/` 目录下，以 `overlay2` 文件系统为例，其中 `containers` 目录存放容器信息，`image` 目录存放镜像信息，`overlay2` 目录下存放具体的镜像层文件。\n\n### 构建 Docker 镜像应该遵循哪些原则？\n\n答：整体原则上，尽量保持镜像功能的明确和内容的精简，要点包括\n\n* 尽量选取满足需求但较小的基础系统镜像，例如大部分时候可以选择 `alpine` 镜像，仅有不足六兆大小；\n* 清理编译生成文件、安装包的缓存等临时文件；\n* 安装各个软件时候要指定准确的版本号，并避免引入不需要的依赖；\n* 从安全角度考虑，应用要尽量使用系统的库和依赖；\n* 如果安装应用时候需要配置一些特殊的环境变量，在安装后要还原不需要保持的变量值；\n* 使用 Dockerfile 创建镜像时候要添加。dockerignore 文件或使用干净的工作目录。\n\n更多内容请查看 [Dockerfile 最佳实践](../best_practices.md)\n\n### 碰到网络问题，无法 pull 镜像，命令行指定 http\\_proxy 无效？\n\n答：在 Docker 配置文件中添加 `export http_proxy=\"http://<PROXY_HOST>:<PROXY_PORT>\"`，之后重启 Docker 服务即可。\n\n## 容器相关\n\n### 容器退出后，通过 docker container ls 命令查看不到，数据会丢失么？\n\n答：容器退出后会处于终止 (exited) 状态，此时可以通过 `docker container ls -a` 查看。其中的数据也不会丢失，还可以通过 `docker start` 命令来启动它。只有删除掉容器才会清除所有数据。\n\n### 如何停止所有正在运行的容器？\n\n答：可以使用 `docker stop $(docker container ls -q)` 命令。\n\n### 如何批量清理已经停止的容器？\n\n答：可以使用 `docker container prune` 命令。\n\n### 如何获取某个容器的 PID 信息？\n\n答：可以使用\n\n```bash\ndocker inspect --format '{{ .State.Pid }}' <CONTAINER ID or NAME>\n```\n\n### 如何获取某个容器的 IP 地址？\n\n答：可以使用\n\n```bash\ndocker inspect --format '{{ .NetworkSettings.IPAddress }}' <CONTAINER ID or NAME>\n```\n\n### 如何给容器指定一个固定 IP 地址，而不是每次重启容器 IP 地址都会变？\n\n答：使用以下命令启动容器可以使容器 IP 固定不变\n\n```bash\n$ docker network create -d bridge --subnet 172.25.0.0/16 my-net\n\n$ docker run --network=my-net --ip=172.25.3.3 -itd --name=my-container busybox\n```\n\n### 如何临时退出一个正在交互的容器的终端，而不终止它？\n\n答：按 `Ctrl-p Ctrl-q`。如果按 `Ctrl-c` 往往会让容器内应用进程终止，进而会终止容器。\n\n### 使用 `docker port` 命令映射容器的端口时，系统报错 “Error：No public port ‘80’ published for xxx”？\n\n答：\n\n* 创建镜像时 `Dockerfile` 要通过 `EXPOSE` 指定正确的开放端口；\n* 容器启动时指定 `PublishAllPort = true`。\n\n### 可以在一个容器中同时运行多个应用进程么？\n\n答：一般并不推荐在同一个容器内运行多个应用进程。如果有类似需求，可以通过一些额外的进程管理机制，比如 `supervisord` 来管理所运行的进程。可以参考 [Docker 官方说明](https://docs.docker.com/engine/containers/multi-service_container/)。\n\n### 如何控制容器占用 CPU、内存等系统资源的份额？\n\n答：在使用 `docker create` 命令创建容器或使用 `docker run` 创建并启动容器的时候，可以使用 -c|--cpu-shares\\[=0] 参数来调整容器使用 CPU 的权重；使用 -m|--memory\\[=MEMORY] 参数来调整容器使用内存的大小。\n\n## 仓库相关\n\n### 仓库、注册服务器、注册索引有何关系？\n\n首先，仓库是存放一组关联镜像的集合，比如同一个应用的不同版本的镜像。\n\n注册服务器是存放实际的镜像文件的地方。注册索引则负责维护用户的账号、权限、搜索、标签等的管理。因此，注册服务器利用注册索引来实现认证等管理。\n\n## 配置相关\n\n### Docker 的配置文件放在哪里，如何修改配置？\n\n答：使用 `systemd` 的系统 (如 Ubuntu 22.04+、Debian 12+、Rocky/Alma/CentOS Stream 9+) 的配置文件在 `/etc/docker/daemon.json`。\n\n### 如何更改 Docker 的默认存储位置？\n\n答：Docker 的默认存储位置是 `/var/lib/docker`，如果希望将 Docker 的本地文件存储到其他分区，可以使用 Linux 软连接的方式来完成，或者修改配置文件 `/etc/docker/daemon.json` 的 `data-root` 项。可以使用 `docker info | grep \"Docker Root Dir\"` 查看当前使用的存储位置。\n\n例如，如下操作将默认存储位置迁移到 /storage/docker。\n\n```bash\n[root@s26 ~]# df -h\nFilesystem                    Size  Used Avail Use% Mounted on\n/dev/mapper/VolGroup-lv_root   50G  5.3G   42G  12% /\ntmpfs                          48G  228K   48G   1% /dev/shm\n/dev/sda1                     485M   40M  420M   9% /boot\n/dev/mapper/VolGroup-lv_home  222G  188M  210G   1% /home\n/dev/sdb2                     2.7T  323G  2.3T  13% /storage\n[root@s26 ~]# service docker stop\n[root@s26 ~]# cd /var/lib/\n[root@s26 lib]# mv docker /storage/\n[root@s26 lib]# ln -s /storage/docker/ docker\n[root@s26 lib]# ls -la docker\nlrwxrwxrwx. 1 root root 15 11月 17 13:43 docker -> /storage/docker\n[root@s26 lib]# service docker start\n```\n\n### 使用内存和 swap 限制启动容器时候报警告：“WARNING：Your kernel does not support cgroup swap limit。WARNING：Your kernel does not support swap limit capabilities。Limitation discarded。”？\n\n答：这是因为系统默认没有开启对内存和 swap 使用的统计功能，引入该功能会带来性能的下降。要开启该功能，可以采取如下操作：\n\n* 编辑 `/etc/default/grub` 文件 (Ubuntu 系统为例)，配置 `GRUB_CMDLINE_LINUX=\"cgroup_enable=memory swapaccount=1\"`\n* 更新 grub：`$ sudo update-grub`\n* 重启系统，即可。\n\n## Docker 与虚拟化\n\n### Docker 与 LXC 有何不同？\n\n答：LXC 利用 Linux 上相关技术实现了容器。Docker 则在如下的几个方面进行了改进：\n\n* 移植性：通过抽象容器配置，容器可以实现从一个平台移植到另一个平台；\n* 镜像系统：基于 OverlayFS 的镜像系统为容器的分发带来了很多的便利，同时共同的镜像层只需要存储一份，实现高效率的存储；\n* 版本管理：类似于 Git 的版本管理理念，用户可以更方便的创建、管理镜像文件；\n* 仓库系统：仓库系统大大降低了镜像的分发和管理的成本；\n* 周边工具：各种现有工具 (配置管理、云平台) 对 Docker 的支持，以及基于 Docker 的 PaaS、CI 等系统，让 Docker 的应用更加方便和多样化。\n\n### Docker 与 Vagrant 有何不同？\n\n答：两者的定位完全不同。\n\n* Vagrant 类似 Boot2Docker (一款运行 Docker 的最小内核)，是一套虚拟机的管理环境。Vagrant 可以在多种系统上和虚拟机软件中运行，可以在 Windows，Mac 等非 Linux 平台上为 Docker 提供支持，自身具有较好的包装性和移植性。\n* 原生的 Docker 自身只能运行在 Linux 平台上，但启动和运行的性能都比虚拟机要快，往往更适合快速开发和部署应用的场景。\n\n简单说：Vagrant 适合用来管理虚拟机，而 Docker 适合用来管理应用环境。\n\n### 开发环境中 Docker 和 Vagrant 该如何选择？\n\n答：Docker 不是虚拟机，而是进程隔离，对于资源的消耗很少，但是目前需要 Linux 环境支持。Vagrant 是虚拟机上做的封装，虚拟机本身会消耗资源。\n\n如果本地使用的 Linux 环境，推荐都使用 Docker。\n\n如果本地使用的是 macOS 或者 Windows 环境，那就需要开虚拟机，单一开发环境下 Vagrant 更简单；多环境开发下推荐在 Vagrant 里面再使用 Docker 进行环境隔离。\n\n## 其它\n\n### Docker 能在非 Linux 平台上运行么？比如 Windows 或 macOS\n\n答：完全可以。安装方法请查看[安装 Docker](../../03_install/README.md) 一节\n\n### 如何将一台宿主主机的 Docker 环境迁移到另外一台宿主主机？\n\n答：停止 Docker 服务。将整个 Docker 存储文件夹复制到另外一台宿主主机，然后调整另外一台宿主主机的配置即可。\n\n### 如何进入 Docker 容器的网络命名空间？\n\n答：Docker 在创建容器后，删除了宿主主机上 `/var/run/netns` 目录中的相关的网络命名空间文件。因此，在宿主主机上是无法看到或访问容器的网络命名空间的。\n\n用户可以通过如下方法来手动恢复它。\n\n首先，使用下面的命令查看容器进程信息，比如这里的 1234。\n\n```bash\n$ docker inspect --format='{{. State.Pid}} ' $container_id\n1234\n```\n\n接下来，在 `/proc` 目录下，把对应的网络命名空间文件链接到 `/var/run/netns` 目录。\n\n```bash\n$ sudo ln -s /proc/1234/ns/net /var/run/netns/\n```\n\n然后，在宿主主机上就可以看到容器的网络命名空间信息。例如\n\n```bash\n$ sudo ip netns show\n1234\n```\n\n此时，用户可以通过正常的系统命令来查看或操作容器的命名空间了。例如修改容器的 IP 地址信息为 `172.17.0.100/16`。\n\n```bash\n$ sudo ip netns exec 1234 ifconfig eth0 172.17.0.100/16\n```\n\n### 如何获取容器绑定到本地那个 veth 接口上？\n\n答：Docker 容器启动后，会通过 veth 接口对连接到本地网桥，veth 接口命名跟容器命名毫无关系，十分难以找到对应关系。\n\n最简单的一种方式是通过查看接口的索引号，在容器中执行 `ip a` 命令，查看到本地接口最前面的接口索引号，如 `205`，将此值加上 1，即 `206`，然后在本地主机执行 `ip a` 命令，查找接口索引号为 `206` 的接口，两者即为连接的 veth 接口对。\n"
  },
  {
    "path": "appendix/faq/errors.md",
    "content": "## 常见错误速查表\n\n| 错误信息 / 现象 | 可能原因 | 解决方案 |\n| :--- | :--- | :--- |\n| `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 |\n| `permission denied while trying to connect to the Docker daemon socket` | 当前用户不在 `docker` 用户组 | `sudo usermod -aG docker $USER` (需重新登录) |\n| `manifest for ... not found: manifest unknown` | 镜像 tag 不存在 | 检查 Docker Hub 该镜像是否存在该 tag，或拼写是否正确 |\n| `connection refused` (pull image) | 网络不通或镜像源无法访问 | 检查网络，配置[镜像加速器](../../03_install/3.9_mirror.md) |\n| `Bind for 0.0.0.0:8080 failed: port is already allocated` | 端口被占用 | 检查占用端口的进程 (`lsof -i:8080`) 并杀掉，或换个端口映射 (`-p 8081:80`) |\n| `exec user process caused \"exec format error\"` | 架构不匹配 (如在 x86 上跑 ARM 镜像) | 使用 `docker buildx` 构建多架构镜像，或拉取对应架构的镜像 |\n| `standard_init_linux.go:211: exec user process caused \"no such file or directory\"` | 找不到解释器或依赖库 | 检查 `ENTRYPOINT`/`CMD` 脚本开头的 shebang (`#!/bin/sh` vs `#!/bin/bash`)，或确认二进制文件是否依赖缺失 (Alpine 常见缺少 glibc) |\n| `iptables: No chain/target/match by that name` | 防火墙规则缺失或冲突 | 重启 Docker 服务重置 iptables 链: `sudo systemctl restart docker` |\n| 容器内无法访问外网 | DNS 配置或转发问题 | 检查 `/etc/docker/daemon.json` 中的 DNS 配置 |\n"
  },
  {
    "path": "appendix/glossary.md",
    "content": "# 附录七：术语表\n\n本附录整理了本书中常见的一些专业术语及其解释。\n\n## A\n\n* **Alpine**：一个轻量级的 Linux 发行版，常作为基础镜像用于构建体积较小的 Docker 镜像。\n* **API (Application Programming Interface)**：应用程序编程接口，Docker Daemon 提供 RESTful API 供客户端或外部程序与之交互。\n\n## B\n\n* **Base Image (基础镜像)**：没有父镜像的镜像，通常是操作系统的最小安装集合（如 `ubuntu` 或 `alpine`）。\n* **BuildKit**：Docker 下一代的构建引擎，提供了更高的构建性能、更好的缓存处理和并发构建支持。\n* **Buildx**：Docker CLI 的一个插件，扩展了构建功能，支持 BuildKit 的所有高级特性，例如多系统架构镜像构建。\n\n## C\n\n* **Cgroups (Control Groups)**：控制组，Linux 内核特性，用于限制、记录、隔离进程组使用的物理资源（如 CPU、内存、磁盘 I/O 等）。\n* **Cluster (集群)**：一组协同工作的节点（如主机、虚拟机等），在容器领域常指 Kubernetes 集群。\n* **Compose (Docker Compose)**：用于定义和运行多容器 Docker 应用程序的工具，通过 YAML 文件配置应用服务。\n* **Container (容器)**：镜像的运行实例，带有额外的可写文件层，具有独立性。\n* **Containerd**：行业标准的容器运行时，核心功能是管理宿主机上容器的生命周期（创建、启动、停止、销毁）。\n\n## D\n\n* **Daemon (守护进程)**：Docker 的后台守护进程，负责接收和处理 Docker API 请求，并管理镜像、容器、网络和数据卷等对象。\n* **Docker**：开源的应用容器引擎，让开发者可以打包应用程序及其依赖包到一个可移植的容器中，然后发布到任何流行的 Linux 或 Windows 机器上。\n* **Docker Desktop**：包含 Docker Engine、Docker CLI 客户端、Docker Compose 和 Kubernetes 等的桌面应用程序，适用于 macOS 和 Windows。\n* **Docker Hub**：Docker 官方的公共镜像仓库服务，提供容器镜像的存储和分发。\n* **Dockerfile**：包含用于组合镜像的命令的文本文件，Docker 通过读取 `Dockerfile` 中的指令即可自动完成镜像构建。\n\n## E\n\n* **Etcd**：一个高可用、强一致性的分布式键值存储系统，常用于容器集群（如 Kubernetes）的服务发现和状态配置管理。\n\n## I\n\n* **Image (镜像)**：Docker 镜像是一个只读模板，带有创建 Docker 容器的说明。\n\n## K\n\n* **Kubernetes (K8s)**：开源的容器编排引擎，用于自动化容器化应用程序的部署、扩展和管理。\n\n## L\n\n* **Layer (镜像层)**：Docker 镜像由多个只读层叠合而成，每一层通常代表 Dockerfile 中的一条指令的操作结果，通过联合文件系统（UFS）叠加在一起形成完整的文件系统。\n\n## M\n\n* **Multistage Build (多阶段构建)**：Dockerfile 中的特性，允许在同一个 Dockerfile 中使用多个 `FROM` 语句，从一个阶段复制所需的构建产物到另一个阶段，从而大幅减小最终镜像的体积。\n\n## N\n\n* **Namespace (命名空间)**：Linux 内核特性，用于隔离各种系统资源，如进程、网络、挂载点等，使容器看起来就像是一个独立的操作系统。\n* **Node (节点)**：容器集群（如 Kubernetes）中的一台工作机器，可以是物理机或虚拟机。\n\n## O\n\n* **OCI (Open Container Initiative)**：开放容器规范，由多家行业领头企业共同制定的容器运行时和镜像格式的行业标准。\n* **Orchestration (编排)**：自动化部署、管理、扩展和网络配置容器的系统和技术（如 Kubernetes）。\n\n## P\n\n* **Pod**：Kubernetes 中最小的、可部署的计算单元，包含一个或多个紧密相关的容器，共享相同的网络命名空间和存储。\n* **Prometheus**：开源的系统监控和告警工具包，广泛应用于云原生的监控体系中。\n\n## R\n\n* **Registry (注册服务器)**：提供 Docker 镜像下载和上传等存储分发服务的服务器。\n* **Repository (仓库)**：集中存放某个应用的所有镜像的地方，通常由镜像名定义。一个 Registry 中可以包含多个 Repository。\n\n## S\n\n* **Swarm (Docker Swarm)**：Docker 原生的集群和编排管理工具，可将多个 Docker 主机组合成一个统一的虚拟 Docker 主机池。\n\n## U\n\n* **UFS (Union File System)**：联合文件系统，一种分层、轻量级并且高性能的文件系统，它支持对文件系统的修改一层层叠加。\n\n## V\n\n* **Volume (数据卷)**：专为绕过联合文件系统而设计的特殊目录，用于实现容器数据的持久化，或在多个容器之间提供文件共享。\n"
  },
  {
    "path": "appendix/learning_roadmap.md",
    "content": "## 附录八：Docker 学习路线图与知识体系\n\n本附录为学习者提供清晰的学习路线、知识点依赖关系、认证指南和常见面试题，帮助快速成长为 Docker 和 DevOps 专家。\n\n### 学习阶段划分\n\nDocker 学习可分为四个递进阶段，每个阶段都有明确的学习目标和时间投入。\n\n#### 第一阶段：基础入门（0-2 周）\n\n**学习目标：**\n- 理解容器化的基本概念\n- 能够运行、管理基本的容器\n- 了解镜像和仓库的基本操作\n\n**核心内容：**\n```text\nDocker 简介\n├── 为什么需要 Docker\n├── 容器 vs 虚拟机 vs 云计算\n└── Docker 的三大核心概念\n    ├── 镜像（Image）\n    ├── 容器（Container）\n    └── 仓库（Repository）\n\n基础命令\n├── docker run / create / start / stop / rm\n├── docker ps / logs / exec / inspect\n├── docker pull / push / tag\n└── docker build -t\n\nDocker 安装配置\n├── Linux 平台安装\n├── macOS 和 Windows 安装\n├── 镜像加速器配置\n└── 权限和用户配置\n```\n\n**学习资源：**\n- [官方教程](https://docs.docker.com/get-started/)\n- 本书第 1-3 章：入门篇基础概念\n- [Docker CLI 参考](https://docs.docker.com/engine/reference/commandline/)\n\n**时间投入：**\n- 理论学习：3-4 小时\n- 实操练习：8-10 小时\n- 总计：2 周\n\n**验证学习成果：**\n```bash\n# 完成以下任务说明基础入门完成\n1. 运行官方 nginx 镜像，访问 http://localhost\n2. 使用 docker exec 进入容器修改首页\n3. 提交修改为新镜像\n4. 推送镜像到 Docker Hub（需创建账户）\n```\n\n#### 第二阶段：核心开发（2-6 周）\n\n**学习目标：**\n- 掌握 Dockerfile 编写\n- 能够构建自己的应用镜像\n- 理解数据管理和网络配置\n- 熟悉 Docker Compose 编排\n\n**核心内容：**\n```text\nDockerfile 指令详解\n├── FROM / RUN / COPY / ADD\n├── WORKDIR / ENV / ARG\n├── EXPOSE / CMD / ENTRYPOINT\n├── VOLUME / USER / HEALTHCHECK\n└── 最佳实践和性能优化\n    ├── 分层缓存机制\n    ├── 减少镜像体积\n    ├── 多阶段构建\n    └── 安全最佳实践\n\n容器数据管理\n├── 数据卷（Volume）\n│   ├── 命名卷\n│   ├── 匿名卷\n│   └── 卷挂载最佳实践\n├── 绑定挂载（Bind Mount）\n│   ├── 宿主机路径映射\n│   └── 权限和隔离\n└── tmpfs 挂载\n    └── 临时文件系统\n\n容器网络\n├── 网络类型\n│   ├── bridge（默认）\n│   ├── host\n│   ├── overlay\n│   └── macvlan\n├── 端口映射\n├── 容器互联\n├── DNS 配置\n└── 自定义网络\n\nDocker Compose\n├── compose.yml/docker-compose.yml 编写\n├── services 定义\n├── volumes 配置\n├── networks 配置\n├── 依赖关系\n├── 环境变量\n└── 命令操作\n    ├── up / down / ps / logs\n    ├── exec / run\n    └── build / push\n```\n\n**学习资源：**\n- 本书第 4-11 章：进阶篇\n- [Docker 官方最佳实践](https://docs.docker.com/develop/dev-best-practices/)\n- [Dockerfile 参考](https://docs.docker.com/engine/reference/builder/)\n\n**时间投入：**\n- 理论学习：8-10 小时\n- 实操练习：30-40 小时（多个实战项目）\n- 总计：4-6 周\n\n**项目实战：**\n```text\n项目 1: Python Web 应用（Flask/Django）\n- 编写多阶段 Dockerfile\n- 使用 Compose 配置数据库\n- 实现热重载开发环境\n\n项目 2: Node.js 微服务\n- 优化镜像大小\n- 配置 Compose 多个服务\n- 设置网络和环保境变量\n\n项目 3: 数据库容器化\n- PostgreSQL/MySQL 配置\n- 数据持久化\n- 备份恢复策略\n```\n\n#### 第三阶段：生产优化（6-12 周）\n\n**学习目标：**\n- 掌握容器安全最佳实践\n- 理解性能监控和优化\n- 学会容器编排（Kubernetes 基础）\n- 熟悉 CI/CD 集成\n\n**核心内容：**\n```text\n容器安全\n├── 镜像安全\n│   ├── 漏洞扫描（Trivy/Grype/Snyk）\n│   ├── 镜像签名和验证（Cosign）\n│   ├── SBOM 生成和管理\n│   └── 供应链安全\n├── 运行时安全\n│   ├── 用户和权限\n│   ├── Linux 能力机制\n│   ├── AppArmor 和 SELinux\n│   ├── Rootless 容器\n│   └── 安全的 Docker socket 访问\n└── 宿主机安全\n    ├── API 访问控制\n    ├── TLS 认证\n    └── 审计日志\n\n性能监控和优化\n├── 监控指标体系\n│   ├── CPU / 内存 / 网络 / I/O\n│   └── 应用级指标\n├── 监控工具\n│   ├── docker stats\n│   ├── cAdvisor\n│   ├── Prometheus\n│   └── Grafana\n├── 性能优化\n│   ├── 镜像大小优化\n│   ├── 内存和 CPU 限制\n│   ├── OOM 诊断和处理\n│   └── 网络性能优化\n└── 日志管理\n    ├── 日志驱动配置\n    ├── ELK Stack\n    └── 日志聚合\n\n容器编排基础\n├── Kubernetes 核心概念\n│   ├── Pod / Deployment / Service\n│   ├── ConfigMap / Secret\n│   └── 健康检查和自动恢复\n├── 容器执行环境\n│   ├── containerd\n│   ├── CRI-O\n│   └── Docker\n├── 网络插件\n│   ├── CNI 标准\n│   ├── Calico / Flannel / Cilium\n│   └── 网络策略\n└── 存储和有状态应用\n    ├── PV / PVC\n    ├── StorageClass\n    └── StatefulSet\n\nCI/CD 集成\n├── GitHub Actions\n│   ├── 镜像构建和推送\n│   ├── 安全扫描\n│   └── 自动化测试\n├── GitLab CI\n├── Jenkins Docker 集成\n└── Drone\n\n生态工具\n├── Buildx（多架构构建）\n├── Skopeo（镜像管理）\n├── Podman（替代方案）\n├── Buildah（镜像构建）\n└── Kollabot\n```\n\n**学习资源：**\n- 本书第 12-21 章：深入篇和实战篇\n- [Kubernetes 官方文档](https://kubernetes.io/docs/)\n- [CNCF 学习路线](https://landscape.cncf.io/)\n\n**时间投入：**\n- 理论学习：15-20 小时\n- 实操练习：60-80 小时（多个生产级项目）\n- 总计：6-12 周\n\n**项目实战：**\n```text\n项目 1: 安全镜像构建流程\n- 集成 Trivy 扫描\n- 镜像签名和验证\n- 生成 SBOM 文档\n\n项目 2: 完整监控栈\n- 搭建 Prometheus + Grafana\n- 配置告警规则\n- 性能数据采集和分析\n\n项目 3: CI/CD 流程\n- GitHub Actions 或 GitLab CI 配置\n- 自动化镜像构建\n- 安全扫描和合规检查\n- 自动化部署到 Kubernetes\n\n项目 4: Kubernetes 集群部署\n- 本地 K3s/Kind 集群\n- 部署有状态应用\n- 配置持久化存储\n```\n\n#### 第四阶段：专家深造（12+ 周）\n\n**学习目标：**\n- 掌握 Kubernetes 高级特性\n- 理解容器运行时底层实现\n- 能够设计和优化大规模容器平台\n- 贡献开源社区\n\n**核心内容：**\n```text\nKubernetes 高级特性\n├── 集群管理\n│   ├── 节点管理和驱逐\n│   ├── 集群自动扩缩容\n│   └── 节点亲和性和污点容忍\n├── 存储编排\n│   ├── 动态存储配置\n│   ├── 有状态应用管理（StatefulSet）\n│   └── 备份和灾难恢复\n├── 服务网格（Service Mesh）\n│   ├── Istio / Linkerd / Cilium\n│   ├── 流量管理\n│   └── 可观测性增强\n├── 安全和多租户\n│   ├── RBAC（角色访问控制）\n│   ├── Network Policy 深入\n│   ├── Pod Security Policy\n│   └── 准入控制器（Admission Controller）\n└── 性能和扩展性\n    ├── 大规模集群优化\n    ├── 自定义 Operator\n    └── 集群联邦\n\n容器运行时底层\n├── Linux 内核机制\n│   ├── Namespace 详解\n│   ├── Cgroup v1 和 v2\n│   ├── OverlayFS 和 UnionFS\n│   └── SELinux 和 AppArmor\n├── 容器运行时\n│   ├── containerd 源码阅读\n│   ├── runc 实现\n│   ├── gVisor 和 Kata\n│   └── Firecracker\n└── OCI 标准\n    ├── Image Spec\n    └── Runtime Spec\n\nDevOps 工程化\n├── 大规模集群管理\n│   ├── Helm / Kustomize\n│   ├── GitOps（Flux / ArgoCD）\n│   └── 配置管理\n├── 灾难恢复和高可用\n│   ├── 多集群部署\n│   ├── 故障转移\n│   └── 备份策略\n├── 成本优化\n│   ├── 资源申请和限制\n│   ├── 自动扩缩容\n│   └── 成本监控\n└── 团队协作\n    ├── GitFlow 工作流\n    ├── 代码审查\n    └── 文档和最佳实践传播\n```\n\n**贡献机会：**\n- [Kubernetes](https://github.com/kubernetes/kubernetes)\n- [Cilium](https://github.com/cilium/cilium)\n- [Prometheus](https://github.com/prometheus/prometheus)\n- [Docker/Moby](https://github.com/moby/moby)\n\n### 知识点依赖关系\n\n```text\n基础概念 (Week 0-2)\n├── 容器 vs 虚拟机\n├── Docker 三大概念\n└── 基础命令\n    ↓\nDockerfile 和镜像构建 (Week 2-4)\n├── Dockerfile 指令\n├── 多阶段构建\n└── 镜像优化\n    ↓ ↓ ↓\n数据管理 ← 网络配置 ← Docker Compose (Week 4-6)\n├── Volume    ├── Bridge    ├── YAML 编写\n├── Bind Mount├── Overlay   ├── 多容器编排\n└── tmpfs     └── 自定义网络└── 开发工作流\n    ↓            ↓            ↓\n    └─────────────────────────┘\n          实战项目开发 (Week 6-10)\n          ├── Web 应用容器化\n          ├── 数据库容器化\n          ├── 微服务架构\n          └── 本地开发环境\n              ↓\n容器安全 ← 性能优化 ← 监控和日志 (Week 10-14)\n├── 镜像扫描  ├── 大小优化  ├── Prometheus\n├── 漏洞管理  ├── 内存优化  ├── Grafana\n├── 镜像签名  ├── CPU 优化  └── ELK Stack\n└── SBOM    └── 诊断工具\n    ↓          ↓          ↓\n    └─────────────────────┘\n          安全生产环境 (Week 14-18)\n          ├── CI/CD 流程\n          ├── 镜像仓库\n          ├── 日志集中\n          └── 告警系统\n              ↓\nKubernetes 基础 (Week 18-24)\n├── Pod / Service / Deployment\n├── 资源管理\n├── 存储管理\n└── 网络策略\n    ↓\nKubernetes 进阶 (Week 24-36)\n├── StatefulSet / DaemonSet\n├── Operator 开发\n├── 集群管理\n└── 服务网格\n    ↓\n企业级平台设计 (Week 36+)\n├── 多集群管理\n├── GitOps 工作流\n├── 成本优化\n└── 开源贡献\n```\n\n### 推荐学习资源\n\n#### 官方文档\n\n| 资源 | URL | 推荐程度 |\n|------|-----|--------|\n| Docker 官方文档 | [docs.docker.com](https://docs.docker.com) | ⭐⭐⭐⭐⭐ |\n| Docker Hub | [hub.docker.com](https://hub.docker.com) | ⭐⭐⭐⭐⭐ |\n| Kubernetes 官方 | [kubernetes.io/docs](https://kubernetes.io/docs) | ⭐⭐⭐⭐⭐ |\n| CNCF 景观 | [landscape.cncf.io](https://landscape.cncf.io) | ⭐⭐⭐⭐ |\n\n#### 在线课程\n\n- **Udemy**：Docker 和 Kubernetes 完整课程（70-100 小时）\n- **Linux Academy**：Linux 和容器管理\n- **A Cloud Guru**：AWS/Azure 容器服务\n- **Pluralsight**：Docker 和容器生态系统\n\n#### 书籍推荐\n\n- 《Docker 深入浅出》- 本书的原版\n- 《Kubernetes 权威指南》- 深入 Kubernetes 的必读书\n- 《容器技术核心技术与应用》- 理解底层实现\n- 《SRE Google 运维之道》- 生产环保最佳实践\n\n#### 博客和社区\n\n- [Docker 官方博客](https://www.docker.com/blog/)\n- [Kubernetes 官方博客](https://kubernetes.io/blog/)\n- [CNCF 博客](https://www.cncf.io/blog/)\n- [DZone](https://dzone.com/containers-cloud)\n\n### 认证指南\n\n#### Docker 认证\n\n**Docker Certified Associate (DCA)**\n\n考试信息：\n- 题目数：55 道\n- 时间限制：90 分钟\n- 及格分数：73%（约 41 道题）\n- 费用：$165 USD\n- 有效期：3 年\n\n考试内容比例：\n```text\n镜像和仓库（20%）\n- 镜像构建和管理\n- 镜像层和缓存\n- 私有仓库配置\n\n容器运行（15%）\n- 容器生命周期\n- 资源限制\n- 容器隔离\n\n网络（15%）\n- 网络驱动\n- 容器通信\n- 端口映射\n\n存储（10%）\n- Volume 管理\n- 数据持久化\n- 绑定挂载\n\n编排（20%）\n- Docker Compose\n- Docker Swarm 基础\n\n安全（15%）\n- 用户和权限\n- 密钥管理\n- 镜像安全\n- 守护进程安全\n\n和日志（5%）\n- Logging drivers\n- 事件处理\n```\n\n准备建议：\n```bash\n# 1. 学习本书第 1-11 章（基础到中级）\n# 2. 完成 20+ 个实战项目\n# 3. 参考官方学习指南\ncurl https://docker.training.kodekloud.com/dca-guide\n\n# 4. 模拟考试\n- Linux Academy DCA 练习题\n- Whizlabs DCA 模拟考试\n\n# 5. 重点掌握的命令\ndocker build / push / pull / tag\ndocker run / exec / logs / inspect / ps\ndocker volume / network / service\ndocker compose up / down / logs / ps\ndocker stats / events / inspect\n```\n\n#### Kubernetes 认证\n\n**认证路径：**\n1. **CKA - Certified Kubernetes Administrator**\n   - 难度：高\n   - 时间：3 小时（实操）\n   - 费用：$395\n   - 内容：集群安装、管理、故障排查\n\n2. **CKAD - Certified Kubernetes Application Developer**\n   - 难度：中\n   - 时间：2 小时（实操）\n   - 费用：$395\n   - 内容：应用开发和部署\n\n3. **CKS - Certified Kubernetes Security Specialist**\n   - 难度：很高\n   - 时间：2 小时（实操）\n   - 费用：$395\n   - 内容：安全最佳实践\n\n### 常见面试题与答案要点\n\n#### 基础概念面试题\n\n**Q1: Docker 容器和虚拟机有什么区别？**\n\nA（要点）：\n```text\n虚拟机：\n- 完整的操作系统环境（GB 级）\n- 启动时间：分钟级\n- 隔离级别：完全硬件隔离\n- 性能开销：高（5-20%）\n\n容器：\n- 共享内核，包含应用和依赖（MB 级）\n- 启动时间：秒级\n- 隔离级别：进程级隔离（Namespace/Cgroup）\n- 性能开销：低（1-5%）\n\n总结：容器更轻量、更快、密度更高\n```\n\n**Q2: 什么是 Docker 镜像？它如何存储的？**\n\nA（要点）：\n```text\n镜像本质：\n- 只读的文件系统快照\n- 分层存储结构\n- 每一层是前一层的增量\n\n存储方式：\n- Union FS：多个只读层 + 一个可写层\n- 每个 RUN/COPY/ADD 指令创建一层\n- 层之间通过 diff 增量存储，节省空间\n\n优点：\n- 共享基础层减少存储\n- 层级缓存加快构建\n- 支持高效分发\n```\n\n**Q3: 容器如何实现隔离？**\n\nA（要点）：\n```text\n技术手段：\n1. Namespace（资源隔离）：\n   - PID Namespace：进程隔离\n   - Network Namespace：网络隔离\n   - Mount Namespace：文件系统隔离\n   - UTS Namespace：主机名隔离\n   - IPC Namespace：进程间通信隔离\n\n2. Cgroup（资源限制）：\n   - 限制 CPU 使用\n   - 限制内存使用\n   - 限制磁盘 I/O\n   - 限制网络带宽\n\n3. Linux 能力机制（权限控制）：\n   - 削减不必要的 root 权限\n   - 限制容器能力\n\n4. SELinux / AppArmor（强制访问控制）\n```\n\n#### Dockerfile 面试题\n\n**Q4: 如何优化 Docker 镜像大小？**\n\nA（要点）：\n```text\n1. 选择合适的基础镜像：\n   scratch < alpine:3.17 < python:3.11-slim < python:3.11\n\n2. 多阶段构建：\n   - 构建阶段只保留编译工具\n   - 运行阶段只包含最终二进制\n   - 典型场景：Go、Node.js、Java\n\n3. 清理包管理器缓存：\n   apt-get clean && rm -rf /var/lib/apt/lists/*\n   yum clean all && rm -rf /var/cache/yum\n   pip install --no-cache-dir\n\n4. 合并 RUN 指令：\n   减少镜像层数\n\n5. 使用 .dockerignore：\n   排除不必要的构建上下文\n\n6. 去除调试符号：\n   Go: -ldflags=\"-w -s\"\n   C/C++: strip binary\n\n7. 压缩资源：\n   gzip 静态文件，压缩图片\n```\n\n**Q5: CMD 和 ENTRYPOINT 有什么区别？**\n\nA（要点）：\n```text\nCMD：\n- 定义容器默认命令\n- 容器运行时可被覆盖：docker run image_name custom_cmd\n- 可以有多个 CMD，只有最后一个生效\n\nENTRYPOINT：\n- 定义容器的可执行程序\n- 容器运行时参数追加而非覆盖\n- 与 CMD 配合使用\n\n推荐用法：\nENTRYPOINT [\"python\", \"app.py\"]\nCMD [\"--port\", \"8000\"]\n\n# 运行 docker run image --debug 会执行：\n# python app.py --debug\n```\n\n#### 网络和存储面试题\n\n**Q6: Docker 网络驱动的区别？**\n\nA（要点）：\n```text\nBridge（默认）：\n- 虚拟网桥，容器间通过网桥通信\n- 支持端口映射\n- 隔离性好，性能适中\n\nHost：\n- 使用宿主机网络栈\n- 性能最优，隔离性最差\n- 容器端口直接映射到宿主机\n\nOverlay：\n- 跨主机通信，基于 VXLAN\n- Swarm 和 Kubernetes 标准\n- 性能略低，支持分布式\n\nmacvlan：\n- 容器获得 MAC 地址\n- 表现为物理机，性能好\n- 用于物理网络集成\n\nNone：\n- 无网络，完全隔离\n```\n\n**Q7: Volume 和 Bind Mount 有什么区别？**\n\nA（要点）：\n```text\nVolume：\n- Docker 管理，存储位置：/var/lib/docker/volumes/\n- 跨平台兼容，隔离性好\n- 支持驱动，可扩展\n- 推荐在生产环境使用\n\nBind Mount：\n- 宿主机管理，任意位置\n- 跨平台兼容性一般\n- 性能好，用于开发环境\n- 权限管理复杂\n\ntmpfs：\n- 内存文件系统，不持久化\n- 用于临时文件、敏感数据\n- 性能最好，重启丢失\n```\n\n#### 安全和生产面试题\n\n**Q8: 如何提高 Docker 安全性？**\n\nA（要点）：\n```text\n镜像安全：\n- 使用官方镜像或可信镜像源\n- 定期扫描漏洞（Trivy/Grype）\n- 镜像签名验证（Cosign）\n- 生成和管理 SBOM\n\n容器运行：\n- 以非 root 用户运行\n- 使用 read-only 文件系统\n- 限制 Linux 能力\n- 使用 AppArmor 或 SELinux\n\n宿主机安全：\n- 启用 TLS 认证 API\n- 不暴露 /var/run/docker.sock\n- 使用 Rootless 容器\n- 定期更新 Docker\n\n网络安全：\n- 使用自定义网络隔离\n- 配置网络策略\n- 限制出入站流量\n```\n\n**Q9: 容器被 OOM 杀死，如何诊断和解决？**\n\nA（要点）：\n```text\n诊断：\n1. 检查容器是否被 OOM 杀死：\n   docker inspect <container> | grep OOMKilled\n\n2. 查看宿主机日志：\n   dmesg | grep -i oom\n   journalctl -u docker | grep -i oom\n\n3. 监控内存使用：\n   docker stats <container>\n   docker exec <container> ps aux --sort=-%mem\n\n解决：\n1. 增加内存限制：\n   docker update -m 2g <container>\n\n2. 检查内存泄漏：\n   使用内存分析工具（heapdump、pprof）\n\n3. 优化应用：\n   - 增加垃圾回收频率\n   - 减少缓存大小\n   - 使用对象池模式\n\n4. 使用内存交换（最后手段）：\n   docker run -m 512m --memory-swap 1g\n```\n\n**Q10: 如何在 CI/CD 中集成 Docker？**\n\nA（要点）：\n```text\n构建阶段：\n- 触发器：Push / PR 事件\n- 构建镜像：docker build\n- 标记：git sha、版本号\n- 扫描：Trivy 漏洞扫描\n- 签名：Cosign 镜像签名\n\n存储阶段：\n- 推送到镜像仓库：docker push\n- 记录 SBOM 和扫描报告\n\n部署阶段：\n- 验证镜像签名\n- 获取镜像摘要\n- 更新部署配置\n- 触发 GitOps 工作流\n\n监控阶段：\n- 收集应用日志\n- 监控性能指标\n- 告警异常情况\n\n示例工作流：\n1. GitHub Actions / GitLab CI 监听 push\n2. 运行单元测试\n3. 构建 Docker 镜像\n4. 推送到 Docker Hub / ECR\n5. 触发 ArgoCD / Flux 自动部署\n6. 监控部署状态\n```\n\n### 学习进度跟踪模板\n\n```markdown\n# Docker 学习进度跟踪\n\n## 第一阶段：基础入门（目标：2 周）\n- [ ] 学完第 1-3 章（6 小时）\n- [ ] 完成基础命令练习（10 小时）\n- [ ] 运行官方镜像\n- [ ] 创建和推送第一个镜像到 Docker Hub\n- [ ] 完成度：___%\n\n## 第二阶段：核心开发（目标：4-6 周）\n- [ ] 学完第 4-11 章（15 小时）\n- [ ] 完成 3 个 Dockerfile 最佳实践项目\n- [ ] 掌握 Docker Compose（5 个项目）\n- [ ] 学习数据管理和网络（8 小时）\n- [ ] 完成度：___%\n\n## 第三阶段：生产优化（目标：6-12 周）\n- [ ] 学完第 12-21 章（25 小时）\n- [ ] 镜像安全扫描和签名\n- [ ] 搭建完整监控栈\n- [ ] 配置 CI/CD 流程\n- [ ] Kubernetes 基础（30 小时）\n- [ ] 完成度：___%\n\n## 第四阶段：专家深造（目标：12+ 周）\n- [ ] Kubernetes 高级特性\n- [ ] 服务网格学习\n- [ ] 底层实现研究\n- [ ] 贡献开源项目\n- [ ] 完成度：___%\n\n## 证书目标\n- [ ] Docker DCA 认证\n- [ ] CKA 认证\n- [ ] CKAD 认证\n\n## 实战项目清单\n- [ ] Python Web 应用容器化\n- [ ] Node.js 微服务\n- [ ] 数据库容器化\n- [ ] 完整微服务架构\n- [ ] 监控和日志系统\n- [ ] CI/CD 流程实现\n```\n\n### 快速参考速查表\n\n**常用命令速查：**\n\n```bash\n# 镜像管理\ndocker build -t image:tag .              # 构建镜像\ndocker images                             # 列出镜像\ndocker rmi image:tag                      # 删除镜像\ndocker tag source:tag target:tag          # 标记镜像\ndocker push registry/image:tag            # 推送镜像\ndocker pull image:tag                     # 拉取镜像\ndocker history image:tag                  # 查看镜像历史\ndocker inspect image:tag                  # 查看镜像详情\n\n# 容器管理\ndocker run [OPTIONS] image                # 运行容器\ndocker ps [-a]                            # 列出容器\ndocker stop/start/restart container       # 容器生命周期\ndocker rm container                       # 删除容器\ndocker logs [-f] container                # 查看日志\ndocker exec -it container cmd             # 进入容器\ndocker inspect container                  # 查看容器详情\ndocker stats [container]                  # 查看资源使用\n\n# 网络管理\ndocker network ls                         # 列出网络\ndocker network create name                # 创建网络\ndocker network connect/disconnect         # 连接/断开网络\ndocker network inspect name               # 查看网络详情\n\n# 卷管理\ndocker volume ls                          # 列出卷\ndocker volume create name                 # 创建卷\ndocker volume rm name                     # 删除卷\ndocker volume inspect name                # 查看卷详情\n\n# Docker Compose\ndocker compose up [-d]                    # 启动服务\ndocker compose down                       # 停止服务\ndocker compose ps                         # 列出服务\ndocker compose logs [-f] [service]        # 查看日志\ndocker compose exec service cmd           # 在服务中执行命令\ndocker compose build                      # 构建服务镜像\n```\n"
  },
  {
    "path": "appendix/repo/README.md",
    "content": "# 附录二：热门镜像介绍\n\n本章将介绍一些热门镜像的功能，使用方法等。包括 Ubuntu、CentOS、MySQL、MongoDB、Redis、Nginx、Wordpress、Node.js 等。\n"
  },
  {
    "path": "appendix/repo/centos.md",
    "content": "## CentOS\n\n### 基本信息\n\n[CentOS](https://en.wikipedia.org/wiki/CentOS) 是流行的 Linux 发行版，其软件包大多跟 RedHat 系列保持一致。\n\n> ⚠️ **重要提示**：CentOS 8 已于 2021 年 12 月 31 日停止维护 (EOL)，CentOS 7 也已于 2024 年 6 月 30 日 **完全结束支持**。Docker Hub 上的 CentOS 官方镜像 **已停止更新** 且存在未修复的安全漏洞。\n>\n> 2026 年了，对于任何新项目，**强烈建议** 使用以下生产级替代方案：\n> - [Rocky Linux](https://hub.docker.com/_/rockylinux)：CentOS 原创始人发起的社区驱动项目，目前主流为 Rocky Linux 9。\n> - [AlmaLinux](https://hub.docker.com/_/almalinux)：由 CloudLinux 支持的企业级发行版，提供长期支持。\n> - [CentOS Stream](https://hub.docker.com/r/centos/centos)：RHEL 的上游开发分支 (适合开发测试，不建议用于生产环境)。\n\n该仓库位于 [Docker Hub 的 CentOS 官方镜像页](https://hub.docker.com/_/centos)，提供了 CentOS 从 5 ~ 8 各个版本的镜像（仅作为历史归档，不再更新）。\n\n### 使用方法\n\n使用 Rocky Linux 9 替代 (**推荐**)：\n\n```bash\n$ docker run --name rocky -it rockylinux:9 bash\n```\n\n使用旧版 CentOS 7 (**仅用于维护旧项目，不推荐**)：\n\n```bash\n$ docker run --name centos -it centos:7 bash\n```\n\n### Dockerfile\n\n请到 [CentOS 官方镜像文档目录](https://github.com/docker-library/docs/tree/master/centos) 查看。\n"
  },
  {
    "path": "appendix/repo/minio.md",
    "content": "## Minio\n\n**MinIO** 是一个基于 Apache License v2.0 开源协议的对象存储服务。它兼容亚马逊 S3 云存储服务接口，非常适合于存储大容量非结构化的数据，例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等，而一个对象文件可以是任意大小，从几 kb 到最大 5T 不等。\n\nMinIO 是一个非常轻量的服务，可以很简单的和其他应用的结合，类似 NodeJS，Redis 或者 MySQL。\n\n[官方文档](https://docs.min.io/)\n\n### 简单使用\n\n测试、开发环境下不考虑数据存储的情况下可以使用下面的命令快速开启服务。\n\n```bash\n$ docker run -d -p 9000:9000 -p 9090:9090 minio/minio server /data --console-address ':9090'\n```\n\n### 离线部署\n\n许多生产环境是一般是没有公网资源的，这就需要从有公网资源的服务器上把镜像导出，然后导入到需要运行镜像的内网服务器。\n\n#### 导出镜像\n\n在有公网资源的服务器上下载好 `minio/minio` 镜像\n\n```bash\n$ docker save -o minio.tar minio/minio:latest\n```\n\n> 使用 docker save 的时候，也可以使用 image id 来导出，但是那样导出的时候，就会丢失原来的镜像名称，推荐，还是使用镜像名字+tag 来导出镜像\n\n#### 导入镜像\n\n把压缩文件复制到内网服务器上，使用下面的命令导入镜像\n\n```bash\n$ docker load -i minio.tar\n```\n\n#### 运行 minio\n\n- 把 `/mnt/data` 改成要替换的数据目录\n- 替换 `MINIO_ROOT_USER` 的值\n- 替换 `MINIO_ROOT_PASSWORD` 的值\n- 替换 name,minio1 (可选)\n- 如果 9000、9090 端口冲突，替换端口前面的如 `9009:9000`\n\n```bash\n$ sudo docker run -d -p 9000:9000 -p 9090:9090 --name minio1 \\\n  -e \"MINIO_ROOT_USER=改成自己需要的\" \\\n  -e \"MINIO_ROOT_PASSWORD=改成自己需要的\" \\\n  -v /mnt/data:/data \\\n  --restart=always \\\n  minio/minio server /data --console-address ':9090'\n```\n\n#### 访问 web 管理页面\n\n打开 `http://<server-ip>:9090` 访问 Web 控制台。\n"
  },
  {
    "path": "appendix/repo/mongodb.md",
    "content": "## MongoDB\n\n### 基本信息\n\n[MongoDB](https://en.wikipedia.org/wiki/MongoDB) 是开源的 NoSQL 数据库实现。\n\n该仓库位于 `https://hub.docker.com/_/mongo/`。具体可用版本以 Docker Hub 上的 tags 列表为准。\n\n### 使用方法\n\n默认会在 `27017` 端口启动数据库。\n\n```bash\n$ docker run --name mongo -d mongo\n```\n\n使用其他应用连接到容器，首先创建网络\n```bash\n$ docker network create my-mongo-net\n```\n\n然后启动 MongoDB 容器\n```bash\n$ docker run --name some-mongo -d --network my-mongo-net mongo\n```\n\n最后启动应用容器\n```bash\n$ docker run --name some-app -d --network my-mongo-net application-that-uses-mongo\n```\n\n或者通过 `mongo`\n\n```bash\n$ docker run -it --rm \\\n    --network my-mongo-net \\\n    mongo \\\n    sh -c 'exec mongo \"some-mongo:27017/test\"'\n```\n\n### Dockerfile\n\n请到 [Mongo 官方镜像文档目录](https://github.com/docker-library/docs/tree/master/mongo) 查看。\n"
  },
  {
    "path": "appendix/repo/mysql.md",
    "content": "## MySQL\n\n### 基本信息\n\n[MySQL](https://en.wikipedia.org/wiki/MySQL) 是开源的关系数据库实现。\n\n该仓库位于 [Docker Hub 的 MySQL 官方镜像页](https://hub.docker.com/_/mysql/)。具体可用版本以 Docker Hub 上的 tags 列表为准。\n\n### 使用方法\n\n默认会在 `3306` 端口启动数据库。\n\n```bash\n$ docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=mysecretpassword -d mysql\n```\n\n之后就可以使用其它应用来连接到该容器。\n\n首先创建网络\n```bash\n$ docker network create my-mysql-net\n```\n\n然后启动 MySQL 容器\n```bash\n$ docker run --name some-mysql -d --network my-mysql-net -e MYSQL_ROOT_PASSWORD=mysecretpassword mysql\n```\n\n最后启动应用容器\n```bash\n$ docker run --name some-app -d --network my-mysql-net application-that-uses-mysql\n```\n\n或者通过 `mysql` 命令行连接。\n\n```bash\n$ docker run -it --rm \\\n    --network my-mysql-net \\\n    mysql \\\n    sh -c 'exec mysql -hsome-mysql -P3306 -uroot -pmysecretpassword'\n```\n\n\n### Dockerfile\n\n请到 [MySQL 官方镜像文档目录](https://github.com/docker-library/docs/tree/master/mysql) 查看。\n"
  },
  {
    "path": "appendix/repo/nginx.md",
    "content": "## Nginx\n\n### 基本信息\n\n[Nginx](https://en.wikipedia.org/wiki/Nginx) 是开源的高效的 Web 服务器实现，支持 HTTP、HTTPS、SMTP、POP3、IMAP 等协议。\n\n该仓库位于 [Docker Hub 的 Nginx 官方镜像页](https://hub.docker.com/_/nginx/)。具体可用版本以 Docker Hub 上的 tags 列表为准。\n\n### 使用方法\n\n下面的命令将作为一个静态页面服务器启动。\n\n```bash\n$ docker run --name some-nginx -v /some/content:/usr/share/nginx/html:ro -d nginx\n```\n\n用户也可以不使用这种映射方式，通过利用 Dockerfile 来直接将静态页面内容放到镜像中，内容为\n\n```docker\nFROM nginx\nCOPY static-html-directory /usr/share/nginx/html\n```\n\n之后生成新的镜像，并启动一个容器。\n\n```bash\n$ docker build -t some-content-nginx .\n$ docker run --name some-nginx -d some-content-nginx\n```\n\n开放端口，并映射到本地的 `8080` 端口。\n\n```bash\n$ docker run --name some-nginx -d -p 8080:80 some-content-nginx\n```\n\nNginx 的默认配置文件路径为 `/etc/nginx/nginx.conf`，可以通过映射它来使用本地的配置文件，例如\n\n```bash\n$ docker run -d \\\n    --name some-nginx \\\n    -p 8080:80 \\\n    -v /path/nginx.conf:/etc/nginx/nginx.conf:ro \\\n    nginx\n```\n\n### Dockerfile\n\n请到 [Nginx 官方镜像文档目录](https://github.com/docker-library/docs/tree/master/nginx) 查看。\n"
  },
  {
    "path": "appendix/repo/nodejs.md",
    "content": "## Node.js\n\n### 基本信息\n\n[Node.js](https://en.wikipedia.org/wiki/Node.js) 是基于 JavaScript 的可扩展服务端和网络软件开发平台。\n\n该仓库位于 `https://hub.docker.com/_/node/`。具体可用版本以 Docker Hub 上的 tags 列表为准。\n\n### 使用方法\n\n在项目中创建一个 Dockerfile。\n\n```docker\nFROM node:20\n## replace this with your application's default port\n\nEXPOSE 8888\n```\n\n然后创建镜像，并启动容器。\n\n```bash\n$ docker build -t my-nodejs-app\n$ docker run -it --rm --name my-running-app my-nodejs-app\n```\n\n也可以直接运行一个简单容器。\n\n```bash\n$ docker run -it --rm \\\n    --name my-running-script \\\n    # -v \"$ \":/usr/src/myapp \\\n\n    --mount type=bind,src=\"$(pwd)\",target=/usr/src/myapp \\\n    -w /usr/src/myapp \\\n    node:20-alpine \\\n    node your-daemon-or-script.js\n```\n\n### Dockerfile\n\n请到 [Node 官方镜像文档目录](https://github.com/docker-library/docs/tree/master/node) 查看。\n"
  },
  {
    "path": "appendix/repo/php.md",
    "content": "## PHP\n\n### 基本信息\n\n[PHP](https://en.wikipedia.org/wiki/Php) (Hypertext Preprocessor 超文本预处理器的字母缩写) 是一种被广泛应用的开放源代码的多用途脚本语言，它可嵌入到 HTML 中，尤其适合 web 开发。\n\n该仓库位于 [Docker Hub 的 PHP 官方镜像页](https://hub.docker.com/_/php/)。具体可用版本以 Docker Hub 上的 tags 列表为准。\n\n### 使用方法\n\n下面的命令将运行一个已有的 PHP 脚本。\n\n```bash\n$ docker run -it --rm -v \"$PWD\":/app -w /app php:alpine php your-script.php\n```\n\n### Dockerfile\n\n请到 [PHP 官方镜像文档目录](https://github.com/docker-library/docs/tree/master/php) 查看。\n"
  },
  {
    "path": "appendix/repo/redis.md",
    "content": "## Redis\n\n### 基本信息\n\n[Redis](https://en.wikipedia.org/wiki/Redis) 是开源的内存 Key-Value 数据库实现。\n\n该仓库位于 [Docker Hub 的 Redis 官方镜像页](https://hub.docker.com/_/redis/)。具体可用版本以 Docker Hub 上的 tags 列表为准。\n\n### 使用方法\n\n默认会在 `6379` 端口启动数据库。\n\n```bash\n$ docker run --name some-redis -d -p 6379:6379 redis\n```\n\n另外还可以启用[持久存储](https://redis.io/topics/persistence)。\n\n```bash\n$ docker run --name some-redis -d -p 6379:6379 redis redis-server --appendonly yes\n```\n\n默认数据存储位置在 `VOLUME/data`。可以使用 `--volumes-from some-volume-container` 或 `-v /docker/host/dir:/data` 将数据存放到本地。\n\n使用其他应用连接到容器，首先创建网络\n```bash\n$ docker network create my-redis-net\n```\n\n然后启动 redis 容器\n```bash\n$ docker run --name some-redis -d --network my-redis-net redis\n```\n\n最后启动应用容器\n```bash\n$ docker run --name some-app -d --network my-redis-net application-that-uses-redis\n```\n\n或者通过 `redis-cli`\n\n```bash\n$ docker run -it --rm \\\n    --network my-redis-net \\\n    redis \\\n    sh -c 'exec redis-cli -h some-redis'\n```\n\n### Dockerfile\n\n请到 [Redis 官方镜像文档目录](https://github.com/docker-library/docs/tree/master/redis) 查看。\n"
  },
  {
    "path": "appendix/repo/ubuntu.md",
    "content": "## Ubuntu\n\n### 基本信息\n\n[Ubuntu](https://en.wikipedia.org/wiki/Ubuntu) 是流行的 Linux 发行版，其自带软件版本往往较新一些。\n\n该仓库位于 [Docker Hub 的 Ubuntu 官方镜像页](https://hub.docker.com/_/ubuntu/)。具体可用版本以 Docker Hub 上的 tags 列表为准。\n\n### 使用方法\n\n默认会启动一个最小化的 Ubuntu 环境。\n\n```bash\n$ docker run --name some-ubuntu -it ubuntu:20.04\nroot@523c70904d54:/#\n```\n\n### Dockerfile\n\n请到 [Ubuntu 官方镜像文档目录](https://github.com/docker-library/docs/tree/master/ubuntu) 查看。\n"
  },
  {
    "path": "appendix/repo/wordpress.md",
    "content": "## WordPress\n\n### 基本信息\n\n[WordPress](https://en.wikipedia.org/wiki/WordPress) 是开源的 Blog 和内容管理系统框架，它基于 PHP 和 MySQL。\n\n该仓库位于 `https://hub.docker.com/_/wordpress/`。具体可用版本以 Docker Hub 上的 tags 列表为准。\n\n### 使用方法\n\n启动容器需要 MySQL 的支持，默认端口为 `80`。\n\n首先创建网络\n```bash\n$ docker network create my-wordpress-net\n```\n\n启动 MySQL 容器\n```bash\n$ docker run --name some-mysql -d --network my-wordpress-net -e MYSQL_ROOT_PASSWORD=mysecretpassword mysql\n```\n\n启动 WordPress 容器\n```bash\n$ docker run --name some-wordpress -d --network my-wordpress-net -e WORDPRESS_DB_HOST=some-mysql -e WORDPRESS_DB_PASSWORD=mysecretpassword wordpress\n```\n\n启动 WordPress 容器时可以指定的一些环境变量包括：\n\n* `WORDPRESS_DB_HOST`：MySQL 服务的主机名\n* `WORDPRESS_DB_USER`：MySQL 数据库的用户名\n* `WORDPRESS_DB_PASSWORD`：MySQL 数据库的密码\n* `WORDPRESS_DB_NAME`：WordPress 要使用的数据库名\n\n\n### Dockerfile\n\n请到 [WordPress 官方镜像文档目录](https://github.com/docker-library/docs/tree/master/wordpress) 查看。\n"
  },
  {
    "path": "appendix/resources.md",
    "content": "## 附录六：资源链接\n\n### 官方网站\n\n* [Docker 官方主页](https://www.docker.com)\n* [Docker 官方博客](https://www.docker.com/blog/)\n* [Docker 官方文档](https://docs.docker.com/)\n* [Docker Hub](https://hub.docker.com)\n* [Docker 源代码仓库](https://github.com/moby/moby)\n* [Docker 路线图](https://github.com/docker/roadmap/projects)\n* [Docker 发布版本历史](https://docs.docker.com/release-notes/)\n* [Docker 常见问题](https://docs.docker.com/engine/faq/)\n* [Docker 远端应用 API](https://docs.docker.com/develop/sdk/)\n\n### 实践参考\n\n* [Dockerfile 参考](https://docs.docker.com/engine/reference/builder/)\n* [Dockerfile 最佳实践](https://docs.docker.com/build/building/best-practices/)\n\n### 技术交流\n\n* [Docker 邮件列表](https://groups.google.com/forum/#!forum/docker-user)\n* [Docker 社区 Slack](https://dockercommunity.slack.com/)\n* [Docker Community Discord](https://discord.gg/docker)\n* [Docker 的 X (Twitter) 主页](https://twitter.com/docker)\n\n### 其它\n\n* [Docker 的 StackOverflow 问答主页](https://stackoverflow.com/search?q=docker)\n"
  },
  {
    "path": "book.json",
    "content": "{\n  \"title\": \"Docker -- 从入门到实践\",\n  \"author\": \"yeasy\",\n  \"language\": \"zh-hans\",\n  \"links\": {\n    \"sidebar\": {\n      \"GitHub\": \"https://github.com/yeasy/docker_practice\"\n    }\n  },\n  \"plugins\": [\n    \"-livereload\",\n    \"mermaid-gb3\",\n    \"github\",\n    \"page-treeview\",\n    \"editlink\"\n  ],\n  \"pluginsConfig\": {\n    \"mermaid-gb3\": {\n      \"theme\": \"default\"\n    },\n    \"github\": {\n      \"url\": \"https://github.com/yeasy/docker_practice\"\n    },\n    \"editlink\": {\n      \"base\": \"https://github.com/yeasy/docker_practice/blob/master/\",\n      \"label\": \"编辑本页\"\n    },\n    \"page-treeview\": {\n      \"copyright\": \"Copyright &#169; yeasy\",\n      \"minHeaderCount\": \"2\",\n      \"minHeaderDeep\": \"2\"\n    }\n  },\n  \"pdf\": {\n    \"pageNumbers\": true,\n    \"fontSize\": 12,\n    \"fontFamily\": \"Arial\",\n    \"paperSize\": \"a4\",\n    \"chapterMark\": \"pagebreak\",\n    \"pageBreaksBefore\": \"/\",\n    \"margin\": {\n      \"right\": 62,\n      \"left\": 62,\n      \"top\": 56,\n      \"bottom\": 56\n    }\n  }\n}\n"
  },
  {
    "path": "docker-compose.yml",
    "content": "version: \"3\"\n\nservices:\n\n  gitbook-build:\n    &gitbook-build\n    image: yeasy/docker_practice:latest\n    volumes:\n      - ./:/srv/gitbook-src\n    command: build\n\n  gitbook-server:\n    << : *gitbook-build\n    ports:\n      - 4000:4000\n    command: server\n\n  # docker run -it --rm -p 4000:80 dockerpracticesig/docker_practice\n  gitbook-offline:\n    &gitbook-offline\n    # this image build by GitHub Action\n    image: dockerpracticesig/docker_practice:gitbook\n    ports:\n      - 4000:80\n\n  vuepress-offline:\n    << : *gitbook-offline\n    image: dockerpracticesig/docker_practice:vuepress\n\n  # developer test docker image\n\n  development:\n    build: ./.travis\n    image: yeasy/docker_practice:latest\n    ports:\n      - 4000:4000\n    volumes:\n      - ./:/srv/gitbook-src\n    command: server\n    # command: build\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"docker_practice\",\n  \"version\": \"1.5.4\",\n  \"description\": \"docker_practice\",\n  \"main\": \"index.js\",\n  \"devDependencies\": {\n    \"chalk\": \"^5.6.2\",\n    \"commander\": \"^14.0.3\",\n    \"esm\": \"^3.0.0\",\n    \"gitbook-plugin-editlink\": \"^1.0.2\",\n    \"gitbook-plugin-github\": \"^3.0.0\",\n    \"gitbook-plugin-mermaid-gb3\": \"^2.1.0\",\n    \"gitbook-plugin-page-treeview\": \"^3.0.6\",\n    \"honkit\": \"^6.1.6\",\n    \"vuepress\": \"1.9.10\",\n    \"vuepress-plugin-container\": \"^2.1.5\",\n    \"vuepress-theme-hope\": \"^1.0.0\"\n  },\n  \"scripts\": {\n    \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\",\n    \"honkit:help\": \"npx honkit help\",\n    \"honkit:build\": \"npx honkit build\",\n    \"honkit:serve\": \"npx honkit serve\",\n    \"vuepress:build\": \"npx vuepress build\",\n    \"vuepress\": \"npx vuepress\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/yeasy/docker_practice.git\"\n  },\n  \"author\": \"\",\n  \"license\": \"ISC\",\n  \"bugs\": {\n    \"url\": \"https://github.com/yeasy/docker_practice/issues\"\n  },\n  \"homepage\": \"https://github.com/yeasy/docker_practice#readme\"\n}\n"
  }
]