리눅스/Debian or Ubuntu

우분투에서 부트로더(boot loader) GNU GRUB 복구

씨실과 날실 2020. 12. 9. 09:00


멀티부팅 시스템을 구축하는 과정에서 손상된 GRUB을 boot-repair라는 패키지를 이용해  간편하게 복구하는 방법을 알아본 적이 있습니다.

그러나 이 방법을 쓰는데는 상당한 제약이 있습니다. boot-repair 패키지는 데비안 계열(Debian, Ubuntu, Linux Mint 등) 패키지(.Deb)이기 때문에 레드햇 등 다른 계열 리눅스에서는 사용하기 힘듭니다. 또한 boot-repair 패키지는 GUI 기반이기 때문에 데스크톱 환경이 없는 서버에서는 사용할 수 없습니다.

이럴 때를 대비해 리눅스 시동 디스크만으로 터미널 환경 즉 명령줄(CLI) 환경에서 복구할 수 있는 방법을 알아 둘 필요가 있습니다.

그런데 각 리눅스 계열마다 복구 방법이 다소 상이합니다. 레드햇 계열이라면 시동 디스크 부팅화면에서 아래 단계에 들어가 복구 작업을 진행해주면 됩니다.

CentOS 8의 예

Troubleshooting → Rescue a CentOS Linux system

레드햇 계열 등 다른 계열 리눅스의 보다 자세한 GRUB 복구 방법은 다른 유능한 분들의 글들을 참고하시기 바랍니다.




CLI를 통한 부트로더(boot loader) GNU GRUB 복구 과정


GRUB 손상 전 환경

OS : Ubuntu 20.04

GRUB 버전 : GNU GRUB 2.04


GRUB 손상 이유 : 멀티 부팅 시스템을 위해 Windows 10 설치

GRUB 복구 작업을 하게 되는 이유는 여러 이유가 존재할 수 있지만 이 글에서는 위 상자에서 적은 상황을 상정하고 복구하는 방법을 진행하도록 하겠습니다.


우분투 시동 디스크로 부팅

우분투 시동디스크로 부팅 후 Try Ubuntu를 선택합니다.

부팅 후 터미널을 실행합니다. 단축키는 Crl+Alt+T입니다.


파티션 이름 및 구조 파악

$ sudo fdisk -l

fdisk는 파티션을 관리하는 기본 명령입니다. -l 옵션을 사용하여 현재 시스템에 물려 있는(마운트 여부와는 상관 없이) 디스크 및 파티션의 자세한 정보를 확인할 수 있습니다.


ubuntu@ubuntu:~$ sudo fdisk -l Disk /dev/loop0: 1.98 GiB, 2103640064 bytes, 4108672 sectors Units: sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes - 중략 - Disk /dev/sda: 1.102 TiB, 2199022206976 bytes, 4294965248 sectors Disk model: VBOX HARDDISK Units: sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes Disklabel type: gpt Disk identifier: 8자리 영숫자-4자리 영숫자-4자리 영숫자-4자리 영숫자-12자리 영숫자 Device Start End Sectors Size Type /dev/sda1 2048 1050623 1048576 512M EFI System /dev/sda2 1050624 2098202623 2097152000 1000G Linux filesystem /dev/sda3 2098202624 2098235391 32768 16M Microsoft reserved /dev/sda4 2098235392 4294963199 2196727808 1T Microsoft basic data Disk /dev/sdb: 29.12 GiB, 31260704768 bytes, 61056064 sectors Disk model: Cruzer Switch Units: sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes Disklabel type: dos Disk identifier: 0x8자리 영숫자 Device Boot Start End Sectors Size Id Type /dev/sdb1 * 0 5439487 5439488 2.6G 0 Empty /dev/sdb2 5017392 5025327 7936 3.9M ef EFI (FAT-12/16/32) /dev/sdb3 5439488 61056063 55616576 26.5G 83 Linux ubuntu@ubuntu:~$

위 터미널 내용을 보면 총 아래와 같이 세 유형의 디스크를 확인할 수 있습니다.

Disk /dev/loopN / Disk /dev/sdaN / Disk /dev/sdbN

Disk /dev/loopN은 루프 디바이스(loop device)로, 블록 디바이스에 접근할 수 있는 일반 파일을 만들어주는 일종의 가상 디바이스입니다.

보다 자세한 내용은 위 링크를 참고하시기 바랍니다.


디스크 /dev/sda는 1차 대용량 기억장치를 의미하는 것으로 일반적으로 OS가 설치되어 있는 디스크를 의미합니다.

