Platform

Linux 네트워크 보안 기초와 클라우드 운영 연결점

Linux 프로세스, 포트, DNS, 라우팅, SSH, UFW, 로그 확인을 클라우드 장애 분석 흐름으로 정리합니다.

Linux 네트워크 보안 기초와 클라우드 운영 연결점

클라우드 서비스를 운영하다 보면 처음에는 문제가 추상적인 이름으로 보인다. Load Balancer, Security Group, NSG, Ingress, DNS, Private Subnet, Bastion 같은 리소스 이름이 앞에 있지만, 실제 장애를 따라가면 마지막에는 Linux 프로세스, 포트, 라우팅 테이블, DNS 해석, 파일 권한, 로그로 내려오는 경우가 많다.

예를 들어 “API가 응답하지 않는다”는 문장은 아직 원인이 아니다. 애플리케이션 프로세스가 죽었는지, 포트가 listen 중인지, 로컬에서는 curl이 되는지, 외부에서만 막히는지, DNS가 올바른 IP를 가리키는지, 라우팅 경로가 맞는지, TLS 인증서나 프록시가 문제인지 차례로 좁혀가야 한다. 이 글은 Linux 기본 명령어와 TCP/IP, 방화벽, SSH, 로그 확인을 클라우드 운영 흐름 안에서 다시 정리한 기록이다.

장애 분석은 프로세스에서 시작한다

서버가 응답하지 않을 때 가장 먼저 볼 것은 프로세스와 서비스 상태다. systemd 기반 Linux에서는 systemctl로 서비스 상태를 확인하고, journalctl로 서비스 로그를 확인한다.

systemctl status nginx
systemctl status ssh
systemctl status my-api

journalctl -u nginx -n 100 --no-pager
journalctl -u my-api -f
journalctl -xe

프로세스 목록은 ps, top, htop, pstree로 확인한다.

ps aux | grep nginx
ps -ef | grep java
pstree -p
top

여기서 중요한 것은 “프로세스가 있다”와 “서비스가 정상이다”를 구분하는 것이다. 프로세스는 살아 있지만 DB 연결 실패로 요청마다 500을 반환할 수 있고, readiness endpoint는 실패하지만 프로세스 자체는 종료되지 않을 수도 있다. 컨테이너나 Kubernetes에서도 같은 관점이 필요하다. Pod가 Running이어도 애플리케이션이 요청을 처리할 준비가 되어 있는지는 별도 확인이 필요하다.

포트와 소켓 확인

프로세스가 살아 있다면 다음은 포트다. 서버가 80, 443, 8080, 3000 같은 포트에서 실제로 listen 중인지 확인한다.

ss -tulnp
ss -antp
netstat -tulnp
lsof -i :8080

ss -tulnp 출력에서 LISTEN 상태가 보이지 않으면 애플리케이션이 해당 포트를 열지 않은 것이다. 이때 외부 요청을 보기 전에 로컬에서 먼저 확인한다.

curl -I http://127.0.0.1:8080
curl -v http://localhost:8080/health

로컬 curl은 성공하는데 외부에서 실패한다면 애플리케이션 자체보다 방화벽, 보안 그룹, 로드밸런서, DNS 쪽을 봐야 한다. 반대로 로컬에서도 실패한다면 애플리케이션 로그, 환경 변수, 포트 설정, 프로세스 실행 계정을 먼저 봐야 한다.

운영에서 자주 쓰는 확인 흐름은 다음처럼 단순하다.

process exists?
  -> port listening?
    -> local curl succeeds?
      -> host firewall allows?
        -> cloud firewall allows?
          -> load balancer target healthy?
            -> DNS points to correct endpoint?

DNS와 이름 해석

사용자는 도메인으로 접근하지만 서버는 IP와 포트로 통신한다. 그래서 DNS는 장애 분석에서 자주 놓치는 지점이다. 먼저 도메인이 어떤 IP로 해석되는지 확인한다.

dig api.example.com
nslookup api.example.com
cat /etc/hosts

