L B T

记 录 过 去 的 经 验

环境信息

  • Centos 7

查看内核已加载的模块

使用 lsmod 命令列出所有已加载的内核模块,输出中包含: 模块名称(Name)大小(Size)何处被使用(Used by)

# lsmod 
Module Size Used by
softdog 12288 0
xt_MASQUERADE 16384 2
nf_conntrack_netlink 53248 0
nfnetlink 20480 2 nf_conntrack_netlink
iptable_nat 12288 1
nf_nat 57344 2 iptable_nat,xt_MASQUERADE
xt_addrtype 12288 2
br_netfilter 32768 0
overlay 180224 0
ipt_REJECT 12288 2
nf_reject_ipv4 16384 1 ipt_REJECT
xt_comment 12288 7
xt_multiport 16384 2
xt_conntrack 12288 10
nf_conntrack 188416 4 xt_conntrack,nf_nat,nf_conntrack_netlink,xt_MASQUERADE
nf_defrag_ipv6 24576 1 nf_conntrack
nf_defrag_ipv4 12288 1 nf_conntrack

查看内核模块的详细信息

要查看已加载的内核模块的详细信息,可以使用 modinfo 命令,不是所有的模块都有详细的描述信息,如果没有,则无任何返回

# modinfo -d ena
Elastic Network Adapter (ENA)

# modinfo -d lp

# modinfo -n ena
/lib/modules/6.8.0-1015-aws/kernel/drivers/net/ethernet/amazon/ena/ena.ko

modinfo 命令常用选项:

选项 说明 示例
-a, --author 打印模块的作者
-d, --description 打印模块的描述信息
-p, --parameters 打印模块的 ‘parm’
-n, --filename Print only ‘filename’

加载内核模块

insmod

使用 insmod 命令加载内核模块. 模块需要完整后缀(如果有)

insmod simple.ko

modprobe

使用 modprobe 命令加载内核模块. 模块不需要完整后缀(如果有) 。临时加载,重启后会消失。

# modprobe parport

卸载内核模块

使用 rmmod 命令卸载内核模块。无需后缀,只需要给定模块名

rmmod simple

也可以使用命令 modprobe -r 移除模块,它不仅会移除指定的模块,还会移除未被继续使用的依赖的模块

环境信息

  • Centos 7

常用目录和文件说明

etc 目录常用文件说明

文件路径 说明 示例
/etc/motd 登录成功后的欢迎信息,ssh 登录和 console 登录成功后都会显示
/etc/issue 在登录系统输入用户名之前显示的信息,远程 ssh 连接的时候并不会显示此信息 说明示例
/etc/services 记录网络服务名和它们对应使用的端口号及协议
/etc/protocols 该文件是网络协议定义文件,里面记录了 TCP/IP 协议族的所有协议类型。文件中的每一行对应一个协议类型,它有3个字段,分别表示 协议名称协议号协议别名
/etc/vimrc
~/.vimrc
vim 启动时会读取 /etc/vimrc(全局配置) 和 ~/.vimrc (用户配置) vim
/etc/passwd
/etc/shadow
/etc/group
用户数据库,其中记录了 用户名id用户家目录shell
用户密码文件
组信息
/etc/fstab 系统启动时需要自动挂载的文件系统列表
/etc/mtab 当前系统已挂载的文件系统,并由 mount 命令自动更新。当需要当前挂载的文件系统的列表时使用(例如df命令)
/etc/shells 系统可使用的 shell
/etc/filesystems 系统可使用的 文件系统
/etc/hostname 存放这主机名
/etc/hosts 主机名查询静态表,域名和 ip 本地静态表
/etc/nsswitch.conf 它规定通过哪些途径以及按照什么顺序以及通过这些途径来查找特定类型的信息,还可以指定某个方法奏效或失效时系统将采取什么动作 hosts: files dns myhostname
此配置设定:在查找域名解析的时候,先查找本地 /etc/hosts,再发送给 DNS 服务器查询
/etc/rsyslog.conf rsyslog 服务的配置文件,用来托管其他服务的日志 linux rsyslog 服务
/etc/logrotate.conf linux 日志切割工具 linux logrotate 服务
/etc/rsyncd.conf rsync 服务的配置文件 rsyncd 服务
/etc/sysctl.conf
/etc/sysctl.d/
内核的运行参数配置文件,sysctl 命令对内核参数的修改仅在当前生效,重启系统后参数丢失,如果希望参数永久生效可以修改此配置文件 Linux 常用内核参数说明
阅读全文 »

在 Python 中,set 是一种无序的、可变的集合数据类型,用于存储唯一的元素。它主要用于快速去重和集合运算(如交集、并集、差集等)。

环境信息

  • Python 3

set 基本操作

创建集合

可以使用花括号 {}set() 函数来创建集合。

  • 使用花括号创建集合

    # 创建一个包含一些元素的集合
    my_set = {1, 2, 3, 4, 5}
    print(my_set) # 输出: {1, 2, 3, 4, 5}
  • 使用 set() 函数创建集合

    # 使用 set() 函数从一个可迭代对象创建集合
    my_set = set([1, 2, 3, 4, 5])
    print(my_set) # 输出: {1, 2, 3, 4, 5}

    # 创建一个空集合
    empty_set = set()
    print(empty_set) # 输出: set()

集合的基本操作

添加元素

使用 add() 方法向集合添加单个元素。

my_set = {1, 2, 3}
my_set.add(4)
print(my_set) # 输出: {1, 2, 3, 4}

移除元素

使用 remove() 方法移除集合中的指定元素,如果元素不存在会引发 KeyError。使用 discard() 方法移除元素,如果元素不存在不会引发异常。

my_set = {1, 2, 3}
my_set.remove(2)
print(my_set) # 输出: {1, 3}

my_set.discard(3)
print(my_set) # 输出: {1}

# remove 不存在的元素会引发 KeyError
# my_set.remove(4) # KeyError: 4

# discard 不存在的元素不会引发异常
my_set.discard(4)
print(my_set) # 输出: {1}

清空集合

使用 clear() 方法清空集合中的所有元素。

my_set = {1, 2, 3}
my_set.clear()
print(my_set) # 输出: set()

集合的长度

使用 len() 函数获取集合中元素的个数。

my_set = {1, 2, 3}
print(len(my_set)) # 输出: 3

集合运算

并集

使用 | 运算符或 union() 方法。

set1 = {1, 2, 3}
set2 = {3, 4, 5}

# 使用 | 运算符
print(set1 | set2) # 输出: {1, 2, 3, 4, 5}

# 使用 union() 方法
print(set1.union(set2)) # 输出: {1, 2, 3, 4, 5}

阅读全文 »

环境信息

  • Python3

字符串转换为变量名

locals() 方法

>>> str1 = 666
>>> a = 'str1'
>>>
>>>
>>> locals()[a]
666

vars() 方法


>>>
>>> str1 = 666
>>> a = 'str1'
>>>
>>>
>>>
>>> vars()[a]
666
>>>

eval() 方法

>>> str1 = 666
>>> a = 'str1'
>>>
>>>
>>> eval(a)
666

yield

yield 指令和 return 相似,都是用来在函数中返回。使用 yield 关键字创建生成器函数时,生成器函数并不会在传统意义上 返回。相反,生成器函数在遇到 yield 语句时会暂停其执行,并返回一个值给调用者。生成器函数的状态会被保存,因此在下一次调用时,它可以从暂停的地方继续执行。

