26M18a

Young-Kyoo Kim·2026년 5월 18일

원인은 JupyterLab/Spark의 인증서 자체 문제라기보다, ingress-nginx controller 내부의 TLS 인증서 캐시 메모리 부족일 가능성이 큽니다.

해당 메시지는 ingress-nginx의 Lua 동적 설정 처리에서 /configuration/servers 요청을 처리하면서 certificate_data shared dictionary에 인증서를 저장하려고 했는데, 공간이 부족해서 기존 LRU 항목을 밀어낸 상황과 일치합니다. ingress-nginx 코드에서도 certificate_data:set() 후 공간 부족으로 항목이 강제 제거되면 "certificate_data dictionary is full, LRU entry has been removed..."를 로깅하도록 되어 있습니다. (GitHub) OpenResty의 ngx.shared.DICT:set()도 shared memory가 부족하면 기존 항목을 LRU 방식으로 제거할 수 있고, 이때 forcible=true가 반환된다고 설명합니다. (OpenResty Reference)

1. 즉시 조치: ingress-nginx lua-shared-dicts 증설

현재 가장 직접적인 해결책은 ingress-nginx ConfigMap에 아래 값을 추가하거나 늘리는 것입니다.

data:
  lua-shared-dicts: "certificate_data: 100, certificate_servers: 20, ocsp_response_cache: 20"

certificate_data는 실제 인증서/키 데이터를 저장하는 영역이고, certificate_servers는 hostname과 인증서 매핑 쪽에 사용됩니다. ingress-nginx 기본값은 코드 기준으로 certificate_data: 20480KB, 즉 약 20MiB, certificate_servers: 5120KB, ocsp_response_cache: 5120KB 수준입니다. 대규모 JupyterLab/Spark 사용자별 Ingress, namespace별 TLS Secret, 긴 인증서 체인 환경에서는 이 값이 쉽게 부족할 수 있습니다. (GitHub) ingress-nginx 공식 ConfigMap 문서에서도 lua-shared-dicts로 기본 Lua shared dictionary 크기를 커스터마이즈할 수 있고, 예시로 certificate_data: 100을 제시합니다. 단위를 생략하면 MB로 해석됩니다. (Kubernetes)

운영 환경에서는 우선 아래 정도를 권장합니다.

lua-shared-dicts: "certificate_data: 100, certificate_servers: 20, ocsp_response_cache: 20"

TLS Secret이 수천 개 이상이거나 namespace마다 동일한 wildcard 인증서를 복사해서 쓰는 구조라면 다음처럼 더 키우는 것도 고려할 수 있습니다.

lua-shared-dicts: "certificate_data: 150, certificate_servers: 50, ocsp_response_cache: 50"

단, 현재 ingress-nginx 코드 기준으로 dictionary 하나의 최대 허용값은 204800KB, 즉 약 200MiB입니다. 이보다 크게 설정하면 무시될 수 있습니다. (GitHub)

2. 적용 예시

ConfigMap 이름은 환경마다 다를 수 있지만 보통 아래 중 하나입니다.

kubectl get cm -A | grep -i ingress

예를 들어 namespace가 ingress-nginx, ConfigMap이 ingress-nginx-controller라면:

kubectl -n ingress-nginx patch configmap ingress-nginx-controller \
  --type merge \
  -p '{"data":{"lua-shared-dicts":"certificate_data: 100, certificate_servers: 20, ocsp_response_cache: 20"}}'

Helm으로 관리 중이면 values에 넣는 방식이 더 안전합니다.

controller:
  config:
    lua-shared-dicts: "certificate_data: 100, certificate_servers: 20, ocsp_response_cache: 20"

적용 후에는 ingress controller를 reload/restart해야 합니다. Lua shared dict는 NGINX shared memory zone이라 런타임에서 자유롭게 확장되는 구조가 아니며, 설정 변경 후 reload가 필요합니다. OpenResty 쪽 설명에서도 shared dict 공간이 부족하면 NGINX 설정을 수정하고 reload해야 한다고 설명합니다. (API7.ai)

kubectl -n ingress-nginx rollout restart deployment/ingress-nginx-controller
kubectl -n ingress-nginx rollout status deployment/ingress-nginx-controller

