13년차의 서버실

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

[작성자:] admin

  • [Cloud] GitLab CI 마이그레이션 회고: 전환 결정 기준

    [Cloud] GitLab CI 마이그레이션 회고: 전환 결정 기준

    GitLab CI 마이그레이션 회고: 전환 결정 기준

    GitLab CI 마이그레이션, YAML 변환보다 먼저 바뀌는 것

    GitLab CI 마이그레이션을 몇 번 해보면 초반 착각이 거의 비슷합니다. 기존 Jenkinsfile이나 사내 CI 스크립트를 .gitlab-ci.yml로 옮기면 끝날 것 같지만, 실제로 흔들리는 지점은 YAML 문법이 아니라 실행 환경, 권한 경계, 캐시 수명, 배포 승인 습관입니다.

    제가 제일 경계하는 실패는 빨간 파이프라인이 아닙니다. 차라리 실패는 빨리 보이거든요. 더 위험한 건 성공처럼 보이는데 산출물이 예전과 다른 경우입니다. 예를 들어 기존 빌드 서버에는 전역 패키지, 로컬 캐시, SSH known_hosts, 사내 CA 인증서가 이미 깔려 있었는데 GitLab Runner의 Docker Executor에서는 전부 사라질 수 있습니다.

    그래서 저는 전환을 시작할 때 ‘CI 도구를 바꾼다’고 보지 않습니다. 빌드 지식을 어디에 둘 것인가, 운영 배포 권한을 누가 어떤 조건에서 행사할 것인가, 실패 로그를 누가 재현 가능한 방식으로 읽을 것인가를 다시 정하는 작업으로 봅니다. 결국 GitLab CI는 도구 선택보다 팀의 배포 습관을 저장소 중심으로 다시 쓸 준비가 되어 있는지의 문제에 가깝더라고요.

    GitLab CI 마이그레이션 전체 흐름 아키텍처 다이어그램

    기존 CI 도구에서 GitLab CI로 넘어갈 때 함께 이동하는 요소를 한눈에 보는 개요 다이어그램입니다.

    GitLab CI 마이그레이션이 바꾸는 운영 경계

    GitLab CI/CD의 핵심은 저장소 안의 .gitlab-ci.yml을 기준으로 Job, Stage, Pipeline을 선언하는 데 있습니다. 그런데 실무에서 중요한 변화는 정의 파일의 위치가 아니라 책임의 위치입니다. 예전에는 빌드 서버 안에 있던 지식이 저장소로 들어오고, 운영팀 개인 계정에 묶여 있던 배포 절차가 Job과 Environment로 드러납니다.

    저는 전환 전에 기존 CI를 다음 네 덩어리로 분해합니다. 이 과정을 생략하면 나중에 ‘왜 Jenkins에서는 됐는데 GitLab에서는 안 되죠?’라는 질문만 반복됩니다.

    • 실행 환경: OS 패키지, 런타임 버전, Docker-in-Docker 사용 여부, 사내 인증서, DNS, 프록시 설정입니다.
    • 상태 저장 지점: 캐시, 아티팩트, 빌드 번호, 릴리스 노트, 컨테이너 이미지 태그가 어디에 남는지입니다.
    • 권한 경계: 배포 토큰, 레지스트리 인증, 클라우드 IAM, SSH 키, protected branch/tag 조건입니다.
    • 사람의 개입: 운영 배포 승인, 장애 시 재실행 기준, 롤백 명령을 누가 실행하는지입니다.

    이 네 가지가 정리되어 있으면 GitLab CI 문법은 금방 따라옵니다. 반대로 이게 흐릿하면 문법을 아무리 예쁘게 써도 파이프라인은 오래 못 갑니다. 관련해서는 이전에 정리한 CI/CD 체크리스트 글과 함께 보면 결정 기준을 더 빨리 잡을 수 있습니다.

    마이그레이션 결정 요소: 저는 이 표를 먼저 채웁니다

    도구 비교표보다 먼저 보는 건 팀의 현재 배포 습관입니다. 같은 GitLab CI라도 저장소가 이미 GitLab에 있는 팀과, Jenkins 플러그인에 배포 지식이 잔뜩 들어 있는 팀의 난이도는 완전히 다릅니다.

    결정 요소 이럴 땐 전환 우선 이럴 땐 보류 또는 Shadow 운영 실패 모드
    저장소 위치 코드와 MR 리뷰가 이미 GitLab 중심입니다. 여러 SCM에 코드가 흩어져 있고 미러링 정책이 없습니다. 커밋 기준과 빌드 기준이 달라 추적성이 깨집니다.
    Runner 운영 Docker 또는 Kubernetes 기반 격리 실행을 운영할 사람이 있습니다. 빌드 서버가 한 대뿐이고 전역 설치 도구에 강하게 의존합니다. 특정 서버에서만 되는 빌드가 됩니다.
    Secret 관리 토큰 목록과 소유자가 파악되어 있고 변수 범위를 재설계할 수 있습니다. 개인 계정 SSH 키, 오래된 API 토큰, 스크립트 내 평문 값이 섞여 있습니다. Job은 성공하지만 과도한 권한으로 배포됩니다.
    배포 승인 운영 배포 전 승인자와 브랜치 정책이 명확합니다. main push가 곧 운영 반영인데 롤백 절차가 문서화되어 있지 않습니다. 마이그레이션 첫 주에 자동 배포 사고가 납니다.
    외부 연동 컨테이너 레지스트리, 패키지 저장소, 클라우드 계정 접근 방식이 표준화되어 있습니다. Jenkins 플러그인이나 사내 스크립트가 인증을 대신 처리합니다. YAML에는 문제가 없는데 인증 단계에서 계속 pending 또는 401이 납니다.

    제 기준은 단순합니다. GitLab CI로 옮겼을 때 배포 이력과 권한이 더 잘 보이면 전환 가치가 큽니다. 반대로 기존 CI가 레거시 시스템의 접착제 역할을 하고 있다면, 전환이 아니라 먼저 분해가 필요합니다.

    실전 구현: 기존 파이프라인을 작게 쪼개 옮기는 순서

    처음부터 운영 배포까지 한 번에 옮기면 원인 추적이 지옥이 됩니다. 저는 테스트 전용 파이프라인, 산출물 생성, 수동 배포, 기존 CI 제거 순서로 갑니다. 느려 보이지만 장애 복구 시간을 줄여줍니다.

    1. 기존 CI 작업을 빌드, 테스트, 패키징, 이미지 빌드, 배포, 알림으로 나눕니다.
    2. 각 단계가 읽는 파일, 쓰는 산출물, 필요한 환경 변수를 표로 만듭니다.
    3. Runner Executor를 정합니다. 서버 상태 의존을 줄이고 싶으면 Docker Executor를 먼저 봅니다.
    4. 배포 없는 테스트 Job부터 GitLab CI에 붙입니다.
    5. 아티팩트와 캐시를 분리합니다. 결과물은 artifacts, 재사용 의존성은 cache입니다.
    6. 운영 배포는 rules, when: manual, protected branch/tag를 함께 설계합니다.

    아래 예시는 Node.js 프로젝트를 전제로 한 출발점입니다. 의도적으로 only 대신 rules를 썼습니다. GitLab 문서에서도 새 조건 설계는 rules 사용을 권장하고, only/except는 deprecated 키워드로 안내합니다.

    stages:
      - test
      - build
      - deploy
    
    default:
      image: node:20
      interruptible: true
      before_script:
        - node --version
        - npm --version
    
    cache:
      key:
        files:
          - package-lock.json
      paths:
        - .npm/
      policy: pull-push
    
    test:
      stage: test
      script:
        - npm ci --cache .npm --prefer-offline
        - npm test
      artifacts:
        when: always
        paths:
          - junit.xml
        reports:
          junit: junit.xml
        expire_in: 1 week
    
    build:
      stage: build
      script:
        - npm ci --cache .npm --prefer-offline
        - npm run build
      artifacts:
        paths:
          - dist/
        expire_in: 1 week
      needs:
        - job: test
          artifacts: false
    
    deploy_production:
      stage: deploy
      image: alpine:latest
      needs:
        - job: build
          artifacts: true
      script:
        - echo 'deploy script goes here'
      environment:
        name: production
      rules:
        - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
          when: manual
        - when: never

    여기서 needs는 단순한 보기 좋은 문법이 아닙니다. Stage 순서를 무작정 기다리지 않고 필요한 Job 관계를 명시합니다. 다만 남발하면 의존 그래프가 복잡해져 장애 때 읽기 어려워집니다. 빌드 시간이 길고 독립 테스트가 많은 저장소에는 적극적으로 쓰고, 작은 서비스에는 Stage만으로 단순하게 유지하는 편이 낫더라고요.

    GitLab CI 마이그레이션에서 Runner와 설정 파일 구성 관계도

    .gitlab-ci.yml, Runner, Job, Artifact, Cache가 어떻게 연결되는지 보여주는 구성 다이어그램입니다.

    GitLab CI Runner 등록과 검증: 토큰 방식부터 확인하세요

    Self-managed Runner를 쓴다면 등록 단계에서 예전 블로그 글을 그대로 따라 하면 막힐 수 있습니다. Runner registration token 방식은 deprecated 상태이며, 현재 GitLab Runner 등록 흐름은 GitLab UI나 API에서 Runner를 만든 뒤 발급되는 runner authentication token을 사용하는 쪽이 기준입니다. 명령어 예시도 --registration-token이 아니라 --token을 기준으로 잡는 게 안전합니다.

    sudo gitlab-runner register \
      --url https://gitlab.example.com \
      --token glrt-REPLACE_WITH_RUNNER_AUTH_TOKEN \
      --executor docker \
      --docker-image alpine:latest \
      --description docker-runner-01

    Runner 태그와 protected 설정은 GitLab UI에서 관리되는 경우가 많습니다. 등록 명령만 성공했다고 Job이 실행되는 건 아닙니다. Pipeline이 계속 pending이면 저는 아래 순서로 봅니다.

    sudo gitlab-runner status
    sudo gitlab-runner verify
    sudo gitlab-runner list
    sudo gitlab-runner --debug run

    verify가 통과하면 GitLab 서버와 Runner의 통신은 일단 됩니다. 그래도 Job이 안 잡히면 네트워크보다 태그 매칭, protected branch/tag, Runner scope, locked 설정을 먼저 봅니다. Job에 tags가 있는데 Runner에 같은 태그가 없으면 정상적으로 대기합니다. 이건 장애가 아니라 스케줄링 조건 불일치입니다.

    test_with_tagged_runner:
      stage: test
      tags:
        - docker
        - linux
      image: alpine:latest
      script:
        - echo 'runner tag matched'
      rules:
        - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
        - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'

    Executor 선택도 비용과 안정성에 직접 영향을 줍니다. Shell Executor는 빠르고 단순하지만 호스트 오염 위험이 큽니다. Docker Executor는 격리가 좋아 마이그레이션 검증에 유리하지만 이미지 pull, 캐시, Docker 데몬 접근 정책을 설계해야 합니다. Kubernetes Executor는 확장성은 좋지만 클러스터 운영 역량이 없으면 CI 문제가 곧 플랫폼 문제가 됩니다.

    트러블슈팅: 겉으로는 CI 문제, 뿌리는 운영 습관 문제

    로컬에서는 되는데 CI에서 깨지는 상황은 대부분 암묵적 환경 때문입니다. 제가 실제로 본 문제들은 문법 오류보다 환경 차이, 권한 범위, 캐시 오염이 훨씬 많았습니다. 이거 진짜 한 번 겪으면, 다음 마이그레이션부터는 환경 목록부터 보게 됩니다.

    1. 변수 이름은 같지만 범위가 달랐습니다

    기존 CI에서 전역 토큰 하나를 모든 프로젝트가 공유하던 팀이 있었습니다. GitLab으로 옮기면서 프로젝트 변수에 넣었는데, 실제 배포 Job은 하위 템플릿 프로젝트에서 실행되고 있었습니다. 이름은 같은데 스코프가 달라 값이 비어 있었던 겁니다. 해결은 Group Variables로 올릴 값과 Project Variables로 제한할 값을 분리하고, 운영 배포용 변수는 protected branch/tag에서만 노출되도록 하는 것이었습니다.

    git grep -n -E 'DEPLOY|TOKEN|PASSWORD|SECRET|PRIVATE_KEY' -- . ':!node_modules' ':!dist'
    git grep -n -E 'ssh|scp|rsync|kubectl|helm|aws|gcloud|az' -- . ':!node_modules' ':!dist'

    이 명령은 마이그레이션 전에 꼭 돌려봅니다. 결과가 많이 나온다는 건 나쁜 게 아닙니다. 숨어 있던 배포 지식을 드러낸 겁니다. 다만 평문 Secret이 나오면 즉시 회수, 재발급, 변수화까지 같이 해야 합니다. 단순히 YAML에서 지우는 건 보안 조치가 아닙니다.

    2. 캐시가 실패를 숨겼습니다

    node_modules를 통째로 캐시하면 처음엔 빨라 보입니다. 그런데 lock file이 바뀌었는데도 오래된 의존성이 남거나, Runner OS/아키텍처가 바뀌며 네이티브 모듈이 깨지는 경우가 있습니다. 그래서 저는 패키지 매니저 캐시 디렉터리를 캐시하고, 설치 결과 디렉터리를 산출물처럼 다루지 않습니다. npm이면 .npm/, pip이면 wheel/cache 계열처럼 재다운로드 비용을 줄이는 쪽이 낫습니다.

    3. 아티팩트와 캐시를 섞었습니다

    캐시는 다음 파이프라인에도 재사용될 수 있는 가속 장치입니다. 아티팩트는 이번 파이프라인의 결과물입니다. 배포에 필요한 dist/, 패키지 파일, 테스트 리포트는 cache가 아니라 artifacts에 둬야 합니다. 반대로 의존성 다운로드 캐시는 artifacts에 넣으면 보관 비용과 다운로드 시간이 불필요하게 커집니다.

    4. 배포 자동화를 너무 빨리 켰습니다

    마이그레이션 첫 주에는 운영 배포를 when: manual로 둡니다. 저는 이 기간에 로그 포맷, 배포 산출물, 롤백 명령, 승인자 권한을 확인합니다. 자동화는 마지막에 켜도 늦지 않습니다. 검증 안 된 자동 배포는 속도가 아니라 부채입니다.

    GitLab CI 마이그레이션 검증 기준

    마이그레이션 완료 기준을 ‘성공 표시가 떴다’로 잡으면 위험합니다. 저는 같은 커밋에서 같은 산출물이 나오는지, 누가 배포를 눌렀는지, 실패 로그만 보고 원인을 좁힐 수 있는지를 봅니다.

    검증 항목 확인 방법 통과 기준
    재현성 같은 commit SHA로 기존 CI와 GitLab CI 산출물 파일 목록을 비교합니다. 파일 누락, 이름 규칙, 환경별 설정 차이가 설명 가능합니다.
    추적성 Pipeline, Job, Environment, Artifact 링크로 배포 이력을 따라갑니다. 운영 반영 커밋과 실행자를 역추적할 수 있습니다.
    권한 분리 protected branch/tag, protected variables, manual job 권한을 확인합니다. 일반 개발자가 운영 Secret을 읽거나 배포하지 못합니다.
    실패 해석 실패 명령 바로 위의 환경 출력, 버전, 인증 로그를 봅니다. 네트워크, 인증, 의존성, 스크립트 오류 중 하나로 빠르게 좁힙니다.

    로그를 볼 때는 마지막 줄만 보면 안 됩니다. npm ci가 실패했다면 registry 인증, lock file, 네트워크, Node 버전 차이를 봅니다. docker build가 실패했다면 Dockerfile 단계와 build context를 확인합니다. kubectl apply가 실패했다면 kubeconfig 권한, namespace, server-side validation을 분리해서 봅니다.

    git diff --name-only origin/main...HEAD
    find dist -type f | sort > dist-files.txt
    sha256sum package-lock.json dist-files.txt
    printenv | sort | grep -E '^CI_|^GITLAB_|^NODE_|^NPM_'

    로컬에서 Runner를 흉내 내는 방식은 빠른 확인에는 도움이 되지만, GitLab 서버의 모든 동작을 완전히 재현한다고 믿으면 안 됩니다. 특히 protected variables, merge request pipeline 조건, 환경 승인, Runner 스케줄링은 실제 GitLab Pipeline에서 확인해야 합니다.

    GitLab CI 파이프라인 검증과 로그 분석 화면

    파이프라인 성공 여부, 실패 로그, Artifact 확인 흐름을 보여주는 검증 화면 예시입니다.

    CI/CD 전환 전략: 병행 운영이 느린 게 아니라 싸게 먹힙니다

    제가 가장 효과를 본 방식은 기존 CI를 바로 끄지 않는 겁니다. GitLab CI를 먼저 읽기 전용으로 붙이고, 같은 커밋에 대해 테스트와 산출물을 비교합니다. 이 기간은 낭비가 아닙니다. 기존 자동화에 숨어 있던 전역 의존성, 권한 누수, 오래된 배포 스크립트를 찾는 보험입니다.

    1. Inventory 단계: 기존 Job, Secret, 산출물, 외부 연동을 목록화합니다.
    2. Read-only 단계: GitLab CI에서 테스트만 돌리고 배포는 하지 않습니다.
    3. Shadow 단계: 기존 CI와 GitLab CI를 같은 커밋에서 같이 돌립니다.
    4. Manual Deploy 단계: 운영 배포를 수동 승인 Job으로 연결합니다.
    5. Primary 단계: GitLab CI를 기본 파이프라인으로 바꾸고 기존 CI 트리거를 중지합니다.
    6. Cleanup 단계: 기존 토큰, 빌드 서버 권한, 스케줄러, 웹훅을 제거합니다.

    성능과 비용을 볼 때도 단순히 ‘GitLab CI가 빠른가’로 보면 답이 흐립니다. 비용을 좌우하는 건 Runner 대수보다 실패 재실행 횟수, 이미지 pull 정책, 캐시 설계, 병렬화된 Job의 대기 시간입니다. 작은 서비스는 단순한 Stage 기반 파이프라인이 더 안정적이고, 큰 모노레포는 rules:changes, needs, child pipeline을 검토할 가치가 있습니다. 다만 child pipeline은 관측 포인트가 늘어나므로 팀이 로그를 읽을 준비가 된 뒤에 넣는 편이 좋습니다.

    자주 묻는 질문

    GitLab CI 마이그레이션은 언제 하는 게 좋나요?

    저는 코드가 GitLab에 있고, Merge Request 중심으로 리뷰하며, 빌드와 배포 이력을 커밋 기준으로 추적하고 싶을 때 추천합니다. 특히 운영 배포 승인과 결과물을 GitLab 안에서 보고 싶은 팀은 전환 효과가 큽니다.

    Jenkins에서 바로 GitLab CI로 옮겨도 괜찮나요?

    가능하지만 Jenkinsfile을 줄 단위로 번역하면 실패하기 쉽습니다. 먼저 stage 구조, 플러그인 의존성, credential 사용 위치, workspace에 남는 파일을 분해해야 합니다. Jenkins 플러그인이 해주던 일을 GitLab CI에서는 YAML, Runner 설정, 외부 CLI, protected variables 조합으로 다시 설계하는 경우가 많습니다.

    가장 먼저 확인할 설정은 뭔가요?

    Runner 태그, protected branch/tag, CI/CD Variables 범위, artifacts 경로, cache key를 먼저 봅니다. 제 경험상 초반 문제의 상당수는 이 다섯 가지에서 나왔습니다.

    Shell Executor와 Docker Executor 중 무엇을 골라야 하나요?

    기존 빌드 서버의 상태를 그대로 써야 하고 격리 요구가 낮으면 Shell Executor가 빠른 출발점이 될 수 있습니다. 하지만 마이그레이션 품질을 높이고 싶다면 Docker Executor가 낫습니다. 매번 비슷한 컨테이너 환경에서 실행되기 때문에 ‘그 서버에만 깔린 무언가’를 빨리 찾아냅니다.

    GitLab CI 마이그레이션 결정 기준 요약 인포그래픽

    전환 여부를 판단하기 위한 저장소, Runner, 보안, 배포 승인 기준을 요약한 인포그래픽입니다.

    마무리: GitLab CI 마이그레이션은 언제 밀어붙일까

    GitLab CI 마이그레이션은 파이프라인 파일 하나를 만드는 작업이 아닙니다. 빌드 지식, 배포 권한, 장애 대응 루틴을 저장소 중심으로 다시 쓰는 일입니다. 그래서 저는 도구의 인기보다 운영 방식이 더 투명해지는지를 기준으로 봅니다.

    GitLab에 코드가 있고, MR 리뷰가 일상이고, 운영 배포 이력까지 한곳에서 보고 싶다면 GitLab CI로 옮기세요. 이 경우에는 테스트 전용 파이프라인부터 시작해 manual deploy까지 붙이는 방식이 가장 무난합니다. 반대로 Jenkins 플러그인, 개인 SSH 키, 사내 배포 서버의 전역 상태에 강하게 묶여 있다면 바로 전환하지 마세요. 먼저 Shadow Pipeline으로 기존 결과와 GitLab CI 결과를 비교하고, Secret과 배포 권한을 정리한 뒤에 주 파이프라인으로 승격하는 편이 안전합니다.

    제 경험상 성공적인 전환의 신호는 ‘파이프라인이 빨라졌다’보다 ‘실패했을 때 누구나 같은 방식으로 원인을 좁힐 수 있다’에 가깝습니다. 속도는 그다음에 옵니다. 파이프라인은 옮기는 게 아니라, 팀의 배포 습관을 코드로 다시 쓰는 일입니다.

    참고한 1차 문서: GitLab Runner 등록 문서, GitLab CI deprecated keywords, GitLab CI caching 문서

  • [Cloud] Datadog 비용 최적화 전략: 불필요한 지출 줄이기

    [Cloud] Datadog 비용 최적화 전략: 불필요한 지출 줄이기

    Datadog 비용 최적화 전략: 불필요한 지출을 줄이는 운영 방법론

    Datadog 비용 최적화는 모니터링을 약하게 만드는 일이 아닙니다. 장애를 설명하는 신호는 끝까지 남기고, 아무도 보지 않는 반복 신호는 수집 전이나 색인 단계에서 줄이는 작업에 가깝습니다. 이 선을 먼저 정해두면 비용도 줄고, 운영자가 불안해하지 않더라고요.

    Datadog 비용이 커지는 조직을 보면 공통점이 있습니다. 기능을 많이 써서라기보다 수집 정책의 기본값이 운영 정책처럼 굳어져 있습니다. Docker stdout은 전부 로그로 들어오고, Kubernetes의 짧게 뜨는 Job도 서비스처럼 취급되고, DEBUG 로그는 릴리스가 끝난 뒤에도 계속 색인됩니다.

    홈랩에서 Kubernetes와 VM을 섞어 굴릴 때도 비슷했습니다. 서비스 몇 개만 올렸는데 health check 로그, readiness probe 로그, 테스트 Job 출력, DEBUG 로그가 생각보다 빨리 쌓이더라고요. 그때 깨달은 건 단순했습니다. 비용을 줄이려면 UI에서 필터 몇 개 만지는 것보다 수집, 태깅, 색인, 보관, 알림의 경계를 먼저 정해야 합니다.

    Datadog 비용 최적화 전체 흐름을 보여주는 아키텍처 다이어그램

    Datadog 비용 최적화는 한 번의 정리 작업이 아니라 운영 루프입니다. 저는 보통 귀속 → 차단 → 샘플링 → 보관 → 검증 순서로 봅니다. 이 순서를 지키면 무엇을 줄였는지와 무엇을 아직 볼 수 있는지가 같이 남습니다.

    1. Datadog 비용 최적화는 제품명이 아니라 의사결정 단위로 보기

    Datadog 지출을 볼 때 “로그가 비싸다”, “APM이 비싸다”처럼 제품명으로만 보면 조치가 흐려집니다. 실무에서는 다음 질문으로 쪼개야 합니다.

    • 누가 만들었나: team, service, env 태그로 소유자를 찾을 수 있는가?
    • 어디에서 늘었나: host, container, custom metric, indexed log, ingested log, trace 중 어느 축인가?
    • 장애 대응에 쓰였나: 최근 인시던트에서 실제로 검색하거나 알림 조건에 사용했는가?
    • 줄이면 무엇을 잃나: 원인 분석 시간, 보안 감사, 규제 보관, SLO 계산에 영향이 있는가?

    제가 가장 먼저 확인하는 화면과 지표는 아래입니다.

    • Usage Attribution: 설정한 태그 키를 기준으로 사용량을 나눠 봅니다. 다만 모든 제품 사용량이 모든 태그 기준으로 완벽히 분해되지는 않습니다.
    • Estimated Usage Metrics: datadog.estimated_usage.* 메트릭으로 증가 추세를 봅니다. 실시간 추정치라 청구서와 1:1로 맞추는 용도보다는 변화 감지에 더 적합합니다.
    • Log Indexes: 어떤 로그가 검색 가능한 상태로 저장되는지, 어떤 exclusion filter를 통과하는지 확인합니다.
    • Retention: prod 오류 로그와 dev access log가 같은 기간 저장되고 있지 않은지 봅니다.

    비용 절감 작업에서 가장 위험한 말은 “일단 Agent 꺼보죠”입니다. Agent를 끄면 비용은 줄어든 것처럼 보이지만 장애 때 시스템이 침묵합니다. 먼저 소유권과 경로를 잡고, 그다음 수집량을 줄이는 편이 안전합니다.

    2. 태그 표준화: 비용 보고서가 아니라 운영 계약으로 다루기

    Datadog에서 태그는 검색 편의 기능을 넘어 비용 통제의 단위입니다. 태그가 흔들리면 Usage Attribution도 흔들리고, 팀별 비용 대화도 금방 흐려집니다. 저는 최소한 아래 4개를 기본 계약으로 둡니다.

    env:prod | env:staging | env:dev
    service:api | service:worker | service:nginx
    team:platform | team:billing | team:checkout
    cost_center:shared | cost_center:revenue | cost_center:internal

    중요한 건 태그 이름보다 태그가 붙는 위치입니다. 애플리케이션 로그에는 service가 붙었는데 컨테이너 메트릭에는 빠지고, APM trace에는 다른 서비스명이 들어가면 비용 분석이 갈라집니다. 가능하면 애플리케이션, 컨테이너 label, Helm values, Agent 설정에서 같은 네이밍을 유지하세요.

    Kubernetes에서는 Pod label과 Datadog 태그를 연결하는 규칙을 명확히 두는 편이 좋습니다. 예를 들어 Helm values에서 공통 태그를 관리하면 배포마다 태그가 빠지는 실수를 줄일 수 있습니다.

    # datadog-values.yaml 예시
    datadog:
      tags:
        - env:prod
        - team:platform
        - cost_center:shared
      logs:
        enabled: true
        containerCollectAll: false
      apm:
        portEnabled: true

    containerCollectAll: false는 모든 컨테이너 로그를 기본 수집하지 않겠다는 선택입니다. 이 값 하나로 비용이 마법처럼 줄지는 않지만, “로그를 보내려면 의도를 표시한다”는 문화를 만들 수 있습니다. 다만 초기 도입기나 장애가 잦은 시스템에서는 너무 빨리 닫으면 디버깅이 어려워집니다.

    3. Agent 단계에서 버릴 것과 Datadog 안에서 줄일 것 구분하기

    로그 비용 최적화의 첫 갈림길은 “Agent에서 보내지 않을 것인가, Datadog에는 보내되 색인하지 않을 것인가”입니다. 둘은 완전히 다른 결정입니다.

    선택지 언제 쓰나 장점 위험
    Agent 단계 제외 health check, readiness probe, 로컬 개발 Job처럼 분석 가치가 낮고 반복적인 로그 전송과 ingestion 자체를 줄입니다. 나중에 Datadog에서 복구할 수 없습니다.
    Index exclusion filter 들어오긴 해야 하지만 검색 저장은 줄이고 싶은 로그 수집 후 라우팅과 샘플링을 조정할 수 있습니다. ingestion은 계속 발생합니다.
    별도 Index 분리 보관 기간, 접근 권한, 검색 목적이 다른 로그 prod, audit, dev 로그의 보관 정책을 다르게 가져갑니다. 라우팅 쿼리와 순서를 문서화해야 합니다.
    애플리케이션 로그 레벨 수정 DEBUG/TRACE가 prod에서 계속 발생하는 경우 가장 근본적인 해결입니다. 릴리스 관찰에 필요한 신호까지 사라질 수 있습니다.

    제 기준은 간단합니다. 절대 다시 보지 않을 로그는 Agent에서 제외하고, 상황에 따라 필요할 수 있는 로그는 Datadog에 보낸 뒤 색인 정책으로 조절합니다. 보안, 감사, 결제, 인증 실패 로그는 비용만 보고 Agent 단계에서 버리면 안 됩니다.

    4. Datadog Agent 로그 수집을 실제로 줄이는 설정

    먼저 Agent가 무엇을 수집하는지 확인합니다. 비용 최적화 전에 이 명령어 결과를 남겨두면 변경 전후 비교가 쉽습니다.

    sudo datadog-agent status
    sudo datadog-agent configcheck
    sudo datadog-agent flare --local

    컨테이너로 Agent를 운영한다면 아래처럼 확인합니다.

    docker exec -it datadog-agent agent status
    docker exec -it datadog-agent agent configcheck

    NGINX access log에서 health check만 제외하려면 서비스별 conf에서 log_processing_rules를 씁니다. 패턴은 Go 정규식 문법을 따르므로, 운영 반영 전에 샘플 로그로 꼭 검증하세요.

    # /etc/datadog-agent/conf.d/nginx.d/conf.yaml
    logs:
      - type: file
        path: /var/log/nginx/access.log
        service: nginx
        source: nginx
        log_processing_rules:
          - type: exclude_at_match
            name: exclude_healthcheck
            pattern: 'GET /health(?:\s|\?)'

    GET /health처럼 단순 문자열만 쓰면 의도치 않은 경로까지 걸릴 수 있습니다. 비용 필터는 일부러 좁게 잡는 편이 낫습니다. 비용을 아끼려다 필요한 장애 로그를 버리면 복구 비용이 더 커지거든요.

    반대로 특정 로그만 보내고 싶다면 include_at_match를 쓸 수 있습니다. 다만 이 방식은 나머지를 모두 버리는 성격이라 dev나 특정 배치 Job처럼 목적이 좁은 곳에만 권합니다.

    # 특정 배치 Job에서 ERROR/WARN만 Datadog으로 전송
    logs:
      - type: file
        path: /var/log/batch/job.log
        service: settlement-batch
        source: java
        log_processing_rules:
          - type: include_at_match
            name: include_warn_error_only
            pattern: '"level":"(WARN|ERROR)"|\b(WARN|ERROR)\b'
    Datadog Agent 로그 필터링으로 옵저버빌리티 비용 절감하는 구성도

    Agent 단계 필터는 효과가 빠르지만 되돌릴 수 있는 데이터가 없습니다. 그래서 저는 health check, liveness probe, 성공한 정적 파일 요청처럼 장애 설명력이 낮고 재현 가능한 로그부터 제외합니다.

    5. Docker와 Kubernetes: 전체 수집보다 명시 수집을 기본값으로 만들기

    컨테이너 환경에서 비용이 흔들리는 이유는 서비스 수보다 수명 짧은 워크로드와 stdout 기본 수집 때문입니다. CronJob, migration Job, 임시 테스트 컨테이너가 모두 같은 규칙으로 수집되면 비용 그래프가 운영 트래픽과 무관하게 튑니다.

    Docker Compose에서는 Datadog Agent가 자기 자신이나 개발용 컨테이너 로그를 물고 들어가지 않게 제외 규칙을 둡니다. Compose의 environment list 문법에서는 값 전체를 불필요하게 따옴표로 감싸지 않는 편이 읽기도 좋습니다.

    services:
      datadog-agent:
        image: gcr.io/datadoghq/agent:latest
        environment:
          - DD_LOGS_ENABLED=true
          - DD_LOGS_CONFIG_CONTAINER_COLLECT_ALL=true
          - DD_CONTAINER_EXCLUDE_LOGS=name:datadog-agent image:^local-dev-tool:.* name:^tmp-.*
          - DD_CONTAINER_EXCLUDE_METRICS=name:^tmp-.*
        volumes:
          - /var/run/docker.sock:/var/run/docker.sock:ro
          - /var/lib/docker/containers:/var/lib/docker/containers:ro
          - /proc/:/host/proc/:ro
          - /sys/fs/cgroup/:/host/sys/fs/cgroup:ro

    DD_CONTAINER_EXCLUDE는 전역 제외에 가깝고, DD_CONTAINER_EXCLUDE_LOGS와 DD_CONTAINER_EXCLUDE_METRICS는 로그와 메트릭을 따로 다룰 때 씁니다. 저는 보통 로그부터 줄입니다. 메트릭은 장애 대응의 기본 뼈대라서, 임시 Job이 아닌 이상 너무 공격적으로 빼지 않습니다.

    Kubernetes에서는 namespace나 label 정책을 섞어야 합니다. 예를 들어 dev namespace 전체 로그를 줄이고, 꼭 필요한 Pod만 annotation으로 수집하는 방식이 관리하기 편합니다.

    # 로그를 수집할 Pod에만 명시적으로 Autodiscovery annotation 부여
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: api
      namespace: prod
    spec:
      template:
        metadata:
          labels:
            app: api
            tags.datadoghq.com/env: prod
            tags.datadoghq.com/service: api
            tags.datadoghq.com/team: checkout
          annotations:
            ad.datadoghq.com/api.logs: >-
              [{"source":"java","service":"api","log_processing_rules":[{"type":"exclude_at_match","name":"drop_health","pattern":"GET /health(?:\\s|\\?)"}]}]
        spec:
          containers:
            - name: api
              image: registry.example.com/api:stable

    여기서 흔한 실패 모드는 컨테이너 이름 불일치입니다. annotation의 ad.datadoghq.com/api.logs에서 api는 컨테이너 이름과 맞아야 합니다. Deployment 이름이나 Kubernetes Service 이름으로 착각하면 설정이 조용히 빗나갑니다.

    6. Log Index와 Exclusion Filter: 비용 최적화의 진짜 손잡이

    Datadog 로그는 ingestion과 indexing을 나눠 봐야 합니다. 들어온 로그 전체가 곧 검색 가능한 로그는 아닙니다. Index exclusion filter를 쓰면 Datadog에는 로그가 들어오지만, 특정 조건의 로그를 색인하지 않거나 샘플링할 수 있습니다.

    제가 자주 쓰는 판단은 이렇습니다.

    • 2xx access log: 전체 요청 추세는 메트릭으로 보고, 로그는 샘플링합니다.
    • 4xx log: 로그인, 결제, API gateway는 보존 쪽에 무게를 둡니다. 정적 리소스 404는 샘플링 후보입니다.
    • 5xx log: 기본 보존입니다. 줄이고 싶다면 먼저 중복 메시지와 애플리케이션 재시도 폭주를 고칩니다.
    • DEBUG/TRACE: prod 기본 색인은 피합니다. 릴리스 관찰 기간에는 만료 시간을 두고 켭니다.
    • audit/security: 비용 최적화의 첫 대상이 아닙니다. 보관 기간과 접근 권한을 별도 Index로 관리합니다.
    로그 유형 추천 정책 쓰면 좋은 도구 피해야 할 접근
    Health check, readiness probe Agent에서 제외 exclude_at_match, DD_CONTAINER_EXCLUDE_LOGS 검색 UI에서만 숨기고 ingestion을 계속 두기
    정상 2xx access log 샘플링 또는 낮은 보관 기간 Index exclusion filter, log-based metric 모든 2xx를 장기 색인
    오류 5xx, panic, exception 보존 별도 Index, Error Tracking, alert 비용 급증 원인이라는 이유만으로 일괄 제외
    개발/스테이징 로그 수집 범위 제한, 짧은 보관 namespace 정책, env 태그, index routing prod와 같은 Index/retention 적용
    보안/감사 로그 별도 정책으로 보호 전용 Index, RBAC, retention 정책 일반 access log와 같은 exclusion filter 공유

    Index filter에서 특히 조심할 점은 순서입니다. 넓은 라우팅 규칙이 앞에 있으면 뒤의 세밀한 규칙은 기대처럼 작동하지 않을 수 있습니다. prod 오류 로그는 먼저 분리하고, 제외 규칙은 더 좁게 두는 편이 안전합니다.

    7. Estimated Usage Metrics로 변화 감시하기

    비용 최적화는 적용보다 검증이 더 중요합니다. 저는 변경 전후에 아래 메트릭을 대시보드에 올려 둡니다.

    datadog.estimated_usage.logs.ingested_bytes
    datadog.estimated_usage.logs.ingested_events
    datadog.estimated_usage.logs.truncated_count
    datadog.estimated_usage.containers
    datadog.estimated_usage.hosts
    datadog.estimated_usage.metrics.custom

    최근 Metric Name Pricing을 쓰는 조직은 datadog.estimated_usage.metrics.custom 대신 datadog.estimated_usage.metrics.points.ingested, datadog.estimated_usage.metrics.points.indexed, datadog.estimated_usage.billable.metrics 같은 메트릭을 확인해야 할 수 있습니다. 계약과 과금 모델에 따라 보이는 지표가 달라질 수 있으니 Plan & Usage 화면과 함께 검증하세요.

    로그 사용량 메트릭은 service, status, datadog_index, datadog_is_excluded 같은 태그와 함께 볼 수 있습니다. 이 태그들이 비어 있거나 N/A로 보이면 라우팅이나 태깅이 기대대로 되지 않는다는 신호일 수 있습니다.

    모니터는 절대값보다 변화율에 먼저 둡니다. 계약과 트래픽이 조직마다 달라서 “몇 GB 이상이면 문제” 같은 숫자를 박아 넣는 건 오히려 위험합니다. 대신 운영 이벤트와 연결해서 봅니다.

    • 배포 직후 특정 service의 로그 이벤트가 급증했는가?
    • 주말이나 야간에도 env:dev 로그가 계속 색인되는가?
    • datadog_is_excluded:false 비율이 갑자기 늘었는가?
    • status:debug가 prod에서 계속 증가하는가?
    • truncated_count가 늘어 긴 로그가 잘리고 있지는 않은가?
    예시 모니터 쿼리 방향
    sum:datadog.estimated_usage.logs.ingested_events{service:api,env:prod,status:debug}.as_count()
    
    예시 대시보드 분해 기준
    sum:datadog.estimated_usage.logs.ingested_bytes{*} by {service,env,datadog_index,datadog_is_excluded}

    Estimated Usage Metrics는 청구 금액 계산기가 아니라 조기 경보 장치로 쓰는 게 맞습니다. 최종 비용은 계약, 제품별 과금 기준, 보관 정책에 따라 달라지므로 숫자를 단정하지 마세요.

    Datadog 사용량 대시보드에서 비용 최적화 결과를 확인하는 화면

    전후 비교는 총량보다 service, env, team, datadog_index별 변화로 보는 쪽이 실무적입니다. 총량만 보면 트래픽 증가와 낭비 증가를 구분하기 어렵습니다.

    8. 재현 가능한 시나리오: DEBUG 로그가 prod 비용을 밀어 올릴 때

    실제로 자주 보는 장면입니다. 신규 릴리스 후 service:api에서 DEBUG 로그가 켜진 채로 prod에 올라갑니다. 장애는 없는데 로그 색인량만 계속 늘고, 개발팀은 “문제 생기면 보려고 켜둔 것”이라고 말합니다. 이때 바로 DEBUG를 전부 버리면 또 다른 문제가 생깁니다.

    1. Log Explorer에서 env:prod service:api status:debug로 검색합니다.
    2. 상위 message pattern을 확인합니다. 같은 메시지가 반복되면 코드 경로를 찾습니다.
    3. trace_id, request_id, user_id 같은 고카디널리티 필드가 불필요하게 facet이나 태그로 승격됐는지 봅니다.
    4. 릴리스 관찰 목적이면 종료 시각을 정하고, 그 이후에는 INFO 이상으로 되돌립니다.
    5. 즉시 비용 방어가 필요하면 Index exclusion filter로 DEBUG 색인을 줄이되, ERROR/WARN과 특정 릴리스 태그는 보존합니다.
    6. 사후에는 배포 파이프라인에 prod 로그 레벨 검증을 넣습니다.

    애플리케이션 쪽에서 막을 수 있다면 그게 제일 좋습니다. 예를 들어 Kubernetes 배포에 로그 레벨 환경 변수를 명시하고, prod에서 DEBUG가 들어가지 않게 리뷰 규칙을 둡니다.

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: api
    spec:
      template:
        metadata:
          labels:
            app.kubernetes.io/version: "1.2.3"
        spec:
          containers:
            - name: api
              image: registry.example.com/api:stable
              env:
                - name: LOG_LEVEL
                  value: INFO
                - name: DD_SERVICE
                  value: api
                - name: DD_ENV
                  value: prod
                - name: DD_VERSION
                  valueFrom:
                    fieldRef:
                      fieldPath: metadata.labels['app.kubernetes.io/version']

    운영에서 “DEBUG는 무조건 나쁘다”는 식으로 접근하면 팀이 우회합니다. 제가 선호하는 정책은 기본은 INFO, DEBUG는 티켓과 만료 시간을 가진 예외입니다. 이거 진짜 편하더라고요. 비용도 줄고, 필요한 디버그 관찰도 정당하게 할 수 있습니다.

    9. 커스텀 메트릭과 태그 카디널리티 관리

    Datadog 비용 최적화에서 로그만 보고 끝내면 절반만 본 겁니다. 커스텀 메트릭은 태그 조합이 늘어날수록 관리가 어려워집니다. 특히 user_id, request_id, session_id, pod_uid 같은 값이 태그로 들어가면 카디널리티가 급격히 커질 수 있습니다.

    제가 보는 기준은 단호합니다. 집계해서 의사결정하지 않을 값은 메트릭 태그로 올리지 않습니다. 로그 필드로 남기는 것과 메트릭 태그로 승격하는 것은 비용과 쿼리 성능 면에서 다른 선택입니다.

    필드 메트릭 태그 적합성 이유 대안
    env 높음 집계와 알림 분리에 계속 사용됩니다. 공통 태그로 유지
    service 높음 소유권과 장애 범위를 나누는 기본 축입니다. APM, 로그, 메트릭 모두 동일 명칭 사용
    team 중간~높음 비용 귀속과 운영 책임에 유용합니다. 조직 개편 시 매핑 관리
    user_id 낮음 고카디널리티가 되기 쉽습니다. 로그 필드 또는 샘플링 이벤트로 유지
    request_id 낮음 거의 모든 요청마다 값이 달라집니다. trace/log correlation에 사용

    StatsD나 DogStatsD를 쓰는 서비스라면 코드 리뷰에서 태그 추가를 가볍게 보지 마세요. 새 태그 하나가 대시보드 필터를 편하게 만들 수도 있지만, 반대로 비용과 쿼리 성능을 계속 압박할 수도 있습니다.

    # 피하고 싶은 예: request_id가 태그로 들어감
    statsd.increment('checkout.payment_attempt', tags=[
        'env:prod',
        'service:checkout',
        f'request_id:{request_id}'
    ])
    
    # 더 나은 예: 집계 가능한 축만 태그로 사용
    statsd.increment('checkout.payment_attempt', tags=[
        'env:prod',
        'service:checkout',
        f'payment_method:{payment_method}',
        f'result:{result}'
    ])

    10. APM과 Trace: 샘플링을 비용 절감 버튼처럼만 보지 않기

    APM 비용이 부담될 때 흔한 반응은 “샘플링 낮추자”입니다. 그런데 trace는 로그보다 더 조심해야 합니다. 낮은 샘플링은 비용을 줄이지만, 희귀한 장애 경로와 긴 꼬리 지연을 놓칠 수 있습니다.

    제가 쓰는 선택 기준은 이렇습니다.

    • 트래픽은 많고 정상 경로가 반복적: 샘플링을 낮추고 RED 지표는 메트릭으로 봅니다.
    • 오류율은 낮지만 비즈니스 영향이 큼: 오류 trace와 특정 service/resource는 더 오래 보존합니다.
    • 릴리스 직후: 일정 시간 샘플링을 넓히고, 안정화 뒤 기본값으로 되돌립니다.
    • 비용 급증: span tag에 고카디널리티 값이 붙었는지, 새 instrumentation이 과도한 span을 만들었는지 먼저 봅니다.

    즉 APM 최적화의 핵심은 “적게 남기기”가 아니라 희귀하지만 중요한 경로를 보존하고, 반복적인 정상 경로를 얇게 보는 것입니다.

    11. 트러블슈팅: 최적화 뒤 관측성이 깨지는 대표 원인

    비용 최적화 후 “로그가 안 보여요”가 나오면 대부분 아래 원인 중 하나입니다. 겉으로는 비슷해 보여도 근본 원인이 다르므로 확인 순서를 정해두는 게 좋습니다.

    증상 가능한 원인 확인 방법 조치
    특정 Pod 로그가 전혀 안 보임 container exclude 규칙 또는 annotation 이름 불일치 agent configcheck, Agent 로그, Pod container name 확인 annotation의 컨테이너명과 실제 container name을 맞춥니다.
    로그는 들어오는데 검색이 안 됨 Index routing 또는 exclusion filter에 걸림 datadog_index, datadog_is_excluded 태그 확인 Index 순서와 필터 쿼리를 좁힙니다.
    오류 로그까지 줄어듦 Agent exclude 패턴이 너무 넓음 변경된 regex와 샘플 로그 비교 ERROR/WARN 예외를 먼저 분리하거나 Agent 제외 대신 Index 정책으로 옮깁니다.
    비용은 줄었는데 대시보드가 비어 보임 로그 기반 메트릭 또는 facet 의존성 누락 대시보드 쿼리의 데이터 소스 확인 로그를 줄이기 전 필요한 메트릭을 별도로 생성합니다.
    변경했는데 효과가 없음 Agent 재시작 누락, DaemonSet 미롤아웃, 다른 설정 경로 우선 rollout status, agent status 롤아웃과 적용된 최종 설정을 확인합니다.

    Agent 설정을 바꾼 뒤에는 아래 순서로 확인합니다.

    sudo systemctl restart datadog-agent
    sudo datadog-agent configcheck
    sudo datadog-agent status
    sudo journalctl -u datadog-agent --since '10 minutes ago' --no-pager

    Kubernetes라면 DaemonSet 롤아웃과 Agent Pod 로그를 같이 봅니다.

    kubectl -n datadog rollout status daemonset/datadog-agent
    kubectl -n datadog get pods -l app=datadog-agent -o wide
    kubectl -n datadog logs daemonset/datadog-agent --tail=200

    12. 운영 체크리스트: 한 번 줄이고 끝내지 않기

    Datadog 비용은 한 번 정리해도 다시 자랍니다. 새 서비스, 새 로그 필드, 새 대시보드, 새 tracer가 계속 들어오기 때문입니다. 그래서 저는 월 1회 또는 큰 릴리스 뒤에 아래 체크리스트를 돌립니다.

    • env, service, team 태그가 Usage Attribution에 충분히 반영되는가?
    • 지난 7일 또는 지난 릴리스 이후 사용량이 급증한 service가 있는가?
    • prod에서 DEBUG/TRACE 로그가 계속 색인되는가?
    • dev/staging 로그가 prod와 같은 보관 정책을 쓰고 있지 않은가?
    • Index exclusion filter 순서가 문서와 일치하는가?
    • 커스텀 메트릭 태그에 고카디널리티 값이 새로 들어오지 않았는가?
    • APM trace 샘플링이 중요한 오류 경로를 놓치지 않는가?
    • 비용 절감 후 대시보드, SLO, 알림이 정상 동작하는가?
    Datadog 비용 최적화 체크리스트 요약 인포그래픽

    체크리스트는 문서로만 두면 잘 안 봅니다. 저는 대시보드에 “이번 달 사용량 증가 Top service”와 “prod DEBUG 로그”를 같이 올려둡니다. 팀이 자기 사용량을 직접 보게 만드는 편이 중앙 플랫폼팀이 계속 말하는 것보다 훨씬 오래 갑니다.

    FAQ: Datadog 비용 최적화에서 자주 막히는 지점

    로그를 줄이면 장애 대응이 느려지지 않나요?

    무작정 줄이면 느려집니다. 그래서 error, warn, security, audit 로그는 먼저 지키고, health check, 반복 2xx access log, prod DEBUG 같은 후보부터 줄입니다. 필요한 신호를 메트릭으로 승격한 뒤 로그 색인을 줄이는 방식도 좋습니다.

    가장 먼저 손볼 곳은 어디인가요?

    대부분은 로그 색인 정책과 컨테이너 로그 수집 범위부터 보는 게 빠릅니다. 그다음 커스텀 메트릭 카디널리티, APM sampling과 retention을 봅니다. host 수를 줄이는 건 인프라 구조와 연결되어 있어 단기 비용 최적화 카드로는 조심스럽습니다.

    Agent에서 제외하는 것과 Index에서 제외하는 것 중 무엇이 더 좋나요?

    다시 볼 일이 없는 반복 로그는 Agent에서 제외하세요. 나중에 검색할 가능성이 있거나 운영 정책이 바뀔 수 있는 로그는 Datadog에 보낸 뒤 Index exclusion filter나 retention으로 다루는 편이 안전합니다.

    Usage Attribution이 팀별로 깔끔하게 안 나뉩니다. 왜 그런가요?

    계측 단계에서 태그가 빠졌거나, 제품별 사용량이 모든 태그 기준으로 완벽히 분해되지 않을 수 있습니다. 그래서 비용 보고서를 만들기 전에 공통 태그가 로그, 메트릭, trace, 컨테이너에 일관되게 들어가는지 먼저 확인해야 합니다.

    마무리: 줄일 데이터와 지킬 신호를 분리하세요

    Datadog 비용 최적화의 핵심은 “덜 보기”가 아니라 장애를 설명하는 신호만 더 선명하게 남기는 것입니다. 운영 환경이라면 Usage Attribution + 태그 표준화 + Log Index 필터를 먼저 잡으세요. 개발/스테이징 비용이 문제라면 Agent 수집 범위와 컨테이너 제외 규칙부터 손보는 게 빠릅니다.

    이럴 땐 이렇게 가면 됩니다. health check와 반복 정상 로그는 Agent에서 제외하세요. prod 오류, 보안, 감사 로그는 보존하세요. 2xx access log는 샘플링하거나 짧게 보관하세요. DEBUG 로그는 기본 비활성화하고, 필요할 때만 만료 시간을 두고 켜세요. 커스텀 메트릭에는 집계 가능한 태그만 올리세요.

    마지막으로 변경 전후를 꼭 같은 대시보드로 보세요. 감으로 줄였다고 말하지 말고, service, env, team, datadog_index별 사용량 변화로 확인하는 겁니다. 관련 글로는 로그 레벨 운영 정책, Kubernetes 관측성 태그 표준화, 클라우드 모니터링 비용 관리 글을 함께 연결하면 독자가 다음 단계로 이동하기 좋습니다.

  • WireGuard로 홈랩 VPN 구축하기 — 1코어 512MB면 충분한 경량 VPN

    밖에서 집 홈랩에 안전하게 접속하는 방법으로, 저는 WireGuard를 씁니다. 설정이 단순하고, 빠르고, 무엇보다 1코어·512MB짜리 초경량 컨테이너 하나면 충분합니다. 실제 제 홈랩의 WireGuard 구성과 클라이언트 추가 방법을 정리합니다.

    1. 왜 WireGuard인가

    VPN 하면 예전엔 OpenVPN이었지만, 지금 홈랩이라면 WireGuard가 정답에 가깝습니다.

    WireGuard 기존 VPN(OpenVPN 등)
    설정 키 한 쌍 + 짧은 conf 인증서·설정이 복잡
    속도 커널 레벨, 매우 빠름 상대적으로 느림
    코드량 수천 줄(감사 쉬움) 수십만 줄
    자원 1코어·512MB로 충분 더 무거움

    제 홈랩에서도 WireGuard는 1코어·512MB LXC 하나에서 돌아갑니다. 이 가벼움이 홈랩엔 큰 장점입니다.

    2. 동작 방식 — 공개키 한 쌍

    WireGuard는 SSH처럼 공개키/개인키 쌍으로 동작합니다. 서버와 각 클라이언트(피어)가 서로의 공개키만 알면 암호화 터널이 열립니다. 서버 인터페이스는 보통 wg0, 기본 포트는 UDP 51820입니다.

    # 서버 키 쌍 생성
    wg genkey | tee server_private.key | wg pubkey > server_public.key
    
    # 현재 터널 상태 확인
    wg show

    3. 서버 설정 (wg0.conf)

    서버 설정은 /etc/wireguard/wg0.conf 하나입니다. 구조는 이렇게 단순합니다(키·IP는 예시).

    [Interface]
    Address = 10.0.0.1/24
    ListenPort = 51820
    PrivateKey = <서버 개인키>
    
    # 클라이언트(피어) 하나당 [Peer] 블록 추가
    [Peer]
    PublicKey = <클라이언트 공개키>
    AllowedIPs = 10.0.0.2/32
    # 인터페이스 올리기 / 부팅 시 자동 시작
    wg-quick up wg0
    systemctl enable wg-quick@wg0

    4. 클라이언트(폰·노트북) 추가

    클라이언트도 자기 키 쌍을 만들고, 서버를 [Peer]로 가리킵니다.

    [Interface]
    Address = 10.0.0.2/32
    PrivateKey = <클라이언트 개인키>
    
    [Peer]
    PublicKey = <서버 공개키>
    Endpoint = my-home.example.com:51820
    AllowedIPs = 192.168.x.0/24     # 홈랩 내부망만 터널로
    PersistentKeepalive = 25

    여기서 AllowedIPs가 핵심입니다. 홈랩 내부 대역만 넣으면(스플릿 터널) 집 서버 접속에만 VPN을 타고 나머지 인터넷은 평소대로 나갑니다. 0.0.0.0/0을 넣으면 모든 트래픽이 집을 경유하는 풀 터널이 됩니다.

    5. 실전 팁

    • 포트 하나만 UDP 개방: 51820만 열면 됩니다. 단, Cloudflare Tunnel과 달리 WireGuard는 인바운드 포트가 필요합니다(고정 IP·DDNS 조합).
    • PersistentKeepalive = 25: 공유기 NAT 뒤 클라이언트의 연결이 끊기지 않게 유지해 줍니다.
    • 키 관리: 개인키는 절대 공유 금지. 피어를 늘릴 때마다 서버 conf에 [Peer]를 추가하고 wg syncconf로 무중단 반영할 수 있습니다.
    • 스플릿 터널 권장: 대부분은 집 서버 접속만 필요하니 AllowedIPs를 내부망으로 좁히는 게 속도·배터리에 유리합니다.

    밖에서 홈랩에 접속할 일이 있다면, WireGuard는 가장 가볍고 빠른 안전한 통로입니다. 라즈베리파이든 작은 LXC든 하나면 충분하니 부담 없이 올려 보시길 권합니다.

  • [OpenStack] OpenStack 플레이버 설계: 워크로드별 VM 구성 기준

    [OpenStack] OpenStack 플레이버 설계: 워크로드별 VM 구성 기준

    OpenStack 플레이버 설계: 워크로드별 VM 구성 기준

    OpenStack 플레이버 설계가 중요한 이유

    OpenStack 플레이버 설계는 단순히 vCPU 몇 개, RAM 몇 GB를 고르는 일이 아닙니다. 실제 운영 환경에서는 이 선택 하나 때문에 웹 서버는 멀쩡한데 DB만 느려지거나, 작은 배치 작업 하나가 Compute Node(컴퓨트 노드, 가상 머신을 실제로 실행하는 호스트)의 리소스를 오래 붙잡는 상황이 생기거든요.

    저도 처음 홈랩에서 OpenStack을 만졌을 때는 “그냥 small, medium, large 만들면 되겠지”라고 생각했습니다. 그런데 실제로 써보니 꽤 다르더라고요. 같은 4 vCPU VM(Virtual Machine, 가상 머신)이라도 웹 워크로드와 데이터베이스 워크로드가 원하는 리소스 성격은 완전히 달랐습니다. CPU가 순간적으로 치솟는 서비스도 있고, 메모리를 꾸준히 쓰는 서비스도 있고, 디스크 I/O(Input/Output, 입출력) 때문에 전체가 버벅이는 경우도 있었습니다.

    VM 사양은 넉넉해 보이는데 애플리케이션은 느리고, 반대로 어떤 VM은 리소스를 거의 안 쓰는데 너무 큰 플레이버를 물고 있는 상황을 본 적 있으신가요? 이 글에서는 Nova 플레이버를 워크로드 기준으로 어떻게 나누고, 어떤 명령어로 만들고, 운영 중 어떤 지표를 보고 조정할지 실무 관점으로 정리해보겠습니다.

    OpenStack에서 플레이버가 VM 크기뿐 아니라 워크로드 배치 전략의 출발점이 된다는 흐름을 보여주는 개요 이미지입니다.

    Nova 플레이버 개념: VM의 표준 사이즈표

    OpenStack에서 Flavor(플레이버)는 VM을 만들 때 선택하는 리소스 묶음입니다. vCPU, Memory(메모리), Disk(디스크) 같은 기본값을 정의하고, 필요하면 Extra Specs(추가 속성)를 붙여 스케줄링, CPU 정책, 메모리 페이지 정책까지 조정합니다.

    쉽게 말해 옷 사이즈표와 비슷합니다. small, medium, large라는 이름만 있다고 좋은 게 아니라, 우리 서비스에 맞는 치수표를 만들어야 합니다. 운영자가 미리 합리적인 사이즈를 만들어두면 사용자는 매번 VM 사양을 고민하지 않아도 되고, 인프라 입장에서는 리소스 낭비를 줄일 수 있습니다.

    • vCPU: 가상 CPU 개수입니다. CPU 연산이 많은 워크로드에서 중요합니다.
    • RAM: VM에 할당되는 메모리입니다. 캐시, DB, JVM 기반 서비스에서 특히 민감합니다.
    • Root Disk: 기본 부팅 디스크 크기입니다. 이미지 기반 배포에서는 너무 크게 잡지 않는 편이 관리가 쉽습니다.
    • Ephemeral Disk: VM 삭제 시 함께 사라지는 임시 디스크입니다.
    • Extra Specs: CPU 고정, Huge Pages(휴즈 페이지, 큰 메모리 페이지), NUMA(Non-Uniform Memory Access, 비균일 메모리 접근) 같은 고급 옵션을 지정할 때 씁니다.

    중요한 포인트는 하나입니다. 플레이버는 “많이 주면 좋다”가 아닙니다. 워크로드 분석을 먼저 하고, 그 다음에 VM 모양을 맞춰야 합니다. 이 순서가 바뀌면 나중에 튜닝이 꽤 피곤해집니다.

    워크로드 분석 기준: CPU, 메모리, 디스크, 네트워크

    실제 운영에서는 워크로드를 크게 네 부류로 나눠 보는 게 편했습니다. 웹/API 서버, 데이터베이스, 배치/분석 작업, 네트워크 장비형 VM입니다. 이름은 단순하지만 확인해야 할 지표는 서로 다릅니다.

    워크로드 유형 주요 병목 플레이버 설계 방향 확인할 지표
    웹/API 서버 순간 CPU, 네트워크 중간 vCPU, 적당한 RAM, 수평 확장 고려 CPU 사용률, Load Average, 요청 지연
    DB 서버 메모리, 디스크 I/O RAM 여유, 빠른 볼륨, 필요 시 전용 CPU iowait, 디스크 await, 캐시 히트율
    배치/분석 CPU 지속 사용, 메모리 피크 작업 시간대와 동시 실행 수 기준으로 분리 pidstat, sar, 메모리 스왑 여부
    네트워크 장비형 VM 패킷 처리, 인터럽트 CPU 정책과 네트워크 드라이버 확인 pps, 인터페이스 오류, softirq

    실무에서는 평균값보다 피크 패턴을 더 자주 봅니다. CPU 평균이 낮아도 특정 시간대에 요청이 몰리면 체감 성능은 나빠집니다. 반대로 하루에 한 번만 도는 배치 작업에 항상 큰 플레이버를 붙여두면 리소스가 아깝습니다. VM 수가 늘어나면 이 차이가 진짜 크게 느껴지더라고요.

    OpenStack 플레이버 설계 실전: CLI로 생성하기

    이제 실제 명령어로 가보겠습니다. 아래 예시는 OpenStackClient 기준입니다. 환경마다 인증 방식은 다르지만, 보통은 OpenRC 파일을 source 한 뒤 실행합니다.

    source ~/admin-openrc.sh
    
    openstack flavor create m1.web.small \
      --vcpus 2 \
      --ram 4096 \
      --disk 20
    
    openstack flavor create m1.api.medium \
      --vcpus 4 \
      --ram 8192 \
      --disk 30
    
    openstack flavor create m1.db.memory \
      --vcpus 4 \
      --ram 16384 \
      --disk 40
    
    openstack flavor list

    여기서 RAM 단위는 MB입니다. 4096은 4GB, 8192는 8GB로 보면 됩니다. 처음엔 저도 이 단위를 대충 보고 넘겼다가 “왜 생각보다 작게 잡혔지?” 하고 한참 봤던 기억이 있습니다. 은근히 자주 하는 실수입니다.

    DB나 지연 시간에 민감한 워크로드는 Extra Specs(추가 속성)를 고민할 수 있습니다. 예를 들어 CPU를 공유 정책으로 둘지, dedicated(전용 할당) 정책을 쓸지 판단해야 합니다. 다만 hw:cpu_policy=dedicated 같은 설정은 Compute Node의 전용 CPU 집합, Placement 리소스, Nova 설정이 맞아야 의미가 있습니다. 명령어만 넣는다고 마법처럼 빨라지지는 않더라고요.

    Nova 플레이버 생성과 Extra Specs 설정 흐름도

    OpenStack CLI로 기본 플레이버를 만들고 Extra Specs를 붙여 워크로드별로 구분하는 과정을 나타낸 구성 이미지입니다.

    openstack flavor create m1.db.dedicated \
      --vcpus 4 \
      --ram 16384 \
      --disk 40
    
    openstack flavor set m1.db.dedicated \
      --property hw:cpu_policy=dedicated \
      --property hw:mem_page_size=large
    
    openstack flavor show m1.db.dedicated

    hw:cpu_policy=dedicated는 전용 pCPU(Physical CPU, 물리 CPU)에 vCPU를 고정하는 정책이고, hw:mem_page_size=large는 큰 메모리 페이지 사용을 요청합니다. 단, Huge Pages는 호스트 커널과 libvirt/KVM 쪽 준비가 필요합니다. 지원되지 않는 환경에서 무작정 넣으면 VM 생성이 실패할 수 있습니다.

    가상 머신 최적화: 작은 VM 여러 대 vs 큰 VM 한 대

    가상 머신 최적화에서 자주 나오는 고민이 있습니다. “2 vCPU VM 여러 대가 나을까, 8 vCPU VM 한 대가 나을까?” 정답은 워크로드에 따라 다릅니다. 웹/API 서버처럼 stateless(상태를 서버에 많이 저장하지 않는 구조)에 가까우면 작은 VM을 여러 대 두고 로드밸런서로 나누는 편이 운영상 편했습니다. 장애 격리도 좋고, 배포 롤백도 덜 무섭습니다.

    반대로 DB처럼 상태가 크고 디스크 I/O가 중요한 서비스는 무작정 쪼개기 어렵습니다. 이 경우에는 메모리와 디스크 경로를 먼저 보고, 필요하면 전용 CPU 정책이나 빠른 Cinder Volume(신더 볼륨, OpenStack 블록 스토리지)을 붙이는 식으로 접근합니다.

    1. 먼저 애플리케이션의 병목 후보를 정합니다. CPU인지, 메모리인지, 디스크인지 구분합니다.
    2. 작은 기본 플레이버로 시작하되, 모니터링 지표를 붙입니다.
    3. 피크 시간대에 CPU iowait, 메모리 swap, 디스크 await 같은 값을 확인합니다.
    4. 증설이 필요하면 같은 계열의 다음 플레이버로 올립니다.
    5. 계속 같은 병목이 반복되면 플레이버 문제가 아니라 애플리케이션 구조나 스토리지 설계를 다시 봅니다.

    추천하는 방식은 “처음부터 대형 플레이버”가 아니라 계열을 나눠 점진적으로 올리는 방식입니다. 예를 들어 web.small → web.medium → web.large처럼 같은 성격 안에서만 키우면 운영자가 판단하기 쉽습니다. 이거 진짜 편하더라고요.

    점검 명령어: VM 내부와 OpenStack 외부를 같이 보기

    플레이버가 적절한지 보려면 VM 내부 지표와 OpenStack 외부 상태를 같이 봐야 합니다. VM 안에서는 리눅스 성능 도구를 씁니다. 아래 명령어는 대부분의 리눅스 서버에서 sysstat 패키지를 설치하면 사용할 수 있습니다.

    # CPU 사용률과 iowait 확인
    sar -u 1 5
    
    # 디스크 서비스 시간, 대기열, 사용률 확인
    iostat -x 1 5
    
    # 프로세스별 CPU/메모리/디스크 사용 확인
    pidstat -urd 1 5
    
    # 메모리와 swap 확인
    free -h

    해석은 이렇게 합니다. sar에서 %iowait가 계속 눈에 띄게 높다면 CPU가 부족하다기보다 디스크 응답을 기다리는 쪽을 의심합니다. iostat -x에서는 await, %util, r/s, w/s를 함께 봅니다. %util이 계속 높은데 await도 같이 늘면 스토리지 병목 가능성이 커집니다. free -h에서 swap 사용량이 증가하고 줄지 않는다면 메모리 플레이버를 올리거나 애플리케이션 메모리 설정을 줄여야 합니다.

    OpenStack 쪽에서는 현재 하이퍼바이저 자원 상태와 VM 배치를 확인합니다. 일부 명령은 관리자 권한이 필요할 수 있습니다.

    openstack hypervisor list
    openstack hypervisor stats show
    openstack server list --all-projects --long
    openstack server show <SERVER_ID>

    여기서 보는 핵심은 “한 Compute Node에 특정 유형 VM이 몰렸는가”입니다. CPU 집약형 VM이 한 노드에 너무 몰리면 개별 VM 스펙은 정상이어도 전체 성능이 흔들릴 수 있습니다. 이때는 Availability Zone(가용 영역), Host Aggregate(호스트 집합), Flavor Extra Specs를 조합해서 배치 정책을 나누는 쪽을 검토합니다.

    주의사항과 트러블슈팅: 자주 헷갈리는 부분

    OpenStack 플레이버 설계에서 가장 많이 하는 실수는 “플레이버 이름만 그럴듯하게 만든 것”입니다. m1.large라는 이름은 익숙하지만, 정작 어떤 워크로드용인지 아무도 모르면 시간이 지나면서 엉망이 됩니다. 그래서 이름에 목적을 넣는 편이 좋습니다. 예를 들면 m1.web.small, m1.db.memory, m1.batch.cpu처럼요.

    • VM 생성 실패: Extra Specs가 호스트 기능과 맞지 않을 때 발생합니다. flavor show와 nova-compute 로그를 같이 봅니다.
    • 디스크가 느림: vCPU 증설로 해결되지 않습니다. Cinder 백엔드, 볼륨 타입, 게스트 파일시스템, iostat 지표를 확인합니다.
    • 메모리가 부족함: swap이 늘면 체감 성능이 급격히 나빠집니다. RAM 증설 또는 애플리케이션 heap 설정을 조정합니다.
    • CPU를 많이 줬는데도 느림: CPU steal, iowait, 스레드 병렬성, 애플리케이션 락을 같이 봐야 합니다.

    재현 가능한 시나리오 하나를 들어보겠습니다. 웹 서버 VM에 2 vCPU, 4GB RAM을 주고 운영했는데 피크 시간에 응답이 느려졌습니다. 처음에는 CPU가 부족한 줄 알고 4 vCPU로 올렸습니다. 그런데 개선이 애매했습니다. 다시 보니 sar에서 iowait가 계속 보였고, iostat에서는 디스크 대기가 늘어났습니다. 원인은 로그가 같은 루트 디스크에 과하게 쌓이는 구조였고, 별도 볼륨으로 로그 경로를 분리하니 훨씬 안정됐습니다.

    검증과 결과: 플레이버 변경 후 확인할 것

    플레이버를 바꿨다면 “VM이 켜졌다”에서 끝내면 안 됩니다. 저는 최소한 아래 순서로 확인합니다. 특히 resize 이후에는 환경에 따라 confirm 단계가 필요할 수 있으니 운영 정책도 같이 확인하세요.

    1. VM 생성 또는 resize(크기 변경)가 정상 완료됐는지 확인합니다.
    2. 게스트 OS에서 CPU, 메모리, 디스크가 기대한 값으로 보이는지 확인합니다.
    3. 애플리케이션 로그에 오류나 타임아웃이 늘지 않았는지 봅니다.
    4. 피크 시간대 지표를 이전과 비교합니다.
    5. 동일 계열 VM 여러 대의 편차가 크지 않은지 확인합니다.
    openstack server resize --flavor m1.api.medium <SERVER_ID>
    openstack server resize --confirm <SERVER_ID>
    
    # VM 내부에서 확인
    nproc
    free -h
    lsblk

    결과 해석 기준은 간단하게 잡아두면 좋습니다. CPU 사용률이 피크 시간에만 높고 요청 지연이 없다면 굳이 올리지 않아도 됩니다. 메모리는 swap이 생기는지, 디스크는 iowait와 await가 함께 나빠지는지 봅니다. 네트워크는 대역폭뿐 아니라 인터페이스 오류, retransmission(재전송)도 같이 봐야 합니다.

    OpenStack 플레이버 변경 전후 가상 머신 최적화 지표 대시보드

    CPU, 메모리, 디스크 I/O 지표를 기준으로 플레이버 변경 전후를 비교하는 검증용 대시보드 예시입니다.

    자주 묻는 질문: OpenStack 플레이버 설계 FAQ

    Q1. 플레이버는 몇 종류로 시작하는 게 좋나요?

    처음부터 너무 많이 만들지 않는 걸 추천합니다. 웹/API용 2~3개, 메모리 중심 1~2개, CPU 중심 1~2개 정도로 시작하고 실제 사용 패턴을 보면서 늘리는 편이 관리하기 쉽습니다.

    Q2. dedicated CPU는 무조건 좋은 선택인가요?

    아닙니다. 지연 시간에 민감하거나 성능 격리가 중요한 VM에는 도움이 될 수 있지만, 전체 클러스터 효율은 떨어질 수 있습니다. 호스트의 전용 CPU 구성과 운영 정책이 준비된 경우에만 쓰는 게 좋습니다.

    Q3. 디스크 크기를 크게 잡으면 성능도 좋아지나요?

    대부분의 경우 단순 디스크 용량과 성능은 별개로 봐야 합니다. 성능은 스토리지 백엔드, 볼륨 타입, 캐시 정책, I/O 패턴 영향을 더 많이 받습니다.

    마무리: 워크로드별 추천 기준

    OpenStack에서 VM 구성을 잘 잡고 싶다면 플레이버를 “사이즈”가 아니라 “운영 정책”으로 봐야 합니다. 이 관점이 잡히면 OpenStack 플레이버 설계가 훨씬 쉬워집니다.

    • 웹/API 서버라면 작은 VM 여러 대와 수평 확장을 우선 추천합니다.
    • DB 서버라면 메모리, 디스크 I/O, 볼륨 타입을 먼저 보고 필요할 때 전용 CPU를 검토하세요.
    • 배치 작업이라면 실행 시간대와 동시성 기준으로 CPU형 플레이버를 따로 두는 게 좋습니다.
    • 네트워크 장비형 VM이라면 CPU 정책, 인터럽트, 드라이버, 패킷 처리량을 함께 봐야 합니다.

    제 기준에서는 기본 플레이버를 작고 명확하게 시작한 뒤, 관측 지표를 보고 계열을 늘리는 방식이 가장 덜 아팠습니다. 한 번에 완벽한 표준안을 만들려고 하면 오히려 오래 걸리더라고요. 다음 글에서는 Host Aggregate와 Availability Zone을 이용해서 특정 플레이버를 특정 Compute Node 그룹에 배치하는 방법을 다룰 예정입니다. 이전 글에서 다룬 OpenStack 네트워크 기본 구조도 함께 참고하면 흐름이 더 잘 이어질 겁니다.

    워크로드별 OpenStack 플레이버 설계 선택 기준 요약

    웹, DB, 배치, 네트워크형 VM을 어떤 기준으로 나눌지 한눈에 정리한 요약 이미지입니다.

  • 구글 포토 대신 Immich 자가호스팅 — 홈랩 사진서버 실전 (GPU 없이)

    구글 포토의 무료 무제한이 사라진 뒤로 “사진을 어디에 둘까”는 모두의 고민입니다. 저는 홈랩에 Immich를 올려서 스마트폰 사진을 자동 백업하고 있습니다. 구글 포토와 거의 똑같은 경험을 내 서버, 내 디스크에서요. 실제 운영 구성과 GPU 없이 돌리는 요령을 공개합니다.

    1. Immich가 뭐고 왜 좋은가

    Immich는 셀프호스팅 사진·영상 관리 서버입니다. 구글 포토를 대체하도록 만들어져서 —

    • 스마트폰 앱으로 자동 백업(찍으면 알아서 올라감)
    • 얼굴 인식·사물 검색(“바다”, “강아지”로 검색)
    • 타임라인·앨범·공유 등 익숙한 UI
    • 무엇보다 내 데이터가 외부로 안 나감

    2. 실제 구성 — 4개 컨테이너

    제 홈랩에서는 LXC 컨테이너 안에 Docker로 Immich 스택을 돌립니다. 구성은 이렇게 4개로 나뉩니다.

    컨테이너 역할
    immich-server API·웹 UI·업로드 처리
    immich-machine-learning 얼굴 인식·스마트 검색(ML)
    postgres(pgvector) 메타데이터 + 벡터 검색 DB
    redis(valkey) 작업 큐·캐시

    여기서 눈여겨볼 건 postgres가 pgvector 계열이라는 점입니다. 얼굴·사물 검색을 위해 사진의 특징을 벡터로 저장하고, 그 벡터를 DB에서 유사도로 검색합니다. 그래서 “비슷한 사진”이나 자연어 검색이 됩니다.

    3. 사진은 ZFS NAS에, 대용량을 감당

    핵심은 사진 원본을 어디에 두느냐입니다. 저는 컨테이너 디스크가 아니라 ZFS 기반 NAS 데이터셋에 저장합니다. 현재 사진 라이브러리만 1.3TB가 넘는데, 컨테이너 rootfs(수십 GB)로는 어림도 없죠.

    # docker-compose.yml (요지) — 업로드 경로를 NAS 볼륨에 매핑
    services:
      immich-server:
        volumes:
          - /mnt/nas/immich:/usr/src/app/upload   # 원본은 대용량 NAS로
    

    이렇게 하면 애플리케이션(컨테이너)과 데이터(NAS)를 분리할 수 있습니다. 컨테이너를 갈아엎어도 사진은 NAS에 그대로 있고, ZFS라 스냅샷·확장도 자유롭습니다.

    4. GPU 없이 ML 돌리기

    Immich의 얼굴 인식·스마트 검색은 머신러닝이라 “GPU 있어야 하지 않나?” 싶지만, 제 홈랩엔 외장 GPU가 없습니다. ML 컨테이너는 CPU로도 잘 돌아갑니다. 다만 —

    • 처음 대량의 사진을 일괄 인덱싱할 때만 CPU를 오래 씁니다(밤새 돌려두면 됨).
    • 인덱싱이 끝나면 이후 검색·인식은 가볍습니다.
    • 즉 초기 1회만 참으면 GPU 없이도 실사용에 지장이 없습니다.

    수만 장을 한 번에 넣으면 며칠 걸릴 수 있으니, 처음엔 ML 작업을 밤 시간대에 몰아서 돌리는 걸 권합니다.

    5. 운영하며 느낀 점

    • 백업은 별개다: Immich는 사진 “관리”이지 백업이 아닙니다. NAS가 죽으면 끝이니, 사진 원본은 반드시 별도 백업(오프사이트)을 두세요.
    • DB도 소중하다: 앨범·얼굴 정보는 postgres에 있습니다. 사진 파일과 함께 DB 백업도 챙겨야 완전 복구가 됩니다.
    • 업그레이드 전 스냅샷: 마이너 업데이트에서도 DB 마이그레이션이 있으니, 올리기 전에 컨테이너·DB 스냅샷을 찍어두면 안전합니다.

    구글 포토를 벗어나고 싶은 분께 Immich는 현재 가장 완성도 높은 선택지입니다. 홈랩에 NAS가 있다면, 사진 주권을 되찾는 첫걸음으로 강력 추천합니다.

  • [OpenStack] OpenStack Ceph 성능 병목과 실전 해결 방안

    [OpenStack] OpenStack Ceph 성능 병목과 실전 해결 방안

    OpenStack Ceph 성능 병목과 실전 해결 방안

    OpenStack Ceph 성능 문제는 대부분 느린 디스크처럼만 보이지 않습니다

    OpenStack Ceph 성능 장애는 보통 아주 평범한 증상으로 시작합니다. 인스턴스 부팅이 길어지고, 볼륨 attach가 가끔 멈칫하고, Glance 이미지 업로드가 어느 날부터 오래 걸리죠. 사용자 입장에서는 전부 VM 디스크가 느리다고 느끼지만, 운영자 입장에서는 Ceph OSD가 느린지, OpenStack 서비스가 RBD를 제대로 쓰는지, compute node 네트워크가 막히는지, recovery/backfill이 정상적으로 자원을 쓰는지까지 같이 봐야 합니다.

    저는 OpenStack과 Ceph를 같이 볼 때 튜닝값을 먼저 만지지 않는다는 원칙을 꽤 강하게 지킵니다. 특히 운영 환경에서는 osd_max_backfills, osd_recovery_max_active, rbd_cache 같은 값을 감으로 바꾸면 일시적으로 숫자는 좋아져도 장애 반경이 넓어질 수 있거든요. 먼저 병목 위치를 좁히고, 그다음에 설정을 조정해야 합니다.

    이 글은 공식 문서식 옵션 나열이 아니라, 실제 장애 대응 순서에 가깝게 구성했습니다. 느리다는 말을 들었을 때 어떤 명령어를 먼저 치고, 어떤 로그를 같이 열고, 어떤 경우에는 Ceph가 아니라 Nova/Cinder/Glance를 의심해야 하는지에 초점을 맞춥니다.

    OpenStack Ceph 성능 분석을 위한 RBD 연동 전체 아키텍처

    OpenStack Nova, Cinder, Glance가 Ceph RBD 풀과 연결되는 구조를 한눈에 보는 개요 이미지입니다.

    OpenStack에서 Ceph 병목이 헷갈리는 이유

    OpenStack은 Ceph를 단순한 외장 스토리지처럼 쓰지 않습니다. Nova는 VM ephemeral disk를 RBD에 둘 수 있고, Cinder는 volume을 RBD image로 만들고, Glance는 base image를 Ceph pool에 저장할 수 있습니다. 이 셋이 같은 Ceph 클러스터를 쓰더라도 병목 지점은 서로 다릅니다.

    • Nova 병목: VM 생성, 부팅, live migration, libvirt secret, compute node의 RBD 접속 경로에서 드러납니다.
    • Cinder 병목: volume create/delete, snapshot, clone, flatten, attach/detach 지연으로 보입니다.
    • Glance 병목: image upload/download, image conversion, Nova의 base image clone 경로에서 나타납니다.
    • Ceph 병목: OSD latency, PG 상태, recovery/backfill, full/nearfull, 네트워크 손실, CRUSH 배치 문제로 드러납니다.

    현장에서 자주 본 실패 패턴은 HEALTH_OK니까 Ceph는 아니라고 너무 빨리 결론 내리는 경우였습니다. HEALTH_OK는 클러스터의 일관성과 위험 상태를 보는 신호에 가깝지, 모든 OSD가 빠르고 모든 클라이언트 경로가 정상이라는 보장은 아닙니다. 반대로 HEALTH_WARN이 있어도 실제 사용자 지연의 직접 원인은 libvirt secret 불일치일 수 있습니다. 그래서 Ceph와 OpenStack을 항상 나란히 봐야 합니다.

    Ceph 병목을 잡는 첫 10분: 범위를 먼저 자르세요

    처음부터 fio를 돌리거나 설정값을 바꾸지 마세요. 저는 먼저 영향을 받는 범위를 세 가지 질문으로 자릅니다.

    1. 모든 VM이 느린가, 특정 compute node의 VM만 느린가?
    2. 볼륨 작업만 느린가, 이미지 부팅과 업로드도 같이 느린가?
    3. Ceph에서 recovery/backfill/scrub 같은 내부 작업이 돌고 있는가?

    이 질문에 답하면 조사 방향이 빨라집니다. 전체 VM이 느리면 Ceph cluster, OSD, 네트워크를 봅니다. 특정 compute node만 느리면 해당 노드의 ceph.conf, keyring, libvirt secret, NIC 오류를 먼저 봅니다. 볼륨 생성만 느리면 Cinder backend와 volumes pool을 봐야지 Nova 설정부터 만지는 건 순서가 아닙니다.

    # 컨트롤러 또는 Ceph admin 노드에서 1차 확인
    ceph -s
    ceph health detail
    ceph osd tree
    ceph osd df tree
    ceph pg stat
    ceph osd perf
    ceph osd pool stats
    
    # OpenStack 관점에서 영향 범위 확인
    openstack server list --all-projects --long
    openstack volume list --all-projects --long
    openstack image list --long
    openstack hypervisor list
    openstack compute service list
    openstack volume service list
    

    ceph -s에서 recovery, backfill, degraded, undersized, peering 같은 단어가 보이면 성능 저하가 자연스럽게 따라올 수 있습니다. 이때 중요한 건 왜 그런 상태가 됐는지입니다. OSD 하나가 내려갔는지, 디스크 교체 후 재분산 중인지, full ratio에 가까운 OSD가 있는지, 네트워크가 흔들렸는지까지 연결해서 봐야 합니다.

    Ceph OSD 지연은 단독 숫자가 아니라 편차로 봅니다

    ceph osd perf는 OSD별 commit/apply latency를 보여줍니다. 여기서 가장 위험한 해석은 몇 ms 이상이면 무조건 장애처럼 외부 숫자를 그대로 가져오는 겁니다. HDD, SSD, NVMe, replication size, network, BlueStore DB/WAL 구성에 따라 정상 범위가 달라집니다. 대신 같은 장비군에서 특정 OSD만 지속적으로 튀는지, latency 상승과 iostat의 대기열 증가가 같이 움직이는지를 봐야 합니다.

    # Ceph 쪽 편차 확인
    ceph osd perf
    ceph osd df tree
    ceph osd pool stats
    
    # 특정 OSD 호스트에서만 확인합니다. admin socket이 있는 노드에서 실행해야 합니다.
    ceph daemon osd.0 perf dump | less
    
    # 운영 중 bench 명령은 부하를 만들 수 있으니 점검 창에서만 사용하세요.
    ceph tell osd.0 bench
    
    # OSD 호스트에서 디스크와 CPU 확인
    iostat -x 1
    sar -n DEV 1
    sar -u 1
    vmstat 1
    journalctl -k --since '1 hour ago'
    

    iostat -x에서는 await, aqu-sz, %util, r_await, w_await를 같이 봅니다. %util이 높고 aqu-sz가 커지면서 await도 같이 오르면 디스크 대기열이 밀리는 그림입니다. 반대로 디스크는 한가한데 Ceph latency만 높다면 네트워크, CPU, OSD 프로세스, RocksDB/WAL 장치, 또는 클라이언트 경로를 더 봐야 합니다.

    특정 OSD만 튄다면 바로 out 처리하기보다 먼저 원인을 확인하세요. 커널 로그에 I/O error가 있으면 디스크/컨트롤러 쪽 가능성이 커지고, NIC drop이 같이 보이면 네트워크 쪽입니다. OSD가 있는 호스트의 CPU steal이나 softirq가 높으면 스토리지 문제가 아니라 호스트 자원 경합일 수 있습니다.

    Ceph 병목 진단을 위해 OSD latency와 iostat 지표를 비교하는 화면

    OSD latency, iostat, 네트워크 지표를 나란히 비교하며 병목 지점을 좁히는 장면입니다.

    Nova, Cinder, Glance 설정은 같은 Ceph를 보는가부터 확인합니다

    OpenStack Ceph 연동에서 의외로 많은 문제는 Ceph 성능이 아니라 설정 일관성에서 생깁니다. Glance는 Ceph RBD에 이미지를 저장하는데 Nova는 로컬 파일 기반으로 부팅한다든지, Cinder와 Nova가 서로 다른 user/keyring을 본다든지, compute node마다 /etc/ceph/ceph.conf 내용이 다른 식입니다. 이거 진짜 자주 나옵니다.

    Cinder RBD 백엔드 예시

    [DEFAULT]
    enabled_backends = ceph
    
    [ceph]
    volume_driver = cinder.volume.drivers.rbd.RBDDriver
    volume_backend_name = ceph
    rbd_pool = volumes
    rbd_user = cinder
    rbd_ceph_conf = /etc/ceph/ceph.conf
    rbd_flatten_volume_from_snapshot = false
    rbd_max_clone_depth = 5
    rados_connect_timeout = -1
    

    rbd_flatten_volume_from_snapshot = false는 clone 기반 흐름을 살려 생성 속도를 빠르게 가져갈 수 있지만, clone depth가 깊어지면 나중에 읽기 경로가 복잡해질 수 있습니다. rbd_max_clone_depth는 그 균형점입니다. 볼륨 생성 속도가 중요하고 snapshot 계층이 잘 관리된다면 clone을 적극적으로 쓰는 편이 유리합니다. 반대로 오래된 snapshot에서 파생된 볼륨이 많이 쌓이고 삭제 정책이 느슨한 환경이라면 flatten 전략을 더 보수적으로 잡아야 합니다.

    Nova libvirt RBD 설정 예시

    [libvirt]
    images_type = rbd
    images_rbd_pool = vms
    images_rbd_ceph_conf = /etc/ceph/ceph.conf
    rbd_user = nova
    rbd_secret_uuid = 00000000-0000-0000-0000-000000000000
    
    [workarounds]
    rbd_volume_local_attach = false
    

    rbd_secret_uuid는 libvirt secret과 정확히 맞아야 합니다. 예시 UUID를 그대로 쓰면 안 됩니다. 실제 compute node에서 virsh secret-list와 virsh secret-dumpxml로 확인해야 합니다. rbd_volume_local_attach는 RBD Cinder volume을 QEMU의 native RBD 경로가 아니라 compute host의 block device로 붙이는 Nova workaround 옵션입니다. 기본값처럼 false로 두는 구성이 일반적이지만, 배포판과 암호화 볼륨 요구사항에 따라 판단해야 합니다.

    Glance RBD 저장소 예시

    [glance_store]
    stores = file,http,rbd
    default_store = rbd
    rbd_store_ceph_conf = /etc/ceph/ceph.conf
    rbd_store_user = glance
    rbd_store_pool = images
    rbd_store_chunk_size = 8
    

    Glance를 RBD에 두면 Nova가 base image를 clone하는 흐름을 만들기 쉬워집니다. 다만 이미지 변환이 필요한 포맷을 무분별하게 올리면 업로드나 부팅 경로에서 예기치 않은 지연이 생길 수 있습니다. 운영 기준으로는 Glance 이미지 포맷, Nova images_type, Cinder backend가 한 방향으로 맞아야 합니다. 이미지 저장소는 file, VM은 rbd, volume은 rbd처럼 섞인 구성이 꼭 틀린 건 아니지만, 장애 때 확인해야 할 경로가 늘어납니다.

    서비스 계정으로 직접 Ceph에 붙어보면 빠르게 걸러집니다

    운영자가 admin keyring으로 ceph -s를 쳐서 성공하는 것과 Nova/Cinder/Glance가 자기 계정으로 RBD pool에 접근하는 것은 완전히 다른 이야기입니다. 장애 대응 때는 반드시 서비스 계정으로 직접 확인해보는 편이 좋습니다.

    # compute node에서 Nova 계정 확인
    ceph -n client.nova --keyring /etc/ceph/ceph.client.nova.keyring -s
    rbd -n client.nova --keyring /etc/ceph/ceph.client.nova.keyring -p vms ls
    
    # cinder-volume 노드에서 Cinder 계정 확인
    ceph -n client.cinder --keyring /etc/ceph/ceph.client.cinder.keyring -s
    rbd -n client.cinder --keyring /etc/ceph/ceph.client.cinder.keyring -p volumes ls
    
    # glance-api 노드에서 Glance 계정 확인
    ceph -n client.glance --keyring /etc/ceph/ceph.client.glance.keyring -s
    rbd -n client.glance --keyring /etc/ceph/ceph.client.glance.keyring -p images ls
    
    # libvirt secret 확인
    virsh secret-list
    virsh secret-dumpxml 00000000-0000-0000-0000-000000000000
    

    여기서 실패하면 Ceph 튜닝은 잠시 내려놓고 인증과 권한을 봐야 합니다. 흔한 원인은 keyring 파일 경로 불일치, 파일 권한, CephX caps 부족, secret UUID 오타, 배포 자동화가 일부 노드에만 반영된 경우입니다. 특히 compute node가 여러 대일 때는 문제 없는 노드와 느린 노드의 /etc/ceph 디렉터리, Nova 설정, libvirt secret을 비교하면 빠릅니다.

    스토리지 트러블슈팅을 위한 병목 유형별 판단 기준

    아래 표는 실제 점검할 때 쓰는 분류에 가깝습니다. 한 줄짜리 정답표는 아니지만, 어디부터 볼지 정하는 데는 꽤 도움이 됩니다.

    증상 같이 볼 지표 가능성이 큰 원인 먼저 할 조치 피해야 할 대응
    전체 VM 디스크 I/O가 느림 ceph -s, ceph osd perf, iostat -x OSD 지연, recovery/backfill, 네트워크 혼잡 느린 OSD와 내부 작업 여부를 먼저 분리 Nova 설정부터 무작정 변경
    특정 compute node의 VM만 느림 sar -n DEV, journalctl -u nova-compute, virsh secret-list compute node 네트워크, keyring, libvirt secret 정상 노드와 ceph.conf/keyring/secret 비교 Ceph 클러스터 전체 재시작
    볼륨 생성·삭제가 오래 걸림 Cinder 로그, ceph osd pool stats, RBD clone depth Cinder backend 부하, snapshot/clone 누적, pool 병목 Cinder service 상태와 volumes pool I/O 확인 사용자 VM fio 테스트로만 판단
    이미지 업로드나 첫 부팅이 느림 Glance 로그, image format, Nova images_type Glance 저장소 경로, 이미지 변환, RBD clone 미사용 Glance default store와 Nova RBD 설정 일관성 확인 OSD 교체부터 검토
    평소에는 괜찮다가 특정 시간대만 느림 scrub/deep-scrub, backup, snapshot 작업 로그 예약 작업과 클라이언트 I/O 경합 작업 시간대와 Ceph 내부 작업 스케줄 조정 일시 현상으로 방치

    Ceph 최적화 튜닝값은 목적별로 다르게 접근해야 합니다

    Ceph 성능 글에서 자주 보이는 실수는 이 값을 올리면 빨라진다는 식의 단정입니다. 실제 운영에서는 값 하나가 성능, 복구 속도, 사용자 I/O 안정성을 동시에 흔듭니다. 특히 OpenStack은 사용자 VM이 계속 I/O를 내고 있으므로, 복구를 빠르게 끝내는 것과 사용자 지연을 줄이는 것 사이에서 선택해야 합니다.

    결정 지점 A를 선택할 때 B를 선택할 때 운영 판단
    recovery/backfill 속도 장애 복구를 빨리 끝내야 할 때 업무 시간 사용자 I/O를 보호해야 할 때 업무 시간에는 보수적으로, 점검 창에서는 적극적으로 조정
    Glance RBD 사용 Nova/Cinder도 Ceph를 쓰고 이미지 clone 경로를 단순화하고 싶을 때 이미지 저장소를 별도로 운영하고 Ceph 부하를 분리하고 싶을 때 Ceph 기반 OpenStack은 Glance도 RBD로 맞추면 운영이 단순해지는 경우가 많습니다
    pool 분리 images, vms, volumes의 성격과 장애 분석을 나누고 싶을 때 작은 테스트 환경이라 관리 단순성이 더 중요할 때 운영 환경에서는 최소한 images/vms/volumes는 분리하는 쪽을 권합니다
    SSD/NVMe 혼합 워크로드별 CRUSH rule과 pool을 나눌 수 있을 때 장비 수가 적고 장애 도메인이 충분하지 않을 때 빠른 디스크를 섞기만 하면 평균이 좋아지는 게 아니라 예측성이 나빠질 수 있습니다

    제가 겪은 대표 삽질: Ceph는 멀쩡한데 VM만 느린 경우

    한 번은 ceph -s가 멀쩡하고 OSD latency도 크게 튀지 않는데, 특정 compute node의 VM만 디스크가 굼뜨게 느껴진 적이 있습니다. 처음에는 Ceph 최적화 문제라고 생각했습니다. 그런데 정상 노드와 문제 노드를 비교해보니 /etc/ceph/ceph.conf 배포 시점이 달랐고, libvirt secret도 서로 맞지 않았습니다.

    이런 경우에는 Ceph dashboard만 보면 놓칩니다. Ceph는 정상인데, OpenStack 서비스 계층에서 RBD를 제대로 잡지 못하는 상태이기 때문입니다. 저는 이런 케이스 이후로 compute node 단위 문제가 나오면 아래 명령을 거의 습관처럼 칩니다.

    # 문제 compute node에서 실행
    hostname -f
    ls -l /etc/ceph/
    sha256sum /etc/ceph/ceph.conf /etc/ceph/*.keyring
    ceph -n client.nova --keyring /etc/ceph/ceph.client.nova.keyring -s
    rbd -n client.nova --keyring /etc/ceph/ceph.client.nova.keyring -p vms ls
    virsh secret-list
    journalctl -u nova-compute --since '1 hour ago'
    journalctl -u libvirtd --since '1 hour ago'
    journalctl -u virtqemud --since '1 hour ago'
    journalctl -k --since '1 hour ago'
    

    정상 노드와 문제 노드의 sha256sum이 다르면 배포 일관성부터 의심합니다. 물론 Ceph 설정 파일이 일부러 다를 수도 있지만, OpenStack compute node에서 의도치 않게 다른 MON 주소나 keyring을 보고 있다면 성능 문제가 아니라 구성 문제입니다. 이때는 튜닝보다 재배포와 서비스 재시작 순서를 차분히 잡는 게 낫습니다.

    네트워크 병목은 대역폭보다 손실과 재전송을 먼저 봅니다

    Ceph는 네트워크에 민감합니다. 그런데 운영자가 10GbE니까 괜찮겠지라고 생각하는 순간이 위험합니다. 대역폭이 충분해도 packet drop, MTU 불일치, bonding 설정 문제, 스위치 buffer 이슈가 있으면 RBD 클라이언트 입장에서는 지연으로 보입니다.

    # OSD/compute node에서 NIC 오류와 처리량 확인
    ip -s link
    netstat -s | grep -Ei 'retrans|timeout|listen|segments retransmitted'
    sar -n DEV 1
    sar -n TCP,ETCP 1
    
    # 경로와 MTU 확인
    ip route
    ip addr show
    ping -M do -s 8972 CEPH_OSD_IP
    

    MTU 9000을 쓰는 환경이라면 end-to-end로 맞아야 합니다. compute node, storage node, 스위치, bond, VLAN 어디 하나라도 다르면 큰 패킷이 깨지거나 경로에 따라 묘한 지연이 생깁니다. Jumbo frame은 제대로 맞추면 도움이 될 수 있지만, 반쯤 적용된 jumbo frame은 차라리 기본 MTU보다 나쁩니다. 저는 운영 안정성이 먼저인 환경에서는 모든 구간을 검증할 수 있을 때만 jumbo frame을 쓰는 쪽을 선호합니다.

    검증은 고쳤다가 아니라 재현 조건에서 나아졌다로 해야 합니다

    수정 후에는 단순히 HEALTH_OK만 보고 끝내지 않습니다. 같은 증상이 났던 경로를 다시 밟아야 합니다. 이미지 업로드가 문제였으면 Glance 경로를, 볼륨 attach가 문제였으면 Cinder와 Nova attach 경로를, 특정 compute node가 문제였으면 그 노드에서 새 VM을 띄워 봐야 합니다.

    # Ceph 상태 재확인
    ceph -s
    ceph health detail
    ceph osd perf
    ceph osd pool stats
    
    # RBD pool 확인
    rbd -p images ls
    rbd -p volumes ls
    rbd -p vms ls
    
    # OpenStack 리소스 흐름 확인
    openstack image list --long
    openstack volume list --all-projects --long
    openstack server list --all-projects --long
    
    # 최근 로그에서 timeout/auth/error 확인
    journalctl -u nova-compute --since '30 minutes ago' | grep -Ei 'rbd|ceph|timeout|error|auth|secret'
    journalctl -u cinder-volume --since '30 minutes ago' | grep -Ei 'rbd|ceph|timeout|error|auth'
    journalctl -u glance-api --since '30 minutes ago' | grep -Ei 'rbd|ceph|timeout|error|auth'
    

    운영 환경에서 무리한 쓰기 부하 테스트를 바로 돌리는 건 권하지 않습니다. 먼저 작은 테스트 VM과 테스트 볼륨으로 재현 경로를 확인하고, 업무 영향이 낮은 시간대에 점진적으로 부하를 올리는 편이 안전합니다. 스토리지는 한 번 흔들리면 넓게 흔들리는 영역이라서, 확인도 단계적으로 해야 합니다.

    OpenStack Ceph 성능 최적화를 위한 Nova Cinder Glance pool 매핑 구성

    Nova, Cinder, Glance 서비스가 각각 vms, volumes, images 풀을 사용하는 구성을 보여주는 이미지입니다.

    운영 체크리스트: 성능보다 먼저 무너지는 것들

    • pool 분리: 운영 환경에서는 images, vms, volumes pool을 분리해 원인 분석과 정책 적용을 쉽게 만듭니다.
    • CRUSH rule 확인: HDD, SSD, NVMe가 섞인 환경에서는 장치 class와 rule이 의도대로 적용되는지 확인합니다.
    • 시간 동기화: 모든 컨트롤러, compute, storage node에서 chrony 또는 NTP 상태를 확인합니다.
    • 네트워크 역할 분리: 가능하면 Ceph public/client traffic과 cluster replication/backfill traffic을 분리합니다.
    • 복구 작업 정책: 업무 시간과 점검 시간의 recovery/backfill 정책을 다르게 가져갈지 결정합니다.
    • 로그 보존: nova-compute, cinder-volume, glance-api, libvirt 또는 virtqemud, kernel 로그가 충분히 남도록 설정합니다.
    • 배포 일관성: ceph.conf, keyring, libvirt secret이 모든 관련 노드에 동일하게 배포되는지 검증합니다.

    여기서 제일 많이 과소평가되는 항목은 배포 일관성입니다. Ceph 자체가 아무리 튼튼해도 compute node 한 대가 오래된 ceph.conf를 보고 있으면 사용자에게는 OpenStack Ceph 성능 문제로 접수됩니다. 자동화 도구를 쓰더라도 실제 파일 checksum을 비교하는 습관이 도움이 됩니다.

    OpenStack Ceph 성능 개선 후 상태 검증 대시보드

    Ceph health, OSD latency, OpenStack 리소스 상태가 안정화된 모습을 보여주는 검증 이미지입니다.

    증상별 추천 경로: 이럴 땐 이렇게 보세요

    모든 VM이 동시에 느려졌다면 Ceph cluster 상태, OSD latency, recovery/backfill, 네트워크 손실을 먼저 봅니다. 이 경우에는 개별 VM 안에서 fio를 오래 돌리는 것보다 클러스터 전체 지표를 보는 편이 빠릅니다.

    특정 compute node의 VM만 느리다면 Ceph 전체 튜닝보다 해당 노드의 네트워크, /etc/ceph 파일, Nova 설정, libvirt secret을 먼저 비교하세요. 정상 노드와 문제 노드의 차이를 찾는 방식이 가장 빠릅니다.

    볼륨 생성·삭제·attach만 느리다면 Cinder volume 서비스와 volumes pool을 봅니다. snapshot/clone이 많이 쌓인 환경이라면 clone depth와 flatten 정책도 같이 봐야 합니다.

    이미지 업로드나 첫 부팅만 느리다면 Glance 저장소, 이미지 포맷, Nova의 RBD 사용 여부를 확인합니다. Glance와 Nova가 같은 Ceph 기반 흐름을 쓰지 않으면 불필요한 복사나 변환 경로가 끼어들 수 있습니다.

    특정 시간대만 느리다면 백업, snapshot, scrub/deep-scrub, recovery 작업 시간표를 확인하세요. 이 경우에는 하드웨어 증설보다 작업 스케줄과 우선순위 조정이 먼저일 수 있습니다.

    자주 묻는 질문

    Q. OpenStack Ceph 성능 문제는 Ceph 튜닝으로 해결되나요?

    항상 그렇지는 않습니다. 실제 현장에서는 Nova/Cinder/Glance 설정 불일치, compute node 네트워크, libvirt secret, keyring 권한 문제가 꽤 많습니다. Ceph 튜닝은 병목이 Ceph 내부에 있다는 근거를 잡은 뒤에 하는 게 안전합니다.

    Q. OSD latency가 높으면 바로 디스크 교체인가요?

    바로 교체로 가기보다는 특정 OSD만 반복적으로 튀는지, 해당 호스트의 iostat, 커널 로그, NIC 오류, recovery 상태가 같이 움직이는지 봐야 합니다. 디스크 고장일 수도 있지만 네트워크 손실이나 복구 작업 영향일 수도 있습니다.

    Q. Glance도 꼭 Ceph RBD를 써야 하나요?

    필수는 아닙니다. 다만 Nova와 Cinder가 Ceph를 쓰는 구조라면 Glance도 RBD에 두는 구성이 운영과 장애 분석 측면에서 단순해지는 경우가 많습니다. 특히 이미지 clone 경로를 활용하고 싶다면 세 서비스의 저장 경로를 함께 설계하는 편이 좋습니다.

    Q. pool을 꼭 images, vms, volumes로 나눠야 하나요?

    작은 테스트 환경에서는 하나로도 돌아갑니다. 운영 환경에서는 분리하는 쪽을 권합니다. 워크로드 성격이 다르고, 장애 분석과 용량 정책, 권한 설정, 향후 CRUSH rule 적용이 쉬워지기 때문입니다.

    관련 글로는 OpenStack Nova 트러블슈팅, Cinder 백엔드 설계, Ceph CRUSH rule 운영 가이드를 함께 연결해두면 독자가 다음 점검 단계로 이동하기 좋습니다.

    OpenStack Ceph 성능 병목 진단 흐름 요약 인포그래픽

    증상별로 Ceph, OpenStack 서비스, 네트워크, 디스크 중 어디를 먼저 확인할지 정리한 요약 이미지입니다.

    마지막 판단: 먼저 좁히고, 그다음에 바꾸세요

    OpenStack Ceph 성능 문제를 다룰 때 제 추천은 분명합니다. 전체가 느리면 Ceph cluster와 OSD부터, 특정 compute node만 느리면 해당 노드의 RBD 접속 경로부터, 볼륨만 느리면 Cinder와 volumes pool부터, 이미지 부팅만 느리면 Glance와 Nova RBD 연결부터 보세요.

    설정값 변경은 마지막 카드에 가깝습니다. ceph -s, ceph osd perf, iostat -x, 서비스 계정 RBD 접근 테스트, Nova/Cinder/Glance 로그를 통해 병목 위치를 먼저 좁히면 불필요한 튜닝을 줄일 수 있습니다. 운영에서 좋은 트러블슈팅은 멋진 한 방이 아니라, 잘못된 가능성을 빠르게 제거하는 과정에 더 가깝습니다.

  • 홈랩 트러블슈팅 회고 — 디스크 풀과 좀비 프로세스 307개를 잡은 실화

    홈랩은 잘 돌아갈 땐 조용하지만, 한 번 터지면 새벽까지 붙잡게 됩니다. 이번 글은 제가 실제로 겪고 해결한 두 가지 사건 — “빌드하다 디스크가 꽉 참”과 “좀비 프로세스 307개” — 을 그대로 복기합니다. 둘 다 홈랩에서 흔히 만나는 함정이라, 같은 벽에 부딪힌 분께 도움이 될 겁니다.

    사건 1. 컨테이너 빌드 중 “disk quota exceeded”

    Docker 이미지를 새로 빌드하는데 갑자기 실패했습니다.

    failed to solve: ... write ...: disk quota exceeded

    원인을 찾아보니 해당 LXC의 rootfs가 8GB였는데, 그 안에서 Playwright(헤드리스 크롬) 이미지를 재빌드하려니 공간이 부족했습니다. 크로미움을 포함한 이미지는 통째로 1GB를 훌쩍 넘고, 여기에 빌드 캐시가 눈덩이처럼 쌓여 순식간에 rootfs를 채운 겁니다.

    해결은 두 단계였습니다. 먼저 쌓인 빌드 캐시를 비우고,

    # 안 쓰는 빌드 캐시 전량 정리 (공간 즉시 회수)
    docker builder prune -af

    그래도 빠듯해서 Proxmox 호스트에서 LXC rootfs를 온라인으로 확장했습니다(무중단).

    # LXC 113의 rootfs를 8G → 16G 로 확장
    pct resize 113 rootfs +8G

    배운 것: 크로미움·플레이라이트처럼 무거운 이미지를 빌드하는 컨테이너는 rootfs를 처음부터 넉넉히(최소 16GB) 잡아야 합니다. 그리고 docker builder prune을 주기적으로 돌리지 않으면 빌드 캐시가 조용히 디스크를 잠식합니다. 빌드 전 df -h / 확인은 습관으로.

    사건 2. 좀비 프로세스가 307개까지 쌓였다

    어느 날 컨테이너 상태를 보니 좀비(zombie) 프로세스가 307개나 쌓여 있었습니다.

    # 좀비 프로세스 개수 확인
    ps aux | awk '$8 ~ /Z/ {c++} END {print c}'

    범인은 헤드리스 크롬이었습니다. Playwright가 크로미움을 반복 실행/종료하는데, 자식 프로세스가 끝나면 부모가 그 종료 상태를 거둬(reap)줘야 합니다. 그런데 컨테이너의 PID 1(내 파이썬 앱)은 그 일을 하지 않습니다. 일반 리눅스라면 init이 고아 프로세스를 거두지만, 컨테이너 안엔 그 init이 없어서 죽은 크롬 프로세스가 계속 좀비로 남은 겁니다.

    해결은 가벼운 init(tini)을 PID 1로 넣는 것이었습니다. Docker Compose라면 한 줄이면 됩니다.

    services:
      app:
        image: my-bot:latest
        init: true   # tini가 PID 1이 되어 좀비를 자동 수거

    init: true를 켜자 좀비가 더 이상 쌓이지 않았습니다. tini가 PID 1로 앉아 자식들의 종료를 대신 거둬 주기 때문입니다.

    배운 것: 컨테이너 안에서 자식 프로세스를 반복 생성하는 앱(브라우저 자동화, 이미지 변환, 서브프로세스 호출 등)을 돌린다면 init: true는 선택이 아니라 필수입니다. 컨테이너의 PID 1은 “프로세스 청소부” 역할을 하지 않는다는 걸 꼭 기억하세요.

    정리 — 홈랩 삽질에서 얻은 두 습관

    사건 근본 원인 예방 습관
    디스크 풀 빌드 캐시 누적 + 작은 rootfs rootfs 넉넉히, builder prune 주기화, 빌드 전 df -h
    좀비 307개 컨테이너 PID 1이 자식 미수거 서브프로세스 앱엔 init: true

    둘 다 “알고 나면 별것 아니지만, 모르면 새벽을 태우는” 문제였습니다. 홈랩의 실력은 이런 삽질을 하나씩 기록하며 쌓이는 것 같습니다.

  • [3D Printer] 3D 프린터 노즐 막힘, 원인 분석 및 긴급 해결 사례

    [3D Printer] 3D 프린터 노즐 막힘, 원인 분석 및 긴급 해결 사례

    [트러블슈팅] 3D 프린터 노즐 막힘 원인과 긴급 해결

    3D 프린터 노즐 막힘은 노즐 하나의 문제가 아닐 때가 많습니다

    3D 프린터 노즐 막힘을 처음 겪으면 대부분 노즐 끝부터 의심합니다. 저도 그랬습니다. 홈랩에서 서버 브라켓, 케이블 라벨 지그, 임시 덕트 같은 부품을 자주 뽑는데, 한 번은 야간 출력이 아침까지 허공에서만 진행된 적이 있습니다. 베드는 멀쩡하고 헤드는 성실하게 움직이는데 필라멘트만 나오지 않는 상황이었죠.

    그때 노즐만 바꿨다면 당장은 해결됐을 수도 있습니다. 하지만 같은 문제가 며칠 뒤 다시 왔습니다. 원인은 노즐 구멍이 아니라 압출 경로 전체였습니다. 익스트루더 기어가 필라멘트를 살짝 갈아먹고 있었고, 핫엔드 팬 커넥터 접촉도 불안정했습니다. 열이 위로 올라가면서 필라멘트가 히트 브레이크 근처에서 먼저 물러졌고, 그 상태에서 리트랙션이 반복되니 내부 마찰이 커졌습니다.

    제가 지금 쓰는 판단 기준은 단순합니다. 막힌 지점을 찾기 전에 압력이 왜 올라갔는지 먼저 봅니다. 전원, 온도, 필라멘트 상태, 이송 경로, 냉각, 배출 순서로 확인하면 원인이 훨씬 빨리 좁혀집니다. 서버 장애에서 로그 한 줄만 보고 재시작부터 누르지 않는 것과 비슷합니다. 재시작은 빠르지만 원인 제거는 아닐 수 있으니까요.

    3D 프린터 노즐 막힘 원인 흐름도

    노즐 막힘은 한 지점의 고장이 아니라 필라멘트가 지나가는 작은 생산라인의 병목으로 보는 편이 정확합니다. 같은 증상이어도 노즐 찌꺼기, 온도 부족, 히트 크리프, PTFE 튜브 변형, 과한 리트랙션은 해결책이 전부 다릅니다.

    먼저 막힘을 세 가지로 나눠야 합니다

    현장에서 시간을 아끼려면 “막혔다”를 하나의 증상으로 뭉개면 안 됩니다. 저는 노즐 막힘을 세 가지로 나눕니다. 출구 막힘, 열 관리 실패, 이송 실패입니다.

    • 출구 막힘: 노즐 내부에 탄화물, 이전 소재 잔여물, 먼지, 금속 파편이 걸려 토출량이 줄어든 상태입니다. 콜드 풀이나 노즐 교체가 잘 듣습니다.
    • 열 관리 실패: 핫엔드의 뜨거워야 할 곳과 차가워야 할 곳의 경계가 무너진 상태입니다. 히트 브레이크 위쪽에서 필라멘트가 말랑해져 마찰이 커집니다. 콜드 풀만 반복하면 재발합니다.
    • 이송 실패: 익스트루더 기어 장력, 필라멘트 직경 편차, 보우든 튜브 마찰, 스풀 걸림 때문에 충분히 밀어주지 못하는 상태입니다. 노즐을 갈아도 증상이 남습니다.

    노즐은 작은 배관이고, 익스트루더는 펌프에 가깝습니다. 펌프가 정상인데 배관 끝이 막혀도 문제고, 배관은 깨끗한데 펌프가 헛돌아도 문제입니다. 여기에 온도라는 변수가 붙으면 더 헷갈립니다. 그래서 저는 바늘로 찌르기 전에 “수동 압출이 되는가”, “장시간 후에만 멈추는가”, “기어가 필라멘트를 파먹는가”를 먼저 봅니다.

    분류 주요 단서 먼저 할 조치 피해야 할 조치
    출구 막힘 수동 압출 시 가늘게 휘거나 한쪽으로 샘 콜드 풀, 노즐 외부 청소, 노즐 교체 고속 압출로 억지로 밀기
    열 관리 실패 초반은 정상, 일정 시간 뒤 딱딱 소리와 압출 불량 핫엔드 팬, 방열판 먼지, 히트 브레이크 조립 상태 확인 노즐만 반복 교체
    이송 실패 필라멘트가 기어에 파임, 스풀 장력이 불안정함 기어 청소, 장력 조정, 튜브 마찰 확인 온도만 계속 올리기
    첫 레이어 압착 첫 줄부터 재료가 나오지 않거나 노즐에 붙음 Z 오프셋, 베드 레벨, 노즐 외부 찌꺼기 확인 노즐 내부 막힘으로 단정

    이 표 하나만 머릿속에 있어도 대응 순서가 달라집니다. 노즐 막힘은 빠르게 뚫는 것도 중요하지만, 재발할 조건을 남기지 않는 쪽이 더 중요합니다.

    증상별로 보는 3D 프린터 노즐 막힘 원인 분석

    문제는 대부분 출력 중간에 터집니다. 그래서 로그처럼 증상을 남겨두면 좋습니다. 언제부터 안 나왔는지, 딱딱 소리가 있었는지, 수동 압출은 되는지, 필라멘트 끝 모양이 어땠는지 네 가지만 봐도 원인 후보가 확 줄어듭니다.

    증상 가능성이 큰 원인 먼저 볼 곳 판단 기준
    필라멘트가 거의 안 나옴 노즐 내부 이물질 또는 탄화물 노즐, 필라멘트 끝 모양 수동 압출해도 재료가 가늘게 휘거나 한쪽으로 새면 노즐 쪽 저항이 큽니다
    익스트루더에서 딱딱 소리 압출 저항 증가 또는 온도 부족 노즐 온도, 필라멘트 경로, 기어 톱니 기어가 헛돌거나 필라멘트가 파이면 더 밀지 말고 압출 경로를 분리해 봅니다
    처음엔 나오다 중간에 멈춤 히트 크리프 또는 리트랙션 과다 히트 브레이크, 방열팬, 슬라이서 리트랙션 장시간 출력 뒤 반복되면 노즐보다 열 경계와 팬을 먼저 의심합니다
    재료가 덩어리로 뭉침 노즐 외부 오염, Z 오프셋, 첫 레이어 압착 노즐 외부, 베드 높이, 첫 레이어 설정 노즐이 베드에 너무 붙으면 출구가 눌려 정상 노즐도 막힌 것처럼 보입니다
    재료 교체 직후만 불안정함 이전 소재 잔여물, 온도 범위 차이 퍼지 라인, 콜드 풀 결과, 스풀 라벨 색이 섞여 나오거나 점状 찌꺼기가 반복되면 잔여물이 남아 있습니다

    저는 문제를 볼 때 “가장 싼 부품부터 바꾸자”가 아니라 “가장 재현 가능한 테스트부터 하자”로 접근합니다. 노즐은 싸지만, 노즐 교체가 답이 아닌 상황에서 계속 교체하면 시간을 잃습니다. 특히 초반은 정상이고 중간 이후에만 멈추는 경우는 열 관리 문제일 확률이 꽤 높습니다.

    긴급 대응은 온도와 압출 로그부터 잡습니다

    막힌 상태에서 계속 출력 재개를 누르면 익스트루더 기어가 필라멘트를 갈아먹습니다. 그러면 원래는 노즐 청소로 끝날 문제가 이송 불량까지 번집니다. 저는 출력 실패를 발견하면 아래 순서로 멈춥니다.

    1. 출력을 일시정지하거나 중지하고, 헤드가 출력물에 계속 문지르지 않게 합니다.
    2. 노즐 목표 온도와 실제 온도가 정상적으로 붙는지 확인합니다.
    3. 스풀에서 익스트루더까지 필라멘트가 걸리지 않았는지 봅니다.
    4. 익스트루더 기어가 필라멘트를 파먹었는지 확인합니다.
    5. 노즐을 현재 소재의 권장 온도로 올린 뒤 천천히 수동 압출합니다.
    6. 압출물이 휘거나 끊기면 콜드 풀 또는 노즐 교체로 넘어갑니다.

    OctoPrint를 쓰고 있다면 먼저 API로 온도 상태를 확인합니다. 이 단계의 목적은 노즐을 뚫는 게 아니라 “히터가 목표 온도에 정상적으로 도달하는지” 확인하는 것입니다. 목표 온도와 실제 온도가 계속 벌어지거나 요동치면 막힘 이전에 히터 카트리지, 써미스터, 배선, PID 튜닝 쪽을 먼저 봐야 합니다.

    export OCTOPRINT_URL="http://octopi.local"
    export OCTOPRINT_API_KEY="여기에_API_KEY_입력"
    
    curl -s \
      -H "X-Api-Key: ${OCTOPRINT_API_KEY}" \
      "${OCTOPRINT_URL}/api/printer"

    응답에서 <code>temperature.tool0.actual, temperature.tool0.target, state.text를 봅니다. target은 설정 온도, actual은 현재 온도입니다. 여기서 실제 온도가 목표에 붙지 못하면 압출 테스트를 하기 전에 온도 제어부터 해결해야 합니다.

    jq가 설치되어 있다면 필요한 값만 뽑아보는 편이 빠릅니다.

    curl -s \
      -H "X-Api-Key: ${OCTOPRINT_API_KEY}" \
      "${OCTOPRINT_URL}/api/printer" \
    | jq '{state: .state.text, nozzle_actual: .temperature.tool0.actual, nozzle_target: .temperature.tool0.target, bed_actual: .temperature.bed.actual, bed_target: .temperature.bed.target}'

    그다음 수동 압출을 합니다. 저는 가능하면 상대 압출 모드인 M83을 먼저 넣습니다. 기존 G-code 좌표 상태를 덜 건드리기 위해서입니다. 그래도 장비마다 펌웨어 설정이 다를 수 있으니, 출력 중인 작업 위에서 실험하지 말고 정지 상태에서만 실행하세요.

    M109 S200 ; 예시값입니다. 실제 온도는 필라멘트 스풀의 권장값을 사용합니다
    M83      ; 익스트루더를 상대 압출 모드로 전환
    G1 E5 F60
    G1 E5 F60
    G1 E5 F60
    M82      ; 필요하면 절대 압출 모드로 복귀

    해석은 어렵지 않습니다. 정상이라면 재료가 일정한 굵기로 아래로 내려옵니다. 옆으로 휘거나, 실처럼 가늘어지거나, 중간에 툭툭 끊기면 노즐 내부 저항이 남아 있습니다. 이때 속도를 올려 밀어붙이면 기어가 필라멘트를 갉아먹기 쉬워서 멈추는 편이 낫습니다.

    콜드 풀, 바늘, 노즐 교체를 언제 선택할지

    노즐 청소 방법은 많지만, 모든 방법이 같은 상황에 맞지는 않습니다. 제가 쓰는 우선순위는 콜드 풀 → 외부 청소 → 노즐 교체입니다. 청소 바늘은 “구멍이 완전히 막혔는지 확인하는 도구”에 가깝게 씁니다. 깊게 찌르거나 힘으로 밀면 찌꺼기를 더 안쪽으로 넣거나 노즐 구멍을 손상시킬 수 있습니다.

    방법 적합한 상황 장점 주의할 점
    콜드 풀 잔여물, 색상 섞임, 탄화 점이 의심될 때 노즐 내부 찌꺼기를 비교적 깔끔하게 끌어낼 수 있음 온도 타이밍이 맞지 않으면 필라멘트가 끊길 수 있음
    청소 바늘 출구가 완전히 막혔는지 빠르게 확인할 때 장비 분해 없이 시도 가능 노즐 구멍 손상, 찌꺼기 밀어 넣기 위험
    노즐 교체 콜드 풀 후에도 압출이 휘거나 표면 품질이 계속 나쁠 때 시간 대비 확실함 핫엔드 조립과 체결 상태가 나쁘면 누출이 생김
    핫엔드 분해 PTFE 끝단 변형, 히트 브레이크 내부 막힘이 의심될 때 근본 원인까지 볼 수 있음 화상, 나사산 손상, 재조립 누출 위험

    콜드 풀은 숫자 하나로 끝나는 절차가 아닙니다. 소재마다 부드러워지는 온도와 당겨지는 감각이 다릅니다. 그래서 저는 특정 온도를 외워 쓰기보다 지금 쓰는 필라멘트의 권장 온도 범위 안에서 시작하고, 필라멘트가 늘어나기만 하면 조금 더 식히고, 뚝 끊기면 조금 더 데우는 식으로 조정합니다.

    1. 노즐을 현재 필라멘트가 부드럽게 흐르는 온도로 올립니다.
    2. 필라멘트를 손으로 천천히 밀어 노즐 내부를 채웁니다.
    3. 히터 목표 온도를 낮추고 필라멘트가 약간 단단해질 때까지 기다립니다.
    4. 익스트루더 레버를 풀고 필라멘트를 한 번에 당겨 뺍니다.
    5. 끝부분에 검은 점, 이전 색상, 탄화물이 묻어 나오면 반복합니다.
    6. 끝 모양이 노즐 내부 형상처럼 매끈하게 나오고 오염이 줄면 압출 테스트로 넘어갑니다.
    3D 프린터 노즐 청소와 콜드 풀 작업 순서

    콜드 풀의 핵심은 힘이 아니라 타이밍입니다. 너무 뜨거우면 녹은 재료만 늘어지고, 너무 차가우면 내부에서 끊깁니다. 잘 뽑힌 필라멘트 끝은 노즐 안쪽 모양을 어느 정도 복사한 형태로 나옵니다.

    OctoPrint에서 예열과 G-code 전송을 분리해두면 작업이 편합니다. 원격으로 온도만 올리고, 실제 당기는 작업은 장비 앞에서 합니다. 아래 명령은 예열용입니다.

    export OCTOPRINT_URL="http://octopi.local"
    export OCTOPRINT_API_KEY="여기에_API_KEY_입력"
    
    curl -s -X POST \
      -H "X-Api-Key: ${OCTOPRINT_API_KEY}" \
      -H "Content-Type: application/json" \
      -d '{"command":"target","targets":{"tool0":200}}' \
      "${OCTOPRINT_URL}/api/printer/tool"

    온도 값은 예시입니다. PLA, PETG, ABS, TPU처럼 소재가 바뀌면 권장 온도 범위도 달라지고, 같은 PLA라도 제조사마다 체감이 다릅니다. 안전한 기준은 늘 스풀 라벨과 제조사 자료입니다.

    G-code를 API로 직접 보내 압출 테스트를 할 수도 있습니다. 이 방식은 편하지만 위험도 있습니다. 노즐이 충분히 가열되지 않았거나 필라멘트가 걸린 상태에서 반복 압출하면 익스트루더가 손상될 수 있습니다.

    curl -s -X POST \
      -H "X-Api-Key: ${OCTOPRINT_API_KEY}" \
      -H "Content-Type: application/json" \
      -d '{"commands":["M109 S200","M83","G1 E5 F60","G1 E5 F60"]}' \
      "${OCTOPRINT_URL}/api/printer/command"

    분리 청소나 노즐 교체가 필요하다면 핫엔드 구조를 먼저 확인하세요. 일부 핫엔드는 가열 상태에서 노즐을 체결해야 누출이 줄어듭니다. 다만 화상 위험이 크고, 과한 힘을 주면 히트 블록이나 히트 브레이크 나사산을 망가뜨릴 수 있습니다. 저는 내열 장갑, 맞는 렌치, 안정적인 헤드 고정이 없으면 무리하지 않습니다. 시간 압박이 있는 상황에서는 노즐 교체가 청소보다 더 합리적인 선택일 때가 많습니다.

    제가 가장 오래 헤맨 원인은 히트 브레이크 냉각이었습니다

    한 번은 출력 초반 20분 정도는 멀쩡한데, 이후부터 압출이 약해지고 익스트루더에서 딱딱 소리가 났습니다. 노즐을 청소했고 필라멘트도 바꿨습니다. 그래도 같은 시점에 비슷하게 실패했습니다. 이 패턴은 노즐 끝 찌꺼기보다는 열이 위로 올라가는 문제에 가까웠습니다.

    핫엔드는 아래쪽만 뜨거워야 합니다. 히트 브레이크 위쪽과 방열판은 필라멘트가 단단한 상태로 지나가야 합니다. 그런데 방열팬이 약하거나, 먼지가 쌓였거나, 팬 커넥터가 느슨하거나, 챔버 내부 온도가 높으면 열 경계가 위로 밀립니다. 필라멘트는 노즐에 도착하기 전에 말랑해지고, 벽면에 들러붙고, 익스트루더는 더 세게 밀다가 결국 헛돕니다. 겉으로 보면 그냥 노즐 막힘입니다.

    • 핫엔드 팬: 전원을 켰을 때 계속 도는 구조인지, 특정 온도 이후 도는 구조인지 확인합니다. 회전은 해도 풍량이 약하면 먼지와 덕트를 봅니다.
    • 방열판 먼지: 팬이 정상이어도 방열판 사이에 먼지가 끼면 열을 빼지 못합니다.
    • 리트랙션: 되당김이 과하면 녹은 재료가 위쪽으로 올라가 히트 브레이크 부근에서 굳거나 끈적해질 수 있습니다.
    • PTFE 튜브 끝단: 보우든 구조나 PTFE 라이닝 핫엔드에서는 튜브 끝이 타거나 벌어지면 녹은 재료가 틈에 고입니다.
    • 출력 환경: 밀폐 챔버, 고온 소재, 장시간 출력은 히트 크리프 가능성을 키웁니다. PLA처럼 낮은 온도에서 부드러워지는 소재는 특히 민감합니다.

    이럴 때는 노즐 청소보다 재현 테스트가 먼저입니다. 짧은 수동 압출은 정상인데 긴 출력에서만 실패한다면, 냉각과 리트랙션을 봐야 합니다. 슬라이서에서 리트랙션 거리와 속도를 줄여보고, 같은 모델을 낮은 리트랙션 조건으로 짧게 재출력하면 방향이 보입니다. 수치 자체는 프린터 구조와 익스트루더 방식에 따라 달라서 단정하지 않겠습니다. 중요한 건 “더 많이 되당기면 스트링이 줄어든다”는 단순한 생각이 막힘을 만들 수 있다는 점입니다.

    검증은 긴 출력이 아니라 짧은 압출 테스트로 끝냅니다

    청소가 끝났다고 바로 긴 출력물을 다시 걸면 실패 원인을 확인하기 어렵습니다. 저는 세 단계로 검증합니다. 공중 압출, 첫 레이어, 단일 벽 또는 작은 사각형입니다. 이 순서가 좋은 이유는 변수 하나씩만 추가되기 때문입니다.

    1. 공중 압출: 노즐 내부 흐름만 봅니다. 베드 높이와 접착 변수는 제외됩니다.
    2. 첫 레이어: Z 오프셋과 노즐 외부 오염을 확인합니다.
    3. 단일 벽 테스트: 압출 안정성, 온도, 슬라이서 조건을 짧게 검증합니다.
    M109 S200 ; 실제 소재 권장 온도로 변경
    M83
    G1 E10 F80
    G4 P2000 ; 2초 대기
    G1 E10 F80
    G4 P2000
    G1 E10 F80
    M82

    여기서 압출물이 일정하면 노즐 출구는 대체로 살아 있습니다. 압출 시작 때만 잠깐 나오고 곧 얇아지면 내부 저항이나 이송 문제가 남아 있을 수 있습니다. 압출은 정상인데 첫 레이어에서만 끊기면 노즐 막힘보다 Z 오프셋, 베드 레벨, 첫 레이어 속도, 노즐 외부 찌꺼기를 봅니다.

    3D 프린터 노즐 막힘 압출 테스트 결과 비교

    정상 압출은 굵기가 일정하고 아래로 부드럽게 떨어집니다. 막힘이 남아 있으면 흐름이 툭툭 끊기거나 옆으로 휘고, 온도나 이송 문제가 있으면 초반과 후반의 굵기가 달라집니다.

    첫 레이어 검증에서는 큰 모델보다 작은 사각형이 낫습니다. 실패 비용이 낮고, 노즐이 베드에 너무 붙었는지 바로 보입니다. 저는 첫 줄이 투명하게 짓눌리거나 재료가 옆으로만 밀리면 노즐 청소를 다시 하지 않고 Z 오프셋부터 봅니다. 반대로 베드와 무관하게 공중 압출 자체가 불안정하면 다시 콜드 풀이나 노즐 교체로 돌아갑니다.

    재발을 줄이는 슬라이서 점검 포인트

    노즐 막힘은 하드웨어 문제처럼 보이지만 슬라이서 설정이 원인이 되는 경우도 많습니다. 특히 리트랙션, 온도, 속도, 최소 레이어 시간, 냉각 조건은 서로 영향을 줍니다. 설정 하나를 과하게 잡으면 다른 문제가 튀어나옵니다.

    설정 너무 낮거나 약할 때 너무 높거나 강할 때 판단 기준
    노즐 온도 압출 저항 증가, 딱딱 소리, 층간 접착 약화 실 늘어짐, 탄화, 노즐 외부 오염 증가 필라멘트 제조사 권장 범위 안에서 테스트합니다
    리트랙션 거리 스트링 증가 히트 브레이크 쪽으로 녹은 재료가 올라가 막힘 유발 직결식과 보우든은 적정 범위가 다릅니다
    출력 속도 출력 시간이 길어지고 열 노출 시간이 늘어남 필요 압출량이 커져 온도 부족처럼 보일 수 있음 수동 압출은 정상인데 고속 구간만 실패하면 속도를 의심합니다
    냉각 팬 작은 형상에서 뭉침, 브리징 품질 저하 소재에 따라 노즐 주변 온도 안정성이 흔들릴 수 있음 소재별 권장 냉각 조건을 우선합니다
    첫 레이어 높이 접착 불량 출구 압착으로 압출 불량처럼 보임 첫 줄이 과하게 납작하면 막힘보다 Z 오프셋을 봅니다

    제가 권하는 방식은 한 번에 하나만 바꾸는 겁니다. 온도도 올리고, 리트랙션도 줄이고, 속도도 낮추면 성공해도 무엇이 원인이었는지 모릅니다. 장애 대응에서 재현성을 잃는 순간 다음 문제가 다시 어려워집니다.

    테스트용 G-code를 슬라이서 시작 G-code에 영구로 넣는 건 권하지 않습니다. 다만 별도 파일로 저장해두고 문제 상황에서만 실행하면 좋습니다. 예를 들어 “노즐 상태 확인용” 짧은 파일을 만들어두면 매번 메뉴를 뒤지지 않아도 됩니다.

    ; nozzle-flow-check.gcode
    ; 출력물 없이 노즐 흐름만 확인하는 짧은 테스트 파일
    M104 S200
    M109 S200
    M83
    G1 E5 F60
    G1 E5 F60
    G1 E5 F60
    G1 E-2 F1200
    M104 S0
    M82

    이 파일도 온도는 반드시 소재에 맞게 바꿔야 합니다. 특히 고온 소재를 쓰다가 저온 소재로 바꿀 때는 이전 소재가 충분히 빠져나오지 않으면 잔여물이 남습니다. 반대로 저온 소재를 너무 높은 온도에 오래 두면 탄화와 냄새, 막힘 가능성이 커집니다.

    자주 묻는 질문: 노즐 청소와 교체 기준

    노즐 청소 바늘로 찔러도 되나요?

    가능은 하지만 저는 임시 확인용으로만 씁니다. 바늘은 출구가 완전히 막혔는지 보는 데는 유용하지만, 찌꺼기를 안쪽으로 밀어 넣을 수 있고 작은 구경 노즐은 손상 위험도 있습니다. 바늘 후에도 흐름이 휘면 콜드 풀이나 교체가 낫습니다.

    언제 노즐을 교체하는 게 낫나요?

    콜드 풀을 반복해도 압출이 휘거나 끊기고, 외부 청소 후에도 표면 품질이 돌아오지 않으면 교체가 빠릅니다. 마모성 필라멘트를 썼거나 노즐 끝이 닳은 경우도 청소보다 교체 쪽이 합리적입니다.

    필라멘트 잔여물은 왜 생기나요?

    재료 변경 후 이전 소재가 남거나, 높은 온도에서 오래 머무르며 일부가 탄화되거나, 습기를 먹은 필라멘트가 불안정하게 녹으면서 생깁니다. 색상이 바뀐 뒤 한동안 줄무늬나 점이 섞이면 퍼지가 부족했을 가능성이 큽니다.

    히트 브레이크는 꼭 분해해야 하나요?

    처음부터 분해할 필요는 없습니다. 팬 회전, 방열판 먼지, PTFE 튜브 밀착, 리트랙션 설정부터 봅니다. 그래도 장시간 출력에서 같은 패턴으로 반복되면 그때 분해 점검을 고려합니다.

    노즐 온도를 올리면 막힘이 해결되나요?

    온도 부족이 원인일 때는 도움이 됩니다. 하지만 잔여물이 탄화된 상태거나 히트 크리프가 원인이라면 온도를 올리는 게 오히려 문제를 키울 수 있습니다. 온도 조정은 제조사 권장 범위 안에서, 한 번에 하나의 변수로 테스트하는 편이 안전합니다.

    이 상황이면 이렇게 결정하세요

    3D 프린터 노즐 막힘은 빠르게 뚫는 기술보다 빠르게 분류하는 눈이 더 중요합니다. 제 기준은 아래처럼 고정해두고 씁니다. 이 기준을 잡은 뒤로는 노즐을 불필요하게 뜯는 일이 확 줄었습니다.

    • 수동 압출부터 안 된다면 노즐 내부 막힘 가능성이 큽니다. 콜드 풀을 먼저 하고, 반복해도 흐름이 휘면 노즐 교체로 갑니다.
    • 초반은 정상인데 중간부터 멈춘다면 히트 크리프, 방열팬, 리트랙션 과다를 봅니다. 노즐만 갈면 재발할 가능성이 큽니다.
    • 익스트루더가 필라멘트를 파먹는다면 더 밀지 말고 압출 경로를 분리합니다. 기어 청소, 장력, 스풀 걸림, 보우든 튜브 마찰을 확인합니다.
    • 첫 레이어에서만 막힌 것처럼 보인다면 Z 오프셋과 베드 높이를 먼저 봅니다. 노즐이 베드에 눌려 출구가 막힌 것처럼 보일 수 있습니다.
    • 재료를 바꾼 직후라면 이전 소재 잔여물을 의심합니다. 퍼지를 충분히 하고, 색상이나 점状 찌꺼기가 남으면 콜드 풀을 시도합니다.
    • 청소해도 같은 문제가 반복된다면 노즐을 소모품으로 보고 교체하세요. 단, 교체 후에도 반복되면 팬과 히트 브레이크 쪽이 진짜 원인일 수 있습니다.

    예방은 거창하지 않습니다. 필라멘트를 습기에서 보호하고, 재료 변경 후 퍼지를 충분히 하고, 핫엔드 팬과 방열판을 가끔 확인하고, 긴 출력 전에 짧은 압출 테스트를 하는 정도면 실패율이 많이 줄어듭니다.

    제가 추천하는 최종 선택은 이렇습니다. 급한 출력이면 노즐 교체가 가장 빠릅니다. 다만 같은 증상이 두 번 이상 반복되면 청소나 교체가 아니라 원인 분석으로 전환하세요. 수동 압출 실패는 노즐 쪽, 장시간 후 실패는 열 관리 쪽, 첫 레이어만 실패는 Z 오프셋 쪽입니다. 이 세 갈래만 정확히 나눠도 불필요한 분해와 재출력을 꽤 줄일 수 있습니다.

    다음 글에서는 홈랩에서 실제로 쓰는 필라멘트 보관과 습기 관리 방법을 다룰 예정입니다. 노즐 막힘이 자주 반복된다면 청소법만큼이나 보관 환경을 보는 게 효과적입니다.

  • ZFS 스냅샷으로 홈랩 안전망 만들기 — 스냅샷은 백업이 아니다 (실제 구성 공개)

    홈랩을 굴리다 보면 “업데이트 한 번 잘못 눌렀다가 서비스가 통째로 날아갈 뻔한” 순간이 옵니다. 저는 그때마다 ZFS 스냅샷 덕에 몇 초 만에 되돌렸습니다. 이번 글은 제가 실제로 쓰는 ZFS 스냅샷 안전망 — 장점만이 아니라 아직 못 채운 구멍까지 솔직하게 공개합니다. 미화된 튜토리얼이 아니라 실제 제 홈랩 상태 그대로입니다.

    1. 왜 ZFS 스냅샷이 홈랩의 안전망인가

    스냅샷은 특정 시점의 파일시스템 상태를 통째로 얼려 두는 기능입니다. ZFS는 Copy-on-Write 구조라 스냅샷을 찍어도 공간을 거의 안 먹고(변경분만 차지), 찍는 데 1초도 안 걸립니다. 그래서 저는 위험한 작업 직전에 습관처럼 하나 찍습니다.

    # 컨테이너/데이터셋 스냅샷 하나 찍기
    zfs snapshot rpool/data/subvol-102-disk-0@pre_immich_upgrade
    
    # 현재 스냅샷 목록 확인
    zfs list -t snapshot

    실제로 제 홈랩엔 이런 스냅샷이 남아 있습니다 — subvol-102-disk-0@pre_immich_v275. Immich(사진 서버)를 v2.75로 올리기 직전에 찍어 둔 것이고, 용량은 2.38GB(그 뒤 바뀐 만큼만) 차지합니다. 업그레이드가 꼬였으면 아래 한 줄로 되돌렸을 겁니다.

    # 문제 생기면 스냅샷 시점으로 롤백
    zfs rollback rpool/data/subvol-102-disk-0@pre_immich_v275

    2. 덤으로 얻는 것 — 압축

    ZFS를 쓰면 lz4 압축이 사실상 공짜로 따라옵니다. CPU 부담은 거의 없는데 공간은 아껴 줍니다. 제 실제 압축비입니다.

    풀 용도 압축비(실측)
    rpool (SSD 236GB) 시스템·컨테이너 1.66x
    nas-data (14.5TB) 사진·미디어 파일 약 1.01x

    포인트는 압축은 데이터 종류를 탄다는 겁니다. 시스템·설정·텍스트가 많은 SSD 풀은 1.66배(≈40% 절약)나 압축되지만, 이미 압축돼 있는 사진·동영상 풀은 1.01배로 거의 효과가 없습니다. “ZFS 압축 켜면 무조건 이득”이 아니라 미디어엔 의미 없고 시스템·백업 데이터엔 크게 이득입니다.

    3. 가장 중요한 함정 — 스냅샷은 백업이 아니다

    여기서 많은 분이 착각합니다. 스냅샷은 백업이 아닙니다. 스냅샷은 같은 풀·같은 디스크 안에 있습니다. 즉 —

    • 실수로 지운 파일 복구, 잘못된 업데이트 롤백 → 스냅샷으로 충분
    • 디스크(풀) 자체가 고장, 서버 도난·화재, 랜섬웨어 → 스냅샷도 같이 사라짐

    스냅샷은 “되돌리기(undo)”이고, 백업은 “다른 장소에 복제본”입니다. 둘은 역할이 다르고 둘 다 필요합니다.

    4. 솔직한 고백 — 내 구성의 구멍

    제 홈랩을 점검해 보니 이렇습니다.

    • 스냅샷 28개가 있지만 전부 수동입니다. 자동 스냅샷 도구(sanoid 등)도, 예약 타이머도 없습니다.
    • 예약된 오프사이트 백업 잡이 없습니다. 즉 “위험 작업 전 수동 스냅샷”은 잘 하지만, 주기적 자동 스냅샷·외부 백업은 아직 못 갖췄습니다.

    이건 자랑이 아니라 많은 개인 홈랩의 현실이고, 저도 예외가 아니라는 고백입니다. 그래서 다음 단계로 아래를 정리했습니다.

    5. 제대로 하려면 — 다음 단계

    (1) 자동 스냅샷 로테이션 — sanoid
    수동 스냅샷은 잊어버리기 쉽습니다. sanoid를 쓰면 “시간별·일별·주별로 자동으로 찍고 오래된 건 자동 삭제”가 됩니다.

    # /etc/sanoid/sanoid.conf 예시
    [rpool/data]
        use_template = production
        recursive = yes
    
    [template_production]
        hourly = 24
        daily = 14
        weekly = 4
        autosnap = yes
        autoprune = yes

    (2) 진짜 백업 — 3-2-1 규칙
    데이터 3벌, 2가지 매체, 1벌은 오프사이트. ZFS라면 zfs send | zfs recv로 다른 서버·NAS에 증분 복제하거나, Proxmox 환경이면 PBS(Proxmox Backup Server)로 컨테이너·VM을 중복제거 백업하는 게 정석입니다.

    # ZFS 증분 전송(다른 풀/서버로 백업)
    zfs send -i @어제 rpool/data/subvol-102-disk-0@오늘 | \
      ssh backup-host zfs recv backup/subvol-102

    6. 결론

    ZFS 스냅샷은 홈랩에서 가장 값싸고 강력한 안전장치입니다. 위험한 작업 전에 한 줄이면 몇 초 만에 되돌릴 수 있으니까요. 하지만 스냅샷 ≠ 백업이라는 점, 그리고 수동에 기대면 언젠가 빈다는 점을 잊으면 안 됩니다. 저처럼 “수동 스냅샷은 하는데 자동화·오프사이트 백업은 아직”인 분이 많을 겁니다. 스냅샷으로 시작하되, sanoid 자동화와 3-2-1 백업까지 가는 걸 목표로 삼으시길 권합니다. 저도 그렇게 채워 갈 계획입니다.

  • 포트 개방 없이 홈랩 서비스 외부 공개하기 — Cloudflare Tunnel 실전

    홈랩에 올린 서비스를 밖에서 접속하려면 보통 공유기 포트포워딩 + DDNS를 씁니다. 그런데 저는 공유기에 포트를 단 하나도 열지 않고 이 블로그(blog.pswq.net)를 외부에 공개하고 있습니다. 방법은 Cloudflare Tunnel입니다. 실제로 제 홈랩을 이걸로 운영하면서 겪은 구성과 장단점을 그대로 공개합니다.

    1. 왜 포트포워딩을 버렸나

    전통적인 방식(포트포워딩 + DDNS)은 홈랩에서 문제가 많습니다.

    • 고정 IP가 없다 — 가정용 회선은 IP가 바뀌어서 DDNS에 의존해야 함
    • 포트를 여는 순간 공격 표면 — 열린 80/443은 전 세계 스캐너의 표적
    • 내 공인 IP가 노출 — 위치·회선이 드러남
    • 일부 ISP는 80/443 인바운드를 아예 막음

    Cloudflare Tunnel은 이걸 근본적으로 뒤집습니다. 홈랩에서 Cloudflare로 바깥으로 나가는(outbound) 연결만 맺고, 외부 요청은 그 연결을 타고 들어옵니다. 즉 공유기에 열 포트가 0개입니다.

    2. 동작 원리 한 장 요약

    방문자 → Cloudflare 엣지(HTTPS) → [터널] → cloudflared(홈랩) → 내부 서비스
                                        ↑ 홈랩이 먼저 outbound로 연결해 둠

    핵심은 인바운드 포트가 필요 없다는 점입니다. 홈랩의 cloudflared 데몬이 Cloudflare에 먼저 붙어 있고, 외부 트래픽은 그 통로로만 들어옵니다. HTTPS 인증서도 Cloudflare가 자동으로 처리해서 내가 인증서를 관리할 필요가 없습니다.

    3. 실제 구성 — 토큰(원격 관리형) 방식

    Cloudflare Tunnel은 두 가지로 설정할 수 있습니다. 저는 토큰 방식을 씁니다.

    방식 설정 위치 특징
    토큰(원격 관리형) Cloudflare Zero Trust 대시보드 웹에서 라우팅 관리, 서버엔 토큰만
    로컬 config.yml 서버의 설정 파일 파일로 버전관리, GitOps에 유리

    홈랩 서버에서 데몬을 이렇게 띄웁니다(토큰은 절대 공개 금지, 여기선 가림).

    cloudflared --no-autoupdate tunnel run --token <터널토큰>

    --no-autoupdate는 데몬이 멋대로 자동 업데이트해서 예기치 않게 재시작하는 걸 막으려고 붙였습니다(홈랩은 안정성이 우선). 서버 쪽 설정은 이게 전부고, 나머지 라우팅은 전부 대시보드에서 합니다.

    4. 공개 호스트네임 연결

    Zero Trust 대시보드 → 터널 → Public Hostname에서 도메인과 내부 서비스를 매핑합니다.

    항목 값(예)
    Subdomain / Domain blog . (내 도메인)
    Service Type HTTP
    URL http://192.168.x.x:<내부포트>

    여기서 URL은 홈랩 내부에서 본 서비스 주소입니다. 즉 방문자는 HTTPS로 blog.내도메인에 접속하지만, 터널 반대편에선 평범한 사내 HTTP 서비스로 전달됩니다. 내부 서비스는 HTTPS를 몰라도 되고, 공인 IP도 드러나지 않습니다. 저는 이 방식으로 WordPress 블로그를 외부에 공개하고 있습니다.

    5. 직접 써 보고 느낀 장단점

    좋은 점

    • 포트 개방 0 — 공유기 설정을 건드릴 필요가 없고 공격 표면이 사라짐
    • 공인 IP 숨김 — 방문자는 Cloudflare만 봄
    • HTTPS 자동 — 인증서 갱신을 신경 쓸 필요 없음
    • 무료 — 개인 홈랩 규모에선 비용이 들지 않음

    주의할 점

    • 토큰 유출은 치명적 — 토큰이 곧 터널 권한이라, 프로세스 인자·로그·스크린샷에 노출되지 않게 관리해야 함
    • Cloudflare 의존 — 트래픽이 Cloudflare를 경유하므로 서비스 장애의 영향을 받음
    • 관리자 페이지는 반드시 보호 — 외부 공개 시 Cloudflare Access로 접근 제어를 걸어 두는 걸 권장(로그인/관리 경로)

    6. 결론

    홈랩 서비스를 밖에 내놓을 때, 저는 이제 포트포워딩으로 돌아갈 이유를 못 느낍니다. 포트 0개, IP 숨김, 무료 HTTPS라는 조합은 개인 홈랩에 특히 잘 맞습니다. 단, 토큰 관리와 관리자 경로 보호만 확실히 하면 됩니다. 홈랩 서비스를 외부에 공개할 계획이라면 Cloudflare Tunnel부터 검토해 보시길 권합니다.