分类 默认分类 下的文章

eBPF 例子

介绍

What Is eBPF? 是一本非好的入门书.
Learning eBPF 是同一个作者的另外一本进阶书.

key notes:

  1. eBPF 程序分为用户态程序和内核态程序. 内核态使用C或Rust编写,然后clang编译成eBPF 字节码,当内核遇到某种event之后,就会执行这些内核态的eBPF程序, 然后生成数据, 放到一些eBPF程序定义的Map中. 用户态程序主要用来加载eBPF程序并且获取内核态写入Map的数据, 然后整理分析展现这些数据.
  2. 内核态的程序编写成代码, 然后编译成字节码, 然后提交给内核, 然后eBPF虚拟机验证(Verify)这些代码,如果安全, 则加载运行这些代码, 等待事件处罚, 执行Action,写入接口文件.
  3. eBPF 为什么需要一个虚拟机? 运行时动态编译, 验证, 不是预先编译链接. 它不是通用型, 而是特定场景.
  4. eBPF bpf_trace_printk()/sys/kernel/debug/tracing/trace_pipe.
  5. eBPF Map 是内核态和用户态共享数据的渠道. 用户态写入配置, 内核态保存中间结果, 最终输出.
    各种类型的 BPF Map: https://elixir.bootlin.com/linux/v5.15.86/source/include/uapi/linux/bpf.h#L878
    Linux Kernel 关于BPF Map的文档: https://docs.kernel.org/bpf/maps.html
  6. eBPF 程序不允许使用其它函数, 除了helper 函数, 所以要 __always_inline .

bpftrace

经典的 one liner 的例子: https://github.com/iovisor/bpftrace/blob/master/docs/tutorial_one_liners.md

教程: https://github.com/iovisor/bpftrace/blob/master/docs/reference_guide.md

关于网络的部分: https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/configuring_and_managing_networking/network-tracing-using-the-bpf-compiler-collection_configuring-and-managing-networking#doc-wrapper

内置变量: comm, pid, tid, args(所有参数: args.filename)
内置函数: str() 把指针的值变成string.
probe 详细信息, 包括参数: sudo bpftrace -vvl kfunc:vmlinux:tcp_set_state

列出所有的 tracepoint:

sudo bpftrace -l 

https://github.com/lizrice/learning-ebpf

etcd 学习笔记

在一个分布式系统中, ETCD 可以:

  1. 分布式的高度一致性的 Key-Value 存储.
  2. 支持 Service 自动发现.
  3. 高并发 每秒10000次写.
  4. 支持双向认证.
  5. 2379 客户请求, 2380 peer 通信.

CAP理论: The CAP theorem states that distributed systems can have at most two out of three of the following properties: high levels of data consistency, high availability of access to the data, and tolerance of network partitions. Networks cannot be assumed to be reliable, so distributed systems need to account for network partitions. Hence, in terms of the CAP theorem, distributed systems can either be AP or CP.

docker 安装 单实例

文档 https://etcd.io/docs/v2.3/docker_guide/ 命令稍有差别 1)改版本, 2) 改etcd 的命令路径

$ export HostIP="10.249.64.103"
$ docker run -d --restart always -v /usr/share/ca-certificates/:/etc/ssl/certs -p 4001:4001 -p 2380:2380 -p 2379:2379 \
 --name etcd quay.io/coreos/etcd /usr/local/bin/etcd \
 -name etcd0 \
 -advertise-client-urls http://${HostIP}:2379,http://${HostIP}:4001 \
 -listen-client-urls http://0.0.0.0:2379,http://0.0.0.0:4001 \
 -initial-advertise-peer-urls http://${HostIP}:2380 \
 -listen-peer-urls http://0.0.0.0:2380 \
 -initial-cluster-token etcd-cluster-1 \
 -initial-cluster etcd0=http://${HostIP}:2380 \
 -initial-cluster-state new

登录测试:

$ docker exec -it etcd sh
$ etcdctl
$ etcdctl --endpoints=http://localhost:2379 member list

