OpenStack模块代码结构

1.简介

在开始介绍各个OpenStack服务的Puppet模块前,先观察一下所有OpenStack module的目录结构,你会发现所有的模块的部分代码目录结构和命名方式几乎是一致的,这是经过了长期迭代和开发中形成的规范和统一,代码结构统一带来的好处有两点:

  1. 易于维护人员理解和管理
  2. 减少冗余代码,提高代码复用

那么我们就来看看一个OpenStack服务的Module中包含了哪些目录:

  • examples/ 放置示例代码
  • ext/ 放置external代码,和主要代码无关,但是一些有用的脚本
  • lib/ 放置library代码,例如自定义facter,resource type
  • manifests/ 放置puppet代码
  • releasenotes/ 放置releasenote
  • spec/ 放置class,unit,acceptance测试
  • tests/ 已弃用,使用examples替代

以上目录中最重要的是manifests目录,用于放置Puppet代码,在该目录下包含了以下通用代码文件:

名称 说明
init.pp 主类,也称为入口类,通常仅用于管理公共参数(如MQ参数)
params.pp 用于特定操作系统的参数值设置
client.pp 管理客户端的配置
config.pp 用于管理自定义的参数配置
policy.pp policy设置
db/ 支持多种数据库后端的配置
keystone/ keystone endpoint,service,user,role的设置

2.数据库管理

2.1 class <service>::db

class <service>::db用于管理各OpenStack服务中的数据库相关配置,<service>是OpenStack服务的名称,以Aodh为例:

  1. class aodh::db (
  2. $database_db_max_retries = $::os_service_default,
  3. $database_connection = 'sqlite:////var/lib/aodh/aodh.sqlite',
  4. $database_idle_timeout = $::os_service_default,
  5. $database_min_pool_size = $::os_service_default,
  6. $database_max_pool_size = $::os_service_default,
  7. $database_max_retries = $::os_service_default,
  8. $database_retry_interval = $::os_service_default,
  9. $database_max_overflow = $::os_service_default,
  10. ) {
  11. include ::aodh::deps
  12. $database_connection_real = pick($::aodh::database_connection, $database_connection)
  13. $database_idle_timeout_real = pick($::aodh::database_idle_timeout, $database_idle_timeout)
  14. $database_min_pool_size_real = pick($::aodh::database_min_pool_size, $database_min_pool_size)
  15. $database_max_pool_size_real = pick($::aodh::database_max_pool_size, $database_max_pool_size)
  16. $database_max_retries_real = pick($::aodh::database_max_retries, $database_max_retries)
  17. $database_retry_interval_real = pick($::aodh::database_retry_interval, $database_retry_interval)
  18. $database_max_overflow_real = pick($::aodh::database_max_overflow, $database_max_overflow)
  19. oslo::db { 'aodh_config':
  20. db_max_retries => $database_db_max_retries,
  21. connection => $database_connection_real,
  22. idle_timeout => $database_idle_timeout_real,
  23. min_pool_size => $database_min_pool_size_real,
  24. max_pool_size => $database_max_pool_size_real,
  25. max_retries => $database_max_retries_real,
  26. retry_interval => $database_retry_interval_real,
  27. max_overflow => $database_max_overflow_real,
  28. }
  29. }

class aodh::db管理了与数据库相关的配置项,其中通过调用oslo::db来实现,关于puppet-oslo模块,本书会在下一章节详细说明。

2.2 class <service>::db::mysql

class <service>::db::mysql用于创建相关服务的MySQL数据库,用户和授权等。以Aodh为例:

  1. class aodh::db::mysql(
  2. $password,
  3. $dbname = 'aodh',
  4. $user = 'aodh',
  5. $host = '127.0.0.1',
  6. $charset = 'utf8',
  7. $collate = 'utf8_general_ci',
  8. $allowed_hosts = undef
  9. ) {
  10. include ::aodh::deps
  11. validate_string($password)
  12. ::openstacklib::db::mysql { 'aodh':
  13. user => $user,
  14. password_hash => mysql_password($password),
  15. dbname => $dbname,
  16. host => $host,
  17. charset => $charset,
  18. collate => $collate,
  19. allowed_hosts => $allowed_hosts,
  20. }
  21. Anchor['aodh::db::begin']
  22. ~> Class['aodh::db::mysql']
  23. ~> Anchor['aodh::db::end']
  24. }

class aodh::db::mysql管理了MySQL aodh数据库的创建,aodh用户创建和密码设定,数据库编码,访问授权等。其调用了openstacklib::db::mysql来实现上述功能,关于puppet-openstacklib模块,本书会在下一章节详细说明。

2.3 class <service>::db::postgresql

