콘텐츠로 건너뛰기

Ansible을 이용하여 Linux 업데이트 자동화 (4편) - 자동 재부팅

  • 기준

Ansible을 이용하여 Linux 업데이트 자동화 (3편)에서 이어지는 글이다.

2편의 update_all.yml패키지 목록 갱신 → 최신 버전 업그레이드 → 불필요 패키지 정리까지만 수행하는 기본형이었다. 즉, 업데이트는 자동화하지만 업데이트 후 재부팅이 필요한지 여부는 판단하지 않았다. 4편에서는 업데이트 후 재부팅 필요 여부를 감지하고, 필요한 경우 자동 재부팅을 수행한다.
즉, 2편이 "업데이트까지만 자동화"했다면, 4편은 "업데이트 후 재부팅 판단과 재부팅까지 자동화"하는 편이라고 이해하면 된다.

이번 편의 핵심은 패키지 업데이트 자체가 아니라, 업데이트 이후 재부팅이 필요한 상황을 어떻게 자동으로 처리할지에 있다.
Debian/Ubuntu 계열은 needrestart/run/reboot-required를 활용하고, RHEL 계열은 dnf needs-restarting --reboothint를 활용한다.

1) Debian/Ubuntu 계열 자동 재부팅 (~/ansible-lab/playbooks/update_all.yml)

Debian/Ubuntu 계열은 업데이트 후 needrestart/run/reboot-required를 이용해 재부팅 필요 여부를 판단하고, 정말 필요한 경우에만 ansible.builtin.reboot로 자동 재부팅을 수행한다.

# ~/ansible-lab/playbooks/update_all.yml

---
- name: Update Debian family systems
  hosts: debian_family
  serial: 1
  become: true
  gather_facts: true

  tasks:
    - name: Refresh apt cache
      ansible.builtin.apt:
        update_cache: true
        cache_valid_time: 3600

    - name: Upgrade installed packages to latest version
      ansible.builtin.apt:
        name: "*"
        state: latest

    - name: Remove no longer required packages
      ansible.builtin.apt:
        autoremove: true

    - name: Ensure needrestart is installed
      ansible.builtin.apt:
        name: needrestart
        state: present

    - name: Check Debian/Ubuntu reboot-required flag
      ansible.builtin.stat:
        path: /run/reboot-required
      register: deb_reboot_required_file

    - name: Run needrestart in batch mode
      ansible.builtin.command: needrestart -b
      register: deb_needrestart
      changed_when: false
      failed_when: false
      when: not ansible_check_mode

    - name: Show needrestart summary
      ansible.builtin.debug:
        msg: "{{ deb_needrestart.stdout_lines | default([]) }}"
      when: not ansible_check_mode

    - name: Reboot Debian-family host if reboot is required
      ansible.builtin.reboot:
        msg: "Reboot initiated by Ansible after Debian/Ubuntu updates"
        reboot_timeout: 900
        post_reboot_delay: 10
        test_command: whoami
      when:
        - not ansible_check_mode
        - deb_reboot_required_file.stat.exists
          or ((deb_needrestart.stdout | default('')) is search('NEEDRESTART-KSTA:\\s*[23]\\b'))

    - name: Warn if services or sessions still need restart without full reboot
      ansible.builtin.debug:
        msg:
          - "needrestart reported restartable services or sessions."
          - "Consider restarting affected services if no full reboot was performed."
      when:
        - not ansible_check_mode
        - not deb_reboot_required_file.stat.exists
        - not ((deb_needrestart.stdout | default('')) is search('NEEDRESTART-KSTA:\\s*[23]\\b'))
        - deb_needrestart.stdout is defined
        - deb_needrestart.stdout | length > 0

위 코드는 Debian/Ubuntu 계열에서 재부팅 필요 여부를 확인하는 단계와, 필요 시 자동으로 재부팅하는 단계를 추가했다.
이제, 위 코드를 차근차근 뜯어보자.

1-1) needrestart 설치 (Debian/Ubuntu 계열)

- name: Ensure needrestart is installed
  ansible.builtin.apt:
    name: needrestart
    state: present

