在管理一个Openstack集群时,如何维护配置文件无疑是其中最艰难和繁琐的任务之一。因为你不仅要面对众多的核心服务(nova,keystone,glance,cinder,etc)的配置文件,还需要管理其相关服务的配置文件(mysql,rabbitmq,bind9,etc)。此外,Openstack基于组件式的设计架构,将某些功能或是后端驱动抽象为一个个单独的plugin或是pipeline中的一个filter,用户可以根据自己的需求来选择适合自己的架构和技术栈,只需要通过修改配置文件就可以完成架构的变化。

随手举一些例子:

  • 选择使用nova-network还是neutron来构建虚拟网络?
  • glance的后端使用本地存储还是swift或是s3?
  • cinder的后端使用ceph还是sheepdog?
  • 选择template还是sql作为Keystone catalog的driver?
  • 选择kvm还是xen作为nova-compute的虚拟化后端?
  • 如果要使用nova的live migration功能需要做哪些配置?
  • 如果要使用nova的cold migration功能需要做哪些配置?
  • 如何把原先neutron all-in-one角色拆分为Api和network角色?
  • 修改了nova.conf中的quota参数,应该通知哪些服务重启?
  • Swift proxy-server要加载哪些filter到pipeline中?
  • Desingate的后端是选择bind还是powerdns?
  • 如何使Keystone向MQ发送消息使得ceilometer可以监听?
  • .......

从上面罗列的一些简单提问中,我们可以发现,架构和后端驱动的变更其实都是在和配置文件打交道。那么摆在眼前的一个迫切需求就是如何将Openstack各服务的配置文件有效灵活地管理起来:

  1. 仅nova服务就多达近千个配置选项,那么各个服务大量的配置选项如何进行管理?
  2. 如何方便地添加自定义选项?
  3. 当我添加一个新功能时,需要修改多个服务的配置文件,如何关联?
  4. 如何确保配置文件修改后,相应服务被恰当地重启?
  5. ...

Openstack社区中的puppet-openstack项目致力于Openstack的自动化部署,并被广泛地用于业界,如Redhat的Packstack, Mriantis的FuelWeb, UnitedStack的UOS, Cisco的内部云等诸多项目均用到了puppet-openstack的核心模块。从2012年项目伊始,我就参与到其中的开发,在经历了两年多的频繁迭代,最近我们在配置文件的管理上有了不错的进展,最新增加的特性使得配置文件的管理变得更为灵活。

OK,下面开始介绍我们在Openstack配置文件管理所经历的变更以及用到的技术。

模板(template)统治一切

在最早期的时候,我们使用了简单有效的工具:模板(template)。例如,我们希望管理nova.conf中的libvirt_type选项,那么在nova模块的templates文件夹下新建一个nova.conf.erb文件,使用ERB语法在里面添加以下内容:

[default]
libvirt_type = <%= @libvirt_type %>

然后在nova模块的init.pp文件中使用file resource来管理nova.conf。它看起来是这样的:

class nova(
$libvirt_type = 'kvm',
){
.....
file { "nova.conf":
path => "/etc/nova/nova.conf",
owner => 'root',
group => 'root',
mode => '',
content => template('nova/nova.conf.erb'),
} }

这种方法在需要配置选项较少的情况下,还是不错的。但对于处在快速迭代开发中的openstack各核心项目来说,几乎每天都会有选项的新增和删改的变动,那么我们总不能每天都紧盯着Openstack社区的与配置文件变更相关的patch,如果有变更然后就提交一个对应变更的模板patch吧。因此,这种方法带来最麻烦的问题是:不灵活。

拼接(concat)取而代之

部署过openstack的同学会发现,openstack的配置文件是标准的INI格式,每个配置文件由多个section组成。

例如,在swift的proxy-server.conf中有[default],[pipeline:main],[app:proxy-server]等等。其中,[pipeline:main]中的pipeline选项,当这个filter出现在这个pipeline中,下面才会有对应这个filter的section。举个例子:

