Azure Cosmos DB 简单使用入门

Azure Cosmos DB 是 Azure 提供的一个分布式 NoSQL 数据库,Cosmos DB 提供一定的关系型数据库的能力,并且可以无缝地扩容。

Azure 提供了 30 天免费的试用时长,可以注册一个 Azure 帐号体验一下。该帐户中获得前 1000 RU/s 的免费吞吐量和 25 GB 的免费存储。

Cosmos DB 和其他非关系型数据库区别

Cosmos DB 是微软提供的多区域分布式的数据库,可以根据需要自动缩放吞吐量,自动扩容。

Cosmos DB 的优势

  • 多地区,可以在全球范围能有不错的响应时间,Cosmos DB 可以自行复制副本,用户可以与最近的副本进行交互
  • 高吞吐和弹性存储,透明水平分区和多主数据库复制,任何区域内,只需发出一次 API 调用,即可将每秒数千个请求弹性扩容到数百个请求,用户只需为实际使用的吞吐量和存储付费
  • 一致性,用户无需关心多个地区的数据一致性问题
  • 用户无需关心索引,数据库架构,Cosmos DB 会自动为所有数据构建索引
  • SLA 可用性保证,Azure 托管 Cosmos DB,用户不需要管理和操作复杂的多数据中心和部署软件的升级
  • 支持多种数据库模型
    • SQL
    • MongoDB
    • Cassandra

Azure Cosmos DB 账户结构

  • 一个数据库账户下面有零个或多个数据库
  • 一个数据库下面有零个或多个容器
  • 一个容器包括零个或多个项
    • 容器用于存储数据
    • 创建容器时需要提供分区键,文档中选择的要存储的属性,用于路由到要写入,更新或删除的分区
    • 容器会实现集合,表,图等

下面就重点对 Cosmos DB 中的每个概念介绍一下

Azure Cosmos DB databases

在 Azure Cosmos DB 中数据库类似于 namespace。数据库是一组 container。

Azure Cosmos DB containers

Azure Cosmos DB container (容器)就是数据真正存放的地方。和关系型数据库不同的是,当发生扩容的时候,不是扩展 VM 的存储空间, Cosmos DB 会横向扩展。数据会存储在一台或多肽服务器中,称为分区(partitions)。为了增加吞吐量或存储,需要添加更多的分区。这为容器提供了无限的吞吐量和存储。当创建容器的时候,需要添加分区键(partition key)。这个分区键需要从文档的属性中选择。该属性将用于将数据路由到要写入、更新或删除的分区。也可以被用于 WHERE 查询子句,以实现高效的数据检索。

Cosmos DB 中数据的基础存储机制称为物理分区,可以拥有高达 10000 RU/s 和多大 50 GB 的数据。Azure Cosmos DB 使用可存储多达 20 GB 数据的逻辑分区对此进行抽象。随着添加更多分区,逻辑分区允许服务为基础物理分区上的数据提供更大的弹性和更好的管理。 若要详细了解分区和分区键,请参阅将数据分区

创建容器的时候,会需要配置吞吐量:

  • 专用吞吐量:专门留给该容器使用
    • 有两种类型的专用吞吐量:标准和自动缩放
  • 共享吞吐量:
    • 在数据库级别指定,与数据库中最多 25 个容器共享。

容器与 Schema 无关,容器中的项可以具有任意 Schema 或不同的实体,只要它们共享相同的分区键。默认情况下,容器中的所有数据都会自动编制索引,无需显式索引。

容器中的数据必需具有每个逻辑分区键值唯一的 id

Azure Cosmos DB 容器的属性

Cosmos DB 容器具备一组系统定义的属性。这些属性值会根据不同的 API 附加在数据上,比如常见的:

  • _rid,容器唯一标识符
  • _etag,用于乐观并发控制的实体标记
  • _ts,容器上次更新的时间戳
  • _self,容器的可寻址 URI
  • id,用户配置,容器的名字
  • indexingPolicy,用户可配置,提供更改索引的功能
  • TimeToLive,用户配置,从容器自动删除
  • changeFeedPolicy,用户配置,用于读取对容器中的项所做的更改
  • uniqueKeyPolicy,用户配置,确保逻辑分区中一个或多个值的唯一性
  • AnalyticalTimeToLive,用户配置,在设置的时间段后从容器中自动删除功能

Azure Cosmos DB items

数据在 Cosmos DB 中表现为一个项,集合中的一个文件,表格的一行,或者图形中的一个节点或边缘。

item 的属性

  • _rid,项的唯一标识符
  • _etag,乐观并发控制的实体标记
  • _ts,上次更新的事件戳
  • _self,项的可寻址 URI
  • id,逻辑分区中用户定义的唯一名称

建模和分区

嵌入数据

在关系型数据库中,通常是将数据规范化,将数据规范化通常就是将数据拆分成不同的不同的实体。比如一个人的信息可以拆分成联系人,多条地址记录,多条联系人记录等等。通常可以通过类型来进一步区分,比如地址可以是家庭地址,或工作地址等。

规范数据的前提是为了避免冗余记录。而在 Cosmos DB 中可以使用 嵌入的方式,将此人的相关信息嵌入到一个文档中。

{
    "id": "1",
    "firstName": "Thomas",
    "lastName": "Andersen",
    "addresses": [
        {
            "line1": "100 Some Street",
            "line2": "Unit 1",
            "city": "Seattle",
            "state": "WA",
            "zip": 98012
        }
    ],
    "contactDetails": [
        {"email": "thomas@andersen.com"},
        {"phone": "+1 555 555-5555", "extension": 5555}
    ]
}

这样在创建或更新此人的信息时都是单个写入操作。

什么时候使用嵌入

通常是如下的场景:

  • 实体之间存在包含关系
  • 实体之间存在一对多关系
  • 嵌入的数据不经常更改
  • 嵌入 ude 数据在未绑定的情况下不会增长
  • 嵌入的数据会频繁地统一查询

什么时候不嵌入

如果文档中有一个无限制增长的数组,那么最好不要嵌入此数组。

可以考虑将数组拆分开,并在数组的元素中加入原始引用的 ID。

引用数据

通过在文档中包含 ID 的方式来完成引用。

比如下面的文档设计为一个人的股票持仓。

Person document:
{
    "id": "1",
    "firstName": "Thomas",
    "lastName": "Andersen",
    "holdings": [
        { "numberHeld":  100, "stockId": 1},
        { "numberHeld":  50, "stockId": 2}
    ]
}

Stock documents:
{
    "id": "1",
    "symbol": "zbzb",
    "open": 1,
    "high": 2,
    "low": 0.5,
    "vol": 11970000,
    "mkt-cap": 42000000,
    "pe": 5.89
},
{
    "id": "2",
    "symbol": "xcxc",
    "open": 89,
    "high": 93.24,
    "low": 88.87,
    "vol": 2970200,
    "mkt-cap": 1005000,
    "pe": 75.82
}

这种方法的缺点就是当要显示一个人的投资组合的时候,应用程序需要多次访问数据库来加载每个股票的信息。

什么时候使用引用

  • 表示一对多关系。
  • 表示多对多关系。
  • 相关数据频繁更改
  • 引用的数据可能没有限制

尽量避免使用可能较大的可变即集合。而通过将 ID 分散在单个文档中的方式来保存关系。

多对多关系

在关系型数据库中如果要处理多对多的关系,通常是引入一张关系表,保存 ID 对 ID 的关系。

在文档数据库中,可以通过分别在实体中冗余关系的方式来记录多对多的关系。

比如作者,作者图书关系,图书,三个实体之间的联系。一本书可能有多个作者,而一个作者也可能写很多本书。

这个时候就可以考虑在作者的文档中加入 books 数组,在图书的文档中加入 authors 数组来表示。这样就可以不用关系表来保存多对多的关系了。同时也可以减少应用程序需要访问服务器的次数。

创建样例数据

可以使用 New item 菜单添加新的 item

{
    "id": "1",
    "category": "personal",
    "name": "groceries",
    "description": "Pick up apples and strawberries.",
    "isComplete": false
}

其中的 id 为数据的唯一 ID。

Cosmos DB 常用查询语法

最基本的查询语法,比如查询全部文档

SELECT * FROM c

指定顺序

SELECT * FROM c ORDER BY c._ts desc

查询条件

TTL

TTL,生存时间,Cosmos DB 能够在一段时间之后自动将项从容器中删除。可以在容器级别设置 TTL,系统会基于 TTL 值自动删除过期的项,不需要客户端应用程序显式的发出删除请求。TTL 的最大值是 2147483647 秒,大约 24855 天或 68 年。

