Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(network): Add multinode capabilities #82

Draft
wants to merge 2 commits into
base: project-demo-202208
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 47 additions & 3 deletions docs/proposals/network/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,20 +33,32 @@ iptables rules for a new node.
let node_id = "node";
let node_ip_addr = Ipv4Addr::from_str("10.0.0.1").unwrap();
let node_ip_cidr = Ipv4Inet::new(node_ip_addr, 24).unwrap();
let nodes_ips = Vec::new();

let request = SetupNodeRequest::new(node_id.to_string(), node_ip_cidr);
let request = SetupNodeRequest::new(node_id.to_string(), node_ip_cidr, nodes_ips);
let response = setup_node(request).unwrap();

println!("CNI name: {}", response.interface_name);
```

After each node reboot, you need to reconfigure iptables running `setup_iptables` function from
`node` module.
After each node reboot, you need to reconfigure iptables running `setup_iptables` and
`add_other_nodes` functions from `node` module.

```rust
let node_id = "node";
let request = SetupIptablesRequest::new(node_id.to_string());

let other_node_id = "node2";
let other_node_cluster_ip = Ipv4Inet::new(Ipv4Addr::from_str("10.0.1.1").unwrap(), 24).unwrap();
let other_node_external_ip = Ipv4Addr::from_str("22.22.22.22").unwrap();
let new_node_request = NodeRequest::new(
other_node_id.to_string(),
NodeRequest::new(other_node_cluster_ip, other_node_external_ip),
)
let nodes_ips = vec![new_node_request];

setup_iptables(request).unwrap();
add_other_nodes(nodes_ips).unwrap();
```

### Setup instance
Expand Down Expand Up @@ -83,6 +95,38 @@ let namespace_name = get_namespace_name(instance_id.to_string());
println!("Namespace of {}: {}", instance_id, namespace_name);
```

### Add other nodes

When a new node join the cluster, you need to call `new_node_in_cluster` function from `node`
module.

```rust
let node_id = "node";
let node_cluster_ip = Ipv4Inet::new(Ipv4Addr::from_str("10.0.1.1").unwrap(), 24).unwrap();
let node_external_ip = Ipv4Addr::from_str("22.22.22.22").unwrap();
let new_node_request = NodeRequest::new(
node_id.to_string(),
NodeIp::new(node_cluster_ip, node_external_ip),
);

new_node_in_cluster(new_node_request).unwrap();
```

And you have to run `delete_node_in_cluster` function from `node` module when a node leave the
cluster.

```rust
let node_id = "node";
let node_cluster_ip = Ipv4Inet::new(Ipv4Addr::from_str("10.0.1.1").unwrap(), 24).unwrap();
let node_external_ip = Ipv4Addr::from_str("22.22.22.22").unwrap();
let delete_node_request = NodeRequest::new(
node_id.to_string(),
NodeIp::new(node_cluster_ip, node_external_ip),
);

delete_node_in_cluster(delete_node_request).unwrap();
```

### Clean up

To delete CNI and iptables rules of a specific node, use `clean_node` function from `node` module.
Expand Down
1 change: 1 addition & 0 deletions network/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod error;
pub mod instance;
pub mod node;
pub mod node_ip;
pub mod port;
pub mod utils;
167 changes: 166 additions & 1 deletion network/src/node/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pub mod response;
use crate::error::KudoNetworkError;
use crate::utils::{bridge_name, run_command};
use default_net;
use request::{CleanNodeRequest, SetupIptablesRequest, SetupNodeRequest};
use request::{CleanNodeRequest, NodeRequest, SetupIptablesRequest, SetupNodeRequest};
use response::SetupNodeResponse;

/// Create a network interface and add iptables rules to make this device able to route instances
Expand All @@ -31,9 +31,174 @@ pub fn setup_node(request: SetupNodeRequest) -> Result<SetupNodeResponse, KudoNe

setup_iptables(SetupIptablesRequest::new(request.node_id))?;

add_other_nodes(request.nodes_ips)?;

Ok(SetupNodeResponse::new(bridge))
}

pub fn add_other_nodes(nodes: Vec<NodeRequest>) -> Result<(), KudoNetworkError> {
for node in nodes {
new_node_in_cluster(node)?;
}
Ok(())
}

