当前位置:   article > 正文

docker基础篇——万字解读小鲸鱼_不需要小鲸鱼打包docker

不需要小鲸鱼打包docker

        

目录

前言        

为什么会出现docker?

背景

docker理念

容器和虚拟机比较

容器发展简史

 容器虚拟化技术

Why Docker

docker的基本组成

  镜像(image)

容器(container)

仓库(repository)

总结

第一个docker镜像——hello-world

run干了什么

docker为什么比虚拟机快

帮助启动类命令

帮助命令

镜像命令

docker images 查看所有本地主机上的镜像

docker search 搜索镜像

docker pull 下载镜像

查看镜像/容器/数据卷所占的空间

删除镜像

容器命令

新建容器并启动

列出运行中的程序

退出容器

删除容器

启动和停止容器操作

后台启动容器

前台启动和后台启动区别

Detached (-d)

Foregroud(不写-d)

其他常用命令(重点)

查看日志

查看容器中的进程信息

查看镜像的元数据

进入当前正在运行的容器

方式一

方式二

attach与exec对比:

从容器拷贝文件到主机上

 导入和导出容器

Docker镜像

镜像是什么?

镜像是分层的

   UnionFS(联合文件系统)

Docker镜像加载原理

为什么 Docker 镜像要采用这种分层结构呢?

  重点理解

Docker容器数据卷

数据卷是什么?

数据卷能干什么?

 容器和宿主机之间数据共享

查看数据卷是否挂载成功 

  读写规则限制说明

卷的继承和共享

 容器1完成和宿主机的映射 

容器2继承容器1的卷规则

继承举例

思考

总结一波当前学的命令:

练习:下载nginx

端口暴露

查找ngnix的位置 whereis

作业: docker来装一个tomcat

       

前言 

         大家好,我是躺平哥,这是我的docker学习笔记基础篇,用来记录自己的学历历程,我感觉学习docker主要是多敲多练多看。我也是个小白,如果有写的不好的地方也请大家多多指点!     

为什么会出现docker?

背景

在公司开发过程中,开发需要清楚的告诉运维部署团队,用的全部配置文件+所有软件环境。不过,即便如此,仍然常常发生部署失败的状况。

于是——

docker诞生了

Docker的出现使得Docker得以打破过去「程序即应用」的观念。

透过镜像(images)将作业系统核心除外,运作应用程式所需要的系统环境,由下而上打包,达到应用程式跨平台间的无缝接轨运作。

大白话来讲:就是我把环境连带程序都给你一块打包起来了!

docker理念

Docker是基于Go语言实现的云开源项目。

只需要一次配置好环境,换到别的机子上就可以一键部署好,大大简化了操作。

     一句话:

   解决了运行环境配置问题的软件容器, 方便做持续集成并有助于整体发布的容器虚拟化技术。

在了解docker之前我们先聊聊虚拟机,虚拟机也是一个伟大的发明!

容器和虚拟机比较

容器发展简史

传统虚拟机的缺点

  • 资源占用多,因为它要模拟出一整套操作系统
  • 冗余步骤多,虚拟机创建出一个操作系统往往要有很多步骤
  • 启动慢,往往好几分钟。

 容器虚拟化技术

由于前面虚拟机存在某些缺点,Linux发展出了另一种虚拟化技术:

Linux容器(Linux Containers,缩写为 LXC)

Linux 容器不是模拟一个完整的操作系统而是对进程进行隔离。

有了容器,就可以将软件运行所需的所有资源打包到一个隔离的容器中。

容器与虚拟机不同,不需要捆绑一整套操作系统,只需要软件工作所需的库资源和设置。

*容器内的应用进程直接运行于宿主的内核,容器内没有自己的内核且也没有进行硬件虚拟。因此容器要比传统虚拟机更为轻便。

例如下图:

那话又说回来了——

Why Docker

更轻量: 基于容器的虚拟化,仅包含业务运行所需的runtime环境,CentOS/Ubuntu基础镜像仅170M;宿主机可部署100~ 1000个容器

●更高效:无操作系统虚拟化开销

●计算:轻量,无极外开销

#存储:系统盘aufs/dmloverlayts; 数据盘volume

●网络:宿主机网络,NS隔离

