본문으로 건너뛰기
피드

Caddy 인증서 만료 원인은 systemd-resolved와 NextDNS 조합이었다

devops 약 10분
vote
0
댓글
북마크

Matrix 홈서버 앞단의 Caddy 인증서가 만료됐고, 원인은 Caddy가 아니라 특정 도메인의 NXDOMAIN 응답에서 멈추는 systemd-resolved 경로였어. Docker DNS, host stub resolver, NextDNS over TLS, ACME DNS-01 챌린지가 겹치면서 42시간 동안 갱신 실패가 조용히 누적된 장애 후기야.

  • 1

    Caddy는 10분마다 인증서 갱신을 큐에 넣었지만 DNS-01 챌린지에서 42시간 동안 실패했다

  • 2

    직접 upstream DNS에 질의하면 NXDOMAIN이 즉시 왔지만 systemd-resolved의 127.0.0.53 경로만 타임아웃됐다

  • 3

    문제 경로는 Docker 127.0.0.11에서 host 127.0.0.53을 거쳐 NextDNS DoT로 가는 DNS 체인이었다

  • 4

    임시 조치는 Caddy 컨테이너에 1.1.1.1과 8.8.8.8 DNS를 직접 지정하는 것이었고, 근본 조치는 서버에 불필요한 NextDNS DoT 설정을 제거하는 것이었다

  • 5

    인증서 만료 모니터링과 로그 기반 ACME 갱신 실패 알림이 없으면 자동 갱신 실패를 너무 늦게 알게 된다

장애는 인증서 만료 알림에서 시작됨

  • Matrix 홈서버의 federation 포트 인증서가 만료되면서 알림이 터졌음

    • federation 포트는 다른 Matrix 홈서버가 이벤트를 주고받는 공개 TLS 엔드포인트임
    • 인증서가 신뢰되지 않으면 외부 서버가 메시지를 보내지 못하고, 내 서버도 outbound federation이 깨짐
  • 문제의 인증서는 matrix-fed.takeonme.org용 Let's Encrypt 인증서였음

    • 앞단에는 Caddy 컨테이너가 있었고, Cloudflare DNS-01 챌린지로 자동 갱신하도록 설정돼 있었음
    • 인증서는 2월 초 발급, 5월 3일 만료였고 보통 60일 즈음 갱신됐어야 함
  • 컨테이너 상태는 멀쩡했음

    • Synapse, Element, Postgres, Caddy, Redis 등 Matrix 관련 컨테이너는 전부 떠 있었음
    • 그래서 애플리케이션 장애가 아니라 TLS 엔드포인트의 인증서 갱신 문제로 범위가 좁혀짐

Caddy는 계속 재시도하고 있었지만 실패 중이었음

  • Caddy 로그에는 인증서 갱신 필요 메시지가 반복되고 있었음

    • ACME Renewal Information(ARI)이 갱신 시점을 알려줬고, Caddy의 cache maintenance도 갱신 큐에 넣고 있었음
    • 겉으로는 자동 갱신 루프가 잘 돌고 있는 것처럼 보였음
  • 실제 실패 지점은 DNS-01 챌린지였음

    • Caddy가 _acme-challenge.matrix-fed.takeonme.org의 zone을 찾으려다 SERVFAIL을 받았음
    • 로그에는 29번째 시도, 경과 시간 151,329초가 찍혀 있었는데 대략 42시간 동안 실패한 셈임
    • 다음 재시도는 6시간 뒤로 밀려 있었고, 그 사이 인증서는 이미 만료될 수 있었음

⚠️주의

> 자동 갱신이 켜져 있어도 갱신 실패가 알림으로 올라오지 않으면 그냥 조용히 만료될 수 있음. Caddy 로그에 tls.renew 에러가 쌓이는지 따로 봐야 함.

  • 더 찜찜한 점은 Caddy가 Let's Encrypt staging으로 넘어가 있었다는 것임
    • 반복 실패 후 certmagic이 production 대신 staging CA를 시도한 상태였음
    • staging 인증서는 브라우저나 Matrix 홈서버가 신뢰하지 않기 때문에, 설령 성공해도 운영 장애는 해결되지 않음

