Skip to content
Open
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
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,11 @@ docs/_build
/tmp
/graphviz
/k3s-ansible
/rke2-ansible

# Local Claude Code development files
.claude/
.claude-flow/
CLAUDE.md
*.claude-test.*
.claude-cache/
2 changes: 2 additions & 0 deletions cmd/liqoctl/cmd/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
"github.com/liqotech/liqo/pkg/liqoctl/install/kind"
"github.com/liqotech/liqo/pkg/liqoctl/install/kubeadm"
"github.com/liqotech/liqo/pkg/liqoctl/install/openshift"
"github.com/liqotech/liqo/pkg/liqoctl/install/rke2"
"github.com/liqotech/liqo/pkg/liqoctl/output"
"github.com/liqotech/liqo/pkg/liqoctl/utils"
"github.com/liqotech/liqo/pkg/liqoctl/version"
Expand Down Expand Up @@ -188,6 +189,7 @@ func newInstallCommand(ctx context.Context, f *factory.Factory) *cobra.Command {
utils.AddCommand(cmd, newInstallProviderCommand(ctx, options.CommonOptions, kind.New))
utils.AddCommand(cmd, newInstallProviderCommand(ctx, options.CommonOptions, kubeadm.New))
utils.AddCommand(cmd, newInstallProviderCommand(ctx, options.CommonOptions, openshift.New))
utils.AddCommand(cmd, newInstallProviderCommand(ctx, options.CommonOptions, rke2.New))

return cmd
}
Expand Down
1 change: 1 addition & 0 deletions docs/installation/compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ While the following list includes providers that we have specifically tested, Li
| Kubeadm (Cilium) | ✅ | No known issues |
| Kubeadm (Cilium with kube-proxy replacement) | 🟢 | `NodePortExposition` and `LoadBalancerExposition` |
| K3s | 🟢 | `RemoteExec` |
| RKE2 | 🟢 | Supports out-of-band peering for restricted networks; `RemoteExec` similar to K3s |
| K0s | ✅ | No known issues |
| AKS (Azure CNI Overlay) | 🟢 | `CrossClusterAPIServerInteraction` and `ExternalIPRemapping` |
| AKS (Azure CNI (Legacy)) | 🟢 | `CrossClusterAPIServerInteraction` and `NodePortExposition` |
Expand Down
46 changes: 46 additions & 0 deletions docs/installation/install.md
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,52 @@ Alternatively, you can manually specify a desired id with the `--cluster-id` fla

````

````{tab-item} RKE2

```{warning}
RKE2 shares limitations with K3s:
- `kubectl exec` may not work on pods scheduled to virtual nodes
- For restricted networks, use [out-of-band peering](../usage/rke2-oob-peering)
```

```{admonition} Note
RKE2 API server runs on port 9345 (not the standard 6443). Ensure your kubeconfig is properly configured.
```

**Installation**

Liqo can be installed on an RKE2 cluster with:

```bash
liqoctl install rke2
```

Override the API server URL if needed (e.g., NAT, load balancer):

```bash
liqoctl install rke2 --api-server-url https://rke2.example.com:9345
```

The cluster ID is auto-generated. Specify a custom ID with `--cluster-id`:

```bash
liqoctl install rke2 --cluster-id my-rke2-cluster
```

**Out-of-Band Peering**

For restricted networks where clusters cannot directly communicate, use manual peering:

```bash
liqoctl install rke2 --cluster-id cluster-1
# Repeat on second cluster with different ID
# Then follow the out-of-band peering guide
```

See [RKE2 out-of-band peering](../usage/rke2-oob-peering) for the complete procedure.

````

````{tab-item} KinD

**Installation**
Expand Down
148 changes: 148 additions & 0 deletions docs/usage/rke2-oob-peering.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
# RKE2 Out-of-Band Peering

This section describes how to establish peering between RKE2 clusters in restricted networks where `liqoctl peer` cannot be used (e.g., different networks, security policies, GitOps workflows).

## Overview

Out-of-band peering manually creates `ForeignCluster` resources on each cluster, enabling peering without direct cluster-to-cluster communication during setup.
This approach is essential when:

- Clusters cannot directly communicate
- Using declarative GitOps workflows
- Different organizations manage each cluster

## Prerequisites

- Two RKE2 clusters with Liqo installed
- Secure method to exchange configuration (e.g., shared storage, secure transfer)

```{admonition} Note
For standard peering where both clusters are accessible, use `liqoctl peer` instead. See [peer two clusters](/usage/peer).
```

## Install Liqo on both clusters

First, install Liqo with explicit cluster IDs:

```bash
# Consumer cluster
liqoctl install rke2 --cluster-id consumer-rke2

# Provider cluster
liqoctl install rke2 --cluster-id provider-rke2
```

## Create ForeignCluster resources

### On the consumer

Create a `ForeignCluster` representing the provider:

```yaml
apiVersion: core.liqo.io/v1beta1
kind: ForeignCluster
metadata:
name: provider-rke2
labels:
liqo.io/remote-cluster-id: provider-rke2
spec:
clusterID: provider-rke2
modules:
networking: {enabled: true}
authentication: {enabled: true}
offloading: {enabled: true}
```

