使用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。