Wednesday, September 24, 2014

Docker本地仓库的推荐创建方法

Docker支持采用仓库(本处指的是registry)来支持镜像的分发和更新管理。这极大的便利了用户。
官方提供了dockerhub网站来作为一个公开的集中仓库。然而,本地访问dockerhub速度往往很慢,并且很多时候我们需要一个本地的私有仓库只供网内使用。
关于如何创建和使用本地仓库,其实已经有很多文章介绍了。
但是这些文章要么内容已经过时,要么给出了错误的配置,导致无法正常创建仓库。

首先,需要介绍下原理。
Docker仓库实际上提供两方面的功能,一个是镜像管理,一个是认证。
前者主要由docker-registry项目来实现,通过http服务来上传下载;后者可以通过docker-index项目或者利用现成认证方案实现http请求管理。
今天先不讨论如何实现认证环节。
docker-registry既然也是软件应用,自然最简单的方法就是使用官方提供的已经部署好的镜像registry。
官方文档中也给出了建议,直接运行sudo docker run -p 5000:5000 registry命令。这样确实能启动一个registry服务器,但是所有上传的镜像其实都是由docker容器管理,放在了/var/lib/docker/....某个目录下。而且一旦删除容器,镜像也会被删除。
因此,我们需要想办法告诉docker容器镜像应该存放在哪里。
registry镜像中启动后镜像默认位置是/tmp/registry,因此直接映射这个位置即可,比如到本机的/opt/data/registry目录下。
可以使用命令
sudo dockerrun -d -p 5000:5000 -v /opt/data/registry:/tmp/registry registry
这样就可以了,完全不需要指定配置文件等其它复杂配置。

OpenStack节点地址改变

正常在生产环境中,各个节点会做HA,可以用域名机制来管理节点。
但是有时候如果用了IP地址,就面临着一旦改变了IP需要更新配置的问题。

如果要修改的话,主要是两个方面的信息。
一是配置文件,基本都在/etc/目录下。
可以先grep地址下看看是不是都可以直接替换,如果可以的话,执行
$ sed -i "s/old_ip/new_ip/g" `grep old_ip -rl /etc`即可。

另外一个是数据库,这个要略微复杂些。
一般openstack的数据库中有多个database,主要需要修改的在keystone database的endpoint表中。登录到sql后,命令如下
mysql>use keystone;
mysql> update endpoint set
    -> url=replace(url,'1.1.1.1','2.2.2.2');
其它的如token表中内容或其它database中内容,可以根据自己情况来看是否需要修改。

最后是重启所有的相关服务,或者干脆重启机器。

Docker的安全性

现在讨论Docker安全性的话题颇多,主要集中在对使用容器方式的隔离性、攻击防护性等方面。
往往与虚拟机方式进行比较。
首先,从安全性上看,Docker容器的安全性比不上虚拟机,这点是毋庸置疑的。
容器内的应用可以直接访问到主机系统内核;而虚拟机中的应用首先要访问到虚拟机的操作系统,然后在经过hypervisor层才能访问到外部的系统。
而且,虚拟机方式在生产环境中经过诸多检验,更加成熟一些。

然而,从我的角度看,容器方式确实牺牲掉了部分的安全性,但却换来了高效性和灵活性。
且不说它的快速启动和关闭,以及对系统资源极少的需求。
光从内核机制上看,容器的安全性实际上完全依赖于内核。因此,一旦当内核发生问题时候,无论容器还是虚拟机都会出问题。
这个时候容器的优势就体现出来了,因为容器完全可以在正常运行中的同时,本地主机就可以给内核打上补丁或更换新内核。

另外,Linux的内核在安全性方面已经是十分成熟。下一步随着不必给容器分配root权限这方面的改进,Docker的安全问题将得到极大的缓解。

Docker命令速查

基本语法

docker [OPTIONS] COMMAND [arg...]

选项

-D=true|false
    使用debug模式。默认为false。