/etc/hosts는 로컬에서 도메인과 IP를 강제로 매핑할 수 있다. 개발이나 임시 테스트에는 유용하지만, 운영 서버에 남아 있으면 실제 DNS 변경을 무시하는 장애 원인이 될 수 있다.

sudo nano /etc/hosts

DNS가 올바른 IP를 반환해도 캐시, TTL, CDN, 로드밸런서 상태 때문에 사용자 위치마다 결과가 다를 수 있다. CloudFront, Route 53, Traffic Manager 같은 DNS/CDN 기반 구성을 쓸 때는 DNS 응답과 실제 HTTP 응답을 분리해서 본다.

curl -I https://api.example.com
curl -v --resolve api.example.com:443:203.0.113.10 https://api.example.com

--resolve는 특정 IP로 강제 연결하면서도 Host header와 TLS SNI를 유지한다. DNS가 문제인지, 특정 서버 인스턴스가 문제인지 분리해서 확인할 때 유용하다.

TCP/IP 요청 흐름으로 보기

웹 요청은 단번에 “백엔드로 간다”가 아니라 여러 계층을 지난다.

OSI 7계층을 모두 암기하는 것보다 운영에서는 “어느 계층에서 실패했는지”를 구분하는 것이 더 중요했다. DNS가 실패하면 애플리케이션 로그에는 아무것도 남지 않는다. TCP 연결이 실패하면 포트, 방화벽, 라우팅을 봐야 한다. TLS가 실패하면 인증서, SNI, 프록시 설정을 봐야 한다. HTTP 500이면 애플리케이션과 의존 서비스 로그를 봐야 한다.

포트도 같은 맥락이다. 80/443은 외부 웹 트래픽, 22는 SSH, 5432는 PostgreSQL, 6379는 Redis처럼 관례가 있지만, 운영에서는 “그 포트가 어디까지 열려 있어야 하는가”를 더 엄격하게 봐야 한다. 데이터베이스 포트가 인터넷에 열려 있으면 안 되고, 애플리케이션 서브넷에서만 접근 가능해야 한다.

라우팅과 서브넷 감각

TCP/IP에서 IP 주소는 네트워크와 호스트 부분으로 나뉜다. 서브넷 마스크는 그 경계를 정한다.

IP address: 192.168.1.10
Subnet:    255.255.255.0
CIDR:      /24
Network:   192.168.1.0
Hosts:     192.168.1.1 - 192.168.1.254
Broadcast: 192.168.1.255

192.168.1.0/24/26으로 나누면 4개의 작은 네트워크가 생긴다.

192.168.1.0/26     hosts .1   - .62
192.168.1.64/26    hosts .65  - .126
192.168.1.128/26   hosts .129 - .190
192.168.1.192/26   hosts .193 - .254

클라우드의 VPC/VNet, Subnet, Route Table은 이 개념을 리소스로 표현한 것이다. Public Subnet과 Private Subnet을 나누고, NAT Gateway와 Internet Gateway를 붙이고, Security Group/NSG를 설정하는 일은 결국 “어떤 네트워크가 어디로 나갈 수 있는가”를 정의하는 작업이다.

서버에서 현재 IP와 라우팅 테이블을 확인한다.

ip addr
ip link show up
ip route show
route -n

목적지까지 경로를 확인한다.

ping 8.8.8.8
traceroute api.example.com

ICMP가 막힌 환경도 많기 때문에 ping 실패가 곧 서비스 실패는 아니다. HTTP 서비스라면 curl, TCP 연결이라면 nc로 포트 연결을 별도로 확인한다.

nc -vz api.example.com 443
nc -vz 10.0.2.15 5432

운영에서 자주 보는 형태는 다음과 같다.

public subnet
  - load balancer
  - NAT gateway

private application subnet
  - application server
  - container node

private data subnet
  - database
  - cache

이 구조에서는 사용자가 데이터베이스로 직접 접근할 이유가 없다. 브라우저는 로드밸런서까지만 접근하고, 애플리케이션은 private network 안에서 DB와 통신한다. 그래서 방화벽 규칙도 “필요한 방향과 포트만 허용”하는 식으로 좁혀야 한다.

