Zero to JupyterHub 헬름 차트를 쓰면서 로드 밸런서를 쓸 수 없어 ingress-nginx 의 서브패스(https://host/jupyter)로 노출하는 구성이다. 여기서 대부분 같은 벽에 부딪힌다 — 브라우저가 /hub/hub/... 또는 /hub/hub/hub/... 로 끝없이 리다이렉트되거나, hub 파드가 readiness 실패로 올라오지 않는다.
원인은 하나다. JupyterHub 는 자기 base_url 을 알고 있어야 하고, 그 앞단에서 경로를 다시 고쳐 쓰면 안 된다.
| 항목 | 설정 |
|---|---|
| JupyterHub 의 base URL | hub.baseUrl: /jupyter |
| Ingress 경로 | /jupyter (또는 /jupyter(/|$)(.*)) |
rewrite-target |
쓰지 않는다 |
rewrite-target 을 걸면 nginx 가 경로 앞부분을 잘라내고, JupyterHub 는 자기 base_url 을 다시 붙여 리다이렉트를 보낸다. 그 리다이렉트가 다시 Ingress 로 들어와 같은 처리를 받으면서 접두사가 계속 쌓인다. 이것이 /jupyter/hub/hub/... 의 정체다.
JupyterHub 는 base_url 이 설정되면 자기가 알아서 모든 링크와 리다이렉트에 접두사를 붙인다. nginx 가 해 줄 일이 없다.
hub:
baseUrl: /jupyter
readinessProbe:
enabled: true
initialDelaySeconds: 0
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 100
httpGet:
path: /jupyter/hub/health
port: 8081
livenessProbe:
enabled: true
initialDelaySeconds: 300
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 30
httpGet:
path: /jupyter/hub/health
port: 8081
ingress:
enabled: true
ingressClassName: nginx
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: "0"
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
hosts:
- jupyter.example.local
pathSuffix: ""
pathType: Prefix
proxy:
service:
type: ClusterIP
singleuser:
storage:
dynamic:
storageClass: nfs-client
capacity: 10Gi
probe 경로에 base_url 을 포함시켜야 한다. 차트 기본값은 /hub/health 인데, baseUrl 을 바꾸면 실제 경로는 /jupyter/hub/health 가 된다. 기본값 그대로 두면 kubelet 의 probe 가 /hub/health 로 들어가고 JupyterHub 가 /jupyter/hub/health 로 리다이렉트하는데, probe 는 리다이렉트를 따라가지 않아 실패로 기록된다. 파드가 Running 인데 0/1 Ready 에 머무는 형태가 된다.
WebSocket 을 쓰므로 proxy-read-timeout 을 넉넉히 준다. 기본 60초로 두면 노트북 커널 연결이 1분마다 끊긴다. 파일 업로드가 필요하면 proxy-body-size: "0" 으로 크기 제한을 푼다.
설정이 맞아도 https://host/hub 로 직접 들어가면 다시 꼬인다. 사용자 진입점은 반드시 https://host/jupyter 여야 한다. 이미 /hub 로 리다이렉트된 기록이 브라우저에 남아 있으면 설정을 고쳐도 증상이 그대로 재현되므로, 확인 전에 캐시와 쿠키를 지운다.
/hub 로 들어오는 요청을 아예 막고 싶으면 서버 스니펫을 쓴다.
nginx.ingress.kubernetes.io/server-snippet: |
location ~* ^/hub {
return 404;
}
자체 서명 인증서로 붙일 때는 시크릿을 먼저 만들고 차트에서 참조한다.
openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \
-keyout tls.key -out tls.crt \
-subj "/CN=jupyter.example.local" \
-addext "subjectAltName=DNS:jupyter.example.local"
kubectl create secret tls jupyterhub-tls --cert=tls.crt --key=tls.key -n jhub
ingress:
tls:
- hosts:
- jupyter.example.local
secretName: jupyterhub-tls
subjectAltName 을 빠뜨리면 요즘 브라우저와 ingress-nginx 모두 CN 만으로는 이름을 확인하지 않는다.
helm upgrade 가 values don't meet the specifications of the schema(s) 로 거부되는 경우는 차트가 values 스키마를 강제하기 때문이다. 오타이거나, 해당 차트 버전에 없는 키를 넣었거나, 타입이 다른 경우다.
# 어떤 키가 있는지 확인
helm show values jupyterhub/jupyterhub --version ${CHART_VERSION} | less
# 스키마 검사를 임시로 끄고 원인 좁히기
helm upgrade --install jhub jupyterhub/jupyterhub -n jhub -f config.yaml --skip-schema-validation
숫자를 따옴표로 감싸면 문자열이 되어 스키마 검사에 걸린다. 반대로 proxy-body-size 같은 nginx 주석 값은 반드시 문자열이어야 한다. 주석 값은 전부 문자열이라는 점을 기억하면 헷갈리지 않는다.
노트북 파드가 계속 쌓이는 것을 막으려면 cull 설정을 쓴다. 최신 차트에는 전용 키가 있으므로 extraConfig 로 직접 서비스를 정의할 필요가 없다.
cull:
enabled: true
timeout: 3600
every: 600
users: false
maxAge: 0