码迷,mamicode.com
首页 > 其他好文 > 详细

Docker 深入学习

时间:2020-12-24 11:43:36      阅读:0      评论:0      收藏:0      [点我收藏+]

标签:目的   数据共享   mod   dep   包括   block   姓名   独立   elastic   

date: 2020-11-30 15:23:00
updated: 2020-12-07 15:35:00

Docker 深入学习

官方帮助文档

1. 命令

$ docker --help
用法:  docker COMMAND                                                                                                             
A self-sufficient runtime for containers                                                                                           
选项:                                                                                                                           
      --config string      Location of client config files (default                                                                
                           "C:\\Users\\junchow\\.docker")                                                                          
  -D, --debug              开启debug模式
  -H, --host list          Daemon socket(s) to connect to                                                                          
  -l, --log-level string   设置日志级别,级别分为debug|info|warn|error|fatal,默认为info。                                                                             
      --tls                Use TLS; implied by --tlsverify                                                                         
      --tlscacert string   Trust certs signed only by this CA (default                                                             
                           "C:\\Users\\junchow\\.docker\\machine\\machines\\default\\ca.pem")                                      
      --tlscert string     Path to TLS certificate file (default                                                                   
                           "C:\\Users\\junchow\\.docker\\machine\\machines\\default\\cert.pem")                                    
      --tlskey string      Path to TLS key file (default                                                                           
                           "C:\\Users\\junchow\\.docker\\machine\\machines\\default\\key.pem")                                     
      --tlsverify          Use TLS and verify the remote (default true)                                                            
  -v, --version            终端打印显示版本信息并退出                                                                                                                                   
管理命令:                                                                                                               
  config      管理Docker配置
  container   管理容器                                                                                                    
  image       管理镜像
  network     管理网络
  node        管理Swarm节点
  plugin      管理插件
  secret      管理Docker安全
  service     管理服务
  swarm       管理Swarm集群
  system      管理Docker系统
  trust       Manage trust on Docker images                                                                                        
  volume      管理卷
                                                                                                                                   
命令:                                                                                                                          
  attach      将标准输入和标准输出连接到正在运行的容器                                        
  build       使用dockerfile文件创建镜像                                                                                     
  commit      从容器的修改项中创建新的镜像
  cp          将容器的目录或文件复制到本地文件系统中
  create      创建一个新的镜像
  diff        检查容器文件系统的修改
  events      实时输出docker服务器中发生的事件
  exec        从外部运行容器内部的命令
  export      将容器的文件系统到处为tat文件包
  history     显示镜像的历史
  images      输出镜像列表
  import      从压缩为tar文件的文件系统中创建镜像
  info        显示当前系统信息、docker容器与镜像个数、设置信息等
  inspect     使用JSON格式显示容器与镜像的详细信息
  kill        向容器发送kill信号关闭容器
  load        从tar文件或标准输入中加载镜像
  login       登录docker注册服务器
  logout      退出docker注册服务器
  logs        输出容器日志信息
  pause       暂停容器中正在运行的所有进程
  port        查看容器的端口是否处于开放状态
  ps          输出容器列表
  pull        从注册服务器中拉取一个镜像或仓库
  push        将镜像推送到docker注册服务器
  rename      重命名一个容器
  restart     重启一个或多个容器
  rm          删除一个或多个容器,若没有指定标签则删除lastest标签。
  rmi         删除一个或多个镜像,若没有指定标签则删除lastest标签。                                                
  run         在一个新容器中中运行命令,用于指定镜像创建容器。
  save        将一个或多个镜像保存为tar包             
  search      从Docker Hub中搜索镜像
  start       启动一个或多个已经停止的容器
  stats       查看各个容器占用内存、CPU的情况                                                     
  stop        停止一个或多个正在运行的容器
  tag         设置镜像标签
  top         显示容器中正在运行的进程信息
  unpause     重启pause命令暂停的容器
  update      更新一个或多个容器的配置
  version     显示docker版本信息
  wait        等待容器终止然后输出退出码
Run ‘docker COMMAND --help‘ for more information on a command.                                                                     

技术图片

1.1 镜像命令

docker images   # 查询本机上所有的镜像

# 可选项
-a 显示全部
-q 只显示镜像ID
docker search 镜像名

