본문 바로가기
IaC/Ansible

[Ansible] F5 VIP, Pool Member 상태 출력하기

by chan10 2024. 9. 19.

F5에서 Virtual Server, Pool, Member Status를 확인할 수 있는 Network Map을 제공합니다.

그러나 GUI로 확인만 가능할 뿐 파일로 다운로드 받을 수는 없어서 통계를 내기에는 아쉽습니다.

Ansible을 이용해서 위와 같은 내용을 다운로드 받고 각 서비스들의 상태를 파악할 수 있도록 합니다.

 

[Ansible-PlayBook]

---
- name: show_vip_pool_member_status
  hosts: all
  vars_files: # 변수 파일 지정, 미 사용시 인벤토리 호스트 그룹명과 group_vars 파일명이 일치하는 파일 자동매칭 사용
    - /etc/ansible/inventory/group_vars/f5.yml
  connection: local
  gather_facts: no

  collections:
   - f5networks.f5_modules
  
  tasks:
    - name: Collect pool info
      bigip_device_info:
        gather_subset:
          - ltm-pools
        provider: "{{ f5_provider }}"
      delegate_to: localhost
      register: device_pools
    
    - name: Collect virtual-server info
      bigip_device_info:
        gather_subset:
          - virtual-servers
        provider: "{{ f5_provider }}"
      delegate_to: localhost
      register: device_virt

    - name: Prepare data for CSV
      set_fact:
        csv_lines: |
          Virtual Server Name,Virtual Server IP,Virtual Server Port,Pool Name,Pool Member Address,Pool Member Port,Member State
          {% for virt in device_virt['virtual_servers'] %}
            {% if 'default_pool' in virt and virt.default_pool %}
              {% set pool_name_parts = virt.default_pool.split('/') %}
              {% set pool_name = pool_name_parts[-1] %}

              {% set pool = (device_pools['ltm_pools'] | selectattr('name', 'equalto', pool_name) | first) %}
              
              {% if pool %}
                {% if 'members' in pool and pool.members %}
                  {% for member in pool.members %}
                    {% set member_port_parts = member.name.split(':') %}
                    {% set member_port = member_port_parts[-1] %}

                    {{ virt.name }},{{ virt.destination_address }},{{ virt.destination_port }},{{ pool.name }},{{ member.address }},{{ member_port }},{{ member.real_state }}
                  {% endfor %}
                  {% else %}
                    {{ virt.name }},{{ virt.destination_address }},{{ virt.destination_port }},{{ pool.name }},None,None,None
                {% endif %}
              {% endif %}
              
              {% else %}
                {{ virt.name }},{{ virt.destination_address }},{{ virt.destination_port }},None,None,None,None
            {% endif %}
          {% endfor %}

    - name: create directory
      file:
        path: /etc/ansible/result/f5/
        state: directory

    - name: Save CSV file
      copy:
        content: "{{ csv_lines | replace('\n\n', '\n') | trim }}"
        dest: "/etc/ansible/result/f5/{{ inventory_hostname}}_show_vip_pool_status.csv"

 

Code Description
- name: Collect pool info
- name: Prepare data for CSV
- name: Collect virtual-server info
bigip_device_info 모듈을 이용해 Pool, Virtual Server 정보를 수집 후 device_pools, device_virt 변수에 저장합니다. (리스트 형식)
- name: Prepare data for CSV
  set_fact:
    csv_lines: |
수집한 정보의 데이터 파싱 및 csv 파일 형태 저장합니다.
Virtual Server Name,
Virtual Server IP,
Virtual Server Port,
Pool Name,
Pool Member Address,
Pool Member Port,
Member State
CSV 형식에서 각 열의 헤더를 지정합니다.
{% for virt in device_virt['virtual_servers'] %}

{% endfor %}
device_virt 에 저장된 virtual_servers들을 하나씩 가져와 virt 지역 변수에 저장하여 사용합니다.
{% if 'default_pool' in virt and virt.default_pool %}

{% endif %}
'default_pool' in virt : virt'default_pool'이라는 키가 존재하는지 확인합니다.

virt.default_pool : virtdefault_pool 속성이 True(즉, 값이 존재하고 비어있지 않음)인지 확인합니다.
{% set pool_name_parts = virt.default_pool.split('/') %}

{% set pool_name = pool_name_parts[-1] %}

{% set pool = (device_pools['ltm_pools'] | selectattr('name', 'equalto', pool_name) | first) %}
virt.default_pool의 값을 / 기호를 기준으로 나눕니다.

pool_name_parts 리스트의 마지막 요소를 가져와서 pool_name에 저장합니다.