class <service>::db::mysql用于创建相关服务的PostgreSQL数据库,用户和授权等。以Aodh为例:

  1. class aodh::db::postgresql(
  2. $password,
  3. $dbname = 'aodh',
  4. $user = 'aodh',
  5. $encoding = undef,
  6. $privileges = 'ALL',
  7. ) {
  8. include ::aodh::deps
  9. ::openstacklib::db::postgresql { 'aodh':
  10. password_hash => postgresql_password($user, $password),
  11. dbname => $dbname,
  12. user => $user,
  13. encoding => $encoding,
  14. privileges => $privileges,
  15. }
  16. Anchor['aodh::db::begin']
  17. ~> Class['aodh::db::postgresql']
  18. ~> Anchor['aodh::db::end']
  19. }

class aodh::db::postgresql完成了aodh数据库的创建,aodh用户创建和密码设定,数据库编码,访问授权等。其调用了openstacklib::db::postgresql来实现上述功能。

2.4 class <service>::db::sync

class aodh::db::sync用于执行数据库表的初始化和更新操作。以Aodh为例:

  1. class aodh::db::sync (
  2. $user = 'aodh',
  3. ){
  4. include ::aodh::deps
  5. exec { 'aodh-db-sync':
  6. command => 'aodh-dbsync --config-file /etc/aodh/aodh.conf',
  7. path => '/usr/bin',
  8. refreshonly => true,
  9. user => $user,
  10. try_sleep => 5,
  11. tries => 10,
  12. logoutput => on_failure,
  13. subscribe => [
  14. Anchor['aodh::install::end'],
  15. Anchor['aodh::config::end'],
  16. Anchor['aodh::dbsync::begin']
  17. ],
  18. notify => Anchor['aodh::dbsync::end'],
  19. }
  20. }

aodh::db::sync的实现是通过声明exec资源来调用aodh-dbsync命令行完成数据库初始化的操作。

3. Keystone初始化管理

在OpenStack部署工作中,与Keystone相关的初始化操作是集群正常运行必不可少的步骤:

  • 创建Domain
  • 创建Project
  • 创建User,设置Password
  • 创建并指定Role
  • 创建Service
  • 创建Endpoint

也包括在后期的运维中,指定user的password更新或者endpoint的更改等常见操作都可以在Puppet中完成。而这背后的工作是通过<service>::keystone::auth来完成的。

3.1 class <service>::keystone::auth

<service>::keystone::auth用于创建OpenStack服务的user,service和endpoint,以Aodh为例:

  1. class aodh::keystone::auth (
  2. $password,
  3. $auth_name = 'aodh',
  4. $email = 'aodh@localhost',
  5. $tenant = 'services',
  6. $configure_endpoint = true,
  7. $configure_user = true,
  8. $configure_user_role = true,
  9. $service_name = 'aodh',
  10. $service_type = 'alarming',
  11. $region = 'RegionOne',
  12. $public_url = 'http://127.0.0.1:8042',
  13. $internal_url = 'http://127.0.0.1:8042',
  14. $admin_url = 'http://127.0.0.1:8042',
  15. ) {
  16. include ::aodh::deps
  17. keystone::resource::service_identity { 'aodh':
  18. configure_user => $configure_user,
  19. configure_user_role => $configure_user_role,
  20. configure_endpoint => $configure_endpoint,
  21. service_name => $service_name,
  22. service_type => $service_type,
  23. service_description => 'OpenStack Alarming Service',
  24. region => $region,
  25. auth_name => $auth_name,
  26. password => $password,
  27. email => $email,
  28. tenant => $tenant,
  29. public_url => $public_url,
  30. internal_url => $internal_url,
  31. admin_url => $admin_url,
  32. }
  33. }

实际上aodh::keystone::auth在声明define keystone::resource::service_identity的基础上,根据Aodh服务而重写了相关的参数。

下面来看一段代码,关于keystone::resource::service_identity如何实现service的管理:

  1. if $configure_service {
  2. if $service_type {
  3. ensure_resource('keystone_service', "${service_name_real}::${service_type}", {
  4. 'ensure' => $ensure,
  5. 'description' => $service_description,
  6. })
  7. } else {
  8. fail ('When configuring a service, you need to set the service_type parameter.')
  9. }
  10. }

通过函数ensure_resource调用了keystone_service自定义资源类型,并传入两个参数:

  • “${service_name_real}::${service_type}”
  • {‘ensure’ => $ensure, ‘description’ => $service_description,}

有细心的读者读到这里可能会好奇,把服务名称和服务类型作为一个参数传入keystone_service,它是怎么区分的?

先来看keystone_service.rb的代码片段(代码路径puppet-keystone/lib/puppet/type/keystone_service.rb):

  1. def self.title_patterns
  2. PuppetX::Keystone::CompositeNamevar.basic_split_title_patterns(:name, :type)
  3. end

