Linux 9 自动化部署 Zookeeper 集群

Apache ZooKeeper 是一个开源服务,可以实现高度可靠的分布式协调。它常被应用于分布式系统中,管理配置信息、命名服务、分布式同步、法定人数和状态。此外,一些分布式系统依靠 ZooKeeper 来实现共识、leader 选举和组管理。

本文将通过以下主题,讨论在 Rocky Linux 9 上安装和配置满足生产环境高可用性及弹性要求的 Zookeeper 集群:

35.1 部署环境介绍

完成本教程需要四个节点:一个节点作为 Ansible 控制节点,三个节点部署 Zookeeper 集群。

35.1.1 节点信息

四个节点分别为 2 核 CPU、2 GB 内存的虚拟机。

ZooKeeper 将数据保存在内存中以实现高吞吐量和低延迟,因此生产环境要提供更高内存的主机,建议 8 GB 内存。低内存可能产生 JVM 交换,导致 ZooKeeper 服务延迟。高延迟可能会导致客户端会话超时等问题,影响系统稳定。

Ansilbe 节点信息:

# IP:10.211.55.18
# 主机名:automate-host.aiops.red
# 系统版本:Rocky Linux release 9.1

Ansible Inventory hosts:

[zookeeper]
zk1.server.aiops.red
zk2.server.aiops.red
zk3.server.aiops.red


[prometheus]
prometheus.server.aiops.red

Zookeeper 节点信息:

# zookeeper1
# IP:10.211.55.31
# 主机名:zk1.server.aiops.red
# 系统版本:Rocky Linux release 9.1


# zookeeper2
# IP:10.211.55.32
# 主机名:zk2.server.aiops.red
# 系统版本:Rocky Linux release 9.1


# zookeeper3
# IP:10.211.55.33
# 主机名:zk3.server.aiops.red
# 系统版本:Rocky Linux release 9.1

35.1.2 节点要求

为使安装过程顺利进行,节点应满足以下要求。

35.1.2.1 时钟同步

Zookeeper 集群节点时钟须保持同步。

要自动化实现时钟同步,可以参考 “Linux 9 自动化部署 NTP 服务”。

35.1.2.2 主机名解析

Ansible 控制节点能够解析 Zookeeper 节点的主机名,通过主机名访问 Zookeeper 节点。

要实现主机名称解析,可以在 Ansible 控制节点的 /etc/hosts 文件中指定 Zookeeper 节点的 IP、主机名条目,或者参考 “Linux 9 自动化部署 DNS 服务” 一文配置 DNS 服务。

35.1.2.3 账号权限

Ansible 控制节点可以免密登录 Zookeeper 节点,并能够免密执行 sudo。可以参考 “Linux 9 自动化部署 NTP 服务” 中的 “部署环境要求” 一节实现。

35.1.3 扩展知识

在本文的最后部分,演示了通过 Prometheus 自动化监控 Zookeeper 集群。如要完成此部分,需要准备好 Prometheus 监控系统。有关 Prometheus 监控系统的自动化部署,可以参考 ”Linux 9 自动化配置 Prometheus 监控系统“。

在满足以上要求后,开始 Zookeeper 集群的部署。

35.2 下载 Zookeeper 包

Linux 的 dnf 仓库没有提供 Zookeeper 的安装包,因此需要从 Apache Zookeeper 官网下载。应将下载包和安装配置 Zookeeper 分成两个 Ansible Role 实现。因为包下载一次即可,无需多次执行。

35.2.1 创建下载 Zookeeper 的角色

执行以下命令,创建 download_zookeeper Role:

ansible-galaxy role init --init-path ~/roles download_zookeeper
cd ~/roles/download_zookeeper

图 35.1 创建下载 Zookeeper 的角色

tasks/main.yml 文件中,编辑下载 Zookeeper 的任务:

---
# tasks file for download_zookeeper
- name: create download directory task
  ansible.builtin.file:
    path: "{{ download_path }}"
    state: directory


- name: download and unarchive tarball task
  ansible.builtin.unarchive:
    src: "{{ download_url }}"
    dest: "{{ download_path }}"
    remote_src: true
    extra_opts:
      - --strip-components=1

35.2.2 创建下载 Zookeeper 的 Playbook

