# Docker 快速入门


<!--more-->


## 1. Docker历史

2010年，几个的年轻人，就在美国的旧金山成立了一家公司 dotcloud。做一些Paas平台的创业公司！从事LXC（Linux Container容器）有关的容器技术！Linux Container容器是一种内核虚拟化技术，可以提供轻量级的虚拟化，以便隔离进程和资源。他们将自己的技术（容器化技术）命名就是 Docker。Docker刚刚延生的时候，没有引起行业的注意！虽然获得了创业孵化器(Y Combinator)的支持、也获得过一些融资，但随着IT巨头们(微软、谷歌、亚马逊等厂商)也进入PaaS凭他，dotCloud举步维艰，眼看就活不下去！

2013年，dotCloud的创始人，28岁的Solomon Hykes做了一个艰难的决定，将dotCloud的核心引擎开源，这项核心引擎技术能够将Linux容器中的应用程序、代码打包，轻松的在服务器之间进行迁移。这个基于LXC技术的核心管理引擎开源后，让全世界的技术人员感到惊艳。感叹这一切太方便了！！越来越多的人发现docker的优点！火了。Docker每个月都会更新一个版本！2014年6月9日，Docker1.0发布！1.0版本的发布，标志着docker平台已经足够成熟稳定，并可以被应用到生产环境。

docker为什么这么火？因为docker十分的轻巧！在容器技术出来之前，我们都是使用虚拟机技术！在window中装一个VMware，通过这个软件我们可以虚拟出来一台或者多台电脑！笨重！虚拟机也属于虚拟化技术，Docker容器技术，也是一种虚拟化技术！

## 2. Docker版本

docker 从 17.03 版本之后分为 CE（Community Edition: 社区版） 和 EE（Enterprise Edition: 企业版），本教程使用社区版(CE)。

## 3. DevOps(开发,运维)

- 开发环境Windows，最后发布到Linux！

  传统：开发jar，运维来做！

  现在：开发打包部署上线，一套流程做完！ 

- 应用更快速的交付和部署

  传统：帮助文档，安装程序。

  Docker：打包镜像发布测试一键运行。

- 更便捷的升级和扩缩容

  使用了 Docker之后，我们部署应用就和搭积木一样

  项目打包为一个镜像，扩展服务器A！服务器B

- 更简单的系统运维

  在容器化之后，我们的开发，测试环境都是高度一致的 

  更高效的计算资源利用

Docker是内核级别的虚拟化，可以在一个物理机上可以运行很多的容器实例！服务器的性能可以被压榨到极致

## 4. 什么是Docker

当人们说“Docker”时，他们通常是指 Docker Engine，它是一个客户端 - 服务器应用程序，由 Docker守护进程、一个REST API指定与守护进程交互的接口、和一个命令行接口（CLI）与守护进程通信（通过封装REST API）。Docker Engine 从 CLI 中接受docker 命令，例如 docker run 、docker ps 来列出正在运行的容器、docker images 来列出镜像，等等。

- docker是一个软件，可以运行在window、linux、mac等各种操作系统上。

- docker 是一个开源的应用容器引擎，基于Go 语言开发并遵从 Apache2.0 协议开源，项目代码托管在github上进行维护

- docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中，然后发布到任何流行的 Linux 机器上。

- 容器是完全使用沙箱机制，相互之间不会有任何接口,更重要的是容器性能开销极低。

## 5. Docker基本组成

docker主机(Host)：安装了Docker程序的机器（Docker直接安装在操作系统之上）；

docker仓库(Registry)：用来保存各种打包好的软件镜像；仓库分为公有仓库和私有仓库。(很类似maven) 

docker镜像(Images)：软件打包好的镜像；放在docker仓库中；

docker容器(Container)：镜像启动后的实例称为一个容器；容器是独立运行的一个或一组应用

## 6. Docker与操作系统