# 可选项
-f=stars=3000  # 搜索STARS大于3000的
docker pull 镜像名[:tag]

针对不同版本,如果有一样的镜像文件,就不需要重复下载了
docker imi -f 镜像id                   # 删除指定镜像
docker imi -f 镜像id 镜像id 镜像id      # 删除多个镜像
docker imi -f $(docker images -q)     # 把$里面当成参数传递进去,获取所有的镜像id,然后全部删除

1.2 容器命令

docker run [可选参数] image

# 参数说明
--name="Name"   容器名字,用来区分容器
-d              后台方式运行
-i -t           使用交互方式运行,进入容器查看内容
-p              指定容器的端口 -p 8080:8000
    -p 主机端口:容器端口  # 进行映射
    -p 容器端口
-P              随机指定端口
docker ps # 列出所有正在运行的容器

# 可选项
-a      # 列出所有容器
-n=?    # 列出前n个容器
-q      # 只显示容器ID
exit 退出容器并停止
ctrl+P+Q 大写模式下,退出容器且不停止
dcoker rm 容器ID
docker rm -f $(docker ps -q) 删除所有的容器ID
docker start 容器ID
docker restart 容器ID
docker stop 容器ID
docker kill 容器ID   # 强制停止容器

1.3 常用其他命令

通过 docker run -d 镜像名 来启动,然后通过 docker ps 会发现容器停止了

=> docker 容器使用后台运行,就必须要有一个前台进程,docker 发现没有应用,就会自动停止
eg:nginx 容器启动后发现没有提供服务就会自动停止

docker logs     # 查看日志

# 可选项
-t  # 时间戳
-f  # 持续输出日志
    --tail number   # 显示最后的number条日志
docker top 容器ID   # 查看容器中的进程信息,其中pid是进程id,ppid是父进程id
docker inspect 容器ID/镜像ID   # 查看容器/镜像元数据,在下面有一个 layers 里面是镜像文件的每一层文件
很多容器都是后台运行,需要进入容器,修改一些配置

方式一:
docker exec -it 容器ID /bin/bash    # 以命令行的方式来进行交互
# 进入容器后会开启一个新的终端,可以在终端里操作

方式二:
docker attach 容器ID    # 会进入到正在执行的终端,不会启动新的进程
docker cp 容器ID:容器内的路径 主机路径  # 将容器中的文件拷贝到主机中

1.4 测试安装tomcat

docker pull tomcat
docker run -d -p 主机端口:8080 --name tomcat01 tomcat

访问主机端口,发现404
docker exec -it tomcat01 /bin/bash
进入到webapps文件夹下面,发现是空的,原因是官方镜像删减了很多功能,只留下了能运行的最基本的功能
解决方法:可以把 webapps.dist 下面的内容复制到 webapps 文件夹下即可
但是这样的解决方法又太麻烦了,不能每次上线都进入容器部署 => 数据卷

1.5 测试安装ES

docker stats    # 查看各个容器占用内存、CPU的情况
∵ es非常耗内存 ∴ 可以修改配置文件,来增加内存的限制,-e 设置最小内存和最大内存

docker run -d --name elasticserch -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" -e ES_JAVA_OPTS="-Xms256m -Xms512m" elasticsearch:7.6.2

2. 可视化 portainer

一个可视化管理方案,直接用docker来安装即可,做好主机端口映射

3. 联合文件系统

镜像是一种轻量级、可执行的独立软件包,包含某个软件所需的所有内容,包括代码、运行时、库、环境变量和配置文件。

UFS(UnionFS) 联合文件系统,是一种分层、轻量级并且高性能的文件系统,支持对文件系统的修改作为一次提交来一层层的叠加。类似于Git版本控制,每一次提交(操作)都记为一层。如果连个软件有共用的某一层文件,比如都基于linux,那么都可以去挂在这一层的linux的文件,就可以复用。

ufs里面包含比如

  • bootfs: 包含bootloader和kernel,bootloader主要是引导加载kernal,类似于启动系统的时候会有一个加载过程,当系统启动后,这个加载的程序就可以关闭了 => 加载的这部分就是可以复用的部分
  • rootfs: 在bootfs之上,包含的就是典型的linux系统的 /dev, /proc, /bin, /etc 等标准目录和文件 => 比如linux的容器,占的内存很小,是因为镜像文件只需要包含rootfs里最基本的命令、工具即可,可以使用主机的kernel,这样的话可以提高效率

