安全 (security)

security 类不是一套独立的 kustomize 应用,而是给两个安全相关组件 —— cert-manager 和 kyverno —— 补充的监控与网络策略片段。这两个组件的主体 manifest 实际都放在 infrastructure 类的同名目录下,由各自的 ArgoCD Application 同步。

这个类里有什么

applications/security/ 下只有两份目录,各自只装监控相关的零散 manifest:

  • Name
    cert-manager/servicemonitor.yaml
    Description
    一个 ServiceMonitor,namespace 为 cert-manager,按 app.kubernetes.io/instance: cert-manager 选中 Service,从 tcp-prometheus-servicemonitor 端口的 /metrics 每 30s 抓一次指标。
  • Name
    kyverno/servicemonitor.yaml
    Description
    一个 ServiceMonitor,namespace 为 kyverno,覆盖 admission-controller / background-controller / cleanup-controller / reports-controller 四个组件的 metrics-port,每 30s 抓一次 /metrics
  • Name
    kyverno/networkpolicy-allow-prometheus.yaml
    Description
    NetworkPolicy allow-prometheus-scraping,放行来自 prometheus namespace 的 ingress 到 kyverno pod 的 TCP 8000 端口,让 Prometheus 能抓到上面的指标。

组件

两个组件的完整定义都在 infrastructure 目录,下面是从 manifest 直接核对的事实。

  • Name
    cert-manager
    Description
    集群的 TLS 证书签发器,路径 applications/infrastructure/cert-manager/,由 ArgoCD Application cert-manager(project infrastructure,标签 category: security,sync-wave 1)同步到 cert-manager namespace。控制器镜像 quay.io/jetstack/cert-manager-controller:v1.20.2,含 controller / cainjector / webhook 三个 Deployment,各带 VPA;webhook 有 PodDisruptionBudget。ClusterIssuer letsencrypt-prod 走 ACME(acme-v02.api.letsencrypt.org,邮箱 admin@yldm.tech),同时配置 HTTP-01(IngressClass traefik)和 Cloudflare DNS-01 两种 solver;DNS-01 覆盖 yldm.tech / yldm.ai / dunaifen.games / relaya.pro,其中 *.relaya.pro 通配证书只能靠 DNS-01 签发。Cloudflare API token、letsencrypt 私钥等都走 ExternalSecret 从 Vault 取。
  • Name
    kyverno
    Description
    策略引擎 / 准入控制器,路径 applications/infrastructure/kyverno/,由 ArgoCD Application kyverno(project platform,标签 category: policy,sync-wave 5)同步到 kyverno namespace。镜像 reg.kyverno.io/kyverno/kyverno:v1.18.1(initContainer kyvernopre 同版本),拆成 admission-controller / background-controller / cleanup-controller / reports-controller 四个 Deployment,各自独立 ServiceAccount + Role/RoleBinding。webhook 用到的 TLS 证书对(kyverno-tls-pair / kyverno-tls-ca)通过 ExternalSecret 提供。该 Application 开了 ServerSideApply,并对两份体积巨大的 CRD 单独用 compare-options: ServerSideDiff=false 注解避免 perma-OutOfSync(见仓库 #808)。

cert-manager 与 metallb 自签 issuer

除了对外签发 Let's Encrypt 证书,cert-manager 还给集群内部的 webhook 签自签证书。最典型的就是 metallb 的 validating webhook:它的证书不走 ACME,而是用一个 namespace 级的自签 Issuer。

相关对象都在 applications/networking/metallb/,不在 cert-manager 目录里:

# applications/networking/metallb/issuers-selfsigned.yaml
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
  name: selfsigned-issuer
  namespace: metallb-system
spec:
  selfSigned: {}

配套的 certificates-webhook.yaml 用这个 Issuer 给 webhook-server-cert 签一张有效期 1 年、提前 30 天续订的证书,dnsNames 为 webhook-service.metallb-system.svc 及其 .cluster.local 形式。这就是为什么 metallb 的 manifest 相比上游做了定制(--webhook-mode=enabled),它依赖 cert-manager 在场才能起 webhook。

kyverno 的保护策略

kyverno 目前装了两条 ClusterPolicy(applications/infrastructure/kyverno/policies/),都是 validationFailureAction: Enforcebackground: false,作用是阻止误删核心基础设施

ClusterPolicy保护对象拒绝的操作
protect-core-infrastructurekube-system / kube-public / kube-node-lease / default 这几个系统 namespace;kube-system 里的 kube-dns / metrics-server Service 与 coredns / metrics-server Deployment;kube-systemdefault 里的 default ServiceAccountDELETE
protect-kubernetes-servicedefault namespace 里的 kubernetes ServiceDELETE

也就是说,这两条策略只在删除这些关键对象时拦截并报错,对其它资源不做校验。

工作负载隔离:沙箱运行时决策

是否给集群引入 VM/沙箱级容器运行时(gVisor / Kata Containers)做过一次评估。结论:暂不引入,沿用默认 runc。

判断依据:不可信/多租户用户代码的现状是「可能跑,但很少(约 5%)」。为偶发负载维护一个常驻的沙箱运行时,固定运维成本(额外节点要求、guest 内核/镜像维护、两层排障、可观测性失配)摊不薄,ROI 不划算。这是私有 K3s homelab,不是公网多租户平台 —— 沙箱的核心价值(hypervisor 级、每 Pod 独立内核的隔离)在当前威胁模型下基本兑现不了。

将来真要做隔离时的选型结论(已调研,避免重复评估):

运行时适用场景理由
runc(默认)受信任负载(95%+)共享内核 + Pod 硬化即可,零额外运维
gVisor不可信但短生命周期 / CI冷启动比 Kata 快约 2 倍、不依赖 /dev/kvm、固定运维成本低 —— 偶发隔离首选
Kata不可信 + 长生命周期 + 能控节点真 hypervisor 隔离;本集群是裸金属/PVE,/dev/kvm 可得,但仅当三条件同时成立 ROI 才为正
临时 VM极低频、延迟不敏感的一次性任务零常驻平台复杂度,跑完即销毁

重新评估的触发线(出现任一条就重启此决策):不可信任务从偶发变成常规(如开始稳定接外部 PR / 客户代码);开始多租户、租户间共享内核;出现合规层面的强隔离要求。

Pod 安全硬化:现状与缺口

一次实地核对(2026-06):集群目前有「运维 guardrail + 网络隔离」层,但没有「Pod 安全硬化」基线。这与上面的隔离决策是配套的 —— 同样基于「为约 5% 的偶发风险折腾,对私有 homelab 不划算」而有意暂缓

已有:

  • Name
    网络隔离
    Description
    app / platform / game 各一套 NetworkPolicy(bootstrap/applications/*-network-policies.yaml),加各组件自带的 allow 规则。
  • Name
    删除保护 guardrail
    Description
    ValidatingAdmissionPolicy protect-system-resourcescluster/policies/)+ 两条 kyverno ClusterPolicy(见上节表),拦截误删核心对象。
  • Name
    项目级 RBAC
    Description
    AppProject app / gameclusterResourceWhitelist: [],禁止其下服务创建任何 cluster-scoped 资源。

缺口(即「Pod 硬化」层缺失):

  • Name
    无全局 seccomp 默认
    Description
    kubelet 未开 seccompDefault;仅 prometheus / registry / argocd-image-updater / reloader / deadman-switch 等个别 Deployment 在自己的 pod spec 里写了 seccompProfile,不是集群默认。
  • Name
    业务 namespace 无 PSA enforce
    Description
    app / platform / game / data 等 namespace 没有打 Pod Security Admission 的 enforce 标签,默认即 privileged 档(只有 metallb-system / velero 显式打了 privileged 例外,那是它们确实需要特权)。
  • Name
    kyverno 无 Pod 硬化策略
    Description
    现有两条 ClusterPolicy 都是「防删除」guardrail,没有 require-non-root / drop-capabilities / disallow-privileged 这类校验。

若改主意要补,按本仓库既有模式、运行时无关的硬化路径(任选):

  • 沿用 PSA 标签metallb-system / velero 已在用):给业务 namespace 加 pod-security.kubernetes.io/enforce: baseline + warn/audit: restricted,观察无破坏后再把 enforce 升到 restricted
  • kubelet seccompDefault: true:节点级改动,需重启 kubelet(节点在自己手里,可灰度)。
  • 加一条 kyverno ClusterPolicy:kyverno 已安装,用 validationFailureAction: Audit 先观察、再升 Enforce
  • 自己的服务在 pod spec 里补 securityContextrunAsNonRoot / capabilities.drop: [ALL] / allowPrivilegeEscalation: false / seccompProfile.type: RuntimeDefault

看 infrastructure 类全貌

评论