Stories

Detail Return Return

vivo Pulsar 萬億級消息處理實踐(4)-Ansible運維部署 - Stories Detail

作者: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

猜你喜歡

  • vivo Pulsar萬億級消息處理實踐(1)-數據發送原理解析和性能調優
  • vivo Pulsar萬億級消息處理實踐(2)-從0到1建設Pulsar指標監控鏈路
  • vivo Pulsar萬億級消息處理實踐(3)-KoP指標異常修復
user avatar zourongle Avatar tech Avatar yuxl01 Avatar yanyue404 Avatar pannideniupai Avatar explinks Avatar seazhan Avatar idiomeo Avatar danjuanfe Avatar windseek Avatar daguaisou Avatar bao_686ce718ec240 Avatar
Favorites 29 users favorite the story!
Favorites

Add a new Comments

Some HTML is okay.