1. 项目概述:一个基于GitOps的轻量级K3s集群实践
如果你和我一样,厌倦了在Kubernetes集群上手动敲打kubectl apply,每次部署都提心吊胆,生怕哪条命令敲错导致服务中断,那么GitOps可能就是你要找的答案。今天分享的,是我用了一年多的一个生产级家庭/实验室K3s集群的完整GitOps实践。这个项目不是一个简单的Demo,而是一个真正在跑着几十个应用,包括媒体服务、监控告警、数据库和存储系统的实战环境。它的核心很简单:用代码定义集群的一切,用Git仓库作为唯一可信源,所有变更都通过提交代码来自动化完成。
整个体系建立在轻量级的K3s之上,这比完整的K8s发行版节省了超过一半的资源,却保留了绝大部分核心功能,非常适合边缘计算、家庭实验室或资源有限的开发环境。而驱动这一切自动化的是Flux CD v2,一个真正的GitOps原生工具。我的所有应用部署、配置更新、甚至密钥管理,都通过这个仓库里的YAML文件和Helm Chart来声明。当你看到git push之后,集群自动拉取变更并收敛到期望状态时,那种“一切尽在掌控”的感觉,是传统运维方式无法比拟的。
这个项目适合所有正在或打算使用Kubernetes的开发者、运维和爱好者,无论你是想搭建一个稳定的家庭媒体中心,还是需要一个可复现的开发测试环境,亦或是学习现代云原生运维理念,这里面的思路和具体实现都能给你提供直接的参考。接下来,我会拆解整个架构的设计思路、每一步的实操细节,以及我踩过坑后总结出的宝贵经验。
2. 核心架构与设计思路解析
2.1 为什么选择K3s + GitOps这个组合?
在决定技术栈时,我主要权衡了资源消耗、功能完整性和运维复杂度。完整的Kubernetes发行版(如kubeadm部署的集群)功能强大,但每个节点光是系统组件就要吃掉近1GB的内存,这对于我那些只有8GB或16GB内存的迷你PC节点来说太过奢侈。K3s的出现完美解决了这个问题,它将K8s组件打包成一个不到100MB的二进制文件,默认使用轻量级的SQLite代替etcd,并移除了一些非核心的云提供商驱动和过时的功能。实测下来,一个纯计算节点仅需300-400MB内存即可稳定运行K3s,这让我能把宝贵的资源留给实际的应用。
而选择GitOps,则是为了应对配置漂移和部署一致性的老大难问题。传统运维中,通过Shell脚本或手动操作进行的变更,很难被完整记录和回溯。GitOps将基础设施和应用都视为代码,所有对生产环境的变更都必须通过Git提交来发起。这带来了几个核心好处:版本控制(所有变更都有清晰的提交历史)、回滚能力(一键回退到任意历史版本)、审计追踪(谁在什么时候改了什么都一目了然)以及协作友好(可以通过Pull Request进行代码审查)。对于个人或小团队而言,这意味着你可以在出差时用手机审查并合并一个PR,集群就会自动完成部署,既安全又高效。
2.2 整体架构与组件选型考量
我的集群架构遵循了“轻量控制平面,高可用数据平面”的原则。整个集群由一个虚拟化的Master节点和四个物理的Worker节点组成。Master节点运行在TrueNAS Scale的虚拟机上,主要负责集群调度和API服务。由于K3s默认的高可用模式对资源有一定要求,我采用了更简单的单Master设计,但通过定期备份K3s的SQLite数据库和关键配置文件到NAS,来保证控制平面的可恢复性。四个Worker节点则承担了所有的应用负载和分布式存储任务。
在组件选型上,我紧紧围绕“声明式”和“自动化”两个核心:
- Flux CD v2:作为GitOps引擎。相比Argo CD,Flux与Helm的集成更原生,其多租户模型和依赖关系管理(如
Kustomization的依赖链)非常适合管理复杂的应用栈。它的“通知控制器”还能在部署完成后向Slack或Discord发送消息,非常实用。 - SOPS + Age:用于密钥管理。这是GitOps的一个关键挑战——如何安全地将密码、API密钥等敏感信息也放入Git仓库?SOPS(Secrets OPerationS)允许你使用非对称加密(我选用Age)加密YAML文件中的敏感部分,而解密密钥通过Flux安全地注入到集群中。这样,加密后的秘密文件可以直接提交到代码库。
- Rook Ceph:提供分布式块存储。为什么不用更简单的NFS或本地存储?因为我想实现存储的高可用和弹性扩展。当某个节点宕机时,运行在其上的Pod连同它的数据(如果存储在Ceph RBD上)可以快速在其它节点重建,这对于数据库类应用至关重要。Rook将Ceph的部署和管理也Kubernetes化了,完美契合GitOps哲学。
- Metallb:在裸金属集群中提供LoadBalancer服务。家庭环境没有云厂商的负载均衡器,Metallb通过ARP或BGP协议,让特定节点对外宣告一个虚拟IP,从而将外部流量引入集群,这是暴露Ingress Controller等服务的基础。
- Renovate:自动化依赖更新。它像一个智能机器人,持续扫描仓库中的Helm Chart版本、容器镜像标签等,并自动创建Pull Request来提醒或直接更新。这让我能轻松跟上安全补丁和功能更新,是保持集群健康的关键一环。
这个选型组合确保了从基础设施、应用到密钥管理的全栈声明式自动化,形成了一个闭环的自治系统。
3. 基础设施与集群初始化详解
3.1 节点准备与操作系统抉择
我的四个物理节点是Minisforum UN100C和BMax B4 Plus这类迷你PC,它们功耗低、体积小,但性能足够(通常为Intel N系列处理器+16GB RAM+500GB NVMe)。最初我使用Ubuntu Server,但很快发现需要大量Shell脚本来维护节点的一致性配置(如内核参数、软件包、防火墙规则)。这引发了“配置漂移”问题。
最终,我转向了NixOS。这是一个声明式的Linux发行版,其整个系统配置(从内核模块到用户服务)都由一个单一的configuration.nix文件定义。这意味着我的所有节点配置都作为代码,存放在另一个独立的Git仓库中。当需要修改所有节点的SSH策略或时区时,我只需修改一次代码,然后推送到每个节点执行重建即可。这为底层基础设施也带来了GitOps的好处,实现了从底层OS到上层K8s应用的完全代码化管理。节点的主机名(如k3s-node-01)和角色标签都在NixOS配置中定义,确保了集群节点身份的清晰和一致。
3.2 K3s集群的安装与关键配置
K3s的安装极其简单,一行命令即可。但为了适配GitOps和生产需求,有几个关键配置项需要特别注意:
# 在Master节点上 curl -sfL https://get.k3s.io | INSTALL_K3S_VERSION=v1.35.0+k3s1 \ sh -s - server \ --cluster-init \ --disable traefik \ --disable servicelb \ --write-kubeconfig-mode 644 \ --node-taint CriticalAddonsOnly=true:NoExecute # 在Worker节点上 curl -sfL https://get.k3s.io | K3S_URL=https://<MASTER_IP>:6443 \ K3S_TOKEN=<从Master节点/var/lib/rancher/k3s/server/node-token获取> \ sh -s -关键参数解析:
--disable traefik:K3s默认安装Traefik作为Ingress Controller。但我更习惯使用功能更丰富的nginx-ingress-controller,所以选择禁用默认的,后续通过GitOps单独部署。--disable servicelb:这是K3s自带的简单负载均衡器,功能不如Metallb强大,同样禁用,由Metallb替代。--write-kubeconfig-mode 644:这将kubeconfig文件的权限设置为对用户可读,方便后续工具(如Flux CLI)直接使用。--node-taint CriticalAddonsOnly=true:NoExecute:给Master节点打上污点,防止普通Pod调度到Master上,确保控制平面的稳定性。像Flux、Rook Operator这类关键系统组件,我会通过tolerations显式容忍这个污点,让它们运行在Master上。
注意:生产环境中,建议将
K3S_TOKEN存储在密码管理器或通过安全的自动化流程传递,避免在脚本中硬编码。我的做法是在NixOS配置的部署阶段,通过临时环境变量注入。
安装完成后,首要任务是在本地配置好kubectl,并验证集群状态:kubectl get nodes -o wide。确保所有节点状态都是Ready,并且具有正确的角色标签。
4. GitOps核心引擎:Flux CD v2的部署与配置
4.1 Flux Bootstrap:搭建GitOps的基石
Flux的安装遵循一个“自举”过程:你首先在本地用Flux CLI命令,让它在你的集群里安装自己,并配置好与你的Git仓库的连接。这个过程一旦完成,后续对Flux自身的所有升级和配置,也将通过Git仓库来管理,实现了完美的“自托管”。
# 1. 安装Flux CLI (以macOS为例) brew install fluxcd/tap/flux # 2. 准备Git仓库 # 确保你有一个空的Git仓库(如本项目的GitHub仓库),并克隆到本地。 # 3. 执行Bootstrap flux bootstrap github \ --owner=WRMilling \ --repository=k3s-gitops \ --branch=master \ --path=./clusters/my-cluster \ # 配置文件在仓库中的路径 --personal这条命令会做以下几件关键事情:
- 在集群中创建
flux-system命名空间。 - 部署Flux的控制器(source, kustomize, helm, notification等)。
- 在你的Git仓库指定路径下,生成一个包含Flux自身配置的
kustomization.yaml文件。 - 配置一个
GitRepository资源,指向你的仓库,并设置轮询间隔(默认1分钟)。 - 配置一个
Kustomization资源,指向./clusters/my-cluster路径,告诉Flux从这个目录开始协调集群状态。
执行成功后,你可以运行flux get sources git和flux get kustomizations来查看状态。当一切正常时,Flux就成为了集群的“自动驾驶仪”,开始监视你的Git仓库。
4.2 组织结构:如何优雅地管理上百个YAML文件
随着应用增多,YAML文件的管理会变得混乱。我采用了基于命名空间(Namespace)和功能分类的目录结构,这也是k8s-at-home社区推崇的模式:
k3s-gitops/ ├── clusters/ │ └── my-cluster/ # 集群专属配置入口 │ ├── flux-system/ # Flux自身的配置(由bootstrap生成) │ └── kustomization.yaml # 根Kustomization,引用下面各个命名空间 ├── infrastructure/ # 跨命名空间的基础设施组件 │ ├── metallb/ │ ├── nginx-ingress/ │ └── kustomization.yaml ├── apps/ # 所有业务应用 │ ├── default/ # default命名空间下的应用 │ ├── media/ # media命名空间(Plex, Sonarr等) │ ├── monitoring/ # monitoring命名空间(Prometheus, Grafana) │ └── .../ └── system/ # 集群系统组件 ├── cert-manager/ ├── rook-ceph/ └── .../每个命名空间目录下,又按应用进一步划分。对于每个应用,我优先使用HelmRelease来管理,而不是裸的YAML。Helm Chart提供了版本化、参数化和依赖管理的能力。我的典型应用结构如下:
apps/media/plex/ ├── helmrelease.yaml # 定义HelmRelease资源,指定Chart源、版本和Values ├── kustomization.yaml # 将此目录声明为一个Kustomization └── values/ # 可选的、覆盖的values配置 └── config.yaml根目录的kustomization.yaml则像一份索引,通过resources字段引用所有子目录:
# clusters/my-cluster/kustomization.yaml apiVersion: kustomize.toolkit.fluxcd.io/v1 kind: Kustomization metadata: name: my-cluster namespace: flux-system spec: interval: 10m path: ./ prune: true sourceRef: kind: GitRepository name: flux-system validation: client dependsOn: # 定义依赖顺序,确保基础设施先就绪 - name: infrastructure resources: - ../../infrastructure - ../../system - ../../apps这种结构清晰、模块化,并且通过dependsOn确保了部署顺序,例如,nginx-ingress必须在metallb之后部署,因为Ingress Controller需要一个LoadBalancer IP。
5. 关键组件部署与集成实战
5.1 存储基石:Rook Ceph分布式存储部署
在裸金属集群上提供动态持久化存储,Rook Ceph是首选。部署它需要一些前置条件:每个存储节点需要至少一块未分区的裸磁盘(或分区),并且内核需要启用必要的模块(如rbd)。在我的NixOS配置中,我确保了这些模块的加载。
部署过程本身通过GitOps完成。我创建了一个HelmRepository源指向Rook的官方Chart仓库,然后定义一个HelmRelease来安装Rook Operator。Operator安装成功后,再通过一个自定义的CephCluster资源文件来声明Ceph集群的期望状态:
# system/rook-ceph/ceph-cluster.yaml apiVersion: ceph.rook.io/v1 kind: CephCluster metadata: name: rook-ceph namespace: rook-ceph spec: dataDirHostPath: /var/lib/rook cephVersion: image: ceph/ceph:v18.2.0 mon: count: 3 # 建议为奇数,我使用3个Monitor实现高可用 allowMultiplePerNode: false mgr: count: 2 storage: storageClassDeviceSets: - name: set1 count: 3 # 我有3个节点贡献存储,每个节点一块盘 portable: false tuneDeviceClass: true volumeClaimTemplates: - metadata: name: data spec: resources: requests: storage: 400Gi # 每个节点上磁盘的容量 storageClassName: local-path # 临时使用,待Ceph就绪后会被替换 volumeMode: Block accessModes: - ReadWriteOnce实操心得:在Ceph集群初始化期间(特别是PGs正在创建时),IO性能会非常差,甚至导致部署Pod超时失败。我的经验是,先部署Ceph集群,等待其状态完全健康(
kubectl -n rook-ceph get cephcluster显示HEALTH_OK),再部署依赖存储的其他应用。可以通过在应用的Kustomization中设置dependsOn来实现这个顺序。
Ceph集群就绪后,它会自动创建几个StorageClass,如rook-ceph-block(RBD块存储)和rook-cephfs(CephFS文件存储)。之后,任何PVC(持久卷声明)只要指定storageClassName: rook-ceph-block,就能自动动态创建持久卷。
5.2 网络与入口:Metallb + Nginx Ingress的配置
在家庭网络环境中,没有云服务商分配的公网IP或负载均衡器。Metallb通过在二层(ARP)或三层(BGP)网络宣告IP,实现了LoadBalancer服务类型。
我采用Layer 2模式,因为它配置简单,无需额外的路由器支持。配置的核心是定义一个IP地址池,并分配给Metallb。
# infrastructure/metallb/config.yaml apiVersion: metallb.io/v1beta1 kind: IPAddressPool metadata: name: default-pool namespace: metallb-system spec: addresses: - 192.168.1.200-192.168.1.220 # 从家庭路由器DHCP范围外划定的一个IP段 --- apiVersion: metallb.io/v1beta1 kind: L2Advertisement metadata: name: default-l2-advertisement namespace: metallb-system spec: ipAddressPools: - default-pool部署Nginx Ingress Controller时,其Service的类型设为LoadBalancer。Flux协调后,Metallb会从default-pool中分配一个IP(如192.168.1.200)给这个Service。之后,我只需要将家庭路由器的端口(如80, 443)转发到这个IP,外部流量就能到达Ingress Controller。
Ingress Controller再根据Ingress资源中定义的规则(主机名和路径),将流量路由到后端对应的Service。这样,我内部的所有Web服务都可以通过不同的子域名(如grafana.home.lab,plex.home.lab)来访问。
5.3 密钥管理:使用SOPS与Age实现安全GitOps
将密码直接写在Git仓库里是绝对的安全禁忌。SOPS配合Age解决了这个问题。Age是一个简单、现代的加密工具,我使用它生成一个密钥对。
生成Age密钥对:
age-keygen -o age.agekey # 输出公钥,将其配置到Flux中 cat age.agekey | grep \"public\" | cut -d\" \" -f4在Flux中配置解密密钥:创建一个Kubernetes Secret,包含Age私钥。注意,这个Secret本身需要通过安全的方式(例如,在Bootstrap时通过
--secret参数)注入到集群,或者首次手动创建。kubectl create secret generic sops-age \ --namespace=flux-system \ --from-file=age.agekey在Flux的Kustomization中启用解密:
apiVersion: kustomize.toolkit.fluxcd.io/v1 kind: Kustomization metadata: name: apps namespace: flux-system spec: decryption: provider: sops secretRef: name: sops-age加密一个Secret文件:
# 假设有一个明文secret.yaml sops --encrypt --age <你的公钥> --encrypted-regex '^(data|stringData)$' secret.yaml > secret.enc.yaml加密后的
secret.enc.yaml文件可以安全地提交到Git仓库。Flux在同步时,会自动使用集群中的私钥对其进行解密,然后将明文Secret应用到集群。
这种方式实现了“密钥的密钥”管理,主密钥(Age私钥)被安全保管在集群中,而具体的应用密钥则以加密形式存于Git,兼顾了安全性和GitOps的自动化流程。
6. 应用部署示例:以媒体栈和监控栈为例
6.1 部署完整的媒体服务栈(Plex, Sonarr, Radarr)
媒体服务通常需要持久化存储、定期扫描和外部访问。我将其全部部署在media命名空间下。以Plex为例,我使用k8s-at-home社区维护的Helm Chart,因为它针对Kubernetes环境做了很多优化。
# apps/media/plex/helmrelease.yaml apiVersion: helm.toolkit.fluxcd.io/v2beta1 kind: HelmRelease metadata: name: plex namespace: media spec: interval: 5m chart: spec: chart: plex version: 29.0.0 # 始终指定版本,避免意外升级 sourceRef: kind: HelmRepository name: k8s-at-home namespace: flux-system valuesFrom: - kind: ConfigMap name: plex-common-values # 一些通用配置,如时区 valuesKey: values.yaml - kind: Secret name: plex-secret # 加密的Plex Claim Token等 values: image: repository: plexinc/pms-docker tag: latest persistence: config: enabled: true type: pvc existingClaim: "" # 使用动态 provisioning storageClass: rook-ceph-block accessMode: ReadWriteOnce size: 10Gi transcode: enabled: true type: emptyDir # 转码临时文件,无需持久化 ingress: enabled: true hosts: - host: plex.home.lab paths: - path: / pathType: Prefix tls: [] resources: limits: memory: 4Gi requests: memory: 2Gi对于Sonarr、Radarr等应用,模式类似:通过HelmRelease定义,挂载Ceph RBD存储卷来保存配置和媒体库元数据,并通过Ingress暴露Web界面。它们之间通过内部DNS(<service-name>.<namespace>.svc.cluster.local)进行通信,例如Sonarr下载完成后,可以通过http://plex.media.svc.cluster.local:32400的API通知Plex刷新库。
6.2 部署监控告警栈(Prometheus, Grafana, Alertmanager)
一个可观测的集群是健康的集群。我使用Prometheus Operator(通过prometheus-community的Helm Chart)来管理整个监控生态。
- 部署Prometheus Operator:这个Operator会帮我们创建和管理Prometheus、Alertmanager实例以及相关的ServiceMonitor(用于自动发现并监控K8s服务)。
- 配置数据持久化:Prometheus的时序数据量很大,必须持久化。我为其PVC指定
storageClassName: rook-ceph-block,并分配足够的容量(如200Gi)。 - 服务发现:为需要监控的服务(如Nginx Ingress, Ceph集群,甚至自定义应用)创建
ServiceMonitor资源。Prometheus Operator会自动配置Prometheus去抓取这些目标。 - 部署Grafana:同样通过Helm Chart部署,将其数据源配置为指向Prometheus服务(
http://prometheus-operated.monitoring.svc.cluster.local:9090)。Grafana的仪表盘配置可以通过ConfigMap挂载,或者使用其Provisioning功能从Git仓库加载,这也实现了仪表盘的代码化管理。 - 配置告警规则:在Prometheus中定义告警规则(
PrometheusRule资源),例如节点内存使用率超过90%持续5分钟。当触发告警时,Prometheus会将告警发送给Alertmanager。 - Alertmanager路由与通知:配置Alertmanager,将不同严重等级的告警路由到不同的接收器。我将其配置为通过Webhook发送通知到我的Slack频道,这样我就能在手机上及时收到集群异常告警。
这套监控栈部署完成后,我就能在一个统一的Grafana界面上查看集群的CPU、内存、存储使用情况,网络流量,以及所有应用的运行状态,实现了对集群的“全景洞察”。
7. 高级主题:自动化、备份与灾难恢复
7.1 利用Renovate实现自动化依赖更新
手动跟踪几十个Helm Chart和容器镜像的更新是项繁琐且易出错的工作。Renovate可以集成到GitHub仓库,自动扫描依赖并创建更新PR。
我在仓库根目录放置了一个renovate.json配置文件:
{ "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ "config:recommended" ], "platformAutomerge": true, "automergeType": "pr", "prHourlyLimit": 2, "dependencyDashboard": true, "labels": ["dependencies"], "packageRules": [ { "matchUpdateTypes": ["minor", "patch"], "groupName": "all non-major dependencies", "automerge": true }, { "matchPackageNames": ["k3s.io/k3s"], "matchUpdateTypes": ["major"], "enabled": false // 主版本K3s升级需谨慎,手动处理 } ] }这个配置意味着:Renovate会每天扫描仓库;发现非主版本(minor, patch)更新时,会自动创建PR并合并;对于K3s的主版本更新,则禁用自动更新,需要我手动审查。通过这种方式,我的集群总能及时获得安全补丁和功能更新,而无需我时刻关注版本动态。
7.2 集群备份与灾难恢复策略
即使有了GitOps,备份依然至关重要。Git仓库备份了“期望状态”,但一些动态数据(如存储在Ceph里的媒体文件、数据库内容)和Kubernetes集群自身的状态(如CRD、非命名空间资源)也需要备份。我采用Velero来完成这个任务。
Velero的部署同样通过GitOps完成。它需要配置一个对象存储后端(我使用TrueNAS上的S3兼容服务)。定期备份任务通过一个Schedule资源来定义:
apiVersion: velero.io/v1 kind: Schedule metadata: name: daily-backup namespace: velero spec: schedule: \"0 2 * * *\" # 每天凌晨2点 template: includedNamespaces: - media - monitoring - default storageLocation: default ttl: 720h # 保留30天这个备份策略会每天备份指定命名空间的所有资源(包括PVC,但需要存储插件支持CSI快照)。当灾难发生时(例如整个集群硬件故障),恢复流程是:
- 在新的硬件上重新安装K3s和Flux。
- 使用相同的Bootstrap命令,指向同一个Git仓库。Flux会重新构建出基础架构(Ceph, Metallb等)。
- 从Velero的备份中,恢复应用和数据。由于PVC会被恢复,并指向重新部署的Ceph存储,数据得以保留。
这种“GitOps定义环境,Velero备份数据”的组合,提供了从代码到数据的完整可恢复性,将恢复时间目标(RTO)从数天缩短到数小时。
8. 日常运维、问题排查与经验总结
8.1 常用Flux命令与工作流
日常工作中,我几乎不需要直接登录服务器节点。所有操作都在本地和Git仓库中完成。
- 查看同步状态:
flux get all -A查看所有资源状态。flux get kustomizations -A查看各个Kustomization的同步状态和健康情况。 - 手动触发同步:有时不想等轮询间隔,可以执行
flux reconcile kustomization <name> -n <namespace>。 - 暂停与恢复同步:在进行重大变更或调试时,可以暂停某个应用的同步:
flux suspend kustomization <name>。完成后用flux resume kustomization <name>恢复。 - 查看日志:
kubectl -n flux-system logs deployment/source-controller查看源码拉取日志。kubectl -n flux-system logs deployment/kustomize-controller查看协调和部署日志。 - 标准工作流:
- 在本地修改某个应用的
helmrelease.yaml,比如升级镜像版本。 git add . && git commit -m \"feat: upgrade app-x to v2.1.0\"git push origin master- 等待约1分钟(Flux轮询间隔),或手动触发同步。
- 使用
flux get helmrelease app-x -n <namespace>观察升级进度和状态。
- 在本地修改某个应用的
8.2 常见问题与排查技巧
即使自动化程度很高,依然会遇到问题。以下是我总结的排查清单:
| 问题现象 | 可能原因 | 排查步骤 |
|---|---|---|
Flux同步失败,提示reconciliation failed | 1. Git仓库无法访问(网络、密钥问题)。 2. YAML语法错误。 3. Helm Chart版本不存在或拉取失败。 4. 资源依赖未就绪(如StorageClass不存在)。 | 1.flux get sources git检查仓库状态。2. flux reconcile source git flux-system手动重试。3. 检查失败Kustomization/HelmRelease的详细事件: kubectl describe kustomization <name> -n flux-system。4. 查看对应控制器的日志。 |
Pod处于Pending状态 | 1. 资源不足(CPU/内存)。 2. 节点Selector或亲和性不匹配。 3. 污点(Taint)不容忍。 4. PVC无法绑定(StorageClass问题或容量不足)。 | 1.kubectl describe pod <pod-name>查看Events部分,通常有明确提示。2. kubectl get nodes查看节点资源分配情况。3. kubectl get pvc查看PVC是否处于Bound状态。 |
Pod处于CrashLoopBackOff状态 | 1. 应用启动命令或参数错误。 2. 配置文件错误。 3. 依赖服务(如数据库)未就绪。 4. 权限问题(SecurityContext)。 | 1.kubectl logs <pod-name> --previous查看上一次崩溃的日志。2. kubectl describe pod <pod-name>检查容器配置和环境变量。3. 检查 livenessProbe或readinessProbe配置是否过于严格。 |
| Ingress无法访问 | 1. Ingress Controller Pod未运行。 2. Service类型或端口错误。 3. Ingress资源中的主机名或路径规则错误。 4. 防火墙或网络策略(NetworkPolicy)阻止。 | 1.kubectl -n ingress-nginx get pods确认Ingress Controller状态。2. kubectl get svc -n ingress-nginx确认Service有外部IP(由Metallb分配)。3. kubectl describe ingress <ingress-name>查看Ingress状态和事件。4. 在集群内用 curl测试Service是否可达,以排除外部网络问题。 |
Ceph集群健康状态不是HEALTH_OK | 1. OSD(存储守护进程)磁盘故障或未启动。 2. Monitor节点间网络问题。 3. PG(归置组)状态异常。 | 1.kubectl -n rook-ceph exec -it deploy/rook-ceph-tools -- ceph status查看详细状态。2. kubectl -n rook-ceph get pods查看所有Ceph Pod状态。3. 检查 ceph osd tree查看OSD分布和状态。 |
8.3 个人实践中的关键经验
- 循序渐进,分阶段实施:不要试图一次性将所有应用都迁移到GitOps。先从一两个非核心应用开始,熟悉Flux的工作流程和调试方法。然后逐步迁移基础设施组件(如Ingress、存储),最后迁移核心应用。
- 严格区分环境:即使在家庭实验室,也建议使用不同的分支或目录来区分
dev和prod环境。可以通过Flux的Kustomization覆盖来实现配置差异化。这能避免在测试时影响生产服务。 - 善用
kustomize的补丁:对于需要根据不同环境进行微调的资源(如副本数、资源请求量),不要直接修改原始的HelmRelease,而是使用Kustomize的patchesStrategicMerge或patchesJson6902在环境目录中进行覆盖。这保持了基础配置的干净。 - 监控你的GitOps流程本身:为
flux-system命名空间也配置ServiceMonitor,让Prometheus监控Flux控制器的各项指标(如同步延迟、错误次数)。这能让你在GitOps引擎自身出现问题时及时得到告警。 - 文档化你的决策:在仓库的
README或专门的docs目录下,记录你为什么选择某个特定的Chart版本、某个配置参数的含义、以及遇到并解决过的复杂问题。这对未来的自己或协作的伙伴是无价之宝。
经过一年多的运行,这套基于K3s和Flux的GitOps体系已经成为了我基础设施中无声且可靠的基础。它极大地减少了日常维护的琐碎工作,将我的精力从“如何部署和修改”解放出来,更多地投入到“部署什么和优化什么”上。当所有的配置都化为代码,所有的变更都有迹可循时,你对整个系统的掌控感和信心会达到一个全新的高度。