본문으로 건너뛰기
cccskills
무료GitHub에 공개

certmanager-dns01-gke-private-cluster

Fix cert-manager DNS01 ACME challenges stuck in "pending" state with "DNS record not yet propagated" inside GKE private clusters, even when TXT records exist in Cloudflare DNS. Use when: (1) cert-manager challenges show "pending" for hours with propagation check failures, (2) dig from outside cluster shows correct TXT records but cert-manager can't verify them, (3) Using Cloudflare DNS01 solver in a GKE private cluster with Cloud NAT, (4) Google Cloud intercepts 8.8.8.8 DNS queries returning NXDOMAIN for Cloudflare-managed records, (5) Even --dns01-recursive-nameservers with 1.1.1.1 doesn't fix the propagation check despite TXT records being verifiable from busybox pods in the same namespace. Covers the full debugging flow and certbot manual workaround.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

설치 방법 보기

포함된 파일(1)

  • SKILL.md6.9 KB

SKILL.md(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

Cert-Manager DNS01 Challenge Failure in GKE Private Clusters

Problem

Cert-manager DNS01 ACME challenges get stuck in "pending" state indefinitely inside GKE private clusters. The propagation check fails with "DNS record for X not yet propagated" even though the TXT records are correctly created in Cloudflare and verifiable from everywhere — including from inside the cluster using busybox pods.

Context / Trigger Conditions

  • cert-manager with Cloudflare DNS01 solver
  • GKE private cluster (private nodes, public endpoint) with Cloud NAT
  • Challenges show presented: true but state: pending for hours
  • cert-manager logs show: "propagation check failed" err="DNS record for \"example.com\" not yet propagated"
  • dig TXT _acme-challenge.example.com from outside returns correct value
  • busybox nslookup from inside cluster also returns correct value
  • Certificate resource shows Ready: False with reason: RequestChanged

Root Causes Discovered

1. Google Cloud intercepts DNS to 8.8.8.8

Inside GKE VPCs, DNS queries to 8.8.8.8 are intercepted by Google Cloud infrastructure. For Cloudflare-managed domains, this can return NXDOMAIN even when the record exists. This is because Google routes 8.8.8.8 through their internal DNS infrastructure which may have different resolution behavior than the public Google DNS service.

Verification:

# From inside cluster - returns NXDOMAIN
kubectl run dns-test --image=busybox:1.36 --rm -i --restart=Never -- \
  nslookup -type=TXT _acme-challenge.example.com 8.8.8.8

# From inside cluster - returns correct result
kubectl run dns-test --image=busybox:1.36 --rm -i --restart=Never -- \
  nslookup -type=TXT _acme-challenge.example.com 1.1.1.1

2. Cert-manager propagation check still fails with correct resolvers

Even after configuring --dns01-recursive-nameservers=1.1.1.1:53,1.0.0.1:53 and --dns01-recursive-nameservers-only=true, cert-manager's propagation check may still fail. The Go DNS library used by cert-manager (miekg/dns) behaves differently from busybox's nslookup. The exact cause is unclear but may relate to:

  • DNS response parsing differences between miekg/dns and system resolvers
  • TCP vs UDP DNS query differences
  • Internal cert-manager caching or timing issues
  • Cloud NAT interaction with DNS traffic patterns

Solution

Attempt 1: Configure recursive DNS resolvers (may not be sufficient)

Add to cert-manager Helm values:

dns01RecursiveNameservers: "1.1.1.1:53,1.0.0.1:53"
dns01RecursiveNameserversOnly: true

IMPORTANT: Do NOT use 8.8.8.8 — Google Cloud intercepts this inside GKE VPCs.

For ArgoCD-managed cert-manager (Helm chart), add to the Application valuesObject:

valuesObject:
  dns01RecursiveNameservers: "1.1.1.1:53,1.0.0.1:53"
  dns01RecursiveNameserversOnly: true

Attempt 2: Manual cert generation with certbot (reliable workaround)

If the resolver fix doesn't work, generate the cert locally and inject it:

# Install certbot with Cloudflare plugin
pipx install certbot
pipx inject certbot certbot-dns-cloudflare

# Get Cloudflare API token from cluster
CF_TOKEN=$(kubectl get secret cloudflare-api-token-secret -n cert-manager \
  -o jsonpath='{.data.api-token}' | base64 -d)

# Create credentials file
mkdir -p /tmp/certbot-cf
echo "dns_cloudflare_api_token = $CF_TOKEN" > /tmp/certbot-cf/cloudflare.ini
chmod 600 /tmp/certbot-cf/cloudflare.ini

# Generate certificate
certbot certonly \
  --dns-cloudflare \
  --dns-cloudflare-credentials /tmp/certbot-cf/cloudflare.ini \
  --dns-cloudflare-propagation-seconds 30 \
  -d '*.example.com' -d 'example.com' \
  --non-interactive --agree-tos --email admin@example.com \
  --config-dir /tmp/certbot-cf/config \
  --work-dir /tmp/certbot-cf/work \
  --logs-dir /tmp/certbot-cf/logs \
  --key-type ecdsa --elliptic-curve secp256r1

# Inject into cluster
kubectl create secret tls wildcard-tls-secret \
  --cert=/tmp/certbot-cf/config/live/example.com/fullchain.pem \
  --key=/tmp/certbot-cf/config/live/example.com/privkey.pem \
  -n nginx-gateway --dry-run=client -o yaml | kubectl apply -f -

# Clean up
rm -rf /tmp/certbot-cf

Verification

# Check the certificate served by the gateway
echo | openssl s_client -connect upload.example.com:443 \
  -servername upload.example.com 2>/dev/null | \
  openssl x509 -noout -subject -ext subjectAltName

# Test endpoint
curl -s https://upload.example.com/

Debugging Commands

# Check challenge status
kubectl get challenges -n nginx-gateway

# Check challenge details (key = expected TXT value)
kubectl get challenge <name> -n nginx-gateway \
  -o jsonpath='domain: {.spec.dnsName}, key: {.spec.key}, presented: {.status.presented}'

# Check cert-manager args
kubectl get deployment cert-manager -n cert-manager \
  -o jsonpath='{.spec.template.spec.containers[0].args}'

# Check certificate status
kubectl get certificate wildcard-tls -n nginx-gateway -o yaml

# Check what cert is currently in the secret
kubectl get secret wildcard-tls-secret -n nginx-gateway \
  -o jsonpath='{.data.tls\.crt}' | base64 -d | \
  openssl x509 -noout -subject -ext subjectAltName

# Test DNS from inside cluster
kubectl run dns-test --image=busybox:1.36 --rm -i --restart=Never -- \
  nslookup -type=TXT _acme-challenge.example.com 1.1.1.1

# Delete stale order to force refresh
kubectl delete order <order-name> -n nginx-gateway

Notes

  • The manually generated cert expires after 90 days and won't auto-renew
  • cert-manager will eventually overwrite the manually injected secret when/if it successfully issues its own cert — this is fine and desired
  • When requesting both wildcard (*.example.com) and base (example.com), ACME requires separate authorizations that both use _acme-challenge.example.com TXT records with different values
  • ArgoCD root apps with automated.enabled: false won't auto-sync — you need to trigger manually via kubectl patch app root -n argocd --type merge -p '{"operation":{"sync":...}}'
  • The cert-manager container is distroless (no shell) — you can't exec into it to debug DNS

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

Fix ArgoCD ExternalSecret deployment failing with "namespace X is not permitted in project Y". Use when: (1) ExternalSecret shows OutOfSync in ArgoCD but won't sync, (2) ArgoCD application status shows "namespace X is not permitted in project 'infrastructure'", (3) ExternalSecret targets a namespace managed by a different ArgoCD project, (4) Using apps-of-apps pattern with separate infrastructure and application projects.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

divinevideo/divine-mobile2652026년 10월 22일 업데이트

Art direction for any content — reads text, PDF, Word, HTML, PPT, then proposes 2-3 creative directions with photography style, mood, and visual language. After selection, generates AI image prompts and visual briefs section-by-section. Use when the user shares content and needs visual direction, image sourcing, or creative direction for any material.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

divinevideo/divine-mobile2652026년 10월 22일 업데이트

Fix "Null check operator used on a null value" errors when an object is set to null during an async await. Use when: (1) Object reference is nullified while awaiting, (2) Code accesses object with ! after await returns, (3) Cancel/dispose operations run concurrently with async operations on same object. Solution: capture local reference before await.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

divinevideo/divine-mobile2652026년 10월 22일 업데이트

Add custom metadata headers (x-amz-meta-*) to AWS v4 signed requests for GCS S3-compatible API. Use when: (1) Adding custom metadata to GCS uploads via S3 API, (2) Getting signature mismatch errors after adding new headers, (3) x-amz-meta-* headers being ignored or causing 403 errors. Custom headers MUST be included in canonical headers and signed headers list.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

divinevideo/divine-mobile2652026년 10월 22일 업데이트

Fix password/secret authentication failures caused by trailing newlines when creating Google Cloud secrets (or similar) with bash here-strings. Use when: (1) Password authentication fails with correct password, (2) Secret created with `<<< "value"` syntax, (3) Error like "password authentication failed" or "invalid token" despite correct value. Bash here-strings (`<<<`) add a trailing newline that corrupts secrets.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

divinevideo/divine-mobile2652026년 10월 22일 업데이트

Fix silent video/media processing failures caused by URL extraction code that filters on file extensions (.mp4, .webm, .webp). Use when: (1) Media moderation, transcoding, or analysis silently skips files from Blossom or content-addressed storage servers, (2) URL extraction from Nostr event tags (imeta, r tags) drops URLs without recognized extensions, (3) CDN fallback URLs append .mp4 but the actual server uses extensionless content-addressed paths like /{sha256}. Common in Nostr video events (kind 34236) where different clients use different URL formats.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

divinevideo/divine-mobile2652026년 10월 22일 업데이트

divinevideo의 스킬 모두 보기

이 스킬의 문제 신고