在管理一个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语法在里面添加以下内容:

  1. [default]
  2. libvirt_type = <%= @libvirt_type %>

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

  1. class nova(
  2. $libvirt_type = 'kvm',
  3. ){
  4. .....
  5. file { "nova.conf":
  6. path => "/etc/nova/nova.conf",
  7. owner => 'root',
  8. group => 'root',
  9. mode => '',
  10. content => template('nova/nova.conf.erb'),
  11. }
  12.  
  13. }

这种方法在需要配置选项较少的情况下,还是不错的。但对于处在快速迭代开发中的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模板中呢?

  1. [filter:bulk]
  2. use = egg:swift#bulk
  3. # max_containers_per_extraction = 10000
  4. # max_failed_extractions = 1000
  5. # max_deletes_per_request = 10000
  6. # max_failed_deletes = 1000
  7.  
  8. # In order to keep a connection active during a potentially long bulk request,
  9. # Swift may return whitespace prepended to the actual response body. This
  10. # whitespace will be yielded no more than every yield_frequency seconds.
  11. # yield_frequency = 10
  12.  
  13. # Note: The following parameter is used during a bulk delete of objects and
  14. # their container. This would frequently fail because it is very likely
  15. # that all replicated objects have not been deleted by the time the middleware got a
  16. # successful response. It can be configured the number of retries. And the
  17. # number of seconds to wait between each retry will be 1.5**retry
  18.  
  19. # delete_container_retry_count = 0
  20.  
  21. # 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中定义:

  1. class swift::proxy(
  2. $proxy_local_net_ip,
  3. $port = '',
  4. $pipeline = ['healthcheck', 'cache', 'tempauth', 'proxy-server'],
  5. .....
  6. }

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

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

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

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

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

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

  1. [filter:tempauth]
  2. use = egg:swift#tempauth
  3. user_admin_admin = admin .admin .reseller_admin
  4. user_test_tester = testing .admin
  5. user_test2_tester2 = testing2 .admin
  6. user_test_tester3 = testing3

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

  1. # This file is managed by puppet. Do not edit
  2. #
  3. [DEFAULT]
  4. bind_port = <%= @port %>
  5. <% if @proxy_local_net_ip %>
  6. bind_ip = <%= @proxy_local_net_ip %>
  7. <% end %>
  8. workers = <%= @workers %>
  9. user = swift
  10. log_name = swift
  11. log_facility = <%= @log_facility %>
  12. log_level = <%= @log_level %>
  13. log_headers = <%= @log_headers %>
  14. log_address = <%= @log_address %>
  15. <% if @log_udp_host != '' -%>
  16. # If set, log_udp_host will override log_address
  17. log_udp_host = <%= @log_udp_host -%>
  18. <% end %>
  19. <% if @log_udp_host !='' and @log_udp_port != '' -%>
  20. log_udp_port = <%= @log_udp_port -%>
  21. <% end %>
  22.  
  23. [pipeline:main]
  24. pipeline = <%= @pipeline.to_a.join(' ') %>
  25.  
  26. [app:proxy-server]
  27. use = egg:swift#proxy
  28. set log_name = proxy-server
  29. set log_facility = <%= @log_facility %>
  30. set log_level = <%= @log_level %>
  31. set log_address = <%= @log_address %>
  32. log_handoffs = <%= @log_handoffs %>
  33. allow_account_management = <%= @allow_account_management %>
  34. account_autocreate = <%= @account_autocreate %>

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

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

  1. concat { '/etc/swift/proxy-server.conf':
  2. owner => 'swift',
  3. group => 'swift',
  4. mode => '',
  5. require => Package['swift-proxy'],
  6. }

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

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

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

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

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

  1. <%= @new_variable %>

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

  1. $new_vairable = 'hello',

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

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

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

  1. $libvirt_type = 'kvm',
  1. 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,第二个是带有多组键值的字典。

  1. class nova::config (
  2. $nova_config = {},
  3. $nova_paste_api_ini = {},
  4. ) {
  5.  
  6. validate_hash($nova_config)
  7. validate_hash($nova_paste_api_ini)
  8.  
  9. create_resources('nova_config', $nova_config)
  10. create_resources('nova_paste_api_ini', $nova_paste_api_ini)
  11. }

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

  1. nova_config:
  2. DEFAULT/foo:
  3. value: fooValue
  4. DEFAULT/bar:
  5. 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参数,但也希望默认参数不会被篡改,或者被删除:

  1. [filter:debug]
  2. 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. poj2442 堆应用

    #include <cstdio> #include <cstring> #include <string> #include <vector> #in ...

  2. tyvj1051 选课

    /* 分组背包+树形dp:以树的深度作为阶段,以节点编号作为一维状态, 思路:首先dp[u][t]表示选择以第u门课为根,选了t门课的最大值, 状态转移方程dp[u][t]=max(所有儿子中凑出t- ...

  3. python 全栈开发,Day126(创业故事,软件部需求,内容采集,显示内容图文列表,MongoDB数据导入导出JSON)

    作业讲解 下载代码: HBuilder APP和flask后端登录 链接:https://pan.baidu.com/s/1eBwd1sVXTNLdHwKRM2-ytg 密码:4pcw 如何打开APP ...

  4. python 全栈开发,Day38(在python程序中的进程操作,multiprocess.Process模块)

    昨日内容回顾 操作系统纸带打孔计算机批处理 —— 磁带 联机 脱机多道操作系统 —— 极大的提高了CPU的利用率 在计算机中 可以有超过一个进程 进程遇到IO的时候 切换给另外的进程使用CPU 数据隔 ...

  5. 统计uv(转)

    UV是unique visitor的简写,是指通过互联网访问.浏览这个网页的自然人.在同一天内,uv只记录第一次进入网站的具有独立IP的访问者,在同一天内再次访问该网站则不计数.独立IP访问者提供了一 ...

  6. DailyWallpaper v1.03 released

    根据这一段时间的使用发现了一些问题,重新修正一下. 修正电脑从休眠状态中恢复时如果没有网络连接程序报错的bug. 添加了异常处理语句,防止抓取网页数据时的错误. 这个版本将是最后一个bug fix版本 ...

  7. Spring之对象依赖关系(依赖注入Dependency Injection)

    承接上篇: Spring中,如何给对象的属性赋值: 1:通过构造函数,如下所示: <!-- 1:构造函数赋初始值 --><bean id="user1" clas ...

  8. jQuery如何退出each循环 和如何退出function函数

    1.在函数内部使用return false是跳出function; 2.在each的回调函数中使用return false,是跳出each循环;return true 进入下一个循环: 3.break ...

  9. HDU1512 ZOJ2334 Monkey King 左偏树

    欢迎访问~原文出处——博客园-zhouzhendong 去博客园看该题解 题目传送门 - ZOJ2334 题目传送门 - HDU1512 题意概括 在一个森林里住着N(N<=10000)只猴子. ...

  10. centos7.2 使用rpm安装jdk8

    1.下载JDK 去jdk下载页面找到要下载的jdk,用wget下载 wget --no-check-certificate --no-cookies --header "Cookie: or ...