1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

tips: hosts ファイルから Ansible の NIC 設定タスクを構成する ※注記あり

Posted at

hosts ファイルから、Ansible の NIC 設定タスクを構成するスクリプトを作成しました。
管理対象ホストに配布する(であろう) hosts ファイルから設定タスクを構成する格好です。ホスト名、IPアドレスを hosts で一元管理できるので楽ちんです。

例えば、以下のように使用します。

$ cat hosts | hosts2entity.pl '-mng$' | entity2yaml.pl

このスクリプトで、hosts ファイルに登録されたノードそれぞれについて ノード名.yml ファイルを生成します。それを Ansible の host_vars/ に置き、NIC 設定タスクで利用します。


注記: 2018/12/13 付

当初期待したほど楽ちんでもない、と判断するに至りました。以下の理由からです。

理由:

  1. Ansible が接続に行く NIC を Ansible のタスクで設定変更するのはリスクが高い。
  2. 既存 NIC の設定が更新されず、NetworkManager で同じ NIC の新しい設定が出来たりする現象が発生した。
  3. Ansible の nmcli module が依存する NetworkManager-glib は obsoleted 扱いになっている。 (cf. Issue #48055 · ansible/ansible · GitHub )

理由1. は、構造的な問題です。Ansible からは管理系 NIC に接続に行くように構成する(だろう?)から、とするとサービス系 NIC の設定変更だけに留めるのが吉でしょう。そもそも Ansible を使う時点で少なくとも NIC 1つ(管理系?)は設定済みなので、わざわざ設定変更する必要がありません。

理由2. は、Vagrant + VirtualBox 環境との相性なのかも知れません(深く検証していません)。タスクを実行後、nmcli conn で見たら、同じ NIC に UUID の異なる 2 つの設定が出来ていたり、ifcfg-eth1 の他に ifcfg-eth1-1 みたいな設定ファイルが出来ていたり…と、おかしな動作が見受けられました。

理由3. は、過渡的な問題、だと思います。obsolated なので標準では NetworkManager-glib はインストールされません。nmcli モジュールの利用前に追加インストールしておく必要があります。
NetworkManager-glib のパッケージ情報を見ると old API なんて書かれています(cf. NetworkManager-glib-1.12.0-6.el7.x86_64.rpm )。新しい API である libnm に取って代わられるところのようです。
また、Pull Request #48870 を見ると、needs_revision なんてタグが付けられていて、nmcli モジュールに根本的な見直しが図られる、みたいな流れになっている(?)ようです。

以上から、今しばらくは nmcli Module には触らない方が良さそう、という判断に大きく傾きました。

もっとも host_vars/ には人間が見る用に値だけ置いておいて、Ansible タスクでは参照しないでおく、というのもアリでしょう。
何事においてもそうですが、充分な事前検証の上での利用をお薦めいたします。

この注記に関する参考文献

(注記終わり)


どういうこと?

想定する構成

例として、以下のような3つのノードからなるシステムを想定します。

nodes.png

  • 1つのノードが複数のNICを持ち、複数のネットワーク(ここでは管理系、サービス系)に接続する。
  • サービス系ネットワークに接続するアドレスは、'ノード名' のホスト名を割り当てる。
  • 管理系ネットワークに接続するアドレスは、'ノード名-mng' のホスト名を割り当てる。

hosts ファイル

前述のような構成では、以下のような hosts ファイルにまとめられます。

172.16.0.1	node1
172.16.0.2	node2
172.16.0.2	node3

10.0.0.1	node1-mng
10.0.0.2	node2-mng
10.0.0.3	node3-mng

ansible の NIC 設定タスク

ansible で以下のようなタスクを定義しておくと、管理対象ホストそれぞれにホスト名、NICごとにIPアドレスを設定します。

---
- name: setup hostname
  hostname:
    name: "{{ hostname }}"

- name: setup ipv4 addr. for a management network
  nmcli:
    conn_name: "{{ nic0_name }}"
    type: ethernet
    ip4: "{{ nic0_ipv4 }}"
    state: present

- name: setup ipv4 addr. for a service network
  nmcli:
    conn_name: "{{ nic1_name }}"
    type: ethernet
    ip4: "{{ nic1_ipv4 }}"
    state: present

{{ }}で括られた {{ hostname }}{{ nic0_name }}{{ nic0_ipv4 }} といった部分には、変数が当てはめられます。

変数の値をノードごとに設定すれば、各ホストに個別の設定が反映されます。

※Ansible が接続に行く先の NIC (管理系 NIC を想定) は、タスクで設定をいじらない方が良いと思いますので、nic0 に関するタスクはコメントアウトしましょう(前述の注記を参照のこと)。

変数の値をノードごとに設定する

ansible で、Best Practicesに倣い playbook を構成すると、host_vars/ノード名.yml に書かれた変数値が、当該ノードに対するタスクで適用されます。

以下のような host_vars/ノード名.yml ファイルを置くと、前述の NIC 設定タスクに反映されます。

---
hostname: node1
nic0_name: nic0
nic0_ipv4: 10.0.0.1/24
nic1_name: nic1
nic1_ipv4: 172.16.0.1/24

スクリプトで何をするの?

ノード名.yml というファイルを生成します。これらを host_vars/ の下に置き、ansible の NIC 設定タスクに読み込ませよう、という算段です。

スクリプト hosts2entity.pl

このとき、1つのノードが複数のNICを持つので、hosts ファイル上では複数のエントリに分かれて記述されます。それをノードごとに1つにまとめる必要があります。

hosts2entity.pl は、複数行に分かれた hosts エントリを、ホスト名文字列の共通部分を基にまとめます。
ただし、1つのノードにまとめられるホスト名は、あらかじめ【共通部分】+【付加部分】となるように設計しておきます。

例1:

  • node1
  • node1-mng

という、2つのホスト名の hosts エントリを、1つのノード node1 のデータとしてまとめるには、スクリプト hosts2entity.pl-mng$ という引数を渡します。

要するに、【付加部分】にあたる文字列を指定します。正規表現を使用できます。

結果、hosts2entity.plData::Dumper で文字列化された hash データを標準出力に出力します。※以下の出力例は読みやすいように行の順序を入れ替えています。

{
  'node1' => {
               '_entity' => 'node1',
               'node1' => [
                            '172.16.0.1'
                          ],
               'node1-mng' => [
                                '10.0.0.1'
                              ],
               '_rules' => [
                             '',
                             '-mng$'
                           ],
               '_keys' => [
                            'node1',
                            'node1-mng'
                          ]
             },
  'node2' => {
               '_entity' => 'node2',
               'node2' => [
                            '172.16.0.2'
                          ],
               'node2-mng' => [
                                '10.0.0.2'
                              ],
               '_rules' => [
                             '',
                             '-mng$'
                           ],
               '_keys' => [
                            'node2',
                            'node2-mng'
                          ]
             },
  'node3' => {
               '_entity' => 'node3',
               'node3' => [
                            '172.16.0.3'
                          ],
               'node3-mng' => [
                                '10.0.0.3'
                              ],
               '_rules' => [
                             '',
                             '-mng$'
                           ],
               '_keys' => [
                            'node3',
                            'node3-mng'
                          ]
             },
}

スクリプト entity2yaml.pl

entity2yaml.pl は、hosts2entity.pl の出力を YAML 形式に変換して出力します。

とはいっても、hosts2entity.pl の出力からどの値を拾うか、は ansible の NIC 設定タスクの書き方や、ansible の実行環境の構成によって変わります。hosts2entity.pl の出力を眺めて、sub genYAML$file_output辺りを調整して使用します。単純な整形です。

結果、以下のようなファイル出力を得ます。

node1.yml :

hostname: node1
nic0_name: nic0
nic0_ipv4: 10.0.0.1/24
nic1_name: nic1
nic1_ipv4: 172.16.0.1/24

node2.yml :

---
hostname: node2
nic0_name: nic0
nic0_ipv4: 10.0.0.2/24
nic1_name: nic1
nic1_ipv4: 172.16.0.2/24

node3.yml :

---
hostname: node3
nic0_name: nic0
nic0_ipv4: 10.0.0.3/24
nic1_name: nic1
nic1_ipv4: 172.16.0.3/24

ソースコード

以下の環境で、動作を確認しています。

  • CentOS 7
  • perl
  • Ansible 2.7

Ansible は、 Best Practices のディレクトリ構成に従って構成しておきます。

hosts2entity.pl

# !/usr/bin/perl

use strict;
use warnings;
use utf8;

use Data::Dumper;
$Data::Dumper::Terse = 1;

# parse args
my @rules = ("", @ARGV);

# ==========================================================
# PROCESS
# ==========================================================

my $rh_hosts_ip = readHostsFile();
my $rh_entry = unifyEntity($rh_hosts_ip, @rules);

print Dumper($rh_entry);
exit(0);

# ==========================================================
# routines
# ==========================================================

# --------------------
# unifyEntity
#   ... unify several hosts entries as ONE node
#
sub unifyEntity
{
        my ($rh_input, @regexp) = @_;
        my (%return) = ();

        foreach my $k (keys(%{$rh_input}))
        {
                my ($entity, $index) = ($k, 0);

                my $i = 0;
                foreach my $r (@regexp)
                {
                        if($k =~ /$r/)
                        {
                                $entity =~ s/$r//;
                                $index = $i;
                        }
                        $i++;
                }

                if(! exists($return{$entity}))
                {
                        $return{"$entity"} = {
                                "_entity" => "$entity",
                                "_rules"  => [@regexp],
                                "_keys" => [],
                        };
                }

                my $r_value = $return{"$entity"};
                my $values_index = 'v_' . $k;
                $r_value->{"_keys"}->[$index] = $k;
                $r_value->{$k} = $rh_input->{"$k"};
        }
        return \%return;
}

# --------------------
# readHostsFile
#
# returns:
#   {
#     hostname1 => [ ipaddr1 ],
#     hostname2 => [ ipaddr2, ipaddr2x, ...],  # invalid, 2 addr for 1 host.
#      :
#   }
#
sub readHostsFile
{
        my %entry = ();
        while(<STDIN>)
        {
                s/#.*$//;       # delete a comment part in a line.
                next if (/^\s*$/);      # skip a comment and an empty line

                my ($ipaddr, @hostname) = split(/\s+/);
                foreach my $h (@hostname)
                {
                        if(exists($entry{"$h"}))
                        {
                                warn "WARN: a key '$h' duplicated: the entry already exists";
                        }
                        else
                        {
                                $entry{"$h"} = [];
                        }

                        my $r_values = $entry{"$h"};
                        push(@{$r_values}, $ipaddr);
                }
        }
        return \%entry;
}

entity2yaml.pl

# !/usr/bin/perl

use strict;
use warnings;
use utf8;

use Data::Dumper;
$Data::Dumper::Terse = 1;

my ($dumped_hash, $rh_input) = ();
{
        local $/ = '';
        $dumped_hash = <>;
}
eval( '$rh_input = ' . "$dumped_hash" );

# ==========================================================
# PROCESS
# ==========================================================
foreach my $k (keys(%{$rh_input}))
{
        my $output = genYAML($rh_input->{$k});
        my $file_output = sprintf("%s.yml", $k);

        my $fh;
        open($fh, ">", $file_output) && print $fh $output;
        close($fh);
}
exit(0);

# ==========================================================
# routines
# ==========================================================
sub genYAML
{
        my ($r_value) = @_;

        my $hostname = $r_value->{'_entity'};
        my $nic0_host = $r_value->{'_keys'}->[1];
        my $nic1_host = $r_value->{'_keys'}->[0];
        my $nic0_ipv4 = $r_value->{$nic0_host}->[0];
        my $nic1_ipv4 = $r_value->{$nic1_host}->[0];

        my $yaml = <<EOM;
---
hostname: $hostname
nic0_name: nic0
nic0_ipv4: $nic0_ipv4/24
nic1_name: nic1
nic1_ipv4: $nic1_ipv4/24
EOM

        return $yaml;
}

あとがき

今どきどこでも使えるだろう、と見越して perl で実装しましたが、よくよく考えれば Ansible 実行環境なのだから python を使えばよかったです。

参考文献

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?