Ansibleでネットワーク設定の切り替えを自動化してみた

# はじめに

在宅で仕事をしていたり、出社していたりと作業環境が変わるとネットワークの設定を切り替えないといけないことがありました。
具体的には、オフィスではDHCPサーバーを使用して自動で割り当てられるルーターのIPアドレスでネットワークに接続しても問題ないけど、自宅のネットワーク環境だとDHCPサーバーを使用して自動で割り当てられるルーターのIPアドレスから接続すると下の図の青線を辿ってしまってVPN接続が遮断されるので、手動で192.168.11.2に切り替える必要があるといった感じです。


![home-network-image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/625911/20977844-8842-703a-6d02-b75d0f6141b2.png)


いちいち環境設定を開いてルーターの番号を変えて...とするのが面倒＆忘れて毎回「何で繋がらないの🤔」となるので自動化できたらいいなーと思いました。

Ansibleは業務で直接触る機会はなかったのですが、プロジェクトの中でも使われていたので存在は知っていて、学習コスト低めで手軽に始められそうだったので今回使ってみました。
普段は主にVue.jsやLaravelを使っているので、なんとなくそういった言語に置き換えつつ理解していきました。

# Ansibleとは

RedHat公式から引用させていただきました。
> Ansible® は、プロビジョニング、構成管理、アプリケーションのデプロイメント、オーケストレーション、その他多くの IT プロセスを自動化する、オープンソースの IT 自動化ツールです。他の単純な管理ツールと違い、Ansible のユーザー (システム管理者、開発者、アーキテクトなど) は、ソフトウェアのインストール、日常的に行うタスクの自動化、インフラストラクチャのプロビジョニング、セキュリティとコンプライアンスの向上、システムへのパッチ適用、組織全体での自動化の共有に、Ansible の自動化を使用できます。