device_pools['ltm_pools'] 리스트에서 name 속성이 pool_name과 같은 첫 번째 요소를 찾습니다.
selectattr('name', 'equalto', pool_name)는 리스트에서 각 항목의 name 속성과 pool_name을 비교하여 해당하는 항목들을 선택합니다.
최종적으로 결과를 pool 변수에 저장됩니다.
{% if pool %} {% endif %}

{% if 'members' in pool and pool.members %} {% endif %}
pool이 존재하는지 확인합니다.

'members' in pool : pool에 'members'라는 키가 존재하는지 확인합니다.

pool.members : poolmembers 속성이 True(즉, 값이 존재하고 비어있지 않음)인지 확인합니다.
{% for member in pool.members %}

{% set member_port_parts = member.name.split(':') %}

{% set member_port = member_port_parts[-1] %}

{% endfor %}
pool.members 로 member들의 정보를 가져와 member 변수에 저장합니다.

member.name의 값을 . 기호를 기준으로 나눕니다.

member_port_parts 리스트의 마지막 요소를 가져와서 member_port에 저장합니다.
{{ virt.name }},
{{ virt.destination_address }},
{{ virt.destination_port }},
{{ pool.name }},
{{ member.address }},
{{ member_port }},
{{ member.real_state }}
변수에 저장되어 있는 정보를 이용해 CSV 각 열에 맞게 저장합니다.
{% else %}

{{ virt.name }},
{{ virt.destination_address }},
{{ virt.destination_port }},
{{ pool.name }},
None,
None,
None
Pool에 member가 없는 경우 None 상태로 처리합니다.
- name: create directory CSV 파일을 생성할 경로의 디렉토리를 생성합니다.
- name: Save CSV file Virtual Server, Pool 상태 데이터를 저장한 csv_lines를 CSV 파일로 저장합니다.

 

아래는 참고용으로 Virtual Server, Pool 조회 시 JSON 형식으로 조회되는 데이터의 정보입니다.

[VIP 객체]

{"virtual_servers": 
	[
		{
		"full_path": "/Common/vs_PC_Front_443", 
		"name": "vs_PC_Front_443", 
		"auto_lasthop": "default", 
		"cmp_enabled": "yes", 
		"connection_limit": 0, 
		"enabled": "yes", 
		"translate_port": "yes", 
		"translate_address": "yes", 
		"destination": "/Common/xxx.xxx.xxx.84:443", 
		"nat64_enabled": "no", 
		"source_port_behavior": "preserve", 
		"protocol": "tcp", 
		"default_pool": "/Common/p_PC_Front_8100", 
		"rate_limit_mode": "object", 
		"rate_limit_source_mask": 0, 
		"rate_limit": -1, "gtm_score": 0, 
		"rate_limit_destination_mask": 0, 
		"source_address": "0.0.0.0/0", 
		"connection_mirror_enabled": "no", 
		"type": "standard", 
		"profiles": 
		  [
		    {
		    "name": "clientssl_multi_SNI_http2", 
		    "context": "client-side", 
		    "full_path": "/Common/clientssl_multi_SNI_http2"
		    }, 
		    {
		    "name": "http2_100", 
		    "context": "all", 
		    "full_path": "/Common/http2_100"
		    }, 
		    {
		    "name": "http_X-Forwarded", 
		    "context": "all", 
		    "full_path": "/Common/http_X-Forwarded"
		    }, 
		    {
		    "name": "tcp_300", 
		    "context": "all", 
		    "full_path": "/Common/tcp_300"
		    }
		  ], 
		"destination_address": "xxx.xxx.xxx.84", 
		"destination_port": 443, 
		"availability_status": "available", 
		"status_reason": "The virtual server is available", 
		"total_requests": 5413601568, 
		"client_side_bits_in": 139595364699776, 
		"client_side_bits_out": 2277328896232424, 
		"client_side_current_connections": 1868, 
		"client_side_evicted_connections": 0, 
		"client_side_max_connections": 18502, 
		"client_side_pkts_in": 47945985529, 
		"client_side_pkts_out": 58647314986, 
		"client_side_slow_killed": 0, 
		"client_side_total_connections": 938109116, 
		"cmp_mode": "all-cpus", 
		"ephemeral_bits_in": 0, 
		"ephemeral_bits_out": 0, 
		"ephemeral_current_connections": 0, 
		"ephemeral_evicted_connections": 0, 
		"ephemeral_max_connections": 0, 
		"ephemeral_pkts_in": 0, 
		"ephemeral_pkts_out": 0, 
		"ephemeral_slow_killed": 0, 
		"ephemeral_total_connections": 0, 
		"total_software_accepted_syn_cookies": 0, 
		"total_hardware_accepted_syn_cookies": 20307, 
		"total_hardware_syn_cookies": 0, 
		"hardware_syn_cookie_instances": 0, 
		"total_software_rejected_syn_cookies": 28, 
		"software_syn_cookie_instances": 0, 
		"current_syn_cache": 1, 
		"syn_cache_overflow": 0, 
		"total_software_syn_cookies": 0, 
		"syn_cookies_status": "not-activated", 
		"max_conn_duration": 4023859019, 
		"mean_conn_duration": 440698, 
		"min_conn_duration": 4, 
		"cpu_usage_ratio_last_5_min": 1, 
		"cpu_usage_ratio_last_5_sec": 1, 
		"cpu_usage_ratio_last_1_min": 1
		}
	]
}

 

 