所有的镜像都起始于一个基础镜像层,当进行修改或增加新的内容的时候,就会在当前镜像层之上,创建一个新的镜像层

镜像都是只读的,当容器启动时,一个新的可写层(容器层)就会加载到镜像层的顶部

通过 commit 来将容器层和镜像层进行合并打包,变成一个新的镜像文件

docker commit -m="提交的描述信息" -a="作者" 容器ID 目标镜像名[:tag]

5. 数据卷

  1. 每次修改配置文件都需要进入到容器很麻烦
  2. 数据不应该放在容器中,否则删除容器,数据就丢失了

=> 在容器外部提供一个映射路径,类似于数据共享技术,通过卷技术,也就是目录的挂载,将容器内的目录,挂在到linux上面,来实现容器数据的持久化和同步操作,容器之间也可以数据共享。

容器里面的目录就像是快捷方式,宿主机的文件才是真实的

实现方式:

  1. 直接使用命令来挂载 -v
docker run -it -v 主机目录:容器内目录 镜像名 /bin/bash

docker run --name mysql02 -e MYSQL_ROOT_PASSWORD=123456 -p 3310:3306 -v testconf2:/etc/mysql/conf.d -v /Users/mxxct/mysqldata:/var/lib/mysql -d mysql:5.7

通过 docker inspect 容器ID 查看元数据,在 Mounts 里可以看到挂载的详细信息

  • 匿名挂载
    不指定主机名,直接写容器内目录 -v 容器内目录
  • 具名挂载
    也可以分为两种
    • 指定主机目录 -v 主机目录:容器内目录
    • 不指定主机目录,只是写一个名字 -v xxx:容器内目录 这时候会默认映射到主机 /var/lib/docker/volumes/xxx/_data 这个文件夹下面(在MacOS下面没找到这个路径)
docker volume ls    # 查看所有的卷
docker volume inspect 卷名字    # 查看具体数据卷的信息,这里卷名字是指具名挂载的那个名字

可以在容器内目录后面添加权限控制
eg: -v 主机目录:容器内目录:ro/rw
ro readonly; rw readwrite 默认是rw,如果是ro就表示这个路径只能从主机上来操作,容器内只是读权限

  1. 通过 DockerFile 在构建镜像是指定 VOLUME

这里是一个匿名挂载

FROM centos
VOLUME ["vol1", "vol2"]
CMD echo "---- end ----"
CMD /bin/bash

直接创建文件然后执行 docker build 可能会提示 error checking context:can‘t stat xxx 错误,这个提示基本是权限不够外加目录结构不对
解决:

mkdir dockerfile
chmod 777 dockerfile
touch file1
vi file1
docker build -f /Users/mxxct/dockerfile/file1 -t test/centos:1.0 .

(base) localhost:dockerfile mxxct$ docker build -f /Users/mxxct/dockerfile/file1 -t test/centos:1.0 .
Sending build context to Docker daemon  2.048kB
Step 1/4 : FROM centos
latest: Pulling from library/centos
3c72a8ed6814: Pull complete 
Digest: sha256:76d24f3ba3317fa945743bb3746fbaf3a0b752f10b10376960de01da70685fbd
Status: Downloaded newer image for centos:latest
 ---> 0d120b6ccaa8
Step 2/4 : VOLUME ["vol1", "vol2"]
 ---> Running in 7cc3997837d7
Removing intermediate container 7cc3997837d7
 ---> b55d348b6981
Step 3/4 : CMD echo "---- end ----"
 ---> Running in 083ad85879af
Removing intermediate container 083ad85879af
 ---> 11c45d147425
Step 4/4 : CMD /bin/bash
 ---> Running in e7d39262c201
Removing intermediate container e7d39262c201
 ---> ee8398f6597c
Successfully built ee8398f6597c
Successfully tagged test/centos:1.0

创建好镜像后,执行

docker run -it ee8398f6597c /bin/bash

