工作需要搭建了个集群基础服务,记录一下搭建过程中遇到的问题。

基本信息

  • 系统:麒麟 V10 的 ARM 版本
  • 硬件:使用 QEMU 虚拟 ARM 环境
  • Ambari:使用 HiDataPlus ,里面集成了 ARM 版本的相关组件。

虚拟机搭建

根据实际情况配置好启动内存、CPU等信息,由于模拟是纯 CPU 处理,最好是性能强大一些的,减少系统过慢的影响。

虚拟机启动命令、系统等安装过程就略过了。

网络环境

虚拟机上网需要配置一个网卡,并与主机网络桥接,使网络互通。

安装 tap-windows windows 下能够创建一个虚拟网卡(openvpn-connect 或者其他能够创建虚拟网卡的软件都行),配置虚拟机的启动参数 -net tap,ifname= 使用此网卡。

有的教程是使用共享网络上网,但这样只能主机与虚拟机互相访问,其他虚拟机无法直接访问,可以使用接口转发处理,但使用太过麻烦,涉及软件自动获取 IP 的情况也会有问题。

想要将虚拟机暴露在同一网络中可以使用桥接,网上有篇图文教程写的非常清楚,参照执行即可:QEMU桥接图文教程)

问题记录:

  • windows 网桥始终显示断线
    可能是虚拟机没启动,桥接后启动虚拟机,查看网桥是否能自动检测。正常情况下桥接后主机能正常上网。
  • 虚拟机启动后网桥网络依然显示无法工作
    网桥建成后重启系统再次确认,或者检查并更新一下驱动。
  • 网桥图标不见了,但设备依然是在桥接状态
    尝试重启系统。
  • 桥接成功后导致其他机器无法上网
    使用设备将网络隔离,避免干扰。

IP 地址要分配固定 IP ,避免后续重启系统后 IP 变动导致问题。

实现效果:成功之后主机、虚拟机以及其他几台虚拟机能够互相通信。

ambari-server

HiDataPlus 公众号上有一篇教程 Ambari-2.7.6和HDP-3.3.1安装,写的很清楚按着操作基本上没什么问题。

如果安装出问题使用 ambari-server reset 重置,或者手动清理数据库。
如果已经添加了节点,需要搜索 ambari-agent 删除相关文件夹。

启动服务 ambari-server start ,成功后访问 8080 端口能看到登录界面,默认账户密码 admin

image.png

问题记录:

  • 相关路径
    日志文件:/var/log/ambari-server/ambari-server.log
    配置文件:/etc/ambari-server/conf/ambari.properties
  • 始终启动超时
    ambari-server status 查看服务是否已经启动,如果显示已经启动,但网页无法访问,说明虚拟机性能不行,启动等待时间太短。可以查看日志输出确认当前状态。
  • 启动确实失败了
    查看日志 /var/log/ambari-server/ambari-server.log 根据报错信息处理。
  • 界面侧边栏样式错乱
    没做调整,但使用 Firefox 能正常显示,Chrome 错乱,所以直接换 Firefox 用着。

HDP

依然按着教程操作,但这块问题比较多,主要是 ambari-agent 安装上。

修改系统检测文件

在安装之前先要修改一些检测文件,将 kylin 系统识别成其他系统。

/usr/lib/ambari-server/lib/ambari_commons/os_check.py
/usr/lib/ambari-agent/lib/ambari_commons/os_check.py

# Linux specific releases, caching them since they are execution invariants
_IS_ORACLE_LINUX = os.path.exists('/etc/oracle-release')
_IS_REDHAT_LINUX = os.path.exists('/etc/redhat-release')
# !! Added code
_IS_KYLIN_LINUX = os.path.exists('/etc/kylin-release')

OS_RELEASE_FILE = "/etc/os-release"

def _is_oracle_linux():
  return _IS_ORACLE_LINUX

def _is_redhat_linux():
  return _IS_REDHAT_LINUX
# !! Added code
def _is_kylin_linux():
  return _IS_KYLIN_LINUX

# ...

    else:
      # linux distribution
      PYTHON_VER = sys.version_info[0] * 10 + sys.version_info[1]

      if PYTHON_VER < 26:
        distribution = platform.dist()
      elif _is_redhat_linux():
        distribution = platform.dist()
        # !! Added code
      elif _is_kylin_linux():
        distribution = ("redhat", "7", "core")
      else:
        distribution = platform.linux_distribution()

