diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2a9b408..24b1f2e 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -16,3 +16,17 @@ jobs: uses: docker://yeasy/docker_practice with: args: build + - name: vuepress + run: | + npx vuepress build + - name: Upload Vuepress dist + uses: docker://pcit/pages + env: + PCIT_EMAIL: khs1994@khs1994.com + PCIT_GIT_TOKEN: ${{ secrets.PCIT_GIT_TOKEN }} + PCIT_GIT_URL: github.com/docker-practice/vuepress + PCIT_KEEP_HISTORY: "1" + PCIT_LOCAL_DIR: .vuepress/dist + PCIT_MESSAGE: Sync from yeasy/docker-practice@${{github.sha}} by PCIT + PCIT_TARGET_BRANCH: master + PCIT_USERNAME: khs1994 diff --git a/.gitignore b/.gitignore index e9372eb..5bd7cae 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ _book/ *.edx .DS_Store node_modules/ +.vuepress/dist diff --git a/.vuepress/config.js b/.vuepress/config.js new file mode 100644 index 0000000..0943785 --- /dev/null +++ b/.vuepress/config.js @@ -0,0 +1,352 @@ +module.exports = { + title: 'Docker 从入门到实践', + base: '/', + themeConfig: { + docsRepo: 'yeasy/docker_practice', + docsDir: '/', + editLinks: true, + nav: [{ + text: '安装 Docker', + link: '/install/', + }, + { + text: 'Docker 入门', + link: '/' + }, + { + text: 'Docker 实战', + link: '/cases/os/' + }, + { + text: 'CI/CD', + link: '/cases/ci/' + }, + { + text: 'Docker 仓库', + link: '/repository/' + }, + { + text: '底层实现', + link: '/underly/', + }, + { + text: 'Compose', + link: '/compose/', + }, + { + text: 'Kubernetes', + link: '/kubernetes/', + }, + { + text: 'GitHub', + link: 'https://github.com/yeasy/docker_practice' + }, + // { + // text: '捐赠', + // link: '' + // }, + { + text: '腾讯云容器服务', + link: 'https://cloud.tencent.com/redirect.php?redirect=10058&cps_key=3a5255852d5db99dcd5da4c72f05df61' + }, + // { + // text: '语言', + // items: [{ + // text: 'English', + // link: '' + // }] + // } + ], + sidebar: { + '/kubernetes/': [ + 'intro', + 'quickstart', + 'concepts', + 'kubectl', + 'design', + ], + '/compose/': [ + 'introduction', + 'install', + 'usage', + 'commands', + 'compose_file', + 'django', + 'rails', + 'wordpress', + ], + '/install/': [ + '/install/', + 'ubuntu', + 'debian', + 'fedora', + 'centos', + 'raspberry-pi', + 'mac', + 'windows', + 'mirror', + ], + '/underly/': [ + '/underly/', + 'arch', + 'namespace', + 'cgroups', + 'ufs', + 'container_format', + 'network', + ], + '/repository/': [ + '/repository/', + 'dockerhub', + 'registry', + 'registry_auth', + 'nexus3_registry', + ], + '/cases/os/': [ + { + title: "操作系统", + collapsable:false, + children: [ + '/cases/os/', + 'busybox', + 'alpine', + 'debian', + 'centos', + 'summary', + ], + }, + ], + '/cases/ci/': [ + '/cases/ci/', + '/cases/ci/actions/', + { + title: "Drone", + collapsable: false, + children: [ + 'drone/', + 'drone/install' + ] + }, + 'travis/' + ], + '/': [ + '/', + '/CHANGELOG', + '/CONTRIBUTING', + { + title: "Docker 简介", + collapsable: false, + children: [ + '/introduction/', + '/introduction/what', + '/introduction/why', + ] + },{ + title: "基本概念", + collapsable: false, + children: [ + '/basic_concept/', + '/basic_concept/image', + '/basic_concept/container', + '/basic_concept/repository' + ] + }, + { + title: "使用镜像", + collapsable: false, + children: [ + '/image/', + '/image/pull', + '/image/list', + '/image/rm', + '/image/commit', + '/image/build', + '/image/other.md', + '/image/internal.md', + ] + }, + { + title: 'Dockerfile', + collapsable: false, + children: [ + "/image/dockerfile/", + '/image/dockerfile/copy', + '/image/dockerfile/add', + '/image/dockerfile/cmd', + '/image/dockerfile/entrypoint', + '/image/dockerfile/env', + '/image/dockerfile/arg', + '/image/dockerfile/volume', + '/image/dockerfile/expose', + '/image/dockerfile/workdir', + '/image/dockerfile/user', + '/image/dockerfile/healthcheck', + '/image/dockerfile/onbuild', + '/image/dockerfile/references', + '/image/multistage-builds/', + '/image/multistage-builds/laravel', + '/image/manifest', + '/image/buildx_multi-arch-images', + '/image/buildkit', + '/image/buildx', + ] + },{ + title: "操作容器", + collapsable: false, + children: [ + 'container/', + 'container/run', + 'container/daemon', + 'container/stop', + 'container/attach_exec', + 'container/import_export', + 'container/rm', + ], + }, + { + title: "数据管理", + collapsable:false, + children: [ + 'data_management/', + 'data_management/volume', + 'data_management/bind-mounts', + ], + },{ + title: "使用网络", + collapsable:false, + children: [ + 'network/', + 'network/port_mapping', + 'network/linking', + 'network/dns', + ], + }, + { + title: "高级网络配置", + collapsable:false, + children: [ + 'advanced_network/', + 'advanced_network/quick_guide', + 'advanced_network/access_control', + 'advanced_network/port_mapping', + 'advanced_network/bridge', + 'advanced_network/example', + 'advanced_network/config_file', + 'advanced_network/ptp', + ], + }, + { + title: "Swarm mode", + collapsable:false, + children: [ + 'swarm_mode/', + 'swarm_mode/overview', + 'swarm_mode/create', + 'swarm_mode/deploy', + 'swarm_mode/stack', + 'swarm_mode/secret', + 'swarm_mode/config', + 'swarm_mode/rolling_update', + ], + }, + { + title: "安全", + collapsable: false, + children: [ + 'security/', + 'security/kernel_ns', + 'security/control_group', + 'security/daemon_sec', + 'security/kernel_capability', + 'security/other_feature', + 'security/summary', + ], + }, + { + title: "Etcd", + collapsable:false, + children: [ + 'etcd/', + 'etcd/intro', + 'etcd/install', + 'etcd/cluster', + 'etcd/etcdctl', + ], + }, + { + title: "Fedora CoreOS", + collapsable: false, + children: [ + 'coreos/', + 'coreos/intro', + 'coreos/intro_tools', + ], + }, + { + title: "容器与云计算", + collapsable:false, + children: [ + 'cloud/', + 'cloud/intro', + 'cloud/aws', + 'cloud/tencentCloud', + 'cloud/alicloud', + 'cloud/summary', + ], + }, + { + title: "Docker 开源项目", + collapsable:false, + children:[ + 'opensource/', + 'opensource/linuxkit', + ], + }, + { + title: "附录", + collapsable:false, + children: [ + 'appendix/', + 'appendix/faq/', + 'appendix/repo/', + 'appendix/repo/ubuntu', + 'appendix/repo/centos', + 'appendix/repo/nginx', + 'appendix/repo/php', + 'appendix/repo/nodejs', + 'appendix/repo/mysql', + 'appendix/repo/wordpress', + 'appendix/repo/mongodb', + 'appendix/repo/redis', + 'appendix/command/', + 'appendix/best_practices', + 'appendix/debug', + 'appendix/resources' + ], + }, + { + title: "Docker Machine", + collapsable: false, + children: [ + 'machine/', + 'machine/install', + 'machine/usage', + ], + }, + { + title: 'Mesos', + collapsable: false, + children: [ + '/mesos/intro', + '/mesos/installation', + '/mesos/architecture', + '/mesos/configuration', + '/mesos/monitor', + '/mesos/framework', + '/mesos/summary', + ] + }, + ], + }, + } +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 055b45e..dd7d2e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,9 @@ -## 主要修订记录 +# 修订记录 * 1.1.0 2019-12-31 * 全面支持 v19.x 新版本 * 增加 `BuildKit` + * 增加 `docker buildx` 命令使用说明 * 增加 `docker manifest` 命令使用说明 * 移除 `Ubuntu 14.04` `Debian 8` `Debian 7` diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0a24606..65162c1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,4 +1,4 @@ -## 如何贡献项目 +# 如何贡献 领取或创建新的 [Issue](https://github.com/yeasy/docker_practice/issues),如 [issue 235](https://github.com/yeasy/docker_practice/issues/235),添加自己为 `Assignee`。 diff --git a/SUMMARY.md b/SUMMARY.md index b2a8b86..3496d9b 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -42,7 +42,9 @@ * [Dockerfile 多阶段构建](image/multistage-builds/README.md) * [实战多阶段构建 Laravel 镜像](image/multistage-builds/laravel.md) * [构建多种系统架构支持的 Docker 镜像](image/manifest.md) - * [使用 BuildKit 构建镜像](image/buildkit.md) + * [使用 buildx 构建多种系统架构支持的 Docker 镜像](image/buildx_multi-arch-images.md) + * [Docker v18.09 版本使用 BuildKit 构建镜像](image/buildkit.md) + * [Docker v19.03 版本使用 BuildKit 构建镜像](image/buildx.md) * [其它制作镜像的方式](image/other.md) * [实现原理](image/internal.md) * [操作容器](container/README.md) diff --git a/appendix/best_practices.md b/appendix/best_practices.md index 1aa165f..e93f8e2 100644 --- a/appendix/best_practices.md +++ b/appendix/best_practices.md @@ -36,7 +36,7 @@ 下面是来自 `buildpack-deps` 镜像的例子: -```dockerfile +```docker RUN apt-get update && apt-get install -y \ bzr \ cvs \ @@ -72,7 +72,7 @@ RUN apt-get update && apt-get install -y \ >注意:如果你的字符串中包含空格,必须将字符串放入引号中或者对空格使用转义。如果字符串内容本身就包含引号,必须对引号使用转义。 -```dockerfile +```docker # Set one or more individual labels LABEL com.example.version="0.0.1-beta" @@ -85,7 +85,7 @@ LABEL com.example.version.is-production="" 一个镜像可以包含多个标签,但建议将多个标签放入到一个 `LABEL` 指令中。 -```dockerfile +```docker # Set multiple labels at once, using line-continuation characters to break long lines LABEL vendor=ACME\ Incorporated \ com.example.is-beta= \ @@ -108,7 +108,7 @@ LABEL vendor=ACME\ Incorporated \ 永远将 `RUN apt-get update` 和 `apt-get install` 组合成一条 `RUN` 声明,例如: -```dockerfile +```docker RUN apt-get update && apt-get install -y \ package-bar \ package-baz \ @@ -117,7 +117,7 @@ RUN apt-get update && apt-get install -y \ 将 `apt-get update` 放在一条单独的 `RUN` 声明中会导致缓存问题以及后续的 `apt-get install` 失败。比如,假设你有一个 `Dockerfile` 文件: -```dockerfile +```docker FROM ubuntu:18.04 RUN apt-get update @@ -127,7 +127,7 @@ RUN apt-get install -y curl 构建镜像后,所有的层都在 Docker 的缓存中。假设你后来又修改了其中的 `apt-get install` 添加了一个包: -```dockerfile +```docker FROM ubuntu:18.04 RUN apt-get update @@ -139,7 +139,7 @@ Docker 发现修改后的 `RUN apt-get update` 指令和之前的完全一样。 使用 `RUN apt-get update && apt-get install -y` 可以确保你的 Dockerfiles 每次安装的都是包的最新的版本,而且这个过程不需要进一步的编码或额外干预。这项技术叫作 `cache busting`。你也可以显示指定一个包的版本号来达到 `cache-busting`,这就是所谓的固定版本,例如: -```dockerfile +```docker RUN apt-get update && apt-get install -y \ package-bar \ package-baz \ @@ -150,7 +150,7 @@ RUN apt-get update && apt-get install -y \ 下面是一个 `RUN` 指令的示例模板,展示了所有关于 `apt-get` 的建议。 -```dockerfile +```docker RUN apt-get update && apt-get install -y \ aufs-tools \ automake \ @@ -193,7 +193,7 @@ RUN apt-get update && apt-get install -y \ 最后,`ENV` 也能用于设置常见的版本号,比如下面的示例: -```dockerfile +```docker ENV PG_MAJOR 9.3 ENV PG_VERSION 9.3.4 @@ -211,7 +211,7 @@ ENV PATH /usr/local/postgres-$PG_MAJOR/bin:$PATH 如果你的 `Dockerfile` 有多个步骤需要使用上下文中不同的文件。单独 `COPY` 每个文件,而不是一次性的 `COPY` 所有文件,这将保证每个步骤的构建缓存只在特定的文件变化时失效。例如: -```dockerfile +```docker COPY requirements.txt /tmp/ RUN pip install --requirement /tmp/requirements.txt @@ -223,7 +223,7 @@ COPY . /tmp/ 为了让镜像尽量小,最好不要使用 `ADD` 指令从远程 URL 获取包,而是使用 `curl` 和 `wget`。这样你可以在文件提取完之后删掉不再需要的文件来避免在镜像中额外添加一层。比如尽量避免下面的用法: -```dockerfile +```docker ADD http://example.com/big.tar.xz /usr/src/things/ RUN tar -xJf /usr/src/things/big.tar.xz -C /usr/src/things @@ -233,7 +233,7 @@ RUN make -C /usr/src/things all 而是应该使用下面这种方法: -```dockerfile +```docker RUN mkdir -p /usr/src/things \ && curl -SL http://example.com/big.tar.xz \ | tar -xJC /usr/src/things \ @@ -250,7 +250,7 @@ RUN mkdir -p /usr/src/things \ 例如,下面的示例镜像提供了命令行工具 `s3cmd`: -```dockerfile +```docker ENTRYPOINT ["s3cmd"] CMD ["--help"] @@ -295,7 +295,7 @@ exec "$@" 该辅助脚本被拷贝到容器,并在容器启动时通过 `ENTRYPOINT` 执行: -```dockerfile +```docker COPY ./docker-entrypoint.sh / ENTRYPOINT ["/docker-entrypoint.sh"] diff --git a/appendix/faq/README.md b/appendix/faq/README.md index cd91454..5870c85 100644 --- a/appendix/faq/README.md +++ b/appendix/faq/README.md @@ -52,11 +52,18 @@ ### 如何获取某个容器的 PID 信息? -答:可以使用 `docker inspect --format '{{ .State.Pid }}' ` 命令。 +答:可以使用 + +```bash +docker inspect --format '{{ .State.Pid }}' +``` ### 如何获取某个容器的 IP 地址? -答:可以使用 `docker inspect --format '{{ .NetworkSettings.IPAddress }}' ` 命令 +答:可以使用 +```bash +docker inspect --format '{{ .NetworkSettings.IPAddress }}' +``` ### 如何给容器指定一个固定 IP 地址,而不是每次重启容器 IP 地址都会变? diff --git a/basic_concept/container.md b/basic_concept/container.md index aafb409..cd1dd44 100644 --- a/basic_concept/container.md +++ b/basic_concept/container.md @@ -1,4 +1,4 @@ -## Docker 容器 +# Docker 容器 镜像(`Image`)和容器(`Container`)的关系,就像是面向对象程序设计中的 `类` 和 `实例` 一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。 diff --git a/basic_concept/image.md b/basic_concept/image.md index 6e4443e..5718a15 100644 --- a/basic_concept/image.md +++ b/basic_concept/image.md @@ -1,10 +1,10 @@ -## Docker 镜像 +# Docker 镜像 我们都知道,操作系统分为内核和用户空间。对于 Linux 而言,内核启动后,会挂载 `root` 文件系统为其提供用户空间支持。而 Docker 镜像(Image),就相当于是一个 `root` 文件系统。比如官方镜像 `ubuntu:18.04` 就包含了完整的一套 Ubuntu 18.04 最小系统的 `root` 文件系统。 Docker 镜像是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。镜像不包含任何动态数据,其内容在构建之后也不会被改变。 -### 分层存储 +## 分层存储 因为镜像包含操作系统完整的 `root` 文件系统,其体积往往是庞大的,因此在 Docker 设计时,就充分利用 [Union FS](https://en.wikipedia.org/wiki/Union_mount) 的技术,将其设计为分层存储的架构。所以严格来说,镜像并非是像一个 ISO 那样的打包文件,镜像只是一个虚拟的概念,其实际体现并非由一个文件组成,而是由一组文件系统组成,或者说,由多层文件系统联合组成。 diff --git a/basic_concept/repository.md b/basic_concept/repository.md index c1dae82..f725587 100644 --- a/basic_concept/repository.md +++ b/basic_concept/repository.md @@ -1,4 +1,4 @@ -## Docker Registry +# Docker Registry 镜像构建完成后,可以很容易的在当前宿主机上运行,但是,如果需要在其它服务器上使用这个镜像,我们就需要一个集中的存储、分发镜像的服务,[Docker Registry](../repository/registry.md) 就是这样的服务。 @@ -10,7 +10,7 @@ 仓库名经常以 *两段式路径* 形式出现,比如 `jwilder/nginx-proxy`,前者往往意味着 Docker Registry 多用户环境下的用户名,后者则往往是对应的软件名。但这并非绝对,取决于所使用的具体 Docker Registry 的软件或服务。 -### Docker Registry 公开服务 +## Docker Registry 公开服务 Docker Registry 公开服务是开放给用户使用、允许用户管理镜像的 Registry 服务。一般这类公开服务允许用户免费上传、下载公开的镜像,并可能提供收费服务供用户管理私有镜像。 @@ -20,7 +20,7 @@ Docker Registry 公开服务是开放给用户使用、允许用户管理镜像 国内也有一些云服务商提供类似于 Docker Hub 的公开服务。比如 [时速云镜像仓库](https://hub.tenxcloud.com/)、[网易云镜像服务](https://c.163.com/hub#/m/library/)、[DaoCloud 镜像市场](https://hub.daocloud.io/)、[阿里云镜像库](https://cr.console.aliyun.com) 等。 -### 私有 Docker Registry +## 私有 Docker Registry 除了使用公开服务外,用户还可以在本地搭建私有 Docker Registry。Docker 官方提供了 [Docker Registry](https://hub.docker.com/_/registry/) 镜像,可以直接使用做为私有 Registry 服务。在 [私有仓库](../repository/registry.md) 一节中,会有进一步的搭建私有 Registry 服务的讲解。 diff --git a/cases/ci/travis/README.md b/cases/ci/travis/README.md index b845309..d3337da 100644 --- a/cases/ci/travis/README.md +++ b/cases/ci/travis/README.md @@ -10,7 +10,7 @@ 在项目根目录新建一个 `Dockerfile` 文件。 -```dockerfile +```docker FROM alpine RUN echo "Hello World" diff --git a/compose/django.md b/compose/django.md index ce8fd7c..c0f9f45 100644 --- a/compose/django.md +++ b/compose/django.md @@ -8,7 +8,7 @@ 第一步,因为应用将要运行在一个满足所有环境依赖的 Docker 容器里面,那么我们可以通过编辑 `Dockerfile` 文件来指定 Docker 容器要安装内容。内容如下: -```dockerfile +```docker FROM python:3 ENV PYTHONUNBUFFERED 1 RUN mkdir /code diff --git a/compose/rails.md b/compose/rails.md index 96b2bb8..66db4a5 100644 --- a/compose/rails.md +++ b/compose/rails.md @@ -8,7 +8,7 @@ 首先,因为应用将要运行在一个满足所有环境依赖的 Docker 容器里面,那么我们可以通过编辑 `Dockerfile` 文件来指定 Docker 容器要安装内容。内容如下: -```dockerfile +```docker FROM ruby RUN apt-get update -qq && apt-get install -y build-essential libpq-dev RUN mkdir /myapp @@ -117,5 +117,3 @@ $ docker-compose run web rake db:create ``` 这个 web 应用已经开始在你的 docker 守护进程里面监听着 3000 端口了。 - -![](../_images/docker-compose-rails-screenshot.png) diff --git a/compose/usage.md b/compose/usage.md index 7f3d8b5..39cef0c 100644 --- a/compose/usage.md +++ b/compose/usage.md @@ -40,7 +40,7 @@ if __name__ == "__main__": 编写 `Dockerfile` 文件,内容为 -```dockerfile +```docker FROM python:3.6-alpine ADD . /code WORKDIR /code diff --git a/image/build.md b/image/build.md index ed2ca60..135207d 100644 --- a/image/build.md +++ b/image/build.md @@ -16,7 +16,7 @@ $ touch Dockerfile 其内容为: -```dockerfile +```docker FROM nginx RUN echo '