pipeline = catch_errors gatekeeper healthcheck proxy-logging cache container_sync  proxy-logging proxy-server

当需要在pipeline选项中添加bulk时,同时也要在配置文件中添加[filter:bulk]一节。当然,我们可以在proxy-server.conf.erb中,使用if逻辑把这些section都添加上,但这种做法不利于根据代码逻辑进行类的分离,例如,我从来不用bulk这个filter,那么它为什么要出现在proxy-server.conf.erb模板中呢?

[filter:bulk]
use = egg:swift#bulk
# max_containers_per_extraction = 10000
# max_failed_extractions = 1000
# max_deletes_per_request = 10000
# max_failed_deletes = 1000 # In order to keep a connection active during a potentially long bulk request,
# Swift may return whitespace prepended to the actual response body. This
# whitespace will be yielded no more than every yield_frequency seconds.
# yield_frequency = 10 # Note: The following parameter is used during a bulk delete of objects and
# their container. This would frequently fail because it is very likely
# that all replicated objects have not been deleted by the time the middleware got a
# successful response. It can be configured the number of retries. And the
# number of seconds to wait between each retry will be 1.5**retry # delete_container_retry_count = 0 # Note: Put after auth in the pipeline.

为何不把这些section拆成一个个的配置文件片段,我想使用哪些filter,只需要在pipeline中指定,自动地帮我把这些配置文件片段拼接出来?