# ...

    if version :
      if operatingSystem.startswith('kylin linux'):
        #print(version)
        #kylin v10
        if version == '10':
          version = '7'  # !! 8 changed to 7

修改完成后执行 ambari-server restart 让变更生效。

HDP repo源地址就使用前面本地配置的源地址,根据节点注册日志的信息处理问题。

节点添加成功后,进入选择服务组件,第一次安装少选一些组件,可以等成功后再分次安装。

之后就能进入集群管理页面了。

image.png

问题记录:

  • chmod: 无法访问 '/var/lib/ambari-agent/data': 没有那个文件或目录
    问题不大,会自动创建,也可以手动创建一个。
  • 始终安装失败但没什么具体报错原因
    登录目标机器卸载 ambari-agent
  • 已经远程安装过一次,修改系统检测文件没效果
    执行过一次后 ambari-server 端的文件会复制到节点机器里。
    清空 /var/lib/ambari-agent/tmp/ 目录下的文件。
    /usr/lib/ambari-agent/lib/ambari_commons/ 目录下的 os_check.py 也要确保是修改后的。
  • Cluster primary/cluster OS family is redhat8 and local/current OS family is redhat7
    怎么改都没效果的话,保证远程机器已经是识别成修改后的系统了,可以直接跳过检测逻辑。
    修改 /usr/lib/ambari-server/lib/ambari_server/os_check_type.py 文件,将

    if current_os == cluster_os:
    sys.exit(0)
    else:
    raise Exception("Local OS is not compatible with cluster primary OS family. Please perform manual bootstrap on this host.")

    屏蔽了,新增一句 sys.exit(0),跳过检测。
    数据库 hostshost_attributesos_type 两个字段可以确认系统信息是否正确,每次 ambari-agent 重新连接后会刷新信息。

  • 安装组件时提示系统版本不对
    同上,远程机器系统检测问题没有修改完整。
  • Python script has been killed due to timeout after waiting 1800 secs
    很可能是磁盘读写过慢但实际还在安装。
    调整 ambari server 配置文件 /etc/ambari-server/conf/ambari.propertiesagent.package.install.task.timeout=1800 值调大。
  • Hbase 启动不起来
    确认节点日期是否同步。
    确认是否进入了安全模式。
  • ambari-server 启动失败
    具体查看日志检查报错原因。如果启动失败时间很长,确认数据库信息是否对,能否正常连接上数据库。
  • ambari-server 非正常关闭后无法启动
    确认配置文件 /etc/ambari-server/conf/ambari.properties 是否是空的。

ElasticSerach

当前使用的是 ElasticSerach 7,由于 Ambari 里没有自带,需要我们手动将 ElasticSerach 添加成自定义服务注册进 Ambari。

自定义服务

先来了解一下自定义服务的基础概念。

以下是一份简单的服务配置文件结构:

├── metainfo.xml                       # 核心文件 项目的唯一名称、展示名称、启动脚本、服务组件等信息
├── configuration                      # 配置项展示的信息与环境变量信息
│   ├── elastic-config.xml
│   └── elastic-env.xml
├── package
│   └── scripts                        # 脚本逻辑
│       ├── master.py
│       ├── slaver.py
│       └── params.py                  # 读取配置项中的信息脚本
│   └── templates                      # es配置的模板文件
│       ├── elastic_master.jinja2
│       └── elastic_slaver.jinja2
└── quicklinks
    └── quicklinks.json                # 快速链接区域的配置

配置文件里 metainfo.xml 是用于 ambari server 页面展示信息以及脚本引导的入口文件。

metainfo.xml 示例:

<?xml version="1.0"?>
<metainfo>
    <schemaVersion>2.0</schemaVersion>
    <services>
        <service>
            <!-- 服务名称 大写且唯一 -->
            <name>ELASTICSEARCH</name>
            <!-- 页面展示名称 -->
            <displayName>Elasticsearch</displayName>
            <!-- 描述信息 -->
            <comment>Elasticsearch is a distributed, RESTful search and analytics engine capable of solving a growing number of use cases. As the heart of the Elastic Stack, it centrally stores your data so you can discover the expected and uncover the unexpected.</comment>
            <!-- 版本号 -->
            <version>7.8.1</version>
            <!-- 组件组 -->
            <components>
                <!-- 组件 这里只定义了 MASTER -->
                <component>
                    <name>MASTER</name>
                    <displayName>Elasticsearch Master</displayName>
                    <!-- 组件类型:MASTER/SLAVE/CLIENT -->
                    <category>MASTER</category>
                    <!-- 实例化数量 -->
                    <cardinality>1+</cardinality>
                    <!-- 执行脚本 -->
                    <commandScript>
                        <!-- 脚本路径 -->
                        <script>scripts/master.py</script>
                        <!-- 脚本语言 -->
                        <scriptType>PYTHON</scriptType>
                        <!-- 超时时间 -->
                        <timeout>600</timeout>
                    </commandScript>
                </component>
            </components>
            <!-- 针对不同OS可以配置特定依赖组件会自动安装 -->
            <osSpecifics>
                <osSpecific>
                    <osFamily>any</osFamily>
                </osSpecific>
            </osSpecifics>
            <!-- 配置文件 -->
            <configuration-dependencies>
                <config-type>elastic-env</config-type>
                <config-type>elastic-config</config-type>
            </configuration-dependencies>
            <!-- 是否在rack变更后重启 -->
            <restartRequiredAfterChange>false</restartRequiredAfterChange>
            <!-- 快速链接配置文件 -->
            <quickLinksConfigurations>
                <quickLinksConfiguration>
                    <fileName>quicklinks.json</fileName>
                    <default>true</default>
                </quickLinksConfiguration>
            </quickLinksConfigurations>
        </service>
    </services>
</metainfo>

脚本文件示例:

在不同的阶段会调用不同的函数去做相应执行,有五个钩子:install、status、start、stop、configure

#!/usr/bin/env python

import sys, os, glob, pwd, grp, signal, time
from resource_management import *

class Master(Script):

    # Install Elasticsearch
    def install(self, env):
        # Import properties defined in -config.xml file from the params class
        import params
        env.set_params(params)

        # Install dependent packages
        self.install_packages(env)

        # Create user and group for Elasticsearch if they don't exist
        try: grp.getgrnam(params.elastic_group)
        except KeyError: Group(group_name=params.elastic_group)

        try: pwd.getpwnam(params.elastic_user)
        except KeyError: User(username=params.elastic_user,
                              gid=params.elastic_group,
                              groups=[params.elastic_group],
                              ignore_failures=True
                             )

        # Create Elasticsearch directories
        Directory([params.elastic_base_dir, params.elastic_log_dir, params.elastic_pid_dir],
                  mode=0755,
                  cd_access='a',
                  owner=params.elastic_user,
                  group=params.elastic_group,
                  create_parents=True
                 )

        # Create empty Elasticsearch install log
        File(params.elastic_install_log,
             mode=0644,
             owner=params.elastic_user,
             group=params.elastic_group,
             content=''
            )

        # Download Elasticsearch
        cmd = format("cd {elastic_base_dir}; wget {elastic_download} -O elasticsearch.tar.gz -a {elastic_install_log}")
        Execute(cmd, user=params.elastic_user)

        # Install Elasticsearch
        cmd = format("cd {elastic_base_dir}; tar -xf elasticsearch.tar.gz --strip-components=1")
        Execute(cmd, user=params.elastic_user)

        # Ensure all files owned by elasticsearch user
        cmd = format("chown -R {elastic_user}:{elastic_group} {elastic_base_dir}")
        Execute(cmd)

        # Remove Elasticsearch installation file
        cmd = format("cd {elastic_base_dir}; rm elasticsearch.tar.gz")
        Execute(cmd, user=params.elastic_user)

        Execute('echo "Install complete"')


    def configure(self, env):
        # Import properties defined in -config.xml file from the params class
        import params
        env.set_params(params)

        configurations = params.config['configurations']['elastic-config']

        File(format("{elastic_conf_dir}/elasticsearch.yml"),
             content=Template("elastic_master.jinja2",
                              configurations=configurations),
             owner=params.elastic_user,
             group=params.elastic_group
            )

        # Ensure all files owned by elasticsearch user
        cmd = format("chown -R {elastic_user}:{elastic_group} {elastic_base_dir}")
        Execute(cmd)

        Execute('echo "Configuration complete"')

    def stop(self, env):
        # Import properties defined in -config.xml file from the params class
        import params

        # Import properties defined in -env.xml file from the status_params class
        import status_params

        # This allows us to access the params.elastic_pid_file property as
        #  format('{elastic_pid_file}')
        env.set_params(params)

        # Stop Elasticsearch
        kill_process(params.elastic_pid_file, params.elastic_user, params.elastic_log_dir)
        #cmd = format("kill `cat {elastic_pid_file}`")
        #Execute(cmd, user=params.elastic_user, only_if=format("test -f {elastic_pid_file}"))


    def start(self, env):
        # Import properties defined in -config.xml file from the params class
        import params
        env.set_params(params)

        # Configure Elasticsearch
        self.configure(env)

        # Start Elasticsearch
        cmd = format("{elastic_base_dir}/bin/elasticsearch -d -p {elastic_pid_file}")
        Execute(cmd, user=params.elastic_user)


    def status(self, env):
        # Import properties defined in -env.xml file from the status_params class
        import status_params

        # This allows us to access the params.elastic_pid_file property as
        #  format('{elastic_pid_file}')
        env.set_params(status_params)

        # Use built-in method to check status using pidfile
        check_process_status(status_params.elastic_pid_file)

