Ansible 네트워크 자동화 – 2

앤시블 네트워크 자동화 – 2

Ansible 네트워크 자동화

네트워크 자동화는 기본 앤시블 개념의 잇점이지만, 네트워크 모듈의 작동에는 중요한 차이점이 있다.

Control 노드에서 실행

대부분의 앤시블 모듈과는 달리 네트워크 모듈은 managed 노드에서 실행되지 않는다. 사용자 관점에서 네트워크 모듈은 다른 모듈처럼 작동한다. 그것은 ad-hoc 명령, playbook과 role을 가지고 작동한다.
그 이면에서 네트워크 모듈은 다른 (리눅스/유닉스 윈도우) 모듈과 다른 방법을 사용한다. 앤시블은 파이썬으로 작성되고 실행되기 때문에, 네트워크 장비에서 파이썬 실행을 할수 없으므로, 앤시블 네트워크 모듈은 control 노드에서 ansible 이나 ansible-playbook을 실행하는것으로 실행된다.

다양한 통신 프로토콜(multiple communication protocol)

managed 노드 대신 control 노드에서 네트워크 모듈이 실행되기 때문에, 다양한 통신 프로토콜을 지원해야한다.
네트워크 모듈에 선택된 통신 프로토콜(XML over SSH, CLI over SSH, API over HTTPS)은 모듈의 의도와 플랫폼에 따라 다르다.
어떤 네트워크 모듈은 하나의 프로토콜만 지원하며, 어떤 모듈은 프로토콜을 선택할 수 있다.
가장 일반적인 통신 프로토콜은 CLI over SSH 이다.
통신 프로토콜은 ansible_connection 변수로 설정한다.

ansible_connection 변수에는,
ansible.netcommon.network_cli (CLI over ssh), ansible.netcommon.netconf (XML over ssh), ansible.netcommon.httpapi (API over http/https), local 이 있다.
이 중에서 local은 더 이상 사용되지 않는다. eos_eapi와 nxos_nxapi에서 ansible.netcommon.httpapi 도 더이상 사용하지 않는다.

네트워크 플랫폼으로 구성된 collection

네트워크 플랫폼은 ansible collection에 의해 관리되는 os를 가지는 네트워크 장비 집합이다.
예를 들면,
Arista: arista.eos
Cisco: cisco.ios, cisco.iosxr, cisco.nxos
Juniper: junipernetworks.junos
Vyos: vyos.vyos

네트워크 플랫폼의 모든 모듈은 특정 요구조건을 공유한다. 어떤 네트워크 플랫폼들은 특별한 차이를 가지며 자세한것은 플랫폼별 문서를 확인한다.

권한 상승: enable 모드, becom, authorize

어떤 네트워크 플랫폼들은 권한 상승을 지원하며, 특정 작업을 하려면 권한을 가진 사용자여야 한다.
네트워크 장비에서 이것을 enable 모드(유닉스의 sudo 와 비슷한)라 부른다.
앤시블 네트워크 모듈은 그것을 지원하는 네트워크 장비에 대한 권한 상승을 제공한다.
enable 모드를 지원하는 플랫폼에 대한 자세한 내용과 사용법은 플랫폼별 문서를 참고하면 된다.

권한 상승을 위한 becom 사용.
권한 상승을 지원하는 모든 네트워크 플랫폼에서 상승된 권한으로 task, play, playbook 을 실행하려면, become_method: enable을 가지는 최상위 앤시블 파라메터 becom: yes 를 사용한다.
또한, become_method: enable 과 become:yes 를 가지는 connection: network_cli 나 connection: httpapi중 하나를 사용해야한다.
만약 network_cli를 사용하여 앤시블을 네트워크 장비에 연결하려는 경우, group_vars 파일은 다음과 같다.

ansible_connection: ansible.netcommon.network_cli
ansible_network_os: cisco.ios.ios
ansible_become: yes
ansible_become_method: enable

네트워크 자동화의 첫번째 명령과 playbook.

사전 준비사항
Ansible 2.10 이후버전
앤시블과 호환되는 하나 이상의 네트워크 장비
기본적인 리눅스 명령어을 알아야함.
네트워크 스위치나 라우터의 기본적인 설정 방법을 알아야함.

ansible all -i 192.168.0.100, -c ansible.netcommon.network_cli -u admin -k -m cisco.ios.ios_system -e ansible_network_os=cisco.ios.ios

all : 명령을 실행할 모스트 그룹. 여기서는 모든 그룹(all)
-i : 인벤토리 만약 ‘,’ 가 없으면 인벤토리 파일을 의미함
-c : 앤시블 연결 방법
-u : ssh 접속에 필요한 사용자 id
-m : 실행할 앤시블 모듈로 FQCN을 이용.
-e : 기타 변수(위에서는 네트워크 os를 설정했음)