운영 중이면 ingress-nginx controller replica가 2개 이상인지 먼저 확인하세요.

kubectl -n ingress-nginx get deploy ingress-nginx-controller

1개 replica만 있다면 restart 중 짧은 영향이 있을 수 있으므로, 가능하면 2개 이상으로 운영하는 것이 좋습니다.

3. 적용 확인

설정 반영 여부는 controller pod 안의 NGINX 설정에서 확인할 수 있습니다.

POD=$(kubectl -n ingress-nginx get pod \
  -l app.kubernetes.io/component=controller \
  -o jsonpath='{.items[0].metadata.name}')

kubectl -n ingress-nginx exec "$POD" -- nginx -T 2>/dev/null \
  | grep -E 'lua_shared_dict +(certificate_data|certificate_servers|ocsp_response_cache)'

예상 출력은 아래와 비슷해야 합니다.

lua_shared_dict certificate_data 100m;
lua_shared_dict certificate_servers 20m;
lua_shared_dict ocsp_response_cache 20m;

이후 로그에서 아래 메시지가 사라지는지 확인합니다.

kubectl -n ingress-nginx logs -l app.kubernetes.io/component=controller --since=30m \
  | egrep -i 'certificate_data dictionary is full|certificate_servers dictionary is full|certificate not found|fake certificate'

JupyterLab/Spark 접속 도메인에 실제 올바른 인증서가 내려오는지도 확인합니다.

openssl s_client -connect <HOST>:443 -servername <HOST> -showcerts </dev/null 2>/dev/null \
  | openssl x509 -noout -subject -issuer -dates

간헐적으로 Kubernetes Ingress Controller Fake Certificate, default cert, self-signed cert가 보이면 이 문제가 계속 남아 있는 것입니다.

4. 왜 JupyterLab/Spark에서 간헐적으로 보이나?

JupyterLab, Spark UI, Spark History Server, 사용자별 sandbox처럼 사용자/앱별 hostname이 많고 Ingress가 자주 생성·삭제되는 환경에서는 ingress-nginx가 동적으로 server/certificate 설정을 갱신합니다. 이때 certificate_data 캐시가 부족하면 일부 인증서 항목이 LRU로 밀려나고, 특정 SNI 요청 시 올바른 인증서를 못 찾거나 default/fake certificate를 내보낼 수 있습니다. 그래서 모든 서비스가 항상 실패하는 게 아니라, 특정 사용자·특정 hostname·특정 시점에만 인증서 오류가 간헐적으로 보일 수 있습니다.

5. 근본 개선: TLS Secret 수를 줄이는 구조로 변경

증설은 즉시 조치이고, 근본적으로는 Ingress별/namespace별 TLS Secret 중복을 줄이는 것이 좋습니다.

특히 사용자별 JupyterLab/Spark가 다음처럼 구성되어 있다면 문제가 커집니다.

user-a.ns-a.example.com -> ns-a/tls-secret
user-b.ns-b.example.com -> ns-b/tls-secret
user-c.ns-c.example.com -> ns-c/tls-secret
...

가능하면 아래 구조를 권장합니다.

*.lab.example.com
*.spark.example.com
*.notebook.example.com

그리고 ingress-nginx controller에 wildcard 인증서를 default SSL certificate로 설정합니다. ingress-nginx는 --default-ssl-certificate=<namespace>/<secret> 옵션을 통해 기본 인증서를 지정할 수 있고, 이 옵션이 없으면 self-signed certificate를 사용한다고 문서화되어 있습니다. 또한 tls: 섹션은 두되 secretName을 생략하면 default certificate를 사용하면서 HTTPS redirect를 강제할 수 있습니다. (Kubernetes)

예시:

controller:
  extraArgs:
    default-ssl-certificate: "ingress-nginx/lab-wildcard-tls"

각 사용자 Ingress는 가능하면 개별 secretName을 제거합니다.

spec:
  tls:
    - hosts:
        - user-a.lab.example.com
  rules:
    - host: user-a.lab.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: jupyterlab
                port:
                  number: 8888

이렇게 하면 인증서 데이터 수가 사용자 수만큼 증가하지 않고, hostname 매핑 중심으로만 증가하므로 certificate_data 압박이 크게 줄어듭니다.