认证

参考: https://etcd.io/docs/v3.5/demo/
auth, user, rule

版本差别

如果你发现你的 etcdctl 的命令比较少, 那有可能是你使用的版本是V2的命令行, 虽然你的 etcdctl 是 V3 的. 要显示设置使用 v3 的命令 (If using released versions earlier than v3.4, set ETCDCTL_API=3 to use v3 API.):

$ ETCDCTL_API=3 ./etcdctl --endpoints=http://127.0.0.1:2379 get "" --prefix

证书认证参数

这4个global 参数可以通过环境变量设置:
--dial-timeout, --cacert, --cert, --key

ETCDCTL_DIAL_TIMEOUT=3s
ETCDCTL_CACERT=/tmp/ca.pem
ETCDCTL_CERT=/tmp/cert.pem
ETCDCTL_KEY=/tmp/key.pem

ETCDCTL_API=3 /usr/local/bin/etcdctl --endpoints=https://localhost:2379 \
              --cert=/etc/etcdtls/etcd-client.crt \
              --key=/etc/etcdtls/etcd-client.key \
              --cacert=/etc/etcdtls/etcd-client-ca.crt get mykey

KV 命令

$ etcdctl put name eric --lease 2e128a4084e72b83
OK
$ etcdctl put name yuan  --lease 2e128a4084e72b83 --prev-kv
OK
name
eric

lease 命令

增: grant, 删: revoke, 改: keep-alive, 查: list, timetolive.

$ etcdctl lease grant  10
lease 2e128a4084e7269c granted with TTL(10s)
$ etcdctl lease revoke  2e128a4084e7269c
lease 2e128a4084e7269c revoked
$ etcdctl lease timetolive  2e128a4084e7269f
lease 2e128a4084e7269f already expired
$ etcdctl lease timetolive 2d8257079fa1bc0c --keys
# lease 2d8257079fa1bc0c granted with TTL(500s), remaining(472s), attached keys([foo2 foo1])
$ etcdctl lease list
32695410dcc0ca06
$ /etcdctl lease keep-alive 32695410dcc0ca0

lock 命令

$ etcdctl lock mylock
# mylock/1234534535445
$ etcdctl lock mylock1 --ttl 10
mylock1/2e128a4084e72b61
$ etcdctl lock mylock echo lock acquired
# lock acquired
$ etcdctl lock mylock ./etcdctl put foo bar
# OK

elect

candidate or observer

$ etcdctl elect president biden
president/2e128a4084e72b68
biden

$ etcdctl elect president -
president/2e128a4084e72b68
biden

$ etcdctl elect president trump

endpoint

查询 revision

etcdctl endpoint status

watch

etcdctl watch --rev=123 /foo

gremlin 操作 janusgraph 的基本语句

核心概念:

  1. Vertex, Edge, Property.
  2. Graph, Directed.
  3. 图数据库即是内存密集型也是CPU密集型.
  4. 图数据库做为其它数据库的一个补充.

建立本地连接

:remote connect tinkerpop.server conf/remote.yaml session
:remote console timeout none

打印 schema

mgmt = graph.openManagement()
mgmt.printSchema()
mgmt.printVertexLabels()
mgmt.printEdgeLabels()
mgmt.printPropertyKeys()
mgmt.printIndexes()

create schema & index

mgmt = graph.openManagement()
batch = mgmt.makeVertexLabel('batch').make()
date = mgmt.makePropertyKey('date').dataType(String.class).make()
hour = mgmt.makePropertyKey('hour').dataType(Integer.class).make()
mgmt.addProperties(batch, date, hour)

mgmt.buildIndex('indexBatchByDateHour', Vertex.class).addKey(date).addKey(hour).buildCompositeIndex()

# 只在这个 Vertex 上的 index
mgmt.buildIndex('indexBatchByDateHour', Vertex.class).addKey(date).addKey(hour).indexOnly(batch).unique().buildCompositeIndex()

mgmt.commit()
g.tx().commit();

