基於jupyter lab搭建網頁編程環境並添加自定義python kernel和matlab kernel以及plotly的使用

內容轉載自我的博客

說明

即使該系統有用戶zfbroottestubuntu等,下面介紹的步驟隻影響本用戶,既不需要root權限,也不會對其他用戶造成影響(開機自啟的service文件需要root用戶編輯和設置開機自啟,之後就不需要操作了)

1. 創建虛擬環境jupyter

# 安裝venv
sudo apt-get install python3-venv
# 創建虛擬環境,名稱為jupyter
python3 -m venv jupyter

2. 安裝nodejs(用於jupyterlab安裝擴展)

# 下載nvm用於管理npm、nodejs環境
wget -qO- //raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh | bash
# 重新啟動即可使用nvm命令
# nvm ls-remote          列出nodejs所有可用版本
# nvm install 10.10.0    安裝nodejs 10.10.0版本
# 安裝nodejs最新版本
nvm install node

把nvm環境bin文件夾放入PATH,即在~/.bashrc添加一行內容,必須把自己路徑放在前面,避免先搜索到/usr/local/bin目錄:

export PATH=/home/zfb/.nvm/versions/node/v14.5.0/bin:${PATH}

3. 安裝pip包

# 激活虛擬環境jupyter
source jupyter/bin/activate
# 在虛擬環境jupyter中安裝jupyterlab和nodejs
pip install -i //pypi.tuna.tsinghua.edu.cn/simple jupyterlab npm nodejs

4. 使用jupyterlab

先把python虛擬環境jupyterbin文件夾放入PATH,即在~/.bashrc添加一行內容,必須把自己路徑放在前面,避免先搜索到/usr/local/bin目錄:

export PATH=/home/zfb/jupyter/bin:${PATH}

在命令行輸入jupyter lab即可在本地端口打開(不需要激活虛擬環境),可以通過命令which jupyter得到/home/zfb/jupyter/bin/jupyter結果
在jupyterlab運行期間,可以通過命令jupyter notebook list查看當前運行的jupyter實例
列出當前已安裝的擴展:jupyter labextension list
卸載某個擴展:jupyter labextension uninstall my-extension-name
查看jupyter的kernel:jupyter kernelspec list
注意://127.0.0.1:8888/lab是jupyterlab的地址;//127.0.0.1:8888/tree是傳統jupyter notebook的地址

5. 配置jupyterlab

在終端輸入以下命令生成加密秘鑰:

# 激活虛擬環境jupyter
source jupyter/bin/activate
# 密碼設置為123456,此命令輸出密碼的sha1結果,用於下一步配置文件token
python -c "from notebook.auth import passwd;print(passwd('123456'))"

在命令行輸入jupyter lab --generate-config,則會生成文件/home/zfb/.jupyter/jupyter_notebook_config.py,打開該文件,修改以下內容:

c.NotebookApp.allow_remote_access = True
c.NotebookApp.ip = '0.0.0.0'
c.NotebookApp.notebook_dir = '/home/zfb/jp_data/'
c.NotebookApp.open_browser = False
c.NotebookApp.password = 'sha1:10d130e9bad7:b73d9821f96ccc4f42b2071b5dc46f2357373da3'
c.NotebookApp.port = 8888

安裝擴展時如果找不到node,那麼需要確保它在PATH,然後手動啟動jupyter lab,不要使用service啟動即可在瀏覽器點擊install安裝

6. 開機自啟jupyter

切換root用戶(zfb用戶不能執行sudo命令),創建文件jupyter-zfb.service,內容如下:

[Unit]
Description=Auto start jupyter lab Service for web
After=network.target

[Service]
Type=simple
# Type=forking
# PIDFile=/var/pid/master.pid
# 如果是在為其他用戶配置jupyterlab,這裡填對應的用戶名
User=zfb
Restart=on-failure
RestartSec=10s
WorkingDirectory=/home/zfb/jupyter
ExecStart=/home/zfb/jupyter/bin/jupyter lab
# ExecReload=/home/zfb/jupyter/bin/jupyter lab

[Install]
WantedBy=multi-user.target

然後依次執行下面命令:

