vivo Pulsar 萬億級消息處理實(shí)踐(4)-Ansible運(yùn)維部署

Pulsar作為下一代云原生架構(gòu)的分布式消息中間件,存算分離的架構(gòu)設(shè)計(jì)能有效解決大數(shù)據(jù)場景下分布式消息中間件老牌一哥“Kafka”存在的諸多問題,2021年vivo 分布式消息中間件團(tuán)隊(duì)正式開啟對Pulsar的調(diào)研,2022年正式引入Pulsar作為大數(shù)據(jù)場景下的分布式消息中間件,本篇文章主要從Pulsar運(yùn)維痛點(diǎn)、Ansible簡介、Ansible核心模塊詳解、Ansible自動(dòng)化部署zk集群、Ansible自動(dòng)化部署Pulsar集群幾個(gè)維度向大家介紹vivo Pulsar萬億級消息處理實(shí)踐之運(yùn)維部署。
注:本文是《vivo Pulsar萬億級消息處理實(shí)踐》系列文章第4篇。

01、簡介
1.1Pulsar 運(yùn)維面臨的問題
新業(yè)務(wù)增長快,很多新業(yè)務(wù)接入需要搭建獨(dú)立的集群或者資源組。
升級頻次高,對于bug修復(fù),配置更改以及依賴組件替換等,都需要對全集群進(jìn)行升級、配置更改或組件替換。
人力投入大,在集群運(yùn)維時(shí),需要對公共的執(zhí)行步驟進(jìn)行批處理封裝,否則會(huì)耗費(fèi)大量人力在集群的部署和升級上。
1.2什么是 Ansible Playbook
Asnible Playbooks是Ansible自動(dòng)化工具的核心部分。它是基于YAML文件格式,用于在多個(gè)主機(jī)上執(zhí)行的任務(wù)。通過在Playbook中設(shè)置變量、處理器、角色和任務(wù)標(biāo)簽等功能,可以大大提高自動(dòng)化腳本的復(fù)用性和可維護(hù)性。可以理解為批處理任務(wù)。

