之前整理过一篇文章总结了 Flexget 的一些常用方法,长久以来都是在 avistaz.to 生成 RSS,订阅下载,但后来发现国内的大多数站点都是使用 NexusPHP 运行,并且 RSS 支持(过滤条件)也不是非常完善,于是就发现了 flexget-nexusphp 这个插件。
flexget-nexusphp 是一个 Flexget 的插件,可以用来过滤 NexusPHP 的内容。
Flexget 安装插件的方法非常简单,从 GitHub 获取 nexusphp.py
代码。将该文件移动到 Flexget 配置目录。
如果是和我一样使用 Docker 启动的 Flexget,在我上一篇 的配置中,我将 Flexget 的配置文件挂载到了本地目录,所以只要找到 ~/flexget/config
然后在该文件夹下创建 plugins
目录,并放入 nexusphp.py
即可。
注意 plugins 目录需要和 config.yml
这个 YAML 配置文件在同级。
然后重启 Flexget 容器。
完成插件的安装之后就可以在配置文件中使用 nexusphp 选项。
tasks:
hdchina:
rss:
url: http://hdchina.org/rss.xml
other_fields: [link]
download: /data
nexusphp:
cookie: "hdchina=;_GRECAPTCHA=;PHPSESSID="
left-time: 1 hours
discount:
- free
- 2xfree
全部的配置选项:
nexusphp:
cookie: 'a=xxx; b=xxx' # 必填
discount: # 优惠信息 选填
- free
- 2x
- 2x50%
- 2xfree
- 50%
- 30%
seeders: # 做种情况 选填(兼容性较差不建议使用)
min: 1
max: 2
leechers: # 下载情况 选填(兼容性较差不建议使用)
min: 10
max: 100
max_complete: 0.8
left-time: 1 hours # 优惠剩余时间 选填
hr: no # 是否下载HR 选填
adapter: # 站点适配器,自行适配站点,参考最下方常见问题 选填
free: free
2x: twoup
2xfree: twoupfree
30%: thirtypercent
50%: halfdown
2x50%: twouphalfdown
comment: no # 在torrent注释中添加详情链接 选填
user-agent: xxxxxx # 浏览器标识 选填
remember: yes # 记住优惠信息 选填 请勿随意设置
配置解释:
cookie
网站cookie 必须填写discount
优惠类型 默认不限制优惠类型。free 2x 2x50% 2xfree 50% 30%
注意:x为英文字母
seeders
做种情况 做种人数超出范围的,Flexget将不会下载
注意:此选项兼容性较差
min
最小做种人数。整数,默认不限制max
最大做种人数。整数,默认不限制leechers
下载情况 下载人数超出范围的,Flexget将不会下载
注意:此选项兼容性较差
min
最小下载人数。整数,默认不限制max
最大下载人数。整数,默认不限制max_complete
下载者中最大完成度 超过这个值将不下载。 小数,范围0-1.0
,默认为1left-time
最小剩余时间 当实际剩余时间小于设置的值,则不下载。 时间字符串,例如 3 hours
、10 minutes
、1 days
。 例如设置1 hours
,优惠剩余59分钟,那么就不下载。默认不限制hr
是否下载HR种 默认 yes
yes
会下载HR,即不过滤HRno
不下载HRadapter
站点适配器 站点不兼容时可自定义,具体参考 判断站点以及适配站点comment
在torrent注释中添加详情链接
yes
在torrent注释中添加详情链接,方便在BT客户端查看no
默认值user-agent
浏览器标识 默认为Google浏览器remember
记住优惠信息 不建议设置为 no,因为会增大站点压力。默认 yes最后,个人也搭建了一个私人的 PT,分享一些电影、图书、综艺,欢迎来玩。
看过我过去文章的人都知道在此之前我都使用 WizNote 来作为本地笔记应用,但是这两年在记笔记这件事情上出现了非常多的可能性,虽然我本人一直在关注着不同类型的笔记应用,从传统的 Evernote,OneNote 到 Notion 等等模块化的笔记应用,多多少少也尝试了一下,但是一直没有深入的去用,因为就我目前的需求,WizNote + 本地的 markdown + vim + git 基本就满足了,直到昨天晚上我在豆瓣上阅读了 笔记类软件的双向链新浪潮 这篇文章,然后我发现原来我在记笔记的时候遇到的一些问题原来还有这样一种解决方式。
我在用纯手工方式管理笔记的时候遇到的这些问题,中间我也曾想使用 wiki 来代替笔记,尝试了 [[GitBook]], BookStack, [[TiddlyWiki]] 等等,都或多或少的有些问题。
我用原来 markdown + vim + git 的方式存在的最大的问题就是多媒体文件的管理,如果我只简单的记录文本,没有那么多的媒体(图片,音视频) 需要管理,还是能够非常方便的进行管理的,而一旦图片等等媒体文件成倍增长时,这个时候我就陷入了管理困境。
而 Obsidian 将媒体文件和 markdown 文件存放到一起,并且可以简单的使用名字相互进行关联,这使得管理变的轻松。内建的录音器甚至可以直接录音后在文件中插入音频内容。
关于内链,可能我的思路还停留在上个世纪的“超链接”,我文章的大部分链接都是通过超链接相互关联的,而在 WizNote 中,将不同的笔记内容进行关联,我就只能使用 tag,或者手工进行分类来实现。而分类和 Tag 存在一些缺点,比如有些内容可能并不能以某一个分类概括,又或者可能被划分到多个分类下,而 Tag 虽然可以一定程度上避免分类的问题,但是在确定 Tag 的时候就会遇到如何定义 Tag 的问题,比较笼统的定义还是比较精确的定义。
而在 Obsidian 中,只需要 [[topic]]
就可以非常方便的实现链接到名为 topic 的文章,而这一切都是 Obsidian 做的,并且 topic 的这篇笔记甚至可以不存在,在写的时候写下,然后 Obsidian 会生成链接,只需要点击就可以快速的创建这个 topic. 到这里我才发现原来我那种手工管理内部链接的方式简直太愚蠢了。更甚至通过链入和链出就形成了一个非常庞大完整的网状结构,这样就将历史的笔记也可以以这种形式激活起来,不断的丰富自己的知识网络。
在 Obsidian 中通过 internal link 和 graph view 完美的解决了笔记之间关联的问题。
Obsidian 这样介绍自己:
Obsidian is a both a Markdown editor and a knowledge base app.
直接献上官网地址:
再来看看预览图:
Linux 下官方默认给出的是 AppImage,还可以下载 snap 包,通过如下方式安装:
sudo snap install obsidian.snap --dangerous
如果不添加 --dangerous
可能会报错:
error: cannot find signatures with metadata for snap “obsidian_0.13.19_amd64.snap”
Obsidian 这些功能是吸引我让我尝试的理由。
所有数据都以本地文本形式保存。其他一些软件使用私有的格式保存用户数据是让我避而远之的原因之一。
Obsidian 管理的所有数据都在本地,那也就意味着用户可以选用自己喜欢的任何同步工具进行数据加密,同步等等。比如现成的同步方案 Dropbox 等等,而我选择使用另一款 分布式同步工具 Syncthing
如果上手使用 Obsidian,那么第一感受就是可以在笔记的任何地方非常快速的创建内链,通过内链的方式,系统里面的笔记就形成了网状的结构,这也是 bi-directional linking 双向内链的精髓之处。
Obsidian 在官方的 Demo 中引述了 [[洛克 | 约翰 洛克]] 在 1690 年发表的 《An Essay Concerning Human Understanding》来解释人类思想形成的方式: |
The acts of the mind, wherein it exerts its power over simple ideas, are chiefly these three:
- Combining several simple ideas into one compound one, and thus all complex ideas are made.
- The second is bringing two ideas, whether simple or complex, together, and setting them by one another so as to take a view of them at once, without uniting them into one, by which it gets all its ideas of relations.
- The third is separating them from all other ideas that accompany them in their real existence: this is called abstraction, and thus all its general ideas are made.
简单的翻译:
笔记的层级关系 (Hierarchy) 实现了第一点,而内部链接 (linking) 正是第二点,而第三点如何在笔记中应用, Obsidian 的作者也没有想清楚,但他也说了,这可能是更高阶的抽象 —- but it might have something to do with programming or macros.
记笔记是一件非常个人化的事情,这也就意味着不可能有一个包容一切的解决方案适用于所有人。
Note-taking is an incredibly personal thing
所以 Obsidian 并不会武断地尝试去提供一个完整的产品,而是提供一个基础功能以及很多的功能模块,用户可以自己创造并实现自己的需求。
最基础的功能包括,查看文件,编辑文件,搜索文件,而这对于一般的需求也已经完全足够。在此之上,你可以构建独立的功能模块来增加笔记的体验,比如:
Obsidian 不期望有一个插件可以解决所有的问题,但 Obsidian 提供了足够的自由度,在能够实现不同需求的时候,也不会搞乱界面。
最常用的快捷键整理,下面有一些是我已经 remapping 过的,可以根据自己的习惯重新设置快捷键的。
新建笔记相关:
Shortcut | description |
---|---|
Cmd+n | New note |
Cmd+Shift+n | New Zettelkasten note |
Cmd+p | Filter the command |
编辑相关的:
Shortcut | description |
---|---|
Cmd+b | bold |
Cmd+i | italic selection |
Ctrl+Alt+L | format markdown (借助 Linter 插件) |
笔记间跳转:
Shortcut | description |
---|---|
Cmd+o | Open quick switcher, jump to different notes by fuzz search |
Option+Enter | follow the link under cursor |
Cmd+Option+Enter | Open link under cursor in new pane |
Cmd+[ |
Back |
Cmd+] |
Forward |
Cmd+hjkl | Navigate to left/below/above/right pane |
Cmd+w | Close active pane |
Cmd+\ |
Split vertically |
Cmd+- |
Split horizontally |
浏览模式:
Shortcut | description |
---|---|
Cmd+e | Toggle edit/preview mode |
Obsidian 的模板功能非常强大,我自定义了一个快捷键 ⌘ + ⇧ + i 来从模板中快速插入到当前的文档中。
卡片标签式笔记法,Obsidian 也可以兼容,启用插件后侧边栏点击就能够快速建立时间戳开头的笔记迅速进入编写。
都知道 Obsidian 其实是一个本地的离线客户端,官方截止 11 月份还没有推出同步的功能,但是优先推出了 Publish 服务,可以将笔记一键发布到 Obsidian 提供的网站上。但是对于我而言,我习惯将笔记在本地整理,然后将相关的内容整理成文章发布在这个博客中,所以剩下的问题就是我的本地笔记同步的问题了。
我个人是将 Obsidian 的本地仓库放到一个 Git 仓库中管理,并且每隔一定时间自动提交到 Git 中,这样即使我不在电脑边,也可以第一时间访问到我的内容。我使用 Hammerspoon 提供的 task api,写了一个 简单的脚本 自动提交仓库。
Obsidian 的开发 Roadmap
在整理了这篇文章半年后,我几乎每条都在使用 Obsidian,甚至用 Karabine 绑定了 ob 的快捷键 ,在任何情况,任何应用中,我只需要按下 o,不松开,然后快速按下 b,就可以 open Obsidian 了。
这是半年后的 Graph
虽然使用过程中从来没有遇到任何问题,但是却一直收到其他产品的推荐,比如 Logseq, RoamEdit 等等,虽然 Obsidian 已经足够满足我的使用了,但也经不住尝试了一下其他的产品,只能说今年因为 Roam Research 的创新激活了笔记应用的市场。
[[Logseq]] 在自己的官网将自己描述为一个受到 Roam Research, Org mode, Tiddlywiki, Workflowy 启发的开源的 Roam 笔记应用,我打开官网尝试了一下,虽然双向链,块引用,运行在浏览器,以 GitHub 作为笔记的存储,看起来很美好,似乎是 Roam Research 的代替品,但是我在使用的过程中,不清楚是以为网络问题还是因为我没有登录的关系,体验并不是很好,一来创建新页面的时候卡了,而来默认的显示状态和编辑状态的变化略大,使得视觉上的体验不是很好。在界面交互上甚至并不能和其提到 Workflowy 的网页应用流畅度相比。不过因为其开源属性使得每一个人都可以根据自己的喜好进行扩展和修改,其未来可期。
另外一个需要注意的就是目前的 Logseq 只有网页版,并且因为 Logseq 网络访问需要经过其代理才能将数据保存到 GitHub,相较于 Obsidian 直接使用本地 Markdown 文件保存,Logseq 并不占优势。不过有能力可以自行搭建。
Logseq 目前已经可以使用客户端,所有的数据都可以离线在本地。
看名字就知道又是一款 Roam Research 的仿作,不过让我惊奇的是其网页的流畅度相较于 Logseq 还是不错。但是问题依然是那样的,官网一行隐私说明都没有,一个备案号,一个交流群,看着就不想深入使用的样子。可能也是某位大神练手之作,值得鼓励。不过我就不去尝试使用了。
Workflowy 和 RoamEdit 比较类似,并且在网页操作上也比较流畅,但两者虽然都支持导出数据,但依然需要联网才能使用,和我个人的情况并不相符,就算了。
往往有些时候鱼与熊掌不可兼得,当选择了网页版,就自然地获得了数据的同步,也自然可以期望未来的多客户端同步,但问题也就是在此,一旦这些网络的服务关闭,或者再一个没有网络的地方,那么一切都没有办法获取了。而 Obsidian 使用纯文本的方式,并且桌面客户端并不会发起网络连接,那么自然就丢失了同步的便利,而与此同时你也就获得了自己笔记的所有权,即使 Obsidian 未来不再更新了,那么也可以使用 Vim 或者 VS Code 的 Foam 获得一份相差不多的体验,并且因为是纯文本,所以那些 终端里的工具 ,比如 fzf , Vim , rg 等等都可以直接拾起来用。这样的体验反而要比等待网络连接,然后才能进行操作要来的快捷方便很多。另外 Obsidian 不能在移动端使用的问题,我通过 Syncthing 加上 Markor 完美的解决了。其他移动端的可以参考官网给出的 建议 。
再就是块应用,Obsidian 不支持,但是 Obsidian 可以对笔记的子标题进行引用,对我来说似乎也已经足够了。毕竟 Zettelkasten 的精髓部分就是每一则笔记的原子性。
The space used by the Java Runtime to allocate memory to Objects and JRE Classes is called Heap. The heap space can be configured using the following JVM arguments:
-Xmx<size> — Setting maximum Java heap size
-Xms<size> — Setting initial Java heap size
Heap dump is a snapshot of the Java memory. It contains information about the Java objects and classes in the heap at the moment the snapshot is triggered.
First, you have to identify the Java process Id:
ps aux |grep "java"
the normal way to capture the heap dump is using jmap
:
jmap -dump:live,format=b,file=/tmp/heapdump.hprof PID
Try the following. It comes with JDK >= 7:
/usr/lib/jvm/jdk-YOUR-VERSION/bin/jcmd PID GC.heap_dump FILE-PATH-TO-SAVE
Example:
/usr/lib/jvm/jdk1.8.0_91/bin/jcmd 25092 GC.heap_dump /opt/hd/3-19.11-jcmd.hprof
This dumping process is much faster than dumping with jmap
! Dumpfiles are much smaller, but it’s enough to give your the idea, where the leaks are.
The [[Eclipse Memory Analyzer]] is a fast and feature-rich Java heap analyzer that helps you find memory leaks and reduce memory consumption.
Use the Memory Analyzer to analyze productive heap dumps with hundreds of millions of objects, quickly calculate the retained sizes of objects, see who is preventing the Garbage Collector from collecting objects, run a report to automatically extract leak suspects.
Eclipse Memory Analyzer Tool 是一个基于 Eclipse 的分析工具。
I recently installed Eclipse MAT (Eclipse Memory Analyzer Version 1.9.1) on Mac OS Catalina (10.15.3). I needed to review a 4g heap dump. The default JVM heap size for MAT is 1024m.
I think the easiest way to increase the JVM’s heap size is to use a shell window - go to the /Applications/mat.app/Contents/Eclipse/ folder. Then vi MemoryAnalyzer.ini and change -Xmx1024m to your required value, in my case I went with -Xmx10g.
To review the change, restart MAT and go to the help -> About Eclipse Memory Analyzer then click installation details, and look for the entry: eclipse.vmargs=-Xmx10g about 50 lines down.
我的 Trello 中有这样一张卡片 —- 学习在 Trello 中使用 Pomodoro —- 已经很久了,这期间也看了一些《番茄工作法》的书,重要的是我开始重度依赖于 Trello 来作为我的任务管理以及时间管理工具。这期间也尝试用 Plus for Trello 来提高使用 Trello 的效率。当然也得到一些体验,但到现在我依然觉得有哪个地方不太对,我很多时候没有严格按照 Pomodoro 约定的 25-5 分钟来执行一些任务,而正是这些任务,可能耗费太长时间所以在我的 Trello 看板中想生根了一样,很久很久没有移动。虽然每天都想要移动一下,但终究没有结果。所以我想写这篇文章来回顾一下我的使用方式,以及想在回顾的过程中发现存在的问题。
就像 Pomodoro 约定的那样 25 分钟 Focus,5 分钟休息,我在刚刚开始的时候也非常的不适应,我可能把之前的习惯带了过来,并没有制定计划,也不知道一项任务可能花费的时间,就这样糊涂的开始,然后糊涂的结束。所以我开始思考为什么 Pomodoro 工作法需要将时间划分到 25 分钟,经过几个月的思考,我想是因为,必须要保证我的每一个任务都是可以追踪的,可以预估时间的,而只有细分的任务才有被衡量的价值,这样才能够尽快的达到既定目标。而不是因为目标太遥远,日久之后就渐渐忘掉,也不是因为太过于复杂,需要拆分多个任务而迟迟无法展开。
我的常用看板里面里经常有这样一条,看某某书,学习某个新技能,而这两个例子正是无法追踪和衡量的,看某本书,实际可能是一个比较长的过程,尤其是技术类书籍,利用 Trello 的卡片显然是覆盖不了的,同理和学习一个新技能一样,学到什么程度算是学习,是需要入门这个技术,还是需要到熟悉的程度,还是说要精通,这个卡片显然是不能够决定的。所以我想是不是以后我再添加这些卡片的时候,可以比如提前看一下书籍的目录,根据目录将书划分成多个模块,然后利用一列来管理这些章节的内容,同理学习某一个新技术能不能将入门,比如在我的机器上将其 Demo 跑通,然后阅读某块源码单独作为独立的开片来进行管理。
基于这个思路,以及官方 How to Pomodoro Your Way to Long-Lasting Productivity 中所说,我逐渐将我的任务卡片细化,原来我可能只会在开片上写一个关键字来提醒我,而现在我则有意识的将其写成动名词结构,比如“写一篇关于如何在 Trello 中实践 Pomodoro 的文章”,然后我会使用 Plus for Trello 来预估一个完成这个任务的时间。Plus for Trello 会将预估的时间写入到卡片的评论中,这样所有的 Web 界面都可以实时同步。
plus! 0/2
这表示我预估需花费 2 个小时的时间来完成这个任务(这里的语法是 Spent/Estimate)。而每次开始任务都使用 Plus for Trello 的计时器功能,结束时会自动提交一个评论,比如
plus! 0.62/0
则表示这次花费了 0.62 小时。
虽然 Proxmox 自身已经有一个比较简单的系统监控,但对于我来说每一次都需要登录到其后台才能看到,而且它自身的监控视图是没有报警策略的。所以我想着这两天反正在学习 Prometheus 这不是正好是一个不错的契机来在具体环境中使用一下,所以才有了这篇文章。Prometheus 和 Proxmox 相关的内容可以参考之前的文章。
首先来对比一下前后的效果。
Proxmox 后台默认的监控面板。
Grafana 中显示
当然如果你不喜欢这个样式,Grafana 给予了用户非常充分的定制化可能,你可以自己打造自己的监控视图。
准备工作:
sudo groupadd --system prometheus
sudo useradd -s /sbin/nologin --system -g prometheus prometheus
创建配置目录:
sudo mkdir /var/lib/prometheus
for i in rules rules.d files_sd; do sudo mkdir -p /etc/prometheus/${i}; done
下载 Prometheus 二进制:
mkdir -p /tmp/prometheus && cd /tmp/prometheus
curl -s https://api.github.com/repos/prometheus/prometheus/releases/latest \
| grep browser_download_url \
| grep linux-amd64 \
| cut -d '"' -f 4 \
| wget -qi -
解压和配置
tar xvf prometheus*.tar.gz
cd prometheus*/
mv prometheus promtool /usr/local/bin/
mv prometheus.yml /etc/prometheus/prometheus.yml
mv consoles/ console_libraries/ /etc/prometheus/
清理
cd ~/
rm -rf /tmp/prometheus
创建 systemd 配置
这里使用 systemd 来配置管理 Prometheus
sudo tee /etc/systemd/system/prometheus.service<<EOF
[Unit]
Description=Prometheus
Documentation=https://prometheus.io/docs/introduction/overview/
Wants=network-online.target
After=network-online.target
[Service]
Type=simple
User=prometheus
Group=prometheus
ExecReload=/bin/kill -HUP $MAINPID
ExecStart=/usr/local/bin/prometheus \
--config.file=/etc/prometheus/prometheus.yml \
--storage.tsdb.path=/var/lib/prometheus \
--web.console.templates=/etc/prometheus/consoles \
--web.console.libraries=/etc/prometheus/console_libraries \
--web.listen-address=0.0.0.0:9090 \
--web.external-url=
SyslogIdentifier=prometheus
Restart=always
[Install]
WantedBy=multi-user.target
EOF
配置相应的权限
for i in rules rules.d files_sd; do sudo chown -R prometheus:prometheus /etc/prometheus/${i}; done
for i in rules rules.d files_sd; do sudo chmod -R 775 /etc/prometheus/${i}; done
sudo chown -R prometheus:prometheus /var/lib/prometheus/
启动及配置开机启动
sudo systemctl daemon-reload
sudo systemctl start prometheus
sudo systemctl enable prometheus
这个时候可以检查一下 9090 端口(直接访问浏览器,或者 netstat -tupln | grep 9090
)
看过之前关于 Prometheus 那片文章应该就知道 Prometheus 有两种获取数据的方法,pull 和 push,这里我们在 Proxmox 上安装 exporter,然后让 Prometheus 来 pull 数据。
安装必要的组件
apt install python python-pip
pip install prometheus-pve-exporter
创建授权配置:
vi /etc/prometheus/pve.yml
写入:
default:
user: user@pve
password: your_password_here
verify_ssl: false
在这里要注意一个坑,使用 prometheus-pve-exporter
依赖于 Proxmox 的用户授权,而这里的用户需要到 Proxmox 后台,DataCenter -> user 标签下查看,对于我而言,这里需要填写 root@pam
.
systemd
sudo tee /etc/systemd/system/prometheus-pve-exporter.service<<EOF
[Unit]
Description=Prometheus exporter for Proxmox VE
Documentation=https://github.com/znerol/prometheus-pve-exporter
[Service]
Restart=always
User=prometheus
ExecStart=/usr/local/bin/pve_exporter /etc/prometheus/pve.yml
[Install]
WantedBy=multi-user.target
EOF
reload systemd
systemctl daemon-reload
systemctl start prometheus-pve-exporter
systemctl enable prometheus-pve-exporter
将 proxmox-pve-exporter 添加到 Prometheus
vi /etc/prometheus/prometheus.yml
增加配置:
- job_name: 'proxmox'
metrics_path: /pve
static_configs:
- targets: ['localhost:9221']
重启服务:
systemctl restart prometheus
重启后可以到 Prometheus 界面 Target 查看节点是否 Up.
我在配置的过程中遇到一个问题,就是配置授权的时候用户名写的不对,报错
returned HTTP status 500 INTERNAL SERVER ERROR
使用 journalctl -xe
可以看到错误日志:
May 03 12:32:25 pve pvedaemon[291021]: authentication failure; rhost=127.0.0.1 user=xx@pve msg=no such user ('xx@pve')
这个时候一定要检查用户名。另外也不建议直接使用 root
用户, 可以使用 useradd monitor
新建一个用户来进行管理。
useradd monitor
添加 1monitor@pam
设置密码Monitoring
的角色,赋予 Datastore.Audit
, Sys.Audit
, Sys.Modify
, VM.Audit
, VM.Monitor
的权限/
, monitor@pam
, Monitoring
然后新建。此时配置授权的时候就可以使用 monitor@pam
加上设定的密码了。再检查一下 journalctl -xe
日志,/pve
接口返回 200 ,正常了。
pve_exporter
所能提供的打点 Metrics 大致有如下一些:
pve_cluster_info
pve_cpu_usage_limit
pve_cpu_usage_ratio
pve_disk_read_bytes
pve_disk_size_bytes
pve_disk_usage_bytes
pve_disk_write_bytes
pve_guest_info
pve_memory_size_bytes
pve_memory_usage_bytes
pve_network_receive_bytes
pve_network_transmit_bytes
pve_node_info
pve_storage_info
pve_up
pve_uptime_seconds
pve_version_info
安装 node_exporter 的方法之前的文章中已经有写。安装后配置 Prometheus target
- job_name: 'node_exporter'
scrape_interval: 10s
scrape_timeout: 10s
static_configs:
- targets: ['10.0.0.5:9100']
直接参考 官网
添加 source
sudo tee /etc/apt/sources.list.d/grafana.list<<EOF
deb https://packages.grafana.com/oss/deb stable main
EOF
获取 key
curl https://packages.grafana.com/gpg.key | sudo apt-key add -
安装:
sudo apt update && sudo apt install -y apt-transport-https grafana
sudo systemctl enable --now grafana-server
systemctl status grafana-server.service
访问 localhost:3000 ,用户名密码都是 admin
。
进入 Grafana 后,需要添加数据源,然后添加自己的 Dashboard 和 Panel,如果不熟悉操作过程,可以先导入别人的模板,学习一下别人是怎么做的。
侧边栏,Create -> Import,输入神秘代码:1860,可以导入 别人创建好的模板。
在熟悉了整个工作流程之后,就可以自己依照自己的需求来修改自己的 Dashboard.
几个比较不错的 Dashboard 模板
至此所有的配置过程就全部结束了,但这可能只是我学习 Prometheus 以及 Proxmox 的第一步,因为目前这台服务器上的服务并不多,虚拟机也只是因为学着玩而安装的 OpenMediaVault 以及 Ubuntu Server,而 Prometheus 中数据库的 Exporter,Nginx 的 Exporter,可以使用类似的方式慢慢加入进来,这个时候参考官网以及相关的 GitHub 也就能实现了。
在这个过程中也慢慢的体会到 less is more 的哲学,很早就听说过这句话,但尤其在这里更加深刻的理解了,这篇文章中提到的每一个服务都专注于自己的最核心的功能,Prometheus Server 专注于采集数据,Exporter 负责暴露数据,而 Grafana 则负责可视化,tsdb 负责记录数据,Proxmox 则是站在巨人的肩膀上专注于虚拟化。这些服务各自都在自己的领域做到极致,那么最好只需要完整的把他们组合到一起,就可以实现 1+1>2 的效果。
今天想把 Proxmox 的静态地址改一下的,但是重启后发现 Web UI 竟然不工作了。SSH 登录后台 netstat -tupln
看 8006 端口也没有起来。这一下子突然不知所措,只能一点点 Google,不过幸好问题不算太大。
在 官方论坛 里翻到一个帖子,照着他的方法:
service pve-cluster restart
运行命令后服务报错了,这就比较好办了,有报错总比抓瞎好,查看服务日志:
journalctl -xe
然后明显的看到红字:
May 01 20:01:22 pve systemd[1]: pve-cluster.service: Start request repeated too quickly.
May 01 20:01:22 pve systemd[1]: pve-cluster.service: Failed with result 'exit-code'.
-- Subject: Unit failed
-- Defined-By: systemd
-- Support: https://www.debian.org/support
--
-- The unit pve-cluster.service has entered the 'failed' state with result 'exit-code'.
May 01 20:01:22 pve systemd[1]: Failed to start The Proxmox VE cluster filesystem.
-- Subject: A start job for unit pve-cluster.service has failed
-- Defined-By: systemd
-- Support: https://www.debian.org/support
继续看帖子,发现是不是有可能是因为 hostname 没有找到对应的 ip。毕竟在我时隔一个月重启后我发现 hostname 被修改成了 ubuntu2,而我之前明明是 pve 来着。于是顺藤摸瓜看看
vi /etc/hostnames
vi /etc/hosts
果然发现 hostnames 被改了,于是改回 pve
,然后查看 /etc/hosts
里面的内容的时候,顶部几个注释引起了我的关注
# Your system has configured 'manage_etc_hosts' as True.
# As a result, if you wish for changes to this file to persist
# then you will need to either
# a.) make changes to the master file in /etc/cloud/templates/hosts.debian.tmpl
# b.) change or remove the value of 'manage_etc_hosts' in
# /etc/cloud/cloud.cfg or cloud-config from user-data
我突然想起来之前安装过 cloud-init ,然后照着注释的内容在对应的文件里面把 hostname 和 IP 填了进去。然后重启问题就解决了。
最近因为《太阳的后裔》中姜暮烟的医生宣誓再一次把医生的誓言放到了观众面前,而之前在《浪漫医生金师傅》、《라이프》中都或多或少的提到医术的誓言。我脑海突然一下子浮现了韩剧中各色职业的誓言,编剧都把他们融合到了台词,或者主人公的性格中。《Live》中的警察誓言,《秘密森林》、《检察官内传》中检察官的誓言,到《辅佐官》中议员的誓言。韩剧用一种理想化的方式来表现这些公职角色的形象,而最近《You quiz on the block》中的法医,警察,犯罪心理侧写师则是实实在在的于现实中履行自己的义务。
如果这些公职人员都能做到自己力所能及之事,而普通人始终恪守自己的底线,这个社会又会变得怎么样?
我们来看看真实的希波克拉底誓言,韩国医学院毕业生宣誓的内容。有些时候看着这些无私的誓言会觉得这个世界还是存在一些光明的,尤其是在如今肺炎蔓延到全球的时候。
이제 의업에 종사할 허락을 받음에, 나의 생애를 인류봉사에 바칠 것을 엄숙히 서약하노라. 나의 은사에 대하여 존경과 감사를 드리겠노라. 나의 양심과 위엄으로서 의술을 베풀겠노라. 나의 환자가 알려준 모든 비밀을 엄중히 지키겠노라. 나는 의업의 고귀한 전통과 명예를 유지하겠노라 나는 동업자를 형제처럼 여기겠노라. 나는 인류, 종교, 국적, 정당, 정파, 또는 사회적 지위 여하를 초월하여 오직 환자에 대한 나의 의무를 지키겠노라. 나는 인간의 생명을 그 수태된 때로부터 지상의 것으로 존중히 여기겠노라. 비록 위협을 당할지라도 나의 지식을 인도에 어긋나게 쓰지 않겠노라
这里是比较简陋的翻译(非逐字翻译):
现在我已获准从事医疗事业,我庄严宣誓,将我的毕生献给全人类,把病人的健康和生命放在首位,无论种族,宗教,国籍,政党派别和社会地位,只对病人履行我的义务。即使受到威胁,也绝不利用我的知识做违背人道的事情。
Hippocrates:The Oath of Medicine
You do solemnly swear, each by whatever he or she holds most sacred
That you will be loyal to the Profession of Medicine and just and generous to its members
That you will lead your lives and practice your art in uprightness and honor
That into whatsoever house you shall enter, it shall be for the good of the sick to the utmost of your power, your holding yourselves far aloof from wrong, from corruption, from the tempting of others to vice
That you will exercise your art solely for the cure of your patients, and will give no drug, perform no operation, for a criminal purpose, even if solicited, far less suggest it
That whatsoever you shall see or hear of the lives of men or women which is not fitting to be spoken, you will keep inviolably secret
These things do you swear. Let each bow the head in sign of acquiescence
And now, if you will be true to this, your oath, may prosperity and good repute be ever yours; the opposite, if you shall prove yourselves forsworn.
At the time of being admitted as a member of the medical profession
I solemnly pledge myself to consecrate my life to the service of humanity:
I will give to my teachers the respect and gratitude which is their due;
I will practice my profession with conscience and dignity;
The health and life of my patient will be my first consideration;
I will respect the secrets which are confided in me;
I will maintain by all means in my power, the honor and the noble traditions of the medical profession;
My colleagues will be my brothers:
I will not permit considerations of religion, nationality, race, party politics or social standing to intervene between my duty and my patient;
I will maintain the utmost respect for human life, from the time of its conception,
even under threat, I will not use my medical knowledge contrary to the laws of humanity;
I make these promises solemnly, freely and upon my honor…
值此进入医生职业之际,我庄严宣誓为服务于人类而献身。
我对施我以教的师友衷心感佩。
我在行医中一定要保持端庄和良心。
我一定把病人的健康和生命放在一切的首位,病人吐露的一切秘密,我一定严加信守,决不泄露。
我一定要保持医生职业的荣誉和高尚的传统。
我待同事亲如弟兄。
我决不让我对病人的义务受到种族、宗教、国籍、政党和政治或社会地位等方面的考虑的干扰。
对于人的生命,自其孕育之始,就保持最高度的尊重。
即使在威胁之下,我也决不用我的知识作逆于人道法规的事情。
我出自内心以荣誉保证履行以上诺言。
I solemnly pledge to concentrate my life to the service of humanity. I will give my teachers the respect and gratitude that is their due. I will practice my profession with conscience and dignity. The health of my patients will be my number one consideration. I will respect the secrets that are confided in me, even after my patient has died. I will maintain by all the means in my power, the honor and the noble traditions of the medical profession. My colleagues will be my sisters and brothers. I will not permit considerations of age, disease or disability, creed, ethnic origin, gender, race, political affiliation, nationality, sexual orientation, social standing, or any other factor to intervene between my duty and my patient. I will maintain the utmost respect for human life. I will not use my medical knowledge to violate human rights and civil liberties, even under threat. I make these promises solemnly, freely, and upon my honor.
健康所系,性命相托。 当我步入神圣医学学府的时刻,谨庄严宣誓: 我志愿献身医学,热爱祖国,忠于人民,恪守医德,尊师守纪,刻苦钻研,孜孜不倦,精益求精,全面发展。 我决心竭尽全力除人类之病痛,助健康之完美,维护医术的圣洁和荣誉,救死扶伤,不辞艰辛,执着追求,为祖国医药卫生事业的发展和人类身心健康奋斗终生。
본인은 공직자로서 긍지와 보람을 가지고 국가와 국민을 위하여 신명을 바칠 것을 다짐하면서 다음과 같이 선서합니다. 1.본인은 법령을 준수하고 상사의 직무상의 명령에 복종한다. 1.본인은 국민의 편에 서서 정직과 성실로 직무에 전념한다. 1.본인은 창의적인 노력과 능동적인 자세로 소임을 완수한다. 1.본인은 재직 중은 물론 퇴직 후에라도 직무상 알게 된 기밀을 절대로 누설하지 아니한다. 1.본인은정의의 실천자로서 부정의 발본에 앞장선다.
I will well and faithfully serve Her Majesty and Her Heirs and Successors according to law as a police officer, I will obey, uphold and maintain the laws of the Colony of Hong Kong, I will execute the powers and duties of my office honestly, faithfully and diligently without fear of or favour to any person and with malice or ill will towards none, and I will obey without question all lawful orders of those set in authority over me.
余兹身为警员,愿竭忠诚,依法效力英女皇及其皇储与继统人。余愿遵守,维护,并维持香港之法律。余复愿以不屈不挠,毋枉毋徇之精神,一秉至公,励行本人之职,并愿绝对服从本人上级长官之一切合法命令,此誓。
余誓以至誠,恪遵國家法令,盡忠職 守,報效國家;依法執行任務,行使職權;勤謹謙和,為民服務。如違誓 言,願受最嚴厲之處罰,謹誓。
나는 이 순간 국가와 국민의 부름을 받고 영광스러운 대한민국 검사의 직에 나섭니다. 공익의 대표자로서 정의와 인권을 바로 세우고 범죄로부터 내 이웃과 공동체를 지키라는 막중한 사명을 부여받은 것입니다. 나는 불의의 어둠을 걷어내는 용기 있는 검사, 힘없고 소외된 사람들을 돌보는 따뜻한 검사, 오로지 진실만을 따라가는 공평한 검사, 스스로에게 더 엄격한 바른 검사로서, 처음부터 끝까지 혼신의 힘을 다해 국민을 섬기고 국가에 봉사할 것을 나의 명예를 걸고 굳게 다짐합니다.
Ansible 是使用 Python 开发的自动化运维工具,如果这么说比较抽象的话,那么可以说 Ansible 可以让服务器管理人员使用文本来管理服务器,编写一段配置文件,在不同的机器上执行。
Ansible 使用 YAML 作为配置文件,YAML 是一个非常节省空间,并且没有丧失可读性的文件格式,其设计参考了很多语言和文件格式,包括 XML,JSON,C 语言,Python,Perl 以及电子邮件格式 RFC2822 等等。
Ansible 解决的问题正是在运维过程中多机器管理的问题。当有一台机器时运维比较简单,当如果要去管理 100 台机器,复杂度就上升了。使用 Ansible 可以让运维人员通过简单直观的文本配置来对所有纳入管理的机器统一进行管理。如果再用简单的话来概述 Ansible 的话,就是定义一次,无数次执行。
Ansible 是如何做到这件事情的呢?主要是划分了下面几个部分,具体的内容后文详解:
Ansible 中的一些概念。
/usr/bin/ansible
和 /usr/bin/ansible-playbook
hostfile
文件 1工作流程:
Ansible 的安装方法非常多,PPA,源码安装都可以。2
Ubuntu 下安装:
sudo apt update
sudo apt install software-properties-common
sudo apt-add-repository --yes --update ppa:ansible/ansible
sudo apt install ansible
如果不想 PPA,也可以直接安装:
sudo apt-get install -y ansible
# or
sudo pip install ansible
在 macOS 上:
brew install ansible
从源码安装:
sudo apt-get install -y libffi-dev libssl-dev python-dev
sudo pip install paramiko PyYAML Jinja2 httplib2 six pycrypto
git clone https://github.com/ansible/ansible.git --recursive
cd ansible
git pull --rebase
git submodule update --init --recursive
配置 Bash:
source ./hacking/env-setup
sudo pip install --trusted-host mirrors.aliyun.com
--index-url=http://mirrors.aliyun.com/pypi/simple/ ansible==2.7.1
ansible.cfg
文件是 Ansible 中最主要的配置文件,ansible 寻找配置文件按照如下的优先级进行:
ANSIBLE_CONFIG
指定的文件./ansible.cfg
(ansible.cfg
in the current directory)~/.ansible.cfg
(.ansible.cfg
in your home directory)/etc/ansible/ansible.cfg
最简单的 ansible.cfg
配置示例:
[defaults]
hostfile = hosts
remote_user = root
remote_port = 22
host_key_checking = False
说明:
hostfile
文件指定了当前文件夹下的 hosts 文件。hosts 文件中会配置需要管理的机器 host
remote_user
配置默认操作的用户,如果没有配置,默认会使用当前用户host_key_checking
: 禁用 SSH key host checking在上面的配置中可以看到 inventory
指定了一个 hosts 文件,这个文件用来对远程服务器 Hosts 进行管理。
默认的文件路径在 /etc/ansible/hosts
。
这里的 inventory 可以看成需要管理的节点的配置,可以直接配置到全局,然后使用 all
来引用,也可以用分组的形式来引用。
比如,未分组形式定义:
xxx.einverne.info
einverne.info
12.12.12.12
192.168.2.1
192.168.2.200
10.0.0.1
或者采用分组形式,用方括号表示下面的 HOST 都属于 webserver 这个组:
[webserver]
127.0.0.1
foo.example.com
如果有多个 HOST 可以用如下语法添加多个:
[webservers]
www[001:006].example.com
[dbservers]
db-[99:101]-node.example.com
或者配置别名:
dbserver1 ansible_ssh_host=127.0.0.1 ansible_ssh_port=22 color=red
dbserver2 ansible_ssh_host=127.0.0.2 ansible_ssh_port=220
[dbserver] #group
dbserver1
dbserver2
[forum:children] #groups of groups
webserver
dbserver
inventory 中可以配置使用别名,但是推荐在 ssh config
中进行配置管理,编辑 vi ~/.ssh/config
:
Host ds
HostName einverne.info
Port 22
User username
Host aws1
HostName aws.einverne.info
Post 22
User demo-username
Host oracle1
HostName 140.1.1.1
Port 22
User some-username
我个人使用 assh 来对 SSH config 进行管理。
然后就可以在 Ansible 的 inventory 中配置使用 ds
, aws1
或者 oracle1
来指定 host。
更多 inventory 的配置可以参考官方文档
inventory 同样配置用来管理 AWS EC2,或者 OpenStack。3
ansible 命令的基本使用方法:
ansible <pattern> -m <module_name> -a <module_arguments>
说明:这一行命令会定义并在一系列 host 上执行一个 playbook
任务。
ad-hoc 命令可以执行单一的任务,ad-hoc 命令很简单,但不能复用,在了解 playbook 之前可以先体验一下 ad-hoc,感受一下 Ansible 的强大。
简单示例:
# 在指定的 host1 节点上执行
ansible host1 -a "/bin/echo hello"
# 在多个节点执行
ansible host1,host2 -a "/bin/echo hello"
ansible host1:host2 -a "/bin/echo hello"
# ping 全部节点
ansible all -m ping
# 一组节点
ansible webservers -m service -a "name=httpd state=restarted"
# 多组节点
ansible webservers:dbservers -m ping
# 排除节点,在 groupA 但不在 groupB
ansible groupA:!groupB -m ping
# 多组节点的交集,既在 groupA 也在 groupB 中的节点
ansible groupA:&groupB -m ping
这里选择节点的方式可以有很多种,甚至可以选择组节点中的第几个,或者用正则匹配一些等等。4
-m
选项后面的就是 Ansible 的 module,常见的 module,比如上面例子中的 ping,就是用来检测连通性的。
下面介绍一下常用的 module 方便快速进入 Ansible 的世界,理解了下面这些 module 也比较方便之后学习更加复杂的模块。
setup module 用来查看远程主机信息:
ansible all -m setup
每个被管理的节点在接受并运行管理命令之前都会将自己的信息报告给 Ansible 主机。
command 命令模块用于在远程主机执行命令,但是不能使用变量,管道等。
执行命令:
ansible all -m command -a "ls -al ."
ansible all -m command -a "date"
# 切换到 sub-dir 目录,创建文件
ansible all -m command -a "chdir=sub-dir creates=test.file ls"
# 删除文件
ansible all -m command -a "chdir=sub-dir removes=test.file ls"
cron 模块用于配置 crontab 定时任务:
ansible host -m cron -a 'minute="*/10" job="/bin/echo hello" name="test cron job"'
执行这以命令之后就会给 host 主机的 crontab 中写入
#Ansible: test cron job
*/10 * * * * /bin/echo hello
可以通过如下命令验证:
ansible host -a 'crontab -l'
如果要移除 cron 可以:
ansible host -m cron -a 'minute="*/10" job="/bin/echo hello" name="test cron job" state=absent'
user 模块用来管理用户账户。
# 新增用户
ansible all -m user -a 'name="einverne"'
# 删除用户
ansible all -m user -a 'name="einverne" state=absent'
和用户相关的字段:
name 用户名
uid uid
state 状态
group 属于哪个组
groups 附加组
home 家目录
createhome 是否创建家目录
comment 注释信息
system 是否是系统用户
user module 更多的说明可以参考官网
组管理同样拥有这些配置:
gid gid
name 组名
state 状态
system 是否是系统组
file module 可以用来设置文件属性。
# 创建 soft link
ansible all -m file -a "src=/etc/resolv.conf dest=/tmp/resolv.conf state=link"
# 删除 soft link
ansible all -m file -a "path=/tmp/resolv.conf state=absent"
复制本地文件到远程主机指定位置
# 复制本地文件到远程主机,并授予权限
ansible host -m copy -a "src=/etc/ansible/ansible.cfg dest=/tmp/ansible.cfg owner=root group=root mode=644"
# 直接使用 content
ansible host -m copy -a 'content="test content" dest=/tmp/test'
在远程执行 shell 脚本,可以使用管道等
ansible all -m shell -a "~/setup.sh"
ansible all -m shell -a 'echo demo > /tmp/demo'
更多 module 可以使用 ansible-doc -l
查看。
看到这里的话,相信对 ansible 的 module 已经有了一个大致的了解,Ansible 官网提供了非常多的 module 使用说明。
但是你会发现一个问题,所有的这些命令都是一次性使用的,而无法做到复用,除非你拷贝这一行命令执行多次。所以 Ansible 也可以通过配置文件的方式,将这些操作记录下来,以文本的方式进行管理,这就是下面要说到的 Ansible playbook。
上面提到 ad-hoc
可以执行一次性的命令,但如果要把多个 task 组织起来,那就不得不提到 playbook, playbook 可以编排有序的任务,可以在多组主机间,有序执行任务,可以选择同步或者异步发起任务。
下面以一个安装 Docker 的例子做演示:
定义了变量的文件 var/default.yml
:
---
create_containers: 4
default_container_name: docker
default_container_image: ubuntu
default_container_command: sleep 1d
playbook.yml
文件:
---
- hosts: all
become: true
vars_files:
- vars/default.yml
tasks:
- name: Install aptitude using apt
apt: name=aptitude state=latest update_cache=yes force_apt_get=yes
- name: Install required system packages
apt: name= state=latest update_cache=yes
loop: [ 'apt-transport-https', 'ca-certificates', 'curl', 'software-properties-common', 'python3-pip', 'virtualenv', 'python3-setuptools']
- name: Add Docker GPG apt Key
apt_key:
url: https://download.docker.com/linux/ubuntu/gpg
state: present
- name: Add Docker Repository
apt_repository:
repo: deb https://download.docker.com/linux/ubuntu bionic stable
state: present
- name: Update apt and install docker-ce
apt: update_cache=yes name=docker-ce state=latest
- name: Install Docker Module for Python
pip:
name: docker
- name: Pull default Docker image
docker_image:
name: ""
source: pull
# Creates the number of containers defined by the variable create_containers, using values from vars file
- name: Create default containers
docker_container:
name: ""
image: ""
command: ""
state: present
with_sequence: count=
说明:
使用如下命令执行 playbook
ansible-playbook playbook.yml -f 10
-f
表示的是指定并发进程来执行任务。
在 task 后面可以增加 when 用于条件,比如只有系统是 Debian
才执行命令:
tasks:
- name 'test when'
command: /bin/echo hello
when: ansible_os_family == 'Debian'
如果需要重复执行一个任务,可以使用循环,将需要循环的内容定义为 item,然后通过 with_items
语句指定列表,比如新建两个用户:
- name: add user
user: name= state=present
with_items:
- user1
- user2
上面语句的功能等同于下面的语句:
- name: add user testuser1
user: name=user1 state=present
- name: add user testuser2
user: name=user2 state=present
如果还要定义 group,可以使用 key-value 键值对:
- name: add multiple item
user: name= state=present groups=
with_items:
- { name: 'user1', groups: 'g1'}
- { name: 'user2', groups: 'root'}
再来看一个例子:
- hosts:webservers
roles:
- tmux
这里 role 定义了 tmux(tmux 编译安装),则表示用 tmux 执行了一系列的命令。role 由其他一些组件组成:
roles/
tmux/
tasks/
handlers/
files/
templates/
vars/
defaults/
meta/
在 tasks 目录下新建 mail.yml
:
- name: install tmux package
package:
name:
- libevent
- ncurses
- tmux
state: latest
如果想了解更多拆分 playbook 的方法,可以到官网查看更多 include, role 相关的内容。
当使用 check mode 运行 ansible-playbook 时,Ansible 不会在远程服务器上执行任何命令。
ansible-playbook foo.yml --check
ansible-galaxy
命令和 Ansible 命令绑定到了一起,可以通过 ansible-galaxy
来初始化 role.
ansible-galaxy init pyenv
得到:
➜ tree pyenv
pyenv
├── defaults
│ └── main.yml
├── files
├── handlers
│ └── main.yml
├── meta
│ └── main.yml
├── README.md
├── tasks
│ └── main.yml
├── templates
├── tests
│ ├── inventory
│ └── test.yml
└── vars
└── main.yml
在使用时,每一个目录都需要包含一个 mail.yml
文件:
tasks
: 包含 role 需要执行的任务清单handlers
: 包含 handlers, 可能被 role 用到defaults
: 默认变量,Using Variablesvars
: 其他被 role 用到的变量 Variablefiles
: 包含可能被 role 用到的文件templates
: 包含可能被 role 用到的 templatesmeta
: 定义 role 的 meta dataYAML 文件可以被引入,比如不同的系统版本:
# roles/example/tasks/main.yml
- name: added in 2.4, previously you used 'include'
import_tasks: redhat.yml
when: ansible_facts['os_family']|lower == 'redhat'
- import_tasks: debian.yml
when: ansible_facts['os_family']|lower == 'debian'
# roles/example/tasks/redhat.yml
- yum:
name: "httpd"
state: present
# roles/example/tasks/debian.yml
- apt:
name: "apache2"
state: present
其他的运维管理工具 puppet、cfengine、chef、func、fabric.
Redhat 给 Ansible 做了一套 GUI,叫做 Ansible Tower,感兴趣可以了解一下。
https://docs.ansible.com/ansible/latest/user_guide/intro_inventory.html#intro-inventory ↩
https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html ↩
https://docs.ansible.com/ansible/latest/user_guide/intro_dynamic_inventory.html ↩
https://docs.ansible.com/ansible/latest/user_guide/intro_patterns.html ↩
Business Process Model and Notation (BPMN) is a graphical representation for specifying business processes in a business process model.
可供选择的方案,如下。
jBPM 是一个用 Java 写的开源工作流引擎,可以用来执行 BPMN 2.0 定义的工作流。
EasyBPMN Toolbox is a powerful Java library for BPMN 2.0. It can parse and manipulate BPMN 2.0 files easily by providing a Java model for any BPMN 2.0 compliant business process.
EMF 是 Eclipse 下面的一个建模工具,我没有具体细看,但 BPMN 2.0 的规范定义,本质上 bpmn 文件就是一个 xml 格式的文件,如果知道 bpmn 文件的定义规范,使用任意的 xml 解析工具都可以解析出想要的内容。
BPMN2 model is based on EMF model (org.eclipse.bpmn2 project, model folder, BPMN20.ecore file). You can use EMF Java Api to create, read or modify BPMN2 models.
BPMN 2.0 规范
在搜罗了一圈之后在 jBPM 项目中发现了下面的代码。XmlProcessDumper
类实现了 bpmn 文件的解析。
InputStream inputBpmn = getClass().getResourceAsStream("/BPMN2-BrokenStructureRef.bpmn2");
XmlProcessDumper dumper = XmlProcessDumperFactory.getXmlProcessDumperFactoryService().newXmlProcessDumper();
Assert.assertNotNull(dumper);
String processXml = new String(IoUtils.readBytesFromInputStream(inputBpmn), "UTF-8");
Assert.assertNotNull(processXml);
org.kie.api.definition.process.Process proc = dumper.readProcess(processXml);
Assert.assertNotNull(proc);
Assert.assertEquals("BrokenStructureRef", proc.getId());
昨天在和朋友讨论两个项目双向同步的问题,比如,两个从同一分支拉出来的两个独立项目各自发展,但又要求定时双向同步的时候,虽然提出了用 remote 可以临时解决一下。不过后来又和朋友讨论起 git subtree
来,在此之前,我如果有需要在项目内部依赖外部独立的项目时,我一般都使用 git submodule
来解决。不过昨天搜索了一下之后发现 git subtree
似乎更加强大,并且已经成为替代 git submodule
的事实方案。所以这里来学习一下。
在使用 git subtree
之前如果你没有用过 git submodule
,这里先进行一些说明。对于 git submodule
而言,在本地的代码库中可能存在多个 git
代码仓库,而 git subtree
就只有一个代码库。
这两者都可以将另外一个项目作为主项目的一个子目录而作为依赖添加进来,但是实现的方式大有区别。
.gitmodule
的文件来记录父项目添加的子 module,而使用 subtree 则会将子项目完整的克隆到父项目的一个文件夹中。git submodule update --recursive --remote
;而使用 subtree 则直接 pull 即可git subtree
可以让一个 repository 嵌入到另一个项目的子目录中。
.gitmodule
这样的文件,git subtree 的使用对于项目中其他成员可以透明常用的 git subtree
命令:
git subtree add --prefix=<prefix> <commit>
git subtree add --prefix=<prefix> <repository> <ref>
git subtree pull --prefix=<prefix> <repository> <ref>
git subtree push --prefix=<prefix> <repository> <ref>
git subtree merge --prefix=<prefix> <commit>
git subtree split --prefix=<prefix> [OPTIONS] [<commit>]
和 git submodule 一样,在使用 subtree 的时候也需要显式的指定需要添加的子项目
git subtree add --prefix=foo https://github.com/einverne/foo.git master --squash
解释:
--squash
是将 subtree 的改动合并到一个 commit,不用拉取子项目完整的历史纪录--prefix
后面的 =
也可以使用空格,注意这里的 foo
就是项目克隆后在本地的目录名master
指的是 subtree 项目的分支名git status
和 git log
查看提交使用 git subtree
添加项目后,subtree 就将原来的项目作为这个主项目的一个普通文件夹来看待了,对于父级项目而言完全无缝透明。上面的命令就是将 foo 这个项目添加到主项目中 foo 文件夹下。
日常更新的时候,正常的提交代码,如果更改了 foo 目录中的内容也正常的提交即可。
如果依赖的子项目更新了,可以通过如下命令更新:
git subtree pull --prefix=foo https://github.com/einverne/foo.git master --squash
上面命令执行后,就可以将 foo 仓库中 master 上的更新更新到本地,--squash
表示只会在父项目生成一个 commit 提交。
假如在修改代码时修改了依赖的 foo 中的代码,那么可以将这部分代码推送到远端仓库
git subtree push --prefix=foo https://github.com/einverne/foo.git master
看到这里可能发现,每一次操作 subtree 添加的项目时都需要敲一大段 URL 地址,这里可以使用 remote 来简化命令:
git remote add -f foo https://github.com/einverne/foo.git
然后
git subtree add --prefix=foo foo master --squash
git subtree pull --prefix=foo foo master --squash
git subtree push --prefix=foo foo master
到这里其实我们就可以发现,利用 subtree 或者 submodule 也好,都可以很方便的同步子项目,尤其是很多项目都依赖的那个项目。比如 A,B 都依赖与 Z,项目,那么使用这种方式可以很方便的在不同 A,B 项目间共享对 Z 的修改。比如 A,B 都依赖与 Z,项目,那么使用这种方式可以很方便的在不同 A,B 项目间共享对 Z 的修改。
git subtree push
会将父项目中的提交每一次都进行提交,这会导致对于子项目来说无意义的提交信息,但是 git subtree
并没有提供类似 squash 的方式可以将多次提交合并成一次提交,但是 git subtree
提供了分支的特性,可以在父项目中将修改推送到某一个分支,然后在子项目中使用 squash merge 将修改合并到主干分支。
git subtree push --prefix=foo foo feature
这会在 foo 的仓库中创建一个叫做 feature 的分支。然后可以从 feature 分支合并回 master 分支。
一旦最新的提交都合并到 master 分支,可以通过 pull
来更新
git subtree pull --prefix=foo foo master --squash
这会在主项目中创建另外一个提交,包括了子项目中所有的修改。
这样的方式唯一的缺点就是会在父项目中产生一些多余的提交信息。
典型的使用场景就是当 A,B 项目同时依赖 Z 项目,需要对 Z 项目进行管理的时候。
虽然可以用 subtree 来管理 vim plugins,但我个人跟倾向于使用 vim-plug 来管理 Vim 的插件。
这里不过是拿 vim plugin 管理来作为一个例子。1
# add
git subtree add --prefix .vim/bundle/vim-fugitive https://github.com/tpope/vim-fugitive.git master --squash
# update
git subtree pull --prefix .vim/bundle/vim-fugitive https://github.com/tpope/vim-fugitive.git master --squash
无论是 WordPress 还是静态内容生成器 Hexo,Hugo 之类,他们的主题都是主项目下的一个文件夹,如果主题自身是一个仓库的话,那么可以直接使用 subtree 将主题放到主项目中来管理,修改也可以同步提交到主项目中,如果想要提交回主题本身也可以很方便的实现。
git subtree push --prefix=foo foo master --squash
push
的时候添加 --squash
会将多个提交合并成一个。
固然 subtree 有很多的优点,解决了 submodule 存在的一些问题,但是 subtree 也有其自己的问题。
在 submodule 时,子项目是一个单独的项目,和所有的 git 管理的项目一样,可以在子项目中提交,提交再 push
到 upstream ,并且每一个 submodule 都有自己完整的提交历史。
而在 subtree
中因为对子项目的所有修改已经和主项目混合到了一起,所以需要单独对子项目提交,命令:
git subtree push --prefix=foo foo master
在 subtree 执行 push 命令时,git subtree 会为子项目生成新的提交,这个时候就会有一个问题。假如主项目提交过多,那么在 push 到子项目时会花费大量的时间来重新计算子项目的提交。并且因为每次 push 都是重新计算的,所以本地仓库和远端仓库的提交总是不一样的,这会导致 git 无法解决可能的冲突。
基于这些原因,git subtree 提供了 split
命令。
Extract a new, synthetic project history from the history of the subtree. The new history includes only the commits (including merges) that affected , and each of those commits now has the contents of at the root of the project instead of in a subdirectory. Thus, the newly created history is suitable for export as a separate git repository. After splitting successfully, a single commit id is printed to stdout. This corresponds to the HEAD of the newly created tree, which you can manipulate however you want. Repeated splits of exactly the same history are guaranteed to be identical (ie. to produce the same commit ids). Because of this, if you add new commits and then re-split, the new commits will be attached as commits on top of the history you generated last time, so ‘git merge’ and friends will work as expected. Note that if you use ‘-squash’ when you merge, you should usually not just ‘-rejoin’ when you split.
当使用 split
命令后,使用 git subtree push
,git 只会计算 split 后的新提交。23
这里需要注意:如果 push 使用了 --squash
合并提交,那么 split
时不能使用 --rejoin
参数。运行上面的命令后主项目中就多了一个 foo-branch 的分支,这个分支作为子项目的起点,下次 push 时就只会从这个点开始重新计算。
使用 subtree 的时候,如果能够 pull
下来当然 OK,但是一旦出现 Conflict,过程就比较复杂了,甚至需要专门的 git subtree merge strategy 来解决。
假设把 Bproject 作为子项目
# -f flag tell git to immediately fetch the commits after adding
git remote add -f Bproject /path/to/B
准备 merge:
git merge -s ours --no-commit --allow-unrelated-histories Bproject/master
这告诉 git 准备一次 merge commit,使用 ours
merge strategy 来告诉 git 我们需要 merge 的结果是 HEAD,然后将 project b 添加到 repository:
git read-tree --prefix=dir-B/ -u Bproject/master
read-tree
命令读取 B 项目的 master-tree 到 index 中,然后将其保存到给定的目录中 dir-B/
,注意这里的目录结尾必须有 /
,-u
选项让 read-tree 同样更新 working 目录中的内容。
最后提交:
git commit -m "Merge Project B into dir-B"
以后的更新,只需要 pull 即可
git pull -s subtree Bproject master
上面的流程可以看到,虽然使用 subtree 将项目中包含另一个项目的细节对项目的成员隐藏了,但实际上在产生冲突时需要管理者特别注意。456
使用 git subtree
加入到父项目的仓库,如果要切换分支,可以直接将 subtree 删掉,然后新加入子项目的分支即可。
git rm <subtree>
git commit
git subtree add --prefix=<subtree> <repository_url> <subtree_branch>
如果有一天引入的 subtree 不再有用,那么整个 subtree 可以从项目中删除。
git rm -r subtree_folder
git commit -m "Remove subtree"
这种方式是删除整个 subtree,然后提交,但是问题在于 git log 中还会留有 subtree 的提交历史。
还可以使用 filter-branch 来重写提交历史:
git filter-branch -f --index-filter "git rm -r -f -q --cached --ignore-unmatch subtree_folder" --prune-empty HEAD
这种方式可以将 subtree 的提交历史也删除。
就和上文所说那样,因为对 subtree 目录的修改和主项目是混合在一起的。所以为了让 commit messages 清晰,可以对主项目和子项目的修改分开进行。当然如果不在意子项目的 commit messages,那么一起提交,然后在对 subtree push 的时候再统一对 commit message 进行修改也可以。
http://endot.org/2011/05/18/git-submodules-vs-subtrees-for-vim-plugins/ ↩
https://blog.walterlv.com/post/performance-of-git-subtree.html ↩
https://github.com/apenwarr/git-subtree/blob/master/git-subtree.txt
git subtree split [–rejoin] –prefix=foo –branch foo-branch ↩
https://mirrors.edge.kernel.org/pub/software/scm/git/docs/howto/using-merge-subtree.html ↩
https://help.github.com/en/github/using-git/about-git-subtree-merges ↩