JupyterHub - это инструмент с открытым исходным кодом, который предлагает возможность развертывать серверы ноутбуков Jupyter по запросу. Блокноты можно использовать для анализа данных или для создания и выполнения моделей машинного обучения. Istio - это сервисная сетка, которая предлагает безопасный и наблюдаемый механизм связи между различными сервисами в кластере Kubernetes.
Одним из преимуществ запуска JupyterHub в кластере с поддержкой istio является поддержка mTLS (взаимный TLS). ) между различными компонентами JupyterHub. mTLS гарантирует, что вся связь между концентратором и серверами пользователь-ноутбук зашифрована и защищена от прослушивания. Эта возможность была запрошена многими пользователями в сообществе JupyterHub.

Чтобы пройти этот путь, важно знать основные взаимодействия компонентов в JupyterHub.

  • Хаб настраивает прокси, вызывая proxy-api
  • Прокси-сервер по умолчанию перенаправляет все запросы в хаб.
  • Хаб обрабатывает вход в систему и по запросу порождает однопользовательские серверы ноутбуков.
  • Хаб настраивает прокси для пересылки префиксов URL на однопользовательские серверы ноутбуков.

Настраивать

- Установить istio

$ istioctl install --set profile=demo

- Установить JupyterHub

Создайте jupyterhub namespace для установки JupyterHub. Установите метку istio-injection, чтобы настроить автоматическую инъекцию sidecar istio-proxy в поды, которые начинаются в пространстве имен. Установите mTLS mode для всех служб в пространстве имен.

$ kubectl create ns jupyterhub
$ kubectl label namespace jupyterhub istio-injection=enabled
$ kubectl apply -n jupyterhub -f - <<EOF 
apiVersion: "security.istio.io/v1beta1"
kind: "PeerAuthentication"
metadata:
  name: "default"
spec:
  mtls:
    mode: STRICT
EOF

Затем настройте репозиторий Helm Charts.

$ helm repo add jupyterhub https://jupyterhub.github.io/helm-chart/
$ helm repo update

Настройте конфигурацию для диаграммы управления.

$ echo -n "proxy:\n  secretToken: '$(openssl rand -hex 32)'\n" > config.yaml

Установите JupyterHub в пространстве имен jupyterhub

$ helm template  jupyterhub/jupyterhub \
  --version=0.9.0 \
  --values config.yaml | kubectl -n jupyterhub apply -f -
$ # Not using `helm install` is a personal preference. I prefer qbec instead for day to day use. Using helm here as it is used to package JupyterHub for Kubernetes in the community.

Затем мы проверим развертывание, чтобы увидеть, работают ли модули. И концентратор, и прокси-модули работают должным образом.

$ kubectl -n jupyterhub get po
hub-fd88f65b6-6zqb9     2/2     Running     1    5m31s
proxy-98fdbb5fd-bv7nt   2/2     Running     0    5m31s

Часть 2/2 показывает, что в модуле есть два контейнера - основной контейнер и дополнительный контейнер istio-proxy. kubectl -n jupyterhub describe po hub-fd88f65b6-6zqb9 показывает, что модуль концентратора имеет контейнер istio-init и дополнительный компонент istio-proxy.

Сетевой трафик направляется через дополнительный компонент istio-proxy. Для проверки просмотрите журналы доступа на боковой панели.

$ kubectl -n jupyterhub logs hub-fd88f65b6-6zqb9 -c istio-proxy
[2020-09-15T03:50:42.650Z] "GET /api/routes HTTP/1.1" 200 - "-" "-" 0 87 2 1 "-" "Mozilla/5.0 (compatible; pycurl)" "389f6b9c-c966-96d7-8cc3-2a565f623ccd" "10.106.101.111:8001" "10.1.0.19:8001" outbound|8001||proxy-api.jupyterhub.svc.cluster.local 10.1.0.18:52182 10.106.101.111:8001 10.1.0.18:36004 - default

Все идет нормально. Похоже, у нас есть все необходимое. Но переход к общедоступному прокси-серверу приводит к непредвиденной ошибке.

$ kubectl -n jupyterhub port-forward svc/proxy-public 8080:80

Перенаправление портов непосредственно на прокси-модуль также не выполняется с той же ошибкой 404

$ kubectl -n jupyterhub port-forward proxy-98fdbb5fd-265xq 8080:8000

Расследование

Отладка 404-го оказывается немного сложнее. Все проходит проверку, прокси-контейнер может подключаться к хаб-контейнеру через хаб-сервис, пользовательский запрос на переадресацию порта попадает на прокси-контейнер. Может ли это быть проблемой с istio? Отключение впрыска коляски заставляет все снова работать волшебным образом.

