一、简介 Ansible 是一个 IT 自动化工具，他能配置系统，部署软件，编排更复杂的 IT 任务，如连续部署或零停机时间滚动更新。 Ansible 用 Python 编写，Ansible 允许不需要在每个节点上安装自己的组件，可以在不止一个地方控制整个基础架构。 二、工作原理 Inventory：被管理节点的列表，资源池。 PlayBook：类似于 Shell 脚本，通过一系列命令执行自动化操作。 Module。 在 Ansible 管理体系中，存在 管理节点 和 被管理节点 两种角色。 被管理节点通常被成为资产。 在管理节点上，Ansible 将 AdHoc 或 PlayBook 转换为 python 脚本，并通过 SSH 传输到被管理节点上。 在被管理节点上依次执行 Python 脚本。 三、Ansible 安装 3.1、先决条件 3.2、安装 Ansible yum 方式： [root@localhost ~]# yum install epel-release [root@localhost ~]# yum install ansible pip 方式： [root@localhost ~]# yum install epel-release [root@localhost ~]# yum install python2-pip [root@localhost ~]# pip install ansible 查看 Ansible 版本： [root@localhost ~]# ansible --version ansible 2.9.27 config file = /etc/ansible/ansible.cfg configured module search path = [u'/root/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules'] ansible python module location = /usr/lib/python2.7/site-packages/ansible executable location = /usr/bin/ansible python version = 2.7.5 (default, Oct 14 2020, 14:45:30) [GCC 4.8.5 20150623 (Red Hat 4.8.5-44)] 四、Ansible 简单部署 4.1、管理节点与被管理节点建立 SSH 信任关系 管理节点 (ansible) 中创建密钥对： [root@localhost ~]# ssh-keygen -t rsa 将本地的公钥传输到被管理节点： [root@localhost .ssh]# ssh-copy-id root@192.168.200.135 [root@localhost .ssh]# ssh-copy-id root@192.168.200.136 4.2、场景 4.2.1、检测所有被管理主机在线状态 [root@manager ~]# ansible all -i 192.168.19.135,192.168.19.136 -m ping 192.168.19.135 | SUCCESS => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": false, "ping": "pong" } 192.168.19.136 | SUCCESS => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": false, "ping": "pong" } -i : 参数后面是一个列表 (List) , 因此当为一个被管理节点时，我们后面一定要加一个英文逗号 (,) , 告知是 List。 4.2.2、确保 /tmp/a.conf 发布到所有被管理节点上 [root@manager ~]# touch /tmp/a.conf [root@manager ~]# ansible all -i 192.168.19.135,192.168.19.136 -m copy -a "src=/tmp/a.conf dest=/tmp/a.conf" 192.168.19.136 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": true, "checksum": "da39a3ee5e6b4b0d3255bfef95601890afd80709", "dest": "/tmp/a.conf", "gid": 0, "group": "root", "md5sum": "d41d8cd98f00b204e9800998ecf8427e", "mode": "0644", "owner": "root", "secontext": "unconfined_u:object_r:admin_home_t:s0", "size": 0, "src": "/root/.ansible/tmp/ansible-tmp-1702535672.46-6415-225212235428808/source", "state": "file", "uid": 0 } 192.168.19.135 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": true, "checksum": "da39a3ee5e6b4b0d3255bfef95601890afd80709", "dest": "/tmp/a.conf", "gid": 0, "group": "root", "md5sum": "d41d8cd98f00b204e9800998ecf8427e", "mode": "0644", "owner": "root", "secontext": "unconfined_u:object_r:admin_home_t:s0", "size": 0, "src": "/root/.ansible/tmp/ansible-tmp-1702535672.44-6413-170350629067151/source", "state": "file", "uid": 0 } all: 在 Ansible 中，叫做 pattern , 即匹配，用来在 -i 参数的资产中匹配一部分，all 代表匹配所有 -i: 指定 Ansible 的资产，即被管理主机 (也可以是文件名) -m: 指定要运行的模板，比如这里的 ping 模块 和 copy 模块 -a: 指定模块的参数，这里 ping 模块没有指定参数，copy 模块指定了 src 和 dest 参数 五、Ansible 资产 实际场景中，被管理的服务器肯定不止 2 台，因此我们可以将被管理服务器写入一个文件或脚本中。 5.1、静态资产 顾名思义，静态资产本身是一个文本文件，一个类似 INI 的文件 默认情况下，Ansible 的资产文件 位于 /etc/ansible/hosts.pip 安装的可能没有这个文件，创建一个即可。 示例： # This is the default ansible 'hosts' file. # # It should live in /etc/ansible/hosts # # - Comments begin with the '#' character # - Blank lines are ignored # - Groups of hosts are delimited by [header] elements # - You can enter hostnames or ip addresses # - A hostname/ip can be a member of multiple groups `Ex 1: 不属于任何组的单独主机，必须放在所有主机组的前面，也就是必须位于文件最前方 green.example.com blue.example.com 192.168.100.1 192.168.100.10 `Ex 2: 主机组 webservers 包含 4 个主机，调用时可以通过组名匹配主机 [webservers] alpha.example.org beta.example.org 192.168.1.100 192.168.1.110 ` 有规律的主机可以使用这总方法显示，这代表 www001.example.com , www002.example.com , ... www [001:006].example.com `Ex 3: 主机组可以在 组和主机中间添加空格以便阅读 [dbservers] db01.intranet.mydomain.net db02.intranet.mydomain.net 10.25.1.56 10.25.1.57 # Here's another example of host ranges, this time there are no # leading 0s: ## db-[99:101]-node.example.com 5.1.1、自定义资产 vim /etc/ansible/inventory.ini # 没有组名的属于 all 组 1.1.1.1 2.2.2.2 3.3.3.[1:15] test01.lml.com test03.lml.com test [05:09].lml.com [web_servers] 192.168.1.2 192.168.1.3 192.168.1.5 [db_servers] 192.168.2.2 192.168.2.3 192.168.2.5 192.168.1.5 # 创建 all_server 组，并将 db_servers 和 web_servers 添加为自己的子组 # 选择 all_server 组时，则代表选中了 web_servers 和 db_servers 组下的所有主机 [all_servers] [all_servers:children] db_servers web_servers 1、Ansible 的资产文件中，可以以 IP 地址的形式或者主机名的形式存在。 2、Ansible 的资产若连续，可以使用 [start:end] 的形式去表达。 3、可以将服务器按照业务场景定义成组，比如 db_servers 和 web_servers。 4、组合组之间可以存在继承关系，比如 db_servers 和 web_servers 同时继承 all_servers 组。 5.1.2、如何使用自定义资产 通过 -i 参数指定自定义资产的位置即可 (可以是全路径，也可以是相对路径)。 # ansible all -i inventory.ini .........// 伪命令，不可执行 5.1.3、验证自定义资产 列举出所有资产： ansible all -i inventory.ini --list-hosts hosts (19) : 1.1.1.1 2.2.2.2 3.3.3.1 ...... 列举出选定资产 (比如 db_servers)： ansible db_servers -i inventory.ini --list-hosts 5.2、资产选择器 5.2.1、基本语法格式 ansible PATTERN -i inventory -m moudle -a argument PATTERN：可以是 IP 列表，也可以是组名，值得注意的是，资产中写的什么就要写什么，不能通过域名解析 -i：资产列表，从哪里进行筛选 -m：使用哪一个模块 -a：模块所对应的参数 1、选择一台或几台服务器 [root@localhost ansible]# ansible 1.1.1.1 -i inventory.ini --list-host hosts (1): 1.1.1.1 [root@localhost ansible]# ansible test01.lml.com -i inventory.ini --list-host hosts (1): test01.lml.com [root@localhost ansible]# ansible 1.1.1.1,2.2.2.2 -i inventory.ini --list-host hosts (2): 1.1.1.1 2.2.2.2 2、选择一组服务器 [root@localhost ~]# ansible all_servers -i inventory.ini --list-host hosts (6): 192.168.2.2 192.168.2.3 192.168.2.5 192.168.1.2 192.168.1.3 192.168.1.5 # 两个子组的重复部分会被去重 3、使用 * 匹配 [root@localhost ansible]# ansible 3.3.3.1* -i inventory.ini --list-host hosts (7): 3.3.3.10 3.3.3.11 3.3.3.12 3.3.3.13 3.3.3.14 3.3.3.15 3.3.3.1 4、使用逻辑进行匹配 1）并集 (两个组中所有主机，去重) # 显示两个组中所有的主机 [root@localhost ansible]# ansible 'web_servers:db_servers' -i inventory.ini --list-host hosts (6): 192.168.1.2 192.168.1.3 192.168.1.5 192.168.2.2 192.168.2.3 192.168.2.5 2）交集 (两个组中共有主机) # 显示同时承担 web_server 和 db_server 的主机 [root@localhost ansible]# ansible 'web_servers:&db_servers' -i inventory.ini --list-host hosts (1): 192.168.1.5 3）差集 (A 有 B 没有，或 B 有 A 没有) # 在 web_servers 中，但是不在 db_servers 中 [root@localhost ansible]# ansible 'web_servers:!db_servers' -i inventory.ini --list-host hosts (2): 192.168.1.2 192.168.1.3 # 在 db_servers 中，但是不再 web_servers 中 [root@localhost ansible]# ansible 'db_servers:!web_servers' -i inventory.ini --list-host hosts (3): 192.168.2.2 192.168.2.3 192.168.2.5 六、Ansible Ad-Hoc Command Ad-hoc 命令是一个概念性的名字，是相对于 Ansible playbook 来说的，类似于在命令行敲入 shell 和写 shell scripts 两者之间的关系，可以用于执行一些临时命令。 如果我们敲入一些命令去比较快的完成一些事情，而不需要将这些执行的命令特别保存下载，这样的命令就叫做 ad-hoc 命令。 Ansible 提供两种方式去完成任务，一是 ad-hoc , 一是写 Ansible playbook, 前者可以解决一些简单的任务，后者解决较复杂的任务，比如做配置管理或部署。 6.1、命令格式 命令格式语法如下 : ansible pattern [-i inventory] -m moudule -a argument pattern: 资产选择器 -i: 指定资产清单文件的位置。 -m: 指定本次 Ansible ad-hoc 要执行的模块，可以类别成 shell 中的命令。 -a: 模块的参数，可以类比成 SHELL 中的命令参数。 6.3、联机帮助 Ansible 的核心模块和附加模块，数量有 1000+ , 很难一次性掌握，因此我们可以使用 Ansible 的帮助文档，它本身提供的命令就是由 Ansible-doc 实现 列出所有的核心模块和附加模块： ansible-doc -l 查询某个模块的使用方法： ansible-doc modulename 6.4、常用模块 为方便演示，我们使用如下配置文件： 管理机 : 192.168.200.132 被管理机 1 : 192.168.200.133 被管理机 2 : 192.168.200.134 编写如下配置文件到 /etc/ansible/hosts： [dbservers] 192.168.200.133 [webservers] 192.168.200.134 6.4.1、command & shell 模块 两个模块都是在远程服务器上执行命令 command 模块：是 ad-hoc 的默认模块，在执行 ad-hoc 时，若不指定模块的名字则默认使用此模块 [root@localhost ansible]# ansible all -i hosts -a "echo 'hello world'" 192.168.200.133 | CHANGED | rc=0 >> hello world 192.168.200.134 | CHANGED | rc=0 >> hello world [root@localhost ansible]# ansible all -i hosts -m shell -a "echo 'hello world'" 192.168.200.133 | CHANGED | rc=0 >> hello world 192.168.200.134 | CHANGED | rc=0 >> hello world command 模块 和 shell 模块的差异： shell 模块：可以执行 SHELL 的内置命令和特性 (比如管道符)。 command 模块：无法执行 SHELL 的内置命令和特性。 实例： [root@localhost ansible]# ansible all -i hosts -m shell -a "echo 'hello' | grep -o 'e'" 192.168.200.133 | CHANGED | rc=0 >> e 192.168.200.134 | CHANGED | rc=0 >> e [root@localhost ansible]# ansible all -i hosts -m command -a "echo 'hello' | grep -o 'e'" 192.168.200.133 | CHANGED | rc=0 >> hello | grep -o e 192.168.200.134 | CHANGED | rc=0 >> hello | grep -o e 6.4.2、script 模块 将管理节点上的脚本传到被管理节点 (远程服务器) 上进行执行，理论上此模块的执行完全不需要被管理服务器上有 Python。 示例： [root@localhost ansible]# echo "touch /tmp/testfile" > /root/a.sh [root@localhost ansible]# ansible webservers -i hosts -m script -a "/root/a.sh" 192.168.200.134 | CHANGED => { "changed": true, "rc": 0, "stderr": "Shared connection to 192.168.200.134 closed.\r\n", "stderr_lines": [ "Shared connection to 192.168.200.134 closed." ], "stdout": "", "stdout_lines": [] } [root@localhost ansible]# ansible webservers -i hosts -m shell -a "ls -l /tmp" 192.168.200.134 | CHANGED | rc=0 >> -rw-r--r-- 1 root root 0 Dec 18 21:23 testfile 6.4.5、yum 模块 等同于 Linux 上的 yum 命令，对远程服务器上 RPM 包进行管理，常用参数如下： name 要安装的软件包名，多个软件包以 ( , ) 隔开。 state 对当前指定的软件安装，移除操作 (present installed latest absent removed)。 present 确认已经安装，但不升级。 installed 确认已经安装。 latest 确保安装，且升级为最新。 absent 和 removed 确认已删除。 6.4.5.1、安装一个软件包 [root@localhost ansible]# ansible webservers -i hosts -m yum -a "name=nginx state=present" [root@localhost ansible]# ansible webservers -i hosts -m yum -a "name=nginx state=installed" [root@localhost ansible]# ansible webservers -i hosts -m yum -a "name=nginx state=latest" # 检查是否安装 [root@manager ~]# ansible webservers -i /etc/ansible/hosts -m shell -a "nginx -version" 192.168.200.134 | CHANGED | rc=0 >> nginx version: nginx/1.20.1 6.4.5.2、移除一个软件包 [root@manager ~]# ansible webservers -i /etc/ansible/hosts -m yum -a "name=nginx state=absent" [root@manager ~]# ansible webservers -i /etc/ansible/hosts -m yum -a "name=nginx state=removed" [root@manager ~]# ansible webservers -i /etc/ansible/hosts -m shell -a "nginx -version" 192.168.200.134 | FAILED | rc=127 >> /bin/sh: nginx: command not foundnon-zero return code 6.4.5.3、安装一个软件包组 [root@manager ~]# ansible all -i /etc/ansible/hosts -m yum -a "name='@Developmenttools' state=present" 6.4.6、systemd 模块 CentOS6 之前的版本使用 service 模块，使用 ansible-doc service 命令查看帮助信息。 管理远程节点上的 systemd 服务，就是由 systemd 所管理的服务，常用参数如下 daemon_reload：重新载入 systemd , 扫描新的或有变动的单元，值为 yes。 enabled：是否开机自启动 yes | no。 name：必选项，服务名称，比如 httpd , vsftpd 等。 state：对当前服务执行启动，停止，重新加载等操作 (started , stopped , restarted , reloaded)。 6.4.6.1、Example : 重新加载 systemd [root@localhost ~]# ansible webservers -i /etc/ansible/hosts -m systemd -a "daemon_reload=yes" 192.168.200.134 | SUCCESS => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": false, "name": null, "status": {} } 6.4.6.2、Example : 启动 nginx [root@localhost ~]# ansible webservers -i /etc/ansible/hosts -m systemd -a "name=nginx state=started" 6.4.6.3、Example : 关闭 nginx [root@localhost ~]# ansible webservers -i /etc/ansible/hosts -m systemd -a "name=nginx state=stopped" 6.4.7、group 模块 对被管理节点的组进行管理，常用参数如下： name：组名称，必须的。 system：是否为系统组，yes | no。 6.4.7.1、Example : 创建普通组 db_admin [root@localhost ~]# ansible all -i /etc/ansible/hosts -m group -a "name=db_admin" 192.168.200.133 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": true, "gid": 1001, "name": "db_admin", "state": "present", "system": false } 6.4.8、user 模块 用于对被管理节点上的用户进行管理，常用参数如下 : name：必须的参数，指定用户名。 password：设置用户的密码，这里接受的是一个加密的值，因为会直接存到 shadow , 默认不设置密码。 update_password：加入设置的密码不同于原密码，则会更新密码，在 1.3 版本中被加入。 home：指定用户的家目录。 shell：设置用户的 shell。 comment：用户的描述信息。 create_home：在创建用户时，是否创建其家目录，默认创建，加入不创建，设置为 no , 在 2.5 版本之前使用 createhome 参数。 group：设置用户的主组。 groups：将用户加入到多个其他组中，多个用逗号隔开，默认会把用户从其他已经加入的组中删除。 append：值为 yes | no , 和 groups 配合使用，值为 yes 时，不会把用户从其他已经加入的组中删除。 system：值为 yes 时，会创建一个系统账户。 expires：设置用户的过期时间，值为时间戳，会转为天数后，放在 shadow 的第 8 个字段里。 generate_ssh_key：设置为 yes 时，会为用户生成密钥，这不会覆盖原来的密钥。 ssh_key_type：指定用户的密钥类型，默认 rsa , 具体的类型取决于被管理节点。 state：删除或添加用户，yes 为添加，absent 为删除，默认值为 yes。 remove 当与 state=absent 一起使用时，删除一个用户及关联的目录，比如家目录，邮箱目录，可选的值为 yes/no。 先生成加密密码： [root@localhost ~]# pass=$(echo “LML123456” | openssl passwd -l -stdin) [root@localhost ~]# echo $pass 111MQBJpWBh$krH/eQgBQcMpyDMKxVpS.0 6.4.8.1、Example : 创建账号，设置密码 [root@localhost ~]# ansible all -i /etc/ansible/hosts -m user -a "name=foo password=${pass}" 6.4.8.2、Example : 创建账号，设置密码，改密钥类型 [root@localhost ~]# ansible all -i /etc/ansible/hosts -m user -a "name=Dragon password=${pass} ssh_key_type=ecdsa" 6.4.8.3 Example : 创建账号，设置密码，有效期，加组 创建 Cai 用户，并设置其有效期到 2024_4_15 日，加入到 db_admin 组中。 [root@localhost ~]# ansible all -i /etc/ansible/hosts -m user -a "name=Cai password=${pass} group=db_admin append=yes" 6.4.9、file 模块 file 模块主要用于远程主机上的文件操作，常用参数： group：定义文件 / 目录的属组。 mode：定义文件 / 目录的而权限。 owner：定义文件 / 目录属组。 path：必选项，操作文件 / 目录 的路径。 recurse：递归的设置文件的属性，只对目录有效。 src：要被链接 (软/硬) 的源文件的路径，值用于 state=link 的情况。 dest：被链接到的路径，只应用于 state=link 的情况。 state：对文件执行什么操作。 directory：如果目录不存在则创建目录。 file：文件不存在则不不会被创建，存在则返回文件的信息，常用于检查文件是否存在。 link：创建软连接。 hard：创建硬链接。 touch：如果文件不存在，则会创建一个新的文件，如果文件或目录存在，则更新其最后修改时间。 absent：删除目录，文件或者取消链接。 6.4.9.1、Example : 文件操作 # 创建一个文件 [root@localhost ~]# ansible all -i /etc/ansible/hosts -m file -a "path=/tmp/foo.conf state=touch" # 改变文件所有者及权限 [root@localhost ~]# ansible all -i /etc/ansible/hosts -m file -a "path=/tmp/foo.conf owner=nobody group=nobody mode=0644" # 创建一个软连接 [root@localhost ~]# ansible all -i /etc/ansible/hosts -m file -a "src=/tmp/foo.conf dest=/tmp/link.conf state=link" # 创建一个目录 [root@localhost ~]# ansible all -i /etc/ansible/hosts -m file -a "path=/tmp/test_dir state=directory" # 取消一个链接 [root@localhost ~]# ansible all -i /etc/ansible/hosts -m file -a "path=/tmp/link.conf state=absent" # 删除一个文件 [root@localhost ~]# ansible all -i /etc/ansible/hosts -m file -a "path=/tmp/foo.conf state=absent" 6.4.10、cron 模块 管理远程节点的 CRON 服务，等同于 Linux 中的计划任务，常用参数如下。 注意：使用 Ansible 创建的计划任务，是不能使用本地 crontab -e 去编辑，否则 Ansible 无法再次操作此计划任务。 name：指定一个 cron job 的名字，一定要指定，便于日后删除。 minute：指定分钟，可以设置成 (0-59 , * , /2 等) 格式，默认是 * , 也就是每分钟。 hour：指定小时，可以设置达成 (0-23 , * , */2 等) 格式，默认是 * , 也就是每小时。 day：指定天，可以设置成 (1-31 , * , */2 等) 格式，默认是 * , 也就是每天。 month：指定月份，可以设置成 (1-12 , * , */2 等) 格式，默认是 * , 也就是每周。 weekday：指定星期，可以设置成 (0-6 for Sunday-Saturday , * 等) 格式，默认是 * , 也就是每天。 job：要执行的内容，通常可以写个脚本，或者一段内容。 state：指定这个 job 的状态，可以是新增 (present) , 或者是删除 (absent) , 默认为 (present)。 6.4.10.1、Example 1 : 创建一个 cron job 任务 [root@manager ~]# ansible all -i /etc/ansible/hosts -m cron -a "name=newJob minute='0' job='ls -alh > /dev/null'" 验证 [root@host1 ~]# crontab -l #Ansible: newJob 0 * * * * ls -alh > /dev/null 6.4.10.2、Example 2 : 删除一个 cron job 任务 [root@manager ~]# ansible all -i /etc/ansible/hosts -m cron -a "name=newJob state=absent" 6.4.11、debug 模块 6.4.11.1、Example 1 : 打印变量 debug 模块主要用于调试时使用，通常的作用是将一个变量的值打印出来，常用参数如下： var：直接打印一个指定的变量值。 msg：打印一段可以格式化的字符串。 # 直接打印一个变量 (-e 是传入变量) [root@manager ~]# ansible all -i /etc/ansible/hosts -m debug -a "var=role" -e "role=web" # 打印带字符串的 变量 [root@manager ~]# ansible all -i /etc/ansible/hosts -m debug -a "msg='role is {{role}}'" -e "role=web" 6.4.12、template 模块 template 模块使用了 jinjia2 格式作文文件模板，可以进行文档内变量的替换。 它的每次使用都会被 ansible 标记为 “changed” 状态，文件以 .j2 结尾，常用参数如下： src：指定 Ansible 控制端的文件路径。 dest：指定 Ansible 被控制端的文件路径。 owner：指定文件的属主。 group：指定文件的属组。 mode：指定文件的权限。 backup：创建一个包含时间错信息的备份文件，这样如果以某种方式损坏了原始文件，就可以将其复原，yes/no。 6.4.12.1、Example 1 : 传递文件 (带变量) # 1. 创建一个 hello_world.j2 文件 [root@manager ~]# cat hello_world.j2 Hello {{var}} ! # 2. 执行命令，并且设置变量 var 的值为 world [root@manager ~]# ansible all -i /etc/ansible/hosts -m template -a "src=hello_world.j2 dest=/tmp/hello_world.world" -e "var=world" # 3. 在被控主机上验证 Hello world ! 6.4.13、lineinfile 模块 在被管理节点上，使用正则匹配的方式对目标文件的一行内容修改删除等操作。 如果是一个文件中把所有匹配到的多行进行统一处理，参考 replace 模块。 如果相对一个文件进行一次性 添加 / 更新 / 删除等多行内容操作，参考 blockinfile 模块。 常用参数如下： path：目标文件路径，必须的。 state：可选值 present 替换 (默认 | absent 删除。 regexp：在文件的每一行中查找的正则表达式，对于 state=present , 进找到的最后一行将被替代。 line：要在文件中插入/替换的行，需要 state=present。 create：文件不存在时，是否要创建文件并添加内容，yes/no。 6.4.13.1、Example 1 : 删除被控节点文件里的某一条内容 [root@manager ~]# ansible all -i /etc/ansible/hosts -m lineinfile -a "path=/etc/sudoers regexp='^% wheel' state=absent" 6.4.13.2、Example 1 : 关闭远程主机的 selinux [root@manager ~]# ansible all -i /etc/ansible/hosts -m lineinfile -a "path=/etc/selinux/config regexp='^SELINUX=' line='SELINUX=disabled' state=present" 七、Ansible-Playbook Ad-Hoc 的问题：AD-HOC 每次只能在被管理节点上执行简单的命令。 而日常工作中，我们往往面临的是一系列复杂的操作，例如我们可能需要安装软件，更新配置，重启服务等等一系列操作的组合，此时 Ad-Hoc 有些力不从心，使用 PLAYBOOK 可以帮助我们解决这个问题。 7.1、什么是 PalyBook Playbook 通常被大家翻译成剧本。 可以认为它是 Ansible 自定义的一门语言 (可以将 Playbook 比作 Linux 中的 shell) , 而 Ansible 中的 Module 可以比作为 Linux 中的各种命令。 7.2、YAML 学习 安装 pip 对 yaml 文件格式进行检查。 yum install python2-pip 7.2.1、YAML 特点 # YAML 文件以 # 为注释 # YAML 文件以 .yml 或 .yaml 结尾 # YAML 文件以 --- 开始，以... 结束，但是开始和结束标志都是可选的 7.2.2、基本语法 大小写敏感。 使用缩进表示层级关系，一般规范 2 个空格。 缩进时使用 tab 键，还是空格 一定要达到统一，建议使用空格。 相同层级的元素必须左侧对齐。 YAML 支持的数据结构有三种： 字符串 列表 字典 7.2.2.5、校验 YAML 语句 将 YAML 文件，通过 Python 的 YAML 模块进行验证： # 安装 python2-pip [root@manager ~]# yum install python2-pip # 安装 pyyaml [root@manager ~]# pip install pyyaml # cat test.yaml --- - red - green - blue ... # 对 YAML 格式文件进行检验 [root@manager ~]# python -c 'import yaml,sys; print yaml.load (sys.stdin)' < test.yaml ['red', 'green', 'blue'] 7.3、定义一个 Playbook 一个 Playbook 是由一个或多个 Play 构成，那么一个含有多个 Play 的 Playbook 结构上应该是如下的样子： --- - key1: value1 key2: value2 key3: value3 ... 一个含有多个 Play 的 Playbook 结构山应该是如下的样子： --- # 一个含有 3 个 Play 的伪 PlayBook 构成 - key1: value1 key2: value2 key3: value3 - key4: value1 key5: value2 key6: value3 - key1: value1 key2: value2 key3: value3 ... 7.3.1、Play 的属性 Play 中的每一个 key , 比如 key1 , key2 等，这些 key 在 PlayBook 中被定义为 Play 的属性。 这些属性具有特殊意义，我们不能随意的自定义 Play 的属性，常用属性如下： name：每个 play 的名字，用于任务执行后的回显 (一定要写)。 hosts：每个 play 涉及的被管理服务器，通 ad hoc 中的 pattern。 tasks：每个 play 中具体要完成的任务，以列表的形式表达。 become：如果需要提权，则加上 become 相关属性。 become_user：若提权的话，提权到哪个用户上。 remote_user：指定连接用户，若不指定，则默认使用当前执行 ansible Playbook 的用户。 等等 7.3.2、一个完整的剧本 最常规写法： --- - name: the first play example hosts: all remote_user: root tasks: - name: install nginx packet yum: name=nginx state=present - name: copy nginx.conf to remote server copy: src=nginx.conf dest=/etc/nginx/nginx.conf - name: start nginx service service: name=nginx enabled=true state=started ... tasks 属性中任务的多种写法： # 以启动 nginx 服务，并增加开机启动为例 # 一行的形式 service: name=nginx enabled=true state-started # 多行的形式 service: name=nginx enabled=true state=started # 多行字典的形式 service: name: nginx enabled: true state: started 7.3.3、具有多个 Play 的 Playbook --- - name: manage web servers hosts: webservers remote_user: root tasks: - name: install nginx packet yum: name=nginx state=present - name: copy nginx.conf to remote server copy: src=nginx.conf dest=/etc/nginx/nginx.conf - name: start nginx server service: name=nginx enabled=true state=started - name: manager db servers hosts: dbservers tasks: - name: update database config copy: src=my.cnf dest=/etc/my.cnf ... 7.4、校验一个 PlayBook 下面校验的方法，只能校验 PlayBook 是否正确，而不能校验 YAML 文件是否语法正确。 之前 YAML 校验的方法： [root@manager ~]# python -c 'import yaml,sys; print yaml.load (sys.stdin)' < test.yaml PlayBook 校验： [root@manager ~]# ansible-playbook myplaybook.yml --syntax-check playbook: myplaybook.yml 7.5、运行一个 PlayBook [root@manager ~]# ansible-playbook myplaybook.yml 7.6、如何单步跟从调试 PlayBook ansible-playbook myplaybook.yml --step 7.7、如何测试运行 PlayBook // 会执行完整个 PlayBook , 但是所有 Task 中的行为都不会在远程服务器上执行，所有执行都是模拟行为 # ansible-playbook myplaybook.yml -C //-C 为大写字母 C 八、Ansible 变量 8.1、Ansible 变量介绍 变量由 字母 数字 下划线 组成。 但 只能以字母开头，保留关键字不能作为变量名称。 8.2、变量类型 根据变量的作用范围，大体的将变量分为： 全局变量 剧本变量 资产变量 但这只是一个比较除粗糙的划分，不能囊括 Ansible 中的所有变量，下面将分别从这三种变量入手，去介绍变量的使用。 8.2.1、全局变量 全局变量 是 我们使用 ansible 或者使用 ansible-playbook 时，手动通过 -e 参数传递给 Ansible 的变量。 通过 ansible 或 ansible-playbook 的 help 帮助，可以获取具体格式使用方式。 普通的 key-value 模式： [root@manager ~]# ansible webservers -i /etc/ansible/hosts -m debug -a "msg='name is {{key}}'" -e "key='LiuMenglong'" 192.168.200.133 | SUCCESS => { "msg": "name is LiuMenglong" } 传递一个 YAML/JSON 的形式 (他们的最终形式必须是字典)： # 指定时必须加 @ [root@manager ~]# cat a.json {"name": "LiuML", "age": "16"} [root@manager ~]# ansible webservers -i /etc/ansible/hosts -m debug -a "msg='name is {{name}} , age is {{age}}'" -e @a.json [WARNING]: Found variable using reserved name: name 192.168.200.133 | SUCCESS => { "msg": "name is LiuML , age is 16" } [root@manager ~]# cat b.yaml --- name: lisi age: 33 ... [root@manager ~]# ansible webservers -i /etc/ansible/hosts -m debug -a "msg='name is {{name}} , age is {{age}}'" -e @b.yaml [WARNING]: Found variable using reserved name: name 192.168.200.133 | SUCCESS => { "msg": "name is lisi , age is 33" } 8.2.2、剧本变量 在 Playbook 中使用的变量叫做剧本变量，他的定义由多种方式，我们使用两种，文件内定义即引用文件。 8.2.2.1、通过 PLAY 属性 vars 定义 --- - name: test play vars hosts: all vars: user: lisi home: /home/lisi ... 8.2.2.2、通过 PLAY 属性 vars_file 定义 --- - name: test play vars hosts: all vars_files: - vars/users.yaml ... cat vars/users/yaml --- user: lilei home: /home/lilei ... 8.2.2.3、如何在 Playbook 中使用这些变量 在 PlayBook 中使用变量时，使用 {{ 变量名 }} 来使用变量，在使用变量是，大部分情况外侧要加双引号即 “{{ 变量 }}”。 Example1: --- - name: Test Vars using in playbook hosts: all vars: user: lisi home: /home/lisi tasks: - name: create the users {{user}} user: name={{user}} home={{home}} ... [root@manager ~]# ansible-playbook 123456.yaml -C [root@manager ~]# ansible-playbook 123456.yaml 注意事项：在下面的例子 (字典) 中，{{ 变量 }} 外一定要加双引号，否则会 yaml 解析错误 Example2 --- - name: Test Vars using in playbook hosts: all vars: user: lisi home: /home/lisi tasks: - name: create the users {{user}} user: name: "{{user}}" home: "{{home}}" ... 8.2.3、资产变量 资产变量是和资产紧密相关的一种变量，资产变量分为 主机变量 和 主机组变量，分别针对单个主机和一组主机。 8.2.3.1、主机变量 # cat hostsandhostvars [webservers] 192.168.200.133 user=lisi age=18 192.168.200.134 验证： [root@localhost ansible]# ansible webservers -i /etc/ansible/hostsandhostvars -m debug -a "var=user" 192.168.200.133 | SUCCESS => { "user": "lisi" } 192.168.200.134 | SUCCESS => { "user": "VARIABLE IS NOT DEFINED!" } 8.2.3.2、主机组变量 主机变量优先于主机组变量。 # cat hostsandhostvars [webservers] 192.168.200.133 user=lisi age=18 192.168.200.134 [webservers:vars] user=tom 验证： [root@localhost ansible]# ansible webservers -i /etc/ansible/hostsandhostvars -m debug -a "var=user" 192.168.200.133 | SUCCESS => { "user": "lisi" } 192.168.200.134 | SUCCESS => { "user": "tom" } 8.2.3.3、变量的继承 变量是可以继承的： # cat host_v3 [dbservers] 192.168.200.133 [webservers] 192.168.200.134 [allserver] [allserver:children] webservers dbservers [allserver:vars] user=lml 验证： [root@localhost ansible]# ansible webservers -i /etc/ansible/host_v3 -m debug -a "var=user" 192.168.200.134 | SUCCESS => { "user": "lml" } 参考： Ansible 超详细教程 w3cschoo Ansible Ansible 中文权威指南
一、Prometheus 相关概念 1.1、内部存储机制 Prometheus 有着非常高效的时间序列数据存储方法，每个采样数据仅仅占用 3.5byte 左右空间，上百万条时间序列，30 秒间隔，保留 60 天，大概花了 200 多 G（引用官方 PPT）。 Prometheus 内部主要分为三大块： Retrieval 是负责定时去暴露的目标页面上去抓取采样指标数据。 Storage 是负责将采样数据写磁盘。 PromQL 是 Prometheus 提供的查询语言模块。 1.2、数据模型 Prometheus 存储的所有数据都是时间序列数据（Time Serie Data，简称时序数据）。时序数据是具有时间戳的数据流，该数据流属于某个度量指标（Metric）和该度量指标下的多个标签（Label）。 每个 Metric name 代表了一类的指标，他们可以携带不同的 Labels，每个 Metric name + Label 组合成代表了一条时间序列的数据。 在 Prometheus 的世界里面，所有的数值都是 64bit 的。每条时间序列里面记录的其实就是 64bit timestamp (时间戳) + 64bit value (采样值)。 Metric name（指标名称）：该名字应该具有语义，一般用于表示 metric 的功能，例如：http_requests_total, 表示 http 请求的总数。其中，metric 名字由 ASCII 字符，数字，下划线，以及冒号组成，且必须满足正则表达式 [a-zA-Z_:][a-zA-Z0-9_:]*。 Lables（标签）：使同一个时间序列有了不同维度的识别。例如 http_requests_total {method=“Get”} 表示所有 http 请求中的 Get 请求。当 method=“post” 时，则为新的一个 metric。标签中的键由 ASCII 字符，数字，以及下划线组成，且必须满足正则表达式 [a-zA-Z_:][a-zA-Z0-9_:]*。 timestamp (时间戳)：数据点的时间，表示数据记录的时间。 Sample Value（采样值）：实际的时间序列，每个序列包括一个 float64 的值和一个毫秒级的时间戳。 例如图上的数据： http_requests_total {status="200",method="GET"} http_requests_total {status="404",method="GET"} 根据上面的分析，时间序列的存储似乎可以设计成 key-value 存储的方式（基于 BigTable）。 进一步拆分，可以像下面这样子： 上图的第二条样式就是现在 Prometheus 内部的表现形式了，__name__ 是特定的 label 标签，代表了 metric name。 再回顾一下 Prometheus 的整体流程： 上面提到了 K-V 存储，当然是使用了 LevelDB 的引擎，它的特点是顺序读写性能非常高，这是非常符合时间序列的存储的。 1.3、Metric 类型 Prometheus 定义了 4 种不同的指标类型 (metric type)：Counter（计数器）、Gauge（仪表盘）、Histogram（直方图）、Summary（摘要）。 1.3.1、Counter（计数器） 一种累加的 metric，典型的应用如：请求的个数，结束的任务数， 出现的错误数等等。 【例如】查询 http_requests_total {method=“get”, job=“Prometheus”, handler=“query”} 返回 8，10 秒后，再次查询，则返回 14。 1.3.2、Gauge（仪表盘） 数据是一个瞬时值，如果当前内存用量，它随着时间变化忽高忽低。 【例如】go_goroutines {instance=“172.17.0.2”, job=“Prometheus”} 返回值 147，10 秒后返回 124。 1.3.3、Histogram（直方图） Histogram 取样观测的结果（一般是请求持续时间或响应大小）并在一个可配置的分布区间（bucket）内计算这些结果。其也提供所有观测结果的总和。 Histogram 有一个基本 metric 名称 ，在一次抓取中展现多个时间序列： 累加的 counter，代表观测区间：_bucket {le=“”} 所有观测值的总数：_sum 观测到的事件数量：_count 例如 Prometheus server 中 prometheus_local_storage_series_chunks_persisted, 表示 Prometheus 中每个时序需要存储的 chunks 数量，我们可以用它计算待持久化的数据的分位数。 1.3.4、Summary（摘要） 和 histogram 相似，summary 取样观测的结果（一般是请求持续时间或响应大小）。但是它还提供观测的次数和所有值的总和，它通过一个滑动的时间窗口计算可配置的分位数。 Summary 有一个基本的 metric 名称 ，在一次抓取中展现多个时间序列： 观测事件的流式 φ- 分位数（0 ≤ φ ≤ 1）：{quantile=“φ”} 所有观测值的总和：_sum 观测的事件数量：_count 例如 Prometheus server 中 prometheus_target_interval_length_seconds。 1.4、Histogram 和 Summary 的对比 以下是类型为 histogram 和 summary 的样本输出示例： # A histogram, which has a pretty complex representation in the text format: # HELP http_request_duration_seconds A histogram of the request duration. # TYPE http_request_duration_seconds histogram http_request_duration_seconds_bucket {le="0.05"} 24054 http_request_duration_seconds_bucket {le="0.1"} 33444 http_request_duration_seconds_bucket {le="0.2"} 100392 http_request_duration_seconds_bucket {le="+Inf"} 144320 http_request_duration_seconds_sum 53423 http_request_duration_seconds_count 144320 # Finally a summary, which has a complex representation, too: # HELP rpc_duration_seconds A summary of the RPC duration in seconds. # TYPE rpc_duration_seconds summary rpc_duration_seconds {quantile="0.01"} 3102 rpc_duration_seconds {quantile="0.05"} 3272 rpc_duration_seconds {quantile="0.5"} 4773 rpc_duration_seconds_sum 1.7560473e+07 rpc_duration_seconds_count 2693 1.5、任务（JOBS）与实例（INSTANCES） 用 Prometheus 术语来说，可以抓取的端点称为 instance，通常对应于单个进程。 具有相同目的的 instances 的集合（例如，出于可伸缩性或可靠性而复制的过程）称为 job。 例如，一个具有四个复制实例的 API 服务器作业: job: api-server instance 1: 1.2.3.4:5670 instance 2: 1.2.3.4:5671 instance 3: 5.6.7.8:5670 instance 4: 5.6.7.8:5671 instance: 一个单独 scrape 的目标， 一般对应于一个进程。: jobs: 一组同种类型的 instances（主要用于保证可扩展性和可靠性） 1.6、Node exporter Node exporter 主要用于暴露 metrics 给 Prometheus，其中 metrics 包括：cpu 的负载，内存的使用情况，网络等。 1.7、Pushgateway Pushgateway 是 Prometheus 生态中一个重要工具，使用它的原因主要是： Prometheus 采用 pull 模式，可能由于不在一个子网或者防火墙原因，导致 Prometheus 无法直接拉取各个 target 数据。 在监控业务数据的时候，需要将不同数据汇总，由 Prometheus 统一收集。 由于以上原因，不得不使用 pushgateway，但在使用之前，有必要了解一下它的一些弊端： 将多个节点数据汇总到 pushgateway, 如果 pushgateway 挂了，受影响比多个 target 大。 Prometheus 拉取状态 up 只针对 pushgateway, 无法做到对每个节点有效。 Pushgateway 可以持久化推送给它的所有监控数据。因此，即使你的监控已经下线，prometheus 还会拉取到旧的监控数据，需要手动清理 pushgateway 不要的数据。 二、Exporter 介绍 Exporter 是 prometheus 监控中重要的组成部分，负责数据指标的采集。广义上讲所有可以向 Prometheus 提供监控样本数据的程序都可以被称为一个 Exporter。而 Exporter 的一个实例称为 target。官方给出的插件有 blackbox_exporter、node_exporter、mysqld_exporter、snmp_exporter 等，第三方的插件有 redis_exporter，cadvisor 等。 官方和一些社区提供好多 exproter, 我们可以直接拿过来采集我们的数据。 官方的 exporter 地址：https://prometheus.io/docs/instrumenting/exporters/ 2.1、常见的 Exporter 简介 1、blackbox_exporter GitHub 地址：https://github.com/prometheus/blackbox_exporter bloackbox exporter 是 prometheus 社区提供的黑盒监控解决方案，运行用户通过 HTTP、HTTPS、DNS、TCP 以及 ICMP 的方式对网络进行探测。这里通过 blackbox 对我们的站点信息进行采集。 2、node_exporter GitHub 地址：https://github.com/prometheus/node_exporter node_exporter 主要用来采集机器的性能指标数据，包括 cpu，内存，磁盘，io 等基本信息。 3、mysqld_exporter GitHub 地址：https://github.com/prometheus/mysqld_exporter mysql_exporter 是用来收集 MysQL 或者 Mariadb 数据库相关指标的，mysql_exporter 需要连接到数据库并有相关权限。 4、snmp_exporter GitHub 地址：https://github.com/prometheus/snmp_exporter SNMP Exporter 从 SNMP 服务中采集信息提供给 Promethers 监控系统使用。 2.2、Exporter 的来源 从 Exporter 的来源上来讲，主要分为两类： 1、社区提供 Prometheus 社区提供了丰富的 Exporter 实现，涵盖了从基础设施，中间件以及网络等各个方面的监控功能。这些 Exporter 可以实现大部分通用的监控需求。下表列举一些社区中常用的 Exporter： 2、用户自定义 除了直接使用社区提供的 Exporter 程序以外，用户还可以基于 Prometheus 提供的 Client Library 创建自己的 Exporter 程序，目前 Promthues 社区官方提供了对以下编程语言的支持：Go、Java/Scala、Python、Ruby。同时还有第三方实现的如：Bash、C++、Common Lisp、Erlang,、Haskeel、Lua、Node.js、PHP、Rust 等。 2.3、Exporter 的运行方式 从 Exporter 的运行方式上来讲，又可以分为： 1、独立运行 由于操作系统本身并不直接支持 Prometheus，同时用户也无法通过直接从操作系统层面上提供对 Prometheus 的支持。因此，用户只能通过独立运行一个程序的方式，通过操作系统提供的相关接口，将系统的运行状态数据转换为可供 Prometheus 读取的监控数据。 除了 Node Exporter 以外，比如 MySQL Exporter、Redis Exporter 等都是通过这种方式实现的。 这些 Exporter 程序扮演了一个中间代理人的角色（数据转换）。 2、集成到应用中（推荐） 为了能够更好的监控系统的内部运行状态，有些开源项目如 Kubernetes，ETCD 等直接在代码中使用了 Prometheus 的 Client Library，提供了对 Prometheus 的直接支持。这种方式打破的监控的界限，让应用程序可以直接将内部的运行状态暴露给 Prometheus，适合于一些需要更多自定义监控指标需求的项目。 2.4、Exporter 规范 所有的 Exporter 程序都需要按照 Prometheus 的规范，返回监控的样本数据。以 Node Exporter 为例，当访问 /metrics 地址时会返回以下内容： # 取前面 10 行 $ curl -s -k --header "Authorization: Bearer $TOKEN" https://192.168.0.113:6443/metrics|head -10 # HELP aggregator_openapi_v2_regeneration_count [ALPHA] Counter of OpenAPI v2 spec regeneration count broken down by causing APIService name and reason. # TYPE aggregator_openapi_v2_regeneration_count counter aggregator_openapi_v2_regeneration_count {apiservice="*",reason="startup"} 0 aggregator_openapi_v2_regeneration_count {apiservice="k8s_internal_local_delegation_chain_0000000002",reason="update"} 0 aggregator_openapi_v2_regeneration_count {apiservice="v1beta1.metrics.k8s.io",reason="add"} 0 aggregator_openapi_v2_regeneration_count {apiservice="v1beta1.metrics.k8s.io",reason="update"} 0 # HELP aggregator_openapi_v2_regeneration_duration [ALPHA] Gauge of OpenAPI v2 spec regeneration duration in seconds. # TYPE aggregator_openapi_v2_regeneration_duration gauge aggregator_openapi_v2_regeneration_duration {reason="add"} 0.929158077 aggregator_openapi_v2_regeneration_duration {reason="startup"} 0.509336209 Exporter 返回的样本数据，主要由三个部分组成：样本的一般注释信息（HELP），样本的类型注释信息（TYPE）和样本。 Prometheus 会对 Exporter 响应的内容逐行解析： 如果当前行以# HELP 开始，Prometheus 将会按照以下规则对内容进行解析，得到当前的指标名称以及相应的说明信息： # HELP <metrics_name> <doc_string> 如果当前行以# TYPE 开始，Prometheus 会按照以下规则对内容进行解析，得到当前的指标名称以及指标类型: # TYPE <metrics_name> <metrics_type> TYPE 注释行必须出现在指标的第一个样本之前。如果没有明确的指标类型需要返回为 untyped。 除了#开头的所有行都会被视为是监控样本数据。 每一行样本需要满足以下格式规范: metric_name [ “{” label_name “=” "label_value" { “,” label_name “=” "label_value" } [ “,” ] “}” ] value [ timestamp ]
部署后编译的位置： $DOMAIN_HOME/servers/$SERVER_NAME/tmp/_WL_user/$APP_NAME/$VERSION/ 在 WebLogic 中，部署 WAR 包时，通常会将其解压缩并编译到以下文件夹中： $DOMAIN_HOME 是你的 WebLogic 域的根目录。 $SERVER_NAME 是你正在运行的服务器的名称。 $APP_NAME 是你的应用程序的名称。 $VERSION 是应用程序的版本号。 在这个目录下，你可以找到解压后的 WAR 包内容以及编译后的类文件。 解压缩 WAR 包： unzip yourfile.war -d yourdirectory 重新打包成 WAR 包： cd yourdirectory jar cvf newfile.war *
1、初始化 Git 仓库： 在项目文件夹中打开终端，运行以下命令： git init 2、添加文件到暂存区： 将所有文件添加到 Git 的暂存区： git add . 3、提交更改： 提交暂存区的文件，并添加提交信息： git commit -m "初始化提交" 4、创建 GitHub 仓库： 登录到您的 GitHub 账户，创建一个新的仓库。记下仓库的 URL。 5、将远程仓库添加到本地仓库： 在终端中运行以下命令，将远程仓库链接到本地仓库（将 <repository-url> 替换为您的 GitHub 仓库 URL）： git remote add origin https://github.com/xxx/xxx.git # 将当前分支重命名为 main git branch -M main 6、推送到 GitHub： 将本地的提交推送到 GitHub： git push -u origin master 完成以上步骤后，您的项目文件夹将成功初始化并提交到 GitHub。 7、拉取分支代码 git pull origin main # 查看当前分支 git branch # 或者在当前分支更新 git pull
递归算法导出： #!/bin/bash ZK_SERVER="127.0.0.1:2181" ZK_CLI_PATH="/opt/module/zookeeper-3.5.7-bin/bin/zkCli.sh" OUTPUT_FILE="/opt/module/zookeeper-3.5.7-bin/bin/zk_data_export.txt" TMP_SCRIPT="/opt/module/zookeeper-3.5.7-bin/bin/zk_command.tmp" : > $OUTPUT_FILE function zkCommand() { # echo "$1" | $ZK_CLI_PATH -server $ZK_SERVER 2>&1 echo "addauth digest cache:svcctlg" > "$TMP_SCRIPT" echo "$1" >> "$TMP_SCRIPT" $ZK_CLI_PATH -server $ZK_SERVER < "$TMP_SCRIPT" 2>&1 } # 对 ZooKeeper 进行认证 # zkCommand "addauth digest cache:svcctlg" function exportZK() { local path=$1 { echo "节点: $path" local data=$(zkCommand "get $path" | tail -2 | head -1) echo "$data" echo "" } >> $OUTPUT_FILE local children_output=$(zkCommand "ls $path") local children=$(echo "$children_output" | grep -o '\[.*\]' | tail -2 | head -1 | tr -d '[],' | tr ',' '\n') echo $children for child in $children; do local child_path="$path/$child" if [ "$path" == "/" ]; then child_path="/$child" fi exportZK "$child_path" done } exportZK "/a1" echo "ZooKeeper 数据导出完成，文件保存于 $OUTPUT_FILE"
两种迁移场景，分别是：同集群数据迁移、跨集群数据迁移。 一、同集群迁移 同集群之间数据迁移，比如在已有的集群中新增了一个 Broker 节点，此时需要将原来集群中已有的 Topic 的数据迁移部分到新的集群中，缓解集群压力。 将新的节点添加到 Kafka 集群很简单，只需为它们分配一个唯一的 Broker ID，并在新服务器上启动 Kafka。但是，这些新服务器节点不会自动分配任何数据分区，因此除非将分区移动到新增的节点，否则在创建新 Topic 之前新节点不会执行任何操作。因此，通常在将新服务器节点添加到 Kafka 集群时，需要将一些现有数据迁移到这些新的节点。 迁移数据的过程是手动启动的，执行过程是完全自动化的。在 Kafka 后台服务中，Kafka 将添加新服务器作为其正在迁移的分区的 Follower，并允许新增节点完全复制该分区中的现有数据。当新服务器节点完全复制此分区的内容并加入同步副本（ISR）时，其中一个现有副本将删除其分区的数据。 Kafka 系统提供了一个分区重新分配工具 kafka-reassign-partitions.sh，该工具可用于在 Broker 之间迁移分区。理想情况下，将确保所有 Broker 的数据和分区均匀分配。分区重新分配工具无法自动分析 Kafka 群集中的数据分布并迁移分区以实现均匀的负载均衡。因此，管理员在操作的时候，必须弄清楚应该迁移哪些 Topic 或分区。 分区重新分配工具可以在 3 种互斥模式下运行： --generate：在此模式下，给定 Topic 列表和 Broker 列表，该工具会生成候选重新​​分配，以将指定 Topic 的所有分区迁移到新 Broker 中。此选项仅提供了一种方便的方法，可在给定 Topic 和目标 Broker 列表的情况下生成分区重新分配计划。 --execute：在此模式下，该工具将根据用户提供的重新分配计划启动分区的重新分配。 （使用 --reassignment-json-file 选项）。由管理员手动制定自定义重新分配计划，也可以使用 --generate 选项提供。 --verify：在此模式下，该工具将验证最后一次 --execute 期间列出的所有分区的重新分配状态。状态可以有成功、失败或正在进行等状态。 1.1、迁移过程实现 分区重新分配工具可用于将一些 Topic 从当前的 Broker 节点中迁移到新添加的 Broker 中。这在扩展现有集群时通常很有用，因为将整个 Topic 移动到新的 Broker 变得更容易，而不是一次移动一个分区。当执行此操作时，用户需要提供已有的 Broker 节点的 Topic 列表，以及到新节点的 Broker 列表（源 Broker 到新 Broker 的映射关系）。然后，该工具在新的 Broker 中均匀分配给指定 Topic 列表的所有分区。在迁移过程中，Topic 的复制因子保持不变。 现有如下实例，将 Topic 为 ke01，ke02 的所有分区从 Broker1 中移动到新增的 Broker2 和 Broker3 中。由于该工具接受 Topic 的输入列表作为 JSON 文件，因此需要明确迁移的 Topic 并创建 json 文件，如下所示： > cat topic-to-move.json {"topics": [{"topic": "ke01"}, {"topic": "ke02"}], "version":1 } 准备好 JSON 文件，然后使用分区重新分配工具生成候选分配，命令如下： > bin/kafka-reassign-partitions.sh --zookeeper dn1:2181 --topics-to-move-json-file topics-to-move.json --broker-list "1,2" --generate 执行命名之前，Topic（ke01、ke02）的分区如下图所示： 执行完成命令之后，控制台出现如下信息： 该工具生成一个候选分配，将所有分区从 Topic ke01，ke02 移动到 Broker1 和 Broker2。需求注意的是，此时分区移动尚未开始，它只是告诉你当前的分配和建议。保存当前分配，以防你想要回滚它。新的赋值应保存在 JSON 文件（例如 expand-cluster-reassignment.json）中，以使用 --execute 选项执行。JSON 文件如下： {"version":1,"partitions":[{"topic":"ke02","partition":0,"replicas":[2]},{"topic":"ke02","partition":1,"replicas":[1]},{"topic":"ke02","partition":2,"replicas":[2]},{"topic":"ke01","partition":0,"replicas":[2]},{"topic":"ke01","partition":1,"replicas":[1]},{"topic":"ke01","partition":2,"replicas":[2]}]} 执行命令如下所示： > ./kafka-reassign-partitions.sh --zookeeper dn1:2181 --reassignment-json-file expand-cluster-reassignment.json --execute 最后，--verify 选项可与该工具一起使用，以检查分区重新分配的状态。需要注意的是，相同的 expand-cluster-reassignment.json（与 --execute 选项一起使用）应与 --verify 选项一起使用，执行命令如下： > ./kafka-reassign-partitions.sh --zookeeper dn1:2181 --reassignment-json-file expand-cluster-reassignment.json --verify 执行结果如下图所示： 同时，可以通过 Kafka Eagle 工具来查看 Topic 的分区情况。 二、跨集群迁移 这里跨集群迁移，指的是在 Kafka 多个集群之间复制数据 “镜像” 的过程，以避免与单个集群中的节点之间发生的复制混淆。 Kafka 附带了一个用于在 Kafka 集群之间镜像数据的工具。该工具从源集群使用并生成到目标集群。这种镜像的一个常见用例是在另一个数据中心提供副本。 另外，你可以运行许多此类镜像进程以提高吞吐量和容错（如果一个进程终止，其他进程将占用额外负载）。将从源集群中的 Topic 读取数据，并将其写入目标集群中具有相同名称的主题。事实上，“镜像” 数据只不过是一个 Kafka 将消费者和生产者联系在了一起。 源集群和目标集群是完全独立的实体，它们可以具有不同数量的分区，并且偏移量将不相同。出于这个原因，镜像集群并不是真正意图作为容错机制（因为消费者的位置会有所不同）; 为此，建议使用正常的集群内复制。但是，镜像进程将保留并使用消息 Key 进行分区，因此可以按 Key 保留顺序。 下面是一个跨集群的单 Topic 实例，命令如下： ./kafka-mirror-maker.sh --consumer.config consumer.properties --producer.config producer.properties --whitelist ke03 需要注意的是，consumer.properties 文件配置源 Kafka 集群 Broker 地址，producer.properties 文件配置目标 Kafka 集群地址。如果需要迁移多个 Topic，可以使用 --whitelist 'A|B'，如果需要迁移所有的 Topic，可以使用 --whitelist '*'。 三、结果预览 执行跨集群迁移命令后，目标集群中使用 Kafka Eagle 中查看 Topic Size 大小看是否与源集群的 Topic Size 大小相等，或者使用 SQL 语句，验证是否有数据迁移过来，结果如下图所示： select * from "ke03" where "partition" in (0) 跨集群迁移数据的本质是，Kafka 启动了消费者读取源集群数据，并将消费后的数据写入到目标集群，在迁移的过程中，可以启动多个实例，提供迁出的吞吐量。
一、Security 在 Kafka 的官方文档中介绍了如下几种安全方式，以满足用户不同场景对安全的需求。 SASL/GSSAPI (Kerberos) - starting at version 0.9.0.0。主要是为 Kerberos 使用，如果当前已有 Kerberos 认证，只需要为集群中每个 Broker 和访问用户申请 Principle ，然后在 Kafka 配置文件中开启 Kerberos 的支持即可。官方文档详见 Authentication using SASL/Kerberos。 SASL/PLAIN - starting at version 0.10.0.0。一个简单的用户名和密码身份认证机制，通常与 TLS/SSL 一起用于加密，以实现身份验证。是一种比较容易使用的方式，但是也有一个很明显的缺点，这种方式会把用户账户文件配置到静态文件中，每次想要添加新的账户都需要重启 Kafka 去加载静态文件，才能生效，十分不方便。官方文档详见 Authentication using SASL/PLAIN。 SASL/SCRAM-SHA-256 和 SASL/SCRAM-SHA-512 - starting at version 0.10.2.0。通过将认证信息保存在 ZooKeeper 里面，从而动态的获取用户信息，相当于把 ZK 用作一个认证中心使用。这种认证可以在使用过程中，使用 Kafka 提供的命令动态地创建和删除用户，无需重启整个集群，十分方便。官方文档详见 Authentication using SASL/SCRAM 。 SASL/OAUTHBEARER - starting at version 2.0。Kafka 引入的新认证机制，主要是为了实现与 OAuth2 框架的集成，Kafka 不提倡单纯使用 OAUTHBEARER，因为它生成的不安全 Json Web Token，必须配以 SSL 加密才能在生产环境中使用。官方文档详见 Authentication using SASL/OAUTHBEARER 。 二、SASL+ACL 实现用户及权限认证 2.1、下载 wget https://repo.huaweicloud.com/apache/kafka/3.3.1/kafka_2.12-3.3.1.tgz -P /data cd /data tar -zxf kafka_2.12-3.3.1.tgz ln -s kafka_2.12-3.3.1 kafka cd /data/kafka 2.2、Kafka 服务配置 Kafka server 的 jaas 文件如下： /data/kafka/config/jaas/kafka_server_jaas.conf 这里创建五个用户，密码分别如下，后会会对其进行分别授权。 用户 密码 admin admin123 kwriter kwriter123 kreader kreader123 sharga sharga123 shargb shargb123 创建文件： mkdir /data/kafka/config/jaas/ vim /data/kafka/config/jaas/kafka_server_jaas.conf 配置如下： KafkaServer { org.apache.kafka.common.security.plain.PlainLoginModule required username="admin" password="admin123" user_admin="admin123" user_kwriter="kwriter123" user_kreader="kreader123" user_sharga="sharga123" user_shargb="shargb123"; }; 2.3、修改 Kafka 服务启动脚本 1、复制一份作为带安全的启动脚本： cp /data/kafka/bin/kafka-server-start.sh/data/kafka/bin/sasl_kafka-server-start.sh 2、修改大概 44 行为如下，引入上面的 jaas.conf 文件，修改为如下 export LOG_DIR=/var/log/kafka-logs export KAFKA_OPTS="-Djava.security.auth.login.config=/data/kafka/config/jaas/kafka_server_jaas.conf" exec $base_dir/kafka-run-class.sh $EXTRA_ARGS kafka.Kafka "$@" 2.4、配置 server.properties 拷贝一份 ： cp /data/kafka/config/server.properties/data/kafka/config/jaas/server.properties 配置如下： broker.id=0 listeners=SASL_PLAINTEXT://192.168.179.3:9092 advertised.listeners=SASL_PLAINTEXT://192.168.179.3:9092 security.inter.broker.protocol=SASL_PLAINTEXT sasl.mechanism.inter.broker.protocol=PLAIN sasl.enabled.mechanisms=PLAIN # ACL 入口类 authorizer.class.name=kafka.security.authorizer.AclAuthorizer # 设置 admin 用户为超级管理员 super.users=User:admin auto.create.topics.enable=true log.dirs=/data/kafka/data/kafka-logs zookeeper.connect=192.168.179.3:2181/kafka 创建需要的目录： mkdir -p /data/kafka/data/{kafka-logs,zookeeper} 2.5、启动 Zookeeper ZK 服务地址可以使用 Kafka 自带的，也可以使用已部署在的 ZK。如果已有 ZK 可以跳过这一步。这里以 Kafka 自带的为例。 配置 zookeeper.properties： dataDir=/data/kafka/data/zookeeper clientPort=2181 maxClientCnxns=0 admin.enableServer=false 启动 ZK： /data/kafka/bin/zookeeper-server-start.sh -daemon /data/kafka/config/zookeeper.properties 查看日志： tail -f /var/log/zookeeper/server.log 可以通过如下命令进一步查看 znode 信息： /data/kafka/bin/zookeeper-shell.sh 192.168.179.3:2181 2.6、启动 Kafka 集群 # 1 启动 Kafka /data/kafka/bin/sasl_kafka-server-start.sh -daemon /data/kafka/config/jaas/server.properties # 2 查看启动日志 tail -f /var/log/kafka-logs/server.log 关闭的命令如下： /data/kafka/bin/kafka-server-stop.sh /data/kafka/bin/zookeeper-server-stop.sh 如果需要调整 Kafka JVM 参数，可以如下进行修改，调整期堆大小，或者其它参数： vim /data/kafka/bin/sasl_kafka-server-start.sh +28 2.7、ACL 接下来对用户进行授权，大概按照如下进行设置。 用户 密码 权限 说明 admin admin123 All 超级管理员用户 kwriter kwriter123 producer 生产者类权限，Writer、Create、Describe kreader kreader123 consumer 消费者类权限，Read、Describe sharga sharga123 All 仅对 sharga-* 类 Topic 有所有权 shargb shargb123 All 仅对 shargb-* 类 Topic 有所有权 命令： # 1 授予 kwriter 用户为生产者权限 /data/kafka/bin/kafka-acls.sh --authorizer-properties zookeeper.connect=192.168.179.3:2181/kafka \ --add --allow-principal User:kwriter --producer --topic "*" # 2 授予 kreader 用户为消费者权限 /data/kafka/bin/kafka-acls.sh --authorizer-properties zookeeper.connect=192.168.179.3:2181/kafka \ --add --allow-principal User:kreader --consumer --topic "*" --group "*" # 3 对 sharga 用户进行授权，前缀模式 /data/kafka/bin/kafka-acls.sh --authorizer-properties zookeeper.connect=192.168.179.3:2181/kafka \ --add --allow-principal User:sharga --group "*" --topic "sharga" --resource-pattern-type prefixed # 4 对 shargb 用户进行授权，前缀模式 /data/kafka/bin/kafka-acls.sh --authorizer-properties zookeeper.connect=192.168.179.3:2181/kafka \ --add --allow-principal User:shargb --group "*" --topic "shargb" --resource-pattern-type prefixed # 5 查看 ACL 权限列表 /data/kafka/bin/kafka-acls.sh --authorizer-properties zookeeper.connect=192.168.179.3:2181/kafka --list 小的测试验证（在没有使用正确用户时，执行如下会失败）： # 1 创建 Topic（客户端没有使用认证是，创建 topic 会失败） /data/kafka/bin/kafka-topics.sh --create --bootstrap-server node01:9092 \ --partitions 1 --replication-factor 1 --topic test_sasl # 2 查看 topic（客户端没有使用认证是，获取 topic 会失败） /data/kafka/bin/kafka-topics.sh --list --bootstrap-server node01:9092 | grep test # 3 查看 topic 详情（客户端没有使用认证是，获取 topic 详情会失败） /data/kafka/bin/kafka-topics.sh --bootstrap-server 192.168.179.3:9092 \ --describe --topic test_sasl # 4 生产者（此时会因为权限问题，而报错，下面来解决这个问题） /data/kafka/bin/kafka-console-producer.sh --bootstrap-server 192.168.179.3:9092 \ --topic test_sasl # 5 消费者（此时会因为权限问题，而报错，下面来解决这个问题） /data/kafka/bin/kafka-console-consumer.sh --bootstrap-server node01:9092 \ --from-beginning --topic test_sasl 2.7.1、admin 新增 admin 配置属性文件： vim /data/kafka/config/jaas/admin.properties 内容如下： sasl.jaas.config=org.apache.kafka.common.security.plain.PlainLoginModule required username="admin" password="admin123"; security.protocol=SASL_PLAINTEXT sasl.mechanism=PLAIN 以 admin 身份执行如下命令： # 1 再次创建 topic，此时可以成功创建 /data/kafka/bin/kafka-topics.sh --create --bootstrap-server node01:9092 \ --command-config /data/kafka/config/jaas/admin.properties \ --partitions 1 --replication-factor 1 --topic test_sasl # 2 list /data/kafka/bin/kafka-topics.sh --list --bootstrap-server node01:9092 \ --command-config /data/kafka/config/jaas/admin.properties # 3 topic 详新信息 /data/kafka/bin/kafka-topics.sh --bootstrap-server node01:9092 \ --command-config /data/kafka/config/jaas/admin.properties \ --describe --topic test_sasl 如果需要删除 Topic，可使用如下命令 /data/kafka/bin/kafka-topics.sh --bootstrap-server node01:9092 \ --command-config /data/kafka/config/jaas/admin.properties \ --delete --topic test_sasl 其它（可以执行完下面生产者或消费者命令后再执行查看） # 1 查看当前的消费者组 /data/kafka/bin/kafka-consumer-groups.sh --bootstrap-server node01:9092 --command-config \ /data/kafka/config/jaas/admin.properties --list # 2 查看消费者消费进度 /data/kafka/bin/kafka-consumer-groups.sh --bootstrap-server node01:9092 --command-config \ /data/kafka/config/jaas/admin.properties --describe --group test-consumer-group # 3 查看 Kafka Topic 的 offset 信息 /data/kafka/bin/kafka-run-class.sh kafka.tools.GetOffsetShell --bootstrap-server node01:9092 \ --command-config /data/kafka/config/jaas/admin.properties --time -1 --topic test_sasl 2.7.2、生产者 新增生产者 kwrite 用户的配置属性文件： vim /data/kafka/config/jaas/kwriter.properties 内容如下： sasl.jaas.config=org.apache.kafka.common.security.plain.PlainLoginModule required username="kwriter" password="kwriter123"; security.protocol=SASL_PLAINTEXT sasl.mechanism=PLAIN 使用客户端命令行测试： /data/kafka/bin/kafka-console-producer.sh --bootstrap-server node01:9092 \ --producer.config/data/kafka/config/jaas/kwriter.properties --topic test_sasl 发送如下的数据： {"id":100,"name":"one","age":1} {"id":101,"name":"two","age":2} {"id":102,"name":"three","age":3} 2.7.3、消费者 新增生产者 kreader 用户的配置属性文件： vim /data/kafka/config/jaas/kreader.properties 内容如下： group.id=test-consumer-group sasl.jaas.config=org.apache.kafka.common.security.plain.PlainLoginModule required username="kreader" password="kreader123"; security.protocol=SASL_PLAINTEXT sasl.mechanism=PLAIN 消费者测试： /data/kafka/bin/kafka-console-consumer.sh --bootstrap-server node01:9092 \ --consumer.config/data/kafka/config/jaas/kreader.properties --from-beginning --topic test_sasl 2.7.4、sharga 用户 新增生产者 sharga 用户的配置属性文件： vim /data/kafka/config/jaas/sharga.properties 内容如下： group.id=test-sharga-group sasl.jaas.config=org.apache.kafka.common.security.plain.PlainLoginModule required username="sharga" password="sharga123"; security.protocol=SASL_PLAINTEXT sasl.mechanism=PLAIN 执行如下命令： # 1 创建自己的 topic ## sharga-t01 /data/kafka/bin/kafka-topics.sh --create --bootstrap-server node01:9092 --command-config \ /data/kafka/config/jaas/sharga.properties --partitions 1 --replication-factor 1 --topic sharga-t01 # 2 生产者 /data/kafka/bin/kafka-console-producer.sh --bootstrap-server node01:9092 --producer.config \ /data/kafka/config/jaas/sharga.properties --topic sharga-t01 向 sharga-t01 的 Topic 发送如下消息： {"id":100,"name":"one","age":1} {"id":101,"name":"two","age":2} {"id":102,"name":"three","age":3} #3 同样创建 sharga-t02 Topic /data/kafka/bin/kafka-topics.sh --create --bootstrap-server node01:9092 --command-config \ /data/kafka/config/jaas/sharga.properties --partitions 1 --replication-factor 1 --topic sharga-t02 # 4 生产者 /data/kafka/bin/kafka-console-producer.sh --bootstrap-server node01:9092 --producer.config \ /data/kafka/config/jaas/sharga.properties --topic sharga-t02 向 sharga-t02 的 Topic 发送如下消息： {"id":100,"sex":"man"} {"id":101,"sex":"man"} {"id":102,"sex":"woman"} # 5 查看当前的 topic 列表 /data/kafka/bin/kafka-topics.sh --list --bootstrap-server node01:9092 --command-config \ /data/kafka/config/jaas/sharga.properties sharga 用户仅能查看到自己的 Topic。 2.7.6、说明 这种方式是将用户和密码相关的信息保存在了服务器本地文件中了，经过前面的配置，基本涉及到安全认证的配置都放置到了 /data/kafka/config/jaas 目录下，因此如果需要在真实服务上使用时，这个目录需要经读写权限详细的分配给系统的每个用户，对于不允许使用此用户配置信息的其他用户，这个文件的读写权限可以不授予给这个用户。 2.8、生产者客户端代码设置 Properties props = new Properties (); //... props.put ("security.protocol", "SASL_PLAINTEXT"); props.put ("sasl.mechanism", "PLAIN"); props.put ("sasl.jaas.config", "org.apache.kafka.common.security.plain.PlainLoginModule required username='kwriter' password='kwriter123';"); 2.9、消费者客户端代码设置 Properties props = new Properties (); //... props.put ("security.protocol", "SASL_PLAINTEXT"); props.put ("sasl.mechanism", "PLAIN"); props.put ("sasl.jaas.config", "org.apache.kafka.common.security.plain.PlainLoginModule required username='kreader' password='kreader123';"); 2.10、Spring-kafka 需配置如下 spring: #…… kafka: bootstrap-servers: node01:9092 #指定 kafka server 的地址，集群配多个，中间，逗号隔开 producer: key-serializer: org.apache.kafka.common.serialization.StringSerializer value-serializer: org.apache.kafka.common.serialization.StringSerializer properties: # sasl 安全认证 security.protocol: SASL_PLAINTEXT sasl.mechanism: PLAIN sasl.jaas.config: org.apache.kafka.common.security.plain.PlainLoginModule required username='kwriter' password='kwriter123'; consumer: group-id: app_consumer_group #群组 ID enable-auto-commit: true auto-commit-interval: 1000s key-deserializer: org.apache.kafka.common.serialization.StringDeserializer value-deserializer: org.apache.kafka.common.serialization.StringDeserializer properties: security.protocol: SASL_PLAINTEXT sasl.mechanism: PLAIN sasl.jaas.config: org.apache.kafka.common.security.plain.PlainLoginModule required username='kreader' password='kreader123'; 2.11、Flink SQL create table kafka_sharga_t01 ( id int, name string, age int ) with ( 'format' = 'json', 'properties.bootstrap.servers' = '192.168.179.3:9092', 'connector' = 'kafka', 'topic' = 'sharga-t01', 'properties.group.id' = 'group1', 'scan.startup.mode' = 'earliest-offset', 'properties.sasl.jaas.config' = 'org.apache.kafka.common.security.plain.PlainLoginModule required username="sharga" password="sharga123";', 'properties.security.protocol' = 'SASL_PLAINTEXT', 'properties.sasl.mechanism' = 'PLAIN' );
一、Keepalived 介绍 Keepalived 是 Linux 下一个轻量级别的高可用解决方案，通过虚拟路由冗余协议来实现服务或者网络的高可用。 起初是为 LVS 设计的，专门用来监控集群系统中各个服务节点的状态。 如果某个服务器节点出现故障，Keepalived 将检测到后自动将节点从集群系统中剔除。 而在故障节点恢复正常后，Keepalived 又可以自动将此节点重新加入集群中。 这些工作自动完成，不需要人工干预，需要人工完成的只是修复出现故障的节点。 1.1、特点： 1、部署简单，只需要配置一个配置文件即可。 2、加入了虚拟路由冗余协议，可以保证业务或网络不间断稳定运行。 1.2、核心功能： 1.2.1、健康检查 采用 tcp 三次握手，icmp 请求，http 请求，udp、echo 请求等方式对负载均衡器后面的实际的服务器 (通常是承载真实业务的服务器) 进行保活。 1.2.2、故障切换 主要应用在配置了主备的服务器上，使用虚拟路由冗余协议维持主备之间的心跳。 当主服务器出现问题时，由备服务器承载对应的业务，从而在最大限度上减少损失，并提供服务的稳定性。 1.3、VRRP 协议 在现实的网络环境中，主机之间的通信都是通过配置静态路由（默认网关）来完成的。 而主机之间的路由器一旦出现故障，就会通信失败。 因此在这种通信模式下，路由器就会有单点瓶颈问题，为了解决这个问题，引入了 VRRP 协议（虚拟路由冗余协议）。 通过 VRRP 可以在网络发生故障时透明的进行设备切换而不影响主机之间的数据通信。 VRRP 可以将两台或多台物理路由器设备虚拟成一个虚拟路由器，这个虚拟路由器通过虚拟 IP（一个或多个）对外提供服务，而在虚拟路由器内部是多个物理路由器协同工作，VRRP 角色如下： 1.3.1、角色 虚拟路由器：VRRP 组中所有的路由器，拥有虚拟的 IP+MAC (00-00-5e-00-01-VRID) 地址。 主路由器（master）：虚拟路由器内部通常只有一台物理路由器对外提供服务，主路由器是由选举算法产生，对外提供各种网络功能。 备份路由器（backup）：VRRP 组中除主路由器之外的所有路由器，不对外提供任何服务，只接受主路由的通告，当主路由器挂掉之后，重新进行选举算法接替 master 路由器。 master 路由器由选举算法产生，它拥有对外服务的 VIP，提供各种网络服务，如 ARP 请求、数据转发、ICMP 等等，而 backup 路由器不拥有 VIP，也不对外提供网络服务。 当 master 发生故障时，backup 将重新进行选举，产生一个新的 master 继续对外提供服务。 1.3.2、VRRP 工作模式 VRRP 有三种工作状态，分别是： Initialize 状态 Master 状态 Backup 状态 选举机制: 抢占模式：一旦有优先级高的路由器加入，立即成为 Master。 非抢占模式：只要 Master 不挂掉，优先级高的路由器只能等待。 二、Keepalived 原理 keepalived 工作在 TCP/IP 参考模型的第三、四和第五层，也就是网络层、传输层和应用层。 网络层：通过 ICMP 协议向集群每个节点发送一个 ICMP 数据包（类似于 ping 功能），如果某个节点没有返回响应数据包，那么认定此节点发生了故障，Keepalived 将报告此节点失效，并从集群中剔除故障节点。 传输层：通过 TCP 协议的端口连接和扫描技术来判断集群节点是否正常，keepalived 一旦在传输层探测到这些端口没有响应数据返回，就认为这些端口所对应的节点发生故障，从集群中剔除故障节点。 应用层：用户可以通过编写程序脚本来运行 keepalived，keepalived 根据脚本来检测各种程序或者服务是否正常，如果检测到有故障，则把对应的服务从服务器中删除。 2.1、组件架构 我们将整个体系结构分层用户层和内核层： Scheduler I/O Multiplexer I/O 复用分发调用器，负责安排 Keepalived 所有的内部的任务请求 Memory Management 内存管理机制，提供了访问内存的一下通用方法 Keepalived Control Plane 控制面板，实现对配置文件的编译和解析，Keepalived 的配置文件解析比较特殊，它并不是一次解析所有模块的配置，而是只有在用到某模块时才解析相应的配置 Core components Keepalived 的核心组件，包含了一系列功能模块，主要有 watch dog、Checkers、VRRP Stack、IPVS wrapper、Netlink Reflector。 watch dog 一个极为简单又非常有效的检测工具，针对被监视目标设置一个计数器和阈值，watch dog 会自己增加此计数值，然后等待被监视目标周期性的重置该数值，一旦被监控目标发生错误，就无法重置该数值，watch dog 就会检测到。Keepalived 是通过它来监控 Checkers 和 VRRP 进程。 Checkers 实现对服务器运行状态检测和故障隔离。 VRRP Stack 实现 HA 集群中失败切换功能，通过 VRRP 功能再结合 LVS 负载均衡软件即可部署一个高性能的负载均衡集群。 IPVS wrapper 实现 IPVS 功能，该模块可以将设置好的 IPVS 规则发送到内核空间并提交给 IPVS 模块，最终实现负载均衡功能。 Netlink Reflector 实现 VIP 的设置和切换。 三、安装 & 配置 yum install -y keepalived 配置文件都是以块（block）的形式组成，每一个块的内容都包含在 { } 中。 手动安装： tar -zxvf keepalived-2.2.8.tar.gz cd keepalived-2.2.8 ./configure --prefix=/usr/local/keepalived make install 复制 Keepalived 到系统服务目录： cp /root/tool/keepalived-2.2.8/keepalived/etc/init.d/keepalived /etc/rc.d/init.d 设置开机自启动： chkconfig --add keepalived chkconfig keepalived on 3.1、全局配置 ! Configuration File for keepalived global_defs { notification_email { acassen@firewall.loc failover@firewall.loc sysadmin@firewall.loc } notification_email_from Alexandre.Cassen@firewall.loc smtp_server 192.168.200.1 smtp_connect_timeout 30 router_id LVS_DEVEL vrrp_skip_check_adv_addr vrrp_strict vrrp_garp_interval 0 vrrp_gna_interval 0 } 其中： global_defs 全局配置标识。 notification_email 用于设置报警的邮件地址，可以设置多个，每行一个。如果要开启邮件报警，需要开启本机的 sendmail 服务。 notification_email_from 邮件的发送地址。 smtp_server 设置邮件的 smtp server 地址。 smtp_connect_timeout 设置连接 smtp server 的超时时间。 router_id 运行 keepalived 的一个标识，唯一。 vrrp_skip_check_adv_addr 对所有通告报文都检查，会比较消耗性能，启用此配置后，如果收到的通告报文和上一个报文是同一个路由器，则跳过检查，默认值为全检查。 vrrp_strict 严格遵守 VRRP 协议，启用此项后以下状况将无法启动服务：1. 无 VIP 地址 2. 配置了单播邻居 3. 在 VRRP 版本 2 中有 IPv6 地址，开启动此项并且没有配置 vrrp_iptables 时会自动开启 iptables 防火墙规则，默认导致 VIP 无法访问，建议不加此项配置。 vrrp_iptables 此项和 vrrp_strict 同时开启时，则不会添加防火墙规则，如果无配置 vrrp_strict 项，则无需启用此项配置。 vrrp_garp_interval 0 gratuitous ARP messages 报文发送延迟，0 表示不延迟。 vrrp_gna_interval 0 unsolicited NA messages （不请自来）消息发送延迟。 3.2、VRRP 实例配置 vrrp_script nginx_check { script"/tools/nginx_check.sh" interval 1 } vrrp_instance VI_1 { state MASTER interface ens33 virtual_router_id 52 mcast_src_ip 192.168.10.102 # 填写本机ip priority 100 advert_int 1 authentication { auth_type PASS auth_pass test } virtual_ipaddress { 192.168.149.100 } track_script { nginx_check } notify_master /tools/master.sh notify_backup /tools/backup.sh notify_fault /tools/fault.sh notify_stop /tools/stop.sh } 其中： vrrp_instance VI_1 VRRP 实例开始的标识 VI_1 为实例名称。 state 指定 keepalived 的角色，MASTER 表示此主机是主服务器，BACKUP 表示此主机是备服务器。 interface 指定检测网络的网卡接口。 virtual_router_id 虚拟路由标识，数字形式，同一个 VRRP 实例使用唯一的标识，即在同一个 vrrp_instance 下，master 和 backup 必须一致。 mcast_src_ip 填写本机ip priority 节点优先级，数字越大表示节点的优先级越高，在一个 VRRP 实例下，MASTER 的优先级必须要比 BACKUP 高，不然就会切换角色。 advert_int 用于设定 MASTER 与 BACKUP 之间同步检查的时间间隔，单位为秒。 auth_type PASS 预共享密钥认证，同一个虚拟路由器的 keepalived 节点必须一样。 auth_pass 设置密钥。 virtual_ipaddress 设置虚拟 IP 地址，可以设置多种形式：10.0.0.100 不指定网卡，默认为 eth0, 注意：不指定 /prefix, 默认为 / 32；10.0.0.101/24 dev eth1 指定 VIP 的网卡；10.0.0.102/24 dev eth2 label eth2:1 #指定 VIP 的网卡 label。 nopreempt 设置为非抢占模式，同一实例下主备设置必须一样。 preemtp_delay 设置抢占模式的延时时间，单位为秒。 3.3、脚本相关配置 vrrp_script 自定义资源监控脚本，vrrp 实例根据脚本返回值进行下一步操作，脚本可被多个实例调用。 track_script 调用 vrrp_script 定义的脚本去监控资源，定义在实例之内，调用事先定义的 vrrp_script。实现主备切换，保证服务高可用。 vrrp_script 仅仅通过监控脚本返回的状态码来识别集群服务是否正常，如果返回状态码是 0，那么就认为服务正常，反之亦然。 notify_master 当前节点成为主节点时触发的脚本。 notify_backup 当前节点转为备节点时触发的脚本。 notify_fault 当前节点转为 “失败” 状态时触发的脚本。 notify_stop 当停止 VRRP 时触发的脚本。 四、Nginx+Keepalived 案例 4.1、Nginx 配置 使用 Nginx + Keepalived 实现一个 web 服务高可用方案。 架构图如下： 首先安装 Nginx：参照 Nginx 安装。 安装完 Nginx 之后接着配置 web 站点。 nginx1： [root@nginx1 ~]# vim /etc/nginx/conf.d/web.conf server { listen 8080; root /usr/share/nginx/html; index test.html; } [root@nginx1 ~]# echo " This is web1 " > /usr/share/nginx/html/test.html nginx2： #web2 [root@nginx2 ~]# vim /etc/nginx/conf.d/web.conf server { listen 8080; root /usr/share/nginx/html; index test.html; } [root@nginx2 ~]# echo " This is web2 " > /usr/share/nginx/html/test.html 接着启动 Nginx： ./nginx -t 和 ./nginx 4.2、Keepalived 配置 配置 Keepalived（不同角色有些选项不一样）。 web1:master： vim /etc/keepalived/keepalived.conf ! Configuration File for keepalived global_defs { notification_email { acassen@firewall.loc failover@firewall.loc sysadmin@firewall.loc } notification_email_from Alexandre.Cassen@firewall.loc smtp_server 192.168.200.1 smtp_connect_timeout 30 router_id LVS_DEVEL vrrp_skip_check_adv_addr vrrp_garp_interval 0 vrrp_gna_interval 0 } vrrp_script nginx_check { script "/tools/nginx_check.sh" interval 1 } vrrp_instance VI_1 { state MASTER interface ens33 virtual_router_id 52 priority 100 mcast_src_ip 192.168.10.102 # 填写本机ip advert_int 1 authentication { auth_type PASS auth_pass test } virtual_ipaddress { 192.168.149.100 } track_script { nginx_check } notify_master /tools/master.sh notify_backup /tools/backup.sh notify_fault /tools/fault.sh notify_stop /tools/stop.sh } web2:slave： vim /etc/keepalived/keepalived.conf ! Configuration File for keepalived global_defs { notification_email { acassen@firewall.loc failover@firewall.loc sysadmin@firewall.loc } notification_email_from Alexandre.Cassen@firewall.loc smtp_server 192.168.200.1 smtp_connect_timeout 30 router_id LVS_DEVEL vrrp_skip_check_adv_addr vrrp_garp_interval 0 vrrp_gna_interval 0 } vrrp_script nginx_check { script "/tools/nginx_check.sh" interval 1 } vrrp_instance VI_1 { state BACKUP interface ens33 virtual_router_id 52 priority 99 mcast_src_ip 192.168.10.103 # 填写本机ip advert_int 1 authentication { auth_type PASS auth_pass test } virtual_ipaddress { 192.168.149.100 } track_script { nginx_check } notify_master /tools/master.sh notify_backup /tools/backup.sh notify_fault /tools/fault.sh notify_stop /tools/stop.sh } 将所有脚本放在同一个目录下，方便集中管理。 mkdir /tools 首先是编写 Keepalived 日志脚本 (将每次工作过程输出到日志上)。 vim master.sh #!/bin/bash ip=`hostname -I | awk '{print $1}'` dt=$(date +'%Y%m%d %H:%M:%S') echo "$0--${ip}--${dt}--master" >> /tmp/kp.log vim backup.sh #!/bin/bash ip=$(hostname -I | awk '{print $1}') dt=$(date +'%Y%m%d %H:%M:%S') echo "$0--${ip}--${dt}--backup" >> /tmp/kp.log vim fault.sh #!/bin/bash ip=$(ip addr|grep inet| grep 192.168 |awk '{print $2}') dt=$(date +'%Y%m%d %H:%M:%S') echo "$0--${ip}--${dt}--fault" >> /tmp/kp.log vim stop.sh #!/bin/bash ip=$(ip addr|grep inet| grep 192.168| awk '{print $2}') dt=$(date +'%Y%m%d %H:%M:%S') echo "$0--${ip}--${dt}--stop" >> /tmp/kp.log 编写健康检查脚本 nginx_check.sh。 该脚本对 Nginx 进程进行检测，如果发现 Nginx 进程没了则返回失败（1）。 cat nginx_check.sh #!/bin/bash result=`pidof nginx` if [ ! -z "${result}" ]; then exit 0 else exit 1 fi 添加执行权限，并启动 Keepalived。 cd /tools/ && chmod +x *.sh systemctl restart keepalived.service 五、Keepalived 常用命令 1、启动 keepalived sudo systemctl start keepalived 2、停止 keepalived sudo systemctl stop keepalived 3、重启 keepalived sudo systemctl restart keepalived 4、查看 keepalived 状态 sudo systemctl status keepalived 5、查看 keepalived 运行日志 sudo journalctl -u keepalived 6、设置 keepalived 开机自启 sudo systemctl enable keepalived 7、取消 keepalived 开机自启 sudo systemctl disable keepalived 8、检查 keepalived 配置文件语法（部分发行版支持） sudo keepalived -t -f /etc/keepalived/keepalived.conf 9、查看 keepalived 进程 ps -ef | grep keepalived 10、重新加载配置（有些系统支持 reload） sudo systemctl reload keepalived
一、Prometheus 入门 Prometheus 受启发于 Google 的 Brogmon 监控系统（相似的 Kubernetes 是从 Google 的 Brog 系统演变而来），从 2012 年开始由前 Google 工程师在 Soundcloud 以开源软件的形式进行研发，并且于 2015 年早期对外发布早期版本。 2016 年 5 月继 Kubernetes 之后成为第二个正式加入 CNCF 基金会的项目，同年 6 月正式发布 1.0 版本。2017 年底发布了基于全新存储层的 2.0 版本，能更好地与容器平台、云平台配合。 Prometheus 作为新一代的云原生监控系统，目前已经有超过 650 + 位贡献者参与到 Prometheus 的研发工作上，并且超过 120 + 项的第三方集成。 1.1、Prometheus 的特点 Prometheus 是一个开源的完整监控解决方案，其对传统监控系统的测试和告警模型进行了彻底的颠覆，形成了基于中央化的规则计算、统一分析和告警的新模型。 相比于传统监控系统，Prometheus 具有以下优点： 1.1.1、易于管理 Prometheus 核心部分只有一个单独的二进制文件，不存在任何的第三方依赖 (数据库，缓存等等)。唯一需要的就是本地磁盘，因此不会有潜在级联故障的风险。 Prometheus 基于 Pull 模型的架构方式，可以在任何地方（本地电脑，开发环境，测试环境）搭建我们的监控系统。 对于一些复杂的情况，还可以使用 Prometheus 服务发现 (Service Discovery) 的能力动态管理监控目标 1.1.2、监控服务的内部运行状态 Pometheus 鼓励用户监控服务的内部状态，基于 Prometheus 丰富的 Client 库，用户可以轻松的在应用程序中添加对 Prometheus 的支持，从而让用户可以获取服务和应用内部真正的运行状态。 1.1.3、强大的数据模型 所有采集的监控数据均以指标 (metric) 的形式保存在内置的时间序列数据库当中 (TSDB)。所有的样本除了基本的指标名称以外，还包含一组用于描述该样本特征的标签。 http_request_status {code='200',content_path='/api/path',environment='produment'} => [value1@timestamp1,value2@timestamp2...] http_request_status {code='200',content_path='/api/path2',environment='produment'} => [value1@timestamp1,value2@timestamp2...] 每一条时间序列由指标名称 (Metrics Name) 以及一组标签 (Labels) 唯一标识。每条时间序列按照时间的先后顺序存储一系列的样本值。 http_request_status：指标名称 (Metrics Name) {code='200',content_path='/api/path',environment='produment'}：表示维度的标签，基于这些 Labels 我们可以方便地对监控数据进行聚合，过滤，裁剪。 [value1@timestamp1,value2@timestamp2...]：按照时间的先后顺序存储的样本值。 1.1.4、强大的查询语言 PromQL Prometheus 内置了一个强大的数据查询语言 PromQL。 通过 PromQL 可以实现对监控数据的查询、聚合。同时 PromQL 也被应用于数据可视化 (如 Grafana) 以及告警当中。 通过 PromQL 可以轻松回答类似于以下问题： 在过去一段时间中 95% 应用延迟时间的分布范围？ 预测在 4 小时后，磁盘空间占用大致会是什么情况？ CPU 占用率前 5 位的服务有哪些？(过滤) 1.1.5、高效 对于监控系统而言，大量的监控任务必然导致有大量的数据产生。而 Prometheus 可以高效地处理这些数据，对于单一 Prometheus Server 实例而言它可以处理： 数以百万的监控指标 每秒处理数十万的数据点 1.1.6、可扩展 可以在每个数据中心、每个团队运行独立的 Prometheus Sevrer。Prometheus 对于联邦集群的支持，可以让多个 Prometheus 实例产生一个逻辑集群，当单实例 PrometheusServer 处理的任务量过大时，通过使用功能分区 (sharding)+ 联邦集群 (federation) 可以对其进行扩展。 1.1.7、易于集成 使用 Prometheus 可以快速搭建监控服务，并且可以非常方便地在应用程序中进行集成。目前支持：Java，JMX，Python，Go，Ruby，.Net，Node.js 等等语言的客户端 SDK，基于这些 SDK 可以快速让应用程序纳入到 Prometheus 的监控当中，或者开发自己的监控数据收集程序。 同时这些客户端收集的监控数据，不仅仅支持 Prometheus，还能支持 Graphite 这些其他的监控工具。 同时 Prometheus 还支持与其他的监控系统进行集成：Graphite，Statsd，Collected，Scollector， muini， Nagios 等。 Prometheus 社区还提供了大量第三方实现的监控数据采集支持：JMX，CloudWatch，EC2，MySQL，PostgresSQL，Haskell，Bash，SNMP，Consul，Haproxy，Mesos，Bind，CouchDB，Django，Memcached，RabbitMQ，Redis，RethinkDB，Rsyslog 等等。 1.1.8、可视化 Prometheus Server 中自带的 Prometheus UI，可以方便地直接对数据进行查询，并且支持直接以图形化的形式展示数据。同时 Prometheus 还提供了一个独立的基于 Ruby On Rails 的 Dashboard 解决方案 Promdash。 最新的 Grafana 可视化工具也已经提供了完整的 Prometheus 支持，基于 Grafana 可以创建更加精美的监控图标。 基于 Prometheus 提供的 API 还可以实现自己的监控可视化 UI。 1.1.9、开放性 通常来说当我们需要监控一个应用程序时，一般需要该应用程序提供对相应监控系统协议的支持，因此应用程序会与所选择的监控系统进行绑定。为了减少这种绑定所带来的限制，对于决策者而言要么你就直接在应用中集成该监控系统的支持，要么就在外部创建单独的服务来适配不同的监控系统。 而对于 Prometheus 来说，使用 Prometheus 的 client library 的输出格式不止支持 Prometheus 的格式化数据，也可以输出支持其它监控系统的格式化数据，比如 Graphite。因此你甚至可以在不使用 Prometheus 的情况下，采用 Prometheus 的 client library 来让你的应用程序支持监控数据采集。 1.2、Prometheus 的架构 1.2.1、Prometheus 生态圈组件 Prometheus Server：主服务器，负责收集和存储时间序列数据。 client libraies：应用程序代码插桩，将监控指标嵌入到被监控应用程序中。 Pushgateway：推送网关，为支持 short-lived 作业提供一个推送网关。 exporter：专门为一些应用开发的数据摄取组件 —exporter，例如：HAProxy、StatsD、Graphite 等等。 Alertmanager：专门用于处理 alert 的组件。 1.2.2、架构理解 Prometheus 既然设计为一个维度存储模型，可以把它理解为一个 OLAP 系统。 1、存储计算层 Prometheus Server，里面包含了存储引擎和计算引擎。 Retrieval 组件为取数组件，它会主动从 Pushgateway 或者 Exporter 拉取指标数据。 Service discovery，可以动态发现要监控的目标。 TSDB，数据核心存储与查询。 HTTP server，对外提供 HTTP 服务。 2、采集层 采集层分为两类，一类是生命周期较短的作业，还有一类是生命周期较长的作业。 短作业：直接通过 API，在退出时间指标推送给 Pushgateway。 长作业：Retrieval 组件直接从 Job 或者 Exporter 拉取数据。 3、应用层 应用层主要分为两种，一种是 AlertManager，另一种是数据可视化。 AlertManager 对接 Pagerduty，是一套付费的监控报警系统。可实现短信报警、5 分钟无人 ack 打电话通知、仍然无人 ack，通知值班人员 Manager... Emial，发送邮件 数据可视化 Prometheus build-in WebUI Grafana 其他基于 API 开发的客户端 二、Prometheus 的安装 官网：https://prometheus.io/ 下载地址：https://prometheus.io/download/ 2.1、安装 Prometheus Server Prometheus 基于 Golang 编写，编译后的软件包，不依赖于任何的第三方依赖。只需要下载对应平台的二进制包，解压并且添加基本的配置即可正常启动 Prometheus Server。 2.1.1、上传安装包 上传 prometheus-2.29.1.linux-amd64.tar.gz 到虚拟机的 /opt/software 目录 2.1.2、解压安装包 解压到 /opt/module 目录下 tar -zxvf prometheus-2.29.1.linux-amd64.tar.gz -C /opt/module 修改目录名 cd /opt/module mv prometheus-2.29.1.linux-amd64 prometheus-2.29.1 启动 ./prometheus --config.file=prometheus.yml & 访问：http://{IP}:9090/ 查看监控数据：http://{IP}:9090/metrics 2.1.3、修改配置文件 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"] 在 scrape_configs 配置项下添加配置： scrape_configs: - job_name: 'prometheus' static_configs: - targets: ['hadoop102:9090'] # 添加 PushGateway 监控配置 - job_name: 'pushgateway' static_configs: - targets: ['hadoop102:9091'] labels: instance: pushgateway # 添加 Node Exporter 监控配置 - job_name: 'node exporter' static_configs: - targets: ['hadoop102:9100', 'hadoop103:9100'] 配置说明： 1、global 配置块：控制 Prometheus 服务器的全局配置。 scrape_interval 配置拉取数据的时间间隔，默认为 1 分钟。 evaluation_interval 规则验证（生成 alert）的时间间隔，默认为 1 分钟。 2、rule_files 配置块：规则配置文件。 3、scrape_configs 配置块：配置采集目标相关， prometheus 监视的目标。 Prometheus 自身的运行信息可以通过 HTTP 访问，所以 Prometheus 可以监控自己的运行数据。 job_name 监控作业的名称 static_configs 表示静态目标配置，就是固定从某个 target 拉取数据 targets 指定监控的目标，其实就是从哪儿拉取数据。Prometheus 会从 http://hadoop202:9090/metrics 上拉取数据。 Prometheus 是可以在运行时自动加载配置的。启动时需要添加：--web.enable-lifecycle 2.2、安装 Pushgateway Prometheus 在正常情况下是采用拉模式从产生 metric 的作业或者 exporter（比如专门监控主机的 NodeExporter）拉取监控数据。但是我们要监控的是 Flink on YARN 作业，想要让 Prometheus 自动发现作业的提交、结束以及自动拉取数据显然是比较困难的。PushGateway 就是一个中转组件，通过配置 Flink on YARN 作业将 metric 推到 PushGateway，Prometheus 再从 PushGateway 拉取就可以了。 2.2.1、上传安装包 上传 pushgateway-1.4.1.linux-amd64.tar.gz 到虚拟机的 /opt/software 目录。 2.2.2 解压安装包 解压到 /opt/module 目录下 [atguigu@hadoop202 software]$ tar -zxvf pushgateway-1.4.1.linux-amd64.tar.gz -C /opt/module 修改目录名 [atguigu@hadoop202 ~] cd /opt/module [atguigu@hadoop202 module] mv pushgateway-1.4.1.linux-amd64 pushgateway-1.4.1 2.3、安装 Alertmanager（选择性安装） 2.3.1、上传安装包 上传 alertmanager-0.23.0.linux-amd64.tar.gz 到虚拟机的 /opt/software 目录。 2.3.2、解压安装包 解压到 /opt/module 目录下 [atguigu@hadoop202 software]$ tar -zxvf alertmanager-0.23.0.linux-amd64.tar.gz -C /opt/module 修改目录名 [atguigu@hadoop202 ~] cd /opt/module [atguigu@hadoop202 module] mv alertmanager-0.23.0.linux-amd64 alertmanager-0.23.0 2.4、安装 Node Exporter（选择性安装） 在 Prometheus 的架构设计中，Prometheus Server 主要负责数据的收集，存储并且对外提供数据查询支持，而实际的监控样本数据的收集则是由 Exporter 完成。因此为了能够监控到某些东西，如主机的 CPU 使用率，我们需要使用到 Exporter。Prometheus 周期性的从 Exporter 暴露的 HTTP 服务地址（通常是 /metrics）拉取监控样本数据。 Exporter 可以是一个相对开放的概念，其可以是一个独立运行的程序独立于监控目标以外，也可以是直接内置在监控目标中。只要能够向 Prometheus 提供标准格式的监控样本数据即可。 为了能够采集到主机的运行指标如 CPU, 内存，磁盘等信息。我们可以使用 NodeExporter。Node Exporter 同样采用 Golang 编写，并且不存在任何的第三方依赖，只需要下载，解压即可运行。可以从 https://prometheus.io/download/ 获取最新的 nodeexporter 版本的二进制包。 2.4.1、上传安装包 上传 node_exporter-1.2.2.linux-amd64.tar.gz 到虚拟机的 /opt/software 目录。 2.4.2、解压安装包 解压到 /opt/module 目录下 [atguigu@hadoop202 software]$ tar -zxvf node_exporter-1.2.2.linux-amd64.tar.gz -C /opt/module 修改目录名 [atguigu@hadoop202 ~] cd /opt/module [atguigu@hadoop202 module] mv node_exporter-1.2.2.linux-amd64 node_exporter-1.2.2 启动并通过页面查看是否成功 执行 ./node_exporter & 浏览器输入：http://hadoop102:9100/metrics，可以看到当前 node exporter 获取到的当前主机的所有监控数据。 2.4.3、节点分发 将解压后的目录分发到要监控的节点 [atguigu@hadoop202 module] xsync node_exporter-1.2.2 修改 Prometheus 配置文件 prometheus.yml，在 2.1.3 的时候已经添加过配置 - targets: ['hadoop102:9100', 'hadoop103:9100', 'hadoop104:9100'] 三、PromQL 介绍 Prometheus 通过指标名称（metrics name）以及对应的一组标签（labelset）唯一定义一条时间序列。指标名称反映了监控样本的基本标识，而 label 则在这个基本特征上为采集到的数据提供了多种特征维度。用户可以基于这些特征维度过滤，聚合，统计从而产生新的计算后的一条时间序列。PromQL 是 Prometheus 内置的数据查询语言，其提供对时间序列数据丰富的查询，聚合以及逻辑运算能力的支持。并且被广泛应用在 Prometheus 的日常应用当中，包括对数据查询、可视化、告警处理当中。 3.1、基本用法 3.1.1、查询时间序列 当 Prometheus 通过 Exporter 采集到相应的监控指标样本数据后，我们就可以通过 PromQL 对监控样本数据进行查询。 当我们直接使用监控指标名称查询时，可以查询该指标下的所有时间序列。如： prometheus_http_requests_total 等同于： prometheus_http_requests_total {} 该表达式会返回指标名称为 prometheus_http_requests_total 的所有时间序列： prometheus_http_requests_total {code="200",handler="alerts",instance="localhost:9090",job="prometheus",method="get"}= (20889@1518096812.326) prometheus_http_requests_total {code="200",handler="graph",instance="localhost:9090",job="prometheus",method="get"}= (21287@1518096812.326) PromQL 还支持用户根据时间序列的标签匹配模式来对时间序列进行过滤，目前主要支持两种匹配模式：完全匹配和正则匹配。 PromQL 支持使用 = 和！= 两种完全匹配模式: 通过使用 label=value 可以选择那些标签满足表达式定义的时间序列； 反之使用 label!=value 则可以根据标签匹配排除时间序列； 只需要查询所有 prometheus_http_requests_total 时间序列中满足标签 instance 为 localhost:9090 的时间 序列，则可以使用如下表达式： prometheus_http_requests_total {instance="localhost:9090"} 反之使用 instance!="localhost:9090" 则可以排除这些时间序列： prometheus_http_requests_total {instance!="localhost:9090"} PromQL 还可以支持使用正则表达式作为匹配条件，多个表达式之间使用 | 进行分离： 使用 label=~regx 表示选择那些标签符合正则表达式定义的时间序列； 反之使用 label!~regx 进行排除； 例如，如果想查询多个环节下的时间序列序列可以使用如下表达式： prometheus_http_requests_total {environment=~"staging|testing|development",method!="GET"} 排除用法 prometheus_http_requests_total {environment!~"staging|testing|development",method!="GET"} 3.1.2、范围查询 直接通过类似于 PromQL 表达式 httprequeststotal 查询时间序列时，返回值中只会包含该时间序列中的最新的一个样本值，这样的返回结果我们称之为瞬时向量。而相应的这样的表达式称之为__瞬时向量表达式。 而如果想过去一段时间范围内的样本数据时，则需要使用区间向量表达式。区间向量表达式和瞬时向量表达式之间的差异在于在区间向量表达式中需要定义时间选择的范围，时间范围通过时间范围选择器 [] 进行定义。 例如，通过以下表达式可以选择最近 5 分钟内的所有样本数据： prometheus_http_requests_total {}[5m] 该表达式将会返回查询到的时间序列中最近 5 分钟的所有样本数据： PromQL 的时间范围选择器支持其它时间单位： s - 秒 m - 分钟 h - 小时 d - 天 w - 周 y - 年 3.1.3、时间位移操作 在瞬时向量表达式或者区间向量表达式中，都是以当前时间为基准： prometheus_http_requests_total {} # 瞬时向量表达式，选择当前最新的数据 prometheus_http_requests_total {}[5m] # 区间向量表达式，选择以当前时间为基准， 而如果想查询，5 分钟前的瞬时样本数据，或昨天一天的区间内的样本数据呢？可以使用位移操作，位移操作的关键字为 offset。 可以使用 offset 时间位移 操作： prometheus_http_requests_total {} offset 5m prometheus_http_requests_total {}[1d] offset 1d 3.1.4、使用聚合操作 一般来说，如果描述样本特征的标签 (label) 在并非唯一的情况下，通过 PromQL 查询数据，会返回多条满足这些特征维度的时间序列。而 PromQL 提供的聚合操作可以用来对这些时间序列进行处理，形成一条新的时间序列： # 查询系统所有 http 请求的总量 sum (prometheus_http_requests_total) # 按照 mode 计算主机 CPU 的平均使用时间 avg (node_cpu_seconds_total) by (mode) # 按照主机查询各个主机的 CPU 使用率 sum (sum (irate (node_cpu_seconds_total {mode!='idle'}[5m])) /sum (irate (node_cpu_seconds_total [5m]))) by (instance) 3.1.5、标量和字符串 除了使用瞬时向量表达式和区间向量表达式以外，PromQL 还直接支持用户使用标量 (Scalar) 和字符串 (String)。 标量（Scalar）：一个浮点型的数字值 标量只有一个数字，没有时序。 需要注意的是，当使用表达式 count (prometheus_http_requests_total)，返回的数据类型，依然是瞬时向量。用户可以通过内置函数 scalar () 将单个瞬时向量转换为标量。 字符串（String）：一个简单的字符串值 直接使用字符串，作为 PromQL 表达式，则会直接返回字符串。 "this is a string" 'these are unescaped: \n \\ \t' `these are not unescaped: \n '" \t` 3.1.6、合法的 PromQL 表达式 所有的 PromQL 表达式都必须至少包含一个指标名称 (例如 http_request_total)，或者一个不会匹配到空字符串的标签过滤器 (例如 {code=”200”})。 因此以下两种方式，均为合法的表达式： prometheus_http_requests_total # 合法 prometheus_http_requests_total {} # 合法 {method="get"} # 合法 而如下表达式，则不合法： {job=~".*"} # 不合法 同时，除了使用 {label=value} 的形式以外，我们还可以使用内置的 __name__ 标签来指定监控指标名称： {__name__=~"prometheus_http_requests_total"} # 合法 {__name__=~"node_disk_bytes_read|node_disk_bytes_written"} # 合法 3.2、PromQL 操作符 使用 PromQL 除了能够方便的按照查询和过滤时间序列以外，PromQL 还支持丰富的操作符，用户可以使用这些操作符对进一步的对事件序列进行二次加工。这些操作符包括：数学运算符，逻辑运算符，布尔运算符等等。 3.2.1、数学运算 PromQL 支持的所有数学运算符如下所示： + (加法) - (减法) * (乘法) / (除法) % (求余) ^ (幂运算) 3.2.2、布尔运算 Prometheus 支持以下布尔运算符如下： == (相等) != (不相等) >(大于) < (小于) >= (大于等于) <= (小于等于) 使用 bool 修饰符改变布尔运算符的行为 布尔运算符的默认行为是对时序数据进行过滤。而在其它的情况下我们可能需要的是真正的布尔结果。例如，只需要 知道当前模块的 HTTP 请求量是否 >=1000，如果大于等于 1000 则返回 1（true）否则返回 0（false）。这时可以使 用 bool 修饰符改变布尔运算的默认行为。 例如： prometheus_http_requests_total > bool 1000 使用 bool 修改符后，布尔运算不会对时间序列进行过滤，而是直接依次瞬时向量中的各个样本数据与标量的比较结果 0 或者 1。从而形成一条新的时间序列。 prometheus_http_requests_total {code="200",handler="query",instance="localhost:9090",job="prometheus",method="get"} 1 prometheus_http_requests_total {code="200",handler="query_range",instance="localhost:9090",job="prometheus",method="get"} 0 同时需要注意的是，如果是在两个标量之间使用布尔运算，则必须使用 bool 修饰 2 == bool 2 # 结果为 1 3.2.3、使用集合运算符 使用瞬时向量表达式能够获取到一个包含多个时间序列的集合，我们称为瞬时向量。通过集合运算，可以在两个瞬时向量与瞬时向量之间进行相应的集合操作。 目前，Prometheus 支持以下集合运算符： and (并且) or (或者) unless (排除) 3.2.4、操作符优先级 对于复杂类型的表达式，需要了解运算操作的运行优先级。例如，查询主机的 CPU 使用率，可以使用表达式： 100 * (1 - avg (irate (node_cpu_seconds_total {mode='idle'}[5m])) by (job) ) 其中 irate 是 PromQL 中的内置函数，用于计算区间向量中时间序列每秒的即时增长率。在 PromQL 操作符中优先级由高到低依次为： ^ *, /, % +, - ==, !=, <=, =, > and, unless or 3.2.5、PromQL 聚合操作 Prometheus 还提供了下列内置的聚合操作符，这些操作符作用域瞬时向量。可以将瞬时表达式返回的样本数据进行 聚合，形成一个新的时间序列。 sum (求和) min (最小值) max (最大值) avg (平均值) stddev (标准差) stdvar (标准差异) count (计数) count_values (对 value 进行计数) bottomk (后 n 条时序) topk (前 n 条时序) quantile (分布统计) 使用聚合操作的语法如下： <aggr-op>([parameter,] <vector expression>) [without|by ( )] 其中只有 count_values , quantile , topk , bottomk 支持参数 (parameter)。 without 用于从计算结果中移除列举的标签，而保留其它标签。by 则正好相反，结果向量中只保留列出的标签，其余标签则移除。通过 without 和 by 可以按照样本的问题对数据进行聚合。 例如： sum (prometheus_http_requests_total) without (instance) 等价于 sum (prometheus_http_requests_total) by (code,handler,job,method) 如果只需要计算整个应用的 HTTP 请求总量，可以直接使用表达式： sum (prometheus_http_requests_total) count_values 用于时间序列中每一个样本值出现的次数。count_values 会为每一个唯一的样本值输出一个时间序列，并且每一个时间序列包含一个额外的标签。 例如： count_values ("count", prometheus_http_requests_total) topk 和 bottomk 则用于对样本值进行排序，返回当前样本值前 n 位，或者后 n 位的时间序列。 获取 HTTP 请求数前 5 位的时序样本数据，可以使用表达式： topk (5, prometheus_http_requests_total) quantile 用于计算当前样本数据值的分布情况 quantile (φ, express) 其中 0 ≤ φ ≤ 1。 例如，当 φ 为 0.5 时，即表示找到当前样本数据中的中位数： quantile (0.5, prometheus_http_requests_total) 四、Prometheus 和 Grafana 集成 grafana 是一款采用 Go 语言编写的开源应用，主要用于大规模指标数据的可视化展现，是网络架构和应用分析中最流行的时序数据展示工具，目前已经支持绝大部分常用的时序数据库。下载地址： https://grafana.com/grafana/download 4.1、上传并解压 将 grafana-8.1.2.linux-amd64.tar.gz 上传至 /opt/software/ 目录下，解压： tar -zxvf grafana-enterprise-8.1.2.linux-amd64.tar.gz -C /opt/module/ 4.2、启动 Grafana nohup ./bin/grafana-server web > ./grafana.log 2>&1 & 打开 web：http://hadoop202:3000, 默认用户名和密码：admin 4.3、添加数据源 Prometheus 找到 Prometheus，点击 Select。 4.4、手动创建仪表盘Dashboard 点击左边栏的 “+”号，选择Dashboard: 添加新的仪表板，点击 Add an empty panel： 配置仪表板监控项： 4.5、直接添加 Flink 模板 手动一个个添加 Dashboard 比较繁琐，Grafana 社区鼓励用户分享 Dashboard，通过 https://grafana.com/dashboards 网站，可以找到大量可直接使用的 Dashboard 模板。 Grafana 中所有的 Dashboard 通过 JSON 进行共享，下载并且导入这些 JSON 文件，就可以直接使用这些已经定义好的 Dashboard： 进入官网，搜索 Flink 模板： 4.6、添加 Node Exporter 模板 进入 https://grafana.com/dashboards 页面，搜索 Node Exporter，选择下载量最高的中文版本： 4.7、组件启停脚本 创建脚本 flink-monitor.sh： #!/bin/bash case $1 in "start") { echo '----- 启动 prometheus -----' nohup /opt/module/prometheus-2.29.1/prometheus --web.enable-admin-api \ --config.file=/opt/module/prometheus-2.29.1/prometheus.yml > \ /opt/module/prometheus-2.29.1/prometheus.log 2>&1 & echo '----- 启动 pushgateway -----' nohup /opt/module/pushgateway-1.4.1/pushgateway --web.listen-address :9091 > \ /opt/module/pushgateway-1.4.1/pushgateway.log 2>&1 & echo '----- 启动 grafana -----' nohup /opt/module/grafana-8.1.2/bin/grafana-server --homepath \ /opt/module/grafana-8.1.2 web > /opt/module/grafana-8.1.2/grafana.log 2>&1 & };; "stop") { echo '----- 停止 grafana -----' pgrep -f grafana | xargs kill echo '----- 停止 pushgateway -----' pgrep -f pushgateway | xargs kill echo '----- 停止 prometheus -----' pgrep -f prometheus | xargs kill };; esac 脚本添加执行权限： chmod +x flink-monitor.sh 4.8、配置案例 4.8.1、任务失败监控 这一个指标监控主要是基于 flink_jobmanager_job_uptime 这个指标进行了监控。原 理是在 job 任务存活时，会按照配置 metrics.reporter.promgateway.interval 上报频率递增。基于这个特点，当任务失败后这个数值就不会改变，就能监控到任务失败。 1、添加监控项： 30 秒为数据上报到 promgateway 频率，除以 100 为了数据好看，当 job 任务失败 后数 flink 上报的 promgateway 的 flink_jobmanager_job_uptime 指标值不会变化。((flink_jobmanager_job_uptime)-(flink_jobmanager_job_uptime offset 30s))/100 值 就会是 0，可以配置告警。 2、配置告警 在告警通知中可以邮件和 webhook,webhook 可以调用相关接口，执行一些动作。webhook 需要提前配置，在这里配置告警时就可以直接引入。 4.8.2、网络延时或任务重启监控 这个告警也是基于 flink_jobmanager_job_uptime 指标，在出现网络延时或者重启后进行监控通知，监控指标如下： ((flink_jobmanager_job_uptime offset 30s)-(flink_jobmanager_job_uptime))/1000 1）延时会导致值突然小于-30（正常情况为-30） 2）重启会导致 flink_jobmanager_job_uptime 指标清零从新从 0 值上报，导致查询 公式值突然大于 0（正常情况为-30） 4.8.3、重启次数 基于 flink_jobmanager_job_numRestarts 指标，表示 flink job 的重启次数。一般设 置重启策略后，在任务异常重启后这个数值会递增 + 1。可以单纯的监控重启次数，也可以 每次重启都进行告警（差值）。 五、集成第三方告警平台睿象云 邮件通知常会出现接收不及时的问题，为确保通知信息被及时接收，可通过配置 Prometheus 或者 Grafana 与第三方平台告警平台（例如睿象云）集成，进而通过第三方 平台提供的多种告警媒介（例如电话，短信）等发送告警信息。 5.1、注册睿象云账号 集成睿象云之前须在其官网进行注册并登录，注册时需填入个人手机号和电子邮箱，以下是其官方网站 https://www.aiops.com。登录之后会看到如下界面。 5.2、集成 Grafana 1、点击 CA 智能告警平台 2、点击集成 3、选择 Grafana 4、填入应用名称，并点击 “保存并获取应用 key” 5、得到 AppKey 之后，配置 Grafana 6、在 Grafana 中创建 Notification channel， 7、配置 channel 8、Test&Save 测试后会接到电话以及邮件 5.3、配置分派策略 分派策略可以配置，哪些应用的告警信息，发送给哪些用户。例如实时数仓的告警信息发送给张三 1、点击 “配置”→“分派策略”→“新建分派” 2、配置具体分派策略 5.4、配置通知策略 通知策略，可以配置被分派人接收告警的通知方式，通知时间，通知延时等等。 1、点击 “配置”→“通知策略”→“新建通知” 2、配置具体的通知策略 5.5、测试电话、短信和邮件通知 我们将 netcat 停掉，flink 服务就会停止，随后即可触发睿象云的动作，进而根据我们 配置的分派策略和通知策略，发送告警信息。按照本文的配置，告警信息会以邮件、短信和 电话的方式发送到注册时填入的手机号码。