分享

拉开大变革序幕(上):在浪潮之巅观望Docker

fc013 发表于 2015-11-7 17:14:13 [显示全部楼层] 只看大图 回帖奖励 阅读模式 关闭右栏 0 11782
本帖最后由 pig2 于 2015-11-7 18:12 编辑
问题导读:

1.Docker常用命令有哪些?
2.怎样创建Docker映像?
3.怎样进行Docker架构设计?




Docker says: an open platform to build, ship, and run any app, anywhere
20151023105304883.png



Docker Service Overview
(as far as I study)
  • CaaS/PaaS/lightweight IaaS

    • developer oriented
    • connect code manage and cloud machine, to build, deploy and manage
  • Use as VM

    • Tencent ten thousand machines
    • rebuild in-house system
    • promote DevOps
    • provide CI/CD
  • Software Architecture

    • help microservice
    • baozoumanhua.com


Hello Docker, hello SDUer
现在正是云计算‘容器化’的潮流。Docker越来越成为云计算和分布式系统的宠儿和基石。

我们可以从 Docker Hub 或其他registry,如 DockerPool 和 阿里云Docker镜像库, pull下已有的镜像,也可以自己写Dockerfile文件,自己创建镜像。有了镜像,就可以去RUN它。下面依次介绍了RUN一个镜像(docker run),自己创建镜像(Dockerfile语法 和 docker build)。在介绍它的最基本用法之后,开始初步深入它的原理和内核技术,不求理解,只求一个印象 :-)。深入部分会越来越细致,不断完善。之后也会增加Docker Runtime metrics的介绍和命令,这样对Performance的分析也会有帮助。

感谢大家一起帮助博主完善这篇blog。


照个相

先粗略介绍docker常用的几个基本命令:

run

运行容器,如果镜像不存在则先下载

pull

从镜像库上下载容器镜像

start/stop

启动/停止一个container

rm

删除容器

rmi

删除容器镜像

commit

将容器中的修改提交至镜像中

logs

显示容器运行的控制台输出

build

从 Dockerfile 构建一个镜像

inspect

显示容器运行参数

images

显示当前宿主机上的所有镜像


docker run 洒洒水

[mw_shl_code=bash,true]$ sudo docker run [OPTIONS] IMAGE[:TAG] [COMMAND] [ARG...][/mw_shl_code]

docker run命令有两个参数,一个是镜像名,一个是要在镜像中运行的命令。

正确的命令:

[mw_shl_code=bash,true]$ docker run learn/tutorial echo "hello word"[/mw_shl_code]

  • -d:containter将会运行在后台模式
  • –name:给container命名,对于一个container来说有个name会非常方便,因为你可以当你需要link其它容器时或者其他类似需要区分其它容器时,使用容器名称会简化操作
  • –link:连接两个container之间的通信,通过端口。
–link (name or id): alias

[mw_shl_code=bash,true]$ docker run -d -P --name web --link db:db training/webapp python app.py[/mw_shl_code]

上面命令连接了web和db两个container,注意link的参数 db:db,前一个db是容器名,后一个db是alias。

如果一个名为web的container被连接到db container上, –link db:webdb,那么Docker就会在web这个container中创建环境变量 WEBDB_NAME=/web/webdb。其中<alias>_NAME = WEBDB_NAME。

  • -P:container会开放部分端口到host,只要对方可以连接到host,就可以连接到container内部。当使用-P时,docker会查找一个未被占用的端口绑定到container。你可以使用docker port来查找这个随机绑定端口
[mw_shl_code=bash,true]docker run --name mongo_001 -d -P mongo[/mw_shl_code]
  • -p:指定要映射的端口,并且,在一个指定端口上只可以绑定一个容器。支持的格式有 ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort

映射所有接口地址

使用 hostPort:containerPort 格式本地的 5000 端口映射到容器的 5000 端口,可以执行

[mw_shl_code=bash,true]$ sudo docker run -d -p 5000:5000 training/webapp python app.py[/mw_shl_code]

此时默认会绑定本地所有接口上的所有地址。

映射到指定地址的指定端口

可以使用 ip:hostPort:containerPort 格式指定映射使用一个特定地址,比如 localhost 地址 127.0.0.1

[mw_shl_code=bash,true]$ sudo docker run -d -p 127.0.0.1:5000:5000 training/webapp python app.py[/mw_shl_code]