진짜 원인은 로컬 DNS 경로였음

  • upstream DNS를 직접 찌르면 응답은 정상적이었음

    • Cloudflare 1.1.1.1, NextDNS, VPS 제공 DNS 모두 _acme-challenge.matrix-fed.takeonme.org에 즉시 NXDOMAIN을 반환함
    • NXDOMAIN은 레코드가 없다는 정상 응답이고, Caddy가 챌린지 때만 TXT 레코드를 만들었다 지우는 구조라 기대한 결과임
  • 그런데 로컬 stub resolver인 127.0.0.53은 타임아웃이 났음

    • 컨테이너 내부에서는 Docker의 내장 DNS 127.0.0.11을 거침
    • 그 다음 host의 systemd-resolved stub resolver 127.0.0.53으로 가고, 거기서 upstream으로 나가는 체인임
    • Caddy 컨테이너에서 보이는 실패는 사실 host resolver의 실패가 전파된 결과였음
sequenceDiagram
    participant 캐디컨테이너
    participant 도커DNS
    participant 호스트리졸버
    participant 넥스트DNS
    participant 렛츠인크립트
    캐디컨테이너->>도커DNS: _acme-challenge SOA 질의
    도커DNS->>호스트리졸버: 127.0.0.53으로 전달
    호스트리졸버->>넥스트DNS: DoT로 질의
    넥스트DNS-->>호스트리졸버: NXDOMAIN 응답
    호스트리졸버--x도커DNS: 특정 zone에서 타임아웃
    캐디컨테이너-->>렛츠인크립트: DNS-01 챌린지 실패
  • 이상한 건 systemd-resolved가 완전히 죽은 게 아니었다는 점임
    • google.com은 바로 풀렸고, 다른 zone의 NXDOMAIN도 바로 돌아왔음
    • matrix-fed.takeonme.org처럼 존재하는 이름도 정상 응답함
    • 오직 takeonme.org 아래의 존재하지 않는 이름, 즉 특정 zone의 NXDOMAIN만 타임아웃이 났음

먼저 불을 끄고, 그다음 뿌리를 잘랐음

  • 임시 복구는 Caddy 컨테이너가 host resolver를 우회하게 만드는 것이었음

    • compose 설정에 dns: [1.1.1.1, 8.8.8.8]를 추가함
    • 재배포 후 30초 안에 Caddy가 다시 갱신을 시도했고, 이번에는 production Let's Encrypt로 인증서 갱신에 성공함
    • 새 인증서는 5월 4일부터 8월 2일까지 유효했고 Matrix federation도 복구됨
  • 하지만 host resolver 버그는 그대로 남아 있었음

    • 직접 NextDNS DoT로 dig +tls를 날리면 같은 도메인도 즉시 NXDOMAIN이 왔음
    • 즉 NextDNS 자체나 DoT 프로토콜이 아니라, systemd-resolved가 이 조합의 응답을 다루는 경로에서만 문제가 난 셈임
  • 근본 조치는 서버에 불필요한 NextDNS DoT 설정을 제거하는 것이었음

    • /etc/systemd/resolved.conf에 NextDNS 프로필 기반 DNS와 DNSOverTLS=yes가 박혀 있었음
    • 이 서버는 클라이언트 기기가 아니라서 광고 차단이나 추적 방지 DNS 프로필의 이득이 거의 없었음
    • 설정을 주석 처리하고 systemd-resolved를 재시작하자, VPS 제공 link DNS로 돌아가며 NXDOMAIN이 즉시 응답됨

💡

> DNS 헬스체크로 google.com만 보는 건 거의 의미가 없음. 인증서, 서비스 디스커버리, federation처럼 실제 워크로드가 의존하는 이름을 같은 경로로 질의해야 함.

운영 교훈은 꽤 아픔

  • Caddy는 갱신 실패를 로그로는 남기지만 알아서 깨워주진 않음

    • 42시간 동안 구조화된 JSON 로그에 실패 원인이 찍혔는데, 모니터링은 그걸 보고 있지 않았음
    • 공개 엔드포인트의 인증서 만료 시점과 ACME 갱신 실패 로그를 따로 알림으로 잡아야 함
  • 재시도 백오프도 장애를 키울 수 있음

    • Caddy가 CA를 과도하게 두드리지 않으려고 6시간 뒤 재시도를 잡는 건 이해됨
    • 하지만 인증서가 3시간 뒤 만료된다면 그 백오프는 운영 관점에서 너무 김
  • 설정 드리프트가 진짜 무서운 부분임

    • NextDNS DoT 설정은 예전에 개인 기기 템플릿에서 서버로 흘러들어온 잔재였음
    • 몇 년 동안 정상처럼 보이다가 특정 zone, 특정 응답 타입, 특정 resolver 조합에서만 터짐
    • 감사 체크리스트에서 ‘이 서버에 클라이언트용 DNS 프로필이 왜 있지?’를 잡아내지 못하면 이런 문제는 계속 숨어 있음

