ZStack启动流程

本文深入探讨了ZStack的启动流程,重点关注ManagementNode(MN)的启动。MN基于Java SpringMVC,启动时首先加载ComponentLoaderWebListener,进而初始化CloudBus和ManagementNode。CloudBus的初始化涉及RabbitMQ配置,ManagementNode的启动包括组件初始化、注册到消息总线、数据库操作等步骤,确保消息传递和API组件功能正常。

这篇文章我们重点来看看zstack启动的过程中做了什么,如何启动起来的。github: https://github.com/SnailJie/ZstackWithComments.git

我们知道,zstack的组成中,核心是MN(ManagementNode)。Dashboard只是作为面向用户操作的命令发起者。命令发出后还是由MN进行消息分发处理。因此我们主要关注点在于如何MN是如何启动的。Here we go。


MN部分是基于Java SpringMVC进行的研发。因此启动的时候,会首先读取conf/web.xml。可以看看这个代码:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    metadata-complete="true">

    <absolute-ordering />

    <servlet>
        <servlet-name>ZStack Dispatcher Servlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/classes/zstack-servlet-context.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/classes/zstack-servlet-context.xml</param-value>
    </context-param>

    <context-param>
        <param-name>parentContextKey</param-name>
        <param-value>parentContext</param-value>
    </context-param>

    <servlet-mapping>
        <servlet-name>ZStack Dispatcher Servlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>/static/*</url-pattern>
    </servlet-mapping>

    <filter>
        <filter-name>UrlRewriteFilter</filter-name>
        <filter-class>org.tuckey.web.filters.urlrewrite.UrlRewriteFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>UrlRewriteFilter</filter-name>
        <url-pattern>/static/pypi/*</url-pattern>
        <dispatcher>REQUEST</dispatcher>
        <dispatcher>FORWARD</dispatcher>
    </filter-mapping>

    <!-- NOTE: this listener must be put as the first listener to execute, don't change the order !!! BootstrapWebListener里面只有一条调用的方法,目测是想利用直接调用来使得类加载、初始化-->
    <listener>
        <listener-class>
            org.zstack.portal.managementnode.BootstrapWebListener
        </listener-class>
    </listener>

    <listener>
        <listener-class>
            org.springframework.web.context.ContextLoaderListener 
        </listener-class>
    </listener>


<!-- 在这里开始启动Management节点。想看启动过程都做了哪些事情?到ComponentLoaderWebListener里面看看吧 。在web容器启动时,会自动加载这个类的初始化方法-->
    <listener>
        <listener-class>
            org.zstack.portal.managementnode.ComponentLoaderWebListener
        </listener-class>
    </listener>

</web-app>

可以看到,web容器在启动时,会自动去加载ComponentLoaderWebListener,ComponentLoaderWebListener实现了ServletContextListener的两个接口,因此在启动时,会自动去调用ComponentLoaderWebListener里面的contextInitialized方法。这里我们进行看一下具体的代码

ComponentLoaderWebListener.java
*RenJie:
 * 这里开始进行初始化操作。可以看到基本是创建并启动ManagementNode,同时创建云总线。想看
 * ManagementNode 启动过程还是想了解云总线?
*/    

    @Override
    public void contextInitialized(ServletContextEvent event) {
        try {
            if (!isInit) {
                Platform.createComponentLoaderFromWebApplicationContext(WebApplicationContextUtils.getWebApplicationContext(event.getServletContext()));
                node = Platform.getComponentLoader().getComponent(ManagementNodeManager.class);
                bus = Platform.getComponentLoader().getComponent(CloudBus.class);    //创建云总线,可以看看怎么创建的
                node.startNode();      //启动管理节点。具体细节我们进ManagementNodeManagerImpl里面看看
                isInit = true;
            }
        } catch (Throwable t) {
            logger.warn("failed to start management server", t);
            // have to call bus.stop() because its init has been called by spring
            if (bus != null) {
                bus.stop();
            }

            Throwable root = ExceptionDSL.getRootThrowable(t);
            new BootErrorLog().write(root.getMessage());
            if (CoreGlobalProperty.EXIT_JVM_ON_BOOT_FAILURE) {
                System.exit(1);
            } else {
                throw new CloudRuntimeException(t);
            }
        }
    }

可以看到,在这里,主要操作的是两个部分,一个是cloudbus,一个是managementnode。cloudbus是整个后台核心的消息传输通道,可以理解,首先得把“路”修好,然后才能方便地进行后面的其他操作。因此首先是初始化cloudbus了,然后再初始化MN。OK,那我们就先看cloudbus的初始化,然后看看MN。

CloudBus的初始化

CloudBus的初始化并不在ManagementNode的启动流程中,他单独先启了起来,下面的这行代码的实现中,就初始化了CloudBus。