# 複製jupyter-zfb.service文件到指定目錄
sudo cp ./jupyter-zfb.service /etc/systemd/system/
# 設置jupyter-zfb開機自啟
systemctl enable jupyter-zfb.service
# 重載service文件
sudo systemctl daemon-reload
# 查看所有的開機自啟項
systemctl list-unit-files --type=service|grep enabled
# 手動開啟jupyter-zfb服務
service jupyter-zfb start
# 查看jupyter-zfb服務的運行狀態
service jupyter-zfb status
# 停止jupyter-zfb服務
service jupyter-zfb stop

查看服務狀態的輸出如下:

root1@my-Server:~$ service jupyter-zfb status
● jupyter-zfb.service - Auto start jupyter lab Service for web
   Loaded: loaded (/etc/systemd/system/jupyter-zfb.service; enabled; vendor preset: enabled)
   Active: active (running) since Sun 2020-07-19 23:59:44 CST; 3s ago
 Main PID: 19426 (jupyter-lab)
    Tasks: 1 (limit: 7372)
   CGroup: /system.slice/jupyter-zfb.service
           └─19426 /home/zfb/jupyter/bin/python3 /home/zfb/jupyter/bin/jupyter-lab

Jul 19 23:59:44 my-Server systemd[1]: Started Auto start jupyter lab Service for web.
Jul 19 23:59:44 my-Server jupyter[19426]: [I 23:59:44.704 LabApp] JupyterLab extension loaded from /home/zfb/
Jul 19 23:59:44 my-Server jupyter[19426]: [I 23:59:44.704 LabApp] JupyterLab application directory is /home/z
Jul 19 23:59:44 my-Server jupyter[19426]: [I 23:59:44.706 LabApp] Serving notebooks from local directory: /ho
Jul 19 23:59:44 my-Server jupyter[19426]: [I 23:59:44.706 LabApp] The Jupyter Notebook is running at:
Jul 19 23:59:44 my-Server jupyter[19426]: [I 23:59:44.706 LabApp] //my-Server:8888/
Jul 19 23:59:44 my-Server jupyter[19426]: [I 23:59:44.706 LabApp] Use Control-C to stop this server and shut 
root1@my-Server:~$ 

問題:service運行,則一旦安裝擴展之後重新打開,擴展處就顯示500 Internal Server Error;但是直接運行在控制台無問題;nohup jupyter lab &也無問題;screen也無問題

6. 開機自啟和nohup運行

創建文件startjupyterlab.sh並分配執行權限:

#!/bin/bash
# 後台運行,重定向錯誤日誌,導出pid到文件
# nohup會免疫HUP信號,>>表示追加模式
/usr/bin/nohup /home/zfb/jupyter/bin/jupyter lab >> /home/zfb/jupyter/log/jupyterlab.log 2>&1 & echo $! > /home/zfb/jupyter/run_jupyter.pid

ubuntu 18.04不再使用inited管理系統,改用systemd,原本簡單方便的/etc/rc.local文件已經沒有了。systemd默認讀取/etc/systemd/system/下的配置文件,該目錄下的文件會鏈接/lib/systemd/system/下的文件,一般系統安裝完/lib/systemd/system/下會有rc-local.service文件,即我們需要的配置文件,裏面有寫到rc.local的啟動順序和行為,文件內容如下cat /lib/systemd/system/rc-local.service

#  SPDX-License-Identifier: LGPL-2.1+
#
#  This file is part of systemd.
#
#  systemd is free software; you can redistribute it and/or modify it
#  under the terms of the GNU Lesser General Public License as published by
#  the Free Software Foundation; either version 2.1 of the License, or
#  (at your option) any later version.

# This unit gets pulled automatically into multi-user.target by
# systemd-rc-local-generator if /etc/rc.local is executable.
[Unit]
Description=/etc/rc.local Compatibility
Documentation=man:systemd-rc-local-generator(8)
ConditionFileIsExecutable=/etc/rc.local
After=network.target

[Service]
Type=forking
ExecStart=/etc/rc.local start
TimeoutSec=0
RemainAfterExit=yes
GuessMainPID=no

