Giới thiệu
Trong bài viết này, chúng ta sẽ cài đặt JupyterHub đa người dùng trên một môi trường Kubernetes và biến nó thành công cụ thực tiễn cho công việc hàng ngày. Chúng ta sẽ sử dụng Helm để cài đặt, kích hoạt các hồ sơ người dùng và hình ảnh tùy chỉnh, kết nối sổ tay với các dịch vụ trong cụm như PostgreSQL và quản lý khóa API từ sổ tay bằng cách sử dụng Vault với một trợ lý Python nhỏ. Kết quả cuối cùng là một nền tảng sổ tay tự lưu trữ với đăng nhập một lần, các cài đặt hợp lý và trải nghiệm phát triển sạch sẽ.
Nếu bạn đã theo dõi các bài viết trước trong chuỗi này, bạn đã có một cụm k3s, Keycloak cho OIDC, Vault, và (tùy chọn) Longhorn đang hoạt động. Chúng ta sẽ xây dựng trên nền tảng đó ở đây.
Mục lục
- JupyterHub là gì?
- Cài đặt JupyterHub với Helm
- Tùy chỉnh
- Tích hợp dịch vụ
- Quản lý bí mật với Vault
- Lợi ích của việc tự lưu trữ JupyterHub
- Kết luận
- Tài nguyên
JupyterHub là gì?
JupyterHub là một cổng đa người dùng cho Jupyter. Trên Kubernetes, hub tạo ra một pod cho mỗi người dùng, gắn một volume bền vững cho các tệp của họ và chuyển tiếp lưu lượng đến mỗi máy chủ của người dùng. Việc xác thực có thể tùy chỉnh; trong cấu hình này, chúng ta xác thực với Keycloak qua OIDC. Mô hình này cung cấp cho mỗi người một môi trường cô lập, có thể tái tạo trong khi giữ cho việc quản lý tập trung.
Lợi ích cho một nhóm nhỏ hoặc phòng lab tại nhà là rõ ràng: chỉ có một nơi để đăng nhập và quản lý quyền truy cập; người dùng chọn một môi trường phù hợp với công việc của họ; và bạn giữ dữ liệu ở địa phương với hiệu suất và chi phí dự đoán.
Cài đặt JupyterHub với Helm
Kho lưu trữ này cung cấp một bộ công thức Just tự động hóa việc cài đặt JupyterHub.
Bước 1: Clone kho lưu trữ
Clone kho lưu trữ và vào workspace:
bash
git clone https://github.com/buun-ch/buun-stack
cd buun-stack
Bước 2: Cấu hình môi trường
Đảm bảo bạn có một tệp .env.local
với các cài đặt Keycloak và Vault (xem README.md), sau đó chạy:
bash
# Cài đặt tương tác (nhắc nhở cho host, NFS tùy chọn, tích hợp Vault)
just jupyterhub::install
Bước 3: Xác nhận cài đặt
Trong quá trình cài đặt, bạn sẽ được yêu cầu xác nhận:
- Tên miền JupyterHub (FQDN) dùng cho các callback OAuth
- Có muốn kích hoạt NFS PV hay không (cần Longhorn); nếu có, cung cấp IP và đường dẫn NFS
- Có muốn tích hợp Vault cho các bí mật sổ tay hay không
Nếu bạn công bố JupyterHub qua Cloudflare Tunnel, thêm một mục tên miền công khai.
Ví dụ:
- Subdomain:
jupyter
- Domain:
example.com
- Service:
https://localhost:443
- Tùy chọn nâng cao: vô hiệu hóa xác minh TLS
Mở URL trong trình duyệt, đăng nhập với Keycloak và khởi động một máy chủ để xác minh mọi thứ hoạt động.
Tùy chỉnh
Các hồ sơ cho phép người dùng chọn một hình ảnh và hình dạng tài nguyên khi khởi động máy chủ của họ. Chúng được định nghĩa trong các giá trị Helm và được áp dụng bởi KubeSpawner. Theo mặc định, chỉ có hình ảnh “Jupyter Notebook Data Science Stack” chính thức có sẵn, vì vậy việc kéo hình ảnh có thể hoàn thành nhanh chóng. Bạn cũng có thể kích hoạt hình ảnh tùy chỉnh “Buun-stack” bao gồm các thư viện bổ sung và tích hợp Vault được sử dụng bên dưới.
Để kích hoạt hồ sơ Buun-stack, hãy bật nó và tùy chọn biến thể CUDA trong .env.local
:
bash
# Kích hoạt hồ sơ
JUPYTER_PROFILE_BUUN_STACK_ENABLED=true
# Hồ sơ GPU tùy chọn
JUPYTER_PROFILE_BUUN_STACK_CUDA_ENABLED=true
# Tùy chọn: vô hiệu hóa hình ảnh datascience mặc định
JUPYTER_PROFILE_DATASCIENCE_ENABLED=false
# Cấu hình registry hình ảnh và tag trong .env.local theo nhu cầu
IMAGE_REGISTRY=localhost:30500
JUPYTER_PYTHON_KERNEL_TAG=python-3.12-28
Xây dựng và đẩy hình ảnh
Sau đó, xây dựng và đẩy các hình ảnh:
bash
# Xây dựng và đẩy hình ảnh kernel
just jupyterhub::build-kernel-images
just jupyterhub::push-kernel-images
Dockerfile của Buun-stack bao gồm các thành phần Python cần thiết cho Vault cùng với các gói dữ liệu/ML phổ biến, để người dùng có thể bắt đầu lập trình ngay lập tức.
Tùy chọn: Lưu trữ NFS với Longhorn
Nếu bạn làm việc với các tập dữ liệu lớn hoặc chia sẻ, hãy kích hoạt PersistentVolume dựa trên NFS qua Longhorn và gắn nó vào máy chủ người dùng. Điều này giữ dữ liệu gần với môi trường của bạn, giảm bớt việc rời dữ liệu và giúp việc sao lưu trở nên đơn giản. Bạn có thể cấu hình trước các cài đặt NFS và để trình cài đặt cung cấp PV/PVC:
bash
export JUPYTERHUB_NFS_PV_ENABLED=true
export JUPYTER_NFS_IP=192.168.10.1
export JUPYTER_NFS_PATH=/volume1/drive1/jupyter
just jupyterhub::install
Tích hợp dịch vụ
Bởi vì JupyterHub chạy bên trong cụm, các sổ tay có thể truy cập các dịch vụ qua DNS của Kubernetes mà không cần chuyển tiếp cổng. Ví dụ, một URL PostgreSQL có thể là postgresql://user:password@postgres-cluster-rw.postgres:5432/mydb
. Các biến môi trường POSTGRES_HOST
và POSTGRES_PORT
sẽ được tiêm vào máy chủ người dùng, cho phép mã sổ tay của bạn xây dựng các URL này trong thời gian thực như sau:
python
import os
pg_host = os.getenv('POSTGRES_HOST')
pg_port = os.getenv('POSTGRES_PORT')
pg_url = f'postgresql://user:password@{pg_host}:{pg_port}/mydb'
Với DuckDB, bạn có thể đính kèm Postgres và di chuyển dữ liệu trong vài dòng SQL, rất tiện lợi cho việc nhập liệu tạm thời và truy vấn nhanh.
python
>>> import duckdb
>>> def setup_duckdb_postgres():
... con = duckdb.connect()
... con.execute('INSTALL postgres')
... con.execute('LOAD postgres')
... con.execute(f"ATTACH '{pg_url}' AS pg (TYPE POSTGRES)")
... return con
>>> con = setup_duckdb_postgres()
>>> con.execute(f"""
... CREATE OR REPLACE TABLE pg.athlete_events AS
... SELECT * FROM read_csv_auto('{data_dir}/athlete_events.csv')
... """)
>>> con.execute("""
... SELECT Sport, COUNT(*) as count
... FROM pg.athlete_events
... GROUP BY Sport
... ORDER BY count DESC
... LIMIT 5
... """).df()
Sport count
--------------------
0 Athletics. 38624
1 Gymnastics. 26707
2 Swimming. 23195
3 Shooting. 11448
4 Cycling. 10859
Sử dụng JupyterHub và các dịch vụ nội bộ của Kubernetes giữ cho độ trễ thấp và cấu hình sạch sẽ. Nó cũng mở rộng đến các dịch vụ nội bộ khác—lưu trữ đối tượng, API phân tích, hoặc bất kỳ thứ gì khác mà bạn đã triển khai trong cụm.
Quản lý bí mật với Vault
Các sổ tay đám mây như Colab cung cấp một cách đơn giản để lấy bí mật trong mã. Trên Google Colab, bạn có thể lưu trữ và truy xuất bí mật với một trợ lý tích hợp:
python
# Google Colab (ví dụ)
from google.colab import userdata
openai_api_key = userdata.get('OPENAI_API_KEY')
Với Jupyter thông thường, mô hình phổ biến là dán bí mật tại thời điểm chạy bằng cách sử dụng getpass, điều này là thủ công và dễ gây lỗi:
python
# Jupyter thông thường (dán thủ công)
from getpass import getpass
openai_api_key = getpass('OpenAI API key: ')
Chúng tôi tái tạo tính năng tiện lợi như Colab theo cách tự lưu trữ bằng cách sử dụng Vault và một lớp Python nhỏ có tên SecretStore
, được bao gồm trong hình ảnh Buun-stack. Khi máy chủ của bạn khởi động, JupyterHub tạo một chính sách và token cho từng người dùng trong Vault, sau đó tiêm NOTEBOOK_VAULT_TOKEN
và VAULT_ADDR
như các biến môi trường. SecretStore
sử dụng chúng bên dưới và làm mới token khi cần thiết trong các phiên làm việc dài.
Dưới đây là cách nó hoạt động với stack này:
python
from buunstack import SecretStore
secrets = SecretStore()
secrets.put('api-keys', openai='sk-...')
openai_api_key = secrets.get('api-keys', field='openai')
Không cần sao chép và dán token vào các ô, và mỗi người dùng có một không gian tên riêng biệt trong Vault với một nhật ký truy cập đầy đủ. Để sử dụng điều này, hãy kích hoạt Vault trong quá trình cài đặt và chọn một trong các kernel của Buun-stack.
Bạn có thể kích hoạt Vault trước khi cài đặt bằng cách thêm vào .env.local
:
bash
JUPYTERHUB_VAULT_INTEGRATION_ENABLED=true
Chạy trình cài đặt:
bash
just jupyterhub::install
Hoặc thiết lập sau khi cài đặt:
bash
just jupyterhub::setup-vault-jwt-auth
Chi tiết thực hiện (cách hoạt động của SecretStore)
Dưới đây, tích hợp phản ánh những gì bạn mong đợi từ một nền tảng sổ tay được quản lý, nhưng với các thành phần mà bạn kiểm soát:
- Cung cấp token quản trị: hub sử dụng một token quản trị Vault có thể gia hạn được lưu trữ tại một đường dẫn đã biết trong Vault và lấy vào Hub qua một ExternalSecret. Một container bên phụ nhỏ tự động gia hạn token đó tại các khoảng thời gian TTL/2 để nó không bao giờ hết hạn trong quá trình hoạt động bình thường.
- Cô lập người dùng trước khi tạo: khi một người dùng khởi động máy chủ, một hook trước khi tạo sẽ tạo một chính sách Vault cụ thể cho người dùng và một token mồ côi gắn liền với chính sách đó. Token mồ côi không bị giới hạn bởi chính sách của token cha, tránh các vấn đề thừa kế. Token này được tiêm vào container sổ tay như
NOTEBOOK_VAULT_TOKEN
cùng vớiVAULT_ADDR
. - Không gian tên theo người dùng: mỗi chính sách hạn chế quyền truy cập vào không gian bí mật riêng của người dùng đó. Nhật ký kiểm tra của Vault ghi lại mọi lần truy cập.
- Trợ lý trong sổ tay:
SecretStore
(được hỗ trợ bởi gói Pythonbuunstack
) đọc token được tiêm và gọi Vault. Trước mỗi hoạt động, nó kiểm tra xem token có hợp lệ và có thể gia hạn hay không; nếu TTL thấp, nó sẽ gia hạn token để các phiên làm việc dài hạn vẫn hoạt động.
Việc gia hạn token của người dùng được thực hiện bên trong SecretStore._ensure_authenticated()
: kiểm tra token hiện tại với lookup_self
, gia hạn nó khi TTL thấp và token có thể gia hạn, và báo lỗi nếu token không còn hợp lệ để người dùng có thể khởi động lại máy chủ của mình. Việc gia hạn token của quản trị được xử lý riêng bởi sidecar Hub và không liên quan đến mã sổ tay.
Thiết kế này giữ cho trải nghiệm người dùng đơn giản (thiết lập/lấy trong mã) trong khi cung cấp ranh giới mạnh mẽ giữa người dùng và các phiên bền vững mà không cần dán bí mật thủ công. Để biết thêm chi tiết về hoạt động—các chính sách, thiết lập ExternalSecret, phạm vi chính sách người dùng, token mồ côi và hành vi gia hạn—xem docs/jupyterhub.md
.
Lợi ích của việc tự lưu trữ JupyterHub
Chạy JupyterHub trên cụm Kubernetes của riêng bạn cho phép bạn kiểm soát toàn bộ trải nghiệm sổ tay trong khi giữ dữ liệu gần nơi nó được sản xuất và sử dụng. Bạn quyết định hình ảnh và thư viện nào có sẵn, cách phân bổ tài nguyên và cách xác thực và quản lý bí mật.
- Kiểm soát và tùy chỉnh: chọn lọc hình ảnh và hồ sơ phù hợp với cách làm việc của đội ngũ bạn; điều chỉnh cài đặt spawner, tài nguyên và lưu trữ theo nhu cầu của bạn.
- Tính địa phương hóa dữ liệu và hiệu suất: giữ dữ liệu trên mạng của bạn để giảm độ trễ và tuân thủ dễ hơn; điều chỉnh lưu trữ, CPU/MEM và thậm chí GPU.
- Năng suất đội nhóm: các công cụ đã được cài đặt trước giảm thời gian thiết lập; mỗi người dùng nhận được một máy chủ cô lập, có thể tái tạo; các dịch vụ bên trong cụm có thể truy cập bằng các tên DNS đơn giản.
- Hoạt động và bảo mật: SSO qua Keycloak, cô lập theo người dùng, bí mật hỗ trợ Vault với nhật ký kiểm tra và sao lưu/giám sát mà bạn tự quản lý. Đối với các khối lượng công việc ổn định, chi phí là dự đoán và thường thấp hơn so với các sổ tay đám mây tương đương.
Kết luận
Chúng ta đã xây dựng một JupyterHub đa người dùng, an toàn trên Kubernetes và đã chỉ ra cách sử dụng nó hàng ngày:
- Cài đặt với Helm sử dụng công thức Just, bao gồm lưu trữ NFS tùy chọn và tích hợp Vault
- Kích hoạt các hồ sơ và hình ảnh kernel tùy chỉnh (Buun-stack) cho các môi trường đồng nhất
- Kết nối các sổ tay với các dịch vụ trong cụm (ví dụ: PostgreSQL) bằng cách sử dụng DNS của Kubernetes và biến môi trường
- Quản lý bí mật trực tiếp từ các sổ tay với Vault qua trợ lý
SecretStore
Kết quả là một nền tảng sổ tay tự lưu trữ với đăng nhập một lần, tích hợp trong cụm và quản lý bí mật an toàn, tiện lợi—lý tưởng cho các đội ngũ nhỏ, môi trường học tập và phòng lab tại nhà.
Tài nguyên
- Kho lưu trữ: Buun-stack trên GitHub
- Tài liệu:
docs/jupyterhub.md
- Các bài viết trước:
- Xây dựng một phòng lab tại nhà Kubernetes có thể truy cập từ xa với k3s
- Tăng tốc vòng lặp phát triển Kubernetes với Tilt và Telepresence