上圖中我們看到Playbook的主要模塊如下:
- Ansible:Ansible 的核心程序。
- HostInventory:記錄由 Ansible 管理的主機(jī)信息,包括端口、密碼、ip 等。
- Playbooks:“劇本” YAML 格式文件,多個(gè)任務(wù)定義在一個(gè)文件中,定義主機(jī)需要調(diào)用哪些模塊來完成的功能。
- CoreModules:核心模塊,主要操作是通過調(diào)用核心模塊來完成管理任務(wù)。
- CustomModules:自定義模塊,完成核心模塊無法完成的功能,支持多種語言。
- ConnectionPlugins:連接插件,Ansible 和 Host 通信使用。
02、Playbook 語法
2.1書寫格式
playbook 常用到的YMAL格式:
- 文件的第一行應(yīng)該以 "---" (三個(gè)連字符)開始,表明 YMAL 文件的開始。
- 在同一行中,# 之后的內(nèi)容表示注釋,類似于 shell,python 和 ruby。
- YMAL 中的列表元素以 ”-” 開頭然后緊跟著一個(gè)空格,后面為元素內(nèi)容。
- 同一個(gè)列表中的元素應(yīng)該保持相同的縮進(jìn)。否則會(huì)被當(dāng)做錯(cuò)誤處理。
- play 中 hosts,variables,roles,tasks 等對象的表示方法都是鍵值中間以 ":" 分隔表示,":" 后面還要增加一個(gè)空格。
以下是 Playbook 的基本語法書寫格式:
- name: playbook的名稱
hosts: 目標(biāo)主機(jī)或主機(jī)組 # 可以使用普通的 IP 地址或域名,也可以使用主機(jī)組名稱
remote_user: 遠(yuǎn)程用戶 # 使用 SSH 登錄遠(yuǎn)程主機(jī)時(shí)使用的用戶名
become: yes # 是否使用特權(quán)(例如 sudo)運(yùn)行命令
tasks: # Playbook 中的任務(wù)列表
- name: 任務(wù)名稱
module_name: 參數(shù) # Ansible 模塊的名稱和參數(shù)組成的字典,用于執(zhí)行操作
tags: # 與該任務(wù)相關(guān)的標(biāo)記列表,用于執(zhí)行特定的任務(wù)
- 標(biāo)簽名稱
when: 條件 # 指定該任務(wù)在滿足特定條件下才會(huì)被執(zhí)行
notify: 通知列表 # 指定依賴于該任務(wù)的另一個(gè)任務(wù)列表,當(dāng)這個(gè)任務(wù)被執(zhí)行后會(huì)自動(dòng)觸發(fā)這些任務(wù)2.2Tasks & Modules
在Ansible Playbook的語法中,
"Tasks"和"Modules"是兩個(gè)核心概念。
Tasks(任務(wù)):Tasks是Playbook中的操作步驟或任務(wù),它們定義了要在目標(biāo)主機(jī)上執(zhí)行的操作??梢栽赑laybook中定義一個(gè)或多個(gè)任務(wù)。Tasks按照順序執(zhí)行,并且可以有條件地執(zhí)行或跳過。
Modules(模塊):Modules提供了執(zhí)行特定任務(wù)的功能單元。每個(gè)模塊負(fù)責(zé)處理不同的操作,如管理文件、安裝軟件包、查詢系統(tǒng)信息等。Ansible提供了許多內(nèi)置模塊,可以滿足大多數(shù)常見的操作。
通過組合不同的模塊和任務(wù),可以構(gòu)建復(fù)雜的Playbooks來執(zhí)行各種操作和配置任務(wù)。
2.3任務(wù)之間的依賴關(guān)系
在 Ansible 的 playbook 中,任務(wù)之間可以有依賴關(guān)系,你可以使用 dependencies 或者 notify 語句來定義。
2.3.1 使用 dependencies 定義任務(wù)依賴關(guān)系
如果任務(wù) A 依賴任務(wù) B 完成,可以使用 dependencies 定義任務(wù)依賴關(guān)系,語法如下:
- 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 任務(wù)在 Install Nginx 任務(wù)完成之后才會(huì)執(zhí)行。如果在執(zhí)行 Start Nginx 任務(wù)之前,Install Nginx 任務(wù)未完成或者執(zhí)行失敗,則 Start Nginx 任務(wù)也會(huì)失敗。
2.3.2 使用 notify 定義任務(wù)依賴關(guān)系
如果任務(wù) A 完成后需要通知任務(wù) B 執(zhí)行,可以使用 notify 定義任務(wù)依賴關(guān)系,語法如下:
- 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 任務(wù)完成后會(huì)通知 Start Nginx 任務(wù)執(zhí)行。然后 Start Nginx 任務(wù)會(huì)通過 listen 參數(shù)監(jiān)聽,等待通知執(zhí)行。
總之,Ansible 支持在 playbook 中定義任務(wù)之間的依賴關(guān)系。你可以使用 dependencies 或 notify 語句來定義任務(wù)之間的順序和依賴關(guān)系。
2.4條件判斷
在Playbook中,可以使用when關(guān)鍵字來添加條件判斷。when關(guān)鍵字后面跟一個(gè)條件表達(dá)式,如果表達(dá)式返回True,則任務(wù)會(huì)被執(zhí)行;如果返回False,則任務(wù)會(huì)被跳過。
條件表達(dá)式可以使用Ansible的Jinja2模板來編寫,例如:
tasks:
- name: Install Apache if not installed
package:
name: apache2
state: present
when: ansible_pkg_mgr == 'apt'在這個(gè)例子中,如果ansible_pkg_mgr變量等于"apt",則安裝Apache;否則跳過這個(gè)任務(wù)。
除了使用任務(wù)級別的條件判斷,還可以使用Play級別的條件判斷來控制整個(gè)Playbook的執(zhí)行。這可以通過在Play的開始處添加when關(guān)鍵字來實(shí)現(xiàn),例如:
- 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在這個(gè)例子中,deploy_web_app變量的值為True時(shí),才會(huì)執(zhí)行任務(wù)Install Dependencies。如果deploy_web_app變量的值為False,則跳過整個(gè)Playbook的執(zhí)行。
2.5循環(huán)
在Playbook中,可以使用循環(huán)結(jié)構(gòu)來遍歷列表或其他可迭代對象,并對每個(gè)迭代項(xiàng)執(zhí)行相同的任務(wù)。這可以使用Ansible的with_*系列模塊來實(shí)現(xiàn)。
以下是一些常見的循環(huán)結(jié)構(gòu)的示例:
2.5.1 使用with_items模塊來遍歷列表
tasks:
- name: Install packages
apt:
name: "{{ item }}"
state: present
with_items:
- python3
- python3-pip
- git在這個(gè)例子中,將依次安裝python3、python3-pip和git。
03、Playbook 組織
3.1Inclusions
在Playbook的組織中,include和import兩個(gè)指令都可以用來將其他的yaml文件(也就是Tasks文件)包含到當(dāng)前的Playbook中。
它們的區(qū)別在于,當(dāng)主Playbook執(zhí)行到include指令時(shí),它將處理包含的文件中的所有任務(wù),并且在處理完之后繼續(xù)主Playbook的執(zhí)行。而當(dāng)主Playbook執(zhí)行到import指令時(shí),它只會(huì)處理被導(dǎo)入的文件中的變量定義,而不會(huì)處理任務(wù),任務(wù)只有在需要的時(shí)候才會(huì)被引入執(zhí)行。
下面是一個(gè)使用include指令包含其他文件的例子:
- hosts: webservers
tasks:
- name: Include web tasks
include: web-tasks.yml在這個(gè)例子中,主Playbook從web-tasks.yml文件中導(dǎo)入任務(wù),并在執(zhí)行完后繼續(xù)執(zhí)行余下的任務(wù)。
下面是一個(gè)使用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在這個(gè)例子中,在主Playbook中使用import_vars指令來導(dǎo)入變量定義,然后在每個(gè)任務(wù)中都可以使用這些變量。然后我們使用include指令從app-tasks.yml文件中包含任務(wù),這些任務(wù)可以使用在vars.yml文件中定義的變量。這種方式可以在需要時(shí)懶加載任務(wù),提高性能。
需要注意的是,在被引入的文件中,不能再次使用- hosts:指令定義新的主機(jī)組,因?yàn)锳nsible只允許在主Playbook中定義主機(jī)組。被引入的文件只包含任務(wù),任務(wù)必須使用被定義的主機(jī)組來指定目標(biāo)主機(jī)。
3.2Roles
Ansible的Roles是一種組織Playbook的方式,它將Playbook和相關(guān)的變量、模板和其他資源打包在一起,并且可以輕松地在Playbook中重用和分享。一個(gè)Role通常適用于一種操作或功能,比如安裝和配置一個(gè)應(yīng)用程序、部署Web服務(wù)、安裝軟件包等等。
一個(gè)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:默認(rèn)變量定義文件。
- files:包含角色使用的文件。
- handlers/main.yml:Role的處理程序。
- meta/main.yml:Role的元數(shù)據(jù),例如角色名稱、作者、依賴等。
- tasks/main.yml:包含Role組成部分的主要任務(wù)。
- templates:包含角色使用的Jinja2模板。
- tests:Role的測試腳本。
- vars/main.yml:包含Role的變量。
要使用Role,需要在Playbook中定義roles擴(kuò)展,例如:
- hosts: webservers
roles:
- my-role這將運(yùn)行my-role目錄中包含的所有任務(wù)。
通過使用Role,可以更好地組織和重復(fù)使用代碼,并提高代碼的可讀性和可維護(hù)性。它還可以幫助您在Ansible社區(qū)中分享自己的工作,或從其他用戶那里獲得高質(zhì)量的Roles。
3.3引用/定義變量
在Playbook中,可以使用vars關(guān)鍵字來定義變量。例如:
vars:
my_var: "Hello World"這將定義一個(gè)名為my_var的變量,其值為字符串"Hello World"。
要在Playbook中訪問這個(gè)變量,可以使用{{ my_var }}語法。例如:
tasks:
- name: Print Message
debug:
msg: "{{ my_var }}"除了在vars中定義變量,還可以通過set_fact模塊來動(dòng)態(tài)設(shè)置變量。例如:
tasks:
- name: SetDynamic Variable
set_fact:
my_var: "{{ inventory_hostname }} is awesome"3.4使用插件和模板
Ansible提供了插件和模板的功能,使得在Playbook中使用動(dòng)態(tài)內(nèi)容變得更加簡單和方便。
插件是一種可以擴(kuò)展和定制Ansible功能的機(jī)制,可以在Playbook中調(diào)用和使用。常見的插件包括Action、Lookup、Filter、Callback等。使用插件和模板可以使Playbook更加具有可讀性和可維護(hù)性,使得動(dòng)態(tài)內(nèi)容的生成更加靈活和方便。
04、服務(wù)安裝與主機(jī)管理
4.1安裝服務(wù)器依賴
Playbook是Ansible的核心組件之一,用于定義和執(zhí)行一系列任務(wù)。在使用Playbook之前,需要確保服務(wù)器上已經(jīng)安裝了Ansible和相關(guān)的依賴項(xiàng)。以下是安裝服務(wù)器依賴的步驟:
4.1.1 安裝Python3及其相關(guān)依賴項(xiàng)
sudo apt update
sudo apt-get install -y python3 python3-pip python3-dev build-essential libssl-dev libffi-dev4.1.2 安裝Ansible
sudo apt-add-repository ppa:ansible/ansible
sudo apt update
sudo apt-get install -y ansible4.1.3 (可選)安裝 git
sudo apt update
sudo apt-get install -y git4.1.4 檢查Ansible是否安裝
ansible --version這樣,您的服務(wù)器就已經(jīng)安裝了所需的依賴項(xiàng)以及Ansible。如果您計(jì)劃在多臺服務(wù)器上使用Ansible,則需要在每臺服務(wù)器上重復(fù)這些步驟。
4.2配置遠(yuǎn)程服務(wù)器
在使用Playbook配置遠(yuǎn)程服務(wù)器之前,需要確保Ansible已經(jīng)正確安裝在本地機(jī)器上。然后,您需要做以下幾個(gè)步驟:
4.2.1 創(chuàng)建inventory文件
創(chuàng)建新的inventory文件,用于定義您要配置的遠(yuǎn)程服務(wù)器的IP地址或域名。例如,您可以創(chuàng)建一個(gè)名為inventory的文件,并包含以下內(nèi)容:
[webservers]
192.168.1.100
192.168.1.101
[dbservers]
192.168.1.102在此示例中,我們定義了兩個(gè)組,webservers和dbservers,并列出了它們中每個(gè)服務(wù)器的IP地址。
4.2.2 編寫Playbook
編寫一個(gè)Playbook,用于在遠(yuǎn)程服務(wù)器上執(zhí)行特定的任務(wù)。例如,您可以創(chuàng)建一個(gè)名為web.yml的Playbook,并包含以下內(nèi)容:
- 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在這個(gè)Playbook示例中,我們定義了一個(gè)名為Install and start Nginx的任務(wù),它會(huì)在webservers組中的服務(wù)器上啟動(dòng)Nginx服務(wù)器。
4.2.3 運(yùn)行Playbook
運(yùn)行Playbook,在遠(yuǎn)程服務(wù)器上執(zhí)行配置任務(wù)。例如,要在遠(yuǎn)程服務(wù)器上運(yùn)行示例中的web.yml Playbook,可以使用以下命令:
ansible-playbook -i inventory web.yml
在執(zhí)行此命令后,Ansible將使用inventory文件中定義的遠(yuǎn)程服務(wù)器的IP地址,并執(zhí)行web.yml Playbook中定義的任務(wù)。
這是一個(gè)基本的Playbook配置遠(yuǎn)程服務(wù)器的示例。需要根據(jù)具體的場景和任務(wù)需求來進(jìn)行個(gè)性化配置和修改。
4.3部署應(yīng)用程序
Playbook部署應(yīng)用程序一般步驟:
1、準(zhǔn)備應(yīng)用程序的部署包。這通常是一個(gè).tar.gz或.zip文件,包含應(yīng)用程序代碼、依賴項(xiàng)和其他必要文件。
2、在目標(biāo)主機(jī)上安裝所需的依賴項(xiàng)和軟件包。例如,在部署Python應(yīng)用程序時(shí),需要安裝Python解釋器、pip和其他依賴項(xiàng)。
3、創(chuàng)建一個(gè)目錄用于應(yīng)用程序的部署。這通常是在目標(biāo)主機(jī)上的一個(gè)新目錄,例如/home/user/myapp。
4、上傳應(yīng)用程序部署包到目標(biāo)主機(jī)并解壓縮。您可以使用copy模塊將部署包部署到目標(biāo)主機(jī)上。
5、配置應(yīng)用程序的運(yùn)行環(huán)境。例如,在部署Flask應(yīng)用程序時(shí),需要設(shè)置環(huán)境變量、安裝必要的Python包等。
6、配置Web服務(wù)器以偵聽?wèi)?yīng)用程序的請求。例如,您可以使用Nginx或Apache等Web服務(wù)器來代理應(yīng)用程序請求。05、常用模塊的 playbook 語法
- file模塊:可以管理文件系統(tǒng)中的文件和目錄。下面是該模塊的常用參數(shù):
- copy模塊:可以將本地文件復(fù)制到遠(yuǎn)程服務(wù)器上。
- unarchive模塊:Ansible 中用于將壓縮文件解壓縮的模塊。
- apt模塊:可以在Ubuntu或Debian系統(tǒng)上安裝、升級、刪除軟件包。
- service模塊:可以在系統(tǒng)上管理服務(wù)。
- user模塊:可以管理系統(tǒng)用戶。
- shell模塊:可以在遠(yuǎn)程服務(wù)器上運(yùn)行基于命令行的任務(wù)。該模塊只能運(yùn)行命令,不能使用管道、重定向和通配符。
- script模塊:可以將本地腳本或可執(zhí)行文件上傳到遠(yuǎn)程服務(wù)器并在遠(yuǎn)程服務(wù)器上運(yùn)行。該模塊適用于運(yùn)行復(fù)雜的命令和復(fù)雜的腳本。
- template模塊:可以將在Ansible中定義的Jinja2模板應(yīng)用于遠(yuǎn)程服務(wù)器上的文件。在應(yīng)用模板時(shí),您可以使用變量來一次生成多個(gè)文件的不同版本。
- lineinfile模塊:可以從文件中添加、修改或刪除單行文本。該模塊可用于修改文件中的配置文件或語言文件,或添加新行。
- blockinfile模塊:可以在遠(yuǎn)程服務(wù)器文件中添加、修改或刪除代碼塊。該模塊可以替代lineinfile模塊,以單個(gè)塊更新文件。
- debug模塊:可以輸出調(diào)試信息。該模塊在編寫Playbooks時(shí)非常有用,因?yàn)榭梢詸z查任務(wù)的變量和結(jié)果。
06、Ansible部署Pulsar集群運(yùn)維實(shí)戰(zhàn)
6.1部署zookeeper集群
6.1.1 定義host文件
host 文件指定了要在哪些主機(jī)上執(zhí)行任務(wù)。在 playbook 中,可以將 hosts 指定為一個(gè)變量,也可以通過 -i 參數(shù)指定一個(gè)主機(jī)清單文件,該文件包含要操作的主機(jī)列表。
[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=56.1.2 定義變量
group_vars 目錄用于存放針對不同主機(jī)組的變量文件,其中 all 文件是一種特殊的變量文件,它包含了全局的變量定義,將適用于所有主機(jī)組。路徑結(jié)構(gòu)如下:
group_vars/
├── all在all文件中,我們可以定義安裝路徑、JDK版本為、zookeeper版本以及zookeeper相關(guān)的配置信息。比如:
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:檢查端口
判斷配置的端口是否被占用,如果被占用,則不能執(zhí)行后續(xù)的步驟。
目錄結(jié)構(gòu)如下:
check_port/
├── tasks/
│ └── main.yml
main.yml#循環(huán)檢查端口是否是停用狀態(tài)
- 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:分發(fā)安裝包
目錄結(jié)構(gòu)如下:
dispatch_zk/
├── files/
│ └── zookeeper-3.6.3.tar.gz
├── tasks/
│ └── main.ymlfiles:放zookeeper安裝包文件。
main.yml
#分發(fā)zk安裝包并解壓到/tmp路徑下
- name: dispatch_zk
unarchive:
src: "{{zk_tgz}}"
dest: "/tmp"
mode: 755
owner: root
group: root③ config_zk:配置zookeeper
目錄結(jié)構(gòu)如下:
config_zk/
├── tasks/
│ └── main.yml
├── templates/
│ └── zoo.cfgmain.yml
#zoo.cfg模板文件應(yīng)用到指定的路徑下
- name: zoo.cfg
template:
src: zoo.cfg
dest: "{{ app_home }}/zk-{{ cluster_name }}/conf"
#創(chuàng)建zoo_log_dir目錄
- name: mkdir forlog
shell: mkdir -p "{{zoo_log_dir}}"
#創(chuàng)建zk數(shù)據(jù)目錄
- name: mkdir for dataDir
shell: mkdir -p "{{dataDir}}"
#創(chuàng)建zk日志目錄
- name: mkdir for dataLogDir
shell: mkdir -p "{{dataLogDir}}"
#myid文件中輸入每臺主機(jī)的編號
- name: myid file
shell: echo "{{myid}}" > {{dataDir}}/myidzoo.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服務(wù)
目錄結(jié)構(gòu)如下:
deploy_zk/
├── files/
│ └── env.sh
│ └── jdk1.8.0_192.tar.gz
├── tasks/
│ └── main.ymlenv.sh:jdk環(huán)境變量配置
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:$CLASSPATHmain.yml
#創(chuàng)建/opt/bigdata/inst目錄
- name: mkdir for inst_home
shell: mkdir -p {{ inst_home }}
#創(chuàng)建/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
#當(dāng)zk_dir存在時(shí),將/tmp路徑安裝包移到指定目錄并重命名
- name: rename zookeeper dir
command: mv /tmp/{{zk_inst_home}} {{ inst_home }}/{{zk_inst_home}}-{{cluster_name}}
when: zk_dir.stat.exists == False
#創(chuàng)建zk集群軟連接
- name: soft link
file:
path: "{{ app_home }}/zk-{{ cluster_name }}"
src: "{{ inst_home }}/{{ zk_inst_home }}-{{ cluster_name }}"
state: link
#分發(fā)并解壓jdk安裝包
- name: deploy jdk
unarchive:
src: "{{ jdk_tgz }}"
dest: "{{ inst_home }}"
#創(chuàng)建jdk軟連接
- name: create soft link for jdk
file:
path: "{{ app_home }}/{{ jdk_app_home }}"
src: "{{ inst_home }}/{{ jdk_inst_home }}"
state: link
#運(yùn)行jdk環(huán)境變量,使其生效
- name: env
script: env.sh⑤ start_zk:啟動(dòng)zookeeper服務(wù)
目錄結(jié)構(gòu)如下:
start_zk/
├── tasks/
│ └── main.ymlmain.yml
#啟動(dòng)zk服務(wù)
- name: start zookeeper
shell: cd {{ app_home }}/zk-{{cluster_name}}; sh bin/zkServer.sh start6.1.4 編輯任務(wù)執(zhí)行和啟動(dòng)腳本
zookeeper.yml:任務(wù)執(zhí)行腳本
---
- 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_zk6.1.5 部署并啟動(dòng)zookeeper服務(wù)
# 部署并啟動(dòng)zookeeper服務(wù)
ansible-playbook -i hosts-clusterName zookeeper.yml
#只檢查端口和分發(fā)安裝包
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.56.2.2 定義全局變量
group_vars 目錄用于存放針對不同主機(jī)組的變量文件,其中 all 文件是一種特殊的變量文件,它包含了全局的變量定義,將適用于所有主機(jī)組。路徑結(jié)構(gòu)如下:
group_vars/
├── allall文件內(nèi)容中定義變量信息,如下:
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:66506.2.3 編輯roles模塊
① dispatch_pulsar:分發(fā)安裝包
目錄結(jié)構(gòu)如下:
dispatch_pulsar/
├── files/
│ └── apache-pulsar-2.9.2-1.3-bin.tar.gz
├── tasks/
│ └── main.ymlmain.yml
#創(chuàng)建inst_home定義的目錄
- name: mkdir_inst_home
file:
path: "{{ inst_home }}"
state: directory
#創(chuàng)建app_home定義的目錄
- name: mkdir_app_home
file:
path: "{{ app_home }}"
state: directory
#分發(fā)并解壓pulsar安裝包到指定目錄
- name: dispatch_packages
unarchive:
src: "{{ pulsar_tgz }}"
dest: "{{ inst_home }}"
#創(chuàng)建pulsar軟連接
- name: soft_link
file:
path: "{{ app_home }}/pulsar"
src: "{{ inst_home }}/{{ pulsar_inst_home }}"
state: link② check_nar:校驗(yàn)分層存儲(chǔ)和kop擴(kuò)展的依賴包
目錄結(jié)構(gòu)如下:
check_nar/
├── tasks/
│ └── main.ymlmain.yml
#匹配指定路徑protocols和offloaders下是否有nar后綴的文件
- name: check nar
find:
paths: "{{ app_home }}/pulsar/{{ item }}/"
patterns: "*.nar"
register: result
with_items:
- "offloaders"
- "protocols"
#設(shè)置文件匹配的結(jié)果(大于0表示文件存在)
- name: set nar_files_exist variable
set_fact:
nar_files_exist_{{item.item}}: "{{ item.matched > 0 }}"
with_items: "{{ result.results }}"
#如果文件不存在,進(jìn)行提示
- 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
目錄結(jié)構(gòu)如下:
config_pulsar/
├── tasks/
│ └── main.yml
├── templates/
│ └── bkenv.sh
│ └── pulsar_env.shmain.yml
#匹配broker.conf中的advertisedAddress值并設(shè)置為遠(yuǎn)程主機(jī)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值,設(shè)置為主機(jī)ip地址
- name: config_bk_advertisedAddress
lineinfile:
path: "{{ pulsar_conf }}/bookkeeper.conf"
regexp: "^advertisedAddress="
line: "advertisedAddress={{ inventory_hostname }}"
#將模板文件bkenv.sh應(yīng)用到pulsar的配置文件中
- name: config_bkenv.sh
template:
src: bkenv.sh
dest: "{{ pulsar_conf }}"
#將模板文件pulsar_env.sh應(yīng)用到pulsar的配置文件中
- name: config_pulsar_env.sh
template:
src: pulsar_env.sh
dest: "{{ pulsar_conf }}"④ create_data_dir:創(chuàng)建存儲(chǔ)數(shù)據(jù)的目錄
目錄結(jié)構(gòu)如下:
create_data_dir/
├── tasks/
│ └── main.ymlmain.yml
#循環(huán)創(chuàng)建with_items中的數(shù)據(jù)目錄
- 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:配置安全秘鑰
目錄結(jié)構(gòu)如下:
config_secret_key/
├── files/
│ └── admin-secret.key
├── tasks/
│ └── main.ymlmain.yml
#創(chuàng)建存放安全秘鑰的目錄
- name: create_secret_key_dir
file:
path: "{{ secret_key_dir }}"
owner: root
group: root
state: directory
#將安裝秘鑰文件分發(fā)到指定的路徑下
- name: dispatch_secret.key
copy:
src: admin-secret.key
dest: "{{ secret_key_dir }}"⑥ init_meta:初始化集群元數(shù)據(jù)
目錄結(jié)構(gòu)如下:
init_meta/
├── tasks/
│ └── main.yml
├── templates/
│ └── init_meta.shmain.yml
#應(yīng)用init_meta.sh腳本到遠(yuǎn)程主機(jī)
- name: scp init_meta.sh
template:
src: init_meta.sh
dest: "{{ app_home }}/pulsar"
#執(zhí)行初始化腳本文件
- name: init_meta
shell: nohup sh {{ app_home }}/pulsar/init_meta.sh > {{ app_home }}/pulsar/init.log2>&1 &
#等待20s查詢初始化日志中是否出現(xiàn)初始化成功的日志
- name: wait20s
wait_for:
path: "{{ app_home }}/pulsar/init.log"
search_regex: "Cluster metadata for '{{ clusterName }}' setup correctly"
delay: 20
#殺掉集群元數(shù)據(jù)初始化進(jìn)程
- name: kill metadata
shell: ps -efww|grep PulsarClusterMetadataSetup|grep -v grep|cut -c 9-15|xargs kill -9
init_meta.sh:初始化集群元數(shù)據(jù)腳本
{{ 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:啟動(dòng)broker和bookkeeper服務(wù)
目錄結(jié)構(gòu)如下:
start_service/
├── tasks/
│ └── main.ymlmain.yml
#啟動(dòng)遠(yuǎn)程主機(jī)bookkeeper服務(wù)
- name: start bookie
shell: sh {{ app_home }}/pulsar/bin/pulsar-daemon start bookie
#啟動(dòng)遠(yuǎn)程主機(jī)broker服務(wù)
- name: start broker
shell: sh {{ app_home }}/pulsar/bin/pulsar-daemon start broker6.2.4 編輯任務(wù)執(zhí)行腳本
pulsar.yml:任務(wù)執(zhí)行腳本
---
#分發(fā)pulsar安裝包
- name: dispatch_pulsar
hosts: pulsar
remote_user: root
become: yes
become_flags: '-i'
roles:
- dispatch_pulsar
tags: dispatch_pulsar
#檢查安裝包中kop和分層存儲(chǔ)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
#創(chuàng)建磁盤數(shù)據(jù)目錄
- 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
#啟動(dòng)broker和bookkeeper服務(wù)
- name: start_service
hosts: pulsar
remote_user: root
become: yes
become_flags: '-i'
roles:
- start_service
tags: start_service6.2.5 執(zhí)行playbook任務(wù)
#執(zhí)行所有pulsar.yml中的任務(wù)
ansible-playbook -i hosts pulsar.yml
#只執(zhí)行pulsar.yml中標(biāo)簽為dispatch_pulsar,check_nar的任務(wù)
ansible-playbook -i hosts pulsar.yml --tags "dispatch_pulsar,check_nar"07、Playbooks運(yùn)維Pulsar集群總結(jié)
7.1Pulsar運(yùn)維實(shí)踐總結(jié)
Pulsar作為新一代云原生架構(gòu)的分布式消息中間件,目前再超大流量規(guī)模、海量分區(qū)、超高QPS等場景下缺乏長時(shí)間的穩(wěn)定性驗(yàn)證,在極端場景下還存在較多穩(wěn)定性風(fēng)險(xiǎn),當(dāng)前社區(qū)版本迭代活躍;Pulsar集群在vivo內(nèi)部日均處理消息達(dá)萬億+,需要不斷的合并社區(qū)issue及灰度升級高版本。運(yùn)維事項(xiàng)較多、投入的運(yùn)維人力較大。vivo分布式消息中間件團(tuán)隊(duì)通過借助Ansible的模塊化、任務(wù)依賴、配置check、批量腳本執(zhí)行等能力實(shí)現(xiàn)Pulsar集群從zk集群搭建、Pulsar安裝包編譯、自動(dòng)化配置填充、批量分發(fā)部署、服務(wù)啟動(dòng)的一鍵運(yùn)維部署能力。大大縮減了Pulsar集群的運(yùn)維人力投入,Pulsar組件存算分離的架構(gòu)設(shè)計(jì)優(yōu)秀,但部署配置項(xiàng)非常繁雜,通過自動(dòng)化配置填充可有效規(guī)避配置信息不一致、版本不一致等高頻錯(cuò)誤。
7.2playbooks服務(wù)部署步驟
根據(jù)以上實(shí)戰(zhàn)經(jīng)驗(yàn),我們可以總結(jié)出部署某個(gè)服務(wù)時(shí)編寫playbooks腳本的一般步驟如下:

Ansible更多運(yùn)維實(shí)踐可參考:https://github.com/ansible/ansible-examples






























