Mastering Multi-Cluster Kubernetes Federation with Terraform and Ansible on AWS

As organizations increasingly adopt cloud-native architectures and microservices-based designs, the need for efficient management and orchestration of containerized applications across multiple Kubernetes clusters has become a critical challenge. In this article, we will delve into the technical intricacies of implementing a multi-cluster Kubernetes federation using Terraform and Ansible on Amazon Web Services (AWS), a scenario that requires meticulous planning, precision execution, and a deep understanding of Infrastructure as Code (IaC) and DevOps practices.

The purpose of this advanced technical setup is to achieve seamless service discovery, load balancing, and high availability across multiple Kubernetes clusters, spread across different availability zones (AZs) or even regions on AWS. This multi-cluster federation enables organizations to deploy stateless and stateful applications with unparalleled scalability, resilience, and operational efficiency, leveraging the full potential of cloud computing and containerization.

Core Logic

At the heart of this solution lies the architectural decision to utilize Terraform for defining and managing the infrastructure of the Kubernetes clusters, including the underlying EC2 instances, security groups, and networking components. Ansible, on the other hand, is employed for automating the deployment and configuration of the Kubernetes clusters themselves, including the installation of necessary addons and plugins. This separation of concerns allows for a clean, modular, and highly maintainable design, aligned with DevOps best practices and agile methodologies.

Prerequisites

To replicate this setup, you will need access to an AWS account with sufficient privileges to create and manage EC2 instances, VPCs, and other cloud resources. Additionally, you should have Terraform (v1.2.5) and Ansible (v5.0.0) installed on your machine, along with a basic understanding of Kubernetes fundamentals, Linux administration, and cloud security principles. Prior experience with IaC tools and configuration management is highly recommended.

Implementation Guide

Step 1: Initialize your Terraform working directory and define the AWS provider configuration. You can do this by running terraform init and creating a main.tf file with the necessary provider settings.

Step 2: Write Terraform configuration files to provision the necessary AWS resources, including EC2 instances, security groups, and VPCs. An example Terraform module for provisioning an EC2 instance might look like this:

# File: ec2_instance.tf
resource "aws_instance" "k8s_node" {
  ami           = "ami-0c94855ba95c71c99"
  instance_type = "t2.medium"
  vpc_security_group_ids = [aws_security_group.k8s_sg.id]
  key_name               = "k8s-key"
}

Step 3: Utilize Ansible playbooks to automate the deployment and configuration of Kubernetes on the provisioned EC2 instances. This involves installing Docker, Kubernetes, and other required components, followed by the initialization and joining of the Kubernetes cluster nodes.

Step 4: Configure the Kubernetes federation by defining the federation API server and etcd clusters, and then joining the individual Kubernetes clusters to the federation. This step requires careful planning and execution to ensure proper service discovery and load balancing across the federated clusters.

Best Practices & Security

To maintain and secure this setup, it is crucial to follow best practices for Kubernetes security, including the use of network policies, secret management, and role-based access control (RBAC). Regularly update and patch your Kubernetes clusters and underlying infrastructure to protect against known vulnerabilities. Additionally, implement monitoring and logging tools to detect and respond to potential security incidents in a timely manner.

Conclusion

In conclusion, implementing a multi-cluster Kubernetes federation with Terraform and Ansible on AWS is a complex task that requires a deep understanding of cloud-native architectures, containerization, and DevOps practices. By following the steps outlined in this article and adhering to best practices for security and operations, organizations can unlock the full potential of their cloud infrastructure and achieve unparalleled scalability, resilience, and operational efficiency. As the cloud computing landscape continues to evolve, the importance of Infrastructure as Code (IaC), containerization, and Kubernetes will only continue to grow, making this skillset increasingly valuable for IT professionals and organizations alike.

How to Expose Local Apps Securely with Tailscale Serve and Funnel (Free HTTPS in Minutes)

If you build or demo web apps, you have probably fought with port forwarding, firewalls, or complicated reverse proxies. Tailscale’s Serve and Funnel features make this painless: Serve exposes your local service to your tailnet (only authenticated users in your Tailscale network), and Funnel optionally publishes it to the public internet with automatic HTTPS. This guide shows a step-by-step setup in minutes on Linux, with tips for macOS and Windows.