对已有数据建立 index

graph.tx().rollback()
mgmt = graph.openManagement()
key = mgmt.getPropertyKey('key')
myIndex = mgmt.buildIndex('byKey', Vertex.class).addKey(key).unique().buildCompositeIndex()
mgmt.commit()
graph.tx().commit();

//Wait for the index to become available
ManagementSystem.awaitGraphIndexStatus(graph, 'byKey').call()


//Reindex the existing data
mgmt = graph.openManagement()
mgmt.updateIndex(mgmt.getGraphIndex("byKey"), SchemaAction.REINDEX).get()
mgmt.commit()

未结束的 transactions

graph.getOpenTransactions()
graph.getOpenTransactions().getAt(0).rollback()
graph.getOpenTransactions().getAt(0).commit()

查看 index

mgmt.getGraphIndexes(Vertex.class)
mgmt.getGraphIndexes(Edge.class)
mgmt.getGraphIndex("indexBatchByDateHour")

查看 console 信息

$> Gremlin.version()
$> :show imports

查看当前 Graph 支持的 features

$ graph.features()
$ graph.toString()

使用 tinkergraph

$ :plugin use tinkerpop.tinkergraph
$ graph = TinkerGraph.open()
$ g = graph.traversal()

注入 JavaScript on chrome 网页

为什么需要这个功能

有时候, 我们不能更改一个别人做的网页, 但是发现这个页面有些方面不是自己习惯的, 或者它给它添加某些功能, 那么我们可能需要通过页面 注入JavaScript的方式去修改或增强它.

如何做

你可以在每次打开这个页面的时候, 运行一段 JavaScript脚本. 但是这样需要每次都手工打开 Inspect 然后打开console 然后运行脚本.

如果我们可以让它每次打开这页面的时候, 自动执行这个脚本, 岂不是更棒.

所以, 我们可以通过写一个 chrome extension 的方式, 让它自动注入这些脚本.

并且我们可以定义很多自定义脚本, 每当打开某些网页的时候, 自动注入.

最简单的例子

一共4步, 每一步都很简单.

1. 创建一个文件夹

mkdir myext

2. 创建 manifest.json

在上面的文件夹中创建文件 manifest.json, 内容如下:

{
  "manifest_version": 3,
  "name": "My Extension",
  "version": "1.0",
  "permissions": [
    "tabs"
  ],
  "host_permissions": [
    "https://www.google.com/"
  ],
  "content_scripts": [
    {
      "matches": [
        "https://www.google.com/"
      ],
      "js": [
        "google.js"
      ]
    }
  ]
}

3. 创建需要注入的脚本

在上面的文件夹中创建需要注入的脚本文件, 这里跟上面 content_scripts.js里面的保持一致就好.

console.log("running on google.com");

document.querySelector("textarea").value = "llama";
document.querySelector("input[value='Google Search']").click();

4. 加载新插件

打开 chrome 的 extension 管理界面, 然后在页面右上角打开: 开发者模式, 然后选择 加载未打包 的extension, 加载完成后测试即可.

Ubuntu 编译 Linux 内核并从新内核启动

本文在一个 VirtualBox 的 Ubuntu VM 里编译新的Linux Kernel, 然后安装新kernel和模块, 然后更改系统从新内核启动.

下载 Linux 内核

可以选择从各种发布版本下载, 也可以选择从 github.com 下载. 下面选择从 Ubuntu 下载

$ git clone --depth 1 -b master git://git.launchpad.net/~ubuntu-kernel/ubuntu/+source/linux/+git/jammy

编译

要编译内核, 先安装必要的工具:

$ sudo apt update 
$ sudo apt upgrade -y 
$ sudo apt install git fakeroot build-essential ncurses-dev xz-utils libssl-dev bc flex bison libelf-dev -y

然后编译

$ cd jammy # 进入文件夹
$ make help  # 查看 make 命令
$ make menuconfig # 更改配置, 并保存
$ ls -lah .config # 查看 .config 文件

$ make all -j 8

