快速入门Openstack,无脑多节点部署Mitaka(5)--Nova部署

参考资料:官方配置文档(http://docs.openstack.org/mitaka/install-guide-rdo/common/get_started_compute.html)
我觉得我挺高产的,就是没啥人看,不过我的主要目的还是学习,如果有人看到话,希望这个系列的博文能帮助到你。

什么是Nova?

简介

Nova是openstack中用来提供高可扩展、按需、自服务的计算资源服务的一个项目。Nova是openstack最老牌的项目,最初刚发布的时候集成了很多的功能,既包括计算,也包括网络和存储。后来随着openstack的发展壮大,相应的功能被拆分成单独的项目,使得相应功能及开发可以更加细致和深入。最初的nova-network现在被neutron项目取代,最初的nova-volume从nova中分离出来,变成了cinder项目。分离之后,Nova的功能相对单一,结构也更加清楚,更容易理解。
Nova是一个虚拟机的管理程序,为虚拟机的管理和维护提供了一套高度抽象的API,允许用户通过API来管理自己的虚拟机。Nova与系统虚拟化关系不大,Nova是通过调用各种虚拟化方案提供的接口来管理虚拟机的,因此,Nova能够支持多种不同的虚拟化方案。目前,Nova支持的虚拟化方案包括:
Libvirt + KVM/Qemu;
Libvirt + LXC;
Libvirt + Xen;
Windows Hyper-V;
VMware;
最近openstack社区正在积极的拓展Nova的功能,Nova不仅可以用来管理虚拟机,也可以用来管理物理服务器(Ironic)及用来管理容器(Magnum)。

架构

简版:
快速入门Openstack,无脑多节点部署Mitaka(5)--Nova部署_第1张图片

nova services 简介

nova-api
nova-api是整个nova各项服务的对外接口,通过HTTP协议对外提供服务,openstack中的其他项目也是通过nova-api提供的接口与其进行交互的。nova-api有三套接口,其中一套是兼容AWS EC2的接口,另一套是openstack自己开发的接口,最后一套则是给虚拟机提供metadata服务,不直接对外服务。nova-api主要的任务就是接收用户通过HTTP协议发送过来的请求,对请求进行验证并处理用户的请求,最后将请求的结果发送给用户。但是很多时候,nova-api给用户返回操作结果的时候,用户的请求可能还没有全部完成,以创建虚拟机为例,nova-api收到一个创建一台虚拟机的请求后,nova-api首先会对请求进行验证,检查请求中的image和flavor是否存在,如果没有问题,那么nova-api会在数据库中记录虚拟机的基本信息,并通过RPC给计算节点发送一个创建虚拟机的请求,接着,nova-api就会将虚拟机相关的信息发送用户,请求的响应过程结束。nova-api并不会等到虚拟机创建完成才返回,而是将虚拟机创建相关的主要工作交给了nova-compute服务。nova-api在运行的过程中需要连接到数据库,因为nova-api需要更新虚拟机的信息。此外,nova-api还要能够访问消息队列服务器,因为nova-api和nova-compute直接是通过RPC进行通信的。
nova-conductor
conductor服务是在G版进入到nova的,主要的目的是代替nova-compute去访问数据库,这样做主要是基于安全的考虑。在公有云的环境下,nova-compute是部署最为广泛的一个服务,运行在hypervisor上的恶意虚拟机可能可以拿到hypervisor的控制权,从而直接对数据中心的数据库进行修改。为了防止这种情况发生,一种简单的做法就是让尽可能少的人有直接操作数据库的权限。通过nova-conductor来访问数据库,不仅可以提高nova的可扩展性,还能提高数据库访问的性能。除了代替nova-compute访问数据库,nova-conductor还被用来执行一些需要较长时间才能完成的操作,如虚拟机的迁移。
从结构上来说,nova-conductor非常简单,就是不断的从消息服务器获取RPC请求,然后将其转化为数据库的操作,获取到操作结果之后通过消息服务器返回数据库操作的结果。
nova-scheduler
scheduler是nova中的调度服务,决定了如何放置新创建的虚拟机。与conductor服务类似,从结构上来说,scheduler相对比较简单,从具体的实现上来说,scheduler是非常复杂的。scheduler的具体实现方法可以请参考[[grizzly-nova-scheduler]],虽然Havana版中scheduler有些变化,但是总体上没太大的差别。
scheduler为了尽可能好的利用集群资源,需要跟踪每个hypervisor的状态和可用的资源。nova-compute会定期的通过消息队列和数据库更新其自身的状态和可用资源,scheduler也可以通过RPC获取hypervisor的状态和可用资源。因此,scheduler需要直接访问数据库和消息队列服务器。
nova-cert
nova-cert是nova提供的一个证书服务,能够对用户的公钥进行签名,并返回签名后的x509格式的证书。从实现上来说,cert可能是nova中最简单的一个服务。主要包括两部分,一部分是集成在nova-api中的HTTP接口,另一部分就是cert-manager。nova-api收到签名请求之后,会通过消息队列将请求转发给cert-manager,cert-manager对公钥进行签名,并通过消息队列返回签名后的证书。因此,cert服务只需要连接到消息队列服务器即可。
nova-cell
cell服务主要是用来提高openstack集群的可扩展性,cell服务主要负责在不同cell之间的进行信息的同步和虚拟机的调度。在启用了cell的情况下,一个region可以划分为多个cell,每个cell可以使用不同的数据库和消息队列服务器。创建虚拟机时,该请求首先会发送给cell,cell再将该请求转发给某个cell中的scheduler,scheduler再选择某台具体的宿主机,并启动虚拟机。cell服务需要访问数据库和消息队列服务器。
nova-consoleauth
consoleauth的功能相对比较简单,是给vnc代理服务器提供token验证服务。当用户在horizon中想通过vnc连接到虚拟机时,会给vnc代理服务器发送一个请求,代理服务器除了需要通过nova-compute获取虚拟机的vnc连接信息外,还需要通过对url中的token进行验证。token验证的任务就需要调用consoleauth。consoleauth会记录url中的token信息,并返回连接虚拟机vnc所需要的信息。为了获取连接虚拟机vnc相关的信息,consoleauth需要通过RPC调用nova-compute获取。因此,consoleauth服务只需要连接到消息队列服务器即可。
nova-objectstore
objectstore是nova提供的一个仿s3的对象存储接口。不过nova中的实现相对来说非常简单,只能讲上传的文件存储在本地,也不保证上传文件的高可用,只是一个简单的基于HTTP协议的文件存储服务,与nova的其他服务都没有什么关联。
nova-novncproxy
novncproxy是nova中实现的一个vnc代理服务,它能在浏览器和虚拟机的vnc server之间建立一条双向的socket通道,使得用户可以直接在浏览器中通过vnc登陆虚拟机,对虚拟机进行管理和操作。novncproxy在运行的过程中需要接收从用户浏览器过来的请求,并通过RPC调用consoleauth对token进行验证。如果验证可以通过,那么它会在浏览器和虚拟机vnc之间建立一个双向的连接。因此,novncproxy在工作的过程中需要能够访问消息队列服务器。此外,novncproxy通常使用KVM的虚拟化方案时使用。
nova-spicehtml5proxy
spicehtml5proxy在实现原理和工作流程上与novncproxy是一模一样,他们的差别只是体现在通信协议上。一个是使用vnc协议,另外一个则是采用的spice协议。其主要原因是,vnc在虚拟桌面环境中表现的并不是很好,而spice协议则能够较好的满足虚拟桌面的需求。
nova-xvpvncproxy
xvpvncproxy在实现原理和工作流程上与novncproxy是一模一样,它们都是采用vnc协议。不同的是,xvpvncproxy主要用在xen虚拟化平台中,而novncproxy则主要用在KVM虚拟化平台。
nova-console
console代理服务与vnc代理服务类似,用来连接到虚拟机的console接口。目前,只有xen和VMware虚拟化平台实现了console代理服务。
nova-compute
compute是nova项目中最复杂的任务,和众多服务都有关联,需要处理虚拟机相关的所有操作,包括虚拟机的创建、删除、启动、停止、迁移等。compute内部实现非常复杂,需要对多种虚拟化技术进行封装,为虚拟机准备磁盘和网络资源,还有很多周期性的任务需要处理。但是其数据流却相对简单,只是不断的从消息队列获取请求,处理请求,返回响应结果。nova-compute在运行的过程中,需要连接到消息队列服务器和虚拟化服务的管理接口(如:libvirtd)。
nova-network
nova-network是nova中集成的网络管理组件,它能够将一台普通的服务器转化为虚拟机的网关。nova-network实现的网络模型非常简单,却非常实用,尤其是nova-network支持HA模式,它使得每个计算节点都能充当虚拟机的网关。这样不仅可以解决网关高可用的问题,同时也可以提高虚拟机访问外部网络的性能,消除了单点故障和性能瓶颈。nova-network的数据流也相对简单,不断的处理和响应来自消息队列的请求,此外,在处理消息队列的请求中,需要将一些网络相关的配置和参数写入到数据库中。因此,nova-network也需要能够直接访问数据库。

数据流

结合前面的分析,可以得出nova中各个服务的数据流如下图所示。
快速入门Openstack,无脑多节点部署Mitaka(5)--Nova部署_第2张图片


敲黑板,上面这些都是重点。
注意,现在开始都会是多节点安装!

Nova-controller节点部署

前期准备

在安装每个服务之前,我们都必须先创建该服务的数据库、服务证书和API Endpoints。因为数据库这些都是安装在controller节点上,所以接下来的操作都是在controller节点上运行。
1.创建两个数据库,并给nova用户设置权限和密码。123456是我的密码
command:

mysql -u root -p123456
CREATE DATABASE nova_api;
CREATE DATABASE nova;
GRANT ALL PRIVILEGES ON nova_api.* TO 'nova'@'localhost' \
  IDENTIFIED BY '123456';
GRANT ALL PRIVILEGES ON nova_api.* TO 'nova'@'%' \
  IDENTIFIED BY '123456';
GRANT ALL PRIVILEGES ON nova.* TO 'nova'@'localhost' \
  IDENTIFIED BY '123456';
GRANT ALL PRIVILEGES ON nova.* TO 'nova'@'%' \
  IDENTIFIED BY '123456';
exit

OutPut:

[root@controller ~]# mysql -u root -p123456
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 32
Server version: 10.1.12-MariaDB MariaDB Server

Copyright (c) 2000, 2016, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]> CREATE DATABASE nova_api;
Query OK, 1 row affected (0.00 sec)

