SSD Nodes Learn 🎉 VPS $5.50/월부터
가이드 Matt Connor작성자 Matt Connor

Ubuntu VPS 커널 부팅 순서 고정 및 GRUB 설정 방법

Ubuntu 클라우드 이미지에서 GRUB_DEFAULT가 작동하지 않는 이유를 설명합니다. 서버의 실제 메뉴 항목을 확인하고 복구 콘솔 없이 안전하게 부팅 커널을 고정하는 정확한 절차와 주의사항을 안내합니다.

VPS가 부팅할 커널을 결정하는 요소

VPS가 다음에 어떤 커널로 부팅할지는 생성된 파일인 /boot/grub/grub.cfg에 의해 결정됩니다. 이 파일은 직접 수정하지 않습니다. 대신 입력 파일을 수정하고 파일을 다시 생성해야 합니다. Ubuntu 클라우드 이미지의 경우 입력 파일 중 하나가 이미지 공급업체로부터 제공되며, 이로 인해 메뉴 선택이 무의미해질 수 있습니다. 이것이 바로 랩톱 설치 환경에서는 작동하는 GRUB_DEFAULT=1update-grub 과정이 임대 서버에서는 아무런 변화를 일으키지 않는 이유입니다.

다음 순서대로 작업하십시오. 먼저 커널 선택 권한이 본인에게 있는지 확인하십시오. 공급업체가 추가한 파일을 포함하여 모든 입력 파일을 읽어 보십시오. 생성된 출력 파일을 읽고 실제로 포함된 항목의 개수를 세어 보십시오. 그 후에야 고정(pinning) 방법을 선택하십시오. SSH로만 접근 가능한 장비에서 이 과정을 잘못 수행하면 복구 콘솔을 사용해야 하는 상황이 발생합니다. 따라서 이 페이지의 마지막에 있는 가장 안전한 방법을 선택하는 것이 좋으며, 대개 그 방법이 정답입니다.

커널을 고정할 수 있는지 먼저 확인하십시오

uname -r
systemd-detect-virt
ls -1 /boot/vmlinuz-*

systemd-detect-virt 명령을 실행했을 때 kvm, qemu 또는 xen가 출력된다면 사용자가 직접 커널을 운영 중인 것이며 아래의 모든 내용이 적용됩니다. lxc 또는 openvz가 출력된다면 서버가 호스트의 커널을 공유하는 상태이므로 사용자가 제어할 수 있는 부트로더가 없으며 고정할 대상도 없습니다. 이 경우 uname -r/boot/vmlinuz-*에는 전혀 나타나지 않는 버전을 보고하는데, 이는 실행 중인 커널이 호스트의 것이며 디스크의 어떤 설정으로도 이를 변경할 수 없기 때문입니다.

ls -1 /boot/vmlinuz-*은 선택 가능한 커널의 실제 목록입니다. 이 목록에 한 줄만 있다면 이전 커널은 이미 삭제된 상태이며, 어떤 부트로더 설정으로도 복구할 수 없습니다. 이는 보통 autoremove 과정에서 발생하며, 중요한 서버에서 Ubuntu의 오래된 커널 정리를 수행하기 전에 반드시 이해해야 할 부분입니다.

편집하는 파일과 GRUB이 읽는 파일은 서로 다릅니다

/etc/default/grub에는 일반적인 셸 변수 할당문이 포함되어 있습니다. 이는 입력 파일입니다. /boot/grub/grub.cfg는 출력 파일이며, # DO NOT EDIT THIS FILE과 그 이유가 파일 상단에 명시되어 있습니다. 커널 패키지가 설치되거나 제거될 때마다 패키지 스크립트가 이 파일을 재생성하므로, 출력 파일에 직접 작성한 내용은 모두 사라집니다.

cat /usr/sbin/update-grub

update-grub은 래퍼(wrapper)입니다. 이 명령은 grub-mkconfig -o /boot/grub/grub.cfg를 실행하며, grub-mkconfig -o /boot/grub/grub.cfg는 변수를 읽고 /etc/grub.d/에 있는 모든 스크립트를 실행한 뒤 결과를 기록합니다. 두 명령은 한 방향으로 작동합니다. 입력값이 들어가면 grub.cfg가 생성됩니다.

