데이터 다루기
데이터 다루기 (Manipulating data)
플레이북을 작성하다 보면 변수로 복잡한 연산을 해야 할 때가 많아요. Ansible은 데이터 처리 도구로 권장되지는 않지만, 내장된 Jinja2 템플릿팅과 Ansible이 추가한 다양한 필터·룩업·테스트를 함께 쓰면 꽤 복잡한 변환도 할 수 있습니다. 이 페이지에서는 그 방법을 예시와 함께 다룹니다.
출처: 문서
본문
여러 경우에 변수로 복잡한 연산을 수행해야 할 필요가 있어요. Ansible이 데이터 처리/조작 도구로 권장되지는 않지만, 기존 Jinja2 템플릿팅을 Ansible이 추가한 많은 필터, 룩업, 테스트와 함께 사용하면 매우 복잡한 변환을 수행할 수 있습니다.
각 플러그인 유형의 빠른 정의부터 시작하겠습니다:
- lookups: 주로 '외부 데이터'를 조회하는 데 사용해요. Ansible에서 룩업은
with_<lookup>구문을 사용한 루프의 핵심 부분이지만, 처리할 데이터를 반환하기 위해 독립적으로도 사용할 수 있습니다. 앞서 언급했듯 루프에서의 주 기능 때문에 보통 목록을 반환합니다.lookup또는queryJinja2 연산자와 함께 사용합니다. - filters: 데이터를 변경/변환하는 데 사용하며,
|Jinja2 연산자와 함께 사용합니다. - tests: 데이터를 검증하는 데 사용하며,
isJinja2 연산자와 함께 사용합니다.
루프와 리스트 컴프리헨션 (Loops and list comprehensions)
대부분의 프로그래밍 언어에는 객체 목록을 포함한 리스트에 변환을 적용하는 루프(for, while 등)와 리스트 컴프리헨션이 있어요. Jinja2에는 이 기능을 제공하는 몇 가지 필터가 있습니다: map, select, reject, selectattr, rejectattr.
- map: 기본 for 루프로, 리스트의 모든 항목을 변경할 수 있게 해줘요. 'attribute' 키워드를 사용하면 리스트 요소의 속성에 기반해 변환할 수 있습니다.
- select/reject: 조건이 있는 for 루프로, 조건 결과에 따라 일치(또는 불일치)하는 리스트의 부분집합을 만들 수 있게 해줘요.
- selectattr/rejectattr: 위와 매우 비슷하지만 조건문에 리스트 요소의 특정 속성을 사용합니다.
지수 백오프(exponential backoff)를 만들기 위해 루프를 사용해 보세요.
- name: try wait_for_connection up to 10 times with exponential delay
ansible.builtin.wait_for_connection:
delay: '{{ item | int }}'
timeout: 1
loop: '{{ range(1, 11) | map("pow", 2) }}'
loop_control:
extended: true
ignore_errors: "{{ not ansible_loop.last }}"
register: result
when: result is not defined or result is failed
리스트 요소와 일치하는 키를 딕셔너리에서 추출하기 (Extract keys from a dictionary matching elements from a list)
이에 해당하는 Python 코드는:
chains = [1, 2]
for chain in chains:
for config in chains_config[chain]['configs']:
print(config['type'])
Ansible에서는 여러 가지 방법이 있는데, 그중 하나의 예시입니다:
딕셔너리 목록에서 일치하는 키를 추출하는 방법
tasks:
- name: Show extracted list of keys from a list of dictionaries
ansible.builtin.debug:
msg: "{{ chains | map('extract', chains_config) | map(attribute='configs') | flatten | map(attribute='type') | flatten }}"
vars:
chains: [1, 2]
chains_config:
1:
foo: bar
configs:
- type: routed
version: 0.1
- type: bridged
version: 0.2
2:
foo: baz
configs:
- type: routed
version: 1.0
- type: bridged
version: 1.1
debug 작업의 결과, 추출된 키를 담은 리스트:
ok: [localhost] => {
"msg": [
"routed",
"bridged",
"routed",
"bridged"
]
}
호스트마다 다른 변수의 고유 값 리스트 얻기:
vars:
unique_value_list: "{{ groups['all'] | map ('extract', hostvars, 'varname') | list | unique}}"
마운트 지점 찾기 (Find mount point)
이 경우 머신들에서 주어진 경로에 대한 마운트 지점을 찾고 싶어요. 이미 mount 팩트를 수집하고 있으므로 다음을 사용할 수 있습니다:
selectattr로 마운트를 필터링해 목록으로 만든 뒤 정렬하고 마지막 선택하기
- hosts: all
gather_facts: True
vars:
path: /var/lib/cache
tasks:
- name: The mount point for {{path}}, found using the Ansible mount facts, [-1] is the same as the 'last' filter
ansible.builtin.debug:
msg: "{{(ansible_facts.mounts | selectattr('mount', 'in', path) | list | sort(attribute='mount'))[-1]['mount']}}"
같은 딕셔너리 목록에서 값 결합하기 (Combine values from same list of dicts)
위 예시들의 양수·음수 필터를 결합하면, 값이 있을 때 '값'을, 없을 때 '폴백(fallback)'을 얻을 수 있어요.
필요에 따라 selectattr와 rejectattr로 ansible_host 또는 inventory_hostname 얻기
- hosts: localhost
tasks:
- name: Check hosts in inventory that respond to ssh port
wait_for:
host: "{{ item }}"
port: 22
loop: '{{ has_ah + no_ah }}'
vars:
has_ah: '{{ hostvars|dictsort|selectattr("1.ansible_host", "defined")|map(attribute="1.ansible_host")|list }}'
no_ah: '{{ hostvars|dictsort|rejectattr("1.ansible_host", "defined")|map(attribute="0")|list }}'
변수 기반의 커스텀 fileglob (Custom Fileglob Based on a Variable)
이 예시는 Python 인자 목록 언패킹(argument list unpacking)을 사용해 변수에 기반한 커스텀 fileglob 목록을 만듭니다.
변수 기반 리스트로 fileglob 사용하기
- hosts: all
vars:
mygroups:
- prod
- web
tasks:
- name: Copy a glob of files based on a list of groups
copy:
src: "{{ item }}"
dest: "/tmp/{{ item }}"
loop: '{{ q("fileglob", *globlist) }}'
vars:
globlist: '{{ mygroups | map("regex_replace", "^(.*)$", "files/\\1/*.conf") | list }}'
복합 타입 변환 (Complex Type transformations)
Jinja는 단순 데이터 타입 변환(int, bool 등)을 위한 필터를 제공하지만, 데이터 구조를 변환하려 할 때는 그렇게 쉽지 않아요. 위에서 보여준 것처럼 루프와 리스트 컴프리헨션을 사용하고, 다른 필터와 룩업을 연결해 더 복잡한 변환을 이룰 수 있습니다.
리스트에서 딕셔너리 만들기 (Create dictionary from list)
대부분의 언어에서는 쌍(pair)의 리스트에서 딕셔너리(맵/연관 배열/해시 등으로도 불림)를 만드는 것이 쉬워요. Ansible에는 여러 가지 방법이 있으며, 데이터의 출처에 따라 가장 좋은 방법이 달라질 수 있습니다.
다음 예시들은 {"a": "b", "c": "d"}를 만들어냅니다.
리스트를 [key, value, key, value, …]로 가정해 단순 리스트를 dict로:
vars:
single_list: [ 'a', 'b', 'c', 'd' ]
mydict: "{{ dict(single_list[::2] | zip_longest(single_list[1::2])) }}"
쌍의 리스트가 있으면 더 단순합니다:
vars:
list_of_pairs: [ ['a', 'b'], ['c', 'd'] ]
mydict: "{{ dict(list_of_pairs) }}"
둘 다 같은 결과가 나와요. zip_longest가 single_list를 list_of_pairs 생성자로 변환하기 때문입니다.
조금 더 복잡하게, set_fact와 loop를 사용해 2개의 리스트에서 키-값 쌍으로 딕셔너리를 만들거나 업데이트할 수 있습니다:
리스트 집합에서 set_fact로 딕셔너리 만들기
- name: Uses 'combine' to update the dictionary and 'zip' to make pairs of both lists
ansible.builtin.set_fact:
mydict: "{{ mydict | default({}) | combine({item[0]: item[1]}) }}"
loop: "{{ (keys | zip(values)) | list }}"
vars:
keys:
- foo
- var
- bar
values:
- a
- b
- c
이 결과는 {"foo": "a", "var": "b", "bar": "c"}입니다.
이 단순한 예시들을 다른 필터와 룩업과 결합해, 변수 이름에 패턴을 매칭해 딕셔너리를 동적으로 만들 수도 있어요:
작업 없이 리스트 집합에서 'vars'로 딕셔너리 정의하기
vars:
xyz_stuff: 1234
xyz_morestuff: 567
myvarnames: "{{ q('varnames', '^xyz_') }}"
mydict: "{{ dict(myvarnames|map('regex_replace', '^xyz_', '')|list | zip(q('vars', *myvarnames))) }}"
이 두 줄은 풀어야 할 것이 많으므로 간단히 설명할게요:
varnames룩업은 "xyz_로 시작하는" 변수 목록을 반환해요.- 그다음 이전 단계의 목록을
vars룩업에 넣어 값 목록을 얻습니다.*는 '리스트를 역참조(dereference)하는'(Jinja에서 동작하는 파이썬 방식) 데 사용하며, 그렇지 않으면 리스트를 단일 인자로 취급합니다. - 두 목록은
zip필터에 전달되어 (key, value, key2, value2, …) 통합 목록으로 짝을 지어요. - 그런 다음 dict 함수가 이 '쌍의 리스트'를 받아 딕셔너리를 만듭니다.
팩트를 사용해 조건 X를 만족하는 호스트의 데이터를 찾는 예시:
vars:
uptime_of_host_most_recently_rebooted: "{{ansible_play_hosts_all | map('extract', hostvars, 'ansible_uptime_seconds') | sort | first}}"
호스트 업타임을 일/시/분/초로 보여주는 예시(팩트가 수집되었다고 가정):
- name: Show the uptime in days/hours/minutes/seconds
ansible.builtin.debug:
msg: Uptime {{ now().replace(microsecond=0) - now().fromtimestamp(now(fmt='%s') | int - ansible_uptime_seconds) }}
더 보기 (See also)
- 필터로 데이터 다루기 — Ansible에 포함된 Jinja2 필터.
- 테스트 (Tests) — Ansible에 포함된 Jinja2 테스트.
- Jinja2 문서 — 핵심 필터와 테스트 목록을 포함한 Jinja2 문서.
더 알아보기 (Learn more)
- 이 페이지에서 쓴 필터와 룩업의 전체 목록은 "필터로 데이터 다루기(playbooks_filters)"와 "테스트(playbooks_tests)" 페이지에서 확인할 수 있어요.