diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 0971e6ba99057701dddbbdc12b44ed72fca6f907..b1daab71556c81a110b228e691cc92a88e1cf30f 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -20,7 +20,6 @@ variables:
 before_script:
     - pip install -r tests/requirements.txt
     - mkdir -p /.ssh
-    - cp tests/ansible.cfg .
 
 .job: &job
   tags:
diff --git a/cluster.yml b/cluster.yml
index 5ebed30c5e4104d8fe47661db81a8276ddcf11f1..f3e42eec2c61e260b9cb5ed4e1783cab9713418c 100644
--- a/cluster.yml
+++ b/cluster.yml
@@ -68,6 +68,8 @@
   roles:
     - { role: kubespray-defaults}
     - { role: kubernetes/master, tags: master }
+    - { role: kubernetes/client, tags: client }
+    - { role: kubernetes-apps/cluster_roles, tags: cluster-roles }
 
 - hosts: k8s-cluster
   any_errors_fatal: "{{ any_errors_fatal | default(true) }}"
@@ -83,7 +85,6 @@
     - { role: kubernetes-apps/rotate_tokens, tags: rotate_tokens, when: "secret_changed|default(false)" }
     - { role: kubernetes-apps/network_plugin, tags: network }
     - { role: kubernetes-apps/policy_controller, tags: policy-controller }
-    - { role: kubernetes/client, tags: client }
 
 - hosts: calico-rr
   any_errors_fatal: "{{ any_errors_fatal | default(true) }}"
diff --git a/extra_playbooks/upgrade-only-k8s.yml b/extra_playbooks/upgrade-only-k8s.yml
index 90ee84ec948e1bef0ebbb9de957fc49aac4e0a43..b9263cb0277295051b7c4a36af9f730cff84cdd4 100644
--- a/extra_playbooks/upgrade-only-k8s.yml
+++ b/extra_playbooks/upgrade-only-k8s.yml
@@ -47,6 +47,8 @@
     - { role: upgrade/pre-upgrade, tags: pre-upgrade }
     - { role: kubernetes/node, tags: node }
     - { role: kubernetes/master, tags: master }
+    - { role: kubernetes/client, tags: client }
+    - { role: kubernetes-apps/cluster_roles, tags: cluster-roles }
     - { role: upgrade/post-upgrade, tags: post-upgrade }
 
 #Finally handle worker upgrades, based on given batch size
diff --git a/roles/kubernetes-apps/ansible/tasks/main.yml b/roles/kubernetes-apps/ansible/tasks/main.yml
index 26a0a1f99df7e669bf4cd4b0496d875e5d0898e5..025b4fab6b71c083859ac844fed88c3b3df28e2f 100644
--- a/roles/kubernetes-apps/ansible/tasks/main.yml
+++ b/roles/kubernetes-apps/ansible/tasks/main.yml
@@ -5,26 +5,9 @@
   register: result
   until: result.status == 200
   retries: 10
-  delay: 6
+  delay: 2
   when: inventory_hostname == groups['kube-master'][0]
 
-- name: Kubernetes Apps | Add ClusterRoleBinding to admit nodes
-  template:
-    src: "node-crb.yml.j2"
-    dest: "{{ kube_config_dir }}/node-crb.yml"
-  register: node_crb_manifest
-  when: rbac_enabled
-
-- name: Apply workaround to allow all nodes with cert O=system:nodes to register
-  kube:
-    name: "system:node"
-    kubectl: "{{bin_dir}}/kubectl"
-    resource: "clusterrolebinding"
-    filename: "{{ kube_config_dir }}/node-crb.yml"
-  when:
-    - rbac_enabled
-    - node_crb_manifest.changed
-
 - name: Kubernetes Apps | Delete old kubedns resources
   kube:
     name: "kubedns"
