https://github.com/jsabo/local-k8s
https://github.com/jsabo/local-k8s
Last synced: 6 months ago
JSON representation
- Host: GitHub
- URL: https://github.com/jsabo/local-k8s
- Owner: jsabo
- License: apache-2.0
- Created: 2024-07-31T23:55:27.000Z (almost 2 years ago)
- Default Branch: main
- Last Pushed: 2024-08-22T22:38:58.000Z (almost 2 years ago)
- Last Synced: 2024-08-23T00:08:43.776Z (almost 2 years ago)
- Size: 16.6 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
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.