Provisioning a single-node Kubernetes cluster with kubeadm/k3s, Ansible and Terraform
A comprehensive two-stage infrastructure-as-code solution for provisioning single-node Kubernetes clusters with enterprise-grade applications. Uses Ansible for server setup and Kubernetes cluster bootstrap, followed by Terraform for deploying a complete application stack including GitLab, monitoring, storage, VPN, and CI/CD tools.
Core Architecture:
- Stage 1 (Ansible): Server hardening, Kubernetes cluster provisioning (kubeadm/k3s/minikube)
- Stage 2 (Terraform): Application deployment and infrastructure services
- Containerized Tooling: Docker image with kubectl, helm, terraform, ansible, kubent
Supported Platforms:
- Target: Ubuntu AMD64 servers (GitLab limitation)
- Kubernetes Options: kubeadm (recommended), k3s (alternative), minikube (experimental)
- Infrastructure: Single-node clusters optimized for homelab environments
homelab-infrastructure/
├── stage1/ # Ansible playbooks and roles
│ ├── ansible.cfg # Ansible configuration
│ ├── site.yml # Main playbook orchestration
│ ├── inventories/
│ │ └── inventory.yml # Host definitions and version pinning
│ ├── roles/
│ │ ├── host_setup/ # Server hardening (fail2ban, ufw)
│ │ ├── kubeadm_*/ # kubeadm cluster setup
│ │ ├── k3s_*/ # k3s cluster setup
│ │ ├── minikube_*/ # minikube cluster setup
│ │ └── localhost_post_setup/ # Post-deployment tasks
│ └── requirements*.txt # Python/Ansible dependencies
├── stage2/ # Terraform modules
│ ├── main.tf # Module orchestration and dependencies
│ ├── variables.tf # All input variables
│ ├── providers.tf # Provider configurations
│ ├── backend.tf # Terraform Cloud backend
│ └── <module>/ # Individual service modules
├── scripts/ # Helper scripts
├── container/ # Container customization files
├── Dockerfile # Alpine container with all tools
├── Taskfile.yml # Task runner (primary command interface)
├── .pre-commit-config.yaml # Pre-commit hooks configuration
└── .env.sample # Environment variable template
Task Runner (Taskfile.yml):
| Command | Description |
|---|---|
task repo:setup |
Install dependencies (pre-commit, ansible-galaxy, pip) |
task docker:build |
Build Alpine container image |
task docker:run |
Start container with volume mounts |
task docker:exec |
Interactive bash session in container |
task precommit |
Run all pre-commit hooks |
task stage1:ansible:ping |
Verify SSH connectivity to server |
task stage1:ansible:playbook |
Deploy Stage 1 (Ansible) |
task stage2:terraform:init |
Initialize Terraform |
task stage2:terraform:plan |
Plan Terraform deployment |
task stage2:terraform:apply |
Apply Terraform deployment |
task stage2:terraform:lock |
Regenerate provider lock for multi-platform |
Container Tools:
| Tool | Version |
|---|---|
| kubectl | 1.35.0 |
| helm | 3.19.4 |
| terraform | 1.14.3 |
| taskfile | 3.46.4 |
| trivy | 0.68.2 |
-
Set up Terraform Cloud and create an API key.
-
Install Ubuntu AMD64 on a server and configure the following:
- GitLab (
registry.gitlab.com/gitlab-org/build/cng/kubectl) does not support ARM64 yet. - Note that the server SSH port must not be
22.
$ ssh chrislee@192.168.1.100 >$ sudo mkdir -p /etc/systemd/system/ssh.socket.d >$ sudo cat >/etc/systemd/system/ssh.socket.d/override.conf <<EOF [Socket] ListenStream=2222 EOF >$ sudo systemctl daemon-reload >$ reboot $ ssh chrislee@192.168.1.100 -p2222 >$ vim ~/.ssh/authorized_keys # Add the public key located at ~/.ssh/id_rsa.pub to the authorized_keys file
- GitLab (
-
Copy the
.env.samplefile to a new file named.envand configure it accordingly.cp .env.sample .env
- Make sure to set
kubernetes_cluster_typeto eitherk3sorkubeadm. - Note that
minikubecan be provisioned but failed to work with the current setup.
- Make sure to set
-
Ensure that you have an SSH key file ready for use with Ubuntu (e.g.,
~/.ssh/id_rsa.pub). -
Run
repo:setupto make sure you have all the necessary tools.task repo:setup
-
Verify access by running the following commands:
$ task docker:exec /srv# cd stage1 /srv/stage1# ansible all -i "inventories/inventory.yml" -m ping
-
If you want to validate the Ansible playbook, you can run the following command:
/srv/stage1# ansible-playbook --ask-become-pass -i inventories/inventory.yml site.yml --check BECOME password: <ubuntu root password>
-
-
Prepare the VM template by running the following command:
/srv/stage1# ansible-playbook --ask-become-pass -i "inventories/inventory.yml" site.yml BECOME password: <ubuntu root password>
- At the end of the playbook, the
.kube/configshould be copied to the local machine incontainer/root/.kube/config.
- At the end of the playbook, the
-
Initialize Terraform by running the following commands:
$ task docker:exec /srv# cd stage2 /srv/stage2# terraform workspace select <workspace name> /srv/stage2# terraform init
-
Provision infrastructure using Terraform by running the following commands:
/srv/stage2# terraform plan /srv/stage2# terraform apply
Environment Variables (.env file):
# Kubernetes cluster configuration
kubernetes_cluster_type=kubeadm # kubeadm, k3s, or minikube
server_ssh_host=192.168.1.100 # Target server IP
server_ssh_user=ubuntu # SSH username
server_ssh_port=2222 # SSH port (must not be 22)
# Architecture support
host_machine_architecture=amd64 # amd64 or arm64
# Additional configuration
docker_default_data_path=/var/lib/docker
etc_hosts_json=[] # Custom host entriesTerraform Variables:
Extensive variable system in stage2/variables.tf covering:
- Network configuration (domains, IPs, ingress settings)
- Storage settings (Longhorn, MinIO capacity)
- Application configuration (GitLab, monitoring, auth)
- Resource limits and security settings
Terraform Module Dependencies:
flowchart TD
kubernetes[kubernetes]
nginx[nginx]
cert_manager[cert_manager_letsencrypt]
longhorn[longhorn_storage]
minio[minio_object_storage]
gitlab[gitlab_platform]
monitoring[monitoring]
logging[logging]
auth[auth]
kubecost[kubecost]
vpn[vpn]
argocd[argocd]
datadog[datadog]
reloader[reloader]
llmgateway[llmgateway]
kubernetes --> nginx
nginx --> cert_manager
cert_manager --> longhorn
cert_manager --> logging
cert_manager --> kubecost
cert_manager --> datadog
longhorn --> minio
minio --> gitlab
logging --> monitoring
cert_manager --> monitoring
monitoring --> auth
nginx --> auth
gitlab --> argocd
logging --> argocd
kubernetes --> vpn
kubernetes --> reloader
cert_manager --> llmgateway
longhorn --> llmgateway
Core Kubernetes Stack:
-
Cluster Options:
- kubeadm: Production-ready, full control, recommended for AMD64
- k3s: Lightweight, good for resource-constrained environments
- minikube: Development/testing, local development workflows
-
Networking: Cilium CNI for kubeadm, built-in for k3s/minikube
-
Storage: Longhorn distributed storage with configurable data paths
Each module has its own README with detailed configuration options:
| Module | Description |
|---|---|
| Kubernetes | CoreDNS, Prometheus CRDs |
| NGINX | Ingress Controller with MetalLB |
| Cert Manager | TLS certificate management with Let's Encrypt |
| Longhorn | Distributed block storage |
| MinIO | S3-compatible object storage |
| GitLab | CI/CD platform with registry and runners (AMD64 only) |
| Monitoring | Prometheus, Grafana, AlertManager, ElastAlert2 |
| Logging | ECK (Elasticsearch, Kibana, Filebeat) |
| Auth | OAuth2 proxy with Auth0 |
| ArgoCD | GitOps continuous deployment |
| Kubecost | Cost monitoring |
| VPN | Tailscale and WireGuard |
| Datadog | Datadog monitoring (optional) |
| Stakater Reloader | Auto-restart on Secret/ConfigMap changes |
| LLM Gateway | Unified LLM API (optional) |
Modules controlled by enable flags in Terraform variables:
| Module | Variable | Default |
|---|---|---|
| GitLab | host_machine_architecture == "amd64" |
Auto (AMD64 only) |
| Logging (ECK) | logging_module_enable |
true |
| Datadog | datadog_enable |
false |
| Tailscale | tailscale_enable |
false |
| WireGuard | wireguard_enable |
false |
| LLM Gateway | llmgateway_enable |
false |
See TROUBLESHOOTING.md for common issues and solutions.
- Quality Gates: Always run
task precommitbefore commits - Container Testing: Test Ansible/Terraform changes in container environment
- Version Control: Never commit
.envfiles or sensitive configuration - Branch Strategy: Feature branches for infrastructure changes
- Review Process: Infrastructure changes require careful review due to impact
See CONTRIBUTING.md for development guidelines.
See SECURITY.md for vulnerability reporting.
This project is licensed under the MIT License - see the LICENSE file for details.