An open API service indexing awesome lists of open source software.

https://github.com/jsabo/local-k8s


https://github.com/jsabo/local-k8s

Last synced: 6 months ago
JSON representation

Awesome Lists containing this project

README

          

# local-k8s — kubeadm + Multipass demo cluster (WordPress + RBAC + CSR users + Dashboard + Ingress)

This repo builds a **reproducible local Kubernetes cluster** using **kubeadm** on **Multipass** VMs and deploys a **custom WordPress + MySQL Helm chart** with **persistent storage** and **minimum-privilege user access**.

It’s designed to be:
- **Easy to run** on a laptop (single `task` command)
- **Reproducible** (idempotent tasks, stamp files, local state in `./.state`)
- **Security-focused** (namespace RBAC + CSR-based user credentials)
- **Demo-friendly** (prints URLs, tokens, and `/etc/hosts` mapping)

---

## What this project includes

### Cluster
- 3-node Kubernetes cluster:
- 1 control-plane (`cp-0`)
- 2 workers (`worker-0`, `worker-1`)
- Built with:
- **kubeadm**
- **containerd**
- **Calico** (via Tigera operator)
- Local state output:
- `./.state/kubeconfig` (admin kubeconfig for cluster operations)
- `./.state/env` (helper to export `KUBECONFIG`)

### Storage
- **Rancher local-path-provisioner** for dynamic local persistent volumes
- Provides a default `StorageClass` (`local-path`) suitable for demos

### Workloads
- **Custom WordPress Helm chart** (`charts/custom-wordpress`)
- WordPress Deployment + Service
- MySQL Deployment + Service
- PVCs for WordPress and MySQL
- Secret with generated credentials (preserved across upgrades)
- **Two user types** (namespace-scoped):
- **Deployer**: install/upgrade/uninstall the WordPress release in a namespace
- **Accessor**: read basics + port-forward to access WordPress
- **Kubernetes Dashboard** (Helm install) with a read-only `view` token for convenience
- **Ingress-NGINX (NodePort)** + Ingress rules:
- `/` → WordPress
- `/dashboard` → Dashboard
- `/api` → stub API service (currently `traefik/whoami`)

---

## Architecture overview

### Components and flow

1. Multipass launches 3 Ubuntu VMs using `cloud-init.tftpl`
2. `kubeadm init` runs on `cp-0`
3. Workers join using a token generated on `cp-0`
4. Calico installs cluster networking
5. local-path-provisioner installs default storage
6. Namespaced RBAC + CSR user kubeconfigs are created
7. WordPress is installed using the **deployer** kubeconfig
8. Access is performed using:
- Ingress URLs (recommended for the demo), and/or
- Accessor kubeconfig + port-forward

### Conceptual networking

```

```
(your laptop)
|
/etc/hosts: demo.local -> cp-0 IP
|
NodePort 30080 / 30443 (Ingress-NGINX)
|
+--------------+-------------------+
| |
```

"/" -> WordPress Service "/api" -> goapi Service
"/dashboard" -> Dashboard Service (stub whoami)

````

---

## Security model (how access is controlled)

This repo intentionally demonstrates **minimum-privilege access**.

### Authentication
User credentials are created via **Kubernetes CertificateSigningRequest (CSR)**:
- A private key + CSR is generated locally
- A CSR object is submitted to the cluster
- The CSR is approved
- A kubeconfig is generated embedding the user cert and cluster CA

Generated kubeconfigs live in:
- `./.state/users//deployer.kubeconfig`
- `./.state/users//accessor.kubeconfig`

### Authorization (RBAC)
RBAC is namespace-scoped in `./rbac`:
- `wp-deployer` Role:
- create/update/delete common namespaced objects needed for Helm
- manage Deployments/ReplicaSets/Jobs/Ingresses/PVCs/Secrets/etc.
- `wp-accessor` Role:
- read-only discovery (`get/list/watch`) for Pods/Services/Endpoints
- `create` on `pods/portforward` (required for port-forward)
- optional `get` on `pods/log` for troubleshooting

> Note: This keeps application access separate from cluster-admin operations. Cluster bootstrap and cluster-scoped components (Calico, storage class, ingress controller, dashboard install) are installed with the admin kubeconfig.

---

## Repository layout

- `Taskfile.yaml` — automation entrypoint (`task demo:install`)
- `cloud-init.tftpl` — provisions VMs (containerd + kubelet/kubeadm/kubectl)
- `charts/custom-wordpress/` — custom Helm chart
- `rbac/` — namespaced RBAC for deployer/accessor users
- `./.state/` — generated state (kubeconfigs, rendered cloud-init, stamps)

---

## Prerequisites

You need these on your laptop:

- `multipass`
- `kubectl`
- `helm`
- `task` (Taskfile runner)
- `envsubst` (from gettext; used to render cloud-init)

---

## Quickstart