MariaDB [(none)]> CREATE DATABASE nova;
Query OK, 1 row affected (0.00 sec)

MariaDB [(none)]> GRANT ALL PRIVILEGES ON nova_api.* TO 'nova'@'localhost' \
    ->   IDENTIFIED BY '123456';
Query OK, 0 rows affected (0.00 sec)

MariaDB [(none)]> GRANT ALL PRIVILEGES ON nova_api.* TO 'nova'@'%' \
    ->   IDENTIFIED BY '123456';
Query OK, 0 rows affected (0.00 sec)

MariaDB [(none)]> GRANT ALL PRIVILEGES ON nova.* TO 'nova'@'localhost' \
    ->   IDENTIFIED BY '123456';
Query OK, 0 rows affected (0.00 sec)

MariaDB [(none)]> GRANT ALL PRIVILEGES ON nova.* TO 'nova'@'%' \
    ->   IDENTIFIED BY '123456';
Query OK, 0 rows affected (0.00 sec)

MariaDB [(none)]> exit
Bye
[root@controller ~]# 

2.在openstack中创建nova用户,并添加为service project的管理员

[root@controller ~]# source ~/admin-openrc
[root@controller ~]# openstack user create --domain default \
>   --password-prompt nova
User Password:
Repeat User Password:
+-----------+----------------------------------+
| Field     | Value                            |
+-----------+----------------------------------+
| domain_id | 098b1a4d36d241ed87e979ec86d32722 |
| enabled   | True                             |
| id        | 387f268a791446efb1cd43c28e61b11b |
| name      | nova                             |
+-----------+----------------------------------+
[root@controller ~]# openstack role add --project service --user nova admin