引用元: [Ansible とは](https://www.redhat.com/ja/technologies/management/ansible/what-is-ansible)

なんだか小難しい感じがしますが、要はAnsibleを使えばパッケージの導入だったり環境構築を自動化してくれて、誰が何回実行しても同じ状態になることを保証してくれる（←冪等性というらしいです。）ものだとざっくり理解しました。

# 環境

macOS Monterey バージョン 12.4
ansible [core 2.13.6]

# 使い方

まずはHomebrewを使ってAnsibleをインストールします。

```
% brew install ansible
```
バージョンを確認して、ちゃんと表示されるか見てみましょう。

```
ansible --version
```

AnsibleではPlaybook（手順書）と呼ばれるYAML形式のファイルに実行することを上から順番に書いていきます。

手始めにdemo1.ymlというファイルを作成して、ターミナルでwhoamiコマンドを実行させてみます。
以下は(ほぼ)最小構成のPlaybookファイルです。

```yml:demo1.yml
---
- hosts: localhost
  tasks:
    - name: whoamiコマンドを実行する 
      shell: whoami 
```

1行目でyml形式であることを宣言しています。

Playbookのファイルで必須の項目は下記の二つです。

|  名称| 説明|
|:-:|:-:|
| hosts  | モジュールをどの環境で実行するか指定（実行対象）  |
|tasks   | モジュールを呼び出して実際に実行するさまざまな処理を書いていく  |

Ansibleにはたくさんのモジュールがあり、それを呼び出してパラメータ（：の右側）を渡してあげることで処理を実行させていきます。パラメータは必須です。
プログラミング言語でも関数を呼び出して引数を渡してあげて処理を実行させますが、イメージ的にはモジュール ≒ 関数、パラメータ ≒ 引数といったところでしょうか。
モジュールはplaybookを構成する最小単位で、tasksはmoduleが入っている箱みたいなイメージです。


demo1.ymlではhostsにlocalhostを指定しているのでlocalhostでmoduleが実行されます。
tasksでは、nameに「whoamiコマンドを実行する」という文字列をパラメータとして渡してタスク名を指定しているのと、shellモジュールに 'whoami'というパラメータを渡してコマンドを実行させています。


| 名称 | 役割 |
|:-:|:-:|
|name   | タスク名を指定する  |
|shell   | パラメータで渡されたLinuxコマンドを実行する   |

ではdemo1.ymlを実行させてみます。下記はPlaybook実行コマンドです。

```
% ansible-playbook demo1.yml
```

実行結果

```
% ansible-playbook demo1.yml      
[WARNING]: No inventory was parsed, only implicit localhost is available
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'

PLAY [localhost] **************************************************************************************************************************

TASK [Gathering Facts] ********************************************************************************************************************
ok: [localhost]

TASK [whoamiコマンドを実行する] ***********************************************************************************************************
changed: [localhost]

PLAY RECAP ********************************************************************************************************************************
localhost                  : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
```

ちゃんと処理が通っています🎉


# ネットワーク設定を切り替える


では、本題のネットワーク切り替えに入ります。

手順としては、
① 現在利用しているネットワーク名（SSID）を取得する
②-1 それが家のVPN接続できるSSIDであればIPv4の設定を「手入力」にしてIPv4アドレス・サブネットマスク・ルーターを所定の番号にする
②-2 それ以外(会社で接続しているネットワーク)であればIPv4の設定を「DHCPサーバーを使用する」に変更する


SSID情報を取得するには下記のコマンドを使います。

```
% networksetup -getairportnetwork [ネットワークインターフェイス名]
```

ネットワークインターフェイス名は下記コマンドを実行すれば一覧が表示されるので事前に調べておきます。（これ以降はen0と記載します。）

```
% networksetup -listallhardwareports
```

「SSIDによって設定を変えたい」ということは条件分岐することになるので、この取得した情報を何かしら変数に入れてあげる必要があります。
Ansibleではregisterで実行結果を変数に登録することができます。

ここまでをAnsibleで書くとこうなります。

```yml:change-setting-ipv4.yml
---
- hosts: localhost
  tasks:
    - name: airportnetworknameを取得する
      shell: networksetup -getairportnetwork en0
      register: return_airport_network_name
```

これだけだと実行した時にreturn_airport_network_nameの中身が表示されず確認できないので、debugモジュールを使用して表示させます。引数にmsgを渡すとカスタマイズされたメッセージが出力されます。
末尾の.stdoutでシェルの標準出力を表示させています。
配列の中身の特定の１要素だけ取得したい時に$array[0]とインデックスを指定してあげる感覚でしょうか。

```
- name: 取得したairportnetworknameを表示
      debug:
        msg: "{{ return_airport_network_name.stdout }}"
```
実行結果

```
% ansible-playbook change-setting-ipv4.yml
[WARNING]: No inventory was parsed, only implicit localhost is available
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'

PLAY [localhost] **************************************************************************************************************************

TASK [Gathering Facts] ********************************************************************************************************************
ok: [localhost]

TASK [airportnetworknameを取得する] *******************************************************************************************************
changed: [localhost]

TASK [取得したairportnetworknameを表示] ***************************************************************************************************
ok: [localhost] => {
    "msg": "Current Wi-Fi Network: hogehoge"
}
```


これで必要な情報は揃ったのでいよいよ条件分岐です。

## whenで条件分岐

Ansibleの条件分岐はwhenを使用します。
今回は、return_airport_network_name.stdoutの中にhogehogeが含まれているか否かという条件になるので、in演算子を使います。

| 演算子 | 説明 |
|:-:|:-:|
| A in [X, Y, Z]  | A と同じ値が X, Y, Z の中にあるとき  |
|A not in [X, Y, Z]   | A と同じ値が X, Y, Z の中にないとき  |

詳しくは[公式](https://ansible-workbook.readthedocs.io/ja/latest/jyoken-bunki-handler-loop/jyoken-bunki.html#id8)を見てみてください。

またネットワーク設定のコマンドはそれぞれ以下の通りです。


 ```:固定IPアドレス設定(IP手入力)するshellコマンド
% ./networksetup -setmanual <ネットワークサービスのデバイス名> <IPアドレス> <サブネットマスク> <ルーターのIPアドレス>
```



```:DHCP設定(IP自動払い出し)にするshellコマンド
% ./networksetup -setdhcp <ネットワークサービスのデバイス名>
```

Ansibleで書くとこうなります。

```yml:change-setting-ipv4.yml
    - name: hogehogeならルーターを192.168.11.2に切り替える
      shell: networksetup -setmanual qnoteWi-fi 192.168.11.14 255.255.255.0 192.168.11.2
      when: "hogehoge" in return_airport_network_name.stdout
  
    - name: それ以外の場合はDHCPサーバーを使用する
      shell: networksetup -setdhcp qnoteWi-fi
      when: "hogehoge" not in return_airport_network_name.stdout
```

これで、一通りの処理を書くことができました。
ただ、これだとデバイス名やアドレス名がベタ書きのハードコーディングになってしまってるので、管理しやすいように変数化します。

## 変数を別ファイルにする

今回はchange-setting-ipv4.ymlと同じディレクトリにvars.ymlファイルを作成します。

変数の宣言はいたって簡単で左側にキー、右側にバリューを書いてあげるだけです。これで参照する変数を定義できました。

```yml:vars.yml
---

home:
  network:
    devicename: qnoteWi-fi
    name: hogehoge 
    ipv4address: 192.168.11.14
    subnetmask: 255.255.255.0
    router: 192.168.11.2
```

vars.ymlで定義した変数をchange-setting-ipv4.ymlで使うために、hostsの下に `vars_file:` を記入して外部ファイルを呼び出します。

Ansibleでは変数を参照するときは `{{ }}` で括ります。
ただ、whenの中では `{{}}` で括らず、そのまま書くだけで変数として認識されます。（中括弧で括ってしまうとエラーが出ます。）


```yml:change-setting-ipv4.yml
---
- hosts: localhost
  vars_files:
    - ./vars.yml
  connection: local
  become: no
  tasks:
    - name: airportnetworknameを取得する
      shell: networksetup -getairportnetwork en0
      register: return_airport_network_name
      
    - name: 取得したairportnetworknameを表示
      debug:
        msg: "{{ return_airport_network_name.stdout }}"

    - name: "{{ home.network.name }}ならルーターを{{ home.network.router }}に切り替える"
      shell: networksetup -setmanual {{ home.network.devicename }} {{ home.network.ipv4address }} {{ home.network.subnetmask }} {{ home.network.router }}
      when: home.network.name in return_airport_network_name.stdout
  
    - name: それ以外の場合はDHCPサーバーを使用する
      shell: "networksetup -setdhcp {{ home.network.devicename }}"
      when: home.network.name not in return_airport_network_name.stdout
```

最後にconnectionプラグインとbecomeディレクティブも追加して、よりそれっぽいファイルにします。

5行目の `connection` は接続方法を指定していて、今回のように書くとこのPlaybookは必ずローカル環境で実行されます。（他の環境で実行されてしまうことを防いでくれる。）

6行目の `become` は権限昇格といって、管理者権限ユーザーとしてPlaybookを実行できるか否かを制御しています。
今回は管理者権限ユーザーである必要はないのでnoにしています。

これで、Playbookのファイルと変数ファイルは完成です🎉


```zsh
% ansible-playbook change-setting-ipv4.yml
[WARNING]: No inventory was parsed, only implicit localhost is available
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'

PLAY [localhost] **************************************************************************************************************************

TASK [Gathering Facts] ********************************************************************************************************************
ok: [localhost]

TASK [airportnetworknameを取得する] *******************************************************************************************************
changed: [localhost]

TASK [取得したairportnetworknameを表示] ***************************************************************************************************
ok: [localhost] => {
    "msg": "Current Wi-Fi Network: hogehoge"
}

TASK [hogehogeならルーターを192.168.11.2に切り替える] *******************************************************************************
changed: [localhost]

TASK [それ以外の場合はDHCPサーバーを使用する] *********************************************************************************************
skipping: [localhost]

PLAY RECAP ********************************************************************************************************************************
localhost                  : ok=4    changed=2    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0  
```

今はターミナルを開いてansible実行コマンドを打っていますが、せっかく自動化したならもっと簡単に使えるようにしたいです。

Playbookのファイルをダブルクリックしても実行はできないので `ansible-playbook change-setting-ipv4.yml` というコマンドだけが書かれたシェルスクリプトファイルを新規作成します。
MacOSでは拡張子を`.command` に変更するとダブルクリックだけで実行できるようになるので、下記の名前で作成します。

```zsh
% touch change-setting-network.command
```


実行権限を与えるために下記のコマンドを打ちます。

```
chmod +x change-setting-network.command
```

ファイルをダブルクリックしただけで切り替えをしてくれるようになりました！

![ezgif.com-gif-maker (3).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/625911/4a22e1cf-8f0d-feac-b08b-f69b9cd6cd97.gif)


# 参考
[Ansible ワークブック](https://ansible-workbook.readthedocs.io/ja/latest/index.html)
[【TIPS】MacBookインターネット接続方法「有線/無線(WiFi)対応」【networksetupコマンド編】](https://engineer-lifestyle-blog.com/lifestyle/mac/wired-wireless-networksetup-using-commandline-tool/)
[MacのWifi周りのコマンドの紹介](https://www.swakamoto.com/entry/2013/09/24/014540)
[プレイブックの基本](https://zenn.dev/y_mrok/books/ansible-no-tsukaikata/viewer/chapter8)
[macOS でシェルスクリプトをアイコンのダブルクリックで起動する](https://maku77.github.io/mac/command-file.html)

# おわりに

Ansibleのメリットとして学習コストが低いことがよく挙げられていますが、たしかにYAML形式のファイルはとっつきやすく、そこまで時間もかからずに自動化することができました。
今回はネットワークを切り替えるだけの簡単なことしかしていませんが、それでもAnsibleで自動化するのって便利だなーと感じたのでもっと深く勉強してみたいと思います。
Ansibleちょっと触ってみたいな〜という方の参考になれば幸いです。