zaki work log

作業ログやら生活ログやらなんやら

[Ansible] Arista EOSのcEOS-labコンテナにAnsibleでeos_factsするまでいろいろハマったこと

一昨日つくったcEOS-labコンテナにAnsibleのeosモジュール使ってアクセスする。
使うモジュールはひとまず情報収集のeos_factsを使用。

zaki-hmkc.hatenablog.com

ネットワーク初心者の自分にはハマりポイントが大量にあったので色々と備忘録。
ポイントは「相手がコンテナであることは忘れろ。Linuxでも無い」だった笑

答え

コンテナ内22/TCPのpublish設定

(未確認だけど原理的に)無くても(コンテナのIPアドレスチェックして直接接続にいけば多分)動くっちゃ動く(と思う)けど分かりやすいように。

$ docker create --name=ceos-4-21 --privileged -p 25022:22 -e CEOS=1 -e container=docker -e EOS_PLATFORM=ceoslab -e SKIP_ZEROTOUCH_BARRIER_IN_SYSDBINIT=1 -e ETBA=1 -e INTFTYPE=eth -i -t ceos:4.21.12M /sbin/init

こんな感じでコンテナを作り直す。(25022は適当)
要はcEOS-Labコンテナ内ではsshdが動いてるので、それを外からアクセスできるようにする。

[zaki@cloud-dev dev]$ docker exec -it ceos-4-21 bash
bash-4.3# lsof -i:22
COMMAND  PID USER   FD   TYPE  DEVICE SIZE/OFF NODE NAME
xinetd  1412 root    5u  IPv4 3691233      0t0  TCP *:ssh (LISTEN)
xinetd  1412 root    6u  IPv6 3691234      0t0  TCP *:ssh (LISTEN)
sshd    2981 root    3u  IPv4 3729852      0t0  TCP 172.17.0.2:ssh->172.17.0.1:directplaysrvr (ESTABLISHED)
sshd    2981 root    4u  IPv4 3729852      0t0  TCP 172.17.0.2:ssh->172.17.0.1:directplaysrvr (ESTABLISHED)
bash-4.3# ss -anptl
(省略)

ちなみにcEOS-lab 4.21.12Mの土台はFedora 18だった

bash-4.3# cat /etc/redhat-release
Fedora release 18 (Spherical Cow)

cEOS-lab側設定

Cliでアクセスするユーザー作成&enableが出来る権限設定を行う。

bash-4.3# Cli
localhost>enable
localhost#configure
localhost(config)#username lab-user privilege 15 secret p@ssword

これで、外部からsshしつつenableで昇格も可能。

[zaki@cloud-dev ~]$ ssh lab-user@localhost -p 25022
Password: 
localhost>enable 
localhost#

paramiko

paramikoが必要なのでAnsibleコントロールノードにpipでインストールする。
(別件で入れてたので遭遇しなかったけど、無いと「paramikoがないよ」ってエラーになる)

TASK [gather facts] 
fatal: [localhost]: FAILED! => changed=false 
  ansible_facts:
    discovered_interpreter_python: /usr/bin/python
  msg: 'paramiko is not installed: No module named ''paramiko'''

Ansible

playbook

- hosts: eos
  gather_facts: no

  tasks:
  - name: gather facts
    eos_facts:
      gather_subset: all
    register: eosfacts

  - debug:
      msg: "{{eosfacts}}"

inventory

[eos]
localhost

group_vars/eos.yml

ansible_connection: network_cli
ansible_network_os: eos
ansible_port: 25022

ansible_user: lab-user
ansible_password: p@ssword

ansible_become: yes
ansible_become_method: enable

あとは必要に応じてansible.cfgstdout_callback = yamlなど。

ファイルは一式GitHubに置いてる。

github.com

実行

$ ansible-playbook -i inventory.ini playbook.yml

PLAY [eos] *****************************************************************************************************************************************************

TASK [gather facts] ********************************************************************************************************************************************
[WARNING]: default value for `gather_subset` will be changed to `min` from `!config` v2.11 onwards
ok: [localhost]

TASK [debug] ***************************************************************************************************************************************************
ok: [localhost] => 
  msg:
    ansible_facts:
      ansible_net_all_ipv4_addresses: []
      ansible_net_all_ipv6_addresses: []
      ansible_net_api: cliconf
      ansible_net_config: |-
        ! Command: show running-config
        ! device: localhost (cEOSLab, EOS-4.21.12M-18662095.42112M (engineering build))
        !
        transceiver qsfp default-mode 4x10G
        !
        spanning-tree mode mstp
        !
        no aaa root
        !
        username lab-user privilege 15 secret sha512 <*** hash-value ***>
        username test01 privilege 10 secret sha512 <*** hash-value ***>
        username test02 privilege 15 secret sha512 <*** hash-value ***>
        !
        no ip routing
        !
        end
      ansible_net_filesystems:
      - 'file:'
      - 'flash:'
      - 'system:'
      ansible_net_fqdn: localhost
      ansible_net_gather_network_resources: []
      ansible_net_gather_subset:
      - hardware
      - default
      - interfaces
      - config
      ansible_net_hostname: localhost
      ansible_net_interfaces: {}
      ansible_net_memfree_mb: 1179.5234375
      ansible_net_memtotal_mb: 7802.80859375
      ansible_net_model: cEOSLab
      ansible_net_neighbors: {}
      ansible_net_python_version: 2.7.5
      ansible_net_serialnum: ''
      ansible_net_system: eos
      ansible_net_version: 4.21.12M-18662095.42112M (engineering build)
      ansible_network_resources: {}
      discovered_interpreter_python: /usr/bin/python
    changed: false
    failed: false
    warnings:
    - default value for `gather_subset` will be changed to `min` from `!config` v2.11 onwards

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

usernameのところは、作成したユーザーがリストされている。

勘違いしてたこと

コンテナなんだからconnection=docker使う?

昨日書いた記事はこれの準備のつもりだったけど、ハズレでした。

ちなみにansible_connection=dockerで接続しようとするとEOSとして扱おうとしてくれず、そもそも組み合わせ不可となる。

TASK [gather facts] 
fatal: [localhost]: FAILED! => changed=false 
  msg: Connection type docker is not valid for this module

正解は

ansible_connection: network_cli
ansible_network_os: eos

参考

docs.ansible.com

tekunabe.hatenablog.jp

ssh接続用のユーザーをuseraddで用意する

デフォルトだと/etc/passwdを見る限り接続に使えそうなユーザーがいないので、useraddで接続用ユーザーを作ったのですが、これもハズレ。

bash-4.3# useradd -m ansible
bash-4.3# passwd ansible

これで作ったアカウント情報をAnsibleで設定して実行しても

TASK [gather facts] ********************************************************************************************************************************************
fatal: [localhost]: FAILED! => changed=false 
  ansible_facts:
    discovered_interpreter_python: /usr/bin/python
  msg: unable to set terminal parameters

って感じ。
これ、-vvv付けても追加されるスタックトレース

The full traceback is:
WARNING: The below traceback may *not* be related to the actual failure.
  File "/tmp/ansible_eos_facts_payload_cxWm95/ansible_eos_facts_payload.zip/ansible/module_utils/network/common/network.py", line 229, in get_capabilities
    capabilities = Connection(module._socket_path).get_capabilities()
  File "/tmp/ansible_eos_facts_payload_cxWm95/ansible_eos_facts_payload.zip/ansible/module_utils/connection.py", line 185, in __rpc__
    raise ConnectionError(to_text(msg, errors='surrogate_then_replace'), code=code)
fatal: [localhost]: FAILED! => changed=false 

って感じでホントよくわからんかったけど、msgで出ているログは多分ここ。

github.com

    def on_open_shell(self):
        try:
            for cmd in (b'terminal length 0', b'terminal width 512'):
                self._exec_cli_command(cmd)
        except AnsibleConnectionFailure:
            raise AnsibleConnectionFailure('unable to set terminal parameters')