[root@2adb0cbdc19f /]# ls -l
total 56
lrwxrwxrwx   1 root root    7 May 11  2019 bin -> usr/bin
drwxr-xr-x   5 root root  360 Dec  1 06:23 dev
drwxr-xr-x   1 root root 4096 Dec  1 06:23 etc
drwxr-xr-x   2 root root 4096 May 11  2019 home
lrwxrwxrwx   1 root root    7 May 11  2019 lib -> usr/lib
lrwxrwxrwx   1 root root    9 May 11  2019 lib64 -> usr/lib64
drwx------   2 root root 4096 Aug  9 21:40 lost+found
drwxr-xr-x   2 root root 4096 May 11  2019 media
drwxr-xr-x   2 root root 4096 May 11  2019 mnt
drwxr-xr-x   2 root root 4096 May 11  2019 opt
dr-xr-xr-x 159 root root    0 Dec  1 06:23 proc
dr-xr-x---   2 root root 4096 Aug  9 21:40 root
drwxr-xr-x  11 root root 4096 Aug  9 21:40 run
lrwxrwxrwx   1 root root    8 May 11  2019 sbin -> usr/sbin
drwxr-xr-x   2 root root 4096 May 11  2019 srv
dr-xr-xr-x  12 root root    0 Dec  1 06:23 sys
drwxrwxrwt   7 root root 4096 Aug  9 21:40 tmp
drwxr-xr-x  12 root root 4096 Aug  9 21:40 usr
drwxr-xr-x  20 root root 4096 Aug  9 21:40 var
drwxr-xr-x   2 root root 4096 Dec  1 06:23 vol1
drwxr-xr-x   2 root root 4096 Dec  1 06:23 vol2

这里有 vol1 和 vol2 两个目录,然后去查看容器的元数据信息,会发现在 Mounts 这里已经把两个卷给挂在出去了

"Mounts": [
    {
        "Type": "volume",
        "Name": "baeff06c4bf78f14c8135dbdc5ae238bb513cf8375af699b47c602d3e3a0a529",
        "Source": "/var/lib/docker/volumes/baeff06c4bf78f14c8135dbdc5ae238bb513cf8375af699b47c602d3e3a0a529/_data",
        "Destination": "vol1",
        "Driver": "local",
        "Mode": "",
        "RW": true,
        "Propagation": ""
    },
    {
        "Type": "volume",
        "Name": "4cbbd6edd2093b5098b6d9994bc380c992445842c1b19a942816b090a8841e13",
        "Source": "/var/lib/docker/volumes/4cbbd6edd2093b5098b6d9994bc380c992445842c1b19a942816b090a8841e13/_data",
        "Destination": "vol2",
        "Driver": "local",
        "Mode": "",
        "RW": true,
        "Propagation": ""
    }
],

6. 数据卷容器

容器数据卷是容器实现数据持久化的一种机制,数据卷容器是一种特殊的容器
这个主要是通过类似,用于容器间的数据同步,不是容器和主机之间。

docker run -it --name docker01 imageID
docker run -it --name docker02 --volumes-from docker01 imageID

docker01中有挂载的文件夹
通过 --volumes-from 让docker02继承docker01的挂载文件夹,此时docker01挂载出去的文件夹里的数据的任何修改在docker02也可以看到,同时在docker02上做的修改docker01也可以看到。docker01就是数据卷容器。

删除docker01的话,挂载的文件夹数据依然存在,本质是因为他们都挂载到主机的目录下,只要主机对应目录存在,数据就不会丢失
docker01相当于映射主机数据目录的地址(想成一个快捷方式),docker02拿到这个映射地址(快捷方式),放到自己的mount里面

7. DockerFile

通过 docker history imageID 可以查看一个镜像的构建过程

基础知识

  1. 每个关键字指令都必须是大写
  2. 按顺序从上到下执行
  3. # 是注释
  4. 每一个指令都会创建并提交一个新的镜像层。(docker run之后会创建一个可写容器)

指令说明

FROM        # 指定基础镜像
MAINtAINER  # 指定维护者信息,姓名+邮箱
RUN         # 镜像构建的时候需要运行的命令
ADD         # 添加内容,会自动解压,比如tomcat压缩包
WORKDIR     # 镜像的工作目录
VOLUME      # 设置卷,挂载主机目录
EXPOSE      # 指定暴露的端口,或者通过 -p 指定
CMD         # 指定容器启动时要运行的命令,只有最后一个会生效,可被替代 
ENTRYPOINT  # 指定容器启动时要运行的命令,可以追加命令
ONBUILD     # 当构建一个被继承 DockerFile 这个时候会运行ONBUILD的指令,触发指令
COPY        # 类似ADD,将文件拷贝到镜像中
ENV         # 构建的时候设置环境变量

