本文介绍如何将Ansible Collection发布到Galaxy。
关于Ansible的介绍可以参看 Linux常用命令--Ansible
关于将Ansible Role发布到Galaxy的详细介绍可以参看 Ansible Role Publish To Galaxy
1 | # 在当前目录下创建collection |
目录结构
1 | hanqunfeng |
在
galaxy.yml
中配置collection的元数据
在runtime.yml
中配置collection的运行时依赖
在README.md
中配置collection的描述信息
在roles
中创建role,这里可以创建多个role,我这里将上面创建的role复制到roles
目录下,不过名称需要修改一下,不支持下划线,所以这里将role的名称改为centos_mongodb_install
相关代码参考:https://github.com/hanqunfeng/ansible_collections
构建collection的tar包
1 | # 构建collection的tar包时先要进入namespace目录下 |
上传collection到Galaxy
搜索collection
安装collection
1 | # 默认安装最新版本 |
使用collection
1 |
|
删除collection
1 | # 没有现成的remove命令,只能手动删除 |
更新collection
重新安装collection
1 | # 默认安装最新版本 |
先要在ansible-galaxy网站上获取api-key
发布collection
1 | # 注意替换自己的 api-key |
目前2.16.4
这个版本尚不支持在命令行搜索collection,只能通过web界面搜索。
本文介绍如何将Ansible Role发布到Galaxy。
关于Ansible的介绍可以参看 Linux常用命令--Ansible
关于将Ansible Collection发布到Galaxy的详细介绍可以参看 Ansible Collection Publish To Galaxy
在当前目录下创建一个名称为mongodb-install
的role,实际上就是创建一个名称为mongodb-install
的目录结构
1 | ansible-galaxy init mongodb-install |
创建完成后,目录结构如下:
1 | mongodb-install |
编辑role,相关代码参考https://github.com/hanqunfeng/ansible-role-mongodb
这里我们要注意两个文件
README.md
: 这里面配置的是role的描述信息,其会显示在文档
一栏中meta/main.yml
,这里面配置的是role的作者信息
1 | cd mongodb-install |
登录https://galaxy.ansible.com/,点击登录
,使用Github账号登录。
导入Role
填写Role信息
查看导入状态
搜索Role
此时我们在角色列表中搜索关键字hanqunfeng
,就可以搜索到我们刚刚上传的那个role了
点击进去我们就可以查看角色的详细信息了
修改了role信息后,重新上传到github即可
如果同时修改了README.md
等role信息,可以在galaxy中重新上传即可
1 | # 安装 |
在playbook
中引用role
1 |
|
执行playbook
1 | ansible-playbook -i hosts playbook.yml |
给角色代码打tag
1 | # 创建tag,名称为 v1.0.0 |
在galaxy中重新上传,注意此时在要指定tag名称
上传成功后,就可以在galaxy中查看到对应的版本了
1 | # 注意role和版本号之间用逗号隔开,不加版本号,则下载最新版本 |
除了在galaxy网站中搜索,还可以通过命令行搜索
1 | $ ansible-galaxy search hanqunfeng |
本文介绍Ansible的安装和使用。
本文基于CentOS8。
Ansible 是一个开源的自动化运维工具,它基于 Python 语言开发,支持跨平台,可以运行在 Linux、Unix、Mac OS X、Windows 等多种系统环境。
Ansible简单易用,只需要在控制主机上安装 Ansible 并在被管理主机上安装 Python 2.6 或更高版本即可(现有的开源Linux系统基本都自带了Python ,所以相当于远程主机什么都不需要安装),就可以管理远程主机对其进行自动化配置、编排高级工作流程以支持应用程序部署、系统更新等。
Ansible可以用来管理Linux、Unix、Windows、OpenStack、AWS、GCP、OpenShift、Kubernetes等系统环境。
Ansible官网文档:https://docs.ansible.com/ansible/latest/
Ansible中文权威指南:https://ansible-tran.readthedocs.io/en/latest/index.html
Ansible安装方式有多种,这里介绍通过pip安装,当前系统中的python版本为python3.9。
1 | python3 -m pip install ansible |
如果尚未安装pip,可以通过下面的方法进行安装
1 | wget https://bootstrap.pypa.io/get-pip.py |
安装完成后,可以查看一下版本
1 | ansible --version |
Ansible常用参数:
参数 | 说明 |
---|---|
-u | 指定连接到主机的用户名 |
-i | 指定连接hosts文件的路径 |
-k | 指定连接到主机的密码 |
-m | 指定模块名称,可以指定模块的参数,比如指定shell模块的参数:-m shell -a ‘ls -l’ |
-a | 指定模块的参数,比如指定shell模块的参数:-m shell -a ‘ls -l’ |
-e | 指定附加参数,比如指定shell端口: -e ‘ansible_port=22’ |
-b | 切换到root权限 |
-f | 指定并发连接数,默认为5,即可以同时管理5台主机 |
ansible的相关配置都是通过一个名为ansible.cfg
的配置文件进行配置的,但我们通过pip安装是不会默认创建它的,实际上不创建这个配置文件也不影响ansible的执行,因为其会使用一些默认的配置,并且在大多数场景下默认的配置就能满足需求。
ansible查找配置文件的顺序,优先级由上到下逐渐降低
1 | * ANSIBLE_CONFIG (一个环境变量) |
如果需要修改默认配置,我们可以通过如下命令创建一份配置文件
1 | # 此时会创建一份所有配置项都以 ; 开头的配置文件,我们要修改那个配置,就删除其前面的 ; ,然后修改其值即可 |
常用配置详解
1 | # 通用默认配置段; |
需要创建hosts文件,使用ansible时,ansible会到hosts文件中查找对应的远程主机配置,默认的查找路径为/etc/ansible/hosts
,也可以通过ansible.cfg
修改默认配置
hosts文件格式如下:
1 | # 可以配置ip或主机名 |
命令行里加上连接参数
1 | # 执行命令,多个ip逗号分隔 |
hosts文件里也可以配置连接参数
1 | [webservers] |
此时命令行连接时就不需要指定连接参数了
1 | ansible -i hosts -u username -m ping 10.10.2.45,10.10.2.46 |
ansible是基于ssh建立连接的,所以只要ssh能连上远程主机,ansible就可以管理远程主机,如果我们为ssh配置config文件,那么ansible就可以直接将config里的Host名称配置到hosts文件里,这样就不需要配置连接参数了,同时此时也可以支持跳板机。关于ssh的config详细说明可以参考Linux常用命令--ssh、scp与免密登录
1 | # 跳板机 |
1 | [webservers] |
1 | # 通过指定主机名或ip |
列出所有组的配置ip信息
1 | ansible all --list-hosts |
查看当前hosts中配置所有组名称
1 | ansible localhost -m debug -a 'var=groups.keys()' |
列出指定组的配置ip信息
1 | ansible dbservers --list-hosts |
列出所有模块
1 | ansible-doc -l |
查看指定模块的参数信息,如下查看shell模块的参数说明
1 | ansible-doc -s shell |
查看模块的帮助信息,如下查看shell模块的帮助信息,这与官方文档一致:shell模块
1 | ansible-doc shell |
Ansible管理工具常用的模块:command、shell、file、user、copy、service、yum、synchronze、cron、setup、ping
等。可以在命令行里指定各个模块的参数(ansible
命令),也可以将参数配置到yml文件里,然后在命令行里指定yml文件(ansible-playbook
命令),下面会结合两种方式一块介绍。网上有大把的关于ansible命令的使用说明,这里只简单介绍几个模块的使用方法,其他模块的使用方法可以参考ansible模块
下面介绍的都是ansible核心模块:ansible-core
debug模块此模块在执行期间打印语句,可用于调试变量或表达式
通过命令行执行命令
1 | ansible webservers -m debug -a 'msg="System HostName: {{ inventory_hostname }}"' |
通过yml文件执行命令
1 | # debug.yml |
常用参数说明:ansible-doc -s debug
参数 | 描述 |
---|---|
msg | 打印消息,支持变量 |
var | 打印消息,支持变量,注册变量 , 与msg互斥 |
ping模块用于测试主机的连通性,它会尝试连接到主机,验证可用的python,并在成功时返回“pong”,可以指定主机名或ip,也可以指定分组名,也可以指定all
通过命令行执行命令
1 | ansible webservers -m ping |
通过yml文件执行命令
1 | # ping.yml |
ansible-playbook
1 | # 检查yml文件语法格式 |
该模块用于采集被管理设备信息并返回给服务端,后面跟--tree <目录>
,可以将采集信息以ip为文件名保存至指定目录下
1 | # 查看全部信息 |
常用参数说明:ansible-doc -s setup
参数 | 描述 |
---|---|
gather_subset | 指定要收集的系统信息的子集。可以是 all(所有信息)、network(网络信息)、hardware(硬件信息)等。默认为 all。 |
gather_timeout | 设置信息收集的超时时间,单位为秒。默认为 10 秒。 |
filter | 指定要收集的系统信息的过滤条件。可以是一个或多个标签,只收集匹配的信息。 |
fact_path | 指定自定义 facts 文件的路径。 |
这里重点介绍一下gather_subset
1 | all: 收集所有可用的系统信息。 |
command模块为ansible默认模块,主要用于执行Linux基础命令,可以执行远程服务器命令执行、任务执行等操作。
command模块不支持管道符号、变量,只能运行简单命令,复杂命令需要使用shell模块
示例1
1 | ansible webservers -m command -a "df -hT" |
1 | # command.yml |
示例2:参数chdir:切换到指定目录后再运行命令
可以通过
ansible-doc -s command
查看其支持的参数
1 | ansible webservers -m command -a "chdir=/tmp ls -l" |
1 | # command2.yml |
常用参数说明:ansible-doc -s command
参数 | 描述 |
---|---|
argv | 要执行的命令,可以是字符串形式或列表形式。 |
chdir | 在执行命令之前切换到的目录。 |
cmd | 要执行的命令。 |
creates | 指定一个文件名或模式,如果匹配的文件已经存在,则不执行命令。 |
expand_argument_vars | 是否展开作为变量的参数。默认为 true,表示展开变量。 |
free_form | 以字符串形式指定要执行的命令。这个参数并不存在,但是 command 模块接受自由形式的字符串作为命令。 |
removes | 指定一个文件名或模式,如果匹配的文件存在,则执行命令。 |
stdin | 将命令的标准输入设置为指定的值。 |
stdin_add_newline | 是否在标准输入数据后添加换行符。 |
strip_empty_ends | 是否从标准输出的末尾剥离空行。 |
shell模块与command模块类似,可以执行远程服务器命令执行、任务执行等操作,但是shell模块支持管道符号、变量,可以执行复杂命令
示例:
1 | # 查看进程 |
1 | # shell.yml |
常用参数说明:ansible-doc -s shell
参数 | 描述 |
---|---|
argv | 要执行的命令,可以是字符串形式或列表形式。 |
chdir | 在执行命令之前切换到的目录。 |
cmd | 要执行的命令。 |
creates | 指定一个文件名或模式,如果匹配的文件已经存在,则不执行命令。 |
executable | 用于执行命令的可执行程序,默认情况下为 /bin/sh。 |
free_form | 以字符串形式指定要执行的命令。这个参数并不存在,但是 shell 模块接受自由形式的字符串作为命令。 |
removes | 指定一个文件名或模式,如果匹配的文件存在,则执行命令。 |
stdin | 将命令的标准输入设置为指定的值。 |
stdin_add_newline | 是否在标准输入数据后添加换行符。 |
strip_empty_ends | 是否从标准输出的末尾剥离空行。 |
file模块主要用于文件和目录的管理,可以创建、删除、修改文件和目录,可以指定文件或目录的属性,可以指定文件或目录的权限,可以指定文件或目录的owner、group、mode等信息,等等
示例:创建目录
1 | ansible webservers -m file -a "path=/tmp/`date +%F` state=directory mode=755" |
1 | # file.yml |
常用参数说明:ansible-doc -s file
参数 | 描述 |
---|---|
path | 文件或目录的路径。 |
state | 文件或目录的状态。可选值包括 file(文件)、directory(目录)、link(符号链接)。默认为 file。 |
owner | 文件或目录的所有者。 |
group | 文件或目录的所属组。 |
mode | 文件或目录的权限。 |
src | 源文件路径,用于复制文件或创建链接。 |
dest | 目标文件路径,用于复制文件或创建链接。 |
follow | 是否遵循符号链接。如果为 yes,则会遵循符号链接进行操作。默认为 yes。 |
selevel | 文件或目录的 SELinux 安全上下文。 |
serole | 文件或目录的 SELinux 角色。 |
setype | 文件或目录的 SELinux 类型。 |
seuser | 文件或目录的 SELinux 用户。 |
unsafe_writes | 是否启用不安全的写入模式。如果为 yes,则在写入文件之前不会创建备份。默认为 no。 |
这里重点说一下
state
1 | file:表示要求目标主机上存在指定的文件。如果文件已经存在,则不执行任何操作;如果文件不存在,则会创建它。 |
copy模块主要用于将文件复制到远程服务器,可以指定文件的源路径、目标路径、owner、group、mode等信息,等等
示例:复制文件到指定目录
1 | # -b 切换到root用户下执行 |
1 | # copy.yml |
常用参数说明:ansible-doc -s copy
参数 | 描述 |
---|---|
src | 源文件的路径。 |
dest | 目标文件的路径。 |
backup | 是否备份目标文件。如果为 yes,则在复制目标文件之前会创建一个备份文件。默认为 yes。 |
content | 要写入目标文件的内容。 |
directory_mode | 目标目录的权限。只有当目标是一个目录时才会生效。 |
follow | 是否跟随符号链接。如果为 yes,则会跟随符号链接进行操作。默认为 yes。 |
force | 是否强制覆盖目标文件。如果为 yes,则强制复制源文件,即使目标文件已经存在。默认为 no。 |
group | 目标文件的所属组。 |
mode | 目标文件的权限。 |
owner | 目标文件的所有者。 |
remote_src | 指定源文件是否在远程主机上。如果为 yes,表示源文件在远程主机上。默认为 no。 |
fetch模块主要用于将文件从远程服务器复制到本地,可以指定文件的源路径、目标路径、owner、group、mode等信息,等等
示例:将文件从远程服务器复制到本地
1 | ansible webservers -m fetch -a "src=/tmp/a.txt dest=/tmp/ flat=yes" |
1 | # fetch.yml |
常用参数说明:ansible-doc -s fetch
参数 | 描述 |
---|---|
src | 远程主机上要拉取的文件的路径。 |
dest | 本地主机上文件的目标路径。 |
flat | 是否将文件放置在顶层目录中。如果为 yes,则所有文件都将放置在一个目录中。默认为 no。 |
fail_on_missing | 如果为 yes,则在源文件不存在时失败。默认为 yes。 |
validate_checksum | 是否验证远程文件的校验和。默认为 no。 |
cron模块主要用于在远程服务器上创建、修改、删除定时任务
示例:创建定时任务
1 | ansible webservers -m cron -a 'name="restart httpd" hour=*/5 job="systemctl restart httpd"' |
1 | # cron.yml |
常用参数说明:ansible-doc -s cron
参数 | 描述 |
---|---|
name | cron 任务的名称。 |
minute | cron 任务执行的分钟。 |
hour | cron 任务执行的小时。 |
day | cron 任务执行的日期。 |
month | cron 任务执行的月份。 |
weekday | cron 任务执行的星期几。 |
job | 要执行的命令或脚本。 |
cron_file | 要操作的 cron 文件的路径。默认为 /etc/crontab。 |
state | cron 任务的状态。可选值包括 present(默认)和 absent。表示要求任务存在或不存在。 |
user | cron 任务的执行用户。默认为 root。 |
backup | 是否备份 cron 文件。可选值包括 true 和 false。 |
state
1 | present:表示要求指定的 cron 任务存在。如果指定的 cron 任务不存在,则 Ansible 将会创建它。如果已经存在,则不执行任何操作。 |
yum模块主要用于在远程服务器上安装、卸载、更新软件包,可以指定软件包的名称、版本、repo等信息,等等
示例:安装软件包
1 | # 安装软件包,相当于 yum install httpd |
1 | # yum.yml |
常用参数说明:ansible-doc -s yum
参数 | 解释 |
---|---|
name | 指定要操作的包的名称。 |
update_cache | 指定是否在执行操作之前更新 yum 缓存。可选值为 yes 或 no 。默认为 yes 。 |
disable_gpg_check | 指定是否禁用 GPG 检查。如果为 yes ,则禁用 GPG 检查。默认为 no 。 |
disable_plugin | 指定是否禁用指定的 yum 插件。可以是一个插件名称的列表。 |
enablerepo | 指定要启用的仓库。可以是一个仓库名称的列表。 |
disablerepo | 指定要禁用的仓库。可以是一个仓库名称的列表。 |
installroot | 指定要安装软件包的根目录。 |
security | 指定是否只安装安全更新。如果为 yes ,则只安装安全更新。默认为 no 。 |
list | 指定是否列出所有已安装的包。如果为 yes ,则列出已安装的包。默认为 no 。 |
state | 指定软件包的状态。可选值为 present 、latest 、absent 、installed 、removed 。默认为 present 。 |
这里重点说一下
state
1 | present: 表示要求目标主机上存在指定的软件包。如果软件包已经安装,则不执行任何操作;如果软件包未安装,则会安装它。 |
service
命令service模块主要用于在远程服务器上启动、停止、重启、重新加载、启用、禁用、检查服务,可以指定服务的名称、状态、启动方式等信息,等等
示例:启动服务
1 | ansible webservers -m service -a "name=httpd state=started" |
1 | # service.yml |
常用参数说明:ansible-doc -s service
参数 | 描述 |
---|---|
name | 服务的名称。 |
state | 服务的状态。可选值包括 started(已启动)、stopped(已停止)、restarted(已重启)。 |
enabled | 是否在启动时自动启用服务。如果为 yes,则在系统启动时自动启动服务。默认为 yes。 |
pattern | 匹配服务的模式。默认情况下为服务名称。 |
sleep | 在重新启动服务之前等待的秒数。 |
arguments | 启动或停止服务时要传递的参数。 |
state
1 | started:表示要求服务处于已启动状态。如果指定的服务未启动,则 Ansible 将尝试启动该服务。如果服务已经处于运行状态,则不执行任何操作。 |
centos7+
,调用的是systemctl
systemd模块用于控制 systemd 后台服务,允许你启动、重新启动、停止或者重新加载 systemd 服务。此外,你也可以使用它来使服务在系统启动时自动启动或禁止自动启动。除此之外,systemd 模块还允许你检查服务的状态。
示例
1 | ansible webservers -m systemd -a "name=httpd state=restarted" |
1 | # systemd.yml |
主要参数说明:ansible-doc -s systemd
参数 | 描述 |
---|---|
enabled | 指定服务是否应该在启动时自动启用。可选值为 yes 或 no。默认为 yes。 |
masked | 指定服务是否应该被置为 masked 状态,禁止手动启动。可选值为 yes 或 no。默认为 no。 |
name | 服务的名称。 |
state | 指定服务的状态。可选值为 started(启动)、stopped(停止)、restarted(重新启动)、reloaded(重新加载) |
script模块主要用于在远程服务器上执行本地的脚本
示例:执行脚本
1 | ansible webservers -m script -a "chdir=/tmp /tmp/a.sh" |
1 | # script.yml |
常用参数说明:ansible-doc -s script
参数 | 描述 |
---|---|
chdir | 在远程主机上执行脚本之前切换到的目录。 |
free_form | 要在远程主机上执行的脚本内容。 |
creates | 如果指定的文件已经存在,则不执行脚本。 |
executable | 指定要使用的脚本解释器。 |
removes | 在执行脚本之后,如果指定的文件存在,则删除该文件。 |
cmd | 指定要执行的命令。 |
decrypt | 指定要解密的源文件。 |
user模块主要用于在远程服务器上创建、修改、删除用户,可以指定用户的名称、密码、uid、gid、home、shell等信息,等等
示例:创建用户
1 | ansible webservers -m user -a "name=nginx group=nginx shell=/sbin/nologin create_home=no" |
1 | # user.yml |
常用参数说明:ansible-doc -s user
参数 | 描述 |
---|---|
append | 是否将用户添加到现有组,而不是替换组。 |
comment | 对用户的注释信息。 |
createhome | 是否创建用户的家目录。 |
expires | 用户帐户过期日期。 |
force | 是否强制创建或更改用户帐户。 |
generate_ssh_key | 是否生成用户的 SSH 密钥对。 |
group | 用户所属组的名称或 ID。 |
groups | 用户所属的其他组。 |
home | 用户的家目录路径。 |
login_class | 用户登录类。 |
move_home | 是否在更改用户家目录路径时移动其内容。 |
name | 用户的名称。 |
non_unique | 允许用户具有非唯一的数字 ID。 |
password | 用户的密码哈希值或加密后的密码。 |
remove | 是否删除用户。 |
shell | 用户的 shell。 |
state | 用户帐户的状态。 |
system | 是否为系统用户。 |
uid | 用户的数字 ID。 |
state
1 | present:表示要求指定的用户账户存在。如果指定的用户账户不存在,则 Ansible 将会创建该账户。如果用户账户已经存在,则不执行任何操作。 |
get_url模块负责下载文件到目标主机。
示例
1 | ansible webservers -m get_url -a "url=https://www.example.com dest=/tmp" |
1 | # get_url.yml |
主要参数说明:ansible-doc -s get_url
参数 | 描述 |
---|---|
url | 要下载的文件的 URL 地址。 |
dest | 下载文件保存的目标路径。 |
force | 是否强制覆盖目标路径中的文件。可选值为 yes 或 no。默认为 yes。 |
timeout | 下载超时时间,单位为秒。默认为 10 秒。 |
validate_certs | 是否验证 SSL 证书。可选值为 yes 或 no。默认为 yes。 |
owner | 下载后文件的所有者。 |
group | 下载后文件的所属组。 |
mode | 下载后文件的权限模式。 |
backup | 是否创建备份文件。可选值为 yes 或 no。默认为 no。 |
headers | 附加的 HTTP 请求头。 |
force_basic_auth | 是否强制使用 HTTP 基本身份验证。可选值为 yes 或 no。默认为 yes。 |
http_agent | 用于 HTTP 请求的代理。 |
lineinfile模块主要用于在远程主机上查找和替换文件中的行,可以指定要查找的行、要替换的行、要添加的行等信息,等等
示例:设置环境变量,需要注意的是此时环境变量在剧本的上下文中是不生效的,如果需要在剧本上下文生效的环境变量,可以使用 shell
模块
1 | ansible webservers -m lineinfile -a "path=/etc/profile line='PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin' state=present backup=yes" |
1 | # lineinfile.yml |
主要参数说明:ansible-doc -s lineinfile
参数 | 描述 |
---|---|
path | 要修改的文件的路径。 |
line | 要添加、修改或删除的行内容。 |
state | 指定要执行的操作。 |
regexp | 用于匹配行的正则表达式。 |
search_string | 用于匹配行的字符串。 |
backup | 是否创建备份文件。可选值为 yes 或 no。默认为 no。 |
backrefs | 是否允许在 regexp 中使用反向引用。 |
insertbefore | 指定一个行,将新行插入到它之前。 |
insertafter | 指定一个行,将新行插入到它之后。 |
firstmatch | 是否只匹配第一个匹配项。 |
state说明
1 | present:确保指定的行存在于文件中。如果文件中不存在指定的行,则会添加该行。如果文件中已经存在该行,则不做任何改变。 |
ansible-playbook是Ansible的核心命令,用于执行playbook文件,playbook文件是Ansible执行任务的最小单元,一个playbook文件可以包含多个play,每个play可以包含多个task,每个task可以包含多个module,每个module可以执行一个操作,比如创建目录、安装软件包、启动服务、执行脚本等
上面在介绍ansible模块时,我们已经编写了一些playbook文件,就是那些yml文件,运行时也是通过ansible-playbook命令执行的,但是基本上都是单个任务单个模块,下面我们来看一个复杂一些的示例
示例:安装nginx并配置
1 | # playbook.yml |
优化1
问题: 上面的剧本有个问题,就是如果系统已经安装了nginx,则运行这个剧本还是会重新安装
解决方法: 先判断nginx是否已经安装,如果已经安装,直接跳过安装步骤,直接执行启动命令,否则执行安装步骤,然后再执行启动命令
1 |
|
register :用于将命令执行的结果保存到变量中,我们可以调用变量的属性,比如rc表示命令返回的状态码($?),stdout表示命令的输出,stderr表示命令的错误输出
ignore_errors :用于忽略错误
loop :用于循环列表
when :用于判断条件,只有条件满足时才执行
优化2
问题: 上面的步骤有点多,比如下载、解压、编译nginx,这些都是在没有安装nginx的情况下要运行的任务
解决方法: 使用shell模块可以将这些步骤封装成一个任务
1 |
|
优化3
问题: 上面的剧本虽然已经满足了我的需求,但是不够简练,因为很多任务并不是主要任务,我需要在每个任务中进行条件判断来决定是否执行该任务,能否只保留主要任务,其它任务只有当这些主要任务成功运行了才会被运行呢?比如这里的主要任务就是两个:1-判断是否安装了nginx,没安装就去安装,2-判断是否启动了nginx,没启动就去启动
解决方法: 使用基于handlers的notify语句,这样可以减少重复执行任务的次数
何为handlers
Handler 本身是一种非同步的 callback function ,在这里则是指关连于特定 tasks 的事件 (event) 触发机制。当关联 handler 的 tasks 状态为被改变 (changed) 且都已被执行时,才会触发一次
何为 tasks 状态为被改变 (changed)
在Ansible中,task的状态会根据任务执行的结果而改变。当一个任务成功完成并且使得系统的状态与预期不同(即执行了一些更改),那么这个任务的状态就会被标记为"changed"。
具体来说,task状态为"changed"的条件包括但不限于以下情况:
1 | 文件变更:例如复制、创建、删除文件等操作导致了文件系统的变更。 |
1 |
|
优化4
问题: 上面的剧本中nginx的安装版本和安装路径都是写死的,能否动态配置呢?
解决方法: 提取变量,使用vars
1 |
|
1 | # 默认 |
小贴士
ansible_facts
变量获取主机信息,如IP地址、MAC地址、操作系统版本等。也就是通过setup
模块获取的信息。ansible_facts
变量时需要开启gather_facts
选项,默认是开启的。ansible_facts
变量如下,注意这里属性名称前是不加ansible_
前缀的:1 | ansible_facts['distribution']: 远程主机的操作系统分发名称。 |
Ansible Playbook Template是一种Ansible的特性,它允许您在Playbook中使用Jinja2
模板语言来动态生成配置文件或其他文本文件。通过使用模板,您可以根据变量、条件、循环等动态信息来生成目标文件,从而使配置文件更具可扩展性和灵活性。
下面是使用Ansible Playbook Template的一些常见用法和特性:
1 | Jinja2模板语言: Ansible Playbook Template使用Jinja2模板语言,这是一种功能强大的模板引擎,支持变量替换、条件语句、循环语句等功能。 |
Jinja2模板文件后缀为.j2
,常用语法如下:
1 | # 打印变量 |
结合上面部署nginx的示例,我这里增加一个任务,就是替换nginx发布目录下的index.html,然后重启nginx,这里给出index.html.j2
1 |
|
接着我们修改剧本yml
1 |
|
替换之后的效果
1 |
|
template模块
的常用参数说明 ansible-doc -s template
参数 | 必需 | 默认值 | 描述 |
---|---|---|---|
src | 是 | 无 | 模板文件的路径。 |
dest | 是 | 无 | 目标文件的路径。 |
force | 否 | false | 如果目标文件已经存在,是否强制覆盖。 |
backup | 否 | false | 如果设置为true,则在覆盖目标文件之前创建备份。 |
unsafe_writes | 否 | false | 如果设置为true,则会跳过文件的暂时性写入保护(如确保在写入文件之前不会更改其内容)。 |
newline_sequence | 否 | \n | 用于生成文件时的换行符序列。 |
validate | 否 | 无 | 要应用于生成文件的验证器脚本的路径。 |
mode | 否 | 无 | 目标文件的权限模式。 |
owner | 否 | 无 | 目标文件的所有者。 |
group | 否 | 无 | 目标文件的所属组。 |
这里要注意template模块
与copy
模块的区别,前者在上传时会进行变量替换。
Ansible中的Role是一种组织和管理剧本的方法,它允许您将相关的任务、变量、文件和处理程序组合到一个可重用的单元中。Role使得您可以更轻松地管理和组织大型的Ansible项目,并促进了可维护性和复用性。
Role的特性
1 | 组织性:Role允许您将相关的任务和文件组织在一起,使得代码更易于理解和维护。每个Role通常都有一个特定的目的,例如安装特定的软件、配置服务或执行特定的系统管理任务。 |
创建role
1 | # 在当前目录下创建一个名称为nginx-install的role,实际上就是创建一个名称为nginx-install的目录结构 |
1 | files:存放由copy或script模块等调用的文件 |
ansible查找role的路径,推荐放到/etc/ansible/roles
1 | $(pwd)/roles |
nginx-install/tasks/main.yml
1 |
|
nginx-install/handlers/main.yml
1 |
|
nginx-install/vars/main.yml
1 |
|
小贴士
tasks
、handlers
,其目录中都含有一个main.yml文件,这个文件是必须存在的include
进行包含1 |
|
tasks
或者handlers中
还可以使用include_tasks
进行包含1 |
|
vars
里也可以定义多个yml文件存储变量,然后在tasks中通过include_vars
进行包含,注意,必须放在task的yml文件中
1 | # 此时不需要加上vars路径,会自动从vars目录下查找 |
将模板文件和图片文本保存到对应的路径
nginx-install/templates/index.html.j2
nginx-install/files/me.png
执行
假设我们将创建的角色安装到了/etc/ansible/roles
下
创建启动剧本
nginx-install-start.yml
,我们也可以修改tests/test.yml
1 | - name: 安装nginx并配置 |
执行role
1 | ansible-playbook nginx-install-start.yml |
用于从ansible-galaxy官网上查找,下载role
和collection
的工具,如何下载和使用网站上都有说明。
我们也可以将自己创建好的role
发布到ansible-galaxy
上,可以参考 Ansible Role Publish To Galaxy
1 | # 搜索role |
ansible-galaxy同时支持下载 collection
1 | # 查看已经安装的collection |
Ansible Collection 是 Ansible 社区为了更好地管理和组织 Ansible 角色、模块、插件等内容而引入的概念。它可以被视为一种打包机制,用于将相关的 Ansible 内容打包成单独的单元,使得其更易于分享、安装和维护。
具体来说,Ansible Collection 具有以下作用:
1 | 组织和管理角色、模块和插件:通过 Collection,可以将相关的角色、模块和插件打包在一起,形成逻辑上的单元,提高了内容的组织性和可管理性。 |
总的来说,Ansible Collection 提供了一种更高级别的组织和管理方式,使得 Ansible 内容更易于分享、使用和维护,从而提高了 Ansible 的整体生态系统的健壮性和可用性。
小贴士
become
切换用户到root用户,其实在ansible.cfg
中我们可以配置become_user
,这样我们就可以不用每次都切换用户了become
会以一个全新的环境执行任务,所以其不会包含原用户的环境变量。可以理解为其是通过sudo su
切换到root,而非sudo -i
。关于ansible的知识点还有很多没有研究到,后面会慢慢补吧……
关于将Ansible Role发布到Galaxy的详细介绍可以参看 Ansible Role Publish To Galaxy
关于将Ansible Collection发布到Galaxy的详细介绍可以参看 Ansible Collection Publish To Galaxy
本文介绍mtools工具的使用
mtools官方文档,mtools-github
mtools提供的mlaunch
是一个基于python的mongo环境管理工具,可以方便的启动、停止、重启、kill mongo进程,还可以查看mongo进程的运行状态,可以方便初学者快速搭建本地mongo环境,但是其只能在一台机器上运行。
mtools基于python3,支持mongo4+以上版本,但是mtools并且不会为我们安装mongodb,所以需要我们自己先在系统中安装好mongodb,mongodb的安装可以参考MongoDB7.0的安装
按照官网的说法,mtools支持的Python版本为 3.7|3.8|3.9|3.10,其他版本的Python目前不受支持或测试,但作者的python版本为3.11.3,并没有出现兼容性问题。
1 | ➜ ~ python3 --version |
安装mtools依赖
1 | pip3 install python-dateutil psutil pymongo |
安装mtools
1 | pip3 install mtools |
安装mtools会同时为我们安装mtools的所有工具,如果只希望安装mlaunch,可以使用下面的命令
mtools提供的其它工具,如mlogfilter
,mloginfo
,mplotqueries
等并不支持logv2
格式的日志,logv2
是mongodb4.4+
以后的加入的,从mongodb5.0
以后logv2
为默认的日志格式,所以这里不做介绍。
1 | pip3 install 'mtools[mlaunch]' |
查看mlaunch版本
1 | ➜ ~ which mlaunch |
此命令初始化并启动MongoDB独立实例、副本集或分片集群,它只需要为每个环境调用一次。
1 | # 快速构建一个单节点,数据目录在当前路径下的data目录下 |
1 | # 快速构建一个3节点的复制集 |
1 | # 快速构建一个2个分片的分片集群,每个分片是一个包含3每个节点的复制集,包含一个3个节点配置复制集,并且启动3个mongos路由 |
以下命令运行时必须指定数据目录,默认为当前路径下的data目录,可以使用
--dir
指定数据目录,因为需要通过./data/.mlaunch_startup
来获取mongo的构建信息。以分片集群举例:
查看集群状态
1 | # 显示mongo进程的列表 |
查看启动命令
1 | $ mlaunch list --startup |
1 | # 关闭全部mongo进程,注意:如果开启了认证,stop会使用默认的用户名和密码进行认证(user/password),否则需要使用 kill |
1 | # 启动全部mongo进程 |
1 | # 重启,不推荐使用,除了单实例,复制集和分片集群都会出问题 |
1 | # 杀掉全部mongo进程 |
支持的tags
1 | all:环境中的所有节点。 |
安装完linux后,一些常用的设置可以方便我们今后的使用,比如关闭SELinux,增加文件描述符限制,等等。
本文基于CentOS8。
SELinux(Security-Enhanced Linux)是一种基于安全策略的 Linux 安全模块,其主要目的是提供对各种应用程序和进程的更细粒度的访问控制。
SELinux 的安全方案主要基于以下两个策略:
在实际工作中,SELinux 可以用来阻止或限制恶意或损坏的应用程序对系统造成的损害。当配置正确时,SELinux 提供的强大安全机制可以显著提高系统的安全性。
但是,SELinux 也可能会带来一些问题,例如,它可能会导致某些应用程序无法正常工作,或者它可能会在某些情况下导致性能下降。因此,许多 Linux 发行版默认会关闭 SELinux,以避免这些问题。
通过以下命令,可以查看当前的 SELinux 状态:
1 | # 如果当前状态为 Enforcing,表示 SELinux 已启用,否则为 Disabled。 |
临时关闭 SELinux,可以使用以下命令:
1 | $ setenforce 0 |
永久关闭 SELinux,可以使用以下命令:
1 | $ vi /etc/selinux/config |
临时开启 SELinux,可以使用以下命令:
1 | $ setenforce 1 |
永久开启 SELinux,可以使用以下命令:
1 | $ vi /etc/selinux/config |
每个运行中的进程都有一个限制,即它可以同时打开的最大文件描述符数量。这个限制对于避免过度使用系统资源非常重要。然而,在某些情况下,你可能需要增加这个限制来满足应用的需求。
Linux 服务器默认的文件描述符限制为 1024,如果需要使用更多的文件描述符,可以使用以下命令来增加限制:
1 | # 临时设置,即刻生效 |
这样的设置既为 root 用户设置了文件描述符(句柄)的软硬限制数量为65535,也为系统中所有其他用户设置了同样的限制。所谓“软限制”是指用户可以达到但不能超过的限制,而“硬限制”是指系统设置的绝对限制。即软限制不能大于硬限制的值。
通过以下命令,可以查看当前的文件描述符限制:
1 | # 查看的是当前 bash 的“软”文件描述符限制 |
本文介绍如何使用MongoDB7.0的索引
MongoDB版本7.0.6
索引支持在MongoDB中高效执行查询。如果没有索引,MongoDB必须扫描集合中的每个文档才能返回查询结果。如果查询存在适当的索引,MongoDB使用该索引来限制它必须扫描的文档数量。
虽然索引可以提高查询性能,但添加索引对写入操作的性能有负面影响。对于写入读数比较高的集合,索引很昂贵,因为每个插入还必须更新任何索引。
所以合理的创建索引,即可以提升查询性能,又不会对写操作造成太大的影响。
MongoDB采用 B-Tree (准确的说是 B+Tree)
做索引,索引创建在colletions上。
1 | db.collection.createIndex( keys, options ) |
Key 值为你要创建的索引字段,1 按升序创建索引, -1 按降序创建索引
options 选项
参数 | 类型 | 描述 |
---|---|---|
background | Boolean | 建索引过程是否阻塞其它数据库操作,设置为 true 则以后台方式创建索引 |
unique | Boolean | 建立的索引是否唯一,设置为 true 则创建唯一索引 |
name | string | 索引的名称 |
dropDups | Boolean | 3.0+版本已废弃,在建立唯一索引时是否删除重复记录 |
sparse | Boolean | 对文档中不存在的字段数据是否启用索引 |
expireAfterSeconds | integer | 指定一个以秒为单位的数值,完成 TTL 设定,设定集合的生存时间 |
v | index version | 索引的版本号 |
weights | document | 索引权重值,数值在 1 到 99,999 之间,表示该索引相对于其他索引字段的得分权重 |
default_language | string | 对于文本索引,该参数决定了停用词及词干和词器的规则的列表 |
language_override | string | 对于文本索引,该参数指定了包含在文档中的字段名,语言覆盖默认的 language |
与大多数数据库一样,MongoDB支持各种丰富的索引类型,包括单键索引、复合索引,唯一索引等一些常用的结构。由于采用了灵活可变的文档类型,因此它也同样支持对嵌套字段、数组进行索引。通过建立合适的索引,我们可以极大地提升数据的检索速度。在一些特殊应用场景,MongoDB还支持地理空间索引、文本检索索引、TTL索引等不同的特性。
单键索引是MongoDB最简单的索引类型,它将一个字段作为索引键,索引键值唯一。
默认情况下,MongoDB会在ID字段上创建一个单键索引,ID字段是文档的唯一标识符,MongoDB会自动创建一个ID字段,如果用户自己创建ID字段,MongoDB会自动将ID字段作为单键索引。
单键索引的创建方式如下:
1 | # 1表示正序,-1表示倒序 |
复合索引是将多个字段作为索引键,其性质和单字段索引类似。但不同的是,复合索引中字段的顺序、字段的升降序对查询性能有直接的影响,因此在设计复合索引时则需要考虑不同的查询场景。
复合索引的创建方式如下:
1 | db.books.createIndex({title:1, author.name:1}) |
多键索引是将一个字段作为索引键,该字段可以是嵌套文档、数组等复杂数据类型。
多键索引的创建方式如下:
1 | # tags字段为数组 |
Hash索引是MongoDB中的一种特殊的索引类型,它将字段的值计算出一个哈希值,然后将该哈希值作为索引键。由于哈希值的唯一性,因此MongoDB在创建Hash索引时,不会对索引键值进行排序。
Hash索引的创建方式如下:
1 | db.books.createIndex({title:"hashed"}) |
通配符索引是MongoDB中的一种特殊的索引类型,它允许在索引键值中包含通配符,从而支持对通配符匹配的查询。
MongoDB 4.2 引入了通配符索
通配符索引的创建方式如下:
1 | # 示例数据 |
通配符索引是稀疏的,不索引空字段。因此,通配符索引不能支持查询字段不存在的文档。
1 | # 通配符索引不能支持以下查询 |
通配符索引为文档或数组的内容生成条目,而不是文档/数组本身。因此通配符索引不能支持精确的文档/数组相等匹配。通配符索引可以支持查询字段等于空文档{}的情况
1 | #通配符索引不能支持以下查询: |
唯一索引的创建方式如下:
1 | # 单键索引支持唯一约束 |
唯一性索引对于文档中缺失的字段,会使用null值代替,因此不允许存在多个文档缺失索引字段的情况。
对于分片的集合,唯一性约束必须匹配分片规则。换句话说,为了保证全局的唯一性,分片键必须作为唯一性索引的前缀字段。
部分索引仅对满足指定过滤器表达式的文档进行索引。通过在一个集合中为文档的一个子集建立索引,部分索引具有更低的存储需求和更低的索引创建和维护的性能成本。
部分索引的创建方式如下:
1 | # 符合条件{author: {$exists: true}},即存在作者,才对title创建升序索引 |
partialFilterExpression(筛选器表达式)选项接受指定过滤条件的文档:
- 等式表达式(例如:field: value或使用$eq操作符)
- $exists: true
- $gt, $gte, $lt, $lte
- $type
- 顶层的$and
注意:如果同时指定了partialFilterExpression和唯一约束,那么唯一约束只适用于满足筛选器表达式的文档。如果文档不满足筛选条件,那么带有惟一约束的部分索引不会阻止插入不满足惟一约束的文档。
索引的稀疏属性确保索引只包含具有索引字段的文档的条目,索引将跳过没有索引字段的文档。即只对存在字段的文档进行索引(包括字段值为null的文档)。
如果稀疏索引会导致查询和排序操作的结果集不完整,MongoDB将不会使用该索引,除非hint()明确指定索引。
稀疏索引的创建方式如下:
1 | # 数据准备 |
同时具有稀疏性和唯一性的索引可以防止集合中存在字段值重复的文档,但允许不包含此索引字段的文档插入。
1 | # 删除之前创建的索引 |
MongoDB 可以使用它在一定时间或特定时钟时间后自动从集合中删除文档,就是带有过期时间的索引,到期后,MongoDB会自动删除这些过期的文档。
TTL索引只能创建在日期字段上,当文档过期后,MongoDB会自动删除这些文档。
TTL索引的创建方式如下:
1 | # 数据准备 |
TTL 索引不保证过期数据会在过期后立即被删除。文档过期和 MongoDB 从数据库中删除文档的时间之间可能存在延迟。删除过期文档的后台任务每 60 秒运行一次。因此,在文档到期和后台任务运行之间的时间段内,文档可能会保留在集合中。
TTL索引在创建之后,仍然可以对过期时间进行修改。这需要使用collMod命令对索引的定义进行变更
1 | db.runCommand( { |
隐藏索引对查询规划器不可见,不能用于查询。
通过对规划器隐藏索引,用户可以在不实际删除索引的情况下评估删除索引的潜在影响。如果影响是负面的,用户可以取消隐藏索引,而不必重新创建已删除的索引。
隐藏索引的创建方式如下:
1 | # 创建隐藏索引 |
1 | # 查看索引信息 |
1 | # 删除集合指定索引 |
为每一个查询建立合适的索引
创建合适的复合索引,不要依赖于交叉索引
1 | #查找所有年龄小于30岁的深圳市马拉松运动员 |
复合索引字段顺序:匹配条件在前,范围条件在后(Equality First, Range After)
尽可能使用覆盖索引(Covered Index)
建索引要在后台运行
避免设计过长的数组索引
explain执行计划的作用是:查看MongoDB执行查询时的执行计划。
explain执行计划的使用方式如下:
1 | # 语法 |
verbose :可选参数,表示执行计划的输出模式,默认queryPlanner
模式名字 | 描述 |
---|---|
queryPlanner | 执行计划的详细信息,包括查询计划、集合信息、查询条件、最佳执行计划、查询方式和 MongoDB 服务信息等 |
executionStats | 最佳执行计划的执行情况和被拒绝的计划等信息 |
allPlansExecution | 选择并执行最佳执行计划,并返回最佳执行计划和其他执行计划的执行情况 |
输出结果中重点查看stage
,比如queryPlanner
下的winningPlan.stage
stage
类型
状态 | 描述 |
---|---|
COLLSCAN | 全表扫描 |
IXSCAN | 索引扫描 |
FETCH | 根据索引检索指定文档 |
SHARD_MERGE | 将各个分片返回数据进行合并 |
SORT | 在内存中进行了排序 |
LIMIT | 使用 limit 限制返回数 |
SKIP | 使用 skip 进行跳过 |
IDHACK | 对 _id 进行查询 |
SHARDING_FILTER | 通过 mongos 对分片数据进行查询 |
COUNTSCAN | count 不使用索引进行 count 时的 stage 返回 |
COUNT_SCAN | count 使用了索引进行 count 时的 stage 返回 |
SUBPLAN | 未使用到索引的 $or 查询的 stage 返回 |
TEXT | 使用全文索引进行查询时候的 stage 返回 |
PROJECTION | 限定返回字段时候 stage 的返回 |
执行计划的返回结果中尽量不要出现以下stage
:
1 | - COLLSCAN(全表扫描) |
下面是一个结合各种索引类型的示例,假设我们正在为一个电商应用创建和管理一个MongoDB集合products,其中包含以下字段:
1 | _id: 默认的ObjectId类型,作为主键(已自动带有唯一性索引)。 |
创建集合与插入文档
1 | // 假设已经连接到数据库并选择了一个database |
单键索引 - 查询商品按价格排序
1 | db.products.createIndex({ price: 1 }); |
复合索引 - 按品牌和价格查询,并进行排序:
1 | db.products.createIndex({ brand: 1, price: -1 }); |
多键索引 - 根据商品标签进行搜索
1 | db.products.createIndex({ "tags": 1 }, { "sparse": true }); // 如果不是每个文档都有tags,可以使用sparse选项以节省空间 |
唯一索引 - 确保品牌名称不重复
1 | db.products.createIndex({ brand: 1 }, { unique: true }); |
本文介绍如何使用SpringBoot实现MongoDB7.0的聚合操作
SpringBoot版本3.2.3,MongoDB版本7.0.6
聚合操作允许用户处理多个文档并返回计算结果
聚合操作包含三类
db.collection.countDocument()
, db.collection.distinct()
聚合管道是MongoDB中非常强大的功能,它允许用户将多个操作组合在一起,以实现复杂的数据处理。
从效果而言,聚合管道相当于 SQL 查询中的 GROUP BY、 LEFT OUTER JOIN 、 AS等。
整个聚合运算过程称为管道(Pipeline),它是由多个阶段(Stage)组成的, 每个管道:
聚合管道操作语法
1 | pipeline = [$stage1, $stage2, ...$stageN]; |
常用的聚合阶段运算符
阶段运算符 | 描述 | SQL等价运算符 |
---|---|---|
$match | 过滤文档 | WHERE |
$project | 投影,改变文档的形状和内容 | SELECT filedName AS newName |
$group | 将文档分组 | GROUP BY |
$sort | 对文档进行排序 | ORDER BY |
$limit | 限制结果集的大小 | LIMIT |
$skip | 跳过指定数量的文档 | OFFSET |
$unwind | 展开数组 | - |
$lookup | 从其他集合中获取相关文档,左外连接 | LEFT OUTER JOIN |
$out | 将结果集输出到新的集合 | - |
$geoNear | 按照地理位置附近的顺序返回文档 | - |
$graphLookup | 执行递归查询 | - |
$addFields | 添加新字段 | - |
$bucket | 根据指定条件将文档分组成桶 | - |
$facet | 允许在单个聚合阶段内执行多个独立的子聚合 | - |
聚合表达式
获取字段信息
1 | $<field> : 用 $ 指示字段路径 |
常量表达式
1 | $literal :<value> : 指示常量 <value> |
系统变量表达式
1 | $$<variable> 使用 $$ 指示系统变量 |
本示例使用SpringBoot实现MongoDB7.0的聚合操作
初始化数据
1 | /* |
将原始字段投影成指定名称, 如将集合中的 title 投影成 name
1 | // db.books.aggregate([{$project:{name:"$title"}}]) |
剔除不需要的字段
1 | // db.books.aggregate([{$project:{name:"$title",_id:0,type:1,author.name:1}}]) |
过滤出指定条件的文档
1 | // db.books.aggregate([{$match:{type:"technology"}}]) |
组合其它管道
1 | /* |
1 | /* |
按指定的表达式对文档进行分组,并将每个不同分组的文档输出到下一个阶段。输出文档包含一个_id字段,该字段按键包含不同的组。
输出文档还可以包含计算字段,该字段保存由$group的_id字段分组的一些accumulator表达式的值。 $group不会输出具体的文档而只是统计信息。
语法
1 | { $group: { _id: <expression>, <field1>: { <accumulator1> : <expression1> }, ... } } |
accumulator操作符
名称 | 描述 | 类比sql |
---|---|---|
avg | 计算均值 | avg |
first | 返回每组第一个文档,如果有排序,按照排序,如果没有按照默认的存储的顺序的第一个文档。 | limit 0,1 |
last | 返回每组最后一个文档,如果有排序,按照排序,如果没有按照默认的存储的顺序的最后个文档。 | - |
max | 根据分组,获取集合中所有文档对应值得最大值。 | max |
min | 根据分组,获取集合中所有文档对应值得最小值。 | min |
push | 将指定的表达式的值添加到一个数组中。 | - |
addToSet | 将表达式的值添加到一个集合中(无重复值,无序)。 | - |
sum | 计算总和 | sum |
stdDevPop | 返回输入值的总体标准偏差(population standard deviation) | - |
stdDevSamp | 返回输入值的样本标准偏差(the sample standard deviation) | - |
book的数量,收藏总数和平均值
1 | /* |
统计每个作者的book收藏总数
1 | /* |
统计每个作者的每本book的收藏数
1 | /* |
每个作者的book的type合集
1 | /* |
可以将数组拆分为单独的文档
语法
1 | { |
姓名为xx006的作者的book的tag数组拆分为多个文档
1 | /* |
使用includeArrayIndex
选项来输出数组元素的数组索引
1 | /* |
每个作者的book的tag合集
1 | /* |
使用preserveNullAndEmptyArrays
选项在输出中包含缺少tag字段,null或空数组的文档
1 | # 初始化数据,加入一些tag为空数组或不存在tag的文档 |
1 | /* |
$limit:限制传递到管道中下一阶段的文档数
$skip:跳过传递到管道中下一阶段的文档数
$sort:对传递到管道中下一阶段的文档进行排序
姓名为xx006的作者的book的tag数组拆分为多个文档,按照收藏数降序排序,跳过2个文档,取5个文档
1 | /* |
标签的热度排行,标签的热度则按其关联book文档的收藏数(favCount)来计算
1 | /* |
$bucket:根据指定的条件和边界,将文档分组到不同的桶中
统计book文档收藏数[0,10),[10,60),[60,80),[80,100),[100,+∞)
1 | /* |
$lookup:将文档中的一个字段的值与另一个集合中的文档进行匹配,然后将匹配的文档添加到当前文档中
语法
1 | db.collection.aggregate([{ |
名称 | 描述 |
---|---|
from | 同一个数据库下等待被Join的集合。 |
localField | 源集合中的match值,如果输入的集合中,某文档没有 localField这个Key(Field),在处理的过程中,会默认为此文档含有 localField:null的键值对。 |
foreignField | 待Join的集合的match值,如果待Join的集合中,文档没有foreignField值,在处理的过程中,会默认为此文档含有 foreignField:null的键值对。 |
as | 为输出文档的新增值命名。如果输入的集合中已存在该值,则会覆盖掉。 |
其语法功能类似于下面的伪SQL语句
1 | SELECT *, <output array field> |
准备数据
1 | # 顾客信息 |
查询顾客的订单信息,关联字段为customerCode顾客号码
1 | /* |
根据订单信息关联顾客信息和订单明细
1 | /* |
本文介绍如何使用SpringBoot实现MongoDB7.0的单集合的CURD操作
SpringBoot版本3.2.3,MongoDB版本7.0.6
spring-data-mongodb
与MongoDB
版本对应关系可以参看官方文档
spring-boot-starter-data-mongodb
与 spring-data-mongodb
版本对应关系
spring-boot-starter-data-mongodb 版本 | spring-data-mongodb 版本 | MongoDB Server 版本 | Java Driver Version |
---|---|---|---|
3.2.x | 4.2.x | 6.0.x,7.0.x | 4.11.x |
3.1.x | 4.1.x | 6.0.x,7.0.x | 4.9.x |
3.0.x | 4.0.x | 6.0.x | 4.7.x |
2.7.x | 3.4.x | 5.0.x | 4.6.x |
2.6.x | 3.3.x | 5.0.x | 4.4.x |
2.5.x | 3.2.x | 4.4.x | 4.1.x |
2.4.x | 3.1.x | 4.4.x | 4.1.x |
2.3.x | 3.0.x | 4.4.x | 4.0.x |
2.2.x | 2.2.x | 4.2.x | 3.11.x |
2.1.x | 2.1.x | 4.0.x | 3.8.x |
2.0.x | 2.0.x | 3.4.x | 3.5.x |
1.10.x | 1.10.x | 2.4.x | 2.10.x,2.11.x |
Mongo的Java驱动,在3.7.x
及以后的版本叫做mongodb-driver-sync,以前的版本叫做mongo-java-driver
MongoDB 与 Java Driver 兼容性
引入依赖
1 | <dependency> |
配置yml
1 | spring: |
配置类:去掉_class
属性
1 |
|
Mongo语法
1 | # 查询集合中的若干文档 |
query :可选,使用查询操作符指定查询条件
projection :可选,使用投影操作符指定返回的键。查询时返回文档中所有键值, 只需省略该参数即可(默认省略)。投影时,_id为1的时候,其他字段必须是1;_id是0的时候,其他字段可以是0;如果没有_id字段约束,多个其他字段必须同为0或同为1。
查询操作符
操作符 | 解释 | 示例 | 示例解释 |
---|---|---|---|
$lt | 小于 | db.collection.find({ "age": { "$lt": 25 } }) | 查询年龄小于 25 的文档 |
$lte | 小于等于 | db.collection.find({ "age": { "$lte": 25 } }) | 查询年龄小于等于 25 的文档 |
$gt | 大于 | db.collection.find({ "age": { "$gt": 25 } }) | 查询年龄大于 25 的文档 |
$gte | 大于等于 | db.collection.find({ "age": { "$gte": 25 } }) | 查询年龄大于等于 25 的文档 |
$ne | 不等于 | db.collection.find({ "age": { "$ne": 25 } }) | 查询年龄不等于 25 的文档 |
$in | 在指定数组中 | db.collection.find({ "age": { "$in": [20, 25] } }) | 查询年龄存在并且在指定数组中的文档 |
$nin | 不在指定数组中 | db.collection.find({ "age": { "$nin": [20, 25] } }) | 查询年龄不存在或者不在指定数组中的文档 |
$or | 匹配两个或多个条件中的一个 | db.collection.find({ "$or": [ { "age": 20 }, { "salary": { "$gt": 8000 } } ] }) | 查询年龄为 20 或者薪资大于 8000 的文档 |
$and | 匹配全部条件 | db.collection.find({ "$and": [ { "age": 20 }, { "salary": { "$gt": 8000 } } ] }) | 查询年龄为 20 并且薪资大于 8000 的文档 |
$all | 匹配数组中所有元素满足指定条件 | db.collection.find({ "tags": { "$all": [ { "$elemMatch": { "tagKey": "color", "tagValue": "red" } }, { "$elemMatch": { "tagKey": "size", "tagValue": "XL" } } ] } }) | 查询包含 tagKey 为 “color”,tagValue 为 “red” 的标签,并且包含 tagKey 为 “size”,tagValue 为 “XL” 的标签的文档 |
$elemMatch | 匹配数组中至少一个元素满足指定条件 | db.collection.find({ "tags": { "$elemMatch": { "tagKey": "color", "tagValue": "blue" } } }) | 查询包含 tagKey 为 “color”,tagValue 包含 “blue” 的标签的文档 |
$text | 全文搜索 | db.collection.find({ "$text": { "$search": "searchTerm" } }) | 进行全文搜索,查找包含 “searchTerm” 的文档 |
$type | 指定字段类型 | db.collection.find({ "field": { "$type": "string" } }) | 查询指定字段类型为字符串的文档 |
$size | 数组长度 | db.collection.find({ "field": { "$size": sizeValue } }) | 查询指定字段数组长度为 sizeValue 的文档 |
$exists | 字段存在 | db.collection.find({ "field": { "$exists": true } }) | 查询指定字段存在的文档 |
$mod | 取模 | db.collection.find({ "field": { "$mod": [divisor, remainder] } }) | 查询指定字段取模后符合给定除数和余数的文档 |
操作org.bson.Document
对象,无需创建实体映射对象,但操作时需要指定集合名称
1 | package com.hanqf; |
操作实体类
相关注解 | 修饰范围 | 作用 | 属性 |
---|---|---|---|
@Document | 类 | 映射类对象为Mongo文档 | value, collection |
@Id | 成员变量、方法 | 将成员变量值映射为文档的_id的值 | |
@Field | 成员变量、方法 | 将成员变量及值映射为文档中key:value对 | name, value |
@Transient | 成员变量、方法 | 指定成员变量不参与文档的序列化 |
使用 @Document 注解指定集合名
1 | package com.hanqf.mongo.model; |
查询示例
1 | package com.hanqf; |
查询所有文档
1 | // db.employee.find() |
根据_id查询
1 | // db.employee.findOne({ "_id": 1 })| |
查询第一个文档
1 | // db.employee.findOne({}) |
条件查询,排序及分页
1 | /* |
只返回部分字段
1 | /* |
去重
distinct 方法只能直接获取唯一值,并不能在查询过程中进行其他的聚合操作,使用场景非常受限,推荐使用聚合方式进行查询
1 | //语法:db.collectionName.distinct("fieldName", { /* 过滤条件 */ }),这将返回指定字段 fieldName 的唯一值数组。 |
SpringBoot对Mongo的查询,其实就是如何通过Criteria来构建一个Query,所以我们的目标就是要熟练掌握Criteria的语法。当然,如果你熟悉Mongo的查询语法,也可以直接使用BasicQuery来实现Mongo的查询。
Mongo语法
1 | # 插入单个文档 |
writeConcern 是 MongoDB 中用来控制写入确认的选项,可选。以下是 writeConcern 参数的一些常见选项:
w:指定写入确认级别。如果指定为数字,则表示要等待写入操作完成的节点数。如果指定为 majority,则表示等待大多数节点完成写入操作。默认为 1,表示等待写入操作完成的节点数为 1。
j:表示写入操作是否要求持久化到磁盘。如果设置为 true,则表示写入操作必须持久化到磁盘后才返回成功。如果设置为 false,则表示写入操作可能在数据被持久化到磁盘之前返回成功。默认为 false。
wtimeout:表示等待写入操作完成的超时时间,单位为毫秒。如果超过指定的时间仍然没有返回确认信息,则返回错误。默认为 0,表示不设置超时时间。
ordered:指定是否按顺序写入,默认 true,按顺序写入。
插入单个文档
1 | /* |
插入多个文档
1 | /* |
id存在时更新,不存在时插入
1 | /* |
Mongo语法
1 | # 更新单个或多个文档 |
参数 | 描述 |
---|---|
<filter> | 一个筛选器对象,用于指定要更新的文档。 |
<update> | 一个更新操作对象,用于指定如何更新文档。可以使用一些操作符,例如$set、$inc、$unset等,以更新文档中的特定字段。 |
upsert | 一个布尔值,用于指定如果找不到与筛选器匹配的文档时是否应插入一个新文档。如果upsert为true,则会插入一个新文档。默认值为false。 |
writeConcern | 一个文档,用于指定写入操作的安全级别。可以指定写入操作需要到达的节点数或等待写入操作的时间。 |
collation | 一个文档,用于指定用于查询的排序规则。例如,可以通过指定locale属性来指定语言环境,从而实现基于区域设置的排序。 |
arrayFilters | 一个数组,用于指定要更新的数组元素。数组元素是通过使用更新操作符$[]和$来指定的。 |
hint | 一个文档或字符串,用于指定查询使用的索引。该参数仅在MongoDB 4.2.1及以上版本中可用。 |
更新操作符
操作符 | 格式 | 描述 |
---|---|---|
$set | { $set: { field: value } } | 指定一个键并更新值,若键不存在则创建。 |
$unset | { $unset : { field : 1 } } | 删除一个键。 |
$inc | { $inc : { field : value } } | 对数值类型进行增减。 |
$rename | { $rename : { old_field_name : new_field_name } } | 修改字段名称。 |
$push | { $push : { field : value } } | 将数值追加到数组中,若数组不存在则会进行初始化。 |
$pushAll | { $pushAll : { field : value_array } } | 追加多个值到一个数组字段内。 |
$pull | { $pull : { field : _value } } | 从数组中删除指定的元素。 |
$addToSet | { $addToSet : { field : value } } | 添加元素到数组中,具有排重功能。 |
$pop | { $pop : { field : 1 } } | 删除数组的第一个或最后一个元素。 |
只更新满足条件的第一条记录
1 | /* |
更新所有满足条件的记录
1 | /* |
没有符合条件的记录则插入数据
1 | /* |
Mongo语法
1 | db.collection.replaceOne( |
replaceOne
操作会首先使用指定的筛选条件来查找匹配的文档,然后用提供的新文档完全替换掉原始文档,也就是说replaceOne
是整体替换,而不是修改文档中的某些字段。
参数 | 描述 |
---|---|
<filter> | 一个筛选器对象,用于指定要替换的文档。只有与筛选器对象匹配的第一个文档才会被替换。 |
<replacement> | 一个替换文档对象,用于指定用于替换原始文档的新文档。替换文档必须包含所有要在原始文档中修改或替换的字段。 |
upsert | 一个布尔值,表示如果找不到与筛选器匹配的文档时是否应插入一个新文档。如果设置为true,则会插入一个新文档。默认为false。 |
writeConcern | 一个文档,用于指定写入操作的安全级别。可以指定写入操作需要到达的节点数或等待写入操作的时间。 |
collation | 一个文档,用于指定用于查询的排序规则。例如,可以通过指定locale属性来指定语言环境,从而实现基于区域设置的排序。 |
hint | 一个文档或字符串,用于指定查询使用的索引。该参数仅在MongoDB 4.2.1及以上版本中可用 |
示例
1 | /* |
Mongo语法
1 | # 按条件删除多个文档 |
删除所有文档
1 | // db.employee.deleteMany({}) |
按条件删除文档
1 | // db.employee.deleteMany({ "salary": { "$gte": 10000 } }) |
删除查询到的第一个文档
1 | // db.employee.deleteOne({ "salary": { "$gte": 10000 } }) |
bulkwrite()方法提供了执行批量插入、更新和删除操作的能力。
bulkWrite()支持以下写操作:
示例
1 | package com.hanqf; |
本文介绍Linux下MongoDB7.0的安装
MongoDB官方文档
本文基于CentOS8(x86_64)
2024年8月15日,MongoDB正式发布7.0版本,截止目前最新版本为7.0.6。
阿里云关于MongoDB7.0的特性说明,该文档中也包含MongoDB其它版本的特性说明
mongodb的安装方法可以查看官方文档
MongoDB下载地址,选择合适的版本、平台和包类型
1 | #下载MongoDB |
mongod: error while loading shared libraries: libcrypto.so.1.1: cannot open shared object file: No such file or directory
解决方法:
1 | wget https://www.openssl.org/source/openssl-1.1.1w.tar.gz |
命令参数启动
1 | mongod --port=27017 --dbpath=/mongodb/data --logpath=/mongodb/log/mongodb.log --bind_ip=0.0.0.0 --fork |
也可以将上面的参数写到配置文件中,如/mongodb/conf/mongo.conf
文件,必须是yaml格式
1 | systemLog: |
将命令行参数直接转换为yaml:--outputConfig
1 | $ mongod --port=27017 --dbpath=/mongodb/data --logpath=/mongodb/log/mongodb.log --bind_ip=0.0.0.0 --fork --outputConfig |
1 | net: |
删除
outputConfig: true
这一行,然后将其余内容复制到mongo.conf中
关于配置参数的详细信息可以查看官方文档
1 | # 启动mongo服务 |
从mongodb6开始不再支持mongo命令,而是需要使用mongosh命令,关于mongosh命令的使用可以查看官方文档
mongosh命令的使用方式与mongo命令基本一致
下载地址:mongosh下载地址
1 | # 下载安装包 |
mongosh常用命令
命令 | 说明 |
---|---|
show dbs 或 show databases | 显示数据库 |
use 数据库名 | 切换数据库,如果不存在创建数据库 |
db.dropDatabase() | 删除数据库 |
show collections 或 show tables | 显示当前数据库的集合列表 |
db.集合名.stats() | 查看集合详情 |
db.集合名.drop() | 删除集合 |
show users | 显示当前数据库的用户列表 |
show roles | 显示当前数据库的角色列表 |
show profile | 显示最近发生的操作 |
load(“xxx.js”) | 执行一个JavaScript脚本文件 |
exit 或 quit | 退出 |
help | 查看mongodb支持哪些命令 |
db.help() | 查询当前数据库支持的方法 |
db.集合名.help() | 显示集合的帮助信息 |
db.version() | 查看数据库版本 |
cls | 清屏 |
db.shutdownServer() | 关闭mongodb server端 |
创建管理员
1 | # 设置管理员用户名密码需要切换到admin库 |
常用角色
权限名 | 描述 |
---|---|
read | 允许用户读取指定数据库 |
readWrite | 允许用户读写指定数据库 |
dbAdmin | 允许用户在指定数据库中执行管理函数,如索引创建、删除,查看统计或访问system.profile |
dbOwner | 允许用户在指定数据库中执行任意操作,增、删、改、查等 |
userAdmin | 允许用户向system.users集合写入,可以在指定数据库里创建、删除和管理用户 |
clusterAdmin | 只在admin数据库中可用,赋予用户所有分片和复制集相关函数的管理权限 |
readAnyDatabase | 只在admin数据库中可用,赋予用户所有数据库的读权限 |
readWriteAnyDatabase | 只在admin数据库中可用,赋予用户所有数据库的读写权限 |
userAdminAnyDatabase | 只在admin数据库中可用,赋予用户所有数据库的userAdmin权限 |
dbAdminAnyDatabase | 只在admin数据库中可用,赋予用户所有数据库的dbAdmin权限 |
root | 只在admin数据库中可用。超级账号,超级权限 |
创建数据库用户
1 | admin> use mydb |
重置用户密码
1 | mydb> db.changeUserPassword("mytest", "password") |
重新赋予用户角色
1 | # 假设已经创建了用户mytest,需要重新赋予其角色 |
删除用户
1 | mydb> db.dropUser("mytest") |
默认情况下,MongoDB不会启用鉴权,以鉴权模式启动MongoDB有两种方法
命令行参数增加
--auth
1 | mongod -f /mongodb/conf/mongo.conf --auth |
配置文件中加上如下内容
1 | security: |
启用鉴权之后,连接MongoDB的相关操作都需要提供身份认证
1 | mongosh --host=127.0.0.1 --port=27017 -u root -p password --authenticationDatabase=admin |
参数方式
1 | # 可以通过 mongosh --help 查看帮助 |
混合方式
1 | # ip+端口方式连接 |
uri方式
1 | mongosh mongodb://127.0.0.1:27017 |
如果只是连接本机的server端,而且端口为27017,可以省略host和port
1 | mongosh |
默认的vm.max_map_count
值为65530
,如果需要开启MongoDB的分片功能,需要将vm.max_map_count
设置为较高的值,通常推荐为1048576
查看当前vm.max_map_count
的值
1 | sysctl vm.max_map_count |
临时增加vm.max_map_count
的值
1 | sysctl -w vm.max_map_count=1048576 |
永久增加vm.max_map_count
的值
1 | # 修改/etc/sysctl.conf文件 |
重启MongoDB才会生效
MongoDB Database Tools是MongoDB官方提供的数据库管理工具,可以用于管理MongoDB数据库,包括MongoDB的备份、恢复、监控等功能。
1 | # 下载安装包 |
MongoDB Database Tools常用命令,使用方法请查看官方文档
文件名称 | 作用 |
---|---|
mongostat | 数据库性能监控工具 |
mongotop | 热点表监控工具 |
mongodump | 数据库逻辑备份工具 |
mongorestore | 数据库逻辑恢复工具 |
mongoexport | 数据导出工具 |
mongoimport | 数据导入工具 |
bsondump | BSON格式转换工具 |
mongofiles | GridFS文件工具 |
在编写 shell 脚本时,echo 和 printf 是两个常用的命令,用于输出信息到标准输出设备。虽然它们看似简单,但深入理解它们的使用方式和内部机制对于编写高效、可读性强的脚本至关重要。
echo
命令的基本语法为:
1 | echo [选项] [字符串或变量] |
参数和选项
1 | -n:不换行输出。 |
1 | - 打印一个文本消息。注意:引号是可选的 |
1 | echo -e "\e[1;31mError:\e[0m Something went wrong." |
printf
命令的基本语法为:
1 | printf [格式化字符串] [参数] |
1 | - 打印文本消息: |
在 printf 命令中,支持的格式化符号用于指定输出的格式,包括整数、浮点数、字符串等。
以下是常用的格式化符号及其使用示例:
1.整数格式化符号:
1 | printf "%d\n" 42 # 输出:42 |
2.浮点数格式化符号:
1 | printf "%f\n" 3.14159 # 输出:3.141590 |
3.字符串格式化符号:
1 | printf "%c\n" 'A' # 输出:A |
4.其他格式化符号:
1 | printf "%p\n" $var # 输出:0x7ffeefbff748 (变量 var 的地址) |
1 | # 10进制转2进制,obase=2 指定输出进制为二进制,默认为10进制。 |
1 | # 2进制转10进制,ibase=2 指定输入进制为二进制,默认为10进制。 |
1 | # 8进制转10进制 |
1 | # 16进制转10进制 |
echo 会自动换行,而 printf 需要显式指定换行符。
printf 提供更灵活的格式化输出方式。
在大量输出时,echo 通常比 printf 更高效。
简单文本输出场景,使用 echo。
需要格式化输出或者更精确控制输出格式时,使用 printf。
这里列举一些macos下一些有用的shell脚本
与本文脚本对应的centos脚本可以参看centos-shell
1 |
|
1 |
|
1 |
|
1 |
|
1 |
|
1 |
|
1 |
|
1 |
|
1 |
|
1 |
|
1 |
|
1 |
|
本文介绍如何使用AWS SDK for Java V2操作S3。
参考资料:
1 | package com.hanqf.controller; |
磁盘的挂载、分区、格式化、扩容等命令
本文基于AWS EC2 EBS CentOS8(x86_64)
raid后不支持重新挂载到EC2
raid后同样支持分区和扩容,就把raid后的磁盘当作普通磁盘操作即可
1 | # 查看磁盘名称,新挂载的磁盘为 nvme1n1,nvme2n1,nvme3n1 |
1 | $ mkfs.ext4 /dev/md0 |
1 | $ mkfs.xfs /dev/md0 |
1 | $ mkdir /data |
1 | # 检查 RAID5 数组的状态,重点关注 Array Size 和 Used Dev Size,前者表示raid5中总的磁盘大小,后者表示实际使用的磁盘大小 |
1 | $ lsblk # 这里新挂载的磁盘为 nvme4n1 |
1 | # xfs |
1 | $ mdadm --add /dev/md0 /dev/nvme4n1 |
1 | # 查看raid信息,此时可以看到新加入的磁盘被当作了备用盘:Spare Devices : 1 |
1 | # 这里扩容到4快盘 |
1 | $ cat /proc/mdstat |
1 | $ resize2fs /dev/md0 |
1 | $ xfs_growfs /dev/md0 |
raid5后创建的逻辑卷同样支持分区,注意:如果已经mount,则分区前要先umount
1 | $ fdisk /dev/md0 |
1 | $ mkfs.xfs /dev/md0p1 |
1 | $ mkdir /data1 |
raid5中添加新的磁盘参考上面的内容,这里不再赘述,这里添加一个10g的新磁盘
扩容最后一个分区
1 | $ growpart /dev/md0 2 |
同步文件系统
1 | $ xfs_growfs -d /data2 |
磁盘的挂载、分区、格式化、扩容等命令
本文基于AWS EC2 EBS CentOS8(x86_64)
逻辑卷不支持MBR
和GPT
分区,但通过MBR
和GPT
分区后的子分区可以被加入到物理卷组,从而扩展原有逻辑分区或者创建新的逻辑分区
逻辑卷与普通的磁盘和分区一样,同样支持重新挂载到ec2,不过要将组成逻辑卷和物理卷组的所有磁盘都挂载到ec2后才有效
1 | $ df -hT |
xvdg
1 | $ lsblk |
1 | # 创建新物理卷,可以先分区再创建,这里为了省事不进行分区,也就是说,物理卷可以通过磁盘或者磁盘的子分区进行创建 |
1 | # 创建新物理卷组,这里 vgnew 就是新卷组的名称 |
1 | # 创建新逻辑卷,lvnew 为自定义逻辑卷名称,一个物理卷组可以创建多个逻辑卷,重点看物理卷组的剩余空间 |
1 | $ mkfs.xfs /dev/vgnew/lvnew |
1 | $ mkfs.ext4 /dev/vgnew/lvnew |
1 | $ mkdir /new_data |
1 | echo '/dev/mapper/vgnew-lvnew /new_data xfs defaults 0 0' >> /etc/fstab |
1 | echo '/dev/mapper/vgnew-lvnew /new_data ext4 defaults 0 0' >> /etc/fstab |
1 | $ lsblk |
1 | # 创建新物理卷,可以先分区再创建,这里为了省事不进行分区 |
1 | # 将新的物理卷 /dev/sdh 加入卷组 vgnew |
1 | # 将卷组中的剩余空间添加到指定逻辑卷 |
-r
后,就不需要执行的命令了1 | $ xfs_growfs /dev/vgnew/lvnew |
1 | $ resize2fs /dev/mapper/vgnew-lvnew |
1 | $ df -hT |
磁盘的挂载、分区、格式化、扩容等命令
本文基于AWS EC2 EBS CentOS8(x86_64)
1 | $ df -hT |
xvdf
1 | $ lsblk |
可以查看磁盘的格式化类型
1 | #方法1,更简单 |
1 | $ mkfs.xfs /dev/xvdf |
1 | $ mkfs.ext4 /dev/xvdf |
1 | $ fdisk /dev/nvme2n1 |
1 | # 对/dev/nvme3n1进行分区,如果parted命令不存在可以用 yum 安装 |
1 | $ lsblk |
1 | # 格式化第一个分区 |
1 | $ mkdir /data |
1 | $echo '/dev/xvdf /data xfs defaults 0 0' >> /etc/fstab |
1 | $ echo '/dev/xvdf /data ext4 defaults 0 0' >> /etc/fstab |
1 | # /dev/nvme2n1:磁盘名称 2:第几个分区,实测分两个区的时只能扩展第二个分区,扩展第一个分区会提示:NOCHANGE: partition 1 is size 10485760. it cannot be grown |
growpart
growpart 命令是用于调整硬盘分区大小的工具,它基于 cloud-utils 包提供了一种扩展分区的方法。然而,growpart 命令确实有一个限制,它只能调整并扩展磁盘上最后一个分区的大小,而不能直接用于非最后一个分区的大小调整。
这个限制源于底层的分区表结构和文件系统布局。分区表通常是基于硬盘的特定格式(比如 MBR 或 GPT),这些格式决定了分区的组织方式。在这种结构下,非最后一个分区的大小调整可能会影响到其他分区的布局,这可能会导致数据丢失或破坏。
因此,growpart 仅专注于扩展最后一个分区是出于安全和数据完整性的考虑。如果你需要调整非最后一个分区的大小,可能需要采取其他方法,比如使用其他工具或者进行手动操作。但务必在这样做之前备份重要数据,因为调整分区大小可能会对数据安全带来风险。
PS:
执行 growpart 可能会抛出如下错误提示:/bin/growpart:行242: 个扇区: 语法错误: 期待操作数 (错误符号是 “个扇区”)
解决方法:命令行输入 LANG=en_US.UTF-8 回车后再次运行 growpart 即可
1 | # /data 就是 mount 对应的磁盘 |
1 | # 不分区时是磁盘名称 |
本文聊聊SpringBoot R2dbc
本文基于SpringBoot-3.1.2
Github代码地址 web-flux-mysql-redis-demo
,webflux-mysql-multi-demo
方式一: 开启 debug
日志
1 | # 可以打印sql,但不能打印参数值 |
方式二: 使用 r2dbc-proxy
1 | <dependency> |
1 | spring: |
1 | package com.example.r2dbc; |
springboot为我们提供了几个Repository
,如R2dbcRepository
、ReactiveCrudRepository
、ReactiveSortingRepository
等等,我们自己的Repository
通过继承这些父接口,可以获得相应的CURD的能力,但是其没有对条件查询提供支持,此时可以通过自定义Repository
接口及其实现类的方式实现扩展
自定义Repository
接口
1 | package com.example.r2dbc; |
自定义Repository
接口的实现类
1 | package com.example.r2dbc; |
我们需要告知springboot使用我们自定义的Repository
1 | package com.example.config; |
此时我们在创建业务Repository
时就可以继承我们自定义的Repository
了
1 | public interface SysUserRepository extends BaseR2dbcRepository<SysUser, String> { |
可以看到我们自定义的Repository
中,主要是通过Criteria
来提供查询条件的封装,我了便于创建Criteria
对象,这里提供了一个工具类 CustomCriteria
,其主要功能是根据条件来拼接查询条件,代码比较多,就不在这里粘贴了,自行去Github代码地址中查看吧。
1 | /** |
R2dbc提供的<S extends T> Mono<S> save(S entity);
方法,要求table
必须含有主键,其根据实体类中主键是否被填充来判断是新增还是修改操作,如果我们是自定义主键,而非数据库自动填充主键,此时就不能使用save
方法新增记录,解决方法是在业务Repository
中创建一个新增方法,比如:
1 | /** |
注解式事务
1 |
|
编程式事务
1 |
|
application.yaml
1 | spring: |
one:配置类
1 | package com.example.config; |
two:配置类
1 | package com.example.config; |
主要注意如下几点:
R2dbcEntityTemplate
,R2dbcTransactionManager
basePackages
指定不同的扫描路径entityOperationsRef
指定各自的 R2dbcEntityTemplate
完整代码参考 Github代码地址
本文介绍在SpringBoot Security中的Session管理与RememberMe
实现了基于内存、Jdbc和Redis三种配置方式
本文基于SpringBoot-2.7.14和SpringBoot-3.1.2
1 | package com.hanqf.config; |
1 | package com.hanqf.config; |
1 | package com.hanqf.config; |
maven依赖
1 | <dependency> |
建表语句
建表语句可在spring-session-jdbc-[version].jar
的org.springframework.session.jdbc
包路径中查看
1 | package com.hanqf.config; |
1 | package com.hanqf.config; |
1 | <dependency> |
1 | package com.hanqf.config; |
1 | package com.hanqf.config; |
1 | package com.hanqf.common.session; |
1 | package com.hanqf.config; |
1 | package com.hanqf.config; |
1 | package com.hanqf.config; |
1 | package com.hanqf.config; |
1 | package com.hanqf.config; |
1 | package com.hanqf.config; |
自定义基于Redis的PersistentTokenRepository
1 | package com.hanqf.common; |
本文介绍EKS集群Autoscaling 之 Karpenter
参考资料:
Karpenter 是一个开源集群自动缩放器,可以自动为不可安排的pod提供新节点。Karpenter评估了挂起的pod的聚合资源需求,并选择运行它们的最佳实例类型。它将自动扩展或终止没有任何非daemonset pod的实例,以减少浪费。它还支持整合功能,该功能将积极移动pod,并用更便宜的版本删除或替换节点,以降低集群成本。
Karpenter 是aws为 k8s 构建的能用于生产环境的开源的工作节点动态调度控制器。
在Karpenter推出之前,Kubernetes用户主要依靠Amazon EC2 Auto Scaling组和Kubernetes Cluster Autoscaler(CAS)来动态调整其集群的计算容量。
相较于传统的 Cluster Autoscaler 工具,Karpenter 具有调度速度快、更灵活、资源利用率高等众多优势,另外,Karpenter与Kubernetes版本没有那么紧密耦合(像CAS那样),所以其是 EKS 自动扩缩容的首选方案,两者的比较可以参考下图。
特 性 | Cluster Autoscaler | Karpenter |
---|---|---|
资源管理 | Cluster Autoscaler基于现有节点的资源利用率采用反应性方法来扩展节点。 | Karpenter基于未调度的Pod的当前资源需求采取主动方法来进行节点预配。 |
节点管理 | Cluster Autoscaler根据当前工作负载的资源需求来管理节点,使用预定义的自动缩放组。 | Karpenter根据自定义预配程序的配置来扩展、预配和管理节点。 |
扩展 | Cluster Autoscaler更专注于节点级别的扩展,这意味着它可以有效地添加更多的节点以满足需求的增加。 但这也意味着它在缩减资源方面可能不太有效。 | Karpenter根据特定的工作负载需求提供更有效和精细的扩展功能。换句话说,它根据实际使用情况进行扩展。它还允许用户指定特定的扩展策略或规则以满足其需求。 |
调度 | 使用Cluster Autoscaler进行调度更简单,因为它是根据工作负载的当前需求设计的进行扩展或缩减。 | Karpenter可以根据可用区和资源需求有效地调度工作负载。它可以尝试通过Spot实例来优化成本,但它不会知道你已经在aws帐号中做的任何承诺,如RI(预留实例)或Savings Plans(储蓄计划)。 |
1 | # aws认证profile |
1.创建role
1 | $ echo '{ |
2.给这个 role 添加 policy
1 | $ aws iam attach-role-policy --role-name "KarpenterNodeRole-${CLUSTER_NAME}" \ |
3.把 role 授予 EC2 的 instance profile
1 | $ aws iam create-instance-profile \ |
1.创建role
1 | $ cat << EOF > controller-trust-policy.json |
2.为role配置policy
1 | $ cat << EOF > controller-policy.json |
1.为节点组内的子网打标签
1 | $ for NODEGROUP in $(aws eks list-nodegroups --cluster-name ${CLUSTER_NAME} --query 'nodegroups' --output text) |
2.给托管节点组的运行模版的安全组打标签
1 | # 获取节点组 |
将上面为node创建的role加入到集群权限
1 | $ kubectl edit configmap aws-auth -n kube-system |
编辑后完整的内容如下:
1 | apiVersion: v1 |
查看授权信息
1 | $ eksctl get iamidentitymapping --cluster eks-lexing |
1.设置环境变量
1 | # 当前最新版是 v0.29.1 , https://github.com/aws/karpenter/releases |
2.创建 karpenter.yaml 模版
1 | $ helm template karpenter oci://public.ecr.aws/karpenter/karpenter --version ${KARPENTER_VERSION} \ |
3.设置节点亲和性,编辑karpenter.yaml
,找到karpenter deployment的亲和性配置,修改为如下内容,注意这里ng-4d9024eb
要替换为你的${NODEGROUP}
。关节K8s节点亲和性
的介绍可以参考官方文档亲和性与反亲和性
1 | affinity: |
为其他关键集群工作负载设置nodeAffinity
1 | affinity: |
k edit deploy ebs-csi-controller -n kube-system
,添加好nodeAffinity
后保存,然后查看对应的pod是否重启成功,如果一只处于pending
状态,可以试着重启1 | $ k scale deploy ebs-csi-controller --replicas 0 -n kube-system |
1 | $ k scale deploy karpenter --replicas 0 -n karpenter |
查看 karpenter 日志是否正常
1 | $ k logs deployments/karpenter -f -n karpenter |
4.部署 karpenter 及其 相关资源
1 | # 创建Namespace |
5.创建默认的供应者(provisioner)
Provisioner
对 Karpenter 可创建的节点以及可在这些节点上运行的 Pod 设置约束。如果没有配置至少一个Provisioner
,Karpenter 将不会执行任何操作。Provisioner
支持的配置项可以参考官方文档,比如下面就限制了被管控的节点必须符合两个条件:1.实例类别必须在[c, m, r]中
2.实例的生成代次必须大于2。比如 实例类型为c1.xxx,m1.xxx,m2.xxx
就不符合要求
AWSNodeTemplate
节点模板启用AWS特定设置的配置。关于AWSNodeTemplate
支持的配置项可以参考官方文档,比如默认节点关联的存储为20G gp3,如果要修改为40G可以在spec下指定如下内容,先创建后编辑也可以,但只有修改后新创建的节点才会使用新的配置。1 | blockDeviceMappings: |
Provisioner
需要与AWSNodeTemplate
关联使用,通过在Provisioner
的providerRef
中指定AWSNodeTemplate
的name
进行关联,一个AWSNodeTemplate
可以被多个Provisioner
关联。
配置的每个 Provisioner
均由 Karpenter 循环遍历。在 Provisioner
中定义污点以限制可以在 Karpenter 创建的节点上运行的 Pod。建议创建互斥的 Provisioner
。因此任何 Pod 都不应该匹配多个 Provisioner
。如果匹配多个Provisioner
,Karpenter将使用权重最高的Provisioner
。关于K8s中污点
的介绍可以参考官方文档污点和容忍度
下面是一个最基本的Provisioner
定义,你可以根据需要创建自己的Provisioner
,可以参考官方文档或者查看provisioner examples中的示例。
1 | # 创建 default Provisioner |
查看 karpenter 状态
1 | $ k get pod -n karpenter |
查看 karpenter 日志是否正常
1 | $ k logs deployments/karpenter -f -n karpenter |
karpenter创建新的节点时不会在原有的节点组中进行,所以为了摆脱从节点组添加的实例,我们可以将节点组缩小到最小大小
1 | # 如果您有一个多AZ节点组,我们建议至少2个实例。 |
关停Cluster Autoscaler(CAS)
如果EKS中已经开启了CAS,则安装Karpenter后需要关闭CAS
1 | $ kubectl scale deploy/cluster-autoscaler -n kube-system --replicas=0 |
查看当前node信息
1 | $ k get node |
采用 AWS-EKS-18--Autoscaling 之 Cluster Autoscaler(CAS) 中的测试方法,将deploy的副本数设置为50,过一会查看node情况
1 | # 可以看到node数量已经变为3了,说明扩容成功 |
缩容测试用的deploy,副本数设置为1,过一会发现node节点并没有被终止,这是为什么呢?
默认情况下,Karpenter不会主动终止节点,需要为其设置终止节点的方式,参考Karpenter官方文档Deprovisioning部分
在Provisioner
中设置节点终止的方式
spec.ttlSecondsAfterEmpty
: 当最后一个工作负载(非守护程序集)pod停止在节点上运行时,Karpenter会注意到。从那时起,Karpenter在提供程序中等待ttlSecondsAfterEmpty
设置的秒数,然后Karpenter请求删除节点。此功能可以通过删除不再用于工作负载的节点来降低成本。spec.ttlSecondsUntilExpired
: Karpenter 将根据Provisioner
的ttlSecondsUntilExpired
值将节点注释为过期,并在节点生存了设定秒数后取消配置节点。节点过期的一种用例是定期回收节点。spec.consolidation.enabled
: 实现整合,通过删除不需要的节点和缩减无法删除的节点的规模来降低集群成本。与ttlSecondsAfterEmpty
参数互斥。1 | # 编辑 default provisioner,并为其指定 spec.ttlSecondsAfterEmpty: 30,表示空闲超过30秒则终止节点。 |
1 | apiVersion: karpenter.sh/v1alpha5 |
等待30秒后查看node情况,新创建的node已经成功终止
1 | $ k get node |
创建和终止节点的过程可以通过日志进行观察
1 | $ k logs deployments/karpenter -f -n karpenter |
小贴士
当Karpenter管理的Node节点由于某种原因不可用时(比如我们在AWS控制台终止了EC2或通过命令行删除节点k delete node nodeName
),Karpenter会立刻为我们创建一个新的Node节点,并在其上重启Pod。
从Cluster Autoscaler迁移
EKS Cluster Autoscaler 迁移 Karpenter 实践
Karpenter Best Practices
本文介绍EKS集群Autoscaling 之 Cluster Autoscaler(CAS)
参考资料:
弹性伸缩是一项功能,可以自动上下伸缩您的资源以满足不断变化的需求。
Amazon EKS 支持两款自动扩缩产品:
Cluster Autoscaler
是一个可以自动调整Kubernetes
集群大小的组件,以便所有pod
都有运行的地方,并且没有不需要的节点。支持多个公共云提供商。
AWS EKS
集群自动扩容功能可以基于Cluster Autoscaler
自动调整集群中node的数量以适应需求变化。
Cluster Autoscaler
一般以Deployment的方式部署在K8s中,通过service account
赋予的权限来访问AWS autoscaling group
资源,并控制node(EC2)的增减。
AWS EKS Cluster Autoscaler
以 Amazon EC2 Auto Scaling Groups
服务为基础对node进行扩容,所以其扩容或缩容时,也要遵守节点组扩缩中的配置
当有新的Pod无法在现有node上schedule时会触发扩容,当node空闲超过10min时,会触发缩容。
Cluster Autoscaler
的镜像版本要求与K8s版本匹配,所以当EKS(K8s)升级时,Cluster Autoscaler
的镜像也要进行升级。
创建Policy:cluster-autoscaler-policy.json
1 | { |
1 | $ export AWS_PROFILE=eks-ty-old |
创建IAM Role的信任关系:trust-policy.json
1 | { |
创建 IAM Role
1 | $ aws iam create-role \ |
为 Role 添加 Policy
1 | $ aws iam attach-role-policy \ |
下载Autoscaler yaml文件
1 | #下载yaml文件,github仓库中的文件下载路径格式为:https://raw.githubusercontent.com/<Owner>/<RepositoryName>/<branch>/<FilePath> |
修改yaml文件配置
打开Cluster Autoscaler
的github地址,查看与EKS版本匹配的最新Autoscaler镜像版本
eks-lexing
1 | - --balance-similar-node-groups |
--balance-similar-node-groups
:此选项用于启用集群节点组的负载均衡功能。当你有多个具有相似容量的节点组时,启用此选项可以确保 Cluster Autoscaler 尽可能均衡地在这些节点组之间分配 Pod。它帮助确保节点组的资源利用率更加平衡,以提高集群的整体性能。--skip-nodes-with-system-pods=false
:此选项用于设置是否跳过具有系统 Pod 的节点。默认情况下,Cluster Autoscaler 会跳过具有系统 Pod(如 kube-system 命名空间中的核心组件)的节点,以确保这些关键组件的正常运行。将该选项设置为 false,即禁用跳过具有系统 Pod 的节点,可以让 Cluster Autoscaler 考虑包括具有系统 Pod 的节点在内的所有节点进行调整。
部署Cluster Autoscaler
1 | $ kubectl apply -f cluster-autoscaler-autodiscover.yaml |
查看Cluster Autoscaler Deployment
1 | # cluster-autoscaler |
给autoscaler deployment打patch,增加annotation
1 | # 这个注解的作用是告诉 Kubernetes 系统不要将这些 Pod 标记为可以被安全驱逐(evict)的 Pod。 |
查看当前node节点
1 | $ k get node |
创建测试用的deployment:testDeploy.yaml
1 | apiVersion: apps/v1 |
1 | $ k apply -f testDeploy.yaml |
过一会查看node情况
1 | # 可以看到新创建了一个node节点 |
缩容deploy
1 | # 将副本降为1 |
大约过10几分钟就可以看到新增的node已经下线
1 | $ k get node |
Cluster Autoscaler
的镜像版本要求与K8s版本匹配,所以当EKS(K8s)升级时,Cluster Autoscaler
的镜像也要进行升级。
1 | $ kubectl set image deployment cluster-autoscaler \ |
1 | $ k scale deploy cluster-autoscaler -n kube-system --replicas 0 |
本文介绍在EKS集群下创建HPA的方法
参考资料:
Horizontal Pod Autoscaler(HPA)基于资源 CPU 利用率自动调整 deployment、replication controller 或者 replica 中 pod 的数量,这有助于您的应用程序进行扩展以满足增长的需求,或在不需要资源时进行缩减,从而释放出节点用于其他应用程序。当您设置目标 CPU 利用率百分比时,HPA 扩展或缩减应用程序来尝试满足该目标。
Kubernetes 本身已经包含了 HPA 的 controller,所以不需要额外的安装或部署。
HPA 需要获取 metrics 信息,metrics 信息需要从 Metrics Server 中获取或者从第三方软件获取,关于如何在EKS中安装Metrics Server可以查看 AWS-EKS-11--安装 Kubernetes Metrics Server。
HPA 会周期性(默认15秒)查询目标资源的使用情况,然后和 HPA 中定义的值做比较,并根据比较结果相应的调整 pod 数量。
创建pod时,必须为其设定cpu资源,用于与目标值进行比较,目前v2版本的HPA除了支持CPU的对比,还可以设定其它指标,具体参考HorizontalPodAutoscaler 演练。
参考官方示例
定义资源yaml
1 | # php-apache.yaml |
部署yaml
1 | $ k apply -f php-apache.yaml |
创建HPA
1 | # 创建了一个叫“php-apache”的 HPA,与 deployment 的名称相同,可以用 --name='hpa-name' 指定hpa的名称 |
查看HPA
1 | $ k get hpa |
测试HPA
1 | # 通过运行容器为 Web 服务器创建负载 |
此时Ctrl+C
中断测试容器,过一会查看hpa和pod的情况,可以看到平均 CPU 负载已经降到 0 了,但 REPLICAS 还是 8 个,不会立即降低,不要着急,大约5分钟左右 REPLICAS 最终变为 1
1 | $ k get hpa |
1 | $ k delete hpa php-apache |