디스크 /dev/sdb는 2차 대용량 기억장치를 의미하는 것으로 일반적으로 OS가 설치되어 있지 않는 디스크를 의미하며 시스템 내장 하드 디스크가 하나뿐인 경우  USB 메모리 저장장치 등 이동식 저장 매체를 의미합니다.

즉 /dev/sdX는 시스템에서 이용할 수 있는 물리적 저장장치를 의미합니다.

근래에는 장치명으로 /dev/sdX를 사용하는데 이 장치명은 입출력 버스 인터페이스로 SCSI 방식을 사용하는 저장장치를 의미합니다. 이전에는 입출력 버스 인터페이스로 IDE, 또는 E-IDE 방식을 사용했던 적이 있습니다. 이러한 저장장치에 대한 장치명으로는 /dev/hdX를 사용했었으나 이제는 보다 진보된  SCSI 방식을 채택한 저장장치를 사용하게되면서 장치명 /dev/hdX은 리눅스를 다루는 오래된 웹문서에서나 찾아 볼 수 있게 되었습니다.


그외 리눅스에서 사용되는 각종 장치명에 대한 개략적 설명은 위 링크르 참고하시기 바랍니다.


sudo fdisk -l 명령을 입력해 출력되는 정보들 중 우리가 확인할 것은 EFI System과 Linux filesystem의 장치명을 확인하는 것입니다.

위에서 확인해보니 각 파티션의 장치명은 다음과 같습니다.

EFI System : /dev/sda1

Linux filesystem : /dev/sda2

이렇게 확인한 장치명을 기억해두도록 합시다.

근래는 대부분의 시스템들이 EFI System을 사용하기 때문에 EFI System 장치명도 확인되겠지만 드물게 MBR 을 사용하는 구형 시스템인 경우 EFI System 파티션이 구성되지 않기 때문에 관련 장치명도 존재하지 않습니다.


$ sudo blkid

blkid 명령어는 파일시스템의 유형 등을 확인할 때 씁니다.


ubuntu@ubuntu:~$ sudo blkid /dev/sda1: UUID="4자리 영숫자-4자리 영숫자" TYPE="vfat" PARTLABEL="EFI System Partition" PARTUUID="8자리 영숫자-4자리 영숫자-4자리 영숫자-4자리 영숫자-12자리 영숫자" /dev/sda2: UUID="8자리 영숫자-4자리 영숫자-4자리 영숫자-4자리 영숫자-12자리 영숫자" TYPE="ext4" PARTUUID="8자리 영숫자-4자리 영숫자-4자리 영숫자-4자리 영숫자-12자리 영숫자" /dev/sda4: UUID="16자리 영숫자" TYPE="ntfs" PARTLABEL="Basic data partition" PARTUUID="8자리 영숫자-4자리 영숫자-4자리 영숫자-4자리 영숫자-12자리 영숫자" /dev/sr0: UUID="2020-06-04-16-51-04-85" LABEL="VBox_GAs_6.1.10" TYPE="iso9660" /dev/sdb1: UUID="2020-07-31-16-51-12-00" LABEL="Ubuntu 20.04.1 LTS amd64" TYPE="iso9660" PTUUID="8자리 영숫자" PTTYPE="dos" PARTUUID="8자리 영숫자-2자리 숫자" /dev/loop0: TYPE="squashfs" /dev/loop1: TYPE="squashfs" /dev/loop2: TYPE="squashfs" /dev/loop3: TYPE="squashfs" /dev/loop4: TYPE="squashfs" /dev/loop5: TYPE="squashfs" /dev/sda3: PARTLABEL="Microsoft reserved partition" PARTUUID="8자리 영숫자-4자리 영숫자-4자리 영숫자-4자리 영숫자-12자리 영숫자" /dev/sdb2: SEC_TYPE="msdos" UUID="4자리 영숫자-4자리 영숫자" TYPE="vfat" PARTUUID="8자리 영숫자-2자리 숫자" /dev/sdb3: LABEL="writable" UUID="8자리 영숫자-4자리 영숫자-4자리 영숫자-4자리 영숫자-12자리 영숫자" TYPE="ext4" PARTUUID="8자리 영숫자-2자리 숫자" ubuntu@ubuntu:~$

여기서 우리가 확인할 것은 EFI System 장치의 UUID입니다. 물론 여기서 확인하지 못하더라도 나중에 확인할 수 있습니다.


리눅스 주 파티션 마운트

$ sudo mount /dev/sda2 /mnt

mount는 기본적으로 특정 장치를 사용자가 지정한 디렉터리에 연결해주는 명령입니다.