CMD ["ls", "-a"] 这时候的命令相当于 "ls -a"。只有当执行 docker run imageID 的时候才会执行最后一个CMD的命令。(docker run -it imageID这时候是通过命令行交互,所以不会执行CMD)

如果执行 docker run imageID -l 想执行 ls -al 命令会报错,即CMD命令会被替代,不会追加。
换成 ENTRYPOINT 再执行 docker run imageID -l 就可以执行 ls -al

COPY 主机路径/readme.txt 容器内路径
ADD 主机路径/xxxx.tar 容器内路径    # ADD添加的文件会自动解压

RUN yum -y install vim

ENV MYPATH /user/local
WORKDIR $MYPATH

ENV JAVA_HOME /user/local/jdk1.8.0_11
ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
ENV CATALINA_HOME /user/local/apache-tomcat-9.0.22
ENV CATALINA_BASH /user/local/apache-tomcat-9.0.22
ENV PATH $PATH:$JAVA_HOME/bin:$CATALINA_HOME/lib:$CATALINA_HOME/bin

EXPOSE 8080

CMD /user/local/apache-tomcat-9.0.22/bin/startup.sh && tail -f /user/local/apache-tomcat-9.0.22/bin/logs/catalinas.out

8. Docker 网络

每次启动一个程序,Linux 就会相应创建一个新的网卡地址 => veth-pair 一对虚拟设备接口,一端连着协议,一端彼此相连 => 每启动一个容器,docker会创建一对网卡连接地址,一端在主机,一端在容器

Linux 虚拟网络设别 veth-pair。三个经典实验,直接相连、通过 Bridge 相连和通过 OVS 相连

两个docker容器进行通信,需要先走到docker0,也就是类似于docker的路由器,通过广播或注册的方式,路由器会知道下一个容器的网络地址,然后转发请求连接。Docker0就相当于一个网桥的角色。

docker run --link 通过link可以将两个容器网络连接起来,但是这是单向的,A link B, 只是A可以连接B,想B连接A的话还需要再配置B
link 操作相当于在容器内的 hosts 文件中写进去映射关系

不推荐 --link。因为 link 用的是 docker0,而docker0不支持容器名称映射,只能写死ip,如果牵扯到ip迁移,就需要重新配置一遍

docker network ls   # 查看所有的docker网络

网络模式

  • bridge: 桥接(docker默认)
    docker run --net bridge     # 不写的话也默认有这个bridge
    
  • none:不配置
  • host:和主机共享网络
  • container:容器网络连通(用得少,局限很大)

自定义创建网络

docker network create --driver bridge --subnet 192.168.0.0/16 --gateway 192.168.0.1 mynet

创建的容器基于各自不同的网络,可以保证同一类容器使用的是自己的网络ip。但是如果是两类容器想互相连接呢?网络打通。

docker network connect [option] container network   # 将一个容器连接到一个网关上,相当于一个容器两个ip地址。

9. Docker Compose

目的:定义运行多个容器,避免一个个的运维容器
步骤:

  1. DockerFile
  2. docker-compose.yml
  3. run docker-compose up

配置项里有一个 deploy.replicas 可以设置副本,集群部署的时候使用这个参数

单机

10. Docker Swam

相当于是搭建一个小集群,有manager有worker。

集群

docker swarm init		# 初始化节点
docker swarm join-token manager		# 加入一个节点,并将该节点作为manager
docker swarm join-token worker		# 加入一个节点,并将该节点作为worker

11. Raft 协议

拜占庭将军问题:很久很久以前,拜占庭是东罗马帝国的首都。那个时候罗马帝国国土辽阔,为了防御目的,因此每个军队都分隔很远,将军与将军之间只能靠信使传递消息。在打仗的时候,拜占庭军队内所有将军必需达成一致的共识,才能更好地赢得胜利。但是,在军队内有可能存有叛徒,扰乱将军们的决定。这时候,在已知有成员不可靠的情况下,其余忠诚的将军需要在不受叛徒或间谍的影响下达成一致的协议。莱斯利·兰伯特( Leslie Lamport )通过这个比喻,表达了计算机网络中所存在的一致性问题。这个问题被称为拜占庭将军问题。