concat模块(https://github.com/puppetlabs/puppetlabs-concat.git)正是为此而来。继续以swift-proxy为例,我们希望在pipeline中使用healthcheck,cache,tempauth,proxy-server作为默认值,那么只需要在class swift::proxy中定义:

class swift::proxy(
$proxy_local_net_ip,
$port = '',
$pipeline = ['healthcheck', 'cache', 'tempauth', 'proxy-server'],
.....
}

然后在templates下分别定义它们的erb模板:

healthcheck.conf.erb 对应 swift::proxy::healthcheck

[filter:healthcheck] use = egg:swift#healthcheck

cache.conf.erb 对应  swift::proxy::cache

[filter:cache]
use = egg:swift#memcache
memcache_servers = <%= [@memcache_servers].flatten.join(',') %>

tempauth.conf.erb 对应  swift::proxy::tempauth

[filter:tempauth]
use = egg:swift#tempauth
user_admin_admin = admin .admin .reseller_admin
user_test_tester = testing .admin
user_test2_tester2 = testing2 .admin
user_test_tester3 = testing3

最后是proxy-server.conf.erb 对应 swift::proxy

# This file is managed by puppet.  Do not edit
#
[DEFAULT]
bind_port = <%= @port %>
<% if @proxy_local_net_ip %>
bind_ip = <%= @proxy_local_net_ip %>
<% end %>
workers = <%= @workers %>
user = swift
log_name = swift
log_facility = <%= @log_facility %>
log_level = <%= @log_level %>
log_headers = <%= @log_headers %>
log_address = <%= @log_address %>
<% if @log_udp_host != '' -%>
# If set, log_udp_host will override log_address
log_udp_host = <%= @log_udp_host -%>
<% end %>
<% if @log_udp_host !='' and @log_udp_port != '' -%>
log_udp_port = <%= @log_udp_port -%>
<% end %> [pipeline:main]
pipeline = <%= @pipeline.to_a.join(' ') %> [app:proxy-server]
use = egg:swift#proxy
set log_name = proxy-server
set log_facility = <%= @log_facility %>
set log_level = <%= @log_level %>
set log_address = <%= @log_address %>
log_handoffs = <%= @log_handoffs %>
allow_account_management = <%= @allow_account_management %>
account_autocreate = <%= @account_autocreate %>

那么如何把这些模板拼接成一个完整的proxy-server.conf文件呢?

首先在swift::server中声明一个concat resource,指定要拼接的目的文件的读写权限和所有者属性:

 concat { '/etc/swift/proxy-server.conf':
owner => 'swift',
group => 'swift',
mode => '',
require => Package['swift-proxy'],
}

接着使用concat::fragment define把这些template拼接起来,例如,proxy-server:

  concat::fragment { 'swift_proxy':
target => '/etc/swift/proxy-server.conf',
content => template('swift/proxy-server.conf.erb'),
order => '',
}

其实和使用file resource来渲染template类似,唯一的区别就是order参数,指明这片(fragment)配置在目标配置文件中的顺序,升序排列。

自定义资源类型(custom resource type)的出现

前面所提的两种方法的核心都是使用模板来管理配置文件,唯一的区别是concat的方法将一个配置文件进行了拆分,使之变成可组合的。使用模板的一个主要缺点是每次添加一个新变量,需要在模板文件里添加上这个新变量:

<%= @new_variable %>

然后在类文件中添加这个变量:

  $new_vairable = 'hello',

对于一个成熟的项目来说,并不频繁的配置变更,使得使用模板成为理所当然的方法。例如,memcached,mysql的配置文件。但是对于快速迭代,频繁变更的Openstack项目来说,模板将成为一个梦魇。我们的PTL曾经做了一个统计,在G版前的过半提交都是和配置选项有关。那么有没有一种方法,使得终端用户在添加一个新配置选项时,无需在模板中预先定义就可以使用?

我们在邮件列表中针对这个问题做了大量的讨论,最后选择使用自定义的资源类型来解决这个问题。以nova为例,我们使用nova_config来管理nova.conf中所有的配置选项。

例如,我们想管理nova.conf中[default]下的libvirt_type选项。那么只要在类中添加:

 $libvirt_type = 'kvm',
 nova_config { 'default/libvirt_type':  value => $libvirt_type;}

而我无需再去模板中定义这个变量。

使用这种方式的另一大好处是,可以做到细粒度地控制与之相关服务的重载。

例如:

当nova.conf中的connection选项发生变更时,那么需要执行一次db_sync的操作;

当vncproxy_host参数发生变更时,需要通知nova-novncproxy服务去重新加载配置文件;

当quota相关参数发生变更时,需要通知nova-scheduler服务区重新加载配置文件;

......

至此,在puppet openstack核心模块中,模板被完全弃用。

管理自定义参数的*::config类

社区的puppet openstack modules针对的是终端用户的通用需求,因此并不能完全地满足用户的需求。所谓不能满足用户的需求,大致可以划分为两类:

1. 某些plugin或者driver的配置选项缺失,例如cinder的solidfire,neutron的ibm plugin等等,使用的人少,社区的精力有限,暂时还没有完成这些功能的配置,那么开发者会把这些功能放入到作为某个模块中的一个类,然后再推到社区来,但这个过程可能需要数天或者几个礼拜的时间,期间需要等待core devs的aprove;

2. 自定义参数  例如,我司针对neutron和nova做了大量的定制化修改,添加了一些自定义选项。因此,upstream modules就不能满足我的需求,那么我就不得不使用自定义的资源类型去修改源代码,这我来说轻车熟路,但是许多终端用户并不懂puppet,当然最终驱动我去开发这个功能的源头是:懒。

我司的neutron core dev @gongysh 是个活力十足的家伙,每天都会在协作平台上给我分上几个非常boring的task:

a. 在开发环境中,在xxx配置文件中添加xxx参数

b. 把xxx参数的值修改为yyy

c. 删除xxx参数

在被折腾了两个多月后,我终于厌倦这样烦躁的重复劳动了,开始思考如何解决这样的问题。4月初,我留意到iweb的Mathieu(core member)向puppet-nova提交了一个patch,添加了一个新类:nova::config,旨在解决管理那些暂时还没有被upstream module收录的参数,例如nova.conf中rabbit_retry_interval参数还没被puppet-nova管理,但我想使用,又不想去修改源码,使用nova::config就可以处理。

代码很简洁,只有十几行,核心在于create_resouces函数所做的迭代,类似于编程语言中的for循环,Puppet 3.5之前还不支持这种语法的迭代(虽然有trick可以做到),因此一般都会使用create_resouces,create_resources一般接受两个参数,第一个是resource name,第二个是带有多组键值的字典。

class nova::config (
$nova_config = {},
$nova_paste_api_ini = {},
) { validate_hash($nova_config)
validate_hash($nova_paste_api_ini) create_resources('nova_config', $nova_config)
create_resources('nova_paste_api_ini', $nova_paste_api_ini)
}

我在做code review的时候,发现这家伙的use case是错的,我在修复之后,在hieradata里可以按照以下格式添加参数。

 nova_config:
DEFAULT/foo:
value: fooValue
DEFAULT/bar:
value: barValue

某天下午,我在往puppet-neutron里添加自定义参数的时候,突然想到使用这种方法就可以不用预先在class中这个参数,而直接在hierdata中为这个参数赋值!

于是我在社区的邮件列表发起了一个关于更好地管理自定义参数方法的讨论,得到了社区的肯定,并且Cisco的Michael Chapman(core member)将这个议题作为一个“good topic”收录进Atlanta Design Summit上的Puppet section,参见:https://etherpad.openstack.org/p/ATL-ops-unconference-RFC

目前puppet-neutron,puppet-cinder,puppet-glance,puppet-keystone均已支持使用这种方式来管理自定义参数,其他项目还在Code review中。

相关patch的链接参见:

https://review.openstack.org/#/c/79506

https://review.openstack.org/#/c/84987/

https://review.openstack.org/#/c/82699/

https://review.openstack.org/#/c/84976/

https://review.openstack.org/#/c/84981/

https://review.openstack.org/#/c/84999/

鱼与熊掌不能兼得,未来路在何方

从前面的例子中,目前结合使用最新的openstack自定义资源类型和*::config类可以很好地驾驭Openstack这头巨兽,灵活地处理各类参数的配置。但这种方法与模板相比,也有它的缺点:无法做到强收敛性,这些自定义资源和类无法管理那些没有显式调用的选项。

再来举个简单的例子,当系统安装完keystone包后,keystone-paste.ini会被放置到/etc/keystone目录下,我不会去管理keystone-paste.ini中[fileter:debug]下的paste.filter_facotry参数,但也希望默认参数不会被篡改,或者被删除:

[filter:debug]
paste.filter_factory = keystone.common.wsgi:Debug.factory

如果是使用模板,只需要把这段直接粘贴进去即可,但若是使用keystone_config的话,那么就必须在类中显式地指明,这显然是非常低效的,那么有没有更好的方法呢?

暂时没有,在经过了多次的讨论后,社区决定在没有更好的解决办法前,不主动去维护那些不被管理的参数,毕竟在一个严格的生产环境中,被人为篡改的可能不高。

亲,如果你有更好的方案,欢迎向我们提patch ! :)

