명령어/Linux

[RHEL] Rocky 9 to 10 인플레이스 업그레이드 leapp 7관문과 distro-sync 강행

jykim23 2026. 7. 17. 18:55
반응형

Rocky 9 10 인플레이스 업그레이드 leapp 관문 distro-sync 우회 공식 미지원 강행 성공

부제: 공식적으로 안 된다는 Rocky 9→10 인플레이스 업그레이드를 끝까지 밀어붙여 성공시킨 기록

Rocky Linux는 메이저 버전 인플레이스 업그레이드를 공식 지원하지 않는다. 팀의 답은 한결같다 — "백업하고 새로 설치하세요." AlmaLinux의 ELevate(leapp 기반) 도구도 Rocky 9→10 경로는 아직 없다.

그런데 검증용 VM에 백업이 있었다. 그래서 끝까지 가봤다. "공식 불가"가 코드의 어느 지점에서 나오는지, 그리고 그걸 어떻게든 뚫을 수 있는지.

뜻밖의 출발점

먼저 놀란 것 하나. Rocky 9.8의 AppStream 저장소에 leapp-upgrade-el9toel10 패키지가 실제로 있었다. RHEL의 공식 업그레이드 도구 leapp이, Rocky에도 el9→el10 버전으로 들어와 있다. "미지원"이라더니 도구는 있네?

설치하고 leapp preupgrade를 돌렸다. 여기서부터 관문이 시작됐다.

관문들 — leapp이 막는 지점

관문 1. 타깃 버전을 못 정함. RHEL은 구독(RHSM)으로 타깃을 자동 인식하는데 Rocky는 구독이 없다. --target 10.0을 명시해 넘었다.

관문 2. rocky 서명 설정이 없음. leapp이 배포판별 GPG 서명 설정을 files/distro/ 아래에서 찾는데, 거기엔 almalinux·centos·rhel만 있고 정작 rocky가 없었다. Rocky가 배포한 패키지가 자기 서명 설정을 빠뜨린 것이다. Rocky의 진짜 GPG 키(9·10)로 distro/rocky/를 만들어 채웠다.

관문 3. 코드에 rocky가 통째로 없음. 서명 설정만으론 부족했다. leapp의 distro.py_DISTRO_REPOFILES_MAP과 표시명 맵에도 almalinux·centos·rhel만 하드코딩하고 rocky를 아예 구현하지 않았다. 소스에 rocky를 almalinux 복제로 끼워넣었다.

관문 4. CPU가 x86-64-v3 미지원. RHEL/Rocky 10은 CPU 명령어 레벨을 v3로 올렸다. VM의 CPU 모델이 낮으면 여기서 막히고, 넘겨도 업그레이드된 OS가 부팅을 못 한다. 가상화 CPU 타입을 host로 바꿔 실제 CPU(v4)를 노출시켰다.

관문 5. 버전 가드. "설치된 9.8은 미지원", "타깃 10.0은 미지원". leapp의 개발자용 환경변수(LEAPP_UNSUPPORTED=1, LEAPP_DEVEL_TARGET_RELEASE, LEAPP_DEVEL_SKIP_CHECK_OS_RELEASE)로 우회했다.

관문 6. 활성 타깃 저장소가 없음. Rocky가 repomap에 없으니 leapp이 el10 저장소를 모른다. Rocky 10 BaseOS·AppStream·CRB·Extras를 가리키는 저장소 파일을 직접 만들어 --enablerepo로 넘겼다.

여기서 leapp preupgradeinhibitor 0개로 통과했다. 관문 여섯 개를 다 연 것이다.

관문 7 — 진짜 벽

leapp upgrade를 돌리자 다운로드까지 가서 DNF 트랜잭션 계산에서 실패했다. 로그의 결정적 한 줄:

util-linux(el9) from @System does not belong to a distupgrade repository

leapp의 repomap.jsonpes-events.json(패키지 진화 데이터)에 Rocky가 없어서, 설치된 Rocky 9 패키지들이 "매핑 안 된 저장소 소속"으로 취급됐다. 그래서 el9→el10 트랜잭션을 계산할 수 없었다.