$ kubectl label namespace jupyterhub istio-injection=disabled --overwrite

Переадресация портов попадает на страницу входа.

Чтобы копать глубже, требуется больше знаний в области сетевых технологий. Повторное включение внедрения sidecar и одновременное завершение журналов istio-proxy (основанного на envoy) для прокси-модуля показывает событие, которое регистрирует сбой.

$ kubectl -n jupyterhub logs proxy-98fdbb5fd-svnzl -c istio-proxy -f
 
[2020-09-15T04:37:03.884Z] "GET / HTTP/1.1" 404 NR "-" "-" 0 0 0 - "127.0.0.1" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10jupyterhub-istio-proxy6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36" "b0bd27fe-90c3-9bfd-8ae7-e716baa9eb6e" "localhost:8080" "-" - - 10.105.254.81:8081 127.0.0.1:0 - -

10.105.254.81:8081 - пункт назначения (служба k8s), который прослушивает концентратор, но в журналах для istio-proxy на модуле концентратора нет соответствующего входящего события. Настоящая подсказка здесь - 404 NR, что означает, что нет «маршрута» или, другими словами, целевая служба не известна посланнику. Следовательно, envoy отбрасывает исходящий запрос, что приводит к ответу 404 NOT FOUND. Чтобы понять, что происходит, давайте запустим оболочку на прокси-сервере, сделаем несколько индивидуальных запросов и воспроизведем проблему.

$ kubectl -n jupyterhub  exec -it deploy/proxy -c chp sh
# curl hub:8081

Журналы сопроводительного файла прокси показывают событие с успешным перенаправлением, как и ожидалось (302 в данном случае).

[2020-09-15T04:51:18.816Z] "GET / HTTP/1.1" 302 - "-" "-" 0 0 2 1 "-" "curl/7.67.0" "923377f0-4cb1-9c35-8ba9-0c42f8e247da" "hub:8081" "10.1.0.37:8081" outbound|8081||hub.jupyterhub.svc.cluster.local 10.1.0.38:46732 10.105.254.81:8081 10.1.0.38:50312 - default

В журнале сопроводительных сообщений концентратора также отображается соответствующее входящее событие. Поле X-REQUEST-ID можно использовать для отслеживания журналов по сервисам в сервисной сети istio.

[2020-09-15T04:51:18.816Z] "GET / HTTP/1.1" 302 - "-" "-" 0 0 1 1 "-" "curl/7.67.0" "923377f0-4cb1-9c35-8ba9-0c42f8e247da" "hub:8081" "127.0.0.1:8081" inbound|8081||hub.jupyterhub.svc.cluster.local 127.0.0.1:34838 10.1.0.37:8081 10.1.0.38:46732 outbound_.8081_._.hub.jupyterhub.svc.cluster.local default

Теперь давайте направим запрос через chp (configurable-http-proxy) - сервер nodejs, который проксирует вызов. Это приводит к ошибке 404 - той же ошибке, что и при вызовах из браузера.

# curl localhost:8000 # the entry below is from proxy sidecar logs

[2020-09-15T04:54:19.216Z] "GET / HTTP/1.1" 404 NR "-" "-" 0 0 0 - "127.0.0.1" "curl/7.67.0" "0fcf1f52-a809-9855-bb58-50501a17f694" "localhost:8000" "-" - - 10.105.254.81:8081 127.0.0.1:0 - -

Установка правильного заголовка Host в запросе работает должным образом.

# curl localhost:8000 -H "Host: hub:8081" -H "X-REQUEST-ID:
test"
[2020-09-15T05:01:35.988Z] "GET / HTTP/1.1" 302 - "-" "-" 0 0 2 2 "127.0.0.1" "curl/7.67.0" "test" "hub:8081" "10.1.0.37:8081" outbound|8081||hub.jupyterhub.svc.cluster.local 10.1.0.38:46732 10.105.254.81:8081 127.0.0.1:0 - default

Итак, проблема с chp - несоответствие заголовка Host в запросе. Envoy отбрасывает исходящий запрос с NR, и прокси не может попасть в службу концентратора, когда через него маршрутизируется внешний запрос. (Некоторые другие инструменты и фрагменты nodejs, используемые для сужения точной проблемы, были опущены для краткости.)

jupyterhub-istio-proxy спешит на помощь