systemctl status rc-local可以查看當前是否有rc-local這個服務,如果沒有則需要創建ln -fs /lib/systemd/system/rc-local.service /etc/systemd/system/rc-local.service。設置開機啟動並運行服務可以看到如下輸出:

zfb@my-Server:~$ service rc-local status
● rc-local.service - /etc/rc.local Compatibility
   Loaded: loaded (/lib/systemd/system/rc-local.service; static; vendor preset: enabled)
  Drop-In: /lib/systemd/system/rc-local.service.d
           └─debian.conf
   Active: inactive (dead)
Condition: start condition failed at Mon 2020-07-20 14:39:15 CST; 2s ago
           └─ ConditionFileIsExecutable=/etc/rc.local was not met
     Docs: man:systemd-rc-local-generator(8)
zfb@ny-Server:~$

然後執行以下操作:

# 創建文件
sudo vim /etc/rc.local
# 添加內容
#  #!/bin/bash  
#  
#  su - zfb -c "/bin/bash /home/zfb/startjupyterlab.sh"

# 添加執行權限
sudo chmod +x /etc/rc.local

運行service rc-local start即可啟動服務,service rc-local status查看運行狀態
日誌分割:然後創建文件/etc/logrotate.d/jupyter-zfb

su zfb zfb
/home/zfb/jupyter/log/jupyterlab.log{
    weekly
    minsize 10M
    rotate 10
    missingok
    dateext
    notifempty
    sharedscripts
    postrotate
        if [ -f /home/zfb/jupyter/run_jupyter.pid ]; then
            /bin/kill -9 `cat /home/zfb/jupyter/run_jupyter.pid`
        fi
        /usr/bin/nohup /home/zfb/jupyter/bin/jupyter lab >> /home/zfb/jupyter/log/jupyterlab.log 2>&1 & echo $! > /home/zfb/jupyter/run_jupyter.pid
    endscript
}

執行命令logrotate -dvf /etc/logrotate.d/jupyter-zfb可以查看每次輪詢的輸出

  • d表示只是顯示,並不實際執行
  • v表示顯示詳細信息
  • f表示即使不滿足條件也強制執行一次

7. 添加其他python環境的kernel

在不激活任何環境的終端,創建新的虛擬環境py36(最後把它添加到jupyter的kernel)

# 創建新的虛擬環境py36
python3 -m venv py36
# 激活新虛擬環境py36
source py36/bin/activate
# 為新環境安裝需要的庫
# pip install -i //pypi.tuna.tsinghua.edu.cn/simple
# 為虛擬環境安裝kernel
pip install -i //pypi.tuna.tsinghua.edu.cn/simple ipykernel
# 將此虛擬環境配置到jupyter的kernel中,此命令返回
# Installed kernelspec kernel_py36 in /home/zfb/.local/share/jupyter/kernels/kernel_py36
# 若不指定--user,則會提示權限不足,因為默認安裝到/usr/local/share/jupyter
python -m ipykernel install --name kernel_py36 --user
# 啟動jupyterlab,此時可以看到已經有兩個kernel可供切換(jupyter、kernel_py36)
jupyter lab

刪除某個kernel:jupyter kernelspec remove kernel_py36

8. 添加matlab的kernel

激活虛擬環境jupyter(jupyterlab被安裝在此虛擬環境),然後安裝matlab_kernal,再切換到matlab的安裝目錄extern/engines/python/,運行setup.py文件,具體步驟的命令如下:

# 激活虛擬環境jupyter
source jupyter/bin/activate
# 在虛擬環境jupyter安裝matlab_kernel
pip install matlab_kernel
# 若不指定--user,則會提示權限不足
python -m matlab_kernel install --user
# 切換到matlab安裝目錄的extern/engines/python/,然後運行命令
python setup.py install
# --build-base="/home/zfb/build" install --prefix="/home/zfb/jupyter/lib/python3.6/site-packages"
# 此時運行jupyter kernelspec list即可看到如下輸出
# Available kernels:
#   matlab     /home/zfb/jupyter/share/jupyter/kernels/matlab
#   python3    /home/zfb/jupyter/share/jupyter/kernels/python3

保證最後/home/zfb/jupyter/lib/python3.6/site-packages/文件夾下有matlab文件夾和matlab_kernel文件夾:

matlab
├── engine
│   ├── _arch.txt
│   ├── basefuture.py
│   ├── engineerror.py
│   ├── enginehelper.py
│   ├── enginesession.py
│   ├── fevalfuture.py
│   ├── futureresult.py
│   ├── __init__.py
│   ├── matlabengine.py
│   ├── matlabfuture.py
│   └── __pycache__
│       ├── basefuture.cpython-36.pyc
│       ├── engineerror.cpython-36.pyc
│       ├── enginehelper.cpython-36.pyc
│       ├── enginesession.cpython-36.pyc
│       ├── fevalfuture.cpython-36.pyc
│       ├── futureresult.cpython-36.pyc
│       ├── __init__.cpython-36.pyc
│       ├── matlabengine.cpython-36.pyc
│       └── matlabfuture.cpython-36.pyc
├── __init__.py
├── _internal
│   ├── __init__.py
│   ├── mlarray_sequence.py
│   ├── mlarray_utils.py
│   └── __pycache__
│       ├── __init__.cpython-36.pyc
│       ├── mlarray_sequence.cpython-36.pyc
│       └── mlarray_utils.cpython-36.pyc
├── mlarray.py
├── mlexceptions.py
└── __pycache__
    ├── __init__.cpython-36.pyc
    ├── mlarray.cpython-36.pyc
    └── mlexceptions.cpython-36.pyc
5 directories, 31 files


matlab_kernel
├── check.py
├── __init__.py
├── kernel.json
├── kernel.py
├── __main__.py
├── matlab
│   ├── engine
│   │   ├── _arch.txt
│   │   ├── basefuture.py
│   │   ├── engineerror.py
│   │   ├── enginehelper.py
│   │   ├── enginesession.py
│   │   ├── fevalfuture.py
│   │   ├── futureresult.py
│   │   ├── __init__.py
│   │   ├── matlabengine.py
│   │   ├── matlabfuture.py
│   │   └── __pycache__
│   │       ├── basefuture.cpython-36.pyc
│   │       ├── engineerror.cpython-36.pyc
│   │       ├── enginehelper.cpython-36.pyc
│   │       ├── enginesession.cpython-36.pyc
│   │       ├── fevalfuture.cpython-36.pyc
│   │       ├── futureresult.cpython-36.pyc
│   │       ├── __init__.cpython-36.pyc
│   │       ├── matlabengine.cpython-36.pyc
│   │       └── matlabfuture.cpython-36.pyc
│   ├── __init__.py
│   ├── _internal
│   │   ├── __init__.py
│   │   ├── mlarray_sequence.py
│   │   ├── mlarray_utils.py
│   │   └── __pycache__
│   │       ├── __init__.cpython-36.pyc
│   │       ├── mlarray_sequence.cpython-36.pyc
│   │       └── mlarray_utils.cpython-36.pyc
│   ├── mlarray.py
│   ├── mlexceptions.py
│   └── __pycache__
│       ├── __init__.cpython-36.pyc
│       ├── mlarray.cpython-36.pyc
│       └── mlexceptions.cpython-36.pyc
├── matlabengineforpython-R2020a-py3.6.egg-info
└── __pycache__
    ├── check.cpython-36.pyc
    ├── __init__.cpython-36.pyc
    ├── kernel.cpython-36.pyc
    └── __main__.cpython-36.pyc

7 directories, 41 files

可以參考鏈接1鏈接2

9. 使用frp內網穿透

騰訊雲主機的frps.ini添加一行:

# 不需要和frpc.ini一致
vhost_http_port = 8888

運行jupyterlab的服務器的frpc.ini添加一個部分:

[web]
type = http
local_port = 8888
custom_domains = lab.example.cn

