小團隊如何妙用 JuiceFS
早些年還在 ENJOY 的時候, 就已經在用 JuiceFS, 並且一路伴隨著我工作過的四家小公司, 這玩意對我來說, 已經成了理所應當不可或缺的基礎設施, 對於我服務過的小團隊而言, 更是實實在在的好幫手. 趁著最近的徵文活動, 繼續拓展一下我的小團隊系列, 介紹下多年來我們團隊都在如何使用 JuiceFS.
不過這裡講到的用途, 恐怕都不算什麼"妙用", JuiceFS 是一個大社群了, 這些用法恐怕早被玩的滾瓜爛熟, 不過無妨, 本文其實是內部專案文件的拓展, 主要記錄一些維護過程的心得體會.
妙用: 容器共享儲存
雖然已經有了 CSI 支援, 但我們一直以來都將 Juicefs 掛載到所有 Kubernetes 節點的 /jfs
, 這樣一來, 所有容器應用都能輕鬆以 hostPath
方式來掛載宿主機目錄, 然後就有了共享儲存了. 此法的一些需要注意的地方:
jfs.mount
必須先於容器服務啟動, 畢竟二者建立依賴關係了. 以 docker 為例, 可以這麼寫:
# /etc/systemd/system/docker.service.d/12-after-jfs.conf
[Unit]
After=jfs.mount
- 要掛載的目錄必須先手動創建出來, 設定好許可權(使之與容器程序 uid 匹配), 這點其實不太方便, 如果你的團隊受不了這番折騰, 就直接用 CSI 吧.
- 叢集擴容存在一定不便之處, 畢竟此法要求所有節點都掛載好 JuiceFS. 萬一 Kubernetes 叢集冗餘不夠了, 需要加入新節點, 還需要多做一步掛載 JuiceFS, 才能加入叢集. 在極限情況下, 這樣的設計還挺耽誤時間的, 又多了一個直接用 CSI 的理由.
講了這麼多問題, 那看上去確實應該首選 CSI 而不是 hostPath
了, 不過 hostPath
就勝在管理更簡單, 推理更加直白, 因為依照我們的使用慣例, 都會採用類似 /jfs/[appname]-[cluster]
的命名, 比較一目瞭然, 對於不熟悉 Kubernetes PV 那一套的同事而言, 做事情也更加方便一些.
妙用: 網盤
有了一個隨心所欲存檔案的地方, 自然而然的念頭就是如何方便地把檔案分享出去. 大家都知道 JuiceFS 是可以在各個平臺掛載的 (甚至 Windows 上也很好用了), 但這並不是我要介紹的, 因為讓使用者各自在本地掛載 JuiceFS, 操作難度很大(何況還有安全問題). 我的意思是你可以簡單搭建一個 web 服務, 掛載 JuiceFS, 然後將檔案暴露出下載入口.
做這件事真的特別簡單, 我當時從萌生想法到搭建完畢不過 5 分鐘, 多虧了 lain, 只需要這樣一份簡短的 values.yaml
, 就能用 Python 拉起來 http.server
:
appname: jfs-http-server
volumes:
- name: jfs-data
hostPath:
path: "/jfs"
type: Directory
volumeMounts:
- name: jfs-data
mountPath: /jfs
deployments:
web:
replicaCount: 1
image: python:latest
podSecurityContext: {}
resources:
limits:
cpu: 1000m
memory: 80M
requests:
cpu: 10m
memory: 80M
command: ["python", '-m', 'http.server']
workingDir: /jfs
containerPort: 8000
ingresses:
- host: jfs
deployName: web
paths:
- /
稍有開發經驗的人就能看明白, 這是用社群的 python:latest
映象, 執行 http.server
, 然後掛載了宿主機的 /jfs
目錄. 服務上線以後, 訪問 jfs.example.com
就能瀏覽和下載 jfs 下的所有檔案了.
相比 jfs, 這一節似乎更是在給 lain 做廣告, 不過在 DevOps 的世界裡, 好用的東西總會互相吸引, 如果各位的團隊也實行 DevOps, 歡迎參考 lain.
妙用: 在 JupyterLab 進行 ad hoc 程式設計
我們團隊經常要和資料打交道, 不僅是做資料報表, 視覺化分析, 有時也希望在能摸到資料的地方驗證一些開發思路. 總不能讓大家都本地連線線上資料庫吧, 既不方便也不安全, 大夥也不一定都擅長在本地折騰這方面的工具. 因此我部署了 JupyterLab, 在裡邊做了大量易用性改善, 也就是內建了很多公司內部資料庫的快捷方式, 讓所有開發者/資料工程師都能便捷地使用封裝好的 Python 庫來做資料分析, 甚至直接利用 bokeh 來交付資料視覺化的工作.
顯而易見, 在 Jupyter 裡寫的程式碼也是要進入版本管理的, 辛苦寫的程式碼可千萬不能丟了. 因此我直接將 JupyterLab 的工作目錄設定為 JuiceFS, 所有的 notebook 就都存放於 JuiceFS 下了. 用 lain 部署 JupyterLab 十分簡單, 下面就是用到的 values.yaml
:
appname: lab
env:
SHELL: zsh
IPYTHONDIR: /lain/app
volumes:
- name: jfs
hostPath:
path: "/jfs/lab"
type: Directory
volumeMounts:
- name: jfs
mountPath: /jfs/lab
deployments:
web:
replicaCount: 1
podSecurityContext: {'runAsUser': 0}
terminationGracePeriodSeconds: 70
resources:
limits:
cpu: 2
memory: 4Gi
requests:
cpu: 100m
memory: 1Gi
command: ['jupyter', 'lab', '--allow-root', '--collaborative', '--no-browser', '--config=/lain/app/jupyter_notebook_config.py']
containerPort: 8888
workingDir: /jfs/lab/notebooks
ingresses:
- host: lab
deployName: web
paths:
- /
build:
base: lain:latest
prepare:
script:
- apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv E0C56BD4
- echo "deb http://repo.clickhouse.tech/deb/stable/ main/" | tee /etc/apt/sources.list.d/clickhouse.list
- apt-get update
- apt-get install -y apt-transport-https ca-certificates dirmngr clickhouse-client=20.12.8.5 clickhouse-common-static=20.12.8.5
- apt-get clean
- pip3 install -r requirements.txt
script:
- pip3 install -r requirements.txt
光有 Jupyter 其實用途不大, 所以我上邊提到, 要做好易用性建設, 比方說封裝好各種 database client:
from os import environ
import pandas as pd
import pymysql
from IPython.core.display import display
class MySQLClient:
def __init__(self, config):
config.update({
'charset': 'utf8mb4',
'cursorclass': pymysql.cursors.DictCursor,
'autocommit': True,
})
self.config = config
def use(self, db):
self.config['database'] = db
# just to make sure this db exists
return self.execute(f'use {db}')
def fetch(self, sql, *args, **kwargs):
return self.execute(sql, *args, **kwargs)
def fetchone(self, sql, *args, **kwargs):
kwargs.update({'fetchone': True})
return self.execute(sql, *args, **kwargs)
def executemany(self, sql, *args, **kwargs):
con = pymysql.connect(**self.config)
with con.cursor() as cur:
cur.executemany(sql, *args, **kwargs)
res = cur.fetchall()
con.close()
return res
def execute(self, sql, *args, **kwargs):
con = pymysql.connect(**self.config)
with con.cursor() as cur:
fetchone = kwargs.pop('fetchone', None)
as_pandas = kwargs.pop('as_pandas', None)
cur.execute(sql, *args, **kwargs)
if fetchone:
res = cur.fetchone()
else:
res = cur.fetchall()
con.close()
if as_pandas:
return pd.DataFrame(res)
return res
x = execute
def preview(self, table_name=None, n=2):
"""
# first, use a database
mysql_client.use('configcenter')
# show tables
mysql_client.preview()
# select example data from one table
mysql_client.preview('post')
# study one single column
mysql_client.preview('post.visibility')
"""
if not table_name:
return self.execute('show tables', as_pandas=True)
if '.' in table_name:
n = max([n, 20])
table_name, column_name = table_name.split('.')
part = self.execute(
f'''
SELECT DISTINCT {column_name}, count(*) AS count
FROM {table_name}
GROUP BY {column_name}
ORDER BY count DESC
LIMIT {n}
''', as_pandas=True
)
return part
part1 = self.execute(f'''
SELECT `column_name`,
`column_type`,
`column_comment`
FROM `information_schema`.`COLUMNS`
WHERE `table_name` = "{table_name}"
''', as_pandas=True)
display(part1)
part2 = self.execute(
f'''
SELECT *
FROM {table_name}
ORDER BY RAND()
LIMIT {n}
''', as_pandas=True
)
return part2
MYSQL_CONFIG = jalo(environ['MYSQL_CONFIG'])
mysql_client = mysql = my = MySQLClient(MYSQL_CONFIG)
mysql_client.use('mydatabase')
經過了這麼一堆封裝, 有多好用你都不敢想:
就靠著類似的快捷呼叫, 在我任職過的團隊裡, 從後端工程師, 資料分析師, 乃至於產品經理, 都能直接用 JupyterLab 進行工作.
好像主要都在介紹 Jupyter 了, 挺不好意思的. 但實際上這個專案和 JuiceFS 關係也很緊密:
- 產生的資料報表, 或者別的 ad hoc 流程的產物, 都放在 JuiceFS 上, 直接就能方便地分享給他人(見上方"網盤"一節)
- 所有的程式碼(在 Jupyter 的世界裡, 叫 notebook) 都存放在 JuiceFS, 用
juicefs snapshot
來做定期備份
妙用: GitLab, ClickHouse, Elasticsearch, etc.
理論上, 所有應用需要資料落盤的時候, 你都可以放到 JuiceFS 裡, 只要合適的效能區間匹配就行. 在這一節裡介紹一下我們探索過的一些用法:
- GitLab 對磁碟 IO 的要求還是挺高的, 尤其 MR 的時候, 如果程式碼庫很大, 最好還是視情況遷到 SSD. 但如果你是個小團隊, 專案不多也不大, 那放在 JuiceFS 上就能享受到一系列額外的好處, 比如用
juicefs grep
全域性搜尋程式碼倉庫(找垃圾程式碼), 方便地用juicefs snapshot
全量備份所有 repo data, 等等 - 我用 ClickHouse 配合 JuiceFS CSI, 方便地拉起 CH 叢集, 這點在 小團隊如何維護 Sentry 有更詳細介紹, 不重複
妙用: CI
就以 GitLab CI 為例吧, 給 Runner 設定好掛載目錄:
[runners.docker]
volumes = ["/var/run/docker.sock:/var/run/docker.sock", "/jfs:/jfs:rw", "/cache"]
沒想到僅僅是把 JuiceFS 掛載到 CI Runner 裡, 能開啟這麼多可能性, 下邊的這些案例每一個都沒有多"神", 但都是因為有了 JuiceFS, 事情變得特別方便和易維護:
釋出構建產物 (Artifacts)
本來 jfs 就是用來存檔案的, 將構建產物(比方說安卓打包)扔到 jfs, 再配合上文"網盤"一節介紹的檔案分享, 就能輕鬆做出下載連結了. 如果你團隊內部需要把構建產物釋出給非技術人員, JuiceFS + Python http.server 會是一個很好的配合.
持續部署
不是所有服務上線, 都是要更新容器的, 比方說不少前端應用的更新, 其實只是打包釋出靜態檔案, 而這一步往往也是在 CI 裡完成的, 這樣一來, 前端應用的釋出和 jfs 就能做一個很好的配合:
- CI Job 將前端應用的靜態檔案編譯好, 釋出到 jfs 下帶有版本號的路徑
- 更新 Nginx 配置, 將網站指向最新版的路徑, 這樣就算髮布出去了, 需要的話還可以觸發一下 CDN 預熱
- 如果要回滾的話, 也可以戳一下對應版本的 CI Job, 舊版就又部署回去了
又比方說, 我們有一些專案是放到特定的伺服器上執行的, 這些伺服器或許在機房, 或許在辦公室, 我當然可以給這些機器都做好公司內網 VPN, 然後挨個地配置出定期 git clone 更新, 但有了 jfs, 誰還用這種費力的方式來交換資料呢? 於是我們是這樣做的:
- 所有伺服器都掛載了 jfs, 我們的機器初始化流程裡就做好了
- 專案程式碼隨著 CI 釋出到 jfs, 比方說每次更新程式碼, 都會對
/jfs/[appname]
下的內容做覆蓋 - 伺服器上監聽
/jfs/[appname]
的檔案變動, 或者做出每天深夜定時重啟之類的, 都方便
全域性快取
GitLab CI, 或者別的各類 CI 系統, 都有各式各樣的快取機制吧, 但有些 CI 工具可以直接做成 Global, 而不需要 Per Project, 這種時候就直接拿 JuiceFS 存一份就好, 比方說:
Trivy
我們用 Trivy 來做容器映象安全掃描, Trivy 需要的就是各類安全漏洞的特徵資料, 掃誰都用的同一個 db, 因此我做了個 CI Job, 定期往 JuiceFS 下更新資料:
refresh_trivy_db:
stage: schedule
variables:
TRIVY_CACHE_DIR: /jfs/trivycache
rules:
- if: '$CI_PIPELINE_SOURCE == "schedule"'
script:
- trivy --cache-dir $TRIVY_CACHE_DIR image --download-db-only
然後所有專案都可以共用這一份 jfs 下的資料, 來進行映象漏掃了, 還不錯吧:
container_scanning:
stage: release
rules:
- if: '$CI_PIPELINE_SOURCE != "schedule"'
variables:
GIT_STRATEGY: none
TRIVY_CACHE_DIR: /jfs/trivycache
script:
- trivy --cache-dir $TRIVY_CACHE_DIR image --skip-db-update=true --exit-code 0 --no-progress --severity HIGH "${IMAGE}:latest"
- trivy --cache-dir $TRIVY_CACHE_DIR image --skip-db-update=true --exit-code 1 --severity CRITICAL --no-progress "${IMAGE}:latest"
Semgrep
Trivy 是掃映象, Semgrep 則是掃程式碼的, 需要定期更新用於掃描的規則檔案. 通常的姿勢是現場下載規則檔案, 不過由於網路問題, 國內大概更願意預先下載好這些檔案, 然後直接引用. 所以輪到 JuiceFS 出場了:
# ref: http://semgrep.dev/docs/semgrep-ci/sample-ci-configs/#gitlab-ci
semgrep:
image: semgrep-agent:v1
script:
- semgrep-agent
variables:
SEMGREP_RULES: >- # more at semgrep.dev/explore
/jfs/semgrep/security-audit.yaml
/jfs/semgrep/secrets.yaml
/jfs/semgrep/ci.yaml
/jfs/semgrep/python.yaml
/jfs/semgrep/bandit.yaml
rules:
- if: $CI_MERGE_REQUEST_IID
至於更新規則檔案的流程, 沒啥難度, 所以這裡就不贅述了.
如有幫助的話歡迎關注我們專案 Juicedata/JuiceFS 喲! (0ᴗ0✿)
- 30款提升組織效能 SaaS 工具,我們的寶藏工具箱大公開
- Grafana Prometheus 搭建 JuiceFS 視覺化監控系統
- 移動雲使用 JuiceFS 支援 Apache HBase 增效降本的探索
- Grafana Prometheus 搭建 JuiceFS 視覺化監控系統
- JuiceFS 在資料湖儲存架構上的探索
- JuiceFS 在資料湖儲存架構上的探索
- JuiceFS 快取預熱詳解
- JuiceFS 快取預熱詳解
- 巧用 JuiceFS Sync 命令跨雲遷移和同步資料
- 巧用 JuiceFS Sync 命令跨雲遷移和同步資料
- 老同事拉我創業,做一家開源儲存公司
- 小團隊如何妙用 JuiceFS
- 社群投稿|小團隊如何妙用 JuiceFS
- CSI 工作原理與JuiceFS CSI Driver 的架構設計詳解
- JuiceFS CSI Driver 架構設計詳解
- 怎麼做 HDFS 的原地平滑縮容?
- 來自開源社群的她力量
- 雲上共享檔案系統的相容性大比拼
- 用 JuiceFS 備份 Nginx 日誌可以這麼簡單
- 讓 JuiceFS 幫你做好「異地備份」