Platform.createComponentLoaderFromWebApplicationContext(WebApplicationContextUtils.getWebApplicationContext(event.getServletContext()));

进一步查看createComponentLoaderFromWebApplicationContext()的实现方式,有这么一行

bus = loader.getComponentNoExceptionWhenNotExisting(CloudBus.class);

如果进一步进去看,其实这行的代码的实现是利用BeanFactory,采用IOC的机制,去获取CloudBus的Bean。可以看一下CloudBus的spring配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
     ……
    default-init-method="init" default-destroy-method="destroy">

 ……
 ……

    <bean id="CloudBus" class = "org.zstack.core.cloudbus.CloudBusImpl2" depends-on="ThreadFacade,ThreadAspectj">
        <zstack:plugin>
            <zstack:extension interface="org.zstack.header.managementnode.ManagementNodeChangeListener" order="9999"/>
        </zstack:plugin>
    </bean>


</beans>

可以看到具体的CloudBus实现是CloudBusImpl2,默认的初始化方法是init。因此我们去看看CloudBusImpl2的init方法。

//CloudBus的初始化方法,在加载的时候自动调用init方法。CloudBus的具体实现是利用RabbitMQ的实现,因此CloudBus的初始化也主要是对RabbitMQ的配置
    void init() {
        trackerClose = CloudBusGlobalProperty.CLOSE_TRACKER;
        serverIps = CloudBusGlobalProperty.SERVER_IPS;
        tracker = new MessageTracker();

        ConnectionFactory connFactory = new ConnectionFactory();     //ConnectionFactory用于配置RabbitMQ与broker链接的配置信息
        List<Address> addresses = CollectionUtils.transformToList(serverIps, new Function<Address, String>() {
            @Override
            public Address call(String arg) {
                return Address.parseAddress(arg);
            }
        });
        connFactory.setAutomaticRecoveryEnabled(true);
        connFactory.setRequestedHeartbeat(CloudBusGlobalProperty.RABBITMQ_HEART_BEAT_TIMEOUT);
        connFactory.setNetworkRecoveryInterval((int) TimeUnit.SECONDS.toMillis(CloudBusGlobalProperty.RABBITMQ_NETWORK_RECOVER_INTERVAL));
        connFactory.setConnectionTimeout((int) TimeUnit.SECONDS.toMillis(CloudBusGlobalProperty.RABBITMQ_CONNECTION_TIMEOUT));

        logger.info(String.format("use RabbitMQ server IPs: %s", serverIps));

        try {
            if (CloudBusGlobalProperty.RABBITMQ_USERNAME != null) {
                connFactory.setUsername(CloudBusGlobalProperty.RABBITMQ_USERNAME);
                logger.info(String.format("use RabbitMQ username: %s", CloudBusGlobalProperty.RABBITMQ_USERNAME));
            }
            if (CloudBusGlobalProperty.RABBITMQ_PASSWORD != null) {
                connFactory.setPassword(CloudBusGlobalProperty.RABBITMQ_PASSWORD);
                logger.info("use RabbitMQ password: ******");
            }
            if (CloudBusGlobalProperty.RABBITMQ_VIRTUAL_HOST != null) {
                connFactory.setVirtualHost(CloudBusGlobalProperty.RABBITMQ_VIRTUAL_HOST);
                logger.info(String.format("use RabbitMQ virtual host: %s", CloudBusGlobalProperty.RABBITMQ_VIRTUAL_HOST));
            }

            conn = connFactory.newConnection(addresses.toArray(new Address[]{}));    //创建与RabbitMQ的链接
            logger.debug(String.format("rabbitmq connection is established on %s", conn.getAddress()));

            ((Recoverable)conn).addRecoveryListener(new RecoveryListener() {
                @Override
                public void handleRecovery(Recoverable recoverable) {
                    logger.info(String.format("rabbitmq connection is recovering on %s", conn.getAddress().toString()));
                }
            });

            channelPool = new ChannelPool(CloudBusGlobalProperty.CHANNEL_POOL_SIZE, conn);   //创建了100个通道的连接池
            createExchanges();    //在一个channel上建立了三种路由,
            outboundQueue = new BusQueue(makeMessageQueueName(SERVICE_ID), BusExchange.P2P);
            Channel chan = channelPool.acquire();
            chan.queueDeclare(outboundQueue.getName(), false, false, true, queueArguments());
            chan.basicConsume(outboundQueue.getName(), true, consumer);
            chan.queueBind(outboundQueue.getName(), outboundQueue.getBusExchange().toString(), outboundQueue.getBindingKey());
            channelPool.returnChannel(chan);
            //在这儿之下创建了几个队列,具体是用来干啥的现在还不知道
            maid.construct();     
            noRouteEndPoint.construct();
            tracker.construct();
            tracker.trackService(SERVICE_ID);
        } catch (Exception e) {
            throw new CloudRuntimeException(e);
        }
    }

