bara-基于Nginx和Dokcer的单机灰度测试环境原型

什么是bara

bara,即日语的バラ,也就是玫瑰。

bara是一个基于Nignx和Docker的多版本程序同时在线的简易测试环境,只能单机使用,并且只是一个PoC而已,仅供参考。

简单来说,就是使用Nginx做反向代理,代理到后面的Docker容器,bara能动态创建Nignx配置文件,重启Nginx;而浏览器可以通过设置特殊header(默认为一个字母v)来通过Nginx代理,访问指定的后面的Docker容器。

bara概览

bara架构很简单,一个Nginx服务器运行在host上,画图如下:

其背后用到的主要原理就是Nginx的proxy_pass以及map功能,Nginx会根据浏览器设置的自定义header(v)的值,根据map的映射关系,找到合适的upstream代理过去而已。

而bara,就是发挥了Nginx的强大功能,和Docker的灵活轻便,从而能在一台主机上,跑n个版本的应用程序,以方便测试。

bara还有一个概念就是Nginx的配置文件模板,Nginx的配置文件是根据这个模板动态创建的,这个模板如果有{{upstream}}占位符,则会被bara系统自动根据运行中的容器来替换为一个upstream列表。

bara的代码在: https://github.com/liubin/bara

下面,我们就来看看如何使用。

开始使用

启动前的配置

首先,需要修改config/initializers/docker.rbconfig/initializers/nginx.rb 这两个配置文件,指定如何连接Docker daemon和Nginx的配置文件位置以及nginx可执行程序的位置。

创建Nignx配置文件模板

Nginx配置文件模板是动态创建Nginx配置文件的模板,可以根据情况,准备多个模板。在Nginx服务页面,创建Nginx配置文件的时候,会基于状态为active的模板来创建Nginx的配置文件。

Nginx配置文件模板列表页面:

编辑模板页面:

拉取镜像到该host

bara还提供了简单的镜像管理功能:列表,拉取,删除。如果镜像太大,尽量避免在线操作,还是直接在console中使用docker pull来拉取。

这是镜像列表页面:

启动容器

容器列表页面:

启动容器页面:

目前比较简单,只有一个容器名和CMD参数可以填写,默认会以-d和-P的形式启动容器。

生成Nginx配置文件和重启Nginx服务

在Nignx服务页面,可以完成查看当前Nginx配置文件内容,在线修改,以及重启Nginx服务器等。

这是一个Nignx配置文件例子:

设置客户端header

这里我使用了Chrome的插件ModHeader,这个插件,可以根据指定的url规则,设置自定一个header,这里我们添加了v这个header。ModHeader设置页面如下:

如果没有设置header,则Nginx会使用map中default的值作为upstream,目前为取得容器列表后的最后一个容器。

限制

目前bara只是一个原型,有很多不足,可能有人会觉得它

  • 单机
  • 使用-P
  • 不能挂载Volume

等等,有兴趣的可以自己修改吧,代码在 https://github.com/liubin/bara

Posted in DevOps, Docker, Rails, Tech Tagged with: ,

初识OpenTSDB

1. 什么是 OpenTSDB

OpenTSDB ,可以认为是一个时系列数据(库),它基于HBase存储数据,充分发挥了HBase的分布式列存储特性,支持数百万每秒的读写。

2. 架构简介

这里我们简单看一下它的架构,如下图所示。

其最主要的部件就是TSD了,这是接收数据并存储到HBase处理的核心所在。而带有C(collector)标志的Server,则是数据采集源,将数据发给 TSD服务。

3. 安装 OpenTSDB + elasticsearch plugin

为了安装 OpenTSDB ,都需要以下条件和软件:

  • Linux操作系统
  • JRE 1.6 or later
  • HBase 0.92 or later

3.1. 安装GnuPlot

如果你还想使用自带的界面,则需要安装GnuPlot 4.2及以后版本,以及gd和gd-devel等。这里我们选择了GnuPlot 5.0.1的版本。

根据情况执行(没有就装),安装所需软件

$ sudo yum install -y gd gd-devel libpng libpng-devel

之后安装GnuPlot:

$ tar zxvf gnuplot-5.0.1.tar.gz
$ cd gnuplot-5.0.1
$ ./configure
$ make
$ sudo make install

3.2. 安装HBase

首先,确保设置了JAVA_HOME:

$ echo $JAVA_HOME
/usr

这个不多说了,非常简单,只需要按照https://hbase.apache.org/book.html#quickstart这里所说,下载、解压、修改配置文件、启动即可。

这时候,再设置HBASE_HOME:

$ echo $HBASE_HOME
/opt/hbase-1.0.1.1

之后便可启动hbase:

$ /opt/hbase-1.0.1.1/bin/start-hbase.sh
starting master, logging to /opt/hbase-1.0.1.1/logs/hbase-vagrant-master-localhost.localdomain.out

3.3. 安装 OpenTSDB

这个也很简单,如果build失败,那肯定是缺少Make或者Autotools等东西,用包管理器安装即可。

$ git clone git://github.com/OpenTSDB/opentsdb.git
$ cd opentsdb
$ ./build.sh

创建表OpenTSDB所需要的表结构:

$ env COMPRESSION=NONE ./src/create_table.sh
2015-07-08 06:17:58,045 WARN [main] util.NativeCodeLoader: Unable to load native-hadoop library for your platform… using builtin-java classes where applicable
HBase Shell; enter ‘help‘ for list of supported commands.
Type “exit” to leave the HBase Shell
Version 1.0.1.1, re1dbf4df30d214fca14908df71d038081577ea46, Sun May 17 12:34:26 PDT 2015

create ‘tsdb-uid’,
{NAME => ‘id’, COMPRESSION => ‘NONE’, BLOOMFILTER => ‘ROW’},
{NAME => ‘name’, COMPRESSION => ‘NONE’, BLOOMFILTER => ‘ROW’}
0 row(s) in 1.3180 seconds

Hbase::Table – tsdb-uid

create ‘tsdb’,
{NAME => ‘t’, VERSIONS => 1, COMPRESSION => ‘NONE’, BLOOMFILTER => ‘ROW’}
0 row(s) in 0.2400 seconds

Hbase::Table – tsdb

create ‘tsdb-tree’,
{NAME => ‘t’, VERSIONS => 1, COMPRESSION => ‘NONE’, BLOOMFILTER => ‘ROW’}
0 row(s) in 0.2160 seconds

Hbase::Table – tsdb-tree

create ‘tsdb-meta’,
{NAME => ‘name’, COMPRESSION => ‘NONE’, BLOOMFILTER => ‘ROW’}
0 row(s) in 0.4480 seconds

Hbase::Table – tsdb-meta

在habse shell里,可以看到表已经创建成功。

> list
TABLE
tsdb
tsdb-meta
tsdb-tree
tsdb-uid
4 row(s) in 0.0160 seconds

表创建之后,即可启动tsd服务,只需要运行如下命令:

$ build/tsdb tsd

如果看到输出:

2015-07-09 05:51:10,875 INFO [main] TSDMain: Ready to serve on /0.0.0.0:4242

即可认为启动成功。

3.4. 安装opentsdb-search

注意,这不是一个必须的组件,如果你不想试试elasticsearch,也可以跳过此步骤。

这是一个Elasticsearch实现的 OpenTSDB plugin,当TSD收到数据的时候,也会传一份到search plugin,这个search plugin是为了丰富查询功能而提供的第三方插件。不过该plugin已经很久不维护了,不能直接用于 OpenTSDB 2.1上,这里有一个可用版本:https://github.com/liubin/opentsdb-elasticsearch/tree/version-up ,可以直接导入Eclipse开发。

编译之后,需要将插件拷贝到 OpenTSDB 的插件目录(该目录可以自定义。)
比如在我们下载的 OpenTSDB 代码下,创建plugins目录( OpenTSDB 会自动扫描该目录下的所有.jar文件。),这是我的测试环境下的目录结构。

$ pwd
/opt/opentsdb/plugins/elasticsearch

$ ls -l
total 32
-rw-r–r–. 1 vagrant vagrant 7158 Jul 7 05:17 httpclient-failover-1.0.jar
drwxr-xr-x. 5 vagrant vagrant 4096 May 31 11:24 httpcomponents-client-4.5
-rw-r–r–. 1 vagrant vagrant 17173 Jul 7 12:09 otsdb-elasticsearch-1.0.jar

.jar文件拷贝完之后,需要修改配置文件(当前文件夹下,不在build文件夹下。),添加如下内容:

tsd.core.plugin_path = ./plugins
tsd.search.enable = true
tsd.search.plugin = net.opentsdb.search.ElasticSearch
tsd.search.elasticsearch.hosts = localhost

之后重启 OpenTSDB 服务即可。

3.5. 安装elasticsearch和kibana

这两个也是下载解压即可使用,不再多说。这两个软件也装在/opt下。

启动elasticsearch和kibana后,即可创建索引。

$ curl -XPUT ‘localhost:9200/opentsdb?pretty’
{
“acknowledged” : true
}

创建类型映射(其实不创建也可以,这个映射也不是特别全。)

$ curl -X PUT -d @scripts/tsmeta_map.json http://localhost:9200/opentsdb/tsmeta/_mapping
{“acknowledged”:true}

$ curl -X PUT -d @scripts/uidmeta_map.json http://localhoeta/_mappingtsdb/uidm
{“acknowledged”:true}

$ curl -X PUT -d @scripts/annotation_map.json http://locannotation/_mappingb/a
{“acknowledged”:true}

4. 保存数据

在安装并启动所有服务之后,我们就来尝试发送1条数据吧。

最简单的保存数据方式就是使用telnet。

$ telnet localhost 4242

put sys.cpu.user 1436333416 23 host=web01 user=10001

这时,从kibana的界面和 OpenTSDB 自带界面都可以看到这些数据。
由于sys.cpu.sys的数据只有一条,所以 OpenTSDB 只能看到一个点。而elasticsearch则看到了多条数据,这时因为它还索引了meta数据等信息(除了metric,还有tagk和tagv)。

下图为 OpenTSDB 自带的查询界面,访问http://localhost:4242即可。

下图为数据同步到elasticsearch后,在kibana控制面板看到的样子,注意选择创建索引的适合,去掉时间序列的那个勾,否则创建按钮是灰色的。

我们来看看 OpenTSDB 的重要概念uid,先从HBase中存储的数据开始吧,我们来看一下它都有哪些表,以及这些表都是干什么的。

● tsdb:存储数据点

hbase(main):003:0> scan 'tsdb'
ROW                           COLUMN+CELL                                                                         
 \x00\x00\x01U\x9C\xAEP\x00\x column=t:q\x80, timestamp=1436350142588, value=\x17                                 
 00\x01\x00\x00\x01\x00\x00\x                                                                                     
 02\x00\x00\x02                                                                                                   
1 row(s) in 0.2800 seconds

可以看出,该表只有一条数据,我们先不管rowid,只来看看列,只有一列,值为0x17,即十进制23,即该metric的值。

左面的row key则是 OpenTSDB 的特点之一,其规则为:

metric + timestamp + tagk1 + tagv1… + tagkN + tagvN

以上属性值均为对应名称的uid。

我们上面添加的metric为:

sys.cpu.user 1436333416 23 host=web01 user=10001

一共涉及到5个uid,即名为sys.cpu.user的metric,以及host和user两个tagk及其值web01和10001。

上面数据的row key为:

\x00\x00\x01U\x9C\xAEP\x00\x00\x01\x00\x00\x01\x00\x00\x02\x00\x00\x02

