diff --git a/Build/images/DockerBuild.png b/Build/assets/images/DockerBuild.png similarity index 100% rename from Build/images/DockerBuild.png rename to Build/assets/images/DockerBuild.png diff --git a/Build/images/build.png b/Build/assets/images/build.png similarity index 100% rename from Build/images/build.png rename to Build/assets/images/build.png diff --git a/Build/images/build2.png b/Build/assets/images/build2.png similarity index 100% rename from Build/images/build2.png rename to Build/assets/images/build2.png diff --git a/Build/images/构建流程示意图.drawio b/Build/assets/images/构建流程示意图.drawio similarity index 100% rename from Build/images/构建流程示意图.drawio rename to Build/assets/images/构建流程示意图.drawio diff --git a/Build/学习.md b/Build/学习.md index ab96349..693354a 100644 --- a/Build/学习.md +++ b/Build/学习.md @@ -4,13 +4,13 @@ ## 镜像构建流程 -![构建镜像](images/DockerBuild.png) +![构建镜像](assets/images/DockerBuild.png) ## 构建镜像的三种方法 + 基于现有容器,使用 `docker commit` 命令构建 - - ``` shell + + ```shell # 1 创建容器,执行相关操作,之后退出 docker run -it ubuntu:18.04 /bin/bash docker exec -it xxxxxxxx @@ -30,8 +30,8 @@ ``` + 基于本地模板,使用 `docker import` 导入 - - ``` shell + + ```shell # 1 将现有容器,导出为模板文件 docker export b66 > /root/myubuntu.tar @@ -44,7 +44,7 @@ ``` + 基于Dockerfile文件,使用 `docker build` 构建 - + ```tex # 1 编写Dockerfile文件 @@ -89,7 +89,7 @@ > docker是c/s架构,c为docker cli,s为驻守服务进程。 > docker build 构建镜像时,cli只负责把命令和相文件传递给服务进程,服务程序接收命令和文件、创建构建上下文、执行构建并产出镜像。 -![build](images/build2.png) +![build](assets/images/build2.png) ## docker build 命令 @@ -102,15 +102,15 @@ docker build [OPTIONS] PATH|URL|本地文本文件 git库示: 示例:docker build https://github.com/docker/rootfs.git#container:docker -Build Syntax Suffix Commit Used Build Context Used -myrepo.git refs/heads/master / -myrepo.git#mytag refs/tags/mytag / -myrepo.git#mybranch refs/heads/mybranch / -myrepo.git#pull/42/head refs/pull/42/head / -myrepo.git#:myfolder refs/heads/master /myfolder -myrepo.git#master:myfolder refs/heads/master /myfolder -myrepo.git#mytag:myfolder refs/tags/mytag /myfolder -myrepo.git#mybranch:myfolder refs/heads/mybranch /myfolder +Build Syntax Suffix Commit Used Build Context Used +myrepo.git refs/heads/master / +myrepo.git#mytag refs/tags/mytag / +myrepo.git#mybranch refs/heads/mybranch / +myrepo.git#pull/42/head refs/pull/42/head / +myrepo.git#:myfolder refs/heads/master /myfolder +myrepo.git#master:myfolder refs/heads/master /myfolder +myrepo.git#mytag:myfolder refs/tags/mytag /myfolder +myrepo.git#mybranch:myfolder refs/heads/mybranch /myfolder tar包: 示例:docker build http://server/context.tar.gz @@ -121,48 +121,48 @@ Powershell示例:Get-Content Dockerfile | docker build - Options -| 名称与速记 | 默认 | 描述 | -| ------------------------- | ------ | ------------------------------------------------------------ | -| `--add-host` | | 添加自定义主机到 IP 的映射 (host:ip) | -| `--build-arg` | | 设置构建时变量 | -| `--cache-from` | | 要考虑用作缓存源的图像 | -| `--cgroup-parent` | | 容器的可选父 cgroup | -| `--compress` | | 使用 gzip 压缩构建上下文 | -| `--cpu-period` | | 限制 CPU CFS(完全公平的调度程序)期限 | -| `--cpu-quota` | | 限制 CPU CFS(完全公平的调度程序)配额 | -| `--cpu-shares`,`-c` | | CPU 份额(相对权重) | -| `--cpuset-cpus` | | 允许执行的 CPU (0-3, 0,1) | -| `--cpuset-mems` | | 允许执行的 MEM (0-3, 0,1) | -| `--disable-content-trust` | `true` | 跳过镜像验证 | -| `--file`,`-f` | | Dockerfile 的名称(默认值为"PATH/Dockerfile") | -| `--force-rm` | | **始终删除中间容器** | -| `--iidfile` | | 将映像 ID 写入文件 | -| `--isolation` | | 容器隔离技术 | -| `--label` | | 设置图像的元数据 | -| `--memory`,`-m` | | 内存限制 | -| `--memory-swap` | | 交换限制等于内存加交换:"-1",用于启用无限制交换 | -| `--network` | | [**原料检修 1.25+**](https://docs.docker.com/engine/api/v1.25/) 在构建期间设置 RUN 指令的网络模式 | -| `--no-cache` | | 构建映像时不要使用缓存 | -| `--output`,`-o` | | [**1.40+ 原料药**](https://docs.docker.com/engine/api/v1.40/) 输出目标(格式:类型=本地,dest=路径) | -| `--platform` | | [**产品信息 1.38+**](https://docs.docker.com/engine/api/v1.38/) 如果服务器支持多平台,则设置平台 | -| `--progress` | `auto` | 设置进度输出类型(自动、普通、tty)。使用普通版显示容器输出 | -| `--pull` | | 始终尝试拉取较新版本的映像 | -| `--quiet`,`-q` | | 成功时禁止显示生成输出并打印图像 ID | -| `--rm` | `true` | **成功生成后删除中间容器** | -| `--secret` | | [**原料检修 1.39+**](https://docs.docker.com/engine/api/v1.39/) 要向构建版本公开的秘密文件(仅当启用 BuildKit 时):id=mysecret,src=/local/secret | -| `--security-opt` | | 安全选项 | -| `--shm-size` | | /dev/shm 的大小 | +| 名称与速记 | 默认 | 描述 | +| ------------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `--add-host` | | 添加自定义主机到 IP 的映射 (host:ip) | +| `--build-arg` | | 设置构建时变量 | +| `--cache-from` | | 要考虑用作缓存源的图像 | +| `--cgroup-parent` | | 容器的可选父 cgroup | +| `--compress` | | 使用 gzip 压缩构建上下文 | +| `--cpu-period` | | 限制 CPU CFS(完全公平的调度程序)期限 | +| `--cpu-quota` | | 限制 CPU CFS(完全公平的调度程序)配额 | +| `--cpu-shares`,`-c` | | CPU 份额(相对权重) | +| `--cpuset-cpus` | | 允许执行的 CPU (0-3, 0,1) | +| `--cpuset-mems` | | 允许执行的 MEM (0-3, 0,1) | +| `--disable-content-trust` | `true` | 跳过镜像验证 | +| `--file`,`-f` | | Dockerfile 的名称(默认值为"PATH/Dockerfile") | +| `--force-rm` | | **始终删除中间容器** | +| `--iidfile` | | 将映像 ID 写入文件 | +| `--isolation` | | 容器隔离技术 | +| `--label` | | 设置图像的元数据 | +| `--memory`,`-m` | | 内存限制 | +| `--memory-swap` | | 交换限制等于内存加交换:"-1",用于启用无限制交换 | +| `--network` | | [**原料检修 1.25+**](https://docs.docker.com/engine/api/v1.25/) 在构建期间设置 RUN 指令的网络模式 | +| `--no-cache` | | 构建映像时不要使用缓存 | +| `--output`,`-o` | | [**1.40+ 原料药**](https://docs.docker.com/engine/api/v1.40/) 输出目标(格式:类型=本地,dest=路径) | +| `--platform` | | [**产品信息 1.38+**](https://docs.docker.com/engine/api/v1.38/) 如果服务器支持多平台,则设置平台 | +| `--progress` | `auto` | 设置进度输出类型(自动、普通、tty)。使用普通版显示容器输出 | +| `--pull` | | 始终尝试拉取较新版本的映像 | +| `--quiet`,`-q` | | 成功时禁止显示生成输出并打印图像 ID | +| `--rm` | `true` | **成功生成后删除中间容器** | +| `--secret` | | [**原料检修 1.39+**](https://docs.docker.com/engine/api/v1.39/) 要向构建版本公开的秘密文件(仅当启用 BuildKit 时):id=mysecret,src=/local/secret | +| `--security-opt` | | 安全选项 | +| `--shm-size` | | /dev/shm 的大小 | | `--squash` | | [**实验性(守护进程)**](https://docs.docker.com/engine/reference/commandline/dockerd/#daemon-configuration-file)[**原料检修 1.25+**](https://docs.docker.com/engine/api/v1.25/) 将新构建的图层压缩为单个新图层 | -| `--ssh` | | [**原料检修 1.39+**](https://docs.docker.com/engine/api/v1.39/) 要向构建版本公开的 SSH 代理套接字或密钥(仅当启用了 BuildKit 时)(格式:default\|[=\|[,]]) | -| `--stream` | | 流附加到服务器以协商生成上下文 | -| `--tag`,`-t` | | 名称和标签(可选)采用"名称:标签"格式 | -| `--target` | | 设置要生成的目标生成阶段。 | -| `--ulimit` | | 乌利米特选项 | +| `--ssh` | | [**原料检修 1.39+**](https://docs.docker.com/engine/api/v1.39/) 要向构建版本公开的 SSH 代理套接字或密钥(仅当启用了 BuildKit 时)(格式:default\|[=\|[,]]) | +| `--stream` | | 流附加到服务器以协商生成上下文 | +| `--tag`,`-t` | | 名称和标签(可选)采用"名称:标签"格式 | +| `--target` | | 设置要生成的目标生成阶段。 | +| `--ulimit` | | 乌利米特选项 | > **特别注意**: -> +> > 不要将 根目录 用作生成上下文的 , 因为这会导致生成将硬盘驱动器的全部内容传输到 Docker 守护程序。 -> +> > 推荐作法:在空目录中放Dockerfile文件,把需要的文件全部复制到Dockerfile目录或子目录中。 ## 构建环境 @@ -199,7 +199,7 @@ Options => => sha256:a1801b843b1bfaf77c501e7a6d3f709401a1e0c83863037fa3aab063a7fdb9dc 529B / 529B 0.0s => => sha256:5d0da3dc976460b72c77d94c8a1ad043720b0416bfc16c52c45d4847e53fadb6 2.14kB / 2.14kB 0.0s => => extracting sha256:a1d0c75327776413fa0db9ed3adcdbadedc95a662eb1d360dad82bb913f8a1d1 - + # 构建第二阶段 # 执行命令 => [2/2] ADD ./project/ /usr/local/app/ 3.6s @@ -216,18 +216,18 @@ Options ## Dockerfile文件 ->Dockerfile是用来定制镜像的文本文件,内容为构Docker建镜像的一条条指令,由Docker build 命令使用,构建Docker镜像。 +> Dockerfile是用来定制镜像的文本文件,内容为构Docker建镜像的一条条指令,由Docker build 命令使用,构建Docker镜像。 ## 文件组成(注释与指令) > \# 开头的为注释行 -> +> > INSTRUCTION arguments -> +> > 通常包含4部分: -> +> > 基础镜像信息、维护者信息、 镜像操作指令和容器启动时执行指令,新版“维护者信息”放在LABLE中指定 -> +> > 执行:按照Dockerfile指令顺序,从上到下依次执行。 ```dockerfile @@ -245,9 +245,9 @@ ADD ./project/ /usr/local/$file/ + FROM > FROM 指令必须是非注释的第一个指令(新版AGU指令可值于前面), 可以使用 “ AS 别名” 提供别名,在路另外使用( --from=别名)。 -> +> > 指定了基础镜像,后面的所有指令都是运行在该基础镜像环境上的。 -> +> > 如果不以任何镜像为基础,那么写法为:FROM scratch ```dockerfile @@ -271,7 +271,7 @@ FROM mcr.microsoft.com/dotnet/sdk:2.1 AS build + RUN > RUN指令在**docker build** 时运行,用于执行命令并创建新的镜像层,通常用于安装软件包。 -> +> > 可以在Docker build输出中看到执行详情 ```shell @@ -289,8 +289,7 @@ exec形式可以避免 shell 字符串冗余,以及使用不包含指定 shell # 该指令将在当前镜像之上的新层中执行任何命令并提交结果。生成的已提交映像将用于下一步中。 ``` -``` shell - +```shell # 示例 FROM busybox WORKDIR /app @@ -304,7 +303,6 @@ RUN ["/bin/sh", "-c", "echo exec方式执行后写入的内容 > demo2.txt"] # pwd 查看当前目录 # ls 命令查看是否有demo.txt demo2.txt文件 # cat demo.txt 查看内容 - ``` + CMD diff --git a/Build/最佳实践.md b/Build/最佳实践.md index b7d3b75..258741c 100644 --- a/Build/最佳实践.md +++ b/Build/最佳实践.md @@ -1,272 +1,2 @@ -# Docker buuild 最佳实践 - -> [官网文档](https://docs.docker.com/develop/develop-images/dockerfile_best-practices/) - -## 镜像构建流程 - -![构建镜像](images/DockerBuild.png) - -## 构建镜像的三种方法 - -+ 基于现有容器,使用 `docker commit` 命令构建 - - ``` shell - # 1 创建容器,执行相关操作,之后退出 - docker run -it ubuntu:18.04 /bin/bash - docker exec -it xxxxxxxx - - # 2 基于该容器使用构建新的镜像 - docker commit -m "new_image" -a "cnLinuxer" b66 myubuntu:v1 - - 参数解释: - -m选项指定了新镜像的提交信息 - -a标注作者信息 - b66是容器ID - myubuntu:v1是指定的新镜像名称。 - - # 3 使用新镜像 - docker images - docker run -it ubuntu:18.04 /bin/bash - ``` - -+ 基于本地模板,使用 `docker import ` 导入 - - ``` shell - # 1 将现有容器,导出为模板文件 - docker export b66 > /root/myubuntu.tar - - # 2 模板文件导入为镜像 - docker import /root/myubuntu.tar ruyu/ubuntu:18.04 - - # 3 使用新镜像 - docker images - docker run xxxx - ``` - -+ 基于Dockerfile文件,使用 `docker build` 构建 - - ```tex - # 1 编写Dockerfile文件 - - 文件内容示例 - - #指定父镜像 - FROM centos:7.5.1804 - - #指定维护者信息 - MAINTAINER mynginx - - #将本地nginx软件包上传至容器/usr/local/下 - ADD nginx-1.11.1.tar.gz /usr/local - - #yum安装依赖软件 - RUN yum -y install vim wget gcc gcc-c++ make openssl-devel pcre-devel - - #进入nginx工作目录 - WORKDIR /usr/local/nginx-1.11.1/ - - #编译并安装nginx服务 - RUN ./configure --prefix=/usr/local/nginx && make && make install - - #关闭nginx后台运行 - RUN echo "daemon off;" >> /usr/local/nginx/conf/nginx.conf - - #添加nginx的环境变量 - ENV PATH /usr/local/nginx/sbin:$PATH - - #将容器的80端口映射出来 - EXPOSE 80 - - #执行nginx命令,启动nginx - CMD ["nginx"] - - # 2 docker build 构建镜像 - docker build -f Dockerfile -t mynginx:v1 . - ``` - -## 构建过程(docker build -f Dockerfile -t name .) - -> docker是c/s架构,c为docker cli,s为驻守服务进程。 -> docker build 构建镜像时,cli只负责把命令和相文件传递给服务进程,服务程序接收命令和文件、创建构建上下文、执行构建并产出镜像。 - -![build](images/build2.png) - -## docker build 命令 - -```shell -# docker build 详解 -# 语法 -docker build [OPTIONS] PATH|URL|本地文本文件 - -# 参数可以是三种资源:Git存储库、tar压缩包和文本文件 -git库示: -示例:docker build https://github.com/docker/rootfs.git#container:docker - -Build Syntax Suffix Commit Used Build Context Used -myrepo.git refs/heads/master / -myrepo.git#mytag refs/tags/mytag / -myrepo.git#mybranch refs/heads/mybranch / -myrepo.git#pull/42/head refs/pull/42/head / -myrepo.git#:myfolder refs/heads/master /myfolder -myrepo.git#master:myfolder refs/heads/master /myfolder -myrepo.git#mytag:myfolder refs/tags/mytag /myfolder -myrepo.git#mybranch:myfolder refs/heads/mybranch /myfolder - -tar包: -示例:docker build http://server/context.tar.gz - -文件文件: -Powershell示例:Get-Content Dockerfile | docker build - -``` - - - -Options - -| 名称与速记 | 默认 | 描述 | -| ------------------------- | ------ | ------------------------------------------------------------ | -| `--add-host` | | 添加自定义主机到 IP 的映射 (host:ip) | -| `--build-arg` | | 设置构建时变量 | -| `--cache-from` | | 要考虑用作缓存源的图像 | -| `--cgroup-parent` | | 容器的可选父 cgroup | -| `--compress` | | 使用 gzip 压缩构建上下文 | -| `--cpu-period` | | 限制 CPU CFS(完全公平的调度程序)期限 | -| `--cpu-quota` | | 限制 CPU CFS(完全公平的调度程序)配额 | -| `--cpu-shares`,`-c` | | CPU 份额(相对权重) | -| `--cpuset-cpus` | | 允许执行的 CPU (0-3, 0,1) | -| `--cpuset-mems` | | 允许执行的 MEM (0-3, 0,1) | -| `--disable-content-trust` | `true` | 跳过镜像验证 | -| `--file`,`-f` | | Dockerfile 的名称(默认值为"PATH/Dockerfile") | -| `--force-rm` | | **始终删除中间容器** | -| `--iidfile` | | 将映像 ID 写入文件 | -| `--isolation` | | 容器隔离技术 | -| `--label` | | 设置图像的元数据 | -| `--memory`,`-m` | | 内存限制 | -| `--memory-swap` | | 交换限制等于内存加交换:"-1",用于启用无限制交换 | -| `--network` | | [**原料检修 1.25+**](https://docs.docker.com/engine/api/v1.25/) 在构建期间设置 RUN 指令的网络模式 | -| `--no-cache` | | 构建映像时不要使用缓存 | -| `--output`,`-o` | | [**1.40+ 原料药**](https://docs.docker.com/engine/api/v1.40/) 输出目标(格式:类型=本地,dest=路径) | -| `--platform` | | [**产品信息 1.38+**](https://docs.docker.com/engine/api/v1.38/) 如果服务器支持多平台,则设置平台 | -| `--progress` | `auto` | 设置进度输出类型(自动、普通、tty)。使用普通版显示容器输出 | -| `--pull` | | 始终尝试拉取较新版本的映像 | -| `--quiet`,`-q` | | 成功时禁止显示生成输出并打印图像 ID | -| `--rm` | `true` | **成功生成后删除中间容器** | -| `--secret` | | [**原料检修 1.39+**](https://docs.docker.com/engine/api/v1.39/) 要向构建版本公开的秘密文件(仅当启用 BuildKit 时):id=mysecret,src=/local/secret | -| `--security-opt` | | 安全选项 | -| `--shm-size` | | /dev/shm 的大小 | -| `--squash` | | [**实验性(守护进程)**](https://docs.docker.com/engine/reference/commandline/dockerd/#daemon-configuration-file)[**原料检修 1.25+**](https://docs.docker.com/engine/api/v1.25/) 将新构建的图层压缩为单个新图层 | -| `--ssh` | | [**原料检修 1.39+**](https://docs.docker.com/engine/api/v1.39/) 要向构建版本公开的 SSH 代理套接字或密钥(仅当启用了 BuildKit 时)(格式:default\|[=\|[,]]) | -| `--stream` | | 流附加到服务器以协商生成上下文 | -| `--tag`,`-t` | | 名称和标签(可选)采用"名称:标签"格式 | -| `--target` | | 设置要生成的目标生成阶段。 | -| `--ulimit` | | 乌利米特选项 | - -> **特别注意**: -> -> 不要将 根目录 用作生成上下文的 , 因为这会导致生成将硬盘驱动器的全部内容传输到 Docker 守护程序。 -> -> 推荐作法:在空目录中放Dockerfile文件,把需要的文件全部复制到Dockerfile目录或子目录中。 - -## 构建环境 - -1. Dockerfile中所用的所有文件一定要和Dockerfile文件在同一级父目录下,可以为Dockerfile父目录的子目录 -2. Dockerfile中相对路径默认都是Dockerfile所在的目录 -3. 因Docker构建的分层构建,联合挂载的特性,Dockerfile中,每一条指令被视为一层,尽量一行写多个指令(&&连接 \用来分行)。 -4. 约定:指令大写,内容小写 - -```shell -# 构建流程 -# docke build . 默认构建 - -# 输出,#行为手动添加的说明 - # 加载 Dockerfile 文件 - => [internal] load build definition from Dockerfile 0.0s - # 把 dockerfile 文件传输到Docker守护进程服务,大小为 149B - => => transferring dockerfile: 149B 0.0s - # 加载.dockerignore(Dockerfile目录下,不上传文件配置文件) 文件 - => [internal] load .dockerignore 0.0s - # 传输构建上下文(会传Docfile目录及子目录中除.dockerignore配置外的所有文件及文件夹) - => => transferring context: 2B 0.0s - # 从基础镜像加载元数据 - => [internal] load metadata for docker.io/library/centos:latest 3.0s - # 加载构建上下文 - => [internal] load build context 0.0s - # 传输构建上下文 - => => transferring context: 125B 0.0s - # 构建第一阶段:下载与验证基础镜像 - => [1/2] FROM docker.io/library/centos@sha256:a27fd8080b517143cbbbab9dfb7c8571c40d67d534bbdee55bd6c473f432b177 10.8s - => => resolve docker.io/library/centos@sha256:a27fd8080b517143cbbbab9dfb7c8571c40d67d534bbdee55bd6c473f432b177 0.0s - => => sha256:a1d0c75327776413fa0db9ed3adcdbadedc95a662eb1d360dad82bb913f8a1d1 83.52MB / 83.52MB 7.0s - => => sha256:a27fd8080b517143cbbbab9dfb7c8571c40d67d534bbdee55bd6c473f432b177 762B / 762B 0.0s - => => sha256:a1801b843b1bfaf77c501e7a6d3f709401a1e0c83863037fa3aab063a7fdb9dc 529B / 529B 0.0s - => => sha256:5d0da3dc976460b72c77d94c8a1ad043720b0416bfc16c52c45d4847e53fadb6 2.14kB / 2.14kB 0.0s - => => extracting sha256:a1d0c75327776413fa0db9ed3adcdbadedc95a662eb1d360dad82bb913f8a1d1 - - # 构建第二阶段 - # 执行命令 - => [2/2] ADD ./project/ /usr/local/app/ 3.6s - # 导出镜像 - => exporting to image 0.3s - # 导出镜像层 - => => exporting layers 0.0s - # 写入镜像文件 - => => writing image sha256:9f21ab65d16f8a8599aec0ba0c1b31b806ae675be2701a6d671ec42887ab5e56 - 0.0s - # 给镜像打标签 - => => naming to docker.io/library/mycentos:study1 0.0s -``` - -## Dockerfile文件 - ->Dockerfile是用来定制镜像的文本文件,内容为构Docker建镜像的一条条指令,由Docker build 命令使用,构建Docker镜像。 - -## 文件组成 - -> \# 开头的为注释行 -> -> 通常包含4部分:基础镜像信息、维护者信息、 镜像操作指令和容器启动时执行指令,新版“维护者信息”放在LABLE中指定 -> -> 执行:按照Dockerfile指令顺序,从上到下依次执行。 - -```dockerfile -# 这是一个示例Dockerfile文件 -FROM busybox -MAINTAINER username -ENV file=app -ADD ./project/ /usr/local/$file/ -``` - -## 命令 - -+ FROM - -> FROM 指令必须是非注释的第一个指令(新版AGU指令可值于前面), 可以使用 “ AS 别名” 提供别名,在路另外使用( --from=别名)。 -> -> 指定了基础镜像,后面的所有指令都是运行在该基础镜像环境上的。 -> -> 如果不以任何镜像为基础,那么写法为:FROM scratch - -```dockerfile -#语法格式 -FROM -FROM : -FROM @ - -# 特殊例子 -#不以任何镜像为基础(内置空白镜像) -FROM scratch - -# busybox是一个软件工具箱镜像,集成了linux中几百个常用的linux命令以及工具。 -# 大小只有1.2M,适合用来测试 -FROM busybox - -# .net core sdk -FROM mcr.microsoft.com/dotnet/sdk:2.1 AS build -``` - -## run - -``` shell -# /bin/sh -c 参数 --c 参数:把后面的命令(字符串)当成一个整体来执行 -``` +最佳实践 +====== \ No newline at end of file