使用Ansible和serverspec协同实施的批量管理存货和变量的实施示例(支持多台服务器)

首先

为了解决在对通过Ansible配置的Linux服务器集群进行serverspec测试时遇到的以下问题,我尝试对Ansible和serverspec进行改进。

    • Ansibleとserverspecそれぞれで変数ファイルを用意するのが面倒

 

    • Ansibleとserverspecそれぞれで対象ノードを記述したインベントリファイルを用意するのが面倒

serverspec-init で提供される初期設定では、テストコードをテスト対象サーバ名のついたディレクトリ毎に用意する必要があり面倒

请参考如何将Ansible和serverspec结合使用的入门和初级教程。

目标

为了实现以下内容,我们将进行实施。

    • Ansible のインベントリを serverspecでも使い回す

 

    • Ansible Playbook実行時の変数を使って、serverspecでテストする

 

    • Ansibleの変数は指定方法によって優先度が決められているので、最終的にノードに対してPlaybookが実行された際に使われた変数で、serverspecでテストする

 

    • 複数ノードに対して、Ansibleしてserverspecする

 

    serverspecはロールごとにテストコードを分ける

实例展示

环境

Ansible 控制节点

    • CentOS 7.6

 

    • ansible 2.9.0

 

    • serverspec 2.41.5

 

    • ruby 2.6.3p62

 

    Python 2.7.5

Ansible管理节点

    CentOS 7.6 × 2 Node

目录结构

$ tree -aF /autotools
/autotools
|-- .ssh/
|   `-- aws_key.pem    # ManagedNodeのSSH秘密鍵
|-- ansible/
|   |-- ansible.cfg
|   |-- group_vars/    # グループ用変数ディレクトリ
|   |-- host_vars/     # ホスト用変数ディレクトリ
|   |-- inventory/     # Ansible向けインベントリ配置ディレクトリ
|   `-- centos.yml       # Playbook
`-- serverspec/
    |-- .rspec
    |-- Rakefile
    |-- spec/
    |   |-- base/      # baseロール向けのテストコート配置ディレクトリ
    |   |   `-- sample_spec.rb # テストコード
    |   `-- spec_helper.rb
    `-- spec_hosts/    # serverspec向け変数配置ディレクトリ

Ansible 安示

为了整体管理本次服务器群,让我们用英文字母为 project_name 进行命名。我们以 anken 作为例子。

ansible.cfg:Ansible的配置文件。

在这里,我们指定了用于环境变量 ANSIBLE_CONFIG 的 ansible.cfg 文件。
由于我在代码开发中重复使用相同的主机名,所以将 ssh 执行参数记录在这里。

$ export ANSIBLE_CONFIG=/autotools/ansible/ansible.cfg
[defaults]

[ssh_connection]
ssh_args = -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no

[privilege_escalation]
become = true

SSH private key : SSH私钥

将用于连接到Ansible托管节点的SSH密钥放置在/autotools/.ssh目录中。
密钥的路径将在清单文件中指定。

库存文件

请将文件 anken.ini 放置在 /autotools/ansible/inventory/ 目录下。

只要按照Ansible的规则进行编写就可以了,但是因为要与serverspec配合使用,所以请务必进行以下3项必需的配置。

    • ansible対象ノードはすべて プロジェクト名のグループに所属させてください

[all:vars] で project_name を指定してください
ansibleが 対象ノードにssh loginする際に使うユーザID、パスワード or SSH鍵を、それぞれ ansible_user、ansible_password、ansible_ssh_private_key_fileで設定してください。パスワードとSSH鍵はどちらか1つを設定してください。(ここに書いたパスワードとSSH鍵は、後述する変数ファイルを通じて、serverspecでも利用されます)

[anken]
prod_foobar1 ansible_host=xx.xx.xx.xx
dev_foobar1 ansible_host=yy.yy.yy.yy

[anken:vars]
ansible_user=centos
ansible_ssh_private_key_file=~/.ssh/aws_key.pem

[all:vars]
project_name=anken

Ansible的执行是针对ansible_host的IP或名称进行的。

inventory_hostname(例如指定为prod_foobar1 dev_foobar2的位置)不需要与节点的实际主机名匹配。

剧本

在这个实施示例中,使用的Playbook示例如下所示。
name: 在本地配置服务器规范,以输出服务器规范使用的变量文件。