설정을 덮어쓰는 요소: /etc/default/grub.d

grep -rn '^[^#]' /etc/default/grub /etc/default/grub.d/

두 번째 경로는 사용자가 자주 놓치는 부분입니다. grub-mkconfig는 먼저 /etc/default/grub을 소싱(source)한 다음, /etc/default/grub.d/에 있는 모든 *.cfg 파일을 glob 순서대로 처리합니다. 이를 수행하는 코드는 다음과 같습니다.

grep -n 'default/grub' /usr/sbin/grub-mkconfig

소싱은 일반적인 셸 방식으로 이루어지므로, 마지막에 할당된 값이 최종적으로 적용됩니다. Ubuntu 클라우드 이미지는 해당 디렉터리에 파일을 포함하여 배포하며, 사용자의 파일이 읽힌 이후에 타임아웃이나 커널 명령줄 같은 설정을 덮어씁니다. 사용자가 /etc/default/grub에 작성한 GRUB_TIMEOUT=10는 잠시 후 0으로 설정하는 벤더 파일에 의해 덮어쓰여집니다. 위의 grep 명령은 이미지에 적용된 정확한 할당 값을 출력하므로, 이 문장을 무조건 신뢰하기보다 직접 출력된 내용을 확인하십시오.

따라서 실무적인 규칙은 다음과 같습니다. /etc/default/grub를 직접 수정하는 대신, /etc/default/grub.d/99-local.cfg과 같이 정렬 순서상 마지막에 위치하는 파일에 설정을 작성하십시오. 이렇게 하면 이미지와 함께 배포된 어떤 파일도 사용자의 설정 이후에 적용되지 않습니다.

GRUB_FORCE_PARTUUID가 메뉴 선택을 무의미하게 만드는 이유

grep -rn GRUB_FORCE_PARTUUID /etc/default/grub /etc/default/grub.d/
grep -n GRUB_FORCE_PARTUUID /etc/grub.d/10_linux
sudo grep -n 'root=PARTUUID' /boot/grub/grub.cfg

GRUB_FORCE_PARTUUID은 부팅 시 파일 시스템 UUID를 검색하는 대신, root=PARTUUID=...와 같이 커널 명령줄에 직접 기록된 파티션 UUID로 루트 파일 시스템을 찾도록 생성기에 지시합니다. 이미지 공급업체가 이 설정을 사용하는 이유는 제작 환경과 다른 하드웨어에서도 하나의 디스크 이미지가 안정적으로 부팅되도록 하기 위함입니다. 두 번째 grep 명령은 /etc/grub.d/10_linux 내에서 해당 변수를 처리하는 코드를 보여줍니다. 해당 스크립트는 사용자의 디스크에 위치하며, 이미지의 동작 방식을 결정하는 기준이 됩니다.

여기서 중요한 결과는 다음과 같습니다. 해당 경로에서 생성기는 설치된 전체 커널 목록 대신 직접적인 부팅 항목을 작성합니다. 최종적으로 생성된 항목의 개수를 확인하십시오.

sudo grep -cE '^\s*(menuentry|submenu) ' /boot/grub/grub.cfg
sudo grep -nE '^\s*(menuentry|submenu) ' /boot/grub/grub.cfg

항목 개수가 1개라면 선택할 두 번째 항목이 존재하지 않으므로, GRUB_DEFAULT=1은 존재하지 않는 항목을 가리키게 됩니다. GRUB은 이를 해석할 수 없어 첫 번째 항목으로 부팅하며, 이는 사용자가 피하려던 새 커널이 됩니다. grub-set-default 역시 도움이 되지 않습니다. 기본값 설정이 잘못된 것이 아니기 때문입니다. 사용자가 선택하려던 메뉴는 애초에 생성되지 않았습니다.

전체 메뉴를 복구하려면 공급업체 파일을 다른 곳으로 옮기고, 변경 사항을 적용하기 전에 결과를 미리 확인하십시오. -o 없이 grub-mkconfig을 실행하면 표준 출력으로 결과가 표시되며 디스크에는 아무런 영향도 주지 않습니다.

sudo grub-mkconfig 2>/dev/null | grep -cE '^\s*(menuentry|submenu) '
sudo mkdir -p /root/grub-backup
sudo mv /etc/default/grub.d/<the file your grep named> /root/grub-backup/
sudo grub-mkconfig 2>/dev/null | grep -cE '^\s*(menuentry|submenu) '

