Docker常用操作帅哥专供版

没错,这里的帅哥指的就是我自己

  • Docker常用命令
Docker中文资源
Docker中文网站:https://www.docker-cn.com/
Docker安装手册:https://docs.docker-cn.com/engine/installation/
Docker常用教程: https://yeasy.gitbooks.io/docker_practice/content/image/pull.html

Docker 国内镜像
网易加速器:http://hub-mirror.c.163.com
官方中国加速器:https://registry.docker-cn.com
ustc的镜像:https://docker.mirrors.ustc.edu.cn


;查看版本号
docker --version
docker-compose --version
docker-machine --version


注意:
    直接进入镜像, 在镜像中的操作, 在镜像关闭之后,就会还原.
    如果想要做持久化,需要volume或者data container
    有一个我常用的解决方案,因为我是用docker模拟服务器的.
    所以我自己封装一个镜像, 装好所有想要的环境.而项目代码放在本机.
    在启动镜像的时候,将主机目录映射到容器中,但是这样还是会有其他问题.
    所以我建议, 以后还是要用volume或者data container 把产生的数据挂载到宿主机.
    或者直接commit产生一个新的镜像出来.
    volume的生存周期独立于容器,容器消亡,volume不会消亡.因此使用volume后,容器删除或重启后,数据却不会丢失
	ubuntu 镜像默认的 CMD 是 /bin/bash ,如果我们直接 docker run -it ubuntu 的话,会直接进入 bash 。
	我们 也可以在运行时指定运行别的命令,如 docker run -it ubuntu cat /etc/os-release 。
	这就是用 cat /etc/os-release 命令替换了默认的 /bin/bash 命令了,输出了系统版本信息。
    
错误收集:
    在今天的使用当中遇到一个问题.
    //下载官方镜像
    docker pull ubuntun
    //以后台方式运行Ubuntu
    docker run -d ubuntu 
    //此处正常返回了容器ID, 我当然认为运行成功了.
    17f3c98f79dc361ff88787b6bafdbc445ed0e8e7f1a689efb38874f623c54f3b
    //但是实际上通过docker ps 根本看不到当前有在运行,而且docker logs 什么也没有
    //docker ps -a 看到他的status 为exited状态, 即已退出,也就是说,该容器运行就马上退出了
    //百思不得其解
    c9720c00769a        ubuntu              "/bin/bash"         4 seconds ago       Exited (0) 5 seconds ago                                      pedantic_keldysh
    //后台发现Mac下需要一个-t参数就OK了
    //原因在于镜像里面指定的默认启动程序是bash,对于Linux下的shell程序来说,tty是必须的,
    //所以启动容器的时候要加上-t参数
    docker run -t -d ubuntu
    

  • 常用操作
# 删除所有容器
docker rm `docker ps -a -q`

# 停止所有容器
docker stop $(docker ps -q)

;查看镜像、容器、数据卷所占用的空间
docker system df

;目录映射
docker run -v /Users/liuhao/www:/usr/share/nginx/html -d nginx

;目录映射,:ro指定为只读
docker run --name nginx -v 主机目录:docker目录:ro -d nginx

;运行一个新容器,同时为它命名、端口映射、文件夹映射, 以redmine镜像为例
docker run --name redmine -p 9003:80 -p 9023:22 -d -v /var/redmine/files:/redmine/files -v /var/redmine/mysql:/var/lib/mysql sameersbn/redmine


  • 镜像操作
;从Docker Hub查找镜像
docker search 镜像名称

;下载(不指定tag版本号,默认下载最新的一个latest版本,镜像名为全名如:pangee/lnmp)
docker pull 仓库地址 镜像名:版本号
;根据匹配信息拉取docker镜像
docker pull ${name:version}

;登陆到一个Docker镜像仓库
docker login -u 用户名 -p 密码 仓库地址
登录和退出 如果未指定镜像仓库地址,默认为官方地址

;退出一个Docker镜像仓库
docker logout -u 用户名 -p 密码

;标记本地镜像,将其归入指定用户的一仓库
docker tag image:version username/name:tag

;标记本地镜像,归类到该用户的库中,同时重新命名,设置版本号
docker tag myubuntu:v1 chinaliuhan/myubuntu:v1
      