---
- name: Playbook for centos7 managed node
  hosts: all
  gather_facts: true

  tasks:
    - name: Create group
      group:
        name: "{{ item.name }}"
        gid: "{{ item.gid }}"
      loop: "{{ group }}"
      tags: group

    - name: Create User
      user:
        name: "{{ item.name }}"
        uid: "{{ item.uid }}"
        group: "{{ item.group }}"
        groups: "{{ item.groups }}"
        home: "{{ item.home }}"
        shell: "{{ item.shell }}"
      loop: "{{ user }}"
      tags: user

    - name: System service
      systemd:
        name: "{{ item.name }}"
        enabled: "{{ item.enabled }}"
        state: "{{ item.state }}"
      loop: "{{ service }}"
      tags: service

- name: Configure for serverspec at localhost
  hosts: localhost
  connection: local
  gather_facts: false
  tasks:

    - name: Dump hostvars for serverspec
      copy:
        content: "{{ hostvars | to_nice_yaml }}"
        dest: "../serverspec/spec_hosts/{{ project_name }}.yml"
      tags: serverspec

变量文件布置

项目中的共同变量应放置在 /autotools/ansible/group_vars/#{project_name}.yml。如果想要为特定的 inventory_hostname 指定不同的变量,则应将其放置在 /autotools/ansible/host_vars/#{inventory_name}.yml。

请在变量文件中使用 `serverspec_role` 指定在进行 serverspec 测试时要使用的角色。

serverspec_role:
  - base
group:
  - name: unyo
    gid: 1101
  - name: infra
    gid: 1102
  - name: app
    gid: 1103
user:
  - name: user1
    uid: 2001
    group: customer
    groups: [ unyo, infra]
    home: /home/user1
    shell: /bin/bash
  - name: user2
    uid: 2002
    group: customer
    groups: [ app ]
    home: /home/user2
    shell: /bin/bash
  - name: user3
    uid: 2003
    group: customer
    groups: [ app, infra ]
    home: /home/user3
    shell: /bin/bash
service:
  - name: chronyd.service
    enabled: false
    state: stopped
  - name: rsyncd.service
    enabled: true
    state: started

在这里,我们将仅限于prod_foobar节点,尝试覆盖一些变量。

group:
  - name: unyo
    gid: 2101
  - name: infra
    gid: 2102
  - name: app
    gid: 2103

文件目录结构

放置了必要的文件后,应该是这个样子的。

$ tree /autotools/ansible -aF
/autotools/ansible
|-- ansible.cfg
|-- centos.yml
|-- group_vars/
|   `-- anken.yml
|-- host_vars/
|   `-- prod_foobar1
`-- inventory/
    `-- anken.ini

执行

请使用以下方式指定清单文件并执行 centos.yml Playbook。

$ cd /autotools/ansible
$ ansible -i ./inventory/anken centos.yml

服务器规范

文件夹和文件的结构

执行Ansible后,serverspec方面的目录和文件结构应该是这样的。

# tree /autotools/serverspec -aF
/autotools/serverspec
|-- .rspec
|-- Rakefile
|-- spec/
|   |-- base/
|   |   `-- sample_spec.rb
|   `-- spec_helper.rb
`-- spec_hosts/
    `-- anken.yml        # Ansibleによって生成された変数ファイル

运行命令

虽然顺序会有所前后,但serverspec的执行命令如下。这里的意图是将(由Ansible生成的)变量文件名作为参数传递给rake命令进行使用。

$ rake spec anken -T
rake spec               # Run spec to all hosts
rake spec:dev_foobar1   # Run spec to dev_foobar1
rake spec:prod_foobar1  # Run spec to prod_foobar1
$ rake spec anken

Rakefile 可以被翻译为 “耙文件”。

我对由serverspec-init生成的标准Rakefile进行了一些修改。

    • Ansibleが生成した 変数ファイルを読み込むための処理を追加

 

    • serverspec_role と同じ名前のディレクトリ配下の *_spec.rb ファイルを読み込み

 

    rakeコマンドの引数が rakeタスクと誤認されてエラーになっちゃうので、引数と同じ名前の空タスク作成
require 'rake'
require 'rspec/core/rake_task'
require 'yaml'

# 変数ファイルを読み込み
project_name = ARGV[1]
hosts = YAML.load_file("./spec_hosts/#{project_name}.yml")