항목 개수가 1개에서 여러 개로 늘어났다면, 강제 설정이 제거되면서 항목들이 나타난 것입니다. 아직 아무것도 기록되지 않았습니다. 두 번째 개수가 올바르지 않다면 파일을 원래대로 되돌리십시오. 강제된 PARTUUID는 공급업체의 이미지가 루트 파일 시스템을 찾는 방식이며, 이를 제거하면 시스템은 검색 경로를 통해 부팅을 시도하게 됩니다. update-grub을 실제로 실행하기 전에 스냅샷을 생성하십시오.

단지 잘못된 커널로부터 시스템을 보호하는 것이 목적이라면, 여기서 멈추고 아래에 제시된 더 안전한 옵션을 사용하십시오. 단일 업데이트 문제를 해결하기 위해 원격 서버에서 부팅 메뉴를 재구성하는 것은 위험 부담이 큽니다.

엔트리 번호를 고정값으로 사용하는 것이 부적절한 이유

GRUB_DEFAULT은 숫자, 제목 또는 식별자를 인자로 받습니다. 숫자는 최상위 엔트리를 0부터 순서대로 셉니다. 중첩된 엔트리는 >를 구분자로 사용하므로, GRUB_DEFAULT="1>2"은 인덱스 1인 하위 메뉴 내의 인덱스 2인 엔트리를 의미합니다.

인덱스는 변합니다. 10_linux는 커널을 최신순으로 나열하므로, 커널을 설치하면 모든 이전 엔트리가 한 칸씩 아래로 밀리고, 커널을 삭제하면 한 칸씩 위로 당겨집니다. 사용자가 신중하게 설정한 1>2도 그 이후에는 값이 달라집니다. 이제는 다른 커널을 가리키게 됩니다. 오류도 발생하지 않고 경고도 없으며, 재부팅을 하고 나서야 이 사실을 알게 됩니다.

식별자는 위치가 변하지 않습니다. 각 식별자에는 커널 버전이 포함되어 있기 때문입니다. 본인의 식별자를 확인하십시오.

sudo awk -F"'" '/menuentry_id_option/ {print $2, "==>", $4}' /boot/grub/grub.cfg

출력의 앞부분 몇 줄은 헤더에서 정의되는 변수이므로 무시하십시오. 그 이후부터는 왼쪽이 사용자가 보는 제목이고, 오른쪽이 도구에 전달할 식별자입니다. 하위 메뉴 내부의 엔트리의 경우, 숫자 형식과 동일하게 하위 메뉴 식별자와 엔트리 식별자를 >으로 연결하여 사용하십시오.

grub-reboot을 사용하여 이전 커널로 1회 부팅하기

원격 서버에서는 설정이 자동으로 복구되므로 1회성 선택 방식을 사용하는 것이 좋습니다. grub-rebootnext_entry/boot/grub/grubenv에 기록합니다. GRUB은 부팅 과정에서 해당 변수를 읽고 값을 지운 뒤, 그 값을 저장한 다음에 부팅을 진행합니다. 따라서 커널 패닉이 발생하더라도 다음 부팅 시 다시 시도하지 않습니다. 한 번의 시도 후에는 시스템이 자동으로 기본 설정으로 돌아옵니다.

먼저 생성된 설정 파일이 해당 변수를 읽는지 확인하십시오.

sudo grep -n -B2 -A5 'next_entry' /boot/grub/grub.cfg

load_env 라인과 next_entry로부터 default을 설정하는 블록이 있어야 합니다. grep 결과가 출력되지 않는다면 부팅 시 grubenv을 읽지 않는 것이며, 따라서 셸에서 grub-reboot를 입력해도 부트 로더가 이를 무시합니다. 이는 이전 섹션에서 다룬 강제 직접 부팅 경로가 다른 곳에서도 나타나는 경우입니다.

sudo grub-reboot '<the identifier you copied>'
sudo grub-editenv list

이제 grub-editenv list를 실행하면 전달한 값이 정확히 포함된 next_entry= 라인이 출력되어야 합니다. 브라우저 탭에서 제공업체의 콘솔을 열고 재부팅하여 결과를 확인하십시오.