What you will set up

You will run a local app (for example, on http://localhost:3000) and expose it securely via Tailscale in two modes:

Tailnet-only (Serve): Only people signed into your tailnet can access it (ideal for internal testing).
Public (Funnel): Anyone on the internet can access a public HTTPS URL that Tailscale provisions for you (great for demos, webhooks, or sharing temporary previews).

Prerequisites

- A Tailscale account (free is fine for personal use).
- Tailscale installed and logged in on the computer that runs your app (Linux/macOS/Windows).
- Ability to turn on the “Funnel” feature in the Tailscale admin console (if you plan to go public).
- A local service running (e.g., a dev server on port 3000).

1) Install and log in to Tailscale

On Ubuntu/Debian, the quickest way is:

curl -fsSL https://tailscale.com/install.sh | sh

Then bring the node online and sign in:

sudo tailscale up

Verify you are connected:

tailscale status

On macOS and Windows, install the client from the Tailscale website, sign in, and ensure the device appears in your admin console.

2) Expose a local service to your tailnet (Serve)

Let’s assume your app runs at http://localhost:3000. Use Tailscale Serve to proxy HTTPS traffic from your device’s Tailscale HTTPS endpoint to your local port:

tailscale serve https / proxy http://localhost:3000

That command maps path / to your local app. Check the status of your Serve configuration:

tailscale serve status

Now open the tailnet URL printed by the command (usually something like https://<device-name>.<tailnet>.ts.net/). Only users/authenticated devices in your tailnet can access it. This is perfect for internal reviews without exposing anything publicly.

3) Make it public with HTTPS (Funnel)

To publish your service to the public internet, first enable the Funnel feature in the Tailscale admin console (Settings → Feature preview/Settings → Funnel, depending on your account). You can restrict which devices and ports are allowed to use Funnel.

Once enabled, turn on Funnel for HTTPS (port 443) on the device:

tailscale funnel 443 on

That’s it. Tailscale will automatically provision a valid TLS certificate and a public URL (again, typically https://<device-name>.<tailnet>.ts.net/). Share this link with anyone; they do not need Tailscale to view your app.

To turn Funnel off again:

tailscale funnel 443 off

4) Useful variations

- Serve a different path: tailscale serve https /app proxy http://localhost:5173
- Serve a TCP port to your tailnet (e.g., Postgres): tailscale serve tcp 5432 127.0.0.1:5432
- Reset all Serve mappings on this device: tailscale serve reset

5) Testing and verification checklist

- Local works: Browse http://localhost:3000.
- Tailnet works: From another device on your tailnet, open https://<device-name>.<tailnet>.ts.net/. You should see a valid HTTPS certificate and your app.
- Public works: After turning on Funnel, test from a device not logged into Tailscale (cellphone on LTE, for example). The same URL should load over HTTPS.

6) Security tips

- Prefer tailnet-only Serve during development. Switch on Funnel only when you actually need a public demo or webhook endpoint.
- If your app needs authentication, keep it enabled. Funnel does not add login by default; it only provides HTTPS and routing.
- Avoid exposing admin panels, databases, or file shares via Funnel. Keep those tailnet-only or behind application-level authentication.

7) Troubleshooting

“403: Funnel not allowed” — Ensure the Funnel feature is enabled for your tailnet in the admin console and that your device/port is permitted.
Port conflicts — If something else is bound to 443 on your device, stop it or remap your Serve path (e.g., Serve at /app) and still use Funnel on 443.
Blank page or mixed content — If your app hardcodes http:// asset URLs, fix them to be relative or https://.
Service not reachable — Confirm your local app responds at the target URL (e.g., curl http://localhost:3000). Then run tailscale serve status to confirm the mapping exists.

8) Keeping it tidy

- List current mappings: tailscale serve status
- Remove a specific route: change your mapping command or reset and re-apply.
- Keep Tailscale updated: use your OS package manager or reinstall via the install script periodically. Check your version with tailscale version.

Why this approach is great