というのも、useraddで作ったユーザーでsshで入ってシェルのhistory(Ctrl-p)を見ると、terminal length 0を叩いた形跡があり、でもterminalなんてコマンドは無いのでsshのレイヤーでは接続に成功してるけどAnsibleの実行に失敗してることがわかる。
(ちなみに、コンテナ内の/var/log/messagesを見ても、Ansible実行のタイミングでsshstatus=0で終了している)

[zaki@cloud-dev ~]$ ssh ansible@localhost -p 25022
Password: 
Last login: Mon Sep 21 01:06:11 2020 from 172.17.0.1

Arista Networks EOS shell

[ansible@localhost ~]$
[ansible@localhost ~]$ history | grep terminal
    1  terminal length 0
    3  terminal length 0
    9  terminal length 0
   10  history | grep terminal
[ansible@localhost ~]$ terminal length 0
-bash: terminal: command not found

じゃあこのコマンドってどこで実行するのかっていうと、Cliのシェル上で実行するのね。

[ansible@localhost ~]$ Cli
localhost>terminal length 0
Pagination disabled.
localhost>terminal length 512
Pagination set to 512 lines.

つまりAnsibleの実行も、接続したときにCliが起動してるのを期待してるというわけ。
そこで、最初からCliが起動してればいいじゃんということで、chshでログインシェルを変更してみる。

bash-4.3# chsh ansible
Changing shell for ansible.
New shell [/bin/bash]: /usr/bin/Cli
Shell changed.
bash-4.3# chsh ansible
Changing shell for ansible.

これで素のsshでもAnsible実行でも、「作成したユーザーでssh接続しつつEOSのシェル(Cli)でコマンドを打てる」というところまでは動く。

ただし、、、

[zaki@cloud-dev ~]$ ssh ansible@localhost -p 25022
Password: 
Last login: Mon Sep 21 01:14:24 2020 from 172.17.0.1
localhost>show version
 cEOSLab
Hardware version:    
Serial number:       
System MAC address:  Not available

Software image version: 4.21.12M-18662095.42112M (engineering build)
Architecture:           i386
Internal build version: 4.21.12M-18662095.42112M
Internal build ID:      3266415c-55a9-403c-a020-777efe5ff9aa

cEOS tools version: 1.1

Uptime:                 0 weeks, 5 days, 11 hours and 51 minutes
Total memory:           7990076 kB
Free memory:            1296380 kB

localhost>enable 
% Cannot authenticate unknown uid 1000

こんな感じでenableで特権を得られなかった。

ansible-playbookでもこんな感じ。

TASK [gather facts] 
fatal: [localhost]: FAILED! => changed=false 
  ansible_facts:
    discovered_interpreter_python: /usr/bin/python
  msg: 'unable to elevate privilege to enable mode, at prompt [b''\nlocalhost>''] with error: failed to elevate privilege to enable mode still at prompt [b''\nlocalhost>'']'

作成したユーザーの昇格設定がわからなかったのと、rootユーザーをchshしても回避できそうだけど、さすがにその設定は不都合が多すぎの予感がしたので別の方法が無いかと思った。

参考

tekunabe.hatenablog.jp

github.com

github.com


正解は、OSのユーザーを作成するのでなく、EOSのユーザー(って言い方でいいのかな?)を作成すればOK。
権限も設定できるし、作成したユーザー名で外部からsshもできる。

ユーザー作成は一旦rootで(docker exec -itでシェル起動して)Cliコマンドで、configureを起動してからusernameコマンドを使用する。

その際、OSユーザーと同名のアカウントは作成できない模様。

localhost#username ansible privilege 15 secret ansible
% Invalid input
localhost#configure
localhost(config)#username ansible privilege 15 secret ansible
% Unable to create reserved username 'ansible'. Please try another.
localhost(config)#username test01 privilege 10 secret test01
localhost(config)#username test02 privilege 15 secret test02
localhost(config)#username lab-user privilege 15 secret p@ssword

これで、test01test02lab-usersshで接続すればCliが起動する。
(これらのユーザーは/etc/passwdには記載されない)

参考

www.infraexpert.com

beginners-network.com

その他関連情報