Debian/Ubuntu 계열 서버에서 needrestart를 설치해, 업데이트 후 커널 재부팅 필요 여부재시작이 필요한 서비스/세션 정보를 확인할 수 있게 만든다. needrestart -b는 batch mode로 동작하며, 화면 대화상자를 띄우거나 직접 재시작을 수행하지 않고 결과만 출력한다.

  • - name: Ensure needrestart is installed 이 작업의 이름이다.
  • ansible.builtin.apt: Debian/Ubuntu 계열에서 사용하는 APT 패키지 관리자를 다루는 Ansible 모듈이다. 즉, apt install … 같은 작업을 자동화하는 역할이다.
  • name: needrestart 설치하거나 관리할 패키지 이름이 needrestart라는 뜻이다.
  • state: present 해당 패키지가 설치된 상태로 존재해야 한다는 의미다. 이미 설치되어 있으면 아무것도 하지 않고, 설치되어 있지 않으면 설치한다.

1-2) /run/reboot-required 확인 (Debian/Ubuntu 계열)

- name: Check Debian/Ubuntu reboot-required flag
  ansible.builtin.stat:
    path: /run/reboot-required
  register: deb_reboot_required_file

이 단계는 Debian/Ubuntu 계열에서 많이 쓰이는 기본 재부팅 신호를 확인하는 부분이다. /run/reboot-required 파일이 존재하면 "업데이트 후 재부팅이 필요하다"는 뜻으로 볼 수 있다. stat 모듈은 파일 존재 여부 같은 파일 상태를 확인할 때 사용하는 Ansible 기본 모듈이다.

  • - name: Check Debian/Ubuntu reboot-required flag 이 작업의 이름이다.
  • ansible.builtin.stat: stat 모듈은 파일이나 디렉터리의 상태 정보를 확인할 때 쓰는 모듈이다.
  • path: /run/reboot-required 확인할 대상 경로가 /run/reboot-required 라는 뜻이다. Debian/Ubuntu에서는 패키지 업데이트 후, 특히 커널이나 핵심 시스템 라이브러리 등이 바뀌어서 재부팅이 권장되거나 필요한 경우, 이 파일이 생성되는 경우가 많다.
  • register: deb_reboot_required_file 이 태스크의 실행 결과를 deb_reboot_required_file 변수에 저장하겠다는 뜻이다. 즉, 이후 다른 태스크에서 이 값을 참조할 수 있게 된다.

1-3) needrestart -b 실행 (Debian/Ubuntu 계열)

- name: Run needrestart in batch mode
  ansible.builtin.command: needrestart -b
  register: deb_needrestart
  changed_when: false
  failed_when: false
  when: not ansible_check_mode

이 부분이 4편의 핵심이다. needrestart -b를 실행해 현재 시스템이 재부팅이 필요한 상태인지, 혹은 서비스 재시작만 필요한 상태인지를 텍스트로 받아온다. KSTA=1은 재부팅 필요 없음, KSTA=2KSTA=3은 커널 업그레이드가 반영되지 않아 재부팅이 필요한 상태를 뜻한다. 또 when: not ansible_check_mode를 넣은 이유는, check mode에서는 실제 명령 실행이 건너뛰어질 수 있기 때문이다. Ansible 문서도 ansible_check_mode 변수가 check mode 여부를 나타내며, check mode에서 일부 태스크를 건너뛸 때 쓸 수 있다고 설명한다.

  • - name: Run needrestart in batch mode 이 작업의 이름이다.
  • ansible.builtin.command: needrestart -b Ansible의 command 모듈로 needrestart -b 명령을 실행한다. needrestart 업데이트 후에 재부팅이 필요한지, 재시작이 필요한 서비스가 있는지, 세션에 영향이 있는지 등을 점검하는 도구다.
  • -b batch mode 옵션이다. 즉, 사람에게 질문하지 않고 결과를 출력만 하도록 실행한다.
  • register: deb_needrestart 명령 실행 결과를 deb_needrestart 변수에 저장한다. 즉, 이후 태스크에서 이 변수 안의 내용을 꺼내 쓸 수 있다.
  • changed_when: false Ansible에게 이 태스크는 시스템 상태를 바꾸는 작업으로 간주하지 말라고 알려주는 설정이다. 왜냐하면 needrestart -b는 단순히 확인만 하는 명령이지, 패키지를 설치하거나 설정을 변경하는 작업이 아니기 때문이다. 이 설정이 없으면 Ansible은 command 실행을 보통 changed로 표시할 수 있다. 하지만 실제로는 상태를 바꾸지 않으므로 false로 명시해 주는 것이 맞다.
  • failed_when: false 이 태스크는 명령이 어떤 종료 코드를 내더라도 Ansible 태스크 자체를 실패로 처리하지 않도록 한다. 이걸 넣은 이유는 needrestart가 상황에 따라 0이 아닐 수도 있고, 경고성 메시지를 낼 수도 있고, 재부팅/재시작 관련 상태를 표현하는 과정에서 일반적인 성공/실패와 다르게 동작할 수도 있기 때문이다. 즉, 여기서는 명령이 정상적인 점검 결과를 내는 것이 더 중요하고, 그 결과를 나중에 직접 해석하려는 의도다.
  • when: not ansible_check_mode 이 태스크는 체크 모드(--check)에서는 실행하지 않겠다는 뜻이다.

