최신글

RPM/DNF 설치 자동화가 lock 에러로 실패할 수 상황

반응형

RPM 또는 DNF 패키지 설치 자동화 스크립트가 낮은 확률로 lock 에러로 실패할 수 있습니다. 재실행하면 성공해서 넘어가기만 시간이 지나면 어느날 또 lock 에러가 발생할 수 있습니다.

왜 rpm/dnf는 lock을 쓸까요. 이 글은 그 원리에서 시작해 lock 확인 방법 그리고 Ansible에서 안전하게 기다리는 방법을 정리했습니다.

 

RPM 또는 dnf의 lock

rpm/dnf는 한 번에 한 작업만 실행되도록 lock을 사용하는데, 설치 스크립트가 병렬로 설치하거나 cron 업데이트와 겹치면, 뒤늦게 실행된 쪽은 lock을 얻지 못하고 실패합니다.

 

RPM/DNF는 왜 lock을 쓰는가

RPM은 database를 파일 lock으로 지킵니다. RPM database는 /var/lib/rpm에 있고, RPM은 /var/lib/rpm/.rpm.lock 파일에 fcntl write lock을 겁니다. 설치 트랜잭션(rpmtsRun)과 database 초기화·재빌드가 이 lock으로 직렬화됩니다. 익숙한 에러 warning: waiting for transaction lock on /var/lib/rpm/.rpm.lock가 바로 이 lock을 기다리는 상황입니다.

 

왜 database 엔진 내부 lock이 아니라 파일 lock일까요. 패키지 설치는 database 레코드만 바꾸는 게 아니라 파일 시스템에 실제 파일을 풀고 지우는 작업까지 포함합니다. database 트랜잭션 lock은 database 변경은 보호하지만 파일 시스템 변화까지 한 단위로 묶어주지는 못합니다. 설치 전체를 하나로 직렬화하려면 파일 lock이 필요합니다.

ls /var/lib/rpm

 

lock file

lock file은 파일을 lock처럼 사용하는 개념입니다. /tmp 경로에 lock 파일을 만들고, 명령어를 실행할 때마다 lock이 안 걸려 있는지 확인한 뒤 실행하는 식입니다.

 

리눅스에서는 flock 명령어로 이 lock file을 직접 실습할 수 있습니다. flock은 지정한 파일에 lock을 걸고, 그 lock을 잡은 채로 뒤에 오는 명령어를 실행합니다.

# lock 파일 생성
touch /tmp/akbun.lock

# sleep 명령어를 실행하기 전에 lock 획득
flock /tmp/akbun.lock sleep 60

 

누군가 이미 lock을 걸었다면 lock 획득에 실패하고, 뒤의 명령어는 실행되지 않습니다. -n은 lock을 기다리지 않고 바로 실패하라는 옵션입니다.

flock -n /tmp/akbun.lock echo "lock acquired" || echo "lock busy"

 

lock이 걸렸는지 확인하기

lock이 걸려 있는지 확인하는 방법은 여러가지가 있고 4가지를 소개합니다.

 

첫째, fuser로 lock 파일을 누가 잡고 있는지 봅니다. lock이 안 걸려 있으면 결과가 비어 있고, 걸려 있으면 파일을 잡고 있는 프로세스가 나옵니다.

 

둘째, DNF가 남기는 pid 파일을 확인합니다. DNF는 lock을 설정한 process id를 metadata·rpmdb·download 작업별로 파일에 기록합니다.

/var/cache/dnf/metadata_lock.pid
/var/lib/dnf/rpmdb_lock.pid
/var/cache/dnf/download_lock.pid

 

DNF의 lock은 실제 파일 lock이 아니라 이 pid 파일의 존재와 안에 적힌 프로세스가 살아 있는지를 확인하는 방식입니다. 그래서 프로세스가 비정상 종료하면 lock 파일이 남아 오작동할 수 있습니다.

 

셋째, 로그를 확인합니다. DNF는 /var/log/dnf.log에 로그를 남깁니다. RPM은 별도 로그 파일이 없어서, database에 기록된 패키지 설치·변경 시간을 보고 lock을 추측해야 합니다.

# dnf
grep -iE 'lock|waiting for process|pid' /var/log/dnf.log
# rpm
rpm -qa --last

 

넷째, 패키지 매니저 프로세스가 실행 중인지 봅니다. 스크립트가 실행 즉시 lock으로 실패했다면 process 목록에서 dnf, rpm을 찾습니다.

pgrep -x 'dnf|dnf5|yum|packagekitd|rpm'

 

ansible playbook에서 lock 대기

dnf 모듈을 쓴다면 lock_timeout으로 lock을 기다립니다. lock이 풀릴 때까지 최대 지정한 초만큼 대기합니다.

- name: install packages
  ansible.builtin.dnf:
    name:
      - nginx
      - redis
    state: present
    lock_timeout: 60

 

dnf 모듈이 아니라 rpm 등을 직접 쓴다면 프로세스 실행 여부로 대기합니다. pgrep이 프로세스를 못 찾아 종료 코드가 0이 아닐 때(=패키지 매니저가 idle) 다음 단계로 넘어갑니다.

- name: wait until pkg manager is idle
  ansible.builtin.shell: pgrep -x 'dnf|dnf5|yum|packagekitd|rpm'
  register: pkg_busy
  until: pkg_busy.rc != 0
  retries: 36
  delay: 5
  changed_when: false
  failed_when: false

 

 

마치며, rpm 또는 dnf 자동화는 lock이 걸릴 수 있다는 것을 생각해야 한다.

rpm, dnf는 패키지가 꼬이지 않도록 한 번에 하나씩만 실행되게 lock을 씁니다. 손으로 명령어를 칠 때는 이 lock이 잘 보이지 않지만, 스크립트로 자동화하면 병렬 실행이나 cron 업데이트와 겹치는 순간 lock 충돌이 드러납니다. 그래서 자동화 스크립트를 짤 때는 lock을 대기하거나 패키지 작업이 겹치지 않게 하는 로직을 구현해야 합니다.

 

참고자료

반응형