Ansible + Juniper + Ciscoの共存環境を作る

ここは [NTTコミュニケーションズ Advent Calendar 2017](https://qiita.com/advent-calendar/2017/nttcom) の13日目の記事です。

はじめまして。d.hayasaka です。 
普段は社内のあっちこっちにいって、SeleniumやAppiumを使って試験の自動化をしたり、はたまたJenkinsおじさんをスーパーcronとして導入したり、自動化屋の仕事をしています。

# はじめに
検証環境を作ってたりするとどうしても、色々なベンダのサーバとネットワーク機器が入り混じって設定が面倒くさい！ということが往々にしておきます。

今回はAnsible ＋ Juniper + Cisco な環境を作って、サーバのデプロイと同じツールでネットワーク機器もある程度自由に触れるようにしたいよね！という方に向けて記事を書いていきたいと思います。

***なんでAnsibleなんだって？rancidでもなんでもできるだろって？***
***いいだろ俺はAnsibleで自動化したいんだ！***

# 前提
- Ansibleサーバ
    - Ubuntu 16.04.3
- Juniper
    - Junos 17.3R1
    - Junos 13.3R1
- Cisco
    - IOS XR Version 6.0.0.02I
- その他
    - 上記ネットワーク機器へのSSH接続
    - 特にCisco機器は事前にSSH接続を済ませる必要あり

# Ansibleとは？
ここを見ていただいてる方にはAnsibleの説明は不要かもしれませんが、気になる方はぜひ下記をご一読ください。
~~読んで僕に教えてくれ！~~

- [Ansible 公式サイト](https://www.ansible.com/)
- [Ansible Advent Calendar2015より](
https://qiita.com/t_nakayama0714/items/fe55ee56d6446f67113c)

# 環境構築
## Ansibleインストール準備

```bash
$ sudo apt-get install software-properties-common
$ sudo apt-add-repository ppa:ansible/ansible
$ sudo apt-get install sshpass
```

## Juniper向け環境構築&Ansibleインストール
Junos向けのRoleが[ansible-galaxy](https://galaxy.ansible.com/Juniper/junos/)に公開されているのでありがたくお借りします。

```bash
$ sudo ansible-galaxy install Juniper.junos
$ sudo apt-get install python-dev libxslt1-dev libssl-dev libffi-dev
$ wget https://bootstrap.pypa.io/get-pip.py -O - |  sudo python
$ sudo pip install junos-eznc
$ sudo pip install junos-netconify
$ sudo pip install ansible 
```

### ansibleバージョン確認
```bash
$ ansible --version
ansible 2.0.0.2
  config file = /etc/ansible/ansible.cfg
  configured module search path = Default w/o overrides
```

2.0以下のバージョンの人は次の手順が追加で必要です。
Ciscoで2.1でコアになったiosモジュールを使うためです。

```bash
# version2.0以下の人だけやってください
$ sudo pip install ansible==2.4.0
```

このままの設定で進んでいくとネットワーク機器への接続時にエラーになるので、以下のように設定変更します。ansibleはデフォルトでHost Keyチェックをするようになっているためです[^1]

```config:/etc/ansible.cfg
[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o StrictHostKeyChecking=no
```

以下の設定でもJuniperのみであれば動作することが可能ですが、Ciscoを動かすときにハマるので設定しない方がいいです。

```bash
-o UserKnownHostsFile=/dev/null
```

## Juniper動作確認
hostsファイルを適当に作ります。

```text:hosts
[junos]
192.168.1.11

[junos:vars]
ansible_ssh_user=admin
ansible_ssh_pass=nttcom
```

続いて、動作用のyamlファイルを書いていきます。
ここでは、show versionを実行してoutputを標準出力に吐くようにしています。

```yaml:junos.yml
- name: juniper show version
  hosts: junos
  gather_facts: no
  sudo: false

  tasks:
    - name: show version
      raw: show version | no-more
      register: result
    - debug: var=result.stdout_lines
```

ファイルが用意できたので早速動かしてみます。

```
$ ansible-playbook -i hosts junos.yml 
~~略~~
TASK [debug] ********************************************************************************************************
ok: [192.168.1.10] => {
    "result.stdout_lines": [
        "Hostname: hoge.hage", 
        "Model: mx80", 
        "Junos: 17.3R1-S1.6", 
~~略~~
```
と無事に動くようになります。

あとはyamlファイルの

```yaml
raw: show version | no-more
```

の行に好きなコマンドを書いて使ってください。[^2]



## Cisco向け環境構築
CiscoのIOS用のios_command等のモジュールはAnsible2.1以降にデフォルトで含まれているので設定不要です。

## Cisco動作確認
Juniperに続ける形でhostsファイルを作ります。

```text:hosts
[junos]
192.168.1.11

[junos:vars]
ansible_ssh_user=admin
ansible_ssh_pass=nttcom

[ios]
192.168.1.101

[ios:vars]
ansible_ssh_user=admin
ansible_ssh_pass=nttcom
ansible_ssh_common_arg="-o diffie-hellman-group1-sha1"
```

今回利用したIOSだとSHA1を求められるのですが、[openssh](https://www.openssh.com/legacy.html)がlegacy optionだと言ってデフォルトだと通してくれないので、仕方なく通してもらう用の設定を入れます。

同じくyamlファイルファイルも作ります。
ここでは、Juniperと同じくshow versionすることにします。

```yaml:ios.yml
- hosts: ios show version
  gather_facts: no
  connection: local

  tasks:
  - name: include ios configuration
    set_fact:
      provider:
        host: "{{ inventory_hostname }}"
        username: "{{ ansible_ssh_user }}"
        password: "{{ ansible_ssh_pass }}"

  - name: show version
    ios_command:
     provider: "{{ provider }}"
     commands:
      - show version
    register: result
  - debug: var=result.stdout_lines
```

[公式ドキュメント](http://docs.ansible.com/ansible/latest/ios_command_module.html)のサンプルを見たところ、Juniperと同じように認証情報はhostsに書いておけばいけるように思えるのですが、弾かれます。[^3]
一旦Ansibleサーバ自身に繋いで、providerを使って認証情報を引き渡すようにしています。また、自身を経由するため、ansible.cfgのHostKeyChecking=noが働かなくなります。

そのため、Cisco機器には事前にsshで接続しておく必要があります。（現時点での課題）[^4]

一通りの準備が完了したので動かしてみます。

```bash
$ ansible-playbook -i hosts ios.yml
~~略~~
TASK [debug] ********************************************************************************************************
ok: [192.168.1.101] => {
    "result.stdout_lines": [
        [
            "Tue Dec 12 10:59:54.630 UTC", 
            "", 
            "Cisco IOS XR Software, Version 6.0.0.02I", 
~~略~~
```

上記のように動作すればOKです。

```yaml
commands:
 - show version
```

の行に好きなコマンドを書いて使ってください。

# まとめ
- Ansible2.1以上を入れる
- ansible-galaxyでJuniper.junosを入れる
- IOSのバージョン次第で、SHA1を通すようにする

# 最後に / 参考
最後までお付き合い頂き、ありがとうございました。

今回は簡易化のため、hostsファイルに直接ユーザ名やパスワードを書いたりしてますし、yamlに関してもsudo（Ansible2.0より非推奨）を使ったりしていますので、その点ご容赦ください。

本記事作成にあたっては、以下のページを参考にさせていただきました。
ありがとうございました。

- [Ansibleをはじめる人に。](https://qiita.com/t_nakayama0714/items/fe55ee56d6446f67113c)
- [Creating and Executing Ansible Playbooks to Manage Devices Running Junos OS](https://www.juniper.net/documentation/en_US/junos-ansible1.0/topics/task/program/junos-ansible-playbooks-creating-executing.html)
- [【Ansible】AnsibleでCisco機器を制御してみる(サンプルコード1 config収集編)](https://qiita.com/onifinger/items/0a05faf175cc818fe447)
- [backlogで古い鍵交換を使ったssh接続](http://aoyagikouhei.blog8.fc2.com/blog-entry-275.html)


[^1]: sshのconfigに記載することでも対応可能です。
[^2]: `| no-more`を忘れるとタイムアウトまで応答がなくなるのでご注意ください。
[^3]: `invalid connection specified, expected connection=local` 
[^4]: ssh接続時にSHA1で引っかかる人は[こちら](http://aoyagikouhei.blog8.fc2.com/blog-entry-275.html)を参考にさせていただくと良いです。