/// Add iptables rules when a node joins the cluster
pub fn new_node_in_cluster(request: NodeRequest) -> Result<(), KudoNetworkError> {
// Iptables rules to allow workloads to route to the node
run_command(
"iptables",
&[
"-t",
"nat",
"-I",
"PREROUTING",
"-p",
"tcp",
"-d",
&request.nodes_ips.cluster_ip_cidr.to_string(),
"-j",
"DNAT",
"--to-destination",
&request.nodes_ips.external_ip_addr.to_string(),
],
)?;

run_command(
"iptables",
&[
"-t",
"nat",
"-I",
"PREROUTING",
"-p",
"udp",
"-d",
&request.nodes_ips.cluster_ip_cidr.to_string(),
"-j",
"DNAT",
"--to-destination",
&request.nodes_ips.external_ip_addr.to_string(),
],
)?;

// Iptables rules to allow host machines to route to the node
run_command(
"iptables",
&[
"-t",
"nat",
"-I",
"OUTPUT",
"-p",
"tcp",
"-d",
&request.nodes_ips.cluster_ip_cidr.to_string(),
"-j",
"DNAT",
"--to-destination",
&request.nodes_ips.external_ip_addr.to_string(),
],
)?;

run_command(
"iptables",
&[
"-t",
"nat",
"-I",
"OUTPUT",
"-p",
"udp",
"-d",
&request.nodes_ips.cluster_ip_cidr.to_string(),
"-j",
"DNAT",
"--to-destination",
&request.nodes_ips.external_ip_addr.to_string(),
],
)?;

Ok(())
}

/// Remove iptables rules when a node leaves the cluster
pub fn delete_node_in_cluster(request: NodeRequest) -> Result<(), KudoNetworkError> {
run_command(
"iptables",
&[
"-t",
"nat",
"-D",
"PREROUTING",
"-p",
"tcp",
"-d",
&request.nodes_ips.cluster_ip_cidr.to_string(),
"-j",
"DNAT",
"--to-destination",
&request.nodes_ips.external_ip_addr.to_string(),
],
)?;

run_command(
"iptables",
&[
"-t",
"nat",
"-D",
"PREROUTING",
"-p",
"udp",
"-d",
&request.nodes_ips.cluster_ip_cidr.to_string(),
"-j",
"DNAT",
"--to-destination",
&request.nodes_ips.external_ip_addr.to_string(),
],
)?;

run_command(
"iptables",
&[
"-t",
"nat",
"-D",
"OUTPUT",
"-p",
"tcp",
"-d",
&request.nodes_ips.cluster_ip_cidr.to_string(),
"-j",
"DNAT",
"--to-destination",
&request.nodes_ips.external_ip_addr.to_string(),
],
)?;

run_command(
"iptables",
&[
"-t",
"nat",
"-D",
"OUTPUT",
"-p",
"udp",
"-d",
&request.nodes_ips.cluster_ip_cidr.to_string(),
"-j",
"DNAT",
"--to-destination",
&request.nodes_ips.external_ip_addr.to_string(),
],
)?;

Ok(())
}

/// Add iptables rules to route instances traffic
/// This function must be called after each reboot
pub fn setup_iptables(request: SetupIptablesRequest) -> Result<(), KudoNetworkError> {
Expand Down
18 changes: 17 additions & 1 deletion network/src/node/request.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
use cidr::Ipv4Inet;

use crate::node_ip::NodeIp;

// Setup
pub struct SetupNodeRequest {
/// Unique identifier of the node. This identifier is used to create a bridge interface
pub node_id: String,
/// Composed of the node ip and a mask
pub node_ip_cidr: Ipv4Inet,
/// Nodes already in the cluster
pub nodes_ips: Vec<NodeRequest>,
}

impl SetupNodeRequest {
pub fn new(node_id: String, node_ip_cidr: Ipv4Inet) -> Self {
pub fn new(node_id: String, node_ip_cidr: Ipv4Inet, nodes_ips: Vec<NodeRequest>) -> Self {
Self {
node_id,
node_ip_cidr,
nodes_ips,
}
}
}
Expand All @@ -27,6 +32,17 @@ impl SetupIptablesRequest {
}
}

pub struct NodeRequest {
pub node_id: String,
pub nodes_ips: NodeIp,
}

impl NodeRequest {
pub fn new(node_id: String, nodes_ips: NodeIp) -> Self {
Self { node_id, nodes_ips }
}
}

// Clean up
pub struct CleanNodeRequest {
pub node_id: String,
Expand Down
16 changes: 16 additions & 0 deletions network/src/node_ip/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use cidr::Ipv4Inet;
use std::net::Ipv4Addr;

pub struct NodeIp {
pub cluster_ip_cidr: Ipv4Inet,
pub external_ip_addr: Ipv4Addr,
}

impl NodeIp {
pub fn new(cluster_ip_cidr: Ipv4Inet, external_ip_addr: Ipv4Addr) -> Self {
Self {
cluster_ip_cidr,
external_ip_addr,
}
}
}
2 changes: 1 addition & 1 deletion network/src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::error::KudoNetworkError;

const IFACE_MAX_SIZE: usize = 12;

pub(crate) fn bridge_name(node_id: String) -> String {
pub fn bridge_name(node_id: String) -> String {
format!("kbr{}", &node_id[..min(IFACE_MAX_SIZE, node_id.len())])
}

Expand Down