3.创建nova服务实体

[root@controller ~]# openstack service create --name nova \
>   --description "OpenStack Compute" compute
+-------------+----------------------------------+
| Field       | Value                            |
+-------------+----------------------------------+
| description | OpenStack Compute                |
| enabled     | True                             |
| id          | 04fdd2d0e3804d0690ca3c22e7f29e0f |
| name        | nova                             |
| type        | compute                          |
+-------------+----------------------------------+

4.创建Compute service的API Endpoints

[root@controller ~]# openstack endpoint create --region RegionOne \
>   compute public http://controller.example.com:8774/v2.1/%\(tenant_id\)s
+--------------+-------------------------------------------------------+
| Field        | Value                                                 |
+--------------+-------------------------------------------------------+
| enabled      | True                                                  |
| id           | 8e7a3cd7c9a64d5e95acaa31040169a1                      |
| interface    | public                                                |
| region       | RegionOne                                             |
| region_id    | RegionOne                                             |
| service_id   | 04fdd2d0e3804d0690ca3c22e7f29e0f                      |
| service_name | nova                                                  |
| service_type | compute                                               |
| url          | http://controller.example.com:8774/v2.1/%(tenant_id)s |
+--------------+-------------------------------------------------------+
[root@controller ~]# openstack endpoint create --region RegionOne \
>   compute internal http://controller.example.com:8774/v2.1/%\(tenant_id\)s
+--------------+-------------------------------------------------------+
| Field        | Value                                                 |
+--------------+-------------------------------------------------------+
| enabled      | True                                                  |
| id           | 0ca9476104584562a6448805d32a3e31                      |
| interface    | internal                                              |
| region       | RegionOne                                             |
| region_id    | RegionOne                                             |
| service_id   | 04fdd2d0e3804d0690ca3c22e7f29e0f                      |
| service_name | nova                                                  |
| service_type | compute                                               |
| url          | http://controller.example.com:8774/v2.1/%(tenant_id)s |
+--------------+-------------------------------------------------------+
[root@controller ~]# openstack endpoint create --region RegionOne \
>   compute admin http://controller.example.com:8774/v2.1/%\(tenant_id\)s
+--------------+-------------------------------------------------------+
| Field        | Value                                                 |
+--------------+-------------------------------------------------------+
| enabled      | True                                                  |
| id           | 46532b836e6447d6a264445e6d969597                      |
| interface    | admin                                                 |
| region       | RegionOne                                             |
| region_id    | RegionOne                                             |
| service_id   | 04fdd2d0e3804d0690ca3c22e7f29e0f                      |
| service_name | nova                                                  |
| service_type | compute                                               |
| url          | http://controller.example.com:8774/v2.1/%(tenant_id)s |
+--------------+-------------------------------------------------------+
[root@controller ~]# 