sudo reboot
uname -r

uname -r이 이전 버전을 출력한다면 고정(pin) 작업이 성공한 것입니다. 새 버전이 출력된다면 식별자가 해석되지 않았거나 grubenv을 읽지 못하는 상황입니다. 어느 경우든 시스템은 정상적으로 부팅되므로, 1회성 방식을 사용하는 목적은 달성한 셈입니다.

GRUB_DEFAULT=saved를 사용하여 설정 고정하기

GRUB_DEFAULT=savedgrubenv 내의 saved_entry에서 기본값을 가져오도록 하며, grub-set-default를 사용하여 해당 값을 설정합니다. 이 설정은 커널 설치 후에도 유지되는데, 이는 update-grubgrub.cfg를 다시 작성할 때 grubenv는 건드리지 않기 때문입니다.

echo 'GRUB_DEFAULT=saved' | sudo tee /etc/default/grub.d/99-local.cfg
sudo update-grub
sudo grub-set-default '<the identifier you copied>'
sudo grub-editenv list
sudo grep -n 'set default' /boot/grub/grub.cfg

마지막 명령은 set default="${saved_entry}"을 출력해야 합니다. 만약 set default="0"이 출력된다면, 귀하의 파일 이후에 로드된 설정이 GRUB_DEFAULT을 리터럴 값으로 되돌린 것이므로, /etc/default/grub.d/를 다시 나열하여 99-local.cfg이 실제로 마지막에 정렬되는지 확인하십시오.

GRUB_SAVEDEFAULT=true은 이 설정과 혼동하기 쉬운 별개의 설정입니다. 이 옵션은 방금 부팅한 항목을 새로운 기본값으로 저장하므로, 마지막으로 성공한 부팅 항목이 기본값이 됩니다. 서버 환경에서 이는 무인 재부팅 시 의도치 않게 고정된 커널이 변경될 수 있음을 의미합니다. 원하는 동작이 아니라면 이 옵션을 끄십시오.

식별자를 통한 고정 방식도 한 가지 문제가 있습니다. 지정된 커널을 삭제하면 식별자가 더 이상 해석되지 않으며, 이 경우 첫 번째 항목으로 기본값이 돌아갑니다. 따라서 해당 패키지를 고정(hold)하거나, 해당 커널이 autoremove 대상에서 제외되도록 관리하십시오.

제공자 콘솔에 메뉴 표시하기

대화형으로 선택하려면 화면에 메뉴가 나타나야 하지만, 클라우드 이미지는 이를 숨깁니다. 가장 마지막에 정렬되는 파일에 다음 내용을 추가한 뒤 sudo update-grub를 실행하십시오.

GRUB_TIMEOUT=10
GRUB_TIMEOUT_STYLE=menu
GRUB_RECORDFAIL_TIMEOUT=10

GRUB_TIMEOUT_STYLE=hiddenGRUB_TIMEOUT=0를 함께 사용하면 아무것도 표시되지 않으므로, 콘솔을 지켜보는 사람은 커널 메시지가 즉시 시작되는 것을 보고 부트로더가 건너뛰어졌다고 판단하게 됩니다. GRUB_RECORDFAIL_TIMEOUT는 부팅이 완료되지 않았을 때 사용하는 별도의 타임아웃이며, 클라우드 이미지는 이 값도 0으로 설정합니다. 이것이 부팅에 실패한 서버가 멈추지 않고 대기하지 않는 이유입니다.

제공자가 그래픽 콘솔 대신 시리얼 콘솔을 제공하는데도 아무것도 보이지 않는다면, GRUB이 볼 수 없는 터미널로 내용을 출력하고 있는 것입니다. 첫 번째 줄은 출력을 선택하고 두 번째 줄은 포트를 구성하므로, 두 줄을 모두 추가하십시오.

GRUB_TERMINAL="console serial"
GRUB_SERIAL_COMMAND="serial --speed=115200 --unit=0 --word=8 --parity=no --stop=1"

이제부터 모든 부팅 시 10초의 대기 시간이 추가됩니다. 작업이 완료되면 타임아웃을 다시 0으로 설정하십시오.

부트로더 편집보다 안전한 대안