~/playbooks/ 目录下创建 deploy_zookeeper/ 目录:

mkdir ~/playbooks/deploy_zookeeper
cd ~/playbooks/deploy_zookeeper

图 35.2 创建下载 Zookeeper 的 Playbook

创建 download_zookeeper.yaml Playbook 文件,内容如下:

---
- name: download zookeeper tarball play
  hosts: localhost
  become: false
  gather_facts: false
  vars_files:
    - variables.yaml
  roles:
    - role: download_zookeeper
      tags: download_zookeeper
...

创建变量文件:

mkdir vars
touch vars/variables.yaml

vars/variables.yaml 中添加需要的变量:

download_path: ~/software/zookeeper_tools/zookeeper
download_url: https://downloads.apache.org/zookeeper/stable/apache-zookeeper-3.6.3-bin.tar.gz

35.2.3 下载安装包

执行 download_zookeeper.yaml Playbook,下载安装包:

ansible-playbook download_zookeeper.yaml

图 35.3 下载 Zookeeper 安装包

使用 tree 命令查看下载文件的目录结构:

tree -L 1 ~/software/zookeeper_tools/zookeeper

图 35.4 Zookeeper 目录结构

下载的版本为当前稳定版,可以在 https://downloads.apache.org/zookeeper/stable/ 查看。

35.3 部署 JDK

Zookeeper 服务依赖 Java 环境才能运行,因此需要在运行 Zookeeper 的节点上部署 JDK。

有关 JDK 自动化部署的详细介绍,可以参考 ”Linux 9 自动化部署 JDK“。在 JDK 部署完成后,继续下面的步骤。

35.4 部署 Zookeeper 集群

在节点和安装包准备好后,本节讨论 Zookeeper 集群的安装。

35.4.1 设置 Local Facts 变量

Zookeeper 集群依赖 SID 进行快速选举,SID 的值必须写入数据目录的 myid 文件中。通过定义 Local Facts 变量,为节点设置 SID 值。

创建三个 Facts 文件:

for sid in $(seq 1 3); do echo -e "[var]
sid=${sid}" > zk${sid}.fact; done

图 35.5 创建 Local Facts 变量文件

为 Zookeeper 节点创建 /etc/ansible/facts.d/ 目录:

ansible zookeeper -m file -a "path=/etc/ansible/facts.d state=directory" -b

图 35.6 创建 facts.d 目录

将三个文件拷贝到对应节点的 /etc/ansible/facts.d/

ansible zk1.server.aiops.red -m copy -a "src=zk1.fact dest=/etc/ansible/facts.d/zk.fact" -b
ansible zk2.server.aiops.red -m copy -a "src=zk2.fact dest=/etc/ansible/facts.d/zk.fact" -b
ansible zk3.server.aiops.red -m copy -a "src=zk3.fact dest=/etc/ansible/facts.d/zk.fact" -b

图 35.7 拷贝 fact 文件到对应的托管节点

35.4.2 创建部署 Zookeeper 的角色

使用以下命令创建 Role:

ansible-galaxy role init --init-path ~/roles deploy_zookeeper
cd ~/roles/deploy_zookeeper

图 35.8 创建部署 Zookeeper 的角色

35.4.2.1 创建用户

使用独立用户运行服务,可以提高隔离性和安全性。因此,第一个任务是创建用于启动 Zookeeper 服务的用户。

编辑 tasks/main.yml 文件,添加创建用户的任务:

- name: "create {{ user_name }} user task"
  ansible.builtin.user:
    name: "{{ user_name }}"
    home: "{{ home_dir }}"
    shell: /sbin/nologin

需要为任务定义两个变量,user_name 表示创建的用户名;home_dir 用户家目录。Zookeeper 文件将被安装在 home_dir 目录。

变量可以在 defaults/main.yml 文件或 vars/main.yml 文件中定义,前者定义的变量,可以被 Playbook 中的变量覆盖。

35.4.2.2 创建数据目录

Zookeeper 将所有配置、状态数据持久化在磁盘上,这样即使重启后,服务仍能正常工作。

tasks/main.yml 中编写第二个任务,为 Zookeeper 创建数据目录:

