LabHub
배우기 러닝패스 코스

Ansible in Practice

Jinja2 — From Data to Config File

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

한 줄 요약

템플릿은 "환경마다 다른 설정 파일"을 하나의 틀 + 환경별 데이터로 분해한다.

Concept map: 하나의 틀 + 환경별 데이터 · 필터 · 공백 · 첫째, 렌더링 결과가 매번 달라지는 템플릿.

왜 이게 필요했나

nginx 설정을 dev·stage·prod 각각 두면 세 파일이 서로 달라진다. 백엔드 서버가 늘 때마다 세 곳을 고쳐야 하고, 한 곳을 빠뜨리면 그 환경만 트래픽이 몰린다. 템플릿은 서버 목록을 데이터로 두고 틀을 한 번만 쓰게 만든다. 백엔드가 늘면 데이터 한 줄만 늘어난다.

어떻게 동작하나

Jinja2 에서 쓰는 문법은 세 가지뿐이다.

여기에 필터가 붙는다. {{ name | upper }}, {{ timeout | default(30) }}, {{ data | to_nice_json }} 처럼 파이프로 이어 쓴다. default 필터는 특히 중요하다 — 정의되지 않았을 수 있는 값에 안전한 대체값을 주어, 변수가 없을 때 <no value> 나 오류가 나가는 것을 막는다.

가장 자주 문제가 되는 것은 공백이다. {% for %} 같은 제어 구문은 그 자체가 한 줄을 차지하므로 렌더링 결과에 빈 줄이 남는다. YAML 설정이라면 이 빈 줄이 파일을 깨뜨리기도 한다. 해결책은 두 가지다. 구문에 {%- / -%} 를 써서 앞뒤 공백을 지우거나, template 모듈에 trim_blocks/lstrip_blocks 를 켜는 것이다.

template 모듈의 두 옵션도 알아 두면 사고를 막는다. validate 는 렌더링 결과를 지정한 명령으로 검사해 통과할 때만 배치한다. backup: true 는 덮어쓰기 전에 사본을 남긴다. 설정 파일을 자동으로 배포하는 순간, 잘못된 설정이 서비스를 죽이는 경로가 열린다. validate 는 그 경로를 막는 가장 싼 장치다.

현장에서 만나는 모습

첫째, 렌더링 결과가 매번 달라지는 템플릿. 타임스탬프를 주석에 넣으면 파일이 매번 바뀌어 핸들러가 매번 돌고 서비스가 매번 재시작된다. 이 한 줄 때문에 무중단 배포가 무너지는 사례가 흔하다.

둘째, 팩트를 쓰는 템플릿. ansible_facts 의 값을 그대로 쓰면 서버마다 다른 설정이 자동으로 나온다. 다만 특수 환경에서는 팩트가 예상 밖 값을 내므로 default 로 방어선을 둔다.

셋째, 템플릿에 로직을 너무 많이 넣기. if 가 다섯 겹 쌓이면 그것은 템플릿이 아니라 프로그램이다. 그런 분기는 변수 계산 단계로 끌어올려 템플릿은 단순하게 유지하는 편이 유지보수에 낫다.

템플릿에서 자주 쓰는 필터

Jinja2 의 값어치는 필터에서 나옵니다. 이것들만 알아도 대부분 됩니다.

{{ port | default(8080) }}              값이 없으면 기본값
{{ name | mandatory }}                  없으면 에러 — 조용한 빈 값을 막는다
{{ items | join(',') }}                 목록을 문자열로
{{ config | to_nice_yaml(indent=2) }}   딕셔너리를 YAML 블록으로
{{ secret | b64encode }}                쿠버네티스 시크릿용
{{ path | basename }}                   경로 조각
{{ hosts | map(attribute='ip') | list }} 목록에서 필드만 뽑기

mandatory 를 쓰는 습관 이 중요합니다. 변수가 없으면 Jinja2 는 기본적으로 빈 문자열을 넣습니다. 그러면 listen ; 같은 깨진 설정이 만들어지고, 문제는 서비스가 재시작할 때 드러납니다.

ansible.cfg 에 이렇게 두면 정의되지 않은 변수를 즉시 오류로 만듭니다.

[defaults]
error_on_undefined_vars = True

공백을 다루는 법

생성된 파일에 빈 줄이 잔뜩 들어가는 것은 대부분 제어 구조 때문입니다.

{% for h in hosts %}
server {{ h }};
{% endfor %}

이렇게 쓰면 {% %} 줄마다 개행이 남습니다. 하이픈을 붙여 제거합니다.

{% for h in hosts -%}
server {{ h }};
{% endfor -%}

앤서블의 template 모듈은 기본으로 trim_blocks 를 켜지 않으므로, 필요하면 템플릿 첫 줄에 지시합니다.

#jinja2: trim_blocks: True, lstrip_blocks: True

템플릿을 시험하는 법

배포하기 전에 결과를 봅니다.

# 렌더링 결과만 보기 (파일을 쓰지 않는다)
ansible -i inv web -m template -a "src=nginx.conf.j2 dest=/tmp/out.conf" --check --diff

# 문법 검사
ansible-playbook site.yml --syntax-check

# 실제로 무엇이 바뀌는지
ansible-playbook site.yml --check --diff

--check --diff 조합이 가장 유용합니다. 바꾸지 않고 무엇이 바뀔지 보여 줍니다. 운영 배포 전에 이것을 돌리지 않으면 설정 한 줄이 서비스를 멈춥니다.

그리고 생성된 설정을 그 프로그램의 검사기로 확인하는 단계를 넣습니다.

- name: nginx 설정
  template:
    src: nginx.conf.j2
    dest: /etc/nginx/nginx.conf
    validate: 'nginx -t -c %s'      # ← 실패하면 파일을 바꾸지 않는다
  notify: reload nginx

validate 는 임시 파일로 검사한 뒤 통과할 때만 옮깁니다. 깨진 설정이 디스크에 닿지 않습니다.

다음 실습에서 할 것

기본 렌더링에서 시작해 필터·반복·조건을 차례로 쓰고, 공백 제어로 결과를 깔끔하게 만든다. 팩트와 inventory_hostname 을 템플릿에서 쓰고, validate 로 잘못된 설정이 배치되지 않게 막는다. 마지막에는 그룹 변수의 백엔드 목록에서 nginx 설정을 통째로 생성한다.