映射到指定地址的任意端口

使用 ip::containerPort 绑定 localhost 的任意端口到容器的 5000 端口,本地主机会自动分配一个端口。

[mw_shl_code=bash,true]$ sudo docker run -d -p 127.0.0.1::5000 training/webapp python app.py[/mw_shl_code]

还可以使用 udp 标记来指定 udp 端口

[mw_shl_code=bash,true]$ sudo docker run -d -p 127.0.0.1:5000:5000/udp training/webapp python app.py[/mw_shl_code]

查看映射端口配置

使用 docker port 来查看当前映射的端口配置,也可以查看到绑定的地址

[mw_shl_code=bash,true]$ docker port nostalgic_morse 5000 [/mw_shl_code]

127.0.0.1:49155.

Dockerfile语法:事儿多

VOLUME [“mountpoint”] 将本地文件夹或者其他container的文件夹挂载到container中

[mw_shl_code=python,true]# Define mountable directories.
VOLUME ["/data/db"][/mw_shl_code]

WORKDIR /path/to/workdir 切换目录用,可以多次切换(相当于cd命令)

[mw_shl_code=python,true]# Define working directory.
WORKDIR /data[/mw_shl_code]

CMD [“executable”,”param1”,”param2”] container启动时执行的命令,但是一个Dockerfile中只能有一条CMD命令,多条则只执行最后一条CMD,用于在构建过程中执行命令

[mw_shl_code=python,true]# Define default command.
CMD ["mongod"][/mw_shl_code]

EXPOSE port 把这个端口暴露在外,这样容器外可以看到这个端口并与其通信

[mw_shl_code=python,true]# Expose ports.
#   - 27017: process
#   - 28017: http
EXPOSE 27017
EXPOSE 28017[/mw_shl_code]

ENV key value 设置环境变量

[mw_shl_code=python,true]ENV APP_NAME app.js[/mw_shl_code]

ADD <源文件> <目标文件> 用来将一个文件或目录添加到 Docker 镜像中,前面是源文件,后面是目标文件(源文件必须使用相对路径)

[mw_shl_code=python,true]ADD device.jar /device.jar[/mw_shl_code]

ENTRYPOINT 【运行命令】 用来指定运行 Docker 容器时,在容器中执行的命令是什么。如果需要运行多个命令,可以通过 Supervisor 来执行

[mw_shl_code=python,true]ENTRYPOINT java -jar /device.jar[/mw_shl_code]

示例(构建mongodb镜像的Dockerfile)

[mw_shl_code=python,true]#

# MongoDB Dockerfile
#

# Pull base image
FROM       ubuntu:latest

MAINTAINER LIU Qiu Shan <qsliubj@cn.ibm.com>


# Install MongoDB
# The real logic
# Add 10gen official apt source to the sources list
RUN \
  apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10 && \
  echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' > /etc/apt/sources.list.d/mongodb.list && \
  apt-get update && \
  apt-get install -y mongodb-org && \
  rm -rf /var/lib/apt/lists/*

# Define mountable directories
VOLUME ["/data/db"]

# Define working directory
WORKDIR /data

# Define default mongodb command
CMD ["mongod"]

# Expose the process port of mongodb
EXPOSE 27017

# Expose the http port of mongodb
#EXPOSE 28017[/mw_shl_code]

docker build 如此easy
[mw_shl_code=python,true]docker build -t="dockerfile/mongodb" .[/mw_shl_code]

在当前目录下有名为Dockerfile的文件,执行上述命令,构建名为dockerfile/mongodb的镜像。


镜像:等主人完善…查看所有镜像

[mw_shl_code=bash,true]$ docker images[/mw_shl_code]

删除指定镜像

[mw_shl_code=bash,true]$ docker rmi image_name[/mw_shl_code]

注意:

清空/var/lib/docker/devicemapper这个目录后,需要重新build所有用到的镜像,包括用到的操作系统镜像,如ubuntu:latest

[mw_shl_code=bash,true]docker pull ubuntu[/mw_shl_code]

/var/lib/docker 啥玩意

这是docker的配置文件夹。Docker用/var/lib/docker作为默认的目录,所有docker相关的文件,包括images和挂在卷(volumes)都存在这个目录下。