安装

1.install the packages

yum install openstack-nova-api openstack-nova-conductor \
  openstack-nova-console openstack-nova-novncproxy \
  openstack-nova-scheduler -y

2.编辑/etc/nova/nova.conf
cp -p /etc/nova/nova.conf /etc/nova/nova.conf.bak
vim /etc/nova/nova.conf

[DEFAULT]
...
enabled_apis = osapi_compute,metadata
rpc_backend = rabbit
auth_strategy = keystone
my_ip = 192.168.0.17
use_neutron = True
firewall_driver = nova.virt.firewall.NoopFirewallDriver
[api_database]
...
connection = mysql+pymysql://nova:123456@controller.example.com/nova_api
[database]
...
connection = mysql+pymysql://nova:123456@controller.example.com/nova
[oslo_messaging_rabbit]
...
rabbit_host = controller.example.com
rabbit_userid = openstack
rabbit_password = henry
[keystone_authtoken]
...
auth_uri = http://controller.example.com:5000
auth_url = http://controller.example.com:35357
memcached_servers = controller.example.com:11211
auth_type = password
project_domain_name = default
user_domain_name = default
project_name = service
username = nova
password = 123456
[vnc]
...
vncserver_listen = $my_ip
vncserver_proxyclient_address = $my_ip
[glance]
...
api_servers = http://controller.example.com:9292
[oslo_concurrency]
...
lock_path = /var/lib/nova/tmp

3.总览