;上传镜像(需要先行登录)
docker push 镜像名称:版本号
docker push chinaliuhan/myachain:v2

;列出所有本地镜像
docker images
docker image ls 

;根据条件列出镜像
;如何没有${name}代表查看所有的镜像,有则代表条件搜索
docker images ${name}

;列出部分镜像
docker image ls  [镜像名]
        --digests   查看长ID, 删除镜像的时候可能会用得到
        -q 只显示ID

;删除镜像
docker image rm 镜像名/镜像ID/镜像长ID
    -f :强制删除;
    --no-prune :不移除该镜像的过程镜像,默认移除;

;删除镜像,在使用过程中发现和image rm有所不同
docker rmi 镜像名/镜像ID/镜像长ID
    -f :强制删除;
    --no-prune :不移除该镜像的过程镜像,默认移除;

;删除所有镜像
docker rmi $(docker images)

;显示镜像信息,可以使用--format='{{GO语言模板语法}}'做格式化
docker image inspect [--format='{{GO语言模板语法}}'] 镜像ID
  • 容器的使用
;显示所有docker客户端可用命令
docker

;显示所有在运行中的容器
docker ps
    -a 查看所有状态下的容器,默认为正在运行中的
    -q 只显示容器ID

;显示已终止的容器
docker container ls -a
    -q 只显示容器ID

;停止容器
docker stop 容器ID/容器名称

;删除容器(当我们创建一个容器时,及时失败也会存在,比如--name及时失败也会占用名字)
docker rm
    -f 发送 SIGKILL 信号给容器
    -i 移除容器间的网络连接,而非容器本身

;删除指定的容器
docker container rm
    -f 发送 SIGKILL 信号给容器
    -i 移除容器间的网络连接,而非容器本身
    
;清理所有处于终止状态的容器
docker container prune

;获取指定命令的详细帮助信息
docker 指定命令 --help

;创建一个新的容器并运行一个命令
;使用 docker run 命令来在指定容器内运行一个应用程序
;指定容器的时候,可以用容器名也可以用容器ID
;使用容器名称的时候,如果为非latest版本,必须指定tag
docker run
    -t: 在新容器内指定一个伪终端或终端(伪)tty。
    -i: 以交互模式运行容器,通常与 -t 同时使用;
    -e: 设置容器内的环境变量
    -d: 后台运行容器,并返回容器ID;
    -p: 将主机指定的端口的指定网络类型映射到容器:hostPort:containerPort,不指定主机端口则随机
    -v: 同时将主机的目录,映射到容器中hostPath:containerPath,本地文件不存在时会创建在容器路径后使用:ro可以设置为只读
    --mount: 和-v命令的作用差不多,区别是--mount的参数更多,本地文件不存在时会报错
    --rm: 容器退出后随之将其删除
    --name: 为容器指定一个名称,及时失败也会占用容器名字,需要rm掉
    bash:放在镜像名后的是命令,意思是调用容器的的/bin/bash,但是有的时候bash不好使的使用还要用/bin/bash
	
	;根据匹配规则运行指定的容器
	docker run ${name}
    
    ;目录映射,:ro指定为只读
	docker run --name nginx -v 主机目录:docker目录:ro -d nginx
    ;以交互式操作
    docker run -it ubuntu:15.10 /bin/bash
    ;以交互式操作,同时退出容器后删除容器
    docker run -it --rm nginx bash
    ;在容器内部运行指定的内容,这里运行ubuntu16.04 调用/bin/bash 运行echo 输出hello docker
    docker run ubuntu:16.04 echo 'hello docker'
    ;后台模式运行,会输出容器ID
    docker run -d ubuntu:15.10 /bin/sh -c "while true; do echo hello world; sleep 1; done"
    ; udp 标记来指定 udp 端口,运行training/webapp 下的python 启动默认工作目录下的app.py
    docker run -d -p 127.0.0.1:5000:5000/udp training/webapp python app.py
    ;运行容器ubuntu, 同时映射主机的指定目录到容器中
    docker run -it -v /Users/liuhao/www:/home/www ubuntu /bin/bash
    ;运行MySQL5.7镜像,同时通过设置容器内的环境变量将root密码设置为123456
    docker run -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 --name mysql mysql:5.7
    docker run -d -v /home/liuhao/workSpace:/var/www/html -p 9000:9000 --link mysql:mysql --name php-fpm php:7.2-fpm 
    docker run -d -p 80:80 --name nginx -v /home/liuhao/workSpace:/usr/share/nginx/html --link php-fpm:php-fpm --name nginx nginx:1.15.12