if __name__ == "__main__":
    Master().execute()

添加 Elasticsearch 模块

创建文件:/var/lib/ambari-server/resources/stacks/HDP/3.3/services/ELASTICSEARCH/metainfo.xml
添加以下内容:

<?xml version="1.0"?>
<metainfo>
  <schemaVersion>2.0</schemaVersion>
  <services>
    <service>
      <name>ELASTICSEARCH</name>
      <version>7.8.1</version>
      <extends>common-services/ELASTICSEARCH/7.8.1</extends>
    </service>
  </services>
</metainfo>

创建目录:/var/lib/ambari-server/resources/common-services/ELASTICSEARCH

将前面自定义配置文件放到目录中。

重启服务,页面上就能看见 Elasticsearch 的信息了。

Elasticsearch 组集群的配置就是ES自己的配置文件管理。
确认启动成功后,访问检查检查端口可以确认具体信息,通过API访问可以确认集群主机节点信息。
例如查看集群节点信息:curl -X GET "hostname:9200/_cat/nodes"

问题记录:

  • 页面上多了一项,但是名称、描述都是空的
    说明配置文件有问题没有读取到文件信息,检查配置文件。
  • 怎么给每份配置添加不同的IP或者主机名
    启动时每次都会调用 configure,然后生成配置文件,在这个生命周期中可以读取环境变量中的主机名,赋值到模板文件中。
  • 自定义服务配置中的安装脚本修改后远程节点代码没更新
    如果已经安装过一次,需要清空 /var/lib/ambari-agent/cache/ 里相关的目录,然后 ambari server 重启,ambari server 在登录后会复制文件到各个节点。
    如果涉及配置文件相关的进服务页修改配置信息,或者将服务删除重新走添加流程。
  • 安装时报错 KeyError: 'elasticsearch'

    Traceback (most recent call last):
    File "/var/lib/ambari-agent/cache/stack-hooks/before-ANY/scripts/hook.py", line 38, in <module>
    BeforeAnyHook().execute()
    File "/usr/lib/ambari-agent/lib/resource_management/libraries/script/script.py", line 352, in execute
    method(env)
    File "/var/lib/ambari-agent/cache/stack-hooks/before-ANY/scripts/hook.py", line 31, in hook
    setup_users()
    File "/var/lib/ambari-agent/cache/stack-hooks/before-ANY/scripts/shared_initialization.py", line 50, in setup_users
    groups = params.user_to_groups_dict[user],
    KeyError: 'elastic'

    报错信息出自 before-ANY/scripts/hook.py,此脚本每次任务都会去执行,说明是添加的服务中没有指定用户归属某个组,导致环境变量中无法从指定组信息里查到当前用户归属。
    在属性配置里 USER 类型的属性里添加组归属信息:

    <property>
    <name>elastic_user</name>
    <value>elastic</value>
    <property-type>USER</property-type>
    <description>Elasticsearch user</description>
    <value-attributes>
      <type>user</type>
      <overridable>false</overridable>
      <user-groups>
        <property>
          <type>cluster-env</type>
          <name>user_group</name>
        </property>
      </user-groups>
    </value-attributes>
    <on-ambari-upgrade add="true"/>
    </property>
  • 启动后es自动停止,怎么排查错误
    在安装向导里有配置es的日志输出路径,查看日志信息分析问题。
  • 安装成功后无法启动es
    确认在配置目录下日志文件是否有更新。
    有更新,根据日志排查错误,大概率是配文件的问题。
    未更新,查看配置的目录权限是否足够。
    另一种可能是虚拟机效率太低执行缓慢,可能 pid 文件还没生成(进程实际还在),多等一会 pid 文件生成,ambari会自动识别为正在运行状态(在这里被坑很久)。
  • es启动后无法连接到集群,主机ip显示是 127.17.0.1 docker的网关
    es 配置文件中 network_host 不能设定为 0.0.0.0,需要写明确的主机名或是IP。
  • memory locking requested for elasticsearch process but memory is not locked
    *代表的是任意用户,可以更换为指定用户名

    cp /etc/security/limits.conf /etc/security/limits.conf.bak
    sed -i '$c\
    * soft memlock unlimited\
    * hard memlock unlimited\
    # End of file' /etc/security/limits.conf

    修改 /etc/sysctl.conf

    sudo echo "vm.swappiness=0" >> /etc/sysctl.conf
    sudo sysctl -p 
  • max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]
    修改 /etc/sysctl.conf

    sudo echo "vm.max_map_count=262144" >> /etc/sysctl.conf
    sudo sysctl -p 

