はじめに
Redhatさんが定期的に開催しているAnsibleのもくもく会に参加してきたので、その内容のまとめです。
※この勉強会は「ブログ枠」という参加枠があり、そこで参加した人は内容をレポートすることになっています。(要は許可とってます)
公開されている資料はこちらです。
a10用Ansibleモジュールはこちら
※若干注意事項あり(後述)
A10 vThunderとは
今回の題材はA10 Network社が提供しているThunder ADC(Application Delivery Controller)という機材に対するAnsibleの操作になります。
そもそもこのADCが何なのかすら知らなかったのですが、基本的にはロードバランサーと同じ役割を果たすようです。ただし、セッションをパススルーするのではなく、自身がアプリケーションの応答を返すことができる点が異なるとのこと。今回の資料も通常のロードバランサー的な使い方になっています。
From F5
ADCとは「Application Delivery Controller」の略で、ロードバランサ(負荷分散装置)の機能をさらに高度化したものです。
ロードバランサはリバース プロキシの一種であり、クライアントからのリクエストを複数のサーバに振り分ける処理を行うことで、サーバの負荷を分散します。従来のロードバランサは、クライアントとサーバとの間でやり取りされるパケットを、単純に中継するだけのものでした。そのため、TCPコネクションの確立やHTTP/HTTPSのセッションは、クライアントとサーバの間で直接行われるのが一般的でした。
これに対してADCでは、アプリケーション レイヤまでカバーしたフル プロキシとして機能し、クライアントからのリクエストに対し、サーバの代理として応答することが可能になっています。サーバとの間は、クライアントとの間とは別のコネクション/セッションを張ります。このようなアーキテクチャを採用することで、従来のオードバランサに比べて幅広い機能の実現が可能になっています。
構成と概要
今回はvThunderはGatewayモードで構成し、バックエンドにあるHTTPサーバ2台に対する負荷分散を設定します。vThunderは2つのネットワークに接続されていて、それぞれVLANが異なります。
vThunder側で設定する内容としては以下の通り。
- インターフェース設定(クライアント側と、ウェブサーバ側にそれぞれVLANに対応するインターフェースを作成する)
- Service Groupの構成(負荷分散の対象となるウェブサーバをグルーピングしたもの)
- Virtual Serverの設定(外部からアクセスされるIPアドレスの設定と、Service Groupとの関連付け)
- アクセス確認
インターフェース設定
ログインと環境設定
Ansibleが実行可能なサーバにログインし、hostsファイルを作成する。
[all:vars]
a10_username=admin
a10_password=a10
a10_port=443
[vThunder]
10.255.0.1
ここでは、対象ホストの情報に加えて、ログイン情報を変数として定義しています。
VLAN作成
次に各インターフェースにVLAN IDを設定します。設定にはa10社が提供する「a10_network_vlan」というモジュールを使用します。
これを含め、a10の提供するモジュールは下記githubにあります。
https://github.com/a10networks/a10-ansible
※ただし、一部修正する必要があるらしく、修正ポイントは同様にgithubに記載されているそうです。
https://github.com/kishizuka4989/ansible_training_a10_thuner_adc/blob/master/1.0-adc-primer/201909_Ansible_Guide.pdf
---
- hosts: 10.255.0.1
connection: local
gather_facts: no
vars:
a10_host: "10.255.0.1"
tasks:
- name: Create VLAN
a10_network_vlan:
a10_host: "{{ a10_host }}"
a10_port: "{{ a10_port }}"
a10_username: "{{ a10_username }}"
a10_password: "{{ a10_password }}"
vlan_num: "10"
untagged_eth_list:
- untagged_ethernet_start: "1"
untagged_ethernet_end: "1"
ve: "10"
state: present
partition: shared
gather_facts: noについては、vThunderに対する命令はvThunder上ではなく、別のマシンからRESTAPI(aXAPI)で実行されるため、factの収集ができないために必要になります。
a10_network_vlanの下で指定されているパラメータはモジュール特有のパラメータで、設定するインターフェースのID(untagged_eth_list)および設定するVLAN ID(vlan_num)、および作成される仮想インターフェースID(ve, vlan_numとイコールである必要があるらしい)です。
またstateはこの設定が構成された状態にするかどうか(presentはする)、partitionはvThunder上の論理パーティションを指定します(sharedはデフォルトパーティション)。stateの意味合いが少しわかりにくいですが、後でdeleteするときにわかります。ちなみに、各モジュールで設定可能なパラメータはansible-doc a10_network_vlan
で確認できます。
このPlaybookを実行してみます。
[root@ansible playbook]# ansible-playbook -i hosts a10_network_vlan_create.yaml
PLAY [10.255.0.1] ****************************************************************************************************************************
TASK [Create VLAN] ***************************************************************************************************************************
changed: [10.255.0.1]
PLAY RECAP ***********************************************************************************************************************************
10.255.0.1 : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
そのあと、vThunder上で設定を確認します。
vThunder#show running-config
vlan 10
untagged ethernet 1
router-interface ve 10
!
interface ethernet 1
!
interface ve 10
このVLANを削除する場合は、stateをabsentに変えればよいです。
[root@ansible playbook]# cp a10_network_vlan_create.yaml a10_network_vlan_delete.yaml
[root@ansible playbook]# vi a10_network_vlan_delete.yaml
[root@ansible playbook]# diff a10_network_vlan_create.yaml a10_network_vlan_delete.yaml
20c20
< state: present
---
> state: absent
[root@ansible playbook]#
[root@ansible playbook]# ansible-playbook -i hosts a10_network_vlan_delete.yaml
PLAY [10.255.0.1] ****************************************************************************************************************************
TASK [Create VLAN] ***************************************************************************************************************************
changed: [10.255.0.1]
PLAY RECAP ***********************************************************************************************************************************
10.255.0.1 : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
これでvThunder上から構成が削除されます。
vThunder#show running-config
interface management
ip address 10.255.0.1 255.255.0.0
ip default-gateway 10.255.255.1
!
interface ethernet 1
!
interface ethernet 2
!
!
sflow setting local-collection
!
sflow collector ip 127.0.0.1 6343
!
!
end
元に戻すついでに、複数VLANを設定するPlaybookを作成します。
---
- hosts: 10.255.0.1
connection: local
gather_facts: no
vars:
a10_host: "10.255.0.1"
tasks:
- name: Create VLAN
a10_network_vlan:
a10_host: "{{ a10_host }}"
a10_port: "{{ a10_port }}"
a10_username: "{{ a10_username }}"
a10_password: "{{ a10_password }}"
vlan_num: "{{ item.vlan_num }}"
untagged_eth_list:
- untagged_ethernet_start: "{{ item.untagged_ethernet_start }}"
untagged_ethernet_end: "{{ item.untagged_ethernet_end }}"
ve: "{{ item.ve }}"
state: present
partition: shared
with_items:
- { vlan_num: 10, untagged_ethernet_start: 1, untagged_ethernet_end: 1, ve: 10 }
- { vlan_num: 20, untagged_ethernet_start: 2, untagged_ethernet_end: 2, ve: 20 }
with_itemsを使うことでLoopを使うことができます。
[root@ansible playbook]# ansible-playbook -i hosts a10_network_vlans_create.yaml
PLAY [10.255.0.1] ****************************************************************************************************************************
TASK [Create VLAN] ***************************************************************************************************************************
changed: [10.255.0.1] => (item={'vlan_num': 10, 'untagged_ethernet_start': 1, 'untagged_ethernet_end': 1, 've': 10})
changed: [10.255.0.1] => (item={'vlan_num': 20, 'untagged_ethernet_start': 2, 'untagged_ethernet_end': 2, 've': 20})
PLAY RECAP ***********************************************************************************************************************************
10.255.0.1 : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
実行するとTask内で2回の変更が実行されていることがわかります。変更をvThunder側で確認します。ちなみにOSはCISCOのIOSベースなのか似せているのか、同じようなコマンドが通ります。
vThunder#terminal length 0
vThunder#show running-config
!Current configuration: 399 bytes
!Configuration last updated at 11:59:47 IST Fri Sep 13 2019
!Configuration last saved at 03:07:57 IST Fri Sep 13 2019
!64-bit Advanced Core OS (ACOS) version 4.1.4-GR1, build 78 (Jan-18-2019,16:02)
!
multi-config enable
!
terminal idle-timeout 0
!
vlan 10
untagged ethernet 1
router-interface ve 10
!
vlan 20
untagged ethernet 2
router-interface ve 20
!
interface management
ip address 10.255.0.1 255.255.0.0
ip default-gateway 10.255.255.1
!
interface ethernet 1
!
interface ethernet 2
!
interface ve 10
!
interface ve 20
!
!
sflow setting local-collection
!
sflow collector ip 127.0.0.1 6343
!
!
end
またこれもCiscoスイッチ同様、このままでは設定内容が再起動で失われてしまうため、runnning-configの内容を不揮発性メモリ上の設定(startup-config)に保存する必要があり、これには専用のモジュールが存在します。
ちょっと冗長な気がしますが、それ用のPlaybookを以下のように作成します。
---
- hosts: 10.255.0.1
connection: local
gather_facts: no
vars:
a10_host: "10.255.0.1"
tasks:
- name: Write memory
a10_write_memory:
a10_host: "{{ a10_host }}"
a10_username: "{{ a10_username }}"
a10_password: "{{ a10_password }}"
a10_port: "{{ a10_port }}"
state: present
partition: all
同様に実行します。
[root@ansible playbook]# ansible-playbook -i hosts a10_write_memory.yaml
PLAY [10.255.0.1] ****************************************************************************************************************************
TASK [Write memory] **************************************************************************************************************************
changed: [10.255.0.1]
PLAY RECAP ***********************************************************************************************************************************
10.255.0.1 : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
仮想インターフェース作成
次に物理インターフェースに対して、各VLAN用の仮想インターフェースを作成し、IPアドレスを付与します。a10_interface_ve_ipというモジュールを使用します。
---
- hosts: 10.255.0.1
connection: local
gather_facts: no
vars:
a10_host: "10.255.0.1"
tasks:
- name: Add IP to VE
a10_interface_ve_ip:
a10_host: "{{ a10_host }}"
a10_port: "{{ a10_port }}"
a10_username: "{{ a10_username }}"
a10_password: "{{ a10_password }}"
ve_ifnum: "{{ item.ve_ifnum }}"
address_list:
- ipv4_address: "{{ item.ipv4_address }}"
ipv4_netmask: "{{ item.ipv4_netmask }}"
state: present
partition: shared
with_items:
- { ve_ifnum: "10", ipv4_address: 192.168.1.254, ipv4_netmask: 255.255.255.0 }
- { ve_ifnum: "20", ipv4_address: 192.168.2.254, ipv4_netmask: 255.255.255.0 }
- name: Write memory
a10_write_memory:
a10_host: "{{ a10_host }}"
a10_username: "{{ a10_username }}"
a10_password: "{{ a10_password }}"
a10_port: "{{ a10_port }}"
state: present
partition: all
ve_ifnumは先ほど作成したveの番号です。またどうせ実行するため、Write Memoryモジュールの実行も別タスクとして入れてあります。これを実行すると、
[root@ansible playbook]# ansible-playbook -i hosts a10_interface_ve_ip_create.yaml
PLAY [10.255.0.1] ****************************************************************************************************************************
TASK [Add IP to VE] **************************************************************************************************************************
changed: [10.255.0.1] => (item={'ve_ifnum': '10', 'ipv4_address': '192.168.1.254', 'ipv4_netmask': '255.255.255.0'})
changed: [10.255.0.1] => (item={'ve_ifnum': '20', 'ipv4_address': '192.168.2.254', 'ipv4_netmask': '255.255.255.0'})
TASK [Write memory] **************************************************************************************************************************
changed: [10.255.0.1]
PLAY RECAP ***********************************************************************************************************************************
10.255.0.1 : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
vThunder上で確認します。
vThunder#show interfaces brief
Port Link Dupl Speed Trunk Vlan MAC IP Address IPs Name
------------------------------------------------------------------------------------
mgmt Up Full 1000 N/A N/A 2cc2.6064.4ec4 10.255.0.1/16 1
1 Disb None None none 10 2cc2.603c.b0de 0.0.0.0/0 0
2 Disb None None none 20 2cc2.6044.0a45 0.0.0.0/0 0
ve10 Down N/A N/A N/A 10 2cc2.603c.b0de 192.168.1.254/24 1
ve20 Down N/A N/A N/A 20 2cc2.6044.0a45 192.168.2.254/24 1
Global Throughput:0 bits/sec (0 bytes/sec)
Throughput:0 bits/sec (0 bytes/sec)
この時点ではVEは有効になっていないため、有効化するPlaybookを用意します。a10_interface_ethernetというモジュールを使用します。Ansibleの話ではないですが、VE自体ではなく、そのベースになっているインターフェースを有効化するところがちょっと気になりました。
---
- hosts: 10.255.0.1
connection: local
gather_facts: no
vars:
a10_host: "10.255.0.1"
tasks:
- name: Enable ethernet interfaces
a10_interface_ethernet:
a10_host: "{{ a10_host }}"
a10_port: "{{ a10_port }}"
a10_username: "{{ a10_username }}"
a10_password: "{{ a10_password }}"
ifnum: "{{ item.ifnum }}"
action: enable
state: present
partition: shared
with_items:
- { ifnum: 1 }
- { ifnum: 2 }
- name: Write memory
a10_write_memory:
a10_host: "{{ a10_host }}"
a10_username: "{{ a10_username }}"
a10_password: "{{ a10_password }}"
a10_port: "{{ a10_port }}"
state: present
partition: all
このPlaybookを実行します。
[root@ansible playbook]# ansible-playbook -i hosts a10_interfaces_ethernet_enable.yaml
PLAY [10.255.0.1] ****************************************************************************************************************************
TASK [Enable ethernet interfaces] ************************************************************************************************************
changed: [10.255.0.1] => (item={'ifnum': 1})
changed: [10.255.0.1] => (item={'ifnum': 2})
TASK [Write memory] **************************************************************************************************************************
changed: [10.255.0.1]
PLAY RECAP ***********************************************************************************************************************************
10.255.0.1 : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
これでVEの方も有効になります。
ただし自分の場合、1回の実行ではve10しかUpにならず、2回目流すとどちらもUpになりました。(2回目の実行の時はchanged=1(write_memory分のみ)だったが)
vThunder#show interfaces brief
Port Link Dupl Speed Trunk Vlan MAC IP Address IPs Name
------------------------------------------------------------------------------------
mgmt Up Full 1000 N/A N/A 2cc2.6064.4ec4 10.255.0.1/16 1
1 Up Full 10000 none 10 2cc2.603c.b0de 0.0.0.0/0 0
2 Down None None none 20 2cc2.6044.0a45 0.0.0.0/0 0
ve10 Up N/A N/A N/A 10 2cc2.603c.b0de 192.168.1.254/24 1
ve20 Down N/A N/A N/A 20 2cc2.6044.0a45 192.168.2.254/24 1
Global Throughput:0 bits/sec (0 bytes/sec)
Throughput:0 bits/sec (0 bytes/sec)
vThunder#show interfaces brief
Port Link Dupl Speed Trunk Vlan MAC IP Address IPs Name
------------------------------------------------------------------------------------
mgmt Up Full 1000 N/A N/A 2cc2.6064.4ec4 10.255.0.1/16 1
1 Up Full 10000 none 10 2cc2.603c.b0de 0.0.0.0/0 0
2 Up Full 10000 none 20 2cc2.6044.0a45 0.0.0.0/0 0
ve10 Up N/A N/A N/A 10 2cc2.603c.b0de 192.168.1.254/24 1
ve20 Up N/A N/A N/A 20 2cc2.6044.0a45 192.168.2.254/24 1
Service Groupの構成
Serverの追加
初めにService Groupに含める負荷分散対象のサーバを定義します。
---
- hosts: 10.255.0.1
connection: local
gather_facts: no
vars:
a10_host: "10.255.0.1"
tasks:
- name: Configure real server
a10_slb_server:
a10_host: "{{ a10_host }}"
a10_port: "{{ a10_port }}"
a10_username: "{{ a10_username }}"
a10_password: "{{ a10_password }}"
name: "{{ item.name }}"
host: "{{ item.host }}"
port_list:
- port_number: "{{ item.port_number }}"
protocol: "{{ item.protocol }}"
state: present
partition: shared
with_items:
- { name: "s1", host: "192.168.2.1", port_number: 80, protocol: "tcp" }
- { name: "s2", host: "192.168.2.2", port_number: 80, protocol: "tcp" }
- name: Write memory
a10_write_memory:
a10_host: "{{ a10_host }}"
a10_username: "{{ a10_username }}"
a10_password: "{{ a10_password }}"
a10_port: "{{ a10_port }}"
state: present
partition: all
負荷分散の対象となるIPアドレス(192.168.2.1,192.168.2.2)、ポートの組み合わせにs1,s2という名前を付けています。
[root@ansible playbook]# ansible-playbook -i hosts a10_slb_servers_create.yaml
PLAY [10.255.0.1] ****************************************************************************************************************************
TASK [Configure real server] *****************************************************************************************************************
changed: [10.255.0.1] => (item={'name': 's1', 'host': '192.168.2.1', 'port_number': 80, 'protocol': 'tcp'})
changed: [10.255.0.1] => (item={'name': 's2', 'host': '192.168.2.2', 'port_number': 80, 'protocol': 'tcp'})
TASK [Write memory] **************************************************************************************************************************
changed: [10.255.0.1]
PLAY RECAP ***********************************************************************************************************************************
10.255.0.1 : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
vThunder側で確認します。
vThunder#show slb server
Total Number of Servers configured: 2
Total Number of Services configured: 2
Current = Current Connections, Total = Total Connections
Fwd-pkt = Forward packets, Rev-pkt = Reverse packets
Service Current Total Fwd-pkt Rev-pkt Peak-conn State
---------------------------------------------------------------------------------------
s1:80/tcp 0 0 0 0 0 Up
s1: Total 0 0 0 0 0 Up
s2:80/tcp 0 0 0 0 0 Up
s2: Total 0 0 0 0 0 Up
Service Group作成
---
- hosts: 10.255.0.1
connection: local
gather_facts: no
vars:
a10_host: "10.255.0.1"
tasks:
- name: Configure service group
a10_slb_service_group:
a10_host: "{{ a10_host }}"
a10_port: "{{ a10_port }}"
a10_username: "{{ a10_username }}"
a10_password: "{{ a10_password }}"
name: "sg1"
protocol: "tcp"
lb_method: "round-robin"
member_list:
- name: "s1"
port: "80"
- name: "s2"
port: "80"
state: present
partition: shared
- name: Write memory
a10_write_memory:
a10_host: "{{ a10_host }}"
a10_username: "{{ a10_username }}"
a10_password: "{{ a10_password }}"
a10_port: "{{ a10_port }}"
state: present
partition: all
sg1という名前でService Groupを作成し、そこに先ほどのs1,s2をメンバーとして追加します。同時に分散方式(ここではround-robin)も定義します。
[root@ansible playbook]# ansible-playbook -i hosts a10_slb_service_group_create.yaml
PLAY [10.255.0.1] ****************************************************************************************************************************
TASK [Configure service group] ***************************************************************************************************************
changed: [10.255.0.1]
TASK [Write memory] **************************************************************************************************************************
changed: [10.255.0.1]
PLAY RECAP ***********************************************************************************************************************************
10.255.0.1 : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
vThunder側で確認します。
vThunder#show slb service-group
Total Number of Service Groups configured: 1
Current = Current Connections, Total = Total Connections
Fwd-p = Forward packets, Rev-p = Reverse packets
Peak-c = Peak connections
Service Group Name
Service Current Total Fwd-p Rev-p Peak-c
-----------------------------------------------------------------------------------
*sg1 State: All Up
s1:80 0 0 0 0 0
s2:80 0 0 0 0 0
NAT設定
---
- hosts: 10.255.0.1
connection: local
gather_facts: no
vars:
a10_host: "10.255.0.1"
tasks:
- name: Configure NAT Pool
a10_ip_nat_pool:
a10_host: "{{ a10_host }}"
a10_port: "{{ a10_port }}"
a10_username: "{{ a10_username }}"
a10_password: "{{ a10_password }}"
pool_name: "p1"
start_address: "192.168.2.100"
end_address: "192.168.2.100"
netmask: "/24"
state: present
partition: shared
- name: Write memory
a10_write_memory:
a10_host: "{{ a10_host }}"
a10_username: "{{ a10_username }}"
a10_password: "{{ a10_password }}"
a10_port: "{{ a10_port }}"
state: present
partition: all
Service Groupに関連付ける仮想IPアドレスのプールをp1という名前で作成します。ここではIPアドレスは1つしか入れていません。
[root@ansible playbook]# ansible-playbook -i hosts a10_ip_nat_pool_create.yaml
PLAY [10.255.0.1] ****************************************************************************************************************************
TASK [Configure NAT Pool] ********************************************************************************************************************
changed: [10.255.0.1]
TASK [Write memory] **************************************************************************************************************************
changed: [10.255.0.1]
PLAY RECAP ***********************************************************************************************************************************
10.255.0.1 : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
vThunder側で確認。
vThunder#show ip nat pool
Pool Name Start Address End Address Mask Gateway Vrid
------------------------------------------------------------------------------------------------
p1 192.168.2.100 192.168.2.100 /24 0.0.0.0 default
Total IP NAT Pools: 1
Virtual Serverの設定
---
- hosts: 10.255.0.1
connection: local
gather_facts: no
vars:
a10_host: "10.255.0.1"
tasks:
- name: Configure virtual server
a10_slb_virtual_server:
a10_host: "{{ a10_host }}"
a10_port: "{{ a10_port }}"
a10_username: "{{ a10_username }}"
a10_password: "{{ a10_password }}"
name: "vip1"
ip_address: "192.168.1.100"
port_list:
- port_number: "80"
protocol: "http"
service_group: "sg1"
pool: "p1"
state: present
partition: shared
- name: Write memory
a10_write_memory:
a10_host: "{{ a10_host }}"
a10_username: "{{ a10_username }}"
a10_password: "{{ a10_password }}"
a10_port: "{{ a10_port }}"
state: present
partition: all
vip1というVirtual Serverをip_addressというIPアドレスで設定し、関連付けるService Groupとしてsg1を、バックエンドのIPアドレス用プールとしてp1を指定しています。
[root@ansible playbook]# ansible-playbook -i hosts a10_slb_virtual_server_http_create.yaml
PLAY [10.255.0.1] ****************************************************************************************************************************
TASK [Configure virtual server] **************************************************************************************************************
changed: [10.255.0.1]
TASK [Write memory] **************************************************************************************************************************
changed: [10.255.0.1]
PLAY RECAP ***********************************************************************************************************************************
10.255.0.1 : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
vThunder側で確認します。
vThunder#show slb virtual-server
Total Number of Virtual Services configured: 1
Virtual Server Name IP Current Total Request Response Peak
Service-Group Service connection connection packets packets connection
----------------------------------------------------------------------------------------
*vip1 192.168.1.100 All Up
port 80 http 0 0 0 0 0
sg1 80/http 0 0 0 0 0
Total received conn attempts on this port: 0
アクセス確認
この状態で、Virtual Serverに対してアクセスすると、
タイミングによってアクセス先が異なることがわかる。分散状況に関しては、vThunder側で確認できる。きれいに等分されていることがわかる。
vThunder#show slb virtual-server
Total Number of Virtual Services configured: 1
Virtual Server Name IP Current Total Request Response Peak
Service-Group Service connection connection packets packets connection
----------------------------------------------------------------------------------------
*vip1 192.168.1.100 All Up
port 80 http 1 5 37 131 0
sg1 80/http 1 4 31 69 0
Total received conn attempts on this port: 5
感想
ネットワーク機器がそれほどわかっていなくても普通に構成変更できてしまう。
A10さんの用意した教材が非常によくまとまっているのもあるが、Ansibleはやはりわかりやすい。あとA10のモジュールのつくりもちょっと冗長だけど、その分考え方は単純な気がする。
あと、vThunderは初めて知ったけど、評価版もあるらしく、機会があれば実業務でも使ってみたい。