;进入容器内部,需要容器被运行起来之后
docker exec -it 容器名称 /bin/bash

;进入指定容器的内部,貌似已经不能用了
docker attach 容器名称

;查看容器的信息,可以使用--format='{{GO语言模板语法}}'做格式化
docker inspect [-format='{{GO语言模板语法}}'] 容器名

;导出容器
docker export 容器名称 > 路径

;导入容器快照
docker import - 容器名称
    ;也可以通过指定 URL 或者某个目录来导入
    docker import http://example.com/exampleimage.tgz example/imagerepo

docker load与docker import命令的区别,还必须知道docker save与docker export命令:
    docker save images_name:将一个镜像导出为文件,再使用docker load命令将文件导入为一个镜像,会保存该镜像的的所有历史记录。比docker export命令导出的文件大,很好理解,因为会保存镜像的所有历史记录。
    docker export container_id:将一个容器导出为文件,再使用docker import命令将容器导入成为一个新的镜像,但是相比docker save命令,容器文件会丢失所有元数据和历史记录,仅保存容器当时的状态,相当于虚拟机快照。


;查看端口映射,不指定端口则显示所有映射
docker port 容器名称 端口

;查看容器日志
docker logs -f <容器名orID>

;查看容器日志
docker container logs

;容器和主机之间的文件复制操作
docker cp 主机路径 容器ID:容器路径
docker cp 容器ID:容器路径 主机路径

;向容器中添加文件
docker add 

;重启容器,比如docker的nginx一旦在容器内重启nginx,容器就会关闭,这时候我们就可以在外面重启容器
docker restart ContainerID或名称
全写:docker container restart ContainerID或名称

;启动容器,比如容器被关闭之后,要再次启动的话,可以使用该命令
docker start ContainerID或名称
全写:docker container start ContainerID或名称

  • 数据卷(Volume)
数据卷可供一个或多个容器使用的特殊目录,它绕过了UFS,提供了多种可选属性使用.
1. 可在多个容器间共享使用,可以在同一个容器中同时挂载多个数据卷
2. 在数据卷中的修改可立马生效
3. 数据卷的更新不会对镜像有任何影响
4. 数据卷默认会一直存在,即使容器被删除

选择 -v 还是 -–mount 参数 
Docker 新用户应该选择 --mount 参数,经验丰富的 Docker 使用者对 -v 或者 --volume 已经很熟悉了,但是推荐使用 --mount 参数。 
创建数据卷可以使用docker create -v 后面参数因为我不用,所以 不在累述了
或者docker volume create 数据卷名称


;创建一个数据卷,创建数据卷不能指定目录
;如果要指定目录可以在运行容器时使用-v
docker volume create 数据卷名称

;查看数据卷列表
docker volume ls

;查看指定数据卷的详情, 可以使用--format='{{GO语言模板语法}}'格式化显示
docker volume inspect 数据卷名称

;启动容器的时候,将指定的数据卷 挂载到该容器中
docker run -d -t --mount source=数据卷名称,target=容器路径  ubuntu

;启动容器的时候,将指定的主句目录,挂载到容器中,readonly可以指定为只读
;挂载主机的指定到容器的指定目录,目录的默认权限是 读写 
docker run -d -t --mount type=bind,source=主机目录,target=容器目录,readonly ubuntu

;备份数据卷
;1.创建一个新的容器
;2.挂载数据卷容器
;3.挂载宿主机本地目录作为数据卷
;4.将数据卷容器的内容备份到宿主机本地目录挂载的数据卷中
;5.完成备份操作后容器销毁
mkdir /tmp/backup
docker run --rm --volumes-from shiyanloudb -v /tmp/backup:/backup ubuntu tar cvf /backup/shiyanloudb.tar /shiyanloudata

;删除数据卷
docker volume rm 数据卷名称

;清理无主的数据卷
docker volume prune


网络操作

  • docker四种网络模式