[root@controller ~]# cat /etc/nova/nova.conf | grep -v ^# | grep -v ^$
[DEFAULT]
enabled_apis = osapi_compute,metadata   #配置启用只允许compute和metadataAPIs 
rpc_backend = rabbit    #启用RabbitMQ消息队列作为rpc连接访问
auth_strategy = keystone    #启用Identity service认证服务
my_ip = 192.168.0.17    #Controller节点上的管理网络接口IP
use_neutron = True
firewall_driver = nova.virt.firewall.NoopFirewallDriver
[api_database]
connection = mysql+pymysql://nova:123456@controller.example.com/nova_api    #连接nova_api数据库,123456是我的密码
[barbican]
[cache]
[cells]
[cinder]
[conductor]
[cors]
[cors.subdomain]
[database]
connection = mysql+pymysql://nova:123456@controller.example.com/nova    #连接nova数据库,123456是我的密码
[ephemeral_storage_encryption]
[glance]
api_servers = http://controller.example.com:9292    #镜像服务的提供者,我是装在controller节点上
[guestfs]
[hyperv]
[image_file_url]
[ironic]
[keymgr]
[keystone_authtoken]
auth_uri = http://controller.example.com:5000
auth_url = http://controller.example.com:35357
memcached_servers = controller.example.com:11211
auth_type = password
project_domain_name = default
user_domain_name = default
project_name = service
username = nova
password = 123456   #123456是我openstack中nova用户的密码
[libvirt]
[matchmaker_redis]
[metrics]
[neutron]
[osapi_v21]
[oslo_concurrency]
lock_path = /var/lib/nova/tmp
[oslo_messaging_amqp]
[oslo_messaging_notifications]
[oslo_messaging_rabbit]
rabbit_host = controller.example.com    #rabbitMQ服务的提供者,我也是安装在controller节点上的
rabbit_userid = openstack   #这是我的rabbitMQ服务中的账号
rabbit_password = henry #henry是我rabbitMQ服务中openstack账号的密码
[oslo_middleware]
[oslo_policy]
[rdp]
[serial_console]
[spice]
[ssl]
[trusted_computing]
[upgrade_levels]
[vmware]
[vnc]
vncserver_listen = $my_ip
vncserver_proxyclient_address = $my_ip
[workarounds]
[xenserver]
[root@controller ~]# 

4.同步数据库

su -s /bin/sh -c "nova-manage api_db sync" nova
su -s /bin/sh -c "nova-manage db sync" nova

发现一个错误,它提示我访问不到controller中的数据库,我之前的代码是这么写的
这里写图片描述
没错,正确的地址应该是controller.example.com
错误信息描述:

[root@controller ~]# su -s /bin/sh -c "nova-manage api_db sync" nova
error: (pymysql.err.OperationalError) (2003, "Can't connect to MySQL server on 'controller' ([Errno -2] Name or service not known)")

但是下面这种弃用提示是可以忽略的:

[root@controller ~]# su -s /bin/sh -c "nova-manage api_db sync" nova
[root@controller ~]# su -s /bin/sh -c "nova-manage db sync" nova
/usr/lib/python2.7/site-packages/pymysql/cursors.py:146: Warning: Duplicate index 'block_device_mapping_instance_uuid_virtual_name_device_name_idx' defined on the table 'nova.block_device_mapping'. This is deprecated and will be disallowed in a future release.
  result = self._query(query)
/usr/lib/python2.7/site-packages/pymysql/cursors.py:146: Warning: Duplicate index 'uniq_instances0uuid' defined on the table 'nova.instances'. This is deprecated and will be disallowed in a future release.
  result = self._query(query)
[root@controller ~]# 

5.启动服务,并设置为开机自启

systemctl enable openstack-nova-api.service \
  openstack-nova-consoleauth.service openstack-nova-scheduler.service \
  openstack-nova-conductor.service openstack-nova-novncproxy.service
systemctl start openstack-nova-api.service \
  openstack-nova-consoleauth.service openstack-nova-scheduler.service \
  openstack-nova-conductor.service openstack-nova-novncproxy.service

上面部分是针对controller节点的。
按照我们之前的网络拓补图,下面的部署是要在compute节点运行的。

Nova-compute部署

安装

1.Install the packages:

yum install openstack-nova-compute -y 

2.编辑/etc/nova/nova.conf
cp -p /etc/nova/nova.conf /etc/nova/nova.conf.bak
vim /etc/nova/nova.conf