Хотя есть способы взломать текущую реализацию прокси и в некоторых случаях использовать менее безопасный вариант, это в некотором роде излишне, поскольку istio (точнее говоря, базовый посланник) предлагает первоклассную поддержку сетевого прокси. Более того, реализация прокси chp становится узким местом, как только трафик Jupyterhub растет. Его нельзя масштабировать больше одного модуля из-за его технических ограничений. jupyterhub-istio-proxy можно использовать для настройки istio для выполнения фактической сетевой маршрутизации на основе взаимодействия пользователя с JupyterHub. Он также предлагает горизонтально масштабируемое решение, необходимое для масштабного выполнения производственных рабочих нагрузок.

Создайте шлюз istio для обработки входящего трафика в кластер K8s. Шлюз - это точка входа в сеть.

$ kubectl -n jupyterhub apply -f - https://gist.githubusercontent.com/harsimranmaan/4315477268fccea65accf8674f5c49ef/raw/0298f3f420365c7e56aedab7949ad39e00ffbcc3/jupyterhub-istio-proxy-gateway.yaml

Удалите сервис proxy-public, так как он больше не нужен.

$ kubectl -n jupyterhub delete svc proxy-public

Замените развертывание прокси на jupyterhub-istio-proxy:

$ kubectl -n jupyterhub apply -f https://gist.githubusercontent.com/harsimranmaan/2e77cf65019439052122b7b89f926686/raw/d800b8c60c2ac10226d549c1fbc6d8d75e8e6142/jupyterhub-istio-proxy.yaml

После применения вышеуказанной конфигурации появится новая виртуальная служба.

$ kubectl -n jupyterhub get vs
jupyterhub-8a5edab282632443219e051e4ade2d1d5bbc671c781051bf1437897cbdfea0f1   [jupyterhub-gateway]   [*]     37m

Теоретически все должно теперь работать (верно?), Но есть еще несколько проблем, которые нужно решить. По умолчанию концентратор настроен на указание прокси-api (jupyterhub-istio-proxy) направлять трафик на его IP-адрес вместо использования имени службы. Это приводит к настройке виртуальной службы istio с IP-адресом.

Hub log:
[I 2020-09-15 06:28:24.481 JupyterHub proxy:400] Adding default route for Hub: / => http://10.105.254.81:8081

приводит к неверной конфигурации VS

- destination:
        host: 10.105.254.81.jupyterhub.svc.cluster.local
        port:
          number: 8081

Исправьте конфигурацию Jupyterhub, чтобы установить Jupyterhub.hub_connect_ip property на имя службы вместо IP. PROXY_PUBLIC_SERVICE_HOST и PROXY_PUBLIC_SERVICE_PORT больше не используются и могут быть установлены на внешнее имя хоста и порт (localhost: 80 в этой настройке).

kubectl -n jupyterhub get cm/hub-config -o yaml | sed  "s/os\.environ\['HUB_SERVICE_HOST'\]/'hub'/g" | sed  "s/os\.environ\['PROXY_PUBLIC_SERVICE_HOST'\]/'localhost'/g" | sed  "s/os\.environ\['PROXY_PUBLIC_SERVICE_PORT'\]/'80'/g" | kubectl -n jupyterhub apply -f -

Перезапустите концентратор, чтобы получить новую конфигурацию. Имя сервиса устанавливается для маршрута по умолчанию.

[I 2020-09-15 07:27:09.068 JupyterHub proxy:400] Adding default route for Hub: / => http://hub:8081
- destination:
        host: hub.jupyterhub.svc.cluster.local
        port:
          number: 8081

Следуйте официальному руководству, чтобы определить URL-адрес шлюза istio. Перейдите к http: // YOUR_GATEWAY_URL, и вы увидите, что JupyterHub запущен. Завершение TLS для веб-запросов здесь не рассматривается, но его довольно легко настроить с помощью шлюза istio. Это оставлено читателям в качестве упражнения.

Последним недостающим звеном в головоломке является обеспечение того, чтобы серверы пользовательских ноутбуков могли быть развернуты и пользователи могли запускать свои любимые ноутбуки.
Это требует исправления другого компонента JupyterHub (kubespawner). Подробности можно найти в этом PR: https://github.com/jupyterhub/kubespawner/pull/425

Под капотом

jupyterhub-istio-proxy создает виртуальный сервис Istio для каждого запроса маршрута от хаба. Концентратор пересылает запросы маршрутизации на jupyterhub-istio-proxy, который устанавливает желаемый маршрут назначения и ожидает, пока маршрут прогреется, прежде чем отправить подтверждение обратно на концентратор. После создания маршрута хаб перенаправляет пользователя на свой сервер ноутбука.

Если у вас есть вопросы или вы хотите внести свой вклад в разработку jupyterhub-istio-proxy, оставьте сообщение или отправьте свой вклад по адресу https://github.com/splunk/jupyterhub-istio-proxy/issues