1. host模式 :
    docker run 使用 --net=host指定
    docker使用的网络实际上和宿主机一样
    启动案例: docker run -it --name myubuntu -v /Users/liuhao/www:/www --net=host ImageID /bin/bash
    $ docker  run -it --name myubuntu -v /Users/liuhao/www:/www --net=host e13f3d529b1a /bin/bash
    
2. container模式:
    使用 --net=container:container_id/container_name
    多个容器使用共同的网络,看到的ip是一样的。
    启动案例: docker run -it --name myubuntu --net=container:NAME_OR_ID ImageID /bin/bash
    
3. none 模式
    使用 --net=none指定
    这种模式下,不会配置任何网络。
    docker run -it --name myubuntu --net=none ImageID /bin/bash
    
4. bridge模式
    使用 --net=bridge指定
    默认模式,不会指定
    此模式会为每个容器分配一个独立的network namespace
  • 外部访问容器
Docker 允许通过外部访问容器或容器互联的方式来提供网络服务。 
要让外部也可以访问这些应用,可以通过 -P 或 -p 参数来 指定端口映射。 
当使用 -P 标记时,Docker 会随机映射一个 49000~49900 的端口到内部容器开放的网络端 口。 
-p 则可以指定要映射的端口,并且,在一个指定端口上只可以绑定一个容器。支持的格式 
有 ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort 。 

注意: 
容器有自己的内部网络和 ip 地址(使用 docker inspect 可以获取所有的变量,Docker 还可以有一个可变的网络配置。) 
-p 标记可以多次使用来绑定多个端口 


;以后台形式运行Nginx镜像,同时将随机映射一个 49000~49900 的端口到内部容器 
docker run -d -t -P Nginx

;使用 hostPort:containerPort 格式本地的 801 端口映射到容器的 801 端口,可以执行 
docker run -d -t -p 801:801 Nginx

;一次绑定多个端口
docker run -d -t -p 801:801 -p 900:900 nginx

;使用 ip:hostPort:containerPort 格式指定映射使用一个特定地址 
docker run -d -t -p 127.0.0.1:801:801 nginx

;使用 ip::containerPort 绑定本地的任意端口到容器的 801 端口,本地主机会自动分 配一个端口 
docker run -d -t -p 127.0.0.1::801 nginx

;还可以使用 udp 标记来指定 udp 端口 
docker run -d -t -p 127.0.0.1:801:801/udp nginx

;查看当前映射的端口配置,也可以查看到绑定的地址 
 docker port 容器名称 5000
 
;查看容器的信息,可以获取容器所有的变量,包括端口映射,可以使用--format='{{GO语言模板语法}}'做格式化显示
 docker inspect  容器ID

; 使用 docker container ls 可以看到 端口映射关系
docker container ls 

;可以通过 docker logs  命令来查看应用的信息 
docker logs -f 容器ID

  • 容器互联
如果你有多个容器之间需要互相连接,推荐使用 Docker Compose。 

;创建一个新的 Docker 网络 
;-d 参数指定 Docker 网络类型,有 。其中 overlay 网络类型用于 Swarm mode 
docker network create -d bridge 网路名称

;查看网络列表
docker network ls

;删除网路
docker network rm 网络ID

;运行一个容器并连接到新建的 my-net 网络 
docker run -it --rm  --network 目标网络名称 ubuntu sh

;打开新的终端,再运行一个容器并加入到 my-net 网络 
 docker run -it --rm --network 目标网路名称 centos sh

;再打开一个新的终端查看容器信息
docker container ls


自定义镜像

  • 使用commit定制镜像
;将容器保存为镜像
docker commit --author '提交人信息' --message '提交注释' 容器ID  新的名称:版本
docker commit -a '提交人信息' -m '提交注释' 容器ID  新的名称:版本
;案例
docker commit -a 'liuhao' -m 'syn blockchain' 0433eb0a2653  chinaliuhan/myachain
docker commit -a 'liuhao' -m '同步区块' b90bdec1e733 chinaliuhan/myachain:v2

;查看当前在容器中所有的具体操作和变动
docker diff 容器ID

;查看镜像列表
docker image list [镜像名]

;查看镜像内的历史记录
docker history 镜像ID 

  • 使用dockerfile定制镜像