이건 설정이나 소스 몇 줄로 못 채운다. 수천 개 패키지 매핑 데이터가 필요한데, 그게 바로 ELevate가 지원 경로에만 제공하는 것이고 Rocky 9→10엔 아직 없다. "공식 미지원"의 실체가 여기 있었다 — 패키지는 있지만, 업그레이드를 계산할 데이터가 없다.

우회 — leapp을 버리고 distro-sync

leapp이 데이터가 없어 막힌다면, 데이터에 의존하지 않는 더 투박한 방법이 있다. dnf distro-sync 로 el10 저장소에 강제 동기화하는 것이다. RHEL 클론에서 비공식적으로 쓰이는 방법이다.

  • el10 GPG 키를 import하고, Rocky 10 저장소를 dnf에 노출
  • EPEL 등 서드파티는 비활성화(충돌 최소화)
  • dnf --releasever=10 --disablerepo=* --enablerepo=rocky10-* --allowerasing --nobest distro-sync

--allowerasing이 핵심이다. el10에 대응이 없는 패키지는 지우고 나머지를 통째로 el10로 맞춘다. leapp이 못 한 의존성 해결을, dnf가 1269개 패키지(1.6GB) 트랜잭션으로 풀어냈다.

뚫으면서 만난 el10의 함정들

업그레이드 도중과 직후, el10의 변경점이 실전으로 나타났다.

  • SSH가 끊겼다. systemd·glibc·openssh를 교체하는 순간 네트워크 스택이 흔들린다. detached로 돌린 덕에 트랜잭션은 박스 안에서 계속됐다.
  • NIC에 IP가 안 붙었다. el10은 NetworkManager의 ifcfg(network-scripts) 지원을 제거했다. 기존 ifcfg 연결을 못 읽어 IP를 못 받는다. keyfile 형식으로 연결을 새로 만들어(nmcli connection add ... ipv4.method auto) 살렸다.
  • 인터페이스 이름이 바뀌었다. enp6s18ens18.
  • GUI 터미널이 깨졌다. el9 위에서 돌던 데스크톱 세션의 user D-Bus가 라이브러리 교체로 무효화됐다. 텍스트 콘솔(TTY)로 가면 멀쩡하다.
  • rpmdb가 옮겨갔다. el10은 rpm DB를 /var/lib/rpm/usr/lib/sysimage/rpm으로 이전한다.

도착

정리 distro-sync를 한 번 더 돌리고, 사소한 실패 유닛(VM용 mcelog, 이미 끝난 rpmdb-migrate)을 정리한 뒤 재부팅했다.

Rocky Linux 10.2 (Red Quartz) / kernel 6.12 el10
IP 자동 복구 · systemctl is-system-running = running · 실패 유닛 0

재부팅 후 깨끗하게 Rocky 10으로 올라왔고, 네트워크도 자동으로 붙었다. 공식적으로 불가능한 업그레이드가 끝났다.

따라하기 — 전체 명령 (복사용)

⚠️ 반드시 스냅샷/백업 후 검증 VM에서만. 비공식·비지원 절차이며 운영 시스템 금지.

0) leapp·tmux 설치 + Rocky 10 GPG 키 확보 (self-contained)

dnf install -y leapp-upgrade-el9toel10 tmux
# Rocky 10 공식 키를 미러에서 (SSH로 업그레이드하다 끊겨도 tmux가 이어감)
curl -fsSL https://download.rockylinux.org/pub/rocky/RPM-GPG-KEY-Rocky-10 \
  -o /etc/pki/rpm-gpg/RPM-GPG-KEY-Rocky-10

1) 빠진 rocky 서명설정 생성 (관문 2)

# Rocky 10 GPG 키를 /etc/pki/rpm-gpg/RPM-GPG-KEY-Rocky-10 에 미리 배치
# (Rocky 10 시스템의 같은 파일을 복사하거나 미러에서 받는다)
D=/usr/share/leapp-repository/repositories/system_upgrade/common/files/distro/rocky
mkdir -p "$D/rpm-gpg/9" "$D/rpm-gpg/10"
cp -f /etc/pki/rpm-gpg/RPM-GPG-KEY-Rocky-9  "$D/rpm-gpg/9/"
cp -f /etc/pki/rpm-gpg/RPM-GPG-KEY-Rocky-10 "$D/rpm-gpg/10/"
cat > "$D/gpg-signatures.json" <<'JSON'
{
  "keys": {
    "702d426d350d275d": ["gpg-pubkey-350d275d-6279464b"],
    "5b106c736fedfc85": ["gpg-pubkey-6fedfc85-00000000"]
  },
  "obsoleted-keys": { "7": [], "8": [], "9": [], "10": [] }
}
JSON
키 ID는 자기 시스템에서 확인: rpm -q --qf '%{RSAHEADER:pgpsig}\n' bash (Rocky 9), Rocky 10 시스템에서 동일 명령.

2) distro.py에 rocky 추가 (관문 3)

python3 - <<'PY'
f="/usr/share/leapp-repository/repositories/system_upgrade/common/libraries/distro.py"
s=open(f).read()
blk="""_DISTRO_REPOFILES_MAP = {
    'rocky': {
        '9':  {'/etc/yum.repos.d/rocky.repo': ARCH_ACCEPTED, '/etc/yum.repos.d/rocky-addons.repo': ARCH_ACCEPTED,
               '/etc/yum.repos.d/rocky-devel.repo': ARCH_ACCEPTED, '/etc/yum.repos.d/rocky-extras.repo': ARCH_ACCEPTED},
        '10': {'/etc/yum.repos.d/rocky.repo': ARCH_ACCEPTED, '/etc/yum.repos.d/rocky-addons.repo': ARCH_ACCEPTED,
               '/etc/yum.repos.d/rocky-devel.repo': ARCH_ACCEPTED, '/etc/yum.repos.d/rocky-extras.repo': ARCH_ACCEPTED},
    },
"""
s=s.replace("_DISTRO_REPOFILES_MAP = {\n", blk, 1)
s=s.replace('        "almalinux": "AlmaLinux",\n',
            '        "almalinux": "AlmaLinux",\n        "rocky": "Rocky Linux",\n', 1)
open(f,"w").write(s)
PY
find /usr/share/leapp-repository -name 'distro.cpython*.pyc' -path '*common*' -delete

3) CPU를 x86-64-v3 이상으로 (관문 4) — VM이면 하이퍼바이저에서 CPU 타입을 host로. Proxmox 예:

# Proxmox 호스트에서 (VM 정지 후)
qm set <VMID> --cpu host

4) 타깃 저장소 정의 (관문 6)

cat > /etc/leapp/files/leapp_upgrade_repositories.repo <<'REPO'
[rocky10-baseos]
name=Rocky 10 - BaseOS
baseurl=https://download.rockylinux.org/pub/rocky/10/BaseOS/x86_64/os/
enabled=1
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-Rocky-10
[rocky10-appstream]
name=Rocky 10 - AppStream
baseurl=https://download.rockylinux.org/pub/rocky/10/AppStream/x86_64/os/
enabled=1
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-Rocky-10
[rocky10-crb]
name=Rocky 10 - CRB
baseurl=https://download.rockylinux.org/pub/rocky/10/CRB/x86_64/os/
enabled=1
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-Rocky-10
[rocky10-extras]
name=Rocky 10 - Extras
baseurl=https://download.rockylinux.org/pub/rocky/10/extras/x86_64/os/
enabled=1
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-Rocky-10
REPO

4.5) ★ 네트워크를 MAC 기준 keyfile로 미리 만든다 (부작용 예방 — 이걸 안 하면 부팅 후 IP가 안 붙는다)