我们可以为一个容器建立一个卷,这个卷存放在/var/lib/docker/volume目录下,卷名是基于UUID命名的,所以很难与容器名称联系在一起。任何卷中的数据都能在主机操作系统中浏览和编辑。

注意:

在使用mongodb时,创建多个mongodb的containers后,volumes目录下就会存满mongodb的挂在卷,如果过多,会占用大量磁盘空间,如果出现空间不足的错误,可以把不用的volumes删掉。


Docker Daemon

在Docker架构中,Docker Client通过特定的协议与Docker Daemon进行通信,而Docker Daemon主要承载了Docker运行过程中的大部分工作。Docker Daemon是Docker架构中运行在后台的守护进程,大致可以分为Docker Server、Engine和Job三部分。

20150917145703793.jpg

Docker Client先通知Docker Daemon创建container,创建出后Docker Daemon会通知Docker Client已经创建好,这时Docker Client会再次发出start container的请求。收到请求的Docker Daemon会使用以下的Start函数来完成容器启动的所有过程。

[mw_shl_code=python,true]if err := container.Start(); err != nil {
        return job.Errorf("Cannot start container %s: %s", name, err)
}[/mw_shl_code]

“Start函数实现了容器的启动。更为具体的描述是:Start函数实现了进程的启动,另外在启动进程的同时为进程设定了命名空间(namespace),启动完毕之后为进程完成了资源使用的控制,从而保证进程以及之后进程的子进程都会在同一个命名空间内,且受到相同的资源控制。如此一来,Start函数创建的进程,以及该进程的子进程,形成一个进程组,该进程组处于资源隔离和资源控制的环境,我们习惯将这样的进程组环境称为容器,也就是这里的Docker Container。”(《Docker源码分析》)


容器编排

“container orchestration specifically is now a hot area” —— Nati Shalom on June 11, 2015
容器编排是为了帮助人们自动的构建和管理众多容器,尤其在分布式集群中。有很多编排工具,不只是容器编排,根据具体场景可以选择相应的,例如,如果你只使用Docker容器就可以使用Docker提供的编排系统Swarm,如果你在设计微服务,Kubernetes是不错的选择。

另外,Mesos和Mesosphere DCOS是专门设计用来管理大规模容器的。这些高性能的系统在一些世界上最大的数据中心的生产环境中已经身经百战很多年了,例如Twitter,几乎完全是在Mesos上面运行的。

在去年(2014)12月份的欧洲DockerCon上,Docker首席技术官Solomon Hykes说Mesos是生产环境下运行大规模可扩展容器集群的黄金标准。

Docker Swarm可以无缝管理容器集群, 是真正的为了让企业用户能够部署和管理大规模容器。

“我们认为Docker Swarm发布最酷的部分应该是“batteries included but swappable(可插拔式的架构)”。简单的说,这个意思就是当你需要规模化生产的时候,你可以开始使用Docker Swarm,并“换入(swap in)”Mesosphere。我们认为他们做了一个伟大的社区决定, 就是鼓励用户在容器集群调度和协调上面可以自我选择和创新,而不是只规定一种方式。”

“Mesos上的Docker Swarm直接使用Mesos的API,这就意味着它可以完全兼容Mesosphere DCOS,这也使得Docker Swarm同Marathon、 Cronos、Spark、Storm、Hadoop以及Cassandra一样成为Mesos和Mesosphere生态系统里面的一等公民。”

总之,这部分文字简单描述了Docker Swarm, Kubernetes和Mesos的联系,它们也是在Docker在分布式集群上最火热的话题,值得去探讨。

再看一下Docker Swarm,先来看一张它的图:

20151018194326378.jpg

Swarm发现Docker集群中的节点,依靠discovery模块,在发现之前需要先注册(一个Docker Node在Swarm节点上注册,仅仅是注册了Docker Node的IP地址以及Docker监听的端口号)。当发现所有存在的节点时,当Swarm接收到具体的docker管理请求,swarm会通过filter模块决策到底哪些Node满足要求,并通过一定的strategy将请求转发至具体的一个Node中。


Docker Compose
通过YAML文件自动构建container。

在64bit Red Ha上安装Compose,

[mw_shl_code=bash,true]# curl -L https://github.com/docker/compos ... cker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose

#chmod +x /usr/local/bin/docker-compose[/mw_shl_code]

20151015133030779.png