SSH로만 접근 가능한 서버에서 부트로더 설정을 변경하는 것은 이 문서에서 가장 위험한 작업입니다. 더 안전하고 저렴한 해결책이 존재하며, 보통 이러한 방법으로 근본적인 문제를 해결할 수 있습니다.

커널 패키지를 고정(hold)하십시오. "새로운 커널을 설치하지 마라"가 목적이라면, 부트로더가 아닌 패키지 관리자에게 이를 지시해야 합니다.

apt list --installed 2>/dev/null | grep -E '^linux-(image|headers|generic|virtual|kvm|aws|azure|gcp|oracle)'
sudo apt-mark hold linux-image-virtual linux-headers-virtual
apt-mark showhold

첫 번째 명령어가 출력한 이름을 그대로 사용하십시오. 클라우드 이미지는 보통 generic 대신 virtual 또는 kvm 버전을 설치하기 때문입니다. 고정된 패키지는 apt upgrade 실행 시 건너뛰며, 이때 The following packages have been kept back: 메시지가 표시됩니다. 또한 Ubuntu의 자동 업그레이드에서도 해당 패키지는 제외됩니다. 이 방식에는 대가가 따릅니다. 고정된 커널은 보안 패치를 받지 못하므로, 이를 기한이 있는 일시적인 조치로 간주하고 sudo apt-mark unhold 명령어로 다시 해제해야 합니다. 재부팅으로 인한 서비스 중단이 우려되어 커널 업데이트를 피하는 것이라면, VPS에서의 라이브 커널 패치가 더 적절한 해결책입니다.

업그레이드 전 스냅샷을 생성하십시오. 스냅샷을 이용하면 콘솔 입력 없이 몇 분 만에 복구가 가능하며, 부트로더 설정이 불완전하게 적용될 위험도 없습니다. 스냅샷 생성, 업그레이드, 재부팅, 확인 순으로 진행하십시오. 새 커널에 문제가 발생하면 즉시 롤백하여 부팅 경로를 이전 상태로 정확히 되돌릴 수 있습니다.

이미 부팅이 불가능한 서버는 콘솔이나 복구 이미지를 사용하십시오. 서버가 부팅되지 않는 상태라면 부트로더 설정 수정만으로는 해결할 수 없습니다. 이 경우의 복구 절차는 별도로 존재합니다: 커널 업데이트 후 VPS가 부팅되지 않을 때의 대처법.

무엇이 문제이며 어떤 메시지가 나타나는가

/boot/grub/grub.cfg에 대한 수정 사항이 사라졌습니다. 커널 패키지가 설치되거나 제거될 때 관리자 스크립트가 update-grub을 실행하여 입력 파일로부터 해당 파일을 재생성했기 때문입니다. # DO NOT EDIT THIS FILE 헤더에는 두 개의 입력 위치가 명시되어 있습니다. 해당 파일들을 수정하십시오.

grub-editenv: error: environment block too small 오류가 발생합니다. /boot/grub/grubenv 파일이 없거나 내용이 잘렸습니다. sudo grub-editenv /boot/grub/grubenv create 명령으로 파일을 다시 생성한 다음, 값을 다시 설정하고 sudo grub-editenv list 명령으로 확인하십시오.

고정(pinned)된 커널이 VFS: Unable to mount root fs on unknown-block(0,0) 오류와 함께 커널 패닉을 일으킵니다. 고정한 항목이 디스크에 더 이상 존재하지 않는 커널이나 initrd를 가리키고 있습니다. 이는 패키지는 제거되었으나 식별자는 grubenv에 남아있기 때문입니다. 복구하려면 콘솔에서 정상적인 항목으로 부팅한 뒤, 유효하지 않은 값을 삭제하십시오.

재부팅 후에도 uname -r 값이 변경되지 않았습니다. 다음 세 가지를 순서대로 확인하십시오. grub-editenv list에 여전히 설정한 값이 남아있는지 혹은 이미 적용되었는지, 설정한 식별자가 현재 grub.cfg에 나타나는지, grub.cfg에 설정한 변수를 읽어오는 set default 줄이 포함되어 있는지 확인하십시오. 이 세 가지 중 하나가 항상 원인이 됩니다.