desc "Run spec to all hosts"
task :spec => 'spec:all'

namespace :spec do
  task :all => hosts.keys.map {|key| 'spec:' + key }

  hosts.keys.each do |key|
    desc "Run spec to #{key}"
    RSpec::Core::RakeTask.new(key.to_sym) do |t|
      ENV['INVENTORY_HOST'] = key
      ENV['PROJECT_NAME'] = project_name
      # serverspec_role と同じ名前のディレクトリ配下の *_spec.rb ファイルを読み込み
      t.pattern = 'spec/{' + hosts[key]['serverspec_role'].join(',') + '}/*_spec.rb'
      t.fail_on_error = false
    end
  end
end

# rakeコマンドの引数を空タスクとして偽造
ARGV.slice(1,ARGV.size).each{|v| task v.to_sym do; end}

我参考了以下内容,非常感谢您。

参考:在Rake任务中编写像普通参数一样的处理
https://qiita.com/nao58/items/aa50514d97f05eb8d128

请参考:主机特定属性的使用公式,具体可查看 https://serverspec.org/advanced_tips.html

spec_helper.rb 的意思是给 RSpec 提供支持和辅助的文件。

我也对spec_helper.rb的初始状态进行了一些功能更改。

    • Ansibleが生成した 変数ファイルを読み込むための処理を追加

 

    読み込んだ変数ファイルから、Ansibleで使った host、user、password or key を抽出
require 'serverspec'
require 'pathname'
require 'net/ssh'
require 'yaml'

# 変数ymlファイル読み込み
key = ENV['INVENTORY_HOST']
project_name = ENV['PROJECT_NAME']
properties = YAML.load_file("./spec_hosts/#{project_name}.yml")
set_property properties["#{key}"]

set :backend, :ssh
set :path, '/sbin:/usr/sbin:$PATH'

# ssh実行部
RSpec.configure do |c|
  c.before :all do
    # 読み込んだ変数ファイルから、Ansibleで使った host、user、password or key を抽出
    set :host, property['ansible_host']
    options = Net::SSH::Config.for(c.host)
    options[:user] = property['ansible_user']
    if property['ansible_password']
      options[:password] = property['ansible_password']
    else
      options[:keys] = [ property['ansible_ssh_private_key_file'] ]
    end
    options[:user_known_hosts_file] = '/dev/null'
    set :ssh_options, options
  end
end

因为设置了`set :backend, :ssh`,所以这个`spec_helper.rb`文件不支持WinRM。然而,由于Ruby可以编写任何代码,所以应该不难实现Windows的支持。

测试代码

这是一个示例。
正如在spec_helper.rb中所写的那样,可以通过property[‘xxx’]从变量文件中提取变量并进行重用。

# frozen_string_literal: true

require 'spec_helper'

puts "\nRun serverspec to #{property['inventory_hostname']}"

property['group'].each do |attr|
  describe group(attr['name']) do
    it { should exist }
    it { should have_gid attr['gid'] }
  end
end

property['user'].each do |attr|
  describe user(attr['name']) do
    it { should exist }
    it { should have_uid attr['uid'] }
    it { should belong_to_group attr['group'] }
  end
end

property['service'].each do |attr|
  describe service(attr['name']) do
    attr['enabled']            ? it { should be_enabled } : it { should_not be_enabled }
    attr['state'] == 'started' ? it { should be_running } : it { should_not be_running }
  end
end

执行

请将Ansible生成的变量文件的文件名作为参数传递给rake spec命令并执行。也可以逐台进行测试执行。

$ rake spec anken
$
$ rake spec anken -T    # タスク一覧を表示するコマンド
rake spec               # Run spec to all hosts
rake spec:dev_foobar1   # Run spec to dev_foobar1
rake spec:prod_foobar1  # Run spec to prod_foobar1
$
$ rake spec:dev_foobar1 anken

总结

我们成功将Ansible和serverspec中容易进行双重管理的变量文件和清单文件进行统一。同时,我们参考了serverspec官方的方法,成功实现了按角色管理和执行测试代码。

由于Serverspec是一个非常Ruby风格的工具,对于不熟悉Ruby的人来说可能有些难以入门,但一旦熟悉起来,会发现它非常方便写各种处理。

示例代码

我已将以下内容发布在以下网址上:https://github.com/kentarok/autotools。

广告
将在 10 秒后关闭
bannerAds