ssh key를 가지고 ssh-agent를 사용한다면 -k 옵션을 생략할 수 있다.

아래는 실제 명령을 내린 경우인데, 모듈을 찾지 못한다는 메시지를 볼 수 있다.

$ ansible all -i 192.168.10.121, -c ansible.netcommon.network_cli -u admin -k -m cisco.ios.ios_system -e ansible_network_os=ios
SSH password:
192.168.10.121 | FAILED! => {
"msg": "The module cisco.ios.ios_config was not found in configured module paths"
}

이 문제를 해결하기 위해 ansible-galaxy 명령으로 시스코 장비용 모듈을 설치한다. 모듈은 사용자 홈 디렉토리에 설치된다.

$ ansible-galaxy collection install cisco.ios
Process install dependency map
Starting collection install process
Installing 'cisco.ios:2.0.0' to '/home/snowfox/.ansible/collections/ansible_collections/cisco/ios'
Skipping 'ansible.netcommon' as it is already installed
Skipping 'ansible.utils' as it is already installed

이제 다시 실행해 보면 아래와 같은 메시지를 볼 수 있다.

$ ansible all -i 192.168.10.121, -c ansible.netcommon.network_cli -u admin -k -m cisco.ios.ios_facts -e ansible_network_os=ios
SSH password:
192.168.10.121 | SUCCESS => {
"ansible_facts": {
"ansible_net_all_ipv4_addresses": [
"192.168.10.121"
],
"ansible_net_all_ipv6_addresses": [],
"ansible_net_api": "cliconf",
"ansible_net_filesystems": [
"flash:"
],
"ansible_net_filesystems_info": {
"flash:": {
"spacefree_kb": 2893.0,
"spacetotal_kb": 15624.0
}
},
"ansible_net_gather_network_resources": [],
"ansible_net_gather_subset": [
"interfaces",
"hardware",
"default"
],
"ansible_net_hostname": "M1_Traning",
"ansible_net_image": "flash:/c3750-ipservicesk9-mz.122-55.SE11.bin",
"ansible_net_interfaces": {
"GigabitEthernet1/0/1": {
"bandwidth": 1000000,
"description": null,
"duplex": null,
"ipv4": [],
"lineprotocol": null,
"macaddress": "0018.b90c.a281",
"mediatype": "1000BaseLX SFP",
"mtu": 1500,
"operstatus": "up",
"type": "Gigabit Ethernet"
},
"GigabitEthernet1/0/10": {
"bandwidth": 10000,
"description": null,
"duplex": null,
"ipv4": [],
"lineprotocol": null,
"macaddress": "0018.b90c.a28a",
"mediatype": "Not Present",
"mtu": 1500,
"operstatus": "down",
"type": "Gigabit Ethernet"
},
"GigabitEthernet1/0/11": {
"bandwidth": 10000,
"description": null,
"duplex": null,
"ipv4": [],
"lineprotocol": null,
"macaddress": "0018.b90c.a28b",
"mediatype": "Not Present",
"mtu": 1500,
"operstatus": "down",
"type": "Gigabit Ethernet"
},
"GigabitEthernet1/0/12": {
"bandwidth": 1000000,
"description": null,
"duplex": null,
"ipv4": [],
"lineprotocol": null,
"macaddress": "0018.b90c.a28c",
"mediatype": "1000BaseLX SFP",
"mtu": 1500,
"operstatus": "up",
"type": "Gigabit Ethernet"
},
"GigabitEthernet1/0/2": {
"bandwidth": 1000000,
"description": null,
"duplex": null,
"ipv4": [],
"lineprotocol": null,
"macaddress": "0018.b90c.a282",
"mediatype": "1000BaseLX SFP",
"mtu": 1500,
"operstatus": "up",
"type": "Gigabit Ethernet"
},
"GigabitEthernet1/0/3": {
"bandwidth": 10000,
"description": null,
"duplex": null,
"ipv4": [],
"lineprotocol": null,
"macaddress": "0018.b90c.a283",
"mediatype": "Not Present",
"mtu": 1500,
"operstatus": "down",
"type": "Gigabit Ethernet"
},
"GigabitEthernet1/0/4": {
"bandwidth": 1000000,
"description": null,
"duplex": null,
"ipv4": [],
"lineprotocol": null,
"macaddress": "0018.b90c.a284",
"mediatype": "1000BaseLX SFP",
"mtu": 1500,
"operstatus": "up",
"type": "Gigabit Ethernet"
},
"GigabitEthernet1/0/5": {
"bandwidth": 1000000,
"description": null,
"duplex": null,
"ipv4": [],
"lineprotocol": null,
"macaddress": "0018.b90c.a285",
"mediatype": "1000BaseLX SFP",
"mtu": 1500,
"operstatus": "up",
"type": "Gigabit Ethernet"
},
"GigabitEthernet1/0/6": {
"bandwidth": 1000000,
"description": null,
"duplex": null,
"ipv4": [],
"lineprotocol": null,
"macaddress": "0018.b90c.a286",
"mediatype": "1000BaseLX SFP",
"mtu": 1500,
"operstatus": "up",
"type": "Gigabit Ethernet"
},
"GigabitEthernet1/0/7": {
"bandwidth": 10000,
"description": null,
"duplex": null,
"ipv4": [],
"lineprotocol": null,
"macaddress": "0018.b90c.a287",
"mediatype": "Not Present",
"mtu": 1500,
"operstatus": "down",
"type": "Gigabit Ethernet"
},
"GigabitEthernet1/0/8": {
"bandwidth": 1000000,
"description": null,
"duplex": null,
"ipv4": [],
"lineprotocol": null,
"macaddress": "0018.b90c.a288",
"mediatype": "1000BaseLX SFP",
"mtu": 1500,
"operstatus": "up",
"type": "Gigabit Ethernet"
},
"GigabitEthernet1/0/9": {
"bandwidth": 10000,
"description": null,
"duplex": null,
"ipv4": [],
"lineprotocol": null,
"macaddress": "0018.b90c.a289",
"mediatype": "Not Present",
"mtu": 1500,
"operstatus": "down",
"type": "Gigabit Ethernet"
},
"Vlan1": {
"bandwidth": 1000000,
"description": null,
"duplex": null,
"ipv4": [],
"lineprotocol": "down",
"macaddress": "0018.b90c.a2c0",
"mediatype": null,
"mtu": 1500,
"operstatus": "up",
"type": "EtherSVI"
},
"Vlan300": {
"bandwidth": 1000000,
"description": null,
"duplex": null,
"ipv4": [
{
"address": "192.168.10.121",
"subnet": "24"
}
],
"lineprotocol": "up",
"macaddress": "0018.b90c.a2c1",
"mediatype": null,
"mtu": 1500,
"operstatus": "up",
"type": "EtherSVI"
}
},
"ansible_net_iostype": "IOS",
"ansible_net_memfree_mb": 33813.34375,
"ansible_net_memtotal_mb": 66561.171875,
"ansible_net_model": "WS-C3750G-12S",
"ansible_net_neighbors": {
"GigabitEthernet1/0/1": [
{
"host": "Training_5F_PC_3",
"platform": "Cisco WS-C3550-48",
"port": "GigabitEthernet0/1"
}
],
"GigabitEthernet1/0/2": [
{
"host": "Training_5F_PC_1",
"platform": "Cisco WS-C3550-48",
"port": "GigabitEthernet0/2"
}
],
"GigabitEthernet1/0/4": [
{
"host": "Training_5F_PC_5",
"platform": "Cisco WS-C3550-48",
"port": "GigabitEthernet0/1"
}
],
"GigabitEthernet1/0/5": [
{
"host": "Training_5F_Support",
"platform": "cisco WS-C3560-24TS",
"port": "GigabitEthernet0/1"
}
],
"GigabitEthernet1/0/6": [
{
"host": "Training_5F_PC_2",
"platform": "Cisco WS-C3550-48",
"port": "GigabitEthernet0/1"
}
],
"GigabitEthernet1/0/8": [
{
"host": "Training_5F_PC_4",
"platform": "Cisco WS-C3550-48",
"port": "GigabitEthernet0/1"
}
]
},
"ansible_net_python_version": "3.8.5",
"ansible_net_serialnum": "CAT1027NKP2",
"ansible_net_stacked_models": [
"WS-C3750G-12S-S"
],
"ansible_net_stacked_serialnums": [
"CAT1027NKP2"
],
"ansible_net_system": "ios",
"ansible_net_version": "12.2(55)SE11",
"ansible_net_virtual_switch": "STACK",
"ansible_network_resources": {},
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false
}

네트워크를 위한 앤시를 플레이북 생성과 실행

명령을 매일 실행한다면 그것을 플레이북에 저장하고 ansible 대신 ansible-playbook 명령을 사용할 수 있다.
플레이북은 커맨드라인에서 플래그와함께 제공한 많은 파라메터 변수를 저장하여 더 적은 명령을 입력하도록 한다.
이것을 위해 플레이북과 인벤토리 두 개의 파일이 필요하다.

홈디렉토리에 적당한 디렉토리를 만들고, playbook 파일을 만든다.

$ cd; mkdir ansible;cd ansible;
$ vi first_playbook.yml

---

- name: Network Getting Started First Playbook
connection: ansible.netcommon.network_cli
gather_facts: false
hosts: all
tasks:

- name: Get config for IOS devices
cisco.ios.ios_facts:
gather_subset: all

- name: Display the config
debug:
msg: "The hostname is {{ ansible_net_hostname }} and the OS is {{ ansible_net_version }}"

플레이북은 처음의 명령의 7개 값 중 3개를 설정한다.
그룹(hosts: all), -c(connection method, connection: ansible.netcommon.network_cli) , -m (cisco.ios.ios_facts)

플레이북에 설정을 보여주는 작업도 추가한다. 모듈이 플레이북에서 실행될때, 출력은 콘솔에 표시되는 대신 이후 작업에 사용하기 위해 메모리에 저장된다.
쉘에서는 디버그 작업만 보여준다.

작성한 플레이북을 명령어로 실행한다.

$ ansible-playbook -i 192.168.10.121, -u admin -k -e ansible_network_os=cisco.ios.ios first_playbook.yml
SSH password:

PLAY [Network Getting Started First Playbook] ******************************************************************

TASK [Get config for IOS devices] ******************************************************************************
ok: [192.168.10.121]

TASK [Display the config] **************************************************************************************
ok: [192.168.10.121] => {
"msg": "The hostname is M_Traning and the OS is 12.2(55)SE11"
}

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

위의 결과에서 플레이북의 {{ ansible_net_hostname }} 과 {{ ansible_net_version }}부분이 debug 작업에 의해 실제로 보여지는 것을 확인 할 수 있다.

이제, 네트워크 장비의 설정을 검색할 수 있으므로 플레이북을 좀 더 확장하여 호스트네임을 변경해 본다.

$ cat first_playbook_extend.yml
---

- name: Network Getting Started First Playbook
connection: ansible.netcommon.network_cli
gather_facts: false
hosts: all
tasks:

- name: Get config for IOS devices
cisco.ios.ios_facts:
gather_subset: all

- name: Display the config
debug:
msg: "The hostname is {{ ansible_net_hostname }} and the OS is {{ ansible_net_version }}"

- name: Update the hostname
cisco.ios.ios_config:
backup: yes
lines:
- hostname M_Training

- name: Get changed config for ios devices
cisco.ios.ios_facts:
gather_subset: all

- name: Display the cnanged config
debug:
msg: "The new hostname is {{ ansible_net_hostname }} and the OS is {{ ansible_net_version }}"

이제 새로 작성한 플레이북을 실행한다.

$ ansible-playbook -i 192.168.10.121, -u admin -k -e ansible_network_os=cisco.ios.ios first_playbook_extend.yml
SSH password:

PLAY [Network Getting Started First Playbook] ******************************************************************

TASK [Get config for IOS devices] ******************************************************************************
ok: [192.168.10.121]

TASK [Display the config] **************************************************************************************
ok: [192.168.10.121] => {
"msg": "The hostname is M_Training and the OS is 12.2(55)SE11"
}

TASK [Update the hostname] *************************************************************************************
[WARNING]: To ensure idempotency and correct diff the input configuration lines should be similar to how they
appear if present in the running configuration on device
changed: [192.168.10.121]

TASK [Get changed config for ios devices] **********************************************************************
ok: [192.168.10.121]

TASK [Display the cnanged config] ******************************************************************************
ok: [192.168.10.121] => {
"msg": "The new hostname is M_Training and the OS is 12.2(55)SE11"
}

PLAY RECAP *****************************************************************************************************
192.168.10.121 : ok=5 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

위에서, 호스트네임이 변경전과 동일하게 나오지만, ssh 접속하여 확인해 보면 변경된 것을 확인 할 수 있다.(왜 저렇게 보이는지는 모르겠음)

네트워크 장치의 facts 수집
gather_facts 키워드는 표준화된 키/값 쌍으로 네트워크 장비의 facts 수집을 지원한다. 이런 네트워크 facts를 추가 작업에 제공하여 네트워크 장비를 관리할 수 있다.
또한 새로운 gather_network_resources 파라메터를 네트워크 *_facts 모듈(예를 들면 arista.eos.eos_facts)과 함께 사용하여 아래와 같은 장비 설정의 하위 집합만 받을 수 있다.

* 참고문서: https://docs.ansible.com/ansible/latest/network/getting_started/basic_concepts.html

답글 남기기

Your email address will not be published.