Openstack配置文件管理的变迁之路的更多相关文章

  1. Linux安装LAMP开发环境及配置文件管理

    Linux主要分为两大系发行版,分别是RedHat和Debian,lamp环境的安装和配置也会有所不同,所以分别以CentOS 7.1和Ubuntu 14.04做为主机(L) Linux下安装软件,最 ...

  2. Python-day3作业-haproxy配置文件管理脚本

    #!/usr/bin/env python import os,sys,time,re,prettytable,json from collections import defaultdict,Ord ...

  3. 【C#进阶系列】03 配置文件管理与程序集的引用版本重定向

    先来点与标题不相关的: CLR支持两种程序集:弱命名程序集和强命名程序集. 两者的区别在于强命名程序集使用发布者的公钥和私钥进行签名.由于程序集被唯一性地标识,所以当应用程序绑定到强命名程序集时,CL ...

  4. C#配置文件管理

    最近在做项目的过程中用到配置文件,本文简要说明本人在项目过程中是如何使用配置文件的,目的是加深自己对配置文件管理的理解,以便在下次使用时能做到轻松自如. 配置文件,顾名思义,是用户在使用系统或者软件时 ...

  5. OpenStack配置串口显示虚机界面

    OpenStack配置串口显示虚机界面 OpenStack的horizon能够显示虚拟机的界面.horizon是web界面,在我们的电脑上,姑且称之为本地,虚拟机运行在远端服务器上,称之为远端.本地显 ...

  6. openstack配置域名访问

    #openstack配置域名访问 openstack pike 安装 目录汇总 http://www.cnblogs.com/elvi/p/7613861.html #主要是在默认配置的基础上,做了个 ...

  7. 死磕salt系列-salt 配置文件管理

    SLS是Salt State系统的核心,用来描述系统的目标状态,使用YAML语言书写.被用作配置文件管理. SLS文件 sls配置文件分为两种类型 top.sls 这是所有配置文件的入口 sls 这是 ...

  8. disconf实践(四)基于注解的分布式配置文件管理,自动reload

    上一篇讲解了基于xml的自动reload的分布式配置文件管理,这一篇讲解基于注解的自动reload的方式(基于disconf实践二). 1. 修改spring配置文件 <?xml version ...

  9. disconf实践(三)基于XML的分布式配置文件管理,自动reload

    上一篇介绍了基于xml的非自动reload的分布式配置文件管理,这一篇介绍自动reload的方式(基于disconf实践二). 1. 修改RedisConfig.java package org.sp ...