1-4) needrestart 결과 출력 (Debian/Ubuntu 계열)

- name: Show needrestart summary
  ansible.builtin.debug:
    msg: "{{ deb_needrestart.stdout_lines | default([]) }}"

이 단계는 자동 재부팅 자체를 수행하는 부분은 아니지만, 매우 중요하다. 실제로 어떤 서비스가 재시작 필요 상태인지, 현재 커널과 기대 커널이 같은지 다른지를 눈으로 확인할 수 있기 때문이다. 즉, 4편은 단순히 "재부팅한다"가 아니라, 왜 재부팅하는지 로그로 설명 가능한 구조가 되었다고 이해하면 된다. register 변수는 같은 play 안에서 후속 조건식과 출력에 재사용할 수 있다.

1-5) 조건부 자동 재부팅 (Debian/Ubuntu 계열)

- name: Reboot Debian-family host if reboot is required
  ansible.builtin.reboot:
    msg: "Reboot initiated by Ansible after Debian/Ubuntu updates"
    reboot_timeout: 900
    post_reboot_delay: 10
    test_command: whoami
  when:
    - not ansible_check_mode
    - deb_reboot_required_file.stat.exists
      or ((deb_needrestart.stdout | default('')) is search('NEEDRESTART-KSTA:\\s*[23]\\b'))

2편과 가장 큰 차이가 바로 이 부분이다. 이제는 업데이트가 끝난 뒤 무조건 재부팅하는 것이 아니라, 아래 두 조건 중 하나를 만족할 때만 자동 재부팅을 수행한다.
/run/reboot-required 파일이 존재할 때
needrestart 결과에서 KSTA가 2 또는 3일 때
즉, 재부팅이 꼭 필요한 경우에만 재부팅한다. ansible.builtin.reboot 모듈은 단순히 재부팅 명령만 내리는 것이 아니라, 서버가 내려갔다가 다시 올라오고, test_command가 성공할 때까지 기다린다. reboot_timeout은 기다리는 최대 시간, post_reboot_delay는 재부팅 직후 추가로 기다릴 시간을 뜻한다.

1-6) 재부팅은 필요 없지만 재시작이 필요한 경우 안내 (Debian/Ubuntu 계열)

- name: Warn if services or sessions still need restart without full reboot
  ansible.builtin.debug:
    msg:
      - "needrestart reported restartable services or sessions."
      - "Consider restarting affected services if no full reboot was performed."

이 부분은 "재부팅까지는 필요 없지만, 일부 서비스나 세션은 다시 시작해야 할 수 있다"는 점을 알려주는 안내 메시지다. 실제 운영에서는 항상 전체 서버 재부팅이 필요한 것은 아니고, 경우에 따라 특정 서비스만 재시작하면 충분할 수 있다.
그래서 4편 코드는 재부팅 필요서비스 재시작 필요를 구분해서 보여주는 형태로 한 단계 더 현실적인 운영형 코드가 되었다. needrestart는 바로 이런 용도로 많이 사용된다.