yield 指令 将函数转换成生成器(Generator,它在函数中产生一个值,然后暂停函数并保存其状态(下一次调用函数会从此状态恢复执行),再次恢复执行时再生成(返回)yield 的值。

每次调用生成器函数时,并不会立即执行,会创建一个新的生成器对象。

第一次使用 next() 或在 for 循环中开始迭代生成器时,生成器函数开始执行,直到遇到第一个 yield 语句。yield 会暂停生成器函数的执行,并将一个值返回给调用者。再次调用 next() 或继续迭代(for)时,生成器函数从上次暂停的 yield 处继续执行,直到遇到下一个 yield 语句或执行结束。

生成器函数在没有 yield 语句时结束执行,相当于隐式地在最后一个 yield 语句之后遇到 return

当生成器函数结束时,进一步调用 next() 会引发 StopIteration 异常,表示生成器中的值已被全部生成。

yield 有以下优点:

  • 能够以更高效的方式处理大量数据,因为它不需要一次性将所有数据存储在内存中。通过减少内存消耗,提高程序性能。
  • 它提供了一种新的方法来控制函数的执行流程,使得函数可以在任意点暂停和恢复。生成器函数在被暂停后(遇到 yield)不会继续执行,直到再次调用 next() 或通过迭代器进行迭代(for)。

yield 读取大文本数据

在处理大文本数据(如超过 10G)时,如果一次性读取所有文本内容,在可用内存较小的情况下可能出现内存不足导致程序执行失败。这时候可以考虑使用 yield 来批量加载数据。

定义如下函数读取文件内容:

def read_large_file(file_path):
"""
Generator function to read a large file line by line.
"""
with open(file_path, 'r') as file:
for line in file:
yield line

使用以下方法使用大文本中的数据

  1. next 方法。调用生成器函数(read_large_file),会返回一个 Generator 对象,通过 next() 方法会迭代调用生成器的下一个值(yield 表达式的值)
    file_path = 'large_file.txt'

    # next 方法: 首先
    line = read_large_file(file_path)

    next(line) # 返回第一行
    next(line) # 返回第二行,以此类推可以读取所有行

  2. for 循环。调用生成器函数返回一个生成器对象,这个对象实现了迭代器协议。
    def read_large_file(file_path):
    """
    Generator function to read a large file line by line.
    """
    with open(file_path, 'r') as file:
    for line in file:
    yield line

    # Usage example
    file_path = 'large_file.txt'
    for line in read_large_file(file_path):
    print(line.strip())

分批读取大文件中的数据

在处理大文件的过程中,如果需要批量多行读取文件内容,参考以下代码

def read_file_in_chunks(file_path, chunk_size=1024):
"""
Generator function to read a file in chunks.
"""
with open(file_path, 'r') as file:
while True:
chunk = file.readlines(chunk_size)
if not chunk:
break
for line in chunk:
yield line

# Usage example
file_path = 'large_file.txt'
for line in read_file_in_chunks(file_path):
print(line.strip())

enumerate

enumerate 是 Python 内置函数之一,用于遍历可迭代对象(如列表、元组或字符串)时获取元素和对应的索引。

语法:

enumerate(iterable, start=0)
  • iterable : 任何可迭代对象(如列表、字符串、元组、文件对象等)。
  • start : 索引的起始值,默认为 0。如果要让索引号从 1 开始,配置 start=1
# 示例列表
>>> fruits = ['apple', 'banana', 'cherry']

# 使用 enumerate 获取元素及其索引
>>> for index, fruit in enumerate(fruits):
... print(index, fruit)
...
0 apple
1 banana
2 cherry
>>>


# 使用 enumerate 获取元素及其索引,并将起始索引改为 1
>>> for index, fruit in enumerate(fruits, start=1):
... print(index, fruit)
...
1 apple
2 banana
3 cherry

# 使用 enumerate 获取文件中的行号及其内容
>>> f = open('temp_file')
>>> for line_number, line in enumerate(f):
... print(line_number, line)
...
0 85d37fac5cc284914b5d6f79982942b8/Y1iY3k1U.ts

1 85d37fac5cc284914b5d6f79982942b8/Y1x0V8Rc.ts

2 85d37fac5cc284914b5d6f79982942b8/Y22fhGiC.ts

3 85d37fac5cc284914b5d6f79982942b8/Y3p95oau.ts

常见错误

NameError: name ‘null‘ is not defined

使用 evalstring 转化成 dict 时出错,经过排查,发现 string 数据中包含 null,在转换时就会报错: NameError: name ‘null‘ is not defined

解决方法

使用 json 进行转换

try:
response_dict = eval(response)
except NameError:
response_dict = json.loads(str(response.decode()))

  • 蛙化及蛇化

  • 蛙化現象 是日本 2023 年上半年的 Z 世代(出生介於1995年~2010年)流行用語第一名。這個詞源自格林童話《青蛙王子》,描述對另一半突然感到生理或心理上厭惡。

    日本大學教授藤澤伸介在2004年的研究指出,「蛙化現象」是一種普遍狀態,尤其容易發生在情竇初開的青少年身上,因為戀愛經驗少,對感情對象抱持完美的想像。

與蛙化現象相對,近期有對情侶在TikTok發明「蛇化現象」,描述無論另一半做了什麼尷尬行為,都感到好可愛。這種現象迅速散播,成為日本Z世代流行用語。

  • 于高山之巅,方见大河奔涌;于群峰之上,更觉长风浩荡

  • 你永远不可能真正去了解一个人,除非你穿过她的鞋子去走她走过的路,站在她的角度思考问题,可当你走过她走的路时,你连路过都觉得难过。

  • 当一个人因为和你无关的事情而生气并向你抱怨和展示自己的生气和愤怒时,你最好不要对涉及到的人或事发表任何意见,千万不要对涉及到的人或事发表任何意见千万不要对涉及到的人或事发表任何意见,你最好 当个听客,闭紧嘴巴,不然很可能引火烧身。

  • 当你只能孤注一掷的时候,你只能孤注一掷。如果你犹豫不决,说明你其实还有办法,只是不愿意使用。

  • 对一个人好是一件太过笼统的说法,没法测量,如需测量,可以将这个说法进行分解,比如分解为:
    对一个人好 = 能为他着想 + 站在他的角度考虑

状态标识 状态名称 状态说明 示例
R task_running 进程处于运行或就绪状态
S task_interruptible
sleeping
可中断的睡眠状态
D task_uninterruptible 不可中断的睡眠状态
1. 它是一种睡眠状态,意味着处于此状态的进程不会消耗 CPU
2. 睡眠的原因是等待某些资源(比如锁或者磁盘 IO),这也是非常多 D 状态的进程都处在处理 IO 操作的原因
3. 是它不能被中断,这个要区别于 硬件中断 的中断,是指不希望在其获取到资源或者超时前被终止。因此他不会被信号唤醒,也就不会响应 kill -9 这类信号。这也是它跟 S(可中断睡眠)状态的区别
T task_stopped
task_traced
Traced
暂停状态或跟踪状态
Z task_dead
exit_zombie
zombie
退出状态,进程成为僵尸进程
X task_dead
exit_dead
退出状态,进程即将被销毁
I idle 空闲状态

进程命令名和进程可执行文件名

在系统中遇到以下进程:

# ps -elf | grep 18686
5 S root 18686 1239 0 80 0 - 46620 pipe_w 15:50 ? 00:00:00 /usr/sbin/CROND -n
0 R root 18694 18686 7 80 0 - 610547 - 15:50 ? 00:00:02 /usr/local/php73/bin/php /home/www/admin/artisan PullData
0 S root 18754 18686 0 80 0 - 22453 pipe_w 15:50 ? 00:00:00 /usr/sbin/sendmail -FCronDaemon -i -odi -oem -oi -t -f root

其中 PID 为 18686 的进程名为 /usr/sbin/CROND,其启动了另外两个子进程。但是在系统中检查,并不存在路径 /usr/sbin/CROND

# ls -l /usr/sbin/CROND
ls: cannot access /usr/sbin/CROND: No such file or directory

出现此种现象,主要是因为 在启动时,进程的命令名是根据路径传递给 execve() 函数的参数决定的,而不是直接与系统中的文件进行匹配

在 Linux 系统中,ps 命令显示的进程信息是从 /proc 文件系统中获取的,而 /proc 文件系统包含有关正在运行的进程的信息,包括每个进程的命令名。因此,即使实际上系统中不存在 /usr/sbin/CROND 文件,但如果进程的命令名是 /usr/sbin/CROND,那么 ps 命令仍然会显示进程的命令名为 /usr/sbin/CROND

进程的命令名可以查看 /proc/<PID>/cmdline 文件,本示例中显示如下:

# cat /proc/18686/cmdline 
/usr/sbin/CROND-n

对应的系统上的可执行文件的名称可以查看 /proc/<PID>/stat/proc/<PID>/comm/proc/<PID>/status 等文件

# cat /proc/900/comm 
crond

# cat /proc/900/status
Name: crond
Umask: 0022
State: S (sleeping)
Tgid: 900
Ngid: 0
Pid: 900
PPid: 1239
TracerPid: 0

# cat /proc/900/stat
900 (crond) S 1239 1239 1239 0 -1 4202816 1627 0 0 0 0 0 0 0 20 0 1 0 139129633 190955520 1478 18446744073709551615 94685936058368 94685936118156 140733000396032 140733000262488 140427856103840 0 0 4096 16387 18446744071797306256 0 0 17 3 0 0 0 0 0 94685938219080 94685938221648 94685948321792 140733000400770 140733000400789 140733000400789 140733000400872 0

在本示例中,实际执行的命令为 crond

进程状态查看

top 命令

使用 top 命令可以查看系统负载、CPU 和 内存使用情况。也可以查看单个进程的具体信息。

top 命令常用选项

选项 说明 示例
-H Threads Mode,线程模式。默认情况 top 展示进程的简要信息,使用此选项显示进程中的线程状态。
对应交互式命令 H
  • 显示单个进程的(线程)详细信息
    # top -H -p 1423
    top - 09:44:42 up 54 days, 23:53, 2 users, load average: 8.82, 6.84, 7.21
    Threads: 15 total, 0 running, 15 sleeping, 0 stopped, 0 zombie
    %Cpu(s): 40.9 us, 10.8 sy, 0.0 ni, 48.1 id, 0.0 wa, 0.0 hi, 0.2 si, 0.0 st
    KiB Mem : 15790488 total, 466056 free, 7761544 used, 7562888 buff/cache
    KiB Swap: 0 total, 0 free, 0 used. 3895716 avail Mem

    PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
    1423 root 20 0 1477368 778788 4260 S 39.5 4.9 11999:41 [watchdog:1:6]
    2572 root 20 0 1477368 778788 4260 S 37.9 4.9 11363:48 [watchdog:1:6]
    1436 root 20 0 1477368 778788 4260 S 34.2 4.9 11286:08 [watchdog:1:6]
    1435 root 20 0 1477368 778788 4260 S 33.9 4.9 12059:53 [watchdog:1:6]
    1434 root 20 0 1477368 778788 4260 S 33.2 4.9 10249:00 [watchdog:1:6]
    1437 root 20 0 1477368 778788 4260 S 30.6 4.9 11717:47 [watchdog:1:6]
    1431 root 20 0 1477368 778788 4260 S 28.9 4.9 11222:06 [watchdog:1:6]
    21378 root 20 0 1477368 778788 4260 S 27.6 4.9 12143:35 [watchdog:1:6]
    1433 root 20 0 1477368 778788 4260 S 17.6 4.9 8738:21 [watchdog:1:6]
    1428 root 20 0 1477368 778788 4260 S 8.0 4.9 7650:56 [watchdog:1:6]
    1429 root 20 0 1477368 778788 4260 S 0.0 4.9 0:00.04 [watchdog:1:6]
    1430 root 20 0 1477368 778788 4260 S 0.0 4.9 0:00.05 [watchdog:1:6]
    1432 root 20 0 1477368 778788 4260 S 0.0 4.9 0:00.03 [watchdog:1:6]
    1438 root 20 0 1477368 778788 4260 S 0.0 4.9 12260:30 [watchdog:1:6]
    1529 root 20 0 1477368 778788 4260 S 0.0 4.9 11068:39 [watchdog:1:6]

参考文档

Linux进程状态说明

环境信息

  • ansible-core 2.16
  • Docker image python:3.12.3
Ansible 安装部署参考 Ansible playbook 使用介绍

Ansible 使用 Jinja2 模板语言对变量或者 Facts 进行模板化。 [1]

模板数据处理

Filters

使用 Filters 可以进行数据转换(如 JSON –> YAML)、URL 分割等操作。 [2]

为变量提供默认值

在模板中使用的变量未定义的情况下,可能会导致 Ansible 处理失败,为了以更优雅的方式处理此类问题,可以在模板中为变量提供 默认值

{{ some_variable | default(5) }}

也可以在变量计算值为空或者 false 时使用默认值

{{ lookup('env', 'MY_USER') | default('admin', true) }}

配置变量为可选变量

默认情况下,Ansible Template 中所有的变量都必须有值,否则会抛出异常。假如需要在模板中的部分变量没有值或未定义的情况下也可以正常部署,可以将其配置为 可选(optional)

要将变量配置为 **可选(optional)**,可以将其 默认值(default value) 设置为特殊变量 omit

- name: Touch files with an optional mode
ansible.builtin.file:
dest: "{{ item.path }}"
state: touch
mode: "{{ item.mode | default(omit) }}"
loop:
- path: /tmp/foo
- path: /tmp/bar
- path: /tmp/baz
mode: "0444"

变量类型

如果需要对变量类型进行转换,可以参考以下方法

获取变量类型

2.3 以上版本中,可以使用 type_debug 显示变量类型

{{ myvar | type_debug }}

字典转换为列表

New in version 2.6.

{{ dict | dict2items }}

原始字典数据:

tags:
Application: payment
Environment: dev

使用 {{ dict | dict2items }} 转换后的列表数据:

- key: Application
value: payment
- key: Environment
value: dev

转换后的列表默认以关键字 key 指示之前的字典中的 key 值,以关键字 value 指示之前的字典中的 value 值。如果想要自定义 key 名称,dict2items 接受关键字参数 key_namevalue_name

# Dictionary data (before applying the ansible.builtin.dict2items filter):
files:
users: /etc/passwd
groups: /etc/group

# applying the ansible.builtin.dict2items filter
{{ files | dict2items(key_name='file', value_name='path') }}

# List data (after applying the ansible.builtin.dict2items filter):
- file: users
path: /etc/passwd
- file: groups
path: /etc/group

列表转换为字典

{{ tags | items2dict }}

List data (before applying the ansible.builtin.items2dict filter):

tags:
- key: Application
value: payment
- key: Environment
value: dev

Dictionary data (after applying the ansible.builtin.items2dict filter):

Application: payment
Environment: dev

假如 List Data 中的关键字不是 keyvalue,此时必须使用参数 key_namevalue_name 指定

{{ fruits | items2dict(key_name='fruit', value_name='color') }}

强制类型转换

使用以下语法强制转换变量数据类型 [5]

some_string_value | bool

ansible_facts['os_family'] == "RedHat" and ansible_facts['lsb']['major_release'] | int

YAML 和 JSON 数据转换

可以使用以下语法将数据转换为 JSON 或者 YAML 格式

{{ some_variable | to_json }}
{{ some_variable | to_yaml }}

可以使用以下语法将数据转换为方便人类阅读 JSON 或者 YAML 格式

{{ some_variable | to_nice_json }}
{{ some_variable | to_nice_yaml }}

制定行首缩进程度

{{ some_variable | to_nice_json(indent=2) }}
{{ some_variable | to_nice_yaml(indent=8) }}

参考链接

Templating (Jinja2)

脚注

环境信息

  • ansible-core 2.16
  • Docker image python:3.12.3
Ansible 安装部署参考 Ansible templates 使用介绍

Ansible Playbook 语法

Playbooks 使用 YAML 语法定义(描述)。一个 playbook 由一个或多个 play 依序组成。每个 play 运行一个或多个 tasks,每个 task 也成为一个 module

Ansible playbook 示例:

playbook.yml
---
- name: Update web servers
hosts: webservers
remote_user: root

tasks:
- name: Ensure apache is at the latest version
ansible.builtin.yum:
name: httpd
state: latest

- name: Write the apache config file
ansible.builtin.template:
src: /srv/httpd.j2
dest: /etc/httpd.conf

- name: Update db servers
hosts: databases
remote_user: root
vars:
port: 8080

tasks:
- name: Ensure postgresql is at the latest version
ansible.builtin.yum:
name: postgresql
state: latest

- name: Ensure that postgresql is started
ansible.builtin.service:
name: postgresql
state: started

---
- name: Install multiple packages
hosts: webservers
tasks:
- name: Install packages
apt:
name: "{{ item }}"
state: present
loop:
- nginx
- git
- curl

一个 Ansible playbook 由一个或多个 plays 组成,每个 play 包含以下部分:

  • name : 描述性的名称
  • hosts : 指定目标主机
  • become : 提升权限(默认是使用 sudo 提升到 root 用户)
  • remote_user : 用于连接到远程主机的账户。(如果 Inventory 中定义了远程连接的用户,会覆盖此处的配置)
  • tasks : 要执行的一系列任务列表
  • vars : 用于定义变量,便于管理和重用
  • gather_facts : 收集 Facts, 默认值为 yes

tasks 是一个任务列表,每个任务执行特定的操作。任务包含以下元素:

  • name : 描述任务的目的。
  • module_name : Ansible 模块名称,如 aptservice 等。
  • module_options : 模块的参数,以键值对的形式提供。
  • when : 条件语句,控制任务是否执行。
  • loop : 循环执行任务

执行以下命令运行 playbook.yml

ansible-playbook playbook.yml -f 10

常用选项说明

选项 说明 示例
-f
--forks
指定并发执行的数量,默认为 5
-v
--verbose
-vvvvvv
打印 debug 信息,详细程度从 -v-vvvvvv
-C
--check
Check mode,不执行任何实际操作,而是对要执行的操作进行验证
-D
--diff
- 只使用 --diff 会执行 play 定义的实际操作,并对所有受影响的文件或者模板显示其变更前后的具体差异
- --check 一起使用,不会执行 play 定义的实际操作,只显示变更前后的差异,可以在实际执行前,调试/预览将要进行的变更,防止意外配置变更或文件修改
主要用于文件或者模板的变更,对于其他类型的任务(如包安装、服务管理、修改主机名等),不会显示具体的差异( 配合 --check 使用时,结果会显示为 skipping,实际执行时结果为 changed )。
--list-hosts 不执行任何实际操作,只列出符合 pattern 的目标主机
--list-tasks 不执行任何实际操作,只列出将要执行的 task
--syntax-check 不执行任何实际操作,只检查 playbook 文件是否有语法错误

when 语句

在 Ansible playbook 中,when 关键字用于条件执行任务。它允许你根据特定的条件来决定是否执行某个任务。这个功能非常强大,可以帮助你在不同的主机、不同的环境或不同的配置下灵活地执行任务。

when 表达式基于 Jinja2 模板语言,其中的变量主要来自: [1]

变量优先级 参考说明

when 关键字后面跟随一个条件表达式,当条件为真时,任务会执行;当条件为假时,任务会被跳过。

tasks:
- name: Install nginx on Debian
apt:
name: nginx
state: present
when: ansible_facts['os_family'] == 'Debian'

when 关键字支持多种表达式,包括:

  • 简单条件

    • 基于变量 的条件:when: variable == 'value'

      - name: Install nginx only if nginx_install is true
      apt:
      name: nginx
      state: present
      when: nginx_install

      在这个示例中,nginx_install 是一个布尔变量。当 nginx_install 为真时,任务将执行。

    • 基于事实(facts) 的条件:when: ansible_facts['os_family'] == 'Debian'

      - name: Install nginx on Debian systems
      apt:
      name: nginx
      state: present
      when: ansible_facts['os_family'] == 'Debian'
  • 逻辑操作

    • 与操作when: condition1 and condition2
      - name: Install nginx on Debian systems
      apt:
      name: nginx
      state: present
      when: ansible_facts['os_family'] == 'Debian' or ansible_facts['os_family'] == 'Ubuntu'
      当使用多个条件时,也先当于 and 操作
      tasks:
      - name: Shut down CentOS 6 systems
      ansible.builtin.command: /sbin/shutdown -t now
      when:
      - ansible_facts['distribution'] == "CentOS"
      - ansible_facts['distribution_major_version'] == "6"
    • 或操作when: condition1 or condition2
    • 非操作when: not condition
  • 列表和字典操作

    • 列表包含when: 'item' in mylist
      - name: Ensure package is installed if it is in the list
      apt:
      name: "{{ item }}"
      state: present
      loop:
      - nginx
      - git
      when: item in packages_to_install
    • 字典键存在when: 'key' in mydict
      - name: Run only if the key 'run_task' is present in mydict and its value is true
      debug:
      msg: "Running task"
      when: mydict.get('run_task', False)

      这个任务仅在 mydict 中存在键 run_task 且其值为真时执行。

      when: mydict.get('run_task', False)False 为默认值,如果 mydict 字典中不存在 run_task 键,mydict.get('run_task', False) 将返回 False。这种用法确保了在键不存在时,条件判断不会抛出错误。

  • 复杂条件

    • 组合多个条件when: (condition1 and condition2) or condition3

以下是一个完整的示例

---
- name: Example playbook using when conditions
hosts: all
become: yes

vars:
nginx_install: true
packages_to_install:
- nginx
- git
mydict:
run_task: true

tasks:
- name: Install nginx only if nginx_install is true
apt:
name: nginx
state: present
when: nginx_install

- name: Install nginx on Debian or Ubuntu
apt:
name: nginx
state: present
when: ansible_facts['os_family'] == 'Debian' or ansible_facts['os_family'] == 'Ubuntu'

- name: Ensure package is installed if it is in the list
apt:
name: "{{ item }}"
state: present
loop: "{{ packages_to_install }}"
when: item in packages_to_install

- name: Run only if the key 'run_task' is present in mydict and its value is true
debug:
msg: "Running task"
when: mydict.get('run_task', False)

引用 Facts 变量值

Ansible-playbook 运行过程中,默认会收集目标主机的 Facts 信息。可以在 Playbook 定义中引用这些值 [2]

原始的 facts 信息可以通过 setup 模块获取

ansible <hostname> -m ansible.builtin.setup

要在 playbook 或者 template 中引用,可以参考以下方法:

{{ ansible_facts['devices']['xvda']['model'] }}

{{ ansible_facts['nodename'] }}

playbook 示例

修改主机名

以下示例展示修改主机名可使用的 playbook,主机名称修改为 Inventory 中主机的主机名(Alias)

假设 Inventory 文件内容如下:

/etc/ansible/inventory/hosts
test_target1:
hosts:
ansible-target-centos79-1:
ansible_host: ansible-target-centos79-1
ansible_user: root
ansible-target-centos79-2:
ansible_host: ansible-target-centos79-2
ansible_port: 22
ansible_user: root

Playbook 内容如下

change_hostname.yml
---
- name: Change hostname based on inventory alias
hosts: test_target1
become: yes
tasks:
- name: Set hostname from alias
hostname:
name: "{{ inventory_hostname }}"

- name: Update /etc/hosts file with new hostname
lineinfile:
path: /etc/hosts
regexp: '^(127\.0\.1\.1\s+).*'
line: "127.0.1.1 {{ inventory_hostname }}"
state: present
when: ansible_facts['distribution'] == 'Ubuntu'

- name: Update /etc/sysconfig/network with new hostname (CentOS/RedHat)
lineinfile:
path: /etc/sysconfig/network
regexp: '^HOSTNAME=.*'
line: "HOSTNAME={{ inventory_hostname }}"
state: present
when: ansible_facts['distribution'] in ['CentOS', 'RedHat']

- name: Set hostname using hostnamectl
command: "hostnamectl set-hostname {{ inventory_hostname }}"
when: ansible_facts['distribution'] in ['CentOS', 'RedHat', 'Ubuntu']

- name: Reboot the system to apply changes
reboot:
msg: "Rebooting to apply hostname changes"
pre_reboot_delay: 5
post_reboot_delay: 30
reboot_timeout: 300

相关模板变量说明

模板变量 说明 示例
inventory_hostname 引用 Inventory 中的主机名称(Alias) {{ inventory_hostname }}

相关模块使用参考

模块 参考链接 示例
hostname hostname
lineinfile lineinfile
reboot reboot
command command

参考链接

Ansible playbooks

脚注

Compose 项目是 Docker 官方的开源项目,负责实现对 Docker 容器集群 的快速编排。

Compose 定位是 「定义和运行多个 Docker 容器的应用(Defining and running multi-container Docker applications)」,其前身是开源项目 Fig

使用一个 Dockerfile 模板文件,可以让用户很方便的定义一个单独的应用容器。然而,在日常工作中,经常会碰到需要多个容器相互配合来完成某项任务的情况。例如要实现一个 Web 项目,除了 Web 服务容器本身,往往还需要再加上后端的数据库服务容器,甚至还包括负载均衡容器等。

Compose 恰好满足了这样的需求。它允许用户通过一个单独的 docker-compose.yml 模板文件(YAML 格式)来定义一组相关联的应用容器为一个项目(project)。
Compose 中有两个重要的概念:

  • 服务 (service) : 一个应用的容器,实际上可以包括若干运行相同镜像的容器实例。
  • 项目 (project) : 由一组关联的应用容器组成的一个完整业务单元,在 docker-compose.yml 文件中定义。

Compose 的默认管理对象是项目,通过子命令对项目中的一组容器进行便捷地生命周期管理。

Compose 目前分为 2 个大版本: [1]

  • Compose V1 : 目前已经不提供官方支持。使用 Python 编写,通过 docker-compose 命令来调用。Compose V1docker-compose.yml 最开始要包含 version 命令,取值范围 2.03.8
  • Compose V2 : 使用 Go 编写,通过 docker compose 命令来调用。Compose V2 忽略 docker-compose.yml 最开始的 version 指令。Compose V2 向后兼容 Compose V1 版本
阅读全文 »

常用工具安装

查找 netstat 命令由哪个安装包提供

$ yum whatprovides /bin/netstat
net-tools-2.0-0.25.20131004git.el7.x86_64 : Basic networking tools
Repo : base
Matched from:
Filename : /bin/netstat

安装 net-tools

yum install -y net-tools
apt-get install -y net-tools
阅读全文 »

环境信息

  • Centos 7
  • ansible-core 2.16
  • Docker image python:3.12.3

安装

ansible-core 版本及 Python 版本支持对应关系

ansible-core Version Control Node Python Target Python / PowerShell
2.16 Python 3.10 - 3.12 Python 2.7
Python 3.6 - 3.12
Powershell 3 - 5.1

为了环境部署方便灵活,可以选择使用 python:3.12.3 的 Docker 镜像,以其为基础环境安装 ansible-core 2.16 或者直接使用 ansible 镜像启动。

# docker run --rm -it python:3.12.3 bash

# cat /etc/os-release
PRETTY_NAME="Debian GNU/Linux 12 (bookworm)"
NAME="Debian GNU/Linux"
VERSION_ID="12"
VERSION="12 (bookworm)"
VERSION_CODENAME=bookworm
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"

# python --version
Python 3.12.3

# pip install ansible

# pip list
Package Version
------------ -------
ansible 9.5.1
ansible-core 2.16.6
cffi 1.16.0
cryptography 42.0.7
Jinja2 3.1.4
MarkupSafe 2.1.5
packaging 24.0
pip 24.0
pycparser 2.22
PyYAML 6.0.1
resolvelib 1.0.1
setuptools 69.5.1
wheel 0.43.0

# ansible --version
ansible [core 2.16.6]
config file = None
configured module search path = ['/root/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
ansible python module location = /usr/local/lib/python3.12/site-packages/ansible
ansible collection location = /root/.ansible/collections:/usr/share/ansible/collections
executable location = /usr/local/bin/ansible
python version = 3.12.3 (main, May 14 2024, 07:23:41) [GCC 12.2.0] (/usr/local/bin/python)
jinja version = 3.1.4
libyaml = True

Ansible 配置说明

Ansible 主配置文件为 /etc/ansible/ansible.cfg其中的配置都可以被 ansible-playbook 或者命令行参数覆盖

ansible 默认会读取环境变量 ANSIBLE_CONFIG 指定的配置文件,当前路径下的 ansible.cfg,以及用户家目录下的 .ansible.cfg,以及 /etc/ansible/ansible.cfg 作为配置文件,已第一个找到的为准

常用配置说明

配置项 说明 示例
inventory 指定 inventory (主机列表)文件的路径,默认为 /etc/ansible/hosts
remote_user (未指定用户时)连接远程主机时使用的用户
remote_port 连接远程主机时使用的(默认)端口
host_key_checking 默认启用。检查主机密钥可以防止服务器欺骗和中间人攻击。
如果主机重新安装并且在 know_hosts 中拥有不同的密钥,ansible 会提示确认密钥。
如果要禁用此行为,可以配置为 False
ask_pass 默认为 False。当设置为 True 时,ansible 要求输入远端服务器的密码,即使配置了免密登录
log_path 日志文件,默认 /var/log/ansible.log
pattern 当没有给出 pattern 时的默认 pattern,默认值是 * 即所有主机

配置示例

/etc/ansible/ansible.cfg
[defaults]
# 设置默认的 inventory 文件路径
inventory = /etc/ansible/hosts

# 关闭主机密钥检查,方便新主机的快速添加
host_key_checking = False

# 设置默认的远程用户
remote_user = ansible

Inventory 配置说明

默认的 inventory 配置文件路径为 /etc/ansible/hosts,主要用来配置 Managed Hosts 列表 [3]

在命令行中,可以使用选项 -i <path> 指定不同的 inventory 或者可以在 ansible 配置文件 ansible.cfg 中使用指令 inventory 指定 inventory 文件位置。

命令行中可以使用 -i <path1> -i <path2> ... 指定多个 inventory

inventory 文件支持多种格式,最常见的是 INIYAML 格式。

  • Ansible 默认创建了 2 个组:
    • all : 包含所有主机
    • ungrouped : 包含所有不在其他组(all 除外)中的所有主机。

      任何一个主机都会至少在 2 个组中,要么 all 和某个组中,要么 allungrouped

  • 一个主机可以包含在多个组中
  • parent/childchild 组被包含在 parent 组中。
    • INI 配置格式中,使用 :children 后缀配置 parent
    • YAML 配置格式中,使用 children: 配置 parent
      • 任何在 child 组中的主机自动成为 parent 组中的一员
      • 一个组可以包括多个 parentchild 组,但是不能形成循环关系
      • 一个主机可以在多个组中,但是在运行时,只能有一个实例存在,Ansible 会自动将属于多个组的主机合并。
  • 主机范围匹配。如果有格式相似的主机,可以通过范围格式使用一条指令来添加多台主机。
    • INI 配置格式中,使用以下格式
      [webservers]
      www[01:50].example.com

      ## 指定步长增长
      www[01:50:2].example.com

      db-[a:f].example.com
    • YAML 配置格式中,使用以下格式
      # ...
      webservers:
      hosts:
      www[01:50].example.com:

      ## 指定步长增长
      www[01:50:2].example.com:
      db-[a:f].example.com:

      范围格式 的第一项和最后一项也包括在内。即匹配 www01www50

Inventory 多配置文件支持

在主机数量较多,或者组织结构较复杂的情况下,使用单个 Inventory 配置文件会导致主机管理较为复杂。将单个 Inventory 配置文件按照项目或者组织或其他规则进行分割会显著降低维护复杂度。

Inventory 多配置文件支持,可以使用以下方法之一

  • 按照项目或者组织或其他规则将主机分割到多个配置中,命令行中可以使用 -i <path1> -i <path2> ... 指定多个 inventory
  • 按照项目或者组织或其他规则将主机分割放置在多个文件中,并将所有文件统一放置在一个单独的目录中(如 /etc/ansible/inventory/),在命令行中使用选项 -i /etc/ansible/inventory/ 或者在 Ansible 配置文件(ansible.cfg)中使用指令 inventory 配置目录。

    注意事项: Ansible 使用字典顺序加载配置文件,如果在不同的配置文件中配置了 parent groupschild groups,那么定义 child groups 的配置要先用定义 parent groups 的文件加载,否则 Ansible 加载配置会报错: Unable to parse /path/to/source_of_parent_groups as an inventory source [4]

  • 使用 group_varshost_vars 目录分别存储组变量和主机变量 [7]

    注意事项: 组变量和主机变量必须使用 YAML 格式,合法的文件扩展名包括: .yamlyml.json 或者无文件扩展名

INI 格式的 Inventory

主机列表中的主机可以单独出现,也可以位于某个或者多个 组([] 开头的行)中

/etc/ansible/hosts
ansible-demo1.local
ansible-demo2.local

[webserver]
webserver1.local
webserver2.local

[nginxserver]
# 匹配多个主机:nginx1.local, nginx2.local, nginx3.local, nginx4.local
nginx[1:4].local variable1=value1 variable2=value2
nginx-bak.local ansible_ssh_host=10.10.0.1 ansible_ssh_port=22 ansible_ssh_user=root ansible_ssh_pass=PASSWORD
127.0.0.1 http_port=80 maxRequestPerChild=808


连接主机使用的常用配置说明 [6]

配置项 说明 示例
ansible_host 远程主机地址
ansible_port 远程主机端口
ansible_user 连接远程主机的 ssh 用户
Ansible 默认使用 control node 上执行 ansible 的用户名来连接远程主机 [9]
ansible_password 连接远程主机的 ssh 用户密码,建议使用 key 连接
ansible_ssh_private_key_file 连接远程主机的 ssh 私钥文件路径
ansible_become
ansible_sudo
ansible_su
用户权限提升
ansible_become_method 用户权限提升(escalation)的方式
ansible_become_user
ansible_sudo_user
ansible_su_user
用户权限提升(escalation)后的用户
ansible_become_password
ansible_sudo_password
ansible_su_password
sudo 密码(这种方式并不安全,强烈建议使用 --ask-sudo-pass)
ansible_become_exe
ansible_sudo_exe
ansible_su_exe
设置用户权限提升(escalation)后的可执行文件
ansible_connection 与主机的连接类型.比如:local, ssh 或者 paramiko
Ansible 1.2 以前默认使用 paramiko。1.2 以后默认使用 smart,smart 方式会根据是否支持 ControlPersist, 来判断 ssh 方式是否可行.
ansible_shell_type 目标系统的 shell 类型.默认情况下,命令的执行使用 sh 语法,可设置为 cshfish.
ansible_python_interpreter 目标主机的 python 路径
系统中有多个 Python, 或者命令路径不是 /usr/bin/python
阅读全文 »

YAML(YAML Ain’t Markup Language)是一种专门用于数据序列化的格式,常用于配置文件、数据交换等场景。它以其可读性和简洁性而受到开发者的青睐。YAML设计的目标是易于人类阅读和编写,并且易于与其他编程语言进行交互。下面是YAML语法的详细介绍

基本结构

  • 数据类型 :YAML 支持标量(如字符串、整数、浮点数)、序列(列表)和映射(字典)三种基本数据类型。

  • 缩进 :YAML 使用缩进表示结构层级关系,通常每个层级缩进两个或四个空格(禁止使用制表符)。

标量数据类型

标量(Scalars) 是单个的、不可分割的值。可以是字符串、整数或浮点数。标量可以是单行的值,也可以是多行的值

# 单行字符串
name: John Doe

# 整数
age: 35

# 浮点数
height: 5.9

# 布尔值
is_student: false

多行字符串可以使用字面量样式(|)或折叠样式(>):

# 字面量样式保留换行符
address: |
123 Main St
Anytown, WW 12345

# 折叠样式将连续的行合并为一行
description: >
This is a very long sentence
that spans several lines in the YAML
but will be rendered as a single
line in the output.

  • 可以显式指定数据类型,例如字符串可以用单引号或双引号包围
  • 字符串通常不需要引号,但如果含有特殊字符,则需要使用单引号或双引号。
  • true, false, null 等特定词汇表示布尔值和 Null
  • 时间和日期需要遵循ISO格式。

列表(Sequences)

列表(Sequences) 是一组按顺序排列的值(类似于数组或列表),用破折号加空格表示新的元素,每个列表项占一行,也需要正确缩进。

# 列表
hobbies:
- Reading
- Fishing
- Dancing

字典(Mappings)

映射/字典(Mappings) 是键值对的集合(类似于字典或哈希表),用冒号加空格表示键值对,键值对需要正确缩进

# 字典
person:
name: John Doe
age: 35
city: New York

嵌套结构

列表和字典可以嵌套使用,形成复杂的结构

# 嵌套的列表和字典
employees:
- name: John Doe
job: Developer
skills:
- Python
- SQL
- name: Jane Smith
job: Designer
skills:
- Photoshop
- Illustrator

锚点和别名

YAML支持定义锚点(&)和别名(*)来重用(引用)文档中的某部分,使用 & 创建一个锚点(alias),之后可以用 * 引用这个锚点。

使用 <<* 来合并已有(引用)的映射。

# 使用锚点和别名
defaults: &defaults
adapter: postgres
host: localhost

development:
<<: *defaults
database: dev_db

test:
<<: *defaults
database: test_db

# 字符串锚点和引用
name: &name_anchor "John Doe"
contact:
name: *name_anchor

注释

使用井号 # 开始一个注释,井号后面的内容将被视为注释,注释内容直到行尾。

多文档支持

一个 YAML 文件可以包含多个文档,每个文档用三个短横线 --- 分隔。

---
document1:
- item1
---
document2:
- item2

process-exporter Github

安装部署

process-exporter 生成 systemd 服务启动配置文件:

/etc/systemd/system/process_exporter.service
[Unit]
Description=process exporter
Documentation=process exporter

[Service]
ExecStart=/usr/bin/process-exporter -config.path /etc/prometheus/process_exporter.yml

[Install]
WantedBy=multi-user.target

建议将 process-exporter 的配置写入文件并使用 -config.path 指定配置文件。

配置进程监控

process-exporter 在配置文件中使用模板变量来配置筛选要监控的进程,可以使用的模板变量包括:

变量 说明 示例
{{.Comm}} 匹配进程的命令名(不包括路径)。主要来源于 /proc/<pid>/stat 输出中的第二部分
命令名是指进程执行时的名称。在 Linux 系统中,可以通过 /proc/<PID>/comm 文件来获取进程的命令名。例如,如果一个进程执行的命令是 /usr/local/bin/php,那么它的命令名就是 php
{{.ExeBase}} 匹配进程的可执行文件名,不包括路径
可执行文件名是指进程的完整路径的最后一个部分。例如,如果一个进程的完整路径是 /usr/local/bin/php,那么它的可执行文件名就是 php
{{.ExeFull}} 匹配进程的可执行文件的完整路径,例如 /usr/local/php73/bin/php
{{.Username}} 匹配进程的用户名
{{.Matches}} 匹配进程的命令行参数列表
{{.StartTime}}
{{.Cgroups}}

监控系统上的所有进程

要监控系统上的所有进程的运行情况,可以参考以下配置: [1]

/etc/prometheus/process_exporter.yml
process_names:
- name: "{{.Comm}}"
cmdline:
- '.+'
  • 以上配置会获取到系统上的所有进程(子进程被统计入父进程中
  • 假如配置中有多个匹配项,以上配置不能放到第一个,否则因为其可以匹配到系统中所有的进程,后续配置的匹配不会再有机会生效

监控系统上面指定进程

假如系统中运行了多个 php 的子进程,为了获取到各个子进程的统计数据,可以参考以下配置

/etc/prometheus/process_exporter.yml
process_names:

- name: php_pro1
cmdline:
- /usr/local/php73/bin/php
- /home/www/admin/artisan
- Pulldata

- name: php_schedule_run
cmdline:
- /usr/local/php73/bin/php
- /home/www/admin/artisan
- schedule:run

- name: php_artisan_queue
cmdline:
- /usr/local/php73/bin/php
- /home/www/admin/artisan
- queue:work

- name: "{{.Comm}}"
cmdline:
- '.+'

使用此配置,可以获取到系统中以下进程的统计数据:

  • /usr/local/php73/bin/php /home/www/admin/artisan Pulldata
  • /usr/local/php73/bin/php /home/www/admin/artisan schedule:run
  • /usr/local/php73/bin/php /home/www/admin/artisan queue:work

除可以获取到以上特定进程的统计数据外,还可以统计到除此之外的其他所有进程的统计数据。

因为配置中匹配进程的顺序的关系,假如系统中还有除此之外的其他 php 进程,那么由最后的 {{.Comm}} 统计到的 php 进程资源使用数据中不再包含前面 3 个特定进程的资源使用数据。

脚注

Nextcloud All-in-One

Nextcloud All-in-One 在一个 Docker 容器中提供了方便部署和维护的 Nextcloud 方式。[1]

使用 docker compose 部署

为方便后期管理及迁移,建议使用 docker compose 方式部署。docker-compose.yml 参考文件如下: [2]

docker-compose.yml
version: "3"

services:
nextcloud-aio-mastercontainer:
image: nextcloud/all-in-one:latest
container_name: nextcloud-aio-mastercontainer
ports:
- "8000:80"
- "8080:8080"
- "8443:8443"
volumes:
- nextcloud_aio_mastercontainer:/mnt/docker-aio-config
- /var/run/docker.sock:/var/run/docker.sock:ro

volumes:
nextcloud_aio_mastercontainer:
name: nextcloud_aio_mastercontainer
driver: local
driver_opts:
type: none
o: bind
device: /opt/Nextcloud/data/

使用 docker compose 方式部署注意事项:

  • name: nextcloud_aio_mastercontainer: Volume 名称必须是 nextcloud_aio_mastercontainer,否则会报错找不到卷 nextcloud_aio_mastercontainer: It seems like you did not give the mastercontainer volume the correct name? (The 'nextcloud_aio_mastercontainer' volume was not found.). Using a different name is not supported since the built-in backup solution will not work in that case!

启动成功后,根据提示在浏览器中打开 Nextcloud AIO setup 页面并记录页面显示的密码

You should be able to open the Nextcloud AIO Interface now on port 8080 of this server!
E.g. https://internal.ip.of.this.server:8080

根据页面提示登陆,跟随页面提示进行新实例初始化。

初始化过程中要提供域名,系统会自动为域名颁发证书(使用系统 443 端口映射到容器中的 8443 端口)

默认的 Nextcloud AIO 未部署反向代理,要使用反向代理请参考文档: Reverse Proxy Documentation

Nextcloud AIO 使用的端口说明

脚注

环境信息

  • Centos 7
  • Prometheus Server 2.4
  • Node Exporter v1.4.0
  • Grafana v9.2.5

安装

在 Docker 中安装 Prometheus Server

创建 Prometheus Server 配置文件,如 /root/prometheus/prometheus.yml,内容如下 [1]

/data/prometheus/prometheus.yml
# my global config
global:
scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
# scrape_timeout is set to the global default (10s).

# Alertmanager configuration
alerting:
alertmanagers:
- static_configs:
- targets:
# - alertmanager:9093

# Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
rule_files:
# - "first_rules.yml"
# - "second_rules.yml"

# A scrape configuration containing exactly one endpoint to scrape:
# Here it's Prometheus itself.
scrape_configs:
# The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
- job_name: 'prometheus'

# metrics_path defaults to '/metrics'
# scheme defaults to 'http'.

static_configs:
- targets: ['localhost:9090']

使用 Docker 启动时挂载此文件,作为 Prometheus Server 的配置文件,之后需要修改配置,可以直接修改此文件。

docker run -d -p 9090:9090 \
--name prometheus \
-v /root/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml \
prom/prometheus

启动后,可以通过 $Prometheus_IP:9090 访问 Prometheus Server UI

阅读全文 »

Prometheus ValKey & Redis Metrics Exporter

Prometheus Redis Metrics Exporter 下载页面

redis_exporter 安装

wget https://github.com/oliver006/redis_exporter/releases/download/v1.59.0/redis_exporter-v1.59.0.linux-amd64.tar.gz
tar -xf redis_exporter-v1.59.0.linux-amd64.tar.gz
mv redis_exporter-v1.59.0.linux-amd64/redis_exporter /usr/bin/

redis_exporter 生成 systemd 服务配置文件 /usr/lib/systemd/system/redis_exporter.service

/usr/lib/systemd/system/redis_exporter.service
[Unit]
Description=redis_exporter
After=syslog.target
After=network.target

[Service]
Type=simple
ExecStart=/usr/bin/redis_exporter
Restart=always
RestartSec=10
StartLimitInterval=100

[Install]
WantedBy=multi-user.target

启动 redis_exporter 服务

# systemctl daemon-reload

# systemctl enable --now redis_exporter

# systemctl status redis_exporter
● redis_exporter.service - redis_exporter
Loaded: loaded (/usr/lib/systemd/system/redis_exporter.service; enabled; vendor preset: disabled)
Active: active (running) since Tue 2024-04-30 10:45:58 CST; 5s ago
Main PID: 12126 (redis_exporter)
CGroup: /system.slice/redis_exporter.service
└─12126 /usr/bin/redis_exporter

Apr 30 10:45:58 ip-172-31-26-219.us-west-1.compute.internal systemd[1]: Started redis_exporter.
Apr 30 10:45:58 ip-172-31-26-219.us-west-1.compute.internal redis_exporter[12126]: time="2024-04-30T10:45:58+08:00" level=info msg="Redis Metrics Exp...md64"
Apr 30 10:45:58 ip-172-31-26-219.us-west-1.compute.internal redis_exporter[12126]: time="2024-04-30T10:45:58+08:00" level=info msg="Providing metrics...rics"
Hint: Some lines were ellipsized, use -l to show in full.

redis_exporter 服务启动后,默认启动 9121 端口提供 Metrics 数据供 Prometheus 抓取。

redis_exporter 配置

redis 实例及认证信息配置

如果要通过一个 redis_exporter 实例监控多个 Redis 实例,可以参照以下配置文件配置 Redis 实例及其认证信息,如果无需密码认证,则保留密码项为空。

/etc/redis_exporter_pwd_file.json

{
"redis://localhost:7380": "paswd12",
"redis://localhost:7381": "paswd12",
"redis://localhost:7382": "paswd12",
"redis://172.31.19.9:7380": "paswd12",
"redis://172.31.19.9:7381": "paswd12",
"redis://172.31.19.9:7382": ""
}

修改 redis_exporter 启动参数,使其读取上面配置的实例和其认证信息

/usr/bin/redis_exporter -redis.password-file /etc/redis_exporter_pwd_file.json

配置 Prometheus 抓取 redis_exporter 指标

参考以下配置使用文件发现的方式配置被监控的 Redis 实例


scrape_configs:
- job_name: 'redis_exporter_targets'
file_sd_configs:
- files:
- targets-redis-instances.yml
metrics_path: /scrape
relabel_configs:
- source_labels: [__address__]
target_label: __param_target
- source_labels: [__param_target]
target_label: instance
- target_label: __address__
replacement: <<REDIS-EXPORTER-HOSTNAME>>:9121

## config for scraping the exporter itself
- job_name: 'redis_exporter'
static_configs:
- targets:
- <<REDIS-EXPORTER-HOSTNAME>>:9121

targets-redis-instances.yml 文件内容包含 Targets 内容:

targets-redis-instances.yml
- labels:
label1: value1
targets: [ "redis://redis-host-01:6379", "redis://redis-host-02:6379"]
阅读全文 »

环境信息

  • Centos 7
  • Prometheus 2.44.0
  • node_exporter-1.6.0

node_exporter 安装

安装 node_exporter 官方文档

以下步骤演示安装 node_exporter 并使用 systemd 管理服务

wget https://github.com/prometheus/node_exporter/releases/download/v1.6.0/node_exporter-1.6.0.linux-amd64.tar.gz

tar -xf node_exporter-1.6.0.linux-amd64.tar.gz

cp node_exporter-1.6.0.linux-amd64/node_exporter /usr/bin/

生成 systemd 服务配置文件 /usr/lib/systemd/system/node_exporter.service

/usr/lib/systemd/system/node_exporter.service
[Unit]
Description=node_exporter
After=syslog.target
After=network.target

[Service]
Type=simple
ExecStart=/usr/bin/node_exporter
Restart=always
RestartSec=10
StartLimitInterval=100

[Install]
WantedBy=multi-user.target

执行以下命令管理服务

$ systemctl daemon-reload

$ systemctl status node_exporter
● node_exporter.service - node_exporter
Loaded: loaded (/usr/lib/systemd/system/node_exporter.service; disabled; vendor preset: disabled)
Active: inactive (dead)

$ systemctl enable --now node_exporter
Created symlink from /etc/systemd/system/multi-user.target.wants/node_exporter.service to /usr/lib/systemd/system/node_exporter.service.

$ systemctl status node_exporter
● node_exporter.service - node_exporter
Loaded: loaded (/usr/lib/systemd/system/node_exporter.service; enabled; vendor preset: disabled)
Active: active (running) since Fri 2023-06-23 10:34:08 CST; 2s ago
Main PID: 28578 (node_exporter)
CGroup: /system.slice/node_exporter.service
└─28578 /usr/bin/node_exporter

node_exporter 配置信息

启动参数详解

参数 说明 示例
--web.listen-address=":9100" node_exporter 监听端口,默认 9100
--web.telemetry-path="/metrics" prometheus 获取 Metrics 访问的 url,默认 /metrics
--log.level="info" 日志级别
--log.format="logger:stderr" 日志打印格式
--collector.disable-defaults 关闭默认的采集项
--no-collector.${item} 关闭某一项默认开启的采集项 --no-collector.cpu
--collector.systemd.unit-include="(docker'|'sshd).service" 收集指定服务的指标

可以配置通过正则表达式屏蔽或者选择某些监控项 [1]

参数 说明 示例
--collector.diskstats.ignored-devices="^(ram'|'loop'|')\\d+$" 忽略某些磁盘的信息收集
--collector.filesystem.ignored-mount-points="^/(dev'|'proc'|')($'|'/)" 忽略某些文件系统挂载点的信息收集
--collector.filesystem.ignored-fs-types="^(autofs'|'proc)$" 忽略某些文件系统类型的信息收集
--collector.netclass.ignored-devices="^$" 忽略某些网络类的信息收集
--collector.netdev.ignored-devices="^$" 忽略某些网络设备的信息收集
--collector.netstat.fields="^$" 配置需要获取的网络状态信息
--collector.vmstat.fields="^(oom_kill'|'pgpg'|'pswp).*" 配置 vmstat 返回信息中需要收集的选项

功能对照表

默认开启的功能

默认开启的功能 [1]

名称 说明 系统
arp /proc/net/arp 中收集 ARP 统计信息 Linux
conntrack /proc/sys/net/netfilter/ 中收集 conntrack 统计信息 Linux
cpu 收集 cpu 统计信息 Darwin, Dragonfly, FreeBSD, Linux
diskstats /proc/diskstats 中收集磁盘 I/O 统计信息 Linux
edac 错误检测与纠正统计信息 Linux
entropy 可用内核熵信息 Linux
exec execution 统计信息 Dragonfly, FreeBSD
filefd /proc/sys/fs/file-nr 中收集文件描述符统计信息 Linux
filesystem 文件系统统计信息,例如磁盘已使用空间 Darwin, Dragonfly, FreeBSD, Linux, OpenBSD
hwmon /sys/class/hwmon/ 中收集监控器或传感器数据信息 Linux
infiniband 从 InfiniBand 配置中收集网络统计信息 Linux
loadavg 收集系统负载信息 Darwin, Dragonfly, FreeBSD, Linux, NetBSD, OpenBSD, Solaris
mdadm /proc/mdstat 中获取设备统计信息 Linux
meminfo 内存统计信息 Darwin, Dragonfly, FreeBSD, Linux
netdev 网口流量统计信息,单位 bytes Darwin, Dragonfly, FreeBSD, Linux, OpenBSD
netstat /proc/net/netstat 收集网络统计数据,等同于 netstat -s Linux
sockstat /proc/net/sockstat 中收集 socket 统计信息 Linux
stat /proc/stat 中收集各种统计信息,包含系统启动时间,forks, 中断等 Linux
textfile 通过 --collector.textfile.directory 参数指定本地文本收集路径,收集文本信息 any
time 系统当前时间 any
uname 通过 uname 系统调用, 获取系统信息 any
vmstat /proc/vmstat 中收集统计信息 Linux
wifi 收集 wifi 设备相关统计数据 Linux
xfs 收集 xfs 运行时统计信息 Linux (kernel 4.4+)
zfs 收集 zfs 性能统计信息 Linux

默认关闭的功能

默认关闭的功能 [1]

名称 说明 系统
bonding 收集系统配置以及激活的绑定网卡数量 Linux
buddyinfo /proc/buddyinfo 中收集内存碎片统计信息 Linux
devstat 收集设备统计信息 Dragonfly, FreeBSD
drbd 收集远程镜像块设备(DRBD)统计信息 Linux
interrupts 收集更具体的中断统计信息 Linux,OpenBSD
ipvs /proc/net/ip_vs 中收集 IPVS 状态信息,从 /proc/net/ip_vs_stats 获取统计信息 Linux
ksmd /sys/kernel/mm/ksm 中获取内核和系统统计信息 Linux
logind logind 中收集会话统计信息 Linux
meminfo_numa /proc/meminfo_numa 中收集内存统计信息 Linux
mountstats /proc/self/mountstat 中收集文件系统统计信息,包括 NFS 客户端统计信息 Linux
nfs /proc/net/rpc/nfs 中收集 NFS 统计信息,等同于 nfsstat -c Linux
qdisc 收集队列推定统计信息 Linux
runit 收集 runit 状态信息 any
supervisord 收集 supervisord 状态信息 any
systemd systemd 中收集设备系统状态信息 Linux
tcpstat /proc/net/tcp/proc/net/tcp6 收集 TCP 连接状态信息 Linux

配置示例

采集白名单配置

关闭默认的采集项,只开启指定的采集项(白名单)

node-exporter --collector.disable-defaults --collector.cpu --collector.meminfo
阅读全文 »

环境信息

  • Centos 7
  • Python 3

在 Telegram 中生成 Bot

  1. 首先在 telegram 中搜索 @BotFather,和其对话,根据提示创建 机器人,记录下生成的 token 信息

  2. 创建新的 Channel 或者 Group 或者将刚刚新建的 Bot 加入已有的 Channel/Group。

  3. 获取 ChatGroup ID,可以使用以下方法之一

    1. 添加机器人 @get_id_bot 到 Channel,会自动显示 Chat ID

    2. 使用以下代码获取

      >>> import requests
      >>> response = requests.get(f'https://api.telegram.org/bot{token}/getUpdates')
      >>> data = response.json()
      >>> chat_id = data['result'][0]['message']['chat']['id']
      >>> chat_id
      -992754669

使用 curl 向 telegram 发送消息

$ curl -v "https://api.telegram.org/bot{token}/sendMessage?text=sa&chat_id=-992754669"

> GET /bot{token}/sendMessage?text=sa&chat_id=-992754669 HTTP/1.1
> User-Agent: curl/7.29.0
> Host: api.telegram.org
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: nginx/1.18.0
< Date: Fri, 02 Jun 2023 03:03:58 GMT
< Content-Type: application/json
< Content-Length: 276
< Connection: keep-alive
< Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
< Access-Control-Allow-Origin: *
< Access-Control-Allow-Methods: GET, POST, OPTIONS
< Access-Control-Expose-Headers: Content-Length,Content-Type,Date,Server,Connection
<
* Connection #0 to host api.telegram.org left intact
{"ok":true,"result":{"message_id":12,"from":{"id":5683237521,"is_bot":true,"first_name":"AlertManager","username":"AlertManager_Bot"},"chat":{"id":-992754669,"title":"AlertManager Test","type":"group","all_members_are_administrators":true},"date":1685675038,"text":"sa"}}

使用 python 向 telegram 发送消息

使用 requests 库

>>> import requests
>>> response = requests.get(f'https://api.telegram.org/bot{token}/sendMessage?text=sa&chat_id=-992754669')

>>> response.text
'{"ok":true,"result":{"message_id":13,"from":{"id":5683237521,"is_bot":true,"first_name":"AlertManager","username":"AlertManager_Bot"},"chat":{"id":-992754669,"title":"AlertManager Test","type":"group","all_members_are_administrators":true},"date":1685675769,"text":"sa"}}'

使用 telegram 库

需要安装 python-telegram-bot

pip install --upgrade python-telegram-bot

发送消息代码

>>> import telegram
>>> import asyncio
>>> bot = telegram.Bot(token='5683231111:AAHzaGf0oRg8A')
>>> async def send_telegram_message():
... response = await bot.send_message(chat_id=-992754669, text="la")
... print(response)
...
>>> loop = asyncio.get_event_loop()
>>> loop.run_until_complete(send_telegram_message())
Message(channel_chat_created=False, chat=Chat(api_kwargs={'all_members_are_administrators': True}, id=-992754669, title='AlertManager Test', type=<ChatType.GROUP>), date=datetime.datetime(2023, 6, 2, 3, 39, 16, tzinfo=datetime.timezone.utc), delete_chat_photo=False, from_user=User(first_name='AlertManager', id=5683237521, is_bot=True, username='MS_AlertManager_Bot'), group_chat_created=False, message_id=14, supergroup_chat_created=False, text='la')

如果需要在非异步环境中(例如 Django 试图函数) 运行以上异步代码,会报错: RuntimeError: There is no current event loop in thread 'Thread-1'。需要特殊处理,可以使用 asyncio.run() 函数来运行异步代码,它可以在非异步环境中创建一个新的事件循环并运行异步函数。

Django 视图中参考代码如下

def send_message_to_tg(chat_id: int, text: str):
''' 发送消息到 tg'''
bot = telegram.Bot(token=tg_bot_token)

async def send_telegram_message():
response = await bot.send_message(chat_id=chat_id, text=text)
print(response)
# loop = asyncio.get_event_loop()
# loop.run_until_complete(send_telegram_message())
asyncio.run(send_telegram_message())