패킷을 봐야 할 때

애플리케이션 로그와 포트 상태로 답이 나오지 않으면 패킷을 봐야 할 때가 있다. tcpdump는 지정한 인터페이스에서 오가는 패킷을 캡처한다.

sudo tcpdump -i eth0
sudo tcpdump -i eth0 port 80
sudo tcpdump -i eth0 host 10.0.2.15

요청이 서버까지 들어오지 않는다면 앞단 네트워크나 방화벽 문제일 가능성이 크다. 요청은 들어오는데 응답이 나가지 않는다면 애플리케이션, 로컬 방화벽, 라우팅, 응답 경로를 본다. 패킷 캡처는 마지막 수단에 가깝지만, 추측을 줄여주는 강력한 도구다.

SSH 접속은 편의보다 경계가 먼저다

SSH는 원격 서버를 관리하기 위한 기본 도구다. 먼저 설치와 상태를 확인한다.

sudo apt install openssh-server
systemctl status ssh

운영 서버에서는 설정 파일을 바로 수정하기 전에 백업한다.

sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak
sudo nano /etc/ssh/sshd_config

root 직접 로그인은 막고, 필요하다면 기본 포트를 바꿀 수 있다.

Port 2222
PermitRootLogin no
PasswordAuthentication no

설정을 적용한 뒤에는 새 터미널에서 접속 테스트를 먼저 한다. 기존 세션을 닫기 전에 새 설정으로 접속되는지 확인해야 한다.

sudo systemctl restart ssh
ssh user@server.example.com -p 2222

비밀번호보다 SSH key 기반 접속이 안전하다.

ssh-keygen
ssh-copy-id user@server.example.com
ssh user@server.example.com
scp ./app.log user@server.example.com:/tmp/app.log

접속이 실패하면 권한 문제를 확인한다.

chmod 755 ~
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys

클라우드에서는 SSH 포트를 전체 인터넷에 열지 않는 것이 기본이다. Bastion, VPN, SSM Session Manager, Azure Bastion 같은 관리 경로를 두고, 보안 그룹이나 NSG에서 접근 출처를 제한해야 한다.

UFW와 클라우드 방화벽을 함께 보기

Ubuntu에서는 UFW로 기본 방화벽 규칙을 다룰 수 있다.

sudo ufw status
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 2222/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status numbered

잘못 추가한 규칙은 번호로 삭제할 수 있다.

sudo ufw delete 3
sudo ufw reset

클라우드 환경에서는 방화벽이 한 겹이 아니다. Security Group/NSG에서 허용되어도 서버 내부 UFW가 막으면 통신은 실패한다. 반대로 UFW에서 열려 있어도 Security Group/NSG가 막으면 외부에서 접근할 수 없다. 장애를 볼 때는 두 계층을 함께 확인해야 한다.

Internet
  -> Cloud firewall: Security Group / NSG
    -> Host firewall: UFW / iptables / nftables
      -> Process listening port

이 구조를 이해하면 “포트를 열었다”는 말도 더 구체적으로 바뀐다. 어디에서 열었는지, 누구에게 열었는지, 어떤 방향의 트래픽인지, 실제 프로세스가 그 포트를 받고 있는지 확인해야 한다.

WSL2 포트 포워딩에서 배운 네트워크 경계

WSL2에서는 Linux가 별도 가상 네트워크 안에서 동작한다. WSL 내부에서 NGINX나 SSH를 실행했다고 해서 외부 장치가 바로 접근할 수 있는 것은 아니다. WSL IP를 확인하고, Windows에서 portproxy와 방화벽 규칙을 설정해야 한다.

ip addr show eth0

Windows PowerShell 관리자 권한에서 포트 포워딩을 추가한다.

netsh interface portproxy add v4tov4 `
  listenport=80 `
  listenaddress=0.0.0.0 `
  connectport=80 `
  connectaddress=172.29.205.121