title_patterns方法通过调用PuppetX::Keystone::CompositeNamevar.basic_split_title_patterns方法来得到:name:type变量。

接着跳转到basic_split_title_patterns的定义(代码路径lib/puppet_x/keystone/composite_namevar.rb):

  1. def self.not_two_colon_regex
  2. # Anything but 2 consecutive colons.
  3. Regexp.new(/(?:[^:]|:[^:])+/)
  4. end
  5. def self.basic_split_title_patterns(prefix, suffix, separator = '::', *regexps)
  6. associated_regexps = []
  7. if regexps.empty? and separator == '::'
  8. associated_regexps += [not_two_colon_regex, not_two_colon_regex]
  9. else
  10. if regexps.count != 2
  11. raise(Puppet::DevError, 'You must provide two regexps')
  12. else
  13. associated_regexps += regexps
  14. end
  15. end
  16. prefix_re = associated_regexps[0]
  17. suffix_re = associated_regexps[1]
  18. [
  19. [
  20. /^(#{prefix_re})#{separator}(#{suffix_re})$/,
  21. [
  22. [prefix],
  23. [suffix]
  24. ]
  25. ],
  26. [
  27. /^(#{prefix_re})$/,
  28. [
  29. [prefix]
  30. ]
  31. ]
  32. ]
  33. end

可以看到basic_split_title_patterns方法默认使用’::’作为分隔符,通过not_two_colon_regex函数进行正则匹配并切割字符串。
至此,我们从上到下地剖析了如何实现Keystone相关资源的初始化,以加深读者对于代码的理解。在实际使用中,对于终端用户来说,并不需要关心底层的Ruby代码。

3.2 class <service>::keystone::authtoken

<service>::keystone::authtoken用于管理OpenStack各服务配置文件中的keystone_authtoken配置节。以Aodh服务为例:

  1. class aodh::keystone::authtoken(
  2. ...){
  3. ...
  4. keystone::resource::authtoken { 'aodh_config':
  5. username => $username,
  6. password => $password,
  7. project_name => $project_name,
  8. auth_url => $auth_url,
  9. auth_uri => $auth_uri,
  10. auth_version => $auth_version,
  11. auth_type => $auth_type,
  12. auth_section => $auth_section,
  13. ...
  14. memcache_pool_conn_get_timeout => $memcache_pool_conn_get_timeout,
  15. memcache_pool_dead_retry => $memcache_pool_dead_retry,
  16. memcache_pool_maxsize => $memcache_pool_maxsize,
  17. memcache_pool_socket_timeout => $memcache_pool_socket_timeout,
  18. ...
  19. }
  20. }

aodh::keystone::authtoken定义中声明了define keystone::resource::authtoken,并重写了部分参数的默认值。
keystone::resource::authtoken中定义了hash类型变量$keystonemiddleware_options,涵盖了keystone_authtoken配置节下的所有参数,
最终通过调用create_resources函数,传入服务名称参数$name,从而完成指定服务配置文件中keystone_authtoken的配置。

  1. $keystonemiddleware_options = {
  2. 'keystone_authtoken/auth_section' => {'value' => $auth_section},
  3. 'keystone_authtoken/auth_uri' => {'value' => $auth_uri},
  4. 'keystone_authtoken/auth_type' => {'value' => $auth_type},
  5. 'keystone_authtoken/auth_version' => {'value' => $auth_version},
  6. 'keystone_authtoken/cache' => {'value' => $cache},
  7. ...
  8. 'keystone_authtoken/username' => {'value' => $username},
  9. 'keystone_authtoken/password' => {'value' => $password, 'secret' => true},
  10. 'keystone_authtoken/user_domain_name' => {'value' => $user_domain_name},
  11. 'keystone_authtoken/project_name' => {'value' => $project_name},
  12. 'keystone_authtoken/project_domain_name' => {'value' => $project_domain_name},
  13. 'keystone_authtoken/insecure' => {'value' => $insecure},
  14. }
  15. create_resources($name, $keystonemiddleware_options)

4.维护不同Linux发行版之间的数据

PuppetOpenstack支持在Redhat, CentOS, Ubuntu等多个Linux发行版上部署OpenStack服务,然而在不同的Linux发行版中,同一个OpenStack服务的软件包的名称会有所不同。

例如,Nova API软件包的名称在Redhat下是’openstack-nova-api’,在Debian下是’nova-api’。

而这些数据则通过各个模块的class <service>::params维护。

以keystone::params为例,可以看到不同的Linux发行版之间$package_name, $service_name等参数值也有所不同:

  1. class keystone::params {
  2. include ::openstacklib::defaults
  3. $client_package_name = 'python-keystoneclient'
  4. $keystone_user = 'keystone'
  5. $keystone_group = 'keystone'
  6. $keystone_wsgi_admin_script_path = '/usr/bin/keystone-wsgi-admin'
  7. $keystone_wsgi_public_script_path = '/usr/bin/keystone-wsgi-public'
  8. case $::osfamily {
  9. 'Debian': {
  10. $package_name = 'keystone'
  11. $service_name = 'keystone'
  12. $keystone_wsgi_script_path = '/usr/lib/cgi-bin/keystone'
  13. $python_memcache_package_name = 'python-memcache'
  14. $mellon_package_name = 'libapache2-mod-auth-mellon'
  15. $openidc_package_name = 'libapache2-mod-auth-openidc'
  16. }
  17. 'RedHat': {
  18. $package_name = 'openstack-keystone'
  19. $service_name = 'openstack-keystone'
  20. $keystone_wsgi_script_path = '/var/www/cgi-bin/keystone'
  21. $python_memcache_package_name = 'python-memcached'
  22. $mellon_package_name = 'mod_auth_mellon'
  23. $openidc_package_name = 'mod_auth_openidc'
  24. }
  25. default: {
  26. fail("Unsupported osfamily ${::osfamily}")
  27. }
  28. }
  29. }

5. 管理自定义配置项的<service>::config

模板是用于管理配置文件的常见方式,对于成熟的项目而言,模板是一种理想的管理配置文件方式。但对于快速迭代的项目如OpenStack,维护人员会非常痛苦,每增删一个配置项需要同时更新模板和manifets文件。

试想一个module的更新若都在参数的增添上,那对社区开发者来说是极大的成本。有没有一种办法可以不修改module,直接在hiera里定义来添加新配置项呢?

<service>::config类是由笔者在14年初提出的特性,目的是灵活地管理自定义配置项。

自定义配置项是指未被模块管理的参数。怎么理解?

keystone::config为例,其核心是create_resources函数以及keystone_config/keystone_paste_init自定义资源:

  1. # == Class: keystone::config
  2. #
  3. # This class is used to manage arbitrary keystone configurations.
  4. #
  5. # === Parameters
  6. #
  7. # [*keystone_config*]
  8. # (optional) Allow configuration of arbitrary keystone configurations.
  9. # The value is an hash of keystone_config resources. Example:
  10. # { 'DEFAULT/foo' => { value => 'fooValue'},
  11. # 'DEFAULT/bar' => { value => 'barValue'}
  12. # }
  13. # In yaml format, Example:
  14. # keystone_config:
  15. # DEFAULT/foo:
  16. # value: fooValue
  17. # DEFAULT/bar:
  18. # value: barValue
  19. #
  20. # [*keystone_paste_ini*]
  21. # (optional) Allow configuration of /etc/keystone/keystone-paste.ini options.
  22. #
  23. # NOTE: The configuration MUST NOT be already handled by this module
  24. # or Puppet catalog compilation will fail with duplicate resources.
  25. #
  26. class keystone::config (
  27. $keystone_config = {},
  28. $keystone_paste_ini = {},
  29. ) {
  30. include ::keystone::deps
  31. validate_hash($keystone_config)
  32. validate_hash($keystone_paste_ini)
  33. create_resources('keystone_config', $keystone_config)
  34. create_resources('keystone_paste_ini', $keystone_paste_ini)
  35. }

若Keystone在某版本新增了参数new_param,在puppet-keystone模块里没有该参数,此时,只要使用keystone::config就可以轻松完成参数的管理。

在hiera文件中添加以下代码:

  1. ---
  2. keystone::config::keystone_config:
  3. DEFAULT/new_param:
  4. value: newValue

6.管理客户端 <service>::client

<service>::client用于管理各OpenStack服务的Client端,完成客户端的安装。

以Nova为例,nova::client完成了python-novaclient软件包的安装:

  1. class nova::client(
  2. $ensure = 'present'
  3. ) {
  4. include ::nova::deps
  5. package { 'python-novaclient':
  6. ensure => $ensure,
  7. tag => ['openstack', 'nova-support-package'],
  8. }
  9. }

7. 管理策略<service>::policy

<service>::policy用于管理Openstack各服务的策略文件policy.json。

以Cinder为例,下面是cinder::policy代码:

  1. class cinder::policy (
  2. $policies = {},
  3. $policy_path = '/etc/cinder/policy.json',
  4. ) {
  5. include ::cinder::deps
  6. validate_hash($policies)
  7. Openstacklib::Policy::Base {
  8. file_path => $policy_path,
  9. }
  10. create_resources('openstacklib::policy::base', $policies)
  11. oslo::policy { 'cinder_config': policy_file => $policy_path }
  12. }

其中使用create_resources调用了openstacklib::policy::base,以及声明了oslo::policy定义。