diff --git a/.travis.yml b/.travis.yml index 7a68a80..de0aad3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,8 @@ language: bash -sudo: required + services: - docker + before_install: - openssl aes-256-cbc -K $encrypted_6cc8cff04075_key -iv $encrypted_6cc8cff04075_iv -in .travis/id_rsa.enc -out ~/.ssh/id_rsa -d @@ -10,8 +11,10 @@ before_install: - date - git config --global user.name "khs1994" - git config --global user.email "khs1994@khs1994.com" + script: - docker run -it --rm -v $PWD:/srv/gitbook-src yeasy/docker_practice build + after_success: - sudo chmod -R 777 _book - echo "FROM nginx:1.13.8-alpine" >> Dockerfile @@ -29,14 +32,17 @@ after_success: - COMMIT=`date "+%F %T"` - git commit -m "Travis CI Site updated $COMMIT" - git push -f origin master:"$DEPLOY_BRANCH" + env: global: - DEPLOY_BRANCH: pages # - DEPLOY_BRANCH: legacy-pages - REPO: git@github.com:yeasy/docker_practice.git + addons: ssh_known_hosts: - github.com + branches: only: - master diff --git a/CHANGELOG.md b/CHANGELOG.md index c655b83..31a9069 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## 主要修订记录 +* 1.1.0 2019-06-30 + * 增加 `BuildKit` + * 增加 `docker manifest` 命令使用说明 + * 1.0.0: 2018-12-31 * 全面支持 v18.x 新版本 * 添加如何调试 Docker diff --git a/README.md b/README.md index e4296ea..5adb3f5 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![](https://img.shields.io/github/stars/yeasy/docker_practice.svg?style=social&label=Stars)](https://github.com/yeasy/docker_practice) [![](https://travis-ci.org/yeasy/docker_practice.svg?branch=master)](https://travis-ci.org/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%20CE%20v18.x-blue.svg)](https://github.com/docker/docker-ce) [![](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)](https://u.jd.com/tKZmVG) -**v1.0.0** +**v1.1.0** [Docker](https://www.docker.com) 是个划时代的开源项目,它彻底释放了计算虚拟化的威力,极大提高了应用的维护效率,降低了云计算应用开发的成本!使用 Docker,可以让应用的部署、测试和分发都变得前所未有的高效和轻松! @@ -40,7 +40,7 @@ Docker 自身仍在快速发展中,生态环境也在蓬勃成长。建议初 * QQ 群 VII (已满):252403484 * QQ 群 VIII(已满):544818750 * QQ 群 IX (已满):571502246 -* QQ 群 X   (可加):145983035 +* QQ 群 X (可加):145983035 >如果有问题,请通过 [Issues](https://github.com/yeasy/docker_practice/issues/new/choose) 来提出。 diff --git a/SUMMARY.md b/SUMMARY.md index 401734a..4ab42c2 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -41,6 +41,8 @@ * [参考文档](image/dockerfile/references.md) * [Dockerfile 多阶段构建](image/multistage-builds/README.md) * [实战多阶段构建 Laravel 镜像](image/multistage-builds/laravel.md) + * [构建多种系统架构支持的 Docker 镜像](image/manifest.md) + * [使用 BuildKit 构建镜像](image/buildkit.md) * [其它制作镜像的方式](image/other.md) * [实现原理](image/internal.md) * [操作容器](container/README.md) diff --git a/image/buildkit.md b/image/buildkit.md new file mode 100644 index 0000000..cf82aa6 --- /dev/null +++ b/image/buildkit.md @@ -0,0 +1,178 @@ +## 使用 `BuildKit` 构建镜像 + +**BuildKit** 是下一代的镜像构建组件,在 https://github.com/moby/buildkit 开源。 + +**注意:如果您的镜像构建使用的是云服务商提供的镜像构建服务(Docker Hub 自动构建、腾讯云容器服务、阿里云容器服务等),由于上述服务提供商的 Docker 版本低于 18.09,BuildKit 无法使用,将造成镜像构建失败。建议使用 BuildKit 构建镜像时使用一个新的 Dockerfile 文件(例如 Dockerfile.buildkit)** + +**注意:docker-compose build 命令暂时不支持 BuildKit** + +下面介绍如何在 Docker CE 18.09+ 版本中使用 `BuildKit` 提供的 `Dockerfile` 新指令来更快、更安全的构建 Docker 镜像。 + +### 启用 `BuildKit` + +启用 `BuildKit` 必须先设置 **环境变量**。 + +Linux、macOS 执行如下命令: + +```bash +$ export DOCKER_BUILDKIT=1 +``` + +Windows 执行如下命令: + +```powershell +$ set $env:DOCKER_BUILDKIT=1 +``` + +> 以上是设置环境变量的临时方法,若使环境变量永久生效请读者自行设置。 + +### `Dockerfile` 新增指令详解 + +启用 `BuildKit` 之后,我们可以使用下面几个新的指令来加快镜像构建。 + +#### `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 +# syntax = docker/dockerfile:experimental +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 +``` + +**由于 `BuildKit` 为实验特性,每个 `Dockerfile` 文件开头都必须加上如下指令** + +```docker +# syntax = docker/dockerfile:experimental +``` + +第一个 `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 +# syntax = docker/dockerfile:experimental +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 +# syntax = docker/dockerfile:experimental +RUN --mount=type=tmpfs,target=/temp \ + mount | grep /temp +``` + +#### `RUN --mount=type=secret` + +该指令可以将一个文件挂载到指定位置。 + +```docker +# syntax = docker/dockerfile:experimental +RUN --mount=type=secret,id=aws,target=/root/.aws/credentials \ + cat /root/.aws/credentials +``` + +```bash +$ docker build -t test --secret id=aws,src=$HOME/.aws/credentials . +``` + +#### `RUN --mount=type=ssh` + +该指令可以挂载 `ssh` 密钥。 + +```docker +# syntax = docker/dockerfile:experimental +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 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/buildkit/README.md b/image/buildkit/README.md deleted file mode 100644 index 0fa2eab..0000000 --- a/image/buildkit/README.md +++ /dev/null @@ -1,13 +0,0 @@ -## BuildKit - -BuildKit 是下一代的镜像构建组件,在 https://github.com/moby/buildkit 开源。 - -下面介绍如何在 Docker CE 18.09+ 版本中使用 BuildKit 构建 Docker 镜像。 - -```bash -$ export DOCKER_BUILDKIT=1 - -# Windows - -$ set $env:DOCKER_BUILDKIT=1 -``` diff --git a/image/demo/buildkit/Dockerfile b/image/demo/buildkit/Dockerfile new file mode 100644 index 0000000..7e16bbf --- /dev/null +++ b/image/demo/buildkit/Dockerfile @@ -0,0 +1,16 @@ +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 diff --git a/image/demo/buildkit/Dockerfile.buildkit b/image/demo/buildkit/Dockerfile.buildkit new file mode 100644 index 0000000..effcfc9 --- /dev/null +++ b/image/demo/buildkit/Dockerfile.buildkit @@ -0,0 +1,37 @@ +# syntax = docker/dockerfile:experimental + +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 --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,target=/temp \ + mount | grep /temp + +RUN --mount=type=secret,id=aws,target=/root/.aws/credentials \ + cat /root/.aws/credentials + +# docker build -t test --secret id=aws,src=$PWD/aws.txt --progress=plain -f Dockerfile.buildkit . diff --git a/image/demo/buildkit/aws.txt b/image/demo/buildkit/aws.txt new file mode 100644 index 0000000..f400d55 --- /dev/null +++ b/image/demo/buildkit/aws.txt @@ -0,0 +1 @@ +awskey diff --git a/image/demo/buildkit/package.json b/image/demo/buildkit/package.json new file mode 100644 index 0000000..7c2d1da --- /dev/null +++ b/image/demo/buildkit/package.json @@ -0,0 +1,11 @@ +{ + "name": "my_app", + "version": "19.6.0", + "devDependencies": { + "webpack": "*", + "webpack-cli": "*" + }, + "scripts": { + "build": "mkdir -p $PWD/dist && cp -r src/* dist/" + } +} diff --git a/image/demo/buildkit/src/index.js b/image/demo/buildkit/src/index.js new file mode 100644 index 0000000..296d549 --- /dev/null +++ b/image/demo/buildkit/src/index.js @@ -0,0 +1 @@ +console.log(1); diff --git a/image/manifest.md b/image/manifest.md new file mode 100644 index 0000000..0c98c78 --- /dev/null +++ b/image/manifest.md @@ -0,0 +1,178 @@ +## 构建多种系统架构支持的 Docker 镜像 -- docker manifest 命令详解 + +我们知道使用镜像创建一个容器,该镜像必须与 Docker 宿主机系统架构一致,例如 `Linux x86_64` 架构的系统中只能使用 `Linux x86_64` 的镜像创建容器。 + +> macOS 除外,其使用了 [binfmt_misc](https://docs.docker.com/docker-for-mac/multi-arch/) 提供了多种架构支持,在 macOS 系统上 (x86_64) 可以运行 arm 等其他架构的镜像。 + +例如我们在 `Linux x86_64` 中构建一个 `username/test` 镜像。 + +```Dockerfile +FROM alpine + +CMD echo 1 +``` + +构建镜像后推送到 Docker Hub,之后我们尝试在树莓派 `Linux arm64v8` 中使用这个镜像。 + +```bash +$ docker run -it --rm username/test +``` + +可以发现这个镜像根本获取不到。 + +要解决这个问题,通常采用的做法是通过镜像名区分不同系统架构的镜像,例如在 `Linux x86_64` 和 `Linux arm64v8` 分别构建 `username/test` 和 `username/arm64v8-test` 镜像。运行时使用对应架构的镜像即可。 + +这样做显得很繁琐,那么有没有一种方法让 Docker 引擎根据系统架构自动拉取对应的镜像呢? + +我们发现在 `Linux x86_64` 和 `Linux arm64v8` 架构的计算机中执行 `$ docker run golang:alpine go version` 时我们发现可以正确的运行。 + +这是什么原因呢? + +原因就是 `golang:alpine` 官方镜像有一个 [`manifest` 列表](https://docs.docker.com/registry/spec/manifest-v2-2/)。 + +当用户获取一个镜像时,Docker 引擎会首先查找该镜像是否有 `manifest` 列表,如果有的话 Docker 引擎会按照 Docker 运行环境(系统及架构)查找出对应镜像(例如 `golang:alpine`)。如果没有的话会直接获取镜像(例如上例中我们构建的 `username/test`)。 + +我们可以使用 `$ docker manifest inspect golang:alpine` 查看这个 `manifest` 列表的结构。 + +由于该命令属于实验特性,必须设置如下 **环境变量** 之后才能使用: + +```bash +# Linux、macOS + +$ export DOCKER_CLI_EXPERIMENTAL=enabled + +# Windows + +$ set $env:DOCKER_CLI_EXPERIMENTAL=enabled +``` + +> 以上是设置环境变量的临时方法,若使环境变量永久生效请读者自行设置。 + +设置之后,执行结果如下 + +```bash +$ docker manifest inspect golang:alpine +``` + +```json +{ + "schemaVersion": 2, + "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json", + "manifests": [ + { + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "size": 1365, + "digest": "sha256:5e28ac423243b187f464d635bcfe1e909f4a31c6c8bce51d0db0a1062bec9e16", + "platform": { + "architecture": "amd64", + "os": "linux" + } + }, + { + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "size": 1365, + "digest": "sha256:2945c46e26c9787da884b4065d1de64cf93a3b81ead1b949843dda1fcd458bae", + "platform": { + "architecture": "arm", + "os": "linux", + "variant": "v7" + } + }, + { + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "size": 1365, + "digest": "sha256:87fff60114fd3402d0c1a7ddf1eea1ded658f171749b57dc782fd33ee2d47b2d", + "platform": { + "architecture": "arm64", + "os": "linux", + "variant": "v8" + } + }, + { + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "size": 1365, + "digest": "sha256:607b43f1d91144f82a9433764e85eb3ccf83f73569552a49bc9788c31b4338de", + "platform": { + "architecture": "386", + "os": "linux" + } + }, + { + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "size": 1365, + "digest": "sha256:25ead0e21ed5e246ce31e274b98c09aaf548606788ef28eaf375dc8525064314", + "platform": { + "architecture": "ppc64le", + "os": "linux" + } + }, + { + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "size": 1365, + "digest": "sha256:69f5907fa93ea591175b2c688673775378ed861eeb687776669a48692bb9754d", + "platform": { + "architecture": "s390x", + "os": "linux" + } + } + ] +} +``` + +可以看出 `manifest` 列表中包含了不同系统架构所对应的镜像 `digest` 值,这样 Docker 就可以在不同的架构中使用相同的 `manifest` (例如 `golang:alpine`) 获取对应的镜像。 + +下面介绍如何使用 `$ docker manifest` 命令创建并推送 `manifest` 列表到 Docker Hub。 + +### 构建镜像 + +首先在 `Linux x86_64` 构建 `username/x8664-test` 镜像。并在 `Linux arm64v8` 中构建 `username/arm64v8-test` 镜像,构建好之后推送到 Docker Hub。 + +### 创建 `manifest` 列表 + +```bash +# $ docker manifest create MANIFEST_LIST MANIFEST [MANIFEST...] +$ docker manifest create username/test \ + username/x8664-test \ + username/arm64v8-test +``` + +当要修改一个 `manifest` 列表时,可以加入 `-a,--amend` 参数。 + +### 设置 `manifest` 列表 + +```bash +# $ docker manifest annotate [OPTIONS] MANIFEST_LIST MANIFEST +$ docker manifest annotate username/test \ + username/x8664-test \ + --os linux --arch x86_64 + +$ docker manifest annotate username/test \ + username/arm64v8-test \ + --os linux --arch arm64 --variant v8 +``` + +这样就配置好了 `manifest` 列表。 + +### 查看 `manifest` 列表 + +```bash +$ docker manifest inspect username/test +``` + +### 推送 `manifest` 列表 + +最后我们可以将其推送到 Docker Hub。 + +```bash +$ docker manifest push username/test +``` + +### 测试 + +我们在 `Linux x86_64` `Linux arm64v8` 中分别执行 `$ docker run -it --rm username/test` 命令,发现可以正确的执行。 + +### 官方博客 + +详细了解 `manifest` 可以阅读官方博客。 + +* https://blog.docker.com/2017/11/multi-arch-all-the-things/