这里展示一个YAML文件示例,我们通过运行命令 $ docker-compose up -d 依据这个YAML文件的描述构建出各个docker containers。

下面的,hystrix是要构建的container名,image是用到的镜像,ports是指定端口,links是该container要link到哪些containers上。具体语法大家可以google或baidu。

[mw_shl_code=python,true]hystrix:
  image: kbastani/hystrix-dashboard
  ports:
   - "7979:7979"
  links:
   - gateway
   - discovery
discovery:
  image: kbastani/discovery-microservice
  ports:
   - "8761:8761"
configserver:
  image: kbastani/config-microservice
  ports:
   - "8888:8888"
  links:
   - discovery
gateway:
  image: kbastani/api-gateway-microservice
  ports:
   - "10000:10000"
  links:
   - discovery
   - configserver
   - user
   - movie
   - recommendation
user:
  image: kbastani/users-microservice
  links:
   - discovery
   - configserver
movie:
  image: kbastani/movie-microservice
  links:
   - discovery
   - configserver
recommendation:
  image: kbastani/recommendation-microservice
  links:
   - discovery
   - configserver
moviesui:
  image: kbastani/movies-ui
  ports:
     - "9006:9006"
  links:
   - discovery
   - configserver[/mw_shl_code]

Docker Machine
让你轻松部署Docker实例到很多不同的平台。

Docker Machine提供了多平台多Docker主机的集中管理,包括本地虚拟机和私有云、公有云,如VirtualBox、 Digital Ocean、Microsoft Azure(只支持有限的几个平台),只需要一条命令便可搭建好Docker主机。所以它是为了简化部署的复杂性而生的。

“Docker官方是这样介绍Machine的初衷的:

之前,Docker的安装流程非常复杂,用户需要登录到相应的主机上,根据官方的安装和配置指南来安装Docker,并且不同的操作系统的安装步骤也是不一样的。而有了Machine后,不管是在笔记本、虚拟机还是公有云实例上,用户仅仅需要一个命令….当然那你需要先安装Machine。”

这里引入一张经典图片:

20151017214848402.jpg

我已经安装了 VirtualBox,并且要创建一个叫“qianyu”的虚拟机:

[mw_shl_code=bash,true]$  docker-machine create --driver virtualbox qianyu
INFO[0000] Creating SSH key...                          
INFO[0000] Creating VirtualBox VM...                    
INFO[0006] Starting VirtualBox VM...                    
INFO[0006] Waiting for VM to start...                  
INFO[0038] "qianyu" has been created and is now the active machine.
INFO[0038] To point your Docker client at it, run this in your shell: $(docker-machine env testing)[/mw_shl_code]

这样就在本地起了一个virtualbox虚拟机,并在里面部署好了docker。同样我们可以指定云,比如digitalocean,不过你需要提供你的账户信息,这样就会在云上也创建出docker。而这所有的一切都是在一台本地物理机器上完成的,每一个平台的部署仅需一条或几条命令。

“虽然官方只支持几种特定平台,但其它平台的兼容留给那些爱Docker的第三方厂商以及开发者去做。所以接下来一定会有很多的厂商跟进,比如国内阿里云之类的,他们根据官方的接口开发个Driver即可加入Machine的能力。”—— https://linux.cn/article-4393-1.html


深入Docker大山坡
  • Docker概貌

”Docker有两方面的技术非常重要,第一是Linux容器方面的技术,第二是Docker镜像的技术。从技术本身来讲,两者的可复制性很强,不存在绝对的技术难点,然而Docker Hub由于存在大量的数据的原因,导致Docker Hub的可复制性几乎不存在,这需要一个生态的营造。“

容器这种系统级的虚拟化运用了一项技术叫namespace isolation:Namespace isolation使主机能够给每个容器一个虚拟的namespace,容器在这个虚拟的namespace内,只能看到它应该看到的资源。但为了提升效率,许多操作系统文件、目录和运行的服务在容器间共享,并映射到每个容器的namespace。仅当应用在它的容器内改变这些资源时,比如修改一个已存在的文件或创建新文件,容器会从宿主操作系统得到一份副本——利用Docker的“copy-on-write”优化,仅仅复制发生变化的部分。这一共享特性,是在一台主机上高效部署多个容器的技术之一。还有一项技术叫cgroup,利用它可以实现对资源的限制和配置,如限制CPU的使用率。

