diff --git a/ansible/network.yml b/ansible/network.yml
index a9c15791bfbc5f716100da400d256910c68aa5c5..54aa3d8683e410c5acdeb9f12c3339d48e9cf5f9 100644
--- a/ansible/network.yml
+++ b/ansible/network.yml
@@ -4,11 +4,7 @@
   tags:
     - config
     - network
-  vars:
-    ether_interfaces: "{{ network_interfaces | net_select_ethers | list }}"
-    bridge_interfaces: "{{ network_interfaces | net_select_bridges | list }}"
-    bond_interfaces: "{{ network_interfaces | net_select_bonds | list }}"
-  pre_tasks:
+  tasks:
     - block:
         - name: Validate network interface configuration
           fail:
@@ -33,50 +29,13 @@
               {{ item }}. This should be configured via '{{ item }}_interface'.
           with_items: "{{ bond_interfaces }}"
           when: not item | net_interface
+      vars:
+        ether_interfaces: "{{ network_interfaces | net_select_ethers | list }}"
+        bridge_interfaces: "{{ network_interfaces | net_select_bridges | list }}"
+        bond_interfaces: "{{ network_interfaces | net_select_bonds | list }}"
       tags:
         - config-validation
 
-    - name: Ensure NetworkManager is disabled
-      service:
-        name: NetworkManager
-        state: stopped
-        enabled: no
-      become: True
-      register: nm_result
-      failed_when:
-        - nm_result is failed
-        # Ugh, Ansible's service module doesn't handle uninstalled services.
-        - "'Could not find the requested service' not in nm_result.msg"
-
-  roles:
-    - role: ahuffman.resolv
-      when: resolv_is_managed | bool
-      become: True
-
-    - role: MichaelRigart.interfaces
-      interfaces_route_tables: "{{ network_route_tables }}"
-      interfaces_ether_interfaces: >
-        {{ ether_interfaces |
-           map('net_interface_obj') |
-           list }}
-      interfaces_bridge_interfaces: >
-        {{ bridge_interfaces |
-           map('net_bridge_obj') |
-           list }}
-      interfaces_bond_interfaces: >
-        {{ bond_interfaces |
-           map('net_bond_obj') |
-           list }}
-
-# Configure virtual ethernet patch links to connect the workload provision
-# and external network bridges to the Neutron OVS bridge.
-- name: Ensure OVS patch links exist
-  hosts: network:compute
-  tags:
-    - config
-    - network
-  tasks:
-    - import_role:
-        name: veth
-      vars:
-        veth_interfaces: "{{ network_interfaces | net_ovs_veths }}"
+    - name: Configure the network
+      include_role:
+        name: "network-{{ ansible_os_family | lower }}"
diff --git a/ansible/roles/network-debian/tasks/main.yml b/ansible/roles/network-debian/tasks/main.yml
new file mode 100644
index 0000000000000000000000000000000000000000..06f3c83946d6fd37ee4cb95afd7bbf6ceec37a24
--- /dev/null
+++ b/ansible/roles/network-debian/tasks/main.yml
@@ -0,0 +1,51 @@
+---
+- name: Ensure NetworkManager is disabled
+  service:
+    name: NetworkManager
+    state: stopped
+    enabled: no
+  become: True
+  register: nm_result
+  failed_when:
+    - nm_result is failed
+    # Ugh, Ansible's service module doesn't handle uninstalled services.
+    - "'Could not find the requested service' not in nm_result.msg"
+
+- import_role:
+    name: ahuffman.resolv
+  when: resolv_is_managed | bool
+  become: True
+
+- name: Configure network interfaces (RedHat)
+  import_role:
+    name: MichaelRigart.interfaces
+  vars:
+    interfaces_route_tables: "{{ network_route_tables }}"
+    interfaces_ether_interfaces: >
+      {{ network_interfaces |
+         net_select_ethers |
+         map('net_interface_obj') |
+         list }}
+    interfaces_bridge_interfaces: >
+      {{ network_interfaces |
+         net_select_bridges |
+         map('net_bridge_obj') |
+         list }}
+    interfaces_bond_interfaces: >
+      {{ network_interfaces |
+         net_select_bonds |
+         map('net_bond_obj') |
+         list }}
+
+# Ensure that interface bouncing is finished before veth pairs are added,
+# since they are only ephemerally configured on Debian.
+- name: Flush handlers
+  meta: flush_handlers
+
+# Configure virtual ethernet patch links to connect the workload provision
+# and external network bridges to the Neutron OVS bridge.
+- name: Ensure OVS patch links exist
+  import_role:
+    name: veth
+  vars:
+    veth_interfaces: "{{ network_interfaces | net_ovs_veths }}"
diff --git a/ansible/roles/network-redhat/tasks/main.yml b/ansible/roles/network-redhat/tasks/main.yml
new file mode 100644
index 0000000000000000000000000000000000000000..5f1f59d0c251c42b103876d3c9427ec01706e75e
--- /dev/null
+++ b/ansible/roles/network-redhat/tasks/main.yml
@@ -0,0 +1,46 @@
+---
+- name: Ensure NetworkManager is disabled
+  service:
+    name: NetworkManager
+    state: stopped
+    enabled: no
+  become: True
+  register: nm_result
+  failed_when:
+    - nm_result is failed
+    # Ugh, Ansible's service module doesn't handle uninstalled services.
+    - "'Could not find the requested service' not in nm_result.msg"
+
+- import_role:
+    name: ahuffman.resolv
+  when: resolv_is_managed | bool
+  become: True
+
+- name: Configure network interfaces (RedHat)
+  import_role:
+    name: MichaelRigart.interfaces
+  vars:
+    interfaces_route_tables: "{{ network_route_tables }}"
+    interfaces_ether_interfaces: >
+      {{ network_interfaces |
+         net_select_ethers |
+         map('net_interface_obj') |
+         list }}
+    interfaces_bridge_interfaces: >
+      {{ network_interfaces |
+         net_select_bridges |
+         map('net_bridge_obj') |
+         list }}
+    interfaces_bond_interfaces: >
+      {{ network_interfaces |
+         net_select_bonds |
+         map('net_bond_obj') |
+         list }}
+
+# Configure virtual ethernet patch links to connect the workload provision
+# and external network bridges to the Neutron OVS bridge.
+- name: Ensure OVS patch links exist
+  import_role:
+    name: veth
+  vars:
+    veth_interfaces: "{{ network_interfaces | net_ovs_veths }}"