```bash
kubectl apply -f consumer-foreigncluster.yaml
```

### On the provider

Create a `ForeignCluster` representing the consumer:

```yaml
apiVersion: core.liqo.io/v1beta1
kind: ForeignCluster
metadata:
name: consumer-rke2
labels:
liqo.io/remote-cluster-id: consumer-rke2
spec:
clusterID: consumer-rke2
modules:
networking: {enabled: true}
authentication: {enabled: true}
offloading: {enabled: true}
```

```bash
kubectl apply -f provider-foreigncluster.yaml
```

```{admonition} Important
The `liqo.io/remote-cluster-id` label improves lookup performance (O(1) vs O(n)).
```

## Exchange credentials

The peering modules require manual credential exchange. Refer to the individual module documentation:

- [Networking](/advanced/peering/inter-cluster-network) - Gateway configuration
- [Authentication](/advanced/peering/inter-cluster-authentication) - Identity exchange
- [Offloading](/advanced/peering/offloading-in-depth) - ResourceSlice creation

## Verify peering

Check the `ForeignCluster` status:

```bash
# Consumer
kubectl get foreigncluster -n liqo provider-rke2 -o yaml
kubectl get nodes -l liqo.io/type=virtual-node

# Provider
kubectl get foreigncluster -n liqo consumer-rke2 -o yaml
kubectl get resourceslice -n liqo
```

## GitOps integration

Store `ForeignCluster` manifests in Git and apply via your GitOps operator:

```
gitops-repo/
├── clusters/
│ ├── consumer-rke2/
│ │ └── foreignclusters/
│ │ └── provider-rke2.yaml
│ └── provider-rke2/
│ └── foreignclusters/
│ └── consumer-rke2.yaml
```

## Troubleshooting

**ForeignCluster not found:**

```bash
# Verify name matches cluster ID
kubectl get foreigncluster -A

# Add label if missing
kubectl label foreigncluster <name> liqo.io/remote-cluster-id=<cluster-id> -n liqo
```

**Networking issues:**

```bash
kubectl get gatewayserver,gatewayclient -n liqo
kubectl logs -n liqo -l app.kubernetes.io/component=liqo-fabric
```

**Authentication failures:**

```bash
kubectl get identity -n liqo
kubectl logs -n liqo -l app.kubernetes.io/name=controller-manager
```
86 changes: 86 additions & 0 deletions pkg/liqoctl/install/rke2/provider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Copyright 2019-2025 The Liqo Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package rke2

import (
"context"

"github.com/spf13/cobra"

"github.com/liqotech/liqo/pkg/liqoctl/install"
)

var _ install.Provider = (*Options)(nil)

// Options encapsulates the arguments of the install rke2 command.
type Options struct {
*install.Options
}

// New initializes a new Provider object.
func New(o *install.Options) install.Provider {
return &Options{Options: o}
}

// Name returns the name of the provider.
func (o *Options) Name() string { return "rke2" }

// Examples returns the examples string for the given provider.
func (o *Options) Examples() string {
return `Examples:
$ {{ .Executable }} install rke2 --api-server-url https://liqo.example.local:9345 \
--cluster-labels region=us-west,environment=production \
--reserved-subnets 172.16.0.0/16,192.16.254.0/24
or
$ {{ .Executable }} install rke2 --api-server-url https://liqo.example.local:9345 \
--cluster-labels region=us-west,environment=production \
--pod-cidr 10.0.0.0/16 --service-cidr 10.1.0.0/16 \
--reserved-subnets 172.16.0.0/16,192.16.254.0/24
or (with out-of-band peering for restricted networks)
$ {{ .Executable }} install rke2 --api-server-url https://liqo.example.local:9345 \
--cluster-id my-rke2-cluster \
--cluster-labels region=us-west,environment=production
`
}

// RegisterFlags registers the flags for the given provider.
func (o *Options) RegisterFlags(cmd *cobra.Command) {
cmd.Flags().StringVar(&o.APIServer, "api-server-url", "", "The Kubernetes API Server URL (defaults to the one specified in the kubeconfig)")
cmd.Flags().StringVar(&o.PodCIDR, "pod-cidr", "10.42.0.0/16", "The Pod CIDR of the cluster")
cmd.Flags().StringVar(&o.ServiceCIDR, "service-cidr", "10.43.0.0/16", "The Service CIDR of the cluster")
}

// Initialize performs the initialization tasks to retrieve the provider-specific parameters.
func (o *Options) Initialize(_ context.Context) error {
// RKE2 API server typically runs on port 9345 and may use localhost addresses.
// Disable API server sanity checks to support these scenarios.
o.DisableAPIServerSanityChecks = true
return nil
}

// Values returns the customized provider-specifc values file parameters.
func (o *Options) Values() map[string]interface{} {
return map[string]interface{}{
"networking": map[string]interface{}{
"fabric": map[string]interface{}{
"config": map[string]interface{}{
// RKE2 uses nftables by default, but monitoring can cause issues
// in some environments, similar to K3s
"nftablesMonitor": false,
},
},
},
}
}
Loading