새 EC2 인스턴스에 접속했는데 'Permission denied (publickey)'가 납니다. 키 파일은 분명히 지정했는데 왜 안 되는지, 혹은 서버가 갑자기 emergency mode로 빠져 GRUB에서 복구해야 하는 상황입니다. 부팅 프로세스 전 단계(BIOS/UEFI → GRUB → systemd)와 cloud-init 프로비저닝을 이해하면 이런 장애가 터졌을 때 어느 단계에서 무엇을 봐야 하는지 바로 짚을 수 있습니다.
Linux 서버 프로비저닝 & 부팅 프로세스
- 1BIOS/UEFI → GRUB → 커널 → systemd 부팅 전 과정을 단계별로 설명할 수 있다
- 2AWS EC2 인스턴스를 프로비저닝하고 cloud-init으로 첫 부팅 자동화를 구성할 수 있다
- 3SSH 키페어를 생성하고 공개키 인증으로 안전하게 접속할 수 있다
- 4sshd_config로 PasswordAuthentication·PermitRootLogin 등 SSH 보안을 강화할 수 있다
- 5GRUB Rescue Mode로 진입하고 Emergency Mode 부팅 장애를 복구할 수 있다
journalctl -b --no-pager | head -50ssh-keygen -t ed25519 -C 'your@email.com'실행 결과와 오류는 /var/log/cloud-init.log에서 확인합니다
GRUB 실습은 스냅샷을 먼저 생성한 뒤 진행하세요
커널과 쉘의 역할 분리

실수로 /usr/local/bin/ 아래 바이너리를 모두 지웠는데, 서버는 살아있고 SSH도 연결됩니다. 그런데 ls, cp, mv 같은 기본 명령어가 전부 "command not found"입니다. 이 상황에서 커널과 쉘이 분리되어 있다는 사실이 복구의 실마리가 됩니다. 커널은 멀쩡히 돌아가고 있으므로, 다른 서버에서 바이너리를 scp로 가져오거나 busybox를 직접 올려서 명령어를 복원할 수 있습니다. "왜 어떤 작업은 sudo가 필요한가", "왜 쉘이 죽어도 서버는 살아있는가"도 같은 구조에서 답이 나옵니다.
Linux 시스템은 크게 **커널 영역(Kernel Space)**과 **사용자 영역(User Space)**으로 나뉩니다. 이 구분을 이해하면 "왜 어떤 작업은 sudo가 필요한가", "왜 쉘이 죽어도 커널은 살아있는가"를 직관적으로 파악할 수 있습니다.
커널(Kernel)의 역할
커널은 하드웨어와 소프트웨어 사이의 중재자입니다. 사용자 프로그램이 직접 하드웨어에 접근하지 못하도록 막고, 시스템 콜(system call) 인터페이스를 통해 제어된 접근을 제공합니다.
| 커널 기능 | 설명 |
|---|---|
| 프로세스 관리 | CPU 스케줄링, 프로세스 생성/종료 (fork, exec) |
| 메모리 관리 | 가상 메모리, 페이지 할당, OOM Killer |
| 파일시스템 | ext4, xfs, btrfs 등 VFS 레이어를 통한 추상화 |
| 네트워크 스택 | TCP/IP, 소켓, 방화벽 (netfilter/iptables) |
| 디바이스 드라이버 | 하드웨어 장치와의 통신 |
| 보안 | 권한 모델, SELinux/AppArmor, namespace, cgroup |
쉘(Shell)의 역할
쉘은 사용자가 입력한 명령을 해석하고 커널에 전달하는 **명령 해석기(command interpreter)**입니다. 쉘 자체는 사용자 영역에서 실행되는 하나의 프로세스입니다.
# 현재 사용 중인 쉘 확인
echo $SHELL
# 출력: /bin/bash
# 사용 가능한 쉘 목록
cat /etc/shells
# 출력:
# /bin/sh
# /bin/bash
# /usr/bin/bash
# /bin/zsh
# /usr/bin/zsh
# /usr/bin/fish
주요 쉘 비교:
| 쉘 | 특징 | 주 사용처 |
|---|---|---|
bash | Bourne Again Shell, 가장 보편적 | 서버 스크립팅 표준 |
sh | POSIX 호환, 경량 | 시스템 초기화 스크립트 |
zsh | 자동완성, 플러그인 생태계 풍부 | 개발자 로컬 환경 |
fish | 문법 하이라이팅, 사용자 친화적 | 초보자 학습 환경 |
dash | 경량, POSIX 호환 | Ubuntu /bin/sh의 실체 |
커널-쉘 상호작용 예시:
# 사용자가 'ls /var/log'를 입력하면:
# 1. bash(쉘)가 명령을 파싱
# 2. bash가 fork() 시스템콜로 자식 프로세스 생성
# 3. 자식 프로세스가 execve('/bin/ls') 시스템콜 호출
# 4. 커널이 /bin/ls 바이너리를 메모리에 로드
# 5. ls가 openat(), getdents64() 시스템콜로 디렉터리 읽기
# 6. 결과를 write() 시스템콜로 터미널에 출력
# 시스템콜 추적 (실제로 커널과 어떻게 대화하는지 확인)
strace -e trace=openat,read,write ls /var/log 2>&1 | head -20
2. 부팅 프로세스 전체 흐름
BIOS/UEFI → GRUB → 커널 → systemd 완전 해부