删除过期项是一个后台任务,使用剩余的请求单元,即用户请求没有使用的请求单元。TTL 过期后,如果容器出现请求过载的情况,并且没有足够的 RU 使用,也会延迟数据删除操作。但是任何查询都不会通过接口返回 TTL 过期后的数据。

  • 容器的生存时间 (DefaultTimeToLive
    • 缺失,项不回自动过期
    • 如果设置为 -1,默认情况下,项不回过期
    • 如果设置为非零数字,项将在上次修改后 n 秒后过期
  • 项的生存时间 (ttl
    • 仅当父容器的 DefaultTimeToLive 存在且不是设置为 null 时,此属性适用
    • 如果存在,将替代父容器的 DefaultTimeToLive

reference


2021-12-16 azure , cosmos-db , no-sql , microsoft , database

Obsidian 插件篇之 Dataview

在过去的一年里面,我基本上只用 Obsidian 来记录,并且我也将我过去几年的 Jekyll 的内容作为 submodule 引入到了 Obsidian Vault,所以现在这个仓库中有非常多的笔记。

前些天在 Twitter 上看到有人说 Obsidian 基于 Markdown 的管理方式没有 Roam Research,和 [[Logseq]] 那样基于块的灵活,并且无法动态展示内容,比如有人用 Notion 那种表格的形式来管理读书记录,观影记录,用 Obsidian 就没有太好的办法去做到,但其实只要使用 Dataview 这一个插件就可以实现。

在过去的使用过程中,我并没有太多的需求要去使用这一款插件,大部分的场景我都会直接使用搜索(Ctrl+Shift+F)关键字或标签来做到。但随着现在笔记越来越多,我产生了一个需求,比如我想要查看我笔记中打了某个标签的笔记列表,我想查看某个时间段中我记录的关于某个人物的笔记,又或者我会在笔记中将我看过的影视作品记录下来,我想查看过去我看过的某个导演的作品列表,这一些都可以通过 Dataview 结合 Obsidian 来达到。

这样的需求和我之前在了解 [[Zettelkasten 笔记法]] 中关于 [[202008261820-Zettel-笔记中间层]] 的概念不谋而合。当原子的笔记变得越来越多的时候,为了将这些原子的笔记集合到某一个主题下,通常我们会使用分类、标签等等方式来组织,那么使用一个笔记中间层来管理就变得顺理成章,而在过去我通常都是使用手工的方式来组织,比如我在管理读书笔记的时候,我会新建一个年度的笔记 [[Reading-2021]] ,然后在其中以季度为标题,然后记录每一本书。同样观影记录也类似。

而当我得知了 Dataview 插件之后,我发现这样的中间层可以通过组织数据而自动产生,只需要我在每一个笔记中加上 YAML Front matter,然后用适当的查询语句就可以做到。下面就直接来介绍一下这个插件。

基本概念

在 Obsidian 中可以用不同的方式对数据(笔记)进行标记,Dataview 会追踪所有 Markdown 文件中标记的数据。

Dataview 插件中几个重要的概念:

  • YAML Front Matter,Markdown 文件开头标记元数据部分
  • Inline Field, 行内标记字段
  • Implicit Field, 隐式字段

YAML Front Matter

Obsidian 的 metadata 使用 YAML front matter,一般写在文件的最上面,使用 key-value 结构,既对人友好,也对 Obsidian 可读。YAML 是 “Yet Another Markup Language” 的缩写。

Front matter 一般是纯文本文件从第一行开始的一块区域。这是 Markdown 文件通常用来添加 metadata 的方式。Jekyll, Hugo, Gatsby 等等静态网站生成器都使用这个方式。

这个区块就可以对这个 Markdown 文件添加额外的描述信息。

YAML 区块需要三条 --- 短横线标记开始和结束。并且这个区块需要在文件的最上面。

比如:

---
key: value
key2: value2
key3: [one, two, three]
key4:
- four
- five
- six
---

从 Obsidian 0.12.12 开始,有四个原生支持的标签:

  • tags
  • aliases
  • cssclass
  • publish

后两个我一般不怎么使用,所以我创建了一个模板,每一次创建新的笔记,都会自动包含上述两个标签:

---
category: 
aliases:
tags:
time:  
---

你当然还可以用 Templater 插件针对不同的笔记内容插入不同的 YAML 头,比如去记录看过的电影,我就会多加上 rating, comment 字段,简单的记录评分和短评。

Inline Fields

在页面中使用行内字段

除了上面显式地使用 YAML 来对文件进行标记,也还可以在内容中使用行内的语法对 markdown 文件进行标记,Dataview 支持 Key:: Value 这样的格式:

# Markdown Page

One Field:: Value
**Bold Field**::  Nice!

或者也可以写在一行内:

刚刚看完[[浪漫的体质 Melo 体质]],可以打 [rating:: 5] 分,这是一部让人看了非常[mood:: 轻松愉悦]的[category:: 喜剧片]

上面一个句子同样达到了给当前这个文档加上了如下的标签:

  • rating: 5
  • mood: 轻松愉悦
  • category: 喜剧片

在之后的检索中直接可以使用。

在 Task 中使用行内字段

同样在 Task 语法中也可以使用,通常在 Markdown 中我们会使用 -[ ] blah blah 来标记一个任务,比如:

- [ ] todo task [metadata key:: value]
- [x] finished task [completion:: 2021-12-30]

同样可以使用行内的标记。在 Dataview 官方的说明中我们也可以使用 emoji 来分别表示,到期(🗓️YYYY-MM-DD),完成(✅YYYY-MM-DD),创建(➕YYYY-MM-DD)的日期。如果不想使用 emoji 也可以直接用文字 [due:: ], [create:: ], [completion:: ]

行内字段的类型

所有 Dataview 中的字段都有类型,这决定了在渲染的时候的顺序。

如果没有匹配上任何类型,就是字符串:

FieldName:: This is a demo text.

数字类型:

FieldName:: 6
FieldName:: 2.4
FieldName:: -10

布尔值:true/false

FieldName:: true
FieldName:: false

日期:ISO8601 标准,YYYY-MM[-DDTHH:mm:ss.nnn+ZZ]

Example:: 2021-04-18
Example:: 2021-04-18T04:19:35.000
Example:: 2021-04-18T04:19:35.000+06:30

时长:格式 <time> <unit>,比如 6 hours 或者 4 minutes

Example:: 7 hours
Example:: 4min
Example:: 16 days
Example:: 9 years, 8 months, 4 days, 16 hours, 2 minutes
Example:: 9 yrs 8 min

链接:Obsidian 的格式,如果要在 Front matter 中使用,则需要使用双引号

Example:: [[A Page]]
Example:: [[Some Other Page|Render Text]]

数组:

Example:: 1, 2, 3
Example:: "yes", "or", "no"

对象,字典:

field:
  value1: 1
  value2: 2
  ...

字段 Fields

页面中的隐式字段

Dataview 会给每一个文件都自动添加一些默认的 metadata 信息,这些字段在后面的检索语法中都可以使用。

Implicit Field 字段1

  • file.name: 文件标题(字符串)
  • file.folder: 文件所属文件夹路径
  • file.path: 文件路径
  • file.size: (in bytes) 文件大小
  • file.ctime: 文件的创建时间(包含日期和时间)
  • file.mtime: 文件的修改时间
  • file.cday: 文件创建的日期
  • file.mday: 文件修改的日期
  • file.tags: 笔记中所有标签数组,子标签会按照每一个层级分别展开存储,比如 #Tag/A/1,会有三个 [#Tag, #Tag/A, #Tag/A/1]
  • file.etags: 除去子标签的数组
  • file.inlinks: 指向此文件的所有传入链接的数组
  • file.outlinks: 此文件所有出站的链接数组
  • file.aliases: 文件别名数组
  • file.day: 如果文件名中有日期,那么会以这个字段显示。比如文件名中包含 yyyy-mm-dd(年-月-日,例如2021-03-21),那么就会存在这个 metadata。

任务中的隐式字段 implicit field in task

同样对于 Tasks,Dataview 也会自动创建一些隐式的字段。

每一个任务都会有如下的字段:

  • Task 会继承所在文件的所有字段,比如 Task 所在的页面中已经包含了 rating 信息了,那么 task 也会有
  • completed 任务是否完成
  • fullyCompleted: 任务以及所有的子任务是否完成
  • text: 任务名
  • line: task 所在行
  • path: task 所在路径
  • section: 连接到任务所在区块
  • link: 连接到距离任务最近的可连接的区块
  • subtasks: 子任务
  • real: 如果为 true, 则是一个真正的任务,否则就是一个任务之前或之后的元素列表
  • completion: 任务完成的日期
  • due: 任务到期时间
  • created: 创建日期
  • annotated: 如果任务有自定义标记则为 True,否则为 False

Dataview Query Language

一旦定义了上述的字段(标签、属性)就可以用 Dataview 定义的查询语法来检索并展示内容。这是一个类 SQL 的语法,一个最简单的例子:

```dataview
TABLE rating AS "Rating", summary AS "Summary" FROM #games
SORT rating DESC
```

Dataview 插件提供了三种样式的展现形式:

  • table, 检索内容以表格形式展现
  • list, 列表形式展现
  • task, 检索内容中的任务

一个标准的语法是这样的:

```dataview
[list|table|task] field1, (field2 + field3) as myfield, ..., fieldN
from #tag or "folder" or [[link]] or outgoing([[link]])
where field [>|>=|<|<=|=|&|'|'] [field2|literal value] (and field2 ...) (or field3...)
sort field [ascending|descending|asc|desc] (ascending is implied if not provided)
```

说明:

  • dataview 告诉 Obsidian 这个地方需要使用 Dataview 插件
  • list|table|task 告诉 Dataview 插件展现形式
  • from 则告诉 Dataview 包含什么文件,如果是 #tag 获取标签,获取文件夹,等等
  • Where:筛选 from 中的内容,进行过滤,可以指定从某个标签(在yaml字段中的标签),或某个文件夹中检索信息。
  • sort:排序:可以按某个字段进行排序,可以升序(使用参数ascending、asc)或降序(使用参数descending、desc)

table

个人最常使用的一个展现形式,可以以表格的形式展示多列。

```dataview
table field
from #tag 
sort file.ctime desc
```

表示过滤出所有包含 #tag 的文件,并显示 field 列,最后以表格形式,并以文件创建时间倒序渲染展示。

进阶用法

除了上面提到的 Query 语法,Dataview 插件还提供了另外两种查询语法。

  • 行内查询
  • Dataview JS 进阶查询

行内查询 Inline DQL

Dataview 提供的查询可以直接在行内使用,比如

# 查看当前文件名
`= this.file.name`
# 今天日期
`= date(today)`
# 距离某个日期时长
`=(date(2023-12-31)-date(today))`

可以在 Dataview 设置中自定义行内查询的前缀。

Dataview JS

Dataview JS 给了用户进一步查询的能力,可以直接使用 JavaScript , Dataview JS 定义了一些预置函数可以直接使用。

使用 dataviewjs 代码块:

```dataviewjs
let pages = dv.pages("#books and -#books/finished").where(b => b.rating >= 4);
for (let group of pages.groupBy(b => b.genre)) {
   dv.header(group.key);
   dv.list(group.rows.file.name);
}
```

在 Dataview JS 中需要使用 dv 变量,插件定义了一些默认的函数。

检索页面

通过 dv.pages() 函数:

```dataviewjs
dv.pages("#korean-drama") //返回所有带有标签 books 的页面
dv.pages('"folder"') //返回所有在 folder 文件夹的页面
dv.pages("#yes or -#no") //返回所有带有标签 yes 或者没有标签 no 的页面
dv.pages("") //返回所有页面
```

检索路径

返回文件的路径:

```dataviewjs
dv.pagePaths("#books") //返回所有带有标签 books 的页面路径
dv.pagePaths('"folder"') //返回所有在 folder 文件夹的页面路径
dv.pagePaths("#yes or -#no") //返回所有带有标签 yes 或者没有标签 no 的页面路径
```

返回单个页面

```dataviewjs
dv.page("Index") //返回名称为 Index 的页面
dv.page("books/The Raisin.md") //返回所有在 books 文件夹下的 The Raisin 文件的页面
```

渲染

通过 header 来设置标题,比如 level=1 就是设置一级标题:

dv.header(level, text)

列表

```dataviewjs
dv.list([1, 2, 3]) //生成一个1,2,3的列表
dv.list(dv.pages().file.name)  //生成所有文件的文件名列表
dv.list(dv.pages().file.link)  //生成所有文件的文件链接列表,即双链
dv.list(dv.pages("").file.tags.distinct()) //生成所有标签的列表
dv.list(dv.pages("#book").where(p => p.rating > 7)) //生成在标签 book 内所有评分大于 7 的书本列表
```

任务列表

```dataviewjs
// 从所有带有标签 project 的页面中获取所有的任务生成列表
dv.taskList(dv.pages("#project").file.tasks)

// 从所有带有标签 project 的页面中获取所有的未完成任务生成列表
dv.taskList(dv.pages("#project").file.tasks
    .where(t => !t.completed))

// 从所有带有标签 project 的页面中获取所有的带有特定字符的任务生成列表
// 可以设置为特定日期
dv.taskList(dv.pages("#project").file.tasks
    .where(t => t.text.includes("#tag")))

// 将所有未完成且带有字符串的任务列出
dv.taskList(
    dv.pages("").file.tasks
    .where(t => t.text.includes("#todo") && !t.completed),1)
```

表格

```dataviewjs
// 根据标签 book 对应的页面的 YAML 生成一个简单的表格,其中 map 为对应的内容所对应的表头,按顺序填入即可。
// b 可以是任意值,只是代表当前传入的文件为 b
dv.table(["File", "Genre", "Time Read", "Rating"], dv.pages("#book")
    .sort(b => b.rating)
    .map(b => [b.file.link, b.genre, b["time-read"], b.rating]))
```

同样也可以在行内使用 Dataview JS:

`$= dv.current().file.mtime`

总结

再回到我最初的需求,比如我想查看我笔记中所有包含了 korean-drama 标签的笔记,以及我的打分。只要我的笔记中之前已经包含了 Front Matter,就可以用下面的代码直接检索出来。

```dataview
table file.ctime as "Create Time", rating as "Score"
from #korean-drama
sort rating desc
```

结果:

obsidian dataview query result

再比如我想知道我笔记中包含了洛克这个单词的所有笔记,以及出现的行内容。

那么可以用这一段代码:

```dataviewjs
const files = app.vault.getMarkdownFiles()
const prompt = "洛克"

const fileObject = files.map(async (file) => {
    const fileLink = "[["+file.name.split(".")[0]+"]]"
    const content = await app.vault.cachedRead(file)
    return {fileLink, content}
})

Promise.all(fileObject).then(files => {

let values = new Set(files.reduce((acc, file) => {
    const lines = file.content.split("\n").filter(line => line.match(new RegExp(prompt, "i")))
    if (lines[0] && !file.fileLink.includes("Untitled")) {
        if (acc[0]) {
            return [...acc, [file.fileLink, lines.join("\n")]]
        } else {
            return [[file.fileLink, lines.join("\n")]]
        }
    }
    return acc
}, []))

dv.header(1, prompt)
dv.table(["file", "lines"], Array.from(values))

})
```

reference


2021-12-13 obsidian , note-taking , dataview , metadata , query , obsidian-plugin

使用 k3s 和 WireGuard 网络部署 Kubernetes 集群

在今年的黑色星期五入手了几个不同 VPS 提供上的机器,包括之前的 A400,以及 HostHatch,手上的机器可以用来学习搭建一个 Kubernetes。

在之前的文章中已经介绍过netmaker,这是一个可以用来管理 [[WireGuard]] 网络的工具,这篇文章就简单介绍一下如何在不同的云服务器提供商的主机之间,利用 WireGuard 构建的局域网,并使用 k3s 来搭建一个简单的 Kubernetes 集群。

概念介绍

Netmaker

Netmaker 是一个开源的、基于 WireGuard 网络的组网工具,可以非常方便的构建 WireGuard 网络。

WireGuard

[[WireGuard]] 是一个已经合并到 Linux 内核的轻量级 VPN 协议,可以在不同的主机中建立点对点通信隧道。

K3s

[[k3s]] 是 Rancher Lab 发布的一款轻量级的 Kubernetes 发行版。

Kubernetes

Kubernetes 是一个用于管理容器的开源运维平台,非常易于扩展。通常简称 k8s。

工具选择

因为我搭建 k8s 只是为了学习,不是为了应用于生产,所以不直接使用 Kubernetes,而是选择更轻量的 K3s,也可以在性能没有那么好的 VPS 上运行。

Kubernetes 安全性的问题则通过 WireGuard 网络解决,WireGuard 可以在不同主机节点之间建立加密隧道,可以让节点和节点之间的通信都只经过这条加密隧道,这样即使我的不同的主机在不同的地点,可以保证之间的通信是可靠的。但是存在一个问题便是,如果只是只有几台机器,那么完全可以通过手工的方式来管理 WireGuard 网络,但如果机器很多则管理起来会非常麻烦,所以这里使用 Netmaker,可以只通过简单命令快速构建一个私有局域网。

这里不会使用 Netmaker 更加只能的一些特性,比如 DNS,storage,或者 High Availability(高可用),只简单的借助其基本的组网特性。

设置

在这个演示的过程中,我使用两台 RackNerd 的 1核2G VPS(分别叫做 RN1,RN2)作为 worker 节点 ,以及一台 2核4G 的 A400 机器作为 k3s master 节点,然后有一台另外的机器安装 Netmaker 管理端。

  • 操作系统:机器都安装 Ubuntu 20.04
  • 节点机器都已经安装 apt install wireguard-tools
  • Netmaker 节点安装了 Docker 以及 docker-compose,并且保证 80, 8081, 50051 端口是开放的

Netmaker 安装 WireGuard 设置

第一步首先需要通过 Netmaker 构建一个节点与节点之前的安全网络,用于之后的通信。首先在 Netmaker 节点的机器上安装 Netmaker。

教程可以参考之前的 Netmaker 文章,为了方便起见可以选择一键安装脚本:

sudo wget -qO - https://raw.githubusercontent.com/gravitl/netmaker/master/scripts/nm-quick.sh | bash

如果熟悉 docker-compose 可以下载 docker-compose.yml 自行修改配置后启动。

启动之后可以根据脚本的提示获取 Netmaker 登录后台的地址。在 UI 界面中创建用户,并登录。

在界面中创建一个 Network 叫做 k3s,然后在选项中配置私有网络地址的范围是 10.11.11.0/24

这一步完成之后,就可以将不同的主机节点添加到这个网络中,首先点击 Access Keys,选择 k3s 网络,然后创建 Key,Key 的名字重要,可以随意,然后给这个 Key 100 次使用机会。然后就会获得一个安装脚本,通常是 curl 开头的。

然后在去其他节点中依次安装,将其添加到 Netmaker 网络中。

确保节点已经安装 wireguard-tools 包,然后使用 root 账户:

su -
# 粘贴安装脚本
curl ...
# 完成安装后执行 wg show 查看状态是否正常
wg show

wg show 命令会显示网络接口,如果节点添加成功,可以继续在其他节点中依次执行上述步骤,直到把所有节点都添加到网络中。然后其 Netmaker 后台,点开 k3s 网络可以查看到网络中添加进来的主机节点,以及其私有 IP 地址。一般 Netmaker 会根据主机的 Hostname 来在界面中展示,可以点击修改来重命名网络中的任何节点名字。

K3s 安装

假设 master 节点安装在 A400 机器,worker 节点使用两台 RackNerd 节点。

那么首先登录 A400 机器

# 切换到 root
su -
# 查看 IP
ip a

可以看到结果中会有一个 nm-k3s 的网络接口,其中显示的 IP 地址就是 WireGuard 的地址,假设是 10.11.11.4,然后需要将此 IP 地址替换下面命令中的 IP 地址。

curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC=”server --node-ip 10.11.11.4 --node-external-ip 10.11.11.4 --flannel-iface nm-k3s” sh -

等待安装完成,可以使用如下命令查看状态:

systemctl status k3s
kubectl get nodes
kubectl get pods --all-namespaces

一旦确认状态都没有问题,所有的 pods 都正常运行,可以开始部署 worker 节点。首先从 master 节点获取 node key:

cat /var/lib/rancher/k3s/server/node-token

然后在每一个 worker 主机上执行:

# 切换到 root
su -
# 查看 IP
ip a

获取机器的私有 IP 地址,用来替换下面命令中的 10.11.11.x。然后下面命令中的 MASTER 需要替换成 k3s master 机器的 IP,如果是上面的例子就是 10.11.11.4,将 MASTER 修改为 4。 然后将 <TOKEN VAL> 替换成上面 cat /var/lib/rancher/k3s/server/node-token 命令输出的结果。

curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="agent --server https://10.11.11.MASTER:6443 --token <TOKEN VAL> --node-ip 10.11.11.X --node-external-ip 10.11.11.X --flannel-iface nm-k3s" sh -

替换上面的命令中的部分,完成执行,使用如下命令查看状态:

systemctl status k3s-agent

然后依次在两台 worker 机器中执行。

然后到 master 节点执行:

sudo kubectl get nodes
sudo kubectl get pods --all-namespaces -o wide

显示如下:

测试

创建一个 pingtest.yml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: pingtest
  namespace: pingtest
spec:
  selector:
    matchLabels:
      app: pingtest
  replicas: 4
  template:
    metadata:
      labels:
        app: pingtest
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - pingtest
            topologyKey: "kubernetes.io/hostname"
      containers:
      - name: busybox
        image: busybox
        command: ["/bin/sh", "-ec", "sleep 1000"]

然后执行:

kubectl create namespace pingtest
kubectl apply -f pingtest.yml
kubectl get pods -n pingtest -o wide

执行完成之后,可以看到三个节点中有三个运行的 pods,因为上面指定了 replicas 是 4,所以会有一个在 pending 中。

进入一个运行的节点,执行 ping:

kubectl exec -it pingtest-588df6f488-zzcrp -n pingtest -- sh

然后执行 ping 其他节点的操作。

Nginx test

创建一个 nginx.yml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 2
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: nginx
  name: nginx
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: nginx
  sessionAffinity: None
  type: ClusterIP

然后执行:

kubectl create namespace nginx
kubectl apply -f nginx.yml -n nginx

然后

kubectl exec -it pingtest-588df6f488-zzcrp -n pingtest -- sh

在其中可以访问其他 worker 节点中的 nginx index.html

reference


2021-12-13 kubernetes , k3s , netmaker , wireguard

搭建临时 socks5 代理

这篇文章简要的介绍一下如何在 VPS 快速使用 Docker 安装一个 socks 代理来满足临时需要 socks 代理的情况,适合直接在 VPS 上安装,然后用完立即删除。

然后搭配 Chrome 下的浏览器插件直接实现快速代理。

或者可以搭配 [[proxychains-ng]] 来实现终端下的代理。

搭建临时 socks5 代理

docker run -d --name socks5 -p 1090:1080 -e PROXY_USER=<USER> -e PROXY_PASSWORD=<PASSWD> --restart=always serjs/go-socks5-proxy

docker compose 安装可以参考这里

说明:

  • Docker 映射端口,比如 1090:1080 将外部端口 1090 映射到容器内 1080 端口

测试服务是否正常,如果有设置密码:

curl -x socks5://[user:password@]proxyhost[:port]/ url
    curl --socks5 --user <PROXY_USER>:<PROXY_PASSWORD> <docker host ip>:1080 http://ifcfg.co

如果没有密码:

curl --socks5 server.ip:port https://example.com

Proxychains

socks proxy + proxychains-ng

reference


2021-12-12 linux , socks5 , proxy-server , vps , docker

使用 Netmaker 快速组建 WireGuard mesh 网络

前段时间在逛 LET 看到有人发帖询问在 VPS 上安装了什么,论坛中的 @Galeej 提到,自己将所有的 VPS 通过 Tailscale 私有网络连接,对其他 VPS 的操作都通过一台新加坡的 relay 服务器转发(并且设置其他 VPS 的网络连接只允许特定的 IP),而所有需要暴露到公开互联网上的端口都隐藏在 Cloudflare 之后,我看到这样的配置之后发现这样的 VPS 配置非常的安全,公开互联网上没有暴露任何可以攻击的端口,所有的服务都在 WireGuard 内网中。

然后在之后的讨论中 @Ouji 又提出,他将用 Netmaker 来替换 Tailscale,然后我就调研了一下 Netmaker 发现这是 Tailscale 的开源版本,不仅提供了非常友好的配置管理界面,还能够提供自定的网络段(我使用 Tailscale 不舒服的一点就是分配的内网 IP 都是随机的)。所以下面就简单的介绍一下 Netmaker 的使用,并简单的演示一下创建一个点对点 mesh 网络。

什么是 Netmaker

Netmaker 是一个开源的、基于 [[WireGuard]] 的网络(overlay network) 控制工具,可以非常快速的用来组建 WireGuard 网络。

如果你有两台连接互联网的设备,那么 Netmaker 可以组建一个安全的网络,并打通一个安全的隧道提供给两台机器通信。而如果你有数千台机器分布在不同的地区,不同的数据中心,不同的网络中,那么 Netmaker 也可以组建一个网络来提供给这些不同的节点通信。

如果熟悉 AWS,那么 Netmaker 就像 VPC 一样,不过 Netmaker 可以应用在任意的机器中。从 Netmaker 网络中的机器来看,同一个网络中的机器尽管在世界各地,但其相互通信就像是在同一个局域网中一样。

Netmaker 和其他一些产品非常相似,比如 [[Tailscale]], [[ZeroTier]],[[Nebula]] 但不同于这些产品的是,Netmaker 连接更快,更加灵活。Netmaker 使用 [[WireGuard]] 所以更快,Netmaker 中的节点不管是服务端还是Agent都完全可配置,所以提供了更大的灵活性。

Netmaker 优于 [[Tailscale]] 的地方还在于 ,Netmaker 不需要 Google, Microsoft 或者 GitHub 账号。Netmaker 可以认为是一个可以自行托管的 Tailscale。

Netmaker 工作原理

Netmaker 依赖于 WireGuard 来在机器间创建隧道(tunnel), Netmaker 通过管理不同机器的 WireGuard 来创建网络。简单来说,Netmaker 实现 Client/Server 架构:

  • the admin server 管理端,称为 Netmaker,管理网络的界面
  • the agent,客户端,称为 Netclient,客户端通过 gRPC 与服务端通信

作为 Network 管理端,你可以通过管理端来创建网络,管理连接的设备。服务端会保存所有网络和设备的配置信息,这些信息会被 netclient (agent) 来获取使用。

客户端(netclient) 是一个二进制文件,netclient 会在节点被添加到网络中的时候安装到不同的机器中,netclient 可以在大多数系统中运行,不管是虚拟机,物理机,或者 IoT 设备都可以运行 netclient。netclient 会连接服务端,通过服务端的配置来自动管理 WireGuard,动态更新 Peers。通过不断向网络中添加节点的方式,可以创建一个动态的,完全可以配置的虚拟网络。

Netmaker server 不会路由网络流量,否则这个网络就变成了一个中心辐射模型(hub-and-spoke model),这会使得中心服务器变成瓶颈,并且拖慢网络。相反,Network 会告诉网络中的节点他们之间可以相互直接通信,这被称为 full mesh network(网状网络),这会让节点和节点的连接更快。即使管理端宕机,只要现存的节点没有变化,那么这个网络依然可以正常工作。

应用场景 Use Cases

Netmaker 有非常多的应用场景,事实上,现在可能就已经在使用了。

用例:

  • 自动创建 WireGuard mesh network
  • 在云环境和数据中心之间创建 flat, secure 网络
  • 给 IoT 设备提供更安全的网络访问
  • 增强家庭,或办公网络的安全性
  • 在现存网络上增加一层加密
  • 安全的 site-to-site 连接
  • 管理 cryptocurrency proof-of-stake 机器
  • 创建动态的安全的 Kubernetes underlay network

更多的用例也可以参考官网

架构

Netmaker 的架构图:

概念

熟悉一下 Netmaker 中常常被提到的概念,有助于理解。

WireGuard

[[WireGuard]] 相对比较新,但非常强大,WireGuard 被加入到了 Linux kernel。WireGuard 可以非常简单快速地在设备之间创建加密通道。从 WireGuard 官网的介绍中可以看到,“WireGuard 可以被认为是工业界最安全,最方便使用,最简单的 VPN 解决方案”。

之前的解决方案,比如 OpenVPN,或者 IPSec 都被认为又重又复杂,并且性能也不是很好。所有现存的 VPN tunneling 方案都会导致网络延迟增大。WireGuard 是第一个实现了几乎接近有线连接网络速度的 VPN,可以看到 WireGuard 对现有网络连接几乎没有影响。随着 WireGuard 的发布,没有任何理由去使用其他隧道加密技术了。

Mesh Network

当提到 mesh network (网状网络)的时候通常也会说 「full mesh」。一个 full mesh network 指的是网络中的任何节点都可以相互直接连接。

比如在路由器后面的家庭网络,所有的电脑都会通过私有局域网地址相互连接。

Mesh network 通常会和 hub-and-spoke (中心辐射) 网络放到一起对比,中心辐射的网络中,一个节点必须通过 relay server 才能和另外一个节点进行通信。

在一些场景中,你可以需要部分的 mesh network,网络中只有部分设备需要相互直接通信,而其他设备都需要将流量转发给一个 relay/gateway 。Netmaker 在某些时候也可以实现这类模型。在第一张图片中,这个设置就是一个部分的 mesh network,因为节点A-D 是网状网络,而其他的客户端通过 gateway 连接。

Mesh networks 通常比其他拓扑的网络更快,但通常设置也会更加复杂。WireGuard 提供了在设备之间创建加密隧道的方法,但是它不提供设置完整网络的方法。这是 Netmaker 诞生的理由。

Netmaker

Netmaker 是一个建立在 WireGuard 上的平台,允许用户在设备之间构建 mesh networks。Netmaker 可以根据需要创建完全的、或部分的 mesh network。

当我们提及 Netmaker 整体的时候,通常指的是 Netmaker 以及 netclient, 以及其他辅助的服务,比如 CoreDNS,rqlite 和 UI 服务。

从终端用户来看,通常会和 Netmaker UI 交互,或会直接在终端节点中直接运行脚本。而其他部分都会在后台默默地执行。

Netmaker 做了非常多的配置相关的工作,简化了用户的配置。包括了 WireGuard 的端口,endpoints( 终端) , public IPs(公网IP),keys(密钥) 和 peers(节点)。Netmaker 尽可能多地抽象了网络管理,只需简单的在界面点击创建网络,然后点击将计算机添加到网络。也就是说,每一个机器都是不同的,可能需要不同的配置。这就是为什么,Netmaker 使用一套默认设置,则网格内的所有内容都是完全可配置的。

SystemD

SystemD 是一个被 Linux 广泛使用的系统服务器管理器。尽管没有被所有发行版采用,但是不管如何,它已经成为了 Linux 世界的事实标准。非 Linux 可能没有 systemd,而 Linux/Unix 发行版有可代替的 system service managers。

Netmaker 的 netclient (客户端) 会控制节点上的网络,可以通过命令行运行,或者通过作为 system 守护进程(daemon),在 Linux 上会默认以 daemon 运行(依赖于 systemd)。

从 0.8 版本开始,支持了 macOS 和 Windows,在这些操作系统中,netclient 使用 LaunchD 来启动 netclient 守护进程。

Ingress Gateways

在 Netmaker 网络中的任何节点都可以成为 Ingress Gateway,Ingress Gateway 可以接受 Netmaker(WireGuard)网络外部的流量。

Egress Gateways

Egress Gateway 出口网关,允许将内部网络流量转发到外部指定 IP。

Netmaker 安装

硬件要求

服务器:

  • 一台可用的 VPS(最好比较干净,没有占用端口,否则需要根据自己的需要自行调整),不推荐 Oracle Cloud 的机器,网络接口有问题
  • 公开的 IP 地址
  • 至少 1GB RAM,1CPU(4GB RAM,2 CPU 生产环境)
  • 2GB+ 存储
  • Ubuntu 20.04

软件要求

域名:

  • 一个可用的域名(可选)
  • 可以操作管理 DNS 服务(53端口)
  • 保证 443(tcp), 53(tcp udp), 51821-518XX(udp) 端口可用
    • 443 端口,Dashboard,REST API 和 gRPC
    • 53 端口,CoreDNS
    • 51821-518XX,WireGuard,每一个网络需要一个端口,起始端口会使用 51821,可以根据自己的网络端数量需要设定端口范围
    • 允许防火墙 sudo ufw allow proto tcp from any to any port 443 && sudo ufw allow 53/udp && sudo ufw allow 53/tcp && sudo ufw allow 51821:51830/udp

一键安装

如果想使用自己的域名,比如 dashboard.netmaker.example.com 这样,可以参考官网。这里为了演示方便,就使用一键脚本。

Netmaker 官方已经提供了一个 Docker 镜像,并且也提供了安装脚本:

sudo wget -qO - https://raw.githubusercontent.com/gravitl/netmaker/master/scripts/nm-quick.sh | bash

如果没有使用自己的域名,一键脚本会使用一个 nip.io 的泛域名解析映射工具根据 IP 自动获取一个域名,用来访问后台地址。

首次登录后台会要求设定用户名和密码。登录后台之后,左侧的 Networks、 Nodes、 Access Keys 是比较重要的菜单。

在 Network 选项中选择创建 Network,设定 IP 段范围,然后在其他机器上依次安装客户端,添加节点到网络中即可。

在添加到之后网络之后,可以运行 sudo wg show 查看信息:

❯ sudo wg show
interface: nm-k3s
  public key: PGeGQKOlJt4zZJX2axf15dRsWvs6QaFRF/j/fJUfnjw=
  private key: (hidden)
  listening port: 51821

peer: Cjbp/IeTEFgPEJpOldjkaleUvlNjqg+y75hiI/Sq61Q=
  endpoint: 140.XXX.XXX.XXX:51821
  allowed ips: 10.10.11.8/32
  latest handshake: 2 seconds ago
  transfer: 6.74 KiB received, 1.88 KiB sent
  persistent keepalive: every 20 seconds

通常会看到 interface 和 peer 信息。

手动安装

如果需要手动安装,也不是特别麻烦,从官网下载 docker-compose.yml 文件:

wget -O docker-compose.yml https://raw.githubusercontent.com/gravitl/netmaker/master/compose/docker-compose.contained.yml
sed -i 's/NETMAKER_BASE_DOMAIN/<your base domain>/g' docker-compose.yml
sed -i 's/SERVER_PUBLIC_IP/<your server ip>/g' docker-compose.yml
sed -i 's/COREDNS_IP/<default interface ip>/g' docker-compose.yml

生成唯一的 master key:

tr -dc A-Za-z0-9 </dev/urandom | head -c 30 ; echo ''
sed -i 's/REPLACE_MASTER_KEY/<your generated key>/g' docker-compose.yml

配置 Caddy

wget -O /root/Caddyfile https://raw.githubusercontent.com/gravitl/netmaker/master/docker/Caddyfile

sed -i 's/NETMAKER_BASE_DOMAIN/<your base domain>/g' /root/Caddyfile
sed -i 's/YOUR_EMAIL/<your email>/g' /root/Caddyfile

然后启动:

sudo docker-compose up -d

然后可以访问 dashboard.nm.example.com 后台。

需要注意的是,如果使用自己的域名需要添加一个泛域名 A 记录(wildcard A record),比如想要后台访问地址是 dashboard.nm.example.com 那么需要添加 *.nm.example.com

Caddy 会创建三个子域名:

  • dashboard.nm.example.com
  • api.nm.example.com
  • grpc.nm.example.com

netclient 使用

netclient 是一个简单的 CLI,用于创建 WireGuard 配置和接口。netclient 可以管理任意私有网络。

macOS 安装

首先安装依赖:

brew install wireguard-tools

外延

Nebula 是另外一个选择,同样基于 WireGuard。

更多资料可以查看 Gravitl 官网:https://gravitl.com/resources

reference


2021-12-10 netmaker , wireguard , mesh-network , linux , vpn , network , tunnel , secure

每天学习一个命令:mysqlbinlog 命令使用

MySQL 或 MariaDB 中,对数据库做的任何操作都会被记录到 Binary Log 日志文件中。

二进制日志文件在主从复制中非常重要。恢复 MySQL 时也会使用到二进制日志文件。

但是 Binary Log 而二进制文件,所以无法直接使用文本查看工具打开看,所以 MySQL 提供了 mysqlbinlog 命令。

mysqlbinlog 命令是一个以友好可读方式查看 MySQL Binary log 的命令行工具。也可以使用 mysqlbinlog 命令来读取内容并用管道传给其他 MySQL 工具集。

MySQL 的 binary log 文件包含了对数据库文件操作的事件。MySQL 服务器会以二进制文件写入。

举例

获取二进制文件列表

在 MySQL 中执行:

mysql> SHOW BINARY LOGS;

如果没有开启二进制文件,则会报错 ERROR 1381 (HY000): You are not using binary logging

二进制日志文件默认会存放在 /var/lib/mysql 目录下

使用 mysqlbinlog 命令

使用:

mysqlbinlog log_file

mysqlbinlog 常用的参数有:

  • -d, --database=db_name 指定数据库
  • -o, --offset=n 忽略日志前 n 行
  • -v, -vv-v 从 binlog 中重建 SQL 语句,-vv 增加了注释
  • --start-datetime=datatime, --stop-datetime=datetime 指定日期间隔内的所有日志
  • --start-position=position, --stop-position=position 指定位置间隔内的所有日志

常用方法

通过 binlog 查看 delete 语句的执行:

mysqlbinlog /data/mysql_data/bin.000008 --database EpointFrame --base64-output=decode-rows -vv --skip-gtids=true |grep -C 1 -i "delete from Audit_Orga_Specialtype" > /opt/sql.log

说明:

  • /data/mysql_data/bin.000008:需要解析的 binlog 日志。
  • database:只列出该数据库下的行数据,但无法过滤 Rows_query_event。
  • base64-output=decode-rows -vv:显示具体 SQL 语句。
  • skip-gtids=true:忽略 GTID 显示。
  • grep -C 1 -i "delete from dataex_trigger_record":通过管道命令筛选出所需 SQL 及执行时间。
  • /opt/sql.log:将结果导入到日志文件,方便查看。

限定时间范围

设定范围

mysqlbinlog --start-datetime='2021-12-06 12:00:00' --stop-datetime='2021-12-06 16:00:00' mysql-bin.0043* |grep 'admin_user' -B 6 A6

设定位置

mysqlbinlog --start-position=120 --stop-position=330 /path/to/binlog.00001

查看特定数据的 log

可以使用 -d 参数来指定特定数据库上的日志:

mysqlbinlog -d gogs mysqld-bin.000001
mysqlbinlog --database gogs mysqld-bin.000001

reference


2021-12-07 mysql , mysqlbinlog , linux , database , cli , binary-log

OmniEdge 虚拟组网工具使用及原理简介

[[OmniEdge]] 是一个可以用来快速组建点对点私有网络的工具,也可以用来做内网穿透。

官方提供 Starter 套餐,可以供一个用户,最多创建 1 个虚拟网络,连接 20台设备。

2023 年更新

OmniEdge 已经停止运营。

安装

一键安装脚本:

curl https://omniedge.io/install/omniedge-install.sh | bash

Linux

curl https://omniedge.io/install/omniedge-install.sh | bash
omniedge login -u yourname@youremail.com
omniedge login -s yoursecuritykey
omniedge join -n 'virtual-network-id'

Run OmniEdge as a Service

在用 CLI 登录 OmniEdge 之后,推荐在后台运行。

创建 service:

vi /etc/systemd/system/omniedge.service

填入信息:

#/etc/systemd/system/omniedge.service
[Unit]
Description=omniedge process
After=network-online.target syslog.target nfw.target
Wants=network-online.target

[Service]
Type=simple
ExecStartPre=
#Replace to your real virtual network id(can be found by run omniedge join) and auth.json path
ExecStart=/usr/local/bin/omniedge join -n "your_virtual_network_id" -f your_auth_file_path
Restart=on-abnormal
RestartSec=5

[Install]
WantedBy=multi-user.target
Alias=

上面的配置中有两个地方需要修改一下:

  • 一个是网络ID,可以通过命令 omniedge join 获取,或者直接在管理后台获取
  • 一个是 auth.json 在登录成功之后会在 /root/.omniedge/auth.json 目录中

激活服务:

systemctl daemon-reload
systemctl enable omniedge.service
systemctl enable omniedge.service

为什么不使用 WireGuard

[[WireGuard]] 作为一个现代的 VPN 解决方案,简单,快速,并且易于维护,OmniEdge 官方的博客 也说过曾经尝试过使用 WireGuard,但是 WireGuard 存在的一个问题是,当构建一个具有庞大数量节点的网络的时候,管理和维护成本会成倍增加。

为什么不选择 n2n

[[n2n]] 1 是一个轻量、开源的用来组件点对点网络的工具,n2n 让 Super Node 处理节点的管理工作,这也就意味着这些节点可能需要处理大量流量,并且可能影响网络的性能。这使得 n2n 不适合构建一个完善的稳定的企业网络。

架构

OmniEdge 为了解决上面的问题,指定了一些基本的原则:

  • 足够简单,对于用户和网络管理都要足够简单
  • 基于 Zero-trust security model,用户可以通过类似于 Okta, G Suite 等等的验证工具来组件安全的网络
  • 使用 Peer-to-peer 网络通信,提升网络速度,避免单点故障

基于上面的设计目标,收到 n2n 架构的影响,设计了如下的 OmniEdge 架构。

Super Node: 用来协调虚拟网络节点和节点之间的通信

  • 协调节点和节点之间的网络通信
  • 舱室在节点和节点之间建立直接连接;如果不行,则作为节点和节点通信的 relay 节点

Node: 虚拟网络中的具体的节点

  • 保存、管理虚拟网络的信息,比如 keys, network node public keys 等等
  • 在虚拟网络上直接或间接转发 TCP 和 UDP 流量
  • 提供本地的 DNS 解析

Manager: 管理虚拟网络

  • 管理网络节点数据,包括设备ID,公钥,IP 数据,网关,路由表等等其他信息
  • 验证节点,返回网络信息给节点
  • 管理网络的改变,比如节点加入,节点删除
  • 管理节点的生命周期
  • 和用户验证服务交互,管理 ACL 信息

Client: 这是用户用来管理虚拟网络的工具

  • 和节点通信,配置管理节点
  • 处理用户注册,登录流程

利用 supernode 加速

前两天看到 omniedge GitHub 发布了 supernode ,可以用来加速虚拟网络的网络状况。

有兴趣可以自行编译 Docker 镜像:

需要注意的是如果要使用 Sueprnode 必须使用 Pro 或者 Team 套餐。

reference


2021-11-29 omniedge , tailscale , network , private-network , n2n , mesh-network

Linux 下 journal 日志清理

Linux 在运行的过程中会产生很多日志文件,一般存放在 /var/log 目录下,而其中 journal 目录中存放的是 journald daemon 程序生成的日志,其中包括了所有 kernel, initrd, services 等等产生的日志。这些日志在系统发生状况排查问题的时候非常有用。

jounrnald daemon 程序会收集系统运行的日志并存储到二进制文件中。为了查看这些二进制文件通常会使用到 journalctl 命令。但是默认情况下这些日志文件会占用磁盘空间的 10%,而大部分情况下这些日志文件是不需要查看的。所以可以配置减小一些 journal 日志的占用。

默认的日志文件保存在 /var/log/journal 下,可以使用 du 查看。不过我个人推荐使用可视化的 gdu查看

  • du -sh /var/log/journal 查看占用磁盘空间

查看 journal 日志占用大小

可以使用 journalctl 命令查看日志占用:

sudo journalctl --disk-usage

手动清理 journal 日志

如果要去清理 journal 日志,可以先执行 rotate 命令:

journalctl --rotate && \
systemctl restart systemd-journald

删除两天前的日志:

journalctl --vacuum-time=2days

删除两个礼拜前的日志:

journalctl --vacuum-time=2weeks

或者删除超出文件大小的日志:

journalctl --vacuum-size=20M
journalctl --disk-usage
# OR
du -sh /run/log/journal
journalctl --verify
ls -l /run/log/journal/*
systemctl status systemd-journald

修改配置文件限制 journal 日志最大占用

修改配置文件:

sudo vi /etc/systemd/journald.conf

修改其中的两项:

SystemMaxUse=100M
RuntimeMaxUse=100M

SystemMaxUse 设置 /var/log/journal RuntimeMaxUse 设置 /run/log/journal

然后使设置生效:

sudo systemctl daemon-reload

journal 日志过大可能产生的问题

问题

Warning: Journal has been rotated since unit was started. Log output is incomplete or unavailable.

原因是:

the disk usage of the log files for journald journal 日志空间达到了上限


2021-11-29 linux , journal , systemd , journalctl , gdu , du

Proxmox VE 备份和恢复虚拟机

数据是最重要的,本着系统可以挂,但是数据不能丢的原则,这里就整理一下在 Proxmox VE 系统中,直接备份虚拟机,和恢复虚拟机的过程。

为什么需要备份

  • 保证数据的安全性
  • 硬件故障
  • 服务器升级或迁移
  • 恶意软件破坏了系统

准备工作

设置备份存储

设置 Storage,允许保存 VZDump backup 文件:

  • 转到 Datacenter > Storage。
  • 选择备份存储位置。
  • 单击编辑选项卡。
  • 确保有一个 Storage 已经选择了 Content 下的 VZDump backup file
  • 单击确定。

在执行备份之前,需要通过上面的设置设定一个允许备份的 Storage,然后之后的备份文件会存放到该 Storage 中。备份文件以文件形式存储。在大部分的情况下,可以使用 NFS 服务器作为存储备份。

Backup Modes

Proxmox VE 备份服务提供了三种不同的备份模式,在备份菜单中可以看到:

  • stop mode,这个模式以短暂停止 VM 的代价提供了高一致性备份。这个模式会先关闭 VM,然后在后台执行 Qemu 进程来备份 VM 数据,一旦备份开始,如果之前 VM 在运行会继续 VM 的运行状态
  • suspend mode,因兼容原因提供,在调用 snapshot mode 之前 suspend VM,因为 suspend VM 会导致较长时间的停机时间,所以建议使用 snapshot mode
  • snapshot mode,不需要很长的停机时间,代价为可能的一小部分数据不一致,执行 Proxmox VE Live backup,当 VM 在运行的时候拷贝 data blocks

Back File Compression

对于备份的文件,Proxmox VE 提供了多种压缩算法,lzo, gzip, zstd

目前来说,Zstandard(zstd) 是三种算法中最快的,多线程也是 zstd 优于 lzo 和 gzip 的地方。但 lzo 和 gzip 通常来说更加常用。可以安装 pigz 来无缝替换 gzip 以提供更好的性能。

使用了不同的压缩算法的备份文件的扩展名如下:

file extension algorithms
.zst Zstandard(zstd) compression
.gz or .tgz gzip compression
.lzo .zo compression

Backup

备份可以通过 GUI 或者 命令行工具来进行。

备份虚拟机

首先关闭虚拟机

CLI

通过命令行备份:

cd /var/lib/vz/dump
vzdump 101

说明:

  • 以上的命令会在 /var/lib/vz/dump 目录中备份 101 号虚拟机

GUI

通过 UI 界面备份数据:

  • 数据中心> Backup。
  • 转到 添加>创建备份作业。
  • 选择详细信息,例如节点,目标存储,星期几,时间等。
  • 确保备份作业已启用。

备份时跳过特定的目录

在备份虚拟机时有些时候不想要备份虚拟机中的特定目录,比如说缓存目录,这样可以加快备份的速度,以及减小备份文件的体积。

在 Datacenter -> Backup 中建立备份任务之后,会在系统中新建一个 cron,在 /etc/pve/vzdump.cron 文件中:

PATH="/usr/sbin:/usr/bin:/sbin:/bin"

15 2 * * 6           root vzdump 100 101 102 --mailnotification always --compress zstd --mode snapshot --quiet 1 --storage local --exclude-path /mnt/ --exclude-path '/dev/disk/by-id/ata-HGST*'

可以看到,实际使用了 vzdump 命令,直接在后面添加 --exclude-path 并加上不需要备份的目录即可。

更多的用法可以参考 vzdump 命令的使用。

备份时跳过 Disk

配置了备份之后查看日志可以看到:

INFO: Backup started at 2021-10-23 16:59:05
INFO: status = running
INFO: VM Name: ubuntu20.04
INFO: include disk 'scsi0' 'local:101/vm-101-disk-0.qcow2' 64G
INFO: include disk 'scsi1' '/dev/disk/by-id/ata-HGST_HUS726020ALA610_K5HWJ6NG' 1953514584K
INFO: include disk 'scsi2' '/dev/disk/by-id/ata-HGST_HUS726020ALA610_K5J0ZUWA' 1953514584K
INFO: include disk 'scsi3' '/dev/disk/by-id/ata-HGST_HUS726020ALA610_K5HW9RJG' 1953514584K
INFO: backup mode: snapshot
INFO: ionice priority: 7
INFO: creating vzdump archive '/var/lib/vz/dump/vzdump-qemu-101-2021_10_23-16_59_05.vma.zst'

我的虚拟机挂载了三块硬盘,而备份的时候会包括这三块 2T 的硬盘,这是没有必要的,可以通过如下的方法跳过备份挂载而硬盘。

在虚拟机的设置中,点击 Hard Disk,在 Advance 高级选项中可以将 Backup 取消选中,然后保存,在备份的时候就不会保存该设备了。

proxmox ve hard disk backup

定时清理过期的备份

随着虚拟机备份文件的增多,可以占用的本地文件会越来越多,所以定时清理必不可少。

在界面上设置

在 Datacenter -> Storage 在备份的 Storage 中双击进行设置,在 Backup Retention 中可以去掉勾选 Keep all backups 然后进行设置。

proxmox ve backup retention

执行命令

crontab -e 然后编辑:

10 2 * * * find /var/lib/vz/dump/ -mtime +14 -delete

Restore

我们使用 qmrestore 命令从备份中还原 KVM VM 101。

pct restore 600 /mnt/backup/vzdump-lxc-777.tar
qmrestore vzdump-qemu-019-2018_10_14-15_13_31.vma 101

要从GUI还原VM,请执行以下步骤。

  • 浏览到要移动的VM。
  • 单击 Backup。
  • 选择生成的备份文件,然后单击“还原”。
  • 在 Restore 字段中,指定还原虚拟机的位置。
  • 单击 restore 。

reference


2021-11-25 proxmox , pve , vm , backup , data-backup

Linux 虚拟化技术 OpenVZ KVM LXC 对比

介绍一下 Linux 下常见的虚拟化技术。

  • OpenVZ
  • KVM
  • LXC
  • Xen

OpenVZ

OpenVZ 是一种基于 Linux 内核的虚拟化技术,它允许在单个物理服务器上运行多个独立的 Linux 系统实例,每个实例都可以拥有自己的 IP 地址、文件系统、进程等。OpenVZ 使用容器技术实现虚拟化,相比于传统的虚拟机技术,它的性能更高、开销更小,因为它不需要模拟硬件,而是直接利用宿主机的资源。OpenVZ 还提供了一些管理工具,如 vzctl 和 vzlist,方便用户管理和监控容器。

OpenVZ(通常简写成 [[OVZ]]) 只能虚拟化 Linux 操作系统,但是 KVM 可以虚拟化 Linux,Windows,和其他操作系统。

OpenVZ 使用共享的 kernel,所有虚拟化 VPS 中的用户都使用同一个 kernel,因此 kernel 是不可自定义的。

一旦你使用的 RAM 达到了 host 分配的额度,那么剩下的 RAM 就是自由竞争的。如果你运行一些小型程序可能不是问题,但是一定你运行资源密集型程序就可能产生问题。

KVM

KVM 是 Kernel-based Virtual Machine 的缩写,是一个基于内核的虚拟化技术。借助 KVM 可以将 Linux 主机隔离成多个可以独立运行的虚拟环境,即虚拟机。

KVM 允许在单个物理服务器上运行多个独立的虚拟机,每个虚拟机都可以运行不同的操作系统。KVM 使用硬件辅助虚拟化技术,即 Intel VT 或 AMD-V,来提高虚拟机的性能和安全性。KVM 还提供了一些管理工具,如 virt-manager 和 virsh,方便用户管理和监控虚拟机。由于 KVM 是一种完全虚拟化技术,因此它可以运行几乎所有的操作系统,包括 Windows 和其他非 Linux 操作系统。

KVM 是 Linux 的一部分,Linux 2.6.20 版本及之后的版本包含了 KVM。KVM 在 2006 年首次公布,并且之后持续在更新和维护。

与 VMwareESX/ESXi、微软 Hyper-V 和 Xen 等虚拟化产品不同,KVM 的思想是在 Linux 内核的基础上添加虚拟机管理模块,重用 Linux 内核中已经完善的进程调度、内存管理、IO 管理等代码,使之成为一个可以支持运行虚拟机的 Hypervisor。因此,KVM 并不是一个完整的模拟器,而只是一个提供了虚拟化功能的内核插件,具体的模拟器工作需要借助 QEMU 来完成。

KVM 允许你设置使用资源的最小和最大值,这样虚拟化的系统就只能使用这些资源。并且在 KVM 下,RAM,CPU 和硬盘资源都是直接分配给用户,用户可以完全使用这些资源,而不用担心其他虚拟化机器的竞争。

KVM 提供了一个隔离的环境,用户可以自行替换 kernel。

QEMU

KVM 不是一个完整的虚拟机,而是借助 QEMU 来完成虚拟化过程。KVM 是 Linux kernel 的一个模块,通过命令 modprobe 去加载 KVM 模块。加载模块之后,才能通过其他工具创建虚拟机。仅有 KVM 模块不行,用户还需要开源的虚拟化软件 QEMU。

LXC

LXC,一般指 Linux Container,即内核容器技术的简称。LXC 将 Linux 进程沙盒化,使得进程之间相互隔离,并且能够控制各进程的资源分配。

LXC(Linux Containers)是一种基于 Linux 内核的容器化技术,它允许在单个物理服务器上运行多个独立的 Linux 系统实例,每个实例都可以拥有自己的文件系统、进程等,但它们共享宿主机的内核。LXC 使用轻量级的虚拟化技术,相比于传统的虚拟机技术,它的性能更高、开销更小,因为它不需要模拟硬件,而是直接利用宿主机的资源。LXC 还提供了一些管理工具,如 lxc-start 和 lxc-stop,方便用户管理和监控容器。LXC 可以用于构建轻量级的虚拟化环境,比如用于开发、测试、部署等场景。

Linux 容器项目(LXC)提供了一组工具、模板、库和语言绑定。LXC 采用简单的命令行界面,可改善容器启动时的用户体验。

LXC 提供了一个操作系统级的虚拟化环境,可在许多基于 Linux 的系统上安装。在 Linux 发行版中,可能会通过其软件包存储库来提供 LXC。

在 Linux 内核中,提供了 cgroups 功能,来达成资源的区隔化。它同时也提供了名称空间区隔化的功能,使应用程序看到的操作系统环境被区隔成独立区间,包括进程树,网络,用户 id,以及挂载的文件系统。但是 cgroups 并不一定需要引导任何虚拟机。

LXC 利用 cgroups 与名称空间的功能,提供应用软件一个独立的操作系统环境。LXC 不需要 Hypervisor 这个软件层,软件容器(Container)本身极为轻量化,提升了创建虚拟机的速度。软件 Docker 被用来管理 LXC 的环境。

Xen

Xen 最初是剑桥大学 Xensource 的开源项目,2003 年发布首个版本,2007 年 Xensource 被 Citrix 公司收购,开源 Xen 由 xen.org 继续维护。

Xen 是一种基于虚拟机监控器(Hypervisor)的虚拟化技术,它可以在一台物理服务器上运行多个独立的虚拟机,每个虚拟机都可以运行不同的操作系统。Xen 使用硬件辅助虚拟化技术,即 Intel VT 或 AMD-V,来提高虚拟机的性能和安全性。Xen 的虚拟机监控器可以直接访问物理硬件,而虚拟机则运行在虚拟化的环境中,因此可以获得接近于原生系统的性能。Xen 还提供了一些管理工具,如 xm 和 xl,方便用户管理和监控虚拟机。Xen 可以用于构建高性能、高可用性的虚拟化环境,比如用于云计算、虚拟桌面、数据库等场景。

我所购买的 servaRICA 就是 Xen 的虚拟化计数。

Xen 是运行在裸机上的虚拟化管理程序(HyperVisor)。

Xen 会在 Guest VM 边上运行着管理端 VM,Xen 称这个 VM 为 Dom0,虚拟机操作系统叫做 DomU。这个管理 VM 会负责管理整个硬件平台的所有输入输出设备,半虚拟化中 Hypervisor 不对 IO 设备做模拟,只对 CPU 和内存做模拟。

半虚拟化还有一个叫法:操作系统辅助虚拟化(OS Assisted Virtualization),这是因为 Guest VM 自身不带设备驱动,需要向“管理 VM”寻求帮助。这种虚拟化技术允许虚拟化操作系统感知到自己运行在 XEN HyperVisor 上而不是直接运行在硬件上,同时也可以识别出其他运行在相同环境中的虚拟机。


2021-11-18 linux , linux-virtualization , ovz , kvm , lxc

电子书

本站提供服务

最近文章

  • AI Shell 让 AI 在命令行下提供 Shell 命令 AI Shell 是一款在命令行下的 AI 自动补全工具,当你想要实现一个功能,敲一大段命令又记不住的时候,使用自然语言让 AI 给你生成一个可执行的命令,然后确认之后执行。
  • 最棒的 Navidrome 音乐客户端 Sonixd(Feishin) Sonixd 是一款跨平台的音乐播放器,可以使用 [[Subsonic API]],兼容 Jellyfin,[[Navidrome]],Airsonic,Airsonic-Advanced,Gonic,Astiga 等等服务端。 Sonixd 是一款跨平台的音乐播放器,可以使用 [[Subsonic API]],兼容 Jellyfin,[[Navidrome]],Airsonic,Airsonic-Advanced,Gonic,Astiga 等等服务端。
  • 中心化加密货币交易所 Gate 注册以及认证 Gate.io 是一个中心化的加密货币交易所。Gate 中文通常被称为「芝麻开门」,Gate 创立于 2013 年,前身是比特儿,是一家致力于安全、稳定的数字货币交易所,支持超过 1600 种数字货币的交易,提供超过 2700 个交易对。
  • 不重启的情况下重新加载 rTorrent 配置文件 因为我在 Screen 下使用 rTorrent,最近经常调试修改 rtorrent.rc 配置文件,所以想要找一个方法可以在不重启 rTorrent 的情况重新加载配置文件,网上调查了一下之后发现原来挺简单的。
  • Go 语言编写的网络穿透工具 chisel chisel 是一个在 HTTP 协议上的 TCP/UDP 隧道,使用 Go 语言编写,10.9 K 星星。