support for partitioning and update rook version to 1.6.0
[infra/stack/kubernetes.git] / scenarios / k8-multus-dualstack.yaml
1 ---
2 # ============LICENSE_START=======================================================
3 #  Copyright (C) 2021 The Nordix Foundation. All rights reserved.
4 # ================================================================================
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 #      http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16 #
17 # SPDX-License-Identifier: Apache-2.0
18 # ============LICENSE_END=========================================================
19
20 # ============README_START========================================================
21 # This scenario configures a Kubernetes cluster with the Multus CNI backed by
22 # Calico, and configured with IPv6 addresses on the 'public' network. Currently
23 # only the ubuntu1804 base OS is supported, using the bifrost provisioner.
24 #
25 # WARNING: DUAL-STACK IS AN ALPHA FEATURE AS OF 1.16. HERE BE DRAGONS.
26 #
27 # To run this scenario, the following is required:
28 #   - Kubernetes version >= 1.16. [1]
29 #   - Usage of bifrost provisioner
30 #   - Base OS == ubuntu1804
31 #   - A compatible version of Kubespray [2].
32 #   - Additional Ansible variables `libvirt_ipv6_networks`, `node_ipv6_addresses`
33 #     specified (for example, by the environment variable ENGINE_ANSIBLE_PARAMS).
34 #     Default values are provided below.
35 #
36 # The following tasks are performed prior to running the Kubespray installer:
37 #   - libvirt networks specified in `libvirt_ipv6_networks` are re-created with
38 #     the addition of an IPv6 stanza.
39 #   - The hosts specified in `node_ipv6_addresses` have the provided address
40 #     configured by dropping an additional file `$INTERFACE-ipv6.cfg` into
41 #     `/etc/network/interfaces.d/`. An additional reboot of the instances is
42 #     carried out to ensure the interfaces are configured properly.
43 #   - The parameter `enable_dual_stack_networks: true` is set in the Kubespray
44 #     configuration file on-disk.
45 #   - The Kubelet configuration file `/etc/kubernetes/kubelet.env` is modified to
46 #     append the corresponding IPv6 address of the node's interface to the node-ip
47 #     parameter. This ensures that nodes are aware of both their IPv4 and IPv6
48 #     interfaces.
49 #   - The feature gate is added: `IPv6DualStack=true`
50 #
51 # NOTES:
52 # [1] Assigning dual-stack addresses to kubelet via --node-ip=$IPV4,$IPV6
53 #     requires v1.20, see: https://github.com/kubernetes/kubernetes/issues/95239)
54 # [2] Kubespray supports IPv4/IPv6 DualStack as of commit c2c97c3.
55 # ============README_END==========================================================
56
57 - hosts: localhost
58   connection: local
59   gather_facts: false
60   become: false
61   tags:
62     - preinstall
63
64   # Below vars are only for documentation purposes and can be overridden as required.
65   vars:
66     libvirt_ipv6_networks:
67       - name: public
68         address: "2001:db8:0:2::/64"
69     node_ipv6_addresses:
70       master0:
71         interface: ens4
72         address: "2001:db8:0:2::3/64"
73       node0:
74         interface: ens4
75         address: "2001:db8:0:2::4/64"
76
77   tasks:
78     - name: ensure kubernetes version is at least 1.16
79       fail:
80         msg: "Dual-Stack operation requires a Kubernetes version of at least 1.16"
81       when: kubernetes_version is version('v1.16', operator='<', strict=False)
82
83     - name: Configure Multus to use Calico as the primary network plugin
84       lineinfile:
85         path: "{{ engine_cache }}/repos/kubespray/inventory/engine/group_vars/k8s-cluster/k8s-cluster.yml"
86         regexp: "^kube_network_plugin:.*"
87         line: "kube_network_plugin: calico"
88
89     - name: Enable the Multus network plugin
90       lineinfile:
91         path: "{{ engine_cache }}/repos/kubespray/inventory/engine/group_vars/k8s-cluster/k8s-cluster.yml"
92         regexp: "^kube_network_plugin_multus:.*"
93         line: "kube_network_plugin_multus: true"
94
95     # NOTE (cian): this should really be in the installer configure-network role
96     - name: add IPv6 configuration stanzas to /etc/network/interfaces.d/
97       become: true
98       when: provisioner_type == "bifrost"
99       copy:
100         owner: root
101         group: root
102         mode: 0644
103         content: |
104           iface {{ item.value.interface }} inet6 static
105           address {{ item.value.address | ipaddr("address") }}
106           netmask {{ item.value.address | ipaddr("prefix") }}
107           gateway {{ item.value.address | ipaddr(1) | ipaddr("address") }}
108         dest: "/etc/network/interfaces.d/{{ item.value.interface }}-ipv6.cfg"
109       delegate_to: "{{ item.key }}"
110       with_items: "{{ node_ipv6_addresses | dict2items }}"
111
112     # NOTE (cian): this should really be in the provisioner create-libvirt-networks role
113     - name: add IPv6 to existing libvirt networks
114       when: provisioner_type == "bifrost"
115       become: true
116       block:
117         - name: dump libvirt networks needing IPv6
118           become: true
119           virt_net:
120             command: get_xml
121             name: "{{ item.name }}"
122           with_items: "{{ libvirt_ipv6_networks }}"
123           register: libvirt_net_xml
124
125         - name: dump libvirt networks needing IPv6 to engine cache dir
126           copy:
127             mode: 0644
128             content: "{{ item.get_xml }}"
129             dest: "{{ engine_cache }}/config/libvirt_net_{{ item.item.name }}.xml"
130           with_items: "{{ libvirt_net_xml.results }}"
131           register: libvirt_net_dumpxml
132
133         - name: add IPv6 stanza to libvirt networks requiring IPv6
134           blockinfile:
135             path: "{{ engine_cache }}/config/libvirt_net_{{ item.name }}.xml"
136             marker: "<!-- {mark} ANSIBLE MANAGED BLOCK -->"
137             insertbefore: "</network>"
138             block: |
139               <ip family='ipv6' address='{{ item.address | ipaddr(1) | ipaddr("address") }}' prefix='{{ item.address | ipaddr("prefix") }}'>
140                 <dhcp>
141                   <range start='{{ item.address | ipaddr(2) | ipaddr("address") }}' end='{{ item.address | ipaddr(255) | ipaddr("address") }}'/><!-- ' -->
142                 </dhcp>
143               </ip>
144           loop: "{{ libvirt_ipv6_networks | list }}"
145           register: libvirt_nets_blockinfile
146
147         - name: redefine libvirt networks
148           become: true
149           when: libvirt_nets_blockinfile is changed
150           block:
151             - name: stop libvirt VMs
152               virt:
153                 command: shutdown
154                 name: "{{ item }}"
155               loop: "{{ engine.installers.kubespray.hostnames | dict2items | map(attribute='value') | list }}"
156
157             - name: destroy affected libvirt networks
158               virt_net:
159                 command: destroy
160                 name: "{{ item.name }}"
161               with_items: "{{ libvirt_net_dumpxml.results | json_query('[].{name: item.item.name}') | list }}"
162
163             - name: undefine affected libvirt networks
164               virt_net:
165                 command: undefine
166                 name: "{{ item.name }}"
167               with_items: "{{ libvirt_net_dumpxml.results | json_query('[].{name: item.item.name}') | list }}"
168
169             - name: redefine affected libvirt networks
170               virt_net:
171                 command: define
172                 name: "{{ item.name }}"
173                 xml: "{{ lookup('file', item.path) }}"
174               with_items: "{{ libvirt_net_dumpxml.results | json_query('[].{ name: item.item.name, path: dest }') | list }}"
175
176             - name: start affected libvirt networks
177               virt_net:
178                 command: start
179                 name: "{{ item.name }}"
180               with_items: "{{ libvirt_net_dumpxml.results | json_query('[].{name: item.item.name}') | list }}"
181
182             - name: start libvirt VMs
183               virt:
184                 command: start
185                 name: "{{ item }}"
186               loop: "{{ engine.installers.kubespray.hostnames | dict2items | map(attribute='value') | list }}"
187
188     # NOTE (cian): this will not modify the file if regexp does not match because backrefs=true
189     - name: Enable IPv6DualStack feature gate
190       when: provisioner_type == "bifrost"
191       block:
192         - name: Append IPv6DualStack feature gate if feature gates are already set
193           lineinfile:
194             path: "{{ engine_cache }}/config/kubespray-vars.yml"
195             regexp: "^kube_feature_gates:\\s*\\[(.+)\\]\\s*$"
196             line: "kube_feature_gates: [\"IPv6DualStack=true\",\\1]"
197             backrefs: true
198
199         - name: Set IPv6DualStack feature gate if no feature gates are already set
200           lineinfile:
201             path: "{{ engine_cache }}/config/kubespray-vars.yml"
202             regexp: "^kube_feature_gates:\\s*\\[\\]\\s*$"
203             line: "kube_feature_gates: [\"IPv6DualStack=true\"]"
204
205         - name: enable dual-stack networks
206           lineinfile:
207             path: "{{ engine_cache }}/config/kubespray-vars.yml"
208             regexp: "^enable_dual_stack_networks:.*"
209             line: "enable_dual_stack_networks: true"
210
211 - hosts: k8s-nodes
212   tags:
213     - preinstall
214   tasks:
215     - name: wait for libvirt VMs to come back up
216       wait_for_connection:
217         connect_timeout: 10
218         delay: 30
219         timeout: 300
220       register: result
221       until: result is succeeded
222       retries: 3
223       when: provisioner_type == "bifrost"
224
225 # NOTE (cian): scenario-specific postinstall tasks
226 - hosts: k8s-cluster
227   tags:
228     - postinstall
229   tasks:
230     - name: set node-ip with both IPv4 and IPv6 addresses
231       block:
232         - name: Determine existing IPv4 address for kubelet
233           shell: |
234             set -o pipefail && \
235             egrep -o -- 'KUBELET_ADDRESS="--node-ip=[0-9\.]+' /etc/kubernetes/kubelet.env \
236               | awk -F '=' '{print $3}'
237           args:
238             executable: "/bin/bash"
239           register: kubelet_ipv4_address
240           changed_when: true
241
242         - name: Determine corresponding interface for IPv4 address {{ kubelet_ipv4_address.stdout }}
243           shell: |
244             set -o pipefail && \
245             ip -4 -o a \
246               | cut -d ' ' -f 2,7 \
247               | cut -d '/' -f 1 \
248               | grep {{ kubelet_ipv4_address.stdout }} \
249               | cut -d ' ' -f 1
250           args:
251             executable: "/bin/bash"
252           register: kubelet_ipv4_interface
253           changed_when: true
254
255           # NOTE (cian): this will default to the first IPv6 address assigned to the given interface
256           # excluding link-local addresses. If there are multiple IPv6 addresses assigned to one interface,
257           # this will probably not do what we want.
258         - name: Determine first IPv6 address assigned to interface {{ kubelet_ipv4_interface.stdout }}
259           shell: |
260             set -o pipefail && \
261             ip -6 -o a \
262               | cut -d ' ' -f 2,7 \
263               | cut -d '/' -f 1 \
264               | grep {{ kubelet_ipv4_interface.stdout }} \
265               | grep -v "fe80::" \
266               | cut -d ' ' -f 2 \
267               | head -n 1
268           args:
269             executable: "/bin/bash"
270           register: kubelet_ipv6_address
271           changed_when: true
272
273         # NOTE (cian): Dual-Stack NodeIP handling was added in v1.20.0
274         - name: Add comma-separated dualstack NodeIP parameter to Kubelet
275           become: true
276           lineinfile:
277             path: "/etc/kubernetes/kubelet.env"
278             regexp: "^KUBELET_ADDRESS=\"--node-ip=[0-9\\.]+\"\\s*$"
279             line: "KUBELET_ADDRESS=\"--node-ip={{ kubelet_ipv4_address.stdout }},{{ kubelet_ipv6_address.stdout }}\""
280           register: etc_kubernetes_kubelet_env
281
282         - name: Restart kubelet
283           become: true
284           service: name=kubelet state=restarted
285       when:
286         - provisioner_type == "bifrost"
287         - kubernetes_version is version('v1.20', operator='>=', strict=False)
288
289 - name: Execute common postinstall tasks
290   import_playbook: "../playbooks/postinstall.yaml"
291   tags: postinstall
292
293 # vim: set ts=2 sw=2 expandtab: