Ansible 네트워크 자동화 -3
앤시블 인벤토리(Inventory) 만들기
인벤토리 없이 플레이북을 실행하려면 명령어에 여러 플래그들이 필요하다. 또한 하나의 장비에 대해 플레이북을 실행하는 것은 같은 작업을 수동으로 하는 것과 비교하여 크게 효율적이지도 않다.
ansible_network_os 와 ssh 사용자같은 정보를 가지는 managed node를 구성하기위해 인벤토리 파일을 사용한다. 모든 기능을 가지는 인벤토리 파일은 네트워크의 진짜 소스로 쓰일 수 있다.
인벤토리 파일을 사용하여 한 플레이북에서 단일 명령으로 수백개의 네트워크 장비를 유지할 수 있다.
기본 인벤토리
먼저, 인벤토리를 논리적으로 그룹화한다. 우수 사례는 용도(어플리케이션, 스택이나 마이크로서비스), 장소(데이터센터나 지역), 시점(개발 단계)별로 서버와 네트워크 장비를 그룹화 하는 것이다.
그룹 이름에 공백, -, 숫자를 사용하면 안된다. 그룹이름은 대소문자를 구별한다.
아래 간단한 datacenter 예제는 기본 그룹 구조를 보여준다. 그룹은 [metagroupname:children] 문법을 사용하고 메타그룹의 멤버로 그룹을 나열한다. network 그룹은 모든 leap와 spine을 v포함한다. datacenter 그룹은 모든 네트워크장비와 모든 웹서버를 추가한다.
--- leafs: hosts: leaf01: ansible_host: 192.168.100.11 leaf02: ansible_host: 192.168.100.12 spines: hosts: spine01: ansible_host: 192.168.100.13 spine02: ansible_host: 192.168.100.14 network: children: leafs: spines: webservers: hosts: webserver01: ansible_host: 192.168.100.15 webserver02: ansible_host: 192.168.100.16 datacenter: children: network: webservers:
아래 INI 포맷의 인벤토리는 위와 정확히 동일하다.
[leafs] leaf01 leaf02 [spines] spine01 spine02 [network:children] leafs spines [webservers] webserver01 webserver02 [datacenter:children] network webservers
인벤토리에 변수 추가하기.
인벤토리에 ansible 명령어에 필요한 변수와 값을 설정하여 ansible-play 명령을 단순화할 수 있다.
아래의 인벤토리 예제는 각 네트워크 장비의 IP, OS, ssh 사용자가 포함된다.
네트워크 장비를 IP 주소로만 접근할 수 있는 경우 인벤토리 파일에 IP 주소를 추가해야한다.
만약 네트워크장비를 호스트네임을 사용하여 접근 가능하면 IP 주소는 필요 없다.
--- leafs: hosts: leaf01: ansible_host: 192.168.100.11 ansible_network_os: vyos.vyos.vyos ansible_user: my_vyos_user leaf02: ansible_host: 192.168.100.12 ansible_network_os: vyos.vyos.vyos ansible_user: my_vyos_user spines: hosts: spine01: ansible_host: 192.168.100.13 ansible_network_os: vyos.vyos.vyos ansible_user: my_vyos_user spine02: ansible_host: 192.168.100.14 ansible_network_os: vyos.vyos.vyos ansible_user: my_vyos_user network: children: leafs: spines: webservers: hosts: webserver01: ansible_host: 192.168.100.15 ansible_user: my_server_user webserver02: ansible_host: 192.168.100.16 ansible_user: my_server_user datacenter: children: network: webservers:
인벤토리 내의 그룹 변수
OS나 ssh 사용자 같은 변수 값을 공유하는 그룹의 장비에서 그룹 변수로 통합해 중복을 줄이고 유지관리를 단순하게 할 수 있다.
leafs: hosts: leaf01: ansible_host: 192.168.100.11 leaf02: ansible_host: 192.168.100.12 vars: ansible_network_os: vyos.vyos.vyos ansible_user: my_vyos_user spines: hosts: spine01: ansible_host: 192.168.100.13 spine02: ansible_host: 192.168.100.14 vars: ansible_network_os: vyos.vyos.vyos ansible_user: my_vyos_user network: children: leafs: spines: webservers: hosts: webserver01: ansible_host: 192.168.100.15 webserver02: ansible_host: 192.168.100.16 vars: ansible_user: my_server_user datacenter: children: network: webservers:
변수 문법
변수 값의 문법은 인벤토리, 플레이북, group_vars 파일에서 다르다.
플레이북과 grup_vas 파일 모두 YAML로 작성되지만 변수 사용에는 차이가 있다.
ini 스타일의 인벤토리 파일에서는 key=value 형식을 사용해야한다. (예: ansible_network_os=vyos.vyos.vyos)
플레이북이나 group_vars를 포함하는 .yml 이나 .yaml 확장자를 가지는 파일에서는 반드시 YAML 문법을 사용해야한다. (예: key: value)
group_vars 파일에서는 전체 key 이름을 사용한다. (예: ansible_network_os: vyos.vyos.vyos)
플레이북에서 ansible 접두사를 생략하는 짧은 형식의 key 이름을 사용한다. (예: network_os: vyos.vyos.vyos)
플랫폼별 인벤토리 그룹
인벤토리가 커질수록 플랫폼별로 장비를 구룹화 할 수 있다. 이것으로 해당 플랫폼의 모든 장비에 대해 플랫폼별 변수를 쉽게 지정할 수 있다.
--- leafs: hosts: leaf01: ansible_host: 192.168.100.11 leaf02: ansible_host: 192.168.100.12 spines: hosts: spine01: ansible_host: 192.168.100.13 spine02: ansible_host: 192.168.100.14 network: children: leafs: spines: vars: ansible_connection: ansible.netcommon.network_cli ansible_network_os: vyos.vyos.vyos ansible_user: my_vyos_user webservers: hosts: webserver01: ansible_host: 192.168.100.15 webserver02: ansible_host: 192.168.100.16 vars: ansible_user: my_server_user datacenter: children: network: webservers:
이 설정에서 first_playbook.yml은 두개의 플래그로 실행할 수 있다.
ansible-playbook -i inventory.yml -k first_playbook.yml
-k 플래그는 ssh 비밀번호를 묻는 프롬프트를 제공한다.
ansible-vault를 사용하여 group_vars 파일에 ssh 비밀번호를 안전하게 저장할 수 있다.
인벤토리 확인
테스트 인벤토리를 다음과 같이 작성.
--- branch1: hosts: b1_l3: ansible_host: 192.168.215.1 b1_l2: ansible_host: 192.168.215.2 vars: ansible_network_os: extreme.exos.exos ansible_user: admin branch2: hosts: b2_l3: ansible_host: 192.168.232.121 b2_l2: ansible_host: 192.168.232.120 vars: ansible_network_os: cisco.ios.ios ansible_user: admin network: children: branch1: branch2: headquarter: children: network:
ansible-inventory 명령으로 ansible이 보는것 처럼 인벤토리를 표시할 수 있다.
$ ansible-inventory -i inventory_1.yml --list { "_meta": { "hostvars": { "b1_l2": { "ansible_connection": "ansible.netcommon.network_cli", "ansible_host": "192.168.215.2", "ansible_network_os": "extreme.exos.exos", "ansible_user": "admin" }, "b1_l3": { "ansible_connection": "ansible.netcommon.network_cli", "ansible_host": "192.168.215.1", "ansible_network_os": "exos.exos.exos", "ansible_user": "admin" }, "b2_l2": { "ansible_connection": "ansible.netcommon.network_cli", "ansible_host": "192.168.232.120", "ansible_network_os": "cisco.ios.ios", "ansible_user": "admin" }, "b2_l3": { "ansible_connection": "ansible.netcommon.network_cli", "ansible_host": "192.168.232.121", "ansible_network_os": "cisco.ios.ios", "ansible_user": "admin" } } }, "all": { "children": [ "headquarter", "ungrouped" ] }, "branch1": { "hosts": [ "b1_l2", "b1_l3" ] }, "branch2": { "hosts": [ "b2_l2", "b2_l3" ] }, "headquarter": { "children": [ "network" ] }, "network": { "children": [ "branch1", "branch2" ] } }
ansible-vault로 민감한 변수 보호하기
ansible-vault 명령은 비밀번호같은 개별 변수나 파일의 암호화를 제공한다.
아래와 같은 명령으로 데이타베이스 비밀번호, 권한 상승 비밀번호, ssh 접속 비밀번호같은 민감한 정보를 암화화 할 수 있다.
1. ansible-vault 를 위한 비밀번호를 만든다.
비밀번호는 ansible-vault-password 를 사용할 것이다.
$ echo "ansible-vault-password" > ansible-vault-pw-file
2. 위에서 만든 ansible-vault 비밀번호 파일을 사용하여 cisco ios 장비의 비밀번호를 암호화한다.
$ ansible-vault encrypt_string --vault-id admin@~/ansible-vault-pw-file 'adminpasswd' --name 'ansible_password' ansible_password: !vault | $ANSIBLE_VAULT;1.2;AES256;admin 65636463613231633732313436356164356636666331376238333235363234653565393431363937 3339613931356635373434316362616236353734656635350a656464353036353961333236323433 62646261383632303335366530353262303064343536653438643437616464373165656162336163 6231303935383831320a303264386239393161336533356636623434396562313132333463646231 6537 Encryption successful
id 뒤에 파일을 지정하는 대신 prompt를 사용하면, 아래처럼 비밀번호를 입력하는 프롬프트를 내보낸다.
$ ansible-vault encrypt_string --vault-id admin@prompt 'adminpasswd' --name 'ansible_password' New vault password (admin): Confirm new vault password (admin): ansible_password: !vault | $ANSIBLE_VAULT;1.2;AES256;admin 36646430336231653236633435346463343665363866633533383862396430343166383962393165 3662303731626331343434636166646266386364333065320a643166616634616532383037366265 62366335396264636434633333626264366333336663646435313432356465643434303166636132 3363386164616239620a633263343432626332306439663933383036366138306138383930363536
–vault-id 플래그는 다른 사용자나 다른 접근 수준에 대하여 서로다른 vault 암호를 허용한다.
출력은 ansible-vault 명령의 사용자 admin이 포함되며 YAML 구문을 사용한다.
INI 포맷은 인라인 vault를 지원하지 않으므로, YAML포맷을 사용해야한다.
테스트 인벤토리를 아래처럼 수정한다.
--- branch1: hosts: b1_l3: ansible_host: 192.168.245.62 b1_l2: ansible_host: 192.168.245.25 vars: ansible_network_os: extreme.exos.exos ansible_user: admin ansible_password: !vault | $ANSIBLE_VAULT;1.2;AES256;admin 65313437336130346432383833323639303365303764313462393531613039336563613066313361 6537623138383233343234306631636439373032313738300a663063653733366264356565636139 39303766333534333162356162633038643763323234663836393966323263633531393932626635 3935633935616565610a386632353730653562623131623637666636326132376235623438393464 3731 branch2: hosts: b2_l3: ansible_host: 192.168.232.121 b2_l2: ansible_host: 192.168.232.120 vars: ansible_network_os: cisco.ios.ios ansible_user: admin ansible_password: !vault | $ANSIBLE_VAULT;1.2;AES256;admin 65636463613231633732313436356164356636666331376238333235363234653565393431363937 3339613931356635373434316362616236353734656635350a656464353036353961333236323433 62646261383632303335366530353262303064343536653438643437616464373165656162336163 6231303935383831320a303264386239393161336533356636623434396562313132333463646231 6537 network: children: branch1: branch2: vars: ansible_connection: ansible.netcommon.network_cli ansible_user: admin headquarter: children: network:
vault 비밀번호 파일로 플레이북을 실행.
$ ansible-playbook -i inventory_1.yml --vault-id admin@~/ansible-vault-pw-file first_playbook.yml PLAY [Network Getting Started First Playbook] ****************************************************************** TASK [Gathering Facts] ***************************************************************************************** ok: [b1_l3] ok: [b1_l2] ok: [b2_l3] ok: [b2_l2] TASK [Get config for IOS devices] ****************************************************************************** ok: [b1_l3] ok: [b1_l2] ok: [b2_l3] ok: [b2_l2] TASK [Display the config] ************************************************************************************** fatal: [b1_l3]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'ansible_net_version' is undefined\n\nThe error appears to be in '/home/snowfox/ansible/first_playbook.yml': line 13, column 7, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n - name: Display the config\n ^ here\n"} fatal: [b1_l2]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'ansible_net_version' is undefined\n\nThe error appears to be in '/home/snowfox/ansible/first_playbook.yml': line 13, column 7, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n - name: Display the config\n ^ here\n"} ok: [b2_l3] => { "msg": "The hostname is M_Training and the OS is 12.2(55)SE11" } ok: [b2_l2] => { "msg": "The hostname is Mirae_5F_Support_212.8 and the OS is 12.2(55)SE1" } PLAY RECAP ***************************************************************************************************** b1_l2 : ok=2 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0 b1_l3 : ok=2 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0 b2_l2 : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 b2_l3 : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
비밀번호 파일 대신 직접입력하려면 파일 이름대신 prompt를 사용한다.(당연히 입력하는 비밀번호는 파일에 있는것과 동일해야함)
$ ansible-playbook -i inventory_1.yml --vault-id admin@prompt first_playbook.yml Vault password (admin): PLAY [Network Getting Started First Playbook] ****************************************************************** TASK [Gathering Facts] *** ....
원래의 값을 보기위해 디버그 모듈을 사용할 수 있다.
YAML 파일에 ansible_connection 변수를 정의하는경우, 아래 명령처럼 실행해야 적용된다.
이것을 방지하려면 ansible_connection 변수 없이 복사본을 만들면 된다.
참고문서: https://docs.ansible.com/ansible/latest/network/getting_started/basic_concepts.html#inventory