[Pool 객체]

{"ltm_pools": 
	[
		{
		"full_path": "/Common/p_PC_Front_8100", 
		"name": "p_PC_Front_8100", 
		"allow_nat": "yes", 
		"allow_snat": "yes", 
		"ignore_persisted_weight": "no", 
		"client_ip_tos": "pass-through", 
		"server_ip_tos": "pass-through", 
		"client_link_qos": "pass-through", 
		"server_link_qos": "pass-through", 
		"lb_method": "round-robin", 
		"minimum_active_members": 0, 
		"minimum_up_members": 0, 
		"minimum_up_members_action": "failover", 
		"minimum_up_members_checking": "no", 
		"monitors": ["/Common/HNT_HTTP_Channel_health"], 
		"queue_depth_limit": 0, 
		"queue_on_connection_limit": "no", 
		"queue_time_limit": 0, 
		"reselect_tries": 0, 
		"service_down_action": "reset", 
		"slow_ramp_time": 10, 
		"priority_group_activation": 0, 
		"members": 
		[
		  {
		    "name": "xxx.xxx.xxx.31:8100", 
		    "partition": "Common", 
		    "address": "xxx.xxx.xxx.31", 
		    "ephemeral": "no", 
		    "logging": "no", 
		    "ratio": 1, 
		    "connection_limit": 0, 
		    "dynamic_ratio": 1, 
		    "full_path": "/Common/xxx.xxx.xxx.31:8100", 
		    "inherit_profile": "yes", 
		    "priority_group": 0, 
		    "rate_limit": "no", 
		    "fqdn_autopopulate": "no", 
		    "monitors": [], 
		    "real_session": "monitor-enabled", 
		    "real_state": "up", 
		    "state": "present"
		  }, 
		  {
		    "name": "xxx.xxx.xxx.32:8100", 
		    "partition": "Common", 
		    "address": "xxx.xxx.xxx.32", 
		    "ephemeral": "no", 
		    "logging": "no", 
		    "ratio": 1, 
		    "connection_limit": 0, 
		    "dynamic_ratio": 1, 
		    "full_path": "/Common/xxx.xxx.xxx.32:8100", 
		    "inherit_profile": "yes", 
		    "priority_group": 0, 
		    "rate_limit": "no", 
		    "fqdn_autopopulate": "no", 
		    "monitors": [], 
		    "real_session": "monitor-enabled", 
		    "real_state": "up", 
		    "state": "present"
		  }, 
		  { 이하 맴버 생략 }
		], 
		"active_member_count": 7, 
		"available_member_count": 7, 
		"availability_status": "available", 
		"enabled_status": "enabled", 
		"status_reason": "The pool is available", 
		"all_max_queue_entry_age_ever": 0, 
		"all_avg_queue_entry_age": 0, 
		"all_queue_head_entry_age": 0, 
		"all_max_queue_entry_age_recently": 0, 
		"all_num_connections_queued_now": 0, 
		"all_num_connections_serviced": 0, 
		"pool_max_queue_entry_age_ever": 0, 
		"pool_avg_queue_entry_age": 0, 
		"pool_queue_head_entry_age": 0, 
		"pool_max_queue_entry_age_recently": 0, 
		"pool_num_connections_queued_now": 0, 
		"pool_num_connections_serviced": 0, 
		"current_sessions": 20, 
		"member_count": 7, 
		"total_requests": 5433726999, 
		"server_side_bits_in": 159121658406792, 
		"server_side_bits_out": 2338484477973656, 
		"server_side_current_connections": 284, 
		"server_side_max_connections": 6927, 
		"server_side_pkts_in": 41460251958, 
		"server_side_pkts_out": 209621700218, 
		"server_side_total_connections": 1192157412
		}
	]
}