这部分就是具体的初始化代码了。CloudBus的具体实现是利用RabbitMQ的实现,因此CloudBus的初始化也主要是对RabbitMQ的配置,以及创建连接池、创建路由以及几个队列等。

ManagemenNode的初始化

ManagementNode的初始化,就是定义了一系列的操作来初始化对象。可以ManagementNodeManagerImpl里的start方法查看到启动的流程。这里就不贴代码了,太长了。

Managementnode启动过程中,会做以下步骤:

1.初始化云总线
云总线的初始化已经在前面做过了,因此这步直接被跳过
2.启动各种组件
里面的Component,只要实现了Component接口的,都会被加载进入容器,并且被调用Component.start启动组件

ManagementNodeManagerImpl.java
then(new NoRollbackFlow() {
                String __name__ = "populate-components";     //RenJie: 启动各种组件【哪些组件?】

                @Override
                public void run(FlowTrigger trigger, Map data) {
                    populateComponents();    //这个里面的实现
                    trigger.next();
                }

3.把管理节点作为微服务中的服务加入他的消息总线
zstack的进程内微服务理念。管理节点也是众多服务中的一个,需要注册到消息总线上去。
4.把刚才的注册的组件都启动起来
5.初始化数据库
6.把管理节点的信息持久化到数据库中
7.开启心跳监测
8.把API组件注册到消息总线上去
9.开启监控管理节点的生命状态、发布管理节点已经就绪的信息

启动过程中,我觉得比较重要的是一个是消息总线(已经在前面初始化过了),一个是API组件。
消息总线起来了,那么整套核心部分就可以进行消息传递,进行微服务传递。而API组件是进行消息的注册以及转发的。因此也非常重要。

至此呢,MN也成功初始化并且启动起来了。接着就可以接受来自dashboard进行的请求了

05-07
### ZStack 开源云平台 使用指南 #### 一、ZStack 的概述 ZStack 是一种下一代开源 IaaS 软件,专为智能数据中心设计。其核心目标是帮助用户快速构建智能化的云数据中心,并支持多种灵活的云应用场景,如 VDI(虚拟桌面基础架构)、PaaS 和 SaaS 等[^1]。 #### 二、ZStack 的特点 ZStack 提供了一系列显著的优势,使其成为企业级用户的理想选择: - **易用性**:提供了直观的操作界面和完善的 API 支持。 - **稳定性**:经过大量测试和优化,能够满足生产环境的要求。 - **高性能**:相较于其他同类产品,ZStack 展现出了更高的性能表现。 - **灵活性**:允许用户自定义各种资源配置和服务模式[^3]。 #### 三、安装与配置需求 为了成功部署 ZStack 平台,以下是基本的硬件和软件要求: ##### (1)硬件要求 - CPU:至少 4 核心,推荐使用支持 x86 架构硬件虚拟化的处理器(如 Intel VMX 或 AMD SVM),并在 BIOS 中启用虚拟化功能。 - 内存:对于基础演示环境,最低需要 8GB;而在实际生产环境中,则建议配备不少于 64GB 的内存。 - 存储:主存储或镜像存储应具备 2TB 及以上的可用空间。 - 网络:所有物理机需装配千兆网卡,并确保网络连接稳定可靠。此外,还需预先设置好交换机的相关参数以适应特定流量类型的需求。 ##### (2)软件准备 访问官方站点下载最新版本的 ZStack 安装包。登录到 [ZStack官网](https://www.zstack.io/) 后点击“下载”,完成身份验证流程后会收到一封包含具体链接地址的电子邮件用于获取文件资源。 #### 四、典型部署过程简介 下面是一个简化版的小型私有云环境创建步骤说明: 1. 准备好符合条件的一组服务器作为节点; 2. 下载并解压 ZStack 发布的 ISO 文件至本地磁盘路径下; 3. 利用工具制作启动 U 盘或者 CD-ROM 将系统引导加载入每台待初始化的目标机器当中去执行自动化脚本操作直至结束为止; 4. 进入 Web 控制面板调整全局选项设定比如时间同步机制等等细节部分再添加额外组件例如负载均衡器实例之类的东西最后保存更改退出即可[^3]。 #### 五、高级应用案例——Linux 堡垒机集成方案 针对某些特殊业务场景下的安全性考量,可以通过在 ZStack 上架设 Linux 堡垒机的方式实现更精细权限控制的同时降低潜在威胁发生概率。此方法不仅方便了远程接入管理工作还能有效减少人为失误带来的损失风险[^5]。 --- ```bash # 示例命令:检查当前操作系统是否已开启 KVM 功能 egrep -c '(vmx|svm)' /proc/cpuinfo ``` --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值