Commit 513f48fe authored by Muhammad Umair Khan's avatar Muhammad Umair Khan
Browse files

bug fixes: PyInfra

parent 0a582662
Loading
Loading
Loading
Loading
+7 −5
Original line number Diff line number Diff line
@@ -15,6 +15,7 @@ SCRIPT=$(readlink -f "$0")
BASEDIR=$(dirname "$SCRIPT")

# Configure environment
export PATH=$PATH:/usr/local/go/bin:$HOME/gocode/bin
export GOOS=linux
IMAGE_NAME="meepctl"
BINDIR="../../bin/meepctl"
@@ -58,15 +59,16 @@ printf "%b\n" "${BLUE}${BOLD}➤ Running go install${NC}"
go install

echo ""
# Configure sudoers for meepctl certificate trust operations
# Configure sudoers for meepctl certificate trust operations (when HTTPS)
printf "%b\n" "${BLUE}${BOLD}➤ Configuring sudoers NOPASSWD for meepctl certificate operations${NC}"
SUDOERS_RULE="${USER} ALL=(ALL:ALL) NOPASSWD: /usr/bin/cp *, /bin/cp *, /usr/sbin/update-ca-certificates, /usr/bin/update-ca-certificates, /usr/bin/systemctl restart containerd, /usr/bin/systemctl restart docker, /bin/systemctl restart containerd, /bin/systemctl restart docker"
TARGET_USER="${SUDO_USER:-$USER}"
SUDOERS_RULE="${TARGET_USER} ALL=(ALL:ALL) NOPASSWD: /usr/bin/cp *, /bin/cp *, /usr/sbin/update-ca-certificates, /usr/sbin/update-ca-certificates *, /usr/bin/update-ca-certificates, /usr/bin/update-ca-certificates *, /usr/bin/systemctl restart containerd, /usr/bin/systemctl restart docker, /bin/systemctl restart containerd, /bin/systemctl restart docker"
if [ -n "$SUDO_PASSWORD" ]; then
    echo "$SUDO_PASSWORD" | sudo -S sh -c "echo '$SUDOERS_RULE' > /etc/sudoers.d/meepctl && chmod 0440 /etc/sudoers.d/meepctl" 2>/dev/null || true
    echo "$SUDO_PASSWORD" | sudo -S sh -c "echo '$SUDOERS_RULE' > /etc/sudoers.d/meepctl && chmod 0440 /etc/sudoers.d/meepctl" 2>/dev/null || echo "⚠️  WARNING: Failed to create /etc/sudoers.d/meepctl (sudo required)"
elif sudo -n true 2>/dev/null; then
    sudo sh -c "echo '$SUDOERS_RULE' > /etc/sudoers.d/meepctl && chmod 0440 /etc/sudoers.d/meepctl" 2>/dev/null || true
    sudo sh -c "echo '$SUDOERS_RULE' > /etc/sudoers.d/meepctl && chmod 0440 /etc/sudoers.d/meepctl" 2>/dev/null || echo "⚠️  WARNING: Failed to create /etc/sudoers.d/meepctl (sudo required)"
else
    sudo sh -c "echo '$SUDOERS_RULE' > /etc/sudoers.d/meepctl && chmod 0440 /etc/sudoers.d/meepctl" || true
    sudo sh -c "echo '$SUDOERS_RULE' > /etc/sudoers.d/meepctl && chmod 0440 /etc/sudoers.d/meepctl" || echo "⚠️  WARNING: Failed to create /etc/sudoers.d/meepctl (sudo required)"
fi

echo ""
+5 −9
Original line number Diff line number Diff line
@@ -4,18 +4,14 @@
# ----------------------------------------------------
# Inventory Configuration
# ----------------------------------------------------
# Comma-separated list of target IPs or hostnames for K8s masters and workers.
# Defaults to localhost if left blank.
# Target hosts for Kubernetes cluster.
# NOTE: Exactly 1 master node is supported for K8S_MASTERS (control plane).
# For local deployment, use "localhost".
# For remote deployment, specify target hosts in mandatory <username>@<ip> format
# (e.g., K8S_MASTERS="ubuntu@192.168.1.10" and K8S_WORKERS="admin@192.168.1.11,ubuntu@192.168.1.12").
K8S_MASTERS="localhost"
K8S_WORKERS=""