You get automatic TLS, end-to-end encrypted transport, and a clean URL without touching DNS, firewalls, or port forwards. For internal stakeholders, Serve keeps traffic private to your tailnet. For public demos, Funnel gives you one command to go live, then one command to go dark. It’s a powerful quality-of-life upgrade for anyone who ships or supports web apps.

Expose Your Home Server Securely with Cloudflare Tunnel and Docker (No Port Forwarding)

Overview

If you want to publish a self‑hosted service on the internet without opening ports on your router, Cloudflare Tunnel is a modern, zero‑trust solution. In this tutorial, you will deploy Cloudflare Tunnel with Docker, route a subdomain to a local container, and add single‑sign‑on (SSO) protection. This setup works well for homelabs and small businesses, is fast to roll out, and uses Cloudflare’s free plan.

Prerequisites

1) A domain added to Cloudflare (DNS managed by Cloudflare). 2) A Linux server or VM with internet access. 3) Docker and the Docker Compose plugin installed. 4) Basic command‑line access via SSH.

Step 1 — Install Docker and Compose

On Debian/Ubuntu, you can install from the OS repo for a quick start. For production, prefer Docker’s official repositories. Quick start example:

sudo apt update && sudo apt install -y docker.io docker-compose-plugin

Verify installation:

docker --version
docker compose version

Step 2 — Prepare a working directory

Create a project folder to keep tunnel files and your Compose file organized:

mkdir -p ~/cf-tunnel/cloudflared && cd ~/cf-tunnel

Step 3 — Create the Tunnel in Cloudflare

