[fedora atomic k8s] Add boot from volume support

Support boot from volume for Kubernetes all nodes (master and worker)
so that user can create a big size root volume, which could be more
flexible than using docker_volume_size. And user can specify the
volume type so that user can leverage high performance storage, e.g.
NVMe etc.

And a new label etcd_volme_type is added as well so that user can
set volume type for etcd volume.

If the boot_volume_type or etcd_volume_type are not passed by labels,
Magnum will try to read them from config option
default_boot_volume_type and default_etcd_volume_type. A random
volume type from Cinder will be used if those options are not set.

Task: 30374
Story: 2005386

Co-Authorized-By: Feilong Wang<flwang@catalyst.net.nz>

Change-Id: I39dd456bfa285bf06dd948d11c86867fc03d5afb
This commit is contained in:
Mohammed Naser 2018-12-03 19:45:32 -05:00 committed by Bharat Kunwar
parent 41768e0ae1
commit cfe2753fd3
14 changed files with 356 additions and 24 deletions

View File

@ -346,9 +346,15 @@ the table are linked to more details elsewhere in the user guide.
+---------------------------------------+--------------------+---------------+ +---------------------------------------+--------------------+---------------+
| `docker_volume_type`_ | see below | see below | | `docker_volume_type`_ | see below | see below |
+---------------------------------------+--------------------+---------------+ +---------------------------------------+--------------------+---------------+
| `boot_volume_size`_ | see below | see below |
+---------------------------------------+--------------------+---------------+
| `boot_volume_type`_ | see below | see below |
+---------------------------------------+--------------------+---------------+
| `etcd_volume_size`_ | etcd storage | 0 | | `etcd_volume_size`_ | etcd storage | 0 |
| | volume size | | | | volume size | |
+---------------------------------------+--------------------+---------------+ +---------------------------------------+--------------------+---------------+
| `etcd_volume_type`_ | see below | see below |
+---------------------------------------+--------------------+---------------+
| `container_infra_prefix`_ | see below | "" | | `container_infra_prefix`_ | see below | "" |
+---------------------------------------+--------------------+---------------+ +---------------------------------------+--------------------+---------------+
| `availability_zone`_ | AZ for the cluster | "" | | `availability_zone`_ | AZ for the cluster | "" |
@ -1114,10 +1120,26 @@ _`admission_control_list`
The default value corresponds to the one recommended in this doc The default value corresponds to the one recommended in this doc
for our current Kubernetes version. for our current Kubernetes version.
_`boot_volume_size`
This label overrides the default_boot_volume_size of instances which is
useful if your flavors are boot from volume only. The default value is 0,
meaning that cluster instances will not boot from volume.
_`boot_volume_type`
This label overrides the default_boot_volume_type of instances which is
useful if your flavors are boot from volume only. The default value is '',
meaning that Magnum will randomly select a Cinder volume type from all
available options.
_`etcd_volume_size` _`etcd_volume_size`
This label sets the size of a volume holding the etcd storage data. This label sets the size of a volume holding the etcd storage data.
The default value is 0, meaning the etcd data is not persisted (no volume). The default value is 0, meaning the etcd data is not persisted (no volume).
_`etcd_volume_type`
This label overrides the default_etcd_volume_type holding the etcd storage
data. The default value is '', meaning meaning that Magnum will randomly
select a Cinder volume type from all available options.
_`container_infra_prefix` _`container_infra_prefix`
Prefix of all container images used in the cluster (kubernetes components, Prefix of all container images used in the cluster (kubernetes components,
coredns, kubernetes-dashboard, node-exporter). For example, coredns, kubernetes-dashboard, node-exporter). For example,

View File

@ -117,6 +117,7 @@ python-barbicanclient==4.5.2
python-dateutil==2.7.0 python-dateutil==2.7.0
python-editor==1.0.3 python-editor==1.0.3
python-glanceclient==2.8.0 python-glanceclient==2.8.0
python-cinderclient==2.2.0
python-heatclient==1.10.0 python-heatclient==1.10.0
python-keystoneclient==3.8.0 python-keystoneclient==3.8.0
python-mimeparse==1.6.0 python-mimeparse==1.6.0

46
magnum/common/cinder.py Normal file
View File

@ -0,0 +1,46 @@
# Copyright 2019 Catalyst Cloud Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from oslo_config import cfg
from oslo_log import log as logging
from magnum.common import clients
from magnum.common import exception
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
def get_default_docker_volume_type(context):
return (CONF.cinder.default_docker_volume_type or
_get_random_volume_type(context))
def get_default_boot_volume_type(context):
return (CONF.cinder.default_boot_volume_type or
_get_random_volume_type(context))
def get_default_etcd_volume_type(context):
return (CONF.cinder.default_etcd_volume_type or
_get_random_volume_type(context))
def _get_random_volume_type(context):
c_client = clients.OpenStackClients(context).cinder()
volume_types = c_client.volume_types.list()
if volume_types:
return volume_types[0].name
else:
raise exception.VolumeTypeNotFound()