随机推荐

  1. SpringMVC MongoDB之“基本文档查询(Query、BasicQuery)”

    一.简介 spring Data  MongoDB提供了org.springframework.data.mongodb.core.MongoTemplate对MongoDB的CRUD的操作,上一篇我 ...

  2. Bootstrap表格中,thead固定,tbody有垂直滚动条

    1.html源码:这里的table是使用的vue写法,实际生成的表格和一个一个写的tr th td无异 <div class="panel-body no-padding"& ...

  3. js读取xml文件

    假设我们现在要读取下面的 info.xml 文件 <?xml version="1.0" encoding="gb2312"?> <root& ...

  4. hive遇到FAILED: Execution Error, return code 2 from org.apache.hadoop.hive.ql.exec.mr.MapRedTask错误

    hive遇到FAILED: Execution Error, return code 2 from org.apache.hadoop.hive.ql.exec.mr.MapRedTask错误 起因 ...

  5. (第6篇)大数据发展背后的强力推手——HBase分布式存储系统

    摘要: 今天我们介绍可在廉价PC Server上搭建起大规模结构化存储集群的分布式存储系统——HBase. 博主福利 给大家赠送一套hadoop视频课程 授课老师是百度 hadoop 核心架构师 内容 ...

  6. Mistwald POJ

    一开始看不出来是快速幂矩阵的题目 先要把整个地图离散化为1,2,3,4,.... 连成一个有向图 邻接矩阵的平方意为:假如a->b  且b->c     那么一次平方后   a->c ...

  7. P、NP、NPC、NPH问题的区别和联系

    时间复杂度 时间复杂度描述了当输入规模变大时,程序运行时间的变化程度,通常使用\(O\)来表示.比如单层循环的时间复杂度为\(O(n)\),也就是说程序运行的时间随着输入规模的增大线性增长,两层循环的 ...

  8. C++ 对Ctrl+Z的解释

    只有当Ctrl+Z单独位于一行的行首时,才表示输入的终止!(即无论何时,都推荐先回车,再Ctrl+Z,再回车结束输入) 当Ctrl+Z位于行中.行末时,输入都不会结束. (Ctrl+Z表示一个字符,其 ...

  9. 如何在Maven官网下载历史版本

    如何在Maven官网下载历史版本 历史版本一般会隔一段时间,便找不到,官网会及时显示的是最新版本.不多说,直接进入. https://archive.apache.org/dist/maven/bin ...

  10. Bootstrap 警告、进度条、列表组、面板

    摘要:该部分包括警告.进度条.列表组.面板等部分. 1.警告(alert) 1.1 基本的警告(.alert) 警告的基类是 .alert .和其他样式类一块使用.例如: .alert-success ...