Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化,容器是完全使用沙箱机制,相互之间不会有任何接口。
一个完整的Docker有以下几个部分组成:
dockerClient客户端
Docker Daemon守护进程
Docker Image镜像
DockerContainer容器
知乎上有人回答说:Docker(码头工人)的思想来自于集装箱,集装箱解决了什么问题?在一艘大船上,可以把货物规整的摆放起来。并且各种各样的货物被集装箱标准化了,集装箱和集装箱之间不会互相影响。那么我就不需要专门运送水果的船和专门运送化学品的船了。只要这些货物在集装箱里封装的好好的,那我就可以用一艘大船把他们都运走。这里可以看docker的logo来理解,鲸鱼拖着许多集装箱。
这就解释了另外一个应用场景:应用的分离化,假如搭建一个Web网站需要CentOS+Apache+PHP+MySQL,我们即可利用CentOS的集装箱,Apache的集装箱和PHP,MySQL的集装箱来通信实现Web应用,集装箱之间除了交换数据并没有多余的联系。
docker提供一个公共仓库,可以用dockersearch 命令去查找所需要的镜像,用docker pull 命令从仓库中拉取需要的镜像到本地。
https://hub.docker.com/explore/
Docker的镜像类似虚拟机的快照,但更轻量,非常非常轻量。
创建Docker镜像有几种方式,多数是在一个现有镜像基础上创建新镜像,因为几乎你需要的任何东西都有了公共镜像,包括所有主流Linux发行版,你应该不会找不到你需要的镜像。不过,就算你想从头构建一个镜像,也有好几种方法。
要创建一个镜像,你可以拿一个镜像,对它进行修改来创建它的子镜像。实现前述目的的方式有两种:在一个文件中指定一个基础镜像及需要完成的修改;或通过“运行”一个镜像,对其进行修改并提交。不同方式各有优点,不过一般会使用文件来指定所做的变化。
镜像拥有唯一ID,以及一个供人阅读的名字和标签对。镜像可以命名为类似ubuntu:latest、ubuntu:precise、django:1.6、django:1.7等等。
用docker images 列出本地的镜像,dpcler search查找镜像列表。
你可以从镜像中创建容器,这等同于从快照中创建虚拟机,不过更轻量。应用是由容器运行的。
举个例子,你可以下载一个Ubuntu的镜像(有个叫docker registry的镜像公共仓库),通过安装Gunicorn和Django应用及其依赖项来完成对它的修改,然后从该镜像中创建一个容器,在它启动后运行你的应用。
容器与虚拟机一样,是隔离的。它们也拥有一个唯一ID和唯一的供人阅读的名字。容器对外公开服务是必要的,因此Docker允许公开容器的特定端口。
与虚拟机相比,容器有一个很大的差异,它们被设计用来运行单进程,无法很好地模拟一个完整的环境Docker。容器和虚拟机的第二个巨大差异是:当你停止一个虚拟机时,可能除了一些临时文件,没有文件会被删除;当你停止一个Docker容器,对初始状态(创建容器所用的镜像的状态)做的所有变化都会丢失。
容器是设计来运行一个应用的,而非一台机器。你可能会把容器当虚拟机用,但如我们所见,你将失去很多的灵活性,因为Docker提供了用于分离应用与数据的工具,使得你可以快捷地更新运行中的代码/系统,而不影响数据。
数据卷让你可以不受容器生命周期影响进行数据持久化。它们表现为容器内的空间,但实际保存在容器之外,从而允许你在不影响数据的情况下销毁、重建、修改、丢弃容器。Docker允许你定义应用部分和数据部分,并提供工具让你可以将它们分开。使用Docker时必须做出的最大思维变化之一就是:容器应该是短暂和一次性的。
卷是针对容器的,你可以使用同一个镜像创建多个容器并定义不同的卷。卷保存在运行Docker的宿主文件系统上,你可以指定卷存放的目录,或让Docker保存在默认位置。保存在其他类型文件系统上的都不是一个卷。
链接是Docker的另一个重要部分。
容器启动时,将被分配一个随机的私有IP,其它容器可以使用这个IP地址与其进行通讯。这点非常重要,原因有二:一是它提供了容器间相互通信的渠道,二是容器将共享一个本地网络。在同一台机器上为两个客户启动两个elasticsearch容器,但保留集群名称为默认设置,结果这两台elasticsearch服务器立马变成了一个自主集群。注:限制容器间通讯是可行的,请阅读Docker的高级网络文档做进一步了解。
要开启容器间通讯,Docker允许你在创建一个新容器时引用其它现存容器,在你刚创建的容器里被引用的容器将获得一个(你指定的)别名。我们就说,这两个容器链接在了一起。
因此,如果DB容器已经在运行,我可以创建web服务器容器,并在创建时引用这个DB容器,给它一个别名,比如dbapp。在这个新建的web服务器容器里,我可以在任何时候使用主机名dbapp与DB容器进行通讯。
Docker更进一步,要求你声明容器在被链接时要开放哪些端口给其他容器,否则将没有端口可用。
关于docker的应用场景,http://dockone.io/article/126给出了详细的介绍。
虚拟机的最大好处是能在你的硬件设施上运行各种配置不一样的平台(软件、系统),Docker在降低额外开销的情况下提供了同样的功能。它能让你将运行环境和配置放在代码中然后部署,同一个Docker的配置可以在不同的环境中使用,这样就降低了硬件要求和应用环境之间耦合度。
前一个场景对于管理代码的流水线起到了很大的帮助。代码从开发者的机器到最终在生产环境上的部署,需要经过很多的中间环境。而每一个中间环境都有自己微小的差别,Docker给应用提供了一个从开发到上线均一致的环境,让代码的流水线变得简单不少。
不同的开发环境中,我们都想把两件事做好。一是我们想让开发环境尽量贴近生产环境,二是我们想快速搭建开发环境。
理想状态中,要达到第一个目标,我们需要将每一个服务都跑在独立的虚拟机中以便监控生产环境中服务的运行状态。然而,我们却不想每次都需要网络连接,每次重新编译的时候远程连接上去特别麻烦。这就是Docker做的特别好的地方,开发环境的机器通常内存比较小,之前使用虚拟的时候,我们经常需要为开发环境的机器加内存,而现在Docker可以轻易的让几十个服务在Docker中跑起来。
有很多种原因会让你选择在一个机器上运行不同的应用,比如之前提到的提高开发效率的场景等。
我们经常需要考虑两点,一是因为要降低成本而进行服务器整合,二是将一个整体式的应用拆分成松耦合的单个服务。
正如通过虚拟机来整合多个应用,Docker隔离应用的能力使得Docker可以整合多个服务器以降低成本。由于没有多个操作系统的内存占用,以及能在多个实例之间共享没有使用的内存,Docker可以比虚拟机提供更好的服务器整合解决方案。
Docker提供了很多的工具,这些工具不一定只是针对容器,但是却适用于容器。它们提供了很多的功能,包括可以为容器设置检查点、设置版本和查看两个容器之间的差别,这些特性可以帮助调试Bug。
另外一个Docker有意思的使用场景是在多租户的应用中,它可以避免关键应用的重写。这种多租户的基本代码非常复杂,很难处理,重新规划这样一个应用不但消耗时间,也浪费金钱。
使用Docker,可以为每一个租户的应用层的多个实例创建隔离的环境,这不仅简单而且成本低廉,当然这一切得益于Docker环境的启动速度和其高效的diff命令。
在虚拟机之前,引入新的硬件资源需要消耗几天的时间。虚拟化技术(Virtualization)将这个时间缩短到了分钟级别。而Docker通过为进程仅仅创建一个容器而无需启动一个操作系统,再次将这个过程缩短到了秒级。
你可以在数据中心创建销毁资源而无需担心重新启动带来的开销。通常数据中心的资源利用率只有30%,通过使用Docker并进行有效的资源分配可以提高资源的利用率。
快速搭建各种漏洞环境
实例1:快速搭建webgoat环境
docker pull webgoat/webgoat-8.0
docker run -p 8080:8080 -itwebgoat/webgoat-8.0 /home/webgoat/start.sh
访问网址:http://IP:8080/WebGoat/login.mvc即可。
DVWA等开源漏洞平台也可以如上步骤轻松搭建。
实例2:搭建Zabbix漏洞环境
在平时的工作和学习中经常会遇到需要本地复现某个漏洞的情况,很多时候,搭建漏洞环境就会耗去我们较长的时间,其间可能会遇到各种各样的问题,要去修改、调试,直至真正搭建成功,过程还是比较崎岖的。docker完美的解决了这一问题。
以 Zabbix v2.2.x, 3.0.0-3.0.3 jsrpc 参数 profileIdx2 SQL 注入漏洞为例:
可以先使用docker search zabbix命令查找仓库中有哪些zabbix镜像,在镜像列表中选择一个 拉取到本地。
Step1、拉取镜像到本地
docker pull medicean/vulapps:z_zabbix_1
Step2、启动环境
docker run -d -p 8888:80medicean/vulapps:z_zabbix_1
-p 8888:80 前面的 8888代表物理机的端口,可随意指定。
如果获取速度慢,推荐使用 中科大 Docker Mirrors
在同网段访问该主机的8888端口即可看到zabbix服务已经成功启动
如果不适用docker呢?
通常情况下,我们如果选择在一台centos7上安装zabbix,会经历以下过程
Step1.安装依赖包
yum -y install wget net-snmp-devel OpenIPMI-devel httpdopenssl-devel java lrzsz fping-devel libcurl-devel perl-DBI pcre-devel libxml2libxml2-devel mysql-devel gcc php php-bcmath php-gd php-xml php-mbstringphp-ldap php-mysql.x86_64 php-pear php-xmlrpc net-tools wget vim-enhanced
经历了大约10分钟的等待,依赖包全部安装完成。期间可能会出现
Cannot find a valid baseurl for repo:base/7/x86_64的报错信息,可以通过修改DNS来解决,不赘述。
Step2.关闭防火墙
systemctl stop firewalld.service
systemctl disable firewalld.service
关闭selinux
vi /etc/selinux/config
修改SELINUX选项为disabled
重启x系统 reboot
等待ing…..
Step3. 搭建zabbix所需要的lamp环境
下载最新的yum源,
wget -P/etc/yum.repos.d http://mirrors.aliyun.com/repo/Centos-7.repo
开始安装lamp环境
yum -y install mariadbmariadb-server php php-mysql httpd
配置mysql数据库
systemctl enable mariadb.service
systemctl start mariadb.service
初始化mysql数据库,并配置root用户密码mysql_secure_installation
Enter current passwdord for root处,直接敲回车键即可
为root用户配置密码,并刷新相关权限。(密码设为123456,只为实验用,生产环境自定义)
Remove anonymous users? 删除匿名用户?
Disallow root login remotely? 禁止root远程登陆
Remove test database and access to it? 删除测试数据库并且和访问它
Reload privilege tables now? 重新载入特权表
由于Docker是一门较新的技术,所以对系统的要求也是比较新的,Docker需要在内核版本至少在3.0以后的版本中运行。
笔者环境是系统环境为CentOS Linux release 7.3.1611(Core) 内核版本3.10.0-514.el7.x86_64
centos7安装docker
1、Docker 软件包已经包括在默认的 CentOS-Extras 软件源里。因此想要安装 docker,只需要运行下面的 yum 命令:
yum install docker
2、安装完成后,使用下面的命令来启动docker 服务,并将其设置为开机启动:
service docker start
chkconfig docker on
4、下载官方的 CentOS 镜像到本地
docker pull centos
5、确认 CentOS 镜像已经被获取:
docker images centos
6、运行一个 Docker 容器:
docker run -it centos /bin/bash
基础命令
启动docker服务service docker start
设置开机自动启动 chkconfig docker on
docker服务对应版本查看docker version
Docker环境查看docker info
查看docker的所有命令docker
Docker命令帮助docker <command> --help 查看docker命令的帮助信息
查看docker状态ps -ef | grep docker
镜像
查找镜像 docker search <image name >
查看镜像列表 docker images
查看镜像的历史版本 docker history <image name>
创建镜像 docker pull <image name>
删除一个或多个镜像docker rmi <image name>
保存镜像docker save image name > /tmp/image.tar
加载镜像 docker load < /tmp/image/image.tar
容器
新建容器 docker create container name
交互式启动容器 docker run -it < containername> /bin/bash
显示容器状态 docker stats < container name/id>
启动容器 docker start <container name/id>
重启容器 docker restart < container name/id>
停止容器 docker stop < container name/id >
杀死容器 docker kill < container name/id>
删除容器 docker rm < container name/id>
重命名容器 docker rename < container name/id>
备份容器docker commit< container name/id > pro_test1
列出所有容器docker ps -a
列出最近一次启动的容器docker ps -l
列出所有当前正在运行的容器docker ps
Docker 可以通过 Dockerfile 的内容来自动构建镜像。
Dockerfile一般包含下面几个部分:
基础镜像:以哪个镜像作为基础进行制作,用法是FROM 基础镜像名称
维护者信息:需要写下该Dockerfile编写人的姓名或邮箱,用法是MANITAINER 名字/邮箱
镜像操作命令:对基础镜像要进行的改造命令,比如安装新的软件,进行哪些特殊配置等,常见的是RUN 命令
容器启动命令:当基于该镜像的容器启动时需要执行哪些命令,常见的是CMD 命令或ENTRYPOINT
Dockerfile 是一个包含创建镜像所有命令的文本文件,通过docker build命令可以根据 Dockerfile 的内容构建镜像,先介绍下 Dockerfile 的基本语法结构。
Dockerfile 有以下指令选项:
FROM
MAINTAINER
RUN
CMD
EXPOSE
ENV
ADD
COPY
ENTRYPOINT
VOLUME
USER
WORKDIR
ONBUILD
1.FROM
用法:FROM <image>
FROM指定构建镜像的基础源镜像,如果本地没有指定的镜像,则会自动从 Docker 的公共库 pull 镜像下来。
FROM必须是 Dockerfile 中非注释行的第一个指令,即一个 Dockerfile 从FROM语句开始。
FROM可以在一个 Dockerfile 中出现多次,如果有需求在一个 Dockerfile 中创建多个镜像。
如果FROM语句没有指定镜像标签,则默认使用latest标签。
2 MAINTAINER
指定创建镜像的用户。用法:MAINTAINER <name>
3.RUN
RUN命令将在当前image中执行任意合法命令并提交执行结果。命令执行提交后,就会自动执行Dockerfile中的下一个指令。有两种使用方式:
RUN
RUN "executable", "param1", "param2"
每条RUN指令将在当前镜像基础上执行指定命令,并提交为新的镜像,后续的RUN都在之前RUN提交后的镜像为基础,镜像是分层的,可以通过一个镜像的任何一个历史提交点来创建,类似源码的版本控制。
exec 方式会被解析为一个 JSON 数组,所以必须使用双引号而不是单引号。exec 方式不会调用一个命令 shell,所以也就不会继承相应的变量,如:
RUN [ "echo", "$HOME" ]
这种方式是不会达到输出 HOME 变量的,正确的方式应该是这样的
RUN [ "sh", "-c", "echo", "$HOME" ]
RUN产生的缓存在下一次构建的时候是不会失效的,会被重用,可以使用--no-cache选项,即docker build --no-cache,如此便不会缓存。
4 CMD
CMD有三种使用方式:
CMD "executable","param1","param2"
CMD "param1","param2"
CMD command param1 param2 (shell form)
CMD指定在 Dockerfile 中只能使用一次,如果有多个,则只有最后一个会生效。
CMD的目的是为了在启动容器时提供一个默认的命令执行选项。如果用户启动容器时指定了运行的命令,则会覆盖掉CMD指定的命令。
CMD会在启动容器的时候执行,build 时不执行,而RUN只是在构建镜像的时候执行,后续镜像构建完成之后,启动容器就与RUN无关了。
5 EXPOSE
用法:EXPOSE <port> [<port>...]
告诉 Docker 服务端容器对外映射的本地端口,需要在 docker run 的时候使用-p或者-P选项生效。
6 ENV
ENV <key> <value> # 只能设置一个变量
ENV <key>=<value> ... # 允许一次设置多个变量
指定一个环境变量,会被后续RUN指令使用,并在容器运行时保留。
例子:
ENV myName="John Doe" myDog=Rex\ The\ Dog \
myCat=fluffy
等同于
ENV myName John Doe
ENV myDog Rex The Dog
ENV myCat fluffy
ENV设置的环境变量,可以使用 docker inspect命令来查看。同时还可以使用docker run --env <key>=<value>来修改环境变量
7 ADD
用法:ADD <src>... <dest>
ADD 将文件从路径 <src> 复制添加到容器内部路径 <dest>。
<src> 必须是想对于源文件夹的一个文件或目录,也可以是一个远程的url,<dest> 是目标容器中的绝对路径。
所有的新文件和文件夹都会创建UID 和 GID。事实上如果<src> 是一个远程文件URL,那么目标文件的权限将会是600。
支持通过Go的正则模糊匹配,具体规则可参见 Go filepath.Match
ADD hom* /mydir/ # adds all files starting with "hom"
ADD hom?.txt /mydir/ # ? is replaced with any single character
路径必须是绝对路径,如果不存在,会自动创建对应目录;路径必须是 Dockerfile 所在路径的相对路径,如果是一个目录,只会复制目录下的内容,而目录本身则不会被复制。
8 COPY
用法:COPY <src><dest>
COPY 将文件从路径 <src> 复制添加到容器内部路径 <dest>。
COPY复制新文件或者目录从 并且添加到容器指定路径中 。用法同ADD,唯一的不同是不能指定远程文件 URLS。
9 ENTRYPOINT
ENTRYPOINT "executable", "param1","param2"
ENTRYPOINT command param1 param2 (shell form)
配置容器启动后执行的命令,并且不可被 docker run 提供的参数覆盖,而CMD是可以被覆盖的。如果需要覆盖,则可以使用docker run --entrypoint选项。
每个 Dockerfile 中只能有一个ENTRYPOINT,当指定多个时,只有最后一个生效。
Exec form ENTRYPOINT 例子
通过ENTRYPOINT使用 exec form 方式设置稳定的默认命令和选项,而使用CMD添加默认之外经常被改动的选项。
FROM ubuntu
ENTRYPOINT ["top", "-b"]
CMD ["-c"]
通过 Dockerfile 使用ENTRYPOINT展示前台运行 Apache 服务
FROM debian:stable
RUN apt-get update && apt-get install -y --force-yes apache2
EXPOSE 80 443
VOLUME ["/var/www", "/var/log/apache2", "/etc/apache2"]
ENTRYPOINT ["/usr/sbin/apache2ctl", "-D", "FOREGROUND"]
Shell form ENTRYPOINT 例子
这种方式会在/bin/sh -c中执行,会忽略任何CMD或者docker run命令行选项,为了确保docker stop能够停止长时间运行ENTRYPOINT的容器,确保执行的时候使用exec选项。
FROM ubuntu
ENTRYPOINT exec top -b
如果在ENTRYPOINT忘记使用exec选项,则可以使用CMD补上:
FROM ubuntu
ENTRYPOINT top -b
CMD --ignored-param1 # --ignored-param2 ... --ignored-param3 ... 依此类推
10 VOLUME
用法:VOLUME ["/data"]
创建一个可以从本地主机或其他容器挂载的挂载点,一般用来存放数据库和需要保持的数据等。
11 USER
USER daemon
USER 用来切换运行属主身份的。Docker 默认是使用 root,但若不需要,建议切换使用者身分,毕竟 root 权限太大了,使用上有安全的风险,后续的RUN、CMD、ENTRYPOINT也会使用指定用户。
12 WORKDIR
用法:WORKDIR /path/to/workdir
WORKDIR 用来切换工作目录的。Docker 默认的工作目录是/,只有 RUN 能执行cd命令切换目录,而且还只作用在当下下的 RUN,也就是说每一个 RUN 都是独立进行的。如果想让其他指令在指定的目录下执行,就得靠 WORKDIR。WORKDIR 动作的目录改变是持久的,不用每个指令前都使用一次。
为后续的RUN、CMD、ENTRYPOINT指令配置工作目录。可以使用多个WORKDIR指令,后续命令如果参数是相对路径,则会基于之前命令指定的路径。
WORKDIR。
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd
最终路径是/a/b/c。
WORKDIR指令可以在ENV设置变量之后调用环境变量:
ENV DIRPATH /path
WORKDIR $DIRPATH/$DIRNAME
最终路径则为 /path/$DIRNAME。
13 ONBUILD
用法:ONBUILD [INSTRUCTION]
ONBUILD 的作用就是让指令延迟執行,延迟到下一个使用 FROM 的 Dockerfile 在建立 image 时执行,只限延迟一次。
ONBUILD 的使用情景是在建立镜像时取得最新的源码 (搭配 RUN) 与限定系统框架。
例如,Dockerfile 使用如下的内容创建了镜像 image-A:
[...]
ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build --dir /app/src
[...]
如果基于 image-A 创建新的镜像时,新的 Dockerfile 中使用 FROM image-A 指定基础镜像时,会自动执行 ONBUILD 指令内容,等价于在后面添加了两条指令。
# Automatically run the following
ADD . /app/src
RUN /usr/local/bin/python-build --dir /app/src
使用ONBUILD指令的镜像,推荐在标签中注明,例如 ruby:1.9-onbuild。
vim编辑Dockerfile文件
上面的Dockerfile非常简单,创建了一个包含apache的镜像。
FROM指定基础镜像,如果镜像名称中没有制定TAG,默认为latest。
MAINTAINER写明了作者信息
RUN命令默认使用/bin/sh Shell执行,默认为root权限。如果命令过长需要换行,需要在行末尾加\。
CMD命令也是默认在/bin/sh中执行,并且默认只能有一条,如果是多条CMD命令则只有最后一条执行。用户也可以在docker run命令创建容器时指定新的CMD命令来覆盖Dockerfile里的CMD。
这个Dockerfile已经可以使用docker build创建新镜像了,先构建一个版本yishuyu:1:
使用docker images命令查看镜像
使用该镜像创建容器web1,将容器中的端口80映射到本地80端口:
查看最近启动的容器
访问该台机器8080端口,即可看到apache首页
CMD如果只有一个命令,那如果我们需要运行多个服务怎么办呢?最好的办法是分别在不同的容器中运行,通过link进行连接。如果一定要在一个容器中运行多个服务可以考虑用Supervisord来进行进程管理,方式就是将多个启动命令放入到一个启动脚本中。
首先安装Supervisord,添加下面内容到Dockerfile:
RUN easy_install supervisor
RUN mkdir -p /var/log/supervisor
拷贝配置文件到指定的目录:
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
其中supervisord.conf文件需要放在/home/yishuyu/yishuyu下,文件内容如下:
[supervisord]
nodaemon=true
[program:apache2]
command=/bin/bash -c "source /etc/apache2/envvars && exec /usr/sbin/apache2ctl -D FOREGROUND"
如果有多个服务需要启动可以在文件后继续添加[program:xxx],比如如果有ssh服务,可以增加[program:ssh]。
修改CMD命令,启动Supervisord:
CMD ["/usr/bin/supervisord"]
在上述基本的架构下,我们根据需求可以增加新的内容到Dockerfile中。后续的扩展操作都需要放置在Dockerfile的镜像操作部分。
# Version 0.2
FROM centos
MAINTAINER yishuyu@163.com #作者信息
RUN yum -y install apache2 #安装apache
RUN easy_install supervisor #安装Supervisord
RUN mkdir -p /var/log/supervisor #建立supervisor目录
VOLUME ["/var/log/apche2"] #将apache访问的日志数据存储到宿主机可以访问的数据卷中
ADD html.tar /var/www #向镜像中增加文件
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf #拷贝配置文件到指定的目录
WORKDIR /var/www/html #指定后续命令的执行目录
ENV HOSTNAME shiyanloutest #使用ENV设置一些apache启动的环境变量
ENV APACHE_RUN_USER www-data
ENV APACHE_RUN_GROUP www-data
ENV APACHE_LOG_DIR /var/log/apche2
ENV APACHE_PID_FILE /var/run/apache2.pid
ENV APACHE_RUN_DIR /var/run/apache2
ENV APACHE_LOCK_DIR /var/lock/apche2
EXPOSE 80 #设置对外连接端口号
CMD ["/usr/bin/supervisord"] #启动Supervisord
http://dockone.io/article/126
https://lug.ustc.edu.cn/wiki/mirrors/help/docker
https://github.com/Medicean/VulApps/tree/master/z/zabbix/1
http://www.jkeabc.com/176978.html
http://www.linuxidc.com/Linux/2014-12/110034.htm