ubuntu@ubuntu:~$ sudo mount /dev/sda2 /mnt
ubuntu@ubuntu:~$

우분투가 설치되어 있는 주 파티션을 /mnt 디렉터리에 마운트해줍니다.


필수 디렉터리 바인드 마운트

$ for i in /sys /proc /run /dev /dev/pts; do sudo mount --bind "$i" "/mnt$i"; done

시스템 구동에 필요한 여러 필수 디렉터리들을 바인드해 마운트해줍니다.

그 이유는 이따가 /mnt 디렉터리를 가상 루트 디렉터리로 설정하여 현재 구동 중인 우분투 Live 시동 디스크가 아니라 가상 루트 디렉터리의 정보를 토대로 구동 및 작업하기 이함입니다.


위 명령을 이해하기 위해서는 Bash 셸스크립트 구조적 명령의 반복구문 for문을 이애해야 합니다. 이 내용은 리눅스 초심자에게는 꽤 까다로울 수 있습니다.  그러나 최대한 풀어서 설명해보도록 하겠ㅅㅂ니다.

for 문

위에 사용된 for 문은 Bash 셸스크립트에서 사용되는 구조적 명령 중 하나로, 특정 조건을 만족할 때까지 일련의 명령을 반복하는 일종의 반복 구문(= loop 구문)입니다.

이 for 문은 문법은 다소 차이가 날지라도 다른 셸스크립트 뿐만 아니라 파이썬 등 대부분의 프로그래밍 언어에서 사용되는 구문입니다.


for문의 기본 문법은 아래와 같습니다.

for var in list

do

command

done


위 구문을 한줄로 표현하고자 할 때 아래와 같이 작성합니다.

for var in list; do command; done

위 예시와 같이 후행하는 do 문을 세미콜론(;)으로 앞의 list 항목과 구분해주어야 합니다.



var 자리에는 변수명을, list 매개변수에는 반복에 사용할 값들을 나열해줍니다. 그리고 command 자리에는 반복 실행할 명령을 적어줍니다.  

for i in /sys /proc /run /dev /dev/pts; do sudo mount --bind "$i" "/mnt$i"; done

즉 위 구문을 인간이 이해할 수 있는 언어로 번역하면 다음과 같습니다.

반복 적용할 변수를 i라 설정하고 변수 i에 적용할 값(즉 동일한 명령을 수행할 값들)들로 /sys, /proc, /run, /dev, /dev/pts 디렉터리를 지정합니다.

각 디렉터리를 /mnt 디렉터리에 바인드하여 마운트해줍니다.

즉, 현재 마운트트되어 있어 시스템 구동에 사용되고 있는 /sys, /proc, /run, /dev, /dev/pts 디렉터리들을 --bind 옵션을 추가하여 다른 디렉터리인  /mnt 디렉터리 아래에 붙여 마운트해줍니다.


EFI 시스템 장치 확인

$ sudo fdisk -l | grep -i efi

grep은 입력받은 문자열을 찾을 때 쓰는 명령으로, 위 예시에서 사용된 -i 옵션은 대소문자를 무시할 때 씁니다.


ubuntu@ubuntu:~$ sudo fdisk -l | grep -i efi
/dev/sda1        2048    1050623    1048576  512M EFI System
/dev/sdb2       5017392  5025327     7936  3.9M ef EFI (FAT-12/16/32)
ubuntu@ubuntu:~$

위 명령을 이용해 EFI System의 장치명을 확인해줍니다.

/dev/sda1는 기존 시스템의 EFI System 장치명입니다. /dev/sdb2는 우분투 시동디스크 USB 메모리 저장장치에 존재하는 EFI System 장치명입니다.

우리는 기존 시스템의 손상된 GRUB을 복구하고자 하는 것이므로 기존 시스템의 EFI System 장치인  /dev/sda1도 마운트해주도록 합시다.


EFI 시스템 장치 마운트

$ sudo mount /dev/sda1 /mnt/boot/efi

기존 시스템의 EFI System 장치인  /dev/sda1을 mnt/boot/efi에 마운트해줍시다.

이때 mount할 때 --bind를 사용하지 않는 이유는 마운트하고자 하는 것이 디렉터리가 아닌 장치이기 때문입니다.


mount(8)

Description

- 중략 -

The mount command serves to attach the filesystem found on some device to the big file tree.

- 중략-

The standard form of the mount command, is

    mount -t type device dir


기본적으로 mount는 디스크 드라이브 같은 블록 디바이스(Block Device)만 마운트가 가능합니다. 하지만 장치가 아닌 디렉터리를 다른 디렉터리에 마운트할 수도 있는데 이땐 --bind 옵션을 사용해야 합니다.


