Skip to content

Commit 0355a8a

Browse files
committed
salt/volumes: Detach devices before (up|down)grade
Since the management of loop devices changed between 2.6 and 2.7 (introduction of systemd units for this purpose), we want to ensure there will not be duplicated devices pointing to the same sparse file. To do this, we introduce a "cleanup" formula, which can operate in two modes, 'upgrade' and 'downgrade' (controlled via pillar). For upgrade, we manage this cleanup in the `deploy_node` orchestrate, during the drain. For downgrade, we cannot change the `deploy_node` orchestrate, so we manually drain and cleanup from the `downgrade` orchestrate. See: scality#2982
1 parent eb36b59 commit 0355a8a

File tree

7 files changed

+189
-2
lines changed

7 files changed

+189
-2
lines changed

buildchain/buildchain/salt_tree.py

+1
Original file line numberDiff line numberDiff line change
@@ -669,6 +669,7 @@ def _get_parts(self) -> Iterator[str]:
669669
Path('salt/metalk8s/utils/httpd-tools/init.sls'),
670670
Path('salt/metalk8s/utils/httpd-tools/installed.sls'),
671671

672+
Path('salt/metalk8s/volumes/cleanup.sls'),
672673
Path('salt/metalk8s/volumes/init.sls'),
673674
Path('salt/metalk8s/volumes/installed.sls'),
674675
Path('salt/metalk8s/volumes/prepared.sls'),

docs/operation/downgrade.rst

+7
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@ packaged with your current installation.
55
This section describes a reliable downgrade procedure for **MetalK8s**
66
including all the components that are included in the stack.
77

8+
.. warning::
9+
10+
Downgrading a single-node cluster from 2.7.x to 2.6.y with
11+
``SparseLoopDevice`` volumes will force draining of this single node.
12+
Expect an interruption of service during this downgrade (production
13+
deployments **should not** rely on ``SparseLoopDevice`` volumes).
14+
815
Supported Versions
916
******************
1017

docs/operation/upgrade.rst

+7
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@ with every new release.
55
This section describes a reliable upgrade procedure for **MetalK8s** including
66
all the components that are included in the stack.
77

8+
.. warning::
9+
10+
Upgrading a single-node cluster from 2.6.x to 2.7.y with
11+
``SparseLoopDevice`` volumes will force draining of this single node.
12+
Expect an interruption of service during this upgrade (production
13+
deployments **should not** rely on ``SparseLoopDevice`` volumes).
14+
815
Supported Versions
916
******************
1017
.. note::

salt/metalk8s/orchestrate/deploy_node.sls

+50-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
{%- set node_name = pillar.orchestrate.node_name %}
2+
{%- set run_drain = not pillar.orchestrate.get('skip_draining', False) %}
3+
{%- set cleanup_loop_devices = pillar.orchestrate.get('cleanup_loop_devices', False) %}
24
{%- set version = pillar.metalk8s.nodes[node_name].version %}
35

46
{%- set skip_roles = pillar.metalk8s.nodes[node_name].get('skip_roles', []) %}
@@ -70,7 +72,27 @@ Cordon the node:
7072
metalk8s_cordon.node_cordoned:
7173
- name: {{ node_name }}
7274