diff --git a/roles/kubernetes-apps/cluster_roles/tasks/main.yml b/roles/kubernetes-apps/cluster_roles/tasks/main.yml
new file mode 100644
index 0000000000000000000000000000000000000000..24f94aac5f7b73adcf8a0af51079f355d1a24d61
--- /dev/null
+++ b/roles/kubernetes-apps/cluster_roles/tasks/main.yml
@@ -0,0 +1,56 @@
+---
+- name: Kubernetes Apps | Wait for kube-apiserver
+  uri:
+    url: "{{ kube_apiserver_insecure_endpoint }}/healthz"
+  register: result
+  until: result.status == 200
+  retries: 10
+  delay: 6
+  when: inventory_hostname == groups['kube-master'][0]
+
+- name: Kubernetes Apps | Add ClusterRoleBinding to admit nodes
+  template:
+    src: "node-crb.yml.j2"
+    dest: "{{ kube_config_dir }}/node-crb.yml"
+  register: node_crb_manifest
+  when: rbac_enabled
+
+- name: Apply workaround to allow all nodes with cert O=system:nodes to register
+  kube:
+    name: "system:node"
+    kubectl: "{{bin_dir}}/kubectl"
+    resource: "clusterrolebinding"
+    filename: "{{ kube_config_dir }}/node-crb.yml"
+    state: latest
+  when:
+    - rbac_enabled
+    - node_crb_manifest.changed
+
+# This is not a cluster role, but should be run after kubeconfig is set on master
+- name: Write kube system namespace manifest
+  template:
+    src: namespace.j2
+    dest: "{{kube_config_dir}}/{{system_namespace}}-ns.yml"
+  when: inventory_hostname == groups['kube-master'][0]
+  tags:
+    - apps
+
+- name: Check if kube system namespace exists
+  command: "{{ bin_dir }}/kubectl get ns {{system_namespace}}"
+  register: 'kubesystem'
+  changed_when: False
+  failed_when: False
+  when: inventory_hostname == groups['kube-master'][0]
+  tags:
+    - apps
+
+- name: Create kube system namespace
+  command: "{{ bin_dir }}/kubectl create -f {{kube_config_dir}}/{{system_namespace}}-ns.yml"
+  retries: 4
+  delay: "{{ retry_stagger | random + 3 }}"
+  register: create_system_ns
+  until: create_system_ns.rc == 0
+  changed_when: False
+  when: inventory_hostname == groups['kube-master'][0] and kubesystem.rc != 0
+  tags:
+    - apps
diff --git a/roles/kubernetes/master/templates/namespace.j2 b/roles/kubernetes-apps/cluster_roles/templates/namespace.j2
similarity index 100%
rename from roles/kubernetes/master/templates/namespace.j2
rename to roles/kubernetes-apps/cluster_roles/templates/namespace.j2
diff --git a/roles/kubernetes-apps/ansible/templates/node-crb.yml.j2 b/roles/kubernetes-apps/cluster_roles/templates/node-crb.yml.j2
similarity index 100%
rename from roles/kubernetes-apps/ansible/templates/node-crb.yml.j2
rename to roles/kubernetes-apps/cluster_roles/templates/node-crb.yml.j2
diff --git a/roles/kubernetes/master/tasks/static-pod-setup.yml b/roles/kubernetes/master/tasks/static-pod-setup.yml
index a68ffb1374b4487a9c868cfc4de342351e59ea97..79f95d86096af9ac0ad77a4c894713199052d170 100644
--- a/roles/kubernetes/master/tasks/static-pod-setup.yml
+++ b/roles/kubernetes/master/tasks/static-pod-setup.yml
@@ -9,34 +9,6 @@
 
 - meta: flush_handlers
 
-- name: Write kube system namespace manifest
-  template:
-    src: namespace.j2
-    dest: "{{kube_config_dir}}/{{system_namespace}}-ns.yml"
-  when: inventory_hostname == groups['kube-master'][0]
-  tags:
-    - apps
-
-- name: Check if kube system namespace exists
-  command: "{{ bin_dir }}/kubectl get ns {{system_namespace}}"
-  register: 'kubesystem'
-  changed_when: False
-  failed_when: False
-  when: inventory_hostname == groups['kube-master'][0]
-  tags:
-    - apps
-
-- name: Create kube system namespace
-  command: "{{ bin_dir }}/kubectl create -f {{kube_config_dir}}/{{system_namespace}}-ns.yml"
-  retries: 4
-  delay: "{{ retry_stagger | random + 3 }}"
-  register: create_system_ns
-  until: create_system_ns.rc == 0
-  changed_when: False
-  when: inventory_hostname == groups['kube-master'][0] and kubesystem.rc != 0
-  tags:
-    - apps
-
 - name: Write kube-scheduler kubeconfig
   template:
     src: kube-scheduler-kubeconfig.yaml.j2
diff --git a/tests/ansible.cfg b/tests/ansible.cfg
index 780e1524bb0075ec5c790d0955c770495532a8bf..9e734403e518ae0b2ed894ea949e17448840cbe5 100644
--- a/tests/ansible.cfg
+++ b/tests/ansible.cfg
@@ -8,4 +8,5 @@ gathering = smart
 fact_caching = jsonfile
 fact_caching_connection = /tmp
 stdout_callback = skippy
+library = ./library:../library
 callback_whitelist = profile_tasks
diff --git a/upgrade-cluster.yml b/upgrade-cluster.yml
index 747ed6023e09cc09b4b405bfd2f33d41e19148df..652ae9a0885f9e46f8256eb16b8f73e87232b88d 100644
--- a/upgrade-cluster.yml
+++ b/upgrade-cluster.yml
@@ -67,6 +67,8 @@
     - { role: upgrade/pre-upgrade, tags: pre-upgrade }
     - { role: kubernetes/node, tags: node }
     - { role: kubernetes/master, tags: master }
+    - { role: kubernetes/client, tags: client }
+    - { role: kubernetes-apps/cluster_roles, tags: cluster-roles }
     - { role: network_plugin, tags: network }
     - { role: upgrade/post-upgrade, tags: post-upgrade }