6. 현재 환경에서 점검할 명령어

TLS host 개수:

kubectl get ingress -A -o json \
  | jq '[.items[].spec.tls[]?.hosts[]?] | unique | length'

Ingress에서 참조하는 TLS Secret 개수:

kubectl get ingress -A -o json \
  | jq '[.items[] as $i
        | ($i.spec.tls[]?
        | select(.secretName != null)
        | "\($i.metadata.namespace)/\(.secretName)")]
        | unique | length'

TLS Secret 전체 크기 대략 확인:

kubectl get secret -A -o json \
  | jq '[.items[]
        | select(.type=="kubernetes.io/tls")
        | ((.data["tls.crt"] // "" | @base64d | length)
        +  (.data["tls.key"] // "" | @base64d | length))]
        | add / 1024 / 1024'

이 값이 이미 10~20MiB 근처이거나 그 이상이면 기본 certificate_data 20MiB는 부족할 가능성이 큽니다. 실제 shared dict에는 key, metadata, Lua/OpenResty 관리 오버헤드도 들어가기 때문에 단순 PEM 크기보다 여유를 크게 잡아야 합니다.

결론

우선 아래 설정을 적용하는 것이 1차 해결책입니다.

lua-shared-dicts: "certificate_data: 100, certificate_servers: 20, ocsp_response_cache: 20"

그 후에도 같은 로그가 남으면 certificate_data를 150~200까지 올리되, 200MiB 한계에 가까워진다면 단순 증설보다 wildcard/default SSL certificate 사용, per-user TLS Secret 제거, IngressClass/ingress controller 분리를 검토하는 것이 맞습니다.

==

원인 분석

이 에러는 nginx-ingress controller (ingress-nginx) 의 Lua 공유 딕셔너리(certificate_data)가 가득 찼을 때 발생합니다.

certificate_Data dictionary is full → LRU 방식으로 오래된 인증서 항목 제거
→ 해당 인증서가 필요한 요청에서 간헐적 TLS/인증서 에러 발생

해결 방법

방법 1. lua-shared-dicts 용량 증가 (권장)

ingress-nginx ConfigMap을 수정하여 딕셔너리 크기를 늘립니다.

kubectl edit configmap ingress-nginx-controller -n ingress-nginx
apiVersion: v1
kind: ConfigMap
metadata:
  name: ingress-nginx-controller
  namespace: ingress-nginx
data:
  # 기존 값 확인 후 certificate_data 크기를 늘림
  lua-shared-dicts: "certificate_data:20,configuration_data:5"
  #                                    ↑ 기본값 20MB → 상황에 따라 40~100MB로 증가

적용 후 ingress-nginx pod 재시작:

kubectl rollout restart deployment ingress-nginx-controller -n ingress-nginx

방법 2. 현재 딕셔너리 사용량 확인

실제 얼마나 차 있는지 확인합니다.

# ingress-nginx pod 이름 확인
kubectl get pods -n ingress-nginx

# nginx 상태 엔드포인트로 lua dict 사용량 확인
kubectl exec -n ingress-nginx <pod-name> -- \
  curl -s http://localhost:10246/configuration | python3 -m json.tool

또는 nginx 내부에서 직접:

kubectl exec -n ingress-nginx <pod-name> -- \
  /dbg backends all 2>/dev/null | head -50

방법 3. Ingress 수가 많은 경우 - certificate_data 분리 최적화

Ingress 수가 100개 이상인 경우, 아래 설정도 함께 적용:

data:
  lua-shared-dicts: "certificate_data:50,configuration_data:10,global_throttle_conn:1,global_throttle_conn_rps:1"
  
  # 추가로 worker 프로세스 튜닝
  worker-processes: "auto"
  worker-cpu-affinity: "auto"

방법 4. ingress-nginx 버전 업그레이드

구버전(< 1.3.0)은 이 버그가 있어 버전 업이 근본 해결책이 될 수 있습니다.

# 현재 버전 확인
kubectl exec -n ingress-nginx <pod-name> -- /nginx-ingress-controller --version

권장 조치 순서

1. lua-shared-dicts 값 확인 (현재 설정값)
         ↓
2. certificate_data를 40MB 이상으로 증가
         ↓
3. ingress-nginx rollout restart
         ↓
4. JupyterHub/Spark에서 인증서 에러 재현 여부 모니터링
         ↓
5. 에러 지속 시 → 80~100MB로 추가 증가 or 버전 업그레이드

참고: 기본값 vs 권장값

환경certificate_data 권장값
Ingress 50개 이하20MB (기본값)
Ingress 50~200개40~60MB
Ingress 200개 이상80~100MB
JupyterHub + Spark 멀티 도메인최소 50MB 이상

현재 Ingress 수를 확인하려면:

kubectl get ingress -A | wc -l

===

이 메시지는 Kubernetes Ingress-NGINX 내부의 Lua 스크립트(configuration.lua)가 SSL/TLS 인증서 데이터를 캐싱하는 공유 메모리(Shared Dictionary) 공간인 certificate_data가 가득 찼을 때 발생합니다.

메모리가 부족해지자 최신 요청을 저장하기 위해 LRU(Least Recently Used, 가장 오랫동안 사용되지 않은) 알고리즘에 의해 기존 인증서 엔트리를 강제로 밀어내고(Evict) 있는 상태입니다. 이 과정에서 필요한 인증서가 순간적으로 유실되거나, NGINX 워커가 갱신 주기 타이밍을 놓쳐 jupyteralb, spark 등 내부 통신 시 간헐적인 인증서 에러(Expired, Fake Certificate, 또는 Handshake Failure)를 유발하게 됩니다.

이 문제는 관리하는 Ingress 객체나 도메인, Secret(인증서)의 개수가 많아질 때 주로 발생하며, 아래의 방법으로 명확하게 해결할 수 있습니다.


🛠️ 해결책: lua-shared-dicts 설정 확장

Ingress-NGINX Controller의 ConfigMap을 수정하여 인증서 데이터를 담는 Lua 공유 메모리 크기를 늘려주어야 합니다. 기본값은 보통 수 MB 수준으로 작게 잡혀 있습니다.

방법 1: Helm으로 설치한 경우 (권장)

values.yaml 파일에 아래 내용을 추가하거나 업데이트한 후 Helm Upgrade를 진행합니다.

controller:
  config:
    lua-shared-dicts: "certificate_data: 100"  # 기본 단위를 대폭 늘려 (예: 100MB) 공간 확보

💡 일반적으로 certificate_data: 50 또는 100 정도로 지정하면 수천 개의 도메인 및 인증서 캐시를 안정적으로 수용할 수 있습니다.

수정 후 적용:

helm upgrade -n <ingress-namespace> <release-name> ingress-nginx/ingress-nginx -f values.yaml

방법 2: ConfigMap을 직접 수정하는 경우

만약 Manifest 파일로 배포했거나 빠르게 임시 조치를 취해야 한다면, Ingress Controller가 참조하는 ConfigMap(보통 이름이 ingress-nginx-controller)을 직접 편집합니다.

kubectl edit cm -n <ingress-namespace> ingress-nginx-controller

data: 섹션 아래에 lua-shared-dicts 설정을 주입합니다.

apiVersion: v1
kind: ConfigMap
metadata:
  name: ingress-nginx-controller
  namespace: ingress-nginx
data:
  # 기존 설정들이 배치되어 있는 곳에 아래 라인 추가
  lua-shared-dicts: "certificate_data: 100"

저장 후 빠져나오면 Ingress Controller가 변경 사항을 감지하여 동적으로 반영하거나 Pod가 롤링 리스타트됩니다.


⚠️ 주의 사항 (Downtime Risk)

lua-shared-dicts 사이즈를 변경하면, NGINX 메모리 구조가 재조정되면서 기존 캐시에 들어있던 모든 인증서 데이터가 일시적으로 퍼지(Purge/초기화)될 수 있습니다.
이 경우 새롭게 인증서 데이터를 채워 넣는 약 수 분~20분 동안, 외부 요청에 대해 Ingress가 임시 인증서(Fake Certificate)를 반환하여 서비스 단절이나 SSL 경고가 발생할 가능성이 있습니다. 따라서 반드시 트래픽이 적은 유지보수 시간대(정비 시간)에 작업을 진행하시는 것을 강력히 권장합니다.

0개의 댓글