-H, --host=[unix:///var/run/docker.sock]: tcp://[host:port]来绑定或者unix://[/path/to/socket]来使用。
    在daemon模式下绑定的socket,通过一个或多个tcp://host:port, unix:///path/to/socket, fd://* or fd://socketfd来指定。

--api-enable-cors=true|false
    在远端API中启用CORS头。缺省为false。

-b=""
    将容器挂载到一个已存在的网桥上。指定为'none'时则禁用容器的网络。

--bip=""
    让动态创建的docker0采用给定的CIDR地址; 与-b选项互斥。

-d=true|false
    使用daemon模式。缺省为false。

--dns=""
    让Docker使用给定的DNS服务器。

-g=""
    指定Docker运行时的root路径。缺省为/var/lib/docker。

--icc=true|false
    启用容器间通信。默认为true。

--ip=""
    绑定端口时候的默认IP地址。缺省为0.0.0.0。

--iptables=true|false
    禁止Docker添加iptables规则。缺省为true。

--mtu=VALUE
    指定容器网络的mtu。缺省为1500。

-p=""
    指定daemon的PID文件路径。缺省为/var/run/docker.pid。

-s=""
    强制Docker运行时使用给定的存储驱动。

-v=true|false
    输出版本信息并退出。缺省值为false。

--selinux-enabled=true|false
    启用SELinux支持。缺省值为false。SELinux目前不支持BTRFS存储驱动。

命令

docker的命令可以采用docker-CMD或者docker CMD的方式执行。两者一致。
docker-attach(1)
    依附到一个正在运行的容器中。

docker-build(1)
    从一个Dockerfile创建一个image

docker-commit(1)
    从一个容器的修改中创建一个新的image

docker-cp(1)
    从容器中复制文件到宿主系统中

docker-diff(1)
    检查一个容器文件系统的修改

docker-events(1)
    从服务端获取实时的事件

docker-export(1)
    导出容器内容为一个tar包

docker-history(1)
    显示一个image的历史

docker-images(1)
    列出存在的image

docker-import(1)
    导入一个tar包来创建一个image

docker-info(1)
    显示一些相关的系统信息

docker-inspect(1)
    显示一个容器的底层具体信息。

docker-kill(1)
    关闭一个运行中的容器 (包括进程和所有资源)

docker-load(1)
    从一个tar包中加载一个image

docker-login(1)
    注册或登录到一个Docker的仓库服务器

docker-logout(1)
    从Docker的仓库服务器登出

docker-logs(1)
    获取容器的log信息

docker-pause(1)
    暂停一个容器中的所有进程

docker-port(1)
    查找一个nat到一个私有网口的公共口

docker-ps(1)
    列出容器

docker-pull(1)
    从一个Docker的注册服务器下拉一个image或仓库

docker-push(1)
    将一个image或者仓库推送到一个Docker的注册服务器

docker-restart(1)
    重启一个运行中的容器

docker-rm(1)
    删除给定的若干个容器

docker-rmi(1)
    删除给定的若干个image

docker-run(1)
    创建一个新容器,并在其中运行给定命令

docker-save(1)
    保存一个image为tar包文件

docker-search(1)
    在Docker index中搜索一个image

docker-start(1)
    启动一个容器

docker-stop(1)
    终止一个运行中的容器

docker-tag(1)
    为一个image打标签

docker-top(1)
    查看一个容器中的正在运行的进程信息

docker-unpause(1)
    将一个容器内所有的进程从暂停状态中恢复

docker-version(1)
    输出Docker的版本信息

docker-wait(1)
    阻塞直到一个容器终止,然后输出它的退出符

网卡虚拟化技术:VMDq和SR-IOV

本文来谈谈虚机跟服务器物理网卡的交互问题。
通常情况下,一个服务器上跑几十个虚机,对计算和网络的需求是很惊人的。前者促生了当下的多核技术发展,后者则不能简单的用多网卡来实现。
试想,每个虚机如果都需要10G的交换能力,服务器要配置几十块物理网卡,且不说主板是否支持这么多的接口,光成本上就难以接受。
另外,如果给vm分配的接口都是软件交换机的虚拟接口,维护这些接口和转发本身就要消耗大量的服务器计算资源。
因此,业界推出了VMDq和SR-IOV技术来提升虚机的网络性能。


VMDq
VMM在服务器的物理网卡中为每个虚机分配一个独立的队列,这样虚机出来的流量可以直接经过软件交换机发送到指定队列上,软件交换机无需进行排序和路由操作。
但是,VMM和虚拟交换机仍然需要将网络流量在VMDq和虚机之间进行复制。


SR-IOV
对于SR-IOV来说,则更加彻底,它通过创建不同虚拟功能(VF)的方式,呈现给虚拟机的就是独立的网卡,因此,虚拟机直接跟网卡通信,不需要经过软件交换机。
VF和VM之间通过DMA进行高速数据传输。
SR-IOV的性能是最好的,但是需要一系列的支持,包括网卡、主板、VMM等。




[1] http://windowsitpro.com/virtualization/q-are-vmdq-and-sr-iov-performing-same-function

虚拟机网络接入技术

随着云计算技术的发展,跟传统的服务器上直接跑应用相比,服务器上运行虚机,虚机里面跑应用的场景已经越来越普遍。
这种情况下,网络往往成为最关键的性能瓶颈,特别是不同虚机之间的高速网络流量如何进行有效的交换。


要解决大流量交换的问题,基本上有两套简单思路,一个是软交换,即放在服务器里面,让软件来实现,典型的代表有各种虚拟交换的解决方案,例如OpenvSwitch。
另外一个是流量对服务器透明,全部扔到服务器外面的物理交换机做,主要是现在讨论的IEEE Edge Virtual Bridging (EVB)标准,包括802.1qbg and 802.1qbh。


软交换比较容易实现。
VMM为每个虚机创建一个虚拟网卡,对应到软件交换机的一个端口。服务器的物理网卡也接到软件交换机的一个接口上。所有的交换由软件交换机完成。
这种方案的优点是虚机之间转发性能非常好,并且在软件交换机容易实现各种控制功能。但问题就是对服务器os的cpu资源消耗多,并且对外交换时容易出现性能瓶颈。


EVB标准则试图将虚机发出的所有流量都导入到外面的交换机上进行决策。优势是可以利用外面硬件交换机的性能和复杂控制特性,缺点是一进一出带宽效率低,需要改动服务器上的驱动支持。


参考:
[1] http://www.cisco.com/c/en/us/products/collateral/switches/nexus-1000v-switch-vmware-vsphere/whitepaper_c11-620065.html
[2] http://wikibon.org/wiki/v/Edge_Virtual_Bridging

OpenStack Heat template中类型定义的一个坑

最新的Heat template目前支持string | number | json | comma_delimited_list | boolean等类型。


采用默认的hot格式,yaml文件格式。


定义一个string类型的属性,内容为true或false的时候,会报错。


查看heat engine的log会发现这个属性值默认被转为了boolean类型。


这是为何呢?
查看heat的代码,heat是调用的yaml库来直接load文件的,而对于yaml语言来说,如下的字符串都会被解析为bool类型。


y, Y, yes, Yes, YES
n, N, no, No, NO
true, True, TRUE
false, False, FALSE
on, On, ON
off, Off, OFF

OpenStack Heat中添加新资源示例

在OpenStack Heat中,资源都是通过继承resource类来实现的。
heat-engine在启动的时候会扫描预设目录来加载各种资源插件。
这种模式提供了极大的灵活性,用户可以很容易的添加自己的资源类型和指定响应行动。
下面给出了定义新资源的一个简单示例,资源被创建后在log中给出属性信息。

#This file should be put into the plugin_dirs of OpenStack HEAT.
#plugin_dirs=/usr/lib64/heat,/usr/lib/heat
#After that, restart the heat engine to enable it.
# service openstack-heat-engine restart

#Provide the OS::Neutron::ServicePolicy resource.

from heat.engine import attributes
from heat.engine import properties
from heat.engine import resource
from heat.openstack.common import log as logging


LOG = logging.getLogger(__name__)


classServicePolicy(resource.Resource):


    PROPERTIES = (
        NAME, SRC, DST,
    ) = (
        'name', 'src', 'dst',
    )


    ATTRIBUTES = (
        NAME,
    )


    properties_schema = {
        NAME: properties.Schema(
            properties.Schema.STRING,
            _('Name of the service policy.')
        ),
        SRC: properties.Schema(
            properties.Schema.STRING,
            _('Source of the service policy.')
        ),
        DST: properties.Schema(
            properties.Schema.STRING,
            _('Destination of the service policy.')
        ),
    }
    attributes_schema = {
        NAME: attributes.Schema(
            _('Name of the service policy.')
        ),
    }


    def handle_create(self):
        LOG.info(self.properties.get(self.NAME))
        LOG.info(self.properties.get(self.SRC))
        LOG.info(self.properties.get(self.DST))


    def handle_delete(self):
        pass


    def _resolve_attribute(self, name):
        if name == self.NAME:
            return self.properties.get(self.NAME)


def resource_mapping():
    return {
        'OS::Neutron::ServicePolicy':ServicePolicy,
    }