Skip to content

Commit

Permalink
Refactor join of workers nodes
Browse files Browse the repository at this point in the history
  • Loading branch information
Zempashi committed Feb 9, 2024
1 parent 6e3c0aa commit ec7b44a
Show file tree
Hide file tree
Showing 23 changed files with 246 additions and 159 deletions.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,31 @@ What ansible-kubeadm expect to be done and will not do:
- remove unattented-upgrade
- configure CNI


## Quickstart

see [Quickstart](docs/quickstart.md)


## Configuration

If you want a customized (ansible-)kubeadm experience there is a number of variables you can use:

[Variables reference](docs/variables.md)


## Guides

Some operation has their own guided page:

- [join nodes](docs/guides/join_nodes.md)


## Flow

If you're looking for what ansible-kubeadm is doing step-by-step, [hooks && plugins](docs/hooks_and_plugins.md) is a good way to start.


## Migration planning

Long term migration plan, [*] to indicate current phase
Expand Down
70 changes: 70 additions & 0 deletions docs/guides/join_nodes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# To join worker-only nodes

**Note** : For control plane nodes, see dedicated [section](join_nodes.md#to-join-control-plane-nodes)

Let's assume that you have a cluster with two nodes and that you want to add a third node `node-3`
You can join multiple worker node at once with this procedure,

### Add node to the inventory

First, add the node to the inventory like the following inventory:

```
[kube_control_plane]
cp-1
cp-2
cp-3
[kube_workers]
node-1
node-2
node-3
```


### [optional] Deploy local apiserver proxy

If you don't have provision a load-balancer and require the local haproxy to be deployed:

```
ansible-playbook -i inventory enix.kubeadm.00_apiserver_proxy -l kube_control_plane:nodes-3
```
You can skip the `-l` argument, if you're cluster doesn't have pending change you want to preserve on other nodes.
Don't forget to put all control_plane or it will fail to provision the apiserver proxy


### Create bootstrap-token

Then create a bootstrap token by adding using the `bootstrap_token` tag.
Don't use a limit that skip control plane nodes.

```
ansible-playbook -i inventory.cfg enix.kubeadm.01_site -t bootstrap_token
```

No need to retrieve it by yourself, it will be discovered when joining the node
The token has a validity of 1H, so you don't need to repeat this step each time you try to join nodes

### Joining nodes

You can join a node and skip other changes to the cluster by using the `join` tag.
With the tag, you can limit to hosts you want to join.

```
ansible-play -i inventory.cfg enix.kubeadm.01_site -t join -l nodes-3
```

## Alternative method

You can merge the creation of the boostrap token with the joining of the action of join:

```
ansible-playbook -i inventory.cfg enix.kubeadm.01_site -t bootstap_token,join -l kube_control_plane:node-3
```

Please note that you need to include a least one control plane node in the limit host pattern,
You can also skip the limit host pattern to apply to all nodes as those step are indempotent on their own: it will not mess with the current nodes.

# To join control-plane nodes

There is no tag for this operation, you need to apply the entire playbook for this
5 changes: 5 additions & 0 deletions playbooks/00_apiserver_proxy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@
any_errors_fatal: '{{ any_errors_fatal|default(true) }}'
vars:
_control_plane: true
pre_tasks:
- name: 'Fail if not all master in the specified limit'
fail:
msg: 'Not all control_plane provided, ajust --limit to provid all control_plane'
when: groups[kube_cp_group|default("kube_control_plane")]|difference(ansible_play_hosts)|length > 0
roles:
- role: find_ip

Expand Down
3 changes: 3 additions & 0 deletions playbooks/01_site.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@
kubeadm_hook_list: ['pre_config_update']
roles:
- role: bootstrap_token
tags: ['bootstrap_token']
- role: upload_certs
- role: kubeadm_configs_update
tasks:
- include_role:
Expand Down Expand Up @@ -199,6 +201,7 @@
hosts: '{{ kube_worker_group|default("kube_workers") }}'
any_errors_fatal: '{{ any_errors_fatal|default(true) }}'
gather_facts: false
tags: ['join']
pre_tasks:
- include_role:
name: hooks_call
Expand Down
8 changes: 1 addition & 7 deletions roles/bootstrap_token/defaults/main.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,2 @@
---
sensitive_debug: false
cluster_config: {}

kubeadm_config_yaml: '/tmp/kubeadm-config-{{ansible_date_time.iso8601 }}.yaml'

python2_openssl: python-openssl
python3_openssl: python3-openssl
_valid_bootstrap_tokens: []
1 change: 1 addition & 0 deletions roles/bootstrap_token/meta/main.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
dependencies:
- role: common_vars
- role: kubectl_module
galaxy_info:
author: Julien Girardin
Expand Down
41 changes: 0 additions & 41 deletions roles/bootstrap_token/tasks/bootstrap_token.yml

This file was deleted.

106 changes: 25 additions & 81 deletions roles/bootstrap_token/tasks/main.yaml
Original file line number Diff line number Diff line change
@@ -1,83 +1,27 @@
---
- name: 'Select candidate host to run init'
- name: 'Find nodes to join'
set_fact:
kubeadm_host: '{{ groups.cp_running|default(ansible_play_hosts, true)|first }}'

- name: 'Retrieve a valid bootstrap token'
import_tasks: bootstrap_token.yml

- name: 'Create bootstrap token if no valid found'
command: kubeadm token create
run_once: true
delegate_to: '{{ kubeadm_host }}'
when: valid_bootstrap_tokens|length == 0

- name: 'Retrieve a valid bootstrap token'
import_tasks: bootstrap_token.yml
when: valid_bootstrap_tokens|length == 0

# TODO: fix two following tasks to be more platform dependent
- name: 'Install python-openssl'
package:
name: >-
{%- if ansible_python.version.major > 2 -%}
{{ python3_openssl }}
{%- else -%}
{{ python2_openssl }}
{%- endif -%}
state: present
run_once: true
delegate_to: '{{ kubeadm_host }}'

- name: 'Get info from ca'
openssl_certificate_info:
path: /etc/kubernetes/pki/ca.crt
run_once: true
delegate_to: '{{ kubeadm_host }}'
register: ca_info
when: not(groups.cp_init is defined and ansible_check_mode)

- name: 'Display Kubernetes CA(cert) properties'
debug:
var: ca_info
verbosity: 1
run_once: true

- name: 'List current nodes'
kubectl:
state: get
resource_type: nodes
kubeconfig: /etc/kubernetes/admin.conf
run_once: true
delegate_to: '{{ kubeadm_host }}'
register: current_nodes
when:
- not(found_kubectl.rc == 1 and ansible_check_mode)

- name: 'Compute list of "to-join" nodes'
set_fact:
# "items" cannot be defaulted easily as jinja fallback on using method instead
to_join_cp: >-
{{ ansible_play_hosts|difference(
({"items": []}|combine(current_nodes))["items"]|map(attribute="metadata.name")) }}
cert_encryption_key: >-
{{ lookup('password', '/dev/null length=64 chars=hexdigits') }}
run_once: true

- name: 'Display list of node that need to be joined'
debug:
var: to_join_cp
verbosity: 1
run_once: true

- name: 'Upload certificates if control-plane node need to be joined'
command: >-
kubeadm init phase upload-certs
--upload-certs
--certificate-key {{ cert_encryption_key }}
environment:
KUBECONFIG: '/etc/kubernetes/admin.conf'
no_log: '{{ sensitive_debug|bool }}'
run_once: true
delegate_to: '{{ kubeadm_host }}'
when: to_join_cp|length > 0
nodes_to_join: >-
{{ q('inventory_hostnames', kube_cp_group ~ ':' ~ kube_worker_group)
|map('extract', hostvars)
|rejectattr('_kubelet_config_stat.stat.exists')
|map(attribute='inventory_hostname')|list }}
run_once: true

- name: 'Create bootstrap token'
when: nodes_to_join|length > 0
block:
- name: 'Retrieve a valid bootstrap token'
import_role:
name: bootstrap_token_get

- name: 'Create bootstrap token if no valid found'
command: kubeadm token create
run_once: true
delegate_to: '{{ cp_node }}'
when: _valid_bootstrap_tokens|length == 0

- name: 'Retrieve a valid bootstrap token'
import_role:
name: bootstrap_token_get
when: _valid_bootstrap_tokens|length == 0
3 changes: 3 additions & 0 deletions roles/bootstrap_token_get/meta/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
dependencies:
- role: common_vars
40 changes: 40 additions & 0 deletions roles/bootstrap_token_get/tasks/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
---
- name: 'Fetch bootstrap token'
run_once: true
delegate_to: '{{ cp_node }}'
block:
- name: 'Get list of bootstrap token'
kubectl:
state: get
resource_type: secret
namespace: kube-system
extra_args: '--field-selector=type=bootstrap.kubernetes.io/token'
kubeconfig: /etc/kubernetes/admin.conf
register: _bootstrap_tokens
when:
- not(groups.cp_running|default([])|length == 0 and ansible_check_mode)

- name: 'Display all bootstrap tokens'
debug:
var: _bootstrap_tokens
verbosity: 1

- name: 'Filter expire token'
set_fact:
_valid_bootstrap_tokens: >-
{%- if ansible_collection_name is defined and ansible_collection_name is not none -%}
{%- set filter_name = "enix.kubeadm.bootstrap_token_valid" -%}
{%- else -%}
{%- set filter_name = "bootstrap_token_valid" -%}
{%- endif -%}
{{ [bootstrap_tokens_dry_run["items"]
|selectattr('data.usage-bootstrap-authentication', 'defined')|list]
|map(filter_name)|first }}
vars:
# "items" cannot be defaulted easily as jinja fallback on using method instead
bootstrap_tokens_dry_run: "{{ {'items': []}|combine(_bootstrap_tokens) }}"

- name: 'Display valid bootstrap tokens'
debug:
var: _valid_bootstrap_tokens
verbosity: 1
1 change: 1 addition & 0 deletions roles/common_vars/defaults/main.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
---
_kubelet_config_path: /var/lib/kubelet/config.yaml
enable_kubeadm_patches: true
kubeadm_ignore_preflight_errors: []
kubeadm_patch_dir: /etc/kubernetes/patches
Expand Down
1 change: 1 addition & 0 deletions roles/join_nodes/defaults/main.yml
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
---
_control_plane: false
_kubernetes_ca_cert: /etc/kubernetes/pki/ca.crt
3 changes: 3 additions & 0 deletions roles/join_nodes/meta/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
dependencies:
- role: kubectl_module
- role: common_vars
- role: bootstrap_token_get
delegate_to: '{{ cp_node }}'
run_once: true
galaxy_info:
author: Julien Girardin
description: Join node to a kubernetes cluster
Expand Down
14 changes: 14 additions & 0 deletions roles/join_nodes/tasks/ca_info.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
- name: "Extract public key from kubernetes CA"
command: openssl x509 -noout -pubkey -in {{ _kubernetes_ca_cert }}
check_mode: false
changed_when: false
delegate_to: '{{ cp_node }}'
run_once: true
register: _kubernetes_ca_fingerprint

- name: "Compute sha256 of fingertprint"
set_fact:
ca_cert_hash: >-
{{ _kubernetes_ca_fingerprint.stdout|regex_replace('[- A-Z]+\n([+/\w\n]+)\n[- A-Z]+', '\g<1>')|b64decode|hash('sha256') }}
run_once: true
Loading

0 comments on commit ec7b44a

Please sign in to comment.