# The SSH user to connect as (and the owner of the local sandbox files).
# Defaults to your current logged-in user if left blank.
# TARGET_USER="ubuntu"

# Sudo password for operations requiring root privileges.
# SUDO_PASSWORD=""

# ----------------------------------------------------

# Sandbox Configuration
+71 −44
Original line number Diff line number Diff line
# Pyinfra Deployment Framework
# ETSI MEC Sandbox Automated Deployment Guide

This directory (`pyinfra/`) contains the automated infrastructure-as-code (IaC) deployment framework for the ETSI MEC Sandbox. It utilizes a declarative, Python-based deployment model using [Pyinfra](https://pyinfra.com/).
This guide walks you through deploying the **ETSI MEC Sandbox** using our automated, Python-based infrastructure-as-code ([PyInfra](https://pyinfra.com/)) framework.

## What it does
The framework automates the entire provisioning lifecycle—including kernel tuning, Docker/Containerd runtime installation, Kubernetes (`kubeadm`) cluster initialization, development tools (Go, Node.js, NVM), and compiling and running the MEC Sandbox microservices (`meepctl`).

This framework handles the end-to-end provisioning and configuration of the MEC Sandbox environment. Its core responsibilities include:
---

- **System Initialization:** Ensuring proper kernel modules, network routing, and core system dependencies are configured.
- **Container Runtimes:** Idempotent installation and configuration of Docker and Containerd.
- **Kubernetes Cluster Setup:** Bootstrapping the Kubernetes control plane and joining worker nodes via `kubeadm`.
- **Development Environment:** Installing specific versions of Golang, Node.js (via NVM), and linting tools.
- **Platform Orchestration (meepctl):** Configuring and deploying the MEC core platform, frontend, and dependencies using `meepctl`.
## Prerequisites

Everything in this folder is designed to be **idempotent**—you can safely run the deployment multiple times without causing unintended side effects or breaking the system.
Before deploying, ensure your target machine(s) meet the following requirements:
- **Operating System:** Ubuntu 20.04/22.04 LTS (or Debian-compatible Linux).
- **Python:** Python 3.8+ installed on the machine running this deployment.
- **Privileges:** Sudo (root) access on the target deployment machines.
- **Network:** Outbound internet access to download required containers, packages, and binaries.

---

## How to Run the Deployment

Follow these steps to deploy the infrastructure.

### 1. Initial Setup
## Quick Start (3 Steps)

Before deploying, you must initialize your local environment. We provide an automated bootstrap script that ensures Python 3 is installed, sets up an isolated virtual environment (`pyinfra-venv`), and installs the `pyinfra` package cleanly.
### Step 1: Initialize the Deployment Environment
Run the automated setup script to verify Python 3, create an isolated virtual environment (`pyinfra-venv`), and install all required deployment dependencies:

```bash
cd pyinfra
./setup.sh
```

### 2. Configure Environment Variables
### Step 2: Configure Your Environment (`.env`)
The first time you run `./setup.sh`, it generates a `.env` configuration file from `.env.example` and pauses so you can enter your settings.

Open `.env` in your text editor and configure the following required fields:
- **`K8S_MASTERS`:** Mandatory target host for the Kubernetes control plane (exactly 1 master node is supported; e.g., `localhost` for local deployments, or `ubuntu@192.168.1.10` for remote servers).
- **`K8S_WORKERS`:** Optional comma-separated list of worker node IPs/hostnames. Leave blank (`""`) for single-machine deployments.
- **`MEC_HOST_ADDRESS`:** The routable IP address or domain name where the MEC Sandbox frontend will be accessible (e.g., `127.0.0.1`, `192.168.1.100`, or `mec.example.com`).
- **OAuth Provider Credentials:** Provide valid OAuth secrets for **GitHub** (`GITHUB_CLIENT_ID`, `GITHUB_CLIENT_SECRET`), **GitLab**, or both. Unconfigured providers are automatically disabled in the platform configuration.

> [!IMPORTANT]
> **Do not set both `K8S_MASTERS` and `K8S_WORKERS` to `localhost`.**  
> A single machine cannot act as both an independent Kubernetes master and worker node. For an all-in-one sandbox on your local machine, set `K8S_MASTERS="localhost"` and leave `K8S_WORKERS=""`.

If this is your first time running the setup script, it will automatically generate a `.env` file from the `.env.example` template and exit safely to allow you to configure your secrets.
### Step 3: Run the Deployment
Activate the virtual environment and launch the deployment:

Open the `.env` file in your preferred editor and configure the necessary variables:
- **MEC_HOST_ADDRESS:** Set the routable IP or domain for the MEC frontend.
- **OAuth Secrets:** Configure your **GitHub** OAuth credentials (`GITHUB_CLIENT_ID`, `GITHUB_CLIENT_SECRET`), **GitLab** OAuth credentials (`GITLAB_CLIENT_ID`, `GITLAB_CLIENT_SECRET`), or **both**.
  - *Best Practice Check:* At least one OAuth provider (GitHub or GitLab) must be configured with a valid Client ID and Secret. Unconfigured providers are automatically disabled in `.meepctl-repocfg.yaml`, and configured providers are enabled and updated idempotently.
```bash
source pyinfra-venv/bin/activate
pyinfra inventory.py deploy.py
```

---

### 3. Deploying the Infrastructure
## Authentication & Sudo Passwords

Once the `.env` file is properly configured, activate the virtual environment and execute the Pyinfra deployment. 
For security, **sudo passwords are never stored in config files or environment variables.**

The deployment process slightly differs depending on whether you are deploying locally or to remote servers.
When you launch `pyinfra inventory.py deploy.py`:
1. **Prompted Once at Startup:** PyInfra will prompt you in the terminal for your sudo password:
   ```text
   Enter sudo password for K8S_MASTERS node(s) (press Enter for passwordless sudo):
   ```
2. **Worker Credentials (If Applicable):** If you configured remote `K8S_WORKERS`, you will be prompted separately for the worker nodes' sudo password.
3. **Non-Interactive Execution:** After entering your password at startup, PyInfra caches it in memory and automatically authenticates all sudo operations in the background. You will not be prompted again during the deployment.

#### Option A: Local Deployment (Localhost)
If you are deploying the sandbox directly to the machine you are currently logged into:
---

1. Ensure `K8S_MASTERS="localhost"` in your `.env` file.
2. Run the deployment:
## Deployment Modes

### Option A: Local / Single-Machine Deployment (Default)
To deploy the entire MEC Sandbox directly on the machine you are currently logged into:
1. Set `K8S_MASTERS="localhost"` and `K8S_WORKERS=""` in `.env`.
2. Execute:
   ```bash
   source pyinfra-venv/bin/activate
   pyinfra inventory.py deploy.py
   ```
*(Pyinfra will automatically execute commands locally using `sudo` where required).*

#### Option B: Remote Deployment (via SSH)
If you are deploying to remote servers, Pyinfra will execute the deployment over SSH.

1. Ensure `K8S_MASTERS` and `K8S_WORKERS` in your `.env` file contain the remote IP addresses or DNS names (e.g., `K8S_MASTERS="192.168.1.10"`).
2. Ensure you have passwordless SSH access configured for the target servers (e.g., using `ssh-copy-id`).
3. Set the appropriate SSH user by uncommenting and configuring `TARGET_USER` in the `.env` file.
4. Run the deployment:

### Option B: Remote / Multi-Node Kubernetes Cluster
To deploy across multiple remote servers:
1. **Configure Targets in `.env`:** Specify target remote hosts in mandatory `<username>@<ip>` format (note: exactly 1 master node is supported for the control plane):
   ```env
   K8S_MASTERS="ubuntu@192.168.1.10"
   K8S_WORKERS="ubuntu@192.168.1.11,admin@192.168.1.12"
   ```
2. **Execute Deployment & Provide Sudo Passwords:**
   ```bash
   source pyinfra-venv/bin/activate
pyinfra inventory.py deploy.py
   pyinfra inventory.py deploy.py -y
   ```
   - **Mandatory Sudo Prompts:** Installing system packages and Kubernetes requires root privileges (`sudo`). PyInfra will interactively prompt you for the sudo password of your `K8S_MASTERS` nodes (and separately for `K8S_WORKERS`, if configured). Press **Enter** if the target account has passwordless sudo enabled on the remote server.

---

## Resuming Interrupted Deployments

The deployment process is **idempotent and checkpointed**:
- Long-running stages (such as compiling `meepctl` binaries and packaging container images) create checkpoint markers automatically.
- If your network disconnects or an execution is interrupted, simply re-run `pyinfra inventory.py deploy.py`.
- The installer will skip all completed stages and resume immediately from the last checkpoint without restarting from scratch.
+31 −26
Original line number Diff line number Diff line
from pyinfra import local
from pyinfra import host

# Load Pyinfra tasks in the correct Ansible order
# Load Pyinfra tasks

# Kubernetes Master Setup
if "k8s_masters" in host.groups:
    # # System Configuration
    # local.include("tasks/system/common.py")
    # local.include("tasks/system/kernel.py")
    # # Container Runtime
    # local.include("tasks/container_runtime/docker.py")
    # local.include("tasks/container_runtime/containerd.py")

    # # Kubernetes Cluster (Common packages)
    # local.include("tasks/k8s_cluster/kubernetes_common.py")
    # local.include("tasks/k8s_cluster/kubernetes_master.py")
    # local.include("tasks/k8s_cluster/cni_calico.py")
    # local.include("tasks/k8s_cluster/helm.py")

    # Dev Environment & Sandbox
    install_dev_env = host.data.get('install_dev_env', True)
    install_mec_sandbox = host.data.get('install_mec_sandbox', True)
    # if install_dev_env:
    #     local.include("tasks/apps/dev_env.py")
    if install_mec_sandbox: 
        local.include("tasks/apps/mec_sandbox.py")

# Kubernetes Worker Setup
if "k8s_workers" in host.groups:
    # System Configuration
    local.include("tasks/system/common.py")
    local.include("tasks/system/kernel.py")

    # Container Runtime
    local.include("tasks/container_runtime/docker.py")
    local.include("tasks/container_runtime/containerd.py")

    # Kubernetes Cluster (Common packages)
    local.include("tasks/k8s_cluster/kubernetes_common.py")

# Kubernetes Master Setup
if "k8s_masters" in host.groups:
    local.include("tasks/k8s_cluster/kubernetes_master.py")
    local.include("tasks/k8s_cluster/cni_calico.py")
    local.include("tasks/k8s_cluster/helm.py")

# Kubernetes Worker Setup
if "k8s_workers" in host.groups:
    local.include("tasks/k8s_cluster/kubernetes_worker.py")
 No newline at end of file

# Applications & Dev Environment
install_dev_env = host.data.get('install_dev_env', True)
install_mec_sandbox = host.data.get('install_mec_sandbox', True)

if install_dev_env:
    local.include("tasks/apps/dev_env.py")

if install_mec_sandbox:
    local.include("tasks/apps/mec_sandbox.py")
+5 −8
Original line number Diff line number Diff line
@@ -6,13 +6,10 @@ from lib.config_helpers import (
    validate_k8s_hosts as __validate_k8s_hosts,
    get_master_sudo_password as __get_master_sudo_password,
    get_worker_sudo_password as __get_worker_sudo_password,
    get_target_user_and_home as __get_target_user_and_home,
)

__ssh_user, _ = __get_target_user_and_home()

# 1. Host Resolution & Validation:
#    - Converts "localhost"/"127.0.0.1" to PyInfra's local executor alias "@local".
#    - Converts "localhost"/"127.0.0.1" to PyInfra's local executor tuple ("@local", user).
#    - Validates that "@local" is not assigned to both master and worker groups, as a single
#      OS instance cannot act as both an independent control-plane master and worker node.
__master_hosts = __get_k8s_masters()
@@ -32,11 +29,11 @@ __worker_sudo_password = __get_worker_sudo_password() if __worker_hosts else Non
#    - During deployment, any task invoked with `_sudo=True` automatically uses the stored
#      sudo_password to authenticate via `sudo -S` non-interactively.
k8s_masters = [
    (host, {"ssh_user": __ssh_user, "sudo_password": __master_sudo_password})
    for host in __master_hosts
    (host_addr, {"ssh_user": ssh_user, "sudo_password": __master_sudo_password})
    for host_addr, ssh_user in __master_hosts
]

k8s_workers = [
    (host, {"ssh_user": __ssh_user, "sudo_password": __worker_sudo_password})
    for host in __worker_hosts
    (host_addr, {"ssh_user": ssh_user, "sudo_password": __worker_sudo_password})
    for host_addr, ssh_user in __worker_hosts
]
Loading