서버가 부팅이 안 될 때 "GRUB rescue>" 프롬프트만 뜨고 아무것도 안 됩니다. 어디서부터 손을 대야 할지 모르면 그냥 OS 재설치로 처리하게 됩니다. 부팅 프로세스를 단계별로 이해하면 어느 단계에서 멈췄는지 보이고, 재설치 없이 복구할 수 있습니다. BIOS/UEFI가 하드웨어를 초기화하고 부트 장치를 찾는 단계, GRUB이 커널 이미지를 메모리에 로드하는 단계, 커널이 초기 RAM 디스크(initramfs)를 풀어서 루트 파일시스템을 마운트하는 단계, systemd가 나머지 서비스를 순서대로 올리는 단계 — 각 단계의 역할을 알면 장애 위치를 짚어낼 수 있습니다.
서버의 전원 버튼을 누른 순간부터 로그인 프롬프트가 나타나기까지, 총 4단계의 부팅 과정이 진행됩니다.
전원 ON
│
▼
[1단계] BIOS / UEFI
│ - 하드웨어 자가진단(POST)
│ - 부팅 장치 탐색
│ - 부트로더 로드
│
▼
[2단계] GRUB (부트로더)
│ - 커널 이미지(vmlinuz) 로드
│ - initrd/initramfs 로드
│ - 커널에 파라미터 전달
│
▼
[3단계] 리눅스 커널
│ - 하드웨어 초기화
│ - 루트 파일시스템 마운트
│ - PID 1 프로세스 실행
│
▼
[4단계] systemd (PID 1)
│ - 타겟(target) 순서대로 서비스 병렬 시작
│ - 네트워크, 로그인 서비스 활성화
│
▼
로그인 프롬프트 / SSH 접속 준비 완료
1단계: BIOS vs UEFI
| 항목 | BIOS | UEFI |
|---|---|---|
| 출시 시기 | 1975년~ | 2005년~ |
| 파티션 테이블 | MBR (최대 2TB, 파티션 4개) | GPT (최대 9.4ZB, 128개 파티션) |
| 부팅 속도 | 느림 | 빠름 (병렬 초기화) |
| 보안 부팅 | 없음 | Secure Boot 지원 |
| 설정 UI | 텍스트 기반 | 그래픽 GUI 가능 |
| AWS EC2 | 레거시 타입 | Nitro 기반 최신 인스턴스 |
2단계: GRUB (Grand Unified Bootloader)
GRUB는 커널을 메모리에 로드하고 실행시키는 부트로더입니다.
# GRUB 설정 파일 위치
cat /etc/default/grub
# 출력 예시:
# GRUB_TIMEOUT=5
# GRUB_DISTRIBUTOR="$(sed 's, release .*$,,g' /etc/system-release)"
# GRUB_DEFAULT=saved
# GRUB_DISABLE_SUBMENU=true
# GRUB_TERMINAL_OUTPUT="console"
# GRUB_CMDLINE_LINUX="crashkernel=auto resume=/dev/mapper/rhel-swap rd.lvm.lv=rhel/root rd.lvm.lv=rhel/swap rhgb quiet"
# GRUB_DISABLE_RECOVERY="true"
GRUB 설정 변경 후 반드시 반영:
# RHEL/CentOS/Amazon Linux (BIOS)
grub2-mkconfig -o /boot/grub2/grub.cfg
# RHEL/CentOS (UEFI)
grub2-mkconfig -o /boot/efi/EFI/redhat/grub.cfg
# Ubuntu (BIOS/UEFI 공통)
update-grub
3단계: initramfs — 임시 루트 파일시스템
커널은 실제 루트 파일시스템을 마운트하기 전에 **initramfs(initial RAM filesystem)**를 메모리에 올려 필요한 드라이버와 도구를 로드합니다.
# initramfs 파일 확인
ls -lh /boot/initramfs-$(uname -r).img
# 출력: -rw-------. 1 root root 34M Mar 26 09:00 /boot/initramfs-5.14.0-570.el9.x86_64.img
# initramfs 내용 확인
lsinitrd /boot/initramfs-$(uname -r).img | head -30
4단계: systemd — 현대적 Init 시스템
커널이 루트 파일시스템을 마운트하고 나면 /sbin/init를 실행합니다. 현대 배포판에서 이것은 systemd의 심볼릭 링크입니다.
ls -la /sbin/init
# 출력: lrwxrwxrwx. 1 root root 22 Jan 15 12:00 /sbin/init -> ../lib/systemd/systemd
# 부팅 시간 분석
systemd-analyze
# 출력:
# Startup finished in 1.823s (kernel) + 2.918s (initrd) + 8.431s (userspace) = 13.173s
# graphical.target reached after 8.385s in userspace
# 어떤 서비스가 부팅을 느리게 하는지 확인
systemd-analyze blame | head -15
# 출력:
# 3.201s NetworkManager-wait-online.service
# 1.456s dnf-makecache.service
# 0.892s dracut-initqueue.service
# 0.654s systemd-udev-settle.service
# 0.543s lvm2-monitor.service
3. 런레벨 vs systemd Target
전통적인 SysVinit 시스템은 **런레벨(Runlevel)**을 사용했습니다. systemd는 이를 Target으로 대체했으며, 하위 호환성을 위해 런레벨 번호도 여전히 인식합니다.
런레벨과 systemd Target 대응표:
| 런레벨 | systemd Target | 설명 |
|---|---|---|
| 0 | poweroff.target | 시스템 종료 |
| 1 | rescue.target | 단일 사용자 모드 (복구) |
| 2 | multi-user.target | 네트워크 없는 다중 사용자 |
| 3 | multi-user.target | 네트워크 있는 다중 사용자 (서버 표준) |
| 4 | multi-user.target | 사용자 정의 (거의 미사용) |
| 5 | graphical.target | GUI 포함 다중 사용자 |
| 6 | reboot.target | 재부팅 |
# 현재 기본 target 확인
systemctl get-default
# 출력: multi-user.target
# 기본 target 변경 (영구 적용)
systemctl set-default graphical.target
# 즉시 target 전환 (재부팅 없이)
systemctl isolate rescue.target
# 현재 활성화된 target 목록
systemctl list-units --type=target --state=active
# 출력:
# UNIT LOAD ACTIVE SUB DESCRIPTION
# basic.target loaded active active Basic System
# cloud-config.target loaded active active Cloud-config availability
# multi-user.target loaded active active Multi-User System
# network-online.target loaded active active Network is Online
# network.target loaded active active Network
# paths.target loaded active active Path Units
# remote-fs.target loaded active active Remote File Systems
# slices.target loaded active active Slice Units
# sockets.target loaded active active Socket Units
# sshd-keygen.target loaded active active OpenSSH Server Key Generation
# sysinit.target loaded active active System Initialization
# timers.target loaded active active Timer Units
Target 의존성 확인:
# multi-user.target이 필요로 하는 서비스 확인
systemctl list-dependencies multi-user.target
# 출력:
# multi-user.target
# ● ├─auditd.service
# ● ├─crond.service
# ● ├─dbus.service
# ● ├─firewalld.service
# ● ├─NetworkManager.service
# ● ├─rsyslog.service
# ● ├─sshd.service
# ● ├─tuned.service
# ● └─basic.target
# ├─ ...
4. VM/EC2 인스턴스 생성 기초
실습 전 디렉토리와 예제 파일을 먼저 준비합니다.
# 실습 디렉토리 준비
mkdir -p /tmp/linux/part5/exam_21 && cd /tmp/linux/part5/exam_21
이제 실습을 진행합니다.
클라우드에서 서버를 처음 만드는 과정입니다. 여기서는 AWS CLI를 사용한 방법을 다루며, 콘솔 UI와 1:1로 대응됩니다.
사전 준비: AWS CLI 설치 및 설정
# AWS CLI 설치 확인
aws --version
# 출력: aws-cli/2.15.0 Python/3.11.6 Linux/5.14.0 exe/x86_64.amzn.2023
# 자격증명 설정
aws configure
# AWS Access Key ID [None]: AKIAIOSFODNN7EXAMPLE
# AWS Secret Access Key [None]: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
# Default region name [None]: ap-northeast-2
# Default output format [None]: json
Key Pair 생성 (EC2 접속용)
# Key Pair 생성 (프라이빗 키를 로컬에 저장)
aws ec2 create-key-pair \
--key-name my-server-key \
--query 'KeyMaterial' \
--output text > ~/.ssh/my-server-key.pem
# 권한 설정 (소유자만 읽기 가능 — 없으면 SSH가 거부함)
chmod 400 ~/.ssh/my-server-key.pem
ls -la ~/.ssh/my-server-key.pem
# 출력: -r--------. 1 ec2-user ec2-user 1675 Mar 26 09:00 /home/ec2-user/.ssh/my-server-key.pem
보안 그룹 생성
# SSH 접속을 허용하는 보안 그룹 생성
aws ec2 create-security-group \
--group-name web-server-sg \
--description "Web server security group" \
--vpc-id vpc-0123456789abcdef0
# 출력:
# {
# "GroupId": "sg-0123456789abcdef0"
# }
# SSH 포트(22) 오픈
aws ec2 authorize-security-group-ingress \
--group-id sg-0123456789abcdef0 \
--protocol tcp \
--port 22 \
--cidr 0.0.0.0/0
# HTTP 포트(80) 오픈
aws ec2 authorize-security-group-ingress \
--group-id sg-0123456789abcdef0 \
--protocol tcp \
--port 80 \
--cidr 0.0.0.0/0
EC2 인스턴스 시작
# Amazon Linux 2023 AMI로 t3.micro 인스턴스 시작
aws ec2 run-instances \
--image-id ami-0c2d3e23e757b5d84 \
--instance-type t3.micro \
--key-name my-server-key \
--security-group-ids sg-0123456789abcdef0 \
--subnet-id subnet-0123456789abcdef0 \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=my-web-server}]' \
--count 1
# 인스턴스 상태 확인 (running이 될 때까지 대기)
aws ec2 describe-instances \
--filters "Name=tag:Name,Values=my-web-server" \
--query 'Reservations[0].Instances[0].{ID:InstanceId,State:State.Name,IP:PublicIpAddress}'
# 출력:
# {
# "ID": "i-0123456789abcdef0",
# "State": "running",
# "IP": "54.180.123.45"
# }
- aws ec2 describe-instances 결과에서 State.Name이 'running'으로 표시된다
- PublicIpAddress 필드에 실제 공인 IP가 할당된 것을 확인한다
- ssh -i my-key.pem ec2-user@<IP> 로 비밀번호 없이 접속이 성공한다
- aws ec2 describe-security-groups 로 22, 80 포트가 인바운드 허용 목록에 있는 것을 확인한다
5. SSH Key 생성과 sshd_config 설정
SSH 키 기반 인증은 비밀번호 인증보다 훨씬 안전합니다. 서버 접속의 표준 방식입니다.
RSA vs ED25519 키 타입 비교:
| 항목 | RSA-4096 | ED25519 |
|---|---|---|
| 알고리즘 | RSA | EdDSA (타원곡선) |
| 키 길이 | 4096비트 | 256비트 (동등 보안성) |
| 성능 | 느림 | 빠름 |
| 호환성 | 거의 모든 시스템 | OpenSSH 6.5+ (2014년~) |
| 권장 | 레거시 시스템 | 현대 서버 |
# ED25519 키 생성 (현대 표준, 권장)
ssh-keygen -t ed25519 -C "admin@company.com" -f ~/.ssh/id_ed25519
# 출력:
# Generating public/private ed25519 key pair.
# Enter passphrase (empty for no passphrase): [패스프레이즈 입력]
# Enter same passphrase again: [재입력]
# Your identification has been saved in /home/user/.ssh/id_ed25519
# Your public key has been saved in /home/user/.ssh/id_ed25519.pub
# The key fingerprint is:
# SHA256:abc123def456ghi789jkl012mno345pqr678stu admin@company.com
# The key's randomart image is:
# +--[ED25519 256]--+
# | .o+. |
# | . o=+ |
# | o . =+o |
# +----[SHA256]-----+
# RSA 4096 키 생성 (레거시 호환 필요 시)
ssh-keygen -t rsa -b 4096 -C "admin@company.com" -f ~/.ssh/id_rsa
# 생성된 키 파일 확인
ls -la ~/.ssh/
# 출력:
# total 20
# drwx------. 2 user user 80 Mar 26 09:00 .
# drwx------. 8 user user 215 Mar 26 09:00 ..
# -rw-------. 1 user user 419 Mar 26 09:01 id_ed25519 ← 프라이빗 키 (절대 공유 금지)
# -rw-r--r--. 1 user user 105 Mar 26 09:01 id_ed25519.pub ← 퍼블릭 키 (서버에 등록)
# 퍼블릭 키 내용 확인
cat ~/.ssh/id_ed25519.pub
# 출력:
# ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBl0mzGpFqKvGHFj8YKo5VzNm9Bg5f2LqXyZ3wPkQrst admin@company.com
서버에 퍼블릭 키 등록:
# 방법 1: ssh-copy-id 사용 (권장)
ssh-copy-id -i ~/.ssh/id_ed25519.pub ec2-user@54.180.123.45
# 방법 2: 수동 등록
ssh ec2-user@54.180.123.45 "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys" < ~/.ssh/id_ed25519.pub
# 방법 3: EC2 user-data로 초기화 시 자동 등록
# (AWS Console의 'Advanced Details > User data'에 입력)
SSH 데몬의 설정 파일 /etc/ssh/sshd_config를 튜닝하면 보안을 크게 향상시킬 수 있습니다.
# 현재 sshd_config 백업
cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak
# 설정 파일 편집
vi /etc/ssh/sshd_config
보안 강화를 위한 핵심 설정:
# /etc/ssh/sshd_config 주요 설정 항목
# 1. SSH 포트 변경 (기본 22 → 다른 포트로, 스캔 공격 감소)
Port 2222
# 2. 루트 직접 로그인 금지
PermitRootLogin no
# 3. 비밀번호 인증 비활성화 (키 인증만 허용)
PasswordAuthentication no
ChallengeResponseAuthentication no
# 4. 빈 비밀번호 허용 금지
PermitEmptyPasswords no
# 5. 퍼블릭 키 인증 활성화
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
# 6. 최대 인증 시도 횟수 제한
MaxAuthTries 3
# 7. 유휴 세션 타임아웃 (300초 = 5분)
ClientAliveInterval 300
ClientAliveCountMax 2
# 8. X11 포워딩 비활성화 (필요 없는 경우)
X11Forwarding no
# 9. 특정 사용자/그룹만 SSH 허용
AllowUsers ec2-user deploy-user
# AllowGroups sysadmin developers
# 10. 배너 설정 (로그인 전 경고 메시지)
Banner /etc/ssh/banner.txt
# 설정 문법 검사 (재시작 전 반드시 확인)
sshd -t
# 오류가 없으면 아무 출력도 없음
# 상세 문법 검사
sshd -T | grep -E "^(port|permitrootlogin|passwordauthentication|pubkeyauthentication)"
# 출력:
# port 2222
# permitrootlogin no
# passwordauthentication no
# pubkeyauthentication yes
# SSH 서비스 재시작 (기존 세션은 유지됨)
systemctl reload sshd
# 또는 완전 재시작
systemctl restart sshd
# 서비스 상태 확인
systemctl status sshd
# 출력:
# ● sshd.service - OpenSSH server daemon
# Loaded: loaded (/usr/lib/systemd/system/sshd.service; enabled; preset: enabled)
# Active: active (running) since Thu 2026-03-26 09:05:00 KST; 5s ago
# Main PID: 1234 (sshd)
매번 긴 명령어를 입력하지 않도록 ~/.ssh/config 파일로 접속 정보를 관리합니다.
# ~/.ssh/config 파일 생성
cat > ~/.ssh/config << 'EOF'
# 개발 서버
Host dev-server
HostName 54.180.123.45
User ec2-user
Port 22
IdentityFile ~/.ssh/id_ed25519
ServerAliveInterval 60
# 운영 서버 (포트 변경)
Host prod-server
HostName 52.79.200.100
User deploy-user
Port 2222
IdentityFile ~/.ssh/id_rsa
ServerAliveInterval 60
# 점프 호스트를 통한 내부망 접속
Host internal-db
HostName 10.0.1.50
User ubuntu
ProxyJump dev-server
IdentityFile ~/.ssh/id_ed25519
EOF
# 파일 권한 설정
chmod 600 ~/.ssh/config
# 이제 간단하게 접속 가능
ssh dev-server
# 위 명령은 아래와 동일:
# ssh -i ~/.ssh/id_ed25519 -p 22 ec2-user@54.180.123.45
# 첫 접속 시 호스트 지문 확인 메시지
# The authenticity of host '54.180.123.45 (54.180.123.45)' can't be established.
# ED25519 key fingerprint is SHA256:abc123def456...
# Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
# Warning: Permanently added '54.180.123.45' (ED25519) to the list of known hosts.
# 접속 후 시스템 정보 확인
uname -a
# 출력: Linux ip-10-0-1-100 5.10.209-198.812.amzn2023.x86_64 #1 SMP Mon Mar 4 20:28:28 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux
cat /etc/os-release | head -5
# 출력:
# NAME="Amazon Linux"
# VERSION="2023"
# ID="amzn"
# ID_LIKE="fedora"
# VERSION_ID="2023"
6. 부팅 실패 시 Rescue Mode 진입
시스템이 정상 부팅되지 않을 때, GRUB 메뉴에서 Rescue Mode(단일 사용자 모드)로 진입하여 복구 작업을 수행할 수 있습니다.
방법 1: GRUB 메뉴에서 직접 진입
# 서버 재부팅 후 GRUB 메뉴가 나타날 때 'e' 키를 눌러 편집 모드 진입
GNU GRUB version 2.06
+------------------------------------------------------------------+
| Amazon Linux 2023 (5.14.0-570.52.1.el9_6.x86_64) |
| Amazon Linux 2023 (5.14.0-284.11.1.el9_3.x86_64) |
| |
+------------------------------------------------------------------+
Use the ^ and v keys to select which entry is highlighted.
Press enter to boot the selected OS, 'e' to edit the commands...
# 편집 모드에서 'linux' 줄 끝을 찾아 아래 파라미터 추가
linux ($root)/vmlinuz-5.14.0-570.el9.x86_64 root=/dev/mapper/rhel-root \
ro crashkernel=auto resume=/dev/mapper/rhel-swap \
rd.lvm.lv=rhel/root rd.lvm.lv=rhel/swap rhgb quiet \
systemd.unit=rescue.target ← 이 줄 추가
# 또는 단일 사용자 모드:
# single
# 또는 init 교체:
# init=/bin/bash
# Ctrl+X 또는 F10으로 부팅
방법 2: systemctl 명령으로 즉시 전환 (운영 중인 시스템에서)
# Rescue mode로 전환 (root 패스워드 필요)
systemctl rescue
# Emergency mode로 전환 (최소한의 환경, 네트워크 없음)
systemctl emergency
Rescue Mode에서 일반적인 복구 작업:
# 루트 파일시스템 마운트 확인
mount | grep " / "
# 출력: /dev/mapper/rhel-root on / type xfs (ro,relatime,attr2,inode64,logbufs=8,...)
# 읽기-쓰기로 재마운트 (파일 수정을 위해)
mount -o remount,rw /
# 잊어버린 root 패스워드 변경
passwd root
# New password: [새 패스워드 입력]
# Retype new password: [재입력]
# passwd: all authentication tokens updated successfully.
# 손상된 파일시스템 검사 및 복구
fsck -y /dev/sda1
# /etc/fstab 오류로 부팅 실패한 경우 — 잘못된 항목 주석 처리
vi /etc/fstab
# 문제 있는 줄 앞에 # 추가하여 주석 처리
# SELinux 레이블 재설정 (SELinux 문제로 부팅 실패 시)
touch /.autorelabel
# 복구 완료 후 정상 재부팅
systemctl reboot
7. 장애 진단: SSH 접속 거부 (Permission denied) 원인 파악
상황
새 EC2 인스턴스에 키 파일을 지정해서 SSH 접속을 시도했는데 공개키 인증이 거부되어 로그인이 안 됩니다.
원인
클라이언트의 프라이빗 키 권한이 644(600이어야 함), 서버의 authorized_keys 권한·내용 오류, EC2 AMI별 기본 사용자명 불일치(Amazon Linux → ec2-user, Ubuntu → ubuntu), sshd_config에서 PubkeyAuthentication no 설정 중 하나입니다.
진단
# 클라이언트 측 — 상세 디버그로 어느 단계에서 실패하는지 확인
ssh -vvv ec2-user@54.180.123.45 2>&1 | grep -E "Offering|accepts|refused|denied"
# 출력 예:
# debug1: Offering public key: /home/user/.ssh/id_ed25519 ED25519 SHA256:abc123...
# debug1: send_pubkey_test: no mutual signature algorithm ← 알고리즘 불일치
# 프라이빗 키 권한 확인
ls -la ~/.ssh/
# -rw-r--r--. 1 user user 419 id_ed25519 ← 644는 오류 (600이어야 함)
# 서버 SSH 로그 확인 (콘솔 접근 가능한 경우)
journalctl -u sshd -n 30 --no-pager
# Authentication refused: bad ownership or modes for directory /home/ec2-user/.ssh
해결
# 클라이언트 키 권한 수정
chmod 600 ~/.ssh/id_ed25519
chmod 700 ~/.ssh/
# 서버 authorized_keys 권한 수정 (콘솔 접근 후)
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
# EC2 AMI별 올바른 사용자명으로 재시도
ssh -i ~/.ssh/id_ed25519 ec2-user@54.180.123.45 # Amazon Linux
ssh -i ~/.ssh/id_ed25519 ubuntu@54.180.123.45 # Ubuntu
ssh -i ~/.ssh/id_ed25519 admin@54.180.123.45 # Debian
상황
서버에 SSH 접속을 시도했는데 ping은 되지만 포트 22에서 Connection refused가 납니다.
원인
sshd 프로세스가 죽었거나, OS 방화벽(firewalld/iptables)이 22번 포트를 차단하고 있거나, AWS 보안그룹에 22번 인바운드 규칙이 누락됐거나, sshd_config 오타로 sshd 시작이 실패한 경우입니다.
진단
# 포트 연결 여부 먼저 확인
nc -zv 54.180.123.45 22
# Connection refused → sshd 미구동 또는 방화벽 차단
# 서버 콘솔에서 sshd 상태 확인
systemctl status sshd
# ● sshd.service - OpenSSH server daemon
# Active: inactive (dead) ← 죽어있음
# sshd_config 문법 오류 확인
sshd -t
# /etc/ssh/sshd_config: line 42: Bad configuration option: PermitRootLogi ← 오타
# OS 방화벽 확인
firewall-cmd --list-all | grep -E "services|ports"
해결
# sshd 재시작 및 부팅 자동 시작 설정
systemctl start sshd
systemctl enable sshd
# firewalld에 SSH 포트 허용
firewall-cmd --permanent --add-service=ssh
firewall-cmd --reload
# AWS 보안그룹에 22번 인바운드 추가
aws ec2 authorize-security-group-ingress \
--group-id sg-0123456789abcdef0 \
--protocol tcp --port 22 --cidr 0.0.0.0/0
# sshd_config 오타 수정 후 재시작
vi /etc/ssh/sshd_config
sshd -t && systemctl restart sshd
상황
서버를 재부팅했는데 콘솔에 emergency mode 메시지가 뜨고 일반 부팅이 안 됩니다. SSH 접속 자체가 불가능합니다.
원인
/etc/fstab에 존재하지 않는 UUID나 마운트 포인트를 지정했거나(볼륨 분리 후 fstab 미수정), 파일시스템이 손상되어 fsck가 필요하거나, 추가 EBS 볼륨을 unmount 없이 제거한 경우입니다.
진단
# 증상: 서버 콘솔에 아래 메시지 출력 후 정지
# [FAILED] Failed to mount /data.
# See 'journalctl -xb' for details.
# [DEPEND] Dependency failed for Local File Systems.
#
# Welcome to emergency mode! After logging in, type "journalctl -xb" to view
# system logs, "systemctl reboot" to reboot, "systemctl default" or ^D to
# try again to boot into default mode.
# Give root password for maintenance
# (or press Control-D to continue):
해결
# root 패스워드 입력 후 emergency shell 진입
# 에러 로그 확인
journalctl -xb | grep -i "failed\|error" | head -20
# Mar 26 09:00:05 server systemd[1]: Failed to mount /data.
# 루트를 읽기-쓰기로 재마운트 후 fstab 확인
mount -o remount,rw /
cat /etc/fstab
# UUID=yyy /data ext4 defaults 0 0 ← 없는 장치 UUID
# 현재 연결된 블록 장치 UUID 확인 후 fstab 수정
blkid
vi /etc/fstab
# 문제 줄 주석 처리: #UUID=yyy /data ext4 defaults 0 0
# 파일시스템 손상 시 fsck로 복구
fsck -y /dev/xvdb1
# 복구 후 정상 재부팅
systemctl reboot
cloud-init 사용자 스크립트 실행 오류 디버깅
AWS EC2 등 클라우드 인스턴스 기동 시 주입한 user-data 스크립트가 실행되지 않거나 조용히 실패하는 경우가 허다합니다. 이때 SRE는 반드시 아래의 부팅 프로비저닝 물리 로그 경로를 추적해야 합니다.
- /var/log/cloud-init-output.log:
사용자 데이터 스크립트의 실행 과정에서 발생하는 모든 표준 출력(
stdout)과 표준 에러(stderr)가 날것으로 기록되는 핵심 파일입니다. 스크립트 문법 오류나 설치 에러는 모두 이 파일 안에 범죄 증거로 수록되어 있습니다.
8. 실무 관점: 프로비저닝 자동화와 보안
실제 운영 환경에서는 서버를 수동으로 한 대씩 설정하지 않습니다. Infrastructure as Code(IaC) 도구를 사용하여 프로비저닝을 코드로 정의하고 자동화합니다.
User Data를 통한 EC2 초기화 자동화:
#!/bin/bash
# EC2 User Data 스크립트 — 인스턴스 최초 부팅 시 한 번 실행
# 시스템 업데이트
yum update -y
# 필수 패키지 설치
yum install -y nginx git htop
# nginx 설정
cat > /etc/nginx/conf.d/app.conf << 'NGINX'
server {
listen 80;
server_name _;
location / {
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
NGINX
# nginx 활성화 및 시작
systemctl enable nginx
systemctl start nginx
# SSH 보안 강화
sed -i 's/#PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config
sed -i 's/#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
systemctl restart sshd
# 완료 로그
echo "$(date): Provisioning complete" >> /var/log/user-data.log
Terraform을 이용한 선언적 프로비저닝:
# main.tf (Terraform 설정 예시)
# resource "aws_instance" "web_server" {
# ami = "ami-0c2d3e23e757b5d84"
# instance_type = "t3.micro"
# key_name = aws_key_pair.deployer.key_name
#
# vpc_security_group_ids = [aws_security_group.web.id]
#
# user_data = file("setup.sh")
#
# tags = {
# Name = "web-server"
# Environment = "production"
# }
# }
# 인프라 배포
terraform init
terraform plan # 변경 사항 미리 확인
terraform apply # 실제 적용
현업 프로비저닝 도구 비교:
| 도구 | 특징 | 주 사용처 |
|---|---|---|
| Terraform | 선언적 IaC, 멀티 클라우드 | 인프라 생성/관리 |
| Ansible | 에이전트리스, YAML 플레이북 | 서버 구성 관리 |
| AWS CDK | 프로그래밍 언어로 IaC 작성 | AWS 전용 |
| Packer | 이미지 빌드 자동화 | AMI/VM 이미지 생성 |
| cloud-init | 클라우드 초기화 표준 | 부팅 시 초기 설정 |
서버 보안에서 SSH는 가장 많이 공격받는 진입점입니다. 현업에서는 다음 사항들을 필수로 체크합니다.
SSH 접속 로그 모니터링:
# 최근 성공한 SSH 로그인 확인
last | grep ssh | head -20
# 출력:
# ec2-user pts/0 203.0.113.10 Thu Mar 26 09:00 still logged in
# ec2-user pts/1 198.51.100.5 Thu Mar 26 08:30 - 08:45 (00:15)
# 실패한 로그인 시도 확인 (무차별 대입 공격 탐지)
journalctl -u sshd | grep "Failed password\|Invalid user\|Connection closed" | tail -20
# 출력:
# Mar 26 02:13:05 server sshd[5678]: Invalid user admin from 185.220.101.45
# Mar 26 02:13:06 server sshd[5679]: Invalid user root from 185.220.101.45
# Mar 26 02:13:07 server sshd[5680]: Invalid user postgres from 185.220.101.45
# 공격 IP 확인 및 차단
# fail2ban 설치 (자동 IP 차단)
yum install -y fail2ban
systemctl enable --now fail2ban
# fail2ban 상태 확인
fail2ban-client status sshd
# 출력:
# Status for the jail: sshd
# |- Filter
# | |- Currently failed: 3
# | |- Total failed: 127
# | `- Journal matches: _SYSTEMD_UNIT=sshd.service + _COMM=sshd
# `- Actions
# |- Currently banned: 5
# |- Total banned: 23
# `- Banned IP list: 185.220.101.45 185.220.101.46 ...
# SSH 감사 로그 설정 (누가 어떤 명령을 실행했는지 기록)
# /etc/profile.d/audit.sh
# export HISTTIMEFORMAT="%F %T "
# export PROMPT_COMMAND='history -a >(tee -a /var/log/user-commands.log | logger -t "user_cmd")'
현업 SSH 보안 체크리스트:
# 1. 비밀번호 인증 비활성화 확인
sshd -T | grep passwordauthentication
# 출력: passwordauthentication no ← 반드시 no여야 함
# 2. Root 직접 접속 금지 확인
sshd -T | grep permitrootlogin
# 출력: permitrootlogin no ← 반드시 no여야 함
# 3. 오래된 SSH 키 확인 및 교체 (90일 이상 된 키)
find /home -name authorized_keys -exec ls -la {} \;
# 4. 사용하지 않는 계정 비활성화
usermod -L old-employee-user # 계정 잠금
passwd -l old-employee-user # 비밀번호 잠금
# 5. SSH 접속 허용 IP 범위 제한 (보안 그룹 또는 TCP Wrappers)
cat /etc/hosts.allow
# sshd: 10.0.0.0/8, 203.0.113.0/24
cat /etc/hosts.deny
# sshd: ALL
# 6. 정기적인 보안 업데이트 적용
yum update --security -y
# 또는
apt-get update && apt-get upgrade -y
다음 모듈에서는 Cron & 작업 스케줄링 — crontab -e로 주기적 명령 실행을 자동화하고 systemd timer로 더 강력한 작업 스케줄링을 구성하는 방법을 다룹니다.