二将军问题:白军驻扎在沟渠里,蓝军则分散在沟渠两边。白军比任何一支蓝军都更为强大,但是蓝军若能同时合力进攻则能够打败白军。他们不能够远程的沟通,只能派遣通信兵穿过沟渠去通知对方蓝军协商进攻时间。是否存在一个能使蓝军必胜的通信协议,这就是两将军问题。

二将军问题探讨的是不可靠信道下两方的通信准确性问题,而拜占庭将军问题探讨的是多方通信结果一致性和决策正确性的问题。
准确性是指发送的是什么,接收的就是什么;一致性是在节点更多的情况下的准确性;正确性是无论收到的通信结果是否有干扰(不完全满足一致性),正常工作的节点能够做出正确的决策。

=> 二将军问题可以通过TCP3次握手来解决;拜占庭将军问题可以通过Raft/Paxos(谷哥Chubby)/ZAB(zookeeper)/PBFT(区块链)几种算法解决。另外 PS:raft,zab,paxos解决的是节点故障问题,而非拜占庭问题(节点故意欺骗)。PBFT解决了拜占庭问题。

Raft协议

Raft 协议的具体流程:

  1. 在最初,还没有一个主节点的时候,所有节点的身份都是Follower。每一个节点都有自己的计时器,当计时达到了超时时间(Election Timeout),该节点会转变为Candidate。
  2. 成为Candidate的节点,会首先给自己投票,然后向集群中其他所有的节点发起请求,要求大家都给自己投票。
  3. 其他收到投票请求且还未投票的Follower节点会向发起者投票,发起者收到反馈通知后,票数增加。
  4. 当得票数超过了集群节点数量的一半,该节点晋升为Leader节点。Leader节点会立刻向其他节点发出通知,告诉大家自己才是老大。收到通知的节点全部变为Follower,并且各自的计时器清零。

这里需要说明一点,每个节点的超时时间都是不一样的。比如A节点的超时时间是3秒,B节点的超时时间是5秒,C节点的超时时间是4秒。这样一来,A节点将会最先发起投票请求,而不是所有节点同时发起。为什么这样设计呢?设想如果所有节点同时发起投票,必然会导致大家的票数差不多,形成僵局,谁也当不成老大。

那么,成为Leader的节点是否就坐稳了老大的位置呢?并不是。Leader节点需要每隔一段时间向集群其他节点发送心跳通知,表明你们的老大还活着。一旦Leader节点挂掉,发不出通知,那么计时达到了超时时间的Follower节点会转变为Candidate节点,发起选主投票,周而复始......

数据同步的流程:

  1. 由客户端提交数据到Leader节点
  2. 由Leader节点把数据复制到集群内所有的Follower节点。如果一次复制失败,会不断进行重试
  3. Follower节点们接收到复制的数据,会反馈给Leader节点
  4. 如果Leader节点接收到超过半数的Follower反馈,表明复制成功。于是提交自己的数据,并通知客户端数据提交成功。
  5. 由Leader节点通知集群内所有的Follower节点提交数据,从而完成数据同步流程。

ZAB协议

12. 容器服务

docker service create -p ip:端口 --name my-service xxx
# 和 docker run 的区别是,run是容器启动,不具备扩缩容的概念;service是服务启动,可以自动扩缩容

docker service ps my-service	# 查看服务进程
docker service ls		# 查看服务列表,这里有一个 replicas,表示当前服务的副本
docker servie update --replicas N my-service	# 将 my-service 自动扩容到 N 个副本
docker service scale my-service=N		# 将 my-service 自动扩容到 N 个副本

有一个 --mode 参数,默认是 replicated,可以改成 global
replicated 只能在worker节点中运行,global的话manager节点也可以运行

灰度发布:金丝雀发布

命令 -> manager节点调用api -> schedule api -> worker节点创建并维护容器服务

镜像使用的os版本不统一,比如Linux基于的os不一样,版本不一样,命令缺失,阉割版和增肥版都不统一,数据文件可能都修改了位置等等

Docker 深入学习

标签:目的   数据共享   mod   dep   包括   block   姓名   独立   elastic   

原文地址:https://www.cnblogs.com/mxxct/p/14158325.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!