一 Docker的概念
Docker是一个开源的应用容器引擎。也就是说我们可以快速利用Docker快速实现应用容器的功能。比如Solr搜索引擎,即我们可以利用Solr快速实现搜索功能一个道理。
假设我们想在一个Linux系统上建立多个互不干扰的子服务器,传统虚拟方式是这样的。每一个子服务器由着自己对应的操作系统。
那么对于Docker而言呢?也是一个Linux服务器,在其上面有多个容器,这个容器不等同于虚拟机,虚拟机是有操作系统的,而这里的容器是没有操作系统的。
二Docker与虚拟化技术
虚拟化技术是一个总称,广义上讲,包括了虚拟机技术和容器技术。
Docker 和 虚拟机的区别?
所谓Docker和虚拟机的区别,也可以理解为容器和虚拟机的区别。虚拟机技术是利用Hypervisor层抽象底层基础设施资源,提供相聚隔离的机制,通过统一配置,统一管理计算资源的可运维性,以及资源的利用率都有很大的提升。但是不可避免还是会出现资源损失,启动多个虚拟机对资源损耗很大,而且会出现卡顿现象。
而容器严格来说并不是虚拟化,没有自己独立的操作系统,共享内核。不需要Hypervisor实现硬件资源虚拟化,容器可以被视为软件供应链的集装箱,能够把应用需要的运行环境,缓存环境,数据库环境封装起来,以最简洁的方式支持应用运行。由于共享内核,所以隔离性没有虚拟机那么好。说白了就是在一台硬件服务器上虚拟出对个容器,每一个容器都可以设置一个互相隔离的服务器。
三 Docker组件
Docker是一个C/S类型的架构。一般来说用户通过client端进行相应的操作指令会传递到服务器端,然后交由server端Docker daemon进程处理
Docker内部有三个核心组件:
3.1 Docker images(镜像)
镜像一般是只读模板,用户不能直接修改,使用镜像可以很轻松创建容器。相同的镜像可以创建不同的容器。
3.2 Docker containers(容器)
容器与容器之间互相隔离,互不干扰,容器可以进行被开始,停止,删除和启动等操作,它是相对于镜像上的一层可写层。
3.3 Docker registries(仓库)
仓库主要用来下载和上传镜像的,仓库分为共有仓库和私有仓库。
四 Docker Image(Docker镜像)
4.1 什么是镜像
镜像,一种冗余的数据格式,通常我们理解的镜像就是一个磁盘的数据在另外一个磁盘上存在完全相同的副本。
Docker中,镜像文件不会很大。Docker的镜像并不会包含一个独立的操作系统,并且镜像中拥有对应的开发与运行环境。所以使用Docker,可以很方便实现开发环境的快速,批量部署。
4.2 如何获取镜像
docker pull 镜像地址
4.3 如何搜寻镜像
如果我们想在docker index中搜索Docker的镜像,那么我们可以使用镜像搜索的功能。
docker search 关键字
Options:
-f,--filter filter Filter output based onconditions provided
--help Print usage
--limitint Max number of search results(default 25)
--no-trunc Don't truncateoutput
docker search --stars=1 --no-trunc centos
等价于
docker search --filter=stars=3 --no-trunc centos
docker search --automated --no-trunc centos
等价于
docker search --filter=is-automated=true --no-trunccentos
等价于
docker search --filter is-automated --no-trunccentos
多个filter
docker search --filter "is-official=true"--filter "stars=3" centos
4.4 如何创建镜像
创建镜像有三种方式:
4.4.1 根据已有的修改
因为镜像不可修改,那我们要怎样根据已有的修改,我们可以用镜像运行一个容器,容器不是只读的,容器可写,所以可以修改容器,然后将容器变为镜像。
docker run -ti 仓库名:tag /bin/bash
查询本地有哪些镜像
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu 16.04 2d696327ab2e 2 weeksago 122MB
再将这个镜像作为容器运行,然后在容器的基础上加点东西,修改一下,然后退出
$ docker run -ti ubuntu:16.04
root@5c7de7b31017:/# mkdir test
root@5c7de7b31017:/# touch abc.txt
root@5c7de7b31017:/# exit
exit
然后根据修改,产生新的镜像:
docker commit -m "new images" -a"nicky" 5c7de7b31017 ubuntu:16.04-update
sha256:6cfc0af4395a1a3bf76a786f03b112a9e87f319dcd3a8ad23bd0b835b26b038f
4.4.2 使用dockerfile
先创建dockerfile,然后再写入一些代码
在某个目录创建Dockerfile文件,比如touch Dockerfile
# 某个目录下创建Dockerfile
# 编辑Dockerfile,开始写入指令
FROM ubuntu:1010 //从哪一个镜像创建,即作为原始镜像
MAINTAINER nicky // 创建对应的用户
RUN mkdir ./df
RUN touch ./test.txt
RUN mv ./test.txt ./df
# 创建新的镜像
docker build -t=“ubuntu:1010-update” /docker //从哪儿去找Dockerfile
4.4.3 使用官方模板导入
Docker官方提供很多镜像的模板,我们可以下载这些模板,然后根据这些模板变成对应的镜像。
比如我们可以进入网站:
https://download.openvz.org/template/precreated/ 去下载一个模板
然后导入成镜像文件
$ cat ubuntu-12.04-x86_64-minimal.tar.gz | dockerimport - ubuntu:1010
sha256:72815d73c3427a1f5b1ea1f02985c0df04dbb73904f094a604b716a78209af26
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu 1010 72815d73c342 About a minuteago 149MB
4.5 如何删除镜像
# 删除仓库名为ubuntu的仓库,但是不指定tag
docker rmi ubuntu
它会自动删除仓库为ubuntu, tag为latest。如果没有该镜像,则会提示没有该镜像。
# 删除仓库名为ubuntu的仓库,指定tag
docker rmi ubuntu:1010
# 如果有容器占用着镜像,此时不能删除某个镜像
但是如果你非要删除它。你应该这样操作:
删除对应的容器,然后再删除镜像
docker rm 容器id
docker rmi 镜像仓库:tag
# 根据镜像id删除镜像
docker rmi 镜像id
# 强制删除镜像
docker rmi -f 镜像
4.6 写时复制机制
copy-on-write机制:说白了就是Linux父进程创建子进程,创建子进程,然后需要复制父进程的内容到子进程。如果不是写时复制机制,那就是一创建子进程就复制了;而写时复制机制是只有进程空间各段内容发生改变,才进行复制。
4.7 镜像分发
镜像的分发,即镜像的发布。比如你创建一个镜像,想发布出去,给别人使用,这个过程就是镜像分发。
两种方式:
方式一:
把镜像压缩文件发布出去,比如github,网盘等,别人可以下载该文件并恢复为镜像
方式二:
把镜像上传到仓库中,供别人使用。其中上传到仓库类型包括public和private两种类型的
4.8 自动化构建镜像
我们可以把Dockerfile文件上传到互联网上,比如传到GitHub上,然后直接在Dockerfile中根据远程的Dockerfile文件创建,那么这种方式我们将其称为镜像的自动化构建。
4.9 注册服务器的创建
在Docker Hub上创建一个属于自己的远程空间,让自己以后可以把镜像上传到Docker Hub中。
4.10 镜像的上传
可以先给本地镜像起一个别名。一般而言dockerhub的用户名/仓库名:tag
docker tag 72815d73c342 zhanglh046/hello:initial
然后再push到docker hub
docker push zhanglh046/hello:initial
4.11 镜像导入和导出
镜像导出,也叫镜像备份。镜像的导入也叫镜像恢复,把备份的镜像重新导入操作系统。
导出:
docker save -o 名字.tar 要导出的镜像名字
docker save -o ubuntu-bak.tar ubuntu:1010
导入:
docker load --input 备份的镜像
docker load --input ubuntu-bak.tar
4.12 什么是Docker Hub
Docker Hub是官方维护的一个远程的镜像仓库,存出一些公开发布的镜像。
针对Docker Hub传输速度慢的解决方式:
我们可以使用Docker Mirror来解决。首先尝试从Docker Mirror服务器上,如果没有才从国外服务器上获取
五 容器
镜像是只读的,而基于镜像创建出来的容器是可读可写的。实际中我们会经常使用对应镜像创建容器并且使用这些容器。
5.1 docker run参数详解
docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
OPTIONS说明:
-a stdin: 指定标准输入输出内容类型,可选 STDIN/STDOUT/STDERR 三项;
-d: 后台运行容器,并返回容器ID;
-i: 以交互模式运行容器,通常与 -t 同时使用;
-t: 为容器重新分配一个伪输入终端,通常与 -i 同时使用;
--name="nginx-lb": 为容器指定一个名称;
--dns 8.8.8.8: 指定容器使用的DNS服务器,默认和宿主一致;
--dns-search example.com: 指定容器DNS搜索域名,默认和宿主一致;
-h "mars": 指定容器的hostname;
-e username="ritchie": 设置环境变量;
--env-file=[]: 从指定文件读入环境变量;
--cpuset="0-2" or--cpuset="0,1,2": 绑定容器到指定CPU运行;
-m :设置容器使用内存最大值;
--net="bridge": 指定容器的网络连接类型,支持 bridge/host/none/container: 四种类型;
--link=[]: 添加链接到另一个容器;
--expose=[]: 开放一个端口或一组端口;
5.2 根据镜像创建容器
我们可以根据相同的镜像创建不同的容器。
docker run -ti ubuntu:1010 /bin/bash
5.3 容器管理
Docker中可以对创建的容器进行管理,比如容器启动,daemon方式运行,终止管理等操作
5.3.1 启动
方式一:创建容器并且启动
docker run -ti ubuntu:1010
方式二:启动已经终止状态的容器
比如我们exit之后,容器就变成终止状态。如果又想使用它,我们还可以启动它
docker ps -a 查看所有容器
docker start CONTAINER [CONTAINER...] 启动容器
docker restart [OPTIONS] CONTAINER [CONTAINER...] 重启容器
docker start 容器id
docker restart 容器id
5.3.2 daemon方式运行
通过-d参数指定容器以守护态运行,虽然docker ps –a状态为Exit状态,但是它其实是一直在运行的
docker run -d ubuntu:1010-update /bin/bash -c "ls/"
我们可以查看日志:
docker logs 容器id
5.3.3 终止容器
docker stop 容器id
5.4 依附容器
我么可以使用docker attach指令依附到某个容器中,通俗来说,就是指我们可以通过该指令进入某个容器。新登录一个正在执行的容器,如果容器没有在运行那么先start。
nickyzhang@nickyzhang-PC MINGW64 ~
$ docker attach 215b
root@215b5eff67e4:/#
5.5 容器信息查看
5.5.1 ps指令
docker ps 查看正在运行的容器
docker ps -a查看所有的容器
docker ps -f 过滤
docker ps -l 查看最后一次创建的容器
docker ps -q 只会列出容器id
5.5.2 logs指令
docker logs --details 显示额外的一些日志信息
docker logs -f/follow 实时显示日志
docker logs --since 2013-01-02T13:23:37 或者42m
docker --tail 10,即显示最近10行的日志
docker logs -t 显示时间戳
5.6 容器内命令执行
docker exec命令能够在运行着的容器中,执行命令。docker exec命令的使用格式:
docker exec [OPTIONS] container_name COMMAND[ARG...]
OPTIONS说明:
docker exec -d,以后台方式执行命令;
docker exec -e,设置环境变量
docker exec -i,交互模式
docker exec -t,设置TTY
docker exec -u,用户名或UID
比如我在外面,想在运行的容器中执行创建文件的命令:
nickyzhang@nickyzhang-PC MINGW64 ~
$ docker exec -d 215b touch /opt/app/book.xml
$ docker exec -ti 215b touch /opt/app/book.xml
nickyzhang@nickyzhang-PC MINGW64 ~
$ docker attach 215b
root@215b5eff67e4:/# ls /opt/app/
book.xml tools
容器已经有了book.xml这个文件
5.7 容器的导入与导出
我们可以将某容器通过docker export指令导出为一个压缩文件,然后存起来,在必要的时候可以根据压缩文件恢复为容器。
$ docker export 215b > container_215b.tar
$ docker export 215b -o con_215b.tar
相反,我们想把某个容器压缩文件恢复为容器的时候,我们需要通过docker import 指令进行容器的 导入操作。
由于容器是基于镜像而创建的,其核心特征是在镜像上创建啊一层可写层,故而容器的导入过程是这样的:
首先把容器压缩文件快快照导入为一个镜像
cat container_251b.tar | docker import -ubuntu:215b
然后靖宇该镜像创建一个容器,则此时创建的新容器和原容器性能一致。
docker run -ti ubuntu:215b /bin/bash
5.8 删除容器
删除单个容器
docker rm 容器id
删除所有容器
docker rm $(docker ps -a -q)
六 数据卷
每一个容器都会涉及到数据,那我们就需要对这些数据进行管理,管理数据的方式主要有两种:
# 数据卷
是一个可以供一个或者多个容器使用的特殊目录。该目录利用容器的UFS文件系统可以为容器提供一些稳定的特性或者数据共享,类似于Linux的mount挂载命令。所谓挂载就是把一个磁盘分区或者存储设备挂到一个目录,这个目录不为空,但是挂载之后,这个目录以前的东西不可用。那这个目录就是挂载点。
但是需要理解的是:光盘、软盘、其他操作系统使用的文件系统的格式与Linux使用的文件系统格式是不一样的。光盘是ISO9660;软盘是fat16或ext2;windows NT是fat16、NTFS;windows98是fat16、fat32;windows2000和windowsXP是fat16、fat32、NTFS。挂载前要了解Linux是否支持所要挂载的文件系统格式。
# 数据卷容器
6.1 数据卷的特性
数据卷可以在容器之间共享和重用
对数据卷的修改会立马生效
对数据卷的更新不会影响镜像
卷会一直存在,直到没有容器使用
6.2 数据卷的创建
我们使用docker run -v 目录 镜像,参数就可以创建数据卷,并且还要挂载到我们指定的容器里。
docker run -ti -v /abc ubuntu:v-snapshot-0.2/bin/bash
6.3 挂载
我们在容器中建立了数据卷之后,可以把母机器中的某个目录或某个文件挂载到数据卷中,这样,这个目录或者文件与容器中的数据卷内容就相当于是一个了
需求:将容器中的数据卷/convol挂载到宿主机的/vol目录
在宿主机上创建目录:
mkdir /vol
并且创建文件a.txt
touch /vol/a.txt
根据镜像文件启动容器,并且进行挂载操作:
语法如下:
docker run -ti -v 宿主机目录:容器数据卷 镜像仓库:tag /bin/bash
docker run -ti -v /vol:/convolubuntu:v-snapshot-0.1 /bin/bash
就会进入容器,我们此时检查是否/convol是否也包含a.txt文件
root@ff05595e0315:/# ls /convol/
a. txt
确实已经有了这个文件了,我们在测试是不是修改立马生效:
我在宿主机a.txt添加文字:Nothing is difficult!
root@ff05595e0315:/# cat /convol/a.txt
Nothing is difficult!
也有了
然后我们在容器数据卷目录下添加文件:
root@ff05595e0315:/# vim /convol/b.txt
然后去宿主机检查是否也有b这个文件
[root@docker ~]# ls /vol/
a. txt b.txt
确实也有了
6.4 数据卷容器
所谓数据卷容器,本质就是一个容器,但是这个容器是一个专门用来提供数据卷而已。
有时候,我们有一些数据需要持续更新,并且这些数据需要容器间进行共享,那么此时,如果有一个专门的容器来提供数据卷,将会方便很多。所以如果我们要解决数据持久化问题,应当选用数据卷容器。
dcoker run -ti -v 指定数据卷目录 --name 指定数据卷容器名称 镜像仓库:tag /bin/bash
docker run -ti -v /abc --name datavolumeubuntu:17.04 /bin/bash
然后别得就可以挂载这个/abc数据卷,多个容器就可以共享该数据下的数据
需求?普通容器目录挂载到数据卷容器数据卷中
docker run -ti --volumes-from 数据卷容器名称或者id --name 指定当前普通容器名称 镜像仓库:tag /bin/bash
docker run -ti --volumes-from datavolume --namecontainer1 ubuntu:17.04 /bin/bash
6.5 数据迁移
指的是,在有必要的时候,将数据卷容器的数据卷中的数据全部转移到一个新的地方。
原理:首先将数据卷容器的相应数据卷进行备份,然后将备份文件保存起来,在有需要的时候,再将备份文件恢复为数据卷,并且数据卷里面的内容要完整保存
备份原理:
# 加载源数据卷容器里面的数据卷
# 创建新的容器,并把加载过来的数据归档存放
# 直接将目录挂载到新容器存放归档文件的目录,这样就可以直接在当前文件夹读取到对应的压缩文件
需求:备份数据卷容器的数据卷
# 创建数据卷容器,并在数据卷添加一些测试数据
docker run -ti -v /srcvol --name srcvolume ubuntu:v-snapshot-0.1/bin/bash
# 创建一个新的容器,并把加载过来的数据归档存放
docker run -ti --volumes-from srcvolume -v$(pwd):/destvol --name destvolume ubuntu:v-snapshot-0.2 tar cvf/destvol/back.tar /srcvol
tar: Removing leading `/' from member names
/srcvol/
/srcvol/model/
/srcvol/model/a.txt
/srcvol/follow.sh
恢复原理:
备份了数据卷之后,要想实现迁移,还得完成恢复操作。首先进入要恢复的压缩文件所在地;其次创建一个新的容器;最后进入容器将归档文件解压
需求:恢复数据卷
# 进入到宿主机压缩文件所在地方
# 创建新的容器
docker run -ti -v $(pwd):/resume --nameresumevolume ubuntu:v-snapshot-0.2
# 解压文档
root@88b385fa78e9:/resume# tar xvf back.tar
srcvol/
srcvol/model/
srcvol/model/a.txt
srcvol/follow.sh
七 网络服务
Docker可以提供网络服务,主要是两种方式:
外部访问和容器互联
7.1 通过外部网络访问docker容器
我们在docker容器中,可以运行一些网络应用,比如web应用等,如果这些应用需要跟外界进行交互,那么一般使用外部访问容器的方式运行。主要是通过端口映射的方式解决。
docker run -ti -P --expose 95 --name net1nicky/ubuntu:volume-1
-P: 发布所有暴露的端口给一个随机的端口,即通过外部访问该随机端口,就可以访问到你所暴露的端口
--expose 暴露一个或者一个范围内的端口
docker run -t -p 127.0.0.1:1111:5000 --name net4nicky/ubuntu:v-1 /bin/bash
-p 发布一个容器的端口,即外部网络通过1111端口就可以访问到容器的5000端口
7.2 容器间通信的实现
除了端口映射方式外,如果还想实现跟容器内应用进行通信,还可以使用容器互联的方式。
容器互联的方式会在接收容器与源容器间创建一个隧道,接收容器可以看到源容器指定信息
创建源容器,即普通创建
docker run -t --name interconnection1nicky/ubuntu:v-1 /bin/bash
创建接收容器
docker run -ti --name interconnection2 --linkinterconnection1:src1 nicky/ubuntu:v-1 /bin/bash
--link: 表示要互联到哪一个源容器
interconnection1:src1 src1代表别名,也可以不写
7.4 网络配置的查看
我们可以使用一些方法来查看对应的容器的网络配置信息:
# docker ps
适用于广泛查找一些容器的网络配置信息
# docker port
适用于已知某个容器的某个端口,要查找该容器该端口对应的外部IP地址以及端口
[root@localhost ~]# docker port b0bd09cfc828
5000/tcp -> 127.0.0.1:11
7.5 容器命名
我们可以根据docker run–name设置容器名称。
# 根据容器id查找具体的容器名称
docker inspect -f "{{.Name}}"541aca0850cf
# 重名解决方案
Docker不允许重名的容器存在,若出现的解决方案如下:
先删除源容器,再创建新容器
使用--rm来标记
一旦退出,容器就会删掉
docker run -ti --name nifty_davinci --rmnicky/ubuntu:v-1 /bin/bash
八 容器连接
我们在进行了容器互联之后,我时候我们需要查找某些容器的互联信息,此时我们就要进行连接信息的公开,一般而言,要公开连接信息,有两种方式:
8.1 hosts文件法
进入连接容器,然后找到/etc/hosts文件,并cat,即可把该容器连接信息公开出来,包括连接容器对应的源容器信息。公开之后,可以使用ping命令测试连接是否通畅。
# 先启动互联的容器,必须先启动源容器
# docker attach 接收容器
# cat /etc/hosts
8.2环境变量法
docker run --name xxxx --link yyyy:hahanicky/ubuntu:v-1 env /bin/bash
九 Dockerfile指令
FROM 镜像
MAINTAINER 用户名
RUN 命令
ADD <src> <dest>添加文件到容器
<src> 必须是想对于源文件夹的一个文件或目录,也可以是一个远程的url
<dest> 是目标容器中的绝对路径。
CMD: 执行docker run时执行某指令,具有替换性
ENTRYPOINT: 执行docker run时执行某指令,不具有替换性
# CMD 和ENTRYPOINT都是预先定义命令,以使得我们在执行docker run命令的时候,可以使用这些命令,否则提示cannot execute binary file
# 定义命令的方式如下:
CMD["executable","param1","param2"] 像exec
CMD ["param1","param2"]
CMD command param1 param2 像shell
或者
ENTRYPOINT ["executable", "param1","param2"]
ENTRYPOINT command param1 param2
最好还是以数组的形式提供
# CMD和ENTRYPOINT的区别
CMD: 定义的命令,如果在docker run的时候被命中,那么docker run中的命令会替换掉CMD中命令所带的参数
ENTRYPOINT: 定义的命令,如果在docker run的时候被命中, 则不会替换ENTRYPOINT携带的命令参数,而是既会执行ENTRYPOINT的定义的命令和参数,还会执行自己命令和参数
举个例子:
cd /opt/docs/
vim Dockerfile
FROM nicky/ubuntu:v-1
MAINTAINER 'nicky'
# 根据Dockerfile构建镜像
docker build -t "nicky/ubuntu:17.09.01"/opt/docs
# 根据进行镜像启动容器,并执行一些命令
docker run -ti nicky/ubuntu:17.09.01 /bin/bash mkdir /opt/app
/bin/mkdir: /bin/mkdir: cannot execute binary file
docker run -ti nicky/ubuntu:17.09.01 /bin/bash echo happy
/bin/echo: /bin/echo: cannot execute binary file
由于我没有在Dockerfile添加这些命令,所以有错误提示:
cannot execute binary file
# 重新修改Dockerfile
FROM nicky/ubuntu:v-1
MAINTAINER 'nicky'
CMD ["echo ","happy"]
# ENTRYPOINT ["echo","helloworld"]
# 重新构建镜像
docker build -t "nicky/ubuntu:17.09.02"/opt/docs/
# 测试CMD 提供的指令
docker run -ti ubuntu:17.09.02 echo welcome
welcome
他并没有显示happy
# 然后我们测试ENTRYPOINT
我们可以接着修改Dockerfile,然后重新产生镜像ubuntu:17.09.03
FROM nicky/ubuntu:v-1
MAINTAINER 'nicky'
# CMD ["echo ","happy"]
ENTRYPOINT ["echo","helloworld"]
docker run -ti ubuntu:17.09.10 /bin/bash echowelcome
hello world /bin/bash echo welcome
USER: 使用哪一用户执行
EXPOSE: 绑定哪个内部端口
ENV: 环境变量设置
FROM nicky/ubuntu:v-1
MAINTAINER 'nicky'
# CMD ["echo","happy"]
# ENTRYPOINT ["echo","helloworld"]
ENV JAVA_HOME /opt/app/java
EXPOSE 98
十 SSH环境
如果要使用SSH进行远程登录到Docker容器,那么就要在容器中搭建SSH环境,并进行部署。
10.1 在容器中安装ssh
# 创建容器dssh1
docker run -t -i --name dssh1 nicky/ubuntu:v-1/bin/bash
# 安装sshd
apt-get update
apt-get install openssh-server
mdkir -p /var/run/sshd
# 启动sshd服务
/usr/sbin/sshd -D &
10.2 宿主机生成密钥
cd /root/.ssh/
ssh-keygen
最后会生成2个文件:密钥id_rsa以及公钥id_rsa.pub
10.3 在容器中进入/root/.ssh/
我们创建文件authorized_keys,并且把客户端的公钥id_rsa.pub的内容复制到authorized_keys
如果严格模式修改为非严格模式:
vi /etc/ssh/sshd_config
StrictModes no
10.4 退出,根据容器生成新的镜像
docker commit 75172d9bb353 ubuntu:sshd-3
10.5 根据生成的镜像,然后生成新扥容器,配置端口映射
docker run -ti -p 127.0.0.1:8088:22 --name sshd-3ubuntu:sshd-3 /bin/bash
10.6 宿主机就可以去连接容器