기술 맥락

  • 이 장애의 핵심 선택지는 Caddy를 고치는 게 아니라 DNS 경로를 어디까지 신뢰할지였어요. Caddy는 DNS-01 챌린지를 하려면 _acme-challenge 레코드의 zone을 찾아야 하고, 그 과정에서 SOA 질의가 안정적으로 돌아와야 하거든요.

  • Docker 컨테이너 안에서 DNS가 실패하면 컨테이너만 보는 실수를 하기 쉬워요. 하지만 여기서는 컨테이너의 127.0.0.11이 host의 127.0.0.53으로 넘기고 있었기 때문에, 실제 장애 지점은 systemd-resolved였어요.

  • NextDNS DoT 설정을 제거한 이유도 단순히 우회가 아니에요. 서버는 브라우징을 하지 않으니 클라이언트용 DNS 필터링 프로필의 이득이 작고, 반대로 인증서 갱신 같은 핵심 운영 경로에 추가 복잡도를 넣고 있었거든요.

  • Caddy 컨테이너에 별도 DNS를 남겨둔 건 방어선이에요. host resolver가 다시 이상해져도 인증서 갱신 경로는 Cloudflare와 Google DNS를 직접 타니까, 같은 종류의 장애가 바로 반복될 가능성을 낮춰줘요.

  • 모니터링 관점에서는 ‘서비스가 떠 있다’와 ‘인증서가 갱신되고 있다’를 분리해서 봐야 해요. 컨테이너는 전부 healthy였지만 public TLS endpoint는 이미 신뢰를 잃었고, 그 차이를 잡는 건 로그 알림과 실제 엔드포인트 인증서 만료 체크예요.

이 글은 ‘DNS가 된다’는 말이 얼마나 허술한지 잘 보여줌. 운영에서는 google.com이 풀리는지가 아니라, 내 서비스가 실제로 의존하는 이름과 응답 타입이 같은 경로에서 안정적으로 풀리는지가 중요함.

댓글

댓글

댓글을 불러오는 중...

devops

하이퍼스케일 데이터, 비트코인 채굴장을 최대 30억 달러짜리 AI 데이터센터로 전환

하이퍼스케일 데이터의 자회사 ACS가 캘리포니아 네오클라우드 업체와 미시간 캠퍼스 AI 컴퓨팅 용량 공급 계약을 맺었어. 초기 20메가와트로 시작해 최대 52메가와트까지 늘릴 수 있고, 모든 옵션이 행사되면 계약 규모가 30억 달러를 넘을 수 있다는 내용이야.

devops

KT, 분사했던 KT클라우드 다시 합치나…AIDC 투자 때문에 판 다시 짜는 중

KT가 2022년 분사한 KT클라우드를 다시 합치는 방안을 검토 중인 것으로 알려졌어. 클라우드, 인공지능 데이터센터, 네트워크 인프라를 한 몸처럼 묶어 B2B 경쟁력을 키우려는 흐름으로 읽혀. 다만 KT는 아직 구체적으로 검토한 바 없다는 입장이야.

devops

KT, KT클라우드 다시 합치나…AI 인프라 패키지 전략 시동

KT가 2022년 분사했던 KT클라우드를 다시 흡수하는 방안을 검토 중인 것으로 알려졌다. 인공지능 확산으로 클라우드, 데이터센터, 네트워크를 묶은 기업간거래 인프라 수요가 커지면서 KT 본체의 자금력과 영업력을 활용하려는 전략으로 보인다. 다만 외부 투자자 지분 처리와 통신·클라우드 조직 통합이 실제 관건이다.

devops

Bunny DNS, 쿼리 과금 없애고 500개 도메인까지 무료로 푼다

bunny.net이 Bunny DNS의 DNS 쿼리 과금을 없애고 계정당 최대 500개 도메인까지 무료 DNS 호스팅을 제공하기로 했어. 단순한 무료화가 아니라 CDN, 엣지 보안, 스마트 라우팅을 DNS에서 바로 연결하는 방향으로 플랫폼 진입점을 재정리하는 움직임이야.

devops

가비아, AWS 중소·중견기업 클라우드 역량 인증 받음

가비아가 AWS의 ‘AWS SMB 컴피턴시’를 취득했다. 이 인증은 중소·중견기업의 클라우드 전환과 운영 지원 역량을 검증하는 제도로, 가비아는 운영 프레임워크와 고객 레퍼런스를 인정받았다.