Kernel: arch/x86/boot/bzImage is ready  (#1)

$ ls arch/x86/boot/bzImage # 查看 bzImage
-rw-rw-r-- 1 supra supra 11M Jul  1 11:40 arch/x86/boot/bzImage

$ find . -name *.ko # 查看新生成的模块

安装模块前 查看 /lib/modules/ 文件夹

$ ls -lah /lib/modules/
drwxr-xr-x  3 root root 4.0K Jul  1 10:40 .
drwxr-xr-x 88 root root 4.0K Jul  1 11:07 ..
drwxr-xr-x  5 root root 4.0K Jul  1 10:40 5.15.0-76-generic

安装之前查看 /boot/ 目录

$ uname -r # 查看当前运行的 kernel 版本
5.15.0-76-generic

$ ls -lah /boot/
drwxr-xr-x  4 root root 4.0K Jul  1 11:07 .
drwxr-xr-x 19 root root 4.0K Jul  1 10:45 ..
-rw-------  1 root root 6.0M Jun 15 17:47 System.map-5.15.0-76-generic
-rw-r--r--  1 root root 256K Jun 15 17:47 config-5.15.0-76-generic
drwxr-xr-x  5 root root 4.0K Jul  1 10:40 grub
lrwxrwxrwx  1 root root   28 Jul  1 10:40 initrd.img -> initrd.img-5.15.0-76-generic
-rw-r--r--  1 root root 106M Jul  1 11:07 initrd.img-5.15.0-76-generic
lrwxrwxrwx  1 root root   28 Jul  1 10:40 initrd.img.old -> initrd.img-5.15.0-76-generic
drwx------  2 root root  16K Jul  1 10:37 lost+found
lrwxrwxrwx  1 root root   25 Jul  1 10:40 vmlinuz -> vmlinuz-5.15.0-76-generic
-rw-------  1 root root  12M Jun 15 18:21 vmlinuz-5.15.0-76-generic
lrwxrwxrwx  1 root root   25 Jul  1 10:40 vmlinuz.old -> vmlinuz-5.15.0-76-generic

安装 kernel 模块和 kernel image

安装 kernel 模块

$ sudo make INSTALL_MOD_STRIP=1 modules_install

$ ls -lah /lib/modules/ # 可以看到多了一个目录, 我们新编译安装的版本
drwxr-xr-x  4 root root 4.0K Jul  1 11:56 .
drwxr-xr-x 88 root root 4.0K Jul  1 11:07 ..
drwxr-xr-x  5 root root 4.0K Jul  1 10:40 5.15.0-76-generic
drwxr-xr-x  3 root root 4.0K Jul  1 11:58 5.15.92

安装新 kernel image

$ sudo make install

$ ls -lah /boot/ #查看 /boot/ 文件夹, 看到多了一个版本的一份: 5.15.92
drwxr-xr-x  4 root root 4.0K Jul  1 12:00 .
drwxr-xr-x 19 root root 4.0K Jul  1 10:45 ..
-rw-------  1 root root 6.0M Jun 15 17:47 System.map-5.15.0-76-generic
-rw-r--r--  1 root root 5.6M Jul  1 11:59 System.map-5.15.92
-rw-r--r--  1 root root 256K Jun 15 17:47 config-5.15.0-76-generic
-rw-r--r--  1 root root 184K Jul  1 11:59 config-5.15.92
drwxr-xr-x  5 root root 4.0K Jul  1 12:00 grub
lrwxrwxrwx  1 root root   18 Jul  1 12:00 initrd.img -> initrd.img-5.15.92
-rw-r--r--  1 root root 106M Jul  1 11:07 initrd.img-5.15.0-76-generic
-rw-r--r--  1 root root  99M Jul  1 12:00 initrd.img-5.15.92
lrwxrwxrwx  1 root root   28 Jul  1 10:40 initrd.img.old -> initrd.img-5.15.0-76-generic
drwx------  2 root root  16K Jul  1 10:37 lost+found
lrwxrwxrwx  1 root root   15 Jul  1 11:59 vmlinuz -> vmlinuz-5.15.92
-rw-------  1 root root  12M Jun 15 18:21 vmlinuz-5.15.0-76-generic
-rw-r--r--  1 root root  11M Jul  1 11:59 vmlinuz-5.15.92
lrwxrwxrwx  1 root root   25 Jul  1 10:40 vmlinuz.old -> vmlinuz-5.15.0-76-generic

更新 grub

系统启动整体步骤如下:
BIOS -> GRUB -> vmlinuz -> initrd -> rootfs.

grub 核心部分

安装新 Kernel 之后, 要更新 GRUB 的配置. GRUB 的文件都在 /boot/grub/ 目录, 配置文件: /boot/grub/grub.cfg, kernel 模块和GRUB的 image 都在 /boot/grub/i386-pc 目录.

$ find /boot/grub/ -name *.img
/boot/grub/i386-pc/core.img
/boot/grub/i386-pc/boot.img

$ find /boot/grub/ -name *.mod

grub 配置

查看 /boot/grub/grub.cfg 我们可以看到我们新安装的 kernel 版本 5.15.92 已经在里面某个 menuentry 了, 这是在上面 make install 更新的.

$ cat /boot/grub/grub.cfg
    menuentry 'Ubuntu, with Linux 5.15.92' --class ubuntu --class gnu-linux --class gnu --class os $menuentry_id_option 'gnulinux-5.15.92-advanced-84148117-53b4-4df1-bd78-8dd5d40c41da' {
        recordfail
        load_video
        gfxmode $linux_gfx_mode
        insmod gzio
        if [ x$grub_platform = xxen ]; then insmod xzio; insmod lzopio; fi
        insmod part_gpt
        insmod ext2
        set root='hd0,gpt2'
        if [ x$feature_platform_search_hint = xy ]; then
          search --no-floppy --fs-uuid --set=root --hint-bios=hd0,gpt2 --hint-efi=hd0,gpt2 --hint-baremetal=ahci0,gpt2  bea48426-830f-4bb9-9c08-f537663c02e6
        else
          search --no-floppy --fs-uuid --set=root bea48426-830f-4bb9-9c08-f537663c02e6
        fi
        echo    'Loading Linux 5.15.92 ...'
        linux    /vmlinuz-5.15.92 root=/dev/mapper/ubuntu--vg-ubuntu--lv ro
        echo    'Loading initial ramdisk ...'
        initrd    /initrd.img-5.15.92

修改 grub 启动项

稍后如果我们重启, 就能在启动的时候选择我们新版本的kernel 了, 但是重启之前, 需要修改配置文件/etc/default/grub. 把 GRUB_TIMEOUT_STYLE=hidden 注释掉, 把 GRUB_TIMEOUT改成50秒.

  GRUB_DEFAULT=0
  # GRUB_TIMEOUT_STYLE=hidden
  GRUB_TIMEOUT=50

更新上面的配置文件后, 需要运行下面的命令更新 GRUB:

$ sudo update-grub
Sourcing file `/etc/default/grub'
Sourcing file `/etc/default/grub.d/init-select.cfg'
Generating grub configuration file ...
Found linux image: /boot/vmlinuz-5.15.92
Found initrd image: /boot/initrd.img-5.15.92
Found linux image: /boot/vmlinuz-5.15.0-76-generic
Found initrd image: /boot/initrd.img-5.15.0-76-generic
Warning: os-prober will not be executed to detect other bootable partitions.
Systems on them will not be added to the GRUB boot configuration.
Check GRUB_DISABLE_OS_PROBER documentation entry.
done

最后重启:

$ sudo reboot

验证新kernel 版本

回到 VirtualBox 界面, 我们可以看到启动的界面, 可以选择 Advanced 菜单, 然后可以看到选择 kernel 界面:
grub.png

其中每一项代表 /boot/grub/grub.cfg的一个 menuentry. 选择我们的新kernel 然后启动.