如果要使用frp內網穿透的同時又給它設置域名,則域名解析記錄添加一條名稱為lab的A記錄到騰訊雲主機的IP(frps),在騰訊雲主機再添加一個nginx項:

    server{
        listen 80;
        # 如果需要ssl,參考//blog.whuzfb.cn/blog/2020/07/07/web_https/
        # listen 443 ssl;
        # include ssl/whuzfb.cn.ssl.conf;
        # 此時支持http與https
        server_name lab.example.cn;
        access_log /home/ubuntu/frp_linux_amd64/log/access_jupyter.log;
        error_log /home/ubuntu/frp_linux_amd64/log/error_jupyter.log;
        # 防止jupyter保存文件時413 Request Entity Too Large
        # client_max_body_size 50m; 0表示關閉檢測
        client_max_body_size 0;
        location /{
            proxy_set_header Host $host;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_redirect off;
            proxy_buffering off;
            proxy_pass //127.0.0.1:8888;
        }

        location ~* /(api/kernels/[^/]+/(channels|iopub|shell|stdin)|terminals/websocket)/? {
            proxy_pass //127.0.0.1:8888;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header Host $host;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            # WebSocket support
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
        }
        # -------  舊方法:還是有部分報錯/api/kernels err_too_many_redirects  ---------
        # # 必須有,否則請求/api/kernels/ 的狀態碼都是400
        # location /api/kernels/ {
        #     proxy_pass            //127.0.0.1:8888;
        #     proxy_set_header      Host $host;
        #     # websocket support
        #     proxy_http_version    1.1;
        #     proxy_set_header      Upgrade "websocket";
        #     proxy_set_header      Connection "Upgrade";
        #     proxy_read_timeout    86400;
        # }
        # # 必須有,否則請求/terminals/ 的狀態碼都是400
        # location /terminals/ {
        #     proxy_pass            //127.0.0.1:8888;
        #     proxy_set_header      Host $host;
        #     # websocket support
        #     proxy_http_version    1.1;
        #     proxy_set_header      Upgrade "websocket";
        #     proxy_set_header      Connection "Upgrade";
        #     proxy_read_timeout    86400;
        # }
    }

10. VSCode連接jupyter

由於jupyterlab可以運行在本地指定端口,所以可以通過IP和端口在客戶自己瀏覽器進行遠程開發(保證遠程服務器的jupyter lab開機自啟服務),這在局域網內很方便,但是對於沒有公網IP的話,就無法使用此功能
好在VSCode可以直接打開遠程jupyter,具體操作如下

  • 在客戶本地機器安裝Remote Development三件套插件,然後選擇Remote-SSH: Connect to host,可以在本地提前創建配置文件(C:\Users\zfb\.ssh\config或者C:\ProgramData\ssh\ssh_config),內容類似:
# 第一個遠程機器
Host mylab
    HostName 54.33.135.211
    Port 22
    User ubuntu
  • 根據提示輸入遠程服務器的密碼即可連接成功,然後在遠程服務器安裝PythonPylanceIntelliCode這三個插件,打開遠程服務器的文件夾,創建一個擴展名為ipynb的文件,然後VSCode會自動提示選擇Python版本(既可以選擇系統的,也可以根據路徑選擇某個虛擬環境裏面的),接着VSCode會自動連接Kernel,用戶可以在右上角查看當前Kernel的狀態或者切換Kernel

11. ssh連接jupyter在本地打開

在瀏覽器使用遠程ip:port的方法,則服務器必須有公網,而且還費流量,另一種方法,ssh連接,然後端口映射
服務器1:處於內網,已安裝frpc,用戶名為zfb,已安裝配置好jupyterlab,運行在8888端口
雲主機2:處於公網,ip為56.78.12.34,已安裝frps,用戶名為ubuntu,僅用於服務器的內網穿透,端口7001為服務器1提供ssh轉發
執行以下命令,把用戶3的電腦的本地端口8080綁定到服務器1的端口8888:
ssh -p 7001 -NL localhost:8080:localhost:8888 [email protected]
此時在用戶3的本機打開網址//127.0.0.1:8080即可訪問服務器1的jupyterlab

12. matplotlib安裝

首先在虛擬環境jupyter安裝matplotlib庫和ipympl庫,後者用於顯示可交互圖形

# 激活虛擬環境jupyter
source jupyter/bin/activate
# 在虛擬環境jupyter安裝matlab_kernel
pip install -i //pypi.tuna.tsinghua.edu.cn/simple matplotlib ipympl

重新打開瀏覽器會提示rebuild,點擊確定。等待build成功然後點擊reload即可正常使用此插件,如下代碼

