基于K3s与Ansible构建家庭Kubernetes实验室:从零实践云原生
1. 项目概述:为什么我们需要一个家庭Kubernetes实验室?
如果你和我一样,对容器编排、微服务架构和云原生技术充满好奇,但又苦于没有合适的实验环境,那么“PhilipSchmid/k8s-home-lab”这个项目可能就是为你量身定做的。它不是一个简单的脚本集合,而是一个经过精心设计的、旨在将几台普通的家用电脑(甚至是树莓派)打造成一个功能完备、生产级可用的Kubernetes集群的完整解决方案。这个项目的核心价值在于,它为你提供了一个“沙盒”,让你可以在不产生任何云服务费用、不担心影响线上业务的前提下,去实践、去试错、去深入理解Kubernetes的每一个组件和概念。
我最初接触这个项目,是因为厌倦了在单节点的Minikube或Docker Desktop上做实验的局限性。那些环境虽然方便,但无法模拟多节点、高可用、网络策略、存储编排等真实生产场景。而直接在公有云上搭建集群,成本又是个问题。于是,一个运行在家庭网络中的、由真实物理机或虚拟机组成的Kubernetes集群,就成了最佳选择。PhilipSchmid的这个项目,正是解决了从零开始搭建这样一个集群的复杂性和一致性问题。它通过Ansible自动化了从操作系统初始化、Kubernetes组件安装、到网络插件、存储方案、Ingress控制器乃至监控日志全家桶的部署,让你可以像部署一个应用一样,一键部署你的整个实验室环境。
这个项目适合谁呢?我认为有三类人:第一类是正在学习Kubernetes的开发者或运维工程师,你需要一个可以随意“折腾”的环境来巩固知识;第二类是希望将家庭服务(如媒体服务器、智能家居中枢、个人网盘)容器化并统一管理的技术爱好者;第三类是小型团队或初创公司,希望在将应用迁移到生产云环境前,先在内部进行充分的架构验证和压力测试。无论你是哪一类,这个项目都能为你提供一个坚实、可靠的起点。
2. 核心架构与设计哲学解析
2.1 技术栈选型:为什么是K3s + Ansible?
打开项目的配置文件,你会发现它的核心是K3s,而不是原生的K8s。这是一个非常关键且明智的选择。K3s是Rancher Labs推出的一个轻量级Kubernetes发行版,它将所有Kubernetes组件打包成了一个不到100MB的二进制文件,并且默认使用containerd而非Docker作为容器运行时。对于资源受限的家庭环境(比如使用树莓派或旧笔记本),K3s的内存和CPU占用要低得多,启动速度也更快。同时,它内置了Traefik作为Ingress控制器,并默认使用sqlite3作为数据存储(也支持etcd),这大大简化了部署复杂度。
项目选择Ansible作为自动化工具,则是另一个亮点。Ansible基于SSH,无需在目标节点安装代理,采用声明式的YAML语法来描述状态,这与Kubernetes的理念一脉相承。通过Ansible,我们可以将集群的“期望状态”——包括系统配置、软件包、服务状态——定义在Playbook中。这意味着你的整个实验室环境都是可版本化、可重复构建的。今天你在一台机器上实验,明天换一台机器,或者集群被你不小心“玩坏”了,你都可以通过重新执行Ansible Playbook,快速恢复到已知的良好状态。这种“基础设施即代码”的实践,本身就是云原生运维的核心技能之一。
注意 :虽然项目默认使用K3s,但其架构设计是模块化的。如果你需要体验原生Kubernetes组件(如kube-apiserver、kube-controller-manager等),完全可以修改Ansible角色,替换为kubeadm或其他工具的部署方式。这体现了项目良好的可扩展性。
2.2 网络与存储:家庭环境下的特殊考量
在家庭网络中搭建Kubernetes,网络规划是第一个挑战。项目通常假设你的所有节点在同一个局域网段内。你需要为集群规划一个独立的Pod CIDR(如
10.244.0.0/16
)和Service CIDR(如
10.96.0.0/12
),确保它们不会与你的家庭路由器网段(通常是
192.168.1.0/24
)冲突。项目默认集成了Flannel作为CNI(容器网络接口)插件,它使用VXLAN后端,能很好地工作在大多数家庭网络环境中,实现跨节点的Pod通信。
存储是另一个重点。在生产环境中,你可能会有Ceph、Longhorn等分布式存储方案。但在家庭实验室,我们更需要考虑简单、可靠和低功耗。项目的一个常见实践是使用NFS(网络文件系统)。你可以将家中一台NAS或一台始终开机的旧电脑配置为NFS服务器,然后在Kubernetes中通过
nfs-subdir-external-provisioner
这个动态存储供应器,为集群提供按需创建的持久卷。这样,像Nextcloud(个人网盘)、Plex(媒体服务器)这类需要持久化数据的应用,就能稳定地运行了。当然,对于非持久化的数据或缓存,使用节点本地存储(
hostPath
或
local
卷)也是完全可行的,性能更好。
2.3 安全与维护:最小权限与自动化运维
家庭实验室不等于可以忽视安全。项目在设计中遵循了最小权限原则。例如,它会创建一个专用的非root用户来运行K3s服务,并通过Ansible Vault或环境变量来管理敏感信息(如证书、令牌),避免将密码明文写在代码里。此外,它默认会部署
metrics-server
,为HPA(水平Pod自动扩缩容)提供资源指标,虽然家庭应用可能不需要自动扩缩,但这为学习该功能提供了环境。
在维护方面,项目的Ansible Playbook被设计成“幂等”的,这意味着你可以安全地多次运行它,它只会将系统改变到Playbook中定义的状态,而不会引起错误或重复配置。这对于长期维护至关重要。你可以将Playbook放入Git仓库,任何配置变更都通过提交代码、执行Playbook来完成,实现了实验室配置的版本控制和变更追溯。
3. 从零开始:硬件准备与基础环境搭建
3.1 硬件选择与规划
搭建家庭K8s实验室,硬件门槛并不高。以下是几种常见的方案:
- 树莓派集群 :最具性价比和乐趣的方案。3台树莓派4B(4GB或8GB内存)即可组成一个高可用集群(1个master,2个worker)。你需要为每个树莓派配备可靠的MicroSD卡(建议A1/V30级别)、电源和散热片。网络方面,一个千兆交换机是必须的,能让节点间通信更流畅。
- 旧笔记本/台式机 :如果你有淘汰的电脑,这是零成本方案。即使只有双核CPU和4GB内存,也能运行一个轻量级的集群。建议安装无图形界面的Linux发行版以节省资源。
- 微型主机/工控机 :如Intel NUC、Minisforum等,性能更强,体积小巧,功耗控制得也不错,是追求性能和静音平衡的选择。
我的建议是,至少准备3个节点:1个控制平面节点(Master),2个工作节点(Worker)。这样你才能实践高可用、节点调度、Pod迁移等概念。为每个节点分配静态IP地址(通过路由器DHCP绑定或静态配置),并确保所有节点之间可以通过主机名互相解析。我通常会在路由器的DNS服务器中为每个节点添加记录,或者在每个节点的
/etc/hosts
文件中手动配置。
3.2 操作系统安装与基础配置
项目推荐使用Ubuntu Server LTS(如22.04)或Raspbian OS(针对树莓派)。安装时,选择最小化安装即可,无需桌面环境。
系统安装完成后,有几项基础配置必须通过Ansible或手动完成:
-
用户与SSH
:为Ansible创建一个专用管理用户(如
ansible),并配置SSH密钥登录,禁用密码登录以提升安全性。将你的公钥分发到所有节点。# 在控制机(你用来运行Ansible的电脑)上生成密钥对 ssh-keygen -t ed25519 -C "k8s-lab" # 将公钥复制到所有目标节点 ssh-copy-id ansible@<node-ip> -
系统优化
:关闭交换分区(
swapoff -a并注释掉/etc/fstab中的swap行),因为Kubernetes默认认为交换内存会影响性能和稳定性。加载必要的内核模块(如br_netfilter,overlay),并配置sysctl参数以启用IP转发和桥接流量。 -
容器运行时准备
:K3s默认使用containerd。我们需要确保其所需的内核模块和cgroup驱动配置正确。虽然K3s安装脚本会处理大部分,但提前配置好cgroup驱动为
systemd(修改/etc/containerd/config.toml)能与系统更好地集成。
这些步骤都可以编写到Ansible Playbook的“预处理”角色中,实现自动化。
3.3 Ansible控制机与环境准备
你的笔记本电脑或其中一台集群节点可以作为Ansible控制机。首先安装Ansible(
pip install ansible
或通过系统包管理器)。然后,为你的实验室项目创建一个工作目录,结构如下:
k8s-home-lab/
├── inventories/
│ └── production/
│ ├── hosts.ini # 节点清单文件
│ └── group_vars/ # 组变量
│ └── all.yml # 全局变量
├── site.yml # 主Playbook
└── roles/ # Ansible角色目录
├── common/ # 通用配置(用户、SSH、内核参数)
├── k3s/ # K3s安装与配置
├── network/ # CNI插件安装
└── storage/ # 存储配置(如NFS provisioner)
在
hosts.ini
中定义你的节点:
[master]
k8s-master ansible_host=192.168.1.101 ansible_user=ansible
[worker]
k8s-worker1 ansible_host=192.168.1.102 ansible_user=ansible
k8s-worker2 ansible_host=192.168.1.103 ansible_user=ansible
[k3s_cluster:children]
master
worker
在
group_vars/all.yml
中定义全局变量,如K3s版本、Pod CIDR等。
4. 核心部署流程详解与踩坑实录
4.1 使用Ansible部署K3s集群
有了基础环境,核心的部署工作就集中在执行Ansible Playbook上。项目提供的
site.yml
可能类似于以下结构:
---
- name: 部署Kubernetes家庭实验室
hosts: all
become: yes
roles:
- role: common
tags: common
- name: 部署K3s控制平面
hosts: master
become: yes
roles:
- role: k3s
k3s_mode: server
k3s_token: "{{ vault_k3s_token }}" # 从Ansible Vault读取
tags: master
- name: 获取K3s节点令牌
hosts: localhost
gather_facts: no
tasks:
- name: 从master获取token
slurp:
src: /var/lib/rancher/k3s/server/node-token
register: token_content
delegate_to: "{{ groups['master'][0] }}"
- set_fact:
k3s_worker_token: "{{ token_content.content | b64decode }}"
- name: 部署K3s工作节点
hosts: worker
become: yes
roles:
- role: k3s
k3s_mode: agent
k3s_server: "https://{{ groups['master'][0] }}:6443"
k3s_token: "{{ hostvars['localhost']['k3s_worker_token'] }}"
tags: worker
执行部署:
# 首先加密你的敏感变量文件
ansible-vault create group_vars/vault.yml
# 在其中定义 vault_k3s_token 等变量
# 运行整个Playbook
ansible-playbook -i inventories/production/hosts.ini site.yml --ask-vault-pass
# 或者分步运行
ansible-playbook -i inventories/production/hosts.ini site.yml --tags common
ansible-playbook -i inventories/production/hosts.ini site.yml --tags master
ansible-playbook -i inventories/production/hosts.ini site.yml --tags worker
实操心得 :
- 令牌管理 :K3s集群加入需要令牌。上述Playbook展示了如何动态地从Master获取令牌并传递给Worker,这比写死在变量里更安全。务必使用Ansible Vault保护这个令牌。
-
镜像拉取
:在国内环境,K3s拉取镜像可能会很慢。你可以在Master节点上预拉取镜像,或者修改K3s的启动参数,使用国内镜像仓库。一个更彻底的方法是在
/etc/rancher/k3s/registries.yaml中配置镜像仓库镜像。 -
部署验证
:部署完成后,在Master节点上运行
kubectl get nodes,应该能看到所有节点状态为Ready。如果Worker节点没有加入,首先检查Master节点的6443端口是否对Worker开放(防火墙规则),然后检查Worker节点的/var/log/syslog中K3s agent的日志。
4.2 部署CNI插件与核心插件
K3s默认安装Flannel,但有时你可能想换用Calico以获得更强大的网络策略功能。这时,你需要在K3s安装完成后,手动部署Calico的Manifest。
# 在Master节点上
# 首先,如果Flannel已运行,需要先卸载(谨慎操作)
kubectl delete -f /var/lib/rancher/k3s/server/manifests/k3s-flannel.yaml
# 然后部署Calico
curl https://raw.githubusercontent.com/projectcalico/calico/v3.26.1/manifests/tigera-operator.yaml -O
kubectl create -f tigera-operator.yaml
curl https://raw.githubusercontent.com/projectcalico/calico/v3.26.1/manifests/custom-resources.yaml -O
# 编辑custom-resources.yaml,将cidr改为你的Pod CIDR
kubectl create -f custom-resources.yaml
接下来,部署一些几乎必备的插件:
-
MetalLB
:为家庭实验室提供LoadBalancer类型的服务。在没有云供应商的环境里,Service的
LoadBalancer类型会一直处于Pending状态。MetalLB通过ARP或BGP协议,将外部IP地址“分配”给这些服务。
然后创建一个kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.13.10/config/manifests/metallb-native.yamlIPAddressPool,从你的家庭局域网中划分一段IP(例如192.168.1.200-192.168.1.220)给MetalLB管理。 -
NFS Subdir External Provisioner
:如前所述,为集群提供动态NFS存储。
helm repo add nfs-subdir-external-provisioner https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner/ helm install nfs-subdir-external-provisioner nfs-subdir-external-provisioner/nfs-subdir-external-provisioner \ --set nfs.server=192.168.1.99 \ --set nfs.path=/srv/nfs/k8s
4.3 部署Ingress与证书管理
K3s内置了Traefik作为Ingress控制器。你可以直接使用它,也可以替换为更流行的Nginx Ingress Controller。我更喜欢后者,因为其文档和社区资源更丰富。
# 使用Helm安装Nginx Ingress Controller
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm install ingress-nginx ingress-nginx/ingress-nginx \
--namespace ingress-nginx \
--create-namespace \
--set controller.service.type=LoadBalancer \
--set controller.service.annotations."metallb\.universe\.tf/address-pool"=default
安装后,通过
kubectl get svc -n ingress-nginx
查看MetalLB分配的外部IP(假设是
192.168.1.201
)。现在,你可以通过这个IP访问集群内的服务了。
为了让家庭服务拥有漂亮的HTTPS地址,我们需要证书。
cert-manager
是Kubernetes上自动化管理TLS证书的事实标准。虽然家庭环境没有公网域名,但我们可以使用自签名证书或内网DNS(如
home.lab
)配合
cert-manager
的CA颁发者。
# 安装cert-manager
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.2/cert-manager.yaml
# 创建一个自签名的CA Issuer
cat <<EOF | kubectl apply -f -
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: selfsigned-issuer
spec:
selfSigned: {}
EOF
然后,你就可以为你的Ingress资源申请证书了,所有HTTPS的繁琐流程都由
cert-manager
自动完成。
5. 应用部署实践:打造你的家庭服务生态
5.1 部署示例:家庭媒体中心与自动化工具
理论说再多,不如动手部署一个实际应用。让我们以部署“Jellyfin”(媒体服务器)和“Home Assistant”(智能家居)为例,串联起前面部署的所有组件。
首先,为这两个应用创建命名空间和持久化存储声明。
# storage/jellyfin-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: jellyfin-data
namespace: media
spec:
storageClassName: nfs-client # 对应之前部署的NFS provisioner
accessModes:
- ReadWriteMany
resources:
requests:
storage: 100Gi
然后,部署Jellyfin的Deployment和Service。
# apps/jellyfin-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: jellyfin
namespace: media
spec:
selector:
matchLabels:
app: jellyfin
template:
metadata:
labels:
app: jellyfin
spec:
containers:
- name: jellyfin
image: jellyfin/jellyfin:latest
ports:
- containerPort: 8096
volumeMounts:
- name: data
mountPath: /config
- name: media
mountPath: /media
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "2Gi"
cpu: "2000m"
volumes:
- name: data
persistentVolumeClaim:
claimName: jellyfin-data
- name: media
hostPath:
path: /mnt/nas/media # 假设你的媒体文件在NAS的这个路径
type: Directory
---
apiVersion: v1
kind: Service
metadata:
name: jellyfin
namespace: media
spec:
ports:
- port: 8096
targetPort: 8096
selector:
app: jellyfin
最后,创建Ingress,暴露服务到家庭网络,并配置HTTPS。
# networking/jellyfin-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: jellyfin
namespace: media
annotations:
cert-manager.io/cluster-issuer: selfsigned-issuer
nginx.ingress.kubernetes.io/proxy-body-size: "0" # 允许大文件上传
spec:
tls:
- hosts:
- jellyfin.home.lab
secretName: jellyfin-tls
rules:
- host: jellyfin.home.lab
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: jellyfin
port:
number: 8096
部署Home Assistant的过程类似,但它的配置更复杂,可能需要访问宿主机的设备(如USB Zigbee网关)。这时就需要在Pod配置中使用
hostNetwork: true
或挂载
/dev
目录,但这会带来安全风险,需要仔细权衡。
5.2 配置管理与GitOps初探
当你的家庭服务越来越多,手动
kubectl apply
会变得难以管理。这时可以引入GitOps工作流。Argo CD是一个流行的选择,它持续监控你的Git仓库,一旦仓库中的YAML文件发生变化,就自动同步到集群中。
-
在集群中安装Argo CD:
kubectl create namespace argocd && kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml -
获取初始管理员密码:
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d -
通过端口转发访问Argo CD UI:
kubectl port-forward svc/argocd-server -n argocd 8080:443 - 在UI中创建一个新的应用,指向存放你所有K8s YAML文件的Git仓库。
从此以后,你只需要向Git仓库提交代码,Argo CD就会自动帮你部署和更新应用。你的整个家庭实验室基础设施和应用状态,都通过Git仓库得到了版本化和自动化管理。
6. 监控、日志与日常运维
6.1 部署监控栈:Prometheus + Grafana
没有监控,集群就是在“裸奔”。Prometheus+Grafana是云原生监控的事实标准。
你可以使用
kube-prometheus-stack
这个Helm Chart一键部署整套监控系统,它包含了Prometheus、Grafana、Alertmanager以及一系列针对Kubernetes的监控规则和仪表盘。
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack -n monitoring --create-namespace
安装后,通过Ingress暴露Grafana服务。登录后,你就能看到集群节点、Pod、Service等资源的CPU、内存、网络流量等丰富指标。你还可以为Jellyfin、Home Assistant等自定义应用配置监控。
6.2 集中日志收集:Loki + Promtail
对于日志,EFK(Elasticsearch, Fluentd, Kibana)栈比较重。Grafana Labs推出的Loki是一个轻量级的日志聚合系统,它的设计理念是“只索引元数据,不索引日志内容”,因此资源消耗极低,非常适合家庭实验室。
helm repo add grafana https://grafana.github.io/helm-charts
helm install loki grafana/loki-stack -n logging --create-namespace --set promtail.enabled=true
promtail
是一个日志收集代理,它以DaemonSet形式运行在每个节点上,收集节点和容器日志,并发送给Loki。在Grafana中添加Loki数据源,你就可以在Grafana中像查询指标一样,使用LogQL查询语言来搜索和分析日志了。
6.3 日常运维命令与问题排查
家庭实验室也需要日常维护。以下是一些常用命令和排查思路:
-
查看集群状态
:
kubectl get nodes -o wide kubectl get pods -A kubectl get svc -A -
查看Pod详情和日志
:
kubectl describe pod <pod-name> -n <namespace> kubectl logs <pod-name> -n <namespace> -f # -f 表示实时跟踪 -
进入Pod调试
:
kubectl exec -it <pod-name> -n <namespace> -- /bin/sh -
常见问题排查
:
-
Pod一直Pending
:
kubectl describe pod查看事件,通常是资源不足、节点选择器不匹配或PVC无法绑定。 -
Pod启动失败(CrashLoopBackOff)
:
kubectl logs查看应用日志,通常是配置错误、依赖服务未就绪或镜像拉取失败。 -
Service无法访问
:检查Service的Selector是否与Pod标签匹配;检查Endpoint是否正常(
kubectl get endpoints <service-name>);如果通过Ingress访问,检查Ingress Controller的Pod日志。 -
节点NotReady
:SSH到该节点,检查
systemctl status k3s-agent(worker)或k3s(master)服务状态;检查网络连通性;检查/var/lib/rancher/k3s/agent/logs/下的日志。
-
Pod一直Pending
:
一个我踩过的坑
:有一次重启家庭路由器后,所有节点的IP地址变了,导致集群瘫痪。因为K3s证书和配置里写死了旧的IP。解决方案是:要么为节点配置静态IP(推荐),要么在安装K3s时使用
--tls-san
参数添加一个域名或浮动IP,这样证书会包含这个SAN,IP变化后只需更新节点的
/etc/rancher/k3s/k3s.yaml
中的server地址并重启服务即可。这个教训让我深刻理解了在自动化脚本中,对IP地址等“可变”因素进行抽象(使用主机名、DNS)的重要性。
更多推荐
所有评论(0)