13년차의 서버실

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

[카테고리:] linux

  • [Linux] iptables에서 nftables로 전환: 레거시 방화벽 마이그레이션 가이드

    [Linux] iptables에서 nftables로 전환: 레거시 방화벽 마이그레이션 가이드

    [Linux] iptables에서 nftables로 전환: 레거시 방화벽 마이그레이션 가이드

    리눅스 서버를 오래 운영하다 보면 방화벽 규칙이 점점 덕지덕지 붙는 순간이 오더라고요. 저도 홈랩과 실서버를 같이 굴리면서 iptables nftables 전환 작업을 몇 번 했었는데, 처음엔 “굳이 잘 돌아가는 걸 왜 바꿔?” 싶었어요. 근데 규칙이 늘어나고, IPv4와 IPv6를 따로 관리하고, 서비스별 예외가 계속 생기기 시작하면 이야기가 확 달라져요. 그때부터는 레거시 방화벽 구조가 발목을 잡기 시작하거든요. 이번 글에서는 iptables에서 nftables로 전환할 때 어떤 흐름으로 접근하면 덜 고생하는지, 제가 직접 해보며 삽질했던 포인트까지 묶어서 정리해보겠습니다.

    특히 이 글은 새로운 방화벽을 처음 설계하는 분보다는, 이미 운영 중인 규칙을 가진 상태에서 nftables 마이그레이션을 고민하는 분들께 맞춰 썼습니다. “규칙은 많은데 서비스 중단은 싫다”, “iptables-save 출력은 복잡한데 어떻게 옮겨야 할지 모르겠다” 같은 상황이 딱 여기에 해당합니다.

    iptables nftables 전환 아키텍처를 보여주는 리눅스 방화벽 마이그레이션 이미지

    레거시 체인과 테이블이 nftables의 통합 규칙셋으로 재구성되는 흐름을 보여주는 개요 이미지입니다.

    왜 지금 iptables에서 nftables로 전환해야 할까요

    쉽게 말해, nftables는 Linux 커널의 Netfilter(넷필터, 리눅스 패킷 필터링 프레임워크) 위에서 동작하는 더 현대적인 규칙 관리 방식이에요. 예전에는 iptables, ip6tables, arptables, ebtables처럼 도구가 나뉘어 있었는데, nftables 쪽은 이걸 훨씬 일관되게 다룰 수 있게 정리해둔 느낌이 강합니다.

    제가 직접 운영하면서 체감한 장점은 크게 세 가지였어요. 첫째, 규칙 표현이 훨씬 읽기 좋습니다. 둘째, IPv4와 IPv6를 한 구조 안에서 관리하기가 정말 편해요. 셋째, 집합(Set, 셋)과 맵(Map, 매핑) 같은 기능을 활용하면 반복 규칙이 크게 줄어듭니다. 예전엔 포트 하나 열 때마다 규칙을 늘어놓았는데, nftables에서는 묶어서 다루니까 관리가 훨씬 편하더라고요.

    항목 iptables nftables
    규칙 구조 테이블/체인/룰이 분산되어 장황해지기 쉬움 문법이 비교적 일관적이고 집합 활용이 쉬움
    IPv4/IPv6 관리 보통 분리 관리 inet 패밀리로 통합 관리 가능
    변환 도구 기존 자산 많음 iptables-translate 같은 변환 도구 활용 가능
    장기 운영성 레거시 환경과의 호환성은 좋음 새 규칙 설계와 정리에 유리

    여기서 중요한 포인트가 하나 있어요. iptables를 무조건 즉시 버리라는 뜻은 아닙니다. 실제 운영에서는 배포판 기본값, 커널 버전, 시스템 서비스와의 연동 방식까지 같이 봐야 하거든요. 다만 새로 정리할 기회가 있다면 nftables 쪽이 구조적으로 훨씬 낫다는 건 분명해요.

    iptables에서 nftables로 전환 전에 꼭 확인할 체크리스트

    저도 처음엔 규칙부터 바꾸려고 달려들었는데, 나중에 보니 그게 제일 위험한 접근이었어요. 리눅스 방화벽 교체 전에 아래 항목부터 확인하는 게 훨씬 안전합니다.

    1. 현재 규칙 백업
      현재 적용된 IPv4/IPv6 규칙을 반드시 저장합니다.
    2. 원격 접속 경로 확인
      SSH 관리 포트가 어디서 열려 있는지 먼저 확인합니다. 이거 놓치면 진짜 난감해집니다.
    3. 배포판의 기본 방화벽 스택 확인
      일부 환경은 iptables 명령이 내부적으로 nft 백엔드를 쓰기도 해요. 헷갈리기 쉬운 부분이죠.
    4. 자동 시작 서비스 확인
      재부팅 후 `nftables.service` 또는 배포판별 방화벽 서비스가 어떤 규칙을 로드하는지 봐야 합니다.
    5. 롤백 경로 준비
      문제가 생기면 바로 이전 규칙으로 되돌릴 방법을 준비해둡니다.

    예를 들어 현재 규칙 백업은 이렇게 해두면 돼요.

    sudo iptables-save > /root/iptables-backup.rules
    sudo ip6tables-save > /root/ip6tables-backup.rules

    그리고 nftables 쪽 현재 상태도 함께 확인해보세요.

    sudo nft list ruleset

    출력이 비어 있더라도 괜찮습니다. 중요한 건 지금 시스템이 무엇을 기준으로 패킷을 처리하는지 감을 잡는 거예요.

    nftables 개념, 쉽게 말해 이렇게 이해하면 됩니다

    저도 처음엔 `table`, `chain`, `hook` 같은 용어가 한꺼번에 나와서 좀 헷갈렸는데요. 쉽게 말해 이렇습니다.

    • Table(테이블): 규칙을 묶는 큰 서랍이에요.
    • Chain(체인): 실제 패킷이 지나가며 검사되는 규칙 목록입니다.
    • Hook(훅): 커널 네트워크 경로의 어느 지점에서 체인을 실행할지 정하는 연결점입니다.
    • Set(셋): IP, 포트 같은 값을 묶어 재사용하는 구조예요.

    iptables에서는 INPUT, OUTPUT, FORWARD 체인 중심으로 익숙해져 있다 보니 nftables 문법이 낯설게 느껴지는데, 구조를 이해하고 나면 오히려 더 명확합니다. 특히 `inet` 패밀리를 쓰면 IPv4와 IPv6 규칙을 함께 다룰 수 있어서, 실무에서 규칙 중복이 많이 줄어들어요.

    레거시 규칙을 그대로 옮기기보다 재설계가 필요한 이유

    이 부분이 꽤 중요합니다. iptables-translate는 분명 유용한 도구입니다. 하지만 “기계적으로 변환된 규칙 = 가장 좋은 nftables 규칙”은 아니더라고요. 번역은 되는데, 구조가 지저분하게 남는 경우가 많아요. 그래서 저는 보통 이렇게 접근합니다. 먼저 변환 도구로 초안을 만들고, 그다음 사람이 읽기 좋은 구조로 다시 정리합니다. 처음엔 귀찮아 보여도 장기적으로 훨씬 이득이에요.

    실전: iptables 규칙을 nftables로 마이그레이션하는 순서

    이제 본격적으로 옮겨보겠습니다. 여기서는 가장 흔한 서버 기준으로 설명할게요. SSH는 열어두고, 이미 확립된 연결은 유지하고, 기본 정책은 보수적으로 가져가는 흐름입니다.

    1. 현재 iptables 규칙 확인

    sudo iptables -S
    sudo ip6tables -S

    규칙을 읽어보면서 실제로 필요한 것과 과거 흔적을 구분하는 게 먼저예요. 저 같은 경우 예전에 테스트하다 남긴 포트 허용 규칙이 생각보다 많았습니다. 이런 건 옮기기 전에 정리하는 게 맞아요.

    2. 변환 초안 생성

    단일 규칙 한 줄은 `iptables-translate`로, 전체 저장본은 `iptables-restore-translate`로 보는 식으로 접근하면 편합니다.

    sudo iptables-translate -A INPUT -p tcp --dport 22 -j ACCEPT
    sudo iptables-save | sudo iptables-restore-translate

    출력 결과를 바로 적용하기보다, 먼저 파일로 저장해서 검토하는 쪽을 권장해요.

    sudo sh -c 'iptables-save | iptables-restore-translate > /root/translated.nft'

    여기서 중요한 포인트! 변환 결과를 그대로 믿지 마시고, 불필요하게 중복된 규칙이나 체인 구조를 꼭 점검하세요.

    iptables nftables 전환 과정에서 iptables-translate를 사용하는 터미널 이미지

    기존 iptables 규칙을 읽고 nftables 초안으로 변환하는 과정의 핵심 명령 흐름을 보여주는 이미지입니다.

    3. 사람이 읽기 좋은 nftables 규칙으로 정리

    제가 실제로는 아래처럼 새 파일을 다시 구성하는 편입니다. 예시는 가장 기본적인 서버용 필터 규칙셋입니다.

    table inet filter {
        chain input {
            type filter hook input priority 0;
            policy drop;
    
            iif lo accept
            ct state established,related accept
            ct state invalid drop
    
            tcp dport 22 accept
            tcp dport 80 accept
            tcp dport 443 accept
    
            ip protocol icmp accept
            ip6 nexthdr ipv6-icmp accept
        }
    
        chain forward {
            type filter hook forward priority 0;
            policy drop;
        }
    
        chain output {
            type filter hook output priority 0;
            policy accept;
        }
    }

    이 규칙은 설명하기도 좋고, 나중에 봐도 의도가 비교적 분명합니다. `ct state established,related accept` 같은 부분은 기존 연결을 유지하는 데 아주 중요해서 빠뜨리면 안 돼요. 저도 초반에 이걸 빼먹고 “왜 응답 패킷이 이상하지?” 하며 한참 들여다봤었습니다.

    4. Set으로 반복 규칙 줄이기

    nftables의 진짜 장점은 이런 반복 제거에서 드러나요.

    table inet filter {
        set allowed_tcp_ports {
            type inet_service
            elements = { 22, 80, 443 }
        }
    
        chain input {
            type filter hook input priority 0;
            policy drop;
    
            iif lo accept
            ct state established,related accept
            tcp dport @allowed_tcp_ports accept
        }
    }

    포트가 많아질수록 이 방식이 진짜 편하더라고요. 나중에 하나 추가하거나 제거할 때도 눈에 잘 들어옵니다.

    5. 문법 검증 후 적용

    실제 적용 전에는 반드시 문법 검사를 먼저 해요.

    sudo nft -c -f /etc/nftables.conf

    이상 없으면 적용합니다.

    sudo nft -f /etc/nftables.conf

    그리고 자동 시작도 확인해둬요.

    sudo systemctl enable nftables
    sudo systemctl restart nftables

    배포판에 따라 서비스 이름이나 기본 설정 경로가 조금 다를 수 있으니, 이 부분은 현재 환경 기준으로 꼭 다시 확인하셔야 해요.

    ⚠️ 트러블슈팅: 제가 실제로 겪었던 문제들

    여기서부터가 실전입니다. 문서만 보면 금방 끝날 것 같지만, 실제로는 작은 차이 때문에 시간이 꽤 들어가요. 저도 삽질 좀 했습니다 ㅎㅎ

    SSH 접속이 갑자기 끊길 뻔한 경우

    가장 흔한 문제입니다. 기본 정책을 `drop`으로 바꿨는데 SSH 허용 규칙 순서가 뒤에 있거나, 아예 빠져 있으면 바로 위험해져요. 그래서 저는 항상 아래 순서를 지킵니다.

    1. 현재 접속 세션을 유지하는 `established,related` 규칙 먼저 추가
    2. SSH 허용 규칙 추가
    3. 그다음 기본 정책 적용

    이 순서를 지키면 사고 확률이 많이 줄어들어요.

    iptables와 nftables가 동시에 있는 것처럼 보이는 경우

    이건 처음 보면 꽤 혼란스러워요. 배포판에 따라 `iptables` 명령이 내부적으로 nft 기반 호환 레이어를 쓰는 경우가 있거든요. 그래서 “분명 nft로 바꿨는데 iptables 명령도 뭔가 보인다?” 같은 상황이 생깁니다. 이럴 땐 감으로 보지 말고, 실제 활성 규칙셋이 무엇인지 `nft list ruleset` 기준으로 확인하는 습관이 필요해요.

    변환은 됐는데 규칙이 너무 지저분한 경우

    이건 거의 반드시 겪어요. `iptables-translate`는 시작점으로는 훌륭하지만, 최종 결과물로 보기엔 장황한 경우가 많습니다. 여기서 시간을 조금 더 써서 `set`, `inet`, 상태 기반 매칭 중심으로 재구성하면 나중에 유지보수가 훨씬 쉬워져요. 제가 직접 써보니 이 단계가 가장 큰 차이를 만들었습니다.

    nftables 마이그레이션 시 set 기반 규칙 구성을 설명하는 리눅스 방화벽 이미지

    반복되는 포트 허용 규칙을 set으로 정리해 유지보수성을 높이는 구성을 시각화한 이미지입니다.

    검증: nftables 마이그레이션 후 무엇을 확인해야 할까요

    규칙 적용이 끝났다고 바로 마무리하면 안 돼요. 실제로 원하는 동작이 나오는지 검증이 꼭 필요합니다.

    기본 검증 명령

    sudo nft list ruleset
    sudo ss -tulpn
    sudo journalctl -u nftables --no-pager

    첫 번째는 현재 규칙셋 확인, 두 번째는 실제 리슨(Listen, 수신 대기) 포트 확인, 세 번째는 서비스 적용 로그 확인입니다. 저는 여기에 외부 호스트에서 실제 접속 테스트도 꼭 붙입니다. 서버 안에서만 보면 놓치는 게 있거든요.

    체크해야 할 항목

    • SSH, 웹, 모니터링 포트가 의도대로 열려 있는지
    • 허용하지 않은 포트가 막혀 있는지
    • 재부팅 후에도 동일한 규칙이 유지되는지
    • IPv6 트래픽도 의도대로 동작하는지

    특히 재부팅 검증은 꼭 해보세요. 적용은 됐는데 부팅 후 원래 규칙이 다시 올라오거나, 반대로 아무 규칙도 안 올라오는 경우가 있거든요. 저도 예전에 이걸 놓쳐서 “어제 분명 됐는데 왜 오늘 다르지?” 하며 로그를 뒤졌던 적이 있습니다.

    iptables nftables 전환 후 규칙 검증과 포트 확인을 보여주는 운영 점검 이미지

    적용된 규칙셋과 실제 서비스 포트 상태를 함께 확인하는 검증 단계의 결과 이미지입니다.

    iptables와 nftables, 어떤 방식으로 운영 정리하는 게 좋을까요

    운영 기준으로 보면 저는 이렇게 정리해요. 기존 시스템이 매우 안정적으로 돌고 있고 외부 의존성이 강하면, 한 번에 크게 바꾸기보다 단계적으로 옮기는 게 맞습니다. 반대로 규칙이 이미 복잡하고, 중복이 많고, 앞으로도 계속 확장될 예정이라면 nftables로 정리할 가치가 충분해요.

    운영 상황 권장 접근
    단순한 단일 서비스 서버 기본 규칙을 nftables로 재작성 후 빠르게 전환
    규칙이 많은 오래된 서버 iptables 백업 후 변환 초안 생성, 단계적 검증
    IPv4/IPv6 동시 운영 inet 테이블 중심으로 통합 설계
    반복 포트/주소 규칙이 많음 set과 map 활용으로 구조 단순화

    혹시 이런 경험 있으신가요? 규칙은 분명 맞는 것 같은데, 몇 달 뒤 다시 보면 왜 이렇게 짰는지 본인도 기억이 안 나는 경우요. 사실 리눅스 방화벽은 “작동만 하면 된다”가 아니라, 나중에 다시 읽어도 이해되는 구조가 정말 중요합니다. nftables는 바로 그 지점에서 큰 장점이 있어요.

    정리: 리눅스 방화벽 교체는 번역보다 재설계가 핵심입니다

    이번 iptables nftables 전환의 핵심은 단순 치환이 아닙니다. 리눅스 방화벽 교체를 한다고 생각하면, 기존 규칙을 점검하고, 필요한 것만 남기고, nftables 문법과 구조에 맞게 다시 정리하는 과정이 훨씬 중요합니다. `iptables-translate`는 좋은 출발점이지만, 최종 목적지는 결국 사람이 관리하기 좋은 규칙셋이어야 해요.

    제가 직접 해보니 가장 중요한 건 세 가지였습니다. 백업, 순서, 검증입니다. 백업 없이 시작하지 말 것. SSH 같은 필수 허용 규칙을 먼저 둘 것. 적용 후에는 반드시 외부에서 검증할 것. 이 세 가지만 지켜도 큰 사고를 많이 줄일 수 있어요.

    다음 글에서는 `nftables`에서 NAT(Network Address Translation, 네트워크 주소 변환) 규칙과 포트 포워딩을 정리하는 방법도 다뤄볼까 합니다. 홈랩 돌리시는 분들은 그쪽도 꽤 자주 쓰시거든요. 이전 글에서 다룬 리눅스 네트워크 기본 흐름과 함께 보시면 훨씬 이해가 잘 되실 겁니다.

    iptables nftables 전환 핵심 체크리스트를 요약한 인프라 운영 인포그래픽

    전환 이유, 절차, 검증 포인트를 한 번에 복습할 수 있도록 요약한 마무리 인포그래픽입니다.

    자주 묻는 질문

    iptables 규칙을 그대로 자동 변환해서 써도 될까요?

    가능은 하지만 권장하지는 않아요. 초안 생성에는 좋지만, 장기 운영을 생각하면 사람이 읽기 좋은 형태로 정리하는 게 훨씬 낫습니다.

    nftables 마이그레이션 시 가장 위험한 실수는 뭔가요?

    원격 접속 허용 규칙보다 먼저 기본 차단 정책을 적용하는 거예요. SSH 세션이 끊기면 복구가 번거로워집니다.

    IPv6를 안 쓰는 것 같으면 무시해도 될까요?

    환경에 따라 다르지만, 실제로는 IPv6가 살아 있는 경우가 적지 않아요. 인터페이스와 서비스 노출 상태를 먼저 확인해보는 게 안전합니다.

    netfilter를 꼭 깊게 알아야 하나요?

    커널 내부까지 깊게 파고들 필요는 없지만, 패킷이 어느 훅(hook)을 지나고 어느 체인에서 처리되는지 정도는 이해해두면 문제 해결이 훨씬 빨라져요.

  • [Linux] 컨테이너 없이 리눅스 네임스페이스로 프로세스 격리하기: 실제 사례 연구

    [Linux] 컨테이너 없이 리눅스 네임스페이스로 프로세스 격리하기: 실제 사례 연구

    [리눅스] 컨테이너 없이 네임스페이스로 프로세스 격리하기

    리눅스 네임스페이스 사례를 찾다 보면 대부분 Docker나 Kubernetes 이야기로 바로 넘어가더라고요. 근데 현장에서는 꼭 컨테이너 런타임(container runtime)이 있어야만 프로세스 격리를 할 수 있는 건 아닙니다. 저도 홈랩에서 작은 배치 작업, 외부에서 받은 스크립트 검증, 네트워크가 민감한 유지보수 작업을 분리할 때 처음부터 컨테이너를 올리기엔 오버헤드가 애매한 경우가 있었거든요.

    그래서 이번 글에서는 컨테이너 원리의 핵심인 Namespace(네임스페이스, 커널 자원 뷰 분리)를 직접 써서 프로세스를 격리한 사례를 정리해보겠습니다. 제가 직접 해보니 처음엔 이게 뭔가 싶었는데, 구조를 한 번 이해하고 나니까 특정 작업을 빠르게 샌드박스(sandbox, 제한된 실행 공간)로 감싸는 데 꽤 유용했습니다. 특히 보안 강화와 시스템 설계 관점에서 어디까지 가능한지 감이 잡히더라고요.

    리눅스 네임스페이스 사례를 설명하는 프로세스 격리 아키텍처 개요 이미지

    호스트와 격리된 프로세스가 PID, 네트워크, 마운트 관점에서 어떻게 분리되는지 보여주는 개요 이미지입니다.

    1. 왜 컨테이너 없이 격리를 보려고 했는가

    실무에서 늘 Docker가 정답은 아니었습니다. 예를 들어 이런 경우가 있었습니다.

    • 운영 서버에 컨테이너 엔진 설치를 최소화해야 하는 경우
    • 일회성 스크립트를 네트워크에서 분리해 실행하고 싶은 경우
    • 빌드 작업이나 백업 검증 작업을 호스트와 다른 PID 뷰로 띄우고 싶은 경우
    • 문제 재현용 프로세스를 빠르게 분리해 테스트하고 싶은 경우

    저는 홈랩에서 외부 저장소에서 받은 백업 복구 스크립트를 바로 호스트에서 돌리기 좀 꺼려졌습니다. 사실 스크립트 한 줄 잘못되면 마운트 포인트를 건드리거나, 예상 못 한 네트워크 접근을 할 수도 있거든요. 그때 느낀 게, “아 컨테이너 이미지를 만들기 전에 커널 기능만으로도 1차 격리는 가능하겠구나”였습니다.

    2. 네임스페이스 개념 설명: 쉽게 말해 무엇이 분리되나

    쉽게 말해 Namespace(네임스페이스)는 프로세스가 보는 세상 자체를 따로 만드는 기능입니다. 같은 리눅스 커널 위에서 돌지만, 프로세스 입장에서는 자기만의 PID 목록, 네트워크 인터페이스, 호스트 이름, 마운트 뷰를 보게 되는 거죠.

    컨테이너 원리를 아주 거칠게 줄이면 이겁니다. Namespace로 보이는 범위를 나누고, Cgroup(컨트롤 그룹, 자원 사용 제한)으로 CPU/메모리를 제한하고, 파일시스템 레이어를 얹는다. 오늘은 그중 Namespace 쪽에 집중해보겠습니다.

    자주 쓰는 네임스페이스 종류

    • PID Namespace: 프로세스 ID 공간 분리
    • Mount Namespace: 마운트 포인트 뷰 분리
    • Network Namespace: 네트워크 인터페이스, 라우팅 테이블 분리
    • UTS Namespace: hostname, domainname 분리
    • IPC Namespace: 프로세스 간 통신 자원 분리
    • User Namespace: 사용자/권한 매핑 분리

    여기서 중요한 포인트! 네임스페이스만 쓴다고 완전한 보안 샌드박스가 되는 건 아닙니다. 그래도 적어도 “호스트와 같은 뷰를 그대로 공유하지 않게 한다”는 점에서 상당히 큰 차이가 납니다.

    기술별 차이 한눈에 보기

    기술 격리 범위 장점 한계
    chroot(루트 변경) 파일 경로 중심 단순함 보안 격리 수단으로는 약함
    Namespace(네임스페이스) PID, 네트워크, 마운트 등 커널 자원 뷰 가볍고 빠름 자원 제한은 별도 구성 필요
    Container(컨테이너) Namespace + Cgroup + 이미지/런타임 운영 자동화에 유리 런타임 의존성과 운영 복잡도 증가

    3. 실제 사례 연구: 백업 검증 스크립트를 안전하게 분리하기

    제가 실험한 시나리오는 이렇습니다. 백업 파일 무결성을 확인하고 압축을 풀어보는 스크립트를 테스트해야 했는데, 호스트 네트워크를 그대로 쓰게 두고 싶지 않았고, /tmp 아래에서 이것저것 생성하는 것도 호스트와 분리하고 싶었습니다. 그래서 아래 목표를 잡았습니다.

    1. 호스트와 다른 PID 공간에서 실행할 것
    2. 호스트 네트워크를 끊거나 별도 네트워크만 사용할 것
    3. 마운트 뷰를 분리해서 /proc, 작업 디렉터리를 별도로 보이게 할 것
    4. 가능하면 hostname도 바꿔서 “격리된 환경”임을 명확히 할 것

    이 작업은 컨테이너 엔진 없이도 unshare와 nsenter만으로 꽤 깔끔하게 구성할 수 있었습니다.

    리눅스 네임스페이스 사례의 실전 구성과 unshare 기반 프로세스 격리 이미지

    unshare 실행 후 PID, UTS, Mount, Network 네임스페이스가 분리되는 흐름을 설명하는 구성 이미지입니다.

    4. 실전 구현: unshare로 프로세스 격리 만들기

    실제로 써보니까 첫 진입점은 unshare가 제일 직관적이었습니다. 아래 예시는 격리된 셸(shell, 명령 실행 환경)을 띄우는 기본 형태입니다.

    4-1. 가장 기본적인 격리 셸 실행

    sudo unshare \
      --fork \
      --pid \
      --mount \
      --uts \
      --ipc \
      --net \
      --mount-proc \
      bash

    옵션 의미는 이렇습니다.

    • --fork: 새로운 프로세스로 진입
    • --pid: 새로운 PID Namespace 생성
    • --mount: 새로운 Mount Namespace 생성
    • --uts: hostname 분리
    • --ipc: IPC 자원 분리
    • --net: Network Namespace 분리
    • --mount-proc: 새 PID 공간에 맞는 /proc 마운트

    이 상태로 들어가서 ps를 치면, 호스트 전체 프로세스가 아니라 격리된 프로세스 뷰만 보입니다. 처음 확인했을 때 “오, 진짜 분리됐네” 싶더라고요. 이런 순간이 좀 재밌습니다 ㅎㅎ

    4-2. 격리 환경 안에서 hostname 바꾸기

    hostname isolated-lab
    uname -n

    UTS Namespace 덕분에 호스트 이름을 바꿔도 호스트 본체에는 영향이 없습니다. 작은 부분 같아도 테스트 로그를 볼 때 꽤 유용합니다.

    4-3. 마운트 뷰 분리와 작업 디렉터리 고립

    제가 삽질했던 부분이 여기였습니다. Mount Namespace를 만들었는데도 기존 마운트 전파(propagation) 설정 때문에 예상과 다르게 보이는 경우가 있더라고요. 그래서 보통은 아래처럼 먼저 private으로 바꿔주는 쪽이 안전했습니다.

    mount --make-rprivate /
    mkdir -p /tmp/lab-root
    mount -t tmpfs tmpfs /tmp/lab-root
    cd /tmp/lab-root

    이렇게 하면 작업 디렉터리를 메모리 기반 tmpfs로 띄워서 테스트 흔적을 최소화할 수 있습니다. 외부 스크립트를 검증할 때 꽤 편했습니다.

    4-4. 네트워크를 완전히 막고 실행하기

    Network Namespace를 새로 만들면 기본적으로 인터페이스가 거의 없는 상태로 시작합니다. “네트워크 없는 환경에서 이 스크립트가 깨끗하게 도는지” 확인할 때 딱 좋습니다.

    ip link set lo up
    ip addr
    ping -c 1 8.8.8.8

    루프백(loopback)만 올리고 외부 인터페이스를 연결하지 않으면 외부 통신은 되지 않습니다. 저는 백업 검증 스크립트가 혹시라도 외부로 뭔가 호출하는지 확인할 때 이 방법을 자주 썼습니다.

    4-5. 실제 테스트 스크립트 실행 예시

    cat > /tmp/verify-backup.sh <<'EOF'
    #!/usr/bin/env bash
    set -euo pipefail
    mkdir -p work
    cd work
    echo "test file" > sample.txt
    tar -czf sample.tar.gz sample.txt
    tar -xzf sample.tar.gz
    ps -ef
    ip addr || true
    hostname
    EOF
    
    chmod +x /tmp/verify-backup.sh
    /tmp/verify-backup.sh

    여기서 보게 되는 ps, ip addr, hostname 결과가 전부 호스트와 다르게 보이면 1차 격리는 성공입니다. 이게 컨테이너 원리 이해에도 정말 도움이 됩니다.

    프로세스 격리 결과를 보여주는 리눅스 네임스페이스 사례 터미널 이미지

    격리된 프로세스 공간과 제한된 네트워크 환경이 실제 명령 결과에서 어떻게 보이는지 보여주는 예시 이미지입니다.

    5. 조금 더 현실적인 시스템 설계 포인트

    단순 데모를 넘어서 실제 운영에 붙이려면 몇 가지를 같이 봐야 합니다.

    • User Namespace를 함께 검토해서 권한 매핑을 분리할 것
    • Cgroup으로 CPU/메모리 제한을 따로 둘 것
    • seccomp 같은 시스템 콜 필터링까지 가면 더 단단해질 것
    • 파일시스템은 가능하면 읽기 전용(read-only) 바인드 마운트도 고려할 것

    즉, Namespace는 시작점입니다. 보안 강화라는 말이 과장되지 않으려면, “무엇을 분리했고 무엇은 아직 공유하는가”를 명확히 이해해야 합니다. 저도 처음엔 네임스페이스만 쓰면 거의 컨테이너랑 같은 줄 알았는데, 운영 관점에서는 그 사이에 꽤 많은 층이 있더라고요.

    6. ⚠️ 주의사항과 트러블슈팅: 제가 실제로 막혔던 지점

    여기서는 진짜 삽질 포인트만 적어보겠습니다. 혹시 비슷한 경험 있으신가요? 아래 부분에서 많이 막힙니다.

    6-1. /proc가 기대와 다르게 보이는 문제

    원인: 새 PID Namespace를 만들었는데 /proc를 새로 마운트하지 않은 경우입니다.

    해결: --mount-proc 옵션을 쓰거나, 진입 후 직접 proc를 마운트합니다.

    mount -t proc proc /proc

    6-2. 네트워크가 완전히 끊긴 줄 알았는데 아니었던 문제

    원인: 별도 네임스페이스를 안 만들고 호스트 네트워크를 그대로 쓴 경우입니다.

    해결: --net 사용 여부를 먼저 확인하세요. 그리고 격리 환경 안에서 ip addr를 꼭 확인해야 합니다.

    6-3. 마운트가 호스트까지 영향을 주는 것처럼 보이는 문제

    원인: 마운트 전파 설정이 공유(shared) 상태인 경우입니다.

    해결: 작업 초기에 아래 명령으로 전파 범위를 조정합니다.

    mount --make-rprivate /

    6-4. 이게 보안 경계인가요? 라는 질문

    중요한 답은 “상황에 따라 다릅니다”입니다. Namespace만으로 충분한 경우도 있지만, 신뢰할 수 없는 코드를 장시간 돌리거나 외부 입력이 많은 환경이라면 추가 방어 계층이 필요합니다. 그래서 저는 보통 아래 순서로 생각합니다.

    1. Namespace로 뷰 분리
    2. Cgroup으로 자원 제한
    3. 권한 최소화
    4. 읽기 전용 마운트, 임시 작업 공간 분리
    5. 필요시 VM(가상머신) 수준 격리 검토

    7. 검증과 결과: 무엇이 달라졌는지 확인하기

    격리가 제대로 되었는지는 감으로 보면 안 됩니다. 저는 아래 항목을 체크리스트처럼 확인했습니다.

    1. ps -ef 출력에서 호스트 전체 프로세스가 보이지 않는지
    2. hostname 결과가 격리된 값으로 나오는지
    3. ip addr 결과에 호스트 인터페이스가 안 보이는지
    4. 작업 중 생성한 파일이 의도한 임시 공간에만 남는지
    5. 작업 종료 후 흔적 정리가 쉬운지

    결론부터 말하면, 컨테이너 없이도 특정 목적의 프로세스 격리는 충분히 실용적이었습니다. 특히 테스트성 작업, 분석성 작업, 외부 스크립트 검증 같은 용도에서는 빠르게 적용할 수 있었고요. 반대로 이미지 배포, 재현 가능한 패키징, 대규모 운영 자동화까지 가면 컨테이너 쪽이 더 낫습니다.

    리눅스 네임스페이스 사례의 검증 결과와 프로세스 격리 비교 요약 이미지

    PID, 네트워크, 마운트, hostname 항목별로 격리 전후 차이를 정리한 결과 요약 이미지입니다.

    8. 자주 묻는 질문 정리

    Q1. chroot만 쓰면 안 되나요?

    chroot는 파일 경로 뷰를 바꾸는 데는 유용하지만, PID나 네트워크까지 분리해주지는 않습니다. 그래서 프로세스 격리 목적이면 네임스페이스가 훨씬 적합합니다.

    Q2. 이게 Docker를 완전히 대체하나요?

    아닙니다. Docker 같은 컨테이너 도구는 이미지 관리, 배포, 네트워크 구성, 볼륨 운영까지 묶어서 편의성을 제공합니다. 네임스페이스는 그 아래 레이어를 이해하고 직접 다루는 방식에 가깝습니다.

    Q3. 어떤 상황에서 특히 유용했나요?

    저는 홈랩에서 아래 같은 상황에 잘 맞았습니다.

    • 일회성 유지보수 스크립트 검증
    • 네트워크 차단 상태 재현
    • 호스트 프로세스 목록과 분리된 테스트 셸 구성
    • 컨테이너 엔진 없는 환경에서의 임시 샌드박스

    9. 마무리: 네임스페이스를 알면 컨테이너가 더 잘 보입니다

    이번 리눅스 네임스페이스 사례를 통해 느낀 건, 컨테이너는 갑자기 하늘에서 떨어진 기술이 아니라는 점이었습니다. 결국 커널 기능을 조합해서 보안 강화와 운영 편의성을 만든 결과물이더라고요. 제가 직접 해보니 Docker 명령어만 외우던 때보다, 장애를 볼 때도 구조가 훨씬 잘 읽혔습니다.

    처음엔 unshare 옵션이 낯설고, /proc나 마운트 전파 때문에 좀 헷갈릴 수 있습니다. 저도 처음엔 꽤 헤맸거든요. 근데 한 번 손으로 만들어보면 시스템 설계 감각이 정말 좋아집니다. 다음 글에서는 이 흐름을 이어서 Cgroup(컨트롤 그룹, 자원 제한)과 함께 묶어 더 실전적인 샌드박스를 만드는 방법도 다뤄볼 예정입니다. 이전 글에서 다룬 리눅스 프로세스 관리 글이 있다면 같이 보셔도 흐름이 잘 이어질 겁니다. ✅

  • [Linux] Linux 커널 튜닝으로 보는 네트워크 I/O 벤치마크 분석

    [Linux] Linux 커널 튜닝으로 보는 네트워크 I/O 벤치마크 분석

    [Linux] Linux 커널 튜닝으로 보는 네트워크 I/O 벤치마크 분석

    고성능 Linux 서버를 만지다 보면 CPU는 남는데 응답 시간이 흔들리고, 대역폭은 충분한 것 같은데 실제 처리량(throughput, 초당 처리량)이 기대만큼 안 나오는 순간이 꼭 옵니다. 저도 홈랩에서 10GbE 환경을 붙여 놓고 Linux 커널 튜닝만 조금 바꿨을 뿐인데 체감이 확 달라지는 경험을 몇 번 했거든요. 특히 대량 연결이 몰리는 API 서버나 프록시(Proxy, 중계 서버), 로그 수집 노드에서는 네트워크 I/O 최적화가 생각보다 훨씬 중요합니다. 이번 글에서는 제가 실제로 자주 보는 항목들만 골라서, 근거 없는 튜닝이 아니라 벤치마크로 검증 가능한 sysctl 튜닝 중심으로 정리해보겠습니다.

    중요한 포인트는 하나입니다. 튜닝은 숫자로 확인해야 합니다. 감으로 바꾸면 나중에 원인 추적이 더 어려워지더라고요. 그래서 이번 글은 개념 설명보다도, 기준선 측정(Baseline, 변경 전 기준값)과 비교 검증에 조금 더 무게를 두겠습니다.

    Linux 커널 튜닝 기반 네트워크 I/O 최적화 아키텍처 개요 이미지

    벤치마크 전후 비교를 위한 Linux 서버 네트워크 경로와 튜닝 포인트를 한눈에 보여주는 개요 이미지입니다.

    왜 Linux 커널 튜닝이 네트워크 I/O에서 중요할까요

    쉽게 말해 네트워크 패킷(Packet, 네트워크 데이터 조각)은 NIC(Network Interface Card, 네트워크 카드)에서 들어와 커널 큐(queue, 대기열)를 거치고, 소켓 버퍼(socket buffer, 통신 버퍼)를 통과해서 애플리케이션으로 올라갑니다. 이 과정 어디선가 큐가 넘치거나 버퍼가 너무 작으면 드롭(drop, 패킷 유실)이나 재전송(retransmission, 다시 보내기)이 생기고, 결국 처리량이 떨어지거나 지연 시간(latency, 응답 지연)이 튀게 됩니다.

    처음엔 저도 “서버 스펙이 충분한데 왜 느리지?” 싶었는데, 실제로는 애플리케이션 문제가 아니라 커널 기본값이 워크로드에 안 맞는 경우가 꽤 있었습니다. 기본값은 보편적인 안정성을 위한 값이지, 고동시성 서버에 최적화된 값은 아니거든요.

    • 수신 큐 적체: 짧은 시간에 연결이 몰릴 때 backlog가 부족하면 초반부터 손해를 봅니다.
    • 소켓 버퍼 한계: 대역폭은 넓은데 송수신 버퍼가 작으면 링크를 다 못 씁니다.
    • 관측 부재: 튜닝 전 수치가 없으면 좋아진 건지 나빠진 건지 판단이 안 됩니다.

    벤치마크 전에 먼저 보는 기준선 체크리스트

    서버 성능 벤치마크는 설정을 바꾸기 전에 기준선을 잡는 게 핵심입니다. 제가 보통 하는 순서는 아래와 같습니다.

    1. 링크 속도와 duplex 확인
    2. NIC 드롭, 에러, 재전송 지표 확인
    3. 애플리케이션 없이 순수 네트워크 성능 측정
    4. 튜닝 적용
    5. 같은 조건으로 재측정

    1. NIC 상태 확인

    ip -br addr
    ip -s link show dev eth0
    ethtool eth0
    ss -s
    nstat -az | egrep 'Tcp|Ip|Udp'

    여기서 <code>RX dropped, TX dropped, TCP 재전송 계열 카운터를 먼저 봅니다. 숫자가 이미 올라가 있다면 커널 내부 큐나 NIC 쪽 병목을 의심할 수 있습니다.

    2. 순수 대역폭 측정

    # 서버 측
    iperf3 -s
    
    # 클라이언트 측
    iperf3 -c 192.0.2.10 -t 30 -P 4
    iperf3 -c 192.0.2.10 -t 30 -P 8 -R

    -P는 병렬 스트림(parallel stream, 동시 전송 수)입니다. 단일 스트림이 아니라 병렬 스트림까지 봐야 실제 서비스 패턴에 더 가깝습니다. -R은 reverse 모드라서 반대 방향 성능도 확인할 수 있고요.

    3. 지연과 큐 상태 같이 보기

    ping -c 20 192.0.2.10
    sar -n DEV 1 10
    sar -n TCP,ETCP 1 10

    대역폭만 보면 오해하기 쉽습니다. 처리량이 조금 올라갔는데 재전송이 급증하면 오히려 서비스 품질은 나빠질 수 있거든요.

    확인 항목 도구 왜 보나
    링크 상태 ethtool 속도/duplex 협상 문제 확인
    에러/드롭 ip -s link 커널/NIC 큐 병목 확인
    소켓 상태 ss -s 연결 수와 TCP 상태 확인
    처리량 iperf3 튜닝 전후 비교의 기준값
    재전송 nstat, sar 겉보기 성능 상승의 부작용 점검

    실전 Linux 커널 튜닝: sysctl 튜닝은 이렇게 접근합니다

    여기서부터는 제가 비교적 자주 쓰는, 그리고 의미를 설명할 수 있는 항목만 보겠습니다. 무조건 숫자를 크게 넣는 방식은 추천하지 않습니다. 메모리 사용량이 늘고, 오히려 큐 지연(bufferbloat, 버퍼 과적체)이 생길 수 있거든요.

    핵심 sysctl 항목

    항목 의미 체크 포인트
    net.core.somaxconn listen backlog 상한 동시 접속 급증 서버
    net.ipv4.tcp_max_syn_backlog SYN 대기 큐 크기 연결 폭주 시 유용
    net.core.netdev_max_backlog NIC 수신 패킷 백로그 RX 적체 완화
    net.core.rmem_max / wmem_max 소켓 최대 버퍼 고대역폭 전송
    net.ipv4.tcp_rmem / tcp_wmem TCP 자동 버퍼 범위 대용량 흐름 최적화
    sudo cp /etc/sysctl.conf /etc/sysctl.conf.bak
    sudo tee /etc/sysctl.d/99-network-tuning.conf > /dev/null <<'EOF'
    net.core.somaxconn = 4096
    net.ipv4.tcp_max_syn_backlog = 8192
    net.core.netdev_max_backlog = 4096
    net.core.rmem_max = 134217728
    net.core.wmem_max = 134217728
    net.ipv4.tcp_rmem = 4096 262144 134217728
    net.ipv4.tcp_wmem = 4096 262144 134217728
    EOF
    
    sudo sysctl --system

    이 설정은 “무조건 정답”이 아니라 출발점입니다. 예를 들어 작은 패킷이 많이 들어오는 워크로드와 대용량 파일 전송 워크로드는 반응이 다릅니다. 그래서 한 번에 다 바꾸기보다 2~3개씩 묶어서 영향도를 보는 게 좋습니다.

    제가 직접 해보니 somaxconn과 tcp_max_syn_backlog는 접속 급증 구간에서 효과가 보이기 쉬웠고, rmem/wmem 계열은 장거리 네트워크나 큰 전송에서 차이가 보이더라고요. 반대로 이미 애플리케이션이 병목인 상황에서는 기대만큼 변화가 없었습니다. 이건 꼭 기억하셔야 합니다.

    Linux 커널 튜닝의 sysctl 튜닝과 네트워크 큐 흐름 설명 이미지

    sysctl 항목이 수신 큐와 소켓 버퍼에 어떤 영향을 주는지 설명하는 구성 이미지입니다.

    실전 구현 2단계: 애플리케이션과 운영체제 한계도 같이 맞춰야 합니다

    Linux 커널 튜닝만 해놓고 애플리케이션의 파일 디스크립터(file descriptor, 파일/소켓 핸들) 한계가 그대로면 금방 막힙니다. 특히 Nginx, HAProxy, Java 기반 서버를 운영하시면 이 부분을 꼭 같이 보셔야 해요.

    ulimit -n
    cat /proc/sys/fs/file-max
    sysctl fs.file-max
    systemctl show your-service-name | grep LimitNOFILE

    systemd 서비스를 쓰는 경우엔 서비스 단위로 제한이 걸리는 경우가 많습니다.

    sudo systemctl edit your-service-name
    [Service]
    LimitNOFILE=1048576

    적용 후에는 재시작이 필요합니다.

    sudo systemctl daemon-reload
    sudo systemctl restart your-service-name

    여기서 중요한 포인트! 커널 backlog를 키워도 애플리케이션이 실제로 listen()에서 작은 backlog를 쓰고 있으면 기대한 만큼 안 나옵니다. 그러니까 OS와 애플리케이션 설정을 같이 봐야 한다는 거죠.

    ⚠️ 제가 실제로 겪었던 트러블슈팅 포인트

    이 파트는 이론보다 훨씬 중요합니다. 저도 처음엔 숫자만 올리면 빨라질 줄 알았는데, 삽질 좀 했습니다 ㅎㅎ

    1. 버퍼를 너무 크게 잡았더니 메모리 압박

    rmem_max, wmem_max를 크게 올리면 좋을 것 같지만, 연결 수가 많아지면 전체 메모리 사용량이 빠르게 커질 수 있습니다. 특히 컨테이너(Container, 격리 실행 환경) 밀도가 높은 서버에서는 더 민감합니다.

    • 증상: 처리량은 약간 늘었는데 메모리 사용량이 불안정해짐
    • 해결: 최대값보다 자동 조정 범위의 중간값을 먼저 조정하고, 연결 수와 함께 관찰

    2. iperf3 수치만 좋고 실제 서비스는 그대로

    이건 정말 흔합니다. iperf3는 네트워크 자체 성능을 보기엔 좋지만, TLS 종료나 애플리케이션 로직이 있는 실제 서비스와는 다릅니다.

    • 증상: 벤치마크는 상승, 실서비스 p95 응답시간은 변화 없음
    • 해결: 애플리케이션 로그, CPU softirq, 컨텍스트 스위치까지 같이 확인

    3. 드롭은 줄었는데 지연 시간이 늘어남

    큐를 크게 키우면 순간 유실은 줄 수 있지만, 대신 대기열이 길어져서 지연이 늘어날 수 있습니다. 이른바 버퍼블로트(bufferbloat, 과도한 버퍼링) 느낌이 나죠.

    • 증상: 대역폭은 증가, 응답성은 악화
    • 해결: 무조건 큰 값 대신 단계적 조정, ping과 애플리케이션 응답시간 동시 측정

    4. NIC 오프로딩과 드라이버 변수

    환경에 따라 TCP segmentation offload 같은 NIC 기능이 영향을 줄 수 있습니다. 다만 이 부분은 NIC 모델, 드라이버, 커널 버전에 따라 차이가 있어요. 그래서 저는 이 단계는 마지막에 봅니다. 확실한 근거 없이 건드리면 오히려 더 헷갈리더라고요.

    검증: 네트워크 I/O 최적화가 실제로 됐는지 확인하는 방법

    튜닝 후에는 반드시 같은 조건으로 다시 측정합니다. 시간대가 다르거나 상대 서버 상태가 다르면 비교가 흐려지거든요.

    1. 같은 클라이언트와 같은 네트워크 경로 사용
    2. 같은 병렬 스트림 수 유지
    3. 같은 실행 시간 유지
    4. 튜닝 전후에 ss -s, nstat, ip -s link 저장
    mkdir -p ~/benchmarks/network
    
    iperf3 -c 192.0.2.10 -t 30 -P 4 --json > ~/benchmarks/network/iperf3-before.json
    ss -s > ~/benchmarks/network/ss-before.txt
    ip -s link show dev eth0 > ~/benchmarks/network/link-before.txt
    nstat -az > ~/benchmarks/network/nstat-before.txt
    
    # 튜닝 후 동일하게 다시 수행
    iperf3 -c 192.0.2.10 -t 30 -P 4 --json > ~/benchmarks/network/iperf3-after.json
    ss -s > ~/benchmarks/network/ss-after.txt
    ip -s link show dev eth0 > ~/benchmarks/network/link-after.txt
    nstat -az > ~/benchmarks/network/nstat-after.txt

    JSON 결과를 간단히 비교하고 싶다면 jq를 써도 편합니다.

    jq '.end.sum_sent.bits_per_second, .end.sum_received.bits_per_second' ~/benchmarks/network/iperf3-before.json
    jq '.end.sum_sent.bits_per_second, .end.sum_received.bits_per_second' ~/benchmarks/network/iperf3-after.json

    제가 실제로는 아래 세 가지를 같이 봅니다.

    • 처리량 증가: bits per second 상승 여부
    • 안정성: 재전송과 드롭 감소 여부
    • 응답성: ping, p95 응답시간 악화 여부

    이 세 가지가 같이 좋아져야 진짜 개선입니다. 하나만 좋고 나머지가 나빠지면 다시 조정해야 합니다.

    네트워크 I/O 최적화 후 서버 성능 벤치마크 결과 시각화 이미지

    튜닝 전후 처리량, 재전송, 드롭 카운터를 비교한 벤치마크 결과 시각화 이미지입니다.

    비교 정리: 언제 어떤 sysctl 튜닝부터 볼까

    상황 먼저 볼 항목 이유
    접속 폭주 시 연결 누락 somaxconn, tcp_max_syn_backlog listen/SYN 큐 부족 가능성
    RX 드롭 증가 netdev_max_backlog 수신 백로그 적체 가능성
    고대역폭 전송이 기대보다 낮음 rmem_max, wmem_max, tcp_rmem, tcp_wmem 소켓 버퍼 한계 확인
    실서비스 변화 없음 애플리케이션 backlog, LimitNOFILE OS 밖 병목 가능성

    혹시 이런 경험 있으신가요? 벤치마크는 멋지게 올라갔는데 사용자 체감은 그대로인 경우요. 그럴 땐 커널보다 애플리케이션 계층을 먼저 봐야 할 때가 많습니다. 저도 초반엔 커널 숫자만 붙잡고 있었는데, 결국 원인은 워커 수(worker, 처리 프로세스 수)나 연결 풀(connection pool) 설정인 경우가 더 많았어요.

    어떤 상황에서 어떤 항목을 먼저 조정할지 빠르게 판단할 수 있는 요약 인포그래픽입니다.

    자주 묻는 질문 FAQ

    Q1. sysctl 튜닝 값은 높을수록 좋은가요?

    아닙니다. 너무 크게 잡으면 메모리 사용량 증가나 지연 시간 증가로 이어질 수 있습니다. 작게 시작해서 측정하면서 올리는 방식이 안전합니다.

    Q2. iperf3 결과만 보면 충분한가요?

    부족합니다. iperf3는 네트워크 경로 성능을 보기엔 좋지만, 실제 서비스의 응답시간과 애플리케이션 병목까지 설명해주지는 못합니다.

    Q3. 모든 서버에 같은 Linux 커널 튜닝을 적용해도 되나요?

    권장하지 않습니다. 웹 서버, 스토리지 노드, 로그 수집기, 프록시는 패턴이 다릅니다. 같은 Linux 커널 튜닝이라도 워크로드에 따라 결과가 달라집니다.

    마무리: 숫자로 확인되는 튜닝만 남기면 됩니다

    이번 글에서는 Linux 커널 튜닝을 네트워크 I/O 관점에서 어떻게 보고, 어떤 순서로 서버 성능 벤치마크를 해야 하는지 정리해봤습니다. 핵심은 단순합니다. 기준선 측정 → 작은 변경 → 같은 조건으로 재측정 → 부작용 확인. 이 루틴만 지켜도 대부분의 삽질은 줄일 수 있습니다.

    제가 직접 해보니, 화려한 튜닝보다 기본 지표를 꾸준히 저장하고 비교하는 습관이 더 오래 갑니다. 드디어 됐다! 싶은 순간도 결국 로그와 수치가 말해주더라고요. 다음 글에서는 IRQ affinity(인터럽트 분산), RSS(Receive Side Scaling, 수신 분산), RPS/RFS까지 확장해서 CPU 코어 분산 관점의 네트워크 최적화를 다뤄볼 예정입니다. 이전 글에서 다룬 서비스 관측 지표와 함께 보시면 더 이해가 잘 되실 겁니다.

  • [Linux] rsyslog Loki 마이그레이션: 레거시 로그 시스템 현대화 경험

    [Linux] rsyslog Loki 마이그레이션: 레거시 로그 시스템 현대화 경험

    rsyslog Loki 마이그레이션 경험: 레거시 로그 시스템 현대화

    운영 서버가 늘어나면 로그가 제일 먼저 말을 걸어옵니다. 처음엔 rsyslog 하나로도 잘 굴러가거든요. 파일로 남기고, 필요한 서버로 포워딩하고, 급하면 grep으로 뒤지면 되니까요. 그런데 장애가 길어지고 서버 수가 늘어나면 이야기가 달라집니다. 이번 글은 제가 실제로 진행했던 rsyslog Loki 마이그레이션 경험을 바탕으로, 레거시 로그 시스템을 어떻게 현대화했는지 정리한 내용입니다.

    먼저 현재 시점 기준으로 짚고 갈 점이 하나 있습니다. Promtail은 2026년 3월 2일부로 EOL(지원 종료) 상태라서, 신규 구축이라면 Grafana Alloy도 함께 검토하는 게 맞습니다. 다만 이미 rsyslog와 Promtail을 쓰고 있거나, 단계적으로 Loki로 옮겨야 하는 환경이라면 이 글의 접근은 여전히 꽤 현실적이더라고요. 특히 리눅스 로그 관리, 중앙 집중식 로그, 그리고 조회 동선 단축이 고민이라면 더 그렇습니다.

    저도 처음엔 "rsyslog 잘 돌아가는데 굳이 바꿔야 하나?" 싶었습니다. 막상 옮겨보니 검색성, 라벨(label, 분류용 메타데이터), 대시보드 연동이 확실히 다르더라고요. 반대로 아무 생각 없이 옮기면 기존 syslog 습관 때문에 삽질도 꽤 합니다. 여기서 중요한 포인트는 한 번에 뒤엎지 말고, 수집 계층과 조회 계층을 분리해서 천천히 옮기는 것입니다.

    rsyslog Loki 마이그레이션 전체 아키텍처 다이어그램

    기존 rsyslog 기반 수집과 Promtail, Loki, Grafana가 어떻게 연결되는지 한눈에 보여주는 아키텍처 이미지입니다.

    왜 rsyslog만으로는 점점 버거워졌을까

    rsyslog는 여전히 아주 좋은 도구입니다. 특히 Syslog 생태계에서는 검증이 끝난 축에 가깝습니다. 문제는 "수집은 잘하는데, 분석과 검색은 별도 고민이 필요하다"는 점입니다. 서버 수가 늘고 장애가 복합적으로 얽히기 시작하면, 그 차이가 생각보다 크게 느껴집니다.

    • 서버별 로그 파일 위치가 제각각이면 장애 때 동선이 길어집니다.
    • 포워딩만 해두고 검색 체계가 약하면 결국 SSH 접속 후 grep에 의존하게 됩니다.
    • 애플리케이션 로그와 시스템 로그를 함께 보려면 포맷 통일이 필요합니다.
    • 운영자가 바뀌거나 시간이 지나면 "어디에 뭐가 쌓이는지" 문서보다 실서버가 더 진실이 됩니다.

    제가 겪었던 가장 큰 문제는 장애 시점 상관관계(correlation, 연관 분석)가 너무 느렸다는 점이었습니다. 예를 들어 웹 서버에서 502가 튀고, 뒤에서 인증 서비스가 지연되고, 같은 시간대에 커널 메시지까지 흔들리면 이걸 시간축으로 모아봐야 하거든요. rsyslog만으로 불가능한 건 아니지만, 편하게 하기는 어렵습니다. 그래서 결론은 명확했습니다. 수집은 rsyslog의 장점을 활용하고, 조회와 분석은 Loki 쪽으로 넘기자. 이게 제가 정리한 rsyslog Loki 마이그레이션의 핵심 방향이었습니다.

    rsyslog Loki 마이그레이션에서 Loki와 Promtail을 어떻게 볼까

    쉽게 말해 Loki는 로그 저장소이자 검색 엔진 역할을 하고, Promtail은 로그를 모아서 Loki로 보내는 에이전트입니다. Prometheus가 메트릭을 다루듯이, Loki는 로그를 라벨 기반으로 다루는 느낌이라고 보시면 됩니다. 다만 2026년 기준으로는 Promtail보다 Grafana Alloy가 앞으로의 기본 경로라는 점은 꼭 같이 기억하셔야 합니다.

    구성요소 역할 마이그레이션에서의 포인트
    rsyslog 로그 수집, 필터링, 포워딩 기존 서버 설정을 최대한 유지하면서 다음 수집 계층으로 전달
    Promtail 로그 수신 또는 파일 테일링 기존 환경에서 syslog 수신 지점 또는 파일 수집 지점으로 활용 가능
    Loki 로그 저장 및 질의 라벨 설계가 성패를 좌우
    Grafana 조회, 대시보드, 탐색 운영자가 가장 체감하는 개선 지점
    Grafana Alloy 차세대 수집 에이전트 신규 구축이나 장기 운영 기준으로 우선 검토

    많이 헷갈리는 부분이 하나 있습니다. "Promtail이 파일만 읽는 거 아니었나?" 저도 처음엔 그렇게 생각했었습니다. 그런데 Promtail은 Syslog Receiver 설정도 지원합니다. 그래서 기존 rsyslog가 잘 깔려 있다면, rsyslog가 TCP로 Promtail에 넘기고 Promtail이 Loki로 적재하는 구조가 꽤 현실적입니다. 이 방식이 좋은 이유는 레거시 환경을 한 번에 뜯어고치지 않아도 되기 때문입니다.

    마이그레이션 설계: 한 번에 바꾸지 말고 두 단계로

    제가 추천드리는 방식은 아래 순서입니다. 이 흐름이 rsyslog Loki 마이그레이션에서 제일 덜 아프더라고요.

    1. 1단계: 기존 rsyslog는 유지하고, Promtail 또는 Alloy를 syslog 수신기로 세웁니다.
    2. 2단계: Grafana에서 검색과 대시보드를 검증한 뒤, 필요한 서버부터 파일 수집이나 구조화 로그로 확장합니다.

    이렇게 하면 롤백도 쉽습니다. 장애가 나도 rsyslog는 원래 하던 일을 계속하니까요. 운영에서는 이 안정감이 정말 큽니다.

    rsyslog Loki 마이그레이션 2단계 전환 구성도

    기존 rsyslog를 유지한 채 Promtail을 앞단 수신기로 추가하고, 이후 Loki 조회 환경을 점진적으로 확장하는 전환 방식입니다.

    권장 아키텍처

    • 애플리케이션/시스템 로그 발생
    • 로컬 rsyslog가 표준 syslog 포맷으로 정리
    • rsyslog가 TCP로 Promtail에 전달
    • Promtail이 라벨을 붙여 Loki로 전송
    • Grafana에서 검색, 필터링, 시각화

    중요한 포인트! 라벨은 많이 붙인다고 좋은 게 아닙니다. host, job, facility 정도처럼 조회에 꼭 필요한 축만 먼저 가져가세요. 처음부터 프로그램명, PID, path, 환경명, 팀명, 서비스명까지 다 라벨로 올리면 쿼리보다 라벨 관리가 더 힘들어집니다.

    실전 구현: rsyslog에서 Loki로 넘기는 기본 구성

    이제 실제 설정입니다. 아래 예시는 "Promtail이 syslog를 받고 Loki에 넣는 구성"입니다. 이미 Loki, Grafana, Promtail 서비스가 준비되어 있다는 전제로 적겠습니다. 설치 방식은 배포판 패키지, 바이너리, 컨테이너 등 환경마다 다르니 여기서는 마이그레이션 구성 자체에 집중하겠습니다.

    1. Promtail 설정 준비

    Promtail 설정에는 positions 파일이 자주 등장합니다. 파일 테일링이나 journal 수집에서는 어디까지 읽었는지 기억하는 데 중요하거든요. 다만 이 글처럼 syslog receiver만 쓰는 경로라면 중복 수집 방지의 핵심은 positions 파일보다 수집 경로 분리와 송신 설계에 있습니다. 이 부분은 저도 초반에 헷갈렸습니다.

    sudo mkdir -p /etc/promtail
    sudo mkdir -p /var/lib/promtail
    sudo chown -R promtail:promtail /var/lib/promtail

    다음은 Promtail 설정 예시입니다.

    server:
      http_listen_port: 9080
      grpc_listen_port: 0
    
    positions:
      filename: /var/lib/promtail/positions.yaml
    
    clients:
      - url: http://loki.example.internal:3100/loki/api/v1/push
    
    scrape_configs:
      - job_name: syslog
        syslog:
          listen_address: 0.0.0.0:1514
          listen_protocol: tcp
          idle_timeout: 60s
          label_structured_data: true
          labels:
            job: syslog
        relabel_configs:
          - source_labels: ['__syslog_message_hostname']
            target_label: host
          - source_labels: ['__syslog_message_app_name']
            target_label: app
          - source_labels: ['__syslog_message_severity']
            target_label: severity

    여기서 제가 실제로 많이 썼던 건 relabel_configs입니다. syslog 헤더에서 넘어온 값을 바로 host, app 같은 라벨로 바꿔주면 Grafana 탐색이 훨씬 편해집니다. 처음엔 라벨 이름을 제 멋대로 만들다가 쿼리 통일이 안 돼서 다시 정리했었는데, 운영팀 여러 명이 같이 볼 거면 naming rule부터 맞춰두는 게 좋습니다.

    2. rsyslog에서 Promtail로 포워딩

    rsyslog는 omfwd 모듈로 Promtail 쪽으로 보낼 수 있습니다. TCP를 권장하는 이유는 운영에서 안정성이 더 좋기 때문입니다. UDP는 편하지만 장애 상황에서 조용히 놓치는 메시지가 생기면 답답하더라고요. 특히 Promtail 쪽은 RFC5424 계열 syslog 포맷과 octet-counted framing 조합이 가장 무난했습니다.

    # /etc/rsyslog.d/90-promtail.conf
    *.* action(
      type="omfwd"
      protocol="tcp"
      target="promtail.example.internal"
      port="1514"
      Template="RSYSLOG_SyslogProtocol23Format"
      TCP_Framing="octet-counted"
      KeepAlive="on"
      queue.type="linkedList"
    )

    queue.type="linkedList"를 넣은 이유도 꼭 짚고 넘어가야 합니다. 원격 수신기가 잠깐 죽거나 네트워크가 흔들릴 때, 큐가 없으면 송신 쪽이 막히거나 손실을 체감하기 쉬워집니다. 다만 운영 환경에 따라 디스크 큐나 재시도 옵션까지 더 챙겨야 할 수 있으니, 중요한 서비스라면 여기서 한 번 더 보수적으로 잡는 걸 권합니다.

    3. 설정 검증과 서비스 재시작

    설정 파일을 넣었다고 바로 끝은 아닙니다. 문법 검사를 먼저 하고, 서비스 상태를 짧게라도 확인해야 뒤탈이 적습니다. 이 단계에서 1분만 더 써도 나중에 1시간 덜 헤매더라고요.

    sudo rsyslogd -N1
    sudo systemctl restart rsyslog
    sudo systemctl restart promtail
    sudo systemctl status rsyslog --no-pager
    sudo systemctl status promtail --no-pager

    rsyslog는 재시작 전에 rsyslogd -N1로 문법 검사를 꼭 하세요. 이거 안 하고 바로 재시작했다가 로그 수집 전체가 멈추면 진짜 식은땀 납니다.

    4. 테스트 로그 보내기

    테스트는 꼭 단순해야 합니다. logger 한 줄로 시작하세요. 괜히 복잡한 애플리케이션 로그부터 확인하면 어디서 막혔는지 추적이 어렵습니다.

    logger -t migration-test "rsyslog to promtail to loki test message"
    curl -s http://127.0.0.1:9080/ready
    curl -s http://loki.example.internal:3100/ready

    여기서 ready 응답이 나온다고 끝난 건 아닙니다. 실제로 Grafana Explore에서 라벨이 원하는 대로 붙는지까지 봐야 합니다. 운영에서는 이 마지막 한 단계가 제일 중요하더라고요.

    Promtail 활용과 rsyslog 전달 설정 구성 이미지

    Promtail의 syslog 수신 설정과 rsyslog의 omfwd 전달 관계를 시각적으로 정리한 구성 이미지입니다.

    ⚠️ 실제로 많이 겪는 문제와 해결 방법

    여기부터가 진짜 운영 이야기입니다. 문서만 보면 금방 끝날 것 같았는데, 저는 여기서 시간을 꽤 썼습니다. rsyslog Loki 마이그레이션은 개념보다 디테일에서 더 많이 막히더라고요.

    1. 메시지는 들어오는데 라벨이 예상과 다를 때

    Promtail이 syslog 헤더를 내부 라벨로 들고 오는데, relabel_configs에서 이름을 잘못 참조하면 Grafana에서 host가 비어 보일 수 있습니다. 이 경우는 대부분 입력값 이름을 잘못 쓴 겁니다. 예를 들어 hostname, app-name 같은 식으로 감으로 쓰면 안 되고, Promtail이 제공하는 내부 라벨 이름을 기준으로 맞춰야 합니다.

    • host가 비면 __syslog_message_hostname 확인
    • 앱 이름이 비면 __syslog_message_app_name 확인
    • 심각도가 안 잡히면 __syslog_message_severity 확인

    2. rsyslog는 보냈다고 하는데 Promtail에서 안 받을 때

    이건 네트워크와 포맷 두 가지를 같이 보셔야 합니다. TCP 포트가 열려 있는지 먼저 확인하고, 그다음 syslog 포맷을 맞춰야 합니다. 저도 한 번은 포트는 맞게 열어두고 템플릿을 기본값으로 둬서 Promtail 파싱이 애매하게 꼬인 적이 있었습니다. 그 뒤로는 RSYSLOG_SyslogProtocol23Format을 먼저 의심합니다.

    ss -lntp | grep 1514
    sudo journalctl -u promtail -n 50 --no-pager
    sudo journalctl -u rsyslog -n 50 --no-pager

    3. 중복 수집이 생길 때

    이 부분도 자주 나옵니다. 기존에 파일 테일링과 syslog 포워딩을 동시에 물려두면 같은 로그가 두 번 들어갈 수 있습니다. 특히 /var/log/messages를 Promtail이 읽고 있는데, 같은 메시지를 rsyslog가 또 syslog receiver로 보내면 조회 화면에서 두 줄로 보여요. 처음엔 "Loki가 복제했나?" 싶었는데 아니더라고요. 수집 경로를 한 로그 소스당 하나로 정리해야 합니다.

    4. 라벨을 너무 많이 붙여서 쿼리가 무거워질 때

    라벨은 검색에 좋지만, 남발하면 관리 포인트가 폭증합니다. 저는 초기에 프로그램명, 파일경로, 환경명, 인스턴스명, 팀명까지 다 넣었다가 나중에 정리하느라 더 힘들었습니다. 운영에서 오래 가는 구성은 의외로 단순합니다.

    항목 처음 욕심낸 구성 지금 추천하는 구성
    host 사용 사용
    job 사용 사용
    app 사용 선택적 사용
    path 라벨로 사용 가급적 지양
    pid 라벨로 사용 지양
    team/env 모두 라벨화 정말 필요한 것만

    검증: rsyslog Loki 마이그레이션이 제대로 끝났는지 확인하는 방법

    설정이 끝났다고 바로 성공은 아닙니다. 운영에서는 "수집된다"보다 "원하는 방식으로 검색된다"가 더 중요합니다. 저는 아래 체크리스트로 마이그레이션 완료 여부를 봤습니다.

    1. 테스트 메시지가 Loki에 들어오는지 확인
    2. host 라벨로 서버별 필터링이 되는지 확인
    3. 같은 시간대 다른 서버 로그를 한 화면에서 비교 가능한지 확인
    4. 기존 rsyslog 경로와 신규 Loki 경로 결과가 크게 어긋나지 않는지 샘플 비교
    5. 장애 상황에서 운영자가 SSH보다 Grafana를 먼저 열게 되는지 확인

    Grafana Explore에서는 보통 이런 식으로 보기 시작했습니다.

    {job="syslog",host="web-01"}
    {job="syslog",app="nginx"}
    {job="syslog",severity="err"}

    드디어 원하는 대로 host 기준으로 잘 걸리고, 애플리케이션별로도 묶이기 시작하면 그때부터 체감이 옵니다. "아, 이제 장애 볼 때 여기부터 보면 되겠구나" 하는 느낌이요. 이거 진짜 편하더라고요.

    Grafana Loki에서 중앙 집중식 로그를 검색하는 결과 화면

    Grafana Explore에서 host, app, severity 라벨로 로그를 필터링하고 시간축으로 비교하는 결과 화면 이미지입니다.

    마이그레이션 이후 운영 방식이 어떻게 바뀌었나

    가장 큰 변화는 로그 확인 동선이 짧아졌다는 점입니다. 예전에는 "어느 서버지? 어떤 파일이지? 압축됐나? rotate됐나?"부터 시작했는데, 지금은 일단 Grafana에서 시간대와 호스트를 좁혀보고 필요할 때만 서버에 들어갑니다. 중앙 집중식 로그 체계의 장점이 여기서 확실히 보입니다.

    • 장애 초동 대응 속도가 빨라집니다.
    • 서버별 로그 위치를 전부 외우지 않아도 됩니다.
    • 운영 인수인계가 쉬워집니다.
    • 애플리케이션 로그와 시스템 로그를 함께 보기 편해집니다.

    물론 단점도 있습니다. Loki 쪽 저장 정책, 라벨 설계, 대시보드 표준화는 결국 운영팀이 책임져야 합니다. 그래서 저는 마이그레이션을 "도구 교체"보다 운영 습관 교정에 가깝게 봅니다. 혹시 지금도 grep과 SSH에 너무 의존하고 계신가요? 그렇다면 rsyslog를 버리라는 뜻이 아니라, 조회 계층만이라도 현대화해보시라고 말씀드리고 싶네요.

    정리와 다음 단계

    rsyslog Loki 마이그레이션은 생각보다 거창한 프로젝트가 아닐 수도 있습니다. 핵심은 rsyslog를 적으로 보지 않는 겁니다. 기존 수집 안정성은 살리고, Promtail 활용 또는 Alloy 전환을 통해 Loki에 연결해서 검색 경험을 개선하면 됩니다. 제가 직접 해보니 한 번에 완벽하게 옮기려는 순간부터 어려워지더라고요. 반대로 syslog receiver 하나 붙이고, 라벨 몇 개만 정리해도 금방 효과가 납니다.

    정리하면 이렇습니다.

    • 수집은 보수적으로: 기존 rsyslog를 유지합니다.
    • 조회는 현대적으로: Loki와 Grafana로 검색 동선을 줄입니다.
    • 라벨은 절제해서: host, job 같은 핵심부터 시작합니다.
    • 중복 수집은 반드시 제거: 파일 테일링과 syslog 전달 경로를 겹치지 않게 합니다.

    다음 글에서는 Loki 쿼리 정리와 라벨 설계 실전, 그리고 systemd-journald와 함께 가져갈 때 어떤 점을 조심해야 하는지 다뤄볼 예정입니다. 블로그의 이전 글에서 다뤘던 로그 로테이션 설계 내용과 연결해서 보시면 흐름이 더 잘 잡히실 거예요.

    rsyslog와 Loki 기반 로그 시스템 현대화 비교 인포그래픽

    레거시 rsyslog 중심 운영과 Loki 기반 현대화 운영의 차이를 요약 비교한 인포그래픽 이미지입니다.

    자주 묻는 질문

    Q1. rsyslog를 완전히 없애야 하나요?

    아닙니다. 많은 환경에서 rsyslog는 여전히 좋은 전처리와 전달 계층입니다. 저도 처음엔 완전 교체를 고민했는데, 실제로는 공존 전략이 훨씬 안정적이었습니다.

    Q2. Promtail은 파일만 읽는 도구인가요?

    아닙니다. syslog 수신 구성도 가능합니다. 다만 현재는 Promtail이 EOL이라서, 새로 시작하는 환경이라면 Grafana Alloy를 먼저 검토하는 편이 맞습니다.

    Q3. 처음부터 구조화 로그(JSON)를 강제해야 하나요?

    꼭 그렇지는 않습니다. 저는 먼저 중앙 수집과 조회 체계를 만들고, 그다음 서비스별로 구조화 로그를 넓혀갔습니다. 순서를 잘 잡는 게 중요합니다.

    Q4. 가장 먼저 챙길 검증 포인트는 뭔가요?

    중복 수집 여부와 라벨 품질입니다. 로그가 들어오는 것만 보면 반쪽 성공입니다. 검색이 잘 되어야 진짜 성공이거든요.

  • [Linux] CentOS 7에서 AlmaLinux 9로: 성공적인 서버 마이그레이션 전략과 실제 경험

    [Linux] CentOS 7에서 AlmaLinux 9로: 성공적인 서버 마이그레이션 전략과 실제 경험

    [Linux] CentOS 7에서 AlmaLinux 9로: 성공적인 서버 마이그레이션 전략과 실제 경험

    CentOS 7 AlmaLinux 9 마이그레이션 이야기가 요즘 정말 많이 나옵니다. 이유는 단순합니다. CentOS 7 EOL(End of Life, 기술지원 종료)이 이미 현실이 됐기 때문이거든요. 기존에 잘 돌아가던 서버라도 보안 업데이트가 끊기면 운영 입장에서는 그냥 두기 어렵습니다. 저도 홈랩과 업무 환경에서 비슷한 상황을 여러 번 겪었는데, 처음엔 “OS만 바꾸면 끝 아닌가?” 싶었다가 생각보다 체크할 게 많아서 삽질 좀 했습니다 ㅎㅎ

    특히 이번 글은 CentOS 7에서 AlmaLinux 9로 바로 점프하는 상황을 기준으로 정리했습니다. 결론부터 말씀드리면, 저는 이런 경우 인플레이스 업그레이드(in-place upgrade, 현재 서버를 그대로 올리는 방식)보다 병행 구축 후 이전(side-by-side migration, 새 서버를 만들고 데이터와 서비스를 옮기는 방식)을 훨씬 더 추천합니다. 실제로 써보니까 이 방식이 훨씬 덜 위험하더라고요.

    CentOS 7 AlmaLinux 9 마이그레이션 전체 아키텍처 다이어그램

    기존 CentOS 7 서버, 신규 AlmaLinux 9 서버, 데이터 동기화, 검증, DNS 또는 로드밸런서 전환 흐름을 한눈에 보여주는 개요 이미지입니다.

    1. 왜 CentOS 7 AlmaLinux 9 마이그레이션이 중요한가

    쉽게 말해 운영체제는 서버의 바닥입니다. 애플리케이션이 아무리 멀쩡해도 바닥이 낡으면 문제가 생깁니다. CentOS EOL 이후에는 보안 패치, 버그 수정, 생태계 호환성 측면에서 점점 불리해집니다. 지금 당장은 돌아가도, 어느 날 패키지 설치 하나 때문에 막히는 경우가 생겨요. 저도 예전에 오래된 저장소(repository, 패키지 보관소) 의존성 때문에 야간 작업에서 발목 잡힌 적이 있었습니다.

    AlmaLinux는 RHEL(Red Hat Enterprise Linux, 레드햇 엔터프라이즈 리눅스) 계열과의 호환성을 바탕으로 운영하기 좋은 선택지입니다. 실무 관점에서는 “얼마나 화려한가”보다 얼마나 예측 가능하게 굴러가느냐가 중요하잖아요. 그런 면에서 AlmaLinux 전환은 꽤 현실적인 선택입니다.

    • 보안 측면: 지원 종료 OS를 계속 쓰는 리스크를 줄일 수 있습니다.
    • 운영 측면: 최신 패키지와 관리 체계를 받아들이기 쉬워집니다.
    • 표준화 측면: 앞으로의 리눅스 서버 이전 작업도 훨씬 수월해집니다.

    2. 개념부터 정리: 업그레이드와 마이그레이션은 다릅니다

    여기서 중요한 포인트가 하나 있습니다. 많은 분들이 업그레이드와 마이그레이션을 비슷하게 보시는데, 실제 운영에서는 완전히 다르게 접근해야 합니다.

    구분 의미 장점 주의점
    인플레이스 업그레이드 기존 서버 OS를 바로 올림 서버 수가 적고 빠르게 시도 가능 실패 시 롤백이 까다롭고 서비스 영향이 큼
    사이드 바이 사이드 마이그레이션 신규 서버를 만들고 서비스/데이터를 이전 검증과 롤백이 쉽고 운영 안정성이 높음 초기 준비 작업이 더 필요함

    제가 직접 해보니 CentOS 7에서 AlmaLinux 9로는 새 서버를 만들고 옮기는 전략이 가장 안정적이었습니다. 이유는 간단합니다. 메이저 버전 차이가 크면 패키지 체계, 기본 설정, 런타임(runtime, 실행 환경), 보안 정책이 한꺼번에 바뀌거든요. 특히 다음 항목은 꼭 체크해야 합니다.

    • yum에서 dnf: 명령 습관은 비슷하지만 운영 방식이 미묘하게 달라집니다.
    • Python 2에서 Python 3: 운영 스크립트가 있다면 거의 필수 점검입니다.
    • 방화벽 정책: firewalld, nftables 계열 변화에 따라 기존 iptables 습관이 그대로 안 먹히는 경우가 있습니다.
    • OpenSSL/OpenSSH/PHP/MariaDB 같은 런타임 차이: 애플리케이션 호환성 검증이 필요합니다.

    3. 제가 실제로 잡았던 AlmaLinux 9 마이그레이션 전략

    처음엔 저도 “야간 점검 시간에 한 번에 올리면 되지 않을까?” 생각했었습니다. 근데 서비스가 조금이라도 복잡하면 그 접근이 위험하더라고요. 그래서 최종적으로는 아래 순서로 갔습니다.

    1. 기존 CentOS 7 서버의 서비스 목록과 의존성 파악
    2. 신규 AlmaLinux 9 서버 구축
    3. 패키지, 계정, 서비스 설정 재현
    4. 데이터 사전 동기화
    5. 테스트 도메인 또는 hosts 기반 검증
    6. 짧은 점검 시간에 최종 동기화 후 전환
    7. 문제 없으면 기존 서버는 일정 기간 읽기 전용 또는 대기 상태로 유지

    이 방식의 장점은 명확합니다. 언제든 이전 상태로 돌아갈 수 있다는 거예요. 드디어 됐다 싶어도 사람 일은 모르거든요. 실제 운영은 “성공”보다 “실패했을 때 얼마나 빨리 회복하느냐”가 더 중요합니다.

    4. 실전 구현: CentOS 7 AlmaLinux 9 마이그레이션 준비

    4-1. 기존 서버 인벤토리 정리

    먼저 해야 할 일은 감으로 움직이지 않는 겁니다. 서비스 포트, 패키지, 크론(cron, 주기 실행 작업), 사용자 계정, 마운트 정보를 뽑아두세요. 저는 아래처럼 기본 자료부터 수집했습니다.

    hostnamectl
    cat /etc/centos-release
    ip addr
    ss -tulpn
    rpm -qa | sort > /root/pkglist-centos7.txt
    systemctl list-unit-files --type=service > /root/services-centos7.txt
    crontab -l
    ls -al /etc/cron.d/
    getent passwd > /root/passwd.snapshot
    getent group > /root/group.snapshot
    df -h
    mount

    이 단계에서 중요한 건 “무엇이 설치돼 있는가”보다 실제로 무엇이 쓰이고 있는가입니다. 설치만 돼 있고 안 쓰는 패키지는 꽤 많습니다. 이걸 정리해두면 AlmaLinux 전환 후 서버가 훨씬 깔끔해집니다.

    4-2. 신규 AlmaLinux 9 서버 기본 세팅

    새 서버는 기존 서버를 그대로 복제하려 하지 말고, 필요한 것만 다시 만든다는 느낌으로 가는 게 좋습니다. 저도 처음엔 예전 설정을 몽땅 복붙했다가 SELinux(Security-Enhanced Linux, 보안 강제 정책)와 서비스 경로 차이 때문에 다시 정리했었네요.

    dnf update -y
    hostnamectl set-hostname new-app01.example.local
    timedatectl set-timezone Asia/Seoul
    dnf install -y rsync vim tar curl wget firewalld policycoreutils-python-utils
    systemctl enable --now firewalld
    systemctl enable --now sshd

    계정과 SSH 키도 이 시점에 맞춰둡니다.

    useradd -m deploy
    mkdir -p /home/deploy/.ssh
    chmod 700 /home/deploy/.ssh
    chown -R deploy:deploy /home/deploy/.ssh
    CentOS 7 AlmaLinux 9 마이그레이션을 위한 AlmaLinux 9 신규 서버 구성 이미지

    AlmaLinux 9 신규 서버에서 기본 패키지 설치, firewalld 활성화, 호스트명 설정이 끝난 초기 구성 단계를 보여주는 이미지입니다.

    4-3. 애플리케이션 설정과 데이터 이전

    데이터 이전은 보통 rsync(알싱크, 파일 동기화 도구)를 많이 씁니다. 증분 복사(incremental copy, 바뀐 부분만 다시 전송)가 가능해서 정말 편하더라고요. 서비스 종류에 따라 디렉터리만 옮길지, DB를 별도로 덤프(dump, 내보내기)할지는 달라집니다.

    rsync -avzH --numeric-ids /etc/nginx/ root@new-app01:/etc/nginx/
    rsync -avzH --numeric-ids /var/www/ root@new-app01:/var/www/
    rsync -avzH --numeric-ids /data/ root@new-app01:/data/

    DB는 파일 복사보다 논리 백업(logical backup, SQL 기반 백업) 쪽이 더 안전한 경우가 많습니다.

    mysqldump --all-databases --single-transaction --routines --triggers > all.sql
    scp all.sql root@new-app01:/root/
    mysql < /root/all.sql

    웹 서비스라면 설정 파일을 옮긴 뒤 문법 검사를 먼저 합니다.

    nginx -t
    apachectl configtest
    systemctl daemon-reload
    systemctl enable --now nginx

    여기서 제가 많이 하는 방식은 운영 DNS를 바로 바꾸지 않고 hosts 파일이나 임시 도메인으로 먼저 붙어보는 것입니다. 이 단계에서 80% 문제를 잡아요.

    5. ⚠️ 실제로 자주 막히는 문제와 해결법

    이 섹션은 진짜 중요합니다. 문서만 보면 다 쉬워 보이는데, 실전에서는 작은 차이 때문에 시간이 녹습니다.

    5-1. SELinux 때문에 서비스는 뜨는데 동작이 이상한 경우

    처음엔 이게 뭔가 싶었는데, 프로세스는 살아 있는데 파일 접근이 막혀서 웹이 비정상 동작하는 경우가 있더라고요. 저는 로그부터 확인했습니다.

    getenforce
    ausearch -m avc -ts recent
    journalctl -xe

    무작정 비활성화하기보다 컨텍스트(context, 보안 레이블)를 먼저 맞춰보는 게 훨씬 낫더라고요.

    restorecon -Rv /var/www
    semanage fcontext -a -t httpd_sys_content_t "/data/web(/.*)?" 
    restorecon -Rv /data/web

    5-2. 예전 스크립트가 Python 2 기준인 경우

    CentOS 7 시절에 만든 운영 스크립트가 꽤 오래 살아남는 경우가 많죠. 근데 AlmaLinux 9로 오면서 Python 3 기준으로 정리해야 할 때가 많습니다. 저도 백업 스크립트 하나가 print 문법 때문에 바로 죽어서 순간 멈칫했습니다.

    • 쉘 스크립트로 대체 가능한지 먼저 검토
    • Python 스크립트라면 shebang과 모듈 의존성 점검
    • 가상환경(virtual environment, 독립 실행 환경) 필요 여부 확인

    5-3. 방화벽 규칙이 예전과 다르게 느껴지는 경우

    서비스는 정상인데 외부에서 접속이 안 되는 상황, 생각보다 흔합니다. 이럴 때는 애플리케이션만 보지 말고 포트 리슨(listen, 대기 상태) 여부, firewalld 규칙, 보안 그룹 또는 상위 네트워크 정책까지 같이 봐야 합니다.

    ss -tulpn
    firewall-cmd --get-active-zones
    firewall-cmd --list-all
    firewall-cmd --permanent --add-service=http
    firewall-cmd --permanent --add-service=https
    firewall-cmd --reload

    5-4. 패키지 이름은 비슷한데 설정 경로가 미묘하게 다른 경우

    이거 꽤 귀찮습니다. 특히 예전 블로그 글만 보고 따라 하면 안 맞는 경우가 있어요. 그래서 저는 항상 패키지 설치 후 기본 설정 파일 위치와 systemd(unit, 서비스 정의) 이름부터 확인합니다.

    rpm -qc nginx
    systemctl status nginx
    systemctl cat nginx
    CentOS 7 AlmaLinux 9 마이그레이션 트러블슈팅과 SELinux 점검 이미지

    마이그레이션 중 흔히 만나는 SELinux, 방화벽, 파일 동기화 문제를 점검하는 실제 운영자 시점의 트러블슈팅 이미지입니다.

    6. 검증: 전환 전에 꼭 확인한 체크리스트

    제가 실제로 써보니까 AlmaLinux 마이그레이션 성공 여부는 전환 직전보다 전환 전에 얼마나 검증했는가에서 갈리더라고요. 아래 체크리스트는 꼭 추천드립니다.

    1. 서비스 프로세스가 정상 기동하는가
    2. 기존 포트와 동일하게 리슨 중인가
    3. 웹, API, 배치, DB 연결이 모두 정상인가
    4. 로그 경로와 로그 로테이션(log rotation, 로그 순환)이 정상인가
    5. 백업 스크립트와 크론 작업이 정상 동작하는가
    6. 모니터링 대상과 알림 규칙이 새 서버에 반영됐는가
    7. TLS/인증서, 권한, SELinux, 방화벽 정책이 검증됐는가

    저는 간단한 검증 스크립트도 만들어서 썼습니다.

    #!/bin/bash
    set -e
    curl -I http://127.0.0.1
    systemctl is-active nginx
    systemctl is-active mariadb
    ss -tulpn | grep -E ":80|:443|:3306"
    test -f /var/log/messages

    그리고 전환 직전에는 한 번 더 최종 동기화를 했습니다.

    systemctl stop nginx
    rsync -avzH --delete /var/www/ root@new-app01:/var/www/
    rsync -avzH --delete /etc/nginx/ root@new-app01:/etc/nginx/

    이후 DNS를 바꾸거나, 로드밸런서(load balancer, 트래픽 분산 장비) 백엔드를 교체하는 식으로 컷오버(cutover, 실제 전환)를 진행했습니다.

    CentOS 7 AlmaLinux 9 마이그레이션 완료 후 검증 대시보드 이미지

    마이그레이션 완료 후 시스템 서비스 상태, 포트 오픈 현황, 웹 응답, 기본 모니터링 지표가 정상임을 보여주는 검증 이미지입니다.

    7. 결과: AlmaLinux 전환 후 체감했던 변화

    결과적으로는 꽤 만족스러웠습니다. 물론 마이그레이션 자체가 즐거운 작업은 아니죠. 근데 막상 끝나고 나면 얻는 게 분명합니다.

    • 지원 종료에 대한 불안감이 줄어듭니다.
    • 새로운 패키지와 운영 표준으로 정리하기 쉬워집니다.
    • 불필요한 설정과 레거시(legacy, 오래된 자산)를 털어낼 기회가 됩니다.
    • 향후 자동화(Ansible 같은 구성관리 포함) 기반 정리가 쉬워집니다.

    특히 CentOS 7 AlmaLinux 9 마이그레이션은 단순히 OS 이름만 바꾸는 일이 아니라, 운영 환경을 한 번 건강하게 정리하는 계기였습니다. 저도 처음엔 귀찮아서 미루고 싶었는데, 정리하고 나니 다음 서버도 훨씬 자신 있게 이전하게 되더라고요.

    8. 정리와 FAQ: 리눅스 서버 이전 전에 꼭 기억할 것

    CentOS 7 AlmaLinux 9 마이그레이션에서 제가 가장 강조하고 싶은 건 딱 세 가지입니다. 첫째, 무리해서 한 번에 올리지 말 것. 둘째, 새 서버를 만들고 검증한 뒤 전환할 것. 셋째, 롤백 경로를 미리 확보할 것. 이 세 가지만 지켜도 실패 확률이 크게 줄어듭니다.

    혹시 이런 경험 있으신가요? 설정은 맞는 것 같은데 전환 후에만 이상하게 꼬이는 상황이요. 대부분은 서비스 자체보다 권한, 경로, 방화벽, 이름 해석 같은 주변 요소에서 터집니다. 그래서 저는 항상 체크리스트와 검증 스크립트를 같이 둡니다. 이거 진짜 편하더라고요.

    자주 묻는 질문

    • Q. 인플레이스 업그레이드보다 신규 구축이 왜 더 낫나요?
      A. 메이저 버전 차이가 큰 경우 변수도 많아집니다. 신규 구축은 검증과 롤백이 쉬워서 운영 리스크가 낮습니다.
    • Q. 모든 설정 파일을 그대로 복사하면 되나요?
      A. 권장하지 않습니다. 필요한 설정만 검토해서 옮기는 편이 안전합니다.
    • Q. AlmaLinux 전환 전에 가장 먼저 볼 것은 뭔가요?
      A. 서비스 의존성, 런타임 버전, 백업/복구 절차입니다.

    다음 글에서는 AlmaLinux 전환 이후 체크해야 할 보안 하드닝(hardening, 보안 강화) 항목이나 Ansible 기반 자동화 이전 방법도 다뤄볼 예정입니다. 이전 글에서 다뤘던 백업 검증 루틴과 함께 보시면 더 도움이 될 겁니다.

    CentOS 7 AlmaLinux 9 마이그레이션 요약 인포그래픽

    CentOS 7과 AlmaLinux 9의 운영 차이, 추천 마이그레이션 방식, 전환 전후 체크포인트를 요약한 인포그래픽 이미지입니다.

  • [Linux] 리눅스 시스템 장애, 프로세스 비정상 종료 원인 분석 및 해결 사례 연구

    [Linux] 리눅스 시스템 장애, 프로세스 비정상 종료 원인 분석 및 해결 사례 연구

    [Linux] 리눅스 시스템 장애, 프로세스 비정상 종료 원인 분석 및 해결 사례 연구

    운영 중인 서버에서 갑자기 애플리케이션이 내려가고, 재시작은 되는데 원인이 안 보일 때가 있죠. 저도 홈랩과 실무에서 이런 일을 여러 번 겪었습니다. 특히 리눅스 프로세스 비정상 종료는 겉으로 보기엔 단순한 장애처럼 보여도, 실제로는 메모리 부족, 시그널(signal, 운영체제가 프로세스에 보내는 제어 신호), 파일 디스크립터(file descriptor, 열린 파일 핸들), 권한 문제, 디스크 I/O 지연처럼 여러 원인이 겹쳐 있는 경우가 많거든요. 이번 글에서는 제가 직접 겪었던 실제 시스템 장애 사례를 바탕으로, 로그 분석과 원인 분석을 어떻게 진행했는지, 그리고 어떤 순서로 복구했는지 차근차근 정리해보겠습니다.

    리눅스 프로세스 비정상 종료 분석 흐름을 보여주는 서버 운영 다이어그램

    장애 발생부터 로그 수집, 원인 분석, 복구까지의 전체 흐름을 한눈에 보는 개요 이미지입니다.

    1. 왜 리눅스 프로세스 비정상 종료가 까다로운가

    처음 장애를 보면 보통 이렇게 생각합니다. “프로세스가 죽었네? 다시 올리면 되겠지.” 근데 여기서 끝내면 같은 문제가 또 터지더라고요. 쉽게 말해 리눅스 프로세스 비정상 종료는 결과일 뿐이고, 진짜 봐야 하는 건 그 직전의 시스템 상태입니다.

    • OOM Killer(Out Of Memory Killer, 메모리 부족 시 커널이 프로세스를 강제 종료하는 기능)가 개입했는지
    • SIGKILL, SIGSEGV, SIGABRT 같은 종료 신호가 있었는지
    • systemd가 재시작 정책으로 계속 덮어쓰고 있지는 않은지
    • 애플리케이션 로그와 커널 로그의 시간이 정확히 맞는지
    • 디스크 공간이나 inode(아이노드, 파일 메타데이터 구조)가 바닥난 건 아닌지

    여기서 중요한 포인트! 애플리케이션만 보면 절반만 보는 겁니다. 커널 로그, 서비스 매니저, 리소스 한계값까지 같이 봐야 퍼즐이 맞습니다.

    2. 개념부터 정리: 프로세스는 왜 비정상 종료될까요?

    저도 처음엔 헷갈렸는데, 원인을 큰 범주로 나누면 훨씬 수월합니다.

    2-1. 대표적인 종료 원인

    원인 설명 대표 징후
    메모리 부족 커널이 OOM Killer를 실행 dmesg에 kill process 기록
    세그멘테이션 폴트 잘못된 메모리 접근 Segmentation fault, core dumped
    강제 종료 시그널 운영자, 스크립트, 오케스트레이터가 종료 exit code 137, signal 9
    리소스 제한 초과 ulimit, nofile, nproc 초과 Too many open files
    스토리지 문제 디스크 full, inode 고갈, I/O 지연 write 실패, journal 오류
    권한/환경 문제 파일 권한, 환경 변수, 라이브러리 누락 permission denied, missing library

    2-2. 종료 코드(exit code)도 힌트입니다

    운영하다 보면 종료 코드 하나가 실마리가 되기도 합니다. 예를 들어 137은 보통 SIGKILL과 연결해서 보게 되고, 139는 Segmentation fault 가능성을 먼저 의심하게 되죠. 물론 이 숫자만 보고 단정하면 안 됩니다. 저는 항상 “종료 코드 확인 → journald 확인 → 커널 로그 확인” 순서로 갔습니다.

    3. 사례 연구: 새벽에 반복된 시스템 장애, 처음엔 앱 버그인 줄 알았습니다

    이번 사례 연구는 제가 홈랩에서 돌리던 API 서비스에서 실제로 겪은 패턴을 바탕으로 재구성한 내용입니다. 구조는 단순했습니다. Nginx(엔진엑스, 웹 서버) 뒤에 Python 기반 API가 있었고, systemd로 서비스 관리 중이었죠. 증상은 이랬습니다.

    1. 특정 시간대에 응답 지연이 먼저 발생했습니다.
    2. 이후 워커 프로세스가 하나씩 사라졌습니다.
    3. systemd가 자동 재시작했지만 잠시 뒤 다시 종료됐습니다.
    4. 모니터링에서는 CPU보다 메모리 사용량이 비정상적으로 치솟았습니다.

    처음엔 코드 메모리 누수(memory leak, 메모리를 반환하지 못하는 현상)인 줄 알았거든요. 근데 로그를 차근차근 맞춰보니, 원인이 하나가 아니었습니다. 삽질 좀 했습니다 ㅎㅎ

    4. 실전 분석 1단계: 로그 분석으로 타임라인부터 맞췄습니다

    장애 분석에서 제가 제일 먼저 하는 건 “시간축 맞추기”입니다. 여러 로그를 뒤섞어 보면 정신없는데, 같은 시각 기준으로 정렬하면 보이는 게 많아집니다.

    date
    uptime
    systemctl status myapi.service
    journalctl -u myapi.service --since "2026-07-01 00:00:00" --until "2026-07-01 03:00:00"
    journalctl -k --since "2026-07-01 00:00:00" --until "2026-07-01 03:00:00"
    

    여기서 제가 확인한 포인트는 세 가지였습니다.

    • 서비스 종료 시각과 커널 로그 시각이 맞는지
    • 재시작 직전의 에러 메시지가 있는지
    • 같은 시간대에 다른 시스템 이벤트가 있었는지

    실제로 써보니까 journalctl -u만 보면 부족한 경우가 많더라고요. journalctl -k로 커널 메시지를 같이 봐야 합니다.

    journalctl -u myapi.service -n 50
    journalctl -k -n 100 | egrep -i "killed process|oom|segfault|out of memory"
    dmesg -T | egrep -i "killed process|oom|segfault"
    
    리눅스 프로세스 비정상 종료 원인 분석을 위한 로그 분석 장면

    서비스 로그와 커널 로그를 나란히 비교하면서 장애 시점을 맞춰보는 분석 화면을 표현한 이미지입니다.

    5. 실전 분석 2단계: OOM, 파일 디스크립터, 디스크 상태를 함께 봤습니다

    로그를 보니 일단 OOM 메시지가 보였습니다. 그런데 거기서 끝이 아니더라고요. 왜 메모리가 찼는지, 다른 자원도 같이 무너졌는지를 확인해야 재발을 막을 수 있거든요.

    5-1. 메모리 상태 확인

    free -h
    vmstat 1 5
    cat /proc/meminfo | egrep "MemAvailable|SwapTotal|SwapFree"
    ps aux --sort=-%mem | head -20
    

    여기서 특정 워커 프로세스가 메모리를 비정상적으로 많이 먹는 걸 확인했습니다. 근데 또 하나, 스왑(swap, 메모리 부족 시 디스크를 임시 메모리처럼 쓰는 공간)이 사실상 여유가 없더라고요. 메모리 압박이 생기면 커널이 버티지 못하고 강제 종료로 넘어가더라고요.

    5-2. 파일 디스크립터와 프로세스 제한 확인

    ulimit -n
    cat /proc/$(pgrep -f myapi | head -1)/limits
    lsof -p $(pgrep -f myapi | head -1) | wc -l
    ss -tanp | head -20
    

    혹시 이런 경험 있으신가요? 메모리만 보고 있었는데 실제론 소켓(socket, 네트워크 연결 끝점)이 쌓여서 Too many open files가 먼저 터지는 경우요. 저도 예전에 이걸 놓쳐서 원인 분석을 반나절 더 했었습니다.

    5-3. 디스크와 inode 상태 확인

    df -h
    df -i
    iostat -xz 1 3
    

    로그 적재 서버에서는 디스크 공간보다 inode 고갈이 더 자주 문제였습니다. 파일이 너무 잘게 쪼개져 쌓이면 공간이 남아도 쓰기를 못 하거든요. 이 경우 프로세스가 로그 기록 실패 후 연쇄적으로 비정상 동작하는 경우도 있습니다.

    6. 원인 분석 결과: 단일 원인이 아니라 복합 장애였습니다

    분석 결과를 정리하면 이랬습니다.

    1. 배치 작업이 시작되면서 API 워커 메모리 사용량이 급증했습니다.
    2. 동시에 외부 요청이 몰리며 연결 수가 늘어났습니다.
    3. 애플리케이션의 로그 파일 회전(log rotation, 로그 파일 순환 관리)이 늦어져 쓰기 부하가 커졌습니다.
    4. 결국 커널이 OOM Killer를 실행해 워커 프로세스를 종료했습니다.

    즉, 표면적으로는 리눅스 프로세스 비정상 종료였지만, 실제 뿌리는 메모리 압박 + 연결 누적 + 로그 처리 지연의 조합이었습니다. 처음엔 앱 버그 하나만 의심했는데, 시스템 전반을 봐야 답이 나오더라고요.

    6-1. 제가 적용한 systemd 보완 설정

    [Unit]
    Description=My API Service
    After=network.target
    
    [Service]
    User=www-data
    Group=www-data
    WorkingDirectory=/srv/myapi
    ExecStart=/srv/myapi/venv/bin/gunicorn app:app --workers 2 --bind 0.0.0.0:8000
    Restart=on-failure
    RestartSec=5
    LimitNOFILE=65535
    TimeoutStopSec=30
    KillSignal=SIGTERM
    
    [Install]
    WantedBy=multi-user.target
    

    KillSignal과 LimitNOFILE 설정은 생각보다 중요합니다. 종료 시그널을 정리해두면 강제 종료 전에 정리 작업을 할 여지가 생기고, 파일 디스크립터 한계를 현실적으로 맞춰두면 예기치 않은 장애를 줄일 수 있습니다.

    리눅스 프로세스 비정상 종료 대응을 위한 systemd 설정 구성 이미지

    서비스 재시작 정책, 파일 디스크립터 제한, 종료 시그널 처리 지점을 정리한 구성 이미지입니다.

    7. ⚠️ 트러블슈팅: 실제로 자주 놓치는 포인트

    여기부터는 제가 많이 당했던 부분들입니다. 진짜 사소해 보여도 장애 때는 이런 게 치명적이더라고요.

    • 애플리케이션 로그만 보고 커널 로그를 안 보는 실수
      OOM이나 segfault는 앱 로그에 안 남는 경우가 많습니다.
    • 재시작 성공을 복구 완료로 착각하는 실수
      systemd가 살려놨을 뿐, 원인은 그대로일 수 있습니다.
    • exit code를 안 보는 실수
      137, 139 같은 숫자가 꽤 큰 힌트가 됩니다.
    • 모니터링 해상도가 너무 낮은 실수
      1분 단위 그래프만 보면 급격한 스파이크를 놓치기도 합니다.

    저는 이 문제 이후로 장애 체크리스트를 아예 만들어뒀습니다.

    #!/usr/bin/env bash
    set -eu
    
    echo "== service status =="
    systemctl status myapi.service --no-pager || true
    
    echo "== recent service logs =="
    journalctl -u myapi.service -n 50 --no-pager || true
    
    echo "== kernel errors =="
    journalctl -k -n 100 --no-pager | egrep -i "oom|killed process|segfault|error" || true
    
    echo "== memory =="
    free -h || true
    
    echo "== disk =="
    df -h || true
    df -i || true
    

    이런 식으로 기본 수집 스크립트를 만들어두면, 새벽 장애 때 멘탈이 덜 흔들립니다. 드디어 됐다! 싶었던 순간이 이 자동화 만든 뒤였어요.

    8. 검증과 결과: 재현, 완화, 모니터링까지 묶어야 끝입니다

    문제를 고친 뒤에는 반드시 검증해야 합니다. 저는 아래 순서로 확인했습니다.

    1. 동일 부하 조건에서 메모리 사용량이 다시 치솟는지 확인
    2. 서비스 재시작 없이 일정 시간 안정적으로 유지되는지 확인
    3. 로그 회전과 디스크 사용량이 정상 범위인지 확인
    4. 알람 임계치가 현실적으로 설정됐는지 확인
    systemctl daemon-reload
    systemctl restart myapi.service
    systemctl is-active myapi.service
    watch -n 2 'ps aux --sort=-%mem | head -10'
    

    결과적으로 워커가 반복 종료되던 현상은 멈췄고, 피크 시간대에도 응답이 훨씬 안정적으로 유지됐습니다. 무엇보다 좋았던 건, 다음번 비슷한 시스템 장애가 와도 어디부터 볼지 기준이 생겼다는 점입니다.

    리눅스 프로세스 비정상 종료 해결 후 안정화 결과 시각화

    장애 조치 후 메모리 사용량이 안정되고 프로세스 재시작이 줄어든 결과를 보여주는 검증 이미지입니다.

    9. 정리와 다음 단계: 리눅스 프로세스 비정상 종료 대응 체크리스트

    이번 경험에서 다시 느낀 건 하나입니다. 리눅스 프로세스 비정상 종료는 프로세스 하나의 문제가 아니라, 시스템 전체의 신호를 읽는 문제라는 점이죠. 저도 처음엔 “왜 죽었지?”만 붙잡고 있었는데, 지금은 “죽기 전에 시스템이 어떤 상태였지?”를 먼저 봅니다.

    • 1순위: 종료 시각과 로그 타임라인 정렬
    • 2순위: 커널 로그에서 OOM, segfault, I/O 오류 확인
    • 3순위: 메모리, 파일 디스크립터, 디스크 상태 점검
    • 4순위: 재시작 정책과 서비스 제한값 재검토
    • 5순위: 재현 테스트와 모니터링 임계치 보완

    마지막으로, 장애 원인을 찾았더라도 문서화는 꼭 해두세요. 다음 장애 때 팀 전체 속도가 완전히 달라집니다. 다음 글에서는 coredump(core dump, 비정상 종료 시 메모리 상태를 남긴 파일) 분석과 gdb 기반 세그폴트 추적 방법도 다뤄볼 예정입니다. 이전 글에서 다룬 systemd 서비스 운영 팁과 함께 보면 더 흐름이 잘 잡히실 거예요.

    자주 묻는 질문

    • Q. 프로세스가 죽었는데 앱 로그가 비어 있습니다.
      A. 커널 로그와 journalctl -k를 먼저 보세요. OOM이나 segfault는 앱 로그에 안 남을 수 있습니다.
    • Q. 재시작되면 괜찮은 것 아닌가요?
      A. 아닙니다. 자동 재시작은 증상 완화일 뿐이고, 원인 분석이 안 되면 반복됩니다.
    • Q. 가장 먼저 볼 명령어는 뭔가요?
      A. 보통 systemctl status, journalctl -u, journalctl -k, dmesg -T 조합이면 출발점으로 충분합니다.
    리눅스 프로세스 비정상 종료 대응 체크리스트 인포그래픽

    리눅스 프로세스 비정상 종료 대응 절차를 빠르게 복습할 수 있도록 정리한 요약 인포그래픽입니다.

  • [Linux] Linux Namespace 격리 기술, 실제 적용과 검증 방법

    [Linux] Linux Namespace 격리 기술, 실제 적용과 검증 방법

    Linux Namespace 격리 기술, 실제 적용과 검증 방법

    Linux Namespace는 요즘 컨테이너 기술 이야기할 때 거의 빠지지 않는 핵심 요소입니다. Docker를 쓰든, containerd를 쓰든, Kubernetes 위에서 파드를 띄우든 결국 밑바닥에는 이런 격리 기술이 깔려 있거든요. 근데 운영하다 보면 막연히 “컨테이너는 분리돼 있다” 정도로만 이해해서는 한계가 옵니다. 장애가 났을 때 왜 프로세스는 안 보이는지, 왜 네트워크가 따로 노는지, 왜 마운트가 호스트랑 다르게 보이는지 알아야 하더라고요.

    저도 처음엔 이게 뭔가 싶었습니다. 컨테이너는 많이 올려봤는데, 정작 Linux Namespace를 직접 까보지 않으면 문제 원인을 제대로 못 잡겠더라고요. 특히 홈랩에서 테스트할 때 네트워크 네임스페이스를 잘못 건드려서 통신이 안 붙고, 마운트 네임스페이스 때문에 파일이 보였다 안 보였다 해서 삽질 좀 했습니다 ㅎㅎ 이번 글에서는 이론만 나열하지 않고, Linux Namespace를 실제로 어떻게 적용하고 검증하는지, 그리고 현업이나 홈랩에서 어디에 유용한지 경험 기준으로 풀어보겠습니다.

    Linux Namespace 격리 구조와 컨테이너 기술 아키텍처 개요 이미지

    프로세스, 네트워크, 마운트가 각각 분리되는 구조를 한눈에 보여주는 개요 이미지입니다.

    1. 왜 Linux Namespace를 이해해야 하나요

    쉽게 말해 Linux Namespace는 커널(Kernel)이 프로세스에게 보여주는 세상을 따로 나누는 기능이거든요. 같은 서버 위에서 돌아가더라도 A 프로세스는 자기 PID 목록만 보고, B 프로세스는 자기 네트워크 인터페이스만 보게 만들 수 있습니다. 이게 컨테이너 기술의 출발점입니다.

    여기서 중요한 포인트! 가상머신(Virtual Machine)은 커널까지 통째로 분리하는 방식이고, Namespace는 같은 커널을 공유하면서 보이는 범위만 나누는 방식입니다. 그래서 더 가볍고 빠릅니다. 대신 보안 경계(Security Boundary)로 과신하면 안 됩니다. 저도 처음엔 “분리됐으니 완전히 안전하겠지”라고 생각했는데, 실제로 써보니 Namespace는 격리의 한 축일 뿐이고 cgroups, capabilities, seccomp 같은 요소랑 같이 봐야 하더라고요.

    2. Linux Namespace 핵심 개념 정리

    대표적인 Namespace는 아래처럼 이해하시면 됩니다.

    종류 영문 무엇을 격리하나 현실적인 사용 예
    PID Process ID Namespace 프로세스 번호와 프로세스 트리 컨테이너 내부에서 1번 프로세스만 보이게 구성
    NET Network Namespace 네트워크 인터페이스, 라우팅, 포트 컨테이너별 가상 NIC와 독립 라우팅
    MNT Mount Namespace 마운트 지점과 파일시스템 뷰 호스트와 다른 루트 파일시스템 제공
    UTS UNIX Timesharing System Namespace 호스트명, 도메인명 컨테이너별 hostname 분리
    IPC Inter-Process Communication Namespace 공유 메모리, 세마포어, 메시지 큐 프로세스 간 IPC 격리
    USER User Namespace UID/GID 매핑 컨테이너 root를 호스트 비특권 사용자로 매핑

    한 줄로 줄이면 이렇습니다. Linux Namespace는 프로세스가 보는 시스템 자원을 논리적으로 쪼개는 장치거든요. 컨테이너 런타임은 이걸 조합해서 “내 것처럼 보이지만 사실은 제한된 환경”을 만들어냅니다.

    3. 실제 적용 사례: 컨테이너 기술에서 어떻게 쓰이나요

    가장 흔한 사례는 역시 Docker나 containerd 같은 런타임입니다. 예를 들어 웹 애플리케이션 컨테이너 하나를 띄운다고 해보죠. 그 안의 애플리케이션은 자기 PID 공간만 보고, 자기 네트워크 인터페이스만 보고, 자기 루트 파일시스템만 봅니다. 사용자는 그냥 컨테이너가 하나 올라간 걸로 보지만, 내부적으로는 여러 Namespace가 같이 엮여 있죠.

    제가 홈랩에서 많이 해보는 패턴은 이렇습니다.

    1. 리버스 프록시(Reverse Proxy) 컨테이너는 외부와 붙게 둡니다.
    2. 백엔드 애플리케이션 컨테이너는 별도 네트워크 네임스페이스에 둡니다.
    3. 로그 수집기나 모니터링 에이전트는 필요한 마운트만 노출합니다.
    4. 가능하면 User Namespace까지 고려해서 호스트 권한을 줄입니다.

    이 방식이 왜 좋냐면, 장애 범위가 줄어듭니다. 어떤 애플리케이션이 오작동해서 내부 프로세스를 마구 생성하더라도, 적어도 다른 워크로드의 프로세스 공간까지 바로 오염시키지는 않거든요. 물론 이것만으로 충분하진 않지만, 운영 안정성에서 체감 차이가 꽤 큽니다.

    4. 실전 구현: unshare로 Namespace 직접 만들기

    이제 이론 말고 직접 보겠습니다. 저는 Namespace를 설명할 때 항상 <code>unshare부터 보여드립니다. 이걸 한 번 손으로 쳐보면 감이 확 옵니다. 아래 예시는 PID, UTS, Mount Namespace를 분리해서 새 쉘을 띄우는 방식입니다.

    sudo unshare --fork --pid --mount --uts /bin/bash

    이 상태에서 호스트명도 바꿔보겠습니다.

    hostname ns-lab
    hostname
    ps -ef

    여기서 ps -ef를 보면 호스트 전체 프로세스가 아니라 새 Namespace 기준의 프로세스만 보여집니다. 처음 보면 “어? 왜 이렇게 적지?” 싶거든요. 그게 정상입니다.

    마운트도 분리해보죠.

    mount -t proc proc /proc
    mount | grep proc

    Mount Namespace를 분리한 뒤 /proc를 다시 마운트하지 않으면 프로세스 정보가 기대와 다르게 보일 수 있습니다. 이 부분에서 많이 헷갈립니다. 저도 예전에 “PID Namespace가 안 먹었나?” 하고 한참 봤었는데, 알고 보니 /proc를 새로 안 붙였던 거였습니다.

    Linux Namespace 실습에서 PID와 hostname 분리를 확인하는 터미널 이미지

    실습 중간에 확인해야 하는 핵심 명령과 분리된 PID 공간이 보이는 터미널 예시입니다.

    4-1. 네트워크 Namespace 실습

    네트워크는 조금 더 재미있습니다. 별도 네임스페이스를 만들고 veth pair(가상 이더넷 쌍)로 연결하면 거의 미니 컨테이너 네트워크처럼 테스트할 수 있죠.

    sudo ip netns add ns1
    sudo ip link add veth-host type veth peer name veth-ns
    sudo ip link set veth-ns netns ns1
    sudo ip addr add 10.10.10.1/24 dev veth-host
    sudo ip link set veth-host up
    sudo ip netns exec ns1 ip addr add 10.10.10.2/24 dev veth-ns
    sudo ip netns exec ns1 ip link set lo up
    sudo ip netns exec ns1 ip link set veth-ns up
    sudo ip netns exec ns1 ping -c 3 10.10.10.1

    이 실습이 좋은 이유는 컨테이너 기술의 네트워크 격리 원리를 거의 그대로 보여주기 때문입니다. 브리지(Bridge), NAT, 포트 포워딩까지 붙이면 Docker 네트워크 구조를 훨씬 잘 이해하게 됩니다.

    4-2. 간단한 프로세스 격리 확인용 C 코드

    시스템 프로그래밍 관점에서 보면 clone() 계열 시스템 콜로 Namespace를 만드는 흐름을 이해하는 것도 중요합니다. 아래 코드는 개념 확인용으로 아주 단순화한 예시입니다.

    #define _GNU_SOURCE
    #include <sched.h>
    #include <sys/wait.h>
    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>
    
    #define STACK_SIZE 1024 * 1024
    
    static int child_func(void *arg) {
        sethostname("ns-child", 8);
        system("hostname; ps -ef");
        return 0;
    }
    
    int main() {
        char *stack = malloc(STACK_SIZE);
        if (!stack) return 1;
    
        pid_t pid = clone(child_func, stack + STACK_SIZE, CLONE_NEWUTS | CLONE_NEWPID | SIGCHLD, NULL);
        if (pid == -1) return 1;
    
        waitpid(pid, NULL, 0);
        free(stack);
        return 0;
    }

    실무에서 직접 이런 코드를 매번 짜진 않지만, 런타임이 내부적으로 어떤 일을 하는지 이해하는 데는 꽤 도움이 됩니다.

    5. 주의사항과 트러블슈팅: 여기서 많이 막힙니다

    실제로 써보니 Linux Namespace는 개념보다 디버깅이 더 중요하더라고요. 아래는 제가 자주 겪었던 문제들입니다.

    • ⚠️ /proc 재마운트 누락: PID Namespace를 만들었는데 프로세스가 이상하게 보이면 먼저 확인하세요.
    • ⚠️ loopback 미활성화: Network Namespace 안에서 lo를 올리지 않으면 localhost 통신이 어색하게 깨집니다.
    • ⚠️ 권한 문제: User Namespace를 섞으면 UID/GID 매핑 때문에 파일 접근이 예상과 다를 수 있습니다.
    • ⚠️ 네트워크 라우팅 누락: veth만 연결해놓고 라우팅이나 NAT를 안 잡으면 외부 통신이 안 됩니다.

    특히 네트워크 부분은 진짜 많이 삽질합니다. 저는 예전에 “분명 인터페이스는 올라왔는데 왜 패킷이 안 나가지?” 하고 tcpdump만 한참 봤거든요. 알고 보니 호스트 쪽 IP 포워딩(IP Forwarding)이 빠져 있었습니다. 그래서 아래처럼 확인하는 습관을 들였습니다.

    sysctl net.ipv4.ip_forward
    ip netns exec ns1 ip route
    ip addr show veth-host

    보안 측면에서도 한 가지 강조하고 싶습니다. Namespace는 강력하지만 만능은 아니거든요. 격리 기술이라고 해서 무조건 안전한 게 아니고, 커널 취약점이나 잘못된 capability 설정이 있으면 경계가 흐려질 수 있습니다. 그래서 현업에서는 보통 cgroups, seccomp, AppArmor 또는 SELinux 같은 보안 메커니즘과 함께 갑니다.

    Linux Namespace 기반 네트워크 격리와 veth 연결 구성도

    호스트와 네임스페이스가 veth로 연결되고 패킷이 흐르는 경로를 이해하기 위한 구성도입니다.

    6. 검증과 결과 확인: 분리됐는지 어떻게 보나

    설정만 해놓고 끝내면 안 됩니다. 반드시 검증해야 합니다. 저는 보통 아래 순서로 봅니다.

    1. 프로세스가 분리됐는지 ps, /proc로 확인합니다.
    2. 호스트명이 분리됐는지 hostname으로 확인합니다.
    3. 네트워크 인터페이스가 분리됐는지 ip addr로 확인합니다.
    4. 마운트 뷰가 다른지 mount 출력으로 비교합니다.
    5. 필요하면 네임스페이스 핸들을 lsns로 확인합니다.
    lsns
    sudo ip netns exec ns1 ip addr
    sudo ip netns exec ns1 hostname
    sudo ip netns exec ns1 ps -ef

    이렇게 보면 각 격리가 실제로 먹었는지 금방 드러납니다. lsns는 운영 중 문제 파악할 때 꽤 유용하더라고요. 어떤 프로세스가 어떤 Namespace에 속했는지 감을 잡는 데 도움이 되거든요.

    실무 관점의 결과를 정리하면 이렇습니다.

    검증 항목 성공 시 기대 결과 실패 시 의심 포인트
    PID 분리 내부 프로세스만 보임 /proc 재마운트 누락
    hostname 분리 Namespace 내부 이름만 변경됨 UTS 미분리
    네트워크 분리 별도 인터페이스/라우팅 표시 veth 연결, lo 활성화 누락
    마운트 분리 호스트와 다른 마운트 목록 Mount Namespace 미적용
    Linux Namespace 적용 결과를 검증하는 운영 확인 화면

    분리된 Namespace가 실제로 적용됐는지 확인하는 검증 결과 예시 이미지입니다.

    7. 실제 운영에서 어디에 써먹나

    혹시 이런 경험 있으신가요? 개발팀은 “컨테이너니까 독립적이다”라고 생각하는데, 운영팀은 “그래도 결국 같은 커널인데?”라는 불안이 남습니다. 둘 다 맞는 말이거든요. 그래서 Namespace를 이해하면 커뮤니케이션이 훨씬 쉬워집니다.

    제가 현장에서 보거나 직접 적용해본 패턴은 대체로 이렇습니다.

    • 멀티테넌트(Multi-tenant) 환경에서 워크로드 간 기본 격리
    • CI 러너나 빌드 작업의 프로세스/파일시스템 분리
    • 네트워크 실험 환경 구성과 장애 재현
    • 보안 테스트용 최소 권한 실행 환경 만들기

    특히 홈랩에서는 이게 정말 좋습니다. VM 여러 대 띄우기 부담스러울 때 Namespace 기반으로 작은 실험 환경을 빠르게 만들 수 있거든요. 물론 커널 공유라는 특성 때문에 VM을 완전히 대체하진 못하지만, 문제 재현과 구조 학습에는 진짜 편하더라고요.

    8. 정리와 다음 단계

    Linux Namespace를 이해하면 컨테이너가 갑자기 훨씬 덜 추상적으로 보입니다. 그냥 “신기한 배포 단위”가 아니라, PID Namespace, Network Namespace, Mount Namespace 같은 커널 기능의 조합으로 보이기 시작하거든요. 저도 처음엔 Docker 명령만 외웠는데, 바닥 기술을 알고 나니 장애 대응 속도가 확실히 빨라졌습니다. 드디어 됐다! 싶은 순간이 오더라고요.

    오늘 정리한 핵심만 다시 적어보면 이렇습니다.

    • Linux Namespace는 프로세스가 보는 시스템 자원을 분리합니다.
    • 컨테이너 기술은 여러 Namespace를 조합해 가벼운 실행 환경을 만듭니다.
    • 실전에서는 네트워크, 마운트, 권한 매핑에서 가장 많이 막힙니다.
    • 검증 명령을 반드시 함께 익혀야 운영에서 써먹을 수 있습니다.

    다음 단계로는 cgroups와 함께 보면 좋습니다. Namespace가 “보이는 범위”를 나눈다면, cgroups는 CPU와 메모리 같은 자원 사용량을 통제하거든요. 다음 글에서 다룰 예정입니다. 그리고 이전 글에서 컨테이너 런타임 구조를 정리했다면 같이 보시면 연결이 더 잘 됩니다.

    Linux Namespace 종류와 실제 적용 포인트를 정리한 요약 인포그래픽

    PID, NET, MNT 등 주요 Namespace와 운영 포인트를 빠르게 복습할 수 있는 요약 이미지입니다.

    9. 자주 묻는 질문

    Q1. Namespace만 쓰면 컨테이너를 직접 만든 셈인가요?

    완전히 그렇진 않습니다. Namespace는 핵심 구성요소지만, 실제 컨테이너 환경에는 cgroups, 루트 파일시스템, capability 제어, 보안 정책 등이 함께 필요하죠.

    Q2. Docker를 쓰는데도 Namespace를 따로 알아야 하나요?

    네, 운영이나 장애 대응을 생각하면 알아두는 게 좋습니다. 특히 네트워크 안 붙음, 파일 안 보임, 프로세스 이상 동작 같은 문제는 바닥 기술을 이해해야 빨리 풀린다고 봅니다.

    Q3. User Namespace는 꼭 써야 하나요?

    상황에 따라 다릅니다. 보안상 이점은 분명하지만, 권한 매핑과 파일 접근에서 복잡도가 올라갑니다. 운영 환경에서는 호환성과 보안 요구사항을 함께 봐야 합니다.

  • [Linux] cgroup vs namespace: 컨테이너 격리의 핵심 비교 분석

    [Linux] cgroup vs namespace: 컨테이너 격리의 핵심 비교 분석

    [리눅스 커널] cgroup vs namespace: 컨테이너 격리 핵심 비교

    컨테이너 기술을 조금만 깊게 파보면 결국 cgroup vs namespace 이야기로 귀결되더라고요. 저도 처음 Docker를 쓸 때는 그냥 “컨테이너는 가볍고 빠르다” 정도로만 이해했었는데, 실제로 장애를 따라가다 보니 왜 어떤 프로세스는 CPU를 독식하고, 왜 어떤 프로세스는 다른 프로세스 목록을 못 보는지 여기서 갈리더라고요. 쉽게 말해 namespace(네임스페이스, 리소스를 보이는 범위로 분리)는 “무엇을 볼 수 있느냐”를 나누고, cgroup(컨트롤 그룹, 자원 사용량을 제어하는 기능)은 “얼마나 쓸 수 있느냐”를 관리합니다. 이 차이를 이해하면 컨테이너 기술, 격리, 리눅스 커널의 구조가 한 번에 정리됩니다.

    실제로 홈랩에서 테스트 환경을 만들다가, 프로세스는 잘 분리됐는데 메모리 제한이 안 먹어서 삽질 좀 했습니다 ㅎㅎ 그때 확실히 느꼈어요. 컨테이너 격리는 한 가지 기능으로 되는 게 아니라, 여러 커널 기능을 조합해서 만들어지는 거였거든요.

    cgroup vs namespace 기반 컨테이너 격리 구조를 보여주는 리눅스 커널 아키텍처 이미지

    namespace와 cgroup이 각각 어떤 역할로 컨테이너 격리를 만드는지 보여주는 개요 이미지입니다.

    1. 왜 cgroup과 namespace를 같이 봐야 할까요?

    여기서 중요한 게 있어요. 많은 분들이 컨테이너를 “가벼운 VM”처럼 이해하시는데, 엄밀히 보면 다릅니다. VM은 하이퍼바이저 위에 게스트 OS가 따로 올라가고, 컨테이너는 호스트의 리눅스 커널을 공유합니다. 그러니 격리도 커널 기능에 의존할 수밖에 없죠.

    • namespace: 프로세스가 보는 세계를 분리합니다.
    • cgroup: 프로세스가 쓰는 자원을 제어합니다.
    • capabilities(리눅스 권한 비트 분리), seccomp(시스템 콜 필터링), LSM 계열 기능은 추가 보안 계층으로 붙습니다.

    즉, 컨테이너 기술에서 격리는 한 방에 끝나는 개념이 아니라 층층이 쌓이는 구조예요. 저도 처음엔 namespace만 알면 다 되는 줄 알았는데, 운영 환경에서는 cgroup 쪽을 훨씬 자주 보게 되더라고요. 특히 CPU 폭주나 메모리 OOM(Out Of Memory) 이슈는 거의 cgroup 쪽에서 원인을 찾게 됩니다.

    2. namespace(네임스페이스) 쉽게 이해하기

    쉽게 말해 namespace는 “같은 커널 위에 있지만 서로 다른 방에 들어가 있는 상태”를 만드는 기능입니다. 같은 호스트인데도 프로세스마다 보이는 PID, 네트워크 인터페이스, 마운트 포인트가 달라질 수 있어요.

    대표적인 namespace 종류

    • PID namespace: 프로세스 번호 공간을 분리합니다.
    • NET namespace: 네트워크 인터페이스, 라우팅 테이블, 포트를 분리합니다.
    • MNT namespace: 마운트 지점을 분리합니다.
    • UTS namespace: 호스트명, 도메인명 같은 시스템 식별 정보를 분리합니다.
    • IPC namespace: 프로세스 간 통신 자원을 분리합니다.
    • USER namespace: 사용자와 그룹 ID 매핑을 분리합니다.

    예를 들어 PID namespace 안에서는 프로세스가 자기 자신을 PID 1로 볼 수도 있어요. 컨테이너 안에서 <code>ps를 쳤을 때 호스트 전체 프로세스가 안 보이는 이유가 바로 이겁니다. 실제로 써보니까 이 개념 하나만 이해해도 “왜 컨테이너 안에서는 세상이 작아 보이는지”가 확 들어오더라고요.

    3. cgroup(컨트롤 그룹)은 뭐가 다를까요?

    namespace가 시야를 나눈다면, cgroup은 행동 한도를 정합니다. CPU를 얼마나 쓸지, 메모리를 얼마나 먹을지, I/O를 어느 정도까지 허용할지를 관리하는 쪽이죠. 그래서 cgroup은 격리라기보다 제어와 회계(accounting) 관점이 훨씬 강합니다.

    운영에서 흔히 보는 상황이 이거예요. 애플리케이션 하나가 메모리를 계속 먹습니다. namespace만 있으면 자기 공간 안에서만 보일 뿐이지, 호스트 메모리는 그대로 압박할 수 있거든요. 반대로 cgroup으로 제한을 걸어두면 그 프로세스 그룹은 정해진 범위를 넘기기 어려워집니다. 장애 대응할 때 이 차이가 꽤 크더라고요.

    항목 namespace cgroup
    주요 목적 리소스 가시성 분리 자원 사용량 제어 및 계측
    핵심 질문 무엇을 볼 수 있나? 얼마나 쓸 수 있나?
    예시 다른 PID/네트워크를 못 봄 CPU, 메모리 사용량 제한
    영향 범위 프로세스 관점의 논리적 격리 호스트 자원 배분 정책
    컨테이너에서의 역할 독립된 환경처럼 보이게 함 과도한 자원 사용을 막음

    한 줄 정리: namespace만 있으면 “따로 있는 것처럼 보이는 상태”이고, cgroup까지 있어야 “민폐를 덜 끼치는 상태”가 돼요. 이거 진짜 중요합니다.

    4. 실전 구현: namespace부터 직접 확인해보겠습니다

    제가 직접 해보니 추상 개념으로만 볼 때보다 unshare로 눈으로 확인하는 게 훨씬 빠르더라고요. 아래 예시는 리눅스에서 새로운 UTS, PID, mount namespace를 만들어보는 흐름입니다.

    1. 현재 호스트 이름과 프로세스 상태를 확인합니다.
    2. unshare로 새 namespace를 만듭니다.
    3. 새 환경 안에서 hostname과 프로세스 목록이 어떻게 달라지는지 봅니다.
    hostname
    ps -ef | head
    
    sudo unshare --uts --pid --mount --fork bash
    
    hostname container-lab
    mount -t proc proc /proc
    ps -ef
    hostname

    여기서 --fork를 주는 이유는 새 PID namespace 안에서 새 프로세스를 시작하기 위해서예요. 그리고 mount -t proc proc /proc를 안 하면 ps 출력이 기대와 다를 수 있거든요. 저도 처음엔 이걸 빼먹어서 “왜 분리 안 됐지?” 하고 한참 봤었습니다.

    cgroup vs namespace 실습 중 namespace 분리 셸 구성을 표현한 이미지

    namespace 실습 흐름과 분리된 셸 진입 과정을 보여주는 이미지입니다.

    현재 시스템의 namespace 확인

    lsns

    lsns를 보면 현재 시스템에 어떤 namespace가 잡혀 있는지 확인할 수 있어요. 컨테이너 런타임이 떠 있는 서버에서는 생각보다 다양한 namespace가 보이더라고요.

    5. 실전 구현: cgroup으로 자원 제한 걸어보기

    이제 cgroup입니다. 최근 리눅스 배포판은 보통 cgroup v2 기준으로 보는 게 편해요. 시스템마다 마운트 구조가 조금 다를 수는 있지만, 기본 개념은 비슷합니다. CPU와 메모리 제한을 설정할 수 있고, 프로세스를 특정 cgroup 디렉터리 아래로 넣어 관리하죠.

    mount | grep cgroup
    cat /sys/fs/cgroup/cgroup.controllers
    
    sudo mkdir /sys/fs/cgroup/demo-limit
    echo $$ | sudo tee /sys/fs/cgroup/demo-limit/cgroup.procs

    위처럼 현재 셸을 특정 cgroup에 넣을 수 있어요. 다만 실제 제한을 적용하려면 컨트롤러 활성화와 권한 구조를 이해해야 해서, 운영 서버에서는 systemd 기반 도구를 쓰는 편이 안전합니다.

    systemd-run으로 메모리 제한 테스트

    systemd-run --user --scope -p MemoryMax=200M -p CPUQuota=50% bash
    
    cat /sys/fs/cgroup/$(systemctl --user show --property=ControlGroup --value)/memory.max

    환경에 따라 사용자 세션에서 안 될 수도 있어요. 그런 경우는 시스템 범위에서 실행하거나 테스트 VM에서 보는 쪽이 낫습니다. 핵심은 cgroup이 프로세스를 안 보이게 만드는 기능이 아니라, 자원 사용 정책을 강제하는 기능이라는 점이에요.

    Docker로 보면 훨씬 익숙해요.

    docker run --rm -it --memory=256m --cpus=1 alpine sh
    
    cat /sys/fs/cgroup/memory.max 2>/dev/null || true
    cat /sys/fs/cgroup/cpu.max 2>/dev/null || true

    컨테이너 런타임은 이런 옵션을 받아 내부적으로 cgroup 설정과 namespace 구성을 함께 처리해요. 우리가 편하게 docker run 한 줄로 쓰는 뒤에서 리눅스 커널이 꽤 많은 일을 하는 셈이죠.

    6. cgroup vs namespace 비교를 실무 관점에서 정리해보면

    혹시 이런 경험 있으신가요? 컨테이너는 분명 분리돼 있는데, 한 컨테이너가 CPU를 잡아먹어서 같은 노드의 다른 워크로드까지 느려지는 상황 말이에요. 이럴 때 namespace를 아무리 잘 나눠도 해결이 안 됩니다. 반대로 cgroup만 있고 namespace가 약하면 프로세스 시야가 너무 넓어서 격리 체감이 떨어져요.

    • namespace가 강한 상황: 프로세스, 네트워크, 파일시스템 관점에서 독립된 환경처럼 보입니다.
    • cgroup이 강한 상황: noisy neighbor(시끄러운 이웃, 자원 독식 워크로드) 문제를 줄이기 좋습니다.
    • 둘 다 필요: 컨테이너 기술에서 실용적인 격리는 대부분 둘의 조합이에요.

    제가 홈랩 쿠버네티스 환경을 만질 때도 결국 보는 건 두 가지였어요. “이 파드는 무엇을 볼 수 있지?” 그리고 “이 파드는 얼마나 먹지?” 질문이 딱 namespace와 cgroup으로 나뉘더라고요.

    7. ⚠️ 주의사항과 트러블슈팅

    여기서부터는 제가 실제로 많이 헷갈렸던 부분입니다. 처음엔 개념보다 환경 차이 때문에 더 막히더라고요.

    1) ps 결과가 기대와 다를 때

    PID namespace를 만들었는데도 프로세스가 그대로 보이는 경우가 있어요. 보통 /proc를 새로 마운트하지 않아서 그렇습니다.

    mount -t proc proc /proc

    2) cgroup 디렉터리는 있는데 제한이 안 걸릴 때

    cgroup v1과 v2 차이, systemd 관리 방식, 권한 문제 때문일 수 있어요. 특히 배포판마다 기본 구성이 다르니 무작정 예전 블로그 예제를 복붙하면 안 맞는 경우가 있습니다. 저도 예전 자료 따라 했다가 파일 이름이 달라서 한참 헤맸습니다.

    3) 컨테이너 보안과 자원 제어를 같은 문제로 보면 꼬여요

    이건 실무에서 꽤 중요한 부분이에요.

    • namespace는 보이는 범위를 줄이는 쪽입니다.
    • cgroup은 성능 안정성과 자원 관리 쪽입니다.
    • seccomp, AppArmor, SELinux 같은 보안 계층은 또 별개입니다.

    즉, 보안 사고를 막는 이야기와 리소스 폭주를 막는 이야기를 한 덩어리로 보면 판단이 흐려집니다.

    4) USER namespace는 특히 신중하게 봐야 해요

    USER namespace는 루트 권한 매핑 문제와 연결되기 때문에 환경에 따라 정책 차이가 커요. 실습은 가능하지만, 운영 서버에서는 배포판 기본 정책과 런타임 설정을 반드시 같이 봐야 합니다.

    cgroup vs namespace 비교 중 cgroup v2 자원 제어 구조를 설명하는 이미지

    cgroup v2 구조와 자원 제한이 적용되는 핵심 지점을 시각화한 이미지입니다.

    8. 검증과 결과 확인

    설정은 했는데 실제로 격리가 됐는지 확인을 안 하면 의미가 없죠. 저는 보통 아래처럼 확인합니다.

    1. namespace 분리 여부 확인
    2. cgroup 제한 값 확인
    3. 실제 워크로드를 넣어 동작 확인
    lsns
    
    cat /proc/self/cgroup
    
    cat /sys/fs/cgroup/cpu.max 2>/dev/null || true
    cat /sys/fs/cgroup/memory.max 2>/dev/null || true

    Docker 환경이라면 다음도 자주 봐요.

    docker inspect <container_id>
    docker stats

    검증 포인트는 단순합니다.

    • 프로세스 목록이 호스트와 다르게 보이는가?
    • 호스트명이나 네트워크 인터페이스가 분리되어 보이는가?
    • 메모리/CPU 제한이 파일 시스템이나 런타임 정보에 반영되는가?
    • 부하를 줬을 때 제한 정책이 실제로 동작하는가?

    실제로 써보니까, 격리는 “설정했다”보다 “증명했다”가 더 중요하더라고요. 특히 장애 분석 문서 남길 때 이 확인 단계가 있어야 나중에 팀원과 같은 그림을 볼 수 있어요.

    cgroup vs namespace 검증 결과를 운영 화면처럼 보여주는 이미지

    격리 검증 결과를 확인하는 장면을 담은 이미지입니다.

    9. 정리: 컨테이너 기술에서 둘 중 뭐가 더 중요할까요?

    제 답은 늘 같습니다. 둘 중 하나가 아니라 둘 다예요. namespace는 컨테이너를 컨테이너답게 보이게 만들고, cgroup은 컨테이너를 운영 가능한 상태로 만들어줍니다. 하나는 시야를 분리하고, 다른 하나는 자원을 통제하죠.

    질문 봐야 할 기능 실무 해석
    왜 다른 프로세스가 안 보일까? namespace 격리된 실행 환경
    왜 메모리가 256MB 이상 못 올라갈까? cgroup 자원 제한 정책
    왜 한 컨테이너가 노드 전체를 먹지 못할까? cgroup 멀티테넌시 안정성
    왜 컨테이너 안 hostname이 다를까? namespace 독립된 시스템처럼 보이기

    저도 처음엔 cgroup vs namespace를 경쟁 관계처럼 외웠었는데, 실제로는 역할 분담에 더 가깝거든요. 그래서 컨테이너 기술을 공부할 때는 둘을 따로 암기하기보다, 하나의 워크로드가 커널 위에서 어떻게 분리되고 제한되는지 흐름으로 보는 게 훨씬 이해가 잘 됩니다.

    다음 글에서는 seccomp(시스템 콜 필터링)와 capabilities(권한 비트 세분화)까지 이어서, 왜 “컨테이너는 격리되지만 완전한 보안 경계로 보면 안 된다”는 말이 나오는지 다뤄볼 예정입니다. 이전 글에서 다룬 Docker 기본 구조와 함께 보시면 더 잘 연결되실 겁니다.

    cgroup vs namespace 차이를 한눈에 정리한 비교 인포그래픽 이미지

    cgroup과 namespace의 핵심 차이를 요약한 비교 인포그래픽 이미지입니다.

    자주 묻는 질문(FAQ)

    Q1. namespace만 있으면 컨테이너가 완성되나요?

    아니에요. namespace만으로는 자원 제한이 부족합니다. 실무에서는 cgroup, capabilities, seccomp 같은 요소가 함께 들어갑니다.

    Q2. cgroup은 보안 기능인가요?

    주된 목적은 자원 제어와 계측이에요. 보안에 간접적으로 도움이 될 수는 있지만, 보안 격리 그 자체로 보기엔 부족합니다.

    Q3. 리눅스 커널을 공유하면 위험하지 않나요?

    맞아요. 그래서 컨테이너는 VM과 다른 보안 모델을 가져요. 격리 수준과 운영 목적을 구분해서 설계해야 합니다.

    Q4. 쿠버네티스에서도 결국 같은 원리인가요?

    네, 상위 오케스트레이션이 붙어도 아래에서는 리눅스 커널의 namespace와 cgroup 같은 기능을 활용합니다.

  • [Linux] systemd timer vs Cron: 리눅스 작업 스케줄러 성능 및 자원 비교

    [Linux] systemd timer vs Cron: 리눅스 작업 스케줄러 성능 및 자원 비교

    [리눅스] systemd timer vs Cron 비교: 작업 스케줄러 성능 및 자원 사용량

    리눅스 서버를 오래 굴리다 보면 결국 한 번은 붙잡게 되는 주제가 있습니다. 바로 systemd timer Cron 비교입니다. 백업, 로그 정리, 캐시 삭제, 인증서 갱신 같은 작업 자동화는 작아 보여도 장애를 막는 핵심 축이거든요. 저도 처음엔 Cron(크론, 전통적인 작업 스케줄러)만 익숙해서 그냥 crontab부터 열었었는데, systemd timer(시스템디 타이머, systemd 기반 스케줄링)는 또 다른 장점이 분명하더라고요. 특히 서비스 단위 관리, 로그 추적, 의존성 처리에서 차이가 꽤 크거든요.

    이번 글은 단순 기능 소개가 아니라, 리눅스 스케줄러를 실제 운영 관점에서 어떻게 비교해야 하는지, 그리고 성능 벤치마크를 할 때 무엇을 봐야 하는지에 초점을 맞췄습니다. 숫자를 억지로 꾸며 넣는 대신, 제가 홈랩에서 비교할 때 사용한 방식과 해석 포인트를 정리해보겠습니다. 혹시 스케줄러 바꿨다가 로그 찾느라 삽질해보신 적 있으신가요? 그 마음 제가 잘 압니다 ㅎㅎ

    systemd timer Cron 비교를 보여주는 리눅스 스케줄링 개요 다이어그램

    systemd timer와 Cron이 각각 어떤 흐름으로 작업을 실행하는지 보여주는 개요 이미지입니다.

    1. 왜 systemd timer vs Cron 비교가 중요한가

    쉽게 말해 Cron은 시간이 되면 명령어를 실행하는 데 특화되어 있고, systemd timer는 서비스 단위로 작업을 관리하는 데 강하죠. 둘 다 예약 실행은 되지만, 운영에서 중요한 건 그 다음입니다. 실패했을 때 어디서 로그를 볼지, 부팅 직후 누락된 작업을 보정할지, 프로세스 제한을 걸 수 있는지, 다른 서비스가 올라온 뒤에만 실행할지 같은 부분이요.

    • Cron: 단순하고 가볍고 쓰기 편하죠.
    • systemd timer: 추적성과 제어성이 좋거든요.
    • 운영 포인트: 성능 차이보다 관리 편의성 차이가 더 크게 느껴지는 경우가 많습니다.

    여기서 중요한 포인트! 많은 분들이 systemd가 무조건 무겁다고 생각하시는데, 실제로는 작업 자체의 비용보다 어떻게 프로세스를 감싸고 기록하느냐의 차이예요. 이 부분을 제대로 이해하면 선택이 훨씬 쉬워집니다.

    2. 개념 설명: Cron과 systemd timer를 쉽게 말해보면

    Cron은 오래된 표준입니다. <code>* * * * * 같은 표현으로 시간을 적고 명령을 실행하죠. 반면 systemd timer는 .service 파일과 .timer 파일을 분리해서 써요. 처음엔 이게 뭔가 싶었는데, 실제로 써보니까 역할이 나뉘어 있어서 나중에 유지보수할 때 편하더라고요.

    항목 Cron systemd timer
    설정 방식 crontab 한 줄 .service + .timer 파일
    로그 확인 메일 또는 리다이렉션 필요 journalctl(저널ctl, systemd 로그 조회)로 추적 가능
    의존성 처리 제한적 After=, Wants= 등으로 제어 가능
    누락 실행 보정 기본적으로 약함 Persistent=true 지원
    리소스 제어 쉘 수준 처리 위주 CPUQuota=, MemoryMax= 등 cgroup 기반 제어 가능
    진입 장벽 낮음 초반 학습 필요

    systemd timer Cron 비교를 할 때 핵심은 “무엇이 더 빠른가” 하나만 보면 안 된다는 점입니다. 실행 지연(latency, 지연 시간), 프로세스 생성 오버헤드, 로그 추적성, 실패 복구성까지 같이 봐야 제대로 판단할 수 있거든요.

    3. 벤치마크 기준: 성능 및 자원 비교는 무엇을 봐야 하나

    성능 벤치마크라고 하면 보통 처리량부터 떠올리는데, 스케줄러는 조금 달라요. 작업 자동화에서는 아래 기준이 더 실무적입니다.

    1. 실행 정확성: 예약한 시점에 얼마나 정확하게 시작되는가
    2. 오버헤드: 짧은 작업을 실행할 때 추가 비용이 얼마나 붙는가
    3. 로그 가시성: 실패 원인을 얼마나 빨리 찾을 수 있는가
    4. 자원 사용량: 메모리, 프로세스 수, cgroup 제어 가능 여부
    5. 운영 편의성: 배포, 수정, 재시작, 권한 관리가 쉬운가

    제가 직접 체크할 때는 아주 짧은 작업 하나와, 조금 긴 백업성 작업 하나를 나눠서 봅니다. 왜냐하면 짧은 작업에서는 스케줄러 자체의 오버헤드가 더 눈에 띄고, 긴 작업에서는 스케줄러보다 실제 작업 로직이 훨씬 큰 비중을 차지하거든요.

    💡 팁: 성능 벤치마크를 한다면 숫자 하나만 보지 말고 time, journalctl, systemctl status, ps, /usr/bin/time -v를 같이 보는 게 좋아요.

    4. 실전 구현: Cron 방식으로 작업 자동화 구성하기

    먼저 Cron 예시입니다. 가장 익숙한 방식이죠. 예제 작업은 매 5분마다 타임스탬프를 남기는 간단한 스크립트로 잡아보겠습니다.

    mkdir -p ~/scheduler-test
    cat > ~/scheduler-test/job.sh <<'EOF'
    #!/usr/bin/env bash
    set -eu
    printf '%s cron job executed\n' "$(date --iso-8601=seconds)" >> /tmp/scheduler-test.log
    EOF
    chmod +x ~/scheduler-test/job.sh

    이제 crontab에 등록합니다.

    crontab -e
    */5 * * * * /home/USER/scheduler-test/job.sh

    여기서 흔한 삽질이 하나 있어요. Cron은 로그인 셸(login shell)이 아니기 때문에 환경 변수(Environment Variable, 환경 변수)가 생각보다 비어 있습니다. PATH가 달라서 명령을 못 찾는 경우가 진짜 자주 나와요. 저도 처음엔 스크립트는 잘 도는데 Cron에서만 실패해서 한참 봤었네요.

    • 명령어는 가능하면 절대 경로를 써요.
    • 로그 파일 리다이렉션을 명시합니다.
    • 실패 시 메일 설정 또는 별도 알림을 붙여야 해요.
    systemd timer Cron 비교 글의 Cron 설정 예시 이미지

    Cron 작업 등록과 기본 실행 흐름을 이해하기 쉽게 보여주는 예시 이미지입니다.

    5. 실전 구현: systemd timer 방식으로 구성하기

    이번엔 같은 작업을 systemd timer로 옮겨보겠습니다. 파일이 둘로 나뉘니까 복잡해 보이지만, 한 번 패턴 잡히면 오히려 정리가 잘 되거든요.

    5-1. service 파일 작성

    # /etc/systemd/system/scheduler-test.service
    [Unit]
    Description=Scheduler test job
    
    [Service]
    Type=oneshot
    ExecStart=/home/USER/scheduler-test/job.sh
    User=USER
    Group=USER

    5-2. timer 파일 작성

    # /etc/systemd/system/scheduler-test.timer
    [Unit]
    Description=Run scheduler test every 5 minutes
    
    [Timer]
    OnCalendar=*:0/5
    Persistent=true
    Unit=scheduler-test.service
    
    [Install]
    WantedBy=timers.target

    5-3. 활성화 및 확인

    sudo systemctl daemon-reload
    sudo systemctl enable --now scheduler-test.timer
    systemctl list-timers --all | grep scheduler-test
    systemctl status scheduler-test.timer

    실제로 써보니까 여기서 편한 건 딱 세 가지였어요. 첫째, 로그 추적이 쉽거든요. 둘째, 서비스 단위 재실행이 쉽고요. 셋째, 리소스 제한을 붙이기 좋아요. 예를 들어 아래처럼 제어할 수 있거든요.

    [Service]
    Type=oneshot
    ExecStart=/home/USER/scheduler-test/job.sh
    CPUQuota=20%
    MemoryMax=128M
    NoNewPrivileges=true

    이 부분은 Cron보다 systemd 쪽이 확실히 운영 친화적이에요. 특히 여러 작업이 섞이는 서버에서는 누가 언제 뭘 실행했는지 보기가 훨씬 수월합니다.

    6. ⚠️ 주의사항과 트러블슈팅: 제가 자주 겪었던 문제들

    여기서부터가 진짜 실전입니다. 문서만 보면 쉬워 보이는데, 현장에서는 자잘한 문제들이 계속 나와요.

    6-1. Cron은 환경 변수가 다릅니다

    문제: 터미널에서는 되는데 Cron에서 실패합니다.

    원인: PATH, HOME, locale(로케일, 지역화 설정)이 다를 수 있어요.

    해결: 스크립트 상단에 필요한 환경을 명시하고, 명령어 절대 경로를 사용합니다.

    #!/usr/bin/env bash
    export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
    set -eu

    6-2. systemd timer는 service 파일과 짝이 맞아야 합니다

    문제: 타이머는 살아 있는데 작업이 실행되지 않아요.

    원인: Unit= 이름이 다르거나 ExecStart 경로가 틀린 경우가 많거든요.

    해결: systemctl status와 journalctl -u scheduler-test.service를 같이 봐요.

    6-3. 부팅 중 누락 작업은 해석이 다릅니다

    Cron은 시스템이 꺼져 있던 동안의 스케줄을 기본적으로 보정하지 않아요. 반면 systemd timer의 Persistent=true는 누락된 실행을 어느 정도 메워줍니다. 백업이나 동기화 작업처럼 “한 번은 꼭 돌아야 하는” 작업이면 이 차이가 꽤 크거든요.

    • Cron 적합: 단순 정리 작업, 개인 계정 배치
    • systemd 적합: 서비스와 연동된 운영 작업, 추적이 중요한 작업
    systemd timer Cron 비교에서 systemd timer 구성과 로그 흐름을 보여주는 다이어그램

    systemd timer 구성 요소와 로그 확인 포인트를 한 번에 보여주는 다이어그램입니다.

    7. 검증 및 결과: 무엇을 확인하면 비교가 되는가

    이제 결과를 봐야겠죠. 다만 여기서 조심할 점이 있어요. 자원 사용량과 실행 지연은 배포판, systemd 버전, 파일시스템 상태, CPU 절전 정책에 따라 달라집니다. 그래서 저는 특정 숫자를 일반화하기보다, 아래 체크리스트 기준으로 해석하는 편을 권해요.

    1. 스케줄 등록 확인: crontab -l, systemctl list-timers
    2. 실행 이력 확인: 로그 파일, journalctl -u 서비스명
    3. 실행 시간 측정: /usr/bin/time -v로 스크립트 자체 비용 확인
    4. 프로세스 추적: ps -ef, systemd-cgls로 실행 구조 확인
    journalctl -u scheduler-test.service --since today
    systemctl status scheduler-test.service
    /usr/bin/time -v /home/USER/scheduler-test/job.sh

    제가 실무에서 해석하는 기준은 이렇습니다.

    비교 포인트 보통 유리한 쪽 해석
    초기 설정 단순함 Cron 한 줄로 끝나는 작업은 여전히 편해요.
    장애 분석 속도 systemd timer journalctl 기반 추적이 강하죠.
    리소스 제어 systemd timer cgroup 정책을 붙이기 좋거든요.
    짧은 개인 작업 Cron 학습 비용이 낮은 편입니다.
    운영 표준화 systemd timer 서비스 단위 관리가 깔끔해요.

    🎉 정리하면, systemd timer Cron 비교에서 절대적인 승자는 없습니다. 대신 운영 규모가 커질수록 systemd timer 쪽의 장점이 더 또렷하게 보여요. 반대로 가벼운 서버나 개인 계정 자동화라면 Cron이 아직도 충분히 실용적입니다.

    systemd timer Cron 비교의 성능 벤치마크 검증 결과를 표현한 대시보드 이미지

    실행 상태, 로그, 자원 사용량 확인 포인트를 대시보드 형태로 정리한 결과 이미지입니다.

    8. 정리 및 FAQ: 어떤 기준으로 선택하면 되나

    마지막으로 제가 멘토링할 때 가장 많이 드리는 기준을 남겨보겠습니다. 저도 처음엔 Cron만 썼는데, 서비스 운영 범위가 넓어지면서 systemd timer로 조금씩 옮겼거든요. 드디어 기준이 잡히고 나니까 선택이 쉬워졌어요.

    • 단순한 작업 자동화가 필요하면 Cron으로 시작해도 됩니다.
    • 로그 추적, 실패 복구, 의존성 관리가 중요하면 systemd timer가 나아요.
    • 리눅스 스케줄러를 팀 표준으로 맞춘다면 systemd 방식이 문서화하기 편합니다.
    • 성능 벤치마크는 숫자 경쟁보다 운영 관찰 가능성까지 함께 봐야 하고요.

    자주 묻는 질문

    Q. Cron이 systemd timer보다 항상 가볍나요?
    꼭 그렇진 않아요. 짧은 작업에서는 체감 차이가 있을 수 있지만, 대부분은 실제 작업 로직이 더 큰 비중을 차지합니다.

    Q. 기존 Cron을 전부 systemd timer로 바꿔야 하나요?
    아니에요. 운영상 이점이 큰 작업부터 옮기면 돼요. 예를 들면 백업, 동기화, 서비스 연계 배치처럼요.

    Q. 둘을 같이 써도 되나요?
    물론이죠. 저도 실제로는 혼용하는 편입니다. 다만 동일한 작업이 중복 실행되지 않게 기준은 분명히 잡아야 해요.

    다음 글에서는 systemd service 하드닝(hardening, 보안 강화)이나 백업 배치 표준화 쪽을 따로 다뤄볼 예정입니다. 이전 글에서 다뤘던 로그 관리와 묶어서 보면 더 이해가 잘 될 거예요. 혹시 지금 운영 중인 서버에서 어느 쪽이 맞을지 고민된다면, 먼저 “실패했을 때 얼마나 빨리 원인을 찾을 수 있나”부터 따져보세요. 그 질문 하나가 생각보다 방향을 잘 잡아줍니다.

    systemd timer Cron 비교의 선택 기준과 장단점을 정리한 인포그래픽

    Cron과 systemd timer 선택 기준을 빠르게 판단할 수 있도록 요약한 인포그래픽입니다.

  • [Linux] Ubuntu Server vs Debian Stable: 홈서버 OS 선택 가이드

    [Linux] Ubuntu Server vs Debian Stable: 홈서버 OS 선택 가이드

    홈서버 OS, 뭘 골라야 할까요?

    홈서버를 처음 세팅하거나 기존 OS를 갈아엎으려고 할 때 가장 먼저 부딪히는 질문이 있죠. “Ubuntu Server랑 Debian Stable 중에 뭐가 나아요?” 저도 13년 전에 똑같이 고민했거든요. 그 이후로도 계속 두 배포판을 번갈아 쓰면서 비교해왔습니다.

    Ubuntu Server와 Debian Stable 비교는 리눅스 커뮤니티에서 영원히 끝나지 않는 토론 주제 중 하나예요. “어차피 Ubuntu도 Debian 기반이잖아요”라고 하시는 분들도 있는데, 맞는 말이긴 한데 실제로 써보면 체감 차이가 꽤 납니다. 특히 홈서버 운영하면서 패키지 버전 때문에 삽질해본 분들은 공감하실 거예요.

    이 글에서는 두 OS를 홈랩에서 직접 운영하면서 느낀 차이점을 솔직하게 정리해드릴게요. 어느 쪽이 무조건 낫다가 아니라, 여러분의 상황에 맞는 선택을 할 수 있도록 도와드리는 게 목표입니다.

    Ubuntu Server와 Debian Stable 비교 개요 — 홈서버 OS 선택 가이드

    Ubuntu Server와 Debian Stable — 둘 다 훌륭한 서버 배포판이지만, 방향성이 꽤 다릅니다.

    두 배포판의 철학 차이부터 이해하기

    기술적인 비교 전에 철학적인 차이를 먼저 이해하는 게 중요해요. 쉽게 말해서, 두 리눅스 배포판이 추구하는 방향이 근본적으로 다릅니다.

    Debian Stable은 “절대 깨지지 않는 안정성”을 최우선으로 둡니다. Debian 프로젝트는 커뮤니티가 자발적으로 운영하는 비영리 프로젝트인데요, 패키지를 Stable 브랜치에 올리기 전에 수개월에서 1~2년씩 테스트를 거칩니다. 덕분에 패키지 버전이 좀 구식이더라도, 한번 올라간 패키지는 진짜 안 깨져요. 제가 Debian Stable 서버를 3년 넘게 돌린 적 있는데, apt upgrade 하다가 시스템이 망가진 적이 단 한 번도 없었습니다.

    Ubuntu Server는 Canonical이 개발하고 지원하는데요, Debian을 기반으로 하면서 더 최신 패키지와 사용 편의성을 추가한 형태입니다. 6개월마다 일반 릴리스가 나오고, 2년마다 LTS(Long Term Support, 장기 지원 버전)가 나옵니다. LTS는 5년간 보안 업데이트를 받을 수 있어서 서버용으로는 주로 LTS를 씁니다.

    릴리스 사이클 한눈에 비교

    항목 Debian Stable Ubuntu Server LTS
    릴리스 주기 약 2년 (불규칙) 2년 (짝수 연도 4월)
    지원 기간 약 3년 + LTS 1년 5년 (ESM 포함 10년)
    패키지 신선도 보수적 (안정 우선) 비교적 최신
    개발 주체 커뮤니티 Canonical (기업)
    기업 지원 서드파티 유료 Canonical 공식 지원

    패키지 관리: 신선도 vs 안정성

    홈서버 운영하면서 가장 많이 체감하는 차이가 바로 패키지 버전이에요. 이게 생각보다 꽤 중요하더라고요.

    예를 들어볼게요. 제가 Docker를 Debian Stable에서 공식 저장소로 설치하려고 했더니, 패키지 버전이 꽤 오래된 버전이었습니다. 물론 Docker 공식 저장소를 별도로 추가하면 최신 버전을 쓸 수 있지만, 이런 식으로 외부 저장소를 여러 개 추가하다 보면 나중에 의존성 충돌이 생기기도 하거든요. 반면 Ubuntu Server는 Docker 공식 저장소의 패키지와 호환이 잘 되는 편이었습니다.

    그렇다고 Debian이 무조건 불편한 건 아니에요. 패키지가 구식인 대신, 그 버전에서 발생할 수 있는 버그나 보안 취약점은 백포팅(backporting, 최신 보안 패치를 구버전에 역이식)으로 꾸준히 처리해줍니다. 홈서버에서 특별히 최신 기능이 필요한 게 아니라면 충분해요.

    # Debian에서 backports 저장소 추가하는 방법
    # 더 최신 패키지가 필요할 때 활용
    echo "deb http://deb.debian.org/debian $(lsb_release -cs)-backports main" | \
      sudo tee /etc/apt/sources.list.d/backports.list
    
    sudo apt update
    
    # backports에서 특정 패키지 설치
    sudo apt install -t $(lsb_release -cs)-backports <패키지명>

    Ubuntu는 PPA(Personal Package Archive, 개인 패키지 저장소)라는 시스템도 있어서 공식 저장소에 없는 최신 패키지를 쉽게 추가할 수 있습니다. 홈랩에서 이것저것 실험하기엔 편한데, 프로덕션 서버에선 PPA 남발하면 나중에 관리하기 복잡해지더라고요. 경험상 PPA는 정말 필요한 것만 추가하는 게 낫습니다.

    # Ubuntu PPA 추가 예시
    sudo add-apt-repository ppa:저장소/이름
    sudo apt update
    sudo apt install 패키지명
    
    # 추가된 PPA 목록 확인
    ls /etc/apt/sources.list.d/

    네트워크 설정: Netplan vs interfaces

    이 부분에서 처음에 좀 당황했는데요. Ubuntu Server는 Netplan(넷플랜)이라는 독자적인 네트워크 설정 방식을 사용합니다. YAML 파일로 네트워크를 정의하고, 이걸 systemd-networkd나 NetworkManager에 위임하는 구조예요.

    # Ubuntu Netplan 설정 예시
    # /etc/netplan/00-installer-config.yaml
    network:
      version: 2
      ethernets:
        eth0:
          addresses:
            - 192.168.1.100/24
          routes:
            - to: default
              via: 192.168.1.1
          nameservers:
            addresses:
              - 8.8.8.8
              - 1.1.1.1
    # Netplan 설정 적용
    sudo netplan apply

    Debian은 전통적인 /etc/network/interfaces 방식을 기본으로 씁니다. 오래된 방식이지만 레퍼런스가 많아서 오히려 익숙한 분들도 많아요.

    # Debian /etc/network/interfaces 예시
    auto eth0
    iface eth0 inet static
      address 192.168.1.100
      netmask 255.255.255.0
      gateway 192.168.1.1
      dns-nameservers 8.8.8.8 1.1.1.1

    솔직히 Netplan이 처음엔 낯설었는데, 익숙해지면 YAML로 깔끔하게 관리되는 게 나름 편합니다. 근데 Debian의 interfaces 방식도 간결하고 직관적이라 나쁘진 않아요.

    Ubuntu Netplan과 Debian 네트워크 설정 방식 구조 비교 다이어그램

    Ubuntu의 Netplan과 Debian의 전통적인 네트워크 설정 방식 비교 — 구조는 다르지만 목적은 같습니다.

    실전 홈서버 운영 시나리오별 비교

    이론적인 비교는 충분히 했으니, 실제 홈서버에서 어떤 용도로 쓰냐에 따라 어떤 게 나은지 정리해볼게요.

    시나리오 1: Docker + 컨테이너 환경

    요즘 홈서버는 거의 Docker로 돌리시죠? 이 경우엔 솔직히 Ubuntu Server LTS가 좀 더 편합니다. Docker 공식 문서의 설치 가이드가 Ubuntu 기준으로 작성된 게 많고, 커뮤니티 레퍼런스도 Ubuntu가 압도적으로 많거든요. 처음 세팅할 때 막히는 상황이 생기면 구글링하면 바로 해결책이 나오는 게 Ubuntu 쪽이 훨씬 유리합니다.

    # Ubuntu에서 Docker 공식 저장소로 설치
    curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
      sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
    
    echo "deb [arch=$(dpkg --print-architecture) \
      signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] \
      https://download.docker.com/linux/ubuntu \
      $(lsb_release -cs) stable" | \
      sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
    
    sudo apt update && sudo apt install docker-ce docker-ce-cli containerd.io docker-compose-plugin

    시나리오 2: NAS / 파일 서버

    Samba나 NFS로 파일 서버를 운영한다면, 솔직히 두 리눅스 배포판 다 크게 차이 없습니다. 다만 Debian Stable이 장기간 무중단 운영에 더 적합하다는 느낌이 있어요. 업데이트할 때 패키지 변화가 적으니까 예상치 못한 설정 파일 변경이 덜 발생합니다. 저도 NAS 용도로는 Debian을 선호하는 편이에요.

    시나리오 3: 홈 자동화 (Home Assistant 등)

    Home Assistant(홈 어시스턴트)나 Node-RED 같은 홈 자동화 플랫폼을 돌릴 때는 Ubuntu가 유리합니다. 이런 애플리케이션들이 최신 Python 버전이나 최신 의존성 패키지를 필요로 하는 경우가 많아서, 패키지가 보수적인 Debian에서는 추가 설정이 필요할 수 있거든요.

    시나리오 4: 가상화 호스트 (Proxmox 아닌 일반 KVM)

    KVM(Kernel-based Virtual Machine, 커널 기반 가상화)으로 VM 여러 대를 돌리는 경우엔 Debian Stable을 추천드립니다. 호스트 OS는 최대한 안정적이고 변화가 없는 게 좋거든요. VM 안에서 Ubuntu를 돌리면 되니까 호스트는 Debian으로 단단하게 깔아두는 게 제 스타일입니다.

    ⚠️ 주의사항 및 실제 삽질 경험

    두 배포판 모두 써보면서 겪은 실제 문제들을 공유할게요.

    Ubuntu Snap 패키지 주의

    Ubuntu Server를 쓰다 보면 일부 패키지가 apt 대신 Snap(스냅)으로 설치되는 경우가 있습니다. Snap은 샌드박스 환경에서 실행되는 패키지 포맷인데, 경로가 일반 apt 패키지와 달라서 처음엔 당황스럽더라고요. 예를 들어 snap으로 설치된 프로그램은 /snap/bin/에 있어서 스크립트 짤 때 경로 실수가 생기기도 했습니다.

    # Snap 패키지 목록 확인
    snap list
    
    # Snap 대신 apt로 설치하고 싶을 때 (예: lxd)
    sudo snap remove lxd
    sudo apt install lxd  # 혹은 공식 apt 저장소 활용

    Debian 업그레이드 시 설정 파일 충돌

    Debian에서 메이저 버전 업그레이드(예: Bullseye → Bookworm)할 때 설정 파일 충돌 처리가 까다로울 수 있어요. apt가 기존 설정 파일을 유지할지 새 버전으로 교체할지 물어보는데, 여기서 잘못 선택하면 서비스가 안 뜰 수 있습니다. 업그레이드 전에 설정 파일 백업은 필수예요!

    # 중요 설정 파일 백업 스크립트 예시
    backup_dir="/backup/config_$(date +%Y%m%d)"
    sudo mkdir -p "$backup_dir"
    
    # 주요 설정 디렉토리 백업
    sudo cp -a /etc/nginx "$backup_dir/"
    sudo cp -a /etc/samba "$backup_dir/"
    sudo cp -a /etc/network "$backup_dir/"
    
    echo "백업 완료: $backup_dir"

    💡 팁: 어떤 배포판이든 unattended-upgrades는 설정하세요

    홈서버라고 보안 업데이트를 소홀히 하면 안 됩니다. 두 리눅스 배포판 모두 unattended-upgrades로 보안 패치를 자동 적용할 수 있어요.

    # 자동 보안 업데이트 설치
    sudo apt install unattended-upgrades
    sudo dpkg-reconfigure --priority=low unattended-upgrades
    
    # 설정 확인
    cat /etc/apt/apt.conf.d/50unattended-upgrades
    홈서버 용도별 Ubuntu Server vs Debian Stable 선택 플로우차트

    홈서버 용도에 따른 OS 선택 플로우차트 — 어떤 서비스를 돌릴지에 따라 최적의 선택이 달라집니다.

    리눅스 서버 안정성 관점에서의 최종 비교

    리눅스 서버 안정성을 최우선으로 본다면, Debian Stable이 살짝 앞서는 게 사실입니다. 하지만 Ubuntu Server LTS도 5년 지원에 Canonical의 상업적 지원이 뒷받침되니까 절대 불안정한 선택은 아니에요.

    비교 항목 Debian Stable Ubuntu Server LTS 홈서버 관점 추천
    시스템 안정성 ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ 장기 무중단: Debian
    패키지 최신성 ⭐⭐⭐ ⭐⭐⭐⭐ 최신 기능 필요: Ubuntu
    레퍼런스/커뮤니티 ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ 초보자: Ubuntu
    서버 배포판 다양성 ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ Docker 환경: Ubuntu
    리소스 사용량 ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ 저사양 서버: Debian
    설치 편의성 ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ 입문자: Ubuntu

    🎉 결론: 이런 분께 이걸 추천드려요

    13년 동안 두 배포판을 오가며 내린 제 결론은 이렇습니다.

    Ubuntu Server LTS를 선택하세요, 만약:

    • 리눅스 서버가 처음이거나 경험이 많지 않은 경우
    • Docker, Kubernetes 같은 컨테이너 기반 인프라를 주로 운영할 때
    • 최신 패키지와 기능이 중요한 홈 자동화, 미디어 서버 환경
    • 구글링으로 빠르게 문제를 해결하고 싶을 때
    • 향후 Canonical의 기업 지원이나 클라우드 연계를 고려할 때

    Debian Stable을 선택하세요, 만약:

    • “한번 세팅하면 몇 년 동안 손 안 대고 싶다”는 스타일
    • 저사양 하드웨어에서 최소한의 리소스로 운영할 때
    • KVM, LXC 같은 가상화 호스트로 쓸 때
    • Snap 같은 추가 패킹 레이어 없이 깔끔하게 관리하고 싶을 때
    • 리눅스에 어느 정도 익숙하고 직접 설정하는 걸 즐길 때

    저 개인적으론 요즘 홈랩에서 가상화 호스트는 Debian, 컨테이너 워크로드가 있는 VM은 Ubuntu Server LTS로 역할을 나눠서 쓰고 있어요. 둘 다 훌륭한 리눅스 서버 배포판이라, 어떤 걸 선택하든 공부하고 경험 쌓는 데는 부족함이 없습니다.

    다음 글에서는 선택한 OS 위에 Docker와 Docker Compose로 홈서버 스택을 구성하는 방법을 다룰 예정이에요. 어떤 배포판을 고르든 그 내용은 공통으로 적용되니 기대해주세요!

    Ubuntu Server vs Debian Stable 최종 비교 요약 인포그래픽 — 홈서버 OS 선택 가이드

    Ubuntu Server vs Debian Stable 최종 선택 가이드 — 여러분의 홈서버 상황에 맞게 골라보세요.

    자주 묻는 질문 (FAQ)

    Q. Ubuntu Server LTS와 Debian Stable 중 보안 업데이트가 더 빠른 쪽은?

    두 배포판 모두 중요한 CVE(보안 취약점)에 대해 신속하게 업데이트를 배포합니다. 다만 Ubuntu는 Canonical의 전담 보안 팀이 있어서 대형 취약점 대응이 약간 더 조직적으로 이뤄지는 편이에요. Debian도 커뮤니티 보안 팀이 매우 적극적이라 실질적인 차이는 크지 않습니다.

    Q. 나중에 Ubuntu에서 Debian으로, 또는 반대로 마이그레이션 할 수 있나요?

    직접 마이그레이션은 권장하지 않습니다. 같은 Debian 계열이라도 패키지 구성이나 설정 위치가 미묘하게 달라서 문제가 생길 수 있어요. OS를 바꿀 때는 깔끔하게 새로 설치하고, 서비스 설정과 데이터를 옮기는 방식이 훨씬 안전합니다.

    Q. 홈서버에 Ubuntu Desktop 버전을 쓰면 안 되나요?

    쓸 수는 있지만, 서버 용도라면 GUI(그래픽 인터페이스) 없는 Ubuntu Server나 Debian이 리소스를 훨씬 적게 쓰고 관리가 단순합니다. Desktop 버전은 GUI 관련 패키지와 서비스가 많아서 서버로는 오버스펙이에요.