具体这个row key是怎么算出来的,我们来看看tsdb-uid表。

tsdb-uid:存储name和uid的映射关系

下面tsdb-uid表的数据,各行之间人为加了空行,为方便显示。

tsdb-uid用来保存名字和UID(metric,tagk,tagv)之间互相映射的关系,都是成组出现的,即给定一个name和uid,会保存(name,uid)和(uid,name)两条记录。


hbase(main):004:0> scan 'tsdb-uid'
ROW                           COLUMN+CELL                                                                         
 \x00                         column=id:metrics, timestamp=1436350140242, value=\x00\x00\x00\x00\x00\x00\x00\x01  
 \x00                         column=id:tagk, timestamp=1436350141423, value=\x00\x00\x00\x00\x00\x00\x00\x02     
 \x00                         column=id:tagv, timestamp=1436350141475, value=\x00\x00\x00\x00\x00\x00\x00\x02     

 \x00\x00\x01                 column=name:metric_meta, timestamp=1436350142592, value={"type":"METRIC","displayNam
                              e":"","description":"","notes":"","created":1436350140,"custom":null}               
 \x00\x00\x01                 column=name:metrics, timestamp=1436350140348, value=sys.cpu.user                    
 \x00\x00\x01                 column=name:tagk, timestamp=1436350141357, value=host                               
 \x00\x00\x01                 column=name:tagk_meta, timestamp=1436350142592, value={"type":"TAGK","displayName":"
                              ","description":"","notes":"","created":1436350141,"custom":null}                   
 \x00\x00\x01                 column=name:tagv, timestamp=1436350141385, value=web01                              
 \x00\x00\x01                 column=name:tagv_meta, timestamp=1436350142592, value={"type":"TAGV","displayName":"
                              ","description":"","notes":"","created":1436350141,"custom":null}                   

 \x00\x00\x02                 column=name:tagk, timestamp=1436350141462, value=user                               
 \x00\x00\x02                 column=name:tagk_meta, timestamp=1436350142592, value={"type":"TAGK","displayName":"
                              ","description":"","notes":"","created":1436350141,"custom":null}                   
 \x00\x00\x02                 column=name:tagv, timestamp=1436350141480, value=10001                              
 \x00\x00\x02                 column=name:tagv_meta, timestamp=1436350142592, value={"type":"TAGV","displayName":"
                              ","description":"","notes":"","created":1436350141,"custom":null}                   

 10001                        column=id:tagv, timestamp=1436350141495, value=\x00\x00\x02                         

 host                         column=id:tagk, timestamp=1436350141363, value=\x00\x00\x01                         

 sys.cpu.user                 column=id:metrics, timestamp=1436350140408, value=\x00\x00\x01                      

 user                         column=id:tagk, timestamp=1436350141466, value=\x00\x00\x02                         

 web01                        column=id:tagv, timestamp=1436350141396, value=\x00\x00\x01                         

8 row(s) in 0.7280 seconds

我们一共看到了8行数据。

前面我们在tsdb表中已经看到,metric数据的row key为\x00\x00\x01U\x9C\xAEP\x00\x00\x01\x00\x00\x01\x00\x00\x02\x00\x00\x02
,我们将其分解下,用+号连起来(从name到uid的映射为最后5行):

 \x00\x00\x01 + U + \x9C\xAE + P + \x00\x00\x01 + \x00\x00\x01 + \x00\x00\x02  + \x00\x00\x02
sys.cpu.user       1436333416           host    =      web01          user     =    10001

可以看出,这和我们前面说到的row key的构成方式是吻合的。

需要着重说明的是时间戳的存储方式。

虽然我们指定的时间是以秒为单位的,但是,row key中用到的却是以一小时为单位的,即:1436333416 – 1436333416 % 3600 = 1436331600 。

1436331600转换为16进制,即0x55 0x9c 0xae 0x50,而0x55即大写字母U,0x50为大写字母P,这就是4个字节的时间戳存储方式。相信下面这张图能帮助各位更好理解这个意思,即一小时只有一个row key,每秒钟的数据都会存为一列,大大提高查询的速度。

反过来,从uid到name也一样,比如找uid为\x00\x00\x02的tagk,我们从上面结果可以看到,该row key(\x00\x00\x02)有4列,而column=name:tagk的value就是user,非常简单直观。

重要:我们看到,上面的metric也好,tagk或者tagv也好,uid只有3个字节,这是 OpenTSDB 的默认配置,三个字节,应该能表示1600多万的不同数据,这对metric名或者tagk来说足够长了,对tagv来说就不一定了,比如tagv是ip地址的话,或者电话号码,那么这个字段就不够长了,这时可以通过修改源代码来重新编译 OpenTSDB 就可以了,同时要注意的是,重编以后,老数据就不能直接使用了,需要导出后重新导入。

tsdb-meta:元数据表

我们再看下第三个表tsdb-meta,这是用来存储时间序列索引和元数据的表。这也是一个可选特性,默认是不开启的,可以通过配置文件来启用该特性,这里不做特殊介绍了。

hbase(main):005:0> scan 'tsdb-meta'
ROW                           COLUMN+CELL                                                                         
 \x00\x00\x01\x00\x00\x01\x00 column=name:ts_ctr, timestamp=1436350141578, value=\x00\x00\x00\x00\x00\x00\x00\x01 
 \x00\x01\x00\x00\x02\x00\x00                                                                                     
 \x02                                                                                                             
 \x00\x00\x01\x00\x00\x01\x00 column=name:ts_meta, timestamp=1436350142589, value={"tsuid":"0000010000010000010000
 \x00\x01\x00\x00\x02\x00\x00 02000002","displayName":"","description":"","notes":"","created":1436350141,"custom"
 \x02                         :null,"units":"","dataType":"","retention":0,"max":"NaN","min":"NaN"}               
1 row(s) in 0.1320 seconds

tsdb-tree:树形表

第4个表是tsdb-tree,用来以树状层次关系来表示metric的结构,只有在配置文件开启该特性后,才会使用此表,这里我们不介绍了,可以自己尝试。

保存数据除了我们前面用到的telnet方式,也可以选择HTTP API或者批量导入工具import(http://opentsdb.net/docs/build/html/user_guide/cli/import.html),这里我们再对HTTP API进行简单示例说明。

假设我们有如下数据,保存为文件mysql.json:

[
    {
        "metric": "mysql.innodb.row_lock_time",
        "timestamp": 1435716527,
        "value": 1234,
        "tags": {
           "host": "web01",
           "dc": "beijing"
        }
    },
    {
        "metric": "mysql.innodb.row_lock_time",
        "timestamp": 1435716529,
        "value": 2345,
        "tags": {
           "host": "web01",
           "dc": "beijing"
        }
    },
    {
        "metric": "mysql.innodb.row_lock_time",
        "timestamp": 1435716627,
        "value": 3456,
        "tags": {
           "host": "web02",
           "dc": "beijing"
        }
    },
    {
        "metric": "mysql.innodb.row_lock_time",
        "timestamp": 1435716727,
        "value": 6789,
        "tags": {
           "host": "web01",
           "dc": "tianjin"
        }
    }
]

之后执行如下命令:

$ curl -X POST -H “Content-Type: application/json” http://localhost:4242/api/put -d @mysql.json

即可将数据保存到 OpenTSDB 了。

5. 查询数据

看完了如何保存数据,我们再来看看如何查找。

查询数据可以使用query接口,它既可以使用get的query string方式,也可以使用post方式以JSON格式指定查询条件,这里我们以后者为例,对刚才保存的数据进行说明。

首先,保存如下内容为search.json:

{
    "start": 1435716527,
    "queries": [
        {
            "metric": "mysql.innodb.row_lock_time",
            "aggregator": "avg",
            "tags": {
                "host": "*",
                "dc": "beijing"
            }
        }
    ]
}

执行如下命令进行查询。

$ curl -s -X POST -H "Content-Type: application/json" http://localhost:4242/api/query -d @search.json | jq .
[
  {
    "metric": "mysql.innodb.row_lock_time",
    "tags": {
      "host": "web01",
      "dc": "beijing"
    },
    "aggregateTags": [],
    "dps": {
      "1435716527": 1234,
      "1435716529": 2345
    }
  },
  {
    "metric": "mysql.innodb.row_lock_time",
    "tags": {
      "host": "web02",
      "dc": "beijing"
    },
    "aggregateTags": [],
    "dps": {
      "1435716627": 3456
    }
  }
]

可以看出,我们保存了dc=tianjin的数据,但是并没有在此查询中返回,这是因为,我们指定了dc=beijign这一条件。

值得注意的是,tags参数在新版本2.2中,将不被推荐,取而代之的是filters参数。

6. 总结

可以看出来, OpenTSDB 还是非常容易上手的,尤其是单机版,安装也很简单。由HBase作为后盾,查询起来也非常快,很多大公司,类似雅虎等,也都在用此软件。

但是,大规模用起来,多个TDB以及多存储节点等,应该都需要专业、细心的运维工作了。

Posted in DevOps, Tech, Web

解决Kafka“Failed to send messages after 3 tries”错误

如果是在同一台机器(localhost),Kafka和Java Client工作正常,Producer和Consumer都能正常发送和接收消息,但是一旦部署到两台机器,则默认配置的话不能正常工作。会出现“kafka.common.FailedToSendMessageException: Failed to send messages after 3 tries”的错误。


[2015-07-02 15:15:39,295] WARN Error while fetching metadata [{TopicMetadata for topic datadog-dev -> 
No partition metadata for topic datadog-dev due to kafka.common.LeaderNotAvailableException}] for topic [datadog-dev]: class kafka.common.LeaderNotAvailableException  (kafka.producer.BrokerPartitionInfo)
[2015-07-02 15:15:39,295] ERROR Failed to collate messages by topic, partition due to: Failed to fetch topic metadata for topic: datadog-dev (kafka.producer.async.DefaultEventHandler)
[2015-07-02 15:15:39,399] WARN Error while fetching metadata [{TopicMetadata for topic datadog-dev -> 
No partition metadata for topic datadog-dev due to kafka.common.LeaderNotAvailableException}] for topic [datadog-dev]: class kafka.common.LeaderNotAvailableException  (kafka.producer.BrokerPartitionInfo)
[2015-07-02 15:15:39,399] ERROR Failed to send requests for topics datadog-dev with correlation ids in [9,16] (kafka.producer.async.DefaultEventHandler)
[2015-07-02 15:15:39,399] ERROR Error in handling batch of 4 events (kafka.producer.async.ProducerSendThread)
kafka.common.FailedToSendMessageException: Failed to send messages after 3 tries.
  at kafka.producer.async.DefaultEventHandler.handle(DefaultEventHandler.scala:90)
  at kafka.producer.async.ProducerSendThread.tryToHandle(ProducerSendThread.scala:104)
  at kafka.producer.async.ProducerSendThread$$anonfun$processEvents$3.apply(ProducerSendThread.scala:87)
  at kafka.producer.async.ProducerSendThread$$anonfun$processEvents$3.apply(ProducerSendThread.scala:67)
  at scala.collection.immutable.Stream.foreach(Stream.scala:547)
  at kafka.producer.async.ProducerSendThread.processEvents(ProducerSendThread.scala:66)
  at kafka.producer.async.ProducerSendThread.run(ProducerSendThread.scala:44)

解决方法其实很简单,只需要在Kafka的配置文件server.properties中,设置好主机名即可:

# Hostname the broker will bind to. If not set, the server will bind to all interfaces
host.name=queue-server1

究其原因,其实从注释里,我们可以知道,这是一个指定broker的地址(严格来说是所监听的网络接口,或者网卡),同时,也可以看出它还和下面的属性相关。

# Hostname the broker will advertise to producers and consumers. If not set, it uses the
# value for “host.name” if configured. Otherwise, it will use the value returned from
# java.net.InetAddress.getCanonicalHostName().
#advertised.host.name=

也就是说,producer和consumer是通过这个主机名(advertised.host.name)来连接broker的,而如果这个值没有设置,则会使用上面的host.name的值,如果上面的host.name也没有设置,则会使用java.net.InetAddress.getCanonicalHostName()获取的值。

从Zookeeper中可以看出,默认的时候该broker是localhost,从其他机器访问当然不可能成功。


get /brokers/ids/0
{"jmx_port":-1,"timestamp":"1435818382516","host":"localhost","version":1,"port":9092}
cZxid = 0x12
ctime = Thu Jul 02 06:26:22 UTC 2015
mZxid = 0x12
mtime = Thu Jul 02 06:26:22 UTC 2015
pZxid = 0x12
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x14e4d71a0bc0000
dataLength = 86
numChildren = 0

参考资料

https://cwiki.apache.org/confluence/display/KAFKA/FAQ#FAQ-Whycan’tmyconsumers/producersconnecttothebrokers?

https://sathyatechblog.wordpress.com/2014/07/17/kafka-and-storm-commonly-faced-issues/

Posted in DevOps

使用Packer创建Digital Ocean镜像

1. 什么是Packer

Packer没听说过,可能你听说过Vagrant;如果Vagrant都没听说过,那么请原谅我说这真是稍微有点out了(如果你知道DevOps这个词的话)。

Packer和Vagrant都出自HashiCorp这个公司,这个公司实在是太性感了,以至于我还专门写过一些介(ruǎn)绍(wén):http://slides.com/binliu/docker-and-hashicorp#/

除了本文要说的Packer之外,HashiCorp的大杀器还包括:

  • Serf:一个简单的去中心化的集群管理软件;
  • Consul:跨DataCenter的服务发现、配置管理和编配软件;
  • Terraform:一个高抽象度、消除基础设施物理差异,来对基础设施进行构建、变更和版本化的工具;
  • VAULT:用于保存密码、token等机密信息的存储服务;
  • Atlas:通过一种工作流,完成公司内部从开发到发布的流程支撑基础工具。

那么言归正传,什么是Packer呢?按照官方介绍:

Packer is a tool for creating identical machine images for multiple platforms from a single source configuration.

也就是说,Packer是一款创建(OS)镜像的工具,它以代码为表现形式,并支持多种本地和云平台,比如支持VirtualBox的OVF和VMware的VMDK镜像,以及EC2、DigitalOcean、OpenStack、Google Compute Engine等主流云主机,甚至支持构建Docker镜像。

Packer的目的不是取代现有的配置管理工具,比如Chef或者Puppet等,而是可以和这些工具配合使用,沿用公司历史资产。

Packer的项目主页: https://packer.io/ 。

2. 为什么用Packer

首先,那就是基础设施代码化,即Infrastructure as Code。因为我们都是程序员,喜欢代码,喜欢命令行,我们喜欢在Shell执行命令,看结果在屏幕上流动那种感觉。

其次,有利于实现Immutable Infrastructure,即不可变基础设施,这一部分灵感来自于函数式编程,不可变带来的好处是不会发生状态污染,也就是说,环境之间的差异被消除,出了问题,很容易知道是自己代码的问题,还是OS等基础设施软件的问题。

Packer还能提高部署速度。在云计算之前,我们要想启动一台机器老费劲了,可能得从购买CPU、硬盘开始,攒成机器,装上OS,再装软件,把代码拷贝进去,再启动;有了云计算技术,前几步我们都可以省了;而使用Packer的话,只要你的程序不需要更新,所有步骤都可以省了。启动服务,约等于从镜像启动一台主机这么简单、快速。

Packer还能在一定程度上简化、方便在vendor之间进行迁移(备份),使用混合环境。比如,你可以在Digital Ocean上构架staging环境,而生产环境跑在AWS上,开发则使用桌面虚拟机技术,比如VirtualBox等。

面向测试。你知道么,基础设施也是可以测试的,像单元测试一样,比如像下面这样:

# http://serverspec.org/resource_types.html#port
describe port(80) do
  it { should be_listening.on('127.0.0.1').with('tcp') }
end

# http://serverspec.org/resource_types.html#docker_image
describe docker_image('busybox:latest') do
  it { should exist }
end

3. Packer示例

下面我们就来看一下简单的使用方式。

首先要安装Packer,这里就不多说了,可参看官方的安装说明 ,基本上就是下载、修改权限就行了,因为是Go编写的工具,都是直接可运行文件。

首先,我们需要创建一个JSON格式的模板(Template)文件,用来描述要构建的镜像信息。这里我们以在Digital Ocean上为例,来看一下如何构建一个镜像(实际上会保存为一个snapshot)。

示例如下(https://github.com/liubin/packer-templates/blob/master/digitalocean.json):

{

  "variables": {
    "do_api_token": "{{env `DIGITALOCEAN_API_TOKEN`}}"
  },

  "builders": [{
    "type": "digitalocean",
    "api_token": "{{user `do_api_token`}}",
    "droplet_name": "as",
    "image": "centos-7-0-x64",
    "size": "512mb",
    "region": "sfo1"
  }],

  "provisioners": [{
    "type": "shell",
    "inline": [
      "sleep 30",
      "sudo yum update -y",
      "sudo yum install -y http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm",
      "sudo yum install -y nginx"
    ]
  }]

}

配置文件很简单,从名字上能看出大概每一行都在干什么。

provisioners指的是如何进行安装、配置,这里我们简单地使用shell的方式安装了nginx而已。

进行构建也很简单,只需要运行packer build digitalocean.json即可。

运行结果如下:

 

 

如果构建成功,会输出上图下面那部分内容,包括了Creating snapshot: packer-xxxx等信息。

这时候,我们也可以在Digital Ocean控制台上看到刚创建的镜像。

有了此镜像,我们就可以随意创建任意数量的云主机出来了,从而保证了运行环境的统一。这也是基础设施代码化和不可变基础设施的一项基本条件。

当然这个例子很简单,Packer也是HashiCorp众多优秀开源工具之一,也是其工具链上的一环。我们还可以将构建工作放到他们的服务Atlas(https://atlas.hashicorp.com/ )上进行,这样我们就可以将漫长的构建过程从本地分离出来,类似CI服务,当我们更新模板文件后,可以通过诸如 vagrant push、git push甚至其它CI 等命令启动远程构建活动。

此外,构建的镜像也可以保存到Atlas里并进行版本化,以方便 Terraform(https://terraform.io/)等工具使用。

4. 我们该如何使用Packer?

云计算的出现,不只是把服务器从硬件搬到虚拟机那么简单,同时带来的是软件设计思想、开发流程和生命周期管理的变化。材料不同,加工方式也对应有变化。

现在的开源软件,除了基础设施,包括OS、容器、以及中间件等,还包括很多开发工具,甚至git-flow这样的方法论。流程的变化已经是不得不面对的现实,选择合适的技术、工具,并正确的使用,显得越来越重要。

Packer就是随着这种发展而出现的一种工具。我们既可以单独使用,也可以配合其他工具在自己的流程中使用。

HashiCorp做Packer是有目的的,这也是其工具链上的一个重要组成部分,HashiCorp的大本营是Atlas,我们可以以此为枢纽,将Vagrant,Packer,Terraform等工具运用到该流程的各个部分。下面的图来自官网介绍。

 

 

在此图中,HashiCorp想的是使用Vagrant开发,在本地通过vagrant push触发Packer进行镜像(包括应用程序代码)构建,之后Terraform使用构建结果(artifacts )进行物理部署,基础设施(其实这里的基础设施已经包括应用程序了)部署之后,Consul开始对集群的监控工作。

个人认为此流程只能说理想,思路很不错,但是有其缺点,尤其是在国内,离实用化还有一定距离。

首先,我觉得Vagrant不应该出现在其中(或者说地位不应该这么重要),而应该通过git或者CI等来完成build的触发工作。应该更积极的开放出整个流程的一部分功能给第三方;

其次,国内云服务的public API完善程度太低,这个不多说了。软件吞噬世界,API连接一切;

最后,对Docker的支持给人一种hotfix的感觉。Packer构建Docker镜像,也只是简单的export机制,丢失了历史信息;如何在云上高效、透明的运行容器,也缺乏一些方向性的支持。Docker是个产品,也是一整套生态环境,HashCorp应该更多的去拥抱其他开源项目,而不要太过于迷信If a sufficient tool does not exist, we step in to build it.

以上个人愚见。

— 全文完 —

Posted in DevOps, Tech

创建基于Docker的E*K(ElasticSearch、Fluentd、Kibana)日志采集系统

说道日志采集和可视化,有很多方案,而最多的应该莫过于ELK了(ElasticSearch、Logstash和Kibana),而这一套软件,目前也同归于同一项目:https://www.elastic.co/

不过我日志采集系统我没有使用Logstash,而是采用了Fluentd。

一共有两个Docker镜像,一共是日志采集,即fluentd-agent(https://github.com/liubin/fluentd-agent),另一个是日志存储(es)和可视化(kibanna)es-dashboard(https://github.com/liubin/es-dashboard)。其中fluentd-agent运行于nginx服务器上(也可以自己修改td-agent.conf来改写规则),而es-dashboard由两个应用组成,es和kibana,它们既可以运行在一个容器中,也可以分开运行,具体见后面说明。

1. 在同一容器中运行es和kibana

sudo docker run -p 9200:9200 -p 9300:9300 -p 5601:5601 liubin/es-dashboard

2. 或者在两个容器中运行es和kibana

2.1. 启动es

sudo docker run -p 9200:9200 -p 9300:9300 liubin/es-dashboard --name es /tmp/start.sh es

2.2. 启动kibana

得到上一个容器的IP地址和es端口后,启动第二个容器

sudo docker run -p 5601:5601 -e ELASTIC_URl=192.168.33.121:9200 --name kibana liubin/es-dashboard /tmp/start.sh kibana

其中,192.168.33.121:9200需要更加情况使用自己的值。

3. 启动fluentd-agent

es服务器启动后,就可以启动agent了,也很简单。

sudo docker run -e ELASTIC_HOST=172.17.0.92 -e ELASTIC_PORT=9200 -v /var/log:/var/log --name agent liubin/fluentd-agent

需要指定ELASTIC_HOST和ELASTIC_PORT两个变量,都是2.1. es启动后得到的值。

之后,既可以到kibana的首页,默认为http://:5601查看结果了。

3. 安全问题

如果将es服务等放到公网,需要注意安全问题,因为默认es和kibana都不带访问控制的,需要自己使用插件或者其他产品,或者防火墙来实现。

这部分参考资料如下:

1. https://www.elastic.co/blog/scripting-security/
2. https://www.elastic.co/products/shield
3. http://www.elastic.co/guide/en/kibana/current/production.html

Posted in Docker, Tech

Libreoffice permission issue under Nginx web server and PHP

We have a feature need to convert office documents to pdf files,because our servers are all CentOS, so we selected LibreOffice to do the conversion.

The command can run successfully under console, but failed in web server.

// PHP code.

$cmd = "libreoffice --headless ...";
exec($cmd, $output, $code);

Here we got $code with value 77, and $output as an empty array.

It’s a permission issue.But if we change the $cmd to someothers, for example “java xxx”, it’s all OK.

Our nginx server is running as user www, so we su – www and run the convertor command manually. And got these errors:

-bash-4.1$ /usr/bin/libreoffice –headless –convert-to pdf /tmp/fff.docx –outdir /tmp
[Java framework] Error in function createSettingsDocument (elements.cxx).
javaldx failed!
Warning: failed to read path from javaldx

We googled the web, and it seems an permission issue.But the file /user/bin/libreoffice has an normal property as other commands, there is no doubt that we have the execute permission on it.

So we begin inspect the user himself.

Ohh, www user’s home is set to /home, certainly he can do nothing in his home directory.We should change it somewhere else.

Create a new directory /home/www and make it the home directory for user www, su – www and then run the libreoffice command in console, this time we got no errors.

But we still get errors when run it form browser.After restart the Nginx and PHP-FPM service, all got worked.

Posted in Tech Tagged with:

Use DJJob in CodeIgniter

In a web application, time consuming processes, like sending emails, you need make it asynchronous. In this blog, we will introduce how to make a job running in the background using DJJob and crontab or CLI.

In the Rails world, you have many choices: Delayed Job, Resque, Sidekiq. Even in Rails 4.2, it introduced Active Job.

If you are using PHP, it may be inconvenient.But you also have some choice, for example DJJob, through the homepage, it’s a:

DJJob allows PHP web applications to process long-running tasks asynchronously. It is a PHP port of delayed_job (developed at Shopify), which has been used in production at SeatGeek since April 2010.

Like delayed_job, DJJob uses a jobs table for persisting and tracking pending, in-progress, and failed jobs.

Here we will write a simple exapmle, to use DJJob to send email in background.

At first your need to install DJJob by copy the PHP files and create tables.Here we will skip these steps. Let’s start by writing business code directly.

A job class is needed, it’s a normaly PHP class, you don’t need to extend any classes, or keep to any rules(all you need is to write a method named `perform`).

<?php

Class Mail_job{

    private $_CI;
    private $config;
    private $to;
    private $subject;
    private $body;

    public function __construct($to, $subject, $body){
        $this->to = $to;
        $this->subject = $subject;
        $this->body = $body;
    }

    public function perform() {
        $this->_CI = &get_instance();
        $this->_CI->config->load('mail');
        $this->config = $this->_CI->config->item('mail_server');

        $config['protocol'] = 'smtp';
        $config['smtp_host'] = $this->config['smtp_host'];
        $config['smtp_port'] = $this->config['smtp_port'];
        $config['smtp_user'] = $this->config['smtp_user'];
        $config['smtp_pass'] = $this->config['smtp_pass'];
        $config['charset'] = 'utf-8';
        $config['wordwrap'] = TRUE;
        $config['newline'] = "\r\n";
        $config['crlf'] = "\r\n";
        $config['mailtype'] = 'html';

        $this->_CI->email->clear();
        $this->_CI->email->initialize($config);

        $this->_CI->email->from($from_email, 'no-reply');
        $this->_CI->email->to($to);
        $this->_CI->email->subject($subject);
        $this->_CI->email->message($body);

        return $this->_CI->email->send();

    }

}

Notcie:

job class’s __construct should as simply as possible, it’s will affect the size of serialized class.

To use this job class, you will insert these code in your source:

DJJob::configure(['driver'=> 'mysql','host'=> $db->hostname,
            'dbname'=> $db->database,
            'user'=> $db->username,
            'password'=> $db->password]);

$mj = new Mail_job($to, $subject, $body, $mailtype);
DJJob::enqueue($mj, "email");

These code first do the database configuration, and then serialize the Mail_job instance into database use DJJob::enqueue($mj, “email”);  and the queue name is email(the sencond parameter).

Now we have saved a job into database, next let’s learn how to execute the job.

Our job executer will run in an console but not from a web browser, CodeIgniter support this use case by running Controller from CLI, for more infomation you can checkout the offical document here.

We will create a Controller too, let’s name it to Queue_job :


class Queue_job extends CI_Controller {

    function __construct(){
        parent::__construct();
        // this controller can only be called from the command line
        if (!$this->input->is_cli_request()) show_error('Direct access is not allowed');
    }

    function send_mail(){
        $db = $this->db;

        DJJob::configure(['driver'=> 'mysql','host'=> $db->hostname,
            'dbname'=> $db->database,
            'user'=> $db->username,
            'password'=> $db->password]);

        $worker = new \DJWorker(['queue' => 'email']);
        $worker->start();
    }
}

The main method is send_mail, this will first configurate the database an run a DJWorker. The DJWorker will pickup jobs from database and run them,  mark the job status, and lastly remove them from database if the job completed successfully.

If you have finished the Controller, then you can call the Controller from CLI like this:

$ cd /path/to/project;
$ php index.php Queue_job send_mail

or make a cron job like:

0 10 * * * /path/to/php /path/to/project/index.php Queue_job send_mail

Conclusion

Though it’s not as easy as in Rails, but it’s realy simple to run jobs that kicked off from web browser by end-user and running in background asynchronous.

Notice:

If you job class with complex types, you will pay attention to the include path somewhere if you got an error.

Reference:

1. How to Run Cron Jobs in CodeIgniter

2. Writing Cron Jobs and Command Line Scripts in CodeIgniter

Posted in PHP, Tech Tagged with:

Load database config file from libraries in CodeIgniter

Sometime you will want to load database config variables from your libraries.In this case, if you use $this->_CI->config->load(‘database’), you will get an error:

$this->_CI = &get_instance();
$this->_CI->config->load('database');

The error somthing like Your application/config/database.php file does not appear to contain a valid configuration array.

The source is in `system/core/Config.php#138`:


// CodeIgniter Version 2.1.4

if ( ! isset($config) OR ! is_array($config))
{
	if ($fail_gracefully === TRUE)
	{
		return FALSE;
	}
	show_error('Your '.$file_path.' file does not appear to contain a valid configuration array.');
}

You can find that the config will lookup an array with the name $config, but in your database’s config file it contains array variable with the name $db defaultly.This is the reason you got the invalid configuration array error message.

To correctly load database configuration from libraries,you can you code below:

$this->_CI->load->database('default');

$db = $this->_CI->db;

Something::config(['driver'=> 'mysql','host'=> $db->hostname,
    'dbname'=> $db->database,
    'user'=> $db->username,
    'password'=> $db->password]);

Notice the `default` is the active group in your database configuration file.

Posted in Tech Tagged with: ,

《Web应用安全权威指南》出版后记

《Web应用安全权威指南》终于出版了,从开始翻译到上市,历时近一年。这期间,译者,尤其是图灵的编辑都付出了巨大的努力。当然,只有获得读者的承认,所有人的努力才不算浪费。

我也是今天刚拿到纸版的书,从印刷质量上来说,还挺不错,字体正合适阅读,封面设计的也很好看;纸质也不错,虽然有点重,不过只有不到400页,拿在手里正好。关键是,价钱也不贵,大概60左右的样子,貌似China-Pub59块多。

如果书中有什么不对的地方,也希望读者能直接和图灵或者译者联系。

1. 关于图灵和本书

首先,我个人认为这本书的主要面向用户为:初中级水平的开发人员,对安全感兴趣又不是特别深入的人;以及经验相对丰富又想做些查漏补缺的人。

这本书主要是面向开发人员的,意在帮助大家开发安全的Web应用,而不是让你成为黑客或者专业的安全人员。

此外,本书除了全世界共通的技术语言之外,中日之间不同的开发方式,IT背景也能帮我们开阔眼界, 促使我们去考虑一些之前没人去考虑的方面。也许有些东西在国内不现实或者不实用,由于译者能力有限,未能全部按照国内情况改写,深感内疚。

再介绍下原作者德丸浩(Twitter ID:@ockeghem https://twitter.com/ockeghem)。2008年创立HASH咨询公司,任董事长。主要从事网络安全性的诊断与咨询工作,并在工作之余通过博客普及网络安全知识。兼任KYOCERA Communication Systems股份有限公司技术顾问、独立行政法人信息处理推进机构(IPA)客座研究员,活跃于日本安全业界。说到IPA,这是在日本非常权威厉害的IT部门,他们除了举办类似国内的程序员、系统分析员等考试之外,还和有很多非常实用的机构和功能,比如没年的开源大奖,未踏(即还没有人踏入过的领域、技术)大奖等,非常贴近实际产业现状和需求。而KYOCERA,即京瓷,则是最近在中国很火的日本管理大师稻盛和夫所创办的企业。

2. 本书内容

本书共分8章,分别是:

第1章 什么是 Web应用的安全隐患

本章是入门引子,非常简短,主要是介绍了什么什么是Web应用中的安全问题,及其出现原因。并提出安全性bug和安全性功能两个概念。

第2章 搭建试验环境

本章介绍了如何安装书中演示例子的环境,包括虚拟机和调试工具Fiddler等。

本书还附带了一个VMware Player虚拟机,里面是一个简单的Web运行环境,功能不比大多数小网站少,非常方便大家亲手进行实践,包括Apache/PHP/邮件服务等。不过遗憾的是里面环境有些是日文的,大家可以和书对照着看。

整个环境可以从http://www.ituring.com.cn/book/1249中的“随书下载”菜单下下载。

第3章 Web 安全基础:HTTP、会话管理、同源策略

可以认为这章是进入后续章节的入门预习,主要介绍了HTTP原理和会话功能。以及主动攻击和被动攻击,及同源策略。

什么是HTTP,URL的结构式怎样的,一个网页请求都会有哪些网络传输?HTTP请求和返回的各字段都是什么意思?GET和POST的区别等等问题,以及现代Web赖以生存的Cookie和Session机制及安全问题,本章都进行了详细的说明。

本章后半部分,对被动攻击和同源策略进行了具体的介绍。

第4章 Web应用的各种安全隐患

这是本书中分量最重的一章,因为它介绍了现代Web安全的最重要3个问题:XSS,SQL注入和CSRF。除此之外,还有一些可能会被我们忽略的小问题,比如重定向问题,邮件发送问题,调用OS命令等问题。

第5章 典型安全功能

本章主要对为了保护系统安全所编写的安全性功能相关的最佳实践,主要就是账号管理、认证授权,最后也介绍了日志管理。

比如说错误消息提示,如果登录的时候email并没有在应用中注册,那么错误消息怎么显示?是“该用户不存在”,还是“登录出错”?这个大难,还是留给各位在阅读本书时自己思考吧。

第6章 字符编码和安全

什么,字符编码还会导致安全问题?提到字符编码,你一定想首先,或者只会想到令人头痛的乱码问题;而实际上,字符编码确实是能引起安全隐患的,这根OS,所用语言、页面编码等都有关系,不过作为结论来说,不管从B格还是安全上考虑,都用UTF-8吧。

第7章 如何提高 Web网站的安全性

本章则主要从系统管理和运维的角度来审视如何提高Web网站的安全性。包括如何提高主机安全性,避免域名相关的攻击,以及通过导入SSL等增强系统安全性等内容。

第8章 开发安全的 Web应用所需要的管理

从管理上重视安全,将安全提高到企业级高度,这是国内开发商和公司需要提高的地方。尤其是在承包合同中,甲方乙方应该各自注意什么问题,应该履行什么义务等,本章都进行了简单介绍,相信的开发人员,尤其是负责管理管理工作的开发人员,都有一定的参考意义。

安全就是要防患未然,与其从技术入手,从管理和人入手都是一个简单低成本的途径。

3. 相关书籍

最后,再来简单的和其他一些本人看过的书籍做些对比。这两本书我都买了,也都读过,都非常不错。三本书虽然可以称得上是同一类别(至少书店里应该会摆一块吧),但是彼此侧重不同,重合的地方不多,将三本书结合起来看反而更相得益彰,对Web安全的认识也将更系统化和全面。

《Web之困:现代Web应用安全指南》:介绍了非常基础和科学的东西,重点从浏览器和HTTP协议上对安全问题进行了阐述,非常基础的技术,细致周密的讲述。

《白帽子讲Web安全》:介绍的内容非常全面,更接近于本书的内容和难易程度。

本书《Web应用安全权威指南》则非常专注于:Web安全和开发,没有涉及过多的额外内容,比如不想一些国内的其他书籍多多少少会“攒”些相关资料。如果用一个词来形容的话,我想应该是“实用”。

4. 广告时间:

《Web应用安全权威指南》购买地址:
亚马逊:http://www.amazon.cn/Web应用安全权威指南-日-徳丸浩/dp/B00NZNYT0G

互动出版(China-pub):http://product.china-pub.com/3770588

京东:http://item.jd.com/11553708.html

当当:http://product.dangdang.com/23575372.html

最后,再次对图灵的编辑,OWASP的子明表示深深的感谢。

Posted in 生活, Tech

Docker,云时代的程序交付方式

Docker — 云时代的程序分发方式

要说最近一年云计算业界有什么大事件?Google Compute Engine
的正式发布?Azure入华?还是AWS落地中国?留在每个人大脑中的印象可能各不相同,但要是让笔者来排名的话那么Docker绝对应该算是第一位的。如果你之前听说过它的话,那么也许你会说“没错,就是它”,因为几乎世界各地的开发、运维都在谈论着Docker;如果你还没听说过Docker,那么我真的建议你花上10分钟来阅读本文。

1. Docker简介

1.1. 什么是Docker?

Docker是一个重新定义了程序开发测试、交付和部署过程的开放平台。Docker也是容器技术的一种,它运行于Linux宿主机之上,每个运行的容器都是相互隔离的,也被称为轻量级虚拟技术或容器型虚拟技术。而且它有点类似Java的编译一次,到处运行,Docker则可以称为构建一次,在各种平台上运行,包括本地服务器和云主机等(Build once,run anywhere)。

容器就是集装箱,我们的代码都被打包到集装箱里;Docker就是搬运工,帮你把应用运输到世界各地,而且是超高速。

Docker是开源软件,代码托管在GitHub上,使用Go语言编写。Go可以称得上是互联网时代专门为开发分布式、高并发系统而生的编程语言。Docker也可以说是Go语言的一个杀手级应用,而且在Docker生态圈里很多软件也都是使用Go语言编写的。

1.2. Docker历史

Docker项目始于2013年3月,由当时的PaaS服务提供商dotCloud开发,dotClound也是YCombinator S10的毕业生。尽管Docker项目很年轻,到现在也只有15个月而已,然而它的发展势头如此之猛已经让很多人感叹不已了。

2013年10月dotCloud公司名字也由dotCloud, Inc.改为Docker, Inc.,集中更多的精力放到了Docker相关的研发上。

1.3. Docker的技术基石

在进入Docker的世界之前,我们先来看一下Docker实现所依赖的一些技术。

实际上Docker的出现离不开很多Linux kernel提供的功能,甚至可以说Docker在技术上并没有什么特别重大的创新之处,利用的都是已经非常成熟的Linux技术而已,这些技术早在Solaris 10或Linux Kernel 2.6的时候就有了。可以毫不夸张的说Docker就是“站在了巨人的肩膀上”。

下面我们就先来了解一下Docker主要利用的Linux技术。

1.3.1. 容器技术

容器(Container)有时候也被称为操作系统级虚拟化,以区别传统的Hypervisor虚拟技术。它不对硬件进行模拟,只是作为普通进程运行于宿主机的内核之上。

在容器中运行的一般都是一个简易版的Linux系统,有root用户权限、init系统(采用LXC容器的情况下)、进程id、用户id以及网络属性。

容器技术在云计算时代已经被大量使用。Google公司的Joe Beda在今年5月做了一次题为《Containers At Scale — At Google, the Google Cloud Platform and Beyond》注 1的演讲,在其中提到“Everything at Google runs in a container”,每周启动容器次数竟然多达20亿次。

注 1 https://speakerdeck.com/jbeda/containers-at-scale

很多PaaS平台都是基于容器技术实现的,比如目前最成功的PaaS平台Heroku。此外,还有比较著名的开源PaaS平台Cloud Foundry的Warden以及Google的Lmctfy(Let Me Contain That For You)注 2等。

注 2 Let Me Contain That For You,http://github.com/google/lmctfy

1.3.2. LXC

这也是在Linux下使用比较广泛的容器方案。基本上我们可以认为Linux containers = cgroups(资源控制) + namespaces(容器隔离)。

LXC很成熟很强大,然而它却不好使用,比如它不方便在多台机器间移动,不方便创建管理,不可重复操作,也不方便共享等等,相对于开发人员来说,它只是系统管理员的玩具。Docker的出现很好的解决了这些问题,它将容器技术的使用成本拉低到了一个平民价格。

1.3.3. namespaces

这是用来为容器提供进程隔离的技术,每个容器都有自己的命名空间,比如pid/net/ipc/mnt/uts等命名空间,以及为容器提供不同的hostname。namespace能保证不同的容器之间不会相互影响,每个容器都像是一个独立运行着的OS一样。

1.3.4. cgroups

cgroups是一个Google贡献的项目,它主要用来对共享资源的分配、限制、审计及管理,比如它可以为每个容器分配CPU、内存以及blkio等的使用限额等。cgroups使得容器能在宿主机上能友好的相处,并公平的分配资源以及杜绝资源滥用的潜在风险。

容器技术实现方案可以用下面的图进行简单说明。

图 Docker如何和Linux内核打交道
图1 Docker如何和Linux内核打交道

上图中的cgroups、namespaces和apparmor等都是Linux内核提供的功能。不管是传统的LXC还是Docker的libcontainer,都使用了Kernel的这些功能来实现容器功能。

1.3.5. 联合文件系统

联合文件系统是一个分层的轻量、高性能文件系统。Docker之所以这么吸引人,很大程度上在于其在镜像管理上所做出的创新。而联合文件系统正是构建Docker镜像的基础。

AUFS(AnotherUnionFS)是一个分层的基于Copy On Write技术的文件系统,支持Union Mount,就是将具有不同文件夹结构的镜像层进行叠加挂载,让它们看上去就像是一个文件系统那样。

1.4. 容器技术VS虚拟机技术

容器技术和Hypervisor技术虽然不属于同一层次的概念,但是作为具有计算能力的应用运行载体来说,它们还是有一定的共通性和竞争关系,这里作此对比完全是为了加深读者对容器技术的理解而已。

容器技术 虚拟机技术
占用磁盘空间 小,甚至几十KB(镜像层的情况) 非常大,上GB
启动速度 快,几秒钟 慢,几分钟
运行形态 直接运行于宿主机的内核上,不同容器共享同一个Linux内核 运行于Hypervisior上
并发性 一台宿主机可以启动成千上百个容器 最多几十个虚拟机
性能 接近宿主机本地进程 逊于宿主机
资源利用率

比如开源PaaS实现软件tsuru最初使用的是基于虚拟机的技术,创建一个应用程序需要5分钟左右的时间,而在采用Docker之后,已经将这个时间缩短到了10秒钟了注 3

注 3 tsuru and docker by Andrews Medina https://speakerdeck.com/andrewsmedina/tsuru-and-docker

1.5. 我们能用Docker干什么?

Docker可以应用在各种场景下,比如公司内部开发测试使用,或者作为共有或者私有PaaS平台等。

现在PaaS平台的发展已经非常成熟了,这里我们只罗列一些在开发中使用Docker技术可能会给我们带来的益处。

1.5.1 在开发中

构建开发环境变得简单

简单包括几个方面的意思

  • 快速:只需docker run即可
  • 共享:通过Dockerfile或者Registry
  • 自动化:一切代码化的东西都可以自动化
  • 统一:每个人的开发环境都是一模一样的

设想我们要基于Nginx/PHP、MySQL和Redis开发,我们可以创建3个Docker镜像保存到公司私有的Registry中去,每个开发人员使用的时候是需要执行docker run redis即可以享用自己独有的Redis服务了,而且这3个容器不管从占用磁盘空间还是运行性能来说,都比虚拟机要好很多。

1.5.2. 在测试中

解决环境构建问题

有时候构建测试的环境是一项费时费力的工作,而Docker能让这变得轻松。如果你的测试比较简单的话,甚至直接拿开发构建的镜像就可以开始了。

消除环境不一致导致的问题

“在我的机器上运行的好好的,怎么到你那里就不行了?”,我想超过半数的程序员都曾经说过类似的话。如果对导致这一问题的原因进行统计的话,我想排在第一位的应该非“环境不一致”莫属了,这包括操作系统和软件的版本、环境变量、文件路径等。

使用Docker的话你再也不用为此烦恼了。因为你交付的东西不光是你的代码、配置文件、数据库定义,还包括你的应用程序运行的环境:OS加上各种中间件、类库 + 你的应用程序。

1.5.3. 部署和运维

基于容器的部署和自动化

Docker定义了重新打包程序的方法。

Docker容器 + 用户应用 = 部署单位(构件)

Docker可以看作是用代码编写出来的国际集装箱,它可以把任何应用及相关依赖项打包成一个轻量、可移植(Portable)、自包涵的容器。

以前部署代码都是代码级别的,有了Docker,则可以进行容器级别的部署。这样带来的最大的好处就是开发者本地测试、CI服务器测试、测试人员测试,以及生产环境运行的都可以是同一个Docker镜像。

快速进行横向扩展

Docker容器的启动速度很快,可以瞬间启动大量容器,所以在非常适合在业务高峰期进行横向扩展。这比传统的启动EC2实例或者物理机可要快多了。

天生的和云计算技术相结合

当然,由于Docker具有很好的移植性,所以它更强大的地方还在于和云环境结合使用。

Docker容器是可移植,或者说跨平台。将来的应用部署可能是在本地进行打包(成Docker镜像)然后传送到云端运行,至于是AWS还是GCE这不是问题,Docker都能在其上运行。这样不仅能在一定程度上解决vendor-lockin的问题,同时也使得在不同的云服务提供商之间迁移也变得简单。尤其是未来在使用多云(multi-cloud)环境的时候,这将非常便利。

笔者认为基于IaaS + 容器技术的应用交付、部署方式将来一定会成为一种流行的方式。

进行Blue-green部署

「Blue-green deployment」这个词最初出现在《Continuous Delivery: Reliable Software Releases through Build, Test, and Deployment Automation 》一书,后经ThoughtWorks的Martin Fowler发扬光大注 4

注 4 http://martinfowler.com/bliki/BlueGreenDeployment.html

blue_green_deployment

Blue-green deployment方法其实很简单,就是保持两套一样的生产环境,而实际上只有一套环境真正的对外提供服务(图中绿色环境),而另一套环境则处于待机状态(图中蓝色)。部署的时候,我们会先上线到蓝色环境中,如果测试没有问题了,再将路由切换到新的服务上。

Blue-green部署能带来如下好处。

  • 最小化停机时间
  • 快速回滚
  • hot standby

而未来的开发和部署和可能就会像下面这样进行了。

  • ① 开发人员将代码push到Git仓库
  • ② CI工具通过webhook得到最新代码,构建Docker镜像并启动容器进行测试。
  • ③ 测试通过后将镜像打标签后push到私有镜像Registry
  • ④ CI工具通知CD工具
  • ⑤ CD工具通过Mesos/Marathon等进行基于容器的部署
  • ⑥ 测试没有问题后进行容器的切换(即Blue-green切换)

2. Docker架构解析

2.1. Docker整体结构

Docker是一个构建、发布、运行分布式应用的平台(见下图),Docker平台由Docker Engine(运行环境 + 打包工具)、Docker Hub(API + 生态系统)两部分组成。

图 Docker平台
图2 Docker平台

从图中我们可以看到,Docker的底层是各种Linux OS以及云计算基础设施,而上层则是各种应用程序和管理工具,每层之间都是通过API来通信的。

Docker引擎

Docker引擎是一组开源软件,位于Docker平台的核心位置。它提供了容器运行时以及打包、管理等工具。

Docker引擎可以直观理解为就是在某一台机器上运行的Docker程序,实际上它是一个C/S结构的软件,有一个后台守护进程在运行,每次我们运行docker命令的时候实际上都是通过RESTful Remote API来和守护进程进行交互的,即使是在同一台机器上也是如此。

Docker Hub

Docker Hub是一个云端的分布式应用服务,它专注于内容、协作和工作流。Docker Hub除了可以托管、下载、查找Docker镜像之外,还提供了包括更管理、团队协作、生命周期流程自动化等功能,以及对第三方工具和服务的集成。

2.2. Docker镜像(image)

2.2.1. Docker镜像

Docker镜像是Docker系统中的构建模块(Build Component),是启动一个Docker容器的基础。

我们可以通过一个官方提供的示意图来帮助我们来理解一下镜像的概念。

docker image

Docker镜像位于bootfs之上,实际上bootfs在系统启动后会被卸载的。Docker镜像(Images)是分层的,这得益于其采用的联合文件系统,前面我们已经介绍过了。镜像是有继承(父子)关系的,每一层镜像的下面一层称为父镜像,没有父镜像的称为基础镜像(Base Iamge,其实叫做Root Image可能更确切,不过这可能容易和rootfs混淆)。

2.2.2. 镜像仓库

我们可以将Docker镜像仓库理解为Git仓库。Dcoker镜像仓库分为远程和本地,本地的概念好理解,而一般来说远程仓库就是Registry,包括官方的或者自建的私有Registry;我们通过docker pulldocker push命令在本地和远程之间进行镜像传输。

Docker镜像的命名规则和GitHub也很像。比如我们自己创建的仓库名称都是类似liubin/redis这样格式的,前面的liubin是用户名或namespace,后面是仓库名。

不过我们前面已经看到运行的ubuntu镜像的时候是仓库名就是ubuntu,而不带用户名前缀,这是表明它是由官方制作的,或者由官方认可的第三方制作的镜像。我们可以认为官方仓库提供的镜像都是安全的、最新的,所以也可以放心使用。

2.3. Docker容器(Container)

容器是一个基于Docker镜像创建、包含为了运行某一特定程序的所有需要的OS、软件、配置文件和数据,是一个可移植的运行单元。在宿主机来看,它只不过是一个简单的用户进程而已。

容器启动的时候,Docker会在镜像最上层挂载一个read-write的文件系统,即上图中标记为writable的Container层,容器将跑在这个文件系统上。这层可写的文件系统是容器中才有的概念,如果我们对此容器进行commit操作,那么该层文件系统则会被提交为一个新的只读的镜像层,并位于镜像层的最上面的。

我们可以认为Docker镜像是“静”的".exe"文件,只在“硬盘”上;而容器是“动”的,是在“内存中”的,要想启动一个容器,需要先把".exe"装载到内存。

镜像和容器具有如下的转换关系:

  • 镜像 -> docker run -> 容器
  • 容器 -> docker commit -> 镜像

有时候我们经常会将两个名称混用,不过这并不会影响我们的理解。

2.4. Docker Registry

Docker Registry是Docker架构中的分发模块,它用来存储Docker镜像,我们可以将它理解为GitHub。

Docker Hub是一个官方的Docker Registry,也是Docker镜像的默认存储位置。

当然从安全管理的角度上来说,我们可能更愿意在自己公司内部托管一个私有的Docker Registry,这可以通过使用Docker官方提供的Registry注 5软件实现。

注 5 Docker Registry https://github.com/dotcloud/docker-registry

运行私有Registry非常简单,这也是一个典型的Docker风格的应用发布例子。

docker run –p 5000:5000 registry

3. 使用Docker

3.1. 初识容器

3.1.1. 创建并启动容器

这里我们假定各位读者已经在自己的机器上安装好了Docker。Docker主要的命令就是docker了,它的参数很多,关于它的具体使用方法,可以参考官方的文档注 6,这里我们只简单的介绍其中一些常用的用法。

注 6 https://docs.docker.com/reference/commandline/cli/https://docs.docker.com/reference/run/

启动一个容器很简单,我们只需要运行docker run命令就可以了注 6

注 6 为了方便区分,本文中运行命令的时候如果提示符为$,表示实在宿主机(Ubuntu)中,如果是#,则表示是在Docker容器中

$ sudo docker run -i -t ubuntu /bin/bash
Unable to find image 'ubuntu' locally
Pulling repository ubuntu
e54ca5efa2e9: Pulling dependent layers
... 省略 ...
6c37f792ddac: Download complete
... 省略 ...
root@81874a4a6d2e:/#

docker run命令会启动一个容器。参数ubuntu指定了我们需要运行的镜像名称,后面的bash则指定了要运行的命令,注意这个命令是容器中的命令,而不是宿主机中的命令。参数-i用来为容器打开标准输入以和宿主机进行交互,-t则会为容器分配一个终端。

在第一次启动某镜像的时候,如果我们本地还没有这个镜像,则Docker会先从远程仓库(Docker Hub)将容器的镜像下载下来,下载完成之后才会启动容器。

注意Docker里有一个很重要的概念就是容器ID或者镜像ID,比如这个例子里的e54ca5efa2e9。这个ID是一个容器或者镜像的唯一标识,它的长度为64位,不过很多时候都可以简写为12位,这也和Git很像。

3.1.2. 让Docker容器在后台运行

这时候我们可以使用-d参数来通过守护模式启动一个容器,这样容器将会在后台一直运行下去。这非常适合运行服务类程序。如果需要,我们可以再通过docker attach命令连接到运行中的容器。

3.1.3. 常用命令

docker ps

docker ps用来查看正在运行中的容器。

从下面的输出结果我们可以看出该容器状态(STATUS列)为已经停止执行,且没有错误(Exited后面的状态码)。

$ sudo docker ps -a 
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                     PORTS               NAMES
60bab6f881e5        ubuntu:latest       /bin/bash           14 minutes ago      Exited (0) 5 seconds ago                       agitated_hopper     

docker ps命令的常用参数(及组合)如下。

  • -a: 查看所有容器,包括已经停止运行的。
  • -l: 查看刚刚启动的容器。
  • -q: 只显示容器ID
  • -l -q: 则可以返回刚启动的容器ID。

docker stop/start/restart

docker stop用来停止运行中的容器,同时你还可以用docker start来重新启动一个已经停止的容器。

docker restart可以重启一个运行中的容器。这就相当于对一个容器先进行stopstart

3.2. 深入了解Docker镜像

在对Docker容器有一个简单的感性认识之后,我们再来深入了解一下Docker镜像的概念。

Docker镜像实际上就是一个tarball,它是一个能完整运行的OS系统,这非常像OS或VM镜像。它里面有基础OS、各种软件包及类库等。我们启动一个容器,相当于是启动了一个“基础OS”。

3.2.1. 标签(Tag)

我们还可以为镜像打标签,这也和Git非常相似。其实你也可能在前面留意到了,docker images的输出中有一列就是TAG的。我们在执行docker build或者docker commit的时候都可以同时为仓库名称指定一个TAG,格式为user_name/repo_name:tag,如果没有指定这个TAG,则默认为latest

3.2.2. 常见镜像操作

这里我们再介绍一下对镜像常见的一些操作。

查看本地镜像列表

docker images命令用来列出当前系统中的所有本地镜像,即我们已经通过docker run或者docker pull下载下来的镜像,镜像文件保存在本地的/var/lib/docker文件夹下。

下载镜像到本地

只需要运行docker pull命令即可,命令非常简单,问题在于你的网路速度和连通性。

删除镜像

docker rmi用来从本地仓库中删除一个不再需要的镜像,即"rm image"的缩写。

3.3. 构建镜像

我们可以创建自己的Docker镜像,在我们的日常工作中会经常进行镜像构建操作。构建Docker镜像非常简单,而且方法也有几种。

3.3.1. 手工创建

这个方法最简单直接的方法。其流程是启动一个容器,在里面进行一些列安装、配置操作,然后运行docker commit命令来将容器commit为一个新镜像。

$ sudo docker run -t -i ubuntu bash
root@c4be1df52810:/# apt-get update
root@c4be1df52810:/# apt-get -y install redis-server

root@c4be1df52810:/# exit

通过下面的命令得到刚才容器的ID号并进行commit操作。

$ sudo docker ps -q -l
c4be1df52810

$ sudo docker commit -m="manually created image" -a="bin liu <liubin0329@gmail.com>" -run='{"CMD":["/usr/bin/redis-server"], "PortSpecs": ["6379"]}' c4be1df52810 liubin/redis:manually

Warning: '-run' is deprecated, it will be removed soon. See usage.
744ce29b2fcf0ad7ad8b2a89c874db51376c3fdd65d1f4f0c6f233b72f8c3400

注意上面的警告信息,在docker commit命令指定-run选项已经不被推荐了,这里为了说明这个例子而故意使用了这个选项。建议创建镜像还是使用Dockerfile的方式,即能将创建过程代码化、透明化,还能进行版本化。

再次运行docker images命令,就应该能看到我们刚才通过docker commit命令创建的镜像了(镜像ID为744ce29b2fcf,镜像名为liubin/redis:manually)。

3.3.2. 使用Dockerfile文件

使用Dockerfile构建Docker镜像

这是一个官方推荐的方法,即将构建镜像的过程代码化,比如要安装什么软件,拷贝什么文件,进行什么样的配置等都用代码进行描述,然后运行docker build命令来创建镜像文件。官方的自动构建即是基于保存在GitHub等代码托管服务上的Dockerfile进行的。Dockerfile即是具体的用于构建的配置文件名,也是这类文件的类型名称。

使用Dockerfile构建Docker镜像非常简单,我们只需要创建一个名为Dockerfile的文件,并编写相应的安装、配置脚本就可以了。我们还是以上面安装Redis服务为例,看看如何使用Dockerfile构建一个镜像。

首先,创建一个redis文件夹(文件夹名任意,无任何限制),并进入该文件夹,然后创建一个Dockerfile文件。这个文件的文件名是固定的,其内容如下。

FROM        ubuntu
MAINTAINER  bin liu <liubin0329@gmail.com>
RUN         apt-get update
RUN         apt-get -y install redis-server
EXPOSE      6379
ENTRYPOINT  ["/usr/bin/redis-server"]

Dockerfile文件的语法非常简单,每一行都是一条指令,注释则以#开头。每条指令都是“指令名称 参数”的形式,指令名称一般都是大写。比如FROM指令表明了我们的镜像的基础镜像(严格来说叫父镜像,我们的所有操作都将以此镜像为基础),这里是ubuntu,但实际上它可以是存在的任何镜像,比如liubin/rubyRUN指令则用来在构建过程中执行各种命令、脚本,比如这里是apt-get命令,你也可以指定一个很复杂很长的脚本文件路径。AUFS有42层文件系统的限制注 7,这时候我们可以通过在RUN指令中执行多条命令,即cmd1 && cmd2 && cmd3 && ...这种形式就可以可避免该问题了。EXPOSE表示此镜像将对外提供6379端口的服务。ENTRYPOINT则指定了启动该镜像时的默认运行程序。

注 7 https://github.com/dotcloud/docker/issues/1171

具体的Dockerfile语法在官方网站注 8有详细说明,相信花个10分钟就能通读一遍,这里唯一比较容易混淆的就是ENTRYPOINTCMD指令了,关于它们的区别,还是留作每位读者自己的课题去研究一下吧。

注 8 https://docs.docker.com/reference/builder/

Dockerfile准备好了之后,运行docker build命令即可构建镜像了。

$ sudo docker build -t liubin/redis:dockerfile .

这里-t表示为构建好的镜像设置一个仓库名称和Tag(如果省略Tag的话则默认使用latest)。最后的一个.表示Dockerfile文件的所在路径,由于我们是在同一文件夹下运行docker build命令,所以使用了.

由于篇幅所限,这里我们就省略了docker build命令的输出。不过如果你亲自动手执行docker build命令的话,那么从它的输出应该很容易理解,Dockerfile里的每一条指令,都对应着构建过程中的每一步,而且每一步都会生成一个新的类似容器的哈希值一样的镜像层ID。也正是这些层,使得镜像能共享很多信息,并且能进行版本管理、继承和分支关系管理等。这除了能节省大量磁盘空间之外,还能在构建镜像的时候通过使用已经构建过的层(即缓存)来大大加快了镜像构建的速度。比如在我们在使用Dockerfile进行构建镜像时,如果在某一步出错了,那么实际上之前步骤的操作已经被提交了,修改Dockerfile后再次进行构建的话,Docker足够聪明到则会从出错的地方开始重新构建,因为前面的指令执行结构都已经被缓存了。

如果你使用docker history命令来查看该镜像的历史信息,你会发现它的输出和docker build的记录是相匹配的,每一条Dockerfile中的指令都会创建一个镜像层。此命令还能查看每个镜像层所占空间大小,即SIZE列的内容。比如本例中MAINTAINER这样指令,实际上它只是关于镜像的元数据,并不占用额外的磁盘空间,所以它的层大小为0字节。而RUN apt-get -y install redis-server创建的层则会在镜像中增加文件,所以是需要占用磁盘空间的。

自动构建(Automated Builds)

Docker Hub的目的之一就是要成为应用程序交换的中转站,它还支持自动构建功能。自动构建的Dockerfile可以托管在GitHub或者Bitbucket上,当我们将代码提交并push到托管仓库的时候,Docker Hub会自动通过webhook来启动镜像构建任务。

配置自动构建很简单,只需要在Docker Hub中绑定GitHub或者Bitbucket账号就可以了,如何具体操作这里不做详细说明了。

3.3.3. 使用Packer

Packer注 10是一个通过配置文件创建一致机器镜像(identical machine images)的非常方便的工具。Packer同样出自Vagrant的作者Mitchell Hashimoto之手。它支持虚拟机VirtualBox和VMWare等虚拟机软件,以及Amazon EC2、DigitalOcean、GCE以及OpenStack等云平台,最新版的Packer也增加了对Docker的支持。

注 10 http://www.packer.io/

Packer的使用也比较简单,这里我们就举例说明了,读者可以自己试一下。

3.4. 发布镜像

如果你愿意,还可以将在本地制作镜像push到Docker Hub上和其他人分享你的工作成果。

首先你要有一个Docker Hub账号并已经为登录状态,这样才能往Docker Hub上push镜像文件。注册Docker Hub账号只能通过网站注册注 11,这里我们假设各位读者已经拥有Docker Hub了账号。

注 11 https://hub.docker.com/

登录Docker Hub通过docker login命令。

登录成功后,我们就可以push镜像了。注意这里我们没有指定Tag,Docker知道如何去做。

$ sudo docker push liubin/redis

我们前面说过,镜像文件是分层的,很多镜像文件可以共用很多层。比如我们这次往服务器push镜像的时候,实际push的只有一层(744ce29b2fcf)而已,这是因为我们的镜像文件是基于ubuntu这个base镜像创建的,而ubuntu镜像早已经在远程仓库中了。

我们在层744ce29b2fcf中对应的操作是bash命令,并在容器中安装了Redis。而这次修改只有不到6M的容量增加,而如果只是修改配置文件的话,那么一次push操作可能只需要耗费几K的网络带宽而已。

4. DockerCon14总结

首届Docker大会(DockerCon14)于当地时间6月9日~6月10日在旧金山举行。相对于计划中的500个参会名额,最终有超过900人报名,并提交了超过150个演讲申请。

关于这次Docker大会的更多信息可以参考其官方网站:http://www.dockercon.com/。

4.1. Docker官方发布的产品和服务

4.1.1. Docker 1.0的发布及商业支持

在这次大会上最重要的事情莫过于Docker 1.0的发布了。Docker 1.0已经可以在Red Hat、Debian、Ubuntu、Fedora、SuSE等主流Linux系统下运行,在功能、稳定性以及软件质量上都已经达到了企业使用的标准,文档也更加系统、完善。并且提供了Docker Hub云服务,方便开发者和企业进行应用分发。最重要的是Docker, Inc.还宣布了对Docker的商业支持,尤其是对Docker 1.0版本的长期支持。此外,Docker, Inc.还会提供Docker相关的培训、咨询等工作。

4.1.2. Docker Engine + Docker Hub

同时从1.0开始,Docker的架构也发生了较大的变化。Docker已经从单一的软件转变为了一个构建、发布、运行分布式应用的平台。

新的Docker平台由Docker Engine(运行环境 + 打包工具)、Docker Hub(API + 生态系统)两部分组成。

Docker引擎

Docker引擎是一组开源软件,位于Docker平台的核心位置。它提供了容器运行时以及打包、管理等工具。

Docker Hub

Docker Hub是一个云端的分布式应用服务,它专注于内容、协作和工作流。

Docker Hub可以看作是原来Docker index服务的升级版。Docker Hub除了可以托管Docker镜像之外,还提供了包括更管理、团队协作、生命周期流程自动化等功能,以及对第三方工具和服务的集成。

在Docker, Inc.看来,典型的基于Docker Hub的软件开发生命周期为:在本地基于Docker引擎开发 -> 打包应用程序 -> 将应用程序push到Docker Hub -> 从Docker Hub上下载此应用镜像并运行。它将镜像构建的任务交给Dev,将镜像部署的任务交给Ops。

4.1.3. 新组件

Docker Engine也有了一些新的变化,而部分功能实际上早在Docker 0.9就开始提供了。如果你还在运行Docker 0.8及其以前的版本的话,那么还是及早升级的比较好。

libswarm

libswarm是一个"toolkit for composing network services”。它定义了标准接口用于管理和编配一个分布式系统,并提供了一致的API。libswarm打算支持各种编配系统,虽然它看上去更像个高层接口封装的API而已。

libcaontainer

libcontainer是一个容器的参考实现,它通过Go语言实现来使用Linux的命名空间等技术,而不需要额外的外部依赖。

实际上在Docker 0.9的时候这个模块就已经分离出来了,到了1.0的时候,此模块成为了独立项目并且可以单独使用。并且从0.9版本的时候开始Docker就已经开始就采用libcontainer来代替LXC作为默认的容器实现方式了,LXC变成了可选项之一。

libchan

libchan现在是Docker的标准通信层,被称为网络上的go channel,普通的Go channel只能运行在单机上,而libchan可以跨Unix socket或纯TCP/TLS/HTTP2/SPDY/Websocket等运行。使用libchan,可以非常方便的进行任意结构的消息传递、实时双工异步通信、并发编程及同步等。

最后我们再从下面的这张图,更形象的认识一下这三个工具的作用及关系。

libchan,libcontainer,libswarm

4.2. 大公司的热情

如果看一下演讲嘉宾列表注 13,你一定会感叹这阵容太豪华了。不错,很多演讲嘉宾都来自大型互联网公司,比如Facebook、Twitter、Google、Heroku、Yelp以及Group等,很多还都是VP、CTO等高级别的管理人员,可见这次大会规格之高,分量之重。并且他们中的很多人还都进入到了Docker治理委员会。

注 13 http://www.dockercon.com/speakers.html

4.2.1. Google

前面我们已经介绍了Google公司内部的服务都是跑在容器之中的,Google对Docker也表现出了相当浓厚的兴趣。除了他们负责基础设施的VP Eric Brewer进行了主题为《Robust Containers》的演讲之外,他们还介绍了自己开源容器管理软件Kubernetes和对容器资源进行监控的cAdvisor。

4.2.2. Red Hat

Red Hat Enterprise Linux 7版将内置Docker,虽然版本还是0.11,不过很快就会升级的。另外Atomic项目也是Red Hat主导开发的。

4.3. 其它感受

其他一些笔者认为比较有意思的就是使用基于Mesos工具群来对容器进行集群管理了。比如Twitter和Groupon都做了使用Mesos + Aurora/Marathon + ZooKeeper在数据中心进行资源分配和管理的分享;甚至在Twitter看来,数据中心也可以看做是一台计算机,Mesos就是这台计算机的OS。

另外就像我们前面在Docker使用场景中介绍过的那样,很多公司都在使用Docker进行持续集成。

5. Docker现状及展望

在本节我们将会站在一个开放的角度和更高的层次来审视一下Docker的现状,包括其问题点,以及对Docker将来的可能性做一些肤浅的推测。

5.1. 生态系统

Docker的发展离不开其生态系统注 14,我们学习Docker也同样需对其生态系统有所了解。我们可以从下面三点来审视一下Docker当前的发展状况。

注 14 关于Docker的生态环境,大家也可以参考网上有人制作的一份思维导图。http://www.mindmeister.com/389671722/docker-ecosystem

5.1.1. 厂商支持

前面我们已经说过了,包括RedHat等在内的Linux发行商以及Google、AWS、Rackspace等云服务提供商都表示对Docker非常浓厚的兴趣,甚至已经进行了非常深入的实践。从这一点上来说,Docker有非常好的政治背景。

5.1.2. 开源项目

围绕Docker的开源项目就更多了,主要有以下几类,我们将挑选出一些比较有意思且开发较活跃的项目进行简单介绍。

PaaS平台

PaaS平台大多基于容器技术,Docker天生就适合做PaaS。

  • Flynn

Flynn是一个高度模块化的下一代开源PaaS实现。Flynn分为两层,Layer 0是底层,也叫资源层,基于Google的Omega论文注 15开发,这一层也包括服务发现。Layer 1则用来进行部署、管理应用程序。Flynn目前开发比较活跃,是一个值得关注的开源项目,而且今年夏天很可能就会发布1.0的版本了。

注 15 http://eurosys2013.tudos.org/wp-content/uploads/2013/paper/Schwarzkopf.pdf

https://flynn.io/

  • Deis

Deis是一个支持共有和私有PaaS的开源实现。它支持运行使用Ruby, Python, Node.js, Java, PHP和Go等语言进行应用开发,并能部署到AWS, Rackspace和DigitalOcean等云上。

http://deis.io/

CI/CD(持续集成/持续部署)

由于Docker的沙箱性、创建速度快等特性,它与生俱来也适合进行CI/CD。很多基于Docker的CI/CD开源方案和服务如雨后春笋般的涌现出来。

  • Drone

开源的支持各种语言的CI工具,并且提供了CI/CD服务Drone.io

https://drone.io/

  • Strider CD

开源的CI/CD方案,集成GitHub。

http://stridercd.com/

私有仓库托管(Registry)/容器托管

这类服务主要进行私有仓库的托管,根据用户的托管仓库数量收费。Doccker Hub也提供私有仓库的收费套餐。

  • Quay

Quay除了能托管私有镜像之外,还能和GitHub集成,使用Dockerfile进行镜像构建。

https://quay.io/

  • Shippable

Shippable支持Github和Bitbucket,并且提供100%免费的服务,包括私有仓库。

https://www.shippable.com/

  • Orchard

Orchard也是一个和StackDock类似的Docker托管服务,它提供了便捷的命令行工具来运行各种Docker命令。同时它也提供免费的私有Registry服务,前面介绍的Fig工具就是此公司开发的。

https://www.orchardup.com/

笔者认为传统的云计算服务提供商除了在云主机上提供对容器的支持之外,说不定将来还会提供专门托管容器的服务。

开发管理工具

软件工程师天生就是闲不住和想尽一切办法要提高自己效率的一群人。这里我们简单介绍两个方便进行Docker开发的工具。

  • Shipyard

Shipyard是一个Docker镜像和容器管理工具,除了基本的镜像构建,容器启动等功能之外,它还具有在浏览器中attach到容器的功能,并通过hipache16来进行容器之间的连接。同时它也支持跨节点的Docker管理和容器Metrics采集。

注 16 Hipache: a distributed HTTP and websocket proxy https://github.com/dotcloud/hipache

https://github.com/shipyard/shipyard

  • Fig

Fig是一个为了提高基于Docker开发的效率而创建的工具,它通过一个配置文件来管理多个Docker容器,非常适合组合使用多个容器进行开发的场景。

http://orchardup.github.io/fig/index.html

5.1.3. 社区

Docker开发社区非常活跃,除了35名全职员工(外加一只乌龟)之外,还有450名左右的外部代码贡献者。到目前Docker Hub已经拥有超过16000多个应用,在GitHub上也有超过7000个Docker相关的项目,其中不乏很多受关注度非常高的项目。

在Twitter上,科技媒体上以及个人Blog上,每天都能看到很多关于Docker的内容。

线下社区活动也在蓬勃展开中。在世界范围内除了南极洲,Docker Meetup已经遍布35个国家100多个城市,北京在今年3月8日举行了国内第一次的Docker Meetup,当时有超过40人报名参加。而且第二次北京Docker Meetup将在七月中举行,目前正在紧锣密鼓的筹备之中。

5.2. 运用中的问题点

虽然Docker很火,有时候我们也需要反过来看看它还有哪些不令我们满意的地方,或者说在使用上还存有疑虑。当然这里的问题都是笔者个人主观看法,只是非常片面的一部分,各位读者一定要带着批判性的思维去理解它。

5.2.1. Debug、调优

查看日志可能是最简单直接的方式了。当然也有很多人都会在Docker容器中运行一个SSHD服务,然后通过SSH登录到容器中去,不过不建议使用这种方法。

官方推荐使用nsenter注 17工具来完成类似的工作,通过它可以进入到指定的namespace中并控制一个容器。

注 17 https://github.com/jpetazzo/nsenter

5.2.2. 数据管理

这里所说的数据包括数据库文件,Log,用户上传的文件等。

在容器中要想处理数据文件,可能最简单的方式就是通过共享卷标来实现,即docker run -v。但是随之带来的问题是既然是文件,都存在备份问题,如何备份?用ftp或者在容器和宿主机之间共享文件夹的方式?而且随着容器数量的增多,对共享卷标的管理也势必会更复杂。

笔者认为理想的解决方法就是使用云服务,比如数据库使用RDS,文件使用S3。如果不想使用云服务,则可以考虑自己通过FastDFS等实现自己的“云存储”。Log则通过fluentd/logstash进行集计再用Graphite/Kibana等进行可视化。

5.2.3. 如何和配置管理工具配合使用

到底在容器时代,还需不需要传统的Puppet或Chef这样的配置管理工具?当然,从配置管理工具的角度来说,他们都不会放弃对Docker的支持,比如Puppet就已经增加了对Docker(安装、管理镜像和容器)的支持。

但随着不可变基础设施的普及注 18,幂等性将不再重要,因为我们的容器只需要配置一次。要对容器做出修改,可能只需要修改Dockerfile/manifest/recipe文件重新Provisioning即可。而且也不需要在容器内部安装任何agent,这样的话类似Ansible这样纯SSH的配置管理工具比较适合对Docker进行配置。甚至还可能出现专门为Docker的更简单的配置管理工具。

注 18 笔者个人偏见而已

5.2.4. 安全性

是软件就会存在bug,包括安全漏洞,Docker也不例外。就在今年6月份,Docker刚爆出了一个容器逸出的漏洞注 19。不管是Hypervisor技术还是容器技术,安全问题始终都是一个不可避免的话题,虽然它们出问题的几率要比中间件软件(Apache,Nginx、Tomcat)和软件框架(Struts、Rails)等的概率要小很多。

注 19 http://blog.docker.com/category/security-2/

事后Docker, Inc.还是比较积极的面对了这件事,除了及时披露详细情况之外,还着重强调了他们的安全政策。

5.2.5. 有状态和无状态容器

在不可变基础设施(Immutable Infrastructure)里,一切都可以分为有状态(stateful)的和无状态(stateless)的,容器也不例外。容器似乎更适合跑无状态的服务,然而业内对如何分别对待这两种服务还没有太好的最佳实践。

5.3. 对Docker展望

最后再容笔者斗胆对Docker的将来做一些展望。除了Docker本身自己会蓬勃发展之外,围绕Docker的生态圈必将更加成熟和强大。

5.3.1. 集群管理(Orchestration)和服务发现(Service Discovery)

相对于对单台机器进行Provisioning而言,云环境下则需要对多台机器进行Orchestration。Orchestration这个词翻译过来就是编排、编配的意思,我们也可以理解为集群管理。它主要由两部分工作组成:

  • 监控服务器,发现变化(软硬件异常、网络异常、正常变更等)
  • 根据监视事件采取相应的行动。

服务发现

在松耦合的分布式环境下,应用程序不一定跑在同一台机上,甚至是跨越数据中心的。这时候服务发现就显得格外重要了。

  • Zookeeper

Chubby注 20可以称得上是很多服务发现、集群管理软件的鼻祖了,比如Zookeeper注 21,这些软件都提供数据存储、leader选举、元数据存储、分布式锁、事件监听(或watch,监视)等功能。

注 20 http://research.google.com/archive/chubby.html

注 21 http://zookeeper.apache.org/

  • etcd

etcd注 22很新也很轻量,安装很简单,配置也不复杂,所以非常适合入门。etcd存储的是key-value格式的数据。

etcd是CoreOS的一个组件。同时CoreOS提供了一个基于公有云的服务发现服务discovery.etcd.io。

注 22 https://github.com/coreos/etcd

此外,我们还可以有Skydns/Skydock注 23、Discoverd注 24等选择。

注 23 基于DNS的服务发现。https://github.com/crosbymichael/skydock

注 24 Flynn的一个组件,它目前是基于etcd的,但是也可以扩展诸如Zookeeper等分布式存储机制。https://github.com/flynn/discoverd

集群管理

围绕Docker使用场景的开源集群管理软件有很多,比如Geard、Fleet、Consul及Serf等,这些软件都是随着Docker应运而生的;此外还有很多老牌的集群管理软件,比如Mesos等也可以很好的结合Docker使用。

  • Serf和Consul

Serf注 25是一个基于Gossip协议去中心的服务器发现和集群管理工具,它非常轻量,高可用并具备容错机制。

注 25 http://www.serfdom.io/

Consul注 26是一个服务发现和集群配置共享的软件,除了K/V store功能之外,它还支持跨数据中心及容错功能,并能进行服务健康监测。

注 26 http://www.consul.io/

这两个软件都Vagrant作者所在公司HashiCorp注 27发布的产品,这个公司也值得大家关注。

注 27 http://www.hashicorp.com/products

  • Apache Mesos & Marathon & deimos & etc.

Mesos用于对多个节点的资源进行管理,它将多台服务器作为一台“虚拟机”看待,并在这台虚拟机上分配资源,用户通过使用framework进行资源管理。Marathon是一个Mesos的framework,用来启动、管理需要长时间运行的任务。deimos则是一个为Mesos准备的Docker插件。

其它工具

Cloud Foundry在5月份发布的Docker版的BOSH工具,有兴趣的读者可以参考一下Decker注 28项目。

注 28 Decker = Docker + Cloud Foundry. http://www.cloudcredo.com/decker-docker-cloud-foundry/

另外Clocker注 29这个项目也比较有意思,它基于Apache Brooklyn(目前还在孵化器中),能在多云环境下基于Docker容器进行应用部署。这个项目的扩展性很好,非常方便自己定制。不过项目还太年轻,要想使用的话恐怕还需要些时日。

注 29 https://github.com/brooklyncentral/clocker

5.3.2. 和OS的深度结合

在Fedora上使用的systemd注 30就已经提供了集成容器和虚拟机的功能。

注 30 systemd是用来替代Linux中init系统的系统软件,目前已经在Fedora/RHEL等中采用

Docker除了能在各种主流Linux上使用之外,还出现了有专为运行Docker容器而定制的OS了,比如CoreOS注 31,RedHat的Atomic注 32

注 31 https://coreos.com/ ,在6月末刚刚宣布获得了八百万美元的A轮融资
注 32 http://www.projectatomic.io/

CoreOS

CoreOS是一个精简版的Linux,可以运行在既有硬件或者云上,它也是一个最近备受关注的项目。CoreOS不提供类似yum或者apt类似的包管理工具,你不需要在CoreOS中安装软件,而是让程序都在Docker容器中去运行。CoreOS使用systemd和fleet来对容器进行管理,通过etcd进行服务发现和配置信息共享。

Atomic

Project Atomic是最近才发布的一个项目,它也是一个瘦身版的Linux,只包含systemd/geard注 33/rpm-OSTree以及Docker组件,专门用来部署和管理Docker容器。它能在接近硬件裸机级别上高性能的运行大量容器,而且它还是基于SELinux的,在安全上也有保障。

注 33 http://openshift.github.io/geard/

5.3.3. Container技术规范化和兼容性

就在DockerCon14开始的前一天,Flynn发布了Pinkerton,一个支持在其它容器中使用Docker镜像的技术。

而另一方面,我们知道除了LXC,Docker之外,还有很多其它容器技术,比如Zones,jail和LMCTFY等,那么试想这么多的容器之上,是否有统一接口、互相兼容或者在容器上加一层封装的可能性呢?比如让一种容器的镜像,能运行到其它容器中?Docker容器已经能互相连接了,会不会异构的容器之间也能进行某种交互呢?

6. 总结

Docker虽然入门和使用起来非常简单,但整个生态系统还是挺庞大的,而且其底层技术也都很复杂,由于篇幅有限及笔者学识不精,也只能说一些皮毛之事,最多只能算是抛块砖而已;而且笔者也有一种意犹未尽的感觉,但是由于篇幅所限,不能说到面面俱到,更多的内容,还请各位读者自己去深入挖掘。

总之笔者认为Docker还是非常有趣的一个东西,值得大家花些时间体验一下,相信在各位的工作中多多少少都能用的上Docker。

Posted in Docker, Tech

无觅相关文章插件,快速提升流量