시스템 충돌 후 메뉴가 자동으로 나타납니다. GRUB은 부팅 실패를 grubenvrecordfail=1로 기록하며, 사용자가 개입할 수 있도록 다음 부팅 시 강제로 메뉴를 표시합니다. 시스템이 정상화되면 sudo grub-editenv /boot/grub/grubenv unset recordfail 명령으로 해당 기록을 삭제하십시오.


기억해야 할 단 한 문장입니다. 사용자가 수정하는 파일은 GRUB이 읽는 파일이 아니며, 클라우드 이미지에서는 이 둘 사이의 차이가 혼란의 원인이 됩니다. 먼저 생성된 설정 파일을 읽어보십시오. 이 페이지의 모든 결정 사항은 실제 설정 파일의 내용에 근거합니다.

FAQ

GRUB_DEFAULT=1을 설정해도 왜 VPS가 다른 커널로 부팅되지 않습니까?

Ubuntu 클라우드 이미지에서 생성된 /boot/grub/grub.cfg는 보통 단일 부팅 항목만 포함하므로, 인덱스 1은 아무것도 가리키지 않으며 GRUB은 첫 번째 항목으로 되돌아갑니다. sudo grep -cE '^\s*(menuentry|submenu) ' /boot/grub/grub.cfg 명령으로 이를 확인하십시오. 항목 개수가 1개라면 이것이 원인입니다. 근본 원인은 이미지 제공자가 /etc/default/grub.d/ 하위 파일에 설정한 GRUB_FORCE_PARTUUID 때문이며, 이 설정은 설치된 커널 전체 목록을 생성하는 대신 직접 부팅 경로를 사용하도록 만듭니다. grep -rn GRUB_FORCE_PARTUUID /etc/default/grub /etc/default/grub.d/ 명령으로 해당 파일을 찾으십시오.

이전 커널로 딱 한 번만 부팅하려면 어떻게 해야 합니까?

본인의 grub.cfg에서 복사한 식별자를 사용하여 sudo grub-reboot '<identifier>'를 실행한 뒤, 제공자 콘솔을 열어둔 상태에서 재부팅하십시오. GRUB은 부팅 직후 next_entry을 초기화하므로 이 선택은 단 한 번의 시도에만 적용되며, 커널 패닉이 발생해도 해당 커널을 다시 시도하지 않습니다. sudo grub-editenv list 명령으로 값이 제대로 적용되었는지 확인하십시오. 이 방식에 의존하기 전에 sudo grep -n next_entry /boot/grub/grub.cfg을 실행하십시오. 설정 파일이 grubenv를 로드하지 않는 이미지라면 오류 메시지 없이 명령을 무시할 수 있기 때문입니다.

항목 번호로 고정하는 것이 좋습니까, 아니면 식별자로 고정하는 것이 좋습니까?

식별자를 사용하는 것이 좋습니다. 항목 번호는 10_linux이 최신순으로 재구성하는 목록 내의 위치일 뿐이므로, 커널을 설치하거나 삭제하면 번호가 바뀝니다. 오래된 1>2 설정은 여전히 유효한 다른 항목을 가리키게 되어, 경고 없이 의도치 않은 커널로 부팅될 수 있습니다. 식별자에는 커널 버전이 포함되어 있으므로 의도한 커널과 일치하거나, 일치하지 않으면 부팅에 실패하여 문제를 즉시 알 수 있습니다. sudo grep -n menuentry_id_option /boot/grub/grub.cfg 명령으로 식별자 목록을 확인하고 각 항목 줄에 표시된 따옴표로 묶인 문자열을 복사하십시오.

부팅 로더를 변경하는 것보다 커널 패키지를 고정하는 것이 더 안전합니까?

일반적인 목적이라면 그렇습니다. sudo apt-mark hold linux-image-virtual linux-headers-virtual을 사용하면 더 새로운 커널이 설치되지 않으므로 부팅 경로가 변경되지 않으며, 콘솔 접근이 불가능한 상황에서 발생할 수 있는 오류를 방지할 수 있습니다. 먼저 apt list --installed 명령으로 현재 설치된 커널 종류를 확인하고, apt-mark showhold 명령으로 고정 상태를 검증하십시오. 단, 고정된 커널은 보안 업데이트를 받을 수 없으므로, sudo apt-mark unhold을 실행할 시점을 미리 계획한 뒤 고정 설정을 적용하십시오.