%matplotlib widget
import pandas as pd
import numpy as np
import matplotlib
from matplotlib import pyplot as plt

ts = pd.Series(np.random.randn(1000), index=pd.date_range('1/1/2000', periods=1000))
ts = ts.cumsum()

df = pd.DataFrame(np.random.randn(1000, 4), index=ts.index,
                  columns=['A', 'B', 'C', 'D'])
df = df.cumsum()
df.plot()
plt.legend(loc='best')
plt.title('我是中文')

如果中文亂碼,則糾正中文亂碼

13. 使用plotly顯示python程序繪製的圖片

使用方法見官網,python的使用不需要key和用戶名,直接用就行

14. 使用plotly顯示matlab的圖片

詳細使用方法見官網教程。註冊plotly的chart-studio賬號,然後在個人賬戶的setting點擊api keys,選擇Regenerate key,記住這個key和自己的用戶名。然後下載壓縮包並解壓,打開matlab,輸入

>> cd ~/plotly-graphing-library-for-matlab-master/
>> plotlysetup('DemoAccount', 'lr1c44zw81')  % 回車,剩下的內容都是自動執行
Adding Plotly to MATLAB toolbox directory ...  Done
Welcome to Plotly! If you are new to Plotly please enter: >> plotlyhelp to get started!

此時會創建文件~/.plotly/.credentials,裏面已經保存用戶名和key(注意該用戶需要有toolbox的寫入權限)
然後在jupyterlab寫:

[X,Y,Z] = peaks;
contour(X,Y,Z,20);
% 個人用戶還是用離線模式吧,否則只能創建100個圖,還必須是公開分享
getplotlyoffline('//cdn.plot.ly/plotly-latest.min.js')
fig2plotly(gcf, 'offline', true)

該命令會在當前目錄生成一個html文件,雙擊打開即可
注意: 如果發現在其他目錄無法使用fig2plotly函數,則可能是上一步驟,將plotly添加到Matlab工具箱出現了問題。可以自己手動將其複製到指定工具箱路徑,或者直接把plotly-graphing-library-for-matlab-master文件夾的絕對路徑添加到Matlab PATH

15. 使用plotly繪製matlab的包含ColorBar的圖片

如果正在使用新版Matlab(R2019a以後),在.m文件中如果使用colorbar函數,則在調用plotly時候可能會遇到報錯

Insufficient number of outputs from right hand side of equal sign to satisfy assignment.

Error in findColorbarAxis (line 8)
colorbarAxis = obj.State.Axis(colorbarAxisIndex).Handle;

Error in plotlyfig/update (line 557)
                colorbarAxis = findColorbarAxis(obj, handle(cols(c)));

Error in plotlyfig (line 208)
                obj.update;

Error in fig2plotly (line 44)
p = plotlyfig(varargin{:});

參考鏈接,於是打開文件findColorBarAxis.m

# 若Matlab的Plotly工具箱安裝位置為/home/Polyspace/R2020a/toolbox/plotly
sudo vi /home/Polyspace/R2020a/toolbox/plotly/plotlyfig_auz/helpers/findColorBarAxis.m

整個文件內容替換為如下:

function colorbarAxis = findColorbarAxis(obj,colorbarHandle)
if isHG2    
    colorbarAxisIndex = find(arrayfun(@(x)(isequal(getappdata(x.Handle,'ColorbarPeerHandle'),colorbarHandle)),obj.State.Axis));
    % If the above returns empty then we are on a more recent Matlab
    % release where the appdata entry is called LayoutPeers
    if isempty(colorbarAxisIndex)
        colorbarAxisIndex = find(arrayfun(@(x)(isequal(getappdata(x.Handle,'LayoutPeers'),colorbarHandle)),obj.State.Axis));
    end
else
    colorbarAxisIndex = find(arrayfun(@(x)(isequal(getappdata(x.Handle,'LegendColorbarInnerList'),colorbarHandle) + ...
        isequal(getappdata(x.Handle,'LegendColorbarOuterList'),colorbarHandle)),obj.State.Axis));
end
colorbarAxis = obj.State.Axis(colorbarAxisIndex).Handle;
end