# el10은 ifcfg 미지원 + 부팅 시 NIC 이름이 바뀔 수 있다(enp6s18→ens18).
# 그래서 업그레이드 전에 "MAC 주소로 매칭되는 keyfile 연결"을 만들어두면
# el10 NetworkManager가 부팅 때 그걸 읽어 자동 연결한다(콘솔 개입 불필요).
IFACE=$(ip -o link show | awk -F': ' '$2!="lo"{print $2; exit}')
MAC=$(cat /sys/class/net/$IFACE/address)
nmcli connection add type ethernet con-name lan ipv4.method auto ipv6.method auto \
  802-3-ethernet.mac-address "$MAC" connection.autoconnect yes

5) leapp preupgrade (관문 5 env 우회) — 여기서 DNF 트랜잭션 벽까지 확인

LEAPP_UNSUPPORTED=1 LEAPP_DEVEL_TARGET_RELEASE=10.0 LEAPP_DEVEL_SKIP_CHECK_OS_RELEASE=1 \
leapp preupgrade --no-rhsm --target 10.0 \
  --enablerepo rocky10-baseos --enablerepo rocky10-appstream \
  --enablerepo rocky10-crb --enablerepo rocky10-extras
# → inhibitor 0까지 가지만 leapp upgrade는 트랜잭션 계산에서 실패(repomap/pes 부재)

6) leapp 포기, distro-sync로 강행 (진짜 성공 경로 — tmux 안에서)

rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-Rocky-10
cp /etc/leapp/files/leapp_upgrade_repositories.repo /etc/yum.repos.d/rocky10.repo
mv /etc/yum.repos.d/epel*.repo /root/ 2>/dev/null   # 서드파티 비활성화(충돌 최소화)
dnf clean all
# ★ SSH로 하면 systemd·openssh 교체 시점에 끊긴다 → tmux 안에서 실행(끊겨도 계속)
#    끝나면 자동 재부팅까지 체인(4.5의 MAC keyfile 덕에 부팅 후 네트워크 자동 복구)
tmux new-session -d -s upg "
  dnf -y --releasever=10 --disablerepo='*' --enablerepo='rocky10-*' \
    --allowerasing --nobest distro-sync && systemctl reset-failed && reboot"
# 진행은 콘솔 TTY에서 'tmux attach -t upg' 로 볼 수 있다

7) 부팅 후 정리 (네트워크는 4.5 덕에 자동 복구됨 — SSH로 재접속해 마무리)

dnf -y --disablerepo='*' --enablerepo='rocky10-*' --allowerasing --nobest distro-sync
systemctl disable --now mcelog.service   # VM에선 불필요(실패 유닛 원인)
systemctl reset-failed
rm -f /etc/yum.repos.d/rocky10.repo       # 공식 repo 복구됐으니 수제 repo 제거
grep PRETTY_NAME /etc/os-release          # Rocky Linux 10 확인

업그레이드 부작용 / 후유증