.更敏捷、更灵活:

●分层的存储和包管理,devops理念

●支持多种网络配置

docker的基本组成

下面的图可以很清楚的看出docker各个组成之间的转换关系,以及对应命令:

  镜像(image)

Docker 镜像(Image)就是一个只读的模板。镜像可以用来创建 Docker 容器,一个镜像可以建很多容器。

它也相当于是一个root文件系统。比如官方镜像 centos:7 就包含了完整的一套 centos:7 最小系统的 root 文件系统。

相当于容器的“源代码”,docker镜像文件类似于Java的类模板,而docker容器实例类似于java中new出来的实例对象。

容器(container)

下面我们从以下两个角度来介绍容器 :

1 从面向对象角度

        Docker 利用容器(Container)独立运行的一个或一组应用,应用程序或服务运行在容器里面,容器就类似于一个虚拟化的运行环境,容器是用镜像创建的运行实例。就像是Java中的类和实例对象一样,镜像是静态的定义,容器是镜像运行时的实体。容器为镜像提供了一个标准的和隔离的运行环境,它可以被启动、开始、停止、删除。每个容器都是相互隔离的、保证安全的平台

2 从镜像容器角度

可以把容器看做是一个简易版的 Linux 环境(包括root用户权限、进程空间、用户空间和网络空间等)和运行在其中的应用程序。

仓库(repository)

仓库(Repository)是集中存放镜像文件的场所。