View File

@ -13,6 +13,7 @@
# under the License. # under the License.
from barbicanclient.v1 import client as barbicanclient from barbicanclient.v1 import client as barbicanclient
from cinderclient.v2 import client as cinder_client
from glanceclient import client as glanceclient from glanceclient import client as glanceclient
from heatclient import client as heatclient from heatclient import client as heatclient
from keystoneauth1.exceptions import catalog from keystoneauth1.exceptions import catalog
@ -41,6 +42,7 @@ class OpenStackClients(object):
self._nova = None self._nova = None
self._neutron = None self._neutron = None
self._octavia = None self._octavia = None
self._cinder = None
def url_for(self, **kwargs): def url_for(self, **kwargs):
return self.keystone().session.get_endpoint(**kwargs) return self.keystone().session.get_endpoint(**kwargs)
@ -207,3 +209,24 @@ class OpenStackClients(object):
} }
self._neutron = neutronclient.Client(**args) self._neutron = neutronclient.Client(**args)
return self._neutron return self._neutron
@exception.wrap_keystone_exception
def cinder(self):
if self._cinder:
return self._cinder
endpoint_type = self._get_client_option('cinder', 'endpoint_type')
region_name = self._get_client_option('cinder', 'region_name')
cinderclient_version = self._get_client_option('cinder', 'api_version')
endpoint = self.url_for(service_type='block-storage',
interface=endpoint_type,
region_name=region_name)
args = {
'cacert': self._get_client_option('cinder', 'ca_file'),
'insecure': self._get_client_option('cinder', 'insecure')
}
session = self.keystone().session
self._cinder = cinder_client.Client(cinderclient_version,
session=session,
endpoint_override=endpoint, **args)
return self._cinder

View File

@ -279,6 +279,12 @@ class OperationInProgress(Invalid):
"progress.") "progress.")
class VolumeTypeNotFound(ResourceNotFound):
"""The code here changed to 400 according to the latest document."""
message = _("Valid volume type could not be found.")
code = 400
class ImageNotFound(ResourceNotFound): class ImageNotFound(ResourceNotFound):
"""The code here changed to 400 according to the latest document.""" """The code here changed to 400 according to the latest document."""
message = _("Image %(image_id)s could not be found.") message = _("Image %(image_id)s could not be found.")

View File

@ -28,12 +28,53 @@ cinder_opts = [
help=_('The default docker volume_type to use for volumes ' help=_('The default docker volume_type to use for volumes '
'used for docker storage. To use the cinder volumes ' 'used for docker storage. To use the cinder volumes '
'for docker storage, you need to select a default ' 'for docker storage, you need to select a default '
'value.'))] 'value. Otherwise, Magnum will select random one from '
'Cinder volume type list.')),
cfg.StrOpt('default_etcd_volume_type',
default='',
help=_('The default etcd volume_type to use for volumes '
'used for etcd storage. To use the cinder volumes '
'for etcd storage, you need to select a default '
'value. Otherwise, Magnum will select random one from '
'Cinder volume type list.')),
cfg.StrOpt('default_boot_volume_type',
default='',
help=_('The default boot volume_type to use for volumes '
'used for VM of COE. To use the cinder volumes '
'for VM of COE, you need to select a default '
'value. Otherwise, Magnum will select random one from '
'Cinder volume type list.')),
cfg.IntOpt('default_boot_volume_size',
default=0,
help=_('The default volume size to use for volumes '
'used for VM of COE.'))
]
cinder_client_opts = [ cinder_client_opts = [
cfg.StrOpt('region_name', cfg.StrOpt('region_name',
help=_('Region in Identity service catalog to use for ' help=_('Region in Identity service catalog to use for '
'communication with the OpenStack service.'))] 'communication with the OpenStack service.')),
cfg.StrOpt('endpoint_type',
default='publicURL',
help=_('Type of endpoint in Identity service catalog to use '
'for communication with the OpenStack service.')),
cfg.StrOpt('api_version',
default='2',
help=_('Version of Cinder API to use in cinderclient.'))
]
common_security_opts = [
cfg.StrOpt('ca_file',
help=_('Optional CA cert file to use in SSL connections.')),
cfg.StrOpt('cert_file',
help=_('Optional PEM-formatted certificate chain file.')),
cfg.StrOpt('key_file',
help=_('Optional PEM-formatted file that contains the '
'private key.')),
cfg.BoolOpt('insecure',
default=False,
help=_("If set, then the server's certificate will not "
"be verified."))]
def register_opts(conf): def register_opts(conf):
@ -41,6 +82,7 @@ def register_opts(conf):
conf.register_group(cinder_client_group) conf.register_group(cinder_client_group)
conf.register_opts(cinder_opts, group=cinder_group) conf.register_opts(cinder_opts, group=cinder_group)
conf.register_opts(cinder_client_opts, group=cinder_client_group) conf.register_opts(cinder_client_opts, group=cinder_client_group)
conf.register_opts(common_security_opts, group=cinder_client_group)
def list_opts(): def list_opts():