- name: create data directory task
  ansible.builtin.file:
    path: "{{ home_dir }}/data/zookeeper"
    state: directory
    owner: "{{ user_name }}"
    group: "{{ user_name }}"
    mode: 0755

35.4.2.3 拷贝二进制文件

将下载好的 Zookeeper 安装文件,拷贝到 home_dir 下。编辑 tasks/main.yml 文件,添加以下两个任务:

- name: copy the installation file task
  ansible.posix.synchronize:
    src: "{{ download_path }}"
    dest: "{{ home_dir }}"


- name: set the file owner task
  ansible.builtin.shell: "chown {{ user_name }}.{{ user_name }} {{ home_dir }} -R"

ansible.posix.synchronize 模块在此处以 root 用户同步文件。因此,在文件同步结束后,使用 chown 修改目录的属主及属组。

35.4.2.4 配置文件模板

Zookeeper 集群中的节点应为奇数个,这样就能形成有效的法定人数。法定人数是指在提交事务前,需要同意的最小节点数。法定人数为奇数,才能形成多数。偶数个节点可能导致平局,这将意味着节点不会达成多数或共识。

templates/ 目录下,创建 zoo.cfg.j2 文件,内容如下:

tickTime=2000
dataDir={{ home_dir }}/data/zookeeper
clientPort={{ client_port }}
maxClientCnxns=60
initLimit=10
syncLimit=5
{% for host in groups['zookeeper'] %}
server.{{ hostvars[host].ansible_local.zk.var['sid'] }}={{ host }}:{{ follower_port }}:{{ leader_port }}
{% endfor %}

模板配置文件解析:

sid 的值来自于之前定义的 Local Facts 变量,通过 hostvars[host] 从对应主机获取 sid。

准备好了模板文件,接下来在 tasks/main.yml 中定义将模板配置文件渲染为 Zookeeper 配置文件的任务:

- name: render configuration file task
  ansible.builtin.template:
    src: zoo.cfg.j2
    dest: "{{ home_dir }}/zookeeper/conf/zoo.cfg"
  notify: restart zookeeper service handler

notify: restart zookeeper service handler 定义了当配置文件有变化时,执行 ”restart zookeeper service handler“,重启 Zookeeper 服务。

35.4.2.5 myid 模板

为 Zookeeper 生成 myid 文件,该文件的内容应与 sid 一致。创建 templates/myid.j2 文件,内容如下:

{{ ansible_local.zk.var['sid'] }}

tasks/main.yml 中定义将模板渲染为 myid 文件的任务:

- name: render myid file task
  ansible.builtin.template:
    src: myid.j2
    dest: "{{ home_dir }}/data/zookeeper/myid"

myid 文件应存放在 Zookeeper 的 dataDir 目录下。

35.4.2.6 单元文件模板

创建启动 Zookeeper 服务的单元文件模板,templates/zookeeper.service.j2 内容如下:

[Unit]
Description=Zookeeper Daemon
Documentation=http://zookeeper.apache.org
Requires=network.target
After=network.target


[Service]    
Type=forking
WorkingDirectory={{ home_dir }}/zookeeper
User={{ user_name }}
Group={{ user_name }}
ExecStart={{ home_dir }}/zookeeper/bin/zkServer.sh start {{ home_dir }}/zookeeper/conf/zoo.cfg
ExecStop={{ home_dir }}/zookeeper/bin/zkServer.sh stop {{ home_dir }}/zookeeper/conf/zoo.cfg
ExecReload={{ home_dir }}/zookeeper/bin/zkServer.sh restart {{ home_dir }}/zookeeper/conf/zoo.cfg
TimeoutSec=30
Restart=on-failure


[Install]
WantedBy=default.target

tasks/main.yml 中定义将模板文件渲染为 systemd 单元文件的任务:

- name: render systemd unit file task
  ansible.builtin.template:
    src: zookeeper.service.j2
    dest: /usr/lib/systemd/system/zookeeper.service

35.4.2.7 设置防火墙

需要在 Firewalld 上开启 Zookeeper 服务的相关端口。编辑 tasks/main.yml 文件,添加以下任务:

- name: turn on zk ports in the firewalld task
  ansible.builtin.firewalld:
    port: "{{ item }}"
    permanent: true
    immediate: true
    state: enabled
  with_items:
    - "{{ client_port }}/tcp"
    - "{{ follower_port }}/tcp"
    - "{{ leader_port }}/tcp"

35.4.2.8 设置 JAVA_HOME

Zookeeper 在启动时,需要为其指定 JAVA_HOME 变量。编辑 tasks/main.yml 文件,添加在 zkEnv.sh 文件中设置该变量的任务:

- name: set JAVA_HOME in zkEnv.sh file task
  ansible.builtin.lineinfile:
    path: "{{ home_dir }}/zookeeper/bin/zkEnv.sh"
    line: "export JAVA_HOME=/opt/jdk19"
    insertafter: "/usr/bin/env"

JAVA_HOME 变量的值要设置为真实值,通过 “Linux 9 自动化部署 JDK” 安装的 JDK,路径为 /opt/jdk19

35.4.2.9 启动 Zookeeper 集群

安装和配置已经进行完成,可以启动 Zookeeper 服务了。编辑 tasks/main.yml 文件,添加启动任务:

- name: enable and start the zk service task
  ansible.builtin.systemd:
    name: zookeeper.service
    state: started
    enabled: true
    daemon_reload: true

在这个任务中,会重载单元文件,启动 zookeeper 服务,并把该服务设置为开机启动。

35.4.2.10 添加 Handlers

handlers/main.yml 文件中,添加名为 ”restart zookeeper service handler“ 的 Handlers:

---
# handlers file for deploy_zookeeper
- name: restart zookeeper service handler
  ansible.builtin.systemd:
    name: zookeeper.service
    state: restarted

35.4.3 创建部署 Zookeeper 的 Playbook

创建 ~/playbooks/deploy_zookeeper/deploy.yaml 文件,内容如下:

---
- name: deploy zookeeper cluster play
  hosts: zookeeper
  become: true
  gather_facts: true
  vars_files:
    - variables.yaml
  roles:
    - role: deploy_zookeeper
      tags: deploy_zookeeper
...

Playbook 文件中用到了 variables.yaml 变量文件,在该文件中添加需要的变量。

35.4.4 提供变量

vars/variables.yaml 文件内容如下:

download_path: ~/software/zookeeper_tools/zookeeper
download_url: https://downloads.apache.org/zookeeper/stable/apache-zookeeper-3.6.3-bin.tar.gz


home_dir: /opt/zk
user_name: zk


client_port: 2181
follower_port: 2888
leader_port: 3888

35.4.5 部署集群

执行 deploy.yaml Playbook 文件,部署 Zookeeper 集群:

ansible-playbook deploy.yaml

图 35.9 部署 Zookeeper 集群

35.5 验证 Zookeeper

在集群成功部署后,可以对集群进行测试。

登录 Zookeeper 集群的某个节点,执行 zkCli.sh 命令:

ssh zk1.server.aiops.red
sudo su -
/opt/zk/zookeeper/bin/zkCli.sh -server zk2.server.aiops.red:2181

执行以下命令,创建、查看、删除 znode:

# 创建
create /hello "aiops"
# 查看
get /hello
# 删除
delete /hello

图 35.10 验证 Zookeeper 集群

znodes 是 ZooKeeper 中的基本抽象,类似于文件系统上的文件和目录。ZooKeeper 将其数据维护在分层命名空间中,znodes 是该命名空间的数据寄存器。

成功测试了两个 ZooKeeper 节点之间的连接。还通过 creategetdelete znodes 学习了基本的 znode 管理。Zookeeper 集群已成功部署,可以使用 ZooKeeper 了。

35.6 自动化监控 Zookeeper

本节讨论 Zookeeper 的自动化监控。监控服务使用 Prometheus 完成,因此需要准备好 Prometheus 监控系统。可参考上一篇文章完成 Prometheus 的自动化部署。

35.6.1 启用 MetircsProvider

从 3.6.0 版本开始,Zookeeper 内置的指标系统提供了丰富的指标,方便用户对其监控。编辑 Zookeeper 模板配置文件 templates/zoo.cfg.j2,添加 metricsProvider.className=org.apache.zookeeper.metrics.prometheus.PrometheusMetricsProvider