2) RHEL 계열 자동 재부팅 (~/ansible-lab/playbooks/update_all.yml)

# ~/ansible-lab/playbooks/update_all.yml

- name: Update RHEL family systems
  hosts: rhel_family
  serial: 1
  become: true
  gather_facts: true

  tasks:
    - name: Ensure dnf-plugins-core is installed
      ansible.builtin.dnf:
        name: dnf-plugins-core
        state: present

    - name: Refresh dnf cache and upgrade installed packages
      ansible.builtin.dnf:
        name: "*"
        state: latest
        update_cache: true
        update_only: true

    - name: Remove no longer required packages
      ansible.builtin.dnf:
        autoremove: true

    - name: Check whether reboot is required on RHEL family
      ansible.builtin.command: dnf needs-restarting --reboothint
      register: rhel_reboot_hint
      changed_when: false
      failed_when: rhel_reboot_hint.rc not in [0, 1]
      when: not ansible_check_mode

    - name: Reboot RHEL-family host if required
      ansible.builtin.reboot:
        msg: "Reboot initiated by Ansible after package updates"
        reboot_timeout: 900
        post_reboot_delay: 10
        test_command: whoami
      when:
        - not ansible_check_mode
        - rhel_reboot_hint.rc == 1

RHEL 계열은 dnf needs-restarting --reboothint를 활용한다.
RHEL 계열 코드를 차근차근 뜯어보자.

2-1) 재부팅 필요 여부 확인 (RHEL 계열)

- name: Check whether reboot is required on RHEL family
  ansible.builtin.command: dnf needs-restarting --reboothint
  register: rhel_reboot_hint
  changed_when: false
  failed_when: rhel_reboot_hint.rc not in [0, 1]
  when: not ansible_check_mode

RHEL 계열에서는 Debian/Ubuntu의 needrestart 대신 dnf needs-restarting --reboothint를 사용해 재부팅 필요 여부를 확인한다.
이 기능은 dnf-plugins-core에 포함된 needs-restarting 플러그인에 의해 제공된다.
--reboothint 옵션은 재부팅이 필요하면 종료 코드 1, 필요하지 않으면 종료 코드 0을 반환한다. 따라서 Ansible에서는 이 종료 코드를 기준으로 재부팅 여부를 판단할 수 있다.
changed_when: false는 단순 확인 명령을 상태 변경으로 표시하지 않게 하기 위한 설정이고, failed_when: rhel_reboot_hint.rc not in [0, 1] 설정은 정상적인 반환값 범위를 명확히 지정한 것이다.

참고로 이 태스크는 실제 실행 시 재부팅 필요 여부를 확인하기 위한 것이며, check mode에서는 실행 결과가 기대와 다를 수 있다.

2-2) 조건부 자동 재부팅 (RHEL 계열)

- name: Reboot RHEL-family host if required
  ansible.builtin.reboot:
    msg: "Reboot initiated by Ansible after package updates"
    reboot_timeout: 900
    post_reboot_delay: 10
    test_command: whoami
  when:
    - not ansible_check_mode
    - rhel_reboot_hint.rc == 1

RHEL 계열도 Debian/Ubuntu와 마찬가지로, 정말 필요한 경우에만 자동 재부팅하도록 구성했다.
위 태스크는 앞 단계에서 확인한 rhel_reboot_hint.rc 값이 1일 때만 실행된다.
즉, dnf needs-restarting --reboothint가 재부팅 필요 신호를 줄 때만 재부팅이 수행된다.
또한 not ansible_check_mode 조건을 함께 둔 이유는, 이 태스크가 실제 실행 시에만 동작하도록 하기 위해서이다.
check mode (--check)는 실제 변경 없이 예행연습만 수행하는 모드이므로, 재부팅 같은 실제 변경 작업은 실행하지 않는 편이 자연스럽다.
또 앞 단계의 재부팅 필요 여부 확인 태스크도 check mode에서는 건너뛸 수 있으므로, 재부팅 태스크에도 같은 조건을 넣어 두면 변수 참조 문제를 줄이고 코드 흐름도 더 일관되게 유지할 수 있다.
ansible.builtin.reboot 모듈은 단순히 재부팅 명령만 내리는 것이 아니라, 서버가 내려갔다가 다시 올라오고, 지정한 test_command가 성공할 때까지 기다린다.
여기서는 whoami 명령으로 재부팅 이후 시스템이 정상적으로 응답하는지 확인한다.