View File

@ -15,6 +15,7 @@ import json
from oslo_log import log as logging from oslo_log import log as logging
from oslo_utils import strutils from oslo_utils import strutils
from magnum.common import cinder
from magnum.common import exception from magnum.common import exception
from magnum.common.x509 import operations as x509 from magnum.common.x509 import operations as x509
from magnum.conductor.handlers.common import cert_manager from magnum.conductor.handlers.common import cert_manager
@ -91,11 +92,7 @@ class K8sFedoraTemplateDefinition(k8s_template_def.K8sTemplateDefinition):
osc = self.get_osc(context) osc = self.get_osc(context)
extra_params['region_name'] = osc.cinder_region_name() extra_params['region_name'] = osc.cinder_region_name()
# set docker_volume_type self._set_volumes(context, cluster, extra_params)
# use the configuration default if None provided
docker_volume_type = cluster.labels.get(
'docker_volume_type', CONF.cinder.default_docker_volume_type)
extra_params['docker_volume_type'] = docker_volume_type
extra_params['nodes_affinity_policy'] = \ extra_params['nodes_affinity_policy'] = \
CONF.cluster.nodes_affinity_policy CONF.cluster.nodes_affinity_policy
@ -163,6 +160,7 @@ class K8sFedoraTemplateDefinition(k8s_template_def.K8sTemplateDefinition):
self._set_cert_manager_params(cluster, extra_params) self._set_cert_manager_params(cluster, extra_params)
self._get_keystone_auth_default_policy(extra_params) self._get_keystone_auth_default_policy(extra_params)
self._set_volumes(context, cluster, extra_params)
return super(K8sFedoraTemplateDefinition, return super(K8sFedoraTemplateDefinition,
self).get_params(context, cluster_template, cluster, self).get_params(context, cluster_template, cluster,
@ -214,6 +212,28 @@ class K8sFedoraTemplateDefinition(k8s_template_def.K8sTemplateDefinition):
.replace("$PROJECT_ID", extra_params["project_id"]) .replace("$PROJECT_ID", extra_params["project_id"])
extra_params["keystone_auth_default_policy"] = washed_policy extra_params["keystone_auth_default_policy"] = washed_policy
def _set_volumes(self, context, cluster, extra_params):
# set docker_volume_type
docker_volume_type = cluster.labels.get(
'docker_volume_type',
cinder.get_default_docker_volume_type(context))
extra_params['docker_volume_type'] = docker_volume_type
# set etcd_volume_type
etcd_volume_type = cluster.labels.get(
'etcd_volume_type', cinder.get_default_etcd_volume_type(context))
extra_params['etcd_volume_type'] = etcd_volume_type
# set boot_volume_type
boot_volume_type = cluster.labels.get(
'boot_volume_type', cinder.get_default_boot_volume_type(context))
extra_params['boot_volume_type'] = boot_volume_type
# set boot_volume_size
boot_volume_size = cluster.labels.get(
'boot_volume_size', CONF.cinder.default_boot_volume_size)
extra_params['boot_volume_size'] = boot_volume_size
def get_env_files(self, cluster_template, cluster): def get_env_files(self, cluster_template, cluster):
env_files = [] env_files = []

View File

@ -1,4 +1,4 @@
heat_template_version: 2014-10-16 heat_template_version: 2015-04-30
description: > description: >
This template will boot a Kubernetes cluster with one or more This template will boot a Kubernetes cluster with one or more
@ -143,12 +143,27 @@ parameters:
constraints: constraints:
- allowed_values: ["true", "false"] - allowed_values: ["true", "false"]
boot_volume_size:
type: number
description: >
size of the cinder boot volume for nodes root volume
boot_volume_type:
type: string
description: >
type of the cinder boot volume for nodes root volume
etcd_volume_size: etcd_volume_size:
type: number type: number
description: > description: >
size of the cinder volume for etcd storage size of the cinder volume for etcd storage
default: 0 default: 0
etcd_volume_type:
type: string
description: >
type of a cinder volume for etcd storage
docker_volume_size: docker_volume_size:
type: number type: number
description: > description: >
@ -878,7 +893,10 @@ resources:
master_flavor: {get_param: master_flavor} master_flavor: {get_param: master_flavor}
external_network: {get_param: external_network} external_network: {get_param: external_network}
kube_allow_priv: {get_param: kube_allow_priv} kube_allow_priv: {get_param: kube_allow_priv}
boot_volume_size: {get_param: boot_volume_size}
boot_volume_type: {get_param: boot_volume_type}
etcd_volume_size: {get_param: etcd_volume_size} etcd_volume_size: {get_param: etcd_volume_size}
etcd_volume_type: {get_param: etcd_volume_type}
docker_volume_size: {get_param: docker_volume_size} docker_volume_size: {get_param: docker_volume_size}
docker_volume_type: {get_param: docker_volume_type} docker_volume_type: {get_param: docker_volume_type}
docker_storage_driver: {get_param: docker_storage_driver} docker_storage_driver: {get_param: docker_storage_driver}
@ -1062,6 +1080,8 @@ resources:
etcd_server_ip: {get_attr: [etcd_address_lb_switch, private_ip]} etcd_server_ip: {get_attr: [etcd_address_lb_switch, private_ip]}
external_network: {get_param: external_network} external_network: {get_param: external_network}
kube_allow_priv: {get_param: kube_allow_priv} kube_allow_priv: {get_param: kube_allow_priv}
boot_volume_size: {get_param: boot_volume_size}
boot_volume_type: {get_param: boot_volume_type}
docker_volume_size: {get_param: docker_volume_size} docker_volume_size: {get_param: docker_volume_size}
docker_volume_type: {get_param: docker_volume_type} docker_volume_type: {get_param: docker_volume_type}
docker_storage_driver: {get_param: docker_storage_driver} docker_storage_driver: {get_param: docker_storage_driver}

View File

@ -1,4 +1,4 @@
heat_template_version: 2014-10-16 heat_template_version: queens
description: > description: >
This is a nested stack that defines a single Kubernetes master, This stack is This is a nested stack that defines a single Kubernetes master, This stack is
@ -39,11 +39,27 @@ parameters:
constraints: constraints:
- allowed_values: ["true", "false"] - allowed_values: ["true", "false"]
boot_volume_size:
type: number
description: >
size of the cinder boot volume for nodes root volume
default: 0
boot_volume_type:
type: string
description: >
type of the cinder boot volume for nodes root volume
etcd_volume_size: etcd_volume_size:
type: number type: number
description: > description: >
size of a cinder volume to allocate for etcd storage size of a cinder volume to allocate for etcd storage
etcd_volume_type:
type: string
description: >
type of a cinder volume to allocate for etcd storage
docker_volume_size: docker_volume_size:
type: number type: number
description: > description: >
@ -510,6 +526,15 @@ parameters:
default: default:
true true
conditions:
image_based: {equals: [{get_param: boot_volume_size}, 0]}
volume_based:
not:
equals:
- get_param: boot_volume_size
- 0
resources: resources:
###################################################################### ######################################################################
# #
@ -676,7 +701,7 @@ resources:
properties: properties:
signal_transport: HEAT_SIGNAL signal_transport: HEAT_SIGNAL
config: {get_resource: master_config} config: {get_resource: master_config}
server: {get_resource: kube-master} server: {if: ["volume_based", {get_resource: kube-master-bfv}, {get_resource: kube-master}]}
actions: ['CREATE'] actions: ['CREATE']
###################################################################### ######################################################################
@ -684,11 +709,20 @@ resources:
# a single kubernetes master. # a single kubernetes master.
# #
kube_node_volume:
type: OS::Cinder::Volume
condition: volume_based
properties:
image: {get_param: server_image}
size: {get_param: boot_volume_size}
volume_type: {get_param: boot_volume_type}
# do NOT use "_" (underscore) in the Nova server name # do NOT use "_" (underscore) in the Nova server name
# it creates a mismatch between the generated Nova name and its hostname # it creates a mismatch between the generated Nova name and its hostname
# which can lead to weird problems # which can lead to weird problems
kube-master: kube-master:
type: OS::Nova::Server type: OS::Nova::Server
condition: image_based
properties: properties:
name: {get_param: name} name: {get_param: name}
image: {get_param: server_image} image: {get_param: server_image}
@ -702,6 +736,25 @@ resources:
scheduler_hints: { group: { get_param: nodes_server_group_id }} scheduler_hints: { group: { get_param: nodes_server_group_id }}
availability_zone: {get_param: availability_zone} availability_zone: {get_param: availability_zone}
kube-master-bfv:
type: OS::Nova::Server
condition: volume_based
properties:
name: {get_param: name}
flavor: {get_param: master_flavor}
key_name: {get_param: ssh_key_name}
user_data_format: SOFTWARE_CONFIG
software_config_transport: POLL_SERVER_HEAT
user_data: {get_resource: agent_config}
networks:
- port: {get_resource: kube_master_eth0}
scheduler_hints: { group: { get_param: nodes_server_group_id }}
availability_zone: {get_param: availability_zone}
block_device_mapping_v2:
- boot_index: 0
volume_id: {get_resource: kube_node_volume}
delete_on_termination: true
kube_master_eth0: kube_master_eth0:
type: OS::Neutron::Port type: OS::Neutron::Port
properties: properties:
@ -746,11 +799,12 @@ resources:
type: Magnum::Optional::Etcd::Volume type: Magnum::Optional::Etcd::Volume
properties: properties:
size: {get_param: etcd_volume_size} size: {get_param: etcd_volume_size}
volume_type: {get_param: etcd_volume_type}
etcd_volume_attach: etcd_volume_attach:
type: Magnum::Optional::Etcd::VolumeAttachment type: Magnum::Optional::Etcd::VolumeAttachment
properties: properties:
instance_uuid: {get_resource: kube-master} instance_uuid: {if: ["volume_based", {get_resource: kube-master-bfv}, {get_resource: kube-master}]}
volume_id: {get_resource: etcd_volume} volume_id: {get_resource: etcd_volume}
mountpoint: /dev/vdc mountpoint: /dev/vdc
@ -769,7 +823,7 @@ resources:
docker_volume_attach: docker_volume_attach:
type: Magnum::Optional::Cinder::VolumeAttachment type: Magnum::Optional::Cinder::VolumeAttachment
properties: properties:
instance_uuid: {get_resource: kube-master} instance_uuid: {if: ["volume_based", {get_resource: kube-master-bfv}, {get_resource: kube-master}]}
volume_id: {get_resource: docker_volume} volume_id: {get_resource: docker_volume}
mountpoint: /dev/vdb mountpoint: /dev/vdb
@ -787,7 +841,7 @@ resources:
properties: properties:
signal_transport: HEAT_SIGNAL signal_transport: HEAT_SIGNAL
config: {get_resource: upgrade_kubernetes} config: {get_resource: upgrade_kubernetes}
server: {get_resource: kube-master} server: {if: ["volume_based", {get_resource: kube-master-bfv}, {get_resource: kube-master}]}
actions: ['UPDATE'] actions: ['UPDATE']
input_values: input_values:
kube_tag_input: {get_param: kube_tag} kube_tag_input: {get_param: kube_tag}
@ -795,7 +849,7 @@ resources:
outputs: outputs:
OS::stack_id: OS::stack_id:
value: { get_resource: kube-master } value: {if: ["volume_based", {get_resource: kube-master-bfv}, {get_resource: kube-master}]}
kube_master_ip: kube_master_ip:
value: {get_attr: [kube_master_eth0, fixed_ips, 0, ip_address]} value: {get_attr: [kube_master_eth0, fixed_ips, 0, ip_address]}

View File

@ -1,4 +1,4 @@
heat_template_version: 2014-10-16 heat_template_version: queens
description: > description: >
This is a nested stack that defines a single Kubernetes minion, This stack is This is a nested stack that defines a single Kubernetes minion, This stack is
@ -34,6 +34,16 @@ parameters:
constraints: constraints:
- allowed_values: ["true", "false"] - allowed_values: ["true", "false"]
boot_volume_size:
type: number
description: >
size of the cinder boot volume
boot_volume_type:
type: string
description: >
type of the cinder boot volume
docker_volume_size: docker_volume_size:
type: number type: number
description: > description: >
@ -294,6 +304,15 @@ parameters:
default: default:
true true
conditions:
image_based: {equals: [{get_param: boot_volume_size}, 0]}
volume_based:
not:
equals:
- get_param: boot_volume_size
- 0
resources: resources:
agent_config: agent_config:
@ -399,7 +418,7 @@ resources:
properties: properties:
signal_transport: HEAT_SIGNAL signal_transport: HEAT_SIGNAL
config: {get_resource: node_config} config: {get_resource: node_config}
server: {get_resource: kube-minion} server: {if: ["volume_based", {get_resource: kube-minion-bfv}, {get_resource: kube-minion}]}
actions: ['CREATE'] actions: ['CREATE']
###################################################################### ######################################################################
@ -407,14 +426,38 @@ resources:
# a single kubernetes minion. # a single kubernetes minion.
# #
kube_node_volume:
type: OS::Cinder::Volume
condition: volume_based
properties:
image: {get_param: server_image}
size: {get_param: boot_volume_size}
volume_type: {get_param: boot_volume_type}
# do NOT use "_" (underscore) in the Nova server name # do NOT use "_" (underscore) in the Nova server name
# it creates a mismatch between the generated Nova name and its hostname # it creates a mismatch between the generated Nova name and its hostname
# which can lead to weird problems # which can lead to weird problems
kube-minion: kube-minion:
condition: image_based
type: OS::Nova::Server type: OS::Nova::Server
properties: properties:
name: {get_param: name} name: {get_param: name}
flavor: {get_param: minion_flavor}
image: {get_param: server_image} image: {get_param: server_image}
key_name: {get_param: ssh_key_name}
user_data: {get_resource: agent_config}
user_data_format: SOFTWARE_CONFIG
software_config_transport: POLL_SERVER_HEAT
networks:
- port: {get_resource: kube_minion_eth0}
scheduler_hints: { group: { get_param: nodes_server_group_id }}
availability_zone: {get_param: availability_zone}
kube-minion-bfv:
condition: volume_based
type: OS::Nova::Server
properties:
name: {get_param: name}
flavor: {get_param: minion_flavor} flavor: {get_param: minion_flavor}
key_name: {get_param: ssh_key_name} key_name: {get_param: ssh_key_name}
user_data: {get_resource: agent_config} user_data: {get_resource: agent_config}
@ -424,6 +467,10 @@ resources:
- port: {get_resource: kube_minion_eth0} - port: {get_resource: kube_minion_eth0}
scheduler_hints: { group: { get_param: nodes_server_group_id }} scheduler_hints: { group: { get_param: nodes_server_group_id }}
availability_zone: {get_param: availability_zone} availability_zone: {get_param: availability_zone}
block_device_mapping_v2:
- boot_index: 0
volume_id: {get_resource: kube_node_volume}
delete_on_termination: true
kube_minion_eth0: kube_minion_eth0:
type: OS::Neutron::Port type: OS::Neutron::Port
@ -458,7 +505,7 @@ resources:
docker_volume_attach: docker_volume_attach:
type: Magnum::Optional::Cinder::VolumeAttachment type: Magnum::Optional::Cinder::VolumeAttachment
properties: properties:
instance_uuid: {get_resource: kube-minion} instance_uuid: {if: ["volume_based", {get_resource: kube-minion-bfv}, {get_resource: kube-minion}]}
volume_id: {get_resource: docker_volume} volume_id: {get_resource: docker_volume}
mountpoint: /dev/vdb mountpoint: /dev/vdb
@ -476,7 +523,7 @@ resources:
properties: properties:
signal_transport: HEAT_SIGNAL signal_transport: HEAT_SIGNAL
config: {get_resource: upgrade_kubernetes} config: {get_resource: upgrade_kubernetes}
server: {get_resource: kube-minion} server: {if: ["volume_based", {get_resource: kube-minion-bfv}, {get_resource: kube-minion}]}
actions: ['UPDATE'] actions: ['UPDATE']
input_values: input_values:
kube_tag_input: {get_param: kube_tag} kube_tag_input: {get_param: kube_tag}
@ -505,6 +552,6 @@ outputs:
###################################################################### ######################################################################
OS::stack_id: OS::stack_id:
value: { get_resource: kube-minion } value: {if: ["volume_based", {get_resource: kube-minion-bfv}, {get_resource: kube-minion}]}
description: > description: >
This is the Nova server id of the node. This is the Nova server id of the node.

View File

@ -114,7 +114,8 @@ class TestClusterConductorWithK8s(base.TestCase):
'kubescheduler_options': '--kubescheduler', 'kubescheduler_options': '--kubescheduler',
'kubeproxy_options': '--kubeproxy', 'kubeproxy_options': '--kubeproxy',
'influx_grafana_dashboard_enabled': 'True', 'influx_grafana_dashboard_enabled': 'True',
'service_cluster_ip_range': '10.254.0.0/16'}, 'service_cluster_ip_range': '10.254.0.0/16',
'boot_volume_size': '60'},
'master_flavor_id': 'master_flavor_id', 'master_flavor_id': 'master_flavor_id',
'flavor_id': 'flavor_id', 'flavor_id': 'flavor_id',
'project_id': 'project_id', 'project_id': 'project_id',
@ -175,6 +176,10 @@ class TestClusterConductorWithK8s(base.TestCase):
self.mock_enable_octavia = octavia_patcher.start() self.mock_enable_octavia = octavia_patcher.start()
self.mock_enable_octavia.return_value = False self.mock_enable_octavia.return_value = False
self.addCleanup(octavia_patcher.stop) self.addCleanup(octavia_patcher.stop)
CONF.set_override('default_boot_volume_type',
'lvmdriver-1', group='cinder')
CONF.set_override('default_etcd_volume_type',
'lvmdriver-1', group='cinder')
@patch('requests.get') @patch('requests.get')
@patch('magnum.objects.ClusterTemplate.get_by_uuid') @patch('magnum.objects.ClusterTemplate.get_by_uuid')
@ -262,6 +267,8 @@ class TestClusterConductorWithK8s(base.TestCase):
'kube_dashboard_enabled': 'True', 'kube_dashboard_enabled': 'True',
'influx_grafana_dashboard_enabled': 'True', 'influx_grafana_dashboard_enabled': 'True',
'docker_volume_type': 'lvmdriver-1', 'docker_volume_type': 'lvmdriver-1',
'boot_volume_type': 'lvmdriver-1',
'etcd_volume_type': 'lvmdriver-1',
'etcd_volume_size': None, 'etcd_volume_size': None,
'availability_zone': 'az_1', 'availability_zone': 'az_1',
'cert_manager_api': 'False', 'cert_manager_api': 'False',
@ -349,7 +356,10 @@ class TestClusterConductorWithK8s(base.TestCase):
'max_node_count': 2, 'max_node_count': 2,
'master_image': 'image_id', 'master_image': 'image_id',
'minion_image': 'image_id', 'minion_image': 'image_id',
'keystone_auth_default_policy': self.keystone_auth_default_policy 'keystone_auth_default_policy': self.keystone_auth_default_policy,
'boot_volume_size': '60',
'boot_volume_type': 'lvmdriver-1',
'etcd_volume_type': 'lvmdriver-1'
} }
if missing_attr is not None: if missing_attr is not None:
expected.pop(mapping[missing_attr], None) expected.pop(mapping[missing_attr], None)
@ -489,7 +499,10 @@ class TestClusterConductorWithK8s(base.TestCase):
'max_node_count': 2, 'max_node_count': 2,
'master_image': 'image_id', 'master_image': 'image_id',
'minion_image': 'image_id', 'minion_image': 'image_id',
'keystone_auth_default_policy': self.keystone_auth_default_policy 'keystone_auth_default_policy': self.keystone_auth_default_policy,
'boot_volume_size': '60',
'boot_volume_type': 'lvmdriver-1',
'etcd_volume_type': 'lvmdriver-1'
} }
self.assertEqual(expected, definition) self.assertEqual(expected, definition)
@ -611,7 +624,10 @@ class TestClusterConductorWithK8s(base.TestCase):
'max_node_count': 2, 'max_node_count': 2,
'master_image': None, 'master_image': None,
'minion_image': None, 'minion_image': None,
'keystone_auth_default_policy': self.keystone_auth_default_policy 'keystone_auth_default_policy': self.keystone_auth_default_policy,
'boot_volume_size': '60',
'boot_volume_type': 'lvmdriver-1',
'etcd_volume_type': 'lvmdriver-1'
} }
self.assertEqual(expected, definition) self.assertEqual(expected, definition)
self.assertEqual( self.assertEqual(
@ -1044,7 +1060,10 @@ class TestClusterConductorWithK8s(base.TestCase):
'max_node_count': 2, 'max_node_count': 2,
'master_image': 'image_id', 'master_image': 'image_id',
'minion_image': 'image_id', 'minion_image': 'image_id',
'keystone_auth_default_policy': self.keystone_auth_default_policy 'keystone_auth_default_policy': self.keystone_auth_default_policy,
'boot_volume_size': '60',
'boot_volume_type': 'lvmdriver-1',
'etcd_volume_type': 'lvmdriver-1'
} }
self.assertEqual(expected, definition) self.assertEqual(expected, definition)
self.assertEqual( self.assertEqual(

View File

@ -479,6 +479,8 @@ class AtomicK8sTemplateDefinitionTestCase(BaseK8sTemplateDefinitionTestCase):
'influx_grafana_dashboard_enabled') 'influx_grafana_dashboard_enabled')
docker_volume_type = mock_cluster.labels.get( docker_volume_type = mock_cluster.labels.get(
'docker_volume_type') 'docker_volume_type')
boot_volume_size = mock_cluster.labels.get(
'boot_volume_size')
etcd_volume_size = mock_cluster.labels.get( etcd_volume_size = mock_cluster.labels.get(
'etcd_volume_size') 'etcd_volume_size')
kube_tag = mock_cluster.labels.get('kube_tag') kube_tag = mock_cluster.labels.get('kube_tag')
@ -568,6 +570,9 @@ class AtomicK8sTemplateDefinitionTestCase(BaseK8sTemplateDefinitionTestCase):
npd_enabled = mock_cluster.labels.get('npd_enabled') npd_enabled = mock_cluster.labels.get('npd_enabled')
master_image = mock_cluster_template.image_id master_image = mock_cluster_template.image_id
minion_image = mock_cluster_template.image_id minion_image = mock_cluster_template.image_id
boot_volume_size = mock_cluster.labels.get('boot_volume_size')
boot_volume_type = mock_cluster.labels.get('boot_volume_type')
etcd_volume_type = mock_cluster.labels.get('etcd_volume_type')
k8s_def = k8sa_tdef.AtomicK8sTemplateDefinition() k8s_def = k8sa_tdef.AtomicK8sTemplateDefinition()
@ -587,6 +592,7 @@ class AtomicK8sTemplateDefinitionTestCase(BaseK8sTemplateDefinitionTestCase):
'influx_grafana_dashboard_enabled': 'influx_grafana_dashboard_enabled':
influx_grafana_dashboard_enabled, influx_grafana_dashboard_enabled,
'docker_volume_type': docker_volume_type, 'docker_volume_type': docker_volume_type,
'boot_volume_size': boot_volume_size,
'etcd_volume_size': etcd_volume_size, 'etcd_volume_size': etcd_volume_size,
'kubelet_options': kubelet_options, 'kubelet_options': kubelet_options,
'kubeapi_options': kubeapi_options, 'kubeapi_options': kubeapi_options,
@ -650,6 +656,9 @@ class AtomicK8sTemplateDefinitionTestCase(BaseK8sTemplateDefinitionTestCase):
'kube_version': kube_tag, 'kube_version': kube_tag,
'master_kube_tag': kube_tag, 'master_kube_tag': kube_tag,
'minion_kube_tag': kube_tag, 'minion_kube_tag': kube_tag,
'boot_volume_size': boot_volume_size,
'boot_volume_type': boot_volume_type,
'etcd_volume_type': etcd_volume_type
}} }}
mock_get_params.assert_called_once_with(mock_context, mock_get_params.assert_called_once_with(mock_context,
mock_cluster_template, mock_cluster_template,
@ -908,6 +917,8 @@ class AtomicK8sTemplateDefinitionTestCase(BaseK8sTemplateDefinitionTestCase):
'influx_grafana_dashboard_enabled') 'influx_grafana_dashboard_enabled')
docker_volume_type = mock_cluster.labels.get( docker_volume_type = mock_cluster.labels.get(
'docker_volume_type') 'docker_volume_type')
boot_volume_size = mock_cluster.labels.get(
'boot_volume_size')
etcd_volume_size = mock_cluster.labels.get( etcd_volume_size = mock_cluster.labels.get(
'etcd_volume_size') 'etcd_volume_size')
kube_tag = mock_cluster.labels.get('kube_tag') kube_tag = mock_cluster.labels.get('kube_tag')
@ -997,6 +1008,9 @@ class AtomicK8sTemplateDefinitionTestCase(BaseK8sTemplateDefinitionTestCase):
npd_enabled = mock_cluster.labels.get('npd_enabled') npd_enabled = mock_cluster.labels.get('npd_enabled')
master_image = mock_cluster_template.image_id master_image = mock_cluster_template.image_id
minion_image = mock_cluster_template.image_id minion_image = mock_cluster_template.image_id
boot_volume_size = mock_cluster.labels.get('boot_volume_size')
boot_volume_type = mock_cluster.labels.get('boot_volume_type')
etcd_volume_type = mock_cluster.labels.get('etcd_volume_type')
k8s_def = k8sa_tdef.AtomicK8sTemplateDefinition() k8s_def = k8sa_tdef.AtomicK8sTemplateDefinition()
@ -1017,6 +1031,7 @@ class AtomicK8sTemplateDefinitionTestCase(BaseK8sTemplateDefinitionTestCase):
'influx_grafana_dashboard_enabled': 'influx_grafana_dashboard_enabled':
influx_grafana_dashboard_enabled, influx_grafana_dashboard_enabled,
'docker_volume_type': docker_volume_type, 'docker_volume_type': docker_volume_type,
'boot_volume_size': boot_volume_size,
'etcd_volume_size': etcd_volume_size, 'etcd_volume_size': etcd_volume_size,
'kubelet_options': kubelet_options, 'kubelet_options': kubelet_options,
'kubeapi_options': kubeapi_options, 'kubeapi_options': kubeapi_options,
@ -1081,6 +1096,9 @@ class AtomicK8sTemplateDefinitionTestCase(BaseK8sTemplateDefinitionTestCase):
'kube_version': kube_tag, 'kube_version': kube_tag,
'master_kube_tag': kube_tag, 'master_kube_tag': kube_tag,
'minion_kube_tag': kube_tag, 'minion_kube_tag': kube_tag,
'boot_volume_size': boot_volume_size,
'boot_volume_type': boot_volume_type,
'etcd_volume_type': etcd_volume_type
}} }}
mock_get_params.assert_called_once_with(mock_context, mock_get_params.assert_called_once_with(mock_context,
mock_cluster_template, mock_cluster_template,

View File

@ -0,0 +1,13 @@
---
features:
- |
Support boot from volume for Kubernetes all nodes (master and worker)
so that user can create a big size root volume, which could be more
flexible than using docker_volume_size. And user can specify the
volume type so that user can leverage high performance storage, e.g.
NVMe etc. And a new label etcd_volme_type is added as well so that
user can set volume type for etcd volume. If the boot_volume_type
or etcd_volume_type are not passed by labels, Magnum will try to
read them from config option default_boot_volume_type and
default_etcd_volume_type. A random volume type from Cinder will
be used if those options are not set.

View File

@ -42,6 +42,7 @@ pbr!=2.1.0,>=2.0.0 # Apache-2.0
pecan!=1.0.2,!=1.0.3,!=1.0.4,!=1.2,>=1.0.0 # BSD pecan!=1.0.2,!=1.0.3,!=1.0.4,!=1.2,>=1.0.0 # BSD
pycadf!=2.0.0,>=1.1.0 # Apache-2.0 pycadf!=2.0.0,>=1.1.0 # Apache-2.0
python-barbicanclient>=4.5.2 # Apache-2.0 python-barbicanclient>=4.5.2 # Apache-2.0
python-cinderclient>=2.2.0 # Apache-2.0
python-glanceclient>=2.8.0 # Apache-2.0 python-glanceclient>=2.8.0 # Apache-2.0
python-heatclient>=1.10.0 # Apache-2.0 python-heatclient>=1.10.0 # Apache-2.0
python-neutronclient>=6.7.0 # Apache-2.0 python-neutronclient>=6.7.0 # Apache-2.0