73-
{%- if not pillar.orchestrate.get('skip_draining', False) %}
75+
{%- if run_drain or cleanup_loop_devices %}
76+
{%- if not run_drain %}
77+
{# Check if we can avoid the drain (if no loop device needs cleaning up) #}
78+
{%- set volumes = salt.metalk8s_kubernetes.list_objects(
79+
kind="Volume", apiVersion="storage.metalk8s.scality.com/v1alpha1",
80+
) %}
81+
{%- set volumes_to_clean = volumes
82+
| selectattr('spec.nodeName', 'equalto', node_name)
83+
| selectattr('spec.sparseLoopDevice', 'defined')
84+
| list
85+
%}
86+
{%- if volumes_to_clean %}
87+
{%- do salt.log.warning(
88+
'Forcing drain to clean up loop devices for the following Volumes: '
89+
~ volumes_to_clean | map(attribute='metadata.name') | join(', ')
90+
) %}
91+
{%- set run_drain = True %}
92+
{%- endif %}
93+
{%- endif %}
94+
95+
{%- if run_drain %}
7496

7597
Drain the node:
7698
metalk8s_drain.node_drained:
@@ -83,6 +105,7 @@ Drain the node:
83105
- require_in:
84106
- salt: Run the highstate
85107

108+
{%- endif %}
86109
{%- endif %}
87110

88111
{%- if node_name in salt.saltutil.runner('manage.up') %}
@@ -131,6 +154,31 @@ Wait minion available:
131154

132155
{%- endif %}
133156

157+
{#- Nodes in 2.6 or lower rely on manual provisioning of loop devices, which
158+
we need to clean-up to avoid having duplicates generated by the systemd
159+
units, introduced in 2.7
160+
Note that we run this before `metalk8s.roles.etcd`, since this would
161+
provision the aforementioned loop devices (because `etcd` role includes
162+
`node` role, which takes care of volume provisioning) #}
163+
{%- if cleanup_loop_devices %}
164+
165+
Cleanup existing loop devices:
166+
salt.state:
167+
- tgt: {{ node_name }}
168+
- saltenv: metalk8s-{{ version }}
169+
- sls:
170+
- metalk8s.volumes.cleanup
171+
- require:
172+
- salt: Wait minion available
173+
- metalk8s_drain: Drain the node
174+
- require_in:
175+
{%- if 'etcd' in roles and 'etcd' not in skip_roles %}
176+
- salt: Check pillar before etcd deployment
177+
{%- endif %}
178+
- http: Wait for API server to be available before highstate
179+
180+
{%- endif %}
181+
134182
{%- if 'etcd' in roles and 'etcd' not in skip_roles %}
135183

136184
Check pillar before etcd deployment:
@@ -171,7 +219,7 @@ Install etcd node:
171219
Register the node into etcd cluster:
172220
salt.runner:
173221
- name: state.orchestrate
174-
- pillar: {{ pillar | json }}
222+
- pillar: {{ pillar | json }}
175223
- mods:
176224
- metalk8s.orchestrate.register_etcd
177225
- require:

salt/metalk8s/orchestrate/downgrade/init.sls

+52
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,60 @@ Upgrade salt-minion on {{ node }}:
7373
node_name: {{ node }}
7474
- require:
7575
- metalk8s_kubernetes: Set node {{ node }} version to {{ dest_version }}
76+
77+
{%- set force_drain = True %}
78+
79+
{%- if pillar.metalk8s.nodes|length == 1 %}
80+
{# Check if we can avoid the drain (if no loop device needs cleaning up) #}
81+
{%- set volumes = salt.metalk8s_kubernetes.list_objects(
82+
kind="Volume", apiVersion="storage.metalk8s.scality.com/v1alpha1",
83+
) %}
84+
{%- set volumes_to_clean = volumes
85+
| selectattr('spec.nodeName', 'equalto', node)
86+
| selectattr('spec.sparseLoopDevice', 'defined')
87+
| list
88+
%}
89+
{%- if volumes_to_clean %}
90+
{%- do salt.log.warning(
91+
'Forcing drain to clean up loop devices for the following Volumes: '
92+
~ volumes_to_clean | map(attribute='metadata.name') | join(', ')
93+
) %}
94+
{%- else %}
95+
{%- set force_drain = False %}
96+
{%- endif %}
97+
{%- endif %}
98+
99+
{%- if force_drain %}
100+
# We force a node drain manually to handle cleanup of loop devices outside
101+
# of the `deploy_node` orchestrate, since it comes from `dest_version`.
102+
# The `deploy_node` orchestrate will take care of uncordoning.
103+
Cordon node {{ node }}:
104+
metalk8s_cordon.node_cordoned:
105+
- name: {{ node }}
106+
- require:
107+
- salt: Upgrade salt-minion on {{ node }}
108+
109+
Drain node {{ node }}:
110+
metalk8s_drain.node_drained:
111+
- name: {{ node }}
112+
- ignore_daemonset: True
113+
- delete_local_data: True
114+
- force: True
115+
- require:
116+
- metalk8s_cordon: Cordon node {{ node }}
117+
118+
Cleanup loop devices from node {{ node }}:
119+
salt.state:
120+
- tgt: {{ node }}
121+
- saltenv: {{ saltenv }} {#- Use current version of this formula #}
122+
- sls:
123+
- metalk8s.volumes.cleanup
124+
- require:
125+
- metalk8s_drain: Drain node {{ node }}
76126
- require_in:
77127
- salt: Deploy node {{ node }}
128+
129+
{%- endif %}
78130
{%- endif %}
79131

80132
Deploy node {{ node }}:

salt/metalk8s/orchestrate/upgrade/init.sls

+5
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,11 @@ Deploy node {{ node }}:
108108
{#- Do not drain if we are in single node cluster #}
109109
skip_draining: True
110110
{%- endif %}
111+
{%- if salt.pkg.version_cmp(node_version, '2.7.0') < 0 %}
112+
{#- Nodes in 2.6 or lower rely on manual provisioning of loop
113+
devices, which we need to clean-up while the node is drained #}
114+
cleanup_loop_devices: True
115+
{%- endif %}
111116
- require:
112117
- metalk8s_kubernetes: Set node {{ node }} version to {{ dest_version }}
113118
- require_in:

salt/metalk8s/volumes/cleanup.sls

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Clean already attached loop devices before applying highstate, to avoid
2+
# creating duplicates for a same sparse file.
3+
# This state can cleanup either "manually", for upgrade, or by disabling the
4+
# systemd units, for downgrade.
5+
{%- set target_version = pillar.metalk8s.nodes[grains.id].version %}
6+
7+
{%- if '_errors' in pillar.metalk8s.volumes %}
8+
9+
Cannot proceed with volume cleanup (pillar errors):
10+
test.fail_without_changes:
11+
- comment: 'Errors: {{ pillar.metalk8s.volumes._errors | join (", ") }}'
12+
13+
{%- else %}
14+
{%- set sparse_volumes = pillar.metalk8s.volumes.values()
15+
| selectattr('spec.sparseLoopDevice', 'defined')
16+
| list %}
17+
18+
{%- if not sparse_volumes %}
19+
20+
Nothing to cleanup:
21+
test.succeed_without_changes: []
22+
23+
{%- else %}
24+
{%- set target_is_2_7 = salt.pkg.version_cmp(target_version, '2.7.0') >= 0 %}
25+
{%- if target_is_2_7 %}
26+
27+
include:
28+
- .installed
29+
30+
{%- endif %}
31+
32+
{%- for volume in sparse_volumes %}
33+
{%- set volume_name = volume.metadata.name %}
34+
{%- set volume_id = volume.metadata.uid %}
35+
{%- if target_is_2_7 %}
36+
37+
Cleanup loop device for Volume {{ volume_name }}:
38+
cmd.run:
39+
- name: /usr/local/libexec/metalk8s-sparse-volume-cleanup "{{ volume_id }}"
40+
# Only cleanup if the systemd unit doesn't exist yet (this command exits
41+
# with retcode 3 if the service is dead, 4 if the template does not exist)
42+
- unless: systemctl status metalk8s-sparse-volume@{{ volume_id }}
43+
- require:
44+
- test: Ensure Python 3 is available
45+
- file: Install clean-up script
46+
47+
{%- else %} {# target_version < 2.7.0 #}
48+
49+
Disable systemd unit for Volume {{ volume_name }}:
50+
service.dead:
51+
- name: metalk8s-sparse-volume@{{ volume_id }}
52+
- enable: false
53+
- require_in:
54+
- file: Remove systemd template unit for sparse loop device provisioning
55+
56+
{%- endif %}
57+
{%- endfor %}
58+
{%- endif %}
59+
60+
{%- if not target_is_2_7 %}
61+
62+
Remove systemd template unit for sparse loop device provisioning:
63+
file.absent:
64+
- name: /etc/systemd/system/[email protected]
65+
66+
{%- endif %}
67+
{%- endif %}

0 commit comments

Comments
 (0)