New-NetFirewallRule `
  -DisplayName "WSL 80 Port" `
  -Direction Inbound `
  -LocalPort 80 `
  -Protocol TCP `
  -Action Allow

확인과 삭제도 필요하다.

netsh interface portproxy show all
netsh interface portproxy delete v4tov4 listenport=80 listenaddress=0.0.0.0

이 실습은 로컬 개발 환경 이야기처럼 보이지만, 클라우드 네트워크와 닮아 있다. 서비스가 떠 있는 네트워크, 외부에서 들어오는 주소, 중간 포워딩 규칙, 방화벽 허용 규칙이 모두 맞아야 요청이 통과한다.

로그, 파일 권한, 무결성

Linux에서 로그는 장애와 보안 이벤트를 찾는 기본 위치다.

cd /var/log
sudo less auth.log
sudo tail -f /var/log/nginx/access.log
sudo tail -f /var/log/nginx/error.log
last
lastlog
history

파일 권한과 소유자도 자주 문제를 만든다.

ls -lh
chmod 640 app.conf
chown app:app app.conf

민감한 파일은 무결성 확인이나 암호화를 고려할 수 있다.

sha256sum release.tar.gz
gpg -c secrets.txt
gpg secrets.txt.gpg

운영 환경에서는 누가 접속했는지, 어떤 파일이 바뀌었는지, 어떤 서비스가 재시작되었는지 추적할 수 있어야 한다. 로그가 남지 않거나 시간이 맞지 않으면 장애 분석과 감사가 어려워진다. 그래서 서버 시간 동기화, 로그 보존, 접근 권한, 배포 이력은 기술적으로는 작은 설정처럼 보여도 운영 신뢰성의 일부가 된다.

사용자, 패키지, 서비스 관리는 VM 운영의 기본선이다

클라우드에서 VM을 직접 운영하면 네트워크만 볼 수 없다. 누가 서버에 접근할 수 있는지, 어떤 패키지가 설치되어 있는지, 어떤 서비스가 자동으로 시작되는지까지 확인해야 한다. 관리형 서비스에서는 보이지 않던 OS 계층의 책임이 VM에서는 다시 운영자의 몫이 된다.

사용자와 그룹은 권한의 기본 단위다. 모든 작업을 root로 실행하면 빠르지만, 장애나 침해가 발생했을 때 피해 범위를 줄이기 어렵다. 애플리케이션 실행 계정, 배포 계정, 관리자 계정을 분리하고, sudo 권한은 필요한 사용자에게만 준다.

whoami
id appuser
groups appuser

sudo useradd --system --create-home --shell /usr/sbin/nologin appuser
sudo usermod -aG docker deployer
sudo passwd -l appuser

패키지 관리는 보안 패치와 재현성에 연결된다. 운영 VM에서 임의로 패키지를 설치하면 나중에 같은 환경을 다시 만들기 어렵다. 최소한 설치 명령과 버전을 남기고, 반복되는 구성은 cloud-init, Ansible, Packer, Terraform provisioner 같은 방식으로 코드화하는 편이 좋다.

sudo apt update
apt list --upgradable
dpkg -l | grep nginx
apt-cache policy docker.io

서비스 관리는 systemctl을 기준으로 본다. 프로세스가 살아 있는지, 부팅 시 자동 시작되는지, 최근 실패 로그가 있는지를 함께 확인한다.

systemctl status nginx --no-pager
systemctl is-enabled nginx
journalctl -u nginx --since "30 minutes ago" --no-pager

애플리케이션을 systemd 서비스로 등록하면 단순히 백그라운드 실행을 넘어 재시작 정책과 실행 계정, 환경 변수 경계를 명시할 수 있다.

[Unit]
Description=Backend API
After=network.target

[Service]
User=appuser
Group=appuser
WorkingDirectory=/srv/backend
EnvironmentFile=/etc/backend/backend.env
ExecStart=/usr/bin/node /srv/backend/server.js
Restart=on-failure
RestartSec=5
NoNewPrivileges=true

[Install]
WantedBy=multi-user.target

이 파일에서 봐야 할 부분은 ExecStart만이 아니다. 어떤 계정으로 실행되는지, 환경 변수가 어디에서 주입되는지, 실패 시 재시작되는지, 권한 상승을 막는지까지가 운영 설정이다. 컨테이너와 Kubernetes를 쓰더라도 이 감각은 사라지지 않는다. Pod의 securityContext, liveness probe, restartPolicy도 결국 같은 질문을 다른 계층에서 표현한 것이다.

반복 점검은 쉘 스크립트로 남긴다

매번 같은 명령어를 손으로 치면 누락이 생긴다. 기본 점검 순서는 쉘 스크립트로 남겨두는 것이 좋다. 아래 예시는 포트, 서비스 상태, DNS, HTTP health check를 한 번에 확인하는 간단한 스크립트다.

#!/usr/bin/env bash
set -euo pipefail

APP_SERVICE="${APP_SERVICE:-my-api}"
APP_PORT="${APP_PORT:-8080}"
APP_HOST="${APP_HOST:-api.example.com}"

echo "== service =="
systemctl is-active "$APP_SERVICE" || true

echo "== port =="
ss -tulnp | grep ":${APP_PORT}" || true

echo "== dns =="
dig +short "$APP_HOST" || true

echo "== local health =="
curl -fsS "http://127.0.0.1:${APP_PORT}/health" || true

echo "== external health =="
curl -fsS "https://${APP_HOST}/health" || true

실행 권한을 주고 사용한다.

chmod +x check-api.sh
APP_SERVICE=my-api APP_PORT=8080 APP_HOST=api.example.com ./check-api.sh

이 정도 스크립트만 있어도 장애 대응 중에 “어디까지 확인했는지”를 팀원과 공유하기 쉬워진다. 나중에는 GitHub Actions, cron, Azure Functions, AWS Lambda, Prometheus exporter 같은 방식으로 발전시킬 수 있지만, 출발점은 반복 가능한 확인 순서를 만드는 것이다.

클라우드 운영에서의 확인 순서

외부에서 API에 접속할 수 없다고 가정하면 다음 순서로 볼 수 있다.

1. DNS
   dig api.example.com

2. Client-side HTTP
   curl -v https://api.example.com/health

3. Load balancer / Ingress
   target health, backend status

4. Cloud firewall
   Security Group / NSG inbound rule

5. Host firewall
   sudo ufw status

6. Process and port
   systemctl status app
   ss -tulnp

7. Local response
   curl -v http://127.0.0.1:8080/health

8. Logs
   journalctl -u app -n 100
   tail -f application.log

이 순서를 거치면 “서버가 안 된다”는 말이 조금씩 구체화된다. DNS가 틀린 것인지, 로드밸런서가 타깃을 unhealthy로 보는지, 보안 그룹이 막는지, 서버는 열려 있지만 애플리케이션이 500을 내는지 분리할 수 있다.

정리

Linux와 네트워크 기초는 오래된 지식처럼 보일 수 있지만, 클라우드와 Kubernetes 운영에서는 계속 필요한 기반이다. 프로세스, 포트, DNS, 라우팅, 방화벽, 로그를 볼 수 있어야 추상화된 클라우드 리소스 뒤에서 실제로 무슨 일이 일어나는지 좁혀갈 수 있다.

좋은 운영은 모든 명령어를 외우는 데서 나오지 않는다. 장애가 발생했을 때 어느 계층부터 확인해야 하는지 알고, 확인 결과를 다음 판단으로 연결하는 데서 나온다. Linux 명령어는 그 확인 순서를 실행하는 도구이고, TCP/IP와 서브넷은 클라우드 네트워크를 이해하는 언어다. 이 기본기가 있어야 Docker, Kubernetes, Load Balancer, DNS, TLS 문제도 구조적으로 볼 수 있다.

이 글의 무단 복제, 재배포, 자동 수집을 허용하지 않습니다. 인용이 필요한 경우 출처와 원문 링크를 함께 남겨주세요.