最大的公开仓库是 DockerHub(Docker Hub

tar与dockerfile以后会说

总结

镜像文件

  • image 文件生成的容器实例,本身也是一个文件,称为镜像文件。

容器实例

  •  一个容器运行一种服务,当我们需要的时候,就可以通过docker客户端创建一个对应的运行实例,也就是我们的容器

仓库

  • 就是放一堆镜像的地方,我们可以把镜像发布到仓库中,需要的时候再从仓库中拉下来就可以了。

第一个docker镜像——hello-world

run干了什么

docker为什么比虚拟机快

(1)docker有着比虚拟机更少的抽象层:

(2)docker利用的是linux的内核,而不需要加载操作系统OS内核

帮助启动类命令

  • 启动docker : systemctl start docker
  • 停止docker : systemctl stop docker
  • 重启docker : systemctl restart docker
  • 查看docker状态: systemctl status docker
  • 开机启动 : systemctl enable docker
  • 查看docker概要信息: docker info
  • 查看docker 总体帮助文档: docker --help
  • 查看docker命令帮助文档: docker 具体命令 --help

帮助命令

docker version //显示docker 的版本信息

docker info //显示docker 的系统信息 包括镜像和容器数量

docker 命令 -- help

镜像命令

docker images 查看所有本地主机上的镜像

REPOSITORY 仓库的镜像

TAG 镜像的标签,也就是镜像版本号,不写默认最新版

IMAGE ID 镜像的id

CREATE 镜像的创建时间

SIZE 镜像的大小

可选项

  • -a --all 列出所有镜像
  • -q --quiet 只列出镜像的id

docker search 搜索镜像

可选项,通过搜索来过滤

[root@ecs-287241 ~]# docker search mysql --filter=stars=3000

NAME DESCRIPTION STARS OFFICIAL AUTOMATED

mysql MySQL is a widely used, open-source relation… 13384 [OK]

mariadb MariaDB Server is a high performing open sou… 5109 [OK]

[root@ecs-287241 ~]#

筛选starts大于3000的

docker pull 下载镜像

下载镜像 docker pull 镜像名 [: tag版本]

  • docker pull mysql
  • 如果不写tag,默认就是latest
  • 分层下载,这是docker images 的核心,联合文件信息

查看镜像/容器/数据卷所占的空间

docker system df

删除镜像

docker rmi id

[root@ecs-287241 ~]# docker rmi -f feb5d9fea6a5

Untagged: hello-world:latest

Untagged: hello-world@sha256:18a657d0cc1c7d0678a3fbea8b7eb4918bba25968d3e1b0adebfa71caddbc346

Deleted: sha256:feb5d9fea6a5e9606aa995e879d862b825965ba48de054caab5ef356dc6b3412

[root@ecs-287241 ~]# docker images

 面试题:谈谈docker虚悬镜像是什么?

·        是什么

·        仓库名、标签都是<none>的镜像,俗称虚悬镜像dangling image

·        长什么样

·        后续Dockerfile章节再介绍

容器命令

说明:我们有了镜像才可以创建容器,linux,下载一个centos来学习

docker pull centos (centos小型的服务器)

新建容器并启动

docker run [可选参数] image

#参数说明(最常用参数)

--name="Name" 容器名字 tomcat1 tomcat2 用来区分容器

-d 后台方式运行

-i:以交互模式运行容器,通常与 -t 同时使用;

-t:为容器重新分配一个伪输入终端,通常与 -i 同时使用;

也即启动交互式容器(前台有伪终端,等待交互),就是等待你输入命令!

-it 使用交互方式运行,进入容器查看内容


为什么要写 -it?

不写-it你会发现是这样的

我们想要一个终端进行交互!输入命令,所以必须写!

后面往往还要写上/bin/bash: 是指此脚本使用/bin/bash来解释执行。/bin/bash:放在镜像名后的shell命令,这里我们希望有个交互式 Shell,因此用的是 /bin/bash。

#使用镜像centos:latest以交互模式启动一个容器,在容器内执行/bin/bash命令。

docker run -it centos /bin/bash

或者

docker run -it centos bash 也可以!即使你没写/bin/bash 他也会默认在你后面带一个shell脚本


-p 指定容器的端口 -p 8080:8080

-p IP:主机端口:容器端口

-p 主机端口:容器端口 (常用)

-p 容器端口

-P: 随机端口映射,大写P

-p: 指定端口映射,小写p

一般我们用小p

如下图:

左边是宿主机暴露的端口,右边是docker访问redis暴露的端口

启动 并进入容器

[root@ecs-287241 ~]# docker run -it centos /bin/bash

查看容器内的centos,基础镜像都是不完善的

[root@882655ac322c /]# ls

bin etc lib lost+found mnt proc run srv tmp var

dev home lib64 media opt root sbin sys usr

从容器中退到主机

exit

列出运行中的程序

docker ps 命令

-a 当前正在运行,带出历史运行过的容器。

-l:显示最近创建的容器。

-n=? 最近创建的容器。

-q 静默模式,只显示容器的编号。

退出容器

exit 直接容器停止并退出

ctrl +p +q 容器不停止退出

删除容器

docker rm 容器id 删除指定的容器,不能删除正在运行的容器,如果要强制删除 rm -f

可以先docker stop 停止容器 再docker rm

docker rm -f $(docker ps -aq) 删除所有的容器

docker ps -a -qlxargs docker rm

启动和停止容器操作

docker satrt 容器id 启动容器

docker restart 容器id 重启容器

docker stop 容器id 停止当前正在运行的容器

docker kill 容器id 强制停止当前容器

后台启动容器

  • 命令 docker run -d 镜像名!
  • docker run -d centos
  • 问题docker ps,发现了 centos停止了
  • docker run 之后一定要docker ps查看容器是否运行!
  • 常见的坑docker 容器使用后台运行,就必须要有一个前台进程,docker发现没有应用,就会自动停止 。
  • ngnix ,容器启动后,发现自己没有提供服务,就会立即停止,就是没有程序了
  • Nginx (engine x) 是一个高性能的HTTP和反向代理web服务器

前台启动和后台启动区别

  • 前台交互式启动
  •      docker run -it redis:6.0.8
  •      后台守护式启动
  •       docker run -d redis:6.0.8

Detached (-d)

如果在docker run后面追加-d=true或者-d,那么容器将会运行在后台模式。此时所有I/O数据只能通过网络资源或者共享卷组来进行交互。因为容器不再监听你执行docker run的这个终端命令行窗口。但你可以通过执行docker attach来重新附着到该容器的会话中。需要注意的是,容器运行在后台模式下,是不能使用--rm选项的。

Foregroud(不写-d)

在前台模式下(不指定-d参数即可),Docker会在容器中启动进程,同时将当前的命令行窗口附着到容器的标准输入、标准输出和标准错误中。也就是说容器中所有的输出都可以在当前窗口中看到。甚至它都可以虚拟出一个TTY窗口,来执行信号中断。

我们要在在容器中干活!

以redis容器为例,在里面干活

两个开发方式对比,想必你更能体会到容器的威力!

其他常用命令(重点)

查看日志

查看容器日志:docker logs 容器ID

docker logs -f -t --tail 容器,没有日志

显示日志

-tf 显示日志

--tail number 显示日志条数

查看容器中的进程信息

查看容器内运行的进程

命令: docker top 容器id

查看镜像的元数据

命令:

 查看容器内部细节

docker inspect 容器 id

进入当前正在运行的容器

我们通常容器都是使用后台放式运行的,需要进入容器,修改一些配置

方式一

#命令

docker exec -it 容器id bashShell

[root@ecs-287241 /]# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 6e5cf171dca0 centos "/bin/bash" 10 minutes ago Up 10 minutes serene_goodall [root@ecs-287241 /]# docker exec -it 6e5cf171dca0 /bin/bash [root@6e5cf171dca0 /]# ls bin etc lib lost+found mnt proc run srv tmp var dev home lib64 media opt root sbin sys usr [root@6e5cf171dca0 /]# ps -ef UID PID PPID C STIME TTY TIME CMD root 1 0 0 13:42 pts/0 00:00:00 /bin/bash root 15 0 0 13:54 pts/1 00:00:00 /bin/bash root 30 15 0 13:55 pts/1 00:00:00 ps -ef [root@6e5cf171dca0 /]#

方式二

docker attach 容器id

进入后会发现正在执行当前代码!

attach与exec对比:

    attach 直接进入容器启动命令的终端,不会启动新的进程 用exit退出,会导致容器的停止。

    exec 是在容器中打开新的终端,并且可以启动新的进程 用exit退出,不会导致容器的停止。

docker exec 进入容器后开启一个新的终端,可以在里面操作(常用)

docker attach 进入容器正在执行的终端,不会启动新的进程

从容器拷贝文件到主机上

为了防止别人或者自己误删!所以要做备份

docker cp 容器id :容器内路径 目的主机路径

[root@ecs-287241 home]# docker cp 6e5cf171dca0:/home/test.java /home [root@ecs-287241 home]# ls one1 test1 test.java wei wei.java www [root@ecs-287241 home]#

拷贝是一个手动过程,未来我们使用 -v卷的技术,可以实现,自动同步

即容器内的home目录和主机内的home目录连通,打通

 导入和导出容器

比拷贝docker cp 更加强大,这是拷贝整个容器!

·        export 导出容器的内容留作为一个tar归档文件[对应import命令]

·        docker export 容器ID > 文件名.tar

·        import 从tar包中的内容创建一个新的文件系统再导入为镜像[对应export]

·        cat 文件名.tar | docker import - 镜像用户/镜像名:镜像版本号

删除之后,利用import将abcd.tar变成一个镜像,然后在通过run运行成为一个容器。

下面我们来深入探究一下docker运行原理!

Docker镜像

镜像是什么?

镜像

是一种轻量级、可执行的独立软件包,它包含运行某个软件所需的所有内容,我们把应用程序和配置依赖打包好形成一个可交付的运行环境(包括代码、运行时需要的库、环境变量和配置文件等),这个打包好的运行环境就是image镜像文件。

只有通过这个镜像文件才能生成Docker容器实例!

镜像是分层的

以我们的pull为例,在下载的过程中我们可以看到docker的镜像好像是在一层一层的在下载

一层一层的下载! 因此镜像是分层的。

   UnionFS(联合文件系统)

UnionFS(联合文件系统):

Union文件系统(UnionFS)是一种分层、轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下。

Union文件系统是 Docker 镜像的基础。镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。

特性:一次同时加载多个文件系统,但从外面看起来,只能看到一个文件系统,联合加载会把各层文件系统叠加起来,这样最终的文件系统会包含所有底层的文件和目录。

Docker镜像加载原理

Docker镜像加载原理:

   docker的镜像实际上由一层一层的文件系统组成,这种层级的文件系统UnionFS。

bootfs(boot file system)主要包含bootloader和kernel, bootloader主要是引导加载kernel, Linux刚启动时会加载bootfs文件系统,在Docker镜像的最底层是引导文件系统bootfs。

这一层与我们典型的Linux/Unix系统是一样的,包含boot加载器和内核。

当boot加载完成之后整个内核就都在内存中了,此时内存的使用权已由bootfs转交给内核,此时系统也会卸载bootfs。

rootfs (root file system) ,在bootfs之上。包含的就是典型 Linux 系统中的 /dev, /proc, /bin, /etc 等标准目录和文件。rootfs就是各种不同的操作系统发行版,比如Ubuntu,Centos等等。 

说白了我们只要bootfs和rootfs!

平时我们安装进虚拟机的CentOS都是好几个G,为什么docker这里才200M??

对于一个精简的OS,rootfs可以很小,只需要包括最基本的命令、工具和程序库就可以了,因为底层直接用宿主机的linux内核(Host的kernel),自己只需要提供 rootfs 就行了

由此可见对于不同的linux发行版, bootfs基本是一致的, rootfs会有差别, 因此不同的发行版可以公用bootfs。

就像这样:

那么话说回来了——

为什么 Docker 镜像要采用这种分层结构呢?

其实就是像乐高积木一样,想用哪块拿来组装就行了!

镜像分层最大的一个好处就是共享资源,方便复制迁移,就是为了复用。

比如说有多个镜像都从相同的 base 镜像构建而来,那么 Docker Host 只需在磁盘上保存一份 base 镜像;

同时内存中也只需加载一份 base 镜像,就可以为所有容器服务了。而且镜像的每一层都可以被共享。 

一句话来讲分层的意义:

加载简单,资源复用

  重点理解

·        Docker镜像层都是只读的,容器层是可写的当容器启动时,一个新的可写层被加载到镜像的顶部。 这一层通常被称作“容器层”,“容器层”之下的都叫“镜像层”。

所有对容器的改动——无论添加、删除、还是修改文件都只会发生在容器层中。只有容器层是可写的,容器层下面的所有镜像层都是只读的。

Docker容器数据卷

     坑:容器卷记得加入

      --privileged=true

   why? 

 Docker挂载主机目录访问如果出现cannotopen directory .: Permission denied

解决办法:在挂载目录后多加一个--privileged=true参数即可

如果是CentOS7安全模块会比之前系统版本加强,不安全的会先禁止,所以目录挂载的情况被默认为不安全的行为,

在SELinux里面挂载目录被禁止掉了额,如果要开启,我们一般使用--privileged=true命令,扩大容器的权限解决挂载目录没有权限的问题,也即使用该参数,container内的root拥有真正的root权限,否则,container内的root只是外部的一个普通用户权限。一个容器就相当于是一个小的linux内核。

数据卷是什么?

卷就是目录或文件,存在于一个或多个容器中,由docker挂载到容器,但不属于联合文件系统,因此能够绕过Union File System提供一些用于持续存储或共享数据的特性:

卷的设计目的就是数据的持久化,完全独立于容器的生存周期,因此Docker不会在容器删除时删除其挂载的数据卷。

·        一句话:有点类似我们Redis里面的rdb和aof文件

·        将docker容器内的数据保存进宿主机的磁盘中

·        运行一个带有容器卷存储功能的容器实例

·         docker run -it --privileged=true -v /宿主机绝对路径目录:/容器内目录      镜像名

映射我们用“ :”,即冒号表示。

数据卷能干什么?

将运用与运行的环境打包镜像,run后形成容器实例运行 ,但是我们对数据的要求希望是久化的。 

Docker容器产生的数据,如果不备份,那么当容器实例删除后,容器内的数据自然也就没了。

为了能保存数据在docker中我们使用卷。 

特点:

1:数据卷可在容器之间共享或重用数据。

2:卷中的更改可以直接实时生效,爽。

3:数据卷中的更改不会包含在镜像的更新中。

4:数据卷的生命周期一直持续到没有容器使用它为止。

 容器和宿主机之间数据共享

docker:

host宿主机

查看数据卷是否挂载成功 

docker inspect 容器ID

1  docker修改,主机同步获得 

2 主机修改,docker同步获得

3 docker容器stop,主机修改,docker容器重启数据依旧同步。

因此即使是docker容器停止了也能够同步数据

  读写规则限制说明

只限制容器,没有限制主机

 rw = read + write

·         docker run -it --privileged=true -v /宿主机绝对路径目录:/容器内目录:rw      镜像名

·        默认同上案例,默认就是rw

·        只读

·        容器实例内部被限制,只能读取不能写

 /容器目录:ro镜像名              就能完成功能,此时容器自己只能读取不能写  

ro = read only 

此时如果宿主机写入内容,可以同步给容器内,容器可以读取到。

docker run -it --privileged=true -v /宿主机绝对路径目录:/容器内目录:ro      镜像名

操作一下:

容器

宿主机

卷的继承和共享

 容器1完成和宿主机的映射 

docker run -it  --privileged=true -v /mydocker/u:/tmp --name u1ubuntu

容器2继承容器1的卷规则

·        docker run -it  --privileged=true --volumes-from 父类  --name u2 ubuntu

继承举例

u3继承u1

思考

u1stop掉了,u3和宿主机之间同步数据,u1start之后会同步数据吗?

会的!

总结一波当前学的命令:

1.容器上拷贝到linux上?cp

2.讲一个image启动成一个container容器?run

3.运行了容器怎么停止?kill/stop

4.怎么启动容器?start 怎么暂停?pause

5.logs:查看日志 inspect是干什么的?查看源数据

6.attach 进入已经启动的重端,而exec是启动一个新的终端

7.^p^q是以运行的方式退出

8.port 控制端口

9.ps可以看到当前所有的进程信息

10,top查看进程信息

port # 查看映射端口对应的容器内部源端口

pause # 暂停容器

ps # 猎户容器列表

pull # 从docker镜像源服务器拉取指定镜像或者库镜像

push # 推送指定镜像或者库镜像至docker源服务器

restart # 重启运行的容器

rm # 移除一个或多个容器

rmi # 移除一个或多个镜像 (无容器使用该镜像才可删除,否则需要删除相关容器才可继续或 -f 强制删除)

run # 创建一个新的容器并运行一个命令

save # 保存一个镜像为一个 tar 包【对应 load】

search # 在 docker hub 中搜索镜像

start # 启动容器

stop # 停止容器

tag # 给源中镜像打标签

top # 查看容器中运行的进程信息

unpause # 取消暂停容器

version # 查看 docker版本号

wait # 截取容器停止时的退出状态值

练习:下载nginx

端口暴露

[root@ecs-287241 home]# docker run -d --name ngimx01 -p 3344:80 nginx 303bb4275798c811290a237bde71456194ed41fe82fdc9b03fb72df87edb6fbc [root@ecs-287241 home]# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 303bb4275798 nginx "/docker-entrypoint.…" 5 seconds ago Up 4 seconds 0.0.0.0:3344->80/tcp, :::3344->80/tcp ngimx01 [root@ecs-287241 home]# curl localhost:3344 <!DOCTYPE html> <html> <head> <title>Welcome to nginx!</title> <style> html { color-scheme: light dark; } body { width: 35em; margin: 0 auto; font-family: Tahoma, Verdana, Arial, sans-serif; } </style> </head> <body> <h1>Welcome to nginx!</h1> <p>If you see this page, the nginx web server is successfully installed and working. Further configuration is required.</p> <p>For online documentation and support please refer to <a href="http://nginx.org/">nginx.org</a>.<br/> Commercial support is available at <a href="http://nginx.com/">nginx.com</a>.</p> <p><em>Thank you for using nginx.</em></p> </body> </html> [root@ecs-287241 home]#

查找ngnix的位置 whereis

root@303bb4275798:/# whereis ngnix

ngnix:地址

root@303bb4275798:/#

思考问题:我们每次改动nginx配置文件.都需要进入容器内部?

十分的麻烦,我要是可以在容器外部提供一个映射路径 ,达到在容器修改文件名,容器内部就可以自动修改? -v 数据卷

作业: docker来装一个tomcat

#官方的使用

docker run -it --rm tomcat:9.0

#我们之前的启动都是后台,停止了容器之后,容器还是可以查到

docker run -it --rm. 一般用来测试,用完就删除

 感谢大家观看!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Guff_9hys/article/detail/1013770
推荐阅读
相关标签
  

闽ICP备14008679号