总结

环境因素

安装的服务比较少,配置也没做太多调整,整个流程其实并不是太复杂。但因为使用了虚拟机,导致网络环境、执行速度问题折腾排查了一部分时间。

Ambari
另一部分由于 Ambari 不了解,自定义更是没思路,网上资料又少,一遍搜索一遍尝试。好在有一份5.5/6 版本的 es 服务参考,配合一些基础教程理解了自定义服务的基础定制规范,实现修改适配。

适配调整

Ambari 添加节点因为没有适配麒麟系统,所以需要手动对源码修改。好在网上有一篇修改教程,参考其逻辑,一边跟踪 python 代码一边处理发现的问题。总的来说这块问题相对较少,由于开始不了解机制分发代码在哪里、从哪儿进入、应该先看哪个问题、怎么输出错误等都头疼了一下。

借助AI

剩余的问题就是启动后报错,针对错误网上大部分都能搜到文档和方案,理解含义根据当下场景调整一下基本能行。

现在 AI 工具的大力发展,处理问题的便利性确实提升不少。

对于缺乏基础概念,难以理解理解的配置或是知识点,AI 大部分情况下都能解释的挺细致。

如果将遇到的一大串问题,不做拆解直接扔给 AI,不是常见问题 AI 也会蒙圈,最后答非所问,导致在询问和牛头不对马嘴的回答中转圈。

这时候需要根据现有理解的知识点拆分问题,一步步向 AI 询问,根据回答理解其背后逻辑意思。其中可以发现或是自己概念理解不对,或是描述信息缺失 AI 无法判断,然后再调整提示词重新询问,这样就能结合场景情况分析出问题状况和修改方案。

使用中还发现,特别是日志这种信息,一大串很难一下子理清关键点在哪时,直接复制黏贴给 AI 分析,AI 能总结的非常清楚,结合日志提供的帮助,AI 大部分情况下都能提供处理方案。

带联网的 AI 一定程度上已经可以替代搜索引擎,特别是纯知识点上的搜索和总结专门的。大部分操作可以先通过 AI 了解粗略概念,要完整详细的信息再通过搜索引擎查找专门教程学习。

想让 AI 回答的准确,首先需要当一个好的提问者,自己需要先整理消化一遍知识,AI 才能理解你的意图做出针对性的回答。提问的越混乱,或者过程中多次否定 AI 的思路,往往越容易导致 AI 后续回答混乱。如果 AI 思路不对,优先考虑使用编辑功能,调整提问描述,尽量少出现否定 AI 的回答。


LnEoi
707 声望17 粉丝