The bind mounts.

    Since Linux 2.4.0 it is possible to remount part of the file hierarchy somewhere else. The call is

mount --bind olddir newdir

    or shortoption

mount -B olddir newdir

    or fstab entry is:

/olddir /newdir none bind



가상 루트 디렉터리 지정

$ sudo chroot /mnt

chroot는  가상의 루트 디렉터리를 생성할 때 사용합니다. exit 명령으로 빠져나가기 전까지 chroot 명령 이후의 자식 프로세스로 이루어지는 모든 작업은 해당 가상 루트 디렉터리를 기반으로 이루어집니다.


보다 자세한 내용은 위 링크를 참고하시기 바랍니다.


ubuntu@ubuntu:~$ sudo chroot /mnt
root@ubuntu:/#

사용자가 root로 전환됩니다.


GRUB 업데이트

# update-grub

update-grub은 grub의 업데이트 명령입니다.

만약 오류가 발생한다면 GRUB을 인스톨해주어야 합니다.


GRUB 인스톨

# grub-install /dev/sda

grub-install은 grub 설치 명령으로 grub을 설치할 장치를 인수로 가집니다.


root@ubuntu:/# grub-install /dev/sda
Installing for x86_64-efi platform.
Installation finished. No error reported.
root@ubuntu:/#

이제 본격적으로 GRUB을 복구합니다. 이 명령은 GRUB을 설치합니다.


GRUB 업데이트

# update-grub

update-grub은 grub의 업데이트 명령입니다.


root@ubuntu:/# update-grub
Sourcing file `/etc/default/grub'
Sourcing file `/etc/default/grub.d/init-select.cfg'
Generating grub configuration file ...
Found linux image: /boot/vmlinuz-5.4.0-54-generic
Found initrd image: /boot/initrd.img-5.4.0-54-generic
Found linux image: /boot/vmlinuz-5.4.0-42-generic
Found initrd image: /boot/initrd.img-5.4.0-42-generic
Found linux image: /boot/vmlinuz-5.4.0-39-generic
Found initrd image: /boot/initrd.img-5.4.0-39-generic
grub-probe: error: cannot find a GRUB drive for /dev/sdb1.  Check your device.map.
Found Windows Boot Manager on /dev/sda1@/EFI/Microsoft/Boot/bootmgfw.efi
Adding boot menu entry for UEFI Firmware Settings
done
root@ubuntu:/#



EFI 시스템 장치 UUID 동일성 체크

# blkid | grep -i efi


root@ubuntu:/# blkid | grep -i efi
/dev/sda1: UUID="D827-9E9F" TYPE="vfat" PARTLABEL="EFI System Partition" PARTUUID="7e1a1d1f-3744-47e3-a714-2a60c7523b97"
root@ubuntu:/#

기존 시스템의 EFI System 장치의 UUID를 확인합니다.


# grep -i efi /etc/fstab


root@ubuntu:/# grep -i efi /etc/fstab
# /boot/efi was on /dev/sda1 during installation
UUID=D827-9E9F  /boot/efi       vfat    umask=0077      0       1
root@ubuntu:/#

이제 chroot 명령이 실행된 상태(/mnt 디렉터리를 루트 디렉터리로 설정한 이후)에서 fstab 파일 내의 /boot/efi의 UUID를 화신합니다.

기존 시스템의 EFI System 장치(예 : /dev/sda1)의 UUID와 /etc/fstab 파일 내의 /boot/efi의 UUID가 동일해야 합니다.

보통 동일하겠지만 GRUB 또는 시스템의 손상 유형에 따라 UUID가 다를 수도 있습니다.

blkid 명령으로 확인한 현재 EFI 파티션 UUID가 /etc/fstab의 파티션 UUID와 다른 경우 /etc/fstab의 EFI의 UUID 정보를 현재 UUID로 수정해줍니다.


가상 루트 디렉터리 지정 해제

# exit


root@ubuntu:/# exit
exit
ubuntu@ubuntu:~$ 

가상 루트 디렉터리 설정(chroot)를 해제해줍니다.

루트 디렉터리가 chroot 명령 수행하기 전으로 복구되고 사용자 역시 ubuntu로 전환됩니다.


시스템 재부팅

$ sudo reboot

GNU GRUB의 복구 작업은 마무리되었습니다.

이제 시스템을 재부팅하고 시동디스크를 제거한 다음 기존 시스템으로 부팅 절차를 진행해줍니다.

그럼 위 그림과 같이 GNU GRUB이 정상적으로 복구된 것을 확인할 수 있습니다.