行首 # 进行注释的格式
我们在定制镜像的时候可以直接使用git 的repo进行定制
也可以使用tar压缩包进行构建
所谓定制镜像,那一定是以一个镜像为基础,在其上进行定制
除了在上面说的那种使用commit提交, 在原有容器的基础上生成镜像之外.
我们可以使用dockerfile进行单独的镜像封装
一般我们在使用dockerfile进行定制 镜像的时候,都会新建一路目录在目录中操作.
因为,dockerfile在创建镜像的时候回将上下文中的所有东西都打包,如果东西多的话这个过程会非常的慢.

要明白这一块,可能要理解一下docker的上下文关系:
	 就像书上说的那样,这里用我自己的理解来说明一下,docker在运行时分为docker引擎和客户端两部分,
	 我们在用的时候也只是在用客户端来调用引擎里的各种接口,当使用dockerfile
	 进行镜像构建的时候除了用RUN来运行之外,还会有COPY和ADD之类的操作,而docker build
	 并非是客户端来操作,而是docker引擎中构建的,这块其实我们在docker build的时候通过docker
	 输出的信息可以得知	 Sending build context to Docker daemon  496.4MB
	 
	 所以我们在构建时需要指定上下文目录所在,
	 如: "docker build -t mynginx ." 这里的点,意思就是讲上下文设置在当前目录
	 这样 Docker 引擎收到这个上下文包后,展开就会获得构建镜像所需的一切文件。
	 如: "COPY ./nihao.txt /home/www "这里的意思并不是说Dockerfile或docker build命令所在的目录
	 而是我们之前指定上下文的目录,所以我们在使用COPY用的都是相对路径,
	 像这种写法是不行的: "COPY /tmp/nihao.txt /home/www"或者这样也不行"COPY ../nihao.txt /home/www"
	 在COPY的时候会有报错提示如:COPY failed: Forbidden path outside the build context: ../www ()
	 COPY时不能使用软连接
	
	

;该命令是使用当前目录下的Dockerfile构建新的镜像,注意后面有一个.
;在这条命令中,使用-t指定了镜像名为新的镜像名,由于没有使用-f指令,
;所以默认使用上下文路径下名为Dockerfile的文件认为是构建镜像的"Dockerfile"。
;最后指定上下文路径,在这条命令中,上下文路径是"."即为当前目录为上下文的路径
docker build[OPTIONS]上下文路径|URL .
    -t 指定新镜像的名称和版本号
    -f 指定要使用的Dockerfile的所在路径
    docker build -t 新镜像的名称:版本号 .
    docker build -t go/helloworld:1 -f Dockerfile.one .

;上面是使用本地文件进行构建自定义镜像
;下面两句话我们使用tar压缩文件和git的repo进行构建自定义镜像以及从标准输入中读取dockerfile进行构建

;从git repo进行构建自定义镜像
;如果所给出的 URL 不是个 Git repo,而是个 tar 压缩包,那么 Docker 引擎会下载这个包, 
;并自动解压缩,以其作为上下文,开始构建
docker build https://github.com/twang2218/gitlab-ce-zh.git#:8.14
docker build http://server/context.tar.gz
;从标准输入中读取Dockerfile进行构建
docker build - < Dockerfile 或者  cat Dockerfile | docker build - 
;从标准输入中读取上下文压缩包进行构建
docker build - < context.tar.gz


;Dockerfile中常用的命令:

FROM
FROM <源镜像或scratch>
;镜像来源,指定基础镜像,如果使用FROM scratch 意味着你不以任何镜像为基础,
;接下来所写的指令将作 为镜像第一层开始存在
;如: FROM nginx 选择Nginx镜像作为原始镜像
;如: FROM scratch 设置的当前镜像不以任何镜像为基础,接下来所写的命令将作为镜像的第一层存在

RUN
RUN <命令> 
;运行 指令是用来执行命令行命令的run是我们在dockerfile中最常用的命令
;可以用来执行命令行命令和直接的命令
;如: RUN echo '这里是测试内容' > /tmp/test.log
;如: RUN apt-get instal nginx 
;如: RUN buildDeps='gcc libc6-dev make' 此处可以作为变量使用,设置变量$buildDeps,在后面可以直接用$buildDeps来替代该命令