Dockerfile 是软件的原材料,Docker 镜像是软件的交付品,而 Docker 容器则可以认为是软件的运行态。

Dockerfile中的四条命令 FROM, ADD, VOLUME, CMD, 这四条命令可以构建出一个镜像来,分别对应四个镜像层,见下图。

下图的出处是http://www.csdn.net/article/2015-08-21/2825511

20150906215606114.png

Docker最大的创新点在于Docker镜像的设计,下面是张Docker镜像的层次图,分层的文件系统,一层层地搭建出一个完整的容器运行环境:

20150907104721520.png

  • Cgroup/进程/物理资源/隔离/虚拟化/Docker

Cgroups可以限制、记录、隔离进程组所使用的物理资源(包括:CPU、memory、IO等),为容器实现虚拟化提供了基本保证,是构建Docker等一系列虚拟化管理工具的基石,最初由Google工程师(Paul Menage和Rohit Seth)于2006年提出。

Cgroups可以对进程组使用的资源总额进行限制,如设定应用运行时使用内存的上限,一旦超过这个配额就发出OOM(Out of Memory)。通过分配的CPU时间片数量及硬盘IO带宽大小,实际上就相当于控制了进程运行的优先级。 cgroups可以统计系统的资源使用量,如CPU使用时长、内存用量等等,这个功能非常适用于计费。cgroups可以对进程组执行挂起、恢复等操作。

Cgroups也是LXC为实现虚拟化所使用的资源管理手段,可以说没有cgroups就没有LXC。从单个进程的资源控制,到实现操作系统层次的虚拟化(OS Level Virtualization)。