docker是一种轻量级的虚拟化方式。与传统操作系统技术的特性比较如下表：![img](https://img.zhaojq.top/20260727091848692.png "img")

传统的虚拟机方式提供的是相对封闭的隔离。Docker利用Linux系统上的多种防护技术实现了严格的隔离可靠性，并且可以整合众多安全工具。从 1.3.0版本开始，docker重点改善了容器的安全控制和镜像的安全机制， 极大提高了使用docker的安全性

## 7. Docker安装

当我们安装 Docker 的时候，会涉及两个主要组件：

- Docker CLI：客户端

- Docker daemon：有时也被称为“服务端”或者“引擎”

硬件安装要求:![img_1](https://img.zhaojq.top/20260727091848849.png "img_1")

由于我用Ubuntu ,这里示范Ubuntu 安装过程:

```sh
# step 1: 安装必要的一些系统工具

sudo apt-get update

sudo apt-get -y install apt-transport-https ca-certificates curl software-properties-common

# step 2: 安装GPG证书

curl -fsSL http://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo apt-key add -

# Step 3: 写入软件源信息

sudo add-apt-repository "deb [arch=amd64] http://mirrors.aliyun.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable"

# Step 4: 更新并安装 Docker-CE

sudo apt-get -y update

sudo apt-get -y install docker-ce

# 安装指定版本的Docker-CE:

# Step 1: 查找Docker-CE的版本:

# apt-cache madison docker-ce

#  docker-ce | 17.03.1~ce-0~ubuntu-xenial | http://mirrors.aliyun.com/docker-ce/linux/ubuntu xenial/stable amd64 Packages

#  docker-ce | 17.03.0~ce-0~ubuntu-xenial | http://mirrors.aliyun.com/docker-ce/linux/ubuntu xenial/stable amd64 Packages

# Step 2: 安装指定版本的Docker-CE: (VERSION 例如上面的 17.03.1~ce-0~ubuntu-xenial)

# sudo apt-get -y install docker-ce=[VERSION]
```

安装成功查看版本:

```sh
docker -v 
docker version 
docker info
```

## 8. Docker命令分类

- Docker环境信息 — docker [info|version]

- 容器生命周期管理 — docker [create|exec|run|start|stop|restart|kill|rm|pause|unpause]

- 容器操作管理 — docker [ps|inspect|top|attach|wait|export|port|rename|stat]

- 容器rootfs命令 — docker [commit|cp|diff]

- 镜像仓库 — docker [login|pull|push|search]

- 本地镜像管理 — docker [build|images|rmi|tag|save|import|load]

- 容器资源管理 — docker [volume|network]

- 系统日志信息 — docker [events|history|logs]

> 官网: https://docs.docker.com/engine/reference/run/

![img_2](https://img.zhaojq.top/20260727091849083.png "img_2")

## 9. Docker镜像(image)

docker hub类似maven远程仓库地址: https://hub.docker.com/

作为一名研发人员，则可以将镜像理解为类（Class）。是一个应用程序。

首先需要先从镜像仓库服务中拉取镜像。常见的镜像仓库服务是 Docker Hub，但是也存在其他镜像仓库服务。

拉取操作会将镜像下载到本地 Docker 主机，可以使用该镜像启动一个或者多个容器。

镜像由多个层组成，每层叠加之后，从外部看来就如一个独立的对象。镜像内部是一个精简的操作系统（OS），同时还包含应用运行所必须的文件和依赖包。

因为容器的设计初衷就是快速和小巧，所以镜像通常都比较小。

前面多次提到镜像就像停止运行的容器（类）。实际上，可以停止某个容器的运行，并从中创建新的镜像。

在该前提下，镜像可以理解为一种**构建时**（build-time）结构，而容器可以理解为一种**运行时**（run-time）结构，如下图所示。![img_3](https://img.zhaojq.top/20260727091849274.png "img_3")

## 10. Docker镜像常用命令

- pull

  - 下载镜像的命令。镜像从远程镜像仓库服务的仓库中下载。默认情况下，镜像会从 Docker Hub 的仓库中拉取。

  - 通过下载过程，可以看到，一个镜像一般是由多个层组成，类似 f7e2b70d04ae 这样的串表示层的唯一 ID。

  ```
  问题一：小伙伴们可能会想到，如果多个不同的镜像中，同时包含了同一个层,这样重复下载，岂不是导
  致了存储空间的浪费么?
  实际上，Docker 并不会这么傻会去下载重复的层,Docker 在下载之前，会去检测本地是否会有同样 ID
  的层，如果本地已经存在了，就直接使用本地的就好了。
  
  问题二：另一个问题，不同仓库中，可能也会存在镜像重名的情况发生, 这种情况咋办？
  从严格意义上讲，我们在使用 pull命令时，还需要在镜像前面指定仓库地址(Registry), 如果不指定，则
  Docker 会使用您默认配置的仓库地址。例如上面，由于我配置的是国内 docker.io的仓库地址，我在
  pull 的时候，docker 会默认为我加上 docker.io/library 的前缀。
  
  例如：当我执行 docker pull tomcat:9.0.20-jre8命令时，实际上相当于 docker pull
  docker.io/tomcat:9.0.20-jre8，如果您未自定义配置仓库，则默认在下载的时候，会在镜像前面加上
  DockerHub 的地址。Docker 通过前缀地址的不同，来保证不同仓库中，重名镜像的唯一性。
  ```

  ```
  常用参数
  -a, --all-tags=true|false : 是否获取仓库中所有镜像，默认为否；
  --disable-content-trust : 跳过镜像内容的校验，默认为 true;
  ```

- images

  ```
  #列出本机已有的镜像
  docker images 
  docker image ls
  
  各个选项说明:
  REPOSITORY：表示镜像的仓库源
  TAG：镜像的标签
  IMAGE ID：镜像ID
  CREATED：镜像创建时间
  SIZE：镜像大小
  ```

- save

  **一个镜像**

  ```sh
  mkdir -p /data 
  cd /data 
  docker save tomcat:9.0.20-jre8-alpine -o tomcat9.tar 
  docker save tomcat:9.0.20-jre8-slim > tomcat9.slim.tar
  
  -o :输出到的文件
  ```

  **多个镜像**

  > 推荐开发岗的小伙伴使用idea开发工具中的列编辑模式制作docker save命令

  ```sh
  mkdir -p /data 
  cd /data 
  docker save \ 
  ubuntu:20.04 \ 
  alpine:3.12.1 \ 
  debian:10.6-slim \ 
  centos:7.8.2003 \ 
  -o linux.tar 
  
  docker save \ 
  tomcat:9.0.20-jre8-alpine \ 
  tomcat:9.0.20-jre8-slim \ 
  tomcat:9.0.20-jre8 \ 
  -o tomcat9.0.20.tar
  ```

- load

  ```sh
  mkdir -p /data 
  cd /data 
  
  docker load -i linux.tar 
  docker load < tomcat9.0.20.tar
  
  常用参数
  --input , -i : 指定导入的文件。
  --quiet , -q : 精简输出信息。
  ```

- search

  > 不推荐使用search命令查找镜像，不够直观。

  ```sh
  docker search tomcat
  
  常用参数
  -f, --filter filter : 过滤输出的内容；
  --limit int ：指定搜索内容展示个数; --no-index : 不截断输出内容；
  --no-trunc ：不截断输出内容；
  ```

- inspect

  通过 docker inspect 命令，我们可以获取镜像的详细信息，其中，包括创建者，各层的数字摘要等。

  docker inspect 返回的是 JSON格式的信息，如果您想获取其中指定的一项内容，可以通过 -f 来指定，如获取镜像大小

  ```sh
  docker inspect tomcat:9.0.20-jre8-alpine 
  docker inspect -f {{".Size"}} tomcat:9.0.20-jre8-alpine
  ```

- history

  从前面的命令中，我们了解到，一个镜像是由多个层组成的，那么，我们要如何知道各个层的具体内容呢？

  通过 docker history命令，可以列出各个层的创建信息，例如：查看 tomcat:9.0.20-jre8-alpine的各层信息

  ```sh
  docker history tomcat:9.0.20-jre8-alpine
  ```

- tag

  标记本地镜像，将其归入某一仓库

  ```sh
  docker tag tomcat:9.0.20-jre8-alpine qiang/tomcat:9
  ```

- rmi

  通过如下两个都可以删除镜像：

  ```sh
  docker rmi tomcat:9.0.20-jre8-alpine
  docker image rm tomcat:9.0.20-jre8-alpine
  
  常用参数
  -f, -force : 强制删除镜像，即便有容器引用该镜像；
  -no-prune : 不要删除未带标签的父镜像；
  ```

  **通过ID删除镜像:** 除了通过标签名称来删除镜像，我们还可以通过制定镜像 ID, 来删除镜像。一旦制定了通过 ID 来删除镜像，它会先尝试删除所有指向该镜像的标签，然后在删除镜像本身。

  ```sh
  docker rmi ee7cbd482336
  ```

  ```
  推荐通过image的名称删除镜像
  image的ID在终端长度未完全显示，ID值会出现重复
  ```

  **删除镜像的限制**: 删除镜像很简单，但也不是我们何时何地都能删除的，它存在一些限制条件。当通过该镜像创建的容器未被销毁时，镜像是无法被删除的

  ```
  1. 先删除引用这个镜像的容器；
  2. 再删除这个镜像；
  ```

  **清理镜像**: 我们在使用 Docker 一段时间后，系统一般都会残存一些临时的、没有被使用的镜像文件，可以通过以下命令进行清理。执行完命令后，还是告诉我们释放了多少存储空间！

  ```sh
  docker image prune
  常用参数
  -a, --all : 删除所有没有用的镜像，而不仅仅是临时文件；
  -f, --force ：强制删除镜像文件，无需弹出提示确认；
  ```

## 11. Docker容器(container)

容器是镜像的运行时实例。正如从虚拟机模板上启动 VM 一样，用户也同样可以从单个镜像上启动一个或多个容器。虚拟机和容器最大的区别是容器更快并且更轻量级——与虚拟机运行在完整的操作系统之上相比，容器会共享其所在主机的操作系统/内核。下图为使用单个 Docker镜像启动多个容器的示意图。

![img_4](https://img.zhaojq.top/20260727091849436.png "img_4")

Docker容器类似于一个轻量级的沙箱，Docker利用容器来运行和隔离应用. **容器是镜像的一个运行实例**。

```
可以将其启动、开始、停止、删除，而这些容器都是彼此相互隔离的、互不可见的。
可以把容器看做是一个简易版的Linux系统环境（包括root用户权限、进程空间、用户空间和网络空间
等）以及运行在其中的应用程序打包而成的盒子
```

容器是基于镜像启动起来的，容 器中可以运行一个或多个进程。

镜像是Docker生命周期 中的构建或打包阶段，而容器则是启动或执行阶段。

镜像自身是只读的。容器从镜像启动的时候，会在镜像的最上层创建一个可写层。

## 12. Docker容器常用命令

- 新建并启动容器

  ```sh
  docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
  
  例 :
  docker run -it --rm -p 8080:8080 tomcat:9.0.20-jre8-alpine
  常用参数:
  -d, --detach=false: 后台运行容器，并返回容器ID
  -i, --interactive=false: 以交互模式运行容器，通常与 -t 同时使用
  -t, --tty=false: 为容器重新分配一个伪输入终端，通常与 -i 同时使用
  -P, --publish-all=false: 随机端口映射，容器内部端口随机映射到主机的端口。不推荐各位小伙伴
  使用该参数
  -p, --publish=[]: 指定端口映射，格式为：主机(宿主)端口:容器端口，推荐各位小伙伴们使用
  --name="nginx-lb": 为容器指定一个名称
  -h , --hostname="laosiji": 指定容器的hostname
  -e , --env=[]: 设置环境变量，容器中可以使用该环境变量
  --net="bridge": 指定容器的网络连接类型，支持 bridge/host/none/container: 四种类型
  --link=[]: 添加链接到另一个容器；不推荐各位小伙伴使用该参数
  -v, --volume : 绑定一个卷
  --privileged=false: 指定容器是否为特权容器，特权容器拥有所有的capabilities
  --restart=no：指定容器停止后的重启策略
      no：容器退出时不重启 
      on-failure：容器故障退出（返回值非零）时重启 
      always：容器退出时总是重启,推荐各位小伙伴们使用
      
  --rm=false: 指定容器停止后自动删除容器,不能以docker run -d启动的容器
  ```

- 容器日志

  ```sh
  docker logs [OPTIONS] CONTAINER
  
  例:
  docker run -itd --name tomcat9 -p 8080:8080 tomcat:9.0.20-jre8-alpine 
  docker logs -f tomcat9
  常用参数
  -f : 跟踪日志输出
  --tail :仅列出最新N条容器日志
  ```

- 删除容器

  **docker rm** **：**删除一个或多个容器。docker rm命令只能删除处于终止或退出状态的容器，并不能删除还处于运行状态的容器

  ```sh
  docker rm [OPTIONS] CONTAINER [CONTAINER...]
  
  例:
  docker run -itd --name tomcat9 -p 8080:8080 tomcat:9.0.20-jre8-alpine
  
  需要先停止运行中的容器再删除，否则无法删除容器 
  docker stop tomcat9 
  
  按照容器名称删除 
  docker rm tomcat9 
  
  按照容器ID删除 
  docker rm 8dd95a95e687
  
  常用参数
  -f :通过 SIGKILL 信号强制删除一个运行中的容器。
  -l :移除容器间的网络连接，而非容器本身。
  -v :删除与容器关联的卷。
  ```

- 列出容器

  ```sh
  docker ps [OPTIONS]
  
  例:
  docker run -itd --name tomcat9 -p 8080:8080 tomcat:9.0.20-jre8-alpine 
  
  查看运行中的容器 
  docker ps tomcat9 
  
  查看所有容器
  docker ps -a tomcat9
  
  常用参数
  -a :显示所有的容器，包括未运行的。
  -q :只显示容器编号。
  ```

  输出详情介绍：

  **CONTAINER ID:** 容器 ID。

  **IMAGE:** 使用的镜像。

  **COMMAND:** 启动容器时运行的命令。

  **CREATED:** 容器的创建时间。

  **STATUS:** 容器状态。

  ```
  状态有7种：
  created（已创建）
  restarting（重启中）
  running（运行中）
  removing（迁移中）
  paused（暂停）
  exited（停止）
  dead（死亡）
  ```

  **PORTS:** 容器的端口信息和使用的连接类型（tcp
p）。

  **NAMES:** 自动分配的容器名称。

  ```shell
  #实用技巧
  
  停止所有运行容器 
  docker stop $(docker ps -qa) 
  
  删除所有的容器 
  docker rm $(docker ps -aq) 
  docker rm $(docker stop $(docker ps -q)) 
  
  删除所有的镜像 
  docker rmi $(docker images -q)
  ```

- 创建容器

  ```sh
  #创建一个新的容器但不启动它。用法同 docker run命令
  docker create [OPTIONS] IMAGE [COMMAND] [ARG...]
  
  docker create -it --name tomcat9 -p 8080:8080 9.0.20-jre8-alpine
  ```

- 启动,重启,终止容器

  ```sh
  docker start :启动一个或多个已经被停止的容器
  docker stop :停止一个运行中的容器
  docker restart :重启容器
  
  docker start [OPTIONS] CONTAINER [CONTAINER...] 
  docker stop [OPTIONS] CONTAINER [CONTAINER...] 
  docker restart [OPTIONS] CONTAINER [CONTAINER...]
  
  例:
  docker start tomcat9 
  docker stop tomcat9 
  docker restart tomcat9
  ```

- 进入容器

  **docker exec** **：**在运行的容器中执行命令。早期有attach命令，对于阻塞命令会等待，所以不方便。在Docker 1.3.0后提供了exec 可以在容器内直接执行任意命令

  ```sh
  docker exec [OPTIONS] CONTAINER COMMAND [ARG...]
  
  例:
  有bash命令的linux系统：例如centos 
  docker run -it --name tomcat9.1 -p 8080:8080 tomcat:9.0.20-jre8-slim 
  docker exec -it tomcat9.1 /bin/bash
  
  没有bash命令的linux系统：例如alpine系统 
  docker run -it --name tomcat9.2 -p 8081:8080 tomcat:9.0.20-jre8-alpine 
  docker exec -it tomcat9.2 sh
  
  常用参数
  -i :即使没有附加也保持STDIN 打开
  -t :分配一个伪终端
  ```

- 查看容器

  **docker inspect :** 获取容器/镜像的元数据。

  ```sh
  docker inspect [OPTIONS] NAME|ID [NAME|ID...]
  
  例:
  docker run -it --name tomcat9 -p 8081:8080 tomcat:9.0.20-jre8-alpine
  docker inspect tomcat9
  
  常用参数
  -f :指定返回值的模板文件。
  -s :显示总的文件大小。
  --type :为指定类型返回JSON。
  ```

- 更新容器

  **docker update** :可以动态地更新容器配置。可以更新一个或多个容器配置。多个容器名称或ID之间使用空格分隔。但update命令不是很成熟，有很多配置项不能动态更新。推荐大家还是rm容器后，再重新run一个新的镜像

  ```sh
  docker update [OPTIONS] CONTAINER [CONTAINER...]
  
  例：
  docker run -it --name tomcat9 -p 8081:8080 tomcat:9.0.20-jre8-alpine 
  
  更新容器restart策略
  docker update --restart always tomcat9
  ```

- 杀掉容器

  ```sh
  docker kill [OPTIONS] CONTAINER [CONTAINER...]
  
  例:
  docker run -it --name tomcat9 -p 8081:8080 tomcat:9.0.20-jre8-alpine 
  docker kill tomcat9 
  docker ps 
  docker ps -a 
  docker start tomcat9
  
  常用参数
  -s :向容器发送一个信号
  ```

## 13. Docker常用命令汇总

![img_5](https://img.zhaojq.top/20260727091849636.png "img_5")

## 14. 常用软件安装示例

- 安装nginx

  ```sh
  # 拉取镜像
  docker pull nginx:1.19.3-alpine
  # 备份镜像
  docker save nginx:1.19.3-alpine -o nginx.1.19.3.alpine.tar
  # 导入镜像
  docker load -i nginx.1.19.3.alpine.tar
  
  # 运行镜像
  docker run -itd --name nginx -p 80:80 nginx:1.19.3-alpine
  进入容器 
  docker exec -it nginx sh 
  查看html目录 
  cd /usr/share/nginx/html 
  配置文件目录 
  cat /etc/nginx/nginx.conf
  ```

- 安装mysql

  ```sh
  docker pull mysql:5.7.31
  
  #学习docker run -e参数
  -e , --env=[]: 设置环境变量，容器中可以使用该环境变量
  官网中给出进入容器的第三种方式，前边我们学习了/bin/bash，sh
  向my.cnf文件中追加相关配置项
  
  docker run -itd --name mysql --restart always --privileged=true -p 3306:3306 -e MYSQL_ROOT_PASSWORD=admin mysql:5.7.31 --character-set-server=utf8 --collation-server=utf8_general_ci
  ```

  - **privileged**参数

    我们大多数熟悉 Unix 类系统的人，都习惯于通过使用 sudo 来随意提升自己的权限，成为 root 用户。我们在使用 Docker 容器的过程中知道，docker提供了一个 --privileged 的参数，其实它与随意使用sudo 有很大的不同，它可能会让你的应用程序面临不必要的风险，下面我们将向你展示这与以 root 身份运行的区别，以及特权的实际含义。

    ```
    作为 Root 运行
    Docker 允许在其宿主机上隔离一个进程、capabilities 和文件系统，但是大多数容器实际上都是默认以
    root 身份运行。这里我们拿 DockerHub 上几个比较流行的镜像来进行说明。
    
    避免以 root 运行
    虽然在容器内以 root 身份运行是很正常的，但如果你想加固容器的安全性，还是应该避免这样做。
    
    特权模式
    --privileged 可以不受限制地访问任何自己的系统调用。在正常的操作中，即使容器内有 root，
    Docker 也会限制容器的 Linux Capabilities 的，这种限制包括像 CAP_AUDIT_WRITE 这样的东西，
    它允许覆盖内核的审计日志--你的容器化工作负载很可能不需要这个 Capabilities。所以特权只应该在你
    真正需要它的特定设置中使用，简而言之，它给容器提供了几乎所有主机（作为root）可以做的事情的权
    限。
    本质上，它就是一个免费的通行证，可以逃避容器所包含的文件系统、进程、sockets 套接字等，当然
    它有特定的使用场景，比如在很多 CI/CD 系统中需要的 Docker IN Docker 模式（在 Docker 容器内部
    需要 Docker 守护进程），以及需要极端网络的地方。
    ```

  - 测试mysql

    ```sh
    进入容器：根据官网上的实例，使用bash命令进入容器
    docker exec -it mysql bash 
    
    登录mysql
    mysql -uroot -p 
    输入密码 admin 
    
    use mysql; 
    show databases; 
    
    退出mysql 
    exit 
    
    退出容器 
    exit
    ```

- 其他..................

