2020/10 いくつか最近のバージョンでの注意点を追記しました。記事を書いてから2年以上経っていて内容が古くなっていますので近いうちに2.10版として更新したいと思っています
Ansible 2.10 + NetApp ONTAP Collection 20.10.0 を使った SSLの Client認証使うときの設定について記事を書きましたので、新しい環境の方はこちらも見ていただければと思います
AnsibleでNetApp ONTAP Storageを設定
Ansible 2.6以後、NetAppストレージ用のモジュールが追加されました。
この記事では NetApp ONTAP9を搭載したストレージの Clusterが出来た状態から初めて、
NFSでアクセス可能な領域=ボリュームをクライアントに公開するところまでを行うPlayBookを作っています。
お約束
この記事で書かれている PlayBookや Roleは動作を保証されたものではありません。
各環境に合わせてテスト・確認をしたうえで自己責任にて利用していただければと思います。
Ansibleの環境
- Ubuntu 18.04
- Ansible 2.7.6
- netapp-lib 2018.11.13
前提とする ONTAPクラスタの状態
- クラスタが組まれている事
- クラスタ管理IP・ノード管理IPに Ansibleを動かすホストからネットワークが届く事
下準備
ONTAP cluster で HTTPによるアクセスを有効化します
# enable http api access
ssh admin@ONTAP_IP_ADDRESS 'set -privilege advanced; system services web modify -http-enabled true;'
pythonの netapp-libを追加
sudo apt-get install -y python-pip
pip install netapp-lib
Roleと変数とInventory
ONTAPの Ansibleモジュールは かなり細かい粒度(API粒度)で用意されており、
タスク毎に認証情報が必要な事等から普通に書くとかなり冗長な感じになってしまいます。
これを緩和するために一部Roleを使って書いています。
また、認証情報は PlayBookに書かず、変数を利用するようにしていますが
クラスタで一つのファイルにしておきたいので Inventoryを階層的に定義しています。
Role
今回用意したRoleのは以下の2+1個です。
-
VLAN I/Fの作成
-
Broadcastドメインの作成
-
Factを集めて表示する
Roleのメンテナンスが面倒なので、再利用・ループが必要無いようなところは普通にベタっと書いています。
Role:VLAN I/Fの作成
各ノードに対応した host_varsでデータ用インターフェースの一覧を書いておき、
必要に応じて このRoleでvlanインターフェースを作る様にしています。
- na_ontap_net_vlan:
state=present
vlanid={{ vlan_id }}
node={{ inventory_hostname }}
parent_interface={{ item }}
username={{ cluster_username }}
password={{ cluster_password }}
hostname={{ cluster_hostname }}
loop: "{{ data_interfaces }}"
Role:Broadcastドメインの作成
クラスタ内で同一のvlan idを持っているI/Fを集めてブロードキャストドメインを作ります。
vlanのroleと ノードのデータinterface一覧から パラメータを作っているのでちょっと長いです。
- set_fact:
vlan_interface_list: >-
{%- set templist = [] -%}
{%- for i in hostvars[inventory_hostname]['groups'][inventory_hostname+'-nodes'] -%}
{%- if hostvars[i]['vlan_interface_dict'] is defined -%}
{%- if hostvars[i]['vlan_interface_dict'][vlan_id] is defined -%}
{%- set _ = templist.append(hostvars[i]['vlan_interface_dict'][vlan_id]|join(",")) -%}
{%- endif -%}
{%- endif -%}
{%- endfor -%}
{{ templist }}
- name: create broadcast domain for vlan {{ vlan_id }}
na_ontap_broadcast_domain:
state=present
username={{ cluster_username }}
password={{ cluster_password }}
hostname={{ cluster_hostname }}
broadcast_domain={{ bc_name }}
mtu=9000
ipspace={{ ipspace }}
ports={{ vlan_interface_list|join(",")}}
Role:Factの収集
2020/10 2.9時点で na_ontap_gather_factsは Deprecatedになっています。2.13で削除される予定です。_info 系の利用が推奨されます
Ansibleから Storageを管理するためには、代理となるサーバからStorage上にAPIを発行します。
そのままでは Storageの Factを集める事が出来ませんので、ここもモジュールで対応する事になります。
簡単なので普通に書いても良いのですが、いつも使うので Roleにしておきました。
- name: Get ONTAP Info
na_ontap_gather_facts:
state: info
username: "{{ cluster_username }}"
password: "{{ cluster_password }}"
hostname: "{{ cluster_hostname }}"
- debug: var=ontap_facts
変数
クラスタの認証情報と接続先をグループ変数で設定します。
なお、グループ/ホスト変数双方とも ansible_hostに local_hostを指定しています。
playbookを実行するホスト自身に netapp-lib(APIを叩くためのライブラリ)が
インストールされている必要があります。
ストレージの操作を他のホストで実行したい場合は ansible_hostを適宜変更する必要があります。
グループ変数
クラスタ毎の認証情報を含めて作成します。ansible-vaultで暗号化すると良いかと思います。
---
ansible_host: localhost
cluster_name: "クラスタ名"
cluster_username: "admin"
cluster_password: "パスワード"
cluster_hostname: "IPアドレス/ホスト名"
2020/10 最近のVersionで ansible_host側での Python3が必要な場合があるようです。pip3 install netapp-lib
及び 下記の様にInterpreterの指定の追加が必要な場合がありました。
ansible_python_interpreter: "/usr/bin/python3"
ホスト変数
ノード毎の情報です。
---
node_hostname: "<ノード管理IPアドレス>"
data_interfaces:
- e0e
- e0g
data_interfacesにデータインターフェースとして利用する予定のインターフェース名を列挙しています。
これは VLAN I/F作成Roleとブロードキャストドメイン作成Roleで利用していますので
それらのRoleが不要であれば特に設定する必要はありません。
Inventory
実際にネットワーク上に存在するのは以下ですが、これを階層的にします。
- AFF クラスタ
- AFF-01 ノード
- AFF-02 ノード
- svm01 ストレージ仮想マシン
- svm02 ストレージ仮想マシン...
グループ階層はこんな形になります。
AFF-cluster グループ
- AFF
-
AFF-nodes グループ
- AFF-01
- AFF-02...
-
AFF-svms グループ
- svm01
- svm02...
インベントリファイルは以下のようにしています。
ノード名とクラスタ名は実機の設定に完全一致させる必要がありますので
実機の設定通りに大文字小文字等を変えないようにしてください。
---
#
# AFF-clusterグループ
#
AFF-cluster:
hosts:
AFF:
children:
AFF-nodes:
AFF-svms:
#
# AFFクラスタのノード一覧
#
AFF-nodes:
hosts:
AFF-01:
AFF-02:
#
# AFFクラスタのSVM一覧
#
AFF-svms:
hosts:
svm01:
svm02:
タスク・ロールの設定
実際に構成タスクを書いていきます。
各タスクでどこを対象に(hosts指定)しているかを気にしながら見ていただければと思います。
Factの収集
「クラスタに対して」Roleを使ってFactを集めてDebugで表示させます。
#
# ONTAPクラスタのFactsを集める
#
- hosts: AFF
gather_facts: no
roles:
- { role: ontap_facts }
Licenseの登録
「クラスタに対して」ライセンスを登録します。
ロール定義してループで回してやるべきかなと思いつつ今はベタっと書いてます。
以下はbaseだけですが、同様にnfs/iscsi/fcp等も登録します。
#
# ライセンスの登録
#
- hosts: AFF
gather_facts: no
tasks:
- name: base license
na_ontap_license:
state: present
hostname: "{{ cluster_hostname }}"
username: "{{ cluster_username }}"
password: "{{ cluster_password }}"
license_codes: XXXXXXXXXXXXXXAAAAAAAAAAAAAA
license_names: base
Aggregateの作成
「クラスタに対して」各ノードでAggregateを作ります。
指定はディスク本数とディスクタイプ、名前を指定します。
#
# ディスクカウントとタイプ、名前を指定してAggregateを作ります。
#
- hosts: AFF
gather_facts: no
tasks:
- name: create aggregate aggr01
na_ontap_aggregate:
state: present
hostname: "{{ cluster_hostname }}"
username: "{{ cluster_username }}"
password: "{{ cluster_password }}"
disk_count: 22
disk_type: SSD
name: aggr01
nodes: AFF-01
- name: create aggregate aggr02
na_ontap_aggregate:
state: present
hostname: "{{ cluster_hostname }}"
username: "{{ cluster_username }}"
password: "{{ cluster_password }}"
disk_count: 22
disk_type: SSD
name: aggr02
nodes: AFF-02
Interface Groupの作成
「クラスタ内の全ノードに対して」LACP等で物理NICのチームを作ります。
各ノードに対してすべて同じ設定をしています。
以下の例では e0cとe0d を Default bcdomainから抜いたあとに、
ifgrp a0aをlacpで作成して 各ポートを所属させます。
注意:一度インターフェースを作成したあとに、この設定ファイルのmode等を変更して
再度PlayBookを走らせてもifgrpの設定は変更されません。
一度明示的にifgrpを消したあとに実施してください。
#
# インターフェースグループの作成
#
- hosts: AFF-nodes
gather_facts: no
tasks:
- name: remove ports from default bcdomain.
na_ontap_broadcast_domain_ports:
state: absent
hostname: "{{ cluster_hostname }}"
username: "{{ cluster_username }}"
password: "{{ cluster_password }}"
broadcast_domain: Default
ports: "{{ inventory_hostname }}:e0c"
- name: remove ports from default bcdomain.
na_ontap_broadcast_domain_ports:
state: absent
hostname: "{{ cluster_hostname }}"
username: "{{ cluster_username }}"
password: "{{ cluster_password }}"
broadcast_domain: Default
ports: "{{ inventory_hostname }}:e0d"
- name: create or add port to ifgrp for e0c
na_ontap_net_ifgrp:
state: present
hostname: "{{ cluster_hostname }}"
username: "{{ cluster_username }}"
password: "{{ cluster_password }}"
mode: multimode_lacp
distribution_function: port
node: "{{ inventory_hostname }}"
name: a0a
port: e0c
- name: add port to ifgrp for e0d
na_ontap_net_ifgrp:
state: present
hostname: "{{ cluster_hostname }}"
username: "{{ cluster_username }}"
password: "{{ cluster_password }}"
mode: multimode_lacp
distribution_function: port
node: "{{ inventory_hostname }}"
name: a0a
port: e0d
VLAN I/Fを作成
「クラスタ内の全ノードに対して」データインターフェース上にVLAN I/Fを作ります。
データインターフェースと書いていますが、
host_vars内で設定したインターフェースの事です。
#
# vlan i/fの作成
#
- hosts: AFF-nodes
gather_facts: no
roles:
- { role: datavlan, vlan_id: 1011 }
- { role: datavlan, vlan_id: 1012 }
Broadcast Domainの作成
「クラスタに対して」VLAN I/Fに対応した ブロードキャスト ドメインを作ります。
- hosts: AFF
gather_facts: no
roles:
- { role: bcdomain, vlan_id: 1011, ipspace: 'Default' }
- { role: bcdomain, vlan_id: 1012, ipspace: 'Default' }
ipspaceの作成はAnsible 2.7から サポートされています。
SVMを作る
「クラスタに対して」SVMを作成します。
#
# create a svm.
#
- hosts: AFF
gather_facts: no
tasks:
- name: create svm
na_ontap_svm:
state: present
hostname: "{{ cluster_hostname }}"
username: "{{ cluster_username }}"
password: "{{ cluster_password }}"
name: svm01
aggr_list: aggr01,aggr02
root_volume: volroot
root_volume_aggregate: aggr01
root_volume_security_style: unix
- name: create svm
na_ontap_svm:
state: present
hostname: "{{ cluster_hostname }}"
username: "{{ cluster_username }}"
password: "{{ cluster_password }}"
name: svm02
aggr_list: aggr01,aggr02
root_volume: volroot
root_volume_aggregate: aggr02
root_volume_security_style: unix
LIFを作る
「クラスタに対して」SVMを指定してLIFを作成します。
#
# create lif for node a300-01
#
- hosts: AFF
gather_facts: no
tasks:
- name: create svm lif.
na_ontap_interface:
state: present
hostname: "{{ cluster_hostname }}"
username: "{{ cluster_username }}"
password: "{{ cluster_password }}"
vserver: svm01
interface_name: lif_svm01
home_node: AFF-01
home_port: a0a
role: data
protocols: nfs,cifs
address: xxx.xxx.xxx.xxx
netmask: 255.255.255.0
Export Policyを作る
「クラスタに対して」SVMを指定してExport Policyを作成します。
#
# create export policy.
#
- hosts: AFF
gather_facts: no
tasks:
- name: create export policy.
na_ontap_export_policy_rule:
state: present
hostname: "{{ cluster_hostname }}"
username: "{{ cluster_username }}"
password: "{{ cluster_password }}"
vserver: svm01
client_match: 0.0.0.0/0
policy_name: default
protocol: any
ro_rule: any
rw_rule: any
super_user_security: any
NFSサービスをEnableにする
「クラスタに対して」SVMを指定してNFSサービスをEnableにします。
#
# enable nfs service.
#
- hosts: AFF
gather_facts: no
tasks:
- name: enable nfs service.
na_ontap_nfs:
state: present
hostname: "{{ cluster_hostname }}"
username: "{{ cluster_username }}"
password: "{{ cluster_password }}"
vserver: svm01
service_state: started
nfsv3: enabled
nfsv4: enabled
nfsv41: enabled
ボリュームを作る
「クラスタに対して」SVMを指定してボリュームを作成します。
#
# make volume.
#
- hosts: AFF
gather_facts: no
tasks:
- name: make volume.
na_ontap_volume:
state: present
hostname: "{{ cluster_hostname }}"
username: "{{ cluster_username }}"
password: "{{ cluster_password }}"
vserver: svm01
name: vol_data1
size: 200
size_unit: gb
space_guarantee: none
aggregate_name: aggr01
volume_security_style: unix
type: rw
is_online: yes
junction_path: /data1
まとめ
ここまでで ONTAPの初期構築に必要な最低限の作業として
- aggregate
- network
- svm
- lif
- service
- volume
が作成出来るかと思います。便利ですので是非試してみてもらえればと思います。
改善すべき点
以下は改善すべき点かと思ってます。
- 安全のため svm内で完結するオペレーションは svmスコープで作業すべき(今はCluster Scope)
- ライセンス登録なんかは繰り返し書くのはダルいのでRole化