완주했다고 끝이 아니다. 업그레이드된 시스템을 뒤져보니 실제로 깨지고 남은 것들이 있었다. 인플레이스의 대가다.

  • GUI가 깨졌다. gnome-shell은 el10(49.4)로 올라갔는데 xorg-x11-server-Xorgel9(1.20.11.el9) 로 남아 orphan이 됐다. el10은 Wayland 중심으로 가면서 X 스택을 재편했고, 그 미스매치로 데스크톱 세션이 정상 동작하지 않는다. 헤드리스 서버면 무관하지만 GUI를 쓰던 시스템이면 사실상 재구성이 필요하다.
  • 네트워크 연결이 끊겼다. el10이 NetworkManager의 ifcfg(network-scripts) 지원을 제거해, 기존 연결 프로파일이 무효가 되어 부팅 후 IP를 못 받았다. keyfile 형식으로 새로 만들어야 했다(nmcli connection add ... ipv4.method auto). 게다가 NIC 이름이 enp6s18ens18로 바뀌었고, SSH로 업그레이드하면 systemd·glibc·openssh 교체 시점에 연결이 끊긴다(tmux 실행 필수). → 예방책: 업그레이드 전에 MAC 주소로 매칭되는 keyfile 연결을 만들어두면(위 4.5단계) 개명·ifcfg제거를 모두 넘어 부팅 후 자동 복구된다. 실측으로 검증했다.
  • Python이 3.9 → 3.12로 점프. 3.9에 묶인 venv·스크립트·서드파티 모듈은 깨질 수 있다.
  • el9 패키지 약 90개가 orphan으로 잔존. el10에서 폐기된 라이브러리들이다 — clutter·cogl·gtkmm(GNOME 레거시), libdb(Berkeley DB), pcre(→pcre2), libsoup(→libsoup3), teamd/libteam(티밍 폐기), dhcp-client(→NM 내장), 그리고 leapp 자신과 폰트 다수. 무해한 것도 있지만 X 드라이버처럼 "기능이 깨진 채 남은" 것도 있다. 신중히 정리하되 구 커널은 남긴다.
  • 설정 파일이 병합되지 않았다. .rpmsave/.rpmnew가 5개 생겼다(CUPS 등). 커스터마이즈한 설정은 백업(.rpmsave)으로 밀려나고 el10 기본값(.rpmnew)이 들어오니, 수동으로 대조해야 한다.
  • rpm DB가 이전됐다. /var/lib/rpm/usr/lib/sysimage/rpm. rpmdb-migrate.service가 한 번 실패로 뜨지만 실제 DB는 정상 이전돼 있었다.
  • 구 el9 커널이 grub에 남아있다. 실수로 그걸로 부팅할 수 있으니, el10 안정성 확인 후 제거하는 게 좋다.
  • (이 방법 특유) 손댄 흔적이 남는다. distro-sync가 rocky-release로 공식 저장소는 복구하지만, 우리가 넣은 임시 저장소 파일·distro.py 패치·수제 서명설정은 그대로 남는다. 공식 repo와 중복되므로 정리해야 dnf가 깔끔해진다.

정리하면 — 인플레이스는 "성공"과 "무결"이 다르다. 부팅되고 dnf가 돌아도, GUI·네트워크 프로파일·폰트·구식 라이브러리·커스텀 설정은 손봐야 한다.

정리

  • Rocky 9→10 인플레이스는 공식 미지원(Rocky는 클린 설치 권장, ELevate도 경로 없음)
  • 그러나 leapp-upgrade-el9toel10은 Rocky AppStream에 존재 — 돌려보면 7개 관문에서 막힌다: 타깃버전 · rocky 서명설정 부재 · distro.py에 rocky 미구현 · CPU v3 · 버전가드 · 타깃저장소 · 그리고 패키지 매핑 데이터(repomap/pes-events) 부재라는 진짜 벽
  • leapp이 데이터로 막히니 dnf distro-sync --allowerasing으로 우회 → 1269패키지 트랜잭션 성공
  • el10 함정: ifcfg 제거(→keyfile), NIC 개명, rpmdb 이전, systemd/glibc 교체 중 SSH 단절
  • 결과: Rocky 10.2 클린 부팅, 부팅·dnf 정상 (백업은 안전망일 뿐 안 씀)
  • 부작용: GUI 깨짐(Xorg el9 orphan)·Python 3.9→3.12·el9 orphan 90개(폐기 라이브러리/구커널/leapp)·설정 미병합(.rpmsave/.rpmnew)·수제 흔적 잔존 → "성공 ≠ 무결", 후처리 필요

"공식 지원"이 없다는 건 "도구가 없다"가 아니라 "데이터가 없다"는 뜻이었다.
leapp의 골격은 Rocky에도 깔려 있었지만, Rocky를 위한 매핑 데이터가 비어 있어 정공법이 막혔다.
그 빈칸을 손으로 메우고, 안 되는 부분은 distro-sync로 우회해, 결국 뚫었다.
안 된다고 적힌 길도, 어디서 왜 막히는지 알면 지나갈 구멍이 보인다.

⚠️ 이 방법은 비공식·비지원이다. 운영 시스템엔 권장하지 않으며, 반드시 스냅샷/백업 후 검증 환경에서만 시도할 것. 정식 경로는 여전히 "클린 설치"다.
반응형