[DEFAULT]
...
rpc_backend = rabbit
auth_strategy = keystone
my_ip = 192.168.0.18    
use_neutron = True
firewall_driver = nova.virt.firewall.NoopFirewallDriver
[oslo_messaging_rabbit]
...
rabbit_host = controller.example.com
rabbit_userid = openstack
rabbit_password = henry
[keystone_authtoken]
...
auth_uri = http://controller.example.com:5000
auth_url = http://controller.example.com:35357
memcached_servers = controller.example.com:11211
auth_type = password
project_domain_name = default
user_domain_name = default
project_name = service
username = nova
password = 123456
[vnc]
...
enabled = True
vncserver_listen = 0.0.0.0
vncserver_proxyclient_address = $my_ip
novncproxy_base_url = http://controller.example.com:6080/vnc_auto.html
[glance]
...
api_servers = http://controller.example.com:9292
[oslo_concurrency]
...
lock_path = /var/lib/nova/tmp
[libvirt]
...
virt_type = qemu

总览:

[root@compute ~]# cat /etc/nova/nova.conf | grep -v ^# | grep -v ^$
[DEFAULT]
rpc_backend = rabbit
auth_strategy = keystone
my_ip = 192.168.0.18    #compute节点的管理网路的IP,得跟controller节点的管理网络保持在同一条网络上。
use_neutron = True
firewall_driver = nova.virt.firewall.NoopFirewallDriver
[api_database]
[barbican]
[cache]
[cells]
[cinder]
[conductor]
[cors]
[cors.subdomain]
[database]
[ephemeral_storage_encryption]
[glance]
api_servers = http://controller.example.com:9292
[guestfs]
[hyperv]
[image_file_url]
[ironic]
[keymgr]
[keystone_authtoken]
auth_uri = http://controller.example.com:5000
auth_url = http://controller.example.com:35357
memcached_servers = controller.example.com:11211
auth_type = password
project_domain_name = default
user_domain_name = default
project_name = service
username = nova
password = 123456   #nova用户的密码
[libvirt]
virt_type = qemu    #在命令行执行egrep -c '(vmx|svm)' /proc/cpuinfo这条命令,如果返回值不是0,说明你的机器支持kvm,就没必要再用qemu,因此这行可以注释掉或者删除
[matchmaker_redis]
[metrics]
[neutron]
[osapi_v21]
[oslo_concurrency]
lock_path = /var/lib/nova/tmp
[oslo_messaging_amqp]
[oslo_messaging_notifications]
[oslo_messaging_rabbit]
rabbit_host = controller.example.com
rabbit_userid = openstack   #rabbitMQ账户
rabbit_password = henry #rabbitMQ密码
[oslo_middleware]
[oslo_policy]
[rdp]
[serial_console]
[spice]
[ssl]
[trusted_computing]
[upgrade_levels]
[vmware]
[vnc]
enabled = True
vncserver_listen = 0.0.0.0
vncserver_proxyclient_address = $my_ip
novncproxy_base_url = http://controller.example.com:6080/vnc_auto.html
[workarounds]
[xenserver]
[root@compute ~]# 

3.启动服务,并设置为开机自启

systemctl enable libvirtd.service openstack-nova-compute.service
systemctl start libvirtd.service openstack-nova-compute.service

验证

请在controller节点上进行验证

[root@controller ~]# source ~/admin-openrc
[root@controller ~]# openstack compute service list
+----+----------------+----------------+----------+---------+-------+------------------+
| Id | Binary         | Host           | Zone     | Status  | State | Updated At       |
+----+----------------+----------------+----------+---------+-------+------------------+
|  1 | nova-scheduler | controller.exa | internal | enabled | up    | 2016-08-04T07:29 |
|    |                | mple.com       |          |         |       | :26.000000       |
|  2 | nova-          | controller.exa | internal | enabled | up    | 2016-08-04T07:29 |
|    | consoleauth    | mple.com       |          |         |       | :27.000000       |
|  3 | nova-conductor | controller.exa | internal | enabled | up    | 2016-08-04T07:29 |
|    |                | mple.com       |          |         |       | :27.000000       |
|  7 | nova-compute   | compute.exampl | nova     | enabled | up    | 2016-08-04T07:29 |
|    |                | e.com          |          |         |       | :25.000000       |
+----+----------------+----------------+----------+---------+-------+------------------+

**额,看到上面四个服务已经激活,则说明nova的部署已经完成
谢谢观看**

你可能感兴趣的:(openstack)