### 1) Install everything (cluster + apps)
```bash
task demo:install
````

This will:

* create VMs
* init kubeadm
* install Calico
* install local-path-provisioner
* install Dashboard
* install WordPress (using deployer credentials)
* install ingress-nginx + rules
* print URLs + dashboard token + `/etc/hosts` mapping

### 2) Add the host mapping

The demo assumes you’ll resolve `demo.local` to the **cp-0 IP**.

Run:

```bash
task demo:info
```

It prints a line like:

```
192.168.x.y demo.local
```

Add that to `/etc/hosts` on your laptop.

### 3) Browse

* WordPress: `http://demo.local:30080/`
* Dashboard: `https://demo.local:30443/dashboard`
* API stub: `http://demo.local:30080/api`

The Dashboard login token is printed by `task demo:info`.

---

## How to show least-privilege access during a demo

### Admin kubeconfig (cluster operations)

```bash
export KUBECONFIG=./.state/kubeconfig
kubectl get nodes -o wide
```

### Deployer user (application lifecycle via Helm)

The WordPress release is installed using:

* `./.state/users//deployer.kubeconfig`

Example:

```bash
export KUBECONFIG=./.state/users/wordpress-demo/deployer.kubeconfig
kubectl -n wordpress-demo get deploy,svc,pvc
```

### Accessor user (port-forward + basic reads)

Example:

```bash
export KUBECONFIG=./.state/users/wordpress-demo/accessor.kubeconfig
kubectl -n wordpress-demo get svc
kubectl -n wordpress-demo port-forward svc/custom-wordpress 8080:80
# open http://127.0.0.1:8080
```

---

## WordPress chart design

This repo includes a small, transparent Helm chart (`charts/custom-wordpress`) that deploys:

* WordPress Deployment + Service
* MySQL Deployment + Service
* PVCs (WordPress + MySQL) when persistence is enabled
* A Secret for DB credentials

### Credentials behavior

* By default, the chart **generates random passwords**
* On upgrades, it **reuses existing Secret values** to avoid rotating credentials unexpectedly
* You can supply your own Secret via `auth.existingSecret`

To print generated passwords:

```bash
export KUBECONFIG=./.state/kubeconfig
kubectl -n wordpress-demo get secret custom-wordpress-db \
-o jsonpath='{.data.wordpress-password}' | base64 -d; echo
kubectl -n wordpress-demo get secret custom-wordpress-db \
-o jsonpath='{.data.mysql-root-password}' | base64 -d; echo
```

### Storage behavior

* Uses the cluster default StorageClass unless overridden
* The Taskfile installs **local-path-provisioner** and sets `local-path` as default
* WordPress + MySQL each get a PVC (default 5Gi each)

---

## Ingress + Dashboard

### Ingress

Ingress-NGINX is installed as a NodePort service:

* HTTP: `30080`
* HTTPS: `30443`

Ingress rules are created for:

* WordPress: `/`
* Dashboard: `/dashboard` (rewrites and uses HTTPS to backend)
* API stub: `/api`

### Dashboard access

Dashboard is installed via Helm, then a dedicated ServiceAccount is created and bound to the `view` ClusterRole for read-only access.

Get a token:

```bash
export KUBECONFIG=./.state/kubeconfig
kubectl -n kubernetes-dashboard create token dashboard-viewer
```

> Security note: `view` is cluster-wide read-only. It’s convenient for demos; for stricter environments, scope dashboard access more tightly.

---

## Common operations

### List public tasks

```bash
task
```

### Check cluster status

```bash
task cluster:status
```

### Print demo URLs + token + /etc/hosts line

```bash
task demo:info
```

### Uninstall demo apps (keep cluster)

```bash
task demo:uninstall
```

### Delete everything (VMs + local state)

```bash
task cluster:down
```

---

## Troubleshooting

### “demo.local does not resolve”

Run:

```bash
task demo:info
```

Add the printed `cp-0 IP -> demo.local` mapping to `/etc/hosts`.

### Ingress works but the Dashboard path fails

The dashboard Ingress uses regex + rewrite and expects HTTPS to the backend service. If the dashboard Helm chart changes service names, the Taskfile detects the service dynamically, but if needed you can check:

```bash
export KUBECONFIG=./.state/kubeconfig
kubectl -n kubernetes-dashboard get svc
kubectl -n kubernetes-dashboard get ingress
```

### Reset the CSR-generated kubeconfigs

```bash
FORCE=1 task -s wp:user:deployer
FORCE=1 task -s wp:user:accessor
```

### Start from scratch

```bash
task cluster:down
task demo:install
```

---

## Tradeoffs and next steps

### Tradeoffs in this approach

* **CSR-based users** are easy to demonstrate but are operationally heavy at scale:

* manual approvals (or additional automation)
* certificate lifecycle/rotation and revocation complexity
* **Local-path storage** is perfect for demos but not HA and not suitable for production
* Cluster-scoped installs (CNI, ingress controller, dashboard) still require admin-level bootstrapping

### Recommended next steps (if hardening beyond a demo)

* Replace CSR users with **OIDC-based auth** (centralized identity, group claims)
* Add automated policy enforcement (e.g., Pod Security admission, network policies)
* Replace local-path storage with a proper CSI backend if moving beyond a single-machine demo
* Replace the stub API service with a real Go service and least-privilege DB access

---

## License / usage

This repository is intended as a learning/demo environment. Use it freely and adapt as needed.