作者: 孟繁永

  • 基于Markdown的电子书方案:MDEpub

    一个基于 Markdown 的电子书方案,类似 EPUB 标准并原生支持 Mermaid 和 Markmap 等可视化和交互功能,解决 Markdown 的轻量特性与电子书格式的结构化需求之间的平衡,同时确保对 Mermaid(流程图、时序图等)和 Markmap(思维导图)等工具的原生支持。以下是一个详细的设计方案,涵盖架构、实现方法、可视化支持和交互功能。

    1. 设计目标

    • 基于 Markdown:以 Markdown 作为主要内容创作格式,保持简洁和易用。
    • 类似 EPUB 的结构:支持电子书所需的元数据、目录、样式和多文件组织,类似 EPUB 的 OPF 和 NCX 文件。
    • 原生支持 Mermaid 和 Markmap:直接解析和渲染 Mermaid 和 Markmap 代码块为可视化内容(例如 SVG 或交互式 HTML)。
    • 交互性:支持在电子书中嵌入交互式图表(如可缩放的流程图或动态思维导图)。
    • 跨平台兼容:生成的电子书可在多种阅读器上运行,同时支持 Web 浏览器查看。
    • 可扩展性:允许扩展其他可视化工具(如 PlantUML、D3.js 等)。

    2. 方案架构

    基于 Markdown 的电子书格式(暂称为 MDEpub,Markdown-based ePub)可以分为以下核心组件:

    2.1 文件结构

    类似 EPUB,MDEpub 使用文件夹结构组织内容:

    mdepub-book/
    ├── meta.yaml           # 元数据(标题、作者、语言等)
    ├── toc.md              # 目录(类似 EPUB 的 NCX)
    ├── content/            # 内容文件夹
    │   ├── chapter1.md     # 章节 1(Markdown 文件)
    │   ├── chapter2.md     # 章节 2
    │   └── assets/         # 静态资源(图片、CSS、JS 等)
    ├── styles.css          # 全局样式
    └── config.json         # 构建配置(例如 Mermaid/Markmap 渲染设置)
    • meta.yaml:定义电子书元数据,类似 EPUB 的 content.opf。yamltitle: 示例电子书 author: 作者姓名 language: zh-CN publisher: 出版社 date: 2025-05-28
    • toc.md:定义目录结构,支持嵌套章节。markdown# 目录 - [第一章](content/chapter1.md) - [第二章](content/chapter2.md) - [子章节](content/chapter2.md#section1)
    • content/:存放 Markdown 章节,包含 Mermaid 和 Markmap 代码块。
    • assets/:存储图片、字体、CSS、JS(如 Mermaid 和 Markmap 的运行时库)。
    • styles.css:自定义样式,控制电子书外观。
    • config.json:配置可视化工具的参数(例如 Mermaid 主题、Markmap 样式)。

    2.2 Markdown 扩展

    为支持 Mermaid 和 Markmap,扩展 Markdown 语法,允许在代码块中指定渲染参数。例如:

    markdown

    # 第一章
    
    ## 流程图
    ```mermaid {theme: "dark", format: "svg"}
    graph TD
        A[开始] --> B{判断}
        B -->|是| C[执行]
        B -->|否| D[结束]

    思维导图

    markmap {height: “300px”, zoom: true}

    # 思维导图
    ## 分支1
    ### 子节点1
    ## 分支2
    - 使用 `{}` 指定渲染参数(如主题、高度、交互性)。
    - 支持标准 Markdown 语法,同时兼容 Mermaid 和 Markmap 的特定代码块。
    
    #### **2.3 输出格式**
    MDEpub 的最终输出是一个压缩包(类似 EPUB 的 ZIP 格式),包含:
    - **HTML 文件**:每个 Markdown 章节转换为独立的 HTML 文件,嵌入 Mermaid 和 Markmap 的渲染结果(SVG 或交互式 JS)。
    - **元数据文件**:基于 `meta.yaml` 生成的 XML 或 JSON。
    - **目录文件**:基于 `toc.md` 生成的导航文件(类似 NCX 或 HTML TOC)。
    - **资源文件**:CSS、JS、图片等,包含 Mermaid 和 Markmap 的运行时库。
    - **容器文件**:定义文件结构的元文件,类似 EPUB 的 `mimetype` 和 `META-INF/container.xml`。
    
    ---
    
    ### 3. **实现方法**
    以下是实现 MDEpub 的核心步骤和技术选型:
    
    #### **3.1 解析 Markdown**
    - **工具**:使用 [marked](https://marked.js.org/) 或 [markdown-it](https://github.com/markdown-it/markdown-it) 解析 Markdown 文件,转换为 HTML。
    - **扩展插件**:
      - **Mermaid**:集成 `@mermaid-js/mermaid` 解析代码块,渲染为 SVG 或交互式 HTML。
      - **Markmap**:集成 `markmap-lib` 和 `markmap-view`,将 Markmap 代码块渲染为 SVG 或交互式思维导图。
    - **自定义代码块**:扩展 Markdown 解析器,识别 `mermaid` 和 `markmap` 代码块,并调用对应渲染器。
    
    #### **3.2 渲染可视化内容**
    - **Mermaid**:
      - 使用 Mermaid CLI 或 `@mermaid-js/mermaid` 将代码块渲染为 SVG(静态)或嵌入 JS 代码(交互式)。
      - 示例(Node.js 环境):
        ```javascript
        import mermaid from 'mermaid';
        mermaid.initialize({ theme: 'dark' });
        const svg = await mermaid.render('graph', 'graph TD A-->B');
        ```
      - 将 SVG 嵌入 HTML,或包含 Mermaid JS 库以支持交互(例如点击放大)。
    - **Markmap**:
      - 使用 `markmap-lib` 解析 Markmap 代码,生成 SVG 或 HTML+JS。
      - 示例:
        ```javascript
        import { Markmap } from 'markmap-view';
        const mm = Markmap.create(svgElement, { height: 300, zoom: true });
        mm.setData(markmapData);
        ```
      - 支持交互功能(如缩放、展开/折叠节点)。
    - **嵌入方式**:
      - **静态**:将 SVG 直接嵌入 HTML,适合离线阅读器。
      - **动态**:嵌入 Mermaid/Markmap 的 JS 运行时,生成交互式图表,适合 Web 浏览器或支持 JS 的阅读器。
    
    #### **3.3 生成电子书**
    - **构建工具**:开发一个 CLI 工具(例如 `mdepub-cli`),基于 Node.js,执行以下步骤:
      1. 读取 `meta.yaml` 和 `toc.md`,生成元数据和导航。
      2. 解析 `content/` 下的 Markdown 文件,转换为 HTML。
      3. 处理 Mermaid 和 Markmap 代码块,渲染为 SVG 或交互式 HTML。
      4. 合并 CSS 和 JS 资源,生成最终文件结构。
      5. 打包为 ZIP 文件,添加 `mimetype`(例如 `application/mdepub+zip`)。
    - **示例命令**:
      ```bash
      mdepub build mdepub-book/ -o book.mdepub

    3.4 阅读器支持

    • 静态模式:生成 SVG 的 MDEpub 文件可被大多数 EPUB 阅读器(如 Calibre、iBooks)支持。
    • 交互模式:包含 JS 的 MDEpub 需要支持 HTML5 和 JavaScript 的阅读器(如 Web 浏览器、Readium、或自定义阅读器)。
    • 自定义阅读器:开发一个基于 Web 的阅读器(使用 Electron 或 PWA),加载 MDEpub 文件,运行 Mermaid 和 Markmap 的 JS 代码,实现交互。

    4. 支持 Mermaid 和 Markmap 的关键技术

    4.1 Mermaid

    • 渲染:将 Mermaid 代码渲染为 SVG(静态)或 HTML+JS(交互)。
    • 交互性:
      • 支持点击节点高亮、缩放等功能。
      • 需要在 MDEpub 中嵌入 mermaid.min.js(约 1.5MB,压缩后更小)。
    • 样式:通过 config.json 或代码块参数自定义主题(如 dark、forest)。
    • 兼容性:SVG 输出兼容大多数阅读器;交互模式需 JS 支持。

    4.2 Markmap

    • 渲染:将 Markmap 代码渲染为 SVG 或交互式 HTML(使用 D3.js 和 markmap-view)。
    • 交互性:
      • 支持节点展开/折叠、缩放、拖拽。
      • 需要嵌入 markmap-view 和 d3 的 JS 库(约 200KB 压缩后)。
    • 样式:支持自定义颜色、字体、节点样式。
    • 兼容性:类似 Mermaid,SVG 适合静态阅读,交互模式需 JS 支持。

    4.3 其他可视化工具

    • 扩展性:通过插件机制支持其他工具,如 PlantUML、Vega-Lite 等。
    • 实现:为每种工具开发独立的 Markdown 代码块解析器和渲染器,输出 SVG 或 JS。

    5. 优势与挑战

    优势

    • 简洁性:Markdown 作为输入格式,易于编写和维护。
    • 可视化支持:原生支持 Mermaid 和 Markmap,无需外部转换。
    • 交互性:通过 JS 提供动态图表,增强阅读体验。
    • 灵活性:可扩展支持其他可视化工具。
    • 跨平台:兼容现有 EPUB 阅读器(静态模式)和 Web 浏览器(交互模式)。

    挑战

    • 阅读器兼容性:许多 EPUB 阅读器不支持 JavaScript,交互模式可能受限。
    • 文件大小:嵌入 Mermaid 和 Markmap 的 JS 库会增加文件体积。
    • 性能:复杂图表可能导致渲染时间增加,尤其在低端设备上。
    • 标准推广:MDEpub 作为新格式,需要开发专用阅读器或推广兼容性。

    6. 实现工具与工作流

    6.1 开发工具

    • Node.js:核心开发环境,用于解析 Markdown 和渲染图表。
    • markdown-it:Markdown 解析器,支持自定义插件。
    • Mermaid:用于流程图等渲染。
    • Markmap:用于思维导图渲染。
    • archiver:Node.js 库,用于打包 MDEpub 文件为 ZIP。
    • esbuild:压缩和打包 JS/CSS 资源。

    6.2 工作流

    1. 编写:在 mdepub-book/ 中编写 Markdown 文件,包含 Mermaid 和 Markmap 代码块。
    2. 配置:设置 meta.yaml、toc.md 和 config.json。
    3. 构建:运行 mdepub build 命令,生成 MDEpub 文件。
    4. 预览:使用 Web 浏览器或自定义阅读器预览。
    5. 发布:分发 .mdepub 文件,或转换为标准 EPUB(静态模式)。

    6.3 示例命令

    bash

    # 初始化项目
    mdepub init my-book
    # 构建 MDEpub
    mdepub build my-book/ -o my-book.mdepub
    # 预览(启动本地 Web 服务器)
    mdepub preview my-book/
    # 转换为标准 EPUB(静态)
    mdepub export my-book/ -o my-book.epub --static

    7. 与 EPUB 的对比

    特性EPUBMDEpub
    输入格式XHTML, XML, CSSMarkdown
    Mermaid 支持需外部转换原生支持(SVG/JS)
    Markmap 支持需外部转换原生支持(SVG/JS)
    交互性有限(需特定阅读器)强(支持 JS 的交互式图表)
    兼容性广泛(标准阅读器)需专用阅读器或浏览器
    文件大小较小(静态内容)可能较大(含 JS 库)
    开发复杂性高(XML 结构复杂)低(Markdown 简单)

    8. 未来扩展

    • 插件系统:支持更多可视化工具(如 PlantUML、Chart.js)。
    • 云服务:提供在线编辑器和渲染服务,类似 GitBook。
    • 阅读器开发:开发跨平台的 MDEpub 阅读器(基于 Electron 或 Web)。
    • 标准推广:提交 MDEpub 作为开源标准,邀请社区贡献。

    9. 快速原型实现

    以下是一个简单的 Node.js 脚本,展示如何将包含 Mermaid 和 Markmap 的 Markdown 转换为 MDEpub 核心组件:

    javascript

    const fs = require('fs');
    const markdownIt = require('markdown-it')();
    const mermaid = require('mermaid');
    const { Markmap } = require('markmap-view');
    const archiver = require('archiver');
    
    // 初始化 Mermaid
    mermaid.initialize({ startOnLoad: false });
    
    // 自定义 Markdown 插件
    markdownIt.use((md) => {
      md.renderer.rules.fence_custom = {
        mermaid: async (tokens, idx) => {
          const code = tokens[idx].content;
          const { svg } = await mermaid.render('graph', code);
          return `<div class="mermaid">${svg}</div>`;
        },
        markmap: (tokens, idx) => {
          const code = tokens[idx].content;
          return `<div class="markmap" data-markmap="${encodeURIComponent(code)}"></div>`;
        }
      };
    });
    
    // 解析 Markdown 文件
    const mdContent = fs.readFileSync('content/chapter1.md', 'utf-8');
    const htmlContent = markdownIt.render(mdContent);
    
    // 生成 HTML 文件
    fs.writeFileSync('output/chapter1.html', `
    <!DOCTYPE html>
    <html>
    <head>
      <link rel="stylesheet" href="styles.css">
      <script src="mermaid.min.js"></script>
      <script src="d3.min.js"></script>
      <script src="markmap-view.min.js"></script>
    </head>
    <body>
      ${htmlContent}
      <script>
        // 初始化 Markmap
        document.querySelectorAll('.markmap').forEach(el => {
          const data = decodeURIComponent(el.dataset.markmap);
          const mm = Markmap.create(el, { height: 300 });
          mm.setData(data);
        });
      </script>
    </body>
    </html>
    `);
    
    // 打包为 MDEpub
    const output = fs.createWriteStream('book.mdepub');
    const archive = archiver('zip');
    archive.pipe(output);
    archive.directory('output/', false);
    archive.finalize();

    10. 总结

    通过设计一个基于 Markdown 的电子书格式(MDEpub),可以实现类似 EPUB 的功能,同时原生支持 Mermaid 和 Markmap 的可视化和交互功能。核心是扩展 Markdown 解析器,集成 Mermaid 和 Markmap 渲染器,生成包含 SVG 或交互式 HTML 的电子书文件。MDEpub 结合 Markdown 的简洁性和可视化工具的强大功能,适合技术文档、教程和交互式电子书。下一步是开发 CLI 工具和专用阅读器,优化兼容性和性能。

  • mysql服务器优化过程小记

    mysql服务器优化过程小记

    有一个查询很慢,27s,查询了一下mysql的状态:

    mysql> SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool%';
    
    +-------------------------------------------+--------------------------------------------------+
    | Variable_name                             | Value                                            |
    +-------------------------------------------+--------------------------------------------------+
    | Innodb_buffer_pool_dump_status            | Dumping of buffer pool not started               |
    | Innodb_buffer_pool_load_status            | Buffer pool(s) load completed at 241109 21:58:30 |
    | Innodb_buffer_pool_resize_status          |                                                  |
    | Innodb_buffer_pool_resize_status_code     | 0                                                |
    | Innodb_buffer_pool_resize_status_progress | 0                                                |
    | Innodb_buffer_pool_pages_data             | 7168                                             |
    | Innodb_buffer_pool_bytes_data             | 117440512                                        |
    | Innodb_buffer_pool_pages_dirty            | 5                                                |
    | Innodb_buffer_pool_bytes_dirty            | 81920                                            |
    | Innodb_buffer_pool_pages_flushed          | 152905174                                        |
    | Innodb_buffer_pool_pages_free             | 1007                                             |
    | Innodb_buffer_pool_pages_misc             | 17                                               |
    | Innodb_buffer_pool_pages_total            | 8192                                             |
    | Innodb_buffer_pool_read_ahead_rnd         | 0                                                |
    | Innodb_buffer_pool_read_ahead             | 34590772215                                      |
    | Innodb_buffer_pool_read_ahead_evicted     | 46737241                                         |
    | Innodb_buffer_pool_read_requests          | 718733851921                                     |
    | Innodb_buffer_pool_reads                  | 1798889354                                       |
    | Innodb_buffer_pool_wait_free              | 6345356                                          |
    | Innodb_buffer_pool_write_requests         | 506818935                                        |
    +-------------------------------------------+--------------------------------------------------+
    20 rows in set (0.26 sec)

    问了下Grok,它说我的默认配置太低了,数据库服务器是16G内存,数据库占用的很少,大部分是被一个java程序占用了,查了一下原来是当时安装了并未使用的apache-dolphinscheduler,这个东西很占资源,于是干掉。再改以下配置:

    innodb_buffer_pool_size = 8G
    innodb_buffer_pool_instances = 4
    innodb_buffer_pool_dump_at_shutdown = 1
    innodb_buffer_pool_load_at_startup = 1

    重启mysql以后:

    
    mysql> SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool%';
    +-------------------------------------------+--------------------------------------------------+
    | Variable_name                             | Value                                            |
    +-------------------------------------------+--------------------------------------------------+
    | Innodb_buffer_pool_dump_status            | Dumping of buffer pool not started               |
    | Innodb_buffer_pool_load_status            | Buffer pool(s) load completed at 250528 22:42:35 |
    | Innodb_buffer_pool_resize_status          |                                                  |
    | Innodb_buffer_pool_resize_status_code     | 0                                                |
    | Innodb_buffer_pool_resize_status_progress | 0                                                |
    | Innodb_buffer_pool_pages_data             | 231482                                           |
    | Innodb_buffer_pool_bytes_data             | 3792601088                                       |
    | Innodb_buffer_pool_pages_dirty            | 0                                                |
    | Innodb_buffer_pool_bytes_dirty            | 0                                                |
    | Innodb_buffer_pool_pages_flushed          | 1455                                             |
    | Innodb_buffer_pool_pages_free             | 292740                                           |
    | Innodb_buffer_pool_pages_misc             | 66                                               |
    | Innodb_buffer_pool_pages_total            | 524288                                           |
    | Innodb_buffer_pool_read_ahead_rnd         | 0                                                |
    | Innodb_buffer_pool_read_ahead             | 226612                                           |
    | Innodb_buffer_pool_read_ahead_evicted     | 0                                                |
    | Innodb_buffer_pool_read_requests          | 4875046                                          |
    | Innodb_buffer_pool_reads                  | 4713                                             |
    | Innodb_buffer_pool_wait_free              | 0                                                |
    | Innodb_buffer_pool_write_requests         | 4724                                             |
    +-------------------------------------------+--------------------------------------------------+
    20 rows in set (0.00 sec)
    

    不过查询依然很慢,分析了一下请求来自laravel的一个多态关联表,4000W+行数据,两个关键字段是able_type和able_id,于是给这两个字段做了一个联合索引,idx_able,索引创建花了8分钟,索引创建过程中可能有锁,避开访问高峰时操作,完成以后查询降到了300ms以内。

    总结:

    laravel的多态表可以给able_type和able_id建联合索引。

  • WordPress总是挂掉的处理过程

    WordPress总是挂掉的处理过程

    升级服务器的话代价有点高,暂且不考虑,于是:

    1. 切换到官方25
    2. 删除多余的主题
    3. 停止插件自动更新

    接下来观察下是否能解决问题。

  • 既然短信签名审核那么严格,为什么不让运营商统一同一个短信验证码模板呢?

    短信最主要的一个使用场景就是短信验证码了,运营商提供一个由工信部指定的短信验证码模板,一个4位,一个6位,岂不是很简单一件事情,为什么要把精力和时间浪费在签名审核上呢?

  • 老mac设置php版本

    2015的老mac,brew安装的php版本只能是7了,不能再升级,自行安装了php8.2,在.zshrc中通过设置path没有问题,但是vscode却总是提示composer所检查的版本不够。

    直到在vscode中找到

    Laravel: PHP Environment(此处设置为local),可忽略

    Atisan:PHP:Location

    设置为:/usr/local/opt/php@8.2/bin/php

    以此来解决vscode默认调用/usr/bin/php的问题

  • 在Ubuntu24上部署JupyterHub + JupyterLab + DockerSpawner

    在 Ubuntu 系统上部署 JupyterHub、JupyterLab 和 DockerSpawner 的详细方案,涵盖环境准备、安装、配置和部署步骤。目标是创建一个多用户 Jupyter 环境,每个用户通过 DockerSpawner 运行独立的 JupyterLab 容器,确保隔离性和可扩展性。本方案基于官方文档和社区最佳实践,也适用于 Ubuntu 20.04 或 22.04。

    总体上分两种情况,一种是直接在服务器上启动JupyterHub,另一种是在docker中启动JupyterHub,后者隔离效果更好,推荐使用,但是绑定服务器目录时需要注意。以下方案中会作出区分说明。


    1. 环境准备

    1.1 系统要求

    • 操作系统:Ubuntu 20.04 LTS 或 22.04 LTS(推荐服务器版)。
    • 硬件要求:
      • 最低配置:2核CPU,4GB内存,20GB磁盘空间。
      • 推荐配置:4核CPU,8GB内存,50GB+磁盘空间(根据用户数量和数据存储需求调整)。
    • 网络:确保服务器可以访问互联网以拉取 Docker 镜像和安装依赖。
    • 权限:需要 root 或 sudo 权限。

    1.2 安装基本工具

    更新系统并安装必要的工具:

    bash

    sudo apt update && sudo apt upgrade -y

    下面这一步并非必要,主要用于在服务器上直接启动JupyterHub。

    bash

    sudo apt install -y curl git python3 python3-pip python3-venv

    1.3 安装 Docker

    安装 Docker 和 Docker Compose,用于运行 JupyterHub 和用户容器:

    bash

    # 安装 Docker
    sudo apt install -y docker.io
    sudo systemctl start docker
    sudo systemctl enable docker
    
    # 验证 Docker 安装
    docker --version
    
    # 安装 Docker Compose
    sudo curl -L "https://github.com/docker/compose/releases/download/v2.20.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
    sudo chmod +x /usr/local/bin/docker-compose
    
    # 验证 Docker Compose 安装
    docker-compose --version
    
    # 将当前用户添加到 docker 组(避免每次使用 docker 需要 sudo)
    sudo usermod -aG docker $USER
    newgrp docker

    上面这一步需要重新登录或者重启才能生效。其中docker-compose也可以不装,新版的docker中自带docker compose命令。

    2. 安装 JupyterHub 和依赖(这是服务器直接部署JupyterHub,使用docker的话可以跳过)

    2.1 创建虚拟环境

    为了隔离 JupyterHub 的 Python 依赖,创建一个虚拟环境:

    bash

    python3 -m venv /opt/jupyterhub
    source /opt/jupyterhub/bin/activate

    2.2 安装 JupyterHub 和 DockerSpawner

    在虚拟环境中安装 JupyterHub 和相关包:

    bash

    pip install --upgrade pip
    pip install jupyterhub jupyterlab dockerspawner
    • jupyterhub:核心服务,用于管理多用户环境。
    • jupyterlab:为用户提供 JupyterLab 界面。
    • dockerspawner:用于在 Docker 容器中启动单用户 JupyterLab 实例。

    2.3 安装 Node.js(可选,JupyterLab 依赖)

    JupyterLab 需要 Node.js 来支持扩展和前端功能:

    bash

    sudo apt install -y nodejs npm

    3. 配置 JupyterHub(这个配置文件是用于服务器直接部署,docker部署的话其中的设置有所区别)

    3.1 创建 JupyterHub 配置文件

    生成默认配置文件(也可以跳过,直接复制下面的配置文件内容进去,默认配置文件很长,看起来不方便):

    bash

    mkdir /opt/jupyterhub
    jupyterhub --generate-config -f /opt/jupyterhub/jupyterhub_config.py

    配置文件位于 /opt/jupyterhub/etc/jupyterhub_config.py,以下是关键配置步骤。

    3.2 配置 DockerSpawner

    编辑 jupyterhub_config.py,添加以下内容以启用 DockerSpawner 和 JupyterLab:

    python

    c = get_config()
    import os
    import sys
    from dockerspawner import DockerSpawner
    
    # 设置 DockerSpawner
    c.JupyterHub.spawner_class = 'dockerspawner.DockerSpawner'
    c.DockerSpawner.image = 'jupyter/minimal-notebook:x86_64-python-3.11.6'
    c.DockerSpawner.network_name = 'jupyterhub-network'
    
    # 设置 JupyterLab 作为默认界面
    c.Spawner.default_url = '/lab'
    
    # 持久化存储配置
    notebook_dir = '/home/jovyan/work'
    c.DockerSpawner.notebook_dir = notebook_dir
    c.DockerSpawner.volumes = { 
        '/mnt/raid/jupyterhub_persistent/{username}': notebook_dir,
        '/mnt/raid/jupyterhub_shared': '/home/jovyan/shared'
    }
    # 上面路径中的/mnt/raid是我自己服务器的一个地址,可以替换。注意这个地址不管在服务器直接运行JupyterHub还是在docker中,都是设置为服务器上的地址,因为是绑定给用户容器的。
    
    # 动态创建用户目录
    def create_dir_hook(spawner):
        username = spawner.user.name
        volume_path = os.path.join('/mnt/raid/jupyterhub_persistent', username)
        if not os.path.exists(volume_path):
            os.mkdir(volume_path, 0o755)
            os.chown(volume_path, 1000, 100)  # jovyan 用户的 UID 和 GID
    c.Spawner.pre_spawn_hook = create_dir_hook
    
    # 资源限制
    c.DockerSpawner.extra_host_config = {
        'mem_limit': '16g',  # 每个容器 64GB,10 个用户平分 640GB
        'cpu_period': 100000,
        'cpu_quota': 1000000,  # 每个容器 1 核心,10 个用户共 10 核心
    }
    
    # 如果本地端口有冲突可以设置,否则不设置
    c.JupyterHub.bind_url = 'http://:8090'
    c.JupyterHub.hub_connect_ip = '127.0.0.1'
    c.JupyterHub.hub_ip = '0.0.0.0'
    c.JupyterHub.hub_port = 9000
    
    # 可选:设置认证,根据需要选择认证方式,建议用这种自定义用户比较方便,不用系统的用户
    c.JupyterHub.authenticator_class = 'nativeauthenticator.NativeAuthenticator'
    c.NativeAuthenticator.enable_signup = True
    c.NativeAuthenticator.open_signup = True
    c.Authenticator.admin_users = {'admin'}
    
    # 可选:自动停止闲置服务器
    c.JupyterHub.services = [
    #    {
    #        'name': 'cull_idle',
    #        'admin': True,
    #        'command': 'python3 /opt/jupyterhub/cull_idle_servers.py --timeout=3600'.split(),
    #    },
        {
            "name": "jupyterhub-idle-culler-service",
            "command": [
                sys.executable,
                "-m", "jupyterhub_idle_culler",
                "--timeout=3600",
            ],
             "admin": True,
        }
    ]
    

    3.3 创建 Docker 网络

    为 JupyterHub 和用户容器创建一个专用的 Docker 网络:

    bash

    docker network create jupyterhub-network

    3.4 配置认证

    JupyterHub 默认使用 PAM 认证(基于系统用户)。若需要简单测试,可以配置 Native Authenticator 允许用户自注册:

    bash

    pip install jupyterhub-nativeauthenticator

    在 jupyterhub_config.py 中添加:

    python

    c.JupyterHub.authenticator_class = 'nativeauthenticator.NativeAuthenticator'
    c.Authenticator.admin_users = {'admin'}  # 设置管理员用户

    若使用其他认证方式(如 OAuth、LDAP),请参考 JupyterHub 官方文档配置对应 Authenticator。

    3.5 数据持久化

    为确保用户数据持久化,DockerSpawner 使用 Docker 卷(如上配置)。每个用户的卷命名为 jupyterhub-user-{username},存储在 /home/jovyan/work 目录。

    若需要共享数据目录,可以挂载主机路径:

    python

    c.DockerSpawner.volumes = {
        'jupyterhub-user-{username}': notebook_dir,
        '/path/on/host/shared': '/home/jovyan/shared'
    }

    确保主机路径权限正确:

    bash

    sudo chown :1000 /path/on/host/shared
    sudo chmod g+rws /path/on/host/shared
    sudo setfacl -d -m g::rwx /path/on/host/shared

    4. 使用 Docker Compose 部署

    为简化管理,使用 Docker Compose 部署 JupyterHub 和相关服务。

    4.1 创建 docker-compose.yml

    在 /opt/jupyterhub 目录下创建 docker-compose.yml:

    yaml

    version: '3.8'
    services:
      jupyterhub:
        build:
          context: .
          dockerfile: Dockerfile
        container_name: jupyterhub
        restart: unless-stopped
        ports:
          - "9000:8000"
        volumes:
          - /opt/jupyterhub/srv:/srv/jupyterhub # 这里单独设置了一个srv以跟docker中的srv对应,区别于服务器直接部署的配置,测试时方便区分
          - /var/run/docker.sock:/var/run/docker.sock
          - /mnt/raid/jupyterhub/jupyterhub_persistent:/jupyterhub/jupyterhub_persistent
          - /mnt/raid/jupyterhub/jupyterhub_shared:/jupyterhub/jupyterhub_shared
        environment:
          - DOCKER_HOST=unix:///var/run/docker.sock
          - DOCKER_NOTEBOOK_IMAGE=jupyter/scipy-notebook:latest
          - DOCKER_NETWORK_NAME=jupyterhub-network
        networks:
          - jupyterhub-network
    
    networks:
      jupyterhub-network:
        external: true
    

    用户环境定制

    若需要自定义用户镜像,创建 Dockerfile:

    因为启用了nativeauthenticator所以默认的镜像不行,需要自己通过Dockerfile加上。同时也指定自己的jupyterhub_config

    Dockerfile

    # Use the official JupyterHub base image
    FROM jupyterhub/jupyterhub:latest
    
    # Install dockerspawner and nativeauthenticator
    RUN pip install --no-cache-dir \
        dockerspawner \
        jupyterhub-nativeauthenticator \
        jupyterhub-idle-culler
    
    # Create directory for persistent storage (if needed inside the hub container)
    RUN mkdir -p /jupyterhub/jupyterhub_persistent /jupyterhub/jupyterhub_shared
    RUN chmod -R 755 /jupyterhub
    
    # Copy the jupyterhub_config.py into the container
    COPY jupyterhub_config.py /srv/jupyterhub/jupyterhub_config.py
    
    # Expose the default JupyterHub port
    EXPOSE 8000
    
    # Set working directory
    WORKDIR /srv/jupyterhub
    
    # Start JupyterHub
    CMD ["jupyterhub", "-f", "/srv/jupyterhub/jupyterhub_config.py"]
    

    4.2 用docker部署并启动 JupyterHub(这是方式二)

    bash

    cd /opt/jupyterhub
    docker-compose up -d

    4.3 验证部署

    • 访问 http://<服务器IP>:8000,应看到 JupyterHub 登录页面。
    • 使用管理员账户(如 admin)登录,测试创建用户和启动 JupyterLab 容器。
    • 检查 Docker 容器:bashdocker ps应看到 jupyterhub 容器和用户容器(如 jupyter-<username>)。

    4.4 日志查看

    若遇到问题,查看日志:

    bash

    docker logs jupyterhub

    5. 高级配置

    5.1 HTTPS 配置(不建议,建议另外通过caddy代理并设置ssl)

    为生产环境,建议配置 HTTPS:

    1. 使用 Let’s Encrypt 获取免费 SSL 证书:bashsudo apt install -y certbot python3-certbot-nginx certbot certonly --standalone -d <your-domain>
    2. 在 jupyterhub_config.py 中配置 SSL:pythonc.JupyterHub.ssl_key = '/etc/letsencrypt/live/<your-domain>/privkey.pem' c.JupyterHub.ssl_cert = '/etc/letsencrypt/live/<your-domain>/fullchain.pem' c.JupyterHub.port = 443
    3. 更新 docker-compose.yml,将端口改为 443:443。

    5.2 GPU 支持

    若需要 GPU 支持,确保主机安装了 NVIDIA 驱动和 nvidia-container-toolkit:

    bash

    sudo apt install -y nvidia-driver-<version> nvidia-container-toolkit

    在 jupyterhub_config.py 中添加:

    python

    c.DockerSpawner.extra_host_config = {'runtime': 'nvidia'}

    使用支持 GPU 的镜像,如 jupyter/tensorflow-notebook。

    5.3 用户环境定制

    若需要自定义用户镜像,创建 Dockerfile:

    dockerfile

    FROM jupyter/scipy-notebook:latest
    RUN pip install numpy pandas matplotlib
    CMD ["jupyterhub-singleuser"]

    构建并推送到 Docker Hub 或本地仓库:

    bash

    docker build -t my-jupyterlab-image .
    docker tag my-jupyterlab-image <your-dockerhub-username>/my-jupyterlab-image:latest
    docker push <your-dockerhub-username>/my-jupyterlab-image:latest

    在 jupyterhub_config.py 中更新:

    python

    c.DockerSpawner.image = '<your-dockerhub-username>/my-jupyterlab-image:latest'

    6. 常见问题排查

    • “Spawn failed” 错误:
      • 检查 docker logs jupyterhub 是否有 KeyError 或权限问题。
      • 确保 Docker 网络 jupyterhub-network 已创建。
      • 验证 DOCKER_NOTEBOOK_IMAGE 是否可拉取。
    • 用户容器无法访问数据库:
      • 确保数据库容器在同一 Docker 网络中:bashdocker network connect jupyterhub-network <database-container>
    • 权限问题:
      • 检查卷挂载路径权限,确保用户 jovyan(UID 1000)有写权限。
    • JupyterLab 不显示:
      • 确认 c.Spawner.default_url = ‘/lab’ 已设置。
      • 确保镜像中已安装 JupyterLab:bashdocker run -it <image> pip show jupyterlab

    7. 维护与备份

    • 备份用户数据:
      • 用户数据存储在 Docker 卷中,查看卷:bashdocker volume ls
      • 备份卷:bashdocker run --rm -v jupyterhub-user-<username>:/data -v /backup:/backup busybox tar cvf /backup/user-<username>.tar /data
    • 更新 JupyterHub:
      • 停止服务:docker-compose down
      • 更新镜像:docker pull jupyterhub/jupyterhub:latest
      • 重新启动:docker-compose up -d
    • 清理无用容器:bashdocker container prune

    8. 参考资源


    通过以上步骤,您可以在 Ubuntu 上成功部署一个支持 JupyterLab 和 DockerSpawner 的 JupyterHub 环境,适合教学、科研或团队协作场景。如需进一步定制或遇到具体问题,请提供更多细节,我可以为您提供针对性指导!

  • Jetpack对性能要求相对较高,删掉了

    最近博客老是挂掉,开了性能无约束也不行,查了下php的慢记录,发现是Jetpack插件造成的。索性把它删掉。

    本博客所用服务器为1u2G.

  • 我发现了我的数字遗迹:博客中国竟然还活着

    http://beloving.bokee.com/

    为了防止再次遗失,我把里面的几十篇内容挪到了本博客中。

    之前在各个平台上开了好多博客,新浪,搜狐,网易,教育,都黄了,还是要靠自己。

  • 个人博客服务器从Ubuntu16升级到了Ubuntu22

    因为阿里云的ECS开了性能限制,博客有几个访问就挂掉,索性整体迁移一下系统,于是把dnmp整个文件夹备份到本地,然后停机,换镜像,启动,再scp把文件放回去(这里其实应该打包下载和上传,会快很多)。然后用dnmp里面的一键启动命令恢复环境,最后docker compose up -d启动网站。

    不过这中间因为周末玩耍耽误了几天,幸好这个博客也没有多少人。

  • 岳父的慢阻肺

    岳父大人的慢阻肺一直是一个高危因素,自过年加重一次之后一直没有恢复,走走路就掉氧浓度,不到90%,吃了衡水中医院的中药也不见起色,于是约了中日友好医院的床位,等着来个全面检查。期间,到了广安门中医院开了中药,大夫看到他的现状对其是否需要用制氧机和呼吸机做氧疗有所怀疑,似乎没有那么重。等到在中日友好医院住了几天,做了肺功能和其他各项检查,终于给岳父确诊了糖尿病,并调整了心脏的药,把他的血糖和心律控制下来了,于是血氧也很神奇的转好了,这着实让岳父感到意外,我们也是。

    于是,我又一次对地方医院和北京重点医院的诊疗水平差距有了进一步的认识。看上去似乎没有特别的手段,但是北京的大夫规范细致的诊治确实有效,而衡水的大夫虽然救了急,却对具体的康复方法没有任何指导。

    有一个典型的例子,用于治疗慢阻肺的吸入药用到最好的,衡水也是这么开的,但是吸入方法却没有告知,也没有观察用的对不对,在北京问了之后,才发现之前几乎是无效吸入。而血压对血氧的影响之大,也不在之前的感知之内。

    在我想象中,这些都是照手册就能学会的东西,医生应该完全可以自学掌握。