3) Debian/Ubuntu 계열과 RHEL 계열을 포함한 최종 ~/ansible-lab/playbooks/update_all.yml 코드

# ~/ansible-lab/playbooks/update_all.yml

---
- name: Update Debian family systems
  hosts: debian_family
  serial: 1
  become: true
  gather_facts: true

  tasks:
    - name: Refresh apt cache
      ansible.builtin.apt:
        update_cache: true
        cache_valid_time: 3600

    - name: Upgrade installed packages to latest version
      ansible.builtin.apt:
        name: "*"
        state: latest

    - name: Remove no longer required packages
      ansible.builtin.apt:
        autoremove: true

    - name: Ensure needrestart is installed
      ansible.builtin.apt:
        name: needrestart
        state: present

    - name: Check Debian/Ubuntu reboot-required flag
      ansible.builtin.stat:
        path: /run/reboot-required
      register: deb_reboot_required_file

    - name: Run needrestart in batch mode
      ansible.builtin.command: needrestart -b
      register: deb_needrestart
      changed_when: false
      failed_when: false
      when: not ansible_check_mode

    - name: Show needrestart summary
      ansible.builtin.debug:
        msg: "{{ deb_needrestart.stdout_lines | default([]) }}"
      when: not ansible_check_mode

    - name: Reboot Debian-family host if reboot is required
      ansible.builtin.reboot:
        msg: "Reboot initiated by Ansible after Debian/Ubuntu updates"
        reboot_timeout: 900
        post_reboot_delay: 10
        test_command: whoami
      when:
        - not ansible_check_mode
        - deb_reboot_required_file.stat.exists
          or ((deb_needrestart.stdout | default('')) is search('NEEDRESTART-KSTA:\\s*[23]\\b'))

    - name: Warn if services or sessions still need restart without full reboot
      ansible.builtin.debug:
        msg:
          - "needrestart reported restartable services or sessions."
          - "Consider restarting affected services if no full reboot was performed."
      when:
        - not ansible_check_mode
        - not deb_reboot_required_file.stat.exists
        - not ((deb_needrestart.stdout | default('')) is search('NEEDRESTART-KSTA:\\s*[23]\\b'))
        - deb_needrestart.stdout is defined
        - deb_needrestart.stdout | length > 0

- name: Update RHEL family systems
  hosts: rhel_family
  serial: 1
  become: true
  gather_facts: true

  tasks:
    - name: Ensure dnf-plugins-core is installed
      ansible.builtin.dnf:
        name: dnf-plugins-core
        state: present

    - name: Refresh dnf cache and upgrade installed packages
      ansible.builtin.dnf:
        name: "*"
        state: latest
        update_cache: true
        update_only: true

    - name: Remove no longer required packages
      ansible.builtin.dnf:
        autoremove: true

    - name: Check whether reboot is required on RHEL family
      ansible.builtin.command: dnf needs-restarting --reboothint
      register: rhel_reboot_hint
      changed_when: false
      failed_when: rhel_reboot_hint.rc not in [0, 1]
      when: not ansible_check_mode

    - name: Reboot RHEL-family host if required
      ansible.builtin.reboot:
        msg: "Reboot initiated by Ansible after package updates"
        reboot_timeout: 900
        post_reboot_delay: 10
        test_command: whoami
      when:
        - not ansible_check_mode
        - rhel_reboot_hint.rc == 1
playbook 실행
playbook 실행

정리하면, 4편의 핵심은 업데이트 후 재부팅 필요 여부를 감지하고, 필요한 경우에만 자동 재부팅을 수행하는 것이다.
Debian/Ubuntu 계열은 needrestart/run/reboot-required를 함께 사용하고, RHEL 계열은 dnf needs-restarting --reboothint를 사용한다.

다만 RHEL 계열에서 어떤 패키지가 실제로 업그레이드·설치·제거되는지 자세히 미리 확인하는 방법은 다음 5편에서 이어서 다룬다.

Join the conversation

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다