vivo Pulsar 万亿级消息处理实践(4)-Ansible运维部署
作者:Liu Sikang、互联网大数据团队-Luo Mingbo
Pulsar作为下一代云原生架构的分布式消息中间件,存算分离的架构设计能有效解决大数据场景下分布式消息中间件老牌一哥"Kafka"存在的诸多问题,2021年vivo 分布式消息中间件团队正式开启对Pulsar的调研,2022年正式引入Pulsar作为大数据场景下的分布式消息中间件,本篇文章主要从Pulsar运维痛点、Ansible简介、Ansible核心模块详解、Ansible自动化部署zk集群、Ansible自动化部署Pulsar集群几个维度向大家介绍vivo Pulsar万亿级消息处理实践之运维部署。
注:本文是《vivo Pulsar万亿级消息处理实践》系列文章第4篇。
1分钟看图掌握核心观点👇
1、简介
1.1 Pulsar 运维面临的问题
新业务增长快,很多新业务接入需要搭建独立的集群或者资源组。
升级频次高,对于bug修复,配置更改以及依赖组件替换等,都需要对全集群进行升级、配置更改或组件替换。
人力投入大,在集群运维时,需要对公共的执行步骤进行批处理封装,否则会耗费大量人力在集群的部署和升级上。
1.2 什么是 Ansible Playbook
Asnible Playbooks是Ansible自动化工具的核心部分。它是基于YAML文件格式,用于在多个主机上执行的任务。通过在Playbook中设置变量、处理器、角色和任务标签等功能,可以大大提高自动化脚本的复用性和可维护性。可以理解为批处理任务。
上图中我们看到Playbook的主要模块如下:
-
Ansible:Ansible 的核心程序。
-
HostInventory:记录由 Ansible 管理的主机信息,包括端口、密码、ip 等。
-
Playbooks:"剧本" YAML 格式文件,多个任务定义在一个文件中,定义主机需要调用哪些模块来完成的功能。
-
CoreModules:核心模块,主要操作是通过调用核心模块来完成管理任务。
-
CustomModules:自定义模块,完成核心模块无法完成的功能,支持多种语言。
-
ConnectionPlugins:连接插件,Ansible 和 Host 通信使用。
2、Playbook 语法
2.1 书写格式
playbook 常用到的YMAL格式:
-
文件的第一行应该以 "---" (三个连字符)开始,表明 YMAL 文件的开始。
-
在同一行中,# 之后的内容表示注释,类似于 shell,python 和 ruby。
-
YMAL 中的列表元素以 "-" 开头然后紧跟着一个空格,后面为元素内容。
-
同一个列表中的元素应该保持相同的缩进。否则会被当做错误处理。
-
play 中 hosts,variables,roles,tasks 等对象的表示方法都是键值中间以 ":" 分隔表示,":" 后面还要增加一个空格。
以下是 Playbook 的基本语法书写格式:
- name: playbook的名称 hosts: 目标主机或主机组 # 可以使用普通的 IP 地址或域名,也可以使用主机组名称 remote_user: 远程用户 # 使用 SSH 登录远程主机时使用的用户名 become: yes # 是否使用特权(例如 sudo)运行命令 tasks: # Playbook 中的任务列表 - name: 任务名称 module_name: 参数 # Ansible 模块的名称和参数组成的字典,用于执行操作 tags: # 与该任务相关的标记列表,用于执行特定的任务 - 标签名称 when: 条件 # 指定该任务在满足特定条件下才会被执行 notify: 通知列表 # 指定依赖于该任务的另一个任务列表,当这个任务被执行后会自动触发这些任务
2.2 Tasks & Modules
在Ansible Playbook的语法中,
"Tasks"和"Modules"是两个核心概念。
Tasks(任务):Tasks是Playbook中的操作步骤或任务,它们定义了要在目标主机上执行的操作。可以在Playbook中定义一个或多个任务。Tasks按照顺序执行,并且可以有条件地执行或跳过。
Modules(模块):Modules提供了执行特定任务的功能单元。每个模块负责处理不同的操作,如管理文件、安装软件包、查询系统信息等。Ansible提供了许多内置模块,可以满足大多数常见的操作。
通过组合不同的模块和任务,可以构建复杂的Playbooks来执行各种操作和配置任务。
2.3 任务之间的依赖关系
在 Ansible 的 playbook 中,任务之间可以有依赖关系,你可以使用 dependencies 或者 notify 语句来定义。
2.3.1 使用 dependencies 定义任务依赖关系
如果任务 A 依赖任务 B 完成,可以使用 dependencies 定义任务依赖关系,语法如下:
- hosts: web tasks: - name: Install Nginx yum: name: nginx state: present - name: Start Nginx service: name: nginx state: started become: true dependencies: - Install Nginx
在上面的示例中,Start Nginx 任务在 Install Nginx 任务完成之后才会执行。如果在执行 Start Nginx 任务之前,Install Nginx 任务未完成或者执行失败,则 Start Nginx 任务也会失败。
2.3.2 使用 notify 定义任务依赖关系
如果任务 A 完成后需要通知任务 B 执行,可以使用 notify 定义任务依赖关系,语法如下:
- hosts: web tasks: - name: Install Nginx yum: name: nginx state: present notify: -Start Nginx - name: Start Nginx service: name: nginx state: started become: true listen: Start Nginx
在上面的示例中,Install Nginx 任务完成后会通知 Start Nginx 任务执行。然后 Start Nginx 任务会通过 listen 参数监听,等待通知执行。
总之,Ansible 支持在 playbook 中定义任务之间的依赖关系。你可以使用 dependencies 或 notify 语句来定义任务之间的顺序和依赖关系。
2.4 条件判断
在Playbook中,可以使用when关键字来添加条件判断。when关键字后面跟一个条件表达式,如果表达式返回True,则任务会被执行;如果返回False,则任务会被跳过。
条件表达式可以使用Ansible的Jinja2模板来编写,例如:
tasks: - name: Install Apache if not installed package: name: apache2 state: present when: ansible_pkg_mgr == 'apt'
在这个例子中,如果ansible_pkg_mgr变量等于"apt",则安装Apache;否则跳过这个任务。
除了使用任务级别的条件判断,还可以使用Play级别的条件判断来控制整个Playbook的执行。这可以通过在Play的开始处添加when关键字来实现,例如:
- name: Deploy Web App hosts: all vars: deploy_web_app: true tasks: - name: Install Dependencies apt: name: "{{ item }}" state: present with_items: - python3 - python3-pip when: deploy_web_app
在这个例子中,deploy_web_app变量的值为True时,才会执行任务Install Dependencies。如果deploy_web_app变量的值为False,则跳过整个Playbook的执行。
2.5 循环
在Playbook中,可以使用循环结构来遍历列表或其他可迭代对象,并对每个迭代项执行相同的任务。这可以使用Ansible的with_*系列模块来实现。
以下是一些常见的循环结构的示例:
2.5.1 使用with_items模块来遍历列表
tasks: - name: Install packages apt: name: "{{ item }}" state: present with_items: - python3 - python3-pip - git
在这个例子中,将依次安装python3、python3-pip和git。
3、Playbook 组织
3.1 Inclusions
在Playbook的组织中,include和import两个指令都可以用来将其他的yaml文件(也就是Tasks文件)包含到当前的Playbook中。
它们的区别在于,当主Playbook执行到include指令时,它将处理包含的文件中的所有任务,并且在处理完之后继续主Playbook的执行。而当主Playbook执行到import指令时,它只会处理被导入的文件中的变量定义,而不会处理任务,任务只有在需要的时候才会被引入执行。
下面是一个使用include指令包含其他文件的例子:
- hosts: webservers tasks: - name: Include web tasks include: web-tasks.yml
在这个例子中,主Playbook从web-tasks.yml文件中导入任务,并在执行完后继续执行余下的任务。
下面是一个使用import指令包含其他文件的例子:
- name: Load variables import_vars: vars.yml - name: Deploy web app hosts: webservers tasks: - name: Install dependencies apt: name: "{{ item }}" state: present with_items: - python3 - python3-pip - name: Deploy app include: app-tasks.yml
在这个例子中,在主Playbook中使用import_vars指令来导入变量定义,然后在每个任务中都可以使用这些变量。然后我们使用include指令从app-tasks.yml文件中包含任务,这些任务可以使用在vars.yml文件中定义的变量。这种方式可以在需要时懒加载任务,提高性能。
需要注意的是,在被引入的文件中,不能再次使用- hosts:指令定义新的主机组,因为Ansible只允许在主Playbook中定义主机组。被引入的文件只包含任务,任务必须使用被定义的主机组来指定目标主机。
3.2 Roles
Ansible的Roles是一种组织Playbook的方式,它将Playbook和相关的变量、模板和其他资源打包在一起,并且可以轻松地在Playbook中重用和分享。一个Role通常适用于一种操作或功能,比如安装和配置一个应用程序、部署Web服务、安装软件包等等。
一个Role目录通常包含以下文件和目录:
my-role/ ├── README.md ├── defaults/ │ └── main.yml ├── files/ ├── handlers/ │ └── main.yml ├── meta/ │ └── main.yml ├── tasks/ │ └── main.yml ├── templates/ ├── tests/ │ ├── inventory │ └── test.yml └── vars/ └── main.yml
-
README.md:Role的说明文档。
-
defaults/main.yml:默认变量定义文件。
-
files:包含角色使用的文件。
-
handlers/main.yml:Role的处理程序。
-
meta/main.yml:Role的元数据,例如角色名称、作者、依赖等。
-
tasks/main.yml:包含Role组成部分的主要任务。
-
templates:包含角色使用的Jinja2模板。
-
tests:Role的测试脚本。
-
vars/main.yml:包含Role的变量。
要使用Role,需要在Playbook中定义roles扩展,例如:
- hosts: webservers roles: - my-role
这将运行my-role目录中包含的所有任务。
通过使用Role,可以更好地组织和重复使用代码,并提高代码的可读性和可维护性。它还可以帮助您在Ansible社区中分享自己的工作,或从其他用户那里获得高质量的Roles。
3.3 引用/定义变量
在Playbook中,可以使用vars关键字来定义变量。例如:
vars: my_var: "Hello World"
这将定义一个名为my_var的变量,其值为字符串"Hello World"。
要在Playbook中访问这个变量,可以使用{{ my_var }}语法。例如:
tasks: - name: Print Message debug: msg: "{{ my_var }}"
除了在vars中定义变量,还可以通过set_fact模块来动态设置变量。例如:
tasks: - name: SetDynamic Variable set_fact: my_var: "{{ inventory_hostname }} is awesome"
3.4 使用插件和模板
Ansible提供了插件和模板的功能,使得在Playbook中使用动态内容变得更加简单和方便。
插件是一种可以扩展和定制Ansible功能的机制,可以在Playbook中调用和使用。常见的插件包括Action、Lookup、Filter、Callback等。使用插件和模板可以使Playbook更加具有可读性和可维护性,使得动态内容的生成更加灵活和方便。
4、服务安装与主机管理
4.1 安装服务器依赖
Playbook是Ansible的核心组件之一,用于定义和执行一系列任务。在使用Playbook之前,需要确保服务器上已经安装了Ansible和相关的依赖项。以下是安装服务器依赖的步骤:
4.1.1 安装Python3及其相关依赖项
sudo apt update sudo apt-get install -y python3 python3-pip python3-dev build-essential libssl-dev libffi-dev
4.1.2 安装Ansible
sudo apt-add-repository ppa:ansible/ansible sudo apt update sudo apt-get install -y ansible
4.1.3 (可选)安装 git
sudo apt update sudo apt-get install -y git
4.1.4 检查Ansible是否安装
ansible --version
这样,您的服务器就已经安装了所需的依赖项以及Ansible。如果您计划在多台服务器上使用Ansible,则需要在每台服务器上重复这些步骤。
4.2 配置远程服务器
在使用Playbook配置远程服务器之前,需要确保Ansible已经正确安装在本地机器上。然后,您需要做以下几个步骤:
4.2.1 创建inventory文件
创建新的inventory文件,用于定义您要配置的远程服务器的IP地址或域名。例如,您可以创建一个名为inventory的文件,并包含以下内容:
[webservers] 192.168.1.100 192.168.1.101 [dbservers] 192.168.1.102
在此示例中,我们定义了两个组,webservers和dbservers,并列出了它们中每个服务器的IP地址。
4.2.2 编写Playbook
编写一个Playbook,用于在远程服务器上执行特定的任务。例如,您可以创建一个名为web.yml的Playbook,并包含以下内容:
- name: Install andstart Nginx hosts: webservers become: true tasks: - name: Install Nginx apt: name: nginx update_cache: yes state: latest - name: Start Nginx service: name: nginx state: started enabled: true
在这个Playbook示例中,我们定义了一个名为Install and start Nginx的任务,它会在webservers组中的服务器上启动Nginx服务器。
4.2.3 运行Playbook
运行Playbook,在远程服务器上执行配置任务。例如,要在远程服务器上运行示例中的web.yml Playbook,可以使用以下命令:
ansible-playbook -i inventory web.yml
在执行此命令后,Ansible将使用inventory文件中定义的远程服务器的IP地址,并执行web.yml Playbook中定义的任务。
这是一个基本的Playbook配置远程服务器的示例。需要根据具体的场景和任务需求来进行个性化配置和修改。
4.3 部署应用程序
Playbook部署应用程序一般步骤:
1、准备应用程序的部署包。这通常是一个.tar.gz或.zip文件,包含应用程序代码、依赖项和其他必要文件。 2、在目标主机上安装所需的依赖项和软件包。例如,在部署Python应用程序时,需要安装Python解释器、pip和其他依赖项。 3、创建一个目录用于应用程序的部署。这通常是在目标主机上的一个新目录,例如/home/user/myapp。 4、上传应用程序部署包到目标主机并解压缩。您可以使用copy模块将部署包部署到目标主机上。 5、配置应用程序的运行环境。例如,在部署Flask应用程序时,需要设置环境变量、安装必要的Python包等。 6、配置Web服务器以侦听应用程序的请求。例如,您可以使用Nginx或Apache等Web服务器来代理应用程序请求。
5、常用模块的 playbook 语法
-
file模块:可以管理文件系统中的文件和目录。下面是该模块的常用参数:
-
copy模块:可以将本地文件复制到远程服务器上。
-
unarchive模块:Ansible 中用于将压缩文件解压缩的模块。
-
apt模块:可以在Ubuntu或Debian系统上安装、升级、删除软件包。
-
service模块:可以在系统上管理服务。
-
user模块:可以管理系统用户。
-
shell模块:可以在远程服务器上运行基于命令行的任务。该模块只能运行命令,不能使用管道、重定向和通配符。
-
script模块:可以将本地脚本或可执行文件上传到远程服务器并在远程服务器上运行。该模块适用于运行复杂的命令和复杂的脚本。
-
template模块:可以将在Ansible中定义的Jinja2模板应用于远程服务器上的文件。在应用模板时,您可以使用变量来一次生成多个文件的不同版本。
-
lineinfile模块:可以从文件中添加、修改或删除单行文本。该模块可用于修改文件中的配置文件或语言文件,或添加新行。
-
blockinfile模块:可以在远程服务器文件中添加、修改或删除代码块。该模块可以替代lineinfile模块,以单个块更新文件。
-
debug模块:可以输出调试信息。该模块在编写Playbooks时非常有用,因为可以检查任务的变量和结果。
6、Ansible部署Pulsar集群运维实战
6.1 部署zookeeper集群
6.1.1 定义host文件
host 文件指定了要在哪些主机上执行任务。在 playbook 中,可以将 hosts 指定为一个变量,也可以通过 -i 参数指定一个主机清单文件,该文件包含要操作的主机列表。
[all:vars] ansible_ssh_user=xxx ansible_ssh_pass=xxx [zk] 127.xxx.xxx.1 myid=1 127.xxx.xxx.2 myid=2 127.xxx.xxx.3 myid=3 127.xxx.xxx.4 myid=4 127.xxx.xxx.5 myid=5
6.1.2 定义变量
group_vars 目录用于存放针对不同主机组的变量文件,其中 all 文件是一种特殊的变量文件,它包含了全局的变量定义,将适用于所有主机组。路径结构如下:
group_vars/ ├── all
在all文件中,我们可以定义安装路径、JDK版本为、zookeeper版本以及zookeeper相关的配置信息。比如:
inst_home: /opt/bigdata/inst app_home: /opt/bigdata/app zk_inst_home: zookeeper-3.6.3 zk_app_home: zookeeper jdk_inst_home: jdk1.8.0_192 jdk_app_home: jdk jdk_tgz: jdk1.8.0_192.tar.gz zk_tgz: zookeeper-3.6.3.tar.gz cluster_name=clusterName client_port=2181 server_port1=2881 server_port2=2882 jmx_port=9012 admin_port=18080 dataDir="/data/bigdata/zookeeper_{{cluster_name}}/zkDataDir" dataLogDir="/data/bigdata/zookeeper_{{cluster_name}}/zkDataLogDir" zoo_log_dir="/opt/bigdata/inst/zookeeper-3.6.3-{{cluster_name}}/logs/"
6.1.3 编辑roles模块
① check_port:检查端口
判断配置的端口是否被占用,如果被占用,则不能执行后续的步骤。
目录结构如下:
check_port/ ├── tasks/ │ └── main.yml main.yml
#循环检查端口是否是停用状态
- name: Check port wait_for: host: "{{ inventory_hostname }}" port: "{{ item }}" delay: 2 timeout: 3 state: stopped register: result with_items: - "{{ client_port }}" - "{{ server_port1 }}" - "{{ server_port2 }}" - "{{ jmx_port }}" - "{{ admin_port }}" - name: print result debug: msg: "Port {{ item.item }} is {{ item.state }}" with_items: "{{ result.results }}"
② dispatch_zk:分发安装包
目录结构如下:
dispatch_zk/ ├── files/ │ └── zookeeper-3.6.3.tar.gz ├── tasks/ │ └── main.yml
files:放zookeeper安装包文件。
main.yml
#分发zk安装包并解压到/tmp路径下 - name: dispatch_zk unarchive: src: "{{zk_tgz}}" dest: "/tmp" mode: 755 owner: root group: root
③ config_zk:配置zookeeper
目录结构如下:
config_zk/ ├── tasks/ │ └── main.yml ├── templates/ │ └── zoo.cfg
main.yml
#zoo.cfg模板文件应用到指定的路径下 - name: zoo.cfg template: src: zoo.cfg dest: "{{ app_home }}/zk-{{ cluster_name }}/conf" #创建zoo_log_dir目录 - name: mkdir forlog shell: mkdir -p "{{zoo_log_dir}}" #创建zk数据目录 - name: mkdir for dataDir shell: mkdir -p "{{dataDir}}" #创建zk日志目录 - name: mkdir for dataLogDir shell: mkdir -p "{{dataLogDir}}" #myid文件中输入每台主机的编号 - name: myid file shell: echo "{{myid}}" > {{dataDir}}/myid
zoo.cfg:zookeeper配置文件模板。
tickTime=2000 initLimit=10 syncLimit=5 maxClientCnxns=65535 autopurge.snapRetainCount=30 autopurge.purgeInterval=48 clientPort={{client_port}} admin.serverPort={{admin_port}} dataDir={{dataDir}} dataLogDir={{dataLogDir}} {% for host in groups.zk%} server.{{ hostvars[host]['myid'] }}={{host}}:{{server_port1}}:{{server_port2}} {% endfor %}
④ deploy_zk:部署zookeeper服务
目录结构如下:
deploy_zk/ ├── files/ │ └── env.sh │ └── jdk1.8.0_192.tar.gz ├── tasks/ │ └── main.yml
env.sh:jdk环境变量配置
JAVA_HOME=/opt/bigdata/app/jdk JRE_HOME=$JAVA_HOME/jre PATH=$JAVA_HOME/bin:$JRE_HOME/bin:$PATH CLASSPATH=$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JRE_HOME/lib/rt.jar:$CLASSPATH
main.yml
#创建/opt/bigdata/inst目录 - name: mkdir for inst_home shell: mkdir -p {{ inst_home }} #创建/opt/bigdata/app目录 - name: mkdir for app_home shell: mkdir -p {{ app_home }} #注册zk_dir变量 - name: stat_dir stat: path={{ inst_home }}/{{zk_inst_home}}-{{cluster_name}} register: zk_dir #当zk_dir存在时,将/tmp路径安装包移到指定目录并重命名 - name: rename zookeeper dir command: mv /tmp/{{zk_inst_home}} {{ inst_home }}/{{zk_inst_home}}-{{cluster_name}} when: zk_dir.stat.exists == False #创建zk集群软连接 - name: soft link file: path: "{{ app_home }}/zk-{{ cluster_name }}" src: "{{ inst_home }}/{{ zk_inst_home }}-{{ cluster_name }}" state: link #分发并解压jdk安装包 - name: deploy jdk unarchive: src: "{{ jdk_tgz }}" dest: "{{ inst_home }}" #创建jdk软连接 - name: create soft link for jdk file: path: "{{ app_home }}/{{ jdk_app_home }}" src: "{{ inst_home }}/{{ jdk_inst_home }}" state: link #运行jdk环境变量,使其生效 - name: env script: env.sh
⑤ start_zk:启动zookeeper服务
目录结构如下:
start_zk/ ├── tasks/ │ └── main.yml
main.yml
#启动zk服务
- name: start zookeeper shell: cd {{ app_home }}/zk-{{cluster_name}}; sh bin/zkServer.sh start
6.1.4 编辑任务执行和启动脚本
zookeeper.yml:任务执行脚本
--- - name: check_port hosts: zk remote_user: root roles: - check_port tags: check_port - name: dispatch_zk hosts: zk remote_user: root roles: - dispatch_zk tags: dispatch_zk - name: deploy_zk hosts: zk remote_user: root roles: - deploy_zk tags: deploy_zk - name: config_zk hosts: zk remote_user: root roles: - config_zk tags: config_zk - name: start_zk hosts: zk remote_user: root roles: - start_zk tags: start_zk
6.1.5 部署并启动zookeeper服务
# 部署并启动zookeeper服务 ansible-playbook -i hosts-clusterName zookeeper.yml #只检查端口和分发安装包 ansible-playbook -i hosts-clusterName zookeeper.yml --tags "check_port,dispatch_packages"
6.2 部署Pulsar集群
6.2.1 定义hosts文件
[all:vars] ansible_ssh_user=xxx ansible_ssh_pass=xxx [pulsar] 127.xxx.xxx.1 127.xxx.xxx.2 127.xxx.xxx.3 127.xxx.xxx.4 127.xxx.xxx.5
6.2.2 定义全局变量
group_vars 目录用于存放针对不同主机组的变量文件,其中 all 文件是一种特殊的变量文件,它包含了全局的变量定义,将适用于所有主机组。路径结构如下:
group_vars/ ├── all
all文件内容中定义变量信息,如下:
bigdata_home: /opt/bigdata inst_home: /opt/bigdata/inst app_home: /opt/bigdata/app pulsar_app_home: pulsar pulsar_inst_home: apache-pulsar-2.9.2-1.3 pulsar_tgz: apache-pulsar-2.9.2-1.3-bin.tar.gz pulsar_conf: "{{ app_home }}/pulsar/conf" secret_key_dir: "{{ app_home }}/pulsar/data" #bookkeeper.conf ledgerDirectories: /data1/bookkeeper/ledger,/data2/bookkeeper/ledger,/data3/bookkeeper/ledger,/data4/bookkeeper/ledger #broker.conf or client.conf zkServers: "127.xxx.xxx.1:2183,127.xxx.xxx.2:2183,127.xxx.xxx.3:2183/clusterName" clusterName: wenzhu webServiceUrl: http://clusterNamexxxx:8080 brokerServiceUrl: pulsar://clusterNamexxxx:6650
6.2.3 编辑roles模块
① dispatch_pulsar:分发安装包
目录结构如下:
dispatch_pulsar/ ├── files/ │ └── apache-pulsar-2.9.2-1.3-bin.tar.gz ├── tasks/ │ └── main.yml
main.yml
#创建inst_home定义的目录 - name: mkdir_inst_home file: path: "{{ inst_home }}" state: directory #创建app_home定义的目录 - name: mkdir_app_home file: path: "{{ app_home }}" state: directory #分发并解压pulsar安装包到指定目录 - name: dispatch_packages unarchive: src: "{{ pulsar_tgz }}" dest: "{{ inst_home }}" #创建pulsar软连接 - name: soft_link file: path: "{{ app_home }}/pulsar" src: "{{ inst_home }}/{{ pulsar_inst_home }}" state: link
② check_nar:校验分层存储和kop扩展的依赖包
目录结构如下:
check_nar/ ├── tasks/ │ └── main.yml
main.yml
#匹配指定路径protocols和offloaders下是否有nar后缀的文件 - name: check nar find: paths: "{{ app_home }}/pulsar/{{ item }}/" patterns: "*.nar" register: result with_items: - "offloaders" - "protocols" #设置文件匹配的结果(大于0表示文件存在) - name: set nar_files_exist variable set_fact: nar_files_exist_{{item.item}}: "{{ item.matched > 0 }}" with_items: "{{ result.results }}" #如果文件不存在,进行提示 - name: nar files not exist fail: msg: "{{ item.item }} nar files not found" when: nar_files_exist_{{ item.item }} == false ignore_errors: true with_items: "{{ result.results }}" #如果文件存在,列出存在的文件名 - name: print nar files list debug: msg: "{{ item.files | map(attribute='path') | list }}" when: nar_files_exist_{{item.item}} with_items: "{{ result.results }}"
③ config_pulsar:配置pulsar
目录结构如下:
config_pulsar/ ├── tasks/ │ └── main.yml ├── templates/ │ └── bkenv.sh │ └── pulsar_env.sh
main.yml
#匹配broker.conf中的advertisedAddress值并设置为远程主机ip地址 - name: config_advertisedAddress lineinfile: path: "{{ pulsar_conf }}/broker.conf" regexp: "^advertisedAddress=" line: "advertisedAddress={{ inventory_hostname }}" #配置broker.conf中的zookeeperServers值 - name: config_zookeeperServers lineinfile: path: "{{ pulsar_conf }}/broker.conf" regexp: "^zookeeperServers=" line: "zookeeperServers={{ zkServers }}" #配置broker.conf中的clusterName值 - name: config_clusterName lineinfile: path: "{{ pulsar_conf }}/broker.conf" regexp: "^clusterName=" line: "clusterName={{ clusterName }}" #配置broker.conf中的kafkaAdvertisedListeners值 - name: config_kafkaAdvertisedListeners lineinfile: path: "{{ pulsar_conf }}/broker.conf" regexp: "^kafkaAdvertisedListeners=" line: "kafkaAdvertisedListeners=PLAINTEXT://{{ inventory_hostname }}:9093" #配置bookkeeper.conf中的advertisedAddress值,设置为主机ip地址 - name: config_bk_advertisedAddress lineinfile: path: "{{ pulsar_conf }}/bookkeeper.conf" regexp: "^advertisedAddress=" line: "advertisedAddress={{ inventory_hostname }}" #将模板文件bkenv.sh应用到pulsar的配置文件中 - name: config_bkenv.sh template: src: bkenv.sh dest: "{{ pulsar_conf }}" #将模板文件pulsar_env.sh应用到pulsar的配置文件中 - name: config_pulsar_env.sh template: src: pulsar_env.sh dest: "{{ pulsar_conf }}"
④ create_data_dir:创建存储数据的目录
目录结构如下:
create_data_dir/ ├── tasks/ │ └── main.yml
main.yml
#循环创建with_items中的数据目录
- name: mkdir_data_dir file: path: "{{ item }}" state: directory with_items: - /data1/bookkeeper/ledger - /data2/bookkeeper/ledger - /data3/bookkeeper/ledger - /data4/bookkeeper/ledger
⑤ config_secret_key:配置安全秘钥
目录结构如下:
config_secret_key/ ├── files/ │ └── admin-secret.key ├── tasks/ │ └── main.yml
main.yml
#创建存放安全秘钥的目录 - name: create_secret_key_dir file: path: "{{ secret_key_dir }}" owner: root group: root state: directory #将安装秘钥文件分发到指定的路径下 - name: dispatch_secret.key copy: src: admin-secret.key dest: "{{ secret_key_dir }}"
⑥ init_meta:初始化集群元数据
目录结构如下:
init_meta/ ├── tasks/ │ └── main.yml ├── templates/ │ └── init_meta.sh
main.yml
#应用init_meta.sh脚本到远程主机 - name: scp init_meta.sh template: src: init_meta.sh dest: "{{ app_home }}/pulsar" #执行初始化脚本文件 - name: init_meta shell: nohup sh {{ app_home }}/pulsar/init_meta.sh > {{ app_home }}/pulsar/init.log2>&1 & #等待20s查询初始化日志中是否出现初始化成功的日志 - name: wait20s wait_for: path: "{{ app_home }}/pulsar/init.log" search_regex: "Cluster metadata for '{{ clusterName }}' setup correctly" delay: 20 #杀掉集群元数据初始化进程 - name: kill metadata shell: ps -efww|grep PulsarClusterMetadataSetup|grep -v grep|cut -c 9-15|xargs kill -9 init_meta.sh:初始化集群元数据脚本 {{ app_home }}/pulsar/bin/pulsar initialize-cluster-metadata \ --cluster {{ clusterName }} \ --zookeeper {{ zkServers }} \ --configuration-store {{ zkServers }} \ --web-service-url {{ webServiceUrl }} \ --broker-service-url {{ brokerServiceUrl }}
⑦ start_service:启动broker和bookkeeper服务
start_service/ ├── tasks/ │ └── main.yml
main.yml
#启动远程主机bookkeeper服务 - name: start bookie shell: sh {{ app_home }}/pulsar/bin/pulsar-daemon start bookie #启动远程主机broker服务 - name: start broker shell: sh {{ app_home }}/pulsar/bin/pulsar-daemon start broker
6.2.4 编辑任务执行脚本
pulsar.yml:任务执行脚本
--- #分发pulsar安装包 - name: dispatch_pulsar hosts: pulsar remote_user: root become: yes become_flags: '-i' roles: - dispatch_pulsar tags: dispatch_pulsar #检查安装包中kop和分层存储nar包是否存在 - name: check_nar hosts: pulsar remote_user: root roles: - check_nar tags: check_nar #修改pulsar配置 - name: config_pulsar hosts: pulsar remote_user: root become: yes become_flags: '-i' roles: - config_pulsar tags: config_pulsar #创建磁盘数据目录 - name: create_data_dir hosts: pulsar remote_user: root become: yes become_flags: '-i' roles: - create_data_dir tags: create_data_dir #配置证书文件 - name: config_secret_key hosts: pulsar remote_user: root become: yes become_flags: '-i' roles: - config_secret_key tags: config_secret_key #初始化meta信息 - name: init_meta hosts: pulsar[0] remote_user: root become: yes become_flags: '-i' roles: - init_meta tags: init_meta #启动broker和bookkeeper服务 - name: start_service hosts: pulsar remote_user: root become: yes become_flags: '-i' roles: - start_service tags: start_service
6.2.5 执行playbook任务
#执行所有pulsar.yml中的任务 ansible-playbook -i hosts pulsar.yml #只执行pulsar.yml中标签为dispatch_pulsar,check_nar的任务 ansible-playbook -i hosts pulsar.yml --tags "dispatch_pulsar,check_nar"
7、Playbooks运维Pulsar集群总结
7.1 Pulsar运维实践总结
Pulsar作为新一代云原生架构的分布式消息中间件,目前再超大流量规模、海量分区、超高QPS等场景下缺乏长时间的稳定性验证,在极端场景下还存在较多稳定性风险,当前社区版本迭代活跃;Pulsar集群在vivo内部日均处理消息达万亿+,需要不断的合并社区issue及灰度升级高版本。运维事项较多、投入的运维人力较大。vivo分布式消息中间件团队通过借助Ansible的模块化、任务依赖、配置check、批量脚本执行等能力实现Pulsar集群从zk集群搭建、Pulsar安装包编译、自动化配置填充、批量分发部署、服务启动的一键运维部署能力。大大缩减了Pulsar集群的运维人力投入,Pulsar组件存算分离的架构设计优秀,但部署配置项非常繁杂,通过自动化配置填充可有效规避配置信息不一致、版本不一致等高频错误。
7.2 playbooks服务部署步骤
根据以上实战经验,我们可以总结出部署某个服务时编写playbooks脚本的一般步骤如下:
Ansible更多运维实践可参考:https://github.com/ansible/ansible-examples
猜你喜欢

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
腾讯混元开源 Hunyuan-GameCraft
腾讯混元宣布开源新工具Hunyuan-GameCraft,声称可让用户像导演一样打造游戏场景。 Hunyuan-GameCraft 是基于HunyuanVideo底模的高动态交互式游戏视频生成框架,简单来说,它是一个“游戏视频生成工具”,只需要“输入一张图 + 文字描述+动作指令(按键盘方向键)”,就能输出高清动态游戏视频。 无论是第一人称跑酷,还是第三人称探险,它都能实时生成流畅画面,仿佛你真的在游戏世界里自由穿梭。 该工具解决了传统游戏内容生产中的三大难题:动作僵硬、场景静态以及生产成本高昂。Hunyuan-GameCraft 有以下三大优势: 自由流畅 :统一连续动作空间,支持高精度控制(角度/速度),支持“边跑边转视角”的复杂操作;可以生成动态内容(例如主角和NPC运动、云层移动、雨雪、水流运动等)。 记忆增强 :生成长视频时,角色和环境保持稳定不“穿帮”;通过混合历史条件,实现历史帧记忆,避免长视频生成时不连贯; 成本骤降 :无需人工建模或渲染,制作成本更低;对比现有的游戏模型闭源方案,泛化性强。阶段一致性蒸馏方案(Phased Consistency Model, PCM)...
- 下一篇
在中国做软件行业如何赚钱?
上周写的一篇文章《中国的软件行业为什么不赚钱》得到了很多朋友的认可,大家在留言区有很多精彩的评论。在这篇文章里我主要想表达:在中国这样一个非标准化的软件市场里,做软件开发这样一个非标准化的事,是很难赚钱的。 话说回来,即使再怎么不赚钱的行业,依然有赚得盆满钵满的公司存在。今天就来聊聊在中国做软件行业要想赚钱的关键是什么?以下观点都是来自于跟我们禅道软件用户和客户打交道的经历,以及我们自身的实践和思考。由于精力有限,没有办法做更进一步的数据调查,所以我的观点是比较偏主观的,仅供大家参考。 我认为在中国做软件行业赚钱的关键就两个字:规模。规模越小的团队越容易赚钱,规模越大的团队越容易赚钱。中间状态的团队是死亡陷阱,要么快速地冲到更大规模的状态,要么就收缩回小团队的状态,否则的话就是陷入焦油坑的大象。 小规模团队赚钱的逻辑是“小众市场+低成本”,其成功的核心因素是创始人。这类团队的创始人一般都是技术出身,在过往的工作经历中发现了某些小众市场。因为市场小,大公司看不上,所以只要技术比别人更好一些,产品打磨好,这样的软件团队就可以赚到钱。比如我所了解到的有很多个人开发者,就开发一款App,靠应用...
相关文章
文章评论
共有0条评论来说两句吧...