tickTime=2000
dataDir={{ home_dir }}/data/zookeeper
clientPort={{ client_port }}
maxClientCnxns=60
initLimit=10
syncLimit=5
{% for host in groups['zookeeper'] %}
server.{{ hostvars[host].ansible_local.zk.var['sid'] }}={{ host }}:{{ follower_port }}:{{ leader_port }}
{% endfor %}
metricsProvider.className=org.apache.zookeeper.metrics.prometheus.PrometheusMetricsProvider

该配置启用了 Prometheus MetricsProvider,Zookeeper 服务将新增对 TCP 7000 端口的监听。

编辑 Zookeeper Role tasks/main.yml,在 firewalld 任务增加 7000 端口:

- name: turn on zk ports in the firewalld task
  ansible.builtin.firewalld:
    port: "{{ item }}"
    permanent: true
    immediate: true
    state: enabled
  with_items:
    - "{{ client_port }}/tcp"
    - "{{ follower_port }}/tcp"
    - "{{ leader_port }}/tcp"
    - "7000/tcp"

再次运行 Playbook 使配置文件生效:

ansible-playbook deploy.yaml

图 35.11 再次执行 Playbook,启用 Prometheus MetricsProvider

35.6.2 Prometheus 收集 Zookeeper 指标

创建名为 zk_monitor.yaml Playbook 文件,添加以下任务向 Prometheus 的配置文件插入抓取 Zookeeper 目标的配置:

- name: zk monitor play
  hosts: prometheus
  become: true
  gather_facts: false
  tasks:
    - name: insert scrape zk block task
      ansible.builtin.blockinfile:
        path: /opt/monitor/prometheus/prometheus.yaml
        insertafter: EOF
        marker: "# add zk scrape {mark}"
        marker_begin: "begin ==>"
        marker_end: "end ==>"
        create: true
        state: present
        block: |2
            - job_name: "zk"
              static_configs:
                - targets:
            {% for host in groups["zookeeper"] %}
                - "{{ host }}:7000"
            {% endfor %}
      notify: restart prometheus service handler

ansible.builtin.blockinfile 任务将在 promethues.yaml 文件末尾添加抓取 Zookeeper 指标的配置。

有了指标,接下来配置 Dashboard。

35.6.3 配置 Zookeeper Dashboard

zk_monitor.yaml 文件中,新增下载 Grafana Dashboard 的任务。因为 Grafana 与 Prometheus 是同一主机,因此该任务与 “insert scrape zk block task” 任务定义在同一 Play 中:

    - name: downlaod zookeeper dashboard json file task
      ansible.builtin.get_url:
        url: https://grafana.com/api/dashboards/10465/revisions/4/download
        dest: /var/lib/grafana/dashboard/zookeeper.json

35.6.4 配置报警规则

创建 files/ 目录,并在该目录下创建规则文件 zk_rule.yml,内容如下:

groups:
- name: zk-alert-example
  rules:
  - alert: ZooKeeper server is down
    expr:  up == 0
    for: 1m
    labels:
      severity: critical
    annotations:
      summary: "Instance {{ $labels.instance }} ZooKeeper server is down"
      description: "{{ $labels.instance }} of job {{$labels.job}} ZooKeeper server is down: [{{ $value }}]."


  - alert: create too many znodes
    expr: znode_count > 1000000
    for: 1m
    labels:
      severity: warning
    annotations:
      summary: "Instance {{ $labels.instance }} create too many znodes"
      description: "{{ $labels.instance }} of job {{$labels.job}} create too many znodes: [{{ $value }}]."


  - alert: create too many connections
    expr: num_alive_connections > 50 # suppose we use the default maxClientCnxns: 60
    for: 1m
    labels:
      severity: warning
    annotations:
      summary: "Instance {{ $labels.instance }} create too many connections"
      description: "{{ $labels.instance }} of job {{$labels.job}} create too many connections: [{{ $value }}]."


  - alert: znode total occupied memory is too big
    expr: approximate_data_size /1024 /1024 > 1 * 1024 # more than 1024 MB(1 GB)
    for: 1m
    labels:
      severity: warning
    annotations:
      summary: "Instance {{ $labels.instance }} znode total occupied memory is too big"
      description: "{{ $labels.instance }} of job {{$labels.job}} znode total occupied memory is too big: [{{ $value }}] MB."


  - alert: set too many watch
    expr: watch_count > 10000
    for: 1m
    labels:
      severity: warning
    annotations:
      summary: "Instance {{ $labels.instance }} set too many watch"
      description: "{{ $labels.instance }} of job {{$labels.job}} set too many watch: [{{ $value }}]."


  - alert: a leader election happens
    expr: increase(election_time_count[5m]) > 0
    for: 1m
    labels:
      severity: warning
    annotations:
      summary: "Instance {{ $labels.instance }} a leader election happens"
      description: "{{ $labels.instance }} of job {{$labels.job}} a leader election happens: [{{ $value }}]."


  - alert: open too many files
    expr: open_file_descriptor_count > 300
    for: 1m
    labels:
      severity: warning
    annotations:
      summary: "Instance {{ $labels.instance }} open too many files"
      description: "{{ $labels.instance }} of job {{$labels.job}} open too many files: [{{ $value }}]."


  - alert: fsync time is too long
    expr: rate(fsynctime_sum[1m]) > 100
    for: 1m
    labels:
      severity: warning
    annotations:
      summary: "Instance {{ $labels.instance }} fsync time is too long"
      description: "{{ $labels.instance }} of job {{$labels.job}} fsync time is too long: [{{ $value }}]."


  - alert: take snapshot time is too long
    expr: rate(snapshottime_sum[5m]) > 100
    for: 1m
    labels:
      severity: warning
    annotations:
      summary: "Instance {{ $labels.instance }} take snapshot time is too long"
      description: "{{ $labels.instance }} of job {{$labels.job}} take snapshot time is too long: [{{ $value }}]."


  - alert: avg latency is too high
    expr: avg_latency > 100
    for: 1m
    labels:
      severity: warning
    annotations:
      summary: "Instance {{ $labels.instance }} avg latency is too high"
      description: "{{ $labels.instance }} of job {{$labels.job}} avg latency is too high: [{{ $value }}]."


  - alert: JvmMemoryFillingUp
    expr: jvm_memory_bytes_used / jvm_memory_bytes_max{area="heap"} > 0.8
    for: 5m
    labels:
      severity: warning
    annotations:
      summary: "JVM memory filling up (instance {{ $labels.instance }})"
      description: "JVM memory is filling up (> 80%)
 labels: {{ $labels }}  value = {{ $value }}
"

zk_rule.yml 文件拷贝至 Prometheus 安装目录下:

    - name: copy rule file to prometheus task
      ansible.builtin.copy:
        src: zk_rule.yml
        dest: /opt/monitor/prometheus/zk_rule.yml
        owner: monitor
        group: monitor

在 Prometheus 配置文件 prometheus.yaml 插入引用 zk_rule.yml 规则文件的行:

    - name: insert line
      lineinfile:
        path: /opt/monitor/prometheus/prometheus.yaml
        insertafter: "rule_files:"
        line: "  - zk_rule.yml"
      notify: restart prometheus service handler

zk_monitor.yaml 中定义 Handlers:

  handlers:
    - name: restart prometheus service handler
      ansible.builtin.systemd:
        name: prometheus.service
        state: restarted

最后执行 zk_monitor.yaml Playbook 文件:

ansible-playbook zk_monitor.yaml

图 35.12 监控 Zookeeper

35.6.5 查看监控页面

登录 Grafana,查看 Dashboard,点击 “Zookeeper by Prometheus”,Cluster 选择 “zk”,浏览器将显示 Zookeeper 的监控页面:

图 35.13 Zookeeper 监控页面

在 Prometheus 的 rules 页面,能够看到为 Zookeeper 配置的报警规则,如下图:

图 35.14 Zookeeper 报警规则

35.7 总结

本教程演示了 Zookeeper 集群的自动化部署、简单的数据操作,以及通过 Prometheus 对 Zookeeper 集群进行监控。教程同样适用于其他基于 RPM 的 Linux 发行版。

来源:魏文第

展开阅读全文

页面更新:2024-04-30

标签:集群   法定人数   节点   变量   端口   定义   模板   文件   目录   数据

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号

Top