Open Cloudflare Dashboard > Zero Trust > Networks > Tunnels > Add a tunnel. Choose “Cloudflared” and give it a friendly name (for example, homelab-tunnel). After creation, click the tunnel and add a “Public Hostname.” Set Hostname to a subdomain like app.yourdomain.com, and Type to HTTP. For the service URL, use a local address you will run (for example, http://app:3000). Saving this will also create the DNS CNAME for you automatically.

In the same page, download the credentials file (a JSON file with your tunnel ID) and, if offered, the suggested config.yml. Save the JSON to ~/cf-tunnel/cloudflared/ and note the filename; it looks like xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.json.

Step 4 — Create cloudflared config.yml

If you did not download a config from the dashboard, create one now at ~/cf-tunnel/cloudflared/config.yml with the following content (replace placeholders):

tunnel: YOUR-TUNNEL-UUID
credentials-file: /etc/cloudflared/YOUR-TUNNEL-UUID.json

ingress:
  - hostname: app.yourdomain.com
    service: http://app:3000
  - service: http_status:404

This tells cloudflared to forward traffic for your subdomain to the local container named app on port 3000, and return a 404 for everything else.

Step 5 — Create a Docker Compose file

We will run a sample application and cloudflared in the same Docker network. Create ~/cf-tunnel/compose.yml with:

services:
  app:
    image: traefik/whoami:latest
    container_name: whoami
    expose:
     - "80"
    restart: unless-stopped

  cloudflared:
    image: cloudflare/cloudflared:latest
    container_name: cloudflared
    command: tunnel --config /etc/cloudflared/config.yml run
    volumes:
     - ./cloudflared:/etc/cloudflared:ro
    depends_on:
     - app
    restart: unless-stopped

The app service is a tiny HTTP server used as a demo. Cloudflared reads your config and credentials from the mounted folder.

Step 6 — Start the stack and test

Run the following from ~/cf-tunnel:

docker compose up -d
docker logs -f cloudflared

When logs show “Connected to Cloudflare,” visit https://app.yourdomain.com. You should see a response from “whoami.” Use curl -I https://app.yourdomain.com to verify status 200 over HTTPS.

Optional — Add Zero Trust access

To protect your app with SSO, open Cloudflare Dashboard > Zero Trust > Access > Applications > Add an application > Self‑hosted. Set the application domain to app.yourdomain.com. Add a policy to “Allow” specific emails, domains, or identity providers (Google, GitHub, Azure AD). Save. Your app now prompts users to authenticate before reaching your origin.

Maintenance and updates

Update images periodically to receive security patches and performance improvements. Use:

docker compose pull && docker compose up -d

Because the tunnel runs outbound over HTTPS, you do not need to open inbound ports on your router. Keep your server’s system packages current and restrict SSH access with keys and a firewall.

Troubleshooting

502 Bad Gateway: Usually means cloudflared cannot reach your container. Confirm the service name and port in config.yml, ensure both containers share the same Docker network (Compose sets this by default), and that the app is listening on the correct port.

404 Not Found: If you have multiple hostnames, make sure the correct hostname entry exists in the ingress block and that it points to the right service. The last rule should be a 404 fallback.

DNS not resolving: In the Tunnel page, verify the “Public Hostname” exists and the DNS CNAME was created. If needed, create a CNAME for app.yourdomain.com pointing to YOUR-TUNNEL-UUID.cfargotunnel.com.

Authentication loop with Access: Clear cookies or add your domain to “Allowed Cookie Domains” in the Access app settings. Also confirm your policy “Allow” rules match your user identity.

Connectivity drops: Check server time (NTP), ensure no outbound firewall is blocking HTTPS to Cloudflare, and consider running two cloudflared replicas for high availability.

What you achieved

You deployed a secure reverse tunnel with Docker that exposes a local container to the internet under your own domain, without port forwarding or a public IP. You also learned how to enable Cloudflare Access as a zero‑trust layer for SSO and policy control. This pattern scales to multiple services by adding more ingress rules or additional public hostnames in the dashboard, making it a clean, maintainable approach to self‑hosting.

Run MinIO with Docker and Caddy: Secure S3-Compatible Object Storage on Ubuntu 24.04

Overview

This step-by-step guide shows how to deploy MinIO with Docker and secure it behind Caddy for automatic HTTPS. MinIO is a high-performance, S3-compatible object storage that you can self-host for backups, logs, media, and AI datasets. We will use Docker Compose on Ubuntu 24.04, set up Caddy as a reverse proxy with free Let’s Encrypt TLS, create a bucket, and test access using the AWS CLI. The result is a production-ready, S3-compatible endpoint at your own domain.

Prerequisites

You will need: (1) a clean Ubuntu 24.04 server with a public IP, (2) a domain with two DNS A records pointing to your server (for example, minio.example.com and console.example.com), (3) Docker and Docker Compose installed, and (4) port 80/443 open in your firewall and cloud security group. Replace example.com with your domain throughout this tutorial.

Step 1: Open firewall and prepare project

Ensure inbound HTTP/HTTPS traffic is allowed so Caddy can obtain and renew TLS certificates. On Ubuntu with UFW:

sudo ufw allow 80,443/tcp
sudo ufw reload
mkdir -p ~/minio-caddy && cd ~/minio-caddy

Step 2: Create Docker Compose file

We will run MinIO and Caddy on the same Docker network. Caddy will request certificates from Let’s Encrypt automatically and reverse proxy to MinIO’s API and web console.

cat > docker-compose.yml <<'YAML'
version: "3.8"
services:
  minio:
    image: minio/minio:latest
    command: server /data --console-address ":9001" --address ":9000"
    environment:
      - MINIO_ROOT_USER=admin
      - MINIO_ROOT_PASSWORD=ChangeMe-StrongSecret123!
    volumes:
      - minio_data:/data
    restart: unless-stopped
    networks:
      - edge

  caddy:
    image: caddy:2
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
    depends_on:
      - minio
    restart: unless-stopped
    networks:
      - edge

volumes:
  minio_data:

networks:
  edge:
    driver: bridge
YAML

Step 3: Create Caddyfile for automatic HTTPS

This configuration terminates TLS, enables compression, and proxies API and console to MinIO. Make sure both hostnames have valid DNS A records pointing to your server’s public IP before starting.

cat > Caddyfile <<'CADDY'
minio.example.com {
  encode zstd gzip
  reverse_proxy minio:9000
}

console.example.com {
  encode zstd gzip
  reverse_proxy minio:9001
}
CADDY

Step 4: Start the stack

Bring everything up in the background. Caddy will automatically request TLS certificates from Let’s Encrypt on the first request and keep them renewed.

docker compose up -d
docker compose logs -f caddy

Once ready, visit https://console.example.com to access the MinIO console. Log in with the root credentials you set (admin / ChangeMe-StrongSecret123!). For security, change this password after your first login and create dedicated users for apps instead of sharing root.

Step 5: Create a bucket and access keys with MinIO Client (mc)

Use the MinIO Client to create a bucket and a non-root user with read/write access. Running mc in Docker avoids installing additional packages on the host.

# Add the MinIO endpoint alias (uses HTTPS through Caddy)
docker run --rm -it minio/mc \
  alias set myminio https://minio.example.com admin 'ChangeMe-StrongSecret123!'

# Create a bucket
docker run --rm -it minio/mc mb myminio/my-bucket

# Create an app user (access key) and attach readwrite policy
docker run --rm -it minio/mc \
  admin user add myminio appuser 'Another-StrongSecret456!'

docker run --rm -it minio/mc \
  admin policy attach myminio readwrite --user appuser

Step 6: Test with AWS CLI

MinIO is S3-compatible, so existing tools work by pointing to your endpoint. The AWS CLI is a convenient way to confirm access.

# Install AWS CLI if missing (Ubuntu)
sudo apt-get update && sudo apt-get install -y awscli

# Export temporary credentials for testing
export AWS_ACCESS_KEY_ID=appuser
export AWS_SECRET_ACCESS_KEY='Another-StrongSecret456!'

# List buckets on MinIO via your HTTPS endpoint
aws s3 ls --endpoint-url https://minio.example.com

# Upload a file to the new bucket
echo "hello from minio" > test.txt
aws s3 cp test.txt s3://my-bucket/ --endpoint-url https://minio.example.com

Maintenance and hardening tips

- Change the root password after initial setup and keep it offline. Create per-application users with the least privileges required. Rotate secrets regularly.

- Back up the MinIO data volume and, if critical, replicate to another MinIO cluster or cloud S3 using lifecycle policies or tools like rclone. Test restores.

- Keep Docker images up to date: run “docker compose pull && docker compose up -d” during a maintenance window. MinIO releases frequent fixes and performance updates.

- Monitor health and logs: “docker compose logs -f minio” and “docker compose logs -f caddy”. Use MinIO Console dashboards to watch capacity and performance.

Troubleshooting

- Certificate errors: confirm DNS is correct and that port 80 is open. Let’s Encrypt requires HTTP-01 reachability for the first certificate. Check “docker compose logs -f caddy”.

- 502/Bad Gateway: ensure “minio” container is running and healthy, and verify the Caddyfile hostnames match your browser URL. Restart with “docker compose restart”.

- Permission or upload failures: verify bucket policies or user permissions via the MinIO Console, and confirm your AWS CLI command uses “--endpoint-url”.

You now have a secure, S3-compatible object storage endpoint running on your own infrastructure with automatic HTTPS. Use it for application artifacts, backups, and large datasets, and manage everything from an easy web console and standard S3 tooling.

Popular Posts

Install Ollama and Open WebUI on Ubuntu 24.04 with NVIDIA GPU Acceleration (Step-by-Step)

Install Ollama + Open WebUI on Ubuntu 24.04 with NVIDIA GPU Acceleration (Step-by-Step)

Install a Local AI Chatbot on Ubuntu 24.04 with Ollama and Open WebUI (Step-by-Step)

Trending Now

Recovering from Btrfs Boot Failures Using GUI Tools on Fedora

By the end of this guide the reader will be able to identify a Btrfs‑based Fedora installation, boot from a live USB, list and restore snapshots using the graphical utilities btrfs‑assistant and snapper, and verify that the system returns to a functional state without resorting to the command line. Understanding the Btrfs Layout Used by Fedora Fedora Workstation and Fedora KDE install the root filesystem as a single Btrfs partition that contains two default sub‑volumes. One sub‑volume holds the traditional “/” hierarchy, while the second is dedicated to /var/lib/machines . The latter exists to keep container images out of snapshot operations; it remains empty on systems that do not run virtual machines. Because Btrfs stores data in sub‑volumes rather than separate partitions, a snapshot captures the state of an entire sub‑volume at a point in time. The installer (Anaconda) automatically registers these sub‑volumes with the snapper service. Snapper maintains a series of read‑only ...