Hello, Docker!

' > /usr/share/nginx/html/index.html ``` @@ -33,7 +33,7 @@ RUN echo '

Hello, Docker!

' > /usr/share/nginx/html/index.html 除了选择现有镜像为基础镜像外,Docker 还存在一个特殊的镜像,名为 `scratch`。这个镜像是虚拟的概念,并不实际存在,它表示一个空白的镜像。 -```dockerfile +```docker FROM scratch ... ``` @@ -48,7 +48,7 @@ FROM scratch * *shell* 格式:`RUN <命令>`,就像直接在命令行中输入的命令一样。刚才写的 Dockerfile 中的 `RUN` 指令就是这种格式。 -```Dockerfile +```docker RUN echo '

Hello, Docker!

' > /usr/share/nginx/html/index.html ``` @@ -56,7 +56,7 @@ RUN echo '

Hello, Docker!

' > /usr/share/nginx/html/index.html 既然 `RUN` 就像 Shell 脚本一样可以执行命令,那么我们是否就可以像 Shell 脚本一样把每个命令对应一个 RUN 呢?比如这样: -```dockerfile +```docker FROM debian:stretch RUN apt-get update @@ -77,7 +77,7 @@ RUN make -C /usr/src/redis install 上面的 `Dockerfile` 正确的写法应该是这样: -```dockerfile +```docker FROM debian:stretch RUN buildDeps='gcc libc6-dev make wget' \ @@ -142,7 +142,7 @@ docker build [选项] <上下文路径/URL/-> 如果在 `Dockerfile` 中这么写: -```Dockerfile +```docker COPY ./package.json /app/ ``` diff --git a/image/buildkit.md b/image/buildkit.md index cf82aa6..7dc176f 100644 --- a/image/buildkit.md +++ b/image/buildkit.md @@ -6,7 +6,7 @@ **注意:docker-compose build 命令暂时不支持 BuildKit** -下面介绍如何在 Docker CE 18.09+ 版本中使用 `BuildKit` 提供的 `Dockerfile` 新指令来更快、更安全的构建 Docker 镜像。 +下面介绍如何在 Docker CE 18.09 版本中使用 `BuildKit` 提供的 `Dockerfile` 新指令来更快、更安全的构建 Docker 镜像。 ### 启用 `BuildKit` diff --git a/image/buildx.md b/image/buildx.md new file mode 100644 index 0000000..a6ef6c7 --- /dev/null +++ b/image/buildx.md @@ -0,0 +1,148 @@ +## 使用 `Buildx` 构建镜像 + +**BuildKit** 是下一代的镜像构建组件,在 https://github.com/moby/buildkit 开源。 + +**注意:如果您的镜像构建使用的是云服务商提供的镜像构建服务(Docker Hub 自动构建、腾讯云容器服务、阿里云容器服务等),由于上述服务提供商的 Docker 版本低于 18.09,BuildKit 无法使用,将造成镜像构建失败。建议使用 BuildKit 构建镜像时使用一个新的 Dockerfile 文件(例如 Dockerfile.buildkit)** + +下面介绍如何在 Docker CE 19.03+ 版本中使用 `BuildKit` 提供的 `Dockerfile` 新指令来更快、更安全的构建 Docker 镜像。 + +### `Dockerfile` 新增指令详解 + +启用 `BuildKit` 之后,我们可以使用下面几个新的指令来加快镜像构建。为了使用 `BuildKit` 我们 **必须** 使用新的 `$ docker buildx build` 命令来构建 Docker 镜像。 + +#### `RUN --mount=type=cache` + +目前,几乎所有的程序都会使用依赖管理工具,例如 `Go` 中的 `go mod`、`Node.js` 中的 `npm` 等等,当我们构建一个镜像时,往往会重复的从互联网中获取依赖包,难以缓存,大大降低了镜像的构建效率。 + +例如一个前端工程需要用到 `npm`: + +```docker +FROM node:alpine as builder + +WORKDIR /app + +COPY package.json /app/ + +RUN npm i --registry=https://registry.npm.taobao.org \ + && rm -rf ~/.npm + +COPY src /app/src + +RUN npm run build + +FROM nginx:alpine + +COPY --from=builder /app/dist /app/dist +``` + +使用多阶段构建,构建的镜像中只包含了目标文件夹 `dist`,但仍然存在一些问题,当 `package.json` 文件变动时,`RUN npm i && rm -rf ~/.npm` 这一层会重新执行,变更多次后,生成了大量的中间层镜像。 + +为解决这个问题,进一步的我们可以设想一个类似 **数据卷** 的功能,在镜像构建时把 `node_modules` 文件夹挂载上去,在构建完成后,这个 `node_modules` 文件夹会自动卸载,实际的镜像中并不包含 `node_modules` 这个文件夹,这样我们就省去了每次获取依赖的时间,大大增加了镜像构建效率,同时也避免了生成了大量的中间层镜像。 + +`BuildKit` 提供了 `RUN --mount=type=cache` 指令,可以实现上边的设想。 + +```docker +FROM node:alpine as builder + +WORKDIR /app + +COPY package.json /app/ + +RUN --mount=type=cache,target=/app/node_modules,id=my_app_npm_module,sharing=locked \ + --mount=type=cache,target=/root/.npm,id=npm_cache \ + npm i --registry=https://registry.npm.taobao.org + +COPY src /app/src + +RUN --mount=type=cache,target=/app/node_modules,id=my_app_npm_module,sharing=locked \ +# --mount=type=cache,target=/app/dist,id=my_app_dist,sharing=locked \ + npm run build + +FROM nginx:alpine + +# COPY --from=builder /app/dist /app/dist + +# 为了更直观的说明 from 和 source 指令,这里使用 RUN 指令 +RUN --mount=type=cache,target=/tmp/dist,from=builder,source=/app/dist \ + # --mount=type=cache,target/tmp/dist,from=my_app_dist,sharing=locked \ + mkdir -p /app/dist && cp -r /tmp/dist/* /app/dist +``` + +第一个 `RUN` 指令执行后,`id` 为 `my_app_npm_module` 的缓存文件夹挂载到了 `/app/node_modules` 文件夹中。多次执行也不会产生多个中间层镜像。 + +第二个 `RUN` 指令执行时需要用到 `node_modules` 文件夹,`node_modules` 已经挂载,命令也可以正确执行。 + +第三个 `RUN` 指令将上一阶段产生的文件复制到指定位置,`from` 指明缓存的来源,这里 `builder` 表示缓存来源于构建的第一阶段,`source` 指明缓存来源的文件夹。 + +上面的 `Dockerfile` 中 `--mount=type=cache,...` 中指令作用如下: + +|Option |Description| +|---------------------|-----------| +|`id` | `id` 设置一个标志,以便区分缓存。| +|`target` (必填项) | 缓存的挂载目标文件夹。| +|`ro`,`readonly` | 只读,缓存文件夹不能被写入。 | +|`sharing` | 有 `shared` `private` `locked` 值可供选择。`sharing` 设置当一个缓存被多次使用时的表现,由于 `BuildKit` 支持并行构建,当多个步骤使用同一缓存时(同一 `id`)会发生冲突。`shared` 表示多个步骤可以同时读写,`private` 表示当多个步骤使用同一缓存时,每个步骤使用不同的缓存,`locked` 表示当一个步骤完成释放缓存后,后一个步骤才能继续使用该缓存。| +|`from` | 缓存来源(构建阶段),不填写时为空文件夹。| +|`source` | 来源的文件夹路径。| + +#### `RUN --mount=type=bind` + +该指令可以将一个镜像(或上一构建阶段)的文件挂载到指定位置。 + +```docker +RUN --mount=type=bind,from=php:alpine,source=/usr/local/bin/docker-php-entrypoint,target=/docker-php-entrypoint \ + cat /docker-php-entrypoint +``` + +#### `RUN --mount=type=tmpfs` + +该指令可以将一个 `tmpfs` 文件系统挂载到指定位置。 + +```docker +RUN --mount=type=tmpfs,target=/temp \ + mount | grep /temp +``` + +#### `RUN --mount=type=secret` + +该指令可以将一个文件挂载到指定位置。 + +```docker +RUN --mount=type=secret,id=aws,target=/root/.aws/credentials \ + cat /root/.aws/credentials +``` + +```bash +$ docker buildx build -t test --secret id=aws,src=$HOME/.aws/credentials . +``` + +#### `RUN --mount=type=ssh` + +该指令可以挂载 `ssh` 密钥。 + +```docker +FROM alpine +RUN apk add --no-cache openssh-client +RUN mkdir -p -m 0700 ~/.ssh && ssh-keyscan gitlab.com >> ~/.ssh/known_hosts +RUN --mount=type=ssh ssh git@gitlab.com | tee /hello +``` + +```bash +$ eval $(ssh-agent) +$ ssh-add ~/.ssh/id_rsa +(Input your passphrase here) + +$ docker buildx build -t test --ssh default=$SSH_AUTH_SOCK . +``` + +### 清理构建缓存 + +执行以下命令清理构建缓存 + +```bash +$ docker builder prune +``` + +### 官方文档 + +* https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/experimental.md diff --git a/image/buildx_multi-arch-images.md b/image/buildx_multi-arch-images.md new file mode 100644 index 0000000..62ead2b --- /dev/null +++ b/image/buildx_multi-arch-images.md @@ -0,0 +1,78 @@ +# 使用 buildx 构建多种系统架构支持的 Docker 镜像 + +在之前的版本中构建多种系统架构支持的 Docker 镜像,要想使用统一的名字必须使用 [`$ docker manifest`](manifest.md) 命令。 + +在 Docker 19.03+ 版本中可以使用 `$ docker buildx build` 命令使用 `BuildKit` 构建镜像。 + +该命令支持 `--platform` 参数可以同时构建支持多种系统架构的 Docker 镜像,大大简化了构建步骤。 + +## 设置环境变量 + +`buildx` 命令属于实验特性,必须设置环境变量以使用该命令。 + +Linux/macOS + +```bash +$ export DOCKER_CLI_EXPERIMENTAL=enabled +``` + +Windows + +```bash +$ set $env:DOCKER_CLI_EXPERIMENTAL=enabled +``` + +## 新建 `builder` 实例 + +Docker for Linux 不支持构建 `arm` 架构镜像,我们可以运行一个新的容器让其支持该特性,Docker 桌面版无需进行此项设置。 + +```bash +$ docker run --rm --privileged docker/binfmt:820fdd95a9972a5308930a2bdfb8573dd4447ad3 +``` + +由于 Docker 默认的 `builder` 实例不支持同时指定多个 `--platform`,我们必须首先创建一个新的 `builder` 实例。 + +```bash +$ docker buildx create --name mybuilder + +$ docker buildx use mybuilder +``` + +## 构建镜像 + +新建 Dockerfile 文件。 + +```docker +FROM --platform=$TARGETPLATFORM alpine + +RUN uname -a > /os.txt + +CMD cat /os.txt +``` + +使用 `$ docker buildx build` 命令构建镜像,注意将 `myusername` 替换为自己的 Docker Hub 用户名。 + +`--push` 参数表示将构建好的镜像推送到 Docker 仓库。 + +```bash +$ docker buildx build --platform linux/arm,linux/arm64,linux/amd64 -t myusername/hello . --push + +# 查看镜像信息 +$ docker buildx imagetools inspect myusername/hello +``` + +在不同架构运行该镜像,可以得到该架构的信息。 + +```bash +# arm +$ docker run -it --rm myusername/hello +Linux buildkitsandbox 4.9.125-linuxkit #1 SMP Fri Sep 7 08:20:28 UTC 2018 armv7l Linux + +# arm64 +$ docker run -it --rm myusername/hello +Linux buildkitsandbox 4.9.125-linuxkit #1 SMP Fri Sep 7 08:20:28 UTC 2018 aarch64 Linux + +# amd64 +$ docker run -it --rm myusername/hello +Linux buildkitsandbox 4.9.125-linuxkit #1 SMP Fri Sep 7 08:20:28 UTC 2018 x86_64 Linux +``` diff --git a/image/commit.md b/image/commit.md index 37664cb..6f921e5 100644 --- a/image/commit.md +++ b/image/commit.md @@ -21,7 +21,7 @@ $ docker run --name webserver -d -p 80:80 nginx 直接用浏览器访问的话,我们会看到默认的 Nginx 欢迎页面。 - +![](_images/images-mac-example-nginx.png) 现在,假设我们非常不喜欢这个欢迎页面,我们希望改成欢迎 Docker 的文字,我们可以使用 `docker exec` 命令进入容器,修改其内容。 @@ -38,7 +38,7 @@ exit 现在我们再刷新浏览器的话,会发现内容被改变了。 - +![](_images/images-create-nginx-docker.png) 我们修改了容器的文件,也就是改动了容器的存储层。我们可以通过 `docker diff` 命令看到具体的改动。 diff --git a/image/demo/multi-arch/Dockerfile b/image/demo/multi-arch/Dockerfile new file mode 100644 index 0000000..9fbaba7 --- /dev/null +++ b/image/demo/multi-arch/Dockerfile @@ -0,0 +1,5 @@ +FROM --platform=$TARGETPLATFORM alpine + +RUN uname -a > /os.txt + +CMD cat /os.txt diff --git a/image/dockerfile/add.md b/image/dockerfile/add.md index 3c3a2dc..5d9f18f 100644 --- a/image/dockerfile/add.md +++ b/image/dockerfile/add.md @@ -8,7 +8,7 @@ 在某些情况下,这个自动解压缩的功能非常有用,比如官方镜像 `ubuntu` 中: -```Dockerfile +```docker FROM scratch ADD ubuntu-xenial-core-cloudimg-amd64-root.tar.gz / ... @@ -24,7 +24,7 @@ ADD ubuntu-xenial-core-cloudimg-amd64-root.tar.gz / 在使用该指令的时候还可以加上 `--chown=:` 选项来改变文件的所属用户及所属组。 -```Dockerfile +```docker ADD --chown=55:mygroup files* /mydir/ ADD --chown=bin files* /mydir/ ADD --chown=1 files* /mydir/ diff --git a/image/dockerfile/cmd.md b/image/dockerfile/cmd.md index d21c611..9877c00 100644 --- a/image/dockerfile/cmd.md +++ b/image/dockerfile/cmd.md @@ -14,13 +14,13 @@ 如果使用 `shell` 格式的话,实际的命令会被包装为 `sh -c` 的参数的形式进行执行。比如: -```Dockerfile +```docker CMD echo $HOME ``` 在实际执行中,会将其变更为: -```Dockerfile +```docker CMD [ "sh", "-c", "echo $HOME" ] ``` @@ -32,7 +32,7 @@ Docker 不是虚拟机,容器中的应用都应该以前台执行,而不是 一些初学者将 `CMD` 写为: -```Dockerfile +```docker CMD service nginx start ``` @@ -44,6 +44,6 @@ CMD service nginx start 正确的做法是直接执行 `nginx` 可执行文件,并且要求以前台形式运行。比如: -```Dockerfile +```docker CMD ["nginx", "-g", "daemon off;"] ``` diff --git a/image/dockerfile/copy.md b/image/dockerfile/copy.md index 53914a4..d12b446 100644 --- a/image/dockerfile/copy.md +++ b/image/dockerfile/copy.md @@ -9,13 +9,13 @@ `COPY` 指令将从构建上下文目录中 `<源路径>` 的文件/目录复制到新的一层的镜像内的 `<目标路径>` 位置。比如: -```Dockerfile +```docker COPY package.json /usr/src/app/ ``` `<源路径>` 可以是多个,甚至可以是通配符,其通配符规则要满足 Go 的 [`filepath.Match`](https://golang.org/pkg/path/filepath/#Match) 规则,如: -```Dockerfile +```docker COPY hom* /mydir/ COPY hom?.txt /mydir/ ``` @@ -26,7 +26,7 @@ COPY hom?.txt /mydir/ 在使用该指令的时候还可以加上 `--chown=:` 选项来改变文件的所属用户及所属组。 -```Dockerfile +```docker COPY --chown=55:mygroup files* /mydir/ COPY --chown=bin files* /mydir/ COPY --chown=1 files* /mydir/ diff --git a/image/dockerfile/entrypoint.md b/image/dockerfile/entrypoint.md index 07e79e0..25090dd 100644 --- a/image/dockerfile/entrypoint.md +++ b/image/dockerfile/entrypoint.md @@ -16,7 +16,7 @@ 假设我们需要一个得知自己当前公网 IP 的镜像,那么可以先用 `CMD` 来实现: -```Dockerfile +```docker FROM ubuntu:18.04 RUN apt-get update \ && apt-get install -y curl \ @@ -48,7 +48,7 @@ $ docker run myip curl -s https://ip.cn -i 这显然不是很好的解决方案,而使用 `ENTRYPOINT` 就可以解决这个问题。现在我们重新用 `ENTRYPOINT` 来实现这个镜像: -```Dockerfile +```docker FROM ubuntu:18.04 RUN apt-get update \ && apt-get install -y curl \ @@ -91,7 +91,7 @@ Connection: keep-alive 这些准备工作是和容器 `CMD` 无关的,无论 `CMD` 为什么,都需要事先进行一个预处理的工作。这种情况下,可以写一个脚本,然后放入 `ENTRYPOINT` 中去执行,而这个脚本会将接到的参数(也就是 ``)作为命令,在脚本最后执行。比如官方镜像 `redis` 中就是这么做的: -```Dockerfile +```docker FROM alpine:3.4 ... RUN addgroup -S redis && adduser -S -G redis redis diff --git a/image/dockerfile/env.md b/image/dockerfile/env.md index 58108bd..f55d567 100644 --- a/image/dockerfile/env.md +++ b/image/dockerfile/env.md @@ -16,7 +16,7 @@ ENV VERSION=1.0 DEBUG=on \ 定义了环境变量,那么在后续的指令中,就可以使用这个环境变量。比如在官方 `node` 镜像 `Dockerfile` 中,就有类似这样的代码: -```Dockerfile +```docker ENV NODE_VERSION 7.2.0 RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz" \ diff --git a/image/dockerfile/healthcheck.md b/image/dockerfile/healthcheck.md index 7daa2e3..78a83a6 100644 --- a/image/dockerfile/healthcheck.md +++ b/image/dockerfile/healthcheck.md @@ -25,7 +25,7 @@ 假设我们有个镜像是个最简单的 Web 服务,我们希望增加健康检查来判断其 Web 服务是否在正常工作,我们可以用 `curl` 来帮助判断,其 `Dockerfile` 的 `HEALTHCHECK` 可以这么写: -```Dockerfile +```docker FROM nginx RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* HEALTHCHECK --interval=5s --timeout=3s \ diff --git a/image/dockerfile/onbuild.md b/image/dockerfile/onbuild.md index 0c2e022..dbe1384 100644 --- a/image/dockerfile/onbuild.md +++ b/image/dockerfile/onbuild.md @@ -8,7 +8,7 @@ 假设我们要制作 Node.js 所写的应用的镜像。我们都知道 Node.js 使用 `npm` 进行包管理,所有依赖、配置、启动信息等会放到 `package.json` 文件里。在拿到程序代码后,需要先进行 `npm install` 才可以获得所有需要的依赖。然后就可以通过 `npm start` 来启动应用。因此,一般来说会这样写 `Dockerfile`: -```Dockerfile +```docker FROM node:slim RUN mkdir /app WORKDIR /app @@ -24,7 +24,7 @@ CMD [ "npm", "start" ] 那么我们可不可以做一个基础镜像,然后各个项目使用这个基础镜像呢?这样基础镜像更新,各个项目不用同步 `Dockerfile` 的变化,重新构建后就继承了基础镜像的更新?好吧,可以,让我们看看这样的结果。那么上面的这个 `Dockerfile` 就会变为: -```Dockerfile +```docker FROM node:slim RUN mkdir /app WORKDIR /app @@ -33,7 +33,7 @@ CMD [ "npm", "start" ] 这里我们把项目相关的构建指令拿出来,放到子项目里去。假设这个基础镜像的名字为 `my-node` 的话,各个项目内的自己的 `Dockerfile` 就变为: -```Dockerfile +```docker FROM my-node COPY ./package.json /app RUN [ "npm", "install" ] @@ -46,7 +46,7 @@ COPY . /app/ `ONBUILD` 可以解决这个问题。让我们用 `ONBUILD` 重新写一下基础镜像的 `Dockerfile`: -```Dockerfile +```docker FROM node:slim RUN mkdir /app WORKDIR /app @@ -58,7 +58,7 @@ CMD [ "npm", "start" ] 这次我们回到原始的 `Dockerfile`,但是这次将项目相关的指令加上 `ONBUILD`,这样在构建基础镜像的时候,这三行并不会被执行。然后各个项目的 `Dockerfile` 就变成了简单地: -```Dockerfile +```docker FROM my-node ``` diff --git a/image/dockerfile/user.md b/image/dockerfile/user.md index 68af822..260b439 100644 --- a/image/dockerfile/user.md +++ b/image/dockerfile/user.md @@ -6,7 +6,7 @@ 当然,和 `WORKDIR` 一样,`USER` 只是帮助你切换到指定用户而已,这个用户必须是事先建立好的,否则无法切换。 -```Dockerfile +```docker RUN groupadd -r redis && useradd -r -g redis redis USER redis RUN [ "redis-server" ] @@ -14,7 +14,7 @@ RUN [ "redis-server" ] 如果以 `root` 执行的脚本,在执行期间希望改变身份,比如希望以某个已经建立好的用户来运行某个服务进程,不要使用 `su` 或者 `sudo`,这些都需要比较麻烦的配置,而且在 TTY 缺失的环境下经常出错。建议使用 [`gosu`](https://github.com/tianon/gosu)。 -```Dockerfile +```docker # 建立 redis 用户,并使用 gosu 换另一个用户执行命令 RUN groupadd -r redis && useradd -r -g redis redis # 下载 gosu diff --git a/image/dockerfile/volume.md b/image/dockerfile/volume.md index 59dca9c..d27172f 100644 --- a/image/dockerfile/volume.md +++ b/image/dockerfile/volume.md @@ -7,7 +7,7 @@ 之前我们说过,容器运行时应该尽量保持容器存储层不发生写操作,对于数据库类需要保存动态数据的应用,其数据库文件应该保存于卷(volume)中,后面的章节我们会进一步介绍 Docker 卷的概念。为了防止运行时用户忘记将动态文件所保存目录挂载为卷,在 `Dockerfile` 中,我们可以事先指定某些目录挂载为匿名卷,这样在运行时如果用户不指定挂载,其应用也可以正常运行,不会向容器存储层写入大量数据。 -```Dockerfile +```docker VOLUME /data ``` diff --git a/image/manifest.md b/image/manifest.md index 0c98c78..e77eb40 100644 --- a/image/manifest.md +++ b/image/manifest.md @@ -2,11 +2,11 @@ 我们知道使用镜像创建一个容器,该镜像必须与 Docker 宿主机系统架构一致,例如 `Linux x86_64` 架构的系统中只能使用 `Linux x86_64` 的镜像创建容器。 -> macOS 除外,其使用了 [binfmt_misc](https://docs.docker.com/docker-for-mac/multi-arch/) 提供了多种架构支持,在 macOS 系统上 (x86_64) 可以运行 arm 等其他架构的镜像。 +> Windows、macOS 除外,其使用了 [binfmt_misc](https://docs.docker.com/docker-for-mac/multi-arch/) 提供了多种架构支持,在 Windows、macOS 系统上 (x86_64) 可以运行 arm 等其他架构的镜像。 例如我们在 `Linux x86_64` 中构建一个 `username/test` 镜像。 -```Dockerfile +```docker FROM alpine CMD echo 1 diff --git a/image/multistage-builds/README.md b/image/multistage-builds/README.md index 59c3707..97a6161 100644 --- a/image/multistage-builds/README.md +++ b/image/multistage-builds/README.md @@ -26,7 +26,7 @@ func main(){ 编写 `Dockerfile.one` 文件 -```dockerfile +```docker FROM golang:1.9-alpine RUN apk --no-cache add git ca-certificates @@ -56,7 +56,7 @@ $ docker build -t go/helloworld:1 -f Dockerfile.one . 例如,编写 `Dockerfile.build` 文件 -```dockerfile +```docker FROM golang:1.9-alpine RUN apk --no-cache add git @@ -71,7 +71,7 @@ RUN go get -d -v github.com/go-sql-driver/mysql \ 编写 `Dockerfile.copy` 文件 -```dockerfile +```docker FROM alpine:latest RUN apk --no-cache add ca-certificates @@ -125,7 +125,7 @@ go/helloworld 1 f55d3e16affc 2 minutes ago 295MB 例如,编写 `Dockerfile` 文件 -```dockerfile +```docker FROM golang:1.9-alpine as builder RUN apk --no-cache add git @@ -172,7 +172,7 @@ go/helloworld 1 f55d3e16affc 2 minutes ago 295MB 我们可以使用 `as` 来为某一阶段命名,例如 -```dockerfile +```docker FROM golang:1.9-alpine as builder ``` @@ -186,6 +186,6 @@ $ docker build --target builder -t username/imagename:tag . 上面例子中我们使用 `COPY --from=0 /go/src/github.com/go/helloworld/app .` 从上一阶段的镜像中复制文件,我们也可以复制任意镜像中的文件。 -```dockerfile +```docker $ COPY --from=nginx:latest /etc/nginx/nginx.conf /nginx.conf ``` diff --git a/image/multistage-builds/laravel.md b/image/multistage-builds/laravel.md index e8c8f10..5d7a44b 100644 --- a/image/multistage-builds/laravel.md +++ b/image/multistage-builds/laravel.md @@ -50,7 +50,7 @@ server { 第一阶段进行前端构建。 -```dockerfile +```docker FROM node:alpine as frontend COPY package.json /app/ @@ -69,7 +69,7 @@ RUN cd /app \ 第二阶段安装 Composer 依赖。 -```dockerfile +```docker FROM composer as composer COPY database/ /app/database/ @@ -89,7 +89,7 @@ RUN cd /app \ 第三阶段对以上阶段生成的文件进行整合。 -```dockerfile +```docker FROM php:7.2-fpm-alpine as laravel ARG LARAVEL_PATH=/app/laravel @@ -113,7 +113,7 @@ RUN cd ${LARAVEL_PATH} \ ### 最后一个阶段构建 NGINX 镜像 -```dockerfile +```docker FROM nginx:alpine as nginx ARG LARAVEL_PATH=/app/laravel @@ -164,7 +164,7 @@ $ docker run -it --rm --network=laravel -p 8080:80 my/nginx 完整的 `Dockerfile` 文件如下。 -```dockerfile +```docker FROM node:alpine as frontend COPY package.json /app/ diff --git a/introduction/what.md b/introduction/what.md index dd568dd..1351afe 100644 --- a/introduction/what.md +++ b/introduction/what.md @@ -1,4 +1,4 @@ -## 什么是 Docker +# 什么是 Docker **Docker** 最初是 `dotCloud` 公司创始人 [Solomon Hykes](https://github.com/shykes) 在法国期间发起的一个公司内部项目,它是基于 `dotCloud` 公司多年云服务技术的一次革新,并于 [2013 年 3 月以 Apache 2.0 授权协议开源][docker-soft],主要项目代码在 [GitHub](https://github.com/moby/moby) 上进行维护。`Docker` 项目后来还加入了 Linux 基金会,并成立推动 [开放容器联盟(OCI)](https://www.opencontainers.org/)。 diff --git a/introduction/why.md b/introduction/why.md index d083044..df6ce2c 100644 --- a/introduction/why.md +++ b/introduction/why.md @@ -1,20 +1,20 @@ -## 为什么要使用 Docker? +# 为什么要使用 Docker? 作为一种新兴的虚拟化方式,`Docker` 跟传统的虚拟化方式相比具有众多的优势。 -### 更高效的利用系统资源 +## 更高效的利用系统资源 由于容器不需要进行硬件虚拟以及运行完整操作系统等额外开销,`Docker` 对系统资源的利用率更高。无论是应用执行速度、内存损耗或者文件存储速度,都要比传统虚拟机技术更高效。因此,相比虚拟机技术,一个相同配置的主机,往往可以运行更多数量的应用。 -### 更快速的启动时间 +## 更快速的启动时间 传统的虚拟机技术启动应用服务往往需要数分钟,而 `Docker` 容器应用,由于直接运行于宿主内核,无需启动完整的操作系统,因此可以做到秒级、甚至毫秒级的启动时间。大大的节约了开发、测试、部署的时间。 -### 一致的运行环境 +## 一致的运行环境 开发过程中一个常见的问题是环境一致性问题。由于开发环境、测试环境、生产环境不一致,导致有些 bug 并未在开发过程中被发现。而 `Docker` 的镜像提供了除内核外完整的运行时环境,确保了应用运行环境一致性,从而不会再出现 *「这段代码在我机器上没问题啊」* 这类问题。 -### 持续交付和部署 +## 持续交付和部署 对开发和运维([DevOps](https://zh.wikipedia.org/wiki/DevOps))人员来说,最希望的就是一次创建或配置,可以在任意地方正常运行。 @@ -22,15 +22,15 @@ 而且使用 [`Dockerfile`](../image/build.md) 使镜像构建透明化,不仅仅开发团队可以理解应用运行环境,也方便运维团队理解应用运行所需条件,帮助更好的生产环境中部署该镜像。 -### 更轻松的迁移 +## 更轻松的迁移 由于 `Docker` 确保了执行环境的一致性,使得应用的迁移更加容易。`Docker` 可以在很多平台上运行,无论是物理机、虚拟机、公有云、私有云,甚至是笔记本,其运行结果是一致的。因此用户可以很轻易的将在一个平台上运行的应用,迁移到另一个平台上,而不用担心运行环境的变化导致应用无法正常运行的情况。 -### 更轻松的维护和扩展 +## 更轻松的维护和扩展 `Docker` 使用的分层存储以及镜像的技术,使得应用重复部分的复用更为容易,也使得应用的维护更新更加简单,基于基础镜像进一步扩展镜像也变得非常简单。此外,`Docker` 团队同各个开源项目团队一起维护了一大批高质量的 [官方镜像](https://hub.docker.com/search/?type=image&image_filter=official),既可以直接在生产环境使用,又可以作为基础进一步定制,大大的降低了应用服务的镜像制作成本。 -### 对比传统虚拟机总结 +## 对比传统虚拟机总结 | 特性 | 容器 | 虚拟机 | | :-------- | :-------- | :---------- |