COPY
COPY <源路径>... <目标路径>
COPY ["<源路径1>",... "<目标路径>"]
;注意COPY操作不能使用软连接
;源文件的各种元数据都会保留。比如读、写、执 行权限、文件变更时间等
;复制文件 从构建上下文的目录中的 文件/目录 复制到新的镜像内,<源路径> 可以是多个
;<目标路径>可是是绝对路径也可以是相对于工作目录的相对路径,
;工作目录可以 用 WORKDIR 指令来指定)。
;<目标路径>不需要事先创建,如果目录不存在会在复制文件前先行 创建缺失目录。
;如: COPY mytest.txt /tmp  将当前目录下的mytest.txt 复制到镜像中的/tmp目录下
;如: COPY hom* /tmp/        使用通配符进行操作
;如: COPY hom?.txt /mydir/  使用正则进行操作

ADD
ADD <源路径> <目标路径>
;该命令和copy差不多,但是在copy基础上增加了一些功能.
;如: <源路径>可以是一个URL,docker会自动下载到<目标路径>,下载后的权限默认为600.
;如果不想要权限,还需要增加额外的一层RUN进行权限调整.如果下载包是一个压缩包,也需要额外的RUN指令就行解压操作
;所以我们不如直接使用RUN命令,然后使用curl或wget工具下载,处理权限,解压缩,然后清理无用文件更合理.
;所以官方文档不推荐用这个命令,不太实用,官方要求尽可能是的使用COPY,因为COPY的语义很明确,就是复制文件而已.
;而 ADD 则包含了更复杂的功能,其行为也不一定很清晰。最 适合使用 ADD 的场合,就是所提及的需要自动解压缩的场合。
;因此在 COPY 和 ADD 指令中选择的时候,可以遵循这样的原则,所有的文件复制均使用 COPY 指令,仅在需要自动解压缩的场合使用 ADD 。
;




CMD
;容器启动时运行的命令,注意是容器启动的时候
如果想要在创建镜像时运行需要在RUN中配置
shell 格式: CMD <命令>
exec 格式: CMD ["可执行文件", "参数1", "参数2"...]
在指定了 ENTRYPOINT 指令后,用 CMD 指 定具体的参数。
在指令格式上,一般推荐使用 exec 格式,这类格式在解析时会被解析为 JSON 数组,因
此一定要使用双引号 " ,而不要使用单引号。



ENTRYPOINT
入口点
ENTRYPOINT 的目的和 CMD 一样,都是在指定容器启动程序及参数
但是一个Dockerfile中只能有一条ENTRYPOINT命令,如果多条,则只执行最后一条


ENV
设置环境变量
ENV  
ENV = =...


ARG
构建参数
ARG <参数名>[=<默认值>]


VOLUME
定义匿名卷
VOLUME ["<路径1>", "<路径2>"...]
VOLUME <路径>

EXPOSE
声明端口
EXPOSE <端口1> [<端口2>...]


WORKDIR
指定工作目录
WORKDIR <工作目录路径> 
使用 WORKDIR 指令可以来指定工作目录(或者称为当前目录),
以后各层的当前目录就被改 为指定的目录,
如该目录不存在,WORKDIR 会帮你建立目录


USER
指定当前用户
USER <用户名>
和 WORKDIR 一样, USER 只是帮助你切换到指定用户而已,这个用户必须是事先建立 好的,否则无法切换。


HEALTHCHECK
健康检查
HEALTHCHECK [选项] CMD <命令> :设置检查容器健康状况的命令
HEALTHCHECK NONE :如果基础镜像有健康检查指令,使用这行可以屏蔽掉其健康检查指 令


ONBUILD
为他人做嫁衣裳
ONBUILD <其它指令>
ONBUILD 是一个特殊的指令,它后面跟的是其它指令,比如 RUN , COPY 等,
而这些指令, 在当前镜像构建时并不会被执行。
只有当以当前镜像为基础镜像,去构建下一级镜像的时候 才会被执行。

    
  • 使用dockerfile进行多阶段构建镜像
全部放入一个 Dockerfile
一种方式是将所有的构建过程编包含在一个 Dockerfile 中,
包括项目及其依赖库的编译、 测试、打包等流程,
这里可能会带来的一些问题:
Dockerfile 特别长,可维护性降低 镜像层次多,镜像体积较大,
部署时间变长 源代码存在泄露的风险




转载于:https://my.oschina.net/chinaliuhan/blog/3063767

你可能感兴趣的:(Docker常用操作帅哥专供版)