“根据Docker布道师Jerome Petazzoni的说法,Docker约等于LXC+AUFS(之前只支持ubuntu时)。其中LXC负责资源管理,AUFS负责镜像管理;而LXC又包括cgroup、namespace、chroot等组件,并通过cgroup进行资源管理。所以只从资源管理这条线来看的话,Docker、LXC、CGroup三者的关系是:Cgroup在最底层落实资源管理,LXC在cgroup上封装了一层,Docker又在LXC封装了一层,关系图如图1.b所示。”
(出自http://speakingbaicai.blog.51cto.com/5667326/1352962)

20150908113440970.jpg
(a)

20150908113452240.jpg
(b)
图1 Docker-LXC-CGroup结构图

Docker的本质实际上是宿主机上的一个进程,通过namespace实现了资源隔离,通过cgroup实现了资源限制,通过UnionFS实现了Copy on Write的文件操作。


  • Cgroup的术语与规则

以下来自http://www.sel.zju.edu.cn/?p=573

术语表

task(任务):cgroups的术语中,task就表示系统的一个进程。cgroup(控制组):cgroups 中的资源控制都以cgroup为单位实现。cgroup表示按某种资源控制标准划分而成的任务组,包含一个或多个子系统。一个任务可以加入某个cgroup,也可以从某个cgroup迁移到另外一个cgroup。subsystem(子系统):cgroups中的subsystem就是一个资源调度控制器(Resource Controller)。比如CPU子系统可以控制CPU时间分配,内存子系统可以限制cgroup内存使用量。hierarchy(层级树):hierarchy由一系列cgroup以一个树状结构排列而成,每个hierarchy通过绑定对应的subsystem进行资源调度。hierarchy中的cgroup节点可以包含零或多个子节点,子节点继承父节点的属性。整个系统可以有多个hierarchy
规则1: 同一个hierarchy可以附加一个或多个subsystem。如下图1,cpu和memory的subsystem附加到了一个hierarchy。

20150908133045085.png
图1 同一个hierarchy可以附加一个或多个subsystem

规则2: 一个subsystem可以附加到多个hierarchy,当且仅当这些hierarchy只有这唯一一个subsystem。如下图2,小圈中的数字表示subsystem附加的时间顺序,CPU subsystem附加到hierarchy A的同时不能再附加到hierarchy B,因为hierarchy B已经附加了memory subsystem。如果hierarchy B与hierarchy A状态相同,没有附加过memory subsystem,那么CPU subsystem同时附加到两个hierarchy是可以的。

20150908133256904.png
图2 一个已经附加在某个hierarchy上的subsystem不能附加到其他含有别的subsystem的hierarchy上

规则3: 系统每次新建一个hierarchy时,该系统上的所有task默认构成了这个新建的hierarchy的初始化cgroup,这个cgroup也称为root cgroup。对于你创建的每个hierarchy,task只能存在于其中一个cgroup中,即一个task不能存在于同一个hierarchy的不同cgroup中,但是一个task可以存在在不同hierarchy中的多个cgroup中。如果操作时把一个task添加到同一个hierarchy中的另一个cgroup中,则会从第一个cgroup中移除。在下图3中可以看到,httpd进程已经加入到hierarchy A中的/cg1而不能加入同一个hierarchy中的/cg2,但是可以加入hierarchy B中的/cg3。实际上不允许加入同一个hierarchy中的其他cgroup野生为了防止出现矛盾,如CPU subsystem为/cg1分配了30%,而为/cg2分配了50%,此时如果httpd在这两个cgroup中,就会出现矛盾。

20150908133540390.png
图3 一个task不能属于同一个hierarchy的不同cgroup

规则4: 进程(task)在fork自身时创建的子任务(child task)默认与原task在同一个cgroup中,但是child task允许被移动到不同的cgroup中。即fork完成后,父子进程间是完全独立的。如下图4中,小圈中的数字表示task 出现的时间顺序,当httpd刚fork出另一个httpd时,在同一个hierarchy中的同一个cgroup中。但是随后如果PID为4840的httpd需要移动到其他cgroup也是可以的,因为父子任务间已经独立。总结起来就是:初始化时子任务与父任务在同一个cgroup,但是这种关系随后可以改变。

20150908133632976.png

  • namespace, cgroup & AuFS

While namespaces are responsible for isolation between host and container, control groups implement resource accounting and limiting. In addition to the above components, Docker has been using AuFS (Advanced Multi-Layered Unification Filesystem) as a filesystem for containers. AuFS is a layered filesystem that can transparently overlay one or more existing filesystems. When a process needs to modify a file, AuFS creates a copy of that file. AuFS is capable of merging multiple layers into a single representation of a filesystem. This process is called copy-on-write.


  • Cgroups & Docker

Docker既可以使用LXC,也可以使用libcontainer,后者是新的而且是默认的。通过它们我们可以对container进行限制。比如,我们要将container锁定在第一个core上,在 docker run 命令上加上 –cpuset-cpus=0。

另外 –cpu-shares 参数会定下 share 一个CPU的百分比。下面是CloudSigma上的一个示例:

[mw_shl_code=bash,true]$ docker run -d \
    --name='low_prio' \
    --cpuset-cpus=0 \
    --cpu-shares=20 \
    busybox md5sum /dev/urandom
$ docker run -d \
    --name='high_prio' \
    --cpuset-cpus=0 \
    --cpu-shares=80 \
    busybox md5sum /dev/urandom[/mw_shl_code]

20150924145525133.png

如果在一个host上管理很多个Docker container,就要借助Cgroups,Docker支持两个Cgroup driver,分别是LXC,libcontainer,各有优势。


工业界对Docker使用
  • 腾讯万台规模的Docker应用实践
    “... Docker在资源管理纬度方面只有CPU和内存两个维度,这对于共享的云环境下需要完善,也是目前相对于虚拟机不足的地方。Gaia引入磁盘容量管理,网络出入带宽控制以及磁盘IO的控制维护。 ...”


  • 基于容器的自动构建——Docker在美团的应用
    “... 该应用只利用了Docker最核心的容器功能,并没有使用Docker集群管理、调度、自动扩容等高级的功能。 ...”


  • Otto奥托集团的架构选型之路
    “... 主要讲解otto.de的微服务架构 … 为了简化不同微服务的部署和操作问题,每一个服务器运行在独立的Docker容器中。...”


Slides
20151021225806874.png


20151021225819448.png


20151021225833447.png


20151021225850193.png


20151021225909072.png


20151021225923694.png


20151021225936280.png


20151021225945178.png


20151021225958141.png


20151021230008379.png


20151021230017576.png


20151021230029801.png


20151021230037615.png


20151021230047772.png


20151021230107102.png


20151021230115296.png




原文链接:http://blog.csdn.net/bluecloudmatrix/article/details/48156861






没找到任何评论,期待你打破沉寂

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关闭

推荐上一条 /2 下一条