13년차의 서버실

서버, 인프라, 홈랩 기술 블로그

[태그:] nsenter

  • [Linux] Linux namespace 활용 사례: 격리 환경 구축과 보안 강화 실전

    [Linux] Linux namespace 활용 사례: 격리 환경 구축과 보안 강화 실전

    Linux namespace 활용 사례: 격리 환경 구축과 보안 강화 실전

    리눅스 서버를 오래 만지다 보면, 운영 호스트는 그대로 둔 채 어떤 프로세스가 실제로 무엇을 건드리는지 빨리 확인해야 하는 순간이 꼭 옵니다. 패키지를 하나 설치했더니 예상 못 한 소켓을 열고, 에이전트를 하나 올렸더니 부팅 직후 파일을 여기저기 만들고, 테스트 스크립트가 외부망으로 나가 버리는 식이죠. 저도 홈랩이든 실무든 비슷했고요. 그때 가장 손에 익혀둘 가치가 있었던 게 Linux namespace 활용이었습니다. 흔히 컨테이너의 재료 정도로만 설명되지만, 직접 unshare, ip netns, nsenter를 만져보면 이건 단순 개념 설명으로 끝낼 기술이 아니더라고요.

    중요한 건 기대치를 정확히 잡는 겁니다. namespace는 마법 상자가 아닙니다. 커널은 공유하고, CPU·메모리 같은 자원 제한도 기본으로 걸어주지 않습니다. 대신 프로세스가 보는 시야를 분리합니다. 이 차이를 이해하면 “언제 namespace를 직접 쓰고, 언제 컨테이너 런타임이나 VM으로 넘어가야 하는지” 판단이 훨씬 빨라집니다. 오늘은 정의 위주보다, Linux namespace 활용이 실제로 어디에 먹히고 어디서 한계가 드러나는지에 초점을 맞춰 보겠습니다.

    Linux namespace 활용에서 격리 범위를 보여주는 아키텍처 다이어그램

    호스트와 각 namespace가 PID, Network, Mount, UTS 관점에서 어떻게 분리되는지 한눈에 보여주는 개요 이미지입니다.

    1. 왜 Linux namespace 활용이 실무에서 바로 먹히는가

    제가 현장에서 namespace를 꺼내는 순간은 대체로 두 가지입니다. 첫째, 운영 호스트에 패키지나 설정 찌꺼기를 남기고 싶지 않을 때입니다. 둘째, 테스트 대상 프로세스가 어디까지 보이고 어디로 나가는지 통제하고 싶을 때죠. 이건 Dockerfile을 만들 정도로 반복적인 작업은 아닌데, 그렇다고 그냥 호스트에서 돌리기엔 찜찜한 상황에서 특히 유용합니다.

    • 서비스 시작 스크립트가 어떤 파일과 소켓을 만드는지 확인할 때
    • 외부 통신을 막은 채 애플리케이션 기동 여부만 검증할 때
    • 방화벽 정책이나 라우팅 변경 전, 별도 네트워크 공간에서 먼저 재현할 때
    • 문제 프로세스를 호스트 전체와 분리해 관찰할 때

    여기서 VM과 컨테이너, namespace를 같은 선상에서만 비교하면 판단이 흐려집니다. 제가 체감한 기준은 이렇습니다. VM은 커널까지 분리하는 대신 무겁고, 컨테이너는 운영 편의성이 좋지만 준비물이 생기고, namespace 직접 사용은 가장 가볍지만 조립 책임이 사용자에게 있습니다. 즉, namespace는 “기능이 약한 컨테이너”라기보다 “필요한 격리 축만 직접 뽑아 쓰는 커널 도구”에 가깝습니다.

    실무 품질을 좌우하는 포인트도 분명합니다. namespace는 CPU나 메모리 폭주를 막아주지 않습니다. 파일 시스템 쓰기 범위도 자동으로 최소화해 주지 않고요. 그래서 저는 보통 이렇게 선을 그어 설명합니다. namespace는 분리, cgroup은 제한, seccomp는 호출 축소, LSM(AppArmor/SELinux)은 정책 강제. 이걸 구분하지 않고 “격리됐다”라고만 말하면 운영 단계에서 사고 납니다.

    2. Linux namespace 활용 전, 무엇이 분리되는지 먼저 보자

    리눅스 네임스페이스는 종류를 외우는 것보다, 각 종류가 장애 분석과 보안에 어떤 영향을 주는지 아는 편이 더 중요합니다. 아래 표는 제가 실제로 판단할 때 쓰는 관점으로 정리한 겁니다.

    Namespace 종류 분리 대상 실무에서 바로 체감되는 효과 자주 놓치는 포인트 주요 도구
    PID 프로세스 ID 공간 테스트 프로세스를 별도 PID 1처럼 실행 가능 /proc를 새로 준비하지 않으면 ps 결과가 헷갈릴 수 있음 unshare --pid --fork, nsenter --pid
    NET 인터페이스, 라우팅, 포트, 방화벽 외부 송신 차단, veth 기반 통신 테스트, 정책 검증 lo를 올리지 않으면 내부 앱이 예상 밖으로 깨질 수 있음 ip netns, ip link, ip route
    MNT 마운트 포인트와 파일시스템 시야 임시 루트, bind mount, 읽기 전용 뷰 구성 전파 속성(shared/private) 이해 없이 건드리면 의도치 않게 호스트에 반영될 수 있음 unshare --mount, mount, pivot_root
    UTS 호스트명, NIS 도메인명 로그와 프롬프트에서 실험 환경 식별이 쉬워짐 보안 경계 자체는 약하고 주로 식별성 개선용 unshare --uts, hostname
    USER UID/GID 매핑과 일부 권한 범위 비특권 사용자 기반 실험에 유리 배포판 정책, 커널 설정, /etc/subuid//etc/subgid 상태에 따라 동작 차이가 큼 unshare --user --map-root-user
    IPC System V IPC, POSIX 메시지 큐 다른 프로세스와 IPC 충돌 방지 눈에 바로 안 보여서 문제 원인을 놓치기 쉬움 unshare --ipc

    저는 처음부터 전부 묶지 않습니다. 대부분 NET + MNT + PID로 시작하고, 로그 식별이 필요하면 UTS, 비특권 실험이 필요하면 USER를 추가합니다. 이유가 있습니다. namespace는 많이 넣을수록 안전해지는 면도 있지만, 동시에 원인 분리가 어려워집니다. 실패했을 때 어느 층에서 막혔는지 빨리 찾는 편이 실무에선 더 중요하거든요.

    3. 실전 시나리오: 격리된 테스트 환경을 직접 만들어보겠습니다

    이번에는 재현 가능한 시나리오로 가보겠습니다. 상황은 이렇다고 치죠. 새 에이전트를 올리기 전에, 이 프로세스가 외부망으로 나가지 못하게 막은 상태에서 정상 기동하는지 확인하고 싶습니다. 또한 호스트의 프로세스 목록과 파일시스템을 그대로 노출하고 싶지 않습니다. 이럴 때 저는 보통 1단계는 네트워크 분리, 2단계는 PID와 마운트 분리 순으로 갑니다.

    1. 호스트와 분리된 네트워크 namespace를 만듭니다.
    2. veth 페어를 붙여 통신 경로를 의도적으로 만듭니다.
    3. namespace 내부에 서비스 하나를 띄워 실제로 어디에 바인딩되는지 확인합니다.
    4. 그다음 PID, UTS, Mount namespace를 붙여 프로세스와 파일시스템 시야까지 좁힙니다.

    이 순서를 추천하는 이유는 단순합니다. 네트워크 단절, 바인딩 실패, /proc 관찰 오류, 마운트 전파 실수는 각기 보는 포인트가 다릅니다. 한 번에 다 엮으면 증상은 하나인데 원인은 넷 다 될 수 있거든요.

    3-1. 네트워크 namespace 생성

    먼저 가장 자주 쓰는 형태부터 보겠습니다. 아래 예시는 호스트 쪽 veth-host와 namespace 내부 veth-lab를 연결하고, 기본 라우트까지 넣는 최소 구성입니다. 외부 인터넷이 꼭 필요 없다면 default route는 일부러 빼도 괜찮습니다.

    sudo ip netns add labns
    sudo ip link add veth-host type veth peer name veth-lab
    sudo ip link set veth-lab netns labns
    
    sudo ip addr add 10.200.1.1/24 dev veth-host
    sudo ip link set veth-host up
    
    sudo ip netns exec labns ip addr add 10.200.1.2/24 dev veth-lab
    sudo ip netns exec labns ip link set lo up
    sudo ip netns exec labns ip link set veth-lab up
    sudo ip netns exec labns ip route add default via 10.200.1.1
    
    sudo ip netns exec labns ip addr
    sudo ip netns exec labns ip route

    이 단계에서 제일 많이 놓치는 건 두 가지입니다. 하나는 lo를 올리지 않는 것, 다른 하나는 “기본 라우트가 있어야만 내부 테스트가 된다”고 오해하는 겁니다. 사실 동일 서브넷 내 호스트와 namespace 간 통신만 볼 거라면 default route가 없어도 됩니다. 반대로 외부로 내보낼 생각이 없다면 일부러 default route를 넣지 않는 편이 더 안전합니다. 이거 진짜 편하더라고요. 시작부터 길을 다 열어 두면, 통신이 되는 이유가 라우팅인지 NAT인지 애플리케이션 재시도 때문인지 해석이 흐려집니다.

    또 하나, ip netns add는 named network namespace를 /var/run/netns/ 관례 아래에 연결해 관리합니다. 배포판에 따라 실제 경로가 /run/netns/로 보일 수도 있는데, 보통 둘은 같은 런타임 경로 계열로 이해하면 됩니다. 그래서 정리 순서도 중요합니다. 네임스페이스 내부 프로세스가 남아 있으면 삭제가 깔끔하게 안 될 수 있습니다.

    sudo ip netns pids labns
    sudo ip netns exec labns pkill -f http.server || true
    sudo ip netns del labns
    sudo ip link del veth-host || true

    여기서 ip netns pids를 먼저 보는 이유가 있습니다. veth 삭제가 실패하거나 namespace 삭제가 남는 경우, 대개 내부에 살아 있는 프로세스가 원인입니다. 명령어는 간단한데 cleanup을 무시하면 실습이 반복될수록 환경이 꼬입니다.

    3-2. namespace 내부에서 서비스 실행

    이제 namespace 내부에 실제 서비스를 올려 보겠습니다. 예시는 단순하지만, 판단 기준은 실무 그대로 가져갈 수 있습니다. 핵심은 “서비스가 떴는가”가 아니라 어디에 바인딩됐고, 어느 네임스페이스 관점에서 보여야 정상인가를 확인하는 겁니다.

    sudo mkdir -p /srv/labns/www
    printf '%s\n' 'hello from lab namespace' | sudo tee /srv/labns/www/index.html > /dev/null
    
    sudo ip netns exec labns bash -lc 'cd /srv/labns/www && python3 -m http.server 8080 --bind 10.200.1.2'
    
    # 다른 터미널에서 확인
    curl http://10.200.1.2:8080/
    sudo ip netns exec labns ss -lntp
    sudo ip netns exec labns curl -I http://10.200.1.2:8080/

    제가 이 단계에서 항상 같이 보는 건 ss -lntp와 바인딩 주소입니다. 127.0.0.1에만 바인딩된 서비스는 namespace 외부에서 접근되지 않아도 정상일 수 있습니다. 반대로 0.0.0.0에 걸렸다면 namespace 내부에선 모든 인터페이스를 받는다는 뜻이라, 추후 포워딩을 붙이면 생각보다 넓게 열릴 수 있습니다. 즉, 서비스 가시성 문제를 네트워크 문제로 오진하지 않는 것이 중요합니다.

    호스트에서 안 열리는데 namespace 내부에서는 잘 뜨는 경우도 자주 봅니다. 이때는 실패가 아니라, 오히려 격리가 의도대로 작동한 신호일 수 있습니다. 확인 순서는 보통 이렇습니다.

    • ip netns exec labns ss -lntp로 프로세스가 어떤 IP와 포트에 bind 했는지 본다
    • curl을 namespace 내부에서 먼저 날린다
    • 그다음 호스트에서 같은 IP로 접근해 경로가 있는지 확인한다
    • 필요하면 NAT나 포워딩을 추가하되, 마지막 단계로 미룬다
    리눅스 네임스페이스 기반 격리 환경 구축 네트워크 구성도

    호스트의 veth와 namespace 내부 인터페이스가 어떻게 연결되고, 테스트 서비스가 어느 IP에 바인딩되는지 설명하는 구성도입니다.

    3-3. 외부 송신까지 통제하려면 어디를 더 봐야 하나

    네트워크 namespace를 만든 뒤 많은 분들이 “이제 외부 인터넷도 자동으로 막히겠지”라고 생각하시는데, 그건 아닙니다. 경로와 NAT를 어떻게 주느냐에 따라 얼마든지 나갈 수 있습니다. 그래서 보안 검증 목적이면 처음에는 default route를 아예 주지 않거나, 주더라도 포워딩과 NAT를 넣지 않은 상태에서 출발하는 편이 낫습니다.

    외부 통신이 필요한 테스트라면 호스트에 포워딩과 NAT를 붙일 수는 있습니다. 다만 이건 편의성보다 해석 난도가 올라갑니다. 정책 검증이 목적이라면 “닫힌 상태에서 하나씩 여는 방식”이 원인 파악에 훨씬 유리합니다. 최소 예시는 아래처럼 구성합니다. 최근 배포판은 nftables를 기본으로 쓰는 경우도 많으니, 환경에 따라 iptables 명령이 호환 계층인지도 같이 확인하는 편이 좋습니다.

    # 호스트에서 IPv4 forwarding 활성화
    sudo sysctl -w net.ipv4.ip_forward=1
    
    # 예시: labns 대역을 외부 인터페이스 eth0로 NAT
    sudo iptables -t nat -A POSTROUTING -s 10.200.1.0/24 -o eth0 -j MASQUERADE
    sudo iptables -A FORWARD -i eth0 -o veth-host -m state --state RELATED,ESTABLISHED -j ACCEPT
    sudo iptables -A FORWARD -i veth-host -o eth0 -j ACCEPT

    다만 저는 일회성 분석 용도라면 여기까지는 잘 안 갑니다. NAT가 들어오면 이제 문제 원인이 애플리케이션인지, 라우팅인지, 포워딩 정책인지, 외부 DNS인지 한 층 더 늘어나기 때문입니다. 단순 기동 확인이라면 닫힌 내부망에서 끝내는 쪽이 훨씬 깔끔했습니다.

    3-4. PID, UTS, Mount까지 붙여 더 독립적인 실행 환경 만들기

    네트워크 분리만으로는 부족할 때가 많습니다. 프로세스 목록을 호스트와 분리하고, hostname을 바꾸고, 마운트 시야까지 따로 가져가면 디버깅 노이즈가 크게 줄어듭니다. 제가 자주 쓰는 형태는 아래와 같습니다.

    sudo unshare --fork --pid --mount --uts --ipc bash -lc '
      mount --make-rprivate /
      mount -t proc proc /proc
      hostname lab-host
      echo "[inside namespace] hostname: $(hostname)"
      echo "[inside namespace] process list:"
      ps -ef
      sleep 300
    '

    여기서 핵심은 두 줄입니다. mount --make-rprivate /와 mount -t proc proc /proc입니다. 첫 번째는 마운트 이벤트 전파를 끊어 의도치 않은 공유를 줄이는 역할을 하고, 두 번째는 현재 PID namespace 관점의 /proc을 다시 보여주기 위한 겁니다. 참고로 최근 unshare는 새 mount namespace에서 기본적으로 마운트를 private 쪽으로 돌리는 동작을 하기도 하지만, 명시적으로 적어두면 실습 문서나 운영 절차에서 덜 헷갈립니다.

    특히 PID namespace는 PID 1의 성격 때문에 생각보다 함정이 있습니다. 격리 환경 안에서 PID 1은 일반 프로세스보다 시그널 처리와 좀비 수거에 민감합니다. 실험처럼 짧게 돌릴 땐 괜찮지만, 조금이라도 오래 유지할 프로세스라면 tini 같은 init 래퍼나 별도 supervisor 역할을 고려하는 편이 안전합니다. 이 부분은 컨테이너에서도 똑같이 반복되죠.

    비특권 실험이 필요하면 USER namespace도 검토할 수 있습니다.

    unshare --user --map-root-user --mount --pid --fork bash -lc '
      id
      mount -t proc proc /proc
      ps -ef
      cat /proc/self/uid_map
      cat /proc/self/gid_map
    '

    다만 이건 “되면 좋고 아니면 말고” 식으로 접근하면 안 됩니다. 실제 서버에서는 /proc/sys/user/max_user_namespaces 값, 배포판 보안 정책, 그리고 일부 배포판에서만 제공되는 /proc/sys/kernel/unprivileged_userns_clone 같은 설정 때문에 막히는 경우가 적지 않습니다. 저는 이 단계에서 시간을 오래 쓰기보다, 운영 정책이 금지한 기능이라면 굳이 namespace 직접 조합에 집착하지 않는다는 쪽입니다. 홈랩과 운영 서버가 다르게 반응하는 대표적인 영역이 바로 여기거든요.

    4. Linux namespace 활용과 보안 강화, 어디까지 믿어도 되나

    Linux namespace 활용의 가장 큰 장점은 사고 범위를 줄이는 데 있습니다. 테스트 프로세스가 호스트 전체 프로세스 목록을 못 보고, 네트워크 인터페이스도 제한되고, 마운트 시야까지 좁아지면 “한 번 잘못 돌린 실험이 운영 전체를 어지럽히는 일”이 크게 줄어듭니다. 실수 방지 효과가 꽤 큽니다. 하지만 여기까지입니다. namespace만으로 완전한 샌드박스가 됐다고 보면 위험합니다.

    이유는 단순합니다. 커널은 여전히 공유합니다. 커널 취약점이 있거나 capability를 넓게 줬거나, 마운트와 디바이스 접근을 느슨하게 두면 격리 강도는 금방 낮아집니다. 제가 실무에서 namespace를 단독 보안 장치로 보지 않는 이유도 여기에 있습니다. 보통 아래 조합으로 봅니다.

    • namespace: 프로세스가 보는 세계 분리
    • cgroup: CPU, 메모리, pids 폭주 제한
    • seccomp: 허용 syscall 범위 축소
    • AppArmor 또는 SELinux: 파일·소켓·능력 사용 제약
    • capability drop: 필요 없는 권한 제거

    실무 판단을 더 직접적으로 말하면 이렇습니다. namespace는 “격리의 뼈대”이고, seccomp와 capability 조정이 “탈출 비용을 올리는 장치”이며, cgroup은 “망가질 때 피해 규모를 제한하는 장치”입니다. 하나만 넣고 안심하는 구조가 제일 위험했습니다.

    systemd를 쓰는 환경이라면, 별도 컨테이너 런타임 없이도 서비스 단에서 격리 옵션을 꽤 보강할 수 있습니다. 예를 들면 아래 같은 방향입니다.

    [Service]
    ExecStart=/usr/local/bin/my-agent
    NoNewPrivileges=yes
    PrivateTmp=yes
    ProtectSystem=strict
    ProtectHome=yes
    PrivateDevices=yes
    RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
    CapabilityBoundingSet=
    SystemCallFilter=@system-service

    이건 namespace의 대체재라기보다 보완재에 가깝습니다. 특히 “운영 서비스인데 컨테이너까지는 안 가고 싶다”는 구간에서 생각보다 강력합니다. 저도 네트워크 namespace로 송신 범위를 줄이고, systemd 옵션으로 파일시스템과 권한을 더 잠그는 조합을 자주 씁니다. 관련 옵션을 더 파고들고 싶다면 systemd 보안 설정 정리 글도 함께 보시면 흐름이 더 잘 잡힙니다.

    5. 트러블슈팅: 제가 실제로 자주 겪었던 문제들

    명령어보다 중요한 게 해석 기준입니다. namespace는 증상이 비슷해도 원인이 전혀 다른 경우가 많습니다. 아래는 제가 가장 자주 봤던 실패 패턴과, 그때 실제로 다시 확인하는 순서입니다.

    5-1. ping은 되는데 애플리케이션 통신은 안 되는 경우

    이건 라우팅보다 바인딩 주소 문제인 경우가 많았습니다. 127.0.0.1에만 바인딩된 서비스는 namespace 내부에서는 잘 떠 보이는데, 호스트나 다른 namespace에서는 닿지 않습니다. 반대로 0.0.0.0에 걸렸는데도 안 붙는다면 방화벽이나 경로를 보게 되죠. 저는 이럴 때 순서를 고정해 둡니다.

    • ss -lntp로 listening 주소를 먼저 본다
    • curl을 서비스가 실행 중인 namespace 내부에서 먼저 친다
    • 설정 파일의 listen, bind, host 값을 본다
    • 그다음에야 라우팅과 필터링 규칙을 본다

    이 순서를 추천하는 이유는, 네트워크 문제처럼 보이지만 실제론 프로세스 설정 문제인 경우가 정말 많기 때문입니다. bind 주소 확인 없이 iptables부터 보는 건 대개 시간 낭비였습니다.

    5-2. PID namespace에서 ps 결과가 이상한 경우

    거의 항상 /proc 문제였습니다. 새 PID namespace에 들어갔는데 현재 namespace에 맞는 proc 파일시스템을 보지 않으면, 프로세스 목록이 호스트 기준처럼 보여 혼란이 생깁니다.

    mount -t proc proc /proc
    ps -ef
    cat /proc/1/status
    readlink /proc/self/ns/pid

    /proc/1/status를 보면 현재 namespace 관점의 PID 1이 누구인지 알 수 있고, readlink /proc/self/ns/pid는 실제로 다른 namespace inode를 보고 있는지 확인하는 데 도움이 됩니다. 여기서 “ps가 이상하다”는 증상 자체가 문제의 본질이 아니라 관찰 대상이 잘못됐다는 신호인 경우가 많습니다.

    5-3. 사용자 namespace가 서버마다 다르게 동작하는 경우

    이건 커널 기능 차이보다 운영 정책 차이가 더 크게 체감됐습니다. 특히 비특권 사용자로 unshare --user --map-root-user를 시도할 때 서버마다 결과가 달라집니다.

    cat /proc/sys/user/max_user_namespaces
    [ -f /proc/sys/kernel/unprivileged_userns_clone ] && cat /proc/sys/kernel/unprivileged_userns_clone
    id
    uname -r
    • max_user_namespaces가 낮거나 0이면 생성이 막힐 수 있습니다.
    • unprivileged_userns_clone는 일부 배포판에서만 노출되는 설정이라, 파일이 없다고 이상한 건 아닙니다.
    • 보안 기준이 엄격한 서버는 정책상 의도적으로 비활성화한 경우가 많습니다.

    이럴 때 억지로 우회하는 건 보통 좋은 설계가 아닙니다. 제가 권하는 건 두 가지 중 하나입니다. 운영에서 반드시 필요하면 관리자 정책으로 정식 허용을 받거나, 아니면 컨테이너 런타임이나 별도 테스트 노드로 경로를 바꾸는 것. 운영 정책과 싸우는 형태는 오래 못 갑니다.

    5-4. 마운트는 분리한 줄 알았는데 호스트에 영향이 남는 경우

    이건 mount namespace를 처음 만질 때 특히 많이 겪습니다. 원인은 대개 마운트 전파 속성입니다. namespace를 나눴다고 해서 모든 마운트 이벤트가 자동으로 고립되는 건 아닙니다. 그래서 저는 mount 작업 전에 mount --make-rprivate /를 거의 습관처럼 넣습니다. 이 한 줄을 빼먹으면 bind mount나 임시 마운트가 예상 밖으로 공유돼 버릴 수 있습니다.

    증상은 보통 이렇습니다. 실험 환경 안에서만 붙인 줄 알았던 마운트가 호스트에서도 보이거나, 반대로 호스트 변경이 내부에 그대로 따라 들어옵니다. 이 경우에는 namespace 개념 자체보다 전파 모델을 다시 보는 편이 빠릅니다. 실무에서 mount namespace가 네트워크 namespace보다 까다롭게 느껴지는 이유가 여기 있습니다.

    Linux namespace 활용 점검과 트러블슈팅을 표현한 터미널 스타일 이미지

    ip netns exec, ss -lntp, mount -t proc proc /proc 같은 핵심 점검 포인트를 한 장으로 정리한 이미지입니다.

    6. 검증과 결과 확인: 무엇을 보고 성공이라 판단하나

    명령이 에러 없이 끝났다고 성공은 아닙니다. namespace는 “만들어졌다”와 “의도대로 격리됐다” 사이에 차이가 큽니다. 저는 아래 순서대로 확인합니다.

    1. 인터페이스가 의도한 namespace에 들어갔는지 본다
    2. 프로세스가 내부에서 어떤 PID와 hostname으로 보이는지 본다
    3. 서비스 포트가 기대한 주소에 bind 되었는지 확인한다
    4. 호스트와 namespace 사이 통신 범위를 점검한다
    5. 원치 않은 외부 송신 경로가 없는지 마지막에 확인한다
    ip netns list
    sudo ip netns exec labns ip addr
    sudo ip netns exec labns ip route
    sudo ip netns exec labns ps -ef
    sudo ip netns exec labns ss -lntp
    sudo ip netns exec labns curl -I http://10.200.1.2:8080/
    
    # 호스트에서 접근 확인
    curl -I http://10.200.1.2:8080/
    
    # namespace 내부에서 호스트 방향 확인
    sudo ip netns exec labns ping -c 2 10.200.1.1
    
    # 실제 네임스페이스 분리 여부 확인
    sudo ip netns exec labns readlink /proc/self/ns/net
    readlink /proc/self/ns/net

    결과 해석 기준도 미리 정해 두면 좋습니다.

    • ip netns list에 보이면 생성은 된 겁니다. 하지만 그걸로 끝은 아닙니다.
    • ip addr에 기대한 IP가 없으면 링크 이동이나 주소 할당 단계부터 다시 봐야 합니다.
    • ss -lntp에 프로세스가 없으면 서비스 시작 실패입니다. 이때는 로그보다 실행 명령과 bind 주소를 먼저 봅니다.
    • 호스트에서는 안 보이는데 내부에서는 보이면, 실패가 아니라 격리 성공일 수 있습니다.
    • 외부 통신이 되면 안 되는 시나리오인데 HTTP나 DNS가 나가면 default route, 포워딩, NAT부터 다시 봐야 합니다.

    제가 특히 강조하는 건 마지막 항목입니다. 격리 환경 검증에서 자주 빠지는 게 “열려 있지 않아야 할 경로 확인”입니다. 되는 것만 보면 절반만 본 겁니다. 막혀야 할 것이 실제로 막히는지 확인해야 보안 관점의 검증이 됩니다.

    7. 운영에 붙일 때의 현실적인 추천 조합

    실제로 써보면 모든 namespace를 한 번에 다 쓰는 방식은 종종 과합니다. 목적에 따라 조합을 나누는 편이 유지보수성이 더 좋았습니다. 아래 표는 제가 선택할 때 쓰는 기준입니다.

    상황 추천 조합 이유 굳이 피할 상황
    간단한 네트워크 테스트 NET 설정이 단순하고 효과가 바로 보입니다. 장기 운영 서비스처럼 재현성과 배포 표준화가 중요한 경우
    프로세스 독립 실행 검증 PID + UTS + MNT 프로세스, hostname, 파일시스템 시야를 함께 분리하기 좋습니다. 마운트 전파를 이해하지 못한 상태에서 급히 운영에 넣는 경우
    보안 민감한 임시 실행 USER + NET + MNT + cgroup + seccomp 권한, 시야, 자원, syscall 범위를 함께 줄일 수 있습니다. USER namespace 정책이 금지된 운영 서버
    반복 배포되는 서비스 직접 namespace보다 컨테이너 런타임 검토 이미지 관리, 재시작 정책, 로깅, 표준화 측면에서 유리합니다. 일회성 포렌식 재현이나 빠른 실험처럼 준비 비용이 아까운 경우

    여기서 제 추천은 꽤 명확합니다. 한 번성 검증이면 직접 namespace, 반복 운영이면 컨테이너 런타임이 대체로 맞습니다. 전자는 스패너처럼 빠르게 꺼내 쓰는 도구이고, 후자는 운영 체계에 가깝습니다. 둘 중 무엇이 더 우월하냐의 문제가 아니라, 조립 책임을 누가 지느냐의 차이로 보는 편이 현실적입니다.

    비용 관점에서도 비슷합니다. namespace 직접 조합은 초기 준비가 가볍지만 사람이 더 똑똑해야 합니다. 반면 컨테이너 런타임은 준비물이 늘어나지만 반복성이 좋아집니다. 그래서 팀 단위로 넘어가면 후자가 이기고, 개인 디버깅이나 빠른 재현 실험에선 전자가 여전히 강합니다. 관련해서 컨테이너와 VM 비교 글까지 같이 연결하면 내부 링크 흐름도 자연스럽게 만들 수 있습니다.

    Linux namespace 활용과 컨테이너 선택 기준을 비교한 인포그래픽

    일회성 테스트, 반복 운영, 보안 요구사항에 따라 어떤 선택이 적합한지 비교한 요약 이미지입니다.

    8. 자주 받는 질문과 마지막 권고

    Q1. namespace만 쓰면 컨테이너를 대체할 수 있나요?

    부분적으로는 가능합니다. 특히 빠른 재현, 네트워크 격리 실험, 프로세스 시야 분리 정도는 namespace만으로도 충분히 됩니다. 다만 이미지 관리, 배포 파이프라인, 재시작 정책, 로그 수집 표준화까지 생각하면 컨테이너 런타임이 훨씬 편한 구간이 분명히 있습니다. 저는 보통 “문제 재현과 분석 단계는 namespace”, “반복 서비스화 단계는 컨테이너”로 나눠 봅니다.

    Q2. 보안 강화 목적이라면 무엇부터 붙여야 하나요?

    제가 권하는 시작점은 NET namespace로 통신 범위를 먼저 줄이고, 그다음 MNT와 PID를 분리하는 방식입니다. 여기에 capability 최소화, seccomp, cgroup 제한을 얹는 쪽이 사고를 줄이기 좋았습니다. 처음부터 다 넣으면 강해 보이긴 하지만, 실패 시 원인 분리가 너무 어려워집니다.

    Q3. 홈랩에서도 해볼 가치가 있나요?

    충분합니다. 오히려 홈랩이 제일 좋습니다. 실패해도 운영 영향이 없고, namespace 특유의 함정인 bind 주소, loopback, /proc 준비, mount propagation을 몸으로 익히기 좋거든요. 저도 홈랩에서 먼저 손에 익힌 뒤에야 운영 서버에서 판단 속도가 빨라졌습니다.

    마지막으로 추천을 딱 잘라 말씀드리면 이렇습니다. 단발성 테스트, 포렌식성 재현, 서비스 기동 검증이 목적이면 Linux namespace 활용을 바로 써보는 편이 맞습니다. 반대로 팀 단위 반복 운영, 배포 자동화, 표준 이미지 관리가 목적이면 직접 namespace를 조립하기보다 컨테이너 런타임으로 넘어가는 쪽이 낫습니다. 보안 관점에서도 마찬가지입니다. namespace는 아주 좋은 출발점이지만 종착점은 아닙니다. 네트워크 분리만 믿지 말고, 권한과 syscall, 자원 제한까지 묶어야 실전에서 덜 흔들립니다.