学了铭哥的docker,自己也做了些总结,加入了一些概念性以及,docker0网桥修改等等。
所有命令我自己的实验过,目前还没有写完,因为是用markdown写的,贴到论坛会有些变形,
连接也没有办法去点击,大家也可以到下面这个发布地址,这个地址我会一直保持更新。
https://www.zybuluo.com/zhpro/note/314843
docker入门 v1.0
docker
- docker入门 v1.0
- 容器是什么?
- 为啥要用容器?
- 那为啥不用VM?
- 为啥要用容器?
- docker 特点
- docker和虚拟机的区别:
- docker核心概念:
- docker和虚拟机的区别:
- docker安装启动:
- docker 镜像管理:
- 1. 查看本地镜像:
- 2. 在 docker 仓库中搜索镜像:
- 3. 从 docker.com 获取 centos 镜像 :
- 4. 修改镜像名: - 显示在 REPOSITORY 列
- 5. 修改镜像名 + TAG
- 6. 删除一个镜像:
- 7. 将修改过内容的容器保存为一个自定义的镜像
- 8. 将一个现有的镜像导出为一个文件 - 会保存在当前目录下
- 9. 将文件恢复到本地镜像 - 创建一个新的镜像
- 10. 推送一个镜像到dockerhub.com
- 1. 查看本地镜像:
- docker容器管理:
- 1. 依托镜像创建并启动一个容器 :
- 2. 创建一个容器并不启动
- 3. 启动关闭一个容器:
- 4. 进入一个容器
- 5. 查看正在运行的容器或所有容器:
- 6. 删除一个容器:
- 7. 启动容器的同时也可以运行一些命令
- 8. 查看容器的日志输出信息
- 9. 导入导出容器 -方便迁移
- 10. 新建一个容器,退出时自动删除这个容器
- 1. 依托镜像创建并启动一个容器 :
- docker 仓库管理
- 1. 下载私有仓库镜像
- 2. 依托registry镜像来创建容器
- 3. 上传一个镜像到私有仓库
- 4. 使用 Docker 的 RESTful API 可以查看仓库服务器中的镜像:
- 5. 拉取私有库的镜像
- 1. 下载私有仓库镜像
- docker 数据管理
- 1. 将本地目录挂载到容器目录
- 2. 挂载其它容器的目录
- 3.数据卷容器
- 1. 将本地目录挂载到容器目录
- docker 网络管理
- 1. docer 的 4 种网络模式
- 2. docker 网桥
- 2.1 列出当前主机网桥
- 2.2 查看当前 docker0 ip
- 2.3 自定义 docker0 网桥的网段
- 2.3 自定义网桥
- 2.1 列出当前主机网桥
- 3. 外部网络访问容器资源
- 4. 容器互联
- 5. docker 配置桥接网络
- 1. docer 的 4 种网络模式
- 容器是什么?
容器是什么?
官网的介绍是这样的:
Docker is an open platform for developers and sysadmins to build, ship, and run distributed applications....
其实看完这句话还是不明白究竟是啥的,下面就慢慢解释。不过长话短说的话,把他想象成一个用了一种新颖方式实现的超轻量虚拟机,在大概效果上也是正确的。当然在实现的原理和应用上还是和VM有巨大差别的,并且专业的叫法是应用容器(Application Container)
为啥要用容器?
那么应用容器长什么样子呢,一个做好的应用容器长得就好像一个装好了一组特定应用的虚拟机一样。比如我现在想用MySQL那我就找个装好MySQL的容器,运行起来,那么我就可以使用 MySQL了。
那么我直接装个 MySQL不就好了,何必还需要这个容器这么诡异的概念?话是这么说,可是你要真装MySQL的话可能要再装一堆依赖库,根据你的操作系统平台和版本进行设置,有时候还要从源代码编译报出一堆莫名其妙的错误,可不是这么好装。而且万一你机器挂了,所有的东西都要重新来,可能还要把配置在重新弄一遍。但是有了容器,你就相当于有了一个可以运行起来的虚拟机,只要你能运行容器,MySQL的配置就全省了。而且一旦你想换台机器,直接把这个容器端起来,再放到另一个机器就好了。硬件,操作系统,运行环境什么的都不需要考虑了。
在公司中的一个很大的用途就是可以保证线下的开发环境、测试环境和线上的生产环境一致。当年在 Baidu 经常碰到这样的事情,开发把东西做好了给测试去测,一般会给一坨代码和一个介绍上线步骤的上线单。结果代码在测试机跑不起来,开发就跑来跑去看问题,一会儿啊这个配置文件忘了提交了,一会儿啊这个上线命令写错了。找到了一个 bug 提上去,开发一看,啊我怎么又忘了把这个命令写在上线单上了。类似的事情在上线的时候还会发生,变成啊你这个软件的版本和我机器上的不一样……在 Amazon 的时候,由于一个开发直接担任上述三个职位,而且有一套自动化部署的机制所以问题会少一点,但是上线的时候大家还是胆战心惊。
若果利用容器的话,那么开发直接在容器里开发,提测的时候把整个容器给测试,测好了把改动改在容器里再上线就好了。通过容器,整个开发、测试和生产环境可以保持高度的一致。
此外容器也和VM一样具有着一定的隔离性,各个容器之间的数据和内存空间相互隔离,可以保证一定的安全性。
那为啥不用VM?
那么既然容器和 VM 这么类似为啥不直接用 VM 还要整出个容器这么个概念来呢?Docker 容器相对于 VM 有以下几个优点:
启动速度快,容器通常在一秒内可以启动,而 VM 通常要更久
资源利用率高,一台普通 PC 可以跑上千个容器,你跑上千个 VM 试试
性能开销小, VM 通常需要额外的 CPU 和内存来完成 OS 的功能,这一部分占据了额外的资源
为啥相似的功能在性能上会有如此巨大的差距呢,其实这和他们的设计的理念是相关的。
VM 的设计图如下:
http://cms.csdnimg.cn/article/201407/02/53b3c67030ae8.jpg
VM 的 Hypervisor 需要实现对硬件的虚拟化,并且还要搭载自己的操作系统,自然在启动速度和资源利用率以及性能上有比较大的开销。
而 Docker 的设计图是这样的:
http://cms.csdnimg.cn/article/201407/02/53b3c68844d34.jpg
Docker 几乎就没有什么虚拟化的东西,并且直接复用了 Host 主机的 OS,在 Docker Engine 层面实现了调度和隔离重量一下子就降低了好几个档次。 Docker 的容器利用了 LXC,管理利用了 namespaces 来做权限的控制和隔离, cgroups 来进行资源的配置,并且还通过 aufs 来进一步提高文件系统的资源利用率。
其中的 aufs 是个很有意思的东西,是 UnionFS 的一种。他的思想和 git 有些类似,可以把对文件系统的改动当成一次 commit 一层层的叠加。这样的话多个容器之间就可以共享他们的文件系统层次,每个容器下面都是共享的文件系统层次,上面再是各自对文件系统改动的层次,这样的话极大的节省了对存储的需求,并且也能加速容器的启动。
现在应该有个大体了解了吧!
docker 特点
- docker 容器之间是相互隔离的。
- docker 比较轻量,启动非常快,秒级实现。
- 资源利用率比较高,一台机器可以跑上千个docker容器。
- 内核级别的虚拟化,不需要额外的hypevisor支持。子机的内核版本跟随母机 kernel 内核版本最低:2.6.32-573.18.1.el6
- 容易迁移,平台依赖性不强。
- 更快的交付和部署,一次创建配置,任意地方运行
docker和虚拟机的区别:
docker 是以进程状态存在于系统中
docker 比较轻量
docker核心概念:
- 镜像: 是一个只读的模板,类似于安装系统用到的iso镜像。
- 容器: 容器是镜像产生的一个进程,类似于虚拟机本身。
- 仓库: 存放镜像的一个场所。类似于github的仓库概念。分为共有仓库和私有仓库,本地可以做私有仓库。
最大的公开仓库: docker hub
国内公开仓库: dockerpool
OPENVZ OS模板: openvz.org
以下操作以官方镜像centos作为示例 具体命令示例中 ` 号是为了区分颜色,不属于命令中的部分。
docker安装启动:
centos 6
- # yum install -y epel-release
- # yum install -y docker-io
- # /etc/init.d/docker start
centos 7
- # yum install -y docker
- # systemctl start docker
ubuntu 15.10
- # sudo apt-get install -y docker-io
- # /etc/init.d/docker start
docker 镜像管理:
1. 查看本地镜像:
- # docker images
2. 在 docker 仓库中搜索镜像:
- # docker search 镜像名
- `例:docker search centos`
3. 从 docker.com 获取 centos 镜像 :
- # docker pull 镜像名
- `例:docker pull centos`
4. 修改镜像名: - 显示在 REPOSITORY 列
- # docker tag 原镜像名 自定义名
- `例:docker tag centos zhpro`
这个命令和下一个命令其实起到了新建 images 的作用,名称和 TAG 不同 但 IMAGE_ID 与原 image 相同
5. 修改镜像名 + TAG
- # docker tag 原镜像名 自义定名:自定义TAG名
- # docker tag IMAGE_ID 自义定名:自定义TAG名
6. 删除一个镜像:
- # docker rmi 镜像名
- # docker rmi 镜像名:TAG
如果想镜像改过 镜像名或 TAG,又想用镜像名的方法删除这个镜像,后面必须跟上 TAG 名
# docker rmi IMAGE ID 当参数是 IMAGE ID 时 要注意其他镜像中是否有使用相同 IMAGE_ID 的其他镜像
7. 将修改过内容的容器保存为一个自定义的镜像
- # docker commit -m "自定义commit信息" -a "作者名称" CONNTATINER_ID 自定义镜像名称
- *`例:docker commit -m "centos-with-iproute" -a "zhpro" 0da8fd9b82ce mycentos`*
8. 将一个现有的镜像导出为一个文件 - 会保存在当前目录下
- # docker save -o 自定义文件名.tar 镜像名
- # docker save -o 自定义文件名.tar IMAGE_ID
- *`例: docker save -o mycentos.tar centos`*
- *`例: docker save -o mycentos.tar 1ff051d6c93f`*
9. 将文件恢复到本地镜像 - 创建一个新的镜像
- # docker load --input 镜像名.tar
- # docker load < 镜像名.tar
- `例:docker load -i mycentos.tar`
- `例:docker load < mycentos.tar`
--input也可以替换成 -i , 有时候导入的镜像,docker images会显示镜像名和 TAG 为 none 需要用docker tag IMAGE_ID 自定义镜像名 去修改
10. 推送一个镜像到dockerhub.com
- # docker push IMAGE_NAME
- `例:docker push mycentos`
docker容器管理:
1. 依托镜像创建并启动一个容器 :
- # docker run -it 镜像名 /bin/bash
- `例: docker run -it centos /bin/bash`
- `例: docker run -it mycentos:with-iproute /bin/bash`
如果是改过 TAG的镜像 要加 镜像名:TAG 因为默认的 TAG 为 latest
要把 选项 放到镜像名前
-i 表示让容器的标准输入打开
-t 表示分配一个伪终端
/bin/bash 表示在这个容器里面运行的一个命令 也可以直接写为 bash
-d 选项直接放到后台运行 如不加 -d ,exit后,这个容器会关闭
--name 指定容器的NAME列显示 例:# docker run -itd --name zzhcentos mycentos:latest bash
-p 可以指定容器的映射端口 格式为 -p 宿主机端口:容器端口 可见创建私有仓库一节
2. 创建一个容器并不启动
- # docker create -it 镜像名
3. 启动关闭一个容器:
- # docker start CONTAINER_ID/NAME
- # docker stop CONTAINER_ID/NAME
- 例:`docker start 0da8`
- `docker start zhpro`
CONTAINER_ID NAME 为dokcer ps -a出来的第一列 和最后的NAMES列
也可以同时启动关闭多个:
- 例: `# docker start zhpro1 zhpro2 zhpro3`
4. 进入一个容器
- # docker exec -it CONTAINER_ID /bin/bash
- # docker attach CONTAINER_ID
使用 attach 命令如果退出容器,这个容器同时也会终止
如果用--name 指定了容器名的话,后面也可以跟容器名 例: #docker exec -it zhpro bash
5. 查看正在运行的容器或所有容器:
- # docker ps
- # docker ps -a
6. 删除一个容器:
- # docker rm CONTAINER_ID/NAME
-f 选项可强制删除一个容器,如果这个容器正在运行的话
也可以同时删除多个
- 例:`# docker rm zhpro1 zhpro2 zhpro3`
7. 启动容器的同时也可以运行一些命令
- 例: `# docker run -d centos bash -c "while :;do echo "123"; sleep 1;done " `*
8. 查看容器的日志输出信息
- # docker logs CONTAINER_ID
9. 导入导出容器 -方便迁移
- # docker export CONTAINER_ID > file.tar
- # cat file.tar | docker import - 自定义镜像名
为什么导入时候是镜像名呢? 因为容器是作为一个镜像导入的,需要在镜像的基础上,在去产生并 dock run 这个容器
比如说openvz下载下来的镜像其实是容器需要这样导入:
- # cat 包名.tar.gz |docker import - 镜像名(可自定义)
- `例: #cat entos-6-x86_64-minimal.tar.gz|docker import - centosopenvz`
10. 新建一个容器,退出时自动删除这个容器
--rm
docker 仓库管理
1. 下载私有仓库镜像
- # docker pull registry
regisgty 镜像用来创建私有仓库
2. 依托registry镜像来创建容器
- # docker run -d -p 5000:5000 registry
-d 放到后台运行
-p 做一个端口映射,宿主机端口:容器端口 访问宿主机5000端口,就相当于访问该容器
当 docker ps的时候,会在 PORTS 列显示 0.0.0.0:5000->5000/tcp
用 curl 127.0.0.1:5000 测试 会显示"\"docker-registry server\""
3. 上传一个镜像到私有仓库
- 首先要给这个镜像做tag
必须带有私有仓库的 ip:port 要不然会上传到共有仓库里面去
- # docker tag 镜像名 私有库ip:port/镜像名
- `例:docker tag centos 192.168.3.4:5000/centos`
- 其次更改https为http
在 /etc/init.d/docker 中加入 --insecure-registry 本机ip地址:5000
$exec -d $other_args &>> $logfile &
修改为:
$exec -d --insecure-registry 192.168.3.4:5000 $other_args &>> $logfile &
重启 docker 并启动 registry 容器
- 上传
- # docker push 私有库ip:port的镜像名
- `例:docker push 192.168.3.4:5000/centos`
4. 使用 Docker 的 RESTful API 可以查看仓库服务器中的镜像:
- # curl http://ip:port/v1/search
- `例:curl http://192.168.3.4:5000/v1/search`
结果示例:
{"num_results": 2, "query": "", "results": [
{"description": "", "name": "library/busybox"},
{"description": "", "name": "library/centos"}]}
也可以用 docker search 命令
- 例:`# docker search 192.168.3.4:5000/cent
5. 拉取私有库的镜像
需要指定私有库的地址端口
- 例:`# docker pull 192.168.3.4/centos
docker 数据管理
1. 将本地目录挂载到容器目录
- # docker run -itd --name=zhpro -v /data/:/data/ 镜像名/IMAGE_ID bash
可以用其他容器再挂载这个容器的目录 实现多个容器和本地目录同步、共享 如 2.挂载其它容器的目录
-d 选项表示后台运行
-v 选项用来指定挂载(创建映射关系)的目录
/data/:/data/ 宿主机目录:容器目录 名字不一定要相同 只要自己指定好对应的目录就可以 宿主机要手动去创建这个目录 容器会自动生成这个目录
镜像名/IMAGE_ID 为 #docker images 显示的 REPOSITORY 列的 镜像名 或者 IMAGE_ID 列的 id
2. 挂载其它容器的目录
- # docker run -itd --name zhpro1 --volumes-from 容器名/CONTAINER_ID 镜像名/IMAGE_ID bash
- `例: docker run -itd --name zhpro1 --volumes-from zhpro centos bash`
本列中就为挂载 上面创建的 zhpro 这个容器 的/data目录 同时也可以自定义新容器名zhpro1 依赖镜像为centos
--volumes-from 镜像名/IMAGE_ID 指定数据卷容器来源
删除Volumes
这个功能可能会更加重要,如果你已经使用docker rm来删除你的容器,那可能有很多的孤立的Volume仍在占用着空间。
Volume只有在下列情况下才能被删除:
该容器是用docker rm -v命令来删除的(-v是必不可少的)。
docker run中使用了--rm参数
即使用以上两种命令,也只能删除没有容器连接的Volume。连接到用户指定主机目录的Volume永远不会被docker删除。
除非你已经很小心的,总是像这样来运行容器,否则你将会在/var/lib/docker/vfs/dir目录下得到一些僵尸文件和目录,并且还不容易说出它们到底代表什么。
3.数据卷容器
- # docker run -itd --name 自定义容器名 -v /data 镜像名/IMAGE_ID bash
- `例: docker run -itd --name zhpro2 -v /data/ centos bash`
数据卷容器:类似于nfs 创建一个容器挂载宿主机目录,其它容器都映射这个容器的该目录
-v 也可以只指定一个目录 如:
-v /data 此处的/data为容器的目录,不是本地的目录,意思是把这个容器的目录共享出去让其他容器挂载
针对 3.数据卷容器 这种情况,容器没有映射本地的目录,如何备份还原数据呢?
思路是这样的: 假设 数据卷容器1 没有映射本地目录,创建 容器2 挂载 数据卷容器1 目录的同时,再去映射一个本地目录到自己的目录,用这个目录来作为桥梁。在 容器2 里面实现 数据卷容器1 的备份和还原。
假设映射的目录为 backup:backup 当然,你要去手动创建这个本地目录backup
具体命令如下:
- # docker run -itd --name 容器2 --volumes-from 容器1 -v /backup/:/backup/ centos bash
容器即使做过--volumes-from ,备保存为镜像后,依托这个镜像再产生一个容器的话,还是需要用 --volumes-from 来指定数据卷容器来源
docker 网络管理
1. docer 的 4 种网络模式
- host 模式 : 在 docker run 时,用 --net=host 指定 。这种模式下容器复用了宿主机的网卡,容器内的 ip 和宿主机的 ip 是一致的。包括主机名都是一样的。
- container 模式: 使用--net=container:CONTAINER_ID/CONTAINER_NAME 指定。多个容器使用共同的网络。
CONTAINER_ID/CONTAINER_NAME 指复制哪个容器的网络模式。
如下面这个示例:这个新建的test容器就复用了zhpro这个容器的网络模式。ip和test容器的ip是一致的。
- 例: #docker run --it --name=test --container:zhpro centos bash
- none 模式 : 使用 --net=none 指定,这个模式下的容器会不配置任何网络,只有一个lo网卡
- bridge 模式 : 使用 --net=bridge 指定。模式不指定就是这样模式,类似于 vmware 的 NAT 模式。同一个宿主机的容器在一个网段下,之间可以相互通信。
2. docker 网桥
http://blog.opskumu.com/images/vethbridge.png
bridge 模式是 Docker 默认的网络设置,此模式会为每一个容器分配 Network Namespace、设置 IP 等,并将一个主机上的 Docker 容器连接到一个虚拟网桥上。当 Docker server 启动时,会在主机上创建一个名为 docker0 的虚拟网桥,此主机上启动的 Docker 容器会连接到这个虚拟网桥上。虚拟网桥的工作方式和物理交换机类似,这样主机上的所有容器就通过交换机连在了一个二层网络中。接下来就要为容器分配 IP 了,Docker 会从 RFC1918 所定义的私有 IP 网段中,选择一个和宿主机不同的IP地址和子网分配给 docker0,连接到 docker0 的容器就从这个子网中选择一个未占用的 IP 使用。如一般 Docker 会使用 172.17.0.0/16 这个网段,并将 172.17.42.1/16 分配给 docker0 网桥(在主机上使用 ifconfig 命令是可以看到 docker0 的,可以认为它是网桥的管理接口,在宿主机上作为一块虚拟网卡使用)
2.1 列出当前主机网桥
- # brctl show
- 或者
- # brctl show docker0
brctl 工具依赖bridge-utils 包
2.2 查看当前 docker0 ip
- # ifconfig docker0
- 或者
- # ip addr show docker0
2.3 自定义 docker0 网桥的网段
默认情况下 docker0 会分配172.1.42 或者192.168.42 这个网段。
我们也可以手动更改这个网段为 192.168.10.0/24。
要注意顺序:
- # /etc/init.d/docker stop #停掉docker服务
- # ip link set dev docker0 down #停掉网桥docker0
- # ip addr add 192.168.10.1/24 dev docker0 #给docker0添加地址
- # ip addr del 192.168.42.1/24 dev docker0 #删除docker0原有的地址
- # ip link set dev docker0 up #启动网桥docker0
- # /etc/init.d/docker start #启动服务
2.3 自定义网桥
http://cdn.infoqstatic.com/statics_s2_20160301-0105u7/resource/articles/docker-network-and-pipework-open-source-explanation-practice/zh/resources/1419249139653.png
Docker完成以上网络配置的过程大致是这样的:
1. 在主机上创建一对虚拟网卡veth pair设备。veth设备总是成对出现的,它们组成了一个数据的通道,数据从一个设备进入,就会从另一个设备出来。因此,veth设备常用来连接两个网络设备。
2. Docker将veth pair设备的一端放在新创建的容器中,并命名为eth0。另一端放在主机中,以veth65f9这样类似的名字命名,并将这个网络设备加入到docker0网桥中,可以通过brctl show命令查看。
3. 从 docker0 子网中分配一个IP给容器使用,并设置docker0的IP地址为容器的默认网关。
Docker 会尝试寻找没有被主机使用的 ip 段,尽管它适用于大多数情况下,但是它不是万能的,有时候我们还是需要对 ip 进一步规划。在启动 Docker 服务的时候,使用 -b BRIDGE 或 --bridge=BRIDGE 来指定使用的网桥,需要安装 bridge-utils软件包。
这里我们新建一个网桥 br0 作为 docker 的默认网桥 基本步骤如下:
1. 停止服务删除旧网桥
2. 创建自定义网桥
3. 确认新网桥并启动
4. 配置 docker 默认网桥
停止服务删除旧网桥
- # service docker stop
- # ip link set dev docker0 down
- # brctl delbr docker0
创建新网桥br0
- # brctl addbr br0
- # ip addr add 192.168.100.1/24 dev br0
查看确认新网桥并启动
- # ip addr show br0 或者 brctl show br0
- # ip link set dev br0 up
配置 docker 服务,默认连接到 网桥br0 上 .并启动docker
- # echo 'DOCKER_OPTS="-b=br0"' >> /etc/default/docker
- # service docker start
启动 Docker 服务。 新建一个容器,可以看到它已经桥接到了 br0 上。
可以继续用 brctl show 命令查看桥接的信息。另外,在容器中可以使用 ip addr 和 ip route 命令来查看 IP 地址配置和路由信息。
3. 外部网络访问容器资源
使用 -p 本机端口:容器端口 来实现本机端口和容器端口的映射,通过localhost:映射端口 实现容器内应用的访问。
- 首先需要有一个安装了 httpd 服务的镜像,或者在为安装httpd服务的容器中安装httpd服务,并将这个容器保存为一个镜像,再依托这个镜像去产生一个新的容器,因为用 -p 本机端口:容器端口 去做端口映射 是在容器产生的时候去做的。
此过程省略,下面我们用安装了 httpd 服务的镜像来做示例,假设镜像为 web 新建容器名 web1 ( 映射本机端口 5123 到容器端口 80 )
- 例: `# docker run -itd --name web1 -p 5123:80 web bash`
- 测试
可以在 #docker ps PORTS 列看到这个映射关系 0.0.0.0:5123->80/tcp
- 容器web1内:`# curl localhost`
- 宿主机:`# curl localhost:5123`
- 也可以使用浏览器访问 :`http://ip
4. 容器互联
--link 容器名:别名
比如容器 c6mysql 是台 db容器,依托 centos6mysql 镜像创建 假设 ip 为192.168.3.4
c6httpd 是台 web容器,依托 centos6httpd 镜像创建
在web容器刚创建的时候可以指定 --link c6mysql:db
这样web容器就可以以host和别名的方式与db容器来链接
- 例:`# docker run --itd --c6httpd --link c6mysql:db centos6httpd bash
这样,在web容器里面 #ping db #ping c6mysql 都是去往3.4这个地址,也就是db容器。
也可以在web容器里面 /etc/hosts 及 # env 查看到相关的信息。
至于这样的方式具体有什么用,目前还不是很清楚。
当然,我们也可以针对上面例子扩展一下:
建立2个容器db和web同时做端口映射来提供服务。
- 例:
- `# docker run -itd --name c6mysql -p 13306:3306 centos6mysql bash
- # docker run -itd --name c6httpd -p 10080:80 --link c6mysql:db centos6httpd bash`
5. docker 配置桥接网络
- 如果系统是 centos 6
- 如果系统是 centos 7
编辑回复